@nathan33/nao-skill 0.18.2 → 0.18.4

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.
@@ -26,6 +26,7 @@ description: pi-intercom 多会话协作协议(常驻引用)
26
26
  拉起:`bash .agents/scripts/nao-fleet.sh ensure <别名>[@<repo>]`
27
27
 
28
28
  - **任务派生**:`ensure --task <编号> <别名>[@<repo>]` 创建 `<角色>-<编号>` 独立会话(如 `rd-be-T1`),并行隔离、互不排队/打断,避免多任务同名冲突;任务闭环即**回收**:`nao-fleet.sh close --task <编号> <别名>`(内置在跑 turn / tasks-state 闸门)。
29
+ - **子代理会话不属舰队会话身份模型**:由 pi-subagents 拉起的子代理会话以 `subagent-<agent>-<id>`(无名时 `subagent-chat-<id>`)进入 intercom 名册,但**不属**舰队会话 —— `status` 只对其汇总计数(不列名、不参与在线/turn 判重),`close subagent-*` 显式拒绝(生命周期归 pi-subagents 的 stop/retention,nao 不接管)。
29
30
 
30
31
  - **判重 / 在线判定**:脚本按 pi 自设的**终端标题**(`π - <会话名> - <仓库名>`)+ `pi-intercom` 名册判在线(**不用 `pgrep --name`**:pi 启动后 argv 被改写为 `pi`,会恒失配);已运行则跳过并 warn。确需重开加 `--force`。
31
32
  - **标题被改写时的 pane 定位**(v0.9.4):终端标题可被外部改写(实测变为 `pi:c`),此时 `status`/`close`/`ensure` 按三层后备定位 pane:① 标题契约 → ② pi-intercom 名册 `tmuxPane`(注册时读 `$TMUX_PANE`,与标题改名无关,权威)→ ③ 本仓「未被认领 pi pane」唯一兜底。三层均未命中才认「无法确认」并 exit 非 0(不静默 no-op)。`close` 的在跑 turn 判定叠加名册活动状态(thinking / tool:* 等)与 pane 末 12 行状态行双重信号。
@@ -36,6 +37,13 @@ description: pi-intercom 多会话协作协议(常驻引用)
36
37
  显式指定时由 `NAO_MODEL_WHITELIST` 在**命令行入口解析阶段**即校验,
37
38
  不命中直接 `die`,无部分拉起;PM 只需如实转达用户 `-m` 指定。
38
39
 
40
+ ## 会话内子代理(授权与回执归属)
41
+
42
+ - **授权**:角色可在自身 turn 内用 pi-subagents 起**会话内子代理**(scout / researcher / oracle / reviewer / evidence-auditor 等)。默认**只读**;需要写入时须 `worktree: true` 隔离并指定**单一集成者**(父角色),同一 cwd 禁多写。子代理既**不属**舰队会话身份模型 —— 边界与 `status`/`close` 处置**见上文「子代理会话不属舰队会话身份模型」**,此处不重复。
43
+ - **回执归属(硬性)**:子代理的 run 结果**只是证据,不等于回执**;`[编号] done(lite|full)` **只能由父角色**在自行核对后经 intercom `send` 发出(PM 只认父角色 done)。父角色不得把子代理输出直接转贴为回执。
44
+ - **子代理禁令**:子代理**不得以舰队角色名义回执**、**不得发送 `done` 形态消息**、**不得直发 PM**;需决策/阻塞/授权走 `contact_supervisor`(进程内 native 通道,无需 pi-intercom;父侧对应用 `subagent_supervisor`)。
45
+ - **证据格式**:子代理产物须含**命令 + exit code + 例数/文件数(或断言数)+ 路径**,供父角色写入回执「测试」栏;无证据的「完成」不算证据。注入文本见 `@.agents/templates/pi-subagents/intercom-bridge.md`(安装说明见同目录 `README.md`)。
46
+
39
47
  ## 开工前自检(派发闸门)
40
48
 
41
49
  派发前跑一次:
