@mjasnikovs/pi-task 0.13.2 → 0.13.3
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 +3 -1
- package/dist/remote/bridge.js +1 -0
- package/dist/remote/protocol.d.ts +1 -0
- package/dist/remote/ui.js +25 -15
- package/dist/shared/child-process.js +3 -3
- package/dist/task/orchestrator.js +3 -1
- package/dist/task/parsers.d.ts +1 -0
- package/dist/task/parsers.js +31 -9
- package/dist/task/phases.js +9 -7
- package/dist/task/prompts.js +28 -48
- package/dist/workers/docs-project.js +2 -2
- package/dist/workers/pi-worker-docs.js +12 -5
- package/package.json +1 -1
package/dist/remote/bridge.d.ts
CHANGED
|
@@ -22,8 +22,10 @@ export interface AskSpec {
|
|
|
22
22
|
localTitle: string;
|
|
23
23
|
/** Plain question text for the browser card. */
|
|
24
24
|
question: string;
|
|
25
|
-
/**
|
|
25
|
+
/** Primary recommended option, prefilled in the local TUI. */
|
|
26
26
|
recommended?: string;
|
|
27
|
+
/** Secondary recommended option shown as a second button on the remote. */
|
|
28
|
+
recommended2?: string;
|
|
27
29
|
/** Whether the browser card shows a Skip button (answers with empty string). */
|
|
28
30
|
allowSkip: boolean;
|
|
29
31
|
}
|
package/dist/remote/bridge.js
CHANGED
package/dist/remote/ui.js
CHANGED
|
@@ -297,6 +297,7 @@ export function html(wsUrl) {
|
|
|
297
297
|
document.getElementById('viewer-close').onclick = function () { viewer.style.display = 'none'; };
|
|
298
298
|
let activePromptId = null;
|
|
299
299
|
let activeRecommended = '';
|
|
300
|
+
let activeRecommended2 = '';
|
|
300
301
|
let cancelArmTimer = null;
|
|
301
302
|
const toolCallMap = {};
|
|
302
303
|
let currentBubble = null;
|
|
@@ -737,6 +738,7 @@ export function html(wsUrl) {
|
|
|
737
738
|
promptInput.value = '';
|
|
738
739
|
promptInput.style.display = 'none';
|
|
739
740
|
promptRec.style.display = 'none';
|
|
741
|
+
activeRecommended2 = '';
|
|
740
742
|
if (cancelArmTimer) { clearTimeout(cancelArmTimer); cancelArmTimer = null; }
|
|
741
743
|
setEnabled(true);
|
|
742
744
|
}
|
|
@@ -776,36 +778,44 @@ export function html(wsUrl) {
|
|
|
776
778
|
promptButtons.appendChild(makeCancelBtn());
|
|
777
779
|
}
|
|
778
780
|
|
|
779
|
-
// Manual-entry view:
|
|
780
|
-
// recommendation view via "
|
|
781
|
-
function showManualEntry(
|
|
781
|
+
// Manual-entry view: empty textarea + Submit, reachable from the
|
|
782
|
+
// recommendation view via "Manual answer".
|
|
783
|
+
function showManualEntry() {
|
|
782
784
|
promptRec.style.display = 'none';
|
|
783
785
|
promptInput.style.display = 'block';
|
|
784
|
-
promptInput.value =
|
|
786
|
+
promptInput.value = '';
|
|
785
787
|
renderButtons([
|
|
786
|
-
makeBtn('Submit
|
|
788
|
+
makeBtn('Submit', 'primary', function () { answer(promptInput.value); }),
|
|
787
789
|
makeBtn('← Back', 'secondary', function () { showRecommendation(); })
|
|
788
790
|
]);
|
|
789
791
|
promptInput.focus();
|
|
790
792
|
}
|
|
791
793
|
|
|
792
|
-
// Recommendation view
|
|
793
|
-
// accept it in one tap or switch to manual entry.
|
|
794
|
+
// Recommendation view: 2-button mode when both options present, panel mode for one.
|
|
794
795
|
function showRecommendation() {
|
|
795
796
|
promptInput.style.display = 'none';
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
797
|
+
const buttons = [];
|
|
798
|
+
if (activeRecommended2) {
|
|
799
|
+
// Two-option mode: each recommendation is a direct-accept button.
|
|
800
|
+
promptRec.style.display = 'none';
|
|
801
|
+
buttons.push(makeBtn(activeRecommended, 'primary', function () { answer(activeRecommended); }));
|
|
802
|
+
buttons.push(makeBtn(activeRecommended2, 'secondary', function () { answer(activeRecommended2); }));
|
|
803
|
+
} else {
|
|
804
|
+
// Single recommendation: show it in the green panel.
|
|
805
|
+
promptRec.style.display = 'block';
|
|
806
|
+
buttons.push(makeBtn('✓ Accept', 'primary', function () { answer(activeRecommended); }));
|
|
807
|
+
}
|
|
808
|
+
buttons.push(makeBtn('✎ Manual answer', 'secondary', function () { showManualEntry(); }));
|
|
809
|
+
renderButtons(buttons);
|
|
801
810
|
}
|
|
802
811
|
|
|
803
812
|
function showPrompt(msg) {
|
|
804
813
|
activePromptId = msg.id;
|
|
805
814
|
promptQ.textContent = msg.question;
|
|
806
815
|
activeRecommended = msg.recommended || '';
|
|
816
|
+
activeRecommended2 = msg.recommended2 || '';
|
|
807
817
|
if (msg.recommended) {
|
|
808
|
-
// Mode A:
|
|
818
|
+
// Mode A: recommendation(s) present.
|
|
809
819
|
promptRecText.textContent = msg.recommended;
|
|
810
820
|
showRecommendation();
|
|
811
821
|
} else {
|
|
@@ -813,9 +823,9 @@ export function html(wsUrl) {
|
|
|
813
823
|
promptRec.style.display = 'none';
|
|
814
824
|
promptInput.style.display = 'block';
|
|
815
825
|
promptInput.value = '';
|
|
816
|
-
const buttons = [makeBtn('Submit
|
|
826
|
+
const buttons = [makeBtn('Submit', 'primary', function () { answer(promptInput.value); })];
|
|
817
827
|
if (msg.allowSkip) {
|
|
818
|
-
buttons.push(makeBtn('Skip
|
|
828
|
+
buttons.push(makeBtn('Skip', 'secondary', function () { answer(''); }));
|
|
819
829
|
}
|
|
820
830
|
renderButtons(buttons);
|
|
821
831
|
promptInput.focus();
|
|
@@ -199,9 +199,9 @@ export function summarizeToolArgs(toolName, args) {
|
|
|
199
199
|
if (toolName === 'bash' && typeof a.command === 'string') {
|
|
200
200
|
return a.command.replace(/\s+/g, ' ').trim();
|
|
201
201
|
}
|
|
202
|
-
if (toolName === 'pi-worker-docs'
|
|
203
|
-
typeof a.module === 'string'
|
|
204
|
-
typeof a.query === 'string') {
|
|
202
|
+
if (toolName === 'pi-worker-docs'
|
|
203
|
+
&& typeof a.module === 'string'
|
|
204
|
+
&& typeof a.query === 'string') {
|
|
205
205
|
const q = a.query.replace(/\s+/g, ' ').trim();
|
|
206
206
|
const truncated = q.length > 60 ? q.slice(0, 59) + '…' : q;
|
|
207
207
|
return `${a.module} "${truncated}"`;
|
|
@@ -168,7 +168,9 @@ export class TaskRunner {
|
|
|
168
168
|
const debugLogPath = path.join(tasksDir(cwd), `${id}-debug.log`);
|
|
169
169
|
this._deps.logDebug = (msg) => {
|
|
170
170
|
const line = `${new Date().toISOString()} ${msg}\n`;
|
|
171
|
-
fsp.appendFile(debugLogPath, line).catch(() => {
|
|
171
|
+
fsp.appendFile(debugLogPath, line).catch(() => {
|
|
172
|
+
/* ignore */
|
|
173
|
+
});
|
|
172
174
|
};
|
|
173
175
|
this._deps.logDebug(`run: start phase=${resumePhase}`);
|
|
174
176
|
// Register as active.
|
package/dist/task/parsers.d.ts
CHANGED
package/dist/task/parsers.js
CHANGED
|
@@ -102,22 +102,44 @@ export function parseAutoAnswer(raw) {
|
|
|
102
102
|
.split('\n')
|
|
103
103
|
.map(l => l.trim())
|
|
104
104
|
.filter(l => l.length > 0);
|
|
105
|
+
let suggested;
|
|
106
|
+
let alt;
|
|
107
|
+
let sawUnknown = false;
|
|
105
108
|
for (let i = 0; i < lines.length; i++) {
|
|
106
109
|
const t = lines[i];
|
|
107
110
|
const a = /^AN[SW]{1,3}E?R:\s*(.+)$/i.exec(t);
|
|
108
111
|
if (a)
|
|
109
112
|
return { kind: 'answered', text: a[1].trim(), raw };
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
113
|
+
if (!sawUnknown) {
|
|
114
|
+
const u = /^UNKNOWN:\s*(.*)$/i.exec(t);
|
|
115
|
+
if (u) {
|
|
116
|
+
sawUnknown = true;
|
|
117
|
+
const inline = u[1].trim();
|
|
118
|
+
if (inline.length > 0) {
|
|
119
|
+
suggested = inline;
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
const next = lines[i + 1];
|
|
123
|
+
if (next && !/^ALT:/i.test(next))
|
|
124
|
+
suggested = next;
|
|
125
|
+
}
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
if (alt === undefined) {
|
|
130
|
+
const altM = /^ALT:\s*(.+)$/i.exec(t);
|
|
131
|
+
if (altM)
|
|
132
|
+
alt = altM[1].trim();
|
|
119
133
|
}
|
|
120
134
|
}
|
|
135
|
+
if (sawUnknown || suggested !== undefined || alt !== undefined) {
|
|
136
|
+
return {
|
|
137
|
+
kind: 'unknown',
|
|
138
|
+
...(suggested !== undefined && { suggested }),
|
|
139
|
+
...(alt !== undefined && { alt }),
|
|
140
|
+
raw
|
|
141
|
+
};
|
|
142
|
+
}
|
|
121
143
|
if (lines.length > 0)
|
|
122
144
|
return { kind: 'unknown', suggested: lines[0], raw };
|
|
123
145
|
return { kind: 'unknown', raw };
|
package/dist/task/phases.js
CHANGED
|
@@ -373,8 +373,6 @@ export async function phaseGrill(deps, ctx, widgetState, refined, research) {
|
|
|
373
373
|
const shownQ = renderInlineMarkdown(q, theme);
|
|
374
374
|
const plainQ = stripInlineMarkdown(q);
|
|
375
375
|
out.push(`Q${n + 1}: ${plainQ}`);
|
|
376
|
-
const rawTrim = auto.raw.trim();
|
|
377
|
-
out.push(` (auto-worker raw: ${rawTrim.length === 0 ? '(empty)' : rawTrim.replace(/\n/g, ' ⏎ ')})`);
|
|
378
376
|
let answer;
|
|
379
377
|
if (auto.kind === 'answered') {
|
|
380
378
|
answer = stripInlineMarkdown(auto.text);
|
|
@@ -382,21 +380,25 @@ export async function phaseGrill(deps, ctx, widgetState, refined, research) {
|
|
|
382
380
|
}
|
|
383
381
|
else {
|
|
384
382
|
const plainSuggested = auto.suggested === undefined ? undefined : stripInlineMarkdown(auto.suggested);
|
|
385
|
-
const
|
|
386
|
-
|
|
387
|
-
|
|
383
|
+
const plainAlt = auto.alt === undefined ? undefined : stripInlineMarkdown(auto.alt);
|
|
384
|
+
const localTitle = plainSuggested ?
|
|
385
|
+
plainAlt ?
|
|
386
|
+
`${shownQ}\nA: ${renderInlineMarkdown(auto.suggested, theme)}\nB: ${renderInlineMarkdown(auto.alt, theme)}`
|
|
387
|
+
: `${shownQ}\n${renderInlineMarkdown(auto.suggested, theme)}`
|
|
388
|
+
: shownQ;
|
|
388
389
|
widgetState.lastLine = `awaiting Q${n + 1}`;
|
|
389
390
|
const a = await ui.ask({
|
|
390
391
|
localTitle,
|
|
391
392
|
question: plainQ,
|
|
392
393
|
recommended: plainSuggested,
|
|
393
|
-
|
|
394
|
+
recommended2: plainAlt,
|
|
395
|
+
allowSkip: plainSuggested === undefined && plainAlt === undefined
|
|
394
396
|
});
|
|
395
397
|
if (a === undefined)
|
|
396
398
|
throw new Error(USER_CANCELLED);
|
|
397
399
|
const typed = a.trim();
|
|
398
400
|
if (typed.length === 0 && plainSuggested) {
|
|
399
|
-
answer =
|
|
401
|
+
answer = plainSuggested;
|
|
400
402
|
}
|
|
401
403
|
else if (typed.length === 0) {
|
|
402
404
|
answer = '(skipped)';
|
package/dist/task/prompts.js
CHANGED
|
@@ -176,57 +176,37 @@ ${research}
|
|
|
176
176
|
|
|
177
177
|
Answers so far:
|
|
178
178
|
${priorQA.trim() || '(none yet)'}`;
|
|
179
|
-
const GRILL_AUTO_ANSWER_PROMPT = (refined, research, question) => `You are pre-answering a clarifying question for an AI coding task. You have the refined task and the research notes. You
|
|
180
|
-
|
|
181
|
-
Your job is to produce a recommended default
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
-
|
|
187
|
-
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
task file, it's ANSWER.
|
|
199
|
-
|
|
200
|
-
UNKNOWN: accepting your default would do work that is costly to reverse —
|
|
201
|
-
file mutations, destructive operations, irreversible writes, tool
|
|
202
|
-
or dependency choices, format/structure decisions that change
|
|
203
|
-
downstream artifacts, anything that touches state outside the
|
|
204
|
-
task file. This INCLUDES choosing the implementation approach,
|
|
205
|
-
algorithm, or strategy — *how* the task is solved, not just
|
|
206
|
-
whether (e.g. "extract the value with a post-processing regex" vs
|
|
207
|
-
"rewrite the system prompt", "add a fallback step" vs "swap the
|
|
208
|
-
model", "parse manually" vs "use a library"). Approach decisions
|
|
209
|
-
shape the entire spec and are expensive to unwind once the agent
|
|
210
|
-
builds on them, so the user must vet the strategy: tag UNKNOWN and
|
|
211
|
-
surface your recommended approach as the default.
|
|
212
|
-
|
|
213
|
-
Output format — ONE LINE only, no preamble, no markdown:
|
|
179
|
+
const GRILL_AUTO_ANSWER_PROMPT = (refined, research, question) => `You are pre-answering a clarifying question for an AI coding task. You have the refined task and the research notes. You may use the read tool on files mentioned in the research (e.g. package.json) if it helps.
|
|
180
|
+
|
|
181
|
+
Your job is to produce a recommended default. If the default is one the user would almost certainly accept without thinking, tag it ANSWER and skip the user. Otherwise tag it UNKNOWN. YOU MUST PROPOSE A DEFAULT — never refuse, never leave it empty.
|
|
182
|
+
|
|
183
|
+
LIVE-DATA RULE:
|
|
184
|
+
- "### npm: <pkg>" blocks in EXTERNAL CONTEXT are LIVE registry data; use those version numbers, do NOT invent them from memory.
|
|
185
|
+
- "### service: <name>" blocks are LIVE web data; authoritative over training data for that service.
|
|
186
|
+
- "### freshness-check skipped" → tag UNKNOWN and say current state needs verification.
|
|
187
|
+
- No npm block + question is about latest/current version → tag UNKNOWN (training data goes stale).
|
|
188
|
+
|
|
189
|
+
REVERSIBILITY TEST:
|
|
190
|
+
ANSWER: cheap to undo (output style, policy, report format, obvious scope, standard convention).
|
|
191
|
+
UNKNOWN: costly to reverse (file mutations, tool/dependency choice, approach/algorithm, format that shapes downstream artifacts).
|
|
192
|
+
|
|
193
|
+
When the question is a binary "A or B?" choice, emit BOTH options:
|
|
194
|
+
UNKNOWN: <primary recommendation>
|
|
195
|
+
ALT: <alternative>
|
|
196
|
+
|
|
197
|
+
Output — no preamble, no markdown:
|
|
214
198
|
ANSWER: <one-line answer>
|
|
215
|
-
UNKNOWN: <
|
|
199
|
+
UNKNOWN: <primary option>
|
|
200
|
+
ALT: <secondary option> ← required when question is "A or B?"; omit otherwise
|
|
216
201
|
|
|
217
202
|
Examples:
|
|
218
|
-
ANSWER: report a summary with counts and representative examples
|
|
219
|
-
ANSWER: treat all warnings and errors as genuine issues
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
UNKNOWN: write output to ./report.md
|
|
223
|
-
UNKNOWN: extract
|
|
224
|
-
|
|
225
|
-
Examples of FORBIDDEN outputs:
|
|
226
|
-
UNKNOWN:
|
|
227
|
-
UNKNOWN: it depends
|
|
228
|
-
(empty)
|
|
229
|
-
I think the user should decide.
|
|
203
|
+
ANSWER: report a summary with counts and representative examples
|
|
204
|
+
ANSWER: treat all warnings and errors as genuine issues
|
|
205
|
+
UNKNOWN: use npm
|
|
206
|
+
ALT: use pnpm
|
|
207
|
+
UNKNOWN: write output to ./report.md
|
|
208
|
+
UNKNOWN: extract with a post-processing regex step
|
|
209
|
+
ALT: rewrite the system prompt
|
|
230
210
|
|
|
231
211
|
Refined task:
|
|
232
212
|
${refined}
|
|
@@ -52,8 +52,8 @@ function walkTsFiles(root) {
|
|
|
52
52
|
const full = path.join(dir, entry.name);
|
|
53
53
|
if (entry.isDirectory())
|
|
54
54
|
stack.push(full);
|
|
55
|
-
else if (entry.isFile()
|
|
56
|
-
(entry.name.endsWith('.ts') || entry.name.endsWith('.tsx'))) {
|
|
55
|
+
else if (entry.isFile()
|
|
56
|
+
&& (entry.name.endsWith('.ts') || entry.name.endsWith('.tsx'))) {
|
|
57
57
|
out.push(full);
|
|
58
58
|
}
|
|
59
59
|
}
|
|
@@ -82,7 +82,10 @@ export function registerPiWorkerDocs(pi, internals = {}) {
|
|
|
82
82
|
return textResult(`Project docs error: ${projectResult.message}`, {});
|
|
83
83
|
}
|
|
84
84
|
if (projectResult.kind === 'no_chunks') {
|
|
85
|
-
return textResult(`Project "${projectResult.projectName}" has no .ts/.tsx files indexed.`, {
|
|
85
|
+
return textResult(`Project "${projectResult.projectName}" has no .ts/.tsx files indexed.`, {
|
|
86
|
+
hitCache: projectResult.hitCache,
|
|
87
|
+
indexedFiles: projectResult.filesIngested
|
|
88
|
+
});
|
|
86
89
|
}
|
|
87
90
|
const { projectName, chunks, hitCache, filesIngested, indexingMs } = projectResult;
|
|
88
91
|
const baseDetails = {
|
|
@@ -110,10 +113,14 @@ export function registerPiWorkerDocs(pi, internals = {}) {
|
|
|
110
113
|
});
|
|
111
114
|
}
|
|
112
115
|
const parsed = parseChildOutput(child.stdout);
|
|
113
|
-
const verified = parsed.excerpt ?
|
|
114
|
-
|
|
115
|
-
:
|
|
116
|
-
|
|
116
|
+
const verified = parsed.excerpt ? isExcerptInContent(parsed.excerpt, concatenated) : undefined;
|
|
117
|
+
const text = formatResultText({
|
|
118
|
+
name: projectName,
|
|
119
|
+
version: 'local',
|
|
120
|
+
root: ctx.cwd,
|
|
121
|
+
entryDts: null,
|
|
122
|
+
readme: null
|
|
123
|
+
}, parsed, verified);
|
|
117
124
|
return textResult(text, {
|
|
118
125
|
...baseDetails,
|
|
119
126
|
childExitCode: 0,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mjasnikovs/pi-task",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.3",
|
|
4
4
|
"description": "Deterministic spec-orchestration for local models, with a bundled real-time remote web view and web/docs/fetch/worker subagent tools.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|