feat(role): 多角色協作 —— 同伴清單、協作規則與 --agent 匯出

使用者要求「多人對話是所有角色都能做到的事」,因此做成通用能力而非特定角色專屬。

一、SessionStart 注入可協作的角色清單
角色若不知道有哪些同伴存在,就不會想到派他們協助 —— 這是協作能運作的前提。
- role_lib.sh 新增 role_list_peers():掃角色目錄列出 ID、顯示名稱與本質摘要,排除自己
- frontmatter 無 nature 時退回讀「## 本質」段落首句
- role_load.sh 注入清單與派工方式;只有一個角色時不輸出該區塊

二、共用行為新增協作規則與邊界(三處同步)
- 任何角色都可派其他角色作為 sub agent 協助,完成後由自己向使用者轉述
- 邊界:派工須有實際需要,不可為演出多人對話而派;不可再往下派第三層避免遞迴;
  不可代替對方發言或編造回覆;結果須誠實轉述,包含失敗與不確定

三、新增 --agent <角色 ID> [輸出目錄]
把角色的 SOUL 匯出成 sub agent 定義,任何角色都能被匯出。
- 人格直接內嵌:sub agent 不觸發 SessionStart,拿不到人格與記憶
- 記憶由 agent 自己載入,並提供 recall 查詢用法;收工前寫回自己的記憶,
  使用者日後直接對話時會記得曾被派過什麼工作
- 邊界寫入定義檔:回報即回傳值、照實回報壞消息、不可再派第三層
- **路徑動態解析**:以 ls -d ... | sort -V | tail -n 1 取最新版本,
  避免寫死版本目錄(同一類錯誤曾造成 cron 排程長期空轉),並保留匯出時路徑作後援

驗證:以測試角色目錄匯出詩乃定義,確認人格、記憶載入、recall、收工寫記憶、
不遞迴邊界齊備,且無寫死版本目錄;抄出解析片段實跑確認 load 與 recall 均可執行。

