test/codex-turnid-persistence.test.js
Rama main · réplica de lectura del repositorio original.
var test = require("node:test");
var assert = require("node:assert/strict");
var fs = require("fs");
var os = require("os");
var path = require("path");
var createSessionManager = require("../lib/sessions").createSessionManager;
var attachMessageProcessor = require("../lib/sdk-message-processor").attachMessageProcessor;
test("processSDKMessage forwards Codex's real turnId into the persisted message_uuid record", function () {
var recorded = [];
var sm = { sendAndRecord: function (session, obj) { recorded.push(obj); } };
var mp = attachMessageProcessor({ sm: sm, send: function () {} });
var session = { messageUUIDs: [], history: [] };
// Exactly the shape codex.js's flattenEvent emits for turn/started.
mp.processSDKMessage(session, { yokeType: "turn_start", uuid: "u1", messageType: "user", turnId: "turn-live-1" });
assert.equal(session.messageUUIDs[0].turnId, "turn-live-1", "the in-memory entry carries the real turnId");
var persisted = recorded.find(function (m) { return m.type === "message_uuid"; });
assert.ok(persisted, "a message_uuid record is sent for persistence");
assert.equal(persisted.turnId, "turn-live-1",
"the object handed to sendAndRecord -- what actually gets written to the session file -- must carry turnId too, not just the in-memory session.messageUUIDs copy");
});
// Codex's thread/revert needs a real turnId (not a count) to roll back a
// conversation (see lib/sdk-bridge.js's rollbackConversation). That id is
// captured in-memory on session.messageUUIDs, but it must also survive a
// real save/reload round trip (daemon restart, Worker respawn) or every
// rewind on a session that outlives one process would silently lose its
// only valid target and fail closed.
function manager(root) {
return createSessionManager({
cwd: path.join(root, "project"),
sessionsBase: path.join(root, "sessions"),
send: function () {},
});
}
test("a Codex turn's turnId survives a real save and reload round trip", function (t) {
var root = fs.mkdtempSync(path.join(os.tmpdir(), "clay-turnid-persist-"));
t.after(function () { fs.rmSync(root, { recursive: true, force: true }); });
var first = manager(root);
var session = first.createSession({ vendor: "codex", cliSessionId: "thread-abc" });
// Mirrors exactly what sdk-message-processor.js emits for a Codex user turn.
first.sendAndRecord(session, { type: "message_uuid", uuid: "uuid-1", messageType: "user", turnId: "turn-111" });
first.sendAndRecord(session, { type: "message_uuid", uuid: "uuid-2", messageType: "assistant" });
first.saveSessionFile(session);
// A fresh manager over the same sessionsBase simulates a daemon restart.
var second = manager(root);
var reloaded = Array.from(second.sessions.values()).find(function (s) { return s.cliSessionId === "thread-abc"; });
assert.ok(reloaded, "the session is reloaded from disk");
var userEntry = reloaded.messageUUIDs.find(function (m) { return m.uuid === "uuid-1"; });
assert.ok(userEntry, "the user turn's messageUUIDs entry is reconstructed");
assert.equal(userEntry.turnId, "turn-111", "the real Codex turnId is not lost across a restart");
});
test("a Claude turn's absent turnId reloads as null, not a stale leftover", function (t) {
var root = fs.mkdtempSync(path.join(os.tmpdir(), "clay-turnid-claude-"));
t.after(function () { fs.rmSync(root, { recursive: true, force: true }); });
var first = manager(root);
var session = first.createSession({ vendor: "claude", cliSessionId: "claude-session-1" });
first.sendAndRecord(session, { type: "message_uuid", uuid: "uuid-1", messageType: "user", turnId: null });
first.saveSessionFile(session);
var second = manager(root);
var reloaded = Array.from(second.sessions.values()).find(function (s) { return s.cliSessionId === "claude-session-1"; });
var userEntry = reloaded.messageUUIDs.find(function (m) { return m.uuid === "uuid-1"; });
assert.equal(userEntry.turnId, null);
});