@ordewell/core 0.10.5 → 0.10.7
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/{IRunner-Crkt3tHD.d.ts → IFileSystem-C4_mpfze.d.ts} +97 -14
- package/dist/{IRunner-lMQIa5Wc.d.mts → IFileSystem-DUgeWWQN.d.mts} +97 -14
- package/dist/{ModeResolver-B4hhABNf.d.mts → ModeResolver-4qNjxcDs.d.mts} +1 -1
- package/dist/{ModeResolver-CUR5jCSU.d.ts → ModeResolver-DGK2rWHE.d.ts} +1 -1
- package/dist/{Task-D4QHjdGv.d.mts → Task-C0o4i3Nn.d.mts} +23 -4
- package/dist/{Task-D4QHjdGv.d.ts → Task-C0o4i3Nn.d.ts} +23 -4
- package/dist/{chunk-6LEA6WAM.mjs → chunk-DMG4ISUG.mjs} +923 -45
- package/dist/chunk-DMG4ISUG.mjs.map +1 -0
- package/dist/{chunk-RT4NQ2BZ.mjs → chunk-EEE2GBLG.mjs} +114 -27
- package/dist/chunk-EEE2GBLG.mjs.map +1 -0
- package/dist/chunk-H7QX32BH.mjs +647 -0
- package/dist/chunk-H7QX32BH.mjs.map +1 -0
- package/dist/{chunk-F3KIX7WR.mjs → chunk-LTHKFBKM.mjs} +4 -4
- package/dist/chunk-LTHKFBKM.mjs.map +1 -0
- package/dist/index.d.mts +278 -1701
- package/dist/index.d.ts +278 -1701
- package/dist/index.js +2744 -1915
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2186 -1535
- 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 +231 -75
- package/dist/parsing.js.map +1 -1
- package/dist/parsing.mjs +3 -3
- package/dist/plan-utils.d.mts +20 -4
- package/dist/plan-utils.d.ts +20 -4
- package/dist/plan-utils.js +255 -97
- package/dist/plan-utils.js.map +1 -1
- package/dist/plan-utils.mjs +33 -19
- package/dist/{plan-utils-wI8MuDBC.d.mts → runSettingsText-BgOmLxdw.d.mts} +1941 -108
- package/dist/{plan-utils-BVJCzNvm.d.ts → runSettingsText-BgXvajj2.d.ts} +1941 -108
- package/dist/testing.d.mts +2 -3
- package/dist/testing.d.ts +2 -3
- package/dist/testing.js.map +1 -1
- package/dist/testing.mjs +1 -1
- package/package.json +1 -1
- package/dist/IFileSystem-ClBdp59f.d.ts +0 -78
- package/dist/IFileSystem-Db34maxC.d.mts +0 -78
- package/dist/chunk-4QLFM73A.mjs +0 -1407
- package/dist/chunk-4QLFM73A.mjs.map +0 -1
- package/dist/chunk-6LEA6WAM.mjs.map +0 -1
- package/dist/chunk-F3KIX7WR.mjs.map +0 -1
- package/dist/chunk-RT4NQ2BZ.mjs.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { ai as Task, a4 as RunnerId, aq as TaskSnapshot, b as AwaitingReason, av as UsageRecord, a5 as RunnerSessionCost, d as DiscoveredModel, af as SkillsService, aa as SkillInfo, Z as RepoGroupLayout, I as IWorktreeIsolation, w as IsolationRun, k as IsolationHandoff, J as PlanIsolation, x as IsolationTaskRecord, al as TaskIsolation, H as IsolationView, S as PrepareIntent, V as PreviousWork, s as IsolationOutcome, X as RepairEvidence, at as TreeSnapshot, aj as TaskDiffRead, ak as TaskDiffResult, r as IsolationMergeResult, ax as Verdict, ap as TaskSkillSnapshot, ao as TaskSkillNotice, ag as SubagentOutcome, L as LegacyPlanState, C as ConversationMessage, ac as SkillLoadNotice, P as PendingPlanTurn, a as AttemptUsage, a3 as RunSettings, O as PlanUsage, Y as ReplyRun, M as ModelUsage, aw as UsageTotals, f as ExecutionRecord, a0 as ResearchStep, K as PlanState, $ as ResearchProgress, _ as ResearchLogEntry, ab as SkillLoad, N as PlanStatus, ah as SupervisorLevel, a1 as ResearchStepOutcome, R as PlannerUsage, am as TaskIsolationState, as as TaskUsageSummary } from './Task-C0o4i3Nn.js';
|
|
2
|
+
import { N as IRunnerSession, K as IConfig, aL as TaskMessageFrom, ax as RunnerModelRef, ap as RunnerCommand, au as RunnerMcpServer, aE as StructuredTurnEnd, j as ApprovalDecision, aD as StructuredEvent, ah as QueuedTaskMessage, k as ApprovalKind, q as ApprovalSource, M as IRunner, ay as RunnerRegistry, J as IApproval, aU as ToolOutcome, d as AgentProcessDeps, a as AgentAdapter, a4 as OrdewellMcpServer, L as IFileSystem, aV as UsageLimitSignal, ac as PlannerToolHandler, p as ApprovalRequest, i as ApprovalAnswer, f as AiProvider, a3 as OrchestratorOption, a9 as PlannerBackendId } from './IFileSystem-C4_mpfze.js';
|
|
3
|
+
import { R as RunnerModeInfo } from './ModeResolver-DGK2rWHE.js';
|
|
4
4
|
|
|
5
5
|
type NotificationAction = {
|
|
6
6
|
label: string;
|
|
@@ -142,8 +142,6 @@ declare class PlanStore {
|
|
|
142
142
|
setForcedPastGate(id: string, dependencies: string[]): void;
|
|
143
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
144
|
private setStatus;
|
|
145
|
-
blockDependents(id: string): void;
|
|
146
|
-
unblockDependents(id: string): void;
|
|
147
145
|
setTaskVerdict(id: string, verdict: Task['verdict']): void;
|
|
148
146
|
setTaskOutputSummary(id: string, summary: Task['outputSummary']): void;
|
|
149
147
|
/** The owner is saved with the session it belongs to, and dropped with it. */
|
|
@@ -167,26 +165,90 @@ declare class PlanStore {
|
|
|
167
165
|
}
|
|
168
166
|
|
|
169
167
|
/**
|
|
170
|
-
*
|
|
171
|
-
*
|
|
172
|
-
*
|
|
173
|
-
*
|
|
168
|
+
* Who writes a task's assignment. It decides what a model the writer names
|
|
169
|
+
* meets where the task's runner does not offer it or the allowlist excludes it
|
|
170
|
+
* (ADR-0003), which runners it may put a task on, and whether a live runner's
|
|
171
|
+
* hold applies (ADR-0041):
|
|
172
|
+
*
|
|
173
|
+
* - `plan`: a whole plan the planner submitted, and every planner task as it
|
|
174
|
+
* lands. A stray model is replaced, and the planner is told what moved.
|
|
175
|
+
* - `edit`: the planner's edit. A stray model is refused, which the planner
|
|
176
|
+
* answers in the same reply.
|
|
177
|
+
* - `user`: a user's direct edit. A stray model is refused, with the reason.
|
|
178
|
+
* - `userAdd`: a task the user adds. A stray model is replaced, and the user
|
|
179
|
+
* is told what moved.
|
|
180
|
+
* - `runner`: a switch made with the runner's own `/model`, written as the
|
|
181
|
+
* user's edit. The runner already runs it, so neither the allowlist nor the
|
|
182
|
+
* live runner's hold applies.
|
|
183
|
+
*/
|
|
184
|
+
type AssignmentWriter = 'plan' | 'edit' | 'user' | 'userAdd' | 'runner';
|
|
185
|
+
/**
|
|
186
|
+
* What an assignment is resolved against: the discovered models, the manifest
|
|
187
|
+
* modes and the allowlist, as the session holds them when the write lands —
|
|
188
|
+
* the same catalog the planner's per-turn block shows, so nothing is refused
|
|
189
|
+
* that the planner was never told about.
|
|
174
190
|
*/
|
|
175
|
-
interface
|
|
191
|
+
interface AssignmentCatalog {
|
|
192
|
+
/** A runner absent, or listed with nothing, has models discovery could not find, not none. */
|
|
176
193
|
modelsByRunner: Partial<Record<RunnerId, DiscoveredModel[]>>;
|
|
177
|
-
|
|
178
|
-
|
|
194
|
+
/** Absent when no runner's modes are known: no mode is then derived. */
|
|
195
|
+
runnerModes?: Partial<Record<RunnerId, readonly RunnerModeInfo[]>>;
|
|
196
|
+
/** Raw (unfiltered) allowlist, keyed by runner; read through {@link effectiveAllowlist}. */
|
|
179
197
|
perRunnerAllowlist?: Partial<Record<RunnerId, string[]>>;
|
|
180
|
-
/** Full (true) or Guarded: the mode a task
|
|
198
|
+
/** Full (true) or Guarded: the mode a derived task lands on unless it is read-only (ADR-0001). Full when absent. */
|
|
181
199
|
autonomousDefault?: boolean;
|
|
182
200
|
/**
|
|
183
|
-
* Whether a task's runner is
|
|
184
|
-
*
|
|
185
|
-
*
|
|
186
|
-
* Absent means none is.
|
|
201
|
+
* Whether a task's runner is live: an attempt of it is claimed, running,
|
|
202
|
+
* quiet or waiting on someone. Its model, effort and runner are then that
|
|
203
|
+
* runner's (ADR-0041): the process was started on them, and only the
|
|
204
|
+
* runner's own command moves them. Absent means none is.
|
|
187
205
|
*/
|
|
188
206
|
liveRunner?: (taskId: string) => boolean;
|
|
189
207
|
}
|
|
208
|
+
/** What a runner offers a task, as discovery and its manifest have it. */
|
|
209
|
+
interface RunnerCatalog {
|
|
210
|
+
models: DiscoveredModel[];
|
|
211
|
+
modes: RunnerModeInfo[];
|
|
212
|
+
}
|
|
213
|
+
/** A change the resolver made to what was written, named so it is never silent (ADR-0001). */
|
|
214
|
+
interface AssignmentAdjustment {
|
|
215
|
+
taskId: string;
|
|
216
|
+
title: string;
|
|
217
|
+
field: 'assignedModel' | 'thinkingEffort';
|
|
218
|
+
from: string | null;
|
|
219
|
+
to: string | null;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/** Plan placement and dependency rules, shared with browser-bundled surfaces. */
|
|
223
|
+
interface TreeTask<T> {
|
|
224
|
+
id: string;
|
|
225
|
+
order: number;
|
|
226
|
+
title: string;
|
|
227
|
+
subtasks?: readonly T[];
|
|
228
|
+
}
|
|
229
|
+
interface DependencyTask<T> extends TreeTask<T> {
|
|
230
|
+
dependencies: readonly string[];
|
|
231
|
+
}
|
|
232
|
+
interface TaskPlacement<T> {
|
|
233
|
+
task: T;
|
|
234
|
+
parent?: T;
|
|
235
|
+
ancestors: readonly T[];
|
|
236
|
+
siblings: readonly T[];
|
|
237
|
+
label: string;
|
|
238
|
+
}
|
|
239
|
+
declare function walkPlan<T extends TreeTask<T>>(tasks: readonly T[]): Generator<TaskPlacement<T>>;
|
|
240
|
+
declare function taskPlacement<T extends TreeTask<T>>(tasks: readonly T[], id: string): TaskPlacement<T> | undefined;
|
|
241
|
+
/** Includes the root: deletion has to detach every edge into the removed tree. */
|
|
242
|
+
declare function subtreeIds<T extends TreeTask<T>>(tasks: readonly T[], id: string): Set<string>;
|
|
243
|
+
/** Pickers offer earlier siblings; API validity is independent of display order. */
|
|
244
|
+
declare function dependencyCandidates<T extends TreeTask<T>>(tasks: readonly T[], taskId?: string): T[];
|
|
245
|
+
/** The surviving tasks whose dependency lists a subtree removal will change. */
|
|
246
|
+
declare function dependentsOf<T extends DependencyTask<T>>(tasks: readonly T[], taskId: string): T[];
|
|
247
|
+
interface DependencyCheck {
|
|
248
|
+
ok: boolean;
|
|
249
|
+
error?: string;
|
|
250
|
+
}
|
|
251
|
+
declare function canSetDependencies<T extends DependencyTask<T>>(tasks: readonly T[], taskId: string, dependencies: readonly string[]): DependencyCheck;
|
|
190
252
|
|
|
191
253
|
/**
|
|
192
254
|
* Targeted task edits emitted by the planner conversation (the 'task_ops'
|
|
@@ -240,6 +302,8 @@ interface ApplyTaskOpsResult {
|
|
|
240
302
|
errors: string[];
|
|
241
303
|
/** Human-readable summary of what changed, for the chat transcript. */
|
|
242
304
|
summary: string[];
|
|
305
|
+
/** On ok, what the batch's tasks were adjusted to beyond what the planner wrote, as they stand at its end. */
|
|
306
|
+
adjustments?: AssignmentAdjustment[];
|
|
243
307
|
/** On ok, the id of the task each batch handle names. */
|
|
244
308
|
handles?: ReadonlyMap<string, string>;
|
|
245
309
|
}
|
|
@@ -269,22 +333,7 @@ interface TaskRef {
|
|
|
269
333
|
title: string;
|
|
270
334
|
dependencies: string[];
|
|
271
335
|
}
|
|
272
|
-
|
|
273
|
-
declare function dependentsOf<T extends Pick<TaskRef, 'id' | 'dependencies'>>(tasks: T[], taskId: string): T[];
|
|
274
|
-
/**
|
|
275
|
-
* The tasks that may become dependencies of `taskId`: those displayed before it.
|
|
276
|
-
*
|
|
277
|
-
* Offering only earlier tasks is what keeps a hand-edited graph valid without a
|
|
278
|
-
* cycle check — dependencies then only ever point backwards in display order,
|
|
279
|
-
* the same invariant `applyTaskOps` enforces. Omit `taskId` for a task that does
|
|
280
|
-
* not exist yet: it lands last, so every current task is a candidate.
|
|
281
|
-
*/
|
|
282
|
-
declare function dependencyCandidates<T extends Pick<TaskRef, 'id' | 'order'>>(tasks: readonly T[], taskId?: string): T[];
|
|
283
|
-
/** Pre-flight check for a hand-edited dependency list: every id exists and comes earlier. */
|
|
284
|
-
declare function canSetDependencies<T extends TaskRef>(tasks: readonly T[], taskId: string, dependencies: string[]): {
|
|
285
|
-
ok: boolean;
|
|
286
|
-
error?: string;
|
|
287
|
-
};
|
|
336
|
+
|
|
288
337
|
/** Pre-flight check for a split: the task exists and is not locked by execution. */
|
|
289
338
|
declare function canSplitTask(tasks: readonly Task[], taskId: string): {
|
|
290
339
|
ok: boolean;
|
|
@@ -296,7 +345,7 @@ declare function canSplitTask(tasks: readonly Task[], taskId: string): {
|
|
|
296
345
|
* completed tasks untouched), or nothing is returned and `errors` explains
|
|
297
346
|
* why. The caller commits `tasks` on ok.
|
|
298
347
|
*/
|
|
299
|
-
declare function applyTaskOps(currentTasks: readonly Task[], newOps: TaskOp[], runners: RunnerId[], catalog?:
|
|
348
|
+
declare function applyTaskOps(currentTasks: readonly Task[], newOps: TaskOp[], runners: RunnerId[], catalog?: AssignmentCatalog, held?: readonly TaskOp[]): ApplyTaskOpsResult;
|
|
300
349
|
|
|
301
350
|
/**
|
|
302
351
|
* Task skills end to end (ADR-0024): a skill reaches a runner only by being
|
|
@@ -367,6 +416,8 @@ interface IsolationRunListener {
|
|
|
367
416
|
blocked(repos: string[]): void;
|
|
368
417
|
/** An isolated run closed and handed its integration branches over. */
|
|
369
418
|
handoff(handoff: IsolationHandoff): void;
|
|
419
|
+
/** The run was cleared up and forgotten: nothing was handed over, or it was discarded. */
|
|
420
|
+
closed(): void;
|
|
370
421
|
/** Already said through the notifications; also for a surface that shows no toasts. */
|
|
371
422
|
notice(level: IsolationNoticeLevel, message: string): void;
|
|
372
423
|
}
|
|
@@ -733,6 +784,86 @@ declare class Landing {
|
|
|
733
784
|
private isGroup;
|
|
734
785
|
}
|
|
735
786
|
|
|
787
|
+
/**
|
|
788
|
+
* Why a task cannot start now: the one start rule (ADR-0034), which the
|
|
789
|
+
* scheduler, the explicit starts, the command seam's refusals, the task row's
|
|
790
|
+
* merge gate and the planner's notes all read. {@link startWaits} lists every
|
|
791
|
+
* one that holds, in this order, so the first is the reason a task waits.
|
|
792
|
+
*/
|
|
793
|
+
type StartWait =
|
|
794
|
+
/** Under way, waiting on the user, or ended: only a retry, a continue or a resume starts it again. */
|
|
795
|
+
{
|
|
796
|
+
kind: 'started';
|
|
797
|
+
}
|
|
798
|
+
/** Mark complete is landing its work. */
|
|
799
|
+
| {
|
|
800
|
+
kind: 'completing';
|
|
801
|
+
}
|
|
802
|
+
/** An AI task with no prompt yet: there is nothing to run. */
|
|
803
|
+
| {
|
|
804
|
+
kind: 'no-prompt';
|
|
805
|
+
}
|
|
806
|
+
/** Outside the user's approval record (ADR-0035). */
|
|
807
|
+
| {
|
|
808
|
+
kind: 'unapproved';
|
|
809
|
+
}
|
|
810
|
+
/** Pulled out of the scheduler: cancelled, marked not done, or a spawn that failed. */
|
|
811
|
+
| {
|
|
812
|
+
kind: 'on-hold';
|
|
813
|
+
}
|
|
814
|
+
/** The failed tasks it waits on through its dependencies (ADR-0034). */
|
|
815
|
+
| {
|
|
816
|
+
kind: 'blocked';
|
|
817
|
+
failed: string[];
|
|
818
|
+
}
|
|
819
|
+
/** What its dependencies still hold it back for (see {@link unmetDependencies}). */
|
|
820
|
+
| {
|
|
821
|
+
kind: 'dependencies';
|
|
822
|
+
waiting: string[];
|
|
823
|
+
}
|
|
824
|
+
/** A force start queued behind the dependencies still under way or yet to run, which starts once they finish. */
|
|
825
|
+
| {
|
|
826
|
+
kind: 'queued';
|
|
827
|
+
waiting: string[];
|
|
828
|
+
}
|
|
829
|
+
/** An ops task, and a Merge all is under way (ADR-0020). */
|
|
830
|
+
| {
|
|
831
|
+
kind: 'merging';
|
|
832
|
+
}
|
|
833
|
+
/** Its runner hit its usage limit: nothing more starts on that runner until the reset. */
|
|
834
|
+
| {
|
|
835
|
+
kind: 'usage-limit';
|
|
836
|
+
runner: string;
|
|
837
|
+
}
|
|
838
|
+
/** A planner turn holds new starts until its reply ends (ADR-0029). */
|
|
839
|
+
| {
|
|
840
|
+
kind: 'start-hold';
|
|
841
|
+
}
|
|
842
|
+
/** Its merge gate (ADR-0020): the ancestors whose landed work is not in the user's branch yet. */
|
|
843
|
+
| {
|
|
844
|
+
kind: 'merge-gate';
|
|
845
|
+
unmerged: string[];
|
|
846
|
+
}
|
|
847
|
+
/** A user task: the user does it by hand, and nothing starts it. */
|
|
848
|
+
| {
|
|
849
|
+
kind: 'by-hand';
|
|
850
|
+
};
|
|
851
|
+
/**
|
|
852
|
+
* A start the user forced, which goes past what the scheduler waits for: a
|
|
853
|
+
* hold, a failed dependency (ADR-0034), its runner's usage limit, and the
|
|
854
|
+
* merge gate they confirmed (ADR-0020). Made now, it is the user's own act
|
|
855
|
+
* and goes past the whole gate and a planner turn's start hold. Queued behind
|
|
856
|
+
* its dependencies, it starts later with nobody asking, so it waits for a
|
|
857
|
+
* start hold as the scheduler's starts do, and goes past only the gate it was
|
|
858
|
+
* confirmed past: `pastGate`.
|
|
859
|
+
*/
|
|
860
|
+
type ForcedStart = {
|
|
861
|
+
queued: false;
|
|
862
|
+
} | {
|
|
863
|
+
queued: true;
|
|
864
|
+
pastGate: readonly string[];
|
|
865
|
+
};
|
|
866
|
+
|
|
736
867
|
/**
|
|
737
868
|
* The variables one workspace's planner and agents run with, on top of the
|
|
738
869
|
* daemon's own environment (ADR-0016).
|
|
@@ -833,13 +964,21 @@ type TaskLogEvent =
|
|
|
833
964
|
} | {
|
|
834
965
|
type: 'turn_end';
|
|
835
966
|
reason: StructuredTurnEnd;
|
|
836
|
-
}
|
|
967
|
+
}
|
|
968
|
+
/**
|
|
969
|
+
* A piece of the run `runId` of the agent's reply (see `AgentEvent`). A log
|
|
970
|
+
* saved before runs carried an id has none, and opens each later run of a
|
|
971
|
+
* turn with the paragraph break that joined it.
|
|
972
|
+
*/
|
|
973
|
+
| {
|
|
837
974
|
type: 'text_delta';
|
|
975
|
+
runId?: string;
|
|
838
976
|
text: string;
|
|
839
977
|
}
|
|
840
|
-
/** A complete run of the agent's reply, authoritative over the deltas streamed
|
|
978
|
+
/** A complete run of the agent's reply, authoritative over the deltas streamed under its `runId`. */
|
|
841
979
|
| {
|
|
842
980
|
type: 'text';
|
|
981
|
+
runId?: string;
|
|
843
982
|
text: string;
|
|
844
983
|
} | {
|
|
845
984
|
type: 'thinking_delta';
|
|
@@ -973,9 +1112,10 @@ declare function toTaskLogEvent(event: StructuredEvent): TaskLogEvent | null;
|
|
|
973
1112
|
/** A log as a surface is sent it, live or read back: everything but the skills' bodies. */
|
|
974
1113
|
declare function taskLogForSurface(events: readonly TaskLogEvent[]): TaskLogEvent[];
|
|
975
1114
|
/**
|
|
976
|
-
* Merge
|
|
977
|
-
*
|
|
978
|
-
*
|
|
1115
|
+
* Merge consecutive deltas of one stream into one event each: of one run of
|
|
1116
|
+
* text, or of one voice's thinking. Deltas arrive a few characters at a time;
|
|
1117
|
+
* merged, a batch is one message on the wire and one line on disk, and the
|
|
1118
|
+
* view folds to the same blocks either way.
|
|
979
1119
|
*/
|
|
980
1120
|
declare function coalesceTaskLog(events: readonly TaskLogEvent[]): TaskLogEvent[];
|
|
981
1121
|
/**
|
|
@@ -1138,8 +1278,9 @@ type PlannerTurnOutcome = 'message' | 'plan' | 'task_ops' | 'stopped' | 'error';
|
|
|
1138
1278
|
* `planner_turn_ended` with the same `turnId`; every turn-scoped message in
|
|
1139
1279
|
* between carries it. The stream is provisional and the settled messages are
|
|
1140
1280
|
* authoritative:
|
|
1141
|
-
* - `planner_message`
|
|
1142
|
-
* a surface drops what it accumulated
|
|
1281
|
+
* - `planner_message` settles the streamed text of each run it carries in that
|
|
1282
|
+
* run's segment, where it streamed — a surface drops what it accumulated
|
|
1283
|
+
* there and shows the run's text instead.
|
|
1143
1284
|
* - A reply that is a JSON envelope (plan, taskOps, taskQuery) never arrives
|
|
1144
1285
|
* as `planner_text_delta`; it streams as `plan_token`, the "building plan"
|
|
1145
1286
|
* display.
|
|
@@ -1161,9 +1302,12 @@ type SessionMessage =
|
|
|
1161
1302
|
}
|
|
1162
1303
|
/**
|
|
1163
1304
|
* The settled reply of a planner turn, emitted by the session once the turn
|
|
1164
|
-
* is classified.
|
|
1165
|
-
* final segment. `turnId` is absent for replies sent outside a streamed turn.
|
|
1305
|
+
* is classified. `turnId` is absent for replies sent outside a streamed turn.
|
|
1166
1306
|
* `supervisor`: the supervisor said it in a wake-up turn (ADR-0029).
|
|
1307
|
+
* `runs`: what the reply shows, run by run, each authoritative over its
|
|
1308
|
+
* segment's `planner_text_delta`s; `content` stays the whole reply. Without
|
|
1309
|
+
* runs (a note the conversation wrote, an older daemon) the message is
|
|
1310
|
+
* authoritative over the turn's final segment.
|
|
1167
1311
|
*/
|
|
1168
1312
|
| {
|
|
1169
1313
|
type: 'planner_message';
|
|
@@ -1171,6 +1315,7 @@ type SessionMessage =
|
|
|
1171
1315
|
timestamp: string;
|
|
1172
1316
|
turnId?: string;
|
|
1173
1317
|
supervisor?: true;
|
|
1318
|
+
runs?: ReplyRun[];
|
|
1174
1319
|
}
|
|
1175
1320
|
/**
|
|
1176
1321
|
* A planner turn began. Emitted once per turn by whoever runs the turn,
|
|
@@ -1363,6 +1508,8 @@ type SessionMessage =
|
|
|
1363
1508
|
type: 'isolation_merge';
|
|
1364
1509
|
result: IsolationMergeResult;
|
|
1365
1510
|
trigger?: AutoMergeTrigger;
|
|
1511
|
+
} | {
|
|
1512
|
+
type: 'run_closed';
|
|
1366
1513
|
} | {
|
|
1367
1514
|
type: 'planner_liveness';
|
|
1368
1515
|
} | {
|
|
@@ -1586,6 +1733,8 @@ interface OrchestratorObserver {
|
|
|
1586
1733
|
}): void;
|
|
1587
1734
|
/** An isolated run settled; emitted before `onExecutionComplete`, which surfaces treat as terminal. */
|
|
1588
1735
|
onIsolationHandoff?(handoff: IsolationHandoff): void;
|
|
1736
|
+
/** The isolated run was cleared up and forgotten, by a Merge all that left nothing to hand over or by a discard. */
|
|
1737
|
+
onRunClosed?(): void;
|
|
1589
1738
|
/**
|
|
1590
1739
|
* What a run says about how it isolates — the fallback to the workspace root,
|
|
1591
1740
|
* shared paths, copies, a stash. Beside the notification channel, which a
|
|
@@ -1682,7 +1831,7 @@ interface TaskOrchestratorOptions {
|
|
|
1682
1831
|
isApproved?: (taskId: string) => boolean;
|
|
1683
1832
|
}
|
|
1684
1833
|
/**
|
|
1685
|
-
* The pure scheduler. Owns execution state (
|
|
1834
|
+
* The pure scheduler. Owns execution state (the {@link RunState},
|
|
1686
1835
|
* `reviewApproved`, the live task attempts, the start hold) and the verifier.
|
|
1687
1836
|
* All task-shaped state — the plan tree, the flat index, the completed set
|
|
1688
1837
|
* — lives in {@link PlanStore}, injected at construction. The orchestrator
|
|
@@ -1699,8 +1848,8 @@ declare class TaskOrchestrator {
|
|
|
1699
1848
|
private output;
|
|
1700
1849
|
private attempts;
|
|
1701
1850
|
private verifier;
|
|
1702
|
-
|
|
1703
|
-
private
|
|
1851
|
+
/** Changed only by start, stop, a plan load, a usage-limit pause, a move off the limit, and {@link settleRun}. */
|
|
1852
|
+
private runState;
|
|
1704
1853
|
/** Planner turns in flight that new starts wait for (ADR-0029); running attempts carry on. */
|
|
1705
1854
|
private startHolds;
|
|
1706
1855
|
/** Bumped by the user's Stop: a start admitted under an older generation never takes its claim. */
|
|
@@ -1712,14 +1861,6 @@ declare class TaskOrchestrator {
|
|
|
1712
1861
|
private mergesInFlight;
|
|
1713
1862
|
private retryCounts;
|
|
1714
1863
|
private spawnCounts;
|
|
1715
|
-
/** A full-plan run a usage-limit pause holds: no new start until the reset resumes it. */
|
|
1716
|
-
private heldOnLimit;
|
|
1717
|
-
/**
|
|
1718
|
-
* A full-plan run that ended with failed tasks and the tasks they block
|
|
1719
|
-
* (ADR-0034). A retry of a failed task continues it, so what it blocked
|
|
1720
|
-
* starts once it passes — without this the retried task ran alone.
|
|
1721
|
-
*/
|
|
1722
|
-
private endedOnFailure;
|
|
1723
1864
|
/** A Merge all is under way: no ops task starts until it ends (ADR-0020). */
|
|
1724
1865
|
private merging;
|
|
1725
1866
|
/** The last "waiting for Merge all" notice, so a run paused at a gate says it once, not on every tick. */
|
|
@@ -1884,7 +2025,9 @@ declare class TaskOrchestrator {
|
|
|
1884
2025
|
* reset. A Stop is owed to it, whether or not anything runs.
|
|
1885
2026
|
*/
|
|
1886
2027
|
get startsPending(): boolean;
|
|
1887
|
-
/**
|
|
2028
|
+
/** A start admitted now that nothing has to count as live work: one that claims before it awaits, or follows on from one that did. */
|
|
2029
|
+
private admittedNow;
|
|
2030
|
+
/** Run `work`, which awaits before it claims, under an admission it holds throughout and claims under. */
|
|
1888
2031
|
private whileAdmitted;
|
|
1889
2032
|
/**
|
|
1890
2033
|
* "Merge all"; a settled run that merged everything is cleared up and
|
|
@@ -1909,10 +2052,20 @@ declare class TaskOrchestrator {
|
|
|
1909
2052
|
runMergeView(): RunMergeView | null;
|
|
1910
2053
|
/**
|
|
1911
2054
|
* The ancestors an ops or user task waits on at its merge gate: those whose
|
|
1912
|
-
* landed work is not in the user's branch yet (ADR-0020). Empty
|
|
1913
|
-
* task
|
|
2055
|
+
* landed work is not in the user's branch yet (ADR-0020). Empty for every
|
|
2056
|
+
* other task. Judged whatever the task's status but `completed` — a retry of
|
|
2057
|
+
* a failed ops task acts on the checkout as a first start does — so a start
|
|
2058
|
+
* the user is about to confirm is told what it would act without.
|
|
1914
2059
|
*/
|
|
1915
2060
|
getMergeGate(taskId: string): string[];
|
|
2061
|
+
/**
|
|
2062
|
+
* Every reason a task cannot start now, from the one start rule
|
|
2063
|
+
* (ADR-0034); none when it can. `forced` judges a start the user forced.
|
|
2064
|
+
* Takes the task, not its id: the command seam judges one as a held edit
|
|
2065
|
+
* leaves it.
|
|
2066
|
+
*/
|
|
2067
|
+
startWaits(task: Readonly<Task>, forced?: ForcedStart): StartWait[];
|
|
2068
|
+
private startRule;
|
|
1916
2069
|
/** The failed tasks a task not started yet waits on, through its dependencies (ADR-0034); empty when it is not blocked. */
|
|
1917
2070
|
getBlockedBy(taskId: string): string[];
|
|
1918
2071
|
loadPlan(tasks: readonly Task[], planRunners?: RunnerId[]): void;
|
|
@@ -1931,8 +2084,16 @@ declare class TaskOrchestrator {
|
|
|
1931
2084
|
* not a retry, and the tasks it blocks wait for that retry (ADR-0034).
|
|
1932
2085
|
*/
|
|
1933
2086
|
reconcilePlan(newTasks: Task[], planRunners?: RunnerId[]): void;
|
|
2087
|
+
/** Start the full-plan run, or continue one that is held or ended on a failure. */
|
|
1934
2088
|
start(origin?: RunOrigin): Promise<void>;
|
|
1935
|
-
/**
|
|
2089
|
+
/**
|
|
2090
|
+
* A usage-limit pause with a reset to wait for holds the run (ADR-0018,
|
|
2091
|
+
* U1): spawning more would spend the same exhausted limit, and the run
|
|
2092
|
+
* stays open — neither handed over nor merged at its end — however it was
|
|
2093
|
+
* started, so a task started alone keeps its run for the reset too. A pause
|
|
2094
|
+
* no reset will resume holds nothing: it keeps only its own runner's tasks
|
|
2095
|
+
* from starting (`readiness.usage-limit`).
|
|
2096
|
+
*/
|
|
1936
2097
|
private holdOnLimit;
|
|
1937
2098
|
/**
|
|
1938
2099
|
* Stop the run. The user's Stop also withdraws every start admitted before
|
|
@@ -1992,12 +2153,18 @@ declare class TaskOrchestrator {
|
|
|
1992
2153
|
* integration branch.
|
|
1993
2154
|
*/
|
|
1994
2155
|
private land;
|
|
1995
|
-
/**
|
|
2156
|
+
/**
|
|
2157
|
+
* Settle a task by how its landing went; `onLanded` is what the path that
|
|
2158
|
+
* landed it adds to completing it. `admission` is the start the landed work
|
|
2159
|
+
* came from, which a repair follows on from.
|
|
2160
|
+
*/
|
|
1996
2161
|
private applyLanding;
|
|
1997
2162
|
/**
|
|
1998
2163
|
* Work that did not land waits on the user — it never fails the task or
|
|
1999
2164
|
* blocks its dependents — unless its conflict is owed a repair, which takes
|
|
2000
2165
|
* the slot the ending attempt freed and never one more than the run allows.
|
|
2166
|
+
* The repair follows on from the start whose work it repairs: a Stop since
|
|
2167
|
+
* that was admitted withdraws it.
|
|
2001
2168
|
*/
|
|
2002
2169
|
private settleUnlanded;
|
|
2003
2170
|
private landResolved;
|
|
@@ -2020,10 +2187,10 @@ declare class TaskOrchestrator {
|
|
|
2020
2187
|
*/
|
|
2021
2188
|
private openFor;
|
|
2022
2189
|
/**
|
|
2023
|
-
* {@link openFor} for an explicit start
|
|
2024
|
-
*
|
|
2025
|
-
*
|
|
2026
|
-
*
|
|
2190
|
+
* {@link openFor} for an explicit start under `admission`. A Stop while the
|
|
2191
|
+
* run opened interrupts that opening too, which then opens nothing; the
|
|
2192
|
+
* start says it was stopped rather than going quiet as if a dirty tree were
|
|
2193
|
+
* waiting on the user.
|
|
2027
2194
|
*/
|
|
2028
2195
|
private openForAdmitted;
|
|
2029
2196
|
/** The kind a task's next attempt starts as, short of a repair or a continue: what is weighed before one exists. */
|
|
@@ -2078,10 +2245,19 @@ declare class TaskOrchestrator {
|
|
|
2078
2245
|
/**
|
|
2079
2246
|
* Keep a task paused on its runner's usage limit from resuming on its own:
|
|
2080
2247
|
* it waits for a manual retry, as one whose reset time could not be read
|
|
2081
|
-
* does. What a surface's cancel calls
|
|
2082
|
-
*
|
|
2248
|
+
* does. What a surface's cancel calls. A run the pause held goes on once
|
|
2249
|
+
* nothing else waits on a reset. False when no resume was scheduled for it.
|
|
2250
|
+
*/
|
|
2251
|
+
cancelScheduledResume(taskId: string): Promise<boolean>;
|
|
2252
|
+
/** {@link cancelScheduledResume} without its tick, for a caller that ticks or starts once it is done. */
|
|
2253
|
+
private dropResume;
|
|
2254
|
+
/**
|
|
2255
|
+
* The hold a usage-limit pause put on the run is released once nothing
|
|
2256
|
+
* waits on a reset (ADR-0018, U1) — however the last wait went: the reset,
|
|
2257
|
+
* a cancel, Mark complete or the task leaving the plan. The run is still
|
|
2258
|
+
* open, so the next tick schedules it.
|
|
2083
2259
|
*/
|
|
2084
|
-
|
|
2260
|
+
private releaseHold;
|
|
2085
2261
|
/**
|
|
2086
2262
|
* The runner whose attempt left the task's work, when the task has been
|
|
2087
2263
|
* moved off it since: a session cannot cross runners, so whatever starts
|
|
@@ -2093,6 +2269,11 @@ declare class TaskOrchestrator {
|
|
|
2093
2269
|
* Run before writing assignments so cancellation sees the old runner's
|
|
2094
2270
|
* shared wait. A session cannot cross runners, so the next attempt is a
|
|
2095
2271
|
* carry-over of the paused attempt's files (ADR-0018, U1).
|
|
2272
|
+
*
|
|
2273
|
+
* The move routes the task around the limit, so a run the pause held goes
|
|
2274
|
+
* on, as a retry would continue it. That run is still open — a hold never
|
|
2275
|
+
* closes one — and every edit that calls this ticks once it is written,
|
|
2276
|
+
* which starts the task.
|
|
2096
2277
|
*/
|
|
2097
2278
|
releaseMovedResume(taskId: string, changes: Partial<Task>): Partial<Task>;
|
|
2098
2279
|
/**
|
|
@@ -2137,15 +2318,18 @@ declare class TaskOrchestrator {
|
|
|
2137
2318
|
/**
|
|
2138
2319
|
* Retry a task paused on a usage limit, continuing its saved session where
|
|
2139
2320
|
* it left one (ADR-0018, K1), then release the hold the pause put on the
|
|
2140
|
-
* run once nothing else waits on a reset.
|
|
2321
|
+
* run once nothing else waits on a reset. `now` is the Resume now that
|
|
2322
|
+
* asked for it, or for another task on its runner, which sweeps it along.
|
|
2141
2323
|
*/
|
|
2142
2324
|
private resumeTask;
|
|
2143
2325
|
/**
|
|
2144
|
-
* A resume
|
|
2145
|
-
*
|
|
2146
|
-
* time dropped, and said
|
|
2326
|
+
* A resume nobody asked for cannot be refused to anyone. At the limit's
|
|
2327
|
+
* reset, one that may not claim waits for the user instead — its resume
|
|
2328
|
+
* time dropped, and said. One swept along by a Resume now keeps its resume
|
|
2329
|
+
* time, which its caller arms again: the reset has not come. A Stop says
|
|
2330
|
+
* so itself.
|
|
2147
2331
|
*/
|
|
2148
|
-
private
|
|
2332
|
+
private mayResume;
|
|
2149
2333
|
/**
|
|
2150
2334
|
* Manually start a single AI task right now (the "force start" affordance on
|
|
2151
2335
|
* a task card), past a failed dependency (ADR-0034) or a merge gate
|
|
@@ -2161,24 +2345,38 @@ declare class TaskOrchestrator {
|
|
|
2161
2345
|
* task, or already running.
|
|
2162
2346
|
*/
|
|
2163
2347
|
forceStartTask(taskId: string, origin?: RunOrigin): Promise<string | undefined>;
|
|
2348
|
+
/**
|
|
2349
|
+
* A forced start, from its run opening to its claim. It is judged again once
|
|
2350
|
+
* the run has opened, against the task as it stands then, which is what
|
|
2351
|
+
* the claim reads and runs. A queued one that something holds back again
|
|
2352
|
+
* meanwhile goes back in the queue; one made now that an edit gave
|
|
2353
|
+
* dependencies still under way waits for them as if forced after the edit,
|
|
2354
|
+
* and says so.
|
|
2355
|
+
*/
|
|
2356
|
+
private startForced;
|
|
2164
2357
|
/**
|
|
2165
2358
|
* A force start of a task whose dependencies are still under way or yet to
|
|
2166
2359
|
* run: started now, it would build on work that is not there, so it waits
|
|
2167
2360
|
* for them in the run, off any hold, and says so. Dependencies are never
|
|
2168
2361
|
* bypassed silently. A blocked task is forced past its failure as asked.
|
|
2362
|
+
* What it goes past at the merge gate is the gate the user confirmed now.
|
|
2169
2363
|
*/
|
|
2170
|
-
private
|
|
2364
|
+
private queueBehindDependencies;
|
|
2171
2365
|
/**
|
|
2172
|
-
*
|
|
2173
|
-
*
|
|
2174
|
-
*
|
|
2366
|
+
* Starts the queued force starts nothing holds back any more. One the
|
|
2367
|
+
* scheduler now drives the run for, that nothing blocks and that was not
|
|
2368
|
+
* confirmed past a gate, is left to it. One at a gate the user did not
|
|
2369
|
+
* confirm — work that landed after they forced the start — waits for Merge
|
|
2370
|
+
* all, or for another force start, and says so once.
|
|
2175
2371
|
*/
|
|
2176
|
-
private
|
|
2372
|
+
private startQueued;
|
|
2177
2373
|
/**
|
|
2178
|
-
*
|
|
2179
|
-
*
|
|
2374
|
+
* A queued start the start rule holds back, at a gate it was not confirmed
|
|
2375
|
+
* past or otherwise. Taken out of the queue by now, so its dependencies
|
|
2376
|
+
* still under way read as `dependencies` rather than `queued`.
|
|
2180
2377
|
*/
|
|
2181
|
-
private
|
|
2378
|
+
private holdsQueued;
|
|
2379
|
+
private sayQueuedAtGate;
|
|
2182
2380
|
private refuseOpsDuringMerge;
|
|
2183
2381
|
/**
|
|
2184
2382
|
* A force start passes a merge gate (ADR-0020), and the surface confirmed it
|
|
@@ -2186,24 +2384,27 @@ declare class TaskOrchestrator {
|
|
|
2186
2384
|
* on is not in the user's branch.
|
|
2187
2385
|
*/
|
|
2188
2386
|
private notePastGate;
|
|
2189
|
-
/**
|
|
2190
|
-
* Run exactly one task outside full-plan scheduling. The active/starting
|
|
2191
|
-
* session still contributes to {@link hasLiveWork} so every surface exposes
|
|
2192
|
-
* Stop and disables Execute Plan, but onVerdict cannot auto-schedule other
|
|
2193
|
-
* tasks because the plan scheduler's `running` flag remains false.
|
|
2194
|
-
*/
|
|
2195
|
-
runTask(taskId: string): Promise<void>;
|
|
2196
2387
|
getCompletedCount(): number;
|
|
2197
2388
|
getTotalCount(): number;
|
|
2198
2389
|
isAllComplete(): boolean;
|
|
2199
2390
|
isAnyFailed(): boolean;
|
|
2200
2391
|
approveReview(origin?: RunOrigin): Promise<void>;
|
|
2201
2392
|
tick(): Promise<void>;
|
|
2202
|
-
/**
|
|
2393
|
+
/**
|
|
2394
|
+
* The one step that decides, with nothing at work and nothing the scheduler
|
|
2395
|
+
* can start, whether the run goes on, is held, waits on the user or is over
|
|
2396
|
+
* (ADR-0034). Every surface takes `onExecutionComplete` as the run's end, so
|
|
2397
|
+
* only an end emits it. A run the scheduler is not driving — tasks started
|
|
2398
|
+
* on their own — ends on a verdict; a cancel, Mark complete or failed spawn
|
|
2399
|
+
* just closes it, so the next run decides its own mode rather than inherit
|
|
2400
|
+
* this one's.
|
|
2401
|
+
*/
|
|
2402
|
+
private settleRun;
|
|
2203
2403
|
/** Skipped work is not completed work, so a run with any says so rather than that everything completed. */
|
|
2204
2404
|
private sayFinished;
|
|
2405
|
+
/** How a run that ended on failures ends: what it finished, what failed, and what that blocks. */
|
|
2205
2406
|
private sayEndedOnFailure;
|
|
2206
|
-
/** Whether the scheduler may start `id` now:
|
|
2407
|
+
/** Whether the scheduler may start `id` now: nothing in the start rule holds it back, and a slot is free. */
|
|
2207
2408
|
private stillReady;
|
|
2208
2409
|
/**
|
|
2209
2410
|
* A run paused at merge gates says so, as plainly as a task that waits on
|
|
@@ -2212,9 +2413,10 @@ declare class TaskOrchestrator {
|
|
|
2212
2413
|
private sayGate;
|
|
2213
2414
|
/**
|
|
2214
2415
|
* Why a start may not take its claim now, or null when it may: a Stop came
|
|
2215
|
-
* since it was admitted (ADR-0029), the task
|
|
2216
|
-
*
|
|
2217
|
-
*
|
|
2416
|
+
* since it was admitted (ADR-0029), the task left the plan or is being
|
|
2417
|
+
* marked complete, it is outside the user's approval record (ADR-0035), or
|
|
2418
|
+
* every slot is taken. Read in the same synchronous step as the claim it
|
|
2419
|
+
* guards, so nothing can change in between.
|
|
2218
2420
|
*/
|
|
2219
2421
|
private claimRefusal;
|
|
2220
2422
|
/** An explicit start's refusal, before it changes anything: its caller's answer, as a {@link TaskControlError}. */
|
|
@@ -2226,7 +2428,10 @@ declare class TaskOrchestrator {
|
|
|
2226
2428
|
* start, a retry's, a continue, a resume, a repair. Checked and taken in one
|
|
2227
2429
|
* synchronous step ({@link claimRefusal}), then marked claimed before
|
|
2228
2430
|
* anything is awaited, so no other start can take the same task or slot.
|
|
2229
|
-
*
|
|
2431
|
+
* The task is read here, as it stands: a start that awaited first — its run
|
|
2432
|
+
* opening, the user's answer — runs what the plan holds now, not what it
|
|
2433
|
+
* held when it was asked for. A task already claimed is not claimed again:
|
|
2434
|
+
* there is nothing to start.
|
|
2230
2435
|
*/
|
|
2231
2436
|
private claim;
|
|
2232
2437
|
private runClaimed;
|
|
@@ -2254,6 +2459,8 @@ declare class TaskOrchestrator {
|
|
|
2254
2459
|
* A spawn whose attempt ended while it was in flight: kill what it produced
|
|
2255
2460
|
* and take back the claim it made. Only the claim — whatever ended the
|
|
2256
2461
|
* attempt may have decided the task since (mark complete, cancel, retry).
|
|
2462
|
+
* A task Mark complete is landing stays in progress until that lands:
|
|
2463
|
+
* back to not started, the scheduler would start it again under the landing.
|
|
2257
2464
|
*/
|
|
2258
2465
|
private abandonSpawn;
|
|
2259
2466
|
private tell;
|
|
@@ -2282,6 +2489,599 @@ declare class TaskOrchestrator {
|
|
|
2282
2489
|
private endAllAttempts;
|
|
2283
2490
|
}
|
|
2284
2491
|
|
|
2492
|
+
/**
|
|
2493
|
+
* The planner's read channel: a `{"taskQuery":{...}}` reply asking Ordewell to
|
|
2494
|
+
* show it what the per-turn plan block deliberately leaves out.
|
|
2495
|
+
*
|
|
2496
|
+
* The plan block carries only short fields, so a planner asked to "add a line
|
|
2497
|
+
* to task 3's prompt" would be rewriting text it has never read. Inlining every
|
|
2498
|
+
* body on every turn was rejected — a twenty-task plan re-sends thousands of
|
|
2499
|
+
* tokens per message — so the planner asks instead, and pays for the detail
|
|
2500
|
+
* only on the turns it uses it.
|
|
2501
|
+
*
|
|
2502
|
+
* This module owns the wire shape and the rendering of the answer; the loop,
|
|
2503
|
+
* the budgets and the injection live in the Session, and the classification
|
|
2504
|
+
* lives in PlanRepair alongside the plan and task-ops envelopes.
|
|
2505
|
+
*/
|
|
2506
|
+
/** The long fields a query may ask for. Everything else is already in the plan block. */
|
|
2507
|
+
declare const TASK_QUERY_FIELDS: readonly ["description", "prompt", "userSteps", "verdict", "outputSummary", "userStoriesCovered", "skills", "output"];
|
|
2508
|
+
type TaskQueryField = typeof TASK_QUERY_FIELDS[number];
|
|
2509
|
+
/** Lines a `output` read returns when the query sets no count. */
|
|
2510
|
+
declare const OUTPUT_LINES_DEFAULT = 80;
|
|
2511
|
+
/** Hard cap on `outputLines` — the tail read is bounded however the planner asks. */
|
|
2512
|
+
declare const OUTPUT_LINES_MAX = 400;
|
|
2513
|
+
/**
|
|
2514
|
+
* Character budget the rendered answer converges to. A running output tail is
|
|
2515
|
+
* the only part with no natural bound — a runner may print for minutes — so
|
|
2516
|
+
* the tail absorbs the clamp; everything else (task bodies, the catalog cap)
|
|
2517
|
+
* is bounded upstream.
|
|
2518
|
+
*/
|
|
2519
|
+
declare const TASK_QUERY_ANSWER_MAX_CHARS = 20000;
|
|
2520
|
+
interface TaskQuery {
|
|
2521
|
+
/** Task references to read in full, as {@link resolveTaskRef} reads them. */
|
|
2522
|
+
tasks: string[];
|
|
2523
|
+
/** Narrows what comes back. Omitted means every field in {@link TASK_QUERY_FIELDS}. */
|
|
2524
|
+
fields?: TaskQueryField[];
|
|
2525
|
+
/** Also return the full runner/model catalog with labels, variants and mode descriptions. */
|
|
2526
|
+
catalog: boolean;
|
|
2527
|
+
/** Line count for `output` reads. Clamped at render to [1, {@link OUTPUT_LINES_MAX}]. */
|
|
2528
|
+
outputLines?: number;
|
|
2529
|
+
/** A previous answer's next offset: the output tail then covers only what came after it. */
|
|
2530
|
+
outputSince?: number;
|
|
2531
|
+
}
|
|
2532
|
+
declare function textHasTaskQuery(text: string): boolean;
|
|
2533
|
+
/** Parse a `{"taskQuery":...}` reply. Throws PlanParseError when the JSON is unusable. */
|
|
2534
|
+
declare function parseTaskQueryJson(text: string): TaskQuery;
|
|
2535
|
+
/** Stable identity of a query, so a turn can recognise the planner asking the same thing twice. */
|
|
2536
|
+
declare function taskQuerySignature(query: TaskQuery): string;
|
|
2537
|
+
/** What a full-catalog read is answered from. Models arrive already allowlist-filtered. */
|
|
2538
|
+
interface TaskQueryCatalog {
|
|
2539
|
+
runners: RunnerId[];
|
|
2540
|
+
models: Partial<Record<RunnerId, DiscoveredModel[]>>;
|
|
2541
|
+
modes: Record<RunnerId, RunnerModeInfo[]>;
|
|
2542
|
+
autonomousDefault: boolean;
|
|
2543
|
+
}
|
|
2544
|
+
/**
|
|
2545
|
+
* Appended when the planner has spent its read budget, or asked the identical
|
|
2546
|
+
* question twice. The detail still comes back — refusing it would leave the
|
|
2547
|
+
* planner editing text it has not read, which is the whole failure this
|
|
2548
|
+
* channel exists to prevent — but the turn is told to land.
|
|
2549
|
+
*/
|
|
2550
|
+
declare const TASK_QUERY_ANSWER_OR_OPS: string;
|
|
2551
|
+
/**
|
|
2552
|
+
* The protocol as the planner is taught it, owned here beside the parser so the
|
|
2553
|
+
* two cannot drift. Both planner backends get these lines verbatim (ADR-0009):
|
|
2554
|
+
* a harness planner has no tool loop to reach, so the envelope is the channel.
|
|
2555
|
+
*/
|
|
2556
|
+
declare const TASK_QUERY_PROTOCOL: string[];
|
|
2557
|
+
/** The one-line reminder the per-turn plan block carries, so the protocol has a single owner. */
|
|
2558
|
+
declare const TASK_QUERY_REMINDER = "- To READ a task in full (prompt, user steps, verdict, output, user stories) or the whole model/mode catalog before editing, reply with ONLY {\"taskQuery\":{\"tasks\":[\"<id or #order>\"],\"catalog\":true}} \u2014 it changes nothing, and you then reply again with your ops.";
|
|
2559
|
+
/**
|
|
2560
|
+
* Where an `output` read is answered from: the orchestrator's live capture of
|
|
2561
|
+
* the task's latest attempt. Injected so this module stays planner-side and
|
|
2562
|
+
* never imports the orchestrator.
|
|
2563
|
+
*/
|
|
2564
|
+
type LiveOutputLookup = (taskId: string, opts: LiveTailOptions) => LiveTail | null;
|
|
2565
|
+
/**
|
|
2566
|
+
* Render the injected answer to one read. Never persisted — it is context for
|
|
2567
|
+
* the planner's next reply and nothing else — so it is built fresh from live
|
|
2568
|
+
* state on every query rather than cached. `liveOutput` backs the `output`
|
|
2569
|
+
* field; omitting it reads as "nothing captured".
|
|
2570
|
+
*/
|
|
2571
|
+
declare function renderTaskQueryAnswer(query: TaskQuery, tasks: readonly Task[], catalog: TaskQueryCatalog, liveOutput?: LiveOutputLookup): string;
|
|
2572
|
+
|
|
2573
|
+
/**
|
|
2574
|
+
* The SSRF guard for anything that leaves the machine on the planner's behalf.
|
|
2575
|
+
* An unguarded `http://169.254.169.254/` would hand a prompt-injected planner
|
|
2576
|
+
* the cloud metadata credentials, so every outbound host is vetted here: IP
|
|
2577
|
+
* literals by range, names by every address DNS answers with.
|
|
2578
|
+
*/
|
|
2579
|
+
type ResolveAll = (host: string) => Promise<string[]>;
|
|
2580
|
+
|
|
2581
|
+
/**
|
|
2582
|
+
* The one decision every planner web read goes through, whichever runner
|
|
2583
|
+
* plans and whichever path carries the bytes (ADR-0027). A fetch names the
|
|
2584
|
+
* URL it reads; a search names its query and the provider that receives it.
|
|
2585
|
+
*/
|
|
2586
|
+
type WebRead = {
|
|
2587
|
+
kind: 'fetch';
|
|
2588
|
+
url: string;
|
|
2589
|
+
}
|
|
2590
|
+
/** `provider` keys the grant; `providerLabel`, when given, names it to the person asked. */
|
|
2591
|
+
| {
|
|
2592
|
+
kind: 'search';
|
|
2593
|
+
query: string;
|
|
2594
|
+
provider: string;
|
|
2595
|
+
providerLabel?: string;
|
|
2596
|
+
note?: string;
|
|
2597
|
+
};
|
|
2598
|
+
/** A refusal's `reason` is written for the planner: it says what to do next. */
|
|
2599
|
+
type WebReadDecision = {
|
|
2600
|
+
allowed: true;
|
|
2601
|
+
} | {
|
|
2602
|
+
allowed: false;
|
|
2603
|
+
reason: string;
|
|
2604
|
+
};
|
|
2605
|
+
interface DecideOptions {
|
|
2606
|
+
/**
|
|
2607
|
+
* Research subagents run in the background where nothing can prompt
|
|
2608
|
+
* (ADR-0008, T6). When false, a read that would ask is refused with the gap
|
|
2609
|
+
* to report, rather than raising a prompt nobody can answer.
|
|
2610
|
+
*/
|
|
2611
|
+
prompting?: boolean;
|
|
2612
|
+
}
|
|
2613
|
+
interface WebReadPolicyOptions {
|
|
2614
|
+
approval: IApproval;
|
|
2615
|
+
/** The secrets Ordewell holds — provider keys, MCP tokens — read per request. */
|
|
2616
|
+
secrets?: () => Iterable<string>;
|
|
2617
|
+
/** Defaults to `process.env`; its credential-named values are secrets too. */
|
|
2618
|
+
env?: () => Record<string, string | undefined>;
|
|
2619
|
+
/** How many web reads one planner turn may make; read per request, so a settings change applies at once. */
|
|
2620
|
+
budget?: () => number;
|
|
2621
|
+
/** The planner turn in progress; a new one starts a new budget. */
|
|
2622
|
+
turn?: () => string | undefined;
|
|
2623
|
+
/** Override DNS resolution (every answer for a name) in tests. */
|
|
2624
|
+
resolveHost?: ResolveAll;
|
|
2625
|
+
}
|
|
2626
|
+
declare const DEFAULT_WEB_READ_BUDGET = 40;
|
|
2627
|
+
declare class WebReadPolicy {
|
|
2628
|
+
private readonly approval;
|
|
2629
|
+
private readonly resolveHost;
|
|
2630
|
+
private readonly secrets;
|
|
2631
|
+
private readonly env;
|
|
2632
|
+
private readonly budget;
|
|
2633
|
+
private readonly turn;
|
|
2634
|
+
private spent;
|
|
2635
|
+
constructor(opts: WebReadPolicyOptions);
|
|
2636
|
+
/**
|
|
2637
|
+
* Every check that needs no network runs first, then the budget and the
|
|
2638
|
+
* approval, and only then is a fetch's host resolved: a lookup is itself a
|
|
2639
|
+
* request, so `<data>.attacker.example` would leave through DNS even for a
|
|
2640
|
+
* read that is then excluded, over budget or declined.
|
|
2641
|
+
*/
|
|
2642
|
+
decide(read: WebRead, opts?: DecideOptions): Promise<WebReadDecision>;
|
|
2643
|
+
private subagentRefusal;
|
|
2644
|
+
/**
|
|
2645
|
+
* Whether a search through `provider` would run without asking anyone, for
|
|
2646
|
+
* a runner whose own search raises no request and is set on or off when
|
|
2647
|
+
* its session starts (ADR-0027). A zero budget disables it; no query is
|
|
2648
|
+
* known yet, so secrets and individual reads cannot be checked or counted.
|
|
2649
|
+
*/
|
|
2650
|
+
searchesUnasked(provider: string): boolean;
|
|
2651
|
+
/** The last check, the one that leaves the machine: every DNS answer for the host must be public. */
|
|
2652
|
+
private resolved;
|
|
2653
|
+
private refuse;
|
|
2654
|
+
private vetFetch;
|
|
2655
|
+
private vetSearch;
|
|
2656
|
+
private spend;
|
|
2657
|
+
private refund;
|
|
2658
|
+
private carriesSecret;
|
|
2659
|
+
}
|
|
2660
|
+
|
|
2661
|
+
interface IWebFetcher {
|
|
2662
|
+
/**
|
|
2663
|
+
* The web-read policy's decision (ADR-0027), for a read this fetcher will
|
|
2664
|
+
* carry or one a runner carries natively, such as Claude's `WebFetch`.
|
|
2665
|
+
* `fetch` and `search` decide for themselves; never call both.
|
|
2666
|
+
*/
|
|
2667
|
+
decide(read: WebRead): Promise<WebReadDecision>;
|
|
2668
|
+
/** Whether a runner's own search through `provider` would run unasked; see `WebReadPolicy.searchesUnasked`. */
|
|
2669
|
+
searchesUnasked?(provider: string): boolean;
|
|
2670
|
+
fetch(url: string): Promise<ToolOutcome>;
|
|
2671
|
+
/**
|
|
2672
|
+
* Optional web search. A fetcher without it makes the `web_search` tool
|
|
2673
|
+
* report itself unavailable rather than failing the turn — the same
|
|
2674
|
+
* degradation `fetch` already uses when no fetcher is wired at all.
|
|
2675
|
+
*/
|
|
2676
|
+
search?(query: string): Promise<ToolOutcome>;
|
|
2677
|
+
/**
|
|
2678
|
+
* A variant of this fetcher for a research subagent: same policy, cache,
|
|
2679
|
+
* transport and budget, but a read that would ask a person is refused
|
|
2680
|
+
* instead (ADR-0027, ADR-0008 T6). Absent means subagents get no web tools
|
|
2681
|
+
* and the `fetch`/`web_search` tools report themselves unavailable.
|
|
2682
|
+
*/
|
|
2683
|
+
forSubagent?(): IWebFetcher;
|
|
2684
|
+
}
|
|
2685
|
+
|
|
2686
|
+
/**
|
|
2687
|
+
* A person's approvals of project MCP servers, kept across sessions
|
|
2688
|
+
* (ADR-0028). Keyed by repository root and scope, which names the server
|
|
2689
|
+
* and hashes its definition: a changed definition is a new question.
|
|
2690
|
+
*/
|
|
2691
|
+
interface ProjectServerApprovals {
|
|
2692
|
+
approved(repoRoot: string, scope: string): boolean;
|
|
2693
|
+
remember(repoRoot: string, scope: string): void;
|
|
2694
|
+
}
|
|
2695
|
+
|
|
2696
|
+
interface CliAgentAiServiceDeps extends Partial<AgentProcessDeps> {
|
|
2697
|
+
/** Overrides adapter construction. Tests supply a fake agent; production picks by runner id. */
|
|
2698
|
+
createAdapter?: (runner: string, deps: AgentProcessDeps) => AgentAdapter | null;
|
|
2699
|
+
/** Workspace root the agent explores. Defaults to the host process's cwd. */
|
|
2700
|
+
workspaceRoot?: () => string;
|
|
2701
|
+
/** Where a conversation's planner tools are served (ADR-0022). Absent: no conversation can start. */
|
|
2702
|
+
mcpServer?: OrdewellMcpServer;
|
|
2703
|
+
/** A person's approvals of project MCP servers, kept across sessions (ADR-0028). Defaults to Ordewell's data directory. */
|
|
2704
|
+
projectServerApprovals?: ProjectServerApprovals;
|
|
2705
|
+
}
|
|
2706
|
+
/**
|
|
2707
|
+
* A coding agent driven as Ordewell's planner (ADR-0009).
|
|
2708
|
+
*
|
|
2709
|
+
* The second transport behind `IAiService`, sitting beside `OpenAiService` and
|
|
2710
|
+
* `GeminiService`. It deliberately does **not** extend {@link BaseAiService}:
|
|
2711
|
+
* that class's body is Ordewell executing research tools on a model's behalf,
|
|
2712
|
+
* which is precisely the part a coding agent replaces. What it reuses instead
|
|
2713
|
+
* is everything above the transport — `settleReply` (the empty-reply nudge
|
|
2714
|
+
* and the turn's settling), the plan validator, the `ResearchProgress` events the
|
|
2715
|
+
* four surfaces already render. That is why this backend reaches VS Code, the
|
|
2716
|
+
* web UI, the CLI and the TUI without any of them learning a coding agent is
|
|
2717
|
+
* on the other end.
|
|
2718
|
+
*/
|
|
2719
|
+
declare class CliAgentAiService implements IAiService {
|
|
2720
|
+
private config;
|
|
2721
|
+
private readonly runner;
|
|
2722
|
+
private readonly processDeps;
|
|
2723
|
+
private readonly makeAdapter;
|
|
2724
|
+
private readonly workspaceRoot;
|
|
2725
|
+
private environmentSecrets;
|
|
2726
|
+
/** The conversation's session, from its start until the next session boundary. */
|
|
2727
|
+
private session;
|
|
2728
|
+
private readonly mcpServer;
|
|
2729
|
+
private readonly projectServerApprovals;
|
|
2730
|
+
private activeAbort;
|
|
2731
|
+
constructor(config: IConfig, deps?: CliAgentAiServiceDeps);
|
|
2732
|
+
hasActiveConversation(): boolean;
|
|
2733
|
+
/**
|
|
2734
|
+
* False when the running agent process was spawned under a model/effort the
|
|
2735
|
+
* user has since changed in the picker. Unlike a vendor backend, the model
|
|
2736
|
+
* is a spawn-time argument to the agent CLI (`--model`), not a per-request
|
|
2737
|
+
* field — `continueConversation` sends the next turn to whatever process is
|
|
2738
|
+
* already running, so a plain config read here would silently keep planning
|
|
2739
|
+
* on the old model. No conversation yet is vacuously "current".
|
|
2740
|
+
*/
|
|
2741
|
+
conversationMatchesConfig(): boolean;
|
|
2742
|
+
/** Always: a conversation whose planner cannot reach the Ordewell MCP server fails to start rather than falling back to the envelopes. */
|
|
2743
|
+
plannerToolsAttached(): boolean;
|
|
2744
|
+
/** The agent's own session id, a resumption hint only — Ordewell's transcript is authoritative (T4). */
|
|
2745
|
+
nativeSessionId(): string | null;
|
|
2746
|
+
heldSecrets(): readonly string[];
|
|
2747
|
+
restart(): void;
|
|
2748
|
+
reset(): void;
|
|
2749
|
+
startConversation(req: ConversationRequest): Promise<ConversationTurn>;
|
|
2750
|
+
continueConversation(userMessage: string, onProgress: (progress: ResearchProgress) => void, signal?: AbortSignal): Promise<ConversationTurn>;
|
|
2751
|
+
private openingMessage;
|
|
2752
|
+
/**
|
|
2753
|
+
* Drive one user message to a settled planner turn through
|
|
2754
|
+
* {@link settleReply}, the loop the API backend settles through too. The
|
|
2755
|
+
* tool rounds belong to the agent now, so one call is one agent turn —
|
|
2756
|
+
* continued while it left subagents running in the background.
|
|
2757
|
+
*/
|
|
2758
|
+
private runConversation;
|
|
2759
|
+
/**
|
|
2760
|
+
* Run one agent turn: stream its events into `ResearchProgress`, collect the
|
|
2761
|
+
* reply text, and return the research steps produced. Tool calls are matched
|
|
2762
|
+
* to their results by the agent's own call id — matching by tool name puts
|
|
2763
|
+
* one file's body on another file's row the moment an agent runs two reads
|
|
2764
|
+
* at once, which all three of these do routinely.
|
|
2765
|
+
*/
|
|
2766
|
+
/**
|
|
2767
|
+
* A turn, and then — while the agent ended it with subagents still in the
|
|
2768
|
+
* background — a turn asking it to wait for them: whatever they find would
|
|
2769
|
+
* otherwise be said into a closed turn, or, in a one-shot, into a session
|
|
2770
|
+
* already thrown away. Bounded by `waits`, shared across a reply's sends.
|
|
2771
|
+
*/
|
|
2772
|
+
private runTurnAwaitingAgents;
|
|
2773
|
+
private runTurn;
|
|
2774
|
+
/**
|
|
2775
|
+
* The only way this service starts an agent, and it takes a planner start by
|
|
2776
|
+
* type: the read-only boundary (ADR-0008/0009) cannot be crossed into task
|
|
2777
|
+
* mode from here without changing this signature. A planner that cannot
|
|
2778
|
+
* reach its tools cannot submit a plan, so it is respawned once and never
|
|
2779
|
+
* left to plan without them (ADR-0025).
|
|
2780
|
+
*/
|
|
2781
|
+
private startAdapter;
|
|
2782
|
+
private newAdapter;
|
|
2783
|
+
/** Whether the web-read policy lets the runner's own session-wide search run unasked now (ADR-0027). */
|
|
2784
|
+
private nativeSearch;
|
|
2785
|
+
/**
|
|
2786
|
+
* The live agent process, restarted from its own session id if it died
|
|
2787
|
+
* between turns. Resume is a hint: when it fails, the caller's next
|
|
2788
|
+
* `startConversation` reseeds from Ordewell's transcript, which is the same
|
|
2789
|
+
* degradation `restoreChat` already performs on every surface.
|
|
2790
|
+
*
|
|
2791
|
+
* Also restarted when the policy's decision on the runner's own search has
|
|
2792
|
+
* changed since the process was spawned: the running thread would otherwise
|
|
2793
|
+
* keep searching unasked after Full turned Guarded. Codex 0.162 applies
|
|
2794
|
+
* `thread/resume`'s `config.web_search` either way (checked live).
|
|
2795
|
+
*/
|
|
2796
|
+
private ensureAdapter;
|
|
2797
|
+
/**
|
|
2798
|
+
* What the planner gets of the repository's config, its runner being
|
|
2799
|
+
* launched without it (ADR-0028): the instruction files, which are text, in
|
|
2800
|
+
* its system prompt, and the project MCP servers it may start. Decided once
|
|
2801
|
+
* per conversation, so a respawn starts the same servers without asking.
|
|
2802
|
+
*/
|
|
2803
|
+
private repoConfig;
|
|
2804
|
+
private plannerModel;
|
|
2805
|
+
/**
|
|
2806
|
+
* A plan from one-shot agent sessions: only what a submit_plan call
|
|
2807
|
+
* accepted, never the reply text, so a plan the tool refused cannot come
|
|
2808
|
+
* back in as the JSON the agent echoed. A session that ends without an
|
|
2809
|
+
* accepted call is followed by one corrective session, then fails.
|
|
2810
|
+
*/
|
|
2811
|
+
private submittedPlan;
|
|
2812
|
+
/**
|
|
2813
|
+
* A single agent session that answers one prompt and exits. Used by every
|
|
2814
|
+
* non-conversational entry point. A plan-shaped envelope in its text
|
|
2815
|
+
* streams to the plan display, as a vendor planner's one-shot does, and the
|
|
2816
|
+
* prose around it is not streamed at all, since no turn is open to show it in.
|
|
2817
|
+
*/
|
|
2818
|
+
private oneShot;
|
|
2819
|
+
researchAndPlan(userDescription: string, runners: RunnerId[], modelsByRunner: Partial<Record<RunnerId, DiscoveredModel[]>>, fs: IFileSystem, onProgress: (progress: ResearchProgress) => void, _fetcher?: IWebFetcher, runnerModes?: Record<RunnerId, RunnerModeInfo[]>, modes?: PlannerModes, signal?: AbortSignal): Promise<{
|
|
2820
|
+
tasks: Task[];
|
|
2821
|
+
researchLog: ResearchLogEntry[];
|
|
2822
|
+
researchResults: string;
|
|
2823
|
+
}>;
|
|
2824
|
+
generatePlanDirect(userDescription: string, runners?: RunnerId[], modelsByRunner?: Partial<Record<RunnerId, DiscoveredModel[]>>, onToken?: (token: string) => void, _fs?: IFileSystem, _fetcher?: IWebFetcher, runnerModes?: Record<RunnerId, RunnerModeInfo[]>, modes?: PlannerModes, signal?: AbortSignal): Promise<Task[]>;
|
|
2825
|
+
}
|
|
2826
|
+
|
|
2827
|
+
/**
|
|
2828
|
+
* The planner tools a session offers its conversation (ADR-0022). A backend
|
|
2829
|
+
* that can inject the Ordewell MCP server into its planner issues a planner
|
|
2830
|
+
* token bound to `sessionId` and these handlers; API planners use the same task read handlers directly in their tool loop.
|
|
2831
|
+
*/
|
|
2832
|
+
interface PlannerToolsOffer {
|
|
2833
|
+
sessionId: string;
|
|
2834
|
+
handler: PlannerToolHandler;
|
|
2835
|
+
}
|
|
2836
|
+
/**
|
|
2837
|
+
* Everything the conversation loop needs to start planning (ADR-0002).
|
|
2838
|
+
* The AI service keeps the tool-use message history internally across turns;
|
|
2839
|
+
* the surfaces only exchange user/assistant messages with it.
|
|
2840
|
+
*/
|
|
2841
|
+
interface ConversationRequest {
|
|
2842
|
+
goal: string;
|
|
2843
|
+
runners: RunnerId[];
|
|
2844
|
+
modelsByRunner: Partial<Record<RunnerId, DiscoveredModel[]>>;
|
|
2845
|
+
fs: IFileSystem;
|
|
2846
|
+
onProgress: (progress: ResearchProgress) => void;
|
|
2847
|
+
fetcher?: IWebFetcher;
|
|
2848
|
+
runnerModes?: Record<RunnerId, RunnerModeInfo[]>;
|
|
2849
|
+
autonomousDefault?: boolean;
|
|
2850
|
+
/** Where tasks will run (ADR-0013, ADR-0014): in worktrees, the prompt drops file-overlap ordering and describes a repo group. */
|
|
2851
|
+
isolatedExecution?: IsolatedExecution;
|
|
2852
|
+
/** A plan the planner submits starts at once (ADR-0030), so the opening prompt has no outline gate. */
|
|
2853
|
+
autorun?: boolean;
|
|
2854
|
+
signal?: AbortSignal;
|
|
2855
|
+
/**
|
|
2856
|
+
* Persisted dialogue to seed a resumed conversation (session reload). The
|
|
2857
|
+
* turns are injected into the message history verbatim — no LLM call happens
|
|
2858
|
+
* for them; the first call is the turn opened by `initialMessage`.
|
|
2859
|
+
*/
|
|
2860
|
+
priorHistory?: ConversationMessage[];
|
|
2861
|
+
/**
|
|
2862
|
+
* The user message that opens this turn. Defaults to `goal` — set it when
|
|
2863
|
+
* resuming so the system prompt keeps the original goal while the turn
|
|
2864
|
+
* carries the user's new message.
|
|
2865
|
+
*/
|
|
2866
|
+
initialMessage?: string;
|
|
2867
|
+
plannerTools?: PlannerToolsOffer;
|
|
2868
|
+
/**
|
|
2869
|
+
* The session's approval policy, which a harness planner's own tool
|
|
2870
|
+
* requests are decided against as the API planner's are (ADR-0026). Absent:
|
|
2871
|
+
* nobody can be asked, so they are denied.
|
|
2872
|
+
*/
|
|
2873
|
+
approval?: IApproval;
|
|
2874
|
+
/**
|
|
2875
|
+
* The workspace's skill catalog. Every planner is shown the task skills it
|
|
2876
|
+
* may attach; only one with tools is shown the planner skills, which load
|
|
2877
|
+
* through `load_skill` alone (ADR-0024).
|
|
2878
|
+
*/
|
|
2879
|
+
skills?: readonly SkillInfo[];
|
|
2880
|
+
}
|
|
2881
|
+
/**
|
|
2882
|
+
* A run of a turn's reply text, as a backend reports it. `narration`: it began
|
|
2883
|
+
* before its call's last tool call, so it stays when a corrective retry
|
|
2884
|
+
* discards what the call replied.
|
|
2885
|
+
*/
|
|
2886
|
+
interface TurnRun extends ReplyRun {
|
|
2887
|
+
narration?: true;
|
|
2888
|
+
}
|
|
2889
|
+
/**
|
|
2890
|
+
* One planner turn's outcome. The planner talks to the user (`message`),
|
|
2891
|
+
* commits a full plan (`plan`, a `{tasks:[...]}` JSON object), emits targeted
|
|
2892
|
+
* task edits (`task_ops`, a `{taskOps:[...]}` JSON object), or asks to read
|
|
2893
|
+
* task bodies and the full catalog before editing (`task_query`, a
|
|
2894
|
+
* `{taskQuery:{...}}` JSON object). The Session validates and applies task ops
|
|
2895
|
+
* atomically and answers queries out of its own state; the AI service only
|
|
2896
|
+
* parses them.
|
|
2897
|
+
*/
|
|
2898
|
+
type ConversationTurn =
|
|
2899
|
+
/**
|
|
2900
|
+
* `aborted`: the call was stopped, and `text` is only what it had by then.
|
|
2901
|
+
* `failed`: the call ended without a reply (a dead agent, a usage limit),
|
|
2902
|
+
* and `text` says why; `usageLimit`, where the runner also said in a field
|
|
2903
|
+
* whether that was its usage limit (see {@link TurnFailure}).
|
|
2904
|
+
* `runs`, on every kind: the reply's runs the turn's surviving calls
|
|
2905
|
+
* streamed, in order, and last a note written in the planner's place when
|
|
2906
|
+
* `text` is one. An envelope's own run is not among them. Absent from a
|
|
2907
|
+
* backend that does not report runs.
|
|
2908
|
+
*/
|
|
2909
|
+
{
|
|
2910
|
+
kind: 'message';
|
|
2911
|
+
text: string;
|
|
2912
|
+
researchLog: ResearchLogEntry[];
|
|
2913
|
+
aborted?: boolean;
|
|
2914
|
+
failed?: boolean;
|
|
2915
|
+
usageLimit?: UsageLimitSignal | false;
|
|
2916
|
+
runs?: TurnRun[];
|
|
2917
|
+
} | {
|
|
2918
|
+
kind: 'plan';
|
|
2919
|
+
tasks: Task[];
|
|
2920
|
+
text: string;
|
|
2921
|
+
researchLog: ResearchLogEntry[];
|
|
2922
|
+
runs?: TurnRun[];
|
|
2923
|
+
} | {
|
|
2924
|
+
kind: 'task_ops';
|
|
2925
|
+
ops: TaskOp[];
|
|
2926
|
+
text: string;
|
|
2927
|
+
researchLog: ResearchLogEntry[];
|
|
2928
|
+
runs?: TurnRun[];
|
|
2929
|
+
} | {
|
|
2930
|
+
kind: 'task_query';
|
|
2931
|
+
query: TaskQuery;
|
|
2932
|
+
text: string;
|
|
2933
|
+
researchLog: ResearchLogEntry[];
|
|
2934
|
+
runs?: TurnRun[];
|
|
2935
|
+
};
|
|
2936
|
+
interface IAiService {
|
|
2937
|
+
/** Credential values from the harness planner's effective environment, for the outbound secret check. */
|
|
2938
|
+
heldSecrets?(): readonly string[];
|
|
2939
|
+
/**
|
|
2940
|
+
* Begin the planner conversation: collect workspace context, run the
|
|
2941
|
+
* research/tool loop, and return the first planner turn. The service
|
|
2942
|
+
* retains the full API message history for subsequent
|
|
2943
|
+
* {@link continueConversation} calls.
|
|
2944
|
+
*/
|
|
2945
|
+
startConversation(req: ConversationRequest): Promise<ConversationTurn>;
|
|
2946
|
+
/**
|
|
2947
|
+
* Feed the user's reply into the active conversation and return the next
|
|
2948
|
+
* planner turn. Throws if no conversation is active.
|
|
2949
|
+
*/
|
|
2950
|
+
continueConversation(userMessage: string, onProgress: (progress: ResearchProgress) => void, signal?: AbortSignal): Promise<ConversationTurn>;
|
|
2951
|
+
/**
|
|
2952
|
+
* Whether a planner conversation is currently held in memory. A branching
|
|
2953
|
+
* predicate only — never a test for whether there is anything to release.
|
|
2954
|
+
* The harness backend holds an OS process that outlives its conversation, so
|
|
2955
|
+
* guarding {@link reset} with this leaks an agent process; call `reset`
|
|
2956
|
+
* unconditionally instead.
|
|
2957
|
+
*/
|
|
2958
|
+
hasActiveConversation(): boolean;
|
|
2959
|
+
/**
|
|
2960
|
+
* Optional: end the planner's live process but keep its own session id, so
|
|
2961
|
+
* the next turn starts a fresh one resumed from it (ADR-0009). What a
|
|
2962
|
+
* supervisor does to a planner that crashed or hung (ADR-0029). Absent where
|
|
2963
|
+
* there is no process to end: the next call is a fresh request anyway.
|
|
2964
|
+
*/
|
|
2965
|
+
restart?(): void;
|
|
2966
|
+
/**
|
|
2967
|
+
* Whether the active conversation (if any) still matches the planner
|
|
2968
|
+
* model/effort currently configured. Optional, and true when absent: a
|
|
2969
|
+
* vendor backend re-reads its model from config on every turn, so there is
|
|
2970
|
+
* nothing to drift. A harness planner (ADR-0009) is the exception — its
|
|
2971
|
+
* model is baked into the agent process at spawn — so only
|
|
2972
|
+
* {@link CliAgentAiService} answers this for real. False tells the caller
|
|
2973
|
+
* the same thing an absent conversation would: don't call
|
|
2974
|
+
* `continueConversation`, restart instead so the new model takes effect.
|
|
2975
|
+
*/
|
|
2976
|
+
conversationMatchesConfig?(): boolean;
|
|
2977
|
+
/**
|
|
2978
|
+
* Whether the planner reads the catalog and submits plans through Ordewell's
|
|
2979
|
+
* MCP tools (ADR-0022). Optional, and false when absent: only a harness
|
|
2980
|
+
* planner the server attached to has them, and every other planner keeps
|
|
2981
|
+
* the per-turn catalog and the envelopes.
|
|
2982
|
+
*/
|
|
2983
|
+
plannerToolsAttached?(): boolean;
|
|
2984
|
+
/**
|
|
2985
|
+
* Optional: prune bulky raw tool output out of the live conversation, in
|
|
2986
|
+
* place, keeping subagent digests. Returns the characters removed. Absent
|
|
2987
|
+
* where the backend holds no tool transcript to prune (a harness planner's
|
|
2988
|
+
* agent owns its own context).
|
|
2989
|
+
*/
|
|
2990
|
+
pruneContext?(): number;
|
|
2991
|
+
/**
|
|
2992
|
+
* One-shot research + plan for non-conversational surfaces (CLI `plan --goal`,
|
|
2993
|
+
* web REST). Never asks questions — it plans with what it can find.
|
|
2994
|
+
*/
|
|
2995
|
+
researchAndPlan(userDescription: string, runners: RunnerId[], modelsByRunner: Partial<Record<RunnerId, DiscoveredModel[]>>, fs: IFileSystem, onProgress: (progress: ResearchProgress) => void, fetcher?: IWebFetcher, runnerModes?: Record<RunnerId, RunnerModeInfo[]>, modes?: PlannerModes, signal?: AbortSignal): Promise<{
|
|
2996
|
+
tasks: Task[];
|
|
2997
|
+
researchLog: ResearchLogEntry[];
|
|
2998
|
+
researchResults: string;
|
|
2999
|
+
}>;
|
|
3000
|
+
generatePlanDirect(userDescription: string, runners: RunnerId[], modelsByRunner: Partial<Record<RunnerId, DiscoveredModel[]>>, onToken?: (token: string) => void, fs?: IFileSystem, fetcher?: IWebFetcher, runnerModes?: Record<RunnerId, RunnerModeInfo[]>, modes?: PlannerModes, signal?: AbortSignal): Promise<Task[]>;
|
|
3001
|
+
/**
|
|
3002
|
+
* Release everything this service holds: the conversation, any in-flight
|
|
3003
|
+
* turn, and — on the harness backend — the agent process itself. Idempotent
|
|
3004
|
+
* and cheap in every implementation, so callers never gate it.
|
|
3005
|
+
*/
|
|
3006
|
+
reset(): void;
|
|
3007
|
+
}
|
|
3008
|
+
/**
|
|
3009
|
+
* The single branch point between planner transports. Two of them talk HTTP to
|
|
3010
|
+
* an LLM vendor; the third (ADR-0009) drives a coding agent already installed
|
|
3011
|
+
* on the machine. Everything downstream — the plan contract, the research-step
|
|
3012
|
+
* stream, the four surfaces — is shared.
|
|
3013
|
+
*/
|
|
3014
|
+
declare function createAiService(config: IConfig, deps?: CliAgentAiServiceDeps): IAiService;
|
|
3015
|
+
|
|
3016
|
+
/** The dialogue record a fork carries — what {@link PlannerConversation.clone} returns. */
|
|
3017
|
+
interface ForkedDialogue {
|
|
3018
|
+
conversationHistory: ConversationMessage[];
|
|
3019
|
+
researchLog: ResearchLogEntry[];
|
|
3020
|
+
}
|
|
3021
|
+
|
|
3022
|
+
/** A user's message as sent, and the skills its `/name` tokens load. */
|
|
3023
|
+
interface SkillInvocation {
|
|
3024
|
+
text: string;
|
|
3025
|
+
skills: SkillLoad[];
|
|
3026
|
+
}
|
|
3027
|
+
/**
|
|
3028
|
+
* A path with the home directory written `~`, for a notice or listing. On
|
|
3029
|
+
* Windows the drive letter's case is not significant and the separators are
|
|
3030
|
+
* rendered `/`, so the same skill reads alike wherever the session reloads.
|
|
3031
|
+
*/
|
|
3032
|
+
declare function abbreviateHome(file: string, home?: string, platform?: NodeJS.Platform): string;
|
|
3033
|
+
/**
|
|
3034
|
+
* Resolve the `/skill-name` tokens in a message to snapshots of the skills they
|
|
3035
|
+
* name. Pure and exported so surfaces can resolve without a full Session. The text itself is never rewritten.
|
|
3036
|
+
*
|
|
3037
|
+
* Every distinct skill named loads, once, in the order first named. A token
|
|
3038
|
+
* naming no skill is plain text, wherever it stands — a whole message of
|
|
3039
|
+
* `/unknown` included: embedded in a sentence it is as likely to be incidental
|
|
3040
|
+
* text (a path, an example command) as a typo'd invocation. So is one naming
|
|
3041
|
+
* a skill with `user-invocable: false`, which only the model may invoke.
|
|
3042
|
+
*/
|
|
3043
|
+
declare function resolveSkillInvocation(text: string, skillsService: Pick<SkillsService, 'findSkill'>, home?: string): SkillInvocation;
|
|
3044
|
+
/**
|
|
3045
|
+
* What the planner receives for a user message: the bodies of the skills it
|
|
3046
|
+
* loaded, then the message as the user wrote it. The one composition for a
|
|
3047
|
+
* live send and for a replay of the transcript, so a resumed conversation
|
|
3048
|
+
* hands the planner what the live one did.
|
|
3049
|
+
*/
|
|
3050
|
+
declare function plannerMessage(text: string, loads: readonly SkillLoad[]): string;
|
|
3051
|
+
/**
|
|
3052
|
+
* The transcript as the planner is to see it on a replay: each user message
|
|
3053
|
+
* carries the skills loaded after it, composed by {@link plannerMessage}, and
|
|
3054
|
+
* the load entries themselves are folded away. Sessions saved before skill
|
|
3055
|
+
* loads existed hold the expanded text in the message and replay unchanged.
|
|
3056
|
+
* A failed turn's entry is left out: the planner never said it.
|
|
3057
|
+
*/
|
|
3058
|
+
declare function plannerTranscript(history: readonly ConversationMessage[]): ConversationMessage[];
|
|
3059
|
+
|
|
3060
|
+
/**
|
|
3061
|
+
* What a stopped runner says about why it stopped, and what ending an attempt
|
|
3062
|
+
* does to the runner it leaves behind. TaskOrchestrator acts on the answers.
|
|
3063
|
+
*/
|
|
3064
|
+
|
|
3065
|
+
/**
|
|
3066
|
+
* A turn's failure as its runner reported it: its words, and `usageLimit`
|
|
3067
|
+
* where its protocol also says in a field whether it was the limit (Codex's
|
|
3068
|
+
* `codexErrorInfo`).
|
|
3069
|
+
*/
|
|
3070
|
+
interface TurnFailure {
|
|
3071
|
+
message: string;
|
|
3072
|
+
usageLimit?: UsageLimitSignal | false;
|
|
3073
|
+
}
|
|
3074
|
+
|
|
3075
|
+
/** What a usage limit that ended a planner turn led to. */
|
|
3076
|
+
interface PlannerLimit {
|
|
3077
|
+
/** The line the transcript keeps, drawn as a failure. */
|
|
3078
|
+
text: string;
|
|
3079
|
+
/** When what the turn took goes again on its own; null when it waits for the user's next message. */
|
|
3080
|
+
resendAt: Date | null;
|
|
3081
|
+
/** Why nothing goes again on its own, when nothing does: what the planner did, as in "the planner hit its usage limit and …". */
|
|
3082
|
+
held?: string;
|
|
3083
|
+
}
|
|
3084
|
+
|
|
2285
3085
|
/**
|
|
2286
3086
|
* What the user allows the plan to be built from right now: the enabled
|
|
2287
3087
|
* runners and each runner's model allowlist. Structural, so a surface that
|
|
@@ -2328,19 +3128,560 @@ declare function assignmentIssue(task: AssignedTask, scope: AssignmentScope): As
|
|
|
2328
3128
|
declare function assignmentIssueText(issue: AssignmentIssue): string;
|
|
2329
3129
|
|
|
2330
3130
|
/**
|
|
2331
|
-
*
|
|
3131
|
+
* The planner turn (see CONTEXT.md): the one piece of planner work that holds
|
|
3132
|
+
* the conversation, from its claim to its settled outcome. Its signal is what a
|
|
3133
|
+
* stop aborts.
|
|
3134
|
+
*/
|
|
3135
|
+
declare class PlannerTurn {
|
|
3136
|
+
private readonly controller;
|
|
3137
|
+
private dropped;
|
|
3138
|
+
private forced;
|
|
3139
|
+
get signal(): AbortSignal;
|
|
3140
|
+
/** The plan it was answering was dropped under it, so nothing it settles may land. */
|
|
3141
|
+
get abandoned(): boolean;
|
|
3142
|
+
/** A force send stopped it, not the user: the forced turn follows. A turn already stopped stays the user's stop. */
|
|
3143
|
+
get forceSent(): boolean;
|
|
3144
|
+
stop(by?: 'force_send'): void;
|
|
3145
|
+
abandon(): void;
|
|
3146
|
+
}
|
|
3147
|
+
/** What a planner turn may change: the whole plan, or tasks through edits. */
|
|
3148
|
+
type PlanChange = 'plan' | 'edit';
|
|
3149
|
+
/**
|
|
3150
|
+
* A wake-up turn (ADR-0029): a supervisor's turn on wake-up digests alone, no
|
|
3151
|
+
* user message in it. What it may do is its level's, decided by the host.
|
|
3152
|
+
*/
|
|
3153
|
+
interface WakeUpTurn {
|
|
3154
|
+
/** Reads the turn may make, in place of a user message's caps. */
|
|
3155
|
+
readBudget: number;
|
|
3156
|
+
/**
|
|
3157
|
+
* Why a whole plan, or a task edit, is refused in this turn, worded for the
|
|
3158
|
+
* model; undefined when its level allows it. Asked when the change is made
|
|
3159
|
+
* and again as it lands, so a level lowered during the turn applies at once.
|
|
3160
|
+
*/
|
|
3161
|
+
refused(change: PlanChange): string | undefined;
|
|
3162
|
+
/** Told to the model after the digests and never saved, like the plan block: who it is in this turn, and what it may do. */
|
|
3163
|
+
brief?: string;
|
|
3164
|
+
}
|
|
3165
|
+
/** A read made through a planner tool: refused once the turn's budget is gone, else answered, told to land when the budget asks it to. */
|
|
3166
|
+
type ToolRead<T> = {
|
|
3167
|
+
status: 'refused';
|
|
3168
|
+
} | {
|
|
3169
|
+
status: 'answered';
|
|
3170
|
+
value: T;
|
|
3171
|
+
landNow: boolean;
|
|
3172
|
+
};
|
|
3173
|
+
/** A plan or task edit handed to the open turn over a channel other than its reply (ADR-0022). */
|
|
3174
|
+
type PlannerSubmission = {
|
|
3175
|
+
kind: 'plan';
|
|
3176
|
+
tasks: Task[];
|
|
3177
|
+
} | {
|
|
3178
|
+
kind: 'task_ops';
|
|
3179
|
+
ops: TaskOp[];
|
|
3180
|
+
};
|
|
3181
|
+
/** Everything (re)opening a conversation needs besides the dialogue itself. */
|
|
3182
|
+
type ConversationOpening = Omit<ConversationRequest, 'goal' | 'onProgress' | 'signal' | 'priorHistory' | 'initialMessage'>;
|
|
3183
|
+
/**
|
|
3184
|
+
* What the conversation needs from the session that hosts it. The session keeps
|
|
3185
|
+
* plan state, persistence and scheduling; the conversation reaches them only
|
|
3186
|
+
* through here, so a fake host is enough to drive it.
|
|
3187
|
+
*/
|
|
3188
|
+
interface PlannerConversationHost {
|
|
3189
|
+
/** The plan the transcript lives on (`conversationHistory`, `researchLog`). */
|
|
3190
|
+
plan(): LegacyPlanState | null;
|
|
3191
|
+
goal(): string;
|
|
3192
|
+
aiService(): IAiService;
|
|
3193
|
+
onProgress(progress: ResearchProgress): void;
|
|
3194
|
+
/** Fresh discovery, allowlist-filtered the way the system prompt shows it. */
|
|
3195
|
+
opening(runners: RunnerId[]): Promise<ConversationOpening>;
|
|
3196
|
+
/** What the per-turn catalog block and every read draw from, as of now. */
|
|
3197
|
+
catalog(): TaskQueryCatalog;
|
|
3198
|
+
/** Whether autorun is on now (ADR-0030); the planner is told in the turn after it changes. */
|
|
3199
|
+
autorun(): boolean;
|
|
3200
|
+
/** The enabled runners and allowlist as of now; the planner is told in the turn after either changes. */
|
|
3201
|
+
assignmentScope(): AssignmentScope;
|
|
3202
|
+
tasks(): readonly Task[];
|
|
3203
|
+
/**
|
|
3204
|
+
* Why a task cannot start now, from the scheduler's start rule (ADR-0034);
|
|
3205
|
+
* none once nothing holds it back, or while the plan is not yet approved.
|
|
3206
|
+
*/
|
|
3207
|
+
startWaits(task: Readonly<Task>): readonly StartWait[];
|
|
3208
|
+
/**
|
|
3209
|
+
* The orchestrator's live capture for a task's latest attempt, backing the
|
|
3210
|
+
* `output` field of a read. Injected the same way as the catalog so the
|
|
3211
|
+
* conversation never reaches into execution state directly.
|
|
3212
|
+
*/
|
|
3213
|
+
liveOutput: LiveOutputLookup;
|
|
3214
|
+
isExecuting(): boolean;
|
|
3215
|
+
/** The session's single mutation ritual: op → persist → notify (default: the plan). */
|
|
3216
|
+
mutate(op: () => boolean, notify?: () => void): LegacyPlanState | null;
|
|
3217
|
+
broadcast: SessionBroadcaster;
|
|
3218
|
+
/** `turnId`: the planner turn whose commit this is, when one is. */
|
|
3219
|
+
broadcastPlan(turnId?: string): void;
|
|
3220
|
+
/**
|
|
3221
|
+
* Validate a batch against live state; nothing of it is applied. A
|
|
3222
|
+
* supervisor's batch that trips the oscillation guard is refused, and the
|
|
3223
|
+
* user is told of the trip once, in the supervisor's voice (ADR-0029): a note
|
|
3224
|
+
* saved as it is written, like any of Ordewell's own, which a turn in flight
|
|
3225
|
+
* holds nothing in.
|
|
3226
|
+
*/
|
|
3227
|
+
validateOps(ops: TaskOp[]): ApplyTaskOpsResult;
|
|
3228
|
+
/** The workspace's skill catalog under its repo group's layout, which the skills a plan or edit attaches are checked against. */
|
|
3229
|
+
taskSkills(): SkillLookup;
|
|
3230
|
+
/** Tell the user something the transcript does not carry. */
|
|
3231
|
+
notice(level: SessionNotice['level'], message: string): void;
|
|
3232
|
+
/** Load planner-produced tasks: an edit keeps run state, a commit starts over. Returns how many landed. */
|
|
3233
|
+
adoptTasks(tasks: readonly Task[], how: 'edit' | 'commit'): number;
|
|
3234
|
+
capturePrd(text: string): void;
|
|
3235
|
+
/** The planner turn was stopped or abandoned: whatever it is waiting on (a parked approval) will never be answered. */
|
|
3236
|
+
turnAborted(): void;
|
|
3237
|
+
/** A planner turn is about to take the conversation, which nothing holds now. */
|
|
3238
|
+
turnClaiming?(): void;
|
|
3239
|
+
/** A planner turn let the conversation go, so the next one may take it. */
|
|
3240
|
+
turnReleased?(): void;
|
|
3241
|
+
/** A turn ended on the planner's usage limit, which it reported as `said`: what is sent again, and when. */
|
|
3242
|
+
usageLimit(said: TurnFailure): PlannerLimit;
|
|
3243
|
+
}
|
|
3244
|
+
/**
|
|
3245
|
+
* A conversation edit (rewind, fork) the conversation refused because the
|
|
3246
|
+
* request itself is wrong — no such message, nothing to fork. Transport-agnostic
|
|
3247
|
+
* like `PlanEditError`: a route maps it to a status, core carries none.
|
|
3248
|
+
*/
|
|
3249
|
+
declare class ConversationEditError extends Error {
|
|
3250
|
+
constructor(message: string);
|
|
3251
|
+
}
|
|
3252
|
+
/**
|
|
3253
|
+
* Refused because a planner turn is in flight. Distinct from a bad request:
|
|
3254
|
+
* the same call succeeds once the turn settles, and the turn would otherwise
|
|
3255
|
+
* append its reply to a transcript that no longer holds the message it
|
|
3256
|
+
* answers.
|
|
3257
|
+
*/
|
|
3258
|
+
declare class ConversationBusyError extends ConversationEditError {
|
|
3259
|
+
constructor(operation: string);
|
|
3260
|
+
}
|
|
3261
|
+
/**
|
|
3262
|
+
* A planner turn that settled after the plan it was answering was dropped (a
|
|
3263
|
+
* new plan, another session adopted, the session closed). Nothing it produced
|
|
3264
|
+
* was committed: landing it would write one session's reply into another's
|
|
3265
|
+
* transcript.
|
|
3266
|
+
*/
|
|
3267
|
+
declare class PlannerTurnDiscardedError extends Error {
|
|
3268
|
+
constructor(options?: ErrorOptions);
|
|
3269
|
+
}
|
|
3270
|
+
/**
|
|
3271
|
+
* A wake-up turn's backend ended without a reply — a dead agent, a usage
|
|
3272
|
+
* limit — and said why. A user's turn shows that in the chat instead; a
|
|
3273
|
+
* wake-up has nobody waiting on it, so its supervisor is told (ADR-0029).
|
|
3274
|
+
*/
|
|
3275
|
+
declare class PlannerTurnFailedError extends Error {
|
|
3276
|
+
constructor(message: string);
|
|
3277
|
+
}
|
|
3278
|
+
/**
|
|
3279
|
+
* A planner turn ended on the planner's usage limit (#84). Nothing of it
|
|
3280
|
+
* lands: the transcript keeps the failure instead, out of the planner's own
|
|
3281
|
+
* dialogue, and the turn's inputs wait to be sent again — at `resendAt`, or
|
|
3282
|
+
* with the next input when it is null.
|
|
3283
|
+
*/
|
|
3284
|
+
declare class PlannerUsageLimitError extends PlannerTurnFailedError {
|
|
3285
|
+
readonly resendAt: Date | null;
|
|
3286
|
+
/** Why nothing is sent again on its own, when nothing is. */
|
|
3287
|
+
readonly held?: string;
|
|
3288
|
+
constructor(limit: PlannerLimit);
|
|
3289
|
+
}
|
|
3290
|
+
/**
|
|
3291
|
+
* The user stopped the turn and the backend reported that as a thrown error
|
|
3292
|
+
* rather than a settled reply. Typed so a surface can stay quiet for it without
|
|
3293
|
+
* reading the message: some SDKs' abort errors are plain `Error`s by name, and
|
|
3294
|
+
* a real failure can mention "aborted" too.
|
|
3295
|
+
*/
|
|
3296
|
+
declare class PlannerTurnStoppedError extends Error {
|
|
3297
|
+
constructor(options?: ErrorOptions);
|
|
3298
|
+
}
|
|
3299
|
+
/** A user message a rewind can fork the conversation from just before. */
|
|
3300
|
+
interface RewindTarget {
|
|
3301
|
+
/** Position in the transcript — what {@link PlannerConversation.cloneBefore} takes. */
|
|
3302
|
+
index: number;
|
|
3303
|
+
preview: string;
|
|
3304
|
+
/** The whole message — the preview cuts it to one line, and a surface confirming the rewind quotes it. */
|
|
3305
|
+
content: string;
|
|
3306
|
+
timestamp: string;
|
|
3307
|
+
}
|
|
3308
|
+
/** What a rewind forks from, and the message it lands just before. */
|
|
3309
|
+
interface RewoundDialogue {
|
|
3310
|
+
dialogue: ForkedDialogue;
|
|
3311
|
+
rewoundMessage: string;
|
|
3312
|
+
}
|
|
3313
|
+
/** What a compaction left behind. */
|
|
3314
|
+
interface ConversationCompaction {
|
|
3315
|
+
summary: string;
|
|
3316
|
+
/** Transcript entries kept verbatim after the summary entry. */
|
|
3317
|
+
keptMessages: number;
|
|
3318
|
+
}
|
|
3319
|
+
interface ReplyOptions {
|
|
3320
|
+
signal?: AbortSignal;
|
|
3321
|
+
/**
|
|
3322
|
+
* Run inside the mutation that lands the turn's outcome — the edit, the
|
|
3323
|
+
* plan or the reply — so what the caller drops with it (the inputs the turn
|
|
3324
|
+
* answered) leaves disk in the same save.
|
|
3325
|
+
*/
|
|
3326
|
+
onCommit?: () => void;
|
|
3327
|
+
/** Wake-up digests delivered with this turn (ADR-0029), oldest first, ahead of the user's message if there is one. */
|
|
3328
|
+
wakeUps?: readonly string[];
|
|
3329
|
+
/** Set when the turn is a wake-up's alone. */
|
|
3330
|
+
wakeUp?: WakeUpTurn;
|
|
3331
|
+
/** The user's message was force sent: the turn replaces the one it stopped (ADR-0023). */
|
|
3332
|
+
forced?: boolean;
|
|
3333
|
+
}
|
|
3334
|
+
/**
|
|
3335
|
+
* The planner conversation (ADR-0002), end to end: the persisted dialogue
|
|
3336
|
+
* record, the live model context behind it, and every turn from the user's
|
|
3337
|
+
* message to a settled, persisted outcome.
|
|
3338
|
+
*
|
|
3339
|
+
* It is the only writer of `conversationHistory` and of the planner's
|
|
3340
|
+
* `researchLog`. The writes land on the host's plan object — which is what the
|
|
3341
|
+
* session persists and broadcasts — and only ever inside the host's mutation
|
|
3342
|
+
* ritual, so each reaches disk in the save made for it. A planner turn writes
|
|
3343
|
+
* nothing until the mutation that lands it ({@link land}): any other save made
|
|
3344
|
+
* while it runs finds nothing of it to persist.
|
|
3345
|
+
*
|
|
3346
|
+
* The live model context (a vendor service's message list, a harness planner's
|
|
3347
|
+
* process and native session id) is disposable: {@link reset} drops it and the
|
|
3348
|
+
* next turn is replayed from the transcript. That is what lets the transcript
|
|
3349
|
+
* be replaced by a summary, or copied into a fork, without the model's memory
|
|
3350
|
+
* drifting from it.
|
|
3351
|
+
*/
|
|
3352
|
+
declare class PlannerConversation {
|
|
3353
|
+
private readonly host;
|
|
3354
|
+
private readonly briefing;
|
|
3355
|
+
private live;
|
|
3356
|
+
private openTurn;
|
|
3357
|
+
constructor(host: PlannerConversationHost);
|
|
3358
|
+
get transcript(): readonly ConversationMessage[];
|
|
3359
|
+
/** Whether a planner turn holds the conversation. */
|
|
3360
|
+
get isTurnInFlight(): boolean;
|
|
3361
|
+
/** Stop the planner turn in flight; it settles as stopped. False when none is. */
|
|
3362
|
+
stopTurn(by?: 'force_send'): boolean;
|
|
3363
|
+
/**
|
|
3364
|
+
* Drop the planner turn in flight together with the plan it was answering:
|
|
3365
|
+
* it is stopped, whatever it settles is discarded, and the conversation is
|
|
3366
|
+
* free at once rather than when the abandoned turn finishes unwinding.
|
|
3367
|
+
*/
|
|
3368
|
+
abandonTurn(): void;
|
|
3369
|
+
/**
|
|
3370
|
+
* Free the conversation from a turn that did not end when stopped — a hung
|
|
3371
|
+
* wake-up (ADR-0029). Unlike {@link abandonTurn} the plan stays, so the turn
|
|
3372
|
+
* is forgotten as its failure would have been: nothing it held lands, and
|
|
3373
|
+
* what it would have told the planner is told to the next turn.
|
|
3374
|
+
*/
|
|
3375
|
+
abandonHungTurn(): void;
|
|
3376
|
+
/**
|
|
3377
|
+
* Let the live turn go. A user turn's end is announced now, and it says
|
|
3378
|
+
* nothing after — not its stream, not its held edit, not its end again —
|
|
3379
|
+
* since it may settle at any time later: the API services restart nothing,
|
|
3380
|
+
* and a hung call settles whenever it settles, while the next turn runs.
|
|
3381
|
+
*/
|
|
3382
|
+
private drop;
|
|
3383
|
+
/**
|
|
3384
|
+
* Run planner work that is not a conversation turn — one-shot plan
|
|
3385
|
+
* generation — as the planner turn, so it is stopped, refused and superseded
|
|
3386
|
+
* like one. It starts a fresh plan, so it supersedes the turn in flight.
|
|
3387
|
+
*/
|
|
3388
|
+
hold<T>(signal: AbortSignal | undefined, body: (turn: PlannerTurn) => Promise<T>): Promise<T>;
|
|
3389
|
+
/**
|
|
3390
|
+
* Take the conversation for a new planner turn. A caller's own signal stops
|
|
3391
|
+
* the turn too; the relay is detached on release because a caller may reuse
|
|
3392
|
+
* that signal, and a stale listener would stop the next turn with it.
|
|
3393
|
+
*/
|
|
3394
|
+
private claim;
|
|
3395
|
+
/** The user turn being answered, for what the host raises during it — an approval the turn's research asks for. */
|
|
3396
|
+
get currentTurnId(): string | undefined;
|
|
3397
|
+
/** The open turn's wake-up rules, when the supervisor is answering it rather than the user's message. */
|
|
3398
|
+
get currentWakeUp(): WakeUpTurn | undefined;
|
|
3399
|
+
/** A skill the planner loaded in the open turn, held with the turn; false when none is open to take it. */
|
|
3400
|
+
recordPlannerSkill(skill: SkillLoad): boolean;
|
|
3401
|
+
/**
|
|
3402
|
+
* Hand the open turn a plan (already validated) or a task edit. The turn
|
|
3403
|
+
* settles with it exactly as with the same plan or ops parsed out of its
|
|
3404
|
+
* reply, and it wins over any envelope that reply carries. A later
|
|
3405
|
+
* submission replaces an earlier one. False when no user turn is open.
|
|
3406
|
+
*/
|
|
3407
|
+
submit(submission: PlannerSubmission): boolean;
|
|
3408
|
+
/**
|
|
3409
|
+
* Tell surfaces what the turn's edit would leave, from the same applier that
|
|
3410
|
+
* lands it, so an edit that would not apply shows nothing. It stays shown
|
|
3411
|
+
* from the moment it is handed in until it lands, is refused, or the turn
|
|
3412
|
+
* ends: the backend's reply claims it, but it has not landed by then.
|
|
3413
|
+
*/
|
|
3414
|
+
private showHeld;
|
|
3415
|
+
private clearHeld;
|
|
3416
|
+
/**
|
|
3417
|
+
* The edit the open turn already holds, for a further edit in the same reply
|
|
3418
|
+
* to join rather than replace. Empty when it holds nothing; null when it holds
|
|
3419
|
+
* a whole plan, which an edit cannot be layered on.
|
|
3420
|
+
*/
|
|
3421
|
+
pendingOps(): TaskOp[] | null;
|
|
3422
|
+
/**
|
|
3423
|
+
* A read the planner makes through a tool. It spends the same per-turn budget
|
|
3424
|
+
* as an envelope read, so a planner cannot read more by switching channels:
|
|
3425
|
+
* past the soft limit, or on a repeated question, the answer still comes but
|
|
3426
|
+
* is told to land the turn; at the hard limit it is refused. `signature`
|
|
3427
|
+
* names the question, for the repeat check.
|
|
3428
|
+
*/
|
|
3429
|
+
read<T>(signature: string, answer: () => Promise<T>): Promise<ToolRead<T>>;
|
|
3430
|
+
/** Whether the model still holds this conversation in memory. */
|
|
3431
|
+
get isActive(): boolean;
|
|
3432
|
+
/** Call inside the host's mutation ritual. */
|
|
3433
|
+
append(role: ConversationMessage['role'], content: string, opts?: {
|
|
3434
|
+
timestamp?: string;
|
|
3435
|
+
kind?: ConversationMessage['kind'];
|
|
3436
|
+
runs?: ReplyRun[];
|
|
3437
|
+
actor?: ConversationMessage['actor'];
|
|
3438
|
+
}): void;
|
|
3439
|
+
private appendEntries;
|
|
3440
|
+
/** Swap the whole transcript. The live context no longer matches it, so pair with {@link reset}. */
|
|
3441
|
+
replace(messages: readonly ConversationMessage[]): void;
|
|
3442
|
+
/**
|
|
3443
|
+
* The plan scope this conversation served is gone: drop the live model
|
|
3444
|
+
* context, and what the planner was told outside the transcript in it (see
|
|
3445
|
+
* {@link PlannerBriefing}). Idempotent; the harness backend holds an OS
|
|
3446
|
+
* process here, so callers never gate it on {@link isActive}.
|
|
3447
|
+
*/
|
|
3448
|
+
reset(): void;
|
|
3449
|
+
/** A run update for the planner's next turn, whoever opens it (ADR-0029). */
|
|
3450
|
+
tellRunUpdate(text: string): void;
|
|
3451
|
+
/** Drop the live model context alone: the next turn replays from the transcript. */
|
|
3452
|
+
private dropContext;
|
|
3453
|
+
/**
|
|
3454
|
+
* The user messages a rewind may land before. The opening message is the
|
|
3455
|
+
* goal: cutting it leaves a conversation about nothing, which is a new
|
|
3456
|
+
* session, not a rewind. After a compaction the summary entry, always
|
|
3457
|
+
* first, plays the goal's part: what it replaced is gone, so a rewind stops
|
|
3458
|
+
* at it.
|
|
3459
|
+
*/
|
|
3460
|
+
rewindTargets(): RewindTarget[];
|
|
3461
|
+
/**
|
|
3462
|
+
* Replace the transcript with a summary of it, keeping the last two
|
|
3463
|
+
* exchanges as they were, and drop the live context so the next message
|
|
3464
|
+
* replays from the shorter record.
|
|
3465
|
+
*
|
|
3466
|
+
* The summary is one hidden turn through whichever planner is configured —
|
|
3467
|
+
* on the live context when it still matches, replayed from the transcript
|
|
3468
|
+
* when not — so the compaction is the same for a vendor API and a harness
|
|
3469
|
+
* agent. Whatever the turn emits besides the summary is discarded, ops
|
|
3470
|
+
* included: it condenses the conversation, never the plan. Nothing is
|
|
3471
|
+
* written until the summary is in hand, so a failed or stopped turn leaves
|
|
3472
|
+
* the transcript exactly as it was.
|
|
3473
|
+
*/
|
|
3474
|
+
compact(signal?: AbortSignal): Promise<ConversationCompaction>;
|
|
3475
|
+
private summarize;
|
|
3476
|
+
/**
|
|
3477
|
+
* A copy of the dialogue record for a forked session to carry. Refused
|
|
3478
|
+
* mid-turn: the copy would hold the user's message without the reply to it.
|
|
3479
|
+
* The live context is not part of it — the fork replays from this record on
|
|
3480
|
+
* its first turn, like any adopted session.
|
|
3481
|
+
*/
|
|
3482
|
+
clone(): ForkedDialogue;
|
|
3483
|
+
/**
|
|
3484
|
+
* A copy of the dialogue as it stood just before the user message at
|
|
3485
|
+
* `index` (a position from {@link rewindTargets}) — what a rewind forks
|
|
3486
|
+
* from — plus that message's full text, for a surface to offer back. This
|
|
3487
|
+
* conversation is not touched. The research trace is cut at the same point
|
|
3488
|
+
* by time: its entries carry no link to the message that caused them.
|
|
3489
|
+
*/
|
|
3490
|
+
cloneBefore(index: number): RewoundDialogue;
|
|
3491
|
+
/**
|
|
3492
|
+
* What the skill check of a landed plan or edit warned about, kept in the
|
|
3493
|
+
* transcript so every surface shows it, on reload too, and not only the
|
|
3494
|
+
* planner that may have read it in a tool result. Call inside the host's
|
|
3495
|
+
* mutation ritual, so the broadcast plan carries it.
|
|
3496
|
+
*/
|
|
3497
|
+
recordSkillWarnings(warnings: readonly string[], timestamp?: string): void;
|
|
3498
|
+
/**
|
|
3499
|
+
* A call at the command seam (ADR-0029), kept where it happened in the
|
|
3500
|
+
* dialogue: a reload shows it, and a planner replayed from the transcript
|
|
3501
|
+
* knows what was started or stopped, and by whom. Call inside the host's
|
|
3502
|
+
* mutation ritual.
|
|
3503
|
+
*/
|
|
3504
|
+
recordExecution(record: ExecutionRecord, text: string): void;
|
|
3505
|
+
/**
|
|
3506
|
+
* Open the conversation on the host's fresh plan: the goal is its first
|
|
3507
|
+
* message. `prepare` (discovery) runs inside the turn, so a stop reaches it.
|
|
3508
|
+
*/
|
|
3509
|
+
start(goal: SkillInvocation, prepare: () => Promise<ConversationOpening>, signal?: AbortSignal): Promise<LegacyPlanState>;
|
|
3510
|
+
/**
|
|
3511
|
+
* Every later user message, to a persisted outcome. A turn that throws lands
|
|
3512
|
+
* nothing: what it held is dropped with it, so session memory never drifts
|
|
3513
|
+
* from disk and the UI.
|
|
3514
|
+
*/
|
|
3515
|
+
reply(message: string | SkillInvocation, options?: ReplyOptions): Promise<LegacyPlanState>;
|
|
3516
|
+
private replyTurn;
|
|
3517
|
+
/**
|
|
3518
|
+
* A wake-up turn lands only a real answer. Stopped — by the user, or by its
|
|
3519
|
+
* timeout — it lands nothing, not the fragment a backend hands back; a
|
|
3520
|
+
* failure is thrown rather than shown, for the supervisor to retry
|
|
3521
|
+
* (ADR-0029).
|
|
3522
|
+
*/
|
|
3523
|
+
private assertWakeUpAnswered;
|
|
3524
|
+
/**
|
|
3525
|
+
* A turn that ended on the planner's usage limit lands nothing: what it held
|
|
3526
|
+
* is dropped, its failure is kept in its place, and the limit's wait is armed
|
|
3527
|
+
* in the same save. Only a turn Ordewell started gets here, so a limit is
|
|
3528
|
+
* never acted on for a planner nobody asked anything of.
|
|
3529
|
+
*/
|
|
3530
|
+
private assertNotLimited;
|
|
3531
|
+
/**
|
|
3532
|
+
* A failure kept in the transcript, drawn as one on reload too, and never
|
|
3533
|
+
* replayed to the planner. Stamped after the newest entry: surfaces take
|
|
3534
|
+
* only entries newer than the last they synced.
|
|
3535
|
+
*/
|
|
3536
|
+
private recordFailure;
|
|
3537
|
+
private assertIdle;
|
|
3538
|
+
/** Refuse to land a turn that no longer belongs here: it was abandoned, or its plan is no longer the host's. */
|
|
3539
|
+
private assertCurrent;
|
|
3540
|
+
/**
|
|
3541
|
+
* {@link assertCurrent} on the far side of an await that precedes a plan
|
|
3542
|
+
* mutation: the skill check waits on git, and a stop, rewind or session
|
|
3543
|
+
* switch in that window drops the edit instead of landing it on whatever
|
|
3544
|
+
* plan is current by then. A stop counts here because nothing has landed
|
|
3545
|
+
* yet — the user asked for the turn to end before it changed the plan.
|
|
3546
|
+
*/
|
|
3547
|
+
private assertStillCurrent;
|
|
3548
|
+
/**
|
|
3549
|
+
* A live conversation is only safe to continue in-place when it also
|
|
3550
|
+
* matches the model/effort configured right now — a harness planner's
|
|
3551
|
+
* running process was spawned with the old one baked in and cannot pick up
|
|
3552
|
+
* a switch (ADR-0009). Otherwise the turn resumes from the transcript.
|
|
3553
|
+
*/
|
|
3554
|
+
private canContinueLive;
|
|
3555
|
+
/**
|
|
3556
|
+
* Bracket one user turn with its start and end, under an id minted here:
|
|
3557
|
+
* the turn is where the stream a surface draws begins and ends, and only the
|
|
3558
|
+
* conversation sees all of it — every backend call, read and retry.
|
|
3559
|
+
*/
|
|
3560
|
+
private userTurn;
|
|
3561
|
+
private requirePlan;
|
|
3562
|
+
/** The message as typed, then one entry per skill it loaded, sharing its timestamp so a reload keeps them together. */
|
|
3563
|
+
private userEntries;
|
|
3564
|
+
private userPrompt;
|
|
3565
|
+
/** Hold what the turn writes until it lands, each entry where the transcript stands now; see {@link TurnWrites}. */
|
|
3566
|
+
private holdWrites;
|
|
3567
|
+
/**
|
|
3568
|
+
* The one mutation that lands a turn's outcome (ADR-0029). What the turn held
|
|
3569
|
+
* goes in first — each entry where it belongs, ahead of what was written
|
|
3570
|
+
* after it, then the research and the subagents — so the transcript reads in
|
|
3571
|
+
* the order it happened and the inputs leave the plan's pending turns in the
|
|
3572
|
+
* save that carried their answer. `outcome` makes the turn's change to the
|
|
3573
|
+
* plan and returns what the turn said, which is written, then sent in that
|
|
3574
|
+
* order; `announce` follows it on every surface. The turn's briefing is told
|
|
3575
|
+
* unless the planner gave no answer (`unheard`): a failure, or a stop.
|
|
3576
|
+
*/
|
|
3577
|
+
private land;
|
|
3578
|
+
private writeHeld;
|
|
3579
|
+
private recordResearch;
|
|
3580
|
+
/**
|
|
3581
|
+
* The opening a replay starts under, read fresh, the live context dropped
|
|
3582
|
+
* first; what it states of autorun and the runners is what the planner is
|
|
3583
|
+
* told by it. A turn abandoned while the opening was read stops here: the
|
|
3584
|
+
* backend may be its successor's by now.
|
|
3585
|
+
*/
|
|
3586
|
+
private reopen;
|
|
3587
|
+
/**
|
|
3588
|
+
* Reopen the model context from the transcript — because the in-memory one
|
|
3589
|
+
* is gone (session reload, extension restart), or because it is stale
|
|
3590
|
+
* against the planner config now in effect. No LLM call happens for the
|
|
3591
|
+
* replayed turns; the first call is the one the user's message opens.
|
|
3592
|
+
*/
|
|
3593
|
+
private resume;
|
|
3594
|
+
/**
|
|
3595
|
+
* Answer every read the planner emits until it says something else.
|
|
3596
|
+
*
|
|
3597
|
+
* The channel is a text envelope rather than a registered tool because the
|
|
3598
|
+
* protocol has to be identical on both planner backends (ADR-0009): Ordewell
|
|
3599
|
+
* owns a tool loop only in the API case, and a harness planner running as a
|
|
3600
|
+
* coding-agent subprocess can only be reached this way.
|
|
3601
|
+
*
|
|
3602
|
+
* Draining here — outside {@link repairLoop} — is what keeps reads free of
|
|
3603
|
+
* the repair budget. A planner that looks a task up and *then* fumbles its
|
|
3604
|
+
* ops JSON still gets its two corrective retries; charging it for the read
|
|
3605
|
+
* would cost it the chance to fix the edit.
|
|
3606
|
+
*/
|
|
3607
|
+
private drainTaskQueries;
|
|
3608
|
+
/**
|
|
3609
|
+
* A call cut off by something other than the turn's own stop failed: what it
|
|
3610
|
+
* hands back is a fragment of an answer, and the turn keeps what it was sent,
|
|
3611
|
+
* as any failure does. Nothing of Ordewell's resets a backend under a turn on
|
|
3612
|
+
* the same plan (see `PlannerBackend`); one that changes the plan abandons the turn.
|
|
3613
|
+
*/
|
|
3614
|
+
private failedIfCutOff;
|
|
3615
|
+
/**
|
|
3616
|
+
* The backend's answer, with what was handed in while it ran standing in
|
|
3617
|
+
* for whatever its reply carried. Taken once: a corrective re-send after a
|
|
3618
|
+
* rejected edit must not settle on the same submission again. A stopped
|
|
3619
|
+
* turn commits nothing, the same as a stopped reply with a plan in it.
|
|
3620
|
+
*/
|
|
3621
|
+
private claimSubmission;
|
|
3622
|
+
/**
|
|
3623
|
+
* Render one read out of live state. Never persisted to the transcript: the
|
|
3624
|
+
* detail is context for the planner's next reply, and re-sending it on every
|
|
3625
|
+
* later turn is exactly the cost this channel exists to avoid.
|
|
3626
|
+
*/
|
|
3627
|
+
private taskQueryAnswer;
|
|
3628
|
+
/**
|
|
3629
|
+
* Drive a planner turn, its reads already drained, to a persisted, broadcast
|
|
3630
|
+
* outcome. Task edits apply atomically; validation failures are fed back to
|
|
3631
|
+
* the model for up to 2 silent retries, then surfaced as a message with the
|
|
3632
|
+
* plan untouched. The first turn and every later turn route through here —
|
|
3633
|
+
* one path, not two.
|
|
3634
|
+
*/
|
|
3635
|
+
private settle;
|
|
3636
|
+
/** A name not found at submit only warns: a task the attaching one depends on may create it. */
|
|
3637
|
+
private warnSkills;
|
|
3638
|
+
/** Validate + commit a task_ops turn atomically. Returns the errors on rejection (plan untouched). */
|
|
3639
|
+
private applyTaskOps;
|
|
3640
|
+
/** The edit was refused as it landed: what it showed is not coming. */
|
|
3641
|
+
private refusedHeld;
|
|
3642
|
+
/** Commit a settled (non-task_ops) turn: a whole plan, or what it said. */
|
|
3643
|
+
private commit;
|
|
3644
|
+
/**
|
|
3645
|
+
* The runs a reply is saved and sent with (ADR-0017 A1): the narration the
|
|
3646
|
+
* turn kept from calls it did not settle on, then the reply's own. None from
|
|
3647
|
+
* a backend that reports no runs, whose reply then stands as one message.
|
|
3648
|
+
*/
|
|
3649
|
+
private shown;
|
|
3650
|
+
/** What a plan or edit turn said beside what it landed: its reply, or an envelope's narration. */
|
|
3651
|
+
private said;
|
|
3652
|
+
/**
|
|
3653
|
+
* The catalog the planner may actually draw from — model ids and task-mode
|
|
3654
|
+
* ids, per runner in the plan — emitted on EVERY turn, before any plan exists
|
|
3655
|
+
* or after. The system prompt shows this once at conversation start; a long
|
|
3656
|
+
* clarifying conversation outlives that single showing and the planner
|
|
3657
|
+
* starts misquoting it, so this re-states it per turn instead.
|
|
3658
|
+
*
|
|
3659
|
+
* The host's catalog is allowlist-filtered, so a restricted allowlist stays a
|
|
3660
|
+
* hard bound on every turn, and reads the plan's runners live, so a runner
|
|
3661
|
+
* admitted mid-session by a retarget is shown like every other. A planner
|
|
3662
|
+
* with Ordewell's tools gets a reminder to read the catalog instead.
|
|
3663
|
+
*/
|
|
3664
|
+
private catalogBlock;
|
|
3665
|
+
/**
|
|
3666
|
+
* The "you are here" block for post-plan chat: current tasks with stable
|
|
3667
|
+
* references, plus the read and edit protocols. Injected per turn (never
|
|
3668
|
+
* persisted) so the model always sees live statuses — including which tasks
|
|
3669
|
+
* are locked by a running execution.
|
|
3670
|
+
*/
|
|
3671
|
+
private planContextBlock;
|
|
3672
|
+
/** One line per task, subtasks indented under their parent, with stable references — null until the plan has tasks. */
|
|
3673
|
+
private currentPlanLines;
|
|
3674
|
+
}
|
|
3675
|
+
|
|
3676
|
+
/**
|
|
3677
|
+
* Task references: the one grammar for naming a task in a plan, both ways.
|
|
2332
3678
|
* A label is rendered from a task's order under its parent ("2", "2.1"), and
|
|
2333
3679
|
* a reference written by anyone — the planner, a tool call, a command — is
|
|
2334
|
-
* resolved back against the plan tree. Pure
|
|
3680
|
+
* resolved back against the plan tree. Pure so a
|
|
2335
3681
|
* browser-bundled surface can use it through `@ordewell/core/plan-utils`.
|
|
2336
3682
|
*/
|
|
2337
3683
|
/** The shape a reference resolves over: the plan's task tree, or a surface's projection of it. */
|
|
2338
|
-
|
|
2339
|
-
id: string;
|
|
2340
|
-
order: number;
|
|
2341
|
-
title: string;
|
|
2342
|
-
subtasks?: readonly T[];
|
|
2343
|
-
}
|
|
3684
|
+
type RefTask<T> = TreeTask<T>;
|
|
2344
3685
|
/**
|
|
2345
3686
|
* The parent a label extends. A label is the full path ("3.2.1"), so a parent
|
|
2346
3687
|
* below the top level carries the label it was itself reached by; without one
|
|
@@ -2383,6 +3724,100 @@ declare function titledTaskRefOf<T extends RefTask<T>>(tasks: readonly T[], id:
|
|
|
2383
3724
|
* and a subtask is reachable only by its dotted path ("#2.1", "#2.1.3").
|
|
2384
3725
|
*/
|
|
2385
3726
|
declare function resolveTaskRef<T extends RefTask<T>>(tasks: readonly T[], ref: string | number): T | undefined;
|
|
3727
|
+
/**
|
|
3728
|
+
* A reference as the user types one: {@link resolveTaskRef}, then a unique
|
|
3729
|
+
* prefix of a task's id at any depth. Only the user's: a model's reference
|
|
3730
|
+
* that names no task is refused, never matched to the one task a fragment of
|
|
3731
|
+
* it happens to fit.
|
|
3732
|
+
*/
|
|
3733
|
+
declare function resolveUserTaskRef<T extends RefTask<T>>(tasks: readonly T[], ref: string | number): T | undefined;
|
|
3734
|
+
|
|
3735
|
+
interface SessionMeta {
|
|
3736
|
+
id: string;
|
|
3737
|
+
goal: string;
|
|
3738
|
+
runners: RunnerId[];
|
|
3739
|
+
taskCount: number;
|
|
3740
|
+
status: PlanStatus;
|
|
3741
|
+
createdAt: string;
|
|
3742
|
+
updatedAt: string;
|
|
3743
|
+
}
|
|
3744
|
+
interface SessionData {
|
|
3745
|
+
meta: SessionMeta;
|
|
3746
|
+
/** The full plan state — including conversationHistory, prdMarkdown, and queuedMessages. */
|
|
3747
|
+
plan: LegacyPlanState;
|
|
3748
|
+
}
|
|
3749
|
+
|
|
3750
|
+
/**
|
|
3751
|
+
* The bridge between "core needs an answer" and "a human somewhere is looking
|
|
3752
|
+
* at a UI". Core cannot prompt: the human may be at a browser, a TUI, a CLI
|
|
3753
|
+
* stream, or a VS Code webview, and on the web server they are on the far end
|
|
3754
|
+
* of a socket. So the Session parks a promise here, announces the request
|
|
3755
|
+
* through the normal broadcast seam, and every surface answers through the
|
|
3756
|
+
* same `resolve(id, granted)` call.
|
|
3757
|
+
*
|
|
3758
|
+
* Timeouts are load-bearing rather than defensive: a planner turn that blocks
|
|
3759
|
+
* forever on an unanswered prompt would hang the whole research loop with no
|
|
3760
|
+
* visible cause. On expiry the request resolves to denied and the model gets a
|
|
3761
|
+
* normal, actionable tool result. A task runner's request is the exception
|
|
3762
|
+
* (ADR-0018, A1): the runner is parked on it rather than a research loop, and
|
|
3763
|
+
* it waits for a person — or the supervisor — however long that takes.
|
|
3764
|
+
*/
|
|
3765
|
+
interface PendingApproval {
|
|
3766
|
+
id: string;
|
|
3767
|
+
request: ApprovalRequest;
|
|
3768
|
+
createdAt: string;
|
|
3769
|
+
}
|
|
3770
|
+
interface PendingApprovalsOptions {
|
|
3771
|
+
/** Denies and resolves after this long with no answer. Default 5 minutes. */
|
|
3772
|
+
timeoutMs?: number;
|
|
3773
|
+
/** Announce a new request to the surfaces. */
|
|
3774
|
+
onRequest?: (pending: PendingApproval) => void;
|
|
3775
|
+
/** Announce that a request is no longer actionable (answered or expired). */
|
|
3776
|
+
onSettled?: (id: string, granted: boolean, settled: {
|
|
3777
|
+
request: ApprovalRequest;
|
|
3778
|
+
decision: ApprovalDecision;
|
|
3779
|
+
}) => void;
|
|
3780
|
+
}
|
|
3781
|
+
interface AskOptions {
|
|
3782
|
+
/**
|
|
3783
|
+
* An id the caller already announced the request under. One still in use is
|
|
3784
|
+
* refused — denied at once — rather than overwriting the request that has it.
|
|
3785
|
+
*/
|
|
3786
|
+
id?: string;
|
|
3787
|
+
/** Wait for an answer however long it takes. */
|
|
3788
|
+
noTimeout?: boolean;
|
|
3789
|
+
/**
|
|
3790
|
+
* Given the answer as the request settles, before anything awaiting the
|
|
3791
|
+
* promise runs: a caller about to tear down what asked (a runner being
|
|
3792
|
+
* stopped) must deliver the answer before it goes.
|
|
3793
|
+
*/
|
|
3794
|
+
onDecision?: (decision: ApprovalDecision) => void;
|
|
3795
|
+
}
|
|
3796
|
+
declare class PendingApprovals {
|
|
3797
|
+
private readonly opts;
|
|
3798
|
+
private readonly entries;
|
|
3799
|
+
private readonly listeners;
|
|
3800
|
+
constructor(opts?: PendingApprovalsOptions);
|
|
3801
|
+
/** Hear each new request once it is announced — the supervisor wakes on a runner's (ADR-0029). */
|
|
3802
|
+
listen(listener: (pending: PendingApproval) => void): () => void;
|
|
3803
|
+
/** Park a request and return the promise the approval policy awaits. */
|
|
3804
|
+
ask(request: ApprovalRequest, options?: AskOptions): Promise<boolean>;
|
|
3805
|
+
/** Park a request whose answer is more than yes or no. */
|
|
3806
|
+
decide(request: ApprovalRequest, options?: AskOptions): Promise<ApprovalDecision>;
|
|
3807
|
+
/**
|
|
3808
|
+
* Answer one request. Returns false when the id is unknown or already
|
|
3809
|
+
* settled. "Allow for this task" on a request that did not offer it is a
|
|
3810
|
+
* plain allow: no grant is made that the requester never proposed.
|
|
3811
|
+
*/
|
|
3812
|
+
resolve(id: string, answer: ApprovalAnswer): boolean;
|
|
3813
|
+
/** Everything still awaiting an answer — replayed to a surface that connects late. */
|
|
3814
|
+
outstanding(): PendingApproval[];
|
|
3815
|
+
/**
|
|
3816
|
+
* Deny everything in flight, or only the requests `which` picks. Called on
|
|
3817
|
+
* abort and on session reset.
|
|
3818
|
+
*/
|
|
3819
|
+
clear(which?: (request: ApprovalRequest) => boolean, note?: string): void;
|
|
3820
|
+
}
|
|
2386
3821
|
|
|
2387
3822
|
/**
|
|
2388
3823
|
* Pure, no-imports helper so a browser surface can format a conflict's files
|
|
@@ -2510,6 +3945,368 @@ declare class SettingsService {
|
|
|
2510
3945
|
private persist;
|
|
2511
3946
|
}
|
|
2512
3947
|
|
|
3948
|
+
/** What the preflight reads: the planner choice and whatever it needs to be reachable. */
|
|
3949
|
+
type PlannerReadinessConfig = Pick<IConfig, 'aiProvider' | 'planningModel' | 'openaiCompatibleBaseUrl' | 'getProviderApiKey'>;
|
|
3950
|
+
/**
|
|
3951
|
+
* Why the selected planner cannot plan yet, as data. Each surface words it with
|
|
3952
|
+
* its own command spelling (`describePlannerNotReady`), so a daemon, a chat and
|
|
3953
|
+
* a shell script can all say the same thing without sharing a syntax.
|
|
3954
|
+
*/
|
|
3955
|
+
type PlannerNotReady = {
|
|
3956
|
+
code: 'no_key';
|
|
3957
|
+
provider: AiProvider;
|
|
3958
|
+
label: string;
|
|
3959
|
+
keyEnvVar: string;
|
|
3960
|
+
} | {
|
|
3961
|
+
code: 'no_base_url';
|
|
3962
|
+
provider: AiProvider;
|
|
3963
|
+
label: string;
|
|
3964
|
+
baseUrlEnvVar: string;
|
|
3965
|
+
} | {
|
|
3966
|
+
code: 'no_model';
|
|
3967
|
+
provider: AiProvider;
|
|
3968
|
+
label: string;
|
|
3969
|
+
};
|
|
3970
|
+
/**
|
|
3971
|
+
* What the selected planner still needs before it can plan, or null when it is
|
|
3972
|
+
* ready. There are two ways in (ADR-0009), and only one of them involves a
|
|
3973
|
+
* credential: a harness planner drives a coding agent already installed on the
|
|
3974
|
+
* machine, authenticated by the user's own subscription, so demanding an API
|
|
3975
|
+
* key of it turns a working setup into a wall. Its model is optional too: an
|
|
3976
|
+
* unset id means "the agent's own default".
|
|
3977
|
+
*/
|
|
3978
|
+
declare function plannerNotReady(config: PlannerReadinessConfig): PlannerNotReady | null;
|
|
3979
|
+
/**
|
|
3980
|
+
* `plannerNotReady` judged on a host's own config — the one way every host
|
|
3981
|
+
* builds the preflight's input, so none can hand it a key the selected
|
|
3982
|
+
* planner's transport will not read (a stored OpenRouter key passing for an
|
|
3983
|
+
* OpenAI planner, which then failed inside the transport). Every planner turn
|
|
3984
|
+
* judges through this too, so none can let through what another refuses.
|
|
3985
|
+
*/
|
|
3986
|
+
declare function plannerNotReadyFor(config: Pick<IConfig, 'aiProvider' | 'orchestratorModel' | 'planningModel' | 'openaiCompatibleBaseUrl' | 'getProviderApiKey'>): PlannerNotReady | null;
|
|
3987
|
+
/** How one surface spells the commands a not-ready message points at. */
|
|
3988
|
+
interface PlannerCommandSyntax {
|
|
3989
|
+
pickPlanner: string;
|
|
3990
|
+
setKey(provider: AiProvider): string;
|
|
3991
|
+
setModel: string;
|
|
3992
|
+
}
|
|
3993
|
+
declare const TERMINAL_UI_COMMANDS: PlannerCommandSyntax;
|
|
3994
|
+
/** The VS Code chat shares the terminal's slash commands, except that its `/model` needs `set`. */
|
|
3995
|
+
declare const CHAT_COMMANDS: PlannerCommandSyntax;
|
|
3996
|
+
declare const HEADLESS_CLI_COMMANDS: PlannerCommandSyntax;
|
|
3997
|
+
declare function describePlannerNotReady(reason: PlannerNotReady, commands: PlannerCommandSyntax): string;
|
|
3998
|
+
/**
|
|
3999
|
+
* A planner turn refused before it began. Carries the reason as data so a
|
|
4000
|
+
* client that spells commands differently (the headless CLI) can re-word it;
|
|
4001
|
+
* the message is the terminal UI's spelling, which is what most clients show.
|
|
4002
|
+
*/
|
|
4003
|
+
declare class PlannerNotReadyError extends Error {
|
|
4004
|
+
readonly reason: PlannerNotReady;
|
|
4005
|
+
constructor(reason: PlannerNotReady);
|
|
4006
|
+
}
|
|
4007
|
+
|
|
4008
|
+
/** What the user last picked for one planner backend. */
|
|
4009
|
+
interface PlannerModelChoice {
|
|
4010
|
+
model: string;
|
|
4011
|
+
effort?: string;
|
|
4012
|
+
}
|
|
4013
|
+
/**
|
|
4014
|
+
* A candidate model as this planner's catalog lists it. Deliberately plain
|
|
4015
|
+
* data: a harness planner's catalog is its runner's own model list and a
|
|
4016
|
+
* vendor's is a picker list (ADR-0009), and the rule below is the same for
|
|
4017
|
+
* both — so the caller normalizes, and this module never reaches for a
|
|
4018
|
+
* ModelResolver, a config or the network.
|
|
4019
|
+
*/
|
|
4020
|
+
interface PlannerModelCandidate {
|
|
4021
|
+
id: string;
|
|
4022
|
+
variants?: {
|
|
4023
|
+
id: string;
|
|
4024
|
+
}[];
|
|
4025
|
+
}
|
|
4026
|
+
interface PlannerModelRecall {
|
|
4027
|
+
model: string;
|
|
4028
|
+
effort: string;
|
|
4029
|
+
source: 'remembered' | 'catalog-default' | 'none';
|
|
4030
|
+
}
|
|
4031
|
+
/** The slice of `SettingsService` this needs, so tests can pass a plain object. */
|
|
4032
|
+
interface PlannerModelStore {
|
|
4033
|
+
getPlannerModel(provider: string): PlannerModelChoice | undefined;
|
|
4034
|
+
setPlannerModel(provider: string, entry: PlannerModelChoice | undefined): void;
|
|
4035
|
+
}
|
|
4036
|
+
/**
|
|
4037
|
+
* Remembers, per planner backend, the model the user last chose for it.
|
|
4038
|
+
*
|
|
4039
|
+
* Switching the planner has to clear the model — an id is scoped to the agent
|
|
4040
|
+
* that listed it, so an OpenRouter slug handed to Claude Code points it at
|
|
4041
|
+
* something it cannot spawn. That clear is correct and stays; what was missing
|
|
4042
|
+
* is that switching *back* then forced a re-pick of a model the user had
|
|
4043
|
+
* already chosen. The rule itself is `selectPlanner`'s; this is the storage
|
|
4044
|
+
* the surfaces that keep their memory in the settings file hand it.
|
|
4045
|
+
*/
|
|
4046
|
+
declare class PlannerModelMemory {
|
|
4047
|
+
private store;
|
|
4048
|
+
constructor(store: PlannerModelStore);
|
|
4049
|
+
/** Record what the user picked for `provider`; a blank model forgets it instead. */
|
|
4050
|
+
remember(provider: string, model: string, effort?: string): void;
|
|
4051
|
+
getPlannerModel(provider: string): PlannerModelChoice | undefined;
|
|
4052
|
+
/**
|
|
4053
|
+
* What the model and effort should become when `provider` takes over the
|
|
4054
|
+
* planner, given the models it actually serves. The `source` is what the
|
|
4055
|
+
* three surfaces word their notice from — restored, defaulted, or nothing.
|
|
4056
|
+
*/
|
|
4057
|
+
recall(provider: string, catalog: PlannerModelCandidate[]): PlannerModelRecall;
|
|
4058
|
+
}
|
|
4059
|
+
|
|
4060
|
+
/**
|
|
4061
|
+
* The daemon contract: every HTTP body the local daemon sends, in one place.
|
|
4062
|
+
*
|
|
4063
|
+
* The daemon's routes build their bodies against these types and the CLI's
|
|
4064
|
+
* `ApiClient` reads them back, so a field added on one side is a compile error
|
|
4065
|
+
* on the other. Before this there were three hand-written copies — the routes'
|
|
4066
|
+
* inline `c.json`, the client's per-method generics, and the TUI's slice of the
|
|
4067
|
+
* client — and they had drifted (the client declared a `headless` field the
|
|
4068
|
+
* daemon never sent, and typed `GET /sessions/:id`'s plan as the wrong shape).
|
|
4069
|
+
*
|
|
4070
|
+
* Types only. Core is the one package both the daemon and its clients already
|
|
4071
|
+
* depend on; importing from the daemon would pull its runtime into the CLI.
|
|
4072
|
+
*/
|
|
4073
|
+
/**
|
|
4074
|
+
* Why a request was refused, as a stable code a client switches on instead of
|
|
4075
|
+
* reading the message. Absent on a refusal nobody needs to tell apart — a
|
|
4076
|
+
* missing field, say — where the message is all there is.
|
|
4077
|
+
*/
|
|
4078
|
+
type DaemonErrorCode = 'session_not_found' | 'task_not_found' | 'no_plan' | 'already_executing' | 'workspace_not_found' | 'workspace_not_a_project' | 'conversation_busy'
|
|
4079
|
+
/** The user stopped the planner turn: the turn ended as asked, so a client stays quiet. */
|
|
4080
|
+
| 'planner_turn_stopped'
|
|
4081
|
+
/** The planner turn settled after its plan was dropped; nothing it produced was kept. */
|
|
4082
|
+
| 'planner_turn_discarded'
|
|
4083
|
+
/** The selected planner cannot plan yet (no key, no base URL, no model); `plannerNotReady` says which. */
|
|
4084
|
+
| 'planner_not_ready'
|
|
4085
|
+
/** The request asked for something the plan, task or conversation does not allow. */
|
|
4086
|
+
| 'refused' | 'checkpoint_not_waiting' | 'approval_not_outstanding';
|
|
4087
|
+
/** The body of every non-2xx answer a route gives. */
|
|
4088
|
+
interface ErrorBody {
|
|
4089
|
+
error: string;
|
|
4090
|
+
code?: DaemonErrorCode;
|
|
4091
|
+
/** Set with `workspace_not_a_project`: the directory the daemon refused, so a client can offer to initialize it. */
|
|
4092
|
+
workspace?: string;
|
|
4093
|
+
/** Set with `planner_not_ready`: the reason as data, so a client can word it in its own commands. */
|
|
4094
|
+
plannerNotReady?: PlannerNotReady;
|
|
4095
|
+
}
|
|
4096
|
+
/**
|
|
4097
|
+
* What the command seam said of a control the user pressed (ADR-0029), for the
|
|
4098
|
+
* surface to show as it is, so each words a control alike. Absent from a daemon
|
|
4099
|
+
* older than the field, and from a call that never reached the seam.
|
|
4100
|
+
*/
|
|
4101
|
+
interface Worded {
|
|
4102
|
+
message?: string;
|
|
4103
|
+
}
|
|
4104
|
+
interface OkResponse extends Worded {
|
|
4105
|
+
ok: boolean;
|
|
4106
|
+
}
|
|
4107
|
+
/** A task start's answer: `queued` says why it did not start now, when the task waits for its dependencies instead. */
|
|
4108
|
+
interface TaskStartResponse extends OkResponse {
|
|
4109
|
+
queued?: string;
|
|
4110
|
+
}
|
|
4111
|
+
/**
|
|
4112
|
+
* A plan as the planner routes return it: the session's plan state, not the
|
|
4113
|
+
* `SerializedPlan` a websocket `plan_generated` carries. The two differ —
|
|
4114
|
+
* this one holds raw tasks and the dialogue's bookkeeping — and a client that
|
|
4115
|
+
* reads either reads only the fields it needs. Skill bodies stay in the
|
|
4116
|
+
* session: loads and attempt snapshots arrive as notices.
|
|
4117
|
+
*/
|
|
4118
|
+
interface PlanResponse {
|
|
4119
|
+
plan: SurfacePlan;
|
|
4120
|
+
}
|
|
4121
|
+
/** `POST /plans/:id/review/approve`: the plan the user signed off, as it now runs. */
|
|
4122
|
+
interface ApproveReviewResponse extends PlanResponse, Worded {
|
|
4123
|
+
}
|
|
4124
|
+
/** `POST /plans/:id/resolve-conflict`: `null` when the session had no conflict to hand a resolver task. */
|
|
4125
|
+
interface ResolveConflictResponse {
|
|
4126
|
+
plan: SurfacePlan | null;
|
|
4127
|
+
}
|
|
4128
|
+
/** What `GET /api/models` returns, and what the pool's provider discovery produces. */
|
|
4129
|
+
interface ModelsResponse {
|
|
4130
|
+
models: DiscoveredModel[];
|
|
4131
|
+
modelsByRunner: Record<string, DiscoveredModel[]>;
|
|
4132
|
+
/** Each runner's manifest modes, so a surface can offer a per-task mode picker. */
|
|
4133
|
+
modesByRunner: Record<string, RunnerModeInfo[]>;
|
|
4134
|
+
orchestratorModel: string;
|
|
4135
|
+
providers: AiProvider[];
|
|
4136
|
+
/** Full cross-provider catalog for the orchestrator (planner) model picker. */
|
|
4137
|
+
orchestratorModels: OrchestratorOption[];
|
|
4138
|
+
/** Per-provider catalog-fetch failures, keyed by provider id. */
|
|
4139
|
+
providerErrors: Record<string, string>;
|
|
4140
|
+
}
|
|
4141
|
+
/** One-shot planning answers in the phase-tagged shape a saved session is read back in. */
|
|
4142
|
+
interface GeneratePlanResponse extends Pick<ModelsResponse, 'models' | 'modelsByRunner'> {
|
|
4143
|
+
plan: SurfacePlanState;
|
|
4144
|
+
/** The host notes the plan's transcript records, a skill check's warnings among them, which the phase-tagged plan does not carry. */
|
|
4145
|
+
notes?: string[];
|
|
4146
|
+
}
|
|
4147
|
+
interface ExecuteResponse extends Worded {
|
|
4148
|
+
status: 'running';
|
|
4149
|
+
}
|
|
4150
|
+
interface StopResponse extends Worded {
|
|
4151
|
+
status: 'stopped';
|
|
4152
|
+
}
|
|
4153
|
+
interface CancelPlanningResponse {
|
|
4154
|
+
cancelled: boolean;
|
|
4155
|
+
}
|
|
4156
|
+
/** `POST …/resume/cancel` (cancelled is true: a task with no resume scheduled is refused) and `POST …/converse/resend/cancel` (false when nothing waited). */
|
|
4157
|
+
interface CancelResumeResponse extends Worded {
|
|
4158
|
+
cancelled: boolean;
|
|
4159
|
+
}
|
|
4160
|
+
interface TaskMessageResponse {
|
|
4161
|
+
id: string;
|
|
4162
|
+
}
|
|
4163
|
+
interface ForceSendResponse extends Worded {
|
|
4164
|
+
sent: boolean;
|
|
4165
|
+
}
|
|
4166
|
+
interface RemoveMessageResponse extends Worded {
|
|
4167
|
+
removed: boolean;
|
|
4168
|
+
}
|
|
4169
|
+
interface MergeGateEntry {
|
|
4170
|
+
id: string;
|
|
4171
|
+
order: number;
|
|
4172
|
+
title: string;
|
|
4173
|
+
}
|
|
4174
|
+
interface MergeGateResponse {
|
|
4175
|
+
mergeGate: MergeGateEntry[];
|
|
4176
|
+
}
|
|
4177
|
+
interface IsolationDiffResponse {
|
|
4178
|
+
diff: string;
|
|
4179
|
+
}
|
|
4180
|
+
/** A merge that conflicts or is blocked is an outcome, not a refusal, so it answers 200 with the result. */
|
|
4181
|
+
type IsolationMergeResponse = IsolationMergeResult;
|
|
4182
|
+
/** A fork the daemon has already adopted. */
|
|
4183
|
+
interface ConversationForkResponse {
|
|
4184
|
+
sessionId: string;
|
|
4185
|
+
goal: string;
|
|
4186
|
+
plan: SurfacePlan;
|
|
4187
|
+
}
|
|
4188
|
+
/** A fork made by a rewind, with the full text of the message it was made just before. */
|
|
4189
|
+
interface ConversationRewindResponse extends ConversationForkResponse {
|
|
4190
|
+
rewoundMessage: string;
|
|
4191
|
+
}
|
|
4192
|
+
interface RewindTargetsResponse {
|
|
4193
|
+
targets: RewindTarget[];
|
|
4194
|
+
}
|
|
4195
|
+
interface ConversationCompactResponse extends ConversationCompaction, PlanResponse {
|
|
4196
|
+
}
|
|
4197
|
+
interface PrdResponse {
|
|
4198
|
+
prdMarkdown: string;
|
|
4199
|
+
}
|
|
4200
|
+
interface RunnerEntry {
|
|
4201
|
+
id: string;
|
|
4202
|
+
name: string;
|
|
4203
|
+
enabled: boolean;
|
|
4204
|
+
}
|
|
4205
|
+
interface RunnersResponse {
|
|
4206
|
+
runners: RunnerEntry[];
|
|
4207
|
+
orchestratorModel: string;
|
|
4208
|
+
}
|
|
4209
|
+
/** Sessions: a saved one comes off its file, a live one from the pool's store. */
|
|
4210
|
+
interface SessionResponse {
|
|
4211
|
+
meta: SessionMeta;
|
|
4212
|
+
plan: SurfacePlanState;
|
|
4213
|
+
/**
|
|
4214
|
+
* The live session's run as `Session.runSnapshot()` holds it, isolation
|
|
4215
|
+
* marks and handoff included. Absent for a session nobody holds: the saved
|
|
4216
|
+
* file has no run to speak of, and a surface does not rebuild one from it.
|
|
4217
|
+
*/
|
|
4218
|
+
run?: RunView;
|
|
4219
|
+
}
|
|
4220
|
+
interface AdoptSessionResponse extends OkResponse {
|
|
4221
|
+
plan: SurfacePlan;
|
|
4222
|
+
goal: string;
|
|
4223
|
+
/** The adopted session's run, as {@link SessionResponse.run}. */
|
|
4224
|
+
run?: RunView;
|
|
4225
|
+
}
|
|
4226
|
+
interface TaskLogAttemptsResponse {
|
|
4227
|
+
/** Captured with the synchronous listing, including an empty listing. */
|
|
4228
|
+
position?: StreamStamp;
|
|
4229
|
+
attempts: number[];
|
|
4230
|
+
}
|
|
4231
|
+
interface TaskLogResponse {
|
|
4232
|
+
/** Captured with the synchronous log read; live batches after it are not in events. */
|
|
4233
|
+
position?: StreamStamp;
|
|
4234
|
+
attempt: number;
|
|
4235
|
+
events: TaskLogEvent[];
|
|
4236
|
+
}
|
|
4237
|
+
type SessionListResponse = SessionMeta[];
|
|
4238
|
+
interface SettingsResponse {
|
|
4239
|
+
orchestratorModel: string;
|
|
4240
|
+
/** Who plans (ADR-0009) — a vendor id, or a runner serving as a harness planner. */
|
|
4241
|
+
aiProvider: PlannerBackendId;
|
|
4242
|
+
plannerThinkingEffort: string;
|
|
4243
|
+
maxParallel: number;
|
|
4244
|
+
modelAllowlist: UserSettings['modelAllowlist'];
|
|
4245
|
+
plannerModels: UserSettings['plannerModels'];
|
|
4246
|
+
/** The user's run settings (ADR-0030), each at its default until set. */
|
|
4247
|
+
run: GlobalRunSettings;
|
|
4248
|
+
}
|
|
4249
|
+
/** What `PATCH /api/settings` answers: the settings as they now stand, plus what the write itself decided. */
|
|
4250
|
+
interface SettingsUpdateResponse extends SettingsResponse {
|
|
4251
|
+
/**
|
|
4252
|
+
* Which model a planner switch landed on and why — remembered for that
|
|
4253
|
+
* backend, its catalog default, or none. Present only when the write changed
|
|
4254
|
+
* the planner; absent for every other write, where nothing was recalled.
|
|
4255
|
+
*/
|
|
4256
|
+
switchRecall?: PlannerModelRecall;
|
|
4257
|
+
/** Env keys the daemon refused to apply, so a client does not persist what the daemon would not. */
|
|
4258
|
+
rejectedEnvKeys?: string[];
|
|
4259
|
+
}
|
|
4260
|
+
interface CommandDescriptor {
|
|
4261
|
+
name: string;
|
|
4262
|
+
description: string;
|
|
4263
|
+
}
|
|
4264
|
+
interface CommandsResponse {
|
|
4265
|
+
commands: CommandDescriptor[];
|
|
4266
|
+
}
|
|
4267
|
+
interface WorkspacesResponse {
|
|
4268
|
+
workspaces: string[];
|
|
4269
|
+
}
|
|
4270
|
+
interface ApprovalsResponse {
|
|
4271
|
+
pending: PendingApproval[];
|
|
4272
|
+
approvedScopes: string[];
|
|
4273
|
+
}
|
|
4274
|
+
interface ApprovalAnswerResponse extends OkResponse {
|
|
4275
|
+
granted: boolean;
|
|
4276
|
+
decision: ApprovalDecision['decision'];
|
|
4277
|
+
}
|
|
4278
|
+
/**
|
|
4279
|
+
* Where a frame stands in its session's broadcast stream. A client holding
|
|
4280
|
+
* several sockets on one session gets every broadcast on each, and a frame's
|
|
4281
|
+
* content is no identity: two clears of a held edit are the same JSON. `seq`
|
|
4282
|
+
* rises by one per broadcast of the session for as long as the daemon runs,
|
|
4283
|
+
* whichever Session object holds the id; `stream` names the daemon process,
|
|
4284
|
+
* so a restarted daemon's numbering is never compared with the last one's.
|
|
4285
|
+
*/
|
|
4286
|
+
interface StreamStamp {
|
|
4287
|
+
stream: string;
|
|
4288
|
+
seq: number;
|
|
4289
|
+
}
|
|
4290
|
+
/** A broadcast as the session's socket carries it. Unstamped from a daemon older than the stamp. */
|
|
4291
|
+
type SessionFrame = (SessionMessage | SessionNotice) & Partial<StreamStamp>;
|
|
4292
|
+
type ApprovalRequestMessage = Extract<SessionMessage, {
|
|
4293
|
+
type: 'approval_request';
|
|
4294
|
+
}>;
|
|
4295
|
+
/**
|
|
4296
|
+
* What a session's socket sends right after `connected`: its run as it stands,
|
|
4297
|
+
* so a client that joins mid-run has the run view before the next update —
|
|
4298
|
+
* the held edit's preview included — and the planner's approval requests
|
|
4299
|
+
* still waiting, which no later frame would announce again. `seq` is the last
|
|
4300
|
+
* broadcast it accounts for. Every frame after it is a {@link SessionFrame}.
|
|
4301
|
+
*/
|
|
4302
|
+
interface RunSnapshotFrame extends Partial<StreamStamp> {
|
|
4303
|
+
type: 'run_snapshot';
|
|
4304
|
+
sessionId: string;
|
|
4305
|
+
view: RunView;
|
|
4306
|
+
/** Absent from a daemon older than the field: nothing is known, not "none waiting". */
|
|
4307
|
+
approvals?: ApprovalRequestMessage[];
|
|
4308
|
+
}
|
|
4309
|
+
|
|
2513
4310
|
declare const EMPTY_RUN: RunView;
|
|
2514
4311
|
declare function reduceRun(view: RunView, input: RunInput): RunView;
|
|
2515
4312
|
|
|
@@ -2784,10 +4581,10 @@ declare function reduceConversation(view: ConversationView, input: ConversationI
|
|
|
2784
4581
|
|
|
2785
4582
|
/**
|
|
2786
4583
|
* The view a saved session reopens with: what its transcript and research log
|
|
2787
|
-
* kept, in the order it happened — messages,
|
|
2788
|
-
* summary, plan markers, tool calls with
|
|
2789
|
-
* it, and the token line. Reasoning and
|
|
2790
|
-
* reload has none.
|
|
4584
|
+
* kept, in the order it happened — messages, each reply run by run where it
|
|
4585
|
+
* began, notices and the compaction summary, plan markers, tool calls with
|
|
4586
|
+
* each subagent's calls nested under it, and the token line. Reasoning and
|
|
4587
|
+
* streamed text that never settled were never saved, so a reload has none.
|
|
2791
4588
|
*
|
|
2792
4589
|
* The research log interleaves with the transcript by time. Entries older than
|
|
2793
4590
|
* a compaction's first kept message went with the turns it condensed.
|
|
@@ -2812,8 +4609,9 @@ interface TaskLogView extends BlockList {
|
|
|
2812
4609
|
/** What the runner reported over the attempt, subagents included. */
|
|
2813
4610
|
readonly usage?: PlannerUsage;
|
|
2814
4611
|
/**
|
|
2815
|
-
* The block each
|
|
2816
|
-
*
|
|
4612
|
+
* The block each stream of deltas writes into — a run of text by its id,
|
|
4613
|
+
* thinking by whose it is — so the stream's authoritative copy replaces it
|
|
4614
|
+
* instead of repeating it below.
|
|
2817
4615
|
*/
|
|
2818
4616
|
readonly streams: Readonly<Record<string, string>>;
|
|
2819
4617
|
/** The turn's latest agent text block, which a failure that repeats it takes the place of. */
|
|
@@ -2980,6 +4778,40 @@ declare function markdownTokens(text: string): readonly MarkdownToken[];
|
|
|
2980
4778
|
/** A model's text as HTML, safe to set as a webview element's innerHTML. */
|
|
2981
4779
|
declare function markdownHtml(text: string): string;
|
|
2982
4780
|
|
|
4781
|
+
interface TaskLogBatch {
|
|
4782
|
+
attempt: number;
|
|
4783
|
+
events: TaskLogEvent[];
|
|
4784
|
+
position?: StreamStamp;
|
|
4785
|
+
}
|
|
4786
|
+
/** A saved attempt and the live batches that follow it (ADR-0018 P1). */
|
|
4787
|
+
interface TaskLogFollow {
|
|
4788
|
+
view: TaskLogView;
|
|
4789
|
+
attempts: number[];
|
|
4790
|
+
attempt: number;
|
|
4791
|
+
pending: TaskLogBatch[];
|
|
4792
|
+
loaded: boolean;
|
|
4793
|
+
followLatest: boolean;
|
|
4794
|
+
/** The outstanding pick; an older attempt read cannot replace it. */
|
|
4795
|
+
loadingAttempt?: number;
|
|
4796
|
+
/** The saved read's cutoff, also rejecting socket copies delivered after the read. */
|
|
4797
|
+
position?: StreamStamp;
|
|
4798
|
+
}
|
|
4799
|
+
type TaskLogFollowInput = {
|
|
4800
|
+
type: 'opened';
|
|
4801
|
+
} | ({
|
|
4802
|
+
type: 'loaded';
|
|
4803
|
+
attempts?: number[];
|
|
4804
|
+
abandoned?: boolean;
|
|
4805
|
+
} & TaskLogBatch) | ({
|
|
4806
|
+
type: 'live';
|
|
4807
|
+
} & TaskLogBatch) | {
|
|
4808
|
+
type: 'picked';
|
|
4809
|
+
attempt: number;
|
|
4810
|
+
};
|
|
4811
|
+
declare const EMPTY_TASK_LOG_FOLLOW: TaskLogFollow;
|
|
4812
|
+
/** Join by stream position, never by event content or socket arrival time. */
|
|
4813
|
+
declare function followTaskLog(state: TaskLogFollow, input: TaskLogFollowInput): TaskLogFollow;
|
|
4814
|
+
|
|
2983
4815
|
/**
|
|
2984
4816
|
* What a row's status means to a reader, before any surface picks a word,
|
|
2985
4817
|
* icon or colour for it. `quiet` is a running task whose runner has printed
|
|
@@ -3225,9 +5057,10 @@ declare function titledRefs(ids: readonly string[], tasks: readonly Named[]): st
|
|
|
3225
5057
|
* and a bare "Remove task?" would hide that.
|
|
3226
5058
|
*/
|
|
3227
5059
|
declare function dependentsNotice(dependents: readonly {
|
|
5060
|
+
id?: string;
|
|
3228
5061
|
order: number;
|
|
3229
5062
|
title: string;
|
|
3230
|
-
}[]): string | null;
|
|
5063
|
+
}[], tasks?: readonly Named[]): string | null;
|
|
3231
5064
|
/**
|
|
3232
5065
|
* What a start past a merge gate is asked with (ADR-0020): the work the task
|
|
3233
5066
|
* would act without, named. `subject` is how the asking surface refers to the
|
|
@@ -3260,4 +5093,4 @@ declare function parseSwitch(arg: string): boolean | null;
|
|
|
3260
5093
|
/** A whole number at least `min`, as typed; null for anything else. */
|
|
3261
5094
|
declare function parseCount(arg: string | undefined, min: number): number | null;
|
|
3262
5095
|
|
|
3263
|
-
export { type
|
|
5096
|
+
export { type ForceSendResponse as $, ACT_RISK as A, type BlockingSubject as B, CHAT_COMMANDS as C, type ConversationCompactResponse as D, type ConversationCompaction as E, ConversationEditError as F, type ConversationForkResponse as G, type ConversationInput as H, type ConversationRequest as I, type ConversationRewindResponse as J, type ConversationTurn as K, type ConversationView as L, DEFAULT_WEB_READ_BUDGET as M, type DaemonErrorCode as N, type DecideOptions as O, type DiffRow as P, type DiffRowKind as Q, type DiffStat as R, type DisplayBlock as S, EMPTY_CONVERSATION as T, EMPTY_HOLD as U, EMPTY_RUN as V, EMPTY_TASK_LOG as W, EMPTY_TASK_LOG_FOLLOW as X, type ErrorBody as Y, type ExecuteResponse as Z, type ExecutionSummary as _, AUTORUN_RISK as a, type RewindTargetsResponse as a$, type GatedConversation as a0, type GeneratePlanResponse as a1, type GlobalRunSettings as a2, HEADLESS_CLI_COMMANDS as a3, type HeldEdit as a4, type HeldEditMark as a5, type IAiService as a6, type INotification as a7, type IWebFetcher as a8, type IsolationDiffResponse as a9, type PlanResponse as aA, type PlanSnapshot as aB, PlanStore as aC, type PlannerCommandSyntax as aD, PlannerConversation as aE, type PlannerConversationHost as aF, type PlannerModelCandidate as aG, type PlannerModelChoice as aH, PlannerModelMemory as aI, type PlannerModelRecall as aJ, type PlannerModelStore as aK, type PlannerModes as aL, type PlannerNotReady as aM, PlannerNotReadyError as aN, type PlannerReadinessConfig as aO, type PlannerSubmission as aP, PlannerTurnDiscardedError as aQ, PlannerTurnStoppedError as aR, PlannerUsageLimitError as aS, type PrdResponse as aT, type PromptHold as aU, RUN_SETTINGS_DEFAULTS as aV, type RefTask as aW, type RemoveMessageResponse as aX, type ResolveAll as aY, type ResolveConflictResponse as aZ, type RewindTarget as a_, type IsolationMergeResponse as aa, IsolationRunController as ab, type LiveOutputLookup as ac, type LiveTail as ad, type LiveTailOptions as ae, type LocalEntry as af, type MarkdownToken as ag, type MergeGateEntry as ah, type MergeGateResponse as ai, type MessageBlock as aj, type MessageRole as ak, type ModelsResponse as al, NOT_STARTED as am, NO_TURN as an, type NewTaskFields as ao, type NotificationAction as ap, OUTPUT_LINES_DEFAULT as aq, OUTPUT_LINES_MAX as ar, type OkResponse as as, type OrchestratorObserver as at, type OutputPreview as au, type PendingApproval as av, PendingApprovals as aw, type PendingApprovalsOptions as ax, type PlanBlock as ay, type PlanMarkerStatus as az, type AdoptSessionResponse as b, type TaskPlacement as b$, type RunInput as b0, type RunMergeView as b1, RunSettingsError as b2, type RunSnapshotFrame as b3, type RunTask as b4, type RunTaskStatus as b5, type RunView as b6, type RunnerCatalog as b7, type RunnerLoadedBlock as b8, type RunnersResponse as b9, type SubagentChild as bA, type SubagentStatus as bB, type SurfacePlan as bC, type SurfacePlanState as bD, type SurfaceTask as bE, TASK_QUERY_ANSWER_MAX_CHARS as bF, TASK_QUERY_ANSWER_OR_OPS as bG, TASK_QUERY_FIELDS as bH, TASK_QUERY_PROTOCOL as bI, TASK_QUERY_REMINDER as bJ, TERMINAL_UI_COMMANDS as bK, type TakenPrompt as bL, type TaskAttemptSnapshot as bM, TaskControlError as bN, type TaskLogAttemptsResponse as bO, type TaskLogBatch as bP, type TaskLogEvent as bQ, type TaskLogFollow as bR, type TaskLogFollowInput as bS, type TaskLogResponse as bT, type TaskLogView as bU, type TaskMessageResponse as bV, type TaskOp as bW, TaskOrchestrator as bX, type TaskOrchestratorDeps as bY, type TaskOrchestratorOptions as bZ, type TaskOutputSource as b_, STOPPED_BEFORE_START as ba, SUPERVISOR_LEVEL_LABEL as bb, type SerializedConversationMessage as bc, type SerializedPendingTurn as bd, type SerializedPlan as be, type SerializedTask as bf, type SerializedTaskStatus as bg, type SessionBroadcaster as bh, type SessionData as bi, type SessionFrame as bj, type SessionListResponse as bk, type SessionMessage as bl, type SessionMeta as bm, type SessionNotice as bn, type SessionResponse as bo, type SettingsResponse as bp, SettingsService as bq, type SettingsUpdateResponse as br, type SkillInvocation as bs, type SkillLoadBlock as bt, type SkillToken as bu, type StartAdmission as bv, type StartWait as bw, type StopResponse as bx, type StreamStamp as by, type SubagentBlock as bz, type ApplyTaskOpsResult as c, loadedSkillTokens as c$, type TaskQuery as c0, type TaskQueryCatalog as c1, type TaskQueryField as c2, type TaskRef as c3, type TaskRowAction as c4, type TaskRowContext as c5, type TaskRowIsolation as c6, type TaskRowKind as c7, type TaskRowPlacement as c8, type TaskRowSubject as c9, awaitingLabel as cA, blockedByLabel as cB, blockingFailures as cC, canMergeTasks as cD, canSetDependencies as cE, canSplitTask as cF, capConflictFiles as cG, classifyOutcome as cH, coalesceTaskLog as cI, createAiService as cJ, dependencyCandidates as cK, dependentsNotice as cL, dependentsOf as cM, describePlannerNotReady as cN, diffRows as cO, diffStat as cP, diffSummary as cQ, digestTaskLog as cR, drainNext as cS, executionSummary as cT, followTaskLog as cU, followTurn as cV, fromTranscript as cW, getSettingsPath as cX, hasHiddenDetail as cY, holdPrompt as cZ, isTaskRunning as c_, type TaskRowUsage as ca, type TaskRowView as cb, type TaskStartResponse as cc, type TaskStatusKind as cd, type ThinkingDisplayBlock as ce, type ToolBlock as cf, type ToolHeadline as cg, type ToolStatus as ch, type TreeTask as ci, type TurnGate as cj, type UsageBlock as ck, type UserSettings as cl, WAITS_FOR_APPROVAL_LABEL as cm, type WebRead as cn, type WebReadDecision as co, WebReadPolicy as cp, type Worded as cq, type WorkspacesResponse as cr, abbreviateHome as cs, aheadOfDraft as ct, applyTaskOps as cu, approvalLabel as cv, assignmentIssue as cw, assignmentIssueText as cx, automergeSummary as cy, autorunSummary as cz, type ApprovalAnswerResponse as d, unsendAll as d$, markAction as d0, markRequestFor as d1, markdownHtml as d2, markdownTokens as d3, namedRepos as d4, newTaskFields as d5, opsEditable as d6, outputLines as d7, outputPreview as d8, parseCount as d9, skippedLabel as dA, stopTurn as dB, subtreeIds as dC, summarizeToolCall as dD, supervisorSummary as dE, surfacePlan as dF, surfacePlanState as dG, takeAt as dH, taskLogForSurface as dI, taskOrderLabel as dJ, taskPlacement as dK, taskQuerySignature as dL, taskRef as dM, taskRefOf as dN, taskRowActions as dO, taskRowView as dP, taskStartedNotice as dQ, taskStatusKind as dR, textHasTaskOps as dS, textHasTaskQuery as dT, titledRefs as dU, titledTaskRef as dV, titledTaskRefOf as dW, toTaskLogEvent as dX, toolHeadline as dY, trimToolOutput as dZ, truncateCheckpointSummary as d_, parseSwitch as da, parseTaskOpsJson as db, parseTaskQueryJson as dc, pastGateConfirmation as dd, plannerMessage as de, plannerNotReady as df, plannerNotReadyFor as dg, plannerTranscript as dh, reconcileApprovals as di, reduceConversation as dj, reduceRun as dk, reduceTaskLog as dl, renderTaskQueryAnswer as dm, replayTaskLog as dn, resolveSkillInvocation as dp, resolveTaskRef as dq, resolveUserTaskRef as dr, resumeLabel as ds, resumeTime as dt, runSettingsOf as du, runnerToolSubject as dv, serializePlan as dw, serializeTask as dx, serializeTaskStatus as dy, skillTokens as dz, type ApprovalBlock as e, unsendLatest as e0, walkPlan as e1, type ApprovalRequestMessage as f, type ApprovalStatus as g, type ApprovalsResponse as h, type ApproveReviewResponse as i, type AskOptions as j, type AssignmentCatalog as k, type AssignmentIssue as l, type AssignmentScope as m, type AssignmentWriter as n, type AutoMergeTrigger as o, CHECKPOINT_TRUNCATE_LENGTH as p, type CancelPlanningResponse as q, type CancelResumeResponse as r, CliAgentAiService as s, type CliAgentAiServiceDeps as t, type CommandBlock as u, type CommandDescriptor as v, type CommandStatus as w, type CommandsResponse as x, type CompactionBlock as y, ConversationBusyError as z };
|