@mjasnikovs/pi-task 0.40.40 → 0.40.41
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.
|
@@ -96,6 +96,7 @@ export declare function parseEnforceVerdict(text: string): {
|
|
|
96
96
|
/** The subset of a runWorker result the enforcement-child mapping reads. */
|
|
97
97
|
export interface EnforceChildResult extends WorkerFailureInput {
|
|
98
98
|
text: string;
|
|
99
|
+
modelError?: string;
|
|
99
100
|
}
|
|
100
101
|
/**
|
|
101
102
|
* Map the enforcement child's runWorker result to a fatal error message, or null
|
|
@@ -236,9 +236,19 @@ export function parseEnforceVerdict(text) {
|
|
|
236
236
|
* effects don't get re-classified as a user cancel or a crash.
|
|
237
237
|
*/
|
|
238
238
|
export function classifyEnforceChildFailure(r) {
|
|
239
|
-
const
|
|
240
|
-
|
|
241
|
-
|
|
239
|
+
const kill = classifyWorkerFailure(r);
|
|
240
|
+
const named = kill ? describeKill(kill) : null;
|
|
241
|
+
if (named !== null)
|
|
242
|
+
return named;
|
|
243
|
+
// Not a kill, so the ladder leaves it to the consumer: pi reports a failed
|
|
244
|
+
// turn as exit 0, empty text, and the cause in `modelError`. Unread, the
|
|
245
|
+
// empty text parses as "no verdict" and a dead provider is blamed on the work.
|
|
246
|
+
// Checked after a surviving loop too — that arm is a warning, not an answer.
|
|
247
|
+
return r.modelError && r.text.trim().length === 0 ?
|
|
248
|
+
`model error — ${r.modelError.slice(0, 200)}`
|
|
249
|
+
: null;
|
|
250
|
+
}
|
|
251
|
+
function describeKill(failure) {
|
|
242
252
|
switch (failure.kind) {
|
|
243
253
|
case 'stalled':
|
|
244
254
|
// Otherwise a long silence is reported as a user cancel.
|
|
@@ -12,4 +12,10 @@
|
|
|
12
12
|
* — the tool whitelist, not the extension flag, is the bound that holds.
|
|
13
13
|
*/
|
|
14
14
|
import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';
|
|
15
|
-
|
|
15
|
+
import type { SpawnFn } from '../shared/child-process.js';
|
|
16
|
+
/** Test seams: a fake child, and no real backoff sleep between its attempts. */
|
|
17
|
+
export interface PiWorkerInternals {
|
|
18
|
+
spawn?: SpawnFn;
|
|
19
|
+
sleepFor?: (ms: number) => Promise<void>;
|
|
20
|
+
}
|
|
21
|
+
export declare function registerPiWorker(pi: ExtensionAPI, internals?: PiWorkerInternals): void;
|
|
@@ -19,10 +19,28 @@ import { runWorker } from './pi-worker-core.js';
|
|
|
19
19
|
import { contextWindowForGroup } from '../task/context-usage.js';
|
|
20
20
|
import { childFailureReason, formatChildFailure, makeWorkerTool, workerAnswer, workerUnavailable } from './shared.js';
|
|
21
21
|
const RENDER_PROMPT_MAX = 120;
|
|
22
|
+
const STDERR_TAIL = 500;
|
|
23
|
+
function workerDetails(r) {
|
|
24
|
+
return {
|
|
25
|
+
exitCode: r.exitCode,
|
|
26
|
+
attempts: r.attempts,
|
|
27
|
+
restarts: r.restarts.map(x => x.reason),
|
|
28
|
+
...(r.modelError !== undefined ? { modelError: r.modelError } : {}),
|
|
29
|
+
...(r.stderr ? { stderr: r.stderr.slice(-STDERR_TAIL) } : {})
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/** An empty final answer, with the spawns it cost, so the model can tell a
|
|
33
|
+
* retried failure from a child that simply had nothing to say. */
|
|
34
|
+
function describeEmptyAnswer(r) {
|
|
35
|
+
if (r.restarts.length === 0)
|
|
36
|
+
return '(no output)';
|
|
37
|
+
const reasons = r.restarts.map(x => x.reason).join(', ');
|
|
38
|
+
return `(no output after ${r.attempts} attempts; discarded: ${reasons})`;
|
|
39
|
+
}
|
|
22
40
|
const WorkerParams = Type.Object({
|
|
23
41
|
prompt: Type.String({ description: 'Task for the worker to perform.' })
|
|
24
42
|
});
|
|
25
|
-
export function registerPiWorker(pi) {
|
|
43
|
+
export function registerPiWorker(pi, internals = {}) {
|
|
26
44
|
makeWorkerTool(pi, {
|
|
27
45
|
name: 'pi-worker',
|
|
28
46
|
label: 'Pi Worker',
|
|
@@ -67,14 +85,25 @@ export function registerPiWorker(pi) {
|
|
|
67
85
|
// model and its window is the honest one. Without this the churn
|
|
68
86
|
// rule cannot fire — see RunWorkerInput.contextWindow.
|
|
69
87
|
contextWindow: contextWindowForGroup(ctx, 'research') || 'unknown',
|
|
70
|
-
groupArgs: groupChildArgs('research')
|
|
88
|
+
groupArgs: groupChildArgs('research'),
|
|
89
|
+
...(internals.spawn ? { spawn: internals.spawn } : {}),
|
|
90
|
+
...(internals.sleepFor ? { sleepFor: internals.sleepFor } : {})
|
|
71
91
|
});
|
|
72
|
-
const details =
|
|
92
|
+
const details = workerDetails(result);
|
|
73
93
|
const failure = formatChildFailure(result, 'Worker aborted.');
|
|
74
94
|
if (failure !== null) {
|
|
75
95
|
return workerUnavailable(failure, details, childFailureReason(result));
|
|
76
96
|
}
|
|
77
|
-
|
|
97
|
+
// Not a kill, so the ladder leaves it to us: pi reports a failed turn
|
|
98
|
+
// as exit 0, empty text, and the cause in `modelError`.
|
|
99
|
+
if (result.modelError && result.text.trim().length === 0) {
|
|
100
|
+
return workerUnavailable(`Worker failed: model error — ${result.modelError.slice(0, 200)}`, details, 'model-error');
|
|
101
|
+
}
|
|
102
|
+
const text = result.text.trim();
|
|
103
|
+
if (text.length === 0) {
|
|
104
|
+
return workerUnavailable(describeEmptyAnswer(result), details, 'no-answer');
|
|
105
|
+
}
|
|
106
|
+
return workerAnswer(result.text, details);
|
|
78
107
|
},
|
|
79
108
|
renderCall(args, theme) {
|
|
80
109
|
const prompt = args.prompt.replace(/\s+/g, ' ').trim();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mjasnikovs/pi-task",
|
|
3
|
-
"version": "0.40.
|
|
3
|
+
"version": "0.40.41",
|
|
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",
|