Task 007 add agent job admin runner follow-up
This commit is contained in:
@@ -75,6 +75,18 @@ class SchemaStorageContractsIntegrationTest(unittest.TestCase):
|
||||
"transform_script",
|
||||
}.issubset(repository.schema.list_columns("script_config_versions"))
|
||||
)
|
||||
self.assertTrue(
|
||||
{
|
||||
"parent_job_id",
|
||||
"attempt",
|
||||
"stdout",
|
||||
"stderr",
|
||||
"exit_code",
|
||||
"duration_ms",
|
||||
"workspace_path",
|
||||
"output_files",
|
||||
}.issubset(repository.schema.list_columns("agent_jobs"))
|
||||
)
|
||||
self.assertTrue(
|
||||
{
|
||||
"s3_prefix",
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"test:roles": "node tests/role-navigation.test.mjs",
|
||||
"test:ui": "node tests/role-navigation.test.mjs && node tests/article_dashboard.test.mjs && node tests/article_form_validation.test.mjs && node tests/admin_script_versions.model.test.mjs",
|
||||
"test:ui": "node tests/role-navigation.test.mjs && node tests/article_dashboard.test.mjs && node tests/article_form_validation.test.mjs && node tests/admin_script_versions.model.test.mjs && node tests/agent_jobs.model.test.mjs",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
import AdminJobsPage from "@/pages/admin-jobs";
|
||||
|
||||
export default AdminJobsPage;
|
||||
@@ -0,0 +1,33 @@
|
||||
import type { AgentJobSummary } from "@pipeline/shared";
|
||||
|
||||
export type AgentJobRow = {
|
||||
id: string;
|
||||
type: string;
|
||||
status: string;
|
||||
attempt: number;
|
||||
parentJobId: string | null;
|
||||
workspacePath: string | null;
|
||||
stdout: string;
|
||||
stderr: string;
|
||||
exitCode: number | null;
|
||||
durationMs: number | null;
|
||||
outputFiles: string;
|
||||
error: string;
|
||||
};
|
||||
|
||||
export function buildAgentJobRows(jobs: readonly AgentJobSummary[]): AgentJobRow[] {
|
||||
return jobs.map((job) => ({
|
||||
id: job.id,
|
||||
type: job.job_type,
|
||||
status: job.status,
|
||||
attempt: job.attempt,
|
||||
parentJobId: job.parent_job_id ?? null,
|
||||
workspacePath: job.workspace_path ?? null,
|
||||
stdout: job.stdout,
|
||||
stderr: job.stderr,
|
||||
exitCode: job.exit_code ?? null,
|
||||
durationMs: job.duration_ms ?? null,
|
||||
outputFiles: job.output_files.map((file) => file.path).join(", "),
|
||||
error: job.error_category ?? job.error_message ?? "",
|
||||
}));
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import type { AgentJobRow } from "./model";
|
||||
|
||||
type AgentJobsTableProps = {
|
||||
rows: readonly AgentJobRow[];
|
||||
};
|
||||
|
||||
export function AgentJobsTable({ rows }: AgentJobsTableProps) {
|
||||
if (rows.length === 0) {
|
||||
return <p className="emptyState">No agent jobs yet.</p>;
|
||||
}
|
||||
|
||||
return (
|
||||
<table className="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Job</th>
|
||||
<th>Status</th>
|
||||
<th>Attempt</th>
|
||||
<th>Runtime</th>
|
||||
<th>Workspace</th>
|
||||
<th>Logs</th>
|
||||
<th>Outputs</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{rows.map((row) => (
|
||||
<tr key={row.id}>
|
||||
<td>
|
||||
<div>{row.type}</div>
|
||||
<small>{row.id}</small>
|
||||
{row.parentJobId ? <div>retry of {row.parentJobId}</div> : null}
|
||||
</td>
|
||||
<td>{row.status}</td>
|
||||
<td>{row.attempt}</td>
|
||||
<td>
|
||||
<div>exit: {row.exitCode ?? "pending"}</div>
|
||||
<div>{row.durationMs ?? 0} ms</div>
|
||||
{row.error ? <div>{row.error}</div> : null}
|
||||
</td>
|
||||
<td>{row.workspacePath ?? "pending"}</td>
|
||||
<td>
|
||||
<pre className="logBlock">{row.stdout || "stdout pending"}</pre>
|
||||
{row.stderr ? <pre className="logBlock">{row.stderr}</pre> : null}
|
||||
</td>
|
||||
<td>{row.outputFiles || "none"}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import Link from "next/link";
|
||||
|
||||
import { AgentJobsTable } from "@/features/agent-jobs/ui";
|
||||
import { buildAgentJobRows } from "@/features/agent-jobs/model";
|
||||
import { fetchAgentJobs } from "@/shared/pipeline-api";
|
||||
import { RoleNavigation } from "@/widgets/role-navigation";
|
||||
|
||||
export default async function AdminJobsPage() {
|
||||
const jobs = await fetchAgentJobs();
|
||||
const rows = buildAgentJobRows(jobs.jobs);
|
||||
|
||||
return (
|
||||
<main>
|
||||
<header className="pageHeader">
|
||||
<div>
|
||||
<h1>Agent Jobs</h1>
|
||||
<p>Monitor runner status, logs, outputs, and retry lineage.</p>
|
||||
</div>
|
||||
<Link href="/">Back to dashboard</Link>
|
||||
<span className="roleBadge">Admin demo</span>
|
||||
</header>
|
||||
<RoleNavigation role="ADMIN" />
|
||||
<section className="panel">
|
||||
<AgentJobsTable rows={rows} />
|
||||
</section>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -2,6 +2,9 @@ import type {
|
||||
ArticleCreateRequest,
|
||||
ArticleCreateResponse,
|
||||
ArticleDetailResponse,
|
||||
AgentJobListResponse,
|
||||
AgentJobResponse,
|
||||
AgentJobTestCodexRequest,
|
||||
ArticleListResponse,
|
||||
CurrentUserResponse,
|
||||
ScriptConfigVersionCreateRequest,
|
||||
@@ -149,6 +152,45 @@ export function fetchCurrentUser(): Promise<CurrentUserResponse> {
|
||||
return apiGet<CurrentUserResponse>("/api/me");
|
||||
}
|
||||
|
||||
export function fetchAgentJobs(
|
||||
userEmail = DEMO_ADMIN_EMAIL,
|
||||
): Promise<AgentJobListResponse> {
|
||||
return apiGet<AgentJobListResponse>("/api/agent-jobs", { userEmail });
|
||||
}
|
||||
|
||||
export function createTestCodexJob(
|
||||
request: AgentJobTestCodexRequest = { fake_result: "valid" },
|
||||
userEmail = DEMO_ADMIN_EMAIL,
|
||||
): Promise<AgentJobResponse> {
|
||||
return apiPost<AgentJobTestCodexRequest, AgentJobResponse>(
|
||||
"/api/agent-jobs/test-codex",
|
||||
request,
|
||||
{ userEmail },
|
||||
);
|
||||
}
|
||||
|
||||
export function retryAgentJob(
|
||||
jobId: string,
|
||||
userEmail = DEMO_ADMIN_EMAIL,
|
||||
): Promise<AgentJobResponse> {
|
||||
return apiPost<unknown, AgentJobResponse>(
|
||||
`/api/agent-jobs/${jobId}/retry`,
|
||||
undefined,
|
||||
{ userEmail },
|
||||
);
|
||||
}
|
||||
|
||||
export function cancelAgentJob(
|
||||
jobId: string,
|
||||
userEmail = DEMO_ADMIN_EMAIL,
|
||||
): Promise<AgentJobResponse> {
|
||||
return apiPost<unknown, AgentJobResponse>(
|
||||
`/api/agent-jobs/${jobId}/cancel`,
|
||||
undefined,
|
||||
{ userEmail },
|
||||
);
|
||||
}
|
||||
|
||||
export function fetchTargetSites(): Promise<TargetSiteConfigResponse[]> {
|
||||
return apiGet<TargetSiteConfigResponse[]>("/api/sites");
|
||||
}
|
||||
|
||||
@@ -38,6 +38,12 @@ export const ROLE_NAVIGATION_ITEMS = [
|
||||
href: "/admin/scripts",
|
||||
roles: ["ADMIN"],
|
||||
},
|
||||
{
|
||||
id: "agent-jobs",
|
||||
label: "Agent jobs",
|
||||
href: "/admin/jobs",
|
||||
roles: ["ADMIN"],
|
||||
},
|
||||
] as const satisfies readonly NavigationItem[];
|
||||
|
||||
export function getRoleNavigationItems(role: Role): NavigationItem[] {
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { readFileSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
import ts from "typescript";
|
||||
|
||||
const testDir = path.dirname(fileURLToPath(import.meta.url));
|
||||
const sourcePath = path.resolve(testDir, "../src/features/agent-jobs/model.ts");
|
||||
const source = readFileSync(sourcePath, "utf8");
|
||||
const compiled = ts.transpileModule(source, {
|
||||
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022 },
|
||||
});
|
||||
|
||||
const moduleExports = {};
|
||||
new Function("exports", compiled.outputText)(moduleExports);
|
||||
|
||||
const { buildAgentJobRows } = moduleExports;
|
||||
|
||||
const rows = buildAgentJobRows([
|
||||
{
|
||||
id: "11111111-1111-1111-1111-111111111111",
|
||||
article_id: null,
|
||||
parent_job_id: "00000000-0000-0000-0000-000000000000",
|
||||
attempt: 2,
|
||||
job_type: "TEST_CODEX",
|
||||
agent_profile: "fake-codex",
|
||||
status: "SUCCEEDED",
|
||||
workspace_path: "/tmp/job",
|
||||
input_files: [{ path: "inputs/job.json" }],
|
||||
output_files: [{ path: "outputs/result.json" }],
|
||||
error_category: null,
|
||||
error_message: null,
|
||||
stdout: "done",
|
||||
stderr: "",
|
||||
exit_code: 0,
|
||||
duration_ms: 5,
|
||||
queued_at: "2026-05-21T00:00:00Z",
|
||||
started_at: "2026-05-21T00:00:01Z",
|
||||
finished_at: "2026-05-21T00:00:02Z",
|
||||
},
|
||||
]);
|
||||
|
||||
assert.equal(rows.length, 1);
|
||||
assert.equal(rows[0].status, "SUCCEEDED");
|
||||
assert.equal(rows[0].attempt, 2);
|
||||
assert.equal(rows[0].parentJobId, "00000000-0000-0000-0000-000000000000");
|
||||
assert.equal(rows[0].outputFiles, "outputs/result.json");
|
||||
assert.equal(rows[0].stdout, "done");
|
||||
@@ -36,5 +36,9 @@ assert.equal(
|
||||
adminItems.some((item) => item.id === "script-versions"),
|
||||
true,
|
||||
);
|
||||
assert.equal(
|
||||
adminItems.some((item) => item.id === "agent-jobs"),
|
||||
true,
|
||||
);
|
||||
|
||||
console.log("role navigation hides Admin-only items for Editors");
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import time
|
||||
from pathlib import Path
|
||||
@@ -90,6 +91,34 @@ def run_fake_runner_once(
|
||||
)
|
||||
|
||||
|
||||
def run_fake_runner_loop(
|
||||
*,
|
||||
backend_url: str,
|
||||
workspace_root: Path,
|
||||
poll_interval_seconds: float,
|
||||
) -> None:
|
||||
backend_client = HttpBackendJobClient(backend_url)
|
||||
while True:
|
||||
try:
|
||||
run_fake_runner_once(backend_client, workspace_root=workspace_root)
|
||||
except Exception:
|
||||
pass
|
||||
time.sleep(poll_interval_seconds)
|
||||
|
||||
|
||||
def get_backend_url() -> str:
|
||||
backend_url = os.environ.get("BACKEND_URL")
|
||||
if not backend_url:
|
||||
raise RuntimeError("BACKEND_URL is required for fake runner")
|
||||
return backend_url
|
||||
|
||||
|
||||
def get_workspace_root() -> Path:
|
||||
return Path(
|
||||
os.environ.get("RUNNER_WORKSPACE_ROOT", "/tmp/pipeline-runner-workspaces")
|
||||
)
|
||||
|
||||
|
||||
def materialize_fake_workspace(
|
||||
job: dict[str, Any],
|
||||
*,
|
||||
|
||||
@@ -6,7 +6,13 @@ import threading
|
||||
from http import HTTPStatus
|
||||
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
||||
|
||||
from src.application.fake_runner import get_backend_url, get_workspace_root, run_fake_runner_loop
|
||||
from src.application.fake_runner import (
|
||||
HttpBackendJobClient,
|
||||
get_backend_url,
|
||||
get_workspace_root,
|
||||
run_fake_runner_loop,
|
||||
run_fake_runner_once,
|
||||
)
|
||||
|
||||
|
||||
class HealthHandler(BaseHTTPRequestHandler):
|
||||
@@ -22,6 +28,32 @@ class HealthHandler(BaseHTTPRequestHandler):
|
||||
self.end_headers()
|
||||
self.wfile.write(body)
|
||||
|
||||
def do_POST(self) -> None:
|
||||
if self.path != "/run-once":
|
||||
self.send_error(HTTPStatus.NOT_FOUND)
|
||||
return
|
||||
|
||||
try:
|
||||
result = run_fake_runner_once(
|
||||
HttpBackendJobClient(get_backend_url()),
|
||||
workspace_root=get_workspace_root(),
|
||||
)
|
||||
except Exception as error:
|
||||
body = json.dumps({"status": "error", "detail": str(error)}).encode("utf-8")
|
||||
self.send_response(HTTPStatus.INTERNAL_SERVER_ERROR)
|
||||
self.send_header("Content-Type", "application/json")
|
||||
self.send_header("Content-Length", str(len(body)))
|
||||
self.end_headers()
|
||||
self.wfile.write(body)
|
||||
return
|
||||
|
||||
body = json.dumps({"status": "ok", "job": result}, default=str).encode("utf-8")
|
||||
self.send_response(HTTPStatus.OK)
|
||||
self.send_header("Content-Type", "application/json")
|
||||
self.send_header("Content-Length", str(len(body)))
|
||||
self.end_headers()
|
||||
self.wfile.write(body)
|
||||
|
||||
def log_message(self, format: str, *args: object) -> None:
|
||||
return
|
||||
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import tempfile
|
||||
import sys
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parents[3]
|
||||
RUNNER_ROOT = REPO_ROOT / "apps" / "runner"
|
||||
sys.path.insert(0, str(RUNNER_ROOT))
|
||||
|
||||
from src.application.fake_runner import run_fake_runner_once
|
||||
|
||||
|
||||
class FakeBackendJobClient:
|
||||
def __init__(self, job: dict[str, Any] | None) -> None:
|
||||
self.job = job
|
||||
self.completed: dict[str, Any] | None = None
|
||||
|
||||
def claim_job(self) -> dict[str, Any] | None:
|
||||
job = self.job
|
||||
self.job = None
|
||||
return job
|
||||
|
||||
def complete_job(self, job_id: str, payload: dict[str, Any]) -> dict[str, Any]:
|
||||
self.completed = {"id": job_id, **payload}
|
||||
return self.completed
|
||||
|
||||
|
||||
class FakeRunnerTest(unittest.TestCase):
|
||||
def test_fake_runner_claims_job_writes_workspace_and_completes(self) -> None:
|
||||
backend = FakeBackendJobClient(
|
||||
{
|
||||
"id": "11111111-1111-1111-1111-111111111111",
|
||||
"agent_profile": "fake-codex",
|
||||
}
|
||||
)
|
||||
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
result = run_fake_runner_once(
|
||||
backend,
|
||||
workspace_root=Path(temp_dir),
|
||||
)
|
||||
self.assertIsNotNone(result)
|
||||
assert result is not None
|
||||
workspace_path = Path(result["workspace_path"])
|
||||
self.assertTrue((workspace_path / "inputs" / "job.json").is_file())
|
||||
self.assertTrue((workspace_path / "logs" / "stdout.log").is_file())
|
||||
self.assertTrue((workspace_path / "logs" / "stderr.log").is_file())
|
||||
self.assertTrue((workspace_path / "outputs" / "result.json").is_file())
|
||||
self.assertEqual(0, result["exit_code"])
|
||||
self.assertEqual("SUCCEEDED", result["output"]["status"])
|
||||
self.assertEqual([{"path": "outputs/result.json"}], result["output"]["output_files"])
|
||||
|
||||
def test_fake_runner_can_emit_invalid_schema_fixture(self) -> None:
|
||||
backend = FakeBackendJobClient(
|
||||
{
|
||||
"id": "22222222-2222-2222-2222-222222222222",
|
||||
"agent_profile": "fake-invalid-schema",
|
||||
}
|
||||
)
|
||||
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
result = run_fake_runner_once(
|
||||
backend,
|
||||
workspace_root=Path(temp_dir),
|
||||
)
|
||||
|
||||
self.assertIsNotNone(result)
|
||||
assert result is not None
|
||||
self.assertEqual({"status": "NOT_A_STATUS"}, result["output"])
|
||||
|
||||
def test_fake_runner_noops_without_queued_job(self) -> None:
|
||||
backend = FakeBackendJobClient(None)
|
||||
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
result = run_fake_runner_once(
|
||||
backend,
|
||||
workspace_root=Path(temp_dir),
|
||||
)
|
||||
|
||||
self.assertIsNone(result)
|
||||
self.assertIsNone(backend.completed)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -41,8 +41,12 @@ services:
|
||||
dockerfile: apps/runner/Dockerfile
|
||||
environment:
|
||||
RUNNER_MODE: fake
|
||||
BACKEND_URL: http://backend:8000
|
||||
ports:
|
||||
- "${RUNNER_PORT:-8010}:8010"
|
||||
depends_on:
|
||||
backend:
|
||||
condition: service_started
|
||||
|
||||
postgres:
|
||||
image: postgres:16-alpine
|
||||
|
||||
+417
-21
@@ -19,6 +19,23 @@
|
||||
"title": "AgentJobErrorCategory",
|
||||
"type": "string"
|
||||
},
|
||||
"AgentJobListResponse": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"jobs": {
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/AgentJobSummary"
|
||||
},
|
||||
"title": "Jobs",
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"jobs"
|
||||
],
|
||||
"title": "AgentJobListResponse",
|
||||
"type": "object"
|
||||
},
|
||||
"AgentJobOutput": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
@@ -74,6 +91,19 @@
|
||||
"title": "AgentJobOutput",
|
||||
"type": "object"
|
||||
},
|
||||
"AgentJobResponse": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"job": {
|
||||
"$ref": "#/components/schemas/AgentJobSummary"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"job"
|
||||
],
|
||||
"title": "AgentJobResponse",
|
||||
"type": "object"
|
||||
},
|
||||
"AgentJobStatus": {
|
||||
"enum": [
|
||||
"QUEUED",
|
||||
@@ -94,10 +124,37 @@
|
||||
"type": "string"
|
||||
},
|
||||
"article_id": {
|
||||
"anyOf": [
|
||||
{
|
||||
"format": "uuid",
|
||||
"title": "Article Id",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"title": "Article Id"
|
||||
},
|
||||
"attempt": {
|
||||
"default": 1,
|
||||
"minimum": 1,
|
||||
"title": "Attempt",
|
||||
"type": "integer"
|
||||
},
|
||||
"duration_ms": {
|
||||
"anyOf": [
|
||||
{
|
||||
"minimum": 0,
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"title": "Duration Ms"
|
||||
},
|
||||
"error_category": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -121,6 +178,18 @@
|
||||
"default": null,
|
||||
"title": "Error Message"
|
||||
},
|
||||
"exit_code": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"title": "Exit Code"
|
||||
},
|
||||
"finished_at": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -156,6 +225,24 @@
|
||||
"title": "Output Files",
|
||||
"type": "array"
|
||||
},
|
||||
"parent_job_id": {
|
||||
"anyOf": [
|
||||
{
|
||||
"format": "uuid",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"title": "Parent Job Id"
|
||||
},
|
||||
"queued_at": {
|
||||
"format": "date-time",
|
||||
"title": "Queued At",
|
||||
"type": "string"
|
||||
},
|
||||
"started_at": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -172,6 +259,16 @@
|
||||
"status": {
|
||||
"$ref": "#/components/schemas/AgentJobStatus"
|
||||
},
|
||||
"stderr": {
|
||||
"default": "",
|
||||
"title": "Stderr",
|
||||
"type": "string"
|
||||
},
|
||||
"stdout": {
|
||||
"default": "",
|
||||
"title": "Stdout",
|
||||
"type": "string"
|
||||
},
|
||||
"workspace_path": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -187,14 +284,27 @@
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"article_id",
|
||||
"job_type",
|
||||
"agent_profile",
|
||||
"status"
|
||||
"status",
|
||||
"queued_at"
|
||||
],
|
||||
"title": "AgentJobSummary",
|
||||
"type": "object"
|
||||
},
|
||||
"AgentJobTestCodexRequest": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"fake_result": {
|
||||
"default": "valid",
|
||||
"pattern": "^(valid|invalid_schema)$",
|
||||
"title": "Fake Result",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"title": "AgentJobTestCodexRequest",
|
||||
"type": "object"
|
||||
},
|
||||
"AgentJobType": {
|
||||
"enum": [
|
||||
"BOUNDARY_QUESTIONS",
|
||||
@@ -1563,16 +1673,6 @@
|
||||
"ScriptConfigVersionSummary": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"created_at": {
|
||||
"format": "date-time",
|
||||
"title": "Created At",
|
||||
"type": "string"
|
||||
},
|
||||
"created_by": {
|
||||
"format": "uuid",
|
||||
"title": "Created By",
|
||||
"type": "string"
|
||||
},
|
||||
"activated_at": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -1583,8 +1683,19 @@
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"title": "Activated At"
|
||||
},
|
||||
"created_at": {
|
||||
"format": "date-time",
|
||||
"title": "Created At",
|
||||
"type": "string"
|
||||
},
|
||||
"created_by": {
|
||||
"format": "uuid",
|
||||
"title": "Created By",
|
||||
"type": "string"
|
||||
},
|
||||
"diff": {
|
||||
"additionalProperties": true,
|
||||
"title": "Diff",
|
||||
@@ -1600,14 +1711,6 @@
|
||||
"title": "Publishing Yaml Hash",
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"$ref": "#/components/schemas/ScriptConfigVersionStatus"
|
||||
},
|
||||
"target_site_id": {
|
||||
"format": "uuid",
|
||||
"title": "Target Site Id",
|
||||
"type": "string"
|
||||
},
|
||||
"rollback_target_version_id": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -1618,8 +1721,17 @@
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"title": "Rollback Target Version Id"
|
||||
},
|
||||
"status": {
|
||||
"$ref": "#/components/schemas/ScriptConfigVersionStatus"
|
||||
},
|
||||
"target_site_id": {
|
||||
"format": "uuid",
|
||||
"title": "Target Site Id",
|
||||
"type": "string"
|
||||
},
|
||||
"transform_script_hash": {
|
||||
"minLength": 1,
|
||||
"title": "Transform Script Hash",
|
||||
@@ -2116,6 +2228,290 @@
|
||||
},
|
||||
"openapi": "3.1.0",
|
||||
"paths": {
|
||||
"/api/agent-jobs": {
|
||||
"get": {
|
||||
"operationId": "get_agent_jobs_api_agent_jobs_get",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "header",
|
||||
"name": "X-Demo-User-Email",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "X-Demo-User-Email"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/AgentJobListResponse"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Successful Response"
|
||||
},
|
||||
"422": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Validation Error"
|
||||
}
|
||||
},
|
||||
"summary": "Get Agent Jobs",
|
||||
"tags": [
|
||||
"agent-jobs"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/agent-jobs/test-codex": {
|
||||
"post": {
|
||||
"operationId": "post_test_codex_job_api_agent_jobs_test_codex_post",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "header",
|
||||
"name": "X-Demo-User-Email",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "X-Demo-User-Email"
|
||||
}
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/AgentJobTestCodexRequest"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"201": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/AgentJobResponse"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Successful Response"
|
||||
},
|
||||
"422": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Validation Error"
|
||||
}
|
||||
},
|
||||
"summary": "Post Test Codex Job",
|
||||
"tags": [
|
||||
"agent-jobs"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/agent-jobs/{job_id}": {
|
||||
"get": {
|
||||
"operationId": "get_agent_job_route_api_agent_jobs__job_id__get",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "path",
|
||||
"name": "job_id",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"format": "uuid",
|
||||
"title": "Job Id",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"in": "header",
|
||||
"name": "X-Demo-User-Email",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "X-Demo-User-Email"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/AgentJobResponse"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Successful Response"
|
||||
},
|
||||
"422": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Validation Error"
|
||||
}
|
||||
},
|
||||
"summary": "Get Agent Job Route",
|
||||
"tags": [
|
||||
"agent-jobs"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/agent-jobs/{job_id}/cancel": {
|
||||
"post": {
|
||||
"operationId": "post_cancel_agent_job_api_agent_jobs__job_id__cancel_post",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "path",
|
||||
"name": "job_id",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"format": "uuid",
|
||||
"title": "Job Id",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"in": "header",
|
||||
"name": "X-Demo-User-Email",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "X-Demo-User-Email"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/AgentJobResponse"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Successful Response"
|
||||
},
|
||||
"422": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Validation Error"
|
||||
}
|
||||
},
|
||||
"summary": "Post Cancel Agent Job",
|
||||
"tags": [
|
||||
"agent-jobs"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/agent-jobs/{job_id}/retry": {
|
||||
"post": {
|
||||
"operationId": "post_retry_agent_job_api_agent_jobs__job_id__retry_post",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "path",
|
||||
"name": "job_id",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"format": "uuid",
|
||||
"title": "Job Id",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"in": "header",
|
||||
"name": "X-Demo-User-Email",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "X-Demo-User-Email"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"201": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/AgentJobResponse"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Successful Response"
|
||||
},
|
||||
"422": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Validation Error"
|
||||
}
|
||||
},
|
||||
"summary": "Post Retry Agent Job",
|
||||
"tags": [
|
||||
"agent-jobs"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/articles": {
|
||||
"get": {
|
||||
"operationId": "list_articles_route_api_articles_get",
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
|
||||
export type AgentJobErrorCategory = "FAILED_SCHEMA_VALIDATION" | "CLI_EXIT_CODE_FAILURE" | "TIMEOUT" | "MISSING_OUTPUT_FILE" | "UNSUPPORTED_CLAIMS_FOUND" | "SOURCE_RETRIEVAL_FAILED" | "RESEARCH_ARTIFACT_UPLOAD_FAILED" | "PUBLISH_DRY_RUN_FAILED" | "GIT_CHECKOUT_FAILED" | "GIT_COMMIT_FAILED" | "GIT_PUSH_NON_FAST_FORWARD" | "PUBLISH_VERIFICATION_FAILED";
|
||||
|
||||
export type AgentJobListResponse = {
|
||||
jobs: AgentJobSummary[];
|
||||
};
|
||||
|
||||
export type AgentJobOutput = {
|
||||
artifacts?: ResearchArtifactSummary[];
|
||||
error_category?: AgentJobErrorCategory | null;
|
||||
@@ -12,23 +16,38 @@ export type AgentJobOutput = {
|
||||
status: AgentJobStatus;
|
||||
};
|
||||
|
||||
export type AgentJobResponse = {
|
||||
job: AgentJobSummary;
|
||||
};
|
||||
|
||||
export type AgentJobStatus = "QUEUED" | "RUNNING" | "SUCCEEDED" | "FAILED" | "CANCELLED";
|
||||
|
||||
export type AgentJobSummary = {
|
||||
agent_profile: string;
|
||||
article_id: string;
|
||||
article_id?: string | null;
|
||||
attempt?: number;
|
||||
duration_ms?: number | null;
|
||||
error_category?: AgentJobErrorCategory | null;
|
||||
error_message?: string | null;
|
||||
exit_code?: number | null;
|
||||
finished_at?: string | null;
|
||||
id: string;
|
||||
input_files?: RunnerFileRef[];
|
||||
job_type: AgentJobType;
|
||||
output_files?: RunnerFileRef[];
|
||||
parent_job_id?: string | null;
|
||||
queued_at: string;
|
||||
started_at?: string | null;
|
||||
status: AgentJobStatus;
|
||||
stderr?: string;
|
||||
stdout?: string;
|
||||
workspace_path?: string | null;
|
||||
};
|
||||
|
||||
export type AgentJobTestCodexRequest = {
|
||||
fake_result?: string;
|
||||
};
|
||||
|
||||
export type AgentJobType = "BOUNDARY_QUESTIONS" | "PLAN_GENERATION" | "RESEARCH" | "EVIDENCE_MATRIX" | "SECTION_SCAFFOLD" | "DRAFT_ASSEMBLY" | "SEO_REVIEW" | "LANGUAGE_REVIEW" | "PUBLISH_DRY_RUN" | "PUBLISH_COMMIT" | "TEST_CODEX";
|
||||
|
||||
export type ArticleCreateRequest = {
|
||||
@@ -266,15 +285,15 @@ export type ScriptConfigVersionResponse = {
|
||||
export type ScriptConfigVersionStatus = "DRAFT" | "ACTIVE" | "DEPRECATED";
|
||||
|
||||
export type ScriptConfigVersionSummary = {
|
||||
activated_at?: string | null;
|
||||
created_at: string;
|
||||
created_by: string;
|
||||
activated_at?: string | null;
|
||||
diff: Record<string, unknown>;
|
||||
diff?: Record<string, unknown>;
|
||||
id: string;
|
||||
publishing_yaml_hash: string;
|
||||
rollback_target_version_id?: string | null;
|
||||
status: ScriptConfigVersionStatus;
|
||||
target_site_id: string;
|
||||
rollback_target_version_id?: string | null;
|
||||
transform_script_hash: string;
|
||||
version: number;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user