@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
|
@@ -81,32 +81,49 @@ export declare const USER_CANCELLED = "__user_cancelled__";
|
|
|
81
81
|
* context-usage tracking. This is the typed convenience wrapper used by
|
|
82
82
|
* phase-level code.
|
|
83
83
|
*/
|
|
84
|
-
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,
|
|
85
|
-
/** Internal `-e` extension paths for in-run guards (see childArgs). */
|
|
86
|
-
extensions?: readonly string[],
|
|
87
84
|
/**
|
|
88
|
-
*
|
|
89
|
-
* needs the size of what actually entered the child's context, which the
|
|
90
|
-
* CALL alone does not carry (task/stall-detector.ts).
|
|
91
|
-
*/
|
|
92
|
-
onToolResult?: (text: string, isError: boolean) => void,
|
|
93
|
-
/**
|
|
94
|
-
* The child's context window in tokens. Nothing in pi's event stream reports
|
|
95
|
-
* one (issue #16), so the parent hands its own down — children carry no `-m`
|
|
96
|
-
* and resolve the same default model. 0 / omitted = unknown, as before.
|
|
97
|
-
*/
|
|
98
|
-
contextWindow?: number,
|
|
99
|
-
/**
|
|
100
|
-
* The resolved `['--thinking', level]` fragment for this child's reasoning
|
|
101
|
-
* group, or `[]`/omitted to inherit the session default as before.
|
|
85
|
+
* One child-pi invocation, as a value.
|
|
102
86
|
*
|
|
103
|
-
* A
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
87
|
+
* WHY A RECORD. This was thirteen ordered positionals, and the thirteenth
|
|
88
|
+
* carried its own DEBT note saying so. Two production callers reached it, and
|
|
89
|
+
* they had already drifted: the degrade attempt wrote three bare `undefined`s to
|
|
90
|
+
* reach the later slots and passed the RAW signal, so it escaped the wall clock
|
|
91
|
+
* its own strike siblings run under — the same defect class this file already
|
|
92
|
+
* recorded for `runAutoInstall` and for the deleted `runPhaseWithLoopGuard`.
|
|
93
|
+
* Adjacent optionals of the same type can no longer swap without a type error.
|
|
107
94
|
*/
|
|
108
|
-
|
|
109
|
-
|
|
95
|
+
export interface ChildRun {
|
|
96
|
+
cwd: string;
|
|
97
|
+
/** `''` means `--no-tools`. See childArgs. */
|
|
98
|
+
tools: string;
|
|
99
|
+
prompt: string;
|
|
100
|
+
signal: AbortSignal;
|
|
101
|
+
onLine?: (line: string) => void;
|
|
102
|
+
onContextUsage?: (snapshot: ContextSnapshot) => void;
|
|
103
|
+
onToolCall?: (call: ToolCall) => LoopHit | null;
|
|
104
|
+
spawn?: SpawnFn;
|
|
105
|
+
/** Internal `-e` extension paths for in-run guards (see childArgs). */
|
|
106
|
+
extensions?: readonly string[];
|
|
107
|
+
/**
|
|
108
|
+
* Every finished tool call's result text. The StallDetector's churn rule
|
|
109
|
+
* needs the size of what actually entered the child's context, which the
|
|
110
|
+
* CALL alone does not carry (task/stall-detector.ts).
|
|
111
|
+
*/
|
|
112
|
+
onToolResult?: (text: string, isError: boolean) => void;
|
|
113
|
+
/**
|
|
114
|
+
* The child's context window in tokens. Nothing in pi's event stream reports
|
|
115
|
+
* one (issue #16), so the parent hands its own down — children carry no `-m`
|
|
116
|
+
* and resolve the same default model. 0 / omitted = unknown, as before.
|
|
117
|
+
*/
|
|
118
|
+
contextWindow?: number;
|
|
119
|
+
/**
|
|
120
|
+
* The resolved `['--thinking', level]` fragment for this child's reasoning
|
|
121
|
+
* group, or `[]`/omitted to inherit the session default as before.
|
|
122
|
+
*/
|
|
123
|
+
thinking?: readonly string[];
|
|
124
|
+
}
|
|
125
|
+
export declare function runChild({ cwd, tools, prompt, signal, onLine, onContextUsage, onToolCall, spawn: spawnFn, extensions, onToolResult, contextWindow, thinking }: ChildRun): Promise<PhaseRunResult>;
|
|
126
|
+
export interface PhaseDeps {
|
|
110
127
|
cwd: string;
|
|
111
128
|
taskId: string;
|
|
112
129
|
signal: AbortSignal;
|
|
@@ -211,7 +228,55 @@ interface PhaseDeps {
|
|
|
211
228
|
*/
|
|
212
229
|
searchFn?: (input: SearchCoreInput) => Promise<SearchCoreResult>;
|
|
213
230
|
}
|
|
214
|
-
|
|
231
|
+
/**
|
|
232
|
+
* The subset of `PhaseDeps` a CALLER may supply — every injectable seam, derived
|
|
233
|
+
* by naming what the runner owns instead of by listing what it does not.
|
|
234
|
+
*
|
|
235
|
+
* `TaskRunnerOptions` used to declare `spawnFn`, `runChild`, `runWorker` and a
|
|
236
|
+
* seven-name `Pick` called `lookups` separately; `RunSingleTaskOptions` re-picked
|
|
237
|
+
* all four, `runSingleTask` re-forwarded each by name, and the constructor spread
|
|
238
|
+
* them back together. Four coordinated edits, none of which failed to compile if
|
|
239
|
+
* you skipped one — the exact indictment this codebase already recorded against
|
|
240
|
+
* `ConfigItem`. Four seams (`timeoutMs`, `sleepFor`, `childExtensions`,
|
|
241
|
+
* `logDebug`) had in fact been left behind, so a runner-driven test of the
|
|
242
|
+
* connection-error rung really slept and the debug trail could not be asserted at
|
|
243
|
+
* all. Derived by `Omit`, a NEW seam field joins this with no second edit.
|
|
244
|
+
*/
|
|
245
|
+
export type PhaseSeams = Omit<PhaseDeps, 'cwd' | 'taskId' | 'signal' | 'onChildOutput' | 'onContextUsage' | 'contextWindow' | 'recordSubStep'>;
|
|
246
|
+
/**
|
|
247
|
+
* Run a child pi and return its assistant text. Throws if exit code != 0.
|
|
248
|
+
*
|
|
249
|
+
* If the child leaks a tool call as plain text (wrong dialect — never executed),
|
|
250
|
+
* re-prompt with a correction hint up to MAX_LEAK_RETRIES times; if it keeps
|
|
251
|
+
* leaking, throw LeakedToolCallError rather than returning the unexecuted call.
|
|
252
|
+
* Empty completions and connection-class model errors share that same budget —
|
|
253
|
+
* see triageChildResult, which decides every one of those cases.
|
|
254
|
+
*
|
|
255
|
+
* THREE RUNAWAY GUARDS ride the same budget, because this is the runner every
|
|
256
|
+
* /task-auto planning child goes through (clarify, decompose, coverage,
|
|
257
|
+
* contract-extract) and until mx5-n 2026-08-14 it had none:
|
|
258
|
+
* • a LoopDetector, so an identical repeated tool call is killed and
|
|
259
|
+
* re-prompted instead of being allowed to fill the context window;
|
|
260
|
+
* • a StallDetector, the backstop for the varied-args thrash the loop
|
|
261
|
+
* detector's short window cannot see — the shape that actually cost us a
|
|
262
|
+
* 16-minute decompose child that was never going to return. It bounds
|
|
263
|
+
* consecutive no-new-ground calls and total context churn, NOT elapsed time;
|
|
264
|
+
* • PHASE_CHILD_TIMEOUT_MS, a hard wall clock, OFF by default because the
|
|
265
|
+
* measured healthy range (610-927s for a reasoning-on decompose) overlaps
|
|
266
|
+
* any value that would catch the pathology. See its comment.
|
|
267
|
+
* All three are checked BEFORE the triage ladder: we killed the child, so its
|
|
268
|
+
* exit status describes our SIGTERM and says nothing about its verdict.
|
|
269
|
+
*/
|
|
270
|
+
/**
|
|
271
|
+
* The `--thinking` fragment for a named child, or `[]` when the name is unmapped.
|
|
272
|
+
*
|
|
273
|
+
* An unmapped name INHERITS rather than throwing: a child that reaches the model
|
|
274
|
+
* with today's argv is always safe, and aborting a user's task over a missing
|
|
275
|
+
* table row would be a worse failure than the one it reports. The guard that
|
|
276
|
+
* makes the table complete is `reasoning-groups.test.ts`, which fails the BUILD —
|
|
277
|
+
* where someone can actually fix it.
|
|
278
|
+
*/
|
|
279
|
+
export declare function thinkingForChild(name: string): string[];
|
|
215
280
|
export declare function runPhaseChild(deps: PhaseDeps, name: string, tools: string, prompt: string, opts?: PhaseChildOptions): Promise<string>;
|
|
216
281
|
export declare function formatLoopHint(hit: LoopHit): string;
|
|
217
282
|
/**
|
|
@@ -16,7 +16,7 @@ import { readSection, setTaskSection } from './task-io.js';
|
|
|
16
16
|
import { streamStallCause } from '../shared/stream-watchdog.js';
|
|
17
17
|
import { getConfig } from '../config/config.js';
|
|
18
18
|
import { groupThinkingArgs } from '../config/reasoning-args.js';
|
|
19
|
-
import { reasoningGroupForChild } from '
|
|
19
|
+
import { reasoningGroupForChild } from '../config/reasoning.js';
|
|
20
20
|
// ─── Loop detection constants ────────────────────────────────────────────────
|
|
21
21
|
// Defined here (not in phases.ts) to avoid a circular dependency:
|
|
22
22
|
// phases.ts → child-runner.ts → phases.ts
|
|
@@ -167,37 +167,7 @@ thinking = []) {
|
|
|
167
167
|
// Sentinel error thrown when the user dismisses a grill-me dialog.
|
|
168
168
|
// Defined here (not in failure-classifier.ts) to avoid circular dependency.
|
|
169
169
|
export const USER_CANCELLED = '__user_cancelled__';
|
|
170
|
-
|
|
171
|
-
/**
|
|
172
|
-
* Run a child pi process with JSON event-stream output, loop detection, and
|
|
173
|
-
* context-usage tracking. This is the typed convenience wrapper used by
|
|
174
|
-
* phase-level code.
|
|
175
|
-
*/
|
|
176
|
-
export async function runChild(cwd, tools, prompt, signal, onLine, onContextUsage, onToolCall, spawnFn,
|
|
177
|
-
/** Internal `-e` extension paths for in-run guards (see childArgs). */
|
|
178
|
-
extensions,
|
|
179
|
-
/**
|
|
180
|
-
* Every finished tool call's result text. The StallDetector's churn rule
|
|
181
|
-
* needs the size of what actually entered the child's context, which the
|
|
182
|
-
* CALL alone does not carry (task/stall-detector.ts).
|
|
183
|
-
*/
|
|
184
|
-
onToolResult,
|
|
185
|
-
/**
|
|
186
|
-
* The child's context window in tokens. Nothing in pi's event stream reports
|
|
187
|
-
* one (issue #16), so the parent hands its own down — children carry no `-m`
|
|
188
|
-
* and resolve the same default model. 0 / omitted = unknown, as before.
|
|
189
|
-
*/
|
|
190
|
-
contextWindow,
|
|
191
|
-
/**
|
|
192
|
-
* The resolved `['--thinking', level]` fragment for this child's reasoning
|
|
193
|
-
* group, or `[]`/omitted to inherit the session default as before.
|
|
194
|
-
*
|
|
195
|
-
* A 13th trailing positional is not pretty. It is how `contextWindow` (the
|
|
196
|
-
* 12th) was added, and an options-bag refactor of this signature is a
|
|
197
|
-
* separate change from wiring one flag — doing both at once would put a
|
|
198
|
-
* behaviour change inside a mechanical one. DEBT: convert to an options bag.
|
|
199
|
-
*/
|
|
200
|
-
thinking) {
|
|
170
|
+
export async function runChild({ cwd, tools, prompt, signal, onLine, onContextUsage, onToolCall, spawn: spawnFn, extensions, onToolResult, contextWindow, thinking }) {
|
|
201
171
|
const invocation = getPiInvocation(childArgs(tools, extensions, thinking), prompt);
|
|
202
172
|
let loopHit;
|
|
203
173
|
const result = await runChildUnified(spawnFn ?? spawn, invocation, cwd, signal, {
|
|
@@ -338,10 +308,30 @@ async function triageChildResult(deps, name, r, attempt, budget, verb) {
|
|
|
338
308
|
* makes the table complete is `reasoning-groups.test.ts`, which fails the BUILD —
|
|
339
309
|
* where someone can actually fix it.
|
|
340
310
|
*/
|
|
341
|
-
function thinkingForChild(name) {
|
|
311
|
+
export function thinkingForChild(name) {
|
|
342
312
|
const group = reasoningGroupForChild(name);
|
|
343
313
|
return group ? groupThinkingArgs(group) : [];
|
|
344
314
|
}
|
|
315
|
+
/**
|
|
316
|
+
* What a PHASE child's invocation carries, said once.
|
|
317
|
+
*
|
|
318
|
+
* Both callers of `runChild` in this file are phase children — the strike
|
|
319
|
+
* attempts and the no-tools degrade that rescues them — and everything they
|
|
320
|
+
* disagree about is in `over`. Anything not there is the same by construction,
|
|
321
|
+
* which is what the degrade's own comment ("the degrade changes the TOOLS, not
|
|
322
|
+
* the role") claimed while three bare `undefined`s quietly made it false.
|
|
323
|
+
*/
|
|
324
|
+
function phaseChildRun(deps, over) {
|
|
325
|
+
return {
|
|
326
|
+
cwd: deps.cwd,
|
|
327
|
+
onLine: deps.onChildOutput,
|
|
328
|
+
onContextUsage: deps.onContextUsage,
|
|
329
|
+
spawn: deps.spawn,
|
|
330
|
+
extensions: deps.childExtensions,
|
|
331
|
+
contextWindow: deps.contextWindow,
|
|
332
|
+
...over
|
|
333
|
+
};
|
|
334
|
+
}
|
|
345
335
|
export async function runPhaseChild(deps, name, tools, prompt, opts = {}) {
|
|
346
336
|
if (deps.runChild)
|
|
347
337
|
return await deps.runChild(name, tools, prompt);
|
|
@@ -369,13 +359,21 @@ export async function runPhaseChild(deps, name, tools, prompt, opts = {}) {
|
|
|
369
359
|
const clock = phaseTimeout(deps.signal, budgetMs);
|
|
370
360
|
let r;
|
|
371
361
|
try {
|
|
372
|
-
r = await runChild(
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
362
|
+
r = await runChild(phaseChildRun(deps, {
|
|
363
|
+
tools,
|
|
364
|
+
prompt: prependHint(hint, prompt),
|
|
365
|
+
signal: clock.signal,
|
|
366
|
+
thinking,
|
|
367
|
+
onContextUsage: snapshot => {
|
|
368
|
+
// Real window or nothing: noteContext ignores 0, and until
|
|
369
|
+
// deps.contextWindow existed 0 was all it ever saw, which
|
|
370
|
+
// left the churn rule permanently disarmed (issue #16).
|
|
371
|
+
stall.noteContext(snapshot.contextWindow);
|
|
372
|
+
deps.onContextUsage?.(snapshot);
|
|
373
|
+
},
|
|
374
|
+
onToolCall: call => detector.record(call) ?? stall.record(call),
|
|
375
|
+
onToolResult: (text, isError) => stall.noteResult(text, isError)
|
|
376
|
+
}));
|
|
379
377
|
}
|
|
380
378
|
finally {
|
|
381
379
|
clock.cleanup();
|
|
@@ -482,13 +480,36 @@ async function appendLoopEvent(cwd, taskId, phase, hit, strike, outcome) {
|
|
|
482
480
|
*/
|
|
483
481
|
async function runDegradedFinalAttempt(deps, name, prompt, hit, loopHistory) {
|
|
484
482
|
deps.logDebug?.(`${name}: loop budget exhausted — degrading to a no-tools final attempt`);
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
//
|
|
488
|
-
//
|
|
489
|
-
//
|
|
490
|
-
|
|
483
|
+
// BEHAVIOUR DELTA. This attempt now runs under the same wall clock as the
|
|
484
|
+
// strikes that led here. It used to pass `deps.signal` raw — one of the
|
|
485
|
+
// three drifts that came of reaching past bare `undefined`s to the later
|
|
486
|
+
// positionals — so the one attempt made after a loop budget was spent was
|
|
487
|
+
// also the one attempt that could hang forever.
|
|
488
|
+
const clock = phaseTimeout(deps.signal, deps.timeoutMs ?? PHASE_CHILD_TIMEOUT_MS);
|
|
489
|
+
let r;
|
|
490
|
+
try {
|
|
491
|
+
r = await runChild(phaseChildRun(deps, {
|
|
492
|
+
tools: '', // --no-tools: the model cannot read/grep/list, only answer
|
|
493
|
+
prompt: prependHint(formatDegradeHint(hit), prompt),
|
|
494
|
+
signal: clock.signal,
|
|
495
|
+
// Same group as the attempts that led here. The degrade changes the
|
|
496
|
+
// TOOLS, not the role — running it at a different thinking level would
|
|
497
|
+
// make the fallback a different experiment from the thing it rescues.
|
|
498
|
+
thinking: thinkingForChild(name)
|
|
499
|
+
}));
|
|
500
|
+
}
|
|
501
|
+
finally {
|
|
502
|
+
clock.cleanup();
|
|
503
|
+
}
|
|
491
504
|
if (r.exitCode !== 0 || r.modelError || r.text.trim().length === 0) {
|
|
505
|
+
// A wall-clock kill is NOT a loop. The clock above is new here, and
|
|
506
|
+
// without this check a degrade that outran its budget was reported as
|
|
507
|
+
// "loop budget exhausted", carrying a loop history that did not cause it
|
|
508
|
+
// — the same mislabel class the worker-kill roster exists to prevent, on
|
|
509
|
+
// the very path that clock was added to guard.
|
|
510
|
+
if (clock.timedOut()) {
|
|
511
|
+
throw new PhaseTimeoutError(name, deps.timeoutMs ?? PHASE_CHILD_TIMEOUT_MS, 1);
|
|
512
|
+
}
|
|
492
513
|
throw new LoopExhaustedError(name, loopHistory);
|
|
493
514
|
}
|
|
494
515
|
return r.text;
|
|
@@ -19,10 +19,9 @@ import type { ExtensionAPI, ExtensionCommandContext } from '@earendil-works/pi-c
|
|
|
19
19
|
import { type WidgetState } from './widget.js';
|
|
20
20
|
import { type RunTaskFn } from './gate-deps.js';
|
|
21
21
|
import { type GateDeps } from './task-gates.js';
|
|
22
|
-
import { type
|
|
22
|
+
import { type PhaseSeams } from './child-runner.js';
|
|
23
23
|
import { type ThinkingControl } from './implementation-thinking.js';
|
|
24
24
|
import { type RunEnd } from './run-end.js';
|
|
25
|
-
import type { SpawnFn } from '../shared/child-process.js';
|
|
26
25
|
import { type SuperviseOptions } from './implementation-turn.js';
|
|
27
26
|
/**
|
|
28
27
|
* Everything one TaskRunner needs, as one object. The runner is the shared core
|
|
@@ -38,25 +37,20 @@ export interface TaskRunnerOptions {
|
|
|
38
37
|
resumeId?: string;
|
|
39
38
|
/** Deliver the finished spec to the main session. Absent → nothing is sent. */
|
|
40
39
|
sendSpec?: (spec: string) => Promise<void>;
|
|
41
|
-
/** Test seam: spawn function forwarded into `PhaseDeps.spawn`. Keep for tests
|
|
42
|
-
* that drive the Error-triage ladder or a real process. */
|
|
43
|
-
spawnFn?: SpawnFn;
|
|
44
40
|
/**
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
* the
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
41
|
+
* Every injectable phase seam, in one field (`PhaseSeams`, child-runner.ts).
|
|
42
|
+
*
|
|
43
|
+
* `spawn` drives the Error-triage ladder or a real process. `runChild(name,
|
|
44
|
+
* tools, prompt)` answers every phase child BY NAME, with none of the
|
|
45
|
+
* ladder's guards — use it when the child is a premise of the test, not its
|
|
46
|
+
* subject. `runWorker(label, input)` answers every research worker by name,
|
|
47
|
+
* so the three Research retry gates are reachable without matching a marker
|
|
48
|
+
* sentence inside a prompt. The EXTERNAL CONTEXT lookups and the file
|
|
49
|
+
* inventory each default to the real implementation when absent. `timeoutMs`,
|
|
50
|
+
* `sleepFor`, `childExtensions` and `logDebug` are seams too, and were
|
|
51
|
+
* unreachable from here until this became one field.
|
|
55
52
|
*/
|
|
56
|
-
|
|
57
|
-
/** Test seam: the four EXTERNAL CONTEXT lookups plus the file inventory. Each
|
|
58
|
-
* defaults to the real implementation when absent. */
|
|
59
|
-
lookups?: Pick<PhaseDeps, 'getFileInventory' | 'docsRaw' | 'fetchRaw' | 'npmVersionLookup' | 'docsFocused' | 'fetchFocused' | 'searchFn'>;
|
|
53
|
+
seams?: PhaseSeams;
|
|
60
54
|
/** Called with the resolved task id once its file exists, before any phase
|
|
61
55
|
* work. Lets callers record the id (e.g. stamp the /task-auto entry) so an
|
|
62
56
|
* interrupted run can be resumed instead of restarted. */
|
|
@@ -147,7 +141,7 @@ export declare class TaskRunner {
|
|
|
147
141
|
*/
|
|
148
142
|
private _specForDelivery;
|
|
149
143
|
}
|
|
150
|
-
export interface RunSingleTaskOptions extends Pick<TaskRunnerOptions, 'resumeId' | '
|
|
144
|
+
export interface RunSingleTaskOptions extends Pick<TaskRunnerOptions, 'resumeId' | 'seams' | 'onStart' | 'planContext' | 'fixInstruction'> {
|
|
151
145
|
/** Await the session going idle after the spec is delivered, so the caller
|
|
152
146
|
* blocks until the agent has implemented it. Default false. */
|
|
153
147
|
waitForImplementation?: boolean;
|
|
@@ -126,10 +126,7 @@ export class TaskRunner {
|
|
|
126
126
|
cwd,
|
|
127
127
|
taskId: '',
|
|
128
128
|
signal: this._abort.signal,
|
|
129
|
-
|
|
130
|
-
runChild: opts.runChild,
|
|
131
|
-
runWorker: opts.runWorker,
|
|
132
|
-
...opts.lookups,
|
|
129
|
+
...opts.seams,
|
|
133
130
|
// Deliberately NOT a ChildStatus (child-status.ts): the phase widget's
|
|
134
131
|
// state is the whole-run WidgetState — task id, phase, label — shared by
|
|
135
132
|
// reference with PhaseContext and written by the phases themselves
|
|
@@ -231,7 +228,10 @@ export class TaskRunner {
|
|
|
231
228
|
const debugLogPath = path.join(tasksDir(cwd), `${id}-debug.log`);
|
|
232
229
|
// Left UNSET at level `off`, so the ~39 `logDebug?.(…)` sites downstream
|
|
233
230
|
// short-circuit before they format a string and the file is never created.
|
|
234
|
-
|
|
231
|
+
// A caller-supplied `logDebug` seam WINS: it is the only way to observe
|
|
232
|
+
// the ~39 trail decisions from a runner-driven test, and production never
|
|
233
|
+
// sets one, so the file writer is unaffected.
|
|
234
|
+
this._deps.logDebug ??= gateDebugWriter((msg) => {
|
|
235
235
|
const line = `${new Date().toISOString()} ${msg}\n`;
|
|
236
236
|
fsp.appendFile(debugLogPath, line).catch(() => {
|
|
237
237
|
/* ignore */
|
|
@@ -480,10 +480,7 @@ export async function runSingleTask(ctx, cwd, rawPrompt, opts = {}) {
|
|
|
480
480
|
release();
|
|
481
481
|
}
|
|
482
482
|
},
|
|
483
|
-
|
|
484
|
-
runChild: opts.runChild,
|
|
485
|
-
runWorker: opts.runWorker,
|
|
486
|
-
lookups: opts.lookups,
|
|
483
|
+
seams: opts.seams,
|
|
487
484
|
onStart: opts.onStart,
|
|
488
485
|
planContext: opts.planContext,
|
|
489
486
|
fixInstruction: opts.fixInstruction,
|
|
@@ -536,6 +533,12 @@ export const gateRunTask = (c, cwd, t, opts) => runSingleTask(c, cwd, t, {
|
|
|
536
533
|
onStart: opts?.onStart,
|
|
537
534
|
planContext: opts?.planContext,
|
|
538
535
|
fixInstruction: opts?.fixInstruction
|
|
536
|
+
// NO `seams` here, deliberately. Threading them would need a field on
|
|
537
|
+
// `GateParams` and another on `GateDeps`, and nothing — production or
|
|
538
|
+
// test — would set either: the gate is reached through two orchestrators
|
|
539
|
+
// that build their params from a task file. An unused field on a seam
|
|
540
|
+
// roster is the `WorkerOutcome.reason` shape (written twelve times, read
|
|
541
|
+
// by nothing), so this stays unplumbed until a test actually needs it.
|
|
539
542
|
});
|
|
540
543
|
/**
|
|
541
544
|
* Demote a task file to a resumable state after a gate (or its implementation)
|
package/dist/task/phases.d.ts
CHANGED
|
@@ -123,6 +123,22 @@ export declare function resolveOwnedFreezeForThisTask(deps: PhaseDeps, spec: str
|
|
|
123
123
|
export declare function claimOwnedFreezeForThisTask(deps: PhaseDeps, refined: string): Promise<void>;
|
|
124
124
|
export declare const phaseRefine: (deps: PhaseDeps, raw: string, planContext?: string) => Promise<string>;
|
|
125
125
|
export declare function phaseVerifyTooling(deps: PhaseDeps, research: string): Promise<string>;
|
|
126
|
+
/**
|
|
127
|
+
* The worker channels the APIS research worker is given.
|
|
128
|
+
*
|
|
129
|
+
* `pi-worker-search` + `pi-worker-fetch` ride along only when the configured
|
|
130
|
+
* engine is usable (a keyless engine always is; brave needs its key). A tool
|
|
131
|
+
* without a key just errors, and a weak model burns calls on it — while search
|
|
132
|
+
* being ABSENT was structural in the other direction: three consecutive audited
|
|
133
|
+
* runs made 0 search calls because the child literally did not have the tool.
|
|
134
|
+
*
|
|
135
|
+
* Both halves of "given a channel" — the tools string and the `-e` path — come
|
|
136
|
+
* from the same rows, so they cannot disagree.
|
|
137
|
+
*/
|
|
138
|
+
export declare function apisWorkerChannels(): {
|
|
139
|
+
tools: string;
|
|
140
|
+
extensions: string[];
|
|
141
|
+
};
|
|
126
142
|
/**
|
|
127
143
|
* Is live web search configured for this process? The keyless providers (exa,
|
|
128
144
|
* ddg) always are; only brave needs its API key.
|
|
@@ -156,29 +172,6 @@ export declare const SINGLE_READ_EXTENSION_PATH: string;
|
|
|
156
172
|
* mentions; the rest are unchanged or only lightly trimmed.
|
|
157
173
|
*/
|
|
158
174
|
export declare function scopedToolingGoal(refined: string): string;
|
|
159
|
-
/**
|
|
160
|
-
* Build a degraded section body for a runaway worker: a one-line marker naming
|
|
161
|
-
* the failure (so downstream phases and a human reading the task file know this
|
|
162
|
-
* section is incomplete) followed by whatever partial answer the worker streamed
|
|
163
|
-
* before it was killed. The marker is always present even when there is no
|
|
164
|
-
* partial text, so an empty degrade is never mistaken for a real finding.
|
|
165
|
-
*/
|
|
166
|
-
export declare function degradedSectionBody(name: string, reason: string, partial: string): string;
|
|
167
|
-
/**
|
|
168
|
-
* The body written for a research section the worker confirmed has no entries.
|
|
169
|
-
*
|
|
170
|
-
* Three states have to stay distinguishable to anyone — human or later phase —
|
|
171
|
-
* reading a research section, so each carries its own marker:
|
|
172
|
-
* `(none — …)` the worker RAN and answered "nothing applies" (this)
|
|
173
|
-
* `(degraded: …)` the worker was killed mid-answer, text may be partial
|
|
174
|
-
* (degradedSectionBody)
|
|
175
|
-
* section absent the worker never got that far — the phase threw
|
|
176
|
-
*
|
|
177
|
-
* Naming the worker inside the marker keeps it true after assembly, where the
|
|
178
|
-
* section headings are all that separate the four workers' output.
|
|
179
|
-
*/
|
|
180
|
-
export declare function emptySectionBody(name: string): string;
|
|
181
|
-
export declare function isBareNoneAnswer(text: string): boolean;
|
|
182
175
|
export declare function phaseResearch(deps: PhaseDeps, refined: string): Promise<string>;
|
|
183
176
|
export declare function phaseAutoAnswer(deps: PhaseDeps, refined: string, research: string, question: string): Promise<AutoAnswer>;
|
|
184
177
|
export declare function phaseGrill(deps: PhaseDeps, ctx: ExtensionCommandContext, widgetState: WidgetState, refined: string, research: string): Promise<string>;
|