msg

package
v0.2.6 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 15, 2026 License: Apache-2.0 Imports: 8 Imported by: 0

Documentation

Overview

Package msg contains all typed message structs used for inter-actor communication via NATS. Messages are plain Go structs (no protobuf). They are serialized into NATSEnvelope for transport.

Autonomous agent messages.

Humanoid messages, including contact pairing and the allowlist.

Lane and pane-group messages: the two middle levels of the tree. Kept together because a pane group only ever exists inside a lane, and the operations on them mirror each other.

The `##llm select` picker payload: WorkspaceActor → attached front-ends, pushed on pane.<paneID>.llm.picker.

The daemon does not drive the picker. It answers a bare `##llm select` with everything a front-end needs to run the interaction itself — the activatable models, why the rest are not activatable, the scopes a selection can bind at, and whether each model's provider already has a key — and then goes back to its mailbox. Whatever the user picks comes back as an ordinary `##` command (`##<scope> model <ref>`, `##secret new <NAME> <value>`), so every existing guard applies unchanged and nothing new can bind a model behind their back.

This is what keeps an arrow-key picker compatible with the constraint that forced the numbered menu in the first place (see actors.cmdLLMSelect): a `##` command runs on the WorkspaceActor's mailbox goroutine and cannot block waiting for an answer. Here it never waits — it publishes and returns.

A front-end that ignores this push loses nothing: the numbered menu is still printed, and `##llm select <n>` still works.

Pane-level messages: input and lifecycle for a single pane, its LLM actor, cross-pane listening, pipeline mode, attention and replay.

Sharing messages: raw PTY relay for remote observers, upstream pane sharing, the shared-pane protocol, per-pane share restrictions, and WebSocket connection reliability.

Snapshot request/reply messages. Every level of the tree answers one of these; they are grouped here rather than split across the four level-specific files because they are a single request/reply protocol.

Tab-level messages: what the workspace and the CLI send to a TabActor, plus the tab's active-pane query and its layout operations.

Workspace-level messages: what the TUI and the CLI send to the WorkspaceActor, and the workspace-level layout operations.

Package msg proxy_audit_aliases.go — re-exports rysh-shared/msg governance proxy audit types (design 001 §4.5) so rysh-cli references them as msg.MsgProxyRequestAudit etc. The codec is registered in rysh-shared's DefaultCodecRegistry, which the CLI registry is built from, so no CLI-side codec registration is needed (mirrors usage_aliases.go).

Package msg shared_aliases.go — re-exports rysh-shared/msg types as type aliases. Type aliases (=) are NOT new types — they are the same underlying Go type. This is required so that messages sent from rysh-cli actors to rysh-shared/agentic actors match the type switches in the shared actors' Receive() methods.

Package msg usage_aliases.go — re-exports rysh-shared/msg usage-ledger types (design 003) so rysh-cli actors reference them as msg.MsgUsageRecord etc. The codecs are registered in rysh-shared's DefaultCodecRegistry, which the CLI registry is built from, so no CLI-side codec registration is needed.

Index

Constants

View Source
const (
	// TUI → WorkspaceActor
	TagCreateTab           = "MsgCreateTab"
	TagCreatePane          = "MsgCreatePane"
	TagCreatePaneDown      = "MsgCreatePaneDown"
	TagClosePane           = "MsgClosePane"
	TagFocusNextTab        = "MsgFocusNextTab"
	TagFocusPrevTab        = "MsgFocusPrevTab"
	TagFocusTabIndex       = "MsgFocusTabIndex"
	TagMoveTab             = "MsgMoveTab"
	TagSetTabBarOrient     = "MsgSetTabBarOrientation"
	TagFocusPane           = "MsgFocusPane"
	TagFocusPaneByID       = "MsgFocusPaneByID"
	TagResizePane          = "MsgResizePane"
	TagResizePaneHeight    = "MsgResizePaneHeight"
	TagSubmitInput         = "MsgSubmitInput"
	TagWebPromptDispatched = "MsgWebPromptDispatched"
	TagWebActivate         = "MsgWebActivate"
	TagWebDeactivate       = "MsgWebDeactivate"
	TagRenamePane          = "MsgRenamePane"
	TagRenameTab           = "MsgRenameTab"
	TagRenameLane          = "MsgRenameLane"
	TagShutdown            = "MsgShutdown"
	TagSwitchWorkspace     = "MsgSwitchWorkspace"
	TagReconcileWorkspaces = "MsgReconcileWorkspaces"

	// MCP restart-state (follow-up 6b). Published session-globally to
	// T("mcp","status"); rendered by the TUI footer.
	TagMCPStatus = "MsgMCPStatus"

	// Prompt auto-reload trigger (follow-up 2b). Published to ws.inbox by
	// the fsnotify watcher; the active WorkspaceActor runs the same reload +
	// broadcast path as ##agent reload-prompts.
	TagReloadPromptsRequest = "MsgReloadPromptsRequest"

	// Broadcast down Workspace → Tab → Lane → PaneGroup to update pane cwd.
	TagSetWorkingDir = "MsgSetWorkingDir"

	// WorkspaceActor → TabActor
	TagTabCreatePane       = "MsgTabCreatePane"
	TagTabCreatePaneDown   = "MsgTabCreatePaneDown"
	TagTabClosePane        = "MsgTabClosePane"
	TagTabFocus            = "MsgTabFocus"
	TagTabFocusPaneByID    = "MsgTabFocusPaneByID"
	TagTabSetPaneHidden    = "MsgTabSetPaneHidden"
	TagTabResizePane       = "MsgTabResizePane"
	TagTabResizePaneHeight = "MsgTabResizePaneHeight"
	TagTabSubmitInput      = "MsgTabSubmitInput"
	TagTabSetActive        = "MsgTabSetActive"
	TagTabSetInactive      = "MsgTabSetInactive"

	// TabActor → LaneActor
	TagLaneCreatePaneGroup   = "MsgLaneCreatePaneGroup"
	TagLaneClosePaneGroup    = "MsgLaneClosePaneGroup"
	TagLaneCloseActivePane   = "MsgLaneCloseActivePane"
	TagLaneFocusGroup        = "MsgLaneFocusGroup"
	TagLaneFocusPaneByID     = "MsgLaneFocusPaneByID"
	TagLaneSetPaneHidden     = "MsgLaneSetPaneHidden"
	TagLaneCreateStackedPane = "MsgLaneCreateStackedPane"
	TagLaneStackedPane       = "MsgLaneStackedPane"
	TagLaneStackedPaneSelect = "MsgLaneStackedPaneSelect"
	TagLaneStackedPaneMove   = "MsgLaneStackedPaneMove"
	TagLaneResizeGroupHeight = "MsgLaneResizeGroupHeight"
	TagGetLaneSnapshot       = "MsgGetLaneSnapshot"
	TagLaneSnapshotReply     = "MsgLaneSnapshotReply"
	TagGetLaneActivePane     = "MsgGetLaneActivePane"
	TagLaneActivePaneReply   = "MsgLaneActivePaneReply"
	// LaneActor → PaneGroupActor
	TagGetPaneGroupSnapshot     = "MsgGetPaneGroupSnapshot"
	TagPaneGroupSnapshotReply   = "MsgPaneGroupSnapshotReply"
	TagGetPaneGroupActivePane   = "MsgGetPaneGroupActivePane"
	TagPaneGroupActivePaneReply = "MsgPaneGroupActivePaneReply"

	// Stacked panes: TUI → Workspace
	TagCreateStackedPane = "MsgCreateStackedPane"
	TagStackedPaneRotate = "MsgStackedPaneRotate"
	TagStackedPaneSelect = "MsgStackedPaneSelect"
	TagStackedPaneMove   = "MsgStackedPaneMove"

	// Stacked panes: Workspace → Tab
	TagTabCreateStackedPane = "MsgTabCreateStackedPane"
	TagTabStackedPane       = "MsgTabStackedPane"
	TagTabStackedPaneSelect = "MsgTabStackedPaneSelect"
	TagTabStackedPaneMove   = "MsgTabStackedPaneMove"

	// Stacked panes: Tab/Lane → PaneGroup
	TagPaneGroupCreateStackedPane = "MsgPaneGroupCreateStackedPane"
	TagPaneGroupStackedPane       = "MsgPaneGroupStackedPane"
	TagPaneGroupStackedPaneSelect = "MsgPaneGroupStackedPaneSelect"
	TagPaneGroupFocusPaneByID     = "MsgPaneGroupFocusPaneByID"
	TagPaneGroupSetPaneHidden     = "MsgPaneGroupSetPaneHidden"
	TagPaneGroupStackedPaneMove   = "MsgPaneGroupStackedPaneMove"

	// Given-name messages (Workspace → Pane, direct)
	TagPaneSetGivenName = "MsgPaneSetGivenName"

	// Off-screen toggle for a live pane (Workspace → Pane, direct).
	TagPaneSetHidden = "MsgPaneSetHidden"

	// Per-pane metadata for whoever is driving the pane (Workspace → Pane,
	// direct), and the foreground-program announcement (Pane → anyone).
	TagPaneSetMeta        = "MsgPaneSetMeta"
	TagLaunchClaudeInPane = "MsgLaunchClaudeInPane"
	TagPaneProcess        = "MsgPaneProcess"

	// Native agent panes — `##claude` / `##codex` (design 029).
	TagDiscoverCodexSession = "MsgDiscoverCodexSession"
	TagResumeNativeAgents   = "MsgResumeNativeAgents"

	// Per-pane provider override (Workspace → Pane, direct; design 002 §3.4)
	TagPaneSetProvider = "MsgPaneSetProvider"

	// Interactive `##llm select` picker (Workspace → front-end push)
	TagLLMPickerOpen = "MsgLLMPickerOpen"

	// Per-pane mode enable/disable (Workspace → Pane, direct)
	TagPaneEnableMode    = "MsgPaneEnableMode"
	TagPaneDisableMode   = "MsgPaneDisableMode"
	TagPaneActivateMode  = "MsgPaneActivateMode"
	TagPaneWebHeadless   = "MsgPaneWebHeadless"
	TagPaneImportCookies = "MsgPaneImportCookies"

	// PaneGroupActor → PaneActor
	TagPaneSubmitInput = "MsgPaneSubmitInput"
	TagPaneExecShell   = "MsgPaneExecShell"
	TagPaneExecPrompt  = "MsgPaneExecPrompt"
	TagPaneExecRysh    = "MsgPaneExecRysh"
	TagPaneExecChat    = "MsgPaneExecChat"
	TagPaneSetTitle    = "MsgPaneSetTitle"
	TagPaneStop        = "MsgPaneStop"

	// PaneActor → LLMActor
	TagExecPrompt   = "MsgExecPrompt"
	TagCancelPrompt = "MsgCancelPrompt"

	// Snapshot request/reply
	TagGetWorkspaceSnapshot   = "MsgGetWorkspaceSnapshot"
	TagWorkspaceSnapshotReply = "MsgWorkspaceSnapshotReply"
	TagGetTabSnapshot         = "MsgGetTabSnapshot"
	TagTabSnapshotReply       = "MsgTabSnapshotReply"
	TagGetPaneSnapshot        = "MsgGetPaneSnapshot"
	TagPaneSnapshotReply      = "MsgPaneSnapshotReply"
	TagGetPaneVT              = "MsgGetPaneVT"
	TagPaneVTReply            = "MsgPaneVTReply"
	TagGetPaneScrollback      = "MsgGetPaneScrollback"
	TagPaneScrollbackReply    = "MsgPaneScrollbackReply"

	TagGetPaneScrollbackDelta   = "MsgGetPaneScrollbackDelta"
	TagPaneScrollbackDeltaReply = "MsgPaneScrollbackDeltaReply"
	TagGetMirrorScrollback      = "MsgGetMirrorScrollback"
	TagMirrorScrollbackReply    = "MsgMirrorScrollbackReply"
	TagGetMirrorPaneVT          = "MsgGetMirrorPaneVT"
	TagMirrorPaneVTReply        = "MsgMirrorPaneVTReply"
	TagMirrorPaneVTFrame        = "MsgMirrorPaneVTFrame"

	// Supervision notifications
	TagPaneTerminated     = "MsgPaneTerminated"
	TagPaneKillForeground = "MsgPaneKillForeground"
	TagTabTerminated      = "MsgTabTerminated"

	// TabActor active-pane query
	TagGetActivePane   = "MsgGetActivePane"
	TagActivePaneReply = "MsgActivePaneReply"

	// Pane listener (cross-pane output listening)
	TagStartPaneListener = "MsgStartPaneListener"
	TagStopPaneListener  = "MsgStopPaneListener"

	// Hop commands
	TagPaneHopContent = "MsgPaneHopContent"
	TagPaneHopResume  = "MsgPaneHopResume"
	TagPaneHopClear   = "MsgPaneHopClear"

	// Pipeline mode
	TagTogglePipelineMode = "MsgTogglePipelineMode"
	TagPipelineCommand    = "MsgPipelineCommand"
	TagTabPipelineEnable  = "MsgTabPipelineEnable"
	TagTabPipelineDisable = "MsgTabPipelineDisable"

	// Layout management: TUI → Workspace
	TagEqualizeHorizontal = "MsgEqualizeHorizontal"
	TagEqualizeVertical   = "MsgEqualizeVertical"
	TagEqualizeAll        = "MsgEqualizeAll"
	TagEqualizePanes      = "MsgEqualizePanes"
	TagResizePaneWidth    = "MsgResizePaneWidth"
	TagSwapPane           = "MsgSwapPane"

	// Layout management: Workspace → Tab
	TagTabEqualizeHorizontal = "MsgTabEqualizeHorizontal"
	TagTabEqualizeVertical   = "MsgTabEqualizeVertical"
	TagTabEqualizeAll        = "MsgTabEqualizeAll"
	TagTabEqualizePanes      = "MsgTabEqualizePanes"
	TagTabResizePaneWidth    = "MsgTabResizePaneWidth"
	TagTabSwapPane           = "MsgTabSwapPane"

	// Layout management: Tab → Lane
	TagLaneEqualizeGroups = "MsgLaneEqualizeGroups"

	// CLI messages
	TagCLICreateTab                 = "MsgCLICreateTab"
	TagCLIDeleteTab                 = "MsgCLIDeleteTab"
	TagCLICreateLane                = "MsgCLICreateLane"
	TagCLIDeleteLane                = "MsgCLIDeleteLane"
	TagCLICreatePaneGroup           = "MsgCLICreatePaneGroup"
	TagCLIDeletePaneGroup           = "MsgCLIDeletePaneGroup"
	TagCLICreatePane                = "MsgCLICreatePane"
	TagCLIDeletePane                = "MsgCLIDeletePane"
	TagCLICreateStackedPane         = "MsgCLICreateStackedPane"
	TagCLIPipelineEnable            = "MsgCLIPipelineEnable"
	TagCLIPipelineDisable           = "MsgCLIPipelineDisable"
	TagCLIResponse                  = "MsgCLIResponse"
	TagCLIRyshCommand               = "MsgCLIRyshCommand"
	TagTabDeleteLane                = "MsgTabDeleteLane"
	TagTabCreatePaneGroupInLane     = "MsgTabCreatePaneGroupInLane"
	TagTabCreateGrid                = "MsgTabCreateGrid"
	TagTabCreateGroupsInLane        = "MsgTabCreateGroupsInLane"
	TagTabCreateStackedPaneInLane   = "MsgTabCreateStackedPaneInLane"
	TagLaneDeletePaneGroup          = "MsgLaneDeletePaneGroup"
	TagLaneCreateStackedPaneInGroup = "MsgLaneCreateStackedPaneInGroup"
	TagPaneGroupDeletePane          = "MsgPaneGroupDeletePane"

	// Remote upstream / pane sharing
	TagPaneShareStart           = "MsgPaneShareStart"
	TagPaneShareStop            = "MsgPaneShareStop"
	TagPaneShareStatus          = "MsgPaneShareStatus"
	TagPaneShareStatusReply     = "MsgPaneShareStatusReply"
	TagPaneSetSharingState      = "MsgPaneSetSharingState"
	TagRemoteUpstreamConnect    = "MsgRemoteUpstreamConnect"
	TagRemoteUpstreamDisconnect = "MsgRemoteUpstreamDisconnect"
	TagRemoteUpstreamStatus     = "MsgRemoteUpstreamStatus"

	// Shared-panes-via-upstream
	TagShareEntity               = "MsgShareEntity"
	TagShareForgedAPI            = "MsgShareForgedAPI"
	TagPaneRegisterForgedProxies = "MsgPaneRegisterForgedProxies"
	TagUnshareEntity             = "MsgUnshareEntity"
	TagShareStatus               = "MsgShareStatus"
	TagShareStatusReply          = "MsgShareStatusReply"
	TagShareList                 = "MsgShareList"
	TagShareListReply            = "MsgShareListReply"
	TagUpstreamCommand           = "MsgUpstreamCommand"
	TagUpstreamCommandAck        = "MsgUpstreamCommandAck"
	TagShareRegisterAck          = "MsgShareRegisterAck"
	TagUpstreamSharesList        = "MsgUpstreamSharesList"
	TagUpstreamSubscribe         = "MsgUpstreamSubscribe"
	TagUpstreamUnsubscribe       = "MsgUpstreamUnsubscribe"
	TagUpstreamSendCommand       = "MsgUpstreamSendCommand"
	TagShareOutput               = "MsgShareOutput"

	// Controller mode (remote upstream)
	TagSetControllerMode    = "MsgSetControllerMode"
	TagSetConnectedPane     = "MsgSetConnectedPane"
	TagRemoteForwardCommand = "MsgRemoteForwardCommand"
	TagExecRyshOnPane       = "MsgExecRyshOnPane"
	TagMirrorTabOp          = "MsgMirrorTabOp"
	TagMirrorMaximizePane   = "MsgMirrorMaximizePane"
	TagRemotePaneFullscreen = "MsgRemotePaneFullscreen"

	// Share restrictions
	TagShareDisableMode         = "MsgShareDisableMode"
	TagShareEnableMode          = "MsgShareEnableMode"
	TagShareShellAllow          = "MsgShareShellAllow"
	TagShareShellForbid         = "MsgShareShellForbid"
	TagShareShellClear          = "MsgShareShellClear"
	TagShareSetFileBrowse       = "MsgShareSetFileBrowse"
	TagShareShowRestrictions    = "MsgShareShowRestrictions"
	TagShareRestrictionsUpdated = "MsgShareRestrictionsUpdated"
	TagPaneSetShareRestrictions = "MsgPaneSetShareRestrictions"

	// WebSocket connection reliability
	TagUpstreamReconnected      = "MsgUpstreamReconnected"
	TagUpstreamConnectionClosed = "MsgUpstreamConnectionClosed"

	// Autonomous agents
	TagAgentCreate         = "MsgAgentCreate"
	TagAgentDelete         = "MsgAgentDelete"
	TagAgentStop           = "MsgAgentStop"
	TagAgentContinue       = "MsgAgentContinue"
	TagAgentActivate       = "MsgAgentActivate"
	TagAgentDeactivate     = "MsgAgentDeactivate"
	TagAgentList           = "MsgAgentList"
	TagAgentListReply      = "MsgAgentListReply"
	TagAgentPrompt         = "MsgAgentPrompt"
	TagAgentRegisterPane   = "MsgAgentRegisterPane"
	TagAgentUnregisterPane = "MsgAgentUnregisterPane"

	// Humanoids (agents with external communication channels)
	TagHumanoidCreate            = "MsgHumanoidCreate"
	TagHumanoidDelete            = "MsgHumanoidDelete"
	TagHumanoidStop              = "MsgHumanoidStop"
	TagHumanoidContinue          = "MsgHumanoidContinue"
	TagHumanoidActivate          = "MsgHumanoidActivate"
	TagHumanoidDeactivate        = "MsgHumanoidDeactivate"
	TagHumanoidList              = "MsgHumanoidList"
	TagHumanoidListReply         = "MsgHumanoidListReply"
	TagHumanoidPrompt            = "MsgHumanoidPrompt"
	TagHumanoidRegisterPane      = "MsgHumanoidRegisterPane"
	TagHumanoidUnregisterPane    = "MsgHumanoidUnregisterPane"
	TagHumanoidChannelStart      = "MsgHumanoidChannelStart"
	TagHumanoidChannelStop       = "MsgHumanoidChannelStop"
	TagHumanoidChannelStatus     = "MsgHumanoidChannelStatus"
	TagHumanoidSetReplyMode      = "MsgHumanoidSetReplyMode"
	TagHumanoidSetGovernance     = "MsgHumanoidSetGovernance"
	TagHumanoidGovernanceChanged = "MsgHumanoidGovernanceChanged"
	TagHumanoidReplyModeChanged  = "MsgHumanoidReplyModeChanged"
	TagHumanoidSetProvider       = "MsgHumanoidSetProvider"
	TagHumanoidInboundMessage    = "MsgHumanoidInboundMessage"
	TagHumanoidOutboundMessage   = "MsgHumanoidOutboundMessage"
	TagHumanoidEmailList         = "MsgHumanoidEmailList"
	TagHumanoidEmailRead         = "MsgHumanoidEmailRead"
	TagHumanoidEmailListReply    = "MsgHumanoidEmailListReply"
	TagHumanoidEmailReadReply    = "MsgHumanoidEmailReadReply"
	TagHumanoidEmailChanged      = "MsgHumanoidEmailChanged"
	TagHumanoidSetFocus          = "MsgHumanoidSetFocus"
	TagHumanoidEmailCompose      = "MsgHumanoidEmailCompose"
	TagHumanoidEmailComposeReply = "MsgHumanoidEmailComposeReply"

	TagHumanoidWhatsAppList      = "MsgHumanoidWhatsAppList"
	TagHumanoidWhatsAppRead      = "MsgHumanoidWhatsAppRead"
	TagHumanoidWhatsAppListReply = "MsgHumanoidWhatsAppListReply"
	TagHumanoidWhatsAppReadReply = "MsgHumanoidWhatsAppReadReply"
	TagHumanoidWhatsAppChanged   = "MsgHumanoidWhatsAppChanged"

	// Pane ← Humanoid
	TagPaneSetHumanoid = "MsgPaneSetHumanoid"

	// Contact pairing & allowlists (WS3, design 003) — humanoid.{name}.pairing
	TagChannelPairRequest   = "MsgChannelPairRequest"
	TagChannelPairApprove   = "MsgChannelPairApprove"
	TagChannelAllow         = "MsgChannelAllow"
	TagChannelPairList      = "MsgChannelPairList"
	TagChannelPairListReply = "MsgChannelPairListReply"
	TagChannelPairQR        = "MsgChannelPairQR"
	TagChannelPairStatus    = "MsgChannelPairStatus"
	TagChannelPairLink      = "MsgChannelPairLink"

	// Attention mechanism
	TagAttentionEvent   = "MsgAttentionEvent"
	TagAttentionAck     = "MsgAttentionAck"
	TagAttentionEnable  = "MsgAttentionEnable"
	TagAttentionDisable = "MsgAttentionDisable"

	// Raw/interactive terminal mode
	TagPaneResize      = "MsgPaneResize"
	TagPaneReleaseSize = "MsgPaneReleaseSize"
	TagPaneResized     = "MsgPaneResized"
	TagRawKeyInput     = "MsgRawKeyInput"
	TagPaneClearOutput = "MsgPaneClearOutput"
	TagPaneNativeMode  = "MsgPaneNativeMode"
	TagRelayActivate   = "MsgRelayActivate"
	TagRelayDeactivate = "MsgRelayDeactivate"

	// Interactive sharing
	TagPaneRawOutputAppend         = "MsgPaneRawOutputAppend"
	TagPaneShareModeChange         = "MsgPaneShareModeChange"
	TagPaneRawDirty                = "MsgPaneRawDirty"
	TagBoardPost                   = "MsgBoardPost"
	TagBoardRegister               = "MsgBoardRegister"
	TagCLIBoardPost                = "MsgCLIBoardPost"
	TagBoardAgentPrompt            = "MsgBoardAgentPrompt"
	TagAnsaRoute                   = "MsgAnsaRoute"
	TagAnsaRouteResult             = "MsgAnsaRouteResult"
	TagCLIAnsaSend                 = "MsgCLIAnsaSend"
	TagPaneReplayShareState        = "MsgPaneReplayShareState"
	TagRemoteInteractiveModeChange = "MsgRemoteInteractiveModeChange"
	TagPaneSetRemoteSubscriber     = "MsgPaneSetRemoteSubscriber"
	TagRemoteVTScreenUpdate        = "MsgRemoteVTScreenUpdate"
	TagRemoteScrollbackAppend      = "MsgRemoteScrollbackAppend"
	TagMirrorDirty                 = "MsgMirrorDirty"
	TagLayoutDirty                 = "MsgLayoutDirty"

	// Session replay v2 (design 006) — dedicated replay pane
	TagReplayControl = "MsgReplayControl"
	TagPaneStopped   = "MsgPaneStopped"
)
View Source
const (
	AnsaModeShell  = "shell"
	AnsaModePrompt = "prompt"
)

