Documentation
¶
Index ¶
- Constants
- Variables
- func DeleteDeviceViaCore(stateDir, baseURL, room, deviceID string) error
- func FleetAudit(stateDir, format string, args ...any)
- func FleetPairingURI(base, room, secret, name string) string
- func FleetStalePending(pending int, connected bool, lastSeenAt string, now time.Time) bool
- func MaxRooms() int
- func PairingURI(base, room, secret, name string) string
- func PairingURIMode(base, room, secret, name string, envelope bool) string
- func PushTestViaCore(stateDir, baseURL, room, deviceID string) (pushed bool, reason string, err error)
- func RefusePairURIForOperator(raw string) error
- func RunNewLinkCode(ctx context.Context, stateDir, coreURL string, link Link, out io.Writer) error
- func ToCommonMark(text, format string) string
- type AgentCatalog
- type AgentInfo
- type AppVersion
- type DeviceCreds
- type DeviceRecord
- type DeviceState
- type Element
- type FleetAuthority
- type FleetCaps
- type FleetCapsBin
- type FleetCapsFleet
- type FleetCapsHarness
- type FleetCapsList
- type FleetDirection
- type FleetEdge
- type FleetEdgeView
- type FleetPeerCaps
- type FleetProvider
- func (p *FleetProvider) Capabilities() provider.Capabilities
- func (p *FleetProvider) DownloadAttachment(context.Context, mcpchan.DownloadInput) (string, bool)
- func (p *FleetProvider) EditMessage(context.Context, mcpchan.EditInput) (string, bool)
- func (p *FleetProvider) FleetCaps(_ context.Context, peer string) (string, bool)
- func (p *FleetProvider) FleetList(_ context.Context) (string, bool)
- func (p *FleetProvider) FleetSend(ctx context.Context, in mcpchan.FleetSendInput) (string, bool)
- func (p *FleetProvider) HiddenSource() bool
- func (p *FleetProvider) Name() string
- func (p *FleetProvider) OnPermissionRequest(context.Context, mcpchan.PermissionRequestParams)
- func (p *FleetProvider) React(context.Context, mcpchan.ReactInput) (string, bool)
- func (p *FleetProvider) Reply(context.Context, mcpchan.ReplyInput) (string, bool)
- func (p *FleetProvider) Start(ctx context.Context, sink provider.InboundSink) error
- type FleetStore
- func (s *FleetStore) AppendJournalFrame(edgeID, dir string, frame json.RawMessage) (uint64, error)
- func (s *FleetStore) BoundPeerCaps(edge FleetEdge) (*FleetPeerCaps, error)
- func (s *FleetStore) ClearEdgeUnreachable(edgeID string) (cleared bool, err error)
- func (s *FleetStore) CommitInbound(edgeID, cid string, frame json.RawMessage, rateAllow func() bool) (inboundCommit, error)
- func (s *FleetStore) DialEdges() ([]FleetEdge, error)
- func (s *FleetStore) EdgeActivity(edgeID string) (sent24h, recv24h, dropped24h uint64, err error)
- func (s *FleetStore) EdgeState(edgeID string) (fleetEdgeState, error)
- func (s *FleetStore) Edges() ([]FleetEdge, error)
- func (s *FleetStore) EnqueueOutboundTx(edgeID, cid string, frame json.RawMessage) (seq uint64, dropped bool, err error)
- func (s *FleetStore) FlagKeyFPMismatch(edgeID string) error
- func (s *FleetStore) GrantAuthority(arg string, ttl time.Duration) (FleetEdge, error)
- func (s *FleetStore) InboundDelivered(edgeID, cid string) (bool, error)
- func (s *FleetStore) InboundSeen(edgeID, cid string) (bool, error)
- func (s *FleetStore) IncDroppedInbound(edgeID string) error
- func (s *FleetStore) Join(uri, alias string, opts JoinOptions) (FleetEdge, error)
- func (s *FleetStore) JournalEntries(edgeID string) ([]fleetJournalEntry, error)
- func (s *FleetStore) Link(base, alias string) (FleetEdge, string, error)
- func (s *FleetStore) LiveEdge(edgeID string) (FleetEdge, bool)
- func (s *FleetStore) MarkDialEdgeDead(edgeID, reason string) error
- func (s *FleetStore) MarkEdgeUnreachable(edgeID string, attempts int) error
- func (s *FleetStore) MarkInboundDelivered(edgeID, cid string) error
- func (s *FleetStore) PeerCaps(edgeID string) (*FleetPeerCaps, error)
- func (s *FleetStore) PendingDepth(edgeID string) (int, error)
- func (s *FleetStore) PendingOutbound(edgeID string) ([]fleetPendingOut, error)
- func (s *FleetStore) PinPeerKeyFP(edgeID, fp string) (pinned, mismatch bool, err error)
- func (s *FleetStore) RecentInboundCIDs(edgeID string, n int) ([]string, error)
- func (s *FleetStore) ReconcileInboundCursor(edgeID string) (generation uint64, resynced bool, err error)
- func (s *FleetStore) RecordPeerAckCID(edgeID, cid string) (found bool, remaining int, err error)
- func (s *FleetStore) Remove(arg string) (FleetEdge, error)
- func (s *FleetStore) Rename(arg, alias string) (FleetEdge, error)
- func (s *FleetStore) ResolveEdge(arg string) (FleetEdge, error)
- func (s *FleetStore) RevokeAuthority(arg string) (edge FleetEdge, had bool, err error)
- func (s *FleetStore) ServedFleetRooms() ([]FleetEdge, error)
- func (s *FleetStore) SetPeerBoxName(edgeID, name string) error
- func (s *FleetStore) SetPeerCaps(edgeID string, c FleetCaps) error
- func (s *FleetStore) TouchLastSeen(edgeID string)
- type FleetTombstone
- type FleetUnreachable
- type JobDriver
- func (d *JobDriver) DoneCard(jobID, state, detail, notify, chatID string) error
- func (d *JobDriver) RehydrateCard(jobID, elementID, msgID, chatID, title, detail string, startedAt int64, ...)
- func (d *JobDriver) StartCard(title, detail, chatID string, progress *float64) (jobID, msgID, elementID string, err error)
- func (d *JobDriver) UpdateCard(jobID, detail, chatID string, progress *float64) error
- type JoinOptions
- type Link
- type LiveActivityContent
- type LiveActivityRegistration
- type LiveActivityRequest
- type LiveActivityTarget
- type MailboxItem
- type PairParams
- type Provider
- func (p *Provider) AgentInfoSink() func(mcpchan.AgentInfoParams)
- func (p *Provider) Capabilities() provider.Capabilities
- func (p *Provider) DownloadAttachment(ctx context.Context, in mcpchan.DownloadInput) (string, bool)
- func (p *Provider) EditMessage(ctx context.Context, in mcpchan.EditInput) (string, bool)
- func (p *Provider) HarnessCatalogSink() func(mcpchan.HarnessCatalogParams)
- func (p *Provider) Job(ctx context.Context, in mcpchan.JobInput) (string, bool, bool)
- func (p *Provider) JobDriver() *JobDriver
- func (p *Provider) Name() string
- func (p *Provider) OnPermissionRequest(context.Context, mcpchan.PermissionRequestParams)
- func (p *Provider) PublishArtifact(ctx context.Context, in mcpchan.PublishInput) (string, bool, bool)
- func (p *Provider) React(ctx context.Context, in mcpchan.ReactInput) (string, bool)
- func (p *Provider) Reply(ctx context.Context, in mcpchan.ReplyInput) (string, bool)
- func (p *Provider) SDKApplyResultSink() func(mcpchan.SDKApplyResultParams)
- func (p *Provider) SetSDKApplyForwarder(fn func(ctx context.Context, rid string, model, effort *string) error)
- func (p *Provider) Start(ctx context.Context, sink provider.InboundSink) error
- func (p *Provider) TranscriptFile() string
- type RelayStore
- func (s *RelayStore) ActiveDevices() []DeviceRecord
- func (s *RelayStore) ActiveLiveActivityTargets(jobID string) []LiveActivityTarget
- func (s *RelayStore) ActivePushTarget(deviceID string) (token, keyID, room string, ok bool)
- func (s *RelayStore) CurrentRoom() (RoomRecord, bool)
- func (s *RelayStore) Device(deviceID string) (DeviceRecord, bool)
- func (s *RelayStore) Devices() []DeviceRecord
- func (s *RelayStore) DropLiveActivityIfToken(deviceID, jobID, token string) (bool, error)
- func (s *RelayStore) DropPushToken(deviceID string) error
- func (s *RelayStore) IdentityName() (string, bool)
- func (s *RelayStore) MintLink(base, name string) (Link, error)
- func (s *RelayStore) MintLinkMode(base, name string, envelope bool) (Link, error)
- func (s *RelayStore) RemoveLiveActivity(deviceID, jobID string) error
- func (s *RelayStore) ResolveRevoke(arg string) (RevokeResolution, error)
- func (s *RelayStore) Revoke(id string) (DeviceRecord, error)
- func (s *RelayStore) RevokeRoom(id string) (RoomRecord, error)
- func (s *RelayStore) RoomStateFor(r RoomRecord) RoomState
- func (s *RelayStore) RotateAll(base, name string, envelope bool) (Link, error)
- func (s *RelayStore) SeedIdentityName(name string) (stored string, seeded bool, err error)
- func (s *RelayStore) ServedRooms() []RoomRecord
- func (s *RelayStore) SetDeviceJobCompletionPush(deviceID string, enabled bool) error
- func (s *RelayStore) SetDevicePushPreview(deviceID string, clear bool) error
- func (s *RelayStore) SetIdentityName(name string) error
- func (s *RelayStore) SetLiveActivity(deviceID, jobID, token string) error
- func (s *RelayStore) SetPush(deviceID, token, platform string) error
- func (s *RelayStore) SetPushKeyID(deviceID, keyID string) error
- func (s *RelayStore) TakeLiveActivityTargets(jobID string) ([]LiveActivityTarget, error)
- func (s *RelayStore) VerifyAndLink(room, deviceID, secret string) (VerifyResult, bool, error)
- type RevokeResolution
- type RoomRecord
- type RoomState
- type Server
- type Tools
- func (t *Tools) DownloadAttachment(_ context.Context, in mcpchan.DownloadInput) (string, bool)
- func (t *Tools) EditMessage(_ context.Context, in mcpchan.EditInput) (string, bool)
- func (t *Tools) Job(ctx context.Context, in mcpchan.JobInput) (string, bool)
- func (t *Tools) PublishArtifact(_ context.Context, in mcpchan.PublishInput) (string, bool)
- func (t *Tools) React(_ context.Context, in mcpchan.ReactInput) (string, bool)
- func (t *Tools) Reply(ctx context.Context, in mcpchan.ReplyInput) (string, bool)
- type VerifyResult
Examples ¶
Constants ¶
const FleetChatPrefix = "fleet:"
FleetChatPrefix is the chat_id namespace operator tools refuse (F11).
const FleetKindList = "brief|task|result|ack|ping|cancel|status_req|refuse"
FleetKindList is the human-readable enum for tool schemas and error strings.
const FleetMaxEdges = fleetMaxEdges
FleetMaxEdges exposes the live-edge cap for CLI reporting.
const ProtocolVersion = 2
Variables ¶
var ( // ErrCodeStrikeout is the 3-strike abort (design §4.6): too many wrong codes. ErrCodeStrikeout = errors.New("code linking: too many wrong attempts — code cancelled") // ErrCodeExpired is the box-enforced TTL abort. ErrCodeExpired = errors.New("code linking: code expired before it was claimed") // create returned 503 provider_unavailable, so the caller should print the // standard pair URI/QR for the already-minted room instead of a code. ErrCodesUnavailable = errors.New("code linking: codes temporarily unavailable") )
Sentinel outcomes so the CLI can print the right message / exit nonzero.
var ErrMailboxFull = errors.New("mailbox full")
Functions ¶
func DeleteDeviceViaCore ¶
DeleteDeviceViaCore is a CLI helper (`hotline relay revoke` in core mode): it removes a device from the core registry (SPEC §2.3, idempotent).
func FleetAudit ¶
FleetAudit appends one operator-audit line to <stateDir>/fleet.log from a CLI process — the same append-only log the running box writes session lines to, so grant/revoke land in the operator's single fleet timeline. Best-effort and nil-safe.
func FleetPairingURI ¶
FleetPairingURI builds a purpose-bound fleet pair URI (A2A v2 §2): the operator form plus the additive p=fleet param and a required e=1 envelope. `fleet join` requires p=fleet; the operator redeem path refuses it (PairParams.validateOperator). Old parsers ignore the unknown p param.
func FleetStalePending ¶
FleetStalePending reports the F2 liveness alarm for one edge: it holds undelivered outbound, it has no live session, and its last contact is older than fleetStalePendingAfter (an edge that was NEVER seen counts as stale the moment it has something queued for it). Exported so the CLI — which cannot read the running box's session map — applies the identical predicate to its disk-derived view.
func MaxRooms ¶
func MaxRooms() int
MaxRooms exposes the effective served-room cap for status/CLI reporting.
func PairingURI ¶
Example ¶
fmt.Println(PairingURI("ws://127.0.0.1:8787", "Ab3dEf6hIj8lMn0pQr2tUv", "Ab3dEf6hIj8lMn0pQr2tUvWx4zAb3dEf6hIj8lMn0pQ", "pi") != "")
Output: true
func PairingURIMode ¶
PairingURIMode builds the pair URI, adding the additive core-v1 e=1 param when envelope is set (SPEC §1.2). e=1 marks the pairing envelope-mode; old parsers ignore the unknown param per v2 rules. The plaintext form (envelope=false) is byte-identical to the pre-core PairingURI output.
func PushTestViaCore ¶
func PushTestViaCore(stateDir, baseURL, room, deviceID string) (pushed bool, reason string, err error)
PushTestViaCore is a CLI helper (`hotline relay push-test`): it builds a bounded core client from the box key and sends a push-test hint (SPEC §2.5). It returns whether a push was sent and any reason the core reported.
func RefusePairURIForOperator ¶
RefusePairURIForOperator parses a pair URI and refuses it when it is a fleet URI (§2, F7). It is the reusable operator-redeem guard: any Go operator redeem path calls it before treating a URI as an operator pairing. (The Go tree has no operator redeem COMMAND today — the apps parse URIs on redeem — so this is the library form the guarantee lives in.)
func RunNewLinkCode ¶
RunNewLinkCode runs the box-initiator PAKE for an already-minted envelope room (design §6.1). The room MUST have been minted with MintLinkMode(envelope=true) so link.Secret is present — the box registers it (so it is live before the device dials), generates the human code, and drives create → poll → verify → finish. On a claimed, verified client it seals the pair URI and posts it; the caller relays byte-identically into the frozen paste-link flow. Blocks until success, the 3-strike cap, TTL, or ctx cancellation.
func ToCommonMark ¶
ToCommonMark translates a reply/edit body from the reply tool's `format` into the plain CommonMark the app channel renders client-side.
The app is not Telegram: it consumes CommonMark-ish markdown, so the two Telegram-native formats have to be down-converted or their escape backslashes and tag/entity syntax leak onto the screen (the mdv2 "\." bug). The contract:
- "text": pass through unchanged.
- "markdownv2": strip mdv2 escape backslashes and map syntax to CommonMark.
- "html": convert the basic Telegram HTML tag set to CommonMark.
- anything else: treated as plain text (passed through).
This is a pragmatic bridge translator, not a full parser. The load-bearing invariants are: never leak an escape backslash, and never corrupt a code span or pre block.
Types ¶
type AgentCatalog ¶
AgentCatalog is the box-held model catalog, sourced from the harness and mirrored to devices on the transient agent_catalog frame. The zero value (no Models) means "no catalog" and is never emitted.
type AgentInfo ¶
type AgentInfo struct {
Harness string
Model string
Effort string
// ModelKnown / EffortKnown record that the HARNESS reported this field, as
// opposed to it merely being empty because nothing ever said otherwise.
//
// Empty-means-unknown cannot express a clear (hot-clear amendment). A model
// cleared back to the harness default produces an empty value that used to
// be indistinguishable from "not reported yet" — so the box merged nothing,
// kept advertising the OLD model, and its no-op check went on believing that
// model was live. Re-selecting it later was answered "already effective" and
// never applied, with the app showing a model the session was not running.
//
// With these set, an empty-and-known field means "the harness says there is
// no explicit value here", and the config fallback (which reads the boot
// env, not the live session) is correctly skipped.
ModelKnown bool
EffortKnown bool
}
AgentInfo is the box-side harness/model identity carried as optional, additive wire metadata on the two identity envelopes (welcome §3.2 and the agent_state snapshot §1.2). All fields optional; absent = unknown (old boxes). Harness is the kind ("claude" = the TUI, "claude-sdk", "pi", "opencode"); Model is the resolved or configured model id; Effort is the operator effort knob verbatim.
type AppVersion ¶
AppVersion is the box binary's version info (cmd/hotline main.version/commit/date), resolved in package main and threaded into NewProvider so the caps builder — which lives in internal/app and cannot read package main — can stamp bin{} (caps-design §1, box-read).
type DeviceCreds ¶
type DeviceCreds struct {
DeviceID string `json:"device_id"`
Room string `json:"room"`
RelayURL string `json:"relay_url"`
Secret string `json:"secret,omitempty"`
Envelope bool `json:"envelope"`
}
DeviceCreds is the dial-side (direction=dial) credential set the Lane L3 dialer will use to run the device hello against the peer's fleet room. Stored on the registry entry AND mirrored into the edge state.json. Secret is zeroed on rm (patched in place — the cursor is never reset).
type DeviceRecord ¶
type DeviceRecord struct {
ID string `json:"id"`
Room string `json:"room"`
SecretHash string `json:"secret_hash"`
PushToken string `json:"push_token,omitempty"`
PushPlatform string `json:"push_platform,omitempty"`
// PushKeyID is the gateway credential id returned by /registrations/complete
// for the current PushToken (gateway mode only; empty for the Expo path).
// PushRegState tracks the registration lifecycle: "" (none/needs registration),
// "pending", "active", or "dropped".
PushKeyID string `json:"push_key_id,omitempty"`
PushRegState string `json:"push_reg_state,omitempty"`
// PushPreviewClear is the device's own push-preview preference (FB23): true =
// this device wants the full message text in its push body, false = generic
// "New Message". A nil pointer means the device never expressed a preference,
// so the wake path falls back to the box env default (HOTLINE_PUSH_PREVIEW).
// omitempty keeps a device that never toggled byte-identical to the old shape.
PushPreviewClear *bool `json:"push_preview_clear,omitempty"`
// JobCompletionPush is the device's FB44 successful-job notification
// preference. nil defaults enabled for additive state compatibility; false
// opts this device out and true explicitly opts it in.
JobCompletionPush *bool `json:"job_completion_push,omitempty"`
// LiveActivities holds direct APNs ActivityKit registrations by active job.
// It is additive and omitted for devices that have never registered one.
LiveActivities map[string]LiveActivityRegistration `json:"live_activities,omitempty"`
State DeviceState `json:"state"`
LinkedAt string `json:"linked_at"`
}
type DeviceState ¶
type DeviceState string
const ( DeviceActive DeviceState = "active" DeviceUnbound DeviceState = "unbound" DeviceBanned DeviceState = "banned" // DeviceRevoked is the legacy non-terminal state written by link rotation // before explicit operator bans had their own durable state. DeviceRevoked DeviceState = "revoked" )
type Element ¶
type Element struct {
El string `json:"el"`
ID string `json:"id"`
Fallback string `json:"fallback"`
// chip
Kind string `json:"kind,omitempty"`
Label string `json:"label,omitempty"`
Value string `json:"value,omitempty"`
// job
Title string `json:"title,omitempty"`
State string `json:"state,omitempty"`
Detail string `json:"detail,omitempty"`
StartedAt int64 `json:"startedAt,omitempty"`
Progress *float64 `json:"progress,omitempty"`
// decision
Prompt string `json:"prompt,omitempty"`
Options []decisionOption `json:"options,omitempty"`
ChosenKey *string `json:"chosenKey,omitempty"`
// approval
ApproveLabel string `json:"approveLabel,omitempty"`
DenyLabel string `json:"denyLabel,omitempty"`
Resolved *string `json:"resolved,omitempty"`
// checklist
Items []checklistItem `json:"items,omitempty"`
}
Element is the union of every element variant. json omitempty keeps a marshaled element to exactly the fields its variant uses, so the wire frame carries no empty variant fields and golden frames stay stable. Field order here is the marshaled order (encoding/json emits struct fields in declaration order), so it is deliberately: common fields, then chip, job, decision, approval, checklist.
type FleetAuthority ¶
type FleetAuthority struct {
KeyFP string `json:"key_fp"`
GrantedAt string `json:"granted_at"`
// ExpiresAt is the RFC3339 TTL horizon. Empty means no expiry (revoke-only).
ExpiresAt string `json:"expires_at,omitempty"`
}
FleetAuthority is the operator-granted orchestrator authority on ONE edge of the RECEIVING box's registry (§A1). Its presence flips inbound framing for the typed down-kinds only. KeyFP is the peer box-key fingerprint the grant is bound to, copied from the edge's pin at grant time — the grant applies only while the edge's live pin still equals it.
type FleetCaps ¶
type FleetCaps struct {
V int `json:"v"`
At string `json:"at"`
Box string `json:"box,omitempty"`
KeyFP string `json:"key_fp,omitempty"`
Bin *FleetCapsBin `json:"bin,omitempty"`
UptimeS int64 `json:"uptime_s,omitempty"`
StartedAt string `json:"started_at,omitempty"`
Harness *FleetCapsHarness `json:"harness,omitempty"`
Loops *FleetCapsList `json:"loops,omitempty"`
Schedules *FleetCapsList `json:"schedules,omitempty"`
Fleet *FleetCapsFleet `json:"fleet,omitempty"`
}
FleetCaps is the box-attested capabilities manifest (caps-design §1). Every field is optional except V and At; an absent field means UNKNOWABLE on that box (the builder never guesses). Exported so the CLI (`fleet ls --json`) and the fleet MCP tool (`action:"caps"`) can render a stored peer manifest.
type FleetCapsBin ¶
type FleetCapsBin struct {
Version string `json:"version,omitempty"`
Commit string `json:"commit,omitempty"`
Date string `json:"date,omitempty"`
}
FleetCapsBin is the box binary identity (versionInfo, box-read).
type FleetCapsFleet ¶
type FleetCapsFleet struct {
Edges int `json:"edges"`
}
FleetCapsFleet is the live (non-tombstoned) edge count only — no aliases/ids: v1 does not ship topology to peers (caps-design §1).
type FleetCapsHarness ¶
type FleetCapsHarness struct {
Kind string `json:"kind,omitempty"`
StartedAt string `json:"started_at,omitempty"`
Model string `json:"model,omitempty"`
Effort string `json:"effort,omitempty"`
}
FleetCapsHarness is the box's harness identity. Kind/StartedAt are box-read; Model and Effort are harness-REPORTED (over the control channel) and box-relayed — one hop weaker, still not agent-token-writable (caps-design §4, two-tier provenance).
type FleetCapsList ¶
type FleetCapsList struct {
Count int `json:"count"`
Active int `json:"active"`
Names []string `json:"names,omitempty"`
Truncated bool `json:"truncated,omitempty"`
}
FleetCapsList is a count/active tally with a bounded, cadence-only NAME list (caps-design §1). For loops the name is the label; for schedules it is id + " " + Describe(recurrence) — NEVER the prompt/body (prompts can hold private detail; the id+cadence is the capability).
type FleetDirection ¶
type FleetDirection string
FleetDirection is the pairing role for an edge: serve (this box minted the room and answers the peer's device hello) or dial (this box holds device creds and dials the peer's room — Lane L3).
const ( FleetServe FleetDirection = "serve" FleetDial FleetDirection = "dial" )
type FleetEdge ¶
type FleetEdge struct {
EdgeID string `json:"edge_id"`
Alias string `json:"alias"`
Direction FleetDirection `json:"direction"`
Room string `json:"room"`
RelayURL string `json:"relay_url"`
RelayOrigin string `json:"relay_origin,omitempty"`
Secret string `json:"secret,omitempty"`
DeviceCreds *DeviceCreds `json:"device_creds,omitempty"`
Envelope bool `json:"envelope"`
PeerBoxName string `json:"peer_box_name,omitempty"`
PeerKeyFP string `json:"peer_key_fp,omitempty"`
AddedAt string `json:"added_at"`
LastSeenAt string `json:"last_seen_at,omitempty"`
Tombstone *FleetTombstone `json:"tombstone,omitempty"`
// Authority is the F1 operator-granted orchestrator authority for INBOUND frames
// on this edge (fleetauthority.go). It is written ONLY by `hotline fleet
// grant|revoke` — never by any wire, session, or tool path — and is cleared with
// the creds on any removal.
Authority *FleetAuthority `json:"authority,omitempty"`
// Unreachable is the F2 recoverable dead-mark (dial side): set when the dialer
// drops to the cold-retry tier, cleared by the next successful handshake. Never a
// tombstone — the edge stays live and its creds are retained.
Unreachable *FleetUnreachable `json:"unreachable,omitempty"`
}
FleetEdge is one registry entry (§1). edge_id is the immutable durable address everywhere (chat_id, journal, logs — F17); alias is display-only and renameable. Envelope is REQUIRED (e=1 always, F1). Serve edges keep the raw room Secret so the fleet handler can derive the e1 content keys; dial edges keep DeviceCreds instead. RelayURL is always the NORMALIZED rendezvous (B7); RelayOrigin records the operator-approved origin the dialer is allowed to use.
func (FleetEdge) AuthorityStatus ¶
AuthorityStatus renders the operator-facing state of an edge's grant for `fleet ls` and the fleet tool: "" (no grant), "granted", "granted,expires_in=…", or "expired". Display only — never the framing decision (that is fleetAuthorityFor).
func (FleetEdge) HasAuthority ¶
HasAuthority reports whether a live, key-bound, unexpired grant exists (the boolean the operator surfaces carry). Not the framing gate — kind gating lives in fleetAuthorityFor.
func (FleetEdge) Redacted ¶
func (e FleetEdge) Redacted() FleetEdgeView
Redacted is the exported secret-free view of an edge (B5), for CLI JSON output.
type FleetEdgeView ¶
type FleetEdgeView struct {
EdgeID string `json:"edge_id"`
Alias string `json:"alias"`
Direction FleetDirection `json:"direction"`
Room string `json:"room"`
RelayURL string `json:"relay_url"`
RelayOrigin string `json:"relay_origin,omitempty"`
Envelope bool `json:"envelope"`
DeviceID string `json:"device_id,omitempty"`
PeerBoxName string `json:"peer_box_name,omitempty"`
PeerKeyFP string `json:"peer_key_fp,omitempty"`
AddedAt string `json:"added_at"`
LastSeenAt string `json:"last_seen_at,omitempty"`
Tombstone *FleetTombstone `json:"tombstone,omitempty"`
// Authority is the F1 grant (never a secret — it is a key fingerprint the operator
// already sees, plus timestamps), so the operator surfaces can render it.
Authority *FleetAuthority `json:"authority,omitempty"`
// Unreachable is the F2 recoverable dead-mark, surfaced so the operator can see a
// cold-retrying edge without tailing fleet.log.
Unreachable *FleetUnreachable `json:"unreachable,omitempty"`
}
FleetEdgeView is the redacted DTO for `fleet ls --json` (B5): it NEVER carries a serve secret or a dial device_creds.secret. The storage struct (FleetEdge) is never serialized to a caller.
type FleetPeerCaps ¶
FleetPeerCaps is the per-edge stored peer manifest (caps-design §3): the received, clamped manifest plus the LOCAL clock arrival time (skew-proof staleness anchor).
type FleetProvider ¶
type FleetProvider struct {
// contains filtered or unexported fields
}
FleetProvider is Lane L2's fleet channel: a Provider registered in NewRouter (a2a-design-v2 §3.3/§4) whose ONLY job on the inbound side is to hand the box a source="fleet"-tagged InboundSink — the serve-side fleet session (fleetsession.go) injects through it. The fleet room manager itself runs inside the shared app Server's Run, so this provider does not serve rooms; it binds the sink and blocks. It is a HIDDEN source: it never appears in the operator tool schemas' source enum (reply/react/… stay single-source on an app-only box), and it REFUSES every operator ToolSet method — fleet peers are reachable only through fleet_send (F11). It also implements mcpchan.FleetTools (the fleet + fleet_send tools), backed by the same shared Server.
func NewFleetProvider ¶
func NewFleetProvider(ap *Provider) (*FleetProvider, bool)
NewFleetProvider builds the fleet channel sharing the app provider's Server (so the fleet room manager, registry, session registry, and box identity are one). ok=false when the app box has no fleet store (empty state dir) — the caller then skips fleet entirely.
func (*FleetProvider) Capabilities ¶
func (p *FleetProvider) Capabilities() provider.Capabilities
func (*FleetProvider) DownloadAttachment ¶
func (p *FleetProvider) DownloadAttachment(context.Context, mcpchan.DownloadInput) (string, bool)
func (*FleetProvider) EditMessage ¶
func (*FleetProvider) FleetCaps ¶
FleetCaps implements mcpchan.FleetTools (caps-design §3): the FULL box-attested capabilities manifest(s) for routing work by capability. peer != "" selects one edge; "" returns every non-tombstoned edge. box-attested by the peer's OWN box — honest-box assumption, displayed under the pinned peer key; never "verified".
func (*FleetProvider) FleetList ¶
func (p *FleetProvider) FleetList(_ context.Context) (string, bool)
FleetList implements mcpchan.FleetTools: the redacted registry + static liveness (connected, last_seen, pending depth, dropped counter). Never a secret.
func (*FleetProvider) FleetSend ¶
func (p *FleetProvider) FleetSend(ctx context.Context, in mcpchan.FleetSendInput) (string, bool)
FleetSend implements mcpchan.FleetTools: resolve the edge, stamp the box's OWN identity into from{} (the agent cannot set it — E3), journal the frozen wire fleet_msg, queue it durably, and deliver it (serve: live session push or queued for next attach; dial: queued for the Lane-L3 dialer). Success is DURABLY QUEUED, not consumed (F9).
func (*FleetProvider) HiddenSource ¶
func (p *FleetProvider) HiddenSource() bool
HiddenSource marks fleet as non-operator-selectable: the router keeps it for inbound Start + refusal but excludes it from Sources() so the human tools do not grow a required "source" arg on an otherwise single-provider box.
func (*FleetProvider) Name ¶
func (p *FleetProvider) Name() string
func (*FleetProvider) OnPermissionRequest ¶
func (p *FleetProvider) OnPermissionRequest(context.Context, mcpchan.PermissionRequestParams)
func (*FleetProvider) React ¶
func (p *FleetProvider) React(context.Context, mcpchan.ReactInput) (string, bool)
func (*FleetProvider) Reply ¶
func (p *FleetProvider) Reply(context.Context, mcpchan.ReplyInput) (string, bool)
The operator ToolSet methods all refuse: a fleet peer is not an operator chat.
func (*FleetProvider) Start ¶
func (p *FleetProvider) Start(ctx context.Context, sink provider.InboundSink) error
Start binds the fleet-tagged inbound sink (the router wraps it with source="fleet") into the shared Server, then blocks until shutdown. The actual fleet room serving is driven by the app Server's Run.
After binding, it kicks off the fleet-journal replay (H2): every inbound fleet_msg that was journaled but never delivered to the agent (a box that died between journal and inject, or restarted before the sink bound) is re-injected exactly once — the fleet journal is the replay source of truth, since Kind:"fleet" rows are deliberately excluded from the generic transcript catch- up. Run in a goroutine so a sink that back-pressures on a not-yet-ready consumer cannot block the provider's Start handshake.
type FleetStore ¶
type FleetStore struct {
// contains filtered or unexported fields
}
FleetStore is the fleet registry. It is stateless between calls — every operation loads fresh from disk under a filesystem lock — so a box process and a CLI process share one authoritative view.
func OpenFleetStore ¶
func OpenFleetStore(stateDir string) (*FleetStore, error)
OpenFleetStore returns the registry handle under <stateDir>/fleet/. It does not read or write anything (the file is loaded fresh per operation).
func (*FleetStore) AppendJournalFrame ¶
func (s *FleetStore) AppendJournalFrame(edgeID, dir string, frame json.RawMessage) (uint64, error)
AppendJournalFrame appends one FROZEN journal entry wrapping a complete wire frame, fsyncing BEFORE returning so acceptance is durable (B4). The monotonic seq is derived from the journal tail under the flock. A write/sync failure is returned so the caller rejects the frame (never shrugged).
func (*FleetStore) BoundPeerCaps ¶
func (s *FleetStore) BoundPeerCaps(edge FleetEdge) (*FleetPeerCaps, error)
BoundPeerCaps is the ONE pin-aware accessor for the CLI + MCP surfaces (review B4): it returns the stored manifest ONLY when it is bound to the edge's pinned peer key AND the edge carries no persisted KeyFPMismatch (a mismatch overrides attestation until the edge is removed). Otherwise nil — so no surface ever renders an unbound or mismatched manifest under box-attested language. edge carries the authoritative pin (registry).
func (*FleetStore) ClearEdgeUnreachable ¶
func (s *FleetStore) ClearEdgeUnreachable(edgeID string) (cleared bool, err error)
ClearEdgeUnreachable revives an edge on a successful handshake (F2). cleared reports whether a dead-mark was actually present, so the caller logs the revival once.
func (*FleetStore) CommitInbound ¶
func (s *FleetStore) CommitInbound(edgeID, cid string, frame json.RawMessage, rateAllow func() bool) (inboundCommit, error)
func (*FleetStore) DialEdges ¶
func (s *FleetStore) DialEdges() ([]FleetEdge, error)
DialEdges returns the dial-direction, non-tombstoned edges the dial manager drives (L3), sorted by edge id.
func (*FleetStore) EdgeActivity ¶
func (s *FleetStore) EdgeActivity(edgeID string) (sent24h, recv24h, dropped24h uint64, err error)
EdgeActivity returns an edge's rolling 24h sent/recv/dropped fleet_msg counts (L4, §6), computed fresh from the persisted per-hour buckets.
func (*FleetStore) EdgeState ¶
func (s *FleetStore) EdgeState(edgeID string) (fleetEdgeState, error)
EdgeState returns a copy of an edge's runtime state.json (introspection/tests).
func (*FleetStore) Edges ¶
func (s *FleetStore) Edges() ([]FleetEdge, error)
Edges returns every registry entry (including tombstoned) sorted by edge_id.
func (*FleetStore) EnqueueOutboundTx ¶
func (s *FleetStore) EnqueueOutboundTx(edgeID, cid string, frame json.RawMessage) (seq uint64, dropped bool, err error)
EnqueueOutboundTx is the outbound path for fleet_send (review B4): under a SINGLE flock it revalidates the edge is still live (fail-closed against a concurrent rm — no queuing against a tombstone) and journals the frozen wire frame dir=out. The journal IS the outbound WAL — there is no second state write to strand a frame in, so a crash the instant after the journal fsync still recovers the frame (it is an unacked dir=out record, which PendingOutbound derives on the next attach). dropped reports whether the derived pending queue is now over the cap (oldest excluded from the drain set, logged by the caller).
func (*FleetStore) FlagKeyFPMismatch ¶
func (s *FleetStore) FlagKeyFPMismatch(edgeID string) error
FlagKeyFPMismatch persists the M5 identity-mismatch flag on the edge state.
func (*FleetStore) GrantAuthority ¶
GrantAuthority is the OPERATOR-ONLY write path for orchestrator authority (§A1) — reachable from `hotline fleet grant` and nowhere else. It binds the grant to the edge's CURRENT pinned peer key, so a grant can never be minted for an unidentified peer, and refuses outright on a tombstoned edge or one carrying a key mismatch. ttl <= 0 means no expiry (revoke-only).
func (*FleetStore) InboundDelivered ¶
func (s *FleetStore) InboundDelivered(edgeID, cid string) (bool, error)
InboundDelivered reports whether an inbound cid was already injected.
func (*FleetStore) InboundSeen ¶
func (s *FleetStore) InboundSeen(edgeID, cid string) (bool, error)
InboundSeen reports whether an inbound cid is in the durable journal (test/support).
func (*FleetStore) IncDroppedInbound ¶
func (s *FleetStore) IncDroppedInbound(edgeID string) error
IncDroppedInbound bumps the persisted inbound-drop counter (rate cap, §5).
func (*FleetStore) Join ¶
func (s *FleetStore) Join(uri, alias string, opts JoinOptions) (FleetEdge, error)
Join parses a fleet pair URI (strict, §2), requires p=fleet + e=1, enforces the relay-origin allowlist (B7), generates a stable device id flt-<edgeID>, and persists the NORMALIZED creds in the edge dir with a registry entry direction=dial. It does NOT dial the peer — dialing is Lane L3.
func (*FleetStore) JournalEntries ¶
func (s *FleetStore) JournalEntries(edgeID string) ([]fleetJournalEntry, error)
JournalEntries returns the parsed journal entries for an edge (introspection + startup replay), across both rotation generations oldest-first (L4, §6).
func (*FleetStore) Link ¶
func (s *FleetStore) Link(base, alias string) (FleetEdge, string, error)
Link mints a fleet room DIRECTLY into fleet.json (serve side, §2). It reuses the mintRoom recipe (22-char room id + 32-byte secret + e=1) but NEVER MintLinkMode, so relay-state.json is byte-untouched. It fails at the 16-edge cap and on an alias collision. The edge dir is STAGED before the registry is published (B6), so a failure can never leave an active ghost edge.
func (*FleetStore) LiveEdge ¶
func (s *FleetStore) LiveEdge(edgeID string) (FleetEdge, bool)
LiveEdge loads ONE non-tombstoned edge fresh from disk. It is the authoritative per-frame check the fleet session uses (B2): a corrupt registry, a missing edge, or a tombstone all return ok=false (fail-closed → the session terminates).
func (*FleetStore) MarkDialEdgeDead ¶
func (s *FleetStore) MarkDialEdgeDead(edgeID, reason string) error
MarkDialEdgeDead tombstones a dial edge the peer has revoked, whose room is gone, or whose creds are unrecoverable (review B5/SF3): the dialer stops dialing and the operator sees it dead in `fleet ls`. It mirrors Remove (tombstone + zero creds + patch state secret, cursor preserved) but records the specific reason. A no-op on an already-dead or missing edge. Local-only — the peer holds the room.
func (*FleetStore) MarkEdgeUnreachable ¶
func (s *FleetStore) MarkEdgeUnreachable(edgeID string, attempts int) error
MarkEdgeUnreachable records the F2 recoverable dead-mark on a live edge: the dialer has failed `attempts` consecutive handshakes on a known-good edge and has dropped to the cold-retry tier. It does NOT tombstone and does NOT zero creds — that is the whole point — so `unreachable` stays categorically distinct from the operator's `removed` and the peer's `revoked`. Re-marking an already-unreachable edge refreshes the attempt count and keeps the original Since.
func (*FleetStore) MarkInboundDelivered ¶
func (s *FleetStore) MarkInboundDelivered(edgeID, cid string) error
MarkInboundDelivered records that an inbound cid was injected to the agent (H2), so startup replay never re-injects it. Idempotent; FIFO-bounded.
func (*FleetStore) PeerCaps ¶
func (s *FleetStore) PeerCaps(edgeID string) (*FleetPeerCaps, error)
PeerCaps returns the stored peer manifest for an edge (caps-design §3), or nil when none has been received. Raw — NOT pin-checked; prefer BoundPeerCaps for any surface that renders under box-attested language.
func (*FleetStore) PendingDepth ¶
func (s *FleetStore) PendingDepth(edgeID string) (int, error)
PendingDepth returns the live unacked-outbound depth for an edge (L4 fleet ls / fleet_state), derived from the durable journal WAL — the accurate figure the legacy state.json Pending cache no longer holds.
func (*FleetStore) PendingOutbound ¶
func (s *FleetStore) PendingOutbound(edgeID string) ([]fleetPendingOut, error)
PendingOutbound returns the live outbound queue (oldest-first) DERIVED from the durable journal (review B4): every dir=out frame whose cid has no dir=ack marker, in journal order, capped to the newest fleetPendingCap (oldest excluded from the drain set). The journal is the single source of truth — a crash between the outbound fsync and any other write can never strand a frame.
func (*FleetStore) PinPeerKeyFP ¶
func (s *FleetStore) PinPeerKeyFP(edgeID, fp string) (pinned, mismatch bool, err error)
PinPeerKeyFP is the M5 trust-on-first-use pin: it sets the edge's PeerKeyFP in the registry the FIRST time a peer presents one, and reports a mismatch when a later, DIFFERENT fp arrives (the stored pin is never overwritten). pinned=true means this call set the pin; mismatch=true means fp differed from the pin.
func (*FleetStore) RecentInboundCIDs ¶
func (s *FleetStore) RecentInboundCIDs(edgeID string, n int) ([]string, error)
RecentInboundCIDs returns the last n inbound-committed cids (journal order) for a fleet_resume advertisement (review B2). Bounded so the frame stays compact.
func (*FleetStore) ReconcileInboundCursor ¶
func (s *FleetStore) ReconcileInboundCursor(edgeID string) (generation uint64, resynced bool, err error)
ReconcileInboundCursor realigns the persisted Cursor with the durable journal's inbound tail at edge load (review B3 — handles divergence in EITHER direction). The journal is the dedup + cursor authority, so a torn state.json (cursor ahead OR behind the journal) is repaired by setting Cursor = inbound tail and bumping Generation. resynced reports whether a realignment occurred.
func (*FleetStore) RecordPeerAckCID ¶
func (s *FleetStore) RecordPeerAckCID(edgeID, cid string) (found bool, remaining int, err error)
RecordPeerAckCID durably records that the peer acked an outbound cid (review B4): it appends a dir=ack WAL marker (fsync'd) so the drained state survives a crash — PendingOutbound never resurrects an acked frame. found reports whether an unacked dir=out record with that cid existed; remaining is the post-ack pending depth. Idempotent: a duplicate ack for an already-acked cid writes no second marker.
func (*FleetStore) Remove ¶
func (s *FleetStore) Remove(arg string) (FleetEdge, error)
Remove tombstones an edge (§2 rm), zeroing its creds while retaining the edge dir (journal history). Credential zeroing PATCHES only the secret fields — it never resets the cursor or rewrites state.json wholesale (B6). A state-patch failure is returned (visible/retriable), not swallowed.
func (*FleetStore) Rename ¶
func (s *FleetStore) Rename(arg, alias string) (FleetEdge, error)
Rename changes an edge's display alias (§1: alias renameable, edge_id immutable). It enforces the alias rules + active-uniqueness.
func (*FleetStore) ResolveEdge ¶
func (s *FleetStore) ResolveEdge(arg string) (FleetEdge, error)
ResolveEdge maps a CLI/tool arg (alias, edge_id, or unique edge_id prefix) to exactly one live edge, loading fresh under the lock. It is the fleet_send address resolver (§4). Zero/ambiguous/tombstoned all error.
func (*FleetStore) RevokeAuthority ¶
func (s *FleetStore) RevokeAuthority(arg string) (edge FleetEdge, had bool, err error)
RevokeAuthority drops an edge's grant (§A1: revocation is a registry write and takes effect on the NEXT inbound frame, because injectInbound re-reads the edge per directive-kind delivery). had reports whether a grant was actually present, so the CLI can say so plainly. Works on a tombstoned edge too — clearing a stale grant is always allowed.
func (*FleetStore) ServedFleetRooms ¶
func (s *FleetStore) ServedFleetRooms() ([]FleetEdge, error)
ServedFleetRooms returns the serve-direction, non-tombstoned, STRUCTURALLY VALID edges the fleet room manager can serve (§3.1, should-fix 3), sorted by room id. An invalid registry surfaces as an error so the caller logs instead of silently serving a malformed edge.
func (*FleetStore) SetPeerBoxName ¶
func (s *FleetStore) SetPeerBoxName(edgeID, name string) error
SetPeerBoxName records the peer's self-reported box display name at the INITIAL key pin only (review SF2). It is control-stripped and clamped; a tombstoned/ missing edge or a name change on an already-pinned edge is a no-op.
func (*FleetStore) SetPeerCaps ¶
func (s *FleetStore) SetPeerCaps(edgeID string, c FleetCaps) error
SetPeerCaps stores the peer's box-attested capabilities manifest on the edge state (caps-design §3, latest-wins) with a local received_at. The manifest is already clamped by the caller (storePeerCaps). A tombstoned/missing edge simply writes its retained state dir — caps only ever arrive on a live session, so this is benign.
func (*FleetStore) TouchLastSeen ¶
func (s *FleetStore) TouchLastSeen(edgeID string)
TouchLastSeen records a peer contact time (best-effort; used by the fleet handler). It is flock-guarded like every mutation so it cannot race a concurrent CLI rm and resurrect a tombstone (B1/B2): a tombstoned or missing edge is left untouched.
type FleetTombstone ¶
FleetTombstone marks a removed edge (§2 rm). The edge dir (journal history) is retained; only the creds are zeroed and the edge is excluded from serving.
type FleetUnreachable ¶
type FleetUnreachable struct {
Since string `json:"since"`
Attempts int `json:"attempts"`
LastAt string `json:"last_at,omitempty"`
}
FleetUnreachable is the F2 RECOVERABLE dead-mark: a known-good dial edge whose handshakes have been failing long enough that the dialer dropped to the cold-retry tier. It is deliberately NOT a tombstone — creds are retained, the edge keeps serving and accepting frames, and one successful handshake clears it. Operator removal (`removed`) and peer revocation (`revoked`) stay terminal tombstones; only this state, which nothing but a network outage produces, is reversible.
It exists because permanence was wrong: an ~8-minute relay wobble (54 × code=1006 "unexpected EOF" in 24h) retired a working box-to-box edge forever, with no revive path in the CLI, while the peer kept queueing frames for a box that had written it off.
type JobDriver ¶
type JobDriver struct {
// contains filtered or unexported fields
}
JobDriver adapts the app channel's job registry to the automatic jobspool dispatcher. Its method set matches jobspool.JobSink structurally, so the dispatcher drives the SAME cards the manual `job` MCP tool drives — one card lifecycle, one registry — without either package importing the other.
func (*JobDriver) DoneCard ¶
DoneCard terminalizes a card (ok|err|cancelled) with an optional buzz line.
Every closure through this path is AUTOMATIC — a completion hook, the lease reaper, or the restart sweep decided it, not the agent — so it is recorded as correctable: a later explicit `job done` from the agent that owns the work may still overwrite it. See jobs.go finishedJob.
func (*JobDriver) RehydrateCard ¶
func (d *JobDriver) RehydrateCard(jobID, elementID, msgID, chatID, title, detail string, startedAt int64, progress *float64)
RehydrateCard re-registers a card that survived a restart so the following Update/Done resolves against the surviving message instead of erroring.
func (*JobDriver) StartCard ¶
func (d *JobDriver) StartCard(title, detail, chatID string, progress *float64) (jobID, msgID, elementID string, err error)
StartCard creates a running card. It errors (rather than opening a phantom card) when no device can receive it, so the dispatcher retries once one links.
type JoinOptions ¶
JoinOptions carries the operator's relay-origin allowlist for a dial-side join (B7 SSRF gate). AllowedOrigins is the default set (the box's configured rendezvous); AllowRelay is an explicit operator override from `--allow-relay`.
type LiveActivityContent ¶
type LiveActivityContent struct {
Title string
State string
Detail string
Progress *float64
StartedAt int64
}
LiveActivityContent is the exact content-state object sent to ActivityKit.
type LiveActivityRequest ¶
type LiveActivityRequest struct {
DeviceID string
JobID string
Token string
Event string
Timestamp int64
Content LiveActivityContent
}
LiveActivityRequest is one immutable ActivityKit lifecycle event. Tokens and Content are transport-private and must never be logged.
type LiveActivityTarget ¶
LiveActivityTarget is an immutable store snapshot used by the asynchronous APNs sender. Token remains private transport data and must never be logged.
type MailboxItem ¶
type PairParams ¶
type PairParams struct {
Version string
URL string // rendezvous base (ws://|wss://)
Room string
Secret string
Name string
Purpose string // p= ("" operator, "fleet" fleet lane)
Envelope string // e= ("1" when envelope-mode)
}
PairParams is the parsed content of a hotline://pair URI.
func ParsePairURI ¶
func ParsePairURI(raw string) (PairParams, error)
ParsePairURI strictly parses a hotline://pair?... URI into its params. It validates the scheme/host, rejects userinfo/path/fragment and duplicate params, and requires v/u/r/s. Purpose- and envelope-specific validation is left to validateFleet / validateOperator so the same parse serves both roles.
func (PairParams) IsFleet ¶
func (p PairParams) IsFleet() bool
IsFleet reports whether the URI is purpose-bound to the fleet lane.
type Provider ¶
type Provider struct {
// contains filtered or unexported fields
}
func NewProvider ¶
func NewProvider(name string, cfg *config.Config, log *transcript.Logger, agent AgentInfo, boxRoot string, version AppVersion) (*Provider, error)
NewProvider builds the app-channel provider. agent seeds the box identity metadata (harness kind + configured model/effort) resolved by the caller — config reads stay out of internal/app; the harness_info notification refines it live via AgentInfoSink. boxRoot is this box's state root, so a model/effort change lands in THIS box's .env rather than the machine-wide one (sol review #10); empty = the default box, which is the base root. version stamps the box binary identity into the caps manifest's bin{} field (caps-design §1): it lives in package main and is resolved by the caller, keeping the version/VCS read out of internal/app exactly like the AgentInfo seed.
func (*Provider) AgentInfoSink ¶
func (p *Provider) AgentInfoSink() func(mcpchan.AgentInfoParams)
AgentInfoSink adapts the run child's harness_info notification (transport interception, mcpchan.AgentInfoParams) into the server's live identity merge — the same discovery pattern as JobDriver.
func (*Provider) Capabilities ¶
func (p *Provider) Capabilities() provider.Capabilities
func (*Provider) DownloadAttachment ¶
func (*Provider) EditMessage ¶
func (*Provider) HarnessCatalogSink ¶
func (p *Provider) HarnessCatalogSink() func(mcpchan.HarnessCatalogParams)
HarnessCatalogSink adapts the run child's harness_catalog notification (transport interception, mcpchan.HarnessCatalogParams) into the server's held catalog — the same discovery pattern as AgentInfoSink, one level up: where that carries the one live model, this carries the selectable set. Everything the harness reports is sanitized and bounded on the way in.
func (*Provider) Job ¶
Job implements the optional mcpchan.JobRunner interface: the app channel owns the job registry and the live job card, so it always handles the tool.
func (*Provider) JobDriver ¶
JobDriver exposes the app provider's job registry to the jobspool dispatcher.
func (*Provider) OnPermissionRequest ¶
func (p *Provider) OnPermissionRequest(context.Context, mcpchan.PermissionRequestParams)
func (*Provider) PublishArtifact ¶
func (*Provider) SDKApplyResultSink ¶
func (p *Provider) SDKApplyResultSink() func(mcpchan.SDKApplyResultParams)
SDKApplyResultSink adapts the run child's sdk_apply_result notification (transport interception) into the server's pending-apply resolution — the mirror of AgentInfoSink.
func (*Provider) SetSDKApplyForwarder ¶
func (p *Provider) SetSDKApplyForwarder(fn func(ctx context.Context, rid string, model, effort *string) error)
SetSDKApplyForwarder binds the harness-bound hot-apply forwarder (SDK hot-model amendment 2026-07-19): the server calls it to forward a model-only set_sdk_config as a sdk_apply notification to the injected harness. Bound pre-Run by the claude-sdk wiring only (same posture as the agentInfo seed — no lock needed); nil keeps the restart path.
func (*Provider) TranscriptFile ¶
type RelayStore ¶
type RelayStore struct {
// contains filtered or unexported fields
}
func OpenRelayStore ¶
func OpenRelayStore(stateDir string) (*RelayStore, error)
func (*RelayStore) ActiveDevices ¶
func (s *RelayStore) ActiveDevices() []DeviceRecord
func (*RelayStore) ActiveLiveActivityTargets ¶
func (s *RelayStore) ActiveLiveActivityTargets(jobID string) []LiveActivityTarget
ActiveLiveActivityTargets snapshots every active, live-room device currently registered for jobID. The deterministic order is useful to lifecycle callers that synchronously enqueue an immutable request for each target.
func (*RelayStore) ActivePushTarget ¶
func (s *RelayStore) ActivePushTarget(deviceID string) (token, keyID, room string, ok bool)
ActivePushTarget returns the device's push token together with its OWN bound room (SPEC §5/MD3), read atomically under the store lock, and only when the device is currently active and its room is live (open|bound, not dead). Resolving the room via the device's binding — instead of the global current_room — is what makes push per-device across N concurrently served rooms. Reading the token and the room in a single locked snapshot preserves the atomic-snapshot property: no concurrent mint/revoke can pair a token with a stale room label.
func (*RelayStore) CurrentRoom ¶
func (s *RelayStore) CurrentRoom() (RoomRecord, bool)
func (*RelayStore) Device ¶
func (s *RelayStore) Device(deviceID string) (DeviceRecord, bool)
func (*RelayStore) Devices ¶
func (s *RelayStore) Devices() []DeviceRecord
func (*RelayStore) DropLiveActivityIfToken ¶
func (s *RelayStore) DropLiveActivityIfToken(deviceID, jobID, token string) (bool, error)
DropLiveActivityIfToken conditionally removes a registration only when it still carries the token APNs rejected. A replacement registered while the old request was in flight is therefore preserved.
func (*RelayStore) DropPushToken ¶
func (s *RelayStore) DropPushToken(deviceID string) error
DropPushToken permanently removes a device's push token and gateway credential after a terminal APNs rejection (410 / token_invalid / drop_token). A missing device is not an error (nothing to drop).
func (*RelayStore) IdentityName ¶
func (s *RelayStore) IdentityName() (string, bool)
IdentityName returns the box-owned assistant name (FB21) and whether it has been seeded yet. It reads fresh from disk so a rename done by another process (e.g. a device set_name handled by the running box while a CLI reads) is seen.
func (*RelayStore) MintLinkMode ¶
func (s *RelayStore) MintLinkMode(base, name string, envelope bool) (Link, error)
MintLinkMode mints a new pairing ADDITIVELY (SPEC §2.1): the new room is inserted into the rooms map alongside every existing room and device — no prior pairing is touched. It fails at the served-room cap. current_room is still pointed at the newest room for old-binary rollback (SPEC §6), but new code never routes on it. envelope=false is byte-for-byte the legacy per-room shape (SecretHash only, no e param, no Secret on disk).
The destructive whole-map rotation now lives in RotateAll.
func (*RelayStore) RemoveLiveActivity ¶
func (s *RelayStore) RemoveLiveActivity(deviceID, jobID string) error
RemoveLiveActivity idempotently unregisters one job from one device.
func (*RelayStore) ResolveRevoke ¶
func (s *RelayStore) ResolveRevoke(arg string) (RevokeResolution, error)
ResolveRevoke classifies a revoke argument against BOTH the device roster and the open-room set, accepting a unique prefix like the CLI's other id args (FB27). Devices and non-dead rooms share one prefix namespace: exactly one match total resolves; zero or more than one is an error (including a cross-kind prefix collision). A room that a live device rides is refused with guidance to revoke the device instead — a bound room must never be nuked by room-id without its device.
func (*RelayStore) Revoke ¶
func (s *RelayStore) Revoke(id string) (DeviceRecord, error)
func (*RelayStore) RevokeRoom ¶
func (s *RelayStore) RevokeRoom(id string) (RoomRecord, error)
RevokeRoom kills an OPEN (unredeemed) room by its full id (FB27): the record is deleted locally, freeing the slot it squatted. It refuses a room a live device rides — that path must go through device-id Revoke — so a bound room is never nuked out from under its device. The relay expires the room server-side, so no remote unregister call is needed (hotline-core exposes no room-delete control action).
func (*RelayStore) RoomStateFor ¶
func (s *RelayStore) RoomStateFor(r RoomRecord) RoomState
RoomStateFor reports the display state of a room (SPEC §2.4): dead if tombstoned, bound if a live device rides it, else open.
func (*RelayStore) RotateAll ¶
func (s *RelayStore) RotateAll(base, name string, envelope bool) (Link, error)
RotateAll is the destructive panic-button mint (SPEC §2.1, `new-link --rotate-all`): it unbinds every non-banned device and REPLACES the whole rooms map with the single new room. This is byte-for-byte the pre-multi-device MintLinkMode behavior and the only path that mass-unbinds. It ignores the cap (it collapses to one room).
func (*RelayStore) SeedIdentityName ¶
func (s *RelayStore) SeedIdentityName(name string) (stored string, seeded bool, err error)
SeedIdentityName installs the box identity ONCE (FB21 §1). If a name is already seeded it is left untouched and returned with seeded=false — the seed never re-rolls or overwrites on a later boot. Otherwise name becomes the durable identity, is persisted, and returned with seeded=true. The seed does NOT restamp existing room records (those stay as historical pre-connect placeholders); the live name reaches every device through the snapshot.
func (*RelayStore) ServedRooms ¶
func (s *RelayStore) ServedRooms() []RoomRecord
ServedRooms returns every room the connector should serve — every non-dead room — sorted by id for a deterministic spawn order (SPEC §3). It reloads the on-disk state so a `relay new-link` / `relay revoke` from a separate CLI process is observed within one poll.
func (*RelayStore) SetDeviceJobCompletionPush ¶
func (s *RelayStore) SetDeviceJobCompletionPush(deviceID string, enabled bool) error
SetDeviceJobCompletionPush records this device's FB44 successful-job push preference. Persisting a concrete bool distinguishes an explicit choice from nil, whose additive default is enabled. A missing device is not an error.
func (*RelayStore) SetDevicePushPreview ¶
func (s *RelayStore) SetDevicePushPreview(deviceID string, clear bool) error
SetDevicePushPreview records this device's own push-preview preference (FB23): clear=true wants the full message text in its push body, clear=false wants the generic "New Message". Persisting a concrete value (not nil) marks the preference explicit, so the wake path honors it over the box env default. A missing device is not an error (nothing to record).
func (*RelayStore) SetIdentityName ¶
func (s *RelayStore) SetIdentityName(name string) error
SetIdentityName renames the box identity (FB21 §4, device set_name). It also restamps every non-dead room's Name to the new identity (FB21 §5) so `hotline relay status` and reconnect placeholders (welcomeFrame room name) agree with the live name. Callers validate the name first.
func (*RelayStore) SetLiveActivity ¶
func (s *RelayStore) SetLiveActivity(deviceID, jobID, token string) error
SetLiveActivity registers or replaces one job's ActivityKit token for an active device. Adding a 33rd distinct job evicts the oldest registration.
func (*RelayStore) SetPush ¶
func (s *RelayStore) SetPush(deviceID, token, platform string) error
func (*RelayStore) SetPushKeyID ¶
func (s *RelayStore) SetPushKeyID(deviceID, keyID string) error
SetPushKeyID records the gateway credential id returned by a successful registration complete() and marks the device's push registration active.
func (*RelayStore) TakeLiveActivityTargets ¶
func (s *RelayStore) TakeLiveActivityTargets(jobID string) ([]LiveActivityTarget, error)
TakeLiveActivityTargets atomically snapshots and removes every registration for jobID. Terminal lifecycle code uses the returned tokens for one final end event after the durable clear has succeeded.
func (*RelayStore) VerifyAndLink ¶
func (s *RelayStore) VerifyAndLink(room, deviceID, secret string) (VerifyResult, bool, error)
type RevokeResolution ¶
RevokeResolution classifies a `relay revoke <arg>` target so the caller can route to the right kill path. Kind is "device" or "room"; ID is the full matched id.
type RoomRecord ¶
type RoomRecord struct {
ID string `json:"id"`
URL string `json:"url"`
Name string `json:"name"`
SecretHash string `json:"secret_hash"`
CreatedAt string `json:"created_at"`
// State is the multi-device room lifecycle marker. Empty means an active
// (open|bound) room — only a "dead" room is persisted explicitly (see
// RoomState). Old relay-state files have no state and load as active.
State RoomState `json:"state,omitempty"`
// Envelope marks an e1 E2E pairing (core-v1 SPEC §1): the connector wraps
// every v2 frame in the e1 envelope for this room's lifetime. Empty/false for
// every plaintext room (the default, and every room minted outside core mode).
Envelope bool `json:"envelope,omitempty"`
// Secret is the raw pairing secret, persisted ONLY for envelope rooms so the
// box can derive the e1 content keys and the register auth_hash. Plaintext
// rooms keep only SecretHash (the secret never lands on disk), so a box that
// never enters core mode has byte-for-byte the same relay-state.json as before.
Secret string `json:"secret,omitempty"`
}
type RoomState ¶
type RoomState string
RoomState is the multi-device room lifecycle (SPEC §1). Only the terminal "dead" value is ever persisted; open/bound are computed from an absent state (a room with a live device is bound, otherwise open). Persisting only "dead" keeps additive mints and --rotate-all byte-for-byte compatible with the pre-multi-device on-disk shape and makes the load-time migration implicit.
type Server ¶
type Server struct {
// contains filtered or unexported fields
}
func (*Server) MergeAgentInfo ¶
MergeAgentInfo merges a presence-aware identity report and, on any change, schedules an agent_state emit. nil leaves a field untouched; a pointer to "" is an explicit CLEAR — the value is emptied and marked known, so the box stops advertising the old one and its no-op check stops believing it.
func (*Server) SetAgentCatalog ¶
func (s *Server) SetAgentCatalog(cat AgentCatalog)
SetAgentCatalog replaces the box's catalog with the harness's latest report and broadcasts it when it actually changed. Replace, not merge: the harness sends a complete list every time, so a merge could only resurrect models a re-scoped box no longer offers.
An empty report CLEARS the catalog (and broadcasts the clear), which is how a re-scoped box whose patterns now match nothing tells the app to fall back rather than keep showing a list that no longer exists.
func (*Server) SetAgentInfo ¶
SetAgentInfo merges non-empty fields into the box identity and, on any change, schedules an agent_state emit so attached devices refresh live (the claude-sdk harness reports its RESOLVED model only once the SDK session initializes — after a device may already be connected).
This is the value-merge entry point: it can SET a field, never clear one. The seed path and every non-harness caller use it. A harness reporting an explicit clear goes through MergeAgentInfo.
type Tools ¶
type Tools struct {
// contains filtered or unexported fields
}
func (*Tools) DownloadAttachment ¶
func (*Tools) EditMessage ¶
func (*Tools) Job ¶
Job implements the job tool (SPEC §2.2): start | update | done. It is the sugar the harness main loop calls around dispatches, and it drives both the live job card and the agent_state runs list.
func (*Tools) PublishArtifact ¶
Source Files
¶
- agentcatalog.go
- agentstate.go
- attachments.go
- blobs.go
- coalesce.go
- codelink.go
- connector.go
- connlog.go
- coreclient.go
- corekey.go
- delivery.go
- elaction.go
- elementproj.go
- elements.go
- envelope.go
- fleetauthority.go
- fleetcaps.go
- fleetconn.go
- fleetdialer.go
- fleetinject.go
- fleetliveness.go
- fleetprovider.go
- fleetsession.go
- fleetstate.go
- fleetstore.go
- fleeturi.go
- jobdriver.go
- jobregistry.go
- jobs.go
- liveactivity.go
- mailbox.go
- markdown.go
- outbox.go
- provider.go
- push.go
- pushsign.go
- sdkconfig.go
- server.go
- store.go
- tools.go
- typing.go
- util.go
- wire.go