helloagents 3.0.31 → 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.31",
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.31",
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.31-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)
@@ -211,6 +211,7 @@ Runtime evidence files include:
211
211
  - `.helloagents/sessions/<workspace>/<session>/artifacts/loop-results.tsv`
212
212
 
213
213
  Delivery gate, guard, and loop messages use action-oriented wording such as processing path, closeout action, and visual validation action, so blocked flows show what to do next without turning executable steps into optional suggestions. Final closeout also enforces a single HelloAGENTS wrapper, so one reply does not emit duplicate closeout headers.
214
+ That wrapper is now reserved for direct final-user delivery only. Intermediate reports, delegated task results, and sub-agent replies stay natural, and sub-agent stop hooks reject wrapped closeout replies.
214
215
 
215
216
  ### 7) Safer install, update, cleanup, and diagnostics
216
217
 
@@ -612,7 +613,7 @@ Default shape:
612
613
  | Key | Default | Meaning |
613
614
  |-----|---------|---------|
614
615
  | `output_language` | `""` | follow the user language unless set |
615
- | `output_format` | `true` | main-agent final closeout must use the HelloAGENTS layout; intermediate and sub-agent output stays natural |
616
+ | `output_format` | `true` | direct final-user closeout from the main agent uses the HelloAGENTS layout; intermediate, delegated, and sub-agent output stays natural |
616
617
  | `notify_level` | `0` | `0` off, `1` desktop, `2` sound, `3` both |
617
618
  | `ralph_loop_enabled` | `true` | run verification for explicit `~verify` / `~loop` or required closeout gates |
618
619
  | `guard_enabled` | `true` | block dangerous commands |
@@ -656,7 +657,7 @@ Codex is rules-file driven by default.
656
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
657
658
  - cleanup removes only the HelloAGENTS-managed hook trust entries and legacy managed notify residues, while keeping user-owned hook state untouched
658
659
  - Codex hooks only synchronize runtime state and enforce Stop gates; they do not inject HelloAGENTS rules or route text through hook output
659
- - 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
660
661
  - `/goal` remains Codex-native. Enable it explicitly with `helloagents codex goals enable` when long-running plan execution is needed
661
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
662
663
 
@@ -679,7 +680,7 @@ The current suite covers:
679
680
  - `helloagents doctor`
680
681
  - project storage and `repo-shared` behavior
681
682
  - session-scoped `state_path`, runtime signals, and evidence
682
- - 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
683
684
  - README and skill contract alignment
684
685
 
685
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.31-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)
@@ -211,6 +211,7 @@ HelloAGENTS 不把“命令通过”和“任务完成”简单画等号。交
211
211
  - `.helloagents/sessions/<workspace>/<session>/artifacts/loop-results.tsv`
212
212
 
213
213
  交付门控、守卫和循环提示使用执行性表述,例如处理路径、收尾动作和视觉验收动作。阻塞流程会说明下一步要做什么,而不是把可执行步骤写成泛化建议。最终回复还会强制只保留一个 HelloAGENTS 外层块,避免同一条回复重复输出完成标题。
214
+ 这个外层格式现在只保留给直接面向最终用户的终局交付。中间汇报、委派任务结果和子代理回复都保持自然输出;子代理结束钩子也会拦截错误的外层收尾格式。
214
215
 
215
216
  ### 7)更安全的安装、更新、清理和诊断
216
217
 
@@ -616,7 +617,7 @@ UI 任务遵循以下优先级:
616
617
  | 键 | 默认值 | 含义 |
617
618
  |----|--------|------|
618
619
  | `output_language` | `""` | 默认跟随用户语言 |
619
- | `output_format` | `true` | 主代理最终回复必须使用 HelloAGENTS 格式;中间输出和子代理输出保持自然 |
620
+ | `output_format` | `true` | 仅主代理直接面向最终用户的终局交付使用 HelloAGENTS 格式;中间输出、委派结果和子代理输出保持自然 |
620
621
  | `notify_level` | `0` | `0` 关闭,`1` 桌面通知,`2` 声音,`3` 两者 |
621
622
  | `ralph_loop_enabled` | `true` | 显式 `~verify` / `~loop` 或收尾要求时运行验证 |
622
623
  | `guard_enabled` | `true` | 拦截危险命令 |
