161 lines
5.1 KiB
Bash
Executable File
161 lines
5.1 KiB
Bash
Executable File
#!/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] 完成"
|