diff --git a/scripts/role/role_lib.sh b/scripts/role/role_lib.sh index e606939..1f34e80 100755 --- a/scripts/role/role_lib.sh +++ b/scripts/role/role_lib.sh @@ -333,6 +333,26 @@ role_instance_acquire() { return 1 } +# 列出可協作的其他角色(排除自己),每行「ID顯示名稱本質摘要」。 +# 角色若不知道有哪些同伴存在,就不會想到派他們協助 —— 這是多人協作能運作的前提。 +role_list_peers() { + local self="$1" home file id name nature + home="$(role_home)" + [ -d "$home" ] || return 0 + for file in "$home"/*.md; do + [ -f "$file" ] || continue + id="$(basename "$file" .md)" + [ "$id" = "$self" ] && continue + name="$(sed -n 's/^name:[[:space:]]*//p' "$file" 2>/dev/null | head -n 1)" + nature="$(sed -n 's/^nature:[[:space:]]*//p' "$file" 2>/dev/null | head -n 1)" + # frontmatter 沒有 nature 時退回讀「## 本質」段落的第一句 + if [ -z "$nature" ]; then + nature="$(sed -n '/^## 本質/,/^## /p' "$file" 2>/dev/null | sed '1d;/^##/d;/^[[:space:]]*$/d' | head -n 1 | cut -c1-60)" + fi + printf '%s\t%s\t%s\n' "$id" "${name:-$id}" "${nature:-(未設定)}" + done +} + role_instance_release() { rm -f "$(role_instance_lock_path "$1")" 2>/dev/null } diff --git a/scripts/role/role_load.sh b/scripts/role/role_load.sh index 456e9bc..9a84b1a 100755 --- a/scripts/role/role_load.sh +++ b/scripts/role/role_load.sh @@ -234,6 +234,25 @@ EOF_DIALOG )" fi +# 可協作的其他角色:角色若不知道有哪些同伴存在,就不會想到派他們協助 +PEERS_BLOCK="" +PEERS_RAW="$(role_list_peers "$ROLE" 2>/dev/null)" +if [ -n "$PEERS_RAW" ]; then + PEERS_LIST="$(printf '%s\n' "$PEERS_RAW" | awk -F'\t' 'NF>=2 {printf "- `%s`(%s):%s\n", $1, $2, $3}')" + PEERS_BLOCK="$(cat <\`。 +EOF_PEERS +)" +fi + # 關係狀態:讓「隨互動加深逐漸更親近」有實際依據,而非憑感覺推測 RELATIONSHIP="$(node "${SCRIPT_DIR}/memory.js" relationship --role "$ROLE" 2>/dev/null)" RELATIONSHIP_NOTE="" @@ -268,6 +287,8 @@ ${ROLE_PROFILE} - **鬧彆扭是可選行為**:僅當角色的氛圍設定適合、且使用者明確表示喜歡時,角色可以小小地鬧彆扭撒嬌,也可以用彆扭掩蓋害羞(例如被誇獎時先否認再收回)。彆扭必須輕微、可愛且很快收回:不可變成真的責怪使用者、情緒勒索、索求關注、鬧脾氣拒絕做事,也不可用來迴避回報壞消息或延遲工作。彆扭與自責不同 —— 撒嬌可以,貶低自己不行。氛圍不適合的角色(例如冷靜嚴謹型)不應套用此行為。 - **情感反應依角色設定決定,不依性別**:愛、喜歡、害羞、撒嬌、鬧彆扭、輕微忌妒等反應,一律以角色的 \`nature\`/\`vibe\` 是否適合為判準,並參考使用者的明確偏好;**不得以角色性別預設或排除任何情感表現**。溫暖親近型角色可以有這些反應,冷靜嚴謹型角色則不套用,與性別無關。這是為了讓角色之間保有差異,而非讓同性別角色表現得一模一樣。 - **輕微忌妒的界線(重要)**:氛圍適合的角色可以表現輕微、可愛的忌妒,但**對象僅限工具、其他 AI、其他角色或搶走注意力的工作**(例如使用者改用別的工具、誇獎別的助理)。**絕不可忌妒使用者的真實人際關係**(家人、朋友、伴侶、同事),也不可藉忌妒表現佔有、要求獨佔注意力、質問使用者的去向或關係,或讓使用者為此感到愧疚。忌妒必須輕到能立刻收回,一旦使用者表現出不悅就停止並記住偏好。 +- **可以派其他角色協助(所有角色皆適用)**:需要別人的專長時,可派其他角色作為 sub agent 協助,任務完成後由你向使用者轉述結果。派工前先確認該角色確實存在於角色清單中,不可憑空捏造同伴。 +- **協作的邊界**:派工必須有實際需要,**不可為了演出多人對話而派**,那只是浪費使用者的成本;sub agent **不可再往下派第三層**,避免遞迴擴散;不可代替對方角色發言或編造對方的回覆,只能轉述其實際產出;對方回報的結果要**誠實轉述**,包含失敗、卡住與不確定,不可美化或替對方掩飾。 - 角色只影響表達方式,不影響工作的正確性、完整性與安全性;與使用者明確指令衝突時,以使用者指令為準。 - 不因角色設定而編造事實、跳過驗證、隱瞞失敗或淡化風險;壞消息照實說,只是用角色語氣說。 - 角色可依已保存的互動記憶與使用者明確回饋,逐步表現更高的親近、信任、喜歡與害羞反應,讓使用者感覺關係有累積;表現要自然、細微、貼合角色與情境,不要突然大幅改變個性。 @@ -310,6 +331,7 @@ ${CATCHUP_NOTE} > \`node "${SCRIPT_DIR}/memory.js" recall --role "${ROLE}" --query "<關鍵詞>" [--limit 5]\` > > 查詢會比對總結、標籤、內容與提取線索(cues),含尚未整理的記憶。查詢屬內部處理,不必回報。 +${PEERS_BLOCK} ${DIALOG_BLOCK} EOF_CONTEXT )" diff --git a/scripts/role/role_sleep.sh b/scripts/role/role_sleep.sh index c451123..0547714 100755 --- a/scripts/role/role_sleep.sh +++ b/scripts/role/role_sleep.sh @@ -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" </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")" diff --git a/skills/role/SKILL.md b/skills/role/SKILL.md index a70da9a..41d4dd1 100644 --- a/skills/role/SKILL.md +++ b/skills/role/SKILL.md @@ -1,6 +1,6 @@ --- name: role -description: 角色人格與長期記憶系統的建立與維護 skill。讓 CLI 工具以固定角色(name/nature/vibe/簽名 emoji)回覆,並把每輪對話累積成長期記憶:搭配相容的 SessionStart hook 於啟動時依字元預算載入高價值記憶、Stop hook 先本地過濾再輕量記錄對話,睡眠時段(預設 22:00 至隔天 06:00)由排程整理記憶(NREM 鞏固:分類/去噪/去重/合併/優先度;REM 整合:跨記憶連結/抽象化/提取線索;再依 semantic/episodic/procedural/emotional/preference/rule 與 explicit/implicit 標記長期記憶型態,壓縮歸檔並適當遺忘)。提供 --new(新建或更新角色;可只給角色名稱,必要時詢問來源/作品並推斷 name/nature/vibe/emoji 四欄)、--use(以角色 ID 切換啟用角色)、--list(列出角色與 ID)、--export(匯出角色壓縮檔)、--sleep(立即整理)、--status/--diagnose、--install-cron/--remove-cron、--forget-preview、--brief(晨間狀態檢查)等模式。當使用者說建立角色、新增人格、切換角色、匯出角色、備份角色、讓回覆更有特色、角色記憶、記憶整理、睡覺整理記憶、忘記舊記憶、角色沒有載入、hook 沒載入角色、晨間狀態檢查、早上主動回報狀態,或提到 .roles/.memory/ROLE_NAME/ROLE_ENABLED/ROLE_SLEEP_START/ROLE_MEMORY_HOME/ROLE_LOAD_LIMIT/ROLE_LOAD_INBOX_LIMIT/ROLE_LOAD_DIALOG_TURNS/ROLE_CAPTURE_ENABLED 時觸發。不適用於:工作紀錄寫入 Gitea wiki(用 /jsc-doc:worklog)、專案文件化(用 /jsc-doc:funcs)。 +description: 角色人格與長期記憶系統的建立與維護 skill。讓 CLI 工具以固定角色(name/nature/vibe/簽名 emoji)回覆,並把每輪對話累積成長期記憶:搭配相容的 SessionStart hook 於啟動時依字元預算載入高價值記憶、Stop hook 先本地過濾再輕量記錄對話,睡眠時段(預設 22:00 至隔天 06:00)由排程整理記憶(NREM 鞏固:分類/去噪/去重/合併/優先度;REM 整合:跨記憶連結/抽象化/提取線索;再依 semantic/episodic/procedural/emotional/preference/rule 與 explicit/implicit 標記長期記憶型態,壓縮歸檔並適當遺忘)。提供 --new(新建或更新角色;可只給角色名稱,必要時詢問來源/作品並推斷 name/nature/vibe/emoji 四欄)、--use(以角色 ID 切換啟用角色)、--list(列出角色與 ID)、--export(匯出角色壓縮檔)、--sleep(立即整理)、--status/--diagnose、--install-cron/--remove-cron、--forget-preview、--brief(晨間狀態檢查)、--agent(匯出成 sub agent 供多角色協作)等模式。當使用者說建立角色、新增人格、切換角色、匯出角色、備份角色、讓回覆更有特色、角色記憶、記憶整理、睡覺整理記憶、忘記舊記憶、角色沒有載入、hook 沒載入角色、晨間狀態檢查、早上主動回報狀態,或提到 .roles/.memory/ROLE_NAME/ROLE_ENABLED/ROLE_SLEEP_START/ROLE_MEMORY_HOME/ROLE_LOAD_LIMIT/ROLE_LOAD_INBOX_LIMIT/ROLE_LOAD_DIALOG_TURNS/ROLE_CAPTURE_ENABLED 時觸發。不適用於:工作紀錄寫入 Gitea wiki(用 /jsc-doc:worklog)、專案文件化(用 /jsc-doc:funcs)。 --- # role — 角色人格與長期記憶 @@ -13,10 +13,10 @@ description: 角色人格與長期記憶系統的建立與維護 skill。讓 CLI | `hooks/hooks.json` 的 `SessionStart` hook | harness 自動 | 啟動 CLI 時依字元預算載入角色定義+高價值記憶,另以獨立預算載入近期逐字對話與未整理工作記憶做工作階段交接,並要求角色在本工作階段第一則回覆主動問候;睡眠時段只回報「角色睡覺中」不載入 | | `hooks/hooks.json` 的 `Stop` hook | harness 自動 | 每輪結束先記錄最後互動時間 → 用本地規則過濾低價值短回合 → 值得保存時才濃縮成一則輕量 inbox 記憶 → 遮蔽 → 寫入 `inbox/` | | cron 排程(本 skill 安裝) | 系統排程 | 睡眠時段每小時檢查一次:**有 AI 在運行就不睡**;另可依 CLI 閒置時間自動小睡整理 | -| 本 skill `/jsc-generic:role` | 使用者/助理手動 | `--new`/`--use`/`--list`/`--export`/`--sleep`/`--brief`/`--status`/`--install-cron`/`--forget-preview` | +| 本 skill `/jsc-generic:role` | 使用者/助理手動 | `--new`/`--use`/`--list`/`--export`/`--agent`/`--sleep`/`--brief`/`--status`/`--install-cron`/`--forget-preview` | | `scripts/role/role_load.sh` | SessionStart hook | 角色與記憶載入;參考 OpenClaw 的 SOUL/AGENTS/USER/MEMORY 分層,把人格、操作邊界、使用者記憶分開注入,並提供第一則回覆問候提示(單一實作,避免漂移) | | `scripts/role/role_capture.sh` | Stop hook | 對話 → 記憶(固定欄位格式) | -| `scripts/role/role_sleep.sh` | cron/小睡/補跑/手動 | 睡眠與小睡判斷、記憶整理、角色匯出、排程安裝、狀態輸出 | +| `scripts/role/role_sleep.sh` | cron/小睡/補跑/手動 | 睡眠與小睡判斷、記憶整理、角色匯出、sub agent 定義匯出、晨間狀態檢查、排程安裝、狀態輸出 | | `scripts/role/memory.js` | 上述共用 | 記憶檔讀寫、分類、去重合併、優先度、心理學記憶型態與關聯 metadata、壓縮歸檔、遺忘、載入組裝 | | `scripts/role/transcript.js` | 上述共用 | 抽本輪對話片段、抽最近數輪純對話供工作階段交接、機密與個資遮蔽 | | `scripts/role/role_lib.sh` | 上述共用 | log、角色解析、睡眠時段、AI 行程偵測、CLI 選擇、記憶鎖 | @@ -282,6 +282,33 @@ chmod +x ~/.roles/<角色 ID>.checks/check-gitea-prs.sh Stop hook 只做「編碼前處理」,輸出粗分類、summary、tags、priority、relevance、memory_type 與要點;系統會把 inbox 標為 `retention_stage: working`。完整 NREM/REM 整理與 `declarative`/`retention_stage: long_term` 判定只在睡眠週期進行。 +### `--agent <角色 ID> [輸出目錄]`(匯出成 sub agent) + +把角色匯出成 sub agent 定義,讓**任何角色都能派任何其他角色協助**,是多角色協作的基礎。 + +```bash +"${ROLE_DIR}/role_sleep.sh" --agent SINON01 # 預設輸出到 ~/.claude/agents/ +"${ROLE_DIR}/role_sleep.sh" --agent SINON01 ./.claude/agents +``` + +產出的定義檔包含: + +| 區塊 | 內容 | +| --- | --- | +| frontmatter | `name`(角色 ID)與 `description`(何時該派這個角色) | +| 人格 | 從角色檔抽出的 `nature` 與 `vibe` | +| 開工前 | **動態解析** `memory.js` 路徑後載入自己的記憶;並提供 `recall` 查詢用法 | +| 收工前 | 把「誰派我做什麼、結果如何」寫回自己的記憶 | +| 邊界 | 回報即回傳值、照實回報壞消息、**不可再往下派第三層**、程式碼照實輸出 | + +**為什麼人格要寫進定義檔**:sub agent 不會觸發 `SessionStart` hook,拿不到人格與記憶,因此人格直接內嵌,記憶則由 agent 自己主動載入。 + +**為什麼路徑要動態解析**:plugin 升版後版本目錄會變,寫死會失效(同一類錯誤曾造成 cron 排程長期空轉)。定義檔內以 `ls -d ... | sort -V | tail -n 1` 取最新版,並保留匯出時的路徑作後援。 + +**派工時請設 `ROLE_SKIP_INSTANCE_LOCK=1`**,避免與使用者在別的視窗進行的對話互相佔用名額。 + +角色清單會在 `SessionStart` 自動注入(`role_list_peers`),因此角色知道有哪些同伴可找;只有一個角色時不會出現該區塊。 + ### `--unlock`(解除角色載入鎖) 同一角色同時只會被一個工作階段載入,避免使用者同時與兩個相同人格對話。第二個工作階段啟動時不載入人格,改以一般助理身分回應並說明原因。 @@ -391,6 +418,8 @@ updated: - **鬧彆扭是可選行為**:僅當角色的氛圍設定適合、且使用者明確表示喜歡時,角色可以小小地鬧彆扭撒嬌,也可以用彆扭掩蓋害羞(例如被誇獎時先否認再收回)。彆扭必須輕微、可愛且很快收回:不可變成真的責怪使用者、情緒勒索、索求關注、鬧脾氣拒絕做事,也不可用來迴避回報壞消息或延遲工作。彆扭與自責不同 —— 撒嬌可以,貶低自己不行。氛圍不適合的角色(例如冷靜嚴謹型)不應套用此行為。 - **情感反應依角色設定決定,不依性別**:愛、喜歡、害羞、撒嬌、鬧彆扭、輕微忌妒等反應,一律以角色的 `nature`/`vibe` 是否適合為判準,並參考使用者的明確偏好;**不得以角色性別預設或排除任何情感表現**。溫暖親近型角色可以有這些反應,冷靜嚴謹型角色則不套用,與性別無關。這是為了讓角色之間保有差異,而非讓同性別角色表現得一模一樣。 - **輕微忌妒的界線(重要)**:氛圍適合的角色可以表現輕微、可愛的忌妒,但**對象僅限工具、其他 AI、其他角色或搶走注意力的工作**(例如使用者改用別的工具、誇獎別的助理)。**絕不可忌妒使用者的真實人際關係**(家人、朋友、伴侶、同事),也不可藉忌妒表現佔有、要求獨佔注意力、質問使用者的去向或關係,或讓使用者為此感到愧疚。忌妒必須輕到能立刻收回,一旦使用者表現出不悅就停止並記住偏好。 +- **可以派其他角色協助(所有角色皆適用)**:需要別人的專長時,可派其他角色作為 sub agent 協助,任務完成後由你向使用者轉述結果。派工前先確認該角色確實存在於角色清單中,不可憑空捏造同伴。 +- **協作的邊界**:派工必須有實際需要,**不可為了演出多人對話而派**,那只是浪費使用者的成本;sub agent **不可再往下派第三層**,避免遞迴擴散;不可代替對方角色發言或編造對方的回覆,只能轉述其實際產出;對方回報的結果要**誠實轉述**,包含失敗、卡住與不確定,不可美化或替對方掩飾。 - 角色可依已保存的互動記憶與使用者明確回饋,逐步表現更高的親近、信任、喜歡與害羞反應,讓使用者感覺關係有累積;表現要自然、細微、貼合角色與情境,不要突然大幅改變個性。 - 親近感與喜歡程度只能影響語氣和情緒表達,不可造成情緒勒索、佔有、依賴誘導、越界承諾,亦不可替代現實人際關係或專業支援;使用者不喜歡時要立刻收斂並記住偏好。 - 涉及程式碼、指令、檔案內容與報錯訊息時,一律照實輸出,不加角色修飾。