feat(role): 新增點名載入,對話中叫名字即可切換角色
- `UserPromptSubmit` hook(`role_call.sh`)比對訊息開頭的角色名稱/ID/`aliases` 別名, 命中即注入該角色人格與記憶並接手本輪;未點名時不輸出內容也不寫檔案。 - 抽出 `role_context.sh` 共用 context 組裝,SessionStart 與點名載入共用同一份人格與操作規則, 避免兩種載入方式漂移;實測 SessionStart 注入內容與改動前 byte-identical。 - 新增階段角色狀態(`~/.roles/.sessions/<工作階段>.role`),`Stop` hook 改以「本階段實際角色」 寫記憶,避免點名換人後把跟 A 的對話記進 B 的記憶。 - `SessionEnd` 改為釋放本階段名下所有角色鎖並清掉階段狀態檔。 - 切換時先確認取得目標角色鎖才釋放原角色鎖,避免出現兩個角色都沒有的空窗。 - 睡眠時段、目標角色已被其他活躍階段佔用、點名的是已在場的角色時,一律不切換。 - 新增 `ROLE_CALL_ENABLED`(總開關)與 `ROLE_CALL_MARKER_ONLY`(只認 `@名字`)。 - 順帶修正 `role_capture.sh` 的 `--postcompact` 分支在 `PROJECT` 賦值前就使用它, 導致壓縮摘要記憶的 `sources` 一直為空。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
# 用途:角色(role)系統的共用函式庫。提供統一 log、啟用判斷、角色解析、
|
||||
# 睡眠時段判斷、AI 行程偵測、摘要 CLI 選擇與呼叫、記憶目錄鎖。
|
||||
# 本檔僅供 source,不可直接執行。
|
||||
# 更新時間:2026/07/29 12:55:00
|
||||
# 更新時間:2026/07/29 13:25:00
|
||||
# 相依:bash;摘要路徑需 README 定義的任一 headless CLI。
|
||||
# 機密:不 echo 任何 token;角色與記憶內容僅在程序記憶體與檔案間傳遞。
|
||||
# ==============================================================================
|
||||
@@ -406,6 +406,103 @@ role_instance_release() {
|
||||
rm -f "$(role_instance_lock_path "$1")" 2>/dev/null
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# 本階段實際角色(點名載入用)
|
||||
#
|
||||
# 為什麼需要:Stop hook 原本一律以 ROLE_NAME/.active 決定記憶寫給誰。使用者在對話中
|
||||
# 點名另一個角色接手後,這個判斷就錯了 —— 跟被點名的角色聊的內容會被寫進原角色的記憶,
|
||||
# 兩份記憶一起髒掉。因此點名時把「這個工作階段目前實際是誰」記進狀態檔,
|
||||
# Stop 與 SessionEnd 一律以它為準,取不到才退回 ROLE_NAME/.active。
|
||||
#
|
||||
# 為什麼用 session_id 當 key:UserPromptSubmit 與 Stop 兩個 hook 都拿得到同一個
|
||||
# session_id;取不到時退回 transcript 檔名。兩者皆無則不寫狀態 —— 無法識別工作階段時
|
||||
# 寧可維持原本行為,也不要把記憶寫給錯的角色。
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
role_session_dir() { printf '%s/.sessions' "$(role_home)"; }
|
||||
|
||||
role_session_key() {
|
||||
# $1=session_id、$2=transcript 路徑;輸出可安全當檔名的 key,皆無則輸出空字串
|
||||
local sid="$1" transcript="$2" key=""
|
||||
if [ -n "$sid" ] && [ "$sid" != "-" ]; then
|
||||
key="$sid"
|
||||
elif [ -n "$transcript" ] && [ "$transcript" != "-" ]; then
|
||||
key="$(basename "$transcript")"
|
||||
key="${key%.jsonl}"
|
||||
fi
|
||||
[ -n "$key" ] || return 0
|
||||
printf '%s' "$key" | tr -c 'A-Za-z0-9_.-' '_'
|
||||
}
|
||||
|
||||
role_session_file() {
|
||||
local key="$1"
|
||||
[ -n "$key" ] || return 1
|
||||
printf '%s/%s.role' "$(role_session_dir)" "$key"
|
||||
}
|
||||
|
||||
role_session_set() {
|
||||
# $1=key、$2=角色 ID、$3=transcript;key 為空代表無法識別階段,不寫
|
||||
local key="$1" role="$2" transcript="$3" file
|
||||
file="$(role_session_file "$key")" || return 1
|
||||
mkdir -p "$(role_session_dir)" 2>/dev/null
|
||||
{
|
||||
printf 'role=%s\n' "$role"
|
||||
printf 'transcript=%s\n' "$transcript"
|
||||
printf 'updated=%s\n' "$(role_now)"
|
||||
} > "$file" 2>/dev/null
|
||||
role_session_prune
|
||||
}
|
||||
|
||||
role_session_get() {
|
||||
# $1=key;輸出本階段實際角色。無狀態檔、欄位空白或角色定義已不存在時回傳 1
|
||||
local key="$1" file role
|
||||
file="$(role_session_file "$key")" || return 1
|
||||
[ -f "$file" ] || return 1
|
||||
role="$(sed -n 's/^role=//p' "$file" 2>/dev/null | head -n 1)"
|
||||
[ -n "$role" ] || return 1
|
||||
[ -f "$(role_file "$role")" ] || return 1
|
||||
printf '%s' "$role"
|
||||
}
|
||||
|
||||
role_session_exists() {
|
||||
# 本階段是否已有明確狀態(含「刻意記成沒有人格」的空 role),用來區分
|
||||
# 「這個階段確定沒載入角色」與「完全沒有資訊」—— 後者才該退回 ROLE_NAME/.active
|
||||
local key="$1" file
|
||||
file="$(role_session_file "$key")" || return 1
|
||||
[ -f "$file" ]
|
||||
}
|
||||
|
||||
role_session_clear() {
|
||||
local key="$1" file
|
||||
file="$(role_session_file "$key")" || return 0
|
||||
rm -f "$file" 2>/dev/null
|
||||
}
|
||||
|
||||
role_session_prune() {
|
||||
# 清掉 7 天以上未更新的狀態檔,避免長期累積殘留
|
||||
find "$(role_session_dir)" -maxdepth 1 -name '*.role' -mtime +7 -delete 2>/dev/null || true
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# 點名載入開關
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
role_call_enabled() {
|
||||
# 對話中以名字點名切換角色的總開關
|
||||
case "${ROLE_CALL_ENABLED:-1}" in
|
||||
0|false|no|off) return 1 ;;
|
||||
*) return 0 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
role_call_marker_only() {
|
||||
# 設 1 時只認 `@名字` 這種明確標記,避免話中提到名字就誤切
|
||||
case "${ROLE_CALL_MARKER_ONLY:-0}" in
|
||||
1|true|yes|on) return 0 ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
role_project_name() {
|
||||
# 專案判定:git remote 的 <owner>/<repo> 優先,其次目錄名
|
||||
local cwd="$1" origin cleaned owner_repo
|
||||
|
||||
Reference in New Issue
Block a user