@ordewell/core 0.7.1 → 0.8.0

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.
Files changed (52) hide show
  1. package/README.md +5 -2
  2. package/dist/{IFileSystem-BnLh-_SK.d.mts → IFileSystem-D96mT-bm.d.ts} +1 -1
  3. package/dist/{IFileSystem-DofiRcCo.d.ts → IFileSystem-DXJCRw5x.d.mts} +1 -1
  4. package/dist/IRunner-CGCihlGQ.d.ts +1165 -0
  5. package/dist/IRunner-XVUddYVX.d.mts +1165 -0
  6. package/dist/{ModeResolver-DkMYf4uS.d.ts → ModeResolver-DE-o2lRF.d.mts} +2 -2
  7. package/dist/{ModeResolver-DZzJSdIU.d.mts → ModeResolver-EWlpmVAy.d.ts} +2 -2
  8. package/dist/Task-1Q19y7sZ.d.mts +1122 -0
  9. package/dist/Task-1Q19y7sZ.d.ts +1122 -0
  10. package/dist/{chunk-ZV5LDNCO.mjs → chunk-2M4X3VB7.mjs} +18 -6
  11. package/dist/chunk-2M4X3VB7.mjs.map +1 -0
  12. package/dist/{chunk-Y36NIASW.mjs → chunk-BLLIBCCS.mjs} +153 -264
  13. package/dist/chunk-BLLIBCCS.mjs.map +1 -0
  14. package/dist/chunk-H5O74L77.mjs +185 -0
  15. package/dist/chunk-H5O74L77.mjs.map +1 -0
  16. package/dist/{chunk-LGQNXPBX.mjs → chunk-UUU46PVQ.mjs} +72 -192
  17. package/dist/chunk-UUU46PVQ.mjs.map +1 -0
  18. package/dist/index.d.mts +1119 -930
  19. package/dist/index.d.ts +1119 -930
  20. package/dist/index.js +8361 -8849
  21. package/dist/index.js.map +1 -1
  22. package/dist/index.mjs +8197 -8614
  23. package/dist/index.mjs.map +1 -1
  24. package/dist/order-labels.d.mts +1 -4
  25. package/dist/order-labels.d.ts +1 -4
  26. package/dist/{parsing-ddZXZFP4.d.ts → parsing-CZdp2p_n.d.ts} +5 -3
  27. package/dist/{parsing-B6ctfF80.d.mts → parsing-GgUJoYKs.d.mts} +5 -3
  28. package/dist/parsing.d.mts +3 -6
  29. package/dist/parsing.d.ts +3 -6
  30. package/dist/parsing.js +25 -4
  31. package/dist/parsing.js.map +1 -1
  32. package/dist/parsing.mjs +3 -2
  33. package/dist/{plan-utils-CKgNtDwQ.d.mts → plan-utils-BMayhGBj.d.mts} +89 -14
  34. package/dist/{plan-utils--kQRzSE-.d.ts → plan-utils-Cv48fe7r.d.ts} +89 -14
  35. package/dist/plan-utils.d.mts +4 -4
  36. package/dist/plan-utils.d.ts +4 -4
  37. package/dist/plan-utils.js +128 -65
  38. package/dist/plan-utils.js.map +1 -1
  39. package/dist/plan-utils.mjs +10 -2
  40. package/dist/testing.d.mts +12 -24
  41. package/dist/testing.d.ts +12 -24
  42. package/dist/testing.js +29 -29
  43. package/dist/testing.js.map +1 -1
  44. package/dist/testing.mjs +21 -27
  45. package/dist/testing.mjs.map +1 -1
  46. package/package.json +1 -1
  47. package/skills/tdd/SKILL.md +19 -0
  48. package/dist/Task-BnLxkVQF.d.mts +0 -2594
  49. package/dist/Task-BnLxkVQF.d.ts +0 -2594
  50. package/dist/chunk-LGQNXPBX.mjs.map +0 -1
  51. package/dist/chunk-Y36NIASW.mjs.map +0 -1
  52. package/dist/chunk-ZV5LDNCO.mjs.map +0 -1
