var test = require("node:test"); var assert = require("node:assert"); var fs = require("fs"); var os = require("os"); var path = require("path"); var continuationModule = require("../lib/project-driver-continuation"); var continuationAccess = require("../lib/driver-continuation-access"); var continuationLease = require("../lib/driver-continuation-lease"); var createSessionManager = require("../lib/sessions").createSessionManager; var permissionsModule = require("../lib/users-permissions"); var attachUserMessage = require("../lib/project-user-message").attachUserMessage; var createSplitGroupStore = require("../lib/session-split-groups").createSplitGroupStore; function payload(result) { return JSON.parse(result.content[0].text); } function tick() { return new Promise(function (resolve) { setImmediate(resolve); }); } function fixture(options) { var opts = options || {}; var source = { localId: 1, cliSessionId: "source-cli", ownerId: opts.ownerId || null, title: "Foundation work", vendor: "codex", model: "gpt-6-astra", effort: "high", permissionMode: opts.permissionMode || "default", effectivePermissionMode: opts.effectivePermissionMode || null, lastContextUsage: Object.prototype.hasOwnProperty.call(opts, "contextUsage") ? opts.contextUsage : { input_tokens: 85000, contextWindow: 100000 }, sessionVisibility: "shared", history: [{ type: "user_message", text: "private full transcript secret", _ts: 1 }], pendingPermissions: {}, pendingAskUser: {}, _sdkQueryGeneration: 3, }; var sessions = new Map([[1, source]]); var updates = []; var results = []; var starts = []; var switches = []; var deleted = []; var identityListeners = []; var saves = []; var nextId = 2; var worker = null; var splitDir = null; var splitStore = null; if (opts.withPair) { source.sessionOriginId = source.sessionOriginId || "source-origin"; worker = { localId: nextId++, cliSessionId: "worker-cli", ownerId: source.ownerId || null, title: "Existing Worker", history: [{ type: "assistant", text: "preserved worker history" }], pendingPermissions: {}, pendingAskUser: {}, pendingElicitations: {}, pendingUserDialogs: {}, sessionOriginId: "worker-origin", _pairGeneration: 4, sessionProvenance: { version: 1, kind: "worker", parentSessionOriginId: source.sessionOriginId, generation: 4, createdVia: "split-worker", createdAt: 10 }, }; sessions.set(worker.localId, worker); splitDir = fs.mkdtempSync(path.join(os.tmpdir(), "clay-continuation-pair-")); splitStore = createSplitGroupStore({ sessions: sessions, sessionsDir: splitDir, usersModule: null, persistGroups: opts.persistGroups }); var createdGroup = splitStore.createOwned(source.ownerId || null, { members: [source.localId, worker.localId], pair: { driverId: source.localId, workerId: worker.localId } }); assert.strictEqual(createdGroup.ok, true); source._pairTurnControl = { serial: 7, humanStopped: true, stoppedAt: 20, stoppedWorkerId: worker.localId, creations: 1, replacements: 0, operations: Object.create(null) }; source._workerGenerations = [{ generation: 4, workerSessionId: worker.localId, workerOriginId: worker.sessionOriginId, vendor: "codex", model: "gpt-6-astra", effort: "high", startedAt: 10, endedAt: null, observed: null, evaluation: null }]; } var sm = { sessions: sessions, sendAndRecord: function (session, event) { session.history.push(event); }, sendToSession: function (session, event) { updates.push({ session: session, event: event }); }, saveSessionFile: function (session) { var kind = session === source ? "source" : "target"; var count = saves.filter(function (item) { return item.kind === kind; }).length + 1; saves.push({ kind: kind, count: count, session: session }); if (typeof opts.saveBehavior === "function") return opts.saveBehavior(kind, count, session); if (opts.failSourceSave && session === source) return false; if (opts.failTargetSave && session !== source) return false; return true; }, createSessionRaw: function (settings) { var target = Object.assign({ localId: nextId++, history: [], pendingPermissions: {}, pendingAskUser: {}, sessionOriginId: "origin-target-" + nextId, }, settings); sessions.set(target.localId, target); return target; }, deleteSessionQuiet: function (id) { deleted.push(id); sessions.delete(id); }, broadcastSessionList: function () {}, switchSession: function (id, ws) { switches.push(id); ws._clayActiveSession = id; }, addOnSessionIdentityAssigned: function (listener) { identityListeners.push(listener); return function () { identityListeners = identityListeners.filter(function (item) { return item !== listener; }); }; }, notifySessionIdentityAssigned: function (id) { identityListeners.slice().forEach(function (listener) { listener(id); }); }, }; var sdk = { startQuery: function (target, prompt) { starts.push({ target: target, prompt: prompt }); if (typeof opts.onStart === "function") opts.onStart(target, splitStore); if (opts.startReject) return Promise.resolve(false); target.cliSessionId = "target-cli-" + target.localId; return Promise.resolve(true); }, }; sdk.startQueryWithAcceptance = function (target, prompt) { return Promise.resolve(sdk.startQuery(target, prompt)).then(function (accepted) { if (accepted !== true) return { accepted: false, reason: "The successor did not accept or retain its initial query." }; target.queryInstance = target.queryInstance || { fixture: true }; target._awaitingTurnResult = true; return { accepted: true, initialAccepted: true, queryAlive: true, queryGeneration: 1 }; }); }; var group = opts.group || null; if (!splitStore) splitStore = { groupForMember: function () { return group; } }; var attached = continuationModule.attachDriverContinuation({ sm: sm, isMate: false, projectSlug: "clay", splitStore: splitStore, pendingMessageQueue: { hasActive: function () { return !!opts.pendingQueue; } }, dangerouslySkipPermissions: !!opts.forcedSkip, isMultiUser: function () { return !!opts.multiUser; }, isDriverOperatedSession: function (session) { return session.driverOperated === true; }, authorizeSession: function (session, ownerId) { if (typeof opts.authorizeSession === "function") return opts.authorizeSession(session, ownerId); if (opts.denied) return false; return !opts.multiUser || ownerId === session.ownerId; }, getSdk: function () { return sdk; }, getLinuxUserForSession: function () { return null; }, onProcessingChanged: function () {}, sendTo: function (ws, event) { results.push(event); }, consumePendingMessage: opts.consumePendingMessage, identityWaitMs: opts.identityWaitMs, }); var ws = { _clayActiveSession: 1, _clayUser: opts.multiUser ? { id: opts.actorId || source.ownerId } : null }; return { attached: attached, source: source, worker: worker, splitStore: splitStore, splitDir: splitDir, sessions: sessions, updates: updates, results: results, starts: starts, switches: switches, deleted: deleted, saves: saves, ws: ws, sm: sm, sdk: sdk }; } async function propose(f) { var tool = f.attached.getToolDefs(f.source, 3)[0]; var result = payload(await tool.handler(proposalArgs({ reason: "A real compaction completed and the current task is at a safe boundary.", }))); assert.strictEqual(result.status, "posted"); var proposals = f.source.history.filter(function (entry) { return entry.type === "driver_continuation_proposal"; }); return proposals[proposals.length - 1]; } function proposalArgs(overrides) { return Object.assign({ reason: "The completed phase leaves a clear boundary for a focused continuation.", milestone: "The current implementation phase and its focused verification are complete.", benefit: "A compact handoff lets the successor focus on the independently reviewable next phase.", goal: "Finish Driver continuation safely.", constraints: "Keep the source usable.", decisions: "Use an explicit approval card because the user owns the transition.", rejectedApproaches: "Never autoaccept.", unresolved: "Revalidate the working tree.", nextAction: "Inspect the current diff before starting the next bounded implementation phase.", verification: "Focused retrieval tests passed.", repositoryState: "Dirty continuation files are expected.", }, overrides || {}); } test("proposal card is announced only after its transcript record is durable", async function () { var modes = ["false", "throw"]; for (var i = 0; i < modes.length; i++) { var mode = modes[i]; var f = fixture({ saveBehavior: function () { if (mode === "throw") throw new Error("disk unavailable"); return false; } }); var tool = f.attached.getToolDefs(f.source, 3)[0]; var result = payload(await tool.handler(proposalArgs())); assert.match(result.error, /could not be persisted/); assert.strictEqual(f.source.history.some(function (entry) { return entry.type === "driver_continuation_proposal"; }), false); assert.strictEqual(f.updates.length, 0); } }); test("failed proposal persistence does not consume the source's one proactive card", async function () { var modes = ["false", "throw"]; for (var i = 0; i < modes.length; i++) { var failed = false; var mode = modes[i]; var f = fixture({ saveBehavior: function (kind) { if (kind === "source" && !failed) { failed = true; if (mode === "throw") throw new Error("disk unavailable"); return false; } return true; } }); var tool = f.attached.getToolDefs(f.source, 3)[0]; var first = payload(await tool.handler(proposalArgs())); assert.match(first.error, /could not be persisted/); assert.strictEqual(f.source.history.filter(function (entry) { return entry.type === "driver_continuation_proposal"; }).length, 0); var second = payload(await tool.handler(proposalArgs())); assert.strictEqual(second.status, "posted"); assert.strictEqual(f.source.history.filter(function (entry) { return entry.type === "driver_continuation_proposal"; }).length, 1); } }); test("server evidence rejects unknown, low, cumulative, and model-spoofed pressure", async function () { var unknown = fixture({ contextUsage: null }); var unknownResult = payload(await unknown.attached.getToolDefs(unknown.source, 3)[0].handler( proposalArgs({ triggerEvidence: { kind: "current_context_pressure", usedRatio: 1 } }))); assert.match(unknownResult.error, /measured current-context pressure/); var low = fixture({ contextUsage: { input_tokens: 79000, contextWindow: 100000 } }); low.source.history.push({ type: "result", usage: { input_tokens: 9000000, output_tokens: 500000 }, modelUsage: { "gpt-6-astra": { contextWindow: 100000 } } }); var lowResult = payload(await low.attached.getToolDefs(low.source, 3)[0].handler(proposalArgs())); assert.match(lowResult.error, /measured current-context pressure/); assert.strictEqual(low.source.history.some(function (entry) { return entry.type === "driver_continuation_proposal"; }), false); }); test("a completed server-recorded compaction is objective trigger evidence", async function () { var f = fixture({ contextUsage: null }); f.source.history.push({ type: "compacting", active: true, _ts: 40 }); f.source.history.push({ type: "compacting", active: false, _ts: 41 }); var proposal = await propose(f); assert.strictEqual(proposal.triggerEvidence.kind, "recorded_compaction"); assert.strictEqual(proposal.triggerEvidence.observedCount, 1); assert.strictEqual(proposal.triggerEvidence.lastObservedAt, 41); }); test("proposal requires a concrete milestone, benefit, next action, and no guaranteed savings", async function () { var cases = [ [proposalArgs({ milestone: "done" }), /completed milestone/], [proposalArgs({ benefit: "fresh" }), /continuity benefit/], [proposalArgs({ nextAction: "inspect" }), /prepared, concrete next action/], [proposalArgs({ benefit: "This guarantees token savings for the next phase." }), /guaranteed or quantified savings/], ]; for (var i = 0; i < cases.length; i++) { var f = fixture(); var result = payload(await f.attached.getToolDefs(f.source, 3)[0].handler(cases[i][0])); assert.match(result.error, cases[i][1]); } }); test("proposal permits its own active turn but rejects other pending source work", async function () { var active = fixture(); active.source.isProcessing = true; active.source._awaitingTurnResult = true; var posted = payload(await active.attached.getToolDefs(active.source, 3)[0].handler(proposalArgs())); assert.strictEqual(posted.status, "posted"); var blocked = [ fixture({ pendingQueue: true }), fixture(), fixture(), ]; blocked[1].source.pendingPermissions = { permission: {} }; blocked[2].source.autonomousRun = { state: "running" }; var patterns = [/Pending human messages/, /permission or user-input/, /Until complete/]; for (var i = 0; i < blocked.length; i++) { var result = payload(await blocked[i].attached.getToolDefs(blocked[i].source, 3)[0].handler(proposalArgs())); assert.match(result.error, patterns[i]); assert.strictEqual(blocked[i].source.history.some(function (entry) { return entry.type === "driver_continuation_proposal"; }), false); } }); test("proposal is recorded with actual context and never exposed under Skip permissions", async function () { var f = fixture(); var guidance = f.attached.getSystemPrompt(f.source); assert.match(guidance, /current_context_pressure/); assert.match(guidance, /at most once/); assert.match(guidance, /Human acceptance is always required/); var proposal = await propose(f); assert.strictEqual(proposal.status, "pending"); assert.strictEqual(proposal.projectSlug, "clay"); assert.strictEqual(proposal.contextStatus.current.scope, "current_context"); assert.strictEqual(proposal.triggerEvidence.kind, "current_context_pressure"); assert.strictEqual(proposal.triggerEvidence.usedRatio, 0.85); assert.strictEqual(proposal.triggerEvidence.thresholdRatio, 0.8); assert.match(proposal.milestone, /implementation phase/); assert.match(proposal.benefit, /compact handoff/); assert.strictEqual(f.attached.getSystemPrompt(f.source), "", "a posted card permanently removes proactive guidance"); assert.deepStrictEqual(fixture({ permissionMode: "bypassPermissions" }).attached.getToolDefs(f.source), []); assert.deepStrictEqual(fixture({ effectivePermissionMode: "bypassPermissions" }).attached.getToolDefs(f.source), []); assert.deepStrictEqual(fixture({ forcedSkip: true }).attached.getToolDefs(f.source), []); f.source.permissionMode = "bypassPermissions"; await assert.rejects(f.attached.respond(f.ws, { sourceSessionId: 1, sourceOriginId: proposal.sourceOriginId, proposalId: proposal.proposalId, accepted: true }), /disabled while Skip permissions/); }); test("proposal handlers are bound to the exact query generation", async function () { var f = fixture(); var tool = f.attached.getToolDefs(f.source, 3)[0]; f.source._sdkQueryGeneration = 4; var result = payload(await tool.handler({ reason: "boundary", goal: "continue", nextAction: "inspect" })); assert.match(result.error, /older query/); assert.strictEqual(f.source.history.length, 1); }); test("finished Until complete remains a Driver while hidden and Worker identities are excluded", function () { var f = fixture(); f.source.autonomousRun = { state: "complete" }; assert.strictEqual(f.attached.getToolDefs(f.source, 3).length, 1); f.source.hidden = true; assert.deepStrictEqual(f.attached.getToolDefs(f.source, 3), []); f.source.hidden = false; f.source.sessionProvenance = { kind: "worker" }; assert.deepStrictEqual(f.attached.getToolDefs(f.source, 3), []); f.source.sessionProvenance = null; f.source.scheduledTaskRun = { role: "driver" }; assert.deepStrictEqual(f.attached.getToolDefs(f.source, 3), []); delete f.source.scheduledTaskRun; f.source.projectLogReview = { ref: "log:review" }; assert.deepStrictEqual(f.attached.getToolDefs(f.source, 3), []); delete f.source.projectLogReview; f.source.isMate = true; assert.deepStrictEqual(f.attached.getToolDefs(f.source, 3), []); f.source.isMate = false; f.source.driverOperated = true; assert.deepStrictEqual(f.attached.getToolDefs(f.source, 3), []); }); test("proposal authorization is rechecked by both retained handlers and tool discovery", async function () { var options = {}; var f = fixture(options); var tool = f.attached.getToolDefs(f.source, 3)[0]; options.denied = true; var result = payload(await tool.handler({ reason: "boundary", goal: "continue", nextAction: "inspect" })); assert.match(result.error, /authorized live Project Driver/); assert.deepStrictEqual(f.attached.getToolDefs(f.source, 3), []); }); test("production project authorization rejects missing, inactive, and fail-open actor records", function () { var actor = { id: "owner" }; var users = { isMultiUser: function () { return true; }, findUserById: function () { return actor; }, canAccessSession: function () { return true; }, }; var settings = { usersModule: users, projectSlug: "clay", getProjectAccess: function () { return { visibility: "private", ownerId: "owner" }; }, canAccessProjectSlug: function () { return true; }, }; var session = { ownerId: "owner" }; assert.strictEqual(continuationAccess.authorize(settings, session, "owner"), true); actor = null; assert.strictEqual(continuationAccess.authorize(settings, session, "owner"), false); actor = { id: "owner", disabled: true }; assert.strictEqual(continuationAccess.authorize(settings, session, "owner"), false); actor = { id: "owner", active: false }; assert.strictEqual(continuationAccess.authorize(settings, session, "owner"), false); actor = { id: "owner", status: "inactive" }; assert.strictEqual(continuationAccess.authorize(settings, session, "owner"), false); actor = { id: "owner" }; settings.getProjectAccess = function () { throw new Error("unavailable"); }; assert.strictEqual(continuationAccess.authorize(settings, session, "owner"), false); delete settings.getProjectAccess; assert.strictEqual(continuationAccess.authorize(settings, session, "owner"), false); }); test("production continuation authorization uses live users-permissions access", function () { var actors = [{ id: "owner", role: "user" }]; function findUserById(id) { return actors.filter(function (actor) { return actor.id === id; })[0] || null; } var permissions = permissionsModule.attachPermissions({ loadUsers: function () { return { users: actors }; }, saveUsers: function () {}, findUserById: findUserById, }); var settings = { usersModule: { isMultiUser: function () { return true; }, findUserById: findUserById, canAccessSession: permissions.canAccessSession, }, projectSlug: "clay", getProjectAccess: function () { return { visibility: "private", ownerId: "owner", allowedUsers: [] }; }, canAccessProjectSlug: function (ownerId, slug) { return ownerId === "owner" && slug === "clay"; }, }; var session = { ownerId: "owner", sessionVisibility: "private" }; assert.strictEqual(continuationAccess.authorize(settings, session, "owner"), true); actors[0].disabled = true; assert.strictEqual(continuationAccess.authorize(settings, session, "owner"), false); delete actors[0].disabled; actors[0].active = false; assert.strictEqual(continuationAccess.authorize(settings, session, "owner"), false); actors.splice(0, 1); assert.strictEqual(continuationAccess.authorize(settings, session, "owner"), false); }); test("live production authorization gates discovery and retained proposal handlers", async function () { var actor = { id: "owner", role: "user" }; var actors = [actor]; function findUserById(id) { return actors.filter(function (candidate) { return candidate.id === id; })[0] || null; } var permissions = permissionsModule.attachPermissions({ loadUsers: function () { return { users: actors }; }, saveUsers: function () {}, findUserById: findUserById, }); var settings = { usersModule: { isMultiUser: function () { return true; }, findUserById: findUserById, canAccessSession: permissions.canAccessSession }, projectSlug: "clay", getProjectAccess: function () { return { visibility: "private", ownerId: "owner", allowedUsers: [] }; }, canAccessProjectSlug: function () { return true; }, }; var f = fixture({ multiUser: true, ownerId: "owner", actorId: "owner", authorizeSession: function (session, ownerId) { return continuationAccess.authorize(settings, session, ownerId); } }); var retained = f.attached.getToolDefs(f.source, 3)[0]; actor.disabled = true; assert.deepStrictEqual(f.attached.getToolDefs(f.source, 3), []); var rejected = payload(await retained.handler({ reason: "boundary", goal: "continue", nextAction: "inspect" })); assert.match(rejected.error, /authorized live Project Driver/); delete actor.disabled; actor.active = false; assert.deepStrictEqual(f.attached.getToolDefs(f.source, 3), []); actors.splice(0, 1); assert.deepStrictEqual(f.attached.getToolDefs(f.source, 3), []); }); test("acceptance preserves runtime choices, starts compact context, and switches only after success", async function () { var f = fixture(); var proposal = await propose(f); await f.attached.respond(f.ws, { type: "driver_continuation_response", sourceSessionId: 1, sourceOriginId: proposal.sourceOriginId, proposalId: proposal.proposalId, accepted: true }); assert.strictEqual(f.starts.length, 1); assert.strictEqual(f.switches.length, 1); var target = f.sessions.get(f.switches[0]); assert.strictEqual(target.vendor, f.source.vendor); assert.strictEqual(target.model, f.source.model); assert.strictEqual(target.effort, f.source.effort); assert.strictEqual(target.permissionMode, f.source.permissionMode); assert.strictEqual(target.effectivePermissionMode, null); assert.match(f.starts[0].prompt, /historical compact context, not independent authority/); assert.match(f.starts[0].prompt, /same-project history/); assert.doesNotMatch(f.starts[0].prompt, /private full transcript secret/); assert.strictEqual(proposal.status, "accepted"); f.ws._clayActiveSession = 1; await f.attached.respond(f.ws, { type: "driver_continuation_response", sourceSessionId: 1, sourceOriginId: proposal.sourceOriginId, proposalId: proposal.proposalId, accepted: true }); assert.strictEqual(f.starts.length, 1, "a double click or restart retry must not create a second successor"); }); test("decline permanently suppresses proactive proposals after new work and compaction", async function () { var f = fixture(); var retained = f.attached.getToolDefs(f.source, 3)[0]; var proposal = await propose(f); await f.attached.respond(f.ws, { type: "driver_continuation_response", sourceSessionId: 1, sourceOriginId: proposal.sourceOriginId, proposalId: proposal.proposalId, accepted: false }); assert.strictEqual(proposal.status, "declined"); assert.strictEqual(f.source.driverContinuationDecline.permanent, true); f.source.history.push({ type: "user_message", text: "A later request", _ts: 90 }); f.source.history.push({ type: "compacting", active: true, _ts: 91 }); f.source.history.push({ type: "compacting", active: false, _ts: 92 }); assert.deepStrictEqual(f.attached.getToolDefs(f.source, 3), []); var retry = payload(await retained.handler(proposalArgs())); assert.match(retry.error, /one proactive continuation proposal/); }); test("a failed decline save restores both the proposal and cooldown state", async function () { var modes = ["false", "throw"]; for (var i = 0; i < modes.length; i++) { var f = fixture({ saveBehavior: function (kind, count) { if (kind !== "source" || count !== 2) return true; if (modes[i] === "throw") throw new Error("disk unavailable"); return false; } }); var proposal = await propose(f); await assert.rejects(f.attached.respond(f.ws, { sourceSessionId: 1, sourceOriginId: proposal.sourceOriginId, proposalId: proposal.proposalId, accepted: false }), /could not be persisted/); assert.strictEqual(proposal.status, "pending"); assert.strictEqual(f.source.driverContinuationDecline, undefined); } }); test("a new human request invalidates the only pending card without allowing another", async function () { var f = fixture(); var retained = f.attached.getToolDefs(f.source, 3)[0]; var proposal = await propose(f); f.source.history.push({ type: "user_message", text: "The work changed.", _ts: 99 }); await assert.rejects(f.attached.respond(f.ws, { sourceSessionId: 1, sourceOriginId: proposal.sourceOriginId, proposalId: proposal.proposalId, accepted: true }), /source work changed/); assert.strictEqual(f.starts.length, 0); assert.strictEqual(proposal.status, "superseded"); var retry = payload(await retained.handler(proposalArgs())); assert.match(retry.error, /one proactive continuation proposal/); assert.deepStrictEqual(f.attached.getToolDefs(f.source, 3), []); }); test("exact source owner and active-session correlation are enforced", async function () { var f = fixture({ multiUser: true, ownerId: "owner", actorId: "intruder" }); var proposal = await propose(f); await assert.rejects(f.attached.respond(f.ws, { sourceSessionId: 1, sourceOriginId: proposal.sourceOriginId, proposalId: proposal.proposalId, accepted: true }), /source Driver session no longer exists/); f.ws._clayUser.id = "owner"; f.ws._clayActiveSession = 99; await assert.rejects(f.attached.respond(f.ws, { sourceSessionId: 1, sourceOriginId: proposal.sourceOriginId, proposalId: proposal.proposalId, accepted: true }), /another source session/); }); test("proposal origin must still match the current source origin", async function () { var f = fixture(); var proposal = await propose(f); proposal.sourceOriginId = "origin-from-another-source"; await assert.rejects(f.attached.respond(f.ws, { sourceSessionId: 1, sourceOriginId: f.source.sessionOriginId, proposalId: proposal.proposalId, accepted: false }), /exact continuation proposal was not found/); assert.strictEqual(proposal.status, "pending"); }); test("replayed decision treats the old local ID as a hint and resolves the current source by origin", async function () { var f = fixture(); var proposal = await propose(f); var oldLocalId = f.source.localId; var decoy = { localId: oldLocalId, ownerId: null, sessionOriginId: "origin-decoy", history: [] }; f.sessions.delete(oldLocalId); f.source.localId = 9; f.sessions.set(oldLocalId, decoy); f.sessions.set(f.source.localId, f.source); f.ws._clayActiveSession = f.source.localId; var decided = await f.attached.respond(f.ws, { sourceSessionId: oldLocalId, sourceOriginId: proposal.sourceOriginId, proposalId: proposal.proposalId, accepted: false }); assert.strictEqual(decided.status, "declined"); assert.strictEqual(decided.sourceSessionId, f.source.localId); assert.strictEqual(proposal.status, "declined"); assert.strictEqual(decoy.history.length, 0); var wrong = fixture(); var wrongProposal = await propose(wrong); wrong.sessions.delete(wrong.source.localId); wrong.source.localId = 11; wrong.sessions.set(11, wrong.source); wrong.ws._clayActiveSession = 11; await assert.rejects(wrong.attached.respond(wrong.ws, { sourceSessionId: 1, sourceOriginId: "origin-wrong", proposalId: wrongProposal.proposalId, accepted: false }), /source Driver session no longer exists/); assert.strictEqual(wrongProposal.status, "pending"); }); test("busy and pending source work produce specific retry blockers without mutation", async function () { var cases = [ ["isProcessing", true, /current Driver turn/], ["pendingPermissions", { p: {} }, /permission or user-input/], ["autonomousRun", { state: "running" }, /Until complete/], ["scheduledMessage", { text: "later" }, /scheduled callback/], ]; for (var i = 0; i < cases.length; i++) { var f = fixture(); var proposal = await propose(f); f.source[cases[i][0]] = cases[i][1]; await assert.rejects(f.attached.respond(f.ws, { sourceSessionId: 1, sourceOriginId: proposal.sourceOriginId, proposalId: proposal.proposalId, accepted: true }), cases[i][2]); assert.strictEqual(f.starts.length, 0); assert.strictEqual(proposal.status, "pending"); } var queuedOptions = {}; var queued = fixture(queuedOptions); var queuedProposal = await propose(queued); queuedOptions.pendingQueue = true; await assert.rejects(queued.attached.respond(queued.ws, { sourceSessionId: 1, sourceOriginId: queuedProposal.sourceOriginId, proposalId: queuedProposal.proposalId, accepted: true }), /Pending human messages/); var workerProposal = fixture(); var continuation = await propose(workerProposal); workerProposal.source.history.push({ type: "worker_proposal", proposalId: "worker", status: "pending" }); await assert.rejects(workerProposal.attached.respond(workerProposal.ws, { sourceSessionId: 1, sourceOriginId: continuation.sourceOriginId, proposalId: continuation.proposalId, accepted: true }), /Split Worker proposal/); }); test("an idle same-owner Split Worker moves intact to the accepted successor", async function () { var firstQueryPair = null; var diskOwnerDuringStart = null; var sourceRuntimeAuthorityDuringStart = null; var f; f = fixture({ withPair: true, onStart: function (target, store) { firstQueryPair = store.groupForMember(target.localId); sourceRuntimeAuthorityDuringStart = !!store.groupForMember(f.source.localId); store.refreshAnchors(f.worker.localId); var persisted = JSON.parse(fs.readFileSync(path.join(f.splitDir, "split-groups.json"))); diskOwnerDuringStart = persisted[0].pair.driverId; } }); var originalGroup = f.splitStore.groupForMember(f.source.localId); var originalProvenance = JSON.stringify(f.worker.sessionProvenance); var proposal = await propose(f); await f.attached.respond(f.ws, { sourceSessionId: 1, sourceOriginId: proposal.sourceOriginId, proposalId: proposal.proposalId, accepted: true }); var target = f.sessions.get(f.switches[0]); var transferred = f.splitStore.groupForMember(target.localId); assert.strictEqual(transferred.id, originalGroup.id); assert.strictEqual(transferred.pair.driverId, target.localId); assert.strictEqual(transferred.pair.workerId, f.worker.localId); assert.strictEqual(f.splitStore.groupForMember(f.source.localId), null); assert.strictEqual(f.sessions.get(f.worker.localId), f.worker); assert.strictEqual(f.worker.history[0].text, "preserved worker history"); assert.strictEqual(JSON.stringify(f.worker.sessionProvenance), originalProvenance); assert.strictEqual(diskOwnerDuringStart, f.source.localId, "SDK identity anchor refresh must not persist staged successor ownership"); assert.strictEqual(sourceRuntimeAuthorityDuringStart, false, "the source must not retain runtime pair authority while the successor initializes"); assert.ok(firstQueryPair, "the successor first query must mount with the transferred pair context"); assert.strictEqual(firstQueryPair.pair.driverId, target.localId); assert.strictEqual(firstQueryPair.pair.workerId, f.worker.localId); assert.notStrictEqual(target._pairTurnControl, f.source._pairTurnControl); assert.notStrictEqual(target._pairTurnControl.operations, f.source._pairTurnControl.operations); assert.strictEqual(target._pairTurnControl.humanStopped, true); assert.strictEqual(target._pairTurnControl.stoppedWorkerId, f.worker.localId); target._pairTurnControl.serial++; assert.strictEqual(f.source._pairTurnControl.serial, 7, "successor turn-control mutations must not alias the source"); assert.deepStrictEqual(target._workerGenerations, f.source._workerGenerations); assert.notStrictEqual(target._workerGenerations, f.source._workerGenerations); f.worker.isProcessing = true; f.ws._clayActiveSession = f.source.localId; await f.attached.respond(f.ws, { sourceSessionId: 1, sourceOriginId: proposal.sourceOriginId, proposalId: proposal.proposalId, accepted: true }); delete f.worker.isProcessing; assert.strictEqual(f.starts.length, 1); assert.strictEqual(f.switches.length, 2, "committed navigation must not revalidate an already transferred Worker"); assert.strictEqual(f.splitStore.groups.length, 1); assert.strictEqual(f.splitStore.groups[0].pair.driverId, target.localId); }); test("busy, delegated, queued, and input-waiting Workers reject continuation without mutation", async function () { var cases = [ ["isProcessing", true, /still processing/], ["_pairDelegation", { taskId: "task" }, /open delegation/], ["_pairFollowups", [{ status: "queued" }], /queued follow-ups/], ["pendingPush", [{ text: "queued" }], /waiting for delivery/], ["pendingUserDialogs", { dialog: {} }, /permission or user-input/], ]; for (var i = 0; i < cases.length; i++) { var f = fixture({ withPair: true }); f.worker[cases[i][0]] = cases[i][1]; var tool = f.attached.getToolDefs(f.source, 3)[0]; var result = payload(await tool.handler(proposalArgs())); assert.match(result.error, cases[i][2]); assert.strictEqual(f.splitStore.groupForMember(f.source.localId).pair.driverId, f.source.localId); assert.strictEqual(f.starts.length, 0); } }); test("paired startup rejection rolls ownership back without replacing the Worker or group", async function () { var f = fixture({ withPair: true, startReject: true }); var group = f.splitStore.groupForMember(f.source.localId); var proposal = await propose(f); await assert.rejects(f.attached.respond(f.ws, { sourceSessionId: 1, sourceOriginId: proposal.sourceOriginId, proposalId: proposal.proposalId, accepted: true }), /did not accept/); var restored = f.splitStore.groupForMember(f.source.localId); assert.strictEqual(restored.id, group.id); assert.strictEqual(restored.pair.driverId, f.source.localId); assert.strictEqual(restored.pair.workerId, f.worker.localId); assert.strictEqual(f.sessions.get(f.worker.localId), f.worker); assert.strictEqual(f.sessions.size, 2); assert.strictEqual(proposal.status, "pending"); }); test("restart recovery commits one existing successor and transfers the persisted source pair", async function () { var f = fixture({ withPair: true }); var proposal = await propose(f); var target = f.sm.createSessionRaw({ ownerId: f.source.ownerId || null, vendor: f.source.vendor, model: f.source.model, effort: f.source.effort, permissionMode: f.source.permissionMode }); target.cliSessionId = "restart-successor"; target.driverContinuation = { proposalId: proposal.proposalId, sourceOriginId: proposal.sourceOriginId, sourceSessionRef: proposal.sourceSessionRef, projectSlug: "clay", status: "starting", startupProof: "initialized", acceptedQueryGeneration: 8, acceptedInitialAt: Date.now() }; proposal.status = "starting"; proposal.targetSessionId = target.localId; proposal.targetOriginId = target.sessionOriginId; await f.attached.respond(f.ws, { sourceSessionId: 1, sourceOriginId: proposal.sourceOriginId, proposalId: proposal.proposalId, accepted: true }); assert.strictEqual(f.starts.length, 0); assert.strictEqual(f.switches[0], target.localId); assert.strictEqual(f.splitStore.groups.length, 1); assert.strictEqual(f.splitStore.groups[0].pair.driverId, target.localId); assert.strictEqual(f.splitStore.groups[0].pair.workerId, f.worker.localId); assert.strictEqual(f.splitStore.groupForMember(f.source.localId), null); }); test("startup rejection leaves the source pending and never switches", async function () { var f = fixture({ startReject: true }); var proposal = await propose(f); await assert.rejects(f.attached.respond(f.ws, { sourceSessionId: 1, sourceOriginId: proposal.sourceOriginId, proposalId: proposal.proposalId, accepted: true }), /did not accept/); assert.strictEqual(f.switches.length, 0); assert.strictEqual(f.sessions.size, 1); assert.strictEqual(proposal.status, "pending"); assert.match(proposal.error, /did not accept/); }); test("source persistence failure restores the review card and does not start a successor", async function () { var f = fixture({ saveBehavior: function (kind, count) { return kind !== "source" || count !== 2; } }); var proposal = await propose(f); await assert.rejects(f.attached.respond(f.ws, { sourceSessionId: 1, sourceOriginId: proposal.sourceOriginId, proposalId: proposal.proposalId, accepted: true }), /could not be persisted/); assert.strictEqual(f.starts.length, 0); assert.strictEqual(f.switches.length, 0); assert.strictEqual(proposal.status, "pending"); assert.strictEqual(f.updates.some(function (item) { return item.event.type === "driver_continuation_update" && item.event.status === "starting"; }), false, "a failed write must not announce an unpersisted starting state"); }); test("accepted startup without a durable identity never switches or deletes the live successor", async function () { var f = fixture({ identityWaitMs: 5 }); var proposal = await propose(f); f.sdk.startQueryWithAcceptance = function (target) { target.queryInstance = { live: true }; target._awaitingTurnResult = true; return Promise.resolve({ accepted: true, initialAccepted: true, queryAlive: true, queryGeneration: 1 }); }; await assert.rejects(f.attached.respond(f.ws, { sourceSessionId: 1, sourceOriginId: proposal.sourceOriginId, proposalId: proposal.proposalId, accepted: true }), /durable session identity/); assert.strictEqual(f.switches.length, 0); assert.strictEqual(f.sessions.size, 2, "the accepted live successor must not be silently deleted"); assert.strictEqual(proposal.status, "pending"); var target = Array.from(f.sessions.values())[1]; assert.strictEqual(target.driverContinuation.status, "starting"); target.cliSessionId = "late-durable-identity"; await f.attached.respond(f.ws, { sourceSessionId: 1, sourceOriginId: proposal.sourceOriginId, proposalId: proposal.proposalId, accepted: true }); assert.strictEqual(target.driverContinuation.status, "accepted"); assert.strictEqual(f.switches[0], target.localId); }); test("accepted startup waits for provider identity before persisting and switching", async function () { var f = fixture({ identityWaitMs: 50 }); var proposal = await propose(f); f.sdk.startQueryWithAcceptance = function (target) { target.queryInstance = { live: true }; target._awaitingTurnResult = true; setTimeout(function () { target.cliSessionId = "provider-assigned-id"; f.sm.notifySessionIdentityAssigned(target.localId); }, 5); return Promise.resolve({ accepted: true, initialAccepted: true, queryAlive: true, queryGeneration: 1 }); }; await f.attached.respond(f.ws, { sourceSessionId: 1, sourceOriginId: proposal.sourceOriginId, proposalId: proposal.proposalId, accepted: true }); assert.strictEqual(f.switches.length, 1); assert.strictEqual(f.sessions.get(f.switches[0]).driverContinuation.status, "accepted"); }); test("post-start revalidation cancels the successor and requires a fresh proposal", async function () { var options = {}; var f = fixture(options); var proposal = await propose(f); var release; var startupCalls = 0; f.sdk.startQueryWithAcceptance = function (target) { startupCalls++; target.cliSessionId = "durable-target"; target.queryInstance = { live: true }; return new Promise(function (resolve) { release = function () { resolve({ accepted: true, initialAccepted: true, queryAlive: true, queryGeneration: 8 }); }; }); }; var first = f.attached.respond(f.ws, { sourceSessionId: 1, sourceOriginId: proposal.sourceOriginId, proposalId: proposal.proposalId, accepted: true }); await tick(); options.pendingQueue = true; release(); await assert.rejects(first, /Pending human messages/); assert.ok(f.saves.some(function (save) { return save.kind === "target"; }), "startup proof must be saved before revalidation"); assert.strictEqual(f.switches.length, 0); assert.strictEqual(f.sessions.size, 1); assert.strictEqual(proposal.status, "superseded"); assert.strictEqual(startupCalls, 1, "an unsafe proposal must never start a second successor"); }); test("accepted lifecycle without complete initialization proof cannot commit or switch", async function () { var f = fixture(); var proposal = await propose(f); f.sdk.startQueryWithAcceptance = function (target) { target.cliSessionId = "target-without-generation"; target.queryInstance = { live: true }; return Promise.resolve({ accepted: true, initialAccepted: true, queryAlive: true }); }; await assert.rejects(f.attached.respond(f.ws, { sourceSessionId: 1, sourceOriginId: proposal.sourceOriginId, proposalId: proposal.proposalId, accepted: true }), /verified startup proof/); assert.strictEqual(f.switches.length, 0); assert.strictEqual(proposal.status, "pending"); }); test("late initialization retains the lease until retry commits the single target", async function () { var f = fixture(); var proposal = await propose(f); var lateStartup; var startupCalls = 0; f.sdk.startQueryWithAcceptance = function (target, prompt, images, user, guard, onLate) { startupCalls++; assert.strictEqual(guard(), true); lateStartup = onLate; target.cliSessionId = "late-target"; target.queryInstance = { close: function () {} }; return Promise.resolve({ accepted: false, initialAccepted: true, queryAlive: true, queryGeneration: 9, timedOut: true, reason: "The successor did not initialize in time." }); }; await assert.rejects(f.attached.respond(f.ws, { sourceSessionId: 1, sourceOriginId: proposal.sourceOriginId, proposalId: proposal.proposalId, accepted: true }), /did not initialize in time/); assert.strictEqual(f.sessions.size, 2); assert.strictEqual(f.source._driverContinuationLease.waitingLateStartup, true); lateStartup({ accepted: true, queryAlive: true, queryGeneration: 9 }); assert.strictEqual(f.source._driverContinuationLease.waitingCommit, true); await f.attached.respond(f.ws, { sourceSessionId: 1, sourceOriginId: proposal.sourceOriginId, proposalId: proposal.proposalId, accepted: true }); assert.strictEqual(f.source._driverContinuationLease, undefined); assert.strictEqual(startupCalls, 1); assert.strictEqual(f.sessions.size, 2); assert.strictEqual(f.switches.length, 1); }); test("human work after late init cancels the target and drains the preserved queue once", async function () { var consumed = 0; var consumedAfterDelete = false; var admitted = []; var sent = []; var f; var options = { consumePendingMessage: function (source) { consumed++; consumedAfterDelete = f.sessions.size === 1 && source === f.source; } }; f = fixture(options); var proposal = await propose(f); var lateStartup; var closed = 0; f.sdk.startQueryWithAcceptance = function (target, prompt, images, user, guard, onLate) { lateStartup = onLate; target.cliSessionId = "late-human-target"; target.queryInstance = { close: function () { closed++; } }; return Promise.resolve({ accepted: false, initialAccepted: true, queryAlive: true, queryGeneration: 11, timedOut: true, reason: "The successor did not initialize in time." }); }; await assert.rejects(f.attached.respond(f.ws, { sourceSessionId: 1, sourceOriginId: proposal.sourceOriginId, proposalId: proposal.proposalId, accepted: true }), /did not initialize in time/); lateStartup({ accepted: true, queryAlive: true, queryGeneration: 11 }); assert.strictEqual(f.source._driverContinuationLease.waitingCommit, true); assert.strictEqual(typeof f.source._driverContinuationLease.onCancel, "function"); var historyLength = f.source.history.length; var queue = { inspect: function () { return null; }, admit: function (source, message) { admitted.push({ source: source, message: message }); return { ok: true }; }, isPaused: function () { return false; }, getRevision: function () { return 7; }, }; var messages = attachUserMessage({ cwd: process.cwd(), slug: "clay", isMate: false, sm: {}, sdk: {}, sendTo: function (ws, message) { sent.push(message); }, sendToSession: function () {}, sendToSessionOthers: function () {}, clients: new Set(), opts: {}, usersModule: { isMultiUser: function () { return false; } }, getSessionForWs: function () { return f.source; }, pendingMessageQueue: queue, isDriverOperatedSession: function () { return false; }, _loop: { handleLoopMessage: function () { return false; } }, }); messages.handleUserMessage({ _clayUser: null }, { type: "message", text: "Keep working in the source", clientMessageId: "late-human-1", }); await tick(); assert.strictEqual(admitted.length, 1); assert.strictEqual(f.source.history.length, historyLength, "queued source input must not be replayed early"); assert.strictEqual(closed, 1); assert.strictEqual(proposal.status, "superseded"); assert.deepStrictEqual(f.deleted, [2]); assert.strictEqual(f.sessions.size, 1); assert.strictEqual(f.source._driverContinuationLease, undefined); assert.strictEqual(consumed, 1); assert.strictEqual(consumedAfterDelete, true, "source draining starts only after the target is removed"); assert.ok(sent.some(function (message) { return message.type === "message_queued"; })); }); test("late-init commit persistence failure retains exclusive ownership for retry", async function () { var options = {}; var f = fixture(options); var proposal = await propose(f); var lateStartup; var startupCalls = 0; f.sdk.startQueryWithAcceptance = function (target, prompt, images, user, guard, onLate) { startupCalls++; lateStartup = onLate; target.cliSessionId = "late-persistence-target"; target.queryInstance = { close: function () {} }; return Promise.resolve({ accepted: false, initialAccepted: true, queryAlive: true, queryGeneration: 12, timedOut: true, reason: "The successor did not initialize in time." }); }; await assert.rejects(f.attached.respond(f.ws, { sourceSessionId: 1, sourceOriginId: proposal.sourceOriginId, proposalId: proposal.proposalId, accepted: true })); lateStartup({ accepted: true, queryAlive: true, queryGeneration: 12 }); var lease = f.source._driverContinuationLease; var failed = false; options.saveBehavior = function (kind) { if (kind === "target" && !failed) { failed = true; return false; } return true; }; await assert.rejects(f.attached.respond(f.ws, { sourceSessionId: 1, sourceOriginId: proposal.sourceOriginId, proposalId: proposal.proposalId, accepted: true }), /prepared successor relationship/); assert.strictEqual(f.source._driverContinuationLease, lease); assert.strictEqual(f.switches.length, 0); assert.strictEqual(f.sessions.size, 2); options.saveBehavior = null; await f.attached.respond(f.ws, { sourceSessionId: 1, sourceOriginId: proposal.sourceOriginId, proposalId: proposal.proposalId, accepted: true }); assert.strictEqual(startupCalls, 1); assert.strictEqual(f.source._driverContinuationLease, undefined); assert.strictEqual(f.switches.length, 1); }); test("late initialization target-save failure retains the pair lease and retries one successor", async function () { var options = {}; var f = fixture(Object.assign(options, { withPair: true })); var proposal = await propose(f); var lateStartup; var startupCalls = 0; f.sdk.startQueryWithAcceptance = function (target, prompt, images, user, guard, onLate) { startupCalls++; lateStartup = onLate; target.cliSessionId = "late-pair-target"; target.queryInstance = { close: function () {} }; return Promise.resolve({ accepted: false, initialAccepted: true, queryAlive: true, queryGeneration: 14, timedOut: true, reason: "The successor did not initialize in time." }); }; await assert.rejects(f.attached.respond(f.ws, { sourceSessionId: 1, sourceOriginId: proposal.sourceOriginId, proposalId: proposal.proposalId, accepted: true })); var failed = false; options.saveBehavior = function (kind) { if (kind === "target" && !failed) { failed = true; return false; } return true; }; lateStartup({ accepted: true, queryAlive: true, queryGeneration: 14 }); var lease = f.source._driverContinuationLease; assert.ok(lease); assert.strictEqual(lease.waitingCommit, true); assert.strictEqual(f.sessions.size, 3); assert.strictEqual(f.splitStore.groupForMember(f.source.localId), null, "the staged owner remains exclusive while persistence is retried"); options.saveBehavior = null; await f.attached.respond(f.ws, { sourceSessionId: 1, sourceOriginId: proposal.sourceOriginId, proposalId: proposal.proposalId, accepted: true }); assert.strictEqual(startupCalls, 1); assert.strictEqual(f.source._driverContinuationLease, undefined); assert.strictEqual(f.switches.length, 1); var target = f.sessions.get(f.switches[0]); assert.strictEqual(f.splitStore.groupForMember(target.localId).pair.workerId, f.worker.localId); }); test("paired proof-save retries revalidate staged Worker work before committing", async function () { var cases = [ ["isProcessing", true, /still processing/], ["_pairDelegation", { taskId: "task" }, /open delegation/], ["_pairFollowups", [{ status: "queued" }], /queued follow-ups/], ["pendingPermissions", { request: {} }, /permission or user-input/], ]; for (var i = 0; i < cases.length; i++) { var failedProof = false; var consumed = 0; var options = { withPair: true, consumePendingMessage: function () { consumed++; } }; options.saveBehavior = function (kind, count) { if (kind === "target" && count === 1 && !failedProof) { failedProof = true; return false; } return true; }; var f = fixture(options); var proposal = await propose(f); var decision = { sourceSessionId: 1, sourceOriginId: proposal.sourceOriginId, proposalId: proposal.proposalId, accepted: true }; await assert.rejects(f.attached.respond(f.ws, decision), /verified successor startup could not be persisted/); var lease = f.source._driverContinuationLease; var target = lease.target; var originalProvenance = JSON.stringify(f.worker.sessionProvenance); options.saveBehavior = null; f.worker[cases[i][0]] = cases[i][1]; await assert.rejects(f.attached.respond(f.ws, decision), cases[i][2]); var persisted = JSON.parse(fs.readFileSync(path.join(f.splitDir, "split-groups.json"))); assert.strictEqual(f.source._driverContinuationLease, lease); assert.strictEqual(f.sessions.has(target.localId), true); assert.strictEqual(f.switches.length, 0); assert.strictEqual(consumed, 0, "a rejected retry must not drain queued source work"); assert.strictEqual(persisted.length, 1); assert.strictEqual(persisted[0].pair.driverId, f.source.localId); assert.strictEqual(f.splitStore.groupForMember(target.localId).pair.workerId, f.worker.localId); assert.strictEqual(JSON.stringify(f.worker.sessionProvenance), originalProvenance); delete f.worker[cases[i][0]]; await f.attached.respond(f.ws, decision); await tick(); assert.strictEqual(f.starts.length, 1); assert.strictEqual(f.switches[0], target.localId); assert.strictEqual(f.source._driverContinuationLease, undefined); assert.strictEqual(f.splitStore.groups.length, 1); assert.strictEqual(f.splitStore.groups[0].pair.driverId, target.localId); assert.strictEqual(f.splitStore.groups[0].pair.workerId, f.worker.localId); assert.strictEqual(consumed, 1); } }); test("paired retry cancels cleanly when the ownership commit cannot persist", async function () { var modes = ["false", "throw"]; for (var i = 0; i < modes.length; i++) { var failCommit = false; var failedProof = false; var consumed = 0; var options = { withPair: true, consumePendingMessage: function () { consumed++; } }; options.persistGroups = function (file, serialized) { if (failCommit) { if (modes[i] === "throw") throw new Error("split disk unavailable"); return false; } fs.writeFileSync(file, serialized); return true; }; options.saveBehavior = function (kind, count) { if (kind === "target" && count === 1 && !failedProof) { failedProof = true; return false; } return true; }; var f = fixture(options); var proposal = await propose(f); var decision = { sourceSessionId: 1, sourceOriginId: proposal.sourceOriginId, proposalId: proposal.proposalId, accepted: true }; await assert.rejects(f.attached.respond(f.ws, decision), /verified successor startup could not be persisted/); var target = f.source._driverContinuationLease.target; options.saveBehavior = null; failCommit = true; await assert.rejects(f.attached.respond(f.ws, decision), /ownership transfer could not be persisted/); await tick(); assert.strictEqual(f.sessions.has(target.localId), false); assert.strictEqual(f.source._driverContinuationLease, undefined); assert.strictEqual(f.switches.length, 0); assert.strictEqual(proposal.status, "superseded"); assert.strictEqual(f.splitStore.groups.length, 1); assert.strictEqual(f.splitStore.groups[0].pair.driverId, f.source.localId); assert.strictEqual(f.splitStore.groups[0].pair.workerId, f.worker.localId); assert.strictEqual(consumed, 1); } }); test("paired retry retains a stopped successor when commit cancellation cannot persist", async function () { var failCommit = false; var failProof = true; var failCancellation = false; var closed = 0; var consumed = 0; var options = { withPair: true, consumePendingMessage: function () { consumed++; } }; options.persistGroups = function (file, serialized) { if (failCommit) return false; fs.writeFileSync(file, serialized); return true; }; options.saveBehavior = function (kind, count) { if (kind === "target" && count === 1 && failProof) { failProof = false; return false; } if (kind === "source" && failCancellation) return false; return true; }; var f = fixture(options); var proposal = await propose(f); var decision = { sourceSessionId: 1, sourceOriginId: proposal.sourceOriginId, proposalId: proposal.proposalId, accepted: true }; await assert.rejects(f.attached.respond(f.ws, decision), /verified successor startup could not be persisted/); var lease = f.source._driverContinuationLease; var target = lease.target; target.queryInstance = { close: function () { closed++; } }; failCommit = true; failCancellation = true; await assert.rejects(f.attached.respond(f.ws, decision), /cancelled continuation state could not be persisted/); assert.strictEqual(closed, 1, "the retained successor must be stopped before source authority is restored"); assert.strictEqual(f.sessions.has(target.localId), true); assert.strictEqual(f.source._driverContinuationLease, lease); assert.strictEqual(f.splitStore.groupForMember(f.source.localId).pair.driverId, f.source.localId); assert.strictEqual(f.splitStore.groupForMember(target.localId), null); assert.strictEqual(f.switches.length, 0); assert.strictEqual(consumed, 0); failCancellation = false; lease.cancel("Retry cancellation persistence."); await tick(); assert.strictEqual(f.sessions.has(target.localId), false); assert.strictEqual(f.source._driverContinuationLease, undefined); assert.strictEqual(proposal.status, "superseded"); assert.strictEqual(consumed, 1); }); test("paired cancellation retains the successor when source cancellation persistence fails", async function () { var options = { withPair: true }; var consumed = 0; options.consumePendingMessage = function () { consumed++; }; var f = fixture(options); var proposal = await propose(f); var lateStartup; f.sdk.startQueryWithAcceptance = function (target, prompt, images, user, guard, onLate) { lateStartup = onLate; target.cliSessionId = "late-cancel-target"; target.queryInstance = { close: function () {} }; return Promise.resolve({ accepted: false, initialAccepted: true, queryAlive: true, queryGeneration: 15, timedOut: true, reason: "The successor did not initialize in time." }); }; await assert.rejects(f.attached.respond(f.ws, { sourceSessionId: 1, sourceOriginId: proposal.sourceOriginId, proposalId: proposal.proposalId, accepted: true })); lateStartup({ accepted: true, queryAlive: true, queryGeneration: 15 }); var target = Array.from(f.sessions.values()).find(function (session) { return session !== f.source && session !== f.worker; }); assert.strictEqual(f.splitStore.groupForMember(target.localId).pair.driverId, target.localId); options.saveBehavior = function (kind) { return kind !== "source"; }; assert.strictEqual(continuationLease.cancelForHumanMessage(f.source), true); assert.strictEqual(f.sessions.has(target.localId), true, "an unpersisted cancellation must not delete the successor"); assert.ok(f.source._driverContinuationLease); assert.strictEqual(f.splitStore.groupForMember(f.source.localId).pair.driverId, f.source.localId); assert.strictEqual(consumed, 0); options.saveBehavior = null; continuationLease.cancelForHumanMessage(f.source); await tick(); assert.strictEqual(f.sessions.has(target.localId), false); assert.strictEqual(f.source._driverContinuationLease, undefined); assert.strictEqual(proposal.status, "superseded"); assert.strictEqual(consumed, 1); }); test("late startup error and restart-stale startup resolve without a second proactive card", async function () { var consumed = 0; var f = fixture({ consumePendingMessage: function () { consumed++; } }); var proposal = await propose(f); var lateStartup; f.sdk.startQueryWithAcceptance = function (target, prompt, images, user, guard, onLate) { assert.strictEqual(guard(), true); lateStartup = onLate; target.cliSessionId = "late-error-target"; target.queryInstance = { close: function () {} }; return Promise.resolve({ accepted: false, initialAccepted: true, queryAlive: true, queryGeneration: 10, timedOut: true, reason: "The successor did not initialize in time." }); }; await assert.rejects(f.attached.respond(f.ws, { sourceSessionId: 1, sourceOriginId: proposal.sourceOriginId, proposalId: proposal.proposalId, accepted: true })); continuationLease.cancelForHumanMessage(f.source); lateStartup({ accepted: false, queryAlive: false, queryGeneration: 10, reason: "Query failed after timeout." }); await tick(); assert.strictEqual(f.sessions.size, 1); assert.strictEqual(proposal.status, "superseded"); assert.strictEqual(f.source._driverContinuationLease, undefined); assert.strictEqual(consumed, 1, "late failure releases the normal source drain exactly once"); var restarted = fixture(); var staleProposal = await propose(restarted); var staleTarget = restarted.sm.createSessionRaw({ ownerId: null }); staleTarget.cliSessionId = "restart-stale-target"; staleTarget.driverContinuation = { proposalId: staleProposal.proposalId, sourceOriginId: staleProposal.sourceOriginId, projectSlug: "clay", status: "starting" }; await assert.rejects(restarted.attached.respond(restarted.ws, { sourceSessionId: 1, sourceOriginId: staleProposal.sourceOriginId, proposalId: staleProposal.proposalId, accepted: true }), /did not survive startup/); assert.strictEqual(restarted.sessions.size, 1); assert.strictEqual(staleProposal.status, "superseded"); assert.deepStrictEqual(restarted.attached.getToolDefs(restarted.source, 3), []); }); test("acceptance revalidates changed work, queue state, Skip, and access after startup", async function () { var cases = ["context", "queue", "skip", "access"]; for (var i = 0; i < cases.length; i++) { var release; var options = {}; var f = fixture(options); var proposal = await propose(f); f.sdk.startQueryWithAcceptance = function (target) { target.cliSessionId = "target-cli"; target.queryInstance = { live: true }; return new Promise(function (resolve) { release = function () { resolve({ accepted: true, initialAccepted: true, queryAlive: true, queryGeneration: 4 }); }; }); }; var accepting = f.attached.respond(f.ws, { sourceSessionId: 1, sourceOriginId: proposal.sourceOriginId, proposalId: proposal.proposalId, accepted: true }); await tick(); if (cases[i] === "context") f.source.history.push({ type: "user_message", text: "new work", _ts: 55 }); if (cases[i] === "queue") options.pendingQueue = true; if (cases[i] === "skip") f.source.permissionMode = "bypassPermissions"; if (cases[i] === "access") f.source.ownerId = "revoked"; release(); await assert.rejects(accepting); assert.strictEqual(f.switches.length, 0); assert.strictEqual(f.starts.length, 0, "the custom lifecycle is invoked directly without the fixture start shim"); assert.strictEqual(f.sessions.size, 1, "the unsafe successor must be cancelled before it can act"); assert.strictEqual(proposal.status, "superseded"); } }); test("false and throwing saves at every transition phase never switch and retry without duplicate startup", async function () { var phases = [ ["source", 2], ["target", 1], ["target", 2], ["source", 3], ["target", 3], ["source", 4], ]; var modes = ["false", "throw"]; for (var mi = 0; mi < modes.length; mi++) { for (var pi = 0; pi < phases.length; pi++) { var failed = false; var phase = phases[pi]; var f = fixture({ saveBehavior: function (kind, count) { if (!failed && kind === phase[0] && count === phase[1]) { failed = true; if (modes[mi] === "throw") throw new Error("disk unavailable"); return false; } return true; } }); var proposal = await propose(f); await assert.rejects(f.attached.respond(f.ws, { sourceSessionId: 1, sourceOriginId: proposal.sourceOriginId, proposalId: proposal.proposalId, accepted: true })); assert.strictEqual(f.switches.length, 0); await f.attached.respond(f.ws, { sourceSessionId: 1, sourceOriginId: proposal.sourceOriginId, proposalId: proposal.proposalId, accepted: true }); assert.strictEqual(f.starts.length, 1, modes[mi] + " " + phase.join(":") + " must start at most once"); assert.strictEqual(f.switches.length, 1); var target = f.sessions.get(f.switches[0]); assert.strictEqual(proposal.status, "accepted"); assert.strictEqual(target.driverContinuation.status, "accepted"); assert.strictEqual(proposal.transactionId, target.driverContinuation.transactionId); } } }); test("message handler reports async failure without autoaccepting or switching", async function () { var f = fixture({ startReject: true }); var proposal = await propose(f); assert.strictEqual(f.attached.handleMessage(f.ws, { type: "driver_continuation_response", requestId: "request-1", projectSlug: "clay", sourceOriginId: proposal.sourceOriginId, sourceSessionId: 1, proposalId: proposal.proposalId, accepted: true }), true); await tick(); assert.strictEqual(f.results.length, 1); assert.strictEqual(f.results[0].ok, false); assert.strictEqual(f.results[0].requestId, "request-1"); assert.strictEqual(f.results[0].projectSlug, "clay"); assert.strictEqual(f.results[0].sourceOriginId, proposal.sourceOriginId); assert.strictEqual(f.switches.length, 0); }); test("message handler rejects incomplete correlation before touching a proposal", async function () { var f = fixture(); var proposal = await propose(f); assert.strictEqual(f.attached.handleMessage(f.ws, { type: "driver_continuation_response", sourceSessionId: 1, sourceOriginId: proposal.sourceOriginId, proposalId: proposal.proposalId, accepted: true }), true); assert.strictEqual(f.results.length, 1); assert.strictEqual(f.results[0].ok, false); assert.match(f.results[0].error, /correlation is incomplete or stale/); assert.strictEqual(proposal.status, "pending"); assert.strictEqual(f.starts.length, 0); }); test("a concurrent decision cannot switch to a successor before startup proof", async function () { var release; var f = fixture(); var proposal = await propose(f); f.sdk.startQueryWithAcceptance = function (target) { target.cliSessionId = "target-cli"; return new Promise(function (resolve) { release = function (accepted) { resolve({ accepted: accepted, initialAccepted: accepted, queryAlive: accepted, queryGeneration: 6 }); }; }); }; var first = f.attached.respond(f.ws, { sourceSessionId: 1, sourceOriginId: proposal.sourceOriginId, proposalId: proposal.proposalId, accepted: true }); await tick(); await assert.rejects(f.attached.respond(f.ws, { sourceSessionId: 1, sourceOriginId: proposal.sourceOriginId, proposalId: proposal.proposalId, accepted: true }), /still starting/); assert.strictEqual(f.switches.length, 0); release(true); await first; assert.strictEqual(f.switches.length, 1); }); test("successor opens its original by stable origin after local IDs change", async function () { var f = fixture(); var proposal = await propose(f); await f.attached.respond(f.ws, { sourceSessionId: 1, sourceOriginId: proposal.sourceOriginId, proposalId: proposal.proposalId, accepted: true }); var target = f.sessions.get(f.switches[0]); f.sessions.delete(1); f.source.localId = 9; f.sessions.set(9, f.source); f.ws._clayActiveSession = target.localId; var result = f.attached.openSource(f.ws, { proposalId: proposal.proposalId, sourceOriginId: proposal.sourceOriginId }); assert.strictEqual(result.targetSessionId, 9); assert.strictEqual(f.switches[f.switches.length - 1], 9); }); test("restart renumbering resolves decisions and successors by durable origin", async function () { var root = fs.mkdtempSync(path.join(os.tmpdir(), "clay-continuation-restart-")); var sessionsBase = path.join(root, "sessions"); var cliSessionsDir = path.join(root, "cli"); fs.mkdirSync(cliSessionsDir, { recursive: true }); var cwd = path.join(root, "project"); fs.mkdirSync(cwd, { recursive: true }); function manager() { return createSessionManager({ cwd: cwd, sessionsBase: sessionsBase, cliSessionsDir: cliSessionsDir, send: function () {}, sendTo: function () {} }); } function attach(sm, switches, starts) { sm.switchSession = function (id, ws) { switches.push(id); ws._clayActiveSession = id; }; return continuationModule.attachDriverContinuation({ sm: sm, isMate: false, projectSlug: "clay", splitStore: { groupForMember: function () { return null; } }, pendingMessageQueue: { hasActive: function () { return false; } }, dangerouslySkipPermissions: false, isMultiUser: function () { return false; }, authorizeSession: function () { return true; }, getSdk: function () { return { startQueryWithAcceptance: function (target) { starts.push(target.localId); target.cliSessionId = "target-cli"; target.queryInstance = { initialized: true }; target._awaitingTurnResult = true; return Promise.resolve({ accepted: true, initialAccepted: true, queryAlive: true, queryGeneration: 7 }); } }; }, getLinuxUserForSession: function () { return null; }, onProcessingChanged: function () {}, sendTo: function () {}, identityWaitMs: 10, }); } try { var firstManager = manager(); var padding = firstManager.createSessionRaw({}); padding.cliSessionId = "padding-cli"; padding.createdAt = 1; firstManager.saveSessionFile(padding); var source = firstManager.createSessionRaw({ vendor: "codex", model: "gpt-6-astra", effort: "high" }); source.cliSessionId = "source-cli"; source.lastContextUsage = { input_tokens: 85000, contextWindow: 100000 }; source.createdAt = 2; source._sdkQueryGeneration = 3; source.history.push({ type: "user_message", text: "continue durable work", _ts: 1 }); firstManager.saveSessionFile(source); var firstStarts = []; var firstSwitches = []; var firstAttached = attach(firstManager, firstSwitches, firstStarts); var tool = firstAttached.getToolDefs(source, 3)[0]; var posted = payload(await tool.handler(proposalArgs())); var proposal = continuationModule.findProposal(source, posted.proposalId); var firstWs = { _clayActiveSession: source.localId, _clayUser: null }; var accepted = await firstAttached.respond(firstWs, { sourceSessionId: source.localId, sourceOriginId: source.sessionOriginId, proposalId: proposal.proposalId, accepted: true }); var oldSourceId = source.localId; var oldTargetId = accepted.targetSessionId; assert.strictEqual(firstStarts.length, 1); fs.unlinkSync(path.join(firstManager.sessionsDir, "padding-cli.jsonl")); var secondManager = manager(); var restoredSource = continuationAccess.findOwnedSessionByOrigin(secondManager.sessions, source.sessionOriginId, null); var restoredTarget = continuationAccess.findOwnedSessionByOrigin(secondManager.sessions, proposal.targetOriginId, null); assert.notStrictEqual(restoredSource.localId, oldSourceId); assert.notStrictEqual(restoredTarget.localId, oldTargetId); var replayed = continuationModule.findProposal(restoredSource, proposal.proposalId); assert.strictEqual(replayed.sourceSessionId, oldSourceId, "the persisted card retains its stale daemon-local snapshot"); assert.strictEqual(replayed.targetSessionId, oldTargetId, "the persisted relation retains its stale daemon-local snapshot"); var secondStarts = []; var secondSwitches = []; var secondAttached = attach(secondManager, secondSwitches, secondStarts); assert.deepStrictEqual(secondAttached.getToolDefs(restoredSource, restoredSource._sdkQueryGeneration), [], "a historical durable card suppresses proactive proposals after restart"); var secondWs = { _clayActiveSession: restoredSource.localId, _clayUser: null }; var retried = await secondAttached.respond(secondWs, { sourceSessionId: oldSourceId, sourceOriginId: restoredSource.sessionOriginId, proposalId: replayed.proposalId, accepted: true }); assert.strictEqual(secondStarts.length, 0, "restart retry must reuse the one durable successor"); assert.strictEqual(retried.sourceSessionId, restoredSource.localId); assert.strictEqual(retried.targetSessionId, restoredTarget.localId); assert.deepStrictEqual(secondSwitches, [restoredTarget.localId]); } finally { fs.rmSync(root, { recursive: true, force: true }); } });