Commit Graph
2 Commits
Author SHA1 Message Date
Brennan Benson 9fdd90ffa7 feat(orchestration): a native chat can be a dispatch worker, like a terminal agent (#22972)
* feat(native-chat): a queued card can record a Dispatch's task as its source

A chat worker's task is held in the chat's queue like any agent message, so
the card needs to say which Dispatch it is from: the sender, run, task and
Dispatch ids. An older build reads an unknown kind as the person's card and
sends it as written.

* feat(orchestration): a chat can be a dispatch worker, like a terminal agent

`dispatch --to orca_session_id:<id>` and `worker-start --terminal
orca_session_id:<id>` now accept a chat on this host instead of refusing it.

- The chat is refused only where mail to it would be: unknown, a provider id,
  another host, or closed. A chat can't be its own coordinator's worker.
- Its task goes through sendAgentTurn as a queued send, as mail notices do:
  an idle chat starts a turn, a busy one holds a card naming the Dispatch.
  The operation id is derived from the Dispatch, so a resend replays.
- worker-start reads that outcome as the terminal path reads its write; a
  card held behind a running turn is handed over with its start unobserved.
- The Dispatch names the chat by its /clear root, with no pane or process,
  and records its Orca session id, so its own sub-dispatches nest under it.
- Mail to dispatch:<id> reaches the chat; worker-show/list/read read the
  chat's session records; stop and abandon never close the chat; closing the
  chat fails its Dispatch as closing a terminal does.
- A dispatch preamble for a structured session names the CLI the structured
  mail lane names.

* test(orchestration): a chat worker's reach, and its Dispatch across a /clear

At rest is live, a closed chat has exited, and another host or nothing to
read is unverifiable. A /clear keeps the chat's Dispatch, and the session
that continues the chat reports as it.

* fix(orchestration): chat worker review round 1

- dispatch --inject is a keepalive-backed wait, so a chat that takes a while
  to accept its task no longer reads as a dead runtime at the 30 s idle cut.
- An injected task whose delivery is unknown keeps its Dispatch open, as an
  unknown worker-start does, instead of failing it while the chat may run it.
- A busy chat's worker-start receipt says the task waits as a card in the
  chat's queue, and that worker-abandon does not remove that card.
- A close that puts the chat's tab back no longer fails its Dispatch: the
  closed-chat settle runs after the close's outcome, not at its hide.
- worker-start adopting a chat says it gave the task to the chat, instead of
  claiming it started a terminal agent; the mode value is unchanged.
- Test: a chat whose agent has not taken its task reads outcome_unknown.

* test(native-chat): a close's hide defers its hidden notice until the close settles

The rollback suite pinned the hide's arguments; it now expects the deferred
notice, and that the notice is sent once, after a rolled-back tab is back.

* fix(orchestration): a chat worker whose successor is unknown is unverifiable, not exited

A /clear successor this host has no record of is missing evidence, not proof
the chat is gone. Only a closed chat reads exited, so only a closed chat
settles its Dispatch; a lost successor reads unverifiable with that reason.

* fix(orchestration): chat worker review round 2

- A chat's close notice settles only that chat's Dispatch. A scan of every
  chat Dispatch read another chat whose close was still in flight (its tab
  hidden, maybe to be put back) as closed and failed its Dispatch.
- Only a dispatch --inject into a chat is a long poll; a terminal inject
  writes and returns, and keeps its short-RPC slot.
- Receipt wording: worker-start says it gave the task to the chat, and a
  queued task is sent when the chat's queue reaches it.

* fix(orchestration): chat worker wording, review round 3

- worker-start's mode sentence for a chat states the placement, which is
  true whether the task is delivered, queued or refused.
- Comments and a test title no longer claim a close notice re-derives every
  chat worker, or that a queued task waits for the current turn.

* fix(orchestration): build a structured worker's task sender from the start's own ids

The minted worker's preamble names who its task is from with the run, task
and Dispatch ids worker-start already holds, so it reads no Dispatch row.

* fix(orchestration): point a chat worker at its held Dispatch mail again

A chat worker's coordinator mail lands in its Dispatch's mailbox, but a
chat's idle edge re-derived the Dispatch mailbox only for a party with a
terminal handle. A pointer lost with the provider then waited for new mail
after a restart or a /clear. The re-derivation now looks the Dispatch up by
the party's address, which names a worker by its handle and a chat by its
root.

* fix(native-chat): open a task card's sender by address; a task carries no mail

Opening an agent message's sender looked up the mail it carried, which only a
mail notice has; with the task kind in the union that read no longer typed.
A task's coordinator is found by its address alone.
2026-10-06 18:36:21 -07:00
Brennan Benson c893048aeb feat(native-chat): show which agent a chat message is from, and open it (#25888)
* feat(native-chat): show which agent a chat message is from

A message another agent sends into a native chat (today the orchestration
mail notice) looked exactly like the person's own: a right-hand bubble and a
queued card identical to theirs. The sender was recorded only on the queue
row, so a message sent to an idle chat, or a card once it was sent, had no
sender at all.

The sender now lives on the message itself (`from` on the message body):
- The send path carries it on both deliveries; an idle chat records it too,
  and it survives the queue, /clear's carry, restart and history.
- No fingerprint covers it: one shared payload projection leaves it out at
  every fingerprint site, so stored digests are unchanged and the provider's
  echo still folds into the same bubble. The provider never receives it.
- A client cannot set it: its send params stay strict.
- One reader normalizes it where it is read (journal load, queued row,
  client projection); a newer message kind keeps the sender an agent's.
- Each sender keeps a name snapshot from names Orca controls: a chat's
  conversation name or agent label, a terminal's own tab title or agent,
  a federated sender's task name. Never an agent-set terminal title.
- The separate queued-row sender column and its plumbing are removed; it
  never shipped.

Desktop shows such a message as a left-aligned "Message from <name>" row;
the name opens the sender: its chat through the existing open-chat flow
(after a host lookup that follows /clear and dispatch addresses), or its
terminal through the terminal-link path, with the existing feedback when
it is gone. A queued card shows a plain "From <name>" line. Mobile shows
the same attribution, not yet clickable.

* fix(native-chat): name and open an agent's message by what Orca shows for it

Review fixes for showing who an agent message is from.

- Names: a sender is named by its dispatch's task (local workers too, not
  only federated ones), else a chat by the label its tab shows, else a
  terminal by its tab's stored title, else its agent. The conversation
  name the snapshot read before has no writer, so every chat read
  "<Agent> Chat".
- One sender builder (agentMessageSender) and a public runtime naming
  object (orchestrationSenderNames) any send site can use; the terminal
  naming moves out of the unrelated runtime layer into its own module.
- Opening a terminal sender always asks the chat's host: a terminal handle
  is issued per run, so the host finds an earlier run's terminal again
  through the pane its mail was sent from. The pane key never leaves the
  host.
- Feedback follows what the host found: a gone chat or a gone pane by the
  kind it lost, "pane no longer available" only when the terminal answer
  proves it, and the existing "couldn't reach" / "needs a newer Orca"
  words otherwise.
- Another agent's message is not offered rewind (it would put the
  agent's text in the person's composer), and a Codex rewind keeps the
  sender on the messages it retains.