@@ -243,8 +243,14 @@ check_roles_indent() {
243
243
  check_alias_resolution() {
244
244
  local rc=0 ga gr
245
245
  local -A owner=()
246
+ # T525 BR6/AC7:`subagent-` 前缀保留给 pi-subagents 子代理会话 —— canonical id 与 alias **都不得占用**
247
+ # (否则 status/close 的排除面会吃掉该角色)。折入本函数(唯一枚举点,避第二处遍历漂移),
248
+ # 与缩进/别名唯一性各自累积 rc。
246
249
  while IFS=$'\t' read -r ga gr; do
247
250
  [[ -n "$ga" && -n "$gr" ]] || continue
251
+ if [[ "$ga" == subagent-* ]]; then
252
+ printf ' ✗ 角色 id/别名 %s 以 subagent- 开头(该前缀保留给 pi-subagents 子代理会话,nao 不接管)\n' "$ga"; rc=1
253
+ fi
248
254
  if [[ -n "${owner[$ga]:-}" && "${owner[$ga]}" != "$gr" ]]; then
249
255
  printf ' ✗ 别名 %s 跨角色重名:%s 与 %s\n' "$ga" "${owner[$ga]}" "$gr"; rc=1
250
256
  fi
@@ -261,11 +267,14 @@ check_alias_resolution() {
261
267
  for (i=1;i<=n;i++){ gsub(/^[[:space:]]+|[[:space:]]+$/, "", a[i]); if(a[i]!="") print a[i] "\t" c }
262
268
  }' "$MANIFEST")
263
269
  for gr in "${ROLE_ORDER[@]}"; do
270
+ if [[ "$gr" == subagent-* ]]; then
271
+ printf ' ✗ 角色 id/别名 %s 以 subagent- 开头(该前缀保留给 pi-subagents 子代理会话,nao 不接管)\n' "$gr"; rc=1
272
+ fi
264
273
  if [[ "${owner[$gr]:-}" != "$gr" ]]; then
265
274
  printf ' ✗ 角色 %s 的 canonical id 未登记于 aliases(BR2:canonical 须可解析为自身)\n' "$gr"; rc=1
266
275
  fi
267
276
  done
268
- (( rc == 0 )) && printf ' ✓ 别名解析守卫:全部别名可解析且无跨角色重名\n'
277
+ (( rc == 0 )) && printf ' ✓ 别名解析守卫:全部别名可解析且无跨角色重名 + 无 subagent- 保留前缀\n'
269
278
  return $rc
270
279
  }
271
280
 
@@ -606,6 +615,17 @@ derived_in_field() { # <name> → 派生名列表(每行一个;空 = 无)
606
615
  done < <(visible_session_names)
607
616
  }
608
617
 
618
+ # 子代理会话计数(T525):与 derived_in_field 同源(visible_session_names,已去重)中锚首 `subagent-` 者。
619
+ # 子代理(pi-subagents)不属舰队会话身份模型 —— status 只计数不列名;生命周期归 pi-subagents,nao 不回收。
620
+ # BR6 前缀精确:`subagent-*` 不匹配 `subagentfoo` / 裸 `subagent` / `mysubagent-x`。
621
+ subagent_in_field() { # → echo 子代理会话数(0..N)
622
+ local n c=0
623
+ while IFS= read -r n; do
624
+ [[ "$n" == subagent-* ]] && c=$((c + 1))
625
+ done < <(visible_session_names)
626
+ printf '%s\n' "$c"
627
+ }
628
+
609
629
  # screen 会话精确令牌(T516 F1/F2 · arch 评审):从 `screen -ls` 取**精确** nao-<名> 会话的 `pid.name` 令牌。
610
630
  # 与 running()/close 检测同一锚定(名字后必须是空白或行尾;`\b` 在派生名的 "-" 处仍成立,故不用)。
611
631
  # 检测与销毁共用本 helper ⇒ 检测锚与销毁目标不会漂移;**取不到令牌即空,调用方不得 quit**(宁不回收,也不误伤派生)。
@@ -661,12 +681,27 @@ visible_session_names() {
661
681
  } | awk -F'\t' 'NF {print $1}' | awk '!seen[$0]++'
662
682
  }
663
683
 
