feat(router,cli): replyTo override + CLI admin-transport flows
- InboundEvent gains an optional replyTo; router stamps the row's address
fields from it when set, so replies can route to a different channel than
the one the inbound came in on.
- ChannelSetup adds onInboundEvent for admin-transport adapters that build
the full event themselves.
- CLI wire format accepts {text, to, reply_to}. Routed messages go through
onInboundEvent and do not evict an active chat client.
- init-first-agent hands the DM welcome to the running service via
data/cli.sock — synchronous wake, no sweep wait. Fails loudly if the
service is down; no silent fallback.
- Split the CLI scratch-agent bootstrap into scripts/init-cli-agent.ts;
init-first-agent is DM-only.
Agents cannot set replyTo: it lives only on the inbound/router seam and is
consumed once when writing messages_in.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -32,32 +32,12 @@ import { resolveSession, writeSessionMessage } from './session-manager.js';
|
||||
import { wakeContainer } from './container-runner.js';
|
||||
import { getSession } from './db/sessions.js';
|
||||
import type { AgentGroup, MessagingGroup, MessagingGroupAgent } from './types.js';
|
||||
import type { InboundEvent } from './channels/adapter.js';
|
||||
|
||||
function generateId(): string {
|
||||
return `msg-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
||||
}
|
||||
|
||||
export interface InboundEvent {
|
||||
channelType: string;
|
||||
platformId: string;
|
||||
threadId: string | null;
|
||||
message: {
|
||||
id: string;
|
||||
kind: 'chat' | 'chat-sdk';
|
||||
content: string; // JSON blob
|
||||
timestamp: string;
|
||||
/**
|
||||
* Platform-confirmed bot-mention signal forwarded from the adapter.
|
||||
* When defined, it's authoritative — use this instead of text-matching
|
||||
* agent_group_name, which breaks on platforms where the mention token
|
||||
* is the bot's platform username (e.g. Telegram). undefined means the
|
||||
* adapter doesn't provide the signal; evaluateEngage falls back to
|
||||
* agent-name regex.
|
||||
*/
|
||||
isMention?: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sender-resolver hook. Runs before agent resolution.
|
||||
*
|
||||
@@ -408,13 +388,23 @@ async function deliverToAgent(
|
||||
|
||||
const { session, created } = resolveSession(agent.agent_group_id, mg.id, event.threadId, effectiveSessionMode);
|
||||
|
||||
// The inbound row's (channel_type, platform_id, thread_id) is the address
|
||||
// the agent's reply will be delivered to. Normally it mirrors the source
|
||||
// (stamped from the event). When the caller supplied `replyTo` (CLI admin
|
||||
// transport acting on operator intent), the reply is redirected there.
|
||||
const deliveryAddr = event.replyTo ?? {
|
||||
channelType: event.channelType,
|
||||
platformId: event.platformId,
|
||||
threadId: event.threadId,
|
||||
};
|
||||
|
||||
writeSessionMessage(session.agent_group_id, session.id, {
|
||||
id: messageIdForAgent(event.message.id, agent.agent_group_id),
|
||||
kind: event.message.kind,
|
||||
timestamp: event.message.timestamp,
|
||||
platformId: event.platformId,
|
||||
channelType: event.channelType,
|
||||
threadId: event.threadId,
|
||||
platformId: deliveryAddr.platformId,
|
||||
channelType: deliveryAddr.channelType,
|
||||
threadId: deliveryAddr.threadId,
|
||||
content: event.message.content,
|
||||
trigger: wake ? 1 : 0,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user