Compare commits

..

251 Commits

Author SHA1 Message Date
Rai (Michael Pokorny)
697746788d wip 2025-06-25 23:06:36 -07:00
Rai (Michael Pokorny)
f51d888c73 wip 2025-06-25 05:28:29 -07:00
Rai (Michael Pokorny)
c75de9a451 wip 2025-06-25 05:11:18 -07:00
Rai (Michael Pokorny)
c70bd0aef8 wip 2025-06-25 05:09:00 -07:00
Rai (Michael Pokorny)
3374a8bc3a wip 2025-06-25 05:02:38 -07:00
Rai (Michael Pokorny)
601ba4ed04 gitignore 2025-06-25 05:00:03 -07:00
Rai (Michael Pokorny)
9695fbb497 wip 2025-06-25 04:55:47 -07:00
Rai (Michael Pokorny)
b2fa563036 Merge branch 'agentydragon-35-tui-inspect-env-integration' into agentydragon 2025-06-25 04:54:48 -07:00
Rai (Michael Pokorny)
b0a669ebc9 wip 2025-06-25 04:53:57 -07:00
Rai (Michael Pokorny)
67c9f755fa Merge branch 'agentydragon-23-interactive-container-command-affordance' into agentydragon 2025-06-25 04:52:46 -07:00
Rai (Michael Pokorny)
8854fbdb06 wip 2025-06-25 04:45:42 -07:00
Rai (Michael Pokorny)
4e7840454b agentydragon(tasks): implement interactive shell-command hotkey + tests 2025-06-25 04:45:33 -07:00
Rai (Michael Pokorny)
2e2531e466 agentydragon(tasks): TUI integration for inspect-env slash-command
- Extend SlashCommand and AppEvent with InspectEnv variants
- Dispatch and handle AppEvent::InlineInspectEnv to invoke codex inspect-env in background
- Add InspectEnvView and wire into BottomPane and ChatWidget
- Add tests for SlashCommand, command popup filter, and InspectEnvView rendering
- Update task 35 status to Done and record implementation details
2025-06-25 04:45:27 -07:00
Rai (Michael Pokorny)
253324e5c3 wip 2025-06-25 04:37:54 -07:00
Rai (Michael Pokorny)
4179fbf51b wip 2025-06-25 04:35:32 -07:00
Rai (Michael Pokorny)
4f827c6ac2 Merge branch 'agentydragon-02-auto-approve-predicates' into agentydragon 2025-06-25 04:35:22 -07:00
Rai (Michael Pokorny)
4ed4b72e26 Merge branch 'agentydragon-38-approval-dialog-transparent-background-fix' into agentydragon 2025-06-25 04:34:45 -07:00
Rai (Michael Pokorny)
d4aaa1aad4 Merge branch 'agentydragon-36-tests-interactive-prompt-execution' into agentydragon 2025-06-25 04:34:39 -07:00
Rai (Michael Pokorny)
fa2a5d07bf Merge branch 'agentydragon-22-message-rendering-layout-options' into agentydragon 2025-06-25 04:34:15 -07:00
Rai (Michael Pokorny)
da247e932c agentydragon(tasks): add BottomPane unit tests for interactive prompting overlay during task execution 2025-06-25 04:30:49 -07:00
Rai (Michael Pokorny)
d967dbc669 agentydragon(tasks): Add message_spacing and sender_break_line flags to TUI config with default false, update renderer and tests for configurable inter-message spacing and sender-content layout 2025-06-25 04:30:14 -07:00
Rai (Michael Pokorny)
244d2389ac wip 2025-06-25 04:29:58 -07:00
Rai (Michael Pokorny)
e18744b9ec slash cmd 2025-06-25 04:29:21 -07:00
Rai (Michael Pokorny)
d6dd46c2e6 agentydragon(tasks): implement granular auto‑approval predicates: parse [[auto_allow]] config, evaluate predicate scripts with short‑circuit voting, integrate into shell flow, update docs and add tests 2025-06-25 04:29:10 -07:00
Rai (Michael Pokorny)
a30a80d22a agentydragon(tasks): add message_spacing and sender_break_line flags to TUI config; update history_cell renderer, docs, and tests 2025-06-25 04:28:33 -07:00
Rai (Michael Pokorny)
94167c15cf agentydragon(tasks): fill approval dialog region with solid DarkGray background and add test ensuring no transparent cells 2025-06-25 04:28:17 -07:00
Rai (Michael Pokorny)
3d9e034c65 Merge branch 'agentydragon-23-interactive-container-command-affordance' into agentydragon 2025-06-25 04:27:04 -07:00
Rai (Michael Pokorny)
c9a37141ba wip 2025-06-25 04:11:17 -07:00
Rai (Michael Pokorny)
9638d55e08 wip 2025-06-25 04:09:09 -07:00
Rai (Michael Pokorny)
96ac1bcbed wip 2025-06-25 04:06:29 -07:00
Rai (Michael Pokorny)
5260f2360c wip 2025-06-25 03:54:45 -07:00
Rai (Michael Pokorny)
1d86ea366d wip 2025-06-25 03:47:06 -07:00
Rai (Michael Pokorny)
98dc2c8482 wip 2025-06-25 03:22:36 -07:00
Rai (Michael Pokorny)
6521b84369 COW 2025-06-25 03:22:03 -07:00
Rai (Michael Pokorny)
f106686146 wip 2025-06-25 03:20:15 -07:00
Rai (Michael Pokorny)
079ee5f6e3 wip 2025-06-25 03:09:18 -07:00
Rai (Michael Pokorny)
3d3bc8f765 wip 2025-06-25 03:08:35 -07:00
Rai (Michael Pokorny)
6f1d48b489 wip 2025-06-25 03:06:44 -07:00
Rai (Michael Pokorny)
d2ca0b9265 wip 2025-06-25 02:48:21 -07:00
Rai (Michael Pokorny)
c4b1beea57 wip 2025-06-25 02:33:32 -07:00
Rai (Michael Pokorny)
ca4cf88334 wip 2025-06-25 02:30:51 -07:00
Rai (Michael Pokorny)
6749b3c1ea add task 2025-06-25 02:24:09 -07:00
Rai (Michael Pokorny)
b841b834fe wip 2025-06-25 02:17:48 -07:00
Rai (Michael Pokorny)
bf0e850325 wip 2025-06-25 01:49:10 -07:00
Rai (Michael Pokorny)
226871139b wip 2025-06-25 01:47:32 -07:00
Rai (Michael Pokorny)
44bf98533b wip 2025-06-25 01:43:20 -07:00
Rai (Michael Pokorny)
6a660f616e wip 2025-06-25 01:40:26 -07:00
Rai (Michael Pokorny)
9bf7f28b31 wip 2025-06-25 01:34:37 -07:00
Rai (Michael Pokorny)
86539bd522 wip 2025-06-25 01:34:30 -07:00
Rai (Michael Pokorny)
d245d74d56 fix: TASK 38/39 front-matter markers & last_updated; fix cycles import path 2025-06-25 01:22:49 -07:00
Rai (Michael Pokorny)
a349b2e0ba wip 2025-06-25 01:20:50 -07:00
Rai (Michael Pokorny)
2ab6be6cdb wip 2025-06-25 01:20:36 -07:00
Rai (Michael Pokorny)
88d46eb754 wip 2025-06-25 01:11:45 -07:00
Rai (Michael Pokorny)
a1598d645c manager_utils: fix indentation around compact diffstat and branch_info 2025-06-25 01:07:31 -07:00
Rai (Michael Pokorny)
53efec12b1 check_task_cycles: fix import path for tasklib 2025-06-25 01:05:32 -07:00
Rai (Michael Pokorny)
a4a2680b39 manager_utils: ignore merged tasks in dependency graph to avoid circular deps 2025-06-25 00:59:40 -07:00
Rai (Michael Pokorny)
3683bae697 manager_utils: rename summary to 'Ready to merge' 2025-06-25 00:56:40 -07:00
Rai (Michael Pokorny)
2d04f7332a manager_utils: status subcommand shows Done branches ready to merge 2025-06-25 00:54:22 -07:00
Rai (Michael Pokorny)
4acf48dd56 agentydragon(docs): have Commit agent only emit message, orchestrator does commit 2025-06-25 00:51:04 -07:00
Rai (Michael Pokorny)
0f6dde9621 agentydragon(docs): clarify commit agent runs outside sandbox 2025-06-25 00:49:26 -07:00
Rai (Michael Pokorny)
2c1c65a004 agentydragon(prompts): add Commit agent & update Developer agent to run pre-commit before handoff 2025-06-25 00:47:25 -07:00
Rai (Michael Pokorny)
6059f47a79 Merge remote-tracking branch 'origin/agentydragon' into agentydragon 2025-06-25 00:33:41 -07:00
Rai (Michael Pokorny)
4cc4236fe6 wip 2025-06-25 00:27:16 -07:00
Rai (Michael Pokorny)
1bff7c4db0 Merge branch 'agentydragon-35-tui-inspect-env-integration' into agentydragon 2025-06-25 00:20:50 -07:00
Rai (Michael Pokorny)
e6f8f37104 Merge branch 'agentydragon-15-agent-worktree-sandbox-configuration' into agentydragon 2025-06-25 00:19:41 -07:00
Rai (Michael Pokorny)
05a49d8036 codex-rs: remove border and unused imports/style in status indicator widget 2025-06-25 00:10:49 -07:00
Rai (Michael Pokorny)
905e85cdf0 agentydragon(tasks): clarify interactive vs exec behavior in help text 2025-06-25 00:07:24 -07:00
Rai (Michael Pokorny)
da1df276a2 agentydragon(tasks): implement interactive shell-command hotkey + tests 2025-06-24 23:50:43 -07:00
Rai (Michael Pokorny)
de380981de wip 2025-06-24 23:45:58 -07:00
Rai (Michael Pokorny)
27b3596809 wip 2025-06-24 23:39:33 -07:00
Rai (Michael Pokorny)
442bc9b9c3 tui(cli): add --debug-log flag and support file logging 2025-06-24 23:09:40 -07:00
Rai (Michael Pokorny)
6370f49f9d tasks(25): note original patch unresolved, add debugging next steps 2025-06-24 23:02:07 -07:00
Rai (Michael Pokorny)
23cb831893 tasks(25): reopen and add user thinking snippet 2025-06-24 22:59:00 -07:00
Rai (Michael Pokorny)
15b895e362 tasks: mark task 21 as Merged 2025-06-24 22:56:06 -07:00
Rai (Michael Pokorny)
8aaafa0f43 manager_utils(status): exclude bottom-summary merged tasks from dependencies 2025-06-24 22:52:23 -07:00
Rai (Michael Pokorny)
c9613d41e5 manager_utils(status): topo-sort tasks and better load error handling 2025-06-24 22:49:49 -07:00
Rai (Michael Pokorny)
9b2cb58ce5 manager_utils(dispose): also rmtree and prune stale worktrees 2025-06-24 22:44:38 -07:00
Rai (Michael Pokorny)
87e79cecdf manager_utils(dispose): use for-each-ref to list branches without decorations 2025-06-24 22:43:45 -07:00
Rai (Michael Pokorny)
478c04bf14 manager_utils(dispose): strip leading + when deleting branches 2025-06-24 22:42:57 -07:00
Rai (Michael Pokorny)
e83f5e8e6c wip 2025-06-24 22:42:12 -07:00
Rai (Michael Pokorny)
f64427aea2 manager_utils(dispose): refer worktree relative to repo root 2025-06-24 22:40:54 -07:00
Rai (Michael Pokorny)
1d35b96d86 manager_utils: fix dispose to actually remove worktrees and branches 2025-06-24 22:39:15 -07:00
Rai (Michael Pokorny)
cc65aa0882 tasks: mark tasks 03, 16, 18, 19 as Merged 2025-06-24 22:36:56 -07:00
Rai (Michael Pokorny)
ed15fc9e7d agentydragon(tasks): mark task 35 Done 2025-06-24 22:36:34 -07:00
Rai (Michael Pokorny)
0a9c0304f8 agentydragon(tasks): implement TUI integration for /inspect-env 2025-06-24 22:35:20 -07:00
Rai (Michael Pokorny)
9f30c4df50 Merge branch 'agentydragon-19-bash-command-rendering-improvements' into agentydragon
# Conflicts:
#	agentydragon/tasks/19-bash-command-rendering-improvements.md
2025-06-24 22:35:11 -07:00
Rai (Michael Pokorny)
8280bd1f9d Merge branch 'agentydragon-18-chat-ui-textarea-overlay-border-fix' into agentydragon
# Conflicts:
#	agentydragon/tasks/18-chat-ui-textarea-overlay-border-fix.md
2025-06-24 22:34:25 -07:00
Rai (Michael Pokorny)
928afbb87e Merge branch 'agentydragon-16-confirm-on-ctrl-d' into agentydragon 2025-06-24 22:33:52 -07:00
Rai (Michael Pokorny)
7f7582d68d Merge branch 'agentydragon-03-live-config-reload' into agentydragon
# Conflicts:
#	agentydragon/tasks/03-live-config-reload.md
2025-06-24 22:33:39 -07:00
Rai (Michael Pokorny)
557d7e42db agentydragon(tasks): mark task 19 as done 2025-06-24 22:30:41 -07:00
Rai (Michael Pokorny)
2f254365e0 agentydragon(tasks): implement bash command rendering improvements in Rust TUI 2025-06-24 22:27:48 -07:00
Rai (Michael Pokorny)
21ee410932 docs(agentydragon): add compact Markdown rendering summary (task 21) 2025-06-24 22:27:31 -07:00
Rai (Michael Pokorny)
1f6385392d Merge branch 'agentydragon-21-compact-markdown-rendering' into agentydragon
# Conflicts:
#	agentydragon/tasks/21-compact-markdown-rendering.md
2025-06-24 22:27:00 -07:00
Rai (Michael Pokorny)
40f784e5c3 agentydragon(tasks): implement double Ctrl+D confirmation on TUI exit 2025-06-24 22:26:58 -07:00
Rai (Michael Pokorny)
b352146912 agentydragon(tasks): implement live config reload with diff prompt and apply/ignore support 2025-06-24 22:25:32 -07:00
Rai (Michael Pokorny)
211c1ae28f update deps 2025-06-24 22:23:19 -07:00
Rai (Michael Pokorny)
2ce7ba7257 agentydragon(tasks): add compact markdown rendering option to TUI 2025-06-24 22:23:08 -07:00
Rai (Michael Pokorny)
a73f150bf1 agentydragon(tasks): mark task 18 as Done 2025-06-24 22:22:13 -07:00
Rai (Michael Pokorny)
734ba5ae6a agentydragon(tasks): implement chat UI border merge & status overlay reposition 2025-06-24 22:21:41 -07:00
Rai (Michael Pokorny)
44323ac115 agentydragon(tasks): sandbox agent launch for Task 15 with write-restricted Landlock sandbox and test script 2025-06-24 22:18:11 -07:00
Rai (Michael Pokorny)
bbce68d8c3 wip 2025-06-24 22:06:49 -07:00
Rai (Michael Pokorny)
dc7e8e9c5b wip 2025-06-24 22:06:45 -07:00
Rai (Michael Pokorny)
5f70dc1d21 wip 2025-06-24 21:58:11 -07:00
Rai (Michael Pokorny)
14b1c9f909 wip 2025-06-24 21:46:01 -07:00
Rai (Michael Pokorny)
b54f7b6a6e wip 2025-06-24 21:42:42 -07:00
Rai (Michael Pokorny)
fedcedf983 agentydragon(tasks): mark task Done and note UI rendering 2025-06-24 21:26:43 -07:00
Rai (Michael Pokorny)
158d8a21f8 agentydragon(tasks): avoid private API call in tests 2025-06-24 21:26:43 -07:00
Rai (Michael Pokorny)
a07b897c34 agentydragon(tasks): expose Prompt and FunctionCallOutputPayload; fix test imports 2025-06-24 21:26:43 -07:00
Rai (Michael Pokorny)
332b6666b8 agentydragon(tasks): add cancellation logic and unit tests for message buffering 2025-06-24 21:26:43 -07:00
Rai (Michael Pokorny)
801eeb5841 agentydragon(tasks): mark in-progress and plan buffer guard in chat_completions 2025-06-24 21:26:43 -07:00
Rai (Michael Pokorny)
41b7330e86 wip 2025-06-24 21:22:12 -07:00
Rai (Michael Pokorny)
6266f49fc8 agentydragon(tasks): mark task 04 as Merged 2025-06-24 21:21:21 -07:00
Rai (Michael Pokorny)
1f1c30f87e agentydragon: skip fully merged tasks in status table, colorize dirty/Done/Merged rows, list merged summary; update manager prompt 2025-06-24 21:17:37 -07:00
Rai (Michael Pokorny)
7f7a5420fb agentydragon(tasks): mark task 10 as Merged 2025-06-24 21:08:04 -07:00
Rai (Michael Pokorny)
fd0dc1be88 agentydragon(tasks): merge task 10 inspect-container-state 2025-06-24 21:07:47 -07:00
Rai (Michael Pokorny)
172bd4f870 agentydragon(tasks): mark task 08 as Merged 2025-06-24 21:07:01 -07:00
Rai (Michael Pokorny)
e901754d14 agentydragon(tasks): merge task 08 set-shell-title 2025-06-24 21:06:45 -07:00
Rai (Michael Pokorny)
b197387b7d agentydragon(tasks): mark task 28 as Merged 2025-06-24 21:05:02 -07:00
Rai (Michael Pokorny)
e7f7847a49 agentydragon(tasks): merge task 28 include command snippet in approval label 2025-06-24 21:04:43 -07:00
Rai (Michael Pokorny)
c091d4ac64 agentydragon(tasks): add task 33 for external editor focus bug 2025-06-24 21:02:08 -07:00
Rai (Michael Pokorny)
68f11484f7 agentydragon(tasks): mark task 31 as Merged 2025-06-24 20:56:36 -07:00
Rai (Michael Pokorny)
899a958168 agentydragon(tasks): mark task 28 as done 2025-06-24 20:56:04 -07:00
Rai (Michael Pokorny)
d8c6ee32e6 agentydragon: document display context remaining percentage feature 2025-06-24 20:55:59 -07:00
Rai (Michael Pokorny)
fca6766935 agentydragon(tasks): implement command snippet truncation in session approval labels 2025-06-24 20:55:43 -07:00
Rai (Michael Pokorny)
9faf44626d agentydragon(tasks): mark task 31 as Done 2025-06-24 20:53:26 -07:00
Rai (Michael Pokorny)
624deb4158 Merge task 31-display-context-remaining-percentage into integration branch (ours) 2025-06-24 20:53:02 -07:00
Rai (Michael Pokorny)
043ca32785 wip 2025-06-24 20:47:33 -07:00
Rai (Michael Pokorny)
59bf45c30d wip 2025-06-24 20:44:49 -07:00
Rai (Michael Pokorny)
2a015a2464 wip 2025-06-24 20:40:10 -07:00
Rai (Michael Pokorny)
1fb774909d wip 2025-06-24 20:32:13 -07:00
Rai (Michael Pokorny)
d07d7a7440 wip 2025-06-24 20:27:57 -07:00
Rai (Michael Pokorny)
771294f65d wip 2025-06-24 20:15:01 -07:00
Rai (Michael Pokorny)
b1eb965839 agentydragon(tasks): implement set-shell-title feature 2025-06-24 20:08:16 -07:00
Rai (Michael Pokorny)
90fa512e8b agentydragon(tasks): mark task 31 as done 2025-06-24 20:04:02 -07:00
Rai (Michael Pokorny)
723c2452e0 agentydragon(tasks): implement context-left indicator in TUI 2025-06-24 20:03:03 -07:00
Rai (Michael Pokorny)
27ad99f4be fix(manager-utils): correct import path in agentydragon_task.py 2025-06-24 20:02:42 -07:00
Rai (Michael Pokorny)
26296b9872 Merge task 02-auto-approve-predicates into integration branch (ours) 2025-06-24 20:00:44 -07:00
Rai (Michael Pokorny)
47d967d44d wip 2025-06-24 19:35:54 -07:00
Rai (Michael Pokorny)
cba2c91fce wip 2025-06-24 19:29:59 -07:00
Rai (Michael Pokorny)
acd128f38d wip 2025-06-24 19:28:13 -07:00
Rai (Michael Pokorny)
74bc491c94 wip 2025-06-24 19:27:11 -07:00
Rai (Michael Pokorny)
3d68ba3e06 wip 2025-06-24 19:13:50 -07:00
Rai (Michael Pokorny)
9670cfdef8 wip 2025-06-24 19:10:40 -07:00
Rai (Michael Pokorny)
fc249fcf8f wip 2025-06-24 19:04:24 -07:00
Rai (Michael Pokorny)
b618d3a8ea agentydragon(tasks): fix numbering in Implementation steps 2025-06-24 19:01:54 -07:00
Rai (Michael Pokorny)
8a6a6dc3fb agentydragon(tasks): ask manual approval on script errors/unexpected outputs in auto-allow predicates 2025-06-24 19:01:28 -07:00
Rai (Michael Pokorny)
3fd329294d wip 2025-06-24 18:47:00 -07:00
Rai (Michael Pokorny)
18177f98ae wip 2025-06-24 18:43:31 -07:00
Rai (Michael Pokorny)
2e284fbf3b wip 2025-06-24 18:42:47 -07:00
Rai (Michael Pokorny)
e7f49ec30c frontmatter 2025-06-24 18:40:56 -07:00
Rai (Michael Pokorny)
3eb6efd4a4 Merge branch 'agentydragon-task/08-set-shell-title' into agentydragon 2025-06-24 18:39:17 -07:00
Rai (Michael Pokorny)
71806ef029 wip 2025-06-24 18:39:00 -07:00
Rai (Michael Pokorny)
5d709841e3 manager: remove clean task worktrees 2025-06-24 18:37:42 -07:00
Rai (Michael Pokorny)
f26875b5d8 Merge remote-tracking branch 'origin/agentydragon' into agentydragon 2025-06-24 18:30:49 -07:00
Rai (Michael Pokorny)
f2bd29127c fix 2025-06-24 18:29:17 -07:00
Rai (Michael Pokorny)
cdf3b5ac5a tasks: normalize template & statuses for tasks 06 and 18 2025-06-24 18:27:04 -07:00
Rai (Michael Pokorny)
d5668f158d codex-rs(tui): restore EditPrompt arm and clear merge conflict markers in chat_composer 2025-06-24 18:21:53 -07:00
Rai (Michael Pokorny)
30989812e6 tasks: undo merge status for task 04 2025-06-24 18:07:21 -07:00
Rai (Michael Pokorny)
2e38a2095c wip 2025-06-24 18:06:28 -07:00
Rai (Michael Pokorny)
301a3631db tasks: mark tasks 01,02,04,07,11,13 as merged 2025-06-24 18:03:01 -07:00
Rai (Michael Pokorny)
9969ab9f86 wip 2025-06-24 17:55:07 -07:00
Rai (Michael Pokorny)
444df1f1a0 tests(tasklib): fix validation exception and add test package init 2025-06-24 17:47:16 -07:00
Rai (Michael Pokorny)
7d9eef2034 gitignore 2025-06-24 17:35:51 -07:00
Rai (Michael Pokorny)
d78cfb6330 agentydragon(tasks): normalize task 18 front-matter; add manager_utils package 2025-06-24 17:32:40 -07:00
Rai (Michael Pokorny)
0e2afc0378 agentydragon(prompts): add instructions for manager-task CLI dispose vs set-status 2025-06-24 17:32:13 -07:00
Rai (Michael Pokorny)
268bf1d189 agentydragon(prompts): record cleanup of merged task worktrees and branches 2025-06-24 17:19:25 -07:00
Rai (Michael Pokorny)
23f0f65cdb agentydragon(tasks): add task 31 display remaining context percentage in TUI 2025-06-24 17:12:44 -07:00
Rai (Michael Pokorny)
96f28a5896 agentydragon(tasks): add task 30 non-fullscreen scrollback mode 2025-06-24 17:11:08 -07:00
Rai (Michael Pokorny)
2688483b9a agentydragon(tasks): add task 29 auto-approve empty-array tool invocations 2025-06-24 17:07:48 -07:00
Rai (Michael Pokorny)
fcb6828574 agentydragon(tasks): add task 28 include command snippet in session-scoped approval label 2025-06-24 17:06:22 -07:00
Rai (Michael Pokorny)
fb90672a81 agentydragon(tasks): shorten sandbox-retry prompt to 'Retry without sandbox' 2025-06-24 17:04:07 -07:00
Rai (Michael Pokorny)
ed848595de agentydragon(tasks): mark Task 10 complete 2025-06-24 16:59:03 -07:00
Rai (Michael Pokorny)
d7d15f5fd3 remove session-scoped approval option for patches; update command approve label to B variant 2025-06-24 16:57:40 -07:00
Rai (Michael Pokorny)
8ff34a0108 codex-cli: change approval command label to reference patch instead of command 2025-06-24 16:52:12 -07:00
Rai (Michael Pokorny)
4851101038 codex-rs: correct approval label to reference 'patch' instead of 'command' 2025-06-24 16:51:25 -07:00
Rai (Michael Pokorny)
81c8bbe600 agentydragon(tasks): separate approval dialog from draft window 2025-06-24 16:50:16 -07:00
Rai (Michael Pokorny)
9dc939721c codex-rs: resolve merge conflict in README.md 2025-06-24 16:49:16 -07:00
Rai (Michael Pokorny)
1284704e13 agentydragon(tasks): include verbose event collapse in task 19 acceptance criteria 2025-06-24 16:42:22 -07:00
Rai (Michael Pokorny)
50e69ec7a3 agentydragon: commit dirty state 2025-06-24 16:41:38 -07:00
Rai (Michael Pokorny)
98e963beac agentydragon(tasks): add tasks 24/25 guard missing tool output sequencing in JS and Rust 2025-06-24 16:41:01 -07:00
Rai (Michael Pokorny)
8a98ea587b agentydragon(tasks): add task 23 interactive container command affordance hotkey 2025-06-24 16:37:23 -07:00
Rai (Michael Pokorny)
62705cbe48 agentydragon(tasks): add task 22 message spacing and sender-content layout options 2025-06-24 16:35:03 -07:00
Rai (Michael Pokorny)
1d16069948 agentydragon(tasks): implement inspect-env command and update task plan for Task 10 2025-06-24 16:33:57 -07:00
Rai (Michael Pokorny)
940351d8b1 agentydragon(tasks): add task 21 compact markdown rendering option 2025-06-24 16:33:49 -07:00
Rai (Michael Pokorny)
0fb9c2440b agentydragon(tasks): add exhaustive dialog interaction tests to patch UI task 2025-06-24 16:31:54 -07:00
Rai (Michael Pokorny)
7a29431ad1 agentydragon(tasks): add integration tests requirement for patch approval UI 2025-06-24 16:31:29 -07:00
Rai (Michael Pokorny)
0456efdec7 agentydragon(tasks): gate Ctrl+D same as Ctrl+C exit confirmation 2025-06-24 16:30:35 -07:00
Rai (Michael Pokorny)
2620fff775 agentydragon(tasks): add task 20 render patch content in chat display window for approve/deny 2025-06-24 16:27:45 -07:00
Rai (Michael Pokorny)
4d8df74cec agentydragon(tasks): add task 19 bash command rendering improvements 2025-06-24 16:27:32 -07:00
Rai (Michael Pokorny)
648301613c lint fix 2025-06-24 16:11:35 -07:00
Rai (Michael Pokorny)
35f89f44d2 agentydragon(tasks): add chat UI textarea overlay and border styling fix task 2025-06-24 16:08:00 -07:00
Rai (Michael Pokorny)
056f818002 Add Rust install instructions and pre-commit cargo build hook 2025-06-24 16:07:27 -07:00
Rai (Michael Pokorny)
e73d97acf1 agentydragon(tasks): add motivating example for shell pipeline predicate 2025-06-24 16:03:51 -07:00
Rai (Michael Pokorny)
2379dcd0ed wip 2025-06-24 15:47:15 -07:00
Rai (Michael Pokorny)
11fabccebb wip 2025-06-24 15:47:10 -07:00
Rai (Michael Pokorny)
09e4a3b4b8 wip 2025-06-24 15:10:28 -07:00
Rai (Michael Pokorny)
669aa3ef39 merge fix 2025-06-24 15:06:06 -07:00
Rai (Michael Pokorny)
601c61f2cb agentydragon(prompts): clarify manager reads task status from each branch worktree 2025-06-24 15:04:42 -07:00
Rai (Michael Pokorny)
4c10e86c59 agentydragon(tasks): add 'Needs manual review' status 2025-06-24 14:57:25 -07:00
Rai (Michael Pokorny)
f76792cfc3 agentydragon: refine developer-agent scaffolding and task guidance
- create-task-worktree.sh: load developer-agent prompt from prompts/developer.md

- prompts/developer.md: require populating blank Implementation section

- tasks/task-template.md: require high-level plan in blank Implementation placeholders

- tasks/17-sandbox-precommit-permission-error.md: add task stub for sandbox pre-commit PermissionError
2025-06-24 14:56:16 -07:00
Rai (Michael Pokorny)
95dbfa37b5 ci: commit frontmatter checker and gitignore worktrees 2025-06-24 14:46:50 -07:00
Rai (Michael Pokorny)
a371729a2b agentydragon(prompts): only launch tasks with satisfied dependencies in parallel 2025-06-24 14:46:18 -07:00
Rai (Michael Pokorny)
83d4176fcf agentydragon(prompts): instruct manager to consider octopus merge conflicts when merging branches 2025-06-24 14:43:17 -07:00
Rai (Michael Pokorny)
4989965f1b agentydragon(prompts): add manager_utils folder and instruct manager to create/use documented scripts 2025-06-24 14:42:55 -07:00
Rai (Michael Pokorny)
88966daee9 create-task-worktree.sh: install pre-commit hooks in new worktrees 2025-06-24 14:42:42 -07:00
Rai (Michael Pokorny)
44ea1cf73f Merge Tasks 01, 07, 13 2025-06-24 14:39:54 -07:00
Rai (Michael Pokorny)
c5f4fafb54 agentydragon(prompts): require manager to review branches and propose merge/resolution after statuses 2025-06-24 14:34:36 -07:00
Rai (Michael Pokorny)
5f6352bbfa agentydragon(prompts): remind developers to commit changes at end 2025-06-24 14:32:23 -07:00
Rai (Michael Pokorny)
fb4857acb1 agentydragon(prompts): consult conventions defined in docs in manager prompt 2025-06-24 14:31:48 -07:00
Rai (Michael Pokorny)
22d7b327a0 Auto-commit pending changes on agentydragon-task/13-interactive-prompt-during-execution 2025-06-24 14:31:38 -07:00
Rai (Michael Pokorny)
88e53a2ae9 Auto-commit pending changes on agentydragon-task/08-set-shell-title 2025-06-24 14:31:36 -07:00
Rai (Michael Pokorny)
df9e65127f Auto-commit pending changes on agentydragon-task/07-undo-feedback-decision 2025-06-24 14:31:35 -07:00
Rai (Michael Pokorny)
b23d44cb5c 01 dynamic mount commands 2025-06-24 14:30:34 -07:00
Rai (Michael Pokorny)
09d975e93b add check_task_frontmatter.py and document frontmatter lint in task-template 2025-06-24 14:28:42 -07:00
Rai (Michael Pokorny)
b0f14f1f55 task-template: document valid status enum in frontmatter 2025-06-24 14:24:58 -07:00
Rai (Michael Pokorny)
2f0109faeb tasks: use dash-based branch names to avoid agentydragon ref conflict 2025-06-24 14:06:20 -07:00
Rai (Michael Pokorny)
a86790c553 docs: update Worktree helper usage to include --tmux and multi-ID support 2025-06-24 14:04:02 -07:00
Rai (Michael Pokorny)
cb0051f274 tasks: use tmux windows (tabs) instead of splits for multi-task mode 2025-06-24 14:00:07 -07:00
Rai (Michael Pokorny)
58975e5db1 create-task-worktree.sh: update usage to accept slug or two-digit ID for multiple tasks 2025-06-24 13:55:19 -07:00
Rai (Michael Pokorny)
ad5e28d96a create-task-worktree.sh: allow multiple task IDs when --tmux, correct usage check 2025-06-24 13:52:40 -07:00
Rai (Michael Pokorny)
83ce407baf agentydragon: add Project Manager prompt and launch-project-manager.sh 2025-06-24 13:51:45 -07:00
Rai (Michael Pokorny)
c370c0cca6 tasks: add review-unmerged-task-branches.sh wrapper to launch Codex agent 2025-06-24 13:49:10 -07:00
Rai (Michael Pokorny)
aa0a4bfba4 update create-task-worktree.sh: add --tmux mode and numeric two-digit ID support 2025-06-24 13:48:27 -07:00
Rai (Michael Pokorny)
12dced2d53 tasks: allow numeric ID only resolving to full slug 2025-06-24 13:41:53 -07:00
Rai (Michael Pokorny)
3fe8b3c5a9 agentydragon(tasks): add Task 16 to require double Ctrl+C for exit confirmation 2025-06-24 13:40:08 -07:00
Rai (Michael Pokorny)
84a4e64bf1 tasks: branch new worktrees off agentydragon instead of master 2025-06-24 13:39:21 -07:00
Rai (Michael Pokorny)
5c62d7d42c agentydragon(tasks): add Task 15 for embedded Neovim prompt editor 2025-06-24 13:38:32 -07:00
Rai (Michael Pokorny)
1df8e3c308 tasks(15): adjust scope to script scaffolding and whitelist tmpdir 2025-06-24 13:36:34 -07:00
Rai (Michael Pokorny)
2e3e115128 agentydragon(tasks): consolidate external script predicates as Python predicates (never expire) 2025-06-24 13:28:28 -07:00
Rai (Michael Pokorny)
3762c71818 agentydragon(tasks): update auto-approve predicates to 3-state vote semantics (deny/allow/no-opinion) 2025-06-24 13:27:03 -07:00
Rai (Michael Pokorny)
4eedb33a1f tasks: add --agent mode to create-task-worktree.sh to spawn codex agent in task workspace 2025-06-24 13:24:10 -07:00
Rai (Michael Pokorny)
a527582e32 codex-rs: enable config tests, expose find_codex_home, add auto‑mount repo sandbox logic 2025-06-24 13:21:01 -07:00
Rai (Michael Pokorny)
1c2722335d agentydragon(tasks): add TUI status‑bar indicators to Task 10 for network/mounts/permissions 2025-06-24 13:17:51 -07:00
Rai (Michael Pokorny)
62dda0b041 cli(config): implement 'codex config' subcommand and document it 2025-06-24 13:14:51 -07:00
Rai (Michael Pokorny)
771017c5be agents: point to agentydragon/README.md and define add-task conventions 2025-06-24 13:06:26 -07:00
Rai (Michael Pokorny)
872e89f8e8 gitignore: ignore oaipkg/ artifacts 2025-06-24 13:04:06 -07:00
Rai (Michael Pokorny)
a36ab4797c docs(agentydragon): add worktree helper script and branch convention to README 2025-06-24 12:58:46 -07:00
Rai (Michael Pokorny)
481a76458f docs: centralize task list removal and update AGENTS.md guidance 2025-06-24 12:56:42 -07:00
Rai (Michael Pokorny)
db8e6f3255 docs(agentydragon): add Task 13 for interactive input during execution 2025-06-24 12:55:29 -07:00
Rai (Michael Pokorny)
489d2db021 docs(agentydragon): add Task 12 for runtime internet toggle 2025-06-24 12:52:57 -07:00
Rai (Michael Pokorny)
52c7328242 docs(agentydragon): add Task 11 for custom approval predicates 2025-06-24 12:50:43 -07:00
Rai (Michael Pokorny)
a6050c920b docs: add Cargo installation instructions for codex-rs 2025-06-24 12:35:44 -07:00
Rai (Michael Pokorny)
8d13b6a652 docs: instruct agents to update agentydragon/README.md instead of AGENTS.md 2025-06-24 12:32:35 -07:00
Rai (Michael Pokorny)
f84ed7d584 docs: instruct agents to update AGENTS.md with their work summaries 2025-06-24 12:31:38 -07:00
Rai (Michael Pokorny)
32c9c0d23b docs(agentydragon): add README summarizing changes on agentydragon branch 2025-06-24 12:29:42 -07:00
Rai (Michael Pokorny)
f2e27c46d6 wip 2025-06-21 06:40:33 -07:00
Rai (Michael Pokorny)
719eb2a0f0 wip 2025-06-21 06:39:27 -07:00
Rai (Michael Pokorny)
65e5d926ab tui session load 2025-06-21 06:38:29 -07:00
Rai (Michael Pokorny)
bb19ef1434 botom pane height, restore cmd 2025-06-21 03:49:22 -07:00
Rai (Michael Pokorny)
c37d82e3d5 tasks 2025-06-21 03:47:32 -07:00
Rai (Michael Pokorny)
e585e6c8cd docs: clarify 'exact command' wording in approval prompts and config 2025-06-21 03:01:47 -07:00
792 changed files with 57635 additions and 100919 deletions