684
+ # 名册「条目感知」pane 认领集(T525 E2):name↔tmuxPane 配对后**过滤** `^subagent-` 条目 ⇒ 每行一个 pane。
685
+ # 兼容紧凑/pretty 两种 JSON:`sed 's/},/}\n/g'` 先按对象边界切行(纯状态机 awk 在紧凑单行名册里会因
686
+ # 贪婪匹配塌缩成末条 ⇒ 假告警);取值法与 intercom_pane_for 的逐条目取法同源。
687
+ # ⚠ 仅用于 unattributed_pi_panes:fallback_pane_for 的 claimed 语义是「该 pane 已被占用」(含子代理),
688
+ # 排除会致兜底**误认领父 pane**(D-A 已裁定,见 PRD §三.4)。
689
+ roster_pane_claims() { # → 被非子代理会话认领的 pane_id(每行一个;rc 恒 0)
690
+ intercom_list_json 2>/dev/null | sed 's/},/}\n/g' | awk '
691
+ /"name":[[:space:]]*"/ { s=$0; sub(/.*"name":[[:space:]]*"/,"",s); sub(/".*/,"",s); n=s }
692
+ /"tmuxPane":[[:space:]]*"/ { s=$0; sub(/.*"tmuxPane":[[:space:]]*"/,"",s); sub(/".*/,"",s); p=s
693
+ if (p != "" && n !~ /^subagent-/) print p }
694
+ ' || true
695
+ }
696
+
664
697
  # 未能归属到任何会话名的 pi pane(标题不符契约且名册无 tmuxPane 记录):
665
698
  # 输出 pane_id<TAB>pane_title;供 status 做「非静默」提示(不假装看不到)。
