@nathan33/nao-skill 0.17.3 → 0.18.3

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 行状态行双重信号。
@@ -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
 
@@ -1673,7 +1718,7 @@ cmd_close() {
1673
1718
  fi
1674
1719
  warn "无法确认 $name 在线状态:宿主无 tmux/screen 句柄,且 pi-intercom 名册不可用"
1675
1720
  warn " pi 启动后 argv 被改写为 \"pi\",进程扫描不可信 ⇒ 不做静默 no-op(本命令 exit 非 0)"
1676
- warn " 请用 intercom list 人工核对;确认已退出后忽略本提示,或用 --force 跳过闸门"
1721
+ warn " 请用 intercom list 人工核对;确认已退出后忽略本提示"
1677
1722
  return 1
1678
1723
  fi
1679
1724
 
@@ -53,6 +53,7 @@ PM 在**立项 / 终签 / 发布**时读取;RD 在**起分支 / 开 PR / 合
53
53
 
54
54
  - 版本号:SemVer,PM 按批次范围定(feat→MINOR / fix→PATCH / 破坏性→MAJOR);项目 `AGENTS.md` 有约定则从之。
55
55
  - 门禁:AC 全过 + 验收闭环 + main 门禁全绿 + 工作区干净 + PR 已合并。
56
+ - **发布前置(严格闸门)**:若仓库提供 `test:t4:full` 脚本,发布前须先 `npm run test:t4:full` **rc=0**(判据 = **SKIP 计数与 DEGRADE 计数均为 0**,`SKIP`/`DEGRADE` 即 FAIL)。
56
57
  - **优先 `gh`**:`gh release create <tag> --title "vX.Y.Z" --notes-file docs/releases/<version>.md`(**标题口径(强约束):@.agents/common/release-notes.md** —— 标题仅裸版本号,描述进正文首段)。
57
58
  - 探测:`gh auth status` 按 **exit code** 判定(`keyring` 警告但 exit 0 **仍可用**,可 `gh api user` 复核);**不自动 `gh auth login`**(涉用户凭证)。
58
59
  - release notes 落盘 `docs/releases/<version>.md`(**写作规范(强约束):@.agents/common/release-notes.md** —— 面向用户「本次更新」置顶、内部编号 / 文件名 / 属性名一律下沉「技术细节」、正文不用表格;骨架见 @.agents/templates/release-notes.md.example)。
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.17.3 # 声明 pin + 把机制包物化到项目内 .pi/npm
27
+ pi install --local npm:@nathan33/nao-skill@0.18.3 # 声明 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.17.3",
3
+ "version": "0.18.3",
4
4
  "description": "nao 多角色 AI 开发舰队:角色卡 / 技能 / 协作协议 / 工具链,一键接入项目(pi / Claude / 任意 Agent)",
5
5
  "type": "module",
6
6
  "bin": {
@@ -39,9 +39,12 @@
39
39
  "node": ">=18"
40
40
  },
41
41
  "scripts": {
42
- "test": "bash .agents/scripts/nao-fleet.sh check && npm run check:agents",
42
+ "test": "npm run test:agents && npm run test:t4",
43
+ "test:agents": "bash .agents/scripts/nao-fleet.sh check && npm run check:agents",
44
+ "test:t4": "bash tests/t4/run.sh",
45
+ "test:t4:full": "T4_ALLOW_MISSING=0 T4_REQUIRE_FULL=1 bash tests/t4/run.sh",
43
46
  "check:agents": "tsc -p tsconfig.agents.json",
44
- "prepack": "npm test"
47
+ "prepack": "npm run test:agents"
45
48
  },
46
49
  "devDependencies": {
47
50
  "@types/node": "^20.19.0",