@yaklabs-ai/plugin-sdk 0.13.6 → 0.13.8

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.
@@ -0,0 +1,1217 @@
1
+ /**
2
+ * The URI module for one vendored-MCP integration plugin.
3
+ *
4
+ * What this module is FOR is worth stating plainly, because it is not what Linear's module does.
5
+ * A vendored-MCP plugin's value is three things: the vendor's MCP server (the whole action
6
+ * surface), recognition of the vendor's public links, and a branded, openable pill for them. This
7
+ * module is the second and third. It projects a pasted vendor URL onto a canonical `<ns>://` URI
8
+ * and back, and it answers `read` from the URI alone.
9
+ *
10
+ * ## Why `read` does not call MCP
11
+ *
12
+ * It could, and V1 deliberately does not. These servers have no uniform read: several expose only
13
+ * tools, and PostHog's is a single `exec` tool over a CLI-style DSL. There is no shape to project
14
+ * generically, so a generic MCP read would be a guess per vendor wearing one function's clothes.
15
+ * Instead `read` returns a reference card — what the resource is, its captured ids, the URL that
16
+ * opens it, and the server to call for anything deeper. When a vendor earns a real projection, it
17
+ * attaches as a per-vendor `read` beside this one; the shared card stays the floor.
18
+ *
19
+ * ## Why there is no `requiresAuth`
20
+ *
21
+ * Linear's module declares one because its `read` calls MCP and genuinely needs a credential.
22
+ * This module's `read` is a pure projection of the URI it was handed — no network, no credential.
23
+ * Declaring `requiresAuth` anyway would not be a harmless formality: an auth-gated module whose
24
+ * binding is unsatisfied has its public-web `urlPattern` claims suppressed and its `matches()`
25
+ * wrapped to decline `http(s)` (`agent-session.ts` `_suppressPublicUrlClaims`, and
26
+ * `authSuppressedCatalogModule` for discovery). A pasted Supabase link would therefore stay
27
+ * unbranded for every user who has not yet connected Supabase — which is exactly the user the
28
+ * branded pill is for. The vendor's MCP tools remain gated on their own, by the MCP host.
29
+ */
30
+ export declare function createVendoredMcpUriModule(descriptor: VendoredMcpDescriptor): UriExtensionModule;
31
+
32
+ declare type JsonPrimitive = string | number | boolean | null;
33
+
34
+ declare type JsonSchema = Record<string, unknown>;
35
+
36
+ declare type JsonValue = JsonPrimitive | {
37
+ readonly [key: string]: JsonValue;
38
+ } | readonly JsonValue[];
39
+
40
+ declare interface LineRange {
41
+ readonly start: number;
42
+ readonly end: number;
43
+ }
44
+
45
+ declare interface LineSelector {
46
+ readonly ranges: readonly LineRange[];
47
+ readonly raw: boolean;
48
+ }
49
+
50
+ declare interface RawResource {
51
+ readonly uri: string;
52
+ readonly content: string | Uint8Array;
53
+ readonly mimeType: string;
54
+ readonly metadata?: RawResourceMetadata;
55
+ readonly selector?: SelectorDisposition;
56
+ }
57
+
58
+ declare interface RawResourceCacheControl {
59
+ /** False marks degraded/fallback content that read-through caches must not store. */
60
+ readonly store?: boolean;
61
+ }
62
+
63
+ declare interface RawResourceIdentity {
64
+ readonly id: string;
65
+ readonly version: string;
66
+ readonly variantId?: string;
67
+ }
68
+
69
+ declare interface RawResourceMetadata {
70
+ readonly size?: number;
71
+ readonly finalUrl?: string;
72
+ /** Provider resource MIME type; absent when the source does not report one. */
73
+ readonly sourceMimeType?: string;
74
+ readonly notes?: readonly string[];
75
+ readonly cache?: RawResourceCacheControl;
76
+ /**
77
+ * The absolute local filesystem path the resource's bytes were read from,
78
+ * when the transport read them from local disk. Converter-facing
79
+ * provenance: finalize threads it into the content probe so a converter
80
+ * can direct shell tools at the file (e.g. archive extraction). Absent for
81
+ * remote/synthesized content.
82
+ */
83
+ readonly localPath?: string;
84
+ /**
85
+ * Opaque source-native revision suitable only for a later equality check.
86
+ * A host records it as the session's witness for this resource; when a
87
+ * module reports none, the host records a content digest of `content`
88
+ * instead (`contentDigest`, prefixed `sha256:`), which the module can
89
+ * reproduce at mutation time from the same content its read returns.
90
+ */
91
+ readonly revision?: string;
92
+ /**
93
+ * Opaque immutable identity for the exact resource bytes returned by a
94
+ * provider. Transport preserves it for observers; it never parses it.
95
+ */
96
+ readonly resourceIdentity?: RawResourceIdentity;
97
+ /**
98
+ * The resource's stable public address when `uri` reports something more
99
+ * specific than the address a consumer names it by — a retained revision
100
+ * (`skill://<id>/@revision/<version>`) served for a `skill://<source>/<name>`
101
+ * read. Present only when the two differ. Consumers that attribute by name
102
+ * (the read tool's `skillName` detail, and everything keyed off it) read
103
+ * this; consumers that need the exact bytes keep `uri`.
104
+ */
105
+ readonly canonicalUri?: string;
106
+ }
107
+
108
+ /** One all-of arm in an ordered scope requirement. */
109
+ declare interface ScopeAlternative {
110
+ readonly allOf: readonly ScopeNeed[];
111
+ }
112
+
113
+ /** One scope in an authorization requirement; `reason` is presentation metadata only. */
114
+ declare interface ScopeNeed {
115
+ readonly scope: string;
116
+ readonly reason?: string;
117
+ }
118
+
119
+ /**
120
+ * The minimum OAuth authority for one action.
121
+ *
122
+ * Alternatives are ordered by acquisition preference. Evaluation accepts the first covered arm;
123
+ * when none is covered, callers request only the first arm rather than unioning unrelated grants.
124
+ */
125
+ declare interface ScopeRequirement {
126
+ readonly purpose: string;
127
+ readonly alternatives: readonly ScopeAlternative[];
128
+ }
129
+
130
+ declare type SelectorDisposition = {
131
+ readonly kind: "deferred";
132
+ readonly window?: LineSelector;
133
+ } | {
134
+ readonly kind: "applied";
135
+ } | {
136
+ readonly kind: "invalid";
137
+ readonly message: string;
138
+ };
139
+
140
+ declare interface UriAgentHint {
141
+ readonly summary: string;
142
+ readonly commonFlows?: readonly UriCommonFlow[];
143
+ readonly helpUri?: string;
144
+ readonly triggers?: UriHintTriggers;
145
+ /**
146
+ * Skill invocation names (flat `localId`s, e.g. `"gmail-email-triage"`) that hold this module's
147
+ * deeper workflow judgment — the name a `read skill://<name>` dispatch resolves, not a display
148
+ * title or a path. Point here only at skills that actually exist in the composed skill roster;
149
+ * a dangling name sends the agent to a `skill://` read for nothing.
150
+ */
151
+ readonly skills?: readonly string[];
152
+ }
153
+
154
+ declare type UriAuthActionClassification = {
155
+ readonly kind: "action";
156
+ readonly actionId: string;
157
+ } | {
158
+ readonly kind: "public";
159
+ };
160
+
161
+ /**
162
+ * Finite authorization actions plus the pure route classifier that selects one before dispatch.
163
+ * An exception, an unknown action id, or `undefined` classification fails closed in the host.
164
+ */
165
+ declare interface UriAuthActionPolicy {
166
+ readonly actions: Readonly<Record<string, ScopeRequirement>>;
167
+ classifyOperation(request: {
168
+ readonly operation: UriOperation;
169
+ readonly uri: string;
170
+ }): UriAuthActionClassification | undefined;
171
+ }
172
+
173
+ declare interface UriAuthRef {
174
+ readonly serviceId: string;
175
+ readonly schemeId: string;
176
+ readonly accountId?: string;
177
+ }
178
+
179
+ declare interface UriAuthRequirement {
180
+ readonly serviceId: string;
181
+ /**
182
+ * Optional scheme preference. Absent binds service-level: any injectable scheme of the
183
+ * service satisfies the module, and an explicit account resolves against its own backing
184
+ * scheme. Present, it names the scheme resolution tries first.
185
+ */
186
+ readonly schemeId?: string;
187
+ /**
188
+ * The scopes this module needs its resolved credential to carry. Threaded by
189
+ * the host's dispatch into every credential load, so a grant-enforcing vault
190
+ * can refuse the module per call. Absent declares no scope requirement.
191
+ */
192
+ readonly requiredScopes?: readonly string[];
193
+ /**
194
+ * The service id connect guidance should NAME when this module is gated — a host-composed
195
+ * front door whose connect grants exactly this module's scopes (e.g. `gmail` expanding onto
196
+ * the shared `google.account` credential). Guidance only; resolution keys on
197
+ * `(serviceId, schemeId)`. Absent, guidance names `serviceId`.
198
+ */
199
+ readonly connectServiceId?: string;
200
+ }
201
+
202
+ declare type UriBackingCandidate = {
203
+ readonly path: string;
204
+ readonly root: string;
205
+ readonly relativePath?: never;
206
+ /** Optional display label for where the content canonically comes from (e.g. its source repository), rendered beside the path in projection metadata. Opaque display text; never parsed. */
207
+ readonly sourceRepositoryLabel?: string;
208
+ } | {
209
+ /** Path relative to the sole host-protected root owned by this module. */
210
+ readonly relativePath: string;
211
+ readonly path?: never;
212
+ readonly root?: never;
213
+ /** Optional display label for where the content canonically comes from (e.g. its source repository), rendered beside the path in projection metadata. Opaque display text; never parsed. */
214
+ readonly sourceRepositoryLabel?: string;
215
+ };
216
+
217
+ declare interface UriCacheInvalidation {
218
+ /** Drop cached entries whose cache URI starts with this prefix. */
219
+ readonly prefix?: string;
220
+ /** Drop the exact cache URI. */
221
+ readonly uri?: string;
222
+ }
223
+
224
+ declare interface UriCachePolicy {
225
+ /** Read-family cache TTL in milliseconds; absent or zero means not cached. */
226
+ readonly ttlMs?: number;
227
+ /** Mutation directive applied after a successful write/edit/delete. */
228
+ readonly invalidate?: readonly UriCacheInvalidation[];
229
+ }
230
+
231
+ /**
232
+ * Resolve cache policy for a concrete URI operation. User-specific modules that
233
+ * return a policy must declare `requiresAuth` or `requiresConnection` so hosts
234
+ * can partition cache keys by identity.
235
+ */
236
+ declare type UriCachePolicyResolver = (operation: UriOperation, cacheUri: string, ctx: UriResolveContext) => UriCachePolicy | undefined;
237
+
238
+ declare type UriClaim = UriSchemeClaim | UriPatternClaim | UrlPatternClaim;
239
+
240
+ declare interface UriCommonFlow {
241
+ readonly label: string;
242
+ readonly when?: string;
243
+ readonly example: string;
244
+ }
245
+
246
+ declare interface UriConnection {
247
+ readonly id: string;
248
+ readonly moduleId: string;
249
+ readonly adapterId: string;
250
+ readonly displayName: string;
251
+ readonly authRef?: UriAuthRef;
252
+ readonly metadata?: Record<string, unknown>;
253
+ /**
254
+ * Carries the vault's default flag for the account behind this row. A
255
+ * selector-less address resolves to the single flagged row; the flag is per
256
+ * service + scheme, so a module that accepts two schemes (GitHub OAuth and
257
+ * PAT) can see two, which resolves as ambiguous rather than guessing.
258
+ */
259
+ readonly isDefault?: boolean;
260
+ }
261
+
262
+ /**
263
+ * The typed answer to "can this be undone?" for a route's `delete` verb.
264
+ *
265
+ * - `trash`: moved somewhere recoverable; `restore` names the address or payload that
266
+ * brings it back ("edit with {addLabels:['INBOX']}").
267
+ * - `permanent`: gone on success.
268
+ * - `conditional`: depends on the address or payload; `how` states the rule
269
+ * ("trash by default; ?permanent=true deletes permanently").
270
+ */
271
+ declare type UriDeleteEffect = {
272
+ readonly kind: "trash";
273
+ readonly restore?: string;
274
+ } | {
275
+ readonly kind: "permanent";
276
+ } | {
277
+ readonly kind: "conditional";
278
+ readonly how: string;
279
+ };
280
+
281
+ declare type UriDescribeContext = Pick<UriResolveContext, "cwd" | "sessionId">;
282
+
283
+ declare interface UriExample {
284
+ readonly task: string;
285
+ readonly uri: string;
286
+ readonly operation?: UriOperation;
287
+ }
288
+
289
+ declare interface UriExtensionModule extends Omit<UriModule, "verbs"> {
290
+ readonly verbs: UriVerbSupport;
291
+ /** Human-facing per-verb effect docs; see {@link UriVerbDocs}. */
292
+ readonly verbDocs?: UriVerbDocs;
293
+ /**
294
+ * Plugin-declared default permission per verb group. A verb group set to `"ask"`
295
+ * compiles into a defaults-level ask rule — the user is prompted by default but can
296
+ * override with an explicit allow. Undeclared = `"allow"` (fail-open).
297
+ * See {@link UriPermissionDefaults}.
298
+ */
299
+ readonly permissionDefaults?: UriPermissionDefaults;
300
+ /** Requires a resolved URI connection before any verb handler runs. */
301
+ readonly requiresConnection?: boolean;
302
+ /** Pure route-aware permission facts; see {@link UriExtensionOperationPermissionPolicy}. */
303
+ readonly permission?: UriExtensionOperationPermissionPolicy;
304
+ /** False rejects generic write-tool `source` copies before source fetch. */
305
+ readonly acceptsSourceCopy?: boolean;
306
+ }
307
+
308
+ declare interface UriExtensionOperationPermissionPolicy {
309
+ /**
310
+ * Classify one canonical route, purely. `write`/`edit`/`delete` may only be made stricter
311
+ * than the verb default; `help`/`read`/`list`/`search` must stay `"read"` (a `ruleKey` is
312
+ * allowed), because the kernel reruns a crash-interrupted read without asking again. Any
313
+ * other answer, or a throw, fails the call closed.
314
+ */
315
+ classifyOperation(request: {
316
+ readonly operation: UriOperation;
317
+ readonly uri: string;
318
+ }): {
319
+ readonly kind: "read" | "mutate" | "destructive" | "execute";
320
+ readonly ruleKey?: string;
321
+ } | undefined;
322
+ }
323
+
324
+ declare interface UriExternalCliRunner {
325
+ readonly run: (request: UriExternalCliRunRequest) => Promise<UriExternalCliRunResult>;
326
+ }
327
+
328
+ declare interface UriExternalCliRunRequest {
329
+ readonly args: readonly string[];
330
+ readonly cwd?: string;
331
+ readonly timeoutMs?: number;
332
+ readonly maxOutputChars?: number;
333
+ readonly signal?: AbortSignal;
334
+ }
335
+
336
+ declare interface UriExternalCliRunResult {
337
+ readonly exitCode: number;
338
+ readonly stdout: string;
339
+ readonly stderr: string;
340
+ readonly stdoutTruncated?: boolean;
341
+ readonly stderrTruncated?: boolean;
342
+ }
343
+
344
+ declare interface UriHelp {
345
+ readonly uri: string;
346
+ readonly title: string;
347
+ readonly summary: string;
348
+ readonly capabilities?: readonly UriNodeCapability[];
349
+ readonly templates?: readonly UriTemplate[];
350
+ readonly examples?: readonly UriExample[];
351
+ readonly notes?: readonly string[];
352
+ /**
353
+ * Set when this help, for the address it was asked about, carries text a third party wrote and
354
+ * the module relays as it received it (an MCP server's own tool names and descriptions), rather
355
+ * than text the module's author wrote. Absent: the module's own text.
356
+ */
357
+ readonly thirdPartyContent?: true;
358
+ }
359
+
360
+ declare interface UriHintTriggers {
361
+ readonly keywords?: readonly string[];
362
+ readonly patterns?: readonly string[];
363
+ }
364
+
365
+ declare interface UriListItem {
366
+ readonly uri: string;
367
+ readonly title?: string;
368
+ readonly summary?: string;
369
+ /** Provider resource MIME type; absent when the source does not report one. */
370
+ readonly sourceMimeType?: string;
371
+ readonly operations?: readonly UriOperation[];
372
+ }
373
+
374
+ declare interface UriListMetadata {
375
+ readonly totalCount?: number;
376
+ readonly totalCountLabel?: string;
377
+ readonly returnedCount?: number;
378
+ /**
379
+ * True when the provider could not search/enumerate the whole corpus for this
380
+ * page, so the result may be missing matches (e.g. a federated query that spans
381
+ * multiple stores and timed out part way). The caller should treat the result as
382
+ * partial and, where possible, narrow the scope. Omit when results are complete.
383
+ */
384
+ readonly incompleteSearch?: boolean;
385
+ }
386
+
387
+ declare interface UriListParams {
388
+ readonly cursor?: string;
389
+ readonly limit?: number;
390
+ }
391
+
392
+ declare interface UriListResult {
393
+ readonly uri: string;
394
+ readonly items: readonly UriListItem[];
395
+ readonly nextCursor?: string;
396
+ readonly metadata?: UriListMetadata;
397
+ }
398
+
399
+ declare type UriLocateContext = Pick<UriResolveContext, "cwd" | "signal" | "connection" | "sessionId" | "paths"> & {
400
+ /** Exact resource returned by the read whose supplemental backing is being located. */
401
+ readonly readResource?: RawResource;
402
+ };
403
+
404
+ declare type UriLocateResult = {
405
+ readonly kind: "candidate";
406
+ readonly candidate: UriBackingCandidate;
407
+ } | {
408
+ readonly kind: "malformed";
409
+ readonly message: string;
410
+ } | {
411
+ readonly kind: "not-disk-backed";
412
+ };
413
+
414
+ declare type UriMarkdownRenderInput = {
415
+ readonly operation: "help";
416
+ readonly uri: string;
417
+ readonly result: UriHelp;
418
+ } | {
419
+ readonly operation: "list";
420
+ readonly uri: string;
421
+ readonly result: UriListResult;
422
+ } | {
423
+ readonly operation: "search";
424
+ readonly uri: string;
425
+ readonly result: UriSearchResult;
426
+ } | {
427
+ readonly operation: Extract<UriOperation, "write" | "edit" | "delete">;
428
+ readonly uri: string;
429
+ readonly result: UriMutationResult;
430
+ };
431
+
432
+ declare interface UriModule {
433
+ readonly id: string;
434
+ /**
435
+ * Explicit human-readable name for catalog/display purposes (the plugin
436
+ * manifest's `contributes.uriModules[].displayName`, threaded through by
437
+ * hand since there is no automatic manifest→runtime-module composition
438
+ * step). Falls back to a mechanical id-derived title when absent — see
439
+ * `uriCatalogTitle`.
440
+ */
441
+ readonly title?: string;
442
+ readonly fallback?: boolean;
443
+ /**
444
+ * Whether this module parses the read tool's trailing line selector out of its own addresses.
445
+ * Absent or false: the host owns the explicit forms (`:50-200`, `:50-`, `:50+10`, `:raw`). The
446
+ * read tool strips one before dispatch, hands `read` the bare address, and applies the window
447
+ * to what comes back, so a module never has to know the grammar for the read tool's own
448
+ * `Continue with '<address>:N-M'` banner to work on its addresses (#10754). A bare trailing
449
+ * `:<digits>` is never taken from such a module: it is too common inside real ids (a Figma
450
+ * node `1:2`), so it reaches the module as written.
451
+ *
452
+ * Declare `true` only when the module must see the raw address: its grammar can end in an
453
+ * explicit-range shape, it wants the bare `:50` form too, or it windows the content itself
454
+ * (streaming a large file, skipping a cap it applies to an unwindowed read). Such a module
455
+ * calls `parseLineSelector` itself and reports what it did through `RawResource.selector`.
456
+ */
457
+ readonly ownsLineSelectors?: boolean;
458
+ readonly claims?: readonly UriClaim[];
459
+ /** Migration-only prompt summary until structured help/catalog coverage replaces prose. */
460
+ readonly description?: string;
461
+ readonly agentHint?: UriAgentHint;
462
+ readonly requiresAuth?: UriAuthRequirement;
463
+ readonly authActions?: UriAuthActionPolicy;
464
+ /** Pure route/payload validation applied before a consequential permission ask. */
465
+ readonly preflight?: UriOperationPreflightPolicy;
466
+ /**
467
+ * Structured facts for approval surfaces, or `undefined` when unavailable.
468
+ * A one-line description must name the whole consequence; `preview` instead
469
+ * supplies expanded, literal-text detail, and without a `quote` it suppresses one-line
470
+ * approval (a module that quotes the whole message keeps its line beside it). The module
471
+ * owns the static action phrase, target resolution, payload decoding and declaration
472
+ * of covered payload fields and query parameters. Unknown fields cost the summary.
473
+ * The host owns framing, the whole-quote fit (no elision) and display-safety admission.
474
+ *
475
+ * Unlike preflight, this hook is async and may do I/O. Resolve names through `ctx`,
476
+ * the same dispatch context used by execution, never an ambient or cached account. A name
477
+ * the module resolved under that account may be cached for a bounded time, keyed by that
478
+ * account's identity and never answered to another account or after a failed lookup.
479
+ * The host calls it once before raising the ask, enforces the short `ctx.signal`
480
+ * deadline and discards late answers. A throw yields no summary and a module-attributed
481
+ * diagnostic; declining costs a click into the app, never an under-described approval.
482
+ */
483
+ describeOperation?(request: {
484
+ readonly operation: UriOperation;
485
+ readonly uri: string;
486
+ readonly payload: unknown;
487
+ }, ctx: UriResolveContext): Promise<UriOperationDescription | undefined>;
488
+ /**
489
+ * Rebuild a call from the person's "Allow with edits" answer: the same payload
490
+ * {@link describeOperation} was given, with the values the person typed into the preview's
491
+ * `editable` fields put in their place. Implement it beside any preview that marks a field
492
+ * `editable`; without it the host drops every editable marker from that module's previews, so
493
+ * a field is never offered for editing that cannot be honoured.
494
+ *
495
+ * Pure and fast: it must not send, write or otherwise act, and it should not do I/O (it gets
496
+ * the dispatch context `ctx` only so a name can be resolved the way `describeOperation` does).
497
+ * Return the COMPLETE amended payload in the shape the module's own handler reads. Validate it
498
+ * with the handler's own schema, so an amended call is exactly as valid as one the model made.
499
+ * Decline by returning `undefined` or throwing; either denies the call with nothing done.
500
+ *
501
+ * The host calls it once, after the person allowed the call, never for a denial, and only for
502
+ * a `write` whose payload this module described. The kernel passes the `edits` through
503
+ * unchanged. Bonsai's daemon has already refused, before the ask settled, a key that is not an
504
+ * `editable` field of the preview it showed, a repeated key, a blank value and a value past the
505
+ * field's `maxLength`, but another host may not, so refuse each of those here yourself (return
506
+ * `undefined`).
507
+ *
508
+ * The host then holds the amended call to what the person was shown, in two steps, and runs it
509
+ * only if both pass. It classifies the amended call again and requires the same tool, rule key,
510
+ * kind, resources, scheme and target address, so an edit cannot reach a different route. It then
511
+ * calls {@link describeOperation} on the amended call and requires that preview to read exactly
512
+ * as the one the person saw, apart from the values of the fields they edited: the same title, the
513
+ * same fields in the same order, each with the same label, key, `editable` marker and
514
+ * `maxLength`, and every read-only field with the same value. Anything else refuses the call
515
+ * with nothing done: a mismatch, and also a describer that is missing, declines, throws or
516
+ * overruns its deadline. The `description` line is not compared.
517
+ *
518
+ * So rebuild from the original payload and change only the fields you offered. A recipient, a
519
+ * thread or an attachment list that an amendment changed is caught by the second step, but only
520
+ * where the preview shows it: put whatever says where a call goes, or what it does beyond its
521
+ * editable fields, in the preview's `title` or a field, never only in its `description`, and
522
+ * keep recipients, destinations and thread bindings out of `editable` fields.
523
+ */
524
+ amendOperation?(request: {
525
+ readonly operation: UriOperation;
526
+ readonly uri: string;
527
+ readonly payload: unknown;
528
+ readonly edits: readonly UriPreviewEdit[];
529
+ }, ctx: UriResolveContext): Promise<UriOperationAmendment | undefined>;
530
+ /**
531
+ * A short, render-only name for the resource a settled call on `request.uri` touched, which the
532
+ * host stamps on the tool result's `details` so a transcript row can name it where it would
533
+ * otherwise print the address (`#lane-analytics` and `thread`, not a channel id). Answer
534
+ * `undefined` for any address you cannot honestly name.
535
+ *
536
+ * Called once per successful URI verb call, AFTER the verb handler has returned, with the same
537
+ * dispatch context. Resolve names through `ctx` and nothing else, and prefer a cache your own
538
+ * verb handlers warm, keyed by the executing account: a name one account saw is never answered
539
+ * to another, and a failed, blank or aborted lookup is never cached. The host bounds the wait
540
+ * (a finished result is held at most a quarter of a second), gives `ctx.signal` a hard deadline,
541
+ * and drops a late, thrown or refused answer without failing the call; a throw is reported as a
542
+ * module-attributed diagnostic. The label is a label, never an identity: the address remains the
543
+ * click target, hover text and accessible name. It is UI-only, so it never enters the tool
544
+ * result's content and no telemetry reads it.
545
+ *
546
+ * Unlike `describeOperation` this is not an approval surface: it carries no consequence, no
547
+ * quote and no coverage claim, and nothing may gate a permission on it.
548
+ */
549
+ labelResource?(request: UriResourceLabelRequest, ctx: UriResolveContext): Promise<UriResourceLabel | undefined>;
550
+ /**
551
+ * Per-resource observability: does a `format: "observation"` read of THIS
552
+ * uri render source-native material ({@link UriResolveContext.format})?
553
+ * Route-parse only — cheap, side-effect-free, no I/O, no auth (the same
554
+ * discipline as `matches`); a throw is treated as `false`. Returning true
555
+ * is the resource's contract that its observation read returns an
556
+ * observation render and that failures under that format throw typed,
557
+ * status-bearing errors rather than resolving a friendly fallback page.
558
+ * The kernel stamps the answer onto the resource's access events, which is
559
+ * what ambient index consumers replay on — so "PRs are observable" is
560
+ * declared here, route by route, never module-wide. Absent means no read
561
+ * of this module is observable. A vendor's own gone-vocabulary (HTTP 410,
562
+ * a 200-body "ok:false"/"archived:true"/"status:cancelled", or a
563
+ * 403/404-ambiguous lost-access response) is this module's job to
564
+ * translate into `UriNotFoundError` or a `status: 404` error under the
565
+ * observation format — the fold that retires a doc never learns vendor
566
+ * vocabulary (see `ObservationMaterial`'s doc comment).
567
+ */
568
+ observes?(uri: string): boolean;
569
+ /**
570
+ * Head-only revision probe: the resource's CURRENT source-native revision
571
+ * without fetching its body, or `undefined` when that cannot be known
572
+ * cheaply. The host calls it before a repeat `read` of an address the
573
+ * session already holds, with `ctx.expectedRevision` set to the revision
574
+ * that read reported (`RawResourceMetadata.revision`); a value equal to it
575
+ * means "unchanged" and the fetch is skipped, anything else falls through
576
+ * to the ordinary read. A module whose read revision embeds more than the
577
+ * head can know (a content digest beside a stat stamp) may answer relative
578
+ * to `expectedRevision` — return it when the cheap part matches, `undefined`
579
+ * when it does not — but must never return a value it has not verified.
580
+ * `undefined` is always safe: it costs one fetch, never a wrong answer.
581
+ * Must be cheaper than the read (a metadata request, a `stat`); a throw is
582
+ * treated as `undefined`.
583
+ */
584
+ revision?(uri: string, ctx: UriResolveContext): Promise<string | undefined>;
585
+ readonly getCachePolicy?: UriCachePolicyResolver;
586
+ /** Retained matching escape hatch; future dispatch should prefer compiled claims. */
587
+ matches(input: string, ctx: UriResolveContext): boolean;
588
+ normalize?(input: string, ctx: UriResolveContext): UriNormalizeResult | undefined;
589
+ describe?(input: string, ctx: UriDescribeContext): UriResourceDescription | undefined;
590
+ locate?(input: string, ctx: UriLocateContext): Promise<UriLocateResult>;
591
+ /**
592
+ * `read`, `help`, `list` and `search` observe; they never change the resource or anything
593
+ * outside the host. Repeating one with the same input must be harmless: the kernel reruns a
594
+ * call of these verbs that a crash cut off, with the same input, without asking the user
595
+ * again. Idempotent bookkeeping (a cache fill, an index of what was read, a usage count) is
596
+ * fine; a send, a mark-as-read or a create is not, and belongs on a mutating verb.
597
+ */
598
+ read(input: string, ctx: UriResolveContext): Promise<RawResource>;
599
+ /** Observes only; see {@link read}. */
600
+ help?(input: string, ctx: UriResolveContext): Promise<UriHelp>;
601
+ write?(input: string, content: UriPayload, ctx: UriResolveContext): Promise<UriMutationResult | void>;
602
+ /** Opt-in structured write with one or more host-resolved resources delivered beside the content. */
603
+ writeWithSources?(input: string, content: UriPayload, sources: readonly UriWriteSource[], ctx: UriResolveContext): Promise<UriMutationResult | void>;
604
+ /**
605
+ * Opt-in byte sink: write RAW BYTES to this address, no UTF-8 round trip.
606
+ * `UriPayload` is text/JSON by contract, so a module that can store bytes
607
+ * declares it here instead of stretching `write`. The only caller today is
608
+ * the `write` tool's `source` copy leg, which is how a provider's binary
609
+ * resource — a Gmail attachment, an image, a PDF, a zip — reaches a
610
+ * byte-capable destination without ever being decoded into the model's
611
+ * context. A module that omits it is text-only, and a binary copy targeting
612
+ * it is refused with an error naming a byte-capable destination.
613
+ *
614
+ * `source` describes what the bytes are: a filesystem-backed sink can ignore
615
+ * it (the target path already carries the name), while a remote provider
616
+ * sink needs it to name and type the stored file. It sits before `ctx` so a
617
+ * forwarding wrapper that fails to thread it is a compile error, not a
618
+ * silent drop.
619
+ */
620
+ writeBytes?(input: string, bytes: Uint8Array, source: UriSourceDescriptor, ctx: UriResolveContext): Promise<UriMutationResult | void>;
621
+ edit?(input: string, patch: UriPayload, ctx: UriResolveContext): Promise<UriMutationResult>;
622
+ /** Observes only; see {@link read}. */
623
+ list?(input: string, params: UriListParams, ctx: UriResolveContext): Promise<UriListResult>;
624
+ /** Observes only; see {@link read}. */
625
+ search?(input: string, params: UriSearchParams, ctx: UriResolveContext): Promise<UriSearchResult>;
626
+ delete?(input: string, ctx: UriResolveContext): Promise<UriMutationResult>;
627
+ toMarkdown?(input: UriMarkdownRenderInput, ctx: UriResolveContext): string | undefined;
628
+ }
629
+
630
+ /** Authoritative lifecycle facts for a successful record-like URI mutation. */
631
+ declare interface UriMutationReceipt {
632
+ readonly uri: string;
633
+ readonly operation: "create" | "update" | "trash" | "restore" | "delete";
634
+ readonly state: "active" | "trashed" | "deleted";
635
+ readonly revision?: string;
636
+ readonly changedFields?: readonly string[];
637
+ readonly recoverable: boolean;
638
+ readonly recoveryUri?: string;
639
+ readonly originalUri?: string;
640
+ readonly restoreTarget?: string;
641
+ }
642
+
643
+ declare interface UriMutationResult {
644
+ readonly uri: string;
645
+ readonly summary?: string;
646
+ readonly resource?: RawResource | JsonValue;
647
+ readonly validation?: readonly UriValidationMessage[];
648
+ readonly metadata?: Record<string, unknown>;
649
+ /** Typed state/recovery evidence; successful callers may trust it without a proof read. */
650
+ readonly receipt?: UriMutationReceipt;
651
+ /** Opaque source-native revision after this successful mutation. */
652
+ readonly revision?: string;
653
+ }
654
+
655
+ declare interface UriNodeCapability {
656
+ readonly rel: string;
657
+ readonly operation: UriOperation;
658
+ readonly uri: string;
659
+ readonly title: string;
660
+ readonly description?: string;
661
+ readonly parameters?: readonly UriParameter[];
662
+ readonly accepts?: UriPayloadContract;
663
+ readonly returns?: UriPayloadContract;
664
+ }
665
+
666
+ declare interface UriNormalizeResult {
667
+ readonly uri: string;
668
+ readonly source: string;
669
+ }
670
+
671
+ declare type UriOperation = "help" | "read" | "write" | "edit" | "list" | "search" | "delete";
672
+
673
+ /** The call {@link UriModule.amendOperation} rebuilt: the whole payload, in the shape the module reads. */
674
+ declare interface UriOperationAmendment {
675
+ readonly payload: unknown;
676
+ }
677
+
678
+ declare interface UriOperationDescription {
679
+ /** Static verb phrase authored by the module. Never interpolated from input. */
680
+ readonly action: string;
681
+ /** Human name resolved through the connection that will execute the call. */
682
+ readonly target: string;
683
+ /** Model-authored content; the host flattens, budgets, elides and bounds it. */
684
+ readonly quote?: string;
685
+ /** Full, render-only detail. Hosts must not turn a rich preview into one-line approval. */
686
+ readonly preview?: {
687
+ readonly title: string;
688
+ readonly description?: string;
689
+ readonly fields?: readonly {
690
+ readonly label: string;
691
+ readonly value: string;
692
+ readonly oldValue?: string;
693
+ /**
694
+ * A stable, module-defined id for this field, unique within the preview (`subject`,
695
+ * `body`, `text`). It names the field in an "Allow with edits" answer, so it never
696
+ * depends on the displayed label. Required for an `editable` field.
697
+ */
698
+ readonly key?: string;
699
+ /**
700
+ * `true` offers this field to the person for editing on the approval card. Offer it only
701
+ * when the module implements {@link UriModule.amendOperation} for this call and can honour
702
+ * any value within `maxLength`; the host drops the offer otherwise. Absent means read-only.
703
+ */
704
+ readonly editable?: true;
705
+ /** Longest edited value, in UTF-16 code units. Meaningful only with `editable`. */
706
+ readonly maxLength?: number;
707
+ }[];
708
+ };
709
+ /** True only when every displayed fact comes from the input, independent of the executing account. */
710
+ readonly accountIndependent?: true;
711
+ /** What this description accounts for; the host refuses undeclared input. */
712
+ readonly accounts: {
713
+ /** The payload actually read by the module, after its own decoding. */
714
+ readonly payload: unknown;
715
+ readonly payloadFields: readonly string[];
716
+ readonly queryParams?: readonly string[];
717
+ };
718
+ }
719
+
720
+ /**
721
+ * Pure, route-aware validation that runs before permission policy can surface an ask.
722
+ *
723
+ * This is deliberately narrower than execution: it receives only the canonical URI,
724
+ * operation, and operation payload, performs no I/O, and returns a user-actionable
725
+ * rejection for calls that the module already knows cannot succeed. The handler must
726
+ * still validate again when it executes because approval is not an authority boundary.
727
+ */
728
+ declare interface UriOperationPreflightPolicy {
729
+ /**
730
+ * The rejection half. Optional since kay#7684: a module whose only preflight
731
+ * fact is affirmative (`acceptsPayloadlessWrite`) must not have to stub a
732
+ * validator that never rejects.
733
+ */
734
+ validateOperation?(request: {
735
+ readonly operation: UriOperation;
736
+ readonly uri: string;
737
+ readonly payload: unknown;
738
+ }): string | undefined;
739
+ /**
740
+ * Route-level affirmative: may a `write` to this URI carry NO payload at all
741
+ * — neither `content` nor `source`? (The canonical case is folder creation,
742
+ * e.g. `?type=folder`.) The write tool's generic shape rule requires exactly
743
+ * one payload form; a route answers `true` here when a payloadless write is
744
+ * meaningful FOR THAT ROUTE, and the rule then admits the call.
745
+ *
746
+ * This exists because an empty body is not always sendable: on some
747
+ * model/provider paths a top-level empty-string argument (`content: ""`)
748
+ * never reaches the host — the key arrives absent, indistinguishable from an
749
+ * omitted argument (kay#7684). A contract that can only be invoked with an
750
+ * empty string is unreachable on those paths, so a route whose write is
751
+ * meaningful with no body must declare it and let the caller omit the
752
+ * payload instead.
753
+ *
754
+ * Pure and side-effect-free like {@link validateOperation}: route-parse
755
+ * only, no I/O. A throw NEVER admits the write, but the two consult paths
756
+ * disagree on purpose about what happens next: pre-ask (permission
757
+ * resolution, which must never crash the gate) answers no and records a
758
+ * module-attributed diagnostic; at execute time the write tool asks only
759
+ * when the call genuinely carries no payload, and a throw there is the
760
+ * module's own bug surfacing as the call's error — louder than a misleading
761
+ * \"provide exactly one of content or source\" the model could never
762
+ * satisfy. Absent means no route of this module accepts a payloadless
763
+ * write.
764
+ */
765
+ acceptsPayloadlessWrite?(request: {
766
+ readonly uri: string;
767
+ }): boolean;
768
+ }
769
+
770
+ declare interface UriParameter {
771
+ readonly name: string;
772
+ readonly in: "uri" | "query" | "selector";
773
+ readonly required: boolean;
774
+ readonly description?: string;
775
+ readonly schema: JsonSchema;
776
+ readonly examples?: readonly unknown[];
777
+ }
778
+
779
+ declare interface UriPatternClaim {
780
+ readonly kind: "uriPattern";
781
+ readonly pattern: UriPatternInit;
782
+ /**
783
+ * Canonical spelling rebuilt from the captures. When the template keeps the
784
+ * input's scheme (the module's own addressing), the input's query string is
785
+ * carried over verbatim so a route flag such as `?permanent=true` survives
786
+ * canonicalization (kay#7859); a template that re-targets to another scheme
787
+ * (a projection to a web link) drops it, as does a pattern that consumed the
788
+ * query itself or a template that spells its own.
789
+ */
790
+ readonly normalizeTo?: string;
791
+ readonly examples?: readonly string[];
792
+ }
793
+
794
+ declare interface UriPatternInit {
795
+ readonly protocol?: string;
796
+ readonly hostname?: string;
797
+ /**
798
+ * Slash-delimited path pattern. A final capture may use `:name*` to consume
799
+ * the remaining path segments, preserving `/` in the captured value.
800
+ */
801
+ readonly pathname?: string;
802
+ readonly search?: string;
803
+ }
804
+
805
+ declare type UriPayload = string | JsonValue;
806
+
807
+ declare interface UriPayloadContract {
808
+ readonly name: string;
809
+ readonly mediaType?: string;
810
+ readonly description?: string;
811
+ /** Omitted when the node accepts or returns an unstructured payload. */
812
+ readonly schema?: JsonSchema;
813
+ /** Cross-value invariants the module enforces but plain JSON Schema cannot express. */
814
+ readonly semanticConstraints?: readonly string[];
815
+ readonly examples?: readonly unknown[];
816
+ }
817
+
818
+ /**
819
+ * The three-state permission vocabulary a plugin declares for its default permissions.
820
+ * Maps to `PermissionRule.effect`: `allow` → allow, `ask` → ask, `never` → deny.
821
+ */
822
+ declare type UriPermissionDefault = "allow" | "ask" | "never";
823
+
824
+ /**
825
+ * Plugin-declared default permission for each verb group (read/write/edit/delete).
826
+ * Undeclared verb groups default to `"allow"` (fail-open, same optionality as
827
+ * {@link UriVerbDocs}). Lives here in the protocol layer (beside `UriVerbDocs`)
828
+ * so both `@herd/uri-extensions`' `UriExtensionModule` and herd-agent's kernel
829
+ * `UriModule` can carry it.
830
+ */
831
+ declare interface UriPermissionDefaults {
832
+ readonly read?: UriPermissionDefault;
833
+ readonly write?: UriPermissionDefault;
834
+ readonly edit?: UriPermissionDefault;
835
+ readonly delete?: UriPermissionDefault;
836
+ }
837
+
838
+ /**
839
+ * One change a person made to an `editable` field of a call's action preview ("Allow with
840
+ * edits"): the field's `key` and the value they want it to hold. Values are message content, so
841
+ * a module must not log or persist them.
842
+ */
843
+ declare interface UriPreviewEdit {
844
+ readonly key: string;
845
+ readonly value: string;
846
+ }
847
+
848
+ /** The read shape a caller may request via {@link UriResolveContext.format}. */
849
+ declare type UriReadFormat = "observation";
850
+
851
+ declare interface UriResolveContext {
852
+ readonly cwd: string;
853
+ readonly signal?: AbortSignal;
854
+ readonly connection?: UriConnection;
855
+ /** Host-populated execution auth for verb handlers; always absent during matching. */
856
+ readonly auth?: UriResolvedAuth;
857
+ /**
858
+ * Id of the session dispatching this call, stamped by the host at the dispatch site.
859
+ * It never originates from anything the caller can influence — no verb argument, no write
860
+ * body — so a module may treat it as trustworthy caller identity and stamp it onto writes.
861
+ *
862
+ * Absent when no session owns the dispatch: registry matching and permission pre-checks
863
+ * resolve a URI outside any verb call.
864
+ */
865
+ readonly sessionId?: string;
866
+ /**
867
+ * WHO the dispatching session acts as, stamped by the host at the same dispatch site as
868
+ * {@link UriResolveContext.sessionId} and just as un-influenceable by the caller — so a module
869
+ * writing to a store it owns can record the actor without the agent being able to choose one.
870
+ *
871
+ * Free text minted by the host's own runtime (Bonsai's form is `user`, `subagent`,
872
+ * `system/<surface>`, `followup/<surface>`) rather than a closed union: the surface half is the
873
+ * host's vocabulary, and a second enumeration of it here would drift from the first.
874
+ *
875
+ * Absent whenever the host has nothing to say — no session owns the dispatch, or the session
876
+ * predates the host recording an origin. A module must then omit the actor rather than assume
877
+ * one; an invented actor written into a durable artifact can never be walked back.
878
+ */
879
+ readonly actor?: string;
880
+ /**
881
+ * Identity of the tool invocation that owns this dispatch, stamped by the host from its
882
+ * execution frame. Like `sessionId`, this is never accepted from URI arguments or payloads.
883
+ * Absent for dispatches that do not run inside a session-owned tool invocation.
884
+ */
885
+ readonly invocation?: {
886
+ readonly toolCallId: string;
887
+ readonly sessionId: string;
888
+ readonly origin: "model" | "host";
889
+ /**
890
+ * The daemon-assigned id of the run driving this invocation, when the host's own execution
891
+ * frame carried one. Absent means the frame carried none — never a reason to guess one: a
892
+ * module recording this onto a durable artifact must treat absence as unresolved provenance,
893
+ * not as "no run was active".
894
+ */
895
+ readonly runId?: string;
896
+ /** The registered name of the tool making this call, when the host's frame carried one. */
897
+ readonly toolName?: string;
898
+ };
899
+ /**
900
+ * An OPAQUE, host-captured context for the mutation this dispatch is about to make —
901
+ * the artifact-provenance counterpart of `sessionId`/`actor`, and stamped at the same
902
+ * un-influenceable dispatch site.
903
+ *
904
+ * The host composes it from facts a caller cannot supply: which session is acting, the
905
+ * run driving it, the tool invocation, and whatever the host resolved about how that
906
+ * session came to exist. The kernel treats it as an uninterpreted JSON value — it never
907
+ * reads a field, only checks that the value is lossless JSON before it crosses the
908
+ * dispatch boundary — so this layer stays ignorant of any one product's provenance
909
+ * semantics. A module that owns a durable artifact carries it to its store and validates
910
+ * it against its OWN schema at the point it persists, where the target revision is known.
911
+ *
912
+ * Absent means the host captured nothing for this dispatch (a read, a host with no
913
+ * capture resolver, or a capture that failed) — never a claim that there was nothing to
914
+ * capture. Present only on mutating dispatches (`write`/`edit`/`delete`); reads never pay
915
+ * for it.
916
+ */
917
+ readonly captureContext?: Readonly<Record<string, unknown>>;
918
+ /**
919
+ * Requested read shape. A module may ignore this and return its normal read; honoring
920
+ * it means returning an `ObservationMaterial` via `observationResource(...)` instead.
921
+ * Opt-in only — there is no advertisement surface or capability flag.
922
+ */
923
+ readonly format?: UriReadFormat;
924
+ /**
925
+ * Host-provided writable data root for the OWNING extension of the module handling this call —
926
+ * structurally identical to the per-extension `paths` a tool/handler context already receives, so
927
+ * a module authored against both surfaces reads `ctx.paths?.dataDir` uniformly. Lets a URI module
928
+ * own durable local state (a JSON store, a small file tree) instead of the host constructing and
929
+ * injecting it. Host-populated after dispatch selects a module and keyed to that module's owner;
930
+ * always absent during matching, and absent for modules with no owning extension (builtins) or a
931
+ * host that wires no per-module data dir. Unlike a tool context, the host only NAMES this path for
932
+ * a URI module and does NOT pre-create the directory — create it on first write and tolerate its
933
+ * absence on read.
934
+ */
935
+ readonly paths?: {
936
+ readonly dataDir: string;
937
+ };
938
+ /**
939
+ * What this session last saw of the resource, handed to every mutation: the
940
+ * module's own `RawResourceMetadata.revision` from the last read, else the
941
+ * host's content digest of what that read returned. Never model input. A
942
+ * module holding the current content compares with `assertResourceUnchanged`;
943
+ * absent means the host requires no witness for this call and the module
944
+ * proceeds as before.
945
+ */
946
+ readonly expectedRevision?: string;
947
+ /** Host marks a mediated session that enforces its private revision witness ledger. */
948
+ readonly requiresRevisionWitness?: boolean;
949
+ /** Host-derived descriptor for a top-level copied resource; absent for inline writes. */
950
+ readonly writeSource?: UriSourceDescriptor;
951
+ }
952
+
953
+ declare interface UriResolvedAuth {
954
+ readonly connection?: UriConnection;
955
+ /** Present only for verb handlers of a module declaring `requiresAuth`; absent during matching. */
956
+ readonly credential?: UriResolvedCredential;
957
+ /** Present for handlers gated by a non-injectable external-CLI readiness scheme. */
958
+ readonly externalCli?: UriExternalCliRunner;
959
+ /** Opaque host metadata; not a credential channel. */
960
+ readonly metadata?: Record<string, unknown>;
961
+ }
962
+
963
+ declare interface UriResolvedCredential {
964
+ /** Bearer for OAuth schemes, apiKey for api-key schemes. Host-resolved and refreshed. */
965
+ readonly accessToken: string;
966
+ readonly accountId: string;
967
+ readonly schemeId: string;
968
+ readonly scopes?: readonly string[];
969
+ /**
970
+ * Every value of a multi-field api-key credential, keyed by the scheme's declared field id
971
+ * (`accessToken` mirrors the primary field). Absent for single-secret schemes.
972
+ */
973
+ readonly fields?: Readonly<Record<string, string>>;
974
+ }
975
+
976
+ declare interface UriResourceDescription {
977
+ readonly uri: string;
978
+ /** Absent when the caller should retain its existing label. */
979
+ readonly title?: string;
980
+ /** Provider-owned public URL when the resource also has a web address. */
981
+ readonly sourceUrl?: string;
982
+ }
983
+
984
+ /**
985
+ * What a settled call touched, as a person would name it.
986
+ *
987
+ * `name` is the resource itself (`#lane-analytics`). `section` is the part of it the address
988
+ * reaches into (`thread`), and is absent when the address is the resource.
989
+ */
990
+ declare interface UriResourceLabel {
991
+ readonly name: string;
992
+ readonly section?: string;
993
+ }
994
+
995
+ /** The call a module is asked to label. */
996
+ declare interface UriResourceLabelRequest {
997
+ readonly operation: UriOperation;
998
+ /** The dispatch address: selector-stripped, the exact form the module's verb handler received. */
999
+ readonly uri: string;
1000
+ }
1001
+
1002
+ declare interface UriSchemeClaim {
1003
+ readonly kind: "scheme";
1004
+ readonly scheme: string;
1005
+ }
1006
+
1007
+ declare interface UriSearchItem {
1008
+ readonly uri: string;
1009
+ readonly title?: string;
1010
+ readonly summary?: string;
1011
+ /** Provider resource MIME type; absent when the source does not report one. */
1012
+ readonly sourceMimeType?: string;
1013
+ readonly score?: number;
1014
+ readonly reason?: string;
1015
+ readonly operations?: readonly UriOperation[];
1016
+ }
1017
+
1018
+ declare interface UriSearchParams {
1019
+ readonly query: string;
1020
+ readonly limit?: number;
1021
+ readonly cursor?: string;
1022
+ }
1023
+
1024
+ declare interface UriSearchResult {
1025
+ readonly uri: string;
1026
+ readonly query: string;
1027
+ readonly items: readonly UriSearchItem[];
1028
+ readonly nextCursor?: string;
1029
+ /**
1030
+ * Optional result-set metadata, mirroring {@link UriListResult.metadata}. Lets a
1031
+ * search surface completeness signals such as {@link UriListMetadata.incompleteSearch}
1032
+ * so the caller knows the matches may be partial.
1033
+ */
1034
+ readonly metadata?: UriListMetadata;
1035
+ }
1036
+
1037
+ /**
1038
+ * What a copied resource IS, minus its bytes: the identity half of a mediated
1039
+ * source. The host derives it once (filename inferred from the source address,
1040
+ * `mimeType` carried from the source module's own `RawResource.mimeType`) and
1041
+ * delivers it on both byte-bearing write hooks — inside each `UriWriteSource`,
1042
+ * and as the standalone third argument to `writeBytes` — so a remote sink can
1043
+ * name and type what it stores without re-deriving either from raw bytes.
1044
+ */
1045
+ declare interface UriSourceDescriptor {
1046
+ readonly uri: string;
1047
+ readonly filename: string;
1048
+ readonly mimeType?: string;
1049
+ }
1050
+
1051
+ declare interface UriTemplate {
1052
+ readonly uri: string;
1053
+ readonly description: string;
1054
+ readonly operations?: readonly UriOperation[];
1055
+ /**
1056
+ * Per-operation effect docs for this route, keyed by operation. A route
1057
+ * whose shared {@link description} spans several verbs ("Read, edit, or
1058
+ * trash a thread") is ambiguous for any surface that groups by verb — a
1059
+ * permission view must not describe `read` with a sentence that mentions
1060
+ * trashing. Each entry says what THAT operation concretely does at THIS
1061
+ * route; operations without an entry fall back to the shared description.
1062
+ * Additive and optional — existing templates are unaffected.
1063
+ */
1064
+ readonly operationDocs?: Partial<Record<UriOperation, string>>;
1065
+ /**
1066
+ * Per-operation default permission for this route, keyed by operation.
1067
+ * When present, overrides the module-level {@link UriPermissionDefaults}
1068
+ * for the named operations at this specific route (resource-glob scoped).
1069
+ * Undeclared operations fall back to the module-level default, then to
1070
+ * `"allow"` (fail-open).
1071
+ */
1072
+ readonly operationDefaults?: Partial<Record<UriOperation, UriPermissionDefault>>;
1073
+ /**
1074
+ * Parameters are DERIVED from the address pattern by `deriveTemplateParameters`
1075
+ * (`template-route.ts`): every `<placeholder>` path segment and every `?key=<value>`
1076
+ * query key becomes one. Author an entry only to add a description, a tighter schema,
1077
+ * or examples; it overrides the derived entry with the same name. An authored name
1078
+ * that is not a placeholder in `uri` is a lint failure, not a hidden extra.
1079
+ */
1080
+ readonly parameters?: readonly UriParameter[];
1081
+ /**
1082
+ * The payload contract for each mutating verb at THIS route, built with `payload()`
1083
+ * from the same Zod validator the handler parses with. A contract without a `schema`
1084
+ * is the typed "unstructured" declaration (raw text, YAML, a file body) and must be
1085
+ * true of the handler. This is the only place a route's write or edit shape is
1086
+ * disclosed; `help` renders it at the route and the tool catalog derives its facets
1087
+ * from it.
1088
+ */
1089
+ readonly accepts?: Partial<Record<"write" | "edit", UriPayloadContract>>;
1090
+ /** What `delete` does at this route and whether it can be undone. Required by lint on
1091
+ * every route that lists `delete`. */
1092
+ readonly deleteEffect?: UriDeleteEffect;
1093
+ }
1094
+
1095
+ declare interface UriValidationMessage {
1096
+ readonly path?: string;
1097
+ readonly message: string;
1098
+ }
1099
+
1100
+ /**
1101
+ * Author-declared, human-facing documentation of what each supported verb
1102
+ * CONCRETELY does against the provider — the capability half of a module's
1103
+ * boolean verb-support declaration. Written for a permission/capability
1104
+ * surface ("what am I allowing?"), not for the model: one plain sentence per
1105
+ * verb naming the real-world effect ("Sends mail, creates/updates drafts,
1106
+ * applies label changes"), never wire mechanics or URI shapes. Verbs the
1107
+ * module does not support are omitted; at runtime a supported verb with no
1108
+ * entry simply renders undocumented (fail-open metadata) — but for a plugin's
1109
+ * `kay.agent` URI modules, `auditPluginDisclosure` (`@bonsai/plugin-sdk/
1110
+ * disclosure`) enforces coverage of every supported verb at authoring/load
1111
+ * time.
1112
+ *
1113
+ * Lives here in the protocol layer (beside {@link UriTemplate.operationDocs},
1114
+ * its per-route sibling) so both `@herd/uri-extensions`' `UriExtensionModule`
1115
+ * and herd-agent's kernel `UriModule` can carry it without inverting the
1116
+ * `@herd/uri` ← `@herd/uri-extensions` dependency.
1117
+ */
1118
+ declare interface UriVerbDocs {
1119
+ /** What a `read` of this address space returns (also covers `help`). */
1120
+ readonly read?: string;
1121
+ /** What `list` enumerates, when it differs meaningfully from `read`. */
1122
+ readonly list?: string;
1123
+ /** What `search` queries, when it differs meaningfully from `read`. */
1124
+ readonly search?: string;
1125
+ /** What a `write` creates or overwrites at the provider. */
1126
+ readonly write?: string;
1127
+ /** What an `edit` changes in place at the provider. */
1128
+ readonly edit?: string;
1129
+ /** What a `delete` destroys or removes at the provider (and how recoverable it is). */
1130
+ readonly delete?: string;
1131
+ }
1132
+
1133
+ declare interface UriVerbSupport {
1134
+ readonly readable: boolean;
1135
+ readonly writable: boolean;
1136
+ readonly editable: boolean;
1137
+ readonly deletable?: boolean;
1138
+ readonly listable: boolean;
1139
+ }
1140
+
1141
+ /** A mediated resource supplied beside structured write content without entering model context. */
1142
+ declare interface UriWriteSource extends UriSourceDescriptor {
1143
+ readonly content: string | Uint8Array;
1144
+ }
1145
+
1146
+ declare interface UrlPatternClaim {
1147
+ readonly kind: "urlPattern";
1148
+ readonly pattern: UriPatternInit;
1149
+ readonly normalizeTo?: string;
1150
+ readonly examples?: readonly string[];
1151
+ }
1152
+
1153
+ /** The claim shape a module declares, sourced from the module contract rather than re-declared. */
1154
+ export declare type VendoredMcpClaim = NonNullable<UriExtensionModule["claims"]>[number];
1155
+
1156
+ /**
1157
+ * Everything a vendored-MCP plugin's URI module is, expressed as data.
1158
+ *
1159
+ * The plugin package supplies one of these and gets back a conformant {@link UriExtensionModule};
1160
+ * it writes no read projection, no help document, and no route parsing. That is deliberate — this
1161
+ * kit exists because five pilot plugins were about to become five copies of one 460-line module,
1162
+ * and ~40 more are queued behind them.
1163
+ */
1164
+ export declare interface VendoredMcpDescriptor {
1165
+ /** Global module id, e.g. `supabase.resources`. Matches the manifest's resolved uriModule id. */
1166
+ readonly moduleId: string;
1167
+ /** The plugin namespace and canonical scheme, e.g. `supabase`. */
1168
+ readonly namespace: string;
1169
+ /** The vendor's display name, e.g. `Supabase`. */
1170
+ readonly displayName: string;
1171
+ /**
1172
+ * What one of these resources IS, in the user's words, e.g. `Supabase project`. Keep it equal
1173
+ * to the manifest's `referencePresentations[].resourceKind` so the pill and the card agree.
1174
+ */
1175
+ readonly resourceKind: string;
1176
+ /** One sentence for the module description and the agent hint. Clamped at 220 chars upstream. */
1177
+ readonly summary: string;
1178
+ /**
1179
+ * The public-web `urlPattern` claims that recognise this vendor's links. The scheme claim for
1180
+ * {@link namespace} is added by the factory, so a descriptor can never forget the one claim the
1181
+ * disclosure floor requires.
1182
+ */
1183
+ readonly claims: readonly VendoredMcpClaim[];
1184
+ /** The canonical shapes this module addresses. At least one. */
1185
+ readonly routes: readonly VendoredMcpRoute[];
1186
+ /** Up to 8 keywords that should surface this module. */
1187
+ readonly triggerKeywords: readonly string[];
1188
+ /** The vendored MCP server's flat name, exactly as keyed in the manifest's `mcpServers`. */
1189
+ readonly mcpServerName: string;
1190
+ /** One line naming what the vendor's MCP tools cover, shown on every read card. */
1191
+ readonly mcpSummary: string;
1192
+ /** The plugin's own skill local ids, primer first. */
1193
+ readonly skills: readonly string[];
1194
+ }
1195
+
1196
+ /**
1197
+ * One addressable resource shape, as the PAIR that makes the whole pattern work: the canonical
1198
+ * `<ns>://…` URI Bonsai stores, and the public web URL that URI came from and opens back to.
1199
+ *
1200
+ * Both templates use the same `:name` capture syntax and MUST name exactly the same captures —
1201
+ * {@link createVendoredMcpUriModule} throws otherwise. That symmetry is the point: a canonical
1202
+ * URI carrying an id the web URL drops is a pill that cannot be opened, and a web URL naming a
1203
+ * capture the canonical URI never captured renders a literal `:pageId` into the user's browser.
1204
+ * Both are silent in a type system and loud here.
1205
+ */
1206
+ export declare interface VendoredMcpRoute {
1207
+ /** Canonical URI template, e.g. `supabase://project/:projectRef`. Needs at least one path segment. */
1208
+ readonly uri: string;
1209
+ /** Public web URL template over the same captures, e.g. `https://supabase.com/dashboard/project/:projectRef`. */
1210
+ readonly webUrl: string;
1211
+ /** One line naming what this address is, for `help()` templates and the read card. */
1212
+ readonly description: string;
1213
+ /** A concrete canonical URI for `help()`'s examples. Must match {@link uri}. */
1214
+ readonly example: string;
1215
+ }
1216
+
1217
+ export { }