Delivery modes. These are the pane inbox's own vocabulary, not ANSA's invention — see MsgPaneExecShell / MsgPaneExecPrompt.

View Source
const (
	// AnsaErrNoTarget — the caller named nobody.
	AnsaErrNoTarget = "no_target"
	// AnsaErrNoText — the caller sent nothing to deliver.
	AnsaErrNoText = "no_text"
	// AnsaErrUnknownTarget — the name/id matched no pane in the session.
	AnsaErrUnknownTarget = "unknown_target"
	// AnsaErrAmbiguousTarget — the name matched MORE THAN ONE pane. ANSA
	// refuses rather than picking one. See the note on given-names below.
	AnsaErrAmbiguousTarget = "ambiguous_target"
	// AnsaErrUnreachable — the target pane exists in the layout but did not
	// answer a liveness probe, so publishing to its inbox would be a write into
	// a subject nobody is reading.
	AnsaErrUnreachable = "unreachable"
	// AnsaErrBadMode — an unrecognised delivery mode. NOT defaulted silently:
	// a typo'd mode that quietly became a shell command is a message delivered
	// as something other than what the sender meant.
	AnsaErrBadMode = "bad_mode"
	// AnsaErrPublishFailed — the transport itself refused.
	AnsaErrPublishFailed = "publish_failed"
	// AnsaErrNotAnID — the router was handed a NAME where an id belongs. The
	// caller skipped edge resolution. Its own code because it is a caller bug
	// with a specific fix ("resolve the @name first"), not a missing pane.
	AnsaErrNotAnID = "not_an_id"
	// AnsaErrDirectory — ANSA could not enumerate the session's panes, so it
	// cannot know whether the target exists. Distinct from unknown_target on
	// purpose: "I looked and it is not there" and "I could not look" are
	// different facts, and only one of them means the caller should give up.
	AnsaErrDirectory = "directory_unavailable"
)

Refusal codes. Every one of these is a message that did NOT get delivered and a caller that WAS told so.

They are exhaustive by construction: the router's only success path returns OK, and every other branch sets one of these. A new failure mode must add a code here, which is a visible edit rather than a silent `return nil`.

View Source
const (
	BoardKindMilestone = "milestone"
	BoardKindTaskDone  = "task-done"
	BoardKindBlocked   = "blocked"
	BoardKindReply     = "reply"
)

Board message kinds. Kind is deliberately a free-form string rather than an enum: fleetctl's --kind is already free-form (it carries WORK ORDER, REPORT, PROGRESS, BLOCKED, NUDGE and whatever an agent invents next), and the board must render an unknown kind rather than drop the message. These are the well-known values, not the permitted set.

View Source
const (
	TagProxyRequestAudit         = sharedmsg.TagProxyRequestAudit
	TagProxyAuditSnapshotRequest = sharedmsg.TagProxyAuditSnapshotRequest
	TagProxyAuditSnapshotReply   = sharedmsg.TagProxyAuditSnapshotReply

	ProxyBudgetOK          = sharedmsg.ProxyBudgetOK
	ProxyBudgetExceeded    = sharedmsg.ProxyBudgetExceeded
	ProxyBudgetRateLimited = sharedmsg.ProxyBudgetRateLimited
	ProxyBlocked           = sharedmsg.ProxyBlocked
)
View Source
const (
	ConvShell   = sharedmsg.ConvShell
	ConvAI      = sharedmsg.ConvAI
	ConvRysh    = sharedmsg.ConvRysh
	ConvChat    = sharedmsg.ConvChat
	ConvEmail   = sharedmsg.ConvEmail
	ConvSlack   = sharedmsg.ConvSlack
	ConvChatbot = sharedmsg.ConvChatbot
)

Conversation type constants

View Source
const (
	TurnQuestion = sharedmsg.TurnQuestion
	TurnAnswer   = sharedmsg.TurnAnswer
)

Turn type constants

View Source
const (
	InputShell    = sharedmsg.InputShell
	InputPrompt   = sharedmsg.InputPrompt
	InputCommand  = sharedmsg.InputCommand
	InputApproval = sharedmsg.InputApproval
	InputMessage  = sharedmsg.InputMessage
)

Input type constants

View Source
const (
	SourceHuman    = sharedmsg.SourceHuman
	SourceAI       = sharedmsg.SourceAI
	SourceExternal = sharedmsg.SourceExternal
	SourceAgent    = sharedmsg.SourceAgent
	SourceSubagent = sharedmsg.SourceSubagent
	SourceHumanoid = sharedmsg.SourceHumanoid
	SourceSystem   = sharedmsg.SourceSystem
)

Message source constants

View Source
const (
	// Output tags
	TagPaneOutputAppend         = sharedmsg.TagPaneOutputAppend
	TagPaneShellOutputAppend    = sharedmsg.TagPaneShellOutputAppend
	TagPaneAIOutputAppend       = sharedmsg.TagPaneAIOutputAppend
	TagPaneChatOutputAppend     = sharedmsg.TagPaneChatOutputAppend
	TagPaneRyshOutputAppend     = sharedmsg.TagPaneRyshOutputAppend
	TagPaneExternalOutputAppend = sharedmsg.TagPaneExternalOutputAppend
	TagPaneStatusUpdate         = sharedmsg.TagPaneStatusUpdate
	TagPipelineOutputAppend     = sharedmsg.TagPipelineOutputAppend

	// History tags
	TagPaneHistoryAppend         = sharedmsg.TagPaneHistoryAppend
	TagPaneShellHistoryAppend    = sharedmsg.TagPaneShellHistoryAppend
	TagPaneAIHistoryAppend       = sharedmsg.TagPaneAIHistoryAppend
	TagPaneChatHistoryAppend     = sharedmsg.TagPaneChatHistoryAppend
	TagPaneRyshHistoryAppend     = sharedmsg.TagPaneRyshHistoryAppend
	TagPaneExternalHistoryAppend = sharedmsg.TagPaneExternalHistoryAppend

	// Unified conversation tags (new)
	TagConversationAppend        = sharedmsg.TagConversationAppend
	TagConversationHistoryAppend = sharedmsg.TagConversationHistoryAppend

	// Agentic tags
	TagAgenticPrompt             = sharedmsg.TagAgenticPrompt
	TagAgenticCancel             = sharedmsg.TagAgenticCancel
	TagOrchestratorDone          = sharedmsg.TagOrchestratorDone
	TagToolCall                  = sharedmsg.TagToolCall
	TagToolResult                = sharedmsg.TagToolResult
	TagAgenticOutput             = sharedmsg.TagAgenticOutput
	TagAgenticStatus             = sharedmsg.TagAgenticStatus
	TagApprovalRequest           = sharedmsg.TagApprovalRequest
	TagApprovalResponse          = sharedmsg.TagApprovalResponse
	TagSpawnSubOrchestrator      = sharedmsg.TagSpawnSubOrchestrator
	TagSubOrchestratorResult     = sharedmsg.TagSubOrchestratorResult
	TagGetConversationHistory    = sharedmsg.TagGetConversationHistory
	TagConversationHistoryReply  = sharedmsg.TagConversationHistoryReply
	TagRestoreConversation       = sharedmsg.TagRestoreConversation
	TagSetChatOutputPane         = sharedmsg.TagSetChatOutputPane
	TagCreateApprovalPane        = sharedmsg.TagCreateApprovalPane
	TagDestroyApprovalPane       = sharedmsg.TagDestroyApprovalPane
	TagPaneSetApprovalPaneGroups = sharedmsg.TagPaneSetApprovalPaneGroups
	TagMemoryStateUpdate         = sharedmsg.TagMemoryStateUpdate

	// Memory tags
	TagMemoryAppend        = sharedmsg.TagMemoryAppend
	TagMemoryGet           = sharedmsg.TagMemoryGet
	TagMemoryGetReply      = sharedmsg.TagMemoryGetReply
	TagMemorySummarize     = sharedmsg.TagMemorySummarize
	TagMemorySummarizeDone = sharedmsg.TagMemorySummarizeDone

	// Approval decision constants
	ApprovalTypeDiff        = sharedmsg.ApprovalTypeDiff
	ApprovalTypeDestructive = sharedmsg.ApprovalTypeDestructive
	ApprovalTypeChoice      = sharedmsg.ApprovalTypeChoice
	ApprovalTypeQuestion    = sharedmsg.ApprovalTypeQuestion

	DecisionYes               = sharedmsg.DecisionYes
	DecisionYesAlways         = sharedmsg.DecisionYesAlways
	DecisionNo                = sharedmsg.DecisionNo
	DecisionNoWithExplanation = sharedmsg.DecisionNoWithExplanation
	DecisionChoiceSelected    = sharedmsg.DecisionChoiceSelected
)
View Source
const (
	TagUsageRecord          = sharedmsg.TagUsageRecord
	TagUsageCheck           = sharedmsg.TagUsageCheck
	TagUsageCheckReply      = sharedmsg.TagUsageCheckReply
	TagUsageSnapshotRequest = sharedmsg.TagUsageSnapshotRequest
	TagUsageSnapshotReply   = sharedmsg.TagUsageSnapshotReply
	TagUsageBudgetSet       = sharedmsg.TagUsageBudgetSet

	UsageSourceAgent = sharedmsg.UsageSourceAgent
	UsageSourceProxy = sharedmsg.UsageSourceProxy
	UsageSourceEval  = sharedmsg.UsageSourceEval
	UsageSourceCI    = sharedmsg.UsageSourceCI

	UsageScopePane   = sharedmsg.UsageScopePane
	UsageScopeTenant = sharedmsg.UsageScopeTenant
)
View Source
const AnsaSchemaVersion = 1

AnsaSchemaVersion is stamped on every route request, from the first release, for the same reason the board's is: a router that cannot tell v1 from v2 cannot be changed later without breaking every live agent.

View Source
const BoardAliveReply = "recording"

BoardAliveReply is what a live recorder answers. The content is deliberately trivial: the fact that a reply arrived at all is the signal.

View Source
const BoardSchemaVersion = 1

BoardSchemaVersion is stamped on every board message from the first release. A board that cannot tell v1 from v2 cannot be changed later without breaking every live agent, so this ships before there is anything to be compatible with.

View Source
const DefaultBoardID = "session"

DefaultBoardID names the session board: the one every claude can post to and the one a pane with no board of its own belongs to.

It is a NAME, not a subject token. IsDefaultBoard is what decides the wire shape, and for this id the wire shape is the legacy three-token subject.

View Source
const MaxBoardIDLen = 32

MaxBoardIDLen bounds a board id. A board id becomes a SUBJECT TOKEN and, through internal/board, a KV key prefix; both are cheaper to keep short than to explain later.

Variables

View Source
var (
	ProxyAuditSubject         = sharedmsg.ProxyAuditSubject
	ProxyAuditWildcardSubject = sharedmsg.ProxyAuditWildcardSubject
	ProxyAuditInboxSubject    = sharedmsg.ProxyAuditInboxSubject
)
View Source
var (
	NewTurnID         = sharedmsg.NewTurnID
	NowMs             = sharedmsg.NowMs
	NewShellQuestion  = sharedmsg.NewShellQuestion
	NewShellAnswer    = sharedmsg.NewShellAnswer
	NewAIQuestion     = sharedmsg.NewAIQuestion
	NewAIAnswer       = sharedmsg.NewAIAnswer
	NewRyshQuestion   = sharedmsg.NewRyshQuestion
	NewRyshAnswer     = sharedmsg.NewRyshAnswer
	NewChatMessage    = sharedmsg.NewChatMessage
	NewChannelMessage = sharedmsg.NewChannelMessage
)

Constructor aliases

View Source
var (
	NewMemoryEntry            = sharedmsg.NewMemoryEntry
	FormatMemoryForPrompt     = sharedmsg.FormatMemoryForPrompt
	MemorySummarizationPrompt = sharedmsg.MemorySummarizationPrompt
)

Memory constructor and helper aliases

View Source
var (
	UsageSubject         = sharedmsg.UsageSubject
	UsageWildcardSubject = sharedmsg.UsageWildcardSubject
	UsageCheckSubject    = sharedmsg.UsageCheckSubject
	UsageInboxSubject    = sharedmsg.UsageInboxSubject
)
View Source
var InitMsgLog = sharedmsg.InitMsgLog
View Source
var NewCodecRegistry = sharedmsg.NewCodecRegistry
View Source
var NewNATSPublisher = sharedmsg.NewNATSPublisher
View Source
var SessionPrefix = sharedmsg.SessionPrefix

SessionPrefix returns the current NATS subject prefix.

View Source
var SetSessionPrefix = sharedmsg.SetSessionPrefix

SetSessionPrefix sets the NATS subject prefix for this session. All subjects constructed by T() and by rysh-shared publishers will use this prefix.

T constructs a NATS subject by joining the session prefix with the given parts. Delegates to rysh-shared/msg so that the session prefix is consistent across all publishers (CLI actors and shared agentic actors).

Functions

func AnsaInboxSubject added in v0.2.4

func AnsaInboxSubject() string

AnsaInboxSubject is where the router listens. Session-scoped like every other subject: msg.T renders the session prefix, so this is never a literal.

func BoardAlivePattern added in v0.2.6

func BoardAlivePattern() string

BoardAlivePattern is the wildcard a recorder subscribes to in order to answer for every named board. Like the post pattern, it does not match the default board's shorter subject.

func BoardAliveSubject added in v0.2.4

func BoardAliveSubject(board string) string

BoardAliveSubject is where the recorder answers "are you recording?".

A REQUEST, not a heartbeat. Every failure on this track came from inferring state from a proxy — a pane's idle flag, send.ok, os.path.isfile, a persisted roster entry, a KV revision. A heartbeat is one more proxy: it reports that something wrote a key recently, which is not the same as a recorder being alive now, and it needs a staleness threshold that can be wrong under load.

Asking the recorder directly has no threshold to tune, and a timeout tests the ACTUAL path rather than an artifact's freshness. It also keeps liveness out of the board's KV bucket, where a second writer to that bucket would falsify the single-writer detector in internal/actors/abla_test.go — that bucket's precondition is "nothing else writes here", and liveness has no business breaking it. Per board since design 028: a caller asks whether the recorder for a NAMED board is listening. One recorder serves every board in a session today (internal/actors/abla.go), so every board's alive subject is answered by the same actor — but the subject is per board so that stays an implementation detail rather than something callers encode.

func BoardIDFromMeta added in v0.2.6

func BoardIDFromMeta(id string) string

BoardIDFromMeta resolves which board a PANE reads and posts to, from the value of its `board.id` meta.

ONE PREDICATE, TWO SURFACES. The TUI renders board panes itself (tui.Model.boardIDForPane) and the web server answers the app's board_get with the same question; before this existed the TUI owned the only copy, and the second surface would have had to restate it. That restatement is the F-18 shape design 025 §8b puts a rule in one predicate to avoid — and here it would be invisible, because both copies return a VALID board id. They would simply return different ones, and the app would confidently render a board the TUI does not show for the same pane.

An empty or INVALID id is the session board rather than an error, matching boardSubject's fallback: a pane whose meta was hand-edited to something unusable still belongs somewhere, and the session board is where an unattributed pane belongs. Callers that took an id from a human — the CLI's --board, ##board open — must keep REFUSING instead; this is the pane-meta edge, where there is nobody to tell.

func BoardIDFromSubject added in v0.2.6

func BoardIDFromSubject(subject, leaf string) (string, bool)

BoardIDFromSubject extracts the board a delivered message was addressed to.

It parses from the END, never by counting from the front: T's prefix is the session name (rysh-shared/msg/topics.go), and a session name is free-form enough to contain a dot. Counting tokens from the left would then read part of the session name as a board id — the sort of failure that shows up as an empty board rather than as an error.

<session>.board.post          → ("session", true)
<session>.board.epic-07.post  → ("epic-07", true)
<session>.pane.x.output       → ("", false)

func BoardPersona added in v0.2.4

func BoardPersona(givenName, title, paneID string) string

BoardPersona resolves a display name for a pane, in the order the board renders it: given-name, then the auto-generated title, then "pane-<8>".

It never returns an empty string and never returns the literal "no-name" that ##pane info prints for an unnamed pane — an unnamed agent still needs a stable label to be followed by.

The approval-pane guard is not hypothetical: approval panes OVERLOAD GivenName to carry "requestID\x1FresponseSubject" for the TUI (internal/actors/approval_pane.go). Rendering that blindly would one day print a NATS subject as somebody's name, so a name carrying the unit separator is rejected and falls through to the next candidate.

func BoardPostPattern added in v0.2.6

func BoardPostPattern() string

BoardPostPattern / BoardRegisterPattern are the wildcard subjects a subscriber uses to hear EVERY named board at once.

They deliberately do NOT match the default board: its subject has one token fewer, so a subscriber that wants everything takes the pattern AND the legacy subject. That is board.Subscribe's job, and it is why these are exported next to the builders rather than assembled at the call site.

func BoardPostSubject added in v0.2.4

func BoardPostSubject(board string) string

BoardPostSubject is the subject agents post to and a board view subscribes to, for ONE board (design 028; board ids in board_id.go).

The empty string and DefaultBoardID both mean the session board, whose subject is unchanged from before board ids existed — see the wire-shape note in board_id.go.

func BoardQueryPattern added in v0.2.6

func BoardQueryPattern() string

BoardQueryPattern is the wildcard the recorder serves every named board on.

func BoardQuerySubject added in v0.2.6

func BoardQuerySubject(board string) string

BoardQuerySubject is where the recorder answers "what is ON the board?".

A SECOND REQUEST SUBJECT IN THE SHAPE OF BoardAliveSubject, deliberately, and served by the same actor for the same two reasons.

  1. ASK THE RECORDER, DO NOT READ THE TRACE IT LEFT (design 026 §5.4). ABLA holds the authoritative in-memory board — restored from the KV at start and fed by the live subscription since. Its answer is what the session actually heard. The KV is a durable copy of that, one restore behind, and reading it would be inferring the board's state from an artifact instead of asking the thing that owns it.

  2. F-23 IS WHAT HAPPENS WHEN A SECOND CALL SITE DERIVES THE BUCKET NAME. runAttachUI built a bus.Config with no SessionName, so an attaching TUI opened rysh-board-default while the daemon wrote rysh-board-<session>. Subjects were fine; only the restore was dead, and it failed LOOKING HEALTHY — an empty board renders identically to a quiet one. A read path that goes through this subject cannot reintroduce that: it has no bucket name to get wrong, and a recorder that does not answer is an ERROR rather than an empty result (board.ErrNoRecorder).

func BoardRegisterPattern added in v0.2.6

func BoardRegisterPattern() string

func BoardRegisterSubject added in v0.2.4

func BoardRegisterSubject(board string) string

BoardRegisterSubject carries persona announcements for one board.

func FleetQuerySubject added in v0.2.6

func FleetQuerySubject() string

FleetQuerySubject is where the fleet actor answers "what fleets exist?".

SAME SHAPE AND SAME REASON AS BoardQuerySubject: a reader ASKS THE ACTOR that owns the registry rather than opening the KV bucket at a second call site. F-23 is what happens when a second call site derives a bucket name — the read failed while LOOKING HEALTHY, because an empty answer and a wrong bucket are indistinguishable.

func FleetUpdateSubject added in v0.2.6

func FleetUpdateSubject() string

FleetUpdateSubject is where the fleet actor accepts registrations, membership changes and state transitions.

func IsDefaultBoard added in v0.2.6

func IsDefaultBoard(id string) bool

IsDefaultBoard reports whether id addresses the session board.

func MintThreadID added in v0.2.4

func MintThreadID(paneID string, n int) string

MintThreadID builds a thread id the poster owns outright: "<pane-uuid>/<n>".

It is derived from the FULL pane uuid, not the persona and not the envelope's 8-char truncation, so two agents can never mint colliding ids even when they share a given-name across lanes. No round trip to the board is involved, which is what lets an agent open a thread while blind to the board — and what makes a retried post idempotent instead of minting a second root.

func NormalizeBoardID added in v0.2.6

func NormalizeBoardID(id string) string

NormalizeBoardID maps the several spellings of "the session board" onto one. An empty id, the default id itself, and any case variant of it are the same board. Everything else is returned lower-cased and otherwise untouched — this does NOT sanitise; ValidateBoardID is what rejects.

func SendBoardPost added in v0.2.4

func SendBoardPost(p *NATSPublisher, board string, post *MsgBoardPost) error

SendBoardPost publishes one board message.

Package-level, not a method, for the reason given at the top of this file: NATSPublisher is a type alias for a rysh-shared type and Go does not allow methods on aliased external types.

The caller stamps V and TS via NewBoardPost rather than having them filled in here, so that a post relayed from elsewhere keeps its original clock. The board is named by the CALLER and is not carried in the post: passing "" posts to the session board, which is what every non-fleet claude does.

func SendBoardRegister added in v0.2.4

func SendBoardRegister(p *NATSPublisher, board string, reg *MsgBoardRegister) error

SendBoardRegister publishes a persona announcement. Advisory: the board renders posts from unregistered panes too.

func SendMirrorPaneVTFrame

func SendMirrorPaneVTFrame(p *NATSPublisher, mirrorID string, f *MsgMirrorPaneVTFrame) error

SendMirrorPaneVTFrame PUSHES a keyframe/delta VT frame directly on the per-pane topic rysh.pane.{mirrorID}.vtframe — no request/reply, no WorkspaceActor mailbox hop. It is the steady-state path for an interactive mirror pane's live screen: the WorkspaceActor marshals once at update time and the TUI subscriber applies it against its last-applied seq (keyframe + delta, sequence-numbered). Fire-and-forget; a dropped frame becomes a seq gap healed by the TUI's resync pull (MsgGetMirrorPaneVT).

func SendPaneRawDirty

func SendPaneRawDirty(p *NATSPublisher, paneID string) error

SendPaneRawDirty publishes a tiny notification that a pane's raw VT screen has changed. The TUI subscribes via wildcard and uses it to drive a push-based, per-change fetch of raw panes instead of the legacy 50ms poll of every visible raw pane. Fire-and-forget; dropped messages are healed by the longer-interval reconcile tick. Best-effort error semantics match the other sharing helpers above.

func SendRawOutput

func SendRawOutput(p *NATSPublisher, paneID string, data []byte) error

SendRawOutput publishes raw PTY bytes (base64-encoded) for interactive sharing. This is a package-level function because NATSPublisher is a type alias from rysh-shared/msg and Go does not allow adding methods to aliased external types.

func SendShareModeChange

func SendShareModeChange(p *NATSPublisher, paneID string, interactive bool, rows, cols int) error

