@mjasnikovs/pi-task 0.38.6 → 0.38.8
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.
|
@@ -25,7 +25,7 @@ import { requestCancel, resetCancel, isCancelRequested, cancelCheckpoint } from
|
|
|
25
25
|
import { armCancelListener, disarmCancelListener } from './cancel-input.js';
|
|
26
26
|
import { beginRun, endRun } from './mid-run-input.js';
|
|
27
27
|
import { reportDroppedInput } from './dropped-input.js';
|
|
28
|
-
import { refineExistingFilesBlock } from './phases.js';
|
|
28
|
+
import { refineExistingFilesBlock, SINGLE_READ_EXTENSION_PATH } from './phases.js';
|
|
29
29
|
import { SessionUI, registerBridgeCommand, publishLifecycleNotice } from '../remote/bridge.js';
|
|
30
30
|
import { pushNotify } from '../remote/push.js';
|
|
31
31
|
import { startAutoLoader } from './widget.js';
|
|
@@ -1079,6 +1079,13 @@ function defaultDeps(ctx, cwd, signal, title) {
|
|
|
1079
1079
|
cwd,
|
|
1080
1080
|
taskId: '',
|
|
1081
1081
|
signal,
|
|
1082
|
+
// IN-RUN thrash guard for the planning children (mx5-n 2026-08-14: a
|
|
1083
|
+
// decompose child re-read DESIGN/marketplace.html until it filled a
|
|
1084
|
+
// 120k window, and ran 16m23s without returning). Every planning child
|
|
1085
|
+
// gets its source doc INLINED in its prompt, so a second read of a file
|
|
1086
|
+
// it has already opened can only be thrash — which makes the read-once
|
|
1087
|
+
// block safe here in a way it is not for a phase that must explore.
|
|
1088
|
+
childExtensions: [SINGLE_READ_EXTENSION_PATH],
|
|
1082
1089
|
onChildOutput: (line) => {
|
|
1083
1090
|
lastLine = line;
|
|
1084
1091
|
},
|
|
@@ -10,6 +10,40 @@ import type { DebugLine } from './debug-log.js';
|
|
|
10
10
|
export declare const LOOP_WINDOW = 20;
|
|
11
11
|
export declare const LOOP_THRESHOLD = 5;
|
|
12
12
|
export declare const MAX_LOOP_RESTARTS = 2;
|
|
13
|
+
/**
|
|
14
|
+
* Hard wall-clock bound on ONE spawn of a phase child.
|
|
15
|
+
*
|
|
16
|
+
* The loop detector above only sees IDENTICAL repeated calls; a child that
|
|
17
|
+
* re-reads the same design file at varying offsets slips past it and, with pi
|
|
18
|
+
* compacting its context whenever the window fills, never exits on its own.
|
|
19
|
+
* mx5-n 2026-08-14 is the observed case: a decompose child ran 16m23s at
|
|
20
|
+
* 117,370 of a 120,064-token window, adding ~56k tokens of tool output per
|
|
21
|
+
* minute, and had to be killed by hand. `streamInactivityMs` cannot catch it —
|
|
22
|
+
* that guard fires on SILENCE and this child was the opposite of silent.
|
|
23
|
+
*
|
|
24
|
+
* Sized against measured HEALTHY planning children on the same local 27B
|
|
25
|
+
* backend, which is the slowest thing we run: requirement extraction 54s,
|
|
26
|
+
* artifact closure 47s, decompose 89s (22 titles), coverage 17s, and a whole
|
|
27
|
+
* plan phase (clarify + two extractions + decompose) 321s end to end. Ten
|
|
28
|
+
* minutes is 3-6x the slowest of those and well under the runaway, so it ends
|
|
29
|
+
* the pathology without ever trimming honest work. Deliberately far above
|
|
30
|
+
* RESEARCH_WORKER_TIMEOUT_MS (240s): a research worker answers one question,
|
|
31
|
+
* a planning child reasons over the whole design doc.
|
|
32
|
+
*/
|
|
33
|
+
export declare const PHASE_CHILD_TIMEOUT_MS = 600000;
|
|
34
|
+
/**
|
|
35
|
+
* Restart hint after a phase child burns its whole wall-clock budget. It
|
|
36
|
+
* diagnoses over-exploration, which is what the cap actually catches — the same
|
|
37
|
+
* job WORKER_TIMEOUT_HINT does for research workers.
|
|
38
|
+
*/
|
|
39
|
+
export declare const PHASE_TIMEOUT_HINT: string;
|
|
40
|
+
/** Thrown when a phase child spends its whole restart budget hitting the cap. */
|
|
41
|
+
export declare class PhaseTimeoutError extends Error {
|
|
42
|
+
readonly childName: string;
|
|
43
|
+
readonly budgetMs: number;
|
|
44
|
+
readonly attempts: number;
|
|
45
|
+
constructor(childName: string, budgetMs: number, attempts: number);
|
|
46
|
+
}
|
|
13
47
|
export declare function isConnectionError(cause: string): boolean;
|
|
14
48
|
/** Exponential backoff before a connection-error retry: 500ms, 1s, 2s, …, so a
|
|
15
49
|
* brief saturation window can drain before we re-issue the request. */
|
|
@@ -24,14 +58,16 @@ export interface PhaseRunResult {
|
|
|
24
58
|
/** Set when the child's final turn failed with stopReason "error" (model/provider failure). */
|
|
25
59
|
modelError?: string;
|
|
26
60
|
}
|
|
27
|
-
export declare function childArgs(tools: string): string[];
|
|
61
|
+
export declare function childArgs(tools: string, extensions?: readonly string[]): string[];
|
|
28
62
|
export declare const USER_CANCELLED = "__user_cancelled__";
|
|
29
63
|
/**
|
|
30
64
|
* Run a child pi process with JSON event-stream output, loop detection, and
|
|
31
65
|
* context-usage tracking. This is the typed convenience wrapper used by
|
|
32
66
|
* phase-level code.
|
|
33
67
|
*/
|
|
34
|
-
export declare function runChild(cwd: string, tools: string, prompt: string, signal: AbortSignal, onLine?: (line: string) => void, onContextUsage?: (snapshot: ContextSnapshot) => void, onToolCall?: (call: ToolCall) => LoopHit | null, spawnFn?: SpawnFn
|
|
68
|
+
export declare function runChild(cwd: string, tools: string, prompt: string, signal: AbortSignal, onLine?: (line: string) => void, onContextUsage?: (snapshot: ContextSnapshot) => void, onToolCall?: (call: ToolCall) => LoopHit | null, spawnFn?: SpawnFn,
|
|
69
|
+
/** Internal `-e` extension paths for in-run guards (see childArgs). */
|
|
70
|
+
extensions?: readonly string[]): Promise<PhaseRunResult>;
|
|
35
71
|
interface PhaseDeps {
|
|
36
72
|
cwd: string;
|
|
37
73
|
taskId: string;
|
|
@@ -45,6 +81,24 @@ interface PhaseDeps {
|
|
|
45
81
|
*/
|
|
46
82
|
recordSubStep?: (label: string, ms: number) => void;
|
|
47
83
|
spawn?: SpawnFn;
|
|
84
|
+
/**
|
|
85
|
+
* Internal `-e` extension paths loaded into this child for IN-RUN guards.
|
|
86
|
+
*
|
|
87
|
+
* The point of a guard that runs inside the child is that it does not have
|
|
88
|
+
* to kill it: pi turns a `tool_call` handler's `{block, reason}` into an
|
|
89
|
+
* error tool result, so the model reads the reason as its own tool output
|
|
90
|
+
* and continues with its context intact. The host's only alternative is to
|
|
91
|
+
* kill and re-spawn from nothing, which just re-runs a model that
|
|
92
|
+
* deterministically re-thrashes (workers/single-read-guard.ts).
|
|
93
|
+
*/
|
|
94
|
+
childExtensions?: readonly string[];
|
|
95
|
+
/**
|
|
96
|
+
* Wall-clock budget for ONE spawn of this child, in ms. Defaults to
|
|
97
|
+
* PHASE_CHILD_TIMEOUT_MS; `0` disables the cap. Mirrors runWorker's
|
|
98
|
+
* `timeoutMs` input, which is the same backstop one layer down
|
|
99
|
+
* (workers/pi-worker-core.ts). Tests inject a short budget.
|
|
100
|
+
*/
|
|
101
|
+
timeoutMs?: number;
|
|
48
102
|
/**
|
|
49
103
|
* Write a timestamped line to the per-task debug log. Fire-and-forget, and
|
|
50
104
|
* UNSET entirely when the trail is off — so a caller must keep the `?.` and
|
|
@@ -68,6 +122,17 @@ export type { PhaseDeps };
|
|
|
68
122
|
* leaking, throw LeakedToolCallError rather than returning the unexecuted call.
|
|
69
123
|
* Empty completions and connection-class model errors share that same budget —
|
|
70
124
|
* see triageChildResult, which decides every one of those cases.
|
|
125
|
+
*
|
|
126
|
+
* TWO RUNAWAY GUARDS ride the same budget, because this is the runner every
|
|
127
|
+
* /task-auto planning child goes through (clarify, decompose, coverage,
|
|
128
|
+
* contract-extract) and until mx5-n 2026-08-14 it had neither:
|
|
129
|
+
* • a LoopDetector, so an identical repeated tool call is killed and
|
|
130
|
+
* re-prompted instead of being allowed to fill the context window;
|
|
131
|
+
* • PHASE_CHILD_TIMEOUT_MS, the backstop for the varied-args thrash the
|
|
132
|
+
* detector cannot see — the shape that actually cost us a 16-minute
|
|
133
|
+
* decompose child that was never going to return.
|
|
134
|
+
* Both are checked BEFORE the triage ladder: we killed the child, so its exit
|
|
135
|
+
* status describes our SIGTERM and says nothing about its verdict.
|
|
71
136
|
*/
|
|
72
137
|
export declare function runPhaseChild(deps: PhaseDeps, name: string, tools: string, prompt: string): Promise<string>;
|
|
73
138
|
export declare function formatLoopHint(hit: LoopHit): string;
|
|
@@ -21,6 +21,86 @@ export const LOOP_WINDOW = 20;
|
|
|
21
21
|
export const LOOP_THRESHOLD = 5;
|
|
22
22
|
export const MAX_LOOP_RESTARTS = 2; // 3 strikes total (initial attempt + 2 restarts)
|
|
23
23
|
// MAX_LEAK_RETRIES lives in shared/leaked-tool-call.ts (imported above).
|
|
24
|
+
// ─── Phase-child wall-clock cap ──────────────────────────────────────────────
|
|
25
|
+
/**
|
|
26
|
+
* Hard wall-clock bound on ONE spawn of a phase child.
|
|
27
|
+
*
|
|
28
|
+
* The loop detector above only sees IDENTICAL repeated calls; a child that
|
|
29
|
+
* re-reads the same design file at varying offsets slips past it and, with pi
|
|
30
|
+
* compacting its context whenever the window fills, never exits on its own.
|
|
31
|
+
* mx5-n 2026-08-14 is the observed case: a decompose child ran 16m23s at
|
|
32
|
+
* 117,370 of a 120,064-token window, adding ~56k tokens of tool output per
|
|
33
|
+
* minute, and had to be killed by hand. `streamInactivityMs` cannot catch it —
|
|
34
|
+
* that guard fires on SILENCE and this child was the opposite of silent.
|
|
35
|
+
*
|
|
36
|
+
* Sized against measured HEALTHY planning children on the same local 27B
|
|
37
|
+
* backend, which is the slowest thing we run: requirement extraction 54s,
|
|
38
|
+
* artifact closure 47s, decompose 89s (22 titles), coverage 17s, and a whole
|
|
39
|
+
* plan phase (clarify + two extractions + decompose) 321s end to end. Ten
|
|
40
|
+
* minutes is 3-6x the slowest of those and well under the runaway, so it ends
|
|
41
|
+
* the pathology without ever trimming honest work. Deliberately far above
|
|
42
|
+
* RESEARCH_WORKER_TIMEOUT_MS (240s): a research worker answers one question,
|
|
43
|
+
* a planning child reasons over the whole design doc.
|
|
44
|
+
*/
|
|
45
|
+
export const PHASE_CHILD_TIMEOUT_MS = 600_000;
|
|
46
|
+
/**
|
|
47
|
+
* Restart hint after a phase child burns its whole wall-clock budget. It
|
|
48
|
+
* diagnoses over-exploration, which is what the cap actually catches — the same
|
|
49
|
+
* job WORKER_TIMEOUT_HINT does for research workers.
|
|
50
|
+
*/
|
|
51
|
+
export const PHASE_TIMEOUT_HINT = '[SYSTEM NOTE: Your previous attempt ran out of time before answering — you '
|
|
52
|
+
+ 'were re-reading source material you had already seen. Read each file AT '
|
|
53
|
+
+ 'MOST ONCE, then write your answer from what you have. Do not re-open a '
|
|
54
|
+
+ 'file you have already read.]';
|
|
55
|
+
/**
|
|
56
|
+
* Combine the caller's abort signal with a wall-clock timer into one signal,
|
|
57
|
+
* keeping the two causes apart: `timedOut()` is true only when the timer fired,
|
|
58
|
+
* never when the user cancelled — so a cap can restart the child while a cancel
|
|
59
|
+
* still ends the run. `ms <= 0` disables the timer entirely.
|
|
60
|
+
*
|
|
61
|
+
* (workers/pi-worker-core.ts has the same shape for research workers. It is not
|
|
62
|
+
* shared because that module imports FROM this one; a common home for it would
|
|
63
|
+
* be worth it if a third caller ever appears.)
|
|
64
|
+
*/
|
|
65
|
+
function phaseTimeout(external, ms) {
|
|
66
|
+
const ctrl = new AbortController();
|
|
67
|
+
let firedByTimer = false;
|
|
68
|
+
const armed = ms > 0 && Number.isFinite(ms);
|
|
69
|
+
const timer = armed ?
|
|
70
|
+
setTimeout(() => {
|
|
71
|
+
firedByTimer = true;
|
|
72
|
+
ctrl.abort();
|
|
73
|
+
}, ms)
|
|
74
|
+
: undefined;
|
|
75
|
+
const onExternal = () => ctrl.abort();
|
|
76
|
+
if (external.aborted)
|
|
77
|
+
ctrl.abort();
|
|
78
|
+
else
|
|
79
|
+
external.addEventListener('abort', onExternal, { once: true });
|
|
80
|
+
return {
|
|
81
|
+
signal: ctrl.signal,
|
|
82
|
+
timedOut: () => firedByTimer,
|
|
83
|
+
cleanup: () => {
|
|
84
|
+
if (timer)
|
|
85
|
+
clearTimeout(timer);
|
|
86
|
+
external.removeEventListener('abort', onExternal);
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
/** Thrown when a phase child spends its whole restart budget hitting the cap. */
|
|
91
|
+
export class PhaseTimeoutError extends Error {
|
|
92
|
+
childName;
|
|
93
|
+
budgetMs;
|
|
94
|
+
attempts;
|
|
95
|
+
constructor(childName, budgetMs, attempts) {
|
|
96
|
+
super(`${childName} child exceeded its ${Math.round(budgetMs / 1000)}s budget on all `
|
|
97
|
+
+ `${attempts} attempt(s) — it never stopped working long enough to answer`);
|
|
98
|
+
this.childName = childName;
|
|
99
|
+
this.budgetMs = budgetMs;
|
|
100
|
+
this.attempts = attempts;
|
|
101
|
+
this.name = 'PhaseTimeoutError';
|
|
102
|
+
}
|
|
103
|
+
}
|
|
24
104
|
// ─── Connection-error retry ──────────────────────────────────────────────────
|
|
25
105
|
/**
|
|
26
106
|
* A connection-class model error is transient: a single dropped fetch to a live
|
|
@@ -47,7 +127,7 @@ export function connectionRetryBackoffMs(attempt) {
|
|
|
47
127
|
}
|
|
48
128
|
const defaultSleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
|
49
129
|
// ─── Spawn helpers ───────────────────────────────────────────────────────────
|
|
50
|
-
export function childArgs(tools) {
|
|
130
|
+
export function childArgs(tools, extensions = []) {
|
|
51
131
|
// `--mode json` puts the child into the structured event stream the
|
|
52
132
|
// unified runner parses in `mode: 'json-events'`. Without it the child
|
|
53
133
|
// emits plain text, every line fails JSON.parse, finalText stays empty,
|
|
@@ -62,8 +142,13 @@ export function childArgs(tools) {
|
|
|
62
142
|
// The prompt is NOT an argv element: it goes to the child over stdin (see
|
|
63
143
|
// runChild below / getPiInvocation), so a large inlined-design prompt can't
|
|
64
144
|
// overflow the OS command-line limit (Windows `spawn ENAMETOOLONG`).
|
|
145
|
+
//
|
|
146
|
+
// `extensions` are internal `-e` loads for in-run guards (the caller supplies
|
|
147
|
+
// the path). A no-tools child cannot make a tool call, so it never carries
|
|
148
|
+
// one — the guards all hang off pi's `tool_call` hook.
|
|
65
149
|
const toolFlags = tools === '' ? ['--no-tools'] : ['--tools', tools];
|
|
66
|
-
|
|
150
|
+
const internal = tools === '' ? [] : extensions;
|
|
151
|
+
return [...childBaseArgs(internal), '--mode', 'json', ...toolFlags];
|
|
67
152
|
}
|
|
68
153
|
// Sentinel error thrown when the user dismisses a grill-me dialog.
|
|
69
154
|
// Defined here (not in failure-classifier.ts) to avoid circular dependency.
|
|
@@ -74,8 +159,10 @@ export const USER_CANCELLED = '__user_cancelled__';
|
|
|
74
159
|
* context-usage tracking. This is the typed convenience wrapper used by
|
|
75
160
|
* phase-level code.
|
|
76
161
|
*/
|
|
77
|
-
export async function runChild(cwd, tools, prompt, signal, onLine, onContextUsage, onToolCall, spawnFn
|
|
78
|
-
|
|
162
|
+
export async function runChild(cwd, tools, prompt, signal, onLine, onContextUsage, onToolCall, spawnFn,
|
|
163
|
+
/** Internal `-e` extension paths for in-run guards (see childArgs). */
|
|
164
|
+
extensions) {
|
|
165
|
+
const invocation = getPiInvocation(childArgs(tools, extensions), prompt);
|
|
79
166
|
let loopHit;
|
|
80
167
|
const result = await runChildUnified(spawnFn ?? spawn, invocation, cwd, signal, {
|
|
81
168
|
mode: 'json-events',
|
|
@@ -188,11 +275,52 @@ async function triageChildResult(deps, name, r, attempt, budget, verb) {
|
|
|
188
275
|
* leaking, throw LeakedToolCallError rather than returning the unexecuted call.
|
|
189
276
|
* Empty completions and connection-class model errors share that same budget —
|
|
190
277
|
* see triageChildResult, which decides every one of those cases.
|
|
278
|
+
*
|
|
279
|
+
* TWO RUNAWAY GUARDS ride the same budget, because this is the runner every
|
|
280
|
+
* /task-auto planning child goes through (clarify, decompose, coverage,
|
|
281
|
+
* contract-extract) and until mx5-n 2026-08-14 it had neither:
|
|
282
|
+
* • a LoopDetector, so an identical repeated tool call is killed and
|
|
283
|
+
* re-prompted instead of being allowed to fill the context window;
|
|
284
|
+
* • PHASE_CHILD_TIMEOUT_MS, the backstop for the varied-args thrash the
|
|
285
|
+
* detector cannot see — the shape that actually cost us a 16-minute
|
|
286
|
+
* decompose child that was never going to return.
|
|
287
|
+
* Both are checked BEFORE the triage ladder: we killed the child, so its exit
|
|
288
|
+
* status describes our SIGTERM and says nothing about its verdict.
|
|
191
289
|
*/
|
|
192
290
|
export async function runPhaseChild(deps, name, tools, prompt) {
|
|
193
291
|
let hint = null;
|
|
292
|
+
const loopHistory = [];
|
|
293
|
+
const budgetMs = deps.timeoutMs ?? PHASE_CHILD_TIMEOUT_MS;
|
|
194
294
|
for (let attempt = 0; attempt <= MAX_LEAK_RETRIES; attempt++) {
|
|
195
|
-
const
|
|
295
|
+
const detector = new LoopDetector(LOOP_WINDOW, LOOP_THRESHOLD);
|
|
296
|
+
const clock = phaseTimeout(deps.signal, budgetMs);
|
|
297
|
+
let r;
|
|
298
|
+
try {
|
|
299
|
+
r = await runChild(deps.cwd, tools, prependHint(hint, prompt), clock.signal, deps.onChildOutput, deps.onContextUsage, call => detector.record(call), deps.spawn, deps.childExtensions);
|
|
300
|
+
}
|
|
301
|
+
finally {
|
|
302
|
+
clock.cleanup();
|
|
303
|
+
}
|
|
304
|
+
// A user cancel must not be mistaken for either guard.
|
|
305
|
+
if (deps.signal.aborted)
|
|
306
|
+
throw new Error(USER_CANCELLED);
|
|
307
|
+
if (r.loopHit) {
|
|
308
|
+
loopHistory.push(r.loopHit);
|
|
309
|
+
if (attempt === MAX_LEAK_RETRIES)
|
|
310
|
+
throw new LoopExhaustedError(name, loopHistory);
|
|
311
|
+
deps.logDebug?.(`${name}: looped on ${r.loopHit.call.name} — retry ${attempt + 1}/${MAX_LEAK_RETRIES}`);
|
|
312
|
+
hint = formatLoopHint(r.loopHit);
|
|
313
|
+
continue;
|
|
314
|
+
}
|
|
315
|
+
if (clock.timedOut()) {
|
|
316
|
+
if (attempt === MAX_LEAK_RETRIES) {
|
|
317
|
+
throw new PhaseTimeoutError(name, budgetMs, MAX_LEAK_RETRIES + 1);
|
|
318
|
+
}
|
|
319
|
+
deps.logDebug?.(`${name}: exceeded its ${Math.round(budgetMs / 1000)}s budget — `
|
|
320
|
+
+ `retry ${attempt + 1}/${MAX_LEAK_RETRIES}`);
|
|
321
|
+
hint = PHASE_TIMEOUT_HINT;
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
196
324
|
const step = await triageChildResult(deps, name, r, attempt, MAX_LEAK_RETRIES, 'retry');
|
|
197
325
|
if (step.done)
|
|
198
326
|
return step.text;
|
package/dist/task/phases.d.ts
CHANGED
|
@@ -111,6 +111,15 @@ export declare function searchConfigured(getEnv?: (k: string) => string | undefi
|
|
|
111
111
|
/** Extra prompt block for the APIS worker when search is available — trigger-framed
|
|
112
112
|
* (the validated shape for getting a local model to actually reach for search). */
|
|
113
113
|
export declare const RESEARCH_SEARCH_HINT: string;
|
|
114
|
+
/**
|
|
115
|
+
* In-process guards loaded into the TOOLING worker only: block a re-read of any
|
|
116
|
+
* file already read, and block any byte-identical grep/find/ls repeat, feeding
|
|
117
|
+
* the model "you already have this, answer now" instead of letting it re-run.
|
|
118
|
+
* TOOLING reads each file once and never needs an identical search twice in any
|
|
119
|
+
* healthy recorded run, so neither rule has a legitimate false positive here.
|
|
120
|
+
* See single-read-guard.ts.
|
|
121
|
+
*/
|
|
122
|
+
export declare const SINGLE_READ_EXTENSION_PATH: string;
|
|
114
123
|
/**
|
|
115
124
|
* The TOOLING worker only needs to know which verification commands the task
|
|
116
125
|
* cares about — never the per-file edit list. Big refined prompts embed a long
|
package/dist/task/phases.js
CHANGED
|
@@ -332,7 +332,7 @@ export const RESEARCH_SEARCH_HINT = '\n\nLIVE WEB — use pi-worker-search for e
|
|
|
332
332
|
* healthy recorded run, so neither rule has a legitimate false positive here.
|
|
333
333
|
* See single-read-guard.ts.
|
|
334
334
|
*/
|
|
335
|
-
const SINGLE_READ_EXTENSION_PATH = fileURLToPath(new URL('../workers/single-read-extension.js', import.meta.url));
|
|
335
|
+
export const SINGLE_READ_EXTENSION_PATH = fileURLToPath(new URL('../workers/single-read-extension.js', import.meta.url));
|
|
336
336
|
/**
|
|
337
337
|
* Task-file heading under which a research worker's validated output is cached.
|
|
338
338
|
* A resumed research phase reads these to skip workers that already succeeded,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mjasnikovs/pi-task",
|
|
3
|
-
"version": "0.38.
|
|
3
|
+
"version": "0.38.8",
|
|
4
4
|
"description": "Deterministic task planning and spec-orchestration for local models — crash-safe /task pipelines with verify/enforce gates, a real-time remote web view, and web/docs/fetch/worker subagent tools.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|