@mjasnikovs/pi-task 0.30.0 → 0.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Prompts for /task-plan's two planning children.
3
+ *
4
+ * /task-plan plans ONE task interactively before any spec work happens. Its
5
+ * question generator is deliberately the SAME SHAPE as /task-auto's clarify head
6
+ * (`AUTO_CLARIFY_PROMPT` in auto-prompts.ts): one numbered question, a required
7
+ * `SUGGESTED:` line, an optional `ALT:` line for a binary fork, the literal token
8
+ * NONE when nothing remains. That is not a coincidence — it means
9
+ * {@link parseClarifyList} parses this output UNCHANGED, and the boxed picker,
10
+ * the duplicate backstop and the YOLO picker all work here with no new code.
11
+ *
12
+ * What differs is the JOB. /task-auto's clarify asks what changes how a feature is
13
+ * SPLIT INTO TASKS; every question it asks is about plan shape, ordering, and which
14
+ * subsystems are in or out. /task-plan is planning a single unit of work that /task
15
+ * will implement in one run, so a "how do we split this" question is off-topic here
16
+ * and the prompt below rules it out explicitly.
17
+ */
18
+ /**
19
+ * Ask the SINGLE most important remaining question about ONE task.
20
+ *
21
+ * `priorQA` carries every decision made so far — model questions the user
22
+ * answered, answers the user volunteered, and the Q&A from questions the user
23
+ * asked the model — so each next question adapts to them. Output MUST match
24
+ * parseClarifyList.
25
+ */
26
+ export declare const PLAN_QUESTION_PROMPT: (task: string, priorQA: string) => string;
27
+ /**
28
+ * Answer a question the USER asked the model during planning.
29
+ *
30
+ * This is the one channel /task-plan adds that no existing phase has: everywhere
31
+ * else in pi-task the model asks and the user answers. Here the user asks. The
32
+ * answer is advisory — it is recorded in the plan file as a note, and it does NOT
33
+ * by itself decide anything; the user still answers the model's own questions.
34
+ *
35
+ * The abstention rule matters more here than anywhere else in the pipeline: a
36
+ * planning answer that invents a file, a flag, or an API reads exactly like a
37
+ * grounded one, and the user is asking BECAUSE they do not know. Saying "I could
38
+ * not confirm this" is a correct answer; a confident guess is not.
39
+ */
40
+ export declare const PLAN_ANSWER_PROMPT: (task: string, priorQA: string, question: string) => string;
@@ -0,0 +1,138 @@
1
+ /**
2
+ * Prompts for /task-plan's two planning children.
3
+ *
4
+ * /task-plan plans ONE task interactively before any spec work happens. Its
5
+ * question generator is deliberately the SAME SHAPE as /task-auto's clarify head
6
+ * (`AUTO_CLARIFY_PROMPT` in auto-prompts.ts): one numbered question, a required
7
+ * `SUGGESTED:` line, an optional `ALT:` line for a binary fork, the literal token
8
+ * NONE when nothing remains. That is not a coincidence — it means
9
+ * {@link parseClarifyList} parses this output UNCHANGED, and the boxed picker,
10
+ * the duplicate backstop and the YOLO picker all work here with no new code.
11
+ *
12
+ * What differs is the JOB. /task-auto's clarify asks what changes how a feature is
13
+ * SPLIT INTO TASKS; every question it asks is about plan shape, ordering, and which
14
+ * subsystems are in or out. /task-plan is planning a single unit of work that /task
15
+ * will implement in one run, so a "how do we split this" question is off-topic here
16
+ * and the prompt below rules it out explicitly.
17
+ */
18
+ /**
19
+ * Ask the SINGLE most important remaining question about ONE task.
20
+ *
21
+ * `priorQA` carries every decision made so far — model questions the user
22
+ * answered, answers the user volunteered, and the Q&A from questions the user
23
+ * asked the model — so each next question adapts to them. Output MUST match
24
+ * parseClarifyList.
25
+ */
26
+ export const PLAN_QUESTION_PROMPT = (task, priorQA) => `You are helping a user plan ONE implementation task before it is built, one clarifying question at a time.
27
+
28
+ TASK:
29
+ ${task.trim()}
30
+
31
+ DECISIONS SO FAR:
32
+ ${priorQA.trim() || '(none yet)'}
33
+
34
+ READ FIRST. Use the read tool on the repo and any referenced docs before you
35
+ decide what to ask — open the files this task would touch. A question you could
36
+ have asked without opening anything is almost always the wrong one: the good
37
+ question is the fork you can only see once you know what is already there, and
38
+ the SUGGESTED default has to name real, existing things to be worth accepting.
39
+ Reading is expected and costs you nothing; only your REPLY is short.
40
+
41
+ Output the SINGLE most important question that REMAINS — the one whose answer
42
+ would most change HOW THIS ONE TASK IS BUILT: what is in and out of its scope,
43
+ which approach or data shape it commits to, which existing code it changes versus
44
+ leaves alone, how it behaves at the edges the request does not pin down. Account
45
+ for the decisions so far:
46
+ - Never re-ask something already decided above.
47
+ - If a decision introduced a new fork or contradicts an assumption in the request
48
+ (for example, the user picked an approach the request did not anticipate), ask
49
+ about the most important consequence of that choice next.
50
+ - Drop questions the decisions have made irrelevant.
51
+
52
+ SCOPE RULES — read carefully:
53
+ - This is ONE task, implemented in ONE run. Do NOT ask how the work should be
54
+ split, sequenced, phased, or broken into separate tasks, milestones, or PRs —
55
+ that decision is not on the table here.
56
+ - Questions must clarify the EXISTING request. Do NOT propose new deliverables,
57
+ enhancements, migrations, or "while we're here" cleanups.
58
+ - Skip anything the implementer will naturally resolve by reading the code
59
+ (where a file lives, what a function is currently called, which test runner the
60
+ repo uses).
61
+ - Ask about decisions that are costly to reverse once the code is written.
62
+
63
+ YOU MUST propose a default answer for the question — every question you emit
64
+ carries exactly one SUGGESTED line. Never omit it, never leave it blank, never
65
+ refuse. Infer the most sensible, concrete, decisive default from the request, the
66
+ repo, and any stated constraints; it is shown to the user as a recommendation they
67
+ accept or override. When the question is a genuine binary "A or B?" fork, also give
68
+ the single best alternative as an ALT line; otherwise emit only the one SUGGESTED.
69
+
70
+ OUTPUT FORMAT (exact) — read as much as you like, but your written REPLY is 2 or
71
+ 3 lines and nothing else:
72
+ - Do NOT report what you read. No preamble, no analysis, no findings, no numbered
73
+ notes about files. The FIRST line of your reply is the question itself.
74
+ (Measured failure: a reply that opened with a numbered observation about a file
75
+ was read as the question.) What you learned belongs INSIDE the question and its
76
+ SUGGESTED line, as concrete names — not in a summary of your investigation.
77
+ - One question as a single numbered line: "1. ...".
78
+ - On the NEXT line (never inline), a line that begins with "SUGGESTED: <your recommended default>". This line is REQUIRED for every question.
79
+ - If your question names two alternatives — ANY question of the form "should it be X or Y?" — you MUST add a third line beginning with "ALT: <the option your SUGGESTED did not take>". Only a question with no second alternative (a genuinely open "what should X be?") omits the ALT line. Do not offer a choice in the question and then leave the user only one card.
80
+ - Put the core question in **bold**, followed by a short one-line rationale in plain prose. Backticks around code/identifiers are fine. Avoid other markdown (headings, bullet lists, links).
81
+ - Only when nothing decision-changing is left to ask — the request and the decisions above already pin down how this task is built — output exactly the single token NONE on its own line (and no SUGGESTED line).
82
+
83
+ EXAMPLES (format only — your wording will differ):
84
+
85
+ Open-ended question:
86
+ 1. **Which existing callers must keep working unchanged?** This decides whether the change can alter the exported signature or must add a new one alongside it.
87
+ SUGGESTED: keep every current caller working — add the new option with a default that preserves today's behaviour
88
+
89
+ Binary "A or B?" fork:
90
+ 1. **Should the retry live in the client wrapper or in each call site?** This decides whether one shared code path owns the backoff or every caller repeats it.
91
+ SUGGESTED: put it in the client wrapper so every call site inherits the same backoff
92
+ ALT: retry at each call site, so a caller can opt out
93
+
94
+ No question remains:
95
+ NONE`;
96
+ /**
97
+ * Answer a question the USER asked the model during planning.
98
+ *
99
+ * This is the one channel /task-plan adds that no existing phase has: everywhere
100
+ * else in pi-task the model asks and the user answers. Here the user asks. The
101
+ * answer is advisory — it is recorded in the plan file as a note, and it does NOT
102
+ * by itself decide anything; the user still answers the model's own questions.
103
+ *
104
+ * The abstention rule matters more here than anywhere else in the pipeline: a
105
+ * planning answer that invents a file, a flag, or an API reads exactly like a
106
+ * grounded one, and the user is asking BECAUSE they do not know. Saying "I could
107
+ * not confirm this" is a correct answer; a confident guess is not.
108
+ */
109
+ export const PLAN_ANSWER_PROMPT = (task, priorQA, question) => `You are helping a user plan ONE implementation task. The user has asked YOU a question about it. Answer it.
110
+
111
+ TASK:
112
+ ${task.trim()}
113
+
114
+ DECISIONS SO FAR:
115
+ ${priorQA.trim() || '(none yet)'}
116
+
117
+ THE USER'S QUESTION:
118
+ ${question.trim()}
119
+
120
+ Use the read tool to check the repo before you answer. Ground the answer in what
121
+ is actually there.
122
+
123
+ RULES — read carefully:
124
+ - Answer the question that was asked. Do not answer a different, easier one.
125
+ - Name only files, symbols, commands, and options you have VERIFIED exist — by
126
+ reading them, or because they appear in the task above. Never name a path or an
127
+ API from memory.
128
+ - If you cannot confirm the answer from the repo, say so plainly and say what you
129
+ would need to check. "I could not confirm X" is a correct answer here; a
130
+ confident guess is not.
131
+ - If the question asks for a recommendation, give ONE, and say in a sentence what
132
+ it costs.
133
+ - Do not start implementing, do not write code blocks longer than a few lines, and
134
+ do not restate the task back to the user.
135
+
136
+ OUTPUT:
137
+ - Plain prose, at most 8 short lines. No preamble, no headings, no bullet lists
138
+ longer than 4 items, no code fences.`;
@@ -0,0 +1,47 @@
1
+ /**
2
+ * The read-only contract for /task-plan.
3
+ *
4
+ * Planning happens BEFORE the work is agreed, so a plan session must leave the
5
+ * project exactly as it found it: no file created, edited, or deleted. Nothing is
6
+ * built yet, so any artifact a plan produced would be an artifact nobody approved
7
+ * — and it would sit in the tree looking like part of the change the user is
8
+ * still deciding whether to make.
9
+ *
10
+ * Two layers, because one of them is somebody else's flag:
11
+ *
12
+ * PREVENTION — the planning children run with {@link PLAN_TOOLS}, an allowlist
13
+ * of exactly one tool. pi applies `--tools` to built-in, extension AND custom
14
+ * tools, so a write tool contributed by a whitelisted extension is excluded too
15
+ * (proven live — scripts/live-task-plan-readonly.ts). This is what actually
16
+ * makes the session read-only.
17
+ *
18
+ * VERIFICATION — after every child, the working tree is compared against the
19
+ * snapshot taken before it. `.pi-tasks/` is excluded (that is where the plan
20
+ * file itself lives — see collectTreeChanges), so what remains is precisely
21
+ * "did planning touch the project". If it ever fires, prevention has a hole:
22
+ * it is reported loudly and recorded in the plan file rather than silently
23
+ * tolerated. It never deletes anything — an unexplained file is a thing to show
24
+ * the user, not a thing to quietly destroy.
25
+ */
26
+ import type { TreeChangeSummary } from './write-guard.js';
27
+ /**
28
+ * The tool allowlist every /task-plan child runs under. One tool: `read`.
29
+ *
30
+ * Deliberately a named constant with a test pinning it (plan-readonly.test.ts):
31
+ * widening this string is the single edit that would end the read-only guarantee,
32
+ * and it should never happen by accident. The same value grill and clarify use
33
+ * for their generation children — planning has never needed more.
34
+ */
35
+ export declare const PLAN_TOOLS = "read";
36
+ /**
37
+ * What appeared in `after` that was not already in `before`.
38
+ *
39
+ * A set difference, not an emptiness check: /task-plan runs against whatever the
40
+ * user already has in progress, and a tree that was dirty when planning started
41
+ * is normal — only what planning ADDED is a violation.
42
+ */
43
+ export declare function newTreeChanges(before: TreeChangeSummary, after: TreeChangeSummary): TreeChangeSummary;
44
+ /** True when the summary names nothing at all. */
45
+ export declare function isEmptyChange(c: TreeChangeSummary): boolean;
46
+ /** One line naming what a planning step touched, for the notify and the record. */
47
+ export declare function formatReadOnlyViolation(step: string, c: TreeChangeSummary): string;
@@ -0,0 +1,67 @@
1
+ /**
2
+ * The read-only contract for /task-plan.
3
+ *
4
+ * Planning happens BEFORE the work is agreed, so a plan session must leave the
5
+ * project exactly as it found it: no file created, edited, or deleted. Nothing is
6
+ * built yet, so any artifact a plan produced would be an artifact nobody approved
7
+ * — and it would sit in the tree looking like part of the change the user is
8
+ * still deciding whether to make.
9
+ *
10
+ * Two layers, because one of them is somebody else's flag:
11
+ *
12
+ * PREVENTION — the planning children run with {@link PLAN_TOOLS}, an allowlist
13
+ * of exactly one tool. pi applies `--tools` to built-in, extension AND custom
14
+ * tools, so a write tool contributed by a whitelisted extension is excluded too
15
+ * (proven live — scripts/live-task-plan-readonly.ts). This is what actually
16
+ * makes the session read-only.
17
+ *
18
+ * VERIFICATION — after every child, the working tree is compared against the
19
+ * snapshot taken before it. `.pi-tasks/` is excluded (that is where the plan
20
+ * file itself lives — see collectTreeChanges), so what remains is precisely
21
+ * "did planning touch the project". If it ever fires, prevention has a hole:
22
+ * it is reported loudly and recorded in the plan file rather than silently
23
+ * tolerated. It never deletes anything — an unexplained file is a thing to show
24
+ * the user, not a thing to quietly destroy.
25
+ */
26
+ /**
27
+ * The tool allowlist every /task-plan child runs under. One tool: `read`.
28
+ *
29
+ * Deliberately a named constant with a test pinning it (plan-readonly.test.ts):
30
+ * widening this string is the single edit that would end the read-only guarantee,
31
+ * and it should never happen by accident. The same value grill and clarify use
32
+ * for their generation children — planning has never needed more.
33
+ */
34
+ export const PLAN_TOOLS = 'read';
35
+ /**
36
+ * What appeared in `after` that was not already in `before`.
37
+ *
38
+ * A set difference, not an emptiness check: /task-plan runs against whatever the
39
+ * user already has in progress, and a tree that was dirty when planning started
40
+ * is normal — only what planning ADDED is a violation.
41
+ */
42
+ export function newTreeChanges(before, after) {
43
+ const diff = (a, b) => {
44
+ const seen = new Set(b);
45
+ return a.filter(p => !seen.has(p));
46
+ };
47
+ return {
48
+ modified: diff(after.modified, before.modified),
49
+ added: diff(after.added, before.added),
50
+ deleted: diff(after.deleted, before.deleted)
51
+ };
52
+ }
53
+ /** True when the summary names nothing at all. */
54
+ export function isEmptyChange(c) {
55
+ return c.modified.length + c.added.length + c.deleted.length === 0;
56
+ }
57
+ /** One line naming what a planning step touched, for the notify and the record. */
58
+ export function formatReadOnlyViolation(step, c) {
59
+ const parts = [];
60
+ if (c.added.length > 0)
61
+ parts.push(`created ${c.added.join(', ')}`);
62
+ if (c.modified.length > 0)
63
+ parts.push(`modified ${c.modified.join(', ')}`);
64
+ if (c.deleted.length > 0)
65
+ parts.push(`deleted ${c.deleted.join(', ')}`);
66
+ return `/task-plan is read-only, but the ${step} step ${parts.join('; ')}`;
67
+ }
@@ -0,0 +1,184 @@
1
+ /**
2
+ * The /task-plan interaction loop.
3
+ *
4
+ * Sequential & adaptive, exactly like /task's grill (phases.ts `phaseGrill`) and
5
+ * /task-auto's clarify (auto-orchestrator.ts `planAuto`): ask ONE question at a
6
+ * time, feed every answer back into the next generation call so later questions
7
+ * react to earlier ones, and stop when the model emits NONE. The duplicate
8
+ * backstop (`isDuplicateQuestion` + `DUP_REPROMPT_HINT` + `MAX_DUP_STRIKES`), the
9
+ * markdown handling, the A/B answer-letter mapping and the YOLO policy are the
10
+ * SAME modules those two loops use — none of that is new here.
11
+ *
12
+ * What IS new is the control surface. In grill and clarify the user's only move is
13
+ * to answer the question in front of them. Here three moves are available at every
14
+ * single prompt, in that order of appearance:
15
+ *
16
+ * ❓ ask the model a question — the user asks, the model answers (PLAN_ASK)
17
+ * ✎ answer in your own words — the free-text card askQuestionBox already
18
+ * appends to every boxed picker; it is not new,
19
+ * it is simply always present here, and it
20
+ * doubles as "state a decision" when the model
21
+ * has nothing to ask
22
+ * ▶ proceed to execution — stop planning, hand the decisions to /task
23
+ * (PLAN_PROCEED)
24
+ *
25
+ * The loop is pure with respect to I/O: every side effect (child calls, dialogs,
26
+ * persistence) arrives through {@link PlanSessionDeps}, so the whole interaction
27
+ * is unit-testable without a TUI or a model.
28
+ */
29
+ import type { AskSpec } from '../remote/bridge.js';
30
+ import { type PlanEntry, type AnswerSource } from './plan-io.js';
31
+ /**
32
+ * Sentinel values the picker resolves to when the user takes a control action
33
+ * instead of answering. Deliberately shaped like the existing `USER_CANCELLED`
34
+ * sentinel (child-runner.ts): a value no model answer and no human ever types.
35
+ */
36
+ export declare const PLAN_ASK = "__plan_ask__";
37
+ export declare const PLAN_PROCEED = "__plan_proceed__";
38
+ export declare const PLAN_ASK_LABEL = "\u2753 Ask the model a question\u2026";
39
+ export declare const PLAN_PROCEED_LABEL = "\u25B6 Proceed to execution (hand off to /task)";
40
+ /** Free-text card label while a model question is on screen. */
41
+ export declare const PLAN_ANSWER_LABEL = "\u270E Answer in your own words\u2026";
42
+ /** …and when there is no question to answer, so the same card reads correctly. */
43
+ export declare const PLAN_STATE_LABEL = "\u270E Add a decision of your own\u2026";
44
+ /** Header shown once the model has nothing left to ask. */
45
+ export declare const PLAN_NO_QUESTIONS = "No further questions \u2014 the decisions so far settle how this task is built.";
46
+ /**
47
+ * Hard ceiling on model-generated questions for one plan. The loop is open-ended
48
+ * (it stops when the model emits NONE); this only bounds a model that never
49
+ * does. Matches /task-auto's MAX_CLARIFY_QUESTIONS, for the same reason.
50
+ */
51
+ export declare const MAX_PLAN_QUESTIONS = 8;
52
+ /**
53
+ * Corrective re-prompt for a question reply that did not follow the format —
54
+ * either nothing parseable at all, or a question with no `SUGGESTED:` line. Same
55
+ * shape and same one-shot budget as GRILL_AUTO_FORMAT_HINT (prompts.ts), which
56
+ * exists because the local model drops a required tag every so often and a
57
+ * silent fallback is worse than one extra call: an unparsed reply reads as "no
58
+ * questions left" and a missing SUGGESTED leaves the picker with nothing to
59
+ * recommend.
60
+ */
61
+ export declare const PLAN_FORMAT_HINT: string;
62
+ /** True when the reply is the deliberate "nothing left to ask" sentinel, as
63
+ * opposed to output the parser simply could not read. */
64
+ export declare function isNoneReply(raw: string): boolean;
65
+ /**
66
+ * Which of the parsed entries is the actual question.
67
+ *
68
+ * parseClarifyList turns EVERY numbered line into an entry, and the local model
69
+ * sometimes writes a numbered analysis note or two before the question it was
70
+ * asked for (measured live: the first numbered line was a note like
71
+ * "1. gateDebugWriter in orchestrator.ts — wraps a raw append function"). Taking
72
+ * entry 0 blindly then shows the note as the question and loses the SUGGESTED
73
+ * line that was attached further down.
74
+ *
75
+ * The SUGGESTED line is the reliable marker of the real question — the prompt
76
+ * requires exactly one, and parseClarifyList attaches it to the entry it follows.
77
+ * So: prefer the first entry that has one; fall back to the first entry when none
78
+ * does, which is the case the format re-prompt then covers.
79
+ */
80
+ export declare function pickQuestion<T extends {
81
+ suggested?: string;
82
+ }>(parsed: T[]): T | undefined;
83
+ /**
84
+ * Does the question offer the user a choice between two named alternatives?
85
+ * Deliberately shallow — an "X or Y?" in the question's own clause.
86
+ */
87
+ export declare function looksLikeFork(question: string): boolean;
88
+ /**
89
+ * Corrective re-prompt for a fork-shaped question that shipped only ONE option.
90
+ *
91
+ * Measured on the local model (scripts/live-task-plan-step0.ts, 15 reps): the
92
+ * SUGGESTED line is always there, but 10/15 questions named two alternatives and
93
+ * gave only one of them — so the picker showed a single card and the user had to
94
+ * type out the option the model itself had just proposed.
95
+ *
96
+ * The retry quotes the question back because the child is stateless (a fresh
97
+ * process per call, prompt only), so it cannot otherwise know what it just wrote.
98
+ * Validated before wiring (scripts/live-task-plan-fork-alt.ts): 6/6 fires
99
+ * recovered an ALT, and 6/6 re-asked the SAME question rather than changing the
100
+ * subject. It costs one extra child call on the questions where it fires.
101
+ */
102
+ export declare function planForkHint(question: string): string;
103
+ /** The ask spec the session hands to the UI: an {@link AskSpec} plus the picker
104
+ * entries. Kept structurally identical to what phaseGrill/planAuto build so the
105
+ * same SessionUI.ask serves all three. */
106
+ export type PlanAskSpec = AskSpec & {
107
+ options: {
108
+ label: string;
109
+ value: string;
110
+ }[];
111
+ manualLabel: string;
112
+ actions: {
113
+ label: string;
114
+ value: string;
115
+ }[];
116
+ };
117
+ export interface PlanSessionDeps {
118
+ /** Run the question-generation child. `hint` is the duplicate reprompt. */
119
+ generateQuestion(priorQA: string, hint: string | null): Promise<string>;
120
+ /** Run the child that answers a question the USER asked. */
121
+ answerUserQuestion(priorQA: string, question: string): Promise<string>;
122
+ /** Show the picker; resolves to a value, a control sentinel, or undefined
123
+ * when the user dismissed it. */
124
+ ask(spec: PlanAskSpec): Promise<string | undefined>;
125
+ /** Collect free text (the user's own question). undefined = cancelled. */
126
+ promptText(title: string, question: string): Promise<string | undefined>;
127
+ /** Display the model's answer to the user's question. */
128
+ showAnswer(question: string, answer: string): void | Promise<void>;
129
+ /** Called after every transcript change, for persistence. */
130
+ onEntries?(entries: readonly PlanEntry[]): void | Promise<void>;
131
+ /** Theme-aware markdown renderer for displayed text; identity when absent. */
132
+ renderMarkdown?(text: string): string;
133
+ /** Status line while a child runs (the widget's `lastLine`). */
134
+ setStatus?(line: string | undefined): void;
135
+ yolo?: boolean;
136
+ logDebug?(msg: string): void;
137
+ }
138
+ export type PlanOutcome = {
139
+ kind: 'proceed';
140
+ entries: PlanEntry[];
141
+ } | {
142
+ kind: 'cancelled';
143
+ entries: PlanEntry[];
144
+ };
145
+ interface PendingQuestion {
146
+ /** Plain text — persisted, and fed back to the model. */
147
+ plain: string;
148
+ /** Markdown-rendered — displayed. */
149
+ shown: string;
150
+ suggested?: string;
151
+ shownSuggested?: string;
152
+ alt?: string;
153
+ shownAlt?: string;
154
+ }
155
+ /**
156
+ * Build the picker for a pending model question: the recommendation first (index
157
+ * 0 is the green RECOMMENDED card), the alternative second when the question is a
158
+ * binary fork, then the two control actions. The free-text card is appended by
159
+ * askQuestionBox itself — that is the "answer in your own words" affordance, and
160
+ * it is the same card grill and clarify already show.
161
+ */
162
+ export declare function buildQuestionSpec(p: PendingQuestion): PlanAskSpec;
163
+ /**
164
+ * Build the picker for the state with NO pending question — the model is out of
165
+ * questions, or the cap/duplicate backstop stopped it. The same three moves are
166
+ * still on offer; only "answer this question" is gone, because there is no
167
+ * question, so the free-text card becomes "add a decision of your own".
168
+ */
169
+ export declare function buildIdleSpec(): PlanAskSpec;
170
+ /**
171
+ * Map what the picker returned onto the answer that gets recorded. Mirrors the
172
+ * identical mapping in phaseGrill and planAuto: an empty submit accepts the
173
+ * recommendation, a bare "A"/"B" from a remote user or the free-text fallback maps
174
+ * back to the option's full text, and anything else is taken verbatim.
175
+ */
176
+ export declare function resolveAnswer(p: PendingQuestion, raw: string): {
177
+ answer: string;
178
+ source: AnswerSource;
179
+ };
180
+ /** Copy for the dialog that collects the user's own question. */
181
+ export declare const ASK_TITLE = "Ask the model";
182
+ export declare const ASK_QUESTION = "What do you want to ask about this task? The answer is recorded as a note; it does not decide anything by itself.";
183
+ export declare function runPlanSession(deps: PlanSessionDeps): Promise<PlanOutcome>;
184
+ export {};