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:
gavrielc
2026-04-20 23:30:47 +03:00
parent dadf258136
commit 6c26c0413a
15 changed files with 503 additions and 213 deletions

View File

@@ -10,6 +10,14 @@ export interface ChannelSetup {
/** Called when an inbound message arrives from the platform. */
onInbound(platformId: string, threadId: string | null, message: InboundMessage): void | Promise<void>;
/**
* Called by admin-transport adapters (CLI) that want to route a message to
* an arbitrary channel/platform and optionally redirect replies elsewhere.
* Regular chat adapters should use `onInbound`; `onInboundEvent` skips the
* adapter-channel-type injection so the caller can target any wired mg.
*/
onInboundEvent(event: InboundEvent): void | Promise<void>;
/** Called when the adapter discovers metadata about a conversation. */
onMetadata(platformId: string, name?: string, isGroup?: boolean): void;
@@ -17,6 +25,41 @@ export interface ChannelSetup {
onAction(questionId: string, selectedOption: string, userId: string): void;
}
/** Delivery address used for reply-to overrides and (normally) the inbound's own origin. */
export interface DeliveryAddress {
channelType: string;
platformId: string;
threadId: string | null;
}
/**
* Full inbound event handed to the router.
*
* `channelType` + `platformId` + `threadId` identify which messaging group /
* session receives the message. `replyTo`, when set, overrides where the
* agent's reply is delivered — used by the CLI admin transport when the
* operator wants a message routed to one channel but replies echoed back to
* their terminal. Agents cannot set `replyTo`; it is a router-layer concept
* set only by external adapters carrying operator intent.
*/
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.
* See InboundMessage.isMention for the full explanation.
*/
isMention?: boolean;
};
replyTo?: DeliveryAddress;
}
/** Inbound message from adapter to host. */
export interface InboundMessage {
id: string;