B4 加了 updateJson/updateEmotion,但這幾個呼叫點還是「loadEmotion → 改 → writeJson」的裸讀改寫,鎖形同不存在: - sleep 的 emotion-decay、remember --emotion(每輪對話的熱路徑)、 Stop hook 與 SessionEnd hook 的時間衰減,四處都改走 pl.updateEmotion()。 衰減與 delta 的算法一個字都沒動,只是把讀與寫收進同一把鎖裡。 - sync.json 同理:背景 sync push 跟前景指令會同時寫它,兩邊撞上時後寫的會把 pushed_at/overwrites 整段蓋掉——覆蓋紀錄就這樣安靜地消失。新增 updateSyncState()(帶鎖),六處 loadSyncState + saveSyncState 成對使用全部 改過去;補預設欄位的邏輯抽成 normalizeSyncState() 給兩邊共用。 saveSyncState 沒有呼叫端了,直接移掉,免得下次又有人拿它裸寫。 sync.json 的欄位格式與 overwrites 留 10 筆的行為都沒變。 selftest 448 項(+1):新增「並行 8 次 remember --emotion 跟循序結果一樣」, 把對話熱路徑的情緒寫入也蓋進競態測試——原本只蓋到 emotion --apply。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
34 lines
958 B
JavaScript
34 lines
958 B
JavaScript
#!/usr/bin/env node
|
||
// SessionEnd:釋放人格鎖與所有 guest 租約,人格才能被下一個程序載入。
|
||
|
||
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 data = pl.loadSession(sessionId);
|
||
const host = data.host;
|
||
|
||
if (host && pl.personaExists(host)) {
|
||
const state = pl.updateEmotion(host, (s) => pl.decayEmotion(s));
|
||
pl.appendJsonl(pl.journalPath(host), {
|
||
ts: pl.nowIso(),
|
||
kind: "session-end",
|
||
session: sessionId.slice(0, 8),
|
||
mood: pl.mood(state),
|
||
});
|
||
}
|
||
|
||
const released = pl.unbindSession(sessionId);
|
||
pl.gcRuntime();
|
||
|
||
const out = { suppressOutput: true };
|
||
if (released.host) {
|
||
out.systemMessage =
|
||
`[jsc-persona] 已釋放 \`${released.host}\` 的載入鎖` +
|
||
(released.guests.length ? `,guest:${released.guests.join(", ")}` : "");
|
||
}
|
||
respond(out);
|