prompt

package
v1.21.33 Latest Latest
Warning

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

Go to latest
Published: Jun 28, 2026 License: Apache-2.0 Imports: 12 Imported by: 0

Documentation

Overview

Package prompt is a generic, reusable question/prompt manager that mirrors the task manager: a Go API whose pending-prompt state streams to ready-made UI components. Every prompt is described by a JSON Schema (rendered by clicky-ui's JsonSchemaForm), state lives behind a swappable cache-backed Store (in-memory by default, valkey in the sibling submodule), and the global Manager doubles as the "interactive sink" the clicky root routes TTY-less prompts to.

Producers (commit checks, tool-permission approvals, AI elicitation) call Manager.Ask and block; consumers (the dashboard via SSE/JSON, or an AI model via a validated tool result) resolve the prompt by id.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ConfirmSchema

func ConfirmSchema(title string) json.RawMessage

ConfirmSchema builds an object schema with a single boolean "value".

func ConfirmValue

func ConfirmValue(ans Answer) bool

ConfirmValue extracts the boolean from a ConfirmSchema answer.

func HasInteractiveSink

func HasInteractiveSink() bool

HasInteractiveSink reports whether a Manager is installed — i.e. whether a TTY-less prompt has somewhere (the dashboard) to go instead of failing.

func MultiSelectSchema

func MultiSelectSchema(title string, options []string, maxItems int) json.RawMessage

MultiSelectSchema builds an object schema whose "choice" is an array of option indices. uniqueItems rejects a repeated selection; maxItems (when > 0) caps the number of selections so the sink/dashboard path enforces the same limit as the terminal multi-select.

func PreferSink

func PreferSink(ctx context.Context) bool

PreferSink reports that a prompt under ctx should be routed to the interactive sink in preference to any attached TTY: a manager is installed and ctx carries a Scope, meaning the prompt was raised on behalf of a UI-driven operation (e.g. the dashboard's auto-commit). Without this, a dashboard server launched from a terminal would render its prompts on that terminal instead of the dashboard.

func SelectSchema

func SelectSchema(title string, options []string) json.RawMessage

SelectSchema builds an object schema with a single required radio "choice" whose value is the selected option's index.

func SelectedIndex

func SelectedIndex(ans Answer) int

SelectedIndex extracts the chosen option index from a SelectSchema answer, or -1 if absent/unparseable.

func SelectedIndexes

func SelectedIndexes(ans Answer) []int

SelectedIndexes extracts the chosen option indices from a MultiSelectSchema answer.

func SetDefault

func SetDefault(m *Manager)

SetDefault installs the process-wide Manager that GlobalManager returns and that HasInteractiveSink reports on. A host installs one to route TTY-less prompts to the dashboard.

func TextSchema

func TextSchema(title string, secret bool) json.RawMessage

TextSchema builds an object schema with a single required free-text "value".

func TextValue

func TextValue(ans Answer) string

TextValue extracts the entered string from a TextSchema answer.

func Validate

func Validate(schema json.RawMessage, values map[string]any) error

Validate reports whether values satisfies schema. A nil/empty schema accepts anything (callers that supply no schema opt out of validation).

func WithScope

func WithScope(ctx context.Context, s Scope) context.Context

WithScope returns a context that tags prompts raised under it with s.

Types

type Answer

type Answer struct {
	Values    map[string]any
	Cancelled bool
	At        time.Time
}

Answer is the resolution of a Prompt. Values must satisfy the prompt's Schema (validated on Resolve) unless Cancelled is set.

type Filter

type Filter struct {
	Owner  string
	Kind   string
	State  string
	Labels map[string]string
}

Filter scopes a snapshot listing. Empty fields match everything; Labels entries must all match (AND).

type Manager

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

Manager brokers prompts between producers (which Ask and block) and consumers (which Resolve by id). It mirrors the approval-registry channel handoff but is generic and backed by a Store so the UI can list/stream pending prompts. The process-wide Manager (GlobalManager) also serves as the interactive sink the clicky root routes TTY-less PromptSelect/PromptText calls to.

func GlobalManager

func GlobalManager() *Manager

GlobalManager returns the installed process-wide Manager, or nil.

func NewManager

func NewManager(store Store) *Manager

NewManager returns a Manager backed by store (use NewMemory for the default).

func (*Manager) Ask

func (m *Manager) Ask(ctx context.Context, p Prompt) (Answer, error)

Ask registers p as a pending prompt and blocks until it is resolved or ctx is cancelled. A blank ID is assigned a unique one. The snapshot is always left in a terminal state (answered/cancelled/expired) and the pending channel removed before returning.

func (*Manager) JSONHandler

func (m *Manager) JSONHandler() http.Handler

JSONHandler serves the current prompt snapshots (filtered) as JSON — the poll fallback and reconnect path for the UI.

func (*Manager) List

func (m *Manager) List(filter Filter) []PromptSnapshot

List returns snapshots matching filter (newest first).

func (*Manager) Pending

func (m *Manager) Pending(id string) (PromptSnapshot, bool)

Pending returns the prompt's snapshot if it is still awaiting an answer.

func (*Manager) RegisterHandlers

func (m *Manager) RegisterHandlers(mux *http.ServeMux, prefix string)

RegisterHandlers wires the prompt API under prefix:

GET  {prefix}/prompts             snapshot listing (?owner=&kind=&state=&label=k=v)
GET  {prefix}/prompts/stream      SSE stream of snapshots (same filters)
GET  {prefix}/prompts/{id}        single snapshot
POST {prefix}/prompts/{id}/answer resolve a prompt

The static "stream" route is registered before "{id}" so it is not parsed as an id (Go 1.22 path-value routing).

func (*Manager) Resolve

func (m *Manager) Resolve(id string, ans Answer) error

Resolve delivers an answer to a pending prompt, unblocking its Ask. A non- cancelled answer is validated against the prompt's schema first; an invalid answer is rejected (and the prompt stays pending) so the producer never receives a malformed value.

func (*Manager) ResolveHandler

func (m *Manager) ResolveHandler() http.Handler

ResolveHandler resolves a prompt by id from a POSTed answer body. The id is read from the {id} path value. A schema-invalid answer yields 400; an unknown or already-resolved prompt yields 409.

func (*Manager) SSEHandler

func (m *Manager) SSEHandler() http.Handler

SSEHandler streams prompt snapshots (filtered) as Server-Sent Events, re-emitting only when the snapshot set changes. Unlike the task stream it never sends a terminal event: a dashboard stays subscribed to observe new prompts for the life of the page.

func (*Manager) Select

func (m *Manager) Select(ctx context.Context, title string, labels []string, opts SelectOptions) ([]int, bool)

Select asks the user to choose among labels and returns the chosen indices. ok is false when the prompt was cancelled. The prompt inherits any Scope on ctx. This is the entry point the clicky root routes PromptSelect/PromptMultiSelect to when no TTY is attached.

func (*Manager) Snapshot

func (m *Manager) Snapshot(id string) (PromptSnapshot, bool)

Snapshot returns the prompt's current snapshot regardless of state.

func (*Manager) Text

func (m *Manager) Text(ctx context.Context, title, def string, secret bool) (string, bool)

Text asks the user for a free-text value. ok is false when cancelled.

type MemoryConfig

type MemoryConfig struct {
	Retention time.Duration
}

MemoryConfig tunes the in-process Store. Zero Retention -> 10m: resolved prompts are kept that long so a reconnecting UI still sees the outcome, then GC'd. Pending prompts are never GC'd.

type Prompt

type Prompt struct {
	ID          string
	Kind        string
	Title       string
	Description string
	// Schema is a JSON Schema (object) describing the shape of Answer.Values. It is
	// what clicky-ui renders and what Resolve validates the answer against.
	Schema    json.RawMessage
	Default   map[string]any
	Owner     string
	Labels    map[string]string
	CreatedAt time.Time
}

Prompt is a question awaiting an answer, described by a JSON Schema form. Owner and Labels are the scoping keys the UI filters on (e.g. Owner=todoID, Labels{"session": id}) — the same role kind/labels/owner play for task RunMeta.

type PromptSnapshot

type PromptSnapshot struct {
	ID          string            `json:"id"`
	Kind        string            `json:"kind,omitempty"`
	Title       string            `json:"title"`
	Description string            `json:"description,omitempty"`
	Schema      json.RawMessage   `json:"schema"`
	State       string            `json:"state"`
	Value       map[string]any    `json:"value,omitempty"`
	Cancelled   bool              `json:"cancelled,omitempty"`
	Owner       string            `json:"owner,omitempty"`
	Labels      map[string]string `json:"labels,omitempty"`
	CreatedAt   string            `json:"createdAt,omitempty"`  // RFC3339
	ResolvedAt  string            `json:"resolvedAt,omitempty"` // RFC3339
}

PromptSnapshot is the JSON-serializable state of a prompt for UI/SSE consumption. It mirrors task.TaskSnapshot: a flat, omitempty-friendly wire shape.

type Scope

type Scope struct {
	Owner  string
	Kind   string
	Labels map[string]string
}

Scope tags prompts a producer raises so the UI can filter them to a todo (Owner) or a session (Labels["session"]). It travels on the context because the generic clicky PromptSelect/PromptText entry points carry no domain identifiers; a host (e.g. gavel's auto-commit) wraps its context with WithScope so prompts it triggers inherit the right Owner/Labels/Kind.

func ScopeFrom

func ScopeFrom(ctx context.Context) Scope

ScopeFrom returns the Scope attached to ctx, or the zero Scope.

func (Scope) IsZero

func (s Scope) IsZero() bool

IsZero reports whether s carries no scoping information.

type SelectOptions

type SelectOptions struct {
	Multi    bool
	MaxItems int
}

SelectOptions configures Manager.Select. Multi toggles checkbox vs radio; MaxItems (> 0, multi only) caps the number of selections so the sink path enforces the same limit as the terminal multi-select.

type State

type State string

State is the lifecycle of a prompt.

const (
	StatePending   State = "pending"
	StateAnswered  State = "answered"
	StateCancelled State = "cancelled"
	StateExpired   State = "expired"
)

type Store

type Store interface {
	Set(snap PromptSnapshot) error
	Get(id string) (PromptSnapshot, bool)
	Delete(id string) error
	// List returns snapshots matching filter, newest first.
	List(filter Filter) []PromptSnapshot
}

Store persists prompt snapshots so the manager's state survives a UI reconnect and can (with a valkey-backed Store) be shared across processes. It mirrors the metrics.Timeseries split: NewMemory here, valkey.New in the submodule. Implementations must be safe for concurrent use.

func NewMemory

func NewMemory(cfg MemoryConfig) Store

NewMemory returns an in-process Store. It needs no backend and is the zero-config default for CLIs, tests, and single-process servers.

Jump to

Keyboard shortcuts

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