- 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
+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 }
+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,
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 ## 原因
+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`
+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
+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
+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 安装副本示例:
+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`
+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
+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
+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
+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
+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
+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 ;;