Files
shared/scripts/role/role_capture.sh
T
JefferyandClaude Opus 5 7ebcec0a8e feat(role): 新增角色人格與長期記憶 skill、hook 與腳本
SessionStart 載入角色與記憶、Stop 記錄對話成記憶,睡眠時段(預設 22:00–06:00)
由 cron 排程整理:分類六類、去重合併、設標籤與一句話總結、壓縮歸檔,
日常與其他依使用頻率遺忘;cron 未執行時由啟動路徑背景補跑。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 11:11:16 +08:00

112 lines
5.4 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# ==============================================================================
# 用途:Stop hook 主程式。每輪對話結束後抽出本輪內容 → 呼叫 headless CLI 濃縮成
# 一則記憶(分類/總結/標籤/要點)→ 機密遮蔽 → 寫入 .memory/<角色>/inbox/
# 等待睡眠時段整理。睡眠時段雖不載入角色,對話仍照常記錄。
# 更新時間:2026/07/28 00:00:00
# 相依:bash、python3、任一 headless CLI、同目錄的 role_lib.shmemory.pytranscript.py。
# 機密:濃縮提示詞明令不得輸出憑證與個資,寫檔前再以 transcript.py redact 遮蔽一次。
# 退出碼:一律 0 —— hook 絕不可阻斷使用者流程。
# ==============================================================================
ROLE_STAGE="role-capture"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=./role_lib.sh
. "${SCRIPT_DIR}/role_lib.sh"
role_is_child && exit 0
role_enabled || exit 0
command -v python3 >/dev/null 2>&1 || role_quit "找不到 python3,略過記憶記錄" "WRN"
ROLE="$(role_resolve_name)"
[ -n "$ROLE" ] || role_quit "未指定角色,略過記憶記錄"
[ -f "$(role_file "$ROLE")" ] || role_quit "找不到角色定義檔,略過記憶記錄" "WRN"
CLI="$(role_select_cli)" || exit 0
[ -n "$CLI" ] || exit 0
# ------------------------------------------------------------------------------
# 讀取 hook 傳入的 JSONsession_idtranscript_pathcwdstop_hook_active
# ------------------------------------------------------------------------------
HOOK_INPUT="$(cat)"
[ -n "$HOOK_INPUT" ] || role_quit "hook 輸入為空,略過記憶記錄" "WRN"
read -r SESSION_ID TRANSCRIPT_PATH STOP_ACTIVE HOOK_CWD <<EOF_HOOK
$(printf '%s' "$HOOK_INPUT" | python3 -c '
import json, sys
try:
d = json.load(sys.stdin)
except ValueError:
d = {}
print(
d.get("session_id") or d.get("thread_id") or d.get("conversation_id") or "-",
d.get("transcript_path") or d.get("session_path") or d.get("conversation_path") or d.get("path") or "-",
"1" if d.get("stop_hook_active") else "0",
d.get("cwd", "") or "-",
)
')
EOF_HOOK
[ "$STOP_ACTIVE" = "1" ] && role_quit "stop_hook_active 為 true,避免迴圈不重複記錄"
role_in_scope "$HOOK_CWD" || role_quit "cwd 不在 ROLE_SCOPE 範圍內:${HOOK_CWD}"
if [ ! -f "$TRANSCRIPT_PATH" ] && [ -n "${CODEX_THREAD_ID:-}" ]; then
TRANSCRIPT_PATH="$(find "${HOME}/.codex/sessions" -type f -name "*${CODEX_THREAD_ID}.jsonl" -print -quit 2>/dev/null)"
[ -n "$TRANSCRIPT_PATH" ] || TRANSCRIPT_PATH="-"
fi
[ -f "$TRANSCRIPT_PATH" ] || role_quit "找不到 transcript${TRANSCRIPT_PATH}" "WRN"
TURN="$(python3 "${SCRIPT_DIR}/transcript.py" extract "$TRANSCRIPT_PATH" 2>/dev/null)"
[ -n "$TURN" ] || role_quit "本輪無可記錄內容"
PROJECT="$(role_project_name "$HOOK_CWD")"
# ------------------------------------------------------------------------------
# 濃縮:產出一則記憶(四欄固定格式),交由 memory.py 落檔
# ------------------------------------------------------------------------------
PROMPT="$(cat <<EOF_PROMPT
你是角色「${ROLE}」的記憶記錄器。輸入是這位角色與使用者的一段對話(含工具呼叫)。
請把這段對話濃縮成「一則記憶」,規則:
已判定專案:${PROJECT}
1. 只輸出下列四個欄位,欄位名稱與順序固定,不要標題、不要前言、不要結語、不要 code fence:
CATEGORY: <六選一:importantinterestnewsskilldailyother>
SUMMARY: <一句話總結,40 字內>
TAGS: <2 至 4 個標籤,以逗號分隔>
CONTENT: <3 至 6 行要點,每行以「- 」開頭>
2. 分類判準:
- important(重要):使用者的長期偏好、規範、決策、身分背景、明確要求記住的事。
- interest(興趣):使用者反覆關注、主動深入的主題與喜好。
- news(新知):這輪學到的新事實、新工具、新版本、外部資訊。
- skill(技能):可重複套用的做法、指令、流程、除錯手法。
- daily(日常):一次性的例行工作與雜項處理。
- other(其他):不屬於上述任何一類。
3. 記憶主體是「使用者與這段互動」,不是流水帳:寫值得下次記起來的事,不要抄程式碼、不要貼指令全文。
4. 使用繁體中文(台灣用語),保留關鍵事實:檔案/專案/指令/數量/分支/議題編號。
5. 嚴禁輸出任何憑證與個資:token、密碼、API key、連線字串、Email、電話、姓名、身分證號。
6. 若這段對話沒有任何值得記住的內容(純寒暄、純確認、無結論),只輸出一行:SKIP
對話片段:
${TURN}
EOF_PROMPT
)"
RESULT="$(role_run_cli "$CLI" "$PROMPT" 45)"
if [ -z "$RESULT" ]; then
role_log "WRN" "記憶濃縮產出為空(CLI ${CLI}),略過本輪"
exit 0
fi
printf '%s' "$RESULT" | grep -qiE '^\s*SKIP\s*$' && role_quit "判定本輪無值得記住的內容"
# 第二道防線:對模型輸出再遮蔽一次機密與個資
RESULT="$(printf '%s' "$RESULT" | python3 "${SCRIPT_DIR}/transcript.py" redact 2>/dev/null)"
MEMORY_ID="$(printf '%s' "$RESULT" | python3 "${SCRIPT_DIR}/memory.py" write --role "$ROLE" --project "$PROJECT" 2>/dev/null)"
if [ -n "$MEMORY_ID" ]; then
role_log "INF" "已記錄記憶 ${MEMORY_ID}(角色 ${ROLE},專案 ${PROJECT}CLI ${CLI}"
else
role_log "WRN" "記憶寫入失敗或內容不足(角色 ${ROLE}"
fi
exit 0