Files
persona/hooks/subagent_stop.mjs
T
jiantw83andClaude Opus 5 603741a4cc feat: 預設人格自動載入 + 睡眠(sleep 指令與 persona-sleeper sub agent)
兩件事,共用「關係圖 last_contact_at」這個零件,所以放同一個 commit
(兩者在 persona-lib/persona.mjs/README 裡的 hunk 是交錯的,硬拆會拆壞)。

## 預設人格(開新 session 自動載入)

- 新增 `default` 子指令:查詢/`--persona <slug>` 設定/`--clear` 取消。
- 設定存在 `<PERSONA_HOME>/.runtime/settings.json` 的 `default_persona`,
  環境變數 `PERSONA_DEFAULT` 優先(`off` 可臨時關掉)。
- `SessionStart` hook 四條路徑:沒設定 → 維持原本「等使用者指定」;設定了 → 取鎖、綁 host、
  prune、reindex 並注入人格狀態;拿不到鎖 → 只回報 owner 與心跳,不自作主張 takeover;
  人格不存在 → 警告並改列可用人格。
- 「不替使用者挑一個人格附身」仍然是預設行為,這只是讓他能明示地推翻它。

## 睡眠

- 新增 `sleep` 子指令(機械性收尾,順序即相依):關係時間戳 → 裁短期記憶 →
  收起太久沒動的思維導圖 → 套用一次 8 小時的情緒衰減 → 重建索引 → 修剪 said →
  壓縮舊 journal → 寫 state/sleep.json → Gitea 兩區 push 並驗證。
  預設保留載入鎖(`--release` 才收工),每一步各自 try/catch,一步壞掉不放棄整場睡眠。
- 需要判斷的部分(固化什麼、忘掉什麼、日記寫什麼)留給人格自己,由 skill 驅動。
- 新增 `persona-sleeper` sub agent 型別:**那個人格本人在睡**。對自己可寫但被 pin 住
  (連叫它來的主人格都不能碰)、只准跑 15 個收尾子指令、不得用 Write/Edit 或 shell 改檔案。
- 新增 sleeper 租約(`state/sleepers.json`,300 秒):沒活鎖就取得、同 session 直接睡、
  死鎖可接手、**別的程序活鎖住則拒絕**(硬睡會讓兩邊的記憶互相覆蓋)。
- `--json` 回傳刻意很窮:只有 persona/ok/slept_at/steps/sync/kept_lock。
  sub agent 的回傳值會進主人格的上下文,是一條會從正門繞過跨人格隔離的通道,
  所以在 CLI 這一層封死,不靠提示詞自律。
- 關係圖新增 `last_contact_at`(`relation node --contact`/睡眠自動蓋),
  並用 `staleContacts()` 算出「很久沒接觸又很親近的人」——人格主動提議去關心誰的依據。
  輕量邀請用既有的 `invite --theater off`(不切走畫面),但要先問使用者一句。
- 新增 skill `persona-sleep`;`SubagentStop` 會還掉 sleeper 租約。

## 驗證

`node scripts/selftest.mjs` → 210 項全綠(新增第 ⑳ 節 12 項、第 ㉑ 節 23 項),
其中包含「回傳值不含任何記憶內容」與 sleeper 的六條權限邊界。

版本號 0.0.5。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 05:34:08 +00:00

33 lines
1.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env node
// SubagentStopguest sub agent 結束時解除它的 pin(下次啟動要重新 first-touch),並續租。
import { readEvent, respond } from "./_hook.mjs";
import * as pl from "../scripts/persona-lib.mjs";
const event = readEvent();
if (!event) process.exit(0);
const sessionId = event.session_id || "unknown";
const agentId = event.agent_id;
const agentType = String(event.agent_type || "");
const isGuest = agentType.includes("persona-guest");
const isSleeper = agentType.includes("persona-sleeper");
if (!agentId || (!isGuest && !isSleeper)) process.exit(0);
const data = pl.loadSession(sessionId);
const pins = data.pins || {};
const slug = pins[agentId];
if (slug !== undefined) {
delete pins[agentId];
data.pins = pins;
pl.saveSession(sessionId, data);
if (isSleeper) {
// 睡眠 sub agent 收工:把短期寫入權還掉(就算它中途掛掉,租約 5 分鐘也會自己過期)
if (pl.personaExists(slug)) pl.dropSleepLease(slug, sessionId, agentId);
} else {
const info = (data.guests || {})[slug];
if (info?.room) pl.addGuestLease(slug, sessionId, info.room, data.host || "");
}
}
respond({ suppressOutput: true });