699
+ # T525 E2:claimed 用 roster_pane_claims(条目感知、排除 `^subagent-`)⇒ 仅被子代理继承 tmuxPane 的孤儿
700
+ # pane 回到「无法归属会话名」告警(BR8 非静默优先)。
666
701
  unattributed_pi_panes() {
667
702
  command -v tmux >/dev/null 2>&1 || return 0
668
703
  local claimed p tt
669
- claimed="$(intercom_list_json 2>/dev/null | sed -n 's/.*"tmuxPane":[[:space:]]*"\([^"]*\)".*/\1/p')" || claimed=""
704
+ claimed="$(roster_pane_claims)" || claimed=""
670
705
  while IFS=$'\t' read -r p tt; do
671
706
  [[ -n "$p" ]] || continue
672
707
  grep -qxF "$p" <<< "$claimed" && continue
@@ -1560,6 +1595,10 @@ cmd_status() {
1560
1595
  done < <(visible_session_names)
1561
1596
  (( found )) || echo ' (无)'
1562
1597
  [[ -f "$TASKS_STATE" ]] || echo " ($TASKS_STATE 不存在,残留判定已跳过)"
1598
+ # T525:子代理会话(pi-subagents)不属舰队会话身份模型 —— 派生节后独立一行、只计数不列名、N=0 不打印(BR3/BR6)。
1599
+ local nsub
1600
+ nsub="$(subagent_in_field)"
1601
+ (( nsub > 0 )) && printf ' · 另有 %d 个子代理会话(subagent-*)· 生命周期由 pi-subagents 管理,nao 不回收\n' "$nsub"
1563
1602
  # 标题被改写/无名的 pi pane:非静默——显式列出,供人工核对
1564
1603
  local -a orph=()
1565
1604
  local pn pt
@@ -1577,6 +1616,12 @@ cmd_status() {
1577
1616
  cmd_close() {
1578
1617
  local force="$1" task="$2" target="${3:-}"
1579
1618
  [[ -n "$target" ]] || die "close 需要目标:角色别名或派生会话名(如 rd-be / rd-be-T1 / qa-T1)"
1619
+ # T525 BR1/BR2/BR4:子代理会话显式拒绝 —— 判定**只针对 $target 位置名**(`close --task subagent-x qa`
1620
+ # 是合法派生 qa-subagent-x,不得误杀);锚首 glob(`subagentfoo`/裸 `subagent` 不匹配);`--force` 不旁路;
1621
+ # die 于任何 pane 定位/捕获/kill 之前 ⇒ 父 pane 必存活(父+子共用 tmuxPane 亦然)。
1622
+ if [[ "$target" == subagent-* ]]; then
1623
+ die "拒绝回收 $target:这是 pi-subagents 子代理会话(subagent-*),其生命周期不归 nao 回收(请用 pi-subagents 的 stop / retention 管理)"
1624
+ fi
1580
1625
  local name repo cls bus pane pids st tgt a best="" stok
1581
1626
  intercom_list_json >/dev/null 2>&1 || true # 预热名册缓存
1582
1627
 
@@ -0,0 +1,56 @@
1
+ # nao 版子代理桥指令(`intercomBridge.instructionFile`)安装说明
2
+
3
+ 本目录是 nao 舰队对「会话内子代理」的口径模板,供 pi-subagents 的 `intercomBridge.instructionFile` 使用。模板只做一件事:把**回执归属 / 身份禁令 / 证据格式**三条 nao 语义注入子代理。协议侧对应条款见 `@.agents/common/intercom-protocol.md`「会话内子代理(授权与回执归属)」。
4
+
5
+ - **本批不写任何用户配置文件**:下面步骤由使用者自行执行;本仓不修改 `~/.pi/...`。
6
+ - **零敏感值 / 零绝对家目录路径**:本目录内所有文档只用 `~/...` 或 `$HOME/...` 形式。
7
+
8
+ ## 一、安装(两条命令 + 一段配置)
9
+
10
+ 1. 把模板复制到 pi-subagents 的配置目录(目录不存在时先建):
11
+
12
+ ```bash
13
+ mkdir -p ~/.pi/agent/extensions/subagent
14
+ cp <本仓>/.agents/templates/pi-subagents/intercom-bridge.md \
15
+ ~/.pi/agent/extensions/subagent/nao-intercom-bridge.md
16
+ ```
17
+
18
+ 2. 在用户级配置 `~/.pi/agent/extensions/subagent/config.json` 中启用(**相对**路径,不写绝对家目录):
19
+
20
+ ```json
21
+ {
22
+ "intercomBridge": {
23
+ "mode": "always",
24
+ "instructionFile": "nao-intercom-bridge.md",
25
+ "resultDelivery": false
26
+ }
27
+ }
28
+ ```
29
+
30
+ - `instructionFile` 的相对路径从 `~/.pi/agent/extensions/subagent/` 解析,因此与模板放在同一目录即可。
31
+ - `mode` 取值 `off` / `always` / `fork-only`;nao 场景用 `always`(每次子代理都注入)。
32
+ - `resultDelivery` 保持 `false`:nao 不需要外部监听者 ack 结果。
33
+
34
+ 3. 生效检查:在 pi 会话内运行 `/subagents-doctor`,确认 `bridge: active` 且指令来自上述文件。
35
+
36
+ ## 二、为什么不用 `{orchestratorTarget}` 占位符
37
+
38
+ pi-subagents 的默认模板支持一个「父会话目标」占位符,但 nao 版**刻意不使用**它:
39
+
40
+ - 父会话目标由 `contact_supervisor` 在子代理运行时**自行解析**(native 通道,不依赖 pi-intercom);把父会话名写进模板反而会把启动契约摘要绑到具体父会话上,父会话一变就可能让启动前置校验失败。
41
+ - 目标名写死还会带来「换个 PM 会话名就失效」的可移植性问题。
42
+ - 因此本模板只写「用 `contact_supervisor` 回父角色」,不出现任何目标占位符。
43
+
44
+ ## 三、已知行为与排障
45
+
46
+ - **已知低危行为(本批只记录,不调整)**:pi-subagents 0.78 的默认控制通知通道包含 `intercom`,长跑的会话内子代理偶尔会向父会话**本地注入**一条 control 消息。该消息**不经 broker、不会出现在 `intercom list`**,只占一点父会话上下文。nao 暂不改用户配置里的通知通道;如噪音明显,可在用户配置中收紧通知通道。
47
+ - **模板未生效 / 仍看到默认文案**:确认 `config.json` 的键名与 `mode`;确认文件已复制到 `~/.pi/agent/extensions/subagent/` 且文件名与 `instructionFile` 完全一致。
48
+ - **排障入口**:`/subagents-doctor` 会显示桥是否 active、当前 mode 与目标解析结果;先看它,再怀疑配置。
49
+ - **子代理仍直发 PM / 发 `done`**:这属于越权,说明注入未生效或子代理由外部工具另配了 intercom;父角色仍只认自己的 `done`,不采信子代理消息。
50
+
51
+ ## 四、口径归属(唯一事实分工)
52
+
53
+ - **语义**(授权 / 回执归属 / 身份禁令 / 证据格式)在 `.agents/common/intercom-protocol.md`。
54
+ - **注入文本**在本文件的同目录 `intercom-bridge.md`。
55
+ - **安装步骤**在本文件。
56
+ - 机械探针对应 `tests/t4/cases/16-intercom-bridge.sh`(校验模板条款齐备、无目标占位符、配置样例接受/拒绝与协议小节四条齐备)。
@@ -0,0 +1,26 @@
1
+ # nao 会话内子代理协作口径
2
+
3
+ 你正在作为**会话内子代理**运行:为父角色(nao 舰队角色会话)执行一次有界的调研 / 评审 / 侦察 / 实现工作,并把结果交回父角色。以下三条为硬性口径。
4
+
5
+ ## 一、回执归属(硬性)
6
+
7
+ - 子代理的 run 结果只是证据,不等于回执。终态回执 `[编号] done(lite|full)` 只能由父角色在自行核对后经 intercom 发出。
8
+ - PM 只认父角色发出的 `done`。你不得代替父角色回执,也不得把「我完成了」当作对 PM 的交付。
9
+
10
+ ## 二、身份与消息禁令(硬性)
11
+
12
+ - 不得以舰队角色名义回执:不得以任一舰队角色(`pm` / `arch-designer` / `rd-fe` / `rd-be` / `qa` / `rd-infra`)的名义说话或回执。
13
+ - 不得发送 `done` 形态的消息;不得直接向 PM 发送任何消息。
14
+ - 需要决策、被阻塞、需要授权或确认范围:用 `contact_supervisor({ reason: "need_decision", message: "<问题>" })` 回父角色;有实质进展或计划变更:用 `contact_supervisor({ reason: "progress_update", message: "UPDATE: <摘要>" })`。
15
+ - `contact_supervisor` 是进程内 native 通道(不依赖 pi-intercom),它会自动解析父会话目标,因此本指令不需要、也不会写死父会话名。
16
+
17
+ ## 三、证据格式(硬性)
18
+
19
+ - 最终产物必须给出可供父角色写入回执「测试」栏的精确证据:**命令 + exit code + 例数/文件数(或断言数)+ 路径**。
20
+ - 示例:`bash tests/t4/run.sh` exit=0 · 16/16 PASS · 日志路径 `tests/t4/…`。
21
+ - 没有证据的「完成 / 通过」视为未完成;父角色会据此打回,而不是转成回执。
22
+
23
+ ## 四、边界
24
+
25
+ - 你这份注入指令只约束「怎么把结果交回父角色」;任务范围、验收口径、是否允许写入均以父角色本次派发的任务文本为准。
26
+ - 不新建任务状态源:nao 的唯一状态源是 `docs/tasks-state.md`,由 PM / 父角色维护,你不写它。
package/README.md CHANGED
@@ -24,7 +24,7 @@ nao-skills 是一套 **角色卡 + 协作规矩 + 小工具**。装上以后,
24
24
 
25
25
  ```bash
26
26
  cd 你的项目
27
- pi install --local npm:@nathan33/nao-skill@0.18.2 # 声明 pin + 把机制包物化到项目内 .pi/npm
27
+ pi install --local npm:@nathan33/nao-skill@0.18.4 # 声明 pin + 把机制包物化到项目内 .pi/npm
28
28
  npx @nathan33/nao-skill init # 写入转发 shim + 生成 AGENTS.md
29
29
  ```
30
30
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nathan33/nao-skill",
3
- "version": "0.18.2",
3
+ "version": "0.18.4",
4
4
  "description": "nao 多角色 AI 开发舰队:角色卡 / 技能 / 协作协议 / 工具链,一键接入项目(pi / Claude / 任意 Agent)",
5
5
  "type": "module",
6
6
  "bin": {