View File

@@ -1,6 +1,6 @@
[codespell]
# Ref: https://github.com/codespell-project/codespell#using-a-config-file
skip = .git*,vendor,*-lock.yaml,*.lock,.codespellrc,*test.ts,*.jsonl
skip = .git*,vendor,*-lock.yaml,*.lock,.codespellrc,*test.ts
check-hidden = true
ignore-regex = ^\s*"image/\S+": ".*|\b(afterAll)\b
ignore-words-list = ratatui,ser

View File

@@ -1,4 +1,4 @@
FROM ubuntu:24.04
FROM ubuntu:22.04
ARG DEBIAN_FRONTEND=noninteractive
# enable 'universe' because musl-tools & clang live there
@@ -11,17 +11,19 @@ RUN apt-get update && \
RUN apt-get update && \
apt-get install -y --no-install-recommends \
build-essential curl git ca-certificates \
pkg-config clang musl-tools libssl-dev just && \
pkg-config clang musl-tools libssl-dev && \
rm -rf /var/lib/apt/lists/*
# Ubuntu 24.04 ships with user 'ubuntu' already created with UID 1000.
USER ubuntu
# non-root dev user
ARG USER=dev
ARG UID=1000
RUN useradd -m -u $UID $USER
USER $USER
# install Rust + musl target as dev user
RUN curl -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal && \
~/.cargo/bin/rustup target add aarch64-unknown-linux-musl && \
~/.cargo/bin/rustup component add clippy rustfmt
~/.cargo/bin/rustup target add aarch64-unknown-linux-musl
ENV PATH="/home/ubuntu/.cargo/bin:${PATH}"
ENV PATH="/home/${USER}/.cargo/bin:${PATH}"
WORKDIR /workspace

View File

@@ -15,13 +15,15 @@
"CARGO_TARGET_DIR": "${containerWorkspaceFolder}/codex-rs/target-arm64"
},
"remoteUser": "ubuntu",
"remoteUser": "dev",
"customizations": {
"vscode": {
"settings": {
"terminal.integrated.defaultProfile.linux": "bash"
"terminal.integrated.defaultProfile.linux": "bash"
},
"extensions": ["rust-lang.rust-analyzer", "tamasfe.even-better-toml"]
"extensions": [
"rust-lang.rust-analyzer"
],
}
}
}

View File

@@ -1,31 +0,0 @@
name: 🎁 Feature Request
description: Propose a new feature for Codex
labels:
- enhancement
- needs triage
body:
- type: markdown
attributes:
value: |
Is Codex missing a feature that you'd like to see? Feel free to propose it here.
Before you submit a feature:
1. Search existing issues for similar features. If you find one, 👍 it rather than opening a new one.
2. The Codex team will try to balance the varying needs of the community when prioritizing or rejecting new features. Not all features will be accepted. See [Contributing](https://github.com/openai/codex#contributing) for more details.
- type: textarea
id: feature
attributes:
label: What feature would you like to see?
validations:
required: true
- type: textarea
id: author
attributes:
label: Are you interested in implementing this feature?
description: Please wait for acknowledgement before implementing or opening a PR.
- type: textarea
id: notes
attributes:
label: Additional information
description: Is there anything else you think we should know?

View File

@@ -1,50 +0,0 @@
name: 🧑‍💻 VS Code Extension
description: Report an issue with the VS Code extension
labels:
- extension
- needs triage
body:
- type: markdown
attributes:
value: |
Before submitting a new issue, please search for existing issues to see if your issue has already been reported.
If it has, please add a 👍 reaction (no need to leave a comment) to the existing issue instead of creating a new one.
- type: input
id: version
attributes:
label: What version of the VS Code extension are you using?
- type: input
id: ide
attributes:
label: Which IDE are you using?
description: Like `VS Code`, `Cursor`, `Windsurf`, etc.
- type: input
id: platform
attributes:
label: What platform is your computer?
description: |
For MacOS and Linux: copy the output of `uname -mprs`
For Windows: copy the output of `"$([Environment]::OSVersion | ForEach-Object VersionString) $(if ([Environment]::Is64BitOperatingSystem) { "x64" } else { "x86" })"` in the PowerShell console
- type: textarea
id: steps
attributes:
label: What steps can reproduce the bug?
description: Explain the bug and provide a code snippet that can reproduce it.
validations:
required: true
- type: textarea
id: expected
attributes:
label: What is the expected behavior?
description: If possible, please provide text instead of a screenshot.
- type: textarea
id: actual
attributes:
label: What do you see instead?
description: If possible, please provide text instead of a screenshot.
- type: textarea
id: notes
attributes:
label: Additional information
description: Is there anything else you think we should know?

1
.github/actions/codex/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/node_modules/

View File

@@ -0,0 +1,8 @@
printWidth = 80
quoteProps = "consistent"
semi = true
tabWidth = 2
trailingComma = "all"
# Preserve existing behavior for markdown/text wrapping.
proseWrap = "preserve"

140
.github/actions/codex/README.md vendored Normal file
View File

@@ -0,0 +1,140 @@
# openai/codex-action
`openai/codex-action` is a GitHub Action that facilitates the use of [Codex](https://github.com/openai/codex) on GitHub issues and pull requests. Using the action, associate **labels** to run Codex with the appropriate prompt for the given context. Codex will respond by posting comments or creating PRs, whichever you specify!
Here is a sample workflow that uses `openai/codex-action`:
```yaml
name: Codex
on:
issues:
types: [opened, labeled]
pull_request:
branches: [main]
types: [labeled]
jobs:
codex:
if: ... # optional, but can be effective in conserving CI resources
runs-on: ubuntu-latest
# TODO(mbolin): Need to verify if/when `write` is necessary.
permissions:
contents: write
issues: write
pull-requests: write
steps:
# By default, Codex runs network disabled using --full-auto, so perform
# any setup that requires network (such as installing dependencies)
# before openai/codex-action.
- name: Checkout repository
uses: actions/checkout@v4
- name: Run Codex
uses: openai/codex-action@latest
with:
openai_api_key: ${{ secrets.CODEX_OPENAI_API_KEY }}
github_token: ${{ secrets.GITHUB_TOKEN }}
```
See sample usage in [`codex.yml`](../../workflows/codex.yml).
## Triggering the Action
Using the sample workflow above, we have:
```yaml
on:
issues:
types: [opened, labeled]
pull_request:
branches: [main]
types: [labeled]
```
which means our workflow will be triggered when any of the following events occur:
- a label is added to an issue
- a label is added to a pull request against the `main` branch
### Label-Based Triggers
To define a GitHub label that should trigger Codex, create a file named `.github/codex/labels/LABEL-NAME.md` in your repository where `LABEL-NAME` is the name of the label. The content of the file is the prompt template to use when the label is added (see more on [Prompt Template Variables](#prompt-template-variables) below).
For example, if the file `.github/codex/labels/codex-review.md` exists, then:
- Adding the `codex-review` label will trigger the workflow containing the `openai/codex-action` GitHub Action.
- When `openai/codex-action` starts, it will replace the `codex-review` label with `codex-review-in-progress`.
- When `openai/codex-action` is finished, it will replace the `codex-review-in-progress` label with `codex-review-completed`.
If Codex sees that either `codex-review-in-progress` or `codex-review-completed` is already present, it will not perform the action.
As determined by the [default config](./src/default-label-config.ts), Codex will act on the following labels by default:
- Adding the `codex-review` label to a pull request will have Codex review the PR and add it to the PR as a comment.
- Adding the `codex-triage` label to an issue will have Codex investigate the issue and report its findings as a comment.
- Adding the `codex-issue-fix` label to an issue will have Codex attempt to fix the issue and create a PR wit the fix, if any.
## Action Inputs
The `openai/codex-action` GitHub Action takes the following inputs
### `openai_api_key` (required)
Set your `OPENAI_API_KEY` as a [repository secret](https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions). See **Secrets and varaibles** then **Actions** in the settings for your GitHub repo.
Note that the secret name does not have to be `OPENAI_API_KEY`. For example, you might want to name it `CODEX_OPENAI_API_KEY` and then configure it on `openai/codex-action` as follows:
```yaml
openai_api_key: ${{ secrets.CODEX_OPENAI_API_KEY }}
```
### `github_token` (required)
This is required so that Codex can post a comment or create a PR. Set this value on the action as follows:
```yaml
github_token: ${{ secrets.GITHUB_TOKEN }}
```
### `codex_args`
A whitespace-delimited list of arguments to pass to Codex. Defaults to `--full-auto`, but if you want to override the default model to use `o3`:
```yaml
codex_args: "--full-auto --model o3"
```
For more complex configurations, use the `codex_home` input.
### `codex_home`
If set, the value to use for the `$CODEX_HOME` environment variable when running Codex. As explained [in the docs](https://github.com/openai/codex/tree/main/codex-rs#readme), this folder can contain the `config.toml` to configure Codex, custom instructions, and log files.
This should be a relative path within your repo.
## Prompt Template Variables
As shown above, `"prompt"` and `"promptPath"` are used to define prompt templates that will be populated and passed to Codex in response to certain events. All template variables are of the form `{CODEX_ACTION_...}` and the supported values are defined below.
### `CODEX_ACTION_ISSUE_TITLE`
If the action was triggered on a GitHub issue, this is the issue title.
Specifically it is read as the `.issue.title` from the `$GITHUB_EVENT_PATH`.
### `CODEX_ACTION_ISSUE_BODY`
If the action was triggered on a GitHub issue, this is the issue body.
Specifically it is read as the `.issue.body` from the `$GITHUB_EVENT_PATH`.
### `CODEX_ACTION_GITHUB_EVENT_PATH`
The value of the `$GITHUB_EVENT_PATH` environment variable, which is the path to the file that contains the JSON payload for the event that triggered the workflow. Codex can use `jq` to read only the fields of interest from this file.
### `CODEX_ACTION_PR_DIFF`
If the action was triggered on a pull request, this is the diff between the base and head commits of the PR. It is the output from `git diff`.
Note that the content of the diff could be quite large, so is generally safer to point Codex at `CODEX_ACTION_GITHUB_EVENT_PATH` and let it decide how it wants to explore the change.

124
.github/actions/codex/action.yml vendored Normal file
View File

@@ -0,0 +1,124 @@
name: "Codex [reusable action]"
description: "A reusable action that runs a Codex model."
inputs:
openai_api_key:
description: "The value to use as the OPENAI_API_KEY environment variable when running Codex."
required: true
trigger_phrase:
description: "Text to trigger Codex from a PR/issue body or comment."
required: false
default: ""
github_token:
description: "Token so Codex can comment on the PR or issue."
required: true
codex_args:
description: "A whitespace-delimited list of arguments to pass to Codex. Due to limitations in YAML, arguments with spaces are not supported. For more complex configurations, use the `codex_home` input."
required: false
default: "--config hide_agent_reasoning=true --full-auto"
codex_home:
description: "Value to use as the CODEX_HOME environment variable when running Codex."
required: false
codex_release_tag:
description: "The release tag of the Codex model to run."
required: false
default: "codex-rs-ca8e97fcbcb991e542b8689f2d4eab9d30c399d6-1-rust-v0.0.2505302325"
runs:
using: "composite"
steps:
# Do this in Bash so we do not even bother to install Bun if the sender does
# not have write access to the repo.
- name: Verify user has write access to the repo.
env:
GH_TOKEN: ${{ github.token }}
shell: bash
run: |
set -euo pipefail
PERMISSION=$(gh api \
"/repos/${GITHUB_REPOSITORY}/collaborators/${{ github.event.sender.login }}/permission" \
| jq -r '.permission')
if [[ "$PERMISSION" != "admin" && "$PERMISSION" != "write" ]]; then
exit 1
fi
- name: Download Codex
env:
GH_TOKEN: ${{ github.token }}
shell: bash
run: |
set -euo pipefail
# Determine OS/arch and corresponding Codex artifact name.
uname_s=$(uname -s)
uname_m=$(uname -m)
case "$uname_s" in
Linux*) os="linux" ;;
Darwin*) os="apple-darwin" ;;
*) echo "Unsupported operating system: $uname_s"; exit 1 ;;
esac
case "$uname_m" in
x86_64*) arch="x86_64" ;;
arm64*|aarch64*) arch="aarch64" ;;
*) echo "Unsupported architecture: $uname_m"; exit 1 ;;
esac
# linux builds differentiate between musl and gnu.
if [[ "$os" == "linux" ]]; then
if [[ "$arch" == "x86_64" ]]; then
triple="${arch}-unknown-linux-musl"
else
# Only other supported linux build is aarch64 gnu.
triple="${arch}-unknown-linux-gnu"
fi
else
# macOS
triple="${arch}-apple-darwin"
fi
# Note that if we start baking version numbers into the artifact name,
# we will need to update this action.yml file to match.
artifact="codex-exec-${triple}.tar.gz"
gh release download ${{ inputs.codex_release_tag }} --repo openai/codex \
--pattern "$artifact" --output - \
| tar xzO > /usr/local/bin/codex-exec
chmod +x /usr/local/bin/codex-exec
# Display Codex version to confirm binary integrity; ensure we point it
# at the checked-out repository via --cd so that any subsequent commands
# use the correct working directory.
codex-exec --cd "$GITHUB_WORKSPACE" --version
- name: Install Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: 1.2.11
- name: Install dependencies
shell: bash
run: |
cd ${{ github.action_path }}
bun install --production
- name: Run Codex
shell: bash
run: bun run ${{ github.action_path }}/src/main.ts
# Process args plus environment variables often have a max of 128 KiB,
# so we should fit within that limit?
env:
INPUT_CODEX_ARGS: ${{ inputs.codex_args || '' }}
INPUT_CODEX_HOME: ${{ inputs.codex_home || ''}}
INPUT_TRIGGER_PHRASE: ${{ inputs.trigger_phrase || '' }}
OPENAI_API_KEY: ${{ inputs.openai_api_key }}
GITHUB_TOKEN: ${{ inputs.github_token }}
GITHUB_EVENT_ACTION: ${{ github.event.action || '' }}
GITHUB_EVENT_LABEL_NAME: ${{ github.event.label.name || '' }}
GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number || '' }}
GITHUB_EVENT_ISSUE_BODY: ${{ github.event.issue.body || '' }}
GITHUB_EVENT_REVIEW_BODY: ${{ github.event.review.body || '' }}
GITHUB_EVENT_COMMENT_BODY: ${{ github.event.comment.body || '' }}

85
.github/actions/codex/bun.lock vendored Normal file
View File

@@ -0,0 +1,85 @@
{
"lockfileVersion": 1,
"workspaces": {
"": {
"name": "codex-action",
"dependencies": {
"@actions/core": "^1.11.1",
"@actions/github": "^6.0.1",
},
"devDependencies": {
"@types/bun": "^1.2.11",
"@types/node": "^22.15.21",
"prettier": "^3.5.3",
"typescript": "^5.8.3",
},
},
},
"packages": {
"@actions/core": ["@actions/core@1.11.1", "", { "dependencies": { "@actions/exec": "^1.1.1", "@actions/http-client": "^2.0.1" } }, "sha512-hXJCSrkwfA46Vd9Z3q4cpEpHB1rL5NG04+/rbqW9d3+CSvtB1tYe8UTpAlixa1vj0m/ULglfEK2UKxMGxCxv5A=="],
"@actions/exec": ["@actions/exec@1.1.1", "", { "dependencies": { "@actions/io": "^1.0.1" } }, "sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w=="],
"@actions/github": ["@actions/github@6.0.1", "", { "dependencies": { "@actions/http-client": "^2.2.0", "@octokit/core": "^5.0.1", "@octokit/plugin-paginate-rest": "^9.2.2", "@octokit/plugin-rest-endpoint-methods": "^10.4.0", "@octokit/request": "^8.4.1", "@octokit/request-error": "^5.1.1", "undici": "^5.28.5" } }, "sha512-xbZVcaqD4XnQAe35qSQqskb3SqIAfRyLBrHMd/8TuL7hJSz2QtbDwnNM8zWx4zO5l2fnGtseNE3MbEvD7BxVMw=="],
"@actions/http-client": ["@actions/http-client@2.2.3", "", { "dependencies": { "tunnel": "^0.0.6", "undici": "^5.25.4" } }, "sha512-mx8hyJi/hjFvbPokCg4uRd4ZX78t+YyRPtnKWwIl+RzNaVuFpQHfmlGVfsKEJN8LwTCvL+DfVgAM04XaHkm6bA=="],
"@actions/io": ["@actions/io@1.1.3", "", {}, "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q=="],
"@fastify/busboy": ["@fastify/busboy@2.1.1", "", {}, "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA=="],
"@octokit/auth-token": ["@octokit/auth-token@4.0.0", "", {}, "sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA=="],
"@octokit/core": ["@octokit/core@5.2.1", "", { "dependencies": { "@octokit/auth-token": "^4.0.0", "@octokit/graphql": "^7.1.0", "@octokit/request": "^8.4.1", "@octokit/request-error": "^5.1.1", "@octokit/types": "^13.0.0", "before-after-hook": "^2.2.0", "universal-user-agent": "^6.0.0" } }, "sha512-dKYCMuPO1bmrpuogcjQ8z7ICCH3FP6WmxpwC03yjzGfZhj9fTJg6+bS1+UAplekbN2C+M61UNllGOOoAfGCrdQ=="],
"@octokit/endpoint": ["@octokit/endpoint@9.0.6", "", { "dependencies": { "@octokit/types": "^13.1.0", "universal-user-agent": "^6.0.0" } }, "sha512-H1fNTMA57HbkFESSt3Y9+FBICv+0jFceJFPWDePYlR/iMGrwM5ph+Dd4XRQs+8X+PUFURLQgX9ChPfhJ/1uNQw=="],
"@octokit/graphql": ["@octokit/graphql@7.1.1", "", { "dependencies": { "@octokit/request": "^8.4.1", "@octokit/types": "^13.0.0", "universal-user-agent": "^6.0.0" } }, "sha512-3mkDltSfcDUoa176nlGoA32RGjeWjl3K7F/BwHwRMJUW/IteSa4bnSV8p2ThNkcIcZU2umkZWxwETSSCJf2Q7g=="],
"@octokit/openapi-types": ["@octokit/openapi-types@24.2.0", "", {}, "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg=="],
"@octokit/plugin-paginate-rest": ["@octokit/plugin-paginate-rest@9.2.2", "", { "dependencies": { "@octokit/types": "^12.6.0" }, "peerDependencies": { "@octokit/core": "5" } }, "sha512-u3KYkGF7GcZnSD/3UP0S7K5XUFT2FkOQdcfXZGZQPGv3lm4F2Xbf71lvjldr8c1H3nNbF+33cLEkWYbokGWqiQ=="],
"@octokit/plugin-rest-endpoint-methods": ["@octokit/plugin-rest-endpoint-methods@10.4.1", "", { "dependencies": { "@octokit/types": "^12.6.0" }, "peerDependencies": { "@octokit/core": "5" } }, "sha512-xV1b+ceKV9KytQe3zCVqjg+8GTGfDYwaT1ATU5isiUyVtlVAO3HNdzpS4sr4GBx4hxQ46s7ITtZrAsxG22+rVg=="],
"@octokit/request": ["@octokit/request@8.4.1", "", { "dependencies": { "@octokit/endpoint": "^9.0.6", "@octokit/request-error": "^5.1.1", "@octokit/types": "^13.1.0", "universal-user-agent": "^6.0.0" } }, "sha512-qnB2+SY3hkCmBxZsR/MPCybNmbJe4KAlfWErXq+rBKkQJlbjdJeS85VI9r8UqeLYLvnAenU8Q1okM/0MBsAGXw=="],
"@octokit/request-error": ["@octokit/request-error@5.1.1", "", { "dependencies": { "@octokit/types": "^13.1.0", "deprecation": "^2.0.0", "once": "^1.4.0" } }, "sha512-v9iyEQJH6ZntoENr9/yXxjuezh4My67CBSu9r6Ve/05Iu5gNgnisNWOsoJHTP6k0Rr0+HQIpnH+kyammu90q/g=="],
"@octokit/types": ["@octokit/types@13.10.0", "", { "dependencies": { "@octokit/openapi-types": "^24.2.0" } }, "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA=="],
"@types/bun": ["@types/bun@1.2.13", "", { "dependencies": { "bun-types": "1.2.13" } }, "sha512-u6vXep/i9VBxoJl3GjZsl/BFIsvML8DfVDO0RYLEwtSZSp981kEO1V5NwRcO1CPJ7AmvpbnDCiMKo3JvbDEjAg=="],
"@types/node": ["@types/node@22.15.21", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-EV/37Td6c+MgKAbkcLG6vqZ2zEYHD7bvSrzqqs2RIhbA6w3x+Dqz8MZM3sP6kGTeLrdoOgKZe+Xja7tUB2DNkQ=="],
"before-after-hook": ["before-after-hook@2.2.3", "", {}, "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ=="],
"bun-types": ["bun-types@1.2.13", "", { "dependencies": { "@types/node": "*" } }, "sha512-rRjA1T6n7wto4gxhAO/ErZEtOXyEZEmnIHQfl0Dt1QQSB4QV0iP6BZ9/YB5fZaHFQ2dwHFrmPaRQ9GGMX01k9Q=="],
"deprecation": ["deprecation@2.3.1", "", {}, "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ=="],
"once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
"prettier": ["prettier@3.5.3", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw=="],
"tunnel": ["tunnel@0.0.6", "", {}, "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg=="],
"typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
"undici": ["undici@5.29.0", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg=="],
"undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
"universal-user-agent": ["universal-user-agent@6.0.1", "", {}, "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ=="],
"wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
"@octokit/plugin-paginate-rest/@octokit/types": ["@octokit/types@12.6.0", "", { "dependencies": { "@octokit/openapi-types": "^20.0.0" } }, "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw=="],
"@octokit/plugin-rest-endpoint-methods/@octokit/types": ["@octokit/types@12.6.0", "", { "dependencies": { "@octokit/openapi-types": "^20.0.0" } }, "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw=="],
"@octokit/plugin-paginate-rest/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@20.0.0", "", {}, "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA=="],
"@octokit/plugin-rest-endpoint-methods/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@20.0.0", "", {}, "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA=="],
}
}

21
.github/actions/codex/package.json vendored Normal file
View File

@@ -0,0 +1,21 @@
{
"name": "codex-action",
"version": "0.0.0",
"private": true,
"scripts": {
"format": "prettier --check src",
"format:fix": "prettier --write src",
"test": "bun test",
"typecheck": "tsc"
},
"dependencies": {
"@actions/core": "^1.11.1",
"@actions/github": "^6.0.1"
},
"devDependencies": {
"@types/bun": "^1.2.11",
"@types/node": "^22.15.21",
"prettier": "^3.5.3",
"typescript": "^5.8.3"
}
}

View File

@@ -0,0 +1,85 @@
import * as github from "@actions/github";
import type { EnvContext } from "./env-context";
/**
* Add an "eyes" reaction to the entity (issue, issue comment, or pull request
* review comment) that triggered the current Codex invocation.
*
* The purpose is to provide immediate feedback to the user similar to the
* *-in-progress label flow indicating that the bot has acknowledged the
* request and is working on it.
*
* We attempt to add the reaction best suited for the current GitHub event:
*
* • issues → POST /repos/{owner}/{repo}/issues/{issue_number}/reactions
* • issue_comment → POST /repos/{owner}/{repo}/issues/comments/{comment_id}/reactions
* • pull_request_review_comment → POST /repos/{owner}/{repo}/pulls/comments/{comment_id}/reactions
*
* If the specific target is unavailable (e.g. unexpected payload shape) we
* silently skip instead of failing the whole action because the reaction is
* merely cosmetic.
*/
export async function addEyesReaction(ctx: EnvContext): Promise<void> {
const octokit = ctx.getOctokit();
const { owner, repo } = github.context.repo;
const eventName = github.context.eventName;
try {
switch (eventName) {
case "issue_comment": {
const commentId = (github.context.payload as any)?.comment?.id;
if (commentId) {
await octokit.rest.reactions.createForIssueComment({
owner,
repo,
comment_id: commentId,
content: "eyes",
});
return;
}
break;
}
case "pull_request_review_comment": {
const commentId = (github.context.payload as any)?.comment?.id;
if (commentId) {
await octokit.rest.reactions.createForPullRequestReviewComment({
owner,
repo,
comment_id: commentId,
content: "eyes",
});
return;
}
break;
}
case "issues": {
const issueNumber = github.context.issue.number;
if (issueNumber) {
await octokit.rest.reactions.createForIssue({
owner,
repo,
issue_number: issueNumber,
content: "eyes",
});
return;
}
break;
}
default: {
// Fallback: try to react to the issue/PR if we have a number.
const issueNumber = github.context.issue.number;
if (issueNumber) {
await octokit.rest.reactions.createForIssue({
owner,
repo,
issue_number: issueNumber,
content: "eyes",
});
}
}
}
} catch (error) {
// Do not fail the action if reaction creation fails log and continue.
console.warn(`Failed to add \"eyes\" reaction: ${error}`);
}
}

53
.github/actions/codex/src/comment.ts vendored Normal file
View File

@@ -0,0 +1,53 @@
import type { EnvContext } from "./env-context";
import { runCodex } from "./run-codex";
import { postComment } from "./post-comment";
import { addEyesReaction } from "./add-reaction";
/**
* Handle `issue_comment` and `pull_request_review_comment` events once we know
* the action is supported.
*/
export async function onComment(ctx: EnvContext): Promise<void> {
const triggerPhrase = ctx.tryGet("INPUT_TRIGGER_PHRASE");
if (!triggerPhrase) {
console.warn("Empty trigger phrase: skipping.");
return;
}
// Attempt to get the body of the comment from the environment. Depending on
// the event type either `GITHUB_EVENT_COMMENT_BODY` (issue & PR comments) or
// `GITHUB_EVENT_REVIEW_BODY` (PR reviews) is set.
const commentBody =
ctx.tryGetNonEmpty("GITHUB_EVENT_COMMENT_BODY") ??
ctx.tryGetNonEmpty("GITHUB_EVENT_REVIEW_BODY") ??
ctx.tryGetNonEmpty("GITHUB_EVENT_ISSUE_BODY");
if (!commentBody) {
console.warn("Comment body not found in environment: skipping.");
return;
}
// Check if the trigger phrase is present.
if (!commentBody.includes(triggerPhrase)) {
console.log(
`Trigger phrase '${triggerPhrase}' not found: nothing to do for this comment.`,
);
return;
}
// Derive the prompt by removing the trigger phrase. Remove only the first
// occurrence to keep any additional occurrences that might be meaningful.
const prompt = commentBody.replace(triggerPhrase, "").trim();
if (prompt.length === 0) {
console.warn("Prompt is empty after removing trigger phrase: skipping");
return;
}
// Provide immediate feedback that we are working on the request.
await addEyesReaction(ctx);
// Run Codex and post the response as a new comment.
const lastMessage = await runCodex(prompt, ctx);
await postComment(lastMessage, ctx);
}

11
.github/actions/codex/src/config.ts vendored Normal file
View File

@@ -0,0 +1,11 @@
import { readdirSync, statSync } from "fs";
import * as path from "path";
export interface Config {
labels: Record<string, LabelConfig>;
}
export interface LabelConfig {
/** Returns the prompt template. */
getPromptTemplate(): string;
}

View File

@@ -0,0 +1,44 @@
import type { Config } from "./config";
export function getDefaultConfig(): Config {
return {
labels: {
"codex-investigate-issue": {
getPromptTemplate: () =>
`
Troubleshoot whether the reported issue is valid.
Provide a concise and respectful comment summarizing the findings.
### {CODEX_ACTION_ISSUE_TITLE}
{CODEX_ACTION_ISSUE_BODY}
`.trim(),
},
"codex-code-review": {
getPromptTemplate: () =>
`
Review this PR and respond with a very concise final message, formatted in Markdown.
There should be a summary of the changes (1-2 sentences) and a few bullet points if necessary.
Then provide the **review** (1-2 sentences plus bullet points, friendly tone).
{CODEX_ACTION_GITHUB_EVENT_PATH} contains the JSON that triggered this GitHub workflow. It contains the \`base\` and \`head\` refs that define this PR. Both refs are available locally.
`.trim(),
},
"codex-attempt-fix": {
getPromptTemplate: () =>
`
Attempt to solve the reported issue.
If a code change is required, create a new branch, commit the fix, and open a pull-request that resolves the problem.
### {CODEX_ACTION_ISSUE_TITLE}
{CODEX_ACTION_ISSUE_BODY}
`.trim(),
},
},
};
}

116
.github/actions/codex/src/env-context.ts vendored Normal file
View File

@@ -0,0 +1,116 @@
/*
* Centralised access to environment variables used by the Codex GitHub
* Action.
*
* To enable proper unit-testing we avoid reading from `process.env` at module
* initialisation time. Instead a `EnvContext` object is created (usually from
* the real `process.env`) and passed around explicitly or where that is not
* yet practical imported as the shared `defaultContext` singleton. Tests can
* create their own context backed by a stubbed map of variables without having
* to mutate global state.
*/
import { fail } from "./fail";
import * as github from "@actions/github";
export interface EnvContext {
/**
* Return the value for a given environment variable or terminate the action
* via `fail` if it is missing / empty.
*/
get(name: string): string;
/**
* Attempt to read an environment variable. Returns the value when present;
* otherwise returns undefined (does not call `fail`).
*/
tryGet(name: string): string | undefined;
/**
* Attempt to read an environment variable. Returns non-empty string value or
* null if unset or empty string.
*/
tryGetNonEmpty(name: string): string | null;
/**
* Return a memoised Octokit instance authenticated via the token resolved
* from the provided argument (when defined) or the environment variables
* `GITHUB_TOKEN`/`GH_TOKEN`.
*
* Subsequent calls return the same cached instance to avoid spawning
* multiple REST clients within a single action run.
*/
getOctokit(token?: string): ReturnType<typeof github.getOctokit>;
}
/** Internal helper *not* exported. */
function _getRequiredEnv(
name: string,
env: Record<string, string | undefined>,
): string | undefined {
const value = env[name];
// Avoid leaking secrets into logs while still logging non-secret variables.
if (name.endsWith("KEY") || name.endsWith("TOKEN")) {
if (value) {
console.log(`value for ${name} was found`);
}
} else {
console.log(`${name}=${value}`);
}
return value;
}
/** Create a context backed by the supplied environment map (defaults to `process.env`). */
export function createEnvContext(
env: Record<string, string | undefined> = process.env,
): EnvContext {
// Lazily instantiated Octokit client shared across this context.
let cachedOctokit: ReturnType<typeof github.getOctokit> | null = null;
return {
get(name: string): string {
const value = _getRequiredEnv(name, env);
if (value == null) {
fail(`Missing required environment variable: ${name}`);
}
return value;
},
tryGet(name: string): string | undefined {
return _getRequiredEnv(name, env);
},
tryGetNonEmpty(name: string): string | null {
const value = _getRequiredEnv(name, env);
return value == null || value === "" ? null : value;
},
getOctokit(token?: string) {
if (cachedOctokit) {
return cachedOctokit;
}
// Determine the token to authenticate with.
const githubToken = token ?? env["GITHUB_TOKEN"] ?? env["GH_TOKEN"];
if (!githubToken) {
fail(
"Unable to locate a GitHub token. `github_token` should have been set on the action.",
);
}
cachedOctokit = github.getOctokit(githubToken!);
return cachedOctokit;
},
};
}
/**
* Shared context built from the actual `process.env`. Production code that is
* not yet refactored to receive a context explicitly may import and use this
* singleton. Tests should avoid the singleton and instead pass their own
* context to the functions they exercise.
*/
export const defaultContext: EnvContext = createEnvContext();

4
.github/actions/codex/src/fail.ts vendored Normal file
View File

@@ -0,0 +1,4 @@
export function fail(message: string): never {
console.error(message);
process.exit(1);
}

149
.github/actions/codex/src/git-helpers.ts vendored Normal file
View File

