- 新增命令: lark-delegate(跨项目委派)、lark-inbox(收件箱)、lark-me(自发送)、stop(任务收尾) - 新增工具: lark-consumer/consumer-ensure/inbox-check(飞书事件实时消费与会话隔离)、tmux-monitor(监控面板) - runcc.sh 增加 --list/--kill/--send 操作模式与多会话自增命名 - CLAUDE.md 增加强制要求(汇报格式、收尾通知、上下文压缩)
145 lines
5.0 KiB
Bash
Executable File
145 lines
5.0 KiB
Bash
Executable File
#!/bin/bash
|
||
# ============================================================================
|
||
# 飞书事件消费者 (纯 bash) — 从 lark-cli event consume 管道读取 NDJSON,
|
||
# 去重后写入 inbox ndjson 文件,并通过 tmux send-keys 转发到当前 CC pane。
|
||
#
|
||
# 用法: lark-consumer.sh --inbox-file <路径> --cc-pane <pane_id> \
|
||
# [--submit-delay <秒>] [--diag-log <路径>] [--scope <标识>]
|
||
#
|
||
# 由 lark-consumer-ensure.sh 经 setsid 启动,全局单例运行。
|
||
# 所有配置通过命令行参数传入,避免 shell 嵌入转义问题。
|
||
#
|
||
# 环境变量:
|
||
# LARK_SENDER_ALLOW - sender open_id 白名单(优先)
|
||
# LARK_ME_USER_ID - sender open_id 白名单(fallback)
|
||
# ============================================================================
|
||
set -eo pipefail
|
||
# 不使用 set -u:空关联数组在 -u 下会触发"未绑定的变量"错误
|
||
|
||
# ── 参数解析 ──
|
||
INBOX_FILE=""
|
||
CC_PANE=""
|
||
SUBMIT_DELAY="0.5"
|
||
DIAG_LOG=""
|
||
SCOPE="singleton"
|
||
|
||
while [[ $# -gt 0 ]]; do
|
||
case "$1" in
|
||
--inbox-file) INBOX_FILE="$2"; shift 2 ;;
|
||
--cc-pane) CC_PANE="$2"; shift 2 ;;
|
||
--submit-delay) SUBMIT_DELAY="$2"; shift 2 ;;
|
||
--diag-log) DIAG_LOG="$2"; shift 2 ;;
|
||
--scope) SCOPE="$2"; shift 2 ;;
|
||
*) echo "未知参数: $1" >&2; exit 1 ;;
|
||
esac
|
||
done
|
||
|
||
# ── sender 白名单(三级 fallback:LARK_SENDER_ALLOW → LARK_ME_USER_ID → 默认值)──
|
||
SENDER_ALLOW="${LARK_SENDER_ALLOW:-${LARK_ME_USER_ID:-ou_2c245a54dbea4bc2197f6d63bd940bb0}}"
|
||
|
||
# ── 日志函数 ──
|
||
diag() {
|
||
[[ -n "${DIAG_LOG}" ]] || return 0
|
||
printf '[%s] [%s] [consumer] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "${SCOPE}" "$*" >> "${DIAG_LOG}"
|
||
}
|
||
|
||
# ── 检测目标 CC pane 是否存活 ──
|
||
pane_alive() {
|
||
[[ -z "${CC_PANE}" ]] && return 0 # 非 tmux 模式,永远 true
|
||
tmux display-message -t "${CC_PANE}" -p '#{pane_id}' &>/dev/null
|
||
}
|
||
|
||
# ── 从 ndjson 行提取 JSON 字段 ──
|
||
field() {
|
||
jq -r "$1 // empty" <<< "$2" 2>/dev/null
|
||
}
|
||
|
||
# ── 加载已有 message_id 到关联数组(启动去重)──
|
||
declare -A SEEN
|
||
load_seen() {
|
||
[[ -f "${INBOX_FILE}" ]] || return 0
|
||
local line mid
|
||
while IFS= read -r line; do
|
||
[[ -z "${line}" ]] && continue
|
||
mid=$(field '.id' "${line}") || true
|
||
[[ -n "${mid}" ]] && SEEN["${mid}"]=1
|
||
done < "${INBOX_FILE}"
|
||
}
|
||
|
||
diag "消费者 bash 进程启动 (PID=$$)"
|
||
diag "inbox=${INBOX_FILE} cc_pane=${CC_PANE} delay=${SUBMIT_DELAY} sender=${SENDER_ALLOW}"
|
||
|
||
load_seen
|
||
diag "已加载 ${#SEEN[@]} 条历史 message_id"
|
||
|
||
# 就绪信号(写入 stderr,由 ensure.sh 经 CONSUMER_LOG 检测)
|
||
echo "consumer ready (pid=$$, seen=${#SEEN[@]})" >&2
|
||
|
||
# ── 主循环:逐行读取 lark-cli 输出 ──
|
||
LAST_CHECK=$(date +%s)
|
||
CHECK_INTERVAL=30
|
||
|
||
while IFS= read -r line; do
|
||
[[ -z "${line}" ]] && continue
|
||
|
||
# ── 定期巡检 CC pane 存活 ──
|
||
_now=$(date +%s)
|
||
if (( _now - LAST_CHECK > CHECK_INTERVAL )); then
|
||
LAST_CHECK=${_now}
|
||
if ! pane_alive; then
|
||
diag "目标 pane 已消失(CC/session 退出),消费者主动退出"
|
||
exit 0
|
||
fi
|
||
fi
|
||
|
||
# ── sender 白名单过滤 ──
|
||
_sender=$(field '.sender' "${line}") || true
|
||
if [[ -n "${SENDER_ALLOW}" && "${_sender}" != "${SENDER_ALLOW}" ]]; then
|
||
diag "跳过非白名单 sender=${_sender}"
|
||
continue
|
||
fi
|
||
|
||
# ── 去重 ──
|
||
_mid=$(field '.id' "${line}") || true
|
||
if [[ -z "${_mid}" ]]; then
|
||
diag "警告: 缺少 message_id,跳过: ${line:0:80}"
|
||
continue
|
||
fi
|
||
if [[ -n "${SEEN[${_mid}]:-}" ]]; then
|
||
continue
|
||
fi
|
||
SEEN["${_mid}"]=1
|
||
|
||
# ── 写入 inbox(保留原始多行 msg)──
|
||
printf '%s\n' "${line}" >> "${INBOX_FILE}"
|
||
diag "新消息写入 inbox: id=${_mid}"
|
||
|
||
# ── send-keys 转发到 CC pane ──
|
||
if [[ -n "${CC_PANE}" ]]; then
|
||
if ! pane_alive; then
|
||
diag "转发前检测到 pane 已消失,消费者退出"
|
||
exit 0
|
||
fi
|
||
_msg=$(field '.msg' "${line}") || true
|
||
if [[ -n "${_msg}" ]]; then
|
||
# 多行压平:避免 tmux send-keys 把换行当多次回车,导致一条消息被拆成多次提交
|
||
_flat=$(printf '%s' "${_msg}" | tr '\n' ' ' | tr -s ' ')
|
||
diag "转发 → pane=${CC_PANE} len=${#_flat}"
|
||
if ! tmux send-keys -t "${CC_PANE}" -l "${_flat}" 2>/dev/null; then
|
||
diag "转发失败: send-keys -l 返回非零,pane=${CC_PANE} 可能已不存在"
|
||
continue
|
||
fi
|
||
sleep "${SUBMIT_DELAY}"
|
||
if ! tmux send-keys -t "${CC_PANE}" Enter 2>/dev/null; then
|
||
diag "转发失败: send-keys Enter 返回非零,pane=${CC_PANE} 可能已不存在"
|
||
continue
|
||
fi
|
||
diag "转发完成"
|
||
fi
|
||
else
|
||
diag "cc_pane 为空,仅写 inbox"
|
||
fi
|
||
done
|
||
|
||
diag "stdin 已关闭(lark-cli 退出),消费者结束"
|