helloagents 3.0.32 → 3.0.33

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "helloagents",
3
- "version": "3.0.32",
3
+ "version": "3.0.33",
4
4
  "description": "HelloAGENTS — The orchestration kernel that makes any AI CLI smarter. Adds intelligent routing, quality verification (Ralph Loop), safety guards, and notifications.",
5
5
  "author": {
6
6
  "name": "HelloWind",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "helloagents",
3
- "version": "3.0.32",
3
+ "version": "3.0.33",
4
4
  "description": "HelloAGENTS — Quality-driven orchestration kernel for AI CLIs with intelligent routing, quality verification (Ralph Loop), safety guards, and notifications.",
5
5
  "author": {
6
6
  "name": "HelloWind",
package/README.md CHANGED
@@ -8,7 +8,7 @@
8
8
 
9
9
  **A workflow layer for AI coding CLIs: skills, project knowledge, delivery checks, safer config writes, and resumable execution.**
10
10
 
11
- [![Version](https://img.shields.io/badge/version-3.0.32-orange.svg)](./package.json)
11
+ [![Version](https://img.shields.io/badge/version-3.0.33-orange.svg)](./package.json)
12
12
  [![npm](https://img.shields.io/npm/v/helloagents.svg)](https://www.npmjs.com/package/helloagents)
13
13
  [![Node](https://img.shields.io/badge/node-%3E%3D18-339933.svg)](./package.json)
14
14
  [![Skills](https://img.shields.io/badge/skills-14-6366f1.svg)](./skills)
@@ -657,7 +657,7 @@ Codex is rules-file driven by default.
657
657
  - global mode installs the native local-plugin chain, but keeps `~/.helloagents/helloagents` as the single managed runtime source by linking plugin roots, plugin cache, and `~/.codex/helloagents` back to it
658
658
  - cleanup removes only the HelloAGENTS-managed hook trust entries and legacy managed notify residues, while keeping user-owned hook state untouched
659
659
  - Codex hooks only synchronize runtime state and enforce Stop gates; they do not inject HelloAGENTS rules or route text through hook output
660
- - Codex closeout de-duplicates Stop hooks and native `codex-notify`, so one turn does not notify twice
660
+ - Codex closeout de-duplicates Stop hooks and native `codex-notify`, so one turn does not notify twice, and clientless delegated child-completion events stay silent when the managed Stop hook is active
661
661
  - `/goal` remains Codex-native. Enable it explicitly with `helloagents codex goals enable` when long-running plan execution is needed
662
662
  - Goal-aware commands resume from `tasks.md`, `contract.json`, and `state_path`; they do not create goals automatically or mark them complete before HelloAGENTS verification and closeout
663
663
 
@@ -680,7 +680,7 @@ The current suite covers:
680
680
  - `helloagents doctor`
681
681
  - project storage and `repo-shared` behavior
682
682
  - session-scoped `state_path`, runtime signals, and evidence
683
- - runtime injection, routing, guard, verification, visual evidence, delivery gates, closeout de-duplication, and successful-mode tracking after native install failures
683
+ - runtime injection, routing, guard, verification, visual evidence, delivery gates, closeout de-duplication, sub-agent wrapper and notification suppression, and successful-mode tracking after native install failures
684
684
  - README and skill contract alignment
685
685
 
686
686
  ## FAQ
package/README_CN.md CHANGED
@@ -8,7 +8,7 @@
8
8
 
9
9
  **面向 AI 编码 CLI 的工作流层:技能、知识库、交付检查、更安全的配置写入,以及可恢复的执行流程。**
10
10
 
11
- [![Version](https://img.shields.io/badge/version-3.0.32-orange.svg)](./package.json)
11
+ [![Version](https://img.shields.io/badge/version-3.0.33-orange.svg)](./package.json)
12
12
  [![npm](https://img.shields.io/npm/v/helloagents.svg)](https://www.npmjs.com/package/helloagents)
13
13
  [![Node](https://img.shields.io/badge/node-%3E%3D18-339933.svg)](./package.json)
14
14
  [![Skills](https://img.shields.io/badge/skills-14-6366f1.svg)](./skills)
@@ -661,7 +661,7 @@ Codex 默认走规则文件驱动。
661
661
  - 全局模式安装原生本地插件流程,但仍把 `~/.helloagents/helloagents` 作为唯一受管运行时源;插件根目录、插件缓存和 `~/.codex/helloagents` 都会回链到它
662
662
  - 清理时只删除 HelloAGENTS 自己写入的 hook trust 条目和旧式受管 notify 残留,不影响用户已有的 hook 状态
663
663
  - Codex hooks 只做静默运行态同步和 Stop 门禁,不通过 hook 注入 HelloAGENTS 规则或路由说明
664
- - Codex 收尾会对 Stop hook 和原生 `codex-notify` 去重,避免同一轮重复通知
664
+ - Codex 收尾会对 Stop hook 和原生 `codex-notify` 去重,避免同一轮重复通知;受管 Stop hook 生效时,client 为空的委派子任务完成事件也会保持静默
665
665
  - `/goal` 保持 Codex 原生能力;需要长程执行时,用 `helloagents codex goals enable` 显式启用
666
666
  - 感知 goal 的命令从 `tasks.md`、`contract.json` 和 `state_path` 恢复;不会自动创建 goal,也不会在 HelloAGENTS 验证和收尾前标记完成
667
667
 
@@ -684,7 +684,7 @@ npm test
684
684
  - `helloagents doctor`
685
685
  - 项目存储和 `repo-shared`
686
686
  - 会话级 `state_path`、运行态信号和证据
687
- - 运行时注入、选路、Guard、验证、视觉证据、交付门控、收尾去重,以及原生安装失败后的模式记录
687
+ - 运行时注入、选路、Guard、验证、视觉证据、交付门控、收尾去重、子代理外层格式与通知静默保护,以及原生安装失败后的模式记录
688
688
  - README 与 skill 契约一致性
689
689
 
690
690
  ## FAQ
package/bootstrap-lite.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # HelloAGENTS
2
2
 
3
- 当当前任务属于上级代理分派的局部任务、当前输出会交回上级代理或控制器继续汇总/决策/复述,或当前回复并非直接面向最终用户的终局交付时,按子代理处理:仅跳过输出格式、交互确认与停顿、统一执行流程、任务分层、完成判定、命令路由和流程状态,直接执行并返回结果。不使用 `~command`,不包装 HelloAGENTS 外层格式;其余规则持续生效。
3
+ 当当前任务属于上级代理分派的局部任务、由宿主协作/委派机制创建(包括 spawn_agent、worker / explorer、并行代理、Codex agent/delegate/wait 等)、当前输出会交回上级代理或控制器继续汇总/决策/复述,或当前回复并非直接面向最终用户的终局交付时,一律按子代理处理:仅跳过输出格式、交互确认与停顿、统一执行流程、任务分层、完成判定、命令路由和流程状态,直接执行并返回结果。只返回结果、证据或阻塞项;禁止输出 `【HelloAGENTS】` 外层格式、`🔄 下一步:`、面向最终用户的收尾或 turn-state;其余规则持续生效。
4
4
 
5
5
  ## 配置
6
6
  配置文件: ~/.helloagents/helloagents.json
package/bootstrap.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # HelloAGENTS
2
2
 
3
- 当当前任务属于上级代理分派的局部任务、当前输出会交回上级代理或控制器继续汇总/决策/复述,或当前回复并非直接面向最终用户的终局交付时,按子代理处理:仅跳过输出格式、交互确认与停顿、统一执行流程、任务分层、完成判定、命令路由和流程状态,直接执行并返回结果。不使用 `~command`,不包装 HelloAGENTS 外层格式;其余规则持续生效。
3
+ 当当前任务属于上级代理分派的局部任务、由宿主协作/委派机制创建(包括 spawn_agent、worker / explorer、并行代理、Codex agent/delegate/wait 等)、当前输出会交回上级代理或控制器继续汇总/决策/复述,或当前回复并非直接面向最终用户的终局交付时,一律按子代理处理:仅跳过输出格式、交互确认与停顿、统一执行流程、任务分层、完成判定、命令路由和流程状态,直接执行并返回结果。只返回结果、证据或阻塞项;禁止输出 `【HelloAGENTS】` 外层格式、`🔄 下一步:`、面向最终用户的收尾或 turn-state;其余规则持续生效。
4
4
 
5
5
  ## 配置
6
6
  配置文件: ~/.helloagents/helloagents.json
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "helloagents",
3
- "version": "3.0.32",
3
+ "version": "3.0.33",
4
4
  "description": "Quality-driven orchestration kernel for AI CLIs",
5
5
  "contextFileName": "bootstrap.md",
6
6
  "author": "HelloWind",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "helloagents",
3
- "version": "3.0.32",
3
+ "version": "3.0.33",
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",
@@ -55,7 +55,7 @@ function buildAliasRouteNote(skillName) {
55
55
  }
56
56
 
57
57
  function buildDelegatedTaskHint() {
58
- return '若当前输入明显来自上级代理、控制器或多代理协作上下文,且本次输出会交回上级代理继续汇总、决策或复述,而不是直接交付给最终用户,则按子代理处理:直接完成局部任务并返回结果、证据或阻塞项,不使用 HelloAGENTS 外层输出格式,不写 turn-state,不做面向最终用户的收尾。'
58
+ return '若当前任务由上级代理、控制器或宿主协作/委派机制创建,或本次输出会交回上级代理继续汇总、决策或复述,而不是直接交付给最终用户,则一律按子代理处理:直接完成局部任务并返回结果、证据或阻塞项;禁止输出 HelloAGENTS 外层格式、`🔄 下一步:`、turn-state 或面向最终用户的收尾。'
59
59
  }
60
60
 
61
61
  export function buildCompactionContext({ payload, pkgRoot, settings, bootstrapFile, host }) {
@@ -14,6 +14,14 @@ const PAYLOAD_KEY_ALIASES = {
14
14
  stop_hook_active: 'stopHookActive',
15
15
  'goal-id': 'goalId',
16
16
  goal_id: 'goalId',
17
+ is_subagent: 'isSubagent',
18
+ sub_agent: 'subagent',
19
+ parent_agent_id: 'parentAgentId',
20
+ parent_turn_id: 'parentTurnId',
21
+ delegated_by_agent_id: 'delegatedByAgentId',
22
+ agent_id: 'agentId',
23
+ agent_role: 'agentRole',
24
+ agent_kind: 'agentKind',
17
25
  }
18
26
 
19
27
  function assignAlias(target, source, sourceKey, targetKey) {
@@ -4,7 +4,7 @@
4
4
  */
5
5
  import { platform } from 'node:os';
6
6
  import { join } from 'node:path';
7
- import { existsSync } from 'node:fs';
7
+ import { appendFileSync, existsSync } from 'node:fs';
8
8
  import { execFileSync, spawn } from 'node:child_process';
9
9
 
10
10
  const PLAT = platform();
@@ -19,6 +19,17 @@ const NOTIFY_MESSAGES = {
19
19
 
20
20
  const WIN_APPID = 'HelloAgents.Notification';
21
21
  const DISABLE_OS_NOTIFICATIONS = process.env.HELLOAGENTS_DISABLE_OS_NOTIFICATIONS === '1';
22
+ const TEST_NOTIFY_LOG = String(process.env.HELLOAGENTS_NOTIFY_TEST_LOG || '').trim();
23
+
24
+ function recordTestTransport(kind, event) {
25
+ if (!TEST_NOTIFY_LOG) return false;
26
+ try {
27
+ appendFileSync(TEST_NOTIFY_LOG, `${kind} ${String(event || '')}\n`, 'utf-8');
28
+ return true;
29
+ } catch {
30
+ return false;
31
+ }
32
+ }
22
33
 
23
34
  function escapeToastText(value = '') {
24
35
  return String(value)
@@ -100,6 +111,7 @@ function runSoundHelper(pkgRoot, event, mode = 'background') {
100
111
 
101
112
  export function playSound(pkgRoot, event, options = {}) {
102
113
  if (DISABLE_OS_NOTIFICATIONS) return;
114
+ if (recordTestTransport('sound', event)) return;
103
115
  const wav = resolveWav(pkgRoot, event);
104
116
  if (!wav) { process.stderr.write('\x07'); return; }
105
117
  if (runSoundHelper(pkgRoot, event, options.mode === 'blocking' ? 'blocking' : 'background')) return;
@@ -152,6 +164,7 @@ $toast = [Windows.UI.Notifications.ToastNotification]::new($doc)
152
164
 
153
165
  export function desktopNotify(pkgRoot, event, extra) {
154
166
  if (DISABLE_OS_NOTIFICATIONS) return;
167
+ if (recordTestTransport('desktop', event)) return;
155
168
  const notification = buildDesktopNotificationContent(event, extra);
156
169
  try {
157
170
  if (PLAT === 'win32') {
@@ -88,6 +88,59 @@ function getSettings() {
88
88
  return readSettings(CONFIG_FILE);
89
89
  }
90
90
 
91
+ function hasTruthyAgentFlag(value) {
92
+ if (typeof value === 'boolean') return value;
93
+ const normalized = String(value || '').trim().toLowerCase();
94
+ return ['1', 'true', 'yes', 'subagent'].includes(normalized);
95
+ }
96
+
97
+ function hasNonEmptyValue(value) {
98
+ return String(value || '').trim().length > 0;
99
+ }
100
+
101
+ function looksLikeCodexDelegatedTurn(payload = {}) {
102
+ if (!IS_CODEX || !payload || typeof payload !== 'object') return false;
103
+ const client = String(payload.client || '').trim();
104
+ const inputMessages = Array.isArray(payload.inputMessages) ? payload.inputMessages : [];
105
+ return !client && inputMessages.length > 1;
106
+ }
107
+
108
+ function isSubagentPayload(payload = {}) {
109
+ if (!payload || typeof payload !== 'object') return false;
110
+
111
+ if ([payload.isSubagent, payload.subagent].some(hasTruthyAgentFlag)) {
112
+ return true;
113
+ }
114
+
115
+ const roleLike = [
116
+ payload.role,
117
+ payload.agentRole,
118
+ payload.agent_role,
119
+ payload.agentKind,
120
+ payload.agent_kind,
121
+ payload.kind,
122
+ ]
123
+ .map((value) => String(value || '').trim().toLowerCase())
124
+ .filter(Boolean);
125
+
126
+ if (roleLike.some((value) => ['subagent', 'delegate', 'delegated', 'worker', 'explorer'].includes(value))) {
127
+ return true;
128
+ }
129
+
130
+ if ([
131
+ payload.parentAgentId,
132
+ payload.parent_agent_id,
133
+ payload.parentTurnId,
134
+ payload.parent_turn_id,
135
+ payload.delegatedByAgentId,
136
+ payload.delegated_by_agent_id,
137
+ ].some(hasNonEmptyValue)) {
138
+ return true;
139
+ }
140
+
141
+ return looksLikeCodexDelegatedTurn(payload);
142
+ }
143
+
91
144
  function shouldRunRalphLoop(cwd, turnState, payload = {}) {
92
145
  if (!turnState || turnState.kind !== 'complete') return false;
93
146
  if (turnState.requiresDeliveryGate) return true;
@@ -230,13 +283,28 @@ async function runRalphLoop(payload, { turnState } = {}) {
230
283
  blockEvent: 'verify_gate_blocked',
231
284
  exportName: 'evaluateRalphLoop',
232
285
  evaluateArgs: [payload, {
233
- isSubagent: false,
286
+ isSubagent: isSubagentPayload(payload),
234
287
  isGemini: IS_GEMINI,
235
288
  hookEventName: HOST === 'codex' ? 'Stop' : (IS_GEMINI ? 'SessionEnd' : 'Stop'),
236
289
  }],
237
290
  });
238
291
  }
239
292
 
293
+ async function runCodexSubagentGate(payload) {
294
+ if (!IS_CODEX || !isSubagentPayload(payload)) return false;
295
+ return await runInlineGate({
296
+ payload,
297
+ source: 'ralph-loop',
298
+ blockEvent: 'verify_gate_blocked',
299
+ exportName: 'evaluateRalphLoop',
300
+ evaluateArgs: [payload, {
301
+ isSubagent: true,
302
+ isGemini: false,
303
+ hookEventName: 'Stop',
304
+ }],
305
+ });
306
+ }
307
+
240
308
  async function runDeliveryGate(payload) {
241
309
  return await runInlineGate({
242
310
  payload,
@@ -447,6 +515,11 @@ async function cmdStop() {
447
515
  const payload = readPayloadFromStdin();
448
516
  const cwd = payload.cwd || process.cwd();
449
517
  const turnPayload = attachTurnSession(payload, cwd);
518
+ if (await runCodexSubagentGate(turnPayload)) return;
519
+ if (IS_CODEX && isSubagentPayload(turnPayload)) {
520
+ emptySuppress();
521
+ return;
522
+ }
450
523
  const turnState = readMainTurnState(cwd, turnPayload);
451
524
  const managedCodexStopHook = IS_CODEX && hasManagedCodexStopHook();
452
525
  const skipCompleteNotify = managedCodexStopHook && hasCodexQuickNotifyEvidence(cwd, {
@@ -504,7 +577,10 @@ async function cmdCodexNotify() {
504
577
  return;
505
578
  }
506
579
  if (type !== 'agent-turn-complete') return;
507
- if (hasManagedCodexStopHook()) {
580
+ const managedCodexStopHook = hasManagedCodexStopHook();
581
+ if (managedCodexStopHook && !String(turnPayload.client || '').trim()) return;
582
+ if (isSubagentPayload(turnPayload)) return;
583
+ if (managedCodexStopHook) {
508
584
  const turnState = readMainTurnState(cwd, turnPayload);
509
585
  if (shouldEmitManagedCodexCompleteNotify(cwd, turnState, turnPayload)) {
510
586
  notifyByLevel('complete', buildNotifyExtra(data), getSettings(), { mode: 'blocking' });
@@ -98,6 +98,59 @@ function getLastAssistantMessage(data = {}) {
98
98
  ).trim();
99
99
  }
100
100
 
101
+ function hasTruthyAgentFlag(value) {
102
+ if (typeof value === 'boolean') return value;
103
+ const normalized = String(value || '').trim().toLowerCase();
104
+ return ['1', 'true', 'yes', 'subagent'].includes(normalized);
105
+ }
106
+
107
+ function hasNonEmptyValue(value) {
108
+ return String(value || '').trim().length > 0;
109
+ }
110
+
111
+ function looksLikeCodexDelegatedTurn(data = {}) {
112
+ if (!data || typeof data !== 'object') return false;
113
+ const client = String(data.client || '').trim();
114
+ const inputMessages = Array.isArray(data.inputMessages) ? data.inputMessages : [];
115
+ return !client && inputMessages.length > 1;
116
+ }
117
+
118
+ function inferSubagentFromPayload(data = {}) {
119
+ if (!data || typeof data !== 'object') return false;
120
+
121
+ if ([data.isSubagent, data.subagent].some(hasTruthyAgentFlag)) {
122
+ return true;
123
+ }
124
+
125
+ const roleLike = [
126
+ data.role,
127
+ data.agentRole,
128
+ data.agent_role,
129
+ data.agentKind,
130
+ data.agent_kind,
131
+ data.kind,
132
+ ]
133
+ .map((value) => String(value || '').trim().toLowerCase())
134
+ .filter(Boolean);
135
+
136
+ if (roleLike.some((value) => ['subagent', 'delegate', 'delegated', 'worker', 'explorer'].includes(value))) {
137
+ return true;
138
+ }
139
+
140
+ if ([
141
+ data.parentAgentId,
142
+ data.parent_agent_id,
143
+ data.parentTurnId,
144
+ data.parent_turn_id,
145
+ data.delegatedByAgentId,
146
+ data.delegated_by_agent_id,
147
+ ].some(hasNonEmptyValue)) {
148
+ return true;
149
+ }
150
+
151
+ return looksLikeCodexDelegatedTurn(data);
152
+ }
153
+
101
154
  function hasHelloagentsWrapper(message = '') {
102
155
  if (!message.includes('【HelloAGENTS】')) return false;
103
156
  const firstNonEmptyLine = message
@@ -138,7 +191,7 @@ export function evaluateRalphLoop(data = {}, runtime = {}) {
138
191
 
139
192
  const cwd = data.cwd || process.cwd();
140
193
  const runtimeOptions = { payload: data };
141
- const isSubagent = runtime.isSubagent ?? IS_SUBAGENT;
194
+ const isSubagent = runtime.isSubagent === true || inferSubagentFromPayload(data) || IS_SUBAGENT;
142
195
  const hookEventName = runtime.hookEventName || HOOK_EVENT;
143
196
 
144
197
  if (isSubagent) {
@@ -6,7 +6,7 @@ description: 按任务类型适用 — 建立质量驱动工作流,通过技
6
6
  # HelloAGENTS
7
7
 
8
8
  主代理触发或读取任意 skill 时,只有直接面向最终用户、且当前对话已经结束的终局交付,才按通用输出格式包装;流式内容、进度或状态汇报、中间文本,以及任何仍将继续执行或会交回上级代理继续消费的文本,都保持自然输出。最终回复中的 `🔄 下一步` 写真实动作,不写当前状态;等待用户授权时使用等待输入态收尾,已获授权且可继续执行时不得收尾。同一条最终回复只包装一次;若需要分段,在同一个外层块内展开,不在正文里再次输出 `【HelloAGENTS】` 或第二个 `🔄 下一步`。
9
- 若当前输出会交回上级代理、控制器或其他代理继续汇总、决策、复述或等待后续动作,则按子代理处理:只豁免输出格式、交互确认与停顿、统一执行流程、任务分层、完成判定、命令路由和流程状态,直接执行任务;安全、质量、验证和失败处理规则仍持续生效,且不得包装 HelloAGENTS 外层输出格式。
9
+ 若当前输出会交回上级代理、控制器或其他代理继续汇总、决策、复述或等待后续动作,或当前任务由宿主协作/委派机制创建(包括 spawn_agent、worker / explorer、并行代理、Codex agent/delegate/wait 等),则一律按子代理处理:只豁免输出格式、交互确认与停顿、统一执行流程、任务分层、完成判定、命令路由和流程状态,直接执行任务;安全、质量、验证和失败处理规则仍持续生效。子代理只返回结果、证据或阻塞项;不得包装 HelloAGENTS 外层输出格式,不写 `🔄 下一步:`,不做面向最终用户的收尾。
10
10
  只有运行时必须识别当前对话“完成 / 等待输入 / 阻塞”时,主代理才写 turn-state;普通问候、普通问答、T0 只读分析和一次性解释不调用。必须调用场景:显式 `~auto` / `~loop`、非只读任务完成验证并进入收尾、需要让运行时识别当前对话已完成、等待输入或已阻塞时、已进入项目连续流程或方案包闭环。首选 `helloagents-turn-state write --kind complete --role main`;等待或阻塞时写 `kind=waiting` / `kind=blocked`,并同时写 `reasonCategory` 与 `reason`。显式 `~auto` / `~loop` 下,还必须写入 `blocker.target`、`blocker.evidence`、`blocker.requiredAction`。不要查找、读取或拼接 `turn-state.mjs` 源码路径。子代理不得写 turn-state。
11
11
  普通问答、解释、分析、改写、邮件回复和其他一次性交付虽然不进入完整实现、验证或收尾流程,但仍属于交付:默认只交付与当前请求直接对应的一版最终结果;请求已满足时直接结束,不主动追加无执行价值的延伸、派生版本、不同写法、第二版或邀约式收尾,除非用户明确要求。
12
12