Executable
+160
@@ -0,0 +1,160 @@
|
||||
#!/bin/bash
|
||||
# tmux-msg-notify.sh — 面板间消息完成通知
|
||||
#
|
||||
# 由接收方 cc 通过 Bash 工具调用,通知发送方任务完成。
|
||||
#
|
||||
# 用法:
|
||||
# tmux-msg-notify.sh <msg_id> <reply_to_pane> "<summary>"
|
||||
#
|
||||
# 行为:
|
||||
# 1. 更新原消息状态为 completed
|
||||
# 2. 创建回复信封
|
||||
# 3. 通过 tmux-send-prompt.sh 向发送方发送通知
|
||||
#
|
||||
# 示例:
|
||||
# tmux-msg-notify.sh msg-20260419-142357-a1b2 %1 "已完成用户 API 实现"
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
MSG_ID="${1:?用法: tmux-msg-notify.sh <msg_id> <reply_to_pane> \"<summary>\"}"
|
||||
REPLY_TO_PANE="${2:?需要 reply_to_pane 参数}"
|
||||
SUMMARY="${3:?需要完成摘要}"
|
||||
|
||||
MSG_DIR="${TMUX_MSG_DIR:-/workspace/.tmux-messages}"
|
||||
INDEX_FILE="${MSG_DIR}/index.jsonl"
|
||||
SEND_SCRIPT="/workspace/scripts/tmux-send-prompt.sh"
|
||||
|
||||
# ─── 辅助函数 ───
|
||||
|
||||
append_index() {
|
||||
local json_line="$1"
|
||||
mkdir -p "$(dirname "$INDEX_FILE")"
|
||||
(
|
||||
flock -x 200
|
||||
echo "$json_line" >> "$INDEX_FILE"
|
||||
) 200>"${INDEX_FILE}.lock"
|
||||
}
|
||||
|
||||
write_envelope() {
|
||||
local filepath="$1" json="$2"
|
||||
mkdir -p "$(dirname "$filepath")"
|
||||
local tmp="${filepath}.tmp"
|
||||
echo "$json" > "$tmp"
|
||||
mv "$tmp" "$filepath"
|
||||
}
|
||||
|
||||
get_pane_title() {
|
||||
local pane_id="$1"
|
||||
tmux display-message -t "$pane_id" -p '#{pane_title}' 2>/dev/null || echo "未知"
|
||||
}
|
||||
|
||||
# ─── 主流程 ───
|
||||
|
||||
echo "[tmux-notify] 处理完成通知: ${MSG_ID}"
|
||||
|
||||
# 查找原消息信封
|
||||
ENVELOPE_PATH=$(find "$MSG_DIR" -name "${MSG_ID}.json" -type f 2>/dev/null | head -1)
|
||||
|
||||
if [[ -z "$ENVELOPE_PATH" ]]; then
|
||||
echo "[tmux-notify] 警告: 未找到消息 ${MSG_ID},仍发送通知"
|
||||
# 即使找不到原消息,也发送通知(宽容模式)
|
||||
FROM_TITLE=$(get_pane_title "$TMUX_PANE")
|
||||
TO_TITLE=$(get_pane_title "$REPLY_TO_PANE")
|
||||
|
||||
REPLY_PROMPT="[通知] 来自 ${FROM_TITLE} 的完成通知
|
||||
|
||||
原始任务 ID: ${MSG_ID}
|
||||
完成摘要: ${SUMMARY}
|
||||
|
||||
无需回复此通知。"
|
||||
# 直接发送
|
||||
PROMPT_DIR_FALLBACK="${MSG_DIR}/prompts"
|
||||
mkdir -p "$PROMPT_DIR_FALLBACK"
|
||||
TMP_PROMPT="${PROMPT_DIR_FALLBACK}/notify-${MSG_ID}.md"
|
||||
echo "$REPLY_PROMPT" > "$TMP_PROMPT"
|
||||
bash "$SEND_SCRIPT" "$REPLY_TO_PANE" "$TMP_PROMPT" 2>&1 || true
|
||||
echo "[tmux-notify] 通知已发送(无信封模式)"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# 读取原消息
|
||||
ENVELOPE=$(cat "$ENVELOPE_PATH")
|
||||
|
||||
# 解析原消息字段
|
||||
FROM_PANE=$(echo "$ENVELOPE" | python3 -c "import sys,json; print(json.loads(sys.stdin.read())['from_pane'])" 2>/dev/null)
|
||||
FROM_TITLE=$(echo "$ENVELOPE" | python3 -c "import sys,json; print(json.loads(sys.stdin.read())['from_title'])" 2>/dev/null)
|
||||
TO_TITLE=$(echo "$ENVELOPE" | python3 -c "import sys,json; print(json.loads(sys.stdin.read())['to_title'])" 2>/dev/null)
|
||||
TASK_SUMMARY=$(echo "$ENVELOPE" | python3 -c "import sys,json; d=json.loads(sys.stdin.read()); print(d.get('task_summary','')[:80])" 2>/dev/null)
|
||||
|
||||
NOW=$(date -Iseconds)
|
||||
|
||||
# 更新原消息状态
|
||||
python3 -c "
|
||||
import sys, json
|
||||
with open('${ENVELOPE_PATH}') as f:
|
||||
d = json.load(f)
|
||||
d['status'] = 'completed'
|
||||
d['replied_at'] = '${NOW}'
|
||||
d['reply_summary'] = '''${SUMMARY}'''
|
||||
with open('${ENVELOPE_PATH}', 'w') as f:
|
||||
json.dump(d, f, ensure_ascii=False, indent=2)
|
||||
" 2>/dev/null
|
||||
|
||||
# 创建回复信封
|
||||
REPLY_MSG_ID="reply-${MSG_ID}"
|
||||
DAY_DIR=$(date +%Y/%m/%d)
|
||||
REPLY_ENVELOPE_PATH="${MSG_DIR}/${DAY_DIR}/${REPLY_MSG_ID}.json"
|
||||
|
||||
SENDER_PANE="${TMUX_PANE:-%0}"
|
||||
SENDER_TITLE=$(get_pane_title "$SENDER_PANE")
|
||||
|
||||
REPLY_ENVELOPE=$(cat <<EOJSON
|
||||
{"msg_id":"${REPLY_MSG_ID}","direction":"reply","in_reply_to":"${MSG_ID}","from_pane":"${SENDER_PANE}","from_title":"${SENDER_TITLE}","to_pane":"${REPLY_TO_PANE}","to_title":"${FROM_TITLE}","task_summary":null,"task_file":null,"status":"created","created_at":"${NOW}","delivered_at":null,"replied_at":null,"reply_msg_id":null,"reply_summary":"${SUMMARY}","error":null}
|
||||
EOJSON
|
||||
)
|
||||
|
||||
write_envelope "$REPLY_ENVELOPE_PATH" "$REPLY_ENVELOPE"
|
||||
|
||||
# 写入索引
|
||||
INDEX_LINE=$(printf '{"msg_id":"%s","direction":"reply","in_reply_to":"%s","from":"%s","to":"%s","status":"created","summary":"%s","ts":"%s"}\n' \
|
||||
"$REPLY_MSG_ID" "$MSG_ID" "$SENDER_PANE" "$REPLY_TO_PANE" "${SUMMARY}" "$NOW")
|
||||
append_index "$INDEX_LINE"
|
||||
|
||||
# 构建通知提示词
|
||||
REPLY_PROMPT="## 任务完成通知 [${REPLY_MSG_ID}]
|
||||
|
||||
来自: ${SENDER_TITLE} (Pane ${SENDER_PANE})
|
||||
原始任务: ${TASK_SUMMARY}
|
||||
消息 ID: ${MSG_ID}
|
||||
完成摘要: ${SUMMARY}
|
||||
|
||||
无需回复此通知。"
|
||||
|
||||
# 写提示词文件(持久化,供发送方 cc 异步读取)
|
||||
PROMPT_DIR="${MSG_DIR}/prompts"
|
||||
mkdir -p "$PROMPT_DIR"
|
||||
TMP_PROMPT="${PROMPT_DIR}/notify-${MSG_ID}.md"
|
||||
echo "$REPLY_PROMPT" > "$TMP_PROMPT"
|
||||
|
||||
echo "[tmux-notify] 发送通知到 ${REPLY_TO_PANE}..."
|
||||
|
||||
if bash "$SEND_SCRIPT" "$REPLY_TO_PANE" "$TMP_PROMPT" 2>&1; then
|
||||
# 更新回复状态
|
||||
DELIVERED_TS=$(date -Iseconds)
|
||||
python3 -c "
|
||||
import sys, json
|
||||
with open('${REPLY_ENVELOPE_PATH}') as f:
|
||||
d = json.load(f)
|
||||
d['status'] = 'delivered'
|
||||
d['delivered_at'] = '${DELIVERED_TS}'
|
||||
with open('${REPLY_ENVELOPE_PATH}', 'w') as f:
|
||||
json.dump(d, f, ensure_ascii=False, indent=2)
|
||||
" 2>/dev/null
|
||||
|
||||
echo "[tmux-notify] 通知已发送: ${REPLY_MSG_ID}"
|
||||
else
|
||||
echo "[tmux-notify] 警告: 通知发送失败,但完成状态已记录"
|
||||
fi
|
||||
|
||||
# 提示词文件保留,供发送方 cc 异步读取
|
||||
echo "[tmux-notify] 完成"
|
||||
Reference in New Issue
Block a user