Files
shared/scripts/role/role_load.sh
T

127 lines
5.8 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
# ==============================================================================
# 用途:SessionStart hook 主程式。CLI 工具啟動時載入角色設定與記憶:
# 非睡眠時段注入角色定義+重要/興趣記憶全文+其餘記憶的總結與標籤;
# 睡眠時段(預設 22:00 至隔日 06:00)只回報角色正在睡覺,不載入角色。
# 白天發現昨夜未整理記憶時,於背景補跑一次睡眠整理。
# 更新時間:2026/07/28 12:21:11
# 相依:bash、node、同目錄的 role_lib.sh 與 memory.js。
# 退出碼:一律 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 node >/dev/null 2>&1 || role_quit "找不到 node,略過角色載入" "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" | node -e '
let raw = "";
process.stdin.setEncoding("utf8");
process.stdin.on("data", (chunk) => { raw += chunk; });
process.stdin.on("end", () => {
let data = {};
try { data = JSON.parse(raw); } catch {}
process.stdout.write(data.cwd || "");
});
' 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(由 node 負責跳脫,避免內容含引號或換行破壞格式)
printf '%s' "$1" | node -e '
let context = "";
process.stdin.setEncoding("utf8");
process.stdin.on("data", (chunk) => { context += chunk; });
process.stdin.on("end", () => {
process.stdout.write(JSON.stringify({
hookSpecificOutput: { hookEventName: "SessionStart", additionalContext: context },
}));
});
'
}
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="$(node "${SCRIPT_DIR}/memory.js" load --role "$ROLE" 2>/dev/null)"
# 補跑判斷:cron 未執行(例如 WSL 沒開 cron 服務)時,白天啟動 CLI 補做一次整理
CATCHUP_NOTE=""
if [ "$(node "${SCRIPT_DIR}/memory.js" 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_LOAD_LIMIT
> 字元預算限制;需要細節時可自行讀取 $(role_memory_home)/${ROLE}/ 下對應分類的記憶檔。
> 主動補記:每輪對話結束後系統會自動記錄記憶,不需你動手。但若使用者明確要求記住某件事,
> 或你察覺到值得長期記住的偏好、決策、規範,可執行下列指令補一則記憶(下次睡眠時整理歸檔):
>
> \`printf 'CATEGORY: important\nSUMMARY: <一句話總結>\nTAGS: <標籤1,標籤2>\nCONTENT:\n- <要點>\n' | node "${SCRIPT_DIR}/memory.js" write --role "${ROLE}"\`
>
> CATEGORY 六選一:importantinterestnewsskilldailyother。切勿把憑證或個資寫進記憶。
EOF_CONTEXT
)"
emit_context "$CONTEXT"
role_log "INF" "已載入角色 ${ROLE}(記憶 $(printf '%s' "$MEMORY" | wc -c) 位元組)"
exit 0