meeting

package
v0.2.11 Latest Latest
Warning

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

Go to latest
Published: Oct 19, 2026 License: Apache-2.0 Imports: 41 Imported by: 0

Documentation

Overview

Package meeting implements a daemon-owned, explicitly started Recall.ai meeting bridge.

Index

Constants

View Source
const (
	LiveKitURLSecret     = "LIVEKIT_URL"        // ws(s):// or http(s):// of the LiveKit server
	LiveKitKeySecret     = "LIVEKIT_API_KEY"    // API key (token issuer)
	LiveKitSecretSecret  = "LIVEKIT_API_SECRET" // API secret (token signing key)
	LiveKitJoinURLSecret = "LIVEKIT_JOIN_URL"   // optional: join page base; default a local LiveKit Meet
)

Secret names for the LiveKit server; values live in the session store.

View Source
const (
	LiveKitUtterance = "utterance" // a final STT segment from one participant
	LiveKitLeft      = "left"      // a participant left the room
)

LiveKitEvent kinds.

View Source
const (
	KindWeb     = "web"     // Google Meet / Zoom / Teams; its bot is a Recall bot
	KindLiveKit = "livekit" // a LiveKit room; its bot is a LiveKit participant
)

Meeting kinds (design 043 §Registry data model).

View Source
const (
	ProviderDeepgram   = "deepgram"
	ProviderElevenLabs = "elevenlabs"
	ProviderOpenAI     = "openai"

	DefaultTTS = ProviderDeepgram
	DefaultSTT = ProviderDeepgram
)

Provider names, as typed in --tts/--stt and MEETING_TTS/MEETING_STT.

View Source
const (
	PlatformMeet  = "google_meet"
	PlatformZoom  = "zoom"
	PlatformTeams = "microsoft_teams"
	// PlatformLiveKit is a rysh-hosted LiveKit room (`rysh meeting host`).
	PlatformLiveKit = "livekit"
)

Platforms Recall.ai joins for us. The platform is chosen from the meeting URL; nothing else in the join differs today (see createPayload).

View Source
const (
	WallJoining   = "joining"
	WallListening = "listening"
	WallHeard     = "heard"
	WallApproval  = "awaiting_approval" // operator gate (approve mode only)
	WallThinking  = "thinking"
	WallPreparing = "preparing_speech"
	WallSpeaking  = "speaking"
	WallError     = "error"
	WallPaused    = "paused"
	WallEnded     = "ended"
)

Wall phases: the names Phase returns. Keep in sync with LABELS in media.html.

View Source
const (
	ZoomClientIDSecret     = "ZOOM_OAUTH_CLIENT_ID"
	ZoomClientSecretSecret = "ZOOM_OAUTH_CLIENT_SECRET"
	ZoomRefreshTokenSecret = "ZOOM_OAUTH_REFRESH_TOKEN"
)

Secret names; values live only in the session secret store.

View Source
const MaxAgentBots = 4

MaxAgentBots caps the agent bots in one meeting (D9): every bot transcribes the whole call, so each one adds a Recall/STT bill.

View Source
const ReporterConsent = "participants know a rysh reporter bot joins to write a meeting report: the meeting's speech is transcribed (Recall or Deepgram), " +
	"the transcript is kept in memory only until it is summarized and is never saved, and the report (topics, previous tasks, blockers, plan, assignments, with speakers' display names) is saved in this workspace"

ReporterConsent is what `--consent` confirms for a reporter.

View Source
const ReporterName = "reporter"

ReporterName is the reporter's agent name: its public-URL path and its LiveKit identity (agent-reporter). `##meeting add agent <id> reporter` means the reporter, so no ##agent of that name can join a meeting.

View Source
const ReporterTranscriptConsent = "participants know a rysh reporter bot joins to write a meeting report and that the raw transcript of this meeting is kept: " +
	"the meeting's speech is transcribed (Recall or Deepgram) and every line, with the speaker's display name and time, is saved in this workspace in " + TranscriptFile + " beside the report, readable by the workspace owner only; " +
	"it is kept until the owner deletes it, and nothing deletes it automatically. The report (topics, previous tasks, blockers, plan, assignments, with speakers' display names) is saved there too"

ReporterTranscriptConsent is what `--transcript-consent` confirms when the owner turns on keeping the raw transcript (H-61). It replaces ReporterConsent, whose "never saved" would then be false.

View Source
const RyshAPIKeySecret = "RYSH_API_KEY"

RyshAPIKeySecret is the session secret holding a rysh workspace API key (`rysh_…`) with the write permission; without it the daemon's upstream key (upstream.api_key) is used.

View Source
const TranscriptFile = "transcript.txt"

