refactor: complete room registration ssot cleanup
This commit is contained in:
352
src/db.test.ts
352
src/db.test.ts
@@ -7,6 +7,7 @@ import { describe, it, expect, beforeEach } from 'vitest';
|
|||||||
import {
|
import {
|
||||||
_initTestDatabase,
|
_initTestDatabase,
|
||||||
_initTestDatabaseFromFile,
|
_initTestDatabaseFromFile,
|
||||||
|
_deleteStoredRoomSettingsForTests,
|
||||||
_setMemoryTimestampsForTests,
|
_setMemoryTimestampsForTests,
|
||||||
_setRegisteredGroupForTests,
|
_setRegisteredGroupForTests,
|
||||||
assignRoom,
|
assignRoom,
|
||||||
@@ -30,6 +31,7 @@ import {
|
|||||||
getLatestPairedTaskForChat,
|
getLatestPairedTaskForChat,
|
||||||
getLatestTurnNumber,
|
getLatestTurnNumber,
|
||||||
getLastRespondingAgentType,
|
getLastRespondingAgentType,
|
||||||
|
getRegisteredGroup,
|
||||||
getMessagesSinceSeq,
|
getMessagesSinceSeq,
|
||||||
getNewMessagesBySeq,
|
getNewMessagesBySeq,
|
||||||
getOpenWorkItem,
|
getOpenWorkItem,
|
||||||
@@ -1658,6 +1660,326 @@ describe('room assignment writes', () => {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('recreates inferred room_settings when renaming a legacy projection-only room', () => {
|
||||||
|
_setRegisteredGroupForTests('dc:legacy-rename', {
|
||||||
|
name: 'Legacy Rename',
|
||||||
|
folder: 'legacy-rename',
|
||||||
|
trigger: '@Andy',
|
||||||
|
added_at: '2024-01-01T00:00:00.000Z',
|
||||||
|
agentType: 'claude-code',
|
||||||
|
});
|
||||||
|
_setRegisteredGroupForTests('dc:legacy-rename', {
|
||||||
|
name: 'Legacy Rename',
|
||||||
|
folder: 'legacy-rename',
|
||||||
|
trigger: '@Codex',
|
||||||
|
added_at: '2024-01-01T00:00:00.000Z',
|
||||||
|
agentType: 'codex',
|
||||||
|
});
|
||||||
|
|
||||||
|
_deleteStoredRoomSettingsForTests('dc:legacy-rename');
|
||||||
|
expect(getStoredRoomSettings('dc:legacy-rename')).toBeUndefined();
|
||||||
|
|
||||||
|
updateRegisteredGroupName('dc:legacy-rename', 'Legacy Rename Updated');
|
||||||
|
|
||||||
|
expect(getStoredRoomSettings('dc:legacy-rename')).toMatchObject({
|
||||||
|
chatJid: 'dc:legacy-rename',
|
||||||
|
roomMode: 'tribunal',
|
||||||
|
modeSource: 'inferred',
|
||||||
|
name: 'Legacy Rename Updated',
|
||||||
|
folder: 'legacy-rename',
|
||||||
|
});
|
||||||
|
expect(
|
||||||
|
getAllRegisteredGroups('claude-code')['dc:legacy-rename'],
|
||||||
|
).toMatchObject({
|
||||||
|
name: 'Legacy Rename Updated',
|
||||||
|
agentType: 'claude-code',
|
||||||
|
});
|
||||||
|
expect(getAllRegisteredGroups('codex')['dc:legacy-rename']).toMatchObject({
|
||||||
|
name: 'Legacy Rename Updated',
|
||||||
|
agentType: 'codex',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('ignores stale registered_groups capability rows once room_settings exists', () => {
|
||||||
|
const tempDir = fs.mkdtempSync('/tmp/ejclaw-room-ssot-');
|
||||||
|
const dbPath = path.join(tempDir, 'messages.db');
|
||||||
|
const legacyDb = new Database(dbPath);
|
||||||
|
|
||||||
|
legacyDb.exec(`
|
||||||
|
CREATE TABLE room_settings (
|
||||||
|
chat_jid TEXT PRIMARY KEY,
|
||||||
|
room_mode TEXT NOT NULL DEFAULT 'single',
|
||||||
|
mode_source TEXT NOT NULL DEFAULT 'explicit',
|
||||||
|
name TEXT,
|
||||||
|
folder TEXT,
|
||||||
|
trigger_pattern TEXT,
|
||||||
|
requires_trigger INTEGER,
|
||||||
|
is_main INTEGER,
|
||||||
|
owner_agent_type TEXT,
|
||||||
|
work_dir TEXT,
|
||||||
|
updated_at TEXT
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE registered_groups (
|
||||||
|
jid TEXT NOT NULL,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
folder TEXT NOT NULL,
|
||||||
|
trigger_pattern TEXT NOT NULL,
|
||||||
|
added_at TEXT NOT NULL,
|
||||||
|
agent_config TEXT,
|
||||||
|
requires_trigger INTEGER,
|
||||||
|
is_main INTEGER,
|
||||||
|
agent_type TEXT,
|
||||||
|
work_dir TEXT,
|
||||||
|
PRIMARY KEY (jid, agent_type),
|
||||||
|
UNIQUE (folder, agent_type)
|
||||||
|
);
|
||||||
|
`);
|
||||||
|
|
||||||
|
legacyDb
|
||||||
|
.prepare(
|
||||||
|
`INSERT INTO room_settings (
|
||||||
|
chat_jid,
|
||||||
|
room_mode,
|
||||||
|
mode_source,
|
||||||
|
name,
|
||||||
|
folder,
|
||||||
|
trigger_pattern,
|
||||||
|
requires_trigger,
|
||||||
|
is_main,
|
||||||
|
owner_agent_type,
|
||||||
|
work_dir,
|
||||||
|
updated_at
|
||||||
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||||
|
)
|
||||||
|
.run(
|
||||||
|
'dc:ssot-room',
|
||||||
|
'single',
|
||||||
|
'explicit',
|
||||||
|
'SSOT Room',
|
||||||
|
'ssot-room',
|
||||||
|
'@Andy',
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
'codex',
|
||||||
|
null,
|
||||||
|
'2026-04-08T00:00:00.000Z',
|
||||||
|
);
|
||||||
|
|
||||||
|
legacyDb
|
||||||
|
.prepare(
|
||||||
|
`INSERT INTO registered_groups (
|
||||||
|
jid,
|
||||||
|
name,
|
||||||
|
folder,
|
||||||
|
trigger_pattern,
|
||||||
|
added_at,
|
||||||
|
agent_config,
|
||||||
|
requires_trigger,
|
||||||
|
is_main,
|
||||||
|
agent_type,
|
||||||
|
work_dir
|
||||||
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||||
|
)
|
||||||
|
.run(
|
||||||
|
'dc:ssot-room',
|
||||||
|
'Stale Projection',
|
||||||
|
'ssot-room',
|
||||||
|
'@Codex',
|
||||||
|
'2026-04-08T00:00:00.000Z',
|
||||||
|
null,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
'codex',
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
|
||||||
|
legacyDb
|
||||||
|
.prepare(
|
||||||
|
`INSERT OR REPLACE INTO registered_groups (
|
||||||
|
jid,
|
||||||
|
name,
|
||||||
|
folder,
|
||||||
|
trigger_pattern,
|
||||||
|
added_at,
|
||||||
|
agent_config,
|
||||||
|
requires_trigger,
|
||||||
|
is_main,
|
||||||
|
agent_type,
|
||||||
|
work_dir
|
||||||
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||||
|
)
|
||||||
|
.run(
|
||||||
|
'dc:ssot-room',
|
||||||
|
'Stale Projection',
|
||||||
|
'ssot-room',
|
||||||
|
'@Claude',
|
||||||
|
'2026-04-08T00:00:00.000Z',
|
||||||
|
null,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
'claude-code',
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
legacyDb.close();
|
||||||
|
|
||||||
|
_initTestDatabaseFromFile(dbPath);
|
||||||
|
|
||||||
|
expect(getRegisteredGroup('dc:ssot-room')).toMatchObject({
|
||||||
|
folder: 'ssot-room',
|
||||||
|
agentType: 'codex',
|
||||||
|
});
|
||||||
|
expect(getRegisteredGroup('dc:ssot-room', 'claude-code')).toBeUndefined();
|
||||||
|
expect(getAllRegisteredGroups('claude-code')['dc:ssot-room']).toBeUndefined();
|
||||||
|
expect(getRegisteredAgentTypesForJid('dc:ssot-room')).toEqual(['codex']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('re-materializes explicit room_settings writes back into the projection rows', () => {
|
||||||
|
const tempDir = fs.mkdtempSync('/tmp/ejclaw-room-writeback-');
|
||||||
|
const dbPath = path.join(tempDir, 'messages.db');
|
||||||
|
const legacyDb = new Database(dbPath);
|
||||||
|
|
||||||
|
legacyDb.exec(`
|
||||||
|
CREATE TABLE room_settings (
|
||||||
|
chat_jid TEXT PRIMARY KEY,
|
||||||
|
room_mode TEXT NOT NULL DEFAULT 'single',
|
||||||
|
mode_source TEXT NOT NULL DEFAULT 'explicit',
|
||||||
|
name TEXT,
|
||||||
|
folder TEXT,
|
||||||
|
trigger_pattern TEXT,
|
||||||
|
requires_trigger INTEGER,
|
||||||
|
is_main INTEGER,
|
||||||
|
owner_agent_type TEXT,
|
||||||
|
work_dir TEXT,
|
||||||
|
updated_at TEXT
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE registered_groups (
|
||||||
|
jid TEXT NOT NULL,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
folder TEXT NOT NULL,
|
||||||
|
trigger_pattern TEXT NOT NULL,
|
||||||
|
added_at TEXT NOT NULL,
|
||||||
|
agent_config TEXT,
|
||||||
|
requires_trigger INTEGER,
|
||||||
|
is_main INTEGER,
|
||||||
|
agent_type TEXT,
|
||||||
|
work_dir TEXT,
|
||||||
|
PRIMARY KEY (jid, agent_type),
|
||||||
|
UNIQUE (folder, agent_type)
|
||||||
|
);
|
||||||
|
`);
|
||||||
|
|
||||||
|
legacyDb
|
||||||
|
.prepare(
|
||||||
|
`INSERT INTO room_settings (
|
||||||
|
chat_jid,
|
||||||
|
room_mode,
|
||||||
|
mode_source,
|
||||||
|
name,
|
||||||
|
folder,
|
||||||
|
trigger_pattern,
|
||||||
|
requires_trigger,
|
||||||
|
is_main,
|
||||||
|
owner_agent_type,
|
||||||
|
work_dir,
|
||||||
|
updated_at
|
||||||
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||||
|
)
|
||||||
|
.run(
|
||||||
|
'dc:ssot-writeback',
|
||||||
|
'single',
|
||||||
|
'explicit',
|
||||||
|
'Explicit Writeback',
|
||||||
|
'ssot-writeback',
|
||||||
|
'@Codex',
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
'codex',
|
||||||
|
null,
|
||||||
|
'2026-04-08T00:00:00.000Z',
|
||||||
|
);
|
||||||
|
|
||||||
|
const insertProjection = legacyDb.prepare(
|
||||||
|
`INSERT INTO registered_groups (
|
||||||
|
jid,
|
||||||
|
name,
|
||||||
|
folder,
|
||||||
|
trigger_pattern,
|
||||||
|
added_at,
|
||||||
|
agent_config,
|
||||||
|
requires_trigger,
|
||||||
|
is_main,
|
||||||
|
agent_type,
|
||||||
|
work_dir
|
||||||
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||||
|
);
|
||||||
|
|
||||||
|
insertProjection.run(
|
||||||
|
'dc:ssot-writeback',
|
||||||
|
'Stale Projection',
|
||||||
|
'ssot-writeback',
|
||||||
|
'@Codex',
|
||||||
|
'2026-04-08T00:00:00.000Z',
|
||||||
|
null,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
'codex',
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
insertProjection.run(
|
||||||
|
'dc:ssot-writeback',
|
||||||
|
'Stale Projection',
|
||||||
|
'ssot-writeback',
|
||||||
|
'@Claude',
|
||||||
|
'2026-04-08T00:00:00.000Z',
|
||||||
|
null,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
'claude-code',
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
legacyDb.close();
|
||||||
|
|
||||||
|
_initTestDatabaseFromFile(dbPath);
|
||||||
|
|
||||||
|
updateRegisteredGroupName('dc:ssot-writeback', 'SSOT Writeback Renamed');
|
||||||
|
|
||||||
|
expect(getStoredRoomSettings('dc:ssot-writeback')).toMatchObject({
|
||||||
|
chatJid: 'dc:ssot-writeback',
|
||||||
|
roomMode: 'single',
|
||||||
|
modeSource: 'explicit',
|
||||||
|
name: 'SSOT Writeback Renamed',
|
||||||
|
ownerAgentType: 'codex',
|
||||||
|
});
|
||||||
|
|
||||||
|
const rawDb = new Database(dbPath);
|
||||||
|
const projectionRows = rawDb
|
||||||
|
.prepare(
|
||||||
|
`SELECT agent_type, name
|
||||||
|
FROM registered_groups
|
||||||
|
WHERE jid = ?
|
||||||
|
ORDER BY agent_type`,
|
||||||
|
)
|
||||||
|
.all('dc:ssot-writeback') as Array<{
|
||||||
|
agent_type: string | null;
|
||||||
|
name: string;
|
||||||
|
}>;
|
||||||
|
rawDb.close();
|
||||||
|
|
||||||
|
expect(projectionRows).toEqual([
|
||||||
|
{
|
||||||
|
agent_type: 'codex',
|
||||||
|
name: 'SSOT Writeback Renamed',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
clearExplicitRoomMode('dc:ssot-writeback');
|
||||||
|
|
||||||
|
expect(getExplicitRoomMode('dc:ssot-writeback')).toBeUndefined();
|
||||||
|
expect(getEffectiveRoomMode('dc:ssot-writeback')).toBe('single');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('paired room registration', () => {
|
describe('paired room registration', () => {
|
||||||
@@ -1938,6 +2260,36 @@ describe('paired room registration', () => {
|
|||||||
expect(getEffectiveRuntimeRoomMode('dc:explicit-single')).toBe('single');
|
expect(getEffectiveRuntimeRoomMode('dc:explicit-single')).toBe('single');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('restores inferred paired mode when clearing an explicit single override', () => {
|
||||||
|
_setRegisteredGroupForTests('dc:explicit-single-clear', {
|
||||||
|
name: 'Explicit Single Clear',
|
||||||
|
folder: 'explicit-single-clear',
|
||||||
|
trigger: '@Andy',
|
||||||
|
added_at: '2024-01-01T00:00:00.000Z',
|
||||||
|
agentType: 'claude-code',
|
||||||
|
});
|
||||||
|
_setRegisteredGroupForTests('dc:explicit-single-clear', {
|
||||||
|
name: 'Explicit Single Clear',
|
||||||
|
folder: 'explicit-single-clear',
|
||||||
|
trigger: '@Codex',
|
||||||
|
added_at: '2024-01-01T00:00:00.000Z',
|
||||||
|
agentType: 'codex',
|
||||||
|
});
|
||||||
|
|
||||||
|
setExplicitRoomMode('dc:explicit-single-clear', 'single');
|
||||||
|
|
||||||
|
expect(getExplicitRoomMode('dc:explicit-single-clear')).toBe('single');
|
||||||
|
expect(getEffectiveRoomMode('dc:explicit-single-clear')).toBe('single');
|
||||||
|
|
||||||
|
clearExplicitRoomMode('dc:explicit-single-clear');
|
||||||
|
|
||||||
|
expect(getExplicitRoomMode('dc:explicit-single-clear')).toBeUndefined();
|
||||||
|
expect(getEffectiveRoomMode('dc:explicit-single-clear')).toBe('tribunal');
|
||||||
|
expect(getEffectiveRuntimeRoomMode('dc:explicit-single-clear')).toBe(
|
||||||
|
'tribunal',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it('lets explicit tribunal become runnable when the configured reviewer can run on the solo registration', () => {
|
it('lets explicit tribunal become runnable when the configured reviewer can run on the solo registration', () => {
|
||||||
_setRegisteredGroupForTests('dc:explicit-tribunal', {
|
_setRegisteredGroupForTests('dc:explicit-tribunal', {
|
||||||
name: 'Explicit Tribunal Claude',
|
name: 'Explicit Tribunal Claude',
|
||||||
|
|||||||
170
src/db.ts
170
src/db.ts
@@ -39,6 +39,7 @@ import {
|
|||||||
normalizeRoomModeSource,
|
normalizeRoomModeSource,
|
||||||
normalizeStoredAgentType,
|
normalizeStoredAgentType,
|
||||||
parseRegisteredGroupRow,
|
parseRegisteredGroupRow,
|
||||||
|
resolveStoredRoomCapabilityTypes,
|
||||||
resolveAssignedRoomFolder,
|
resolveAssignedRoomFolder,
|
||||||
updateStoredRoomMetadata,
|
updateStoredRoomMetadata,
|
||||||
} from './db/room-registration.js';
|
} from './db/room-registration.js';
|
||||||
@@ -313,6 +314,14 @@ export function _setStoredRoomOwnerAgentTypeForTests(
|
|||||||
).run(ownerAgentType, new Date().toISOString(), chatJid);
|
).run(ownerAgentType, new Date().toISOString(), chatJid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @internal - for tests only. */
|
||||||
|
export function _deleteStoredRoomSettingsForTests(chatJid: string): void {
|
||||||
|
if (!db) {
|
||||||
|
throw new Error('Database not initialized');
|
||||||
|
}
|
||||||
|
db.prepare('DELETE FROM room_settings WHERE chat_jid = ?').run(chatJid);
|
||||||
|
}
|
||||||
|
|
||||||
/** @internal - for tests only. */
|
/** @internal - for tests only. */
|
||||||
export function _setMemoryTimestampsForTests(
|
export function _setMemoryTimestampsForTests(
|
||||||
id: number,
|
id: number,
|
||||||
@@ -749,12 +758,7 @@ export function getRegisteredGroup(
|
|||||||
const requestedAgentType = normalizeStoredAgentType(agentType);
|
const requestedAgentType = normalizeStoredAgentType(agentType);
|
||||||
const stored = getStoredRoomSettingsRowFromDatabase(db, jid);
|
const stored = getStoredRoomSettingsRowFromDatabase(db, jid);
|
||||||
if (stored) {
|
if (stored) {
|
||||||
const group = buildRegisteredGroupFromStoredSettings(
|
return buildRegisteredGroupFromStoredSettings(db, stored, requestedAgentType);
|
||||||
db,
|
|
||||||
stored,
|
|
||||||
requestedAgentType,
|
|
||||||
);
|
|
||||||
if (group) return group;
|
|
||||||
}
|
}
|
||||||
return getLegacyRegisteredGroup(db, jid, agentType);
|
return getLegacyRegisteredGroup(db, jid, agentType);
|
||||||
}
|
}
|
||||||
@@ -763,31 +767,58 @@ function writeLegacyRegisteredGroupAndSyncRoomSettings(
|
|||||||
jid: string,
|
jid: string,
|
||||||
group: RegisteredGroup,
|
group: RegisteredGroup,
|
||||||
): void {
|
): void {
|
||||||
|
const existingStored = getStoredRoomSettingsRowFromDatabase(db, jid);
|
||||||
const existingRoomMode = getStoredRoomModeRow(jid);
|
const existingRoomMode = getStoredRoomModeRow(jid);
|
||||||
if (!isValidGroupFolder(group.folder)) {
|
if (!isValidGroupFolder(group.folder)) {
|
||||||
throw new Error(`Invalid group folder "${group.folder}" for JID ${jid}`);
|
throw new Error(`Invalid group folder "${group.folder}" for JID ${jid}`);
|
||||||
}
|
}
|
||||||
const tx = db.transaction(() => {
|
const tx = db.transaction(() => {
|
||||||
db.prepare(
|
const seededAgentType = group.agentType || 'claude-code';
|
||||||
`INSERT OR REPLACE INTO registered_groups (jid, name, folder, trigger_pattern, added_at, agent_config, requires_trigger, is_main, agent_type, work_dir)
|
const agentTypes = new Set<AgentType>(collectRegisteredAgentTypes(db, jid));
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
agentTypes.add(seededAgentType);
|
||||||
).run(
|
const inferredRoomMode = inferRoomModeFromRegisteredAgentTypes([
|
||||||
jid,
|
...agentTypes,
|
||||||
group.name,
|
]);
|
||||||
group.folder,
|
const roomMode =
|
||||||
group.trigger,
|
existingRoomMode?.source === 'explicit'
|
||||||
group.added_at,
|
? existingRoomMode.roomMode
|
||||||
group.agentConfig ? JSON.stringify(group.agentConfig) : null,
|
: inferredRoomMode;
|
||||||
group.requiresTrigger === undefined ? 1 : group.requiresTrigger ? 1 : 0,
|
const ownerAgentType =
|
||||||
group.isMain ? 1 : 0,
|
existingStored?.modeSource === 'explicit' && existingStored.ownerAgentType
|
||||||
group.agentType || 'claude-code',
|
? existingStored.ownerAgentType
|
||||||
group.workDir || null,
|
: inferOwnerAgentTypeFromRegisteredAgentTypes([...agentTypes]);
|
||||||
);
|
const snapshot: RoomRegistrationSnapshot = {
|
||||||
|
name: group.name,
|
||||||
|
folder: group.folder,
|
||||||
|
triggerPattern:
|
||||||
|
existingStored?.modeSource === 'explicit' && existingStored.trigger
|
||||||
|
? existingStored.trigger
|
||||||
|
: group.trigger,
|
||||||
|
requiresTrigger:
|
||||||
|
group.requiresTrigger ?? existingStored?.requiresTrigger ?? true,
|
||||||
|
isMain: group.isMain ?? existingStored?.isMain ?? false,
|
||||||
|
ownerAgentType,
|
||||||
|
workDir: group.workDir ?? existingStored?.workDir ?? null,
|
||||||
|
};
|
||||||
|
|
||||||
syncStoredRoomRegistrationSnapshotForJid(jid);
|
if (existingStored) {
|
||||||
if (!existingRoomMode || existingRoomMode.source === 'inferred') {
|
updateStoredRoomMetadata(db, jid, snapshot);
|
||||||
syncInferredRoomModeForJid(jid);
|
if (!existingRoomMode || existingRoomMode.source === 'inferred') {
|
||||||
|
upsertStoredRoomMode(jid, roomMode, 'inferred');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
insertStoredRoomSettings(db, jid, roomMode, 'inferred', snapshot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
materializeRegisteredGroupsForRoom(
|
||||||
|
db,
|
||||||
|
jid,
|
||||||
|
snapshot,
|
||||||
|
roomMode,
|
||||||
|
ownerAgentType,
|
||||||
|
seededAgentType === ownerAgentType ? group.agentConfig : undefined,
|
||||||
|
group.added_at,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
tx();
|
tx();
|
||||||
}
|
}
|
||||||
@@ -874,18 +905,24 @@ export function assignRoom(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function updateRegisteredGroupName(jid: string, name: string): void {
|
export function updateRegisteredGroupName(jid: string, name: string): void {
|
||||||
db.prepare(
|
const plan = buildRoomRegistrationPlanForJid(jid, { name });
|
||||||
`UPDATE room_settings
|
if (!plan) {
|
||||||
SET name = ?, updated_at = ?
|
return;
|
||||||
WHERE chat_jid = ?`,
|
|
||||||
).run(name, new Date().toISOString(), jid);
|
|
||||||
db.prepare('UPDATE registered_groups SET name = ? WHERE jid = ?').run(
|
|
||||||
name,
|
|
||||||
jid,
|
|
||||||
);
|
|
||||||
if (!getStoredRoomSettingsRowFromDatabase(db, jid)) {
|
|
||||||
syncStoredRoomRegistrationSnapshotForJid(jid);
|
|
||||||
}
|
}
|
||||||
|
db.transaction(() => {
|
||||||
|
if (plan.hasStoredRoom) {
|
||||||
|
updateStoredRoomMetadata(db, jid, plan.snapshot);
|
||||||
|
} else {
|
||||||
|
insertStoredRoomSettings(db, jid, plan.roomMode, 'inferred', plan.snapshot);
|
||||||
|
}
|
||||||
|
materializeRegisteredGroupsForRoom(
|
||||||
|
db,
|
||||||
|
jid,
|
||||||
|
plan.snapshot,
|
||||||
|
plan.roomMode,
|
||||||
|
plan.snapshot.ownerAgentType,
|
||||||
|
);
|
||||||
|
})();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getAllRegisteredGroups(
|
export function getAllRegisteredGroups(
|
||||||
@@ -921,6 +958,10 @@ export function getAllRegisteredGroups(
|
|||||||
|
|
||||||
export function getRegisteredAgentTypesForJid(jid: string): AgentType[] {
|
export function getRegisteredAgentTypesForJid(jid: string): AgentType[] {
|
||||||
if (!db) return [];
|
if (!db) return [];
|
||||||
|
const stored = getStoredRoomSettingsRowFromDatabase(db, jid);
|
||||||
|
if (stored) {
|
||||||
|
return resolveStoredRoomCapabilityTypes(db, stored);
|
||||||
|
}
|
||||||
return collectRegisteredAgentTypes(db, jid);
|
return collectRegisteredAgentTypes(db, jid);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -977,6 +1018,63 @@ function syncStoredRoomRegistrationSnapshotForJid(chatJid: string): void {
|
|||||||
updateStoredRoomMetadata(db, chatJid, snapshot);
|
updateStoredRoomMetadata(db, chatJid, snapshot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildRoomRegistrationSnapshotFromStoredRoom(
|
||||||
|
stored: StoredRoomSettings,
|
||||||
|
overrides?: Partial<Pick<RoomRegistrationSnapshot, 'name'>>,
|
||||||
|
): RoomRegistrationSnapshot {
|
||||||
|
const capabilityTypes = resolveStoredRoomCapabilityTypes(db, stored);
|
||||||
|
const ownerAgentType =
|
||||||
|
stored.ownerAgentType ||
|
||||||
|
(capabilityTypes.length > 0
|
||||||
|
? inferOwnerAgentTypeFromRegisteredAgentTypes(capabilityTypes)
|
||||||
|
: OWNER_AGENT_TYPE);
|
||||||
|
const name = overrides?.name ?? stored.name ?? stored.chatJid;
|
||||||
|
|
||||||
|
return {
|
||||||
|
name,
|
||||||
|
folder: resolveAssignedRoomFolder(db, stored.chatJid, name, stored.folder),
|
||||||
|
triggerPattern: stored.trigger || `@${ASSISTANT_NAME}`,
|
||||||
|
requiresTrigger: stored.requiresTrigger ?? true,
|
||||||
|
isMain: stored.isMain ?? false,
|
||||||
|
ownerAgentType,
|
||||||
|
workDir: stored.workDir ?? null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildRoomRegistrationPlanForJid(
|
||||||
|
chatJid: string,
|
||||||
|
overrides?: Partial<Pick<RoomRegistrationSnapshot, 'name'>>,
|
||||||
|
):
|
||||||
|
| {
|
||||||
|
snapshot: RoomRegistrationSnapshot;
|
||||||
|
roomMode: RoomMode;
|
||||||
|
hasStoredRoom: boolean;
|
||||||
|
}
|
||||||
|
| undefined {
|
||||||
|
const stored = getStoredRoomSettingsRowFromDatabase(db, chatJid);
|
||||||
|
if (stored) {
|
||||||
|
return {
|
||||||
|
snapshot: buildRoomRegistrationSnapshotFromStoredRoom(stored, overrides),
|
||||||
|
roomMode: stored.roomMode,
|
||||||
|
hasStoredRoom: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const snapshot = collectRoomRegistrationSnapshot(db, chatJid);
|
||||||
|
if (!snapshot) return undefined;
|
||||||
|
|
||||||
|
return {
|
||||||
|
snapshot: {
|
||||||
|
...snapshot,
|
||||||
|
name: overrides?.name ?? snapshot.name,
|
||||||
|
},
|
||||||
|
roomMode: inferRoomModeFromRegisteredAgentTypes(
|
||||||
|
collectRegisteredAgentTypes(db, chatJid),
|
||||||
|
),
|
||||||
|
hasStoredRoom: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function upsertStoredRoomMode(
|
function upsertStoredRoomMode(
|
||||||
chatJid: string,
|
chatJid: string,
|
||||||
roomMode: RoomMode,
|
roomMode: RoomMode,
|
||||||
@@ -1010,7 +1108,7 @@ export function setExplicitRoomMode(chatJid: string, roomMode: RoomMode): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function clearExplicitRoomMode(chatJid: string): void {
|
export function clearExplicitRoomMode(chatJid: string): void {
|
||||||
const agentTypes = getRegisteredAgentTypesForJid(chatJid);
|
const agentTypes = collectRegisteredAgentTypes(db, chatJid);
|
||||||
if (agentTypes.length === 0) {
|
if (agentTypes.length === 0) {
|
||||||
db.prepare('DELETE FROM room_settings WHERE chat_jid = ?').run(chatJid);
|
db.prepare('DELETE FROM room_settings WHERE chat_jid = ?').run(chatJid);
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -147,9 +147,28 @@ export function getLegacyRegisteredGroupRows(
|
|||||||
return (
|
return (
|
||||||
agentTypeFilter
|
agentTypeFilter
|
||||||
? database
|
? database
|
||||||
.prepare('SELECT * FROM registered_groups WHERE agent_type = ?')
|
.prepare(
|
||||||
|
`SELECT *
|
||||||
|
FROM registered_groups
|
||||||
|
WHERE agent_type = ?
|
||||||
|
AND NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM room_settings
|
||||||
|
WHERE chat_jid = registered_groups.jid
|
||||||
|
)`,
|
||||||
|
)
|
||||||
.all(agentTypeFilter)
|
.all(agentTypeFilter)
|
||||||
: database.prepare('SELECT * FROM registered_groups').all()
|
: database
|
||||||
|
.prepare(
|
||||||
|
`SELECT *
|
||||||
|
FROM registered_groups
|
||||||
|
WHERE NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM room_settings
|
||||||
|
WHERE chat_jid = registered_groups.jid
|
||||||
|
)`,
|
||||||
|
)
|
||||||
|
.all()
|
||||||
) as RegisteredGroupDatabaseRow[];
|
) as RegisteredGroupDatabaseRow[];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,6 +177,10 @@ export function getLegacyRegisteredGroup(
|
|||||||
jid: string,
|
jid: string,
|
||||||
agentType?: string,
|
agentType?: string,
|
||||||
): (RegisteredGroup & { jid: string }) | undefined {
|
): (RegisteredGroup & { jid: string }) | undefined {
|
||||||
|
if (getStoredRoomSettingsRowFromDatabase(database, jid)) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
const row = (
|
const row = (
|
||||||
agentType
|
agentType
|
||||||
? database
|
? database
|
||||||
@@ -276,7 +299,7 @@ export function buildRegisteredGroupFromStoredSettings(
|
|||||||
stored: StoredRoomSettings,
|
stored: StoredRoomSettings,
|
||||||
requestedAgentType?: AgentType,
|
requestedAgentType?: AgentType,
|
||||||
): (RegisteredGroup & { jid: string }) | undefined {
|
): (RegisteredGroup & { jid: string }) | undefined {
|
||||||
const capabilityTypes = collectRegisteredAgentTypes(database, stored.chatJid);
|
const capabilityTypes = resolveStoredRoomCapabilityTypes(database, stored);
|
||||||
const resolvedAgentType = requestedAgentType
|
const resolvedAgentType = requestedAgentType
|
||||||
? capabilityTypes.includes(requestedAgentType)
|
? capabilityTypes.includes(requestedAgentType)
|
||||||
? requestedAgentType
|
? requestedAgentType
|
||||||
@@ -315,6 +338,27 @@ export function buildRegisteredGroupFromStoredSettings(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function resolveStoredRoomCapabilityTypes(
|
||||||
|
database: Database,
|
||||||
|
stored: StoredRoomSettings,
|
||||||
|
): AgentType[] {
|
||||||
|
const projectedTypes = collectRegisteredAgentTypes(database, stored.chatJid);
|
||||||
|
if (stored.modeSource !== 'explicit') {
|
||||||
|
return projectedTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ownerAgentType =
|
||||||
|
stored.ownerAgentType ||
|
||||||
|
(projectedTypes.length > 0
|
||||||
|
? inferOwnerAgentTypeFromRegisteredAgentTypes(projectedTypes)
|
||||||
|
: OWNER_AGENT_TYPE);
|
||||||
|
const types = new Set<AgentType>([ownerAgentType]);
|
||||||
|
if (stored.roomMode === 'tribunal') {
|
||||||
|
types.add(REVIEWER_AGENT_TYPE);
|
||||||
|
}
|
||||||
|
return [...types];
|
||||||
|
}
|
||||||
|
|
||||||
function detectChannelPrefixForFolder(chatJid: string): string {
|
function detectChannelPrefixForFolder(chatJid: string): string {
|
||||||
if (chatJid.startsWith('dc:')) return 'discord';
|
if (chatJid.startsWith('dc:')) return 'discord';
|
||||||
if (chatJid.startsWith('tg:')) return 'telegram';
|
if (chatJid.startsWith('tg:')) return 'telegram';
|
||||||
|
|||||||
Reference in New Issue
Block a user