@ordewell/core 0.10.2 → 0.10.4
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/dist/{IFileSystem-CyTIoiSn.d.ts → IFileSystem-_effm8-b.d.ts} +1 -1
- package/dist/{IFileSystem-j0naIoAQ.d.mts → IFileSystem-vyqMdL-x.d.mts} +1 -1
- package/dist/{IRunner---PfQVdc.d.mts → IRunner-D_UkkMbJ.d.ts} +202 -196
- package/dist/{IRunner-WwL-Bjqq.d.ts → IRunner-NtTHQgLR.d.mts} +202 -196
- package/dist/{ModeResolver-DrMU-5lw.d.ts → ModeResolver-Dy3Ef6p-.d.ts} +1 -1
- package/dist/{ModeResolver-CjQ7zqTN.d.mts → ModeResolver-wnTFsDnF.d.mts} +1 -1
- package/dist/{Task-AfA1m4qW.d.mts → Task-DZs9eKgg.d.mts} +25 -6
- package/dist/{Task-AfA1m4qW.d.ts → Task-DZs9eKgg.d.ts} +25 -6
- package/dist/{chunk-4FEARWTS.mjs → chunk-645FALZX.mjs} +5 -5
- package/dist/chunk-645FALZX.mjs.map +1 -0
- package/dist/{chunk-3LNZDBCX.mjs → chunk-A57ITQE7.mjs} +35 -16
- package/dist/chunk-A57ITQE7.mjs.map +1 -0
- package/dist/{chunk-UQGR3UCA.mjs → chunk-FH6M5QZG.mjs} +136 -122
- package/dist/chunk-FH6M5QZG.mjs.map +1 -0
- package/dist/{chunk-CIL3BQIF.mjs → chunk-SM6BV6XS.mjs} +17 -3
- package/dist/chunk-SM6BV6XS.mjs.map +1 -0
- package/dist/index.d.mts +427 -40
- package/dist/index.d.ts +427 -40
- package/dist/index.js +2589 -1498
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +10799 -9764
- package/dist/index.mjs.map +1 -1
- package/dist/parsing.d.mts +2 -2
- package/dist/parsing.d.ts +2 -2
- package/dist/parsing.js +12 -12
- package/dist/parsing.js.map +1 -1
- package/dist/parsing.mjs +3 -3
- package/dist/{plan-utils-DL7yOuJs.d.mts → plan-utils-CtKceSsQ.d.mts} +1292 -1147
- package/dist/{plan-utils-gpeqV-Lp.d.ts → plan-utils-POIz5d8z.d.ts} +1292 -1147
- package/dist/plan-utils.d.mts +4 -4
- package/dist/plan-utils.d.ts +4 -4
- package/dist/plan-utils.js +16 -0
- package/dist/plan-utils.js.map +1 -1
- package/dist/plan-utils.mjs +7 -3
- package/dist/testing.d.mts +3 -3
- package/dist/testing.d.ts +3 -3
- package/dist/testing.js +3 -1
- package/dist/testing.js.map +1 -1
- package/dist/testing.mjs +4 -2
- package/dist/testing.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-3LNZDBCX.mjs.map +0 -1
- package/dist/chunk-4FEARWTS.mjs.map +0 -1
- package/dist/chunk-CIL3BQIF.mjs.map +0 -1
- package/dist/chunk-UQGR3UCA.mjs.map +0 -1
|
@@ -1,6 +1,17 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { R as RunnerModeInfo } from './ModeResolver-
|
|
3
|
-
import { B as IRunnerSession, aC as TaskMessageFrom, ai as RunnerCommand, an as RunnerMcpServer, av as StructuredTurnEnd, j as ApprovalDecision, au as StructuredEvent,
|
|
1
|
+
import { af as Task, a2 as RunnerId, an as TaskSnapshot, b as AwaitingReason, ar as UsageRecord, d as DiscoveredModel, ac as SkillsService, a7 as SkillInfo, X as RepoGroupLayout, I as IWorktreeIsolation, w as IsolationRun, k as IsolationHandoff, J as PlanIsolation, x as IsolationTaskRecord, ai as TaskIsolation, H as IsolationView, R as PrepareIntent, U as PreviousWork, s as IsolationOutcome, W as RepairEvidence, ap as TreeSnapshot, ag as TaskDiffRead, ah as TaskDiffResult, r as IsolationMergeResult, at as Verdict, am as TaskSkillSnapshot, al as TaskSkillNotice, ad as SubagentOutcome, L as LegacyPlanState, C as ConversationMessage, a9 as SkillLoadNotice, P as PendingPlanTurn, a as AttemptUsage, M as ModelUsage, as as UsageTotals, O as PlanUsage, f as ExecutionRecord, _ as ResearchStep, a1 as RunSettings, K as PlanState, ae as SupervisorLevel, $ as ResearchStepOutcome, Y as ResearchLogEntry, Q as PlannerUsage, aj as TaskIsolationState, aq as UsageLine } from './Task-DZs9eKgg.js';
|
|
2
|
+
import { R as RunnerModeInfo } from './ModeResolver-Dy3Ef6p-.js';
|
|
3
|
+
import { B as IRunnerSession, y as IConfig, aC as TaskMessageFrom, ai as RunnerCommand, an as RunnerMcpServer, av as StructuredTurnEnd, j as ApprovalDecision, au as StructuredEvent, ab as QueuedTaskMessage, k as ApprovalKind, q as ApprovalSource, z as IRunner, aq as RunnerRegistry } from './IRunner-D_UkkMbJ.js';
|
|
4
|
+
|
|
5
|
+
type NotificationAction = {
|
|
6
|
+
label: string;
|
|
7
|
+
value: string;
|
|
8
|
+
};
|
|
9
|
+
interface INotification {
|
|
10
|
+
info(msg: string): void;
|
|
11
|
+
warn(msg: string): void;
|
|
12
|
+
error(msg: string): void;
|
|
13
|
+
confirm(msg: string, options: string[]): Promise<string | undefined>;
|
|
14
|
+
}
|
|
4
15
|
|
|
5
16
|
interface LiveTailOptions {
|
|
6
17
|
maxLines: number;
|
|
@@ -33,6 +44,126 @@ interface TaskOutputSource {
|
|
|
33
44
|
liveTail(taskId: string, opts: LiveTailOptions): LiveTail | null;
|
|
34
45
|
}
|
|
35
46
|
|
|
47
|
+
/**
|
|
48
|
+
* The deep module owning all plan-shaped state. Holds `planTasks` (the ordered
|
|
49
|
+
* tree the user edits), the flattened `allTasks` view and the `taskMap` index.
|
|
50
|
+
* The orchestrator calls `markCompleted`/`markFailed`/`markInProgress`/`retry`
|
|
51
|
+
* to update task status — it never mutates task state directly.
|
|
52
|
+
*
|
|
53
|
+
* Completion and failure are read from `task.status` and nothing else, so
|
|
54
|
+
* `isCompleted`, `completedCount`, `isAllComplete` and the scheduler's
|
|
55
|
+
* dependency checks cannot disagree. The store owns its task objects: `load`
|
|
56
|
+
* copies what it is given, the getters hand out readonly views, and
|
|
57
|
+
* `snapshot` is the copy a caller may keep or write to disk.
|
|
58
|
+
*
|
|
59
|
+
* `rebuild` is the internal seam that keeps the flat views in sync with the
|
|
60
|
+
* tree.
|
|
61
|
+
*
|
|
62
|
+
* `planRunners` lives here because it's part of the plan's identity (the runner
|
|
63
|
+
* set, carried on the plan). `validateAssignedRunners` is pure store logic.
|
|
64
|
+
*/
|
|
65
|
+
declare class PlanStore {
|
|
66
|
+
private _planTasks;
|
|
67
|
+
private _allTasks;
|
|
68
|
+
private _taskMap;
|
|
69
|
+
/** Task id → the top-level task it hangs under, itself for a top-level task. */
|
|
70
|
+
private _rootMap;
|
|
71
|
+
private _planRunners;
|
|
72
|
+
private _onMutate;
|
|
73
|
+
private _onTree;
|
|
74
|
+
private _executionLog;
|
|
75
|
+
/** Hook called after every structural mutation (add/remove/update/resetForRun). */
|
|
76
|
+
set onMutate(cb: (() => void) | null);
|
|
77
|
+
/**
|
|
78
|
+
* Called with every task id, subtasks included, each time the tree is
|
|
79
|
+
* rebuilt — on every route a task enters or leaves the plan, loads and
|
|
80
|
+
* reconciled edits included, which `onMutate` does not hear.
|
|
81
|
+
*/
|
|
82
|
+
set onTree(cb: ((ids: ReadonlySet<string>) => void) | null);
|
|
83
|
+
get planTasks(): ReadonlyArray<Readonly<Task>>;
|
|
84
|
+
get allTasks(): ReadonlyArray<Readonly<Task>>;
|
|
85
|
+
get planRunners(): RunnerId[];
|
|
86
|
+
get completedCount(): number;
|
|
87
|
+
get failedCount(): number;
|
|
88
|
+
isAllComplete(): boolean;
|
|
89
|
+
isAnyFailed(): boolean;
|
|
90
|
+
isCompleted(id: string): boolean;
|
|
91
|
+
isSkipped(id: string): boolean;
|
|
92
|
+
/** Nothing is left to do: every task completed or was skipped. */
|
|
93
|
+
isFinished(): boolean;
|
|
94
|
+
isFailed(id: string): boolean;
|
|
95
|
+
get(taskId: string): Readonly<Task> | undefined;
|
|
96
|
+
/**
|
|
97
|
+
* Whether a task runs as an ops task (ADR-0020). Only a top-level AI task
|
|
98
|
+
* can be one; a subtask runs with the top-level task it hangs under.
|
|
99
|
+
*/
|
|
100
|
+
isOps(taskId: string): boolean;
|
|
101
|
+
/** A copy of the task tree, detached from the store: later status changes do not reach it. */
|
|
102
|
+
snapshot(): Task[];
|
|
103
|
+
getExecutionLog(): ReadonlyArray<TaskSnapshot>;
|
|
104
|
+
appendToLog(snapshot: TaskSnapshot): void;
|
|
105
|
+
/**
|
|
106
|
+
* Drop a task's archived snapshot. Un-marking a completion has to erase the
|
|
107
|
+
* "finished" record too — dependent tasks are prompted from the log, so a
|
|
108
|
+
* left-behind snapshot would keep feeding them a result that no longer exists.
|
|
109
|
+
*/
|
|
110
|
+
removeFromLog(taskId: string): void;
|
|
111
|
+
clearLog(): void;
|
|
112
|
+
private notifyMutate;
|
|
113
|
+
private countStatus;
|
|
114
|
+
load(tasks: ReadonlyArray<Readonly<Task>>, runners: readonly RunnerId[]): void;
|
|
115
|
+
add(partial: Partial<Task>): Task;
|
|
116
|
+
remove(taskId: string): void;
|
|
117
|
+
update(taskId: string, changes: Partial<Task>): Task | undefined;
|
|
118
|
+
/**
|
|
119
|
+
* Run preparation as one named op: flip every AI task to 'approved'. By
|
|
120
|
+
* default completed tasks keep their status so a reloaded half-finished plan
|
|
121
|
+
* resumes the remainder instead of re-running work that already succeeded,
|
|
122
|
+
* and skipped ones stay skipped: a new run is not a retry of them.
|
|
123
|
+
* `preserveCompleted: false` is for a freshly committed plan, where
|
|
124
|
+
* everything starts over. `keepScheduledResumes` leaves a task waiting on
|
|
125
|
+
* its runner's usage-limit reset waiting: an edit adopted between runs is
|
|
126
|
+
* not the user starting over.
|
|
127
|
+
*/
|
|
128
|
+
resetForRun(opts?: {
|
|
129
|
+
preserveCompleted?: boolean;
|
|
130
|
+
keepScheduledResumes?: boolean;
|
|
131
|
+
}): void;
|
|
132
|
+
markCompleted(id: string): void;
|
|
133
|
+
markFailed(id: string): void;
|
|
134
|
+
markSkipped(id: string, reason?: string): void;
|
|
135
|
+
markInProgress(id: string): void;
|
|
136
|
+
markAwaitingUser(id: string, reason?: AwaitingReason): void;
|
|
137
|
+
/** When a task paused on a usage limit resumes on its own; undefined leaves it to a manual retry. */
|
|
138
|
+
setTaskResumeAt(id: string, resumeAt: string | undefined): void;
|
|
139
|
+
markPending(id: string): void;
|
|
140
|
+
retry(id: string): void;
|
|
141
|
+
/** Kept on the task: it was force-started past its merge gate, before these ancestors' work was merged (ADR-0020). */
|
|
142
|
+
setForcedPastGate(id: string, dependencies: string[]): void;
|
|
143
|
+
/** A reason outlives nothing: any status but `awaiting_user` drops it, and the resume time with it; any status drops a skip's reason. */
|
|
144
|
+
private setStatus;
|
|
145
|
+
blockDependents(id: string): void;
|
|
146
|
+
unblockDependents(id: string): void;
|
|
147
|
+
setTaskVerdict(id: string, verdict: Task['verdict']): void;
|
|
148
|
+
setTaskOutputSummary(id: string, summary: Task['outputSummary']): void;
|
|
149
|
+
setTaskRunnerSessionId(id: string, runnerSessionId: string | undefined): void;
|
|
150
|
+
clearCarriedFrom(id: string): void;
|
|
151
|
+
startAttemptUsage(id: string, attempt: number, runner: string, startedAt: string): void;
|
|
152
|
+
recordAttemptUsage(id: string, attempt: number, record: UsageRecord, now: string): void;
|
|
153
|
+
endAttemptUsage(id: string, attempt: number, now: string): void;
|
|
154
|
+
setTaskAttemptSkills(id: string, skills: Task['attemptSkills']): void;
|
|
155
|
+
/**
|
|
156
|
+
* Widen the plan's runner set. Retargeting a task onto a runner the plan has
|
|
157
|
+
* not used before has to land here as well as on the plan state, or
|
|
158
|
+
* {@link resolveTaskRunner} reads the new runner as foreign and spawns the
|
|
159
|
+
* plan's first one instead.
|
|
160
|
+
*/
|
|
161
|
+
admitRunner(runner: RunnerId): void;
|
|
162
|
+
resolveTaskRunner(task: Readonly<Task>): RunnerId;
|
|
163
|
+
private rebuild;
|
|
164
|
+
private validateAssignedRunners;
|
|
165
|
+
}
|
|
166
|
+
|
|
36
167
|
/**
|
|
37
168
|
* The catalog a model/task-mode edit is checked against — the same discovered
|
|
38
169
|
* models and manifest modes the planner was shown in the per-turn catalog
|
|
@@ -100,6 +231,8 @@ interface ApplyTaskOpsResult {
|
|
|
100
231
|
errors: string[];
|
|
101
232
|
/** Human-readable summary of what changed, for the chat transcript. */
|
|
102
233
|
summary: string[];
|
|
234
|
+
/** On ok, the id of the task each batch handle names. */
|
|
235
|
+
handles?: ReadonlyMap<string, string>;
|
|
103
236
|
}
|
|
104
237
|
/**
|
|
105
238
|
* Pre-flight check for a merge: would collapsing the selected tasks into one
|
|
@@ -200,454 +333,119 @@ interface PlannerModes {
|
|
|
200
333
|
taskSkills?: SkillLookup;
|
|
201
334
|
}
|
|
202
335
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
*/
|
|
212
|
-
type TaskLogEvent =
|
|
213
|
-
/**
|
|
214
|
-
* The task skills this attempt's prompt carried, as read at its spawn
|
|
215
|
-
* (ADR-0024): first in its log. The file keeps their bodies; a surface is
|
|
216
|
-
* sent notices ({@link taskLogForSurface}).
|
|
217
|
-
*/
|
|
218
|
-
{
|
|
219
|
-
type: 'task_skills';
|
|
220
|
-
skills: TaskSkillSnapshot[] | TaskSkillNotice[];
|
|
336
|
+
type IsolationNoticeLevel = 'info' | 'warn' | 'error';
|
|
337
|
+
/** How an attempt that may run in a worktree gets one: fresh, the kept one for a repair, or restoring the previous attempt's work. */
|
|
338
|
+
type AttemptIntent = 'fresh' | 'repair' | PrepareIntent;
|
|
339
|
+
interface AttemptWorkspace {
|
|
340
|
+
cwd: string;
|
|
341
|
+
worktree: boolean;
|
|
342
|
+
/** Set for a `restore` in a worktree; absent in the checkout, where the previous attempt's edits remain. */
|
|
343
|
+
previousWork?: PreviousWork;
|
|
221
344
|
}
|
|
222
345
|
/**
|
|
223
|
-
*
|
|
224
|
-
*
|
|
225
|
-
* message events marks one Ordewell sent rather than the user.
|
|
346
|
+
* What the controller reports. It never schedules or emits on its own: the
|
|
347
|
+
* orchestrator turns these into observer events and decides what runs next.
|
|
226
348
|
*/
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
349
|
+
interface IsolationRunListener {
|
|
350
|
+
/**
|
|
351
|
+
* The run record changed and should be persisted with the plan. `detached`
|
|
352
|
+
* is set instead when the record that changed is no longer the plan's own —
|
|
353
|
+
* a release that outlived the plan load that replaced its run — and belongs
|
|
354
|
+
* to the session that was left.
|
|
355
|
+
*/
|
|
356
|
+
changed(detached?: IsolationRun): void;
|
|
357
|
+
/** A run did not start: `repos` of the group have tracked changes. */
|
|
358
|
+
blocked(repos: string[]): void;
|
|
359
|
+
/** An isolated run closed and handed its integration branches over. */
|
|
360
|
+
handoff(handoff: IsolationHandoff): void;
|
|
361
|
+
/** Already said through the notifications; also for a surface that shows no toasts. */
|
|
362
|
+
notice(level: IsolationNoticeLevel, message: string): void;
|
|
363
|
+
}
|
|
364
|
+
interface IsolationRunControllerDeps {
|
|
365
|
+
isolation: IWorktreeIsolation;
|
|
366
|
+
config: IConfig;
|
|
367
|
+
notifications: INotification;
|
|
368
|
+
workspaceRoot: () => string;
|
|
369
|
+
listener: IsolationRunListener;
|
|
370
|
+
/**
|
|
371
|
+
* The tasks no clean-up of the whole run may take a worktree from: an
|
|
372
|
+
* attempt is running them, or the plan has them in progress or waiting on
|
|
373
|
+
* the user mid-attempt.
|
|
374
|
+
*/
|
|
375
|
+
liveTasks: () => ReadonlySet<string>;
|
|
233
376
|
}
|
|
234
377
|
/**
|
|
235
|
-
*
|
|
236
|
-
*
|
|
378
|
+
* The lifecycle of a plan's isolation run (ADR-0013, ADR-0014, ADR-0020),
|
|
379
|
+
* between the scheduler and the git layer: deciding, at a run's first change
|
|
380
|
+
* task, whether it executes in worktrees, a blocked run and how it goes on,
|
|
381
|
+
* each attempt's working directory, releasing worktrees, which landed work the
|
|
382
|
+
* user has merged, the tree check of an ops task, the handoff when the run
|
|
383
|
+
* closes, and what the user does with it afterwards. The record it keeps
|
|
384
|
+
* outlives one run — a resumed plan continues it.
|
|
237
385
|
*/
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
/** The runner compacted its own context, asked to or on its own. */
|
|
250
|
-
| {
|
|
251
|
-
type: 'compaction';
|
|
252
|
-
trigger: 'manual' | 'auto';
|
|
253
|
-
preTokens?: number;
|
|
254
|
-
postTokens?: number;
|
|
255
|
-
}
|
|
256
|
-
/** What the runner loaded from the user's and the project's setup, as its process started. */
|
|
257
|
-
| {
|
|
258
|
-
type: 'runner_loaded';
|
|
259
|
-
commands: RunnerCommand[];
|
|
260
|
-
mcpServers: RunnerMcpServer[];
|
|
261
|
-
} | {
|
|
262
|
-
type: 'turn_end';
|
|
263
|
-
reason: StructuredTurnEnd;
|
|
264
|
-
} | {
|
|
265
|
-
type: 'text_delta';
|
|
266
|
-
text: string;
|
|
267
|
-
}
|
|
268
|
-
/** A complete run of the agent's reply, authoritative over the deltas streamed for it. */
|
|
269
|
-
| {
|
|
270
|
-
type: 'text';
|
|
271
|
-
text: string;
|
|
272
|
-
} | {
|
|
273
|
-
type: 'thinking_delta';
|
|
274
|
-
text: string;
|
|
275
|
-
subagentId?: string;
|
|
276
|
-
} | {
|
|
277
|
-
type: 'thinking';
|
|
278
|
-
text: string;
|
|
279
|
-
subagentId?: string;
|
|
280
|
-
}
|
|
281
|
-
/** `args` is the call's arguments as JSON, exactly as the runner announced them. */
|
|
282
|
-
| {
|
|
283
|
-
type: 'tool_call';
|
|
284
|
-
id: string;
|
|
285
|
-
name: string;
|
|
286
|
-
args: string;
|
|
287
|
-
subagentId?: string;
|
|
288
|
-
}
|
|
289
|
-
/** `omittedLines` is set when {@link trimToolOutput} cut the middle of `output`. */
|
|
290
|
-
| {
|
|
291
|
-
type: 'tool_result';
|
|
292
|
-
id: string;
|
|
293
|
-
output: string;
|
|
294
|
-
success: boolean;
|
|
295
|
-
omittedLines?: number;
|
|
296
|
-
subagentId?: string;
|
|
297
|
-
} | {
|
|
298
|
-
type: 'subagent_started';
|
|
299
|
-
subagentId: string;
|
|
300
|
-
brief: string;
|
|
301
|
-
model?: string;
|
|
302
|
-
} | {
|
|
303
|
-
type: 'subagent_finished';
|
|
304
|
-
subagentId: string;
|
|
305
|
-
outcome: SubagentOutcome;
|
|
306
|
-
digest: string;
|
|
307
|
-
} | {
|
|
308
|
-
type: 'usage';
|
|
309
|
-
record: UsageRecord;
|
|
310
|
-
}
|
|
311
|
-
/**
|
|
312
|
-
* Waiting in Ordewell's queue — again, for a message the runner was handed
|
|
313
|
-
* and let go of (ADR-0023, D4). `forced` moves it ahead of the rest (F2).
|
|
314
|
-
*/
|
|
315
|
-
| {
|
|
316
|
-
type: 'message_queued';
|
|
317
|
-
messageId: string;
|
|
318
|
-
text: string;
|
|
319
|
-
forced?: boolean;
|
|
320
|
-
from?: TaskMessageFrom;
|
|
321
|
-
} | {
|
|
322
|
-
type: 'message_removed';
|
|
323
|
-
messageId: string;
|
|
324
|
-
} | {
|
|
325
|
-
type: 'message_undelivered';
|
|
326
|
-
messageId: string;
|
|
327
|
-
text: string;
|
|
328
|
-
from?: TaskMessageFrom;
|
|
329
|
-
}
|
|
330
|
-
/** The runner accepted a queued message into its running turn; it can no longer be taken back. */
|
|
331
|
-
| {
|
|
332
|
-
type: 'message_handed_over';
|
|
333
|
-
messageId: string;
|
|
334
|
-
}
|
|
335
|
-
/**
|
|
336
|
-
* The model read a message inside its running turn (ADR-0023, Q2), logged
|
|
337
|
-
* where it read it. A message that opened a turn is that turn's `turn_start`.
|
|
338
|
-
*/
|
|
339
|
-
| {
|
|
340
|
-
type: 'message_delivered';
|
|
341
|
-
messageId: string;
|
|
342
|
-
text: string;
|
|
343
|
-
from?: TaskMessageFrom;
|
|
344
|
-
}
|
|
345
|
-
/** The agent's own words for a failed turn. */
|
|
346
|
-
| {
|
|
347
|
-
type: 'error';
|
|
348
|
-
message: string;
|
|
349
|
-
}
|
|
350
|
-
/**
|
|
351
|
-
* The runner asked to use a tool its mode does not cover (ADR-0018, A1).
|
|
352
|
-
* `approvalId` is what the answer is given under; `args` is the call's
|
|
353
|
-
* arguments as JSON; `allowForTask` says the runner offered its own
|
|
354
|
-
* session-scoped grant. `decided` is set when the request was never left
|
|
355
|
-
* open — an Ordewell tool or an auto-approving mode — so a surface does not
|
|
356
|
-
* offer to answer what is already answered (it is still followed by the
|
|
357
|
-
* matching `approval_decided`, which a reader may rely on).
|
|
358
|
-
*/
|
|
359
|
-
| {
|
|
360
|
-
type: 'approval_requested';
|
|
361
|
-
approvalId: string;
|
|
362
|
-
tool: string;
|
|
363
|
-
args: string;
|
|
364
|
-
allowForTask: boolean;
|
|
365
|
-
toolCallId?: string;
|
|
366
|
-
decided?: ApprovalDecision['decision'];
|
|
367
|
-
} | {
|
|
368
|
-
type: 'approval_decided';
|
|
369
|
-
approvalId: string;
|
|
370
|
-
decision: ApprovalDecision['decision'];
|
|
371
|
-
note?: string;
|
|
372
|
-
}
|
|
373
|
-
/** The request went unanswered: the runner withdrew it, or its process ended. */
|
|
374
|
-
| {
|
|
375
|
-
type: 'approval_withdrawn';
|
|
376
|
-
approvalId: string;
|
|
377
|
-
}
|
|
378
|
-
/**
|
|
379
|
-
* The runner's process ended. A call it had not reported back on is not
|
|
380
|
-
* running any more: a task's runner is ended the moment its `task_complete`
|
|
381
|
-
* lands, often before it reports that call's result.
|
|
382
|
-
*/
|
|
383
|
-
| {
|
|
384
|
-
type: 'runner_ended';
|
|
385
|
-
};
|
|
386
|
-
/**
|
|
387
|
-
* Keep a tool result's head and tail. A build log or a whole file read can
|
|
388
|
-
* run to megabytes, and every attempt's file keeps it for good; the ends are
|
|
389
|
-
* where a reader looks — the command's start, and how it finished.
|
|
390
|
-
*/
|
|
391
|
-
declare function trimToolOutput(output: string): {
|
|
392
|
-
output: string;
|
|
393
|
-
omittedLines?: number;
|
|
394
|
-
};
|
|
395
|
-
/**
|
|
396
|
-
* The log entry for one structured event, or null for what the log does not
|
|
397
|
-
* keep: background-agent launches, which the subagent's own events already show.
|
|
398
|
-
*/
|
|
399
|
-
declare function toTaskLogEvent(event: StructuredEvent): TaskLogEvent | null;
|
|
400
|
-
/** A log as a surface is sent it, live or read back: everything but the skills' bodies. */
|
|
401
|
-
declare function taskLogForSurface(events: readonly TaskLogEvent[]): TaskLogEvent[];
|
|
402
|
-
/**
|
|
403
|
-
* Merge runs of deltas into one event each. Deltas arrive a few characters at
|
|
404
|
-
* a time; merged, a batch is one message on the wire and one line on disk,
|
|
405
|
-
* and the view folds to the same blocks either way.
|
|
406
|
-
*/
|
|
407
|
-
declare function coalesceTaskLog(events: readonly TaskLogEvent[]): TaskLogEvent[];
|
|
408
|
-
/**
|
|
409
|
-
* What an attempt did to the world, for the retry of a task whose effects
|
|
410
|
-
* outlive it (ADR-0020): one line per tool call and how it ended.
|
|
411
|
-
*/
|
|
412
|
-
declare function digestTaskLog(events: readonly TaskLogEvent[], maxCalls: number): string;
|
|
413
|
-
|
|
414
|
-
type NotificationAction = {
|
|
415
|
-
label: string;
|
|
416
|
-
value: string;
|
|
417
|
-
};
|
|
418
|
-
interface INotification {
|
|
419
|
-
info(msg: string): void;
|
|
420
|
-
warn(msg: string): void;
|
|
421
|
-
error(msg: string): void;
|
|
422
|
-
confirm(msg: string, options: string[]): Promise<string | undefined>;
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
/**
|
|
426
|
-
* The deep module owning all plan-shaped state. Holds `planTasks` (the ordered
|
|
427
|
-
* tree the user edits), the flattened `allTasks` view and the `taskMap` index.
|
|
428
|
-
* The orchestrator calls `markCompleted`/`markFailed`/`markInProgress`/`retry`
|
|
429
|
-
* to update task status — it never mutates task state directly.
|
|
430
|
-
*
|
|
431
|
-
* Completion and failure are read from `task.status` and nothing else, so
|
|
432
|
-
* `isCompleted`, `completedCount`, `isAllComplete` and the scheduler's
|
|
433
|
-
* dependency checks cannot disagree. The store owns its task objects: `load`
|
|
434
|
-
* copies what it is given, the getters hand out readonly views, and
|
|
435
|
-
* `snapshot` is the copy a caller may keep or write to disk.
|
|
436
|
-
*
|
|
437
|
-
* `rebuild` is the internal seam that keeps the flat views in sync with the
|
|
438
|
-
* tree.
|
|
439
|
-
*
|
|
440
|
-
* `planRunners` lives here because it's part of the plan's identity (the runner
|
|
441
|
-
* set, carried on the plan). `validateAssignedRunners` is pure store logic.
|
|
442
|
-
*/
|
|
443
|
-
declare class PlanStore {
|
|
444
|
-
private _planTasks;
|
|
445
|
-
private _allTasks;
|
|
446
|
-
private _taskMap;
|
|
447
|
-
/** Task id → the top-level task it hangs under, itself for a top-level task. */
|
|
448
|
-
private _rootMap;
|
|
449
|
-
private _planRunners;
|
|
450
|
-
private _onMutate;
|
|
451
|
-
private _executionLog;
|
|
452
|
-
/** Hook called after every structural mutation (add/remove/update/resetForRun). */
|
|
453
|
-
set onMutate(cb: (() => void) | null);
|
|
454
|
-
get planTasks(): ReadonlyArray<Readonly<Task>>;
|
|
455
|
-
get allTasks(): ReadonlyArray<Readonly<Task>>;
|
|
456
|
-
get planRunners(): RunnerId[];
|
|
457
|
-
get completedCount(): number;
|
|
458
|
-
get failedCount(): number;
|
|
459
|
-
isAllComplete(): boolean;
|
|
460
|
-
isAnyFailed(): boolean;
|
|
461
|
-
isCompleted(id: string): boolean;
|
|
462
|
-
isSkipped(id: string): boolean;
|
|
463
|
-
/** Nothing is left to do: every task completed or was skipped. */
|
|
464
|
-
isFinished(): boolean;
|
|
465
|
-
isFailed(id: string): boolean;
|
|
466
|
-
get(taskId: string): Readonly<Task> | undefined;
|
|
386
|
+
declare class IsolationRunController {
|
|
387
|
+
private readonly isolation;
|
|
388
|
+
private readonly config;
|
|
389
|
+
private readonly notifications;
|
|
390
|
+
private readonly workspaceRoot;
|
|
391
|
+
private readonly listener;
|
|
392
|
+
private readonly liveTasks;
|
|
393
|
+
private run;
|
|
394
|
+
/** Copied paths already reported for the current run: every task gets the same copies. */
|
|
395
|
+
private reportedCopies;
|
|
396
|
+
private reportedSetupFailures;
|
|
467
397
|
/**
|
|
468
|
-
*
|
|
469
|
-
*
|
|
398
|
+
* How the open run executes; null while no run is open, `undecided` until
|
|
399
|
+
* its first change task asks. A run is one Execute-Plan or one manual task
|
|
400
|
+
* run, from its start until it settles or is stopped.
|
|
470
401
|
*/
|
|
471
|
-
|
|
472
|
-
/**
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
402
|
+
private mode;
|
|
403
|
+
/** Resolver task id → the conflicted task it resolves; see {@link linkResolver}. */
|
|
404
|
+
private resolvers;
|
|
405
|
+
private opening;
|
|
406
|
+
/** Bumped by an interrupt, so an activation in flight across it does not open the run it ended. */
|
|
407
|
+
private activations;
|
|
408
|
+
/** The start a dirty tree turned away, handed back once the user chooses how to go on. */
|
|
409
|
+
private blockedStart;
|
|
410
|
+
/** The dirty repos behind {@link blockedStart}, for the stash notice. */
|
|
411
|
+
private blockedRepos;
|
|
412
|
+
constructor(deps: IsolationRunControllerDeps);
|
|
413
|
+
/** The plan's run record, open or not; null when the plan has not isolated. */
|
|
414
|
+
get current(): IsolationRun | null;
|
|
415
|
+
get isOpen(): boolean;
|
|
416
|
+
/** The open run has decided whether it isolates. */
|
|
417
|
+
get decided(): boolean;
|
|
418
|
+
/** The open run executes in worktrees. */
|
|
419
|
+
get isolating(): boolean;
|
|
420
|
+
/** A run is waiting on the user to stash or to go on without isolation. */
|
|
421
|
+
get blocked(): boolean;
|
|
422
|
+
/** What the plan persists of isolated execution; null when no run ever isolated. */
|
|
423
|
+
get planIsolation(): PlanIsolation | null;
|
|
424
|
+
/** The one guard of every action on the run: a plan that never isolated is a request refused, not a fault. */
|
|
425
|
+
requireRun(): IsolationRun;
|
|
426
|
+
/** A task's record in the open run, only while that run isolates. */
|
|
427
|
+
openRecord(taskId: string): IsolationTaskRecord | undefined;
|
|
428
|
+
/** Where a task's isolated work stands; null when the plan has no isolation run to speak of. */
|
|
429
|
+
taskIsolation(taskId: string): TaskIsolation | null;
|
|
430
|
+
view(): IsolationView | null;
|
|
476
431
|
/**
|
|
477
|
-
*
|
|
478
|
-
*
|
|
479
|
-
*
|
|
432
|
+
* Take over a plan's persisted isolation, or none for a plan that has not
|
|
433
|
+
* isolated yet. Whatever a crashed process left behind for the run — a
|
|
434
|
+
* worktree still marked active, a directory no record owns — is pruned,
|
|
435
|
+
* while kept, failed and conflicted worktrees stay for the user.
|
|
436
|
+
*
|
|
437
|
+
* Deliberately not reported as a change: adopting is not a change to
|
|
438
|
+
* persist, and a host that adopts without persisting (VS Code's restore)
|
|
439
|
+
* would otherwise write a new session file on every reload.
|
|
480
440
|
*/
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
private countStatus;
|
|
485
|
-
load(tasks: ReadonlyArray<Readonly<Task>>, runners: readonly RunnerId[]): void;
|
|
486
|
-
add(partial: Partial<Task>): Task;
|
|
487
|
-
remove(taskId: string): void;
|
|
488
|
-
update(taskId: string, changes: Partial<Task>): Task | undefined;
|
|
441
|
+
adopt(state: PlanIsolation | null): Promise<void>;
|
|
442
|
+
/** Open a run if none is, leaving whether it isolates to its first change task. */
|
|
443
|
+
open(): void;
|
|
489
444
|
/**
|
|
490
|
-
*
|
|
491
|
-
*
|
|
492
|
-
*
|
|
493
|
-
*
|
|
494
|
-
* `preserveCompleted: false` is for a freshly committed plan, where
|
|
495
|
-
* everything starts over. `keepScheduledResumes` leaves a task waiting on
|
|
496
|
-
* its runner's usage-limit reset waiting: an edit adopted between runs is
|
|
497
|
-
* not the user starting over.
|
|
498
|
-
*/
|
|
499
|
-
resetForRun(opts?: {
|
|
500
|
-
preserveCompleted?: boolean;
|
|
501
|
-
keepScheduledResumes?: boolean;
|
|
502
|
-
}): void;
|
|
503
|
-
markCompleted(id: string): void;
|
|
504
|
-
markFailed(id: string): void;
|
|
505
|
-
markSkipped(id: string, reason?: string): void;
|
|
506
|
-
markInProgress(id: string): void;
|
|
507
|
-
markAwaitingUser(id: string, reason?: AwaitingReason): void;
|
|
508
|
-
/** When a task paused on a usage limit resumes on its own; undefined leaves it to a manual retry. */
|
|
509
|
-
setTaskResumeAt(id: string, resumeAt: string | undefined): void;
|
|
510
|
-
markPending(id: string): void;
|
|
511
|
-
retry(id: string): void;
|
|
512
|
-
/** Kept on the task: it was force-started past its merge gate, before these dependencies' work was merged (ADR-0020). */
|
|
513
|
-
setForcedPastGate(id: string, dependencies: string[]): void;
|
|
514
|
-
/** A reason outlives nothing: any status but `awaiting_user` drops it, and the resume time with it; any status drops a skip's reason. */
|
|
515
|
-
private setStatus;
|
|
516
|
-
blockDependents(id: string): void;
|
|
517
|
-
unblockDependents(id: string): void;
|
|
518
|
-
setTaskVerdict(id: string, verdict: Task['verdict']): void;
|
|
519
|
-
setTaskOutputSummary(id: string, summary: Task['outputSummary']): void;
|
|
520
|
-
setTaskRunnerSessionId(id: string, runnerSessionId: string | undefined): void;
|
|
521
|
-
clearCarriedFrom(id: string): void;
|
|
522
|
-
startAttemptUsage(id: string, attempt: number, runner: string, startedAt: string): void;
|
|
523
|
-
recordAttemptUsage(id: string, attempt: number, record: UsageRecord, now: string): void;
|
|
524
|
-
endAttemptUsage(id: string, attempt: number, now: string): void;
|
|
525
|
-
setTaskAttemptSkills(id: string, skills: Task['attemptSkills']): void;
|
|
526
|
-
/**
|
|
527
|
-
* Widen the plan's runner set. Retargeting a task onto a runner the plan has
|
|
528
|
-
* not used before has to land here as well as on the plan state, or
|
|
529
|
-
* {@link resolveTaskRunner} reads the new runner as foreign and spawns the
|
|
530
|
-
* plan's first one instead.
|
|
531
|
-
*/
|
|
532
|
-
admitRunner(runner: RunnerId): void;
|
|
533
|
-
resolveTaskRunner(task: Readonly<Task>): RunnerId;
|
|
534
|
-
private rebuild;
|
|
535
|
-
private validateAssignedRunners;
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
type IsolationNoticeLevel = 'info' | 'warn' | 'error';
|
|
539
|
-
/** How an attempt that may run in a worktree gets one: fresh, the kept one for a repair, or restoring the previous attempt's work. */
|
|
540
|
-
type AttemptIntent = 'fresh' | 'repair' | PrepareIntent;
|
|
541
|
-
interface AttemptWorkspace {
|
|
542
|
-
cwd: string;
|
|
543
|
-
worktree: boolean;
|
|
544
|
-
/** Set for a `restore` in a worktree; absent in the checkout, where the previous attempt's edits remain. */
|
|
545
|
-
previousWork?: PreviousWork;
|
|
546
|
-
}
|
|
547
|
-
/**
|
|
548
|
-
* What the controller reports. It never schedules or emits on its own: the
|
|
549
|
-
* orchestrator turns these into observer events and decides what runs next.
|
|
550
|
-
*/
|
|
551
|
-
interface IsolationRunListener {
|
|
552
|
-
/**
|
|
553
|
-
* The run record changed and should be persisted with the plan. `detached`
|
|
554
|
-
* is set instead when the record that changed is no longer the plan's own —
|
|
555
|
-
* a release that outlived the plan load that replaced its run — and belongs
|
|
556
|
-
* to the session that was left.
|
|
557
|
-
*/
|
|
558
|
-
changed(detached?: IsolationRun): void;
|
|
559
|
-
/** A run did not start: `repos` of the group have tracked changes. */
|
|
560
|
-
blocked(repos: string[]): void;
|
|
561
|
-
/** An isolated run closed and handed its integration branches over. */
|
|
562
|
-
handoff(handoff: IsolationHandoff): void;
|
|
563
|
-
/** Already said through the notifications; also for a surface that shows no toasts. */
|
|
564
|
-
notice(level: IsolationNoticeLevel, message: string): void;
|
|
565
|
-
}
|
|
566
|
-
interface IsolationRunControllerDeps {
|
|
567
|
-
isolation: IWorktreeIsolation;
|
|
568
|
-
config: IConfig;
|
|
569
|
-
notifications: INotification;
|
|
570
|
-
workspaceRoot: () => string;
|
|
571
|
-
listener: IsolationRunListener;
|
|
572
|
-
/**
|
|
573
|
-
* The tasks no clean-up of the whole run may take a worktree from: an
|
|
574
|
-
* attempt is running them, or the plan has them in progress or waiting on
|
|
575
|
-
* the user mid-attempt.
|
|
576
|
-
*/
|
|
577
|
-
liveTasks: () => ReadonlySet<string>;
|
|
578
|
-
}
|
|
579
|
-
/**
|
|
580
|
-
* The lifecycle of a plan's isolation run (ADR-0013, ADR-0014, ADR-0020),
|
|
581
|
-
* between the scheduler and the git layer: deciding, at a run's first change
|
|
582
|
-
* task, whether it executes in worktrees, a blocked run and how it goes on,
|
|
583
|
-
* each attempt's working directory, releasing worktrees, which landed work the
|
|
584
|
-
* user has merged, the tree check of an ops task, the handoff when the run
|
|
585
|
-
* closes, and what the user does with it afterwards. The record it keeps
|
|
586
|
-
* outlives one run — a resumed plan continues it.
|
|
587
|
-
*/
|
|
588
|
-
declare class IsolationRunController {
|
|
589
|
-
private readonly isolation;
|
|
590
|
-
private readonly config;
|
|
591
|
-
private readonly notifications;
|
|
592
|
-
private readonly workspaceRoot;
|
|
593
|
-
private readonly listener;
|
|
594
|
-
private readonly liveTasks;
|
|
595
|
-
private run;
|
|
596
|
-
/** Copied paths already reported for the current run: every task gets the same copies. */
|
|
597
|
-
private reportedCopies;
|
|
598
|
-
private reportedSetupFailures;
|
|
599
|
-
/**
|
|
600
|
-
* How the open run executes; null while no run is open, `undecided` until
|
|
601
|
-
* its first change task asks. A run is one Execute-Plan or one manual task
|
|
602
|
-
* run, from its start until it settles or is stopped.
|
|
603
|
-
*/
|
|
604
|
-
private mode;
|
|
605
|
-
/** Resolver task id → the conflicted task it resolves; see {@link linkResolver}. */
|
|
606
|
-
private resolvers;
|
|
607
|
-
private opening;
|
|
608
|
-
/** Bumped by an interrupt, so an activation in flight across it does not open the run it ended. */
|
|
609
|
-
private activations;
|
|
610
|
-
/** The start a dirty tree turned away, handed back once the user chooses how to go on. */
|
|
611
|
-
private blockedStart;
|
|
612
|
-
/** The dirty repos behind {@link blockedStart}, for the stash notice. */
|
|
613
|
-
private blockedRepos;
|
|
614
|
-
constructor(deps: IsolationRunControllerDeps);
|
|
615
|
-
/** The plan's run record, open or not; null when the plan has not isolated. */
|
|
616
|
-
get current(): IsolationRun | null;
|
|
617
|
-
get isOpen(): boolean;
|
|
618
|
-
/** The open run has decided whether it isolates. */
|
|
619
|
-
get decided(): boolean;
|
|
620
|
-
/** The open run executes in worktrees. */
|
|
621
|
-
get isolating(): boolean;
|
|
622
|
-
/** A run is waiting on the user to stash or to go on without isolation. */
|
|
623
|
-
get blocked(): boolean;
|
|
624
|
-
/** What the plan persists of isolated execution; null when no run ever isolated. */
|
|
625
|
-
get planIsolation(): PlanIsolation | null;
|
|
626
|
-
/** The one guard of every action on the run: a plan that never isolated is a request refused, not a fault. */
|
|
627
|
-
requireRun(): IsolationRun;
|
|
628
|
-
/** A task's record in the open run, only while that run isolates. */
|
|
629
|
-
openRecord(taskId: string): IsolationTaskRecord | undefined;
|
|
630
|
-
/** Where a task's isolated work stands; null when the plan has no isolation run to speak of. */
|
|
631
|
-
taskIsolation(taskId: string): TaskIsolation | null;
|
|
632
|
-
view(): IsolationView | null;
|
|
633
|
-
/**
|
|
634
|
-
* Take over a plan's persisted isolation, or none for a plan that has not
|
|
635
|
-
* isolated yet. Whatever a crashed process left behind for the run — a
|
|
636
|
-
* worktree still marked active, a directory no record owns — is pruned,
|
|
637
|
-
* while kept, failed and conflicted worktrees stay for the user.
|
|
638
|
-
*
|
|
639
|
-
* Deliberately not reported as a change: adopting is not a change to
|
|
640
|
-
* persist, and a host that adopts without persisting (VS Code's restore)
|
|
641
|
-
* would otherwise write a new session file on every reload.
|
|
642
|
-
*/
|
|
643
|
-
adopt(state: PlanIsolation | null): Promise<void>;
|
|
644
|
-
/** Open a run if none is, leaving whether it isolates to its first change task. */
|
|
645
|
-
open(): void;
|
|
646
|
-
/**
|
|
647
|
-
* Decide once per run, as its first change task starts, whether it executes
|
|
648
|
-
* in worktrees (ADR-0020): an ops task needs none, so a run of only ops
|
|
649
|
-
* tasks never asks. `resume` is what a dirty tree parks until the user
|
|
650
|
-
* chooses how to go on. Resolves false while the run waits on that choice.
|
|
445
|
+
* Decide once per run, as its first change task starts, whether it executes
|
|
446
|
+
* in worktrees (ADR-0020): an ops task needs none, so a run of only ops
|
|
447
|
+
* tasks never asks. `resume` is what a dirty tree parks until the user
|
|
448
|
+
* chooses how to go on. Resolves false while the run waits on that choice.
|
|
651
449
|
*/
|
|
652
450
|
decide(resume: () => Promise<void>): Promise<boolean>;
|
|
653
451
|
/**
|
|
@@ -948,728 +746,273 @@ interface WorkspaceEnv {
|
|
|
948
746
|
trackedEnvFile: string | null;
|
|
949
747
|
}
|
|
950
748
|
|
|
951
|
-
/** What ran an automatic Merge all: a task reaching its merge gate, or the run settling with landed work unmerged. */
|
|
952
|
-
type AutoMergeTrigger = {
|
|
953
|
-
kind: 'gate';
|
|
954
|
-
taskId: string;
|
|
955
|
-
} | {
|
|
956
|
-
kind: 'run_end';
|
|
957
|
-
};
|
|
958
749
|
/**
|
|
959
|
-
*
|
|
960
|
-
*
|
|
961
|
-
*
|
|
750
|
+
* One entry of a structured task's log (ADR-0018, P1): what a runner did,
|
|
751
|
+
* normalized and serializable, streamed live as `task_log` and appended to
|
|
752
|
+
* the attempt's `.jsonl` file. The live view and the reload fold the same
|
|
753
|
+
* events through `reduceTaskLog`, so the two cannot disagree.
|
|
754
|
+
*
|
|
755
|
+
* Readers skip a type they do not know, which is how later events join
|
|
756
|
+
* without a format version.
|
|
962
757
|
*/
|
|
963
|
-
type
|
|
964
|
-
/** What the scheduler reads of the open run's settings, at each decision it makes from them. */
|
|
965
|
-
type SchedulerRunSettings = Pick<RunSettings, 'mergeAtGatesAutomatically'>;
|
|
758
|
+
type TaskLogEvent =
|
|
966
759
|
/**
|
|
967
|
-
* The
|
|
968
|
-
*
|
|
969
|
-
*
|
|
760
|
+
* The task skills this attempt's prompt carried, as read at its spawn
|
|
761
|
+
* (ADR-0024): first in its log. The file keeps their bodies; a surface is
|
|
762
|
+
* sent notices ({@link taskLogForSurface}).
|
|
970
763
|
*/
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
/**
|
|
975
|
-
* A run reached an outcome that stands until the user acts — completed,
|
|
976
|
-
* failed, or awaiting the user — on its own, not as the answer to a call.
|
|
977
|
-
* Emitted before the `onTaskChanged` that announces it, so a listener can
|
|
978
|
-
* save the outcome before any surface is told.
|
|
979
|
-
*/
|
|
980
|
-
onTaskSettled?(data: {
|
|
981
|
-
taskId: string;
|
|
982
|
-
}): void;
|
|
983
|
-
onRunStarted?(data: {
|
|
984
|
-
origin: RunOrigin;
|
|
985
|
-
}): void;
|
|
986
|
-
/** An automatic Merge all settled, at a gate or at the run's end; failures are attention events for a supervisor. */
|
|
987
|
-
onAutoMerge?(data: {
|
|
988
|
-
trigger: AutoMergeTrigger;
|
|
989
|
-
result: IsolationMergeResult;
|
|
990
|
-
}): void;
|
|
991
|
-
onTick?(): void;
|
|
992
|
-
onUsageChanged?(): void;
|
|
993
|
-
onExecutionComplete?(): void;
|
|
994
|
-
/**
|
|
995
|
-
* A task paused on its runner's usage limit, or its resume changed:
|
|
996
|
-
* `resumeAt` is when it resumes on its own (ISO time), null when it waits
|
|
997
|
-
* for a manual retry — the reset could not be read, the resume was
|
|
998
|
-
* cancelled, or the resumed attempt hit the limit again at once.
|
|
999
|
-
* `cancelled` marks the resume being cancelled, which is no new pause.
|
|
1000
|
-
*/
|
|
1001
|
-
onUsageLimitWait?(data: {
|
|
1002
|
-
taskId: string;
|
|
1003
|
-
runner: string;
|
|
1004
|
-
resumeAt: string | null;
|
|
1005
|
-
cancelled?: true;
|
|
1006
|
-
}): void;
|
|
1007
|
-
/** A live task's runner went silent (ADR-0018's idle timer); paused while it waits on input, a checkpoint or an approval. */
|
|
1008
|
-
onTaskIdle?(data: {
|
|
1009
|
-
taskId: string;
|
|
1010
|
-
idleSince: string;
|
|
1011
|
-
}): void;
|
|
1012
|
-
onReviewNeeded?(data: {
|
|
1013
|
-
tasks: Task[];
|
|
1014
|
-
planRunners: RunnerId[];
|
|
1015
|
-
}): void;
|
|
1016
|
-
onReviewApproved?(data: {
|
|
1017
|
-
tasks: Task[];
|
|
1018
|
-
}): void;
|
|
1019
|
-
onCheckpoint?(data: {
|
|
1020
|
-
taskId: string;
|
|
1021
|
-
taskTitle: string;
|
|
1022
|
-
summary: string;
|
|
1023
|
-
}): void;
|
|
1024
|
-
/**
|
|
1025
|
-
* The isolation run record changed and should be persisted with the plan.
|
|
1026
|
-
* `detached` is the record of a run another plan has replaced since — a
|
|
1027
|
-
* release that outlived a plan load — for the session it belongs to.
|
|
1028
|
-
*/
|
|
1029
|
-
onIsolationChanged?(detached?: IsolationRun): void;
|
|
1030
|
-
/** A run did not start: the tree is dirty, and the user picks stash or no isolation. `repos` names the dirty repos of a group. */
|
|
1031
|
-
onIsolationBlocked?(data: {
|
|
1032
|
-
reason: 'dirty';
|
|
1033
|
-
repos: string[];
|
|
1034
|
-
}): void;
|
|
1035
|
-
/** An isolated run settled; emitted before `onExecutionComplete`, which surfaces treat as terminal. */
|
|
1036
|
-
onIsolationHandoff?(handoff: IsolationHandoff): void;
|
|
1037
|
-
/**
|
|
1038
|
-
* What a run says about how it isolates — the fallback to the workspace root,
|
|
1039
|
-
* shared paths, copies, a stash. Beside the notification channel, which a
|
|
1040
|
-
* daemon may leave unwired, so a surface without toasts can still show it.
|
|
1041
|
-
*/
|
|
1042
|
-
onIsolationNotice?(data: {
|
|
1043
|
-
level: 'info' | 'warn' | 'error';
|
|
1044
|
-
message: string;
|
|
1045
|
-
}): void;
|
|
764
|
+
{
|
|
765
|
+
type: 'task_skills';
|
|
766
|
+
skills: TaskSkillSnapshot[] | TaskSkillNotice[];
|
|
1046
767
|
}
|
|
1047
768
|
/**
|
|
1048
|
-
* A
|
|
1049
|
-
*
|
|
1050
|
-
*
|
|
769
|
+
* A turn began by delivering `message`; `messageId` names it when it had been
|
|
770
|
+
* queued, `forced` when it was force sent. `from` on this and the other
|
|
771
|
+
* message events marks one Ordewell sent rather than the user.
|
|
1051
772
|
*/
|
|
1052
|
-
|
|
1053
|
-
|
|
773
|
+
| {
|
|
774
|
+
type: 'turn_start';
|
|
775
|
+
message: string;
|
|
776
|
+
messageId?: string;
|
|
777
|
+
forced?: boolean;
|
|
778
|
+
from?: TaskMessageFrom;
|
|
1054
779
|
}
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
780
|
+
/**
|
|
781
|
+
* A turn began by running the runner's own command `/name args` (#66): the
|
|
782
|
+
* `turn_start` of a command turn. `messageId` names it when it had been queued.
|
|
783
|
+
*/
|
|
784
|
+
| {
|
|
785
|
+
type: 'command';
|
|
786
|
+
name: string;
|
|
787
|
+
args?: string;
|
|
788
|
+
messageId?: string;
|
|
789
|
+
}
|
|
790
|
+
/** What the running command printed, for one that runs without the model. */
|
|
791
|
+
| {
|
|
792
|
+
type: 'command_output';
|
|
793
|
+
text: string;
|
|
794
|
+
}
|
|
795
|
+
/** The runner compacted its own context, asked to or on its own. */
|
|
796
|
+
| {
|
|
797
|
+
type: 'compaction';
|
|
798
|
+
trigger: 'manual' | 'auto';
|
|
799
|
+
preTokens?: number;
|
|
800
|
+
postTokens?: number;
|
|
801
|
+
}
|
|
802
|
+
/** What the runner loaded from the user's and the project's setup, as its process started. */
|
|
803
|
+
| {
|
|
804
|
+
type: 'runner_loaded';
|
|
805
|
+
commands: RunnerCommand[];
|
|
806
|
+
mcpServers: RunnerMcpServer[];
|
|
807
|
+
} | {
|
|
808
|
+
type: 'turn_end';
|
|
809
|
+
reason: StructuredTurnEnd;
|
|
810
|
+
} | {
|
|
811
|
+
type: 'text_delta';
|
|
812
|
+
text: string;
|
|
813
|
+
}
|
|
814
|
+
/** A complete run of the agent's reply, authoritative over the deltas streamed for it. */
|
|
815
|
+
| {
|
|
816
|
+
type: 'text';
|
|
817
|
+
text: string;
|
|
818
|
+
} | {
|
|
819
|
+
type: 'thinking_delta';
|
|
820
|
+
text: string;
|
|
821
|
+
subagentId?: string;
|
|
822
|
+
} | {
|
|
823
|
+
type: 'thinking';
|
|
824
|
+
text: string;
|
|
825
|
+
subagentId?: string;
|
|
826
|
+
}
|
|
827
|
+
/** `args` is the call's arguments as JSON, exactly as the runner announced them. */
|
|
828
|
+
| {
|
|
829
|
+
type: 'tool_call';
|
|
830
|
+
id: string;
|
|
831
|
+
name: string;
|
|
832
|
+
args: string;
|
|
833
|
+
subagentId?: string;
|
|
834
|
+
}
|
|
835
|
+
/** `omittedLines` is set when {@link trimToolOutput} cut the middle of `output`. */
|
|
836
|
+
| {
|
|
837
|
+
type: 'tool_result';
|
|
838
|
+
id: string;
|
|
839
|
+
output: string;
|
|
840
|
+
success: boolean;
|
|
841
|
+
omittedLines?: number;
|
|
842
|
+
subagentId?: string;
|
|
843
|
+
} | {
|
|
844
|
+
type: 'subagent_started';
|
|
845
|
+
subagentId: string;
|
|
846
|
+
brief: string;
|
|
847
|
+
model?: string;
|
|
848
|
+
} | {
|
|
849
|
+
type: 'subagent_finished';
|
|
850
|
+
subagentId: string;
|
|
851
|
+
outcome: SubagentOutcome;
|
|
852
|
+
digest: string;
|
|
853
|
+
} | {
|
|
854
|
+
type: 'usage';
|
|
855
|
+
record: UsageRecord;
|
|
1066
856
|
}
|
|
1067
857
|
/**
|
|
1068
|
-
*
|
|
1069
|
-
*
|
|
1070
|
-
* one through it too. The constructor makes nothing itself, so there is exactly
|
|
1071
|
-
* one place defaults live.
|
|
858
|
+
* Waiting in Ordewell's queue — again, for a message the runner was handed
|
|
859
|
+
* and let go of (ADR-0023, D4). `forced` moves it ahead of the rest (F2).
|
|
1072
860
|
*/
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
/**
|
|
1093
|
-
* The open run's settings, read as each automatic merge is decided rather
|
|
1094
|
-
* than copied when the run opens, so a change to the run under way applies
|
|
1095
|
-
* at once (ADR-0030); absent before any run.
|
|
1096
|
-
*/
|
|
1097
|
-
runSettings: () => SchedulerRunSettings | undefined;
|
|
861
|
+
| {
|
|
862
|
+
type: 'message_queued';
|
|
863
|
+
messageId: string;
|
|
864
|
+
text: string;
|
|
865
|
+
forced?: boolean;
|
|
866
|
+
from?: TaskMessageFrom;
|
|
867
|
+
} | {
|
|
868
|
+
type: 'message_removed';
|
|
869
|
+
messageId: string;
|
|
870
|
+
} | {
|
|
871
|
+
type: 'message_undelivered';
|
|
872
|
+
messageId: string;
|
|
873
|
+
text: string;
|
|
874
|
+
from?: TaskMessageFrom;
|
|
875
|
+
}
|
|
876
|
+
/** The runner accepted a queued message into its running turn; it can no longer be taken back. */
|
|
877
|
+
| {
|
|
878
|
+
type: 'message_handed_over';
|
|
879
|
+
messageId: string;
|
|
1098
880
|
}
|
|
1099
881
|
/**
|
|
1100
|
-
*
|
|
1101
|
-
*
|
|
1102
|
-
* resolved wiring the orchestrator itself runs on.
|
|
882
|
+
* The model read a message inside its running turn (ADR-0023, Q2), logged
|
|
883
|
+
* where it read it. A message that opened a turn is that turn's `turn_start`.
|
|
1103
884
|
*/
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
skillsAt?: (roots: readonly string[]) => SkillLookup;
|
|
1115
|
-
previousAttemptFromLog?: (taskId: string) => string | null;
|
|
1116
|
-
runSettings?: () => SchedulerRunSettings | undefined;
|
|
885
|
+
| {
|
|
886
|
+
type: 'message_delivered';
|
|
887
|
+
messageId: string;
|
|
888
|
+
text: string;
|
|
889
|
+
from?: TaskMessageFrom;
|
|
890
|
+
}
|
|
891
|
+
/** The agent's own words for a failed turn. */
|
|
892
|
+
| {
|
|
893
|
+
type: 'error';
|
|
894
|
+
message: string;
|
|
1117
895
|
}
|
|
1118
896
|
/**
|
|
1119
|
-
* The
|
|
1120
|
-
* `
|
|
1121
|
-
*
|
|
1122
|
-
*
|
|
1123
|
-
*
|
|
1124
|
-
*
|
|
1125
|
-
*
|
|
1126
|
-
* evidence is a visible failure and does not unblock dependent work.
|
|
897
|
+
* The runner asked to use a tool its mode does not cover (ADR-0018, A1).
|
|
898
|
+
* `approvalId` is what the answer is given under; `args` is the call's
|
|
899
|
+
* arguments as JSON; `allowForTask` says the runner offered its own
|
|
900
|
+
* session-scoped grant. `decided` is set when the request was never left
|
|
901
|
+
* open — an Ordewell tool or an auto-approving mode — so a surface does not
|
|
902
|
+
* offer to answer what is already answered (it is still followed by the
|
|
903
|
+
* matching `approval_decided`, which a reader may rely on).
|
|
1127
904
|
*/
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
/**
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
/**
|
|
1200
|
-
|
|
1201
|
-
/**
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
*/
|
|
1208
|
-
private opsPreviousAttempt;
|
|
1209
|
-
/** Recent clean output of a task's latest attempt, running or ended; null if it never ran. */
|
|
1210
|
-
getLiveOutput(taskId: string, opts: LiveTailOptions): LiveTail | null;
|
|
1211
|
-
get storeInstance(): PlanStore;
|
|
1212
|
-
/**
|
|
1213
|
-
* The variables a task's agent gets from its workspace. What cannot be
|
|
1214
|
-
* applied is said once — silently starting without them is how agents ran
|
|
1215
|
-
* under the wrong account when an edited `.envrc` was left unallowed.
|
|
1216
|
-
*/
|
|
1217
|
-
private envForTask;
|
|
1218
|
-
approveCheckpoint(taskId: string): void;
|
|
1219
|
-
rejectCheckpoint(taskId: string, reason?: string): void;
|
|
1220
|
-
/** What the task's waiting checkpoint asks, whole; undefined once it settles or when none waits. */
|
|
1221
|
-
getCheckpointQuestion(taskId: string): string | undefined;
|
|
1222
|
-
awaitsCheckpoint(taskId: string): boolean;
|
|
1223
|
-
/**
|
|
1224
|
-
* Send a structured task a user message (ADR-0018, M1). Mid-turn it queues
|
|
1225
|
-
* behind the turn; a task waiting for input takes it at once and is back in
|
|
1226
|
-
* progress. Returns the message's id, for {@link removeQueuedTaskMessage}.
|
|
1227
|
-
*/
|
|
1228
|
-
sendTaskMessage(taskId: string, text: string): string;
|
|
1229
|
-
/**
|
|
1230
|
-
* Force send (ADR-0023, F1–F3): interrupt the running turn and deliver the
|
|
1231
|
-
* message as the next one, ahead of anything queued, without the task
|
|
1232
|
-
* waiting for input in between. A task already waiting for input just takes it.
|
|
1233
|
-
*/
|
|
1234
|
-
forceSendTaskMessage(taskId: string, text: string): string;
|
|
1235
|
-
/**
|
|
1236
|
-
* Run the task runner's own command `/name args` (#66) as a turn of its
|
|
1237
|
-
* own, waiting behind a running turn as a message would. Ordewell carries
|
|
1238
|
-
* it; what it does is the runner's business (#24).
|
|
1239
|
-
*/
|
|
1240
|
-
invokeTaskCommand(taskId: string, name: string, args?: string): string;
|
|
1241
|
-
private messageTask;
|
|
1242
|
-
/** Force send a message not yet delivered, queued or handed over; false once it was delivered. */
|
|
1243
|
-
forceSendQueuedTaskMessage(taskId: string, id: string): boolean;
|
|
1244
|
-
/** Take back a message still queued behind a turn; false once it was delivered. */
|
|
1245
|
-
removeQueuedTaskMessage(taskId: string, id: string): boolean;
|
|
1246
|
-
/** Stop a structured task's running turn; it then waits for input like any turn that ends without a `task_complete` call. */
|
|
1247
|
-
interruptTask(taskId: string): Promise<void>;
|
|
1248
|
-
/** What is waiting for a structured task's turn to end; empty for any other task. */
|
|
1249
|
-
getQueuedTaskMessages(taskId: string): QueuedTaskMessage[];
|
|
1250
|
-
private runnerSession;
|
|
1251
|
-
/**
|
|
1252
|
-
* A structured turn that ended without a `task_complete` call (ADR-0018, W1). No
|
|
1253
|
-
* verdict is guessed: the task waits for input, unless a checkpoint in the
|
|
1254
|
-
* same turn already has it waiting, or a queued message went straight out —
|
|
1255
|
-
* the session never passes through idle then, so nothing flickers. The first
|
|
1256
|
-
* turn of an attempt to end that way, cleanly, gets the completion reminder
|
|
1257
|
-
* instead: weaker models often finish the work and report in prose.
|
|
1258
|
-
*/
|
|
1259
|
-
private onTurnEnd;
|
|
1260
|
-
subscribe(observer: OrchestratorObserver): () => void;
|
|
1261
|
-
/**
|
|
1262
|
-
* Isolated per observer: one that throws must not take down the scheduler
|
|
1263
|
-
* or stop the remaining observers from hearing the event.
|
|
1264
|
-
*/
|
|
1265
|
-
private emit;
|
|
1266
|
-
get isRunning(): boolean;
|
|
1267
|
-
/**
|
|
1268
|
-
* A runner is executing *right now*. Narrower than {@link isRunning}, which
|
|
1269
|
-
* also covers the armed-but-idle scheduler `tick()` deliberately leaves
|
|
1270
|
-
* behind when a plan is paused on a user task, a checkpoint or a hold —
|
|
1271
|
-
* nothing is executing then, so nothing is reading the plan mid-mutation.
|
|
1272
|
-
*/
|
|
1273
|
-
get hasLiveWork(): boolean;
|
|
1274
|
-
get isReviewApproved(): boolean;
|
|
1275
|
-
/** The scheduler is driving the plan: ready tasks start on their own as slots free. Narrower than {@link isRunning}. */
|
|
1276
|
-
get isScheduling(): boolean;
|
|
1277
|
-
/**
|
|
1278
|
-
* A full-plan run with nothing starting that something still resumes: a
|
|
1279
|
-
* usage limit's reset, or a retry of a failed task its blocked tasks wait on.
|
|
1280
|
-
*/
|
|
1281
|
-
get awaitsResume(): boolean;
|
|
1282
|
-
get status(): 'approved' | 'running' | 'completed';
|
|
1283
|
-
get activeTaskIds(): string[];
|
|
1284
|
-
/** Task id → session id of every attempt whose runner is up; a spawn in flight has no session yet. */
|
|
1285
|
-
get activeSessionMap(): Map<string, string>;
|
|
1286
|
-
getAttempt(taskId: string): TaskAttemptSnapshot | undefined;
|
|
1287
|
-
/** A planner turn holds new starts: what is ready starts when it releases them. */
|
|
1288
|
-
get startsHeld(): boolean;
|
|
1289
|
-
/**
|
|
1290
|
-
* Hold the scheduler's new starts while a planner turn decides what runs
|
|
1291
|
-
* next, so its edits do not race a task that would otherwise start under
|
|
1292
|
-
* it. Running attempts are untouched. The release is idempotent and starts
|
|
1293
|
-
* nothing itself: whoever releases ticks, which starts what became ready.
|
|
1294
|
-
*/
|
|
1295
|
-
holdStarts(): () => void;
|
|
1296
|
-
/**
|
|
1297
|
-
* "Merge all"; a settled run that merged everything is cleared up and
|
|
1298
|
-
* forgotten. During a run it merges what has landed and the run goes on:
|
|
1299
|
-
* the tasks waiting at a merge gate for that work then start (ADR-0020).
|
|
1300
|
-
* Never alongside ops work, either way round ({@link mergeExcludes}).
|
|
1301
|
-
*/
|
|
1302
|
-
mergeRun(): Promise<IsolationMergeResult>;
|
|
1303
|
-
private mergeLanded;
|
|
1304
|
-
/**
|
|
1305
|
-
* Close the settled run. With merge at gates automatically on, landed work
|
|
1306
|
-
* not merged yet is merged first, through the gate's path, so no surface
|
|
1307
|
-
* hears of a handoff for a run about to merge; one that stopped is handed
|
|
1308
|
-
* over as before.
|
|
1309
|
-
*/
|
|
1310
|
-
private closeRun;
|
|
1311
|
-
/**
|
|
1312
|
-
* The run as a surface offers Merge all mid-run (ADR-0020): what has landed
|
|
1313
|
-
* and is not merged, and whether the run is paused at its gates with nothing
|
|
1314
|
-
* else running. Null once everything landed is merged.
|
|
1315
|
-
*/
|
|
1316
|
-
runMergeView(): RunMergeView | null;
|
|
1317
|
-
/**
|
|
1318
|
-
* The dependencies an ops or user task waits on at its merge gate: those
|
|
1319
|
-
* whose landed work is not in the user's branch yet (ADR-0020). Empty once
|
|
1320
|
-
* the task has started, and for every other task.
|
|
1321
|
-
*/
|
|
1322
|
-
getMergeGate(taskId: string): string[];
|
|
1323
|
-
/** The failed tasks a task not started yet waits on, through its dependencies (ADR-0034); empty when it is not blocked. */
|
|
1324
|
-
getBlockedBy(taskId: string): string[];
|
|
1325
|
-
loadPlan(tasks: readonly Task[], planRunners?: RunnerId[]): void;
|
|
1326
|
-
/**
|
|
1327
|
-
* Adopt an edited plan without letting go of the run in progress. Everything
|
|
1328
|
-
* the scheduler owns — live sessions, holds, retry counts, the verifier and
|
|
1329
|
-
* the review approval — survives, because `loadPlan` clears all of it and a
|
|
1330
|
-
* mid-run edit is not a new run. Only the tasks change.
|
|
1331
|
-
*
|
|
1332
|
-
* The ids with live sessions are defended: one dropped from the edited plan is
|
|
1333
|
-
* carried over (its verdict still has to land somewhere), and one whose status
|
|
1334
|
-
* the snapshot predates is put back to `in_progress` — adopting the snapshot's
|
|
1335
|
-
* "pending" would offer the scheduler work a runner is already doing.
|
|
1336
|
-
*
|
|
1337
|
-
* A failed task stays failed, unlike a plan load's fresh chance: an edit is
|
|
1338
|
-
* not a retry, and the tasks it blocks wait for that retry (ADR-0034).
|
|
1339
|
-
*/
|
|
1340
|
-
reconcilePlan(newTasks: Task[], planRunners?: RunnerId[]): void;
|
|
1341
|
-
start(origin?: RunOrigin): Promise<void>;
|
|
1342
|
-
/** A usage-limit pause holds the run: spawning more would spend the same exhausted limit. */
|
|
1343
|
-
private holdOnLimit;
|
|
1344
|
-
stop(): void;
|
|
1345
|
-
/** Name the tasks a stop took down, so none is mistaken for one still at work. */
|
|
1346
|
-
private sayStopped;
|
|
905
|
+
| {
|
|
906
|
+
type: 'approval_requested';
|
|
907
|
+
approvalId: string;
|
|
908
|
+
tool: string;
|
|
909
|
+
args: string;
|
|
910
|
+
allowForTask: boolean;
|
|
911
|
+
toolCallId?: string;
|
|
912
|
+
decided?: ApprovalDecision['decision'];
|
|
913
|
+
} | {
|
|
914
|
+
type: 'approval_decided';
|
|
915
|
+
approvalId: string;
|
|
916
|
+
decision: ApprovalDecision['decision'];
|
|
917
|
+
note?: string;
|
|
918
|
+
}
|
|
919
|
+
/** The request went unanswered: the runner withdrew it, or its process ended. */
|
|
920
|
+
| {
|
|
921
|
+
type: 'approval_withdrawn';
|
|
922
|
+
approvalId: string;
|
|
923
|
+
}
|
|
924
|
+
/**
|
|
925
|
+
* The runner's process ended. A call it had not reported back on is not
|
|
926
|
+
* running any more: a task's runner is ended the moment its `task_complete`
|
|
927
|
+
* lands, often before it reports that call's result.
|
|
928
|
+
*/
|
|
929
|
+
| {
|
|
930
|
+
type: 'runner_ended';
|
|
931
|
+
};
|
|
932
|
+
/**
|
|
933
|
+
* Keep a tool result's head and tail. A build log or a whole file read can
|
|
934
|
+
* run to megabytes, and every attempt's file keeps it for good; the ends are
|
|
935
|
+
* where a reader looks — the command's start, and how it finished.
|
|
936
|
+
*/
|
|
937
|
+
declare function trimToolOutput(output: string): {
|
|
938
|
+
output: string;
|
|
939
|
+
omittedLines?: number;
|
|
940
|
+
};
|
|
941
|
+
/**
|
|
942
|
+
* The log entry for one structured event, or null for what the log does not
|
|
943
|
+
* keep: background-agent launches, which the subagent's own events already show.
|
|
944
|
+
*/
|
|
945
|
+
declare function toTaskLogEvent(event: StructuredEvent): TaskLogEvent | null;
|
|
946
|
+
/** A log as a surface is sent it, live or read back: everything but the skills' bodies. */
|
|
947
|
+
declare function taskLogForSurface(events: readonly TaskLogEvent[]): TaskLogEvent[];
|
|
948
|
+
/**
|
|
949
|
+
* Merge runs of deltas into one event each. Deltas arrive a few characters at
|
|
950
|
+
* a time; merged, a batch is one message on the wire and one line on disk,
|
|
951
|
+
* and the view folds to the same blocks either way.
|
|
952
|
+
*/
|
|
953
|
+
declare function coalesceTaskLog(events: readonly TaskLogEvent[]): TaskLogEvent[];
|
|
954
|
+
/**
|
|
955
|
+
* What an attempt did to the world, for the retry of a task whose effects
|
|
956
|
+
* outlive it (ADR-0020): one line per tool call and how it ended.
|
|
957
|
+
*/
|
|
958
|
+
declare function digestTaskLog(events: readonly TaskLogEvent[], maxCalls: number): string;
|
|
959
|
+
|
|
960
|
+
type SerializedTaskStatus = {
|
|
961
|
+
id: string;
|
|
962
|
+
status: Task['status'];
|
|
963
|
+
verdict: {
|
|
964
|
+
outcome: 'pass' | 'fail';
|
|
965
|
+
reason: string;
|
|
966
|
+
checks: Verdict['checks'];
|
|
967
|
+
} | null;
|
|
968
|
+
/** Advisory silence timestamp from VerdictEngine — not part of task status semantics. */
|
|
969
|
+
idleSince?: string | null;
|
|
970
|
+
/** Absent unless the plan has an isolation run, so a shared-root plan's updates are unchanged. */
|
|
971
|
+
isolation?: TaskIsolation;
|
|
972
|
+
/** What an `awaiting_user` task waits on, when it was saved (ADR-0018, W1). */
|
|
973
|
+
awaitingReason?: AwaitingReason;
|
|
974
|
+
/** When a task paused on a usage limit resumes on its own (ISO time); absent when it waits for a manual retry. */
|
|
975
|
+
resumeAt?: string;
|
|
976
|
+
/** Why a `skipped` task was skipped; absent when no reason was given, and on any other status. */
|
|
977
|
+
skipReason?: string;
|
|
978
|
+
/** The whole question of the checkpoint the task waits at; absent when it waits at none. */
|
|
979
|
+
checkpoint?: string;
|
|
980
|
+
/** Messages waiting for a structured task's turn to end, oldest first; absent when there are none. */
|
|
981
|
+
queued?: QueuedTaskMessage[];
|
|
982
|
+
/** Set when the task can be continued in its saved runner session (ADR-0018, K1); the id itself stays in the daemon. */
|
|
983
|
+
continuable?: true;
|
|
1347
984
|
/**
|
|
1348
|
-
*
|
|
1349
|
-
*
|
|
1350
|
-
*
|
|
1351
|
-
* read as running — or, in an isolated run, count as live work forever.
|
|
985
|
+
* How many of a structured task's runner requests wait for an answer
|
|
986
|
+
* (ADR-0018, A1) — "waiting for approval", which leaves `status` alone.
|
|
987
|
+
* Absent when none do.
|
|
1352
988
|
*/
|
|
1353
|
-
|
|
989
|
+
awaitingApproval?: number;
|
|
1354
990
|
/**
|
|
1355
|
-
*
|
|
1356
|
-
*
|
|
1357
|
-
*
|
|
1358
|
-
* waiting on the user's decision instead (a conflict, a usage limit, files
|
|
1359
|
-
* an ops task changed) is not: no runner is at work on it, and a clean-up
|
|
1360
|
-
* keeps what it holds on a branch before its worktree goes.
|
|
991
|
+
* The ancestors an ops or user task waits on at its merge gate: their
|
|
992
|
+
* work has landed but is not merged into the user's branch (ADR-0020).
|
|
993
|
+
* Absent when it waits for no Merge all.
|
|
1361
994
|
*/
|
|
1362
|
-
|
|
995
|
+
mergeGate?: string[];
|
|
996
|
+
/** The ancestors, by title, a force start went past the merge gate of (ADR-0020). */
|
|
997
|
+
forcedPastGate?: string[];
|
|
1363
998
|
/**
|
|
1364
|
-
*
|
|
1365
|
-
*
|
|
1366
|
-
* run up, so neither may happen under a task the plan still shows at work.
|
|
999
|
+
* Added after the user approved the plan and not approved since, so the
|
|
1000
|
+
* scheduler leaves it for them (ADR-0035). Absent for any other task.
|
|
1367
1001
|
*/
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
private onVerdict;
|
|
1381
|
-
private afterVerdict;
|
|
1382
|
-
/**
|
|
1383
|
-
* A repair's verdict never replaces the verdict the task's own work earned,
|
|
1384
|
-
* and a repair that does not land leaves the task waiting on the user —
|
|
1385
|
-
* never a failed task, so never one that blocks its dependents.
|
|
1386
|
-
*/
|
|
1387
|
-
private settleRepair;
|
|
1388
|
-
/**
|
|
1389
|
-
* Hand an attempt's verdict to Landing. The attempt stays live while that
|
|
1390
|
-
* settles — it may wait on the module's merge queue — so a cancel, retry or
|
|
1391
|
-
* stop in that window still wins, and waits for the merge before tearing the
|
|
1392
|
-
* worktree down; nothing counts the task as done before its work is on the
|
|
1393
|
-
* integration branch.
|
|
1394
|
-
*/
|
|
1395
|
-
private land;
|
|
1396
|
-
/** Settle a task by how its landing went; `onLanded` is what the path that landed it adds to completing it. */
|
|
1397
|
-
private applyLanding;
|
|
1398
|
-
/**
|
|
1399
|
-
* Work that did not land waits on the user — it never fails the task or
|
|
1400
|
-
* blocks its dependents — unless its conflict is owed a repair, which takes
|
|
1401
|
-
* the slot the ending attempt freed and never one more than the run allows.
|
|
1402
|
-
*/
|
|
1403
|
-
private settleUnlanded;
|
|
1404
|
-
private landResolved;
|
|
1405
|
-
getReadyTasks(): Task[];
|
|
1406
|
-
private readiness;
|
|
1407
|
-
private mergesAutomatically;
|
|
1408
|
-
/**
|
|
1409
|
-
* The user changed the run settings under an open run (ADR-0030). The
|
|
1410
|
-
* scheduler reads them at each decision it makes from them, so a merge gate
|
|
1411
|
-
* already reached is merged now rather than at the next event. Fire-and-forget
|
|
1412
|
-
* — no caller waits on the change — and its failure is said through
|
|
1413
|
-
* {@link unawaited}, never left unhandled.
|
|
1414
|
-
*/
|
|
1415
|
-
runSettingsChanged(): void;
|
|
1416
|
-
private openRun;
|
|
1417
|
-
/**
|
|
1418
|
-
* Open the run a task starts in, and decide whether it isolates if the
|
|
1419
|
-
* task's next attempt needs that ({@link decidesIsolation}). False while a
|
|
1420
|
-
* dirty tree waits on the user; `resume` is what their choice replays.
|
|
1421
|
-
*/
|
|
1422
|
-
private openFor;
|
|
1423
|
-
/** The kind a task's next attempt starts as, short of a repair or a continue: what is weighed before one exists. */
|
|
1424
|
-
private nextAttemptKind;
|
|
1425
|
-
/**
|
|
1426
|
-
* Cancel a running (or scheduled) task: kill its session and return it to
|
|
1427
|
-
* 'pending' — "not executed". The task is put on hold so the scheduler
|
|
1428
|
-
* doesn't immediately restart it; Retry / Force Start release the hold.
|
|
1429
|
-
*
|
|
1430
|
-
* The attempt's worktree is kept, as a stopped or failed one is: a runner is
|
|
1431
|
-
* often cancelled because it looked stuck after doing the work, and Mark
|
|
1432
|
-
* complete can still land that work. The next attempt replaces it.
|
|
1433
|
-
*/
|
|
1434
|
-
cancelTask(taskId: string): Promise<void>;
|
|
1435
|
-
private cancelAttempt;
|
|
1436
|
-
/**
|
|
1437
|
-
* Let go of a task that is leaving the plan. A live runner is cancelled
|
|
1438
|
-
* as {@link cancelTask} does, but its worktree goes: no task is left to land
|
|
1439
|
-
* it into. A spawn still in flight just loses its attempt,
|
|
1440
|
-
* which is what makes {@link startTask} kill the session it is about to
|
|
1441
|
-
* receive. The id's cross-attempt bookkeeping goes too — a hold or retry
|
|
1442
|
-
* count kept for a task that no longer exists would be inherited by nothing.
|
|
1443
|
-
*/
|
|
1444
|
-
releaseTask(taskId: string): Promise<void>;
|
|
1445
|
-
markTaskComplete(taskId: string): Promise<void>;
|
|
1446
|
-
/**
|
|
1447
|
-
* Undo a completion: return the task to "not executed" — pending, verdict and
|
|
1448
|
-
* summary dropped, archive entry removed. It is put on hold like a cancel, so
|
|
1449
|
-
* a running plan does not immediately re-spawn the work the user just
|
|
1450
|
-
* un-marked; Retry / Force Start / Run release the hold. Dependents fall back
|
|
1451
|
-
* to waiting on their own, because the scheduler gates on `isCompleted`.
|
|
1452
|
-
*/
|
|
1453
|
-
markTaskIncomplete(taskId: string): Promise<void>;
|
|
1454
|
-
/**
|
|
1455
|
-
* Skip: end a task as `skipped`, with no verdict and nothing of its work
|
|
1456
|
-
* landed. Its worktree is kept, as a failed attempt's is, so the user can
|
|
1457
|
-
* still look at it or Mark complete it later. Its dependents go ahead
|
|
1458
|
-
* without it, and a run that ended on a failure continues for them, as a
|
|
1459
|
-
* retry would continue it. A running task is stopped before it is skipped.
|
|
1460
|
-
*/
|
|
1461
|
-
skipTask(taskId: string, reason?: string, origin?: RunOrigin): Promise<void>;
|
|
1462
|
-
private logAndArchive;
|
|
1463
|
-
retryTask(taskId: string, origin?: RunOrigin): Promise<void>;
|
|
1464
|
-
/**
|
|
1465
|
-
* Continue a finished structured task in its saved runner session (ADR-0018,
|
|
1466
|
-
* K1): a retry whose first turn is the user's message, sent to the session
|
|
1467
|
-
* the task ended in rather than a fresh one given its prompt again. The
|
|
1468
|
-
* worktree keeps the same path, where the runner keeps its sessions, and
|
|
1469
|
-
* restores the previous work so that session's memory matches its files.
|
|
1470
|
-
*/
|
|
1471
|
-
continueTask(taskId: string, message: string, origin?: RunOrigin): Promise<void>;
|
|
1472
|
-
/**
|
|
1473
|
-
* Keep a task paused on its runner's usage limit from resuming on its own:
|
|
1474
|
-
* it waits for a manual retry, as one whose reset time could not be read
|
|
1475
|
-
* does. What a surface's cancel calls, and what moving the task to another
|
|
1476
|
-
* runner has to call first. False when no resume was scheduled for it.
|
|
1477
|
-
*/
|
|
1478
|
-
cancelScheduledResume(taskId: string): boolean;
|
|
1479
|
-
/**
|
|
1480
|
-
* Run before writing assignments so cancellation sees the old runner's
|
|
1481
|
-
* shared wait. A session cannot cross runners, so the next attempt is a
|
|
1482
|
-
* carry-over of the paused attempt's files (ADR-0018, U1).
|
|
1483
|
-
*/
|
|
1484
|
-
releaseMovedResume(taskId: string, changes: Partial<Task>): Partial<Task>;
|
|
1485
|
-
/**
|
|
1486
|
-
* A task's attempt stopped on its runner's usage limit, without a
|
|
1487
|
-
* completion call. Failing would paint a red X on a task that only has to
|
|
1488
|
-
* wait, and spawning more tasks would spend the same exhausted limit, so the
|
|
1489
|
-
* task pauses and the run holds. When the runner said when its limit
|
|
1490
|
-
* resets, the task joins its runner's wait and resumes on its own then
|
|
1491
|
-
* (#84); otherwise, or when it was resumed a moment ago and hit the limit
|
|
1492
|
-
* again, it waits for the user to retry it.
|
|
1493
|
-
*/
|
|
1494
|
-
private pauseOnLimit;
|
|
1495
|
-
/**
|
|
1496
|
-
* The limit arrived as the turn's error with the runner still up: the
|
|
1497
|
-
* attempt ends here, as its verdict would have ended it, so a resume is a
|
|
1498
|
-
* continue in a fresh process (ADR-0018, L1/K1).
|
|
1499
|
-
*/
|
|
1500
|
-
private stopOnLimit;
|
|
1501
|
-
/** The tasks waiting on `runner`'s reset to resume on their own. */
|
|
1502
|
-
private waitingOn;
|
|
1503
|
-
/**
|
|
1504
|
-
* Arm `runner`'s one wait for the latest resume time among its tasks, and
|
|
1505
|
-
* put that time on each of them, so they resume together.
|
|
1506
|
-
*/
|
|
1507
|
-
private armLimitWait;
|
|
1508
|
-
/**
|
|
1509
|
-
* The waits a loaded plan still owes. A resume time that passed while
|
|
1510
|
-
* nothing was running — the daemon was down, say — resumes at once.
|
|
1511
|
-
*/
|
|
1512
|
-
private rearmLimitWaits;
|
|
1513
|
-
/**
|
|
1514
|
-
* Resume a task paused on its runner's usage limit now, in its saved
|
|
1515
|
-
* session, once the user says the limit has reset early. The limit is the
|
|
1516
|
-
* runner's account's, so every task waiting on that runner's reset resumes
|
|
1517
|
-
* with it, as at the reset itself. `message` follows the reset notice in the
|
|
1518
|
-
* same continuation. A resumed attempt that hits the limit again at once
|
|
1519
|
-
* waits for the user, as an automatic resume's does: that is what keeps a
|
|
1520
|
-
* caller from resuming it round and round.
|
|
1521
|
-
*/
|
|
1522
|
-
resumeNow(taskId: string, origin?: RunOrigin, message?: string): Promise<void>;
|
|
1523
|
-
private resumeAfterLimit;
|
|
1524
|
-
/**
|
|
1525
|
-
* Retry a task paused on a usage limit, continuing its saved session where
|
|
1526
|
-
* it left one (ADR-0018, K1), then release the hold the pause put on the
|
|
1527
|
-
* run once nothing else waits on a reset.
|
|
1528
|
-
*/
|
|
1529
|
-
private resumeTask;
|
|
1530
|
-
/**
|
|
1531
|
-
* Manually start a single AI task right now, bypassing dependency/readiness
|
|
1532
|
-
* gating (the "force start" affordance on a task card). Reuses the scheduler's
|
|
1533
|
-
* own startTask so a force-started task gets the same augmented prompt, session
|
|
1534
|
-
* tracking, and exit handling — callers must not re-spawn the runner themselves.
|
|
1535
|
-
* No-op if the task is unknown, not an AI task, or already running.
|
|
1536
|
-
*/
|
|
1537
|
-
forceStartTask(taskId: string, origin?: RunOrigin): Promise<void>;
|
|
1538
|
-
private refuseOpsDuringMerge;
|
|
1539
|
-
/**
|
|
1540
|
-
* A force start passes a merge gate (ADR-0020), and the surface confirmed it
|
|
1541
|
-
* first; the choice is kept on the task and said, because the work it acts
|
|
1542
|
-
* on is not in the user's branch.
|
|
1543
|
-
*/
|
|
1544
|
-
private notePastGate;
|
|
1545
|
-
/**
|
|
1546
|
-
* Run exactly one task outside full-plan scheduling. The active/starting
|
|
1547
|
-
* session still contributes to {@link hasLiveWork} so every surface exposes
|
|
1548
|
-
* Stop and disables Execute Plan, but onVerdict cannot auto-schedule other
|
|
1549
|
-
* tasks because the plan scheduler's `running` flag remains false.
|
|
1550
|
-
*/
|
|
1551
|
-
runTask(taskId: string): Promise<void>;
|
|
1552
|
-
getCompletedCount(): number;
|
|
1553
|
-
getTotalCount(): number;
|
|
1554
|
-
isAllComplete(): boolean;
|
|
1555
|
-
isAnyFailed(): boolean;
|
|
1556
|
-
approveReview(origin?: RunOrigin): Promise<void>;
|
|
1557
|
-
tick(): Promise<void>;
|
|
1558
|
-
/** How a run that ended on failures ends: what it finished, what failed, and what that blocks. */
|
|
1559
|
-
/** Skipped work is not completed work, so a run with any says so rather than that everything completed. */
|
|
1560
|
-
private sayFinished;
|
|
1561
|
-
private sayEndedOnFailure;
|
|
1562
|
-
/** Whether the scheduler may start `id` now: it passes every readiness gate and a slot is free. */
|
|
1563
|
-
private stillReady;
|
|
1564
|
-
/**
|
|
1565
|
-
* A run paused at merge gates says so, as plainly as a task that waits on
|
|
1566
|
-
* the user: which tasks wait, and that Merge all lets them go on (ADR-0020).
|
|
1567
|
-
*/
|
|
1568
|
-
private sayGate;
|
|
1569
|
-
private startTask;
|
|
1570
|
-
/**
|
|
1571
|
-
* The fallible half of starting a task: resolving its cwd/worktree and
|
|
1572
|
-
* spawning the runner. Resolves false when the attempt never committed —
|
|
1573
|
-
* abandoned because it was ended from under it, or a failed spawn already
|
|
1574
|
-
* unwound (both leave `startTask` with nothing further to announce).
|
|
1575
|
-
*/
|
|
1576
|
-
private spawnAttempt;
|
|
1577
|
-
/** For a skill the main checkout has, the folder a worktree only gets once it is committed. */
|
|
1578
|
-
private uncommittedSkillFolder;
|
|
1579
|
-
/**
|
|
1580
|
-
* The roots whose `.ordewell/skills/` an attempt at `cwd` reads: in a repo
|
|
1581
|
-
* group, `cwd` holds each repo — a worktree of it when isolated, the
|
|
1582
|
-
* workspace's own checkout when not — and no folder of its own (ADR-0014).
|
|
1583
|
-
*/
|
|
1584
|
-
private skillRoots;
|
|
1585
|
-
/**
|
|
1586
|
-
* A continue whose runner never announced the session it was told to
|
|
1587
|
-
* resume. Nothing was continued, and nothing fresh was started in its place.
|
|
1588
|
-
*/
|
|
1589
|
-
private unresumed;
|
|
1590
|
-
/**
|
|
1591
|
-
* A spawn whose attempt ended while it was in flight: kill what it produced
|
|
1592
|
-
* and take back the claim it made. Only the claim — whatever ended the
|
|
1593
|
-
* attempt may have decided the task since (mark complete, cancel, retry).
|
|
1594
|
-
*/
|
|
1595
|
-
private abandonSpawn;
|
|
1596
|
-
private tell;
|
|
1597
|
-
private say;
|
|
1598
|
-
/**
|
|
1599
|
-
* Replay the start a dirty tree turned away. `stash` puts the user's tracked
|
|
1600
|
-
* changes on the git stash first, so the run isolates; `shared` runs this one
|
|
1601
|
-
* run in the workspace root, knowingly.
|
|
1602
|
-
*/
|
|
1603
|
-
continueBlockedRun(how: 'stash' | 'shared'): Promise<void>;
|
|
1604
|
-
/**
|
|
1605
|
-
* The one way an attempt ends. Dropping the record is itself what
|
|
1606
|
-
* invalidates a spawn still in flight ({@link startTask} kills the session
|
|
1607
|
-
* it receives for an attempt that is no longer current).
|
|
1608
|
-
*
|
|
1609
|
-
* A user interruption also bumps the verifier's generation *before* stopping
|
|
1610
|
-
* the runner: a runner may fire onExit synchronously from
|
|
1611
|
-
* stop(), and if that exit reaches VerdictEngine under the still-valid
|
|
1612
|
-
* generation it delivers a verdict that marks the task 'completed' for one
|
|
1613
|
-
* tick — long enough for the scheduler to start a dependent task. Stop and
|
|
1614
|
-
* load reset the whole verifier themselves.
|
|
1615
|
-
*/
|
|
1616
|
-
private endAttempt;
|
|
1617
|
-
/** Kept on the task once the attempt ends, so a continue can resume it after a reload (ADR-0018, K1). */
|
|
1618
|
-
private saveNativeSession;
|
|
1619
|
-
private endAllAttempts;
|
|
1002
|
+
waitsForApproval?: true;
|
|
1003
|
+
attemptUsage?: AttemptUsage[];
|
|
1004
|
+
};
|
|
1005
|
+
/**
|
|
1006
|
+
* Landed work not yet in the user's branch. `paused` means the running plan
|
|
1007
|
+
* waits at a merge gate with no attempts active.
|
|
1008
|
+
*/
|
|
1009
|
+
interface RunMergeView {
|
|
1010
|
+
waitingAtGate: boolean;
|
|
1011
|
+
paused: boolean;
|
|
1012
|
+
repos: IsolationHandoff['repos'];
|
|
1013
|
+
landed: IsolationHandoff['landed'];
|
|
1620
1014
|
}
|
|
1621
|
-
|
|
1622
|
-
type SerializedTaskStatus = {
|
|
1623
|
-
id: string;
|
|
1624
|
-
status: Task['status'];
|
|
1625
|
-
verdict: {
|
|
1626
|
-
outcome: 'pass' | 'fail';
|
|
1627
|
-
reason: string;
|
|
1628
|
-
checks: Verdict['checks'];
|
|
1629
|
-
} | null;
|
|
1630
|
-
/** Advisory silence timestamp from VerdictEngine — not part of task status semantics. */
|
|
1631
|
-
idleSince?: string | null;
|
|
1632
|
-
/** Absent unless the plan has an isolation run, so a shared-root plan's updates are unchanged. */
|
|
1633
|
-
isolation?: TaskIsolation;
|
|
1634
|
-
/** What an `awaiting_user` task waits on, when it was saved (ADR-0018, W1). */
|
|
1635
|
-
awaitingReason?: AwaitingReason;
|
|
1636
|
-
/** When a task paused on a usage limit resumes on its own (ISO time); absent when it waits for a manual retry. */
|
|
1637
|
-
resumeAt?: string;
|
|
1638
|
-
/** Why a `skipped` task was skipped; absent when no reason was given, and on any other status. */
|
|
1639
|
-
skipReason?: string;
|
|
1640
|
-
/** The whole question of the checkpoint the task waits at; absent when it waits at none. */
|
|
1641
|
-
checkpoint?: string;
|
|
1642
|
-
/** Messages waiting for a structured task's turn to end, oldest first; absent when there are none. */
|
|
1643
|
-
queued?: QueuedTaskMessage[];
|
|
1644
|
-
/** Set when the task can be continued in its saved runner session (ADR-0018, K1); the id itself stays in the daemon. */
|
|
1645
|
-
continuable?: true;
|
|
1646
|
-
/**
|
|
1647
|
-
* How many of a structured task's runner requests wait for an answer
|
|
1648
|
-
* (ADR-0018, A1) — "waiting for approval", which leaves `status` alone.
|
|
1649
|
-
* Absent when none do.
|
|
1650
|
-
*/
|
|
1651
|
-
awaitingApproval?: number;
|
|
1652
|
-
/**
|
|
1653
|
-
* The dependencies an ops or user task waits on at its merge gate: their
|
|
1654
|
-
* work has landed but is not merged into the user's branch (ADR-0020).
|
|
1655
|
-
* Absent when it waits for no Merge all.
|
|
1656
|
-
*/
|
|
1657
|
-
mergeGate?: string[];
|
|
1658
|
-
/** The dependencies, by title, a force start went past the merge gate of (ADR-0020). */
|
|
1659
|
-
forcedPastGate?: string[];
|
|
1660
|
-
attemptUsage?: AttemptUsage[];
|
|
1661
|
-
};
|
|
1662
|
-
/**
|
|
1663
|
-
* Landed work not yet in the user's branch. `paused` means the running plan
|
|
1664
|
-
* waits at a merge gate with no attempts active.
|
|
1665
|
-
*/
|
|
1666
|
-
interface RunMergeView {
|
|
1667
|
-
waitingAtGate: boolean;
|
|
1668
|
-
paused: boolean;
|
|
1669
|
-
repos: IsolationHandoff['repos'];
|
|
1670
|
-
landed: IsolationHandoff['landed'];
|
|
1671
|
-
}
|
|
1672
|
-
type SerializedTask = {
|
|
1015
|
+
type SerializedTask = {
|
|
1673
1016
|
id: string;
|
|
1674
1017
|
order: number;
|
|
1675
1018
|
title: string;
|
|
@@ -1741,6 +1084,22 @@ type SerializedPlan = {
|
|
|
1741
1084
|
conversationHistory?: SerializedConversationMessage[];
|
|
1742
1085
|
prdMarkdown?: string;
|
|
1743
1086
|
};
|
|
1087
|
+
/** What a held edit does to a task once it lands. */
|
|
1088
|
+
type HeldEditMark = 'added' | 'changed' | 'removed';
|
|
1089
|
+
/**
|
|
1090
|
+
* The plan as the open planner turn's edit would leave it, in plan order with
|
|
1091
|
+
* the removed tasks where they were; flat, a subtask after its parent. `label`
|
|
1092
|
+
* is the task's `#2.1` reference; `mark` is absent for a task the edit leaves
|
|
1093
|
+
* alone. Proposed, not approved: the tasks need the user's go-ahead to run.
|
|
1094
|
+
*/
|
|
1095
|
+
interface HeldEdit {
|
|
1096
|
+
turnId: string;
|
|
1097
|
+
tasks: Array<{
|
|
1098
|
+
label: string;
|
|
1099
|
+
mark?: HeldEditMark;
|
|
1100
|
+
task: SerializedTask;
|
|
1101
|
+
}>;
|
|
1102
|
+
}
|
|
1744
1103
|
/** How a planner turn ended: the reply kind it settled on, a user stop, or a failure. */
|
|
1745
1104
|
type PlannerTurnOutcome = 'message' | 'plan' | 'task_ops' | 'stopped' | 'error';
|
|
1746
1105
|
/**
|
|
@@ -1806,6 +1165,16 @@ type SessionMessage =
|
|
|
1806
1165
|
turnId: string;
|
|
1807
1166
|
skill: SkillLoadNotice;
|
|
1808
1167
|
}
|
|
1168
|
+
/**
|
|
1169
|
+
* What the open planner turn's task edit would leave, sent whenever the
|
|
1170
|
+
* edit it holds changes (ADR-0029: it lands as the reply ends). `held` is
|
|
1171
|
+
* null once nothing is held: the edit landed, was refused as it landed, or
|
|
1172
|
+
* the turn ended without it. The plan itself is untouched until it lands.
|
|
1173
|
+
*/
|
|
1174
|
+
| {
|
|
1175
|
+
type: 'plan_edit_held';
|
|
1176
|
+
held: HeldEdit | null;
|
|
1177
|
+
}
|
|
1809
1178
|
/**
|
|
1810
1179
|
* A planner turn is over; nothing more carries its `turnId`. Emitted exactly
|
|
1811
1180
|
* once per `planner_turn_started`, stop and failure included, after the
|
|
@@ -2015,7 +1384,7 @@ type PlanSnapshot = {
|
|
|
2015
1384
|
plan: SurfacePlan | SurfacePlanState;
|
|
2016
1385
|
};
|
|
2017
1386
|
/** What only the status stream says about a task; a plan refresh keeps these as they were. */
|
|
2018
|
-
type RunTaskStatus = Partial<Pick<SerializedTaskStatus, 'verdict' | 'idleSince' | 'isolation' | 'checkpoint' | 'queued' | 'continuable' | 'awaitingApproval' | 'mergeGate' | 'forcedPastGate'>>;
|
|
1387
|
+
type RunTaskStatus = Partial<Pick<SerializedTaskStatus, 'verdict' | 'idleSince' | 'isolation' | 'checkpoint' | 'queued' | 'continuable' | 'awaitingApproval' | 'mergeGate' | 'forcedPastGate' | 'waitsForApproval'>>;
|
|
2019
1388
|
/** A task in the run view: the plan's fields, then the status stream's. */
|
|
2020
1389
|
type RunTask = Omit<SerializedTask, 'subtasks'> & RunTaskStatus & {
|
|
2021
1390
|
subtasks: RunTask[];
|
|
@@ -2033,6 +1402,8 @@ interface RunView {
|
|
|
2033
1402
|
usage?: PlanUsage;
|
|
2034
1403
|
handoff: IsolationHandoff | null;
|
|
2035
1404
|
mergeResult: IsolationMergeResult | null;
|
|
1405
|
+
/** The edit the open planner turn holds, as it would leave the plan; absent when it holds none. */
|
|
1406
|
+
heldEdit?: HeldEdit;
|
|
2036
1407
|
}
|
|
2037
1408
|
type RunInput = SessionMessage | PlanSnapshot;
|
|
2038
1409
|
/**
|
|
@@ -2048,7 +1419,7 @@ type SessionNotice = {
|
|
|
2048
1419
|
};
|
|
2049
1420
|
type SessionBroadcaster = (msg: SessionMessage) => void;
|
|
2050
1421
|
declare function serializeTask(t: SurfaceTask): SerializedTask;
|
|
2051
|
-
declare function serializeTaskStatus(t: Task, idleSince?: string | null, isolation?: TaskIsolation | null, queued?: readonly QueuedTaskMessage[], awaitingApproval?: number, mergeGate?: readonly string[], checkpoint?: string): SerializedTaskStatus;
|
|
1422
|
+
declare function serializeTaskStatus(t: Task, idleSince?: string | null, isolation?: TaskIsolation | null, queued?: readonly QueuedTaskMessage[], awaitingApproval?: number, mergeGate?: readonly string[], checkpoint?: string, waitsForApproval?: boolean): SerializedTaskStatus;
|
|
2052
1423
|
/** The one place a plan's skill bodies come off before it leaves the session: every wire path that carries plan state goes through here. */
|
|
2053
1424
|
declare function surfacePlan({ queuedMessages: _carriedOverAtLoad, ...plan }: LegacyPlanState): SurfacePlan;
|
|
2054
1425
|
declare function surfacePlanState(state: PlanState): SurfacePlanState;
|
|
@@ -2065,10 +1436,762 @@ interface ExecutionSummary {
|
|
|
2065
1436
|
blocked: number;
|
|
2066
1437
|
skipped: number;
|
|
2067
1438
|
}
|
|
2068
|
-
declare function executionSummary(tasks: readonly Task[]): ExecutionSummary;
|
|
2069
|
-
declare const CHECKPOINT_TRUNCATE_LENGTH = 120;
|
|
2070
|
-
/** Truncate a checkpoint summary to a single line of at most CHECKPOINT_TRUNCATE_LENGTH chars. */
|
|
2071
|
-
declare function truncateCheckpointSummary(summary: string): string;
|
|
1439
|
+
declare function executionSummary(tasks: readonly Task[]): ExecutionSummary;
|
|
1440
|
+
declare const CHECKPOINT_TRUNCATE_LENGTH = 120;
|
|
1441
|
+
/** Truncate a checkpoint summary to a single line of at most CHECKPOINT_TRUNCATE_LENGTH chars. */
|
|
1442
|
+
declare function truncateCheckpointSummary(summary: string): string;
|
|
1443
|
+
|
|
1444
|
+
/** What ran an automatic Merge all: a task reaching its merge gate, or the run settling with landed work unmerged. */
|
|
1445
|
+
type AutoMergeTrigger = {
|
|
1446
|
+
kind: 'gate';
|
|
1447
|
+
taskId: string;
|
|
1448
|
+
} | {
|
|
1449
|
+
kind: 'run_end';
|
|
1450
|
+
};
|
|
1451
|
+
/**
|
|
1452
|
+
* Who opened a run (ADR-0029): a call the user made, or one made for them; a
|
|
1453
|
+
* call of the supervisor's own; or a usage-limit reset resuming what it paused.
|
|
1454
|
+
* Only the user's starts a run's supervision afresh.
|
|
1455
|
+
*/
|
|
1456
|
+
type RunOrigin = 'user' | 'supervisor' | 'limit-reset';
|
|
1457
|
+
/** What the scheduler reads of the open run's settings, at each decision it makes from them. */
|
|
1458
|
+
type SchedulerRunSettings = Pick<RunSettings, 'mergeAtGatesAutomatically'>;
|
|
1459
|
+
/**
|
|
1460
|
+
* A start someone asked for, from the moment it was admitted until it has
|
|
1461
|
+
* taken its claim or been let go (ADR-0029): a start waiting on the user's
|
|
1462
|
+
* answer, on a held edit, or on the run to open. Stop counts it as live work,
|
|
1463
|
+
* and a Stop since it was admitted refuses its claim.
|
|
1464
|
+
*/
|
|
1465
|
+
interface StartAdmission {
|
|
1466
|
+
/** No Stop came since it was admitted. */
|
|
1467
|
+
current(): boolean;
|
|
1468
|
+
/** It claimed, or will not: Stop no longer counts it. Idempotent. */
|
|
1469
|
+
release(): void;
|
|
1470
|
+
}
|
|
1471
|
+
/** What a start the user's Stop withdrew answers, wherever it was waiting. */
|
|
1472
|
+
declare const STOPPED_BEFORE_START = "Not done: stopped before it could start. The run was stopped meanwhile; start it again if it is still wanted.";
|
|
1473
|
+
/**
|
|
1474
|
+
* The one notification channel out of the orchestrator. Everything that used
|
|
1475
|
+
* to travel over a separate callback (onRefresh) is an observer event; the
|
|
1476
|
+
* Session subscribes once and turns these into SessionMessages.
|
|
1477
|
+
*/
|
|
1478
|
+
interface OrchestratorObserver {
|
|
1479
|
+
/** Any task-shaped state changed (store mutation, checkpoint, retry, …). */
|
|
1480
|
+
onTaskChanged?(): void;
|
|
1481
|
+
/**
|
|
1482
|
+
* A run reached an outcome that stands until the user acts — completed,
|
|
1483
|
+
* failed, or awaiting the user — on its own, not as the answer to a call.
|
|
1484
|
+
* Emitted before the `onTaskChanged` that announces it, so a listener can
|
|
1485
|
+
* save the outcome before any surface is told.
|
|
1486
|
+
*/
|
|
1487
|
+
onTaskSettled?(data: {
|
|
1488
|
+
taskId: string;
|
|
1489
|
+
}): void;
|
|
1490
|
+
onRunStarted?(data: {
|
|
1491
|
+
origin: RunOrigin;
|
|
1492
|
+
}): void;
|
|
1493
|
+
/** An automatic Merge all settled, at a gate or at the run's end; failures are attention events for a supervisor. */
|
|
1494
|
+
onAutoMerge?(data: {
|
|
1495
|
+
trigger: AutoMergeTrigger;
|
|
1496
|
+
result: IsolationMergeResult;
|
|
1497
|
+
}): void;
|
|
1498
|
+
onTick?(): void;
|
|
1499
|
+
onUsageChanged?(): void;
|
|
1500
|
+
onExecutionComplete?(): void;
|
|
1501
|
+
/**
|
|
1502
|
+
* A task paused on its runner's usage limit, or its resume changed:
|
|
1503
|
+
* `resumeAt` is when it resumes on its own (ISO time), null when it waits
|
|
1504
|
+
* for a manual retry — the reset could not be read, the resume was
|
|
1505
|
+
* cancelled, or the resumed attempt hit the limit again at once.
|
|
1506
|
+
* `cancelled` marks the resume being cancelled, which is no new pause.
|
|
1507
|
+
*/
|
|
1508
|
+
onUsageLimitWait?(data: {
|
|
1509
|
+
taskId: string;
|
|
1510
|
+
runner: string;
|
|
1511
|
+
resumeAt: string | null;
|
|
1512
|
+
cancelled?: true;
|
|
1513
|
+
}): void;
|
|
1514
|
+
/** A live task's runner went silent (ADR-0018's idle timer); paused while it waits on input, a checkpoint or an approval. */
|
|
1515
|
+
onTaskIdle?(data: {
|
|
1516
|
+
taskId: string;
|
|
1517
|
+
idleSince: string;
|
|
1518
|
+
}): void;
|
|
1519
|
+
onReviewNeeded?(data: {
|
|
1520
|
+
tasks: Task[];
|
|
1521
|
+
planRunners: RunnerId[];
|
|
1522
|
+
}): void;
|
|
1523
|
+
onReviewApproved?(data: {
|
|
1524
|
+
tasks: Task[];
|
|
1525
|
+
}): void;
|
|
1526
|
+
onCheckpoint?(data: {
|
|
1527
|
+
taskId: string;
|
|
1528
|
+
taskTitle: string;
|
|
1529
|
+
summary: string;
|
|
1530
|
+
}): void;
|
|
1531
|
+
/**
|
|
1532
|
+
* The isolation run record changed and should be persisted with the plan.
|
|
1533
|
+
* `detached` is the record of a run another plan has replaced since — a
|
|
1534
|
+
* release that outlived a plan load — for the session it belongs to.
|
|
1535
|
+
*/
|
|
1536
|
+
onIsolationChanged?(detached?: IsolationRun): void;
|
|
1537
|
+
/** A run did not start: the tree is dirty, and the user picks stash or no isolation. `repos` names the dirty repos of a group. */
|
|
1538
|
+
onIsolationBlocked?(data: {
|
|
1539
|
+
reason: 'dirty';
|
|
1540
|
+
repos: string[];
|
|
1541
|
+
}): void;
|
|
1542
|
+
/** An isolated run settled; emitted before `onExecutionComplete`, which surfaces treat as terminal. */
|
|
1543
|
+
onIsolationHandoff?(handoff: IsolationHandoff): void;
|
|
1544
|
+
/**
|
|
1545
|
+
* What a run says about how it isolates — the fallback to the workspace root,
|
|
1546
|
+
* shared paths, copies, a stash. Beside the notification channel, which a
|
|
1547
|
+
* daemon may leave unwired, so a surface without toasts can still show it.
|
|
1548
|
+
*/
|
|
1549
|
+
onIsolationNotice?(data: {
|
|
1550
|
+
level: 'info' | 'warn' | 'error';
|
|
1551
|
+
message: string;
|
|
1552
|
+
}): void;
|
|
1553
|
+
}
|
|
1554
|
+
/**
|
|
1555
|
+
* A message or interrupt a task cannot take: it is not running, or it is
|
|
1556
|
+
* waiting on something a message does not answer.
|
|
1557
|
+
* The request is wrong, not the orchestrator, so a surface says why.
|
|
1558
|
+
*/
|
|
1559
|
+
declare class TaskControlError extends Error {
|
|
1560
|
+
constructor(message: string);
|
|
1561
|
+
}
|
|
1562
|
+
type AttemptPhase = 'starting' | 'running' | 'integrating';
|
|
1563
|
+
/** Read-only view of a task's live attempt. */
|
|
1564
|
+
interface TaskAttemptSnapshot {
|
|
1565
|
+
taskId: string;
|
|
1566
|
+
attempt: number;
|
|
1567
|
+
phase: AttemptPhase;
|
|
1568
|
+
sessionId: string | null;
|
|
1569
|
+
runner: string;
|
|
1570
|
+
cwd: string | null;
|
|
1571
|
+
startedAt: string;
|
|
1572
|
+
skills: readonly TaskSkillSnapshot[];
|
|
1573
|
+
}
|
|
1574
|
+
/**
|
|
1575
|
+
* The wiring a {@link TaskOrchestrator} runs on, every collaborator resolved.
|
|
1576
|
+
* {@link TaskOrchestrator.compose} is the only production caller; tests build
|
|
1577
|
+
* one through it too. The constructor makes nothing itself, so there is exactly
|
|
1578
|
+
* one place defaults live.
|
|
1579
|
+
*/
|
|
1580
|
+
interface TaskOrchestratorDeps {
|
|
1581
|
+
config: IConfig;
|
|
1582
|
+
notifications: INotification;
|
|
1583
|
+
runner: IRunner;
|
|
1584
|
+
store: PlanStore;
|
|
1585
|
+
output: TaskOutputSource;
|
|
1586
|
+
/** The plan's isolation run and the open run's lifecycle (ADR-0013). */
|
|
1587
|
+
runs: IsolationRunController;
|
|
1588
|
+
/** A passed attempt's work onto the integration branch, and any conflict repair. */
|
|
1589
|
+
landing: Landing;
|
|
1590
|
+
registry: RunnerRegistry | null;
|
|
1591
|
+
/** The workspace root, read at call time — VS Code can change it. */
|
|
1592
|
+
workspaceRoot: () => string;
|
|
1593
|
+
/** The workspace's own variables for a task's cwd (ADR-0016). */
|
|
1594
|
+
workspaceEnv: (cwd: string) => Promise<WorkspaceEnv>;
|
|
1595
|
+
/** The skill catalog over workspace roots, as {@link workspaceSkillRoots} orders them — read at each spawn. */
|
|
1596
|
+
skillsAt: (roots: readonly string[]) => SkillLookup;
|
|
1597
|
+
/** What the task's last saved attempt did, or null when it left no log (ADR-0020). */
|
|
1598
|
+
previousAttemptFromLog: (taskId: string) => string | null;
|
|
1599
|
+
/**
|
|
1600
|
+
* The open run's settings, read as each automatic merge is decided rather
|
|
1601
|
+
* than copied when the run opens, so a change to the run under way applies
|
|
1602
|
+
* at once (ADR-0030); absent before any run.
|
|
1603
|
+
*/
|
|
1604
|
+
runSettings: () => SchedulerRunSettings | undefined;
|
|
1605
|
+
/**
|
|
1606
|
+
* The user's approval record, read at each start (ADR-0035): the Session
|
|
1607
|
+
* owns it, and the scheduler starts nothing outside it.
|
|
1608
|
+
*/
|
|
1609
|
+
isApproved: (taskId: string) => boolean;
|
|
1610
|
+
}
|
|
1611
|
+
/**
|
|
1612
|
+
* What a caller supplies to {@link TaskOrchestrator.compose}; every optional
|
|
1613
|
+
* one gets its default there. This is what a host or test chooses, not the
|
|
1614
|
+
* resolved wiring the orchestrator itself runs on.
|
|
1615
|
+
*/
|
|
1616
|
+
interface TaskOrchestratorOptions {
|
|
1617
|
+
config: IConfig;
|
|
1618
|
+
notifications: INotification;
|
|
1619
|
+
runner: IRunner;
|
|
1620
|
+
store?: PlanStore;
|
|
1621
|
+
output?: TaskOutputSource;
|
|
1622
|
+
isolation?: IWorktreeIsolation;
|
|
1623
|
+
registry?: RunnerRegistry | null;
|
|
1624
|
+
workspaceRoot?: () => string;
|
|
1625
|
+
workspaceEnv?: (cwd: string) => Promise<WorkspaceEnv>;
|
|
1626
|
+
skillsAt?: (roots: readonly string[]) => SkillLookup;
|
|
1627
|
+
previousAttemptFromLog?: (taskId: string) => string | null;
|
|
1628
|
+
runSettings?: () => SchedulerRunSettings | undefined;
|
|
1629
|
+
/** Every task is approved unless a Session supplies its record: an orchestrator on its own has no user to ask. */
|
|
1630
|
+
isApproved?: (taskId: string) => boolean;
|
|
1631
|
+
}
|
|
1632
|
+
/**
|
|
1633
|
+
* The pure scheduler. Owns execution state (`running`, `planStatus`,
|
|
1634
|
+
* `reviewApproved`, the live task attempts, the start hold) and the verifier.
|
|
1635
|
+
* All task-shaped state — the plan tree, the flat index, the completed set
|
|
1636
|
+
* — lives in {@link PlanStore}, injected at construction. The orchestrator
|
|
1637
|
+
* calls `store.markCompleted(id)` / `store.markFailed(id)` instead of mutating
|
|
1638
|
+
* task state directly. A task completes only after the runner reports it done
|
|
1639
|
+
* through `task_complete` on its attempt's token; process exit without that
|
|
1640
|
+
* evidence is a visible failure and does not unblock dependent work.
|
|
1641
|
+
*/
|
|
1642
|
+
declare class TaskOrchestrator {
|
|
1643
|
+
private config;
|
|
1644
|
+
private notifications;
|
|
1645
|
+
private runner;
|
|
1646
|
+
private store;
|
|
1647
|
+
private output;
|
|
1648
|
+
private attempts;
|
|
1649
|
+
private verifier;
|
|
1650
|
+
private running;
|
|
1651
|
+
private planStatus;
|
|
1652
|
+
/** Planner turns in flight that new starts wait for (ADR-0029); running attempts carry on. */
|
|
1653
|
+
private startHolds;
|
|
1654
|
+
/** Bumped by the user's Stop: a start admitted under an older generation never takes its claim. */
|
|
1655
|
+
private stopGeneration;
|
|
1656
|
+
/** Starts admitted and not yet claimed or let go; see {@link StartAdmission}. */
|
|
1657
|
+
private admissions;
|
|
1658
|
+
private reviewApproved;
|
|
1659
|
+
private autoMergeBlocked;
|
|
1660
|
+
private mergesInFlight;
|
|
1661
|
+
private retryCounts;
|
|
1662
|
+
private spawnCounts;
|
|
1663
|
+
/** A full-plan run a usage-limit pause holds: no new start until the reset resumes it. */
|
|
1664
|
+
private heldOnLimit;
|
|
1665
|
+
/**
|
|
1666
|
+
* A full-plan run that ended with failed tasks and the tasks they block
|
|
1667
|
+
* (ADR-0034). A retry of a failed task continues it, so what it blocked
|
|
1668
|
+
* starts once it passes — without this the retried task ran alone.
|
|
1669
|
+
*/
|
|
1670
|
+
private endedOnFailure;
|
|
1671
|
+
/** A Merge all is under way: no ops task starts until it ends (ADR-0020). */
|
|
1672
|
+
private merging;
|
|
1673
|
+
/** The last "waiting for Merge all" notice, so a run paused at a gate says it once, not on every tick. */
|
|
1674
|
+
private gateNotice;
|
|
1675
|
+
/** The workspace's own variables for a task's cwd (ADR-0016); swapped out in tests. */
|
|
1676
|
+
private workspaceEnv;
|
|
1677
|
+
/** What the workspace env has already warned about, so a run says it once, not per task. */
|
|
1678
|
+
private envWarnings;
|
|
1679
|
+
/**
|
|
1680
|
+
* Tasks pulled out of auto-scheduling (user-cancelled or failed to spawn).
|
|
1681
|
+
* They stay 'pending' — "not executed" — but the scheduler skips them until
|
|
1682
|
+
* the user retries or force-starts, which would otherwise loop forever on a
|
|
1683
|
+
* task whose spawn always throws.
|
|
1684
|
+
*/
|
|
1685
|
+
private onHold;
|
|
1686
|
+
/**
|
|
1687
|
+
* Tasks Mark complete is landing after it ended their attempt: in progress
|
|
1688
|
+
* with no attempt for as long as that takes, and not orphans for it.
|
|
1689
|
+
*/
|
|
1690
|
+
private completing;
|
|
1691
|
+
/** One wait per runner for its usage limit to reset; the tasks it resumes, and when, are on the plan (#84). */
|
|
1692
|
+
private limitWaits;
|
|
1693
|
+
/**
|
|
1694
|
+
* The plan's isolation run and the open run's lifecycle (ADR-0013); see
|
|
1695
|
+
* {@link IsolationRunController}. Read and acted on there directly, except
|
|
1696
|
+
* Merge all: that goes through {@link mergeRun}, which keeps it apart from
|
|
1697
|
+
* ops tasks and starts the tasks its merge lets through.
|
|
1698
|
+
*/
|
|
1699
|
+
readonly runs: IsolationRunController;
|
|
1700
|
+
/** A passed attempt's work onto the integration branch, and the conflict repair a landing may need. */
|
|
1701
|
+
private landing;
|
|
1702
|
+
private registry;
|
|
1703
|
+
private workspaceRootFn;
|
|
1704
|
+
private observers;
|
|
1705
|
+
private skillsAt;
|
|
1706
|
+
private previousAttemptFromLog;
|
|
1707
|
+
private runSettings;
|
|
1708
|
+
private isApproved;
|
|
1709
|
+
constructor(deps: TaskOrchestratorDeps);
|
|
1710
|
+
/**
|
|
1711
|
+
* The composition root: resolves every optional collaborator and wires the
|
|
1712
|
+
* isolation listener that the constructor cannot, because it needs the
|
|
1713
|
+
* instance's own (private) emit seam. Nothing else in the
|
|
1714
|
+
* codebase constructs an orchestrator, so this is the one place defaults
|
|
1715
|
+
* live — a host or test supplies what it cares about and gets the rest.
|
|
1716
|
+
*/
|
|
1717
|
+
static compose(options: TaskOrchestratorOptions): TaskOrchestrator;
|
|
1718
|
+
/** Advisory silence timestamp for a task's live runner, or null if not idle. */
|
|
1719
|
+
getIdleSince(taskId: string): string | null;
|
|
1720
|
+
/**
|
|
1721
|
+
* What an ops retry is told about the attempt before. The saved log is read
|
|
1722
|
+
* whether or not the session was reloaded, so both behave alike; the output
|
|
1723
|
+
* buffer is the fallback for a runner that has no log. A reload clears the
|
|
1724
|
+
* attempt count along with the buffer, so a retry made since is what says
|
|
1725
|
+
* there was an attempt to report on.
|
|
1726
|
+
*/
|
|
1727
|
+
private opsPreviousAttempt;
|
|
1728
|
+
/** Recent clean output of a task's latest attempt, running or ended; null if it never ran. */
|
|
1729
|
+
getLiveOutput(taskId: string, opts: LiveTailOptions): LiveTail | null;
|
|
1730
|
+
get storeInstance(): PlanStore;
|
|
1731
|
+
/**
|
|
1732
|
+
* The variables a task's agent gets from its workspace. What cannot be
|
|
1733
|
+
* applied is said once — silently starting without them is how agents ran
|
|
1734
|
+
* under the wrong account when an edited `.envrc` was left unallowed.
|
|
1735
|
+
*/
|
|
1736
|
+
private envForTask;
|
|
1737
|
+
approveCheckpoint(taskId: string): void;
|
|
1738
|
+
rejectCheckpoint(taskId: string, reason?: string): void;
|
|
1739
|
+
/** What the task's waiting checkpoint asks, whole; undefined once it settles or when none waits. */
|
|
1740
|
+
getCheckpointQuestion(taskId: string): string | undefined;
|
|
1741
|
+
awaitsCheckpoint(taskId: string): boolean;
|
|
1742
|
+
/**
|
|
1743
|
+
* Send a structured task a user message (ADR-0018, M1). Mid-turn it queues
|
|
1744
|
+
* behind the turn; a task waiting for input takes it at once and is back in
|
|
1745
|
+
* progress. Returns the message's id, for {@link removeQueuedTaskMessage}.
|
|
1746
|
+
*/
|
|
1747
|
+
sendTaskMessage(taskId: string, text: string): string;
|
|
1748
|
+
/**
|
|
1749
|
+
* Force send (ADR-0023, F1–F3): interrupt the running turn and deliver the
|
|
1750
|
+
* message as the next one, ahead of anything queued, without the task
|
|
1751
|
+
* waiting for input in between. A task already waiting for input just takes it.
|
|
1752
|
+
*/
|
|
1753
|
+
forceSendTaskMessage(taskId: string, text: string): string;
|
|
1754
|
+
/**
|
|
1755
|
+
* Run the task runner's own command `/name args` (#66) as a turn of its
|
|
1756
|
+
* own, waiting behind a running turn as a message would. Ordewell carries
|
|
1757
|
+
* it; what it does is the runner's business (#24).
|
|
1758
|
+
*/
|
|
1759
|
+
invokeTaskCommand(taskId: string, name: string, args?: string): string;
|
|
1760
|
+
private messageTask;
|
|
1761
|
+
/** Force send a message not yet delivered, queued or handed over; false once it was delivered. */
|
|
1762
|
+
forceSendQueuedTaskMessage(taskId: string, id: string): boolean;
|
|
1763
|
+
/** Take back a message still queued behind a turn; false once it was delivered. */
|
|
1764
|
+
removeQueuedTaskMessage(taskId: string, id: string): boolean;
|
|
1765
|
+
/** Stop a structured task's running turn; it then waits for input like any turn that ends without a `task_complete` call. */
|
|
1766
|
+
interruptTask(taskId: string): Promise<void>;
|
|
1767
|
+
/** What is waiting for a structured task's turn to end; empty for any other task. */
|
|
1768
|
+
getQueuedTaskMessages(taskId: string): QueuedTaskMessage[];
|
|
1769
|
+
private runnerSession;
|
|
1770
|
+
/**
|
|
1771
|
+
* A structured turn that ended without a `task_complete` call (ADR-0018, W1). No
|
|
1772
|
+
* verdict is guessed: the task waits for input, unless a checkpoint in the
|
|
1773
|
+
* same turn already has it waiting, or a queued message went straight out —
|
|
1774
|
+
* the session never passes through idle then, so nothing flickers. The first
|
|
1775
|
+
* turn of an attempt to end that way, cleanly, gets the completion reminder
|
|
1776
|
+
* instead: weaker models often finish the work and report in prose.
|
|
1777
|
+
*/
|
|
1778
|
+
private onTurnEnd;
|
|
1779
|
+
subscribe(observer: OrchestratorObserver): () => void;
|
|
1780
|
+
/**
|
|
1781
|
+
* Isolated per observer: one that throws must not take down the scheduler
|
|
1782
|
+
* or stop the remaining observers from hearing the event.
|
|
1783
|
+
*/
|
|
1784
|
+
private emit;
|
|
1785
|
+
get isRunning(): boolean;
|
|
1786
|
+
/**
|
|
1787
|
+
* A runner is executing *right now*. Narrower than {@link isRunning}, which
|
|
1788
|
+
* also covers the armed-but-idle scheduler `tick()` deliberately leaves
|
|
1789
|
+
* behind when a plan is paused on a user task, a checkpoint or a hold —
|
|
1790
|
+
* nothing is executing then, so nothing is reading the plan mid-mutation.
|
|
1791
|
+
*/
|
|
1792
|
+
get hasLiveWork(): boolean;
|
|
1793
|
+
get isReviewApproved(): boolean;
|
|
1794
|
+
/** The scheduler is driving the plan: ready tasks start on their own as slots free. Narrower than {@link isRunning}. */
|
|
1795
|
+
get isScheduling(): boolean;
|
|
1796
|
+
/**
|
|
1797
|
+
* A full-plan run with nothing starting that something still resumes: a
|
|
1798
|
+
* usage limit's reset, or a retry of a failed task its blocked tasks wait on.
|
|
1799
|
+
*/
|
|
1800
|
+
get awaitsResume(): boolean;
|
|
1801
|
+
get status(): 'approved' | 'running' | 'completed';
|
|
1802
|
+
get activeTaskIds(): string[];
|
|
1803
|
+
/** Task id → session id of every attempt whose runner is up; a spawn in flight has no session yet. */
|
|
1804
|
+
get activeSessionMap(): Map<string, string>;
|
|
1805
|
+
getAttempt(taskId: string): TaskAttemptSnapshot | undefined;
|
|
1806
|
+
/** A planner turn holds new starts: what is ready starts when it releases them. */
|
|
1807
|
+
get startsHeld(): boolean;
|
|
1808
|
+
/**
|
|
1809
|
+
* Hold the scheduler's new starts while a planner turn decides what runs
|
|
1810
|
+
* next, so its edits do not race a task that would otherwise start under
|
|
1811
|
+
* it. Running attempts are untouched. The release is idempotent and starts
|
|
1812
|
+
* nothing itself: whoever releases ticks, which starts what became ready.
|
|
1813
|
+
*/
|
|
1814
|
+
holdStarts(): () => void;
|
|
1815
|
+
/**
|
|
1816
|
+
* Admit a start that will wait before it claims — on the user's answer, a
|
|
1817
|
+
* held edit, the run opening. Until it is released, Stop counts it as live
|
|
1818
|
+
* work; once a Stop comes it is no longer current, and its claim is refused.
|
|
1819
|
+
*/
|
|
1820
|
+
admitStart(): StartAdmission;
|
|
1821
|
+
/**
|
|
1822
|
+
* Something can still start that nobody has to ask for again: a start
|
|
1823
|
+
* admitted and not yet claimed, or a task that resumes on its own at its
|
|
1824
|
+
* usage limit's reset. A Stop is owed to it, whether or not anything runs.
|
|
1825
|
+
*/
|
|
1826
|
+
get startsPending(): boolean;
|
|
1827
|
+
/** Run `work`, which awaits before it claims, under an admission it holds throughout; it is handed the generation it was admitted under. */
|
|
1828
|
+
private whileAdmitted;
|
|
1829
|
+
/**
|
|
1830
|
+
* "Merge all"; a settled run that merged everything is cleared up and
|
|
1831
|
+
* forgotten. During a run it merges what has landed and the run goes on:
|
|
1832
|
+
* the tasks waiting at a merge gate for that work then start (ADR-0020).
|
|
1833
|
+
* Never alongside ops work, either way round ({@link mergeExcludes}).
|
|
1834
|
+
*/
|
|
1835
|
+
mergeRun(): Promise<IsolationMergeResult>;
|
|
1836
|
+
private mergeLanded;
|
|
1837
|
+
/**
|
|
1838
|
+
* Close the settled run. With merge at gates automatically on, landed work
|
|
1839
|
+
* not merged yet is merged first, through the gate's path, so no surface
|
|
1840
|
+
* hears of a handoff for a run about to merge; one that stopped is handed
|
|
1841
|
+
* over as before.
|
|
1842
|
+
*/
|
|
1843
|
+
private closeRun;
|
|
1844
|
+
/**
|
|
1845
|
+
* The run as a surface offers Merge all mid-run (ADR-0020): what has landed
|
|
1846
|
+
* and is not merged, and whether the run is paused at its gates with nothing
|
|
1847
|
+
* else running. Null once everything landed is merged.
|
|
1848
|
+
*/
|
|
1849
|
+
runMergeView(): RunMergeView | null;
|
|
1850
|
+
/**
|
|
1851
|
+
* The ancestors an ops or user task waits on at its merge gate: those whose
|
|
1852
|
+
* landed work is not in the user's branch yet (ADR-0020). Empty once the
|
|
1853
|
+
* task has started, and for every other task.
|
|
1854
|
+
*/
|
|
1855
|
+
getMergeGate(taskId: string): string[];
|
|
1856
|
+
/** The failed tasks a task not started yet waits on, through its dependencies (ADR-0034); empty when it is not blocked. */
|
|
1857
|
+
getBlockedBy(taskId: string): string[];
|
|
1858
|
+
loadPlan(tasks: readonly Task[], planRunners?: RunnerId[]): void;
|
|
1859
|
+
/**
|
|
1860
|
+
* Adopt an edited plan without letting go of the run in progress. Everything
|
|
1861
|
+
* the scheduler owns — live sessions, holds, retry counts, the verifier and
|
|
1862
|
+
* the review approval — survives, because `loadPlan` clears all of it and a
|
|
1863
|
+
* mid-run edit is not a new run. Only the tasks change.
|
|
1864
|
+
*
|
|
1865
|
+
* The ids with live sessions are defended: one dropped from the edited plan is
|
|
1866
|
+
* carried over (its verdict still has to land somewhere), and one whose status
|
|
1867
|
+
* the snapshot predates is put back to `in_progress` — adopting the snapshot's
|
|
1868
|
+
* "pending" would offer the scheduler work a runner is already doing.
|
|
1869
|
+
*
|
|
1870
|
+
* A failed task stays failed, unlike a plan load's fresh chance: an edit is
|
|
1871
|
+
* not a retry, and the tasks it blocks wait for that retry (ADR-0034).
|
|
1872
|
+
*/
|
|
1873
|
+
reconcilePlan(newTasks: Task[], planRunners?: RunnerId[]): void;
|
|
1874
|
+
start(origin?: RunOrigin): Promise<void>;
|
|
1875
|
+
/** A usage-limit pause holds the run: spawning more would spend the same exhausted limit. */
|
|
1876
|
+
private holdOnLimit;
|
|
1877
|
+
/**
|
|
1878
|
+
* Stop the run. The user's Stop also withdraws every start admitted before
|
|
1879
|
+
* it and not yet claimed (ADR-0029); a `restart` only disarms an idle
|
|
1880
|
+
* scheduler for the run about to start, which must not undo starts asked
|
|
1881
|
+
* for meanwhile.
|
|
1882
|
+
*/
|
|
1883
|
+
stop(reason?: 'user' | 'restart'): void;
|
|
1884
|
+
/** Name the tasks a stop took down, so none is mistaken for one still at work. */
|
|
1885
|
+
private sayStopped;
|
|
1886
|
+
/**
|
|
1887
|
+
* A task in progress with no attempt has nothing running it: a stop, a plan
|
|
1888
|
+
* load or anything else that ended its attempt left its status behind. It
|
|
1889
|
+
* is put back to not started, and said, so it can be retried rather than
|
|
1890
|
+
* read as running — or, in an isolated run, count as live work forever.
|
|
1891
|
+
*/
|
|
1892
|
+
private recoverOrphans;
|
|
1893
|
+
/**
|
|
1894
|
+
* Task ids still live, whose worktrees no clean-up of the whole run may
|
|
1895
|
+
* take: an attempt is running them, or the plan has them in progress or
|
|
1896
|
+
* waiting on the user mid-attempt — at a checkpoint, or for input. One
|
|
1897
|
+
* waiting on the user's decision instead (a conflict, a usage limit, files
|
|
1898
|
+
* an ops task changed) is not: no runner is at work on it, and a clean-up
|
|
1899
|
+
* keeps what it holds on a branch before its worktree goes.
|
|
1900
|
+
*/
|
|
1901
|
+
private liveTaskIds;
|
|
1902
|
+
/**
|
|
1903
|
+
* Whether the run may close with its handoff: no task is live. A handoff
|
|
1904
|
+
* tells the user the run has finished, and a Merge all after it clears the
|
|
1905
|
+
* run up, so neither may happen under a task the plan still shows at work.
|
|
1906
|
+
*/
|
|
1907
|
+
private get settled();
|
|
1908
|
+
/**
|
|
1909
|
+
* The boundary a verdict crosses: nothing awaits the verifier's listener, so
|
|
1910
|
+
* a throw from reading the output, landing, releasing a worktree or the tick
|
|
1911
|
+
* after would be an unhandled rejection — and an attempt left integrating is
|
|
1912
|
+
* live work forever. One that never settled fails (a repair waits on the
|
|
1913
|
+
* user, as its conflict did) and blocks only its dependents; either way the
|
|
1914
|
+
* run says why and goes on.
|
|
1915
|
+
*/
|
|
1916
|
+
private deliverVerdict;
|
|
1917
|
+
/** Work nothing awaits: a rejection is said, not left unhandled. */
|
|
1918
|
+
private unawaited;
|
|
1919
|
+
private onVerdict;
|
|
1920
|
+
private afterVerdict;
|
|
1921
|
+
/**
|
|
1922
|
+
* A repair's verdict never replaces the verdict the task's own work earned,
|
|
1923
|
+
* and a repair that does not land leaves the task waiting on the user —
|
|
1924
|
+
* never a failed task, so never one that blocks its dependents.
|
|
1925
|
+
*/
|
|
1926
|
+
private settleRepair;
|
|
1927
|
+
/**
|
|
1928
|
+
* Hand an attempt's verdict to Landing. The attempt stays live while that
|
|
1929
|
+
* settles — it may wait on the module's merge queue — so a cancel, retry or
|
|
1930
|
+
* stop in that window still wins, and waits for the merge before tearing the
|
|
1931
|
+
* worktree down; nothing counts the task as done before its work is on the
|
|
1932
|
+
* integration branch.
|
|
1933
|
+
*/
|
|
1934
|
+
private land;
|
|
1935
|
+
/** Settle a task by how its landing went; `onLanded` is what the path that landed it adds to completing it. */
|
|
1936
|
+
private applyLanding;
|
|
1937
|
+
/**
|
|
1938
|
+
* Work that did not land waits on the user — it never fails the task or
|
|
1939
|
+
* blocks its dependents — unless its conflict is owed a repair, which takes
|
|
1940
|
+
* the slot the ending attempt freed and never one more than the run allows.
|
|
1941
|
+
*/
|
|
1942
|
+
private settleUnlanded;
|
|
1943
|
+
private landResolved;
|
|
1944
|
+
getReadyTasks(): Task[];
|
|
1945
|
+
private readiness;
|
|
1946
|
+
private mergesAutomatically;
|
|
1947
|
+
/**
|
|
1948
|
+
* The user changed the run settings under an open run (ADR-0030). The
|
|
1949
|
+
* scheduler reads them at each decision it makes from them, so a merge gate
|
|
1950
|
+
* already reached is merged now rather than at the next event. Fire-and-forget
|
|
1951
|
+
* — no caller waits on the change — and its failure is said through
|
|
1952
|
+
* {@link unawaited}, never left unhandled.
|
|
1953
|
+
*/
|
|
1954
|
+
runSettingsChanged(): void;
|
|
1955
|
+
private openRun;
|
|
1956
|
+
/**
|
|
1957
|
+
* Open the run a task starts in, and decide whether it isolates if the
|
|
1958
|
+
* task's next attempt needs that ({@link decidesIsolation}). False while a
|
|
1959
|
+
* dirty tree waits on the user; `resume` is what their choice replays.
|
|
1960
|
+
*/
|
|
1961
|
+
private openFor;
|
|
1962
|
+
/**
|
|
1963
|
+
* {@link openFor} for an explicit start admitted at `admittedAt`. A Stop
|
|
1964
|
+
* while the run opened interrupts that opening too, which then opens
|
|
1965
|
+
* nothing; the start says it was stopped rather than going quiet as if a
|
|
1966
|
+
* dirty tree were waiting on the user.
|
|
1967
|
+
*/
|
|
1968
|
+
private openForAdmitted;
|
|
1969
|
+
/** The kind a task's next attempt starts as, short of a repair or a continue: what is weighed before one exists. */
|
|
1970
|
+
private nextAttemptKind;
|
|
1971
|
+
/**
|
|
1972
|
+
* Cancel a running (or scheduled) task: kill its session and return it to
|
|
1973
|
+
* 'pending' — "not executed". The task is put on hold so the scheduler
|
|
1974
|
+
* doesn't immediately restart it; Retry / Force Start release the hold.
|
|
1975
|
+
*
|
|
1976
|
+
* The attempt's worktree is kept, as a stopped or failed one is: a runner is
|
|
1977
|
+
* often cancelled because it looked stuck after doing the work, and Mark
|
|
1978
|
+
* complete can still land that work. The next attempt replaces it.
|
|
1979
|
+
*/
|
|
1980
|
+
cancelTask(taskId: string): Promise<void>;
|
|
1981
|
+
private cancelAttempt;
|
|
1982
|
+
/**
|
|
1983
|
+
* Let go of a task that is leaving the plan. A live runner is cancelled
|
|
1984
|
+
* as {@link cancelTask} does, but its worktree goes: no task is left to land
|
|
1985
|
+
* it into. A spawn still in flight just loses its attempt,
|
|
1986
|
+
* which is what makes {@link runClaimed} kill the session it is about to
|
|
1987
|
+
* receive. The id's cross-attempt bookkeeping goes too — a hold or retry
|
|
1988
|
+
* count kept for a task that no longer exists would be inherited by nothing.
|
|
1989
|
+
*/
|
|
1990
|
+
releaseTask(taskId: string): Promise<void>;
|
|
1991
|
+
markTaskComplete(taskId: string): Promise<void>;
|
|
1992
|
+
/**
|
|
1993
|
+
* Undo a completion: return the task to "not executed" — pending, verdict and
|
|
1994
|
+
* summary dropped, archive entry removed. It is put on hold like a cancel, so
|
|
1995
|
+
* a running plan does not immediately re-spawn the work the user just
|
|
1996
|
+
* un-marked; Retry / Force Start / Run release the hold. Dependents fall back
|
|
1997
|
+
* to waiting on their own, because the scheduler gates on `isCompleted`.
|
|
1998
|
+
*/
|
|
1999
|
+
markTaskIncomplete(taskId: string): Promise<void>;
|
|
2000
|
+
/**
|
|
2001
|
+
* Skip: end a task as `skipped`, with no verdict and nothing of its work
|
|
2002
|
+
* landed. Its worktree is kept, as a failed attempt's is, so the user can
|
|
2003
|
+
* still look at it or Mark complete it later. Its dependents go ahead
|
|
2004
|
+
* without it once the work it waited on is done, and a run that ended on a
|
|
2005
|
+
* failure continues for them, as a retry would continue it. A running task is stopped before it is skipped.
|
|
2006
|
+
*/
|
|
2007
|
+
skipTask(taskId: string, reason?: string, origin?: RunOrigin): Promise<void>;
|
|
2008
|
+
private logAndArchive;
|
|
2009
|
+
retryTask(taskId: string, origin?: RunOrigin): Promise<void>;
|
|
2010
|
+
/**
|
|
2011
|
+
* Continue a finished structured task in its saved runner session (ADR-0018,
|
|
2012
|
+
* K1): a retry whose first turn is the user's message, sent to the session
|
|
2013
|
+
* the task ended in rather than a fresh one given its prompt again. The
|
|
2014
|
+
* worktree keeps the same path, where the runner keeps its sessions, and
|
|
2015
|
+
* restores the previous work so that session's memory matches its files.
|
|
2016
|
+
*/
|
|
2017
|
+
continueTask(taskId: string, message: string, origin?: RunOrigin): Promise<void>;
|
|
2018
|
+
/**
|
|
2019
|
+
* Keep a task paused on its runner's usage limit from resuming on its own:
|
|
2020
|
+
* it waits for a manual retry, as one whose reset time could not be read
|
|
2021
|
+
* does. What a surface's cancel calls, and what moving the task to another
|
|
2022
|
+
* runner has to call first. False when no resume was scheduled for it.
|
|
2023
|
+
*/
|
|
2024
|
+
cancelScheduledResume(taskId: string): boolean;
|
|
2025
|
+
/**
|
|
2026
|
+
* Run before writing assignments so cancellation sees the old runner's
|
|
2027
|
+
* shared wait. A session cannot cross runners, so the next attempt is a
|
|
2028
|
+
* carry-over of the paused attempt's files (ADR-0018, U1).
|
|
2029
|
+
*/
|
|
2030
|
+
releaseMovedResume(taskId: string, changes: Partial<Task>): Partial<Task>;
|
|
2031
|
+
/**
|
|
2032
|
+
* A task's attempt stopped on its runner's usage limit, without a
|
|
2033
|
+
* completion call. Failing would paint a red X on a task that only has to
|
|
2034
|
+
* wait, and spawning more tasks would spend the same exhausted limit, so the
|
|
2035
|
+
* task pauses and the run holds. When the runner said when its limit
|
|
2036
|
+
* resets, the task joins its runner's wait and resumes on its own then
|
|
2037
|
+
* (#84); otherwise, or when it was resumed a moment ago and hit the limit
|
|
2038
|
+
* again, it waits for the user to retry it.
|
|
2039
|
+
*/
|
|
2040
|
+
private pauseOnLimit;
|
|
2041
|
+
/**
|
|
2042
|
+
* The limit arrived as the turn's error with the runner still up: the
|
|
2043
|
+
* attempt ends here, as its verdict would have ended it, so a resume is a
|
|
2044
|
+
* continue in a fresh process (ADR-0018, L1/K1).
|
|
2045
|
+
*/
|
|
2046
|
+
private stopOnLimit;
|
|
2047
|
+
/** The tasks waiting on `runner`'s reset to resume on their own. */
|
|
2048
|
+
private waitingOn;
|
|
2049
|
+
/**
|
|
2050
|
+
* Arm `runner`'s one wait for the latest resume time among its tasks, and
|
|
2051
|
+
* put that time on each of them, so they resume together.
|
|
2052
|
+
*/
|
|
2053
|
+
private armLimitWait;
|
|
2054
|
+
/**
|
|
2055
|
+
* The waits a loaded plan still owes. A resume time that passed while
|
|
2056
|
+
* nothing was running — the daemon was down, say — resumes at once.
|
|
2057
|
+
*/
|
|
2058
|
+
private rearmLimitWaits;
|
|
2059
|
+
/**
|
|
2060
|
+
* Resume a task paused on its runner's usage limit now, in its saved
|
|
2061
|
+
* session, once the user says the limit has reset early. The limit is the
|
|
2062
|
+
* runner's account's, so every task waiting on that runner's reset resumes
|
|
2063
|
+
* with it, as at the reset itself. `message` follows the reset notice in the
|
|
2064
|
+
* same continuation. A resumed attempt that hits the limit again at once
|
|
2065
|
+
* waits for the user, as an automatic resume's does: that is what keeps a
|
|
2066
|
+
* caller from resuming it round and round.
|
|
2067
|
+
*/
|
|
2068
|
+
resumeNow(taskId: string, origin?: RunOrigin, message?: string): Promise<void>;
|
|
2069
|
+
private resumeAfterLimit;
|
|
2070
|
+
/**
|
|
2071
|
+
* Retry a task paused on a usage limit, continuing its saved session where
|
|
2072
|
+
* it left one (ADR-0018, K1), then release the hold the pause put on the
|
|
2073
|
+
* run once nothing else waits on a reset.
|
|
2074
|
+
*/
|
|
2075
|
+
private resumeTask;
|
|
2076
|
+
/**
|
|
2077
|
+
* A resume at the limit's reset that nobody waits on cannot be refused to
|
|
2078
|
+
* anyone, so one that may not claim waits for the user instead — its resume
|
|
2079
|
+
* time dropped, and said — unless a Stop is why, which said so itself.
|
|
2080
|
+
*/
|
|
2081
|
+
private mayResumeAtReset;
|
|
2082
|
+
/**
|
|
2083
|
+
* Manually start a single AI task right now, bypassing dependency/readiness
|
|
2084
|
+
* gating (the "force start" affordance on a task card) but never the claim:
|
|
2085
|
+
* it still needs the user's approval, a free slot, and no Stop since it was
|
|
2086
|
+
* asked for, and throws {@link TaskControlError} saying which it lacks.
|
|
2087
|
+
* Reuses the scheduler's own claim so a force-started task gets the same
|
|
2088
|
+
* augmented prompt, session tracking, and exit handling — callers must not
|
|
2089
|
+
* re-spawn the runner themselves. No-op if the task is unknown, not an AI
|
|
2090
|
+
* task, or already running.
|
|
2091
|
+
*/
|
|
2092
|
+
forceStartTask(taskId: string, origin?: RunOrigin): Promise<void>;
|
|
2093
|
+
private refuseOpsDuringMerge;
|
|
2094
|
+
/**
|
|
2095
|
+
* A force start passes a merge gate (ADR-0020), and the surface confirmed it
|
|
2096
|
+
* first; the choice is kept on the task and said, because the work it acts
|
|
2097
|
+
* on is not in the user's branch.
|
|
2098
|
+
*/
|
|
2099
|
+
private notePastGate;
|
|
2100
|
+
/**
|
|
2101
|
+
* Run exactly one task outside full-plan scheduling. The active/starting
|
|
2102
|
+
* session still contributes to {@link hasLiveWork} so every surface exposes
|
|
2103
|
+
* Stop and disables Execute Plan, but onVerdict cannot auto-schedule other
|
|
2104
|
+
* tasks because the plan scheduler's `running` flag remains false.
|
|
2105
|
+
*/
|
|
2106
|
+
runTask(taskId: string): Promise<void>;
|
|
2107
|
+
getCompletedCount(): number;
|
|
2108
|
+
getTotalCount(): number;
|
|
2109
|
+
isAllComplete(): boolean;
|
|
2110
|
+
isAnyFailed(): boolean;
|
|
2111
|
+
approveReview(origin?: RunOrigin): Promise<void>;
|
|
2112
|
+
tick(): Promise<void>;
|
|
2113
|
+
/** How a run that ended on failures ends: what it finished, what failed, and what that blocks. */
|
|
2114
|
+
/** Skipped work is not completed work, so a run with any says so rather than that everything completed. */
|
|
2115
|
+
private sayFinished;
|
|
2116
|
+
private sayEndedOnFailure;
|
|
2117
|
+
/** Whether the scheduler may start `id` now: it passes every readiness gate and a slot is free. */
|
|
2118
|
+
private stillReady;
|
|
2119
|
+
/**
|
|
2120
|
+
* A run paused at merge gates says so, as plainly as a task that waits on
|
|
2121
|
+
* the user: which tasks wait, and that Merge all lets them go on (ADR-0020).
|
|
2122
|
+
*/
|
|
2123
|
+
private sayGate;
|
|
2124
|
+
/**
|
|
2125
|
+
* Why a start may not take its claim now, or null when it may: a Stop came
|
|
2126
|
+
* since it was admitted (ADR-0029), the task is outside the user's approval
|
|
2127
|
+
* record (ADR-0035), or every slot is taken. Read in the same synchronous
|
|
2128
|
+
* step as the claim it guards, so nothing can change in between.
|
|
2129
|
+
*/
|
|
2130
|
+
private claimRefusal;
|
|
2131
|
+
/** An explicit start's refusal, before it changes anything: its caller's answer, as a {@link TaskControlError}. */
|
|
2132
|
+
private refuseClaim;
|
|
2133
|
+
/** An explicit start's claim, once {@link refuseClaim} let it through: its spawn, or the refusal as a rejection. */
|
|
2134
|
+
private claimed;
|
|
2135
|
+
/**
|
|
2136
|
+
* The one claim every attempt start passes — the scheduler's, a run or force
|
|
2137
|
+
* start, a retry's, a continue, a resume, a repair. Checked and taken in one
|
|
2138
|
+
* synchronous step ({@link claimRefusal}), then marked claimed before
|
|
2139
|
+
* anything is awaited, so no other start can take the same task or slot.
|
|
2140
|
+
* A task already claimed is not claimed again: there is nothing to start.
|
|
2141
|
+
*/
|
|
2142
|
+
private claim;
|
|
2143
|
+
private runClaimed;
|
|
2144
|
+
/**
|
|
2145
|
+
* The fallible half of starting a task: resolving its cwd/worktree and
|
|
2146
|
+
* spawning the runner. Resolves false when the attempt never committed —
|
|
2147
|
+
* abandoned because it was ended from under it, or a failed spawn already
|
|
2148
|
+
* unwound (both leave `runClaimed` with nothing further to announce).
|
|
2149
|
+
*/
|
|
2150
|
+
private spawnAttempt;
|
|
2151
|
+
/** For a skill the main checkout has, the folder a worktree only gets once it is committed. */
|
|
2152
|
+
private uncommittedSkillFolder;
|
|
2153
|
+
/**
|
|
2154
|
+
* The roots whose `.ordewell/skills/` an attempt at `cwd` reads: in a repo
|
|
2155
|
+
* group, `cwd` holds each repo — a worktree of it when isolated, the
|
|
2156
|
+
* workspace's own checkout when not — and no folder of its own (ADR-0014).
|
|
2157
|
+
*/
|
|
2158
|
+
private skillRoots;
|
|
2159
|
+
/**
|
|
2160
|
+
* A continue whose runner never announced the session it was told to
|
|
2161
|
+
* resume. Nothing was continued, and nothing fresh was started in its place.
|
|
2162
|
+
*/
|
|
2163
|
+
private unresumed;
|
|
2164
|
+
/**
|
|
2165
|
+
* A spawn whose attempt ended while it was in flight: kill what it produced
|
|
2166
|
+
* and take back the claim it made. Only the claim — whatever ended the
|
|
2167
|
+
* attempt may have decided the task since (mark complete, cancel, retry).
|
|
2168
|
+
*/
|
|
2169
|
+
private abandonSpawn;
|
|
2170
|
+
private tell;
|
|
2171
|
+
private say;
|
|
2172
|
+
/**
|
|
2173
|
+
* Replay the start a dirty tree turned away. `stash` puts the user's tracked
|
|
2174
|
+
* changes on the git stash first, so the run isolates; `shared` runs this one
|
|
2175
|
+
* run in the workspace root, knowingly.
|
|
2176
|
+
*/
|
|
2177
|
+
continueBlockedRun(how: 'stash' | 'shared'): Promise<void>;
|
|
2178
|
+
/**
|
|
2179
|
+
* The one way an attempt ends. Dropping the record is itself what
|
|
2180
|
+
* invalidates a spawn still in flight ({@link runClaimed} kills the session
|
|
2181
|
+
* it receives for an attempt that is no longer current).
|
|
2182
|
+
*
|
|
2183
|
+
* A user interruption also bumps the verifier's generation *before* stopping
|
|
2184
|
+
* the runner: a runner may fire onExit synchronously from
|
|
2185
|
+
* stop(), and if that exit reaches VerdictEngine under the still-valid
|
|
2186
|
+
* generation it delivers a verdict that marks the task 'completed' for one
|
|
2187
|
+
* tick — long enough for the scheduler to start a dependent task. Stop and
|
|
2188
|
+
* load reset the whole verifier themselves.
|
|
2189
|
+
*/
|
|
2190
|
+
private endAttempt;
|
|
2191
|
+
/** Kept on the task once the attempt ends, so a continue can resume it after a reload (ADR-0018, K1). */
|
|
2192
|
+
private saveNativeSession;
|
|
2193
|
+
private endAllAttempts;
|
|
2194
|
+
}
|
|
2072
2195
|
|
|
2073
2196
|
/**
|
|
2074
2197
|
* Task references: the one grammar for naming a task in a plan, both ways.
|
|
@@ -2465,6 +2588,18 @@ interface ConversationView extends BlockList {
|
|
|
2465
2588
|
}[];
|
|
2466
2589
|
}
|
|
2467
2590
|
declare const EMPTY_CONVERSATION: ConversationView;
|
|
2591
|
+
type Message<T extends SessionMessage['type']> = Extract<SessionMessage, {
|
|
2592
|
+
type: T;
|
|
2593
|
+
}>;
|
|
2594
|
+
/**
|
|
2595
|
+
* Bring the view's approval cards in line with the planner's requests the
|
|
2596
|
+
* session says are still waiting — for a surface that was not listening the
|
|
2597
|
+
* whole time: one that reconnected, reloaded, or just attached. A pending card
|
|
2598
|
+
* the session no longer has was answered or expired meanwhile; this surface
|
|
2599
|
+
* never heard how, so the card goes rather than guessing an answer. A waiting
|
|
2600
|
+
* request with no card gets one, answerable as the live one would have been.
|
|
2601
|
+
*/
|
|
2602
|
+
declare function reconcileApprovals(view: ConversationView, waiting: readonly Message<'approval_request'>[]): ConversationView;
|
|
2468
2603
|
/**
|
|
2469
2604
|
* Fold one input into the view. Pure and incremental: blocks the input does
|
|
2470
2605
|
* not touch keep their identity, and an input that changes nothing returns
|
|
@@ -2697,6 +2832,12 @@ declare function awaitingLabel(task: {
|
|
|
2697
2832
|
declare function blockedByLabel(refs: readonly string[]): string;
|
|
2698
2833
|
/** "Skipped: no GPU on this host", or "Skipped" when no reason was given. */
|
|
2699
2834
|
declare function skippedLabel(reason: string | null): string;
|
|
2835
|
+
/**
|
|
2836
|
+
* A task added after the user approved the plan, which the scheduler leaves
|
|
2837
|
+
* for them (ADR-0035). Not a runner approval: nothing has started, so no
|
|
2838
|
+
* runner is asking anything.
|
|
2839
|
+
*/
|
|
2840
|
+
declare const WAITS_FOR_APPROVAL_LABEL = "Waiting for your approval to start";
|
|
2700
2841
|
/** Runner requests a task waits on (ADR-0018, A1); null when none do. */
|
|
2701
2842
|
declare function approvalLabel(count: number | undefined): string | null;
|
|
2702
2843
|
/**
|
|
@@ -2767,6 +2908,8 @@ interface TaskRowSubject {
|
|
|
2767
2908
|
/** Its last attempt left a runner session to continue (ADR-0018, K1). */
|
|
2768
2909
|
continuable?: boolean;
|
|
2769
2910
|
awaitingApproval?: number;
|
|
2911
|
+
/** Added after the user approved the plan, and not approved since (ADR-0035). */
|
|
2912
|
+
waitsForApproval?: boolean;
|
|
2770
2913
|
isolation?: TaskRowIsolation | null;
|
|
2771
2914
|
mergeGate?: readonly string[];
|
|
2772
2915
|
forcedPastGate?: readonly string[];
|
|
@@ -2833,6 +2976,8 @@ interface TaskRowView {
|
|
|
2833
2976
|
resumes: string | null;
|
|
2834
2977
|
/** Runner requests waiting on the user, in sentence case; null when none do. */
|
|
2835
2978
|
approvals: string | null;
|
|
2979
|
+
/** That it waits for the user's approval to start, in sentence case; null once it started, or when it needs none. */
|
|
2980
|
+
waitsForApproval: string | null;
|
|
2836
2981
|
/** The task's mode is tagged autonomous: it runs without permission prompts. */
|
|
2837
2982
|
autonomous: boolean;
|
|
2838
2983
|
/** The repos its isolated work changed, by path; empty for a group of one. */
|
|
@@ -2855,7 +3000,7 @@ interface TaskRowView {
|
|
|
2855
3000
|
files: string;
|
|
2856
3001
|
landed: boolean;
|
|
2857
3002
|
} | null;
|
|
2858
|
-
/** The
|
|
3003
|
+
/** The ancestors, by reference, whose work is not merged into the user's branch yet. */
|
|
2859
3004
|
mergeGate: string[] | null;
|
|
2860
3005
|
/** The failed tasks, by reference, a task not started waits on through its dependencies (ADR-0034); null when none. */
|
|
2861
3006
|
blockedBy: string[] | null;
|
|
@@ -2863,7 +3008,7 @@ interface TaskRowView {
|
|
|
2863
3008
|
skipReason: string | null;
|
|
2864
3009
|
/** An ops task changed tracked files in the user's checkout and nothing was committed. */
|
|
2865
3010
|
opsChangedFiles: boolean;
|
|
2866
|
-
/** The
|
|
3011
|
+
/** The ancestors, by title, a force start went past the merge gate of. */
|
|
2867
3012
|
forcedPastGate: readonly string[] | null;
|
|
2868
3013
|
/** The task's dependencies, by reference. */
|
|
2869
3014
|
dependencies: string[];
|
|
@@ -2930,4 +3075,4 @@ declare function parseSwitch(arg: string): boolean | null;
|
|
|
2930
3075
|
/** A whole number at least `min`, as typed; null for anything else. */
|
|
2931
3076
|
declare function parseCount(arg: string | undefined, min: number): number | null;
|
|
2932
3077
|
|
|
2933
|
-
export { type
|
|
3078
|
+
export { type RunTask as $, ACT_RISK as A, type BlockingSubject as B, CHECKPOINT_TRUNCATE_LENGTH as C, type DiffRow as D, EMPTY_CONVERSATION as E, NO_TURN as F, type GatedConversation as G, type HeldEdit as H, type INotification as I, type NewTaskFields as J, type NotificationAction as K, type LiveTail as L, type MarkdownToken as M, NOT_STARTED as N, type OrchestratorObserver as O, type OutputPreview as P, type PlanBlock as Q, type PlanMarkerStatus as R, type PlanSnapshot as S, PlanStore as T, type PlannerModes as U, type PromptHold as V, RUN_SETTINGS_DEFAULTS as W, type RefTask as X, type RunInput as Y, type RunMergeView as Z, RunSettingsError as _, AUTORUN_RISK as a, capConflictFiles as a$, type RunTaskStatus as a0, type RunView as a1, type RunnerLoadedBlock as a2, STOPPED_BEFORE_START as a3, SUPERVISOR_LEVEL_LABEL as a4, type SerializedConversationMessage as a5, type SerializedPendingTurn as a6, type SerializedPlan as a7, type SerializedTask as a8, type SerializedTaskStatus as a9, type TaskRowContext as aA, type TaskRowIsolation as aB, type TaskRowKind as aC, type TaskRowPlacement as aD, type TaskRowSubject as aE, type TaskRowUsage as aF, type TaskRowView as aG, type TaskStatusKind as aH, type ThinkingDisplayBlock as aI, type ToolBlock as aJ, type ToolHeadline as aK, type ToolStatus as aL, type TurnGate as aM, type UsageBlock as aN, type UserSettings as aO, WAITS_FOR_APPROVAL_LABEL as aP, aheadOfDraft as aQ, applyTaskOps as aR, approvalLabel as aS, automergeSummary as aT, autorunSummary as aU, awaitingLabel as aV, blockedByLabel as aW, blockingFailures as aX, canMergeTasks as aY, canSetDependencies as aZ, canSplitTask as a_, type SessionBroadcaster as aa, type SessionMessage as ab, type SessionNotice as ac, SettingsService as ad, type SkillLoadBlock as ae, type SkillLookup as af, type SkillToken as ag, type StartAdmission as ah, type SubagentBlock as ai, type SubagentChild as aj, type SubagentStatus as ak, type SurfacePlan as al, type SurfacePlanState as am, type SurfaceTask as an, type TakenPrompt as ao, type TaskAttemptSnapshot as ap, TaskControlError as aq, type TaskLogEvent as ar, type TaskLogView as as, type TaskOp as at, TaskOrchestrator as au, type TaskOrchestratorDeps as av, type TaskOrchestratorOptions as aw, type TaskOutputSource as ax, type TaskRef as ay, type TaskRowAction as az, type ApplyTaskOpsResult as b, titledTaskRefOf as b$, classifyOutcome as b0, coalesceTaskLog as b1, dependencyCandidates as b2, dependentsNotice as b3, dependentsOf as b4, diffRows as b5, diffStat as b6, diffSummary as b7, digestTaskLog as b8, drainNext as b9, resolveTaskRef as bA, resumeLabel as bB, resumeTime as bC, runSettingsOf as bD, runnerToolSubject as bE, serializePlan as bF, serializeTask as bG, serializeTaskStatus as bH, skillTokens as bI, skippedLabel as bJ, stopTurn as bK, summarizeToolCall as bL, supervisorSummary as bM, surfacePlan as bN, surfacePlanState as bO, takeAt as bP, taskLogForSurface as bQ, taskOrderLabel as bR, taskRef as bS, taskRefOf as bT, taskRowActions as bU, taskRowView as bV, taskStartedNotice as bW, taskStatusKind as bX, textHasTaskOps as bY, titledRefs as bZ, titledTaskRef as b_, executionSummary as ba, followTurn as bb, fromTranscript as bc, getSettingsPath as bd, hasHiddenDetail as be, holdPrompt as bf, isTaskRunning as bg, loadedSkillTokens as bh, markAction as bi, markRequestFor as bj, markdownHtml as bk, markdownTokens as bl, namedRepos as bm, newTaskFields as bn, opsEditable as bo, outputLines as bp, outputPreview as bq, parseCount as br, parseSwitch as bs, parseTaskOpsJson as bt, pastGateConfirmation as bu, reconcileApprovals as bv, reduceConversation as bw, reduceRun as bx, reduceTaskLog as by, replayTaskLog as bz, type ApprovalBlock as c, toTaskLogEvent as c0, toolHeadline as c1, trimToolOutput as c2, truncateCheckpointSummary as c3, unsendAll as c4, unsendLatest as c5, type ApprovalStatus as d, type AutoMergeTrigger as e, type CommandBlock as f, type CommandStatus as g, type CompactionBlock as h, type ConversationInput as i, type ConversationView as j, type DiffRowKind as k, type DiffStat as l, type DisplayBlock as m, EMPTY_HOLD as n, EMPTY_RUN as o, EMPTY_TASK_LOG as p, type EditCatalog as q, type ExecutionSummary as r, type GlobalRunSettings as s, type HeldEditMark as t, type IsolatedExecution as u, IsolationRunController as v, type LiveTailOptions as w, type LocalEntry as x, type MessageBlock as y, type MessageRole as z };
|