- commit
- 4796db4
- parent
- d7a83fd
- author
- im_wower
- date
- 2026-03-26 01:24:38 +0800 CST
merge: land T-S013 to T-S016
32 files changed,
+954,
-109
+9,
-9
1@@ -56,9 +56,9 @@
2
3 ### 本地只读观察面
4
5-- `http://100.71.210.78:4318`
6-- 由 `apps/status-api` 提供
7-- 只用于本地调试和过渡观察,不再作为默认对外业务接口
8+- 推荐入口已经收口到 `conductor` 的 `http://100.71.210.78:4317/v1/status` 与 `/v1/status/ui`
9+- `http://100.71.210.78:4318` 仍由 `apps/status-api` 提供兼容包装层
10+- 只用于本地调试、过渡观察和 legacy 路径兼容,不再作为默认对外业务接口
11
12 ### 浏览器控制面
13
14@@ -72,7 +72,7 @@
15
16 - `mini` 本地常驻进程
17 - 负责 heartbeat、租约续约、最小调度循环和 runtime 探针
18-- 是后续统一 discovery / control / task / run 接口的目标承载面
19+- 是后续统一 discovery / control / status-view / task / run 接口的目标承载面
20
21 ### legacy `control-api` surface(已移出当前仓库)
22
23@@ -82,10 +82,10 @@
24
25 ### `apps/status-api`
26
27-- `mini` 本地状态读取面
28+- `mini` 本地状态兼容包装层
29 - 默认读取 `BAA_CONDUCTOR_LOCAL_API` 对应的 conductor `/v1/system/state`
30 - `BAA_CONTROL_API_BASE` 只剩手工兼容回退语义
31-- 定位是迁移期本地只读观察服务,不是主控制面
32+- 继续保留 `/describe`、`/v1/status`、`/v1/status/ui`、`/ui` 等 legacy 本地观察合同,不是主控制面
33
34 ### `codexd`(已实现独立 daemon,仍在继续收口)
35
36@@ -219,9 +219,9 @@
37 ## 11. 当前已知残留
38
39 - `conductor-daemon` 和 runtime 脚本已经有 canonical 名字 `BAA_CONDUCTOR_PUBLIC_API_BASE` / `--public-api-base`,但仍保留 legacy 别名 `BAA_CONTROL_API_BASE` / `--control-api-base`
40-- `status-api` 仍是独立的本地只读观察面,是否并入 `conductor-daemon` 尚未定案
41-- 根验证入口已经落到 `pnpm lint` / `pnpm test`,但 `worker-runner` 和 runtime / e2e 检查还没全部接进去
42-- runtime 默认服务集合仍包含 `status-api`;是否继续维持默认安装/启动,尚未定案
43+- `conductor-daemon` 已承接 `/v1/status` 和 `/v1/status/ui`;`status-api` 退到显式 opt-in 的本地兼容包装层
44+- 根验证入口已经落到 `pnpm lint` / `pnpm test`,工作区还补了 `pnpm smoke`;但 on-node `mini` 检查还没收成单独 wrapper
45+- runtime 默认服务集合已经收口到 `conductor` + `codexd`;`status-api` 改成显式 opt-in 观察服务
46 - `conductor.makefile.so` 目前只回源 `conductor-daemon` 已有路由,尚未承接完整业务 API
47 - 线上和历史文档里仍可能残留 `control-api.makefile.so`、Cloudflare / D1 相关资产或表述
48 - 如果后续再次出现 app-server 未发出合法 `turn/completed` 就提前断流,应视为新的 child / transport 故障,而不是 reopen 已修复的 BUG-008 / BUG-010
+11,
-9
1@@ -88,7 +88,9 @@ docs/
2
3 - 所有新接口设计默认先落 `mini` 本地 `4317`
4 - 所有新公网说明统一写 `conductor.makefile.so`
5-- `status-api` 只作为本地只读观察面,不再作为默认对外业务接口
6+- 只读状态视图的推荐入口已收口到 `conductor` 的 `/v1/status` 和 `/v1/status/ui`
7+- `status-api` 只作为本地只读观察兼容层,不再作为默认对外业务接口
8+- `mini` on-node 静态+运行态检查统一入口是 `./scripts/runtime/verify-mini.sh`(仓库根可用 `pnpm verify:mini`)
9 - 运行中的浏览器插件代码以 [`plugins/baa-firefox`](./plugins/baa-firefox) 为准
10 - 当前正式浏览器 HTTP 面是 `/v1/browser/*`,只支持 Claude,且通过本地 `/ws/firefox` 转发到 Firefox 插件页面内 HTTP 代理
11 - `codexd` 目前还是半成品,不是已上线组件
12@@ -99,9 +101,9 @@ docs/
13
14 | 面 | 地址 | 定位 | 说明 |
15 | --- | --- | --- | --- |
16-| local API | `http://100.71.210.78:4317` | 唯一主接口、内网真相源 | 当前已承接 `/describe`、`/health`、`/version`、`/v1/capabilities`、`/v1/browser/*`、`/v1/system/state`、`/v1/controllers`、`/v1/tasks`、`/v1/runs`、`pause/resume/drain`、`/v1/exec`、`/v1/files/read` 和 `/v1/files/write`;其中 `/v1/browser/*` 当前只支持 Claude,host-ops 统一要求 `Authorization: Bearer <BAA_SHARED_TOKEN>` |
17+| local API | `http://100.71.210.78:4317` | 唯一主接口、内网真相源 | 当前已承接 `/describe`、`/health`、`/version`、`/v1/capabilities`、`/v1/status`、`/v1/status/ui`、`/v1/browser/*`、`/v1/system/state`、`/v1/controllers`、`/v1/tasks`、`/v1/runs`、`pause/resume/drain`、`/v1/exec`、`/v1/files/read` 和 `/v1/files/write`;其中 `/v1/browser/*` 当前只支持 Claude,host-ops 统一要求 `Authorization: Bearer <BAA_SHARED_TOKEN>` |
18 | public host | `https://conductor.makefile.so` | 唯一公网域名 | 由 VPS Nginx 回源到 `100.71.210.78:4317`;`/v1/exec` 和 `/v1/files/*` 不再允许匿名调用 |
19-| local status view | `http://100.71.210.78:4318` | 本地只读观察面 | 迁移期保留,不是主控制面 |
20+| local status view | `http://100.71.210.78:4318` | 本地只读观察兼容层 | 显式 opt-in 保留,继续提供 `/describe`、`/v1/status`、`/v1/status/ui` 和 `/ui` 等 legacy 合同,不是主控制面 |
21
22 legacy 兼容说明:
23
24@@ -113,21 +115,21 @@ legacy 兼容说明:
25
26 1. 把 `/describe`、能力发现、状态/任务/运行查询与控制动作并到 `mini` 本地 API,并保持 `conductor.makefile.so` 同步暴露。
27 2. 让浏览器、CLI、AI、运维文档全部默认走 `conductor.makefile.so` / `100.71.210.78:4317`。
28-3. 让 `status-api` 退回本地辅助视图,或并入 `conductor` 主接口。
29+3. 让 `status-api` 退回本地兼容包装层,并把 `/v1/status`、`/v1/status/ui` 并到 `conductor` 主接口。
30 4. 删除 `control-api.makefile.so`、Cloudflare Worker、D1 和 hand/shell 的主系统角色。
31
32 ## 当前最重要的事
33
34 - 保持 `mini` launchd、自启动和本地探针稳定
35 - 保持 `conductor.makefile.so -> 100.71.210.78:4317` 的链路稳定
36-- 把剩余测试入口继续收口到仓库根
37-- 决定 `status-api` 是否还应该留在默认 runtime 服务集合里
38+- 把剩余 on-node 检查继续收口成更统一的入口
39+- 继续清理仍依赖 `4318` wrapper 的旧脚本、书签和文档
40
41 ## 当前已知 gap
42
43-- `worker-runner` 还没有稳定包级 `test`,所以根 `pnpm test` 仍未覆盖它
44-- `status-api` 仍是独立的本地只读观察服务,而且仍在默认 runtime 安装/启动/检查集合里;是否继续保留这个默认位次,仍待后续任务决定
45-- `status-api` 已经默认读取 `conductor-daemon` 的 `/v1/system/state`,但仍是单独的本地只读观察服务;是否继续保留还是并入主接口,仍待后续任务决定
46+- `verify-mini.sh` 只收口静态检查和运行态探针;会话级链路回归仍要单独跑 `pnpm smoke` 或 `./scripts/runtime/codexd-e2e-smoke.sh`
47+- `status-api` 已降为显式 opt-in 的本地只读兼容包装层;旧调用方仍可能继续依赖 `4318`
48+- `status-api` 和 `conductor /v1/status` 现在共享同一套状态拼装/渲染语义;后续如果要删 `status-api`,还需要先盘点残留调用方
49
50 ## 本机能力层
51
+2,
-2
1@@ -8,10 +8,10 @@
2 "@baa-conductor/host-ops": "workspace:*"
3 },
4 "scripts": {
5- "build": "pnpm -C ../.. -F @baa-conductor/db build && pnpm -C ../.. -F @baa-conductor/host-ops build && pnpm exec tsc -p tsconfig.json",
6+ "build": "pnpm -C ../.. -F @baa-conductor/db build && pnpm -C ../.. -F @baa-conductor/host-ops build && pnpm -C ../.. -F @baa-conductor/status-api build && pnpm exec tsc -p tsconfig.json",
7 "dev": "pnpm run build && node dist/index.js",
8 "start": "node dist/index.js",
9 "test": "pnpm run build && node --test src/index.test.js",
10- "typecheck": "pnpm -C ../.. -F @baa-conductor/db build && pnpm -C ../.. -F @baa-conductor/host-ops build && pnpm exec tsc --noEmit -p tsconfig.json"
11+ "typecheck": "pnpm -C ../.. -F @baa-conductor/db build && pnpm -C ../.. -F @baa-conductor/host-ops build && pnpm -C ../.. -F @baa-conductor/status-api build && pnpm exec tsc --noEmit -p tsconfig.json"
12 }
13 }
+42,
-0
1@@ -1757,6 +1757,32 @@ test("handleConductorHttpRequest serves the migrated local business endpoints fr
2 localApiContext
3 );
4 assert.equal(parseJsonBody(systemStateResponse).data.mode, "paused");
5+
6+ const statusViewResponse = await handleConductorHttpRequest(
7+ {
8+ method: "GET",
9+ path: "/v1/status"
10+ },
11+ localApiContext
12+ );
13+ assert.equal(statusViewResponse.status, 200);
14+ const statusViewPayload = parseJsonBody(statusViewResponse);
15+ assert.deepEqual(Object.keys(statusViewPayload).sort(), ["data", "ok"]);
16+ assert.equal(statusViewPayload.data.mode, "paused");
17+ assert.equal(statusViewPayload.data.queueDepth, 0);
18+ assert.equal(statusViewPayload.data.activeRuns, 1);
19+
20+ const statusViewUiResponse = await handleConductorHttpRequest(
21+ {
22+ method: "GET",
23+ path: "/v1/status/ui"
24+ },
25+ localApiContext
26+ );
27+ assert.equal(statusViewUiResponse.status, 200);
28+ assert.equal(statusViewUiResponse.headers["content-type"], "text/html; charset=utf-8");
29+ assert.match(statusViewUiResponse.body, /JSON endpoint: <strong>\/v1\/status<\/strong>/u);
30+ assert.match(statusViewUiResponse.body, /HTML endpoint: <strong>\/v1\/status\/ui<\/strong>/u);
31 } finally {
32 await codexd.stop();
33 rmSync(hostOpsDir, {
34@@ -1961,6 +1987,21 @@ test("ConductorRuntime serves health and migrated local API endpoints over HTTP"
35 assert.equal(systemStatePayload.data.holder_id, "mini-main");
36 assert.equal(systemStatePayload.data.mode, "running");
37
38+ const statusViewResponse = await fetch(`${baseUrl}/v1/status`);
39+ assert.equal(statusViewResponse.status, 200);
40+ const statusViewPayload = await statusViewResponse.json();
41+ assert.deepEqual(Object.keys(statusViewPayload).sort(), ["data", "ok"]);
42+ assert.equal(statusViewPayload.data.mode, "running");
43+ assert.equal(statusViewPayload.data.queueDepth, 0);
44+ assert.equal(statusViewPayload.data.activeRuns, 0);
45+
46+ const statusViewUiResponse = await fetch(`${baseUrl}/v1/status/ui`);
47+ assert.equal(statusViewUiResponse.status, 200);
48+ assert.equal(statusViewUiResponse.headers.get("content-type"), "text/html; charset=utf-8");
49+ const statusViewUiHtml = await statusViewUiResponse.text();
50+ assert.match(statusViewUiHtml, /Readable automation state for people and browser controls\./u);
51+ assert.match(statusViewUiHtml, /HTML endpoint: <strong>\/v1\/status\/ui<\/strong>/u);
52+
53 const codexStatusResponse = await fetch(`${baseUrl}/v1/codex`);
54 assert.equal(codexStatusResponse.status, 200);
55 const codexStatusPayload = await codexStatusResponse.json();
56@@ -2031,6 +2072,7 @@ test("ConductorRuntime serves health and migrated local API endpoints over HTTP"
57 const businessDescribePayload = await businessDescribeResponse.json();
58 assert.equal(businessDescribePayload.data.surface, "business");
59 assert.match(JSON.stringify(businessDescribePayload.data.endpoints), /\/v1\/codex/u);
60+ assert.match(JSON.stringify(businessDescribePayload.data.endpoints), /\/v1\/status/u);
61
62 const controlDescribeResponse = await fetch(`${baseUrl}/describe/control`);
63 assert.equal(controlDescribeResponse.status, 200);
+70,
-0
1@@ -25,6 +25,10 @@ import {
2 type FileReadOperationRequest,
3 type FileWriteOperationRequest
4 } from "../../../packages/host-ops/dist/index.js";
5+// @ts-ignore conductor reuses the built status-api snapshot normalizer directly.
6+import { createStatusSnapshotFromControlApiPayload } from "../../status-api/dist/apps/status-api/src/data-source.js";
7+// @ts-ignore conductor reuses the built status-api HTML renderer directly.
8+import { renderStatusPage } from "../../status-api/dist/apps/status-api/src/render.js";
9
10 import {
11 jsonResponse,
12@@ -58,6 +62,10 @@ const CODEX_ROUTE_IDS = new Set([
13 const HOST_OPERATIONS_ROUTE_IDS = new Set(["host.exec", "host.files.read", "host.files.write"]);
14 const HOST_OPERATIONS_AUTH_HEADER = "Authorization: Bearer <BAA_SHARED_TOKEN>";
15 const HOST_OPERATIONS_WWW_AUTHENTICATE = 'Bearer realm="baa-conductor-host-ops"';
16+const STATUS_VIEW_HTML_HEADERS = {
17+ "cache-control": "no-store",
18+ "content-type": "text/html; charset=utf-8"
19+} as const;
20 const BROWSER_CLAUDE_PLATFORM = "claude";
21 const BROWSER_CLAUDE_ROOT_URL = "https://claude.ai/";
22 const BROWSER_CLAUDE_ORGANIZATIONS_PATH = "/api/organizations";
23@@ -322,6 +330,20 @@ const LOCAL_API_ROUTES: LocalApiRouteDefinition[] = [
24 pathPattern: "/v1/system/state",
25 summary: "读取本地系统状态"
26 },
27+ {
28+ id: "status.view.json",
29+ kind: "read",
30+ method: "GET",
31+ pathPattern: "/v1/status",
32+ summary: "读取兼容 status-api 的只读 JSON 状态视图"
33+ },
34+ {
35+ id: "status.view.ui",
36+ kind: "read",
37+ method: "GET",
38+ pathPattern: "/v1/status/ui",
39+ summary: "读取兼容 status-api 的只读 HTML 状态面板"
40+ },
41 {
42 id: "system.pause",
43 kind: "write",
44@@ -2408,6 +2430,8 @@ function routeBelongsToSurface(
45 "service.health",
46 "service.version",
47 "system.capabilities",
48+ "status.view.json",
49+ "status.view.ui",
50 "browser.status",
51 "browser.claude.open",
52 "browser.claude.send",
53@@ -2462,6 +2486,7 @@ function buildCapabilitiesData(
54 workflow: [
55 "GET /describe",
56 "GET /v1/capabilities",
57+ "GET /v1/status for the narrower read-only status view",
58 "GET /v1/browser if browser mediation is needed",
59 "GET /v1/system/state",
60 "GET /v1/browser/claude/current or /v1/tasks or /v1/codex",
61@@ -2601,6 +2626,12 @@ async function handleDescribeRead(context: LocalApiRequestContext, version: stri
62 path: "/v1/system/state",
63 curl: buildCurlExample(origin, requireRouteDefinition("system.state"))
64 },
65+ {
66+ title: "Read the read-only compatibility status view",
67+ method: "GET",
68+ path: "/v1/status",
69+ curl: buildCurlExample(origin, requireRouteDefinition("status.view.json"))
70+ },
71 {
72 title: "Pause local automation explicitly",
73 method: "POST",
74@@ -2631,6 +2662,7 @@ async function handleDescribeRead(context: LocalApiRequestContext, version: stri
75 ],
76 notes: [
77 "AI callers should prefer /describe/business for business queries and /describe/control for control actions.",
78+ "GET /v1/status and GET /v1/status/ui expose the narrow read-only compatibility status view; /v1/system/state remains the fuller control-oriented truth surface.",
79 "The formal /v1/browser/* surface currently supports Claude only and forwards browser work through the local Firefox bridge.",
80 "All /v1/codex routes proxy the independent codexd daemon; this process does not host Codex sessions itself.",
81 "POST /v1/exec and POST /v1/files/* require Authorization: Bearer <BAA_SHARED_TOKEN>; missing or wrong tokens return 401 JSON.",
82@@ -2690,6 +2722,12 @@ async function handleScopedDescribeRead(
83 path: "/v1/browser/claude/current",
84 curl: buildCurlExample(origin, requireRouteDefinition("browser.claude.current"))
85 },
86+ {
87+ title: "Read the compatibility status snapshot",
88+ method: "GET",
89+ path: "/v1/status",
90+ curl: buildCurlExample(origin, requireRouteDefinition("status.view.json"))
91+ },
92 {
93 title: "List recent tasks",
94 method: "GET",
95@@ -2714,6 +2752,7 @@ async function handleScopedDescribeRead(
96 ],
97 notes: [
98 "This surface is intended to be enough for business-query discovery without reading external docs.",
99+ "Use GET /v1/status for the narrow read-only compatibility snapshot and GET /v1/status/ui for the matching HTML panel.",
100 "The formal /v1/browser/* surface currently supports Claude only and rides on the local Firefox bridge.",
101 "All /v1/codex routes proxy the independent codexd daemon instead of an in-process bridge.",
102 "If you pivot to /describe/control for /v1/exec or /v1/files/*, those host-ops routes require Authorization: Bearer <BAA_SHARED_TOKEN>.",
103@@ -2865,6 +2904,33 @@ async function handleSystemStateRead(context: LocalApiRequestContext): Promise<C
104 );
105 }
106
107+async function loadStatusViewSnapshot(context: LocalApiRequestContext) {
108+ return createStatusSnapshotFromControlApiPayload(
109+ await buildSystemStateData(requireRepository(context.repository)),
110+ new Date(context.now())
111+ );
112+}
113+
114+async function handleStatusViewJsonRead(context: LocalApiRequestContext): Promise<ConductorHttpResponse> {
115+ return jsonResponse(200, {
116+ ok: true,
117+ data: await loadStatusViewSnapshot(context)
118+ });
119+}
120+
121+async function handleStatusViewUiRead(context: LocalApiRequestContext): Promise<ConductorHttpResponse> {
122+ return {
123+ status: 200,
124+ headers: {
125+ ...STATUS_VIEW_HTML_HEADERS
126+ },
127+ body: renderStatusPage(await loadStatusViewSnapshot(context), {
128+ htmlPaths: ["/v1/status/ui"],
129+ jsonPath: "/v1/status"
130+ })
131+ };
132+}
133+
134 async function handleBrowserStatusRead(context: LocalApiRequestContext): Promise<ConductorHttpResponse> {
135 return buildSuccessEnvelope(context.requestId, 200, buildBrowserStatusData(context));
136 }
137@@ -3436,6 +3502,10 @@ async function dispatchBusinessRoute(
138 return handleCodexTurnCreate(context);
139 case "system.state":
140 return handleSystemStateRead(context);
141+ case "status.view.json":
142+ return handleStatusViewJsonRead(context);
143+ case "status.view.ui":
144+ return handleStatusViewUiRead(context);
145 case "system.pause":
146 return handleSystemMutation(context, "paused");
147 case "system.resume":
+1,
-0
1@@ -64,6 +64,7 @@ test("status-api describe reports conductor local truth with legacy compatibilit
2 assert.equal(payload.data.truth_source.base_url, "http://100.71.210.78:4317");
3 assert.deepEqual(payload.data.notes, [
4 "Status API is read-only.",
5+ "Preferred entry lives on conductor: /v1/status and /v1/status/ui.",
6 "Default truth source comes from BAA_CONDUCTOR_LOCAL_API.",
7 "Use BAA_CONTROL_API_BASE only for legacy ad-hoc compatibility overrides."
8 ]);
+37,
-3
1@@ -1,11 +1,24 @@
2 import type { StatusSnapshot } from "./contracts.js";
3
4-export function renderStatusPage(snapshot: StatusSnapshot): string {
5+const DEFAULT_STATUS_JSON_PATH = "/v1/status";
6+const DEFAULT_STATUS_HTML_PATHS = ["/", "/v1/status/ui"] as const;
7+
8+export interface StatusPageRenderOptions {
9+ htmlPaths?: readonly string[];
10+ jsonPath?: string;
11+}
12+
13+export function renderStatusPage(
14+ snapshot: StatusSnapshot,
15+ options: StatusPageRenderOptions = {}
16+): string {
17 const modeLabel = formatMode(snapshot.mode);
18 const leaderLabel = snapshot.leaderHost ?? snapshot.leaderId ?? "No active leader lease";
19 const leaderDetail = snapshot.leaderId == null ? "Truth source did not report an active holder." : `holder_id=${snapshot.leaderId}`;
20 const leaseLabel = snapshot.leaseExpiresAt == null ? "No lease expiry" : formatTimestamp(snapshot.leaseExpiresAt);
21 const leaseDetail = snapshot.leaseActive ? "Lease is currently valid." : "Lease is missing or stale.";
22+ const jsonPath = normalizeStatusPath(options.jsonPath, DEFAULT_STATUS_JSON_PATH);
23+ const htmlPaths = normalizeStatusPathList(options.htmlPaths, DEFAULT_STATUS_HTML_PATHS);
24
25 return `<!doctype html>
26 <html lang="en">
27@@ -240,8 +253,8 @@ export function renderStatusPage(snapshot: StatusSnapshot): string {
28 </section>
29
30 <section class="footer">
31- <p class="meta">JSON endpoint: <strong>/v1/status</strong></p>
32- <p class="meta">HTML endpoint: <strong>/</strong> or <strong>/v1/status/ui</strong></p>
33+ <p class="meta">JSON endpoint: ${renderEndpointList([jsonPath])}</p>
34+ <p class="meta">HTML endpoint: ${renderEndpointList(htmlPaths)}</p>
35 <p class="meta">Observed at: ${escapeHtml(formatTimestamp(snapshot.observedAt))}</p>
36 </section>
37 </main>
38@@ -249,6 +262,27 @@ export function renderStatusPage(snapshot: StatusSnapshot): string {
39 </html>`;
40 }
41
42+function normalizeStatusPath(value: string | undefined, fallback: string): string {
43+ const normalized = value?.trim();
44+
45+ return normalized == null || normalized === "" ? fallback : normalized;
46+}
47+
48+function normalizeStatusPathList(
49+ value: readonly string[] | undefined,
50+ fallback: readonly string[]
51+): string[] {
52+ const normalized = value
53+ ?.map((entry) => entry.trim())
54+ .filter((entry) => entry !== "");
55+
56+ return normalized == null || normalized.length === 0 ? [...fallback] : normalized;
57+}
58+
59+function renderEndpointList(paths: readonly string[]): string {
60+ return paths.map((path) => `<strong>${escapeHtml(path)}</strong>`).join(" or ");
61+}
62+
63 function renderMetricCard(label: string, value: string, detail: string, accent = false): string {
64 return `<article class="card${accent ? " accent-panel" : ""}">
65 <p class="label">${escapeHtml(label)}</p>
+2,
-1
1@@ -162,7 +162,7 @@ function buildStatusApiDescribeData(options: StatusApiHandlerOptions): Record<st
2 name: "baa-conductor-status-api",
3 version: resolveStatusApiVersion(options.version),
4 description:
5- "Read-only status view service. It does not own conductor truth; it renders a narrow status snapshot for humans, browsers, and AI clients.",
6+ "Read-only compatibility status view service. It does not own conductor truth; new callers should prefer conductor /v1/status while this service preserves the legacy local status-api contract.",
7 pid: processInfo.pid,
8 uptime_sec: processInfo.uptimeSec,
9 cwd: processInfo.cwd,
10@@ -191,6 +191,7 @@ function buildStatusApiDescribeData(options: StatusApiHandlerOptions): Record<st
11 ],
12 notes: [
13 "Status API is read-only.",
14+ "Preferred entry lives on conductor: /v1/status and /v1/status/ui.",
15 "Default truth source comes from BAA_CONDUCTOR_LOCAL_API.",
16 "Use BAA_CONTROL_API_BASE only for legacy ad-hoc compatibility overrides."
17 ]
+1,
-0
1@@ -5,6 +5,7 @@
2 "main": "dist/index.js",
3 "scripts": {
4 "build": "pnpm exec tsc -p tsconfig.json && BAA_DIST_DIR=apps/worker-runner/dist BAA_DIST_ENTRY=apps/worker-runner/src/index.js BAA_IMPORT_ALIASES='@baa-conductor/logging=../../../packages/logging/src/index.js;@baa-conductor/checkpointing=../../../packages/checkpointing/src/index.js' BAA_FIX_RELATIVE_EXTENSIONS=true pnpm -C ../.. run build:runtime-postprocess",
5+ "test": "pnpm run build && node --test src/index.test.js",
6 "typecheck": "pnpm exec tsc --noEmit -p tsconfig.json"
7 }
8 }
+171,
-0
1@@ -0,0 +1,171 @@
2+import assert from "node:assert/strict";
3+import { mkdir, mkdtemp, readFile, readdir, rm } from "node:fs/promises";
4+import { tmpdir } from "node:os";
5+import { join } from "node:path";
6+import test from "node:test";
7+
8+import {
9+ prepareStepRun,
10+ runStep
11+} from "../dist/index.js";
12+
13+async function createFixture(t) {
14+ const rootDir = await mkdtemp(join(tmpdir(), "baa-worker-runner-"));
15+ const repoRoot = join(rootDir, "repo");
16+ const worktreePath = join(rootDir, "worktrees", "task");
17+ const runsRootDir = join(rootDir, "runs");
18+
19+ await mkdir(worktreePath, { recursive: true });
20+ await mkdir(repoRoot, { recursive: true });
21+
22+ t.after(async () => {
23+ await rm(rootDir, { recursive: true, force: true });
24+ });
25+
26+ return {
27+ repoRoot,
28+ worktreePath,
29+ runsRootDir
30+ };
31+}
32+
33+function createRequest(runtime, overrides = {}) {
34+ return {
35+ taskId: "task-worker-runner",
36+ stepId: "step-001",
37+ runId: "run-001",
38+ attempt: 1,
39+ stepName: "Prepare local run",
40+ stepKind: "review",
41+ workerKind: "shell",
42+ timeoutSec: 45,
43+ runtime,
44+ createdAt: "2026-03-26T00:00:00.000Z",
45+ checkpoint: {
46+ mode: "capture",
47+ logTailLines: 10,
48+ summaryHint: "Prepared by worker-runner test."
49+ },
50+ ...overrides
51+ };
52+}
53+
54+test("prepareStepRun creates local run layout without checkpoints when disabled", async (t) => {
55+ const runtime = await createFixture(t);
56+ const run = await prepareStepRun(
57+ createRequest(runtime, {
58+ checkpoint: {
59+ mode: "disabled"
60+ }
61+ })
62+ );
63+
64+ assert.equal(run.metadata.checkpointMode, "disabled");
65+ assert.equal(run.state.status, "prepared");
66+ assert.equal(run.state.lastEventSeq, 1);
67+ assert.equal(run.checkpoint.mode, "disabled");
68+ assert.deepEqual(run.checkpoint.supportedTypes, []);
69+ assert.equal(run.checkpoint.records.length, 0);
70+ assert.deepEqual(
71+ run.logSession.worker.entries.map((entry) => entry.type),
72+ ["run_prepared"]
73+ );
74+
75+ const checkpointFiles = await readdir(run.logPaths.checkpointsDir);
76+ const workerLog = await readFile(run.logPaths.workerLogPath, "utf8");
77+
78+ assert.deepEqual(checkpointFiles, []);
79+ assert.match(workerLog, /"type":"run_prepared"/);
80+});
81+
82+test("runStep default placeholder executor persists capture checkpoints and lifecycle logs", async (t) => {
83+ const runtime = await createFixture(t);
84+ const result = await runStep(createRequest(runtime));
85+
86+ assert.equal(result.ok, true);
87+ assert.equal(result.outcome, "prepared");
88+ assert.equal(result.state.status, "prepared");
89+ assert.equal(result.metrics.checkpointCount, 2);
90+ assert.equal(result.checkpoint.records.length, 2);
91+ assert.equal(result.logSummary.lifecycleEventCount, 6);
92+ assert.equal(result.logSummary.stdoutChunkCount, 0);
93+ assert.equal(result.logSummary.stderrChunkCount, 0);
94+ assert.equal(result.state.checkpointSeq, 2);
95+ assert.deepEqual(
96+ result.lifecycleEvents.map((entry) => entry.type),
97+ [
98+ "run_prepared",
99+ "checkpoint_slot_reserved",
100+ "worker_started",
101+ "worker_execution_deferred",
102+ "worker_exited",
103+ "step_prepared"
104+ ]
105+ );
106+ assert.equal(result.checkpoint.records[0]?.type, "summary");
107+ assert.equal(result.checkpoint.records[1]?.type, "log_tail");
108+ assert.match(
109+ result.checkpoint.records[1]?.contentText ?? "",
110+ /\[worker\]/
111+ );
112+ assert.ok(result.artifacts.some((artifact) => artifact.name === "meta.json"));
113+ assert.ok(result.artifacts.some((artifact) => artifact.name === "checkpoints"));
114+
115+ const checkpointFiles = await readdir(result.logPaths.checkpointsDir);
116+ const workerLog = await readFile(result.logPaths.workerLogPath, "utf8");
117+
118+ assert.equal(checkpointFiles.length, 2);
119+ assert.match(workerLog, /"type":"worker_execution_deferred"/);
120+});
121+
122+test("runStep records blocked outcomes, stream logs, and follow-up metadata from a custom executor", async (t) => {
123+ const runtime = await createFixture(t);
124+ const result = await runStep(createRequest(runtime), {
125+ async execute(run) {
126+ return {
127+ ok: false,
128+ outcome: "blocked",
129+ summary: `Manual approval required for ${run.request.stepId}.`,
130+ blocked: true,
131+ needsHuman: true,
132+ stdout: ["stdout line"],
133+ stderr: ["stderr line"],
134+ suggestedFollowup: [
135+ {
136+ stepName: "Ask operator",
137+ stepKind: "planner",
138+ reason: "Approval required before continuing."
139+ }
140+ ],
141+ artifacts: [
142+ {
143+ name: "handoff.txt",
144+ kind: "artifact",
145+ path: join(run.logPaths.artifactsDir, "handoff.txt"),
146+ description: "Operator handoff note."
147+ }
148+ ],
149+ exitCode: 17
150+ };
151+ }
152+ });
153+
154+ assert.equal(result.ok, false);
155+ assert.equal(result.outcome, "blocked");
156+ assert.equal(result.blocked, true);
157+ assert.equal(result.needsHuman, true);
158+ assert.equal(result.state.status, "blocked");
159+ assert.equal(result.state.exitCode, 17);
160+ assert.equal(result.logSummary.stdoutChunkCount, 1);
161+ assert.equal(result.logSummary.stderrChunkCount, 1);
162+ assert.equal(result.lifecycleEvents.at(-1)?.type, "step_blocked");
163+ assert.equal(result.suggestedFollowup.length, 1);
164+ assert.equal(result.suggestedFollowup[0]?.stepKind, "planner");
165+ assert.ok(result.artifacts.some((artifact) => artifact.name === "handoff.txt"));
166+
167+ const stdoutLog = await readFile(result.logPaths.stdoutLogPath, "utf8");
168+ const stderrLog = await readFile(result.logPaths.stderrLogPath, "utf8");
169+
170+ assert.equal(stdoutLog, "stdout line\n");
171+ assert.equal(stderrLog, "stderr line\n");
172+});
+36,
-10
1@@ -20,7 +20,8 @@
2 原则:
3
4 - `conductor-daemon` 本地 API 是这些业务接口的真相源
5-- `status-api` 仍是只读状态视图
6+- 推荐只读状态视图入口是 `conductor` 上的 `GET /v1/status` 和 `GET /v1/status/ui`
7+- `status-api` 保留为本地只读状态视图兼容包装层
8 - Cloudflare Worker / D1 / `control-api.makefile.so` 都只剩 legacy 兼容或残留依赖盘点背景,不再是这批业务接口的主路径
9
10 ## 入口
11@@ -28,10 +29,10 @@
12 | 服务 | 地址 | 说明 |
13 | --- | --- | --- |
14 | conductor public host | `https://conductor.makefile.so` | 唯一公网入口;VPS Nginx 回源到同一个 `conductor-daemon` local-api |
15-| conductor-daemon local-api | `BAA_CONDUCTOR_LOCAL_API`,默认可用值如 `http://127.0.0.1:4317` | 本地真相源;承接 describe/health/version/capabilities/browser/system/controllers/tasks/codex/host-ops |
16+| conductor-daemon local-api | `BAA_CONDUCTOR_LOCAL_API`,默认可用值如 `http://127.0.0.1:4317` | 本地真相源;承接 describe/health/version/capabilities/status/browser/system/controllers/tasks/codex/host-ops |
17 | codexd local-api | `BAA_CODEXD_LOCAL_API_BASE`,默认可用值如 `http://127.0.0.1:4319` | 独立 `codexd` 本地服务;支持 `GET /describe` 自描述;`conductor-daemon` 的 `/v1/codex/*` 只代理到这里 |
18 | conductor-daemon local-firefox-ws | 由 `BAA_CONDUCTOR_LOCAL_API` 派生,例如 `ws://127.0.0.1:4317/ws/firefox` | 本地 Firefox 插件双向 bridge;复用同一个 listener,不单独开公网端口 |
19-| status-api local view | `http://127.0.0.1:4318` | 本地只读状态 JSON 和 HTML 视图;默认从 `BAA_CONDUCTOR_LOCAL_API` 的 `/v1/system/state` 取数,不承担公网入口角色 |
20+| status-api local view | `http://127.0.0.1:4318` | 本地只读状态兼容包装层;继续提供 `/describe`、`/v1/status`、`/v1/status/ui` 和 `/ui`,默认从 `BAA_CONDUCTOR_LOCAL_API` 的 `/v1/system/state` 取数,不承担公网入口角色 |
21
22 ## Describe First
23
24@@ -39,11 +40,12 @@
25
26 1. `GET ${BAA_CONDUCTOR_LOCAL_API}/describe/business` 或 `GET ${BAA_CONDUCTOR_LOCAL_API}/describe/control`
27 2. 如有需要,再看 `GET ${BAA_CONDUCTOR_LOCAL_API}/v1/capabilities`
28-3. 如果是控制动作,再看 `GET ${BAA_CONDUCTOR_LOCAL_API}/v1/system/state`
29-4. 按需查看 `browser`、`controllers`、`tasks`、`codex`
30-5. 如果要做本机 shell / 文件操作,先读 `GET ${BAA_CONDUCTOR_LOCAL_API}/describe/control` 返回里的 `host_operations`,并准备 `Authorization: Bearer <BAA_SHARED_TOKEN>`
31-6. 只有在明确需要写操作时,再调用 `pause` / `resume` / `drain` 或 `host-ops`
32-7. 只有在明确需要浏览器双向通讯时,再手动连接 `/ws/firefox`
33+3. 如果只需要窄的只读状态视图,先看 `GET ${BAA_CONDUCTOR_LOCAL_API}/v1/status`
34+4. 如果是控制动作或需要完整 truth shape,再看 `GET ${BAA_CONDUCTOR_LOCAL_API}/v1/system/state`
35+5. 按需查看 `browser`、`controllers`、`tasks`、`codex`
36+6. 如果要做本机 shell / 文件操作,先读 `GET ${BAA_CONDUCTOR_LOCAL_API}/describe/control` 返回里的 `host_operations`,并准备 `Authorization: Bearer <BAA_SHARED_TOKEN>`
37+7. 只有在明确需要写操作时,再调用 `pause` / `resume` / `drain` 或 `host-ops`
38+8. 只有在明确需要浏览器双向通讯时,再手动连接 `/ws/firefox`
39
40 如果是直接调用 `codexd`:
41
42@@ -90,6 +92,19 @@
43 - 成功时直接返回最新 system state,而不是只回一个 ack
44 - 这样 HTTP 客户端和 WS `action_request` 都能复用同一份状态合同
45
46+### 只读状态视图接口
47+
48+| 方法 | 路径 | 说明 |
49+| --- | --- | --- |
50+| `GET` | `/v1/status` | 与 `status-api` 对齐的窄 JSON 状态快照;适合人和 AI 做只读观察 |
51+| `GET` | `/v1/status/ui` | 与 `status-api` 对齐的 HTML 状态面板 |
52+
53+说明:
54+
55+- `/v1/system/state` 仍是控制型 truth shape,不因为兼容状态视图而改合同
56+- 新调用方默认优先走 `conductor` 上的这两个入口
57+- `status-api` 仅在需要保留 `4318` / `/ui` / `/describe` 兼容位次时启用
58+
59 ### Codex 代理接口
60
61 这些路由固定代理到独立 `codexd`:
62@@ -271,7 +286,12 @@ host-ops 约定:
63
64 ## Status API
65
66-`status-api` 仍是本地只读视图服务,不拥有真相,也不承担公网入口角色。
67+`status-api` 现在是本地只读状态兼容包装层,不拥有真相,也不承担公网入口角色。
68+
69+推荐入口:
70+
71+- 新调用方默认使用 `conductor` 上的 `GET /v1/status` 和 `GET /v1/status/ui`
72+- 只有还需要 `4318`、`/describe`、`/ui` 这组 legacy 路径时,才显式启动 `status-api`
73
74 truth source:
75
76@@ -279,8 +299,9 @@ truth source:
77 - `https://conductor.makefile.so` 是同一套 conductor 主接口的公网入口;只有本地 `4317` 不可达时才需要显式改到公网
78 - `BAA_CONDUCTOR_PUBLIC_API_BASE` 是 `conductor-daemon` upstream/public API base 的 canonical 变量名;legacy `BAA_CONTROL_API_BASE` 只保留两个兼容点:`conductor-daemon` 把它当别名,`status-api` 只在手工或旧配置缺少 `BAA_CONDUCTOR_LOCAL_API` 时回退使用
79 - conductor launchd 安装副本会同时写入 `BAA_CONDUCTOR_PUBLIC_API_BASE` 和 legacy `BAA_CONTROL_API_BASE`;默认 launchd 不再给 `status-api` 写入这些变量
80+- runtime 默认安装/启动/检查集合现在只包含 `conductor` 和 `codexd`;`status-api` 只在显式 `--service status-api` 或 `install-mini --with-status-api` 时启用
81 - 如果旧文档或配置里还出现 legacy `control-api.makefile.so`,只能按迁移兼容 / 残留依赖盘点目标理解,绝不再是默认或 canonical truth source
82-- `status-api` 负责把该状态整理成 JSON 或 HTML
83+- `status-api` 和 `conductor /v1/status*` 共享同一套状态拼装和 HTML 渲染语义
84
85 当前端点:
86
87@@ -309,6 +330,11 @@ LOCAL_API_BASE="${BAA_CONDUCTOR_LOCAL_API:-http://127.0.0.1:4317}"
88 curl "${LOCAL_API_BASE}/v1/system/state"
89 ```
90
91+```bash
92+LOCAL_API_BASE="${BAA_CONDUCTOR_LOCAL_API:-http://127.0.0.1:4317}"
93+curl "${LOCAL_API_BASE}/v1/status"
94+```
95+
96 ```bash
97 LOCAL_API_BASE="${BAA_CONDUCTOR_LOCAL_API:-http://127.0.0.1:4317}"
98 curl "${LOCAL_API_BASE}/v1/tasks?limit=5"
+32,
-6
1@@ -4,8 +4,9 @@
2
3 - `pnpm lint`
4 - `pnpm test`
5+- `pnpm smoke`
6
7-它们的目标是给协作者一个在仓库根可重复执行的最小验收口径,而不是替代所有 smoke / runtime / on-node 检查。
8+它们的目标是给协作者一个在仓库根可重复执行的主线验收口径,而不是替代所有 on-node 检查。
9
10 ## `pnpm lint`
11
12@@ -34,23 +35,48 @@
13 - `@baa-conductor/host-ops`
14 - `@baa-conductor/codex-app-server`
15 - `@baa-conductor/codex-exec`
16+- `@baa-conductor/worker-runner`
17 - `@baa-conductor/status-api`
18 - `@baa-conductor/conductor-daemon`
19 - `@baa-conductor/codexd`
20
21-脚本会按顺序执行每个包自己的 `pnpm --filter <pkg> test`,这样失败点能直接定位到具体包。`@baa-conductor/status-api` 的包级 `test` 会先执行自身 `build`,再跑 `node --test src/index.test.js`,不依赖人工交互或线上环境。
22+脚本会按顺序执行每个包自己的 `pnpm --filter <pkg> test`,这样失败点能直接定位到具体包。`@baa-conductor/status-api` 和 `@baa-conductor/worker-runner` 的包级 `test` 都会先执行自身 `build`,再跑 `node --test src/index.test.js`,不依赖人工交互或线上环境。
23
24 刻意不覆盖:
25
26-- `@baa-conductor/worker-runner` 等当前没有稳定 `test` 脚本的工作区
27+- 仍未提供稳定 `test` 脚本的工作区
28 - 包内 `smoke` 命令
29-- `scripts/runtime/*.sh`、`tests/**/*.mjs` 这类更慢或需要额外运行时条件的端到端检查
30+- `scripts/runtime/*.sh`、`tests/**/*.mjs` 这类更慢或更接近 runtime 的检查
31+
32+## `pnpm smoke`
33+
34+`pnpm smoke` 会顺序执行:
35+
36+1. `pnpm build`
37+2. `node --test scripts/runtime/public-api-base.test.mjs`
38+3. `node --test tests/control-api/control-api-smoke.test.mjs`
39+4. `node --test tests/codexd/codexd-e2e-smoke.test.mjs`
40+5. `node --test tests/browser/browser-control-e2e-smoke.test.mjs`
41+
42+覆盖范围:
43+
44+- runtime 脚本的 conductor public API compatibility 行为
45+- legacy `control-api-worker` / Cloudflare / importer absence 约束
46+- codexd 本地 app-server 会话链路 e2e smoke
47+- browser-control 本地 Firefox bridge e2e smoke
48+
49+说明:
50+
51+- 这条入口比 `pnpm test` 慢,因为会先执行整仓 `build`
52+- 它只覆盖仓库内可自举的 smoke,不替代真实 `mini` 节点上的 launchd / HTTP / port 检查
53
54 ## 额外检查
55
56 以下验证仍建议按场景单独执行:
57
58 - `pnpm typecheck`
59+- `pnpm verify:mini`
60 - `./scripts/runtime/check-node.sh --node mini`
61-- `./scripts/runtime/codexd-e2e-smoke.sh`
62-- `./scripts/runtime/browser-control-e2e-smoke.sh`
63+- `./scripts/runtime/check-launchd.sh --node mini`
64+
65+其中 `pnpm verify:mini` 是 `mini` 节点 on-node 的统一入口:默认检查 `conductor`、`codexd`,并顺序执行 `check-launchd.sh` 与 `check-node.sh --skip-static-check`。需要把可选观察面纳入时,再显式加 `pnpm verify:mini --service status-api`。
+24,
-8
1@@ -18,7 +18,8 @@
2 - codexd local API: `http://127.0.0.1:4319`
3 - codexd event stream: `ws://127.0.0.1:4319/v1/codexd/events`
4 - canonical public host: `https://conductor.makefile.so`
5-- `status-api` `http://100.71.210.78:4318` 只作为本地只读观察面,默认回源 `BAA_CONDUCTOR_LOCAL_API`,当前 canonical 值是 `http://100.71.210.78:4317`
6+- `conductor` `http://100.71.210.78:4317` 现在直接提供 `/v1/status` 和 `/v1/status/ui` 只读状态视图
7+- `status-api` `http://100.71.210.78:4318` 只作为本地只读兼容包装层,默认回源 `BAA_CONDUCTOR_LOCAL_API`
8 - `https://conductor.makefile.so` 是同一套 conductor 主接口的公网入口
9 - `BAA_CONDUCTOR_PUBLIC_API_BASE` 是 `conductor` upstream/public API base 的 canonical 变量名;launchd 会连同 legacy `BAA_CONTROL_API_BASE` 一起写给 `conductor`
10 - `status-api` 只保留对 legacy `BAA_CONTROL_API_BASE` 的代码层兼容回退,不再把它当 canonical truth source
11@@ -27,9 +28,9 @@
12
13 ## 当前正式服务
14
15-- `conductor`: `launchd` 托管的主控制面,承载 `4317` 本地 API
16+- `conductor`: `launchd` 托管的主控制面,承载 `4317` 本地 API,也承接 `/v1/status` 和 `/v1/status/ui`
17 - `codexd`: `launchd` 托管的独立 Codex 运行面,只走 `codex app-server` 路线,监听 `127.0.0.1:4319`
18-- `status-api`: `launchd` 托管的本地只读观察面,监听 `4318`,默认读取 `4317` 上的 conductor `/v1/system/state`
19+- `status-api`: 可选 `launchd` 本地只读兼容包装层,监听 `4318`,默认读取 `4317` 上的 conductor `/v1/system/state`
20
21 `codexd` 正式能力面只保留:
22
23@@ -49,25 +50,40 @@
24 1. `./scripts/runtime/install-mini.sh`
25 2. `./scripts/runtime/status-launchd.sh`
26 3. `./scripts/runtime/restart-launchd.sh`
27-4. `./scripts/runtime/check-node.sh --node mini`
28+4. `./scripts/runtime/verify-mini.sh`
29 5. `./scripts/runtime/codexd-e2e-smoke.sh`
30 6. `./scripts/runtime/browser-control-e2e-smoke.sh`
31
32+如果还需要保留 `4318` 的 legacy 状态包装层,再显式加:
33+
34+7. `./scripts/runtime/install-mini.sh --with-status-api`
35+8. `./scripts/runtime/status-launchd.sh --service status-api`
36+
37 ## 当前推荐入口
38
39 - 安装并切到当前正式仓库路径:
40 - `./scripts/runtime/install-mini.sh`
41+- 读取默认只读状态视图:
42+ - `curl "${BAA_CONDUCTOR_LOCAL_API:-http://100.71.210.78:4317}/v1/status"`
43+- 如果旧脚本仍要求 `4318` wrapper,再安装:
44+ - `./scripts/runtime/install-mini.sh --with-status-api`
45 - 查看 `launchd` / HTTP 状态:
46 - `./scripts/runtime/status-launchd.sh`
47- - 默认同时检查 `conductor`、`codexd`、`status-api`
48+ - 默认同时检查 `conductor`、`codexd`
49+ - 需要确认 `4318` 兼容包装层时,用 `--service status-api`
50 - 停止 / 启动 / 重启:
51 - `./scripts/runtime/stop-launchd.sh`
52 - `./scripts/runtime/start-launchd.sh`
53 - `./scripts/runtime/restart-launchd.sh`
54- - 默认同时操作 `conductor`、`codexd`、`status-api`
55- - 需要单独管理时,用 `--service codexd`
56+ - 默认同时操作 `conductor`、`codexd`
57+ - 需要单独管理时,用 `--service codexd`、`--service conductor` 或 `--service status-api`
58 - 节点检查:
59- - `./scripts/runtime/check-node.sh --node mini`
60+ - `./scripts/runtime/verify-mini.sh`
61+ - 或在仓库根跑 `pnpm verify:mini`
62+ - 默认顺序执行 `check-launchd.sh` + `check-node.sh --skip-static-check`
63+ - 默认只检查 `conductor`、`codexd`
64+ - 需要把 `4318` 兼容包装层纳入检查时,显式加 `--service status-api`
65+ - 需要单独定位静态或运行态问题时,再直接调用底层 `check-launchd.sh` / `check-node.sh`
66 - 会话链路 smoke:
67 - `./scripts/runtime/codexd-e2e-smoke.sh`
68 - 会起临时 `codexd` + `conductor`,覆盖 `codexd status`、`GET /v1/codex`、session create/read、turn create/read,以及 `logs/codexd/**`、`state/codexd/**` 落盘
+11,
-3
1@@ -121,12 +121,20 @@ Firefox WS 派生规则:
2 --node mini \
3 --service conductor \
4 --service codexd \
5- --service status-api \
6 --shared-token-file /Users/george/.config/baa-conductor/shared-token.txt \
7 --local-api-base http://100.71.210.78:4317 \
8 --local-api-allowed-hosts 100.71.210.78 \
9- --codexd-local-api-base http://127.0.0.1:4319 \
10- --status-api-host 100.71.210.78
11+ --codexd-local-api-base http://127.0.0.1:4319
12 ```
13
14 默认 mini 安装不需要显式传 `--public-api-base`;只有 `conductor` 的 upstream/public API base 不是 `https://conductor.makefile.so` 时才需要覆盖。`--control-api-base` 仍可作为 legacy 兼容别名使用;如果同一轮 CLI 同时给出新旧参数名,`--public-api-base` 优先。脚本现在也只会把这些变量写给 `conductor` 安装副本;`status-api` 默认仍然优先读取 `--local-api-base` 对应的 conductor 主接口。
15+
16+如果要安装可选状态观察面,再单独执行:
17+
18+```bash
19+./scripts/runtime/install-launchd.sh \
20+ --repo-dir /Users/george/code/baa-conductor \
21+ --node mini \
22+ --service status-api \
23+ --status-api-host 100.71.210.78
24+```
+23,
-8
1@@ -6,7 +6,7 @@
2
3 - `conductor` 由 `launchd` 托管,并承载 canonical local API `http://100.71.210.78:4317`
4 - `codexd` 由 `launchd` 托管,并作为正式独立 Codex 运行面监听 `http://127.0.0.1:4319`
5-- `status-api` 随默认安装一起部署,但只作为本地只读观察面,默认从 `BAA_CONDUCTOR_LOCAL_API` 读取 `/v1/system/state`
6+- `status-api` 是显式 opt-in 的本地只读观察面,默认从 `BAA_CONDUCTOR_LOCAL_API` 读取 `/v1/system/state`
7 - 工作目录固定到 `/Users/george/code/baa-conductor`
8 - 通过仓库内脚本统一安装、启动、停止、重启与验证
9
10@@ -20,10 +20,16 @@
11
12 1. 初始化 runtime 目录
13 2. 构建仓库
14-3. 渲染并安装 `conductor` / `codexd` / `status-api` 的 LaunchAgents
15+3. 渲染并安装默认 `conductor` / `codexd` 的 LaunchAgents
16 4. 重启 launchd 服务
17 5. 跑静态检查和节点检查
18
19+如果还要把本地状态观察面一起装上,用:
20+
21+```bash
22+./scripts/runtime/install-mini.sh --with-status-api
23+```
24+
25 默认会把共享 token 收口到:
26
27 - `~/.config/baa-conductor/shared-token.txt`
28@@ -78,7 +84,7 @@
29 ./scripts/runtime/restart-launchd.sh
30 ```
31
32-这些命令默认同时操作 `conductor`、`codexd`、`status-api`。需要单独管理时,用 `--service codexd`、`--service conductor` 或 `--service status-api`。
33+这些命令默认同时操作 `conductor`、`codexd`。需要单独管理可选观察面时,再显式加 `--service status-api`。
34
35 例如只重启 `codexd`:
36
37@@ -96,7 +102,7 @@
38
39 ## 渲染安装副本
40
41-完整 mini 安装副本示例:
42+默认 mini 安装副本示例:
43
44 ```bash
45 ./scripts/runtime/install-launchd.sh \
46@@ -104,17 +110,26 @@
47 --node mini \
48 --service conductor \
49 --service codexd \
50- --service status-api \
51 --install-dir /Users/george/Library/LaunchAgents \
52 --shared-token-file /Users/george/.config/baa-conductor/shared-token.txt \
53 --local-api-base http://100.71.210.78:4317 \
54 --local-api-allowed-hosts 100.71.210.78 \
55- --codexd-local-api-base http://127.0.0.1:4319 \
56- --status-api-host 100.71.210.78
57+ --codexd-local-api-base http://127.0.0.1:4319
58 ```
59
60 默认 mini 安装不需要显式传 `--public-api-base`;只有 `conductor` 的 upstream/public API base 不是 `https://conductor.makefile.so` 时才需要覆盖。`--control-api-base` 仍可作为 legacy 兼容别名使用;如果新旧参数同时出现,`--public-api-base` 优先。脚本现在会把 `BAA_CONDUCTOR_PUBLIC_API_BASE` 和 legacy `BAA_CONTROL_API_BASE` 一起写给 `conductor` 安装副本;`status-api` 实际默认仍然优先读 `--local-api-base http://100.71.210.78:4317`。
61
62+如果需要本地状态观察面,再显式安装:
63+
64+```bash
65+./scripts/runtime/install-launchd.sh \
66+ --repo-dir /Users/george/code/baa-conductor \
67+ --node mini \
68+ --service status-api \
69+ --install-dir /Users/george/Library/LaunchAgents \
70+ --status-api-host 100.71.210.78
71+```
72+
73 单独安装 `codexd`:
74
75 ```bash
76@@ -149,7 +164,7 @@
77
78 - `conductor`: 读取 `BAA_CONDUCTOR_LOCAL_API`
79 - `codexd`: 读取 `BAA_CODEXD_LOCAL_API_BASE`
80-- `status-api`: 读取 `BAA_STATUS_API_HOST` 并按默认端口 `4318` 探活
81+- `status-api`: 只有显式选择时才读取 `BAA_STATUS_API_HOST` 并按默认端口 `4318` 探活
82
83 mini 正确安装后,建议追加一轮代理验证:
84
+37,
-14
1@@ -4,11 +4,38 @@
2
3 - `conductor` `http://100.71.210.78:4317`
4 - `codexd` `http://127.0.0.1:4319`
5-- `status-api` `http://100.71.210.78:4318`,默认读取 `http://100.71.210.78:4317/v1/system/state`
6+- `status-api` `http://100.71.210.78:4318`,是显式 opt-in 的本地只读观察面,默认读取 `http://100.71.210.78:4317/v1/system/state`
7
8 其中 `codexd` 的正式产品面仍是 `status / sessions / turn / events`,但 on-node 运维探针只要求 `/healthz` 和 `/v1/codexd/status`。会话级端到端链路单独由仓库 smoke 覆盖。
9
10-## 1. 构建与静态检查
11+## 1. 推荐入口:统一 mini verify wrapper
12+
13+```bash
14+./scripts/runtime/verify-mini.sh \
15+ --repo-dir /Users/george/code/baa-conductor \
16+ --install-dir /Users/george/Library/LaunchAgents \
17+ --shared-token-file /Users/george/.config/baa-conductor/shared-token.txt \
18+ --local-api-base http://100.71.210.78:4317 \
19+ --local-api-allowed-hosts 100.71.210.78 \
20+ --codexd-api-base http://127.0.0.1:4319 \
21+ --expected-rolez leader \
22+ --check-loaded
23+```
24+
25+这条入口会顺序执行:
26+
27+- `check-launchd.sh`
28+- `check-node.sh --skip-static-check`
29+
30+说明:
31+
32+- 默认服务集合是 `conductor`、`codexd`
33+- `status-api` 仍是显式 opt-in;如果已经安装,再额外加 `--service status-api`
34+- 需要覆盖 `status-api` 地址时,再补 `--status-api-base http://100.71.210.78:4318 --status-api-host 100.71.210.78`
35+- 如需从仓库根调用,可以用 `pnpm verify:mini --check-loaded`
36+- 会话级链路 smoke 仍然单独执行,不并入这条 wrapper
37+
38+## 2. 底层静态检查
39
40 ```bash
41 npx --yes pnpm -r build
42@@ -17,13 +44,11 @@ npx --yes pnpm -r build
43 --node mini \
44 --service conductor \
45 --service codexd \
46- --service status-api \
47 --install-dir /Users/george/Library/LaunchAgents \
48 --shared-token-file /Users/george/.config/baa-conductor/shared-token.txt \
49 --local-api-base http://100.71.210.78:4317 \
50 --local-api-allowed-hosts 100.71.210.78 \
51- --codexd-local-api-base http://127.0.0.1:4319 \
52- --status-api-host 100.71.210.78
53+ --codexd-local-api-base http://127.0.0.1:4319
54 ```
55
56 说明:
57@@ -33,9 +58,10 @@ npx --yes pnpm -r build
58 - `check-launchd.sh` 现在会优先校验 `conductor` 安装副本里的 `BAA_CONDUCTOR_PUBLIC_API_BASE`,同时接受只带 legacy `BAA_CONTROL_API_BASE` 的旧安装副本;其他服务要求这两个变量都不存在
59 - `check-launchd.sh` 现在也会校验 `conductor` 安装副本里的 `BAA_CODEXD_LOCAL_API_BASE`
60 - `check-launchd.sh` 现在会校验 `codexd` 的监听地址、事件流路径、日志目录、状态目录和 `app-server` child 配置
61+- 如果 `status-api` 已显式安装,再额外加 `--service status-api --status-api-host 100.71.210.78`
62 - 这些静态检查不包含 run/exec 路线
63
64-## 2. 运行态检查
65+## 3. 底层运行态检查
66
67 ```bash
68 ./scripts/runtime/check-node.sh \
69@@ -43,13 +69,10 @@ npx --yes pnpm -r build
70 --node mini \
71 --service conductor \
72 --service codexd \
73- --service status-api \
74 --install-dir /Users/george/Library/LaunchAgents \
75 --local-api-base http://100.71.210.78:4317 \
76 --local-api-allowed-hosts 100.71.210.78 \
77 --codexd-api-base http://127.0.0.1:4319 \
78- --status-api-base http://100.71.210.78:4318 \
79- --status-api-host 100.71.210.78 \
80 --expected-rolez leader \
81 --check-loaded
82 ```
83@@ -61,11 +84,11 @@ npx --yes pnpm -r build
84 - `logs/launchd/*.log` 是否存在
85 - `conductor` 是否监听 `4317` 并返回 `/healthz`、`/readyz`、`/rolez`、`/v1/codex`
86 - `codexd` 是否监听 `4319` 并返回 `/healthz`、`/v1/codexd/status`
87-- `status-api` 是否监听 `4318` 并返回 `/healthz`、`/v1/status`
88-- `status-api /v1/status` 是否能跟随同机 `conductor` 的 `/v1/system/state`
89+- 如果显式纳入 `--service status-api`,再验证 `status-api` 是否监听 `4318` 并返回 `/healthz`、`/v1/status`
90+- 如果显式纳入 `--service status-api`,再验证 `status-api /v1/status` 是否能跟随同机 `conductor` 的 `/v1/system/state`
91 - 不要求探测 run/exec 路线
92
93-## 3. 会话链路 smoke
94+## 4. 会话链路 smoke
95
96 ```bash
97 ./scripts/runtime/codexd-e2e-smoke.sh
98@@ -79,7 +102,7 @@ npx --yes pnpm -r build
99 - turn create/read
100 - `logs/codexd/**` 和 `state/codexd/**` 落盘
101
102-## 4. 手工探针
103+## 5. 手工探针
104
105 主路径:
106
107@@ -103,7 +126,7 @@ curl -fsSL http://100.71.210.78:4318/v1/status
108
109 会话链路回归不要直接手工拼 `run/exec` 探针,统一跑 `./scripts/runtime/codexd-e2e-smoke.sh`。
110
111-## 5. 常见失败点
112+## 6. 常见失败点
113
114 - `conductor /rolez` 不是 `leader`
115 - `codexd` 没有监听 `127.0.0.1:4319`
+2,
-0
1@@ -8,6 +8,8 @@
2 "typecheck": "pnpm -r typecheck",
3 "lint": "node ./scripts/verify-workspace.mjs lint",
4 "test": "node ./scripts/verify-workspace.mjs test",
5+ "smoke": "node ./scripts/verify-workspace.mjs smoke",
6+ "verify:mini": "bash ./scripts/runtime/verify-mini.sh",
7 "tasks": "ls tasks"
8 },
9 "devDependencies": {
+9,
-5
1@@ -6,10 +6,11 @@
2
3 ## 当前代码基线
4
5-- 主线基线:`main@7b27e5a`
6+- 主线基线:`main@d7a83fd`
7 - 任务文档已统一收口到 `tasks/`
8 - 当前活动任务见 `tasks/TASK_OVERVIEW.md`
9 - `T-S001` 到 `T-S012` 已经合入主线
10+- 当前工作区已经落下 `T-S013`、`T-S014`,并继续补根级 `pnpm smoke`
11
12 ## 当前状态
13
14@@ -56,11 +57,14 @@
15 - `T-S010`:仓库根 `pnpm lint` / `pnpm test` 已变成真实入口,并新增 repo verification 文档
16 - `T-S011`:`status-api` 已补包级 `test` 入口,并接入根 `pnpm test`
17 - `T-S012`:repo 模板、`install-mini.sh` 帮助文本、`pnpm-lock.yaml` 与 legacy smoke 已进一步收口到当前主线口径
18+- `T-S013`:`worker-runner` 已补包级 `test`,并接入根 `pnpm test`
19+- `T-S014`:runtime 默认服务集合已收口到 `conductor` + `codexd`,`status-api` 改为显式 opt-in
20+- 根级 `pnpm smoke` 已补进工作区,覆盖 runtime public-api compatibility、legacy absence、codexd e2e 和 browser-control e2e smoke
21
22 ## 下一步任务
23
24-- `T-S013`:给 `worker-runner` 补包级测试并接入根验证入口
25-- `T-S014`:把 `status-api` 从默认 runtime 服务集合里降为显式 opt-in
26+- `T-S015`:给 `mini` 单节点补统一 on-node verify wrapper
27+- `T-S016`:收口 `status-api` 终局并给 `conductor` 提供兼容状态视图
28
29 ## 当前仍需关注
30
31@@ -69,5 +73,5 @@
32 - 仍保留的 `control-api` 命名已经限定在历史任务卡、legacy 测试路径、兼容变量名和外部残留资产说明里;如果未来要继续删旧,需要单独评估文件名和兼容面
33 - 如果未来新增 runtime 测试绕开 `withRuntimeFixture(...)`,同类 listener 泄漏仍可能重新出现
34 - 这次没有改 `ConductorRuntime.stop()` 内部逻辑;如果未来关闭路径本身阻塞,还需要单独补运行时层测试
35-- 根 `pnpm test` 目前还没有覆盖 `worker-runner` 和 runtime / e2e 检查
36-- `status-api` 虽然已经退回本地只读观察面,但 runtime 默认服务集合仍把它当默认安装/启动对象;是否继续保留这个默认位次,尚未收口
37+- 根 `pnpm test` 现在已经覆盖 `worker-runner`;runtime / e2e 检查则由工作区里的 `pnpm smoke` 继续收口
38+- `status-api` 虽然已经退回 opt-in 本地只读观察面,但是否继续保留独立服务、还是并入 `conductor`,尚未收口
+2,
-1
1@@ -40,7 +40,8 @@ Options:
2 --help Show this help text.
3
4 Notes:
5- If no service is specified, conductor + codexd + status-api are checked.
6+ If no service is specified, conductor + codexd are checked.
7+ Use --service status-api to validate the optional local read-only observer.
8 status-api defaults to BAA_CONDUCTOR_LOCAL_API /v1/system/state; launchd no
9 longer writes conductor public-api base env vars for it.
10 --public-api-base only applies when conductor is part of the checked set.
+3,
-2
1@@ -40,8 +40,9 @@ Options:
2 --help Show this help text.
3
4 Notes:
5- The default runtime check set is conductor + codexd + status-api. Use
6- --service to narrow the scope or --all-services to include worker-runner.
7+ The default runtime check set is conductor + codexd. Use --service status-api
8+ to include the optional local read-only observer, or --all-services to also
9+ include worker-runner.
10 status-api defaults to BAA_CONDUCTOR_LOCAL_API /v1/system/state; launchd no
11 longer writes conductor public-api base env vars for it.
12 conductor HTTP probes include /v1/codex to ensure proxy wiring to codexd.
+2,
-2
1@@ -81,11 +81,11 @@ validate_node() {
2 }
3
4 default_services() {
5- printf '%s\n' conductor codexd status-api
6+ printf '%s\n' conductor codexd
7 }
8
9 default_node_verification_services() {
10- printf '%s\n' conductor codexd status-api
11+ printf '%s\n' conductor codexd
12 }
13
14 all_services() {
+2,
-1
1@@ -37,9 +37,10 @@ Options:
2 --help Show this help text.
3
4 Notes:
5- If no service is specified, conductor + codexd + status-api are installed.
6+ If no service is specified, conductor + codexd are installed.
7 Use --service codexd to render codexd independently; it does not require a
8 shared token.
9+ Use --service status-api to install the optional local read-only observer.
10 status-api defaults to BAA_CONDUCTOR_LOCAL_API /v1/system/state; launchd no
11 longer writes conductor public-api base env vars for it.
12 --public-api-base only affects conductor install copies, and the default is
+25,
-14
1@@ -20,6 +20,7 @@ Options:
2 Falls back to legacy
3 ~/.config/baa-conductor/control-api-worker.secrets.env
4 only if the default file is missing.
5+ --with-status-api Also install/restart/check the optional local status-api service.
6 --skip-build Skip pnpm build.
7 --skip-restart Skip launchd restart.
8 --skip-check Skip check-launchd/check-node verification.
9@@ -29,9 +30,10 @@ Notes:
10 This is the single-node mini convenience wrapper. It:
11 1. bootstraps runtime directories
12 2. builds the repo
13- 3. installs conductor + codexd + status-api LaunchAgents
14+ 3. installs conductor + codexd LaunchAgents by default
15 4. restarts them
16 5. verifies the node
17+ Add --with-status-api when you also want the optional local read-only observer.
18 codexd verification only treats /healthz and /v1/codexd/status as install acceptance;
19 /v1/codexd/runs* and codex exec are not part of the formal runtime contract.
20 EOF
21@@ -51,6 +53,7 @@ secrets_env=""
22 skip_build="0"
23 skip_restart="0"
24 skip_check="0"
25+with_status_api="0"
26 codexd_api_base="${BAA_RUNTIME_DEFAULT_CODEXD_LOCAL_API}"
27 status_api_base="http://100.71.210.78:4318"
28
29@@ -80,6 +83,10 @@ while [[ $# -gt 0 ]]; do
30 skip_build="1"
31 shift
32 ;;
33+ --with-status-api)
34+ with_status_api="1"
35+ shift
36+ ;;
37 --skip-restart)
38 skip_restart="1"
39 shift
40@@ -178,12 +185,19 @@ if [[ "$skip_build" != "1" ]]; then
41 )
42 fi
43
44+install_services=(
45+ --service conductor
46+ --service codexd
47+)
48+
49+if [[ "$with_status_api" == "1" ]]; then
50+ install_services+=(--service status-api)
51+fi
52+
53 run_or_print 0 "${SCRIPT_DIR}/install-launchd.sh" \
54 --repo-dir "$repo_dir" \
55 --node mini \
56- --service conductor \
57- --service codexd \
58- --service status-api \
59+ "${install_services[@]}" \
60 --install-dir "$install_dir" \
61 --shared-token-file "$shared_token_file" \
62 --local-api-base "http://100.71.210.78:4317" \
63@@ -194,22 +208,21 @@ run_or_print 0 "${SCRIPT_DIR}/install-launchd.sh" \
64 if [[ "$skip_restart" != "1" ]]; then
65 run_or_print 0 "${SCRIPT_DIR}/restart-launchd.sh" \
66 --install-dir "$install_dir" \
67- --service conductor \
68- --service codexd \
69- --service status-api
70+ "${install_services[@]}"
71 fi
72
73 if [[ "$skip_check" != "1" ]]; then
74 wait_for_http "conductor" "http://100.71.210.78:4317/healthz"
75 wait_for_http "codexd" "${codexd_api_base}/healthz"
76- wait_for_http "status-api" "${status_api_base}/healthz"
77+
78+ if [[ "$with_status_api" == "1" ]]; then
79+ wait_for_http "status-api" "${status_api_base}/healthz"
80+ fi
81
82 run_or_print 0 "${SCRIPT_DIR}/check-launchd.sh" \
83 --repo-dir "$repo_dir" \
84 --node mini \
85- --service conductor \
86- --service codexd \
87- --service status-api \
88+ "${install_services[@]}" \
89 --install-dir "$install_dir" \
90 --shared-token-file "$shared_token_file" \
91 --local-api-base "http://100.71.210.78:4317" \
92@@ -221,9 +234,7 @@ if [[ "$skip_check" != "1" ]]; then
93 run_or_print 0 "${SCRIPT_DIR}/check-node.sh" \
94 --repo-dir "$repo_dir" \
95 --node mini \
96- --service conductor \
97- --service codexd \
98- --service status-api \
99+ "${install_services[@]}" \
100 --install-dir "$install_dir" \
101 --shared-token-file "$shared_token_file" \
102 --local-api-base "http://100.71.210.78:4317" \
+2,
-1
1@@ -22,7 +22,8 @@ Options:
2 --help Show this help text.
3
4 Notes:
5- If no service is specified, conductor + codexd + status-api are reloaded.
6+ If no service is specified, conductor + codexd are reloaded.
7+ Use --service status-api to reload the optional local read-only observer.
8 codexd reload recovery waits on /healthz only; /v1/codexd/runs* and
9 codex exec are not part of reload acceptance.
10 EOF
+2,
-1
1@@ -21,7 +21,8 @@ Options:
2 --help Show this help text.
3
4 Notes:
5- If no service is specified, conductor + codexd + status-api are started.
6+ If no service is specified, conductor + codexd are started.
7+ Use --service status-api to start the optional local read-only observer.
8 EOF
9 }
10
+2,
-1
1@@ -24,7 +24,8 @@ Options:
2 --help Show this help text.
3
4 Notes:
5- If no service is specified, conductor + codexd + status-api are shown.
6+ If no service is specified, conductor + codexd are shown.
7+ Use --service status-api to inspect the optional local read-only observer.
8 conductor HTTP status also reports /v1/codex to surface codexd proxy wiring.
9 codexd HTTP status only reports /healthz and /v1/codexd/status.
10 /v1/codexd/runs* is not treated as a formal runtime probe.
+2,
-1
1@@ -21,7 +21,8 @@ Options:
2 --help Show this help text.
3
4 Notes:
5- If no service is specified, conductor + codexd + status-api are stopped.
6+ If no service is specified, conductor + codexd are stopped.
7+ Use --service status-api to stop the optional local read-only observer.
8 EOF
9 }
10
+119,
-0
1@@ -0,0 +1,119 @@
2+#!/usr/bin/env bash
3+set -euo pipefail
4+
5+SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
6+# shellcheck source=./common.sh
7+source "${SCRIPT_DIR}/common.sh"
8+
9+usage() {
10+ cat <<'EOF'
11+Usage:
12+ scripts/runtime/verify-mini.sh [options]
13+
14+Options:
15+ --node mini Optional compatibility flag. Only mini is supported.
16+ --scope agent|daemon Expected launchd scope. Defaults to agent.
17+ --service NAME Add one service to the verify set. Repeatable.
18+ --all-services Verify conductor, codexd, worker-runner, and status-api.
19+ --repo-dir PATH Repo root used to derive runtime paths.
20+ --home-dir PATH HOME value expected in installed plist files.
21+ --install-dir PATH Validate installed copies under this directory.
22+ --shared-token TOKEN Expect this exact token in installed copies.
23+ --shared-token-file PATH Read the expected token from a file.
24+ --public-api-base URL Expected conductor BAA_CONDUCTOR_PUBLIC_API_BASE.
25+ --control-api-base URL Legacy alias for --public-api-base.
26+ --local-api-base URL Conductor local API base URL.
27+ --local-api-allowed-hosts CSV
28+ Expected BAA_CONDUCTOR_LOCAL_API_ALLOWED_HOSTS.
29+ --codexd-api-base URL codexd local API base URL for both static and runtime checks.
30+ --codexd-local-api-base URL Alias for --codexd-api-base.
31+ --codexd-event-stream-path PATH
32+ Expected BAA_CODEXD_EVENT_STREAM_PATH.
33+ --codexd-server-command COMMAND
34+ Expected BAA_CODEXD_SERVER_COMMAND.
35+ --codexd-server-cwd PATH Expected BAA_CODEXD_SERVER_CWD.
36+ --status-api-base URL Status API base URL.
37+ --status-api-host HOST Expected BAA_STATUS_API_HOST in installed copies.
38+ --username NAME Expected UserName for LaunchDaemons.
39+ --domain TARGET launchctl domain target for --check-loaded.
40+ --check-loaded Also require launchctl print to succeed for each service.
41+ --expected-rolez VALUE Expected conductor /rolez body: leader or any.
42+ --skip-dist-check Skip dist/index.js existence checks during static verification.
43+ --skip-port-check Skip local TCP LISTEN checks during runtime verification.
44+ --skip-process-check Skip host process command-line checks during runtime verification.
45+ --skip-http-check Skip conductor/codexd/status-api HTTP probes.
46+ --skip-log-check Skip launchd stdout/stderr file checks during runtime verification.
47+ --help Show this help text.
48+
49+Notes:
50+ This wrapper runs check-launchd.sh first, then check-node.sh with
51+ --skip-static-check so the static pass only runs once.
52+ If no service is specified, mini defaults to conductor + codexd.
53+ Use --service status-api to include the optional local read-only observer.
54+ Use the lower-level scripts directly only when you need to isolate static
55+ versus runtime failures.
56+EOF
57+}
58+
59+launchd_args=()
60+node_args=()
61+
62+while [[ $# -gt 0 ]]; do
63+ case "$1" in
64+ --)
65+ shift
66+ ;;
67+ --node)
68+ validate_node "$2"
69+ shift 2
70+ ;;
71+ --scope | --service | --repo-dir | --home-dir | --install-dir | --shared-token | \
72+ --shared-token-file | --public-api-base | --control-api-base | --local-api-base | \
73+ --local-api-allowed-hosts | --status-api-host | --username | --domain)
74+ launchd_args+=("$1" "$2")
75+ node_args+=("$1" "$2")
76+ shift 2
77+ ;;
78+ --all-services | --check-loaded)
79+ launchd_args+=("$1")
80+ node_args+=("$1")
81+ shift
82+ ;;
83+ --codexd-api-base | --codexd-local-api-base)
84+ launchd_args+=(--codexd-local-api-base "$2")
85+ node_args+=(--codexd-api-base "$2")
86+ shift 2
87+ ;;
88+ --codexd-event-stream-path | --codexd-server-command | --codexd-server-cwd)
89+ launchd_args+=("$1" "$2")
90+ shift 2
91+ ;;
92+ --skip-dist-check)
93+ launchd_args+=("$1")
94+ shift
95+ ;;
96+ --status-api-base | --expected-rolez)
97+ node_args+=("$1" "$2")
98+ shift 2
99+ ;;
100+ --skip-port-check | --skip-process-check | --skip-http-check | --skip-log-check)
101+ node_args+=("$1")
102+ shift
103+ ;;
104+ --help)
105+ usage
106+ exit 0
107+ ;;
108+ *)
109+ die "Unknown option: $1"
110+ ;;
111+ esac
112+done
113+
114+runtime_log "mini verify: static launchd checks"
115+"${SCRIPT_DIR}/check-launchd.sh" --node mini "${launchd_args[@]}"
116+
117+runtime_log "mini verify: runtime checks"
118+"${SCRIPT_DIR}/check-node.sh" --node mini --skip-static-check "${node_args[@]}"
119+
120+runtime_log "mini verify passed"
+25,
-2
1@@ -11,6 +11,7 @@ const testPackages = [
2 "@baa-conductor/host-ops",
3 "@baa-conductor/codex-app-server",
4 "@baa-conductor/codex-exec",
5+ "@baa-conductor/worker-runner",
6 "@baa-conductor/status-api",
7 "@baa-conductor/conductor-daemon",
8 "@baa-conductor/codexd"
9@@ -30,14 +31,36 @@ const entrypoints = {
10 test: testPackages.map((pkg) => ({
11 label: `${pkg} test`,
12 command: ["pnpm", "--filter", pkg, "test"]
13- }))
14+ })),
15+ smoke: [
16+ {
17+ label: "workspace build",
18+ command: ["pnpm", "build"]
19+ },
20+ {
21+ label: "runtime public API compatibility smoke",
22+ command: ["node", "--test", "scripts/runtime/public-api-base.test.mjs"]
23+ },
24+ {
25+ label: "legacy control-api absence smoke",
26+ command: ["node", "--test", "tests/control-api/control-api-smoke.test.mjs"]
27+ },
28+ {
29+ label: "codexd e2e smoke",
30+ command: ["node", "--test", "tests/codexd/codexd-e2e-smoke.test.mjs"]
31+ },
32+ {
33+ label: "browser-control e2e smoke",
34+ command: ["node", "--test", "tests/browser/browser-control-e2e-smoke.test.mjs"]
35+ }
36+ ]
37 };
38
39 const mode = process.argv[2];
40 const steps = entrypoints[mode];
41
42 if (!steps) {
43- console.error("Usage: node scripts/verify-workspace.mjs <lint|test>");
44+ console.error("Usage: node scripts/verify-workspace.mjs <lint|test|smoke>");
45 process.exit(1);
46 }
47
+118,
-0
1@@ -0,0 +1,118 @@
2+# Task T-S015:给 `mini` 单节点补统一 on-node verify wrapper
3+
4+## 直接给对话的提示词
5+
6+读 `/Users/george/code/baa-conductor/tasks/T-S015.md` 任务文档,完成开发任务。
7+
8+如需补背景,再读:
9+
10+- `/Users/george/code/baa-conductor/scripts/runtime/check-launchd.sh`
11+- `/Users/george/code/baa-conductor/scripts/runtime/check-node.sh`
12+- `/Users/george/code/baa-conductor/docs/runtime/node-verification.md`
13+- `/Users/george/code/baa-conductor/docs/runtime/README.md`
14+- `/Users/george/code/baa-conductor/docs/ops/repo-verification.md`
15+- `/Users/george/code/baa-conductor/package.json`
16+
17+## 当前基线
18+
19+- 仓库:`/Users/george/code/baa-conductor`
20+- 分支:`main`
21+- 提交:`d7a83fd`
22+- 开工要求:不要从其他任务分支切出;如需新分支,从当前 `main` 新切
23+
24+## 建议分支名
25+
26+- `chore/add-mini-verify-wrapper`
27+
28+## 目标
29+
30+把 `mini` 节点的 on-node 静态检查和运行态检查收成一条统一入口,降低运维和回归时的命令记忆成本。
31+
32+## 背景
33+
34+- 当前仓库根已经有 `pnpm lint`、`pnpm test`、`pnpm smoke`,但这些都属于 repo 内可自举验证。
35+- 真正的 on-node 检查仍然需要分别记住 `check-launchd.sh` 和 `check-node.sh`,而且要自己拼同一组参数。
36+- `status-api` 已经降成 opt-in 观察面,新的 wrapper 也需要跟这个默认集合保持一致。
37+
38+## 涉及仓库
39+
40+- `/Users/george/code/baa-conductor`
41+
42+## 范围
43+
44+- 新增一条统一的 `mini` on-node verify wrapper
45+- 让默认检查集合跟当前 runtime 默认服务集合一致
46+- 回写 runtime / ops 文档和根脚本入口
47+
48+## 路径约束
49+
50+- 优先复用现有 `check-launchd.sh` 和 `check-node.sh`
51+- 不要重写已有静态检查或 HTTP 探针逻辑
52+- 不要把任务扩展成 launchd 安装流程重构
53+
54+## 推荐实现边界
55+
56+建议优先做:
57+
58+- 新增 `scripts/runtime/verify-mini.sh` 或等价 wrapper
59+- 默认同时跑静态检查和运行态检查
60+- 提供显式 `--service status-api` 或等价开关,保持 opt-in 观察面能力
61+
62+## 允许修改的目录
63+
64+- `/Users/george/code/baa-conductor/scripts/runtime/`
65+- `/Users/george/code/baa-conductor/docs/runtime/`
66+- `/Users/george/code/baa-conductor/docs/ops/`
67+- `/Users/george/code/baa-conductor/package.json`
68+- `/Users/george/code/baa-conductor/README.md`
69+
70+## 尽量不要修改
71+
72+- `/Users/george/code/baa-conductor/apps/`
73+- `/Users/george/code/baa-conductor/tasks/`
74+
75+## 必须完成
76+
77+### 1. 补统一 on-node wrapper
78+
79+- 新入口默认覆盖 `conductor` 和 `codexd`
80+- 调用方不需要再手动拼两套重复参数
81+
82+### 2. 保持现有检查脚本职责不变
83+
84+- `check-launchd.sh` 和 `check-node.sh` 仍然保留
85+- wrapper 只是编排,不要复制一份内部逻辑
86+
87+### 3. 回写文档和根入口
88+
89+- runtime 文档要写清新的推荐 on-node 用法
90+- 如有必要,根 `package.json` 补一个合适的调用入口
91+
92+## 需要特别注意
93+
94+- 不要破坏当前 `status-api` opt-in 语义
95+- 不要把 repo 内 `pnpm smoke` 和 on-node verify 混成一条命令
96+- 与 `status-api` 终局任务并行时,尽量避免同时大改同一段 runtime 文档
97+
98+## 验收标准
99+
100+- `mini` on-node 检查有统一入口
101+- 默认服务集合与当前 runtime 默认集合一致
102+- 文档和帮助文本已同步
103+- `git diff --check` 通过
104+
105+## 推荐验证命令
106+
107+- `bash -n /Users/george/code/baa-conductor/scripts/runtime/verify-mini.sh`
108+- `bash -n /Users/george/code/baa-conductor/scripts/runtime/check-launchd.sh`
109+- `bash -n /Users/george/code/baa-conductor/scripts/runtime/check-node.sh`
110+- `git -C /Users/george/code/baa-conductor diff --check`
111+
112+## 交付要求
113+
114+完成后请说明:
115+
116+- 新 wrapper 叫什么、默认会跑什么
117+- 默认服务集合是什么
118+- 修改了哪些脚本和文档
119+- 还有哪些 on-node 检查仍需要人工判断
+119,
-0
1@@ -0,0 +1,119 @@
2+# Task T-S016:收口 `status-api` 终局并给 `conductor` 提供兼容状态视图
3+
4+## 直接给对话的提示词
5+
6+读 `/Users/george/code/baa-conductor/tasks/T-S016.md` 任务文档,完成开发任务。
7+
8+如需补背景,再读:
9+
10+- `/Users/george/code/baa-conductor/apps/conductor-daemon/src/local-api.ts`
11+- `/Users/george/code/baa-conductor/apps/conductor-daemon/src/index.test.js`
12+- `/Users/george/code/baa-conductor/apps/status-api/src/service.ts`
13+- `/Users/george/code/baa-conductor/apps/status-api/src/index.test.js`
14+- `/Users/george/code/baa-conductor/docs/api/README.md`
15+- `/Users/george/code/baa-conductor/docs/runtime/README.md`
16+- `/Users/george/code/baa-conductor/README.md`
17+
18+## 当前基线
19+
20+- 仓库:`/Users/george/code/baa-conductor`
21+- 分支:`main`
22+- 提交:`d7a83fd`
23+- 开工要求:不要从其他任务分支切出;如需新分支,从当前 `main` 新切
24+
25+## 建议分支名
26+
27+- `feat/conductor-status-view-compat`
28+
29+## 目标
30+
31+把当前只读状态视图进一步收口到 `conductor` 主接口上,同时保留 `status-api` 的兼容位次或明确降级路径。
32+
33+## 背景
34+
35+- `status-api` 现在已经是显式 opt-in 的本地观察服务,不再属于默认 runtime 套件。
36+- 但 JSON / HTML 状态视图仍然是单独服务能力,导致状态面仍然分散在两个本地端口上。
37+- 如果主线要继续收口,最自然的下一步是让 `conductor` 自己也能提供这层只读状态视图。
38+
39+## 涉及仓库
40+
41+- `/Users/george/code/baa-conductor`
42+
43+## 范围
44+
45+- 给 `conductor` 增加与当前 `status-api` 对齐的只读状态视图入口
46+- 决定 `status-api` 保留为兼容包装层,还是进一步降级为 legacy/可删组件
47+- 回写 API / runtime / README 文档
48+
49+## 路径约束
50+
51+- 优先复用现有 `status-api` 的取数和渲染逻辑,避免复制两套状态拼装代码
52+- 不要把任务扩展成重新设计 `/v1/system/state`
53+- 不要顺手改 runtime 安装脚本默认服务集合,它已经在前一轮任务里收口
54+
55+## 推荐实现边界
56+
57+建议优先做:
58+
59+- 在 `conductor` 上补 `status-api` 等价或明确兼容的状态视图路由
60+- 保持现有 `status-api` 合同兼容,至少不让现有调用方直接断掉
61+- 用测试覆盖 JSON 和 HTML 只读视图
62+
63+## 允许修改的目录
64+
65+- `/Users/george/code/baa-conductor/apps/conductor-daemon/`
66+- `/Users/george/code/baa-conductor/apps/status-api/`
67+- `/Users/george/code/baa-conductor/docs/api/`
68+- `/Users/george/code/baa-conductor/docs/runtime/`
69+- `/Users/george/code/baa-conductor/README.md`
70+- `/Users/george/code/baa-conductor/DESIGN.md`
71+
72+## 尽量不要修改
73+
74+- `/Users/george/code/baa-conductor/scripts/runtime/`
75+- `/Users/george/code/baa-conductor/tasks/`
76+
77+## 必须完成
78+
79+### 1. 给 `conductor` 提供兼容状态视图
80+
81+- 至少有一组稳定入口能返回当前 JSON 状态视图
82+- 如保留 HTML 视图,也要说明其定位
83+
84+### 2. 保持兼容迁移路径
85+
86+- 现有 `status-api` 不应在没有说明的情况下直接消失
87+- 文档里要明确推荐入口和兼容入口
88+
89+### 3. 补测试和文档
90+
91+- `conductor-daemon` / `status-api` 至少一侧需要新增或更新测试
92+- API / runtime / README 口径要一致
93+
94+## 需要特别注意
95+
96+- 不要破坏现有 `/v1/system/state`
97+- 不要把只读状态视图误写成新的控制接口
98+- 与 on-node verify wrapper 任务并行时,尽量避免同时改同一段 README / runtime 文档
99+
100+## 验收标准
101+
102+- `conductor` 提供可用的兼容状态视图入口
103+- `status-api` 的保留/降级路径清楚且不突兀
104+- 测试通过,文档同步
105+- `git diff --check` 通过
106+
107+## 推荐验证命令
108+
109+- `npx --yes pnpm -C /Users/george/code/baa-conductor -F @baa-conductor/conductor-daemon test`
110+- `npx --yes pnpm -C /Users/george/code/baa-conductor -F @baa-conductor/status-api test`
111+- `git -C /Users/george/code/baa-conductor diff --check`
112+
113+## 交付要求
114+
115+完成后请说明:
116+
117+- `conductor` 新增了哪些状态视图入口
118+- `status-api` 现在的定位是什么
119+- 修改了哪些测试和文档
120+- 还有哪些兼容风险
+11,
-5
1@@ -9,7 +9,7 @@
2 - `control-api.makefile.so`、Cloudflare Worker、D1 只剩迁移期 legacy 兼容残留和依赖盘点用途
3 - `baa-hand` / `baa-shell` 只保留为接口语义参考,不再作为主系统维护
4 - 当前任务卡都放在本目录
5-- 当前任务基线:`main@7b27e5a`
6+- 当前任务基线:`main@d7a83fd`
7
8 ## 最近完成任务
9
10@@ -27,6 +27,12 @@
11 10. [`T-S010.md`](./T-S010.md):补仓库根验证入口
12 11. [`T-S011.md`](./T-S011.md):把 `status-api` 测试接入根验证入口
13 12. [`T-S012.md`](./T-S012.md):清理 repo 中最后一批 legacy 模板与残留 importer
14+13. [`T-S013.md`](./T-S013.md):给 `worker-runner` 补包级测试并接入根验证入口
15+14. [`T-S014.md`](./T-S014.md):把 `status-api` 从默认 runtime 服务集合里降为显式 opt-in
16+
17+当前工作区还在继续推进:
18+
19+- 根级 `pnpm smoke` 入口,覆盖 repo 内可自举的 runtime compatibility / legacy absence / codexd e2e / browser-control e2e smoke
20
21 说明:
22
23@@ -36,13 +42,13 @@
24
25 围绕剩余技术债,当前建议继续推进这 2 张任务卡:
26
27-1. [`T-S013.md`](./T-S013.md):给 `worker-runner` 补包级测试并接入根验证入口
28-2. [`T-S014.md`](./T-S014.md):把 `status-api` 从默认 runtime 服务集合里降为显式 opt-in
29+1. [`T-S015.md`](./T-S015.md):给 `mini` 单节点补统一 on-node verify wrapper
30+2. [`T-S016.md`](./T-S016.md):收口 `status-api` 终局并给 `conductor` 提供兼容状态视图
31
32 说明:
33
34-- `T-S013` 主要改 `apps/worker-runner/`、根验证脚本和 repo verification 文档
35-- `T-S014` 主要改 `scripts/runtime/**`、`docs/runtime/**`、`README` 和少量 launchd 模板
36+- `T-S015` 主要改 `scripts/runtime/**`、`docs/runtime/**`、`docs/ops/**` 和根脚本入口
37+- `T-S016` 主要改 `apps/conductor-daemon/`、`apps/status-api/`、`docs/api/**`、`docs/runtime/**`
38
39 ## 任务文档约定
40