setup: narrow legacy room registration surface

This commit is contained in:
ejclaw
2026-04-11 05:28:26 +09:00
parent 9237173e43
commit 30dda74621
9 changed files with 336 additions and 430 deletions

View File

@@ -4,7 +4,7 @@ import path from 'path';
import { afterEach, describe, expect, it, vi } from 'vitest';
import { Database } from 'bun:sqlite';
import { detectAssignedRooms, run as runEnvironment } from './environment.js';
import { run as runEnvironment } from './environment.js';
/**
* Tests for the environment check step.
@@ -20,77 +20,6 @@ describe('environment detection', () => {
});
});
describe('assigned rooms detection', () => {
const tempDirs: string[] = [];
afterEach(() => {
for (const dir of tempDirs.splice(0)) {
fs.rmSync(dir, { recursive: true, force: true });
}
});
it('returns false when the database does not exist', () => {
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ejclaw-env-'));
tempDirs.push(tempDir);
expect(
detectAssignedRooms({ dbPath: path.join(tempDir, 'messages.db') }),
).toBe(false);
});
it('returns false for an empty room_settings table', () => {
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ejclaw-env-'));
tempDirs.push(tempDir);
const dbPath = path.join(tempDir, 'messages.db');
const db = new Database(dbPath);
db.exec(`CREATE TABLE room_settings (chat_jid TEXT PRIMARY KEY)`);
db.close();
expect(detectAssignedRooms({ dbPath })).toBe(false);
});
it('returns true when canonical room assignments exist', () => {
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ejclaw-env-'));
tempDirs.push(tempDir);
const dbPath = path.join(tempDir, 'messages.db');
const db = new Database(dbPath);
db.exec(`
CREATE TABLE room_settings (
chat_jid TEXT PRIMARY KEY,
room_mode TEXT NOT NULL,
updated_at TEXT NOT NULL
)
`);
db.prepare(
`INSERT INTO room_settings (chat_jid, room_mode, updated_at)
VALUES (?, ?, ?)`,
).run('dc:room-1', 'single', '2024-01-01T00:00:00.000Z');
db.close();
expect(detectAssignedRooms({ dbPath })).toBe(true);
});
it('returns false when only legacy registered_groups rows exist', () => {
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ejclaw-env-'));
tempDirs.push(tempDir);
const dbPath = path.join(tempDir, 'messages.db');
const db = new Database(dbPath);
db.exec(`
CREATE TABLE registered_groups (
jid TEXT PRIMARY KEY,
agent_type TEXT
)
`);
db.exec(`
INSERT INTO registered_groups (jid, agent_type)
VALUES ('dc:legacy-room', 'claude-code')
`);
db.close();
expect(detectAssignedRooms({ dbPath })).toBe(false);
});
});
describe('environment step legacy-room handling', () => {
const originalCwd = process.cwd();
const tempDirs: string[] = [];

View File

@@ -16,7 +16,7 @@ import {
} from './platform.js';
import {
detectRoomRegistrationState,
getLegacyMigrationGuidance,
getRoomRegistrationGateFailure,
} from './room-registration-state.js';
import { emitStatus } from './status.js';
@@ -51,11 +51,11 @@ export async function run(_args: string[]): Promise<void> {
projectRoot,
dbPath: path.join(projectRoot, 'store', 'messages.db'),
});
const legacyMigrationGuidance = getLegacyMigrationGuidance(
const roomRegistrationGateFailure = getRoomRegistrationGateFailure(
roomState,
'setup',
);
const status = legacyMigrationGuidance ? 'failed' : 'success';
const status = roomRegistrationGateFailure ? 'failed' : 'success';
logger.info(
{
@@ -94,10 +94,10 @@ export async function run(_args: string[]): Promise<void> {
apparmorRestrictUnprivilegedUserns ?? 'n/a',
HAS_BWRAP_READONLY_SANDBOX_CAPABILITY:
hasBubblewrapReadonlySandboxCapability,
...(legacyMigrationGuidance
...(roomRegistrationGateFailure
? {
ERROR: legacyMigrationGuidance.error,
NEXT_STEP: legacyMigrationGuidance.nextStep,
ERROR: roomRegistrationGateFailure.error,
NEXT_STEP: roomRegistrationGateFailure.nextStep,
}
: {}),
STATUS: status,

View File

@@ -0,0 +1,299 @@
import fs from 'fs';
import os from 'os';
import path from 'path';
import { afterEach, describe, expect, it } from 'vitest';
import { Database } from 'bun:sqlite';
import {
detectRoomRegistrationState,
getRoomRegistrationGateFailure,
} from './room-registration-state.js';
describe('room registration state', () => {
const tempRoots: string[] = [];
afterEach(() => {
for (const root of tempRoots.splice(0)) {
fs.rmSync(root, { recursive: true, force: true });
}
});
it('reports no assigned rooms when the database does not exist', () => {
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'ejclaw-room-reg-'));
tempRoots.push(tempRoot);
expect(
detectRoomRegistrationState({
projectRoot: tempRoot,
dbPath: path.join(tempRoot, 'messages.db'),
}),
).toEqual({
assignedRooms: 0,
roomsByOwnerAgent: {},
legacyRegisteredGroupRows: 0,
legacyRoomMigrationRequired: false,
unexpectedDataStateFiles: [],
unexpectedDataStateDetected: false,
});
});
it('counts canonical room_settings rows and owner-agent breakdowns', () => {
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'ejclaw-room-reg-'));
tempRoots.push(tempRoot);
const dbPath = path.join(tempRoot, 'messages.db');
const db = new Database(dbPath);
db.exec(`
CREATE TABLE room_settings (
chat_jid TEXT PRIMARY KEY,
room_mode TEXT NOT NULL,
owner_agent_type TEXT,
updated_at TEXT NOT NULL
);
`);
db.exec(`
INSERT INTO room_settings (chat_jid, room_mode, owner_agent_type, updated_at) VALUES
('group-1', 'single', 'claude-code', '2024-01-01T00:00:00.000Z'),
('group-2', 'single', 'codex', '2024-01-01T00:00:00.000Z'),
('group-3', 'tribunal', 'codex', '2024-01-01T00:00:00.000Z');
`);
db.close();
expect(
detectRoomRegistrationState({ projectRoot: tempRoot, dbPath }),
).toEqual({
assignedRooms: 3,
roomsByOwnerAgent: {
'claude-code': 1,
codex: 2,
},
legacyRegisteredGroupRows: 0,
legacyRoomMigrationRequired: false,
unexpectedDataStateFiles: [],
unexpectedDataStateDetected: false,
});
});
it('marks legacy-only sqlite registrations as migration-required', () => {
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'ejclaw-room-reg-'));
tempRoots.push(tempRoot);
const dbPath = path.join(tempRoot, 'messages.db');
const db = new Database(dbPath);
db.exec(`
CREATE TABLE registered_groups (
jid TEXT PRIMARY KEY,
agent_type TEXT
)
`);
db.exec(`
INSERT INTO registered_groups (jid, agent_type) VALUES
('legacy-room', 'claude-code')
`);
db.close();
expect(
detectRoomRegistrationState({ dbPath, projectRoot: tempRoot }),
).toEqual({
assignedRooms: 0,
roomsByOwnerAgent: {},
legacyRegisteredGroupRows: 1,
legacyRoomMigrationRequired: true,
unexpectedDataStateFiles: [],
unexpectedDataStateDetected: false,
});
});
it('ignores legacy projection rows when matching canonical room_settings already exist', () => {
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'ejclaw-room-reg-'));
tempRoots.push(tempRoot);
const dbPath = path.join(tempRoot, 'messages.db');
const db = new Database(dbPath);
db.exec(`
CREATE TABLE room_settings (
chat_jid TEXT PRIMARY KEY,
room_mode TEXT NOT NULL,
mode_source TEXT NOT NULL DEFAULT 'explicit',
name TEXT,
folder TEXT,
trigger_pattern TEXT,
requires_trigger INTEGER DEFAULT 1,
is_main INTEGER DEFAULT 0,
owner_agent_type TEXT,
work_dir TEXT,
updated_at TEXT NOT NULL
);
CREATE TABLE room_role_overrides (
chat_jid TEXT NOT NULL,
role TEXT NOT NULL,
agent_type TEXT NOT NULL,
agent_config_json TEXT,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
PRIMARY KEY (chat_jid, role)
);
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 DEFAULT 1,
is_main INTEGER DEFAULT 0,
agent_type TEXT NOT NULL,
work_dir TEXT,
PRIMARY KEY (jid, agent_type)
)
`);
db.exec(`
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 (
'same-room',
'single',
'explicit',
'Same Room',
'same-room',
'@Andy',
1,
0,
'codex',
NULL,
'2024-01-01T00:00:00.000Z'
)
`);
db.exec(`
INSERT INTO registered_groups (
jid,
name,
folder,
trigger_pattern,
added_at,
agent_config,
requires_trigger,
is_main,
agent_type,
work_dir
) VALUES (
'same-room',
'Same Room',
'same-room',
'@Andy',
'2024-01-01T00:00:00.000Z',
NULL,
1,
0,
'codex',
NULL
)
`);
db.exec(`
INSERT INTO room_role_overrides (
chat_jid,
role,
agent_type,
agent_config_json,
created_at,
updated_at
) VALUES (
'same-room',
'owner',
'codex',
NULL,
'2024-01-01T00:00:00.000Z',
'2024-01-01T00:00:00.000Z'
)
`);
db.close();
expect(
detectRoomRegistrationState({ dbPath, projectRoot: tempRoot }),
).toEqual({
assignedRooms: 1,
roomsByOwnerAgent: {
codex: 1,
},
legacyRegisteredGroupRows: 0,
legacyRoomMigrationRequired: false,
unexpectedDataStateFiles: [],
unexpectedDataStateDetected: false,
});
});
it('marks unexpected data-state files as blockers', () => {
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'ejclaw-room-reg-'));
tempRoots.push(tempRoot);
fs.mkdirSync(path.join(tempRoot, 'data'), { recursive: true });
fs.writeFileSync(
path.join(tempRoot, 'data', 'registered_groups.json'),
'{"dc:legacy":{"name":"Legacy","folder":"legacy","trigger":"@Andy","added_at":"2024-01-01T00:00:00.000Z"}}',
);
fs.writeFileSync(
path.join(tempRoot, 'data', 'router_state.json'),
JSON.stringify({ last_timestamp: '1234' }),
);
expect(detectRoomRegistrationState({ projectRoot: tempRoot })).toEqual({
assignedRooms: 0,
roomsByOwnerAgent: {},
legacyRegisteredGroupRows: 0,
legacyRoomMigrationRequired: false,
unexpectedDataStateFiles: ['registered_groups.json', 'router_state.json'],
unexpectedDataStateDetected: true,
});
});
it('returns no gate failure when room-registration state is clean', () => {
expect(
getRoomRegistrationGateFailure(
{
legacyRoomMigrationRequired: false,
unexpectedDataStateDetected: false,
},
'setup',
),
).toBeUndefined();
});
it('returns a targeted gate failure when legacy room migration is pending', () => {
expect(
getRoomRegistrationGateFailure(
{
legacyRoomMigrationRequired: true,
unexpectedDataStateDetected: false,
},
'verification',
),
).toEqual({
error: 'legacy_room_migration_required',
nextStep:
'Run `bun setup/index.ts --step migrate-room-registrations` before continuing with verification',
});
});
it('returns a combined gate failure when legacy migration and unexpected data files are both present', () => {
expect(
getRoomRegistrationGateFailure(
{
legacyRoomMigrationRequired: true,
unexpectedDataStateDetected: true,
},
'setup',
),
).toEqual({
error: 'legacy_migration_and_unexpected_data_state_detected',
nextStep:
'Run `bun setup/index.ts --step migrate-room-registrations` and remove or archive unexpected data state files before continuing with setup',
});
});
});

View File

@@ -16,7 +16,7 @@ export interface AssignedRoomsSummary {
unexpectedDataStateDetected: boolean;
}
export interface LegacyMigrationGuidance {
export interface RoomRegistrationGateFailure {
error: string;
nextStep: string;
}
@@ -25,13 +25,13 @@ function getDataDir(projectRoot: string): string {
return process.env.EJCLAW_DATA_DIR || path.join(projectRoot, 'data');
}
export function getLegacyMigrationGuidance(
export function getRoomRegistrationGateFailure(
summary: Pick<
AssignedRoomsSummary,
'legacyRoomMigrationRequired' | 'unexpectedDataStateDetected'
>,
target: 'setup' | 'verification',
): LegacyMigrationGuidance | undefined {
): RoomRegistrationGateFailure | undefined {
if (
!summary.legacyRoomMigrationRequired &&
!summary.unexpectedDataStateDetected

View File

@@ -11,7 +11,6 @@ import {
buildVerifySummary,
detectChannelAuth,
detectCredentials,
loadAssignedRoomsSummary,
loadRoleRoutingRequirementsSummary,
} from './verify-state.js';
@@ -100,348 +99,6 @@ describe('verify state helpers', () => {
});
});
it('loads assigned room counts from the sqlite store', () => {
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'ejclaw-verify-'));
tempRoots.push(tempRoot);
const dbPath = path.join(tempRoot, 'messages.db');
const db = new Database(dbPath);
db.exec(`
CREATE TABLE room_settings (
chat_jid TEXT PRIMARY KEY,
room_mode TEXT NOT NULL,
owner_agent_type TEXT,
updated_at TEXT NOT NULL
);
`);
db.exec(`
INSERT INTO room_settings (chat_jid, room_mode, owner_agent_type, updated_at) VALUES
('group-1', 'single', 'claude-code', '2024-01-01T00:00:00.000Z'),
('group-2', 'single', 'codex', '2024-01-01T00:00:00.000Z'),
('group-3', 'tribunal', 'codex', '2024-01-01T00:00:00.000Z');
`);
db.close();
expect(loadAssignedRoomsSummary({ dbPath })).toEqual({
assignedRooms: 3,
roomsByOwnerAgent: {
'claude-code': 1,
codex: 2,
},
legacyRegisteredGroupRows: 0,
legacyRoomMigrationRequired: false,
unexpectedDataStateFiles: [],
unexpectedDataStateDetected: false,
});
});
it('marks legacy-only sqlite registrations as migration-required', () => {
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'ejclaw-verify-'));
tempRoots.push(tempRoot);
const dbPath = path.join(tempRoot, 'messages.db');
const db = new Database(dbPath);
db.exec(`
CREATE TABLE registered_groups (
jid TEXT PRIMARY KEY,
agent_type TEXT
)
`);
db.exec(`
INSERT INTO registered_groups (jid, agent_type) VALUES
('legacy-room', 'claude-code')
`);
db.close();
expect(loadAssignedRoomsSummary({ dbPath, projectRoot: tempRoot })).toEqual(
{
assignedRooms: 0,
roomsByOwnerAgent: {},
legacyRegisteredGroupRows: 1,
legacyRoomMigrationRequired: true,
unexpectedDataStateFiles: [],
unexpectedDataStateDetected: false,
},
);
});
it('marks mixed canonical and pending legacy sqlite registrations as migration-required', () => {
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'ejclaw-verify-'));
tempRoots.push(tempRoot);
const dbPath = path.join(tempRoot, 'messages.db');
const db = new Database(dbPath);
db.exec(`
CREATE TABLE room_settings (
chat_jid TEXT PRIMARY KEY,
room_mode TEXT NOT NULL,
mode_source TEXT NOT NULL DEFAULT 'explicit',
name TEXT,
folder TEXT,
trigger_pattern TEXT,
requires_trigger INTEGER DEFAULT 1,
is_main INTEGER DEFAULT 0,
owner_agent_type TEXT,
work_dir TEXT,
updated_at TEXT NOT NULL
);
CREATE TABLE room_role_overrides (
chat_jid TEXT NOT NULL,
role TEXT NOT NULL,
agent_type TEXT NOT NULL,
agent_config_json TEXT,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
PRIMARY KEY (chat_jid, role)
);
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 DEFAULT 1,
is_main INTEGER DEFAULT 0,
agent_type TEXT NOT NULL,
work_dir TEXT,
PRIMARY KEY (jid, agent_type)
)
`);
db.exec(`
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 (
'canonical-room',
'single',
'explicit',
'Canonical Room',
'canonical-room',
'@Andy',
1,
0,
'codex',
NULL,
'2024-01-01T00:00:00.000Z'
)
`);
db.exec(`
INSERT INTO registered_groups (
jid,
name,
folder,
trigger_pattern,
added_at,
agent_config,
requires_trigger,
is_main,
agent_type,
work_dir
) VALUES (
'legacy-room',
'Legacy Room',
'legacy-room',
'@Andy',
'2024-01-01T00:00:00.000Z',
NULL,
1,
0,
'claude-code',
NULL
)
`);
db.close();
expect(loadAssignedRoomsSummary({ dbPath, projectRoot: tempRoot })).toEqual(
{
assignedRooms: 1,
roomsByOwnerAgent: {
codex: 1,
},
legacyRegisteredGroupRows: 1,
legacyRoomMigrationRequired: true,
unexpectedDataStateFiles: [],
unexpectedDataStateDetected: false,
},
);
});
it('ignores legacy projection rows when matching canonical room_settings already exist', () => {
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'ejclaw-verify-'));
tempRoots.push(tempRoot);
const dbPath = path.join(tempRoot, 'messages.db');
const db = new Database(dbPath);
db.exec(`
CREATE TABLE room_settings (
chat_jid TEXT PRIMARY KEY,
room_mode TEXT NOT NULL,
mode_source TEXT NOT NULL DEFAULT 'explicit',
name TEXT,
folder TEXT,
trigger_pattern TEXT,
requires_trigger INTEGER DEFAULT 1,
is_main INTEGER DEFAULT 0,
owner_agent_type TEXT,
work_dir TEXT,
updated_at TEXT NOT NULL
);
CREATE TABLE room_role_overrides (
chat_jid TEXT NOT NULL,
role TEXT NOT NULL,
agent_type TEXT NOT NULL,
agent_config_json TEXT,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
PRIMARY KEY (chat_jid, role)
);
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 DEFAULT 1,
is_main INTEGER DEFAULT 0,
agent_type TEXT NOT NULL,
work_dir TEXT,
PRIMARY KEY (jid, agent_type)
)
`);
db.exec(`
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 (
'same-room',
'single',
'explicit',
'Same Room',
'same-room',
'@Andy',
1,
0,
'codex',
NULL,
'2024-01-01T00:00:00.000Z'
)
`);
db.exec(`
INSERT INTO registered_groups (
jid,
name,
folder,
trigger_pattern,
added_at,
agent_config,
requires_trigger,
is_main,
agent_type,
work_dir
) VALUES (
'same-room',
'Same Room',
'same-room',
'@Andy',
'2024-01-01T00:00:00.000Z',
NULL,
1,
0,
'codex',
NULL
)
`);
db.exec(`
INSERT INTO room_role_overrides (
chat_jid,
role,
agent_type,
agent_config_json,
created_at,
updated_at
) VALUES (
'same-room',
'owner',
'codex',
NULL,
'2024-01-01T00:00:00.000Z',
'2024-01-01T00:00:00.000Z'
)
`);
db.close();
expect(loadAssignedRoomsSummary({ dbPath, projectRoot: tempRoot })).toEqual(
{
assignedRooms: 1,
roomsByOwnerAgent: {
codex: 1,
},
legacyRegisteredGroupRows: 0,
legacyRoomMigrationRequired: false,
unexpectedDataStateFiles: [],
unexpectedDataStateDetected: false,
},
);
});
it('marks unexpected data-state files as setup blockers', () => {
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'ejclaw-verify-'));
tempRoots.push(tempRoot);
fs.mkdirSync(path.join(tempRoot, 'data'), { recursive: true });
fs.writeFileSync(
path.join(tempRoot, 'data', 'registered_groups.json'),
'{"dc:legacy":{"name":"Legacy","folder":"legacy","trigger":"@Andy","added_at":"2024-01-01T00:00:00.000Z"}}',
);
expect(loadAssignedRoomsSummary({ projectRoot: tempRoot })).toEqual({
assignedRooms: 0,
roomsByOwnerAgent: {},
legacyRegisteredGroupRows: 0,
legacyRoomMigrationRequired: false,
unexpectedDataStateFiles: ['registered_groups.json'],
unexpectedDataStateDetected: true,
});
});
it('marks unexpected router/session json files as setup blockers', () => {
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'ejclaw-verify-'));
tempRoots.push(tempRoot);
fs.mkdirSync(path.join(tempRoot, 'data'), { recursive: true });
fs.writeFileSync(
path.join(tempRoot, 'data', 'router_state.json'),
JSON.stringify({ last_timestamp: '1234' }),
);
fs.writeFileSync(
path.join(tempRoot, 'data', 'sessions.json'),
JSON.stringify({ 'group-a': 'session-123' }),
);
expect(loadAssignedRoomsSummary({ projectRoot: tempRoot })).toEqual({
assignedRooms: 0,
roomsByOwnerAgent: {},
legacyRegisteredGroupRows: 0,
legacyRoomMigrationRequired: false,
unexpectedDataStateFiles: ['router_state.json', 'sessions.json'],
unexpectedDataStateDetected: true,
});
});
it('builds a successful verification summary when all gates pass', () => {
const services: ServiceCheck[] = [{ name: 'ejclaw', status: 'running' }];
const serviceDefs: ServiceDef[] = [

View File

@@ -11,7 +11,7 @@ import path from 'path';
import { logger } from '../src/logger.js';
import { getServiceManager } from './platform.js';
import { getLegacyMigrationGuidance } from './room-registration-state.js';
import { getRoomRegistrationGateFailure } from './room-registration-state.js';
import { getServiceDefs } from './service-defs.js';
import { emitStatus } from './status.js';
import {
@@ -66,7 +66,7 @@ export async function run(_args: string[]): Promise<void> {
activeArbiterTasks,
},
);
const legacyMigrationGuidance = getLegacyMigrationGuidance(
const roomRegistrationGateFailure = getRoomRegistrationGateFailure(
roomSummary,
'verification',
);
@@ -100,10 +100,10 @@ export async function run(_args: string[]): Promise<void> {
UNEXPECTED_DATA_STATE_DETECTED: roomSummary.unexpectedDataStateDetected,
REVIEWER_CHANNEL_CONFIGURED: reviewerChannelConfigured,
ARBITER_CHANNEL_CONFIGURED: arbiterChannelConfigured,
...(legacyMigrationGuidance
...(roomRegistrationGateFailure
? {
ERROR: legacyMigrationGuidance.error,
NEXT_STEP: legacyMigrationGuidance.nextStep,
ERROR: roomRegistrationGateFailure.error,
NEXT_STEP: roomRegistrationGateFailure.nextStep,
}
: {}),
STATUS: status,

View File

@@ -2271,6 +2271,25 @@ describe('message query LIMIT', () => {
// --- RegisteredGroup isMain round-trip ---
describe('registered group isMain', () => {
it('projects trigger metadata from canonical room settings into registered groups', () => {
_setRegisteredGroupForTests('dc:triggered', {
name: 'Triggered Room',
folder: 'triggered-room',
trigger: '@Andy',
added_at: '2024-01-01T00:00:00.000Z',
requiresTrigger: true,
});
expect(getRegisteredGroup('dc:triggered')).toMatchObject({
trigger: '@Andy',
requiresTrigger: true,
});
expect(getAllRoomBindings()['dc:triggered']).toMatchObject({
trigger: '@Andy',
requiresTrigger: true,
});
});
it('persists isMain=true through set/get round-trip', () => {
_setRegisteredGroupForTests('dc:main', {
name: 'Main Chat',

View File

@@ -244,8 +244,10 @@ export function buildRegisteredGroupFromStoredSettings(
jid: stored.chatJid,
name: stored.name || stored.chatJid,
folder: stored.folder,
trigger: stored.trigger,
added_at: capabilityMetadata?.createdAt || new Date(0).toISOString(),
agentConfig: capabilityMetadata?.agentConfig,
requiresTrigger: stored.requiresTrigger ?? false,
isMain: stored.isMain ? true : undefined,
agentType: resolvedAgentType,
workDir: stored.workDir,

View File

@@ -151,7 +151,7 @@ export interface RegisteredGroup {
trigger?: string;
added_at: string;
agentConfig?: AgentConfig;
requiresTrigger?: boolean; // Compatibility-only legacy field
requiresTrigger?: boolean; // Whether non-paired messages must match the room trigger
isMain?: boolean; // True for the main control group (no trigger, elevated privileges)
agentType?: AgentType;
workDir?: string; // Working directory for the agent (defaults to group folder)