Last extraction of Phase 3. Moves inter-agent messaging + create_agent +
destination projection into src/modules/agent-to-agent/. Core retains:
- `channel_type === 'agent'` dispatch in delivery.ts, guarded by
hasTable('agent_destinations') + dynamic import into module.
- Channel-permission ACL in delivery.ts, guarded by hasTable, with
inlined SQL (no module import from core).
- writeDestinations call in container-runner.ts, guarded by hasTable +
dynamic import into module.
- createMessagingGroupAgent's destination side effect in db/messaging-groups.ts,
guarded by hasTable. This is a documented transitional tier violation
(core imports from optional module), analogous to src/access.ts.
Migration `004-agent-destinations.ts` renamed to `module-agent-to-agent-
destinations.ts` preserving `name: 'agent-destinations'` so existing DBs
don't re-run it.
delivery.ts: 600 → 449 lines. handleSystemAction's last switch case gone
(just registry + default log-and-drop). notifyAgent helper removed (only
create_agent used it).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
65 lines
2.5 KiB
TypeScript
65 lines
2.5 KiB
TypeScript
/**
|
|
* Agent-to-agent message routing.
|
|
*
|
|
* Outbound messages with `channel_type === 'agent'` target another agent
|
|
* group rather than a channel. Permission is enforced via `agent_destinations` —
|
|
* the source agent must have a row for the target. Content is copied verbatim;
|
|
* the target's formatter looks up the source agent in its own local map to
|
|
* display a name.
|
|
*
|
|
* Self-messages are always allowed (used for system notes injected back into
|
|
* an agent's own session, e.g. post-approval follow-up prompts).
|
|
*
|
|
* Core delivery.ts dispatches into this via a dynamic import guarded by a
|
|
* `channel_type === 'agent'` check. When the module is absent the check in
|
|
* core throws with a "module not installed" message so retry → mark failed.
|
|
*/
|
|
import { getAgentGroup } from '../../db/agent-groups.js';
|
|
import { getSession } from '../../db/sessions.js';
|
|
import { wakeContainer } from '../../container-runner.js';
|
|
import { log } from '../../log.js';
|
|
import { resolveSession, writeSessionMessage } from '../../session-manager.js';
|
|
import type { Session } from '../../types.js';
|
|
import { hasDestination } from './db/agent-destinations.js';
|
|
|
|
export interface RoutableAgentMessage {
|
|
id: string;
|
|
platform_id: string | null;
|
|
content: string;
|
|
}
|
|
|
|
export async function routeAgentMessage(msg: RoutableAgentMessage, session: Session): Promise<void> {
|
|
const targetAgentGroupId = msg.platform_id;
|
|
if (!targetAgentGroupId) {
|
|
throw new Error(`agent-to-agent message ${msg.id} is missing a target agent group id`);
|
|
}
|
|
if (
|
|
targetAgentGroupId !== session.agent_group_id &&
|
|
!hasDestination(session.agent_group_id, 'agent', targetAgentGroupId)
|
|
) {
|
|
throw new Error(
|
|
`unauthorized agent-to-agent: ${session.agent_group_id} has no destination for ${targetAgentGroupId}`,
|
|
);
|
|
}
|
|
if (!getAgentGroup(targetAgentGroupId)) {
|
|
throw new Error(`target agent group ${targetAgentGroupId} not found for message ${msg.id}`);
|
|
}
|
|
const { session: targetSession } = resolveSession(targetAgentGroupId, null, null, 'agent-shared');
|
|
writeSessionMessage(targetAgentGroupId, targetSession.id, {
|
|
id: `a2a-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
kind: 'chat',
|
|
timestamp: new Date().toISOString(),
|
|
platformId: session.agent_group_id,
|
|
channelType: 'agent',
|
|
threadId: null,
|
|
content: msg.content,
|
|
});
|
|
log.info('Agent message routed', {
|
|
from: session.agent_group_id,
|
|
to: targetAgentGroupId,
|
|
targetSession: targetSession.id,
|
|
});
|
|
const fresh = getSession(targetSession.id);
|
|
if (fresh) await wakeContainer(fresh);
|
|
}
|