@@ -660,7 +661,7 @@ Codex 默认走规则文件驱动。
660
661
  - 全局模式安装原生本地插件流程,但仍把 `~/.helloagents/helloagents` 作为唯一受管运行时源;插件根目录、插件缓存和 `~/.codex/helloagents` 都会回链到它
661
662
  - 清理时只删除 HelloAGENTS 自己写入的 hook trust 条目和旧式受管 notify 残留,不影响用户已有的 hook 状态
662
663
  - Codex hooks 只做静默运行态同步和 Stop 门禁,不通过 hook 注入 HelloAGENTS 规则或路由说明
663
- - Codex 收尾会对 Stop hook 和原生 `codex-notify` 去重,避免同一轮重复通知
664
+ - Codex 收尾会对 Stop hook 和原生 `codex-notify` 去重,避免同一轮重复通知;受管 Stop hook 生效时,client 为空的委派子任务完成事件也会保持静默
664
665
  - `/goal` 保持 Codex 原生能力;需要长程执行时,用 `helloagents codex goals enable` 显式启用
665
666
  - 感知 goal 的命令从 `tasks.md`、`contract.json` 和 `state_path` 恢复;不会自动创建 goal,也不会在 HelloAGENTS 验证和收尾前标记完成
666
667
 
@@ -683,7 +684,7 @@ npm test
683
684
  - `helloagents doctor`
684
685
  - 项目存储和 `repo-shared`
685
686
  - 会话级 `state_path`、运行态信号和证据
686
- - 运行时注入、选路、Guard、验证、视觉证据、交付门控、收尾去重,以及原生安装失败后的模式记录
687
+ - 运行时注入、选路、Guard、验证、视觉证据、交付门控、收尾去重、子代理外层格式与通知静默保护,以及原生安装失败后的模式记录
687
688
  - README 与 skill 契约一致性
688
689
 
689
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
@@ -112,8 +112,8 @@
112
112
 
113
113
  排除条件:
114
114
  - 当 `output_format` 为 `false` 时,所有回复保持自然输出,不得使用输出格式。
115
- - 以下内容一律视为中间输出,必须自然输出,不得使用输出格式:流式输出阶段的可见文本、思考/进度说明、工具调用前的说明、工具执行中的状态汇报,以及任何发出后仍会继续调用工具或继续执行的回复。
116
- - 子代理在任何场景下都不得使用输出格式。
115
+ - 以下内容一律视为中间输出,必须自然输出,不得使用输出格式:流式输出阶段的可见文本、思考/进度说明、工具调用前的说明、工具执行中的状态汇报,以及任何发出后仍会继续调用工具、继续执行,或会交回上级代理/控制器继续消费的回复。
116
+ - 凡是不直接面向最终用户终局交付的回复,包括子代理、协作汇报和会交回上级代理继续处理的结果,都不得使用输出格式。
117
117
 
118
118
  输出格式:
119
119
 
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
@@ -112,8 +112,8 @@
112
112
 
113
113
  排除条件:
114
114
  - 当 `output_format` 为 `false` 时,所有回复保持自然输出,不得使用输出格式。
115
- - 以下内容一律视为中间输出,必须自然输出,不得使用输出格式:流式输出阶段的可见文本、思考/进度说明、工具调用前的说明、工具执行中的状态汇报,以及任何发出后仍会继续调用工具或继续执行的回复。
116
- - 子代理在任何场景下都不得使用输出格式。
115
+ - 以下内容一律视为中间输出,必须自然输出,不得使用输出格式:流式输出阶段的可见文本、思考/进度说明、工具调用前的说明、工具执行中的状态汇报,以及任何发出后仍会继续调用工具、继续执行,或会交回上级代理/控制器继续消费的回复。
116
+ - 凡是不直接面向最终用户终局交付的回复,包括子代理、协作汇报和会交回上级代理继续处理的结果,都不得使用输出格式。
117
117
 
118
118
  输出格式:
119
119
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "helloagents",
3
- "version": "3.0.31",
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.31",
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",
@@ -54,6 +54,10 @@ function buildAliasRouteNote(skillName) {
54
54
  return '';
55
55
  }
56
56
 
