fix: /clear now resets both owner and reviewer sessions in paired rooms

This commit is contained in:
Eyejoker
2026-03-29 19:00:42 +09:00
parent a1e5418a2f
commit 9e0de1297c
10 changed files with 87 additions and 36 deletions

View File

@@ -556,7 +556,8 @@ describe('paired task state', () => {
reviewer_service_id: 'codex-review', reviewer_service_id: 'codex-review',
title: 'wire up workspaces', title: 'wire up workspaces',
source_ref: 'HEAD', source_ref: 'HEAD',
plan_notes: null, round_trip_count: 0, plan_notes: null,
round_trip_count: 0,
review_requested_at: null, review_requested_at: null,
status: 'active', status: 'active',
created_at: '2026-03-28T00:00:00.000Z', created_at: '2026-03-28T00:00:00.000Z',
@@ -594,7 +595,8 @@ describe('paired task state', () => {
reviewer_service_id: 'codex-review', reviewer_service_id: 'codex-review',
title: null, title: null,
source_ref: null, source_ref: null,
plan_notes: null, round_trip_count: 0, plan_notes: null,
round_trip_count: 0,
review_requested_at: null, review_requested_at: null,
status: 'active', status: 'active',
created_at: '2026-03-28T00:00:00.000Z', created_at: '2026-03-28T00:00:00.000Z',

View File

@@ -1550,6 +1550,13 @@ export function deleteSession(groupFolder: string): void {
).run(groupFolder, SERVICE_AGENT_TYPE); ).run(groupFolder, SERVICE_AGENT_TYPE);
} }
export function deleteAllSessionsForGroup(groupFolder: string): void {
db.prepare('DELETE FROM service_sessions WHERE group_folder = ?').run(
groupFolder,
);
db.prepare('DELETE FROM sessions WHERE group_folder = ?').run(groupFolder);
}
export function getAllSessions(): Record<string, string> { export function getAllSessions(): Record<string, string> {
const serviceRows = db const serviceRows = db
.prepare( .prepare(

View File

@@ -37,6 +37,7 @@ import {
initDatabase, initDatabase,
setRegisteredGroup, setRegisteredGroup,
setRouterState, setRouterState,
deleteAllSessionsForGroup,
deleteSession, deleteSession,
setSession, setSession,
storeChatMetadata, storeChatMetadata,
@@ -208,9 +209,16 @@ function saveState(): void {
setRouterState('last_agent_seq', JSON.stringify(lastAgentTimestamp)); setRouterState('last_agent_seq', JSON.stringify(lastAgentTimestamp));
} }
function clearSession(groupFolder: string): void { function clearSession(
groupFolder: string,
opts?: { allRoles?: boolean },
): void {
delete sessions[groupFolder]; delete sessions[groupFolder];
deleteSession(groupFolder); if (opts?.allRoles) {
deleteAllSessionsForGroup(groupFolder);
} else {
deleteSession(groupFolder);
}
} }
function registerGroup(jid: string, group: RegisteredGroup): void { function registerGroup(jid: string, group: RegisteredGroup): void {

View File

@@ -326,7 +326,8 @@ describe('runAgentForGroup room memory', () => {
title: null, title: null,
source_ref: 'HEAD', source_ref: 'HEAD',
review_requested_at: null, review_requested_at: null,
plan_notes: null, round_trip_count: 0, plan_notes: null,
round_trip_count: 0,
status: 'active', status: 'active',
created_at: '2026-03-29T00:00:00.000Z', created_at: '2026-03-29T00:00:00.000Z',
updated_at: '2026-03-29T00:00:00.000Z', updated_at: '2026-03-29T00:00:00.000Z',
@@ -386,7 +387,8 @@ describe('runAgentForGroup room memory', () => {
reviewer_service_id: 'codex-main', reviewer_service_id: 'codex-main',
title: null, title: null,
source_ref: 'HEAD', source_ref: 'HEAD',
plan_notes: null, round_trip_count: 0, plan_notes: null,
round_trip_count: 0,
review_requested_at: null, review_requested_at: null,
status: 'active', status: 'active',
created_at: '2026-03-28T00:00:00.000Z', created_at: '2026-03-28T00:00:00.000Z',
@@ -467,7 +469,8 @@ describe('runAgentForGroup room memory', () => {
reviewer_service_id: 'claude', reviewer_service_id: 'claude',
title: null, title: null,
source_ref: 'HEAD', source_ref: 'HEAD',
plan_notes: null, round_trip_count: 0, plan_notes: null,
round_trip_count: 0,
review_requested_at: null, review_requested_at: null,
status: 'active', status: 'active',
created_at: '2026-03-28T00:00:00.000Z', created_at: '2026-03-28T00:00:00.000Z',

View File

@@ -262,7 +262,8 @@ describe('createMessageRuntime', () => {
reviewer_service_id: 'codex-main', reviewer_service_id: 'codex-main',
title: null, title: null,
source_ref: 'HEAD', source_ref: 'HEAD',
plan_notes: null, round_trip_count: 0, plan_notes: null,
round_trip_count: 0,
review_requested_at: '2026-03-29T00:00:00.000Z', review_requested_at: '2026-03-29T00:00:00.000Z',
status: 'review_ready', status: 'review_ready',
created_at: '2026-03-29T00:00:00.000Z', created_at: '2026-03-29T00:00:00.000Z',
@@ -354,7 +355,8 @@ describe('createMessageRuntime', () => {
reviewer_service_id: 'codex-main', reviewer_service_id: 'codex-main',
title: null, title: null,
source_ref: 'HEAD', source_ref: 'HEAD',
plan_notes: null, round_trip_count: 0, plan_notes: null,
round_trip_count: 0,
review_requested_at: null, review_requested_at: null,
status: 'active', status: 'active',
created_at: '2026-03-29T00:00:00.000Z', created_at: '2026-03-29T00:00:00.000Z',

View File

@@ -101,7 +101,7 @@ export interface MessageRuntimeDeps {
getLastAgentTimestamps: () => Record<string, string>; getLastAgentTimestamps: () => Record<string, string>;
saveState: () => void; saveState: () => void;
persistSession: (groupFolder: string, sessionId: string) => void; persistSession: (groupFolder: string, sessionId: string) => void;
clearSession: (groupFolder: string) => void; clearSession: (groupFolder: string, opts?: { allRoles?: boolean }) => void;
} }
export function createMessageRuntime(deps: MessageRuntimeDeps): { export function createMessageRuntime(deps: MessageRuntimeDeps): {
@@ -542,7 +542,7 @@ export function createMessageRuntime(deps: MessageRuntimeDeps): {
deps.queue.closeStdin(chatJid, { deps.queue.closeStdin(chatJid, {
reason: 'session-command', reason: 'session-command',
}), }),
clearSession: () => deps.clearSession(group.folder), clearSession: (opts) => deps.clearSession(group.folder, opts),
advanceCursor: (cursorOrTimestamp) => { advanceCursor: (cursorOrTimestamp) => {
advanceLastAgentCursor( advanceLastAgentCursor(
deps.getLastAgentTimestamps(), deps.getLastAgentTimestamps(),

View File

@@ -215,7 +215,8 @@ describe('paired execution context', () => {
reviewer_service_id: 'codex-review', reviewer_service_id: 'codex-review',
title: null, title: null,
source_ref: 'HEAD', source_ref: 'HEAD',
plan_notes: null, round_trip_count: 0, plan_notes: null,
round_trip_count: 0,
review_requested_at: '2026-03-28T00:00:00.000Z', review_requested_at: '2026-03-28T00:00:00.000Z',
status: 'review_ready', status: 'review_ready',
created_at: '2026-03-28T00:00:00.000Z', created_at: '2026-03-28T00:00:00.000Z',
@@ -246,7 +247,8 @@ describe('paired execution context', () => {
reviewer_service_id: 'codex-review', reviewer_service_id: 'codex-review',
title: null, title: null,
source_ref: 'HEAD', source_ref: 'HEAD',
plan_notes: null, round_trip_count: 0, plan_notes: null,
round_trip_count: 0,
review_requested_at: '2026-03-28T00:00:00.000Z', review_requested_at: '2026-03-28T00:00:00.000Z',
status: 'review_ready', status: 'review_ready',
created_at: '2026-03-28T00:00:00.000Z', created_at: '2026-03-28T00:00:00.000Z',
@@ -280,7 +282,8 @@ describe('paired execution context', () => {
reviewer_service_id: 'codex-review', reviewer_service_id: 'codex-review',
title: null, title: null,
source_ref: 'HEAD', source_ref: 'HEAD',
plan_notes: null, round_trip_count: 0, plan_notes: null,
round_trip_count: 0,
review_requested_at: null, review_requested_at: null,
status: 'active', status: 'active',
created_at: '2026-03-28T00:00:00.000Z', created_at: '2026-03-28T00:00:00.000Z',
@@ -320,7 +323,8 @@ describe('paired execution context', () => {
reviewer_service_id: 'codex-review', reviewer_service_id: 'codex-review',
title: null, title: null,
source_ref: 'HEAD', source_ref: 'HEAD',
plan_notes: null, round_trip_count: 0, plan_notes: null,
round_trip_count: 0,
review_requested_at: '2026-03-28T00:00:00.000Z', review_requested_at: '2026-03-28T00:00:00.000Z',
status: 'in_review', status: 'in_review',
created_at: '2026-03-28T00:00:00.000Z', created_at: '2026-03-28T00:00:00.000Z',
@@ -374,7 +378,8 @@ describe('paired execution context', () => {
reviewer_service_id: 'codex-review', reviewer_service_id: 'codex-review',
title: null, title: null,
source_ref: 'HEAD', source_ref: 'HEAD',
plan_notes: null, round_trip_count: 0, plan_notes: null,
round_trip_count: 0,
review_requested_at: null, review_requested_at: null,
status: 'active', status: 'active',
created_at: '2026-03-28T00:00:00.000Z', created_at: '2026-03-28T00:00:00.000Z',
@@ -388,7 +393,8 @@ describe('paired execution context', () => {
reviewer_service_id: 'codex-review', reviewer_service_id: 'codex-review',
title: null, title: null,
source_ref: 'HEAD', source_ref: 'HEAD',
plan_notes: null, round_trip_count: 0, plan_notes: null,
round_trip_count: 0,
review_requested_at: '2026-03-28T00:00:00.000Z', review_requested_at: '2026-03-28T00:00:00.000Z',
status: 'review_ready', status: 'review_ready',
created_at: '2026-03-28T00:00:00.000Z', created_at: '2026-03-28T00:00:00.000Z',
@@ -455,7 +461,8 @@ describe('paired execution context', () => {
reviewer_service_id: 'codex-review', reviewer_service_id: 'codex-review',
title: null, title: null,
source_ref: 'HEAD', source_ref: 'HEAD',
plan_notes: null, round_trip_count: 0, plan_notes: null,
round_trip_count: 0,
review_requested_at: null, review_requested_at: null,
status: 'active', status: 'active',
created_at: '2026-03-28T00:00:00.000Z', created_at: '2026-03-28T00:00:00.000Z',
@@ -469,7 +476,8 @@ describe('paired execution context', () => {
reviewer_service_id: 'codex-review', reviewer_service_id: 'codex-review',
title: null, title: null,
source_ref: 'HEAD', source_ref: 'HEAD',
plan_notes: null, round_trip_count: 0, plan_notes: null,
round_trip_count: 0,
review_requested_at: '2026-03-28T00:00:00.000Z', review_requested_at: '2026-03-28T00:00:00.000Z',
status: 'review_ready', status: 'review_ready',
created_at: '2026-03-28T00:00:00.000Z', created_at: '2026-03-28T00:00:00.000Z',
@@ -511,7 +519,8 @@ describe('paired execution context', () => {
reviewer_service_id: 'codex-review', reviewer_service_id: 'codex-review',
title: null, title: null,
source_ref: 'HEAD', source_ref: 'HEAD',
plan_notes: null, round_trip_count: 0, plan_notes: null,
round_trip_count: 0,
review_requested_at: null, review_requested_at: null,
status: 'active', status: 'active',
created_at: '2026-03-28T00:00:00.000Z', created_at: '2026-03-28T00:00:00.000Z',
@@ -525,7 +534,8 @@ describe('paired execution context', () => {
reviewer_service_id: 'codex-review', reviewer_service_id: 'codex-review',
title: null, title: null,
source_ref: 'HEAD', source_ref: 'HEAD',
plan_notes: null, round_trip_count: 0, plan_notes: null,
round_trip_count: 0,
review_requested_at: '2026-03-29T00:00:00.000Z', review_requested_at: '2026-03-29T00:00:00.000Z',
status: 'review_ready', status: 'review_ready',
created_at: '2026-03-28T00:00:00.000Z', created_at: '2026-03-28T00:00:00.000Z',

View File

@@ -1,7 +1,11 @@
import { execFileSync } from 'child_process'; import { execFileSync } from 'child_process';
import crypto from 'crypto'; import crypto from 'crypto';
import { SERVICE_ID, normalizeServiceId, PAIRED_MAX_ROUND_TRIPS } from './config.js'; import {
SERVICE_ID,
normalizeServiceId,
PAIRED_MAX_ROUND_TRIPS,
} from './config.js';
import { import {
createPairedTask, createPairedTask,
getLatestPairedTaskForChat, getLatestPairedTaskForChat,
@@ -228,7 +232,11 @@ export function completePairedExecutionContext(args: {
if (role === 'owner') { if (role === 'owner') {
if (task.round_trip_count >= PAIRED_MAX_ROUND_TRIPS) { if (task.round_trip_count >= PAIRED_MAX_ROUND_TRIPS) {
logger.info( logger.info(
{ taskId, roundTrips: task.round_trip_count, max: PAIRED_MAX_ROUND_TRIPS }, {
taskId,
roundTrips: task.round_trip_count,
max: PAIRED_MAX_ROUND_TRIPS,
},
'Round trip limit reached, skipping auto-review', 'Round trip limit reached, skipping auto-review',
); );
return; return;

View File

@@ -74,7 +74,8 @@ describe('paired workspace manager', () => {
reviewer_service_id: 'codex-review', reviewer_service_id: 'codex-review',
title: 'review the owner changes', title: 'review the owner changes',
source_ref: 'HEAD', source_ref: 'HEAD',
plan_notes: null, round_trip_count: 0, plan_notes: null,
round_trip_count: 0,
review_requested_at: null, review_requested_at: null,
status: 'active', status: 'active',
created_at: now, created_at: now,
@@ -162,7 +163,8 @@ describe('paired workspace manager', () => {
reviewer_service_id: 'codex-review', reviewer_service_id: 'codex-review',
title: null, title: null,
source_ref: 'HEAD', source_ref: 'HEAD',
plan_notes: null, round_trip_count: 0, plan_notes: null,
round_trip_count: 0,
review_requested_at: null, review_requested_at: null,
status: 'active', status: 'active',
created_at: now, created_at: now,
@@ -214,7 +216,8 @@ describe('paired workspace manager', () => {
reviewer_service_id: 'codex-review', reviewer_service_id: 'codex-review',
title: 'cross service review', title: 'cross service review',
source_ref: 'HEAD', source_ref: 'HEAD',
plan_notes: null, round_trip_count: 0, plan_notes: null,
round_trip_count: 0,
review_requested_at: null, review_requested_at: null,
status: 'active', status: 'active',
created_at: now, created_at: now,
@@ -289,7 +292,8 @@ describe('paired workspace manager', () => {
reviewer_service_id: 'codex-review', reviewer_service_id: 'codex-review',
title: null, title: null,
source_ref: 'HEAD', source_ref: 'HEAD',
plan_notes: null, round_trip_count: 0, plan_notes: null,
round_trip_count: 0,
review_requested_at: null, review_requested_at: null,
status: 'active', status: 'active',
created_at: now, created_at: now,
@@ -363,7 +367,8 @@ describe('paired workspace manager', () => {
reviewer_service_id: 'codex-review', reviewer_service_id: 'codex-review',
title: null, title: null,
source_ref: 'HEAD', source_ref: 'HEAD',
plan_notes: null, round_trip_count: 0, plan_notes: null,
round_trip_count: 0,
review_requested_at: null, review_requested_at: null,
status: 'active', status: 'active',
created_at: now, created_at: now,
@@ -503,7 +508,8 @@ describe('paired workspace manager', () => {
reviewer_service_id: 'codex-review', reviewer_service_id: 'codex-review',
title: null, title: null,
source_ref: 'HEAD', source_ref: 'HEAD',
plan_notes: null, round_trip_count: 0, plan_notes: null,
round_trip_count: 0,
review_requested_at: null, review_requested_at: null,
status: 'active', status: 'active',
created_at: now, created_at: now,
@@ -564,7 +570,8 @@ describe('paired workspace manager', () => {
reviewer_service_id: 'codex-review', reviewer_service_id: 'codex-review',
title: null, title: null,
source_ref: 'HEAD', source_ref: 'HEAD',
plan_notes: null, round_trip_count: 0, plan_notes: null,
round_trip_count: 0,
review_requested_at: null, review_requested_at: null,
status: 'active', status: 'active',
created_at: now, created_at: now,
@@ -619,7 +626,8 @@ describe('paired workspace manager', () => {
reviewer_service_id: 'codex-review', reviewer_service_id: 'codex-review',
title: null, title: null,
source_ref: 'HEAD', source_ref: 'HEAD',
plan_notes: null, round_trip_count: 0, plan_notes: null,
round_trip_count: 0,
review_requested_at: '2026-03-28T00:01:00.000Z', review_requested_at: '2026-03-28T00:01:00.000Z',
status: 'review_ready', status: 'review_ready',
created_at: now, created_at: now,
@@ -680,7 +688,8 @@ describe('paired workspace manager', () => {
reviewer_service_id: 'codex-review', reviewer_service_id: 'codex-review',
title: null, title: null,
source_ref: 'HEAD', source_ref: 'HEAD',
plan_notes: null, round_trip_count: 0, plan_notes: null,
round_trip_count: 0,
review_requested_at: null, review_requested_at: null,
status: 'active', status: 'active',
created_at: now, created_at: now,
@@ -743,7 +752,8 @@ describe('paired workspace manager', () => {
reviewer_service_id: 'codex-review', reviewer_service_id: 'codex-review',
title: null, title: null,
source_ref: 'HEAD', source_ref: 'HEAD',
plan_notes: null, round_trip_count: 0, plan_notes: null,
round_trip_count: 0,
review_requested_at: null, review_requested_at: null,
status: 'active', status: 'active',
created_at: now, created_at: now,
@@ -831,7 +841,8 @@ describe('paired workspace manager', () => {
reviewer_service_id: 'codex-review', reviewer_service_id: 'codex-review',
title: null, title: null,
source_ref: 'HEAD', source_ref: 'HEAD',
plan_notes: null, round_trip_count: 0, plan_notes: null,
round_trip_count: 0,
review_requested_at: null, review_requested_at: null,
status: 'active', status: 'active',
created_at: now, created_at: now,

View File

@@ -73,7 +73,7 @@ export interface SessionCommandDeps {
onOutput: (result: AgentResult) => Promise<void>, onOutput: (result: AgentResult) => Promise<void>,
) => Promise<'success' | 'error'>; ) => Promise<'success' | 'error'>;
closeStdin: () => void; closeStdin: () => void;
clearSession: () => void; clearSession: (opts?: { allRoles?: boolean }) => void;
advanceCursor: (timestamp: string) => void; advanceCursor: (timestamp: string) => void;
formatMessages: (msgs: NewMessage[], timezone: string) => string; formatMessages: (msgs: NewMessage[], timezone: string) => string;
isAdminSender: (msg: NewMessage) => boolean; isAdminSender: (msg: NewMessage) => boolean;
@@ -158,7 +158,7 @@ export async function handleSessionCommand(opts: {
if (command === '/clear') { if (command === '/clear') {
deps.closeStdin(); deps.closeStdin();
deps.clearSession(); deps.clearSession({ allRoles: true });
deps.advanceCursor(cmdMsg.timestamp); deps.advanceCursor(cmdMsg.timestamp);
await deps.sendMessage( await deps.sendMessage(
'Current session cleared. The next message will start a new conversation.', 'Current session cleared. The next message will start a new conversation.',