@mjasnikovs/pi-task 0.35.0 → 0.36.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/dist/remote/bridge.d.ts +6 -0
- package/dist/remote/bridge.js +1 -0
- package/dist/task/plan-io.d.ts +22 -4
- package/dist/task/plan-io.js +30 -7
- package/dist/task/plan-prompts.js +8 -0
- package/dist/task/plan-session.d.ts +36 -3
- package/dist/task/plan-session.js +89 -14
- package/dist/task/question-box.d.ts +8 -0
- package/dist/task/question-box.js +6 -4
- package/package.json +1 -1
package/dist/remote/bridge.d.ts
CHANGED
|
@@ -57,6 +57,12 @@ export interface AskSpec {
|
|
|
57
57
|
* {@link AskQuestionBoxSpec.manualLabel}). Ignored without `options`.
|
|
58
58
|
*/
|
|
59
59
|
manualLabel?: string;
|
|
60
|
+
/**
|
|
61
|
+
* Where the free-text card sits among `options` (see
|
|
62
|
+
* {@link AskQuestionBoxSpec.manualPosition}). Local picker only — remote
|
|
63
|
+
* browsers always render the text box above the action buttons.
|
|
64
|
+
*/
|
|
65
|
+
manualPosition?: number;
|
|
60
66
|
/**
|
|
61
67
|
* Extra buttons the BROWSER card shows alongside the recommendation, each
|
|
62
68
|
* answering with its own `value`. Unlike `options` — which are answers, and
|
package/dist/remote/bridge.js
CHANGED
package/dist/task/plan-io.d.ts
CHANGED
|
@@ -43,13 +43,31 @@ export declare function buildPlanBody(task: string): string;
|
|
|
43
43
|
* there is no hidden channel.
|
|
44
44
|
*/
|
|
45
45
|
export declare function formatPlanDecisions(entries: readonly PlanEntry[]): string;
|
|
46
|
+
/**
|
|
47
|
+
* The line that pins the DELIVERABLE, and it is not optional.
|
|
48
|
+
*
|
|
49
|
+
* The task prompt leads the handoff verbatim, and users reach /task-plan by
|
|
50
|
+
* phrasing the request as planning — live (aiz-client TASK_PLAN_0001,
|
|
51
|
+
* 2026-08-05): "Lets plan new tab and report @src/app/reports/". /task's refine
|
|
52
|
+
* read the verb as the deliverable and produced a task titled "Plan the addition
|
|
53
|
+
* of a new sub-tab…", whose ACCEPTANCE was "a planning document exists with
|
|
54
|
+
* placeholder sections" and whose VERIFY asserted that no `.ts`/`.tsx` file had
|
|
55
|
+
* changed. It passed. Nothing was built.
|
|
56
|
+
*
|
|
57
|
+
* Planning already happened — this prompt IS its output — so the handoff says so
|
|
58
|
+
* rather than letting the request's own wording re-open it. It rides on every
|
|
59
|
+
* handoff, decisions or none: the verb leaks regardless of how much got settled.
|
|
60
|
+
*/
|
|
61
|
+
export declare const HANDOFF_DELIVERABLE_RULE: string;
|
|
46
62
|
/**
|
|
47
63
|
* The prompt handed to /task when the user proceeds to execution.
|
|
48
64
|
*
|
|
49
65
|
* The task prompt leads, exactly as a bare `/task <prompt>` would, so refine sees
|
|
50
|
-
* a normal task description first; the
|
|
51
|
-
* Anything the user did NOT settle is simply
|
|
52
|
-
* about what is left, which is why this
|
|
53
|
-
* gap.
|
|
66
|
+
* a normal task description first; the deliverable rule and then the decisions
|
|
67
|
+
* follow as an authoritative block. Anything the user did NOT settle is simply
|
|
68
|
+
* absent — /task's own grill phase asks about what is left, which is why this
|
|
69
|
+
* block never invents a decision to fill a gap. A question the user left
|
|
70
|
+
* unanswered is likewise absent: "(skipped)" is not a decision, and carrying it
|
|
71
|
+
* as one is how a non-answer becomes an instruction.
|
|
54
72
|
*/
|
|
55
73
|
export declare function buildHandoffPrompt(task: string, entries: readonly PlanEntry[]): string;
|
package/dist/task/plan-io.js
CHANGED
|
@@ -73,20 +73,43 @@ export function buildPlanBody(task) {
|
|
|
73
73
|
export function formatPlanDecisions(entries) {
|
|
74
74
|
return entries.length === 0 ? '(none yet)' : formatPlanTranscript(entries);
|
|
75
75
|
}
|
|
76
|
+
/**
|
|
77
|
+
* The line that pins the DELIVERABLE, and it is not optional.
|
|
78
|
+
*
|
|
79
|
+
* The task prompt leads the handoff verbatim, and users reach /task-plan by
|
|
80
|
+
* phrasing the request as planning — live (aiz-client TASK_PLAN_0001,
|
|
81
|
+
* 2026-08-05): "Lets plan new tab and report @src/app/reports/". /task's refine
|
|
82
|
+
* read the verb as the deliverable and produced a task titled "Plan the addition
|
|
83
|
+
* of a new sub-tab…", whose ACCEPTANCE was "a planning document exists with
|
|
84
|
+
* placeholder sections" and whose VERIFY asserted that no `.ts`/`.tsx` file had
|
|
85
|
+
* changed. It passed. Nothing was built.
|
|
86
|
+
*
|
|
87
|
+
* Planning already happened — this prompt IS its output — so the handoff says so
|
|
88
|
+
* rather than letting the request's own wording re-open it. It rides on every
|
|
89
|
+
* handoff, decisions or none: the verb leaks regardless of how much got settled.
|
|
90
|
+
*/
|
|
91
|
+
export const HANDOFF_DELIVERABLE_RULE = 'PLANNING IS ALREADY DONE. This prompt is the OUTPUT of an interactive planning session '
|
|
92
|
+
+ 'that has now ended; you are the implementation step. Build the thing. Do not produce a '
|
|
93
|
+
+ 'plan, a design, a proposal, or a "planning-only" deliverable, do not write a document '
|
|
94
|
+
+ 'whose acceptance is that no code changed, and do not defer the work pending user '
|
|
95
|
+
+ 'confirmation — no user is available from here on.';
|
|
76
96
|
/**
|
|
77
97
|
* The prompt handed to /task when the user proceeds to execution.
|
|
78
98
|
*
|
|
79
99
|
* The task prompt leads, exactly as a bare `/task <prompt>` would, so refine sees
|
|
80
|
-
* a normal task description first; the
|
|
81
|
-
* Anything the user did NOT settle is simply
|
|
82
|
-
* about what is left, which is why this
|
|
83
|
-
* gap.
|
|
100
|
+
* a normal task description first; the deliverable rule and then the decisions
|
|
101
|
+
* follow as an authoritative block. Anything the user did NOT settle is simply
|
|
102
|
+
* absent — /task's own grill phase asks about what is left, which is why this
|
|
103
|
+
* block never invents a decision to fill a gap. A question the user left
|
|
104
|
+
* unanswered is likewise absent: "(skipped)" is not a decision, and carrying it
|
|
105
|
+
* as one is how a non-answer becomes an instruction.
|
|
84
106
|
*/
|
|
85
107
|
export function buildHandoffPrompt(task, entries) {
|
|
86
|
-
const decisions = entries.filter(e => e.kind !== 'note');
|
|
108
|
+
const decisions = entries.filter(e => e.kind !== 'note' && !(e.kind === 'decision' && /^\(skipped/i.test(e.answer.trim())));
|
|
109
|
+
const head = `${task.trim()}\n\n${HANDOFF_DELIVERABLE_RULE}`;
|
|
87
110
|
if (decisions.length === 0)
|
|
88
|
-
return
|
|
89
|
-
return (`${
|
|
111
|
+
return head;
|
|
112
|
+
return (`${head}\n\n`
|
|
90
113
|
+ `PLANNING DECISIONS — these were settled with the user before this task was started. `
|
|
91
114
|
+ `They are authoritative: implement them as written, and do not re-open or contradict `
|
|
92
115
|
+ `them. They may not cover everything; decide anything they leave open as usual.\n\n`
|
|
@@ -67,6 +67,14 @@ repo, and any stated constraints; it is shown to the user as a recommendation th
|
|
|
67
67
|
accept or override. When the question is a genuine binary "A or B?" fork, also give
|
|
68
68
|
the single best alternative as an ALT line; otherwise emit only the one SUGGESTED.
|
|
69
69
|
|
|
70
|
+
The SUGGESTED must DECIDE. Never recommend asking, clarifying, confirming, or
|
|
71
|
+
checking with the user, and never recommend waiting, deferring, or leaving the
|
|
72
|
+
question open — the user is answering this very question right now, so "find out
|
|
73
|
+
from the user" is not an answer, it is the question you just asked. If you truly
|
|
74
|
+
cannot tell which way to go, still commit to the option you would take if it were
|
|
75
|
+
your call, and let the user override it. A default that could not be implemented
|
|
76
|
+
as written is not a default.
|
|
77
|
+
|
|
70
78
|
OUTPUT FORMAT (exact) — read as much as you like, but your written REPLY is 2 or
|
|
71
79
|
3 lines and nothing else:
|
|
72
80
|
- Do NOT report what you read. No preamble, no analysis, no findings, no numbered
|
|
@@ -20,7 +20,10 @@
|
|
|
20
20
|
* doubles as "state a decision" when the model
|
|
21
21
|
* has nothing to ask
|
|
22
22
|
* ▶ proceed to execution — stop planning, hand the decisions to /task
|
|
23
|
-
* (PLAN_PROCEED)
|
|
23
|
+
* (PLAN_PROCEED). Always the LAST card in the
|
|
24
|
+
* box — it ends the session, so it sits under
|
|
25
|
+
* every move that continues it, including the
|
|
26
|
+
* free-text card (see `manualPosition`).
|
|
24
27
|
*
|
|
25
28
|
* The loop is pure with respect to I/O: every side effect (child calls, dialogs,
|
|
26
29
|
* persistence) arrives through {@link PlanSessionDeps}, so the whole interaction
|
|
@@ -85,6 +88,32 @@ export declare function pickQuestion<T extends {
|
|
|
85
88
|
* Deliberately shallow — an "X or Y?" in the question's own clause.
|
|
86
89
|
*/
|
|
87
90
|
export declare function looksLikeFork(question: string): boolean;
|
|
91
|
+
/**
|
|
92
|
+
* Does the recommended default DEFER the decision instead of making one?
|
|
93
|
+
*
|
|
94
|
+
* The prompt asks for a "concrete, decisive default", and nothing enforced it.
|
|
95
|
+
* Live (aiz-client TASK_PLAN_0001, 2026-08-05): the model asked "what specific
|
|
96
|
+
* report should this new tab display?" and recommended
|
|
97
|
+
* "clarify with the user what the report is meant to show before proceeding".
|
|
98
|
+
* The user pressed enter, so it was recorded `(accepted recommendation)` and rode
|
|
99
|
+
* into /task's handoff as an AUTHORITATIVE decision — an order not to proceed,
|
|
100
|
+
* addressed to a run where no user exists. /task duly built a task whose
|
|
101
|
+
* ACCEPTANCE was "a planning document with placeholder sections" and whose VERIFY
|
|
102
|
+
* asserted that no source file had changed.
|
|
103
|
+
*
|
|
104
|
+
* A deferral is not an answer, and the one place it can never be one is here: the
|
|
105
|
+
* user IS present during planning, so "ask the user" is a null move — that IS the
|
|
106
|
+
* question. Detection is anchored to the START of the default, which keeps it off
|
|
107
|
+
* legitimate product behaviour ("prompt the user to confirm deletion" decides
|
|
108
|
+
* something; "ask the user which report" decides nothing).
|
|
109
|
+
*/
|
|
110
|
+
export declare function isDeferralSuggestion(suggested: string): boolean;
|
|
111
|
+
/**
|
|
112
|
+
* Corrective re-prompt for a default that deferred the decision. Same one-shot
|
|
113
|
+
* budget and same quote-it-back shape as {@link planForkHint}, because the child
|
|
114
|
+
* is stateless and cannot otherwise know what it just recommended.
|
|
115
|
+
*/
|
|
116
|
+
export declare function planDecisiveHint(question: string, suggested: string): string;
|
|
88
117
|
/**
|
|
89
118
|
* Corrective re-prompt for a fork-shaped question that shipped only ONE option.
|
|
90
119
|
*
|
|
@@ -109,6 +138,7 @@ export type PlanAskSpec = AskSpec & {
|
|
|
109
138
|
value: string;
|
|
110
139
|
}[];
|
|
111
140
|
manualLabel: string;
|
|
141
|
+
manualPosition: number;
|
|
112
142
|
actions: {
|
|
113
143
|
label: string;
|
|
114
144
|
value: string;
|
|
@@ -155,9 +185,10 @@ interface PendingQuestion {
|
|
|
155
185
|
/**
|
|
156
186
|
* Build the picker for a pending model question: the recommendation first (index
|
|
157
187
|
* 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
|
|
188
|
+
* binary fork, then the two control actions. The free-text card is supplied by
|
|
159
189
|
* askQuestionBox itself — that is the "answer in your own words" affordance, and
|
|
160
|
-
* it is the same card grill and clarify already show
|
|
190
|
+
* it is the same card grill and clarify already show — placed just above the
|
|
191
|
+
* trailing "proceed to execution" card so proceed stays last.
|
|
161
192
|
*/
|
|
162
193
|
export declare function buildQuestionSpec(p: PendingQuestion): PlanAskSpec;
|
|
163
194
|
/**
|
|
@@ -165,6 +196,8 @@ export declare function buildQuestionSpec(p: PendingQuestion): PlanAskSpec;
|
|
|
165
196
|
* questions, or the cap/duplicate backstop stopped it. The same three moves are
|
|
166
197
|
* still on offer; only "answer this question" is gone, because there is no
|
|
167
198
|
* question, so the free-text card becomes "add a decision of your own".
|
|
199
|
+
* Proceed stays last here, as it is under a question: handing off to /task ends
|
|
200
|
+
* planning, so it never sits where a reflexive first-item press can hit it.
|
|
168
201
|
*/
|
|
169
202
|
export declare function buildIdleSpec(): PlanAskSpec;
|
|
170
203
|
/**
|
|
@@ -20,7 +20,10 @@
|
|
|
20
20
|
* doubles as "state a decision" when the model
|
|
21
21
|
* has nothing to ask
|
|
22
22
|
* ▶ proceed to execution — stop planning, hand the decisions to /task
|
|
23
|
-
* (PLAN_PROCEED)
|
|
23
|
+
* (PLAN_PROCEED). Always the LAST card in the
|
|
24
|
+
* box — it ends the session, so it sits under
|
|
25
|
+
* every move that continues it, including the
|
|
26
|
+
* free-text card (see `manualPosition`).
|
|
24
27
|
*
|
|
25
28
|
* The loop is pure with respect to I/O: every side effect (child calls, dialogs,
|
|
26
29
|
* persistence) arrives through {@link PlanSessionDeps}, so the whole interaction
|
|
@@ -98,6 +101,51 @@ export function pickQuestion(parsed) {
|
|
|
98
101
|
export function looksLikeFork(question) {
|
|
99
102
|
return /\bor\b/i.test(question.split('?')[0] ?? '');
|
|
100
103
|
}
|
|
104
|
+
/**
|
|
105
|
+
* Does the recommended default DEFER the decision instead of making one?
|
|
106
|
+
*
|
|
107
|
+
* The prompt asks for a "concrete, decisive default", and nothing enforced it.
|
|
108
|
+
* Live (aiz-client TASK_PLAN_0001, 2026-08-05): the model asked "what specific
|
|
109
|
+
* report should this new tab display?" and recommended
|
|
110
|
+
* "clarify with the user what the report is meant to show before proceeding".
|
|
111
|
+
* The user pressed enter, so it was recorded `(accepted recommendation)` and rode
|
|
112
|
+
* into /task's handoff as an AUTHORITATIVE decision — an order not to proceed,
|
|
113
|
+
* addressed to a run where no user exists. /task duly built a task whose
|
|
114
|
+
* ACCEPTANCE was "a planning document with placeholder sections" and whose VERIFY
|
|
115
|
+
* asserted that no source file had changed.
|
|
116
|
+
*
|
|
117
|
+
* A deferral is not an answer, and the one place it can never be one is here: the
|
|
118
|
+
* user IS present during planning, so "ask the user" is a null move — that IS the
|
|
119
|
+
* question. Detection is anchored to the START of the default, which keeps it off
|
|
120
|
+
* legitimate product behaviour ("prompt the user to confirm deletion" decides
|
|
121
|
+
* something; "ask the user which report" decides nothing).
|
|
122
|
+
*/
|
|
123
|
+
export function isDeferralSuggestion(suggested) {
|
|
124
|
+
const s = suggested.trim().replace(/^["'`*_\s]+/, '');
|
|
125
|
+
return (/^(ask|clarify|confirm|check|discuss|decide)\b[^.]{0,60}\b(with |from |the )?user\b/i.test(s)
|
|
126
|
+
|| /^(wait|hold off|hold|defer|postpone|pause|park)\b/i.test(s)
|
|
127
|
+
|| /^(tbd|to be (determined|decided|defined|specified))\b/i.test(s)
|
|
128
|
+
|| /^(pending|awaiting|await)\b/i.test(s)
|
|
129
|
+
|| /^leave (it|this|that)?\s*(to|for|open|undecided|unspecified)\b/i.test(s)
|
|
130
|
+
|| /^the user (must|should|needs? to|has to|will)\b/i.test(s)
|
|
131
|
+
|| /^(do not|don'?t|no)\b[^.]{0,40}\b(proceed|implement|build|start|write|decide)\b/i.test(s));
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Corrective re-prompt for a default that deferred the decision. Same one-shot
|
|
135
|
+
* budget and same quote-it-back shape as {@link planForkHint}, because the child
|
|
136
|
+
* is stateless and cannot otherwise know what it just recommended.
|
|
137
|
+
*/
|
|
138
|
+
export function planDecisiveHint(question, suggested) {
|
|
139
|
+
return ('[SYSTEM NOTE: Your previous reply asked this question:\n'
|
|
140
|
+
+ `"${question}"\n`
|
|
141
|
+
+ `and recommended: "${suggested}"\n`
|
|
142
|
+
+ 'That recommendation DEFERS the decision instead of making one — it tells the user to '
|
|
143
|
+
+ 'ask, clarify, wait, or leave it open. The user is answering this question RIGHT NOW, so '
|
|
144
|
+
+ '"find out from the user" is not an answer, it is the question you just asked. Ask the '
|
|
145
|
+
+ 'SAME question again — do not change the subject — and this time make SUGGESTED a '
|
|
146
|
+
+ 'concrete choice that could be implemented as written, naming real things from the repo. '
|
|
147
|
+
+ 'If the question is a genuine A-or-B fork, add the ALT line too. Nothing else.]');
|
|
148
|
+
}
|
|
101
149
|
/**
|
|
102
150
|
* Corrective re-prompt for a fork-shaped question that shipped only ONE option.
|
|
103
151
|
*
|
|
@@ -124,9 +172,10 @@ export function planForkHint(question) {
|
|
|
124
172
|
/**
|
|
125
173
|
* Build the picker for a pending model question: the recommendation first (index
|
|
126
174
|
* 0 is the green RECOMMENDED card), the alternative second when the question is a
|
|
127
|
-
* binary fork, then the two control actions. The free-text card is
|
|
175
|
+
* binary fork, then the two control actions. The free-text card is supplied by
|
|
128
176
|
* askQuestionBox itself — that is the "answer in your own words" affordance, and
|
|
129
|
-
* it is the same card grill and clarify already show
|
|
177
|
+
* it is the same card grill and clarify already show — placed just above the
|
|
178
|
+
* trailing "proceed to execution" card so proceed stays last.
|
|
130
179
|
*/
|
|
131
180
|
export function buildQuestionSpec(p) {
|
|
132
181
|
const options = [];
|
|
@@ -153,7 +202,10 @@ export function buildQuestionSpec(p) {
|
|
|
153
202
|
allowSkip: false,
|
|
154
203
|
options: [...options, ...actions],
|
|
155
204
|
actions,
|
|
156
|
-
manualLabel: PLAN_ANSWER_LABEL
|
|
205
|
+
manualLabel: PLAN_ANSWER_LABEL,
|
|
206
|
+
// The free-text card goes ABOVE "proceed to execution" — proceed ends the
|
|
207
|
+
// session, so it is the last card in the box, under every other move.
|
|
208
|
+
manualPosition: options.length + actions.length - 1
|
|
157
209
|
};
|
|
158
210
|
}
|
|
159
211
|
/**
|
|
@@ -161,11 +213,13 @@ export function buildQuestionSpec(p) {
|
|
|
161
213
|
* questions, or the cap/duplicate backstop stopped it. The same three moves are
|
|
162
214
|
* still on offer; only "answer this question" is gone, because there is no
|
|
163
215
|
* question, so the free-text card becomes "add a decision of your own".
|
|
216
|
+
* Proceed stays last here, as it is under a question: handing off to /task ends
|
|
217
|
+
* planning, so it never sits where a reflexive first-item press can hit it.
|
|
164
218
|
*/
|
|
165
219
|
export function buildIdleSpec() {
|
|
166
220
|
const actions = [
|
|
167
|
-
{ label:
|
|
168
|
-
{ label:
|
|
221
|
+
{ label: PLAN_ASK_LABEL, value: PLAN_ASK },
|
|
222
|
+
{ label: PLAN_PROCEED_LABEL, value: PLAN_PROCEED }
|
|
169
223
|
];
|
|
170
224
|
return {
|
|
171
225
|
localTitle: PLAN_NO_QUESTIONS,
|
|
@@ -177,7 +231,8 @@ export function buildIdleSpec() {
|
|
|
177
231
|
allowSkip: false,
|
|
178
232
|
options: actions,
|
|
179
233
|
actions,
|
|
180
|
-
manualLabel: PLAN_STATE_LABEL
|
|
234
|
+
manualLabel: PLAN_STATE_LABEL,
|
|
235
|
+
manualPosition: actions.length - 1
|
|
181
236
|
};
|
|
182
237
|
}
|
|
183
238
|
/**
|
|
@@ -278,11 +333,31 @@ export async function runPlanSession(deps) {
|
|
|
278
333
|
formatHint = PLAN_FORMAT_HINT;
|
|
279
334
|
continue;
|
|
280
335
|
}
|
|
336
|
+
// A default that defers decides nothing, and an accepted deferral
|
|
337
|
+
// reaches /task dressed as an authoritative decision. One re-prompt to
|
|
338
|
+
// make it decisive; if it comes back deferring anyway the option is
|
|
339
|
+
// DROPPED rather than shown, so an empty submit records "(skipped)" —
|
|
340
|
+
// an unanswered question — instead of a decision the user never made.
|
|
341
|
+
const defers = suggested !== undefined && isDeferralSuggestion(suggested);
|
|
342
|
+
if (defers && formatHint === null) {
|
|
343
|
+
deps.logDebug?.('plan: SUGGESTED deferred the decision — one re-prompt');
|
|
344
|
+
formatHint = planDecisiveHint(plain, suggested);
|
|
345
|
+
continue;
|
|
346
|
+
}
|
|
347
|
+
// When only the recommendation defers, the ALT is still a real
|
|
348
|
+
// commitment: promote it so the question keeps a usable default.
|
|
349
|
+
const usableSuggested = defers ? alt : suggested;
|
|
350
|
+
const usableAlt = defers ? undefined : alt;
|
|
351
|
+
if (defers) {
|
|
352
|
+
deps.logDebug?.(usableSuggested === undefined ?
|
|
353
|
+
'plan: SUGGESTED still deferred — question shown with no recommendation'
|
|
354
|
+
: 'plan: SUGGESTED still deferred — promoted the ALT to the recommendation');
|
|
355
|
+
}
|
|
281
356
|
// A question that offers a choice but ships one option leaves the
|
|
282
357
|
// user typing out the alternative the model just named. Same one-shot
|
|
283
358
|
// budget, quoting the question back so the (stateless) child re-asks
|
|
284
359
|
// this one instead of a new one.
|
|
285
|
-
if (
|
|
360
|
+
if (usableAlt === undefined && formatHint === null && !defers && looksLikeFork(plain)) {
|
|
286
361
|
deps.logDebug?.('plan: fork-shaped question with no ALT — one re-prompt');
|
|
287
362
|
formatHint = planForkHint(plain);
|
|
288
363
|
continue;
|
|
@@ -294,13 +369,13 @@ export async function runPlanSession(deps) {
|
|
|
294
369
|
pending = {
|
|
295
370
|
plain,
|
|
296
371
|
shown: render(question),
|
|
297
|
-
...(
|
|
298
|
-
suggested: stripInlineMarkdown(
|
|
299
|
-
shownSuggested: render(
|
|
372
|
+
...(usableSuggested !== undefined && {
|
|
373
|
+
suggested: stripInlineMarkdown(usableSuggested),
|
|
374
|
+
shownSuggested: render(usableSuggested)
|
|
300
375
|
}),
|
|
301
|
-
...(
|
|
302
|
-
alt: stripInlineMarkdown(
|
|
303
|
-
shownAlt: render(
|
|
376
|
+
...(usableAlt !== undefined && {
|
|
377
|
+
alt: stripInlineMarkdown(usableAlt),
|
|
378
|
+
shownAlt: render(usableAlt)
|
|
304
379
|
})
|
|
305
380
|
};
|
|
306
381
|
}
|
|
@@ -84,6 +84,14 @@ export interface AskQuestionBoxSpec {
|
|
|
84
84
|
* what the card actually does.
|
|
85
85
|
*/
|
|
86
86
|
manualLabel?: string;
|
|
87
|
+
/**
|
|
88
|
+
* Where the free-text card sits among the options. Defaults to the end, which
|
|
89
|
+
* is right when the picker is answering a question. /task-plan passes an
|
|
90
|
+
* earlier index so its "proceed to execution" card — the one that ENDS the
|
|
91
|
+
* session — is literally the last entry in the list, and never sits above a
|
|
92
|
+
* card the user is more likely to want.
|
|
93
|
+
*/
|
|
94
|
+
manualPosition?: number;
|
|
87
95
|
}
|
|
88
96
|
/**
|
|
89
97
|
* Show the boxed picker and resolve to the chosen option's `value`, the text the
|
|
@@ -143,12 +143,14 @@ export class QuestionBoxComponent {
|
|
|
143
143
|
*/
|
|
144
144
|
export async function askQuestionBox(ctx, spec) {
|
|
145
145
|
const { question, options, inputTitle, signal } = spec;
|
|
146
|
+
const manualIndex = Math.max(0, Math.min(options.length, spec.manualPosition ?? options.length));
|
|
147
|
+
const asCard = (o) => ({ label: o.label, recommended: o.recommended });
|
|
146
148
|
const cards = [
|
|
147
|
-
...options.
|
|
148
|
-
{ label: spec.manualLabel ?? MANUAL_CARD_LABEL }
|
|
149
|
+
...options.slice(0, manualIndex).map(asCard),
|
|
150
|
+
{ label: spec.manualLabel ?? MANUAL_CARD_LABEL },
|
|
151
|
+
...options.slice(manualIndex).map(asCard)
|
|
149
152
|
];
|
|
150
153
|
const colors = boxColors(ctx.ui.theme);
|
|
151
|
-
const manualIndex = options.length;
|
|
152
154
|
const choice = await ctx.ui.custom((_tui, _theme, _kb, done) => {
|
|
153
155
|
if (signal.aborted) {
|
|
154
156
|
done(undefined);
|
|
@@ -167,5 +169,5 @@ export async function askQuestionBox(ctx, spec) {
|
|
|
167
169
|
if (choice === manualIndex) {
|
|
168
170
|
return ctx.ui.input(inputTitle, undefined, { signal });
|
|
169
171
|
}
|
|
170
|
-
return options[choice]?.value;
|
|
172
|
+
return options[choice < manualIndex ? choice : choice - 1]?.value;
|
|
171
173
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mjasnikovs/pi-task",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.36.0",
|
|
4
4
|
"description": "Deterministic task planning and spec-orchestration for local models — crash-safe /task pipelines with verify/enforce gates, a real-time remote web view, and web/docs/fetch/worker subagent tools.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|