Help us shape the future of Go! Take the 2026 Go Developer Survey

command

package
v0.15.0 Latest Latest
Warning

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

Go to latest
Published: Nov 2, 2026 License: AGPL-3.0 Imports: 55 Imported by: 0

Documentation

Overview

Package command implements the POST /api/command dispatch table. Runtime registers concrete handler functions; the Dispatcher routes incoming commands by type and handles envelope-level concerns (body parsing, validation).

Idempotency is the Idempotency-Key header middleware (internal/server/idempotency.go), not this package.

Package command intercepts `/compact` before it reaches KAS, since typed slash commands are not parsed there and would otherwise reach the model as prose. It calls the real `_kiro/session/compact` verb instead.

Index

Constants

View Source
const (
	SteerRefuseNoTurn     = "no_turn"
	SteerRefuseFull       = "full"
	SteerRefuseNotWaiting = "not_waiting"
	SteerRefuseNotUser    = "not_user"
	SteerRefuseAgentRows  = "agent_rows_waiting"
	SteerRefuseSettling   = "settling"
	SteerRefuseChatGone   = "chat_gone"
	// SteerRefuseNoReply is a clear that got no reply: nothing changed.
	SteerRefuseNoReply = "no_reply"
	// SteerRefuseConsumed is a delete whose target the agent read first.
	SteerRefuseConsumed = "consumed"
)

The refusal classes a steer operation answers with; the client branches on them.

View Source
const MaxDocumentBytes = 10 * 1024 * 1024

MaxDocumentBytes caps the size of one document or image attachment (10 MiB), also KAS's own MAX_IMAGE_SIZE. Kept in file bytes: the number a user can predict from a file listing, and the cheap pre-read gate.

View Source
const MaxPromptBytes = 512 * 1024

MaxPromptBytes caps the text field of a prompt command.

Variables

View Source
var (
	// ErrNotATangent is TangentAccess's answer for a chat no session of which was forked.
	ErrNotATangent = errors.New("this chat has no parent chat to merge into")
	// ErrTangentParentGone is TangentAccess's answer when the forked-from session belongs to no
	// chat any more.
	ErrTangentParentGone = errors.New("the chat this tangent came from no longer exists")
)
View Source
var (
	ErrMissingChatID  = errors.New("missing chat_id")
	ErrInvalidPayload = errors.New("invalid payload")

	ErrChatNotFound = errors.New("chat not found")
)

Static command errors returned to the client.

View Source
var AdmissionWait = 20 * time.Second

AdmissionWait is how long a prompt waits for a contended admission slot before answering 409; it must stay below the client's API timeout (TestAdmissionWait_StaysUnderTheClientAPITimeout). A var only so tests can shrink it.

View Source
var CancelGrace = 10 * time.Second

