Archived
feat(sessions): add the pending ask store
Own the one-open-ask-per-session lifecycle in daemon-side domain logic: validate model-authored question sets, validate submitted answers against them, and compute the answered-versus-unanswered outcome both the model-facing follow-up message and the browser record are rendered from. Also lands the answer half of the shared ask contract alongside its first consumer.
This commit is contained in:
+61
-5
@@ -440,6 +440,17 @@ export interface QueuedSessionMessage {
|
||||
text: string;
|
||||
}
|
||||
|
||||
/** Largest question set one `ask_user` call may post. */
|
||||
export const ASK_USER_QUESTION_LIMIT = 20;
|
||||
/** Largest option list one question may offer. */
|
||||
export const ASK_USER_OPTION_LIMIT = 12;
|
||||
/** Length bound for ids: the ask id, question ids, and option values. */
|
||||
export const ASK_USER_ID_MAX_LENGTH = 128;
|
||||
/** Length bound for model-authored prose: questions, details, and option labels. */
|
||||
export const ASK_USER_TEXT_MAX_LENGTH = 1_000;
|
||||
/** Length bound for the free text a user types into an "other" field. */
|
||||
export const ASK_USER_OTHER_TEXT_MAX_LENGTH = 4_000;
|
||||
|
||||
/** One selectable option of an {@link AskUserQuestion}. */
|
||||
export interface AskUserQuestionOption {
|
||||
/** Stable machine value reported back to the model. */
|
||||
@@ -481,13 +492,58 @@ export interface PendingAskUser {
|
||||
questions: AskUserQuestion[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Why an ask stopped being the session's open ask. The answer/outcome types that
|
||||
* describe *what* the user replied arrive with the pending-ask store that
|
||||
* computes them.
|
||||
*/
|
||||
/** Why an ask stopped being the session's open ask. */
|
||||
export type AskUserCloseReason = "submitted" | "superseded" | "cancelled";
|
||||
|
||||
/**
|
||||
* What the user replied to one question. Absent from a submission means the
|
||||
* question was left untouched; an empty `values` with no `otherText` means the
|
||||
* same thing.
|
||||
*/
|
||||
export interface AskUserAnswer {
|
||||
/** Matches an {@link AskUserQuestion.id} of the open ask. */
|
||||
id: string;
|
||||
/** Selected {@link AskUserQuestionOption.value} entries; several only when the question allows it. */
|
||||
values: string[];
|
||||
/** Free text typed into the "other" field, when the question allows it. */
|
||||
otherText?: string;
|
||||
}
|
||||
|
||||
/** One submit of the open ask: answers for some or all of its questions. */
|
||||
export interface AskUserSubmission {
|
||||
answers: AskUserAnswer[];
|
||||
}
|
||||
|
||||
/**
|
||||
* One question of a closed ask paired with what came back for it. Carries the
|
||||
* question itself so the record renders without the original ask still existing.
|
||||
*/
|
||||
export interface AskUserQuestionRecord {
|
||||
question: AskUserQuestion;
|
||||
/** True when at least one option was selected or "other" text was given. */
|
||||
answered: boolean;
|
||||
values: string[];
|
||||
otherText?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* The complete result of an ask, computed when it closes. Shared by the
|
||||
* model-facing follow-up message and the browser's read-only record, so both
|
||||
* report the same answered and unanswered questions.
|
||||
*/
|
||||
export interface AskUserOutcome {
|
||||
askId: string;
|
||||
reason: AskUserCloseReason;
|
||||
askedAt: string;
|
||||
closedAt: string;
|
||||
questions: AskUserQuestionRecord[];
|
||||
answeredCount: number;
|
||||
/** Ids of the questions left unanswered, in the order they were asked. */
|
||||
unansweredIds: string[];
|
||||
/** One line, for example `Answered 3 of 5; unanswered: q2, q5`. */
|
||||
summary: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Progress of the session startup window, where the daemon is still
|
||||
* constructing the agent session and no `PiAgentSession` exists yet, so
|
||||
|
||||
Reference in New Issue
Block a user