holo-codex 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/README.md +141 -75
- package/README.zh-CN.md +141 -75
- package/docs/release-checklist.md +12 -8
- package/package.json +2 -1
- package/plugins/autonomous-pr-loop/.codex-plugin/plugin.json +1 -1
- package/plugins/autonomous-pr-loop/core/cli.ts +17 -1
- package/plugins/autonomous-pr-loop/core/doctor.ts +35 -11
- package/plugins/autonomous-pr-loop/core/hook-diagnostics.ts +153 -0
- package/plugins/autonomous-pr-loop/core/local-install.ts +13 -19
- package/plugins/autonomous-pr-loop/hooks/hooks.json +1 -104
- package/plugins/autonomous-pr-loop/mcp-server/src/index.ts +1 -1
- package/plugins/autonomous-pr-loop/package.json +1 -1
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.1
|
|
4
|
+
|
|
5
|
+
- Fixed the bundled Codex plugin hooks schema for newer Codex config parsers.
|
|
6
|
+
- Kept stable runtime identifiers unchanged: `agent-loop`, `.agent-loop/`, and `autonomous-pr-loop`.
|
|
7
|
+
|
|
8
|
+
## 0.1.0
|
|
9
|
+
|
|
10
|
+
- Initial public npm release of HOLO-Codex.
|
package/README.md
CHANGED
|
@@ -4,62 +4,109 @@
|
|
|
4
4
|
|
|
5
5
|