SendShareModeChange publishes an interactive mode transition for sharing.

func ValidateBoardID added in v0.2.6

func ValidateBoardID(id string) error

ValidateBoardID rejects anything that cannot safely be a subject token.

THIS IS A SUBJECT-INJECTION GUARD, not a style preference. A board id ends up between "board" and "post" in a NATS subject, so an id containing "." would silently publish into a subject nobody subscribes to, and an id of "*" or ">" would publish into (or subscribe to) EVERY board. Both failures are silent at the publisher and invisible at the board that should have received the post, which is why this is checked at every edge that accepts an id from a human, a flag, or pane meta.

Types

type AgentInfo

type AgentInfo struct {
	Name            string   `json:"name"`
	Active          bool     `json:"active"`
	SystemPrompt    string   `json:"system_prompt"`
	RegisteredPanes []string `json:"registered_panes,omitempty"`
	// WorktreePath is the agent's isolation worktree (design 008), "" when the
	// agent runs on the shared checkout. Surfaced so the worktree lifecycle can
	// refuse to auto-remove a worktree a live agent still claims.
	WorktreePath string `json:"worktree_path,omitempty"`
}

AgentInfo describes an agent's state.

type ApprovalDecision

type ApprovalDecision = sharedmsg.ApprovalDecision

type ApprovalType

type ApprovalType = sharedmsg.ApprovalType

type AttachmentInfo

type AttachmentInfo struct {
	Filename    string `json:"filename"`
	ContentType string `json:"content_type"`
	Size        int    `json:"size"`
}

AttachmentInfo describes an email attachment without the full data.

type AttentionCategory

type AttentionCategory string

AttentionCategory identifies the source of an attention event.

const (
	AttentionApproval AttentionCategory = "approval"
	AttentionSlack    AttentionCategory = "slack"
	AttentionEmail    AttentionCategory = "email"
	AttentionChatbot  AttentionCategory = "chatbot"
	AttentionWhatsApp AttentionCategory = "whatsapp"
	AttentionPhone    AttentionCategory = "phone"
)

type AttentionPriority

type AttentionPriority int

AttentionPriority defines urgency levels.

const (
	AttentionPriorityLow      AttentionPriority = 0
	AttentionPriorityNormal   AttentionPriority = 1
	AttentionPriorityHigh     AttentionPriority = 2
	AttentionPriorityCritical AttentionPriority = 3
)

type BoardIDError added in v0.2.6

type BoardIDError struct {
	ID     string
	Reason string
}

BoardIDError is a rejected board id, carrying what was wrong so the surface that took the id can say it back to whoever typed it.

func (*BoardIDError) Error added in v0.2.6

func (e *BoardIDError) Error() string

type ChannelConfig

type ChannelConfig struct {
	// Common
	Enabled   bool   `json:"enabled" yaml:"enabled"`
	ReplyMode string `json:"reply_mode,omitempty" yaml:"reply_mode"` // "messages" (default) or "mentions"

	// WhatsApp (Meta Cloud API). Phone is the numeric phone_number_id (NOT the
	// display number); APIKey is the Graph API access token. Inbound arrives via
	// a local webhook server the adapter runs on WebhookPort: VerifyToken answers
	// Meta's GET verification handshake (hub.verify_token) and AppSecret, when
	// set, validates the X-Hub-Signature-256 HMAC on inbound POSTs. GraphVersion
	// overrides the default Graph API version. (AppSecret is also the Teams bot
	// client secret — see the Teams block below.)
	Phone        string `json:"phone,omitempty" yaml:"phone"`
	APIKey       string `json:"api_key,omitempty" yaml:"api_key"`
	BusinessID   string `json:"business_id,omitempty" yaml:"business_id"`
	VerifyToken  string `json:"verify_token,omitempty" yaml:"verify_token"`
	AppSecret    string `json:"app_secret,omitempty" yaml:"app_secret"`
	WebhookPort  string `json:"webhook_port,omitempty" yaml:"webhook_port"` // string so it can hold a ${SECRET}; parsed to int by the adapter
	GraphVersion string `json:"graph_version,omitempty" yaml:"graph_version"`
	// Governance controls inbound handling: "ai" (default, auto-reply) or "human"
	// (inbound is displayed only; the human drives list/read/draft/approve/send via
	// the whatsapp_* tools). Mirrors EmailChannelConfig.Governance.
	Governance string `json:"governance,omitempty" yaml:"governance"`
	// Relay mode. When true the adapter does NOT run a local webhook listener or
	// hold platform credentials; inbound arrives over the upstream rysh-server on
	// ws.{RelayWorkspaceID}.{type}.{RelayConnectionID}.inbound and outbound is
	// published to the sibling .outbound subject, where the server delivers it
	// using the credentials it already stores.
	//
	// This is what lets a humanoid run on a laptop behind NAT: the session dials
	// out to the server rather than needing to be reachable by the platform.
	Relay             bool   `json:"relay,omitempty" yaml:"relay"`
	RelayURL          string `json:"relay_url,omitempty" yaml:"relay_url"`
	RelayAPIKey       string `json:"relay_api_key,omitempty" yaml:"relay_api_key"`
	RelayWorkspace    string `json:"relay_workspace,omitempty" yaml:"relay_workspace"`
	RelayWorkspaceID  string `json:"relay_workspace_id,omitempty" yaml:"relay_workspace_id"`
	RelayConnectionID string `json:"relay_connection_id,omitempty" yaml:"relay_connection_id"`

	// Slack
	BotToken string   `json:"bot_token,omitempty" yaml:"bot_token"`
	AppToken string   `json:"app_token,omitempty" yaml:"app_token"`
	Channels []string `json:"channels,omitempty" yaml:"channels"`

	// Email — type selects the provider (e.g. "gmail"), config holds all
	// connection details (IMAP/SMTP hosts, ports, credentials).
	EmailType   string              `json:"type,omitempty" yaml:"type"`
	EmailConfig *EmailChannelConfig `json:"config,omitempty" yaml:"config"`

	// Phone / SMS (Twilio). Number is the sending number in E.164 ("+15550100")
	// — it is also the Signal account id, so the two channels share the field.
	// AccountSID/AuthToken are the Twilio REST credentials. Inbound arrives via
	// a loopback webhook server on WebhookPort, which the operator exposes with
	// a tunnel; WebhookURL is that public URL and is what makes inbound
	// signature validation possible (Twilio signs the URL it requested, so the
	// adapter cannot reconstruct it from the loopback request alone).
	//
	// SMS/MMS only: voice calls are not handled. A voice webhook reaching this
	// endpoint is logged and rejected rather than answered.
	Number     string `json:"number,omitempty" yaml:"number"`
	Provider   string `json:"provider,omitempty" yaml:"provider"`
	AccountSID string `json:"account_sid,omitempty" yaml:"account_sid"`
	AuthToken  string `json:"auth_token,omitempty" yaml:"auth_token"`

	// Microsoft Teams (Azure Bot Service / Bot Framework). AppID is the bot's
	// Microsoft App (client) ID and AppSecret its client secret — the same
	// AppSecret field WhatsApp uses for HMAC validation, since a channel only
	// ever means one of the two. TenantID selects the Entra tenant for a
	// single-tenant bot; empty uses the multi-tenant "botframework.com"
	// authority. Inbound arrives via a loopback webhook on WebhookPort (the
	// Azure Bot's Messaging Endpoint points at the operator's tunnel), and
	// every inbound activity's Bot Framework JWT is verified — there is no
	// switch to turn that off.
	AppID    string `json:"app_id,omitempty" yaml:"app_id"`
	TenantID string `json:"tenant_id,omitempty" yaml:"tenant_id"`

	// Chatbot (local HTTP server mode)
	WebhookURL  string   `json:"webhook_url,omitempty" yaml:"webhook_url"`
	CORSOrigins []string `json:"cors_origins,omitempty" yaml:"cors_origins"`
	ListenPort  int      `json:"listen_port,omitempty" yaml:"listen_port"`

	// Chatbot (remote server mode — CLI connects to rysh-server chatbot panes).
	// WorkspaceID is required: every operator-side chatbot route on rysh-server
	// is nested under /api/workspaces/:wsID/chatbots/:id/... (X2).
	ServerURL        string   `json:"server_url,omitempty" yaml:"server_url"`
	WorkspaceID      string   `json:"workspace_id,omitempty" yaml:"workspace_id"`
	ConfigID         string   `json:"config_id,omitempty" yaml:"config_id"`
	AutoTakeover     bool     `json:"auto_takeover,omitempty" yaml:"auto_takeover"`
	TakeoverKeywords []string `json:"takeover_keywords,omitempty" yaml:"takeover_keywords"`

	// Mode is a channel-polysemous transport selector.
	//   Telegram: "poll" (default, getUpdates long-poll — needs no public
	//     endpoint) or "webhook" (reuses WebhookPort/WebhookURL). BotToken and
	//     Channels are shared with Slack/Discord.
	//   WhatsApp: "direct" (default) runs the local webhook server + direct Graph
	//     API sends using the WhatsApp fields above; "relay" routes inbound and
	//     outbound through rysh-server's channel relay over the upstream NATS bus
	//     (ws.{workspace}.whatsapp.{connection}.inbound/outbound), so the platform
	//     access token stays server-side, no local webhook port is bound, and the
	//     connection id is resolved from the workspace's enabled WhatsApp External
	//     Connection. Relay mode requires an enabled [upstream] connection
	//     (URL + api_key + workspace).
	//     The canonical relay spelling is `relay: true` (the Relay field above);
	//     `mode: relay` is an accepted alias that the humanoid skill-file parser
	//     folds into Relay at load. Any other value than "relay"/"direct" — or a
	//     conflict between the two spellings — fails the skill-file load loudly
	//     (see actors.normalizeWhatsAppRelayMode). The adapter itself reads only
	//     Relay, never Mode.
	Mode string `json:"mode,omitempty" yaml:"mode"`

	// WhatsApp GA (out-of-window re-engagement). DefaultTemplate names a
	// pre-approved Meta template used when the 24h customer-service window has
	// closed; TemplateLang is its language code (e.g. "en_US").
	DefaultTemplate string `json:"default_template,omitempty" yaml:"default_template"`
	TemplateLang    string `json:"template_lang,omitempty" yaml:"template_lang"`

	// Signal (signal-cli sidecar). SidecarAddr is the JSON-RPC endpoint of a
	// signal-cli daemon (UNIX socket path or host:port); SidecarCmd optionally
	// names a command rysh spawns to launch the daemon itself.
	SidecarAddr string `json:"sidecar_addr,omitempty" yaml:"sidecar_addr"`
	SidecarCmd  string `json:"sidecar_cmd,omitempty" yaml:"sidecar_cmd"`
	// Link, when true, runs the signal-cli device-link flow on Start
	// (startLink → QR → finishLink) via the PairingChannel path (X4, design
	// 009). It is a first-run/onboarding switch: leaving it set re-links on
	// every start (signal-cli provisions a fresh linked device each time), so
	// it is meant to be turned on once. Requires an account-less daemon.
	Link bool `json:"link,omitempty" yaml:"link"`

	// iMessage (macOS host bridge). DBPath overrides the default
	// ~/Library/Messages/chat.db location (mainly for testing).
	DBPath string `json:"db_path,omitempty" yaml:"db_path"`

	// Contact pairing & allowlists (WS3, design 003). Allowlist is the declared
	// seed of pre-approved SenderIDs, merged into the runtime PairingStore at
	// spawn (${ENV} references are expanded like other channel fields).
	// PairingPolicy selects how a non-allowlisted sender is handled: "request"
	// (default — a pending pairing request a human approves) or "drop"
	// (discarded with a log line), or "open" (an EXPLICIT opt-out: ungated on
	// purpose, which `rysh doctor` accepts silently).
	//
	// A channel with NO allowlist and NO pairing_policy follows the session
	// default `humanoid_defaults.pairing_default` (RYSH_PAIRING_DEFAULT):
	// "open" (the shipped default, pre-WS3 behaviour) admits every sender;
	// "closed" gates it with policy "request" per design 003 G5. Until the
	// default is set to closed, `rysh doctor` WARNs for each such channel.
	Allowlist     []string `json:"allowlist,omitempty" yaml:"allowlist"`
	PairingPolicy string   `json:"pairing_policy,omitempty" yaml:"pairing_policy"`
}

ChannelConfig describes the configuration for a single communication channel.

type ChannelStatus

type ChannelStatus struct {
	Type      string `json:"type"` // "whatsapp", "slack", "email", "phone", "chatbot"
	Connected bool   `json:"connected"`
	Error     string `json:"error,omitempty"`
	Details   string `json:"details,omitempty"` // e.g. "listening on #support, #engineering"
}

ChannelStatus reports the state of one communication channel.

type Choice

type Choice = sharedmsg.Choice

type CodecRegistry

type CodecRegistry = sharedmsg.CodecRegistry

func DefaultCodecRegistry

func DefaultCodecRegistry() *CodecRegistry

DefaultCodecRegistry returns a CodecRegistry pre-registered with all message types: shared agentic/output types (from rysh-shared) plus CLI-specific workspace routing, snapshot, and sharing messages.

type ConversationMessage

type ConversationMessage = sharedmsg.ConversationMessage

type ConversationTurnInfo

type ConversationTurnInfo = sharedmsg.ConversationTurnInfo

type ConversationType

type ConversationType = sharedmsg.ConversationType

type DiffPayload

type DiffPayload = sharedmsg.DiffPayload

type Direction

type Direction string

Direction represents a navigation direction for consolidated focus messages.

const (
	DirNext  Direction = "next"
	DirPrev  Direction = "prev"
	DirLeft  Direction = "left"
	DirRight Direction = "right"
	DirUp    Direction = "up"
	DirDown  Direction = "down"
)

type EmailChannelConfig

type EmailChannelConfig struct {
	Governance string `json:"governance,omitempty" yaml:"governance"` // "ai" (default) or "human"
	Address    string `json:"address,omitempty" yaml:"address"`
	IMAPHost   string `json:"imap_host,omitempty" yaml:"imap_host"`
	IMAPPort   int    `json:"imap_port,omitempty" yaml:"imap_port"`
	SMTPHost   string `json:"smtp_host,omitempty" yaml:"smtp_host"`
	SMTPPort   int    `json:"smtp_port,omitempty" yaml:"smtp_port"`
	Username   string `json:"username,omitempty" yaml:"username"`
	Password   string `json:"password,omitempty" yaml:"password"`
}

EmailChannelConfig holds all connection details for an email channel: IMAP/SMTP hosts, ports, and credentials.

type EmailDetail

type EmailDetail struct {
	ID          string           `json:"id"` // short 4-char handle for prompts
	UID         int              `json:"uid"`
	From        string           `json:"from"`
	To          string           `json:"to"`
	Subject     string           `json:"subject"`
	Date        string           `json:"date"`
	Body        string           `json:"body"`
	MessageID   string           `json:"message_id"`
	InReplyTo   string           `json:"in_reply_to"`
	Attachments []AttachmentInfo `json:"attachments,omitempty"`
}

EmailDetail holds the full content of a single email.

type EmailSummary

type EmailSummary struct {
	ID        string `json:"id"` // short 4-char handle for prompts (stable per session)
	UID       int    `json:"uid"`
	From      string `json:"from"`
	Subject   string `json:"subject"`
	Date      string `json:"date"`
	Snippet   string `json:"snippet"`
	MessageID string `json:"message_id"`
	InReplyTo string `json:"in_reply_to"`
}

EmailSummary holds a brief overview of an email for listing. MessageID and InReplyTo are carried so a UI can thread messages and identify which email to act on (e.g. set reply In-Reply-To) without a full ReadEmail.

type ForgedInvokeRequest

type ForgedInvokeRequest struct {
	Op   string          `json:"op"`
	Args json.RawMessage `json:"args,omitempty"`
	// Auth, when non-empty, is the SUBSCRIBER's current access token (forged-API
	// auth plan, Model B / delegated identity). The owner injects it as the bearer
	// for this one call so the backend enforces the subscriber's authorization,
	// then discards it — it is never cached, persisted, or logged. It is carried
	// out-of-band from Args (so it is excluded from arg-schema validation) and is
	// honored ONLY when the owner's share opted into delegated auth.
	Auth string `json:"auth,omitempty"`
}

ForgedInvokeRequest is the payload a subscriber sends to invoke a shared forge-origin operation (Task 2, phase 2b). It travels inside a MsgUpstreamCommand of CommandType "invoke_op" (Payload = JSON of this struct) and, on the subscriber side, as the body of the local invoke request to the RemoteShareListenerActor. Op is the forge tool name (e.g. "weather_getWeather"); Args is the tool's input JSON.

type ForgedInvokeResult

type ForgedInvokeResult struct {
	Content   string `json:"content,omitempty"`
	Error     string `json:"error,omitempty"`
	ErrorKind string `json:"error_kind,omitempty"`
}

ForgedInvokeResult is the owner's reply to a ForgedInvokeRequest. It mirrors tools.ToolOutput's user-facing fields so the subscriber proxy can reconstruct a ToolOutput. Content is redacted on the owner side when the share's --redact is on (the default). ErrorKind uses the tools.ErrKind* taxonomy.

type ForgedOpSpec

type ForgedOpSpec struct {
	Name        string `json:"name"`
	Description string `json:"description"`
	Schema      string `json:"schema"`
	Mutating    bool   `json:"mutating"`
}

ForgedOpSpec describes one shareable forged-API operation (Task 2 phase 2a). Name is the forge operation/tool name (e.g. "weather_getWeather"); Schema is the operation's JSON-schema text; Mutating is true for non-GET/HEAD operations.

type GroundingReportInfo

type GroundingReportInfo = sharedmsg.GroundingReportInfo

type HumanoidInfo

type HumanoidInfo struct {
	Name            string          `json:"name"`
	Active          bool            `json:"active"`
	SystemPrompt    string          `json:"system_prompt"`
	RegisteredPanes []string        `json:"registered_panes,omitempty"`
	Channels        []ChannelStatus `json:"channels,omitempty"`
}

HumanoidInfo describes a humanoid's state.

type ImportCookie

type ImportCookie struct {
	Name     string  `json:"name"`
	Value    string  `json:"value"`
	Domain   string  `json:"domain"`
	Path     string  `json:"path"`
	Expires  float64 `json:"expires"` // unix seconds; <=0 means a session cookie
	HTTPOnly bool    `json:"httpOnly"`
	Secure   bool    `json:"secure"`
	SameSite string  `json:"sameSite"` // "Strict" | "Lax" | "None" | ""
}

ImportCookie is one browser cookie transferred from a real-Chrome login jar into the desktop app's Electron session partition. Fields/JSON tags mirror cdp.Cookie (from Storage.getCookies) so the wire payload is identical.

type InputType

type InputType = sharedmsg.InputType

type LLMPickerModel added in v0.2.3

type LLMPickerModel struct {
	Ref      string `json:"ref"`      // "<provider>/<name>" as declared in .rysh/llms
	Model    string `json:"model"`    // provider API model id
	Provider string `json:"provider"` // rysh provider family serving it
	Current  bool   `json:"current"`  // is the session default right now
	// KeyMissing reports that this model's provider family has no API key in
	// reach — neither ##secret nor the daemon environment — so a selection would
	// fail to authenticate on the next prompt. KeyName is the variable to set
	// (empty when the family needs no key, e.g. ollama).
	KeyMissing bool   `json:"key_missing,omitempty"`
	KeyName    string `json:"key_name,omitempty"`
}

LLMPickerModel is one activatable row of the picker.

type LLMPickerScope added in v0.2.3

type LLMPickerScope struct {
	Name string `json:"name"` // "session", "workspace", "tab", "lane", "stack", "pane"
	// Hint is the one-line explanation shown beside the name — how far the
	// binding reaches, in the user's terms.
	Hint string `json:"hint"`
	// Command is the `##` command that binds the chosen model at this scope,
	// missing only the model ref. The front-end appends the ref and submits it,
	// so the scope hierarchy stays defined in one place: the daemon.
	Command string `json:"command"`
}

LLMPickerScope is one binding level offered in step two of the picker.

type MemoryEntry

type MemoryEntry = sharedmsg.MemoryEntry

type MemoryState

type MemoryState = sharedmsg.MemoryState

type MessageLogger

type MessageLogger = sharedmsg.MessageLogger

type MessageOrigin

type MessageOrigin = sharedmsg.MessageOrigin

type MessageSource

type MessageSource = sharedmsg.MessageSource

type MsgActivePaneReply

type MsgActivePaneReply struct {
	PaneID    string `json:"pane_id"`
	PaneCount int    `json:"pane_count"`
}

MsgActivePaneReply carries the TabActor's active pane ID and pane count.

type MsgAgentActivate

type MsgAgentActivate struct {
	Name string `json:"name"`
}

MsgAgentActivate activates a deactivated agent.

type MsgAgentContinue

type MsgAgentContinue struct {
	Name string `json:"name"`
}

MsgAgentContinue resumes an agent's paused LLM run by name (used by @@agent-name continue). No-op with a notice when nothing is paused.

type MsgAgentCreate

type MsgAgentCreate struct {
	Name           string `json:"name"`
	SystemPrompt   string `json:"system_prompt"`
	WorktreeBranch string `json:"worktree_branch,omitempty"`
	WorktreePath   string `json:"worktree_path,omitempty"`
	// AutoApprove carries the skill-file `auto_approve:` field. Nil = absent =
	// the default, TRUE: an agent runs autonomously with no terminal, so a
	// gated tool call would block on a dialog nobody can answer.
	AutoApprove *bool `json:"auto_approve,omitempty"`
}

MsgAgentCreate creates a new autonomous agent. WorktreeBranch/WorktreePath record the agent's isolation worktree when one was provisioned at spawn (`isolation: worktree` frontmatter or `##agent spawn --worktree`, design 008); empty means the agent works on the shared checkout.

type MsgAgentDeactivate

type MsgAgentDeactivate struct {
	Name string `json:"name"`
}

MsgAgentDeactivate deactivates an agent (keeps state, stops processing).

type MsgAgentDelete

type MsgAgentDelete struct {
	Name string `json:"name"`
}

MsgAgentDelete deletes an agent by name.

type MsgAgentList

type MsgAgentList struct{}

MsgAgentList requests a list of all agents.

type MsgAgentListReply

type MsgAgentListReply struct {
	Agents []AgentInfo `json:"agents"`
}

MsgAgentListReply carries the list of agents.

type MsgAgentPrompt

type MsgAgentPrompt struct {
	AgentName    string `json:"agent_name"`
	Prompt       string `json:"prompt"`
	SourcePaneID string `json:"source_pane_id"`
	ScopeHint    string `json:"scope_hint,omitempty"` // invoking pane's scope chain (resolved by the workspace)
}

MsgAgentPrompt sends a prompt to a named agent.

type MsgAgentRegisterPane

type MsgAgentRegisterPane struct {
	AgentName string `json:"agent_name"`
	PaneID    string `json:"pane_id"`
	PaneName  string `json:"pane_name"`
}

MsgAgentRegisterPane registers an agent to output to a specific pane.

type MsgAgentStop

type MsgAgentStop struct {
	Name string `json:"name"`
}

MsgAgentStop interrupts an agent's in-flight LLM run by name (used by @@agent-name stop). PAUSE semantics: the agent stays alive and its conversation state / session memory are preserved — MsgAgentContinue (or a new prompt) resumes from the checkpoint. Deleting an agent is a separate operation (MsgAgentDelete / ##agent delete).

type MsgAgentUnregisterPane

type MsgAgentUnregisterPane struct {
	AgentName string `json:"agent_name"`
	PaneID    string `json:"pane_id"`
}

MsgAgentUnregisterPane removes an agent's pane registration.

type MsgAgenticCancel

type MsgAgenticCancel = sharedmsg.MsgAgenticCancel

