baa-conductor

git clone 

commit
80c6202
parent
6845b72
author
im_wower
date
2026-03-23 08:49:49 +0800 CST
Align codexd runtime surface with sessions and turns
14 files changed,  +59, -77
M apps/codexd/src/index.test.js
+8, -13
 1@@ -288,7 +288,7 @@ test("CodexdDaemon persists daemon identity, child state, session registry, and
 2   assert.match(eventLog, /session\.registered/);
 3 });
 4 
 5-test("CodexdLocalService starts the local HTTP surface and supports status, sessions, turns, and runs", async () => {
 6+test("CodexdLocalService starts the local HTTP surface and supports status, sessions, turns, and rejects runs routes", async () => {
 7   const repoRoot = mkdtempSync(join(tmpdir(), "codexd-service-test-"));
 8   const config = resolveCodexdConfig({
 9     localApiBase: "http://127.0.0.1:0",
10@@ -395,22 +395,17 @@ test("CodexdLocalService starts the local HTTP surface and supports status, sess
11     assert.equal(completedSession.json.data.session.currentTurnId, null);
12     assert.equal(completedSession.json.data.session.lastTurnStatus, "completed");
13 
14-    const createdRun = await postJson(`${baseUrl}/v1/codexd/runs`, {
15+    const listRuns = await fetchJson(`${baseUrl}/v1/codexd/runs`);
16+    assert.equal(listRuns.status, 404);
17+
18+    const createRun = await postJson(`${baseUrl}/v1/codexd/runs`, {
19       cwd: repoRoot,
20       prompt: "Inspect repo"
21     });
22-    assert.equal(createdRun.status, 202);
23-    const runId = createdRun.json.data.run.runId;
24-
25-    const completedRun = await waitFor(async () => {
26-      const current = await fetchJson(`${baseUrl}/v1/codexd/runs/${runId}`);
27-      return current.json.data.run.status === "completed" ? current : null;
28-    });
29-    assert.equal(completedRun.json.data.run.summary, "completed Inspect repo");
30+    assert.equal(createRun.status, 404);
31 
32-    const runs = await fetchJson(`${baseUrl}/v1/codexd/runs`);
33-    assert.equal(runs.status, 200);
34-    assert.ok(runs.json.data.runs.some((run) => run.runId === runId));
35+    const readRun = await fetchJson(`${baseUrl}/v1/codexd/runs/run-legacy`);
36+    assert.equal(readRun.status, 404);
37   } finally {
38     await service.stop();
39   }
M apps/codexd/src/local-service.ts
+0, -58
 1@@ -5,12 +5,10 @@ import {
 2   CodexdDaemon,
 3   type CodexdCreateSessionInput,
 4   type CodexdDaemonOptions,
 5-  type CodexdRunInput,
 6   type CodexdTurnInput
 7 } from "./daemon.js";
 8 import type {
 9   CodexdResolvedConfig,
10-  CodexdRunRecord,
11   CodexdSessionPurpose,
12   CodexdSessionRecord,
13   CodexdStatusSnapshot
14@@ -296,25 +294,6 @@ export class CodexdLocalService {
15       });
16     }
17 
18-    if (method === "GET" && pathname === "/v1/codexd/runs") {
19-      return jsonResponse(200, {
20-        data: {
21-          runs: this.daemon.listRuns()
22-        },
23-        ok: true
24-      });
25-    }
26-
27-    if (method === "POST" && pathname === "/v1/codexd/runs") {
28-      const run = await this.daemon.createRun(parseCreateRunInput(body));
29-      return jsonResponse(202, {
30-        data: {
31-          run
32-        },
33-        ok: true
34-      });
35-    }
36-
37     const sessionMatch = pathname.match(/^\/v1\/codexd\/sessions\/([^/]+)$/u);
38 
39     if (method === "GET" && sessionMatch?.[1] != null) {
40@@ -334,24 +313,6 @@ export class CodexdLocalService {
41       });
42     }
43 
44-    const runMatch = pathname.match(/^\/v1\/codexd\/runs\/([^/]+)$/u);
45-
46-    if (method === "GET" && runMatch?.[1] != null) {
47-      const runId = decodeURIComponent(runMatch[1]);
48-      const run = this.daemon.getRun(runId);
49-
50-      if (run == null) {
51-        throw new CodexdHttpError(404, `Unknown codexd run "${runId}".`);
52-      }
53-
54-      return jsonResponse(200, {
55-        data: {
56-          run
57-        },
58-        ok: true
59-      });
60-    }
61-
62     throw new CodexdHttpError(404, `Unknown codexd route ${method} ${pathname}.`);
63   }
64 }
65@@ -413,25 +374,6 @@ function normalizePathname(value: string): string {
66   return normalized === "" ? "/" : normalized;
67 }
68 
69-function parseCreateRunInput(body: JsonRecord): CodexdRunInput {
70-  return {
71-    additionalWritableDirectories: readStringArray(body.additionalWritableDirectories),
72-    config: readStringArray(body.config),
73-    cwd: readOptionalString(body.cwd) ?? undefined,
74-    env: readStringMap(body.env),
75-    images: readStringArray(body.images),
76-    metadata: readStringMap(body.metadata),
77-    model: readOptionalString(body.model) ?? undefined,
78-    profile: readOptionalString(body.profile) ?? undefined,
79-    prompt: readRequiredString(body.prompt, "prompt"),
80-    purpose: readOptionalString(body.purpose),
81-    sandbox: readOptionalString(body.sandbox),
82-    sessionId: readOptionalString(body.sessionId),
83-    skipGitRepoCheck: readOptionalBoolean(body.skipGitRepoCheck),
84-    timeoutMs: readOptionalInteger(body.timeoutMs)
85-  };
86-}
87-
88 function parseCreateSessionInput(body: JsonRecord): CodexdCreateSessionInput {
89   return {
90     approvalPolicy: (body.approvalPolicy as CodexdCreateSessionInput["approvalPolicy"]) ?? null,
M docs/decisions/0002-codexd-independent-daemon.md
+1, -0
1@@ -14,6 +14,7 @@
2 - `codexd` 是唯一 Codex 运行面
3 - `conductor-daemon` 只通过本地接口调用 `codexd`
4 - `codexd` 主接口基于 `codex app-server`
5+- 正式运行面只保留 session / turn / status / events
6 - 不实现 `codex exec` 式无交互正式模式
7 
8 ## 原因
M docs/runtime/README.md
+11, -0
 1@@ -29,6 +29,17 @@
 2 - `codexd`: `launchd` 托管的独立 Codex 运行面,只走 `codex app-server` 路线,监听 `127.0.0.1:4319`
 3 - `status-api`: `launchd` 托管的本地只读观察面,监听 `4318`
 4 
 5+`codexd` 正式能力面只保留:
 6+
 7+- `GET /healthz`
 8+- `GET /v1/codexd/status`
 9+- `GET /v1/codexd/sessions`
10+- `POST /v1/codexd/sessions`
11+- `POST /v1/codexd/turn`
12+- `WS /v1/codexd/events`
13+
14+不把 `/v1/codexd/runs*` 或 `codex exec` 写进当前 runtime / launchd / 运维口径。
15+
16 ## 最短路径
17 
18 1. `./scripts/runtime/install-mini.sh`
M docs/runtime/codexd.md
+9, -5
 1@@ -46,7 +46,7 @@ BAA_CODEXD_STATE_DIR=/Users/george/code/baa-conductor/state/codexd
 2 负责:
 3 
 4 - 启动和托管 Codex child
 5-- 管理本地 Codex 会话和运行态状态
 6+- 管理本地 Codex session / turn / recent events 运行态
 7 - 维护 `logs/codexd/**` 与 `state/codexd/**`
 8 - 提供本地 HTTP / WS 服务面
 9 - 处理 child 崩溃后的重建
10@@ -65,16 +65,19 @@ BAA_CODEXD_STATE_DIR=/Users/george/code/baa-conductor/state/codexd
11 - `GET /v1/codexd/sessions`
12 - `POST /v1/codexd/sessions`
13 - `POST /v1/codexd/turn`
14-- `GET /v1/codexd/runs`
15-- `POST /v1/codexd/runs`
16 - `WS /v1/codexd/events`
17 
18+正式口径只保留 `status / sessions / turn / events`。
19+
20+- `runs` 不属于当前正式 runtime / launchd / 运维运行面
21+- `codex exec` 也不作为正式无交互模式进入这一层
22+- 如果仓库里仍保留 run registry 或一次性执行相关内部代码,应视为非正式内部遗留,不属于当前对外能力描述
23+
24 当前骨架已经会维护:
25 
26 - daemon identity
27 - child state
28 - session registry
29-- run registry
30 - recent event cache
31 - 结构化事件日志
32 
33@@ -90,12 +93,13 @@ BAA_CODEXD_STATE_DIR=/Users/george/code/baa-conductor/state/codexd
34 
35 下面这些不属于正式 launchd 运行面:
36 
37+- `/v1/codexd/runs*` 正式接口
38 - `codex exec` 正式模式
39 - TUI 常驻模式
40 - 两个进程互相直接拉起
41 - 把 `codexd` 描述成“可选以后再说”的附属能力
42 
43-`codex exec` 可以继续存在于测试、临时工具或过渡代码里,但不进入 `mini` 正式 runtime 配置、launchd 模板或安装脚本。
44+`codex exec` 可以继续存在于测试、临时工具或过渡代码里,但不进入 `mini` 正式 runtime 配置、launchd 模板、安装脚本或运维验收。
45 
46 ## 当前仍未补齐的部分
47 
M docs/runtime/environment.md
+2, -0
1@@ -63,6 +63,8 @@ BAA_CODEXD_STATE_DIR=/Users/george/code/baa-conductor/state/codexd
2 说明:
3 
4 - 正式运行面只支持 `app-server`
5+- 正式 API 只保留 `/healthz`、`/v1/codexd/status`、`/v1/codexd/sessions`、`/v1/codexd/turn`、`/v1/codexd/events`
6+- 不为正式运行面暴露 `/v1/codexd/runs*` 相关变量或开关
7 - 不为 launchd 运行面增加 `codex exec` 正式开关
8 - `BAA_CODEXD_LOCAL_API_BASE` 必须保持 loopback host;当前默认是 `127.0.0.1:4319`
9 
M docs/runtime/launchd.md
+9, -0
 1@@ -39,6 +39,8 @@
 2 - `codexd` 独立安装时不需要共享 token
 3 - `--control-api-base` 仍然保留,只是为了写入兼容变量 `BAA_CONTROL_API_BASE`
 4 - `codexd` 正式运行面只写入 `app-server` 相关默认值,不暴露 `codex exec` 正式开关
 5+- `codexd` 正式服务面只保留 `/healthz`、`/v1/codexd/status`、`/v1/codexd/sessions`、`/v1/codexd/turn`、`/v1/codexd/events`
 6+- install / reload / status / check 脚本都不把 `/v1/codexd/runs*` 作为正式探针或验收项
 7 
 8 ## 日常管理
 9 
10@@ -74,6 +76,13 @@
11 ./scripts/runtime/restart-launchd.sh --service codexd
12 ```
13 
14+当前运维观察口径:
15+
16+- `status-launchd.sh` 对 `codexd` 只展示 `/healthz` 和 `/v1/codexd/status`
17+- `reload-launchd.sh` 只等待 `codexd /healthz` 恢复
18+- `check-node.sh` 只要求 `codexd /healthz` 和 `/v1/codexd/status` 返回正常
19+- 不要求、也不建议把 `/v1/codexd/runs*` 当成 launchd 运行面验收
20+
21 ## 渲染安装副本
22 
23 完整 mini 安装副本示例:
M docs/runtime/node-verification.md
+6, -0
 1@@ -6,6 +6,8 @@
 2 - `codexd` `http://127.0.0.1:4319`
 3 - `status-api` `http://100.71.210.78:4318`
 4 
 5+其中 `codexd` 的正式产品面仍是 `status / sessions / turn / events`,但 on-node 运维探针只要求 `/healthz` 和 `/v1/codexd/status`;不把 `/v1/codexd/runs*` 当成必验项。
 6+
 7 ## 1. 构建与静态检查
 8 
 9 ```bash
10@@ -29,6 +31,7 @@ npx --yes pnpm -r build
11 
12 - `--control-api-base` 仍是当前静态检查参数,但只用于校验兼容变量 `BAA_CONTROL_API_BASE`
13 - `check-launchd.sh` 现在会校验 `codexd` 的监听地址、事件流路径、日志目录、状态目录和 `app-server` child 配置
14+- 这些静态检查不要求 `/v1/codexd/runs*`
15 
16 ## 2. 运行态检查
17 
18@@ -57,6 +60,7 @@ npx --yes pnpm -r build
19 - `conductor` 是否监听 `4317` 并返回 `/healthz`、`/readyz`、`/rolez`
20 - `codexd` 是否监听 `4319` 并返回 `/healthz`、`/v1/codexd/status`
21 - `status-api` 是否监听 `4318` 并返回 `/healthz`、`/v1/status`
22+- 不要求探测 `/v1/codexd/runs*`
23 
24 ## 3. 手工探针
25 
26@@ -79,6 +83,8 @@ curl -fsSL http://127.0.0.1:4319/v1/codexd/status
27 curl -fsSL http://100.71.210.78:4318/v1/status
28 ```
29 
30+不需要再额外探测 `http://127.0.0.1:4319/v1/codexd/runs*`。
31+
32 ## 4. 常见失败点
33 
34 - `conductor /rolez` 不是 `leader`
M scripts/runtime/check-launchd.sh
+2, -0
1@@ -40,6 +40,8 @@ Options:
2 
3 Notes:
4   If no service is specified, conductor + codexd + status-api are checked.
5+  codexd static checks only validate app-server launchd wiring; they do not
6+  require /v1/codexd/runs* or codex exec as formal runtime capabilities.
7 EOF
8 }
9 
M scripts/runtime/check-node.sh
+2, -0
1@@ -41,6 +41,8 @@ Options:
2 Notes:
3   The default runtime check set is conductor + codexd + status-api. Use
4   --service to narrow the scope or --all-services to include worker-runner.
5+  For codexd, the HTTP probes only cover /healthz and /v1/codexd/status.
6+  /v1/codexd/runs* and codex exec are not part of node verification.
7 EOF
8 }
9 
M scripts/runtime/install-launchd.sh
+2, -0
1@@ -39,6 +39,8 @@ Notes:
2   If no service is specified, conductor + codexd + status-api are installed.
3   Use --service codexd to render codexd independently; it does not require a
4   shared token.
5+  codexd launchd wiring stays on app-server mode and does not expose
6+  /v1/codexd/runs* or codex exec as a formal service contract.
7 EOF
8 }
9 
M scripts/runtime/install-mini.sh
+2, -0
1@@ -30,6 +30,8 @@ Notes:
2   3. installs conductor + codexd + status-api LaunchAgents
3   4. restarts them
4   5. verifies the node
5+  codexd verification only treats /healthz and /v1/codexd/status as install acceptance;
6+  /v1/codexd/runs* and codex exec are not part of the formal runtime contract.
7 EOF
8 }
9 
M scripts/runtime/reload-launchd.sh
+2, -0
1@@ -23,6 +23,8 @@ Options:
2 
3 Notes:
4   If no service is specified, conductor + codexd + status-api are reloaded.
5+  codexd reload recovery waits on /healthz only; /v1/codexd/runs* and
6+  codex exec are not part of reload acceptance.
7 EOF
8 }
9 
M scripts/runtime/status-launchd.sh
+3, -1
 1@@ -25,6 +25,8 @@ Options:
 2 
 3 Notes:
 4   If no service is specified, conductor + codexd + status-api are shown.
 5+  codexd HTTP status only reports /healthz and /v1/codexd/status.
 6+  /v1/codexd/runs* is not treated as a formal runtime probe.
 7 EOF
 8 }
 9 
10@@ -157,7 +159,7 @@ if [[ "$skip_http" != "1" ]]; then
11         printf '=== codexd http ===\n'
12         printf 'healthz: '
13         curl -fsS "${codexd_api_base%/}/healthz" || true
14-        printf '\nstatus: '
15+        printf '\n/v1/codexd/status: '
16         curl -fsS "${codexd_api_base%/}/v1/codexd/status" || true
17         printf '\n\n'
18         ;;