TranscriptFile is the kept raw transcript in a meeting's report directory (H-61 opt-in only).

View Source
const UpstreamAPIKeyName = "upstream.api_key"

UpstreamAPIKeyName labels the session's upstream.api_key in doctor output.

Variables

View Source
var (
	LiveKitVoiceSpeech       func(tts, key string) func(ctx context.Context, voice, text string) ([]byte, error)
	LiveKitVoiceSpeechStream func(tts, key string) func(ctx context.Context, voice, text string) (io.ReadCloser, error)
)

LiveKitVoiceSpeech and LiveKitVoiceSpeechStream are the same in an agent's own voice. Without them every agent in a room speaks in the default voice — two agent bots (D9) would sound the same.

View Source
var DialLiveKit func(ctx context.Context, serverURL, token string, opts LiveKitDialOptions) (LiveKitRoom, error)

DialLiveKit connects the bot to a room (livekit.go registers it). If nil, `host` still creates the room and prints the join link, but reports that the bot could not join.

View Source
var DoctorSecretNames = []string{"RECALL_API_KEY", "RECALL_VERIFICATION_SECRET", "OPENAI_API_KEY", "DEEPGRAM_API_KEY", "ELEVENLABS_API_KEY", ZoomClientIDSecret, ZoomClientSecretSecret, ZoomRefreshTokenSecret, RyshAPIKeySecret, UpstreamAPIKeyName, LiveKitURLSecret, LiveKitKeySecret, LiveKitSecretSecret}

DoctorSecretNames is the order doctor output lists its checks in.

View Source
var LiveKitSpeech func(tts, key string) Synthesize

LiveKitSpeech, when set, replaces the Service's reply synthesis for LiveKit rooms so the audio arrives in the format Play publishes without transcoding (tts is a ValidTTS provider, key its API key).

View Source
var LiveKitSpeechStream func(tts, key string) SynthesizeStream

LiveKitSpeechStream, when set, streams default-voice replies: playback starts with the provider's first Ogg page instead of the whole file.

Functions

func CheckRoom

func CheckRoom(ctx context.Context, ref RoomRef, key string) error

CheckRoom confirms the room exists and is readable with the session key.

func MeetingPlatform

func MeetingPlatform(raw string) (string, error)

MeetingPlatform validates a meeting link against a strict per-platform allowlist (https only, exact hosts, known paths and query keys, no credentials or fragments) and returns the Recall platform it belongs to. Redirect, shortened or mail-wrapped links are refused: Recall itself only joins real platform URLs, and we never ask a third party to fetch an arbitrary host on the operator's behalf.

func NewOpusVoiceSpeech

func NewOpusVoiceSpeech(provider, key string) func(ctx context.Context, voice, text string) ([]byte, error)