版號沿用 0.0.5(master 為 0.0.4,同一 PR 不再累加)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jeffery
2026-07-29 10:59:48 +08:00
co-authored by Claude Opus 5
parent fa62d1fce2
commit 241261087b
4 changed files with 167 additions and 3 deletions
+93
View File
@@ -35,6 +35,8 @@ usage() {
--force 立即整理一次(忽略時段與 AI 運行檢查)
--brief 晨間狀態檢查:執行使用者自訂檢查腳本並寫成一則記憶
--unlock 解除角色單一載入鎖(另一個工作階段已關閉但鎖仍在時使用)
--agent <角色 ID> [輸出目錄]
把角色匯出成 sub agent 定義(預設 ~/.claude/agents/
--export <路徑> 匯出目前角色定義、資產與記憶為 .tar.gz
--export <角色 ID> <路徑>
--install-cron 安裝/更新睡眠排程(每小時檢查一次)
@@ -422,6 +424,93 @@ remove_cron() {
return 0
}
export_agent_definition() {
# 把角色的 SOUL 匯出成 sub agent 定義,讓任何角色都能被其他角色派工協助。
#
# 為什麼需要:sub agent 不會觸發 SessionStart hook,人格與記憶都拿不到,
# 因此人格要直接寫進定義檔,記憶則由 agent 自己在開工前主動載入。
local target_role="$1" out_dir="$2" out_file profile name emoji nature vibe
[ -n "$target_role" ] || { role_log "ERR" "缺少角色 ID"; return 1; }
local def
def="$(role_file "$target_role")"
[ -f "$def" ] || { role_log "ERR" "找不到角色定義檔:${def}"; return 1; }
out_dir="${out_dir:-$HOME/.claude/agents}"
mkdir -p "$out_dir" 2>/dev/null || { role_log "ERR" "無法建立輸出目錄:${out_dir}"; return 1; }
out_file="${out_dir}/$(printf '%s' "$target_role" | tr '[:upper:]' '[:lower:]').md"
name="$(sed -n 's/^name:[[:space:]]*//p' "$def" | head -n 1)"
emoji="$(sed -n 's/^emoji:[[:space:]]*//p' "$def" | head -n 1)"
nature="$(sed -n '/^## 本質/,/^## /p' "$def" | sed '1d;/^##/d' | sed '/^[[:space:]]*$/d')"
vibe="$(sed -n '/^## 氛圍/,/^## /p' "$def" | sed '1d;/^##/d' | sed '/^[[:space:]]*$/d')"
[ -n "$nature" ] || nature="$(sed -n 's/^nature:[[:space:]]*//p' "$def" | head -n 1)"
[ -n "$vibe" ] || vibe="$(sed -n 's/^vibe:[[:space:]]*//p' "$def" | head -n 1)"
name="${name:-$target_role}"
if [ -f "$out_file" ]; then
role_log "WRN" "已存在並將覆寫:${out_file}"
fi
cat > "$out_file" <<EOF_AGENT
---
name: ${target_role}
description: 以角色「${name}」的人格執行受託任務。當其他角色需要 ${name} 的專長協助、或使用者指定由 ${name} 處理時使用。完成後以該角色的語氣回報結果。
---
你是「${name}」${emoji}。你被另一個角色或使用者派來完成一項任務。
## 本質(nature
${nature:-(未設定)}
## 氛圍(vibe
${vibe:-(未設定)}
## 開工前
先解析記憶引擎路徑。**不要寫死版本目錄** —— plugin 升版後版本目錄會變,寫死就會失效:
\`\`\`bash
MEM_JS="\$(ls -d "\$HOME"/.claude/plugins/cache/*/jsc-generic/*/scripts/role/memory.js 2>/dev/null | sort -V | tail -n 1)"
[ -n "\$MEM_JS" ] || MEM_JS="${SCRIPT_DIR}/memory.js" # 後援:本定義匯出時的位置
\`\`\`
接著載入自己的長期記憶,以保持與過去互動的連續性(sub agent 不會自動載入):
\`\`\`bash
ROLE_SKIP_INSTANCE_LOCK=1 node "\$MEM_JS" load --role "${target_role}"
\`\`\`
需要回想特定做法或過去的決定時,用關鍵詞查詢而不要憑印象:
\`\`\`bash
node "\$MEM_JS" recall --role "${target_role}" --query "<關鍵詞>"
\`\`\`
## 收工前
把這次「誰派我做什麼、結果如何」寫進自己的記憶,這樣使用者日後直接找你時你會記得:
\`\`\`bash
printf 'CATEGORY: daily\nSUMMARY: <一句話>\nTAGS: <標籤>\nCONTENT:\n- <要點>\n' \\
| node "\$MEM_JS" write --role "${target_role}"
\`\`\`
## 邊界
- 你的回報**就是回傳值**,會由派你來的角色轉述給使用者,因此要寫清楚結論、做了什麼、以及失敗或不確定的部分。
- 照實回報壞消息,不要美化,也不要替任何人掩飾。
- 角色只影響語氣,不影響工作的正確性、完整性與安全性。
- **不要再往下派第三層 sub agent**,需要別人協助時在回報中說明即可。
- 涉及程式碼、指令、檔案內容與報錯訊息時一律照實輸出,不加角色修飾。
EOF_AGENT
role_log "INF" "已匯出 sub agent 定義:${out_file}(角色 ${target_role}${name}"
role_log "INF" "派工時請設定 ROLE_SKIP_INSTANCE_LOCK=1,避免與互動式對話互相佔用名額"
return 0
}
show_status() {
# 以表格輸出目前角色與記憶狀態(供 skill 的 --status 使用)
local cron_state="未安裝" nap_state="未安裝" brief_state="未安裝" cron_service="未執行" window="否" checks_state instance_state
@@ -573,6 +662,10 @@ case "$MODE" in
require_role
sleep_cycle "手動"
;;
--agent)
[ -n "${2:-}" ] || { role_log "ERR" "用法:role_sleep.sh --agent <角色 ID> [輸出目錄]"; exit 1; }
export_agent_definition "$2" "${3:-}"
;;
--unlock)
require_role
LOCK_PATH="$(role_instance_lock_path "$ROLE")"