@mjasnikovs/pi-task 0.13.25 → 0.13.27
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 +16 -4
- package/assets/pipeline.png +0 -0
- package/dist/task/phases.d.ts +8 -0
- package/dist/task/phases.js +64 -21
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
[](https://www.npmjs.com/package/@mjasnikovs/pi-task)
|
|
10
10
|
[](./LICENSE)
|
|
11
11
|
[](https://www.npmjs.com/package/@earendil-works/pi-coding-agent)
|
|
12
|
-
[](#development)
|
|
13
13
|
[](./tsconfig.json)
|
|
14
14
|
|
|
15
15
|
</div>
|
|
@@ -49,7 +49,8 @@ pi install npm:@mjasnikovs/pi-task
|
|
|
49
49
|
| `/task-auto <feature>` | Plan a feature into a task list and run each title through `/task` in order (resumable). |
|
|
50
50
|
| `/task-auto-resume` | Resume the active `/task-auto` run at the next unfinished task. |
|
|
51
51
|
| `/task-auto-cancel` | Stop the `/task-auto` loop after the current task (still resumable). |
|
|
52
|
-
| `/
|
|
52
|
+
| `/task-config` | Toggle pi-task settings in an editor dialog: remote server, compress reasoning, and auto-commit. |
|
|
53
|
+
| `/remote` | Show the QR code & URLs for the web view (`/remote stop` to stop). Answer grill questions, start tasks, and watch progress from your phone. |
|
|
53
54
|
|
|
54
55
|
## The pipeline
|
|
55
56
|
|
|
@@ -77,10 +78,11 @@ A real feature is usually several tasks, not one. `/task-auto` is a thin planner
|
|
|
77
78
|
- **Clarify first.** It asks the few clarifying questions whose answers change how the feature splits, then decomposes the answers into an ordered list of task titles written to `.pi-tasks/TASK_AUTO_NNNN.md`.
|
|
78
79
|
- **Sequential, blocking.** Each title runs through `/task` to a spec, the spec is implemented, and the loop waits for that to finish before starting the next title. No overlap.
|
|
79
80
|
- **Crash- and cancel-safe.** Progress is the markdown checkboxes in the AUTO file. `/task-auto-resume` (no id) automatically picks up the active run at the first unchecked title. If a title's `/task` run fails, the loop stops and leaves the run resumable.
|
|
81
|
+
- **One commit per task.** When **auto-commit** is on (the default) and you're in a git repo, the working tree is snapshotted into a single commit after each title passes, so the run produces a clean per-task history. It's best-effort: outside a repo, with nothing to commit, or on any git error, the loop reports the reason and keeps going. Toggle it in `/task-config`.
|
|
80
82
|
|
|
81
83
|
## Remote — drive a task from your phone
|
|
82
84
|
|
|
83
|
-
The remote server is **
|
|
85
|
+
The remote server is **on by default** — it starts automatically with each session, with nothing taking up screen space (disable it in `/task-config`). Run `/remote` any time to pop a QR code and the connection URLs: a **Tailscale** line and a **LAN** line when both are available (the QR encodes the Tailscale-preferred one). Open the URL on any device that can reach the host and you get a live view of the session: streaming output, tool calls, and the `/task` status block (phase, elapsed, context). It's bidirectional — the browser can:
|
|
84
86
|
|
|
85
87
|
- **Answer grill / `/task-auto` clarify questions.** Each question appears as a card with the recommended default pre-filled (Accept), a free-text box (Submit), Skip, or Cancel task.
|
|
86
88
|
- **Start and control tasks.** Type `/task …`, `/task-auto …`, `/task-cancel`, `/task-resume`, etc. — they run on the host.
|
|
@@ -136,6 +138,16 @@ Resolves an installed npm package, indexes its `.d.ts` files and README into a l
|
|
|
136
138
|
- The first call for a `(package, version)` pair pays a one-time ingestion cost; later calls are FTS-only.
|
|
137
139
|
- Cache lives at `${XDG_CACHE_HOME:-~/.cache}/pi-worker/docs.sqlite` — delete it to reset.
|
|
138
140
|
|
|
141
|
+
## Settings — `/task-config`
|
|
142
|
+
|
|
143
|
+
Run `/task-config` to toggle pi-task's behavior in an editor dialog. Settings persist to `~/.config/pi-task/config.json` and all default to **on**:
|
|
144
|
+
|
|
145
|
+
| Setting | What it does |
|
|
146
|
+
| --- | --- |
|
|
147
|
+
| **remote** | The remote UI server (QR code, phone access). Turn off to never start it. |
|
|
148
|
+
| **compress reasoning** | After each message, compresses the model's `<think>` blocks down to the decisions/constraints/facts that matter later — keeping long local-model runs from drowning their own context in self-talk. |
|
|
149
|
+
| **auto-commit** | Snapshots the working tree into one git commit per `/task-auto` sub-task (see above). |
|
|
150
|
+
|
|
139
151
|
## Configuration
|
|
140
152
|
|
|
141
153
|
| Variable | Used by | Notes |
|
|
@@ -153,7 +165,7 @@ Tasks are persisted to `<cwd>/.pi-tasks/TASK_NNNN.md`. Add `.pi-tasks/` to your
|
|
|
153
165
|
|
|
154
166
|
```sh
|
|
155
167
|
bun install
|
|
156
|
-
bun test src/ #
|
|
168
|
+
bun test src/ # 731 tests across 57 files
|
|
157
169
|
bun run lint # prettier + eslint + tsc --noEmit
|
|
158
170
|
bun run build # tsc → dist/
|
|
159
171
|
```
|
|
Binary file
|
package/dist/task/phases.d.ts
CHANGED
|
@@ -56,6 +56,14 @@ export interface PhaseResearchDeps extends ExternalContextDeps {
|
|
|
56
56
|
* mentions; the rest are unchanged or only lightly trimmed.
|
|
57
57
|
*/
|
|
58
58
|
export declare function scopedToolingGoal(refined: string): string;
|
|
59
|
+
/**
|
|
60
|
+
* Build a degraded section body for a runaway worker: a one-line marker naming
|
|
61
|
+
* the failure (so downstream phases and a human reading the task file know this
|
|
62
|
+
* section is incomplete) followed by whatever partial answer the worker streamed
|
|
63
|
+
* before it was killed. The marker is always present even when there is no
|
|
64
|
+
* partial text, so an empty degrade is never mistaken for a real finding.
|
|
65
|
+
*/
|
|
66
|
+
export declare function degradedSectionBody(name: string, reason: string, partial: string): string;
|
|
59
67
|
export declare function phaseResearch(deps: PhaseDeps, refined: string, researchDeps?: PhaseResearchDeps): Promise<string>;
|
|
60
68
|
export interface PhaseAutoAnswerDeps {
|
|
61
69
|
docsFocused?: typeof docsFocused;
|
package/dist/task/phases.js
CHANGED
|
@@ -122,34 +122,67 @@ export function scopedToolingGoal(refined) {
|
|
|
122
122
|
return firstBullet === -1 ? goal : goal.slice(0, firstBullet).trim();
|
|
123
123
|
}
|
|
124
124
|
/**
|
|
125
|
-
*
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
*
|
|
125
|
+
* Classify a research worker's result so the phase can react per-worker instead
|
|
126
|
+
* of treating every failure the same. Two distinct failure shapes:
|
|
127
|
+
*
|
|
128
|
+
* - 'runaway' (loop-kill OR per-worker wall-clock timeout): the worker explored
|
|
129
|
+
* too long and was killed *after* burning its MAX_LOOP_RESTARTS restarts. It
|
|
130
|
+
* did real work and left partial text; the other three workers are unaffected.
|
|
131
|
+
* Failing the whole task here would throw away every already-good worker AND
|
|
132
|
+
* abort the entire auto-run over the weakest section — and because the loop is
|
|
133
|
+
* deterministic, a resume just re-loops and re-fails. So this DEGRADES: keep
|
|
134
|
+
* the partial answer (marked), cache it, move on. A loop-kill is a SIGTERM
|
|
135
|
+
* (exit 143) OR a clean exit 0 with truncated text, so loopHit/timedOut — not
|
|
136
|
+
* exitCode — are the reliable signal and are checked first.
|
|
137
|
+
*
|
|
138
|
+
* - 'fatal' (non-zero exit that isn't a loop-kill, empty output, or a leaked
|
|
139
|
+
* never-executed tool call): the output is untrustworthy in a way partial text
|
|
140
|
+
* can't paper over (broken env, model disconnect, wrong tool-call dialect).
|
|
141
|
+
* These still throw — degrading them would launder a real breakage into a
|
|
142
|
+
* plausible-looking section. Returns null when the result is trustworthy.
|
|
132
143
|
*/
|
|
133
|
-
function
|
|
144
|
+
function classifyResearchWorker(name, result) {
|
|
134
145
|
if (result.loopHit) {
|
|
135
146
|
const argsStr = JSON.stringify(result.loopHit.call.args);
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
147
|
+
return {
|
|
148
|
+
kind: 'runaway',
|
|
149
|
+
reason: `stuck in a loop — called ${result.loopHit.call.name}(${argsStr}) `
|
|
150
|
+
+ `×${result.loopHit.count} in the last ${result.loopHit.windowSize} calls `
|
|
151
|
+
+ `and still looped after restarts`
|
|
152
|
+
};
|
|
139
153
|
}
|
|
140
154
|
if (result.timedOut) {
|
|
141
|
-
|
|
155
|
+
return { kind: 'runaway', reason: 'timed out after restarts' };
|
|
142
156
|
}
|
|
143
157
|
if (result.exitCode !== 0) {
|
|
144
|
-
|
|
158
|
+
return {
|
|
159
|
+
kind: 'fatal',
|
|
160
|
+
error: new Error(`Research ${name} worker failed (exit ${result.exitCode}): ${result.stderr.slice(-500)}`)
|
|
161
|
+
};
|
|
145
162
|
}
|
|
146
163
|
if (result.text.trim().length === 0) {
|
|
147
|
-
|
|
164
|
+
return { kind: 'fatal', error: new Error(`Research ${name} worker produced no output`) };
|
|
148
165
|
}
|
|
149
166
|
if (result.leakedToolCall) {
|
|
150
|
-
|
|
151
|
-
|
|
167
|
+
return {
|
|
168
|
+
kind: 'fatal',
|
|
169
|
+
error: new Error(`Research ${name} worker wrote a tool call as text instead of invoking it `
|
|
170
|
+
+ `(${result.leakedToolCall.trim()}) — it never ran`)
|
|
171
|
+
};
|
|
152
172
|
}
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Build a degraded section body for a runaway worker: a one-line marker naming
|
|
177
|
+
* the failure (so downstream phases and a human reading the task file know this
|
|
178
|
+
* section is incomplete) followed by whatever partial answer the worker streamed
|
|
179
|
+
* before it was killed. The marker is always present even when there is no
|
|
180
|
+
* partial text, so an empty degrade is never mistaken for a real finding.
|
|
181
|
+
*/
|
|
182
|
+
export function degradedSectionBody(name, reason, partial) {
|
|
183
|
+
const marker = `(degraded: research ${name} worker ${reason}; this section may be incomplete)`;
|
|
184
|
+
const body = partial.trim();
|
|
185
|
+
return body.length > 0 ? `${marker}\n\n${body}` : marker;
|
|
153
186
|
}
|
|
154
187
|
export async function phaseResearch(deps, refined, researchDeps = {}) {
|
|
155
188
|
const fileInventoryFn = researchDeps.getFileInventory ?? getFileInventory;
|
|
@@ -265,11 +298,21 @@ export async function phaseResearch(deps, refined, researchDeps = {}) {
|
|
|
265
298
|
+ (r.stderr ? ` stderr=${r.stderr.slice(0, 300)}` : '')
|
|
266
299
|
+ (r.leakedToolCall ? ` leaked=${r.leakedToolCall.trim().slice(0, 80)}` : ''));
|
|
267
300
|
updateProgress();
|
|
268
|
-
//
|
|
269
|
-
// workers
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
301
|
+
// A fatal failure (crash/empty/leak) still throws — the already-cached
|
|
302
|
+
// workers survive for the resume. A runaway (loop/timeout) degrades to its
|
|
303
|
+
// partial output instead, so one weak worker can't abort a whole auto-run;
|
|
304
|
+
// the degraded section is cached too, so a resume doesn't re-loop it.
|
|
305
|
+
const failure = classifyResearchWorker(spec.section, r);
|
|
306
|
+
if (failure?.kind === 'fatal')
|
|
307
|
+
throw failure.error;
|
|
308
|
+
const sectionText = failure?.kind === 'runaway' ?
|
|
309
|
+
degradedSectionBody(spec.section, failure.reason, r.text)
|
|
310
|
+
: r.text.trim();
|
|
311
|
+
if (failure?.kind === 'runaway') {
|
|
312
|
+
deps.logDebug?.(`${spec.label}: degraded — ${failure.reason}`);
|
|
313
|
+
}
|
|
314
|
+
await setTaskSection(deps.cwd, deps.taskId, cacheHeading, sectionText);
|
|
315
|
+
sections.push({ name: spec.section, text: sectionText });
|
|
273
316
|
}
|
|
274
317
|
// All workers succeeded — the assembled output below becomes the canonical
|
|
275
318
|
// 'research' section (written by the orchestrator). The per-worker caches
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mjasnikovs/pi-task",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.27",
|
|
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",
|
|
@@ -66,6 +66,7 @@
|
|
|
66
66
|
"pi": {
|
|
67
67
|
"extensions": [
|
|
68
68
|
"dist/index.js"
|
|
69
|
-
]
|
|
69
|
+
],
|
|
70
|
+
"image": "https://raw.githubusercontent.com/mjasnikovs/pi-task/main/assets/pipeline.png"
|
|
70
71
|
}
|
|
71
72
|
}
|