Fix owner context loss after finalize; green the test suite
Owner continuity: - Seed a freshly created owner task with the previous task's latest owner final on a cold start after the previous task already closed, so a user reply to a finalized TASK_DONE no longer produces a "no context" answer. Activated for this deployment via PAIRED_CARRY_FORWARD_LATEST_OWNER_FINAL (.env); carried text is injected as clearly-marked background only. - Skip intermediate STEP_DONE outputs when picking the carry-forward anchor. Single-mode routing: - enforceRoomModeOnLease strips a stale reviewer/arbiter lease from a room switched back to single, preventing single-mode messages from stalling in the paired path on a stuck execution lease. Session auth / credentials: - Pre-sync Claude credentials into each session dir before the agent spawns. - Honor CLAUDE_CREDENTIALS_PATH in setup/login.ts (per-service isolation). - Add a relogin-required gate so a permanently logged-out claude-code room asks the user to re-login instead of spawning a doomed agent. Other: - Arbiter verdicts written in the user's language (verdict keyword stays EN). - status-dashboard chatName field; runtime-inventory credential path resolver. Tests (make suite fully green: 1595 pass / 3 skip): - service-routing: default owner is now the claude service and reviewer is codex-review; update the 7 failover/default expectations accordingly. - migrate-room-registrations: owner inferred as claude-code (configured OWNER_AGENT_TYPE) for a dual legacy room; reviewer becomes codex. - register: mock paired-workspace provisioning + reload signal (registration now provisions a workspace and hot-reloads); assert RELOADED status. - paired-execution-context: force a claude-code reviewer to exercise the Claude read-only branch regardless of the deployment default. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -229,14 +229,22 @@ function cancelOutstandingFinalizeOwnerTurn(task: PairedTask): void {
|
||||
);
|
||||
}
|
||||
|
||||
function getLatestTurnOutputByRole(
|
||||
taskId: string,
|
||||
role: PairedRoomRole,
|
||||
): PairedTurnOutput | null {
|
||||
function isIntermediateStepOutput(outputText: string): boolean {
|
||||
// STEP_DONE marks an intermediate step that keeps the task active; it is not
|
||||
// the user-facing finalize summary, so it is a poor carry-forward anchor.
|
||||
return /^\s*STEP_DONE\b/.test(outputText);
|
||||
}
|
||||
|
||||
function getLatestOwnerFinalOutput(taskId: string): PairedTurnOutput | null {
|
||||
const ownerOutputs = [...getPairedTurnOutputs(taskId)]
|
||||
.reverse()
|
||||
.filter((output) => output.role === 'owner');
|
||||
return (
|
||||
[...getPairedTurnOutputs(taskId)]
|
||||
.reverse()
|
||||
.find((output) => output.role === role) ?? null
|
||||
ownerOutputs.find(
|
||||
(output) => !isIntermediateStepOutput(output.output_text),
|
||||
) ??
|
||||
ownerOutputs[0] ??
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
@@ -248,10 +256,7 @@ function carryForwardLatestOwnerFinal(args: {
|
||||
return;
|
||||
}
|
||||
|
||||
const latestOwnerFinal = getLatestTurnOutputByRole(
|
||||
args.sourceTask.id,
|
||||
'owner',
|
||||
);
|
||||
const latestOwnerFinal = getLatestOwnerFinalOutput(args.sourceTask.id);
|
||||
if (!latestOwnerFinal) {
|
||||
return;
|
||||
}
|
||||
@@ -293,16 +298,27 @@ export function resolveOwnerTaskForHumanMessage(args: {
|
||||
args.existingTask ?? getLatestOpenPairedTaskForChat(args.chatJid) ?? null;
|
||||
|
||||
if (!existing) {
|
||||
maybeRecordTaskDoneReopen(getLatestPairedTaskForChat(args.chatJid) ?? null);
|
||||
const previousTask = getLatestPairedTaskForChat(args.chatJid) ?? null;
|
||||
maybeRecordTaskDoneReopen(previousTask);
|
||||
const newTask = canonicalWorkDir
|
||||
? createActiveTaskForRoom({
|
||||
group: args.group,
|
||||
chatJid: args.chatJid,
|
||||
canonicalWorkDir,
|
||||
roomRoleContext: args.roomRoleContext,
|
||||
})
|
||||
: null;
|
||||
// Cold start after the previous task already closed (completed): seed the
|
||||
// fresh task with the previous task's latest owner final so the owner keeps
|
||||
// continuity across sessions instead of answering with "no context".
|
||||
if (newTask && previousTask) {
|
||||
carryForwardLatestOwnerFinal({
|
||||
sourceTask: previousTask,
|
||||
targetTask: newTask,
|
||||
});
|
||||
}
|
||||
return {
|
||||
task: canonicalWorkDir
|
||||
? createActiveTaskForRoom({
|
||||
group: args.group,
|
||||
chatJid: args.chatJid,
|
||||
canonicalWorkDir,
|
||||
roomRoleContext: args.roomRoleContext,
|
||||
})
|
||||
: null,
|
||||
task: newTask,
|
||||
supersededTask: null,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user