Skip to content

feat(chat): launch prompt delivered as a Chat message; no Chat for terminal sessions - #1050

Merged
selfcontained merged 5 commits into
mainfrom
agt_29017e45a209/chat-r5
Sep 4, 2026
Merged

feat(chat): launch prompt delivered as a Chat message; no Chat for terminal sessions#1050
selfcontained merged 5 commits into
mainfrom
agt_29017e45a209/chat-r5

Conversation

@selfcontained

@selfcontained selfcontained commented Sep 4, 2026

Copy link
Copy Markdown
Owner

What

Round 5 of the chat surface. Two changes, both from live use.

The launch prompt arrives as a Chat message

With the flag on and a launch-context post recorded, the CLI's first user turn is now the prompt wrapped in the same --- DISPATCH CHAT (id: …) --- envelope a composer message uses: the attachments block (startup files, links, pins) and the trailer telling the agent the user reads Chat and to reply with dispatch_chat_post. So an agent started with context replies in Chat, reply-linked to its launch post, instead of only in the Console.

  • The post id is minted before the command is built and threaded through a new ChatService.prepareLaunchContext, so the pane and the feed post share one id and one set of attachment lines.
  • Unwrapped as before for: flag off, no launch context, job runs (their prompt is a system-prompt append), terminal agents.
  • Child launches keep the "You were launched by…" header inside the envelope; the feed post shows the launcher's bare prompt.

Terminal sessions never offer Chat

A terminal session has no CLI to talk to, so with the flag on it shows a plain Terminal tab, Console only: no toggle, no unread badge, split values fold to the terminal view, and /agents/:id/chat redirects to the bare route.

Deviation worth reading

Resolving the launch post is now on the launch's critical path, since the first turn needs its id (round 4 only had to keep the write off the path). It's bounded by a 5s resolve timeout: on timeout or error the agent launches unwrapped with no post, so the pane can never name a row that was never written. The write stays detached and bounded as before.

Reviews

Backend security and general code review filed five items; a verification pass found two more. All seven are fixed and verified:

  • Envelope markers inside embedded text are escaped at the single wrapping point (covering prompt, attachment lines, and code attachments), with CRLF / lone CR / U+2028 / U+2029 normalized first so a forged marker cannot hide behind an unusual separator.
  • dispatch_chat_post now requires replyTo to be a message on the posting agent's own feed.
  • The envelope is only used when the launch post is durably written: bounded resolve and write, collision-safe ON CONFLICT DO NOTHING insert, plain prompt with no post otherwise.
  • A prompt longer than the Chat limit makes the post say it is showing less than the turn.
  • Flag-off, job, terminal and inert launches keep the round-4 detached path, so a hung recorder adds no launch latency and no settings query.
  • The enveloped turn lists every startup file, link and pin again; only the stored row is capped, with a count line.
  • Create-path failures (including the new settings read) route through failCreate, so a rejecting query can no longer strand an agent row in creating.

Checks

pnpm run check, prettier, full pnpm run test (4948 passed), finalize:web, E2E (195 passed). Hands-on: a Claude agent launched with a prompt, a file and a link showed the envelope in its Console and replied in Chat with replyTo set to the launch post; a terminal session showed no Chat.

🤖 Generated with Claude Code

selfcontained and others added 4 commits September 3, 2026 22:18
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…sions

Round 5 of the chat surface. With the flag on and a launch post recorded,
the CLI's first user turn is that post wrapped in the same DISPATCH CHAT
envelope a Chat message is injected with — its id, its attachment lines,
and the trailer pointing the agent at dispatch_chat_post — so an agent
started from the Chat tab answers there instead of in the terminal.

The post id is minted before the command is built and handed to
ChatService.prepareLaunchContext, which resolves the attachments and hands
back both the envelope lines it will store and the write itself, so the
pane and the feed describe the same attachments and the envelope names the
row that was actually written. Resolving is bounded (it is on the launch's
critical path): on timeout the agent launches unwrapped with no post.
Unwrapped otherwise too — flag off, no context, job runs, terminal agents.

Web: a terminal session has no CLI to chat with, so agentSupportsChat is
ANDed with the flag once in agents-view and the narrowed value drives the
tab label, the pane, the split-pane normaliser and the /chat redirect.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Four security-review findings plus one regression on the round-5 launch
envelope:

- Neutralize envelope markers in embedded text. buildChatEnvelope escapes
  any line of its body matching the marker grammar, so a prompt or an
  attachment cannot close Dispatch's block and open a forged one naming
  another message id. ChatService.post now rejects a replyTo that is not a
  message on the posting agent's own feed.
- A durable post is the precondition for the envelope. createAgent awaits
  both the resolve and the write before building the wrapped turn, and
  drops the envelope on rejection, timeout or an id collision
  (ChatStore.insertIfAbsent is ON CONFLICT DO NOTHING).
- The post says when it shows less than the turn: an explicit truncation
  note past CHAT_MESSAGE_MAX_CHARS, and a count of the attachments the
  20-attachment cap left off.
- Only a wrapped launch pays for the post. The chat-surface flag is read
  once, up front; a flag-off, job, terminal or inert launch keeps the
  round-4 detached path.
- The enveloped turn lists every startup file, link and pin again, not
  just the first 20.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ate cleanly

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@selfcontained
selfcontained marked this pull request as ready for review September 4, 2026 05:59
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@selfcontained
selfcontained merged commit 257c62a into main Sep 4, 2026
1 check passed
@selfcontained
selfcontained deleted the agt_29017e45a209/chat-r5 branch September 4, 2026 14:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant