Skip to content

feat(console): operator participant mode — agents can reply to you#206

Open
Lanzelot1 wants to merge 1 commit into
feat/console-controlfrom
feat/console-participant
Open

feat(console): operator participant mode — agents can reply to you#206
Lanzelot1 wants to merge 1 commit into
feat/console-controlfrom
feat/console-participant

Conversation

@Lanzelot1

Copy link
Copy Markdown
Collaborator

What

Sending from cotal console was one-way: the console joins as an invisible observer (registerPresence:false) with no inbox (consume:false), so an agent's DM reply to the operator had no consumer to land in and expired unread. Channel posts already round-tripped (agents reply in-channel, the tap shows it); DMs/:call/:ask didn't.

Now, on the operator's first send, the console upgrades the observer in place (no rebuild, no provisioning, no manager):

  • Presence — a new CotalEndpoint.startPresence() registers the operator as a role:"operator" peer, so agents see it on the roster and can reply. Clean offline leave on exit (reuses the existing stop path).
  • ReceiveMeshView reveals the DM lens and captures the operator's own inbox. On an auth mesh (chat-narrowed tap) it opens a second tap scoped to inst.<selfId>.>; on an open mesh the whole-space tap already covers it (so no second tap — avoids a double-count). A best-effort one-shot backfill catches a reply that raced activation.
  • Outbound DMs are recorded locally (auth path) so a thread shows both sides.

Gating / reach: canWrite-gated, so a pure-watch session never registers (stays invisible). It works where the console can actually send — an open mesh (bare) or an auth mesh with a capable --creds. Under the auth-default read-only god-view cred the console can't send at all, so participant mode never activates there; full auth-default participation would need a dedicated participant cred profile (future work, deliberately out of scope to keep this minimal and not touch the core cred matrix).

Stacked on #200 (feat/console-control) — retarget to main after it lands.

Verified

  • pnpm typecheck + pnpm test + smoke:view clean.
  • Live end-to-end (open broker + a stub agent that acks any DM): before activation the operator is off the roster; after a :dm, it registers (the stub sees it), the agent's ack reaches the operator's DM lens as a two-sided thread, and there's exactly one distinct outbound (no double-count). 5/5.
  • tmux click-through: :dm @stub hi there → operator joins the roster as an endpoint peer, the feed shows the reply, the DM lens shows user → stub / stub → user: ack; on q the operator deregisters cleanly (only the agent remains live).

Core change

One small, generic method — CotalEndpoint.startPresence() (post-construction presence + heartbeat, idempotent) + making doRegister mutable + a scoped inboxHistory(selfId). Everything else is console/MeshView.

@Lanzelot1 Lanzelot1 force-pushed the feat/console-control branch from 36f96ba to 29745a9 Compare July 8, 2026 08:35
@Lanzelot1 Lanzelot1 force-pushed the feat/console-participant branch from 8909549 to c6c57ed Compare July 8, 2026 08:35
@Lanzelot1 Lanzelot1 force-pushed the feat/console-control branch from 29745a9 to d245b87 Compare July 8, 2026 08:51
@Lanzelot1 Lanzelot1 force-pushed the feat/console-participant branch from c6c57ed to e452a23 Compare July 8, 2026 08:51
@Lanzelot1 Lanzelot1 force-pushed the feat/console-control branch from d245b87 to e29a877 Compare July 8, 2026 09:07
@Lanzelot1 Lanzelot1 force-pushed the feat/console-participant branch 2 times, most recently from a9a5f91 to 36b9355 Compare July 8, 2026 09:19
A console send was one-way: the observer is invisible (registerPresence
false) with no inbox (consume false), so an agent's DM reply to the operator
had nowhere to land. On the operator's first send the console now upgrades
the observer in place — CotalEndpoint.startPresence() registers presence so
agents see and can reply to the operator, and MeshView reveals the DM lens
and captures the operator's own inbox (inst.<self>.>). Outbound DMs are
recorded locally (auth path) for two-sided threads; a clean offline leave
fires on exit. canWrite-gated, so a pure-watch session stays invisible;
works on open meshes (or a capable --creds). Runtime upgrade, no rebuild, no
provisioning or manager.
@Lanzelot1 Lanzelot1 force-pushed the feat/console-control branch from e29a877 to f4d3961 Compare July 8, 2026 09:42
@Lanzelot1 Lanzelot1 force-pushed the feat/console-participant branch from 36b9355 to 5dcde20 Compare July 8, 2026 09:42
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