Apply formatter after local memory refactor
This commit is contained in:
@@ -392,10 +392,13 @@ describe('prepareContainerSessionEnvironment codex compatibility', () => {
|
|||||||
role: 'reviewer',
|
role: 'reviewer',
|
||||||
});
|
});
|
||||||
|
|
||||||
const claudeMd = fs.readFileSync(path.join(sessionDir, 'CLAUDE.md'), 'utf-8');
|
const claudeMd = fs.readFileSync(
|
||||||
expect(fs.readFileSync(path.join(sessionDir, '.codex', 'AGENTS.md'), 'utf-8')).toBe(
|
path.join(sessionDir, 'CLAUDE.md'),
|
||||||
claudeMd,
|
'utf-8',
|
||||||
);
|
);
|
||||||
|
expect(
|
||||||
|
fs.readFileSync(path.join(sessionDir, '.codex', 'AGENTS.md'), 'utf-8'),
|
||||||
|
).toBe(claudeMd);
|
||||||
expect(
|
expect(
|
||||||
fs.readFileSync(path.join(sessionDir, '.codex', 'auth.json'), 'utf-8'),
|
fs.readFileSync(path.join(sessionDir, '.codex', 'auth.json'), 'utf-8'),
|
||||||
).toContain('"auth_mode":"chatgpt"');
|
).toContain('"auth_mode":"chatgpt"');
|
||||||
|
|||||||
@@ -25,7 +25,10 @@ import {
|
|||||||
readPairedRoomPrompt,
|
readPairedRoomPrompt,
|
||||||
readPlatformPrompt,
|
readPlatformPrompt,
|
||||||
} from './platform-prompts.js';
|
} from './platform-prompts.js';
|
||||||
import { getEffectiveChannelLease, hasReviewerLease } from './service-routing.js';
|
import {
|
||||||
|
getEffectiveChannelLease,
|
||||||
|
hasReviewerLease,
|
||||||
|
} from './service-routing.js';
|
||||||
import type { AgentType, RegisteredGroup } from './types.js';
|
import type { AgentType, RegisteredGroup } from './types.js';
|
||||||
|
|
||||||
// writeCodexApiKeyAuth removed — Codex uses OAuth only.
|
// writeCodexApiKeyAuth removed — Codex uses OAuth only.
|
||||||
@@ -330,10 +333,7 @@ EJCLAW_GROUP_FOLDER = ${JSON.stringify(args.group.folder)}
|
|||||||
EJCLAW_IS_MAIN = ${JSON.stringify(args.isMain ? '1' : '0')}
|
EJCLAW_IS_MAIN = ${JSON.stringify(args.isMain ? '1' : '0')}
|
||||||
EJCLAW_AGENT_TYPE = ${JSON.stringify(args.env.EJCLAW_AGENT_TYPE)}
|
EJCLAW_AGENT_TYPE = ${JSON.stringify(args.env.EJCLAW_AGENT_TYPE)}
|
||||||
`;
|
`;
|
||||||
fs.writeFileSync(
|
fs.writeFileSync(sessionConfigPath, toml.trimEnd() + '\n' + mcpSection);
|
||||||
sessionConfigPath,
|
|
||||||
toml.trimEnd() + '\n' + mcpSection,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
delete args.env.ANTHROPIC_API_KEY;
|
delete args.env.ANTHROPIC_API_KEY;
|
||||||
@@ -404,7 +404,8 @@ export function prepareGroupEnvironment(
|
|||||||
// Canonical lease state now exposes owner failover directly, so prefer the
|
// Canonical lease state now exposes owner failover directly, so prefer the
|
||||||
// explicit flag over the older CODEX_REVIEW_SERVICE_ID shadow heuristic.
|
// explicit flag over the older CODEX_REVIEW_SERVICE_ID shadow heuristic.
|
||||||
const useCodexReviewFailoverPromptPack =
|
const useCodexReviewFailoverPromptPack =
|
||||||
isReviewService(SERVICE_ID) && effectiveLease.owner_failover_active === true;
|
isReviewService(SERVICE_ID) &&
|
||||||
|
effectiveLease.owner_failover_active === true;
|
||||||
|
|
||||||
const ownerCommonPlatformPrompt = readOptionalPromptFile(
|
const ownerCommonPlatformPrompt = readOptionalPromptFile(
|
||||||
projectRoot,
|
projectRoot,
|
||||||
|
|||||||
@@ -22,69 +22,64 @@ import {
|
|||||||
const customArbiterEnabled =
|
const customArbiterEnabled =
|
||||||
ARBITER_AGENT_TYPE === 'codex' && ARBITER_SERVICE_ID != null;
|
ARBITER_AGENT_TYPE === 'codex' && ARBITER_SERVICE_ID != null;
|
||||||
|
|
||||||
describe.skipIf(!customArbiterEnabled)(
|
describe.skipIf(!customArbiterEnabled)('custom arbiter service shadow', () => {
|
||||||
'custom arbiter service shadow',
|
beforeEach(() => {
|
||||||
() => {
|
_initTestDatabase();
|
||||||
beforeEach(() => {
|
clearGlobalFailover();
|
||||||
_initTestDatabase();
|
refreshChannelOwnerCache(true);
|
||||||
clearGlobalFailover();
|
});
|
||||||
refreshChannelOwnerCache(true);
|
|
||||||
|
it('maps codex arbiter shadow to the configured arbiter service id', () => {
|
||||||
|
expect(resolveRoleServiceShadow('arbiter', 'codex')).toBe(
|
||||||
|
ARBITER_SERVICE_ID,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('preserves the configured arbiter service id in explicit channel leases', () => {
|
||||||
|
setChannelOwnerLease({
|
||||||
|
chat_jid: 'dc:custom-arbiter-lease',
|
||||||
|
owner_agent_type: 'codex',
|
||||||
|
reviewer_agent_type: 'claude-code',
|
||||||
|
arbiter_agent_type: 'codex',
|
||||||
|
});
|
||||||
|
refreshChannelOwnerCache(true);
|
||||||
|
|
||||||
|
expect(getChannelOwnerLease('dc:custom-arbiter-lease')).toMatchObject({
|
||||||
|
owner_service_id: CODEX_MAIN_SERVICE_ID,
|
||||||
|
reviewer_service_id: CLAUDE_SERVICE_ID,
|
||||||
|
arbiter_service_id: ARBITER_SERVICE_ID,
|
||||||
|
owner_agent_type: 'codex',
|
||||||
|
reviewer_agent_type: 'claude-code',
|
||||||
|
arbiter_agent_type: 'codex',
|
||||||
|
});
|
||||||
|
expect(getEffectiveChannelLease('dc:custom-arbiter-lease')).toMatchObject({
|
||||||
|
owner_service_id: CODEX_MAIN_SERVICE_ID,
|
||||||
|
reviewer_service_id: CLAUDE_SERVICE_ID,
|
||||||
|
arbiter_service_id: ARBITER_SERVICE_ID,
|
||||||
|
owner_agent_type: 'codex',
|
||||||
|
reviewer_agent_type: 'claude-code',
|
||||||
|
arbiter_agent_type: 'codex',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('uses the configured arbiter service id for derived arbiter handoffs', () => {
|
||||||
|
const handoff = createServiceHandoff({
|
||||||
|
chat_jid: 'dc:custom-arbiter-handoff',
|
||||||
|
group_folder: 'custom-arbiter-handoff',
|
||||||
|
source_role: 'owner',
|
||||||
|
source_agent_type: 'codex',
|
||||||
|
target_role: 'arbiter',
|
||||||
|
target_agent_type: 'codex',
|
||||||
|
prompt: 'arbiter please decide',
|
||||||
|
intended_role: 'arbiter',
|
||||||
});
|
});
|
||||||
|
|
||||||
it('maps codex arbiter shadow to the configured arbiter service id', () => {
|
expect(handoff).toMatchObject({
|
||||||
expect(resolveRoleServiceShadow('arbiter', 'codex')).toBe(
|
source_service_id: CODEX_MAIN_SERVICE_ID,
|
||||||
ARBITER_SERVICE_ID,
|
target_service_id: ARBITER_SERVICE_ID,
|
||||||
);
|
source_role: 'owner',
|
||||||
|
target_role: 'arbiter',
|
||||||
|
target_agent_type: 'codex',
|
||||||
});
|
});
|
||||||
|
});
|
||||||
it('preserves the configured arbiter service id in explicit channel leases', () => {
|
});
|
||||||
setChannelOwnerLease({
|
|
||||||
chat_jid: 'dc:custom-arbiter-lease',
|
|
||||||
owner_agent_type: 'codex',
|
|
||||||
reviewer_agent_type: 'claude-code',
|
|
||||||
arbiter_agent_type: 'codex',
|
|
||||||
});
|
|
||||||
refreshChannelOwnerCache(true);
|
|
||||||
|
|
||||||
expect(getChannelOwnerLease('dc:custom-arbiter-lease')).toMatchObject({
|
|
||||||
owner_service_id: CODEX_MAIN_SERVICE_ID,
|
|
||||||
reviewer_service_id: CLAUDE_SERVICE_ID,
|
|
||||||
arbiter_service_id: ARBITER_SERVICE_ID,
|
|
||||||
owner_agent_type: 'codex',
|
|
||||||
reviewer_agent_type: 'claude-code',
|
|
||||||
arbiter_agent_type: 'codex',
|
|
||||||
});
|
|
||||||
expect(getEffectiveChannelLease('dc:custom-arbiter-lease')).toMatchObject(
|
|
||||||
{
|
|
||||||
owner_service_id: CODEX_MAIN_SERVICE_ID,
|
|
||||||
reviewer_service_id: CLAUDE_SERVICE_ID,
|
|
||||||
arbiter_service_id: ARBITER_SERVICE_ID,
|
|
||||||
owner_agent_type: 'codex',
|
|
||||||
reviewer_agent_type: 'claude-code',
|
|
||||||
arbiter_agent_type: 'codex',
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('uses the configured arbiter service id for derived arbiter handoffs', () => {
|
|
||||||
const handoff = createServiceHandoff({
|
|
||||||
chat_jid: 'dc:custom-arbiter-handoff',
|
|
||||||
group_folder: 'custom-arbiter-handoff',
|
|
||||||
source_role: 'owner',
|
|
||||||
source_agent_type: 'codex',
|
|
||||||
target_role: 'arbiter',
|
|
||||||
target_agent_type: 'codex',
|
|
||||||
prompt: 'arbiter please decide',
|
|
||||||
intended_role: 'arbiter',
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(handoff).toMatchObject({
|
|
||||||
source_service_id: CODEX_MAIN_SERVICE_ID,
|
|
||||||
target_service_id: ARBITER_SERVICE_ID,
|
|
||||||
source_role: 'owner',
|
|
||||||
target_role: 'arbiter',
|
|
||||||
target_agent_type: 'codex',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|||||||
@@ -794,9 +794,7 @@ registerChannel(DISCORD_OWNER_CHANNEL, (opts: ChannelOpts) => {
|
|||||||
'owner',
|
'owner',
|
||||||
);
|
);
|
||||||
if (!token) {
|
if (!token) {
|
||||||
logger.warn(
|
logger.warn('Discord: DISCORD_OWNER_BOT_TOKEN not set');
|
||||||
'Discord: DISCORD_OWNER_BOT_TOKEN not set',
|
|
||||||
);
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return new DiscordChannel(token, opts, undefined, DISCORD_OWNER_CHANNEL);
|
return new DiscordChannel(token, opts, undefined, DISCORD_OWNER_CHANNEL);
|
||||||
|
|||||||
@@ -396,7 +396,9 @@ describe('session accessors', () => {
|
|||||||
const migratedDb = new Database(dbPath, { readonly: true });
|
const migratedDb = new Database(dbPath, { readonly: true });
|
||||||
expect(
|
expect(
|
||||||
migratedDb
|
migratedDb
|
||||||
.prepare(`SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'service_sessions'`)
|
.prepare(
|
||||||
|
`SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'service_sessions'`,
|
||||||
|
)
|
||||||
.get(),
|
.get(),
|
||||||
).toBeUndefined();
|
).toBeUndefined();
|
||||||
migratedDb.close();
|
migratedDb.close();
|
||||||
@@ -2354,29 +2356,39 @@ describe('service handoff completion', () => {
|
|||||||
|
|
||||||
const migratedDb = new Database(dbPath, { readonly: true });
|
const migratedDb = new Database(dbPath, { readonly: true });
|
||||||
expect(
|
expect(
|
||||||
(migratedDb
|
(
|
||||||
.prepare(`PRAGMA table_info(work_items)`)
|
migratedDb.prepare(`PRAGMA table_info(work_items)`).all() as Array<{
|
||||||
.all() as Array<{ name: string }>).map((row) => row.name),
|
name: string;
|
||||||
|
}>
|
||||||
|
).map((row) => row.name),
|
||||||
).not.toContain('service_id');
|
).not.toContain('service_id');
|
||||||
expect(
|
expect(
|
||||||
(migratedDb
|
(
|
||||||
.prepare(`PRAGMA table_info(channel_owner)`)
|
migratedDb.prepare(`PRAGMA table_info(channel_owner)`).all() as Array<{
|
||||||
.all() as Array<{ name: string }>).map((row) => row.name),
|
name: string;
|
||||||
|
}>
|
||||||
|
).map((row) => row.name),
|
||||||
).not.toContain('owner_service_id');
|
).not.toContain('owner_service_id');
|
||||||
expect(
|
expect(
|
||||||
(migratedDb
|
(
|
||||||
.prepare(`PRAGMA table_info(paired_tasks)`)
|
migratedDb.prepare(`PRAGMA table_info(paired_tasks)`).all() as Array<{
|
||||||
.all() as Array<{ name: string }>).map((row) => row.name),
|
name: string;
|
||||||
|
}>
|
||||||
|
).map((row) => row.name),
|
||||||
).not.toContain('owner_service_id');
|
).not.toContain('owner_service_id');
|
||||||
expect(
|
expect(
|
||||||
(migratedDb
|
(
|
||||||
.prepare(`PRAGMA table_info(service_handoffs)`)
|
migratedDb
|
||||||
.all() as Array<{ name: string }>).map((row) => row.name),
|
.prepare(`PRAGMA table_info(service_handoffs)`)
|
||||||
|
.all() as Array<{ name: string }>
|
||||||
|
).map((row) => row.name),
|
||||||
).not.toContain('source_service_id');
|
).not.toContain('source_service_id');
|
||||||
expect(
|
expect(
|
||||||
(migratedDb
|
(
|
||||||
.prepare(`PRAGMA table_info(service_handoffs)`)
|
migratedDb
|
||||||
.all() as Array<{ name: string }>).map((row) => row.name),
|
.prepare(`PRAGMA table_info(service_handoffs)`)
|
||||||
|
.all() as Array<{ name: string }>
|
||||||
|
).map((row) => row.name),
|
||||||
).toContain('source_agent_type');
|
).toContain('source_agent_type');
|
||||||
migratedDb.close();
|
migratedDb.close();
|
||||||
});
|
});
|
||||||
@@ -2493,8 +2505,12 @@ describe('memories', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
expect(recalled).toHaveLength(300);
|
expect(recalled).toHaveLength(300);
|
||||||
expect(recalled.some((memory) => memory.content === 'memory-0')).toBe(false);
|
expect(recalled.some((memory) => memory.content === 'memory-0')).toBe(
|
||||||
expect(recalled.some((memory) => memory.content === 'memory-304')).toBe(true);
|
false,
|
||||||
|
);
|
||||||
|
expect(recalled.some((memory) => memory.content === 'memory-304')).toBe(
|
||||||
|
true,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('archives stale compact memories before recall using last_used_at TTL', () => {
|
it('archives stale compact memories before recall using last_used_at TTL', () => {
|
||||||
@@ -2526,9 +2542,13 @@ describe('memories', () => {
|
|||||||
limit: 10,
|
limit: 10,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(recalled.some((memory) => memory.content === '오래된 compact memory')).toBe(false);
|
|
||||||
expect(
|
expect(
|
||||||
recalled.some((memory) => memory.content === '최근에 다시 쓰인 compact memory'),
|
recalled.some((memory) => memory.content === '오래된 compact memory'),
|
||||||
|
).toBe(false);
|
||||||
|
expect(
|
||||||
|
recalled.some(
|
||||||
|
(memory) => memory.content === '최근에 다시 쓰인 compact memory',
|
||||||
|
),
|
||||||
).toBe(true);
|
).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
239
src/db.ts
239
src/db.ts
@@ -179,7 +179,10 @@ export interface ServiceHandoff {
|
|||||||
last_error: string | null;
|
last_error: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface StoredWorkItemRow extends Omit<WorkItem, 'service_id' | 'agent_type'> {
|
interface StoredWorkItemRow extends Omit<
|
||||||
|
WorkItem,
|
||||||
|
'service_id' | 'agent_type'
|
||||||
|
> {
|
||||||
agent_type: string;
|
agent_type: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -198,11 +201,13 @@ interface LegacyChannelOwnerLeaseServiceRow extends StoredChannelOwnerLeaseRow {
|
|||||||
arbiter_service_id?: string | null;
|
arbiter_service_id?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface StoredServiceHandoffRow
|
interface StoredServiceHandoffRow extends Omit<
|
||||||
extends Omit<
|
ServiceHandoff,
|
||||||
ServiceHandoff,
|
| 'source_service_id'
|
||||||
'source_service_id' | 'target_service_id' | 'source_agent_type' | 'target_agent_type'
|
| 'target_service_id'
|
||||||
> {
|
| 'source_agent_type'
|
||||||
|
| 'target_agent_type'
|
||||||
|
> {
|
||||||
source_agent_type?: string | null;
|
source_agent_type?: string | null;
|
||||||
target_agent_type: string;
|
target_agent_type: string;
|
||||||
}
|
}
|
||||||
@@ -212,15 +217,14 @@ interface LegacyServiceHandoffServiceRow extends StoredServiceHandoffRow {
|
|||||||
target_service_id?: string | null;
|
target_service_id?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface StoredPairedTaskRow
|
interface StoredPairedTaskRow extends Omit<
|
||||||
extends Omit<
|
PairedTask,
|
||||||
PairedTask,
|
| 'owner_service_id'
|
||||||
| 'owner_service_id'
|
| 'reviewer_service_id'
|
||||||
| 'reviewer_service_id'
|
| 'owner_agent_type'
|
||||||
| 'owner_agent_type'
|
| 'reviewer_agent_type'
|
||||||
| 'reviewer_agent_type'
|
| 'arbiter_agent_type'
|
||||||
| 'arbiter_agent_type'
|
> {
|
||||||
> {
|
|
||||||
owner_agent_type?: string | null;
|
owner_agent_type?: string | null;
|
||||||
reviewer_agent_type?: string | null;
|
reviewer_agent_type?: string | null;
|
||||||
arbiter_agent_type?: string | null;
|
arbiter_agent_type?: string | null;
|
||||||
@@ -286,20 +290,19 @@ function hydrateChannelOwnerLeaseRow(
|
|||||||
const reviewerAgentType =
|
const reviewerAgentType =
|
||||||
row.reviewer_agent_type == null
|
row.reviewer_agent_type == null
|
||||||
? null
|
? null
|
||||||
: normalizeStoredAgentType(row.reviewer_agent_type) ??
|
: (normalizeStoredAgentType(row.reviewer_agent_type) ??
|
||||||
resolveStableReviewerAgentType(ownerAgentType, null);
|
resolveStableReviewerAgentType(ownerAgentType, null));
|
||||||
const arbiterAgentType =
|
const arbiterAgentType =
|
||||||
row.arbiter_agent_type == null
|
row.arbiter_agent_type == null
|
||||||
? null
|
? null
|
||||||
: normalizeStoredAgentType(row.arbiter_agent_type) ??
|
: (normalizeStoredAgentType(row.arbiter_agent_type) ??
|
||||||
ARBITER_AGENT_TYPE ??
|
ARBITER_AGENT_TYPE ??
|
||||||
null;
|
null);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
chat_jid: row.chat_jid,
|
chat_jid: row.chat_jid,
|
||||||
owner_service_id:
|
owner_service_id:
|
||||||
resolveRoleServiceShadow('owner', ownerAgentType) ??
|
resolveRoleServiceShadow('owner', ownerAgentType) ?? CLAUDE_SERVICE_ID,
|
||||||
CLAUDE_SERVICE_ID,
|
|
||||||
reviewer_service_id:
|
reviewer_service_id:
|
||||||
reviewerAgentType == null
|
reviewerAgentType == null
|
||||||
? null
|
? null
|
||||||
@@ -323,12 +326,15 @@ function hydratePairedTaskRow(row: StoredPairedTaskRow): PairedTask {
|
|||||||
row.reviewer_agent_type ?? null,
|
row.reviewer_agent_type ?? null,
|
||||||
);
|
);
|
||||||
const arbiterAgentType =
|
const arbiterAgentType =
|
||||||
normalizeStoredAgentType(row.arbiter_agent_type) ?? ARBITER_AGENT_TYPE ?? null;
|
normalizeStoredAgentType(row.arbiter_agent_type) ??
|
||||||
|
ARBITER_AGENT_TYPE ??
|
||||||
|
null;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...row,
|
...row,
|
||||||
owner_service_id:
|
owner_service_id:
|
||||||
resolveRoleServiceShadow('owner', ownerAgentType) ?? CODEX_MAIN_SERVICE_ID,
|
resolveRoleServiceShadow('owner', ownerAgentType) ??
|
||||||
|
CODEX_MAIN_SERVICE_ID,
|
||||||
reviewer_service_id:
|
reviewer_service_id:
|
||||||
resolveRoleServiceShadow('reviewer', reviewerAgentType) ??
|
resolveRoleServiceShadow('reviewer', reviewerAgentType) ??
|
||||||
CODEX_REVIEW_SERVICE_ID,
|
CODEX_REVIEW_SERVICE_ID,
|
||||||
@@ -338,7 +344,9 @@ function hydratePairedTaskRow(row: StoredPairedTaskRow): PairedTask {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function hydrateServiceHandoffRow(row: StoredServiceHandoffRow): ServiceHandoff {
|
function hydrateServiceHandoffRow(
|
||||||
|
row: StoredServiceHandoffRow,
|
||||||
|
): ServiceHandoff {
|
||||||
const sourceAgentType =
|
const sourceAgentType =
|
||||||
normalizeStoredAgentType(row.source_agent_type) ??
|
normalizeStoredAgentType(row.source_agent_type) ??
|
||||||
(row.source_role
|
(row.source_role
|
||||||
@@ -365,22 +373,22 @@ function hydrateServiceHandoffRow(row: StoredServiceHandoffRow): ServiceHandoff
|
|||||||
target_agent_type: targetAgentType,
|
target_agent_type: targetAgentType,
|
||||||
source_service_id:
|
source_service_id:
|
||||||
row.source_role != null
|
row.source_role != null
|
||||||
? resolveRoleServiceShadow(row.source_role, sourceAgentType) ??
|
? (resolveRoleServiceShadow(row.source_role, sourceAgentType) ??
|
||||||
SERVICE_SESSION_SCOPE
|
SERVICE_SESSION_SCOPE)
|
||||||
: SERVICE_SESSION_SCOPE,
|
: SERVICE_SESSION_SCOPE,
|
||||||
target_service_id:
|
target_service_id:
|
||||||
row.target_role != null
|
row.target_role != null
|
||||||
? resolveRoleServiceShadow(row.target_role, targetAgentType) ??
|
? (resolveRoleServiceShadow(row.target_role, targetAgentType) ??
|
||||||
SERVICE_SESSION_SCOPE
|
SERVICE_SESSION_SCOPE)
|
||||||
: SERVICE_SESSION_SCOPE,
|
: SERVICE_SESSION_SCOPE,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTableColumns(database: Database, tableName: string): string[] {
|
function getTableColumns(database: Database, tableName: string): string[] {
|
||||||
return (
|
return (
|
||||||
database
|
database.prepare(`PRAGMA table_info(${tableName})`).all() as Array<{
|
||||||
.prepare(`PRAGMA table_info(${tableName})`)
|
name: string;
|
||||||
.all() as Array<{ name: string }>
|
}>
|
||||||
).map((column) => column.name);
|
).map((column) => column.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -783,7 +791,9 @@ function createSchema(database: Database): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
database.exec(`ALTER TABLE service_handoffs ADD COLUMN source_agent_type TEXT`);
|
database.exec(
|
||||||
|
`ALTER TABLE service_handoffs ADD COLUMN source_agent_type TEXT`,
|
||||||
|
);
|
||||||
} catch {
|
} catch {
|
||||||
/* column already exists */
|
/* column already exists */
|
||||||
}
|
}
|
||||||
@@ -795,13 +805,17 @@ function createSchema(database: Database): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
database.exec(`ALTER TABLE paired_tasks ADD COLUMN reviewer_agent_type TEXT`);
|
database.exec(
|
||||||
|
`ALTER TABLE paired_tasks ADD COLUMN reviewer_agent_type TEXT`,
|
||||||
|
);
|
||||||
} catch {
|
} catch {
|
||||||
/* column already exists */
|
/* column already exists */
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
database.exec(`ALTER TABLE paired_tasks ADD COLUMN arbiter_agent_type TEXT`);
|
database.exec(
|
||||||
|
`ALTER TABLE paired_tasks ADD COLUMN arbiter_agent_type TEXT`,
|
||||||
|
);
|
||||||
} catch {
|
} catch {
|
||||||
/* column already exists */
|
/* column already exists */
|
||||||
}
|
}
|
||||||
@@ -1362,9 +1376,7 @@ function normalizeMemoryKeywords(keywords?: string[]): string[] {
|
|||||||
if (!Array.isArray(keywords)) return [];
|
if (!Array.isArray(keywords)) return [];
|
||||||
return [
|
return [
|
||||||
...new Set(
|
...new Set(
|
||||||
keywords
|
keywords.map((keyword) => keyword.trim().toLowerCase()).filter(Boolean),
|
||||||
.map((keyword) => keyword.trim().toLowerCase())
|
|
||||||
.filter(Boolean),
|
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -1453,7 +1465,9 @@ function queryFtsRowOrder(query: RecallMemoryQuery): Map<number, number> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function touchMemories(ids: number[]): void {
|
export function touchMemories(ids: number[]): void {
|
||||||
const uniqueIds = [...new Set(ids.filter((id) => Number.isInteger(id) && id > 0))];
|
const uniqueIds = [
|
||||||
|
...new Set(ids.filter((id) => Number.isInteger(id) && id > 0)),
|
||||||
|
];
|
||||||
if (uniqueIds.length === 0) return;
|
if (uniqueIds.length === 0) return;
|
||||||
const now = new Date().toISOString();
|
const now = new Date().toISOString();
|
||||||
const stmt = db.prepare(
|
const stmt = db.prepare(
|
||||||
@@ -1573,7 +1587,9 @@ export function rememberMemory(input: {
|
|||||||
input.sourceRef ?? null,
|
input.sourceRef ?? null,
|
||||||
createdAt,
|
createdAt,
|
||||||
);
|
);
|
||||||
const row = db.prepare('SELECT last_insert_rowid() AS id').get() as { id: number };
|
const row = db.prepare('SELECT last_insert_rowid() AS id').get() as {
|
||||||
|
id: number;
|
||||||
|
};
|
||||||
expireStaleMemories({
|
expireStaleMemories({
|
||||||
scopeKind: input.scopeKind,
|
scopeKind: input.scopeKind,
|
||||||
scopeKey: input.scopeKey,
|
scopeKey: input.scopeKey,
|
||||||
@@ -1598,8 +1614,9 @@ export function recallMemories(query: RecallMemoryQuery): MemoryRecord[] {
|
|||||||
const scored = rows
|
const scored = rows
|
||||||
.map((row, index) => {
|
.map((row, index) => {
|
||||||
const keywords = parseMemoryKeywords(row.keywords_json);
|
const keywords = parseMemoryKeywords(row.keywords_json);
|
||||||
const exactMatches = keywords.filter((keyword) => exactKeywords.has(keyword))
|
const exactMatches = keywords.filter((keyword) =>
|
||||||
.length;
|
exactKeywords.has(keyword),
|
||||||
|
).length;
|
||||||
const ftsScore = ftsOrder.get(row.id) ?? 0;
|
const ftsScore = ftsOrder.get(row.id) ?? 0;
|
||||||
const recencyScore = rows.length - index;
|
const recencyScore = rows.length - index;
|
||||||
return {
|
return {
|
||||||
@@ -2016,12 +2033,9 @@ export function getOpenWorkItem(
|
|||||||
id ASC
|
id ASC
|
||||||
LIMIT 1`,
|
LIMIT 1`,
|
||||||
)
|
)
|
||||||
.get(
|
.get(chatJid, agentType, preferredRole, preferredRole) as
|
||||||
chatJid,
|
| StoredWorkItemRow
|
||||||
agentType,
|
| undefined;
|
||||||
preferredRole,
|
|
||||||
preferredRole,
|
|
||||||
) as StoredWorkItemRow | undefined;
|
|
||||||
return row ? hydrateWorkItem(row) : undefined;
|
return row ? hydrateWorkItem(row) : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2066,9 +2080,9 @@ export function createProducedWorkItem(input: {
|
|||||||
db.prepare('SELECT last_insert_rowid() as id').get() as { id: number }
|
db.prepare('SELECT last_insert_rowid() as id').get() as { id: number }
|
||||||
).id;
|
).id;
|
||||||
return hydrateWorkItem(
|
return hydrateWorkItem(
|
||||||
db.prepare('SELECT * FROM work_items WHERE id = ?').get(
|
db
|
||||||
lastId,
|
.prepare('SELECT * FROM work_items WHERE id = ?')
|
||||||
) as StoredWorkItemRow,
|
.get(lastId) as StoredWorkItemRow,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2474,9 +2488,7 @@ export function getSession(
|
|||||||
.prepare(
|
.prepare(
|
||||||
'SELECT session_id FROM sessions WHERE group_folder = ? AND agent_type = ?',
|
'SELECT session_id FROM sessions WHERE group_folder = ? AND agent_type = ?',
|
||||||
)
|
)
|
||||||
.get(groupFolder, agentType) as
|
.get(groupFolder, agentType) as { session_id: string } | undefined;
|
||||||
| { session_id: string }
|
|
||||||
| undefined;
|
|
||||||
return row?.session_id;
|
return row?.session_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2613,8 +2625,7 @@ function buildRegisteredGroupFromStoredSettings(
|
|||||||
name: stored.name || stored.chatJid,
|
name: stored.name || stored.chatJid,
|
||||||
folder: stored.folder,
|
folder: stored.folder,
|
||||||
trigger: stored.trigger || `@${ASSISTANT_NAME}`,
|
trigger: stored.trigger || `@${ASSISTANT_NAME}`,
|
||||||
added_at:
|
added_at: capabilityMetadata?.added_at || new Date(0).toISOString(),
|
||||||
capabilityMetadata?.added_at || new Date(0).toISOString(),
|
|
||||||
agentConfig: capabilityMetadata?.agentConfig,
|
agentConfig: capabilityMetadata?.agentConfig,
|
||||||
requiresTrigger: stored.requiresTrigger,
|
requiresTrigger: stored.requiresTrigger,
|
||||||
isMain: stored.isMain ? true : undefined,
|
isMain: stored.isMain ? true : undefined,
|
||||||
@@ -2692,12 +2703,16 @@ export function assignRoom(
|
|||||||
const roomMode = input.roomMode || existing?.roomMode || 'single';
|
const roomMode = input.roomMode || existing?.roomMode || 'single';
|
||||||
const ownerAgentType =
|
const ownerAgentType =
|
||||||
input.ownerAgentType || existing?.ownerAgentType || OWNER_AGENT_TYPE;
|
input.ownerAgentType || existing?.ownerAgentType || OWNER_AGENT_TYPE;
|
||||||
const folder = resolveAssignedRoomFolder(db, chatJid, input.name, input.folder);
|
const folder = resolveAssignedRoomFolder(
|
||||||
|
db,
|
||||||
|
chatJid,
|
||||||
|
input.name,
|
||||||
|
input.folder,
|
||||||
|
);
|
||||||
const snapshot: RoomRegistrationSnapshot = {
|
const snapshot: RoomRegistrationSnapshot = {
|
||||||
name: input.name,
|
name: input.name,
|
||||||
folder,
|
folder,
|
||||||
triggerPattern:
|
triggerPattern: input.trigger || existing?.trigger || `@${ASSISTANT_NAME}`,
|
||||||
input.trigger || existing?.trigger || `@${ASSISTANT_NAME}`,
|
|
||||||
requiresTrigger: input.requiresTrigger ?? existing?.requiresTrigger ?? true,
|
requiresTrigger: input.requiresTrigger ?? existing?.requiresTrigger ?? true,
|
||||||
isMain: input.isMain ?? existing?.isMain ?? false,
|
isMain: input.isMain ?? existing?.isMain ?? false,
|
||||||
ownerAgentType,
|
ownerAgentType,
|
||||||
@@ -2911,7 +2926,9 @@ function getLegacyRegisteredGroup(
|
|||||||
'SELECT * FROM registered_groups WHERE jid = ? AND agent_type = ?',
|
'SELECT * FROM registered_groups WHERE jid = ? AND agent_type = ?',
|
||||||
)
|
)
|
||||||
.get(jid, agentType)
|
.get(jid, agentType)
|
||||||
: database.prepare('SELECT * FROM registered_groups WHERE jid = ?').get(jid)
|
: database
|
||||||
|
.prepare('SELECT * FROM registered_groups WHERE jid = ?')
|
||||||
|
.get(jid)
|
||||||
) as RegisteredGroupDatabaseRow | undefined;
|
) as RegisteredGroupDatabaseRow | undefined;
|
||||||
return parseRegisteredGroupRow(row);
|
return parseRegisteredGroupRow(row);
|
||||||
}
|
}
|
||||||
@@ -2949,7 +2966,9 @@ function getRegisteredGroupCapabilityMetadata(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function getStoredRoomRowsFromDatabase(database: Database): StoredRoomSettings[] {
|
function getStoredRoomRowsFromDatabase(
|
||||||
|
database: Database,
|
||||||
|
): StoredRoomSettings[] {
|
||||||
return database
|
return database
|
||||||
.prepare(
|
.prepare(
|
||||||
`SELECT chat_jid
|
`SELECT chat_jid
|
||||||
@@ -2958,7 +2977,10 @@ function getStoredRoomRowsFromDatabase(database: Database): StoredRoomSettings[]
|
|||||||
)
|
)
|
||||||
.all()
|
.all()
|
||||||
.map((row) =>
|
.map((row) =>
|
||||||
getStoredRoomSettingsRowFromDatabase(database, (row as { chat_jid: string }).chat_jid),
|
getStoredRoomSettingsRowFromDatabase(
|
||||||
|
database,
|
||||||
|
(row as { chat_jid: string }).chat_jid,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
.filter((row): row is StoredRoomSettings => Boolean(row));
|
.filter((row): row is StoredRoomSettings => Boolean(row));
|
||||||
}
|
}
|
||||||
@@ -2995,7 +3017,12 @@ function collectReservedFolders(
|
|||||||
FROM registered_groups
|
FROM registered_groups
|
||||||
WHERE ? IS NULL OR jid != ?`,
|
WHERE ? IS NULL OR jid != ?`,
|
||||||
)
|
)
|
||||||
.all(exceptChatJid ?? null, exceptChatJid ?? null, exceptChatJid ?? null, exceptChatJid ?? null) as Array<{
|
.all(
|
||||||
|
exceptChatJid ?? null,
|
||||||
|
exceptChatJid ?? null,
|
||||||
|
exceptChatJid ?? null,
|
||||||
|
exceptChatJid ?? null,
|
||||||
|
) as Array<{
|
||||||
folder: string | null;
|
folder: string | null;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
@@ -3042,7 +3069,9 @@ function resolveAssignedRoomFolder(
|
|||||||
const reserved = collectReservedFolders(database, chatJid);
|
const reserved = collectReservedFolders(database, chatJid);
|
||||||
if (explicitFolder) {
|
if (explicitFolder) {
|
||||||
if (!isValidGroupFolder(explicitFolder)) {
|
if (!isValidGroupFolder(explicitFolder)) {
|
||||||
throw new Error(`Invalid group folder "${explicitFolder}" for JID ${chatJid}`);
|
throw new Error(
|
||||||
|
`Invalid group folder "${explicitFolder}" for JID ${chatJid}`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (reserved.has(explicitFolder)) {
|
if (reserved.has(explicitFolder)) {
|
||||||
throw new Error(`Group folder "${explicitFolder}" is already assigned`);
|
throw new Error(`Group folder "${explicitFolder}" is already assigned`);
|
||||||
@@ -3050,7 +3079,10 @@ function resolveAssignedRoomFolder(
|
|||||||
return explicitFolder;
|
return explicitFolder;
|
||||||
}
|
}
|
||||||
|
|
||||||
const existingFolder = getStoredRoomSettingsRowFromDatabase(database, chatJid)?.folder;
|
const existingFolder = getStoredRoomSettingsRowFromDatabase(
|
||||||
|
database,
|
||||||
|
chatJid,
|
||||||
|
)?.folder;
|
||||||
if (existingFolder) return existingFolder;
|
if (existingFolder) return existingFolder;
|
||||||
|
|
||||||
return buildGeneratedRoomFolder(database, chatJid, name);
|
return buildGeneratedRoomFolder(database, chatJid, name);
|
||||||
@@ -3089,7 +3121,10 @@ function materializeRegisteredGroupsForRoom(
|
|||||||
added_at: string;
|
added_at: string;
|
||||||
agent_config: string | null;
|
agent_config: string | null;
|
||||||
}>;
|
}>;
|
||||||
const existingByType = new Map<AgentType, { added_at: string; agent_config: string | null }>();
|
const existingByType = new Map<
|
||||||
|
AgentType,
|
||||||
|
{ added_at: string; agent_config: string | null }
|
||||||
|
>();
|
||||||
for (const row of existingRows) {
|
for (const row of existingRows) {
|
||||||
const agentType = normalizeStoredAgentType(row.agent_type);
|
const agentType = normalizeStoredAgentType(row.agent_type);
|
||||||
if (agentType) {
|
if (agentType) {
|
||||||
@@ -3101,8 +3136,10 @@ function materializeRegisteredGroupsForRoom(
|
|||||||
const existing = existingByType.get(agentType);
|
const existing = existingByType.get(agentType);
|
||||||
const agentConfig =
|
const agentConfig =
|
||||||
agentType === ownerAgentType
|
agentType === ownerAgentType
|
||||||
? ownerAgentConfig ??
|
? (ownerAgentConfig ??
|
||||||
(existing?.agent_config ? JSON.parse(existing.agent_config) : undefined)
|
(existing?.agent_config
|
||||||
|
? JSON.parse(existing.agent_config)
|
||||||
|
: undefined))
|
||||||
: existing?.agent_config
|
: existing?.agent_config
|
||||||
? JSON.parse(existing.agent_config)
|
? JSON.parse(existing.agent_config)
|
||||||
: undefined;
|
: undefined;
|
||||||
@@ -3262,9 +3299,8 @@ function collectRoomRegistrationSnapshot(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const agentTypes = collectRegisteredAgentTypes(database, jid);
|
const agentTypes = collectRegisteredAgentTypes(database, jid);
|
||||||
const inferredOwnerAgentType = inferOwnerAgentTypeFromRegisteredAgentTypes(
|
const inferredOwnerAgentType =
|
||||||
agentTypes,
|
inferOwnerAgentTypeFromRegisteredAgentTypes(agentTypes);
|
||||||
);
|
|
||||||
const preferExplicitTrigger =
|
const preferExplicitTrigger =
|
||||||
existingStored?.modeSource === 'explicit' && existingStored.trigger;
|
existingStored?.modeSource === 'explicit' && existingStored.trigger;
|
||||||
const preferExplicitOwner =
|
const preferExplicitOwner =
|
||||||
@@ -3280,7 +3316,8 @@ function collectRoomRegistrationSnapshot(
|
|||||||
: undefined;
|
: undefined;
|
||||||
const inferredOwnerRow =
|
const inferredOwnerRow =
|
||||||
rows.find(
|
rows.find(
|
||||||
(row) => normalizeStoredAgentType(row.agent_type) === inferredOwnerAgentType,
|
(row) =>
|
||||||
|
normalizeStoredAgentType(row.agent_type) === inferredOwnerAgentType,
|
||||||
) ?? rows[0];
|
) ?? rows[0];
|
||||||
const ownerAgentType = preferredOwnerAgentType
|
const ownerAgentType = preferredOwnerAgentType
|
||||||
? preferredOwnerRow
|
? preferredOwnerRow
|
||||||
@@ -3292,12 +3329,11 @@ function collectRoomRegistrationSnapshot(
|
|||||||
return {
|
return {
|
||||||
name: first.name,
|
name: first.name,
|
||||||
folder: first.folder,
|
folder: first.folder,
|
||||||
triggerPattern:
|
triggerPattern: preferExplicitTrigger
|
||||||
preferExplicitTrigger
|
? existingStored.trigger!
|
||||||
? existingStored.trigger!
|
: preferredOwnerRow != null
|
||||||
: preferredOwnerRow != null
|
? preferredOwnerRow.trigger_pattern
|
||||||
? preferredOwnerRow.trigger_pattern
|
: ownerRow.trigger_pattern,
|
||||||
: ownerRow.trigger_pattern,
|
|
||||||
requiresTrigger: (first.requires_trigger ?? 1) === 1,
|
requiresTrigger: (first.requires_trigger ?? 1) === 1,
|
||||||
isMain: (first.is_main ?? 0) === 1,
|
isMain: (first.is_main ?? 0) === 1,
|
||||||
ownerAgentType,
|
ownerAgentType,
|
||||||
@@ -3376,14 +3412,20 @@ function updateStoredRoomMetadata(
|
|||||||
|
|
||||||
function syncStoredRoomRegistrationSnapshotForJid(chatJid: string): void {
|
function syncStoredRoomRegistrationSnapshotForJid(chatJid: string): void {
|
||||||
const existingSettings = getStoredRoomSettingsRowFromDatabase(db, chatJid);
|
const existingSettings = getStoredRoomSettingsRowFromDatabase(db, chatJid);
|
||||||
const snapshot = collectRoomRegistrationSnapshot(db, chatJid, existingSettings);
|
const snapshot = collectRoomRegistrationSnapshot(
|
||||||
|
db,
|
||||||
|
chatJid,
|
||||||
|
existingSettings,
|
||||||
|
);
|
||||||
if (!snapshot) return;
|
if (!snapshot) return;
|
||||||
|
|
||||||
if (!existingSettings) {
|
if (!existingSettings) {
|
||||||
insertStoredRoomSettings(
|
insertStoredRoomSettings(
|
||||||
db,
|
db,
|
||||||
chatJid,
|
chatJid,
|
||||||
inferRoomModeFromRegisteredAgentTypes(getRegisteredAgentTypesForJid(chatJid)),
|
inferRoomModeFromRegisteredAgentTypes(
|
||||||
|
getRegisteredAgentTypesForJid(chatJid),
|
||||||
|
),
|
||||||
'inferred',
|
'inferred',
|
||||||
snapshot,
|
snapshot,
|
||||||
);
|
);
|
||||||
@@ -3521,9 +3563,14 @@ function collectRegisteredAgentTypesForFolder(
|
|||||||
|
|
||||||
function resolveStablePairedTaskOwnerAgentType(
|
function resolveStablePairedTaskOwnerAgentType(
|
||||||
database: Database,
|
database: Database,
|
||||||
task: Pick<StoredPairedTaskRow, 'chat_jid' | 'group_folder' | 'owner_agent_type'>,
|
task: Pick<
|
||||||
|
StoredPairedTaskRow,
|
||||||
|
'chat_jid' | 'group_folder' | 'owner_agent_type'
|
||||||
|
>,
|
||||||
): AgentType | undefined {
|
): AgentType | undefined {
|
||||||
const persistedOwnerAgentType = normalizeStoredAgentType(task.owner_agent_type);
|
const persistedOwnerAgentType = normalizeStoredAgentType(
|
||||||
|
task.owner_agent_type,
|
||||||
|
);
|
||||||
if (persistedOwnerAgentType) {
|
if (persistedOwnerAgentType) {
|
||||||
return persistedOwnerAgentType;
|
return persistedOwnerAgentType;
|
||||||
}
|
}
|
||||||
@@ -3729,13 +3776,13 @@ function backfillChannelOwnerRoleMetadata(database: Database): void {
|
|||||||
const reviewerServiceId =
|
const reviewerServiceId =
|
||||||
row.reviewer_service_id == null
|
row.reviewer_service_id == null
|
||||||
? null
|
? null
|
||||||
: resolveRoleServiceShadow('reviewer', reviewerAgentType) ??
|
: (resolveRoleServiceShadow('reviewer', reviewerAgentType) ??
|
||||||
row.reviewer_service_id;
|
row.reviewer_service_id);
|
||||||
const arbiterServiceId =
|
const arbiterServiceId =
|
||||||
row.arbiter_service_id == null
|
row.arbiter_service_id == null
|
||||||
? null
|
? null
|
||||||
: resolveRoleServiceShadow('arbiter', arbiterAgentType) ??
|
: (resolveRoleServiceShadow('arbiter', arbiterAgentType) ??
|
||||||
row.arbiter_service_id;
|
row.arbiter_service_id);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
ownerServiceId === row.owner_service_id &&
|
ownerServiceId === row.owner_service_id &&
|
||||||
@@ -3911,13 +3958,13 @@ function backfillServiceHandoffServiceShadows(database: Database): void {
|
|||||||
|
|
||||||
const normalizedSourceServiceId =
|
const normalizedSourceServiceId =
|
||||||
row.source_role != null
|
row.source_role != null
|
||||||
? resolveRoleServiceShadow(row.source_role, sourceAgentType) ??
|
? (resolveRoleServiceShadow(row.source_role, sourceAgentType) ??
|
||||||
row.source_service_id
|
row.source_service_id)
|
||||||
: row.source_service_id;
|
: row.source_service_id;
|
||||||
const normalizedTargetServiceId =
|
const normalizedTargetServiceId =
|
||||||
row.target_role != null
|
row.target_role != null
|
||||||
? resolveRoleServiceShadow(row.target_role, targetAgentType) ??
|
? (resolveRoleServiceShadow(row.target_role, targetAgentType) ??
|
||||||
row.target_service_id
|
row.target_service_id)
|
||||||
: row.target_service_id;
|
: row.target_service_id;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@@ -4624,16 +4671,16 @@ export function setChannelOwnerLease(input: {
|
|||||||
const reviewerAgentType =
|
const reviewerAgentType =
|
||||||
input.reviewer_service_id == null && input.reviewer_agent_type == null
|
input.reviewer_service_id == null && input.reviewer_agent_type == null
|
||||||
? null
|
? null
|
||||||
: normalizeStoredAgentType(input.reviewer_agent_type) ??
|
: (normalizeStoredAgentType(input.reviewer_agent_type) ??
|
||||||
inferAgentTypeFromServiceShadow(input.reviewer_service_id ?? null) ??
|
inferAgentTypeFromServiceShadow(input.reviewer_service_id ?? null) ??
|
||||||
resolveStableReviewerAgentType(ownerAgentType, null);
|
resolveStableReviewerAgentType(ownerAgentType, null));
|
||||||
const arbiterAgentType =
|
const arbiterAgentType =
|
||||||
input.arbiter_service_id == null && input.arbiter_agent_type == null
|
input.arbiter_service_id == null && input.arbiter_agent_type == null
|
||||||
? null
|
? null
|
||||||
: normalizeStoredAgentType(input.arbiter_agent_type) ??
|
: (normalizeStoredAgentType(input.arbiter_agent_type) ??
|
||||||
inferAgentTypeFromServiceShadow(input.arbiter_service_id ?? null) ??
|
inferAgentTypeFromServiceShadow(input.arbiter_service_id ?? null) ??
|
||||||
ARBITER_AGENT_TYPE ??
|
ARBITER_AGENT_TYPE ??
|
||||||
null;
|
null);
|
||||||
|
|
||||||
db.prepare(
|
db.prepare(
|
||||||
`INSERT OR REPLACE INTO channel_owner (
|
`INSERT OR REPLACE INTO channel_owner (
|
||||||
@@ -4719,9 +4766,9 @@ export function createServiceHandoff(input: {
|
|||||||
db.prepare('SELECT last_insert_rowid() as id').get() as { id: number }
|
db.prepare('SELECT last_insert_rowid() as id').get() as { id: number }
|
||||||
).id;
|
).id;
|
||||||
return hydrateServiceHandoffRow(
|
return hydrateServiceHandoffRow(
|
||||||
db.prepare('SELECT * FROM service_handoffs WHERE id = ?').get(
|
db
|
||||||
lastId,
|
.prepare('SELECT * FROM service_handoffs WHERE id = ?')
|
||||||
) as StoredServiceHandoffRow,
|
.get(lastId) as StoredServiceHandoffRow,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -894,10 +894,9 @@ describe('assign_room success', () => {
|
|||||||
modeSource: 'explicit',
|
modeSource: 'explicit',
|
||||||
name: 'Legacy Tribunal Renamed',
|
name: 'Legacy Tribunal Renamed',
|
||||||
});
|
});
|
||||||
expect(getRegisteredAgentTypesForJid('legacy-tribunal@g.us').sort()).toEqual([
|
expect(
|
||||||
'claude-code',
|
getRegisteredAgentTypesForJid('legacy-tribunal@g.us').sort(),
|
||||||
'codex',
|
).toEqual(['claude-code', 'codex']);
|
||||||
]);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('main group can assign a tribunal room and materialize capability rows', async () => {
|
it('main group can assign a tribunal room and materialize capability rows', async () => {
|
||||||
|
|||||||
16
src/ipc.ts
16
src/ipc.ts
@@ -701,7 +701,10 @@ export async function processTaskIpc(
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Array.isArray(data.keywords) && !data.keywords.every((v) => typeof v === 'string')) {
|
if (
|
||||||
|
Array.isArray(data.keywords) &&
|
||||||
|
!data.keywords.every((v) => typeof v === 'string')
|
||||||
|
) {
|
||||||
logger.warn(
|
logger.warn(
|
||||||
{ sourceGroup, keywords: data.keywords },
|
{ sourceGroup, keywords: data.keywords },
|
||||||
'Invalid persist_memory request - keywords must be strings',
|
'Invalid persist_memory request - keywords must be strings',
|
||||||
@@ -717,10 +720,13 @@ export async function processTaskIpc(
|
|||||||
memoryKind:
|
memoryKind:
|
||||||
typeof data.memory_kind === 'string' ? data.memory_kind : null,
|
typeof data.memory_kind === 'string' ? data.memory_kind : null,
|
||||||
sourceKind:
|
sourceKind:
|
||||||
(data.source_kind as 'compact' | 'explicit' | 'import' | 'system' | undefined) ??
|
(data.source_kind as
|
||||||
'compact',
|
| 'compact'
|
||||||
sourceRef:
|
| 'explicit'
|
||||||
typeof data.source_ref === 'string' ? data.source_ref : null,
|
| 'import'
|
||||||
|
| 'system'
|
||||||
|
| undefined) ?? 'compact',
|
||||||
|
sourceRef: typeof data.source_ref === 'string' ? data.source_ref : null,
|
||||||
});
|
});
|
||||||
logger.info(
|
logger.info(
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -115,8 +115,8 @@ export async function runAgentForGroup(
|
|||||||
const inferredRole = resolveActiveRole(pairedTask?.status);
|
const inferredRole = resolveActiveRole(pairedTask?.status);
|
||||||
const canHonorForcedRole = Boolean(
|
const canHonorForcedRole = Boolean(
|
||||||
args.forcedRole === 'owner' ||
|
args.forcedRole === 'owner' ||
|
||||||
(args.forcedRole === 'reviewer' && currentLease.reviewer_agent_type) ||
|
(args.forcedRole === 'reviewer' && currentLease.reviewer_agent_type) ||
|
||||||
(args.forcedRole === 'arbiter' && currentLease.arbiter_agent_type),
|
(args.forcedRole === 'arbiter' && currentLease.arbiter_agent_type),
|
||||||
);
|
);
|
||||||
const activeRole = canHonorForcedRole ? args.forcedRole! : inferredRole;
|
const activeRole = canHonorForcedRole ? args.forcedRole! : inferredRole;
|
||||||
const effectiveServiceId = resolveLeaseServiceId(currentLease, activeRole);
|
const effectiveServiceId = resolveLeaseServiceId(currentLease, activeRole);
|
||||||
|
|||||||
@@ -20,9 +20,7 @@ import {
|
|||||||
type ServiceHandoff,
|
type ServiceHandoff,
|
||||||
type WorkItem,
|
type WorkItem,
|
||||||
} from './db.js';
|
} from './db.js';
|
||||||
import {
|
import { isSessionCommandSenderAllowed } from './config.js';
|
||||||
isSessionCommandSenderAllowed,
|
|
||||||
} from './config.js';
|
|
||||||
import { GroupQueue, GroupRunContext } from './group-queue.js';
|
import { GroupQueue, GroupRunContext } from './group-queue.js';
|
||||||
import {
|
import {
|
||||||
findChannel,
|
findChannel,
|
||||||
@@ -61,9 +59,7 @@ import {
|
|||||||
} from './types.js';
|
} from './types.js';
|
||||||
import { createScopedLogger, logger } from './logger.js';
|
import { createScopedLogger, logger } from './logger.js';
|
||||||
import { resolveGroupIpcPath } from './group-folder.js';
|
import { resolveGroupIpcPath } from './group-folder.js';
|
||||||
import {
|
import { hasReviewerLease } from './service-routing.js';
|
||||||
hasReviewerLease,
|
|
||||||
} from './service-routing.js';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if a message is a duplicate of the last bot final message in a paired room.
|
* Check if a message is a duplicate of the last bot final message in a paired room.
|
||||||
|
|||||||
@@ -88,7 +88,12 @@ function ownerBranchName(groupFolder: string): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function ownerWorkspacePath(groupFolder: string): string {
|
function ownerWorkspacePath(groupFolder: string): string {
|
||||||
return path.join(process.env.EJCLAW_DATA_DIR!, 'workspaces', groupFolder, 'owner');
|
return path.join(
|
||||||
|
process.env.EJCLAW_DATA_DIR!,
|
||||||
|
'workspaces',
|
||||||
|
groupFolder,
|
||||||
|
'owner',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('paired workspace manager', () => {
|
describe('paired workspace manager', () => {
|
||||||
@@ -739,14 +744,18 @@ describe('paired workspace manager', () => {
|
|||||||
groupFolder: 'head-base-room',
|
groupFolder: 'head-base-room',
|
||||||
});
|
});
|
||||||
|
|
||||||
const ownerWorkspace =
|
const ownerWorkspace = manager.provisionOwnerWorkspaceForPairedTask(
|
||||||
manager.provisionOwnerWorkspaceForPairedTask('paired-task-head-base');
|
'paired-task-head-base',
|
||||||
|
);
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
runGit(['branch', '--show-current'], ownerWorkspace.workspace_dir),
|
runGit(['branch', '--show-current'], ownerWorkspace.workspace_dir),
|
||||||
).toBe(ownerBranchName('head-base-room'));
|
).toBe(ownerBranchName('head-base-room'));
|
||||||
expect(
|
expect(
|
||||||
fs.readFileSync(path.join(ownerWorkspace.workspace_dir, 'README.md'), 'utf-8'),
|
fs.readFileSync(
|
||||||
|
path.join(ownerWorkspace.workspace_dir, 'README.md'),
|
||||||
|
'utf-8',
|
||||||
|
),
|
||||||
).toBe('feature base\n');
|
).toBe('feature base\n');
|
||||||
expect(runGit(['rev-parse', 'HEAD'], ownerWorkspace.workspace_dir)).toBe(
|
expect(runGit(['rev-parse', 'HEAD'], ownerWorkspace.workspace_dir)).toBe(
|
||||||
runGit(['rev-parse', 'HEAD'], canonicalDir),
|
runGit(['rev-parse', 'HEAD'], canonicalDir),
|
||||||
@@ -781,7 +790,10 @@ describe('paired workspace manager', () => {
|
|||||||
runGit(['branch', '--show-current'], secondProvision.workspace_dir),
|
runGit(['branch', '--show-current'], secondProvision.workspace_dir),
|
||||||
).toBe(ownerBranchName('tree-source-room'));
|
).toBe(ownerBranchName('tree-source-room'));
|
||||||
expect(
|
expect(
|
||||||
fs.readFileSync(path.join(secondProvision.workspace_dir, 'README.md'), 'utf-8'),
|
fs.readFileSync(
|
||||||
|
path.join(secondProvision.workspace_dir, 'README.md'),
|
||||||
|
'utf-8',
|
||||||
|
),
|
||||||
).toBe('tree source\n');
|
).toBe('tree source\n');
|
||||||
expect(runGit(['rev-parse', 'HEAD'], secondProvision.workspace_dir)).toBe(
|
expect(runGit(['rev-parse', 'HEAD'], secondProvision.workspace_dir)).toBe(
|
||||||
runGit(['rev-parse', 'HEAD'], canonicalDir),
|
runGit(['rev-parse', 'HEAD'], canonicalDir),
|
||||||
@@ -814,9 +826,9 @@ describe('paired workspace manager', () => {
|
|||||||
expect(
|
expect(
|
||||||
runGit(['branch', '--show-current'], reprovisioned.workspace_dir),
|
runGit(['branch', '--show-current'], reprovisioned.workspace_dir),
|
||||||
).toBe(ownerBranchName('repair-room'));
|
).toBe(ownerBranchName('repair-room'));
|
||||||
expect(
|
expect(runGit(['worktree', 'list', '--porcelain'], canonicalDir)).toContain(
|
||||||
runGit(['worktree', 'list', '--porcelain'], canonicalDir),
|
reprovisioned.workspace_dir,
|
||||||
).toContain(reprovisioned.workspace_dir);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('lazy-migrates a detached dirty owner workspace to a new channel branch', async () => {
|
it('lazy-migrates a detached dirty owner workspace to a new channel branch', async () => {
|
||||||
@@ -844,17 +856,23 @@ describe('paired workspace manager', () => {
|
|||||||
runGit(['branch', '--show-current'], ownerWorkspace.workspace_dir),
|
runGit(['branch', '--show-current'], ownerWorkspace.workspace_dir),
|
||||||
).toBe(ownerBranchName('migrate-room'));
|
).toBe(ownerBranchName('migrate-room'));
|
||||||
expect(
|
expect(
|
||||||
fs.readFileSync(path.join(ownerWorkspace.workspace_dir, 'README.md'), 'utf-8'),
|
fs.readFileSync(
|
||||||
|
path.join(ownerWorkspace.workspace_dir, 'README.md'),
|
||||||
|
'utf-8',
|
||||||
|
),
|
||||||
).toBe('detached dirty\n');
|
).toBe('detached dirty\n');
|
||||||
expect(
|
expect(
|
||||||
fs.readFileSync(path.join(ownerWorkspace.workspace_dir, 'NEW_FILE.txt'), 'utf-8'),
|
fs.readFileSync(
|
||||||
|
path.join(ownerWorkspace.workspace_dir, 'NEW_FILE.txt'),
|
||||||
|
'utf-8',
|
||||||
|
),
|
||||||
).toBe('new file\n');
|
).toBe('new file\n');
|
||||||
expect(runGit(['status', '--short'], ownerWorkspace.workspace_dir)).toContain(
|
expect(
|
||||||
'M README.md',
|
runGit(['status', '--short'], ownerWorkspace.workspace_dir),
|
||||||
);
|
).toContain('M README.md');
|
||||||
expect(runGit(['status', '--short'], ownerWorkspace.workspace_dir)).toContain(
|
expect(
|
||||||
'?? NEW_FILE.txt',
|
runGit(['status', '--short'], ownerWorkspace.workspace_dir),
|
||||||
);
|
).toContain('?? NEW_FILE.txt');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('lazy-migrates a detached dirty owner workspace onto an existing matching channel branch', async () => {
|
it('lazy-migrates a detached dirty owner workspace onto an existing matching channel branch', async () => {
|
||||||
@@ -875,7 +893,10 @@ describe('paired workspace manager', () => {
|
|||||||
const workspaceDir = ownerWorkspacePath('migrate-existing-room');
|
const workspaceDir = ownerWorkspacePath('migrate-existing-room');
|
||||||
fs.mkdirSync(path.dirname(workspaceDir), { recursive: true });
|
fs.mkdirSync(path.dirname(workspaceDir), { recursive: true });
|
||||||
runGit(['worktree', 'add', workspaceDir, 'HEAD'], canonicalDir);
|
runGit(['worktree', 'add', workspaceDir, 'HEAD'], canonicalDir);
|
||||||
fs.writeFileSync(path.join(workspaceDir, 'README.md'), 'detached existing\n');
|
fs.writeFileSync(
|
||||||
|
path.join(workspaceDir, 'README.md'),
|
||||||
|
'detached existing\n',
|
||||||
|
);
|
||||||
fs.writeFileSync(path.join(workspaceDir, 'NOTES.md'), 'keep me\n');
|
fs.writeFileSync(path.join(workspaceDir, 'NOTES.md'), 'keep me\n');
|
||||||
|
|
||||||
const ownerWorkspace = manager.provisionOwnerWorkspaceForPairedTask(
|
const ownerWorkspace = manager.provisionOwnerWorkspaceForPairedTask(
|
||||||
@@ -886,10 +907,16 @@ describe('paired workspace manager', () => {
|
|||||||
runGit(['branch', '--show-current'], ownerWorkspace.workspace_dir),
|
runGit(['branch', '--show-current'], ownerWorkspace.workspace_dir),
|
||||||
).toBe(ownerBranchName('migrate-existing-room'));
|
).toBe(ownerBranchName('migrate-existing-room'));
|
||||||
expect(
|
expect(
|
||||||
fs.readFileSync(path.join(ownerWorkspace.workspace_dir, 'README.md'), 'utf-8'),
|
fs.readFileSync(
|
||||||
|
path.join(ownerWorkspace.workspace_dir, 'README.md'),
|
||||||
|
'utf-8',
|
||||||
|
),
|
||||||
).toBe('detached existing\n');
|
).toBe('detached existing\n');
|
||||||
expect(
|
expect(
|
||||||
fs.readFileSync(path.join(ownerWorkspace.workspace_dir, 'NOTES.md'), 'utf-8'),
|
fs.readFileSync(
|
||||||
|
path.join(ownerWorkspace.workspace_dir, 'NOTES.md'),
|
||||||
|
'utf-8',
|
||||||
|
),
|
||||||
).toBe('keep me\n');
|
).toBe('keep me\n');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -588,7 +588,14 @@ export function provisionOwnerWorkspaceForPairedTask(
|
|||||||
runGit(['worktree', 'add', workspaceDir, targetBranch], canonicalWorkDir);
|
runGit(['worktree', 'add', workspaceDir, targetBranch], canonicalWorkDir);
|
||||||
} else {
|
} else {
|
||||||
runGit(
|
runGit(
|
||||||
['worktree', 'add', '-b', targetBranch, workspaceDir, canonicalHeadCommit],
|
[
|
||||||
|
'worktree',
|
||||||
|
'add',
|
||||||
|
'-b',
|
||||||
|
targetBranch,
|
||||||
|
workspaceDir,
|
||||||
|
canonicalHeadCommit,
|
||||||
|
],
|
||||||
canonicalWorkDir,
|
canonicalWorkDir,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,14 +55,14 @@ function normalizeLeaseRow(
|
|||||||
const reviewerAgentType =
|
const reviewerAgentType =
|
||||||
row.reviewer_service_id == null
|
row.reviewer_service_id == null
|
||||||
? null
|
? null
|
||||||
: row.reviewer_agent_type ??
|
: (row.reviewer_agent_type ??
|
||||||
inferAgentTypeFromServiceShadow(row.reviewer_service_id) ??
|
inferAgentTypeFromServiceShadow(row.reviewer_service_id) ??
|
||||||
resolveRoleAgentPlan({
|
resolveRoleAgentPlan({
|
||||||
paired: true,
|
paired: true,
|
||||||
groupAgentType: ownerAgentType,
|
groupAgentType: ownerAgentType,
|
||||||
configuredReviewer: REVIEWER_AGENT_TYPE,
|
configuredReviewer: REVIEWER_AGENT_TYPE,
|
||||||
configuredArbiter: ARBITER_AGENT_TYPE,
|
configuredArbiter: ARBITER_AGENT_TYPE,
|
||||||
}).reviewerAgentType;
|
}).reviewerAgentType);
|
||||||
const arbiterAgentType =
|
const arbiterAgentType =
|
||||||
row.arbiter_agent_type ??
|
row.arbiter_agent_type ??
|
||||||
(row.arbiter_service_id
|
(row.arbiter_service_id
|
||||||
@@ -79,12 +79,14 @@ function normalizeLeaseRow(
|
|||||||
resolveRoleServiceShadow('owner', ownerAgentType) ??
|
resolveRoleServiceShadow('owner', ownerAgentType) ??
|
||||||
normalizeServiceId(row.owner_service_id),
|
normalizeServiceId(row.owner_service_id),
|
||||||
reviewer_service_id: reviewerAgentType
|
reviewer_service_id: reviewerAgentType
|
||||||
? resolveRoleServiceShadow('reviewer', reviewerAgentType) ??
|
? (resolveRoleServiceShadow('reviewer', reviewerAgentType) ??
|
||||||
normalizeServiceId(row.reviewer_service_id!)
|
normalizeServiceId(row.reviewer_service_id!))
|
||||||
: null,
|
: null,
|
||||||
arbiter_service_id: arbiterAgentType
|
arbiter_service_id: arbiterAgentType
|
||||||
? resolveRoleServiceShadow('arbiter', arbiterAgentType) ??
|
? (resolveRoleServiceShadow('arbiter', arbiterAgentType) ??
|
||||||
(row.arbiter_service_id ? normalizeServiceId(row.arbiter_service_id) : null)
|
(row.arbiter_service_id
|
||||||
|
? normalizeServiceId(row.arbiter_service_id)
|
||||||
|
: null))
|
||||||
: row.arbiter_service_id
|
: row.arbiter_service_id
|
||||||
? normalizeServiceId(row.arbiter_service_id)
|
? normalizeServiceId(row.arbiter_service_id)
|
||||||
: isArbiterEnabled()
|
: isArbiterEnabled()
|
||||||
@@ -218,19 +220,19 @@ export function resolveLeaseServiceId(
|
|||||||
);
|
);
|
||||||
case 'reviewer':
|
case 'reviewer':
|
||||||
return lease.reviewer_agent_type
|
return lease.reviewer_agent_type
|
||||||
? resolveRoleServiceShadow('reviewer', lease.reviewer_agent_type) ??
|
? (resolveRoleServiceShadow('reviewer', lease.reviewer_agent_type) ??
|
||||||
(lease.reviewer_service_id
|
(lease.reviewer_service_id
|
||||||
? normalizeServiceId(lease.reviewer_service_id)
|
? normalizeServiceId(lease.reviewer_service_id)
|
||||||
: null)
|
: null))
|
||||||
: lease.reviewer_service_id
|
: lease.reviewer_service_id
|
||||||
? normalizeServiceId(lease.reviewer_service_id)
|
? normalizeServiceId(lease.reviewer_service_id)
|
||||||
: null;
|
: null;
|
||||||
case 'arbiter':
|
case 'arbiter':
|
||||||
return lease.arbiter_agent_type
|
return lease.arbiter_agent_type
|
||||||
? resolveRoleServiceShadow('arbiter', lease.arbiter_agent_type) ??
|
? (resolveRoleServiceShadow('arbiter', lease.arbiter_agent_type) ??
|
||||||
(lease.arbiter_service_id
|
(lease.arbiter_service_id
|
||||||
? normalizeServiceId(lease.arbiter_service_id)
|
? normalizeServiceId(lease.arbiter_service_id)
|
||||||
: null)
|
: null))
|
||||||
: lease.arbiter_service_id
|
: lease.arbiter_service_id
|
||||||
? normalizeServiceId(lease.arbiter_service_id)
|
? normalizeServiceId(lease.arbiter_service_id)
|
||||||
: null;
|
: null;
|
||||||
@@ -242,7 +244,9 @@ export function isOwnerServiceForChat(
|
|||||||
serviceId: string = SERVICE_ID,
|
serviceId: string = SERVICE_ID,
|
||||||
): boolean {
|
): boolean {
|
||||||
const lease = getEffectiveChannelLease(chatJid);
|
const lease = getEffectiveChannelLease(chatJid);
|
||||||
return normalizeServiceId(serviceId) === resolveLeaseServiceId(lease, 'owner');
|
return (
|
||||||
|
normalizeServiceId(serviceId) === resolveLeaseServiceId(lease, 'owner')
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isReviewerServiceForChat(
|
export function isReviewerServiceForChat(
|
||||||
@@ -250,11 +254,16 @@ export function isReviewerServiceForChat(
|
|||||||
serviceId: string = SERVICE_ID,
|
serviceId: string = SERVICE_ID,
|
||||||
): boolean {
|
): boolean {
|
||||||
const lease = getEffectiveChannelLease(chatJid);
|
const lease = getEffectiveChannelLease(chatJid);
|
||||||
return normalizeServiceId(serviceId) === resolveLeaseServiceId(lease, 'reviewer');
|
return (
|
||||||
|
normalizeServiceId(serviceId) === resolveLeaseServiceId(lease, 'reviewer')
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function hasReviewerLease(chatJid: string): boolean {
|
export function hasReviewerLease(chatJid: string): boolean {
|
||||||
return resolveLeaseServiceId(getEffectiveChannelLease(chatJid), 'reviewer') !== null;
|
return (
|
||||||
|
resolveLeaseServiceId(getEffectiveChannelLease(chatJid), 'reviewer') !==
|
||||||
|
null
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isArbiterServiceForChat(
|
export function isArbiterServiceForChat(
|
||||||
@@ -262,7 +271,9 @@ export function isArbiterServiceForChat(
|
|||||||
serviceId: string = SERVICE_ID,
|
serviceId: string = SERVICE_ID,
|
||||||
): boolean {
|
): boolean {
|
||||||
const lease = getEffectiveChannelLease(chatJid);
|
const lease = getEffectiveChannelLease(chatJid);
|
||||||
return normalizeServiceId(serviceId) === resolveLeaseServiceId(lease, 'arbiter');
|
return (
|
||||||
|
normalizeServiceId(serviceId) === resolveLeaseServiceId(lease, 'arbiter')
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function shouldServiceProcessChat(
|
export function shouldServiceProcessChat(
|
||||||
|
|||||||
@@ -35,7 +35,8 @@ describe('sqlite-memory-store helpers', () => {
|
|||||||
id: 2,
|
id: 2,
|
||||||
scopeKind: 'room',
|
scopeKind: 'room',
|
||||||
scopeKey: 'room:ejclaw',
|
scopeKey: 'room:ejclaw',
|
||||||
content: '자동 recall/compact persist를 호스트가 책임지는 방향으로 합의함.',
|
content:
|
||||||
|
'자동 recall/compact persist를 호스트가 책임지는 방향으로 합의함.',
|
||||||
keywords: ['room:ejclaw'],
|
keywords: ['room:ejclaw'],
|
||||||
memoryKind: null,
|
memoryKind: null,
|
||||||
sourceKind: 'compact',
|
sourceKind: 'compact',
|
||||||
|
|||||||
@@ -138,11 +138,7 @@ vi.mock('./service-routing.js', () => ({
|
|||||||
hasReviewerLease: vi.fn(() => false),
|
hasReviewerLease: vi.fn(() => false),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
import {
|
import { _initTestDatabase, createTask, getTaskById } from './db.js';
|
||||||
_initTestDatabase,
|
|
||||||
createTask,
|
|
||||||
getTaskById,
|
|
||||||
} from './db.js';
|
|
||||||
import * as codexTokenRotation from './codex-token-rotation.js';
|
import * as codexTokenRotation from './codex-token-rotation.js';
|
||||||
import { TIMEZONE } from './config.js';
|
import { TIMEZONE } from './config.js';
|
||||||
import * as serviceRouting from './service-routing.js';
|
import * as serviceRouting from './service-routing.js';
|
||||||
|
|||||||
@@ -757,7 +757,11 @@ async function runGithubCiTask(
|
|||||||
if (check.terminal) {
|
if (check.terminal) {
|
||||||
await statusTracker.update('completed');
|
await statusTracker.update('completed');
|
||||||
if (check.completionMessage) {
|
if (check.completionMessage) {
|
||||||
await sendScheduledMessage(deps, task.chat_jid, check.completionMessage);
|
await sendScheduledMessage(
|
||||||
|
deps,
|
||||||
|
task.chat_jid,
|
||||||
|
check.completionMessage,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
deleteTask(task.id);
|
deleteTask(task.id);
|
||||||
completedAndDeleted = true;
|
completedAndDeleted = true;
|
||||||
|
|||||||
Reference in New Issue
Block a user