helloagents 3.0.9-beta.1 → 3.0.10
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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/README.md +403 -649
- package/README_CN.md +405 -649
- package/bootstrap-lite.md +18 -18
- package/bootstrap.md +22 -22
- package/gemini-extension.json +1 -1
- package/package.json +2 -12
- package/scripts/cli-codex-config.mjs +8 -2
- package/scripts/cli-codex.mjs +5 -3
- package/scripts/cli-lifecycle.mjs +11 -0
- package/scripts/cli-messages.mjs +3 -3
- package/scripts/cli-toml-values.mjs +25 -0
- package/scripts/cli-toml.mjs +15 -0
- package/scripts/guard.mjs +2 -2
- package/scripts/notify-context.mjs +7 -7
- package/scripts/notify-events.mjs +0 -8
- package/scripts/notify-gates.mjs +128 -0
- package/scripts/notify-ui.mjs +3 -0
- package/scripts/notify.mjs +44 -76
- package/scripts/project-storage.mjs +5 -5
- package/scripts/workflow-core.mjs +5 -5
- package/scripts/workflow-recommendation.mjs +4 -4
- package/scripts/workflow-state.mjs +1 -1
- package/skills/commands/auto/SKILL.md +13 -13
- package/skills/commands/build/SKILL.md +6 -6
- package/skills/commands/clean/SKILL.md +6 -6
- package/skills/commands/commit/SKILL.md +2 -2
- package/skills/commands/help/SKILL.md +3 -3
- package/skills/commands/idea/SKILL.md +5 -5
- package/skills/commands/init/SKILL.md +4 -4
- package/skills/commands/loop/SKILL.md +14 -14
- package/skills/commands/plan/SKILL.md +13 -13
- package/skills/commands/prd/SKILL.md +13 -13
- package/skills/commands/verify/SKILL.md +3 -3
- package/skills/commands/wiki/SKILL.md +5 -5
- package/skills/hello-subagent/SKILL.md +1 -1
- package/skills/hello-ui/SKILL.md +14 -14
- package/skills/hello-verify/SKILL.md +2 -2
- package/skills/helloagents/SKILL.md +3 -2
- package/templates/plans/contract.json +2 -2
package/bootstrap-lite.md
CHANGED
|
@@ -172,10 +172,10 @@
|
|
|
172
172
|
- `T0` — 只读分析、创意探索、方案比较 → 自然响应或 `~idea`
|
|
173
173
|
- `T1` — 低风险小改动、明确实现、显式验证 → 直接执行或 `~build` / `~verify`
|
|
174
174
|
- `T2` — 多文件功能、新项目、需要结构化产物 → `~plan` 或 `~auto`
|
|
175
|
-
- `T3` —
|
|
175
|
+
- `T3` — 高风险或不可逆操作(权限、安全、支付、数据库、生产发布等)→ 先 `~plan` / `~prd`,再 `~build` / `~verify`
|
|
176
176
|
|
|
177
177
|
## 完成约束
|
|
178
|
-
- 未激活项目且未进入方案包 / `contract.json` /
|
|
178
|
+
- 未激活项目且未进入方案包 / `contract.json` / 证据文件时,声称完成前必须完成与任务类型匹配的必要检查;无法执行的检查必须明确说明,不得直接宣称完成
|
|
179
179
|
- 当前项目已激活,或已存在方案包 / `contract.json` / 证据文件时,以完整流程、对应 skill 与运行时交付约束为准,不得降级为本节
|
|
180
180
|
- 只读分析、创意探索、方案比较、中间进度和阻塞汇报不适用本节
|
|
181
181
|
|
|
@@ -193,14 +193,14 @@
|
|
|
193
193
|
所有文件的创建和更新必须按 templates/ 目录中对应模板的格式执行,不可自由发挥格式。
|
|
194
194
|
说明:
|
|
195
195
|
- `.helloagents/` 表示项目级存储路径,也是 standby 模式的激活信号
|
|
196
|
-
- `
|
|
197
|
-
-
|
|
196
|
+
- `state_path` 指向的状态文件、`.ralph-*.json`、`loop-results.tsv` 等运行态文件始终保留在项目本地 `.helloagents/`
|
|
197
|
+
- `state_path` 是状态文件的唯一位置。宿主提供会话标识时,写入 `.helloagents/sessions/{branch}/{session}/STATE.md`;没有稳定会话标识时,写入 `.helloagents/sessions/{branch}/default/STATE.md`
|
|
198
198
|
- 若 helloagents.json 中 `project_store_mode = "repo-shared"`,`context.md`、`guidelines.md`、`CHANGELOG.md`、`verify.yaml`、`DESIGN.md`、`modules/`、`plans/`、`archive/` 改按当前上下文中已注入的“当前项目存储”/“项目知识/方案目录”解析;未注入具体路径时,按当前存储模式自行解析,不要假定这些文件一定实际位于当前工作树中
|
|
199
199
|
templates/ 查找路径(按优先级;首次确定模板根目录后,本轮复用):
|
|
200
200
|
按上文 `~command` 路由中的相同技能根目录规则确定;确定根目录后读取其中的 `templates/`。
|
|
201
201
|
|
|
202
202
|
### 流程状态(不受 kb_create_mode 控制,始终可写)
|
|
203
|
-
-
|
|
203
|
+
- 状态文件(`state_path`)— ≤70 行,用来记录“上次做到哪里”。判断当前任务时,当前用户消息、显式命令、活跃方案包 / PRD、代码与验证证据优先于状态文件
|
|
204
204
|
内容:主线目标、正在做什么、关键上下文(决策/变更/假设)、下一步(具体可执行动作含文件路径)、阻塞项
|
|
205
205
|
适用边界:
|
|
206
206
|
- 强制创建并持续更新:`~wiki`、`~init`、`~plan`、`~build`、`~auto`、`~prd`、`~loop`,以及进入统一执行流程/已激活项目的连续任务
|
|
@@ -208,14 +208,14 @@ templates/ 查找路径(按优先级;首次确定模板根目录后,本轮
|
|
|
208
208
|
- 已有则更新:`~verify`、`~review`(兼容别名)、`~test`、`~commit`
|
|
209
209
|
- 不创建:`~help`、`~idea`、普通问答、一次性只读任务、子代理自身执行过程、压缩/恢复钩子
|
|
210
210
|
更新规则:
|
|
211
|
-
-
|
|
212
|
-
-
|
|
211
|
+
- 属于“强制创建并持续更新”范围且状态文件不存在时,按 templates/STATE.md 创建
|
|
212
|
+
- 每次更新是重写,不是追加。状态文件只记录当前状态,不记录历史
|
|
213
213
|
- 更新时机:任务开始、关键决策落定、子任务完成、遇到/解除阻塞、任务完成
|
|
214
|
-
-
|
|
215
|
-
-
|
|
216
|
-
-
|
|
217
|
-
-
|
|
218
|
-
-
|
|
214
|
+
- 长流程中状态文件过时就立即重写,不等任务结束
|
|
215
|
+
- 恢复时先看当前用户消息;如果仍是同一任务,再参考状态文件;否则按当前消息、活跃方案包与代码事实重新判断任务,并立即重写状态文件
|
|
216
|
+
- 当前项目只有状态文件时,它只是恢复参考,不是项目规则或自动授权
|
|
217
|
+
- 若宿主进入压缩/恢复前置阶段,且当前任务属于状态文件适用范围,必须先确认状态文件已同步到最新
|
|
218
|
+
- 自检:如果现在上下文被压缩,下一轮能否凭状态文件找回进度?不能 → 该更新了
|
|
219
219
|
- “关键上下文”只保留恢复所需的信息,已不再相关的决策和变更移除
|
|
220
220
|
- DESIGN.md — 项目级稳定 UI 契约(仅 UI 项目),`~plan` / `~auto` / `~prd` 创建或更新;不存在且当前任务涉及 UI → 按 templates/DESIGN.md 创建;不替代单次需求的 `plan.md`
|
|
221
221
|
- plans/{feature}/ — 活跃方案包。`~plan` / `~auto` 生成:`requirements.md` + `plan.md` + `tasks.md` + `contract.json`;`~prd` 生成:`prd/` 目录(多维度文档)+ `tasks.md` + `decisions.md` + `contract.json`
|
|
@@ -223,7 +223,7 @@ templates/ 查找路径(按优先级;首次确定模板根目录后,本轮
|
|
|
223
223
|
- archive/YYYY-MM/ — 已归档的方案包(整个 plans/{feature}/ 目录移入)
|
|
224
224
|
- archive/_index.md — 归档索引
|
|
225
225
|
|
|
226
|
-
###
|
|
226
|
+
### 知识记录(受 kb_create_mode 控制,0=关闭/1=已激活项目或全局模式中编码自动/2=已激活项目或全局模式中始终)
|
|
227
227
|
- context.md — 项目架构、技术栈、目录结构、模块索引
|
|
228
228
|
- guidelines.md — 编码约定(仅含非显而易见的约定)
|
|
229
229
|
- CHANGELOG.md — 变更历史
|
|
@@ -242,17 +242,17 @@ templates/ 查找路径(按优先级;首次确定模板根目录后,本轮
|
|
|
242
242
|
主线判断依据优先级:
|
|
243
243
|
1. 当前用户最新消息、显式 `~command`、本轮已确认的范围与结论
|
|
244
244
|
2. 当前活跃方案包 / PRD、代码与验证证据
|
|
245
|
-
3.
|
|
246
|
-
4.
|
|
245
|
+
3. 当前状态文件(`state_path`,只用于补齐最近进度)
|
|
246
|
+
4. 其他知识记录与历史归档
|
|
247
247
|
|
|
248
248
|
### .helloagents/ 文件读取优先级
|
|
249
249
|
以下文件在任务需要时按需读取,按优先级分层:
|
|
250
250
|
说明:
|
|
251
|
-
- Tier 1
|
|
251
|
+
- Tier 1 始终读取当前 `state_path`
|
|
252
252
|
- Tier 2 / Tier 3 中的 `.helloagents/...` 路径默认按项目级存储路径解析;`project_store_mode=repo-shared` 时按共享知识/方案目录解析
|
|
253
253
|
|
|
254
|
-
Tier 1 —
|
|
255
|
-
-
|
|
254
|
+
Tier 1 — 恢复当前任务时优先读取:
|
|
255
|
+
- 当前状态文件(`state_path`)→ 先确认当前消息仍是同一任务,再用它找回最近进度
|
|
256
256
|
|
|
257
257
|
Tier 2 — 理解项目时读取:
|
|
258
258
|
- .helloagents/context.md → 项目架构、技术栈、目录结构、模块索引
|
package/bootstrap.md
CHANGED
|
@@ -176,14 +176,14 @@
|
|
|
176
176
|
- `T0` — 只读分析、创意探索、方案比较 → 自然响应或 `~idea`
|
|
177
177
|
- `T1` — 低风险小改动、明确实现、显式验证 → 直接执行或 `~build` / `~verify`
|
|
178
178
|
- `T2` — 多文件功能、新项目、需要结构化产物 → `~plan` 或 `~auto`
|
|
179
|
-
- `T3` —
|
|
179
|
+
- `T3` — 高风险或不可逆操作(权限、安全、支付、数据库、生产发布等)→ 先 `~plan` / `~prd`,再 `~build` / `~verify`,必要时单独确认
|
|
180
180
|
- 创意探索 / 方案比较 → `~idea`
|
|
181
181
|
- 明确实现 / 小范围修复 → `~build`
|
|
182
182
|
- 为指定模块编写测试 → `~test`
|
|
183
183
|
- 结构化规划 / 新功能 / 新项目 → `~plan`
|
|
184
184
|
- 完整产品规格 → `~prd`
|
|
185
185
|
- 审查 / 执行验证 → `~verify`
|
|
186
|
-
-
|
|
186
|
+
- 不确定或希望端到端自动推进时使用 `~auto`
|
|
187
187
|
|
|
188
188
|
当前项目只要已建立 `.helloagents/`(例如执行过 `~wiki`、`~init`,或已进入项目级连续流程),就按项目级完整流程执行。
|
|
189
189
|
|
|
@@ -209,9 +209,9 @@ hello-* 技能读取路径:`{HELLOAGENTS_READ_ROOT}/skills/{技能名}/SKILL.m
|
|
|
209
209
|
- `~plan` 生成 `requirements.md`、`plan.md`、`tasks.md`、`contract.json`
|
|
210
210
|
- `~prd` 生成 PRD 维度文档、`tasks.md`、`decisions.md`
|
|
211
211
|
- `~build` 读取现有方案包并做定位,不重复发明方案
|
|
212
|
-
- `contract.json` 是方案包的机器契约,至少明确 `verifyMode`、`reviewerFocus`、`testerFocus`;只有在 T3 / UI /
|
|
212
|
+
- `contract.json` 是方案包的机器契约,至少明确 `verifyMode`、`reviewerFocus`、`testerFocus`;只有在 T3 / UI / 高风险流程确有收益时,才额外声明 `advisor`;进入验证或最终交付前,优先消费它而不是从自然语言描述里回推验证路径
|
|
213
213
|
- 涉及 UI 时,设计约束优先级固定为:当前 `plan.md` / PRD UI 决策 → 逻辑 `.helloagents/DESIGN.md`(实际路径按当前项目存储模式解析) → 通用 UI 规则
|
|
214
|
-
- `~idea` 在输出比较与推荐后结束,不进入实现,也不创建 `.helloagents
|
|
214
|
+
- `~idea` 在输出比较与推荐后结束,不进入实现,也不创建 `.helloagents/`、状态文件或方案包
|
|
215
215
|
|
|
216
216
|
### 4. BUILD — 实现
|
|
217
217
|
进入实现时,读取 PLAN 阶段标记的技能 SKILL.md(按上方 hello-* 技能查找路径读取 `skills/{技能名}/SKILL.md`),按其规范执行。
|
|
@@ -233,10 +233,10 @@ hello-* 技能读取路径:`{HELLOAGENTS_READ_ROOT}/skills/{技能名}/SKILL.m
|
|
|
233
233
|
非编码任务(文档 / 方案 / 审查等):
|
|
234
234
|
- 收集已激活技能的交付检查清单,逐项确认通过
|
|
235
235
|
|
|
236
|
-
### 6. CONSOLIDATE —
|
|
236
|
+
### 6. CONSOLIDATE — 状态、资料与归档
|
|
237
237
|
所有任务:
|
|
238
238
|
- 有方案包且准备报告完成 → 优先调用 `scripts/closeout-state.mjs write` 写 `.helloagents/.ralph-closeout.json`,记录“需求覆盖”和“交付清单”;每项写明 `PASS` / `BLOCKED` 与简要摘要,再进入最终交付
|
|
239
|
-
- `
|
|
239
|
+
- 状态文件维护:按上文“流程状态”中的适用范围执行。属于“强制创建并持续更新”范围时,重写 `state_path` 指向的文件(“正在做什么”更新为已完成,清空关键上下文 / 下一步 / 阻塞项);属于“已有则更新”范围时,仅在文件已存在时重写;属于“不创建”范围时不生成此文件
|
|
240
240
|
- 有方案包且任务已完成 → 将整个 `plans/{feature}/` 目录归档到 `.helloagents/archive/YYYY-MM/`,并更新 `archive/_index.md`。清理临时文件(`loop-results.tsv`、`.ralph-breaker.json`、`.ralph-verify.json`、`.ralph-review.json`、`.ralph-closeout.json`)
|
|
241
241
|
- 按 `kb_create_mode` 同步知识库(0=关闭 / 1=已激活项目或全局模式中编码自动 / 2=已激活项目或全局模式中始终):
|
|
242
242
|
- `.helloagents/` 不存在则按 templates/ 创建知识库文件(`context.md`、`guidelines.md`、`verify.yaml`、`CHANGELOG.md`、`modules/`)
|
|
@@ -262,14 +262,14 @@ hello-* 技能读取路径:`{HELLOAGENTS_READ_ROOT}/skills/{技能名}/SKILL.m
|
|
|
262
262
|
所有文件的创建和更新必须按 templates/ 目录中对应模板的格式执行,不可自由发挥格式。
|
|
263
263
|
说明:
|
|
264
264
|
- `.helloagents/` 表示项目级存储路径,也是 standby 模式的激活信号
|
|
265
|
-
- `
|
|
266
|
-
-
|
|
265
|
+
- `state_path` 指向的状态文件、`.ralph-*.json`、`loop-results.tsv` 等运行态文件始终保留在项目本地 `.helloagents/`
|
|
266
|
+
- `state_path` 是状态文件的唯一位置。宿主提供会话标识时,写入 `.helloagents/sessions/{branch}/{session}/STATE.md`;没有稳定会话标识时,写入 `.helloagents/sessions/{branch}/default/STATE.md`
|
|
267
267
|
- 若 helloagents.json 中 `project_store_mode = "repo-shared"`,`context.md`、`guidelines.md`、`CHANGELOG.md`、`verify.yaml`、`DESIGN.md`、`modules/`、`plans/`、`archive/` 改按当前上下文中已注入的“当前项目存储”/“项目知识/方案目录”解析;未注入具体路径时,按当前存储模式自行解析,不要假定这些文件一定实际位于当前工作树中
|
|
268
268
|
templates/ 查找路径(按优先级;首次确定模板根目录后,本轮复用):
|
|
269
269
|
按上文相同的技能根目录规则确定;确定根目录后读取其中的 `templates/`。
|
|
270
270
|
|
|
271
271
|
### 流程状态(不受 kb_create_mode 控制,始终可写)
|
|
272
|
-
-
|
|
272
|
+
- 状态文件(`state_path`)— ≤70 行,用来记录“上次做到哪里”。判断当前任务时,当前用户消息、显式命令、活跃方案包 / PRD、代码与验证证据优先于状态文件
|
|
273
273
|
内容:主线目标、正在做什么、关键上下文(决策/变更/假设)、下一步(具体可执行动作含文件路径)、阻塞项
|
|
274
274
|
适用边界:
|
|
275
275
|
- 强制创建并持续更新:`~wiki`、`~init`、`~plan`、`~build`、`~auto`、`~prd`、`~loop`,以及进入统一执行流程/已激活项目的连续任务
|
|
@@ -277,14 +277,14 @@ templates/ 查找路径(按优先级;首次确定模板根目录后,本轮
|
|
|
277
277
|
- 已有则更新:`~verify`、`~review`(兼容别名)、`~test`、`~commit`
|
|
278
278
|
- 不创建:`~help`、`~idea`、普通问答、一次性只读任务、子代理自身执行过程、压缩/恢复钩子
|
|
279
279
|
更新规则:
|
|
280
|
-
-
|
|
281
|
-
-
|
|
280
|
+
- 属于“强制创建并持续更新”范围且状态文件不存在时,按 templates/STATE.md 创建
|
|
281
|
+
- 每次更新是重写,不是追加。状态文件只记录当前状态,不记录历史
|
|
282
282
|
- 更新时机:任务开始、关键决策落定、子任务完成、遇到/解除阻塞、任务完成
|
|
283
|
-
-
|
|
284
|
-
-
|
|
285
|
-
-
|
|
286
|
-
-
|
|
287
|
-
-
|
|
283
|
+
- 长流程中状态文件过时就立即重写,不等任务结束
|
|
284
|
+
- 恢复时先看当前用户消息;如果仍是同一任务,再参考状态文件;否则按当前消息、活跃方案包与代码事实重新判断任务,并立即重写状态文件
|
|
285
|
+
- 当前项目只有状态文件时,它只是恢复参考,不是项目规则或自动授权
|
|
286
|
+
- 若宿主进入压缩/恢复前置阶段,且当前任务属于状态文件适用范围,必须先确认状态文件已同步到最新
|
|
287
|
+
- 自检:如果现在上下文被压缩,下一轮能否凭状态文件找回进度?不能 → 该更新了
|
|
288
288
|
- “关键上下文”只保留恢复所需的信息,已不再相关的决策和变更移除
|
|
289
289
|
- DESIGN.md — 项目级稳定 UI 契约(仅 UI 项目),`~plan` / `~auto` / `~prd` 创建或更新;不存在且当前任务涉及 UI → 按 templates/DESIGN.md 创建;不替代单次需求的 `plan.md`
|
|
290
290
|
- plans/{feature}/ — 活跃方案包。`~plan` / `~auto` 生成:`requirements.md` + `plan.md` + `tasks.md` + `contract.json`;`~prd` 生成:`prd/` 目录(多维度文档)+ `tasks.md` + `decisions.md` + `contract.json`
|
|
@@ -292,7 +292,7 @@ templates/ 查找路径(按优先级;首次确定模板根目录后,本轮
|
|
|
292
292
|
- archive/YYYY-MM/ — 已归档的方案包(整个 plans/{feature}/ 目录移入)
|
|
293
293
|
- archive/_index.md — 归档索引
|
|
294
294
|
|
|
295
|
-
###
|
|
295
|
+
### 知识记录(受 kb_create_mode 控制,0=关闭/1=已激活项目或全局模式中编码自动/2=已激活项目或全局模式中始终)
|
|
296
296
|
- context.md — 项目架构、技术栈、目录结构、模块索引
|
|
297
297
|
- guidelines.md — 编码约定(仅含非显而易见的约定)
|
|
298
298
|
- CHANGELOG.md — 变更历史
|
|
@@ -311,17 +311,17 @@ templates/ 查找路径(按优先级;首次确定模板根目录后,本轮
|
|
|
311
311
|
主线判断依据优先级:
|
|
312
312
|
1. 当前用户最新消息、显式 `~command`、本轮已确认的范围与结论
|
|
313
313
|
2. 当前活跃方案包 / PRD、代码与验证证据
|
|
314
|
-
3.
|
|
315
|
-
4.
|
|
314
|
+
3. 当前状态文件(`state_path`,只用于补齐最近进度)
|
|
315
|
+
4. 其他知识记录与历史归档
|
|
316
316
|
|
|
317
317
|
### .helloagents/ 文件读取优先级
|
|
318
318
|
以下文件在任务需要时按需读取,按优先级分层:
|
|
319
319
|
说明:
|
|
320
|
-
- Tier 1
|
|
320
|
+
- Tier 1 始终读取当前 `state_path`
|
|
321
321
|
- Tier 2 / Tier 3 中的 `.helloagents/...` 路径默认按项目级存储路径解析;`project_store_mode=repo-shared` 时按共享知识/方案目录解析
|
|
322
322
|
|
|
323
|
-
Tier 1 —
|
|
324
|
-
-
|
|
323
|
+
Tier 1 — 恢复当前任务时优先读取:
|
|
324
|
+
- 当前状态文件(`state_path`)→ 先确认当前消息仍是同一任务,再用它找回最近进度
|
|
325
325
|
|
|
326
326
|
Tier 2 — 理解项目时读取:
|
|
327
327
|
- .helloagents/context.md → 项目架构、技术栈、目录结构、模块索引
|
package/gemini-extension.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "helloagents",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.10",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "HelloAGENTS — The orchestration kernel that makes any AI CLI smarter. Adds intelligent routing, quality verification (Ralph Loop), safety guards, and notifications.",
|
|
6
6
|
"author": "HelloWind",
|
|
@@ -36,17 +36,7 @@
|
|
|
36
36
|
".claude-plugin/",
|
|
37
37
|
".codex-plugin/"
|
|
38
38
|
],
|
|
39
|
-
"keywords": [
|
|
40
|
-
"ai",
|
|
41
|
-
"agent",
|
|
42
|
-
"claude",
|
|
43
|
-
"codex",
|
|
44
|
-
"cli",
|
|
45
|
-
"orchestration",
|
|
46
|
-
"subagent",
|
|
47
|
-
"hooks",
|
|
48
|
-
"skills"
|
|
49
|
-
],
|
|
39
|
+
"keywords": ["ai", "agent", "claude", "codex", "cli", "orchestration", "subagent", "hooks", "skills"],
|
|
50
40
|
"engines": {
|
|
51
41
|
"node": ">=18"
|
|
52
42
|
}
|
|
@@ -27,7 +27,13 @@ export function isManagedCodexModelInstruction(line = '') {
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
export function isManagedCodexNotify(line = '') {
|
|
30
|
-
|
|
30
|
+
const value = String(line || '').replace(/\\/g, '/')
|
|
31
|
+
return value.includes(CODEX_MANAGED_TOML_COMMENT)
|
|
32
|
+
|| (
|
|
33
|
+
value.includes('codex-notify')
|
|
34
|
+
&& value.includes('/scripts/notify.mjs')
|
|
35
|
+
&& /(^|[/\\])helloagents([/\\]|-|$)|[/\\]plugins[/\\]helloagents[/\\]/i.test(value)
|
|
36
|
+
)
|
|
31
37
|
}
|
|
32
38
|
|
|
33
39
|
export function isManagedCodexBackupInstruction(line = '') {
|
|
@@ -51,7 +57,7 @@ function formatManagedCodexNotifyValue(notifyScriptPath) {
|
|
|
51
57
|
}
|
|
52
58
|
|
|
53
59
|
function formatManagedCodexNotifyLine(notifyScriptPath) {
|
|
54
|
-
return `notify = ${formatManagedCodexNotifyValue(notifyScriptPath)}`
|
|
60
|
+
return `notify = ${formatManagedCodexNotifyValue(notifyScriptPath)} ${CODEX_MANAGED_TOML_COMMENT}`
|
|
55
61
|
}
|
|
56
62
|
|
|
57
63
|
function removeTopLevelLinesBeingReplaced(toml, lines) {
|
package/scripts/cli-codex.mjs
CHANGED
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
} from './cli-codex-config.mjs';
|
|
21
21
|
import {
|
|
22
22
|
readTopLevelTomlLine,
|
|
23
|
+
readTopLevelTomlBlock,
|
|
23
24
|
readTomlKeyInSection,
|
|
24
25
|
removeTomlKeyInSection,
|
|
25
26
|
ensureTomlKeyInSection,
|
|
@@ -98,7 +99,8 @@ function removeCodexMarketplaceEntry(marketplaceFile) {
|
|
|
98
99
|
}
|
|
99
100
|
if (!removedHelloagents) return false;
|
|
100
101
|
if (!nextPlugins.length) {
|
|
101
|
-
|
|
102
|
+
marketplace.plugins = [];
|
|
103
|
+
safeWrite(marketplaceFile, JSON.stringify(marketplace, null, 2) + '\n');
|
|
102
104
|
return true;
|
|
103
105
|
}
|
|
104
106
|
marketplace.plugins = nextPlugins;
|
|
@@ -130,7 +132,7 @@ function cleanupCodexManagedConfig(configPath, { removePluginConfig = false } =
|
|
|
130
132
|
let toml = safeRead(configPath) || '';
|
|
131
133
|
|
|
132
134
|
const currentModelInstructions = readTopLevelTomlLine(toml, 'model_instructions_file');
|
|
133
|
-
const currentNotify =
|
|
135
|
+
const currentNotify = readTopLevelTomlBlock(toml, 'notify');
|
|
134
136
|
const currentCodexHooks = readTomlKeyInSection(toml, '[features]', 'codex_hooks');
|
|
135
137
|
|
|
136
138
|
const shouldRestoreModelInstructions = isManagedCodexModelInstruction(currentModelInstructions);
|
|
@@ -153,7 +155,7 @@ function cleanupCodexManagedConfig(configPath, { removePluginConfig = false } =
|
|
|
153
155
|
}
|
|
154
156
|
|
|
155
157
|
const backupModelInstructions = readTopLevelTomlLine(backupToml, 'model_instructions_file');
|
|
156
|
-
const backupNotify =
|
|
158
|
+
const backupNotify = readTopLevelTomlBlock(backupToml, 'notify');
|
|
157
159
|
const backupCodexHooks = readTomlKeyInSection(backupToml, '[features]', 'codex_hooks');
|
|
158
160
|
|
|
159
161
|
toml = restoreCodexTopLevelConfig(toml, {
|
|
@@ -181,6 +181,17 @@ function runAllHostsLifecycle(action, explicitMode) {
|
|
|
181
181
|
}
|
|
182
182
|
|
|
183
183
|
const settings = readSettings(true)
|
|
184
|
+
if (action === 'update' && !explicitMode) {
|
|
185
|
+
for (const host of HOSTS) {
|
|
186
|
+
const mode = resolveHostMode(host, '', settings)
|
|
187
|
+
const result = runHostLifecycle(runtime, action, host, mode)
|
|
188
|
+
if (!result.skipped) setTrackedHostMode(settings, host, mode)
|
|
189
|
+
}
|
|
190
|
+
writeSettings(settings)
|
|
191
|
+
runtime.printInstallMsg(settings.install_mode || DEFAULTS.install_mode, 'refresh')
|
|
192
|
+
return
|
|
193
|
+
}
|
|
194
|
+
|
|
184
195
|
const mode = resolveInstallMode(explicitMode, settings)
|
|
185
196
|
if (explicitMode) settings.install_mode = explicitMode
|
|
186
197
|
installAllHosts(runtime, mode)
|
package/scripts/cli-messages.mjs
CHANGED
|
@@ -44,8 +44,8 @@ function renderInstallMessage(context, mode, state) {
|
|
|
44
44
|
}
|
|
45
45
|
return msg(
|
|
46
46
|
refresh
|
|
47
|
-
? ' global 模式已刷新。\n Claude Code / Gemini 请保持插件已安装;Codex
|
|
48
|
-
: ' 所有项目将自动启用完整 HelloAGENTS 规则。\n Claude Code / Gemini 请手动安装插件;Codex
|
|
47
|
+
? ' global 模式已刷新。\n Claude Code / Gemini 请保持插件已安装;Codex 原生本地插件已重装并同步最新文件。'
|
|
48
|
+
: ' 所有项目将自动启用完整 HelloAGENTS 规则。\n Claude Code / Gemini 请手动安装插件;Codex 已自动安装原生本地插件。',
|
|
49
49
|
refresh
|
|
50
50
|
? ' Global mode refreshed.\n Keep Claude Code / Gemini plugins installed; Codex native local-plugin files were reinstalled and synced.'
|
|
51
51
|
: ' All projects will use full HelloAGENTS rules.\n Install Claude Code / Gemini plugins manually; Codex now uses the native local-plugin path automatically.',
|
|
@@ -92,7 +92,7 @@ ${msg('单 CLI 管理', 'Scoped CLI management')}:
|
|
|
92
92
|
${msg('诊断', 'Diagnostics')}:
|
|
93
93
|
helloagents doctor
|
|
94
94
|
helloagents doctor codex --json
|
|
95
|
-
${msg('检查 carrier、链接、hooks、配置注入、Codex
|
|
95
|
+
${msg('检查 carrier、链接、hooks、配置注入、Codex 插件安装、受管 model_instructions_file 指向与版本漂移', 'Checks carriers, links, hooks, config injections, Codex plugin installation, managed model_instructions_file targeting, and version drift')}
|
|
96
96
|
|
|
97
97
|
${msg('卸载', 'Uninstall')}:
|
|
98
98
|
helloagents cleanup ${msg('(推荐先执行,显式清理所有 CLI 注入/链接)', '(recommended first, explicitly cleans CLI injections/links)')}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export function getTomlArrayDepthDelta(text) {
|
|
2
|
+
let depth = 0
|
|
3
|
+
let quoted = false
|
|
4
|
+
let escaped = false
|
|
5
|
+
|
|
6
|
+
for (const char of String(text || '')) {
|
|
7
|
+
if (escaped) {
|
|
8
|
+
escaped = false
|
|
9
|
+
continue
|
|
10
|
+
}
|
|
11
|
+
if (char === '\\' && quoted) {
|
|
12
|
+
escaped = true
|
|
13
|
+
continue
|
|
14
|
+
}
|
|
15
|
+
if (char === '"') {
|
|
16
|
+
quoted = !quoted
|
|
17
|
+
continue
|
|
18
|
+
}
|
|
19
|
+
if (quoted) continue
|
|
20
|
+
if (char === '[') depth += 1
|
|
21
|
+
if (char === ']') depth -= 1
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return depth
|
|
25
|
+
}
|
package/scripts/cli-toml.mjs
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
* Targets the small subset of TOML structures used by Codex CLI config.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import { getTomlArrayDepthDelta } from './cli-toml-values.mjs'
|
|
7
|
+
|
|
6
8
|
export function isTomlTableHeader(line) {
|
|
7
9
|
const trimmed = String(line || '').trim();
|
|
8
10
|
return trimmed.startsWith('[') && trimmed.endsWith(']');
|
|
@@ -62,6 +64,19 @@ function findTopLevelTomlBlock(text, key) {
|
|
|
62
64
|
const closeIndex = normalized.indexOf('"""', openIndex + 3);
|
|
63
65
|
end = closeIndex >= 0 ? closeIndex + 3 : normalized.length;
|
|
64
66
|
}
|
|
67
|
+
if (value.startsWith('[')) {
|
|
68
|
+
let depth = getTomlArrayDepthDelta(firstLine.slice(firstLine.indexOf('=') + 1));
|
|
69
|
+
let lineStart = firstLineEnd + (normalized[firstLineEnd] === '\n' ? 1 : 0);
|
|
70
|
+
|
|
71
|
+
while (depth > 0 && lineStart < normalized.length) {
|
|
72
|
+
const lineEndIndex = normalized.indexOf('\n', lineStart);
|
|
73
|
+
const nextLineEnd = lineEndIndex >= 0 ? lineEndIndex : normalized.length;
|
|
74
|
+
const nextLine = normalized.slice(lineStart, nextLineEnd);
|
|
75
|
+
depth += getTomlArrayDepthDelta(nextLine);
|
|
76
|
+
end = nextLineEnd;
|
|
77
|
+
lineStart = nextLineEnd + 1;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
65
80
|
|
|
66
81
|
while (end < normalized.length && normalized[end] === '\n') {
|
|
67
82
|
end += 1;
|
package/scripts/guard.mjs
CHANGED
|
@@ -124,7 +124,7 @@ function buildPostWriteWarnings(data) {
|
|
|
124
124
|
const filePath = data.tool_input?.file_path || ''
|
|
125
125
|
return [
|
|
126
126
|
...(detectIdeaBoundaryContext(data)?.zeroSideEffect
|
|
127
|
-
? ['~idea
|
|
127
|
+
? ['~idea 本轮要求只读探索;检测到写入文件的工具调用,请回到探索输出,或升级到 ~plan / ~build / ~prd / ~auto 后再修改文件']
|
|
128
128
|
: []),
|
|
129
129
|
...scanUnrequestedFiles(filePath, data.tool_name),
|
|
130
130
|
...(content ? [...scanForSecrets(content), ...scanDangerousPackages(content, filePath)] : []),
|
|
@@ -195,7 +195,7 @@ function handleHighRiskCommand(data, command) {
|
|
|
195
195
|
function emitShellWarnings(data, command, highRiskWarnings, shellSafetyWarnings) {
|
|
196
196
|
const sections = []
|
|
197
197
|
if (highRiskWarnings.length > 0) {
|
|
198
|
-
sections.push(`⚠️ [HelloAGENTS
|
|
198
|
+
sections.push(`⚠️ [HelloAGENTS 高风险操作提醒] 检测到高风险命令:\n${highRiskWarnings.map((warning) => ` - ${warning}`).join('\n')}\n请确认已完成相应规划/审查并获得必要授权。`)
|
|
199
199
|
}
|
|
200
200
|
if (shellSafetyWarnings.length > 0) {
|
|
201
201
|
sections.push(`⚠️ [HelloAGENTS Shell 安全提醒] 检测到建议调整的命令写法:\n${shellSafetyWarnings.map((warning) => ` - ${warning}`).join('\n')}\n当前仅提示,不中断执行。`)
|
|
@@ -74,8 +74,8 @@ export function buildCompactionContext({ payload, pkgRoot, settings, bootstrapFi
|
|
|
74
74
|
const stateSyncHint = buildStateSyncHint(cwd, workflowOptions);
|
|
75
75
|
if (stateSnapshot.exists && stateSnapshot.content) {
|
|
76
76
|
summaryParts.push('');
|
|
77
|
-
summaryParts.push(`##
|
|
78
|
-
summaryParts.push('
|
|
77
|
+
summaryParts.push(`## 状态文件(从 ${stateSnapshot.statePath.replace(/\\/g, '/')} 读取,只用于找回上次停在哪)`);
|
|
78
|
+
summaryParts.push('恢复时先看当前用户消息;如果仍是同一任务,再参考状态文件。');
|
|
79
79
|
summaryParts.push(stateSnapshot.content);
|
|
80
80
|
}
|
|
81
81
|
|
|
@@ -109,7 +109,7 @@ export function buildCompactionContext({ payload, pkgRoot, settings, bootstrapFi
|
|
|
109
109
|
|
|
110
110
|
if (stateSyncHint) {
|
|
111
111
|
summaryParts.push('');
|
|
112
|
-
summaryParts.push('##
|
|
112
|
+
summaryParts.push('## 状态文件提醒');
|
|
113
113
|
summaryParts.push(stateSyncHint);
|
|
114
114
|
}
|
|
115
115
|
|
|
@@ -140,10 +140,10 @@ export function buildInjectContext({ source, bootstrap, settings, pkgRoot, host,
|
|
|
140
140
|
if (projectStorageBlock) context += `\n\n${projectStorageBlock}`;
|
|
141
141
|
if (workflowHint) context += `\n\n## 当前工作流提示\n${workflowHint}`;
|
|
142
142
|
if (capabilityHint) context += `\n\n## 当前按需能力\n${capabilityHint}`;
|
|
143
|
-
if (stateSyncHint) context += `\n\n##
|
|
143
|
+
if (stateSyncHint) context += `\n\n## 状态文件提醒\n${stateSyncHint}`;
|
|
144
144
|
context += settingsBlock;
|
|
145
145
|
if (source === 'resume' || source === 'compact') {
|
|
146
|
-
context += `\n\n> ⚠️
|
|
146
|
+
context += `\n\n> ⚠️ 会话已恢复/压缩,请先读取 \`state_path\` 指向的 \`${stateSnapshot.statePath.replace(/\\/g, '/')}\`;先看当前用户消息,如果仍是同一任务,再参考状态文件。`;
|
|
147
147
|
}
|
|
148
148
|
return context;
|
|
149
149
|
}
|
|
@@ -168,8 +168,8 @@ export function buildSemanticRouteInstruction(cwd, payload = {}) {
|
|
|
168
168
|
return [
|
|
169
169
|
'当前消息未使用 ~command。',
|
|
170
170
|
'请根据用户请求的真实意图选路,不依赖关键词表。',
|
|
171
|
-
'Delivery Tier: T0=探索/比较;T1=低风险小改动或显式验证;T2=多文件功能/新项目/需要结构化产物;T3
|
|
172
|
-
'路由映射:~idea=只读探索,不创建文件;~build=明确实现;~verify=审查/验证;~plan=结构化规划;~prd=重型规格;~auto
|
|
171
|
+
'Delivery Tier: T0=探索/比较;T1=低风险小改动或显式验证;T2=多文件功能/新项目/需要结构化产物;T3=高风险或不可逆操作。',
|
|
172
|
+
'路由映射:~idea=只读探索,不创建文件;~build=明确实现;~verify=审查/验证;~plan=结构化规划;~prd=重型规格;~auto=自动选择并继续执行后续阶段。',
|
|
173
173
|
'若判定为 T3,默认先走 ~plan / ~prd;纯审查/验证请求才优先 ~verify。',
|
|
174
174
|
`涉及 UI 任务时,设计决策优先级:当前活跃 plan / PRD → ${describeProjectStoreFile(cwd, 'DESIGN.md')} → 通用 UI 规则。`,
|
|
175
175
|
projectStorageHint,
|
|
@@ -5,11 +5,3 @@ export function shouldIgnoreCodexNotifyClient(client) {
|
|
|
5
5
|
export function shouldIgnoreFormattedSubagent(lastMsg, outputFormatEnabled) {
|
|
6
6
|
return outputFormatEnabled && !lastMsg.includes('【HelloAGENTS】');
|
|
7
7
|
}
|
|
8
|
-
|
|
9
|
-
export function claimsTaskComplete(lastMsg) {
|
|
10
|
-
if (!lastMsg) return false;
|
|
11
|
-
if (/^✅【HelloAGENTS】- .*(当前任务已完成|任务已完成|已修复|完成交付|done|fixed|completed|finished)/im.test(lastMsg)) {
|
|
12
|
-
return true;
|
|
13
|
-
}
|
|
14
|
-
return /(当前任务已完成|任务已完成|已全部完成|已修复|修复完成|\b(done|fixed|completed|finished)\b)/i.test(lastMsg);
|
|
15
|
-
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs'
|
|
2
|
+
import { spawnSync } from 'node:child_process'
|
|
3
|
+
|
|
4
|
+
function truncateText(value = '') {
|
|
5
|
+
const text = String(value || '').trim()
|
|
6
|
+
return text.length > 1000 ? `${text.slice(0, 1000)}\n...(truncated)` : text
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function buildGateErrorReason(source, detail = '') {
|
|
10
|
+
return [
|
|
11
|
+
`[HelloAGENTS Runtime] ${source} 执行失败,已暂停完成通知。`,
|
|
12
|
+
detail ? `原因:${detail}` : '',
|
|
13
|
+
'请修复脚本或重新运行验证后再报告完成。',
|
|
14
|
+
].filter(Boolean).join('\n')
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function emitGateError({
|
|
18
|
+
payload,
|
|
19
|
+
host,
|
|
20
|
+
source,
|
|
21
|
+
reason,
|
|
22
|
+
appendReplayEvent,
|
|
23
|
+
output,
|
|
24
|
+
}) {
|
|
25
|
+
appendReplayEvent(payload.cwd || process.cwd(), {
|
|
26
|
+
host,
|
|
27
|
+
event: 'runtime_gate_error',
|
|
28
|
+
source,
|
|
29
|
+
reason,
|
|
30
|
+
})
|
|
31
|
+
output({
|
|
32
|
+
decision: 'block',
|
|
33
|
+
reason,
|
|
34
|
+
suppressOutput: true,
|
|
35
|
+
})
|
|
36
|
+
return true
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function runGateScript({
|
|
40
|
+
payload,
|
|
41
|
+
host,
|
|
42
|
+
scriptPath,
|
|
43
|
+
args = [],
|
|
44
|
+
source,
|
|
45
|
+
blockEvent,
|
|
46
|
+
timeout,
|
|
47
|
+
appendReplayEvent,
|
|
48
|
+
output,
|
|
49
|
+
}) {
|
|
50
|
+
if (!existsSync(scriptPath)) {
|
|
51
|
+
return emitGateError({
|
|
52
|
+
payload,
|
|
53
|
+
host,
|
|
54
|
+
source,
|
|
55
|
+
reason: buildGateErrorReason(source, `脚本不存在:${scriptPath}`),
|
|
56
|
+
appendReplayEvent,
|
|
57
|
+
output,
|
|
58
|
+
})
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const result = spawnSync(process.execPath, [scriptPath, ...args], {
|
|
62
|
+
input: JSON.stringify(payload),
|
|
63
|
+
encoding: 'utf-8',
|
|
64
|
+
timeout,
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
if (result.error) {
|
|
68
|
+
return emitGateError({
|
|
69
|
+
payload,
|
|
70
|
+
host,
|
|
71
|
+
source,
|
|
72
|
+
reason: buildGateErrorReason(source, result.error.message),
|
|
73
|
+
appendReplayEvent,
|
|
74
|
+
output,
|
|
75
|
+
})
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (result.status !== 0) {
|
|
79
|
+
const detail = truncateText(`${result.stderr || ''}\n${result.stdout || ''}`) || `退出码 ${result.status}`
|
|
80
|
+
return emitGateError({
|
|
81
|
+
payload,
|
|
82
|
+
host,
|
|
83
|
+
source,
|
|
84
|
+
reason: buildGateErrorReason(source, detail),
|
|
85
|
+
appendReplayEvent,
|
|
86
|
+
output,
|
|
87
|
+
})
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const stdout = String(result.stdout || '').trim()
|
|
91
|
+
if (!stdout) {
|
|
92
|
+
return emitGateError({
|
|
93
|
+
payload,
|
|
94
|
+
host,
|
|
95
|
+
source,
|
|
96
|
+
reason: buildGateErrorReason(source, '脚本未返回有效结果'),
|
|
97
|
+
appendReplayEvent,
|
|
98
|
+
output,
|
|
99
|
+
})
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
let gateOutput
|
|
103
|
+
try {
|
|
104
|
+
gateOutput = JSON.parse(stdout)
|
|
105
|
+
} catch {
|
|
106
|
+
return emitGateError({
|
|
107
|
+
payload,
|
|
108
|
+
host,
|
|
109
|
+
source,
|
|
110
|
+
reason: buildGateErrorReason(source, `脚本返回了无法解析的 JSON:${truncateText(stdout)}`),
|
|
111
|
+
appendReplayEvent,
|
|
112
|
+
output,
|
|
113
|
+
})
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (gateOutput.decision === 'block') {
|
|
117
|
+
appendReplayEvent(payload.cwd || process.cwd(), {
|
|
118
|
+
host,
|
|
119
|
+
event: blockEvent,
|
|
120
|
+
source,
|
|
121
|
+
reason: gateOutput.reason || '',
|
|
122
|
+
})
|
|
123
|
+
output(gateOutput)
|
|
124
|
+
return true
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return false
|
|
128
|
+
}
|
package/scripts/notify-ui.mjs
CHANGED
|
@@ -18,6 +18,7 @@ const NOTIFY_MESSAGES = {
|
|
|
18
18
|
};
|
|
19
19
|
|
|
20
20
|
const WIN_APPID = 'HelloAgents.Notification';
|
|
21
|
+
const DISABLE_OS_NOTIFICATIONS = process.env.HELLOAGENTS_DISABLE_OS_NOTIFICATIONS === '1';
|
|
21
22
|
|
|
22
23
|
function escapeToastText(value = '') {
|
|
23
24
|
return String(value)
|
|
@@ -55,6 +56,7 @@ function resolveWav(pkgRoot, event) {
|
|
|
55
56
|
}
|
|
56
57
|
|
|
57
58
|
export function playSound(pkgRoot, event) {
|
|
59
|
+
if (DISABLE_OS_NOTIFICATIONS) return;
|
|
58
60
|
const wav = resolveWav(pkgRoot, event);
|
|
59
61
|
if (!wav) { process.stderr.write('\x07'); return; }
|
|
60
62
|
try {
|
|
@@ -83,6 +85,7 @@ function ensureWinAppId(pkgRoot) {
|
|
|
83
85
|
}
|
|
84
86
|
|
|
85
87
|
export function desktopNotify(pkgRoot, event, extra) {
|
|
88
|
+
if (DISABLE_OS_NOTIFICATIONS) return;
|
|
86
89
|
const notification = buildDesktopNotificationContent(event, extra);
|
|
87
90
|
try {
|
|
88
91
|
if (PLAT === 'win32') {
|