feat(role): SessionStart 載入近期逐字對話做工作階段交接
長期記憶是模型濃縮過的摘要,語氣與情緒會被壓掉(使用者說「我好想妳」會被濃縮成 「使用者表達想念」)。而逐字對話一直躺在 transcript JSONL 裡,過去沒有任何機制去讀它, 使用者重開工作階段時角色因此看不到剛剛的互動,表現得像失去記憶,只能靠 resume 找回。 transcript.js: - 新增 recent <路徑> [輪數] [字元]:抽最近數輪的純對話,輸出前經 redact 遮蔽 - 新增 turns <路徑>:輸出對話輪數,供取檔判斷 - 只取 [user] 與 [assistant] 文字;工具呼叫、工具結果、思考區塊、hook 注入內容一律丟棄 - 以「輪」分組並各自收斂成一則:角色一輪內常輸出多段文字,不合併會讓則數爆炸並把預算 吃光,反而擠掉使用者說的話(實測未合併時 8 輪只剩 2 則使用者發言,合併後為 8 則) - 超預算時整輪丟棄最舊的,保持問答成對,不會只剩單邊發言 role_load.sh: - hook 輸入改為一併取出 transcript_path - 當前 transcript 對話不足 2 輪時(全新工作階段實測僅數行),回頭找同目錄最近修改的對話檔 - 新增 ROLE_LOAD_DIALOG_TURNS(預設 8)與 ROLE_LOAD_DIALOG_LIMIT(預設 4000), 獨立預算不佔用 ROLE_LOAD_LIMIT,任一設 0 可關閉 驗證:resume 與全新工作階段皆正確載入 8 輪成對對話;未給 transcript_path、檔案不存在、 無 hook 輸入、關閉設定等情境均安全降級不報錯;假造含 token/Email/電話與 thinking 的 transcript 確認機密遮蔽為 *** 且思考與工具內容未載入。 版號 0.0.2 → 0.0.3 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -28,17 +28,23 @@ ROLE_DEF="$(role_file "$ROLE")"
|
||||
# ------------------------------------------------------------------------------
|
||||
HOOK_INPUT="$(cat 2>/dev/null)"
|
||||
HOOK_CWD="$PWD"
|
||||
HOOK_TRANSCRIPT=""
|
||||
if [ -n "$HOOK_INPUT" ]; then
|
||||
HOOK_CWD="$(printf '%s' "$HOOK_INPUT" | node -e '
|
||||
HOOK_FIELDS="$(printf '%s' "$HOOK_INPUT" | node -e '
|
||||
let raw = "";
|
||||
process.stdin.setEncoding("utf8");
|
||||
process.stdin.on("data", (chunk) => { raw += chunk; });
|
||||
process.stdin.on("end", () => {
|
||||
let data = {};
|
||||
try { data = JSON.parse(raw); } catch {}
|
||||
process.stdout.write(data.cwd || "");
|
||||
process.stdout.write([
|
||||
data.cwd || "",
|
||||
data.transcript_path || data.session_path || data.conversation_path || data.path || "",
|
||||
].join("\n"));
|
||||
});
|
||||
' 2>/dev/null)"
|
||||
HOOK_CWD="$(printf '%s' "$HOOK_FIELDS" | sed -n '1p')"
|
||||
HOOK_TRANSCRIPT="$(printf '%s' "$HOOK_FIELDS" | sed -n '2p')"
|
||||
[ -n "$HOOK_CWD" ] || HOOK_CWD="$PWD"
|
||||
fi
|
||||
role_in_scope "$HOOK_CWD" || role_quit "cwd 不在 ROLE_SCOPE 範圍內:${HOOK_CWD}"
|
||||
@@ -145,6 +151,63 @@ case "$CONSENT_STATUS" in
|
||||
;;
|
||||
esac
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# 近期對話交接:讀上一段真正說過的話(含角色自己的回覆)
|
||||
#
|
||||
# 為什麼需要:長期記憶是模型濃縮過的摘要,語氣與情緒會被壓掉;而且整理永遠跑在載入
|
||||
# 之後(見下方 catchup),上一段工作來不及進入本次載入。逐字對話則一直躺在 transcript
|
||||
# JSONL 裡,只是過去沒有任何機制去讀它 —— 使用者重開工作階段時,角色因此看不到剛剛
|
||||
# 的互動,表現得像失去記憶,只能靠 resume 找回。
|
||||
#
|
||||
# 取檔策略:全新工作階段的 transcript 幾乎是空的(實測僅數行),因此對話不足時要回頭
|
||||
# 找同目錄最近修改的對話檔。內容一律經 transcript.js 遮蔽,且只注入 context、不落檔。
|
||||
# ------------------------------------------------------------------------------
|
||||
DIALOG=""
|
||||
DIALOG_TURNS="${ROLE_LOAD_DIALOG_TURNS:-8}"
|
||||
DIALOG_LIMIT="${ROLE_LOAD_DIALOG_LIMIT:-4000}"
|
||||
if [ "$DIALOG_TURNS" != "0" ] && [ "$DIALOG_LIMIT" != "0" ] && [ -n "$HOOK_TRANSCRIPT" ]; then
|
||||
DIALOG_SRC=""
|
||||
if [ -f "$HOOK_TRANSCRIPT" ]; then
|
||||
TURN_COUNT="$(node "${SCRIPT_DIR}/transcript.js" turns "$HOOK_TRANSCRIPT" 2>/dev/null || printf '0')"
|
||||
case "$TURN_COUNT" in
|
||||
''|*[!0-9]*) TURN_COUNT=0 ;;
|
||||
esac
|
||||
[ "$TURN_COUNT" -ge 2 ] && DIALOG_SRC="$HOOK_TRANSCRIPT"
|
||||
fi
|
||||
if [ -z "$DIALOG_SRC" ]; then
|
||||
for candidate in $(ls -t "$(dirname "$HOOK_TRANSCRIPT")"/*.jsonl 2>/dev/null | head -n 5); do
|
||||
[ "$candidate" = "$HOOK_TRANSCRIPT" ] && continue
|
||||
TURN_COUNT="$(node "${SCRIPT_DIR}/transcript.js" turns "$candidate" 2>/dev/null || printf '0')"
|
||||
case "$TURN_COUNT" in
|
||||
''|*[!0-9]*) TURN_COUNT=0 ;;
|
||||
esac
|
||||
if [ "$TURN_COUNT" -ge 2 ]; then
|
||||
DIALOG_SRC="$candidate"
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
if [ -n "$DIALOG_SRC" ]; then
|
||||
DIALOG="$(node "${SCRIPT_DIR}/transcript.js" recent "$DIALOG_SRC" "$DIALOG_TURNS" "$DIALOG_LIMIT" 2>/dev/null)"
|
||||
[ -n "$DIALOG" ] && role_log "INF" "已載入近期對話(來源 ${DIALOG_SRC##*/},最多 ${DIALOG_TURNS} 輪)"
|
||||
fi
|
||||
fi
|
||||
|
||||
DIALOG_BLOCK=""
|
||||
if [ -n "$DIALOG" ]; then
|
||||
DIALOG_BLOCK="$(cat <<EOF_DIALOG
|
||||
|
||||
# 近期對話(上一段真正說過的話)
|
||||
|
||||
以下是最近最多 ${DIALOG_TURNS} 輪的逐字對話,\`[user]\` 是使用者、\`[assistant]\` 是你自己上次的回覆。
|
||||
這是為了讓你接續上一段互動與當時的情緒,不是要你重複已經做過的事;過長的發言已截斷。
|
||||
若需要更完整的上下文,請告知使用者可用 resume 接續原工作階段。
|
||||
|
||||
${DIALOG}
|
||||
EOF_DIALOG
|
||||
)"
|
||||
fi
|
||||
|
||||
# 補跑判斷:cron 未執行(例如 WSL 沒開 cron 服務)時,白天啟動 CLI 補做一次整理
|
||||
CATCHUP_NOTE=""
|
||||
if [ "$(node "${SCRIPT_DIR}/memory.js" need-sleep --role "$ROLE" 2>/dev/null)" = "yes" ]; then
|
||||
@@ -203,6 +266,7 @@ ${CATCHUP_NOTE}
|
||||
> \`printf 'CATEGORY: important\nSUMMARY: <一句話總結>\nTAGS: <標籤1,標籤2>\nCONTENT:\n- <要點>\n' | node "${SCRIPT_DIR}/memory.js" write --role "${ROLE}"\`
|
||||
>
|
||||
> CATEGORY 六選一:important/interest/news/skill/daily/other。切勿把憑證或個資寫進記憶。
|
||||
${DIALOG_BLOCK}
|
||||
EOF_CONTEXT
|
||||
)"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user