sleep: 判斷式收尾與 sleeper 隔離
- sleeper 帶 --as-sleeper 就能在沒有 exclusive 鎖時做收尾 - sleeper 被 pin 在自己的人格上,讀寫其他人格(含主人格)一律攔下 - sleeper 只能走 CLI,不得用 Write 或 shell 重導向改人格檔案 - 主人格載入別的人格時,sleeper 仍以自己的 pin 為準 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+29
-3
@@ -22,6 +22,8 @@ const TEMPLATE_DIR = path.join(HERE, "..", "skills", "persona-create", "template
|
||||
const SELF = path.join(HERE, "persona.mjs");
|
||||
|
||||
let QUIET = false;
|
||||
// 本次呼叫的旗標(權限檢查要看 --as-sleeper/--agent-id,不想改十幾個 requireOwner 呼叫點)
|
||||
let CURRENT_FLAGS = {};
|
||||
|
||||
function die(message, code = 1) {
|
||||
process.stderr.write(`✖ ${message}\n`);
|
||||
@@ -49,7 +51,7 @@ function emit(payload, asJson, lines) {
|
||||
// --------------------------------------------------------------------------- //
|
||||
|
||||
const FLAGS = new Set([
|
||||
"json", "quiet", "force", "takeover", "as-guest", "on", "off", "with-meta", "all",
|
||||
"json", "quiet", "force", "takeover", "as-guest", "as-sleeper", "on", "off", "with-meta", "all",
|
||||
"with-journal", "gzip", "record", "load", "allow-repeat", "clear", "release", "keep-lock", "contact",
|
||||
"if-due", "no-gitea", "public", "rename", "from-source",
|
||||
]);
|
||||
@@ -110,11 +112,30 @@ function requireSession(flags) {
|
||||
// 權限檢查
|
||||
// --------------------------------------------------------------------------- //
|
||||
|
||||
/** 呼叫者必須是這個人格的 exclusive 持有者。 */
|
||||
/**
|
||||
* 睡眠 sub agent 的寫入權:它替某個人格收尾,手上沒有 exclusive 鎖,只有 5 分鐘的 sleeper 租約。
|
||||
* 第一個收尾指令會把租約取起來(`acquireSleepLease` 內含鎖檢查:目標正被別的程序載入著就拒絕),
|
||||
* 之後每個指令續租,最後由 `sleep` 還掉。`--as-sleeper` 由 PreToolUse hook 把關,
|
||||
* 只有 persona-sleeper 型的 sub agent 能用。
|
||||
*/
|
||||
function sleeperAccess(slug, sessionId, agentId) {
|
||||
const mine = pl.liveSleepers(slug).find((s) => s.session_id === sessionId);
|
||||
if (mine) {
|
||||
pl.heartbeatSleepLease(slug, sessionId, mine.agent_id ?? null);
|
||||
return mine;
|
||||
}
|
||||
return pl.acquireSleepLease(slug, sessionId, { agentId: agentId || null });
|
||||
}
|
||||
|
||||
/** 呼叫者必須是這個人格的 exclusive 持有者(或替它收尾的 sleeper)。 */
|
||||
function requireOwner(slug, sessionId) {
|
||||
if (!slug) die("未指定人格,且本 session 沒有載入人格。");
|
||||
if (!pl.personaExists(slug)) die(`人格 \`${slug}\` 不存在。可用:${pl.listPersonas().join(", ") || "(無)"}`);
|
||||
const data = pl.loadSession(sessionId);
|
||||
if (CURRENT_FLAGS["as-sleeper"]) {
|
||||
sleeperAccess(slug, sessionId, str(CURRENT_FLAGS["agent-id"]));
|
||||
return data;
|
||||
}
|
||||
if (data.host !== slug) {
|
||||
die(
|
||||
`本 session 的 host 人格是 \`${data.host || "(未載入)"}\`,不是 \`${slug}\`。` +
|
||||
@@ -826,7 +847,10 @@ commands.consolidate = ({ flags }) => {
|
||||
if (flags["body-file"]) body = fs.readFileSync(str(flags["body-file"]), "utf8");
|
||||
if (!body.trim()) die("需要 `--body` 或 `--body-file`。");
|
||||
const type = str(flags.type) || "fact";
|
||||
const VALID_TYPES = ["fact", "preference", "event", "promise", "relationship", "insight", "boundary", "canon"];
|
||||
// diary:睡眠時寫的第一人稱回顧(persona-sleep/persona-sleeper 都用這個型別)
|
||||
const VALID_TYPES = [
|
||||
"fact", "preference", "event", "promise", "relationship", "insight", "boundary", "canon", "diary",
|
||||
];
|
||||
if (!VALID_TYPES.includes(type)) die(`--type 只能是 ${VALID_TYPES.join("/")}。`);
|
||||
const front = [
|
||||
"---",
|
||||
@@ -1809,6 +1833,7 @@ const HELP = `persona.mjs — jsc-persona 人格 / 記憶 / 情緒 / 關係圖 C
|
||||
guard (內部)從 stdin 讀 hook event 測試隔離判斷
|
||||
|
||||
全域旗標:--json(機器可讀)、--quiet(成功時不輸出;劇場模式必用)
|
||||
--as-sleeper(睡眠 sub agent 專用:以 sleeper 租約代替 exclusive 鎖,hook 會驗身分)
|
||||
`;
|
||||
|
||||
async function main(argv) {
|
||||
@@ -1821,6 +1846,7 @@ async function main(argv) {
|
||||
if (!command) die(`未知子指令 \`${sub}\`。用 \`node persona.mjs --help\` 看清單。`);
|
||||
const parsed = parseArgs(argv.slice(1));
|
||||
QUIET = Boolean(parsed.flags.quiet);
|
||||
CURRENT_FLAGS = parsed.flags;
|
||||
try {
|
||||
await command({ flags: parsed.flags, positional: parsed._ });
|
||||
} catch (err) {
|
||||
|
||||
Reference in New Issue
Block a user