@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.
- package/README.md +31 -0
- package/dist/index.js +2 -0
- package/dist/remote/bridge.d.ts +18 -0
- package/dist/remote/bridge.js +2 -0
- package/dist/remote/protocol.d.ts +9 -0
- package/dist/remote/ui-script.js +20 -0
- package/dist/task/accept-debt.d.ts +51 -2
- package/dist/task/accept-debt.js +140 -7
- package/dist/task/auto-orchestrator.d.ts +1 -0
- package/dist/task/auto-orchestrator.js +10 -1
- package/dist/task/final-gate.d.ts +54 -1
- package/dist/task/final-gate.js +115 -3
- package/dist/task/plan-io.d.ts +55 -0
- package/dist/task/plan-io.js +94 -0
- package/dist/task/plan-orchestrator.d.ts +67 -0
- package/dist/task/plan-orchestrator.js +313 -0
- package/dist/task/plan-prompts.d.ts +40 -0
- package/dist/task/plan-prompts.js +138 -0
- package/dist/task/plan-readonly.d.ts +47 -0
- package/dist/task/plan-readonly.js +67 -0
- package/dist/task/plan-session.d.ts +184 -0
- package/dist/task/plan-session.js +373 -0
- package/dist/task/question-box.d.ts +8 -0
- package/dist/task/question-box.js +1 -1
- package/dist/task/spec-validation.d.ts +14 -0
- package/dist/task/spec-validation.js +21 -1
- package/dist/task/widget.d.ts +4 -0
- package/dist/task/widget.js +2 -2
- package/package.json +1 -1
|
@@ -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 {};
|