@ordewell/core 0.9.0 → 0.9.1

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.
@@ -1,4 +1,4 @@
1
- import { I as IApproval } from './IRunner-BiQio8gJ.js';
1
+ import { I as IApproval } from './IRunner-Corods20.js';
2
2
 
3
3
  interface ToolOutcome {
4
4
  success: boolean;
@@ -71,6 +71,8 @@ interface IFileSystem {
71
71
  * test doubles stay small; every adapter extending `BaseFileSystem` has it.
72
72
  */
73
73
  setApproval?(approval: IApproval): void;
74
+ /** Held credentials read live by the outbound secret check. */
75
+ setOutboundSecrets?(secrets: () => Iterable<string>): void;
74
76
  }
75
77
 
76
78
  export { type FindSymbolOptions as F, GREP_DEFAULT_HEAD_LIMIT as G, type IFileSystem as I, type ReadFileOpts as R, SEARCH_EXCLUSIONS as S, type ToolOutcome as T, type GlobOptions as a, type GrepOptions as b, type GrepOutputMode as c };
@@ -1,4 +1,4 @@
1
- import { I as IApproval } from './IRunner-CxiymkP_.mjs';
1
+ import { I as IApproval } from './IRunner-D6YfbmHn.mjs';
2
2
 
3
3
  interface ToolOutcome {
4
4
  success: boolean;
@@ -71,6 +71,8 @@ interface IFileSystem {
71
71
  * test doubles stay small; every adapter extending `BaseFileSystem` has it.
72
72
  */
73
73
  setApproval?(approval: IApproval): void;
74
+ /** Held credentials read live by the outbound secret check. */
75
+ setOutboundSecrets?(secrets: () => Iterable<string>): void;
74
76
  }
75
77
 
76
78
  export { type FindSymbolOptions as F, GREP_DEFAULT_HEAD_LIMIT as G, type IFileSystem as I, type ReadFileOpts as R, SEARCH_EXCLUSIONS as S, type ToolOutcome as T, type GlobOptions as a, type GrepOptions as b, type GrepOutputMode as c };
@@ -20,7 +20,7 @@ import { z } from 'zod/v4';
20
20
  * - an **MCP tool rule**, `mcp:find-*`: a glob over the tool's own name,
21
21
  * without the server, case-insensitive.
22
22
  * - a **scope pattern**, anything starting with `/`, `~`, `http://`,
23
- * `https://`, `mcp__` or a drive letter: matched against an approval's
23
+ * `https://`, `web_search:`, `mcp__`, `mcp-server:` or a drive letter: matched against an approval's
24
24
  * scope as ADR-0008 always matched pre-approved entries, `*` matching any
25
25
  * run of characters.
26
26
  *
@@ -74,6 +74,289 @@ declare function scopeAllowed(list: PlannerAllowlist, scope: string): boolean;
74
74
  */
75
75
  declare const DEFAULT_PLANNER_ALLOWLIST: readonly string[];
76
76
 
77
+ /**
78
+ * Which interpreter runs the planner's `bash` tool, and in which language.
79
+ *
80
+ * `AUTO_COMMANDS` — the read-only tier that runs with no prompt — is `ls`, `cat`,
81
+ * `wc`, `head`, `tail`, `du`, `df`, `file`, `sort`, `uniq`, `basename`, `find`,
82
+ * `grep`. That set is POSIX, and passing it to `shell: true` on Windows means
83
+ * cmd.exe, where most of those names do not exist and `find` is an unrelated
84
+ * program that returns plausible wrong output instead of an error. The planner's
85
+ * whole research surface degraded to guessing, quietly.
86
+ *
87
+ * Rather than write a Windows dialect of every research command — a second
88
+ * behavior to keep in step forever — this finds a POSIX shell to run the
89
+ * existing ones in. Git for Windows ships a full one, and git is already a
90
+ * prerequisite for everything Ordewell does, so in practice it is there. Failing
91
+ * that, cmd.exe is used and the classifier is told so, because the one thing
92
+ * that must never happen is classifying a command in one language and running it
93
+ * in another: `commandPolicy` reads {@link ResearchShell.dialect} for exactly
94
+ * that reason.
95
+ *
96
+ * POSIX resolves to `{ file: null }`, meaning "use `shell: true`" — byte for
97
+ * byte what the adapters did before this module existed.
98
+ *
99
+ * Windows paths are built with `path.win32` explicitly, not the host-flavoured
100
+ * `path`, so the probe is the same on a Windows host as it is under a Linux
101
+ * test runner.
102
+ */
103
+ type ShellDialect = 'posix' | 'cmd';
104
+ interface ResearchShell {
105
+ /**
106
+ * Executable that takes a command string, or null to use the host default
107
+ * via `shell: true`.
108
+ */
109
+ file: string | null;
110
+ /** Arguments preceding the command string. */
111
+ args: string[];
112
+ /** The language the command string will be interpreted in. */
113
+ dialect: ShellDialect;
114
+ /**
115
+ * Directory holding this shell's POSIX utilities, when it brings its own.
116
+ * Prepended to PATH for the search subprocesses (`grep`, `tree`) that
117
+ * `execFile` starts without a shell, so they resolve too.
118
+ */
119
+ utilsDir: string | null;
120
+ }
121
+ interface ResearchShellDeps {
122
+ platform?: NodeJS.Platform;
123
+ exists?: (candidate: string) => boolean;
124
+ env?: NodeJS.ProcessEnv;
125
+ }
126
+ /**
127
+ * The shell the planner's `bash` tool should use on this host. Resolved once per
128
+ * process — the answer cannot change while Ordewell runs, and the probe costs a
129
+ * handful of `stat` calls.
130
+ */
131
+ declare function resolveResearchShell(deps?: ResearchShellDeps): ResearchShell;
132
+ /** Test seam: drop the per-process cache so the next call re-probes. */
133
+ declare function clearResearchShellCache(): void;
134
+ /**
135
+ * PATH for the search subprocesses the adapters start without a shell, with the
136
+ * research shell's own utilities in front. Without this, `grep` — the fallback
137
+ * when ripgrep is absent — is unresolvable on a Windows box even when a
138
+ * perfectly good `grep.exe` sits in the Git tree beside the shell.
139
+ */
140
+ declare function researchToolsPath(shell: ResearchShell, basePath: string | undefined): string;
141
+ /**
142
+ * A message explaining a degraded research surface, or null when there is
143
+ * nothing to explain. Surfaced by the adapters on the first refused command so
144
+ * the limitation is visible rather than inferred from bad answers.
145
+ */
146
+ declare function researchShellWarning(shell: ResearchShell): string | null;
147
+
148
+ /**
149
+ * Tiered classification for the planner's `bash` tool.
150
+ *
151
+ * The planner is a read-only researcher: it never edits files, and the runners
152
+ * do the real work. But research legitimately means running things — querying a
153
+ * cloud control plane (`az`, `gh`), reproducing a failure (`npm test`), or
154
+ * shaping output (`jq`). The old allowlist refused all of that, so the model's
155
+ * only escape was to guess.
156
+ *
157
+ * Three tiers replace the flat allowlist:
158
+ *
159
+ * auto read-only inspection — runs with no prompt (the historical list)
160
+ * ask anything else that is not obviously destructive — one approval,
161
+ * remembered for the rest of the session at `scope` granularity
162
+ * refuse writes, privilege escalation, and anything that would smuggle
163
+ * arbitrary code past this classifier — never runs, never prompts
164
+ *
165
+ * `refuse` is deliberately not promptable. A planner that can `rm` is a planner
166
+ * that can silently break the workspace it was asked to reason about, and the
167
+ * architecture already says mutation belongs to the runners.
168
+ *
169
+ * Classification walks every segment of the command line (pipes, `&&`, `;`, and
170
+ * command substitution) rather than matching substrings against the raw string.
171
+ * The old substring denylist both over-matched (`ls docs/removed` tripped `rm`)
172
+ * and under-matched (`$(rm -rf /)` was invisible once chaining was allowed).
173
+ *
174
+ * A segment is classified by the command that will actually execute, not by the
175
+ * name at the front of it: wrappers are unwrapped first, recursively, so
176
+ * `timeout 10 env nice rm -rf x` is an `rm`. See {@link WRAPPER_FAMILY}. A
177
+ * runner that feeds its command arguments nobody can see is unwrapped the same
178
+ * way but never runs unprompted ({@link XARGS_TARGETS}), and one that hands its
179
+ * command to a shell is refused ({@link REFUSED_RUNNERS}).
180
+ *
181
+ * A permitted binary is permitted with the flags it is known to be read-only
182
+ * with, not with any flag at all: several of them will run a helper program or
183
+ * write a file when asked to, so `auto` is a per-binary flag allowlist and an
184
+ * unrecognised flag is refused. See {@link FLAG_POLICY}.
185
+ *
186
+ * The lexer is dialect-aware, because the interpreter that will actually run
187
+ * the command decides what the tokens are, and getting that wrong is not a
188
+ * cosmetic error here — it is the difference between classifying what runs and
189
+ * classifying something else. See {@link Dialect}.
190
+ */
191
+
192
+ /**
193
+ * `env` is deliberately absent. Given a command it is a wrapper, classified by
194
+ * what it actually runs (see {@link WRAPPER_FAMILY}); given none it prints the
195
+ * whole process environment — provider credentials included — into the research
196
+ * log, which is a disclosure the developer should get to see coming.
197
+ *
198
+ * Membership here is necessary but not sufficient: every name in this list also
199
+ * declares the flags it is read-only with in {@link FLAG_POLICY}.
200
+ */
201
+ declare const AUTO_COMMANDS: string[];
202
+ /**
203
+ * `ls-remote` is deliberately absent, and it is the one entry here that is
204
+ * read-only against the repository. It reaches the network: it contacts
205
+ * whatever host the named remote or URL resolves to, which routes around the
206
+ * web fetcher's per-origin approval and its request-forgery guard. So it
207
+ * prompts rather than running silently.
208
+ *
209
+ * Prompting only became the right answer once {@link scopeFor} widened. Under
210
+ * the old scope it would have been remembered as `git ls-remote`, so one
211
+ * approval of the workspace's own remote would have authorised a listing
212
+ * against any host at all — which is the same hole with an approval dialog in
213
+ * front of it. The scope now carries the destination.
214
+ */
215
+ declare const GIT_READONLY_SUBCOMMANDS: string[];
216
+ /**
217
+ * Never runs, with or without approval.
218
+ *
219
+ * The `cmd.exe` builtins at the end matter as much as the POSIX names above
220
+ * them. `del`, `rd`, `move`, and friends mutate exactly what `rm` and `mv` do,
221
+ * and on Windows they are what a model reaches for — so without them the whole
222
+ * refusal tier was bypassable on that platform by writing the command the way
223
+ * the platform spells it. Listed unconditionally rather than per-platform: a
224
+ * POSIX box has no `del` to refuse, so the extra names cost nothing there.
225
+ */
226
+ declare const REFUSED_COMMANDS: string[];
227
+ /**
228
+ * Options threaded through classification.
229
+ *
230
+ * `dialect` is keyed to the interpreter rather than the OS on purpose: a Windows
231
+ * host that has Git Bash runs the planner's commands in a POSIX shell (see
232
+ * {@link resolveResearchShell}), and classifying those under cmd.exe rules
233
+ * would be the same mismatch in the other direction. Callers pass the dialect of
234
+ * the shell they are actually going to use; omitting it falls back to the host
235
+ * default.
236
+ */
237
+ interface CommandPolicyOptions {
238
+ dialect?: ShellDialect;
239
+ /** Only flags generated from vetted DNS answers by BaseFileSystem, never caller input. */
240
+ trustedWebResolves?: ReadonlySet<string>;
241
+ autonomous?: boolean;
242
+ approvalMode?: 'ask' | 'allow' | 'deny';
243
+ /**
244
+ * `CDPATH` is set in the environment the command will run in. A relative
245
+ * `cd` may then land in a directory the command line never names, so `cd`
246
+ * stops being navigation the classifier can follow.
247
+ */
248
+ cdpathSet?: boolean;
249
+ /** The planner's standing approvals (ADR-0026); an `ask` command they cover carries {@link CommandClassification.allowedBy}. */
250
+ allow?: PlannerAllowlist;
251
+ }
252
+ type CommandTier = 'auto' | 'ask' | 'refuse';
253
+ interface CommandClassification {
254
+ tier: CommandTier;
255
+ /** A human must approve this invocation even when its origin was already granted. */
256
+ perInvocation?: boolean;
257
+ /** Separate grants for commands that name several origins. */
258
+ scopes?: string[];
259
+ webUrls?: URL[];
260
+ webOutboundTexts?: string[];
261
+ /** What a grant covers, for `ask`. Derived from the non-auto segments only. */
262
+ scope: string;
263
+ /** Populated for `refuse`: why, in a sentence the model can act on. */
264
+ reason?: string;
265
+ /** For `ask`: the allowlist rule that pre-approves every part of the command that needed asking. */
266
+ allowedBy?: string;
267
+ }
268
+ /**
269
+ * Classify one command line. Output redirection is refused outright unless the
270
+ * target provably writes nothing — `/dev/null`, or an fd duplication like
271
+ * `2>&1` — since a planner that writes files has stopped being a planner.
272
+ */
273
+ declare function classifyCommand(command: string, opts?: CommandPolicyOptions): CommandClassification;
274
+
275
+ /**
276
+ * Decides whether one out-of-envelope capability may run, and remembers the
277
+ * answer for the rest of the session.
278
+ *
279
+ * Grants are keyed on {@link ApprovalRequest.scope}, never on the concrete
280
+ * subject: approving a read of `/tmp/foo/a.log` grants `/tmp/foo/*`, and
281
+ * approving `az group list` grants `az group list`. Without that, a planner
282
+ * doing real research would prompt on every single call and the feature would
283
+ * be unusable.
284
+ *
285
+ * `mode` is the policy floor:
286
+ * ask consult the human channel; no channel means deny (headless, web)
287
+ * allow grant everything the tier system did not already refuse
288
+ * deny grant nothing beyond `preApproved` — the allowlist-only mode
289
+ *
290
+ * Note the ordering: `preApproved` is honored under every mode including
291
+ * `deny`, because it is an explicit operator decision rather than a default.
292
+ * Its entries are allowlist rules (see `plannerAllowlist.ts`), and both it and
293
+ * `mode` may be read afresh on every request, so a settings change applies to
294
+ * the next call.
295
+ */
296
+ type ApprovalMode = 'ask' | 'allow' | 'deny';
297
+ /**
298
+ * The configured mode. `auto` follows the autonomy level (ADR-0026): Guarded
299
+ * asks, Full runs the allowlist alone and asks nobody, so a planner in Full
300
+ * never waits on a person and never acts outside its standing approvals.
301
+ */
302
+ type ApprovalModeSetting = ApprovalMode | 'auto';
303
+ declare function effectiveApprovalMode(setting: ApprovalModeSetting, autonomous: boolean, kind?: ApprovalKind): ApprovalMode;
304
+ /** A configured mode as written: `allowlist` is the user-facing name of `deny`, and anything unknown is `auto`. */
305
+ declare function parseApprovalModeSetting(raw: string | undefined): ApprovalModeSetting;
306
+ interface ApprovalPolicyOptions {
307
+ /** Read per request, for the kind being asked about. */
308
+ mode?: ApprovalMode | ((kind: ApprovalKind) => ApprovalMode);
309
+ autonomous?: () => boolean;
310
+ /** Allowlist entries granted up front from config (ADR-0026). */
311
+ preApproved?: readonly string[] | (() => readonly string[]);
312
+ /** The human channel. Absent means there is nobody to ask. */
313
+ ask?: (req: ApprovalRequest) => Promise<boolean>;
314
+ /** Called whenever a decision is reached, for surfacing in a UI or a log. */
315
+ onDecision?: (req: ApprovalRequest, granted: boolean, source: ApprovalSource) => void;
316
+ }
317
+ /**
318
+ * Why a request was decided as it was. The next four are refusals reached
319
+ * before the policy itself is consulted (ADR-0027's web-read guards), handed
320
+ * to {@link ApprovalPolicy.record} so they surface like any other decision:
321
+ * an `!` allowlist exclusion, the scheme and SSRF guard, the outbound secret
322
+ * check, and the per-turn web-read budget. `unsupported` comes after: an
323
+ * approved project MCP server the planner's runner version cannot give the
324
+ * planner as tools of their own (ADR-0028).
325
+ */
326
+ type ApprovalSource = 'pre-approved' | 'remembered' | 'mode' | 'asked' | 'no-channel' | 'excluded' | 'guard' | 'secret' | 'budget' | 'unsupported';
327
+ declare class ApprovalPolicy implements IApproval {
328
+ private readonly modeOf;
329
+ private readonly autonomousOf;
330
+ private readonly entriesOf;
331
+ private parsed;
332
+ private readonly asker?;
333
+ private readonly onDecision?;
334
+ private readonly granted;
335
+ private readonly refused;
336
+ /** One in-flight ask per scope: a parallel tool round must not prompt twice for the same thing. */
337
+ private readonly inFlight;
338
+ private generation;
339
+ constructor(opts?: ApprovalPolicyOptions);
340
+ request(req: ApprovalRequest): Promise<boolean>;
341
+ commandPolicy(): Pick<CommandPolicyOptions, 'autonomous' | 'approvalMode'>;
342
+ /**
343
+ * What `request` would answer without a person, for a caller that may never
344
+ * prompt (a research subagent): the same standing grants, remembered
345
+ * refusals and mode the interactive path uses, stopping at `ask`.
346
+ */
347
+ peek(req: ApprovalRequest): StandingDecision;
348
+ private standing;
349
+ /** Report a decision a guard reached before this policy was asked. */
350
+ record(req: ApprovalRequest, granted: boolean, source: ApprovalSource): void;
351
+ grantsUnasked({ kind, scope }: Pick<ApprovalRequest, 'kind' | 'scope'>): boolean;
352
+ /** The standing approvals, parsed from the entries in force now. */
353
+ allowlist(): PlannerAllowlist;
354
+ /** Scopes the user has granted this session — for display and for persistence. */
355
+ grantedScopes(): string[];
356
+ /** Drop every session-scoped decision. Called on session reset. */
357
+ reset(): void;
358
+ }
359
+
77
360
  /**
78
361
  * The planner's single approval seam. Every capability that reaches beyond the
79
362
  * default read-only, in-workspace envelope — a path outside the workspace root,
@@ -91,11 +374,16 @@ declare const DEFAULT_PLANNER_ALLOWLIST: readonly string[];
91
374
  * for an answer as long as it takes, and never passes through the planner's
92
375
  * policy — the runner's mode already decided it needed asking. `mcp_tool` is
93
376
  * a harness planner reaching for an MCP tool the user configured (ADR-0026),
94
- * scoped to that one tool.
377
+ * scoped to that one tool. `web_search` is scoped to the search provider, the
378
+ * party that receives every query (ADR-0027). `mcp_server` is starting an MCP
379
+ * server the repository declares for a planner, scoped to its definition
380
+ * (ADR-0028).
95
381
  */
96
- type ApprovalKind = 'external_path' | 'shell_command' | 'url_fetch' | 'mcp_tool' | 'runner_tool';
382
+ type ApprovalKind = 'external_path' | 'shell_command' | 'url_fetch' | 'web_search' | 'mcp_tool' | 'mcp_server' | 'runner_tool';
97
383
  interface ApprovalRequest {
98
384
  kind: ApprovalKind;
385
+ /** Credentials, data and redirects need a fresh human answer for each invocation. */
386
+ perInvocation?: boolean;
99
387
  /** The concrete thing being asked about: an absolute path, a command line, a URL. */
100
388
  subject: string;
101
389
  /**
@@ -143,10 +431,32 @@ declare function toApprovalDecision(answer: ApprovalAnswer): ApprovalDecision;
143
431
  declare function isGranted(decision: ApprovalDecision): boolean;
144
432
  declare function approvalScopes(request: ApprovalRequest): string[];
145
433
  declare function isRunnerApproval(request: ApprovalRequest): boolean;
434
+ type StandingDecision = {
435
+ decision: 'allow' | 'deny';
436
+ source: 'pre-approved' | 'remembered' | 'mode';
437
+ } | {
438
+ decision: 'deny';
439
+ source: 'excluded';
440
+ } | {
441
+ decision: 'ask';
442
+ source: 'no-channel';
443
+ };
146
444
  interface IApproval {
147
445
  request(req: ApprovalRequest): Promise<boolean>;
148
446
  /** The standing approvals a command is classified against, when this approval has any (ADR-0026). */
149
447
  allowlist?(): PlannerAllowlist;
448
+ commandPolicy?(): Pick<CommandPolicyOptions, 'autonomous' | 'approvalMode'>;
449
+ /** Surface a decision reached before `request` was called, as `request`'s own are. */
450
+ record?(req: ApprovalRequest, granted: boolean, source: ApprovalSource): void;
451
+ /**
452
+ * Resolve a request without a person (ADR-0027): `allow` when it is already
453
+ * granted or the mode allows without asking, `deny` when a refusal is
454
+ * remembered or the mode forbids, `ask` when it would still need a prompt. A
455
+ * background research subagent never prompts, so `ask` means refused.
456
+ */
457
+ peek?(req: ApprovalRequest): StandingDecision;
458
+ /** Whether `request` would grant this scope now without asking anyone. Nothing is asked or recorded. */
459
+ grantsUnasked?(req: Pick<ApprovalRequest, 'kind' | 'scope'>): boolean;
150
460
  }
151
461
  /** Denies everything. The safe default when a surface wires no approval channel. */
152
462
  declare const DENY_ALL: IApproval;
@@ -179,6 +489,12 @@ declare const listModelsInput: z.ZodObject<{
179
489
  declare const runCommandInput: z.ZodObject<{
180
490
  command: z.ZodString;
181
491
  }, z.core.$strip>;
492
+ declare const fetchInput: z.ZodObject<{
493
+ url: z.ZodString;
494
+ }, z.core.$strip>;
495
+ declare const webSearchInput: z.ZodObject<{
496
+ query: z.ZodString;
497
+ }, z.core.$strip>;
182
498
  declare const loadSkillInput: z.ZodObject<{
183
499
  name: z.ZodString;
184
500
  }, z.core.$strip>;
@@ -274,6 +590,8 @@ type ListRunnersArgs = z.infer<typeof listRunnersInput>;
274
590
  type ListModelsArgs = z.infer<typeof listModelsInput>;
275
591
  type LoadSkillArgs = z.infer<typeof loadSkillInput>;
276
592
  type RunCommandArgs = z.infer<typeof runCommandInput>;
593
+ type FetchArgs = z.infer<typeof fetchInput>;
594
+ type WebSearchArgs = z.infer<typeof webSearchInput>;
277
595
  type SubmitPlanArgs = z.infer<typeof submitPlanInput>;
278
596
  type EditPlanArgs = z.infer<typeof editPlanInput>;
279
597
  type TaskQueryArgs = z.infer<typeof taskQueryInput>;
@@ -309,6 +627,8 @@ interface TaskToolHandler {
309
627
  interface PlannerToolHandler {
310
628
  loadSkill?: Run<LoadSkillArgs>;
311
629
  runCommand?: Run<RunCommandArgs>;
630
+ fetch?: Run<FetchArgs>;
631
+ webSearch?: Run<WebSearchArgs>;
312
632
  listRunners?: Run<ListRunnersArgs>;
313
633
  listModels?: Run<ListModelsArgs>;
314
634
  submitPlan?: Run<SubmitPlanArgs>;
@@ -352,11 +672,19 @@ interface TaskTokenScope {
352
672
  }
353
673
  interface PlannerTokenScope {
354
674
  sessionId: string;
675
+ /**
676
+ * Planner tools this token neither lists nor serves, by their names on the
677
+ * server: the ones a runner carries natively instead (ADR-0027), which a
678
+ * model would otherwise pick over its own.
679
+ */
680
+ hidden?: readonly string[];
355
681
  }
356
682
  /** Where a runner reaches the server and what it presents there. Never put either half in a prompt. */
357
683
  interface McpCredential {
358
684
  url: string;
359
685
  token: string;
686
+ /** The planner tools the token hides ({@link PlannerTokenScope.hidden}), so the runner is told of and pre-allows only what it is served. */
687
+ hidden?: readonly string[];
360
688
  }
361
689
  declare class OrdewellMcpServer {
362
690
  private readonly grants;
@@ -370,6 +698,8 @@ declare class OrdewellMcpServer {
370
698
  get url(): string | undefined;
371
699
  issueTaskToken(scope: TaskTokenScope, handler?: TaskToolHandler): Promise<McpCredential>;
372
700
  issuePlannerToken(scope: PlannerTokenScope, handler?: PlannerToolHandler): Promise<McpCredential>;
701
+ /** Every token still good here: secrets the outbound check keeps out of a URL (ADR-0027). */
702
+ heldTokens(): string[];
373
703
  /** Refuse every later request carrying `token`, and abort the calls it has in flight. */
374
704
  revoke(token: string): void;
375
705
  dispose(): Promise<void>;
@@ -386,6 +716,8 @@ interface McpClientConfig {
386
716
  name: typeof ORDEWELL_MCP_SERVER_NAME;
387
717
  url: string;
388
718
  headers: Record<string, string>;
719
+ /** Planner tools the token hides: a runner must neither pre-allow nor mention them. */
720
+ hidden?: readonly string[];
389
721
  }
390
722
  /**
391
723
  * The one shape every runner adapter renders its own MCP configuration from.
@@ -407,6 +739,23 @@ interface OwnerOnlyFile {
407
739
  */
408
740
  declare function ownerOnlyConfigFile(fileName: string, contents: string): OwnerOnlyFile;
409
741
 
742
+ /**
743
+ * One MCP server a repository declares. `definition` is the entry as
744
+ * Ordewell parsed it, in the runner's own shape: an approved server is
745
+ * handed to the runner as exactly this, never re-read from the file.
746
+ */
747
+ interface ProjectMcpServer {
748
+ name: string;
749
+ /** The file that declares it, relative to the workspace. */
750
+ source: string;
751
+ transport: 'stdio' | 'remote';
752
+ /** What would run, as written: the command line, or the URL. */
753
+ target: string;
754
+ /** The variables it sets, or the headers it sends, by name. */
755
+ names: string[];
756
+ definition: Record<string, unknown>;
757
+ }
758
+
410
759
  /**
411
760
  * The harness-planner transport contract (ADR-0009).
412
761
  *
@@ -575,12 +924,25 @@ type PlannerAsk =
575
924
  } | {
576
925
  kind: 'fetch';
577
926
  url: string;
927
+ }
928
+ /**
929
+ * A search the runner carries itself, at `provider`; Ordewell decides it, it does not run it.
930
+ * `providerLabel` names that party in the approval.
931
+ */
932
+ | {
933
+ kind: 'search';
934
+ query: string;
935
+ provider: string;
936
+ providerLabel?: string;
578
937
  } | {
579
938
  kind: 'path';
580
939
  path: string;
581
940
  directory: boolean;
582
- } | {
941
+ }
942
+ /** `note` is why, for a tool the runner knows the planner must not use, in place of the envelope's generic refusal. */
943
+ | {
583
944
  kind: 'other';
945
+ note?: string;
584
946
  };
585
947
  interface AgentStartCommon {
586
948
  /** Workspace root. The agent works from here and, in read-only mode, cannot leave it. */
@@ -603,6 +965,20 @@ interface PlannerStartOptions extends AgentStartCommon {
603
965
  effort?: string;
604
966
  /** The Ordewell MCP server to inject, pre-authorized (ADR-0022). Only for an adapter with {@link AgentAdapter.mcpAttached}. */
605
967
  mcp?: McpClientConfig;
968
+ /**
969
+ * Whether the runner's own search is on, for a runner whose search raises
970
+ * no permission request and is set on or off when its session starts
971
+ * (ADR-0027). Decided once, before the planner token is issued, so the
972
+ * tools the token serves and the session's config cannot disagree. Absent
973
+ * means off.
974
+ */
975
+ nativeSearch?: boolean;
976
+ /**
977
+ * The repository's own MCP servers this planner may start, as approved
978
+ * (ADR-0028). The runner is launched without the repository's config, so
979
+ * these are the only ones it gets, and each is handed over as parsed.
980
+ */
981
+ projectServers?: ProjectMcpServer[];
606
982
  }
607
983
  /**
608
984
  * What a runner manifest says the task's mode and effort mean (ADR-0001),
@@ -670,6 +1046,12 @@ interface AgentAdapter {
670
1046
  * adapter that cannot inject the server at all, which cannot run tasks.
671
1047
  */
672
1048
  mcpAttached?(): Promise<boolean>;
1049
+ /**
1050
+ * The approved project servers (ADR-0028) this runner started but cannot
1051
+ * give the planner as tools of their own, known once `start` resolved.
1052
+ * Absent on an adapter that gives the planner every one it starts.
1053
+ */
1054
+ unusableProjectServers?(): ProjectMcpServer[];
673
1055
  /**
674
1056
  * Answer an open `permission_request`. False when the id is not open — it
675
1057
  * was answered, cancelled, or never asked. Absent on an adapter that leaves
@@ -865,68 +1247,6 @@ interface RunnerEntry {
865
1247
  manifest: RunnerManifest;
866
1248
  }
867
1249
 
868
- /**
869
- * Decides whether one out-of-envelope capability may run, and remembers the
870
- * answer for the rest of the session.
871
- *
872
- * Grants are keyed on {@link ApprovalRequest.scope}, never on the concrete
873
- * subject: approving a read of `/tmp/foo/a.log` grants `/tmp/foo/*`, and
874
- * approving `az group list` grants `az group list`. Without that, a planner
875
- * doing real research would prompt on every single call and the feature would
876
- * be unusable.
877
- *
878
- * `mode` is the policy floor:
879
- * ask consult the human channel; no channel means deny (headless, web)
880
- * allow grant everything the tier system did not already refuse
881
- * deny grant nothing beyond `preApproved` — the allowlist-only mode
882
- *
883
- * Note the ordering: `preApproved` is honored under every mode including
884
- * `deny`, because it is an explicit operator decision rather than a default.
885
- * Its entries are allowlist rules (see `plannerAllowlist.ts`), and both it and
886
- * `mode` may be read afresh on every request, so a settings change applies to
887
- * the next call.
888
- */
889
- type ApprovalMode = 'ask' | 'allow' | 'deny';
890
- /**
891
- * The configured mode. `auto` follows the autonomy level (ADR-0026): Guarded
892
- * asks, Full runs the allowlist alone and asks nobody, so a planner in Full
893
- * never waits on a person and never acts outside its standing approvals.
894
- */
895
- type ApprovalModeSetting = ApprovalMode | 'auto';
896
- declare function effectiveApprovalMode(setting: ApprovalModeSetting, autonomous: boolean): ApprovalMode;
897
- /** A configured mode as written: `allowlist` is the user-facing name of `deny`, and anything unknown is `auto`. */
898
- declare function parseApprovalModeSetting(raw: string | undefined): ApprovalModeSetting;
899
- interface ApprovalPolicyOptions {
900
- mode?: ApprovalMode | (() => ApprovalMode);
901
- /** Allowlist entries granted up front from config (ADR-0026). */
902
- preApproved?: readonly string[] | (() => readonly string[]);
903
- /** The human channel. Absent means there is nobody to ask. */
904
- ask?: (req: ApprovalRequest) => Promise<boolean>;
905
- /** Called whenever a decision is reached, for surfacing in a UI or a log. */
906
- onDecision?: (req: ApprovalRequest, granted: boolean, source: ApprovalSource) => void;
907
- }
908
- type ApprovalSource = 'pre-approved' | 'remembered' | 'mode' | 'asked' | 'no-channel';
909
- declare class ApprovalPolicy implements IApproval {
910
- private readonly modeOf;
911
- private readonly entriesOf;
912
- private parsed;
913
- private readonly asker?;
914
- private readonly onDecision?;
915
- private readonly granted;
916
- private readonly refused;
917
- /** One in-flight ask per scope: a parallel tool round must not prompt twice for the same thing. */
918
- private readonly inFlight;
919
- private generation;
920
- constructor(opts?: ApprovalPolicyOptions);
921
- request(req: ApprovalRequest): Promise<boolean>;
922
- /** The standing approvals, parsed from the entries in force now. */
923
- allowlist(): PlannerAllowlist;
924
- /** Scopes the user has granted this session — for display and for persistence. */
925
- grantedScopes(): string[];
926
- /** Drop every session-scoped decision. Called on session reset. */
927
- reset(): void;
928
- }
929
-
930
1250
  interface CatalogModel {
931
1251
  id: string;
932
1252
  name: string;
@@ -1102,6 +1422,8 @@ interface IConfig {
1102
1422
  approvalPreApproved: string[];
1103
1423
  /** Whether the built-in allowlist of read-only command families and MCP tools applies. */
1104
1424
  approvalDefaults: boolean;
1425
+ /** How many fetches and searches one planner turn may make (ADR-0027). User-only, like the approval settings. */
1426
+ webReadBudget: number;
1105
1427
  /** Get the base URL for an OpenAI-compatible provider. */
1106
1428
  getProviderBaseUrl(provider: AiProvider): string;
1107
1429
  /** Get the API key for a provider. */
@@ -1296,4 +1618,4 @@ interface IRunner {
1296
1618
  activeCount: number;
1297
1619
  }
1298
1620
 
1299
- export { type ProviderModelsResult as $, type AgentAdapter as A, type McpCredential as B, type CatalogModel as C, DEFAULT_PLANNER_ALLOWLIST as D, type EditPlanArgs as E, type FetchAllProviderModelsOptions as F, type McpTool as G, type McpToolContext as H, type IApproval as I, type McpToolReply as J, ModelCatalog as K, LineBuffer as L, type McpClientConfig as M, type ModelShortcut as N, ORCHESTRATOR_SHORTCUTS as O, ORDEWELL_MCP_PATH as P, ORDEWELL_MCP_SERVER_NAME as Q, type OrchestratorOption as R, OrdewellMcpServer as S, type OwnerOnlyFile as T, PLANNER_TOOLS as U, type PlannerAllowlist as V, type PlannerStartOptions as W, type PlannerTokenScope as X, type PlannerToolHandler as Y, type ProviderCredentialSource as Z, type ProviderModelLists as _, type AgentAdapterFactory as a, type QueuedTaskMessage as a0, type RunCommandArgs as a1, type RunnerEntry as a2, type RunnerFeatures as a3, type RunnerManifest as a4, type RunnerMode as a5, type RunnerModelDiscovery as a6, RunnerRegistry as a7, type SpawnFn as a8, type StructuredEvent as a9, resolveModelShortcut as aA, resolveProvider as aB, scopeAllowed as aC, sharedMcpServer as aD, toApprovalDecision as aE, toOrchestratorOptions as aF, toolAllowed as aG, type StructuredTurnEnd as aa, type SubmitPlanArgs as ab, TASK_COMPLETE_TOOL as ac, TASK_TOOLS as ad, type TaskCompleteArgs as ae, type TaskModeAgentAdapter as af, TaskModeUnsupportedError as ag, type TaskOutputArgs as ah, type TaskQueryArgs as ai, type TaskRunnerFlags as aj, type TaskStartOptions as ak, type TaskTokenScope as al, type TaskToolHandler as am, approvalScopes as an, collectProviderCredentials as ao, commandAllowedBy as ap, effectiveApprovalMode as aq, enabledRunners as ar, fetchAllProviderModels as as, isGranted as at, isRunnerApproval as au, knownModelId as av, mcpClientConfig as aw, ownerOnlyConfigFile as ax, parseAllowlist as ay, parseApprovalModeSetting as az, 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, type ApprovalModeSetting as k, ApprovalPolicy as l, type ApprovalPolicyOptions as m, type ApprovalRequest as n, type ApprovalSource as o, type CheckpointAnswer as p, type CheckpointArgs as q, type CommandWords as r, DENY_ALL as s, type DiscoveryCommand as t, type IConfig as u, type IRunner as v, type IRunnerSession as w, type ListModelsArgs as x, type ListRunnersArgs as y, type LoadSkillArgs as z };
1621
+ export { type PlannerTokenScope as $, AUTO_COMMANDS as A, type ListModelsArgs as B, type CatalogModel as C, DEFAULT_PLANNER_ALLOWLIST as D, type EditPlanArgs as E, type FetchAllProviderModelsOptions as F, GIT_READONLY_SUBCOMMANDS as G, type ListRunnersArgs as H, type IApproval as I, type LoadSkillArgs as J, type McpCredential as K, LineBuffer as L, type McpClientConfig as M, type McpTool as N, type McpToolContext as O, type McpToolReply as P, ModelCatalog as Q, type ModelShortcut as R, ORCHESTRATOR_SHORTCUTS as S, ORDEWELL_MCP_PATH as T, ORDEWELL_MCP_SERVER_NAME as U, type OrchestratorOption as V, OrdewellMcpServer as W, type OwnerOnlyFile as X, PLANNER_TOOLS as Y, type PlannerAllowlist as Z, type PlannerStartOptions as _, type AgentAdapter as a, type PlannerToolHandler as a0, type ProjectMcpServer as a1, type ProviderCredentialSource as a2, type ProviderModelLists as a3, type ProviderModelsResult as a4, type QueuedTaskMessage as a5, REFUSED_COMMANDS as a6, type ResearchShell as a7, type ResearchShellDeps as a8, type RunCommandArgs as a9, collectProviderCredentials as aA, commandAllowedBy as aB, effectiveApprovalMode as aC, enabledRunners as aD, fetchAllProviderModels as aE, isGranted as aF, isRunnerApproval as aG, knownModelId as aH, mcpClientConfig as aI, ownerOnlyConfigFile as aJ, parseAllowlist as aK, parseApprovalModeSetting as aL, researchShellWarning as aM, researchToolsPath as aN, resolveModelShortcut as aO, resolveProvider as aP, resolveResearchShell as aQ, scopeAllowed as aR, sharedMcpServer as aS, toApprovalDecision as aT, toOrchestratorOptions as aU, toolAllowed as aV, type RunnerEntry as aa, type RunnerFeatures as ab, type RunnerManifest as ac, type RunnerMode as ad, type RunnerModelDiscovery as ae, RunnerRegistry as af, type ShellDialect as ag, type SpawnFn as ah, type StandingDecision as ai, type StructuredEvent as aj, type StructuredTurnEnd as ak, type SubmitPlanArgs as al, TASK_COMPLETE_TOOL as am, TASK_TOOLS as an, type TaskCompleteArgs as ao, type TaskModeAgentAdapter as ap, TaskModeUnsupportedError as aq, type TaskOutputArgs as ar, type TaskQueryArgs as as, type TaskRunnerFlags as at, type TaskStartOptions as au, type TaskTokenScope as av, type TaskToolHandler as aw, approvalScopes as ax, classifyCommand as ay, clearResearchShellCache as az, type AgentAdapterFactory as b, type AgentEvent as c, type AgentProcessDeps as d, type AgentStartOptions as e, type AiProvider as f, type AllProviderModels as g, type ApprovalAnswer as h, type ApprovalDecision as i, type ApprovalKind as j, type ApprovalMode as k, type ApprovalModeSetting as l, ApprovalPolicy as m, type ApprovalPolicyOptions as n, type ApprovalRequest as o, type ApprovalSource as p, type CheckpointAnswer as q, type CheckpointArgs as r, type CommandClassification as s, type CommandTier as t, type CommandWords as u, DENY_ALL as v, type DiscoveryCommand as w, type IConfig as x, type IRunner as y, type IRunnerSession as z };