Files
shared/scripts/role/role_capture.sh
T

128 lines
6.5 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 輕量濃縮成一則 inbox 記憶(粗分類/總結/
# 標籤/優先度/關聯/要點)→ 機密遮蔽 → 寫入 .memory/<角色>/inbox/
# 等待睡眠時段做完整 NREM/REM 整理。睡眠時段雖不載入角色,對話仍照常記錄。
# 更新時間:2026/07/28 12:21:11
# 相依:bash、node、任一 headless CLI、同目錄的 role_lib.shmemory.jstranscript.js。
# 機密:濃縮提示詞明令不得輸出憑證與個資,寫檔前再以 transcript.js 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 node >/dev/null 2>&1 || role_quit "找不到 node,略過記憶記錄" "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" | node -e '
let raw = "";
process.stdin.setEncoding("utf8");
process.stdin.on("data", (chunk) => { raw += chunk; });
process.stdin.on("end", () => {
let d = {};
try { d = JSON.parse(raw); } catch {}
process.stdout.write([
d.session_id || d.thread_id || d.conversation_id || "-",
d.transcript_path || d.session_path || d.conversation_path || d.path || "-",
d.stop_hook_active ? "1" : "0",
d.cwd || "-",
].join(" "));
});
')
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="$(node "${SCRIPT_DIR}/transcript.js" extract "$TRANSCRIPT_PATH" 2>/dev/null)"
[ -n "$TURN" ] || role_quit "本輪無可記錄內容"
PROJECT="$(role_project_name "$HOOK_CWD")"
CAPTURE_MIN_CHARS="${ROLE_CAPTURE_MIN_CHARS:-240}"
CAPTURE_TIMEOUT="${ROLE_CAPTURE_TIMEOUT:-25}"
if [ "${ROLE_CAPTURE_ENABLED:-1}" = "0" ]; then
role_quit "ROLE_CAPTURE_ENABLED=0,略過記憶記錄"
fi
if [ "${#TURN}" -lt "$CAPTURE_MIN_CHARS" ] && ! printf '%s' "$TURN" | grep -qiE '記住|remember|決定|規範|偏好|preference|always|不要|以後'; then
role_quit "本輪低於記憶長度門檻且無明確記憶線索,略過記錄"
fi
# ------------------------------------------------------------------------------
# 濃縮:產出一則輕量 inbox 記憶,交由 memory.js 落檔;完整整理留到睡眠週期
# ------------------------------------------------------------------------------
PROMPT="$(cat <<EOF_PROMPT
你是角色「${ROLE}」的記憶記錄器。輸入是這位角色與使用者的一段對話(含工具呼叫)。
請只做「編碼前處理」,把這段對話濃縮成最多一則 inbox 記憶;不要做跨記憶合併或長期整理。
已判定專案:${PROJECT}
1. 只輸出下列欄位,欄位名稱與順序固定,不要標題、不要前言、不要結語、不要 code fence:
CATEGORY: <六選一:importantinterestnewsskilldailyother>
SUMMARY: <一句話總結,40 字內>
TAGS: <2 至 4 個標籤,以逗號分隔>
PRIORITY: <1 到 5>
RELEVANCE: <1 至 4 個,以逗號分隔;explicit/future/repeated/novelty/emotional/temporary/inbox/project>
CONTENT: <3 至 6 行要點,每行以「- 」開頭>
2. 分類判準:
- important(重要):使用者的長期偏好、規範、決策、身分背景、明確要求記住的事。
- interest(興趣):使用者反覆關注、主動深入的主題與喜好。
- news(新知):這輪學到的新事實、新工具、新版本、外部資訊。
- skill(技能):可重複套用的做法、指令、流程、除錯手法。
- daily(日常):一次性的例行工作與雜項處理。
- other(其他):不屬於上述任何一類。
3. 優先度判準:5=使用者明確要求記住、長期規範、穩定偏好;4=可重複套用的流程/技能/決策;3=專案相關且未來可能有用;2=短期進度;1=低價值暫存。
4. 記憶主體是「使用者與這段互動」,不是流水帳:寫值得下次記起來的事,不要抄程式碼、不要貼指令全文。
5. 使用繁體中文(台灣用語),保留關鍵事實:檔案/專案/指令/數量/分支/議題編號。
6. 嚴禁輸出任何憑證與個資:token、密碼、API key、連線字串、Email、電話、姓名、身分證號。
7. 若這段對話沒有任何值得記住的內容(純寒暄、純確認、無結論、只有簡短狀態回報),只輸出一行:SKIP
對話片段:
${TURN}
EOF_PROMPT
)"
RESULT="$(role_run_cli "$CLI" "$PROMPT" "$CAPTURE_TIMEOUT")"
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" | node "${SCRIPT_DIR}/transcript.js" redact 2>/dev/null)"
MEMORY_ID="$(printf '%s' "$RESULT" | node "${SCRIPT_DIR}/memory.js" 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