@mjasnikovs/pi-task 0.38.11 → 0.38.12
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 +7 -3
- package/dist/shared/child-process.d.ts +8 -0
- package/dist/shared/command-watchdog.d.ts +1 -1
- package/dist/shared/command-watchdog.js +1 -1
- package/dist/task/accept-debt.d.ts +47 -0
- package/dist/task/accept-debt.js +127 -28
- package/dist/task/auto-orchestrator.js +91 -114
- package/dist/task/child-runner.d.ts +39 -25
- package/dist/task/child-runner.js +59 -31
- package/dist/task/child-status.d.ts +95 -0
- package/dist/task/child-status.js +99 -0
- package/dist/task/command-run.d.ts +36 -0
- package/dist/task/command-run.js +48 -1
- package/dist/task/command-watchdog.js +1 -1
- package/dist/task/context-usage.d.ts +4 -3
- package/dist/task/context-usage.js +4 -3
- package/dist/task/contracts.js +18 -35
- package/dist/task/deep-render-check.d.ts +47 -0
- package/dist/task/deep-render-check.js +110 -65
- package/dist/task/env-notes.d.ts +3 -3
- package/dist/task/env-notes.js +24 -35
- package/dist/task/final-gate-fix.d.ts +1 -1
- package/dist/task/final-gate-fix.js +1 -1
- package/dist/task/final-gate.d.ts +5 -151
- package/dist/task/final-gate.js +81 -379
- package/dist/task/gate-child.d.ts +8 -10
- package/dist/task/gate-child.js +15 -19
- package/dist/task/gate-deps.d.ts +29 -0
- package/dist/task/gate-deps.js +192 -206
- package/dist/task/gate-tally.d.ts +189 -0
- package/dist/task/gate-tally.js +249 -0
- package/dist/task/implementation-turn.d.ts +201 -0
- package/dist/task/implementation-turn.js +263 -0
- package/dist/task/launch-contract.js +27 -43
- package/dist/task/ledger.d.ts +38 -0
- package/dist/task/ledger.js +83 -0
- package/dist/task/loop-detector.d.ts +14 -8
- package/dist/task/loop-detector.js +36 -12
- package/dist/task/orchestrator.d.ts +61 -126
- package/dist/task/orchestrator.js +67 -294
- package/dist/task/plan-orchestrator.js +34 -33
- package/dist/task/requirements.d.ts +1 -1
- package/dist/task/requirements.js +50 -66
- package/dist/task/root-cause-repair.js +20 -32
- package/dist/task/run-bracket.d.ts +75 -0
- package/dist/task/run-bracket.js +41 -0
- package/dist/task/stall-detector.d.ts +110 -0
- package/dist/task/stall-detector.js +159 -0
- package/dist/task/verify-work.d.ts +53 -67
- package/dist/task/verify-work.js +15 -11
- package/dist/workers/single-read-extension.d.ts +1 -1
- package/dist/workers/single-read-extension.js +5 -4
- package/dist/workers/single-read-guard.d.ts +32 -10
- package/dist/workers/single-read-guard.js +67 -16
- package/package.json +1 -1
|
@@ -19,7 +19,58 @@ 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 PhaseDeps } from './child-runner.js';
|
|
22
23
|
import type { SpawnFn } from '../shared/child-process.js';
|
|
24
|
+
import { type SuperviseOptions } from './implementation-turn.js';
|
|
25
|
+
/**
|
|
26
|
+
* Everything one TaskRunner needs, as one object. The runner is the shared core
|
|
27
|
+
* under `runSingleTask` (and so under /task-auto's per-task loop), so this is the
|
|
28
|
+
* shape both of those construct; `RunSingleTaskOptions` extends the injectable
|
|
29
|
+
* subset and adds what only the wrapper reads.
|
|
30
|
+
*/
|
|
31
|
+
export interface TaskRunnerOptions {
|
|
32
|
+
ctx: ExtensionCommandContext;
|
|
33
|
+
cwd: string;
|
|
34
|
+
rawPrompt: string;
|
|
35
|
+
/** Resume an existing task by ID instead of starting a new one. */
|
|
36
|
+
resumeId?: string;
|
|
37
|
+
/** Deliver the finished spec to the main session. Absent → nothing is sent. */
|
|
38
|
+
sendSpec?: (spec: string) => Promise<void>;
|
|
39
|
+
/** Test seam: spawn function forwarded into `PhaseDeps.spawn`. Keep for tests
|
|
40
|
+
* that drive the Error-triage ladder or a real process. */
|
|
41
|
+
spawnFn?: SpawnFn;
|
|
42
|
+
/**
|
|
43
|
+
* Test seam: `PhaseDeps.runChild(name, tools, prompt)`. Present → every phase
|
|
44
|
+
* child is answered by name, with none of the ladder's guards. Use this when
|
|
45
|
+
* the child is a premise of the test, not its subject.
|
|
46
|
+
*/
|
|
47
|
+
runChild?: PhaseDeps['runChild'];
|
|
48
|
+
/** Called with the resolved task id once its file exists, before any phase
|
|
49
|
+
* work. Lets callers record the id (e.g. stamp the /task-auto entry) so an
|
|
50
|
+
* interrupted run can be resumed instead of restarted. */
|
|
51
|
+
onStart?: (taskId: string) => void | Promise<void>;
|
|
52
|
+
/**
|
|
53
|
+
* Scope fence naming the sibling steps of a /task-auto plan. Forwarded into
|
|
54
|
+
* the refine phase so a single decomposed step bounds its slice instead of
|
|
55
|
+
* re-expanding the whole referenced spec doc. Set only by /task-auto's loop;
|
|
56
|
+
* a bare /task leaves it undefined and the refine prompt is unchanged.
|
|
57
|
+
*/
|
|
58
|
+
planContext?: string;
|
|
59
|
+
/**
|
|
60
|
+
* Marks this run as a verify-FAIL re-attempt. When set (only by /task-auto's
|
|
61
|
+
* autofix path, with `resumeId` pointing at the already-composed task), the
|
|
62
|
+
* text — the verify gate's failure reason plus any guidance the user typed —
|
|
63
|
+
* is prepended to the delivered spec as a RE-ATTEMPT banner, so the
|
|
64
|
+
* implementer fixes the specific failure and re-satisfies the VERIFY block
|
|
65
|
+
* rather than blindly redoing the task. Empty/undefined on a first attempt.
|
|
66
|
+
*/
|
|
67
|
+
fixInstruction?: string;
|
|
68
|
+
/** True when the caller awaits the implementation turn (waitForImplementation):
|
|
69
|
+
* the impl widget stays armed across the whole impl phase (incl. compaction /
|
|
70
|
+
* steer turns) and is disarmed here. False for fire-and-forget /task, where the
|
|
71
|
+
* widget is armed one-shot and its own agent_end disarms it. */
|
|
72
|
+
implAwaited?: boolean;
|
|
73
|
+
}
|
|
23
74
|
/** Encapsulates the full lifecycle of a single pi-task run. */
|
|
24
75
|
export declare class TaskRunner {
|
|
25
76
|
private readonly _ctx;
|
|
@@ -30,10 +81,7 @@ export declare class TaskRunner {
|
|
|
30
81
|
private readonly _onStart;
|
|
31
82
|
private readonly _planContext;
|
|
32
83
|
private readonly _fixInstruction;
|
|
33
|
-
/**
|
|
34
|
-
* the impl widget stays armed across the whole impl phase (incl. compaction /
|
|
35
|
-
* steer turns) and is disarmed here. False for fire-and-forget /task, where the
|
|
36
|
-
* widget is armed one-shot and its own agent_end disarms it. */
|
|
84
|
+
/** See {@link TaskRunnerOptions.implAwaited}. */
|
|
37
85
|
private readonly _implAwaited;
|
|
38
86
|
private readonly _abort;
|
|
39
87
|
private readonly _startedAt;
|
|
@@ -50,15 +98,20 @@ export declare class TaskRunner {
|
|
|
50
98
|
*/
|
|
51
99
|
private readonly _timings;
|
|
52
100
|
private _currentPhaseChildren;
|
|
53
|
-
constructor(
|
|
101
|
+
constructor(opts: TaskRunnerOptions);
|
|
54
102
|
get taskId(): string;
|
|
55
103
|
get signal(): AbortSignal;
|
|
56
104
|
/** Return the current widget state, or null if not started. */
|
|
57
105
|
status(): WidgetState | null;
|
|
58
106
|
/** Cancel the running task by aborting the signal. */
|
|
59
107
|
cancel(): void;
|
|
60
|
-
/** Execute the full task lifecycle.
|
|
108
|
+
/** Execute the full task lifecycle. Mid-run input holds instead of starting
|
|
109
|
+
* a competing turn for the whole of it, and the terminal interception is
|
|
110
|
+
* armed for the same window (`withRun`); nested inside `runGatedTask` or
|
|
111
|
+
* the `/task-auto` loop the bracket refcounts, so this changes nothing there
|
|
112
|
+
* and covers the fire-and-forget `runSingleTask` path on its own. */
|
|
61
113
|
run(): Promise<void>;
|
|
114
|
+
private _run;
|
|
62
115
|
/** Stop the phase widget — clearing both the terminal and remote surfaces —
|
|
63
116
|
* exactly once. Nulling the disposer makes repeat calls no-ops, so the
|
|
64
117
|
* failure flash that handleFailure sets after the catch isn't wiped by the
|
|
@@ -76,18 +129,10 @@ export declare class TaskRunner {
|
|
|
76
129
|
*/
|
|
77
130
|
private _specForDelivery;
|
|
78
131
|
}
|
|
79
|
-
export interface RunSingleTaskOptions {
|
|
132
|
+
export interface RunSingleTaskOptions extends Pick<TaskRunnerOptions, 'resumeId' | 'spawnFn' | 'runChild' | 'onStart' | 'planContext' | 'fixInstruction'> {
|
|
80
133
|
/** Await the session going idle after the spec is delivered, so the caller
|
|
81
134
|
* blocks until the agent has implemented it. Default false. */
|
|
82
135
|
waitForImplementation?: boolean;
|
|
83
|
-
/** Resume an existing task by ID instead of starting a new one. */
|
|
84
|
-
resumeId?: string;
|
|
85
|
-
/** Test seam: spawn function forwarded to TaskRunner. */
|
|
86
|
-
spawnFn?: SpawnFn;
|
|
87
|
-
/** Called with the resolved task id once its file exists, before any phase
|
|
88
|
-
* work. Lets callers record the id (e.g. stamp the /task-auto entry) so an
|
|
89
|
-
* interrupted run can be resumed instead of restarted. */
|
|
90
|
-
onStart?: (taskId: string) => void | Promise<void>;
|
|
91
136
|
/**
|
|
92
137
|
* Ask the user for a steering message after they interrupt (ESC) the
|
|
93
138
|
* implementation turn. Return text to continue the same task as another turn,
|
|
@@ -96,7 +141,7 @@ export interface RunSingleTaskOptions {
|
|
|
96
141
|
* raced against a remote browser card); injectable so the steer loop is
|
|
97
142
|
* testable without a real dialog.
|
|
98
143
|
*/
|
|
99
|
-
promptSteer?:
|
|
144
|
+
promptSteer?: SuperviseOptions['promptSteer'];
|
|
100
145
|
/**
|
|
101
146
|
* Push a "Task finished" notification to subscribed devices when this run
|
|
102
147
|
* reaches a terminal state (completed / failed / cancelled). Set only by the
|
|
@@ -104,33 +149,7 @@ export interface RunSingleTaskOptions {
|
|
|
104
149
|
* internal per-task runs, which must stay silent. Default false.
|
|
105
150
|
*/
|
|
106
151
|
notifyFinish?: boolean;
|
|
107
|
-
/**
|
|
108
|
-
* Scope fence naming the sibling steps of a /task-auto plan. Forwarded into
|
|
109
|
-
* the refine phase so a single decomposed step bounds its slice instead of
|
|
110
|
-
* re-expanding the whole referenced spec doc. Set only by /task-auto's loop;
|
|
111
|
-
* a bare /task leaves it undefined and the refine prompt is unchanged.
|
|
112
|
-
*/
|
|
113
|
-
planContext?: string;
|
|
114
|
-
/**
|
|
115
|
-
* Marks this run as a verify-FAIL re-attempt. When set (only by /task-auto's
|
|
116
|
-
* autofix path, with `resumeId` pointing at the already-composed task), the
|
|
117
|
-
* text — the verify gate's failure reason plus any guidance the user typed —
|
|
118
|
-
* is prepended to the delivered spec as a RE-ATTEMPT banner, so the
|
|
119
|
-
* implementer fixes the specific failure and re-satisfies the VERIFY block
|
|
120
|
-
* rather than blindly redoing the task. Empty/undefined on a first attempt.
|
|
121
|
-
*/
|
|
122
|
-
fixInstruction?: string;
|
|
123
152
|
}
|
|
124
|
-
/**
|
|
125
|
-
* The slice of the replacement-session context the steer loop needs.
|
|
126
|
-
* `sendUserMessage` lives on ReplacedSessionContext (not the base command ctx,
|
|
127
|
-
* and not re-exported from the package), so we narrow to just what we call.
|
|
128
|
-
*/
|
|
129
|
-
export type SteerCtx = ExtensionCommandContext & {
|
|
130
|
-
sendUserMessage(content: string, options?: {
|
|
131
|
-
deliverAs?: 'steer' | 'followUp';
|
|
132
|
-
}): Promise<void>;
|
|
133
|
-
};
|
|
134
153
|
export interface RunSingleTaskResult {
|
|
135
154
|
taskId: string;
|
|
136
155
|
ok: boolean;
|
|
@@ -163,90 +182,6 @@ export interface RunSingleTaskResult {
|
|
|
163
182
|
*/
|
|
164
183
|
reason?: string;
|
|
165
184
|
}
|
|
166
|
-
/**
|
|
167
|
-
* True when the implementation turn went idle right after a context compaction —
|
|
168
|
-
* the most recent entry in the branch is a `compaction` boundary sitting after the
|
|
169
|
-
* last assistant message.
|
|
170
|
-
*
|
|
171
|
-
* A *threshold* auto-compaction (the runtime's "context is getting large" path)
|
|
172
|
-
* compacts and then deliberately does NOT auto-continue: it returns to idle and
|
|
173
|
-
* expects a manual continue (`_runAutoCompaction("threshold", false)` →
|
|
174
|
-
* `hasQueuedMessages()` is false → the agent loop stops). Our implementation wait
|
|
175
|
-
* resolves at exactly that idle. Without this check it reads as "the model
|
|
176
|
-
* finished" (the last assistant message is a normal `stop`, not `aborted`/`error`),
|
|
177
|
-
* so the run jumps straight to the verify gate and abandons a half-done task at the
|
|
178
|
-
* compaction boundary — the failure this detector closes.
|
|
179
|
-
*
|
|
180
|
-
* Position-based, not timestamp-based: the runtime APPENDS the compaction entry to
|
|
181
|
-
* the tail of the branch after the assistant message that triggered it
|
|
182
|
-
* (`appendCompaction` → `_appendEntry` push), so a `compaction` after the last
|
|
183
|
-
* assistant message means we are parked on a compaction with no continuation. A
|
|
184
|
-
* genuinely finished turn ends on an assistant message with no trailing compaction;
|
|
185
|
-
* an *overflow* compaction self-retries, so it never leaves us idle here.
|
|
186
|
-
*/
|
|
187
|
-
export declare function endedAtCompactionBoundary(ctx: ExtensionCommandContext): boolean;
|
|
188
|
-
/**
|
|
189
|
-
* Nudge that resumes an implementation turn the runtime parked at a compaction
|
|
190
|
-
* boundary. It must let a turn that was genuinely finished (then tipped over the
|
|
191
|
-
* threshold by its own final message) confirm completion without inventing busywork
|
|
192
|
-
* — we cannot tell "paused mid-task by compaction" from "finished, then compacted"
|
|
193
|
-
* from the boundary alone, so the wording lets a done turn end in one line.
|
|
194
|
-
*/
|
|
195
|
-
export declare const CONTINUE_AFTER_COMPACTION: string;
|
|
196
|
-
/**
|
|
197
|
-
* Safety cap on compaction-driven resumes for a single implementation turn. Each
|
|
198
|
-
* resume follows a real compaction (which only fires after the model produced a
|
|
199
|
-
* turn large enough to cross the threshold), so a legitimately large task may
|
|
200
|
-
* resume a handful of times; the cap exists only to stop a pathological loop from
|
|
201
|
-
* auto-sending forever with no user in the loop. Hitting it stops resuming and lets
|
|
202
|
-
* the verify gate / `/task-auto-resume` catch any leftover incompleteness.
|
|
203
|
-
*/
|
|
204
|
-
export declare const MAX_COMPACTION_RESUMES = 20;
|
|
205
|
-
/**
|
|
206
|
-
* Resume an implementation turn that went idle at a threshold-compaction boundary.
|
|
207
|
-
* The runtime compacts and parks at idle without auto-continuing; we send a
|
|
208
|
-
* continue and wait again, repeating across successive compactions until the turn
|
|
209
|
-
* ends on a real assistant message (genuine completion). A user ESC takes priority
|
|
210
|
-
* (it is not a compaction boundary, and `wasInterrupted` guards the loop so the
|
|
211
|
-
* steer loop handles it), and the safety cap bounds a runaway. Returns the number
|
|
212
|
-
* of resumes performed (0 when the turn did not end on a compaction).
|
|
213
|
-
*/
|
|
214
|
-
export declare function resumeAcrossCompactions(ctx: SteerCtx): Promise<number>;
|
|
215
|
-
/**
|
|
216
|
-
* Timing knobs for the watchdog-abort guard in {@link steerUntilDone}, injectable
|
|
217
|
-
* so tests exercise the grace expiry without a 10-second wait. `graceMs` bounds
|
|
218
|
-
* how long the loop waits for the watchdog's follow-up to be DELIVERED (not to
|
|
219
|
-
* finish — its turn may legitimately run for minutes afterwards); delivery is
|
|
220
|
-
* normally near-instant, so the grace only expires on a stale flag.
|
|
221
|
-
*/
|
|
222
|
-
export interface SteerWatchdogDeps {
|
|
223
|
-
consume: () => boolean;
|
|
224
|
-
graceMs: number;
|
|
225
|
-
pollMs: number;
|
|
226
|
-
}
|
|
227
|
-
/**
|
|
228
|
-
* After the implementation turn settles, honour a user ESC by letting them steer.
|
|
229
|
-
*
|
|
230
|
-
* `waitForIdle` resolves both on natural completion AND on an ESC (which aborts
|
|
231
|
-
* the turn → idle). When the last turn was aborted, the host's main input loop is
|
|
232
|
-
* blocked inside our command handler, so a message typed in the editor would only
|
|
233
|
-
* queue, never run (interactive-mode routes idle input through onInputCallback,
|
|
234
|
-
* which is unset while we hold the loop). We therefore solicit the steering text
|
|
235
|
-
* ourselves and feed it back as another turn via sendUserMessage — which runs to
|
|
236
|
-
* completion when the session is idle. Repeat until a turn finishes uninterrupted.
|
|
237
|
-
*
|
|
238
|
-
* A WATCHDOG abort also ends the turn with stopReason 'aborted' — indistinguishable
|
|
239
|
-
* from a human ESC by the session entries alone at that instant. The watchdog
|
|
240
|
-
* queues its own recovery follow-up, so prompting there would show a steering
|
|
241
|
-
* dialog to an empty room and wedge an unattended run on the race. The one-shot
|
|
242
|
-
* flag (set synchronously before the abort) routes that case to
|
|
243
|
-
* {@link awaitWatchdogFollowUp} instead; a stale flag degrades to a bounded wait
|
|
244
|
-
* followed by the ordinary prompt, never to a suppressed one.
|
|
245
|
-
*
|
|
246
|
-
* Returns true when the user declined to steer (empty/cancelled) and the run
|
|
247
|
-
* should pause; false when the implementation completed (steered or not).
|
|
248
|
-
*/
|
|
249
|
-
export declare function steerUntilDone(ctx: SteerCtx, promptSteer?: (ctx: ExtensionCommandContext) => Promise<string | undefined>, watchdog?: Partial<SteerWatchdogDeps>): Promise<boolean>;
|
|
250
185
|
/**
|
|
251
186
|
* Run one prompt through the full single-task pipeline in a fresh session and
|
|
252
187
|
* deliver its spec. With waitForImplementation, block until the agent finishes
|