type MsgAgenticContinue

type MsgAgenticContinue = sharedmsg.MsgAgenticContinue

type MsgAgenticOutput

type MsgAgenticOutput = sharedmsg.MsgAgenticOutput

type MsgAgenticPrompt

type MsgAgenticPrompt = sharedmsg.MsgAgenticPrompt

type MsgAgenticStatus

type MsgAgenticStatus = sharedmsg.MsgAgenticStatus

type MsgAgenticStep

type MsgAgenticStep = sharedmsg.MsgAgenticStep

type MsgAnsaRoute added in v0.2.4

type MsgAnsaRoute struct {
	V int `json:"v"` // AnsaSchemaVersion

	// From is the sender's pane id, for attribution and for the audit line the
	// target sees. Optional: a route from something that is not a pane (cron, a
	// humanoid, a test) is legitimate and must not be refused for it.
	From string `json:"from,omitempty"`

	// To is the target's FULL PANE UUID. Always. A name never travels as an
	// address.
	//
	// @name is resolved to an id at the EDGE — in the command that accepted the
	// @ — never in the router. The reason is one layer below the board's:
	// given-names are unique per LANE, not per session
	// (TabActor.IsGivenNameTakenInLane), so a name is a label for humans and an
	// id is an address. A name arriving here is AnsaErrNotAnID, because
	// resolving it late would re-open the ambiguity the edge already closed.
	//
	// Ambiguity is therefore HANDLED rather than assumed away, which is why
	// session-unique given-names are an improvement to this design and not a
	// safety gate on it.
	To string `json:"to"`

	// Mode is AnsaModeShell or AnsaModePrompt. Empty means shell, which is the
	// pane inbox's own default; anything else is AnsaErrBadMode.
	Mode string `json:"mode,omitempty"`

	Text string `json:"text"`
}

MsgAnsaRoute is a routing request: deliver Text to the pane named by To.

Published to T("ansa", "inbox") as a REQUEST — the reply is MsgAnsaRouteResult and is the whole point. Fire-and-forget would reintroduce exactly the silent drop this actor exists to prevent.

func NewAnsaRoute added in v0.2.4

func NewAnsaRoute(from, to, mode, text string) *MsgAnsaRoute

NewAnsaRoute stamps the schema version so V is never forgotten at a new call site — the same reason NewBoardPost exists.

type MsgAnsaRouteResult added in v0.2.4

type MsgAnsaRouteResult struct {
	OK bool `json:"ok"`

	// TargetPaneID is the FULL pane uuid the message was delivered to —
	// resolved, never echoed back from the request. A caller that addressed by
	// name learns which pane it actually reached.
	TargetPaneID string `json:"target_pane_id,omitempty"`
	// TargetPersona is that pane's display name, for the caller's own logs.
	TargetPersona string `json:"target_persona,omitempty"`

	// Code is one of the AnsaErr* constants when OK is false, and empty when
	// OK is true.
	Code  string `json:"code,omitempty"`
	Error string `json:"error,omitempty"`

	// Candidates lists the panes an ambiguous name matched, so the caller can
	// re-address by id without having to go and look. Only set for
	// AnsaErrAmbiguousTarget — a refusal that tells you how to succeed.
	Candidates []string `json:"candidates,omitempty"`
}

MsgAnsaRouteResult is the answer. OK is the only success; every false carries both a Code (to branch on) and an Error (to show a human).

func AnsaRefusal added in v0.2.4

func AnsaRefusal(code, format string, a ...any) *MsgAnsaRouteResult

AnsaRefusal builds a failed result. Constructing refusals through one function is what keeps "a code was set" from being a thing anyone has to remember at eight separate return statements.

type MsgApprovalRequest

type MsgApprovalRequest = sharedmsg.MsgApprovalRequest

type MsgApprovalResponse

type MsgApprovalResponse = sharedmsg.MsgApprovalResponse

type MsgAttentionAck

type MsgAttentionAck struct {
	PaneID string `json:"pane_id"`
}

MsgAttentionAck is published when the user acknowledges a pane's attention.

type MsgAttentionDisable

type MsgAttentionDisable struct {
	PaneID       string `json:"pane_id,omitempty"`
	HumanoidName string `json:"humanoid_name,omitempty"`
}

MsgAttentionDisable disables attention for a humanoid or approval pane.

type MsgAttentionEnable

type MsgAttentionEnable struct {
	PaneID       string `json:"pane_id,omitempty"`
	HumanoidName string `json:"humanoid_name,omitempty"`
}

MsgAttentionEnable enables attention for a humanoid or approval pane.

type MsgAttentionEvent

type MsgAttentionEvent struct {
	PaneID       string            `json:"pane_id"`
	HumanoidName string            `json:"humanoid_name,omitempty"`
	Category     AttentionCategory `json:"category"`
	Priority     AttentionPriority `json:"priority"`
	Title        string            `json:"title"`
	Summary      string            `json:"summary"`
	Timestamp    int64             `json:"timestamp"`
}

MsgAttentionEvent is published when a pane/humanoid needs user attention.

type MsgBoardAgentPrompt added in v0.2.6

type MsgBoardAgentPrompt struct {
	Text string `json:"text"`

	// Board names WHICH board's claude to reach (design 028, `D-13` ruled
	// per-fleet on 2026-08-11). Empty means the session board.
	//
	// It does not weaken ruling 2: every prompt still goes to a board claude
	// and there is still no bypass. What it fixes is that with one mind per
	// fleet, "the board claude" stopped being a single pane — and a prompt
	// typed into fleet epic-07's board must not be acted on by epic-08's.
	Board string `json:"board,omitempty"`
}

MsgBoardAgentPrompt is one line typed into the agents-board input field (design 027 §5.2).

It carries NO target. Every prompt goes to the board claude — that is the founder's ruling 2, and there is deliberately no verbatim `@tag` bypass — so naming a target here would create a second way to route that the board claude is not in the path of. Resolving which pane is the board claude belongs to the workspace, which is also where the refusal lives when two panes share the name.

type MsgBoardPost added in v0.2.4

type MsgBoardPost struct {
	V int `json:"v"` // BoardSchemaVersion

	// PaneID is the FULL pane uuid of the poster and is the identity key.
	// Never the 8-char truncation the fleet envelope carries.
	PaneID string `json:"pane_id"`

	// Persona is the poster's display name — its pane given-name, falling back
	// to the auto-title and then to "pane-<first 8 of uuid>". Display only; not
	// unique (see the file header).
	Persona string `json:"persona"`

	Kind string `json:"kind"` // free-form; see BoardKind* for the well-known ones
	Text string `json:"text"`

	// ThreadID is empty when this post IS a root. Otherwise it is the root's
	// id, minted by whichever agent opened the thread as "<pane-uuid>/<n>".
	// A reply whose root has not arrived yet is rendered as a provisional root
	// and re-parented when the root lands — that is expected, not an error.
	ThreadID string `json:"thread_id,omitempty"`

	TS int64 `json:"ts"` // unix millis, the POSTER's clock: arrival order, not causal order

	// ToPersona / ToPaneID — the recipient of a DIRECTED message.
	//
	// GATE 4 WAS REOPENED (founder, 2026-08-09) and this reverses the earlier
	// ruling that removed these. The original argument for asking before v1 was
	// that adding them later is a breaking change; the schema is still V=1, so
	// it is still cheap.
	//
	// A directed message is still CHAT, not routing: this says who was spoken
	// TO, exactly as a chat app shows an @mention. It carries no chain, no
	// msg id and no delivery semantics, and ANSA — not this field — is what
	// actually delivers anything.
	//
	// Both are empty for a broadcast, which stays the default. Gate 3 is
	// unaffected: a non-fleet claude directs a message exactly as a fleet one
	// does, because neither field mentions a fleet.
	ToPersona string `json:"to_persona,omitempty"`
	ToPaneID  string `json:"to_pane_id,omitempty"`
}

MsgBoardPost is one message on the agents board: a root, or a reply under a root. Published on T("board", "post"); the board view subscribes.

func NewBoardPost added in v0.2.4

func NewBoardPost(paneID, persona, kind, text string, nowMillis int64) *MsgBoardPost

NewBoardPost stamps the schema version and the poster's clock. Every producer goes through here so that V is never forgotten on a new call site.

type MsgBoardRegister added in v0.2.4

type MsgBoardRegister struct {
	V       int    `json:"v"`
	PaneID  string `json:"pane_id"`
	Persona string `json:"persona"`
	TS      int64  `json:"ts"`
}

MsgBoardRegister is a persona announcement: an agent telling the board who it is, so the board can show a roster without waiting for a first post. Published on T("board", "register"). Registration is advisory — a post from an unregistered pane is still rendered, because a board that drops messages from agents that forgot to introduce themselves is worse than one with a thin roster.

func NewBoardRegister added in v0.2.4

func NewBoardRegister(paneID, persona string, nowMillis int64) *MsgBoardRegister

NewBoardRegister stamps the schema version and the announcer's clock, for the same reason NewBoardPost does: every producer goes through a constructor so V is never forgotten on a new call site.

type MsgBrowserActionRequest

type MsgBrowserActionRequest = sharedmsg.MsgBrowserActionRequest

Browser-action messages (server-side AI ↔ web-pane executor).

type MsgBrowserActionResponse

type MsgBrowserActionResponse = sharedmsg.MsgBrowserActionResponse

type MsgCLIAnsaSend added in v0.2.4

type MsgCLIAnsaSend struct {
	// AsPaneID is the SENDER, and unlike the board's it is optional: routing
	// from outside a pane is legitimate. It is never inferred from the active
	// pane, because a wrong "from" is a lie about who is talking.
	AsPaneID string `json:"as_pane_id,omitempty"`

	// To is required — the whole operation is "deliver to this target".
	To   string `json:"to"`
	Mode string `json:"mode,omitempty"`
	Text string `json:"text"`
}

MsgCLIAnsaSend is the AGENT door, reached by `rysh ansa send`.

It is its own CLI message rather than a ##ansa run through MsgCLIRyshCommand, for the three reasons the board's agent door exists (see internal/actors/workspace_board.go): that path attributes to the workspace's ambient active pane when none is named, FOCUSES the pane that is named, and ECHOES the command line into that pane's output buffers. A control channel used by dozens of agents must do none of the three — an agent routing a message must not yank the human's cursor to its own pane.

type MsgCLIBoardPost added in v0.2.4

type MsgCLIBoardPost struct {
	// AsPaneID is the poster, and is REQUIRED. An empty value is an error, not
	// a fallback to the active pane — silently attributing a post to a
	// bystander is the exact defect this message exists to avoid.
	AsPaneID string `json:"as_pane_id"`

	Kind     string `json:"kind,omitempty"` // default: BoardKindMilestone
	Text     string `json:"text"`
	ThreadID string `json:"thread_id,omitempty"` // empty = start a new root

	// Board names which board to post to (design 028). Empty means "resolve it
	// from the poster", which the workspace does: the pane's own board.id meta,
	// then the session board.
	//
	// It rides the CLI message and NOT MsgBoardPost, and the difference is the
	// whole of D-12: this is a request that says where to deliver, while the
	// post itself stays free of any board or fleet identity. Adding it here
	// costs nothing to gate 4, which is about the schema on the wire between
	// agents.
	Board string `json:"board,omitempty"`
}

MsgCLIBoardPost is the agent-facing door: `rysh --board-post --as <pane-id>`.

It exists as its OWN CLI message rather than riding MsgCLIRyshCommand because that path has three properties a board post must not have, each verified against the running code in wave 1:

  • with no --pane-id it runs as the workspace's ACTIVE pane, so a post would be attributed to a bystander (WorkspaceActor.handleCLIRyshCommand);
  • naming a pane calls focusPaneByID, which switches the active tab and moves the human's focus — unusable when dozens of agents post;
  • runRyshCommand echoes the command line into the pane's output buffers, so a post would pollute whichever pane it was attributed to.

So this message carries its poster EXPLICITLY in AsPaneID, never inherits the ambient pane, and its handler neither focuses nor echoes.

type MsgCLICreateLane

type MsgCLICreateLane struct {
	TabID string `json:"tab_id"` // empty = active tab
}

MsgCLICreateLane creates a new lane in the specified tab.

type MsgCLICreatePane

type MsgCLICreatePane struct {
	TabID string `json:"tab_id"` // empty = active tab
}

MsgCLICreatePane creates a new pane (lane split right) in the specified tab.

type MsgCLICreatePaneGroup

type MsgCLICreatePaneGroup struct {
	TabID  string `json:"tab_id"`  // empty = active tab
	LaneID string `json:"lane_id"` // empty = active lane
}

MsgCLICreatePaneGroup creates a new pane group (split down) in the specified lane.

type MsgCLICreateStackedPane

type MsgCLICreateStackedPane struct {
	TabID       string `json:"tab_id"`        // empty = active tab
	LaneID      string `json:"lane_id"`       // empty = active lane
	PaneGroupID string `json:"pane_group_id"` // empty = active group
}

MsgCLICreateStackedPane creates a stacked pane in the specified pane group.

type MsgCLICreateTab

type MsgCLICreateTab struct{}

MsgCLICreateTab creates a new tab via CLI.

type MsgCLIDeleteLane

type MsgCLIDeleteLane struct {
	TabID  string `json:"tab_id"`
	LaneID string `json:"lane_id"`
}

MsgCLIDeleteLane deletes a lane by ID within a tab.

type MsgCLIDeletePane

type MsgCLIDeletePane struct {
	PaneID string `json:"pane_id"`
}

MsgCLIDeletePane deletes a specific pane by ID.

type MsgCLIDeletePaneGroup

type MsgCLIDeletePaneGroup struct {
	TabID       string `json:"tab_id"`
	LaneID      string `json:"lane_id"`
	PaneGroupID string `json:"pane_group_id"`
}

MsgCLIDeletePaneGroup deletes a pane group by ID.

type MsgCLIDeleteTab

type MsgCLIDeleteTab struct {
	TabID string `json:"tab_id"`
}

MsgCLIDeleteTab deletes a tab by ID.

type MsgCLIPipelineDisable

type MsgCLIPipelineDisable struct {
	TabID string `json:"tab_id"` // empty = active tab
}

MsgCLIPipelineDisable disables pipeline mode for a tab.

type MsgCLIPipelineEnable

type MsgCLIPipelineEnable struct {
	TabID string `json:"tab_id"` // empty = active tab
}

MsgCLIPipelineEnable enables pipeline mode for a tab.

type MsgCLIResponse

type MsgCLIResponse struct {
	OK     bool   `json:"ok"`
	Error  string `json:"error,omitempty"`
	ID     string `json:"id,omitempty"`     // newly created entity ID, if applicable
	Output string `json:"output,omitempty"` // captured textual output (e.g. ## command result)
}

MsgCLIResponse is the response to all CLI commands.

type MsgCLIRyshCommand

type MsgCLIRyshCommand struct {
	TabID   string `json:"tab_id"`  // empty = active tab
	PaneID  string `json:"pane_id"` // empty = active (or TabID's active) pane
	Command string `json:"command"` // rysh command body WITHOUT leading "##"
}

MsgCLIRyshCommand asks the WorkspaceActor to run a "##" system command on behalf of the CLI, targeting a specific pane (and tab) and replying with the captured command output. It is the command-line equivalent of typing a "##" command in a pane: e.g. `rysh --cmd --tab-id <t> --pane-id <p> "echo hi"` maps to Command="cmd echo hi" run on pane <p>.