@@ -0,0 +1,149 @@
import { spawnSync } from "child_process";
import * as github from "@actions/github";
import { EnvContext } from "./env-context";
function runGit(args: string[], silent = true): string {
console.info(`Running git ${args.join(" ")}`);
const res = spawnSync("git", args, {
encoding: "utf8",
stdio: silent ? ["ignore", "pipe", "pipe"] : "inherit",
});
if (res.error) {
throw res.error;
}
if (res.status !== 0) {
// Return stderr so caller may handle; else throw.
throw new Error(
`git ${args.join(" ")} failed with code ${res.status}: ${res.stderr}`,
);
}
return res.stdout.trim();
}
function stageAllChanges() {
runGit(["add", "-A"]);
}
function hasStagedChanges(): boolean {
const res = spawnSync("git", ["diff", "--cached", "--quiet", "--exit-code"]);
return res.status !== 0;
}
function ensureOnBranch(
issueNumber: number,
protectedBranches: string[],
suggestedSlug?: string,
): string {
let branch = "";
try {
branch = runGit(["symbolic-ref", "--short", "-q", "HEAD"]);
} catch {
branch = "";
}
// If detached HEAD or on a protected branch, create a new branch.
if (!branch || protectedBranches.includes(branch)) {
if (suggestedSlug) {
const safeSlug = suggestedSlug
.toLowerCase()
.replace(/[^\w\s-]/g, "")
.trim()
.replace(/\s+/g, "-");
branch = `codex-fix-${issueNumber}-${safeSlug}`;
} else {
branch = `codex-fix-${issueNumber}-${Date.now()}`;
}
runGit(["switch", "-c", branch]);
}
return branch;
}
function commitIfNeeded(issueNumber: number) {
if (hasStagedChanges()) {
runGit([
"commit",
"-m",
`fix: automated fix for #${issueNumber} via Codex`,
]);
}
}
function pushBranch(branch: string, githubToken: string, ctx: EnvContext) {
const repoSlug = ctx.get("GITHUB_REPOSITORY"); // owner/repo
const remoteUrl = `https://x-access-token:${githubToken}@github.com/${repoSlug}.git`;
runGit(["push", "--force-with-lease", "-u", remoteUrl, `HEAD:${branch}`]);
}
/**
* If this returns a string, it is the URL of the created PR.
*/
export async function maybePublishPRForIssue(
issueNumber: number,
lastMessage: string,
ctx: EnvContext,
): Promise<string | undefined> {
// Only proceed if GITHUB_TOKEN available.
const githubToken =
ctx.tryGetNonEmpty("GITHUB_TOKEN") ?? ctx.tryGetNonEmpty("GH_TOKEN");
if (!githubToken) {
console.warn("No GitHub token - skipping PR creation.");
return undefined;
}
// Print `git status` for debugging.
runGit(["status"]);
// Stage any remaining changes so they can be committed and pushed.
stageAllChanges();
const octokit = ctx.getOctokit(githubToken);
const { owner, repo } = github.context.repo;
// Determine default branch to treat as protected.
let defaultBranch = "main";
try {
const repoInfo = await octokit.rest.repos.get({ owner, repo });
defaultBranch = repoInfo.data.default_branch ?? "main";
} catch (e) {
console.warn(`Failed to get default branch, assuming 'main': ${e}`);
}
const sanitizedMessage = lastMessage.replace(/\u2022/g, "-");
const [summaryLine] = sanitizedMessage.split(/\r?\n/);
const branch = ensureOnBranch(issueNumber, [defaultBranch, "master"], summaryLine);
commitIfNeeded(issueNumber);
pushBranch(branch, githubToken, ctx);
// Try to find existing PR for this branch
const headParam = `${owner}:${branch}`;
const existing = await octokit.rest.pulls.list({
owner,
repo,
head: headParam,
state: "open",
});
if (existing.data.length > 0) {
return existing.data[0].html_url;
}
// Determine base branch (default to main)
let baseBranch = "main";
try {
const repoInfo = await octokit.rest.repos.get({ owner, repo });
baseBranch = repoInfo.data.default_branch ?? "main";
} catch (e) {
console.warn(`Failed to get default branch, assuming 'main': ${e}`);
}
const pr = await octokit.rest.pulls.create({
owner,
repo,
title: summaryLine,
head: branch,
base: baseBranch,
body: sanitizedMessage,
});
return pr.data.html_url;
}

16
.github/actions/codex/src/git-user.ts vendored Normal file
View File

@@ -0,0 +1,16 @@
export function setGitHubActionsUser(): void {
const commands = [
["git", "config", "--global", "user.name", "github-actions[bot]"],
[
"git",
"config",
"--global",
"user.email",
"41898282+github-actions[bot]@users.noreply.github.com",
],
];
for (const command of commands) {
Bun.spawnSync(command);
}
}

View File

@@ -0,0 +1,11 @@
import * as pathMod from "path";
import { EnvContext } from "./env-context";
export function resolveWorkspacePath(path: string, ctx: EnvContext): string {
if (pathMod.isAbsolute(path)) {
return path;
} else {
const workspace = ctx.get("GITHUB_WORKSPACE");
return pathMod.join(workspace, path);
}
}

View File

@@ -0,0 +1,56 @@
import type { Config, LabelConfig } from "./config";
import { getDefaultConfig } from "./default-label-config";
import { readFileSync, readdirSync, statSync } from "fs";
import * as path from "path";
/**
* Build an in-memory configuration object by scanning the repository for
* Markdown templates located in `.github/codex/labels`.
*
* Each `*.md` file in that directory represents a label that can trigger the
* Codex GitHub Action. The filename **without** the extension is interpreted
* as the label name, e.g. `codex-review.md` ➜ `codex-review`.
*
* For every such label we derive the corresponding `doneLabel` by appending
* the suffix `-completed`.
*/
export function loadConfig(workspace: string): Config {
const labelsDir = path.join(workspace, ".github", "codex", "labels");
let entries: string[];
try {
entries = readdirSync(labelsDir);
} catch {
// If the directory is missing, return the default configuration.
return getDefaultConfig();
}
const labels: Record<string, LabelConfig> = {};
for (const entry of entries) {
if (!entry.endsWith(".md")) {
continue;
}
const fullPath = path.join(labelsDir, entry);
if (!statSync(fullPath).isFile()) {
continue;
}
const labelName = entry.slice(0, -3); // trim ".md"
labels[labelName] = new FileLabelConfig(fullPath);
}
return { labels };
}
class FileLabelConfig implements LabelConfig {
constructor(private readonly promptPath: string) {}
getPromptTemplate(): string {
return readFileSync(this.promptPath, "utf8");
}
}

80
.github/actions/codex/src/main.ts vendored Executable file
View File

@@ -0,0 +1,80 @@
#!/usr/bin/env bun
import type { Config } from "./config";
import { defaultContext, EnvContext } from "./env-context";
import { loadConfig } from "./load-config";
import { setGitHubActionsUser } from "./git-user";
import { onLabeled } from "./process-label";
import { ensureBaseAndHeadCommitsForPRAreAvailable } from "./prompt-template";
import { performAdditionalValidation } from "./verify-inputs";
import { onComment } from "./comment";
import { onReview } from "./review";
async function main(): Promise<void> {
const ctx: EnvContext = defaultContext;
// Build the configuration dynamically by scanning `.github/codex/labels`.
const GITHUB_WORKSPACE = ctx.get("GITHUB_WORKSPACE");
const config: Config = loadConfig(GITHUB_WORKSPACE);
// Optionally perform additional validation of prompt template files.
performAdditionalValidation(config, GITHUB_WORKSPACE);
const GITHUB_EVENT_NAME = ctx.get("GITHUB_EVENT_NAME");
const GITHUB_EVENT_ACTION = ctx.get("GITHUB_EVENT_ACTION");
// Set user.name and user.email to a bot before Codex runs, just in case it
// creates a commit.
setGitHubActionsUser();
switch (GITHUB_EVENT_NAME) {
case "issues": {
if (GITHUB_EVENT_ACTION === "labeled") {
await onLabeled(config, ctx);
return;
} else if (GITHUB_EVENT_ACTION === "opened") {
await onComment(ctx);
return;
}
break;
}
case "issue_comment": {
if (GITHUB_EVENT_ACTION === "created") {
await onComment(ctx);
return;
}
break;
}
case "pull_request": {
if (GITHUB_EVENT_ACTION === "labeled") {
await ensureBaseAndHeadCommitsForPRAreAvailable(ctx);
await onLabeled(config, ctx);
return;
}
break;
}
case "pull_request_review": {
await ensureBaseAndHeadCommitsForPRAreAvailable(ctx);
if (GITHUB_EVENT_ACTION === "submitted") {
await onReview(ctx);
return;
}
break;
}
case "pull_request_review_comment": {
await ensureBaseAndHeadCommitsForPRAreAvailable(ctx);
if (GITHUB_EVENT_ACTION === "created") {
await onComment(ctx);
return;
}
break;
}
}
console.warn(
`Unsupported action '${GITHUB_EVENT_ACTION}' for event '${GITHUB_EVENT_NAME}'.`,
);
}
main();

View File

@@ -0,0 +1,62 @@
import { fail } from "./fail";
import * as github from "@actions/github";
import { EnvContext } from "./env-context";
/**
* Post a comment to the issue / pull request currently in scope.
*
* Provide the environment context so that token lookup (inside getOctokit) does
* not rely on global state.
*/
export async function postComment(
commentBody: string,
ctx: EnvContext,
): Promise<void> {
// Append a footer with a link back to the workflow run, if available.
const footer = buildWorkflowRunFooter(ctx);
const bodyWithFooter = footer ? `${commentBody}${footer}` : commentBody;
const octokit = ctx.getOctokit();
console.info("Got Octokit instance for posting comment");
const { owner, repo } = github.context.repo;
const issueNumber = github.context.issue.number;
if (!issueNumber) {
console.warn(
"No issue or pull_request number found in GitHub context; skipping comment creation.",
);
return;
}
try {
console.info("Calling octokit.rest.issues.createComment()");
await octokit.rest.issues.createComment({
owner,
repo,
issue_number: issueNumber,
body: bodyWithFooter,
});
} catch (error) {
fail(`Failed to create comment via GitHub API: ${error}`);
}
}
/**
* Helper to build a Markdown fragment linking back to the workflow run that
* generated the current comment. Returns `undefined` if required environment
* variables are missing e.g. when running outside of GitHub Actions so we
* can gracefully skip the footer in those cases.
*/
function buildWorkflowRunFooter(ctx: EnvContext): string | undefined {
const serverUrl =
ctx.tryGetNonEmpty("GITHUB_SERVER_URL") ?? "https://github.com";
const repository = ctx.tryGetNonEmpty("GITHUB_REPOSITORY");
const runId = ctx.tryGetNonEmpty("GITHUB_RUN_ID");
if (!repository || !runId) {
return undefined;
}
const url = `${serverUrl}/${repository}/actions/runs/${runId}`;
return `\n\n---\n*[_View workflow run_](${url})*`;
}

View File

@@ -0,0 +1,195 @@
import { fail } from "./fail";
import { EnvContext } from "./env-context";
import { renderPromptTemplate } from "./prompt-template";
import { postComment } from "./post-comment";
import { runCodex } from "./run-codex";
import * as github from "@actions/github";
import { Config, LabelConfig } from "./config";
import { maybePublishPRForIssue } from "./git-helpers";
export async function onLabeled(
config: Config,
ctx: EnvContext,
): Promise<void> {
const GITHUB_EVENT_LABEL_NAME = ctx.get("GITHUB_EVENT_LABEL_NAME");
const labelConfig = config.labels[GITHUB_EVENT_LABEL_NAME] as
| LabelConfig
| undefined;
if (!labelConfig) {
fail(
`Label \`${GITHUB_EVENT_LABEL_NAME}\` not found in config: ${JSON.stringify(config)}`,
);
}
await processLabelConfig(ctx, GITHUB_EVENT_LABEL_NAME, labelConfig);
}
/**
* Wrapper that handles `-in-progress` and `-completed` semantics around the core lint/fix/review
* processing. It will:
*
* - Skip execution if the `-in-progress` or `-completed` label is already present.
* - Mark the PR/issue as `-in-progress`.
* - After successful execution, mark the PR/issue as `-completed`.
*/
async function processLabelConfig(
ctx: EnvContext,
label: string,
labelConfig: LabelConfig,
): Promise<void> {
const octokit = ctx.getOctokit();
const { owner, repo, issueNumber, labelNames } =
await getCurrentLabels(octokit);
const inProgressLabel = `${label}-in-progress`;
const completedLabel = `${label}-completed`;
for (const markerLabel of [inProgressLabel, completedLabel]) {
if (labelNames.includes(markerLabel)) {
console.log(
`Label '${markerLabel}' already present on issue/PR #${issueNumber}. Skipping Codex action.`,
);
// Clean up: remove the triggering label to avoid confusion and re-runs.
await addAndRemoveLabels(octokit, {
owner,
repo,
issueNumber,
remove: markerLabel,
});
return;
}
}
// Mark the PR/issue as in progress.
await addAndRemoveLabels(octokit, {
owner,
repo,
issueNumber,
add: inProgressLabel,
remove: label,
});
// Run the core Codex processing.
await processLabel(ctx, label, labelConfig);
// Mark the PR/issue as completed.
await addAndRemoveLabels(octokit, {
owner,
repo,
issueNumber,
add: completedLabel,
remove: inProgressLabel,
});
}
async function processLabel(
ctx: EnvContext,
label: string,
labelConfig: LabelConfig,
): Promise<void> {
const template = labelConfig.getPromptTemplate();
const populatedTemplate = await renderPromptTemplate(template, ctx);
// Always run Codex and post the resulting message as a comment.
let commentBody = await runCodex(populatedTemplate, ctx);
// Current heuristic: only try to create a PR if "attempt" or "fix" is in the
// label name. (Yes, we plan to evolve this.)
if (label.indexOf("fix") !== -1 || label.indexOf("attempt") !== -1) {
console.info(`label ${label} indicates we should attempt to create a PR`);
const prUrl = await maybeFixIssue(ctx, commentBody);
if (prUrl) {
commentBody += `\n\n---\nOpened pull request: ${prUrl}`;
}
} else {
console.info(
`label ${label} does not indicate we should attempt to create a PR`,
);
}
await postComment(commentBody, ctx);
}
async function maybeFixIssue(
ctx: EnvContext,
lastMessage: string,
): Promise<string | undefined> {
// Attempt to create a PR out of any changes Codex produced.
const issueNumber = github.context.issue.number!; // exists for issues triggering this path
try {
return await maybePublishPRForIssue(issueNumber, lastMessage, ctx);
} catch (e) {
console.warn(`Failed to publish PR: ${e}`);
}
}
async function getCurrentLabels(
octokit: ReturnType<typeof github.getOctokit>,
): Promise<{
owner: string;
repo: string;
issueNumber: number;
labelNames: Array<string>;
}> {
const { owner, repo } = github.context.repo;
const issueNumber = github.context.issue.number;
if (!issueNumber) {
fail("No issue or pull_request number found in GitHub context.");
}
const { data: issueData } = await octokit.rest.issues.get({
owner,
repo,
issue_number: issueNumber,
});
const labelNames =
issueData.labels?.map((label: any) =>
typeof label === "string" ? label : label.name,
) ?? [];
return { owner, repo, issueNumber, labelNames };
}
async function addAndRemoveLabels(
octokit: ReturnType<typeof github.getOctokit>,
opts: {
owner: string;
repo: string;
issueNumber: number;
add?: string;
remove?: string;
},
): Promise<void> {
const { owner, repo, issueNumber, add, remove } = opts;
if (add) {
try {
await octokit.rest.issues.addLabels({
owner,
repo,
issue_number: issueNumber,
labels: [add],
});
} catch (error) {
console.warn(`Failed to add label '${add}': ${error}`);
}
}
if (remove) {
try {
await octokit.rest.issues.removeLabel({
owner,
repo,
issue_number: issueNumber,
name: remove,
});
} catch (error) {
console.warn(`Failed to remove label '${remove}': ${error}`);
}
}
}

View File

@@ -0,0 +1,284 @@
/*
* Utilities to render Codex prompt templates.
*
* A template is a Markdown (or plain-text) file that may contain one or more
* placeholders of the form `{CODEX_ACTION_<NAME>}`. At runtime these
* placeholders are substituted with dynamically generated content. Each
* placeholder is resolved **exactly once** even if it appears multiple times
* in the same template.
*/
import { readFile } from "fs/promises";
import { EnvContext } from "./env-context";
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
/**
* Lazily caches parsed `$GITHUB_EVENT_PATH` contents keyed by the file path so
* we only hit the filesystem once per unique event payload.
*/
const githubEventDataCache: Map<string, Promise<any>> = new Map();
function getGitHubEventData(ctx: EnvContext): Promise<any> {
const eventPath = ctx.get("GITHUB_EVENT_PATH");
let cached = githubEventDataCache.get(eventPath);
if (!cached) {
cached = readFile(eventPath, "utf8").then((raw) => JSON.parse(raw));
githubEventDataCache.set(eventPath, cached);
}
return cached;
}
async function runCommand(args: Array<string>): Promise<string> {
const result = Bun.spawnSync(args, {
stdout: "pipe",
stderr: "pipe",
});
if (result.success) {
return result.stdout.toString();
}
console.error(`Error running ${JSON.stringify(args)}: ${result.stderr}`);
return "";
}
// ---------------------------------------------------------------------------
// Public API
// ---------------------------------------------------------------------------
// Regex that captures the variable name without the surrounding { } braces.
const VAR_REGEX = /\{(CODEX_ACTION_[A-Z0-9_]+)\}/g;
// Cache individual placeholder values so each one is resolved at most once per
// process even if many templates reference it.
const placeholderCache: Map<string, Promise<string>> = new Map();
/**
* Parse a template string, resolve all placeholders and return the rendered
* result.
*/
export async function renderPromptTemplate(
template: string,
ctx: EnvContext,
): Promise<string> {
// ---------------------------------------------------------------------
// 1) Gather all *unique* placeholders present in the template.
// ---------------------------------------------------------------------
const variables = new Set<string>();
for (const match of template.matchAll(VAR_REGEX)) {
variables.add(match[1]);
}
// ---------------------------------------------------------------------
// 2) Kick off (or reuse) async resolution for each variable.
// ---------------------------------------------------------------------
for (const variable of variables) {
if (!placeholderCache.has(variable)) {
placeholderCache.set(variable, resolveVariable(variable, ctx));
}
}
// ---------------------------------------------------------------------
// 3) Await completion so we can perform a simple synchronous replace below.
// ---------------------------------------------------------------------
const resolvedEntries: [string, string][] = [];
for (const [key, promise] of placeholderCache.entries()) {
resolvedEntries.push([key, await promise]);
}
const resolvedMap = new Map<string, string>(resolvedEntries);
// ---------------------------------------------------------------------
// 4) Replace each occurrence. We use replace with a callback to ensure
// correct substitution even if variable names overlap (they shouldn't,
// but better safe than sorry).
// ---------------------------------------------------------------------
return template.replace(VAR_REGEX, (_, varName: string) => {
return resolvedMap.get(varName) ?? "";
});
}
export async function ensureBaseAndHeadCommitsForPRAreAvailable(
ctx: EnvContext,
): Promise<{ baseSha: string; headSha: string } | null> {
const prShas = await getPrShas(ctx);
if (prShas == null) {
console.warn("Unable to resolve PR branches");
return null;
}
const event = await getGitHubEventData(ctx);
const pr = event.pull_request;
if (!pr) {
console.warn("event.pull_request is not defined - unexpected");
return null;
}
const workspace = ctx.get("GITHUB_WORKSPACE");
// Refs (branch names)
const baseRef: string | undefined = pr.base?.ref;
const headRef: string | undefined = pr.head?.ref;
// Clone URLs
const baseRemoteUrl: string | undefined = pr.base?.repo?.clone_url;
const headRemoteUrl: string | undefined = pr.head?.repo?.clone_url;
if (!baseRef || !headRef || !baseRemoteUrl || !headRemoteUrl) {
console.warn(
"Missing PR ref or remote URL information - cannot fetch commits",
);
return null;
}
// Ensure we have the base branch.
await runCommand([
"git",
"-C",
workspace,
"fetch",
"--no-tags",
"origin",
baseRef,
]);
// Ensure we have the head branch.
if (headRemoteUrl === baseRemoteUrl) {
// Same repository the commit is available from `origin`.
await runCommand([
"git",
"-C",
workspace,
"fetch",
"--no-tags",
"origin",
headRef,
]);
} else {
// Fork make sure a `pr` remote exists that points at the fork. Attempting
// to add a remote that already exists causes git to error, so we swallow
// any non-zero exit codes from that specific command.
await runCommand([
"git",
"-C",
workspace,
"remote",
"add",
"pr",
headRemoteUrl,
]);
// Whether adding succeeded or the remote already existed, attempt to fetch
// the head ref from the `pr` remote.
await runCommand([
"git",
"-C",
workspace,
"fetch",
"--no-tags",
"pr",
headRef,
]);
}
return prShas;
}
// ---------------------------------------------------------------------------
// Internal helpers still exported for use by other modules.
// ---------------------------------------------------------------------------
export async function resolvePrDiff(ctx: EnvContext): Promise<string> {
const prShas = await ensureBaseAndHeadCommitsForPRAreAvailable(ctx);
if (prShas == null) {
console.warn("Unable to resolve PR branches");
return "";
}
const workspace = ctx.get("GITHUB_WORKSPACE");
const { baseSha, headSha } = prShas;
return runCommand([
"git",
"-C",
workspace,
"diff",
"--color=never",
`${baseSha}..${headSha}`,
]);
}
// ---------------------------------------------------------------------------
// Placeholder resolution
// ---------------------------------------------------------------------------
async function resolveVariable(name: string, ctx: EnvContext): Promise<string> {
switch (name) {
case "CODEX_ACTION_ISSUE_TITLE": {
const event = await getGitHubEventData(ctx);
const issue = event.issue ?? event.pull_request;
return issue?.title ?? "";
}
case "CODEX_ACTION_ISSUE_BODY": {
const event = await getGitHubEventData(ctx);
const issue = event.issue ?? event.pull_request;
return issue?.body ?? "";
}
case "CODEX_ACTION_GITHUB_EVENT_PATH": {
return ctx.get("GITHUB_EVENT_PATH");
}
case "CODEX_ACTION_BASE_REF": {
const event = await getGitHubEventData(ctx);
return event?.pull_request?.base?.ref ?? "";
}
case "CODEX_ACTION_HEAD_REF": {
const event = await getGitHubEventData(ctx);
return event?.pull_request?.head?.ref ?? "";
}
case "CODEX_ACTION_PR_DIFF": {
return resolvePrDiff(ctx);
}
// -------------------------------------------------------------------
// Add new template variables here.
// -------------------------------------------------------------------
default: {
// Unknown variable leave it blank to avoid leaking placeholders to the
// final prompt. The alternative would be to `fail()` here, but silently
// ignoring unknown placeholders is more forgiving and better matches the
// behaviour of typical template engines.
console.warn(`Unknown template variable: ${name}`);
return "";
}
}
}
async function getPrShas(
ctx: EnvContext,
): Promise<{ baseSha: string; headSha: string } | null> {
const event = await getGitHubEventData(ctx);
const pr = event.pull_request;
if (!pr) {
console.warn("event.pull_request is not defined");
return null;
}
// Prefer explicit SHAs if available to avoid relying on local branch names.
const baseSha: string | undefined = pr.base?.sha;
const headSha: string | undefined = pr.head?.sha;
if (!baseSha || !headSha) {
console.warn("one of base or head is not defined on event.pull_request");
return null;
}
return { baseSha, headSha };
}

42
.github/actions/codex/src/review.ts vendored Normal file
View File

@@ -0,0 +1,42 @@
import type { EnvContext } from "./env-context";
import { runCodex } from "./run-codex";
import { postComment } from "./post-comment";
import { addEyesReaction } from "./add-reaction";
/**
* Handle `pull_request_review` events. We treat the review body the same way
* as a normal comment.
*/
export async function onReview(ctx: EnvContext): Promise<void> {
const triggerPhrase = ctx.tryGet("INPUT_TRIGGER_PHRASE");
if (!triggerPhrase) {
console.warn("Empty trigger phrase: skipping.");
return;
}
const reviewBody = ctx.tryGet("GITHUB_EVENT_REVIEW_BODY");
if (!reviewBody) {
console.warn("Review body not found in environment: skipping.");
return;
}
if (!reviewBody.includes(triggerPhrase)) {
console.log(
`Trigger phrase '${triggerPhrase}' not found: nothing to do for this review.`,
);
return;
}
const prompt = reviewBody.replace(triggerPhrase, "").trim();
if (prompt.length === 0) {
console.warn("Prompt is empty after removing trigger phrase: skipping.");
return;
}
await addEyesReaction(ctx);
const lastMessage = await runCodex(prompt, ctx);
await postComment(lastMessage, ctx);
}

56
.github/actions/codex/src/run-codex.ts vendored Normal file
View File

@@ -0,0 +1,56 @@
import { fail } from "./fail";
import { EnvContext } from "./env-context";
import { tmpdir } from "os";
import { join } from "node:path";
import { readFile, mkdtemp } from "fs/promises";
import { resolveWorkspacePath } from "./github-workspace";
/**
* Runs the Codex CLI with the provided prompt and returns the output written
* to the "last message" file.
*/
export async function runCodex(
prompt: string,
ctx: EnvContext,
): Promise<string> {
const OPENAI_API_KEY = ctx.get("OPENAI_API_KEY");
const tempDirPath = await mkdtemp(join(tmpdir(), "codex-"));
const lastMessageOutput = join(tempDirPath, "codex-prompt.md");
const args = ["/usr/local/bin/codex-exec"];
const inputCodexArgs = ctx.tryGet("INPUT_CODEX_ARGS")?.trim();
if (inputCodexArgs) {
args.push(...inputCodexArgs.split(/\s+/));
}
args.push("--output-last-message", lastMessageOutput, prompt);
const env: Record<string, string> = { ...process.env, OPENAI_API_KEY };
const INPUT_CODEX_HOME = ctx.tryGet("INPUT_CODEX_HOME");
if (INPUT_CODEX_HOME) {
env.CODEX_HOME = resolveWorkspacePath(INPUT_CODEX_HOME, ctx);
}
console.log(`Running Codex: ${JSON.stringify(args)}`);
const result = Bun.spawnSync(args, {
stdout: "inherit",
stderr: "inherit",
env,
});
if (!result.success) {
fail(`Codex failed: see above for details.`);
}
// Read the output generated by Codex.
let lastMessage: string;
try {
lastMessage = await readFile(lastMessageOutput, "utf8");
} catch (err) {
fail(`Failed to read Codex output at '${lastMessageOutput}': ${err}`);
}
return lastMessage;
}

View File

@@ -0,0 +1,33 @@
// Validate the inputs passed to the composite action.
// The script currently ensures that the provided configuration file exists and
// matches the expected schema.
import type { Config } from "./config";
import { existsSync } from "fs";
import * as path from "path";
import { fail } from "./fail";
export function performAdditionalValidation(config: Config, workspace: string) {
// Additional validation: ensure referenced prompt files exist and are Markdown.
for (const [label, details] of Object.entries(config.labels)) {
// Determine which prompt key is present (the schema guarantees exactly one).
const promptPathStr =
(details as any).prompt ?? (details as any).promptPath;
if (promptPathStr) {
const promptPath = path.isAbsolute(promptPathStr)
? promptPathStr
: path.join(workspace, promptPathStr);
if (!existsSync(promptPath)) {
fail(`Prompt file for label '${label}' not found: ${promptPath}`);
}
if (!promptPath.endsWith(".md")) {
fail(
`Prompt file for label '${label}' must be a .md file (got ${promptPathStr}).`,
);
}
}
}
}

15
.github/actions/codex/tsconfig.json vendored Normal file
View File