57
+ function buildDelegatedTaskHint() {
58
+ return '若当前任务由上级代理、控制器或宿主协作/委派机制创建,或本次输出会交回上级代理继续汇总、决策或复述,而不是直接交付给最终用户,则一律按子代理处理:直接完成局部任务并返回结果、证据或阻塞项;禁止输出 HelloAGENTS 外层格式、`🔄 下一步:`、turn-state 或面向最终用户的收尾。'
59
+ }
60
+
57
61
  export function buildCompactionContext({ payload, pkgRoot, settings, bootstrapFile, host }) {
58
62
  const summaryParts = [];
59
63
  summaryParts.push('## HelloAGENTS 压缩摘要');
@@ -148,7 +152,7 @@ export function buildRouteInstruction({ skillName, extraRules = '', cwd, pkgRoot
148
152
  const commandHint = buildCommandRouteHint(canonicalSkillName, cwd, workflowOptions);
149
153
  const capabilityHint = buildCapabilityHint({ cwd, skillName: canonicalSkillName, options: workflowOptions });
150
154
  const projectStorageHint = buildProjectStorageHint(cwd, workflowOptions);
151
- return `用户使用了 ~${skillName} 命令。当前命令技能文件已解析为:${skillPath}。请直接读取这个 SKILL.md;不要再探测其他 helloagents 路径。${aliasNote ? ` ${aliasNote}` : ''}${projectStorageHint ? ` ${projectStorageHint}` : ''}${commandHint ? ` ${commandHint}` : ''}${capabilityHint ? ` ${capabilityHint}` : ''}${extraRules}`;
155
+ return `用户使用了 ~${skillName} 命令。当前命令技能文件已解析为:${skillPath}。请直接读取这个 SKILL.md;不要再探测其他 helloagents 路径。 ${buildDelegatedTaskHint()}${aliasNote ? ` ${aliasNote}` : ''}${projectStorageHint ? ` ${projectStorageHint}` : ''}${commandHint ? ` ${commandHint}` : ''}${capabilityHint ? ` ${capabilityHint}` : ''}${extraRules}`;
152
156
  }
153
157
 
154
158
  export function buildSemanticRouteInstruction(cwd, payload = {}) {
@@ -159,6 +163,7 @@ export function buildSemanticRouteInstruction(cwd, payload = {}) {
159
163
  return [
160
164
  '当前消息未使用 ~command。',
161
165
  '请根据用户请求的真实意图选路,不依赖关键词表。',
166
+ buildDelegatedTaskHint(),
162
167
  'Delivery Tier: T0=探索/比较;T1=低风险小改动或显式验证;T2=多文件功能/新项目/需要结构化产物;T3=高风险或不可逆操作。',
163
168
  '路由映射:~idea=只读探索,不创建文件;~build=明确实现;~verify=审查/验证;~plan=结构化规划;~prd=重型规格;~auto=自动选择并继续执行后续阶段。',
164
169
  '若判定为 T3,默认先走 ~plan / ~prd;纯审查/验证请求才优先 ~verify。',
@@ -4,10 +4,6 @@ export function shouldIgnoreCodexNotifyClient(client) {
4
4
  return normalized !== 'codex' && !normalized.startsWith('codex-');
5
5
  }
6
6
 
7
- export function shouldIgnoreFormattedSubagent(lastMsg, outputFormatEnabled) {
8
- return outputFormatEnabled && !lastMsg.includes('【HelloAGENTS】');
9
- }
10
-
11
7
  export function resolveNotifyHost(argv = []) {
12
8
  const args = Array.from(argv, (value) => String(value || ''));
13
9
  const command = args[2] || args[0] || '';
@@ -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) {
@@ -17,7 +17,7 @@ export function resolveBootstrapFile(cwd, settings = {}, host = '') {
17
17
  }
18
18
 
19
19
  function shouldBypassRoute(prompt) {
20
- return !prompt || /^\[子代理任务\]/.test(prompt)
20
+ return !prompt
21
21
  }
22
22
 
23
23
  function buildHelpExtraRules(skillName) {
@@ -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' });
@@ -89,6 +89,83 @@ function runVerify(commands, cwd) {
89
89
  return failures;
90
90
  }
91
91
 
92
+ function getLastAssistantMessage(data = {}) {
93
+ return String(
94
+ data.lastAssistantMessage
95
+ || data.last_assistant_message
96
+ || data['last-assistant-message']
97
+ || '',
98
+ ).trim();
99
+ }
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
+
154
+ function hasHelloagentsWrapper(message = '') {
155
+ if (!message.includes('【HelloAGENTS】')) return false;
156
+ const firstNonEmptyLine = message
157
+ .split(/\r?\n/)
158
+ .map((line) => line.trim())
159
+ .find(Boolean);
160
+ return /^[💡⚡🔵✅❓⚠️❌]【HelloAGENTS】- /.test(firstNonEmptyLine || '') || message.includes('【HelloAGENTS】');
161
+ }
162
+
163
+ function validateSubagentOutput(data = {}) {
164
+ const message = getLastAssistantMessage(data);
165
+ if (!message || !hasHelloagentsWrapper(message)) return '';
166
+ return '[HelloAGENTS Runtime] 子代理输出不应使用 HelloAGENTS 外层格式。当前回复不是直接面向最终用户的终局交付,请改为自然输出,只返回结果、证据或阻塞项。';
167
+ }
168
+
92
169
  /** Filter commands to fast checks only for subagent mode. Returns null if no fast commands found. */
93
170
  function filterSubagentCommands(commands) {
94
171
  const fast = commands.filter(cmd =>
@@ -114,9 +191,20 @@ export function evaluateRalphLoop(data = {}, runtime = {}) {
114
191
 
115
192
  const cwd = data.cwd || process.cwd();
116
193
  const runtimeOptions = { payload: data };
117
- const isSubagent = runtime.isSubagent ?? IS_SUBAGENT;
194
+ const isSubagent = runtime.isSubagent === true || inferSubagentFromPayload(data) || IS_SUBAGENT;
118
195
  const hookEventName = runtime.hookEventName || HOOK_EVENT;
119
196
 
197
+ if (isSubagent) {
198
+ const formatReason = validateSubagentOutput(data);
199
+ if (formatReason) {
200
+ return {
201
+ decision: 'block',
202
+ reason: formatReason,
203
+ suppressOutput: true,
204
+ };
205
+ }
206
+ }
207
+
120
208
  let commands = detectCommands(cwd);
121
209
  if (!commands?.length) {
122
210
  return { suppressOutput: true };
@@ -12,7 +12,7 @@ description: 使用子代理执行任务、并行开发、分派工作时使用
12
12
  ## 派遣规范
13
13
  - 每个子代理获得:tasks.md 中的对应任务 + 方案包中的相关约束(~plan: requirements.md + plan.md;~prd: prd/ 中的相关维度文件 + decisions.md)+ 验证命令;涉及 UI 时,再附 `.helloagents/DESIGN.md`(按当前项目存储模式解析)或其中相关片段
14
14
  - 新鲜上下文:不继承主会话历史,避免上下文污染
15
- - 提示开头标记 [子代理任务],让子代理跳过 HelloAGENTS 外层流程
15
+ - 明确这是局部委派任务:当前输出会交回上级代理继续汇总、决策或复述,而不是直接交付给最终用户;因此只返回结果、证据或阻塞项,不包装 HelloAGENTS 外层格式,也不写 turn-state
16
16
  - 单一职责:一个子代理只做一件事
17
17
  - 只提取子代理需要的上下文,不把整个方案包全部塞给子代理
18
18
 
@@ -5,8 +5,8 @@ description: 按任务类型适用 — 建立质量驱动工作流,通过技
5
5
 
6
6
  # HelloAGENTS
7
7
 
8
- 主代理触发或读取任意 skill 时,只有当前对话的最终回复才按通用输出格式包装;流式内容、进度或状态汇报、中间文本,以及任何仍将继续执行的文本,都保持自然输出。最终回复中的 `🔄 下一步` 写真实动作,不写当前状态;等待用户授权时使用等待输入态收尾,已获授权且可继续执行时不得收尾。同一条最终回复只包装一次;若需要分段,在同一个外层块内展开,不在正文里再次输出 `【HelloAGENTS】` 或第二个 `🔄 下一步`。
9
- 子代理只豁免输出格式、交互确认与停顿、统一执行流程、任务分层、完成判定、命令路由和流程状态,直接执行任务;安全、质量、验证和失败处理规则仍持续生效,且不得包装 HelloAGENTS 外层输出格式。
8
+ 主代理触发或读取任意 skill 时,只有直接面向最终用户、且当前对话已经结束的终局交付,才按通用输出格式包装;流式内容、进度或状态汇报、中间文本,以及任何仍将继续执行或会交回上级代理继续消费的文本,都保持自然输出。最终回复中的 `🔄 下一步` 写真实动作,不写当前状态;等待用户授权时使用等待输入态收尾,已获授权且可继续执行时不得收尾。同一条最终回复只包装一次;若需要分段,在同一个外层块内展开,不在正文里再次输出 `【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