Documentation
¶
Overview ¶
Package question carries the ask_user round trip: the agent loop registers a batch of questions it cannot answer on its own, blocks until a client replies, and resumes with the answers as the tool result.
It is the sibling of internal/permission, and deliberately the same shape — a request that is created, announced on the bus, and awaited on a buffered reply channel, so the HTTP handler that answers it runs on a different goroutine from the one blocked on it. The difference is the payload: a permission is answered with a fixed word, a question is answered with free-form structure (selected option labels plus typed text) that only this package knows how to carry.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func AnswersByID ¶ added in v0.39.0
AnswersByID pairs a positional reply with the batch, keyed by question id. A question without an id is left out: nothing can branch on it.
func Visibility ¶ added in v0.39.0
Visibility reports, for each question in order, whether the user is shown it given the answers to the questions before it. Because a condition only ever names an earlier question, the walk is a single pass — a screen behind a skipped branch is skipped too, since its condition then reads a blank answer.
Types ¶
type Answer ¶
type Answer struct {
Selected []string `json:"selected,omitempty"`
Text string `json:"text,omitempty"`
}
Answer is the reply to one question. Selected carries the labels chosen from the model's options; Text carries whatever the user typed. Either may be empty: a blank answer is a legitimate non-answer, and what the model should do with it depends on the question — a preference left open is licence to use its own judgement, whereas a question whose content it actually needs was simply not answered.
type Condition ¶ added in v0.39.0
type Condition struct {
// Question is the id of an earlier question in the same batch.
Question string `json:"question"`
// Options are option labels of that question. The gated screen is shown
// when the answer selected any of them; an empty list matches any answer at
// all (a selection or typed text).
Options []string `json:"options,omitempty"`
// Not inverts the match: shown when none of Options was selected.
Not bool `json:"not,omitempty"`
}
Condition gates a question on an answer already given — the branching that lets one screen decide what the next screen asks. It names an earlier question by id and the option labels that reveal this one.
type Manager ¶
type Manager struct {
// contains filtered or unexported fields
}
Manager holds the pending batches. It is safe for concurrent use: the loop goroutine calls Create/Remove while the HTTP handler goroutine calls Reply.
func NewManager ¶
func NewManager() *Manager
func (*Manager) Create ¶
func (m *Manager) Create(req Request) *PendingRequest
func (*Manager) Get ¶
func (m *Manager) Get(id QuestionID) *PendingRequest
func (*Manager) PendingForSession ¶
PendingForSession returns all unanswered batches for a session. The UI uses this to restore the dialog when the user switches back to a session whose question was hidden by the view change — the agent loop is still blocked. Order is not guaranteed (map iteration).
func (*Manager) Remove ¶
func (m *Manager) Remove(id QuestionID)
Remove discards a pending batch without replying — used when the request is abandoned (e.g. the agent loop was cancelled while waiting for an answer).
type Option ¶
type Option struct {
Label string `json:"label"`
Description string `json:"description,omitempty"`
}
Option is one proposed answer the model offered. It is a suggestion, not a constraint: the dialog always carries a free-text field, so the set need not be exhaustive and the user is free to answer in their own words.
type PendingRequest ¶
PendingRequest holds a batch awaiting a reply.
type Question ¶
type Question struct {
ID string `json:"id,omitempty"`
Header string `json:"header,omitempty"`
Question string `json:"question"`
Options []Option `json:"options,omitempty"`
MultiSelect bool `json:"multiSelect,omitempty"`
ShowWhen *Condition `json:"showWhen,omitempty"`
}
Question is one prompt within a batch. Header is the short title shown on the question's own screen; MultiSelect says whether several options may be selected at once (single-select is the default).
ID is a short slug another question can branch on; ShowWhen makes this screen conditional on an earlier answer. A question with no ShowWhen is always shown, so a batch that does not branch is unchanged.
type QuestionID ¶
type QuestionID = id.QuestionID
func NewQuestionID ¶
func NewQuestionID() QuestionID
type Reply ¶
type Reply struct {
Answers []Answer `json:"answers"`
}
Reply is the reply to a batch: one Answer per question, in the order the questions were asked.
type Request ¶
type Request struct {
ID QuestionID `json:"questionId"`
SessionID string `json:"sessionId"`
// OwnerSessionID names the session whose loop is actually blocked when this
// copy was surfaced under another session — a teammate's batch mirrored to
// its parent. Empty when the batch is its own session's.
OwnerSessionID string `json:"ownerSessionId,omitempty"`
// Teammate is the board handle of the teammate that asked, so the dialog can
// say whose question the user is answering. It is set on every copy of a
// teammate's batch — the teammate's own page's copy included — and empty
// only for a batch the session itself asked, with no teammate involved.
Teammate string `json:"teammate,omitempty"`
Questions []Question `json:"questions"`
}
Request is a whole batch, as handed to the UI.