Files
EJClaw/src/runtime-state.ts
Eyejoker 86e3fa7521 [codex] Clear role-scoped sessions (#139)
* add gated codex goals support

* sync README SDK versions

* bump claude agent sdk

* add codex goals settings toggle

* reuse status dashboard message and simplify settings actions

* refine settings page UX

* fix settings nav hash routing

* add dashboard UX verification

* refine dashboard settings and inbox UX

* remove inbox top-level navigation

* remove dashboard health top-level navigation

* fix: allow runtime image attachment paths

* feat: configure attachment allowlist dirs

* fix: clean duplicate dashboard status messages

* fix: poll dashboard duplicate cleanup between status updates

* fix: delete dashboard duplicates on create

* Refine settings IA with tabbed sections

* Add read-only runtime inventory settings

* Fix runtime inventory MCP JSON parsing

* Add room skill settings inventory

* Add room skill setting mutations

* Apply room skill overrides to runner spawn

* Refine scheduled task dashboard UX

* Remove reviewer final prompt reinjection

* Stabilize pnpm verification fixture

* Remove previous owner final prompt carryover

* Clear role-scoped sessions
2026-05-17 01:02:40 +08:00

141 lines
4.1 KiB
TypeScript

import fs from 'fs';
import path from 'path';
import { listAvailableGroups } from './available-groups.js';
import {
type AssignRoomInput,
assignRoom,
deleteAllSessionsForGroup,
deleteSession,
getAllRoomBindings,
getAllSessions,
getRouterState,
setRouterState,
setSession,
} from './db.js';
import { resolveGroupFolderPath } from './group-folder.js';
import { logger } from './logger.js';
import { normalizeStoredSeqCursor } from './message-cursor.js';
import type { AgentType, RegisteredGroup } from './types.js';
export interface RuntimeState {
loadState: () => void;
saveState: () => void;
clearSession: (groupFolder: string, opts?: { allRoles?: boolean }) => void;
assignRoomForIpc: (jid: string, input: AssignRoomInput) => void;
getAvailableGroups: () => import('./agent-runner.js').AvailableGroup[];
getLastTimestamp: () => string;
setLastTimestamp: (timestamp: string) => void;
getLastAgentTimestamps: () => Record<string, string>;
getSessions: () => Record<string, string>;
persistSession: (groupFolder: string, sessionId: string) => void;
getRoomBindings: () => Record<string, RegisteredGroup>;
setRoomBindings: (groups: Record<string, RegisteredGroup>) => void;
}
export function createRuntimeState(): RuntimeState {
let lastTimestamp = '';
let sessions: Record<string, string> = {};
let roomBindings: Record<string, RegisteredGroup> = {};
let lastAgentTimestamp: Record<string, string> = {};
const saveState = (): void => {
setRouterState('last_seq', lastTimestamp);
setRouterState('last_agent_seq', JSON.stringify(lastAgentTimestamp));
};
const clearSession = (
groupFolder: string,
opts?: { allRoles?: boolean },
): void => {
if (opts?.allRoles) {
for (const key of [
groupFolder,
`${groupFolder}:reviewer`,
`${groupFolder}:arbiter`,
]) {
delete sessions[key];
}
deleteAllSessionsForGroup(groupFolder);
} else {
delete sessions[groupFolder];
deleteSession(groupFolder);
}
};
const persistSession = (groupFolder: string, sessionId: string): void => {
sessions[groupFolder] = sessionId;
setSession(groupFolder, sessionId);
};
const loadState = (): void => {
lastTimestamp = normalizeStoredSeqCursor(getRouterState('last_seq'));
const agentTs = getRouterState('last_agent_seq');
try {
const parsed = agentTs
? (JSON.parse(agentTs) as Record<string, string>)
: {};
lastAgentTimestamp = Object.fromEntries(
Object.entries(parsed).map(([chatJid, cursor]) => [
chatJid,
normalizeStoredSeqCursor(cursor, chatJid),
]),
);
} catch {
logger.warn('Corrupted last_agent_seq in DB, resetting');
lastAgentTimestamp = {};
}
sessions = getAllSessions();
roomBindings = getAllRoomBindings();
logger.info(
{
groupCount: Object.keys(roomBindings).length,
agentType: 'unified' satisfies AgentType | 'unified',
},
'State loaded',
);
};
const assignRoomForIpc = (jid: string, input: AssignRoomInput): void => {
const assignedGroup = assignRoom(jid, input);
if (!assignedGroup) {
logger.warn({ jid }, 'Failed to assign room from IPC');
return;
}
let groupDir: string;
try {
groupDir = resolveGroupFolderPath(assignedGroup.folder);
} catch (err) {
logger.warn(
{ jid, folder: assignedGroup.folder, err },
'Rejecting room assignment with invalid folder',
);
return;
}
const { jid: _ignoredJid, ...storedGroup } = assignedGroup;
roomBindings[jid] = storedGroup;
fs.mkdirSync(path.join(groupDir, 'logs'), { recursive: true });
};
return {
loadState,
saveState,
clearSession,
assignRoomForIpc,
getAvailableGroups: () => listAvailableGroups(roomBindings),
getLastTimestamp: () => lastTimestamp,
setLastTimestamp: (timestamp) => {
lastTimestamp = timestamp;
},
getLastAgentTimestamps: () => lastAgentTimestamp,
getSessions: () => sessions,
persistSession,
getRoomBindings: () => roomBindings,
setRoomBindings: (groups) => {
roomBindings = groups;
},
};
}