@mjasnikovs/pi-task 0.13.24 → 0.13.26
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 +3 -3
- 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
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
<div align="center">
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+

|
|
4
4
|
|
|
5
|
-
#
|
|
5
|
+
# pi-task
|
|
6
6
|
|
|
7
7
|
**Deterministic spec-orchestration for local models — with bundled web, docs, fetch, and worker sub-agent tools.**
|
|
8
8
|
|
|
@@ -69,7 +69,7 @@ A real feature is usually several tasks, not one. `/task-auto` is a thin planner
|
|
|
69
69
|
|
|
70
70
|
<div align="center">
|
|
71
71
|
|
|
72
|
-
|
|
72
|
+

|
|
73
73
|
|
|
74
74
|
</div>
|
|
75
75
|
|
|
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.26",
|
|
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
|
}
|