- The queued card and mobile name every sender, two that share a name
  included, and count in "+N" only those left out; the transcript row
  separates names with ", ".
- The host no longer normalizes the sender when loading the journal or
  the queue: nothing on the host reads it, and clients read it through
  the one reader at their projections.

* fix(native-chat): name a worker's final report by its task; clearer open-sender failures

- A worker's own worker_done settles its dispatch before the mail lane
  names who the report is from, so a terminal worker's completion read
  "Codex". A local worker is now named by the dispatch it holds, else the
  one it last held (handles are per run, so that is this run's). The
  coordinator mail rig now attests the worker's process, so its report is
  accepted and settles as it does in production.
- Opening a sender: a version block or an unknown answer says "Update Orca
  to open it.", since either side may be the older one; "This needs a
  newer Orca on the computer running this chat" stays only for a host
  that predates the lookup. "Orca couldn't reach the agent." is followed
  by "Try again.", each sentence translated on its own. No new copy.

* fix(native-chat): name a worker only by work it holds or just reported

A worker with no active dispatch was named by the newest dispatch ever
assigned to its terminal, at any status and any age: one that failed before
it ran, or a task finished long ago that its later, unrelated mail kept
showing. Now a worker is named by its active dispatch, else by the dispatch
its own worker_done in the announced mail names (only if that dispatch is
its own), else by its tab or agent as any terminal is.

* fix(native-chat): restore a rewound message's sender through its typed body; mobile card fixtures name their attribution

- The rewind's held-sender restore lives with the retained rows and reads both
  bodies through the journal's message-body check, instead of a shared helper
  taking any object (anti-slop: no-object-parameters).
- Mobile queued-card test fixtures carry the attribution field the card gained.

* fix(native-chat): never call a sender gone without proof; the sender builder moves out of the mail lane

- A federated sender (dispatch:<id>) runs on another host, which the chat's
  host cannot open: its name is plain text, and the host's lookup answers
  'not found' rather than 'gone' for a dispatch it does not run. Not seeing a
  worker is no evidence it stopped.
- The lookup calls a chat gone only when the identity is proven lost; any
  other failure reaches the client as 'could not reach'.
- A sender open that throws shows 'couldn't reach the agent' instead of an
  unhandled rejection.
- agentMessageSender, the one sender builder, lives in its own module, so the
  runtime's naming no longer imports from the mail lane.
2026-10-06 15:14:04 -07:00