@yaklabs-ai/plugin-sdk 0.15.2 → 0.26.3

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.
@@ -12,23 +12,30 @@
12
12
  * It could, and V1 deliberately does not. These servers have no uniform read: several expose only
13
13
  * tools, and PostHog's is a single `exec` tool over a CLI-style DSL. There is no shape to project
14
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
15
+ * Instead `read` returns a reference card - what the resource is, its captured ids, the URL that
16
16
  * opens it, and the server to call for anything deeper. When a vendor earns a real projection, it
17
17
  * attaches as a per-vendor `read` beside this one; the shared card stays the floor.
18
18
  *
19
19
  * ## Why there is no `requiresAuth`
20
20
  *
21
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.
22
+ * This module's `read` is a pure projection of the URI it was handed - no network, no credential.
23
23
  * Declaring `requiresAuth` anyway would not be a harmless formality: an auth-gated module whose
24
24
  * binding is unsatisfied has its public-web `urlPattern` claims suppressed and its `matches()`
25
25
  * wrapped to decline `http(s)` (herd-agent's `uri-catalog-projection.ts`: `suppressPublicUrlClaims`,
26
26
  * and `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
27
+ * unbranded for every user who has not yet connected Supabase - which is exactly the user the
28
28
  * branded pill is for. The vendor's MCP tools remain gated on their own, by the MCP host.
29
29
  */
30
30
  export declare function createVendoredMcpUriModule(descriptor: VendoredMcpDescriptor): UriExtensionModule;
31
31
 
32
+ declare interface FactPattern {
33
+ protocol?: string;
34
+ hostname?: string;
35
+ pathname?: string;
36
+ search?: string;
37
+ }
38
+
32
39
  declare type JsonPrimitive = string | number | boolean | null;
33
40
 
34
41
  declare type JsonSchema = Record<string, unknown>;
@@ -47,12 +54,107 @@ declare interface LineSelector {
47
54
  readonly raw: boolean;
48
55
  }
49
56
 
57
+ /**
58
+ * A source-native read of one URI, shaped for downstream indexing rather than
59
+ * direct model consumption. A module's read returns it in
60
+ * `RawResource.observations`, built from the same fetch as the content, once
61
+ * per document it wants indexed; absence means "not indexed".
62
+ *
63
+ * Gone signal: the index retires a doc when a read throws `UriNotFoundError`
64
+ * or an error whose `.status === 404`. A module whose vendor signals "gone"
65
+ * differently (HTTP 410, a 200-body `{ok:false}`/`archived:true`, a
66
+ * 403/404-ambiguous lost-access response) must translate that into one of
67
+ * those two shapes itself; losing access to a resource counts as gone too.
68
+ * A read that cannot build a complete observation (a secondary fetch failed)
69
+ * returns none rather than a partial one.
70
+ *
71
+ * Account-qualified identity: a module whose read can resolve against more
72
+ * than one connected account (the account selector is stripped before route
73
+ * parsing, so two accounts reading the same opaque provider id would
74
+ * otherwise collide on one doc key) must fold `ctx.auth.credential.accountId`
75
+ * into `uri` - e.g. `gmail://@<accountId>/threads/<threadId>` - never the
76
+ * user-renamable connection/selector label. `ctx.auth.credential` is the
77
+ * reliable source of the resolved account at a `requiresAuth` module's
78
+ * `read()`: it is always populated once auth resolution runs (an
79
+ * `authContext`-style check already throws first when no credential
80
+ * resolved). `ctx.connection` may ALSO be populated by dispatch - a
81
+ * connection-required module receives it whenever the host has a tracked
82
+ * connection for the account - so do not assume it is absent; a module that
83
+ * wants the human-facing label rather than the account id reads
84
+ * `ctx.connection?.displayName`.
85
+ *
86
+ * `accountId` is guaranteed stable only for the life of the stored credential
87
+ * record, not necessarily across a disconnect/reconnect of the same
88
+ * real-world provider account. A scheme that mints an opaque id at connect
89
+ * time (the default for an api-key or delegated scheme, absent a
90
+ * provider-derived override) gets a fresh id on every connect; a scheme that
91
+ * instead derives the id from provider data (an oauth2-pkce scheme supplying
92
+ * `resolveAccountId`, e.g. reading an OIDC `sub`) keeps the same id across
93
+ * reconnect. A module on a scheme without a provider-stable id has two honest
94
+ * choices: derive its own stable identity from provider data at read time
95
+ * (call the provider's own account/"who am I" endpoint with the resolved
96
+ * credential and fold THAT into `uri` instead of `accountId`), or
97
+ * account-qualify with `accountId` knowing a disconnect/reconnect of the same
98
+ * real-world account re-keys its documents under a new id rather than
99
+ * updating them in place - nothing on the host side reconciles the old and
100
+ * new keys.
101
+ */
102
+ declare interface ObservationMaterial {
103
+ /** Canonical URI of the observed resource - may differ from the input URI. */
104
+ readonly uri: string;
105
+ /** Scheme-prefixed content type, e.g. `"github.pr"`, `"gmail.thread"`, `"wikipedia.article"`. */
106
+ readonly kind: string;
107
+ readonly title?: string;
108
+ /** Source-native seams: one element per message/comment/section, in source order. */
109
+ readonly text?: readonly string[];
110
+ /** ISO-8601 creation time; provenance only - not persisted by the store yet. */
111
+ readonly createdAt?: string;
112
+ /** ISO-8601 last-update time; provenance only - not persisted by the store yet. */
113
+ readonly updatedAt?: string;
114
+ /** Source-native revision/version marker, when the source exposes one. */
115
+ readonly version?: string;
116
+ /**
117
+ * How long this resource stays worth semantically searching, measured from
118
+ * the LAST time it was observed - not from when it was first indexed. Every
119
+ * observation of an unchanged resource pushes the deadline out again, so a
120
+ * thread that keeps getting read keeps living and a quiet one ages out.
121
+ *
122
+ * Retention is opt-in per resource: absent means the index keeps the
123
+ * resource until its source says it is gone. A route declares this only
124
+ * when staleness is real for that shape - a chat thread or an inbox
125
+ * conversation goes cold, a durable note the user wrote does not.
126
+ */
127
+ readonly retainForMs?: number;
128
+ }
129
+
50
130
  declare interface RawResource {
51
131
  readonly uri: string;
52
132
  readonly content: string | Uint8Array;
53
133
  readonly mimeType: string;
54
134
  readonly metadata?: RawResourceMetadata;
55
135
  readonly selector?: SelectorDisposition;
136
+ /**
137
+ * The indexable forms of this resource, built from the same fetch as
138
+ * `content`: one per document the read wants indexed (a thread read offers
139
+ * one per message). Absent or empty means nothing is indexed. The read tool
140
+ * takes them off before the cache and the model; other host reads
141
+ * (out-of-band host reads) return them untouched.
142
+ */
143
+ readonly observations?: readonly ObservationMaterial[];
144
+ /** How long the host may cache this result; see {@link UriCachePolicy.ttlMs}. */
145
+ readonly cachePolicy?: Pick<UriCachePolicy, "ttlMs">;
146
+ /**
147
+ * A short name for the resource this call touched, for the transcript row; absent shows the
148
+ * address. Render-only: the host stamps it on the tool result's `details`, never in `content`.
149
+ */
150
+ readonly label?: UriResourceLabel;
151
+ /**
152
+ * Answer to a `read` called with `ctx.knownRevision`: the module checked the source's current
153
+ * revision and it equals `knownRevision`, so `content` is empty and `metadata.revision` equals
154
+ * `knownRevision`. Set it only when that check was made and matched; a module that cannot
155
+ * check cheaply, or that found a different revision, returns the full resource without it.
156
+ */
157
+ readonly unchanged?: true;
56
158
  }
57
159
 
58
160
  declare interface RawResourceCacheControl {
@@ -96,7 +198,7 @@ declare interface RawResourceMetadata {
96
198
  readonly resourceIdentity?: RawResourceIdentity;
97
199
  /**
98
200
  * The resource's stable public address when `uri` reports something more
99
- * specific than the address a consumer names it by — a retained revision
201
+ * specific than the address a consumer names it by - a retained revision
100
202
  * (`skill://<id>/@revision/<version>`) served for a `skill://<source>/<name>`
101
203
  * read. Present only when the two differ. Consumers that attribute by name
102
204
  * (the read tool's `skillName` detail, and everything keyed off it) read
@@ -105,6 +207,24 @@ declare interface RawResourceMetadata {
105
207
  readonly canonicalUri?: string;
106
208
  }
107
209
 
210
+ /**
211
+ * The credential the host resolved for one call to plugin code that declares `requiresAuth`: a URI
212
+ * verb (`ctx.auth.credential`), a tool (`ctx.credential`) or a search provider (`ctx.credential`).
213
+ * Plain data, so it crosses a process boundary with the call; plugin code never asks for one.
214
+ */
215
+ declare interface ResolvedCredential {
216
+ /** Bearer for OAuth schemes, apiKey for api-key schemes. Host-resolved and refreshed. */
217
+ readonly accessToken: string;
218
+ readonly accountId: string;
219
+ readonly schemeId: string;
220
+ readonly scopes?: readonly string[];
221
+ /**
222
+ * Every value of a multi-field api-key credential, keyed by the scheme's declared field id
223
+ * (`accessToken` mirrors the primary field). Absent for single-secret schemes.
224
+ */
225
+ readonly fields?: Readonly<Record<string, string>>;
226
+ }
227
+
108
228
  /** One all-of arm in an ordered scope requirement. */
109
229
  declare interface ScopeAlternative {
110
230
  readonly allOf: readonly ScopeNeed[];
@@ -144,32 +264,43 @@ declare interface UriAgentHint {
144
264
  readonly triggers?: UriHintTriggers;
145
265
  /**
146
266
  * 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
267
+ * deeper workflow judgment - the name a `read skill://<name>` dispatch resolves, not a display
148
268
  * title or a path. Point here only at skills that actually exist in the composed skill roster;
149
269
  * a dangling name sends the agent to a `skill://` read for nothing.
150
270
  */
151
271
  readonly skills?: readonly string[];
152
272
  }
153
273
 
154
- declare type UriAuthActionClassification = {
155
- readonly kind: "action";
156
- readonly actionId: string;
157
- } | {
158
- readonly kind: "public";
159
- };
160
-
161
274
  /**
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.
275
+ * Finite authorization actions plus the rules that select one before dispatch. A call no rule
276
+ * matches, or a rule naming an action that is not in `actions`, fails closed in the host.
164
277
  */
165
278
  declare interface UriAuthActionPolicy {
166
279
  readonly actions: Readonly<Record<string, ScopeRequirement>>;
167
- classifyOperation(request: {
168
- readonly operation: UriOperation;
169
- readonly uri: string;
170
- }): UriAuthActionClassification | undefined;
280
+ /** Absent matches no call, so every call fails closed. */
281
+ readonly rules?: readonly UriAuthActionRule[];
171
282
  }
172
283
 
284
+ /**
285
+ * One row of a module's sign-in action rules: which calls need which action. A row matches a call
286
+ * when every condition it names holds; a condition it leaves out matches anything. The first
287
+ * matching row decides, so a row with no conditions at the end means "everything else".
288
+ */
289
+ declare type UriAuthActionRule = {
290
+ /** The verbs this row covers. */
291
+ readonly operations?: readonly UriOperation[];
292
+ /** The claim `route` ids this row covers, matched against the call's address. */
293
+ readonly routes?: readonly string[];
294
+ /** Query parameters the address must carry with exactly these values (`{ permanent: "true" }`). */
295
+ readonly query?: Readonly<Record<string, string>>;
296
+ } & ({
297
+ /** The {@link UriAuthActionPolicy.actions} entry the call needs. */
298
+ readonly action: string;
299
+ } | {
300
+ /** The call needs no credential. */
301
+ readonly public: true;
302
+ });
303
+
173
304
  declare interface UriAuthRef {
174
305
  readonly serviceId: string;
175
306
  readonly schemeId: string;
@@ -191,7 +322,7 @@ declare interface UriAuthRequirement {
191
322
  */
192
323
  readonly requiredScopes?: readonly string[];
193
324
  /**
194
- * The service id connect guidance should NAME when this module is gated — a host-composed
325
+ * The service id connect guidance should NAME when this module is gated - a host-composed
195
326
  * front door whose connect grants exactly this module's scopes (e.g. `gmail` expanding onto
196
327
  * the shared `google.account` credential). Guidance only; resolution keys on
197
328
  * `(serviceId, schemeId)`. Absent, guidance names `serviceId`.
@@ -237,6 +368,21 @@ declare type UriCachePolicyResolver = (operation: UriOperation, cacheUri: string
237
368
 
238
369
  declare type UriClaim = UriSchemeClaim | UriPatternClaim | UrlPatternClaim;
239
370
 
371
+ /**
372
+ * A URL with `:name` placeholders for the claim's captures. Each capture is escaped for where it
373
+ * lands: lowercased in the host, one path segment (a `:name*` capture keeps its `/`), or a query
374
+ * or fragment value. `{transform(...)}` runs one named, built-in transform over the text inside
375
+ * it, captures pasted in unescaped, and escapes the output for where it lands:
376
+ *
377
+ * - `base64`: unpadded base64 of the UTF-8 text (`{base64(:id :calendar)}`).
378
+ * - `slackTimestamp`: a Slack archive message token as the API's timestamp
379
+ * (`p1783350000000100` becomes `1783350000.000100`).
380
+ *
381
+ * A transform that cannot read its input fills nothing. A claim whose `normalizeTo` fills nothing
382
+ * does not match; a `sourceUrl` that fills nothing paints no link.
383
+ */
384
+ declare type UriClaimTemplate = string;
385
+
240
386
  declare interface UriCommonFlow {
241
387
  readonly label: string;
242
388
  readonly when?: string;
@@ -286,41 +432,28 @@ declare interface UriExample {
286
432
  readonly operation?: UriOperation;
287
433
  }
288
434
 
289
- declare interface UriExtensionModule extends Omit<UriModule, "verbs"> {
435
+ /**
436
+ * A plugin's URI module. Its claims route, spell and describe its addresses, so it has no
437
+ * `matches`, `normalize` or `describe`. It also has no `fallback` (a plugin's addresses are always
438
+ * claimed) and no `locate` (a plugin has no file on disk for the daemon to open).
439
+ */
440
+ declare interface UriExtensionModule extends Omit<UriModule, "verbs" | "normalize" | "matches" | "describe" | "fallback" | "locate"> {
290
441
  readonly verbs: UriVerbSupport;
291
442
  /** Human-facing per-verb effect docs; see {@link UriVerbDocs}. */
292
443
  readonly verbDocs?: UriVerbDocs;
293
444
  /**
294
445
  * 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
446
+ * compiles into a defaults-level ask rule - the user is prompted by default but can
296
447
  * override with an explicit allow. Undeclared = `"allow"` (fail-open).
297
448
  * See {@link UriPermissionDefaults}.
298
449
  */
299
450
  readonly permissionDefaults?: UriPermissionDefaults;
300
451
  /** Requires a resolved URI connection before any verb handler runs. */
301
452
  readonly requiresConnection?: boolean;
302
- /** Pure route-aware permission facts; see {@link UriExtensionOperationPermissionPolicy}. */
303
- readonly permission?: UriExtensionOperationPermissionPolicy;
304
453
  /** False rejects generic write-tool `source` copies before source fetch. */
305
454
  readonly acceptsSourceCopy?: boolean;
306
455
  }
307
456
 
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
457
  declare interface UriExternalCliRunner {
325
458
  readonly run: (request: UriExternalCliRunRequest) => Promise<UriExternalCliRunResult>;
326
459
  }
@@ -355,6 +488,8 @@ declare interface UriHelp {
355
488
  * than text the module's author wrote. Absent: the module's own text.
356
489
  */
357
490
  readonly thirdPartyContent?: true;
491
+ /** The module's own markdown for this result, shown instead of the host's default rendering. */
492
+ readonly markdown?: string;
358
493
  }
359
494
 
360
495
  declare interface UriHintTriggers {
@@ -394,6 +529,12 @@ declare interface UriListResult {
394
529
  readonly items: readonly UriListItem[];
395
530
  readonly nextCursor?: string;
396
531
  readonly metadata?: UriListMetadata;
532
+ /** How long the host may cache this result; see {@link UriCachePolicy.ttlMs}. */
533
+ readonly cachePolicy?: Pick<UriCachePolicy, "ttlMs">;
534
+ /** The module's own markdown for this result, shown instead of the host's default rendering. */
535
+ readonly markdown?: string;
536
+ /** The touched resource's name for the transcript row, as on `RawResource.label`. */
537
+ readonly label?: UriResourceLabel;
397
538
  }
398
539
 
399
540
  declare type UriLocateContext = Pick<UriResolveContext, "cwd" | "signal" | "connection" | "sessionId" | "paths"> & {
@@ -411,31 +552,13 @@ declare type UriLocateResult = {
411
552
  readonly kind: "not-disk-backed";
412
553
  };
413
554
 
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
555
  declare interface UriModule {
433
556
  readonly id: string;
434
557
  /**
435
558
  * Explicit human-readable name for catalog/display purposes (the plugin
436
559
  * 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
560
+ * hand since there is no automatic manifest->runtime-module composition
561
+ * step). Falls back to a mechanical id-derived title when absent - see
439
562
  * `uriCatalogTitle`.
440
563
  */
441
564
  readonly title?: string;
@@ -455,14 +578,26 @@ declare interface UriModule {
455
578
  * calls `parseLineSelector` itself and reports what it did through `RawResource.selector`.
456
579
  */
457
580
  readonly ownsLineSelectors?: boolean;
581
+ /** Opt in to host-resolved supplementary sources; absent means unsupported. */
582
+ readonly acceptsWriteSources?: boolean;
583
+ /**
584
+ * Opt in to raw bytes as `write`'s content; absent means text and JSON only. The `write`
585
+ * tool's `source` copy leg is the only producer: a provider's binary resource (an attachment,
586
+ * an image, a PDF) reaches a module that declares this as a `Uint8Array`, never decoded into
587
+ * the model's context, and a binary copy to any other module is refused before it runs.
588
+ */
589
+ readonly acceptsBytes?: boolean;
458
590
  readonly claims?: readonly UriClaim[];
591
+ /**
592
+ * The routes the module's scheme-root `help` lists, as data, so a host can list them without
593
+ * calling `help`.
594
+ */
595
+ readonly templates?: readonly UriTemplate[];
459
596
  /** Migration-only prompt summary until structured help/catalog coverage replaces prose. */
460
597
  readonly description?: string;
461
598
  readonly agentHint?: UriAgentHint;
462
599
  readonly requiresAuth?: UriAuthRequirement;
463
600
  readonly authActions?: UriAuthActionPolicy;
464
- /** Pure route/payload validation applied before a consequential permission ask. */
465
- readonly preflight?: UriOperationPreflightPolicy;
466
601
  /**
467
602
  * Structured facts for approval surfaces, or `undefined` when unavailable.
468
603
  * A one-line description must name the whole consequence; `preview` instead
@@ -472,120 +607,46 @@ declare interface UriModule {
472
607
  * of covered payload fields and query parameters. Unknown fields cost the summary.
473
608
  * The host owns framing, the whole-quote fit (no elision) and display-safety admission.
474
609
  *
475
- * Unlike preflight, this hook is async and may do I/O. Resolve names through `ctx`,
610
+ * This hook is async and may do I/O. Resolve names through `ctx`,
476
611
  * the same dispatch context used by execution, never an ambient or cached account. A name
477
612
  * the module resolved under that account may be cached for a bounded time, keyed by that
478
613
  * 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.
614
+ * The host calls it once before raising the ask and waits for the answer, with no deadline;
615
+ * `ctx.signal` fires when the host stops waiting (the session closes, or a caller gives up),
616
+ * and a late answer is discarded. A throw yields no
617
+ * summary and a module-attributed diagnostic; declining costs a click into the app, never an
618
+ * under-described approval.
500
619
  *
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
620
+ * "Allow with edits": a field marked `editable` may be rewritten by the person on the card.
621
+ * The host rebuilds the call itself (see the field's `editable`), then holds the rebuilt call to
622
+ * what the person was shown, in two steps, and runs it only if both pass. It classifies the
623
+ * rebuilt call again and requires the same tool, rule key, kind, resources, scheme and target
624
+ * address. It then calls this hook on the rebuilt call and requires that preview to read exactly
625
+ * as the one the person saw, apart from the values of the fields they edited: the same title,
626
+ * the same fields in the same order, each with the same label, key, `editable` marker and
514
627
  * `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
628
+ * with nothing done: a mismatch, and also an answer of `undefined` or a throw. The `description`
629
+ * line is not compared. So put whatever says where a call goes, or what it does beyond its
521
630
  * editable fields, in the preview's `title` or a field, never only in its `description`, and
522
631
  * keep recipients, destinations and thread bindings out of `editable` fields.
523
632
  */
524
- amendOperation?(request: {
633
+ describeOperation?(request: {
525
634
  readonly operation: UriOperation;
526
635
  readonly uri: string;
527
636
  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>;
637
+ }, ctx: UriResolveContext): Promise<UriOperationDescription | undefined>;
638
+ readonly getCachePolicy?: UriCachePolicyResolver;
550
639
  /**
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).
640
+ * Code matching, for the host's own modules whose addresses no claim can describe (a bare file
641
+ * path). A plugin module has none; it routes through its `claims` alone.
567
642
  */
568
- observes?(uri: string): boolean;
643
+ matches?(input: string, ctx: UriResolveContext): boolean;
644
+ /** The official spelling, for the host's own modules; a plugin's comes from its claims' `normalizeTo`. */
645
+ normalize?(input: string, ctx: UriResolveContext): UriNormalizeResult | undefined;
569
646
  /**
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`.
647
+ * The pill's title and link, for the host's own modules only; a plugin module has none, and its
648
+ * pill comes from its claims' `title` and `sourceUrl`.
583
649
  */
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
650
  describe?(input: string, ctx: UriDescribeContext): UriResourceDescription | undefined;
590
651
  locate?(input: string, ctx: UriLocateContext): Promise<UriLocateResult>;
591
652
  /**
@@ -598,33 +659,21 @@ declare interface UriModule {
598
659
  read(input: string, ctx: UriResolveContext): Promise<RawResource>;
599
660
  /** Observes only; see {@link read}. */
600
661
  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
662
  /**
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.
663
+ * `sources` is empty for ordinary writes; populated only with `acceptsWriteSources: true`.
664
+ * `content` is a `Uint8Array` only with `acceptsBytes: true`, and then `ctx.writeSource`
665
+ * names and types it. It is `undefined` when the call carried neither `content` nor `source`.
666
+ * The module decides what that means: a route that takes no body (a folder
667
+ * create) proceeds, and a route that needs one refuses with an error naming
668
+ * what is missing. Never treat it as empty content.
619
669
  */
620
- writeBytes?(input: string, bytes: Uint8Array, source: UriSourceDescriptor, ctx: UriResolveContext): Promise<UriMutationResult | void>;
670
+ write?(input: string, content: UriPayload | Uint8Array | undefined, sources: readonly UriWriteSource[], ctx: UriResolveContext): Promise<UriMutationResult | void>;
621
671
  edit?(input: string, patch: UriPayload, ctx: UriResolveContext): Promise<UriMutationResult>;
622
672
  /** Observes only; see {@link read}. */
623
673
  list?(input: string, params: UriListParams, ctx: UriResolveContext): Promise<UriListResult>;
624
674
  /** Observes only; see {@link read}. */
625
675
  search?(input: string, params: UriSearchParams, ctx: UriResolveContext): Promise<UriSearchResult>;
626
676
  delete?(input: string, ctx: UriResolveContext): Promise<UriMutationResult>;
627
- toMarkdown?(input: UriMarkdownRenderInput, ctx: UriResolveContext): string | undefined;
628
677
  }
629
678
 
630
679
  /** Authoritative lifecycle facts for a successful record-like URI mutation. */
@@ -650,6 +699,12 @@ declare interface UriMutationResult {
650
699
  readonly receipt?: UriMutationReceipt;
651
700
  /** Opaque source-native revision after this successful mutation. */
652
701
  readonly revision?: string;
702
+ /** Cached reads this mutation made stale; see {@link UriCachePolicy.invalidate}. */
703
+ readonly cachePolicy?: Pick<UriCachePolicy, "invalidate">;
704
+ /** The module's own markdown for this result, shown instead of the host's default rendering. */
705
+ readonly markdown?: string;
706
+ /** The touched resource's name for the transcript row, as on `RawResource.label`. */
707
+ readonly label?: UriResourceLabel;
653
708
  }
654
709
 
655
710
  declare interface UriNodeCapability {
@@ -670,11 +725,6 @@ declare interface UriNormalizeResult {
670
725
 
671
726
  declare type UriOperation = "help" | "read" | "write" | "edit" | "list" | "search" | "delete";
672
727
 
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
728
  declare interface UriOperationDescription {
679
729
  /** Static verb phrase authored by the module. Never interpolated from input. */
680
730
  readonly action: string;
@@ -697,9 +747,11 @@ declare interface UriOperationDescription {
697
747
  */
698
748
  readonly key?: string;
699
749
  /**
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.
750
+ * `true` offers this field to the person for editing on the approval card. The host applies
751
+ * an edit itself: it copies the payload and replaces the value of the top-level string
752
+ * property named `key`. So offer a field only when the payload is an object, `key` is one
753
+ * of its string properties, and the module would send any value within `maxLength` as it
754
+ * sends the model's; the host drops the offer otherwise. Absent means read-only.
703
755
  */
704
756
  readonly editable?: true;
705
757
  /** Longest edited value, in UTF-16 code units. Meaningful only with `editable`. */
@@ -717,56 +769,6 @@ declare interface UriOperationDescription {
717
769
  };
718
770
  }
719
771
 
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
772
  declare interface UriParameter {
771
773
  readonly name: string;
772
774
  readonly in: "uri" | "query" | "selector";
@@ -776,29 +778,60 @@ declare interface UriParameter {
776
778
  readonly examples?: readonly unknown[];
777
779
  }
778
780
 
781
+ /**
782
+ * A claim on a module's own custom-scheme addresses. The pattern matches with or without an
783
+ * account prefix: `gcal://@work/events/x` is matched as `gcal://events/x`, and the prefix is put
784
+ * back on the official spelling, so one claim covers both spellings.
785
+ */
779
786
  declare interface UriPatternClaim {
780
787
  readonly kind: "uriPattern";
781
788
  readonly pattern: UriPatternInit;
782
789
  /**
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
790
+ * Canonical spelling rebuilt from the captures (see {@link UriClaimTemplate}). When the template
791
+ * keeps the input's scheme (the module's own addressing), the input's query string is
785
792
  * carried over verbatim so a route flag such as `?permanent=true` survives
786
793
  * canonicalization (kay#7859); a template that re-targets to another scheme
787
794
  * (a projection to a web link) drops it, as does a pattern that consumed the
788
795
  * query itself or a template that spells its own.
789
796
  */
790
- readonly normalizeTo?: string;
797
+ readonly normalizeTo?: UriClaimTemplate;
798
+ /**
799
+ * Names the route this claim matches, so data keyed by route (a module's sign-in action rules)
800
+ * can say which addresses it means. Several claims may name the same route.
801
+ */
802
+ readonly route?: string;
803
+ /**
804
+ * The web page a matched address opens, filled from the captures (see
805
+ * {@link UriClaimTemplate}). The host matches an address's official spelling and paints its
806
+ * reference with the source URL of the first matching claim that declares one.
807
+ */
808
+ readonly sourceUrl?: UriClaimTemplate;
809
+ /**
810
+ * The name a matched address's reference shows: literal text, with each `:name` replaced by
811
+ * that capture as decoded (`"Flow :flow"`, or a fixed `"BFS"`). The first matching claim that
812
+ * declares one names the address.
813
+ */
814
+ readonly title?: string;
791
815
  readonly examples?: readonly string[];
792
816
  }
793
817
 
794
818
  declare interface UriPatternInit {
795
819
  readonly protocol?: string;
820
+ /**
821
+ * Dot-delimited host pattern. A pattern that is one capture (`:owner`) takes the whole host,
822
+ * dots included, as a `github://some.org/repo` owner must.
823
+ */
796
824
  readonly hostname?: string;
797
825
  /**
798
826
  * Slash-delimited path pattern. A final capture may use `:name*` to consume
799
- * the remaining path segments, preserving `/` in the captured value.
827
+ * the remaining path segments, preserving `/` in the captured value. Unless the pattern itself
828
+ * ends in `/`, an input path ending in one `/` matches as if the slash were absent, so
829
+ * `threads/t1/` is `threads/t1` (captures and official spelling included) and a lone `/` after a
830
+ * host is the empty path; a doubled `//` is never dropped, nor the root path of an address with
831
+ * no host (`bfs:///`).
800
832
  */
801
833
  readonly pathname?: string;
834
+ /** `&`-delimited query pattern; `""` matches only an address with no query. */
802
835
  readonly search?: string;
803
836
  }
804
837
 
@@ -817,7 +850,7 @@ declare interface UriPayloadContract {
817
850
 
818
851
  /**
819
852
  * The three-state permission vocabulary a plugin declares for its default permissions.
820
- * Maps to `PermissionRule.effect`: `allow` → allow, `ask` → ask, `never` → deny.
853
+ * Maps to `PermissionRule.effect`: `allow` -> allow, `ask` -> ask, `never` -> deny.
821
854
  */
822
855
  declare type UriPermissionDefault = "allow" | "ask" | "never";
823
856
 
@@ -835,19 +868,6 @@ declare interface UriPermissionDefaults {
835
868
  readonly delete?: UriPermissionDefault;
836
869
  }
837
870
 
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
871
  declare interface UriResolveContext {
852
872
  readonly cwd: string;
853
873
  readonly signal?: AbortSignal;
@@ -856,8 +876,8 @@ declare interface UriResolveContext {
856
876
  readonly auth?: UriResolvedAuth;
857
877
  /**
858
878
  * 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.
879
+ * It never originates from anything the caller can influence - no verb argument, no write
880
+ * body - so a module may treat it as trustworthy caller identity and stamp it onto writes.
861
881
  *
862
882
  * Absent when no session owns the dispatch: registry matching and permission pre-checks
863
883
  * resolve a URI outside any verb call.
@@ -865,14 +885,14 @@ declare interface UriResolveContext {
865
885
  readonly sessionId?: string;
866
886
  /**
867
887
  * 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
888
+ * {@link UriResolveContext.sessionId} and just as un-influenceable by the caller - so a module
869
889
  * writing to a store it owns can record the actor without the agent being able to choose one.
870
890
  *
871
891
  * Free text minted by the host's own runtime (Bonsai's form is `user`, `subagent`,
872
892
  * `system/<surface>`, `followup/<surface>`) rather than a closed union: the surface half is the
873
893
  * host's vocabulary, and a second enumeration of it here would drift from the first.
874
894
  *
875
- * Absent whenever the host has nothing to say — no session owns the dispatch, or the session
895
+ * Absent whenever the host has nothing to say - no session owns the dispatch, or the session
876
896
  * predates the host recording an origin. A module must then omit the actor rather than assume
877
897
  * one; an invented actor written into a durable artifact can never be walked back.
878
898
  */
@@ -888,7 +908,7 @@ declare interface UriResolveContext {
888
908
  readonly origin: "model" | "host";
889
909
  /**
890
910
  * 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
911
+ * frame carried one. Absent means the frame carried none - never a reason to guess one: a
892
912
  * module recording this onto a durable artifact must treat absence as unresolved provenance,
893
913
  * not as "no run was active".
894
914
  */
@@ -897,39 +917,33 @@ declare interface UriResolveContext {
897
917
  readonly toolName?: string;
898
918
  };
899
919
  /**
900
- * An OPAQUE, host-captured context for the mutation this dispatch is about to make —
920
+ * An OPAQUE, host-captured context for the mutation this dispatch is about to make -
901
921
  * the artifact-provenance counterpart of `sessionId`/`actor`, and stamped at the same
902
922
  * un-influenceable dispatch site.
903
923
  *
904
924
  * The host composes it from facts a caller cannot supply: which session is acting, the
905
925
  * 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
926
+ * session came to exist. The kernel treats it as an uninterpreted JSON value - it never
907
927
  * 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
928
+ * dispatch boundary - so this layer stays ignorant of any one product's provenance
909
929
  * semantics. A module that owns a durable artifact carries it to its store and validates
910
930
  * it against its OWN schema at the point it persists, where the target revision is known.
911
931
  *
912
932
  * 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
933
+ * capture resolver, or a capture that failed) - never a claim that there was nothing to
914
934
  * capture. Present only on mutating dispatches (`write`/`edit`/`delete`); reads never pay
915
935
  * for it.
916
936
  */
917
937
  readonly captureContext?: Readonly<Record<string, unknown>>;
918
938
  /**
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 —
939
+ * Host-provided writable data root for the OWNING extension of the module handling this call -
926
940
  * structurally identical to the per-extension `paths` a tool/handler context already receives, so
927
941
  * a module authored against both surfaces reads `ctx.paths?.dataDir` uniformly. Lets a URI module
928
942
  * own durable local state (a JSON store, a small file tree) instead of the host constructing and
929
943
  * injecting it. Host-populated after dispatch selects a module and keyed to that module's owner;
930
944
  * always absent during matching, and absent for modules with no owning extension (builtins) or a
931
945
  * 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
946
+ * a URI module and does NOT pre-create the directory - create it on first write and tolerate its
933
947
  * absence on read.
934
948
  */
935
949
  readonly paths?: {
@@ -944,6 +958,15 @@ declare interface UriResolveContext {
944
958
  * proceeds as before.
945
959
  */
946
960
  readonly expectedRevision?: string;
961
+ /**
962
+ * The revision this session already holds for the address a `read` is about to fetch: the
963
+ * `RawResourceMetadata.revision` its last read reported. Set only on a repeat `read`, never
964
+ * on a mutation (`expectedRevision` is the mutation witness). A module with a cheap
965
+ * current-revision check (a metadata request, a `stat`) may compare first and, when it has
966
+ * verified the two are equal, answer {@link RawResource.unchanged} with no body. A module
967
+ * with no such check ignores it and reads in full, which is always correct.
968
+ */
969
+ readonly knownRevision?: string;
947
970
  /** Host marks a mediated session that enforces its private revision witness ledger. */
948
971
  readonly requiresRevisionWitness?: boolean;
949
972
  /** Host-derived descriptor for a top-level copied resource; absent for inline writes. */
@@ -953,26 +976,13 @@ declare interface UriResolveContext {
953
976
  declare interface UriResolvedAuth {
954
977
  readonly connection?: UriConnection;
955
978
  /** Present only for verb handlers of a module declaring `requiresAuth`; absent during matching. */
956
- readonly credential?: UriResolvedCredential;
979
+ readonly credential?: ResolvedCredential;
957
980
  /** Present for handlers gated by a non-injectable external-CLI readiness scheme. */
958
981
  readonly externalCli?: UriExternalCliRunner;
959
982
  /** Opaque host metadata; not a credential channel. */
960
983
  readonly metadata?: Record<string, unknown>;
961
984
  }
962
985
 
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
986
  declare interface UriResourceDescription {
977
987
  readonly uri: string;
978
988
  /** Absent when the caller should retain its existing label. */
@@ -992,13 +1002,6 @@ declare interface UriResourceLabel {
992
1002
  readonly section?: string;
993
1003
  }
994
1004
 
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
1005
  declare interface UriSchemeClaim {
1003
1006
  readonly kind: "scheme";
1004
1007
  readonly scheme: string;
@@ -1032,15 +1035,21 @@ declare interface UriSearchResult {
1032
1035
  * so the caller knows the matches may be partial.
1033
1036
  */
1034
1037
  readonly metadata?: UriListMetadata;
1038
+ /** How long the host may cache this result; see {@link UriCachePolicy.ttlMs}. */
1039
+ readonly cachePolicy?: Pick<UriCachePolicy, "ttlMs">;
1040
+ /** The module's own markdown for this result, shown instead of the host's default rendering. */
1041
+ readonly markdown?: string;
1042
+ /** The touched resource's name for the transcript row, as on `RawResource.label`. */
1043
+ readonly label?: UriResourceLabel;
1035
1044
  }
1036
1045
 
1037
1046
  /**
1038
1047
  * What a copied resource IS, minus its bytes: the identity half of a mediated
1039
1048
  * source. The host derives it once (filename inferred from the source address,
1040
1049
  * `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.
1050
+ * delivers it inside each `UriWriteSource`, and on `ctx.writeSource` for a
1051
+ * `source` copy - so a remote sink can name and type what it stores without
1052
+ * re-deriving either from raw bytes.
1044
1053
  */
1045
1054
  declare interface UriSourceDescriptor {
1046
1055
  readonly uri: string;
@@ -1055,11 +1064,11 @@ declare interface UriTemplate {
1055
1064
  /**
1056
1065
  * Per-operation effect docs for this route, keyed by operation. A route
1057
1066
  * whose shared {@link description} spans several verbs ("Read, edit, or
1058
- * trash a thread") is ambiguous for any surface that groups by verb — a
1067
+ * trash a thread") is ambiguous for any surface that groups by verb - a
1059
1068
  * permission view must not describe `read` with a sentence that mentions
1060
1069
  * trashing. Each entry says what THAT operation concretely does at THIS
1061
1070
  * route; operations without an entry fall back to the shared description.
1062
- * Additive and optional — existing templates are unaffected.
1071
+ * Additive and optional - existing templates are unaffected.
1063
1072
  */
1064
1073
  readonly operationDocs?: Partial<Record<UriOperation, string>>;
1065
1074
  /**
@@ -1099,13 +1108,13 @@ declare interface UriValidationMessage {
1099
1108
 
1100
1109
  /**
1101
1110
  * Author-declared, human-facing documentation of what each supported verb
1102
- * CONCRETELY does against the provider — the capability half of a module's
1111
+ * CONCRETELY does against the provider - the capability half of a module's
1103
1112
  * boolean verb-support declaration. Written for a permission/capability
1104
1113
  * surface ("what am I allowing?"), not for the model: one plain sentence per
1105
1114
  * verb naming the real-world effect ("Sends mail, creates/updates drafts,
1106
1115
  * applies label changes"), never wire mechanics or URI shapes. Verbs the
1107
1116
  * 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
1117
+ * entry simply renders undocumented (fail-open metadata) - but for a plugin's
1109
1118
  * `kay.agent` URI modules, `auditPluginDisclosure` (`@bonsai/plugin-sdk/
1110
1119
  * disclosure`) enforces coverage of every supported verb at authoring/load
1111
1120
  * time.
@@ -1113,7 +1122,7 @@ declare interface UriValidationMessage {
1113
1122
  * Lives here in the protocol layer (beside {@link UriTemplate.operationDocs},
1114
1123
  * its per-route sibling) so both `@herd/uri-extensions`' `UriExtensionModule`
1115
1124
  * and herd-agent's kernel `UriModule` can carry it without inverting the
1116
- * `@herd/uri` ← `@herd/uri-extensions` dependency.
1125
+ * `@herd/uri` <- `@herd/uri-extensions` dependency.
1117
1126
  */
1118
1127
  declare interface UriVerbDocs {
1119
1128
  /** What a `read` of this address space returns (also covers `help`). */
@@ -1136,6 +1145,7 @@ declare interface UriVerbSupport {
1136
1145
  readonly editable: boolean;
1137
1146
  readonly deletable?: boolean;
1138
1147
  readonly listable: boolean;
1148
+ readonly searchable?: boolean;
1139
1149
  }
1140
1150
 
1141
1151
  /** A mediated resource supplied beside structured write content without entering model context. */
@@ -1143,10 +1153,12 @@ declare interface UriWriteSource extends UriSourceDescriptor {
1143
1153
  readonly content: string | Uint8Array;
1144
1154
  }
1145
1155
 
1156
+ /** A claim on a public web link (`http` or `https`), usually folded onto the module's own scheme. */
1146
1157
  declare interface UrlPatternClaim {
1147
1158
  readonly kind: "urlPattern";
1148
1159
  readonly pattern: UriPatternInit;
1149
- readonly normalizeTo?: string;
1160
+ readonly normalizeTo?: UriClaimTemplate;
1161
+ readonly route?: string;
1150
1162
  readonly examples?: readonly string[];
1151
1163
  }
1152
1164
 
@@ -1157,7 +1169,7 @@ export declare type VendoredMcpClaim = NonNullable<UriExtensionModule["claims"]>
1157
1169
  * Everything a vendored-MCP plugin's URI module is, expressed as data.
1158
1170
  *
1159
1171
  * 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
1172
+ * it writes no read projection, no help document, and no route parsing. That is deliberate - this
1161
1173
  * kit exists because five pilot plugins were about to become five copies of one 460-line module,
1162
1174
  * and ~40 more are queued behind them.
1163
1175
  */
@@ -1193,11 +1205,89 @@ export declare interface VendoredMcpDescriptor {
1193
1205
  readonly skills: readonly string[];
1194
1206
  }
1195
1207
 
1208
+ /** The claim shapes a vendored plugin declares, with plain mutable arrays so a manifest can spread them. */
1209
+ declare type VendoredMcpFactClaim = {
1210
+ kind: "scheme";
1211
+ scheme: string;
1212
+ } | {
1213
+ kind: "uriPattern";
1214
+ pattern: FactPattern;
1215
+ normalizeTo: string;
1216
+ route: string;
1217
+ sourceUrl: string;
1218
+ } | {
1219
+ kind: "urlPattern";
1220
+ pattern: FactPattern;
1221
+ normalizeTo?: string;
1222
+ route?: string;
1223
+ examples?: string[];
1224
+ };
1225
+
1226
+ /**
1227
+ * ---
1228
+ * responsibility: The one interface through which a vendored-MCP plugin's code calls a tool on its
1229
+ * own vendored MCP server, so every read-through plugin and the daemon that implements the port
1230
+ * agree on the same shape by construction.
1231
+ * owns:
1232
+ * - {@link VendoredMcpPort} - what a plugin's URI module and tools call.
1233
+ * - {@link VendoredMcpOutcome} - the typed result a plugin decodes, including every failure it
1234
+ * must surface to the agent rather than swallow.
1235
+ * does_not_own:
1236
+ * - The implementation. A plugin never dials MCP itself: it takes the port from
1237
+ * `api.vendoredMcp.port(server)` (`@bonsai/plugin-sdk/kay-agent`), which the daemon binds to the
1238
+ * plugin's own hosted server over its MCP host. Plugin source stays host-free, and a plugin
1239
+ * under test substitutes a fake port.
1240
+ * - Which tool names exist. `TTool` is the plugin's own union of the vendor tools it calls; the
1241
+ * port is generic so a typo in a tool name fails typecheck inside the plugin.
1242
+ * ---
1243
+ */
1244
+ /**
1245
+ * Why a call failed, as the plugin must tell the agent. `needs-auth` carries the exact service to
1246
+ * reconnect (`serviceId`) so a plugin can name it in its error instead of returning an empty
1247
+ * result; today's plugins name their statically declared `requiresAuth` service instead, which is
1248
+ * the same service for a single-service plugin. `unknown-server` and `unknown-tool` are wiring
1249
+ * faults (a server Bonsai does not host for this plugin, or a tool name the server does not have)
1250
+ * and stay loud for the same reason: an unreachable host must never decode as "no data".
1251
+ */
1252
+ declare type VendoredMcpFailureReason = "needs-auth" | "unknown-server" | "unknown-tool" | "failed";
1253
+
1254
+ export declare type VendoredMcpOutcome =
1255
+ /**
1256
+ * `value` is the tool's `structuredContent` when it sent one, else its text content parsed as
1257
+ * JSON, else that text as a string: a vendor tool that answers in prose (a transcript, a
1258
+ * markdown summary) is a result, not a failure. A route that needs a record rejects a string
1259
+ * itself, with its own message.
1260
+ */
1261
+ {
1262
+ readonly ok: true;
1263
+ readonly value: unknown;
1264
+ } | {
1265
+ readonly ok: false;
1266
+ readonly reason: VendoredMcpFailureReason;
1267
+ readonly message: string;
1268
+ /** Exact reconnect target supplied by the MCP host for an auth failure. */
1269
+ readonly serviceId?: string;
1270
+ };
1271
+
1272
+ /**
1273
+ * One vendored MCP server, as the plugin that declares it calls it. `invoke` calls any tool the
1274
+ * server has, whether it reads or writes. The port checks no permission of its own: a call is as
1275
+ * permitted as the plugin tool or URI verb that makes it.
1276
+ */
1277
+ export declare interface VendoredMcpPort<TTool extends string = string> {
1278
+ invoke(input: {
1279
+ /** The session's working directory; the host resolves the server binding per cwd. */
1280
+ readonly cwd: string;
1281
+ readonly tool: TTool;
1282
+ readonly args: Readonly<Record<string, unknown>>;
1283
+ }): Promise<VendoredMcpOutcome>;
1284
+ }
1285
+
1196
1286
  /**
1197
1287
  * 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.
1288
+ * `<ns>://...` URI Bonsai stores, and the public web URL that URI came from and opens back to.
1199
1289
  *
1200
- * Both templates use the same `:name` capture syntax and MUST name exactly the same captures —
1290
+ * Both templates use the same `:name` capture syntax and MUST name exactly the same captures -
1201
1291
  * {@link createVendoredMcpUriModule} throws otherwise. That symmetry is the point: a canonical
1202
1292
  * URI carrying an id the web URL drops is a pill that cannot be opened, and a web URL naming a
1203
1293
  * capture the canonical URI never captured renders a literal `:pageId` into the user's browser.
@@ -1214,4 +1304,43 @@ export declare interface VendoredMcpRoute {
1214
1304
  readonly example: string;
1215
1305
  }
1216
1306
 
1307
+ /** What a vendored-MCP plugin's manifest declares for its URI module. */
1308
+ export declare interface VendoredMcpUriModuleFacts {
1309
+ description: string;
1310
+ agentHint: {
1311
+ summary: string;
1312
+ commonFlows: {
1313
+ label: string;
1314
+ example: string;
1315
+ }[];
1316
+ triggers: {
1317
+ keywords: string[];
1318
+ };
1319
+ helpUri: string;
1320
+ skills: string[];
1321
+ };
1322
+ verbs: {
1323
+ readable: true;
1324
+ writable: false;
1325
+ editable: false;
1326
+ listable: false;
1327
+ };
1328
+ verbDocs: {
1329
+ read: string;
1330
+ };
1331
+ templates: {
1332
+ uri: string;
1333
+ description: string;
1334
+ operations: ["read"];
1335
+ }[];
1336
+ claims: VendoredMcpFactClaim[];
1337
+ }
1338
+
1339
+ /**
1340
+ * The facts for one descriptor. Each route becomes a `uriPattern` claim whose `normalizeTo` is the
1341
+ * route's canonical URI and whose `sourceUrl` is the route's web URL, so a pasted or stored address
1342
+ * resolves to its canonical spelling and a pill that opens the vendor's page.
1343
+ */
1344
+ export declare function vendoredMcpUriModuleFacts(descriptor: VendoredMcpDescriptor): VendoredMcpUriModuleFacts;
1345
+
1217
1346
  export { }