FN-8424: route CLI chat replies through inbox mail
Route agent replies to the correct CLI or dashboard mailbox with bounded polling deadlines. - add reply-parent routing validation and CLI/dashboard inbox selection - preserve named mailbox conversations while handling per-message reply deadlines - document chat and inbox interfaces and cover deadline and routing regressions Files changed: .changeset/fn-8424-cli-chat-reply-routing.md | 7 + docs/agents.md | 20 +- docs/cli-reference.md | 29 +- packages/cli/src/bin.ts | 15 +- packages/cli/src/commands/__tests__/chat.test.ts | 262 +++++++++--------- .../cli/src/commands/__tests__/message.test.ts | 12 + packages/cli/src/commands/chat.ts | 293 ++++++++++++--------- packages/cli/src/commands/message.ts | 18 +- ...tools-send-message-recipient-validation.test.ts | 86 +++++- packages/engine/src/agent-heartbeat-prompts.ts | 8 +- packages/engine/src/agent-tools.ts | 71 +++-- 11 files changed, 523 insertions(+), 298 deletions(-) Fusion-Task-Id: FN-8424 Fusion-Task-Lineage: 28d0ef88-717e-4f39-8880-64d2fef94706 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
@@ -26,16 +26,17 @@ Use `fn chat` to message an agent from your terminal.
|
||||
### Synopsis
|
||||
|
||||
```bash
|
||||
fn chat <agent-id> [message…] [--once] [--non-interactive] [--poll-ms <n>] [--conversation-id <id>]
|
||||
fn chat <agent-id> [message…] [--once] [--non-interactive] [--poll-ms <n>] [--reply-timeout-ms <n>] [--conversation-id <id>]
|
||||
```
|
||||
|
||||
### Behavior
|
||||
|
||||
- `fn chat <agent-id>` opens an interactive **mailbox conversation** REPL. It delivers to the target agent's MessageStore inbox (`fn_read_messages`), not to a dashboard ChatStore/ChatView session or multi-agent room.
|
||||
- `fn chat <agent-id>` opens an interactive mailbox-conversation REPL.
|
||||
- Each message is stored as a `user-to-agent` MessageStore message from `cli` with `metadata.wakeRecipient=true`, `metadata.kind="cli-chat"`, and a durable `metadata.conversationId`.
|
||||
- The default conversation id is `cli-chat:cli:<agent-id>` and is stable for that CLI-user/agent pair in the selected project. Use `--conversation-id <id>` to name or share a different mailbox thread; the CLI banner prints the active id.
|
||||
- `fn message send` remains a distinct one-shot mailbox command and does not add CLI-chat conversation metadata.
|
||||
- Agent replies are polled from your inbox and printed only when they carry that `conversationId` or use `replyTo.messageId` to reference a message already in the thread; reply streaming and dashboard operator-inbox readability are separately tracked by FN-8424 / issue #2363.
|
||||
- CLI chat is MessageStore mail plus polling, not a token-streaming SSE session. Replies are printed only when they carry the active conversation ID or reply to a known thread message.
|
||||
- On a direct-message reply, agents must pass `reply_to_message_id` and either set `to_id` to the exact `[from: type:id]` value reported by `fn_read_messages` (including `cli`) or omit it to use the safe parent-sender default. Parent-derived routing is allowed only when the parent was addressed to the replying agent; an explicit `to_id` remains available for intentional forwarding.
|
||||
- The default conversation ID is `cli-chat:cli:<agent-id>`; use `--conversation-id <id>` to name or share a different mailbox thread.
|
||||
- One-shot replies have a deadline independent of `--poll-ms`; polling sleeps are capped at the remaining deadline. The interactive REPL maintains one pending deadline per outbound message, reports and clears an unanswered request, then continues to receive later replies.
|
||||
- Dashboard-created agent chat sessions request the target agent's declared `metadata.skills` plus enabled plugin-contributed skills, so skills such as `ce-debug` are available in chat when the contributing plugin is enabled for the requesting project. Model-only QuickChat sessions request enabled plugin skills, and room responder sessions request the responder agent's skills.
|
||||
- Agent-acting session lanes share the same skill-injection contract as executor sessions: executor, merger, triage, reviewer, heartbeat, step-session, dashboard chat/room responders, CLI agent execution, planning, mission interview, milestone/slice interview, agent-onboarding interview, workflow design, memory dreams/insight extraction, and scheduled cron automation all request agent/fallback skills plus enabled plugin-contributed skills when a plugin runner is available. Utility-only lanes that only summarize/extract/generate JSON (title/PR summaries, memory compaction, subtask breakdown, text refinement, agent generation, PR metadata generation, evaluator/research synthesis, and similar one-shot helpers) intentionally stay exempt to avoid loading skills where no agent-style tool loop can use them.
|
||||
- In dashboard model-loop chat (main chat, QuickChat, and room responders), typing `/skill:{name}` requests that skill for the current AI session and strips the slash token from the prompt sent to the model. Slash and catalog-style names such as `/skill:review/pr`, `/skill:review/pr/SKILL.md`, and `source::skills/review/pr/SKILL.md` resolve to the matching discovered bare skill token across chat and agent session lanes. The requested skill is still subject to the normal enabled/disabled execution-skill filters; CLI-agent-backed PTY chat keeps raw terminal input semantics and does not interpret this command.
|
||||
@@ -63,8 +64,9 @@ For the user-facing gallery and notification UX, see [Artifacts View](./dashboar
|
||||
|
||||
- `--once` send one message and exit after first reply (or timeout)
|
||||
- `--non-interactive` read full stdin to EOF as message body
|
||||
- `--poll-ms <n>` override poll interval in milliseconds (default `1000`, or `FUSION_CHAT_POLL_MS`)
|
||||
- `--conversation-id <id>` override the default named mailbox conversation
|
||||
- `--poll-ms <n>` override poll interval in milliseconds (default `1000`, or `FUSION_CHAT_POLL_MS`); sleeps never extend past the nearest reply deadline
|
||||
- `--reply-timeout-ms <n>` set the per-reply deadline in milliseconds (default `60000`, or `FUSION_CHAT_REPLY_TIMEOUT_MS`)
|
||||
- `--conversation-id <id>` override the default named mailbox conversation ID
|
||||
|
||||
### Examples
|
||||
|
||||
@@ -1052,8 +1054,8 @@ Mailbox replies use `message.metadata.replyTo.messageId` as the stable reply lin
|
||||
|
||||
- `fn_read_messages` includes each message ID in its human-readable output so agents can target a specific message.
|
||||
- When a message has `metadata.replyTo.messageId`, `fn_read_messages` now includes one-level reply-parent context inline (and in structured tool details) so heartbeat/mailbox runs can understand what the message is replying to without expanding full threads.
|
||||
- `fn_send_message` supports `reply_to_message_id`; when provided, the sent message is stored with `metadata.replyTo.messageId`.
|
||||
- Heartbeat prompts explicitly instruct agents to include `reply_to_message_id` when replying.
|
||||
- `fn_send_message` supports `reply_to_message_id`; when provided, the sent message is stored with `metadata.replyTo.messageId`. If that parent was addressed to the sending agent, the tool safely defaults its recipient to the parent's sender; foreign, missing, or misaddressed parents cannot supply a recipient.
|
||||
- Heartbeat prompts explicitly instruct agents to include `reply_to_message_id` and the exact sender ID when replying.
|
||||
|
||||
The dashboard mailbox UI also uses the same metadata contract when users click **Reply**, so user and agent replies share one threading model.
|
||||
|
||||
|
||||
@@ -1031,6 +1031,7 @@ User mailbox operations for sending and managing direct messages with agents.
|
||||
|
||||
```bash
|
||||
fn message inbox
|
||||
fn message inbox --user dashboard
|
||||
fn message outbox
|
||||
fn message send AGENT-001 "Please prioritize FN-222"
|
||||
fn message read MSG-123
|
||||
@@ -1039,7 +1040,7 @@ fn message delete MSG-123
|
||||
|
||||
| Subcommand | Description |
|
||||
|---|---|
|
||||
| `fn message inbox` | List your inbox messages (newest first, up to 20). |
|
||||
| `fn message inbox [--user <cli\|dashboard>]` | List the selected user mailbox (newest first, up to 20); defaults to the CLI mailbox. |
|
||||
| `fn message outbox` | List messages you sent (newest first, up to 20). |
|
||||
| `fn message send <agent-id> <content>` | Send a user→agent message and print the created message ID. |
|
||||
| `fn message read <id>` | Show one full message by ID and auto-mark it as read if unread. |
|
||||
@@ -1047,7 +1048,8 @@ fn message delete MSG-123
|
||||
|
||||
### Mailbox behavior
|
||||
|
||||
- `inbox` header shows unread totals as `Inbox (<count> unread)`.
|
||||
- `inbox` defaults to the separate CLI user mailbox (`cli`). Use `fn message inbox --user dashboard` to automate reads of the dashboard operator mailbox (`dashboard`) shown by the UI; the two identities are intentionally not unified.
|
||||
- `inbox` header shows unread totals as `Inbox (<count> unread)` or `Dashboard Inbox (<count> unread)`.
|
||||
- Unread inbox rows are prefixed with `●`; read rows have no dot.
|
||||
- Inbox sender labels use `Agent <id>` for agent senders and raw user IDs for user senders.
|
||||
- Outbox recipient labels use `Agent <id>` for agent recipients.
|
||||
@@ -1062,29 +1064,30 @@ fn message delete MSG-123
|
||||
| Option | Description |
|
||||
|---|---|
|
||||
| `--project <name>` | Route mailbox operations to a specific registered project (resolved via project context). Supported by all `fn message` subcommands. |
|
||||
| `--user <cli\|dashboard>` | Select the mailbox for `fn message inbox`; defaults to `cli`. |
|
||||
|
||||
### Related command
|
||||
|
||||
`fn agent mailbox <agent-id>` is separate from `fn message`: it inspects an **agent-owned mailbox** (agent inbox view), while `fn message ...` manages the **CLI user mailbox**.
|
||||
`fn agent mailbox <agent-id>` is separate from `fn message`: it inspects an **agent-owned mailbox** (agent inbox view), while `fn message ...` manages the CLI or dashboard operator user mailbox.
|
||||
|
||||
---
|
||||
|
||||
## `fn chat`
|
||||
|
||||
Named mailbox conversation loop with a specific agent. It delivers through the agent's MessageStore inbox; it is **not** a dashboard ChatView/ChatStore session or a multi-agent room.
|
||||
Interactive CLI conversation loop with a specific agent.
|
||||
|
||||
```bash
|
||||
fn chat <agent-id> [message…] [--once] [--non-interactive] [--poll-ms <n>] [--conversation-id <id>]
|
||||
fn chat <agent-id> [message…] [--once] [--non-interactive] [--poll-ms <n>] [--reply-timeout-ms <n>] [--conversation-id <id>]
|
||||
```
|
||||
|
||||
### Behavior
|
||||
|
||||
- `fn chat <agent-id>` starts an interactive mailbox-conversation REPL.
|
||||
- `fn chat <agent-id> <message…>` sends one message and waits for a reply (`--once` implied).
|
||||
- The default conversation id is `cli-chat:cli:<agent-id>`, stable for the CLI user and target agent in the selected project. The session banner prints it so a later invocation resumes the same named thread.
|
||||
- Messages are sent as `user-to-agent` records from CLI user `cli` with `metadata.wakeRecipient=true`, `metadata.kind="cli-chat"`, and `metadata.conversationId`.
|
||||
- `fn message send` remains a one-shot mailbox command and does not add `cli-chat` conversation metadata.
|
||||
- Replies are polled from the CLI user inbox and printed only when they carry the active `conversationId` or use `replyTo.messageId` to reference a message already in that thread. This mailbox path does not create a dashboard chat session or a multi-agent room.
|
||||
- Messages are sent as `user-to-agent` records from CLI user `cli` with `metadata.wakeRecipient=true`, `metadata.kind="cli-chat"`, and a durable `metadata.conversationId`.
|
||||
- This is MessageStore mail plus polling, not token-streaming SSE. Replies are printed only when they carry the active conversation ID or reply to a known thread message.
|
||||
- Agents replying through `fn_send_message` should pass `reply_to_message_id`; replies default to the original sender only when that parent message was addressed to the replying agent.
|
||||
- One-shot chat has a reply deadline independent of the polling interval. Interactive chat tracks each outbound message independently: it prints a timeout for an unanswered request, clears that request, and continues the REPL for later messages.
|
||||
|
||||
### Options
|
||||
|
||||
@@ -1092,8 +1095,9 @@ fn chat <agent-id> [message…] [--once] [--non-interactive] [--poll-ms <n>] [--
|
||||
|---|---|
|
||||
| `--once` | Send one message and exit after first reply (or timeout). |
|
||||
| `--non-interactive` | Read full stdin to EOF as message body (useful for pipes/scripts). |
|
||||
| `--poll-ms <n>` | Poll interval in milliseconds (default `1000`, or `FUSION_CHAT_POLL_MS`). |
|
||||
| `--conversation-id <id>` | Override the default mailbox conversation id to name or share a thread. |
|
||||
| `--poll-ms <n>` | Poll interval in milliseconds (default `1000`, or `FUSION_CHAT_POLL_MS`). Poll sleeps are capped at the nearest reply deadline. |
|
||||
| `--reply-timeout-ms <n>` | Per-reply deadline in milliseconds (default `60000`, or `FUSION_CHAT_REPLY_TIMEOUT_MS`), independent of `--poll-ms`. |
|
||||
| `--conversation-id <id>` | Override the default named mailbox conversation ID. |
|
||||
|
||||
### Examples
|
||||
|
||||
@@ -1108,7 +1112,7 @@ fn chat agent-abc123 "status update?"
|
||||
printf "deploy report" | fn chat agent-abc123 --once --non-interactive
|
||||
```
|
||||
|
||||
> Agent replies require a running engine for the same project (for example `fn dashboard` or `fn serve`). Reply streaming and dashboard-operator inbox readability are tracked separately in FN-8424 / issue #2363.
|
||||
> Agent replies require a running engine for the same project (for example `fn dashboard` or `fn serve`).
|
||||
>
|
||||
> See [Agents: Interactive CLI Chat](./agents.md#interactive-cli-chat) for agent-oriented details.
|
||||
|
||||
@@ -1293,6 +1297,7 @@ Subcommands: `search`, `install`.
|
||||
| `--once` | `fn chat` |
|
||||
| `--non-interactive` | `fn chat` |
|
||||
| `--poll-ms` | `fn chat` |
|
||||
| `--reply-timeout-ms` | `fn chat` |
|
||||
|
||||
For configuration details used by these commands, see [Settings Reference](./settings-reference.md).
|
||||
|
||||
|
||||
Reference in New Issue
Block a user