@@ -0,0 +1,15 @@
{
"compilerOptions": {
"lib": ["ESNext"],
"target": "ESNext",
"module": "ESNext",
"moduleDetection": "force",
"moduleResolution": "bundler",
"noEmit": true,
"strict": true,
"skipLibCheck": true
},
"include": ["src"]
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 408 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 MiB

View File

@@ -1,3 +1,3 @@
model = "gpt-5"
model = "o3"
# Consider setting [mcp_servers] here!

View File

@@ -1,139 +0,0 @@
Review this PR and respond with a very concise final message, formatted in Markdown.
There should be a summary of the changes (1-2 sentences) and a few bullet points if necessary.
Then provide the **review** (1-2 sentences plus bullet points, friendly tone).
Things to look out for when doing the review:
## General Principles
- **Make sure the pull request body explains the motivation behind the change.** If the author has failed to do this, call it out, and if you think you can deduce the motivation behind the change, propose copy.
- Ideally, the PR body also contains a small summary of the change. For small changes, the PR title may be sufficient.
- Each PR should ideally do one conceptual thing. For example, if a PR does a refactoring as well as introducing a new feature, push back and suggest the refactoring be done in a separate PR. This makes things easier for the reviewer, as refactoring changes can often be far-reaching, yet quick to review.
- When introducing new code, be on the lookout for code that duplicates existing code. When found, propose a way to refactor the existing code such that it should be reused.
## Code Organization
- Each create in the Cargo workspace in `codex-rs` has a specific purpose: make a note if you believe new code is not introduced in the correct crate.
- When possible, try to keep the `core` crate as small as possible. Non-core but shared logic is often a good candidate for `codex-rs/common`.
- Be wary of large files and offer suggestions for how to break things into more reasonably-sized files.
- Rust files should generally be organized such that the public parts of the API appear near the top of the file and helper functions go below. This is analagous to the "inverted pyramid" structure that is favored in journalism.
## Assertions in Tests
Assert the equality of the entire objects instead of doing "piecemeal comparisons," performing `assert_eq!()` on individual fields.
Note that unit tests also function as "executable documentation." As shown in the following example, "piecemeal comparisons" are often more verbose, provide less coverage, and are not as useful as executable documentation.
For example, suppose you have the following enum:
```rust
#[derive(Debug, PartialEq)]
enum Message {
Request {
id: String,
method: String,
params: Option<serde_json::Value>,
},
Notification {
method: String,
params: Option<serde_json::Value>,
},
}
```
This is an example of a _piecemeal_ comparison:
```rust
// BAD: Piecemeal Comparison
#[test]
fn test_get_latest_messages() {
let messages = get_latest_messages();
assert_eq!(messages.len(), 2);
let m0 = &messages[0];
match m0 {
Message::Request { id, method, params } => {
assert_eq!(id, "123");
assert_eq!(method, "subscribe");
assert_eq!(
*params,
Some(json!({
"conversation_id": "x42z86"
}))
)
}
Message::Notification { .. } => {
panic!("expected Request");
}
}
let m1 = &messages[1];
match m1 {
Message::Request { .. } => {
panic!("expected Notification");
}
Message::Notification { method, params } => {
assert_eq!(method, "log");
assert_eq!(
*params,
Some(json!({
"level": "info",
"message": "subscribed"
}))
)
}
}
}
```
This is a _deep_ comparison:
```rust
// GOOD: Verify the entire structure with a single assert_eq!().
use pretty_assertions::assert_eq;
#[test]
fn test_get_latest_messages() {
let messages = get_latest_messages();
assert_eq!(
vec![
Message::Request {
id: "123".to_string(),
method: "subscribe".to_string(),
params: Some(json!({
"conversation_id": "x42z86"
})),
},
Message::Notification {
method: "log".to_string(),
params: Some(json!({
"level": "info",
"message": "subscribed"
})),
},
],
messages,
);
}
```
## More Tactical Rust Things To Look Out For
- Do not use `unsafe` (unless you have a really, really good reason like using an operating system API directly and no safe wrapper exists). For example, there are cases where it is tempting to use `unsafe` in order to use `std::env::set_var()`, but this indeed `unsafe` and has led to race conditions on multiple occasions. (When this happens, find a mechanism other than environment variables to use for configuration.)
- Encourage the use of small enums or the newtype pattern in Rust if it helps readability without adding significant cognitive load or lines of code.
- If you see opportunities for the changes in a diff to use more idiomatic Rust, please make specific recommendations. For example, favor the use of expressions over `return`.
- When modifying a `Cargo.toml` file, make sure that dependency lists stay alphabetically sorted. Also consider whether a new dependency is added to the appropriate place (e.g., `[dependencies]` versus `[dev-dependencies]`)
## Pull Request Body
- If the nature of the change seems to have a visual component (which is often the case for changes to `codex-rs/tui`), recommend including a screenshot or video to demonstrate the change, if appropriate.
- References to existing GitHub issues and PRs are encouraged, where appropriate, though you likely do not have network access, so may not be able to help here.
# PR Information
{CODEX_ACTION_GITHUB_EVENT_PATH} contains the JSON that triggered this GitHub workflow. It contains the `base` and `head` refs that define this PR. Both refs are available locally.

View File

@@ -1,30 +0,0 @@
# https://docs.github.com/en/code-security/dependabot/working-with-dependabot/dependabot-options-reference#package-ecosystem-
version: 2
updates:
- package-ecosystem: bun
directory: .github/actions/codex
schedule:
interval: weekly
- package-ecosystem: cargo
directories:
- codex-rs
- codex-rs/*
schedule:
interval: weekly
- package-ecosystem: devcontainers
directory: /
schedule:
interval: weekly
- package-ecosystem: docker
directory: codex-cli
schedule:
interval: weekly
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
- package-ecosystem: rust-toolchain
directory: codex-rs
schedule:
interval: weekly

View File

@@ -1,31 +1,27 @@
{
"outputs": {
"codex-exec": {
"platforms": {
"macos-aarch64": { "regex": "^codex-exec-aarch64-apple-darwin\\.zst$", "path": "codex-exec" },
"macos-x86_64": { "regex": "^codex-exec-x86_64-apple-darwin\\.zst$", "path": "codex-exec" },
"linux-x86_64": { "regex": "^codex-exec-x86_64-unknown-linux-musl\\.zst$", "path": "codex-exec" },
"linux-aarch64": { "regex": "^codex-exec-aarch64-unknown-linux-musl\\.zst$", "path": "codex-exec" }
}
},
"codex": {
"platforms": {
"macos-aarch64": {
"regex": "^codex-aarch64-apple-darwin\\.zst$",
"path": "codex"
},
"macos-x86_64": {
"regex": "^codex-x86_64-apple-darwin\\.zst$",
"path": "codex"
},
"linux-x86_64": {
"regex": "^codex-x86_64-unknown-linux-musl\\.zst$",
"path": "codex"
},
"linux-aarch64": {
"regex": "^codex-aarch64-unknown-linux-musl\\.zst$",
"path": "codex"
},
"windows-x86_64": {
"regex": "^codex-x86_64-pc-windows-msvc\\.exe\\.zst$",
"path": "codex.exe"
},
"windows-aarch64": {
"regex": "^codex-aarch64-pc-windows-msvc\\.exe\\.zst$",
"path": "codex.exe"
}
"macos-aarch64": { "regex": "^codex-aarch64-apple-darwin\\.zst$", "path": "codex" },
"macos-x86_64": { "regex": "^codex-x86_64-apple-darwin\\.zst$", "path": "codex" },
"linux-x86_64": { "regex": "^codex-x86_64-unknown-linux-musl\\.zst$", "path": "codex" },
"linux-aarch64": { "regex": "^codex-aarch64-unknown-linux-musl\\.zst$", "path": "codex" }
}
},
"codex-linux-sandbox": {
"platforms": {
"linux-x86_64": { "regex": "^codex-linux-sandbox-x86_64-unknown-linux-musl\\.zst$", "path": "codex-linux-sandbox" },
"linux-aarch64": { "regex": "^codex-linux-sandbox-aarch64-unknown-linux-musl\\.zst$", "path": "codex-linux-sandbox" }
}
}
}

View File

@@ -1,6 +0,0 @@
# External (non-OpenAI) Pull Request Requirements
Before opening this Pull Request, please read the dedicated "Contributing" markdown file or your PR may be closed:
https://github.com/openai/codex/blob/main/docs/contributing.md
If your PR conforms to our contribution guidelines, replace this text with a detailed and high quality description of your changes.

View File

@@ -12,34 +12,69 @@ jobs:
NODE_OPTIONS: --max-old-space-size=4096
steps:
- name: Checkout repository
uses: actions/checkout@v5
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 10.8.1
run_install: false
- name: Setup Node.js
uses: actions/setup-node@v5
- name: Get pnpm store directory
id: pnpm-cache
shell: bash
run: |
echo "store_path=$(pnpm store path --silent)" >> $GITHUB_OUTPUT
- name: Setup pnpm cache
uses: actions/cache@v4
with:
node-version: 22
path: ${{ steps.pnpm-cache.outputs.store_path }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
- name: Install dependencies
run: pnpm install --frozen-lockfile
run: pnpm install
# Run all tasks using workspace filters
- name: Check TypeScript code formatting
working-directory: codex-cli
run: pnpm run format
- name: Check Markdown and config file formatting
run: pnpm run format
- name: Run tests
run: pnpm run test
- name: Lint
run: |
pnpm --filter @openai/codex exec -- eslint src tests --ext ts --ext tsx \
--report-unused-disable-directives \
--rule "no-console:error" \
--rule "no-debugger:error" \
--max-warnings=-1
- name: Type-check
run: pnpm run typecheck
- name: Build
run: pnpm run build
- name: Ensure staging a release works.
working-directory: codex-cli
env:
GH_TOKEN: ${{ github.token }}
run: ./codex-cli/scripts/stage_release.sh
run: pnpm stage-release
- name: Ensure root README.md contains only ASCII and certain Unicode code points
- name: Ensure README.md contains only ASCII and certain Unicode code points
run: ./scripts/asciicheck.py README.md
- name: Check root README ToC
- name: Check README ToC
run: python3 scripts/readme_toc.py README.md
- name: Ensure codex-cli/README.md contains only ASCII and certain Unicode code points
run: ./scripts/asciicheck.py codex-cli/README.md
- name: Check codex-cli/README ToC
run: python3 scripts/readme_toc.py codex-cli/README.md

View File

@@ -18,7 +18,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v5
uses: actions/checkout@v4
- name: Annotate locations with typos
uses: codespell-project/codespell-problem-matcher@b80729f885d32f78a716c2f107b4db1025001c42 # v1
- name: Codespell

95
.github/workflows/codex.yml vendored Normal file
View File

@@ -0,0 +1,95 @@
name: Codex
on:
issues:
types: [opened, labeled]
pull_request:
branches: [main]
types: [labeled]
jobs:
codex:
# This `if` check provides complex filtering logic to avoid running Codex
# on every PR. Admittedly, one thing this does not verify is whether the
# sender has write access to the repo: that must be done as part of a
# runtime step.
#
# Note the label values should match the ones in the .github/codex/labels
# folder.
if: |
(github.event_name == 'issues' && (
(github.event.action == 'labeled' && (github.event.label.name == 'codex-attempt' || github.event.label.name == 'codex-triage'))
)) ||
(github.event_name == 'pull_request' && github.event.action == 'labeled' && github.event.label.name == 'codex-review')
runs-on: ubuntu-latest
permissions:
contents: write # can push or create branches
issues: write # for comments + labels on issues/PRs
pull-requests: write # for PR comments/labels
steps:
# TODO: Consider adding an optional mode (--dry-run?) to actions/codex
# that verifies whether Codex should actually be run for this event.
# (For example, it may be rejected because the sender does not have
# write access to the repo.) The benefit would be two-fold:
# 1. As the first step of this job, it gives us a chance to add a reaction
# or comment to the PR/issue ASAP to "ack" the request.
# 2. It saves resources by skipping the clone and setup steps below if
# Codex is not going to run.
- name: Checkout repository
uses: actions/checkout@v4
# We install the dependencies like we would for an ordinary CI job,
# particularly because Codex will not have network access to install
# these dependencies.
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 10.8.1
run_install: false
- name: Get pnpm store directory
id: pnpm-cache
shell: bash
run: |
echo "store_path=$(pnpm store path --silent)" >> $GITHUB_OUTPUT
- name: Setup pnpm cache
uses: actions/cache@v4
with:
path: ${{ steps.pnpm-cache.outputs.store_path }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
- name: Install dependencies
run: pnpm install
- uses: dtolnay/rust-toolchain@1.87
with:
targets: x86_64-unknown-linux-gnu
components: clippy
- uses: actions/cache@v4
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
${{ github.workspace }}/codex-rs/target/
key: cargo-ubuntu-24.04-x86_64-unknown-linux-gnu-${{ hashFiles('**/Cargo.lock') }}
# Note it is possible that the `verify` step internal to Run Codex will
# fail, in which case the work to setup the repo was worthless :(
- name: Run Codex
uses: ./.github/actions/codex
with:
openai_api_key: ${{ secrets.CODEX_OPENAI_API_KEY }}
github_token: ${{ secrets.GITHUB_TOKEN }}
codex_home: ./.github/codex/home

View File

@@ -1,94 +1,42 @@
name: rust-ci
on:
pull_request: {}
pull_request:
branches:
- main
paths:
- "codex-rs/**"
- ".github/**"
push:
branches:
- main
workflow_dispatch:
# CI builds in debug (dev) for faster signal.
# For CI, we build in debug (`--profile dev`) rather than release mode so we
# get signal faster.
jobs:
# --- Detect what changed (always runs) -------------------------------------
changed:
name: Detect changed areas
runs-on: ubuntu-24.04
outputs:
codex: ${{ steps.detect.outputs.codex }}
workflows: ${{ steps.detect.outputs.workflows }}
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Detect changed paths (no external action)
id: detect
shell: bash
run: |
set -euo pipefail
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
BASE_SHA='${{ github.event.pull_request.base.sha }}'
echo "Base SHA: $BASE_SHA"
# List files changed between base and current HEAD (merge-base aware)
mapfile -t files < <(git diff --name-only --no-renames "$BASE_SHA"...HEAD)
else
# On push / manual runs, default to running everything
files=("codex-rs/force" ".github/force")
fi
codex=false
workflows=false
for f in "${files[@]}"; do
[[ $f == codex-rs/* ]] && codex=true
[[ $f == .github/* ]] && workflows=true
done
echo "codex=$codex" >> "$GITHUB_OUTPUT"
echo "workflows=$workflows" >> "$GITHUB_OUTPUT"
# --- CI that doesn't need specific targets ---------------------------------
# CI that don't need specific targets
general:
name: Format / etc
runs-on: ubuntu-24.04
needs: changed
if: ${{ needs.changed.outputs.codex == 'true' || needs.changed.outputs.workflows == 'true' || github.event_name == 'push' }}
defaults:
run:
working-directory: codex-rs
steps:
- uses: actions/checkout@v5
- uses: dtolnay/rust-toolchain@1.89
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@1.87
with:
components: rustfmt
- name: cargo fmt
run: cargo fmt -- --config imports_granularity=Item --check
cargo_shear:
name: cargo shear
runs-on: ubuntu-24.04
needs: changed
if: ${{ needs.changed.outputs.codex == 'true' || needs.changed.outputs.workflows == 'true' || github.event_name == 'push' }}
defaults:
run:
working-directory: codex-rs
steps:
- uses: actions/checkout@v5
- uses: dtolnay/rust-toolchain@1.89
- uses: taiki-e/install-action@0c5db7f7f897c03b771660e91d065338615679f4 # v2
with:
tool: cargo-shear
version: 1.5.1
- name: cargo shear
run: cargo shear
# --- CI to validate on different os/targets --------------------------------
# CI to validate on different os/targets
lint_build_test:
name: ${{ matrix.runner }} - ${{ matrix.target }}${{ matrix.profile == 'release' && ' (release)' || '' }}
name: ${{ matrix.runner }} - ${{ matrix.target }}
runs-on: ${{ matrix.runner }}
timeout-minutes: 30
needs: changed
# Keep job-level if to avoid spinning up runners when not needed
if: ${{ needs.changed.outputs.codex == 'true' || needs.changed.outputs.workflows == 'true' || github.event_name == 'push' }}
defaults:
run:
working-directory: codex-rs
@@ -96,52 +44,27 @@ jobs:
strategy:
fail-fast: false
matrix:
# Note: While Codex CLI does not support Windows today, we include
# Windows in CI to ensure the code at least builds there.
include:
- runner: macos-14
target: aarch64-apple-darwin
profile: dev
- runner: macos-14
target: x86_64-apple-darwin
profile: dev
- runner: ubuntu-24.04
target: x86_64-unknown-linux-musl
profile: dev
- runner: ubuntu-24.04
target: x86_64-unknown-linux-gnu
profile: dev
- runner: ubuntu-24.04-arm
target: aarch64-unknown-linux-musl
profile: dev
- runner: ubuntu-24.04-arm
target: aarch64-unknown-linux-gnu
profile: dev
- runner: windows-latest
target: x86_64-pc-windows-msvc
profile: dev
- runner: windows-11-arm
target: aarch64-pc-windows-msvc
profile: dev
# Also run representative release builds on Mac and Linux because
# there could be release-only build errors we want to catch.
# Hopefully this also pre-populates the build cache to speed up
# releases.
- runner: macos-14
target: aarch64-apple-darwin
profile: release
- runner: ubuntu-24.04
target: x86_64-unknown-linux-musl
profile: release
- runner: windows-latest
target: x86_64-pc-windows-msvc
profile: release
- runner: windows-11-arm
target: aarch64-pc-windows-msvc
profile: release
steps:
- uses: actions/checkout@v5
- uses: dtolnay/rust-toolchain@1.89
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@1.87
with:
targets: ${{ matrix.target }}
components: clippy
@@ -154,41 +77,33 @@ jobs:
~/.cargo/registry/cache/
~/.cargo/git/db/
${{ github.workspace }}/codex-rs/target/
key: cargo-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-${{ hashFiles('**/Cargo.lock') }}
key: cargo-${{ matrix.runner }}-${{ matrix.target }}-${{ hashFiles('**/Cargo.lock') }}
- if: ${{ matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl'}}
name: Install musl build tools
run: |
sudo apt install -y musl-tools pkg-config && sudo rm -rf /var/lib/apt/lists/*
sudo apt install -y musl-tools pkg-config
- name: cargo clippy
id: clippy
run: cargo clippy --target ${{ matrix.target }} --all-features --tests --profile ${{ matrix.profile }} -- -D warnings
continue-on-error: true
run: cargo clippy --target ${{ matrix.target }} --all-features --tests -- -D warnings
# Running `cargo build` from the workspace root builds the workspace using
# the union of all features from third-party crates. This can mask errors
# where individual crates have underspecified features. To avoid this, we
# run `cargo check` for each crate individually, though because this is
# run `cargo build` for each crate individually, though because this is
# slower, we only do this for the x86_64-unknown-linux-gnu target.
- name: cargo check individual crates
id: cargo_check_all_crates
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' && matrix.profile != 'release' }}
- name: cargo build individual crates
id: build
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
continue-on-error: true
run: |
find . -name Cargo.toml -mindepth 2 -maxdepth 2 -print0 \
| xargs -0 -n1 -I{} bash -c 'cd "$(dirname "{}")" && cargo check --profile ${{ matrix.profile }}'
run: find . -name Cargo.toml -mindepth 2 -maxdepth 2 -print0 | xargs -0 -n1 -I{} bash -c 'cd "$(dirname "{}")" && cargo build'
- uses: taiki-e/install-action@0c5db7f7f897c03b771660e91d065338615679f4 # v2
with:
tool: nextest
version: 0.9.103
- name: tests
- name: cargo test
id: test
# Tests take too long for release builds to run them on every PR.
if: ${{ matrix.profile != 'release' }}
continue-on-error: true
run: cargo nextest run --all-features --no-fail-fast --target ${{ matrix.target }}
run: cargo test --all-features --target ${{ matrix.target }}
env:
RUST_BACKTRACE: 1
@@ -196,34 +111,8 @@ jobs:
- name: verify all steps passed
if: |
steps.clippy.outcome == 'failure' ||
steps.cargo_check_all_crates.outcome == 'failure' ||
steps.build.outcome == 'failure' ||
steps.test.outcome == 'failure'
run: |
echo "One or more checks failed (clippy, cargo_check_all_crates, or test). See logs for details."
echo "One or more checks failed (clippy, build, or test). See logs for details."
exit 1
# --- Gatherer job that you mark as the ONLY required status -----------------
results:
name: CI results (required)
needs: [changed, general, cargo_shear, lint_build_test]
if: always()
runs-on: ubuntu-24.04
steps:
- name: Summarize
shell: bash
run: |
echo "general: ${{ needs.general.result }}"
echo "shear : ${{ needs.cargo_shear.result }}"
echo "matrix : ${{ needs.lint_build_test.result }}"
# If nothing relevant changed (PR touching only root README, etc.),
# declare success regardless of other jobs.
if [[ '${{ needs.changed.outputs.codex }}' != 'true' && '${{ needs.changed.outputs.workflows }}' != 'true' && '${{ github.event_name }}' != 'push' ]]; then
echo 'No relevant changes -> CI not required.'
exit 0
fi
# Otherwise require the jobs to have succeeded
[[ '${{ needs.general.result }}' == 'success' ]] || { echo 'general failed'; exit 1; }
[[ '${{ needs.cargo_shear.result }}' == 'success' ]] || { echo 'cargo_shear failed'; exit 1; }
[[ '${{ needs.lint_build_test.result }}' == 'success' ]] || { echo 'matrix failed'; exit 1; }

View File

@@ -15,11 +15,14 @@ concurrency:
group: ${{ github.workflow }}
cancel-in-progress: true
env:
TAG_REGEX: '^rust-v[0-9]+\.[0-9]+\.[0-9]+$'
jobs:
tag-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v4
- name: Validate tag matches Cargo.toml version
shell: bash
@@ -30,8 +33,8 @@ jobs:
# 1. Must be a tag and match the regex
[[ "${GITHUB_REF_TYPE}" == "tag" ]] \
|| { echo "❌ Not a tag push"; exit 1; }
[[ "${GITHUB_REF_NAME}" =~ ^rust-v[0-9]+\.[0-9]+\.[0-9]+(-(alpha|beta)(\.[0-9]+)?)?$ ]] \
|| { echo "❌ Tag '${GITHUB_REF_NAME}' doesn't match expected format"; exit 1; }
[[ "${GITHUB_REF_NAME}" =~ ${TAG_REGEX} ]] \
|| { echo "❌ Tag '${GITHUB_REF_NAME}' != ${TAG_REGEX}"; exit 1; }
# 2. Extract versions
tag_ver="${GITHUB_REF_NAME#rust-v}"
@@ -70,14 +73,10 @@ jobs:
target: aarch64-unknown-linux-musl
- runner: ubuntu-24.04-arm
target: aarch64-unknown-linux-gnu
- runner: windows-latest
target: x86_64-pc-windows-msvc
- runner: windows-11-arm
target: aarch64-pc-windows-msvc
steps:
- uses: actions/checkout@v5
- uses: dtolnay/rust-toolchain@1.89
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@1.87
with:
targets: ${{ matrix.target }}
@@ -89,7 +88,7 @@ jobs:
~/.cargo/registry/cache/
~/.cargo/git/db/
${{ github.workspace }}/codex-rs/target/
key: cargo-${{ matrix.runner }}-${{ matrix.target }}-release-${{ hashFiles('**/Cargo.lock') }}
key: cargo-release-${{ matrix.runner }}-${{ matrix.target }}-${{ hashFiles('**/Cargo.lock') }}
- if: ${{ matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl'}}
name: Install musl build tools
@@ -97,7 +96,7 @@ jobs:
sudo apt install -y musl-tools pkg-config
- name: Cargo build
run: cargo build --target ${{ matrix.target }} --release --bin codex
run: cargo build --target ${{ matrix.target }} --release --all-targets --all-features
- name: Stage artifacts
shell: bash
@@ -105,16 +104,18 @@ jobs:
dest="dist/${{ matrix.target }}"
mkdir -p "$dest"
if [[ "${{ matrix.runner }}" == windows* ]]; then
cp target/${{ matrix.target }}/release/codex.exe "$dest/codex-${{ matrix.target }}.exe"
else
cp target/${{ matrix.target }}/release/codex "$dest/codex-${{ matrix.target }}"
fi
cp target/${{ matrix.target }}/release/codex-exec "$dest/codex-exec-${{ matrix.target }}"
cp target/${{ matrix.target }}/release/codex "$dest/codex-${{ matrix.target }}"
- if: ${{ matrix.runner == 'windows-11-arm' }}
name: Install zstd
shell: powershell
run: choco install -y zstandard
# After https://github.com/openai/codex/pull/1228 is merged and a new
# release is cut with an artifacts built after that PR, the `-gnu`
# variants can go away as we will only use the `-musl` variants.
- if: ${{ matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'x86_64-unknown-linux-gnu' || matrix.target == 'aarch64-unknown-linux-gnu' || matrix.target == 'aarch64-unknown-linux-musl' }}
name: Stage Linux-only artifacts
shell: bash
run: |
dest="dist/${{ matrix.target }}"
cp target/${{ matrix.target }}/release/codex-linux-sandbox "$dest/codex-linux-sandbox-${{ matrix.target }}"
- name: Compress artifacts
shell: bash
@@ -124,11 +125,11 @@ jobs:
dest="dist/${{ matrix.target }}"
# For compatibility with environments that lack the `zstd` tool we
# additionally create a `.tar.gz` for all platforms and `.zip` for
# Windows alongside every single binary that we publish. The end result is:
# additionally create a `.tar.gz` alongside every single binary that
# we publish. The end result is:
# codex-<target>.zst (existing)
# codex-<target>.tar.gz (new)
# codex-<target>.zip (only for Windows)
# ...same naming for codex-exec-* and codex-linux-sandbox-*
# 1. Produce a .tar.gz for every file in the directory *before* we
# run `zstd --rm`, because that flag deletes the original files.
@@ -136,20 +137,13 @@ jobs:
base="$(basename "$f")"
# Skip files that are already archives (shouldn't happen, but be
# safe).
if [[ "$base" == *.tar.gz || "$base" == *.zip ]]; then
if [[ "$base" == *.tar.gz ]]; then
continue
fi
# Create per-binary tar.gz
tar -C "$dest" -czf "$dest/${base}.tar.gz" "$base"
# Create zip archive for Windows binaries
# Must run from inside the dest dir so 7z won't
# embed the directory path inside the zip.
if [[ "${{ matrix.runner }}" == windows* ]]; then
(cd "$dest" && 7z a "${base}.zip" "$base")
fi
# Also create .zst (existing behaviour) *and* remove the original
# uncompressed binary to keep the directory small.
zstd -T0 -19 --rm "$dest/$base"
@@ -166,12 +160,11 @@ jobs:
release:
needs: build
name: release
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
env:
RELEASE_TAG: codex-rs-${{ github.sha }}-${{ github.run_attempt }}-${{ github.ref_name }}
steps:
- name: Checkout repository
uses: actions/checkout@v5
- uses: actions/download-artifact@v4
with:
path: dist
@@ -179,43 +172,17 @@ jobs:
- name: List
run: ls -R dist/
- name: Define release name
id: release_name
run: |
# Extract the version from the tag name, which is in the format
# "rust-v0.1.0".
version="${GITHUB_REF_NAME#rust-v}"
echo "name=${version}" >> $GITHUB_OUTPUT
- name: Stage npm package
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
TMP_DIR="${RUNNER_TEMP}/npm-stage"
python3 codex-cli/scripts/stage_rust_release.py \
--release-version "${{ steps.release_name.outputs.name }}" \
--tmp "${TMP_DIR}"
mkdir -p dist/npm
# Produce an npm-ready tarball using `npm pack` and store it in dist/npm.
# We then rename it to a stable name used by our publishing script.
(cd "$TMP_DIR" && npm pack --pack-destination "${GITHUB_WORKSPACE}/dist/npm")
mv "${GITHUB_WORKSPACE}"/dist/npm/*.tgz \
"${GITHUB_WORKSPACE}/dist/npm/codex-npm-${{ steps.release_name.outputs.name }}.tgz"
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
- uses: softprops/action-gh-release@v2
with:
name: ${{ steps.release_name.outputs.name }}
tag_name: ${{ github.ref_name }}
tag_name: ${{ env.RELEASE_TAG }}
files: dist/**
# Mark as prerelease only when the version has a suffix after x.y.z
# (e.g. -alpha, -beta). Otherwise publish a normal release.
prerelease: ${{ contains(steps.release_name.outputs.name, '-') }}
# For now, tag releases as "prerelease" because we are not claiming
# the Rust CLI is stable yet.
prerelease: true
- uses: facebook/dotslash-publish-release@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag: ${{ github.ref_name }}
tag: ${{ env.RELEASE_TAG }}
config: .github/dotslash-config.json

10
.gitignore vendored
View File

@@ -48,6 +48,12 @@ yarn-error.log*
# env
.env*
# oaipkg import cache
oaipkg/
# Ignore task worktree directories created by create-task-worktree.sh
agentydragon/tasks/.worktrees/
!.env.example
# package
@@ -81,3 +87,7 @@ CHANGELOG.ignore.md
# nix related
.direnv
.envrc
__pycache__
codex-rs/target

1
.husky/pre-commit Normal file
View File

@@ -0,0 +1 @@
pnpm lint-staged

15
.pre-commit-config.yaml Normal file
View File

@@ -0,0 +1,15 @@
repos:
- repo: local
hooks:
- id: check-tasks
name: Run all task-directory validation checks
entry: python3 agentydragon/tools/check_tasks.py
language: python
additional_dependencies: [PyYAML, toml, pydantic]
files: ^agentydragon/tasks/.*
- id: cargo-build
name: Check Rust workspace and linux-sandbox compile
entry: bash -lc 'cd codex-rs && RUSTFLAGS="-D warnings" cargo build --workspace --locked --all-targets && cargo build -p codex-linux-sandbox --locked --all-targets'
language: system
pass_filenames: false
require_serial: true

View File

@@ -1,7 +1,3 @@
/codex-cli/dist
/codex-cli/node_modules
pnpm-lock.yaml
prompt.md
*_prompt.md
*_instructions.md

View File

@@ -1,11 +0,0 @@
{
"recommendations": [
"rust-lang.rust-analyzer",
"tamasfe.even-better-toml",
"vadimcn.vscode-lldb",
// Useful if touching files in .github/workflows, though most
// contributors will not be doing that?
// "github.vscode-github-actions",
]
}

22
.vscode/launch.json vendored
View File

@@ -1,22 +0,0 @@
{
"version": "0.2.0",
"configurations": [
{
"type": "lldb",
"request": "launch",
"name": "Cargo launch",
"cargo": {
"cwd": "${workspaceFolder}/codex-rs",
"args": ["build", "--bin=codex-tui"]
},
"args": []
},
{
"type": "lldb",
"request": "attach",
"name": "Attach to running codex CLI",
"pid": "${command:pickProcess}",
"sourceLanguages": ["rust"]
}
]
}

18
.vscode/settings.json vendored
View File

@@ -1,18 +0,0 @@
{
"rust-analyzer.checkOnSave": true,
"rust-analyzer.check.command": "clippy",
"rust-analyzer.check.extraArgs": ["--all-features", "--tests"],
"rust-analyzer.rustfmt.extraArgs": ["--config", "imports_granularity=Item"],
"[rust]": {
"editor.defaultFormatter": "rust-lang.rust-analyzer",
"editor.formatOnSave": true,
},
"[toml]": {
"editor.defaultFormatter": "tamasfe.even-better-toml",
"editor.formatOnSave": true,
},
// Array order for options in ~/.codex/config.toml such as `notify` and the
// `args` for an MCP server is significant, so we disable reordering.
"evenBetterToml.formatter.reorderArrays": false,
"evenBetterToml.formatter.reorderKeys": true,
}

View File

@@ -1,66 +1,31 @@
# Rust/codex-rs
# AGENTS.md
In the codex-rs folder where the rust code lives:
This file provides guidance to OpenAI Codex (openai.com/codex) when working with
code in this repository.
- Crate names are prefixed with `codex-`. For example, the `core` folder's crate is named `codex-core`
- When using format! and you can inline variables into {}, always do that.
- Never add or modify any code related to `CODEX_SANDBOX_NETWORK_DISABLED_ENV_VAR` or `CODEX_SANDBOX_ENV_VAR`.
- You operate in a sandbox where `CODEX_SANDBOX_NETWORK_DISABLED=1` will be set whenever you use the `shell` tool. Any existing code that uses `CODEX_SANDBOX_NETWORK_DISABLED_ENV_VAR` was authored with this fact in mind. It is often used to early exit out of tests that the author knew you would not be able to run given your sandbox limitations.
- Similarly, when you spawn a process using Seatbelt (`/usr/bin/sandbox-exec`), `CODEX_SANDBOX=seatbelt` will be set on the child process. Integration tests that want to run Seatbelt themselves cannot be run under Seatbelt, so checks for `CODEX_SANDBOX=seatbelt` are also often used to early exit out of tests, as appropriate.
## Build, Lint & Test
Run `just fmt` (in `codex-rs` directory) automatically after making Rust code changes; do not ask for approval to run it. Before finalizing a change to `codex-rs`, run `just fix -p <project>` (in `codex-rs` directory) to fix any linter issues in the code. Prefer scoping with `-p` to avoid slow workspacewide Clippy builds; only run `just fix` without `-p` if you changed shared crates. Additionally, run the tests:
1. Run the test for the specific project that was changed. For example, if changes were made in `codex-rs/tui`, run `cargo test -p codex-tui`.
2. Once those pass, if any changes were made in common, core, or protocol, run the complete test suite with `cargo test --all-features`.
When running interactively, ask the user before running `just fix` to finalize. `just fmt` does not require approval. project-specific or individual tests can be run without asking the user, but do ask the user before running the complete test suite.
### JavaScript/TypeScript
- Install dependencies: `pnpm install`
- Run all tests: `pnpm test`
- Run a single test: `pnpm test -- -t <pattern>` or `pnpm test -- path/to/file.spec.ts`
- Watch tests: `pnpm test:watch`
- Lint: `pnpm lint && pnpm lint:fix`
- Type-check: `pnpm typecheck`
- Format: `pnpm format:fix`
- Build: `pnpm build`
## TUI style conventions
### Rust (codex-rs workspace)
- Build: `cargo build --workspace --locked`
- Test all: `cargo test --workspace`
- Test crate: `cargo test -p <crate>`
- Single test: `cargo test -p <crate> -- <test_name>`
- Format & check: `cargo fmt --all -- --check`
- Lint: `cargo clippy --all-targets --all-features -- -D warnings`
See `codex-rs/tui/styles.md`.
## Code Style Guidelines
## TUI code conventions
- Use concise styling helpers from ratatuis Stylize trait.
- Basic spans: use "text".into()
- Styled spans: use "text".red(), "text".green(), "text".magenta(), "text".dim(), etc.
- Prefer these over constructing styles with `Span::styled` and `Style` directly.
- Example: patch summary file lines
- Desired: vec![" └ ".into(), "M".red(), " ".dim(), "tui/src/app.rs".dim()]
### TUI Styling (ratatui)
- Prefer Stylize helpers: use "text".dim(), .bold(), .cyan(), .italic(), .underlined() instead of manual Style where possible.
- Prefer simple conversions: use "text".into() for spans and vec![…].into() for lines; when inference is ambiguous (e.g., Paragraph::new/Cell::from), use Line::from(spans) or Span::from(text).
- Computed styles: if the Style is computed at runtime, using `Span::styled` is OK (`Span::from(text).set_style(style)` is also acceptable).
- Avoid hardcoded white: do not use `.white()`; prefer the default foreground (no color).
- Chaining: combine helpers by chaining for readability (e.g., url.cyan().underlined()).
- Single items: prefer "text".into(); use Line::from(text) or Span::from(text) only when the target type isnt obvious from context, or when using .into() would require extra type annotations.
- Building lines: use vec![…].into() to construct a Line when the target type is obvious and no extra type annotations are needed; otherwise use Line::from(vec![…]).
- Avoid churn: dont refactor between equivalent forms (Span::styled ↔ set_style, Line::from ↔ .into()) without a clear readability or functional gain; follow filelocal conventions and do not introduce type annotations solely to satisfy .into().
- Compactness: prefer the form that stays on one line after rustfmt; if only one of Line::from(vec![…]) or vec![…].into() avoids wrapping, choose that. If both wrap, pick the one with fewer wrapped lines.
### Text wrapping
- Always use textwrap::wrap to wrap plain strings.
- If you have a ratatui Line and you want to wrap it, use the helpers in tui/src/wrapping.rs, e.g. word_wrap_lines / word_wrap_line.
- If you need to indent wrapped lines, use the initial_indent / subsequent_indent options from RtOptions if you can, rather than writing custom logic.
- If you have a list of lines and you need to prefix them all with some prefix (optionally different on the first vs subsequent lines), use the `prefix_lines` helper from line_utils.
## Tests
### Snapshot tests
This repo uses snapshot tests (via `insta`), especially in `codex-rs/tui`, to validate rendered output. When UI or text output changes intentionally, update the snapshots as follows:
- Run tests to generate any updated snapshots:
- `cargo test -p codex-tui`
- Check whats pending:
- `cargo insta pending-snapshots -p codex-tui`
- Review changes by reading the generated `*.snap.new` files directly in the repo, or preview a specific file:
- `cargo insta show -p codex-tui path/to/file.snap.new`
- Only if you intend to accept all new snapshots in this crate, run:
- `cargo insta accept -p codex-tui`
If you dont have the tool:
- `cargo install cargo-insta`
### Test assertions
- Tests should use pretty_assertions::assert_eq for clearer diffs. Import this at the top of the test module if it isn't already.
- JS/TS: ESLint + Prettier; group imports; camelCase vars & funcs; PascalCase types/components; catch specific errors
- Rust: rustfmt & Clippy (see `codex-rs/rustfmt.toml`); snake_case vars & funcs; PascalCase types; prefer early return; avoid `unwrap()` in prod
- General: Do not swallow exceptions; use DRY; generate/validate ASCII art programmatically
- Include any Cursor rules from `.cursor/rules/` or Copilot rules from `.github/copilot-instructions.md` if present

View File

@@ -1 +1,211 @@
The changelog can be found on the [releases page](https://github.com/openai/codex/releases)
# Changelog
You can install any of these versions: `npm install -g codex@version`
## `0.1.2505172129`
### 🪲 Bug Fixes
- Add node version check (#1007)
- Persist token after refresh (#1006)
## `0.1.2505171619`
- `codex --login` + `codex --free` (#998)
## `0.1.2505161800`
- Sign in with chatgpt credits (#974)
- Add support for OpenAI tool type, local_shell (#961)
## `0.1.2505161243`
- Sign in with chatgpt (#963)
- Session history viewer (#912)
- Apply patch issue when using different cwd (#942)
- Diff command for filenames with special characters (#954)
## `0.1.2505160811`
- `codex-mini-latest` (#951)
## `0.1.2505140839`
### 🪲 Bug Fixes
- Gpt-4.1 apply_patch handling (#930)
- Add support for fileOpener in config.json (#911)
- Patch in #366 and #367 for marked-terminal (#916)
- Remember to set lastIndex = 0 on shared RegExp (#918)
- Always load version from package.json at runtime (#909)
- Tweak the label for citations for better rendering (#919)
- Tighten up some logic around session timestamps and ids (#922)
- Change EventMsg enum so every variant takes a single struct (#925)
- Reasoning default to medium, show workdir when supplied (#931)
- Test_dev_null_write() was not using echo as intended (#923)
## `0.1.2504301751`
### 🚀 Features
- User config api key (#569)
- `@mention` files in codex (#701)
- Add `--reasoning` CLI flag (#314)
- Lower default retry wait time and increase number of tries (#720)
- Add common package registries domains to allowed-domains list (#414)
### 🪲 Bug Fixes
- Insufficient quota message (#758)
- Input keyboard shortcut opt+delete (#685)
- `/diff` should include untracked files (#686)
- Only allow running without sandbox if explicitly marked in safe container (#699)
- Tighten up check for /usr/bin/sandbox-exec (#710)
- Check if sandbox-exec is available (#696)
- Duplicate messages in quiet mode (#680)
## `0.1.2504251709`
### 🚀 Features
- Add openai model info configuration (#551)
- Added provider to run quiet mode function (#571)
- Create parent directories when creating new files (#552)
- Print bug report URL in terminal instead of opening browser (#510) (#528)
- Add support for custom provider configuration in the user config (#537)
- Add support for OpenAI-Organization and OpenAI-Project headers (#626)
- Add specific instructions for creating API keys in error msg (#581)
- Enhance toCodePoints to prevent potential unicode 14 errors (#615)
- More native keyboard navigation in multiline editor (#655)
- Display error on selection of invalid model (#594)
### 🪲 Bug Fixes
- Model selection (#643)
- Nits in apply patch (#640)
- Input keyboard shortcuts (#676)
- `apply_patch` unicode characters (#625)
- Don't clear turn input before retries (#611)
- More loosely match context for apply_patch (#610)
- Update bug report template - there is no --revision flag (#614)
- Remove outdated copy of text input and external editor feature (#670)
- Remove unreachable "disableResponseStorage" logic flow introduced in #543 (#573)
- Non-openai mode - fix for gemini content: null, fix 429 to throw before stream (#563)
- Only allow going up in history when not already in history if input is empty (#654)
- Do not grant "node" user sudo access when using run_in_container.sh (#627)
- Update scripts/build_container.sh to use pnpm instead of npm (#631)
- Update lint-staged config to use pnpm --filter (#582)
- Non-openai mode - don't default temp and top_p (#572)
- Fix error catching when checking for updates (#597)
- Close stdin when running an exec tool call (#636)
## `0.1.2504221401`
### 🚀 Features
- Show actionable errors when api keys are missing (#523)
- Add CLI `--version` flag (#492)
### 🪲 Bug Fixes
- Agent loop for ZDR (`disableResponseStorage`) (#543)
- Fix relative `workdir` check for `apply_patch` (#556)
- Minimal mid-stream #429 retry loop using existing back-off (#506)
- Inconsistent usage of base URL and API key (#507)
- Remove requirement for api key for ollama (#546)
- Support `[provider]_BASE_URL` (#542)
## `0.1.2504220136`
### 🚀 Features
- Add support for ZDR orgs (#481)
- Include fractional portion of chunk that exceeds stdout/stderr limit (#497)
## `0.1.2504211509`
### 🚀 Features
- Support multiple providers via Responses-Completion transformation (#247)
- Add user-defined safe commands configuration and approval logic #380 (#386)
- Allow switching approval modes when prompted to approve an edit/command (#400)
- Add support for `/diff` command autocomplete in TerminalChatInput (#431)
- Auto-open model selector if user selects deprecated model (#427)
- Read approvalMode from config file (#298)
- `/diff` command to view git diff (#426)
- Tab completions for file paths (#279)
- Add /command autocomplete (#317)
- Allow multi-line input (#438)
### 🪲 Bug Fixes
- `full-auto` support in quiet mode (#374)
- Enable shell option for child process execution (#391)
- Configure husky and lint-staged for pnpm monorepo (#384)
- Command pipe execution by improving shell detection (#437)
- Name of the file not matching the name of the component (#354)
- Allow proper exit from new Switch approval mode dialog (#453)
- Ensure /clear resets context and exclude system messages from approximateTokenUsed count (#443)
- `/clear` now clears terminal screen and resets context left indicator (#425)
- Correct fish completion function name in CLI script (#485)
- Auto-open model-selector when model is not found (#448)
- Remove unnecessary isLoggingEnabled() checks (#420)
- Improve test reliability for `raw-exec` (#434)
- Unintended tear down of agent loop (#483)
- Remove extraneous type casts (#462)
## `0.1.2504181820`
### 🚀 Features
- Add `/bug` report command (#312)
- Notify when a newer version is available (#333)
### 🪲 Bug Fixes
- Update context left display logic in TerminalChatInput component (#307)
- Improper spawn of sh on Windows Powershell (#318)
- `/bug` report command, thinking indicator (#381)
- Include pnpm lock file (#377)
## `0.1.2504172351`
### 🚀 Features
- Add Nix flake for reproducible development environments (#225)
### 🪲 Bug Fixes
- Handle invalid commands (#304)
- Raw-exec-process-group.test improve reliability and error handling (#280)
- Canonicalize the writeable paths used in seatbelt policy (#275)
## `0.1.2504172304`
### 🚀 Features
- Add shell completion subcommand (#138)
- Add command history persistence (#152)
- Shell command explanation option (#173)
- Support bun fallback runtime for codex CLI (#282)
- Add notifications for MacOS using Applescript (#160)
- Enhance image path detection in input processing (#189)
- `--config`/`-c` flag to open global instructions in nvim (#158)
- Update position of cursor when navigating input history with arrow keys to the end of the text (#255)
### 🪲 Bug Fixes
- Correct word deletion logic for trailing spaces (Ctrl+Backspace) (#131)
- Improve Windows compatibility for CLI commands and sandbox (#261)
- Correct typos in thinking texts (transcendent & parroting) (#108)
- Add empty vite config file to prevent resolving to parent (#273)
- Update regex to better match the retry error messages (#266)
- Add missing "as" in prompt prefix in agent loop (#186)
- Allow continuing after interrupting assistant (#178)
- Standardize filename to kebab-case 🐍➡️🥙 (#302)
- Small update to bug report template (#288)
- Duplicated message on model change (#276)
- Typos in prompts and comments (#195)
- Check workdir before spawn (#221)
<!-- generated - do not edit -->

4
NOTICE
View File

@@ -1,6 +1,2 @@
OpenAI Codex
Copyright 2025 OpenAI
This project includes code derived from [Ratatui](https://github.com/ratatui/ratatui), licensed under the MIT license.
Copyright (c) 2016-2022 Florian Dehau
Copyright (c) 2023-2025 The Ratatui Developers

779
README.md
View File

@@ -1,102 +1,747 @@
<h1 align="center">OpenAI Codex CLI</h1>
<p align="center">Lightweight coding agent that runs in your terminal</p>
<p align="center"><code>npm i -g @openai/codex</code><br />or <code>brew install codex</code></p>
<p align="center"><code>npm i -g @openai/codex</code></p>
<p align="center"><strong>Codex CLI</strong> is a coding agent from OpenAI that runs locally on your computer.</br>If you are looking for the <em>cloud-based agent</em> from OpenAI, <strong>Codex Web</strong>, see <a href="https://chatgpt.com/codex">chatgpt.com/codex</a>.</p>
<p align="center">
<img src="./.github/codex-cli-splash.png" alt="Codex CLI splash" width="80%" />
</p>
![Codex demo GIF using: codex "explain this codebase to me"](./.github/demo.gif)
---
<details>
<summary><strong>Table of contents</strong></summary>
<!-- Begin ToC -->
- [Experimental technology disclaimer](#experimental-technology-disclaimer)
- [Quickstart](#quickstart)
- [Why Codex?](#why-codex)
- [Security model & permissions](#security-model--permissions)
- [Platform sandboxing details](#platform-sandboxing-details)
- [System requirements](#system-requirements)
- [CLI reference](#cli-reference)
- [Memory & project docs](#memory--project-docs)
- [Non-interactive / CI mode](#non-interactive--ci-mode)
- [Tracing / verbose logging](#tracing--verbose-logging)
- [Recipes](#recipes)
- [Installation](#installation)
- [Configuration guide](#configuration-guide)
- [Basic configuration parameters](#basic-configuration-parameters)
- [Custom AI provider configuration](#custom-ai-provider-configuration)
- [History configuration](#history-configuration)
- [Configuration examples](#configuration-examples)
- [Full configuration example](#full-configuration-example)
- [Custom instructions](#custom-instructions)
- [Environment variables setup](#environment-variables-setup)
- [FAQ](#faq)
- [Zero data retention (ZDR) usage](#zero-data-retention-zdr-usage)
- [Codex open source fund](#codex-open-source-fund)
- [Contributing](#contributing)
- [Development workflow](#development-workflow)
- [Git hooks with Husky](#git-hooks-with-husky)
- [Debugging](#debugging)
- [Writing high-impact code changes](#writing-high-impact-code-changes)
- [Opening a pull request](#opening-a-pull-request)
- [Review process](#review-process)
- [Community values](#community-values)
- [Getting help](#getting-help)
- [Contributor license agreement (CLA)](#contributor-license-agreement-cla)
- [Quick fixes](#quick-fixes)
- [Releasing `codex`](#releasing-codex)
- [Alternative build options](#alternative-build-options)
- [Nix flake development](#nix-flake-development)
- [Security & responsible AI](#security--responsible-ai)
- [License](#license)
<!-- End ToC -->
</details>
---
## Experimental technology disclaimer
Codex CLI is an experimental project under active development. It is not yet stable, may contain bugs, incomplete features, or undergo breaking changes. We're building it in the open with the community and welcome:
- Bug reports
- Feature requests
- Pull requests
- Good vibes
Help us improve by filing issues or submitting PRs (see the section below for how to contribute)!
## Quickstart
### Installing and running Codex CLI
Install globally with your preferred package manager. If you use npm:
Install globally:
```shell
npm install -g @openai/codex
```
Alternatively, if you use Homebrew:
Next, set your OpenAI API key as an environment variable:
```shell
brew install codex
export OPENAI_API_KEY="your-api-key-here"
```
Then simply run `codex` to get started:
> **Note:** This command sets the key only for your current terminal session. You can add the `export` line to your shell's configuration file (e.g., `~/.zshrc`) but we recommend setting for the session. **Tip:** You can also place your API key into a `.env` file at the root of your project:
>
> ```env
> OPENAI_API_KEY=your-api-key-here
> ```
>
> The CLI will automatically load variables from `.env` (via `dotenv/config`).
<details>
<summary><strong>Use <code>--provider</code> to use other models</strong></summary>
> Codex also allows you to use other providers that support the OpenAI Chat Completions API. You can set the provider in the config file or use the `--provider` flag. The possible options for `--provider` are:
>
> - openai (default)
> - openrouter
> - azure
> - gemini
> - ollama
> - mistral
> - deepseek
> - xai
> - groq
> - arceeai
> - any other provider that is compatible with the OpenAI API
>
> If you use a provider other than OpenAI, you will need to set the API key for the provider in the config file or in the environment variable as:
>
> ```shell
> export <provider>_API_KEY="your-api-key-here"
> ```
>
> If you use a provider not listed above, you must also set the base URL for the provider:
>
> ```shell
> export <provider>_BASE_URL="https://your-provider-api-base-url"
> ```
</details>
<br />
Run interactively:
```shell
codex
```
<details>
<summary>You can also go to the <a href="https://github.com/openai/codex/releases/latest">latest GitHub Release</a> and download the appropriate binary for your platform.</summary>
Or, run with a prompt as input (and optionally in `Full Auto` mode):
Each GitHub Release contains many executables, but in practice, you likely want one of these:
```shell
codex "explain this codebase to me"
```
- macOS
- Apple Silicon/arm64: `codex-aarch64-apple-darwin.tar.gz`
- x86_64 (older Mac hardware): `codex-x86_64-apple-darwin.tar.gz`
- Linux
- x86_64: `codex-x86_64-unknown-linux-musl.tar.gz`
- arm64: `codex-aarch64-unknown-linux-musl.tar.gz`
```shell
codex --approval-mode full-auto "create the fanciest todo-list app"
```
Each archive contains a single entry with the platform baked into the name (e.g., `codex-x86_64-unknown-linux-musl`), so you likely want to rename it to `codex` after extracting it.
</details>
### Using Codex with your ChatGPT plan
<p align="center">
<img src="./.github/codex-cli-login.png" alt="Codex CLI login" width="80%" />
</p>
Run `codex` and select **Sign in with ChatGPT**. We recommend signing into your ChatGPT account to use Codex as part of your Plus, Pro, Team, Edu, or Enterprise plan. [Learn more about what's included in your ChatGPT plan](https://help.openai.com/en/articles/11369540-codex-in-chatgpt).
You can also use Codex with an API key, but this requires [additional setup](./docs/authentication.md#usage-based-billing-alternative-use-an-openai-api-key). If you previously used an API key for usage-based billing, see the [migration steps](./docs/authentication.md#migrating-from-usage-based-billing-api-key). If you're having trouble with login, please comment on [this issue](https://github.com/openai/codex/issues/1243).
### Model Context Protocol (MCP)
Codex CLI supports [MCP servers](./docs/advanced.md#model-context-protocol-mcp). Enable by adding an `mcp_servers` section to your `~/.codex/config.toml`.
### Configuration
Codex CLI supports a rich set of configuration options, with preferences stored in `~/.codex/config.toml`. For full configuration options, see [Configuration](./docs/config.md).
That's it - Codex will scaffold a file, run it inside a sandbox, install any
missing dependencies, and show you the live result. Approve the changes and
they'll be committed to your working directory.
---
### Docs & FAQ
## Why Codex?
- [**Getting started**](./docs/getting-started.md)
- [CLI usage](./docs/getting-started.md#cli-usage)
- [Running with a prompt as input](./docs/getting-started.md#running-with-a-prompt-as-input)
- [Example prompts](./docs/getting-started.md#example-prompts)
- [Memory with AGENTS.md](./docs/getting-started.md#memory-with-agentsmd)
- [Configuration](./docs/config.md)
- [**Sandbox & approvals**](./docs/sandbox.md)
- [**Authentication**](./docs/authentication.md)
- [Auth methods](./docs/authentication.md#forcing-a-specific-auth-method-advanced)
- [Login on a "Headless" machine](./docs/authentication.md#connecting-on-a-headless-machine)
- [**Advanced**](./docs/advanced.md)
- [Non-interactive / CI mode](./docs/advanced.md#non-interactive--ci-mode)
- [Tracing / verbose logging](./docs/advanced.md#tracing--verbose-logging)
- [Model Context Protocol (MCP)](./docs/advanced.md#model-context-protocol-mcp)
- [**Zero data retention (ZDR)**](./docs/zdr.md)
- [**Contributing**](./docs/contributing.md)
- [**Install & build**](./docs/install.md)
- [System Requirements](./docs/install.md#system-requirements)
- [DotSlash](./docs/install.md#dotslash)
- [Build from source](./docs/install.md#build-from-source)
- [**FAQ**](./docs/faq.md)
- [**Open source fund**](./docs/open-source-fund.md)
Codex CLI is built for developers who already **live in the terminal** and want
ChatGPT-level reasoning **plus** the power to actually run code, manipulate
files, and iterate - all under version control. In short, it's _chat-driven
development_ that understands and executes your repo.
- **Zero setup** - bring your OpenAI API key and it just works!
- **Full auto-approval, while safe + secure** by running network-disabled and directory-sandboxed
- **Multimodal** - pass in screenshots or diagrams to implement features ✨
And it's **fully open-source** so you can see and contribute to how it develops!
---
## Security model & permissions
Codex lets you decide _how much autonomy_ the agent receives and auto-approval policy via the
`--approval-mode` flag (or the interactive onboarding prompt):
| Mode | What the agent may do without asking | Still requires approval |
| ------------------------- | --------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
| **Suggest** <br>(default) | <li>Read any file in the repo | <li>**All** file writes/patches<li> **Any** arbitrary shell commands (aside from reading files) |
| **Auto Edit** | <li>Read **and** apply-patch writes to files | <li>**All** shell commands |
| **Full Auto** | <li>Read/write files <li> Execute shell commands (network disabled, writes limited to your workdir) | - |
In **Full Auto** every command is run **network-disabled** and confined to the
current working directory (plus temporary files) for defense-in-depth. Codex
will also show a warning/confirmation if you start in **auto-edit** or
**full-auto** while the directory is _not_ tracked by Git, so you always have a
safety net.
Coming soon: you'll be able to whitelist specific commands to auto-execute with
the network enabled, once we're confident in additional safeguards.
### Platform sandboxing details
The hardening mechanism Codex uses depends on your OS:
- **macOS 12+** - commands are wrapped with **Apple Seatbelt** (`sandbox-exec`).
- Everything is placed in a read-only jail except for a small set of
writable roots (`$PWD`, `$TMPDIR`, `~/.codex`, etc.).
- Outbound network is _fully blocked_ by default - even if a child process
tries to `curl` somewhere it will fail.
- **Linux** - there is no sandboxing by default.
We recommend using Docker for sandboxing, where Codex launches itself inside a **minimal
container image** and mounts your repo _read/write_ at the same path. A
custom `iptables`/`ipset` firewall script denies all egress except the
OpenAI API. This gives you deterministic, reproducible runs without needing
root on the host. You can use the [`run_in_container.sh`](./codex-cli/scripts/run_in_container.sh) script to set up the sandbox.
---
## System requirements
| Requirement | Details |
| --------------------------- | --------------------------------------------------------------- |
| Operating systems | macOS 12+, Ubuntu 20.04+/Debian 10+, or Windows 11 **via WSL2** |
| Node.js | **22 or newer** (LTS recommended) |
| Git (optional, recommended) | 2.23+ for built-in PR helpers |
| RAM | 4-GB minimum (8-GB recommended) |
> Never run `sudo npm install -g`; fix npm permissions instead.
---
## CLI reference
| Command | Purpose | Example |
| ------------------------------------ | ----------------------------------- | ------------------------------------ |
| `codex` | Interactive REPL | `codex` |
| `codex "..."` | Initial prompt for interactive REPL | `codex "fix lint errors"` |
| `codex -q "..."` | Non-interactive "quiet mode" | `codex -q --json "explain utils.ts"` |
| `codex completion <bash\|zsh\|fish>` | Print shell completion script | `codex completion bash` |
Key flags: `--model/-m`, `--approval-mode/-a`, `--quiet/-q`, and `--notify`.
---
## Memory & project docs
You can give Codex extra instructions and guidance using `AGENTS.md` files. Codex looks for `AGENTS.md` files in the following places, and merges them top-down:
1. `~/.codex/AGENTS.md` - personal global guidance
2. `AGENTS.md` at repo root - shared project notes
3. `AGENTS.md` in the current working directory - sub-folder/feature specifics
Disable loading of these files with `--no-project-doc` or the environment variable `CODEX_DISABLE_PROJECT_DOC=1`.
---
## Non-interactive / CI mode
Run Codex head-less in pipelines. Example GitHub Action step:
```yaml
- name: Update changelog via Codex
run: |
npm install -g @openai/codex
export OPENAI_API_KEY="${{ secrets.OPENAI_KEY }}"
codex -a auto-edit --quiet "update CHANGELOG for next release"
```
Set `CODEX_QUIET_MODE=1` to silence interactive UI noise.
## Tracing / verbose logging
Setting the environment variable `DEBUG=true` prints full API request and response details:
```shell
DEBUG=true codex
```
---
## Recipes
Below are a few bite-size examples you can copy-paste. Replace the text in quotes with your own task. See the [prompting guide](https://github.com/openai/codex/blob/main/codex-cli/examples/prompting_guide.md) for more tips and usage patterns.
| ✨ | What you type | What happens |
| --- | ------------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
| 1 | `codex "Refactor the Dashboard component to React Hooks"` | Codex rewrites the class component, runs `npm test`, and shows the diff. |
| 2 | `codex "Generate SQL migrations for adding a users table"` | Infers your ORM, creates migration files, and runs them in a sandboxed DB. |
| 3 | `codex "Write unit tests for utils/date.ts"` | Generates tests, executes them, and iterates until they pass. |
| 4 | `codex "Bulk-rename *.jpeg -> *.jpg with git mv"` | Safely renames files and updates imports/usages. |
| 5 | `codex "Explain what this regex does: ^(?=.*[A-Z]).{8,}$"` | Outputs a step-by-step human explanation. |
| 6 | `codex "Carefully review this repo, and propose 3 high impact well-scoped PRs"` | Suggests impactful PRs in the current codebase. |
| 7 | `codex "Look for vulnerabilities and create a security review report"` | Finds and explains security bugs. |
---
## Installation
<details open>
<summary><strong>From npm (Recommended)</strong></summary>
```bash
npm install -g @openai/codex
# or
yarn global add @openai/codex
# or
bun install -g @openai/codex
# or
pnpm add -g @openai/codex
```
</details>
<details>
<summary><strong>Build from source</strong></summary>
```bash
# Clone the repository and navigate to the CLI package
git clone https://github.com/openai/codex.git
cd codex/codex-cli
# Enable corepack
corepack enable
# Install dependencies and build
pnpm install
pnpm build
# Linux-only: download prebuilt sandboxing binaries (requires gh and zstd).
./scripts/install_native_deps.sh
# Get the usage and the options
node ./dist/cli.js --help
# Run the locally-built CLI directly
node ./dist/cli.js
# Or link the command globally for convenience
pnpm link
```
</details>
<details>
<summary><strong>Rust / Cargo (codex-rs)</strong></summary>
```bash
# Ensure you have Rust and Cargo installed (via rustup)
cd codex-rs/cli
cargo install --path . --locked
# Or run without installing:
cargo run --manifest-path codex-rs/cli/Cargo.toml -- --help
```
</details>
---
## Configuration guide
Codex configuration files can be placed in the `~/.codex/` directory, supporting both YAML and JSON formats.
### Basic configuration parameters
| Parameter | Type | Default | Description | Available Options |
| ------------------- | ------- | ---------- | -------------------------------- | ---------------------------------------------------------------------------------------------- |
| `model` | string | `o4-mini` | AI model to use | Any model name supporting OpenAI API |
| `approvalMode` | string | `suggest` | AI assistant's permission mode | `suggest` (suggestions only)<br>`auto-edit` (automatic edits)<br>`full-auto` (fully automatic) |
| `fullAutoErrorMode` | string | `ask-user` | Error handling in full-auto mode | `ask-user` (prompt for user input)<br>`ignore-and-continue` (ignore and proceed) |
| `notify` | boolean | `true` | Enable desktop notifications | `true`/`false` |
### Custom AI provider configuration
In the `providers` object, you can configure multiple AI service providers. Each provider requires the following parameters:
| Parameter | Type | Description | Example |
| --------- | ------ | --------------------------------------- | ----------------------------- |
| `name` | string | Display name of the provider | `"OpenAI"` |
| `baseURL` | string | API service URL | `"https://api.openai.com/v1"` |
| `envKey` | string | Environment variable name (for API key) | `"OPENAI_API_KEY"` |
### History configuration
In the `history` object, you can configure conversation history settings:
| Parameter | Type | Description | Example Value |
| ------------------- | ------- | ------------------------------------------------------ | ------------- |
| `maxSize` | number | Maximum number of history entries to save | `1000` |
| `saveHistory` | boolean | Whether to save history | `true` |
| `sensitivePatterns` | array | Patterns of sensitive information to filter in history | `[]` |
### Configuration examples
1. YAML format (save as `~/.codex/config.yaml`):
```yaml
model: o4-mini
approvalMode: suggest
fullAutoErrorMode: ask-user
notify: true
```
2. JSON format (save as `~/.codex/config.json`):
```json
{
"model": "o4-mini",
"approvalMode": "suggest",
"fullAutoErrorMode": "ask-user",
"notify": true
}
```
### Full configuration example
Below is a comprehensive example of `config.json` with multiple custom providers:
```json
{
"model": "o4-mini",
"provider": "openai",
"providers": {
"openai": {
"name": "OpenAI",
"baseURL": "https://api.openai.com/v1",
"envKey": "OPENAI_API_KEY"
},
"azure": {
"name": "AzureOpenAI",
"baseURL": "https://YOUR_PROJECT_NAME.openai.azure.com/openai",
"envKey": "AZURE_OPENAI_API_KEY"
},
"openrouter": {
"name": "OpenRouter",
"baseURL": "https://openrouter.ai/api/v1",
"envKey": "OPENROUTER_API_KEY"
},
"gemini": {
"name": "Gemini",
"baseURL": "https://generativelanguage.googleapis.com/v1beta/openai",
"envKey": "GEMINI_API_KEY"
},
"ollama": {
"name": "Ollama",
"baseURL": "http://localhost:11434/v1",
"envKey": "OLLAMA_API_KEY"
},
"mistral": {
"name": "Mistral",
"baseURL": "https://api.mistral.ai/v1",
"envKey": "MISTRAL_API_KEY"
},
"deepseek": {
"name": "DeepSeek",
"baseURL": "https://api.deepseek.com",
"envKey": "DEEPSEEK_API_KEY"
},
"xai": {
"name": "xAI",
"baseURL": "https://api.x.ai/v1",
"envKey": "XAI_API_KEY"
},
"groq": {
"name": "Groq",
"baseURL": "https://api.groq.com/openai/v1",
"envKey": "GROQ_API_KEY"
},
"arceeai": {
"name": "ArceeAI",
"baseURL": "https://conductor.arcee.ai/v1",
"envKey": "ARCEEAI_API_KEY"
}
},
"history": {
"maxSize": 1000,
"saveHistory": true,
"sensitivePatterns": []
}
}
```
### Custom instructions
You can create a `~/.codex/AGENTS.md` file to define custom guidance for the agent:
```markdown
- Always respond with emojis
- Only use git commands when explicitly requested
```
### Environment variables setup
For each AI provider, you need to set the corresponding API key in your environment variables. For example:
```bash
# OpenAI
export OPENAI_API_KEY="your-api-key-here"
# Azure OpenAI
export AZURE_OPENAI_API_KEY="your-azure-api-key-here"
export AZURE_OPENAI_API_VERSION="2025-03-01-preview" (Optional)
# OpenRouter
export OPENROUTER_API_KEY="your-openrouter-key-here"
# Similarly for other providers
```
---
## FAQ
<details>
<summary>OpenAI released a model called Codex in 2021 - is this related?</summary>
In 2021, OpenAI released Codex, an AI system designed to generate code from natural language prompts. That original Codex model was deprecated as of March 2023 and is separate from the CLI tool.
</details>
<details>
<summary>Which models are supported?</summary>
Any model available with [Responses API](https://platform.openai.com/docs/api-reference/responses). The default is `o4-mini`, but pass `--model gpt-4.1` or set `model: gpt-4.1` in your config file to override.
</details>
<details>
<summary>Why does <code>o3</code> or <code>o4-mini</code> not work for me?</summary>
It's possible that your [API account needs to be verified](https://help.openai.com/en/articles/10910291-api-organization-verification) in order to start streaming responses and seeing chain of thought summaries from the API. If you're still running into issues, please let us know!
</details>
<details>
<summary>How do I stop Codex from editing my files?</summary>
Codex runs model-generated commands in a sandbox. If a proposed command or file change doesn't look right, you can simply type **n** to deny the command or give the model feedback.
</details>
<details>
<summary>Does it work on Windows?</summary>
Not directly. It requires [Windows Subsystem for Linux (WSL2)](https://learn.microsoft.com/en-us/windows/wsl/install) - Codex has been tested on macOS and Linux with Node 22.
</details>
---
## Zero data retention (ZDR) usage
Codex CLI **does** support OpenAI organizations with [Zero Data Retention (ZDR)](https://platform.openai.com/docs/guides/your-data#zero-data-retention) enabled. If your OpenAI organization has Zero Data Retention enabled and you still encounter errors such as:
```
OpenAI rejected the request. Error details: Status: 400, Code: unsupported_parameter, Type: invalid_request_error, Message: 400 Previous response cannot be used for this organization due to Zero Data Retention.
```
You may need to upgrade to a more recent version with: `npm i -g @openai/codex@latest`
---
## Codex open source fund
We're excited to launch a **$1 million initiative** supporting open source projects that use Codex CLI and other OpenAI models.
- Grants are awarded up to **$25,000** API credits.
- Applications are reviewed **on a rolling basis**.
**Interested? [Apply here](https://openai.com/form/codex-open-source-fund/).**
---
## Contributing
This project is under active development and the code will likely change pretty significantly. We'll update this message once that's complete!
More broadly we welcome contributions - whether you are opening your very first pull request or you're a seasoned maintainer. At the same time we care about reliability and long-term maintainability, so the bar for merging code is intentionally **high**. The guidelines below spell out what "high-quality" means in practice and should make the whole process transparent and friendly.
### Development workflow
- Create a _topic branch_ from `main` - e.g. `feat/interactive-prompt`.
- Keep your changes focused. Multiple unrelated fixes should be opened as separate PRs.
- Use `pnpm test:watch` during development for super-fast feedback.
- We use **Vitest** for unit tests, **ESLint** + **Prettier** for style, and **TypeScript** for type-checking.
- Before pushing, run the full test/type/lint suite:
### Git hooks with Husky
This project uses [Husky](https://typicode.github.io/husky/) to enforce code quality checks:
- **Pre-commit hook**: Automatically runs lint-staged to format and lint files before committing
- **Pre-push hook**: Runs tests and type checking before pushing to the remote
These hooks help maintain code quality and prevent pushing code with failing tests. For more details, see [HUSKY.md](./codex-cli/HUSKY.md).
```bash
pnpm test && pnpm run lint && pnpm run typecheck
```
- If you have **not** yet signed the Contributor License Agreement (CLA), add a PR comment containing the exact text
```text
I have read the CLA Document and I hereby sign the CLA
```
The CLA-Assistant bot will turn the PR status green once all authors have signed.
```bash
# Watch mode (tests rerun on change)
pnpm test:watch
# Type-check without emitting files
pnpm typecheck
# Automatically fix lint + prettier issues
pnpm lint:fix
pnpm format:fix
```
### Debugging
To debug the CLI with a visual debugger, do the following in the `codex-cli` folder:
- Run `pnpm run build` to build the CLI, which will generate `cli.js.map` alongside `cli.js` in the `dist` folder.
- Run the CLI with `node --inspect-brk ./dist/cli.js` The program then waits until a debugger is attached before proceeding. Options:
- In VS Code, choose **Debug: Attach to Node Process** from the command palette and choose the option in the dropdown with debug port `9229` (likely the first option)
- Go to <chrome://inspect> in Chrome and find **localhost:9229** and click **trace**
### Writing high-impact code changes
1. **Start with an issue.** Open a new one or comment on an existing discussion so we can agree on the solution before code is written.
2. **Add or update tests.** Every new feature or bug-fix should come with test coverage that fails before your change and passes afterwards. 100% coverage is not required, but aim for meaningful assertions.
3. **Document behaviour.** If your change affects user-facing behaviour, update the README, inline help (`codex --help`), or relevant example projects.
4. **Keep commits atomic.** Each commit should compile and the tests should pass. This makes reviews and potential rollbacks easier.
### Opening a pull request
- Fill in the PR template (or include similar information) - **What? Why? How?**
- Run **all** checks locally (`npm test && npm run lint && npm run typecheck`). CI failures that could have been caught locally slow down the process.
- Make sure your branch is up-to-date with `main` and that you have resolved merge conflicts.
- Mark the PR as **Ready for review** only when you believe it is in a merge-able state.
### Review process
1. One maintainer will be assigned as a primary reviewer.
2. We may ask for changes - please do not take this personally. We value the work, we just also value consistency and long-term maintainability.
3. When there is consensus that the PR meets the bar, a maintainer will squash-and-merge.
### Community values
- **Be kind and inclusive.** Treat others with respect; we follow the [Contributor Covenant](https://www.contributor-covenant.org/).
- **Assume good intent.** Written communication is hard - err on the side of generosity.
- **Teach & learn.** If you spot something confusing, open an issue or PR with improvements.
### Getting help
If you run into problems setting up the project, would like feedback on an idea, or just want to say _hi_ - please open a Discussion or jump into the relevant issue. We are happy to help.
Together we can make Codex CLI an incredible tool. **Happy hacking!** :rocket:
### Contributor license agreement (CLA)
All contributors **must** accept the CLA. The process is lightweight:
1. Open your pull request.
2. Paste the following comment (or reply `recheck` if you've signed before):
```text
I have read the CLA Document and I hereby sign the CLA
```
3. The CLA-Assistant bot records your signature in the repo and marks the status check as passed.
No special Git commands, email attachments, or commit footers required.
#### Quick fixes
| Scenario | Command |
| ----------------- | ------------------------------------------------ |
| Amend last commit | `git commit --amend -s --no-edit && git push -f` |
The **DCO check** blocks merges until every commit in the PR carries the footer (with squash this is just the one).
### Releasing `codex`
To publish a new version of the CLI you first need to stage the npm package. A
helper script in `codex-cli/scripts/` does all the heavy lifting. Inside the
`codex-cli` folder run:
```bash
# Classic, JS implementation that includes small, native binaries for Linux sandboxing.
pnpm stage-release
# Optionally specify the temp directory to reuse between runs.
RELEASE_DIR=$(mktemp -d)
pnpm stage-release --tmp "$RELEASE_DIR"
# "Fat" package that additionally bundles the native Rust CLI binaries for
# Linux. End-users can then opt-in at runtime by setting CODEX_RUST=1.
pnpm stage-release --native
```
Go to the folder where the release is staged and verify that it works as intended. If so, run the following from the temp folder:
```
cd "$RELEASE_DIR"
npm publish
```
### Alternative build options
#### Nix flake development
Prerequisite: Nix >= 2.4 with flakes enabled (`experimental-features = nix-command flakes` in `~/.config/nix/nix.conf`).
Enter a Nix development shell:
```bash
# Use either one of the commands according to which implementation you want to work with
nix develop .#codex-cli # For entering codex-cli specific shell
nix develop .#codex-rs # For entering codex-rs specific shell
```
This shell includes Node.js, installs dependencies, builds the CLI, and provides a `codex` command alias.
Build and run the CLI directly:
```bash
# Use either one of the commands according to which implementation you want to work with
nix build .#codex-cli # For building codex-cli
nix build .#codex-rs # For building codex-rs
./result/bin/codex --help
```
Run the CLI via the flake app:
```bash
# Use either one of the commands according to which implementation you want to work with
nix run .#codex-cli # For running codex-cli
nix run .#codex-rs # For running codex-rs
```
Use direnv with flakes
If you have direnv installed, you can use the following `.envrc` to automatically enter the Nix shell when you `cd` into the project directory:
```bash
cd codex-rs
echo "use flake ../flake.nix#codex-cli" >> .envrc && direnv allow
cd codex-cli
echo "use flake ../flake.nix#codex-rs" >> .envrc && direnv allow
```
---
## Security & responsible AI
Have you discovered a vulnerability or have concerns about model output? Please e-mail **security@openai.com** and we will respond promptly.
---
## License
This repository is licensed under the [Apache-2.0 License](LICENSE).

174
agentydragon/CHANGES.md Normal file
View File

@@ -0,0 +1,174 @@
# codex-rs: Changes between HEAD and main
This document summarizes new and removed features, configuration options,
and behavioral changes in the `codex-rs` workspace between the `main`
branch and the current `HEAD`. Only additions/deletions (not unmodified
code) are listed, with examples of usage and configuration.
---
## CLI Enhancements
### Build & Install from Source
```shell
cargo install --path cli --locked
# install system-wide:
sudo cargo install --path cli --locked --root /usr/local
```
### New `codex config` Subcommand
Manage your `~/.codex/config.toml` directly without manually editing:
```shell
codex config edit # open config in $EDITOR (or vi)
codex config set KEY VALUE # set a TOML literal, e.g. tui.auto_mount_repo true
```
### New `codex inspect-env` Command
Inspect the sandbox/container environment (mounts, permissions, network):
```shell
codex inspect-env --full-auto
codex inspect-env -s network=disable -s mount=/mydir=rw
```
### Resume TUI Sessions by UUID
```shell
codex session <SESSION_UUID>
```
### MCP Server (JSONRPC) Support
Launch Codex as an MCP _server_ over stdin/stdout and speak the
Model Context Protocol (JSON-RPC):
```shell
npx @modelcontextprotocol/inspector codex mcp
```
#### Sample JSONRPC Interaction
```jsonc
// ListTools request
{ "jsonrpc": "2.0", "id": 1, "method": "tools/list", "params": {} }
// CallTool request
{ "jsonrpc": "2.0", "id": 2, "method": "tools/call",
"params": { "name": "codex", "arguments": { "prompt": "Hello" } }
}
// CallTool response (abbreviated)
{ "jsonrpc": "2.0", "id": 2, "result": {
"content": [ { "type": "text", "text": "Hi there", "annotations": null } ],
"is_error": false
}}
```
---
## Configuration Changes
### `auto_allow` Predicate Scripts
Automatically approve or deny shell commands via custom scripts:
```toml
[[auto_allow]]
script = "/path/to/approve_predicate.sh"
[[auto_allow]]
script = "my_predicate --flag"
```
Vote resolution:
- A `deny` vote aborts execution.
- An `allow` vote auto-approves.
- Otherwise falls back to manual approval prompt.
### `base_instructions_override`
Override or disable the built-in system prompt (`prompt.md`):
```bash
export CODEX_BASE_INSTRUCTIONS_FILE=custom_prompt.md # use custom prompt
export CODEX_BASE_INSTRUCTIONS_FILE="" # disable base prompt
```
### TUI Configuration Options
In `~/.codex/config.toml`, under the `[tui]` table:
```toml
editor = "${VISUAL:-${EDITOR:-nvim}}" # external editor for prompt
message_spacing = true # insert blank line between messages
sender_break_line = true # sender label on its own line
```
---
## Core Library Updates
### System Prompt Composition Customization
System messages now combine:
1. Built-in prompt (`prompt.md`),
2. User instructions (`AGENTS.md`/`instructions.md`),
3. `apply-patch` tool instructions (for GPT-4.1),
4. User command/prompt.
Controlled via `CODEX_BASE_INSTRUCTIONS_FILE`.
### Chat Completions Tool Call Buffering
User turns emitted during an in-flight tool invocation are buffered
and flushed after the tool result, preventing interleaved messages.
### SandboxPolicy API Extensions
```rust
policy.allow_disk_write_folder("/path/to/folder".into());
policy.revoke_disk_write_folder("/path/to/folder");
```
### AutoApproval Predicate Engine
```rust
use codex_core::safety::{evaluate_auto_allow_predicates, AutoAllowVote};
let vote = evaluate_auto_allow_predicates(&cmd, &config.auto_allow);
match vote {
AutoAllowVote::Allow => /* auto-approve */,
AutoAllowVote::Deny => /* reject */,
AutoAllowVote::NoOpinion => /* prompt user */,
}
```
---
## TUI Improvements
### Double Ctrl+D Exit Confirmation
Prevent accidental exits by requiring two Ctrl+D within a timeout:
```rust
use codex_tui::confirm_ctrl_d::ConfirmCtrlD;
let mut confirm = ConfirmCtrlD::new(require_double, timeout_secs);
// confirm.handle(now) returns true to exit, false to prompt confirmation
```
### Markdown & Header Compact Rendering
New rendering options (code-level) for more compact chat layout:
- `markdown_compact`
- `header_compact`
---
## Documentation & Tests
- `codex-rs/config.md`, `codex-rs/README.md`, `core/README.md` updated with examples.
- New `core/init.md` guidance for generating `AGENTS.md` templates.
- Added tests for `codex config`, `ConfirmCtrlD`, and `evaluate_auto_allow_predicates`.

87
agentydragon/README.md Normal file
View File

@@ -0,0 +1,87 @@
# agentydragon
This file documents the changes introduced on the `agentydragon` branch
(off the `main` branch) of the codex repository.
## codex-rs: session resume and playback
- Added `session` subcommand to the CLI (`codex session <UUID>`) to resume TUI sessions by UUID.
- Integrated the `uuid` crate for session identifiers.
- Updated TUI (`codex-rs/tui`) to respect and replay previous session transcripts:
- Methods: `set_session_id`, `session_id`, `replay_items`.
- Load rollouts from `sessions/rollout-<UUID>.jsonl`.
- Printed resume command on exit: `codex session <UUID>`.
## codex-core enhancements
- Exposed core model types: `ContentItem`, `ReasoningItemReasoningSummary`, `ResponseItem`.
- Added `composer_max_rows` setting (with serde default) to TUI configuration.
## Dependency updates
- Added `uuid` crate to `codex-rs/cli` and `codex-rs/tui`.
## Pre-commit config changes
- Configured Rust build hook in `.pre-commit-config.yaml` to fail on warnings by setting `RUSTFLAGS="-D warnings"`.
## codex-rs/tui: Undo feedback decision with Esc key
- Pressing `Esc` in feedback-entry mode now cancels feedback entry and returns to the select menu, preserving the partially entered feedback text.
- Added a unit test for the ESC cancellation behavior in `tui/src/user_approval_widget.rs`.
## codex-rs/tui: restore inline mount DSL and slash-command dispatch
- Reintroduced logic in `ChatComposer` to dispatch `AppEvent::InlineMountAdd` and `AppEvent::InlineMountRemove` when `/mount-add` or `/mount-remove` is entered with inline arguments.
- Restored dispatch of `AppEvent::DispatchCommand` for slash commands selected via the command popup, including proper cleanup of the composer input.
## codex-rs/tui: slash-command `/edit-prompt` opens external editor
- Fixed slash-command `/edit-prompt` to invoke the configured external editor for prompt drafting (in addition to Ctrl+E).
## codex-rs/tui: display context remaining percentage
- Added module `tui/src/context.rs` with heuristics (`approximate_tokens_used`, `max_tokens_for_model`, `calculate_context_percent_remaining`).
- Updated `ChatWidget` and `ChatComposer::render_ref` to track history items and render `<N>% context left` indicator with color thresholds.
- Added unit tests in `tui/tests/context_percent.rs` for token counting and percent formatting boundary conditions.
## codex-rs/tui: compact Markdown rendering option
- Added `markdown_compact` config flag under UI settings to collapse heading-content spacing when enabled.
- When enabled, headings render immediately adjacent to content with no blank line between them.
- Updated Markdown rendering in chat UI and logs to honor compact mode globally (diffs, docs, help messages).
- Added unit tests covering H1H6 heading spacing for both compact and default modes.
## codex-rs: document MCP servers example in README
- Added an inline TOML snippet under “Model Context Protocol Support” in `codex-rs/README.md` showing how to configure external `mcp_servers` entries in `~/.codex/config.toml`.
- Documented `codex mcp` behavior: JSON-RPC over stdin/stdout, optional sandbox, no ephemeral container, default `codex` tool schema, and example ListTools/CallTool schema.
## Documentation tasks
## codex-rs/tui: interactive shell-command affordance via hotkey
- Bound `Ctrl+M` to open a ShellCommandView overlay for arbitrary container shell input.
- Toggled shell-command mode with `Ctrl+M` to enter or exit prompt, with styled border in shell mode.
- Executed commands asynchronously (`sh -c`) and recorded outputs inline in conversation history.
- Added unit tests for ShellCommandView event emission and shell-mode toggling behavior.
Tasks live under `agentydragon/tasks/` as individual Markdown files. Please update each tasks **Status** and **Implementation** sections in place rather than maintaining a static list here.
### Branch & Worktree Workflow
- **Branch convention**: work on each task in its own branch named `agentydragon-<task-id>-<task-slug>`, to avoid refname conflicts.
- **Worktree helper**: in `agentydragon/tasks/`, run:
-
- ```sh
- # Accept a full slug (NN-slug) or two-digit task ID (NN), optionally multiple; --tmux opens each in its own tmux pane and auto-commits each task as its Developer agent finishes:
- agentydragon/tools/create_task_worktree.py [--agent] [--tmux] [--interactive] [--shell] [--skip-presubmit] <task-slug|NN> [<task-slug|NN>...]
- ```
-
- Without `--agent`, this creates or reuses a worktree at
- `agentydragon/tasks/.worktrees/<task-id>-<task-slug>` off the `agentydragon` branch.
- Internally, the helper uses CoW hydration instead of a normal checkout: it registers the worktree with `git worktree add --no-checkout`, then performs a filesystem-level reflink
- of all files (macOS: `cp -cRp`; Linux: `cp --reflink=auto`), falling back to `rsync` if reflinks arent supported. This makes new worktrees appear nearly instantly on supported filesystems while
- preserving untracked files.
- With `--agent`, after setting up a new worktree it runs presubmit pre-commit checks (aborting with a clear message on failure unless `--skip-presubmit` is passed), then launches the Developer Codex agent (using `prompts/developer.md` and the task file).
- After the Developer agent exits, if the tasks **Status** is set to `Done`, it automatically runs the Commit agent helper to stage fixes and commit the work.
**Commit agent helper**: in `agentydragon/tasks/`, run:
```sh
# Generate and apply commit(s) for completed task(s) in their worktrees:
agentydragon/tools/launch_commit_agent.py <task-slug|NN> [<task-slug|NN>...]
```
After the Developer agent finishes and updates the task file, the Commit agent will write the commit message to a temporary file and then commit using that file (`git commit -F`). An external orchestrator can then stage files and run pre-commit hooks as usual. You do not need to run `git commit` manually.
---
*This README was autogenerated to summarize changes on the `agentydragon` branch.*

38
agentydragon/WORKFLOW.md Normal file
View File

@@ -0,0 +1,38 @@
# Agent Handoff Workflow
This document explains the multi-agent handoff pattern used for task development and commits
in the `agentydragon` workspace. It consolidates shared guidance so individual agent prompts
do not need to repeat these details.
## 1. Developer Agent
- **Scope**: Runs inside a sandboxed git worktree for a single task branch (`agentydragon-<ID>-<slug>`).
- **Actions**:
1. If the tasks **Status** is `Needs input`, stop immediately and await further instructions; do **not** implement code changes or run pre-commit hooks.
2. Update the task Markdown files **Status** to `Done` when implementation is complete.
3. Implement the code changes for the task.
4. Run `pre-commit run --files $(git diff --name-only)` to apply and stage any autofix changes.
5. **Do not** run `git commit`.
## 2. Commit Agent
- **Scope**: Runs in the sandbox (read-only `.git`) or equivalent environment.
- **Actions**:
1. Emit exactly one line to stdout: the commit message prefixed `agentydragon(tasks): `
summarizing the tasks **Implementation** section.
2. Stop immediately.
## 3. Orchestrator
- **Scope**: Outside the sandbox with full Git permissions.
- **Actions**:
1. Stage all changes: `git add -u`.
2. Run `pre-commit run --files $(git diff --name-only --cached)`.
3. Read the commit message and run `git commit -m "$MSG"`.
## 4. Status & Launch
- Use `agentydragon_task.py status` to view tasks (including those in `.done/`).
- Summaries:
- **Merged:** tasks with no branch/worktree.
- **Ready to merge:** tasks marked Done with branch commits ahead.
- **Unblocked:** tasks with no outstanding dependencies.
- The script also prints a `agentydragon/tools/create_task_worktree.py --agent --tmux <IDs>` command for all unblocked tasks.
This guide centralizes the handoff workflow for all agents.

View File

@@ -0,0 +1,16 @@
## Commit Agent Prompt
Refer to `agentydragon/WORKFLOW.md` for the overall Developer→Commit→Orchestrator handoff workflow.
You are the **Commit** Codex agent for the `codex` repository. Your job is to stage and commit the changes made by the Developer agent.
Your sole responsibility is to generate the Git commit message on stdout.
Do **not** modify any files or run Git commands; this agent must remain sandbox-friendly.
When you run, **output exactly** the desired commit message (with no extra commentary) on stdout. The message must:
- Be prefixed with `agentydragon(tasks): `
- Concisely summarize the work performed as described in the tasks **Implementation** section.
Stop immediately after emitting the commit message. An external orchestrator will stage, run hooks, and commit using this message.
Below, you will get the task description the agent got. But still verify that the agent actually did what it was supposed to, and adjust the commit message according to what is actually implemented, DO NOT just copy what's in the task file.

View File

@@ -0,0 +1,24 @@
## Developer Agent Prompt
Refer to `agentydragon/WORKFLOW.md` for the overall Developer→Commit→Orchestrator handoff workflow.
You are the **Developer** Codex agent for the `codex` repository. You are running inside a dedicated git worktree for a single task branch.
Use the task Markdown file under `agentydragon/tasks/` as your progress tracker: update its **Status** and **Implementation** sections to record your progress.
Before making any changes, read the task definition in `agentydragon/tasks/` and note that its **Status** and **Implementation** sections are placeholders.
After reviewing, update the tasks **Status** to "In progress" and fill in the **Implementation** section with your planned approach.
If the **Implementation** section is blank or does not describe your intended design and steps, populate it with a concise highlevel plan before proceeding.
Then proceed directly to implement the full functionality in the codebase as a single atomic unit—regardless of how many components are involved, do not split the work into separate sub-steps or pause to ask whether to decompose it.
Do not pause to seek user confirmation after editing the Markdown;
only ask clarifying questions if you encounter genuine ambiguities in the requirements.
At any point, you may set the tasks **Status** to any valid state (e.g. Not started, In progress, Needs input, Needs manual review, Done, Cancelled) as appropriate. Use **Needs input** to request further clarification or resources before proceeding.
When you have finished working on the task file:
- If the tasks **Status** is "Needs input", stop immediately and await further instructions; do **not** run pre-commit hooks or invoke the Commit agent.
- Otherwise, set the tasks **Status** to "Done".
- Run the repositorys pre-commit hooks on all changed files (e.g. `pre-commit run --files <changed-files>`), and stage any autofix changes.
- Do **not** stage or commit beyond hook-driven fixes. Instead, stop and await the Commit agent to record your updates.
Then stop and await further instructions.

View File

@@ -0,0 +1,54 @@
# Project Manager Agent Prompt
You are the **Project Manager** Codex agent for the `codex` repository.
Refer to `agentydragon/WORKFLOW.md` for the standard Developer→Commit→Orchestrator handoff workflow.
Your responsibilities include:
- **Reading documentation**: Load and understand all relevant docs in this repo (especially those defining task, worktree, and branch conventions, as well as each task file and toplevel README files).
- **Task orchestration**: Maintain the list of tasks, statuses, and dependencies; plan waves of work; and generate commands to launch work in parallel using `agentydragon/tools/create_task_worktree.py` (or the legacy `agentydragon/tools/create-task-worktree.sh`) with `--agent` and `--tmux`.
- **Task creation**: When creating a new task stub, review the descriptions of all existing tasks; set the `dependencies` front-matter field to list the tasks that must be completed before work on this task can begin; and include a brief rationale as a Markdown comment (e.g., `<!-- rationale: depends on tasks X and Y because ... -->`) explaining why these dependencies are required and why other tasks are not.
- **Live coordination**: Continuously monitor and report progress, adjust the plan as tasks complete or new ones appear, and surface any blockers.
- **Worktree monitoring**: Check each tasks worktree for uncommitted changes or dirty state to detect agents still working or potential crashes, and report their status as in-progress or needing attention.
- When displaying the task-status table, highlight dirty worktrees in red and tasks marked Done or Merged in green; exclude tasks that are Merged with no branch and no worktree from the main table (they should instead be listed in a green “Done & merged:” summary at the bottom), and filter such merged tasks out of other tasks dependency lists.
- **Background polling**: On user request, enter a sleepandscan loop (e.g. 5min interval) to detect tasks marked “Done” in their Markdown; for each completed task, review its branch worktree, check for merge conflicts, propose merging cleanly mergeable branches, and suggest conflictresolution steps for any that arent cleanly mergeable.
- **Manager utilities**: Create and maintain utility scripts under `agentydragon/tools/manager_utils/` to support your work (e.g., branch scanning, conflict checking, merge proposals, polling loops). Include clear documentation (header comments or docstrings with usage examples) in each script, and invoke these scripts in your workflow.
- **Merge orchestration**: When proposing merges of completed task branches into the integration branch, consider both single-branch and octopus (multi-branch) merges. Detect and report conflicts between branches as well as with the integration branch, and recommend resolution steps or merge ordering to avoid or resolve conflicts.
### First Actions
1. For each task branch (named `agentydragon-<task-id>-<task-slug>`), **without changing the current working directorys Git HEAD or modifying its status**, create or open a dedicated worktree for that branch (e.g. via `agentydragon/tools/create_task_worktree.py <task-slug>`) and read the tasks Markdown copy in that worktree to extract and list the task number, title, live **Status**, and dependencies. *(Always read the **Status** and dependencies from the copy of the task file in the branchs worktree, never from master/HEAD.)*
2. Produce a oneline tmux launch command to spin up only those tasks whose dependencies are satisfied and can actually run in parallel, following the conventions defined in repository documentation.
3. Describe the highlevel wavebywave plan and explain which tasks can run in parallel.
### Usage Examples
```bash
# Parallel worktree launch
agentydragon/tools/create_task_worktree.py --agent --tmux 02 04 07
# Wave-by-wave plan
# Wave 1: tasks 02,04 (no unmet deps)
# Wave 2: task 07 (depends on 02,04)
# Background polling loop (every 5 min)
while true; do
python3 agentydragon/tools/check_tasks.py && \
python3 agentydragon/tools/launch_commit_agent.py $(python3 agentydragon/tools/find_done_tasks.py)
sleep 300
done
# Dispose a task worktree
python3 agentydragon/tools/manager_utils/agentydragon_task.py dispose 07
```
More functionality and refinements will be added later. Begin by executing these steps and await further instructions.
*If instructed, enter a background polling loop (sleep for a configured interval, e.g. 5minutes) to watch for tasks whose Markdown status is updated to “Done” and then prepare review/merge steps for only those branches.*
Once a task branch is merged cleanly into the integration branch, dispose of its worktree and delete its Git branch. To record that merge, use:
python3 agentydragon/tools/manager_utils/agentydragon_task.py set-status <task-id> Merged
Use `python3 agentydragon/tools/manager_utils/agentydragon_task.py dispose <task-id>` to remove the worktree and branch without changing the status (e.g. for cancelled tasks).

View File

@@ -0,0 +1,5 @@
Read the full diff between HEAD and main and produce a list of everything that was added/removed.
Include examples of how to use the features, how to configure them, etc.
Use Markdown format. Write into $(git rev-parse --show-toplevel)/agentydragon/CHANGES.md. Delete it if it already exists.
Only document changes under codex-rs.
Do not include things that already exist on main branch - only what was changed.

View File

@@ -0,0 +1,4 @@
read the description of all tasks in agentydragon/tasks/*.md and relevant context in codex-rs. for every task: disregard existing dependecy declarations in the frontmatter. think long about
why and how they might depend on each other and if there's any way they might conflict and whether the overall picturen of how they fit toether makes sense. for each, *REGENERATE* the
dependency list in frontmatter to the list of tasks the muast be done before each gvien taks becomes unblocked. no need to populate this for already merged tasks. also no need to list
merged tasks inside any dependency list.

View File

@@ -0,0 +1,66 @@
You are the AI “Scaffolding Assistant” for the `codex` monorepo. Your mission is to generate, in separate commits, all of the initial scaffolding needed for the
tydragon-driven task workflow:
1. **Task stubs**
- Create `agentydragon/tasks/task-template.md`.
- Create numbered task stubs (`01-*.md`, `02-*.md`, …) for each planned feature (mounting, approval predicates, livereload, editor integration, etc.), filling in
e, “Status”, “Goal”, and sections for “Acceptance Criteria”, “Implementation”, and “Notes”.
2. **Worktree launcher**
- Implement `agentydragon/tools/create_task_worktree.py` with:
- `--agent` mode to spin up a Codex agent in the worktree,
- `--tmux` to tile panes for multiple tasks in a single tmux session,
- twodigit or slug ID resolution.
- Ensure usage, help text, and numeric/slug handling are correct.
3. **Helper scripts**
- Add `agentydragon/tasks/review-unmerged-task-branches.sh` to review and merge task branches.
- Add `agentydragon/tools/launch-project-manager.sh` to invoke the Project Manager agent prompt.
4. **Projectmanager prompts**
- Create `agentydragon/prompts/manager.md` containing the following Project Manager agent prompt:
```
# Project Manager Agent Prompt
You are the **Project Manager** Codex agent for the `codex` repository. Your responsibilities include:
- **Reading documentation**: Load and understand all relevant docs in this repo (especially those defining task, worktree, and branch conventions, as well as each task file and toplevel README files).
- **Task orchestration**: Maintain the list of tasks, statuses, and dependencies; plan waves of work; and generate shell commands to launch work on tasks in parallel using `create_task_worktree.py` with `--agent` and `--tmux`.
- **Live coordination**: Continuously monitor and report progress, adjust the plan as tasks complete or new ones appear, and surface any blockers.
- **Worktree monitoring**: Check each tasks worktree for uncommitted changes or dirty state to detect agents still working or potential crashes, and report their status as in-progress or needing attention.
- **Background polling**: On user request, enter a sleepandscan loop (e.g. 5min interval) to detect tasks marked “Done” in their Markdown; for each completed task, review its branch worktree, check for merge conflicts, propose merging cleanly mergeable branches, and suggest conflictresolution steps for any that arent cleanly mergeable.
- **Manager utilities**: Create and maintain utility scripts under `agentydragon/tools/manager_utils/` to support your work (e.g., branch scanning, conflict checking, merge proposals, polling loops). Include clear documentation (header comments or docstrings with usage examples) in each script, and invoke these scripts in your workflow.
- **Merge orchestration**: When proposing merges of completed task branches into the integration branch, consider both single-branch and octopus (multi-branch) merges. Detect and report conflicts between branches as well as with the integration branch, and recommend resolution steps or merge ordering to avoid or resolve conflicts.
### First Actions
1. For each task branch (named `agentydragon-<task-id>-<task-slug>`), **without changing the current working directorys Git HEAD or modifying its status**, create or open a dedicated worktree for that branch (e.g. via `create_task_worktree.py <task-slug>`) and read the tasks Markdown copy under that worktrees `agentydragon/tasks/` to extract and list the task number, title, live **Status**, and dependencies. *(Always read the **Status** and dependencies from the copy of the task file in the branchs worktree, never from master/HEAD.)*
2. Produce a oneline tmux launch command to spin up only those tasks whose dependencies are satisfied and can actually run in parallel, following the conventions defined in repository documentation.
3. Describe the highlevel wavebywave plan and explain which tasks can run in parallel.
More functionality and refinements will be added later. Begin by executing these steps and await further instructions.
```
5. **Wavebywave plan**
- Draft a humanreadable plan outlining task dependencies and four “waves” of work, indicating which tasks can run in parallel.
6. **Bootstrap commands**
- Provide concrete shell/`rg`/`tmux` oneliner examples to launch Wave1 (e.g. tasks 06, 03, 08) in parallel.
- Provide a single tmux oneliner to spin up all unblocked tasks.
**Before you begin**, read the existing docs under `agentydragon/tasks/`, toplevel `README.md` and `oaipackaging/README.md` so you fully understand the context and
entions.
**Commit strategy**
- Commit each major component (tasks, script, helper scripts, prompts, plan) as its own Git commit.
- Follow our existing commit-message style: prefix with `agentydragon(tasks):`, `agentydragon:`, etc.
- Dont batch everything into one huge commit; keep each logical piece isolated for easy review.
**Reporting**
After each commit, print a short status message (e.g. “✅ Task stubs created”, “✅ create_task_worktree.py implemented”, etc.) and await confirmation before continuing
the next step.
---
Begin now by listing the current task directory contents and generating `task-template.md`.

View File

@@ -0,0 +1 @@
# Keep this directory in version control

View File

@@ -0,0 +1,64 @@
+++
id = "01"
title = "Dynamic Mount-Add and Mount-Remove Commands"
status = "Merged"
dependencies = ""
last_updated = "2025-06-25T01:40:09.501150"
+++
# Task 01: Dynamic Mount-Add and Mount-Remove Commands
> *This task is specific to codex-rs.*
## Status
**General Status**: Merged
**Summary**: Implemented inline DSL and interactive dialogs for `/mount-add` and `/mount-remove`, with dynamic sandbox policy updates.
## Goal
Implement the `/mount-add` and `/mount-remove` slash commands in the TUI, supporting two modes:
1. **Inline DSL**: e.g. `/mount-add host=/path/to/host container=/path/in/agent mode=rw`
2. **Interactive dialog**: if the user just types `/mount-add` or `/mount-remove` without args, pop up a prompt to fill in `host`, `container`, and optional `mode` fields.
These commands should:
- Create or remove symlinks (or real directories) under the current working directory.
- Update the in-memory `SandboxPolicy` to grant or revoke read/write permission for the host path.
- Emit confirmation or error messages into the TUI log pane.
## Acceptance Criteria
- Users can type `/mount-add host=... container=... mode=...` and the mount is created immediately.
- Users can type `/mount-add` alone to open a small TUI form prompting for the three fields.
- Symmetrically for `/mount-remove` by container path.
- The `sandbox_policy` is updated so subsequent shell commands can read/write the newly mounted folder.
## Implementation
**How it was implemented**
- Added two new slash commands (`mount-add`, `mount-remove`) to the TUIs `slash-command` popup.
- Inline DSL parsing: commands typed as `/mount-add host=... container=... mode=...` or `/mount-remove container=...` are detected and handled immediately by parsing key/value args, performing the mount/unmount, and updating the `Config.sandbox_policy` in memory.
- Interactive dialogs: selecting `/mount-add` or `/mount-remove` without args opens a bottompane form (`MountAddView` or `MountRemoveView`) that prompts sequentially for the required fields and then triggers the same mount logic.
- Mount logic implemented in `do_mount_add`/`do_mount_remove`:
- Creates/removes a symlink under `cwd` pointing to the host path (`std::os::unix::fs::symlink` on Unix, platform equivalents on Windows).
- Uses new `SandboxPolicy` methods (`allow_disk_write_folder`/`revoke_disk_write_folder`) to grant or revoke `DiskWriteFolder` permissions for the host path.
- Emits success or error messages via `tracing::info!`/`tracing::error!`, which appear in the TUI log pane.
**How it works**
1. **Inline DSL**
- User types:
```
/mount-add host=/path/to/host container=path/in/cwd mode=ro
```
- The first-stage popup intercepts the mount-add command with args, dispatches `InlineMountAdd`, and the app parses the args and runs the mount logic immediately.
2. **Interactive dialog**
- User types `/mount-add` (or selects it via the popup) without args.
- A small form appears that prompts for `host`, `container`, then `mode`.
- Upon completion, the same mount logic runs.
3. **Unmount**
- `/mount-remove container=...` (inline) or `/mount-remove` (interactive) remove the symlink and revoke write permissions.
4. **Policy update**
- `allow_disk_write_folder` appends a `DiskWriteFolder` permission for new mounts.
- `revoke_disk_write_folder` removes the corresponding permission on unmount.
## Notes
- This builds on the static `[[sandbox.mounts]]` support introduced earlier.

View File

@@ -0,0 +1,42 @@
+++
id = "03"
title = "Live Config Reload and Prompt on Changes"
status = "Merged"
dependencies = "02,07,09,11,14,29"
last_updated = "2025-06-25T05:36:17.783726"
+++
# Task 03: Live Config Reload and Prompt on Changes
> *This task is specific to codex-rs.*
## Status
**General Status**: Done
**Summary**: Live config watcher, diff prompt, and reload integration implemented.
## Goal
Detect changes to the user `config.toml` file while a session is running and prompt the user to apply or ignore the updated settings.
## Acceptance Criteria
- A background file watcher watches `$CODEX_HOME/config.toml` (or active user config path).
- On any write event, compute a unified diff between the in-memory config and the on-disk file.
- Pause the agent, display the diff in the TUI bottom pane, and offer two actions: `Apply new config now` or `Continue with old config`.
- If the user applies, re-parse the config, merge overrides, and resume using the new settings. Otherwise, discard changes and resume.
## Implementation
**How it was implemented**
- Added `codex_tui::config_reload::generate_diff` to compute unified diffs via the `similar` crate (with a unit test).
- Spawned a `notify`-based filesystem watcher thread in `tui::run_main` that debounces write events on `$CODEX_HOME/config.toml`, generates diffs against the last-read contents, and posts `AppEvent::ConfigReloadRequest(diff)`.
- Introduced `AppEvent` variants (`ConfigReloadRequest`, `ConfigReloadApply`, `ConfigReloadIgnore`) and wired them in `App::run` to display a new `BottomPaneView` overlay.
- Created `BottomPaneView` implementation `ConfigReloadView` to render the diff and handle `<Enter>`/`<Esc>` for apply or ignore.
- On apply, reloaded `Config` via `Config::load_with_cli_overrides`, updated both `App.config` and `ChatWidget` (rebuilding its bottom pane with updated settings).
**How it works**
- The watcher thread detects on-disk changes and pushes a diff request into the UI event loop.
- Upon `ConfigReloadRequest`, the TUI bottom pane overlays the diff view and blocks normal input.
- `<Enter>` applies the new config (re-parses and updates runtime state); `<Esc>` dismisses the overlay and continues with the old settings.
## Notes
- Leverage a crate such as `notify` for FS events and `similar` or `diff` for unified diff generation.

View File

@@ -0,0 +1,42 @@
+++
id = "06"
title = "External Editor Integration for Prompt Entry"
status = "Merged"
dependencies = "02,07,09,11,14,29"
last_updated = "2025-06-25T02:40:09.505778"
+++
# Task 06: External Editor Integration for Prompt Entry
> *This task is specific to codex-rs.*
## Status
**General Status**: Done
**Summary**: External editor integration for prompt entry implemented.
## Goal
Allow users to spawn an external editor (e.g. Neovim) to compose or edit the chat prompt. The prompt box should update with the editor's contents when closed.
## Acceptance Criteria
- A slash command `/edit-prompt` (or `Ctrl+E`) launches the user's preferred editor on a temporary file pre-populated with the current draft.
- Upon editor exit, the draft is re-read into the composer widget.
- Configurable via `editor = "${VISUAL:-${EDITOR:-nvim}}"` setting in `config.toml`.
## Implementation
**How it was implemented**
- Added `editor` option to `[tui]` section in `config.toml`, defaulting to `${VISUAL:-${EDITOR:-nvim}}`.
- Exposed the `tui.editor` setting in the `codex-core` config model (`config_types.rs`) and wired it through to the TUI.
- Added a new slash-command variant `EditPrompt` in `tui/src/slash_command.rs` to trigger external-editor mode.
- Implemented `ChatComposer::open_external_editor()` in `tui/src/bottom_pane/chat_composer.rs`:
- Creates a temporary file pre-populated with the current draft prompt.
- Launches the configured editor (from `VISUAL`/`EDITOR` with `nvim` fallback) in a blocking subprocess.
- Reads the edited contents back into the `TextArea` on editor exit.
- Wired both `Ctrl+E` and the `/edit-prompt` slash command to invoke `open_external_editor()`.
- Updated `config.md` to document the new `editor` setting under `[tui]`.
**How it works**
- Pressing `Ctrl+E`, or typing `/edit-prompt` and hitting Enter, spawns the user's preferred editor on a temporary file containing the current draft.
- When the editor process exits, the plugin reads back the file and updates the chat composer with the edited text.
- The default editor is determined by `VISUAL`, then `EDITOR`, falling back to `nvim` if neither is set.

View File

@@ -0,0 +1,37 @@
+++
id = "07"
title = "Undo Feedback Decision with Esc Key"
status = "Merged"
dependencies = "01,04,10,12,16,17"
last_updated = "2025-06-25T01:40:09.506146"
+++
# Task 07: Undo Feedback Decision with Esc Key
> *This task is specific to codex-rs.*
## Status
**General Status**: Merged
**Summary**: ESC key now cancels feedback entry and returns to the select menu, preserving any entered text; implementation and tests added.
## Goal
Enhance the user-approval dialog so that if the user opted to leave feedback (“No, enter feedback”) they can press `Esc` to cancel the feedback flow and return to the previous approval choice menu (e.g. “Yes, proceed” vs. “No, enter feedback”).
## Acceptance Criteria
- While the feedback-entry textarea is active, pressing `Esc` closes the feedback editor and reopens the yes/no confirmation dialog.
- The cancellation must restore the dialog state without losing any partially entered feedback text.
## Implementation
**How it was implemented**
- In `tui/src/user_approval_widget.rs`, updated `UserApprovalWidget::handle_input_key` so that pressing `Esc` in input mode switches `mode` back to `Select` (rather than sending a deny decision), and restores `selected_option` to the feedback entry item without clearing the input buffer.
- Added a unit test in the same module to verify that `Esc` cancels input mode, preserves the feedback text, and does not emit any decision event.
**How it works**
- When the widget is in `Mode::Input` (feedback-entry), receiving `KeyCode::Esc` resets `mode` to `Select` and sets `selected_option` to the index of the “Edit or give feedback” option.
- The `input` buffer remains intact, so any partially typed feedback is preserved for if/when the user re-enters feedback mode.
- No approval decision is sent on `Esc`, so the modal remains active and the user can still approve, deny, or re-enter feedback.
## Notes
- Changes in `tui/src/user_approval_widget.rs` to treat `Esc` in input mode as a cancel-feedback action and added corresponding tests.

View File

@@ -0,0 +1,52 @@
+++
id = "08"
title = "Set Shell Title to Reflect Session Status"
status = "Merged"
dependencies = "02,07,09,11,14,29"
last_updated = "2025-06-25T04:06:55.265790"
+++
# Task 08: Set Shell Title to Reflect Session Status
> *This task is specific to codex-rs.*
## Status
**General Status**: Done
**Summary**: Implemented session title persistence, `/set-title` slash command, and real-time ANSI updates in both TUI and exec clients.
## Goal
Allow the CLI to update the terminal title bar to reflect the current session status—executing, thinking (sampling), idle, or waiting for approval decision—and persist the title with the session. Users should also be able to explicitly set a custom title.
## Acceptance Criteria
- Implement a slash command or API (`/set-title <title>`) for users to explicitly set the session title.
- Persist the title in session metadata so that on resume the last title is restored.
- Dynamically update the shell/terminal title in real time based on session events:
- Executing: use a play symbol (e.g. ▶)
- Thinking/sampling: use an hourglass or brain symbol (e.g. ⏳)
- Idle: use a green dot or sleep symbol (e.g. 🟢)
- Waiting for approval decision: use an attention-grabbing symbol (e.g. ❗)
- Ensure title updates work across Linux, macOS, and Windows terminals via ANSI escape sequences.
## Implementation
**Note**: Populate this section with a concise high-level plan before beginning detailed implementation.
**Planned approach**
- Extend the session protocol schema (`SessionConfiguredEvent`) in `codex-rs/core` to include an optional `title` field and introduce a new `SessionUpdatedTitleEvent` type.
- Add a `SetTitle { title: String }` variant to the `Op` enum for custom titles and implement the `/set-title <text>` slash command in the TUI crates (`tui/src/slash_command.rs`, `tui/src/app_event.rs`, and `tui/src/app.rs`).
- Modify the core agent loop to handle `Op::SetTitle`: persist the new title in session metadata, emit a `SessionUpdatedTitleEvent`, and include the persisted title in `SessionConfiguredEvent` on startup/resume.
- Implement event listeners in both the interactive TUI (`tui/src/chatwidget.rs`) and non-interactive exec client (`exec/src/event_processor.rs`) that respond to session, title, and lifecycle events (session start, task begin/end, reasoning, idle, approval) by emitting ANSI escape sequences (`\x1b]0;<symbol> <title>\x07`) to update the terminal title bar.
- Choose consistent Unicode symbols for each session state—executing (▶), thinking (⏳), idle (🟢), awaiting approval (❗)—and apply these as status indicators prefixed to the title.
- On session startup or resume, restore the last persisted title or fall back to a default if none exists.
**How it works**
- Users type `/set-title MyTitle` to set a custom session title; the core persists it and broadcasts a `SessionUpdatedTitleEvent`.
- Clients print the appropriate ANSI escape code to update the terminal title before rendering UI or logs, reflecting real-time session state via the selected status symbol prefix.
## Notes
- Use ANSI escape code `\033]0;<title>\007` to set the terminal title.
- Extend the session JSON schema to include a `title` field.
- Select Unicode symbols that render consistently in common terminal fonts.

View File

@@ -0,0 +1,52 @@
+++
id = "10"
title = "Inspect Container State (Mounts, Permissions, Network)"
status = "Merged"
dependencies = ""
last_updated = "2025-06-25T04:07:56.197523"
+++
# Task 10: Inspect Container State (Mounts, Permissions, Network)
> *This task is specific to codex-rs.*
## Status
**General Status**: Completed
**Summary**: Implemented `codex inspect-env` subcommand, CLI output and TUI bindings, tested in sandbox and headless modes.
## Goal
Provide a runtime command that displays the current sandbox/container environment details—what is mounted where, permission scopes, network access status, and other relevant sandbox policies.
## Acceptance Criteria
- Implement a slash command or CLI subcommand (`/inspect-env` or `codex inspect-env`) that outputs:
- List of bind mounts (host path → container path, mode)
- File-system permission policies in effect
- Network sandbox status (restricted or allowed)
- Runtime TUI statusbar indicators for key sandbox attributes (e.g. network enabled/disabled, mount count, read/write scopes)
- Any additional sandbox rules or policy settings applied
- Format the output in a human-readable table or tree view in the TUI and plaintext for logs.
- Ensure the command works in both interactive TUI sessions and non-interactive (headless) modes.
- Include a brief explanation header summarizing each section to help users understand what they are seeing.
## Implementation
**How it was implemented**
Implemented a new `inspect-env` subcommand in `codex-cli`, reusing `create_sandbox_policy` and `Config::load_with_cli_overrides` to derive the effective sandbox policy and working directory. The code computes read-only or read-write mount entries (root and writable roots), enumerates granted `SandboxPermission`s, and checks `has_full_network_access()`. It then prints a formatted table (via `println!`) and summary counts.
**How it works**
Running `codex inspect-env` loads user overrides, builds the sandbox policy, and:
- Lists mounts (path and mode) in a table.
- Prints each granted permission.
- Shows network status as `enabled`/`disabled`.
- Outputs summary counts for mounts and writable roots.
This command works both in CI/headless and inside the TUI (status-bar integration).
## Notes
- Leverage existing sandbox policy data structures used at startup.
- Reuse TUI table or tree components for formatting (e.g., tui-rs widgets).
- Include clear labels for network status (e.g., `NETWORK: disabled` or `NETWORK: enabled`).

View File

@@ -0,0 +1,61 @@
+++
id = "11"
title = "User-Configurable Approval Predicates"
status = "Merged"
dependencies = "01,04,10,12,16,17"
last_updated = "2025-06-25T01:40:09.508560"
+++
# Task 11: User-Configurable Approval Predicates
> *This task is specific to codex-rs.*
## Status
**General Status**: Merged
**Summary**: Implemented custom approval predicates feature: configuration parsing, predicate invocation logic, tests, and documentation.
## Goal
Allow users to plug in an external executable that makes approval decisions for shell commands based on session context.
## Acceptance Criteria
- Support a new `[[approval_predicates]]` section in `config.toml` for Python-based predicates, each with a `python_predicate_binary = "..."` field (pointing to the predicate executable) and an implicit `never_expire = true` setting.
- Before prompting the user, invoke each configured predicate in order, passing the following (via CLI args or env vars):
- Session ID
- Container working directory (CWD)
- Host working directory (CWD)
- Candidate shell command string
- The predicate must print exactly one of `allow`, `deny`, or `ask` on stdout:
- `allow` → auto-approve and skip remaining predicates
- `deny` → auto-reject and skip remaining predicates
- `ask` → open the standard approval dialog and skip remaining predicates
- If a predicate exits non-zero or outputs anything else, treat it as `ask` and continue to the next predicate.
- Write unit and integration tests covering typical and edge-case predicate behavior.
- Document configuration syntax and behavior in the top-level config docs (`config.md`).
## Implementation
**How it was implemented**
- Added `approval_predicates` field to `ConfigToml` and `Config` in `codex_core::config`, supporting a `python_predicate_binary: PathBuf` and an implicit `never_expire = true`.
- Hooked into the command-approval code path in `codex_core::safety` to invoke each configured predicate executable before showing the approval prompt. Predicates are launched via `std::process::Command` with context passed in environment variables (`CODEX_SESSION_ID`, `CODEX_CONTAINER_CWD`, `CODEX_HOST_CWD`, `CODEX_COMMAND`).
- Parsed each predicates stdout for exactly `allow`, `deny`, or `ask`, short-circuiting on `allow` or `deny` (auto-approve/auto-reject) and treating failures or unexpected output as `ask` to continue to the next predicate.
- Wrote unit tests for configuration parsing and predicate-invocation behavior, covering exit-code and output edge cases, plus integration tests verifying end-to-end approval decisions.
- Updated `config.md` to document the `[[approval_predicates]]` table syntax, default semantics, and runtime behavior.
**How it works**
When a shell command requires approval, Codex iterates over each entry in `[[approval_predicates]]` in order. For each predicate:
- Launch the configured binary with session context in its environment.
- If it exits successfully and writes `allow`, Codex auto-approves and skips remaining predicates.
- If it writes `deny`, Codex auto-rejects and skips remaining predicates.
- Otherwise (writes `ask`, fails, or emits unexpected output), Codex moves to the next predicate or falls back to the manual approval dialog if none return `allow` or `deny`.
This mechanism lets users automate approval decisions via custom Python scripts while retaining manual control when predicates defer.
## Notes
- Consider passing context via environment variables (e.g. `CODEX_SESSION_ID`, `CODEX_CONTAINER_CWD`, `CODEX_HOST_CWD`, `CODEX_COMMAND`).
- Reuse invocation logic from the auto-approval predicates feature (Task 02).
- **Motivating example**: auto-approve `pre-commit run --files <any number of space-separated files>`.
- **Motivating example**: auto-approve any `git` command (e.g. `git add`, `git commit`, `git push`, `git status`, etc.) provided its repository root is under `<directory>`, correctly handling common flags and safe invocation modes.
- **Motivating example**: auto-approve any shell pipeline composed out of `<these known-safe commands>` operating on `<known-safe files>` with `<known-safe params>`, using a general pipeline parser to ensure safety—a nontrivial example of predicate logic.

View File

@@ -0,0 +1,45 @@
+++
id = "13"
title = "Interactive Prompting and Commands While Executing"
status = "Merged"
dependencies = "02,07,09,11,14,29"
last_updated = "2025-06-25T01:40:09.509881"
+++
# Task 13: Interactive Prompting and Commands While Executing
> *This task is specific to codex-rs.*
## Status
**General Status**: Merged
**Summary**: Implemented interactive prompt overlay allowing user input during streaming without aborting runs.
## Goal
Allow users to interleave composing prompts and issuing slash-commands while the agent is actively executing (e.g. streaming completions), without aborting the current run.
## Acceptance Criteria
- While the LLM is streaming a response or executing a tool, the input box remains active for user edits and slash-commands.
- Sending a message or `/`-command does not implicitly cancel or abort the ongoing execution.
- Any tool invocation messages from the agent must still be immediately followed by their corresponding tool output messages (or the API will error).
- Ensure the TUI correctly preserves the stream and appends new user input at the bottom, scrolling as needed.
- No deadlocks or lost events if the agent finishes while the user is typing; buffer and render properly.
- Update tests to simulate concurrent user input during streaming and validate UI state.
## Implementation
**How it was implemented**
- Modified `BottomPane::handle_key_event` in `tui/src/bottom_pane/mod.rs` to special-case the `StatusIndicatorView` while `is_task_running`, forwarding key events to `ChatComposer` and preserving the overlay.
- Updated `BottomPane::render_ref` to always render the composer first and then overlay the active view, ensuring the input box remains visible and editable under the status indicator.
- Added unit tests in `tui/src/bottom_pane/mod.rs` to verify input is forwarded during task execution and that the status indicator overlay is removed upon task completion.
**How it works**
During LLM streaming or tool execution, the `StatusIndicatorView` remains active as an overlay. The modified event handler detects this overlay and forwards user key events to the underlying `ChatComposer` without dismissing the overlay. On task completion (`set_task_running(false)`), the overlay is automatically removed (via `should_hide_when_task_is_done`), returning to the normal input-only view.
## Notes
- Look at the ChatComposer and streaming loop in `tui/src/bottom_pane/chat_composer.rs` for input and stream handling.
- Ensure event loop in `app.rs` multiplexes between agent stream events and user input events without blocking.
- Consider locking or queuing tool-use messages to guarantee prompt tool-output pairing.

View File

@@ -0,0 +1,95 @@
+++
id = "15"
title = "Agent Worktree Sandbox Configuration"
status = "Merged"
dependencies = "02,07,09,11,14,29"
last_updated = "2025-06-25T07:26:13.570520"
+++
# Task 15: Agent Worktree Sandbox Configuration
## Status
**General Status**: Done
**Summary**: Enhanced the task scaffolding script to launch a Codex agent in a sandboxed worktree with writable worktree and TMPDIR, auto-approved file I/O and Git operations, and network disabled.
## Goal
Use `create-task-worktree.sh --agent` to wrap the agent invocation in a sandbox with these properties:
- The task worktree path and the system temporary directory (`$TMPDIR` or `/tmp`) are mounted read-write.
- All other paths on the host are treated as read-only.
- Git operations in the worktree (e.g. `git add`, `git commit`) succeed without additional confirmation.
- Any file read or write under the worktree root is automatically approved.
## Acceptance Criteria
The `create-task-worktree.sh --agent` invocation:
- launches the agent via `codex debug landlock` (or equivalent), passing flags to mount only the worktree and tempdir as writable.
- sets up Landlock permissions so that all other host paths are read-only.
- auto-approves any file system operation under the worktree directory.
- auto-approves Git commands in the worktree without prompting.
- still permits using system temp dir for ephemeral files.
- contains tests or manual verifications demonstrating blocked writes outside and allowed writes inside.
## Implementation
**How it was implemented**
- Extended `create-task-worktree.sh` `--agent` mode to launch the Codex agent under a Landlock+seccomp sandbox by invoking `codex debug landlock --full-auto`, which grants write access only to the worktree (`cwd`) and the platform temp folder (`TMPDIR`), and disables network.
- Updated the `-a|--agent` help text to reflect the new sandbox behavior and tempdir whitelist.
- Added a test script demonstrating allowed writes inside the worktree and TMPDIR and blocked writes to directories outside those paths:
```bash
#!/usr/bin/env bash
# Test script for Task 15: verify sandbox restrictions and allowances
set -euo pipefail
worktree_root="$(cd "$(dirname "$0")"/.. && pwd)"
echo "Running sandbox tests in worktree: $worktree_root"
# Test write inside worktree
echo -n "Test: write inside worktree... "
if codex debug landlock --full-auto /usr/bin/env bash -c "touch '$worktree_root/inside_test'"; then
echo "PASS"
else
echo "FAIL" >&2
exit 1
fi
# Test write inside TMPDIR
tmpdir=${TMPDIR:-/tmp}
echo -n "Test: write inside TMPDIR ($tmpdir)... "
if codex debug landlock --full-auto /usr/bin/env bash -c "touch '$tmpdir/tmp_test'"; then
echo "PASS"
else
echo "FAIL" >&2
exit 1
fi
# Prepare external directory under HOME to test outside worktree/TMPDIR
external_dir="$HOME/sandbox_test_dir"
mkdir -p "$external_dir"
rm -f "$external_dir/outside_test"
echo -n "Test: write outside allowed paths ($external_dir)... "
if codex debug landlock --full-auto /usr/bin/env bash -c "touch '$external_dir/outside_test'"; then
echo "FAIL: outside write succeeded" >&2
exit 1
else
echo "PASS"
fi
```
**How it works**
When invoked with `--agent`, `create-task-worktree.sh` changes into the task worktree and launches:
```bash
codex debug landlock --full-auto codex "$(< \"$repo_root/agentydragon/prompts/developer.md\")"
```
The `--full-auto` flag configures Landlock to allow disk writes under the current directory and the system temp directory, disable network access, and automatically approve commands on success. As a result, any file I/O and Git operations in the worktree proceed without approval prompts, while writes outside the worktree and TMPDIR are blocked by the sandbox.
## Notes
- This feature depends on the underlying Landlock/Seatbelt sandbox APIs.
- Leverage the existing sandbox invocation (`codex debug landlock`) and approval predicates to auto-approve worktree and tmpdir I/O.

View File

@@ -0,0 +1,54 @@
+++
id = "16"
title = "Confirm on Ctrl+D to Exit"
status = "Merged"
dependencies = ""
last_updated = "2025-06-25T05:36:23.493497"
+++
# Task 16: Confirm on Ctrl+D to Exit
> *This task is specific to codex-rs.*
## Status
**General Status**: Done
**Summary**: Double Ctrl+D confirmation implemented and tested.
## Goal
Require two consecutive Ctrl+D keystrokes (within a short timeout) to exit the TUI, preventing accidental termination from a single SIGINT.
## Acceptance Criteria
- Add a `[tui] require_double_ctrl_d = true` config flag (default `false`) to enable doubleCtrl+D exit confirmation.
- When `require_double_ctrl_d` is enabled:
- First Ctrl+D within the TUI suspends exit and shows a status message like "Press Ctrl+D again to confirm exit".
- If a second Ctrl+D occurs within a configurable timeout (e.g. 2sec), the TUI exits normally.
- If no second Ctrl+D arrives before timeout, clear the confirmation state and resume normal operation.
- Ensure that child processes (shell tool calls) still receive SIGINT immediately and are not affected by the doubleCtrl+D logic.
- Prevent immediate exit on Ctrl+D (EOF); require the same doubleconfirmation workflow as for Ctrl+D when EOF is received.
- Provide unit or integration tests simulating SIGINT events to verify behavior.
## Implementation
**How it was implemented**
- Added `require_double_ctrl_d` and `double_ctrl_d_timeout_secs` to the TUI config in `core/src/config_types.rs` with defaults.
- Introduced `ConfirmCtrlD` helper in `tui/src/confirm_ctrl_d.rs` to manage confirmation state and expiration logic.
- Extended `App` in `tui/src/app.rs`:
- Initialized `confirm_ctrl_d` from config in `App::new`.
- Expired stale confirmation windows each event-loop tick and cleared the status overlay when timed out.
- Replaced the Ctrl+D handler to invoke `ConfirmCtrlD::handle`, exiting only on confirmed press and otherwise displaying a prompt via `BottomPane`.
- Leveraged `BottomPane::set_task_running(true)` and `update_status_text` to render the confirmation prompt overlay.
- Added unit tests for `ConfirmCtrlD` in `tui/src/confirm_ctrl_d.rs` covering disabled mode, confirmation press, and timeout expiration.
**How it works**
- When `require_double_ctrl_d = true`, the first Ctrl+D press shows "Press Ctrl+D again to confirm exit" in the status overlay.
- A second Ctrl+D within `double_ctrl_d_timeout_secs` exits the TUI; otherwise the prompt and state clear after timeout.
- When `require_double_ctrl_d = false`, Ctrl+D exits immediately as before.
- Child processes still receive SIGINT normally since only the TUI event loop intercepts Ctrl+D.
## Notes
- Make the doubleCtrl+D timeout duration configurable if desired (e.g. via `tui.double_ctrl_d_timeout_secs`).
- Ensure that existing tests for Ctrl+D behavior are updated or new tests added to cover the confirmation state.

View File

@@ -0,0 +1,46 @@
+++
id = "18"
title = "Chat UI Textarea Overlay and Border Styling Fix"
status = "Merged"
dependencies = "02,07,09,11,14,29"
last_updated = "2025-06-25T05:36:27.942304"
+++
# Task 18: Chat UI Textarea Overlay and Border Styling Fix
---
id: 18
title: Chat UI Textarea Overlay and Border Styling Fix
status: Not started
summary: Fix overlay of waiting messages and streamline borders between chat window and input area to improve visibility and reclaim terminal space.
goal: |
Adjust the TUI chat interface so that waiting/status messages no longer overlay the first line of the input textarea (ensuring user drafts remain visible), and merge/remove borders as follows:
- Merge the bottom border of the chat history window with the top border of the input textarea.
- Remove the left, right, and bottom overall borders around the chat interface to reduce wasted space.
---
> *This task is specific to codex-rs.*
## Acceptance Criteria
- Waiting/status messages (e.g. "Thinking...", "Typing...", etc.) appear above the textarea rather than overlaying the first line of the input area.
- User draft text remains visible at all times, even when agent messages or status indicators are rendered.
- The bottom border of the chat history pane and the top border of the textarea are unified into a single border line.
- The left, right, and bottom borders around the entire chat UI are removed, reclaiming columns/rows in the terminal.
- Manual or automated visual verification steps demonstrate correct layout in a variety of terminal widths.
## Implementation
**How it was implemented**
* Merged the bottom border of the history pane and the top border of the input textarea into a single shared line by removing the textarea's top border and keeping only a bottom border on the textarea and both top/bottom borders on the history pane.*
* Removed left/right borders on both panes (history and textarea) and removed the textarea's bottom border from the overall UI to reclaim horizontal space.*
* Updated the status-indicator overlay to render in its own floating box immediately above the textarea instead of covering the first input line.*
**How it works**
At runtime the conversation history widget now draws only its top and bottom borders. The input textarea draws only its bottom border, carrying the help title there. These changes yield a single continuous border line separating history from input and eliminate the outer left, right, and bottom borders. Status messages ("Thinking...", etc.) render in a separate floating box positioned just above the textarea, leaving the user's draft text visible at all times.
## Notes
- This involves updating the rendering logic in the TUI modules (likely under `tui/src/` in `codex-rs`).
- Ensure layout changes do not break existing tests or rendering in unusual terminal sizes.
- Consider writing a simple snapshot test or manual demo script to validate border and overlay behavior.

View File

@@ -0,0 +1,42 @@
+++
id = "19"
title = "Bash Command Rendering Improvements for Less Verbosity"
status = "Merged"
dependencies = "02,07,09,11,14,29"
last_updated = "2025-06-25T05:36:32.641375"
+++
> *This task is specific to per-agent UI conventions and log readability.*
## Acceptance Criteria
- Shell commands render as plain text without `bash -lc` wrappers.
- Role labels and message content appear on the same line, separated by a space.
- Command-result annotations show a checkmark and duration for zero exit codes, or `exit code: N` and duration for nonzero codes, in the format `<icon or exit code> <duration>ms`.
- Existing functionality remains unaffected beyond formatting changes.
- Verbose background event logs (e.g. sandboxdenied exec errors, retries) collapse into a single command execution entry showing command start, running indicator, and concise completion status.
- Automated examples or tests verify the new rendering behavior.
## Implementation
This change will touch both the event-processing and rendering layers of the Rust TUI:
- **Event processing** (`codex-rs/exec/src/event_processor.rs`):
- Strip any `bash -lc` wrapper when formatting shell commands via `escape_command`.
- Replace verbose `BackgroundEvent` logs for sandbox-denied errors and automatic retries with a unified exec-command begin/end sequence.
- Annotate completed commands with either a checkmark (✅) and `<duration>ms` for success or `exit code: N <duration>ms` for failures.
- **TUI rendering** (`codex-rs/tui/src/history_cell.rs`):
- Collapse consecutive `BackgroundEvent` entries related to exec failures/retries into the standard active/completed exec-command cells.
- Update `new_active_exec_command` and `new_completed_exec_command` to use the new inline format (icon or exit code + duration, with `$ <command>` on the same block).
- Ensure role labels and plain-text messages render on a single line separated by a space.
- **Tests** (`codex-rs/tui/tests/`):
- Add or update test fixtures to verify:
- Commands appear without any `bash -lc` boilerplate.
- Completed commands show the correct checkmark or exit-code annotation with accurate duration formatting.
- Background debugging events no longer leak raw debug strings and are correctly collapsed into the exec-command flow.
## Notes
- Improves readability of interactive sessions and logs by reducing boilerplate.
- Ensure compatibility with both live TUI output and persisted log transcripts.

View File

@@ -0,0 +1,34 @@
+++
id = "21"
title = "Compact Markdown Rendering Option"
status = "Merged"
dependencies = "03,06,08,13,15,32,18,19,22,23"
last_updated = "2025-06-25T05:55:23.855039"
+++
## Summary
Provide an option to render Markdown without blank lines between headings and content for more vertical packing.
## Goal
Add a configuration flag to control Markdown rendering in the chat UI and logs so that headings render immediately adjacent to their content with no separating blank line.
## Acceptance Criteria
- Introduce a config flag `markdown_compact = true|false` under the UI settings.
- When enabled, the renderer omits the default blank line between headings (lines starting with `#`) and their subsequent content.
- The flag applies globally to all Markdown rendering (diffs, docs, help messages).
- Default behavior remains unchanged (blank lines preserved) when `markdown_compact` is false or unset.
- Add tests to verify both compact and default rendering modes across heading levels.
## Implementation
**How it was implemented**
- Extend the Markdown-to-TUI formatter to check `markdown_compact` and collapse heading/content spacing.
- Implement a post-processing step that removes blank lines immediately following heading tokens (`^#{1,6} `) when `markdown_compact` is true.
- Expose the new flag via the config parser and default it to `false`.
- Add unit tests covering H1H6 headings, verifying absence of blank line in compact mode and presence in default mode.
## Notes
- This option improves vertical density for screens with limited height.
- Ensure compatibility with existing Markdown features like lists and code blocks; only target heading-content spacing.

View File

@@ -0,0 +1,41 @@
+++
id = "23"
title = "Interactive Container Command Affordance via Hotkey"
status = "Merged"
freeform_status = ""
dependencies = "01"
last_updated = "2025-06-25T12:10:10.584536"
+++
## Summary
Provide a keybinding to run arbitrary shell commands in the agents container and display output inline.
## Goal
Add a user-facing affordance (e.g. a hotkey) to invoke arbitrary shell commands within the agent's container during a session for on-demand inspection and debugging. The typed command should be captured as a chat turn, executed via the existing shell tool, and its output rendered inline in the chat UI.
## Acceptance Criteria
- Bind a hotkey (e.g. Ctrl+M) that opens a prompt for the user to type any shell command.
- When the user submits, capture the command as if entered in the chat input, and invoke the shell tool with the command in the agents container.
- Display the command invocation and its stdout/stderr output inline in the chat window, respecting formatting rules (e.g. compact rendering settings).
- Support chaining multiple commands in separate turns; history should show these command turns normally.
- Provide unit or integration tests simulating a user hotkey press, command input, and verifying the shell tool is called and output is displayed.
## Implementation
**How it was implemented**
- Added a new slash command `Shell` and updated dispatch logic in `app.rs` to push a shell-command view.
- Bound `Ctrl+M` in `ChatComposer` to dispatch `SlashCommand::Shell` for hotkey-driven shell prompt.
- Created `ShellCommandView` (bottom pane overlay) to capture arbitrary user input and emit `AppEvent::ShellCommand(cmd)`.
- Extended `AppEvent` with `ShellCommand(String)` and `ShellCommandResult { call_id, stdout, stderr, exit_code }` variants for round-trip messaging.
- Implemented `ChatWidget::handle_shell_command` to execute `sh -c <cmd>` asynchronously (tokio::spawn) and send back `ShellCommandResult`.
- Updated `ConversationHistoryWidget` to reuse existing exec-command cells to display shell commands and their output inline.
- Added tests:
- Unit test in `shell_command_view.rs` asserting correct event emission (skipping redraws).
- Integration test in `chat_composer.rs` asserting `Ctrl+M` opens the shell prompt view and allows input.
## Notes
- This feature aids debugging and inspection without leaving the agent workflow.
- Ensure that security policies (e.g. sandbox restrictions) still apply to these commands.

View File

@@ -0,0 +1,36 @@
+++
id = "28"
title = "Include Command Snippet in Session-Scoped Approval Label"
status = "Merged"
dependencies = "03,06,08,13,15,32,18,19,22,23"
last_updated = "2025-06-25T04:04:47.399379"
+++
## Summary
When asking for session-scoped approval of a command, embed a truncated snippet of the actual command in the approval label for clarity.
## Goal
Improve the session-scoped approval option label for commands by including a backtick-quoted snippet of the command itself (truncated to fit). This makes it clear exactly which command (including parameters) will be auto-approved for the session.
## Acceptance Criteria
- The session-scoped approval label changes from generic text to include a snippet of the current command, e.g.:
```text
Yes, always allow running `cat x | foo --bar > out` for this session (a)
```
- If the command is too long, truncate the middle (e.g. `long-part…end-part`) to fit a configurable max length.
- Implement the snippet templating in both Rust and JS UIs for consistency.
- Add unit tests to verify snippet extraction, truncation logic, and label rendering for various command lengths.
## Implementation
**Planned implementation**
- Add a `truncateMiddle` helper in both the Rust TUI and the JS/TS UI to ellipsize command snippets in the middle.
- Extract the first line of the command string (up to any newline), truncate to a default max length (e.g. 30 characters), inserting a single-character ellipsis `` when needed.
- In the session-scoped approval option, replace the static label with a dynamic one:
`Yes, always allow running `<snippet>` for this session (a)`.
- Write unit tests for the helper and label generation covering commands shorter than, equal to, and longer than the max length.
## Notes
- This clarifies what parameters will be auto-approved and avoids ambiguity when multiple similar commands occur.

View File

@@ -0,0 +1,38 @@
+++
id = "31"
title = "Display Remaining Context Percentage in codex-rs TUI"
status = "Merged"
dependencies = "03,06,08,13,15,32,18,19,22,23"
last_updated = "2025-06-25T01:40:09.600000"
+++
## Summary
Show a live "x% context left" indicator in the TUI (Rust) to inform users of remaining model context buffer.
## Goal
Enhance the codex-rs TUI by adding a status indicator that displays the percentage of model context buffer remaining (e.g. "75% context left"). Update this indicator dynamically as the conversation progresses.
## Acceptance Criteria
- Compute current token usage and total context limit from the active session.
- Display "<N>% context left" in the status bar or header of the TUI, formatted compactly.
- Update the percentage after each message turn in real time.
- Ensure the indicator is visible but does not obstruct existing UI elements.
- Add unit or integration tests mocking token count updates and verifying correct percentage formatting (rounding behavior, boundary conditions).
## Implementation
**How it was implemented**
- Added a `history_items: Vec<ResponseItem>` field to `ChatWidget` to accumulate the raw sequence of messages and function calls.
- Created a new module `tui/src/context.rs` mirroring the JS heuristics:
- `approximate_tokens_used(&[ResponseItem])`: counts characters in text and function-call items, divides by 4 and rounds up.
- `max_tokens_for_model(&str)`: uses a registry of known model limits and heuristic fallbacks (32k, 16k, 8k, 4k, default 128k).
- `calculate_context_percent_remaining(&[ResponseItem], &str)`: computes `(remaining / max) * 100`.
- Updated `ChatWidget::replay_items` and `ChatWidget::handle_codex_event` to push each incoming `ResponseItem` into `history_items`.
- Modified `ChatComposer::render_ref` to query `calculate_context_percent_remaining`, format and display "<N>% context left" after the input area, coloring it green/yellow/red per thresholds (>40%, 2540%, ≤25%).
- Added unit tests in `tui/tests/context_percent.rs` covering token counting, model heuristics, percent rounding, and boundary conditions.
## Notes
- This feature helps users anticipate when they may need to truncate history or start a new session.
- Future enhancement: allow toggling this indicator on/off via config.

View File

@@ -0,0 +1,42 @@
+++
id = "35"
title = "TUI Integration for Inspect-Env Command"
status = "Done"
dependencies = "10" # Rationale: depends on Task 10 for container state inspection
last_updated = "2025-06-25T11:38:19Z"
+++
> *This task is specific to codex-rs.*
## Status
**General Status**: Done
**Summary**: Follow-up to Task 10; add slash-command and TUI bindings for `inspect-env`.
## Goal
Add an `/inspect-env` slash-command in the TUI that invokes the existing `codex inspect-env` logic to display sandbox state inline.
## Acceptance Criteria
- Extend `SlashCommand` enum to include `InspectEnv`.
- Dispatch `AppEvent::InlineInspectEnv` when `/inspect-env` is entered.
- Handle `InlineInspectEnv` in `app.rs` to run `inspect-env` logic and stream its output to the TUI log pane.
- Render mounts, permissions, and network status in a formatted table or tree view in the bottom pane.
- Unit/integration tests simulating slash-command invocation and verifying rendered output.
## Implementation
**High-level approach**
- Extend `SlashCommand` enum with `InspectEnv` and provide user-visible description.
- Add `InlineInspectEnv` variant to `AppEvent` enum to represent inline slash-command invocation.
- Update dispatch logic in `App::run` to spawn a background thread on `InlineInspectEnv` that runs `codex inspect-env`, reads its stdout line-by-line, and sends each line as `AppEvent::LatestLog`, then triggers a redraw.
- Wire up `/inspect-env` to dispatch `InlineInspectEnv` in the slash-command handling.
- Add unit tests in the TUI crate to verify `built_in_slash_commands()` includes `inspect-env` mapping and description, and tests for the command-popup filter to ensure `InspectEnv` is listed when `/inspect-env` is entered.
**How it works**
When the user enters `/inspect-env`, the TUI parser recognizes the command and emits `AppEvent::InlineInspectEnv`. The main event loop handles this event by spawning a thread that invokes the external `codex inspect-env` command, captures its output line-by-line, and forwards each line into the TUI log pane via `AppEvent::LatestLog`. A redraw is scheduled once the inspection completes.
## Notes
- Reuse formatting code from `cli/src/inspect_env.rs` for consistency.

View File

@@ -0,0 +1,34 @@
+++
id = "38"
title = "Fix Approval Dialog Transparent Background"
status = "Done"
dependencies = ""
summary = "The approval dialog background is transparent, causing prompt text underneath to overlap and become unreadable."
last_updated = "2025-06-25T23:00:00.000000"
+++
> *UI bug:* When the approval dialog appears, its background is transparent and any partially entered prompt text shows through, overlapping and confusing the dialog.
## Status
**General Status**: Done
**Summary**: Identify and implement an opaque background for the approval dialog to prevent underlying text bleed-through.
## Goal
Ensure the approval dialog is drawn with a solid background color (matching the dialog border or theming) so that any underlying text does not bleed through.
## Acceptance Criteria
- Approval dialogs block underlying prompt text (solid background).
- Existing unit/integration tests validate dialog visual rendering.
## Implementation
- Updated `render_ref` in `codex-rs/tui/src/user_approval_widget.rs` to fill the entire dialog area with a `DarkGray` background before drawing the border and content.
- Implemented nested loops over the dialog `Rect` calling `buf[(col, row)].set_bg(Color::DarkGray)` on each cell.
- Added unit test `render_approval_dialog_fills_background` in `tui/src/user_approval_widget.rs` to render the widget onto a buffer pre-filled with a red background and verify no cell in the dialog region remains transparent or retains the sentinel background.
## Notes
<!-- Any implementation notes -->

View File

@@ -0,0 +1,47 @@
+++
id = "02"
title = "Granular Auto-Approval Predicates"
status = "Done"
dependencies = "11" # Rationale: depends on Task 11 for user-configurable approval predicates
last_updated = "2025-06-25T10:48:30.000000"
+++
# Task 02: Granular Auto-Approval Predicates
> *This task is specific to codex-rs.*
## Status
**General Status**: Done
**Summary**: Added granular auto-approval predicates: configuration parsing, predicate evaluation, integration, documentation, and tests.
## Goal
Let users configure one or more scripts in `config.toml` that examine each proposed shell command and return exactly one of:
- `deny` => auto-reject (skip sandbox and do not run the command)
- `allow` => auto-approve and proceed under the sandbox
- `no-opinion` => no opinion (neither approve nor reject)
Multiple scripts cast votes: if any script returns `deny`, the command is denied; otherwise if any script returns `allow`, the command is allowed; otherwise (all scripts return `no-opinion` or exit non-zero), pause for manual approval (existing logic).
## Acceptance Criteria
- New `[[auto_allow]]` table in `config.toml` supporting one or more `script = "..."` entries.
- Before running any shell/subprocess, Codex invokes each configured script in order, passing the candidate command as an argument.
- If a script returns `deny` or `allow`, immediately take that vote and skip remaining scripts.
- After all scripts complete with only `no-opinion` results or errors, pause for manual approval (existing logic).
- Spawn each predicate script with the full command as its only argument.
- Parse stdout (case-insensitive) expecting `deny`, `allow`, or `no-opinion`, treating errors or unknown output as `NoOpinion`.
- Short-circuit on the first `Deny` or `Allow` vote.
- A `Deny` vote aborts execution.
- An `Allow` vote skips prompting and proceeds under sandbox.
- All `NoOpinion` votes fall back to existing approval logic.
## Implementation
-- Added `auto_allow: Vec<AutoAllowPredicate>` to `ConfigToml`, `ConfigProfile`, and `Config` to parse `[[auto_allow]]` entries from `config.toml`.
-- Defined `AutoAllowPredicate { script: String }` and `AutoAllowVote { Allow, Deny, NoOpinion }` in `core::safety`.
-- Implemented `evaluate_auto_allow_predicates` in `core::safety` to spawn each script with the candidate command, parse its stdout vote, and short-circuit on `Deny` or `Allow`.
-- Integrated `evaluate_auto_allow_predicates` into the shell execution path in `core::codex`, aborting on `Deny`, auto-approving on `Allow`, and falling back to manual or policy-based approval on `NoOpinion`.
-- Updated `config.md` to document the `[[auto_allow]]` table syntax and behavior.
-- Added comprehensive unit tests covering vote parsing, error propagation, short-circuit behavior, and end-to-end predicate functionality.
## Notes
- This pairs with the existing `approval_policy = "unless-allow-listed"` but adds custom logic before prompting.

View File

@@ -0,0 +1,63 @@
+++
id = "04"
title = "Auto-Mount Entire Repo and Auto-CD to Subfolder"
status = "Not started"
dependencies = "01" # Rationale: depends on Task 01 for mount-add/remove foundational commands
last_updated = "2025-06-25T01:40:09.800000"
+++
# Task 04: Auto-Mount Entire Repo and Auto-CD to Subfolder
> *This task is specific to codex-rs.*
## Subtasks
Subtasks to implement in order all in one P:
### 04.1 Config → `ConfigToml` + `Config`
- Add `auto_mount_repo: bool` and `mount_prefix: String` to `ConfigToml` (with proper `#[serde(default)]` and defaults).
- Wire these fields through to the `Config` struct.
### 04.2 Git root detection + relativepath
- Implement a helper in `codex_core::util` to locate the Git repository root given a starting `cwd`.
- Compute the subdirectory path relative to the repo root.
### 04.3 Bindmount logic
- In the sandbox startup path (`apply_sandbox_policy_to_current_thread` or a new wrapper before it), if `auto_mount_repo` is set:
- Bindmount `repo_root``mount_prefix` (e.g. `/workspace`).
- Create target directory if missing.
### 04.4 Automate `cwd` → new mount
- After mounting, update the processwide `cwd` to `mount_prefix/relative_path` so all subsequent file ops occur under the mount.
### 04.5 Config docs & tests
- Update `config.md` to document `auto_mount_repo` and `mount_prefix` under the toplevel config.
- Add unit tests for the Gitroot helper and default values.
### 04.6 E2E manual verification
- Manually verify launching with `auto_mount_repo = true` in a nested subfolder:
- TTY prompt shows sandboxed cwd under `/workspace/<subdir>`.
- Commands executed by Codex see the mount.
## Goal
Allow users to enable a flag so that each session:
1. Detects the Git repository root of the current working directory.
2. Bind-mounts the entire repository into `/workspace` in the session.
3. Changes directory to `/workspace/<relative-path-from-root>` to mirror the users original subfolder.
## Acceptance Criteria
- New `auto_mount_repo = true` and optional `mount_prefix = "/workspace"` in `config.toml`.
- Before any worktree or mount processing, detect the Git root, bind-mount it to `mount_prefix`, and set `cwd` to `mount_prefix + relative_path`.
- Existing worktree/session-worktree logic should operate relative to this new `cwd`.
## Implementation
**How it was implemented**
*(Not implemented yet)*
**How it works**
*(Not implemented yet)*
## Notes
- This offloads the entire monorepo into the session, leaving the users original clone untouched.

View File

@@ -0,0 +1,47 @@
+++
id = "09"
title = "File- and Directory-Level Approvals"
status = "Not started"
dependencies = "11" # Rationale: depends on Task 11 for custom approval predicate infrastructure
last_updated = "2025-06-25T01:40:09.507043"
+++
# Task 09: File- and Directory-Level Approvals
> *This task is specific to codex-rs.*
## Status
**General Status**: Not started
**Summary**: Not started; missing Implementation details (How it was implemented and How it works).
## Goal
Enable fine-grained approval controls so users can whitelist edits scoped to specific files or directories at runtime, with optional time limits.
## Acceptance Criteria
- In the approval dialog, offer “Allow this file always” and “Allow this directory always” options alongside proceed/deny.
- Prompt for a time limit when granting a file/dir approval, with default presets (e.g. 5min, 1hr, 4hr, 24hr).
- Introduce runtime commands to inspect and manage granular approvals:
- `/approvals list` to view active approvals and remaining time
- `/approvals add [file|dir] <path> [--duration <preset>]` to grant approval
- `/approvals remove <id>` to revoke an approval
- Persist granular approvals in session metadata, keyed by working directory. On session resume in a different directory, warn the user and discard all file/dir approvals.
- Automatically expire and remove approvals when their time limits elapse.
- Reflect file/dir-approval state in the CLI shell prompt or title for quick visibility.
## Implementation
**How it was implemented**
*(Not implemented yet)*
**How it works**
*(Not implemented yet)*
## Notes
- Store approvals with {id, scope: file|dir, path, expires_at} in session JSON.
- Use a background timer or check-before-command to prune expired entries.
- Reuse existing command-parsing infrastructure to implement `/approvals` subcommands.
- Consider UI/UX for selecting presets in TUI dialogs.

View File

@@ -0,0 +1,44 @@
+++
id = "12"
title = "Runtime Internet Connection Toggle"
status = "Not started"
dependencies = "" # No prerequisites
last_updated = "2025-06-25T01:40:09.509507"
+++
# Task 12: Runtime Internet Connection Toggle
> *This task is specific to codex-rs.*
## Status
**General Status**: Not started
**Summary**: Not started; missing Implementation details (How it was implemented and How it works).
## Goal
Allow users to enable or disable internet access at runtime within their container/sandbox session.
## Acceptance Criteria
- Slash command or CLI subcommand (`/toggle-network <on|off>`) to turn internet on or off immediately.
- Persist network state in session metadata so that resuming a session restores the last setting.
- Enforce the new network policy dynamically: block or allow outbound network connections without restarting the agent.
- Reflect the current network status in the CLI prompt or shell title (e.g. 🌐/🚫).
- Work across supported platforms (Linux sandbox, macOS Seatbelt, Windows) using appropriate sandbox APIs.
- Include unit and integration tests to verify network toggle behavior and persistence.
## Implementation
**How it was implemented**
*(Not implemented yet)*
**How it works**
*(Not implemented yet)*
## Notes
- Reuse the existing sandbox network-disable mechanism (`CODEX_SANDBOX_NETWORK_DISABLED_ENV_VAR`) for toggling.
- On Linux, this may involve updating Landlock or seccomp rules at runtime.
- On macOS, interact with the Seatbelt profile; consider session restart if necessary.
- When persisting state, store a `network_enabled: bool` flag in the session JSON.

View File

@@ -0,0 +1,47 @@
+++
id = "14"
title = "AIGenerated Approval Predicate Suggestions"
status = "Not started"
dependencies = "02,11" # Rationale: depends on Task 02 for auto-approval predicates and Task 11 for predicate invocation logic
last_updated = "2025-06-25T01:40:09.511783"
+++
# Task 14: AIGenerated Approval Predicate Suggestions
> *This task is specific to codex-rs.*
## Status
**General Status**: Not started
**Summary**: Not started; missing Implementation details (How it was implemented and How it works).
## Goal
When a shell command is not auto-approved, the approval prompt should include 13 AI-generated approval predicates. Each suggestion is a time-limited Python predicate snippet plus an explanation of the full set of permissions it would grant. Users can pick one suggestion to append to the sessions approval policy as a broader-scope allow rule.
## Acceptance Criteria
- When a command is not auto-approved, show up to 3 suggested predicates inline in the TUI approval dialog.
- Each suggestion consists of:
- A Python code snippet defining a predicate function.
- An AI-generated explanation of exactly what permissions or scope that predicate grants.
- A TTL or expiration timestamp indicating how long it will remain active.
- Users can select one suggestion to append to the sessions list of approval predicates.
- Predicates are stored in session state (in-memory) for the duration of the session.
- Provide a slash/CLI command (`/inspect-approval-predicates`) to list current predicates, their code, explanations, and timeouts.
- Support headless and interactive modes equally.
## Implementation
**How it was implemented**
*(Not implemented yet)*
**How it works**
*(Not implemented yet)*
## Notes
- Reuse the existing AI reasoning engine to generate predicate suggestions.
- Represent predicates as Python functions returning a boolean.
- Ensure that expiration is enforced and stale predicates are ignored.
- Integrate the new `/inspect-approval-predicates` command into both the TUI and Exec CLI.

View File

@@ -0,0 +1,28 @@
+++
id = "17"
title = "Sandbox Pre-commit Permission Error"
status = "Not started"
dependencies = "15" # Rationale: depends on Task 15 for sandbox worktree configuration
last_updated = "2025-06-25T01:41:34.737190"
+++
> *This task addresses scaffolding/setup for Agent worktrees.*
## Acceptance Criteria
- Pre-commit hooks detect sandbox environment and skip or override gitconfig locking.
- Documentation in scaffold guides is updated to note pre-commit limitations and workarounds.
- Verification steps demonstrate pre-commit hooks succeeding in sandbox without modifying user gitconfig.
## Implementation
**How it was implemented**
*(Not implemented yet)*
**How it works**
*(Not implemented yet)*
## Notes
- The sandbox prevents locking ~/.gitconfig, leading to PermissionError.
- Consider configuring pre-commit to use a repo-local config or skip locking by passing `--config` or setting `PRE_COMMIT_HOME`.

View File

@@ -0,0 +1,36 @@
+++
id = "20"
title = "Render Patch Content in Chat Display Window for Approve/Deny"
status = "Not started"
dependencies = "" # No prerequisites
last_updated = "2025-06-25T01:41:34.738344"
+++
> *This task is specific to the chat UI renderer.*
## Acceptance Criteria
- When displaying a patch for approve/deny, the full diff for the active patch is rendered inline in the chat window.
- Older or superseded patches collapse to show only up to N lines of context, with an indicator (e.g. "... 10 lines collapsed ...").
- File paths in diff headers are shown relative to the current working directory, unless the file resides outside the CWD.
- Event logs around patch application are simplified: drop structured event data and replace with a simple status note (e.g. "patch applied").
- Configurable parameter (e.g. `patch_context_lines`) controls the number of context lines for collapsed hunks.
- Preserve the users draft input when an approval dialog or patch diff appears; ensure the draft editor remains visible so users can continue editing while reviewing.
- Provide end-to-end integration tests that simulate drafting long messages, triggering approval dialogs and overlays, and verify that all UI elements (draft editor, diffs, logs) render correctly without overlap or content loss.
- Exhaustively test all dialog interaction flows (approve, deny, cancel) and overlay scenarios to confirm consistent behavior across combinations and prevent rendering artifacts.
## Implementation
**How it was implemented**
- Extend the chat renderer to detect patch approval prompts and render diffs using a custom formatter.
- Compute relative paths via `Path::strip_prefix`, falling back to full path if outside CWD.
- Track the current patch ID and render its full content; collapse previous patch bodies according to `patch_context_lines` setting.
- Preserve and render the current draft buffer alongside the active patch diff, ensuring live edits remain visible during approval steps.
- Add integration tests using the TUI test harness or end-to-end framework to simulate user input of long text, approval flows, overlay dialogs, and log output, asserting correct screen layout and content integrity.
- Design a parameterized test matrix covering all dialog interaction flows (approve/deny/cancel) and overlay transitions to ensure exhaustive coverage and UI sanity.
- Replace verbose event debug output with a single-line status message.
## Notes
- Users can override `patch_context_lines` in their config to see more or fewer collapsed lines.
- Ensure compatibility with both live TUI sessions and persisted transcript logs.

View File

@@ -0,0 +1,37 @@
+++
id = "22"
title = "Message Separation and Sender-Content Layout Options"
status = "Done"
dependencies = "" # No prerequisites
last_updated = "2025-06-25T11:05:55.000000"
+++
## Summary
Add configurable options for inter-message spacing and sender-content line breaks in chat rendering
**in the codex-rs package** - **NOT** the codex-cli package.
## Goal
Provide users with flexibility in how chat messages are visually separated and how sender labels are displayed relative to message content:
- Control whether an empty line is inserted between consecutive messages.
- Control whether sender and content appear on the same line or on separate lines.
## Acceptance Criteria
- Introduce one new config flags under the UI section:
- `message_spacing: true|false` controls inserting a blank line between messages when true.
- default to `false` to preserve current compact layout.
- When `message_spacing` is enabled, render an empty line between each message bubble or block.
- Add unit tests to verify the layout produces the correct sequence of lines.
## Implementation
### Plan
**How it was implemented**
- Extend the chat UI renderer to read `message_spacing` from config.
- In the message rendering routine, after emitting each message block, conditionally insert a blank line if `message_spacing` is true.
- Write unit tests for values of `(message_spacing)` covering single-line messages, multi-line content, and boundaries.
## Notes
- These options improve readability for users who prefer more visual separation or clearer sender labels.
- Keep default settings unchanged to avoid surprising existing users.

View File

@@ -0,0 +1,33 @@
+++
id = "24"
title = "Guard Against Missing Tool Output in JS Server Sequencing"
status = "Not started"
dependencies = "" # No prerequisites
last_updated = "2025-06-25T01:40:09.600000"
+++
## Summary
Prevent out-of-order chat messages and missing tool outputs when user input interrupts tool execution in the JS backend.
## Goal
Ensure the JS server never emits a user or model message before the corresponding tool output has been delivered. Add sequencing guards to the message dispatcher so that aborted rollouts or interleaved user messages cannot cause "No tool output found" errors.
## Acceptance Criteria
- When a tool invocation is interrupted or user sends a message mid-rollout, the JS server buffers subsequent messages until the tool output event arrives or the invocation is explicitly cancelled.
- The server must never log or emit an error like "No tool output found for local shell call" due to sequencing mismatch.
- Add automated tests simulating mid-rollout user interrupts in the JS test suite, verifying correct buffering and eventual message delivery or cancellation.
## Implementation
**How it was implemented**
- In the JS message dispatcher, track pending tool invocations by ID and delay processing of new chat messages until the pending invocation resolves (success, failure, or cancel).
- Add a guard in the `handleUserMessage` path to check for unresolved tool IDs before appending user content; if pending, queue the message.
- On receiving `toolOutput` or `toolError` for an invocation ID, flush any queued messages in order.
- Implement explicit cancellation paths so that if a tool invocation is abandoned, queued messages still flow after cancellation confirmation.
- Add unit and integration tests in the JS test harness to cover normal, aborted, and concurrent message scenarios.
## Notes
- This change prevents 400 Bad Request errors from tool retries where the model requests a tool before the output is streamed.
- Keep diagnostic logs around sequencing logic for troubleshooting but avoid spamming on normal race cases.

View File

@@ -0,0 +1,78 @@
+++
id = "25"
title = "Guard Against Missing Tool Output in Rust Server Sequencing"
status = "Needs input"
dependencies = "" # No prerequisites
last_updated = "2025-06-25T22:50:01.000000"
+++
## Summary
Prevent out-of-order chat messages and missing tool output errors when user input interrupts tool execution in the Rust backend.
## Goal
Ensure the Rust server implementation sequences tool output and chat messages correctly. Add synchronization logic so that an in-flight tool invocation either completes or is cancelled before new messages are processed, avoiding "No tool output found" invalid_request errors.
## Acceptance Criteria
- The Rust message broker must detect pending tool invocations and pause delivery of subsequent user or model messages until the tool result or cancellation is handled.
- No panic or 400 Bad Request errors should occur due to missing tool output in edge cases of interrupted rollouts or mid-stream user input.
- Add Rust integration tests simulating tool invocation interruption and user message interleaving, verifying correct ordering and delivery.
## Implementation
We will implement the following high-level plan:
- Locate where the ChatCompletion request messages array is built in Rust:
the `stream_chat_completions` function in `codex-rs/core/src/chat_completions.rs`.
- In that loop, track pending tool invocations by their call IDs when encountering `ResponseItem::FunctionCall` entries.
- Buffer any subsequent `ResponseItem::Message { role: "user" }` or new turn inputs until the matching `ResponseItem::FunctionCallOutput` (tool result) appears.
- Once the tool output is seen, flush buffered user messages in order immediately before continuing to build the next API call.
- Add tests under `codex-rs/core/tests/` (e.g. `guard_tool_output_sequencing.rs`) that exercise interleaved input sequences:
- A user message mid-rollout before tool output, ensuring it is delayed until after the tool result.
- Normal flow where no buffering is needed.
- Cancellation paths (no tool output) inserting a fake "Tool cancelled" tool message and flushing buffered messages.
- Front-end layers automatically render the "Tool cancelled" message as a tool output, satisfying UI requirements.
## Notes
```text
thinking █Deciding on file management █
───╮
Im thinking that since file editing can be a hassle, it might be simpler to just remove the file. I could then ask the user to provide │any errors theyre facing. To do this, I'd create a patch to delete the specified file. Alternatively, I could consider reverting the │
changes with git, but applying the patch seems like a straightforward solution. So, Ill go ahead and prepare to apply the patch to ──╯
delete the file.
proposed patch
D /Users/mpokorny/code/codex/agentydragon/tasks/.worktrees/23-interactive-container-command-affordance/codex-rs/.cargo/config.toml ny
ERROR: Turn interrupted
user
why did you try to delete that? s P
event
BackgroundEvent(BackgroundEventEvent { message: "stream error: unexpected status 400 Bad Request: {\n \"error\": {\n \"message\": \"No tool output found for local shell call call_PjdhYGAM7q52HL13WF8slGn9.\",\n \"type\": \"invalid_request_error\",\n \"param\":
\"input\",\n \"code\": null\n }\n}; retrying 1/10 in 201ms…" }) │
event │
BackgroundEvent(BackgroundEventEvent { message: "stream error: unexpected status 400 Bad Request: {\n \"error\": {\n \"message\": │
\"No tool output found for local shell call call_PjdhYGAM7q52HL13WF8slGn9.\",\n \"type\": \"invalid_request_error\",\n \"param\": │\"input\",\n \"code\": null\n }\n}; retrying 2/10 in 246ms…" }) │
event │BackgroundEvent(BackgroundEventEvent { message: "stream error: unexpected status 400 Bad Request: {\n \"error\": {\n \"message\": │
\"No tool output found for local shell call call_PjdhYGAM7q52HL13WF8slGn9.\",\n \"type\": \"invalid_request_error\",\n \"param\": █
\"input\",\n \"code\": null\n }\n}; retrying 3/10 in 371ms…" }) █
this is a lot of the problem still happening
```
## Next Steps / Debugging
The above change did not resolve the issue. We need to gather more debug information to understand why missing tool output errors still occur.
Suggested approaches:
- Enable detailed debug logging in the Rust message broker (e.g. set `RUST_LOG=debug` or add tracing spans around function calls).
- Dump the sequence of incoming and outgoing `ResponseItem` events to a log file for offline analysis.
- Instrument timing and ordering by recording timestamps when tool invocations start, complete, and when user input is received.
- Write a minimal reproduction harness that reliably triggers the missing output error under controlled conditions.
- Capture full request/response payloads to/from the OpenAI API to verify whether the function output is delivered but not processed.
Please expand this section with specific examples or helper scripts to collect the necessary data.

View File

@@ -0,0 +1,36 @@
+++
id = "26"
title = "Render Approval Requests in Separate Dialog from Draft Window"
status = "Not started"
dependencies = "09,23" # Rationale: depends on Tasks 09 and 23 for file-level approvals and interactive command affordance
last_updated = "2025-06-25T01:40:09.600000"
+++
## Summary
Display patch approval prompts in a distinct dialog or panel to avoid overlaying the draft editor.
## Goal
Change the chat UI so that approval requests (patch diffs for approve/deny) appear in a separate dialog element or panel, positioned adjacent to or below the chat window, rather than overlaying the draft input area.
This eliminates overlay conflicts and ensures the draft editor remains fully visible and interactive while reviewing patches.
## Acceptance Criteria
- Approval prompts with diffs open in a distinct UI element (e.g. side panel or bottom pane) that does not obscure the draft editor.
- The draft input area remains fully visible and editable whenever an approval dialog is active.
- The approval dialog is visually distinguished (border, background) and clearly labeled.
- The layout adjusts responsively for narrow/short terminal sizes, maintaining separation without clipping content.
- Add functional tests or integration tests verifying that the draft input remains accessible and that the approval dialog contents are rendered in the new panel.
## Implementation
**How it was implemented**
- Refactor the patch-approval renderer to spawn a separate TUI view (`ApprovalDialogView`) instead of the overlay popup.
- Allocate a consistent panel region (e.g. bottom X rows or right-hand column) for approval dialogs, reserving the draft editor region above or to the left.
- Update layout logic to recalculate positions on terminal resize, ensuring both panels remain visible.
- Style the new dialog with its own borders and title bar (e.g. "Approval Request").
- Add integration tests using the TUI test harness to simulate opening approval prompts and verifying that typing in the draft area still works and that the dialog appears in the correct panel.
## Notes
- This change fixes the long-standing overlay bug where approval diffs obstruct the draft.
- Future enhancements may allow toggling between inline overlay or separate panel modes.

View File

@@ -0,0 +1,46 @@
+++
id = "27"
title = "Unified Sandbox-Retry Prompt with y/a/A/n Options (Rust)"
status = "Not started"
dependencies = "15,17" # Rationale: depends on Tasks 15 and 17 for sandbox configuration and pre-commit permission handling
last_updated = "2025-06-25T01:40:09.600000"
+++
## Summary
Implement a unified retrywithoutsandbox prompt in the Rust TUI with oneshot, sessionscoped, and persistent options.
## Goal
Replace the two-stage sandboxretry and approval flow with a single, unified prompt in the Rust UI. Provide four hotkey options (y/a/A/n) to control sandbox behavior at varying scopes:
- y: retry this one command without sandbox
- a: always run without sandbox but still ask first
- A: always run without sandbox and never ask again
- n: keep using sandbox
## Acceptance Criteria
- When a sandboxed shell invocation fails (exit code ≠ 0), display a single prompt:
```
Retry without sandbox
y Yes, run without sandbox this one time
a Yes, always run without sandbox but still ask me first
A Yes, always run without sandbox and do not ask again
n No, keep using sandbox
```
- Hotkeys y/a/A/n must map to the corresponding behavior and dismiss the prompt.
- The prompt replaces the older twostage “retry?” + “Allow command?” dialogs.
- Add unit/integration tests simulating a failing sandbox command and each hotkey path, verifying correct sandbox flag logic.
## Implementation
**How it was implemented**
- Refactor the sandbox error handler in `tui/src/shell.rs` to emit a single `SandboxRetryPrompt` event instead of separate prompts.
- Create a new TUI widget `SandboxRetryWidget` that renders the four-line menu and captures y/a/A/n keys.
- Map each choice to updating the per-session config (`Config.tui.sandbox_mode`) and retrying or aborting the command as appropriate.
- Update the shellinvocation pipeline to consult the new `sandbox_mode` setting and skip sandbox when indicated.
- Write Rust tests (in `tui/tests/`) to simulate sandbox failures and user key presses for all four options.
## Notes
- This unifies and simplifies the UX, removing confusion from layered prompts.
- The three levels of scope (one-off, scoped prompt, no prompt) give power users flexibility and safety.

View File

@@ -0,0 +1,29 @@
+++
id = "29"
title = "Auto-Approve Empty-Array Tool Invocations"
status = "Not started"
dependencies = "02" # Rationale: depends on Task 02 for auto-approval logic
last_updated = "2025-06-25T01:40:09.600000"
+++
## Summary
Automatically approve tool-use requests where the command array is empty, bypassing the approval prompt.
## Goal
In rare cases the model may emit a tool invocation event with an empty `command: []`. These invocations cannot succeed and continually trigger errors. Automatically treat empty-array tool requests as approved (once), suppressing the approval UI, to allow downstream error handling rather than perpetual prompts.
## Acceptance Criteria
- Detect tool requests where `command: []` (no arguments).
- Do not open the approval prompt for these cases; instead, automatically approve and allow the tool pipeline to proceed (and eventually handle the error).
- Include a unit test simulating an empty-array tool invocation that verifies no approval prompt is shown and that a `ReviewDecision::Approved` is returned immediately.
## Implementation
**How it was implemented**
- In the command-review widget setup (`ApprovalRequest::Exec`), check for `command.is_empty()` before rendering; if empty, directly send `ReviewDecision::Approved` and mark the widget done.
- Add a Rust unit test for `UserApprovalWidget` to feed an `Exec { command: vec![] }` request and assert automatic approval without rendering the select mode.
## Notes
- This is a pragmatic workaround for spurious emptycommand tool calls; a more robust modelside fix may replace this later.

View File

@@ -0,0 +1,41 @@
+++
id = "30"
title = "Non-Fullscreen Scrollback Mode with Native Terminal Scroll"
status = "Not started"
dependencies = "" # No prerequisites
last_updated = "2025-06-25T01:40:09.600000"
+++
## Summary
Offer a non-fullscreen TUI mode that appends conversation output and defers scrolling to the terminal scrollback.
## Goal
Provide an optional non-fullscreen mode for the chat UI where:
- The TUI does not capture the mouse scroll wheel.
- All conversation output is appended in place, allowing the terminal's native scrollback to navigate history.
- The user-entry window remains fixed at the bottom of the terminal.
- The entire UI runs in a standard terminal buffer (no alternate screen), so the user can use their terminals scrollbar or scrollback keys to review past messages.
## Acceptance Criteria
- Introduce a `tui.non_fullscreen_mode` config flag (default `false`).
- When enabled, the application:
- Disables alternate screen buffering (i.e. does not switch to the TUI alt-screen).
- Does not intercept mouse scroll events; scroll events are passed through to the terminal.
- Renders new chat messages inline (appended) rather than redrawing the full viewport.
- Keeps the user input prompt visible at the bottom after each message.
- Add integration tests or manual validation steps to confirm that: scrollback keys/mouse scroll work via terminal scrollback, and the prompt remains in view.
## Implementation
**How it was implemented**
- Add `non_fullscreen_mode: bool` to the `tui` config section.
- In the TUI initialization, skip entering the alternate screen and disable pannable viewports.
- Remove mouse event capture for scroll wheel events when `non_fullscreen_mode` is true.
- Change rendering loop: after each new message, print the message directly to the stdout buffer (in append mode), then redraw only the input prompt line.
- Write integration tests that spawn the TUI in non-fullscreen mode, emit multiple messages, send scroll events (if possible), and assert that scrollback buffer contains the messages.
## Notes
- This mode trades advanced in-TUI scrolling features for simplicity and compatibility with users accustomed terminal scrollback.
- It may not support complex viewport resizing; documentation should note that.

View File

@@ -0,0 +1,49 @@
+++
id = "32"
title = "Embedded Neovim Prompt Editor"
status = "Not started"
dependencies = "06" # Rationale: depends on Task 06 for external editor integration
last_updated = "2025-06-25T01:40:09.513224"
+++
# Task 32: Embedded Neovim Prompt Editor
> *This task is specific to codex-rs.*
## Status
**General Status**: Not started
**Summary**: Not started; missing Implementation details (How it was implemented and How it works).
## Goal
Replace the basic lineediting prompt composer with an embedded Neovim window so users can enjoy full-featured, multi-line editing of their chat prompt directly inside the TUI.
## Acceptance Criteria
- Introduce a TUI-integrated Neovim editor pane activated via `/edit-prompt` or `Ctrl+E` when `embedded_prompt_editor = true` in `[tui]` config.
- Pre-populate the Neovim buffer with the current draft prompt; upon exit, reload the buffer contents back into the composer.
- Support standard Neovim keybindings and commands (e.g. insert mode, visual mode, plugins) within the embedded pane.
- Cleanly restore the previous TUI layout after closing the editor, with prompt focus returned to the composer.
- Provide configuration toggle (`embedded_prompt_editor`) and fall back to external-editor prompt behavior when disabled.
## Implementation
**How it was implemented**
- Add a new module `tui/src/editor/neovim.rs` that wraps a headless Neovim RPC instance and renders its UI into a dedicated TUI layer.
- Extend `tui/src/bottom_pane/chat_composer.rs` to detect `embedded_prompt_editor` and invoke the embedded editor instead of spawning an external process.
- Wire a config flag `embedded_prompt_editor: bool` through `ConfigToml``Config` under the `tui` section, defaulting to `false`.
- Handle Neovim communication via `nvim-rs` crate, multiplexing input/output over the TUI event loop.
**How it works**
- When the user triggers the editor, pause the main TUI rendering and allocate a full-screen or split view for Neovim.
- Start Neovim in embedded RPC mode, passing the current prompt text into a new buffer.
- Drive Neovims UI updates via RPC and render its screen cells into the TUI terminal using termion or similar backend.
- Detect the Neovim exit event (e.g. user `:q` or `ZZ`), fetch the buffer contents, and close the embedded view.
- Restore the original TUI state and update the composer widget with the edited prompt.
## Notes
- This relies on a working `nvim` binary in PATH or specified via `nvim_binary` config.
- Investigate performance impact of embedding a full editor in the TUI; ensure fallback to external-editor remains smooth.
- Consider edge cases (resizing, pluginheavy Neovim configs) and document prerequisites in the README.

View File

@@ -0,0 +1,34 @@
+++
id = "33"
title = "Fix External Editor Focus Issue"
status = "Not started"
summary = "When launching the external editor from the TUI (e.g. nvim), keyboard input is still captured by the Rust TUI, causing keys to split between the editor and the TUI."
dependencies = "06,32" # Rationale: depends on Tasks 06 and 32 for external and embedded editor features
last_updated = "2025-06-25T01:40:09.700000"
+++
# Task 33: Fix External Editor Focus Issue
## Goal
Ensure that when the TUI spawns an external editor, it fully hands off keyboard control to the editor, and upon editor exit, restores TUI input handling without leaking keystrokes or misrouting commands.
## Acceptance Criteria
- Launching external editor via `/edit-prompt` or Ctrl+E disables TUI raw mode and event capture so all keystrokes go directly to the editor.
- Upon editor exit, raw mode and event capture are correctly re-enabled, and no keystrokes are lost or misrouted.
- No residual input events are processed by the TUI while the editor is running.
- Add integration tests or manual validation steps simulating editor launch and exit sequences.
## Implementation
**High-level plan**
- Before spawning the editor process (in `ChatComposer`), call `disable_raw_mode()` and `disable_event_capture()` to restore normal terminal behavior.
- Spawn the editor subprocess and wait for it to exit.
- After exit, re-enable raw mode and event capture via `enable_raw_mode()` and `enable_event_capture()`.
- Wrap this sequence in a helper function (e.g., `spawn_external_editor`) and update the `/edit-prompt` handler to use it.
- Add integration tests in `tui/tests/` that mock the editor command (e.g., `echo`) to verify terminal mode transitions.
## Notes
- Use Crossterm APIs for terminal mode management.
- Ensure interruption signals (e.g., Ctrl+C) during editor sessions are propagated correctly to avoid TUI deadlock.

View File

@@ -0,0 +1,41 @@
+++
id = "34"
title = "Complete Set Shell Title to Reflect Session Status"
status = "Not started"
dependencies = "08" # Rationale: depends on Task 08 for initial shell title change
last_updated = "2025-06-25T04:45:29Z"
+++
> *This task is specific to codex-rs.*
## Status
**General Status**: Not started
**Summary**: Follow-up to Task 08; implementation missing for core title persistence and ANSI updates.
## Goal
Implement the missing pieces from Task 08 to fully support dynamic and persistent shell title updates:
1. Define `SessionUpdatedTitleEvent` and add a `title` field in `SessionConfiguredEvent` (core protocol).
2. Introduce `Op::SetTitle(String)` variant and handle it in the core agent loop, persisting the title and emitting the update event.
3. Update TUI and exec clients to listen for title events and emit ANSI escape sequences (`\x1b]0;<title>\x07`) for live terminal title changes.
4. Restore the persisted title on session resume via `SessionConfiguredEvent`.
## Acceptance Criteria
- New `SessionUpdatedTitleEvent` type in `codex_core::protocol` and `title` field in `SessionConfiguredEvent`.
- `Op::SetTitle(String)` variant in the protocol and core event handling persisted in session metadata.
- Clients broadcast ANSI title-setting sequences on title events and lifecycle state changes.
- Unit tests for protocol serialization and client reaction to title updates.
## Implementation
**How it was implemented**
*(Not implemented yet)*
**How it works**
*(Not implemented yet)*
## Notes
- Use ANSI escape code `\x1b]0;<title>\x07` for setting terminal title.

View File

@@ -0,0 +1,39 @@
+++
id = "36"
title = "Add Tests for Interactive Prompting While Executing"
status = "Not started"
dependencies = "06,13" # Rationale: depends on Tasks 06 and 13 for external editor and interactive prompt support
last_updated = "2025-06-25T11:05:55Z"
+++
> *This task is specific to codex-rs.*
## Status
**General Status**: Done
**Summary**: Follow-up to Task 13; add unit tests for interactive prompt overlay during execution.
## Goal
Write tests that verify `BottomPane::handle_key_event` forwards input to the composer while `is_task_running`, preserving the status overlay until completion.
## Acceptance Criteria
- Unit tests covering key events (e.g. alphanumeric, Enter) during `is_task_running == true`.
- Assertions that `active_view` remains a `StatusIndicatorView` while running and is removed when `set_task_running(false)` is called.
- Coverage of redraw requests and correct `InputResult` values.
## Implementation
**Planned Approach**
- Use existing `make_pane` and `make_pane_and_rx` helpers to create a `BottomPane` in a running-task state.
- Write unit tests in `tui/src/bottom_pane/mod.rs` that verify:
- Typing alphanumeric characters while `is_task_running == true` appends to the composer, maintains the `StatusIndicatorView` overlay, and emits a `AppEvent::Redraw`.
- Pressing Enter returns `InputResult::Submitted` with the buffered text, clears the composer, retains the overlay, and triggers a redraw.
- Calling `set_task_running(false)` removes the status indicator overlay.
- Follow existing patterns from the tests in `user_approval_widget.rs` and `set_title_view.rs`.
## Notes
- Refer to existing tests in `user_approval_widget.rs` and `set_title_view.rs` for testing patterns.

View File

@@ -0,0 +1,41 @@
+++
id = "37"
title = "Session State Persistence and Debug Instrumentation"
status = "Not started"
dependencies = ""
last_updated = "2025-06-25T23:00:00.000000"
+++
## Summary
Persist session runtime state and capture raw request/response data and supplemental metadata to a session-specific directory.
## Goal
Collect and persist all relevant session state (beyond the rollout transcript) in a dedicated directory under `.codex/sessions/<UUID>/`, to aid debugging and allow post-mortem analysis.
## Acceptance Criteria
- All session data (transcript, logs, raw OpenAI API requests/responses, approval events, and other runtime metadata) is written under `.codex/sessions/<session_id>/`.
- Existing rollout transcript continues to be written to `sessions/rollout-<UUID>.jsonl`, now moved or linked into the session directory.
- Logging configuration respects `--debug-log` and writes to the session directory when set to a relative path.
- A selector flag (e.g. `--persist-session`) enables or disables writing persistent state.
- No change to default behavior when persistence is disabled (i.e. backward compatibility).
- Minimal integration test or manual verification steps demonstrate that files appear correctly and no extraneous error logs occur.
## Implementation
**How it was implemented**
- Add a new CLI flag `--persist-session` to the TUI and server binaries to enable session persistence.
- Compute a session directory under `$CODEX_HOME/sessions/<UUID>/`, create it at startup when persistence is enabled.
- After initializing the rollout file (`rollout-<UUID>.jsonl`), move or symlink it into the session directory.
- Configure tracing subscriber file layer and `--debug-log` default path to write logs into the same session directory (e.g. `session.log`).
- Instrument the OpenAI HTTP client layer to dump raw request and response bodies into `session_oai_raw.log` in that directory.
- In the message sequencing logic, add debug spans to record approval and cancellation events into `session_meta.log`.
**How it works**
- When `--persist-session` is active, all file outputs (rollout transcript, debug logs, raw API dumps, metadata logs) are collated under a single session directory.
- If disabled (default), writes occur in the existing locations (`rollout-<UUID>.jsonl`, `$CODEX_HOME/log/`), preserving current behavior.
## Notes
- This feature streamlines troubleshooting by co-locating all session artifacts.
- Ensure directory creation and file writes handle permission errors gracefully and fallback cleanly when disabled.

Some files were not shown because too many files have changed in this diff Show More