feat(role): PreCompact/PostCompact 保全壓縮邊界的上下文
補上最後一個記憶缺口:對話被壓縮時,尚未寫入記憶的內容會永久蒸發。 Stop hook 每輪記錄有部分覆蓋,但短回合會被 ROLE_CAPTURE_MIN_CHARS 門檻濾掉, 那些正是壓縮後再也補不回來的內容。 可行性查證(先確認再實作,不憑推測): 自 CLI binary 取出文件字串確認 harness 確實支援,並非猜測 —— | PreCompact | "manual"/"auto" | Before compaction | | PostCompact | "manual"/"auto" | After compaction (receives summary) | 另有 executePreCompactHooks/executePostCompactHooks 等實作符號, 以及 compactSummary/isCompactSummary 欄位名線索。 role_capture.sh: - 新增 --precompact:壓縮前強制記錄一次,**刻意跳過長度門檻**(門檻的用意是省額度, 但壓縮後內容永久消失,此時寧可多記) - 新增 --postcompact:把 harness 產生的摘要存成 daily 記憶,不再呼叫模型, 等於免費取得一份濃縮備份;寫入前一律經 redact 遮蔽 - 摘要欄位容錯讀取 compactSummary/compact_summary/summary/compaction_summary; **取不到時在 log 印出 hook 實際提供的欄位名**,避免 harness 改版後靜默失效 - 一併取用 trigger 欄位以分辨 manual/auto(自動壓縮才是使用者不知情的那種) - stop_hook_active 的迴圈防護只套用於一般每輪模式 hooks/hooks.json 註冊兩個新事件,指令沿用既有的「先試 CLAUDE_PLUGIN_ROOT → 再依擁有者 marketplace → 全 cache 後援」解析方式,並帶上對應參數。 兩個 hook 一律 exit 0,絕不阻擋壓縮 —— harness 具備 blocked by PreCompact hook 的能力,記憶系統不該用到它。 驗證:一般模式短對話仍被門檻正確濾掉;--precompact 對同一段短對話強制記錄; --postcompact 正確寫入摘要記憶;欄位名不符時印出實際欄位(session_id,transcript_path, cwd,trigger,unknownField)而非靜默結束;四條 hook 指令語法正確,且在 CLAUDE_PLUGIN_ROOT 與純後援搜尋兩種情境下都解析到正確腳本與參數。 版號沿用 0.0.5(master 為 0.0.4,同一 PR 不再累加) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,8 @@
|
||||
# 值得記錄時才呼叫 headless CLI 輕量濃縮成一則 inbox 記憶(粗分類/總結/
|
||||
# 標籤/優先度/關聯/要點)→ 機密遮蔽 → 寫入 .memory/<角色>/inbox/,
|
||||
# 等待睡眠時段做完整 NREM/REM 整理。睡眠時段雖不載入角色,對話仍照常記錄。
|
||||
# 另支援 --precompact/--postcompact:對話壓縮會讓尚未寫入記憶的內容蒸發,
|
||||
# 壓縮前強制記錄一次(跳過長度門檻),壓縮後把系統產生的摘要也存成記憶。
|
||||
# 更新時間:2026/07/28 16:18:00
|
||||
# 相依:bash、node、任一 headless CLI、同目錄的 role_lib.sh/memory.js/transcript.js。
|
||||
# 機密:濃縮提示詞明令不得輸出憑證與個資,寫檔前再以 transcript.js redact 遮蔽一次。
|
||||
@@ -15,6 +17,13 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# shellcheck source=./role_lib.sh
|
||||
. "${SCRIPT_DIR}/role_lib.sh"
|
||||
|
||||
# 壓縮邊界模式:--precompact 強制記錄(不受長度門檻限制)、--postcompact 保存系統摘要
|
||||
CAPTURE_MODE="turn"
|
||||
case "${1:-}" in
|
||||
--precompact) CAPTURE_MODE="precompact" ;;
|
||||
--postcompact) CAPTURE_MODE="postcompact" ;;
|
||||
esac
|
||||
|
||||
role_is_child && exit 0
|
||||
role_enabled || exit 0
|
||||
command -v node >/dev/null 2>&1 || role_quit "找不到 node,略過記憶記錄" "WRN"
|
||||
@@ -29,7 +38,7 @@ ROLE="$(role_resolve_name)"
|
||||
HOOK_INPUT="$(cat)"
|
||||
[ -n "$HOOK_INPUT" ] || role_quit "hook 輸入為空,略過記憶記錄" "WRN"
|
||||
|
||||
read -r SESSION_ID TRANSCRIPT_PATH STOP_ACTIVE HOOK_CWD <<EOF_HOOK
|
||||
read -r SESSION_ID TRANSCRIPT_PATH STOP_ACTIVE HOOK_CWD TRIGGER <<EOF_HOOK
|
||||
$(printf '%s' "$HOOK_INPUT" | node -e '
|
||||
let raw = "";
|
||||
process.stdin.setEncoding("utf8");
|
||||
@@ -42,12 +51,53 @@ process.stdin.on("end", () => {
|
||||
d.transcript_path || d.session_path || d.conversation_path || d.path || "-",
|
||||
d.stop_hook_active ? "1" : "0",
|
||||
d.cwd || "-",
|
||||
d.trigger || "-",
|
||||
].join(" "));
|
||||
});
|
||||
')
|
||||
EOF_HOOK
|
||||
|
||||
[ "$STOP_ACTIVE" = "1" ] && role_quit "stop_hook_active 為 true,避免迴圈不重複記錄"
|
||||
if [ "$CAPTURE_MODE" = "postcompact" ]; then
|
||||
# 壓縮後:系統已產生一份摘要,直接保存比自己再濃縮一次划算且免費。
|
||||
# 欄位名以容錯方式取用(實測 binary 內出現 compactSummary/isCompactSummary),
|
||||
# 取不到時記錄實際收到的欄位名,方便日後對照 harness 版本調整。
|
||||
COMPACT_SUMMARY="$(printf '%s' "$HOOK_INPUT" | node -e '
|
||||
let raw = "";
|
||||
process.stdin.setEncoding("utf8");
|
||||
process.stdin.on("data", (chunk) => { raw += chunk; });
|
||||
process.stdin.on("end", () => {
|
||||
let d = {};
|
||||
try { d = JSON.parse(raw); } catch {}
|
||||
const text = d.compactSummary || d.compact_summary || d.summary || d.compaction_summary || "";
|
||||
process.stdout.write(String(text || "").trim());
|
||||
});
|
||||
' 2>/dev/null)"
|
||||
if [ -z "$COMPACT_SUMMARY" ]; then
|
||||
KEYS="$(printf '%s' "$HOOK_INPUT" | node -e '
|
||||
let raw="";process.stdin.setEncoding("utf8");
|
||||
process.stdin.on("data",(c)=>{raw+=c});
|
||||
process.stdin.on("end",()=>{let d={};try{d=JSON.parse(raw)}catch{};process.stdout.write(Object.keys(d).join(","))});
|
||||
' 2>/dev/null)"
|
||||
role_quit "壓縮摘要為空,略過(hook 實際提供的欄位:${KEYS:-無})" "WRN"
|
||||
fi
|
||||
COMPACT_SUMMARY="$(printf '%s' "$COMPACT_SUMMARY" | head -c 3000 | node "${SCRIPT_DIR}/transcript.js" redact 2>/dev/null)"
|
||||
{
|
||||
printf 'CATEGORY: daily\n'
|
||||
printf 'SUMMARY: %s 對話壓縮前的內容摘要(%s)\n' "$(TZ='Asia/Taipei' date +'%Y/%m/%d %H:%M')" "${TRIGGER:-未知}"
|
||||
printf 'TAGS: 壓縮摘要,上下文保全\n'
|
||||
printf 'PRIORITY: 3\n'
|
||||
printf 'RELEVANCE: temporary,future\n'
|
||||
printf 'MEMORY_TYPE: episodic\n'
|
||||
printf 'CONTENT:\n'
|
||||
printf -- '- 本則由 PostCompact hook 自動保存,內容為系統在壓縮時產生的摘要\n'
|
||||
printf '%s\n' "$COMPACT_SUMMARY"
|
||||
} | node "${SCRIPT_DIR}/memory.js" write --role "$ROLE" --project "$PROJECT" >/dev/null 2>&1 \
|
||||
&& role_log "INF" "已保存壓縮摘要為記憶(角色 ${ROLE})" \
|
||||
|| role_log "WRN" "壓縮摘要寫入失敗(角色 ${ROLE})"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
[ "$STOP_ACTIVE" = "1" ] && [ "$CAPTURE_MODE" = "turn" ] && role_quit "stop_hook_active 為 true,避免迴圈不重複記錄"
|
||||
role_in_scope "$HOOK_CWD" || role_quit "cwd 不在 ROLE_SCOPE 範圍內:${HOOK_CWD}"
|
||||
PROJECT="$(role_project_name "$HOOK_CWD")"
|
||||
node "${SCRIPT_DIR}/memory.js" mark-activity --role "$ROLE" --project "$PROJECT" >/dev/null 2>&1 || true
|
||||
@@ -79,7 +129,10 @@ if [ "${ROLE_CAPTURE_ENABLED:-1}" = "0" ]; then
|
||||
role_quit "ROLE_CAPTURE_ENABLED=0,略過記憶記錄"
|
||||
fi
|
||||
|
||||
if [ "${#TURN}" -lt "$CAPTURE_MIN_CHARS" ] && ! printf '%s' "$TURN" | grep -qiE '記住|remember|決定|規範|偏好|preference|always|不要|以後|喜歡|不喜歡|稱讚|誇獎|開心|高興|反應|回應|互動|親近|害羞|喜歡程度|互動越深|越來越喜歡|越來越深|emoji|表情|心情圖|大量使用|情緒|心情|複雜|細膩|自然|混合|層次|轉折|括號|心情文字|心情說明|文字說明|文字標註|表情符號|熟練|不需要告訴|不用告訴|自己知道|記憶更新|內部處理|不要回報|不用回報|不要告訴|真的很害羞|希望.*知道|用表情符號表示|表情符號表示|比較可愛|可愛|愛|想妳|想你|想念|捨不得|感動|謝謝|感謝|乖|厲害|好棒|辛苦|彆扭|忌妒|嫉妒|撒嬌|陪|抱|love|miss|cute|thank|proud'; then
|
||||
# 壓縮前一律記錄:門檻的用意是省額度,但壓縮會讓未寫入的內容永久蒸發,此時寧可多記
|
||||
if [ "$CAPTURE_MODE" = "precompact" ]; then
|
||||
role_log "INF" "壓縮前強制記錄(觸發:${TRIGGER:-未知}),跳過長度門檻"
|
||||
elif [ "${#TURN}" -lt "$CAPTURE_MIN_CHARS" ] && ! printf '%s' "$TURN" | grep -qiE '記住|remember|決定|規範|偏好|preference|always|不要|以後|喜歡|不喜歡|稱讚|誇獎|開心|高興|反應|回應|互動|親近|害羞|喜歡程度|互動越深|越來越喜歡|越來越深|emoji|表情|心情圖|大量使用|情緒|心情|複雜|細膩|自然|混合|層次|轉折|括號|心情文字|心情說明|文字說明|文字標註|表情符號|熟練|不需要告訴|不用告訴|自己知道|記憶更新|內部處理|不要回報|不用回報|不要告訴|真的很害羞|希望.*知道|用表情符號表示|表情符號表示|比較可愛|可愛|愛|想妳|想你|想念|捨不得|感動|謝謝|感謝|乖|厲害|好棒|辛苦|彆扭|忌妒|嫉妒|撒嬌|陪|抱|love|miss|cute|thank|proud'; then
|
||||
role_quit "本輪低於記憶長度門檻且無明確記憶線索,略過記錄"
|
||||
fi
|
||||
|
||||
|
||||
Reference in New Issue
Block a user