@@ -0,0 +1,1165 @@
1
+ import { ChildProcess } from 'child_process';
2
+ import { ao as UsageRecord, a8 as SubagentOutcome, W as RunnerId, ag as TaskSkillSnapshot } from './Task-1Q19y7sZ.mjs';
3
+ import { z } from 'zod/v4';
4
+
5
+ /**
6
+ * The planner's single approval seam. Every capability that reaches beyond the
7
+ * default read-only, in-workspace envelope — a path outside the workspace root,
8
+ * a shell command outside the auto-allowed set, a URL fetch — routes through
9
+ * one `request()` so the policy has exactly one owner (the same "one repair
10
+ * owner" shape as PlanRepair).
11
+ *
12
+ * Surfaces supply the human channel. VS Code answers with a modal; the web
13
+ * server currently has no prompt UI, so it denies unless the scope was
14
+ * pre-approved through config. Denial is always a visible, actionable tool
15
+ * result — never a silent success.
16
+ */
17
+ /**
18
+ * `runner_tool` is a task runner's own tool request (ADR-0018, A1): it waits
19
+ * for an answer as long as it takes, and never passes through the planner's
20
+ * policy — the runner's mode already decided it needed asking.
21
+ */
22
+ type ApprovalKind = 'external_path' | 'shell_command' | 'url_fetch' | 'runner_tool';
23
+ interface ApprovalRequest {
24
+ kind: ApprovalKind;
25
+ /** The concrete thing being asked about: an absolute path, a command line, a URL. */
26
+ subject: string;
27
+ /**
28
+ * What a grant covers. Approving remembers this, not `subject`, so reading a
29
+ * second file from an already-approved directory does not prompt again.
30
+ */
31
+ scope: string;
32
+ /**
33
+ * Every scope one answer settles, when a command needs more than one — its
34
+ * own and each outside directory it touches — so it prompts once rather than
35
+ * once per scope. `scope` is the first. Absent means `scope` alone.
36
+ */
37
+ scopes?: string[];
38
+ /** One-line context for the prompt. */
39
+ detail?: string;
40
+ /** The task whose runner asked; absent for the planner's own requests. */
41
+ taskId?: string;
42
+ /** "Allow for this task" can be offered: the runner proposed its own session-scoped grant. */
43
+ allowForTask?: boolean;
44
+ }
45
+ /**
46
+ * An answer, from whoever gives it — a person on any surface, or later the
47
+ * supervisor (#28). `allowForTask` is Allow plus the runner's own grant for
48
+ * the rest of the task; `note` goes back to the agent with a denial.
49
+ */
50
+ type ApprovalDecision = {
51
+ decision: 'allow';
52
+ } | {
53
+ decision: 'allowForTask';
54
+ } | {
55
+ decision: 'deny';
56
+ note?: string;
57
+ };
58
+ /** What an answer may be given as: a planner prompt's yes/no still is one. */
59
+ type ApprovalAnswer = boolean | ApprovalDecision;
60
+ declare function toApprovalDecision(answer: ApprovalAnswer): ApprovalDecision;
61
+ declare function isGranted(decision: ApprovalDecision): boolean;
62
+ declare function approvalScopes(request: ApprovalRequest): string[];
63
+ declare function isRunnerApproval(request: ApprovalRequest): boolean;
64
+ interface IApproval {
65
+ request(req: ApprovalRequest): Promise<boolean>;
66
+ }
67
+ /** Denies everything. The safe default when a surface wires no approval channel. */
68
+ declare const DENY_ALL: IApproval;
69
+
70
+ /**
71
+ * The tools of the Ordewell MCP server, per token role (ADR-0022), and the
72
+ * handler interfaces the session side implements to answer them.
73
+ *
74
+ * Every handler method is optional so each tool can be wired on its own; one
75
+ * left unset answers its call with a "not available" error rather than
76
+ * disappearing from `tools/list`, so the list a runner sees never depends on
77
+ * how far the wiring has got.
78
+ */
79
+ declare const taskCompleteInput: z.ZodObject<{
80
+ status: z.ZodEnum<{
81
+ failed: "failed";
82
+ blocked: "blocked";
83
+ done: "done";
84
+ }>;
85
+ summary: z.ZodString;
86
+ reason: z.ZodOptional<z.ZodString>;
87
+ }, z.core.$strip>;
88
+ declare const checkpointInput: z.ZodObject<{
89
+ question: z.ZodString;
90
+ }, z.core.$strip>;
91
+ declare const listRunnersInput: z.ZodObject<{}, z.core.$strip>;
92
+ declare const listModelsInput: z.ZodObject<{
93
+ runner: z.ZodString;
94
+ }, z.core.$strip>;
95
+ declare const loadSkillInput: z.ZodObject<{
96
+ name: z.ZodString;
97
+ }, z.core.$strip>;
98
+ declare const submitPlanInput: z.ZodObject<{
99
+ tasks: z.ZodArray<z.ZodObject<{
100
+ id: z.ZodOptional<z.ZodString>;
101
+ order: z.ZodOptional<z.ZodNumber>;
102
+ title: z.ZodString;
103
+ description: z.ZodOptional<z.ZodString>;
104
+ type: z.ZodOptional<z.ZodEnum<{
105
+ ai: "ai";
106
+ user: "user";
107
+ }>>;
108
+ dependencies: z.ZodOptional<z.ZodArray<z.ZodString>>;
109
+ prompt: z.ZodOptional<z.ZodString>;
110
+ userSteps: z.ZodOptional<z.ZodArray<z.ZodObject<{
111
+ order: z.ZodOptional<z.ZodNumber>;
112
+ instruction: z.ZodString;
113
+ }, z.core.$loose>>>;
114
+ assignedRunner: z.ZodOptional<z.ZodString>;
115
+ assignedModel: z.ZodOptional<z.ZodObject<{
116
+ modelId: z.ZodString;
117
+ modelLabel: z.ZodOptional<z.ZodString>;
118
+ thinkingEffort: z.ZodOptional<z.ZodString>;
119
+ }, z.core.$loose>>;
120
+ taskMode: z.ZodOptional<z.ZodString>;
121
+ autonomy: z.ZodOptional<z.ZodEnum<{
122
+ AFK: "AFK";
123
+ HITL: "HITL";
124
+ }>>;
125
+ sliceType: z.ZodOptional<z.ZodEnum<{
126
+ AFK: "AFK";
127
+ HITL: "HITL";
128
+ }>>;
129
+ ops: z.ZodOptional<z.ZodBoolean>;
130
+ userStoriesCovered: z.ZodOptional<z.ZodArray<z.ZodString>>;
131
+ skills: z.ZodOptional<z.ZodArray<z.ZodString>>;
132
+ subtasks: z.ZodOptional<z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
133
+ }, z.core.$loose>>;
134
+ }, z.core.$strip>;
135
+ declare const editPlanInput: z.ZodObject<{
136
+ ops: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
137
+ op: z.ZodLiteral<"update">;
138
+ taskId: z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>;
139
+ changes: z.ZodRecord<z.ZodString, z.ZodUnknown>;
140
+ }, z.core.$strip>, z.ZodObject<{
141
+ op: z.ZodLiteral<"add">;
142
+ task: z.ZodRecord<z.ZodString, z.ZodUnknown>;
143
+ handle: z.ZodOptional<z.ZodString>;
144
+ }, z.core.$strip>, z.ZodObject<{
145
+ op: z.ZodLiteral<"remove">;
146
+ taskId: z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>;
147
+ }, z.core.$strip>, z.ZodObject<{
148
+ op: z.ZodLiteral<"reorder">;
149
+ taskIds: z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
150
+ }, z.core.$strip>, z.ZodObject<{
151
+ op: z.ZodLiteral<"merge">;
152
+ taskIds: z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
153
+ merged: z.ZodRecord<z.ZodString, z.ZodUnknown>;
154
+ handle: z.ZodOptional<z.ZodString>;
155
+ }, z.core.$strip>, z.ZodObject<{
156
+ op: z.ZodLiteral<"split">;
157
+ taskId: z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>;
158
+ parts: z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
159
+ handle: z.ZodOptional<z.ZodString>;
160
+ }, z.core.$strip>, z.ZodObject<{
161
+ op: z.ZodLiteral<"rearm">;
162
+ taskId: z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>;
163
+ changes: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
164
+ }, z.core.$strip>]>>;
165
+ }, z.core.$strip>;
166
+ declare const taskQueryInput: z.ZodObject<{
167
+ tasks: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>>;
168
+ fields: z.ZodOptional<z.ZodArray<z.ZodEnum<{
169
+ skills: "skills";
170
+ description: "description";
171
+ prompt: "prompt";
172
+ userSteps: "userSteps";
173
+ verdict: "verdict";
174
+ outputSummary: "outputSummary";
175
+ userStoriesCovered: "userStoriesCovered";
176
+ }>>>;
177
+ catalog: z.ZodOptional<z.ZodBoolean>;
178
+ }, z.core.$strip>;
179
+ declare const taskOutputInput: z.ZodObject<{
180
+ task: z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>;
181
+ lines: z.ZodOptional<z.ZodNumber>;
182
+ since: z.ZodOptional<z.ZodNumber>;
183
+ }, z.core.$strip>;
184
+ type TaskCompleteArgs = z.infer<typeof taskCompleteInput>;
185
+ type CheckpointArgs = z.infer<typeof checkpointInput>;
186
+ type ListRunnersArgs = z.infer<typeof listRunnersInput>;
187
+ type ListModelsArgs = z.infer<typeof listModelsInput>;
188
+ type LoadSkillArgs = z.infer<typeof loadSkillInput>;
189
+ type SubmitPlanArgs = z.infer<typeof submitPlanInput>;
190
+ type EditPlanArgs = z.infer<typeof editPlanInput>;
191
+ type TaskQueryArgs = z.infer<typeof taskQueryInput>;
192
+ type TaskOutputArgs = z.infer<typeof taskOutputInput>;
193
+ /** What a handler answers with. `isError` marks a refusal the caller should act on. */
194
+ interface McpToolReply {
195
+ text: string;
196
+ isError?: boolean;
197
+ }
198
+ /**
199
+ * How a checkpoint call ended. `withdrawn` is the attempt going away under it
200
+ * (verdict, cancel, retry, stop) or the call being refused, not a verdict on
201
+ * the question.
202
+ */
203
+ type CheckpointAnswer = {
204
+ kind: 'continue';
205
+ } | {
206
+ kind: 'rejected';
207
+ reason: string;
208
+ } | {
209
+ kind: 'withdrawn';
210
+ why: string;
211
+ };
212
+ interface McpToolContext {
213
+ /** Aborts when the token is revoked or the caller cancels — a waiting handler must stop. */
214
+ signal: AbortSignal;
215
+ }
216
+ type Run<A> = (args: A, context: McpToolContext) => Promise<McpToolReply>;
217
+ interface TaskToolHandler {
218
+ taskComplete?: Run<TaskCompleteArgs>;
219
+ checkpoint?: Run<CheckpointArgs>;
220
+ }
221
+ interface PlannerToolHandler {
222
+ loadSkill?: Run<LoadSkillArgs>;
223
+ listRunners?: Run<ListRunnersArgs>;
224
+ listModels?: Run<ListModelsArgs>;
225
+ submitPlan?: Run<SubmitPlanArgs>;
226
+ editPlan?: Run<EditPlanArgs>;
227
+ taskQuery?: Run<TaskQueryArgs>;
228
+ taskOutput?: Run<TaskOutputArgs>;
229
+ }
230
+ interface McpTool<H> {
231
+ name: string;
232
+ description: string;
233
+ inputSchema: {
234
+ type: 'object';
235
+ [key: string]: unknown;
236
+ };
237
+ annotations?: {
238
+ readOnlyHint: boolean;
239
+ };
240
+ /** Validate `args` and hand them to the handler's method for this tool. */
241
+ call(handler: H, args: unknown, context: McpToolContext): Promise<McpToolReply>;
242
+ }
243
+ /** The task tool whose call settles the attempt (ADR-0022, V1). */
244
+ declare const TASK_COMPLETE_TOOL = "task_complete";
245
+ declare const TASK_TOOLS: readonly McpTool<TaskToolHandler>[];
246
+ declare const PLANNER_TOOLS: readonly McpTool<PlannerToolHandler>[];
247
+
248
+ /**
249
+ * The Ordewell MCP server (ADR-0022): one loopback listener per process,
250
+ * shared by every session in it, where the caller's token alone decides which
251
+ * tools it sees and which session and task its calls reach.
252
+ */
253
+ declare const ORDEWELL_MCP_PATH = "/mcp";
254
+ interface OrdewellMcpServerOptions {
255
+ /** How often a call that is still waiting tells its caller so. */
256
+ heartbeatMs?: number;
257
+ }
258
+ interface TaskTokenScope {
259
+ sessionId: string;
260
+ taskId: string;
261
+ /** The attempt generation the token is good for; a later attempt gets a new token. */
262
+ attempt: number;
263
+ }
264
+ interface PlannerTokenScope {
265
+ sessionId: string;
266
+ }
267
+ /** Where a runner reaches the server and what it presents there. Never put either half in a prompt. */
268
+ interface McpCredential {
269
+ url: string;
270
+ token: string;
271
+ }
272
+ declare class OrdewellMcpServer {
273
+ private readonly grants;
274
+ private http;
275
+ private listening;
276
+ private boundUrl;
277
+ private boundHost;
278
+ private readonly heartbeatMs;
279
+ constructor(options?: OrdewellMcpServerOptions);
280
+ /** The listener's URL, once the first token has started it. */
281
+ get url(): string | undefined;
282
+ issueTaskToken(scope: TaskTokenScope, handler?: TaskToolHandler): Promise<McpCredential>;
283
+ issuePlannerToken(scope: PlannerTokenScope, handler?: PlannerToolHandler): Promise<McpCredential>;
284
+ /** Refuse every later request carrying `token`, and abort the calls it has in flight. */
285
+ revoke(token: string): void;
286
+ dispose(): Promise<void>;
287
+ private issue;
288
+ private start;
289
+ private handle;
290
+ }
291
+ /** The process's one server, which the daemon and the extension host hand to every session they build. */
292
+ declare function sharedMcpServer(): OrdewellMcpServer;
293
+
294
+ /** The server's name in every runner's MCP configuration, and so in its tool names (`mcp__ordewell__*`). */
295
+ declare const ORDEWELL_MCP_SERVER_NAME = "ordewell";
296
+ interface McpClientConfig {
297
+ name: typeof ORDEWELL_MCP_SERVER_NAME;
298
+ url: string;
299
+ headers: Record<string, string>;
300
+ }
301
+ /**
302
+ * The one shape every runner adapter renders its own MCP configuration from.
303
+ * The token travels only in `headers`: the adapter writes it to an owner-only
304
+ * file or an environment variable, never a prompt or a process argument
305
+ * (ADR-0022, A4/A5).
306
+ */
307
+ declare function mcpClientConfig(credential: McpCredential): McpClientConfig;
308
+ /** A runner's MCP configuration on disk, and the way to take it back off. */
309
+ interface OwnerOnlyFile {
310
+ path: string;
311
+ /** Delete the file. Idempotent. */
312
+ remove(): void;
313
+ }
314
+ /**
315
+ * Write a configuration that carries a token where only the user can read it,
316
+ * for a runner that takes its MCP servers from a file (ADR-0022, A5). The
317
+ * directory is private too, so the name cannot be raced before the mode lands.
318
+ */
319
+ declare function ownerOnlyConfigFile(fileName: string, contents: string): OwnerOnlyFile;
320
+
321
+ /**
322
+ * The harness-planner transport contract (ADR-0009).
323
+ *
324
+ * One adapter per coding agent, each speaking that agent's own programmatic
325
+ * protocol and normalizing it to the event union below. Everything above this
326
+ * line — reply classification, the repair loop, plan validation, the four
327
+ * surfaces — is already provider-agnostic, so an adapter is the entire cost of
328
+ * teaching Ordewell to plan with another agent.
329
+ */
330
+ /**
331
+ * One normalized event from a running agent turn. Deliberately smaller than
332
+ * any single agent's native protocol: this is the intersection Ordewell can act
333
+ * on, not a lossless re-encoding. Event fidelity differs by agent — `thinking`
334
+ * is rich on Claude Code and absent elsewhere — so consumers must tolerate a
335
+ * turn that emits nothing but `assistant_text` and `turn_end`.
336
+ */
337
+ type AgentEvent =
338
+ /**
339
+ * A complete run of the assistant's reply. Concatenated in order to form the
340
+ * turn's text. When the same run already streamed as `assistant_text_delta`,
341
+ * this is the authoritative copy of it — it replaces the deltas, it is not
342
+ * appended after them.
343
+ */
344
+ {
345
+ type: 'assistant_text';
346
+ text: string;
347
+ }
348
+ /**
349
+ * An incremental piece of the assistant's reply, for agents that stream
350
+ * partial messages. The planner's own text only: a subagent's words never
351
+ * arrive here, so they can never become the reply.
352
+ */
353
+ | {
354
+ type: 'assistant_text_delta';
355
+ text: string;
356
+ }
357
+ /**
358
+ * Reasoning the agent chose to expose. Never contributes to the reply text.
359
+ * Like `assistant_text`, it supersedes deltas already streamed for it.
360
+ */
361
+ | {
362
+ type: 'thinking';
363
+ text: string;
364
+ subagentId?: string;
365
+ } | {
366
+ type: 'thinking_delta';
367
+ text: string;
368
+ subagentId?: string;
369
+ }
370
+ /** `subagentId` marks a call made inside a subagent rather than by the planner itself. */
371
+ | {
372
+ type: 'tool_call';
373
+ id: string;
374
+ name: string;
375
+ args: Record<string, unknown>;
376
+ subagentId?: string;
377
+ } | {
378
+ type: 'tool_result';
379
+ id: string;
380
+ name: string;
381
+ output: string;
382
+ success: boolean;
383
+ subagentId?: string;
384
+ }
385
+ /** One model call's usage, as the agent reported it — never estimated. */
386
+ | {
387
+ type: 'usage';
388
+ record: UsageRecord;
389
+ }
390
+ /** The agent delegated `brief` to a subagent, whose events carry `subagentId` until it finishes. */
391
+ | {
392
+ type: 'subagent_started';
393
+ subagentId: string;
394
+ brief: string;
395
+ model?: string;
396
+ } | {
397
+ type: 'subagent_finished';
398
+ subagentId: string;
399
+ outcome: SubagentOutcome;
400
+ digest: string;
401
+ }
402
+ /**
403
+ * The agent asked to do something its mode does not cover. A planner always
404
+ * auto-denies it (T1) — a planner that can mutate is not a planner — and the
405
+ * adapter answers so the turn does not hang. A task's request stays open
406
+ * until {@link TaskModeAgentAdapter.answerPermission} (ADR-0018, A1).
407
+ *
408
+ * `input` and `suggestions` are the raw request; `suggestions` are the
409
+ * agent's own session-scoped grants, what "Allow for this task" answers with.
410
+ * `decided` marks a request the task's mode already answered — the adapter
411
+ * replied as the manifest says that mode does — so it is shown, never asked.
412
+ */
413
+ | {
414
+ type: 'permission_request';
415
+ id: string;
416
+ name: string;
417
+ detail: string;
418
+ input?: Record<string, unknown>;
419
+ suggestions?: unknown[];
420
+ toolUseId?: string;
421
+ decided?: ApprovalDecision;
422
+ }
423
+ /** The agent withdrew an open request — an interrupt cancels the call it was for. It takes no answer now. */
424
+ | {
425
+ type: 'permission_cancelled';
426
+ id: string;
427
+ }
428
+ /**
429
+ * The agent delegated work to a subagent it left running in the background,
430
+ * and may end its turn before that work reports. Ordewell's conversation is
431
+ * request/response: a turn that ends hands control back to the user, and
432
+ * anything the agent says afterwards arrives with no turn open and is lost.
433
+ * Naming the launch is what lets the service ask for the results in time.
434
+ */
435
+ | {
436
+ type: 'background_agent';
437
+ id: string;
438
+ }
439
+ /**
440
+ * The agent finished its turn and is waiting for the next user message.
441
+ * `interrupted` marks a turn cut short by {@link TaskModeAgentAdapter.interrupt}
442
+ * rather than one the agent chose to end.
443
+ */
444
+ | {
445
+ type: 'turn_end';
446
+ interrupted?: boolean;
447
+ }
448
+ /**
449
+ * The model has a message handed over by {@link TaskModeAgentAdapter.steer},
450
+ * by the runner's own account rather than because the write succeeded
451
+ * (ADR-0023, D3). With no turn open, the runner opened one for it.
452
+ */
453
+ | {
454
+ type: 'message_delivered';
455
+ id: string;
456
+ }
457
+ /**
458
+ * The runner let go of a message it accepted from `steer` without showing it
459
+ * to the model — Codex does, for one its turn ended before consuming. It is
460
+ * Ordewell's to send again (ADR-0023, D4).
461
+ */
462
+ | {
463
+ type: 'message_dropped';
464
+ id: string;
465
+ }
466
+ /** The turn failed. Carries the agent's own words — never a Ordewell paraphrase. */
467
+ | {
468
+ type: 'error';
469
+ message: string;
470
+ };
471
+ interface AgentStartCommon {
472
+ /** Workspace root. The agent works from here and, in read-only mode, cannot leave it. */
473
+ cwd: string;
474
+ /** Model id from the runner's own discovery catalog. Omitted means the agent's default. */
475
+ model?: string;
476
+ /**
477
+ * The agent's own session id from a previous run. For the planner a hint
478
+ * only: Ordewell's transcript is the source of truth (T4), so a failed resume
479
+ * degrades to a fresh session seeded from the stored history, not an error.
480
+ */
481
+ resumeSessionId?: string;
482
+ }
483
+ /** The read-only planner (ADR-0008/0009). The only start `CliAgentAiService` can express. */
484
+ interface PlannerStartOptions extends AgentStartCommon {
485
+ kind: 'planner';
486
+ /** The planner system prompt, in its harness variant. */
487
+ systemPrompt: string;
488
+ /** Variant / reasoning effort id from that model's `variants` list. */
489
+ effort?: string;
490
+ /** The Ordewell MCP server to inject, pre-authorized (ADR-0022). Only for an adapter with {@link AgentAdapter.mcpAttached}. */
491
+ mcp?: McpClientConfig;
492
+ }
493
+ /**
494
+ * What a runner manifest says the task's mode and effort mean (ADR-0001),
495
+ * resolved from the runner manifest — see `resolveTaskRunnerFlags`.
496
+ * The adapter adds only its protocol flags around these.
497
+ */
498
+ interface TaskRunnerFlags {
499
+ /** The runner's own permission-mode value for the task's mode. */
500
+ permissionMode: string;
501
+ /** The task's raw effort id, present only alongside a model. Each adapter maps it to its own protocol. */
502
+ effort?: string;
503
+ /** The manifest's further settings for the task's mode, by setting name — see `RunnerFeatures.modeSettings`. */
504
+ modeSettings: Record<string, string>;
505
+ }
506
+ /** A plan task driven over the runner's programmatic protocol (ADR-0018, C1). */
507
+ interface TaskStartOptions extends AgentStartCommon {
508
+ kind: 'task';
509
+ /** The task's runner mode id, as the plan names it. */
510
+ mode: string;
511
+ flags: TaskRunnerFlags;
512
+ /**
513
+ * The Ordewell MCP server and this attempt's token (ADR-0022), injected
514
+ * with its tools pre-approved. A task is never started without it: its
515
+ * `task_complete` call is how it reports it is done.
516
+ */
517
+ mcp?: McpClientConfig;
518
+ }
519
+ /**
520
+ * The explicit start switch. Discriminated so that a read-only planner and a
521
+ * mutating task can never be confused by a missing field: every caller names
522
+ * which one it is starting.
523
+ */
524
+ type AgentStartOptions = PlannerStartOptions | TaskStartOptions;
525
+ /** A runner asked to start in task mode that has no task-mode connector yet. */
526
+ declare class TaskModeUnsupportedError extends Error {
527
+ readonly runner: string;
528
+ constructor(runner: string);
529
+ }
530
+ interface AgentAdapter {
531
+ /** The runner id this adapter drives — `claude-code`, `codex`, `opencode`. */
532
+ readonly agentId: string;
533
+ /** Spawn the agent — read-only for a planner, in the task's mode for a task — ready to receive messages. */
534
+ start(opts: AgentStartOptions): Promise<void>;
535
+ /**
536
+ * Send one user message and stream the turn's events until it ends. Resolves
537
+ * when the agent yields the floor; rejects only when the transport itself
538
+ * failed in a way no `error` event could describe.
539
+ *
540
+ * `onActivity`, when given, fires on raw transport traffic — every stdio
541
+ * line or stream chunk the process produces — independent of whether that
542
+ * traffic becomes an `AgentEvent`. An adapter may legitimately emit nothing
543
+ * for long stretches (a subagent's filtered output, most often); a caller
544
+ * using presence-of-events as a liveness signal would read that silence as
545
+ * a hang. `onActivity` is the seam that keeps liveness detection from being
546
+ * coupled to what each adapter chooses to surface.
547
+ */
548
+ send(message: string, onEvent: (event: AgentEvent) => void, signal?: AbortSignal, onActivity?: () => void): Promise<void>;
549
+ /** The agent's native session id once it has announced one. Resumption hint only. */
550
+ nativeSessionId(): string | null;
551
+ /** Kill the process and release its resources. Idempotent. */
552
+ dispose(): void;
553
+ /**
554
+ * Whether the Ordewell MCP server passed in the start options' `mcp` is
555
+ * connected, as the runner itself reports it (ADR-0022, S4). Absent on an
556
+ * adapter that cannot inject the server at all, which cannot run tasks.
557
+ */
558
+ mcpAttached?(): Promise<boolean>;
559
+ }
560
+ /** What an adapter adds to run a task rather than a planner (ADR-0018). */
561
+ interface TaskModeAgentAdapter extends AgentAdapter {
562
+ mcpAttached(): Promise<boolean>;
563
+ /**
564
+ * Ask the running turn to stop, keeping the process and its session. Resolves
565
+ * true once the agent acknowledged it; the turn then ends with
566
+ * `turn_end { interrupted: true }`. False means the agent did not answer
567
+ * within `timeoutMs`, and the caller must fall back to killing it.
568
+ */
569
+ interrupt(timeoutMs: number): Promise<boolean>;
570
+ /**
571
+ * Registers the listener for what the agent does after a turn has ended and
572
+ * before the next message — a turn it opens itself when background work
573
+ * finishes, most often. Without one that output is dropped, which is right
574
+ * for a planner and wrong for a task, which may report completion there.
575
+ */
576
+ onOutOfTurn?(listener: (event: AgentEvent) => void): void;
577
+ /** Registers a listener for the process ending, for any reason. Fires at most once. */
578
+ onProcessExit(listener: (code: number) => void): void;
579
+ /**
580
+ * Hand a message to the running turn, for the runner to show the model at
581
+ * its next step boundary (ADR-0023, D1–D2). Resolves true once the runner
582
+ * accepted it: handed over, not yet delivered — the turn's events later
583
+ * carry `message_delivered` or `message_dropped` for `id`. False when the
584
+ * runner refused it or no turn is running; the caller keeps it for the
585
+ * turn's end. Absent on an adapter whose runner cannot take a message
586
+ * mid-turn.
587
+ */
588
+ steer?(id: string, text: string): Promise<boolean>;
589
+ /**
590
+ * Answer an open `permission_request`. False when the id is not open — it
591
+ * was answered, cancelled, or never asked.
592
+ */
593
+ answerPermission(id: string, decision: ApprovalDecision): boolean;
594
+ }
595
+ type SpawnFn = (command: string, args: string[], options: {
596
+ env: NodeJS.ProcessEnv;
597
+ stdio: Array<'pipe' | 'ignore'>;
598
+ cwd: string;
599
+ /** Set by the Windows batch route, where `args` is already a quoted command line. */
600
+ windowsVerbatimArguments?: boolean;
601
+ /** Set on POSIX so the runner leads its own process group (see `spawnInOwnGroup`). */
602
+ detached?: boolean;
603
+ }) => ChildProcess;
604
+ /**
605
+ * The single injected boundary between Ordewell and the operating system.
606
+ * Tests feed
607
+ * recorded agent output through `spawn` (and, for HTTP-transport agents,
608
+ * `fetch`) so one test exercises adapter parsing, event mapping, reply
609
+ * classification and the repair loop as a single observable behavior.
610
+ */
611
+ interface AgentProcessDeps {
612
+ spawn: SpawnFn;
613
+ fetch: typeof globalThis.fetch;
614
+ /** Resolves the PATH agents are spawned under. Defaults to the augmented PATH. */
615
+ resolvePath?: () => Promise<string>;
616
+ /** Host platform. Defaults to the real one; injected so OS-specific behavior is testable anywhere. */
617
+ platform?: NodeJS.Platform;
618
+ /** True when `workspace` names an existing directory. Defaults to a real filesystem check. */
619
+ isDirectory?: (workspace: string) => boolean;
620
+ /** True when `candidate` names an existing, spawnable file. Defaults to a real filesystem check. */
621
+ exists?: (candidate: string) => boolean;
622
+ /** The workspace's own variables for a cwd (ADR-0016). Defaults to {@link resolveWorkspaceEnv}. */
623
+ workspaceEnv?: (cwd: string) => Promise<Record<string, string>>;
624
+ }
625
+ /** Builds the adapter for one runner id, or null when that runner cannot plan. */
626
+ type AgentAdapterFactory = (runner: string, deps: AgentProcessDeps) => AgentAdapter | null;
627
+ /**
628
+ * Split a stream of chunks into complete lines. Every agent transport here is
629
+ * newline-delimited JSON of some shape, and a chunk boundary lands mid-object
630
+ * often enough that parsing per-chunk silently drops events.
631
+ */
632
+ declare class LineBuffer {
633
+ private buffer;
634
+ push(chunk: string, onLine: (line: string) => void): void;
635
+ /** Anything left unterminated when the stream closed. */
636
+ flush(): string;
637
+ }
638
+
639
+ interface RunnerManifest {
640
+ name: string;
641
+ displayName: string;
642
+ runner: {
643
+ command: string;
644
+ };
645
+ features: RunnerFeatures;
646
+ modelDiscovery: RunnerModelDiscovery;
647
+ contextFile?: string;
648
+ contextFileAltPath?: string;
649
+ modes?: RunnerMode[];
650
+ }
651
+ interface RunnerFeatures {
652
+ permissionModeValues?: Record<string, string>;
653
+ modeSettings?: Record<string, Record<string, string>>;
654
+ }
655
+ type ModelDiscoveryParser = 'claude-help' | 'opencode-models' | 'opencode-models-verbose' | 'anthropic-models' | 'line-by-line' | 'json' | 'json-table';
656
+ interface DiscoveryCommand {
657
+ command: string;
658
+ args: string[];
659
+ parser?: ModelDiscoveryParser;
660
+ }
661
+ type ApiAuthMethod = {
662
+ type: 'env';
663
+ varName: string;
664
+ header: string;
665
+ prefix?: string;
666
+ } | {
667
+ type: 'file';
668
+ path: string;
669
+ jsonPath: string;
670
+ header: string;
671
+ prefix?: string;
672
+ };
673
+ interface ApiDiscoveryConfig {
674
+ url: string;
675
+ headers?: Record<string, string>;
676
+ auth: ApiAuthMethod[];
677
+ parser: ModelDiscoveryParser;
678
+ }
679
+ interface RunnerModelDiscovery {
680
+ method: 'command' | 'hardcoded';
681
+ command?: string;
682
+ args?: string[];
683
+ parser?: ModelDiscoveryParser;
684
+ jsonPath?: string;
685
+ /**
686
+ * Stdio JSON-RPC discovery (Codex `app-server`): spawn the command, send
687
+ * `initialize` then `model/list`, and read the catalog from the response.
688
+ * Tried BEFORE apiDiscovery and command discovery. When the call fails,
689
+ * `cacheFile` (the runner's own on-disk catalog cache, `~` expanded) is
690
+ * read before falling through to the remaining discovery methods.
691
+ */
692
+ appServer?: {
693
+ command: string;
694
+ args: string[];
695
+ cacheFile?: string;
696
+ };
697
+ /**
698
+ * Optional last-resort list for a CLI that cannot enumerate
699
+ * models. Used only when command discovery fails entirely or the CLI is
700
+ * unavailable. Built-in manifests must NOT use this: anything listed here is
701
+ * shown to the user as available even when it isn't.
702
+ */
703
+ fallbackModels?: {
704
+ modelId: string;
705
+ modelLabel: string;
706
+ }[];
707
+ /**
708
+ * Stable `--model` aliases that the runner's CLI always accepts but its help
709
+ * text may omit (e.g. Claude's 'haiku'). Merged into successful discovery
710
+ * results to fill gaps — discovered models take precedence, missing aliases
711
+ * are appended — and used as the last resort when discovery fails entirely.
712
+ * Unlike `fallbackModels`, entries must be stable CLI-accepted aliases
713
+ * (contracts that always resolve), not arbitrary model IDs.
714
+ */
715
+ canonicalAliases?: {
716
+ modelId: string;
717
+ modelLabel: string;
718
+ }[];
719
+ /**
720
+ * HTTP API discovery — tried BEFORE command discovery. When the runner's CLI
721
+ * has no model-listing subcommand (Claude Code), an API endpoint can serve as
722
+ * the authoritative source. Auth methods are tried in order; the first that
723
+ * yields a token is used. If no auth method yields a token or the request
724
+ * fails, discovery falls through to `discoveryCommands` + `canonicalAliases`.
725
+ */
726
+ apiDiscovery?: ApiDiscoveryConfig;
727
+ preferredPatterns?: {
728
+ id: string;
729
+ label: string;
730
+ }[];
731
+ variants?: {
732
+ id: string;
733
+ label: string;
734
+ }[];
735
+ discoveryCommands?: DiscoveryCommand[];
736
+ }
737
+ interface RunnerMode {
738
+ id: string;
739
+ label: string;
740
+ description: string;
741
+ /** CLI value passed to the runner's permission/mode flag. Defaults to id if omitted. */
742
+ cliValue?: string;
743
+ /** Marks this mode as the runner's most permissive mode — the resolved default when autonomous mode is ON. */
744
+ autonomous?: boolean;
745
+ /** Marks this mode as the runner's conservative build mode — the resolved default when autonomous mode is OFF. */
746
+ safe?: boolean;
747
+ }
748
+ interface RunnerEntry {
749
+ manifest: RunnerManifest;
750
+ }
751
+
752
+ /**
753
+ * Decides whether one out-of-envelope capability may run, and remembers the
754
+ * answer for the rest of the session.
755
+ *
756
+ * Grants are keyed on {@link ApprovalRequest.scope}, never on the concrete
757
+ * subject: approving a read of `/tmp/foo/a.log` grants `/tmp/foo/*`, and
758
+ * approving `az group list` grants `az group`. Without that, a planner doing
759
+ * real research would prompt on every single call and the feature would be
760
+ * unusable.
761
+ *
762
+ * `mode` is the policy floor:
763
+ * ask consult the human channel; no channel means deny (headless, web)
764
+ * allow grant everything the tier system did not already refuse
765
+ * deny grant nothing beyond `preApproved`
766
+ *
767
+ * Note the ordering: `preApproved` is honored under every mode including
768
+ * `deny`, because it is an explicit operator decision rather than a default.
769
+ */
770
+ type ApprovalMode = 'ask' | 'allow' | 'deny';
771
+ interface ApprovalPolicyOptions {
772
+ mode?: ApprovalMode;
773
+ /** Scopes granted up front from config. A trailing `*` matches by prefix. */
774
+ preApproved?: string[];
775
+ /** The human channel. Absent means there is nobody to ask. */
776
+ ask?: (req: ApprovalRequest) => Promise<boolean>;
777
+ /** Called whenever a decision is reached, for surfacing in a UI or a log. */
778
+ onDecision?: (req: ApprovalRequest, granted: boolean, source: ApprovalSource) => void;
779
+ }
780
+ type ApprovalSource = 'pre-approved' | 'remembered' | 'mode' | 'asked' | 'no-channel';
781
+ declare class ApprovalPolicy implements IApproval {
782
+ private readonly mode;
783
+ private readonly preApproved;
784
+ private readonly asker?;
785
+ private readonly onDecision?;
786
+ private readonly granted;
787
+ private readonly refused;
788
+ /** One in-flight ask per scope: a parallel tool round must not prompt twice for the same thing. */
789
+ private readonly inFlight;
790
+ private generation;
791
+ constructor(opts?: ApprovalPolicyOptions);
792
+ request(req: ApprovalRequest): Promise<boolean>;
793
+ /** Scopes the user has granted this session — for display and for persistence. */
794
+ grantedScopes(): string[];
795
+ /** Drop every session-scoped decision. Called on session reset. */
796
+ reset(): void;
797
+ }
798
+
799
+ interface CatalogModel {
800
+ id: string;
801
+ name: string;
802
+ description: string;
803
+ pricing: {
804
+ prompt: string;
805
+ completion: string;
806
+ };
807
+ contextLength: number;
808
+ }
809
+ declare class ModelCatalog {
810
+ static fetchModels(apiKey: string, baseUrl?: string, fetchImpl?: typeof fetch): Promise<CatalogModel[]>;
811
+ /**
812
+ * Delete the on-disk catalog cache for a base URL so the next fetch re-runs.
813
+ * Used by the resolver's `invalidate()` in production (no injected fetch);
814
+ * best-effort — a missing file or unreadable dir is a no-op.
815
+ */
816
+ static clearCache(baseUrl?: string): void;
817
+ private static readCache;
818
+ private static writeCache;
819
+ }
820
+
821
+ interface ModelShortcut {
822
+ label: string;
823
+ id: string;
824
+ provider: string;
825
+ description: string;
826
+ pricing?: string;
827
+ }
828
+ declare const ORCHESTRATOR_SHORTCUTS: ModelShortcut[];
829
+ declare function resolveModelShortcut(input: string, shortcuts: ModelShortcut[]): string | null;
830
+ /**
831
+ * Resolves a value to a model id ONLY when it is in the supplied catalog
832
+ * option list — either directly or via a shortcut label. Free-typed, unknown,
833
+ * or not-currently-available values return null, so callers never store an id
834
+ * the user couldn't have selected from the list. Keeps model-setting list-only.
835
+ */
836
+ declare function knownModelId(value: string, optionIds: string[], shortcuts: ModelShortcut[]): string | null;
837
+
838
+ type ProviderModelLists = Record<string, string[]>;
839
+ interface OrchestratorOption {
840
+ id: string;
841
+ label: string;
842
+ provider: string;
843
+ apiProvider: AiProvider;
844
+ description?: string;
845
+ pricing?: string;
846
+ /** The model's context window when the catalog reports one (#49). */
847
+ contextWindow?: number;
848
+ }
849
+
850
+ interface FetchAllProviderModelsOptions {
851
+ apiKeys: Record<string, string>;
852
+ baseUrls: Record<string, string>;
853
+ fetchImpl?: typeof fetch;
854
+ }
855
+ type AllProviderModels = Record<string, CatalogModel[]>;
856
+ /**
857
+ * Result of a picker-catalog fetch fan-out: the per-provider model lists plus
858
+ * the per-provider failures. `errors` is keyed by provider id and holds the
859
+ * failure message for any provider whose catalog fetch rejected — the seam the
860
+ * surfaces (CLI, VS Code) use to flag "this configured provider didn't work"
861
+ * rather than silently showing a short list.
862
+ */
863
+ interface ProviderModelsResult {
864
+ models: AllProviderModels;
865
+ errors: Record<string, string>;
866
+ }
867
+ /** The provider config fields the discovery fan-out needs. */
868
+ interface ProviderCredentialSource {
869
+ getProviderApiKey(provider: AiProvider): string;
870
+ getProviderBaseUrl(provider: AiProvider): string;
871
+ openaiCompatibleBaseUrl: string;
872
+ }
873
+ /**
874
+ * Collect the API keys and base URLs the picker fan-out should probe, applying
875
+ * the single "configured" policy shared by every surface (CLI + VS Code):
876
+ * - a preset is discovered only when it has an API key (OpenRouter included —
877
+ * its public catalog is not probed keyless, matching the product rule that
878
+ * only key-configured providers appear);
879
+ * - `openai_compatible` is discovered only when an explicit endpoint is set
880
+ * (keyless local servers like ollama / LM Studio).
881
+ * Base URLs are handed through only for providers that pass the gate, so the
882
+ * fan-out never touches an unconfigured third-party endpoint.
883
+ */
884
+ declare function collectProviderCredentials(config: ProviderCredentialSource): {
885
+ apiKeys: Record<string, string>;
886
+ baseUrls: Record<string, string>;
887
+ };
888
+ declare function fetchAllProviderModels(opts: FetchAllProviderModelsOptions): Promise<ProviderModelsResult>;
889
+ declare function resolveProvider(modelId: string, providerModelLists: Record<string, string[]>): AiProvider | null;
890
+ declare function toOrchestratorOptions(providerModels: AllProviderModels, shortcuts: ModelShortcut[]): OrchestratorOption[];
891
+
892
+ /**
893
+ * Who plans. Mostly LLM vendors reached over HTTP; the last three are *harness
894
+ * planners* (ADR-0009) — a coding agent CLI already installed on the machine,
895
+ * driven as the planner over its own programmatic transport and authenticated
896
+ * by the subscription the user already holds. They are runners in the provider
897
+ * axis, deliberately: "what plans for me?" is one question, and it belongs in
898
+ * one setting. `isCliProvider` is the single guard that tells the two kinds
899
+ * apart.
900
+ */
901
+ type AiProvider = 'google' | 'openrouter' | 'openai_compatible' | 'openai' | 'xai' | 'groq' | 'deepseek' | 'together' | 'mistral' | 'anthropic' | 'fireworks' | 'perplexity' | 'zhipu' | 'kimi' | 'cerebras' | 'deepinfra' | 'doubao' | 'qwen' | 'hunyuan' | 'baichuan' | 'minimax' | 'yi' | 'stepfun' | 'siliconflow' | 'cohere' | 'novita' | 'claude-code' | 'codex' | 'opencode';
902
+ interface IConfig {
903
+ aiProvider: AiProvider;
904
+ apiKey: string;
905
+ planningModel: string;
906
+ enabledRunners: string[];
907
+ maxParallelSessions: number;
908
+ researchEnabled: boolean;
909
+ researchMaxSteps: number;
910
+ researchMaxFileSize: number;
911
+ openAiBaseUrl: string;
912
+ openAiApiKey: string;
913
+ /** Provider keys + base URL the ModelResolver uses to fetch the picker catalogs. */
914
+ openrouterKey: string;
915
+ geminiKey: string;
916
+ geminiBaseUrl?: string;
917
+ /** Base URL + API key for a user-provided OpenAI-compatible endpoint (ollama, vLLM, LM Studio, etc.). */
918
+ openaiCompatibleBaseUrl: string;
919
+ openaiCompatibleApiKey: string;
920
+ orchestratorModel: string;
921
+ geminiModel: string;
922
+ /** Model for research subagents (issue #34); defaults to the (cheap) planner model. */
923
+ researchSubagentModel: string;
924
+ /**
925
+ * Thinking effort / model variant for a harness planner (ADR-0009), chosen
926
+ * from the agent's own discovered variants. Separate from the per-task
927
+ * efforts the plan carries — this one is the planner's own dial.
928
+ */
929
+ plannerThinkingEffort?: string;
930
+ planMapEnabled: boolean;
931
+ autonomousMode: boolean;
932
+ /**
933
+ * Run each AI task in its own git worktree and integrate the results on a
934
+ * per-run branch (ADR-0013). Non-git workspaces fall back to the shared
935
+ * workspace root regardless of this flag.
936
+ */
937
+ worktreeIsolation: boolean;
938
+ /**
939
+ * Shell command run in a fresh task worktree instead of symlinking ignored
940
+ * artifacts from the main worktree — for repos where sharing `node_modules`
941
+ * or a virtualenv is wrong.
942
+ */
943
+ worktreeSetupCommand?: string;
944
+ /**
945
+ * Repositories of the repo group listed by hand, relative to the workspace
946
+ * (ADR-0014). When non-empty the group is exactly these, replacing the
947
+ * repositories auto-detected directly inside the folder; needed for ones
948
+ * deeper than that, which are never auto-detected.
949
+ */
950
+ workspaceRepos: string[];
951
+ /**
952
+ * Extra paths or globs, relative to each repo root, linked from the real repo
953
+ * into its task worktree where they exist — gitignored local state such as
954
+ * `*.tfstate` that a task must use, not a copy of.
955
+ */
956
+ worktreeLinks: string[];
957
+ /**
958
+ * How many conflict repairs one task may go through before its conflict is
959
+ * left for a person (ADR-0015); 0 turns repair off.
960
+ */
961
+ conflictRepairAttempts: number;
962
+ /**
963
+ * What to do when planner research reaches outside its default envelope — an
964
+ * out-of-workspace path, or a shell command beyond the auto-allowed read-only
965
+ * set. `ask` prompts the user (and denies where no surface can prompt, such as
966
+ * headless runs); `allow` and `deny` skip the prompt entirely.
967
+ */
968
+ approvalMode: ApprovalMode;
969
+ /** Scopes granted up front, so CI and power users never see a prompt. Trailing `*` matches by prefix. */
970
+ approvalPreApproved: string[];
971
+ /** Get the base URL for an OpenAI-compatible provider. */
972
+ getProviderBaseUrl(provider: AiProvider): string;
973
+ /** Get the API key for a provider. */
974
+ getProviderApiKey(provider: AiProvider): string;
975
+ /**
976
+ * Push the canonical provider routing lists in (sole producer: ModelResolver).
977
+ * Config consumes them when resolving a chosen model id to its serving API.
978
+ */
979
+ setProviderModelLists(lists: ProviderModelLists): void;
980
+ }
981
+ declare function enabledRunners(cfg: IConfig): RunnerId[];
982
+
983
+ declare class RunnerRegistry {
984
+ private readonly runners;
985
+ get(id: string): RunnerEntry | undefined;
986
+ getManifest(id: string): RunnerManifest | undefined;
987
+ list(): RunnerEntry[];
988
+ listEnabled(config: IConfig): RunnerEntry[];
989
+ listEnabledIds(config: IConfig): string[];
990
+ }
991
+
992
+ /** How a structured turn ended. `failed` carries the agent's own words in the preceding `error` event. */
993
+ type StructuredTurnEnd = 'completed' | 'interrupted' | 'failed';
994
+ /**
995
+ * One normalized event from a structured task (ADR-0018, O1b): the adapter's
996
+ * events, with the turn made explicit at both ends and the message queue (M1)
997
+ * alongside. Subagent work carries its `subagentId`. The source for the
998
+ * full-fidelity task log, never for verdicts.
999
+ */
1000
+ type StructuredEvent = Exclude<AgentEvent, {
1001
+ type: 'turn_end' | 'permission_cancelled' | 'message_delivered' | 'message_dropped';
1002
+ }>
1003
+ /**
1004
+ * `text` is the user message the turn answers; `messageId` is set when it had
1005
+ * waited in the queue, `forced` when it was force sent (ADR-0023, Q2).
1006
+ */
1007
+ | {
1008
+ type: 'turn_start';
1009
+ text: string;
1010
+ messageId?: string;
1011
+ forced?: boolean;
1012
+ } | {
1013
+ type: 'turn_end';
1014
+ reason: StructuredTurnEnd;
1015
+ }
1016
+ /**
1017
+ * Waiting in Ordewell's queue — again, for a message the runner was handed
1018
+ * and let go of (ADR-0023, D4). `forced` puts it ahead of the rest, behind
1019
+ * earlier forced ones (F2); a message already listed moves there.
1020
+ */
1021
+ | {
1022
+ type: 'message_queued';
1023
+ messageId: string;
1024
+ text: string;
1025
+ forced?: boolean;
1026
+ } | {
1027
+ type: 'message_removed';
1028
+ messageId: string;
1029
+ } | {
1030
+ type: 'message_undelivered';
1031
+ messageId: string;
1032
+ text: string;
1033
+ }
1034
+ /** The runner accepted the message into its running turn; it can no longer be taken back (ADR-0023, D3). */
1035
+ | {
1036
+ type: 'message_handed_over';
1037
+ messageId: string;
1038
+ }
1039
+ /** The model read the message inside the running turn, at this point of it (ADR-0023, Q2). */
1040
+ | {
1041
+ type: 'message_delivered';
1042
+ messageId: string;
1043
+ text: string;
1044
+ }
1045
+ /** An open `permission_request` was answered, by whoever answered it (ADR-0018, A1). */
1046
+ | {
1047
+ type: 'permission_decided';
1048
+ id: string;
1049
+ decision: ApprovalDecision;
1050
+ }
1051
+ /** An open `permission_request` can no longer be answered: the runner withdrew it, or its process is gone. */
1052
+ | {
1053
+ type: 'permission_withdrawn';
1054
+ id: string;
1055
+ };
1056
+ interface QueuedTaskMessage {
1057
+ id: string;
1058
+ text: string;
1059
+ /** The runner has it and owes a delivery, so it cannot be taken back (ADR-0023, Q1). */
1060
+ handedOver?: boolean;
1061
+ /** Force sent: it goes out as soon as the running turn is interrupted (ADR-0023, F1). */
1062
+ forced?: boolean;
1063
+ }
1064
+ interface IRunnerSession {
1065
+ id: string;
1066
+ taskId: string;
1067
+ onOutput(callback: (text: string) => void): void;
1068
+ onExit(callback: (code: number) => void): void;
1069
+ kill(): void;
1070
+ getOutput(): string;
1071
+ write(text: string): void;
1072
+ /** `working` while a turn runs; `idle` between turns, waiting for a message. */
1073
+ turnState(): 'working' | 'idle';
1074
+ onTurnEnd(listener: (reason: StructuredTurnEnd) => void): void;
1075
+ onEvent(listener: (event: StructuredEvent) => void): void;
1076
+ /**
1077
+ * Send a user message: at once when idle; while a turn runs, handed to the
1078
+ * runner for its next step where it can take one, otherwise queued until
1079
+ * the turn ends (ADR-0023). Returns its id, for {@link removeQueued}.
1080
+ */
1081
+ sendMessage(text: string): string;
1082
+ /**
1083
+ * Force send (ADR-0023, F1–F3): interrupt the running turn, as
1084
+ * {@link interrupt} does, and deliver this message as the turn that
1085
+ * replaces it, ahead of anything still queued. The task does not wait for
1086
+ * input in between. With no turn running it is {@link sendMessage}.
1087
+ */
1088
+ forceSend(text: string): string;
1089
+ /** Force send a message still queued, by id. False once it was handed over or delivered. */
1090
+ forceSendQueued(id: string): boolean;
1091
+ /** Take a message back before the runner has it. False once it was handed over or delivered. */
1092
+ removeQueued(id: string): boolean;
1093
+ /** Messages not yet delivered, oldest first: queued ones, and ones handed over that the runner owes. */
1094
+ queued(): QueuedTaskMessage[];
1095
+ /**
1096
+ * Stop the running turn, keeping the session: a soft interrupt first, then —
1097
+ * if the runner does not answer in time — kill and resume. Either way the
1098
+ * turn ends `interrupted`. Resolves once it has.
1099
+ */
1100
+ interrupt(): Promise<void>;
1101
+ /** The runner's own session id once announced — what a continue resumes (ADR-0018, K1). */
1102
+ nativeSessionId(): string | null;
1103
+ /**
1104
+ * Answer a `permission_request` this session emitted, by its event id. False
1105
+ * when it is no longer open — answered, withdrawn, or never asked.
1106
+ */
1107
+ answerPermission(id: string, decision: ApprovalDecision): boolean;
1108
+ /**
1109
+ * The runner's `task_complete` calls on this attempt's token (ADR-0022):
1110
+ * the only completion evidence, for `VerdictEngine` to weigh.
1111
+ */
1112
+ onTaskComplete(listener: (report: TaskCompleteArgs) => void): void;
1113
+ /**
1114
+ * Answers the runner's `checkpoint` calls (ADR-0022, V5): the call stays
1115
+ * open until the returned promise settles, and its result is the answer.
1116
+ * `signal` aborts when the caller goes away.
1117
+ */
1118
+ onToolCheckpoint(handler: (question: string, signal: AbortSignal) => Promise<CheckpointAnswer>): void;
1119
+ }
1120
+ interface IRunner {
1121
+ spawn(opts: {
1122
+ taskId: string;
1123
+ runner: string;
1124
+ prompt: string;
1125
+ modelId?: string;
1126
+ thinkingEffort?: string;
1127
+ modelVariants?: string[];
1128
+ mode?: string;
1129
+ cwd: string;
1130
+ registry?: RunnerRegistry;
1131
+ /** Task order and title — surfaces use these to label task_started/output events. */
1132
+ order?: number;
1133
+ title?: string;
1134
+ /**
1135
+ * The owning plan session. Task ids are only unique within one plan, so
1136
+ * a runner that keys anything by task (the attempt's MCP token) needs this
1137
+ * to keep two plans' identically named tasks apart.
1138
+ */
1139
+ planSessionId?: string;
1140
+ /**
1141
+ * The workspace's own variables (ADR-0016), under the runner's: a
1142
+ * manifest's env still wins over them.
1143
+ */
1144
+ env?: Record<string, string>;
1145
+ /** The runner's own session to continue in (ADR-0018, K1). */
1146
+ resumeSessionId?: string;
1147
+ /**
1148
+ * Which run of the task this is, from 1. A structured runner binds the
1149
+ * attempt's MCP token to it (ADR-0022, A2).
1150
+ */
1151
+ attempt?: number;
1152
+ /**
1153
+ * The task skills already rendered into `prompt`, as resolved for this
1154
+ * attempt. Runners ignore it; it is for what wraps one to record (task logs).
1155
+ */
1156
+ skills?: readonly TaskSkillSnapshot[];
1157
+ /** Something the user should know about how the spawn went that did not stop it — a respawn, most often. */
1158
+ onNotice?: (message: string) => void;
1159
+ }): Promise<IRunnerSession>;
1160
+ stop(sessionId: string): void;
1161
+ stopAll(): void;
1162
+ activeCount: number;
1163
+ }
1164
+
1165
+ export { type RunnerManifest as $, type AgentAdapter as A, type McpToolReply as B, type CatalogModel as C, DENY_ALL as D, type EditPlanArgs as E, type FetchAllProviderModelsOptions as F, ModelCatalog as G, type ModelShortcut as H, type IApproval as I, ORDEWELL_MCP_PATH as J, ORDEWELL_MCP_SERVER_NAME as K, LineBuffer as L, type McpClientConfig as M, type OrchestratorOption as N, ORCHESTRATOR_SHORTCUTS as O, OrdewellMcpServer as P, type OwnerOnlyFile as Q, PLANNER_TOOLS as R, type PlannerStartOptions as S, type PlannerTokenScope as T, type PlannerToolHandler as U, type ProviderCredentialSource as V, type ProviderModelLists as W, type ProviderModelsResult as X, type QueuedTaskMessage as Y, type RunnerEntry as Z, type RunnerFeatures as _, type AgentAdapterFactory as a, type RunnerMode as a0, type RunnerModelDiscovery as a1, RunnerRegistry as a2, type SpawnFn as a3, type StructuredEvent as a4, type StructuredTurnEnd as a5, type SubmitPlanArgs as a6, TASK_COMPLETE_TOOL as a7, TASK_TOOLS as a8, type TaskCompleteArgs as a9, type TaskModeAgentAdapter as aa, TaskModeUnsupportedError as ab, type TaskOutputArgs as ac, type TaskQueryArgs as ad, type TaskRunnerFlags as ae, type TaskStartOptions as af, type TaskTokenScope as ag, type TaskToolHandler as ah, approvalScopes as ai, collectProviderCredentials as aj, enabledRunners as ak, fetchAllProviderModels as al, isGranted as am, isRunnerApproval as an, knownModelId as ao, mcpClientConfig as ap, ownerOnlyConfigFile as aq, resolveModelShortcut as ar, resolveProvider as as, sharedMcpServer as at, toApprovalDecision as au, toOrchestratorOptions as av, type AgentEvent as b, type AgentProcessDeps as c, type AgentStartOptions as d, type AiProvider as e, type AllProviderModels as f, type ApprovalAnswer as g, type ApprovalDecision as h, type ApprovalKind as i, type ApprovalMode as j, ApprovalPolicy as k, type ApprovalPolicyOptions as l, type ApprovalRequest as m, type ApprovalSource as n, type CheckpointAnswer as o, type CheckpointArgs as p, type DiscoveryCommand as q, type IConfig as r, type IRunner as s, type IRunnerSession as t, type ListModelsArgs as u, type ListRunnersArgs as v, type LoadSkillArgs as w, type McpCredential as x, type McpTool as y, type McpToolContext as z };