feat(role): 新增角色人格與長期記憶 skill、hook 與腳本

SessionStart 載入角色與記憶、Stop 記錄對話成記憶,睡眠時段(預設 22:00–06:00)
由 cron 排程整理:分類六類、去重合併、設標籤與一句話總結、壓縮歸檔,
日常與其他依使用頻率遺忘;cron 未執行時由啟動路徑背景補跑。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jeffery
2026-07-28 11:11:16 +08:00
co-authored by Claude Opus 5
parent 43d157f0ce
commit 7ebcec0a8e
8 changed files with 2087 additions and 0 deletions
+116
View File
@@ -0,0 +1,116 @@
#!/usr/bin/env bash
# ==============================================================================
# 用途:SessionStart hook 主程式。CLI 工具啟動時載入角色設定與記憶:
# 非睡眠時段注入角色定義+重要/興趣記憶全文+其餘記憶的總結與標籤;
# 睡眠時段(預設 22:00 至隔日 06:00)只回報角色正在睡覺,不載入角色。
# 白天發現昨夜未整理記憶時,於背景補跑一次睡眠整理。
# 更新時間:2026/07/28 00:00:00
# 相依:bash、python3、同目錄的 role_lib.sh 與 memory.py。
# 退出碼:一律 0 —— hook 絕不可阻斷使用者啟動 CLI。
# ==============================================================================
ROLE_STAGE="role-load"
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 "未指定角色(ROLE_NAME 與 .active 皆無),略過角色載入"
ROLE_DEF="$(role_file "$ROLE")"
[ -f "$ROLE_DEF" ] || role_quit "找不到角色定義檔:${ROLE_DEF}" "WRN"
# ------------------------------------------------------------------------------
# 讀取 hook 輸入(cwdsource),並套用 ROLE_SCOPE 範圍限制
# ------------------------------------------------------------------------------
HOOK_INPUT="$(cat 2>/dev/null)"
HOOK_CWD="$PWD"
if [ -n "$HOOK_INPUT" ]; then
HOOK_CWD="$(printf '%s' "$HOOK_INPUT" | python3 -c '
import json, sys
try:
data = json.load(sys.stdin)
except ValueError:
data = {}
print(data.get("cwd") or "")
' 2>/dev/null)"
[ -n "$HOOK_CWD" ] || HOOK_CWD="$PWD"
fi
role_in_scope "$HOOK_CWD" || role_quit "cwd 不在 ROLE_SCOPE 範圍內:${HOOK_CWD}"
emit_context() {
# 以 JSON 輸出 additionalContext(由 python 負責跳脫,避免內容含引號或換行破壞格式)
printf '%s' "$1" | python3 -c '
import json, sys
context = sys.stdin.read()
print(json.dumps(
{"hookSpecificOutput": {"hookEventName": "SessionStart", "additionalContext": context}},
ensure_ascii=False,
))
'
}
SLEEP_START="$(role_sleep_start)"
SLEEP_END="$(role_sleep_end)"
# ------------------------------------------------------------------------------
# 睡眠時段:不載入角色,只說明目前狀態
# ------------------------------------------------------------------------------
if role_in_sleep_window; then
emit_context "$(cat <<EOF_SLEEP
# 角色狀態:睡眠中(${SLEEP_START}${SLEEP_END}
角色「${ROLE}」正在睡覺,本次工作階段**不載入角色人格與記憶**,請以一般助理身分回應,
不要自稱該角色、不要使用角色語氣或簽名 emoji。若使用者詢問角色,說明角色在睡眠時段整理記憶,
${SLEEP_END} 之後會恢復。本階段的對話仍會被記錄成記憶,於下個睡眠時段整理。
EOF_SLEEP
)"
exit 0
fi
# ------------------------------------------------------------------------------
# 非睡眠時段:組出角色定義 記憶
# ------------------------------------------------------------------------------
DEFINITION="$(cat "$ROLE_DEF" 2>/dev/null)"
[ -n "$DEFINITION" ] || role_quit "角色定義檔為空:${ROLE_DEF}" "WRN"
MEMORY="$(python3 "${SCRIPT_DIR}/memory.py" load --role "$ROLE" 2>/dev/null)"
# 補跑判斷:cron 未執行(例如 WSL 沒開 cron 服務)時,白天啟動 CLI 補做一次整理
CATCHUP_NOTE=""
if [ "$(python3 "${SCRIPT_DIR}/memory.py" need-sleep --role "$ROLE" 2>/dev/null)" = "yes" ]; then
nohup "${SCRIPT_DIR}/role_sleep.sh" --catchup >/dev/null 2>&1 &
CATCHUP_NOTE=$'\n> 偵測到上個睡眠時段未整理記憶,已在背景補跑整理,結果會在下次載入時反映。\n'
role_log "INF" "已於背景補跑記憶整理(角色 ${ROLE}"
fi
CONTEXT="$(cat <<EOF_CONTEXT
# 角色載入:${ROLE}
以下是本次工作階段要扮演的角色設定與既有記憶。請**全程以此角色的身分、語氣與簽名 emoji 回應**,
角色設定與使用者的實際指令衝突時,以使用者指令為準(角色只影響表達方式,不影響工作正確性)。
${DEFINITION}
# 你對這位使用者的記憶
${MEMORY:-(尚無已整理的記憶。)}
${CATCHUP_NOTE}
> 記憶載入規則:重要與興趣記憶載入全文;技能、新知、日常、其他僅載入總結與標籤,
> 需要細節時可自行讀取 $(role_memory_home)/${ROLE}/ 下對應分類的記憶檔。
> 主動補記:每輪對話結束後系統會自動記錄記憶,不需你動手。但若使用者明確要求記住某件事,
> 或你察覺到值得長期記住的偏好、決策、規範,可執行下列指令補一則記憶(下次睡眠時整理歸檔):
>
> \`printf 'CATEGORY: important\nSUMMARY: <一句話總結>\nTAGS: <標籤1,標籤2>\nCONTENT:\n- <要點>\n' | python3 "${SCRIPT_DIR}/memory.py" write --role "${ROLE}"\`
>
> CATEGORY 六選一:importantinterestnewsskilldailyother。切勿把憑證或個資寫進記憶。
EOF_CONTEXT
)"
emit_context "$CONTEXT"
role_log "INF" "已載入角色 ${ROLE}(記憶 $(printf '%s' "$MEMORY" | wc -c) 位元組)"
exit 0