@mjasnikovs/pi-task 0.13.32 → 0.13.34
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.
|
@@ -13,9 +13,9 @@ import { renderInlineMarkdown, stripInlineMarkdown } from './inline-markdown.js'
|
|
|
13
13
|
import { AUTO_CLARIFY_PROMPT, AUTO_DECOMPOSE_PROMPT } from './auto-prompts.js';
|
|
14
14
|
import { isDuplicateQuestion, MAX_DUP_STRIKES, DUP_REPROMPT_HINT } from './question-dedup.js';
|
|
15
15
|
import { allocateAutoId, buildAutoBody, parseDecomposeList, parseTaskList, checkOffTask, stampTaskInProgress, findResumableAuto } from './auto-io.js';
|
|
16
|
-
import { writeTaskFile, readTaskFile, updateTaskFrontMatter, taskFilePath } from './task-io.js';
|
|
16
|
+
import { writeTaskFile, readTaskFile, updateTaskFrontMatter, taskFilePath, tasksDir } from './task-io.js';
|
|
17
17
|
import { gitCommitAll } from './auto-commit.js';
|
|
18
|
-
import { runGuidelineEnforcement, classifyEnforceChildFailure, ENFORCE_TIMEOUT_MS } from './enforce-guidelines.js';
|
|
18
|
+
import { runGuidelineEnforcement, classifyEnforceChildFailure, ENFORCE_TIMEOUT_MS, ENFORCE_LOOP } from './enforce-guidelines.js';
|
|
19
19
|
import { runWorker } from '../workers/pi-worker-core.js';
|
|
20
20
|
import { runPhaseChild, prependHint, USER_CANCELLED } from './child-runner.js';
|
|
21
21
|
import { SessionUI, registerBridgeCommand } from '../remote/bridge.js';
|
|
@@ -328,6 +328,20 @@ function defaultDeps(ctx, cwd, signal, title) {
|
|
|
328
328
|
lastLine = undefined;
|
|
329
329
|
contextUsage = undefined;
|
|
330
330
|
const startedAt = Date.now();
|
|
331
|
+
// Per-pass debug log. The enforce child is otherwise
|
|
332
|
+
// unobservable (it has no per-task file like the impl runner),
|
|
333
|
+
// so a loop/timeout in the verifier was undiagnosable. One
|
|
334
|
+
// rolling file under .pi-tasks/; each pass brackets its
|
|
335
|
+
// tool/text lines with start/end markers naming the task and
|
|
336
|
+
// the terminal outcome, so a kill shows the exact repeated
|
|
337
|
+
// calls that tripped the loop detector. Fire-and-forget.
|
|
338
|
+
const enforceLogPath = path.join(tasksDir(cwd2), 'enforce-debug.log');
|
|
339
|
+
const logEnforce = (msg) => {
|
|
340
|
+
void fsp
|
|
341
|
+
.appendFile(enforceLogPath, `${new Date().toISOString()} ${msg}\n`)
|
|
342
|
+
.catch(() => { });
|
|
343
|
+
};
|
|
344
|
+
logEnforce(`=== enforce start: ${taskTitle} ===`);
|
|
331
345
|
const stopLoader = startAutoLoader(enforceCtx, () => ({
|
|
332
346
|
title: taskTitle,
|
|
333
347
|
kind: 'enforce',
|
|
@@ -345,12 +359,16 @@ function defaultDeps(ctx, cwd, signal, title) {
|
|
|
345
359
|
signal: sig,
|
|
346
360
|
tools,
|
|
347
361
|
timeoutMs: ENFORCE_TIMEOUT_MS,
|
|
362
|
+
loop: ENFORCE_LOOP,
|
|
348
363
|
onLine: line => {
|
|
349
364
|
lastLine = line;
|
|
350
|
-
|
|
365
|
+
logEnforce(line);
|
|
351
366
|
}
|
|
352
367
|
});
|
|
353
368
|
const failure = classifyEnforceChildFailure(r);
|
|
369
|
+
logEnforce(failure ?
|
|
370
|
+
`=== enforce end: FAIL — ${failure} ===`
|
|
371
|
+
: '=== enforce end: verdict captured ===');
|
|
354
372
|
if (failure)
|
|
355
373
|
throw new Error(failure);
|
|
356
374
|
return r.text;
|
|
@@ -7,6 +7,21 @@ declare const ENFORCE_TOOLS = "read,grep,find,ls,edit,write";
|
|
|
7
7
|
* fix pass reads several files and may rewrite a few; give it room but never
|
|
8
8
|
* let a wedged child hang the whole run. */
|
|
9
9
|
declare const ENFORCE_TIMEOUT_MS = 600000;
|
|
10
|
+
/**
|
|
11
|
+
* Loop-guard tuning for the enforcement child. Unlike a read-only research
|
|
12
|
+
* worker, this pass's JOB is to rework files in place, so it legitimately reads,
|
|
13
|
+
* edits, and re-greps the SAME file many times — the default path-revisit
|
|
14
|
+
* heuristic (5 ops on one path) mislabels that as a runaway. It is disabled here
|
|
15
|
+
* (pathThreshold = Infinity); only the exact-match guard remains (a byte-for-byte
|
|
16
|
+
* identical call repeated, raised to 12 over a 40-call window), with the 10-min
|
|
17
|
+
* wall-clock timeout as the real runaway backstop. Validated against a real
|
|
18
|
+
* loop-kill on mx5 TASK_0002, where a single-file fix pass on queries.ts tripped
|
|
19
|
+
* the default detector at 5 revisits. */
|
|
20
|
+
export declare const ENFORCE_LOOP: {
|
|
21
|
+
readonly window: 40;
|
|
22
|
+
readonly threshold: 12;
|
|
23
|
+
readonly pathThreshold: number;
|
|
24
|
+
};
|
|
10
25
|
export interface GuidelineDoc {
|
|
11
26
|
/** Filenames found, in discovery order (e.g. ['AGENTS.md', 'CLAUDE.md']). */
|
|
12
27
|
files: string[];
|
|
@@ -28,6 +28,21 @@ const ENFORCE_TOOLS = 'read,grep,find,ls,edit,write';
|
|
|
28
28
|
* fix pass reads several files and may rewrite a few; give it room but never
|
|
29
29
|
* let a wedged child hang the whole run. */
|
|
30
30
|
const ENFORCE_TIMEOUT_MS = 600_000;
|
|
31
|
+
/**
|
|
32
|
+
* Loop-guard tuning for the enforcement child. Unlike a read-only research
|
|
33
|
+
* worker, this pass's JOB is to rework files in place, so it legitimately reads,
|
|
34
|
+
* edits, and re-greps the SAME file many times — the default path-revisit
|
|
35
|
+
* heuristic (5 ops on one path) mislabels that as a runaway. It is disabled here
|
|
36
|
+
* (pathThreshold = Infinity); only the exact-match guard remains (a byte-for-byte
|
|
37
|
+
* identical call repeated, raised to 12 over a 40-call window), with the 10-min
|
|
38
|
+
* wall-clock timeout as the real runaway backstop. Validated against a real
|
|
39
|
+
* loop-kill on mx5 TASK_0002, where a single-file fix pass on queries.ts tripped
|
|
40
|
+
* the default detector at 5 revisits. */
|
|
41
|
+
export const ENFORCE_LOOP = {
|
|
42
|
+
window: 40,
|
|
43
|
+
threshold: 12,
|
|
44
|
+
pathThreshold: Number.POSITIVE_INFINITY
|
|
45
|
+
};
|
|
31
46
|
/**
|
|
32
47
|
* Read the guideline files that live directly in `cwd` (AGENTS.md, CLAUDE.md).
|
|
33
48
|
* Returns null when none exist or all are empty — the caller treats that as a
|
|
@@ -13,6 +13,17 @@ export interface RunWorkerInput {
|
|
|
13
13
|
onLine?: (line: string) => void;
|
|
14
14
|
/** Per-worker wall-clock timeout in ms. Defaults to RESEARCH_WORKER_TIMEOUT_MS. */
|
|
15
15
|
timeoutMs?: number;
|
|
16
|
+
/**
|
|
17
|
+
* Per-worker loop-detector tuning. Defaults to the read-only research/impl
|
|
18
|
+
* guard (LOOP_WINDOW / LOOP_THRESHOLD, path threshold = exact threshold). An
|
|
19
|
+
* edit/fix pass legitimately revisits one file, so it can raise (or disable
|
|
20
|
+
* via Infinity) `pathThreshold` to keep only the exact-match guard.
|
|
21
|
+
*/
|
|
22
|
+
loop?: {
|
|
23
|
+
window?: number;
|
|
24
|
+
threshold?: number;
|
|
25
|
+
pathThreshold?: number;
|
|
26
|
+
};
|
|
16
27
|
}
|
|
17
28
|
export interface RunWorkerResult {
|
|
18
29
|
text: string;
|
|
@@ -71,7 +71,9 @@ export async function runWorker(input) {
|
|
|
71
71
|
const invocation = getPiInvocation([...baseArgs, prompt]);
|
|
72
72
|
const tStart = Date.now();
|
|
73
73
|
let tFirstByte = null;
|
|
74
|
-
const
|
|
74
|
+
const loopWindow = input.loop?.window ?? LOOP_WINDOW;
|
|
75
|
+
const loopThreshold = input.loop?.threshold ?? LOOP_THRESHOLD;
|
|
76
|
+
const loopDetector = new LoopDetector(loopWindow, loopThreshold, input.loop?.pathThreshold ?? loopThreshold);
|
|
75
77
|
// Capture the hit the detector reports (it also returns it to the unified
|
|
76
78
|
// runner, which kills the child on a hit). Without capturing it here the
|
|
77
79
|
// SIGTERM that kill produces would surface as a bare non-zero exit the
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mjasnikovs/pi-task",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.34",
|
|
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",
|