- commit
- 6845b72
- parent
- c2734c0
- author
- im_wower
- date
- 2026-03-23 08:30:55 +0800 CST
feat(runtime): make codexd a first-class launchd service
18 files changed,
+564,
-552
+25,
-36
1@@ -1,70 +1,59 @@
2 # runtime
3
4-当前 runtime 只定义 `mini` 单节点的长期运行方式,并默认以 local API cutover 为主线。
5+当前 runtime 只定义 `mini` 单节点的正式长期运行方式。
6
7 ## 内容
8
9 - [`layout.md`](./layout.md): runtime 目录布局
10-- [`environment.md`](./environment.md): 必要环境变量
11-- [`launchd.md`](./launchd.md): `mini` 上的 launchd 安装
12+- [`environment.md`](./environment.md): 必要环境变量和默认值
13+- [`launchd.md`](./launchd.md): `mini` 上的 launchd 安装与日常操作
14 - [`node-verification.md`](./node-verification.md): `mini` 节点 on-node 检查
15-- [`codexd.md`](./codexd.md): Codex 常驻代理骨架与后续边界
16+- [`codexd.md`](./codexd.md): `codexd` 的正式运行边界
17
18 ## 当前约定
19
20 - 长期运行节点只有 `mini`
21 - canonical local API: `http://100.71.210.78:4317`
22 - canonical local Firefox WS: `ws://100.71.210.78:4317/ws/firefox`
23+- codexd local API: `http://127.0.0.1:4319`
24+- codexd event stream: `ws://127.0.0.1:4319/v1/codexd/events`
25 - canonical public host: `https://conductor.makefile.so`
26 - `status-api` `http://100.71.210.78:4318` 只作为本地只读观察面
27 - `BAA_CONTROL_API_BASE` 仍保留为兼容变量名,但默认值已经收口到 `https://conductor.makefile.so`
28 - 推荐仓库路径:`/Users/george/code/baa-conductor`
29 - repo 内的 plist 只作为模板;真正加载的是脚本渲染出来的安装副本
30
31-Firefox WS 说明:
32+## 当前正式服务
33
34-- 不单独开新端口,直接复用 `BAA_CONDUCTOR_LOCAL_API`
35-- 固定 upgrade path 是 `/ws/firefox`
36-- 只给本地 Firefox 插件双向通讯使用,不是公网通道
37-
38-`codexd` 说明:
39-
40-- 仓库里已经有 `apps/codexd` 最小骨架
41-- 当前骨架能做的事情:
42- - 解析最小运行配置
43- - 维护 `logs/codexd` 和 `state/codexd`
44- - 启动或占位一个 `codex app-server` 子进程配置
45- - 持久化 daemon identity、child state、session registry、recent event cache
46- - 提供本地 HTTP / WS 服务面
47- - 让 `conductor-daemon` 通过 `/v1/codex/*` 代理 session / turn / status
48-- 运行约束:
49- - `codexd` 是独立常驻进程,不是 `conductor-daemon` 的内嵌 bridge
50- - `/v1/codex/*` 现在以 `conductor-daemon -> codexd` 代理方式提供
51- - `launchd` 负责自启动和硬重启,不做两个进程互相直接拉起
52-- 当前还没有:
53- - 完整的会话恢复和断线重放
54- - 更丰富的增量事件订阅语义
55- - 任何 `/v1/codex/runs*` 的正式代理面
56+- `conductor`: `launchd` 托管的主控制面,承载 `4317` 本地 API
57+- `codexd`: `launchd` 托管的独立 Codex 运行面,只走 `codex app-server` 路线,监听 `127.0.0.1:4319`
58+- `status-api`: `launchd` 托管的本地只读观察面,监听 `4318`
59
60 ## 最短路径
61
62 1. `./scripts/runtime/install-mini.sh`
63 2. `./scripts/runtime/status-launchd.sh`
64-3. `./scripts/runtime/stop-launchd.sh`
65-4. `./scripts/runtime/start-launchd.sh`
66-5. `./scripts/runtime/restart-launchd.sh`
67-6. `./scripts/runtime/check-node.sh --node mini`
68+3. `./scripts/runtime/restart-launchd.sh`
69+4. `./scripts/runtime/check-node.sh --node mini`
70
71 ## 当前推荐入口
72
73 - 安装并切到当前正式仓库路径:
74 - `./scripts/runtime/install-mini.sh`
75-- 查看当前 launchd / HTTP 状态:
76+- 查看 `launchd` / HTTP 状态:
77 - `./scripts/runtime/status-launchd.sh`
78-- 停止:
79+ - 默认同时检查 `conductor`、`codexd`、`status-api`
80+- 停止 / 启动 / 重启:
81 - `./scripts/runtime/stop-launchd.sh`
82-- 启动:
83 - `./scripts/runtime/start-launchd.sh`
84-- 重启:
85 - `./scripts/runtime/restart-launchd.sh`
86- - 脚本会在 reload 后等待 `conductor /healthz` 恢复;如果服务处于 loaded 但未提供 HTTP,会自动再做一次 `launchctl kickstart -k`
87+ - 默认同时操作 `conductor`、`codexd`、`status-api`
88+ - 需要单独管理时,用 `--service codexd`
89+- 节点检查:
90+ - `./scripts/runtime/check-node.sh --node mini`
91+
92+职责边界:
93+
94+- `launchd` 负责开机自启动和硬重启
95+- `conductor-daemon` 和 `codexd` 只负责健康感知、重连和降级
96+- 不做两个进程互相直接拉起
+70,
-316
1@@ -1,63 +1,34 @@
2 # codexd
3
4-`codexd` 是 `baa-conductor` 下一阶段预留的本地常驻组件设计。
5-
6-它的目标不是替代 `conductor-daemon`,而是把 Codex 从“手工打开一个 TUI 窗口”提升为“可被本地系统长期调用的 Codex 代理能力”。
7-
8-当前状态:
9-
10-- 仓库里已经有 `apps/codexd` 最小骨架
11-- 已经有本地 HTTP / WS 服务面
12-- `conductor-daemon` 已经通过 `/v1/codex/*` 代理到它
13-- 主目标仍然围绕 `codex app-server` 演进
14-
15-## 目标
16-
17-`codexd` 需要解决的不是“再开一个 UI”,而是这 4 件事:
18-
19-1. 让 Codex 作为 worker 执行 step
20-2. 让 Codex 能完成异步任务,而不是只跑一次同步 CLI
21-3. 让 Codex 能参与 AI 对话,而不是只能手动开 TUI
22-4. 让 Codex 全程记录日志、可恢复、可观察
23-
24-## 为什么需要它
25-
26-当前仓库已经有:
27-
28-- `conductor-daemon`
29-- `worker-runner`
30-- `planner`
31-- `task / run / step`
32-- `checkpoint / logging`
33-
34-但还没有:
35-
36-- Codex 常驻进程
37-- Codex 会话代理
38-- Codex 子进程管理
39-- Codex 对话与任务统一入口
40-
41-所以目前的“Codex”仍停留在抽象层:
42-
43-- planner 和 step template 里有 `codex` step kind
44-- 但系统没有一个真正的 `codexd`
45-
46-## 定位
47-
48-`codexd` 的定位应该是:
49-
50-- 本地常驻进程
51-- 只跑在 `mini`
52-- 由 `launchd` 托管
53-- 通过本地 IPC、HTTP 或 WS 与 `conductor-daemon` 协作
54-- 必须是独立进程,不接受长期内嵌在 `conductor-daemon` 内
55-
56-它不是:
57-
58-- 新的真相源
59-- 新的主控制面
60-- 新的公网入口
61-- 手工操作的主界面
62+`codexd` 现在是 `mini` 上的正式 Codex 运行时服务。
63+
64+它不是 `conductor-daemon` 的内嵌 bridge,也不是手工调试时顺便开的附属进程;它是由 `launchd` 托管的独立常驻进程,用来承接 Codex child、会话、日志、恢复和本地服务面。
65+
66+## 当前正式运行面
67+
68+- launchd label: `so.makefile.baa-codexd`
69+- 本地监听地址:`http://127.0.0.1:4319`
70+- 本地事件流:`ws://127.0.0.1:4319/v1/codexd/events`
71+- 正式模式:`app-server`
72+- child 策略:`spawn`
73+- child 命令:`codex app-server`
74+- 日志目录:`logs/codexd/`
75+- 状态目录:`state/codexd/`
76+
77+正式 launchd 运行面固定写入:
78+
79+```text
80+BAA_CODEXD_MODE=app-server
81+BAA_CODEXD_LOCAL_API_BASE=http://127.0.0.1:4319
82+BAA_CODEXD_EVENT_STREAM_PATH=/v1/codexd/events
83+BAA_CODEXD_SERVER_STRATEGY=spawn
84+BAA_CODEXD_SERVER_COMMAND=codex
85+BAA_CODEXD_SERVER_ARGS=app-server
86+BAA_CODEXD_SERVER_CWD=/Users/george/code/baa-conductor
87+BAA_CODEXD_SERVER_ENDPOINT=stdio://codex-app-server
88+BAA_CODEXD_LOGS_DIR=/Users/george/code/baa-conductor/logs/codexd
89+BAA_CODEXD_STATE_DIR=/Users/george/code/baa-conductor/state/codexd
90+```
91
92 ## 组件边界
93
94@@ -67,287 +38,70 @@
95
96 - 系统真相
97 - 任务编排
98-- HTTP / WS 主接口
99-- pause / resume / drain
100-- task / run / state / capability 读写
101-
102-### `worker-runner`
103-
104-负责:
105-
106-- 通用 step 执行模型
107-- 本地目录结构
108-- checkpoint 落盘
109-- 日志文件管理
110+- 对外主 HTTP / WS 接口
111+- 健康感知、重连和降级
112
113 ### `codexd`
114
115 负责:
116
117-- 启动和托管 Codex 子进程
118-- 管理 Codex 会话
119-- 处理 Codex 对话和 worker step 执行
120-- 记录 Codex stdout / stderr / 结构化事件
121-- 超时、重试、崩溃恢复
122+- 启动和托管 Codex child
123+- 管理本地 Codex 会话和运行态状态
124+- 维护 `logs/codexd/**` 与 `state/codexd/**`
125+- 提供本地 HTTP / WS 服务面
126+- 处理 child 崩溃后的重建
127
128 一句话:
129
130 - `conductor-daemon` 管系统
131-- `worker-runner` 管通用执行框架
132 - `codexd` 管 Codex 本身
133
134-额外约束:
135+## 当前本地服务面
136
137-- `conductor-daemon` 不自己长期持有 Codex 会话状态
138-- `codexd` 是唯一 Codex 会话 / turn / recent events 的真相源
139+`apps/codexd` 当前已经有本地 HTTP / WS 入口,正式运行时由 `launchd` 长期托管:
140
141-## 运行模型
142+- `GET /healthz`
143+- `GET /v1/codexd/status`
144+- `GET /v1/codexd/sessions`
145+- `POST /v1/codexd/sessions`
146+- `POST /v1/codexd/turn`
147+- `GET /v1/codexd/runs`
148+- `POST /v1/codexd/runs`
149+- `WS /v1/codexd/events`
150
151-`codexd` 最合理的运行模型是:
152+当前骨架已经会维护:
153
154-1. `conductor-daemon` 接到任务或对话请求
155-2. 需要 Codex 时,转给 `codexd`
156-3. `codexd` 负责:
157- - 创建或复用 Codex session
158- - 启动子进程
159- - 采集增量输出
160- - 记录日志
161- - 返回 step 结果或对话结果
162-4. `conductor-daemon` 把结果写回本地真相源
163+- daemon identity
164+- child state
165+- session registry
166+- run registry
167+- recent event cache
168+- 结构化事件日志
169
170-进程恢复规则:
171+## 运行职责边界
172
173-- `launchd` 负责把挂掉的 `conductor-daemon` 或 `codexd` 拉起
174+- `launchd` 负责开机自启动和硬重启
175 - `conductor-daemon` 负责发现 `codexd` 不可用并重连
176 - `codexd` 负责发现 Codex child 不可用并重建子进程
177-- 不做“conductor 直接 spawn codexd”或“codexd 直接 spawn conductor”
178-
179-## 官方接口结论
180-
181-基于当前本机已验证的 Codex CLI 公开接口,`codexd` 的设计结论应明确为:
182-
183-- 主会话与双工能力:基于 `codex app-server`
184-- 不实现 `codex exec` 式无交互正式模式
185-- `conductor-daemon` 对外正式能力只保留 session / turn / status
186-- 不驱动 TUI
187-- 不逆向私有协议
188-
189-原因:
190-
191-- 当前目标是常驻、多轮、可恢复的会话代理,不是一次性命令壳
192-- `codex exec` 虽然适合一次性非交互执行,但天然不是多轮双工模型
193-- 在当前环境里,`codex exec` 还表现出明显卡顿和假死风险
194-- `codex app-server` 虽然仍标记为 experimental,但它已经公开了最完整的线程、turn、流式事件和恢复语义
195-- 已验证单个 `app-server` 进程可承载多个 `thread`,因此默认不需要“一对话一进程”
196-
197-当前推荐口径:
198-
199-- `codexd v1` 继续围绕 `app-server`
200-- `conductor-daemon -> codexd` 是唯一正式代理链路
201-- 不新增、不对外暴露 `/v1/codex/runs*`
202-- 不新增、不扩展、不对外暴露 `codex exec` 路线
203-- 已存在的 `run` / fallback 相关实现视为过渡代码,应逐步删除
204-
205-## 当前骨架已经落下的内容
206-
207-`apps/codexd` 当前已经提供:
208-
209-- 最小 CLI:
210- - `start`
211- - `status`
212- - `config`
213- - `smoke`
214-- 最小配置:
215- - server mode
216- - logs/state dir
217- - app-server endpoint
218- - child process strategy
219-- 最小状态:
220- - daemon identity
221- - child process state
222- - session registry
223- - recent event cache
224-- 最小运行时文件:
225- - `logs/codexd/events.jsonl`
226- - `logs/codexd/stdout.log`
227- - `logs/codexd/stderr.log`
228- - `state/codexd/identity.json`
229- - `state/codexd/daemon-state.json`
230- - `state/codexd/session-registry.json`
231- - `state/codexd/recent-events.json`
232-
233-当前 `smoke` 不依赖真实 Codex CLI。
234-
235-- 它使用内置 stub child 验证骨架目录、状态文件和结构化日志能否闭环写出
236-
237-当前 `start` 的语义是:
238-
239-- `spawn` 策略下,拉起一个配置好的 child command 并持续托管它
240-- `external` 策略下,不启 child,只把 endpoint 和状态占位出来
241-
242-## 当前明确还没做的事
243-
244-当前骨架还没有:
245-
246-- `thread/start` / `thread/resume` / `turn/start` 的真实代理
247-- `codex-app-server` 传输层接线
248-- crash recovery 的自动复连和 session 恢复
249-- 更完整的事件恢复和会话恢复能力
250-
251-## 支持的两类工作
252-
253-### 1. worker 模式
254-
255-用于:
256-
257-- `codex` step
258-- 代码修改
259-- 审阅
260-- 任务执行
261-
262-特点:
263-
264-- 有 task / step / run 关联
265-- 受 timeout / retry / checkpoint 约束
266-- 由 `worker-runner` / `conductor-daemon` 编排
267-- 正式实现只走 `app-server`
268-- 不再把 `exec` 作为 worker 正路或降级路径
269-
270-### 2. duplex 对话模式
271-
272-用于:
273-
274-- 和 AI 的持续双向对话
275-- 支持 CLI / 网页版 AI 参与
276-- 长于一次性命令式调用
277-
278-特点:
279-
280-- 需要会话 ID
281-- 需要增量输出
282-- 需要日志和历史
283-- 可以和任务执行共用同一个 `codexd`,但不能混淆真相
284-- 这一层应直接建立在 `app-server` 的 `thread` / `turn` 模型上
285-
286-## 推荐能力面
287-
288-`codexd` 后续可以提供这些本地能力:
289-
290-### 会话类
291-
292-- 创建会话
293-- 列出会话
294-- 读取会话状态
295-- 关闭会话
296-
297-### 对话类
298-
299-- 向某个会话发送输入
300-- 读取流式输出
301-- 拉取最近日志
302-
303-### worker 类
304-
305-- 启动某个 `codex` step
306-- 查询 step 执行状态
307-- 取消 step
308-- 读取 step 日志和产物
309-
310-## 推荐实现顺序
311-
312-### v1
313-
314-- 启动单个常驻 `codex app-server`
315-- 由 `codexd` 维护:
316- - server 生命周期
317- - thread 映射
318- - turn 启动 / 打断 / 继续
319- - 增量事件日志
320-- 对 `conductor-daemon` 暴露稳定的本地适配层
321-- 这层至少拆成:
322- - 本地 HTTP:session / turn / status
323- - 本地 WS 或 SSE:增量事件流
324-
325-### v1 约束
326-
327-`codexd` 不再提供 `codex exec` 式无交互正式能力:
328-
329-- 不新增 `/runs` 类正式接口
330-- 不把 `exec` 当作 worker 正路
331-- 不把 `exec` 当作会话降级面
332-- 不把 `exec` 暴露给上层 AI 直接调用
333-
334-原因:
335-
336-- 卡顿明显
337-- 容易假死
338-- 不符合常驻双工模型
339-
340-### v2
341-
342-- 在 `app-server` 之上继续补:
343- - 会话恢复
344- - 多 thread 管理
345- - 对话 steering / interrupt
346- - worker 与对话统一日志索引
347-
348-## 日志与可恢复性
349-
350-`codexd` 不应该只把结果打印到终端。
351-
352-最少需要:
353-
354-- 会话级日志
355-- step 级日志
356-- 结构化事件日志
357-- 最近输出缓存
358-
359-推荐落点:
360-
361-- `logs/codexd/`
362-- `runs/<run-id>/`
363-- `state/codexd/`
364-
365-要支持:
366-
367-- 进程重启后恢复会话索引
368-- 任务执行失败后保留日志和上下文
369-- CLI / 网页 UI 可查询最近输出
370-
371-## 自启动
372-
373-当前已经预留了 launchd 模板;后续完整接线时,仍推荐:
374-
375-- 新增 `launchd` 服务
376-- 例如:`so.makefile.baa-codexd`
377-
378-默认行为:
379-
380-- `mini` 启动后自动拉起
381-- 不要求用户手工开 TUI
382-- TUI 只作为调试手段,而不是主运行方式
383-
384-## 当前建议
385-
386-当前实现已经有最小骨架,但继续遵守这些边界:
387+- 不做 `conductor-daemon` 直接 spawn `codexd`
388+- 不做 `codexd` 直接 spawn `conductor-daemon`
389
390-- `conductor-daemon` 是主接口
391-- `worker-runner` 是通用执行框架
392-- `codex` 仍只是 step kind 和未来的 worker 类型
393+## 正式口径明确排除的内容
394
395-如果开始实现 `codexd`,默认遵守这条约束:
396+下面这些不属于正式 launchd 运行面:
397
398-- `app-server` 是主能力面
399-- `conductor-daemon` 只代理独立 `codexd`
400-- 不把 `runs` / `exec` 扩成正式产品能力
401-- `packages/codex-exec` 不再作为正式产品能力推进;后续应缩成内部测试/迁移遗留,或直接删除
402+- `codex exec` 正式模式
403+- TUI 常驻模式
404+- 两个进程互相直接拉起
405+- 把 `codexd` 描述成“可选以后再说”的附属能力
406
407-不要把当前系统误认为已经有:
408+`codex exec` 可以继续存在于测试、临时工具或过渡代码里,但不进入 `mini` 正式 runtime 配置、launchd 模板或安装脚本。
409
410-- Codex daemon
411-- Codex 会话代理
412-- Codex 双工对话桥
413+## 当前仍未补齐的部分
414
415-## 一句话定义
416+当前 runtime / launchd / 检查口径已经正式纳入 `codexd`,但以下能力仍在后续演进范围:
417
418-`codexd` = `mini` 上的 Codex 常驻代理,用来承接 Codex 的 worker 执行、对话、日志与恢复;它是 `baa-conductor` 的后续组件,不是当前已上线能力。
419+- 更完整的 `conductor-daemon -> codexd` 代理接线
420+- 更丰富的会话恢复和断线重放
421+- 更强的 steering / interrupt / replay 语义
422+- 更细的 worker 与对话统一日志索引
+22,
-8
1@@ -4,6 +4,8 @@
2
3 - canonical local API: `http://100.71.210.78:4317`
4 - canonical local Firefox WS: `ws://100.71.210.78:4317/ws/firefox`
5+- codexd local API: `http://127.0.0.1:4319`
6+- codexd event stream: `ws://127.0.0.1:4319/v1/codexd/events`
7 - canonical public host: `https://conductor.makefile.so`
8 - local status view: `http://100.71.210.78:4318`
9
10@@ -20,13 +22,16 @@
11 说明:
12
13 - `BAA_CONTROL_API_BASE` 是兼容变量名,当前主要给 `status-api` 和遗留脚本使用
14-- 它的默认值已经收口到 `https://conductor.makefile.so`,不再代表单独旧控制面
15+- 它的默认值已经收口到 `https://conductor.makefile.so`
16+- `codexd` 独立安装时不要求 `BAA_SHARED_TOKEN`
17
18 ## codexd 变量
19
20 `apps/codexd` 当前识别这些变量:
21
22 - `BAA_CODEXD_REPO_ROOT`
23+- `BAA_CODEXD_LOCAL_API_BASE`
24+- `BAA_CODEXD_EVENT_STREAM_PATH`
25 - `BAA_CODEXD_MODE`
26 - `BAA_CODEXD_LOGS_DIR`
27 - `BAA_CODEXD_STATE_DIR`
28@@ -39,20 +44,27 @@
29 - `BAA_CODEXD_SMOKE_LIFETIME_MS`
30 - `BAA_CODEXD_VERSION`
31
32-当前默认值:
33+正式 launchd 运行面默认写入:
34
35 ```text
36+BAA_CODEXD_REPO_ROOT=/Users/george/code/baa-conductor
37+BAA_CODEXD_LOCAL_API_BASE=http://127.0.0.1:4319
38+BAA_CODEXD_EVENT_STREAM_PATH=/v1/codexd/events
39 BAA_CODEXD_MODE=app-server
40 BAA_CODEXD_SERVER_STRATEGY=spawn
41 BAA_CODEXD_SERVER_COMMAND=codex
42 BAA_CODEXD_SERVER_ARGS=app-server
43+BAA_CODEXD_SERVER_CWD=/Users/george/code/baa-conductor
44 BAA_CODEXD_SERVER_ENDPOINT=stdio://codex-app-server
45+BAA_CODEXD_LOGS_DIR=/Users/george/code/baa-conductor/logs/codexd
46+BAA_CODEXD_STATE_DIR=/Users/george/code/baa-conductor/state/codexd
47 ```
48
49-派生目录:
50+说明:
51
52-- `BAA_CODEXD_LOGS_DIR` 未设置时,默认 `${BAA_LOGS_DIR}/codexd`
53-- `BAA_CODEXD_STATE_DIR` 未设置时,默认 `${BAA_STATE_DIR}/codexd`
54+- 正式运行面只支持 `app-server`
55+- 不为 launchd 运行面增加 `codex exec` 正式开关
56+- `BAA_CODEXD_LOCAL_API_BASE` 必须保持 loopback host;当前默认是 `127.0.0.1:4319`
57
58 ## 节点变量
59
60@@ -66,7 +78,7 @@ BAA_STATUS_API_HOST=100.71.210.78
61 BAA_CONTROL_API_BASE=https://conductor.makefile.so
62 ```
63
64-上面最后一项只是兼容旧代码的变量名;默认目标已经与 canonical public host 对齐。
65+最后一项只是兼容旧代码的变量名;默认目标已经与 canonical public host 对齐。
66
67 Firefox WS 派生规则:
68
69@@ -81,15 +93,17 @@ Firefox WS 派生规则:
70
71 ## 最小例子
72
73-当前脚本仍要求保留兼容参数时,可这样安装:
74-
75 ```bash
76 ./scripts/runtime/install-launchd.sh \
77 --repo-dir /Users/george/code/baa-conductor \
78 --node mini \
79+ --service conductor \
80+ --service codexd \
81+ --service status-api \
82 --shared-token-file /Users/george/.config/baa-conductor/shared-token.txt \
83 --control-api-base https://conductor.makefile.so \
84 --local-api-base http://100.71.210.78:4317 \
85 --local-api-allowed-hosts 100.71.210.78 \
86+ --codexd-local-api-base http://127.0.0.1:4319 \
87 --status-api-host 100.71.210.78
88 ```
+45,
-85
1@@ -5,8 +5,8 @@
2 ## 当前目标状态
3
4 - `conductor` 由 `launchd` 托管,并承载 canonical local API `http://100.71.210.78:4317`
5-- `status-api` 仍会随默认安装一起部署,但只作为本地只读观察面
6-- `codexd` 现在有独立 plist 模板,但还没有接进统一安装脚本
7+- `codexd` 由 `launchd` 托管,并作为正式独立 Codex 运行面监听 `http://127.0.0.1:4319`
8+- `status-api` 随默认安装一起部署,但只作为本地只读观察面
9 - 工作目录固定到 `/Users/george/code/baa-conductor`
10 - 通过仓库内脚本统一安装、启动、停止、重启与验证
11
12@@ -20,16 +20,10 @@
13
14 1. 初始化 runtime 目录
15 2. 构建仓库
16-3. 渲染并安装 `conductor` / `status-api` 的 LaunchAgents
17+3. 渲染并安装 `conductor` / `codexd` / `status-api` 的 LaunchAgents
18 4. 重启 launchd 服务
19 5. 跑静态检查和节点检查
20
21-`codexd` 说明:
22-
23-- 模板文件已经在 [`ops/launchd/so.makefile.baa-codexd.plist`](../../ops/launchd/so.makefile.baa-codexd.plist)
24-- 当前任务没有改 `scripts/runtime/install-launchd.sh`
25-- 所以它现在还是“手工可加载模板”,不是 `install-mini.sh` 的默认安装对象
26-
27 默认会把共享 token 收口到:
28
29 - `~/.config/baa-conductor/shared-token.txt`
30@@ -38,12 +32,13 @@
31
32 - `~/.config/baa-conductor/runtime-secrets.env`
33
34-里提取 `BAA_SHARED_TOKEN` 并生成它。
35+里提取 `BAA_SHARED_TOKEN` 并生成它;如果新的 env 文件不存在,还会回退读取 legacy `control-api-worker.secrets.env`。
36
37 说明:
38
39-- 如果新的 `runtime-secrets.env` 不存在,脚本还会回退读取 legacy `control-api-worker.secrets.env`
40-- 脚本里保留 `--control-api-base` 只是为了写入兼容变量名;默认值已经是 `https://conductor.makefile.so`
41+- `codexd` 独立安装时不需要共享 token
42+- `--control-api-base` 仍然保留,只是为了写入兼容变量 `BAA_CONTROL_API_BASE`
43+- `codexd` 正式运行面只写入 `app-server` 相关默认值,不暴露 `codex exec` 正式开关
44
45 ## 日常管理
46
47@@ -71,116 +66,81 @@
48 ./scripts/runtime/restart-launchd.sh
49 ```
50
51-当前 `restart-launchd.sh` / `reload-launchd.sh` 在 `bootstrap + kickstart` 结束后不会只看 `launchctl` 返回码,而会继续检查 `conductor /healthz`。
52+这些命令默认同时操作 `conductor`、`codexd`、`status-api`。需要单独管理时,用 `--service codexd`、`--service conductor` 或 `--service status-api`。
53
54-- 优先读取已安装 `conductor` plist 里的 `BAA_CONDUCTOR_LOCAL_API`
55-- 默认检查 `<local-api-base>/healthz`
56-- 如果第一次 reload 后仍然是 loaded but not serving,会自动再执行一次:
57+例如只重启 `codexd`:
58
59 ```bash
60-launchctl kickstart -k gui/$(id -u)/so.makefile.baa-conductor
61+./scripts/runtime/restart-launchd.sh --service codexd
62 ```
63
64-- 如果二次 kickstart 后仍未恢复,脚本会返回非零,并输出:
65- - 最后一次 health probe 的 curl 结果
66- - `launchctl print` 诊断
67- - `conductor` stdout/stderr 日志尾部
68- - 手工兜底命令提示
69-
70-## 1. 构建
71-
72-```bash
73-npx --yes pnpm -r build
74-```
75+## 渲染安装副本
76
77-## 2. 初始化 runtime 目录
78-
79-```bash
80-./scripts/runtime/bootstrap.sh --repo-dir /Users/george/code/baa-conductor
81-```
82-
83-## 3. 渲染安装副本
84-
85-保留 `--control-api-base`,只是为了让当前 `status-api` 继续读取兼容变量名:
86+完整 mini 安装副本示例:
87
88 ```bash
89 ./scripts/runtime/install-launchd.sh \
90 --repo-dir /Users/george/code/baa-conductor \
91 --node mini \
92 --service conductor \
93+ --service codexd \
94 --service status-api \
95 --install-dir /Users/george/Library/LaunchAgents \
96 --shared-token-file /Users/george/.config/baa-conductor/shared-token.txt \
97 --control-api-base https://conductor.makefile.so \
98 --local-api-base http://100.71.210.78:4317 \
99 --local-api-allowed-hosts 100.71.210.78 \
100+ --codexd-local-api-base http://127.0.0.1:4319 \
101 --status-api-host 100.71.210.78
102 ```
103
104-## 4. 静态校验
105+单独安装 `codexd`:
106
107 ```bash
108-./scripts/runtime/check-launchd.sh \
109+./scripts/runtime/install-launchd.sh \
110 --repo-dir /Users/george/code/baa-conductor \
111 --node mini \
112- --service conductor \
113- --service status-api \
114+ --service codexd \
115 --install-dir /Users/george/Library/LaunchAgents \
116- --shared-token-file /Users/george/.config/baa-conductor/shared-token.txt \
117- --control-api-base https://conductor.makefile.so \
118- --local-api-base http://100.71.210.78:4317 \
119- --local-api-allowed-hosts 100.71.210.78 \
120- --status-api-host 100.71.210.78
121+ --codexd-local-api-base http://127.0.0.1:4319
122 ```
123
124-## 5. 重载
125+`codexd` 默认写入这些正式运行值:
126
127-```bash
128-./scripts/runtime/reload-launchd.sh \
129- --install-dir /Users/george/Library/LaunchAgents \
130- --service conductor \
131- --service status-api
132-```
133+- `BAA_CODEXD_MODE=app-server`
134+- `BAA_CODEXD_LOCAL_API_BASE=http://127.0.0.1:4319`
135+- `BAA_CODEXD_EVENT_STREAM_PATH=/v1/codexd/events`
136+- `BAA_CODEXD_SERVER_STRATEGY=spawn`
137+- `BAA_CODEXD_SERVER_COMMAND=codex`
138+- `BAA_CODEXD_SERVER_ARGS=app-server`
139+- `BAA_CODEXD_SERVER_CWD=/Users/george/code/baa-conductor`
140+- `BAA_CODEXD_SERVER_ENDPOINT=stdio://codex-app-server`
141
142-推荐最小验证:
143+## reload 行为
144
145-```bash
146-./scripts/runtime/restart-launchd.sh
147-curl -fsS http://100.71.210.78:4317/healthz
148-curl -fsS http://100.71.210.78:4317/rolez
149-```
150+`restart-launchd.sh` / `reload-launchd.sh` 不只看 `launchctl` 返回码,还会等待已选服务的 `/healthz` 恢复:
151
152-预期:
153+- `conductor`: 读取 `BAA_CONDUCTOR_LOCAL_API`
154+- `codexd`: 读取 `BAA_CODEXD_LOCAL_API_BASE`
155+- `status-api`: 读取 `BAA_STATUS_API_HOST` 并按默认端口 `4318` 探活
156
157-- `restart-launchd.sh` 直接成功返回
158-- `/healthz` 返回 `ok`
159-- `/rolez` 返回当前角色
160-- 不需要再手工执行一次 `launchctl kickstart -k gui/$(id -u)/so.makefile.baa-conductor`
161-
162-## 6. 节点检查
163+如果服务处于 loaded 但未提供 HTTP,脚本会自动再执行一次:
164
165 ```bash
166-./scripts/runtime/check-node.sh \
167- --repo-dir /Users/george/code/baa-conductor \
168- --node mini \
169- --service conductor \
170- --service status-api \
171- --install-dir /Users/george/Library/LaunchAgents \
172- --local-api-base http://100.71.210.78:4317 \
173- --local-api-allowed-hosts 100.71.210.78 \
174- --status-api-base http://100.71.210.78:4318 \
175- --status-api-host 100.71.210.78 \
176- --expected-rolez leader \
177- --check-loaded
178+launchctl kickstart -k gui/$(id -u)/<label>
179 ```
180
181-## 7. 当前验证口径
182+如果二次 kickstart 后仍未恢复,脚本会返回非零,并输出:
183
184-最小验证就是这两条:
185+- 最后一次 health probe 的 curl 结果
186+- `launchctl print` 诊断
187+- 对应服务 stdout/stderr 日志尾部
188+- 手工兜底命令提示
189
190-```bash
191-./scripts/runtime/status-launchd.sh
192-./scripts/runtime/check-node.sh --node mini --check-loaded --expected-rolez leader
193-```
194+## 职责边界
195
196-如果要单独试 `codexd` 模板,先 build,再手工复制 plist 到 `~/Library/LaunchAgents`,最后用 `launchctl bootstrap` / `launchctl kickstart` 加载它。
197+- `launchd` 负责开机自启动和硬重启
198+- `conductor-daemon` 负责发现 `codexd` 不可用并重连
199+- `codexd` 负责发现 Codex child 不可用并重建子进程
200+- 不做 `conductor-daemon` 直接 spawn `codexd`
201+- 不做 `codexd` 直接 spawn `conductor-daemon`
+28,
-22
1@@ -1,6 +1,10 @@
2 # node verification
3
4-当前只检查 `mini`,并把 `4317` 视为主接口。
5+当前只检查 `mini`,正式运行面同时认识:
6+
7+- `conductor` `http://100.71.210.78:4317`
8+- `codexd` `http://127.0.0.1:4319`
9+- `status-api` `http://100.71.210.78:4318`
10
11 ## 1. 构建与静态检查
12
13@@ -10,19 +14,21 @@ npx --yes pnpm -r build
14 --repo-dir /Users/george/code/baa-conductor \
15 --node mini \
16 --service conductor \
17+ --service codexd \
18 --service status-api \
19 --install-dir /Users/george/Library/LaunchAgents \
20 --shared-token-file /Users/george/.config/baa-conductor/shared-token.txt \
21 --control-api-base https://conductor.makefile.so \
22 --local-api-base http://100.71.210.78:4317 \
23 --local-api-allowed-hosts 100.71.210.78 \
24+ --codexd-local-api-base http://127.0.0.1:4319 \
25 --status-api-host 100.71.210.78
26 ```
27
28 说明:
29
30 - `--control-api-base` 仍是当前静态检查参数,但只用于校验兼容变量 `BAA_CONTROL_API_BASE`
31-- 它的默认值已经与 `https://conductor.makefile.so` 对齐,不改变 `4317` 是 canonical local API 的事实
32+- `check-launchd.sh` 现在会校验 `codexd` 的监听地址、事件流路径、日志目录、状态目录和 `app-server` child 配置
33
34 ## 2. 运行态检查
35
36@@ -31,17 +37,30 @@ npx --yes pnpm -r build
37 --repo-dir /Users/george/code/baa-conductor \
38 --node mini \
39 --service conductor \
40+ --service codexd \
41 --service status-api \
42 --install-dir /Users/george/Library/LaunchAgents \
43 --local-api-base http://100.71.210.78:4317 \
44 --local-api-allowed-hosts 100.71.210.78 \
45+ --codexd-api-base http://127.0.0.1:4319 \
46 --status-api-base http://100.71.210.78:4318 \
47 --status-api-host 100.71.210.78 \
48 --expected-rolez leader \
49 --check-loaded
50 ```
51
52-## 3. 主路径手工探针
53+`check-node.sh` 当前会验证:
54+
55+- `launchctl print` 是否成功
56+- 进程命令行是否匹配
57+- `logs/launchd/*.log` 是否存在
58+- `conductor` 是否监听 `4317` 并返回 `/healthz`、`/readyz`、`/rolez`
59+- `codexd` 是否监听 `4319` 并返回 `/healthz`、`/v1/codexd/status`
60+- `status-api` 是否监听 `4318` 并返回 `/healthz`、`/v1/status`
61+
62+## 3. 手工探针
63+
64+主路径:
65
66 ```bash
67 curl -fsSL https://conductor.makefile.so/healthz
68@@ -50,34 +69,21 @@ curl -fsSL https://conductor.makefile.so/rolez
69 curl -fsSL https://conductor.makefile.so/v1/runtime
70 ```
71
72-on-node 时也可以直接探:
73+on-node:
74
75 ```bash
76 curl -fsSL http://100.71.210.78:4317/healthz
77 curl -fsSL http://100.71.210.78:4317/v1/runtime
78-```
79-
80-## 4. 兼容层检查
81-
82-如果 `status-api` 仍在本地保留,再检查:
83-
84-```bash
85+curl -fsSL http://127.0.0.1:4319/healthz
86+curl -fsSL http://127.0.0.1:4319/v1/codexd/status
87 curl -fsSL http://100.71.210.78:4318/v1/status
88 ```
89
90-如果你在做残留依赖排查,优先确认安装副本里的兼容变量已经收口到当前公网域名:
91-
92-```bash
93-/usr/libexec/PlistBuddy -c 'Print :EnvironmentVariables:BAA_CONTROL_API_BASE' \
94- /Users/george/Library/LaunchAgents/so.makefile.baa-conductor.plist
95-```
96-
97-期望输出是 `https://conductor.makefile.so`;如果不是,说明节点仍残留旧口径。
98-
99-## 常见失败点
100+## 4. 常见失败点
101
102 - `conductor /rolez` 不是 `leader`
103+- `codexd` 没有监听 `127.0.0.1:4319`
104+- `codexd /v1/codexd/status` 没有返回 `app-server` 运行信息
105 - `conductor.makefile.so` 没有正确回源到 `100.71.210.78:4317`
106-- `status-api /v1/status` 没有正确回源到当前 `conductor.makefile.so` / `4317`,导致本地观察结果漂移
107 - `launchctl print` 失败
108 - `logs/launchd/*.log` 没有新内容
+15,
-3
1@@ -2,9 +2,11 @@
2 <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3 <!--
4 Source template kept in the repo.
5- This codexd plist is intentionally manual for now: install-launchd.sh has not
6- been extended in this task, so render and copy it explicitly if you want to
7- load codexd under launchd before the runtime scripts are taught about it.
8+ Default values target the formal mini codexd runtime at
9+ /Users/george/code/baa-conductor.
10+ Use scripts/runtime/install-launchd.sh to render the actual install copy.
11+ launchd is responsible for auto-start and hard restart; codexd itself only
12+ manages Codex child health and reconnect.
13 -->
14 <plist version="1.0">
15 <dict>
16@@ -30,8 +32,18 @@
17 <string>/Users/george/code/baa-conductor/logs</string>
18 <key>BAA_STATE_DIR</key>
19 <string>/Users/george/code/baa-conductor/state</string>
20+ <key>BAA_CODEXD_REPO_ROOT</key>
21+ <string>/Users/george/code/baa-conductor</string>
22+ <key>BAA_CODEXD_LOGS_DIR</key>
23+ <string>/Users/george/code/baa-conductor/logs/codexd</string>
24+ <key>BAA_CODEXD_STATE_DIR</key>
25+ <string>/Users/george/code/baa-conductor/state/codexd</string>
26 <key>BAA_CODEXD_MODE</key>
27 <string>app-server</string>
28+ <key>BAA_CODEXD_LOCAL_API_BASE</key>
29+ <string>http://127.0.0.1:4319</string>
30+ <key>BAA_CODEXD_EVENT_STREAM_PATH</key>
31+ <string>/v1/codexd/events</string>
32 <key>BAA_CODEXD_SERVER_STRATEGY</key>
33 <string>spawn</string>
34 <key>BAA_CODEXD_SERVER_COMMAND</key>
1@@ -32,9 +32,11 @@
2 <key>BAA_CONDUCTOR_ROLE</key>
3 <string>primary</string>
4 <key>BAA_CONTROL_API_BASE</key>
5- <string>https://control-api.makefile.so</string>
6+ <string>https://conductor.makefile.so</string>
7 <key>BAA_CONDUCTOR_LOCAL_API</key>
8- <string>http://127.0.0.1:4317</string>
9+ <string>http://100.71.210.78:4317</string>
10+ <key>BAA_CONDUCTOR_LOCAL_API_ALLOWED_HOSTS</key>
11+ <string>100.71.210.78</string>
12 <key>BAA_RUNS_DIR</key>
13 <string>/Users/george/code/baa-conductor/runs</string>
14 <key>BAA_WORKTREES_DIR</key>
1@@ -1,7 +1,7 @@
2 <?xml version="1.0" encoding="UTF-8"?>
3 <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
4 <!--
5- Optional local status API.
6+ Local read-only status API for the mini runtime.
7 Keep the same runtime paths as conductor and worker-runner so that service
8 logs and temporary files stay under one repo-owned runtime root.
9 Use scripts/runtime/install-launchd.sh to render the actual install copy.
10@@ -29,11 +29,13 @@
11 <key>BAA_CONDUCTOR_ROLE</key>
12 <string>primary</string>
13 <key>BAA_CONTROL_API_BASE</key>
14- <string>https://control-api.makefile.so</string>
15+ <string>https://conductor.makefile.so</string>
16 <key>BAA_CONDUCTOR_LOCAL_API</key>
17- <string>http://127.0.0.1:4317</string>
18+ <string>http://100.71.210.78:4317</string>
19+ <key>BAA_CONDUCTOR_LOCAL_API_ALLOWED_HOSTS</key>
20+ <string>100.71.210.78</string>
21 <key>BAA_STATUS_API_HOST</key>
22- <string>127.0.0.1</string>
23+ <string>100.71.210.78</string>
24 <key>BAA_RUNS_DIR</key>
25 <string>/Users/george/code/baa-conductor/runs</string>
26 <key>BAA_WORKTREES_DIR</key>
1@@ -30,9 +30,11 @@
2 <key>BAA_CONDUCTOR_ROLE</key>
3 <string>primary</string>
4 <key>BAA_CONTROL_API_BASE</key>
5- <string>https://control-api.makefile.so</string>
6+ <string>https://conductor.makefile.so</string>
7 <key>BAA_CONDUCTOR_LOCAL_API</key>
8- <string>http://127.0.0.1:4317</string>
9+ <string>http://100.71.210.78:4317</string>
10+ <key>BAA_CONDUCTOR_LOCAL_API_ALLOWED_HOSTS</key>
11+ <string>100.71.210.78</string>
12 <key>BAA_RUNS_DIR</key>
13 <string>/Users/george/code/baa-conductor/runs</string>
14 <key>BAA_WORKTREES_DIR</key>
+73,
-13
1@@ -14,7 +14,7 @@ Options:
2 --node mini Select node defaults. Defaults to mini.
3 --scope agent|daemon Expected launchd scope for install copies. Defaults to agent.
4 --service NAME Add one service to the check set. Repeatable.
5- --all-services Check conductor, worker-runner, and status-api.
6+ --all-services Check conductor, codexd, worker-runner, and status-api.
7 --repo-dir PATH Repo root used to derive runtime paths.
8 --home-dir PATH HOME value expected in installed plist files.
9 --install-dir PATH Validate installed copies under this directory.
10@@ -24,6 +24,13 @@ Options:
11 --local-api-base URL Expected BAA_CONDUCTOR_LOCAL_API.
12 --local-api-allowed-hosts CSV
13 Expected BAA_CONDUCTOR_LOCAL_API_ALLOWED_HOSTS.
14+ --codexd-local-api-base URL
15+ Expected BAA_CODEXD_LOCAL_API_BASE.
16+ --codexd-event-stream-path PATH
17+ Expected BAA_CODEXD_EVENT_STREAM_PATH.
18+ --codexd-server-command COMMAND
19+ Expected BAA_CODEXD_SERVER_COMMAND.
20+ --codexd-server-cwd PATH Expected BAA_CODEXD_SERVER_CWD.
21 --status-api-host HOST Expected BAA_STATUS_API_HOST.
22 --username NAME Expected UserName for LaunchDaemons.
23 --skip-dist-check Skip dist/index.js existence checks.
24@@ -32,8 +39,7 @@ Options:
25 --help Show this help text.
26
27 Notes:
28- If no service is specified, only conductor is checked. Use --all-services or
29- repeat --service to opt into worker-runner/status-api templates.
30+ If no service is specified, conductor + codexd + status-api are checked.
31 EOF
32 }
33
34@@ -48,9 +54,13 @@ install_dir=""
35 shared_token=""
36 shared_token_file=""
37 control_api_base="${BAA_RUNTIME_DEFAULT_CONTROL_API_BASE}"
38-local_api_base="${BAA_RUNTIME_DEFAULT_LOCAL_API}"
39-local_api_allowed_hosts="${BAA_CONDUCTOR_LOCAL_API_ALLOWED_HOSTS:-}"
40-status_api_host="${BAA_STATUS_API_HOST:-127.0.0.1}"
41+local_api_base="http://100.71.210.78:4317"
42+local_api_allowed_hosts="${BAA_CONDUCTOR_LOCAL_API_ALLOWED_HOSTS:-100.71.210.78}"
43+codexd_local_api_base="${BAA_CODEXD_LOCAL_API_BASE:-${BAA_RUNTIME_DEFAULT_CODEXD_LOCAL_API}}"
44+codexd_event_stream_path="${BAA_CODEXD_EVENT_STREAM_PATH:-${BAA_RUNTIME_DEFAULT_CODEXD_EVENT_STREAM_PATH}}"
45+codexd_server_command="${BAA_CODEXD_SERVER_COMMAND:-${BAA_RUNTIME_DEFAULT_CODEXD_SERVER_COMMAND}}"
46+codexd_server_cwd="${BAA_CODEXD_SERVER_CWD:-}"
47+status_api_host="${BAA_STATUS_API_HOST:-100.71.210.78}"
48 username="$(default_username)"
49 skip_dist_check="0"
50 check_loaded="0"
51@@ -114,6 +124,22 @@ while [[ $# -gt 0 ]]; do
52 local_api_allowed_hosts="$2"
53 shift 2
54 ;;
55+ --codexd-local-api-base)
56+ codexd_local_api_base="$2"
57+ shift 2
58+ ;;
59+ --codexd-event-stream-path)
60+ codexd_event_stream_path="$2"
61+ shift 2
62+ ;;
63+ --codexd-server-command)
64+ codexd_server_command="$2"
65+ shift 2
66+ ;;
67+ --codexd-server-cwd)
68+ codexd_server_cwd="$2"
69+ shift 2
70+ ;;
71 --status-api-host)
72 status_api_host="$2"
73 shift 2
74@@ -153,7 +179,23 @@ if [[ "${#services[@]}" -eq 0 ]]; then
75 done < <(default_services)
76 fi
77
78-if [[ -n "$shared_token" || -n "$shared_token_file" ]]; then
79+if [[ -z "$codexd_server_cwd" ]]; then
80+ codexd_server_cwd="$repo_dir"
81+fi
82+
83+services_require_shared_token() {
84+ local service
85+
86+ for service in "${services[@]}"; do
87+ if service_requires_shared_token "$service"; then
88+ return 0
89+ fi
90+ done
91+
92+ return 1
93+}
94+
95+if services_require_shared_token && [[ -n "$shared_token" || -n "$shared_token_file" ]]; then
96 shared_token="$(load_shared_token "$shared_token" "$shared_token_file")"
97 fi
98
99@@ -192,7 +234,6 @@ check_installed_plist() {
100 local stdout_path="$3"
101 local stderr_path="$4"
102 local dist_entry="$5"
103- local actual_shared_token=""
104
105 assert_file "$plist_path"
106 plutil -lint "$plist_path" >/dev/null
107@@ -215,11 +256,15 @@ check_installed_plist() {
108 check_string_equals "${service}:stderr" "$(plist_print_value "$plist_path" ":StandardErrorPath")" "$stderr_path"
109 check_string_equals "${service}:entry" "$(plist_print_value "$plist_path" ":ProgramArguments:2")" "$dist_entry"
110
111- actual_shared_token="$(plist_print_value "$plist_path" ":EnvironmentVariables:BAA_SHARED_TOKEN")"
112- if [[ -n "$shared_token" ]]; then
113- check_string_equals "${service}:BAA_SHARED_TOKEN" "$actual_shared_token" "$shared_token"
114- elif [[ -z "$actual_shared_token" || "$actual_shared_token" == "replace-me" ]]; then
115- die "${service}: BAA_SHARED_TOKEN is empty or still replace-me"
116+ if service_requires_shared_token "$service"; then
117+ local actual_shared_token
118+
119+ actual_shared_token="$(plist_print_value "$plist_path" ":EnvironmentVariables:BAA_SHARED_TOKEN")"
120+ if [[ -n "$shared_token" ]]; then
121+ check_string_equals "${service}:BAA_SHARED_TOKEN" "$actual_shared_token" "$shared_token"
122+ elif [[ -z "$actual_shared_token" || "$actual_shared_token" == "replace-me" ]]; then
123+ die "${service}: BAA_SHARED_TOKEN is empty or still replace-me"
124+ fi
125 fi
126
127 if [[ "$service" == "conductor" ]]; then
128@@ -227,6 +272,21 @@ check_installed_plist() {
129 check_string_equals "${service}:role-arg" "$(plist_print_value "$plist_path" ":ProgramArguments:6")" "$conductor_role"
130 fi
131
132+ if [[ "$service" == "codexd" ]]; then
133+ check_string_equals "${service}:start-arg" "$(plist_print_value "$plist_path" ":ProgramArguments:3")" "start"
134+ check_string_equals "${service}:BAA_CODEXD_REPO_ROOT" "$(plist_print_value "$plist_path" ":EnvironmentVariables:BAA_CODEXD_REPO_ROOT")" "$repo_dir"
135+ check_string_equals "${service}:BAA_CODEXD_LOGS_DIR" "$(plist_print_value "$plist_path" ":EnvironmentVariables:BAA_CODEXD_LOGS_DIR")" "${repo_dir}/logs/codexd"
136+ check_string_equals "${service}:BAA_CODEXD_STATE_DIR" "$(plist_print_value "$plist_path" ":EnvironmentVariables:BAA_CODEXD_STATE_DIR")" "${repo_dir}/state/codexd"
137+ check_string_equals "${service}:BAA_CODEXD_MODE" "$(plist_print_value "$plist_path" ":EnvironmentVariables:BAA_CODEXD_MODE")" "$BAA_RUNTIME_DEFAULT_CODEXD_SERVER_MODE"
138+ check_string_equals "${service}:BAA_CODEXD_LOCAL_API_BASE" "$(plist_print_value "$plist_path" ":EnvironmentVariables:BAA_CODEXD_LOCAL_API_BASE")" "$codexd_local_api_base"
139+ check_string_equals "${service}:BAA_CODEXD_EVENT_STREAM_PATH" "$(plist_print_value "$plist_path" ":EnvironmentVariables:BAA_CODEXD_EVENT_STREAM_PATH")" "$codexd_event_stream_path"
140+ check_string_equals "${service}:BAA_CODEXD_SERVER_STRATEGY" "$(plist_print_value "$plist_path" ":EnvironmentVariables:BAA_CODEXD_SERVER_STRATEGY")" "$BAA_RUNTIME_DEFAULT_CODEXD_SERVER_STRATEGY"
141+ check_string_equals "${service}:BAA_CODEXD_SERVER_COMMAND" "$(plist_print_value "$plist_path" ":EnvironmentVariables:BAA_CODEXD_SERVER_COMMAND")" "$codexd_server_command"
142+ check_string_equals "${service}:BAA_CODEXD_SERVER_ARGS" "$(plist_print_value "$plist_path" ":EnvironmentVariables:BAA_CODEXD_SERVER_ARGS")" "$BAA_RUNTIME_DEFAULT_CODEXD_SERVER_ARGS"
143+ check_string_equals "${service}:BAA_CODEXD_SERVER_CWD" "$(plist_print_value "$plist_path" ":EnvironmentVariables:BAA_CODEXD_SERVER_CWD")" "$codexd_server_cwd"
144+ check_string_equals "${service}:BAA_CODEXD_SERVER_ENDPOINT" "$(plist_print_value "$plist_path" ":EnvironmentVariables:BAA_CODEXD_SERVER_ENDPOINT")" "$BAA_RUNTIME_DEFAULT_CODEXD_SERVER_ENDPOINT"
145+ fi
146+
147 if [[ "$service" == "status-api" ]]; then
148 check_string_equals "${service}:BAA_STATUS_API_HOST" "$(plist_print_value "$plist_path" ":EnvironmentVariables:BAA_STATUS_API_HOST")" "$status_api_host"
149 fi
+33,
-8
1@@ -14,7 +14,7 @@ Options:
2 --node mini Select node defaults. Defaults to mini.
3 --scope agent|daemon Expected launchd scope. Defaults to agent.
4 --service NAME Add one service to the runtime check set. Repeatable.
5- --all-services Check conductor, worker-runner, and status-api.
6+ --all-services Check conductor, codexd, worker-runner, and status-api.
7 --repo-dir PATH Repo root used to derive runtime paths.
8 --home-dir PATH HOME value expected in installed plist files.
9 --install-dir PATH Validate installed copies under this directory.
10@@ -24,6 +24,7 @@ Options:
11 --local-api-base URL Conductor local API base URL. Defaults to 127.0.0.1:4317.
12 --local-api-allowed-hosts CSV
13 Expected BAA_CONDUCTOR_LOCAL_API_ALLOWED_HOSTS in installed copies.
14+ --codexd-api-base URL codexd local API base URL. Defaults to 127.0.0.1:4319.
15 --status-api-base URL Status API base URL. Defaults to 127.0.0.1:4318.
16 --status-api-host HOST Expected BAA_STATUS_API_HOST in installed copies.
17 --username NAME Expected UserName for LaunchDaemons.
18@@ -33,13 +34,12 @@ Options:
19 --skip-static-check Skip the underlying check-launchd.sh pass.
20 --skip-port-check Skip local TCP LISTEN checks.
21 --skip-process-check Skip host process command-line checks.
22- --skip-http-check Skip conductor/status-api HTTP probes.
23+ --skip-http-check Skip conductor/codexd/status-api HTTP probes.
24 --skip-log-check Skip launchd stdout/stderr file checks.
25 --help Show this help text.
26
27 Notes:
28- The default runtime check set is conductor + status-api, because that is the
29- minimum on-node surface for a realistic node verification pass. Use
30+ The default runtime check set is conductor + codexd + status-api. Use
31 --service to narrow the scope or --all-services to include worker-runner.
32 EOF
33 }
34@@ -57,10 +57,11 @@ install_dir=""
35 shared_token=""
36 shared_token_file=""
37 control_api_base="${BAA_RUNTIME_DEFAULT_CONTROL_API_BASE}"
38-local_api_base="${BAA_RUNTIME_DEFAULT_LOCAL_API}"
39-local_api_allowed_hosts="${BAA_CONDUCTOR_LOCAL_API_ALLOWED_HOSTS:-}"
40-status_api_base="${BAA_RUNTIME_DEFAULT_STATUS_API}"
41-status_api_host="${BAA_STATUS_API_HOST:-127.0.0.1}"
42+local_api_base="http://100.71.210.78:4317"
43+local_api_allowed_hosts="${BAA_CONDUCTOR_LOCAL_API_ALLOWED_HOSTS:-100.71.210.78}"
44+codexd_api_base="${BAA_RUNTIME_DEFAULT_CODEXD_LOCAL_API}"
45+status_api_base="http://100.71.210.78:4318"
46+status_api_host="${BAA_STATUS_API_HOST:-100.71.210.78}"
47 username="$(default_username)"
48 domain_target=""
49 check_loaded="0"
50@@ -129,6 +130,10 @@ while [[ $# -gt 0 ]]; do
51 local_api_allowed_hosts="$2"
52 shift 2
53 ;;
54+ --codexd-api-base)
55+ codexd_api_base="$2"
56+ shift 2
57+ ;;
58 --status-api-base)
59 status_api_base="$2"
60 shift 2
61@@ -337,6 +342,7 @@ run_static_checks() {
62 --control-api-base "$control_api_base"
63 --local-api-base "$local_api_base"
64 --local-api-allowed-hosts "$local_api_allowed_hosts"
65+ --codexd-local-api-base "$codexd_api_base"
66 --status-api-host "$status_api_host"
67 --username "$username"
68 )
69@@ -455,8 +461,24 @@ check_status_api_runtime() {
70 }
71
72 local_api_base="$(normalize_base_url "$local_api_base")"
73+codexd_api_base="$(normalize_base_url "$codexd_api_base")"
74 status_api_base="$(normalize_base_url "$status_api_base")"
75
76+check_codexd_runtime() {
77+ local codexd_base_url="$1"
78+ local port
79+
80+ if [[ "$skip_port_check" != "1" ]]; then
81+ port="$(extract_port_from_url "codexd" "$codexd_base_url")"
82+ check_listen_port "codexd" "$port"
83+ fi
84+
85+ if [[ "$skip_http_check" != "1" ]]; then
86+ assert_http_contains "codexd /healthz" "${codexd_base_url}/healthz" "200" "\"status\": \"ok\""
87+ assert_http_contains "codexd /v1/codexd/status" "${codexd_base_url}/v1/codexd/status" "200" "\"mode\": \"app-server\""
88+ fi
89+}
90+
91 if [[ "$skip_static_check" != "1" ]]; then
92 run_static_checks
93 elif [[ "$check_loaded" == "1" ]]; then
94@@ -476,6 +498,9 @@ for service in "${services[@]}"; do
95 conductor)
96 check_conductor_runtime "$local_api_base"
97 ;;
98+ codexd)
99+ check_codexd_runtime "$codexd_api_base"
100+ ;;
101 status-api)
102 check_status_api_runtime "$status_api_base"
103 ;;
+36,
-4
1@@ -9,6 +9,13 @@ readonly BAA_RUNTIME_SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &&
2 readonly BAA_RUNTIME_REPO_DIR_DEFAULT="$(cd -- "${BAA_RUNTIME_SCRIPT_DIR}/../.." && pwd)"
3 readonly BAA_RUNTIME_DEFAULT_CONTROL_API_BASE="https://conductor.makefile.so"
4 readonly BAA_RUNTIME_DEFAULT_LOCAL_API="http://127.0.0.1:4317"
5+readonly BAA_RUNTIME_DEFAULT_CODEXD_LOCAL_API="http://127.0.0.1:4319"
6+readonly BAA_RUNTIME_DEFAULT_CODEXD_EVENT_STREAM_PATH="/v1/codexd/events"
7+readonly BAA_RUNTIME_DEFAULT_CODEXD_SERVER_MODE="app-server"
8+readonly BAA_RUNTIME_DEFAULT_CODEXD_SERVER_STRATEGY="spawn"
9+readonly BAA_RUNTIME_DEFAULT_CODEXD_SERVER_COMMAND="codex"
10+readonly BAA_RUNTIME_DEFAULT_CODEXD_SERVER_ARGS="app-server"
11+readonly BAA_RUNTIME_DEFAULT_CODEXD_SERVER_ENDPOINT="stdio://codex-app-server"
12 readonly BAA_RUNTIME_DEFAULT_STATUS_API="http://127.0.0.1:4318"
13 readonly BAA_RUNTIME_DEFAULT_LOCALE="en_US.UTF-8"
14
15@@ -47,7 +54,7 @@ contains_value() {
16
17 validate_service() {
18 case "$1" in
19- conductor | worker-runner | status-api) ;;
20+ conductor | codexd | worker-runner | status-api) ;;
21 *)
22 die "Unsupported service: $1"
23 ;;
24@@ -73,15 +80,26 @@ validate_node() {
25 }
26
27 default_services() {
28- printf '%s\n' conductor
29+ printf '%s\n' conductor codexd status-api
30 }
31
32 default_node_verification_services() {
33- printf '%s\n' conductor status-api
34+ printf '%s\n' conductor codexd status-api
35 }
36
37 all_services() {
38- printf '%s\n' conductor worker-runner status-api
39+ printf '%s\n' conductor codexd worker-runner status-api
40+}
41+
42+service_requires_shared_token() {
43+ case "$1" in
44+ codexd)
45+ return 1
46+ ;;
47+ *)
48+ return 0
49+ ;;
50+ esac
51 }
52
53 service_label() {
54@@ -89,6 +107,9 @@ service_label() {
55 conductor)
56 printf '%s\n' "so.makefile.baa-conductor"
57 ;;
58+ codexd)
59+ printf '%s\n' "so.makefile.baa-codexd"
60+ ;;
61 worker-runner)
62 printf '%s\n' "so.makefile.baa-worker-runner"
63 ;;
64@@ -103,6 +124,9 @@ service_dist_entry_relative() {
65 conductor)
66 printf '%s\n' "apps/conductor-daemon/dist/index.js"
67 ;;
68+ codexd)
69+ printf '%s\n' "apps/codexd/dist/index.js"
70+ ;;
71 worker-runner)
72 printf '%s\n' "apps/worker-runner/dist/index.js"
73 ;;
74@@ -117,6 +141,9 @@ service_default_port() {
75 conductor)
76 printf '%s\n' "4317"
77 ;;
78+ codexd)
79+ printf '%s\n' "4319"
80+ ;;
81 status-api)
82 printf '%s\n' "4318"
83 ;;
84@@ -139,6 +166,9 @@ service_process_match() {
85 conductor)
86 printf '%s --host %s --role %s\n' "$dist_entry" "$conductor_host" "$conductor_role"
87 ;;
88+ codexd)
89+ printf '%s start\n' "$dist_entry"
90+ ;;
91 *)
92 printf '%s\n' "$dist_entry"
93 ;;
94@@ -309,9 +339,11 @@ resolve_runtime_paths() {
95
96 printf '%s\n' \
97 "${repo_dir}/state" \
98+ "${repo_dir}/state/codexd" \
99 "${repo_dir}/runs" \
100 "${repo_dir}/worktrees" \
101 "${repo_dir}/logs" \
102+ "${repo_dir}/logs/codexd" \
103 "${repo_dir}/logs/launchd" \
104 "${repo_dir}/tmp"
105 }
+81,
-10
1@@ -14,7 +14,7 @@ Options:
2 --node mini Select node defaults. Defaults to mini.
3 --scope agent|daemon Install under LaunchAgents or LaunchDaemons. Defaults to agent.
4 --service NAME Add one service to the install set. Repeatable.
5- --all-services Install conductor, worker-runner, and status-api templates.
6+ --all-services Install conductor, codexd, worker-runner, and status-api templates.
7 --repo-dir PATH Repo root used for WorkingDirectory and runtime paths.
8 --home-dir PATH HOME value written into plist files.
9 --install-dir PATH Override launchd install directory.
10@@ -24,13 +24,21 @@ Options:
11 --local-api-base URL Override BAA_CONDUCTOR_LOCAL_API.
12 --local-api-allowed-hosts CSV
13 Override BAA_CONDUCTOR_LOCAL_API_ALLOWED_HOSTS.
14+ --codexd-local-api-base URL
15+ Override BAA_CODEXD_LOCAL_API_BASE.
16+ --codexd-event-stream-path PATH
17+ Override BAA_CODEXD_EVENT_STREAM_PATH.
18+ --codexd-server-command COMMAND
19+ Override BAA_CODEXD_SERVER_COMMAND while keeping app-server mode.
20+ --codexd-server-cwd PATH Override BAA_CODEXD_SERVER_CWD.
21 --status-api-host HOST Override BAA_STATUS_API_HOST.
22 --username NAME UserName for LaunchDaemons. Defaults to the current user.
23 --help Show this help text.
24
25 Notes:
26- If no service is specified, only conductor is installed. Use --all-services or
27- repeat --service to opt into worker-runner/status-api templates.
28+ If no service is specified, conductor + codexd + status-api are installed.
29+ Use --service codexd to render codexd independently; it does not require a
30+ shared token.
31 EOF
32 }
33
34@@ -46,9 +54,14 @@ install_dir=""
35 shared_token="${BAA_SHARED_TOKEN:-}"
36 shared_token_file=""
37 control_api_base="${BAA_RUNTIME_DEFAULT_CONTROL_API_BASE}"
38-local_api_base="${BAA_RUNTIME_DEFAULT_LOCAL_API}"
39-local_api_allowed_hosts="${BAA_CONDUCTOR_LOCAL_API_ALLOWED_HOSTS:-}"
40-status_api_host="${BAA_STATUS_API_HOST:-127.0.0.1}"
41+local_api_base="http://100.71.210.78:4317"
42+local_api_allowed_hosts="${BAA_CONDUCTOR_LOCAL_API_ALLOWED_HOSTS:-100.71.210.78}"
43+codexd_local_api_base="${BAA_CODEXD_LOCAL_API_BASE:-${BAA_RUNTIME_DEFAULT_CODEXD_LOCAL_API}}"
44+codexd_event_stream_path="${BAA_CODEXD_EVENT_STREAM_PATH:-${BAA_RUNTIME_DEFAULT_CODEXD_EVENT_STREAM_PATH}}"
45+codexd_server_command="${BAA_CODEXD_SERVER_COMMAND:-${BAA_RUNTIME_DEFAULT_CODEXD_SERVER_COMMAND}}"
46+codexd_server_cwd="${BAA_CODEXD_SERVER_CWD:-}"
47+codexd_server_cwd_set="0"
48+status_api_host="${BAA_STATUS_API_HOST:-100.71.210.78}"
49 username="$(default_username)"
50 services=()
51
52@@ -109,6 +122,23 @@ while [[ $# -gt 0 ]]; do
53 local_api_allowed_hosts="$2"
54 shift 2
55 ;;
56+ --codexd-local-api-base)
57+ codexd_local_api_base="$2"
58+ shift 2
59+ ;;
60+ --codexd-event-stream-path)
61+ codexd_event_stream_path="$2"
62+ shift 2
63+ ;;
64+ --codexd-server-command)
65+ codexd_server_command="$2"
66+ shift 2
67+ ;;
68+ --codexd-server-cwd)
69+ codexd_server_cwd="$2"
70+ codexd_server_cwd_set="1"
71+ shift 2
72+ ;;
73 --status-api-host)
74 status_api_host="$2"
75 shift 2
76@@ -136,15 +166,33 @@ if [[ "${#services[@]}" -eq 0 ]]; then
77 done < <(default_services)
78 fi
79
80-shared_token="$(load_shared_token "$shared_token" "$shared_token_file")"
81-if [[ -z "$shared_token" ]]; then
82- die "A shared token is required. Use --shared-token, --shared-token-file, or BAA_SHARED_TOKEN."
83+services_require_shared_token() {
84+ local service
85+
86+ for service in "${services[@]}"; do
87+ if service_requires_shared_token "$service"; then
88+ return 0
89+ fi
90+ done
91+
92+ return 1
93+}
94+
95+if services_require_shared_token; then
96+ shared_token="$(load_shared_token "$shared_token" "$shared_token_file")"
97+ if [[ -z "$shared_token" ]]; then
98+ die "A shared token is required. Use --shared-token, --shared-token-file, or BAA_SHARED_TOKEN."
99+ fi
100 fi
101
102 if [[ -z "$install_dir" ]]; then
103 install_dir="$(default_install_dir "$scope" "$home_dir")"
104 fi
105
106+if [[ "$codexd_server_cwd_set" != "1" && -z "$codexd_server_cwd" ]]; then
107+ codexd_server_cwd="$repo_dir"
108+fi
109+
110 set -- $(resolve_node_defaults "$node")
111 conductor_host="$1"
112 conductor_role="$2"
113@@ -157,6 +205,8 @@ worktrees_dir="${repo_dir}/worktrees"
114 logs_dir="${repo_dir}/logs"
115 logs_launchd_dir="${logs_dir}/launchd"
116 tmp_dir="${repo_dir}/tmp"
117+codexd_logs_dir="${logs_dir}/codexd"
118+codexd_state_dir="${state_dir}/codexd"
119
120 assert_directory "$state_dir"
121 assert_directory "$runs_dir"
122@@ -166,6 +216,8 @@ assert_directory "$logs_launchd_dir"
123 assert_directory "$tmp_dir"
124
125 ensure_directory "$install_dir" "755"
126+ensure_directory "$codexd_logs_dir" "700"
127+ensure_directory "$codexd_state_dir" "700"
128
129 for service in "${services[@]}"; do
130 template_path="$(service_template_path "$repo_dir" "$service")"
131@@ -193,16 +245,35 @@ for service in "${services[@]}"; do
132 plist_set_string "$install_path" ":EnvironmentVariables:BAA_TMP_DIR" "$tmp_dir"
133 plist_set_string "$install_path" ":EnvironmentVariables:BAA_STATE_DIR" "$state_dir"
134 plist_set_string "$install_path" ":EnvironmentVariables:BAA_NODE_ID" "$node_id"
135- plist_set_string "$install_path" ":EnvironmentVariables:BAA_SHARED_TOKEN" "$shared_token"
136 plist_set_string "$install_path" ":StandardOutPath" "$stdout_path"
137 plist_set_string "$install_path" ":StandardErrorPath" "$stderr_path"
138 plist_set_string "$install_path" ":ProgramArguments:2" "$dist_entry"
139
140+ if service_requires_shared_token "$service"; then
141+ plist_set_string "$install_path" ":EnvironmentVariables:BAA_SHARED_TOKEN" "$shared_token"
142+ else
143+ plist_delete_key "$install_path" ":EnvironmentVariables:BAA_SHARED_TOKEN"
144+ fi
145+
146 if [[ "$service" == "conductor" ]]; then
147 plist_set_string "$install_path" ":ProgramArguments:4" "$conductor_host"
148 plist_set_string "$install_path" ":ProgramArguments:6" "$conductor_role"
149 fi
150
151+ if [[ "$service" == "codexd" ]]; then
152+ plist_set_string "$install_path" ":EnvironmentVariables:BAA_CODEXD_REPO_ROOT" "$repo_dir"
153+ plist_set_string "$install_path" ":EnvironmentVariables:BAA_CODEXD_LOGS_DIR" "$codexd_logs_dir"
154+ plist_set_string "$install_path" ":EnvironmentVariables:BAA_CODEXD_STATE_DIR" "$codexd_state_dir"
155+ plist_set_string "$install_path" ":EnvironmentVariables:BAA_CODEXD_MODE" "$BAA_RUNTIME_DEFAULT_CODEXD_SERVER_MODE"
156+ plist_set_string "$install_path" ":EnvironmentVariables:BAA_CODEXD_LOCAL_API_BASE" "$codexd_local_api_base"
157+ plist_set_string "$install_path" ":EnvironmentVariables:BAA_CODEXD_EVENT_STREAM_PATH" "$codexd_event_stream_path"
158+ plist_set_string "$install_path" ":EnvironmentVariables:BAA_CODEXD_SERVER_STRATEGY" "$BAA_RUNTIME_DEFAULT_CODEXD_SERVER_STRATEGY"
159+ plist_set_string "$install_path" ":EnvironmentVariables:BAA_CODEXD_SERVER_COMMAND" "$codexd_server_command"
160+ plist_set_string "$install_path" ":EnvironmentVariables:BAA_CODEXD_SERVER_ARGS" "$BAA_RUNTIME_DEFAULT_CODEXD_SERVER_ARGS"
161+ plist_set_string "$install_path" ":EnvironmentVariables:BAA_CODEXD_SERVER_CWD" "$codexd_server_cwd"
162+ plist_set_string "$install_path" ":EnvironmentVariables:BAA_CODEXD_SERVER_ENDPOINT" "$BAA_RUNTIME_DEFAULT_CODEXD_SERVER_ENDPOINT"
163+ fi
164+
165 if [[ "$service" == "status-api" ]]; then
166 plist_set_string "$install_path" ":EnvironmentVariables:BAA_STATUS_API_HOST" "$status_api_host"
167 fi
+10,
-1
1@@ -27,7 +27,7 @@ Notes:
2 This is the single-node mini convenience wrapper. It:
3 1. bootstraps runtime directories
4 2. builds the repo
5- 3. installs conductor + status-api LaunchAgents
6+ 3. installs conductor + codexd + status-api LaunchAgents
7 4. restarts them
8 5. verifies the node
9 EOF
10@@ -47,6 +47,7 @@ secrets_env=""
11 skip_build="0"
12 skip_restart="0"
13 skip_check="0"
14+codexd_api_base="${BAA_RUNTIME_DEFAULT_CODEXD_LOCAL_API}"
15 status_api_base="http://100.71.210.78:4318"
16
17 while [[ $# -gt 0 ]]; do
18@@ -177,35 +178,41 @@ run_or_print 0 "${SCRIPT_DIR}/install-launchd.sh" \
19 --repo-dir "$repo_dir" \
20 --node mini \
21 --service conductor \
22+ --service codexd \
23 --service status-api \
24 --install-dir "$install_dir" \
25 --shared-token-file "$shared_token_file" \
26 --control-api-base "https://conductor.makefile.so" \
27 --local-api-base "http://100.71.210.78:4317" \
28 --local-api-allowed-hosts "100.71.210.78" \
29+ --codexd-local-api-base "$codexd_api_base" \
30 --status-api-host "100.71.210.78"
31
32 if [[ "$skip_restart" != "1" ]]; then
33 run_or_print 0 "${SCRIPT_DIR}/restart-launchd.sh" \
34 --install-dir "$install_dir" \
35 --service conductor \
36+ --service codexd \
37 --service status-api
38 fi
39
40 if [[ "$skip_check" != "1" ]]; then
41 wait_for_http "conductor" "http://100.71.210.78:4317/healthz"
42+ wait_for_http "codexd" "${codexd_api_base}/healthz"
43 wait_for_http "status-api" "${status_api_base}/healthz"
44
45 run_or_print 0 "${SCRIPT_DIR}/check-launchd.sh" \
46 --repo-dir "$repo_dir" \
47 --node mini \
48 --service conductor \
49+ --service codexd \
50 --service status-api \
51 --install-dir "$install_dir" \
52 --shared-token-file "$shared_token_file" \
53 --control-api-base "https://conductor.makefile.so" \
54 --local-api-base "http://100.71.210.78:4317" \
55 --local-api-allowed-hosts "100.71.210.78" \
56+ --codexd-local-api-base "$codexd_api_base" \
57 --status-api-host "100.71.210.78" \
58 --check-loaded
59
60@@ -213,11 +220,13 @@ if [[ "$skip_check" != "1" ]]; then
61 --repo-dir "$repo_dir" \
62 --node mini \
63 --service conductor \
64+ --service codexd \
65 --service status-api \
66 --install-dir "$install_dir" \
67 --shared-token-file "$shared_token_file" \
68 --local-api-base "http://100.71.210.78:4317" \
69 --local-api-allowed-hosts "100.71.210.78" \
70+ --codexd-api-base "$codexd_api_base" \
71 --status-api-base "${status_api_base}" \
72 --status-api-host "100.71.210.78" \
73 --expected-rolez leader \
+69,
-23
1@@ -13,13 +13,16 @@ Usage:
2 Options:
3 --scope agent|daemon launchd domain type. Defaults to agent.
4 --service NAME Add one service to the reload set. Repeatable.
5- --all-services Reload conductor, worker-runner, and status-api.
6+ --all-services Reload conductor, codexd, worker-runner, and status-api.
7 --home-dir PATH Used only to derive the default LaunchAgents path.
8 --install-dir PATH Override launchd install directory.
9 --domain TARGET Override launchctl domain target. Defaults to gui/<uid> or system.
10 --skip-kickstart Skip launchctl kickstart after bootstrap.
11 --dry-run Print launchctl commands instead of executing them.
12 --help Show this help text.
13+
14+Notes:
15+ If no service is specified, conductor + codexd + status-api are reloaded.
16 EOF
17 }
18
19@@ -200,17 +203,43 @@ print_log_tail() {
20 tail -n 20 "$path" >&2
21 }
22
23-print_conductor_diagnostics() {
24- local plist_path="$1"
25- local label="$2"
26- local healthz_url="$3"
27+read_service_healthz_url() {
28+ local service="$1"
29+ local plist_path="$2"
30+ local base_url
31+ local status_host
32+
33+ case "$service" in
34+ conductor)
35+ base_url="$(read_plist_value_or_default "$plist_path" ":EnvironmentVariables:BAA_CONDUCTOR_LOCAL_API" "$BAA_RUNTIME_DEFAULT_LOCAL_API")"
36+ printf '%s/healthz\n' "$(normalize_url "$base_url")"
37+ ;;
38+ codexd)
39+ base_url="$(read_plist_value_or_default "$plist_path" ":EnvironmentVariables:BAA_CODEXD_LOCAL_API_BASE" "$BAA_RUNTIME_DEFAULT_CODEXD_LOCAL_API")"
40+ printf '%s/healthz\n' "$(normalize_url "$base_url")"
41+ ;;
42+ status-api)
43+ status_host="$(read_plist_value_or_default "$plist_path" ":EnvironmentVariables:BAA_STATUS_API_HOST" "127.0.0.1")"
44+ printf 'http://%s:%s/healthz\n' "$status_host" "$(service_default_port "$service")"
45+ ;;
46+ *)
47+ return 1
48+ ;;
49+ esac
50+}
51+
52+print_service_diagnostics() {
53+ local service="$1"
54+ local plist_path="$2"
55+ local label="$3"
56+ local healthz_url="$4"
57 local stdout_path
58 local stderr_path
59
60 stdout_path="$(read_plist_value_or_default "$plist_path" ":StandardOutPath" "")"
61 stderr_path="$(read_plist_value_or_default "$plist_path" ":StandardErrorPath" "")"
62
63- runtime_error "conductor did not recover after launchd reload"
64+ runtime_error "${service} did not recover after launchd reload"
65 runtime_error "health probe: ${healthz_url}"
66 runtime_error "last curl result: exit=${CURL_LAST_EXIT_CODE} http=${CURL_LAST_HTTP_STATUS:-000}"
67 if [[ -n "$CURL_LAST_ERROR" ]]; then
68@@ -222,50 +251,67 @@ print_conductor_diagnostics() {
69 runtime_error "launchctl print failed for ${domain_target}/${label}"
70 fi
71
72- print_log_tail "conductor stdout" "$stdout_path"
73- print_log_tail "conductor stderr" "$stderr_path"
74+ print_log_tail "${service} stdout" "$stdout_path"
75+ print_log_tail "${service} stderr" "$stderr_path"
76 runtime_error "manual recovery hint: launchctl kickstart -k ${domain_target}/${label}"
77 }
78
79-recover_conductor_after_reload() {
80- local service="conductor"
81+service_has_health_probe() {
82+ case "$1" in
83+ conductor | codexd | status-api)
84+ return 0
85+ ;;
86+ *)
87+ return 1
88+ ;;
89+ esac
90+}
91+
92+recover_service_after_reload() {
93+ local service="$1"
94 local label
95 local plist_path
96- local local_api_base
97 local healthz_url
98
99- if ! contains_value "$service" "${services[@]}"; then
100+ if ! service_has_health_probe "$service"; then
101 return 0
102 fi
103
104 label="$(service_label "$service")"
105 plist_path="$(service_install_path "$install_dir" "$service")"
106- local_api_base="$(read_plist_value_or_default "$plist_path" ":EnvironmentVariables:BAA_CONDUCTOR_LOCAL_API" "$BAA_RUNTIME_DEFAULT_LOCAL_API")"
107- healthz_url="$(normalize_url "$local_api_base")/healthz"
108+ healthz_url="$(read_service_healthz_url "$service" "$plist_path")"
109
110 if wait_for_http_healthz "$service" "$healthz_url"; then
111 return 0
112 fi
113
114 if [[ "$skip_kickstart" == "1" ]]; then
115- print_conductor_diagnostics "$plist_path" "$label" "$healthz_url"
116- die "conductor stayed unhealthy after reload with --skip-kickstart"
117+ print_service_diagnostics "$service" "$plist_path" "$label" "$healthz_url"
118+ die "${service} stayed unhealthy after reload with --skip-kickstart"
119 fi
120
121- runtime_error "conductor was loaded but not serving; retrying launchctl kickstart -k ${domain_target}/${label}"
122+ runtime_error "${service} was loaded but not serving; retrying launchctl kickstart -k ${domain_target}/${label}"
123 if ! launchctl kickstart -k "${domain_target}/${label}"; then
124 runtime_error "retry kickstart returned non-zero for ${domain_target}/${label}"
125- print_conductor_diagnostics "$plist_path" "$label" "$healthz_url"
126- die "conductor retry kickstart failed after reload"
127+ print_service_diagnostics "$service" "$plist_path" "$label" "$healthz_url"
128+ die "${service} retry kickstart failed after reload"
129 fi
130
131 if wait_for_http_healthz "$service" "$healthz_url"; then
132- runtime_log "conductor recovered after retry kickstart"
133+ runtime_log "${service} recovered after retry kickstart"
134 return 0
135 fi
136
137- print_conductor_diagnostics "$plist_path" "$label" "$healthz_url"
138- die "conductor failed to recover after reload"
139+ print_service_diagnostics "$service" "$plist_path" "$label" "$healthz_url"
140+ die "${service} failed to recover after reload"
141+}
142+
143+recover_services_after_reload() {
144+ local service
145+
146+ for service in "${services[@]}"; do
147+ recover_service_after_reload "$service"
148+ done
149 }
150
151 for service in "${services[@]}"; do
152@@ -291,7 +337,7 @@ if [[ "$skip_kickstart" != "1" ]]; then
153 fi
154
155 if [[ "$dry_run" != "1" ]]; then
156- recover_conductor_after_reload
157+ recover_services_after_reload
158 fi
159
160 runtime_log "launchd reload completed for ${domain_target}"
+2,
-2
1@@ -13,7 +13,7 @@ Usage:
2 Options:
3 --scope agent|daemon launchd domain type. Defaults to agent.
4 --service NAME Add one service to the start set. Repeatable.
5- --all-services Start conductor, worker-runner, and status-api.
6+ --all-services Start conductor, codexd, worker-runner, and status-api.
7 --home-dir PATH Used only to derive the default LaunchAgents path.
8 --install-dir PATH Override launchd install directory.
9 --domain TARGET Override launchctl domain target. Defaults to gui/<uid> or system.
10@@ -21,7 +21,7 @@ Options:
11 --help Show this help text.
12
13 Notes:
14- If no service is specified, conductor + status-api are started.
15+ If no service is specified, conductor + codexd + status-api are started.
16 EOF
17 }
18
+39,
-11
1@@ -13,17 +13,18 @@ Usage:
2 Options:
3 --scope agent|daemon launchd domain type. Defaults to agent.
4 --service NAME Add one service to the status set. Repeatable.
5- --all-services Show conductor, worker-runner, and status-api.
6+ --all-services Show conductor, codexd, worker-runner, and status-api.
7 --home-dir PATH Used only to derive the default LaunchAgents path.
8 --install-dir PATH Override launchd install directory.
9 --domain TARGET Override launchctl domain target. Defaults to gui/<uid> or system.
10 --local-api-base URL Conductor local API base. Defaults to http://100.71.210.78:4317
11+ --codexd-api-base URL codexd local API base. Defaults to http://127.0.0.1:4319
12 --status-api-base URL Status API base. Defaults to http://100.71.210.78:4318
13 --skip-http Skip HTTP probes.
14 --help Show this help text.
15
16 Notes:
17- If no service is specified, conductor + status-api are shown.
18+ If no service is specified, conductor + codexd + status-api are shown.
19 EOF
20 }
21
22@@ -36,6 +37,7 @@ home_dir="$(default_home_dir)"
23 install_dir=""
24 domain_target=""
25 local_api_base="http://100.71.210.78:4317"
26+codexd_api_base="${BAA_RUNTIME_DEFAULT_CODEXD_LOCAL_API}"
27 status_api_base="http://100.71.210.78:4318"
28 skip_http="0"
29 services=()
30@@ -77,6 +79,10 @@ while [[ $# -gt 0 ]]; do
31 local_api_base="$2"
32 shift 2
33 ;;
34+ --codexd-api-base)
35+ codexd_api_base="$2"
36+ shift 2
37+ ;;
38 --status-api-base)
39 status_api_base="$2"
40 shift 2
41@@ -119,8 +125,10 @@ for service in "${services[@]}"; do
42 printf 'label: %s\n' "$label"
43 printf 'plist: %s\n' "$plist_path"
44
45- if [[ -f "$plist_path" ]]; then
46+ if [[ -f "$plist_path" ]] && plutil -lint "$plist_path" >/dev/null 2>&1; then
47 printf 'plist_lint: ok\n'
48+ elif [[ -f "$plist_path" ]]; then
49+ printf 'plist_lint: invalid\n'
50 else
51 printf 'plist_lint: missing\n'
52 fi
53@@ -135,12 +143,32 @@ for service in "${services[@]}"; do
54 done
55
56 if [[ "$skip_http" != "1" ]]; then
57- printf '=== http ===\n'
58- printf 'conductor healthz: '
59- curl -fsS "${local_api_base%/}/healthz" || true
60- printf '\nrolez: '
61- curl -fsS "${local_api_base%/}/rolez" || true
62- printf '\nstatus-api /v1/status: '
63- curl -fsS "${status_api_base%/}/v1/status" || true
64- printf '\n'
65+ for service in "${services[@]}"; do
66+ case "$service" in
67+ conductor)
68+ printf '=== conductor http ===\n'
69+ printf 'healthz: '
70+ curl -fsS "${local_api_base%/}/healthz" || true
71+ printf '\nrolez: '
72+ curl -fsS "${local_api_base%/}/rolez" || true
73+ printf '\n\n'
74+ ;;
75+ codexd)
76+ printf '=== codexd http ===\n'
77+ printf 'healthz: '
78+ curl -fsS "${codexd_api_base%/}/healthz" || true
79+ printf '\nstatus: '
80+ curl -fsS "${codexd_api_base%/}/v1/codexd/status" || true
81+ printf '\n\n'
82+ ;;
83+ status-api)
84+ printf '=== status-api http ===\n'
85+ printf 'healthz: '
86+ curl -fsS "${status_api_base%/}/healthz" || true
87+ printf '\n/v1/status: '
88+ curl -fsS "${status_api_base%/}/v1/status" || true
89+ printf '\n\n'
90+ ;;
91+ esac
92+ done
93 fi
+2,
-2
1@@ -13,7 +13,7 @@ Usage:
2 Options:
3 --scope agent|daemon launchd domain type. Defaults to agent.
4 --service NAME Add one service to the stop set. Repeatable.
5- --all-services Stop conductor, worker-runner, and status-api.
6+ --all-services Stop conductor, codexd, worker-runner, and status-api.
7 --home-dir PATH Used only to derive the default LaunchAgents path.
8 --install-dir PATH Override launchd install directory.
9 --domain TARGET Override launchctl domain target. Defaults to gui/<uid> or system.
10@@ -21,7 +21,7 @@ Options:
11 --help Show this help text.
12
13 Notes:
14- If no service is specified, conductor + status-api are stopped.
15+ If no service is specified, conductor + codexd + status-api are stopped.
16 EOF
17 }
18