@mjasnikovs/pi-task 0.38.30 → 0.38.32
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 -1
- package/dist/config/config.d.ts +16 -2
- package/dist/config/config.js +7 -2
- package/dist/config/group-args.d.ts +52 -0
- package/dist/config/group-args.js +110 -0
- package/dist/config/group-models.d.ts +88 -0
- package/dist/config/group-models.js +117 -0
- package/dist/config/groups.d.ts +76 -0
- package/dist/config/groups.js +110 -0
- package/dist/config/option-picker.d.ts +42 -0
- package/dist/config/option-picker.js +73 -0
- package/dist/config/reasoning.d.ts +22 -63
- package/dist/config/reasoning.js +37 -108
- package/dist/config/register.d.ts +98 -12
- package/dist/config/register.js +228 -23
- package/dist/index.js +4 -0
- package/dist/remote/push.js +1 -7
- package/dist/shared/command-watchdog.d.ts +63 -0
- package/dist/shared/command-watchdog.js +87 -0
- package/dist/shared/data-home.d.ts +8 -0
- package/dist/shared/data-home.js +14 -0
- package/dist/shared/model-endpoint.d.ts +53 -0
- package/dist/shared/model-endpoint.js +98 -2
- package/dist/shared/reasoning-capability.d.ts +25 -5
- package/dist/shared/reasoning-capability.js +18 -9
- package/dist/task/auto-orchestrator.js +14 -3
- package/dist/task/child-runner.d.ts +92 -15
- package/dist/task/child-runner.js +303 -66
- package/dist/task/context-usage.d.ts +46 -0
- package/dist/task/context-usage.js +41 -0
- package/dist/task/failure-classifier.js +24 -1
- package/dist/task/gate-child.d.ts +15 -4
- package/dist/task/gate-child.js +2 -2
- package/dist/task/gate-deps.js +7 -2
- package/dist/task/implementation-guards.d.ts +26 -0
- package/dist/task/implementation-guards.js +177 -0
- package/dist/task/implementation-hold.d.ts +118 -0
- package/dist/task/implementation-hold.js +165 -0
- package/dist/task/implementation-turn.d.ts +5 -0
- package/dist/task/implementation-turn.js +12 -1
- package/dist/task/loop-detector.d.ts +18 -0
- package/dist/task/loop-detector.js +22 -2
- package/dist/task/model-hold-stash.d.ts +43 -0
- package/dist/task/model-hold-stash.js +70 -0
- package/dist/task/orchestrator.d.ts +18 -5
- package/dist/task/orchestrator.js +63 -6
- package/dist/task/phases.js +18 -5
- package/dist/task/research-worker.d.ts +2 -2
- package/dist/task/research-worker.js +1 -1
- package/dist/workers/docs-core.js +2 -2
- package/dist/workers/docs-lookup.d.ts +4 -3
- package/dist/workers/docs-lookup.js +1 -1
- package/dist/workers/fetch-core.js +2 -2
- package/dist/workers/focused-extractor.d.ts +6 -4
- package/dist/workers/focused-extractor.js +17 -5
- package/dist/workers/index.js +2 -0
- package/dist/workers/model-warning.d.ts +69 -0
- package/dist/workers/model-warning.js +113 -0
- package/dist/workers/pi-worker-core.d.ts +9 -38
- package/dist/workers/pi-worker-core.js +8 -86
- package/dist/workers/pi-worker-docs.js +2 -2
- package/dist/workers/pi-worker.js +4 -4
- package/dist/workers/reasoning-warning.d.ts +17 -9
- package/dist/workers/reasoning-warning.js +69 -22
- package/dist/workers/single-read-guard.d.ts +6 -6
- package/dist/workers/single-read-guard.js +8 -8
- package/dist/workers/worker-profiles.d.ts +11 -3
- package/dist/workers/worker-profiles.js +33 -1
- package/package.json +1 -1
- package/dist/config/reasoning-args.d.ts +0 -23
- package/dist/config/reasoning-args.js +0 -28
- package/dist/task/implementation-thinking.d.ts +0 -56
- package/dist/task/implementation-thinking.js +0 -32
|
@@ -10,10 +10,15 @@
|
|
|
10
10
|
* while it runs, so only "no output AND the endpoint does not answer" counts as
|
|
11
11
|
* a dead backend.
|
|
12
12
|
*
|
|
13
|
-
* Discovery is generic —
|
|
14
|
-
* models.json `providers.*.baseUrl`, with no provider or server name hardcoded.
|
|
13
|
+
* Discovery is generic — no provider or server name is hardcoded anywhere here.
|
|
15
14
|
* No discoverable endpoint means nothing to probe, and the guard then NEVER
|
|
16
15
|
* kills: a child on a backend we cannot see gets the benefit of the doubt.
|
|
16
|
+
*
|
|
17
|
+
* `childModelEndpoints` is what the guards call; `discoverModelEndpoints` is its
|
|
18
|
+
* last resort. The difference is the point. Discovery answers "every endpoint on
|
|
19
|
+
* this machine", and `probeModelEndpoints` ORs over it, so one healthy provider
|
|
20
|
+
* says "reachable" for a child stranded on a dead one. Resolving the child's OWN
|
|
21
|
+
* model first turns that OR into a single exact question.
|
|
17
22
|
*/
|
|
18
23
|
import * as fs from 'node:fs';
|
|
19
24
|
import * as os from 'node:os';
|
|
@@ -33,6 +38,97 @@ export function discoverModelEndpoints(agentDir = path.join(os.homedir(), '.pi',
|
|
|
33
38
|
return [];
|
|
34
39
|
}
|
|
35
40
|
}
|
|
41
|
+
/** A `provider/id` spec as a ref, splitting on the FIRST slash. */
|
|
42
|
+
function specRef(spec) {
|
|
43
|
+
if (spec === undefined)
|
|
44
|
+
return undefined;
|
|
45
|
+
const i = spec.indexOf('/');
|
|
46
|
+
if (i <= 0 || i === spec.length - 1)
|
|
47
|
+
return undefined;
|
|
48
|
+
return { provider: spec.slice(0, i), id: spec.slice(i + 1) };
|
|
49
|
+
}
|
|
50
|
+
export function defaultModelRef(agentDir = path.join(os.homedir(), '.pi', 'agent')) {
|
|
51
|
+
try {
|
|
52
|
+
const j = JSON.parse(fs.readFileSync(path.join(agentDir, 'settings.json'), 'utf8'));
|
|
53
|
+
const provider = j.defaultProvider;
|
|
54
|
+
const id = j.defaultModel;
|
|
55
|
+
if (typeof provider !== 'string' || provider === '')
|
|
56
|
+
return undefined;
|
|
57
|
+
if (typeof id !== 'string' || id === '')
|
|
58
|
+
return undefined;
|
|
59
|
+
return { provider, id };
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* The base URL one model is served from, or `undefined` for "not configured on
|
|
67
|
+
* this machine".
|
|
68
|
+
*
|
|
69
|
+
* Two files, two shapes, and the difference is not cosmetic. `models.json` is
|
|
70
|
+
* hand-written and hangs `baseUrl` off the PROVIDER; `models-store.json` is the
|
|
71
|
+
* cached remote catalogue and hangs it off each MODEL — its provider objects are
|
|
72
|
+
* `{models, checkedAt, lastModified, etag}` with no `baseUrl` key at all. A
|
|
73
|
+
* reader that knows only the first shape sees one endpoint on a machine that has
|
|
74
|
+
* fifteen.
|
|
75
|
+
*
|
|
76
|
+
* `undefined` is the honest answer for a model pi serves from `@earendil-works/
|
|
77
|
+
* pi-ai`'s built-in catalogue, whose URLs live in that package rather than on
|
|
78
|
+
* disk. We do not import it — see the header of this file — so we say we do not
|
|
79
|
+
* know, and the caller declines to probe rather than probing something else.
|
|
80
|
+
*/
|
|
81
|
+
export function modelBaseUrl(ref, agentDir = path.join(os.homedir(), '.pi', 'agent')) {
|
|
82
|
+
const url = (v) => typeof v === 'string' && v.length > 0 ? v : undefined;
|
|
83
|
+
try {
|
|
84
|
+
const j = JSON.parse(fs.readFileSync(path.join(agentDir, 'models.json'), 'utf8'));
|
|
85
|
+
const p = j.providers?.[ref.provider];
|
|
86
|
+
if (p) {
|
|
87
|
+
const own = p.models?.find(m => m.id === ref.id);
|
|
88
|
+
return url(own?.baseUrl) ?? url(p.baseUrl);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
/* fall through to the store */
|
|
93
|
+
}
|
|
94
|
+
try {
|
|
95
|
+
const j = JSON.parse(fs.readFileSync(path.join(agentDir, 'models-store.json'), 'utf8'));
|
|
96
|
+
return url(j[ref.provider]?.models?.find(m => m.id === ref.id)?.baseUrl);
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
return undefined;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* What to probe on behalf of one child — the endpoint that child's own model
|
|
104
|
+
* uses, not every endpoint on the machine.
|
|
105
|
+
*
|
|
106
|
+
* The bug this closes is `probeModelEndpoints`'s `.some(Boolean)`: with a live
|
|
107
|
+
* cloud provider and a dead local one, the OR answers "reachable" and the stall
|
|
108
|
+
* guard is disarmed for a child that will never speak again. Handing it ONE url
|
|
109
|
+
* makes the OR a no-op and the verdict exact.
|
|
110
|
+
*
|
|
111
|
+
* `spec` is the child's OWN `provider/id`, as carried by its argv. `undefined`
|
|
112
|
+
* means the child carries no `--model` and will resolve pi's saved default,
|
|
113
|
+
* which is then the right thing to probe. Reading the saved default for a child
|
|
114
|
+
* that IS pinned asks about the wrong server in BOTH directions: it can kill a
|
|
115
|
+
* child whose own backend is healthy, and it can leave the guard disarmed for
|
|
116
|
+
* one whose backend is dead.
|
|
117
|
+
*
|
|
118
|
+
* Two escapes, both toward never killing:
|
|
119
|
+
* - model known, endpoint not on disk (a built-in provider) → `[]`, which
|
|
120
|
+
* `probeModelEndpoints` reads as reachable. We cannot see that server, so we
|
|
121
|
+
* do not get a vote. Probing some OTHER provider's url instead would import a
|
|
122
|
+
* false positive, which is the one thing today's blind OR never does.
|
|
123
|
+
* - no readable default → today's behaviour, unchanged.
|
|
124
|
+
*/
|
|
125
|
+
export function childModelEndpoints(spec, agentDir = path.join(os.homedir(), '.pi', 'agent')) {
|
|
126
|
+
const ref = specRef(spec) ?? defaultModelRef(agentDir);
|
|
127
|
+
if (!ref)
|
|
128
|
+
return discoverModelEndpoints(agentDir);
|
|
129
|
+
const url = modelBaseUrl(ref, agentDir);
|
|
130
|
+
return url === undefined ? [] : [url];
|
|
131
|
+
}
|
|
36
132
|
/**
|
|
37
133
|
* true → at least one endpoint ANSWERED. Any HTTP status counts, because the
|
|
38
134
|
* question is liveness, not correctness: probing a path that 404s still returns
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
* line-for-line identical to it, ladder included. Nothing here imports pi-ai, so
|
|
31
31
|
* an upstream change will not fail a test — the two have to be re-compared.
|
|
32
32
|
*/
|
|
33
|
-
import { type
|
|
33
|
+
import { type ChildGroup, type GroupSetting } from '../config/reasoning.js';
|
|
34
34
|
/**
|
|
35
35
|
* pi's own level ladder, in order — the same seven names, in the same sequence,
|
|
36
36
|
* as `EXTENDED_THINKING_LEVELS` in pi-ai's models.js. The order IS the algorithm:
|
|
@@ -67,16 +67,36 @@ export declare function supportedThinkingLevels(model: ReasoningModelFacts): Lad
|
|
|
67
67
|
* the model supports it — which is what makes the inequality a mismatch test.
|
|
68
68
|
*/
|
|
69
69
|
export declare function clampToModel(model: ReasoningModelFacts, level: LadderLevel): LadderLevel;
|
|
70
|
-
/** One group whose configured setting the
|
|
70
|
+
/** One group whose configured setting the model it runs on will not honour. */
|
|
71
71
|
export interface ReasoningMismatch {
|
|
72
|
-
group:
|
|
72
|
+
group: ChildGroup;
|
|
73
|
+
/**
|
|
74
|
+
* The model THIS GROUP runs on. Per-item, not per-warning, because groups no
|
|
75
|
+
* longer share one model: a line that opens `model "X" will not run …` while
|
|
76
|
+
* listing groups that run on Y is itself a lie about what it checked.
|
|
77
|
+
*/
|
|
78
|
+
modelName: string;
|
|
73
79
|
/** What /task-config says. Never `inherit` — an inherited group asks for nothing. */
|
|
74
80
|
wanted: LadderLevel;
|
|
75
81
|
/** What pi will send instead. */
|
|
76
82
|
actual: LadderLevel;
|
|
77
83
|
}
|
|
84
|
+
/** What a group runs on, as much of it as this check needs. */
|
|
85
|
+
export interface GroupModelFacts extends ReasoningModelFacts {
|
|
86
|
+
/** What to call it in the warning. */
|
|
87
|
+
name: string;
|
|
88
|
+
/** Where it is served from, for the `/props` probe. Absent ⇒ not probeable. */
|
|
89
|
+
baseUrl?: string;
|
|
90
|
+
}
|
|
78
91
|
/**
|
|
79
|
-
* Every group whose setting the model will silently change.
|
|
92
|
+
* Every group whose setting the model IT RUNS ON will silently change.
|
|
93
|
+
*
|
|
94
|
+
* `modelFor` is a FUNCTION rather than a `Record`, for two reasons: a record
|
|
95
|
+
* would build eleven identical entries for the overwhelmingly common
|
|
96
|
+
* all-`inherit` case, and a function is drivable from a test with two literals.
|
|
97
|
+
* It answers `undefined` for a group whose model cannot be resolved — nothing is
|
|
98
|
+
* reported for those, because the run degrades to the session default and the
|
|
99
|
+
* separate model hint is what names them.
|
|
80
100
|
*
|
|
81
101
|
* `inherit` groups are skipped entirely, because an inherited group asks for
|
|
82
102
|
* nothing. That is not the same as a quiet default: the shipped table is mostly
|
|
@@ -91,4 +111,4 @@ export interface ReasoningMismatch {
|
|
|
91
111
|
* comparison. Warning about one direction while staying silent about the other
|
|
92
112
|
* would ship this feature unable to see its own failure mode.
|
|
93
113
|
*/
|
|
94
|
-
export declare function reasoningMismatches(
|
|
114
|
+
export declare function reasoningMismatches(modelFor: (group: ChildGroup) => GroupModelFacts | undefined, levels: Readonly<Record<ChildGroup, GroupSetting>>): ReasoningMismatch[];
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
* line-for-line identical to it, ladder included. Nothing here imports pi-ai, so
|
|
31
31
|
* an upstream change will not fail a test — the two have to be re-compared.
|
|
32
32
|
*/
|
|
33
|
-
import {
|
|
33
|
+
import { CHILD_GROUPS } from '../config/reasoning.js';
|
|
34
34
|
/**
|
|
35
35
|
* pi's own level ladder, in order — the same seven names, in the same sequence,
|
|
36
36
|
* as `EXTENDED_THINKING_LEVELS` in pi-ai's models.js. The order IS the algorithm:
|
|
@@ -87,7 +87,14 @@ export function clampToModel(model, level) {
|
|
|
87
87
|
return available[0] ?? 'off';
|
|
88
88
|
}
|
|
89
89
|
/**
|
|
90
|
-
* Every group whose setting the model will silently change.
|
|
90
|
+
* Every group whose setting the model IT RUNS ON will silently change.
|
|
91
|
+
*
|
|
92
|
+
* `modelFor` is a FUNCTION rather than a `Record`, for two reasons: a record
|
|
93
|
+
* would build eleven identical entries for the overwhelmingly common
|
|
94
|
+
* all-`inherit` case, and a function is drivable from a test with two literals.
|
|
95
|
+
* It answers `undefined` for a group whose model cannot be resolved — nothing is
|
|
96
|
+
* reported for those, because the run degrades to the session default and the
|
|
97
|
+
* separate model hint is what names them.
|
|
91
98
|
*
|
|
92
99
|
* `inherit` groups are skipped entirely, because an inherited group asks for
|
|
93
100
|
* nothing. That is not the same as a quiet default: the shipped table is mostly
|
|
@@ -102,19 +109,21 @@ export function clampToModel(model, level) {
|
|
|
102
109
|
* comparison. Warning about one direction while staying silent about the other
|
|
103
110
|
* would ship this feature unable to see its own failure mode.
|
|
104
111
|
*/
|
|
105
|
-
export function reasoningMismatches(
|
|
106
|
-
// No model resolved yet (session still starting, or none selected): say
|
|
107
|
-
// nothing. A warning naming no model is noise, not information.
|
|
108
|
-
if (!model)
|
|
109
|
-
return [];
|
|
112
|
+
export function reasoningMismatches(modelFor, levels) {
|
|
110
113
|
const out = [];
|
|
111
|
-
for (const group of
|
|
114
|
+
for (const group of CHILD_GROUPS) {
|
|
112
115
|
const wanted = levels[group];
|
|
113
116
|
if (wanted === 'inherit')
|
|
114
117
|
continue;
|
|
118
|
+
// No model resolved for this group (session still starting, none
|
|
119
|
+
// selected, or a spec this machine cannot resolve): say nothing. A
|
|
120
|
+
// warning naming no model is noise, not information.
|
|
121
|
+
const model = modelFor(group);
|
|
122
|
+
if (!model)
|
|
123
|
+
continue;
|
|
115
124
|
const actual = clampToModel(model, wanted);
|
|
116
125
|
if (actual !== wanted)
|
|
117
|
-
out.push({ group, wanted, actual });
|
|
126
|
+
out.push({ group, modelName: model.name, wanted, actual });
|
|
118
127
|
}
|
|
119
128
|
return out;
|
|
120
129
|
}
|
|
@@ -22,7 +22,7 @@ import { drainRepairQueue, mergeRepairCandidates, planHasRepairFor, parseRepairT
|
|
|
22
22
|
import { writeTaskFile, readTaskFile, updateTaskFrontMatter, taskFilePath, tasksDir } from './task-io.js';
|
|
23
23
|
import { readTextFile } from '../shared/fs-text.js';
|
|
24
24
|
import { findPhantomImports, rewritePhantomSpecifiers } from '../workers/phantom-imports.js';
|
|
25
|
-
import { prependHint, USER_CANCELLED } from './child-runner.js';
|
|
25
|
+
import { isFatalChildCause, prependHint, USER_CANCELLED } from './child-runner.js';
|
|
26
26
|
import { requestCancel, resetCancel, isCancelRequested, cancelCheckpoint } from './cancel-points.js';
|
|
27
27
|
import { withRun, announceTerminal } from './run-bracket.js';
|
|
28
28
|
import { refineExistingFilesBlock, SINGLE_READ_EXTENSION_PATH } from './phases.js';
|
|
@@ -493,8 +493,15 @@ export async function orientFeature(cwd, feature, deps) {
|
|
|
493
493
|
reqEntries = capRequirements(reqEntries, passages, featureForModel);
|
|
494
494
|
logPlanDebug(cwd, `requirement extraction: ${reqEntries.length} grounded requirement(s) kept`);
|
|
495
495
|
}
|
|
496
|
-
catch {
|
|
497
|
-
//
|
|
496
|
+
catch (e) {
|
|
497
|
+
// Best-effort covers a child that answered badly. It must NOT cover a user
|
|
498
|
+
// ESC or a dead backend: planning would continue on an EMPTY ledger, moving
|
|
499
|
+
// the granularity floor and shipping a degraded plan instead of a cancel or
|
|
500
|
+
// a failure. Same rule as verify-resolution.ts.
|
|
501
|
+
if (isFatalChildCause(e))
|
|
502
|
+
throw e;
|
|
503
|
+
// Best-effort, but not silent: nothing else records a guard kill here.
|
|
504
|
+
logPlanDebug(cwd, `requirement extraction: skipped — ${e.message}`);
|
|
498
505
|
}
|
|
499
506
|
// Granularity floor: without it the plan's task COUNT is set by an
|
|
500
507
|
// auto-resolved clarify line the user never sees, so the same spec and the same
|
|
@@ -1101,8 +1108,12 @@ function defaultDeps(ctx, cwd, signal, title) {
|
|
|
1101
1108
|
const status = new ChildStatus({ parentContextWindow });
|
|
1102
1109
|
const phaseDeps = {
|
|
1103
1110
|
cwd,
|
|
1111
|
+
// No task file, so appendLoopEvent swallows its ENOENT. Its docblock
|
|
1112
|
+
// allows that because "the kill is already reported through the debug
|
|
1113
|
+
// log" — which is why logDebug below is not optional here.
|
|
1104
1114
|
taskId: '',
|
|
1105
1115
|
signal,
|
|
1116
|
+
logDebug: msg => logPlanDebug(cwd, msg),
|
|
1106
1117
|
// IN-RUN thrash guard for the planning children: without it a decompose
|
|
1107
1118
|
// child can re-read its design document until it fills the whole context
|
|
1108
1119
|
// window, and never return. Every planning child
|
|
@@ -6,15 +6,14 @@
|
|
|
6
6
|
* for phase-level child pi invocations.
|
|
7
7
|
*/
|
|
8
8
|
import { type SpawnFn, type ContextSnapshot, type ToolCall, type LoopHit } from '../shared/child-process.js';
|
|
9
|
+
import { type CommandKill } from '../shared/command-watchdog.js';
|
|
10
|
+
import { type WorkerGuardPolicy } from '../workers/worker-profiles.js';
|
|
9
11
|
import type { DebugLine } from './debug-log.js';
|
|
10
12
|
import type { RunWorkerInput, RunWorkerResult } from '../workers/pi-worker-core.js';
|
|
11
13
|
import type { docsRaw, docsFocused } from '../workers/docs-core.js';
|
|
12
14
|
import type { fetchRaw, fetchFocused } from '../workers/fetch-core.js';
|
|
13
15
|
import type { npmVersionLookup } from '../workers/npm-version.js';
|
|
14
16
|
import type { SearchCoreInput, SearchCoreResult } from '../workers/search-core.js';
|
|
15
|
-
export declare const LOOP_WINDOW = 20;
|
|
16
|
-
export declare const LOOP_THRESHOLD = 5;
|
|
17
|
-
export declare const MAX_LOOP_RESTARTS = 2;
|
|
18
17
|
/**
|
|
19
18
|
* Optional wall-clock bound on ONE spawn of a phase child. DEFAULT: OFF.
|
|
20
19
|
*
|
|
@@ -50,6 +49,53 @@ export declare class PhaseTimeoutError extends Error {
|
|
|
50
49
|
readonly attempts: number;
|
|
51
50
|
constructor(childName: string, budgetMs: number, attempts: number);
|
|
52
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* The terminal error for a guard kill, or null when the child was not killed.
|
|
54
|
+
*
|
|
55
|
+
* Both spawn paths must ask. A kill reports `exitCode: 0` (child-process.ts uses
|
|
56
|
+
* `code ?? 0`, and a signal gives null), so a path that tests the exit code
|
|
57
|
+
* instead returns the truncated text as the phase's answer.
|
|
58
|
+
*/
|
|
59
|
+
export declare function guardKillError(name: string, r: PhaseRunResult, opts?: {
|
|
60
|
+
finalAttempt?: boolean;
|
|
61
|
+
}): Error | null;
|
|
62
|
+
/**
|
|
63
|
+
* The dead-backend probe killed a phase child on its LAST attempt.
|
|
64
|
+
*
|
|
65
|
+
* Reaching this means every attempt found no endpoint answering, not one. The
|
|
66
|
+
* single-probe verdict is not trusted on its own, because one sample cannot tell
|
|
67
|
+
* a dead server from a blip. Three failed probes cost ~15s; one wrong verdict
|
|
68
|
+
* costs the run.
|
|
69
|
+
*/
|
|
70
|
+
export declare class BackendDownError extends Error {
|
|
71
|
+
readonly childName: string;
|
|
72
|
+
constructor(childName: string);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* A phase child spent every attempt on a command that never returned. Its own
|
|
76
|
+
* class because the fix is in the SPEC, not the model's exploration: a VERIFY
|
|
77
|
+
* block naming an unbounded `dev` command re-hangs every attempt.
|
|
78
|
+
*/
|
|
79
|
+
export declare class CommandTimeoutError extends Error {
|
|
80
|
+
readonly childName: string;
|
|
81
|
+
readonly kill: CommandKill;
|
|
82
|
+
constructor(childName: string, kill: CommandKill);
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Causes a best-effort `catch` must NOT absorb.
|
|
86
|
+
*
|
|
87
|
+
* A phase child that merely answered badly should degrade — that is what those
|
|
88
|
+
* catches are for. These two are different in kind: the run is over either way,
|
|
89
|
+
* and swallowing them ships a half-built spec while every later phase dies
|
|
90
|
+
* against the same dead backend, or turns a user's ESC into silent progress.
|
|
91
|
+
* `failure-classifier.ts` has a verdict for both; a catch that eats them makes it
|
|
92
|
+
* unreachable.
|
|
93
|
+
*/
|
|
94
|
+
export declare function isFatalChildCause(e: unknown): boolean;
|
|
95
|
+
/**
|
|
96
|
+
* Retry budget is three attempts at 500ms/1s/2s — three requests over 3.5s, which
|
|
97
|
+
* is not a storm even against a throttle. pi's own ladder is three at 2s/4s/8s.
|
|
98
|
+
*/
|
|
53
99
|
export declare function isConnectionError(cause: string): boolean;
|
|
54
100
|
/** Exponential backoff before a connection-error retry: 500ms, 1s, 2s, …, so a
|
|
55
101
|
* brief saturation window can drain before we re-issue the request. */
|
|
@@ -63,15 +109,31 @@ export interface PhaseRunResult {
|
|
|
63
109
|
leakedToolCall?: string;
|
|
64
110
|
/** Set when the child's final turn failed with stopReason "error" (model/provider failure). */
|
|
65
111
|
modelError?: string;
|
|
112
|
+
/**
|
|
113
|
+
* Set when the per-command watchdog killed the child: one tool call outran
|
|
114
|
+
* `requestTimeoutMs`. RESTARTABLE (worker-kill.ts) — a hung command is a
|
|
115
|
+
* mistake the next attempt can be told not to repeat.
|
|
116
|
+
*/
|
|
117
|
+
commandKill?: CommandKill;
|
|
118
|
+
/**
|
|
119
|
+
* Set when the dead-backend probe killed the child: no output for the stall
|
|
120
|
+
* window AND the model endpoint unreachable. NOT restartable (worker-kill.ts):
|
|
121
|
+
* re-spawning against a backend that is down buys nothing.
|
|
122
|
+
*/
|
|
123
|
+
stalled?: boolean;
|
|
66
124
|
}
|
|
67
125
|
export declare function childArgs(tools: string, extensions?: readonly string[],
|
|
68
126
|
/**
|
|
69
|
-
*
|
|
70
|
-
* Resolved by the CALLER, never here
|
|
71
|
-
* ROLE, and this function is handed tools and extensions, not
|
|
72
|
-
* Omitted ⇒ byte-identical argv to the version before
|
|
127
|
+
* This child's group fragment: `--model` then `--thinking`, either half
|
|
128
|
+
* possibly absent. Resolved by the CALLER, never here — both are properties
|
|
129
|
+
* of the child's ROLE, and this function is handed tools and extensions, not
|
|
130
|
+
* a name. Omitted ⇒ byte-identical argv to the version before group profiles.
|
|
131
|
+
*
|
|
132
|
+
* ONE field rather than a `model` beside a `thinking`, because nothing may
|
|
133
|
+
* compose the two halves by hand: `groupChildArgs` is the only producer, so a
|
|
134
|
+
* doubled `--thinking` is unreachable rather than merely unlikely.
|
|
73
135
|
*/
|
|
74
|
-
|
|
136
|
+
groupArgs?: readonly string[]): string[];
|
|
75
137
|
export declare const USER_CANCELLED = "__user_cancelled__";
|
|
76
138
|
/**
|
|
77
139
|
* Run a child pi process with JSON event-stream output, loop detection, and
|
|
@@ -116,12 +178,27 @@ export interface ChildRun {
|
|
|
116
178
|
*/
|
|
117
179
|
contextWindow?: number;
|
|
118
180
|
/**
|
|
119
|
-
* The resolved
|
|
120
|
-
*
|
|
181
|
+
* The resolved argv fragment for this child's group — `--model` then
|
|
182
|
+
* `--thinking` — or `[]`/omitted to inherit both defaults as before.
|
|
183
|
+
*/
|
|
184
|
+
groupArgs?: readonly string[];
|
|
185
|
+
/**
|
|
186
|
+
* This attempt's per-command ceiling, already halved for prior hangs by the
|
|
187
|
+
* caller's strike loop. Omitted -> the `phase` row's full configured ceiling,
|
|
188
|
+
* which is the right value for a single-attempt caller.
|
|
121
189
|
*/
|
|
122
|
-
|
|
190
|
+
commandCeilingMs?: number;
|
|
123
191
|
}
|
|
124
|
-
|
|
192
|
+
/**
|
|
193
|
+
* The `phase` row of WORKER_PROFILES, resolved with this machine's config.
|
|
194
|
+
*
|
|
195
|
+
* Read here rather than at module load so a /task-config change reaches the next
|
|
196
|
+
* child, the same contract childBaseArgs already keeps. Both spawn paths in this
|
|
197
|
+
* file go through it, so the degraded final attempt cannot drift from the ordinary
|
|
198
|
+
* one — the mislabel class runDegradedFinalAttempt's own comment warns about.
|
|
199
|
+
*/
|
|
200
|
+
export declare function phasePolicy(): WorkerGuardPolicy;
|
|
201
|
+
export declare function runChild({ cwd, tools, prompt, signal, onLine, onContextUsage, onToolCall, spawn: spawnFn, extensions, onToolResult, contextWindow, groupArgs, commandCeilingMs }: ChildRun): Promise<PhaseRunResult>;
|
|
125
202
|
export interface PhaseDeps {
|
|
126
203
|
cwd: string;
|
|
127
204
|
taskId: string;
|
|
@@ -263,15 +340,15 @@ export type PhaseSeams = Omit<PhaseDeps, 'cwd' | 'taskId' | 'signal' | 'onChildO
|
|
|
263
340
|
* exit status describes our SIGTERM and says nothing about its verdict.
|
|
264
341
|
*/
|
|
265
342
|
/**
|
|
266
|
-
* The
|
|
343
|
+
* The group fragment for a named child, or `[]` when the name is unmapped.
|
|
267
344
|
*
|
|
268
345
|
* An unmapped name INHERITS rather than throwing: a child that reaches the model
|
|
269
346
|
* with today's argv is always safe, and aborting a user's task over a missing
|
|
270
347
|
* table row would be a worse failure than the one it reports. The guard that
|
|
271
|
-
* makes the table complete is `
|
|
348
|
+
* makes the table complete is `config/groups.test.ts`, which fails the BUILD —
|
|
272
349
|
* where someone can actually fix it.
|
|
273
350
|
*/
|
|
274
|
-
export declare function
|
|
351
|
+
export declare function groupArgsForChild(name: string): string[];
|
|
275
352
|
export declare function runPhaseChild(deps: PhaseDeps, name: string, tools: string, prompt: string, opts?: PhaseChildOptions): Promise<string>;
|
|
276
353
|
export declare function formatLoopHint(hit: LoopHit): string;
|
|
277
354
|
/**
|