@mjasnikovs/pi-task 0.2.3 → 0.4.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 +13 -0
- package/dist/index.js +2 -0
- package/dist/remote/bridge.d.ts +78 -0
- package/dist/remote/bridge.js +184 -0
- package/dist/remote/broadcast.d.ts +6 -0
- package/dist/remote/broadcast.js +27 -0
- package/dist/remote/events.d.ts +3 -0
- package/dist/remote/events.js +81 -0
- package/dist/remote/history.d.ts +22 -0
- package/dist/remote/history.js +25 -0
- package/dist/remote/protocol.d.ts +42 -0
- package/dist/remote/protocol.js +13 -0
- package/dist/remote/qr.d.ts +1 -0
- package/dist/remote/qr.js +5 -0
- package/dist/remote/register.d.ts +2 -0
- package/dist/remote/register.js +149 -0
- package/dist/remote/server.d.ts +31 -0
- package/dist/remote/server.js +126 -0
- package/dist/remote/state.d.ts +2 -0
- package/dist/remote/state.js +7 -0
- package/dist/remote/ui.d.ts +1 -0
- package/dist/remote/ui.js +660 -0
- package/dist/task/auto-orchestrator.js +11 -4
- package/dist/task/orchestrator.js +10 -4
- package/dist/task/phases.js +9 -2
- package/dist/task/widget.js +15 -2
- package/package.json +16 -10
|
@@ -15,6 +15,7 @@ import { allocateAutoId, buildAutoBody, parseDecomposeList, parseTaskList, check
|
|
|
15
15
|
import { writeTaskFile, readTaskFile, updateTaskFrontMatter } from './task-io.js';
|
|
16
16
|
import { gitCommitAll } from './auto-commit.js';
|
|
17
17
|
import { runPhaseChild, USER_CANCELLED } from './child-runner.js';
|
|
18
|
+
import { SessionUI, registerBridgeCommand } from '../remote/bridge.js';
|
|
18
19
|
import { startAutoLoader } from './widget.js';
|
|
19
20
|
// Matches pi's @-file completion token (a path after @, until whitespace).
|
|
20
21
|
const MENTION_RE = /(?:^|\s)@([^\s]+)/g;
|
|
@@ -55,6 +56,7 @@ export async function planAuto(ctx, cwd, feature, deps) {
|
|
|
55
56
|
// with the model's recommended default pre-filled (Enter to accept, type to
|
|
56
57
|
// override); we never auto-answer. The model emits NONE when nothing remains.
|
|
57
58
|
const theme = ctx.ui.theme;
|
|
59
|
+
const ui = new SessionUI(ctx);
|
|
58
60
|
// Inline any @file spec the user referenced so clarify/decompose reason over
|
|
59
61
|
// the real content, not a one-line "Implement @file" that reads as trivial.
|
|
60
62
|
const featureForModel = await expandFeatureMentions(cwd, feature);
|
|
@@ -74,7 +76,12 @@ export async function planAuto(ctx, cwd, feature, deps) {
|
|
|
74
76
|
const title = suggested ?
|
|
75
77
|
`${shownQ}\n${theme.fg('muted', 'Recommended:')}\n\n${renderInlineMarkdown(suggested, theme)}\n\n${theme.fg('muted', 'press Enter to accept')}`
|
|
76
78
|
: `${shownQ}\n${theme.fg('muted', '(no recommendation — please answer)')}`;
|
|
77
|
-
const a = await
|
|
79
|
+
const a = await ui.ask({
|
|
80
|
+
localTitle: title,
|
|
81
|
+
question: plainQ,
|
|
82
|
+
recommended: plainSuggested,
|
|
83
|
+
allowSkip: plainSuggested === undefined
|
|
84
|
+
});
|
|
78
85
|
if (a === undefined) {
|
|
79
86
|
ctx.ui.notify('/task-auto cancelled.', 'warning');
|
|
80
87
|
return null;
|
|
@@ -303,15 +310,15 @@ async function handleTaskAutoCancel(_args, ctx) {
|
|
|
303
310
|
}
|
|
304
311
|
// ─── Registration ────────────────────────────────────────────────────────────
|
|
305
312
|
export function registerTaskAuto(pi) {
|
|
306
|
-
pi
|
|
313
|
+
registerBridgeCommand(pi, 'task-auto', {
|
|
307
314
|
description: 'Plan a feature into tasks and run them. Usage: /task-auto <feature>',
|
|
308
315
|
handler: handleTaskAuto
|
|
309
316
|
});
|
|
310
|
-
pi
|
|
317
|
+
registerBridgeCommand(pi, 'task-auto-resume', {
|
|
311
318
|
description: 'Resume the active /task-auto run.',
|
|
312
319
|
handler: handleTaskAutoResume
|
|
313
320
|
});
|
|
314
|
-
pi
|
|
321
|
+
registerBridgeCommand(pi, 'task-auto-cancel', {
|
|
315
322
|
description: 'Stop the running /task-auto loop after the current task.',
|
|
316
323
|
handler: handleTaskAutoCancel
|
|
317
324
|
});
|
|
@@ -21,6 +21,7 @@ import { PHASES, postCommitPhase } from './phases.js';
|
|
|
21
21
|
import { handleFailure } from './failure-classifier.js';
|
|
22
22
|
import { PHASE_INDEX, PHASE_ORDER, allocateTaskId, ensureTasksDir, normaliseTaskId, parseFrontMatter, readSection, readTaskFile, setTaskSection, taskFilePath, tasksDir, updateTaskFrontMatter, writeTaskFile, extractSection, RESUMABLE_STATES } from './task-file.js';
|
|
23
23
|
import { startWidget, WIDGET_KEY } from './widget.js';
|
|
24
|
+
import { publishViewer, publishNotify, registerBridgeCommand } from '../remote/bridge.js';
|
|
24
25
|
import { parseVerifyBlock } from './parsers.js';
|
|
25
26
|
import { formatTimings } from './timings.js';
|
|
26
27
|
// ─── Module-level state ──────────────────────────────────────────────────────
|
|
@@ -341,6 +342,7 @@ async function handleTaskList(_args, ctx) {
|
|
|
341
342
|
if (lines.length === 0)
|
|
342
343
|
lines.push('(no tasks in .pi-tasks/)');
|
|
343
344
|
lines.push('', 'resume: /task-resume <id> (eligible: in_progress, pending, cancelled, failed)');
|
|
345
|
+
publishViewer('Tasks', lines.join('\n'));
|
|
344
346
|
await ctx.ui.editor('Tasks', lines.join('\n'));
|
|
345
347
|
}
|
|
346
348
|
async function handleTaskResume(args, ctx) {
|
|
@@ -354,6 +356,7 @@ async function handleTaskResume(args, ctx) {
|
|
|
354
356
|
}
|
|
355
357
|
catch {
|
|
356
358
|
ctx.ui.notify(`${id} not found in .pi-tasks/`, 'error');
|
|
359
|
+
publishNotify(`${id} not found in .pi-tasks/`, 'error');
|
|
357
360
|
return;
|
|
358
361
|
}
|
|
359
362
|
}
|
|
@@ -382,6 +385,7 @@ async function handleTaskResume(args, ctx) {
|
|
|
382
385
|
candidates.sort((a, b) => b.mtime - a.mtime);
|
|
383
386
|
if (candidates.length === 0) {
|
|
384
387
|
ctx.ui.notify('No resumable tasks.', 'info');
|
|
388
|
+
publishNotify('No resumable tasks.', 'info');
|
|
385
389
|
return;
|
|
386
390
|
}
|
|
387
391
|
id = candidates[0].id;
|
|
@@ -393,27 +397,29 @@ async function handleTaskResume(args, ctx) {
|
|
|
393
397
|
async function handleTaskCancel(_args, ctx) {
|
|
394
398
|
if (!activeTask) {
|
|
395
399
|
ctx.ui.notify('No task is running.', 'info');
|
|
400
|
+
publishNotify('No task is running.', 'info');
|
|
396
401
|
return;
|
|
397
402
|
}
|
|
398
403
|
activeTask.cancel();
|
|
399
404
|
ctx.ui.notify(`Cancelling ${activeTask.taskId}…`, 'warning');
|
|
405
|
+
publishNotify(`Cancelling ${activeTask.taskId}…`, 'warning');
|
|
400
406
|
}
|
|
401
407
|
// ─── Entry point ─────────────────────────────────────────────────────────────
|
|
402
408
|
export function registerTask(pi) {
|
|
403
409
|
piApi = pi;
|
|
404
|
-
pi
|
|
410
|
+
registerBridgeCommand(pi, 'task', {
|
|
405
411
|
description: 'Start a new task. Usage: /task <prompt>',
|
|
406
412
|
handler: handleTask
|
|
407
413
|
});
|
|
408
|
-
pi
|
|
414
|
+
registerBridgeCommand(pi, 'task-list', {
|
|
409
415
|
description: 'List tasks in this project.',
|
|
410
416
|
handler: handleTaskList
|
|
411
417
|
});
|
|
412
|
-
pi
|
|
418
|
+
registerBridgeCommand(pi, 'task-resume', {
|
|
413
419
|
description: 'Resume a task. Usage: /task-resume [id]',
|
|
414
420
|
handler: handleTaskResume
|
|
415
421
|
});
|
|
416
|
-
pi
|
|
422
|
+
registerBridgeCommand(pi, 'task-cancel', {
|
|
417
423
|
description: 'Cancel the currently running task.',
|
|
418
424
|
handler: handleTaskCancel
|
|
419
425
|
});
|
package/dist/task/phases.js
CHANGED
|
@@ -15,6 +15,7 @@ import { setTaskSection, updateTaskFrontMatter } from './task-file.js';
|
|
|
15
15
|
import { renderInlineMarkdown, stripInlineMarkdown } from './inline-markdown.js';
|
|
16
16
|
import { parseVerifyBlock, parseGrillQuestions, parseAutoAnswer, parseVerifyToolingOutput, validateSpecShape, stripSpecPreamble, deriveTitle, isCritiqueClean } from './parsers.js';
|
|
17
17
|
import { runPhaseChild, runPhaseWithLoopGuard, runWithEmphasisRetry, prependHint, USER_CANCELLED } from './child-runner.js';
|
|
18
|
+
import { SessionUI } from '../remote/bridge.js';
|
|
18
19
|
// ─── Re-export constants from their home modules ────────────────────────────
|
|
19
20
|
export { MAX_GRILL_QUESTIONS };
|
|
20
21
|
// ─── Tooling helpers ─────────────────────────────────────────────────────────
|
|
@@ -332,6 +333,7 @@ export async function phaseGrill(deps, ctx, widgetState, refined, research) {
|
|
|
332
333
|
// or surfaced as a pre-filled recommendation. The model emits NONE when
|
|
333
334
|
// nothing ambiguous remains. Kept in sync with /task-auto's clarify dialog.
|
|
334
335
|
const theme = ctx.ui.theme;
|
|
336
|
+
const ui = new SessionUI(ctx);
|
|
335
337
|
const out = []; // human-facing Q&A transcript (with auto-worker debug lines)
|
|
336
338
|
const qa = []; // compact Q&A fed back into the next question
|
|
337
339
|
// Open-ended: keep asking until the model emits NONE or the user dismisses.
|
|
@@ -361,11 +363,16 @@ export async function phaseGrill(deps, ctx, widgetState, refined, research) {
|
|
|
361
363
|
}
|
|
362
364
|
else {
|
|
363
365
|
const plainSuggested = auto.suggested === undefined ? undefined : stripInlineMarkdown(auto.suggested);
|
|
364
|
-
const
|
|
366
|
+
const localTitle = auto.suggested ?
|
|
365
367
|
`${shownQ}\n${theme.fg('muted', 'Recommended:')}\n\n${renderInlineMarkdown(auto.suggested, theme)}\n\n${theme.fg('muted', 'press Enter to accept')}`
|
|
366
368
|
: `${shownQ}\n${theme.fg('muted', '(no recommendation — please answer)')}`;
|
|
367
369
|
widgetState.lastLine = `awaiting Q${n + 1}`;
|
|
368
|
-
const a = await
|
|
370
|
+
const a = await ui.ask({
|
|
371
|
+
localTitle,
|
|
372
|
+
question: plainQ,
|
|
373
|
+
recommended: plainSuggested,
|
|
374
|
+
allowSkip: plainSuggested === undefined
|
|
375
|
+
});
|
|
369
376
|
if (a === undefined)
|
|
370
377
|
throw new Error(USER_CANCELLED);
|
|
371
378
|
const typed = a.trim();
|
package/dist/task/widget.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* context usage, and the latest child-process line.
|
|
6
6
|
*/
|
|
7
7
|
import { PHASE_INDEX, PHASE_ORDER } from './task-file.js';
|
|
8
|
+
import { publishWidget } from '../remote/bridge.js';
|
|
8
9
|
// ─── Constants ───────────────────────────────────────────────────────────────
|
|
9
10
|
export const WIDGET_KEY = 'pi-tasks';
|
|
10
11
|
export const AUTO_WIDGET_KEY = 'pi-task-auto';
|
|
@@ -92,12 +93,15 @@ export function startWidget(ctx, getState) {
|
|
|
92
93
|
return () => { };
|
|
93
94
|
const render = () => {
|
|
94
95
|
const s = getState();
|
|
96
|
+
const lines = s ? buildWidgetLines(s, ctx.ui.theme) : undefined;
|
|
97
|
+
const plain = s ? buildWidgetLines(s, undefined) : undefined; // un-themed for the wire
|
|
95
98
|
try {
|
|
96
|
-
ctx.ui.setWidget(WIDGET_KEY,
|
|
99
|
+
ctx.ui.setWidget(WIDGET_KEY, lines);
|
|
97
100
|
}
|
|
98
101
|
catch {
|
|
99
102
|
/* stale ctx */
|
|
100
103
|
}
|
|
104
|
+
publishWidget(WIDGET_KEY, plain);
|
|
101
105
|
};
|
|
102
106
|
render();
|
|
103
107
|
const timer = setInterval(render, WIDGET_REFRESH_MS);
|
|
@@ -129,12 +133,15 @@ export function startAutoLoader(ctx, getState) {
|
|
|
129
133
|
return () => { };
|
|
130
134
|
const render = () => {
|
|
131
135
|
const s = getState();
|
|
136
|
+
const lines = s ? buildAutoLoaderLines(s, ctx.ui.theme) : undefined;
|
|
137
|
+
const plain = s ? buildAutoLoaderLines(s, undefined) : undefined;
|
|
132
138
|
try {
|
|
133
|
-
ctx.ui.setWidget(AUTO_WIDGET_KEY,
|
|
139
|
+
ctx.ui.setWidget(AUTO_WIDGET_KEY, lines);
|
|
134
140
|
}
|
|
135
141
|
catch {
|
|
136
142
|
/* stale ctx */
|
|
137
143
|
}
|
|
144
|
+
publishWidget(AUTO_WIDGET_KEY, plain);
|
|
138
145
|
};
|
|
139
146
|
render();
|
|
140
147
|
const timer = setInterval(render, WIDGET_REFRESH_MS);
|
|
@@ -147,6 +154,7 @@ export function startAutoLoader(ctx, getState) {
|
|
|
147
154
|
catch {
|
|
148
155
|
/* stale ctx */
|
|
149
156
|
}
|
|
157
|
+
publishWidget(AUTO_WIDGET_KEY, undefined);
|
|
150
158
|
};
|
|
151
159
|
}
|
|
152
160
|
export function flashTerminalWidget(ctx, state, taskId, reason) {
|
|
@@ -163,8 +171,12 @@ export function flashTerminalWidget(ctx, state, taskId, reason) {
|
|
|
163
171
|
line = theme.fg('error', `✘ ${taskId} failed${reason ? ': ' + reason : ''}`);
|
|
164
172
|
clearMs = FAIL_CLEAR_MS;
|
|
165
173
|
}
|
|
174
|
+
const plainLine = state === 'cancelled' ?
|
|
175
|
+
`⚠ ${taskId} cancelled`
|
|
176
|
+
: `✘ ${taskId} failed${reason ? ': ' + reason : ''}`;
|
|
166
177
|
try {
|
|
167
178
|
ctx.ui.setWidget(WIDGET_KEY, [line]);
|
|
179
|
+
publishWidget(WIDGET_KEY, [plainLine]);
|
|
168
180
|
}
|
|
169
181
|
catch {
|
|
170
182
|
/* stale ctx */
|
|
@@ -172,6 +184,7 @@ export function flashTerminalWidget(ctx, state, taskId, reason) {
|
|
|
172
184
|
setTimeout(() => {
|
|
173
185
|
try {
|
|
174
186
|
ctx.ui.setWidget(WIDGET_KEY, undefined);
|
|
187
|
+
publishWidget(WIDGET_KEY, undefined);
|
|
175
188
|
}
|
|
176
189
|
catch {
|
|
177
190
|
/* stale ctx */
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mjasnikovs/pi-task",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Deterministic spec-orchestration for local models, with bundled web/docs/fetch/worker subagent tools.",
|
|
3
|
+
"version": "0.4.0",
|
|
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",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
@@ -17,20 +17,24 @@
|
|
|
17
17
|
"prepublishOnly": "bun run build"
|
|
18
18
|
},
|
|
19
19
|
"peerDependencies": {
|
|
20
|
-
"@earendil-works/pi-coding-agent": "0.
|
|
21
|
-
"@earendil-works/pi-agent-core": "0.
|
|
22
|
-
"@earendil-works/pi-tui": "0.
|
|
20
|
+
"@earendil-works/pi-coding-agent": "0.78.1",
|
|
21
|
+
"@earendil-works/pi-agent-core": "0.78.1",
|
|
22
|
+
"@earendil-works/pi-tui": "0.78.1"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
25
|
"@mozilla/readability": "^0.6.0",
|
|
26
26
|
"@sinclair/typebox": "0.34.49",
|
|
27
27
|
"jsdom": "^29.1.1",
|
|
28
|
-
"
|
|
28
|
+
"qrcode": "^1.5.4",
|
|
29
|
+
"turndown": "^7.2.4",
|
|
30
|
+
"ws": "^8.18.0"
|
|
29
31
|
},
|
|
30
32
|
"devDependencies": {
|
|
31
|
-
"@earendil-works/pi-coding-agent": "0.
|
|
32
|
-
"@earendil-works/pi-agent-core": "0.
|
|
33
|
-
"@earendil-works/pi-tui": "0.
|
|
33
|
+
"@earendil-works/pi-coding-agent": "0.78.1",
|
|
34
|
+
"@earendil-works/pi-agent-core": "0.78.1",
|
|
35
|
+
"@earendil-works/pi-tui": "0.78.1",
|
|
36
|
+
"@types/qrcode": "^1.5.5",
|
|
37
|
+
"@types/ws": "^8.5.14",
|
|
34
38
|
"@eslint/js": "10.0.1",
|
|
35
39
|
"@sinclair/typebox": "0.34.49",
|
|
36
40
|
"@types/bun": "1.3.12",
|
|
@@ -48,7 +52,9 @@
|
|
|
48
52
|
"pi-extension",
|
|
49
53
|
"coding-agent",
|
|
50
54
|
"task-orchestration",
|
|
51
|
-
"local-llm"
|
|
55
|
+
"local-llm",
|
|
56
|
+
"remote",
|
|
57
|
+
"web"
|
|
52
58
|
],
|
|
53
59
|
"repository": {
|
|
54
60
|
"type": "git",
|