@nathan33/nao-skill 0.7.0 → 0.7.1
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,7 +26,7 @@ description: pi-intercom 多会话协作协议(常驻引用)
|
|
|
26
26
|
|
|
27
27
|
- **任务派生**:`ensure --task <编号> <别名>[@<repo>]` 创建 `<角色>-<编号>` 独立会话(如 `rd-be-T1`),并行隔离、互不排队/打断,避免多任务同名冲突;任务闭环即**回收**:`nao-fleet.sh close --task <编号> <别名>`(内置在跑 turn / tasks-state 闸门)。
|
|
28
28
|
|
|
29
|
-
-
|
|
29
|
+
- **判重 / 在线判定**:脚本按 pi 自设的**终端标题**(`π - <会话名> - <仓库名>`)+ `pi-intercom` 名册判在线(**不用 `pgrep --name`**:pi 启动后 argv 被改写为 `pi`,会恒失配);已运行则跳过并 warn。确需重开加 `--force`。
|
|
30
30
|
- **tmux 宿主**:`$TMUX` 存在时在当前窗口分屏拉起(默认布局 `main-row2`:
|
|
31
31
|
首 pane 全高占左,后续每角色往右开列、每列上下 2 个);不在 tmux 内则创建
|
|
32
32
|
detached 会话,需 `tmux attach -t nao-<别名>`。
|
|
@@ -69,7 +69,7 @@ bash .agents/scripts/nao-fleet.sh check
|
|
|
69
69
|
- **离线检测与恢复**:`list` 发现已派发目标从在线变离线(会话死亡/关闭)→ 任务标记「挂起(离线)」入 tasks-state → `ensure --force` 重拉该角色 → `send` 询问进度或按挂起快照重派。
|
|
70
70
|
- **队列唤醒(不单独轮询)**:每次收到回执/汇报/用户输入时,顺带 `list` 检查待派发队列目标是否 `idle`,是则派发——避免排队任务因无人唤醒而悬置。
|
|
71
71
|
- **降级回流**:降级交付(intercom 不可用)的结果经用户转交后,PM 补登记 tasks-state 与归档(`docs/`),标注「降级回流」,恢复状态机一致性。
|
|
72
|
-
- **单 PM 纪律**:同一项目同一时刻**仅一个 PM 调度会话**(fleet
|
|
72
|
+
- **单 PM 纪律**:同一项目同一时刻**仅一个 PM 调度会话**(fleet 按会话名 `pm` 判重;手动会话请用别名 `pm`),避免双头调度互相覆盖 tasks-state。
|
|
73
73
|
|
|
74
74
|
## 终态回执(硬性闸门,所有角色适用)
|
|
75
75
|
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
# 用法
|
|
6
6
|
# nao-fleet.sh check [--strict] [-v] 静态体检:roles.yaml/缩进/EOL/角色卡/交叉引用/PR 模板/白名单/布局
|
|
7
7
|
# 默认单行摘要(含 warn 计数);-v 展开完整报告;失败始终展开
|
|
8
|
-
# nao-fleet.sh status
|
|
8
|
+
# nao-fleet.sh status 角色会话在线状态(按终端标题/名册判定;权威名单见 intercom list)
|
|
9
9
|
# nao-fleet.sh ensure <别名>[@<repo>] [更多...] 拉起角色窗口(默认工作区=roles.yaml workspace)
|
|
10
10
|
# nao-fleet.sh ensure -m <model> <别名>... 显式指定模型(须命中白名单)
|
|
11
11
|
# nao-fleet.sh ensure --task <编号> <别名>[@<repo>] 任务派生会话:--name <别名>-<编号>(并行隔离,避免同名冲突)
|
|
@@ -25,6 +25,13 @@
|
|
|
25
25
|
# NAO_CLOSE_BUSY_PATTERN=<ERE> close 的在跑 turn 判定正则(默认内置 pi 状态行标记)
|
|
26
26
|
# NAO_TASKS_STATE=<path> 任务状态文件(默认 docs/tasks-state.md,供残留检测/回收闸门)
|
|
27
27
|
#
|
|
28
|
+
# 在线判定(status / close / ensure 判重共用)
|
|
29
|
+
# pi 启动后用 OSC 0 把终端标题设为 "π - <会话名> - <cwd basename>" 并改写 argv
|
|
30
|
+
# (/proc/<pid>/cmdline 只剩 "pi")⇒ 判定一律面向「终端标题 / pi-intercom 名册 /
|
|
31
|
+
# tmux·screen 会话名」,不使用 `pgrep --name`(历史假阴性根因)。
|
|
32
|
+
# 名册经 $PI_AGENT_DIR/npm/node_modules/pi-intercom/cli.ts(一次调用、进程内缓存),
|
|
33
|
+
# 不可用时回退 tmux/screen;仍无法确认「不在线」时 close 不做静默 no-op(exit 非 0)。
|
|
34
|
+
#
|
|
28
35
|
# tmux 宿主行为
|
|
29
36
|
# - 已在 tmux 内($TMUX 存在):当前窗口分屏拉起,不新建窗口。
|
|
30
37
|
# - 不在 tmux 内:创建 detached 会话 nao-<角色>,需 tmux attach -t nao-<角色>。
|
|
@@ -223,25 +230,128 @@ detect_host() {
|
|
|
223
230
|
echo screen
|
|
224
231
|
}
|
|
225
232
|
|
|
226
|
-
#
|
|
233
|
+
# ---------------------------------------------------------------------------
|
|
234
|
+
# 会话在线判定
|
|
235
|
+
#
|
|
236
|
+
# 历史假阴性根因:pi 启动后用 OSC 0 把终端标题设为
|
|
237
|
+
# "π - <会话名> - <cwd basename>"(pi 源码 updateTerminalTitle),
|
|
238
|
+
# 并同步改写 argv —— /proc/<pid>/cmdline 只剩 "pi",`pgrep -f --name` 恒失配,
|
|
239
|
+
# 于是 status/close 长期判「不在线」(close 退化为静默空转、exit 0)。
|
|
240
|
+
# 现改为按「终端标题契约」取句柄,argv 扫描一律不用:
|
|
241
|
+
# ① pi-intercom 名册(权威在线名单;跨 tmux/ghostty/ptyxis/screen 宿主)
|
|
242
|
+
# ② tmux pane_title(pi 自设;与 pane_start_command 是否为空无关)
|
|
243
|
+
# ③ tmux detached 会话 / screen 会话名 nao-<会话名>
|
|
244
|
+
# repo(可选):给出时要求标题 basename / 名册 cwd 一致,避免跨项目同名会话互串。
|
|
245
|
+
|
|
246
|
+
PI_AGENT_DIR="${PI_CODING_AGENT_DIR:-$HOME/.pi/agent}"
|
|
247
|
+
PI_TITLE="π"
|
|
248
|
+
_INTERCOM_JSON=""; _INTERCOM_PROBED=0
|
|
249
|
+
|
|
250
|
+
repo_abspath() { ( cd "$1" >/dev/null 2>&1 && pwd ) 2>/dev/null || true; }
|
|
251
|
+
repo_basename() {
|
|
252
|
+
local abs; abs="$(repo_abspath "$1")"
|
|
253
|
+
[[ -n "$abs" ]] && printf '%s\n' "${abs##*/}"
|
|
254
|
+
return 0
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
# intercom 名册 JSON(一次调用、进程内缓存);不可用返回 1(不 die)
|
|
258
|
+
intercom_list_json() {
|
|
259
|
+
if (( _INTERCOM_PROBED )); then
|
|
260
|
+
[[ -n "$_INTERCOM_JSON" ]] && printf '%s' "$_INTERCOM_JSON"
|
|
261
|
+
return
|
|
262
|
+
fi
|
|
263
|
+
_INTERCOM_PROBED=1
|
|
264
|
+
local dir="$PI_AGENT_DIR/npm/node_modules/pi-intercom"
|
|
265
|
+
local tsx="$PI_AGENT_DIR/npm/node_modules/tsx/dist/cli.mjs"
|
|
266
|
+
[[ -f "$dir/cli.ts" && -f "$tsx" ]] || return 1
|
|
267
|
+
command -v node >/dev/null 2>&1 || return 1
|
|
268
|
+
local out
|
|
269
|
+
if command -v timeout >/dev/null 2>&1; then
|
|
270
|
+
out="$(timeout 15 node "$tsx" "$dir/cli.ts" list --json 2>/dev/null)" || return 1
|
|
271
|
+
else
|
|
272
|
+
out="$(node "$tsx" "$dir/cli.ts" list --json 2>/dev/null)" || return 1
|
|
273
|
+
fi
|
|
274
|
+
[[ -n "$out" ]] || return 1
|
|
275
|
+
_INTERCOM_JSON="$out"
|
|
276
|
+
printf '%s' "$out"
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
# 名册 → "会话名<TAB>cwd"(每行一条)
|
|
280
|
+
intercom_roster() {
|
|
281
|
+
local json; json="$(intercom_list_json)" || return 1
|
|
282
|
+
awk '
|
|
283
|
+
/"name":[[:space:]]*"/ { s=$0; sub(/.*"name":[[:space:]]*"/,"",s); sub(/".*/,"",s); n=s }
|
|
284
|
+
/"cwd":[[:space:]]*"/ { s=$0; sub(/.*"cwd":[[:space:]]*"/,"",s); sub(/".*/,"",s); print n "\t" s }
|
|
285
|
+
' <<< "$json"
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
# 名册中是否在线;repo 给出时须 cwd 精确匹配
|
|
289
|
+
intercom_online() {
|
|
290
|
+
local name="$1" repo="${2:-}" abs="" n c
|
|
291
|
+
intercom_list_json >/dev/null 2>&1 || return 1
|
|
292
|
+
if [[ -n "$repo" ]]; then abs="$(repo_abspath "$repo")"; fi
|
|
293
|
+
while IFS=$'\t' read -r n c; do
|
|
294
|
+
[[ "$n" == "$name" ]] || continue
|
|
295
|
+
if [[ -z "$abs" || "$c" == "$abs" ]]; then return 0; fi
|
|
296
|
+
done < <(intercom_roster)
|
|
297
|
+
return 1
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
# 会话所在的 tmux pane(pi 自设标题 "π - <会话名> - <repo basename>")
|
|
301
|
+
find_pane_for() {
|
|
302
|
+
local name="$1" repo="${2:-}" base=""
|
|
303
|
+
command -v tmux >/dev/null 2>&1 || return 0
|
|
304
|
+
if [[ -n "$repo" ]]; then base="$(repo_basename "$repo")"; fi
|
|
305
|
+
tmux list-panes -a -F "#{pane_id}"$'\t'"#{pane_title}" 2>/dev/null \
|
|
306
|
+
| awk -F'\t' -v p="$PI_TITLE" -v n="$name" -v b="$base" '
|
|
307
|
+
{ t=$2
|
|
308
|
+
if (b != "") { if (t == p " - " n " - " b) { print $1; exit } }
|
|
309
|
+
else if (index(t, p " - " n " - ") == 1) { print $1; exit }
|
|
310
|
+
}'
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
# 该会话名对应的 pi 进程 PID(经 pane 句柄取 pane_pid 及其子进程中 comm=pi 者)
|
|
227
314
|
pi_pids_for() {
|
|
228
|
-
local pid
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
315
|
+
local pane root pid
|
|
316
|
+
pane="$(find_pane_for "$1" "${2:-}")"
|
|
317
|
+
[[ -n "$pane" ]] || return 0
|
|
318
|
+
root="$(tmux display-message -p -t "$pane" '#{pane_pid}' 2>/dev/null)"
|
|
319
|
+
[[ -n "$root" ]] || return 0
|
|
320
|
+
{ printf '%s\n' "$root"; pgrep -P "$root" 2>/dev/null || true; } | while IFS= read -r pid; do
|
|
321
|
+
[[ "$(ps -o comm= -p "$pid" 2>/dev/null | tr -d ' ')" == "pi" ]] || continue
|
|
234
322
|
printf '%s\n' "$pid"
|
|
235
323
|
done
|
|
236
324
|
}
|
|
237
325
|
|
|
238
|
-
running() {
|
|
326
|
+
running() {
|
|
327
|
+
local name="$1" repo="${2:-}"
|
|
328
|
+
if intercom_online "$name" "$repo"; then return 0; fi
|
|
329
|
+
if [[ -n "$(find_pane_for "$name" "$repo")" ]]; then return 0; fi
|
|
330
|
+
if command -v tmux >/dev/null 2>&1 && tmux has-session -t "nao-$name" 2>/dev/null; then return 0; fi
|
|
331
|
+
if command -v screen >/dev/null 2>&1 && screen -ls 2>/dev/null | grep -q "[0-9]\.nao-$name\b"; then return 0; fi
|
|
332
|
+
return 1
|
|
333
|
+
}
|
|
239
334
|
|
|
240
|
-
#
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
335
|
+
# 能否可信地断言「不在线」:名册可用(完整),或本机就是 tmux/screen 宿主(句柄可枚举)
|
|
336
|
+
offline_verifiable() {
|
|
337
|
+
intercom_list_json >/dev/null 2>&1 && return 0
|
|
338
|
+
[[ -n "${TMUX:-}" ]] && return 0
|
|
339
|
+
case "${NAO_TERMINAL:-}" in tmux|screen) return 0 ;; esac
|
|
340
|
+
return 1
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
# 本机可见的会话名(tmux 标题 + tmux/screen 会话 + 名册),供 status 枚举;已去重
|
|
344
|
+
visible_session_names() {
|
|
345
|
+
{ if command -v tmux >/dev/null 2>&1; then
|
|
346
|
+
tmux list-panes -a -F '#{pane_title}' 2>/dev/null \
|
|
347
|
+
| sed -n "s/^${PI_TITLE} - \([A-Za-z0-9_-]*\) - .*/\1/p"
|
|
348
|
+
tmux list-sessions -F '#{session_name}' 2>/dev/null | sed -n 's/^nao-//p'
|
|
349
|
+
fi
|
|
350
|
+
if command -v screen >/dev/null 2>&1; then
|
|
351
|
+
screen -ls 2>/dev/null | sed -n 's/.*[0-9]\.nao-\([A-Za-z0-9_-]*\).*/\1/p'
|
|
352
|
+
fi
|
|
353
|
+
intercom_roster 2>/dev/null | awk -F'\t' 'NF {print $1}'
|
|
354
|
+
} | awk '!seen[$0]++'
|
|
245
355
|
}
|
|
246
356
|
|
|
247
357
|
# 该 pane 末 3 行是否显示在跑 turn(启发式:状态行标记;回执/产物以 PM 核对清单为准)
|
|
@@ -252,23 +362,34 @@ pane_busy() {
|
|
|
252
362
|
grep -qE "$CLOSE_BUSY_PATTERN" <<< "$tail3"
|
|
253
363
|
}
|
|
254
364
|
|
|
255
|
-
# 任务在 tasks-state 的归处:
|
|
365
|
+
# 任务在 tasks-state 的归处:
|
|
366
|
+
# active(在表内进行态)/ closed(在归档区表内)/ mentioned(仅散文提及)
|
|
367
|
+
# / absent(全文无记录)/ nofile
|
|
368
|
+
# 归一处:表内单元格可选带反引号/**(常见写法 `T157` / **T157**)→ 去首尾空白与标记后比较。
|
|
369
|
+
# 标题级别不限(`##` 与 `###` 子表都算);归档区 = 标题含「已验收|已归档|归档」。
|
|
370
|
+
# 边界:先按非 [A-Za-z0-9_-] 切词再比较,避免 T15 命中 T155。
|
|
256
371
|
task_state_class() {
|
|
257
372
|
local id="$1"
|
|
258
373
|
[[ -f "$TASKS_STATE" ]] || { echo nofile; return; }
|
|
259
374
|
awk -v id="$id" '
|
|
260
|
-
|
|
375
|
+
function norm(s) { gsub(/^[[:space:]]+|[[:space:]]+$/, "", s); gsub(/`/, "", s); gsub(/\*\*/, "", s); return s }
|
|
376
|
+
/^#+[[:space:]]/ { sec=$0; sub(/^#+[[:space:]]*/, "", sec); next }
|
|
261
377
|
{
|
|
262
378
|
n=split($0, cells, "|")
|
|
263
379
|
for (i=1;i<=n;i++) {
|
|
264
|
-
|
|
265
|
-
if (
|
|
380
|
+
if (norm(cells[i]) != id) continue
|
|
381
|
+
if (sec ~ /已验收|已归档|归档/) arch=1; else cell=1
|
|
382
|
+
break
|
|
266
383
|
}
|
|
384
|
+
line=$0; gsub(/[^A-Za-z0-9_-]/, " ", line)
|
|
385
|
+
m=split(line, toks, " ")
|
|
386
|
+
for (j=1;j<=m;j++) if (toks[j]==id) { seen=1; break }
|
|
267
387
|
}
|
|
268
388
|
END {
|
|
269
|
-
if (
|
|
270
|
-
if (
|
|
271
|
-
print "
|
|
389
|
+
if (arch) { print "closed"; exit }
|
|
390
|
+
if (cell) { print "active"; exit }
|
|
391
|
+
if (seen) { print "mentioned"; exit }
|
|
392
|
+
print "absent"
|
|
272
393
|
}
|
|
273
394
|
' "$TASKS_STATE"
|
|
274
395
|
}
|
|
@@ -663,33 +784,33 @@ cmd_check() {
|
|
|
663
784
|
# ---------------------------------------------------------------------------
|
|
664
785
|
cmd_status() {
|
|
665
786
|
local a
|
|
666
|
-
|
|
787
|
+
intercom_list_json >/dev/null 2>&1 || true # 预热名册缓存(后续子 shell 复用)
|
|
788
|
+
echo "== 角色会话在线状态(权威名单见 intercom({action:'list'});本表按终端标题/名册判定)=="
|
|
667
789
|
for a in "${ROLE_ORDER[@]}"; do
|
|
668
|
-
if running "$a"; then
|
|
790
|
+
if running "$a" "${ROLE_WS[$a]:-}"; then
|
|
669
791
|
printf ' ✓ %-14s 在线(--name %s;卡片 %s)\n' "$a" "$a" "${ROLE_CARDS[$a]}"
|
|
670
792
|
else
|
|
671
793
|
printf ' · %-14s 未运行(ensure 拉起)\n' "$a"
|
|
672
794
|
fi
|
|
673
795
|
done
|
|
674
796
|
echo "== 任务派生会话(--task 拉起,如 rd-be-T1)=="
|
|
675
|
-
local found=0
|
|
676
|
-
while IFS= read -r
|
|
677
|
-
[[ -n "$line" ]] || continue
|
|
678
|
-
dname="$(awk '{for(i=1;i<=NF;i++) if($i=="--name") {print $(i+1); exit}}' <<< "$line")"
|
|
797
|
+
local found=0 dname role id cls tag
|
|
798
|
+
while IFS= read -r dname; do
|
|
679
799
|
[[ -n "$dname" ]] || continue
|
|
680
|
-
id=""
|
|
800
|
+
id=""
|
|
681
801
|
for role in "${ROLE_ORDER[@]}"; do [[ "$dname" == "$role-"* ]] && { id="${dname#"$role"-}"; break; }; done
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
802
|
+
[[ -n "$id" ]] || continue
|
|
803
|
+
cls="$(task_state_class "$id")"
|
|
804
|
+
tag=""
|
|
805
|
+
case "$cls" in
|
|
806
|
+
closed) tag=" ! 残留($id 已归档)→ close --task $id ${dname%-*}" ;;
|
|
807
|
+
active) tag=" · $id 进行态" ;;
|
|
808
|
+
mentioned) tag=" ? $id 仅散文提及(非表内记录)→ 核对后 close --task $id ${dname%-*}" ;;
|
|
809
|
+
absent) tag=" ! 残留(tasks-state 无 $id 记录)→ 核对后 close --task $id ${dname%-*}" ;;
|
|
810
|
+
esac
|
|
690
811
|
printf ' · %s%s\n' "$dname" "$tag"
|
|
691
812
|
found=1
|
|
692
|
-
done < <(
|
|
813
|
+
done < <(visible_session_names)
|
|
693
814
|
(( found )) || echo ' (无)'
|
|
694
815
|
[[ -f "$TASKS_STATE" ]] || echo " ($TASKS_STATE 不存在,残留判定已跳过)"
|
|
695
816
|
}
|
|
@@ -700,6 +821,7 @@ cmd_close() {
|
|
|
700
821
|
local force="$1" task="$2" target="$3"
|
|
701
822
|
[[ -n "$target" ]] || die "close 需要目标:角色别名或派生会话名(如 rd-be / rd-be-T1)"
|
|
702
823
|
local name repo cls bus pane pids r ok
|
|
824
|
+
intercom_list_json >/dev/null 2>&1 || true # 预热名册缓存
|
|
703
825
|
if [[ -n "$task" ]]; then
|
|
704
826
|
resolve_role "$target"
|
|
705
827
|
name="${NAME}-${task}"
|
|
@@ -711,7 +833,8 @@ cmd_close() {
|
|
|
711
833
|
warn " 先把 $task 移入「已验收/已归档」再回收(或 --force:将丢失打回返工所需上下文)"
|
|
712
834
|
return 1
|
|
713
835
|
fi
|
|
714
|
-
[[ "$cls" == "absent" ]] && warn "tasks-state
|
|
836
|
+
[[ "$cls" == "absent" ]] && warn "tasks-state 全文无 $task 记录(仅按会话名回收)"
|
|
837
|
+
[[ "$cls" == "mentioned" ]] && log "tasks-state 仅散文提及 $task(非表内记录,不阻塞回收)"
|
|
715
838
|
[[ "$cls" == "nofile" ]] && log "未启用 $TASKS_STATE(跳过状态闸门)"
|
|
716
839
|
elif [[ -n "${ALIAS_ROLE[$target]:-}" ]]; then
|
|
717
840
|
name="${ALIAS_ROLE[$target]}"; repo="${ROLE_WS[$name]:-$PWD}"
|
|
@@ -726,10 +849,18 @@ cmd_close() {
|
|
|
726
849
|
repo="$PWD"
|
|
727
850
|
fi
|
|
728
851
|
|
|
729
|
-
running "$name"
|
|
852
|
+
if ! running "$name" "$repo"; then
|
|
853
|
+
if offline_verifiable; then
|
|
854
|
+
log "$name 未运行(无需回收)"; return 0
|
|
855
|
+
fi
|
|
856
|
+
warn "无法确认 $name 在线状态:宿主无 tmux/screen 句柄,且 pi-intercom 名册不可用"
|
|
857
|
+
warn " pi 启动后 argv 被改写为 \"pi\",进程扫描不可信 ⇒ 不做静默 no-op(本命令 exit 非 0)"
|
|
858
|
+
warn " 请用 intercom list 人工核对;确认已退出后忽略本提示,或用 --force 跳过闸门"
|
|
859
|
+
return 1
|
|
860
|
+
fi
|
|
730
861
|
|
|
731
862
|
# 闸门②:在跑 turn(tmux 可判;非 tmux 宿主无法判 → 需 --force)
|
|
732
|
-
pane="$(find_pane_for "$name")"
|
|
863
|
+
pane="$(find_pane_for "$name" "$repo")"
|
|
733
864
|
if [[ -n "$pane" ]]; then
|
|
734
865
|
pane_busy "$pane" && bus="tmux pane $pane 末行显示在跑 turn"
|
|
735
866
|
else
|
|
@@ -750,7 +881,7 @@ cmd_close() {
|
|
|
750
881
|
if command -v screen >/dev/null 2>&1 && screen -ls 2>/dev/null | grep -q "nao-$name" && screen -S "nao-$name" -X quit 2>/dev/null; then
|
|
751
882
|
log "已回收 $name(screen 会话 nao-$name)@ $repo"; return 0
|
|
752
883
|
fi
|
|
753
|
-
pids="$(pi_pids_for "$name" | tr '\n' ' ')"
|
|
884
|
+
pids="$(pi_pids_for "$name" "$repo" | tr '\n' ' ')"
|
|
754
885
|
if [[ -n "$pids" ]] && kill $pids 2>/dev/null; then
|
|
755
886
|
log "已回收 $name(结束进程: $pids)@ $repo"; return 0
|
|
756
887
|
fi
|
|
@@ -778,8 +909,8 @@ cmd_ensure() {
|
|
|
778
909
|
key="$repo"; seen=0
|
|
779
910
|
for k in "${cg_done[@]:-}"; do [[ "$k" == "$key" ]] && seen=1; done
|
|
780
911
|
if (( ! seen )); then check_codegraph "$repo"; cg_done+=("$key"); fi
|
|
781
|
-
if [[ "$force" != "true" ]] && running "$disp"; then
|
|
782
|
-
warn "$disp
|
|
912
|
+
if [[ "$force" != "true" ]] && running "$disp" "$repo"; then
|
|
913
|
+
warn "$disp 已在运行(终端标题/名册命中),跳过;确需重开请加 --force"
|
|
783
914
|
continue
|
|
784
915
|
fi
|
|
785
916
|
spawn_one "$disp" "$repo" "$model"
|