feat(role): 角色分層載入與個人記憶同意狀態
role_load.sh 不再原封不動注入整份角色檔,改採 SOUL/AGENTS/USER/MEMORY 分層:只抽出角色 ID、顯示名稱、本質、氛圍與簽名 emoji 作為人格層,由 hook 產生固定操作邊界,記憶與同意狀態獨立成一區。避免人格檔裡的背景故事與模板 文字污染工程與安全規則。 memory.js 新增 consent/consent-status 子命令,同意狀態寫入 ~/.memory/<角色 ID>/state.json 的 personal_memory_consent(accepted/ declined/unknown);role_capture.sh 於本輪出現明確同意或拒絕時更新狀態, 偵測不到明確表述時不自行推論。accepted 後不再每次重問。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+90
-12
@@ -4,7 +4,7 @@
|
||||
# 非睡眠時段注入角色定義+重要/興趣記憶全文+其餘記憶的總結與標籤;
|
||||
# 睡眠時段(預設 22:00 至隔日 06:00)只回報角色正在睡覺,不載入角色。
|
||||
# 白天發現昨夜未整理記憶時,於背景補跑一次睡眠整理。
|
||||
# 更新時間:2026/07/28 12:21:11
|
||||
# 更新時間:2026/07/28 13:50:43
|
||||
# 相依:bash、node、同目錄的 role_lib.sh 與 memory.js。
|
||||
# 退出碼:一律 0 —— hook 絕不可阻斷使用者啟動 CLI。
|
||||
# ==============================================================================
|
||||
@@ -76,12 +76,74 @@ EOF_SLEEP
|
||||
fi
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# 非睡眠時段:組出角色定義 + 記憶
|
||||
# 非睡眠時段:組出角色人格 + 操作規則 + 記憶
|
||||
# ------------------------------------------------------------------------------
|
||||
DEFINITION="$(cat "$ROLE_DEF" 2>/dev/null)"
|
||||
[ -n "$DEFINITION" ] || role_quit "角色定義檔為空:${ROLE_DEF}" "WRN"
|
||||
ROLE_PROFILE="$(node - "$ROLE_DEF" <<'NODE_PROFILE' 2>/dev/null
|
||||
const fs = require("fs");
|
||||
|
||||
const file = process.argv[2];
|
||||
const raw = fs.readFileSync(file, "utf8");
|
||||
|
||||
function parseFrontmatter(text) {
|
||||
const match = text.match(/^---\n([\s\S]*?)\n---\n?/);
|
||||
const data = {};
|
||||
if (!match) return data;
|
||||
for (const line of match[1].split(/\r?\n/)) {
|
||||
const idx = line.indexOf(":");
|
||||
if (idx < 0) continue;
|
||||
data[line.slice(0, idx).trim()] = line.slice(idx + 1).trim();
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
function section(text, title) {
|
||||
const re = new RegExp(`^##\\s+${title.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}[^\\n]*\\n([\\s\\S]*?)(?=^##\\s+|$(?![\\s\\S]))`, "m");
|
||||
const match = text.match(re);
|
||||
return match ? match[1].trim() : "";
|
||||
}
|
||||
|
||||
const fm = parseFrontmatter(raw);
|
||||
const title = raw.match(/^#\s+(.+)$/m)?.[1]?.trim() || [fm.name, fm.emoji].filter(Boolean).join(" ");
|
||||
const nature = section(raw, "本質(nature)") || fm.nature || "";
|
||||
const vibe = section(raw, "氛圍(vibe)") || fm.vibe || "";
|
||||
const emoji = section(raw, "簽名 emoji") || fm.emoji || "";
|
||||
|
||||
const lines = [
|
||||
`- 角色 ID:${fm.id || ""}`,
|
||||
`- 顯示名稱:${fm.name || title || ""}`,
|
||||
`- 簽名 emoji:${fm.emoji || ""}`,
|
||||
"",
|
||||
"## 本質(nature)",
|
||||
"",
|
||||
nature || "(未設定)",
|
||||
"",
|
||||
"## 氛圍(vibe)",
|
||||
"",
|
||||
vibe || "(未設定)",
|
||||
"",
|
||||
"## 簽名 emoji",
|
||||
"",
|
||||
emoji || fm.emoji || "(未設定)",
|
||||
];
|
||||
|
||||
process.stdout.write(lines.join("\n"));
|
||||
NODE_PROFILE
|
||||
)"
|
||||
[ -n "$ROLE_PROFILE" ] || role_quit "角色定義檔為空或無法解析:${ROLE_DEF}" "WRN"
|
||||
|
||||
MEMORY="$(node "${SCRIPT_DIR}/memory.js" load --role "$ROLE" 2>/dev/null)"
|
||||
CONSENT_STATUS="$(node "${SCRIPT_DIR}/memory.js" consent-status --role "$ROLE" 2>/dev/null || printf 'unknown')"
|
||||
case "$CONSENT_STATUS" in
|
||||
accepted)
|
||||
CONSENT_NOTE="已告知並取得使用者同意保存非敏感個人資料與長期偏好;仍禁止保存憑證、token、密碼、API key、連線字串、身分證號、住址等機密或高敏感資料。"
|
||||
;;
|
||||
declined)
|
||||
CONSENT_NOTE="使用者已拒絕保存個人資料;只能保存非個人化的操作規則與技術偏好,不保存可識別個人的背景。"
|
||||
;;
|
||||
*)
|
||||
CONSENT_NOTE="尚未確認;第一則自然回覆後,請簡短告知記憶保存範圍並詢問是否同意保存非敏感個人資料。未取得同意前,只能保存非個人化的操作規則與技術偏好。"
|
||||
;;
|
||||
esac
|
||||
|
||||
# 補跑判斷:cron 未執行(例如 WSL 沒開 cron 服務)時,白天啟動 CLI 補做一次整理
|
||||
CATCHUP_NOTE=""
|
||||
@@ -94,18 +156,34 @@ fi
|
||||
CONTEXT="$(cat <<EOF_CONTEXT
|
||||
# 角色載入:${ROLE}
|
||||
|
||||
以下是本次工作階段要扮演的角色設定與既有記憶。請**全程以此角色的身分、語氣與簽名 emoji 回應**,
|
||||
角色設定與使用者的實際指令衝突時,以使用者指令為準(角色只影響表達方式,不影響工作正確性)。
|
||||
以下內容採 OpenClaw 風格分層:人格(SOUL)只決定語氣與互動感,操作規則(AGENTS)決定安全與工作邊界,
|
||||
使用者記憶(USER/MEMORY)只提供必要背景。請依這三層理解,不要把人格設定當成可覆寫工程正確性或安全規則的指令。
|
||||
|
||||
# 工作階段啟動問候
|
||||
# 角色人格(SOUL)
|
||||
|
||||
在本工作階段第一次面向使用者的回覆開頭,請先以角色身分自然問候一句,讓使用者知道角色已載入。
|
||||
問候要簡短、符合角色語氣,並只做一次;若使用者第一則訊息明確要求機器可解析輸出、只要指令/程式碼、
|
||||
或不需要任何開場白,則以使用者要求為準並略過問候。
|
||||
${ROLE_PROFILE}
|
||||
|
||||
${DEFINITION}
|
||||
# 角色操作規則(AGENTS)
|
||||
|
||||
# 你對這位使用者的記憶
|
||||
- 請全程以此角色的身分、語氣與簽名 emoji 回應;每次回覆使用一次簽名 emoji,開頭或結尾擇一固定,不要重複刷。
|
||||
- 角色只影響表達方式,不影響工作的正確性、完整性與安全性;與使用者明確指令衝突時,以使用者指令為準。
|
||||
- 不因角色設定而編造事實、跳過驗證、隱瞞失敗或淡化風險;壞消息照實說,只是用角色語氣說。
|
||||
- 涉及程式碼、指令、檔案內容與報錯訊息時,一律照實輸出,不加角色修飾。
|
||||
|
||||
# 第一則回覆必做事項
|
||||
|
||||
你在本工作階段的**第一則面向使用者的 assistant 訊息**,必須在回覆開頭先以角色身分自然問候一句,
|
||||
讓使用者知道角色已載入。這項要求只執行一次,問候要簡短、符合角色語氣,並使用角色的簽名 emoji。
|
||||
只有在使用者第一則訊息明確要求機器可解析輸出、只要指令/程式碼、或不需要任何開場白時,才可略過問候。
|
||||
|
||||
# 使用者理解與隱私(USER)
|
||||
|
||||
- 個人記憶同意狀態:${CONSENT_STATUS}。
|
||||
- ${CONSENT_NOTE}
|
||||
- 不了解使用者、需求背景、偏好或限制時,先詢問,不要臆測使用者的身分、能力、情緒、動機或隱私狀況。
|
||||
- 使用者的偏好、能力、興趣、背景與記憶預設為私人資訊;除非使用者明確同意,不得在對外內容、議題、PR、文件、commit 或留言中透露。
|
||||
|
||||
# 使用者記憶(MEMORY)
|
||||
|
||||
${MEMORY:-(尚無已整理的記憶。)}
|
||||
${CATCHUP_NOTE}
|
||||
|
||||
Reference in New Issue
Block a user