@mjasnikovs/pi-task 0.38.22 → 0.38.24
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 +2 -2
- package/dist/config/reasoning-args.d.ts +12 -1
- package/dist/config/reasoning-args.js +5 -2
- package/dist/config/reasoning.d.ts +60 -2
- package/dist/config/reasoning.js +344 -37
- package/dist/config/register.d.ts +76 -32
- package/dist/config/register.js +124 -82
- package/dist/shared/reasoning-capability.d.ts +2 -5
- package/dist/shared/reasoning-capability.js +31 -4
- package/dist/task/auto-orchestrator.d.ts +2 -0
- package/dist/task/auto-orchestrator.js +28 -41
- package/dist/task/child-runner.d.ts +89 -24
- package/dist/task/child-runner.js +67 -46
- package/dist/task/orchestrator.d.ts +14 -20
- package/dist/task/orchestrator.js +12 -9
- package/dist/task/phases.d.ts +16 -23
- package/dist/task/phases.js +47 -452
- package/dist/task/question-dialog.d.ts +56 -0
- package/dist/task/question-dialog.js +53 -0
- package/dist/task/research-worker.d.ts +180 -0
- package/dist/task/research-worker.js +432 -0
- package/dist/workers/brave-warning.js +4 -30
- package/dist/workers/docs-core.d.ts +8 -4
- package/dist/workers/docs-core.js +30 -21
- package/dist/workers/docs-lookup.d.ts +72 -0
- package/dist/workers/docs-lookup.js +53 -0
- package/dist/workers/docs-project.d.ts +9 -0
- package/dist/workers/docs-project.js +15 -0
- package/dist/workers/pi-worker-core.d.ts +87 -1
- package/dist/workers/pi-worker-core.js +3 -7
- package/dist/workers/pi-worker-docs.js +27 -31
- package/dist/workers/reasoning-warning.d.ts +10 -16
- package/dist/workers/reasoning-warning.js +25 -57
- package/dist/workers/session-hint.d.ts +37 -0
- package/dist/workers/session-hint.js +82 -0
- package/dist/workers/worker-failure.d.ts +34 -0
- package/dist/workers/worker-failure.js +27 -16
- package/dist/workers/worker-kill.d.ts +84 -0
- package/dist/workers/worker-kill.js +124 -0
- package/package.json +1 -1
- package/dist/task/reasoning-groups.d.ts +0 -36
- package/dist/task/reasoning-groups.js +0 -36
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A one-line startup hint in the TUI, and the whole widget lifetime around it.
|
|
3
|
+
*
|
|
4
|
+
* WHY IT IS ONE MODULE. Two hints exist (brave-warning, reasoning-warning) and
|
|
5
|
+
* both had written the same ritual out: the `session_start` subscription, the
|
|
6
|
+
* TUI gate, the `setWidget` in a try/catch, the `onTerminalInput` that clears on
|
|
7
|
+
* the first keystroke, the unsubscribe, and the swallow for a stale ctx — down
|
|
8
|
+
* to a byte-identical comment. Two adapters is a real seam, so the ritual lives
|
|
9
|
+
* here once and each hint supplies only its `compose`.
|
|
10
|
+
*
|
|
11
|
+
* The REFINE half is why this is not just deduplication. A hint may learn
|
|
12
|
+
* something after it has painted (the reasoning hint probes the model's server),
|
|
13
|
+
* and the rule that a refinement must never repaint a widget the user already
|
|
14
|
+
* dismissed lived in one closure variable in one of the two files. It is now a
|
|
15
|
+
* property of this module, asserted once.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* Register one startup hint.
|
|
19
|
+
*
|
|
20
|
+
* `compose` is the seam: it runs at `session_start` inside the TUI gate and
|
|
21
|
+
* returns the hint, or null to say nothing at all. Everything it returns is
|
|
22
|
+
* text; nothing about widgets, keystrokes or teardown reaches it.
|
|
23
|
+
*/
|
|
24
|
+
export function registerSessionHint(pi, key, compose) {
|
|
25
|
+
pi.on('session_start', (_event, ctx) => {
|
|
26
|
+
// Terminal-only hint: needs an interactive TUI to render and to catch the
|
|
27
|
+
// keystroke that dismisses it.
|
|
28
|
+
if (ctx.mode !== 'tui')
|
|
29
|
+
return;
|
|
30
|
+
const hint = compose(ctx);
|
|
31
|
+
if (hint === null)
|
|
32
|
+
return;
|
|
33
|
+
let unsubscribe = null;
|
|
34
|
+
let cleared = false;
|
|
35
|
+
let painted = false;
|
|
36
|
+
const clear = () => {
|
|
37
|
+
cleared = true;
|
|
38
|
+
try {
|
|
39
|
+
ctx.ui.setWidget(key, undefined);
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
/* stale ctx after a session switch — nothing to clear */
|
|
43
|
+
}
|
|
44
|
+
unsubscribe?.();
|
|
45
|
+
unsubscribe = null;
|
|
46
|
+
};
|
|
47
|
+
const render = (text) => {
|
|
48
|
+
try {
|
|
49
|
+
ctx.ui.setWidget(key, [ctx.ui.theme.fg('warning', text)]);
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
// Handled BEFORE the paint can bail. `compose` builds this promise
|
|
57
|
+
// eagerly — the reasoning hint kicks its probe off inside it — so a
|
|
58
|
+
// rejection with the handler attached later is an unhandled rejection on
|
|
59
|
+
// exactly the path the try/catch around `setWidget` exists for: a stale
|
|
60
|
+
// ctx after a session switch. `refine`'s contract says a rejection leaves
|
|
61
|
+
// the first line standing, and that has to hold when there is no first
|
|
62
|
+
// line either.
|
|
63
|
+
if (hint.refine !== undefined) {
|
|
64
|
+
void hint.refine
|
|
65
|
+
.then(text => {
|
|
66
|
+
if (cleared || !painted || text === null)
|
|
67
|
+
return;
|
|
68
|
+
render(text);
|
|
69
|
+
})
|
|
70
|
+
.catch(() => { });
|
|
71
|
+
}
|
|
72
|
+
painted = render(hint.text);
|
|
73
|
+
if (!painted)
|
|
74
|
+
return;
|
|
75
|
+
// Disappear on any interaction — the first raw keystroke clears it.
|
|
76
|
+
// Returning undefined leaves the input untouched (we only observe it).
|
|
77
|
+
unsubscribe = ctx.ui.onTerminalInput(() => {
|
|
78
|
+
clear();
|
|
79
|
+
return undefined;
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
}
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
* caused the bug impossible rather than merely fixed.
|
|
27
27
|
*/
|
|
28
28
|
import type { LoopHit } from '../task/loop-detector.js';
|
|
29
|
+
import type { WorkerKillId } from './worker-kill.js';
|
|
29
30
|
/**
|
|
30
31
|
* The subset of a finished child result this classification reads.
|
|
31
32
|
*
|
|
@@ -83,6 +84,39 @@ export type WorkerFailure = {
|
|
|
83
84
|
};
|
|
84
85
|
/** The `kind` of every row, for exhaustiveness checks in consumers. */
|
|
85
86
|
export type WorkerFailureKind = WorkerFailure['kind'];
|
|
87
|
+
/**
|
|
88
|
+
* The ordered ladder. FIRST MATCH WINS — row order IS the precedence, and it is
|
|
89
|
+
* the only statement of it in the codebase.
|
|
90
|
+
*
|
|
91
|
+
* The order, and why:
|
|
92
|
+
*
|
|
93
|
+
* 1. `stalled` — no output AND the model endpoint did not answer a probe. The
|
|
94
|
+
* most specific diagnosis there is, and the one most easily lost: the kill
|
|
95
|
+
* aborts, so anything checked after `aborted` never sees it.
|
|
96
|
+
* 2. `command-timeout` — a watchdog kill naming the tool call that hung. Also
|
|
97
|
+
* aborts. Before the wall-clock timeout because it is the narrower cause
|
|
98
|
+
* (the two cannot be confused: a watchdog kill leaves the worker's own
|
|
99
|
+
* timeout flag false).
|
|
100
|
+
* 3. `stream-stall` — a watchdog kill for a model stream that went silent while
|
|
101
|
+
* no tool was running. Sits next to `command-timeout` because it is the same
|
|
102
|
+
* class of event: a watchdog, not the model, ended the attempt.
|
|
103
|
+
* 4. `worker-timeout` — the wall-clock backstop.
|
|
104
|
+
* 5. `loop` — killed for repeating one tool call past threshold. After the
|
|
105
|
+
* timeouts, matching the enforce ladder this replaces; in practice the two
|
|
106
|
+
* cannot both fire, since a loop kill stops the attempt before its own timer
|
|
107
|
+
* can expire.
|
|
108
|
+
* 6. `leaked-tool-call` — the model wrote a call as prose instead of invoking
|
|
109
|
+
* it. Only ever set on an otherwise clean run.
|
|
110
|
+
* 7. `aborted` — no specific cause survived, so this really is a cancel.
|
|
111
|
+
* 8. `exit` — a plain non-zero exit with no kill behind it: a crash.
|
|
112
|
+
*/
|
|
113
|
+
export declare const FAILURE_RULES: ReadonlyArray<{
|
|
114
|
+
/** The roster id this row matches. `worker-kill.test.ts` checks the sequence
|
|
115
|
+
* against `FAILURE_ORDER`, so a reordering or an omission is a test failure
|
|
116
|
+
* rather than a silently different precedence. */
|
|
117
|
+
id: WorkerKillId;
|
|
118
|
+
match: (r: WorkerFailureInput) => WorkerFailure | null;
|
|
119
|
+
}>;
|
|
86
120
|
/**
|
|
87
121
|
* Classify a finished child. Returns `undefined` when nothing killed it —
|
|
88
122
|
* which is not the same as "it answered": the text may still be empty, and that
|
|
@@ -25,6 +25,8 @@
|
|
|
25
25
|
* in every consumer that has not handled it — which is what makes the drift that
|
|
26
26
|
* caused the bug impossible rather than merely fixed.
|
|
27
27
|
*/
|
|
28
|
+
const _kindsAreKills = true;
|
|
29
|
+
void _kindsAreKills;
|
|
28
30
|
/**
|
|
29
31
|
* The ordered ladder. FIRST MATCH WINS — row order IS the precedence, and it is
|
|
30
32
|
* the only statement of it in the codebase.
|
|
@@ -51,21 +53,30 @@
|
|
|
51
53
|
* 7. `aborted` — no specific cause survived, so this really is a cancel.
|
|
52
54
|
* 8. `exit` — a plain non-zero exit with no kill behind it: a crash.
|
|
53
55
|
*/
|
|
54
|
-
const FAILURE_RULES = [
|
|
55
|
-
r => (r.stalled === true ? { kind: 'stalled' } : null),
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
56
|
+
export const FAILURE_RULES = [
|
|
57
|
+
{ id: 'stalled', match: r => (r.stalled === true ? { kind: 'stalled' } : null) },
|
|
58
|
+
{
|
|
59
|
+
id: 'command-timeout',
|
|
60
|
+
match: r => r.commandTimedOut ?
|
|
61
|
+
{
|
|
62
|
+
kind: 'command-timeout',
|
|
63
|
+
toolName: r.commandTimedOut.toolName,
|
|
64
|
+
timeoutMs: r.commandTimedOut.timeoutMs
|
|
65
|
+
}
|
|
66
|
+
: null
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
id: 'stream-stall',
|
|
70
|
+
match: r => r.streamStalled ? { kind: 'stream-stall', idleMs: r.streamStalled.idleMs } : null
|
|
71
|
+
},
|
|
72
|
+
{ id: 'worker-timeout', match: r => (r.timedOut === true ? { kind: 'worker-timeout' } : null) },
|
|
73
|
+
{ id: 'loop', match: r => (r.loopHit ? { kind: 'loop', hit: r.loopHit } : null) },
|
|
74
|
+
{
|
|
75
|
+
id: 'leaked-tool-call',
|
|
76
|
+
match: r => r.leakedToolCall ? { kind: 'leaked-tool-call', text: String(r.leakedToolCall) } : null
|
|
77
|
+
},
|
|
78
|
+
{ id: 'aborted', match: r => (r.aborted ? { kind: 'aborted' } : null) },
|
|
79
|
+
{ id: 'exit', match: r => (r.exitCode !== 0 ? { kind: 'exit', code: r.exitCode } : null) }
|
|
69
80
|
];
|
|
70
81
|
/**
|
|
71
82
|
* Classify a finished child. Returns `undefined` when nothing killed it —
|
|
@@ -74,7 +85,7 @@ const FAILURE_RULES = [
|
|
|
74
85
|
*/
|
|
75
86
|
export function classifyWorkerFailure(r) {
|
|
76
87
|
for (const rule of FAILURE_RULES) {
|
|
77
|
-
const hit = rule(r);
|
|
88
|
+
const hit = rule.match(r);
|
|
78
89
|
if (hit)
|
|
79
90
|
return hit;
|
|
80
91
|
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The ROSTER of ways a worker child can die, and what each one implies.
|
|
3
|
+
*
|
|
4
|
+
* WHY IT EXISTS. One kill cause was named in six unlinked places: a
|
|
5
|
+
* `RunWorkerInput` guard option, a `RunWorkerResult` field, the
|
|
6
|
+
* `WorkerRestartReason` union, a `RESTART_RULES` row, the `CARRY_FORWARD_REASONS`
|
|
7
|
+
* set, and a `FAILURE_RULES` row. Adding one meant six coordinated edits, and
|
|
8
|
+
* only two of them failed to compile if you skipped one. That is not
|
|
9
|
+
* hypothetical: `worker-failure.ts`'s own header records the bug it cost —
|
|
10
|
+
* *"`streamStalled` was added to the result and to `finalAttemptFailed`, but the
|
|
11
|
+
* enforce ladder never grew an arm for it, so an enforcement child killed for a
|
|
12
|
+
* hung model stream fell all the way through to `if (aborted) return
|
|
13
|
+
* USER_CANCELLED`"*. That fix closed the READER side. This closes the author
|
|
14
|
+
* side.
|
|
15
|
+
*
|
|
16
|
+
* WHAT IS AND IS NOT UNIFIED. The roster is one table. The two ORDERINGS stay
|
|
17
|
+
* two, because they genuinely disagree and each says so in its own prose: the
|
|
18
|
+
* restart ladder puts `loop` first (its hint is the most specific thing to tell a
|
|
19
|
+
* re-spawn), and the failure ladder puts `stalled` first (the diagnosis most
|
|
20
|
+
* easily lost behind the `aborted` every kill path sets). Folding two
|
|
21
|
+
* precedences into one row type would need an escape hatch per row — the same
|
|
22
|
+
* objection that got a `WriteGuard` row table rejected. What the orderings gain
|
|
23
|
+
* here is that neither can name a cause with no row, nor silently omit one.
|
|
24
|
+
*
|
|
25
|
+
* Not every cause appears in both ladders, and that asymmetry is real:
|
|
26
|
+
* `connection-error` is restartable but is reported as a `modelError`, never as a
|
|
27
|
+
* kill; `stalled`, `aborted` and `exit` end an attempt outright and no hint would
|
|
28
|
+
* help.
|
|
29
|
+
*/
|
|
30
|
+
/** Every way a worker attempt can end other than by answering. */
|
|
31
|
+
export type WorkerKillId = 'stalled' | 'command-timeout' | 'stream-stall' | 'worker-timeout' | 'connection-error' | 'loop' | 'leaked-tool-call' | 'aborted' | 'exit';
|
|
32
|
+
export interface WorkerKill {
|
|
33
|
+
id: WorkerKillId;
|
|
34
|
+
/**
|
|
35
|
+
* The `RunWorkerResult` field that reports this cause on the FINAL attempt,
|
|
36
|
+
* or null when the cause never reaches the result under its own name
|
|
37
|
+
* (`connection-error` arrives as `modelError`; `aborted` and `exit` are the
|
|
38
|
+
* generic fields every kill path also sets).
|
|
39
|
+
*
|
|
40
|
+
* A string rather than `keyof RunWorkerResult` so this module stays free of
|
|
41
|
+
* pi-worker-core's import graph; `worker-kill.test.ts` checks it against the
|
|
42
|
+
* real interface.
|
|
43
|
+
*/
|
|
44
|
+
resultField: string | null;
|
|
45
|
+
/**
|
|
46
|
+
* Is a killed attempt's partial output worth carrying into the next one?
|
|
47
|
+
*
|
|
48
|
+
* A clock kill, a hung tool, an idle stream and a dropped socket all discard
|
|
49
|
+
* work the model genuinely did. A loop kill and a leaked tool call do not —
|
|
50
|
+
* the first is by definition the same call repeated, the second is malformed
|
|
51
|
+
* protocol text, and replaying either would feed the failure back to itself.
|
|
52
|
+
*/
|
|
53
|
+
carryForward: boolean;
|
|
54
|
+
/** Does the restart ladder have a rule for this cause? */
|
|
55
|
+
restartable: boolean;
|
|
56
|
+
/** Does this cause reach a consumer as a `WorkerFailure`? */
|
|
57
|
+
reported: boolean;
|
|
58
|
+
}
|
|
59
|
+
export declare const WORKER_KILLS: readonly WorkerKill[];
|
|
60
|
+
/** Look one cause up. `undefined` only for an id with no row, which the suite forbids. */
|
|
61
|
+
export declare function workerKill(id: WorkerKillId): WorkerKill | undefined;
|
|
62
|
+
/**
|
|
63
|
+
* The restart ladder's precedence, as ids. `RESTART_RULES` must be exactly this,
|
|
64
|
+
* in this order.
|
|
65
|
+
*
|
|
66
|
+
* `loop` leads: its hint names the offending call, which is the most useful thing
|
|
67
|
+
* to tell a re-spawn. The two watchdogs come before the wall clock because each
|
|
68
|
+
* is the narrower diagnosis, and they cannot be confused with it — a watchdog
|
|
69
|
+
* kill leaves the worker's own timeout flag false.
|
|
70
|
+
*/
|
|
71
|
+
export declare const RESTART_ORDER: readonly ["loop", "command-timeout", "stream-stall", "worker-timeout", "connection-error", "leaked-tool-call"];
|
|
72
|
+
/**
|
|
73
|
+
* The failure ladder's precedence, as ids. `FAILURE_RULES` must be exactly this,
|
|
74
|
+
* in this order.
|
|
75
|
+
*
|
|
76
|
+
* DIFFERENT from `RESTART_ORDER`, deliberately. Every kill path also sets
|
|
77
|
+
* `aborted` and a non-zero exit, so the specific causes must all be matched
|
|
78
|
+
* before the two generic ones or a dead backend is reported as "you cancelled".
|
|
79
|
+
* `stalled` leads because it is both the most specific diagnosis and the one most
|
|
80
|
+
* easily lost.
|
|
81
|
+
*/
|
|
82
|
+
export declare const FAILURE_ORDER: readonly ["stalled", "command-timeout", "stream-stall", "worker-timeout", "loop", "leaked-tool-call", "aborted", "exit"];
|
|
83
|
+
/** The causes whose partial output is worth keeping. Derived, never hand-kept. */
|
|
84
|
+
export declare const CARRY_FORWARD_IDS: ReadonlySet<WorkerKillId>;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The ROSTER of ways a worker child can die, and what each one implies.
|
|
3
|
+
*
|
|
4
|
+
* WHY IT EXISTS. One kill cause was named in six unlinked places: a
|
|
5
|
+
* `RunWorkerInput` guard option, a `RunWorkerResult` field, the
|
|
6
|
+
* `WorkerRestartReason` union, a `RESTART_RULES` row, the `CARRY_FORWARD_REASONS`
|
|
7
|
+
* set, and a `FAILURE_RULES` row. Adding one meant six coordinated edits, and
|
|
8
|
+
* only two of them failed to compile if you skipped one. That is not
|
|
9
|
+
* hypothetical: `worker-failure.ts`'s own header records the bug it cost —
|
|
10
|
+
* *"`streamStalled` was added to the result and to `finalAttemptFailed`, but the
|
|
11
|
+
* enforce ladder never grew an arm for it, so an enforcement child killed for a
|
|
12
|
+
* hung model stream fell all the way through to `if (aborted) return
|
|
13
|
+
* USER_CANCELLED`"*. That fix closed the READER side. This closes the author
|
|
14
|
+
* side.
|
|
15
|
+
*
|
|
16
|
+
* WHAT IS AND IS NOT UNIFIED. The roster is one table. The two ORDERINGS stay
|
|
17
|
+
* two, because they genuinely disagree and each says so in its own prose: the
|
|
18
|
+
* restart ladder puts `loop` first (its hint is the most specific thing to tell a
|
|
19
|
+
* re-spawn), and the failure ladder puts `stalled` first (the diagnosis most
|
|
20
|
+
* easily lost behind the `aborted` every kill path sets). Folding two
|
|
21
|
+
* precedences into one row type would need an escape hatch per row — the same
|
|
22
|
+
* objection that got a `WriteGuard` row table rejected. What the orderings gain
|
|
23
|
+
* here is that neither can name a cause with no row, nor silently omit one.
|
|
24
|
+
*
|
|
25
|
+
* Not every cause appears in both ladders, and that asymmetry is real:
|
|
26
|
+
* `connection-error` is restartable but is reported as a `modelError`, never as a
|
|
27
|
+
* kill; `stalled`, `aborted` and `exit` end an attempt outright and no hint would
|
|
28
|
+
* help.
|
|
29
|
+
*/
|
|
30
|
+
export const WORKER_KILLS = [
|
|
31
|
+
{
|
|
32
|
+
id: 'stalled',
|
|
33
|
+
resultField: 'stalled',
|
|
34
|
+
carryForward: false,
|
|
35
|
+
restartable: false,
|
|
36
|
+
reported: true
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
id: 'command-timeout',
|
|
40
|
+
resultField: 'commandTimedOut',
|
|
41
|
+
carryForward: true,
|
|
42
|
+
restartable: true,
|
|
43
|
+
reported: true
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
id: 'stream-stall',
|
|
47
|
+
resultField: 'streamStalled',
|
|
48
|
+
carryForward: true,
|
|
49
|
+
restartable: true,
|
|
50
|
+
reported: true
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
id: 'worker-timeout',
|
|
54
|
+
resultField: 'timedOut',
|
|
55
|
+
carryForward: true,
|
|
56
|
+
restartable: true,
|
|
57
|
+
reported: true
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
id: 'connection-error',
|
|
61
|
+
resultField: null,
|
|
62
|
+
carryForward: true,
|
|
63
|
+
restartable: true,
|
|
64
|
+
reported: false
|
|
65
|
+
},
|
|
66
|
+
{ id: 'loop', resultField: 'loopHit', carryForward: false, restartable: true, reported: true },
|
|
67
|
+
{
|
|
68
|
+
id: 'leaked-tool-call',
|
|
69
|
+
resultField: 'leakedToolCall',
|
|
70
|
+
carryForward: false,
|
|
71
|
+
restartable: true,
|
|
72
|
+
reported: true
|
|
73
|
+
},
|
|
74
|
+
{ id: 'aborted', resultField: null, carryForward: false, restartable: false, reported: true },
|
|
75
|
+
{ id: 'exit', resultField: null, carryForward: false, restartable: false, reported: true }
|
|
76
|
+
];
|
|
77
|
+
/** Look one cause up. `undefined` only for an id with no row, which the suite forbids. */
|
|
78
|
+
export function workerKill(id) {
|
|
79
|
+
return WORKER_KILLS.find(k => k.id === id);
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* The restart ladder's precedence, as ids. `RESTART_RULES` must be exactly this,
|
|
83
|
+
* in this order.
|
|
84
|
+
*
|
|
85
|
+
* `loop` leads: its hint names the offending call, which is the most useful thing
|
|
86
|
+
* to tell a re-spawn. The two watchdogs come before the wall clock because each
|
|
87
|
+
* is the narrower diagnosis, and they cannot be confused with it — a watchdog
|
|
88
|
+
* kill leaves the worker's own timeout flag false.
|
|
89
|
+
*/
|
|
90
|
+
export const RESTART_ORDER = [
|
|
91
|
+
'loop',
|
|
92
|
+
'command-timeout',
|
|
93
|
+
'stream-stall',
|
|
94
|
+
'worker-timeout',
|
|
95
|
+
'connection-error',
|
|
96
|
+
'leaked-tool-call'
|
|
97
|
+
// `as const satisfies`, not an annotation: `WorkerRestartReason` is
|
|
98
|
+
// `(typeof RESTART_ORDER)[number]`, and a `readonly WorkerKillId[]`
|
|
99
|
+
// annotation collapses that to the whole `WorkerKillId` union — which would
|
|
100
|
+
// let `noteRestart('aborted')` compile for a cause the restart ladder has no
|
|
101
|
+
// rule for. `satisfies` keeps the membership check without the widening.
|
|
102
|
+
];
|
|
103
|
+
/**
|
|
104
|
+
* The failure ladder's precedence, as ids. `FAILURE_RULES` must be exactly this,
|
|
105
|
+
* in this order.
|
|
106
|
+
*
|
|
107
|
+
* DIFFERENT from `RESTART_ORDER`, deliberately. Every kill path also sets
|
|
108
|
+
* `aborted` and a non-zero exit, so the specific causes must all be matched
|
|
109
|
+
* before the two generic ones or a dead backend is reported as "you cancelled".
|
|
110
|
+
* `stalled` leads because it is both the most specific diagnosis and the one most
|
|
111
|
+
* easily lost.
|
|
112
|
+
*/
|
|
113
|
+
export const FAILURE_ORDER = [
|
|
114
|
+
'stalled',
|
|
115
|
+
'command-timeout',
|
|
116
|
+
'stream-stall',
|
|
117
|
+
'worker-timeout',
|
|
118
|
+
'loop',
|
|
119
|
+
'leaked-tool-call',
|
|
120
|
+
'aborted',
|
|
121
|
+
'exit'
|
|
122
|
+
];
|
|
123
|
+
/** The causes whose partial output is worth keeping. Derived, never hand-kept. */
|
|
124
|
+
export const CARRY_FORWARD_IDS = new Set(WORKER_KILLS.filter(k => k.carryForward).map(k => k.id));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mjasnikovs/pi-task",
|
|
3
|
-
"version": "0.38.
|
|
3
|
+
"version": "0.38.24",
|
|
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",
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Child NAME → reasoning group, for every child that goes through
|
|
3
|
-
* `runPhaseChild` / `runPlanningChild`.
|
|
4
|
-
*
|
|
5
|
-
* WHY KEYED ON THE NAME
|
|
6
|
-
* ---------------------
|
|
7
|
-
* The name is the only identifier in scope at all three spawn paths (phases,
|
|
8
|
-
* /task-auto planning, /task-plan), it is what the loader and the debug trail
|
|
9
|
-
* already print, and it is the one thing a reader can check against the phase
|
|
10
|
-
* list without following the call graph. Threading a group parameter through
|
|
11
|
-
* `PhaseDeps` / `AutoDeps` instead would touch both orchestrators' dep bags to
|
|
12
|
-
* express something the call site already says out loud.
|
|
13
|
-
*
|
|
14
|
-
* AN UNMAPPED NAME IS A BUILD FAILURE, not a silent `inherit`.
|
|
15
|
-
* `reasoning-groups.test.ts` scans every literal child name in src/ and fails if
|
|
16
|
-
* it is missing here. A defaulting lookup would let a phase added next year opt
|
|
17
|
-
* itself out of a measured setting without anyone deciding to — which is exactly
|
|
18
|
-
* how `/no_think` ended up applied to eight prompts and read by none of them.
|
|
19
|
-
*
|
|
20
|
-
* The gate, research and extraction groups are NOT here: those children reach the
|
|
21
|
-
* model through `runWorker` / `focusedChildArgs`, where the group is a property
|
|
22
|
-
* of the call site rather than of a name, and is passed directly.
|
|
23
|
-
*/
|
|
24
|
-
import type { ReasoningGroup } from '../config/reasoning.js';
|
|
25
|
-
export declare const REASONING_GROUP_BY_CHILD: Readonly<Record<string, ReasoningGroup>>;
|
|
26
|
-
/**
|
|
27
|
-
* The group a named child belongs to.
|
|
28
|
-
*
|
|
29
|
-
* Returns `undefined` for a name the table does not know, and the CALLER decides
|
|
30
|
-
* what that means. `runPhaseChild` treats it as `inherit` — a child that reaches
|
|
31
|
-
* the model with today's argv is always safe — while the test treats it as a
|
|
32
|
-
* failure. That split is deliberate: the guard belongs at build time, where
|
|
33
|
-
* someone can fix it, not at run time, where it would abort a user's task over a
|
|
34
|
-
* missing table row.
|
|
35
|
-
*/
|
|
36
|
-
export declare function reasoningGroupForChild(name: string): ReasoningGroup | undefined;
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
export const REASONING_GROUP_BY_CHILD = {
|
|
2
|
-
// ── phase: task/phases.ts + task/title-label.ts ──────────────────────────
|
|
3
|
-
refine: 'phase',
|
|
4
|
-
'verify-tooling': 'phase',
|
|
5
|
-
'grill-auto': 'phase',
|
|
6
|
-
'grill-gen': 'phase',
|
|
7
|
-
compose: 'phase',
|
|
8
|
-
critique: 'phase',
|
|
9
|
-
'critique-triage': 'phase',
|
|
10
|
-
'compress-label': 'phase',
|
|
11
|
-
// ── planning: task/auto-orchestrator.ts ──────────────────────────────────
|
|
12
|
-
'clarify-triage': 'planning',
|
|
13
|
-
'auto-clarify': 'planning',
|
|
14
|
-
'auto-decompose': 'planning',
|
|
15
|
-
'requirement-extract': 'planning',
|
|
16
|
-
'decompose-coverage': 'planning',
|
|
17
|
-
'coverage-map': 'planning',
|
|
18
|
-
'contract-extract': 'planning',
|
|
19
|
-
'launch-extract': 'planning',
|
|
20
|
-
// ── plan: task/plan-orchestrator.ts ──────────────────────────────────────
|
|
21
|
-
'plan-question': 'plan',
|
|
22
|
-
'plan-answer': 'plan'
|
|
23
|
-
};
|
|
24
|
-
/**
|
|
25
|
-
* The group a named child belongs to.
|
|
26
|
-
*
|
|
27
|
-
* Returns `undefined` for a name the table does not know, and the CALLER decides
|
|
28
|
-
* what that means. `runPhaseChild` treats it as `inherit` — a child that reaches
|
|
29
|
-
* the model with today's argv is always safe — while the test treats it as a
|
|
30
|
-
* failure. That split is deliberate: the guard belongs at build time, where
|
|
31
|
-
* someone can fix it, not at run time, where it would abort a user's task over a
|
|
32
|
-
* missing table row.
|
|
33
|
-
*/
|
|
34
|
-
export function reasoningGroupForChild(name) {
|
|
35
|
-
return REASONING_GROUP_BY_CHILD[name];
|
|
36
|
-
}
|