CancelGrace is how long a cooperative session/cancel gets to end the turn before marotte unblocks it itself (KiroCrew's `_CANCEL_GRACE_SECS`). A var only so a test can shrink it.

View Source
var ErrCancelGraceExpired = errors.New("cancel grace expired")

ErrCancelGraceExpired is the cause the grace timer attaches to the prompt context it cancels, so the failure site concludes `cancelled` rather than `interrupted`. Shutdown and an interrupt leave the cause at context.Canceled, and the first cancellation sets the cause, so whichever of shutdown and the grace fires first decides the outcome.

View Source
var ErrCancelNotDelivered = errors.New("a permission card could not be answered")

ErrCancelNotDelivered is a permission card a cancel could not answer on a session that is still live, so no session/cancel was written and the reply goes on.

View Source
var ErrChatGone = errors.New("chat not found")

ErrChatGone reports a chat whose record no longer exists; a bridge open wraps it.

View Source
var ErrPromptStopped = errors.New("the prompt was stopped before it was sent")

ErrPromptStopped fails a prompt the reader stopped before its session/prompt was sent; it concludes cancelled.

View Source
var ErrRunStillLive = errors.New("the run was told to stop but has not stopped yet")

ErrRunStillLive is CancelRun's answer for a run whose cancel landed but whose lease was still held when the wait ran out.

View Source
var ErrTurnStopped = errors.New("the turn this open replaces was stopped")

ErrTurnStopped is OpenTurn's refusal of an open that replaces a turn the reader stopped (TurnOpen.Replaces).

View Source
var ErrTurnSuperseded = errors.New("a newer turn opened since the closed one")

ErrTurnSuperseded is OpenTurn's refusal of a fenced open after a newer turn opened, or after the chat's lifecycle was torn down.

View Source
var ResendBridgeWait = 10 * time.Second

ResendBridgeWait bounds the wait for a dead bridge's frames to fold before its unread steers are resent: a resend admitted earlier would reach the dying process. A var only so tests can shorten it.

Functions

func BuildPromptBlocks

func BuildPromptBlocks(ctx context.Context, text string, attachments []marotte.Attachment, historyImages int, ws Workspace, mcp bridgeCaller) []map[string]any

BuildPromptBlocks constructs the ACP prompt content array: a leading text block (omitted for an attachment-only prompt, since KAS refuses an empty text part), then one block per attachment: a supported document as an embedded `resource`, an image as an `image` fitted by fitImage, anything else a path reference. mcp is the prompt's session, so an MCP reference reads the engine's pool through it; nil leaves them unresolved.

func ClearSteersIn added in v0.13.0

func ClearSteersIn(ctx context.Context, t SteerTarget, endOp func(opID string)) ([]string, error)

ClearSteersIn discards every row of t's buffer. endOp ends the op under the steer lock the caller holds; it runs only when a clear was issued.

func Compact added in v0.12.0

func Compact(ctx context.Context, bridge CompactBridge) (bool, error)

Compact sends KAS's native `_kiro/session/compact`, the verb's one caller, and reports whether KAS accepted it. Accepted is not compacted: three of the five `{success: true}` outcomes compact nothing, so a caller must never synthesize the transcript boundary from the result. KAS answers after the summary commits, bounded at 300 s on its side, so ctx bounds only a wedged bridge.

func RecordEffortSeed added in v0.15.0

func RecordEffortSeed(ctx context.Context, bus broadcaster, configDir, model string, level marotte.EffortLevel)

RecordEffortSeed remembers level as model's next new-chat level (settings.KeyLastEffortByModel), after the pick landed. A failure only logs; an empty model is skipped.

func RemoveSteerFrom added in v0.13.0

func RemoveSteerFrom(ctx context.Context, t SteerTarget, steerID string, endOp func(opID string)) error

RemoveSteerFrom deletes the row steerID from t's buffer: a row KAS cannot hold goes at once, else one clear and one combined resubmit of the kept rows. endOp ends the op under the steer lock the caller holds; it runs only when a clear was issued.

func RenameOutcome added in v0.12.0

func RenameOutcome(resp *marotte.RPCResponse, err error) error

RenameOutcome reads a _kiro/session/rename reply: an RPC error, an in-band error or {success:false} are all failures.

func RenameResultOutcome added in v0.12.0

func RenameResultOutcome(result json.RawMessage) error

RenameResultOutcome reads the result half of a rename reply.

func SendPlanned added in v0.13.0

func SendPlanned(ctx context.Context, t SteerTarget, sends []SteerSend)

SendPlanned sends rows the record already routed (a flush job's plan) into t. The caller holds t's steer lock.

func StatusError

func StatusError(code int, err error) error

StatusError wraps err with the HTTP status the dispatcher should answer with. Exported because Handler is: the runtime registers cmdSwitchModel directly.

func StatusErrorReason

func StatusErrorReason(code int, reason string, err error) error

StatusErrorReason is StatusError plus a machine-readable reason the error envelope carries beside the prose, so a client can branch on a value rather than on error text.

func StatusOf added in v0.13.0

func StatusOf(err error) (status int, reason string, ok bool)

StatusOf answers the HTTP status and machine reason a StatusError* error names; ok is false for any other error.

func SteerInto added in v0.13.0

func SteerInto(ctx context.Context, t SteerTarget, h SteerHolder, key, text string) (refuse string, err error)

SteerInto routes one steer row into t's buffer under the holder h and sends what the record answers. The caller holds t's steer lock. refuse is the record's refusal class; err is the failed send carrying key.

func ValidSteerText added in v0.13.0

func ValidSteerText(raw, messageID string) (text, steerID string, err error)

ValidSteerText answers a steer's trimmed text and the id KAS will stamp on it (marotte.SteerIDFor), or a StatusError. It refuses the two shapes that break that derivation: an empty id, and text KAS would file under `notify-`.

Types

type AdmissionOutcome

type AdmissionOutcome int

AdmissionOutcome is ReserveTurnForPrompt's answer.

const (
	// AdmissionAcquired means the caller holds the chat's admission slot.
	AdmissionAcquired AdmissionOutcome = iota
	// AdmissionBusy is the holder a steer can reach
	// (marotte.TurnOpenSource.SteerableHolder). Answered as the plain 409, on
	// which the client steers or queues, per the chat's mode.
	AdmissionBusy
	// AdmissionStarting is the fail-closed answer when no steerable holder can take
	// the prompt: a cold session open, a rewind, or a run or close hold kiro-cli
	// reports with no steerable turn. Answered as 409 with the additive
	// `reason: "starting"`, which hands the text back.
	AdmissionStarting
)

type AnswerOutcome added in v0.13.0

type AnswerOutcome uint8

AnswerOutcome is how a claimed ask settled under its answer.

const (
	// AnswerDelivered is an answer the agent received.
	AnswerDelivered AnswerOutcome = iota
	// AnswerWithdrawn is an ask a turn cancel, run end, KAS withdrawal or its bridge's end settled
	// first: the answer reached nobody, and that settlement retires the card.
	AnswerWithdrawn
	// AnswerReopened is a write that failed on a live bridge: the ask is pending again.
	AnswerReopened
)

type AskReply added in v0.13.0

type AskReply interface {
	Respond(ctx context.Context, result any) (AnswerOutcome, error)
}

AskReply answers one claimed ask on the bridge it arrived on, never on the chat's bridge at answer time: a successor would resolve an unrelated request under the same id. Respond is called once and settles the ask exactly once, announced as decision_settled: a terminal transition that landed during the write wins (turn cancel or run end silently, a KAS withdrawal as moot, the bridge's end as ended) and is AnswerWithdrawn, as is a write refused because the bridge ended; else AnswerDelivered, as the claim's settledBy. Any other failure re-offers the ask as pending and is AnswerReopened with the write's error, the only outcome carrying one.

type AuthReadiness

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

AuthReadiness holds the outcome of a credential the backend rejected, never its reason, because unauthenticated /api/health must not expose internal paths. Each Record replaces the previous outcome, so a successful turn clears it.

func (*AuthReadiness) Unavailable

func (r *AuthReadiness) Unavailable() bool

Unavailable reports whether the backend rejected the latest credential.

type Bridge

type Bridge interface {
	// contains filtered or unexported methods
}

Bridge is the per-chat ACP bridge as a whole. Only a handler that owns a chat for the length of a turn needs it; helpers take a narrower parameter. Exported because bridgeAccess returns it and the runtime's wiring names it.

type CancelScope added in v0.15.0

type CancelScope uint8

CancelScope selects which of a chat's asks a cancel answers.

const (
	// ScopeChatSession is the chat's own session and its subagents: a chat-parented run's step asks stay pending,
	// because a Stop does not stop the run.
	ScopeChatSession CancelScope = iota + 1
	// ScopeChatAndRuns is every ask filed under the chat, for a teardown that cancels the chat's runs next.
	ScopeChatAndRuns
)

type ChatCreate

type ChatCreate struct {
	// Init fills the new record's fields, called inside chat.Store.Mutate under
	// that chat's record lock; it must not reach either store.
	Init func(c *marotte.Chat)
	// OpID correlates every attempt of one create gesture, so a repeat resolves
	// to the chat the first attempt made instead of minting a second one.
	OpID string
	// ChatID is the id the envelope supplied, or empty to mint one. A supplied id
	// bypasses the ledger: Mutate's exists branch is already idempotent for it.
	ChatID marotte.ChatID
	// RequireChat is a fresh create's precondition, checked under the operation lock
	// so a delete cannot land between the check and the mint.
	RequireChat marotte.ChatID
	// ParentChat names the chat whose tab the new tab hangs under, empty for a
	// top-level tab. A chat id rather than a tab id, so it resolves inside the
	// operation lock; outside it, a parent tab closing would leave Parent naming
	// nothing.
	ParentChat marotte.ChatID
}

ChatCreate is one create-and-open request: what to write on the new record, and where its tab goes.

type ChatOpened

type ChatOpened struct {
	// Chat is read back from the store, since a replay resolves to a chat this
	// request did not write.
	Chat *marotte.Chat
	// Subject is the tab. Zero-valued only when no tab store is wired.
	Subject marotte.TabSubject
	Version uint64
	// Replay reports that this op_id had already created its chat.
	Replay bool
}

ChatOpened is what a create answers with.

type CloseFacts added in v0.12.0

type CloseFacts struct {
	Outcome marotte.TurnOutcome
	// AfterStop are rows queued while kiro-cli ended the stopped run, with no turn
	// live: one of them may go after this close although it was stopped (after any
	// unread steers, which go first); the rest wait for the next close's drain.
	AfterStop []string
	Fence     TurnFence
	// Unattended is a close no one stopped (marotte closing a turn KAS went idle without
	// ending), after which queued user rows still go, as after a clean close.
	Unattended bool
}

CloseFacts is what a winning close hands its drain: the closed turn's outcome and the fence naming it.

type CompactBridge added in v0.12.0

type CompactBridge interface {
	Call(ctx context.Context, method string, params any) (*marotte.RPCResponse, error)
	SessionID() marotte.SessionID
}

CompactBridge is what Compact needs of a bridge: one call on its own session.

type Dispatcher

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

Dispatcher holds the command dispatch table and serves the POST /api/command HTTP endpoint.

func New

func New() *Dispatcher

New constructs a Dispatcher. A handler's own collaborators arrive at registration (see RegisterDefaults).

func (*Dispatcher) DrainAfterClose added in v0.12.0

func (d *Dispatcher) DrainAfterClose(ctx context.Context, chatID marotte.ChatID, closed CloseFacts, ends EndFacts)

DrainAfterClose sends at most one prompt for a close: the chat's unread steers, else one queued user row after a clean close (see drainAfterClose).

func (*Dispatcher) Register

func (d *Dispatcher) Register(t marotte.CommandType, h Handler)

Register adds a handler for the given command type.

func (*Dispatcher) ResolveAfterClose added in v0.12.0

func (d *Dispatcher) ResolveAfterClose(ctx context.Context, chatID marotte.ChatID, fence TurnFence) EndFacts

ResolveAfterClose resolves the pending turn ends of the closed turn and older under the chat's steer lock, then routes the rows it made unsent into a prompt that already started. The zero EndFacts answers when nothing is registered.

func (*Dispatcher) Run added in v0.15.0

Run applies cmd through its registered handler in process, for a command that applies another's effect as part of its own: no envelope, no idempotency replay, no answer discharge, and the handler's response is dropped.

func (*Dispatcher) ServeHTTP

func (d *Dispatcher) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP is the POST /api/command HTTP handler.

func (*Dispatcher) ServeMergeStatus added in v0.13.0

func (d *Dispatcher) ServeMergeStatus(w http.ResponseWriter, r *http.Request)

ServeMergeStatus answers GET /api/chats/{id}/merges/{op}: where the merge_tangent attempt with that op_id on that tangent stands, "absent" when none was admitted or its outcome expired.

type EndFacts added in v0.12.0

type EndFacts struct {
	Death     bool
	Undrained bool
}

EndFacts is what resolving a chat's pending ends learned about bridge deaths: Death when a death end was resolved, Undrained when its forwarder never drained.

type Handler

type Handler func(ctx context.Context, cmd *marotte.ClientCommand) (any, error)

Handler is the signature for a command handler function. It returns its outcome rather than writing to an http.ResponseWriter: the dispatcher marshals the body. A nil error means 200 with that body; an error carrying a status (see StatusError) sets it, and a bare error is a 500.

type LiveRunRef

type LiveRunRef struct {
	ID    string `json:"id"`
	Label string `json:"label"`
}

LiveRunRef names one live run to the reader: KAS's workflow id and the recipe name it was launched from. The shape a rewind's 409 carries.

type Membership

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

Membership owns every operation that spans the chat store and the tab set.

Safe for concurrent use; the zero value is not usable, construct with newMembership. A nil TabSet means no tab store was wired: the chat half of every operation still runs and the tab half reports errTabsUnavailable.

func RegisterDefaults

func RegisterDefaults(d *Dispatcher, r *Roles) *Membership

RegisterDefaults populates the dispatcher with the standard command handlers and returns the membership coordinator it built.

func (*Membership) AdmitRunPurge added in v0.12.0

func (m *Membership) AdmitRunPurge(ctx context.Context, workflowID, parentSessionID string) (done func(), ok bool)

AdmitRunPurge answers whether the run purge may delete workflowID, launched from parentSessionID: false while a tab shows the run, while any chat's chain holds that session, or when the chats cannot all be read. On true both are marked purging until the caller runs done, so OpenTab and CreateChatAndOpen refuse them. Checks and marks share mu, so nothing commits between the decision and the delete.

func (*Membership) CreateChatAndOpen

func (m *Membership) CreateChatAndOpen(ctx context.Context, req ChatCreate) (ChatOpened, error)

CreateChatAndOpen writes a chat record and opens its tab as one operation, so the final slot cannot be consumed between mint and open and no delete lands between the two writes. Returns errOpenChatUnknown (404) when a required chat is absent, errChatUnread (503) when it could not be read, errTabsFull (409) at MaxOpenTabs, and errChatNotCreated (409) when the record is absent after a Mutate that reported no error. A failed tab write leaves the chat created; a retry with the same op_id finishes it.

func (*Membership) DeleteChatAndCloseTabs

func (m *Membership) DeleteChatAndCloseTabs(ctx context.Context, chatID marotte.ChatID, opID string) error

DeleteChatAndCloseTabs is the delete path: remove the record and close its tabs in one critical section, then tear the chat's work down from the session chain the removed record held. The record is the admission barrier: once it is gone every later open is refused and a bridge opening on it settles ErrChatGone, and the teardown after it closes any bridge that opened before. A failed delete tears nothing down, so the chat keeps working. The teardown runs after the lock: its KAS calls have no client-side timeout, so one wedged process would block every tab mutation.

func (*Membership) HasOpenTab

func (m *Membership) HasOpenTab(chatID marotte.ChatID) bool

HasOpenTab reports whether any tab shows this chat, retention's second predicate. Takes no operation lock: it is a read, and the lock orders writes against their events.

func (*Membership) OpenTab

func (m *Membership) OpenTab(ctx context.Context, spec marotte.OpenTab, opID string) (TabOpened, error)

OpenTab opens a tab for something that already exists; it never mints a chat. For a chat tab it gates on the record existing — the other half of the delete ordering — with the check and the open in one critical section. A new run tab whose caller named no open parent nests under its launching chat (launcherTab), and is refused with a 503 when that chat could not be read.

func (*Membership) RetentionClose

func (m *Membership) RetentionClose(ctx context.Context, chatID marotte.ChatID, sessionChain []string)

RetentionClose tears down the work of a chat the retention purge already removed, then closes its tabs. The teardown runs outside the operation lock, as in DeleteChatAndCloseTabs. Tabs to close here mean one was opened between HasOpenTab and the remove.

func (*Membership) SetRetentionWake

func (m *Membership) SetRetentionWake(wake func())

SetRetentionWake registers the callback that asks the retention purge for a pass now; a setter because the purge scheduler is built after the coordinator. Closing a tab owes it: an exempt chat contributes no deadline, so without a wake an all-exempt pass backs off to the one-hour ceiling.

func (*Membership) WakeRetention added in v0.12.0

func (m *Membership) WakeRetention()

WakeRetention asks the purge to reconsider, if a scheduler is wired. A run that just finished owes it as much as a closed tab: with retention off, a finished run no tab shows is purgeable from that moment.

type QueueAccess added in v0.12.0

type QueueAccess interface {
	// AppendIfLive applies fn to the chat's record while the chat holds a live turn,
	// or waits for kiro-cli to end a run the user stopped (the next drain once that wait
	// ends may send one such row despite a stop, CloseFacts.AfterStop; the rest go as
	// after any close), reporting
	// live=false (and writing nothing) when neither. fn's error aborts the write and
	// is returned as is.
	AppendIfLive(ctx context.Context, chatID marotte.ChatID, fn func(c *marotte.Chat) error) (live bool, err error)
	// NextUserRow answers the first queued user row that is not held and whose
	// turn this process has not opened, among only when it is non-nil.
	NextUserRow(ctx context.Context, chatID marotte.ChatID, only []string) (marotte.QueuedPrompt, bool, error)
	// Unqueue removes the row with id. An id whose turn this process opened answers
	// sending=true; an id no row carries is success.
	Unqueue(ctx context.Context, chatID marotte.ChatID, id string) (sending bool, err error)
	// Dequeue takes a sent row off the header once its turn is announced. A failed
	// write is applied by the chat's next mutation.
	Dequeue(ctx context.Context, chatID marotte.ChatID, id string) error
}

QueueAccess is the follow-up queue as the commands and the drain drive it. An append sees the turn registry, so a live turn cannot close between "it is running" and "it is queued" and leave a row no close will send.

type Roles

type Roles struct {
	Bridges bridgeAccess
	// Loader resumes a chat whose log must reflect KAS before anything is decided from it.
	Loader sessionLoader
	// Chats is the chat store directly, not through a getter on a wider composite —
	// that composite would need to carry Broadcast and the run surface too, which is
	// how a host becomes the one thing that qualifies.
	Chats chatStore
	// Sessions answers whether a chat already claims a KAS session, read fresh
	// so the run purge can decide under the membership lock.
	Sessions  sessionClaims
	Bus       broadcaster
	Teardown  chatTeardown
	Perms     pendingPermAccess
	Profiles  profileSwitcher
	Terminals terminalAccess
	Stops     turnStopper
	// Tabs is the open-tab set, and it may be nil: a build with no config
	// dir has no store to persist an arrangement to. The coordinator
	// answers the tab half of every operation with a 503 in that state.
	Tabs TabSet
	// Runs answers which chat launched a run, so a run tab opened with no
	// parent still nests under its conversation.
	Runs runOwner
	// RunCutter is the run registry as a rewind uses it: the live runs a cut
	// launched, and the cancel that stops them before the cut.
	RunCutter runCutter
	// Effort records the transcript entry a reader's reasoning-tier change leaves.
	Effort effortRecorder
	// Modes records the transcript entry a reader's mode switch leaves.
	Modes modeRecorder
	// Lifecycle is the process lifetime: the turn context and the
	// in-flight counter a shutdown waits on.
	Lifecycle   lifecycleAccess
	Admission   turnAdmission
	TurnOutcome turnOutcomeAccess
	// Steers records the steers this server sent, so the translate layer can
	// tell the user's own words from a workflow reporting into the same buffer.
	Steers SteerRecorder
	// Queue appends a follow-up only while the chat's turn is live.
	Queue QueueAccess
	// SteerQueue is the server's record of the user's steers and KAS's buffer,
	// and SteerJobs the same record as the close pipeline drives it.
	SteerQueue SteerQueue
	SteerJobs  SteerJobs
	// Status ends a chat's retained waiting_on_user claim after a command that IS
	// the user answering.
	Status chatStatus
	// AuthReadiness carries prompt authentication outcomes to readiness.
	AuthReadiness *AuthReadiness
	// Compactor runs the compaction policy before a prompt is sent. Nil sends
	// with no pre-send check.
	Compactor preSendCompactor
	// SendGate holds each session/prompt a chat's prompt sends until the session may
	// take it. Nil sends at once.
	SendGate promptSendGate
	// Renamer renames every KAS session in a chat's chain, so a closed chat's
	// rename reaches History at once.
	Renamer SessionRenamer
	// SpecApprovals is the spec-phase approval record, and it may be nil: a build
	// with no config dir has no store to persist one to. The handler answers a
	// 503 in that state rather than reporting an approval nothing kept.
	SpecApprovals SpecApprovals
	// Tangents resolves a tangent's parent and reads its summary for a merge.
	Tangents TangentAccess
	// StepSessions refuses resuming a workflow step's session as a chat. Nil checks nothing.
	StepSessions stepSessions
	// Workspace is last for fieldalignment (a trailing length word stops
	// the leading-pointer count early).
	Workspace Workspace
}

Roles is the wiring-time role set: the host names which interface answers each role once, at registration, and RegisterDefaults hands every handler only the roles its signature declares. A plain struct no handler takes, so nothing widens as the host grows.

type RunStopCause added in v0.12.0

type RunStopCause int

RunStopCause is why a by-chain teardown ends a chat's runs, which decides whether the stop is attributed to the user.

const (
	// RunStopTabClosed is the user closing the chat's tab with retention off.
	RunStopTabClosed RunStopCause = iota
	// RunStopRetention is the retention purge, which nobody asked for.
	RunStopRetention
	// RunStopChatDeleted is the user deleting the chat.
	RunStopChatDeleted
)

type SessionCaller added in v0.13.0

type SessionCaller interface {
	// contains filtered or unexported methods
}

SessionCaller is one call addressed to one session: a chat's own, or the session of one of a run's steps.

type SessionRenamer added in v0.12.0

type SessionRenamer interface {
	RenameSession(ctx context.Context, sessionID, title string) error
}

SessionRenamer renames a KAS session, resident or not, with a session-less engine call.

type ShellText added in v0.12.0

type ShellText struct {
	Text string
	// AltScreen reports that a full-screen program holds the screen, so Text is
	// the shell output beneath it rather than what is shown.
	AltScreen bool
	// Exited reports that the shell process ended; Text is its final output.
	Exited bool
}

ShellText is the built-in shell's output as plain text, oldest line first.

type SpecApprovals

type SpecApprovals interface {
	Approve(ctx context.Context, dir, phase, hash, user string) error
}

SpecApprovals is the approval record as this command uses it. Declared at the consumer; the store is internal/specapproval's.

type SteerEnd added in v0.12.0

type SteerEnd struct {
	Owner string
	End   SteerTurnEnd
}

SteerEnd is one pending turn end: the owner its rows carry and the end itself.

type SteerHolder added in v0.10.12

type SteerHolder struct {
	// Turn is the prompt turn a delivering route is aimed at; the record routes
	// the row only while it names that turn as starting or bound.
	Turn        string
	Held        bool
	PromptClass bool
	Live        bool
	// Delivering is the parked drain itself, which must not re-park its head row.
	Delivering bool
}

SteerHolder is what admission says about a chat when a steer is routed.

type SteerJob added in v0.10.12

type SteerJob struct {
	Chat marotte.ChatID
}

SteerJob is a flush a frame fold hands to the command side, which runs it under the chat's steer lock.

type SteerJobs added in v0.10.12

type SteerJobs interface {
	// Ends lists the chat's pending turn ends, oldest first.
	Ends(chatID marotte.ChatID) []SteerEnd
	// JobRows re-reads an end's rows, lead first; gone is the chat's teardown.
	JobRows(chatID marotte.ChatID, owner string) (rows []SteerRow, gone bool)
	// StraysCleared marks the end's in-KAS rows named by a clear (all of them for a
	// dead bridge) as not in KAS; a row read before the clear leaves the set.
	StraysCleared(chatID marotte.ChatID, owner string, cleared []string, all bool)
	// Release hands the end's remaining rows, or only its in-KAS ones, back.
	Release(chatID marotte.ChatID, owner string, inKASOnly bool)
	// EndUnsent makes every row the end still owns that KAS does not hold unsent
	// and unowned, and drops the end.
	EndUnsent(chatID marotte.ChatID, owner string)
	// UnsentRows answers the unread rows the drain may send, lead first; none
	// while KAS may re-inject them, unless the drain follows a drained death.
	UnsentRows(chatID marotte.ChatID, deathDrained bool) []SteerRow
	// Delivered retires the named rows, once the prompt carrying them has opened.
	Delivered(chatID marotte.ChatID, keys []string)
	// PlanFlush answers the sends the record owes after a read or a clear.
	PlanFlush(chatID marotte.ChatID) []SteerSend
	// NeedsPostLoadClear: KAS may re-inject steers this server also holds, and
	// nothing is reading yet.
	NeedsPostLoadClear(chatID marotte.ChatID) bool
	// PostLoadCleared: a row of this record's the clear named goes back to parked,
	// to be sent again in order.
	PostLoadCleared(chatID marotte.ChatID, cleared []string, landed bool)
	// AgentRowsWaiting reports an agent row in KAS's buffer, which a clear would
	// drop with no re-wake.
	AgentRowsWaiting(chatID marotte.ChatID) bool
	// NextParked answers the head parked row for turnID, the lead first, after
	// parking every unowned unsent row; it parks and answers nothing while the
	// record names turnID neither starting nor bound, or KAS may re-inject them.
	NextParked(chatID marotte.ChatID, turnID string) (key, text string, ok bool)
}

SteerJobs is the record as the close pipeline and the prompt's drain drive it.

type SteerLedger

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

SteerLedger records the steers this server sent, each until it expires. Safe for concurrent use. Construct with NewSteerLedger.

func NewSteerLedger

func NewSteerLedger() *SteerLedger

NewSteerLedger returns an empty ledger.

func (*SteerLedger) ForgetChat

func (l *SteerLedger) ForgetChat(chatID marotte.ChatID)

ForgetChat drops every steer recorded for one chat, at its teardown. A linear scan over the bounded map rather than a second index.

func (*SteerLedger) RecordUserSteer

func (l *SteerLedger) RecordUserSteer(chatID marotte.ChatID, steerID string)

RecordUserSteer records that this server sent steerID for chatID. KAS builds its own steer id from the messageId, so steerID matches every later frame.

func (*SteerLedger) SteerOrigin

func (l *SteerLedger) SteerOrigin(chatID marotte.ChatID, steerID string) marotte.SteerOrigin

SteerOrigin answers whose words the steer is: a recorded, unexpired id is the user's, everything else the agent's. Absence is a real answer, not a failed lookup.

type SteerOpResult added in v0.10.12

type SteerOpResult struct {
	// Resend is the kept rows' combined send, state already written; nil when
	// nothing is kept or their turn closed under the op.
	Resend *SteerSend
	// Reason is a refusal class: chat gone, the clear had no reply, or the target
	// was read before the clear landed.
	Reason string
}

SteerOpResult is what an op learns when it re-reads the record after its clear.

type SteerQueue added in v0.10.12

type SteerQueue interface {
	// LockSteerOps serializes every operation on the chat's steering buffer;
	// different chats never wait on each other. The error is the context's.
	LockSteerOps(ctx context.Context, chatID marotte.ChatID) (unlock func(), err error)
	// AwaitReadLoop parks until the bridge's read loop has folded every frame that
	// preceded a response at seq, reporting whether it got there.
	AwaitReadLoop(ctx context.Context, chatID marotte.ChatID, seq uint64) bool
	// RouteSteer answers the sends to issue, in order, or the refusal class.
	RouteSteer(chatID marotte.ChatID, key, text string, h SteerHolder) (sends []SteerSend, refuse string)
	SteerSent(chatID marotte.ChatID, s SteerSend, queued bool, err error)
	// BeginRemove decides a delete. A row KAS does not hold is deleted at once and
	// needsClear is false; otherwise the op owns its rows until EndOp.
	BeginRemove(chatID marotte.ChatID, key, opID string) (needsClear bool, refuse string)
	// RemoveCleared re-reads after the delete's clear (landed false: no reply).
	RemoveCleared(chatID marotte.ChatID, opID string, cleared []string, landed bool) SteerOpResult
	// BeginDiscard decides a Discard all; needsClear is false when nothing is in KAS.
	BeginDiscard(chatID marotte.ChatID, opID string) (needsClear bool)
	// DiscardCleared re-reads after the discard's clear, or settles it with none.
	DiscardCleared(chatID marotte.ChatID, opID string, landed bool) SteerOpResult
	OpSent(chatID marotte.ChatID, opID string, s SteerSend, queued bool, err error)
	// EndOp releases every row the op still owns. When a turn closed under the op
	// its rows go to a pending end that close's pipeline resolves; when a stale
	// bind ended the op's channel they are settled and reroute is true, for the
	// caller to route them before it lets go of the steer lock.
	EndOp(chatID marotte.ChatID, opID string) (reroute bool)
	OnSteerJob(run func(SteerJob))
	// SetSteerLead records the send-now arrow's row, sent first; undo takes it
	// back when the stop could not be sent.
	SetSteerLead(chatID marotte.ChatID, key string) (undo func())
}

SteerQueue is the server's record of the user's mid-turn steers and of what KAS's buffer holds, as the steer commands drive it. Every method decides under the record's lock and writes state before the caller's RPC; the caller holds the chat's steer lock across the whole operation.

type SteerRecorder

type SteerRecorder interface {
	RecordUserSteer(chatID marotte.ChatID, steerID string)
}

SteerRecorder is what the steer commands need of the steer ledger: record that THIS server sent a steer under an id before the RPC, which is the only ordering that beats KAS's own notification (see cmdSteer).

type SteerRow added in v0.10.12

type SteerRow struct {
	Key   string
	Text  string
	InKAS bool
}

SteerRow is one row a pending end holds, or one unread row the drain sends.

type SteerSend added in v0.10.12

type SteerSend struct {
	ID   string
	Text string
	Keys []string
}

SteerSend is one _session/steer the record decided on, state already written. Keys names the rows it carries when ID is not a row's own key.

type SteerTarget added in v0.13.0

type SteerTarget struct {
	Caller SessionCaller
	Queue  SteerQueue
	Ledger SteerRecorder
	Key    marotte.ChatID
}

SteerTarget is one steering buffer and what reaches it: a chat's own session, or one of a run's step sessions. Key names the buffer's record in Queue and Ledger. Caller is nil when the session is not resident.

type SteerTurnEnd added in v0.10.12

type SteerTurnEnd struct {
	// Exit closes when the closing bridge's forward goroutine has drained, for a
	// bridge death: a resend admitted earlier would reach the dying process.
	Exit   <-chan struct{}
	TurnID string
	Source marotte.TurnOpenSource
	// TurnSeq is the closed turn's open sequence: a close pipeline resolves only
	// the ends at or below its own.
	TurnSeq     uint64
	BridgeDeath bool
}

SteerTurnEnd is a turn's end as the steer record saw it.

type TabOpened

type TabOpened struct {
	Subject marotte.TabSubject
	Version uint64
	// Created is false for an already-open (Kind, Ref), which mutates nothing and
	// emits no event, so a caller waiting on that event would wait forever.
	Created bool
}

TabOpened is what an open answers with.

type TabSet

type TabSet interface {
	Open(ctx context.Context, spec marotte.OpenTab) (subject marotte.TabSubject, created bool, version uint64, err error)
	Close(ctx context.Context, id string) ([]marotte.TabSubject, uint64, error)
	Reorder(ctx context.Context, ids []string) (uint64, error)
	SetPinned(ctx context.Context, id string, pinned bool) (uint64, error)
	SetSeen(ctx context.Context, id string, seen uint64) (uint64, error)
	Reparent(ctx context.Context, id, parent string) (uint64, error)
	List() ([]marotte.TabSubject, uint64)
	Subtree(id string) []marotte.TabSubject
}

TabSet is the open-tab set as this package uses it, declared at the consumer since internal/tabs exports no interface of its own. List is here beyond the mutations because the capacity reservation needs the count and every event needs the expanded order, which no mutation returns; Subtree is what a Close of an id will remove, asked before that close commits.

type TangentAccess added in v0.13.0

type TangentAccess interface {
	// TangentParent answers the chat the tangent chatID was forked from, ErrNotATangent or
	// ErrTangentParentGone, or another error when KAS's session list could not be read.
	TangentParent(ctx context.Context, chatID marotte.ChatID) (marotte.ChatID, error)
	// TurnReply answers the main-lane text the agent wrote in one turn.
	TurnReply(ctx context.Context, chatID marotte.ChatID, turnID string) (string, error)
}

TangentAccess is what a merge reads beyond the prompt path.

type TurnFence added in v0.12.0

type TurnFence struct {
	Epoch uint64
	Seq   uint64
}

TurnFence names a closed turn by its lifecycle epoch and open sequence. A drain opens its turn only while no turn opened after the closed one; the zero fence is unfenced.

type TurnOpen added in v0.12.0

type TurnOpen struct {
	Prompt *marotte.EntryPrompt
	Init   func(*marotte.Chat)
	// Replaces names the turn this open stands in for (the empty-turn retry's): a stop requested after that turn
	// opened refuses the open with ErrTurnStopped, decided in the section that would append it.
	Replaces string
	Source   marotte.TurnOpenSource
	Fence    TurnFence
	// Awaited takes a second completion handle for a caller that awaits the turn
	// past its runner's release; that caller releases it.
	Awaited bool
}

TurnOpen is what OpenTurn appends and registers: the source, the prompt (nil for a source with none), the header fallback, and the fence the open must find intact.

type TurnRef added in v0.15.0

type TurnRef struct {
	Chat marotte.ChatID
	Turn string
}

TurnRef names one turn of one chat. Turn ids are never reused, so an operation handed a ref acts on that turn or, once it has gone, on nothing. The zero ref names none.

func (TurnRef) Named added in v0.15.0

func (r TurnRef) Named() bool

Named reports whether r names a turn.

type TurnStop added in v0.15.0

type TurnStop struct {
	// Own is the chat's own turn, closing included: its terminals are the stop's.
	Own TurnRef
	// Call is a sent prompt whose session/prompt KAS has not answered: Own, or a prompt a later turn_start
	// superseded.
	Call TurnRef
	// Next is the admitted, unsent prompt the send gate would send next because nothing else runs.
	Next TurnRef
	Chat marotte.ChatID
	// Token names the stop's send hold.
	Token uint64
	// Admitted names the reservation of a prompt admitted as Next would be but whose turn has not opened; zero for
	// none. The turn that reservation opens is kept unsent.
	Admitted uint64
	// Wire reports Own as an open turn KAS started.
	Wire bool
}

TurnStop is what one stop targets, captured at its admission; a zero ref names none.

type Workspace

type Workspace struct {
	// Terminal is the built-in shell; nil resolves a terminal reference to a reason.
	Terminal shellReader
	// Dir is the workspace root every relative path resolves against.
	Dir string
	// ConfigDir is where settings and hook files live.
	ConfigDir string
	// UploadsDir is the second root an ATTACHMENT path may name (where composer uploads land,
	// beside the workspace). Nothing else resolves against it, and a relative path never reaches
	// it. Empty is legal and inert: workspace-only confinement.
	UploadsDir string
}

Workspace carries the paths handlers resolve against: the working directory the hook writer and the shell spawn need, the config directory the prompt reads a setting out of, and the uploads directory an attachment may also name. An attachment path is confined to those roots before it is read. It also carries the built-in shell a `#` terminal reference reads.

Jump to

Keyboard shortcuts

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