|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
[](https://www.npmjs.com/package/holo-codex)
|
|
8
|
+
[](https://www.npmjs.com/package/holo-codex)
|
|
9
|
+
[](https://github.com/tizerluo/HOLO-Codex/releases)
|
|
10
|
+
[](https://github.com/tizerluo/HOLO-Codex/actions/workflows/ci.yml)
|
|
11
|
+
[](./LICENSE)
|
|
12
|
+

|
|
8
13
|
|
|
9
|
-
|
|
14
|
+
HOLO-Codex, short for **Human On Loop Codex**, turns long-running Codex workflows into observable, recoverable systems that you can supervise without living inside chat history.
|
|
10
15
|
|
|
11
|
-
|
|
16
|
+
Codex can plan, edit, test, review, and hand work across agents. HOLO-Codex gives that work a local control plane: workflow state, evidence, gates, hooks, review status, recovery actions, and a dashboard you can open while the work is running.
|
|
12
17
|
|
|
13
|
-
|
|
14
|
-
- The `agent-loop` CLI for local loop state, hooks, dashboard, workflow evidence, and rollback-safe local install.
|
|
15
|
-
- Local SQLite state under `.agent-loop/`.
|
|
16
|
-
- A local dashboard for Mission Control, workflow board, observability, gates, review/CI state, workers, artifacts, recovery, notifications, policy config, and theme modes.
|
|
17
|
-
- stdio MCP control plane.
|
|
18
|
-
- Codex hooks for policy checks and observability.
|
|
19
|
-
- TypeScript + Vitest test suite.
|
|
20
|
-
- Bilingual display support for `zh-CN`, `en-US`, and `system` locale selection.
|
|
21
|
-
- Workflow profiles, role profiles, `generic-loop`, and the first bundled workflow: `pr-loop`.
|
|
18
|
+
## Quickstart
|
|
22
19
|
|
|
23
|
-
|
|
20
|
+
Install the package and attach HOLO-Codex to a target repository:
|
|
24
21
|
|
|
25
|
-
|
|
22
|
+
```bash
|
|
23
|
+
npm install --global holo-codex
|
|
24
|
+
agent-loop --repo /path/to/repo init
|
|
25
|
+
agent-loop install-hooks --repo /path/to/repo
|
|
26
|
+
agent-loop --repo /path/to/repo dashboard
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Open the printed loopback URL. The dashboard unlocks locally without putting a token in the URL.
|
|
26
30
|
|
|
27
|
-
|
|
31
|
+
Plugin enablement is separate from the CLI install:
|
|
28
32
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
- Source directory: `plugins/autonomous-pr-loop/`
|
|
33
|
-
- npm package name: `holo-codex`
|
|
34
|
-
- Local marketplace entry name: `codex-auto-pr-loop`
|
|
33
|
+
```bash
|
|
34
|
+
codex plugin marketplace add "$(npm root -g)/holo-codex"
|
|
35
|
+
```
|
|
35
36
|
|
|
36
|
-
|
|
37
|
+
Then enable the `autonomous-pr-loop` plugin in Codex. The CLI is called `agent-loop` for compatibility.
|
|
37
38
|
|
|
38
|
-
##
|
|
39
|
+
## Why HOLO-Codex?
|
|
40
|
+
|
|
41
|
+
| Problem | What HOLO-Codex adds |
|
|
42
|
+
| --- | --- |
|
|
43
|
+
| Long Codex tasks disappear into a thread. | A workflow board shows the current stage, state source, evidence, and next action. |
|
|
44
|
+
| Manual decisions, review reports, and CI signals live in different places. | Evidence is recorded as append-only workflow events and surfaced in the dashboard. |
|
|
45
|
+
| Recovery gets hard after a pause, gate, failed worker, or context switch. | Gates, recovery actions, timeline, artifacts, and hook diagnostics stay tied to the run. |
|
|
46
|
+
|
|
47
|
+
## What It Gives You
|
|
48
|
+
|
|
49
|
+
- Workflow Board for long Codex workflows, including stage status, evidence counts, and current-stage details.
|
|
50
|
+
- Evidence Trail for plans, implementation notes, tests, reviews, PR actions, CI checks, merge readiness, and cleanup.
|
|
51
|
+
- Gates and Recovery for policy blocks, human approval, stale runs, and historical gate re-evaluation.
|
|
52
|
+
- Hook-based Observability that routes Codex hook events by repo, worktree, and session context.
|
|
53
|
+
- Local Dashboard for Mission Control, observability, gates, review/CI state, workers, artifacts, notifications, policy config, and theme modes.
|
|
54
|
+
- CLI and stdio MCP control plane for scripts, skills, and Codex plugin actions.
|
|
55
|
+
- Review and CI visibility through structured review evidence, severity summaries, PR comment links, and merge readiness checks.
|
|
56
|
+
|
|
57
|
+
HOLO-Codex is local-first. It is not a hosted service, and it does not run cloud workers or GitHub webhooks for you.
|
|
39
58
|
|
|
40
|
-
|
|
59
|
+
## How It Fits Together
|
|
41
60
|
|
|
42
|
-
|
|
61
|
+
```mermaid
|
|
62
|
+
flowchart LR
|
|
63
|
+
A["Codex skill or commander"] --> B["agent-loop CLI / MCP"]
|
|
64
|
+
B --> C["Local workflow state<br/>.agent-loop SQLite"]
|
|
65
|
+
B --> D["Codex hooks<br/>repo / worktree / session routing"]
|
|
66
|
+
C --> E["Dashboard workflow board"]
|
|
67
|
+
D --> E
|
|
68
|
+
E --> F["Gates and recovery"]
|
|
69
|
+
E --> G["Review, CI, PR, cleanup evidence"]
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
The dashboard and MCP tools read persisted loop state. They do not depend on the chat transcript being intact.
|
|
73
|
+
|
|
74
|
+
## First Workflow: PR Delivery
|
|
75
|
+
|
|
76
|
+
PR delivery is the first bundled workflow. It is the strongest sample today, not the product boundary.
|
|
43
77
|
|
|
44
78
|
```text
|
|
45
|
-
|
|
46
|
-
bind work item
|
|
47
|
-
plan
|
|
48
|
-
build
|
|
49
|
-
verify
|
|
50
|
-
open PR
|
|
51
|
-
run review / CI
|
|
52
|
-
fix findings
|
|
53
|
-
check merge readiness
|
|
54
|
-
merge
|
|
55
|
-
cleanup
|
|
79
|
+
Work Item -> Plan -> Build -> Verify -> PR -> Review -> Merge Readiness -> Cleanup
|
|
56
80
|
```
|
|
57
81
|
|
|
58
|
-
The
|
|
82
|
+
The bundled PR workflow can bind a GitHub issue, track implementation evidence, collect tester and reviewer reports, show PR and CI readiness, and keep cleanup visible after merge.
|
|
83
|
+
|
|
84
|
+
The same control-plane model can support other long-running Codex workflows:
|
|
85
|
+
|
|
86
|
+
- release preparation
|
|
87
|
+
- repo hygiene
|
|
88
|
+
- security review
|
|
89
|
+
- docs publishing
|
|
90
|
+
- migrations
|
|
91
|
+
- evaluations
|
|
92
|
+
- customer issue triage
|
|
93
|
+
|
|
94
|
+
## Compatibility Names
|
|
95
|
+
|
|
96
|
+
HOLO-Codex is the public product name. Some runtime identifiers keep older names so existing installs and local state do not break:
|
|
59
97
|
|
|
60
|
-
|
|
98
|
+
| Public concept | Stable identifier |
|
|
99
|
+
| --- | --- |
|
|
100
|
+
| CLI command | `agent-loop` |
|
|
101
|
+
| Runtime state directory | `.agent-loop/` |
|
|
102
|
+
| Plugin id and MCP server id | `autonomous-pr-loop` |
|
|
103
|
+
| Source directory | `plugins/autonomous-pr-loop/` |
|
|
104
|
+
| npm package | `holo-codex` |
|
|
105
|
+
| Local marketplace entry | `codex-auto-pr-loop` |
|
|
61
106
|
|
|
62
|
-
|
|
107
|
+
These are compatibility identifiers, not separate products.
|
|
108
|
+
|
|
109
|
+
## Install Details
|
|
63
110
|
|
|
64
111
|
Canonical public source:
|
|
65
112
|
|
|
@@ -74,69 +121,56 @@ Requirements:
|
|
|
74
121
|
- GitHub CLI `gh`
|
|
75
122
|
- Codex CLI / plugin support
|
|
76
123
|
- `pnpm` when installing from source or using rollback-snapshot local install
|
|
77
|
-
- Optional
|
|
124
|
+
- Optional: GitNexus via `npx gitnexus`
|
|
78
125
|
|
|
79
126
|
Install from npm:
|
|
80
127
|
|
|
81
128
|
```bash
|
|
82
129
|
npm install --global holo-codex
|
|
83
|
-
# Replace /path/to/repo with the repository you want HOLO-Codex to supervise.
|
|
84
130
|
agent-loop --repo /path/to/repo init
|
|
85
131
|
agent-loop install-hooks --repo /path/to/repo
|
|
86
132
|
agent-loop --repo /path/to/repo doctor
|
|
87
133
|
```
|
|
88
134
|
|
|
89
|
-
The npm package installs the `agent-loop` CLI. `agent-loop install-hooks` installs or refreshes the hook router and target binding without reinstalling the global CLI.
|
|
135
|
+
The npm package installs the `agent-loop` CLI. `agent-loop install-hooks` installs or refreshes the hook router and target binding without reinstalling the global CLI.
|
|
136
|
+
|
|
137
|
+
To remove an npm install:
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
agent-loop hooks unbind --repo /path/to/repo
|
|
141
|
+
npm uninstall --global holo-codex
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Remove HOLO-Codex router entries from `~/.codex/hooks.json` only when no target repositories still use them.
|
|
90
145
|
|
|
91
|
-
Install from source when developing HOLO-Codex or when you want to inspect the
|
|
146
|
+
Install from source when developing HOLO-Codex or when you want to inspect the checkout:
|
|
92
147
|
|
|
93
148
|
```bash
|
|
94
149
|
git clone https://github.com/tizerluo/HOLO-Codex.git
|
|
95
150
|
cd HOLO-Codex
|
|
96
151
|
pnpm install
|
|
97
152
|
pnpm build:hooks
|
|
98
|
-
# Replace /path/to/repo with the repository you want HOLO-Codex to supervise.
|
|
99
153
|
pnpm agent-loop local install --repo /path/to/repo
|
|
100
154
|
agent-loop --repo /path/to/repo status
|
|
101
155
|
```
|
|
102
156
|
|
|
103
|
-
`pnpm agent-loop ...`
|
|
157
|
+
`pnpm agent-loop ...` runs from the source checkout. `agent-loop ...` is the global command after npm or local source install.
|
|
104
158
|
|
|
105
|
-
For
|
|
159
|
+
For the full install, upgrade, reinstall, uninstall, rollback, and smoke-test checklist, see [Local Release Readiness](./docs/local-release-readiness.md).
|
|
106
160
|
|
|
107
|
-
|
|
161
|
+
## Initialize A Repo
|
|
108
162
|
|
|
109
|
-
|
|
110
|
-
codex plugin marketplace add "$(npm root -g)/holo-codex"
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
For source installs:
|
|
114
|
-
|
|
115
|
-
```bash
|
|
116
|
-
codex plugin marketplace add /path/to/HOLO-Codex
|
|
117
|
-
```
|
|
118
|
-
|
|
119
|
-
Then enable the `autonomous-pr-loop` plugin in Codex. Plugin enablement and global CLI installation are separate steps.
|
|
120
|
-
|
|
121
|
-
## Initialize State
|
|
122
|
-
|
|
123
|
-
Run from the target repository root:
|
|
163
|
+
Run from the target repository root or pass `--repo`:
|
|
124
164
|
|
|
125
165
|
```bash
|
|
126
166
|
agent-loop --repo /path/to/repo init
|
|
127
167
|
agent-loop --repo /path/to/repo doctor
|
|
128
|
-
agent-loop --repo /path/to/repo status
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
Install Codex hooks:
|
|
132
|
-
|
|
133
|
-
```bash
|
|
134
168
|
agent-loop install-hooks --repo /path/to/repo
|
|
135
169
|
```
|
|
136
170
|
|
|
137
|
-
|
|
171
|
+
Hook installation writes one router into `~/.codex/hooks.json`, preserves existing hooks, and records target repository bindings under `~/.codex/agent-loop/hook-bindings.json`.
|
|
138
172
|
|
|
139
|
-
|
|
173
|
+
Multiple repositories can share the same `CODEX_HOME`. Hook events are routed by Codex cwd, worktree, and session context before any repository state is written or policy is applied. A separate `CODEX_HOME` is still useful for high-isolation sandbox testing.
|
|
140
174
|
|
|
141
175
|
Runtime files are written to `.agent-loop/` and must not be committed.
|
|
142
176
|
|
|
@@ -146,7 +180,7 @@ Runtime files are written to `.agent-loop/` and must not be committed.
|
|
|
146
180
|
agent-loop --repo /path/to/repo dashboard
|
|
147
181
|
```
|
|
148
182
|
|
|
149
|
-
The command prints a loopback URL
|
|
183
|
+
The command prints a loopback URL:
|
|
150
184
|
|
|
151
185
|
```text
|
|
152
186
|
dashboard started
|
|
@@ -154,9 +188,9 @@ url: http://127.0.0.1:<port>/
|
|
|
154
188
|
targetRepoRoot: /path/to/repo
|
|
155
189
|
```
|
|
156
190
|
|
|
157
|
-
Dashboard mutations require the local session token and go through the shared controller. The UI does not write SQLite directly. Loopback dashboard sessions unlock through a same-origin
|
|
191
|
+
Dashboard mutations require the local session token and go through the shared controller. The UI does not write SQLite directly. Loopback dashboard sessions unlock through a same-origin bootstrap; the stderr token is only a fallback for static UI or recovery. Do not copy it into docs, logs, PR bodies, commits, artifacts, or screenshots.
|
|
158
192
|
|
|
159
|
-
Dashboard-visible delivery work
|
|
193
|
+
Dashboard-visible delivery work comes from persisted `agent-loop` actions and workflow evidence. Direct terminal edits or commander decisions become visible only when they are recorded as agent-loop events, artifacts, or PR comments.
|
|
160
194
|
|
|
161
195
|
## Common CLI
|
|
162
196
|
|
|
@@ -182,7 +216,7 @@ Human-readable CLI output supports `--locale zh-CN|en-US|system`. JSON output re
|
|
|
182
216
|
|
|
183
217
|
## Workflow Profiles And Themes
|
|
184
218
|
|
|
185
|
-
The default workflow
|
|
219
|
+
The default workflow is `pr-loop` with `default_pr_loop` and `default_pr_roles`. Policy Config can also select `generic-loop` with built-in profiles for research reports, document preparation, repo hygiene, weekly review, and data extraction workflows. For a concrete non-PR workflow, see the [generic-loop repo hygiene example](./docs/examples/generic-loop-repo-hygiene.md).
|
|
186
220
|
|
|
187
221
|
Dashboard theme is a local browser preference. It supports `light`, `dark`, and `system`, and does not write to repo config or SQLite.
|
|
188
222
|
|
|
@@ -192,9 +226,41 @@ Dashboard theme is a local browser preference. It supports `light`, `dark`, and
|
|
|
192
226
|
- The supervisor owns Git and GitHub lifecycle actions.
|
|
193
227
|
- Destructive Git/GitHub commands are blocked by command policy and hooks.
|
|
194
228
|
- Merge readiness depends on config, review/CI evidence, open review comments, scope guard, and policy decisions.
|
|
195
|
-
-
|
|
229
|
+
- HOLO-Codex must not store secrets, raw prompts, raw transcripts, dashboard tokens, or raw hook payloads in docs, logs, artifacts, commits, or PR bodies.
|
|
196
230
|
- Hooks cover the Codex tool loop, not manual commands run in an external terminal.
|
|
197
231
|
|
|
232
|
+
See [Trust and Safety](./docs/trust-and-safety.md) and [Security](./SECURITY.md).
|
|
233
|
+
|
|
234
|
+
## FAQ
|
|
235
|
+
|
|
236
|
+
### Is HOLO-Codex hosted?
|
|
237
|
+
|
|
238
|
+
No. HOLO-Codex runs locally. The dashboard, SQLite state, hooks, and CLI live on your machine.
|
|
239
|
+
|
|
240
|
+
### Does it replace Codex?
|
|
241
|
+
|
|
242
|
+
No. Codex still does the work. HOLO-Codex gives long Codex workflows state, evidence, gates, recovery, and a dashboard.
|
|
243
|
+
|
|
244
|
+
### Why is the CLI still called `agent-loop`?
|
|
245
|
+
|
|
246
|
+
`agent-loop` is the stable runtime command. Keeping it avoids breaking existing installs, scripts, hooks, and local state.
|
|
247
|
+
|
|
248
|
+
### Why is the plugin id still `autonomous-pr-loop`?
|
|
249
|
+
|
|
250
|
+
That id is part of the existing plugin and MCP wiring. HOLO-Codex is the public product name; `autonomous-pr-loop` is a compatibility identifier.
|
|
251
|
+
|
|
252
|
+
### Does it auto-merge PRs?
|
|
253
|
+
|
|
254
|
+
No. HOLO-Codex can track merge readiness and evidence, but the supervisor controls Git and GitHub lifecycle actions.
|
|
255
|
+
|
|
256
|
+
### Does it require GitHub?
|
|
257
|
+
|
|
258
|
+
The bundled PR delivery workflow expects GitHub and `gh`. The underlying loop model can support non-PR workflows through `generic-loop`.
|
|
259
|
+
|
|
260
|
+
### Where is state stored?
|
|
261
|
+
|
|
262
|
+
Repository runtime state lives under `.agent-loop/`. Hook routing bindings live under `~/.codex/agent-loop/`. Runtime files should not be committed.
|
|
263
|
+
|
|
198
264
|
## Development
|
|
199
265
|
|
|
200
266
|
```bash
|
package/README.zh-CN.md
CHANGED
|
@@ -4,62 +4,109 @@
|
|
|
4
4
|
|
|
5
5
|

|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
[](https://www.npmjs.com/package/holo-codex)
|
|
8
|
+
[](https://www.npmjs.com/package/holo-codex)
|
|
9
|
+
[](https://github.com/tizerluo/HOLO-Codex/releases)
|
|
10
|
+
[](https://github.com/tizerluo/HOLO-Codex/actions/workflows/ci.yml)
|
|
11
|
+
[](./LICENSE)
|
|
12
|
+

|
|
8
13
|
|
|
9
|
-
|
|
14
|
+
HOLO-Codex 是 **Human On Loop Codex** 的缩写。它把长流程 Codex workflow 变成可观察、可恢复、可人工接管的系统,让你不用一直守在聊天记录里猜它做到了哪一步。
|
|
10
15
|
|
|
11
|
-
|
|
16
|
+
Codex 负责计划、改代码、测试、review 和调用子代理。HOLO-Codex 负责把这些工作落到本地 control plane 上:workflow 状态、evidence、gates、hooks、review 状态、恢复动作,以及一个可以边跑边看的 dashboard。
|
|
12
17
|
|
|
13
|
-
|
|
14
|
-
- `agent-loop` CLI:管理本地 loop 状态、hooks、dashboard、workflow evidence 和可回滚本地安装。
|
|
15
|
-
- `.agent-loop/` 下的本地 SQLite 状态。
|
|
16
|
-
- 本地 dashboard:Mission Control、workflow board、Observability Console、Gate、Review/CI、Worker、Artifact、Notifications、Recovery、Policy Config、主题模式。
|
|
17
|
-
- stdio MCP control plane。
|
|
18
|
-
- 用于 policy 检查和 observability 的 Codex hooks。
|
|
19
|
-
- TypeScript + Vitest 测试套件。
|
|
20
|
-
- `zh-CN`、`en-US`、`system` 双语显示支持。
|
|
21
|
-
- workflow profile、role profile、`generic-loop`,以及第一个内置 workflow:`pr-loop`。
|
|
18
|
+
## 快速开始
|
|
22
19
|
|
|
23
|
-
|
|
20
|
+
安装 npm package,并把 HOLO-Codex 接到一个目标仓库:
|
|
24
21
|
|
|
25
|
-
|
|
22
|
+
```bash
|
|
23
|
+
npm install --global holo-codex
|
|
24
|
+
agent-loop --repo /path/to/repo init
|
|
25
|
+
agent-loop install-hooks --repo /path/to/repo
|
|
26
|
+
agent-loop --repo /path/to/repo dashboard
|
|
27
|
+
```
|
|
26
28
|
|
|
27
|
-
|
|
29
|
+
打开命令打印出来的本地 URL。Dashboard 会在本机自动解锁,不会把 token 放进 URL。
|
|
28
30
|
|
|
29
|
-
|
|
30
|
-
- 运行态目录:`.agent-loop/`
|
|
31
|
-
- Plugin id 和 MCP server id:`autonomous-pr-loop`
|
|
32
|
-
- 源码目录:`plugins/autonomous-pr-loop/`
|
|
33
|
-
- npm package 名:`holo-codex`
|
|
34
|
-
- 本地 marketplace 条目名:`codex-auto-pr-loop`
|
|
31
|
+
Codex plugin 启用和 CLI 安装是两件事:
|
|
35
32
|
|
|
36
|
-
|
|
33
|
+
```bash
|
|
34
|
+
codex plugin marketplace add "$(npm root -g)/holo-codex"
|
|
35
|
+
```
|
|
37
36
|
|
|
38
|
-
|
|
37
|
+
然后在 Codex 里启用 `autonomous-pr-loop` 插件。CLI 继续叫 `agent-loop`,这是兼容已有安装和本地状态的名字。
|
|
38
|
+
|
|
39
|
+
## 为什么需要 HOLO-Codex
|
|
39
40
|
|
|
40
|
-
|
|
41
|
+
| 问题 | HOLO-Codex 补上的东西 |
|
|
42
|
+
| --- | --- |
|
|
43
|
+
| 长流程 Codex 任务容易消失在一个聊天线程里。 | Workflow board 会显示当前阶段、状态来源、证据和下一步。 |
|
|
44
|
+
| 人工决策、review report、CI 信号分散在不同地方。 | Evidence 会作为 append-only workflow event 记录下来,并显示在 dashboard。 |
|
|
45
|
+
| 一旦暂停、遇到 gate、worker 失败或切上下文,恢复成本很高。 | Gate、recovery、timeline、artifact 和 hook 诊断都会绑定到同一个 run。 |
|
|
41
46
|
|
|
42
|
-
|
|
47
|
+
## 它提供什么
|
|
48
|
+
|
|
49
|
+
- Workflow Board:展示长流程 Codex workflow 的阶段状态、证据数量和当前阶段详情。
|
|
50
|
+
- Evidence Trail:记录计划、实现、测试、review、PR、CI、merge readiness 和 cleanup。
|
|
51
|
+
- Gates and Recovery:处理 policy block、人工批准、stale run 和历史 gate re-evaluate。
|
|
52
|
+
- Hook-based Observability:按 repo、worktree、session context 路由 Codex hook events。
|
|
53
|
+
- Local Dashboard:包含 Mission Control、Observability Console、Gate、Review/CI、Worker、Artifact、Notifications、Recovery、Policy Config 和主题模式。
|
|
54
|
+
- CLI 和 stdio MCP control plane:供脚本、skills 和 Codex plugin 调用。
|
|
55
|
+
- Review and CI visibility:通过结构化 review evidence、severity summary、PR comment link 和 merge readiness 追踪交付状态。
|
|
56
|
+
|
|
57
|
+
HOLO-Codex 是 local-first 工具。它不是托管服务,也不会替你运行云端 worker 或 GitHub webhook daemon。
|
|
58
|
+
|
|
59
|
+
## 它如何工作
|
|
60
|
+
|
|
61
|
+
```mermaid
|
|
62
|
+
flowchart LR
|
|
63
|
+
A["Codex skill or commander"] --> B["agent-loop CLI / MCP"]
|
|
64
|
+
B --> C["Local workflow state<br/>.agent-loop SQLite"]
|
|
65
|
+
B --> D["Codex hooks<br/>repo / worktree / session routing"]
|
|
66
|
+
C --> E["Dashboard workflow board"]
|
|
67
|
+
D --> E
|
|
68
|
+
E --> F["Gates and recovery"]
|
|
69
|
+
E --> G["Review, CI, PR, cleanup evidence"]
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Dashboard 和 MCP tools 读取持久化 loop 状态,不依赖聊天记录还在不在。
|
|
73
|
+
|
|
74
|
+
## 第一个工作流:PR 交付
|
|
75
|
+
|
|
76
|
+
PR 交付是 HOLO-Codex 随附的第一个完整 workflow,也是目前最完整的样板,但它不是产品边界。
|
|
43
77
|
|
|
44
78
|
```text
|
|
45
|
-
|
|
46
|
-
bind work item
|
|
47
|
-
plan
|
|
48
|
-
build
|
|
49
|
-
verify
|
|
50
|
-
open PR
|
|
51
|
-
run review / CI
|
|
52
|
-
fix findings
|
|
53
|
-
check merge readiness
|
|
54
|
-
merge
|
|
55
|
-
cleanup
|
|
79
|
+
Work Item -> Plan -> Build -> Verify -> PR -> Review -> Merge Readiness -> Cleanup
|
|
56
80
|
```
|
|
57
81
|
|
|
58
|
-
|
|
82
|
+
内置 PR workflow 可以绑定 GitHub issue、追踪实现证据、收集 tester 和 reviewer report、显示 PR/CI readiness,并在 merge 后继续保留 cleanup 状态。
|
|
83
|
+
|
|
84
|
+
同一套 control plane 以后也可以承载其他长流程 Codex workflow:
|
|
85
|
+
|
|
86
|
+
- release 准备
|
|
87
|
+
- repo hygiene
|
|
88
|
+
- security review
|
|
89
|
+
- docs publishing
|
|
90
|
+
- migration
|
|
91
|
+
- evaluation
|
|
92
|
+
- customer issue triage
|
|
93
|
+
|
|
94
|
+
## 兼容名称
|
|
95
|
+
|
|
96
|
+
HOLO-Codex 是公开产品名。一些运行时标识会继续保留旧名称,避免破坏已有安装、脚本、hooks 和本地状态:
|
|
59
97
|
|
|
60
|
-
|
|
98
|
+
| 公开概念 | 稳定标识 |
|
|
99
|
+
| --- | --- |
|
|
100
|
+
| CLI 命令 | `agent-loop` |
|
|
101
|
+
| 运行态目录 | `.agent-loop/` |
|
|
102
|
+
| Plugin id 和 MCP server id | `autonomous-pr-loop` |
|
|
103
|
+
| 源码目录 | `plugins/autonomous-pr-loop/` |
|
|
104
|
+
| npm package | `holo-codex` |
|
|
105
|
+
| 本地 marketplace 条目 | `codex-auto-pr-loop` |
|
|
106
|
+
|
|
107
|
+
这些是兼容标识,不是第二个产品名。
|
|
61
108
|
|
|
62
|
-
##
|
|
109
|
+
## 安装细节
|
|
63
110
|
|
|
64
111
|
公开源码入口:
|
|
65
112
|
|
|
@@ -74,19 +121,27 @@ https://github.com/tizerluo/HOLO-Codex
|
|
|
74
121
|
- GitHub CLI `gh`
|
|
75
122
|
- Codex CLI / plugin support
|
|
76
123
|
- 从源码安装或使用 snapshot/rollback local install 时需要 `pnpm`
|
|
77
|
-
-
|
|
124
|
+
- 可选:GitNexus,使用 `npx gitnexus`
|
|
78
125
|
|
|
79
126
|
从 npm 安装:
|
|
80
127
|
|
|
81
128
|
```bash
|
|
82
129
|
npm install --global holo-codex
|
|
83
|
-
# 将 /path/to/repo 替换成你要让 HOLO-Codex 监督的目标仓库。
|
|
84
130
|
agent-loop --repo /path/to/repo init
|
|
85
131
|
agent-loop install-hooks --repo /path/to/repo
|
|
86
132
|
agent-loop --repo /path/to/repo doctor
|
|
87
133
|
```
|
|
88
134
|
|
|
89
|
-
npm package 会安装 `agent-loop` CLI。`agent-loop install-hooks` 会安装或刷新 hook router 和目标仓库绑定,不会重新安装全局 CLI
|
|
135
|
+
npm package 会安装 `agent-loop` CLI。`agent-loop install-hooks` 会安装或刷新 hook router 和目标仓库绑定,不会重新安装全局 CLI。
|
|
136
|
+
|
|
137
|
+
移除 npm 安装:
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
agent-loop hooks unbind --repo /path/to/repo
|
|
141
|
+
npm uninstall --global holo-codex
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
只有确认没有任何目标仓库还在使用 HOLO-Codex router 后,才从 `~/.codex/hooks.json` 移除 HOLO-Codex router entries。
|
|
90
145
|
|
|
91
146
|
开发 HOLO-Codex 或需要直接检查源码 checkout 时,从源码安装:
|
|
92
147
|
|
|
@@ -95,48 +150,27 @@ git clone https://github.com/tizerluo/HOLO-Codex.git
|
|
|
95
150
|
cd HOLO-Codex
|
|
96
151
|
pnpm install
|
|
97
152
|
pnpm build:hooks
|
|
98
|
-
# 将 /path/to/repo 替换成你要让 HOLO-Codex 监督的目标仓库。
|
|
99
153
|
pnpm agent-loop local install --repo /path/to/repo
|
|
100
154
|
agent-loop --repo /path/to/repo status
|
|
101
155
|
```
|
|
102
156
|
|
|
103
|
-
`pnpm agent-loop ...` 是源码 checkout 内的命令。`agent-loop ...` 是 npm
|
|
104
|
-
|
|
105
|
-
完整的本地安装、升级、重装、卸载和 smoke test 清单见:[Local Release Readiness](./docs/local-release-readiness.md)。
|
|
106
|
-
|
|
107
|
-
把 HOLO-Codex 加入本地 Codex plugin marketplace。npm 安装时:
|
|
108
|
-
|
|
109
|
-
```bash
|
|
110
|
-
codex plugin marketplace add "$(npm root -g)/holo-codex"
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
源码安装时:
|
|
157
|
+
`pnpm agent-loop ...` 是源码 checkout 内的命令。`agent-loop ...` 是 npm 或本地源码安装后从任意目录使用的全局命令。
|
|
114
158
|
|
|
115
|
-
|
|
116
|
-
codex plugin marketplace add /path/to/HOLO-Codex
|
|
117
|
-
```
|
|
159
|
+
完整的安装、升级、重装、卸载、rollback 和 smoke test 清单见:[Local Release Readiness](./docs/local-release-readiness.md)。
|
|
118
160
|
|
|
119
|
-
|
|
161
|
+
## 初始化一个仓库
|
|
120
162
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
在目标仓库根目录执行:
|
|
163
|
+
在目标仓库根目录执行,或显式传 `--repo`:
|
|
124
164
|
|
|
125
165
|
```bash
|
|
126
166
|
agent-loop --repo /path/to/repo init
|
|
127
167
|
agent-loop --repo /path/to/repo doctor
|
|
128
|
-
agent-loop --repo /path/to/repo status
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
安装 Codex hooks:
|
|
132
|
-
|
|
133
|
-
```bash
|
|
134
168
|
agent-loop install-hooks --repo /path/to/repo
|
|
135
169
|
```
|
|
136
170
|
|
|
137
|
-
|
|
171
|
+
Hook 安装会向 `~/.codex/hooks.json` 写入一组 router,保留已有 hooks,并在 `~/.codex/agent-loop/hook-bindings.json` 记录目标仓库绑定。
|
|
138
172
|
|
|
139
|
-
|
|
173
|
+
多个仓库可以共用同一个 `CODEX_HOME`。Hook event 会先按 Codex cwd、worktree 和 session context 路由,再写入仓库状态或执行 policy。独立 `CODEX_HOME` 仍适合高隔离 sandbox 测试。
|
|
140
174
|
|
|
141
175
|
运行态文件写入 `.agent-loop/`,不要提交。
|
|
142
176
|
|
|
@@ -146,7 +180,7 @@ agent-loop install-hooks --repo /path/to/repo
|
|
|
146
180
|
agent-loop --repo /path/to/repo dashboard
|
|
147
181
|
```
|
|
148
182
|
|
|
149
|
-
|
|
183
|
+
命令会打印一个本地 URL:
|
|
150
184
|
|
|
151
185
|
```text
|
|
152
186
|
dashboard 已启动
|
|
@@ -154,9 +188,9 @@ url: http://127.0.0.1:<port>/
|
|
|
154
188
|
targetRepoRoot: /path/to/repo
|
|
155
189
|
```
|
|
156
190
|
|
|
157
|
-
Dashboard mutation 必须带本地 session token,并统一走 controller。UI 不直接写 SQLite。本地 loopback dashboard 会用同源
|
|
191
|
+
Dashboard mutation 必须带本地 session token,并统一走 controller。UI 不直接写 SQLite。本地 loopback dashboard 会用同源 bootstrap 自动解锁。stderr token 只作为静态 UI 或恢复场景的 fallback,不要把它复制到 docs、日志、PR body、commit、artifact 或截图里。
|
|
158
192
|
|
|
159
|
-
Dashboard 能看到的交付工作来自持久化的 `agent-loop` 动作和 workflow evidence。直接在终端改文件或 commander 决策不会自动出现在 dashboard,除非它们被记录成 agent-loop event、artifact 或 PR comment
|
|
193
|
+
Dashboard 能看到的交付工作来自持久化的 `agent-loop` 动作和 workflow evidence。直接在终端改文件或 commander 决策不会自动出现在 dashboard,除非它们被记录成 agent-loop event、artifact 或 PR comment。
|
|
160
194
|
|
|
161
195
|
## 常用 CLI
|
|
162
196
|
|
|
@@ -182,9 +216,9 @@ agent-loop --repo /path/to/repo dashboard
|
|
|
182
216
|
|
|
183
217
|
## Workflow Profiles 和主题
|
|
184
218
|
|
|
185
|
-
默认 workflow
|
|
219
|
+
默认 workflow 是 `pr-loop`,使用 `default_pr_loop` 和 `default_pr_roles`。Policy Config 也可以选择 `generic-loop`,并使用内置的调研报告、文档准备、仓库卫生审计、周报、数据抽取 workflow profiles。具体非 PR 工作流可参考:[generic-loop 仓库卫生审计示例](./docs/examples/generic-loop-repo-hygiene.md)。
|
|
186
220
|
|
|
187
|
-
Dashboard 主题是浏览器本地显示偏好,支持 `light`、`dark
|
|
221
|
+
Dashboard 主题是浏览器本地显示偏好,支持 `light`、`dark` 和 `system`,不会写入 repo config 或 SQLite。
|
|
188
222
|
|
|
189
223
|
## 安全边界
|
|
190
224
|
|
|
@@ -192,9 +226,41 @@ Dashboard 主题是浏览器本地显示偏好,支持 `light`、`dark`、`syst
|
|
|
192
226
|
- Supervisor 负责 Git 和 GitHub 生命周期。
|
|
193
227
|
- 破坏性 Git/GitHub 命令由 command policy 和 hooks 阻止。
|
|
194
228
|
- Merge readiness 由 config、review/CI evidence、open review comments、scope guard 和 policy decisions 共同决定。
|
|
195
|
-
-
|
|
229
|
+
- HOLO-Codex 不应该把 secrets、raw prompts、raw transcripts、dashboard tokens 或 raw hook payloads 写入 docs、日志、artifacts、commit 或 PR body。
|
|
196
230
|
- Hooks 只覆盖 Codex tool loop,不拦截外部 Terminal 手动命令。
|
|
197
231
|
|
|
232
|
+
更多细节见:[信任与安全](./docs/trust-and-safety.md) 和 [安全政策](./SECURITY.md)。
|
|
233
|
+
|
|
234
|
+
## FAQ
|
|
235
|
+
|
|
236
|
+
### HOLO-Codex 是托管服务吗?
|
|
237
|
+
|
|
238
|
+
不是。HOLO-Codex 在本机运行。Dashboard、SQLite 状态、hooks 和 CLI 都在你的机器上。
|
|
239
|
+
|
|
240
|
+
### 它会替代 Codex 吗?
|
|
241
|
+
|
|
242
|
+
不会。Codex 还是负责做事。HOLO-Codex 给长流程 Codex 工作补上状态、证据、gate、恢复动作和 dashboard。
|
|
243
|
+
|
|
244
|
+
### 为什么 CLI 还叫 `agent-loop`?
|
|
245
|
+
|
|
246
|
+
`agent-loop` 是稳定运行时命令。保留这个名字可以避免破坏已有安装、脚本、hooks 和本地状态。
|
|
247
|
+
|
|
248
|
+
### 为什么 plugin id 还是 `autonomous-pr-loop`?
|
|
249
|
+
|
|
250
|
+
这个 id 已经接入现有 plugin 和 MCP wiring。HOLO-Codex 是公开产品名,`autonomous-pr-loop` 是兼容标识。
|
|
251
|
+
|
|
252
|
+
### 它会自动 merge PR 吗?
|
|
253
|
+
|
|
254
|
+
不会。HOLO-Codex 可以追踪 merge readiness 和 evidence,但 Git 和 GitHub 生命周期动作仍由 supervisor 控制。
|
|
255
|
+
|
|
256
|
+
### 它必须依赖 GitHub 吗?
|
|
257
|
+
|
|
258
|
+
内置 PR delivery workflow 需要 GitHub 和 `gh`。底层 loop 模型可以通过 `generic-loop` 承载非 PR workflow。
|
|
259
|
+
|
|
260
|
+
### 状态存在哪里?
|
|
261
|
+
|
|
262
|
+
仓库运行态状态在 `.agent-loop/` 下。Hook routing bindings 在 `~/.codex/agent-loop/` 下。运行态文件不要提交。
|
|
263
|
+
|
|
198
264
|
## 开发
|
|
199
265
|
|
|
200
266
|
```bash
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# HOLO-Codex npm Release Checklist
|
|
2
2
|
|
|
3
|
-
Use this checklist for npm releases such as `v0.1.
|
|
3
|
+
Use this checklist for npm releases such as `v0.1.1`. Replace `VERSION` with the package version being released.
|
|
4
4
|
|
|
5
5
|
The public source release remains available at `https://github.com/tizerluo/HOLO-Codex`. The npm package is `holo-codex` and installs the stable `agent-loop` CLI. Compatibility identifiers remain unchanged: `agent-loop`, `.agent-loop/`, `autonomous-pr-loop`, and `plugins/autonomous-pr-loop/`.
|
|
6
6
|
|
|
@@ -33,20 +33,23 @@ Expected result: no real tokens, no committed `.agent-loop/`, no raw hook payloa
|
|
|
33
33
|
## Tarball Smoke
|
|
34
34
|
|
|
35
35
|
```bash
|
|
36
|
-
npm pack --ignore-scripts --json
|
|
36
|
+
pack_json="$(npm pack --ignore-scripts --json)"
|
|
37
|
+
tgz="$(node -e 'const fs = require("fs"); const pack = JSON.parse(fs.readFileSync(0, "utf8")); console.log(pack[0].filename)' <<< "$pack_json")"
|
|
38
|
+
tar -xOf "$tgz" package/plugins/autonomous-pr-loop/hooks/hooks.json
|
|
39
|
+
tar -xOf "$tgz" package/plugins/autonomous-pr-loop/hooks/hooks.json | node -e 'const fs = require("fs"); const hooks = JSON.parse(fs.readFileSync(0, "utf8")); const legacy = Object.keys(hooks).filter((key) => key !== "hooks"); if (!hooks.hooks || typeof hooks.hooks !== "object" || legacy.length) { console.error(JSON.stringify({ legacy }, null, 2)); process.exit(1); }'
|
|
37
40
|
tmp="$(mktemp -d)"
|
|
38
41
|
export CODEX_HOME="$tmp/codex-home"
|
|
39
42
|
mkdir -p "$tmp/target-repo"
|
|
40
43
|
git -C "$tmp/target-repo" init -b main
|
|
41
44
|
git -C "$tmp/target-repo" remote add origin https://github.com/example/holo-codex-smoke.git
|
|
42
|
-
npm install --prefix "$tmp/install"
|
|
45
|
+
npm install --prefix "$tmp/install" "./$tgz"
|
|
43
46
|
"$tmp/install/node_modules/.bin/agent-loop" --help
|
|
44
47
|
"$tmp/install/node_modules/.bin/agent-loop" --repo "$tmp/target-repo" init --json
|
|
45
48
|
"$tmp/install/node_modules/.bin/agent-loop" install-hooks --repo "$tmp/target-repo" --json
|
|
46
49
|
"$tmp/install/node_modules/.bin/agent-loop" --repo "$tmp/target-repo" local doctor --json
|
|
47
50
|
```
|
|
48
51
|
|
|
49
|
-
Do not publish if this smoke fails.
|
|
52
|
+
The extracted `hooks.json` must have a top-level `hooks` object and must not have legacy top-level hook event keys such as `PreToolUse`. Do not publish if this smoke fails.
|
|
50
53
|
|
|
51
54
|
## Publish
|
|
52
55
|
|
|
@@ -126,11 +129,12 @@ Expected result: hooks and binding registry restore from the snapshot, non-agent
|
|
|
126
129
|
After publish and post-publish smoke pass:
|
|
127
130
|
|
|
128
131
|
```bash
|
|
129
|
-
|
|
130
|
-
git
|
|
131
|
-
|
|
132
|
+
VERSION=0.1.1
|
|
133
|
+
git tag "v$VERSION"
|
|
134
|
+
git push origin "v$VERSION"
|
|
135
|
+
gh release create "v$VERSION" \
|
|
132
136
|
--repo tizerluo/HOLO-Codex \
|
|
133
|
-
--title "HOLO-Codex
|
|
137
|
+
--title "HOLO-Codex v$VERSION" \
|
|
134
138
|
--notes-file /path/to/release-notes.md
|
|
135
139
|
```
|
|
136
140
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "holo-codex",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Human On Loop Codex control plane for observable, recoverable Codex workflow loops.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "tizerluo",
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
"files": [
|
|
29
29
|
"README.md",
|
|
30
30
|
"README.zh-CN.md",
|
|
31
|
+
"CHANGELOG.md",
|
|
31
32
|
"LICENSE",
|
|
32
33
|
"SECURITY.md",
|
|
33
34
|
"CONTRIBUTING.md",
|
|
@@ -10,6 +10,7 @@ import { startDashboardServer } from "./dashboard-server.js";
|
|
|
10
10
|
import { runDoctor } from "./doctor.js";
|
|
11
11
|
import { AgentLoopError, isGateCode, toErrorPayload, type AgentLoopErrorCode } from "./errors.js";
|
|
12
12
|
import { recoverBlockedRun } from "./gate-recovery.js";
|
|
13
|
+
import { commandsReferencingLegacyPrivateRepo, inspectAgentLoopBinary, inspectBundledHooksConfig, redactDiagnosticText, type AgentLoopBinaryInspection, type BundledHooksConfigInspection } from "./hook-diagnostics.js";
|
|
13
14
|
import { agentLoopRouterHookEntries, collectHookCommands, isAgentLoopHookCommand, isLegacyAgentLoopHookCommand } from "./hook-installation.js";
|
|
14
15
|
import { hookRegistryPath, inspectHookRegistryLock, listHookBindings, removeHookBinding, upsertHookBinding } from "./hook-router.js";
|
|
15
16
|
import { inspectLocalInstall, installLocalAgentLoop, listLocalInstallSnapshots, pruneLocalInstallSnapshots, rollbackLocalAgentLoop } from "./local-install.js";
|
|
@@ -714,6 +715,9 @@ function hooks(repoRoot: string, args: string[], json: boolean, localeOverride:
|
|
|
714
715
|
report.routerInstalled ? "hook router installed" : "hook router missing",
|
|
715
716
|
`active bindings: ${report.activeBindings}`,
|
|
716
717
|
`legacy entries: ${report.legacyCommands.length}`,
|
|
718
|
+
`old private repo hook refs: ${report.legacyPrivateRepoCommands.length}`,
|
|
719
|
+
`bundled hooks config: ${report.bundledHooksConfig.valid ? "valid" : "invalid"}`,
|
|
720
|
+
`binary old private repo refs: ${report.agentLoopBinary.legacyPrivateRepoReferences.length}`,
|
|
717
721
|
`hook capture: ${report.hookCapture.status} - ${report.hookCapture.reason}`
|
|
718
722
|
]);
|
|
719
723
|
}
|
|
@@ -767,9 +771,12 @@ function local(repoRoot: string, args: string[], json: boolean): CliResult {
|
|
|
767
771
|
"agent-loop local doctor",
|
|
768
772
|
`binary: ${result.binary.path ?? "not found"}`,
|
|
769
773
|
`binary points to expected package: ${result.binary.pointsToExpectedPackage ? "yes" : "no"}`,
|
|
774
|
+
`binary old private repo refs: ${result.binary.legacyPrivateRepoReferences.length}`,
|
|
775
|
+
`bundled hooks config: ${result.hooks.bundledHooksConfig.valid ? "valid" : "invalid"}`,
|
|
770
776
|
`router installed: ${result.hooks.routerInstalled}`,
|
|
771
777
|
`router points to expected dist: ${result.hooks.routerCommandsPointToExpectedDist ? "yes" : "no"}`,
|
|
772
778
|
`legacy entries: ${result.hooks.legacyCommands.length}`,
|
|
779
|
+
`old private repo hook refs: ${result.hooks.legacyPrivateRepoCommands.length}`,
|
|
773
780
|
`current repo bindings: ${result.bindings.currentRepoBindings}`,
|
|
774
781
|
`stale/missing path bindings: ${result.bindings.staleOrMissingPathBindings}`,
|
|
775
782
|
`temp path bindings: ${result.bindings.tempPathBindings}`,
|
|
@@ -1123,6 +1130,9 @@ function hookInstallReport(repoRoot: string, packageRoot: string): {
|
|
|
1123
1130
|
routerInstalled: boolean;
|
|
1124
1131
|
missingRouterEvents: string[];
|
|
1125
1132
|
legacyCommands: string[];
|
|
1133
|
+
legacyPrivateRepoCommands: string[];
|
|
1134
|
+
bundledHooksConfig: BundledHooksConfigInspection;
|
|
1135
|
+
agentLoopBinary: AgentLoopBinaryInspection;
|
|
1126
1136
|
activeBindings: number;
|
|
1127
1137
|
currentRepoBindings: number;
|
|
1128
1138
|
lock: ReturnType<typeof inspectHookRegistryLock>;
|
|
@@ -1145,7 +1155,10 @@ function hookInstallReport(repoRoot: string, packageRoot: string): {
|
|
|
1145
1155
|
const missingRouterEvents = Object.entries(routerEntries)
|
|
1146
1156
|
.filter(([, entries]) => !entries.some((entry) => hookCommands(entry).every((command) => commands.includes(command))))
|
|
1147
1157
|
.map(([event]) => event);
|
|
1148
|
-
const legacyCommands = commands.filter(isLegacyAgentLoopHookCommand);
|
|
1158
|
+
const legacyCommands = commands.filter(isLegacyAgentLoopHookCommand).map(redactDiagnosticText);
|
|
1159
|
+
const legacyPrivateRepoCommands = commandsReferencingLegacyPrivateRepo(commands);
|
|
1160
|
+
const bundledHooksConfig = inspectBundledHooksConfig(packageRoot);
|
|
1161
|
+
const agentLoopBinary = inspectAgentLoopBinary(packageRoot);
|
|
1149
1162
|
let bindings: ReturnType<typeof listHookBindings>;
|
|
1150
1163
|
let registryError: string | undefined;
|
|
1151
1164
|
try {
|
|
@@ -1164,6 +1177,9 @@ function hookInstallReport(repoRoot: string, packageRoot: string): {
|
|
|
1164
1177
|
routerInstalled: hooksJsonError === undefined && missingRouterEvents.length === 0,
|
|
1165
1178
|
missingRouterEvents,
|
|
1166
1179
|
legacyCommands,
|
|
1180
|
+
legacyPrivateRepoCommands,
|
|
1181
|
+
bundledHooksConfig,
|
|
1182
|
+
agentLoopBinary,
|
|
1167
1183
|
activeBindings,
|
|
1168
1184
|
currentRepoBindings,
|
|
1169
1185
|
lock,
|
|
@@ -5,6 +5,7 @@ import { redactRemote, runCommand } from "./command.js";
|
|
|
5
5
|
import { loadConfig, statePath } from "./config.js";
|
|
6
6
|
import { AgentLoopError } from "./errors.js";
|
|
7
7
|
import { inspectHookCapture } from "./hook-capture.js";
|
|
8
|
+
import { commandsReferencingLegacyPrivateRepo, inspectAgentLoopBinary, inspectBundledHooksConfig, redactDiagnosticText } from "./hook-diagnostics.js";
|
|
8
9
|
import { agentLoopRouterHookCommand, collectHookCommands, isLegacyAgentLoopHookCommand } from "./hook-installation.js";
|
|
9
10
|
import { CODEX_HOOK_EVENTS, hookScriptName } from "./hook-events.js";
|
|
10
11
|
import { hookRegistryPath, inspectHookRegistryLock, listHookBindings } from "./hook-router.js";
|
|
@@ -202,10 +203,17 @@ function checkHooksInstalled(repoRoot: string, pluginRoot: string): DoctorCheck
|
|
|
202
203
|
}
|
|
203
204
|
const commands = collectHookCommands(parsedHooks);
|
|
204
205
|
const missing = CODEX_HOOK_EVENTS.filter((event) => !commands.includes(agentLoopRouterHookCommand(event, pluginRoot)));
|
|
205
|
-
const legacyCommands = commands.filter(isLegacyAgentLoopHookCommand);
|
|
206
|
+
const legacyCommands = commands.filter(isLegacyAgentLoopHookCommand).map(redactDiagnosticText);
|
|
206
207
|
const expectedDist = hookDistRoot(pluginRoot);
|
|
207
208
|
const routerCommands = commands.filter((command) => command.includes("autonomous-pr-loop/hooks/dist/"));
|
|
208
|
-
const unexpectedRouterCommands = routerCommands
|
|
209
|
+
const unexpectedRouterCommands = routerCommands
|
|
210
|
+
.filter((command) => !command.includes(expectedDist))
|
|
211
|
+
.map(redactDiagnosticText);
|
|
212
|
+
const bundledHooksConfig = inspectBundledHooksConfig(pluginRoot);
|
|
213
|
+
const agentLoopBinary = inspectAgentLoopBinary(pluginRoot);
|
|
214
|
+
const legacyPrivateRepoCommands = commandsReferencingLegacyPrivateRepo(commands);
|
|
215
|
+
const legacyPrivateRepoDrift =
|
|
216
|
+
legacyPrivateRepoCommands.length > 0 || agentLoopBinary.legacyPrivateRepoReferences.length > 0;
|
|
209
217
|
let bindings: ReturnType<typeof listHookBindings>;
|
|
210
218
|
let registryError: string | undefined;
|
|
211
219
|
try {
|
|
@@ -221,10 +229,12 @@ function checkHooksInstalled(repoRoot: string, pluginRoot: string): DoctorCheck
|
|
|
221
229
|
const installed = missing.length === 0;
|
|
222
230
|
const routerDistDrift = unexpectedRouterCommands.length > 0;
|
|
223
231
|
const captureWarn = capture.status === "ambiguous" || capture.status === "unavailable";
|
|
224
|
-
const status = !installed ? "warn" : routerDistDrift || registryError || lock.stale || legacyCommands.length > 0 || currentRepoBindings.length === 0 || captureWarn ? "warn" : "pass";
|
|
232
|
+
const status = !installed ? "warn" : routerDistDrift || !bundledHooksConfig.valid || legacyPrivateRepoDrift || registryError || lock.stale || legacyCommands.length > 0 || currentRepoBindings.length === 0 || captureWarn ? "warn" : "pass";
|
|
225
233
|
const message = hookInstallMessage({
|
|
226
234
|
installed,
|
|
227
235
|
routerDistDrift,
|
|
236
|
+
bundledHooksConfigValid: bundledHooksConfig.valid,
|
|
237
|
+
legacyPrivateRepoDrift,
|
|
228
238
|
registryError,
|
|
229
239
|
lockStale: lock.stale,
|
|
230
240
|
lockPath: lock.path,
|
|
@@ -247,6 +257,9 @@ function checkHooksInstalled(repoRoot: string, pluginRoot: string): DoctorCheck
|
|
|
247
257
|
legacyCommands,
|
|
248
258
|
routerCommandsPointToExpectedDist: routerCommands.length > 0 && unexpectedRouterCommands.length === 0,
|
|
249
259
|
unexpectedRouterCommands,
|
|
260
|
+
bundledHooksConfig,
|
|
261
|
+
agentLoopBinary,
|
|
262
|
+
legacyPrivateRepoCommands,
|
|
250
263
|
activeBindings: activeBindings.length,
|
|
251
264
|
currentRepoBindings: currentRepoBindings.length,
|
|
252
265
|
lock,
|
|
@@ -260,6 +273,8 @@ function checkHooksInstalled(repoRoot: string, pluginRoot: string): DoctorCheck
|
|
|
260
273
|
function hookInstallMessage(input: {
|
|
261
274
|
installed: boolean;
|
|
262
275
|
routerDistDrift: boolean;
|
|
276
|
+
bundledHooksConfigValid: boolean;
|
|
277
|
+
legacyPrivateRepoDrift: boolean;
|
|
263
278
|
registryError: string | undefined;
|
|
264
279
|
lockStale: boolean;
|
|
265
280
|
lockPath: string;
|
|
@@ -268,28 +283,37 @@ function hookInstallMessage(input: {
|
|
|
268
283
|
installCommand: string;
|
|
269
284
|
codexHome: string;
|
|
270
285
|
}): string {
|
|
286
|
+
const suffix = input.legacyPrivateRepoDrift
|
|
287
|
+
? " Also, hook commands still reference the old private repo."
|
|
288
|
+
: "";
|
|
289
|
+
if (!input.bundledHooksConfigValid) {
|
|
290
|
+
return `Bundled plugin hooks config is not valid for current Codex. Reinstall or refresh the HOLO-Codex plugin after fixing hooks/hooks.json.${suffix}`;
|
|
291
|
+
}
|
|
271
292
|
if (!input.installed && input.routerDistDrift) {
|
|
272
|
-
return `Codex hook router is not installed at the expected hook dist; existing router commands point outside the expected hook dist. Run \`${input.installCommand}\` to refresh router hooks and bind this repo
|
|
293
|
+
return `Codex hook router is not installed at the expected hook dist; existing router commands point outside the expected hook dist. Run \`${input.installCommand}\` to refresh router hooks and bind this repo.${suffix}`;
|
|
273
294
|
}
|
|
274
295
|
if (!input.installed) {
|
|
275
|
-
return `Codex hook router is not installed. Run \`${input.installCommand}\` to install router hooks and bind this repo
|
|
296
|
+
return `Codex hook router is not installed. Run \`${input.installCommand}\` to install router hooks and bind this repo.${suffix}`;
|
|
276
297
|
}
|
|
277
298
|
if (input.routerDistDrift) {
|
|
278
|
-
return `Codex hook router includes commands outside the expected hook dist. Run \`${input.installCommand}\` to refresh router hooks
|
|
299
|
+
return `Codex hook router includes commands outside the expected hook dist. Run \`${input.installCommand}\` to refresh router hooks.${suffix}`;
|
|
279
300
|
}
|
|
280
301
|
if (input.registryError) {
|
|
281
|
-
return `Codex hook binding registry is not valid. Fix ${hookRegistryPath(input.codexHome)}, then run \`${input.installCommand}
|
|
302
|
+
return `Codex hook binding registry is not valid. Fix ${hookRegistryPath(input.codexHome)}, then run \`${input.installCommand}\`.${suffix}`;
|
|
282
303
|
}
|
|
283
304
|
if (input.lockStale) {
|
|
284
|
-
return `Codex hook binding registry lock appears stale. Remove ${input.lockPath} or rerun after the stale writer exits
|
|
305
|
+
return `Codex hook binding registry lock appears stale. Remove ${input.lockPath} or rerun after the stale writer exits.${suffix}`;
|
|
285
306
|
}
|
|
286
307
|
if (input.legacyCommands.length > 0) {
|
|
287
|
-
return `Codex hook router is installed, but legacy per-repo agent-loop hooks remain. Run \`${input.installCommand}\` to migrate them
|
|
308
|
+
return `Codex hook router is installed, but legacy per-repo agent-loop hooks remain. Run \`${input.installCommand}\` to migrate them.${suffix}`;
|
|
309
|
+
}
|
|
310
|
+
if (input.legacyPrivateRepoDrift) {
|
|
311
|
+
return `Codex hook setup still references the old private repo. Run \`${input.installCommand}\` from the canonical HOLO-Codex workspace and reinstall the global CLI if needed.`;
|
|
288
312
|
}
|
|
289
313
|
if (input.currentRepoBindings.length === 0) {
|
|
290
|
-
return `Codex hook router is installed, but this repo is not bound. Run \`${input.installCommand}
|
|
314
|
+
return `Codex hook router is installed, but this repo is not bound. Run \`${input.installCommand}\`.${suffix}`;
|
|
291
315
|
}
|
|
292
|
-
return
|
|
316
|
+
return `HOLO-Codex hook router is installed and this repo has an active binding.${suffix}`;
|
|
293
317
|
}
|
|
294
318
|
|
|
295
319
|
function installHooksCommand(repoRoot: string): string {
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { closeSync, existsSync, openSync, readFileSync, readSync, realpathSync, statSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { CODEX_HOOK_EVENTS } from "./hook-events.js";
|
|
5
|
+
import { redactSecrets } from "./redaction.js";
|
|
6
|
+
|
|
7
|
+
export const LEGACY_PRIVATE_REPO_MARKER = "codex-auto-PR-loop-plusin";
|
|
8
|
+
const BINARY_READ_LIMIT_BYTES = 128 * 1024;
|
|
9
|
+
|
|
10
|
+
export interface BundledHooksConfigInspection {
|
|
11
|
+
path: string;
|
|
12
|
+
valid: boolean;
|
|
13
|
+
legacyTopLevelEvents: string[];
|
|
14
|
+
error?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface AgentLoopBinaryInspection {
|
|
18
|
+
path?: string;
|
|
19
|
+
realPath?: string;
|
|
20
|
+
expectedPackageRoot: string;
|
|
21
|
+
pointsToExpectedPackage: boolean;
|
|
22
|
+
referencesExpectedPackage: boolean;
|
|
23
|
+
legacyPrivateRepoReferences: string[];
|
|
24
|
+
readError?: string;
|
|
25
|
+
readTruncated?: boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Inspect the plugin-bundled Codex hooks config without mutating plugin cache or user config. */
|
|
29
|
+
export function inspectBundledHooksConfig(packageRoot: string): BundledHooksConfigInspection {
|
|
30
|
+
const path = join(packageRoot, "plugins", "autonomous-pr-loop", "hooks", "hooks.json");
|
|
31
|
+
if (!existsSync(path)) {
|
|
32
|
+
return {
|
|
33
|
+
path,
|
|
34
|
+
valid: false,
|
|
35
|
+
legacyTopLevelEvents: [],
|
|
36
|
+
error: "missing bundled hooks config"
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
const parsed = JSON.parse(readFileSync(path, "utf8")) as unknown;
|
|
41
|
+
if (!isRecord(parsed)) {
|
|
42
|
+
return { path, valid: false, legacyTopLevelEvents: [], error: "expected JSON object" };
|
|
43
|
+
}
|
|
44
|
+
const legacyTopLevelEvents = CODEX_HOOK_EVENTS.filter((event) => event in parsed);
|
|
45
|
+
if (!isRecord(parsed.hooks)) {
|
|
46
|
+
return {
|
|
47
|
+
path,
|
|
48
|
+
valid: false,
|
|
49
|
+
legacyTopLevelEvents,
|
|
50
|
+
error: "expected top-level hooks object"
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
path,
|
|
55
|
+
valid: legacyTopLevelEvents.length === 0,
|
|
56
|
+
legacyTopLevelEvents,
|
|
57
|
+
...(legacyTopLevelEvents.length > 0 ? { error: "legacy top-level hook events are not valid bundled hook config" } : {})
|
|
58
|
+
};
|
|
59
|
+
} catch (error) {
|
|
60
|
+
return {
|
|
61
|
+
path,
|
|
62
|
+
valid: false,
|
|
63
|
+
legacyTopLevelEvents: [],
|
|
64
|
+
error: error instanceof Error ? error.message : String(error)
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Inspect the first PATH agent-loop binary for source-install drift. */
|
|
70
|
+
export function inspectAgentLoopBinary(expectedPackageRoot: string): AgentLoopBinaryInspection {
|
|
71
|
+
const path = firstPathBinary("agent-loop");
|
|
72
|
+
const realPath = path && existsSync(path) ? realpathSync(path) : undefined;
|
|
73
|
+
let text = "";
|
|
74
|
+
let readError: string | undefined;
|
|
75
|
+
let readTruncated = false;
|
|
76
|
+
if (path && existsSync(path)) {
|
|
77
|
+
try {
|
|
78
|
+
const result = readTextPrefix(path, BINARY_READ_LIMIT_BYTES);
|
|
79
|
+
text = result.text;
|
|
80
|
+
readTruncated = result.truncated;
|
|
81
|
+
} catch (error) {
|
|
82
|
+
readError = error instanceof Error ? error.message : String(error);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
const referencesExpectedPackage = text.includes(expectedPackageRoot);
|
|
86
|
+
const legacyPrivateRepoReferences = [
|
|
87
|
+
...(path?.includes(LEGACY_PRIVATE_REPO_MARKER) ? [path] : []),
|
|
88
|
+
...(realPath?.includes(LEGACY_PRIVATE_REPO_MARKER) ? [realPath] : []),
|
|
89
|
+
...text.split(/\r?\n/).filter((line) => line.includes(LEGACY_PRIVATE_REPO_MARKER))
|
|
90
|
+
].map(redactDiagnosticText);
|
|
91
|
+
return {
|
|
92
|
+
...(path ? { path } : {}),
|
|
93
|
+
...(realPath ? { realPath } : {}),
|
|
94
|
+
expectedPackageRoot,
|
|
95
|
+
pointsToExpectedPackage: (realPath ? realPath.startsWith(expectedPackageRoot) : false) || referencesExpectedPackage,
|
|
96
|
+
referencesExpectedPackage,
|
|
97
|
+
legacyPrivateRepoReferences,
|
|
98
|
+
...(readError ? { readError } : {}),
|
|
99
|
+
...(readTruncated ? { readTruncated } : {})
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function commandsReferencingLegacyPrivateRepo(commands: string[]): string[] {
|
|
104
|
+
return commands
|
|
105
|
+
.filter((command) => command.includes(LEGACY_PRIVATE_REPO_MARKER))
|
|
106
|
+
.map(redactDiagnosticText);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function redactDiagnosticText(value: string): string {
|
|
110
|
+
return redactLegacyPrivateRepoPaths(redactSecrets(value));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function firstPathBinary(name: string): string | undefined {
|
|
114
|
+
try {
|
|
115
|
+
const output = execFileSync("sh", ["-lc", `command -v ${name} || true`], { encoding: "utf8" }).trim();
|
|
116
|
+
return output || undefined;
|
|
117
|
+
} catch {
|
|
118
|
+
return undefined;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
123
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function redactLegacyPrivateRepoPaths(value: string): string {
|
|
127
|
+
const marker = escapeRegExp(LEGACY_PRIVATE_REPO_MARKER);
|
|
128
|
+
const unixPath = new RegExp(`(?:/[^\\s'"=]+)*/${marker}(?:/[^\\s'"]*)?`, "g");
|
|
129
|
+
const windowsPath = new RegExp(`(?:[A-Za-z]:\\\\[^\\s'"=]+\\\\)*${marker}(?:\\\\[^\\s'"]*)?`, "g");
|
|
130
|
+
return value
|
|
131
|
+
.replace(unixPath, "<legacy-private-repo-path>")
|
|
132
|
+
.replace(windowsPath, "<legacy-private-repo-path>");
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function escapeRegExp(value: string): string {
|
|
136
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function readTextPrefix(path: string, limitBytes: number): { text: string; truncated: boolean } {
|
|
140
|
+
const size = statSync(path).size;
|
|
141
|
+
const length = Math.min(size, limitBytes);
|
|
142
|
+
const buffer = Buffer.alloc(length);
|
|
143
|
+
const fd = openSync(path, "r");
|
|
144
|
+
try {
|
|
145
|
+
const bytesRead = readSync(fd, buffer, 0, length, 0);
|
|
146
|
+
return {
|
|
147
|
+
text: buffer.subarray(0, bytesRead).toString("utf8"),
|
|
148
|
+
truncated: size > bytesRead
|
|
149
|
+
};
|
|
150
|
+
} finally {
|
|
151
|
+
closeSync(fd);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
-
import { execFileSync } from "node:child_process";
|
|
3
2
|
import { chmodSync, copyFileSync, existsSync, mkdirSync, readdirSync, readFileSync, realpathSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
4
3
|
import { homedir } from "node:os";
|
|
5
4
|
import { basename, dirname, join, resolve } from "node:path";
|
|
6
5
|
import { runCommand } from "./command.js";
|
|
7
6
|
import { isRecord } from "./config.js";
|
|
8
7
|
import { AgentLoopError } from "./errors.js";
|
|
8
|
+
import { commandsReferencingLegacyPrivateRepo, inspectAgentLoopBinary, inspectBundledHooksConfig, redactDiagnosticText, type AgentLoopBinaryInspection, type BundledHooksConfigInspection } from "./hook-diagnostics.js";
|
|
9
9
|
import { agentLoopRouterHookCommand, collectHookCommands, isLegacyAgentLoopHookCommand } from "./hook-installation.js";
|
|
10
10
|
import { CODEX_HOOK_EVENTS, hookScriptName } from "./hook-events.js";
|
|
11
11
|
import { hookRegistryPath, inspectHookRegistryLock, listHookBindings } from "./hook-router.js";
|
|
@@ -90,13 +90,19 @@ export interface LocalDoctorReport {
|
|
|
90
90
|
realPath?: string;
|
|
91
91
|
expectedPackageRoot: string;
|
|
92
92
|
pointsToExpectedPackage: boolean;
|
|
93
|
+
referencesExpectedPackage: boolean;
|
|
94
|
+
legacyPrivateRepoReferences: string[];
|
|
95
|
+
readError?: string;
|
|
96
|
+
readTruncated?: boolean;
|
|
93
97
|
};
|
|
94
98
|
hooks: {
|
|
95
99
|
hooksPath: string;
|
|
96
100
|
hooksJsonError?: string;
|
|
101
|
+
bundledHooksConfig: BundledHooksConfigInspection;
|
|
97
102
|
routerInstalled: boolean;
|
|
98
103
|
missingRouterEvents: string[];
|
|
99
104
|
legacyCommands: string[];
|
|
105
|
+
legacyPrivateRepoCommands: string[];
|
|
100
106
|
routerCommandsPointToExpectedDist: boolean;
|
|
101
107
|
};
|
|
102
108
|
bindings: {
|
|
@@ -353,8 +359,7 @@ export function inspectLocalInstall(options: LocalInstallOptions): LocalDoctorRe
|
|
|
353
359
|
const repoRoot = canonicalPath(options.repoRoot);
|
|
354
360
|
const codexHome = codexHomePath();
|
|
355
361
|
const hooksPath = join(codexHome, "hooks.json");
|
|
356
|
-
const
|
|
357
|
-
const realBinaryPath = binaryPath && existsSync(binaryPath) ? canonicalPath(binaryPath) : undefined;
|
|
362
|
+
const binary = inspectAgentLoopBinary(packageRoot);
|
|
358
363
|
const hooks = inspectHooks(hooksPath, packageRoot);
|
|
359
364
|
const bindings = inspectBindings(codexHome, repoRoot);
|
|
360
365
|
const selfLinkPollution = detectSelfLinkPollution(packageRoot);
|
|
@@ -363,12 +368,7 @@ export function inspectLocalInstall(options: LocalInstallOptions): LocalDoctorRe
|
|
|
363
368
|
packageRoot,
|
|
364
369
|
repoRoot,
|
|
365
370
|
codexHome,
|
|
366
|
-
binary
|
|
367
|
-
...(binaryPath ? { path: binaryPath } : {}),
|
|
368
|
-
...(realBinaryPath ? { realPath: realBinaryPath } : {}),
|
|
369
|
-
expectedPackageRoot: packageRoot,
|
|
370
|
-
pointsToExpectedPackage: realBinaryPath ? realBinaryPath.startsWith(packageRoot) : false
|
|
371
|
-
},
|
|
371
|
+
binary,
|
|
372
372
|
hooks,
|
|
373
373
|
bindings,
|
|
374
374
|
selfLinkPollution
|
|
@@ -599,12 +599,15 @@ function inspectHooks(hooksPath: string, packageRoot: string): LocalDoctorReport
|
|
|
599
599
|
const missingRouterEvents = CODEX_HOOK_EVENTS.filter((event) => !commands.includes(agentLoopRouterHookCommand(event, packageRoot)));
|
|
600
600
|
const expectedDist = hookDistRoot(packageRoot);
|
|
601
601
|
const routerCommands = commands.filter((command) => command.includes("autonomous-pr-loop/hooks/dist/"));
|
|
602
|
+
const bundledHooksConfig = inspectBundledHooksConfig(packageRoot);
|
|
602
603
|
return {
|
|
603
604
|
hooksPath,
|
|
604
605
|
...(hooksJsonError ? { hooksJsonError } : {}),
|
|
606
|
+
bundledHooksConfig,
|
|
605
607
|
routerInstalled: missingRouterEvents.length === 0,
|
|
606
608
|
missingRouterEvents,
|
|
607
|
-
legacyCommands: commands.filter(isLegacyAgentLoopHookCommand),
|
|
609
|
+
legacyCommands: commands.filter(isLegacyAgentLoopHookCommand).map(redactDiagnosticText),
|
|
610
|
+
legacyPrivateRepoCommands: commandsReferencingLegacyPrivateRepo(commands),
|
|
608
611
|
routerCommandsPointToExpectedDist: routerCommands.length > 0 && routerCommands.every((command) => command.includes(expectedDist))
|
|
609
612
|
};
|
|
610
613
|
}
|
|
@@ -735,15 +738,6 @@ function gitStatus(cwd: string): string[] {
|
|
|
735
738
|
return result.ok && result.stdout ? result.stdout.split(/\r?\n/).filter(Boolean) : [];
|
|
736
739
|
}
|
|
737
740
|
|
|
738
|
-
function firstPathBinary(name: string): string | undefined {
|
|
739
|
-
try {
|
|
740
|
-
const output = execFileSync("sh", ["-lc", `command -v ${name} || true`], { encoding: "utf8" }).trim();
|
|
741
|
-
return output || undefined;
|
|
742
|
-
} catch {
|
|
743
|
-
return undefined;
|
|
744
|
-
}
|
|
745
|
-
}
|
|
746
|
-
|
|
747
741
|
function localInstallBackupsDir(): string {
|
|
748
742
|
return join(codexHomePath(), "agent-loop", "backups");
|
|
749
743
|
}
|
|
@@ -1,106 +1,3 @@
|
|
|
1
1
|
{
|
|
2
|
-
"
|
|
3
|
-
{
|
|
4
|
-
"matcher": "*",
|
|
5
|
-
"hooks": [
|
|
6
|
-
{
|
|
7
|
-
"type": "command",
|
|
8
|
-
"command": "node ./hooks/dist/pre-tool-use.js",
|
|
9
|
-
"timeout": 1000,
|
|
10
|
-
"statusMessage": "Checking agent-loop command policy"
|
|
11
|
-
}
|
|
12
|
-
]
|
|
13
|
-
}
|
|
14
|
-
],
|
|
15
|
-
"PostToolUse": [
|
|
16
|
-
{
|
|
17
|
-
"matcher": "*",
|
|
18
|
-
"hooks": [
|
|
19
|
-
{
|
|
20
|
-
"type": "command",
|
|
21
|
-
"command": "node ./hooks/dist/post-tool-use.js",
|
|
22
|
-
"timeout": 500,
|
|
23
|
-
"statusMessage": "Recording agent-loop PostToolUse event"
|
|
24
|
-
}
|
|
25
|
-
]
|
|
26
|
-
}
|
|
27
|
-
],
|
|
28
|
-
"UserPromptSubmit": [
|
|
29
|
-
{
|
|
30
|
-
"matcher": "*",
|
|
31
|
-
"hooks": [
|
|
32
|
-
{
|
|
33
|
-
"type": "command",
|
|
34
|
-
"command": "node ./hooks/dist/user-prompt-submit.js",
|
|
35
|
-
"timeout": 500,
|
|
36
|
-
"statusMessage": "Recording agent-loop UserPromptSubmit event"
|
|
37
|
-
}
|
|
38
|
-
]
|
|
39
|
-
}
|
|
40
|
-
],
|
|
41
|
-
"Stop": [
|
|
42
|
-
{
|
|
43
|
-
"matcher": "*",
|
|
44
|
-
"hooks": [
|
|
45
|
-
{
|
|
46
|
-
"type": "command",
|
|
47
|
-
"command": "node ./hooks/dist/stop.js",
|
|
48
|
-
"timeout": 500,
|
|
49
|
-
"statusMessage": "Recording agent-loop Stop event"
|
|
50
|
-
}
|
|
51
|
-
]
|
|
52
|
-
}
|
|
53
|
-
],
|
|
54
|
-
"SessionStart": [
|
|
55
|
-
{
|
|
56
|
-
"matcher": "*",
|
|
57
|
-
"hooks": [
|
|
58
|
-
{
|
|
59
|
-
"type": "command",
|
|
60
|
-
"command": "node ./hooks/dist/session-start.js",
|
|
61
|
-
"timeout": 500,
|
|
62
|
-
"statusMessage": "Recording agent-loop SessionStart event"
|
|
63
|
-
}
|
|
64
|
-
]
|
|
65
|
-
}
|
|
66
|
-
],
|
|
67
|
-
"PreCompact": [
|
|
68
|
-
{
|
|
69
|
-
"matcher": "*",
|
|
70
|
-
"hooks": [
|
|
71
|
-
{
|
|
72
|
-
"type": "command",
|
|
73
|
-
"command": "node ./hooks/dist/pre-compact.js",
|
|
74
|
-
"timeout": 500,
|
|
75
|
-
"statusMessage": "Recording agent-loop PreCompact event"
|
|
76
|
-
}
|
|
77
|
-
]
|
|
78
|
-
}
|
|
79
|
-
],
|
|
80
|
-
"PostCompact": [
|
|
81
|
-
{
|
|
82
|
-
"matcher": "*",
|
|
83
|
-
"hooks": [
|
|
84
|
-
{
|
|
85
|
-
"type": "command",
|
|
86
|
-
"command": "node ./hooks/dist/post-compact.js",
|
|
87
|
-
"timeout": 500,
|
|
88
|
-
"statusMessage": "Recording agent-loop PostCompact event"
|
|
89
|
-
}
|
|
90
|
-
]
|
|
91
|
-
}
|
|
92
|
-
],
|
|
93
|
-
"PermissionRequest": [
|
|
94
|
-
{
|
|
95
|
-
"matcher": "*",
|
|
96
|
-
"hooks": [
|
|
97
|
-
{
|
|
98
|
-
"type": "command",
|
|
99
|
-
"command": "node ./hooks/dist/permission-request.js",
|
|
100
|
-
"timeout": 500,
|
|
101
|
-
"statusMessage": "Recording agent-loop PermissionRequest event"
|
|
102
|
-
}
|
|
103
|
-
]
|
|
104
|
-
}
|
|
105
|
-
]
|
|
2
|
+
"hooks": {}
|
|
106
3
|
}
|
|
@@ -35,7 +35,7 @@ async function handleLine(line: string): Promise<void> {
|
|
|
35
35
|
respond(request.id, {
|
|
36
36
|
protocolVersion: "2024-11-05",
|
|
37
37
|
capabilities: { tools: {} },
|
|
38
|
-
serverInfo: { name: "autonomous-pr-loop", version: "0.1.
|
|
38
|
+
serverInfo: { name: "autonomous-pr-loop", version: "0.1.1" }
|
|
39
39
|
});
|
|
40
40
|
return;
|
|
41
41
|
}
|