@mjasnikovs/pi-task 0.13.13 → 0.13.14
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/events.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { setAgentIdle } from './state.js';
|
|
2
|
-
import { pushNotify } from './push.js';
|
|
3
2
|
import { publishNotify } from './bridge.js';
|
|
4
3
|
import { agentStart, appendText, textEnd, startTool, updateTool, endTool, agentEnd, addUserTurn, addError, addSystemNote } from './session-state.js';
|
|
5
4
|
/** Mirror pi agent events into the authoritative SessionState. Each handler
|
|
@@ -22,10 +21,11 @@ export function setupEvents(pi) {
|
|
|
22
21
|
if (errorMessage || ae.reason === 'error') {
|
|
23
22
|
const message = errorMessage || 'Request failed';
|
|
24
23
|
addError(message);
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
|
|
24
|
+
// No push here: pi-task only notifies for the two cases the user
|
|
25
|
+
// cares about — needing their input (the grill/clarify dialog, see
|
|
26
|
+
// bridge.ask) and a top-level /task or /task-auto run finishing
|
|
27
|
+
// (orchestrator/auto-orchestrator). A push on every host agent
|
|
28
|
+
// error — most of them outside any task — is just noise.
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
31
|
});
|
|
@@ -44,7 +44,12 @@ export function setupEvents(pi) {
|
|
|
44
44
|
pi.on('agent_end', (_event, ctx) => {
|
|
45
45
|
setAgentIdle(true);
|
|
46
46
|
agentEnd(ctx.getContextUsage());
|
|
47
|
-
|
|
47
|
+
// Deliberately no push: agent_end fires on EVERY host turn — every chat
|
|
48
|
+
// reply, every internal phase turn inside /task, and every internal /task
|
|
49
|
+
// run inside /task-auto — so a "Task finished" push here floods the device.
|
|
50
|
+
// The real "a run finished" push is fired from the top-level command
|
|
51
|
+
// handlers instead (orchestrator handleTask/handleTaskResume, and
|
|
52
|
+
// auto-orchestrator's runAutoLoop), which never fire for internal runs.
|
|
48
53
|
});
|
|
49
54
|
pi.on('input', (event, _ctx) => {
|
|
50
55
|
if (event.source === 'interactive' && typeof event.text === 'string') {
|
|
@@ -16,6 +16,7 @@ import { writeTaskFile, readTaskFile, updateTaskFrontMatter, taskFilePath } from
|
|
|
16
16
|
import { gitCommitAll } from './auto-commit.js';
|
|
17
17
|
import { runPhaseChild, USER_CANCELLED } from './child-runner.js';
|
|
18
18
|
import { SessionUI, registerBridgeCommand } from '../remote/bridge.js';
|
|
19
|
+
import { pushNotify } from '../remote/push.js';
|
|
19
20
|
import { getConfig } from '../config/config.js';
|
|
20
21
|
import { startAutoLoader } from './widget.js';
|
|
21
22
|
import { getParentContextWindow, resolveContextUsage } from './context-usage.js';
|
|
@@ -106,7 +107,7 @@ export async function planAuto(ctx, cwd, feature, deps) {
|
|
|
106
107
|
})
|
|
107
108
|
});
|
|
108
109
|
if (a === undefined) {
|
|
109
|
-
ctx
|
|
110
|
+
announceDone(ctx, '/task-auto cancelled.', 'warning');
|
|
110
111
|
return null;
|
|
111
112
|
}
|
|
112
113
|
const typed = a.trim();
|
|
@@ -139,7 +140,7 @@ export async function planAuto(ctx, cwd, feature, deps) {
|
|
|
139
140
|
const listRaw = await deps.runChild('auto-decompose', 'read', AUTO_DECOMPOSE_PROMPT(featureForModel, clarifications));
|
|
140
141
|
const titles = parseDecomposeList(listRaw);
|
|
141
142
|
if (titles.length === 0) {
|
|
142
|
-
ctx
|
|
143
|
+
announceDone(ctx, '/task-auto: no tasks produced from the feature.', 'warning');
|
|
143
144
|
return null;
|
|
144
145
|
}
|
|
145
146
|
// persist
|
|
@@ -220,6 +221,17 @@ let autoRunning = false;
|
|
|
220
221
|
export function requestAutoCancel() {
|
|
221
222
|
cancelRequested = true;
|
|
222
223
|
}
|
|
224
|
+
/**
|
|
225
|
+
* Announce a terminal /task-auto-overall outcome both in the terminal and to
|
|
226
|
+
* subscribed devices. The push body reuses the exact terminal message, so a
|
|
227
|
+
* backgrounded phone learns the same thing the TUI shows. Used ONLY at the
|
|
228
|
+
* overall run's terminal points — never per internal task (those go through
|
|
229
|
+
* runSingleTask without notifyFinish, so they stay silent).
|
|
230
|
+
*/
|
|
231
|
+
function announceDone(ctx, msg, level) {
|
|
232
|
+
ctx.ui.notify(msg, level);
|
|
233
|
+
void pushNotify('Task finished', msg, 'pi-end').catch(() => { });
|
|
234
|
+
}
|
|
223
235
|
export async function runAutoLoop(ctx, cwd, id, deps) {
|
|
224
236
|
cancelRequested = false;
|
|
225
237
|
// Each task runs in its own fresh session (deps.runTask → ctx.newSession),
|
|
@@ -230,7 +242,7 @@ export async function runAutoLoop(ctx, cwd, id, deps) {
|
|
|
230
242
|
try {
|
|
231
243
|
for (;;) {
|
|
232
244
|
if (cancelRequested) {
|
|
233
|
-
active
|
|
245
|
+
announceDone(active, `${id} cancelled — resume with /task-auto-resume.`, 'warning');
|
|
234
246
|
return;
|
|
235
247
|
}
|
|
236
248
|
const { body } = await readTaskFile(cwd, id);
|
|
@@ -238,7 +250,7 @@ export async function runAutoLoop(ctx, cwd, id, deps) {
|
|
|
238
250
|
const next = entries.find(e => !e.done);
|
|
239
251
|
if (!next) {
|
|
240
252
|
await updateTaskFrontMatter(cwd, id, { state: 'completed' });
|
|
241
|
-
active
|
|
253
|
+
announceDone(active, `${id} complete — all ${entries.length} tasks done.`, 'info');
|
|
242
254
|
return;
|
|
243
255
|
}
|
|
244
256
|
active.ui.notify(`${id}: task ${next.index + 1}/${entries.length} — ${next.title}`, 'info');
|
|
@@ -277,7 +289,7 @@ export async function runAutoLoop(ctx, cwd, id, deps) {
|
|
|
277
289
|
});
|
|
278
290
|
active = res.ctx ?? active;
|
|
279
291
|
if (res.sessionCancelled) {
|
|
280
|
-
active
|
|
292
|
+
announceDone(active, `${id} paused — could not start a session. Run /task-auto-resume to retry.`, 'warning');
|
|
281
293
|
return;
|
|
282
294
|
}
|
|
283
295
|
if (res.interrupted) {
|
|
@@ -287,12 +299,12 @@ export async function runAutoLoop(ctx, cwd, id, deps) {
|
|
|
287
299
|
// this task's spec to finish it. (A plain ESC that the user
|
|
288
300
|
// follows with steering text never reaches here — that loops on
|
|
289
301
|
// the same task inside runSingleTask until a turn completes.)
|
|
290
|
-
active
|
|
302
|
+
announceDone(active, `${id} paused at "${next.title}" — resume with /task-auto-resume.`, 'warning');
|
|
291
303
|
return;
|
|
292
304
|
}
|
|
293
305
|
if (!res.ok) {
|
|
294
306
|
await updateTaskFrontMatter(cwd, id, { state: 'failed' });
|
|
295
|
-
active
|
|
307
|
+
announceDone(active, `${id} stopped at "${next.title}" — fix and run /task-auto-resume.`, 'error');
|
|
296
308
|
return;
|
|
297
309
|
}
|
|
298
310
|
// res.ok === true means runner.run() completed, so res.taskId is the
|
|
@@ -319,11 +331,11 @@ export async function runAutoLoop(ctx, cwd, id, deps) {
|
|
|
319
331
|
// mirroring the in-loop per-task failure path.
|
|
320
332
|
const msg = err instanceof Error ? err.message : String(err);
|
|
321
333
|
if (msg === USER_CANCELLED) {
|
|
322
|
-
active
|
|
334
|
+
announceDone(active, `${id} cancelled — resume with /task-auto-resume.`, 'warning');
|
|
323
335
|
return;
|
|
324
336
|
}
|
|
325
337
|
await updateTaskFrontMatter(cwd, id, { state: 'failed' }).catch(() => { });
|
|
326
|
-
active
|
|
338
|
+
announceDone(active, `${id} stopped: ${msg} — fix and run /task-auto-resume.`, 'error');
|
|
327
339
|
}
|
|
328
340
|
finally {
|
|
329
341
|
cancelRequested = false;
|
|
@@ -350,10 +362,10 @@ async function handleTaskAuto(args, ctx) {
|
|
|
350
362
|
autoRunning = false;
|
|
351
363
|
const msg = err instanceof Error ? err.message : String(err);
|
|
352
364
|
if (msg === USER_CANCELLED) {
|
|
353
|
-
ctx
|
|
365
|
+
announceDone(ctx, '/task-auto cancelled.', 'warning');
|
|
354
366
|
return;
|
|
355
367
|
}
|
|
356
|
-
ctx
|
|
368
|
+
announceDone(ctx, `/task-auto planning failed: ${msg}`, 'error');
|
|
357
369
|
return;
|
|
358
370
|
}
|
|
359
371
|
if (!id) {
|
|
@@ -365,7 +377,7 @@ async function handleTaskAuto(args, ctx) {
|
|
|
365
377
|
if (cancelRequested) {
|
|
366
378
|
cancelRequested = false;
|
|
367
379
|
autoRunning = false;
|
|
368
|
-
ctx
|
|
380
|
+
announceDone(ctx, '/task-auto cancelled.', 'warning');
|
|
369
381
|
return;
|
|
370
382
|
}
|
|
371
383
|
await runAutoLoop(ctx, cwd, id, deps);
|
|
@@ -77,6 +77,13 @@ export interface RunSingleTaskOptions {
|
|
|
77
77
|
* steer loop is testable without a real dialog.
|
|
78
78
|
*/
|
|
79
79
|
promptSteer?: (ctx: ExtensionCommandContext) => Promise<string | undefined>;
|
|
80
|
+
/**
|
|
81
|
+
* Push a "Task finished" notification to subscribed devices when this run
|
|
82
|
+
* reaches a terminal state (completed / failed / cancelled). Set only by the
|
|
83
|
+
* top-level /task and /task-resume command handlers — NOT by /task-auto's
|
|
84
|
+
* internal per-task runs, which must stay silent. Default false.
|
|
85
|
+
*/
|
|
86
|
+
notifyFinish?: boolean;
|
|
80
87
|
}
|
|
81
88
|
export interface RunSingleTaskResult {
|
|
82
89
|
taskId: string;
|
|
@@ -24,6 +24,7 @@ import { normaliseTaskId, parseFrontMatter, extractSection } from './task-parser
|
|
|
24
24
|
import { allocateTaskId, ensureTasksDir, readSection, readTaskFile, setTaskSection, taskFilePath, tasksDir, updateTaskFrontMatter, writeTaskFile } from './task-io.js';
|
|
25
25
|
import { startWidget } from './widget.js';
|
|
26
26
|
import { publishViewer, publishNotify, registerBridgeCommand, getBridge } from '../remote/bridge.js';
|
|
27
|
+
import { pushNotify } from '../remote/push.js';
|
|
27
28
|
import { parseVerifyBlock } from './spec-validation.js';
|
|
28
29
|
import { formatTimings } from './timings.js';
|
|
29
30
|
import { getParentContextWindow, resolveContextUsage } from './context-usage.js';
|
|
@@ -342,18 +343,29 @@ export async function runSingleTask(ctx, cwd, rawPrompt, opts = {}) {
|
|
|
342
343
|
});
|
|
343
344
|
if (result.cancelled) {
|
|
344
345
|
// No replacement happened — the original ctx is still live.
|
|
346
|
+
if (opts.notifyFinish) {
|
|
347
|
+
void pushNotify('Task finished', `${taskId || 'Task'} cancelled — could not start a session.`, 'pi-end').catch(() => { });
|
|
348
|
+
}
|
|
345
349
|
return { taskId, ok: false, sessionCancelled: true, ctx };
|
|
346
350
|
}
|
|
347
351
|
let ok = false;
|
|
352
|
+
let state;
|
|
348
353
|
if (taskId) {
|
|
349
354
|
try {
|
|
350
355
|
const { frontMatter } = await readTaskFile(cwd, taskId);
|
|
351
|
-
|
|
356
|
+
state = frontMatter.state;
|
|
357
|
+
ok = state === 'completed';
|
|
352
358
|
}
|
|
353
359
|
catch {
|
|
354
360
|
ok = false;
|
|
355
361
|
}
|
|
356
362
|
}
|
|
363
|
+
if (opts.notifyFinish) {
|
|
364
|
+
// One push per top-level /task or /task-resume, on any terminal end. The
|
|
365
|
+
// file state is the source of truth: 'completed' on success, 'failed' /
|
|
366
|
+
// 'cancelled' otherwise; an unreadable/absent file falls back to 'ended'.
|
|
367
|
+
void pushNotify('Task finished', `${taskId || 'Task'} ${state ?? 'ended'}.`, 'pi-end').catch(() => { });
|
|
368
|
+
}
|
|
357
369
|
return { taskId, ok, sessionCancelled: false, ctx: freshCtx, interrupted };
|
|
358
370
|
}
|
|
359
371
|
// ─── Command handlers ────────────────────────────────────────────────────────
|
|
@@ -366,7 +378,7 @@ async function handleTask(args, ctx) {
|
|
|
366
378
|
ctx.ui.notify('Type your prompt after /task (use @ for file completion).', 'info');
|
|
367
379
|
return;
|
|
368
380
|
}
|
|
369
|
-
const { sessionCancelled } = await runSingleTask(ctx, cwd, raw);
|
|
381
|
+
const { sessionCancelled } = await runSingleTask(ctx, cwd, raw, { notifyFinish: true });
|
|
370
382
|
if (sessionCancelled) {
|
|
371
383
|
ctx.ui.notify('Could not start a fresh session for /task.', 'warning');
|
|
372
384
|
}
|
|
@@ -449,7 +461,7 @@ async function handleTaskResume(args, ctx) {
|
|
|
449
461
|
}
|
|
450
462
|
id = candidates[0].id;
|
|
451
463
|
}
|
|
452
|
-
const { sessionCancelled } = await runSingleTask(ctx, cwd, '', { resumeId: id });
|
|
464
|
+
const { sessionCancelled } = await runSingleTask(ctx, cwd, '', { resumeId: id, notifyFinish: true });
|
|
453
465
|
if (sessionCancelled) {
|
|
454
466
|
ctx.ui.notify('Could not start a fresh session for /task-resume.', 'warning');
|
|
455
467
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mjasnikovs/pi-task",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.14",
|
|
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",
|