Command is the rysh command body WITHOUT the leading "##" (e.g. "cmd echo hi", "pane info", "tab list"). PaneID and TabID select the target pane; an empty PaneID falls back to TabID's active pane, and an empty TabID falls back to the workspace's currently active pane. PaneID/TabID may be an id, alias, title, or given-name (resolved the same way as interactive ## commands).

type MsgCancelPrompt

type MsgCancelPrompt struct{}

MsgCancelPrompt cancels any in-flight LLM completion.

type MsgChannelAllow

type MsgChannelAllow struct {
	HumanoidName string `json:"humanoid_name"`
	Channel      string `json:"channel,omitempty"`
	SenderID     string `json:"sender_id"`
}

MsgChannelAllow adds a sender directly to the allowlist, skipping the code flow (approver → humanoid). An empty Channel applies to every configured channel.

type MsgChannelPairApprove

type MsgChannelPairApprove struct {
	HumanoidName string `json:"humanoid_name"`
	Channel      string `json:"channel,omitempty"`
	Code         string `json:"code"`
}

MsgChannelPairApprove consumes a pending code, promoting its sender to the allowlist (approver → humanoid). An empty Channel means "search every configured channel for the code" — the terminal command omits the channel.

type MsgChannelPairLink struct {
	HumanoidName string `json:"humanoid_name"`
	Channel      string `json:"channel"`
	Force        bool   `json:"force,omitempty"`
}

MsgChannelPairLink asks a humanoid to run a channel's device-link flow on demand (approver → humanoid; `##humanoid pair link`, X4 design 009 §3.4). Force overrides the re-link guard, which otherwise refuses when the daemon already holds a linked account — re-provisioning a live number is the §6 re-link hazard.

type MsgChannelPairList

type MsgChannelPairList struct {
	HumanoidName string `json:"humanoid_name"`
	Channel      string `json:"channel,omitempty"`
}

MsgChannelPairList requests the pending + allowlist state (approver → humanoid, request/reply). An empty Channel aggregates every configured channel.

type MsgChannelPairListReply

type MsgChannelPairListReply struct {
	Pending   []PendingPair `json:"pending,omitempty"`
	Allowlist []string      `json:"allowlist,omitempty"`
}

MsgChannelPairListReply carries the swept pending set and the allowlist (humanoid → approver). When the request aggregated several channels, the allowlist entries are rendered as "channel:sender" so they stay attributable.

type MsgChannelPairQR

type MsgChannelPairQR struct {
	HumanoidName string `json:"humanoid_name"`
	Channel      string `json:"channel"`
	QR           string `json:"qr"`
	// QRImage is a "data:image/png;base64,…" rendering of QR the web dashboard can
	// show in an <img> (X4, design 009). Empty when encoding failed; the pane
	// renders its own half-block QR from QR, so this is dashboard-only.
	QRImage string `json:"qr_image,omitempty"`
}

MsgChannelPairQR carries a QR-channel device-link payload for rendering (humanoid → pane/dashboard). QR is the raw link payload; the dashboard renders it as a scannable image (design 005 DB2).

type MsgChannelPairRequest

type MsgChannelPairRequest struct {
	HumanoidName string `json:"humanoid_name"`
	Channel      string `json:"channel"`
	SenderID     string `json:"sender_id"`
	SenderName   string `json:"sender_name"`
	Code         string `json:"code"`
	FirstMsg     string `json:"first_msg,omitempty"`
	ExpiresAt    int64  `json:"expires_at"`
}

MsgChannelPairRequest announces that a non-allowlisted sender messaged a gated channel and is waiting for approval (humanoid → approvers/dashboard). The code is shown only to the operator, never sent back to the requester.

type MsgChannelPairStatus

type MsgChannelPairStatus struct {
	HumanoidName string `json:"humanoid_name"`
	Channel      string `json:"channel"`
	Connected    bool   `json:"connected"`
	Detail       string `json:"detail,omitempty"`
}

MsgChannelPairStatus reports a QR channel's device-link state transitions (humanoid → pane/dashboard): linked (Connected=true) or a link error (Connected=false with Detail).

type MsgClosePane

type MsgClosePane struct{}

MsgClosePane closes the active pane group (or entire lane/tab if last).

type MsgConversationAppend

type MsgConversationAppend = sharedmsg.MsgConversationAppend

type MsgConversationHistoryAppend

type MsgConversationHistoryAppend = sharedmsg.MsgConversationHistoryAppend

type MsgConversationHistoryReply

type MsgConversationHistoryReply = sharedmsg.MsgConversationHistoryReply

type MsgCreateApprovalPane

type MsgCreateApprovalPane = sharedmsg.MsgCreateApprovalPane

type MsgCreatePane

type MsgCreatePane struct{}

MsgCreatePane creates a new lane to the right of the active lane.

type MsgCreatePaneDown

type MsgCreatePaneDown struct{}

MsgCreatePaneDown creates a new pane group in the active lane.

type MsgCreateStackedPane

type MsgCreateStackedPane struct{}

MsgCreateStackedPane creates a new pane stacked in the active group.

type MsgCreateTab

type MsgCreateTab struct{}

MsgCreateTab creates a new tab.

type MsgDestroyApprovalPane

type MsgDestroyApprovalPane = sharedmsg.MsgDestroyApprovalPane

type MsgDiscoverCodexSession added in v0.2.6

type MsgDiscoverCodexSession struct {
	PaneID string `json:"pane_id"`
	// Cwd is the directory the agent was launched in, and the field discovery
	// matches on — codex records it in the rollout's first line.
	Cwd string `json:"cwd"`
	// NotBefore is the launch time as a unix timestamp. A rollout older than
	// this belongs to an earlier session.
	NotBefore int64 `json:"not_before"`
	Retries   int   `json:"retries"`
}

MsgDiscoverCodexSession is the workspace's note to itself to find out which session a just-launched codex opened (design 029).

It exists because codex, unlike claude, has no launch-time session-id flag: it issues its own id and the only place it publishes it is the rollout file it writes under $CODEX_HOME/sessions. That file appears a moment AFTER the launch command is delivered, so the lookup has to be retried rather than done inline — hence a self-addressed message with a countdown, the same shape MsgLaunchClaudeInPane uses for the same class of problem.

type MsgEqualizeAll

type MsgEqualizeAll struct{}

MsgEqualizeAll resets both lane widths and every lane's group heights to equal, restoring a fully balanced layout in one shot (ctrl+l e).

type MsgEqualizeHorizontal

type MsgEqualizeHorizontal struct{}

MsgEqualizeHorizontal sets all lanes to equal width.

type MsgEqualizePanes

type MsgEqualizePanes struct{}

MsgEqualizePanes resets all lane flex weights to equal.

type MsgEqualizeVertical

type MsgEqualizeVertical struct{}

MsgEqualizeVertical resets all group rowFlex weights in the active lane to equal.

type MsgExecPrompt

type MsgExecPrompt struct {
	Prompt string `json:"prompt"`
}

MsgExecPrompt runs an LLM completion. If a completion is already in flight, it is cancelled first (last-prompt-wins semantics).

type MsgExecRyshOnPane

type MsgExecRyshOnPane struct {
	PaneID  string `json:"pane_id"`
	Command string `json:"command"`
}

MsgExecRyshOnPane asks the WorkspaceActor to run a ## system command on a specific pane. It is used to relay a #### command from a remote subscriber to the shared source pane: the source's UpstreamShareActor publishes this to the workspace inbox so the command runs as "##<Command>" on the target pane. Command is the rysh command body WITHOUT the leading "##".

type MsgFocusNextTab

type MsgFocusNextTab struct{}

MsgFocusNextTab cycles focus to the next tab.

type MsgFocusPane

type MsgFocusPane struct {
	Direction Direction `json:"direction"`
}

MsgFocusPane moves focus in the specified direction. Replaces MsgFocusNextPane, MsgFocusPrevPane, MsgFocusPaneLeft/Right/Up/Down.

type MsgFocusPaneByID

type MsgFocusPaneByID struct {
	ID string `json:"id"`
}

MsgFocusPaneByID focuses a specific pane by its UUID.

type MsgFocusPrevTab

type MsgFocusPrevTab struct{}

MsgFocusPrevTab cycles focus to the previous tab.

type MsgFocusTabIndex

type MsgFocusTabIndex struct {
	Index int `json:"index"`
}

MsgFocusTabIndex jumps to a tab by 0-based index.

type MsgGetActivePane

type MsgGetActivePane struct{}

MsgGetActivePane requests the active pane ID and pane count from a TabActor.

type MsgGetConversationHistory

type MsgGetConversationHistory = sharedmsg.MsgGetConversationHistory

type MsgGetGroundingState

type MsgGetGroundingState = sharedmsg.MsgGetGroundingState

type MsgGetLaneActivePane

type MsgGetLaneActivePane struct{}

MsgGetLaneActivePane requests the active pane ID and pane count from a LaneActor.

type MsgGetLaneSnapshot

type MsgGetLaneSnapshot struct {
	LayoutOnly  bool `json:"layout_only,omitempty"`
	NoHistories bool `json:"no_histories,omitempty"`
}

MsgGetLaneSnapshot requests a lane snapshot. When LayoutOnly is set, the reply omits heavy per-pane content (output/history/VT) so only structural layout fields are carried up the cascade.

type MsgGetMirrorPaneVT

type MsgGetMirrorPaneVT struct {
	PaneID string `json:"pane_id"`
}

MsgGetMirrorPaneVT requests the live VT frame (screen + cursor) of a single mirror pane (id "mirror:<shareID>:<srcPaneID>"). Answered by the subscriber's WorkspaceActor straight from its mirror-tab state — O(one pane), no Tab→Lane→Group cascade — so the TUI can refresh one interactive mirror pane per rawDirty signal instead of re-reading the whole workspace snapshot.

type MsgGetMirrorScrollback

type MsgGetMirrorScrollback struct {
	PaneID string `json:"pane_id"`
}

MsgGetMirrorScrollback requests the accumulated scrollback + current screen of a mirror pane (id prefixed "mirror:"). Answered by the subscriber's WorkspaceActor, which holds the mirror-tab state (mirror panes are synthetic and have no PaneActor of their own).

type MsgGetPaneGroupActivePane

type MsgGetPaneGroupActivePane struct{}

MsgGetPaneGroupActivePane requests the active pane ID and pane count from a PaneGroupActor.

type MsgGetPaneGroupSnapshot

type MsgGetPaneGroupSnapshot struct {
	LayoutOnly  bool `json:"layout_only,omitempty"`
	NoHistories bool `json:"no_histories,omitempty"`
}

MsgGetPaneGroupSnapshot requests a pane group snapshot. When LayoutOnly is set, the reply omits heavy per-pane content so only layout fields are carried.

type MsgGetPaneScrollback

type MsgGetPaneScrollback struct{}

MsgGetPaneScrollback requests an interactive pane's scrollback history plus its current screen, rendered as ANSI rows (oldest line first). Used by the TUI to populate raw-pane scroll mode for inline programs (e.g. claude).

type MsgGetPaneScrollbackDelta

type MsgGetPaneScrollbackDelta struct {
	Since int64 `json:"since"`
}

MsgGetPaneScrollbackDelta requests scrollback rows (rendered ANSI, oldest first) evicted since the given monotonic count, plus the current evicted total. A tab share's layout loop uses this to forward incremental interactive history to subscribers without re-sending the whole buffer each tick.

type MsgGetPaneSnapshot

type MsgGetPaneSnapshot struct {
	LayoutOnly  bool `json:"layout_only,omitempty"`
	NoHistories bool `json:"no_histories,omitempty"`
}

MsgGetPaneSnapshot requests a pane snapshot. When LayoutOnly is set the pane builds a content-free snapshot (no output/VT) — used by the layout cascade. The TUI's direct per-pane backfill/reconcile leaves it false to pull the full content in a single hop. NoHistories additionally drops the command histories, which LayoutOnly keeps (see MsgGetWorkspaceSnapshot.NoHistories — they are 97% of a layout-only pane snapshot).

type MsgGetPaneVT

type MsgGetPaneVT struct{}

MsgGetPaneVT requests ONLY the live interactive VT frame (screen + cursor) of a local raw pane — the lightweight per-frame refresh for inline TUIs (claude, vim) running in a multi-pane / stacked layout. It is served from the same .snapshot request subject as MsgGetPaneSnapshot (the PaneActor dispatches by type) but skips building and marshalling the heavy output/history buffers, so a redraw-heavy app refreshes one pane's screen cheaply instead of pulling the whole pane snapshot on every rawDirty signal.

type MsgGetRunStatus

type MsgGetRunStatus = sharedmsg.MsgGetRunStatus

type MsgGetSessionMemory

type MsgGetSessionMemory = sharedmsg.MsgGetSessionMemory

type MsgGetTabSnapshot

type MsgGetTabSnapshot struct {
	LayoutOnly  bool `json:"layout_only,omitempty"`
	NoHistories bool `json:"no_histories,omitempty"`
}

MsgGetTabSnapshot requests a tab snapshot. LayoutOnly and NoHistories are propagated down the cascade so per-pane content (and optionally command history) is omitted.

type MsgGetWorkspaceSnapshot

type MsgGetWorkspaceSnapshot struct {
	LayoutOnly bool `json:"layout_only,omitempty"`
	// Fresh forces a cache-bypassing rebuild of the snapshot. Set when the request
	// is triggered by an explicit ws.layoutDirty signal (something just changed) —
	// in particular pane-mode/web-binding changes, which mutate PaneActor state
	// without going through the WorkspaceActor's persistToKV cache-invalidation.
	// Without this, a layoutDirty-driven fetch can be served from the ~100ms
	// memoized snapshot built BEFORE the change, so a stream client with no
	// fallback poll (the desktop app) sticks on stale state until the next dirty
	// signal. The blind poll and overlapping internal pollers leave it false and
	// keep sharing the cache.
	Fresh bool `json:"fresh,omitempty"`
	// NoHistories additionally drops the per-pane command histories, which
	// LayoutOnly keeps. They are not incidental: measured live, shell_history was
	// 28.9 KB of a 29.9 KB layout-only pane snapshot — 97.5% of it — because
	// every pane seeds the SAME session history file (pane.go, loadHistoryFile)
	// and the file had grown to ~29 KB. Fifty panes therefore carried fifty
	// identical copies, re-serialized on every cascade (F-7c).
	//
	// Only set this when the caller reads no history at all. The TUI's
	// activeHistory() reads ShellHistory/PromptHistory straight out of its layout
	// snapshot for arrow-key recall, so the TUI must leave this false; the web
	// server's streamPaneVT reads only ids and the RawMode/RemoteInteractive
	// flags, so it sets it.
	NoHistories bool `json:"no_histories,omitempty"`
}

MsgGetWorkspaceSnapshot requests a workspace snapshot. When LayoutOnly is set (the TUI's event-driven layout fetch), the whole Tab→Lane→Group→Pane cascade builds content-free pane snapshots: heavy output/VT buffers are omitted because the TUI streams that content directly per-pane. Internal callers (sharing, CLI, KV) leave it false to get the full snapshot.

LayoutOnly does NOT drop command history — see NoHistories.

type MsgGroundingStateReply

type MsgGroundingStateReply = sharedmsg.MsgGroundingStateReply

type MsgHumanoidActivate

type MsgHumanoidActivate struct {
	Name string `json:"name"`
}

MsgHumanoidActivate activates a deactivated humanoid.

type MsgHumanoidChannelStart

type MsgHumanoidChannelStart struct {
	ChannelType string `json:"channel_type"`
}

MsgHumanoidChannelStart starts a specific channel on a humanoid.

type MsgHumanoidChannelStatus

type MsgHumanoidChannelStatus struct {
	Name        string `json:"name"`
	ChannelType string `json:"channel_type"`
	Connected   bool   `json:"connected"`
	Error       string `json:"error,omitempty"`
}

MsgHumanoidChannelStatus reports a channel's connection state.

type MsgHumanoidChannelStop

type MsgHumanoidChannelStop struct {
	ChannelType string `json:"channel_type"`
}

MsgHumanoidChannelStop stops a specific channel on a humanoid.

type MsgHumanoidContinue

type MsgHumanoidContinue struct {
	Name string `json:"name"`
}

MsgHumanoidContinue resumes a humanoid's paused LLM run by name (used by @@humanoid-name continue). No-op with a notice when nothing is paused.

type MsgHumanoidCreate

type MsgHumanoidCreate struct {
	Name         string                   `json:"name"`
	SystemPrompt string                   `json:"system_prompt"`
	Contacts     map[string]ChannelConfig `json:"contacts,omitempty"`
	Provider     string                   `json:"provider,omitempty" yaml:"provider,omitempty"`
	// Model pins the model for the selected provider. Parsed from skill-file
	// frontmatter since MP2 but never threaded through until R4 — it was dead
	// config, and `rysh assistant` writes it into every generated SKILL.md.
	Model   string `json:"model,omitempty" yaml:"model,omitempty"`
	Profile string `json:"profile,omitempty" yaml:"profile,omitempty"`
	// AutoApprove carries the skill-file `auto_approve:` field. Nil means the
	// field was absent, which resolves to the DEFAULT (true) — a humanoid runs
	// its tool calls without an approval dialog. Set it to false in the skill
	// file to gate every consequential tool call on an owner "yes". A pointer
	// rather than a bool so "absent" and "explicitly false" stay
	// distinguishable across the wire and the KV round trip.
	AutoApprove *bool `json:"auto_approve,omitempty" yaml:"auto_approve,omitempty"`
}

MsgHumanoidCreate creates a new humanoid with optional channel configs. Provider carries the skill-file `provider:` selection (design 006 MP2) so a humanoid can run on a non-default model provider; empty = config default. Profile carries the skill-file `profile:` marker (design 007 PM1/PM3); "assistant" selects the fail-closed personal-assistant defaults.

type MsgHumanoidDeactivate

type MsgHumanoidDeactivate struct {
	Name string `json:"name"`
}

MsgHumanoidDeactivate deactivates a humanoid (keeps state, stops processing).

type MsgHumanoidDelete

type MsgHumanoidDelete struct {
	Name string `json:"name"`
}

MsgHumanoidDelete tears a humanoid down by name and drops it from the registry — the inverse of MsgHumanoidCreate, driven by `##humanoid stop` (and the dashboard's humanoid_delete command). The skill file on disk is untouched, so the humanoid re-spawns by name.

type MsgHumanoidEmailChanged

type MsgHumanoidEmailChanged struct {
	HumanoidName string `json:"humanoid_name"`
}

MsgHumanoidEmailChanged signals that a humanoid's inbox changed (new mail arrived via IMAP IDLE), so the UI can refresh its listing.

type MsgHumanoidEmailCompose

type MsgHumanoidEmailCompose struct {
	To        string `json:"to"`
	Subject   string `json:"subject"`
	Body      string `json:"body"`
	InReplyTo string `json:"in_reply_to,omitempty"` // MessageID being replied to, for threading
}

MsgHumanoidEmailCompose asks a humanoid to send an email the human wrote themselves (the manual-compose path of the email client, distinct from the AI-draft path). The human authored and sent it, so it needs no separate approval gate: the humanoid stages it as an approved draft and sends it over SMTP. Published to humanoid.<name>.inbox; the humanoid replies on humanoid.<name>.email.compose.

type MsgHumanoidEmailComposeReply

type MsgHumanoidEmailComposeReply struct {
	HumanoidName string `json:"humanoid_name"`
	Ok           bool   `json:"ok"`
	Err          string `json:"err,omitempty"`
}

MsgHumanoidEmailComposeReply reports the outcome of a MsgHumanoidEmailCompose send back toward the UI.

type MsgHumanoidEmailList

type MsgHumanoidEmailList struct {
	Count  int    `json:"count"`  // 0 ⇒ default (10), capped at 50 by the adapter
	Search string `json:"search"` // optional SUBJECT substring filter
}

MsgHumanoidEmailList asks a humanoid to fetch its inbox listing. The humanoid serves it from its EmailAdapter and replies on humanoid.<name>.email.list.

type MsgHumanoidEmailListReply

type MsgHumanoidEmailListReply struct {
	HumanoidName string         `json:"humanoid_name"`
	Emails       []EmailSummary `json:"emails"`
	Err          string         `json:"err,omitempty"`
}

MsgHumanoidEmailListReply carries an inbox listing back toward the UI.

type MsgHumanoidEmailRead

type MsgHumanoidEmailRead struct {
	UID int `json:"uid"`
}

MsgHumanoidEmailRead asks a humanoid to fetch one email's full detail. The reply is published on humanoid.<name>.email.detail.

type MsgHumanoidEmailReadReply

type MsgHumanoidEmailReadReply struct {
	HumanoidName string       `json:"humanoid_name"`
	Email        *EmailDetail `json:"email"`
	Err          string       `json:"err,omitempty"`
}

MsgHumanoidEmailReadReply carries a single email's detail back toward the UI.

type MsgHumanoidGovernanceChanged added in v0.2.3

type MsgHumanoidGovernanceChanged struct {
	Name string `json:"name"`
	Mode string `json:"mode"` // "ai" or "human"
}

MsgHumanoidGovernanceChanged is published by a HumanoidActor to the registry inbox after a runtime governance flip was applied, so the registry can record the new mode in the contacts it persists to KV. Without it the flip silently reverts to the skill-file value on the next restart.

type MsgHumanoidInboundMessage

type MsgHumanoidInboundMessage struct {
	ChannelType string            `json:"channel_type"`
	SenderID    string            `json:"sender_id"`
	SenderName  string            `json:"sender_name"`
	Content     string            `json:"content"`
	ThreadID    string            `json:"thread_id,omitempty"`
	Timestamp   int64             `json:"timestamp"`
	Metadata    map[string]string `json:"metadata,omitempty"`
}

MsgHumanoidInboundMessage arrives from an external channel.

type MsgHumanoidList

type MsgHumanoidList struct{}

MsgHumanoidList requests a list of all humanoids.

type MsgHumanoidListReply

type MsgHumanoidListReply struct {
	Humanoids []HumanoidInfo `json:"humanoids"`
}

MsgHumanoidListReply carries the list of humanoids.

type MsgHumanoidOutboundMessage

type MsgHumanoidOutboundMessage struct {
	ChannelType string `json:"channel_type"`
	RecipientID string `json:"recipient_id"`
	Content     string `json:"content"`
	ThreadID    string `json:"thread_id,omitempty"`
}

MsgHumanoidOutboundMessage is sent back to an external channel.

type MsgHumanoidPrompt

type MsgHumanoidPrompt struct {
	HumanoidName string `json:"humanoid_name"`
	Prompt       string `json:"prompt"`
	SourcePaneID string `json:"source_pane_id"`
	ScopeHint    string `json:"scope_hint,omitempty"` // invoking pane's scope chain (resolved by the workspace)
}

MsgHumanoidPrompt sends a prompt to a named humanoid.

type MsgHumanoidRegisterPane

type MsgHumanoidRegisterPane struct {
	HumanoidName string `json:"humanoid_name"`
	PaneID       string `json:"pane_id"`
	PaneName     string `json:"pane_name"`
	PaneGroupID  string `json:"pane_group_id,omitempty"` // for ephemeral approval panes
}

MsgHumanoidRegisterPane registers a humanoid to output to a specific pane.

type MsgHumanoidReplyModeChanged added in v0.2.3

type MsgHumanoidReplyModeChanged struct {
	Name        string `json:"name"`
	ChannelType string `json:"channel_type"`
	Mode        string `json:"mode"` // "messages" or "mentions"
}

MsgHumanoidReplyModeChanged is the reply-mode analogue of MsgHumanoidGovernanceChanged: it persists a runtime `##humanoid reply-to` flip so a restart rebuilds the channel adapter with the flipped mode.

type MsgHumanoidSetFocus

type MsgHumanoidSetFocus struct {
	Listing   bool   `json:"listing"`
	UID       int    `json:"uid"`
	MessageID string `json:"message_id"`
	ThreadID  string `json:"thread_id"`
	From      string `json:"from"`
	Subject   string `json:"subject"`
	Body      string `json:"body,omitempty"`
}

MsgHumanoidSetFocus tells a humanoid which email the desktop UI currently has open (Listing=false), or that the user is browsing the inbox list with no email open (Listing=true). The humanoid prefers this over lastInbound when enriching the LLM prompt, so "reply to this" acts on the email the user is actually looking at — even an older one. Body is included so the LLM need not re-read the message; MessageID is used as the reply In-Reply-To.

type MsgHumanoidSetGovernance

type MsgHumanoidSetGovernance struct {
	Mode string `json:"mode"` // "ai" or "human"
}

MsgHumanoidSetGovernance changes a humanoid's ai|human governance mode at runtime — it applies to EVERY configured channel (see applyGovernanceMode), not only email.

type MsgHumanoidSetProvider

type MsgHumanoidSetProvider struct {
	Provider string `json:"provider"`        // anthropic | openai | ollama
	Model    string `json:"model,omitempty"` // optional; empty keeps the provider default
}

MsgHumanoidSetProvider overrides a humanoid's model provider at runtime (design 006 §4.3 precedence step 2, openclaw_roadmap R4). Applied on the next executor spawn, matching how MsgHumanoidSetGovernance documents its effect.

type MsgHumanoidSetReplyMode

type MsgHumanoidSetReplyMode struct {
	ChannelType string `json:"channel_type"`
	Mode        string `json:"mode"` // "messages" or "mentions"
}

MsgHumanoidSetReplyMode changes a channel's reply mode at runtime. Mode is "messages" (reply to all channel messages) or "mentions" (reply only when the bot is @mentioned).

type MsgHumanoidStop

type MsgHumanoidStop struct {
	Name string `json:"name"`
}

MsgHumanoidStop interrupts a humanoid's in-flight LLM run by name (used by @@humanoid-name stop). PAUSE semantics: the humanoid stays alive (channel adapters keep running) and its conversation state is preserved — MsgHumanoidContinue (or a new prompt) resumes from the checkpoint. Tearing the humanoid down is a separate operation (MsgHumanoidDelete / ##humanoid stop) — note the two "stop"s differ: @@name stop pauses a run, ##humanoid stop ends the humanoid.

type MsgHumanoidUnregisterPane

type MsgHumanoidUnregisterPane struct {
	HumanoidName string `json:"humanoid_name"`
	PaneID       string `json:"pane_id"`
}

MsgHumanoidUnregisterPane removes a humanoid's pane registration.

type MsgHumanoidWhatsAppChanged

type MsgHumanoidWhatsAppChanged struct {
	HumanoidName string `json:"humanoid_name"`
}

MsgHumanoidWhatsAppChanged signals that a humanoid received a new WhatsApp message, so the UI can refresh its listing.

type MsgHumanoidWhatsAppList

type MsgHumanoidWhatsAppList struct {
	Count int `json:"count"` // 0 ⇒ default (50), capped by the adapter store
}

MsgHumanoidWhatsAppList asks a humanoid to return its recent received messages. The humanoid serves it from its WhatsAppAdapter store and replies on humanoid.<name>.whatsapp.list.

type MsgHumanoidWhatsAppListReply

type MsgHumanoidWhatsAppListReply struct {
	HumanoidName string               `json:"humanoid_name"`
	Messages     []WhatsAppMsgSummary `json:"messages"`
	Err          string               `json:"err,omitempty"`
}

MsgHumanoidWhatsAppListReply carries the recent-message listing back to the UI.

type MsgHumanoidWhatsAppRead

type MsgHumanoidWhatsAppRead struct {
	ID string `json:"id"`
}

MsgHumanoidWhatsAppRead asks a humanoid for one message's full detail by its short ID. The reply is published on humanoid.<name>.whatsapp.detail.

type MsgHumanoidWhatsAppReadReply

type MsgHumanoidWhatsAppReadReply struct {
	HumanoidName string             `json:"humanoid_name"`
	Message      *WhatsAppMsgDetail `json:"message"`
	Err          string             `json:"err,omitempty"`
}

MsgHumanoidWhatsAppReadReply carries one message's detail back to the UI.

type MsgLLMPickerOpen added in v0.2.3

type MsgLLMPickerOpen struct {
	PaneID string `json:"pane_id"`
	// Models are the activatable rows, in the same order the numbered menu
	// prints them, so a user reading either sees one list.
	Models []LLMPickerModel `json:"models"`
	// Blocked are the registry entries that cannot be activated, each already
	// rendered with its reason. Shown but never selectable.
	Blocked []string `json:"blocked,omitempty"`
	// Scopes are the binding levels offered in step two, broadest first. The
	// daemon sends them rather than the front-end hard-coding the hierarchy.
	Scopes []LLMPickerScope `json:"scopes"`
	// InEffect labels the model serving this pane right now, for the header.
	InEffect string `json:"in_effect,omitempty"`
}

MsgLLMPickerOpen asks the front-end to open the interactive model picker.

type MsgLaneActivePaneReply

type MsgLaneActivePaneReply struct {
	PaneID    string `json:"pane_id"`
	PaneCount int    `json:"pane_count"`
}

MsgLaneActivePaneReply carries the LaneActor's active pane info.

type MsgLaneCloseActivePane

type MsgLaneCloseActivePane struct{}

MsgLaneCloseActivePane closes the active (top) pane in the active pane group. If the group has only one pane, this is a no-op (use MsgLaneClosePaneGroup instead).

type MsgLaneClosePaneGroup

type MsgLaneClosePaneGroup struct{}

MsgLaneClosePaneGroup closes the active pane group in the lane.

type MsgLaneCreatePaneGroup

type MsgLaneCreatePaneGroup struct {
	PaneID     string `json:"pane_id"`
	Title      string `json:"title"`
	GroupID    string `json:"group_id,omitempty"`
	WorkingDir string `json:"working_dir,omitempty"`
	// PaneType marks the initial pane as a special variant ("replay" panes
	// never start a shell/PTY). Empty = normal pane. See
	// MsgTabCreatePaneGroupInLane.
	PaneType string `json:"pane_type,omitempty"`

	// Meta is metadata the initial pane is born with. See
	// MsgTabCreatePaneGroupInLane.Meta for why birth rather than a follow-up
	// message.
	Meta map[string]string `json:"meta,omitempty"`
}

MsgLaneCreatePaneGroup creates a new pane group in the lane.

GroupID, when set, pre-assigns the new group's ID (normally generated by the lane) so the sender can reference the group it asked for — the worktree lifecycle (design 008) records which group runs in which worktree at create time. WorkingDir, when set, is the directory the group's panes start their shells in, overriding the lane's inherited config. Both are carried at birth rather than sent as a follow-up MsgSetWorkingDir, which would race the group's initial pane creation.

type MsgLaneCreateStackedPane

type MsgLaneCreateStackedPane struct {
	PaneID string `json:"pane_id"`
	Title  string `json:"title"`
}

MsgLaneCreateStackedPane creates a new stacked pane in the active group.

type MsgLaneCreateStackedPaneInGroup

type MsgLaneCreateStackedPaneInGroup struct {
	PaneGroupID string `json:"pane_group_id"`
	PaneID      string `json:"pane_id"`
	Title       string `json:"title"`
}

MsgLaneCreateStackedPaneInGroup creates a stacked pane in a specific group.

type MsgLaneDeletePaneGroup

type MsgLaneDeletePaneGroup struct {
	PaneGroupID string `json:"pane_group_id"`
}

MsgLaneDeletePaneGroup deletes a specific pane group by ID within a lane.

type MsgLaneEqualizeGroups

type MsgLaneEqualizeGroups struct{}

MsgLaneEqualizeGroups resets all group rowFlex weights in the lane to equal.

type MsgLaneFocusGroup

type MsgLaneFocusGroup struct {
	Direction Direction `json:"direction"`
}

MsgLaneFocusGroup moves focus up or down within the lane. Replaces MsgLaneFocusGroupUp and MsgLaneFocusGroupDown.

type MsgLaneFocusPaneByID

type MsgLaneFocusPaneByID struct {
	ID string `json:"id"`
}

MsgLaneFocusPaneByID makes the pane group containing the given pane the active group within the lane, so the lane snapshot reports it as active.

type MsgLaneResizeGroupHeight

type MsgLaneResizeGroupHeight struct {
	Delta int `json:"delta"`
}

MsgLaneResizeGroupHeight adjusts the height of the active pane group within the lane. Delta > 0 grows the group; Delta < 0 shrinks it.

type MsgLaneSetPaneHidden added in v0.2.6

type MsgLaneSetPaneHidden struct {
	PaneID string `json:"pane_id"`
	Hidden bool   `json:"hidden"`
}

MsgLaneSetPaneHidden forwards a hide/reveal to the group holding the pane.

type MsgLaneSnapshotReply

type MsgLaneSnapshotReply struct {
	Snapshot domain.LaneSnapshot `json:"snapshot"`
}

MsgLaneSnapshotReply carries the lane snapshot reply.

type MsgLaneStackedPane

type MsgLaneStackedPane struct {
	Direction Direction `json:"direction"`
}

MsgLaneStackedPane cycles the stack in the specified direction in the active group. Replaces MsgLaneStackedPaneNext and MsgLaneStackedPanePrev.

type MsgLaneStackedPaneMove

type MsgLaneStackedPaneMove struct {
	Direction Direction `json:"direction"`
}

MsgLaneStackedPaneMove reorders the active pane within the active group's stack.

type MsgLaneStackedPaneSelect

type MsgLaneStackedPaneSelect struct {
	Index int `json:"index"`
}

MsgLaneStackedPaneSelect activates the stacked pane at the given 0-based index in the active group.

type MsgLaunchClaudeInPane added in v0.2.3

type MsgLaunchClaudeInPane struct {
	Alias      string `json:"alias"`
	SessionID  string `json:"session_id"`
	PromptFile string `json:"prompt_file,omitempty"`
	Retries    int    `json:"retries"`

	// GivenName and Hidden are applied once the pane resolves, by the same
	// retry loop that starts claude (design 027 §5.5). They ride along rather
	// than getting a loop of their own because they have the identical problem:
	// the pane is created by a message travelling down the actor hierarchy, so
	// its id does not exist when the command returns. A second timer would be a
	// second thing to get wrong, and the board claude needs BOTH — a pane that
	// comes up named but visible puts an agent on everyone's screen, and one
	// that comes up hidden but unnamed cannot be found again.
	GivenName string `json:"given_name,omitempty"`
	Hidden    bool   `json:"hidden,omitempty"`

	// ExtraArgs are appended to the claude invocation verbatim. Empty for
	// `##pane new --claude`, where a human is watching the pane and manual
	// approval is the safe default. The BOARD CLAUDE sets it, because an agent
	// whose entire job is to act on the fleet cannot do that job while it is
	// asking a human to approve each action — and its pane is hidden, so there
	// is nobody there to ask.
	ExtraArgs string `json:"extra_args,omitempty"`
}

MsgLaunchClaudeInPane is the workspace's note to itself to finish a `##pane new --claude`: the pane is created by a message that travels down the actor hierarchy, so its id does not exist yet when the command returns — only the alias does. Retries counts down; each hop is one more chance for the pane to have appeared.

type MsgLayoutDirty

type MsgLayoutDirty struct{}

MsgLayoutDirty is a lightweight notification published by the WorkspaceActor to rysh.ws.layoutDirty whenever the workspace structure/layout changes (tab/pane create/close, focus, resize, stack rotate, rename — anything that calls persistToKV). The TUI subscribes to it and triggers a coalesced (~16 ms) layout-only snapshot refresh instead of polling on a blind timer. It carries no payload; the TUI re-reads the layout tree on receipt.

type MsgMCPStatus

type MsgMCPStatus struct {
	Server  string `json:"server"`
	Phase   string `json:"phase"`             // connected|reconnecting|given_up|disconnected|removed
	Attempt int    `json:"attempt,omitempty"` // 1-based reconnect attempt when reconnecting/given_up
	Max     int    `json:"max,omitempty"`     // MaxRestartAttemptsPerSession at emit time
	Detail  string `json:"detail,omitempty"`  // tool count / last error / retry delay
}

MsgMCPStatus reports a live state transition for one MCP server. It is published session-globally to T("mcp","status") on every transition (connected / reconnecting / given_up / disconnected / removed) by the MCP manager's StatusEmitter, so the TUI footer can surface restart progress without polling the manager. Follow-up 6b.

type MsgMemoryAppend

type MsgMemoryAppend = sharedmsg.MsgMemoryAppend

type MsgMemoryGet

type MsgMemoryGet = sharedmsg.MsgMemoryGet

type MsgMemoryGetReply

type MsgMemoryGetReply = sharedmsg.MsgMemoryGetReply

type MsgMemoryStateUpdate

type MsgMemoryStateUpdate = sharedmsg.MsgMemoryStateUpdate

type MsgMemorySummarize

type MsgMemorySummarize = sharedmsg.MsgMemorySummarize

type MsgMemorySummarizeDone

type MsgMemorySummarizeDone = sharedmsg.MsgMemorySummarizeDone

type MsgMirrorDirty

type MsgMirrorDirty struct {
	ShareID string `json:"share_id,omitempty"`
}

MsgMirrorDirty is a lightweight notification published by the WorkspaceActor to rysh.ws.mirrorDirty whenever a mirror tab's structure or per-pane VT content changes (a layout doc or a raw VT frame was applied). The TUI subscribes to it and triggers a coalesced (~16 ms) snapshot refresh so a mirrored (shared) tab repaints on arrival instead of waiting for the 250 ms render tick. It carries no payload beyond the share id (used only for logging / future filtering); the TUI re-reads the full snapshot regardless.

type MsgMirrorMaximizePane

type MsgMirrorMaximizePane struct {
	On   bool `json:"on"`
	Rows int  `json:"rows,omitempty"`
	Cols int  `json:"cols,omitempty"`
}

MsgMirrorMaximizePane is sent by the subscriber TUI to the WorkspaceActor when the user (un)fullscreens a pane while viewing a shared (mirror) tab. The WorkspaceActor forwards it to the source as a "maximize" control command on the subscriber's focused source pane, so the source maximizes that pane too and its PTY-backed app re-renders at full size. On=false restores the source layout.

Rows/Cols carry the SUBSCRIBER's own fullscreen content dimensions (the PTY size its maximized pane occupies). The source sizes the shared pane's PTY to these dims rather than its own full body, so a subscriber with a larger terminal than the source gets a true full-resolution render at its own screen size instead of one capped at the source's screen. Zero when On=false (restore).

type MsgMirrorPaneVTFrame

type MsgMirrorPaneVTFrame struct {
	PaneID      string        `json:"pane_id"`
	Seq         uint64        `json:"seq"`                // monotonic per pane
	BaseSeq     uint64        `json:"base_seq,omitempty"` // delta applies onto this seq; 0 for keyframe
	Interactive bool          `json:"interactive"`        // false = pane left interactive mode
	Rows        int           `json:"rows"`               // total screen rows (authoritative size)
	Full        []string      `json:"full,omitempty"`     // keyframe: entire screen
	Changed     []VTLineDelta `json:"changed,omitempty"`  // delta: only changed rows
	CursorRow   int           `json:"cursor_row"`
	CursorCol   int           `json:"cursor_col"`
}

MsgMirrorPaneVTFrame is PUSHED on rysh.pane.{mirrorID}.vtframe on every VT change of an interactive mirror pane — no request/reply, no WorkspaceActor mailbox hop. It is keyframe+delta and sequence-numbered for loss recovery:

  • keyframe: Full carries the entire screen, BaseSeq == 0.
  • delta: Changed carries only the changed rows, BaseSeq == the seq the delta applies onto (the receiver's last applied seq must match).

A receiver tracks the last applied Seq per pane; a gap (BaseSeq != lastSeq) is healed by a resync pull (MsgGetMirrorPaneVT, served as a keyframe). A non-interactive frame (Interactive == false) tells the receiver to drop its delta state — the source pane left interactive mode.

type MsgMirrorPaneVTReply

type MsgMirrorPaneVTReply struct {
	PaneID      string   `json:"pane_id"`
	Interactive bool     `json:"interactive"`
	Screen      []string `json:"screen,omitempty"`
	CursorRow   int      `json:"cursor_row"`
	CursorCol   int      `json:"cursor_col"`
	Seq         uint64   `json:"seq"`
}

MsgMirrorPaneVTReply carries one mirror pane's live VT frame. Interactive is false when the pane has no VT state (left interactive mode or unknown id). Seq is the current sequence the push stream (rysh.pane.{mirrorID}.vtframe) is at, so a backfill/resync reply (treated as a keyframe) carries the seq the subsequent deltas will build onto — deltas with BaseSeq == this Seq apply.

type MsgMirrorScrollbackReply

type MsgMirrorScrollbackReply struct {
	Rows []string `json:"rows,omitempty"`
}

MsgMirrorScrollbackReply carries the mirror pane's scrollback + screen rows.

type MsgMirrorTabOp

type MsgMirrorTabOp struct {
	TabID  string `json:"tab_id"`
	PaneID string `json:"pane_id"`
	Op     string `json:"op"`
	Dir    string `json:"dir,omitempty"`
	Delta  int    `json:"delta,omitempty"`
	Name   string `json:"name,omitempty"`
}

MsgMirrorTabOp applies a structural operation relayed from a mirror-tab subscriber to a specific source tab + pane. The source's UpstreamShareActor publishes this to the workspace inbox so the WorkspaceActor can generate a unique alias (for create ops) and target the shared tab by ID. Op is one of create_pane | create_pane_down | create_stacked | close_pane | stack_rotate | stack_move | resize | resize_height | rename_pane. For rename_pane, Name holds the new given-name to assign to the target pane.

type MsgMoveTab

type MsgMoveTab struct {
	Direction Direction `json:"direction"`
}

MsgMoveTab reorders the active tab. DirLeft moves it one position toward the start of the tab list; DirRight moves it one position toward the end. The moved tab stays active; it is a no-op at the edges.

type MsgOrchestratorDone

type MsgOrchestratorDone = sharedmsg.MsgOrchestratorDone

type MsgPaneAIHistoryAppend

type MsgPaneAIHistoryAppend = sharedmsg.MsgPaneAIHistoryAppend

type MsgPaneAIOutputAppend

type MsgPaneAIOutputAppend = sharedmsg.MsgPaneAIOutputAppend

type MsgPaneActivateMode

type MsgPaneActivateMode struct {
	PaneID string `json:"pane_id"`
	Mode   string `json:"mode"`
}

MsgPaneActivateMode tells stream clients (the TUI, the desktop app) to switch a pane's VISIBLE input mode now — the deterministic backend→frontend "show this mode" push that the frontend's local mode cycle otherwise never receives. Used when a humanoid registers its output pane (##humanoid register-output) so the pane flips to the humanoid's surface (the "email" client, or "external") instead of silently filling a buffer the user must cycle to by hand. Published on pane.<id>.activateMode. Mode must be one of the pane's enabled modes.

type MsgPaneChatHistoryAppend

type MsgPaneChatHistoryAppend = sharedmsg.MsgPaneChatHistoryAppend

type MsgPaneChatOutputAppend

type MsgPaneChatOutputAppend = sharedmsg.MsgPaneChatOutputAppend

type MsgPaneClearOutput

type MsgPaneClearOutput struct {
	PaneID string `json:"pane_id"`
}

MsgPaneClearOutput clears a pane's display output buffers without recording anything in command history — the readline Ctrl+L gesture in shell mode (typing `clear` still goes through the shell and IS recorded, like bash). Published directly to rysh.pane.{paneID}.inbox.

type MsgPaneDisableMode

type MsgPaneDisableMode struct {
	PaneID string `json:"pane_id"`
	// Humanoid marks the removal of a dynamic per-humanoid mode.
	Humanoid bool   `json:"humanoid,omitempty"`
	Mode     string `json:"mode"`
}

MsgPaneDisableMode disables (removes) an input mode from a pane's cycle. "shell" cannot be disabled. Routed: WorkspaceActor → PaneActor (direct, pass-through).

type MsgPaneEnableMode

type MsgPaneEnableMode struct {
	PaneID     string `json:"pane_id"`
	Mode       string `json:"mode"` // canonical: shell|prompt|rysh|chat|external|web
	WebProfile string `json:"web_profile,omitempty"`
	WebURL     string `json:"web_url,omitempty"`
	// Humanoid marks a dynamic per-humanoid mode registration (Mode is the
	// humanoid name). Only then is a non-fixed mode name accepted; ##mode keeps
	// the strict fixed-mode set.
	Humanoid bool `json:"humanoid,omitempty"`
}

MsgPaneEnableMode enables (adds) an input mode to a pane's cycle. Idempotent. For Mode=="web", WebProfile/WebURL carry the browser binding and are refreshed on re-enable. Routed: WorkspaceActor → PaneActor (direct, pass-through).

type MsgPaneExecChat

type MsgPaneExecChat struct {
	Message    string `json:"message"`
	SenderName string `json:"sender_name,omitempty"`
}

MsgPaneExecChat sends a chat message to the pane's agentic actor. SenderName, when non-empty, overrides the receiving pane's own profile name so that remote-originated chat messages show the sender's identity rather than the host pane's profile.

type MsgPaneExecPrompt

type MsgPaneExecPrompt struct {
	Prompt string `json:"prompt"`
}

MsgPaneExecPrompt sends a prompt to the LLMActor. PaneActor forwards this to rysh.pane.{paneID}.llm.inbox as MsgExecPrompt.

type MsgPaneExecRysh

type MsgPaneExecRysh struct {
	Command string `json:"command"`
}

MsgPaneExecRysh records a rysh system command in the pane's rysh history.

type MsgPaneExecShell

type MsgPaneExecShell struct {
	Command string `json:"command"`
}

MsgPaneExecShell executes a shell command in the pane's PTY.

type MsgPaneExternalHistoryAppend

type MsgPaneExternalHistoryAppend = sharedmsg.MsgPaneExternalHistoryAppend

type MsgPaneExternalOutputAppend

type MsgPaneExternalOutputAppend = sharedmsg.MsgPaneExternalOutputAppend

type MsgPaneGroupActivePaneReply

type MsgPaneGroupActivePaneReply struct {
	PaneID    string `json:"pane_id"`
	PaneCount int    `json:"pane_count"`
}

MsgPaneGroupActivePaneReply carries the PaneGroupActor's active pane info.

type MsgPaneGroupCreateStackedPane

type MsgPaneGroupCreateStackedPane struct {
	PaneID string `json:"pane_id"`
	Title  string `json:"title"`
}

MsgPaneGroupCreateStackedPane creates a new stacked pane in this group.

type MsgPaneGroupDeletePane

type MsgPaneGroupDeletePane struct {
	PaneID string `json:"pane_id"`
}

MsgPaneGroupDeletePane deletes a specific pane by ID within a group.

type MsgPaneGroupFocusPaneByID

type MsgPaneGroupFocusPaneByID struct {
	PaneID string `json:"pane_id"`
}

MsgPaneGroupFocusPaneByID makes the identified member pane this group's active (expanded) stack pane. Sent by the lane's focus-by-id path so the GROUP's notion of its active pane follows workspace focus: without it, focusing a background stacked pane (mouse click on its title bar, focus restore) updated tab/lane bookkeeping only — the group kept the old pane expanded while input routed to the newly-"active" invisible one.

type MsgPaneGroupSetPaneHidden added in v0.2.6

type MsgPaneGroupSetPaneHidden struct {
	PaneID string `json:"pane_id"`
	Hidden bool   `json:"hidden"`
}

MsgPaneGroupSetPaneHidden hides or reveals one pane of this group, moving focus off it first when it is the active one (design 027 §5.1).

type MsgPaneGroupSnapshotReply

type MsgPaneGroupSnapshotReply struct {
	Snapshot domain.PaneGroupSnapshot `json:"snapshot"`
}

MsgPaneGroupSnapshotReply carries the pane group snapshot reply.

type MsgPaneGroupStackedPane

type MsgPaneGroupStackedPane struct {
	Direction Direction `json:"direction"`
}

MsgPaneGroupStackedPane cycles the stack in the specified direction. Replaces MsgPaneGroupStackedPaneNext and MsgPaneGroupStackedPanePrev.

type MsgPaneGroupStackedPaneMove

type MsgPaneGroupStackedPaneMove struct {
	Direction Direction `json:"direction"`
}

MsgPaneGroupStackedPaneMove reorders the active pane within this group's stack. DirUp moves it toward the front (index 0); DirDown moves it toward the back.

type MsgPaneGroupStackedPaneSelect

type MsgPaneGroupStackedPaneSelect struct {
	Index int `json:"index"`
}

MsgPaneGroupStackedPaneSelect activates the stacked pane at the given 0-based index within this group. Out-of-range indices are ignored.

type MsgPaneHistoryAppend

type MsgPaneHistoryAppend = sharedmsg.MsgPaneHistoryAppend

type MsgPaneHopClear

type MsgPaneHopClear struct{}

MsgPaneHopClear clears the stored hopped content from a pane.

type MsgPaneHopContent

type MsgPaneHopContent struct {
	SourcePaneID string `json:"source_pane_id"`
	SourceAlias  string `json:"source_alias"`
	Content      string `json:"content"`
	ChatContent  string `json:"chat_content,omitempty"`
	// MemoryTurns is the number of LLM conversation turns forked into the
	// target's session memory alongside this content (0 = terminal text
	// only, no memory fork). The fork itself travels separately as
	// MsgSessionMemoryReplace to the target's LLM-execution actor; this
	// count lets the pane render status and pick the native resume prompt.
	MemoryTurns int `json:"memory_turns,omitempty"`
}

MsgPaneHopContent delivers hopped content from a source pane to a target pane.

type MsgPaneHopResume

type MsgPaneHopResume struct{}

MsgPaneHopResume triggers the AI prompt with the stored hopped content. When the hop also forked session memory, the prompt continues the forked conversation natively instead of dumping the copied text.

type MsgPaneImportCookies

type MsgPaneImportCookies struct {
	Profile string         `json:"profile"`
	Cookies []ImportCookie `json:"cookies"`
}

MsgPaneImportCookies carries cookies pulled from a profile's real-Chrome login jar (`##web import-google-session`) to the desktop app, which writes them into persist:<profile> so web panes on that profile become authenticated — e.g. a Google session so third-party "Sign in with Google" completes without Google's embedded-browser block. Routed: WorkspaceActor → web server → app. Profile-global (not pane-scoped).

type MsgPaneKillForeground added in v0.2.6

type MsgPaneKillForeground struct {
	PaneID string `json:"pane_id"`
	Hard   bool   `json:"hard,omitempty"`
}

MsgPaneKillForeground asks a pane to SIGNAL its foreground process group — the hard stop, founder ruling 2026-08-11 reversing 027 ruling 3 for the fleet-stop verb. ESC ends a turn but cannot cancel a pending task-notification, so an interrupted agent with a background task wakes itself (F-41); a dead process cannot be woken, and its absence is verifiable by process state rather than by reading a screen. The claude session is resumable afterwards: the session id is pinned at launch, so `claude --resume <id>` restores the conversation.

Hard=false sends SIGTERM to the group; Hard=true sends SIGKILL. The caller escalates only after verifying the group survived — policy lives in the router, where it is testable.

type MsgPaneNativeMode

type MsgPaneNativeMode struct {
	PaneID string `json:"pane_id"`
	Action string `json:"action"`
}

MsgPaneNativeMode switches a pane's native pass-through shell mode (##native): the pane renders its VT screen permanently and every keystroke goes straight to the PTY — bash owns readline, completion, history and PS1. Action is "on", "off", or "toggle". Sent to rysh.pane.{paneID}.inbox by the ##native command (workspace) and by the TUI's double-Esc exit gesture.

type MsgPaneOutputAppend

type MsgPaneOutputAppend = sharedmsg.MsgPaneOutputAppend

type MsgPaneProcess added in v0.2.3

type MsgPaneProcess struct {
	PaneID string `json:"pane_id"`
	// Event is "start" (a program took the terminal) or "exit" (it gave it
	// back). There is no exit status: the foreground process group is gone by
	// the time we notice, and inventing a status would be worse than omitting
	// one.
	Event   string `json:"event"`
	Program string `json:"program,omitempty"`
	PGID    int    `json:"pgid,omitempty"`
	// UnixMilli rather than time.Time: this crosses the wire as JSON and is
	// consumed by non-Go readers (the fan-out helper is Python).
	At int64 `json:"at"`
}

MsgPaneProcess announces a change in a pane's FOREGROUND program: a command started, or the pane fell back to its shell. Published to rysh.pane.{paneID}.process.

It exists so a supervisor can wait for an event instead of polling a pane's rendered screen. Screen-scraping is how you end up matching on a TUI's footer text ("esc to interrupt") and calling a program finished because it redrew.

Program is the executable name (Linux; empty where it cannot be resolved) and is empty for the shell — "back to the prompt" is the end of a run, and naming the shell would make every exit look like a new program starting.

type MsgPaneRawDirty

type MsgPaneRawDirty struct {
	PaneID string `json:"pane_id"`
}

MsgPaneRawDirty is a fire-and-forget notification that a pane's raw VT state (local p.VTScreen or remote p.RemoteVTScreen) has changed and the TUI should refresh that pane's full content snapshot. Published to rysh.pane.{paneID}.rawDirty at the source-side raw-publish coalesce cadence (~16ms for local raw panes) and at the remote-share listener's render cadence (~33ms for remote raw panes). Replaces the TUI's fixed 50ms wholesale poll of every visible raw pane with push-driven, per-change fetches — idle raw panes incur zero TUI work, and busy panes are still bounded by the listener throttle. The payload is intentionally just the pane id so the message is tiny and cheap to flood.

type MsgPaneRawOutputAppend

type MsgPaneRawOutputAppend struct {
	PaneID string `json:"pane_id"`
	Data   string `json:"data"` // base64-encoded raw PTY bytes
}

MsgPaneRawOutputAppend carries raw PTY bytes (with ANSI sequences intact) for the sharing pipeline during interactive mode. Data is base64-encoded. Published to rysh.pane.{paneID}.rawOutput by rawReadLoop.

type MsgPaneRegisterForgedProxies

type MsgPaneRegisterForgedProxies struct {
	ShareID string         `json:"share_id"`
	Ops     []ForgedOpSpec `json:"ops"`
}

MsgPaneRegisterForgedProxies tells a subscriber pane to (re)register inert forged-API proxies for a remote share's operations (phase 2a — the proxies are visible to the pane's agent but return "not yet enabled" when called).

type MsgPaneReleaseSize added in v0.2.3

type MsgPaneReleaseSize struct {
	ClientID string `json:"client_id"`
}

MsgPaneReleaseSize withdraws a viewport's size claim: the pane drops it and re-sizes to the smallest of whatever remains (or leaves the PTY alone when nothing is left).

Only clients whose liveness the pane cannot check for itself need to send this. A "tui:<pid>" claim is pruned by a process-liveness check, so a terminal UI never has to; the web hub does send it, because a closed WebSocket leaves no trace the PaneActor could test.

type MsgPaneReplayShareState

type MsgPaneReplayShareState struct {
	PaneID string `json:"pane_id"`
}

MsgPaneReplayShareState asks a PaneActor to re-publish its current interactive share state — a MsgPaneShareModeChange followed by a full-screen repaint — so a newly (re)joined share subscriber resumes rendering the live interactive app instead of seeing stale scrollback/history. Share output over NATS is ephemeral pub/sub, so a subscriber that joins after the app entered interactive mode never received the original mode/raw messages; this re-sends them on demand. Sent by UpstreamShareActor on a subscriber-join notification. It is a no-op when the pane is not currently interactive.

type MsgPaneResize

type MsgPaneResize struct {
	Rows int `json:"rows"`
	Cols int `json:"cols"`
	// ClientID names the viewport this claim belongs to, so one viewport's
	// later claim replaces its own earlier one rather than adding to the set.
	// Terminal UIs use "tui:<pid>" — the pane prunes those by checking whether
	// the process is still alive, which covers a clean exit and a crash alike.
	// Web/desktop-app clients use "web:<n>" and are released explicitly by the
	// hub on disconnect. Empty is a legacy/anonymous client and behaves as a
	// single shared claim.
	ClientID string `json:"client_id,omitempty"`
	// Override applies these dimensions to the PTY directly, bypassing
	// arbitration and leaving the claim set untouched. It is for deliberate
	// one-off sizing that is not a viewport measurement — a remote share
	// subscriber asking the source to render at ITS resolution, which is a
	// choice the source honours rather than a constraint to intersect.
	Override bool `json:"override,omitempty"`
}

MsgPaneResize tells the PaneActor how much room a viewport gives this pane.

A pane has ONE PTY but can be on screen in several places at once — a terminal UI and a desktop-app window attached to the same daemon, or two of either. Each renders the pane at its own size, and each used to send this message as a command that won last-writer-wins, so two attached front-ends fought over pty.Setsize and an interactive app reflowed on every frame.

So this is a CLAIM, not a command: the pane records each viewport's size and sizes its PTY to the smallest one (see PaneActor.applyEffectivePaneSize), so the rendered grid fits inside every viewport showing it. A larger viewport letterboxes; a smaller one would otherwise have to truncate or wrap, which corrupts the display of a full-screen app.

type MsgPaneResized

type MsgPaneResized struct {
	PaneID string `json:"pane_id"`
	Rows   int    `json:"rows"`
	Cols   int    `json:"cols"`
}

MsgPaneResized is published by PaneActor after a resize to notify interested local subscribers (e.g. RemoteShareListenerActor) of the new dimensions. Published to rysh.pane.{paneID}.resized.

type MsgPaneRyshHistoryAppend

type MsgPaneRyshHistoryAppend = sharedmsg.MsgPaneRyshHistoryAppend

type MsgPaneRyshOutputAppend

type MsgPaneRyshOutputAppend = sharedmsg.MsgPaneRyshOutputAppend

type MsgPaneScrollbackDeltaReply

type MsgPaneScrollbackDeltaReply struct {
	Evicted int64    `json:"evicted"`
	Rows    []string `json:"rows,omitempty"`
}

MsgPaneScrollbackDeltaReply carries the incremental scrollback rows and the pane's current monotonic evicted total (use as the next Since).

type MsgPaneScrollbackReply

type MsgPaneScrollbackReply struct {
	Rows []string `json:"rows,omitempty"`
}

MsgPaneScrollbackReply carries the rendered scrollback + current-screen rows.

type MsgPaneSetApprovalPaneGroups

type MsgPaneSetApprovalPaneGroups = sharedmsg.MsgPaneSetApprovalPaneGroups

type MsgPaneSetGivenName

type MsgPaneSetGivenName struct {
	Name string `json:"name"`
}

MsgPaneSetGivenName sets the user-assigned given-name for a pane.

type MsgPaneSetHidden added in v0.2.6

type MsgPaneSetHidden struct {
	Hidden bool `json:"hidden"`
}

MsgPaneSetHidden takes a pane off screen, or puts it back (design 027 §5.1).

Rendering only. The pane keeps its PTY and its program, and stays addressable by ANSA and by every `##pane` command — this is not a way to stop a pane, and anything that treats it as one is a bug.

type MsgPaneSetHumanoid

type MsgPaneSetHumanoid struct {
	HumanoidName string `json:"humanoid_name"`
}

MsgPaneSetHumanoid notifies a pane that it is registered to a humanoid.

type MsgPaneSetMeta added in v0.2.3

type MsgPaneSetMeta struct {
	Key   string `json:"key"`
	Value string `json:"value,omitempty"`
}

MsgPaneSetMeta writes one entry of a pane's metadata map (`##pane meta`). An empty Value deletes the key — the same convention as an env var, and it keeps deletion from needing a second message type.

Metadata is for whoever is DRIVING the pane: a supervisor recording the session id of the claude it launched, the task a pane was opened for, which process spawned it. rysh itself never interprets it. It rides the pane's KV snapshot, so it survives a daemon restart and is readable by any tool that can ask for a snapshot — which is the point. A sidecar file in one tool's private directory is invisible to every other tool and to the pane list.

type MsgPaneSetProvider

type MsgPaneSetProvider struct {
	Provider string `json:"provider"`
	Model    string `json:"model,omitempty"`
	// Scope names the level of the model hierarchy this selection came from
	// (session > workspace > tab > lane > stack > pane). "" or "pane" is the
	// pane's OWN choice: it is persisted in the pane's KV record and outranks
	// everything above. Any broader value is INHERITED — applied live but
	// never persisted, because the workspace recomputes it from the binding of
	// the nearest enclosing scope. Keeping the two in separate slots is what
	// stops a lane-wide re-bind from clobbering a pane that chose for itself.
	Scope string `json:"scope,omitempty"`
}

MsgPaneSetProvider sets or clears the pane's runtime provider override (`##pane provider` — design 002 §3.4). Empty Provider clears the override. Applied to the pane's next agentic prompt.

type MsgPaneSetRemoteSubscriber

type MsgPaneSetRemoteSubscriber struct {
	Subscriber bool `json:"subscriber"`
}

MsgPaneSetRemoteSubscriber marks (or unmarks) a pane as the local owner pane of a remote-share subscription (##upstream subscribe). Sent from RemoteShareListenerActor on start/stop. While set, the pane folds non-shell remote modes (chat/rysh/external) into its merged display buffer as well, so a passively-viewing subscriber sees that output in the default view without having to manually switch the pane's input mode. shell/ai already merge.

type MsgPaneSetShareRestrictions

type MsgPaneSetShareRestrictions struct {
	Restrictions ShareRestrictions `json:"restrictions"`
}

MsgPaneSetShareRestrictions propagates restrictions from a remote share owner to a local pane in controller mode (via RemoteShareListenerActor).

type MsgPaneSetSharingState

type MsgPaneSetSharingState struct {
	Sharing bool   `json:"sharing"`
	URL     string `json:"url,omitempty"`
	ShareID string `json:"share_id,omitempty"`
}

MsgPaneSetSharingState tells a PaneActor to update its upstream sharing state. Sent by UpstreamShareActor when it connects or disconnects from the upstream server, so PaneActor.sharing correctly reflects the ShareRegistry-based sharing mechanism (##share pane) in addition to the legacy MsgPaneShareStart path.

type MsgPaneSetTitle

type MsgPaneSetTitle struct {
	Title string `json:"title"`
}

MsgPaneSetTitle renames the pane.

type MsgPaneShareModeChange

type MsgPaneShareModeChange struct {
	PaneID      string `json:"pane_id"`
	Interactive bool   `json:"interactive"`
	Rows        int    `json:"rows"`
	Cols        int    `json:"cols"`
}

MsgPaneShareModeChange signals an interactive mode transition for sharing. Published to rysh.pane.{paneID}.shareMode on interactive enter/exit and PTY resize.

type MsgPaneShareStart

type MsgPaneShareStart struct{}

MsgPaneShareStart tells a PaneActor to start sharing to the remote upstream.

type MsgPaneShareStatus

type MsgPaneShareStatus struct{}

MsgPaneShareStatus requests the sharing status of a PaneActor.

type MsgPaneShareStatusReply

type MsgPaneShareStatusReply struct {
	Sharing   bool   `json:"sharing"`
	URL       string `json:"url"`
	Connected bool   `json:"connected"`
}

MsgPaneShareStatusReply carries the sharing status of a PaneActor.

type MsgPaneShareStop

type MsgPaneShareStop struct{}

MsgPaneShareStop tells a PaneActor to stop sharing to the remote upstream.

type MsgPaneShellHistoryAppend

type MsgPaneShellHistoryAppend = sharedmsg.MsgPaneShellHistoryAppend

type MsgPaneShellOutputAppend

type MsgPaneShellOutputAppend = sharedmsg.MsgPaneShellOutputAppend

type MsgPaneSnapshotReply

type MsgPaneSnapshotReply struct {
	Snapshot domain.PaneSnapshot `json:"snapshot"`
}

MsgPaneSnapshotReply carries the pane snapshot reply.

type MsgPaneStatusUpdate

type MsgPaneStatusUpdate = sharedmsg.MsgPaneStatusUpdate

type MsgPaneStop

type MsgPaneStop struct{}

MsgPaneStop signals the pane to stop itself.

type MsgPaneStopped

type MsgPaneStopped struct {
	PaneID string `json:"pane_id"`
}

MsgPaneStopped announces that a pane actor has fully stopped (any close path: keyboard close, group/lane/tab cascade, CLI delete). Published to ws.inbox from the PaneActor's Stopping hook so the workspace can release per-pane resources it holds — today: stopping an in-flight replay playback when its dedicated replay pane closes (design 006 v2).

type MsgPaneSubmitInput

type MsgPaneSubmitInput struct {
	Text          string                  `json:"text"`
	Mode          string                  `json:"mode"`
	ContentBlocks []provider.ContentBlock `json:"content_blocks,omitempty"`
}

MsgPaneSubmitInput sends raw user input to a PaneActor for mode routing. The PaneActor performs the mode switch internally (shell/prompt/rysh/chat).

ContentBlocks (follow-up 1b) carries optional structured content blocks — e.g. an image attached via `##image <path>` — that the PaneActor forwards to MsgAgenticPrompt.ContentBlocks for prompt mode.

type MsgPaneTerminated

type MsgPaneTerminated struct {
	PaneID string `json:"pane_id"`
	TabID  string `json:"tab_id"`
}

MsgPaneTerminated is published when a PaneActor stops.

type MsgPaneVTReply

type MsgPaneVTReply struct {
	PaneID      string   `json:"pane_id"`
	Interactive bool     `json:"interactive"`
	Screen      []string `json:"screen,omitempty"`
	CursorRow   int      `json:"cursor_row"`
	CursorCol   int      `json:"cursor_col"`
}

MsgPaneVTReply carries one local pane's live interactive VT frame. Interactive is false when the pane is not currently showing an interactive program (the TUI then leaves its last frame in place and lets the slower full-content reconcile path catch the transition).

type MsgPaneWebHeadless

type MsgPaneWebHeadless struct {
	PaneID  string `json:"pane_id"`
	Op      string `json:"op"` // "on" | "off" | "status"
	Profile string `json:"profile,omitempty"`
	URL     string `json:"url,omitempty"`
}

MsgPaneWebHeadless controls the pane's CLI-owned headless browser executor (Phase 4 web automation — runs browser_action requests in a headless Chromium without the desktop app). Routed: WorkspaceActor → PaneActor.

Op "on"     — spawn the headless executor (Profile/URL default to the
              pane's web binding); enables web mode state and unbinds any
              desktop-app view so exactly ONE executor answers
              browser.request.
Op "off"    — stop the executor; rebinds the app view when web mode is
              still enabled.
Op "status" — print the executor state to the pane's rysh output.

type MsgPipelineCommand

type MsgPipelineCommand struct {
	PaneID string `json:"pane_id"` // pane that issued the command (for output routing)
	Cmd    string `json:"cmd"`     // "run"
	Args   string `json:"args"`    // remaining arguments
}

MsgPipelineCommand is sent from WorkspaceActor to TabActor for ##pipe commands that require actor.Context (e.g. "run" which may need to spawn the pipeline actor).

type MsgPipelineOutputAppend

type MsgPipelineOutputAppend = sharedmsg.MsgPipelineOutputAppend

type MsgProxyAuditSnapshotReply

type MsgProxyAuditSnapshotReply = sharedmsg.MsgProxyAuditSnapshotReply

type MsgProxyAuditSnapshotRequest

type MsgProxyAuditSnapshotRequest = sharedmsg.MsgProxyAuditSnapshotRequest

type MsgProxyRequestAudit

type MsgProxyRequestAudit = sharedmsg.MsgProxyRequestAudit

type MsgRawKeyInput

type MsgRawKeyInput struct {
	PaneID string `json:"pane_id"`
	Data   []byte `json:"data"`
}

MsgRawKeyInput sends raw keystroke bytes to the PaneActor's PTY in raw mode. Published to rysh.pane.{paneID}.rawinput as a data-plane bypass for minimal latency.

type MsgReconcileWorkspaces

type MsgReconcileWorkspaces struct{}

MsgReconcileWorkspaces asks the session to reconcile its live workspace set against the on-disk config file. It is published fire-and-forget by the TUI to ws.inbox on attach/startup. The active WorkspaceActor forwards it to the WorkspaceFarmActor, which re-reads the daemon's OWN config file and spawns any workspace that was added since the daemon started (add-only — existing workspaces are never removed or restarted). It carries no payload: the daemon is authoritative for which config file to read.

type MsgRelayActivate

type MsgRelayActivate struct {
	PaneID string `json:"pane_id"`
	Cols   int    `json:"cols"`
	Rows   int    `json:"rows"`
}

MsgRelayActivate tells the PaneActor to enter relay mode: rawReadLoop starts publishing raw PTY bytes to rysh.pane.{id}.relay.data (no envelope, raw binary). The TUI sends this after subscribing to the relay subjects and entering alt screen.

type MsgRelayDeactivate

type MsgRelayDeactivate struct {
	PaneID string `json:"pane_id"`
}

MsgRelayDeactivate tells the PaneActor to exit relay mode. rawReadLoop stops publishing to the relay subject.

type MsgReloadPrompts

type MsgReloadPrompts = sharedmsg.MsgReloadPrompts

Follow-up 2b: prompt hot-reload broadcast.

type MsgReloadPromptsRequest

type MsgReloadPromptsRequest struct {
	Reason string `json:"reason,omitempty"`
}

MsgReloadPromptsRequest asks the active WorkspaceActor to re-read the layered prompt store and rebroadcast MsgReloadPrompts to its panes — the same path as the ##agent reload-prompts command. Published to ws.inbox by the fsnotify auto-reload watcher (follow-up 2b). Reason is for logging only.

type MsgRemoteForwardCommand

type MsgRemoteForwardCommand struct {
	CommandType string `json:"command_type"` // "exec_shell", "exec_prompt", "exec_chat"
	Payload     string `json:"payload"`
	// TargetPaneID names the source pane the keystroke/command was aimed at,
	// captured at press time on the subscriber. Empty for older clients.
	TargetPaneID string `json:"target_pane_id,omitempty"`
}

MsgRemoteForwardCommand is published by a PaneActor in controller mode to ask the WorkspaceActor to forward a command to the active remote share listener.

type MsgRemoteInteractiveModeChange

type MsgRemoteInteractiveModeChange struct {
	Interactive bool `json:"interactive"`
	Rows        int  `json:"rows"`
	Cols        int  `json:"cols"`
}

MsgRemoteInteractiveModeChange notifies a pane that a remote share has entered or exited interactive mode. Sent from RemoteShareListenerActor or PaneSharedOutputListenerActor to the owning PaneActor.

type MsgRemotePaneFullscreen

type MsgRemotePaneFullscreen struct {
	TabID  string `json:"tab_id"`
	PaneID string `json:"pane_id"`
	On     bool   `json:"on"`
	Rows   int    `json:"rows,omitempty"`
	Cols   int    `json:"cols,omitempty"`
}

MsgRemotePaneFullscreen is published by the source UpstreamShareActor to the ws.remoteFullscreen topic when a controlling subscriber maximizes (or restores) a shared pane. The source TUI subscribes and (un)fullscreens that pane locally — reusing the same path as Alt+P f — so the source pane's PTY is resized and the interactive app reflows. The enlarged screen is then mirrored back to subscribers via the existing per-pane VT stream.

Rows/Cols, when > 0, are the subscriber's requested fullscreen PTY dimensions: the source sizes the pane's PTY to these so the subscriber sees a full-resolution render at its own screen size. When 0 the source falls back to its own full body.

type MsgRemoteScrollbackAppend

type MsgRemoteScrollbackAppend struct {
	Reset bool     `json:"reset,omitempty"`
	Rows  []string `json:"rows,omitempty"`
}

MsgRemoteScrollbackAppend forwards newly-evicted scrollback lines (rendered ANSI rows, oldest first) from a remote share's reconstructed VTerm to the owning PaneActor, so subscriber-side copy mode can scroll the remote program's history. Reset=true tells the pane to discard any prior remote scrollback first (sent when the listener (re)creates its VTerm).

type MsgRemoteUpstreamConnect

type MsgRemoteUpstreamConnect struct{}

MsgRemoteUpstreamConnect tells a RemoteUpstreamActor to connect.

type MsgRemoteUpstreamDisconnect

type MsgRemoteUpstreamDisconnect struct{}

MsgRemoteUpstreamDisconnect tells a RemoteUpstreamActor to disconnect.

type MsgRemoteUpstreamStatus

type MsgRemoteUpstreamStatus struct {
	Connected bool   `json:"connected"`
	Error     string `json:"error,omitempty"`
	PaneID    string `json:"pane_id"`
	URL       string `json:"url"`
}

MsgRemoteUpstreamStatus carries the remote upstream connection status.

type MsgRemoteVTScreenUpdate

type MsgRemoteVTScreenUpdate struct {
	Screen    []string `json:"screen"`
	CursorRow int      `json:"cursor_row"`
	CursorCol int      `json:"cursor_col"`
}

MsgRemoteVTScreenUpdate pushes a rendered VTerm screen from a remote share to the owning PaneActor. The screen lines replace the pane's display when RemoteInteractive is true.

type MsgRenameLane

type MsgRenameLane struct {
	Name string `json:"name"`
}

MsgRenameLane sets the name of the currently active lane.

type MsgRenamePane

type MsgRenamePane struct {
	Title string `json:"title"`
}

MsgRenamePane renames the currently active pane.

type MsgRenameTab

type MsgRenameTab struct {
	Title string `json:"title"`
}

MsgRenameTab renames the currently active tab.

type MsgReplayControl

type MsgReplayControl struct {
	PaneID string `json:"pane_id"`
	// Action: "pause" (toggle), "seek" (by DeltaMs), "faster", "slower", "stop".
	Action  string `json:"action"`
	DeltaMs int64  `json:"delta_ms,omitempty"` // seek delta, negative = backward
}

MsgReplayControl is a playback control for the active replay pane, published to ws.inbox by the TUI while the replay pane is focused (space pause, ←/→ seek, +/- speed). PaneID names the replay pane the key was pressed in; the workspace ignores controls that do not match its active replay pane.

type MsgResizePane

type MsgResizePane struct {
	Delta int `json:"delta"`
}

MsgResizePane adjusts the width of the active lane. Delta > 0 grows the lane; Delta < 0 shrinks it.

type MsgResizePaneHeight

type MsgResizePaneHeight struct {
	Delta int `json:"delta"`
}

MsgResizePaneHeight adjusts the height of the active pane group within the lane. Delta > 0 grows the group; Delta < 0 shrinks it.

type MsgResizePaneWidth

type MsgResizePaneWidth struct {
	Delta int `json:"delta"`
}

MsgResizePaneWidth adjusts the width flex of the active lane.

type MsgRestoreConversation

type MsgRestoreConversation = sharedmsg.MsgRestoreConversation

type MsgResumeNativeAgents added in v0.2.6

type MsgResumeNativeAgents struct {
	Retries int `json:"retries"`
}

MsgResumeNativeAgents asks the workspace to bring back the agents it had launched itself, after a stop/start has rebuilt the session from KV (design 029).

Scheduled once the layout is restored, and retried while panes are still coming up: a restored pane's shell is spawned by its own actor's Started hook, so "the pane exists" and "the pane can run a command" are separated by a few hundred milliseconds.

type MsgRunStatusReply

type MsgRunStatusReply = sharedmsg.MsgRunStatusReply

type MsgSessionMemoryReplace

type MsgSessionMemoryReplace = sharedmsg.MsgSessionMemoryReplace

type MsgSessionMemoryReply

type MsgSessionMemoryReply = sharedmsg.MsgSessionMemoryReply

type MsgSetChatOutputPane

type MsgSetChatOutputPane = sharedmsg.MsgSetChatOutputPane

type MsgSetConnectedPane

type MsgSetConnectedPane struct {
	PaneID string `json:"pane_id"` // remote pane ID (empty = disconnected)
}

MsgSetConnectedPane tells a PaneActor that it is connected to (or disconnected from) a remote shared pane via upstream subscription.

type MsgSetControllerMode

type MsgSetControllerMode struct {
	ShareID   string `json:"share_id"`
	PaneAlias string `json:"pane_alias"`
	Active    bool   `json:"active"`
}

MsgSetControllerMode tells a PaneActor to enter or exit controller mode for a remote upstream share. When Active is true, shell/prompt/chat input will be forwarded to the remote pane instead of executing locally.

type MsgSetGroundingMode

type MsgSetGroundingMode = sharedmsg.MsgSetGroundingMode

type MsgSetRunBudget

type MsgSetRunBudget = sharedmsg.MsgSetRunBudget

type MsgSetTabBarOrientation added in v0.2.6

type MsgSetTabBarOrientation struct {
	Vertical bool `json:"vertical"`
	Toggle   bool `json:"toggle,omitempty"`
}

MsgSetTabBarOrientation switches the tab bar between a horizontal strip under the workspace row and a vertical column down the left edge of the body. Orientation is per-workspace state and survives a restart.

Vertical selects the orientation directly; Toggle flips whatever is current and takes precedence over Vertical when set.

type MsgSetWorkingDir

type MsgSetWorkingDir struct {
	Dir string `json:"dir"`
}

MsgSetWorkingDir updates the working directory that newly created panes start in. It is broadcast down the hierarchy (Workspace → Tab → Lane → PaneGroup) so that every pane-creating actor's config copy is updated; existing panes keep their already-started shells. Backs `##session cwd <path>`.

type MsgShareDisableMode

type MsgShareDisableMode struct {
	PaneID string `json:"pane_id"`
	Mode   string `json:"mode"` // "sh", "ai", "rysh", "chat"
}

MsgShareDisableMode disables a mode for remote users on the active pane. Routed: WorkspaceActor → PaneActor (direct, pass-through).

type MsgShareEnableMode

type MsgShareEnableMode struct {
	PaneID string `json:"pane_id"`
	Mode   string `json:"mode"`
}

MsgShareEnableMode re-enables a previously disabled mode. Routed: WorkspaceActor → PaneActor (direct, pass-through).

type MsgShareEntity

type MsgShareEntity struct {
	EntityType string `json:"entity_type"` // "tab" | "lane" | "pane_group" | "pane"
	EntityID   string `json:"entity_id"`
	Mode       string `json:"mode"`     // "view" | "control"
	ShareID    string `json:"share_id"` // pre-generated by caller; if empty, registry generates one
	// EntityAlias is a human-readable name registered with the upstream so remote
	// viewers (e.g. the mobile shares list) can identify the entity. For panes it
	// packs the user-given name and the auto-assigned title as "<given> · <auto>"
	// (or just "<auto>" when there is no distinct given name). Empty falls back to
	// the entity id in the registry.
	EntityAlias string `json:"entity_alias,omitempty"`

	// SharedRootFolder is the working directory captured at share time. It pins
	// the root the rysh-mobile file browser starts from for this share. For a tab
	// share it applies to every pane in the tab. Empty means "resolve the browse
	// root live per request" (the target pane's current working directory).
	SharedRootFolder string `json:"shared_root_folder,omitempty"`

	// Forged-API sharing (Task 2 phase 2a). ShareAPI opts the share's forge-origin
	// operations in; Redact governs result redaction (the caller resolves its
	// default of true); ForgedOps carries the operation specs the workspace
	// computed at share time (forge-origin ∩ the shared entity's scope).
	ShareAPI  bool           `json:"share_api,omitempty"`
	Redact    bool           `json:"redact,omitempty"`
	ForgedOps []ForgedOpSpec `json:"forged_ops,omitempty"`
}

MsgShareEntity starts sharing an entity to the upstream server.

type MsgShareForgedAPI

type MsgShareForgedAPI struct {
	ShareID string         `json:"share_id"`
	Ops     []ForgedOpSpec `json:"ops"`
}

MsgShareForgedAPI carries the owner's shareable forged-API operation specs to subscribers (published last-value on ws.{workspace}.share.{shareID}.api).

type MsgShareList

type MsgShareList struct{}

MsgShareList requests a list of all active shares.

type MsgShareListReply

type MsgShareListReply struct {
	Shares []ShareInfo `json:"shares"`
}

MsgShareListReply carries the list of active shares.

type MsgShareOutput

type MsgShareOutput struct {
	ShareID   string `json:"share_id"`
	PaneID    string `json:"pane_id"`
	PaneAlias string `json:"pane_alias"`
	Text      string `json:"text"`
}

MsgShareOutput carries share output from upstream to local listener.

type MsgShareRegisterAck

type MsgShareRegisterAck struct {
	ShareID string `json:"share_id"`
	Success bool   `json:"success"`
	Error   string `json:"error,omitempty"`
}

MsgShareRegisterAck is the server's acknowledgment of a share registration.

type MsgShareRestrictionsUpdated

type MsgShareRestrictionsUpdated struct {
	PaneID       string            `json:"pane_id"`
	Restrictions ShareRestrictions `json:"restrictions"`
}

MsgShareRestrictionsUpdated notifies UpstreamShareActor of restriction changes. Published by PaneActor to rysh.pane.{paneID}.restrictions on every change.

type MsgShareSetFileBrowse

type MsgShareSetFileBrowse struct {
	PaneID        string `json:"pane_id"`
	AllowAbsolute bool   `json:"allow_absolute"`
}

MsgShareSetFileBrowse sets the file-browse allow-absolute flag for remote subscribers of the pane's share. File browsing itself is always enabled; this only controls whether browsing may escape the pane's working-directory subtree. Routed: WorkspaceActor → PaneActor (direct, pass-through).

type MsgShareShellAllow

type MsgShareShellAllow struct {
	PaneID   string   `json:"pane_id"`
	Commands []string `json:"commands"`
}

MsgShareShellAllow sets the shell command allow-list (clears forbid-list). Routed: WorkspaceActor → PaneActor (direct, pass-through).

type MsgShareShellClear

type MsgShareShellClear struct {
	PaneID string `json:"pane_id"`
}

MsgShareShellClear removes all shell command restrictions. Routed: WorkspaceActor → PaneActor (direct, pass-through).

type MsgShareShellForbid

type MsgShareShellForbid struct {
	PaneID   string   `json:"pane_id"`
	Commands []string `json:"commands"`
}

MsgShareShellForbid sets the shell command forbid-list (clears allow-list). Routed: WorkspaceActor → PaneActor (direct, pass-through).

type MsgShareShowRestrictions

type MsgShareShowRestrictions struct {
	PaneID string `json:"pane_id"`
}

MsgShareShowRestrictions requests current restrictions for display. Routed: WorkspaceActor → PaneActor (direct, pass-through). Reply via rysh output.

type MsgShareStatus

type MsgShareStatus struct {
	EntityID string `json:"entity_id,omitempty"` // empty = all shares
}

MsgShareStatus requests the sharing status of an entity.

type MsgShareStatusReply

type MsgShareStatusReply struct {
	Shares []ShareInfo `json:"shares"`
}

MsgShareStatusReply carries sharing status.

type MsgShutdown

type MsgShutdown struct{}

MsgShutdown triggers a graceful shutdown of the workspace.

type MsgSpawnSubOrchestrator

type MsgSpawnSubOrchestrator = sharedmsg.MsgSpawnSubOrchestrator

type MsgStackedPaneMove

type MsgStackedPaneMove struct {
	Direction Direction `json:"direction"`
}

MsgStackedPaneMove reorders the active pane within its stack by one slot. DirUp moves it toward the front (index 0); DirDown moves it toward the back. The moved pane stays active.

type MsgStackedPaneRotate

type MsgStackedPaneRotate struct {
	Direction Direction `json:"direction"`
}

MsgStackedPaneRotate cycles the stacked pane stack in the specified direction. Replaces MsgStackedPaneNext and MsgStackedPanePrev.

type MsgStackedPaneSelect

type MsgStackedPaneSelect struct {
	Index int `json:"index"`
}

MsgStackedPaneSelect activates the stacked pane at the given 0-based index within the active group (the position shown as n/N in the title bars, minus one). Out-of-range indices are ignored.

type MsgStartPaneListener

type MsgStartPaneListener struct {
	TargetPaneID string `json:"target_pane_id"`
	TargetAlias  string `json:"target_alias"` // for display purposes
}

MsgStartPaneListener tells a PaneActor to start listening to another pane's shared output.

type MsgStopPaneListener

type MsgStopPaneListener struct{}

MsgStopPaneListener tells a PaneActor to stop listening.

type MsgSubOrchestratorResult

type MsgSubOrchestratorResult = sharedmsg.MsgSubOrchestratorResult

type MsgSubmitInput

type MsgSubmitInput struct {
	Text string `json:"text"`
	Mode string `json:"mode"`
	// PaneID, when set, targets the input at that pane instead of the
	// workspace's current active pane. Sent by the desktop app, whose input
	// boxes are per-pane: routing by "active pane" raced daemon-side focus —
	// under PTY churn a starved focus command made Enter execute in the
	// PREVIOUS pane (e.g. straight into a running claude CLI). The workspace
	// aligns focus to this pane before routing, healing any drift.
	PaneID string `json:"pane_id,omitempty"`
	// Programmatic marks input that NOBODY TYPED: an agent's rysh tool, a
	// script, `rysh run`. It routes to PaneID exactly like typed input but must
	// not move the human's focus there — a human typing is the strongest focus
	// signal there is, and a machine submitting on a pane's behalf is no signal
	// at all. Without it, every agent that ran a ## command dragged the cursor
	// (and, across tabs, the whole visible tab) to itself.
	Programmatic bool `json:"programmatic,omitempty"`
}

MsgSubmitInput submits user input (shell command or AI prompt). Mode is "shell" or "prompt"; empty falls back to the "!" prefix check.

type MsgSwapPane

type MsgSwapPane struct{}

MsgSwapPane swaps the active lane position with the next one.

type MsgSwitchWorkspace

type MsgSwitchWorkspace struct {
	Index     int       `json:"index"`
	Direction Direction `json:"direction,omitempty"`
}

MsgSwitchWorkspace switches the active workspace within the session. It is published by the TUI to the active workspace's ws.inbox as a request, so the reply carries the newly activated workspace's snapshot (race-free switch). The active WorkspaceActor resolves the target and forwards the handoff to its parent WorkspaceFarmActor. If Direction is DirNext/DirPrev it takes precedence; otherwise Index selects a 0-based workspace.

type MsgTabClosePane

type MsgTabClosePane struct{}

MsgTabClosePane closes the active pane group in the active lane.

type MsgTabCreateGrid

type MsgTabCreateGrid struct {
	LaneTitles [][]string `json:"lane_titles"`
}

MsgTabCreateGrid seeds a grid of lanes into an existing tab. Each entry in LaneTitles is one new lane appended to the tab; the inner titles are the pane groups (one pane each) within that lane. Existing lanes are preserved. Used by `##new grid <lanes>x<panes> --here`.

type MsgTabCreateGroupsInLane

type MsgTabCreateGroupsInLane struct {
	LaneID string   `json:"lane_id"`
	Titles []string `json:"titles"`
}

MsgTabCreateGroupsInLane appends one pane group (a single pane) per title to the given lane, stacking them vertically, then equalizes the lane's group heights. Used by `##new grid <n>` to stack n panes in the active lane.

type MsgTabCreatePane

type MsgTabCreatePane struct {
	Title string `json:"title,omitempty"` // pre-generated unique alias
}

MsgTabCreatePane creates a new lane in the tab.

type MsgTabCreatePaneDown

type MsgTabCreatePaneDown struct {
	Title string `json:"title,omitempty"` // pre-generated unique alias
}

MsgTabCreatePaneDown creates a new pane group in the active lane.

type MsgTabCreatePaneGroupInLane

type MsgTabCreatePaneGroupInLane struct {
	LaneID     string `json:"lane_id"`
	Title      string `json:"title,omitempty"`
	GroupID    string `json:"group_id,omitempty"`
	WorkingDir string `json:"working_dir,omitempty"`
	PaneID     string `json:"pane_id,omitempty"`
	PaneType   string `json:"pane_type,omitempty"`

	// Meta is metadata the pane is BORN with (design 028). It exists because
	// the alternative — create the pane, then publish MsgPaneSetMeta at it —
	// races the pane actor's own subscription: the id is known, but the inbox
	// may not be listening yet, and a lost fire-and-forget meta write shows up
	// much later as a board pane that has forgotten which board it renders.
	Meta map[string]string `json:"meta,omitempty"`
}

MsgTabCreatePaneGroupInLane creates a new pane group in a specific lane. GroupID / WorkingDir semantics are those of MsgLaneCreatePaneGroup, which this message forwards to (worktree lifecycle, design 008).

PaneID, when set, pre-assigns the initial pane's ID (normally minted by the tab) so the sender can address the pane it asked for — the replay pane (design 006 v2) publishes recorded output to that ID at creation time. PaneType, when set, marks the initial pane as a special variant: "replay" panes never start a shell/PTY (read-only by construction).

type MsgTabCreateStackedPane

type MsgTabCreateStackedPane struct {
	Title string `json:"title,omitempty"` // pre-generated unique alias
}

MsgTabCreateStackedPane creates a new stacked pane in the active group.

type MsgTabCreateStackedPaneInLane

type MsgTabCreateStackedPaneInLane struct {
	LaneID      string `json:"lane_id"`
	PaneGroupID string `json:"pane_group_id"`
	Title       string `json:"title,omitempty"`
}

MsgTabCreateStackedPaneInLane creates a stacked pane in a specific lane's active group.

type MsgTabDeleteLane

type MsgTabDeleteLane struct {
	LaneID string `json:"lane_id"`
}

MsgTabDeleteLane deletes a specific lane by ID within a tab.

type MsgTabEqualizeAll

type MsgTabEqualizeAll struct{}

MsgTabEqualizeAll equalizes lane widths and every lane's group heights.

type MsgTabEqualizeHorizontal

type MsgTabEqualizeHorizontal struct{}

MsgTabEqualizeHorizontal sets all lanes to equal width.

type MsgTabEqualizePanes

type MsgTabEqualizePanes struct{}

MsgTabEqualizePanes resets all lane flex weights to equal.

type MsgTabEqualizeVertical

type MsgTabEqualizeVertical struct{}

MsgTabEqualizeVertical resets all group rowFlex in the active lane to equal.

type MsgTabFocus

type MsgTabFocus struct {
	Direction Direction `json:"direction"`
}

MsgTabFocus moves focus in the specified direction within the tab. Replaces MsgTabFocusNextPane, MsgTabFocusPrevPane, MsgTabFocusPaneLeft/Right/Up/Down.

type MsgTabFocusPaneByID

type MsgTabFocusPaneByID struct {
	ID string `json:"id"`
}

MsgTabFocusPaneByID focuses a pane by UUID within the tab.

type MsgTabPipelineDisable

type MsgTabPipelineDisable struct{}

MsgTabPipelineDisable disables pipeline mode for a tab.

type MsgTabPipelineEnable

type MsgTabPipelineEnable struct{}

MsgTabPipelineEnable enables pipeline mode for a tab.

type MsgTabResizePane

type MsgTabResizePane struct {
	Delta int `json:"delta"`
}

MsgTabResizePane adjusts the width of the active lane within the tab.

type MsgTabResizePaneHeight

type MsgTabResizePaneHeight struct {
	Delta int `json:"delta"`
}

MsgTabResizePaneHeight adjusts the height of the active pane group within the active lane.

type MsgTabResizePaneWidth

type MsgTabResizePaneWidth struct {
	Delta int `json:"delta"`
}

MsgTabResizePaneWidth adjusts the width flex of the active lane.

type MsgTabSetActive

type MsgTabSetActive struct{}

MsgTabSetActive notifies a tab that it became the active tab.

type MsgTabSetInactive

type MsgTabSetInactive struct{}

MsgTabSetInactive notifies a tab that it became inactive.

type MsgTabSetPaneHidden added in v0.2.6

type MsgTabSetPaneHidden struct {
	PaneID string `json:"pane_id"`
	Hidden bool   `json:"hidden"`
}

MsgTabSetPaneHidden takes one pane in this tab off screen, or puts it back (design 027 §5.1). Routed tab → lane → group by pane id, the same traversal MsgTabFocusPaneByID uses, because the GROUP is what has to act: it owns the stack's active index, and hiding the focused pane has to move focus off it first or focus is stranded in a pane nothing draws.

type MsgTabSnapshotReply

type MsgTabSnapshotReply struct {
	Snapshot domain.TabSnapshot `json:"snapshot"`
}

MsgTabSnapshotReply carries the tab snapshot reply.

type MsgTabStackedPane

type MsgTabStackedPane struct {
	Direction Direction `json:"direction"`
}

MsgTabStackedPane cycles the stack in the specified direction in the active group. Replaces MsgTabStackedPaneNext and MsgTabStackedPanePrev.

type MsgTabStackedPaneMove

type MsgTabStackedPaneMove struct {
	Direction Direction `json:"direction"`
}

MsgTabStackedPaneMove reorders the active pane within the active group's stack.

type MsgTabStackedPaneSelect

type MsgTabStackedPaneSelect struct {
	Index int `json:"index"`
}

MsgTabStackedPaneSelect activates the stacked pane at the given 0-based index in the active group.

type MsgTabSubmitInput

type MsgTabSubmitInput struct {
	PaneID string `json:"pane_id"`
	Text   string `json:"text"`
	Mode   string `json:"mode"`
}

MsgTabSubmitInput forwards user input to a specific pane within the tab.

type MsgTabSwapPane

type MsgTabSwapPane struct{}

MsgTabSwapPane swaps the active lane with the next one.

type MsgTabTerminated

type MsgTabTerminated struct {
	TabID string `json:"tab_id"`
}

MsgTabTerminated is published when a TabActor stops.

type MsgTogglePipelineMode

type MsgTogglePipelineMode struct{}

MsgTogglePipelineMode toggles pipeline mode for the active tab.

type MsgToolCall

type MsgToolCall = sharedmsg.MsgToolCall

type MsgToolResult

type MsgToolResult = sharedmsg.MsgToolResult

type MsgUnshareEntity

type MsgUnshareEntity struct {
	EntityID string `json:"entity_id"`
}

MsgUnshareEntity stops sharing an entity.

type MsgUpstreamCommand

type MsgUpstreamCommand struct {
	ShareID     string `json:"share_id"`
	CommandID   string `json:"command_id"`
	CommandType string `json:"command_type"` // "submit_input" | "exec_shell" | "exec_prompt"
	Payload     string `json:"payload"`
	SenderID    string `json:"sender_id"`
	SenderName  string `json:"sender_name"`
	// TargetPaneID names the specific source pane this command should run on.
	// Used by multi-pane (tab/lane/pane_group) control shares so a subscriber can
	// drive a chosen pane. Empty for single-pane shares (routes to that pane).
	TargetPaneID string `json:"target_pane_id,omitempty"`
}

MsgUpstreamCommand carries a command received from a remote session.

type MsgUpstreamCommandAck

type MsgUpstreamCommandAck struct {
	ShareID   string `json:"share_id"`
	CommandID string `json:"command_id"`
	Success   bool   `json:"success"`
	Error     string `json:"error,omitempty"`
}

MsgUpstreamCommandAck acknowledges a remote command.

type MsgUpstreamConnectionClosed

type MsgUpstreamConnectionClosed struct {
	Reason string `json:"reason,omitempty"`
}

MsgUpstreamConnectionClosed signals that the remote NATS connection was permanently closed (all reconnect attempts exhausted, or auth error).

type MsgUpstreamReconnected

type MsgUpstreamReconnected struct{}

MsgUpstreamReconnected signals that a remote NATS connection was restored. Published by nats.go reconnect handlers (in goroutines) to the actor's own mailbox so that re-registration and re-subscription happen on the actor thread.

type MsgUpstreamSendCommand

type MsgUpstreamSendCommand struct {
	ShareID     string `json:"share_id"`
	CommandType string `json:"command_type"`
	Payload     string `json:"payload"`
	// TargetPaneID names the specific source pane to run the command on (for
	// multi-pane mirror tabs). Empty for single-pane control shares.
	TargetPaneID string `json:"target_pane_id,omitempty"`
}

MsgUpstreamSendCommand sends a command to a remote share (control mode).

type MsgUpstreamSharesList

type MsgUpstreamSharesList struct {
	Shares []ShareInfo `json:"shares"`
}

MsgUpstreamSharesList is the reply listing available shares on the upstream.

type MsgUpstreamSubscribe

type MsgUpstreamSubscribe struct {
	ShareID string `json:"share_id"`
}

MsgUpstreamSubscribe subscribes to a remote share's output.

type MsgUpstreamUnsubscribe

type MsgUpstreamUnsubscribe struct {
	ShareID string `json:"share_id"`
}

MsgUpstreamUnsubscribe stops subscribing to a remote share.

type MsgUsageBudgetSet

type MsgUsageBudgetSet = sharedmsg.MsgUsageBudgetSet

type MsgUsageCheck

type MsgUsageCheck = sharedmsg.MsgUsageCheck

type MsgUsageCheckReply

type MsgUsageCheckReply = sharedmsg.MsgUsageCheckReply

type MsgUsageRecord

type MsgUsageRecord = sharedmsg.MsgUsageRecord

type MsgUsageSnapshotReply

type MsgUsageSnapshotReply = sharedmsg.MsgUsageSnapshotReply

type MsgUsageSnapshotRequest

type MsgUsageSnapshotRequest = sharedmsg.MsgUsageSnapshotRequest

type MsgWebActivate

type MsgWebActivate struct {
	PaneID string `json:"pane_id"`
	// Profile and URL carry the pane's web binding so the desktop app can create
	// and navigate the embedded browser directly from this push — without waiting
	// for a snapshot to carry web_profile/web_url (which is timing-fragile: the
	// layout-only snapshot that follows a web-enable can lag and arrive stale,
	// leaving the app with no URL → blank browser).
	Profile string `json:"profile,omitempty"`
	URL     string `json:"url,omitempty"`
}

MsgWebActivate tells the desktop app to switch a pane's display to web mode now (published when `##mode new web` enables/re-binds web on the pane). It is a deterministic signal that doesn't depend on the app noticing the snapshot's web_activate_seq bump — which is timing-fragile, so the pane only flipped to web after an unrelated click forced a fresh snapshot. Relayed by the web server to the app as a "web_activate" message.

type MsgWebDeactivate

type MsgWebDeactivate struct {
	PaneID string `json:"pane_id"`
}

MsgWebDeactivate tells the desktop app to switch a pane's display OFF web mode now (published when `##mode delete web` disables web on the pane). It mirrors MsgWebActivate: a deterministic, push-driven signal so the app drops the pane back to shell display immediately instead of relying on a snapshot's cleared web binding (which is timing-fragile). Relayed by the web server to the app as a "web_deactivate" message.

type MsgWebPromptDispatched

type MsgWebPromptDispatched struct {
	PaneID string `json:"pane_id"`
	Prompt string `json:"prompt"`
}

MsgWebPromptDispatched announces that a prompt was sent to a web pane's AI assistant from outside the in-pane "Ask Rysh" chat box (e.g. via the `##mode web ai <prompt>` system command typed in shell mode). The web server relays it to the desktop app so the prompt renders as a human bubble in the Ask Rysh panel — exactly like a prompt typed into the chat box. Published on the pane's `web.prompt` subject; the actual AI execution goes through the normal prompt-submission path (MsgPaneSubmitInput).

type MsgWorkspaceSnapshotReply

type MsgWorkspaceSnapshotReply struct {
	Snapshot domain.WorkspaceSnapshot `json:"snapshot"`
}

MsgWorkspaceSnapshotReply carries the workspace snapshot reply.

type NATSEnvelope

type NATSEnvelope = sharedmsg.NATSEnvelope

type NATSPublisher

type NATSPublisher = sharedmsg.NATSPublisher

type PendingPair

type PendingPair struct {
	Code       string `json:"code"`
	SenderID   string `json:"sender_id"`
	SenderName string `json:"sender_name"`
	Channel    string `json:"channel"`
	FirstMsg   string `json:"first_msg,omitempty"`
	CreatedAt  int64  `json:"created_at"`
	ExpiresAt  int64  `json:"expires_at"`
}

PendingPair is the wire form of one pending pairing request. It mirrors channels.PendingReq field-for-field (with Unix-second times) so the msg package does not import internal/channels.

type RequestEnvelope

type RequestEnvelope = sharedmsg.RequestEnvelope

type ShareInfo

type ShareInfo struct {
	ShareID    string `json:"share_id"`
	EntityType string `json:"entity_type"`
	EntityID   string `json:"entity_id"`
	Alias      string `json:"alias"`
	Mode       string `json:"mode"`
	Connected  bool   `json:"connected"`
	URL        string `json:"url"`
	Viewers    int    `json:"viewers"`
}

ShareInfo describes an active share.

type ShareRestrictions

type ShareRestrictions struct {
	DisabledModes   []string `json:"disabled_modes,omitempty"`    // "sh", "ai", "rysh", "chat"
	ShellAllowList  []string `json:"shell_allow_list,omitempty"`  // if non-empty, only these shell commands are permitted
	ShellForbidList []string `json:"shell_forbid_list,omitempty"` // if non-empty, these shell commands are blocked

	// AllowFileBrowse gates the per-share file-browse responder (the
	// ws.{ws}.share.{shareID}.fs request/reply subject). It is ALWAYS true for an
	// active share — file browsing is always available — but kept as an explicit
	// flag for clarity and forward-compatibility. By default browsing is confined
	// to the target pane's working-directory subtree.
	AllowFileBrowse bool `json:"allow_file_browse"`

	// AllowAbsolute, when true, lets the file-browse responder serve absolute
	// request paths and browse outside the resolved root subtree. Defaults false:
	// absolute paths are rejected and browsing is confined to the root subtree
	// ("denied" on any escape). Opt-in via "##share pane ... --allow-absolute".
	AllowAbsolute bool `json:"allow_absolute,omitempty"`
}

ShareRestrictions holds per-pane access restrictions for remote users. Stored in PaneActor state, persisted to KV, propagated to UpstreamShareActor and remote subscribers.

type ToolCallInfo

type ToolCallInfo = sharedmsg.ToolCallInfo

type TurnType

type TurnType = sharedmsg.TurnType

type UsageAgg

type UsageAgg = sharedmsg.UsageAgg

type UsageCeiling

type UsageCeiling = sharedmsg.UsageCeiling

type VTLineDelta

type VTLineDelta struct {
	Y int    `json:"y"`
	S string `json:"s"`
}

VTLineDelta is one changed VT row in a delta frame: Y is the row index, S is the new (ANSI-styled) row content.

type WhatsAppMsgDetail

type WhatsAppMsgDetail struct {
	ID        string `json:"id"`
	MessageID string `json:"message_id"`
	From      string `json:"from"`
	Name      string `json:"name"`
	Text      string `json:"text"`
	Time      string `json:"time"`
}

WhatsAppMsgDetail is the full content of a single received WhatsApp message.

type WhatsAppMsgSummary

type WhatsAppMsgSummary struct {
	ID        string `json:"id"`
	MessageID string `json:"message_id"`
	From      string `json:"from"`
	Name      string `json:"name"`
	Snippet   string `json:"snippet"`
	Time      string `json:"time"`
}

WhatsAppMsgSummary is a brief overview of a received WhatsApp message for the list pane. ID is the short handle ("wa-3") used by whatsapp_read.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL