Merge branch 'puter/issue-9-gitea-smoke' into puter/issue-14-integration

# Conflicts:
#	packages/primitives/package.json
This commit is contained in:
-Puter
2026-07-24 02:37:09 +05:30
10 changed files with 1454 additions and 2 deletions

View File

@@ -5,10 +5,16 @@
"": {
"name": "code",
"devDependencies": {
"@code/backend": "workspace:*",
"@code/config": "workspace:*",
"@code/env": "workspace:*",
"@code/primitives": "workspace:*",
"@flue/sdk": "1.0.0-beta.9",
"@types/bun": "catalog:",
"@types/node": "^22.13.14",
"convex": "catalog:",
"convex-test": "catalog:",
"effect": "catalog:",
"oxfmt": "latest",
"oxlint": "latest",
"rolldown": "1.1.4",
@@ -112,6 +118,7 @@
"@code/auth": "workspace:*",
"@code/backend": "workspace:*",
"@code/env": "workspace:*",
"@code/primitives": "workspace:*",
"@code/ui": "workspace:*",
"@flue/react": "1.0.0-beta.9",
"@flue/sdk": "1.0.0-beta.9",

46
docs/SMOKE.md Normal file
View File

@@ -0,0 +1,46 @@
# Zopu Integration Smoke
The smoke harness is a repeatable integration lane for the thin Zopu MVP. It probes the local web app, authenticated Convex control plane, Flue `project-manager` route, AgentOS daemon/workspace contract, and the CPA OpenAI-compatible gateway before it creates any project issue.
The harness does not edit CPA configuration, restart services, print bearer tokens/API keys, or run parallel worker calls. `minimax-m3` is the serial worker-loop model; `glm-5.2` performs the plan and the independent review.
## Setup
Use a disposable/test project that already has a connected repository source and the issue-scoped artifact set. Export a Better Auth/Convex access token for the signed-in user, the local daemon id, and the CPA key/base URL. The CPA URL must include its OpenAI-compatible `/v1` path.
```bash
export ZOPU_SMOKE_ACCESS_TOKEN='...'
export ZOPU_SMOKE_PROJECT_ID='...'
export ZOPU_SMOKE_DAEMON_ID='local-macbook'
export ZOPU_SMOKE_CPA_BASE_URL='https://ai.example.invalid/v1'
export ZOPU_SMOKE_CPA_API_KEY='...'
```
The harness reuses `AGENT_MODEL_*`, `CONVEX_URL`, `DAEMON_ID`, `SITE_URL`, and `VITE_FLUE_URL` when their `ZOPU_SMOKE_*` equivalents are absent. It never rewrites those values. Override the tiny feature request with `ZOPU_SMOKE_FEATURE_REQUEST` when the disposable project needs a more specific target.
## Commands
Run capability probes only:
```bash
bun run smoke:zopu
```
Drive one issue through the project loop and review it:
```bash
bun run smoke:zopu --run --report /tmp/zopu-smoke.json
```
The command prints one of these stable markers:
- `ZOPU_SMOKE_PREFLIGHT_PASSED` means all probes passed and no mutation was requested.
- `ZOPU_SMOKE_COMPLETED` means the issue was created, the worker reported completion, the durable issue reached `completed`, and GLM approved the result.
- `ZOPU_SMOKE_CONTRACT_BLOCKED` means a required capability or configuration is missing; the JSON report names the exact check and next action.
- `ZOPU_SMOKE_FAILED` means runtime execution started but did not satisfy the worker/reviewer contract.
Exit codes are `0` for a passing preflight or completed run, `2` for a contract block, and `1` for a runtime failure. Reports contain only endpoint labels, statuses, counts, ids, and sanitized failure text; model transcripts and credentials are intentionally omitted.
## Current lane boundary
The smoke intentionally stops before mutation when the project source, issue-scoped AgentOS artifacts, or online daemon contract is absent. Those checks are the integration boundary for the project-loop and daemon lanes; do not paper over them in this harness or make a live gateway change to satisfy a failed probe.

View File

@@ -40,6 +40,7 @@
"check": "ultracite check",
"lint": "vp lint",
"format": "vp fmt",
"smoke:zopu": "bun scripts/zopu-smoke.ts",
"staged": "vp staged",
"hooks:setup": "vp config",
"dev:native": "vp run --filter native dev",
@@ -62,16 +63,22 @@
},
"dependencies": {},
"devDependencies": {
"@code/backend": "workspace:*",
"@code/config": "workspace:*",
"@code/env": "workspace:*",
"@code/primitives": "workspace:*",
"@flue/sdk": "1.0.0-beta.9",
"@types/bun": "catalog:",
"@types/node": "^22.13.14",
"convex": "catalog:",
"convex-test": "catalog:",
"effect": "catalog:",
"oxfmt": "latest",
"oxlint": "latest",
"rolldown": "1.1.4",
"typescript": "catalog:",
"ultracite": "7.9.3",
"vite-plus": "0.2.2",
"convex-test": "catalog:",
"vitest": "catalog:"
},
"overrides": {

View File

@@ -8,8 +8,12 @@
"./convex": "./src/convex.ts",
"./native": "./src/native.ts",
"./server": "./src/server.ts",
"./smoke": "./src/smoke.ts",
"./web": "./src/web.ts"
},
"scripts": {
"check-types": "tsc --noEmit"
},
"dependencies": {
"@t3-oss/env-core": "^0.13.11",
"zod": "catalog:"

46
packages/env/src/smoke.ts vendored Normal file
View File

@@ -0,0 +1,46 @@
import { z } from "zod";
const smokeEnvSchema = z.object({
accessToken: z.string().min(1),
agentModelBaseUrl: z.url().optional(),
agentModelName: z.string().min(1).optional(),
agentModelProvider: z.string().min(1).optional(),
convexUrl: z.url(),
cpaApiKey: z.string().min(1),
cpaBaseUrl: z.url(),
daemonId: z.string().min(1),
featureRequest: z.string().min(10),
flueUrl: z.url(),
projectId: z.string().min(1).optional(),
webUrl: z.url(),
});
export type SmokeEnv = z.infer<typeof smokeEnvSchema>;
export const parseSmokeEnv = (
runtimeEnv: Record<string, string | undefined>
): SmokeEnv =>
smokeEnvSchema.parse({
accessToken: runtimeEnv.ZOPU_SMOKE_ACCESS_TOKEN,
agentModelBaseUrl: runtimeEnv.AGENT_MODEL_BASE_URL,
agentModelName: runtimeEnv.AGENT_MODEL_NAME,
agentModelProvider: runtimeEnv.AGENT_MODEL_PROVIDER,
convexUrl: runtimeEnv.ZOPU_SMOKE_CONVEX_URL ?? runtimeEnv.CONVEX_URL,
cpaApiKey:
runtimeEnv.ZOPU_SMOKE_CPA_API_KEY ?? runtimeEnv.AGENT_MODEL_API_KEY,
cpaBaseUrl:
runtimeEnv.ZOPU_SMOKE_CPA_BASE_URL ?? runtimeEnv.AGENT_MODEL_BASE_URL,
daemonId: runtimeEnv.ZOPU_SMOKE_DAEMON_ID ?? runtimeEnv.DAEMON_ID,
featureRequest:
runtimeEnv.ZOPU_SMOKE_FEATURE_REQUEST ??
"Add a tiny accessible status control to the existing web project's primary page, keep existing behavior unchanged, and add a focused test for it.",
flueUrl:
runtimeEnv.ZOPU_SMOKE_FLUE_URL ??
runtimeEnv.VITE_FLUE_URL ??
"http://localhost:3583",
projectId: runtimeEnv.ZOPU_SMOKE_PROJECT_ID,
webUrl:
runtimeEnv.ZOPU_SMOKE_WEB_URL ??
runtimeEnv.SITE_URL ??
"http://localhost:5173",
});

View File

@@ -11,7 +11,8 @@
"./project-issue": "./src/project-issue.ts",
"./project-workspace": "./src/project-workspace.ts",
"./project-work": "./src/project-work.ts",
"./signal": "./src/signal.ts"
"./signal": "./src/signal.ts",
"./smoke": "./src/smoke.ts"
},
"scripts": {
"check-types": "tsc --noEmit",

View File

@@ -6,3 +6,4 @@ export * from "./project-issue";
export * from "./project-workspace";
export * from "./project-work";
export * from "./signal";
export * from "./smoke";

View File

@@ -0,0 +1,164 @@
import { Effect } from "effect";
import { describe, expect, it } from "vitest";
import {
SmokeContractError,
advanceSmokeLoop,
decodeSmokeChatText,
decodeSmokeModelCatalog,
decodeSmokePlan,
decodeSmokeReview,
decodeSmokeWorkerReport,
initialSmokeLoopState,
redactSmokeText,
selectSmokeModelRoles,
validateSmokeCapabilities,
} from "./smoke";
const validRoleSelection = () => ({
plannerReviewerModel: "glm-5.2",
plannerReviewerProvider: "cheaptricks",
workerModel: "minimax-m3",
workerProvider: "cheaptricks",
workerToolCallMode: "serial" as const,
});
const run = <A, E>(effect: Effect.Effect<A, E>): A => Effect.runSync(effect);
describe("smoke primitives", () => {
it("selects the planner/reviewer and serial M3 worker roles", () => {
const roles = run(selectSmokeModelRoles(validRoleSelection()));
expect(roles.worker).toMatchObject({
model: "minimax-m3",
role: "worker",
toolCallMode: "serial",
});
expect(roles.plannerReviewer).toMatchObject({
model: "glm-5.2",
role: "planner-reviewer",
});
});
it("rejects a non-M3 worker or non-GLM planner", () => {
const worker = validRoleSelection();
worker.workerModel = "glm-5.2";
const workerError = run(Effect.flip(selectSmokeModelRoles(worker)));
expect(workerError).toBeInstanceOf(SmokeContractError);
expect(workerError.reason).toBe("InvalidModelRole");
const planner = validRoleSelection();
planner.plannerReviewerModel = "minimax-m3";
const plannerError = run(Effect.flip(selectSmokeModelRoles(planner)));
expect(plannerError.reason).toBe("InvalidModelRole");
});
it("advances only through the serial smoke loop", () => {
let state = initialSmokeLoopState();
state = run(advanceSmokeLoop(state, "preflight-passed"));
state = run(advanceSmokeLoop(state, "plan-validated"));
state = run(advanceSmokeLoop(state, "worker-succeeded"));
state = run(advanceSmokeLoop(state, "review-approved"));
expect(state.phase).toBe("completed");
expect(state.history).toEqual([
"preflight",
"planning",
"worker-running",
"reviewing",
"completed",
]);
});
it("turns contract failures into a terminal state", () => {
const state = run(
advanceSmokeLoop(
initialSmokeLoopState(),
"contract-failed",
"daemon is offline"
)
);
expect(state.phase).toBe("failed");
expect(state.lastError).toBe("daemon is offline");
});
it("rejects out-of-order loop transitions", () => {
const error = run(
Effect.flip(advanceSmokeLoop(initialSmokeLoopState(), "review-approved"))
);
expect(error).toBeInstanceOf(SmokeContractError);
expect(error.reason).toBe("InvalidTransition");
});
it("validates model reports at the primitive boundary", () => {
const plan = run(
decodeSmokePlan({
acceptanceCriteria: ["The button renders"],
nonGoals: [],
steps: ["Add the button", "Run the web check"],
summary: "Add a small button to the disposable project",
})
);
const worker = run(
decodeSmokeWorkerReport({
status: "completed",
summary: "Changed the project and ran the targeted check.",
})
);
const review = run(
decodeSmokeReview({
approved: true,
findings: [],
summary: "The change matches the acceptance criteria.",
})
);
expect(plan.steps).toHaveLength(2);
expect(worker.status).toBe("completed");
expect(review.approved).toBe(true);
});
it("rejects empty or malformed model output", () => {
const empty = run(
Effect.flip(
decodeSmokeChatText({ choices: [{ message: { content: " " } }] })
)
);
expect(empty.reason).toBe("InvalidModelResponse");
const malformed = run(Effect.flip(decodeSmokeReview({ approved: true })));
expect(malformed.reason).toBe("InvalidReport");
});
it("validates the canonical CPA model catalog", () => {
const catalog = run(
decodeSmokeModelCatalog({
data: [{ id: "minimax-m3" }, { id: "glm-5.2" }],
})
);
expect(catalog.data.map(({ id }) => id)).toEqual(["minimax-m3", "glm-5.2"]);
});
it("fails a capability report when any required check fails", () => {
const error = run(
Effect.flip(
validateSmokeCapabilities([
{ id: "web", message: "ready", passed: true },
{ id: "daemon", message: "daemon is offline", passed: false },
])
)
);
expect(error.reason).toBe("InvalidReport");
expect(error.message).toContain("daemon is offline");
});
it("redacts secrets without changing unrelated text", () => {
expect(
redactSmokeText("status token=secret-key endpoint=ok", ["secret-key"])
).toBe("status token=[REDACTED] endpoint=ok");
});
});

View File

@@ -0,0 +1,347 @@
/* eslint-disable max-classes-per-file -- the smoke contract keeps its schemas, errors, and transitions together. */
import { Effect, Schema } from "effect";
const MeaningfulString = Schema.String.check(
Schema.makeFilter((value) => value.trim().length > 0, {
expected: "a non-empty string",
})
);
const SmokePhase = Schema.Literals([
"preflight",
"planning",
"worker-running",
"reviewing",
"completed",
"failed",
]);
export type SmokePhase = typeof SmokePhase.Type;
const SmokeTransition = Schema.Literals([
"preflight-passed",
"plan-validated",
"worker-succeeded",
"review-approved",
"contract-failed",
"runtime-failed",
]);
type SmokeTransition = typeof SmokeTransition.Type;
export const SmokeRole = Schema.Literals(["planner-reviewer", "worker"]);
export type SmokeRole = typeof SmokeRole.Type;
const SmokeToolCallMode = Schema.Literal("serial");
export const SmokeRoleSelectionInput = Schema.Struct({
plannerReviewerModel: MeaningfulString,
plannerReviewerProvider: MeaningfulString,
workerModel: MeaningfulString,
workerProvider: MeaningfulString,
workerToolCallMode: SmokeToolCallMode,
});
export type SmokeRoleSelectionInput = typeof SmokeRoleSelectionInput.Type;
export const SmokeModelRole = Schema.Struct({
model: MeaningfulString,
provider: MeaningfulString,
role: SmokeRole,
toolCallMode: Schema.Literal("serial"),
});
export type SmokeModelRole = typeof SmokeModelRole.Type;
export const SmokeModelRoles = Schema.Struct({
plannerReviewer: SmokeModelRole,
worker: SmokeModelRole,
});
export type SmokeModelRoles = typeof SmokeModelRoles.Type;
export const SmokeContractReason = Schema.Literals([
"InvalidInput",
"InvalidModelRole",
"InvalidTransition",
"InvalidModelResponse",
"InvalidReport",
]);
export type SmokeContractReason = typeof SmokeContractReason.Type;
export class SmokeContractError extends Schema.TaggedErrorClass<SmokeContractError>()(
"SmokeContractError",
{
message: Schema.String,
reason: SmokeContractReason,
}
) {}
export const selectSmokeModelRoles = Effect.fn("Smoke.selectModelRoles")(
function* selectSmokeModelRoles(input: unknown) {
const selection: SmokeRoleSelectionInput =
yield* Schema.decodeUnknownEffect(SmokeRoleSelectionInput)(input).pipe(
Effect.mapError(
(cause) =>
new SmokeContractError({
message: cause.message,
reason: "InvalidInput",
})
)
);
if (selection.workerModel !== "minimax-m3") {
return yield* Effect.fail(
new SmokeContractError({
message: "The smoke worker must use minimax-m3",
reason: "InvalidModelRole",
})
);
}
if (selection.plannerReviewerModel !== "glm-5.2") {
return yield* Effect.fail(
new SmokeContractError({
message: "The smoke planner/reviewer must use glm-5.2",
reason: "InvalidModelRole",
})
);
}
return {
plannerReviewer: {
model: selection.plannerReviewerModel,
provider: selection.plannerReviewerProvider,
role: "planner-reviewer" as const,
toolCallMode: "serial" as const,
},
worker: {
model: selection.workerModel,
provider: selection.workerProvider,
role: "worker" as const,
toolCallMode: "serial" as const,
},
} satisfies SmokeModelRoles;
}
);
export const SmokePlan = Schema.Struct({
acceptanceCriteria: Schema.NonEmptyArray(MeaningfulString),
nonGoals: Schema.Array(MeaningfulString),
steps: Schema.NonEmptyArray(MeaningfulString),
summary: MeaningfulString,
});
export type SmokePlan = typeof SmokePlan.Type;
export const SmokeWorkerReport = Schema.Struct({
status: Schema.Literals(["completed", "blocked", "failed"]),
summary: MeaningfulString,
});
export type SmokeWorkerReport = typeof SmokeWorkerReport.Type;
export const SmokeReview = Schema.Struct({
approved: Schema.Boolean,
findings: Schema.Array(MeaningfulString),
summary: MeaningfulString,
});
export type SmokeReview = typeof SmokeReview.Type;
export const SmokeChatResponse = Schema.Struct({
choices: Schema.NonEmptyArray(
Schema.Struct({
message: Schema.Struct({ content: Schema.String }),
})
),
});
export const SmokeModelCatalog = Schema.Struct({
data: Schema.Array(Schema.Struct({ id: MeaningfulString })),
});
export type SmokeModelCatalog = typeof SmokeModelCatalog.Type;
export const decodeSmokeModelCatalog = Effect.fn("Smoke.decodeModelCatalog")(
function* decodeSmokeModelCatalog(input: unknown) {
return yield* Schema.decodeUnknownEffect(SmokeModelCatalog)(input).pipe(
Effect.mapError(
(cause) =>
new SmokeContractError({
message: cause.message,
reason: "InvalidModelResponse",
})
)
);
}
);
export const decodeSmokeChatText = Effect.fn("Smoke.decodeChatText")(
function* decodeSmokeChatText(input: unknown) {
const response = yield* Schema.decodeUnknownEffect(SmokeChatResponse)(
input
).pipe(
Effect.mapError(
(cause) =>
new SmokeContractError({
message: cause.message,
reason: "InvalidModelResponse",
})
)
);
const text = response.choices[0].message.content;
if (text.trim().length === 0) {
return yield* Effect.fail(
new SmokeContractError({
message: "The model returned an empty response",
reason: "InvalidModelResponse",
})
);
}
return text;
}
);
export const decodeSmokePlan = Effect.fn("Smoke.decodePlan")(
function* decodeSmokePlan(input: unknown) {
return yield* Schema.decodeUnknownEffect(SmokePlan)(input).pipe(
Effect.mapError(
(cause) =>
new SmokeContractError({
message: cause.message,
reason: "InvalidReport",
})
)
);
}
);
export const decodeSmokeWorkerReport = Effect.fn("Smoke.decodeWorkerReport")(
function* decodeSmokeWorkerReport(input: unknown) {
return yield* Schema.decodeUnknownEffect(SmokeWorkerReport)(input).pipe(
Effect.mapError(
(cause) =>
new SmokeContractError({
message: cause.message,
reason: "InvalidReport",
})
)
);
}
);
export const decodeSmokeReview = Effect.fn("Smoke.decodeReview")(
function* decodeSmokeReview(input: unknown) {
return yield* Schema.decodeUnknownEffect(SmokeReview)(input).pipe(
Effect.mapError(
(cause) =>
new SmokeContractError({
message: cause.message,
reason: "InvalidReport",
})
)
);
}
);
export const SmokeCapabilityCheck = Schema.Struct({
id: MeaningfulString,
message: MeaningfulString,
passed: Schema.Boolean,
});
export type SmokeCapabilityCheck = typeof SmokeCapabilityCheck.Type;
export const validateSmokeCapabilities = Effect.fn(
"Smoke.validateCapabilities"
)(function* validateSmokeCapabilities(input: unknown) {
const checks = yield* Schema.decodeUnknownEffect(
Schema.NonEmptyArray(SmokeCapabilityCheck)
)(input).pipe(
Effect.mapError(
(cause) =>
new SmokeContractError({
message: cause.message,
reason: "InvalidInput",
})
)
);
const failed = checks.filter((check) => !check.passed);
if (failed.length > 0) {
return yield* Effect.fail(
new SmokeContractError({
message: failed
.map((check) => `${check.id}: ${check.message}`)
.join("; "),
reason: "InvalidReport",
})
);
}
return checks;
});
export const SmokeLoopState = Schema.Struct({
history: Schema.Array(SmokePhase),
lastError: Schema.NullOr(Schema.String),
phase: SmokePhase,
});
export type SmokeLoopState = typeof SmokeLoopState.Type;
export const initialSmokeLoopState = (): SmokeLoopState => ({
history: ["preflight"],
lastError: null,
phase: "preflight",
});
const nextPhase: Readonly<
Record<SmokePhase, Partial<Record<SmokeTransition, SmokePhase>>>
> = {
completed: {},
failed: {},
planning: {
"plan-validated": "worker-running",
"runtime-failed": "failed",
},
preflight: {
"contract-failed": "failed",
"preflight-passed": "planning",
"runtime-failed": "failed",
},
reviewing: {
"contract-failed": "failed",
"review-approved": "completed",
"runtime-failed": "failed",
},
"worker-running": {
"contract-failed": "failed",
"runtime-failed": "failed",
"worker-succeeded": "reviewing",
},
};
export const advanceSmokeLoop = Effect.fn("Smoke.advanceLoop")(
function* advanceSmokeLoop(
state: SmokeLoopState,
transition: SmokeTransition,
error?: string
) {
const phase = nextPhase[state.phase][transition];
if (phase === undefined) {
return yield* Effect.fail(
new SmokeContractError({
message: `Cannot apply ${transition} while smoke loop is ${state.phase}`,
reason: "InvalidTransition",
})
);
}
return {
history: [...state.history, phase],
lastError: phase === "failed" ? (error ?? "Smoke loop failed") : null,
phase,
} satisfies SmokeLoopState;
}
);
export const redactSmokeText = (
text: string,
secrets: readonly string[]
): string => {
let redacted = text;
for (const secret of secrets) {
if (secret.length > 0) {
redacted = redacted.replaceAll(secret, "[REDACTED]");
}
}
return redacted;
};

829
scripts/zopu-smoke.ts Normal file
View File

@@ -0,0 +1,829 @@
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 only probes the local web/Convex/Flue/AgentOS and CPA 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
`;
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)
);
}
}
};
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,
});
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;