NewOpusVoiceSpeech is NewOpusSpeech in a chosen voice (an agent's; "" is the provider default), so agent bots in a rysh room sound distinct (D9).

func NewOpusVoiceSpeechStream

func NewOpusVoiceSpeechStream(provider, key string) func(ctx context.Context, voice, text string) (io.ReadCloser, error)

NewOpusVoiceSpeechStream is NewOpusSpeechStream in a chosen voice.

func Phase

func Phase(state string, failed bool) string

Phase maps a raw service state to the progress vocabulary the bot's camera tile shows (MTG-13): joining, listening, heard, awaiting_approval (--approve only), thinking, preparing_speech, speaking, error, paused, ended. failed is true while a hands-free turn says failurePhrase, which reads as error.

func RecordToolCall

func RecordToolCall(ctx context.Context, tool, target, status string)

RecordToolCall appends a tool call to the meeting audit log. It is a no-op outside a meeting turn. target is redacted and clipped here.

func Redact

func Redact(v string) string

Redact masks credential-looking parts of a tool target: sensitive KEY=value assignments, bearer tokens, well-known token prefixes, URL userinfo and long opaque strings. Best effort; tool targets are labels, not payloads, and SecretNAT placeholders pass through unchanged.

func ReportActivity

func ReportActivity(ctx context.Context, label string)

ReportActivity lets the executor describe what it is doing for the current turn ("running agent docs", "reading README.md"). The label is shown on the meeting's camera tile, so it must never contain secrets. It is a no-op outside a meeting turn.

func ReportDir

func ReportDir(ryshDir, meetingID string) string

ReportDir is where a meeting's report is written: <rysh dir>/meetings/<id>.

func RevokeInvite

func RevokeInvite(ctx context.Context, ref RoomRef, key, inviteID string) error

RevokeInvite revokes one invite; the server also takes its guest out of the room.

func STTKeyName

func STTKeyName(stt string) string

STTKeyName is the session secret an STT provider needs.

func SpeechKeyName

func SpeechKeyName(tts string) string

SpeechKeyName is the session secret a TTS provider needs.

func TurnSpeaker

func TurnSpeaker(ctx context.Context) string

TurnSpeaker returns the participant label of the current meeting turn.

func ValidAgentName

func ValidAgentName(name string) error

ValidAgentName reports whether name can be attached to a meeting.

func ValidInviteEmail

func ValidInviteEmail(s string) bool

ValidInviteEmail accepts a bare address (no display name, no brackets).

func ValidInviteID

func ValidInviteID(id string) bool

ValidInviteID reports whether id is an invite id (a UUID) as `invites` lists it.

func ValidLang

func ValidLang(l string) bool

ValidLang reports whether l is a language code --lang takes ("" = English).

func ValidMeetingID

func ValidMeetingID(id string) bool

ValidMeetingID reports whether id has the registry's id shape.

func ValidSTT

func ValidSTT(p string) bool

func ValidTTS

func ValidTTS(p string) bool

ValidTTS and ValidSTT report whether p is a provider the selector takes.

func WithActivityReporter

func WithActivityReporter(ctx context.Context, f func(string)) context.Context

WithActivityReporter binds ReportActivity to f, for executors tested outside a Service.

func WithAuditRecorder

func WithAuditRecorder(ctx context.Context, agent, speaker string, f func(AuditEntry)) context.Context

WithAuditRecorder binds RecordToolCall to f outside a Service (tests, other executors).

Types

type Agent

type Agent struct {
	Name    string
	Voice   string
	Execute Execute
	// Model labels the model this agent's turns run on (its `model:` when
	// configured in the session, else the session model); status shows it.
	Model string
	// Note explains a fallback (e.g. an unconfigured `model:`); shown on attach.
	Note string
}

Agent is a Rysh agent (`##agent spawn`) attached to a meeting. Its name is its wake word ("hey <name>"), Voice selects its TTS voice ("" = a distinct default) and Execute answers as its persona. The daemon builds Execute from the agent's definition inside the meeting's isolated executor, never from the live agent actor: see design 040 §MTG-12.

type AuditEntry

type AuditEntry struct {
	Time    time.Time `json:"time"`
	Meeting string    `json:"meeting,omitempty"` // Recall bot id
	// MeetingID is the registry id (m-xxxxxx, design 043) of the meeting
	// the turn ran in.
	MeetingID string `json:"meeting_id,omitempty"`
	Turn      string `json:"turn"`
	Agent     string `json:"agent"`
	Speaker   string `json:"speaker"` // display name: a label, not authentication
	Tool      string `json:"tool"`
	Target    string `json:"target,omitempty"` // redacted, ≤ 160 runes
	Status    string `json:"status"`           // started | ok | error | blocked_by_policy | incomplete (started, never ended: the turn stopped first)
}

AuditEntry is one tool call made from a meeting turn (founder decision 2026-09-28: agents use all their tools with no approval; the allowlist, kill switch and this log are the controls). It never carries tool output, secret values or meeting transcript beyond the tool's own target label.

The audit log is the RECORD of what ran, sub-agent calls (every depth) included: each call has exactly one "started" and one terminal entry. The meeting wall activity (ReportActivity: "thinking with …", "running …") is best-effort LIVE progress, NOT a record: labels for a turn that already ended may never appear, and it must not be cited as evidence of what ran.

type Command

type Command struct {
	Action string `json:"action"`
	// ID names the meeting (m-xxxxxx, design 043); "" = the only meeting with
	// a bot, else the only meeting. join/host without it create a meeting.
	ID        string `json:"id,omitempty"`
	URL       string `json:"url,omitempty"`
	PublicURL string `json:"public_url,omitempty"`
	Listen    string `json:"listen,omitempty"`
	Region    string `json:"region,omitempty"`
	ReadDir   string `json:"read_dir,omitempty"`
	TTS       string `json:"tts,omitempty"`  // openai | deepgram | elevenlabs (design 048)
	STT       string `json:"stt,omitempty"`  // deepgram | elevenlabs; rysh rooms only (Recall transcribes itself)
	Lang      string `json:"lang,omitempty"` // ISO 639 code the agents answer and speak in; "" = English (design 048)
	Consent   bool   `json:"consent,omitempty"`
	// KeepTranscript keeps a reporter's raw transcript on disk for this
	// meeting (H-61, 045 D5; off by default). It needs TranscriptConsent:
	// the reporter's own Consent does not cover it.
	KeepTranscript    bool `json:"keep_transcript,omitempty"`
	TranscriptConsent bool `json:"transcript_consent,omitempty"`
	AutoSpeak         bool `json:"auto_speak,omitempty"`
	// Approve restores per-turn local approval (approve, then speak). Without
	// it the bot is hands-free: an allowed speaker's request runs and the
	// reply is spoken with no terminal input (founder decision 2026-09-27).
	Approve bool `json:"approve,omitempty"`
	// Allow lists who may wake the bot: display names and/or the keywords
	// "host" and "anyone". Empty means "host". Names are labels the
	// participant chose, not authentication.
	Allow []string `json:"allow,omitempty"`
	// AllowFromFlag marks an Allow the operator typed on `##meeting add
	// agent`; an attach refuses it (the allowlist is fixed at bot start).
	// Set by the daemon, never taken from a client.
	AllowFromFlag bool   `json:"-"`
	Minutes       int    `json:"minutes,omitempty"`
	TurnID        string `json:"turn_id,omitempty"`
	// Agents names ##agent-spawned agents to attach at join ("hey <name>");
	// the first one also answers "hey rysh". Agent is the attach/detach target.
	Agents []string `json:"agents,omitempty"`
	Agent  string   `json:"agent,omitempty"`
	// Model labels the session model plain "hey rysh" turns run on. Set by the
	// daemon at join, never taken from a client.
	Model string `json:"-"`
	// AuditPath is the JSONL file meeting tool calls are appended to
	// (audit.go). Set by the daemon under the session dir, never by a client.
	AuditPath string `json:"-"`
	// ZoomOBF gives Recall a zoom.obf_token_url on this listener so a Zoom
	// meeting hosted by another account can be joined on behalf of an
	// authorised Zoom user (zoomobf.go). Off by default.
	ZoomOBF bool `json:"zoom_obf,omitempty"`
	// Room names the LiveKit room `host` creates; "" = a random rysh-… name.
	Room string `json:"room,omitempty"`
	// Server, for host, is a rysh-server base URL: the room, the bot token
	// and the invite come from it instead of local LiveKit signing.
	Server string `json:"server,omitempty"`
	// PaneID is the meeting pane a `register` binds (##meeting join). Set by
	// the daemon, never taken from a client.
	PaneID string `json:"-"`
	// Reporter commands (design 045), all set by the daemon: ReportDir is
	// the meeting's report directory (<rysh dir>/meetings/<id>), Origin the
	// pane told when the report is written, Session the session's name.
	ReportDir string `json:"-"`
	Origin    string `json:"-"`
	Session   string `json:"-"`
	// contains filtered or unexported fields
}

func (*Command) Defaults

func (c *Command) Defaults()

func (Command) Validate

func (c Command) Validate() error

func (Command) ValidateHost

func (c Command) ValidateHost() error

ValidateHost checks a `host` command. The room server comes from session secrets, not arguments, so only local choices are validated here.

type Credentials

type Credentials struct {
	Recall, Verification, Speech string
	Zoom                         ZoomCredentials    // only with --zoom-obf
	LiveKit                      LiveKitCredentials // only for host
	RyshAPIKey                   string             // host --server: rysh API key with write
	// UpstreamAPIKey reports (doctor only) that the session has an
	// upstream.api_key, which host --server uses when RYSH_API_KEY is unset.
	UpstreamAPIKey bool
}

type Execute

type Execute func(context.Context, string) (string, error)

type Invite

type Invite struct {
	ID            string `json:"id"`
	TokenPrefix   string `json:"token_prefix"`
	Label         string `json:"label"`
	GuestName     string `json:"guest_name"`
	AllowGuest    bool   `json:"allow_guest"`
	MaxUses       int    `json:"max_uses"`
	Uses          int    `json:"uses"`
	ExpiresAt     string `json:"expires_at"`
	RevokedAt     string `json:"revoked_at,omitempty"`
	LastUsedAt    string `json:"last_used_at,omitempty"`
	JoinURL       string `json:"-"`
	GuestIdentity string `json:"-"`
}

Invite is one invite as the server reports it. JoinURL (with its secret) is only set right after creation.

func CreateInvite

func CreateInvite(ctx context.Context, ref RoomRef, key string, req InviteRequest) (Invite, error)

CreateInvite asks the room's server for an invite (owner only).

func ListInvites

func ListInvites(ctx context.Context, ref RoomRef, key string) ([]Invite, error)

ListInvites lists the room's invites, newest first (owner only).

type InviteRequest

type InviteRequest struct {
	Email string        // personal invite labelled with this address
	Guest bool          // personal invite without an address
	Name  string        // the guest's default display name
	TTL   time.Duration // 0 = the server's default (24 h, never past the room)
}

InviteRequest is what `##meeting invite` asks the rysh server for.

func (InviteRequest) Personal

func (r InviteRequest) Personal() bool

Personal reports whether the request is for one person (a /join/ link).

type LiveKitConfig

type LiveKitConfig struct {
	URL       string // ws(s):// signalling URL
	APIKey    string
	APISecret string
	Room      string
}

LiveKitConfig names a room and the API key pair that mints tokens for it; used by the synthetic participant (LiveKitProbe) and the dev harness.

type LiveKitCredentials

type LiveKitCredentials struct {
	URL, APIKey, APISecret string
	JoinPage               string // "" = defaultJoinPage
	STTKey                 string // STTKeyName(Command.STT): per-participant streaming STT
}

LiveKitCredentials are the LiveKit server and its API key pair.

type LiveKitDialOptions

type LiveKitDialOptions struct {
	STT, Lang, STTKey string
	// Keyterms is the bot's current agent names, read by the transcriber
	// to bias recognition towards them (E-85); nil = only "Rysh".
	Keyterms func() []string
}

LiveKitDialOptions carries what the room client needs besides the token: the streaming STT provider (Command.STT, "" = Deepgram), the language (Command.Lang, "" = English) and the provider's key, for per-participant transcription.

type LiveKitEvent

type LiveKitEvent struct {
	Kind       string
	Identity   string
	Name       string
	Text       string
	Start, End float64
}

LiveKitEvent is one room event. Identity comes from the participant's signed token. Start/End are seconds since the bot joined; zero means "now" and the transport stamps the arrival time.

type LiveKitProbe

type LiveKitProbe struct {
	// contains filtered or unexported fields
}

LiveKitProbe is a synthetic human for the spike's end-to-end check: a STANDARD participant that speaks prepared Ogg/Opus into the room and records what the other participants (the bot) send back.

func JoinLiveKitProbe

func JoinLiveKitProbe(cfg LiveKitConfig, identity string) (*LiveKitProbe, error)

JoinLiveKitProbe joins cfg.Room as identity (a normal participant).

func (*LiveKitProbe) Close

func (p *LiveKitProbe) Close()

Close leaves the room.

func (*LiveKitProbe) Heard

func (p *LiveKitProbe) Heard() (packets int, first, last time.Time, from string, ogg []byte)

Heard reports audible audio received from other participants since the last Say: packet count, first and last packet time, sender identity, and all received audio as Ogg/Opus.

func (*LiveKitProbe) Say

func (p *LiveKitProbe) Say(ctx context.Context, ogg []byte) (time.Time, error)

Say plays Ogg/Opus into the room in real time and returns when the last packet was sent.

type LiveKitRoom

type LiveKitRoom interface {
	// Events delivers final utterances and participant/connection changes;
	// it is closed when the connection ends.
	Events() <-chan LiveKitEvent
	// Play publishes one reply on the bot's audio track and returns when it
	// has been sent, or when ctx is cancelled (barge-in, stop). audio is what
	// the Service's speech produced: LiveKitSpeech's format when set (LK-1
	// uses Ogg/Opus), else the MP3 of the Recall route.
	Play(ctx context.Context, audio []byte) error
	Close() error
}

LiveKitRoom is the bot's live connection to a room. The WebRTC side — server-sdk-go, one streaming STT session per participant audio track, and publishing reply audio on the bot's own track — implements it (LK-1, livekit.go) and registers itself in DialLiveKit.

type Manager

type Manager struct {
	// contains filtered or unexported fields
}

Manager is the session's meeting registry (design 043): many meetings keyed by id, each holding its bots — one per agent added with `##meeting add agent` (D9), or the one bot of a CLI join/host.

func NewManager

func NewManager() *Manager

func (*Manager) ByPane

func (m *Manager) ByPane(paneID string) (Meeting, bool)

ByPane returns a copy of the meeting shown in paneID.

func (*Manager) Close

func (m *Manager) Close()

func (*Manager) Do

func (m *Manager) Do(c Command, keys Credentials, execute Execute, agents ...Agent) Reply

Do runs one meeting command. agents carries the resolved `##agent` definitions for join (--agent) and attach; other actions ignore it. Commands without c.ID act on the only meeting with a bot, else the only meeting; join and host without c.ID create a new pane-less meeting.

m.mu guards only the registry and is never held across provider I/O (E30 PM-3c): a bot start or leave reserves its meeting (Meeting.busy) under the lock, talks to Recall/LiveKit/rysh-server without it, and commits under it again. Registry reads therefore never wait on a provider, and the workspace mailbox that calls them never stalls.

func (*Manager) Lookup

func (m *Manager) Lookup(id string) (Meeting, bool)

func (*Manager) SetReportHook

func (m *Manager) SetReportHook(f func(ReportEvent))

SetReportHook sets the function told about every report written (final, interim or failed). Set it before the first report_on.

type Meeting

type Meeting struct {
	ID       string // m-xxxxxx
	Kind     string // KindWeb | KindLiveKit
	Platform string // PlatformMeet | PlatformZoom | PlatformTeams | PlatformLiveKit
	Target   string // meeting URL (web) or room id (livekit)
	Server   string // rysh-server base (livekit rooms)
	PaneID   string // "" for pane-less CLI meetings
	// Bots are the meeting's bots in start order: one per agent for
	// `##meeting add agent` (D9), or the one bot of a CLI join/host, which
	// may carry several attached agents.
	Bots      []*Service
	CreatedAt time.Time

	// Reporter is the meeting's listen-only reporter bot (design 045), kept
	// apart from Bots: it answers no wake, is not an agent and does not
	// count toward MaxAgentBots. lastReport is the newest report written.
	Reporter *Service
	// contains filtered or unexported fields
}

Meeting is one entry of the session's meeting registry. A pane meeting (##meeting join) has a PaneID and no bot until an agent is added; a CLI `join`/`host` without --id creates a pane-less entry that holds its bot.

func (*Meeting) BusyError

func (mt *Meeting) BusyError() string

Lookup returns a copy of the meeting with id. BusyError is the refusal a start, leave or agent add/remove on this meeting gets right now ("" when it is not busy). The grammar layer prints it in the invoking pane before it starts anything (E-82); the registry still decides, so a start that slips in between is refused the same way.

type OpusSpeech

type OpusSpeech func(ctx context.Context, text string) ([]byte, error)

OpusSpeech synthesizes text to Ogg/Opus.

func NewOpusSpeech

func NewOpusSpeech(provider, key string) OpusSpeech

NewOpusSpeech is the Recall route's TTS asking for Ogg/Opus instead of MP3: OpenAI gpt-4o-mini-tts (coral), Deepgram Aura 2 or ElevenLabs Flash v2.5.

type ParticipantGrant

type ParticipantGrant struct {
	URL       string `json:"url"`
	Token     string `json:"token"`
	Identity  string `json:"identity"`
	ExpiresAt string `json:"expires_at"`
}

ParticipantGrant is the human participant's LiveKit access for a meeting pane. It is handed to the desktop app and never stored or logged.

func ParticipantToken

func ParticipantToken(ctx context.Context, ref RoomRef, key string) (ParticipantGrant, error)

ParticipantToken asks the rysh server for a participant token for the key's account in ref's room (POST /api/meeting-rooms/<room>/token).

type Reply

type Reply struct {
	ID             string          `json:"id,omitempty"`       // meeting id (m-xxxxxx)
	Kind           string          `json:"kind,omitempty"`     // "web" | "livekit"
	PaneID         string          `json:"pane_id,omitempty"`  // meeting pane; "" = pane-less
	Target         string          `json:"target,omitempty"`   // meeting URL (web) or room id (livekit)
	Meetings       []Reply         `json:"meetings,omitempty"` // list
	Bots           []Reply         `json:"bots,omitempty"`     // one status per bot of the meeting (D9), in start order
	State          string          `json:"state"`
	Platform       string          `json:"platform,omitempty"`
	BotID          string          `json:"bot_id,omitempty"`
	TurnID         string          `json:"turn_id,omitempty"`
	Speaker        string          `json:"speaker,omitempty"`
	Request        string          `json:"request,omitempty"` // explicit local `pending` only
	Result         string          `json:"result,omitempty"`  // explicit local `pending` only
	Error          string          `json:"error,omitempty"`
	Notice         string          `json:"notice,omitempty"`
	Phase          string          `json:"phase,omitempty"` // status-wall vocabulary, see Phase
	Mode           string          `json:"mode,omitempty"`  // "hands_free" or "approve"
	MediaConnected bool            `json:"media_connected"`
	SegmentsHeard  int             `json:"segments_heard,omitempty"`
	IgnoredWakes   int             `json:"ignored_wakes,omitempty"` // wakes from speakers outside the allowlist
	Allow          []string        `json:"allow,omitempty"`         // who may wake the bot in this meeting: display names, "host", "anyone"
	Secrets        map[string]bool `json:"secrets,omitempty"`
	Agent          string          `json:"agent,omitempty"`       // agent addressed by the pending turn; "" = rysh
	Agents         []string        `json:"agents,omitempty"`      // agents attached to this meeting
	Voices         []string        `json:"voices,omitempty"`      // each attached agent's TTS voice, in Agents order ("" = provider default)
	Model          string          `json:"model,omitempty"`       // model answering the pending turn
	ToolCalls      int             `json:"tool_calls"`            // audited tool calls from meeting turns
	AuditLog       string          `json:"audit_log,omitempty"`   // file holding those entries
	Room           string          `json:"room,omitempty"`        // LiveKit room (`host`)
	JoinURL        string          `json:"join_url,omitempty"`    // link for people to join that room (`host` reply only)
	Report         *ReportStatus   `json:"report,omitempty"`      // the meeting's reporter (design 045)
	LastReport     string          `json:"last_report,omitempty"` // the newest report written for this meeting
}

type ReportAssignment

type ReportAssignment struct {
	Who  string `json:"who,omitempty"`
	What string `json:"what"`
	When string `json:"when,omitempty"`
}

type ReportConfig

type ReportConfig struct {
	MeetingID, PaneID, Origin string
	Platform, Session         string
	Dir                       string
	Summarize                 Execute
	OnEvent                   func(ReportEvent)
	KeepTranscript            bool // H-61: also append the raw transcript to TranscriptFile
}

ReportConfig is what the daemon gives a reporter.

type ReportEvent

type ReportEvent struct {
	MeetingID, PaneID, Origin string
	Path                      string
	Interim                   bool
	Error                     string
	Summary                   string // one line for the notice: counts per section
}

ReportEvent tells the daemon a report was written (or could not be).

func RecoverReport

func RecoverReport(dir string, summarize Execute) (ReportEvent, error)

RecoverReport writes report.md from a notes checkpoint left by a reporter whose daemon stopped before the meeting ended (045 D7). The transcript heard after the last checkpoint is gone; the report says so.

type ReportItem

type ReportItem struct {
	Owner  string `json:"owner,omitempty"`
	Item   string `json:"item"`
	Status string `json:"status,omitempty"`
}

ReportItem is a previous task/goal (with Status as reported) or a blocker.

type ReportNotes

type ReportNotes struct {
	Overview      string             `json:"overview,omitempty"`
	Topics        []ReportTopic      `json:"topics,omitempty"`
	PreviousItems []ReportItem       `json:"previous_items,omitempty"`
	Blockers      []ReportItem       `json:"blockers,omitempty"`
	Plan          []string           `json:"plan,omitempty"`
	Assignments   []ReportAssignment `json:"assignments,omitempty"`
	Decisions     []string           `json:"decisions,omitempty"`
}

ReportNotes is the structured content of a report: what one chunk's summary returns, what the final pass merges them into, and what the markdown is rendered from.

type ReportStatus

type ReportStatus struct {
	State        string   `json:"state"`                  // listening | summarizing | writing | written | failed
	BotState     string   `json:"bot_state,omitempty"`    // the reporter bot's state (joining, listening, ended, …)
	Segments     int      `json:"segments"`               // transcript segments heard (count only)
	Chunks       int      `json:"chunks"`                 // chunks summarized so far
	Buffered     int      `json:"buffered_chars"`         // transcript in memory awaiting its summary
	Participants []string `json:"participants,omitempty"` // speakers heard, by display name
	Path         string   `json:"path,omitempty"`         // report.md once written (interim or final)
	Interim      bool     `json:"interim,omitempty"`      // Path holds an interim report
	Transcript   string   `json:"transcript,omitempty"`   // the kept raw transcript (H-61 opt-in); "" = not kept
	Error        string   `json:"error,omitempty"`
}

ReportStatus is the reporter's part of a meeting's status reply.

type ReportTopic

type ReportTopic struct {
	Title   string `json:"title"`
	Summary string `json:"summary,omitempty"`
}

type RoomRef

type RoomRef struct {
	Server string // rysh-server base, e.g. https://rysh.ai
	Room   string // room id
	// ExpiresAt is when the room ends (RFC 3339), set by CreateRoom only.
	ExpiresAt string
}

RoomRef is a rysh room a meeting pane shows.

func CreateRoom

func CreateRoom(ctx context.Context, server, key, title string, ttl time.Duration) (RoomRef, error)

CreateRoom opens a new room on the session's rysh server for a meeting pane (`##meeting join new`). The room lives for ttl (rounded up to hours).

func ParseRoomRef

func ParseRoomRef(arg, server string) (ref RoomRef, ok bool, err error)

ParseRoomRef reads `rysh:<room-id>` or `https://<server>/meet/<room-id>` against the session's rysh server. ok is false when arg is neither form (it may be a Meet/Zoom/Teams link); err is set when it is one of them but unusable — a /meet/ link on another host is refused rather than followed, because following it would send the session's key there.

func (r RoomRef) MeetLink() string

MeetLink is the /meet/<room> page people open to join the room.

type Segment

type Segment struct {
	Participant int     // stable per participant for this meeting
	Name        string  // display label; never authority by itself
	Identity    string  // authenticated identity (LiveKit token); "" on Recall
	Host        bool    // the platform/room says this participant is the host
	Start, End  float64 // seconds, relative to the meeting's own clock
	Words       []string
}

Segment is one finalized transcript segment from one participant, in the transport-neutral form acceptLocked consumes.

type Service

type Service struct {
	// contains filtered or unexported fields
}

func (*Service) ServeHTTP

func (s *Service) ServeHTTP(w http.ResponseWriter, r *http.Request)

type Synthesize

type Synthesize func(context.Context, string) ([]byte, error)

type SynthesizeStream

type SynthesizeStream func(context.Context, string) (io.ReadCloser, error)

SynthesizeStream starts synthesis and returns the audio as it arrives (the provider's HTTP body); closing it abandons the request.

func NewOpusSpeechStream

func NewOpusSpeechStream(provider, key string) SynthesizeStream

NewOpusSpeechStream is NewOpusSpeech returning the audio as it arrives, so playback can start on the first Ogg page. Both providers stream the body.

type Transcriber

type Transcriber func(ctx context.Context, audio <-chan []byte, emit func(text string)) error

Transcriber consumes one participant's Ogg/Opus byte stream and calls emit for every final utterance until audio closes or ctx ends.

func DeepgramTranscriber

func DeepgramTranscriber(key string) Transcriber

DeepgramTranscriber streams containerized Ogg/Opus to Deepgram's live API (nova-3, endpointing 300 ms, keyterm "Rysh" so the wake word is spelled as such more often) and emits each endpointed utterance. WebRTC senders go quiet in silence (DTX, or a finished file), so after 700 ms without audio it asks Deepgram to Finalize instead of waiting for more.

func ElevenLabsTranscriber

func ElevenLabsTranscriber(key string) Transcriber

ElevenLabsTranscriber streams a participant to ElevenLabs Scribe v2 Realtime. The endpoint takes PCM only, so the track's Ogg/Opus is decoded here (pure Go, 16 kHz mono) — the one place the meeting path decodes audio. ElevenLabs' VAD commits each utterance; like Deepgram's Finalize, 700 ms without audio (a sender in DTX silence) commits what is pending.

func NewTranscriber

func NewTranscriber(provider, lang, key string, names func() []string) Transcriber

NewTranscriber is the rysh-room STT for provider in lang ("" = English), key its API key. names, when set, is the bot's current agent names: they and "Rysh" bias recognition (sttKeyterms, E-85), read when each participant stream connects. Deepgram also follows a change mid-stream.

type Transport

type Transport interface {
	// Kind names the backbone: "recall" or "livekit".
	Kind() string
	// Join puts the bot into the meeting and returns its id. A *joinError
	// says whether a failure is definite (nothing was created) or uncertain.
	Join(ctx context.Context) (id string, err error)
	// Leave removes the bot. A *leaveError carries the operator notice.
	Leave(ctx context.Context) error
	// Status reports admission/end; an error is a status outage whose text
	// is the pause notice.
	Status(ctx context.Context) (TransportStatus, error)
	// MediaReadyLocked reports whether audio can be delivered to the meeting.
	MediaReadyLocked() bool
	// SpeakLocked starts playing one reply; completion is reported through
	// Service.playbackLocked(turnID, ok). An error means delivery never
	// started.
	SpeakLocked(turnID string, audio []byte) error
	// ClearLocked stops any playback in progress.
	ClearLocked()
}

Transport is the meeting backbone a Service talks through (design 042, LK-2). Recall joins someone else's meeting (Meet, Zoom, Teams); LiveKit joins a room rysh hosts. Everything above it — wake detection, allowlist, hands-free turns, agents, stop phrases, audit log, progress wall — is the same Service code for both.

Methods named ...Locked run with Service.mu held and must not block on the network; the others run without it.

type TransportStatus

type TransportStatus struct {
	Unknown  bool   // nothing to report yet; leave state as it is
	Admitted bool   // the bot is in the call and hears participants
	Ended    bool   // the meeting ended or the bot was removed
	Why      string // with Ended: the provider's reason, when it gave one
	Waiting  string // while joining: the host action the bot waits for
	Lost     string // after joining: why hearing stopped
}

TransportStatus is one status observation.

type ZoomCredentials

type ZoomCredentials struct {
	ClientID, ClientSecret, RefreshToken string
	SaveRefresh                          func(string) error
}

ZoomCredentials is the Zoom OAuth app (General app with Meeting SDK) and the authorising user's refresh token. SaveRefresh persists a rotated refresh token: Zoom invalidates the old one on each refresh.

Jump to

Keyboard shortcuts

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