tmuxclihook 0.5.10 → 0.5.12
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.
- package/package.json +1 -1
- package/scripts/adapters/claude.sh +19 -0
- package/scripts/install-claude-hooks.sh +14 -1
- package/scripts/install-codex-hooks.sh +14 -1
- package/scripts/install-opencode-hooks.sh +14 -1
- package/scripts/lib-tmux-ai-status.sh +45 -5
- package/scripts/tmux-ai-reload +29 -0
- package/scripts/tmux-ai-status +39 -4
- package/tmux-ai-hooks-status.tmux +35 -13
package/package.json
CHANGED
|
@@ -175,6 +175,24 @@ _ccswitch_warn_gui() {
|
|
|
175
175
|
fi
|
|
176
176
|
}
|
|
177
177
|
|
|
178
|
+
# live 模式的真源是 $HOME/.claude/settings.json(cc-switch 硬编码,不随
|
|
179
|
+
# CLAUDE_CONFIG_DIR 变)。若当前环境的 CLAUDE_CONFIG_DIR 指向非 cc-switch
|
|
180
|
+
# 实例目录(用户自定义目录裸跑 claude),该进程读的是自定义目录,本插件写入
|
|
181
|
+
# 的真源可能不生效——纯诊断告警,不改写入行为(不做双写,理由同 2026-10-13
|
|
182
|
+
# 决策:外部覆盖拉锯风险大于收益)。
|
|
183
|
+
_ccswitch_warn_config_dir_mismatch() {
|
|
184
|
+
[ -n "${CLAUDE_CONFIG_DIR:-}" ] || return 0
|
|
185
|
+
local inst="${HOME}/.cc-switch/instances/claude" resolved
|
|
186
|
+
resolved=$(cd "$CLAUDE_CONFIG_DIR" 2>/dev/null && pwd -P) || resolved="$CLAUDE_CONFIG_DIR"
|
|
187
|
+
case "$resolved" in
|
|
188
|
+
"$inst"|"$inst"/*) ;;
|
|
189
|
+
*)
|
|
190
|
+
echo "WARN: CLAUDE_CONFIG_DIR=$CLAUDE_CONFIG_DIR 不是 cc-switch 实例目录;" \
|
|
191
|
+
"live 模式写入 $(_ccswitch_live_file) 可能不被该进程读取" >&2
|
|
192
|
+
;;
|
|
193
|
+
esac
|
|
194
|
+
}
|
|
195
|
+
|
|
178
196
|
# 读通用配置 value → mktemp 文件,路径写入 $1 命名的变量。
|
|
179
197
|
# 行缺失/空白 → "{}";非空但非法 JSON → 明确报错 return 1(绝不覆盖用户数据)。
|
|
180
198
|
# 注意:内部临时变量名不得与调用方常用名(tmp 等)同名——bash 动态作用域下
|
|
@@ -407,6 +425,7 @@ adapter_install_hooks() {
|
|
|
407
425
|
;;
|
|
408
426
|
live)
|
|
409
427
|
_ccswitch_warn_gui
|
|
428
|
+
_ccswitch_warn_config_dir_mismatch
|
|
410
429
|
target=$(_ccswitch_live_file)
|
|
411
430
|
;;
|
|
412
431
|
*)
|
|
@@ -18,6 +18,15 @@ _LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"
|
|
|
18
18
|
source "${_LIB_DIR}/lib-install-hooks.sh"
|
|
19
19
|
source "${_LIB_DIR}/adapters/claude.sh"
|
|
20
20
|
|
|
21
|
+
# 子命令白名单:必须在取锁前校验——拼错的子命令不应先白等最多 10s 锁超时
|
|
22
|
+
case "${1:-install}" in
|
|
23
|
+
install|uninstall|check|auto) ;;
|
|
24
|
+
*)
|
|
25
|
+
echo "Usage: $0 [install|uninstall|check [--json]|auto]" >&2
|
|
26
|
+
exit 2
|
|
27
|
+
;;
|
|
28
|
+
esac
|
|
29
|
+
|
|
21
30
|
# 写路径互斥(check 只读不锁):临界区覆盖 adapter 调用 + suppress 标记读-改-写,
|
|
22
31
|
# 串行化 auto 与显式 uninstall 的标记 TOCTOU。失败语义见 _install_acquire_lock:
|
|
23
32
|
# 降级无锁(rc=2)继续;等待超时(rc=1)退出。
|
|
@@ -63,8 +72,12 @@ case "${1:-install}" in
|
|
|
63
72
|
adapter_install_hooks
|
|
64
73
|
_hooks_suppress_clear claude || true
|
|
65
74
|
;;
|
|
66
|
-
|
|
75
|
+
install)
|
|
67
76
|
adapter_install_hooks
|
|
68
77
|
_hooks_suppress_clear claude || true
|
|
69
78
|
;;
|
|
79
|
+
*)
|
|
80
|
+
echo "Usage: $0 [install|uninstall|check [--json]|auto]" >&2
|
|
81
|
+
exit 2
|
|
82
|
+
;;
|
|
70
83
|
esac
|
|
@@ -15,6 +15,15 @@ _LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"
|
|
|
15
15
|
source "${_LIB_DIR}/lib-install-hooks.sh"
|
|
16
16
|
source "${_LIB_DIR}/adapters/codex.sh"
|
|
17
17
|
|
|
18
|
+
# 子命令白名单:必须在取锁前校验——拼错的子命令不应先白等最多 10s 锁超时
|
|
19
|
+
case "${1:-install}" in
|
|
20
|
+
install|uninstall|check) ;;
|
|
21
|
+
*)
|
|
22
|
+
echo "Usage: $0 [install|uninstall|check [--json]]" >&2
|
|
23
|
+
exit 2
|
|
24
|
+
;;
|
|
25
|
+
esac
|
|
26
|
+
|
|
18
27
|
# 写路径互斥(check 只读不锁):见 _install_acquire_lock 头注释。
|
|
19
28
|
case "${1:-install}" in
|
|
20
29
|
check) ;;
|
|
@@ -43,8 +52,12 @@ case "${1:-install}" in
|
|
|
43
52
|
exit 1
|
|
44
53
|
fi
|
|
45
54
|
;;
|
|
46
|
-
|
|
55
|
+
install)
|
|
47
56
|
adapter_install_hooks
|
|
48
57
|
_hooks_suppress_clear codex || true
|
|
49
58
|
;;
|
|
59
|
+
*)
|
|
60
|
+
echo "Usage: $0 [install|uninstall|check [--json]]" >&2
|
|
61
|
+
exit 2
|
|
62
|
+
;;
|
|
50
63
|
esac
|
|
@@ -15,6 +15,15 @@ _LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"
|
|
|
15
15
|
source "${_LIB_DIR}/lib-install-hooks.sh"
|
|
16
16
|
source "${_LIB_DIR}/adapters/opencode.sh"
|
|
17
17
|
|
|
18
|
+
# 子命令白名单:必须在取锁前校验——拼错的子命令不应先白等最多 10s 锁超时
|
|
19
|
+
case "${1:-install}" in
|
|
20
|
+
install|uninstall|check) ;;
|
|
21
|
+
*)
|
|
22
|
+
echo "Usage: $0 [install|uninstall|check [--json]]" >&2
|
|
23
|
+
exit 2
|
|
24
|
+
;;
|
|
25
|
+
esac
|
|
26
|
+
|
|
18
27
|
# 写路径互斥(check 只读不锁):见 _install_acquire_lock 头注释。
|
|
19
28
|
case "${1:-install}" in
|
|
20
29
|
check) ;;
|
|
@@ -43,8 +52,12 @@ case "${1:-install}" in
|
|
|
43
52
|
exit 1
|
|
44
53
|
fi
|
|
45
54
|
;;
|
|
46
|
-
|
|
55
|
+
install)
|
|
47
56
|
adapter_install_hooks
|
|
48
57
|
_hooks_suppress_clear opencode || true
|
|
49
58
|
;;
|
|
59
|
+
*)
|
|
60
|
+
echo "Usage: $0 [install|uninstall|check [--json]]" >&2
|
|
61
|
+
exit 2
|
|
62
|
+
;;
|
|
50
63
|
esac
|
|
@@ -94,7 +94,7 @@ _load_pane_snapshot() {
|
|
|
94
94
|
_CURRENT_SERVER_PID=""
|
|
95
95
|
command -v tmux >/dev/null 2>&1 || return 1
|
|
96
96
|
_PANE_SNAPSHOT=$(tmux list-panes -a -F \
|
|
97
|
-
'#{pane_id}|#{pane_current_path}|#{pane_current_command}|#{pane_pid}|#{pid}|#{session_attached}|#{pane_title}' \
|
|
97
|
+
'#{pane_id}|#{s/[|]/_/:pane_current_path}|#{s/[|]/_/:pane_current_command}|#{pane_pid}|#{pid}|#{session_attached}|#{pane_title}' \
|
|
98
98
|
2>/dev/null)
|
|
99
99
|
[ -n "$_PANE_SNAPSHOT" ] || return 1
|
|
100
100
|
_PANE_SNAPSHOT_OK=1
|
|
@@ -184,7 +184,8 @@ _narrow_panes_by_adapter() {
|
|
|
184
184
|
_narrow_panes_by_cwd() {
|
|
185
185
|
local base="$1" want="" matches="" id path cmd pid server attached title
|
|
186
186
|
[ -n "${HOOK_CWD:-}" ] || { printf '%s\n' "$base"; return; }
|
|
187
|
-
|
|
187
|
+
# _normalize_path 对非空输入恒返回 0(cd 失败时返回去尾斜杠原串),无需兜底分支
|
|
188
|
+
want=$(_normalize_path "$HOOK_CWD")
|
|
188
189
|
while IFS='|' read -r id path cmd pid server attached title; do
|
|
189
190
|
[ -n "$id" ] || continue
|
|
190
191
|
[ "$(_normalize_path "$path")" = "$want" ] && matches="$matches $id"
|
|
@@ -410,7 +411,10 @@ build_all_status() {
|
|
|
410
411
|
_ai_names=$(_collect_ai_process_names)
|
|
411
412
|
# 动态追加上 monitor 的 seen 标记(无 SessionEnd 工具的进程树探测结果)。
|
|
412
413
|
# switch/volta-shim 启动的 pane 前台命令不是 codex,必须靠该标记补 idle `-`。
|
|
413
|
-
|
|
414
|
+
# 自由文本字段就地消毒:tmux format 的 s/[|]/_/ 替换(`|` 是 ERE 交替符,
|
|
415
|
+
# 必须写成 [|] 或 \|;裸 s/|/…/ 不生效)。session 名/provider 名含 `|` 时
|
|
416
|
+
# 会劈裂字段——attached 位变状态字符使 $6>0 误判、该 session 整行消失。
|
|
417
|
+
_fmt="#{pane_id}|#{s/[|]/_/:session_name}|#{window_index}|#{pane_index}|#{@ai_pane_status}|#{session_attached}|#{session_created}|#{@ai_entered_time}|#{s/[|]/_/:pane_current_command}|#{s/[|]/_/:@ai_provider}|#{s/[|]/_/:@ai_provider_id}"
|
|
414
418
|
for _n in $_ai_names; do
|
|
415
419
|
_fmt="$_fmt|#{@ai_monitor_seen_$_n}"
|
|
416
420
|
done
|
|
@@ -662,6 +666,23 @@ _clear_pending() {
|
|
|
662
666
|
_release_lock
|
|
663
667
|
}
|
|
664
668
|
|
|
669
|
+
# pending 集合中是否有 N 秒内的条目(每行 "id tool ts")。用于区分「工具确实在
|
|
670
|
+
# 跑」与「泄漏/中断残留的旧条目」——并发 hook 抢锁超时会让 _remove_pending_id
|
|
671
|
+
# 静默放弃,集合可能长期残留陈旧 id(实测存在),不能只按非空判定活跃。
|
|
672
|
+
_pending_recent() { # $1=秒数 → 0=有新鲜条目
|
|
673
|
+
local max="${1:-10}" now f
|
|
674
|
+
now=$(date +%s)
|
|
675
|
+
for f in "$(_pending_pre_path)" "$(_pending_perm_path)"; do
|
|
676
|
+
[ -s "$f" ] || continue
|
|
677
|
+
if awk -v now="$now" -v max="$max" '
|
|
678
|
+
$3 ~ /^[0-9]+$/ && now - $3 <= max { found = 1; exit }
|
|
679
|
+
END { exit !found }' "$f" 2>/dev/null; then
|
|
680
|
+
return 0
|
|
681
|
+
fi
|
|
682
|
+
done
|
|
683
|
+
return 1
|
|
684
|
+
}
|
|
685
|
+
|
|
665
686
|
# 根据当前 pending-perm / pending-pre 派生 STATUS。结果写入全局 DERIVED
|
|
666
687
|
# return 0 = 派生出非空 STATUS, return 1 = 都空(事件需自行决定)
|
|
667
688
|
_derive_status_from_pending() {
|
|
@@ -874,7 +895,7 @@ _is_pane_session_dead() {
|
|
|
874
895
|
# 有对应文件但文件过期且进程已死(Stop 未触发的崩溃场景),也清除。
|
|
875
896
|
_cleanup_stale_panes() {
|
|
876
897
|
local active_panes=""
|
|
877
|
-
local f pane_id
|
|
898
|
+
local f pane_id pane_status pane_dir pid pid_file
|
|
878
899
|
for f in "${_STATUS_DIR}"/*/pane-*; do
|
|
879
900
|
[ -f "$f" ] || continue
|
|
880
901
|
pane_id=$(cat "$f" 2>/dev/null)
|
|
@@ -925,7 +946,6 @@ _cleanup_stale_panes() {
|
|
|
925
946
|
# 清理孤儿 poll 进程
|
|
926
947
|
for pid_file in "${pane_dir}"*-poll-pid; do
|
|
927
948
|
[ -f "$pid_file" ] || continue
|
|
928
|
-
local pid
|
|
929
949
|
pid=$(cat "$pid_file" 2>/dev/null)
|
|
930
950
|
[ -n "$pid" ] && kill "$pid" 2>/dev/null
|
|
931
951
|
done
|
|
@@ -935,6 +955,25 @@ _cleanup_stale_panes() {
|
|
|
935
955
|
done
|
|
936
956
|
}
|
|
937
957
|
|
|
958
|
+
# 清理已断开 client 的 @ai_prev_sess_* 残留。key 是写入端有损 sanitize
|
|
959
|
+
# (tr -c 'A-Za-z0-9._-' '_'),无法从 key 反推 client → 反向枚举存活集比对。
|
|
960
|
+
# 实测关注:长期运行的 server 会累积已 detach 的 client 键(8 option / 4 存活)。
|
|
961
|
+
_prune_prev_sess_options() {
|
|
962
|
+
local alive=" " name key opt
|
|
963
|
+
while IFS= read -r name; do
|
|
964
|
+
[ -n "$name" ] || continue
|
|
965
|
+
key=$(printf '%s' "$name" | tr -c 'A-Za-z0-9._-' '_')
|
|
966
|
+
alive="$alive$key "
|
|
967
|
+
done < <(tmux list-clients -F '#{client_name}' 2>/dev/null)
|
|
968
|
+
for opt in $(tmux show-options -g 2>/dev/null | awk '/^@ai_prev_sess_/{print $1}'); do
|
|
969
|
+
key="${opt#@ai_prev_sess_}"
|
|
970
|
+
case "$alive" in
|
|
971
|
+
*" $key "*) ;;
|
|
972
|
+
*) tmux set-option -gu "$opt" 2>/dev/null || true ;;
|
|
973
|
+
esac
|
|
974
|
+
done
|
|
975
|
+
}
|
|
976
|
+
|
|
938
977
|
# 频率限制的孤儿清理(每次 hook 调用,最多每 60s 执行一次)
|
|
939
978
|
_maybe_cleanup_stale() {
|
|
940
979
|
local marker="${_STATUS_DIR}/.stale-cleanup-ts"
|
|
@@ -947,6 +986,7 @@ _maybe_cleanup_stale() {
|
|
|
947
986
|
[ $((now - ${last:-0})) -lt 60 ] && return
|
|
948
987
|
fi
|
|
949
988
|
_cleanup_stale_panes
|
|
989
|
+
_prune_prev_sess_options
|
|
950
990
|
echo "$now" > "$marker"
|
|
951
991
|
}
|
|
952
992
|
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# tmux-ai-reload — prefix+r 重载:source 实际存在的配置文件后重跑插件入口
|
|
3
|
+
#
|
|
4
|
+
# 背景:入口曾用 mktemp conf 绑定 `source-file ~/.tmux.conf \; run-shell 入口`:
|
|
5
|
+
# 1) 硬编码 ~/.tmux.conf(XDG / tmux -f 用户 reload 读错文件)
|
|
6
|
+
# 2) 一次性哨兵:source-file 内用户自己的 `bind r` 会把绑定重置回原命令,
|
|
7
|
+
# 哨兵已置位 → 纯 TPM 模式(配置无 run-shell 行)从第二次重载起插件初始化
|
|
8
|
+
# 静默停止
|
|
9
|
+
# 本 helper 每次由绑定直接调用(入口每次加载都重绑,自愈):
|
|
10
|
+
# - 按 tmux 的 #{config_files} 候选列表逐个过滤存在性后 source(顺序同启动)
|
|
11
|
+
# - 再显式跑一次入口(纯 TPM 模式也能重新初始化;配置里的 run-shell 行会让
|
|
12
|
+
# 入口再跑一次——幂等,仅一次多余启动)
|
|
13
|
+
set -u
|
|
14
|
+
|
|
15
|
+
_SELF_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
16
|
+
_ENTRY="$(cd "${_SELF_DIR}/.." && pwd)/tmux-ai-hooks-status.tmux"
|
|
17
|
+
|
|
18
|
+
_cfg=$(tmux display-message -p '#{config_files}' 2>/dev/null || true)
|
|
19
|
+
# config_files 实测是「候选列表」而非已加载列表(含不存在路径)→ 逐个 [ -f ] 过滤;
|
|
20
|
+
# 老版本 tmux 无该 format 时回落 ~/.tmux.conf。
|
|
21
|
+
[ -n "$_cfg" ] || _cfg="$HOME/.tmux.conf"
|
|
22
|
+
# 注意用 printf '%s\n':末项无换行符时 read 返回非零,循环体会漏掉最后一项
|
|
23
|
+
printf '%s\n' "$_cfg" | tr ',' '\n' | while IFS= read -r _f; do
|
|
24
|
+
[ -n "$_f" ] && [ -f "$_f" ] || continue
|
|
25
|
+
tmux source-file "$_f" 2>/dev/null || true
|
|
26
|
+
done
|
|
27
|
+
|
|
28
|
+
[ -f "$_ENTRY" ] && tmux run-shell "$_ENTRY" 2>/dev/null || true
|
|
29
|
+
tmux display-message "配置已重载" 2>/dev/null || true
|
package/scripts/tmux-ai-status
CHANGED
|
@@ -78,6 +78,33 @@ _current_pane_status() {
|
|
|
78
78
|
tmux display-message -pt "$TMUX_PANE" -p "#{@ai_pane_status}" 2>/dev/null
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
+
# Notification「隐式」路径(idle_prompt / waiting for your input)的状态解析:
|
|
82
|
+
# 通知可能乱序晚到(async hook 无顺序保证),不能无条件降级为 idle '-'。
|
|
83
|
+
# 判定顺序(⚠️ 不能颠倒):
|
|
84
|
+
# 1. live 审批态 !/? → 保持。审批等待期间 pending-pre 非空(PreToolUse 先于
|
|
85
|
+
# PermissionRequest),若先看 pending 会派生出 '>' 把 '!' 降级——违反
|
|
86
|
+
# 「审批态永不覆盖」
|
|
87
|
+
# 2. pending 派生出 !/? → 采用(codex/opencode 的审批集合;claude 不写 perm)
|
|
88
|
+
# 3. pending 派生出 '>' → **仅当存在 10s 内的新条目才可信**(并发 hook 抢锁
|
|
89
|
+
# 超时会让清理静默放弃,集合可能残留陈旧 id),否则视为残留
|
|
90
|
+
# 4. 其余 → '-'
|
|
91
|
+
# 显式重置消息(denied/cancelled/rejected)不走本函数——denial 携带「等待已结束」
|
|
92
|
+
# 语义,应无条件置 '-'。
|
|
93
|
+
_notification_implicit_status() {
|
|
94
|
+
local live
|
|
95
|
+
live=$(_current_pane_status)
|
|
96
|
+
case "$live" in
|
|
97
|
+
"!"|"?") printf '%s' "$live"; return ;;
|
|
98
|
+
esac
|
|
99
|
+
if _derive_status_from_pending; then
|
|
100
|
+
case "$DERIVED" in
|
|
101
|
+
"!"|"?") printf '%s' "$DERIVED"; return ;;
|
|
102
|
+
esac
|
|
103
|
+
if _pending_recent 10; then printf '%s' ">"; return; fi
|
|
104
|
+
fi
|
|
105
|
+
printf '%s' "-"
|
|
106
|
+
}
|
|
107
|
+
|
|
81
108
|
# 重建聚合状态(_refresh / _entered 共用)
|
|
82
109
|
_run_refresh() {
|
|
83
110
|
_maybe_repair_hooks
|
|
@@ -94,6 +121,7 @@ PREV_STATUS=$(_current_pane_status)
|
|
|
94
121
|
TOOL=""
|
|
95
122
|
TOOL_USE_ID=""
|
|
96
123
|
INPUT_DIGEST=""
|
|
124
|
+
_log_flags=""
|
|
97
125
|
|
|
98
126
|
case "$EVENT" in
|
|
99
127
|
Notification)
|
|
@@ -102,14 +130,15 @@ case "$EVENT" in
|
|
|
102
130
|
INPUT_DIGEST=$(echo "$INPUT" | jq -c '{notification_type, message}' 2>/dev/null)
|
|
103
131
|
case "$NOTIF_TYPE" in
|
|
104
132
|
idle_prompt)
|
|
105
|
-
STATUS
|
|
133
|
+
STATUS=$(_notification_implicit_status)
|
|
106
134
|
;;
|
|
107
135
|
*)
|
|
108
136
|
case "$MSG" in
|
|
109
137
|
*"waiting for your input"*)
|
|
110
|
-
STATUS
|
|
138
|
+
STATUS=$(_notification_implicit_status)
|
|
111
139
|
;;
|
|
112
140
|
*"denied"*|*"cancelled"*|*"canceled"*|*"rejected"*)
|
|
141
|
+
# 显式重置:denial 表示等待已结束,无条件 idle(即便有 pending 残项)
|
|
113
142
|
STATUS="-"
|
|
114
143
|
;;
|
|
115
144
|
*)
|
|
@@ -118,6 +147,8 @@ case "$EVENT" in
|
|
|
118
147
|
esac
|
|
119
148
|
;;
|
|
120
149
|
esac
|
|
150
|
+
# 写入循环内不再对 Notification 重复 re-derive:事件锁(tmux-ai-status 头部)
|
|
151
|
+
# 已串行化同 pane 事件,分支内判定即最终判定。
|
|
121
152
|
;;
|
|
122
153
|
PermissionRequest)
|
|
123
154
|
# 一次 @tsv 提取 tool_name/tool_use_id(digest 单独一次;stdin 只能读一次,
|
|
@@ -209,8 +240,12 @@ case "$EVENT" in
|
|
|
209
240
|
else
|
|
210
241
|
STATUS="✓"
|
|
211
242
|
fi
|
|
212
|
-
# fallback: TMUX_PANE 未解析时,精准清理孤儿状态(不碰其他活跃 pane
|
|
243
|
+
# fallback: TMUX_PANE 未解析时,精准清理孤儿状态(不碰其他活跃 pane)。
|
|
244
|
+
# 此时事件无处写入——不要伪造 ✓ 写入日志(真实 pane 可能仍是 !):
|
|
245
|
+
# STATUS 置空 + flags 标注,日志显示「无状态转移」。
|
|
213
246
|
if [ -n "$TMUX" ] && [ -z "$TMUX_PANE" ]; then
|
|
247
|
+
STATUS=""
|
|
248
|
+
_log_flags="PANE=unresolved"
|
|
214
249
|
_cleanup_stale_panes
|
|
215
250
|
build_all_status
|
|
216
251
|
tmux set-option -g @ai_all_status "$ALL" 2>/dev/null || true
|
|
@@ -294,7 +329,7 @@ if [ -n "$INPUT_DIGEST" ] && [ "$INPUT_DIGEST" != "null" ]; then
|
|
|
294
329
|
_log_input_line="input: $(_ai_log_truncate_input "$INPUT_DIGEST")"
|
|
295
330
|
fi
|
|
296
331
|
fi
|
|
297
|
-
_ai_log_event "$PREV_STATUS" "$STATUS" "" "$_log_tool_line" "$_log_input_line"
|
|
332
|
+
_ai_log_event "$PREV_STATUS" "$STATUS" "$_log_flags" "$_log_tool_line" "$_log_input_line"
|
|
298
333
|
|
|
299
334
|
if [ -n "$TMUX_PANE" ] && { [ -n "$TMUX" ] || command -v tmux &>/dev/null; }; then
|
|
300
335
|
# 写入前重新读取 pending,避免并发事件用过期结果覆盖仍有效的审批态。
|
|
@@ -43,19 +43,25 @@ tmux set-option -g "status-format[${CLAUDE_ROW}]" "#[align=centre]#{?#{@ai_all_s
|
|
|
43
43
|
# 不动 status-right,让用户自行管理第 1 行内容
|
|
44
44
|
# Claude 状态通过多行 status-format 的独立行显示,不修改 status-right
|
|
45
45
|
|
|
46
|
-
# --- Reload
|
|
47
|
-
#
|
|
48
|
-
#
|
|
49
|
-
#
|
|
50
|
-
#
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
tmux
|
|
58
|
-
|
|
46
|
+
# --- Reload 保护:prefix+r = source 实际配置文件 + 重跑本插件初始化 ---
|
|
47
|
+
# 绑定单命令指向 scripts/tmux-ai-reload(不再需要 mktemp conf 规避 \;)。
|
|
48
|
+
# ⚠️ 每次加载都重绑(bind-key 是 upsert,幂等):旧实现用一次性哨兵
|
|
49
|
+
# @claude_hooks_reload_registered,但 source-file 配置内用户自己的 `bind r`
|
|
50
|
+
# 会把绑定重置回原命令,哨兵已置位 → 纯 TPM 模式从第二次重载起插件初始化
|
|
51
|
+
# 静默停止。每次重绑使其自愈。
|
|
52
|
+
# 原绑定保存:覆盖前捕获(排除本插件新旧两种命令形态),供手动恢复:
|
|
53
|
+
# tmux show-option -gv @claude_hooks_saved_bind_r # 查看
|
|
54
|
+
# eval "$(tmux show-option -gv @claude_hooks_saved_bind_r)" # 回放
|
|
55
|
+
_prev_r=$(tmux list-keys -T prefix r 2>/dev/null || true)
|
|
56
|
+
case "$_prev_r" in
|
|
57
|
+
""|*tmux-ai-reload*|*tmux-ai-hooks-status.tmux*) ;;
|
|
58
|
+
*)
|
|
59
|
+
if [ -z "$(tmux show-option -gv @claude_hooks_saved_bind_r 2>/dev/null)" ]; then
|
|
60
|
+
tmux set-option -g @claude_hooks_saved_bind_r "$_prev_r" 2>/dev/null || true
|
|
61
|
+
fi
|
|
62
|
+
;;
|
|
63
|
+
esac
|
|
64
|
+
tmux bind-key r run-shell "'${CURRENT_DIR}/scripts/tmux-ai-reload'" 2>/dev/null || true
|
|
59
65
|
|
|
60
66
|
# --- 自动注册 Claude hooks(幂等,每次插件加载时确保 hooks 存在)---
|
|
61
67
|
# auto 模式:用户显式卸载过(suppressed-claude 标记在)时静默跳过,尊重卸载意图;
|
|
@@ -119,4 +125,20 @@ else
|
|
|
119
125
|
fi
|
|
120
126
|
|
|
121
127
|
# Esc 拒绝检测:! / ? 状态下 Esc → -
|
|
128
|
+
# 全局 root 表绑定会覆盖用户既有 Escape 绑定——覆盖前保存 + 一次性告警
|
|
129
|
+
# (无法安全链式调用任意已有绑定,恢复需手动):
|
|
130
|
+
# tmux show-option -gv @claude_hooks_saved_bind_escape
|
|
131
|
+
_prev_esc=$(tmux list-keys -T root Escape 2>/dev/null || true)
|
|
132
|
+
case "$_prev_esc" in
|
|
133
|
+
""|*tmux-ai-esc*) ;;
|
|
134
|
+
*)
|
|
135
|
+
if [ -z "$(tmux show-option -gv @claude_hooks_saved_bind_escape 2>/dev/null)" ]; then
|
|
136
|
+
tmux set-option -g @claude_hooks_saved_bind_escape "$_prev_esc" 2>/dev/null || true
|
|
137
|
+
fi
|
|
138
|
+
if [ -z "$(tmux show-option -gv @claude_hooks_esc_warned 2>/dev/null)" ]; then
|
|
139
|
+
tmux display-message "⚠ tmux-ai-hooks-status 覆盖了全局 Esc 绑定;原绑定已存 @claude_hooks_saved_bind_escape" 2>/dev/null || true
|
|
140
|
+
tmux set-option -g @claude_hooks_esc_warned "1" 2>/dev/null || true
|
|
141
|
+
fi
|
|
142
|
+
;;
|
|
143
|
+
esac
|
|
122
144
|
tmux bind-key -n Escape run-shell "${CURRENT_DIR}/scripts/tmux-ai-esc"
|