@yaklabs-ai/plugin-sdk 0.54.2 → 0.55.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -13,7 +13,7 @@ it under the name your plugin code actually imports:
13
13
  ```json
14
14
  {
15
15
  "dependencies": {
16
- "@bonsai/plugin-sdk": "npm:@yaklabs-ai/plugin-sdk@^0.54.2"
16
+ "@bonsai/plugin-sdk": "npm:@yaklabs-ai/plugin-sdk@^0.55.1"
17
17
  }
18
18
  }
19
19
  ```
@@ -62,7 +62,7 @@ Every tool the entrypoint registers is declared in the manifest too:
62
62
  "description": "Greets people by name.",
63
63
  "version": "0.1.0",
64
64
  "namespace": "widgets",
65
- "compatibility": { "sdk": "^0.54.2" },
65
+ "compatibility": { "sdk": "^0.55.1" },
66
66
  "targets": {
67
67
  "kay": {
68
68
  "agent": {
@@ -153,7 +153,7 @@ naming both versions, instead of loading it and finding no `activate`.
153
153
  | `@bonsai/plugin-sdk/system-one` | The types and error class for `api.systemOne` / `app.systemOne`, from 0.13.8 |
154
154
  | `@bonsai/plugin-sdk/zod` | `z` (the host's in the daemon), on an entry with no other import, from 0.13.9 |
155
155
 
156
- A plugin that imports these subpaths declares the current supported SDK range (`^0.54.2`).
156
+ A plugin that imports these subpaths declares the current supported SDK range (`^0.55.1`).
157
157
  The rows record when each subpath appeared, not retained runtime compatibility. `vendored-mcp` builds the plugin's URI module (its link cards and help); hosting the
158
158
  vendor's MCP server itself is a separate host decision: Bonsai hosts it once a person approves
159
159
  the plugin's `mcp.host-server` grant for that server's name and URL. A server that needs an OAuth
@@ -161,7 +161,7 @@ sign-in names one of the plugin's auth services in `authService`; that service m
161
161
  one `delegated` scheme, which Bonsai uses to sign the person in to the server. Bonsai declares that
162
162
  scheme at runtime; the plugin's code must not declare it again. From 0.16.2 a plugin's code can also
163
163
  call the server's tools through `api.vendoredMcp.port(<server>)`, typed by this subpath's
164
- `VendoredMcpPort` and `VendoredMcpOutcome` (declare `^0.54.2`).
164
+ `VendoredMcpPort` and `VendoredMcpOutcome` (declare `^0.55.1`).
165
165
 
166
166
  From 0.13.10 the manifest schema carries two facts Bonsai reads for its own plugins only; another
167
167
  plugin's are ignored, with a load diagnostic. An `external-cli` auth scheme's `cli` says how Bonsai
@@ -243,7 +243,7 @@ Provider schemas remain authoritative; declaring a dependency grants no authorit
243
243
  through `app.uri`, parse returned Markdown in the consuming plugin, and retain the read's
244
244
  revision (or `contentDigest` from `/uri`) for edits. The old `memory.read` and `memory.write`
245
245
  grants do not authorize the replacement scheme-and-verb calls. Contracts before 0.49.0 are
246
- refused; migrate and declare `^0.54.2`. The current host also serves the 0.49.0 resource contract.
246
+ refused; migrate and declare `^0.55.1`. The current host also serves the 0.49.0 resource contract.
247
247
 
248
248
  ## What this package is, and is not, at runtime
249
249
 
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "package": "@yaklabs-ai/plugin-sdk",
4
- "version": "0.54.2",
4
+ "version": "0.55.1",
5
5
  "target": "npm",
6
6
  "firstPartyLicense": "MIT",
7
7
  "bundled": [
@@ -2673,8 +2673,15 @@ declare const KAY_PLUGIN_PROTOCOL_VERSION: "kay.plugin.v1";
2673
2673
  * optional `attachments`: each file's name, media type, size and the address its bytes are read
2674
2674
  * from, never the bytes. Gmail lists a mail's attachments there, so Flows can file them. Additive:
2675
2675
  * a plugin that publishes attachments declares ^0.54.2.
2676
+ *
2677
+ * 0.55.0 carries managed MCP calls over resource writes in both daemon and plugin-host code.
2678
+ * Existing agent and background MCP call shapes remain supported.
2679
+ *
2680
+ * 0.55.1 adds canonical path resources on declared tool permissions, asynchronous display-only
2681
+ * tool previews with cancellation, app-scoped availability snapshots and app-target output
2682
+ * artifact usage declarations. A plugin using these declares ^0.55.1.
2676
2683
  */
2677
- declare const KAY_PLUGIN_SDK_VERSION: "0.54.2";
2684
+ declare const KAY_PLUGIN_SDK_VERSION: "0.55.1";
2678
2685
 
2679
2686
  declare const KAY_PLUGIN_TARGET_NAMES: readonly ["kay.agent", "kay.app", "kay.ui"];
2680
2687
 
@@ -3132,13 +3139,8 @@ declare interface KayAgentVendoredMcp {
3132
3139
  * The port to `server`, a name this plugin declares in its `kay.app` target's
3133
3140
  * `contributes.mcpServers`. Throws when the plugin declares no server of that name. A declared
3134
3141
  * server that Bonsai does not host for this plugin still returns a port; each call then answers
3135
- * `unknown-server`. Its message names the plugin that holds the name, or else lists the possible
3136
- * reasons (no approved `mcp.host-server` grant, a sign-in the server cannot use, or a name that
3137
- * a plugin Bonsai ships or another installed plugin also claims) and points at the plugin's load
3138
- * diagnostic.
3139
- *
3140
- * A port reaches this plugin's own server only. To use another plugin's data, declare that
3141
- * plugin in the manifest's `dependencies.plugins`; a later SDK surface reaches it.
3142
+ * `unknown-server`. Each invocation uses the resource caller's exact `kay.agent` `uri.call`
3143
+ * grant for `mcp` / `write`; MCP checks the host-stamped caller against server ownership.
3142
3144
  */
3143
3145
  port<TTool extends string = string>(server: string): VendoredMcpPort<TTool>;
3144
3146
  }
@@ -8376,9 +8378,10 @@ declare interface ToolPermissionHint {
8376
8378
  * labelled facts. Render-only: never matched against a rule, never one-tap approval grounds.
8377
8379
  * The tool says what its input claims, so the gate shows it as the tool's claim, not as the
8378
8380
  * daemon's attestation. The host admits only display-safe text; a throw, a blank title or an
8379
- * unsafe string costs the description and the call is asked about as before.
8381
+ * unsafe string costs the description and the call is asked about as before. A promise is
8382
+ * bounded by the host description timeout; forward its optional cancellation signal to reads.
8380
8383
  */
8381
- readonly describePreview?: (input: Record<string, unknown>) => ToolPreviewDescription | undefined;
8384
+ readonly describePreview?: (input: Record<string, unknown>, signal?: AbortSignal) => ToolPreviewDescription | undefined | Promise<ToolPreviewDescription | undefined>;
8382
8385
  readonly kind?: ToolActionKind;
8383
8386
  readonly scheme?: string;
8384
8387
  readonly ruleKey?: string;
@@ -10639,7 +10642,7 @@ declare interface Usage {
10639
10642
  /**
10640
10643
  * ---
10641
10644
  * responsibility: The one interface through which a vendored-MCP plugin's code calls a tool on its
10642
- * own vendored MCP server, so every read-through plugin and the daemon that implements the port
10645
+ * own vendored MCP server, so every read-through plugin and the SDK that implements the port
10643
10646
  * agree on the same shape by construction.
10644
10647
  * owns:
10645
10648
  * - {@link VendoredMcpPort} - what a plugin's URI module and tools call.
@@ -10647,8 +10650,8 @@ declare interface Usage {
10647
10650
  * must surface to the agent rather than swallow.
10648
10651
  * does_not_own:
10649
10652
  * - The implementation. A plugin never dials MCP itself: it takes the port from
10650
- * `api.vendoredMcp.port(server)` (`@bonsai/plugin-sdk/kay-agent`), which the daemon binds to the
10651
- * plugin's own hosted server over its MCP host. Plugin source stays host-free, and a plugin
10653
+ * `api.vendoredMcp.port(server)` (`@bonsai/plugin-sdk/kay-agent`), which the SDK binds through
10654
+ * resource calls to the plugin's own hosted server. Plugin source stays host-free, and a plugin
10652
10655
  * under test substitutes a fake port.
10653
10656
  * - Which tool names exist. `TTool` is the plugin's own union of the vendor tools it calls; the
10654
10657
  * port is generic so a typo in a tool name fails typecheck inside the plugin.
@@ -10684,8 +10687,8 @@ declare type VendoredMcpOutcome =
10684
10687
 
10685
10688
  /**
10686
10689
  * One vendored MCP server, as the plugin that declares it calls it. `invoke` calls any tool the
10687
- * server has, whether it reads or writes. The port checks no permission of its own: a call is as
10688
- * permitted as the plugin tool or URI verb that makes it.
10690
+ * server has, whether it reads or writes. The resource caller checks the target's exact
10691
+ * `uri.call` grant, and MCP checks the host-stamped caller against server ownership.
10689
10692
  */
10690
10693
  declare interface VendoredMcpPort<TTool extends string = string> {
10691
10694
  invoke(input: {
@@ -817,7 +817,7 @@ declare interface DerivedToolClassification {
817
817
  * `execute`: name, parameters, auth binding and permission all come from the manifest, as in a
818
818
  * session.
819
819
  */
820
- export declare function describedToolForTest(contract: ResolvedPluginContractV1, localId: string, execute: KayAgentToolExecute): ToolDefinition;
820
+ export declare function describedToolForTest(contract: ResolvedPluginContractV1, localId: string, execute: KayAgentToolExecute, environment?: ToolPermissionEnvironment): ToolDefinition;
821
821
 
822
822
  declare const Digit: TDigit;
823
823
 
@@ -2727,8 +2727,15 @@ declare const KAY_PLUGIN_PROTOCOL_VERSION: "kay.plugin.v1";
2727
2727
  * optional `attachments`: each file's name, media type, size and the address its bytes are read
2728
2728
  * from, never the bytes. Gmail lists a mail's attachments there, so Flows can file them. Additive:
2729
2729
  * a plugin that publishes attachments declares ^0.54.2.
2730
+ *
2731
+ * 0.55.0 carries managed MCP calls over resource writes in both daemon and plugin-host code.
2732
+ * Existing agent and background MCP call shapes remain supported.
2733
+ *
2734
+ * 0.55.1 adds canonical path resources on declared tool permissions, asynchronous display-only
2735
+ * tool previews with cancellation, app-scoped availability snapshots and app-target output
2736
+ * artifact usage declarations. A plugin using these declares ^0.55.1.
2730
2737
  */
2731
- declare const KAY_PLUGIN_SDK_VERSION: "0.54.2";
2738
+ declare const KAY_PLUGIN_SDK_VERSION: "0.55.1";
2732
2739
 
2733
2740
  declare const KAY_PLUGIN_TARGET_NAMES: readonly ["kay.agent", "kay.app", "kay.ui"];
2734
2741
 
@@ -3183,13 +3190,8 @@ declare interface KayAgentVendoredMcp {
3183
3190
  * The port to `server`, a name this plugin declares in its `kay.app` target's
3184
3191
  * `contributes.mcpServers`. Throws when the plugin declares no server of that name. A declared
3185
3192
  * server that Bonsai does not host for this plugin still returns a port; each call then answers
3186
- * `unknown-server`. Its message names the plugin that holds the name, or else lists the possible
3187
- * reasons (no approved `mcp.host-server` grant, a sign-in the server cannot use, or a name that
3188
- * a plugin Bonsai ships or another installed plugin also claims) and points at the plugin's load
3189
- * diagnostic.
3190
- *
3191
- * A port reaches this plugin's own server only. To use another plugin's data, declare that
3192
- * plugin in the manifest's `dependencies.plugins`; a later SDK surface reaches it.
3193
+ * `unknown-server`. Each invocation uses the resource caller's exact `kay.agent` `uri.call`
3194
+ * grant for `mcp` / `write`; MCP checks the host-stamped caller against server ownership.
3193
3195
  */
3194
3196
  port<TTool extends string = string>(server: string): VendoredMcpPort<TTool>;
3195
3197
  }
@@ -5139,9 +5141,9 @@ declare const ResolvedPluginContractV1: z.ZodObject<{
5139
5141
  resource: "resource";
5140
5142
  storage: "storage";
5141
5143
  uriModule: "uriModule";
5144
+ app: "app";
5142
5145
  authService: "authService";
5143
5146
  command: "command";
5144
- app: "app";
5145
5147
  skill: "skill";
5146
5148
  converter: "converter";
5147
5149
  artifactUriModule: "artifactUriModule";
@@ -5915,6 +5917,7 @@ declare const ResolvedPluginContractV1: z.ZodObject<{
5915
5917
  displayName: z.ZodOptional<z.ZodString>;
5916
5918
  authServiceId: z.ZodOptional<z.ZodString>;
5917
5919
  authSchemeId: z.ZodOptional<z.ZodString>;
5920
+ requiredScopes: z.ZodOptional<z.ZodArray<z.ZodString>>;
5918
5921
  knownResources: z.ZodOptional<z.ZodLiteral<true>>;
5919
5922
  describesOperations: z.ZodOptional<z.ZodLiteral<true>>;
5920
5923
  looksUpWebLinks: z.ZodOptional<z.ZodLiteral<true>>;
@@ -5973,6 +5976,9 @@ declare const ResolvedPluginContractV1: z.ZodObject<{
5973
5976
  resourcePrefix: z.ZodOptional<z.ZodString>;
5974
5977
  resourceArgs: z.ZodOptional<z.ZodArray<z.ZodString>>;
5975
5978
  resourceTrim: z.ZodOptional<z.ZodLiteral<true>>;
5979
+ resourcePathNormalization: z.ZodOptional<z.ZodObject<{
5980
+ preserveUriSchemes: z.ZodOptional<z.ZodArray<z.ZodString>>;
5981
+ }, z.core.$strict>>;
5976
5982
  defaultPermission: z.ZodOptional<z.ZodEnum<{
5977
5983
  never: "never";
5978
5984
  allow: "allow";
@@ -5999,6 +6005,209 @@ declare const ResolvedPluginContractV1: z.ZodObject<{
5999
6005
  globalId: z.ZodString;
6000
6006
  toolName: z.ZodString;
6001
6007
  }, z.core.$strict>>>;
6008
+ kayAppArtifacts: z.ZodOptional<z.ZodObject<{
6009
+ outputs: z.ZodLiteral<true>;
6010
+ }, z.core.$strict>>;
6011
+ kayAppUriModules: z.ZodOptional<z.ZodArray<z.ZodObject<{
6012
+ revisionBacked: z.ZodOptional<z.ZodLiteral<true>>;
6013
+ mailCalibrationSource: z.ZodOptional<z.ZodLiteral<true>>;
6014
+ screenActions: z.ZodExactOptional<z.ZodObject<{
6015
+ scheme: z.ZodString;
6016
+ hosts: z.ZodArray<z.ZodString>;
6017
+ paths: z.ZodOptional<z.ZodArray<z.ZodString>>;
6018
+ apps: z.ZodArray<z.ZodString>;
6019
+ options: z.ZodRecord<z.ZodString, z.ZodDiscriminatedUnion<[z.ZodObject<{
6020
+ kind: z.ZodLiteral<"exact">;
6021
+ description: z.ZodString;
6022
+ verb: z.ZodEnum<{
6023
+ read: "read";
6024
+ write: "write";
6025
+ edit: "edit";
6026
+ delete: "delete";
6027
+ }>;
6028
+ uri: z.ZodString;
6029
+ }, z.core.$strict>, z.ZodObject<{
6030
+ kind: z.ZodLiteral<"catch-all">;
6031
+ description: z.ZodString;
6032
+ verb: z.ZodEnum<{
6033
+ read: "read";
6034
+ write: "write";
6035
+ edit: "edit";
6036
+ delete: "delete";
6037
+ }>;
6038
+ }, z.core.$strict>], "kind">>;
6039
+ }, z.core.$strict>>;
6040
+ claims: z.ZodExactOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
6041
+ kind: z.ZodLiteral<"scheme">;
6042
+ scheme: z.ZodString;
6043
+ }, z.core.$strict>, z.ZodObject<{
6044
+ kind: z.ZodLiteral<"uriPattern">;
6045
+ pattern: z.ZodObject<{
6046
+ protocol: z.ZodExactOptional<z.ZodString>;
6047
+ hostname: z.ZodExactOptional<z.ZodString>;
6048
+ pathname: z.ZodExactOptional<z.ZodString>;
6049
+ search: z.ZodExactOptional<z.ZodString>;
6050
+ }, z.core.$strict>;
6051
+ normalizeTo: z.ZodExactOptional<z.ZodString>;
6052
+ route: z.ZodExactOptional<z.ZodString>;
6053
+ sourceUrl: z.ZodExactOptional<z.ZodString>;
6054
+ title: z.ZodExactOptional<z.ZodString>;
6055
+ examples: z.ZodExactOptional<z.ZodArray<z.ZodString>>;
6056
+ }, z.core.$strict>, z.ZodObject<{
6057
+ kind: z.ZodLiteral<"urlPattern">;
6058
+ pattern: z.ZodObject<{
6059
+ protocol: z.ZodExactOptional<z.ZodString>;
6060
+ hostname: z.ZodExactOptional<z.ZodString>;
6061
+ pathname: z.ZodExactOptional<z.ZodString>;
6062
+ search: z.ZodExactOptional<z.ZodString>;
6063
+ }, z.core.$strict>;
6064
+ normalizeTo: z.ZodExactOptional<z.ZodString>;
6065
+ route: z.ZodExactOptional<z.ZodString>;
6066
+ examples: z.ZodExactOptional<z.ZodArray<z.ZodString>>;
6067
+ }, z.core.$strict>], "kind">>>;
6068
+ templates: z.ZodExactOptional<z.ZodArray<z.ZodObject<{
6069
+ uri: z.ZodString;
6070
+ description: z.ZodString;
6071
+ operations: z.ZodExactOptional<z.ZodArray<z.ZodEnum<{
6072
+ search: "search";
6073
+ read: "read";
6074
+ list: "list";
6075
+ write: "write";
6076
+ edit: "edit";
6077
+ delete: "delete";
6078
+ help: "help";
6079
+ }>>>;
6080
+ operationDocs: z.ZodExactOptional<z.ZodRecord<z.ZodEnum<{
6081
+ search: "search";
6082
+ read: "read";
6083
+ list: "list";
6084
+ write: "write";
6085
+ edit: "edit";
6086
+ delete: "delete";
6087
+ help: "help";
6088
+ }> & z.core.$partial, z.ZodString>>;
6089
+ operationDefaults: z.ZodExactOptional<z.ZodRecord<z.ZodEnum<{
6090
+ search: "search";
6091
+ read: "read";
6092
+ list: "list";
6093
+ write: "write";
6094
+ edit: "edit";
6095
+ delete: "delete";
6096
+ help: "help";
6097
+ }> & z.core.$partial, z.ZodEnum<{
6098
+ never: "never";
6099
+ allow: "allow";
6100
+ ask: "ask";
6101
+ }>>>;
6102
+ }, z.core.$strict>>>;
6103
+ verbs: z.ZodExactOptional<z.ZodObject<{
6104
+ readable: z.ZodBoolean;
6105
+ writable: z.ZodBoolean;
6106
+ editable: z.ZodBoolean;
6107
+ deletable: z.ZodExactOptional<z.ZodBoolean>;
6108
+ listable: z.ZodBoolean;
6109
+ searchable: z.ZodExactOptional<z.ZodBoolean>;
6110
+ }, z.core.$strict>>;
6111
+ verbDocs: z.ZodExactOptional<z.ZodObject<{
6112
+ read: z.ZodExactOptional<z.ZodString>;
6113
+ list: z.ZodExactOptional<z.ZodString>;
6114
+ search: z.ZodExactOptional<z.ZodString>;
6115
+ write: z.ZodExactOptional<z.ZodString>;
6116
+ edit: z.ZodExactOptional<z.ZodString>;
6117
+ delete: z.ZodExactOptional<z.ZodString>;
6118
+ }, z.core.$strict>>;
6119
+ permissionDefaults: z.ZodExactOptional<z.ZodObject<{
6120
+ read: z.ZodExactOptional<z.ZodEnum<{
6121
+ never: "never";
6122
+ allow: "allow";
6123
+ ask: "ask";
6124
+ }>>;
6125
+ write: z.ZodExactOptional<z.ZodEnum<{
6126
+ never: "never";
6127
+ allow: "allow";
6128
+ ask: "ask";
6129
+ }>>;
6130
+ edit: z.ZodExactOptional<z.ZodEnum<{
6131
+ never: "never";
6132
+ allow: "allow";
6133
+ ask: "ask";
6134
+ }>>;
6135
+ delete: z.ZodExactOptional<z.ZodEnum<{
6136
+ never: "never";
6137
+ allow: "allow";
6138
+ ask: "ask";
6139
+ }>>;
6140
+ }, z.core.$strict>>;
6141
+ authActions: z.ZodExactOptional<z.ZodObject<{
6142
+ actions: z.ZodRecord<z.ZodString, z.ZodObject<{
6143
+ purpose: z.ZodString;
6144
+ alternatives: z.ZodArray<z.ZodObject<{
6145
+ allOf: z.ZodArray<z.ZodObject<{
6146
+ scope: z.ZodString;
6147
+ reason: z.ZodExactOptional<z.ZodString>;
6148
+ }, z.core.$strict>>;
6149
+ }, z.core.$strict>>;
6150
+ }, z.core.$strict>>;
6151
+ rules: z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
6152
+ action: z.ZodString;
6153
+ operations: z.ZodExactOptional<z.ZodArray<z.ZodEnum<{
6154
+ search: "search";
6155
+ read: "read";
6156
+ list: "list";
6157
+ write: "write";
6158
+ edit: "edit";
6159
+ delete: "delete";
6160
+ help: "help";
6161
+ }>>>;
6162
+ routes: z.ZodExactOptional<z.ZodArray<z.ZodString>>;
6163
+ query: z.ZodExactOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
6164
+ }, z.core.$strict>, z.ZodObject<{
6165
+ public: z.ZodLiteral<true>;
6166
+ operations: z.ZodExactOptional<z.ZodArray<z.ZodEnum<{
6167
+ search: "search";
6168
+ read: "read";
6169
+ list: "list";
6170
+ write: "write";
6171
+ edit: "edit";
6172
+ delete: "delete";
6173
+ help: "help";
6174
+ }>>>;
6175
+ routes: z.ZodExactOptional<z.ZodArray<z.ZodString>>;
6176
+ query: z.ZodExactOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
6177
+ }, z.core.$strict>]>>;
6178
+ }, z.core.$strict>>;
6179
+ requiresConnection: z.ZodExactOptional<z.ZodLiteral<true>>;
6180
+ acceptsSourceCopy: z.ZodExactOptional<z.ZodLiteral<false>>;
6181
+ acceptsWriteSources: z.ZodExactOptional<z.ZodLiteral<true>>;
6182
+ acceptsBytes: z.ZodExactOptional<z.ZodLiteral<true>>;
6183
+ ownsLineSelectors: z.ZodExactOptional<z.ZodLiteral<true>>;
6184
+ description: z.ZodExactOptional<z.ZodString>;
6185
+ agentHint: z.ZodExactOptional<z.ZodObject<{
6186
+ summary: z.ZodString;
6187
+ commonFlows: z.ZodExactOptional<z.ZodArray<z.ZodObject<{
6188
+ label: z.ZodString;
6189
+ when: z.ZodExactOptional<z.ZodString>;
6190
+ example: z.ZodString;
6191
+ }, z.core.$strict>>>;
6192
+ helpUri: z.ZodExactOptional<z.ZodString>;
6193
+ triggers: z.ZodExactOptional<z.ZodObject<{
6194
+ keywords: z.ZodExactOptional<z.ZodArray<z.ZodString>>;
6195
+ patterns: z.ZodExactOptional<z.ZodArray<z.ZodString>>;
6196
+ }, z.core.$strict>>;
6197
+ skills: z.ZodExactOptional<z.ZodArray<z.ZodString>>;
6198
+ }, z.core.$strict>>;
6199
+ localId: z.ZodString;
6200
+ adapterId: z.ZodOptional<z.ZodString>;
6201
+ globalId: z.ZodString;
6202
+ schemes: z.ZodArray<z.ZodString>;
6203
+ displayName: z.ZodOptional<z.ZodString>;
6204
+ authServiceId: z.ZodOptional<z.ZodString>;
6205
+ authSchemeId: z.ZodOptional<z.ZodString>;
6206
+ requiredScopes: z.ZodOptional<z.ZodArray<z.ZodString>>;
6207
+ knownResources: z.ZodOptional<z.ZodLiteral<true>>;
6208
+ describesOperations: z.ZodOptional<z.ZodLiteral<true>>;
6209
+ looksUpWebLinks: z.ZodOptional<z.ZodLiteral<true>>;
6210
+ }, z.core.$strict>>>;
6002
6211
  kayAppTools: z.ZodOptional<z.ZodArray<z.ZodObject<{
6003
6212
  description: z.ZodOptional<z.ZodString>;
6004
6213
  toolName: z.ZodString;
@@ -6023,6 +6232,9 @@ declare const ResolvedPluginContractV1: z.ZodObject<{
6023
6232
  resourcePrefix: z.ZodOptional<z.ZodString>;
6024
6233
  resourceArgs: z.ZodOptional<z.ZodArray<z.ZodString>>;
6025
6234
  resourceTrim: z.ZodOptional<z.ZodLiteral<true>>;
6235
+ resourcePathNormalization: z.ZodOptional<z.ZodObject<{
6236
+ preserveUriSchemes: z.ZodOptional<z.ZodArray<z.ZodString>>;
6237
+ }, z.core.$strict>>;
6026
6238
  defaultPermission: z.ZodOptional<z.ZodEnum<{
6027
6239
  never: "never";
6028
6240
  allow: "allow";
@@ -6046,6 +6258,9 @@ declare const ResolvedPluginContractV1: z.ZodObject<{
6046
6258
  }, z.core.$strict>>>;
6047
6259
  }, z.core.$strict>>;
6048
6260
  globalId: z.ZodString;
6261
+ availability: z.ZodOptional<z.ZodObject<{
6262
+ scope: z.ZodLiteral<"app">;
6263
+ }, z.core.$strict>>;
6049
6264
  }, z.core.$strict>>>;
6050
6265
  kayAppApprovalTypes: z.ZodOptional<z.ZodArray<z.ZodObject<{
6051
6266
  kind: z.ZodLiteral<"message">;
@@ -6406,6 +6621,7 @@ declare const ResolvedPluginContractV1: z.ZodObject<{
6406
6621
  "connect-flows.read": "connect-flows.read";
6407
6622
  "connect-flows.dismiss": "connect-flows.dismiss";
6408
6623
  "support.report": "support.report";
6624
+ "report-evidence.read": "report-evidence.read";
6409
6625
  "navigation.open-page": "navigation.open-page";
6410
6626
  "sessions.create": "sessions.create";
6411
6627
  "models.complete": "models.complete";
@@ -9758,6 +9974,11 @@ declare interface ToolExecutionPolicyDefaults {
9758
9974
  */
9759
9975
  declare type ToolParametersWire = unknown;
9760
9976
 
9977
+ /** Explicit host environment used to expand manifest permission paths. */
9978
+ declare interface ToolPermissionEnvironment {
9979
+ readonly homeDirectory: string;
9980
+ }
9981
+
9761
9982
  /**
9762
9983
  * A tool's own declaration of how a call to it should classify, supplied on the tool
9763
9984
  * definition (the same shape of author-declared, kernel-read fact as `requiresAuth` and
@@ -9781,9 +10002,10 @@ declare interface ToolPermissionHint {
9781
10002
  * labelled facts. Render-only: never matched against a rule, never one-tap approval grounds.
9782
10003
  * The tool says what its input claims, so the gate shows it as the tool's claim, not as the
9783
10004
  * daemon's attestation. The host admits only display-safe text; a throw, a blank title or an
9784
- * unsafe string costs the description and the call is asked about as before.
10005
+ * unsafe string costs the description and the call is asked about as before. A promise is
10006
+ * bounded by the host description timeout; forward its optional cancellation signal to reads.
9785
10007
  */
9786
- readonly describePreview?: (input: Record<string, unknown>) => ToolPreviewDescription | undefined;
10008
+ readonly describePreview?: (input: Record<string, unknown>, signal?: AbortSignal) => ToolPreviewDescription | undefined | Promise<ToolPreviewDescription | undefined>;
9787
10009
  readonly kind?: ToolActionKind;
9788
10010
  readonly scheme?: string;
9789
10011
  readonly ruleKey?: string;
@@ -12044,7 +12266,7 @@ declare interface Usage {
12044
12266
  /**
12045
12267
  * ---
12046
12268
  * responsibility: The one interface through which a vendored-MCP plugin's code calls a tool on its
12047
- * own vendored MCP server, so every read-through plugin and the daemon that implements the port
12269
+ * own vendored MCP server, so every read-through plugin and the SDK that implements the port
12048
12270
  * agree on the same shape by construction.
12049
12271
  * owns:
12050
12272
  * - {@link VendoredMcpPort} - what a plugin's URI module and tools call.
@@ -12052,8 +12274,8 @@ declare interface Usage {
12052
12274
  * must surface to the agent rather than swallow.
12053
12275
  * does_not_own:
12054
12276
  * - The implementation. A plugin never dials MCP itself: it takes the port from
12055
- * `api.vendoredMcp.port(server)` (`@bonsai/plugin-sdk/kay-agent`), which the daemon binds to the
12056
- * plugin's own hosted server over its MCP host. Plugin source stays host-free, and a plugin
12277
+ * `api.vendoredMcp.port(server)` (`@bonsai/plugin-sdk/kay-agent`), which the SDK binds through
12278
+ * resource calls to the plugin's own hosted server. Plugin source stays host-free, and a plugin
12057
12279
  * under test substitutes a fake port.
12058
12280
  * - Which tool names exist. `TTool` is the plugin's own union of the vendor tools it calls; the
12059
12281
  * port is generic so a typo in a tool name fails typecheck inside the plugin.
@@ -12089,8 +12311,8 @@ declare type VendoredMcpOutcome =
12089
12311
 
12090
12312
  /**
12091
12313
  * One vendored MCP server, as the plugin that declares it calls it. `invoke` calls any tool the
12092
- * server has, whether it reads or writes. The port checks no permission of its own: a call is as
12093
- * permitted as the plugin tool or URI verb that makes it.
12314
+ * server has, whether it reads or writes. The resource caller checks the target's exact
12315
+ * `uri.call` grant, and MCP checks the host-stamped caller against server ownership.
12094
12316
  */
12095
12317
  declare interface VendoredMcpPort<TTool extends string = string> {
12096
12318
  invoke(input: {
@@ -123,8 +123,12 @@ function createKayPluginLog(write, bound = {}) {
123
123
  }
124
124
 
125
125
  // packages/plugin-sdk/dist/kay-agent/declared-tool.js
126
- function declaredToolPermission(permission) {
127
- const { kind, ruleKey, resourceArgs, scheme, resourcePrefix, resourceTrim, defaultPermission, classificationRules } = permission;
126
+ import { isAbsolute, join, normalize, resolve } from "node:path";
127
+ function declaredToolPermission(permission, environment) {
128
+ const { kind, ruleKey, resourceArgs, scheme, resourcePrefix, resourceTrim, resourcePathNormalization, defaultPermission, classificationRules } = permission;
129
+ if (resourcePathNormalization !== void 0 && environment === void 0) {
130
+ throw new Error("Path-normalized tool resources require a home directory.");
131
+ }
128
132
  return {
129
133
  kind,
130
134
  ...scheme === void 0 ? {} : { scheme },
@@ -145,14 +149,22 @@ function declaredToolPermission(permission) {
145
149
  ...resourceArgs === void 0 ? {} : {
146
150
  deriveResources: (input) => resourceArgs.flatMap((arg) => {
147
151
  const value = input[arg];
148
- return typeof value === "string" ? [
149
- `${resourcePrefix ?? ""}${resourceTrim === true ? value.trim() : value}`
150
- ] : [];
152
+ if (typeof value !== "string")
153
+ return [];
154
+ let resource = resourceTrim === true ? value.trim() : value;
155
+ if (resourcePathNormalization !== void 0 && environment !== void 0) {
156
+ const preserved = resourcePathNormalization.preserveUriSchemes?.some((preservedScheme) => resource.toLowerCase().startsWith(`${preservedScheme.toLowerCase()}://`)) ?? false;
157
+ if (!preserved) {
158
+ const expanded = resource === "~" ? environment.homeDirectory : resource.startsWith("~/") ? join(environment.homeDirectory, resource.slice(2)) : resource;
159
+ resource = isAbsolute(expanded) ? resolve(expanded) : normalize(expanded);
160
+ }
161
+ }
162
+ return [`${resourcePrefix ?? ""}${resource}`];
151
163
  })
152
164
  }
153
165
  };
154
166
  }
155
- function declaredToolDefinition(tool, execute) {
167
+ function declaredToolDefinition(tool, execute, environment) {
156
168
  const { parameters, description, requiresAuth, permission } = tool;
157
169
  if (parameters === void 0 || description === void 0) {
158
170
  throw new Error(`Tool '${tool.toolName}' declares no parameters in kay-plugin.json, so its definition comes from code.`);
@@ -171,7 +183,7 @@ function declaredToolDefinition(tool, execute) {
171
183
  ...requiresAuth.schemeId === void 0 ? {} : { schemeId: requiresAuth.schemeId }
172
184
  }
173
185
  },
174
- ...permission === void 0 ? {} : { permission: declaredToolPermission(permission) },
186
+ ...permission === void 0 ? {} : { permission: declaredToolPermission(permission, environment) },
175
187
  execute
176
188
  };
177
189
  }
@@ -405,12 +417,12 @@ function defaultTestToolContext() {
405
417
  async function invokeToolForTest(tool, params, ctx = defaultTestToolContext()) {
406
418
  return tool.execute("test-call", params, void 0, void 0, ctx);
407
419
  }
408
- function describedToolForTest(contract, localId, execute) {
420
+ function describedToolForTest(contract, localId, execute, environment) {
409
421
  const tool = contract.kayAgentTools?.find((declared) => declared.localId === localId);
410
422
  if (tool === void 0) {
411
423
  throw new Error(`The contract declares no kay.agent tool '${localId}'.`);
412
424
  }
413
- return declaredToolDefinition(tool, execute);
425
+ return declaredToolDefinition(tool, execute, environment);
414
426
  }
415
427
  function producedResourcesForTest(result) {
416
428
  const recorded = recordProducedResources(result.details).details;
@@ -3081,8 +3081,15 @@ declare const KAY_PLUGIN_PROTOCOL_VERSION: "kay.plugin.v1";
3081
3081
  * optional `attachments`: each file's name, media type, size and the address its bytes are read
3082
3082
  * from, never the bytes. Gmail lists a mail's attachments there, so Flows can file them. Additive:
3083
3083
  * a plugin that publishes attachments declares ^0.54.2.
3084
+ *
3085
+ * 0.55.0 carries managed MCP calls over resource writes in both daemon and plugin-host code.
3086
+ * Existing agent and background MCP call shapes remain supported.
3087
+ *
3088
+ * 0.55.1 adds canonical path resources on declared tool permissions, asynchronous display-only
3089
+ * tool previews with cancellation, app-scoped availability snapshots and app-target output
3090
+ * artifact usage declarations. A plugin using these declares ^0.55.1.
3084
3091
  */
3085
- declare const KAY_PLUGIN_SDK_VERSION: "0.54.2";
3092
+ declare const KAY_PLUGIN_SDK_VERSION: "0.55.1";
3086
3093
 
3087
3094
  declare const KAY_PLUGIN_TARGET_NAMES: readonly ["kay.agent", "kay.app", "kay.ui"];
3088
3095
 
@@ -3580,13 +3587,8 @@ export declare interface KayAgentVendoredMcp {
3580
3587
  * The port to `server`, a name this plugin declares in its `kay.app` target's
3581
3588
  * `contributes.mcpServers`. Throws when the plugin declares no server of that name. A declared
3582
3589
  * server that Bonsai does not host for this plugin still returns a port; each call then answers
3583
- * `unknown-server`. Its message names the plugin that holds the name, or else lists the possible
3584
- * reasons (no approved `mcp.host-server` grant, a sign-in the server cannot use, or a name that
3585
- * a plugin Bonsai ships or another installed plugin also claims) and points at the plugin's load
3586
- * diagnostic.
3587
- *
3588
- * A port reaches this plugin's own server only. To use another plugin's data, declare that
3589
- * plugin in the manifest's `dependencies.plugins`; a later SDK surface reaches it.
3590
+ * `unknown-server`. Each invocation uses the resource caller's exact `kay.agent` `uri.call`
3591
+ * grant for `mcp` / `write`; MCP checks the host-stamped caller against server ownership.
3590
3592
  */
3591
3593
  port<TTool extends string = string>(server: string): VendoredMcpPort<TTool>;
3592
3594
  }
@@ -9807,9 +9809,10 @@ declare interface ToolPermissionHint {
9807
9809
  * labelled facts. Render-only: never matched against a rule, never one-tap approval grounds.
9808
9810
  * The tool says what its input claims, so the gate shows it as the tool's claim, not as the
9809
9811
  * daemon's attestation. The host admits only display-safe text; a throw, a blank title or an
9810
- * unsafe string costs the description and the call is asked about as before.
9812
+ * unsafe string costs the description and the call is asked about as before. A promise is
9813
+ * bounded by the host description timeout; forward its optional cancellation signal to reads.
9811
9814
  */
9812
- readonly describePreview?: (input: Record<string, unknown>) => ToolPreviewDescription | undefined;
9815
+ readonly describePreview?: (input: Record<string, unknown>, signal?: AbortSignal) => ToolPreviewDescription | undefined | Promise<ToolPreviewDescription | undefined>;
9813
9816
  readonly kind?: ToolActionKind;
9814
9817
  readonly scheme?: string;
9815
9818
  readonly ruleKey?: string;
@@ -11852,6 +11855,7 @@ export declare class UriProviderError extends UriError {
11852
11855
  readonly uri: string;
11853
11856
  readonly status?: number;
11854
11857
  readonly providerCode?: string;
11858
+ readonly serviceId?: string;
11855
11859
  readonly retryable?: boolean;
11856
11860
  /** Provider-directed delay before retrying, in milliseconds. */
11857
11861
  readonly retryAfterMs?: number;
@@ -11869,6 +11873,8 @@ export declare interface UriProviderErrorOptions {
11869
11873
  readonly message: string;
11870
11874
  readonly status?: number;
11871
11875
  readonly providerCode?: string;
11876
+ /** Service to reconnect when the provider requires sign-in. */
11877
+ readonly serviceId?: string;
11872
11878
  readonly retryable?: boolean;
11873
11879
  /** Provider-directed delay before retrying, in milliseconds. */
11874
11880
  readonly retryAfterMs?: number;
@@ -12340,7 +12346,7 @@ declare interface Usage {
12340
12346
  /**
12341
12347
  * ---
12342
12348
  * responsibility: The one interface through which a vendored-MCP plugin's code calls a tool on its
12343
- * own vendored MCP server, so every read-through plugin and the daemon that implements the port
12349
+ * own vendored MCP server, so every read-through plugin and the SDK that implements the port
12344
12350
  * agree on the same shape by construction.
12345
12351
  * owns:
12346
12352
  * - {@link VendoredMcpPort} - what a plugin's URI module and tools call.
@@ -12348,8 +12354,8 @@ declare interface Usage {
12348
12354
  * must surface to the agent rather than swallow.
12349
12355
  * does_not_own:
12350
12356
  * - The implementation. A plugin never dials MCP itself: it takes the port from
12351
- * `api.vendoredMcp.port(server)` (`@bonsai/plugin-sdk/kay-agent`), which the daemon binds to the
12352
- * plugin's own hosted server over its MCP host. Plugin source stays host-free, and a plugin
12357
+ * `api.vendoredMcp.port(server)` (`@bonsai/plugin-sdk/kay-agent`), which the SDK binds through
12358
+ * resource calls to the plugin's own hosted server. Plugin source stays host-free, and a plugin
12353
12359
  * under test substitutes a fake port.
12354
12360
  * - Which tool names exist. `TTool` is the plugin's own union of the vendor tools it calls; the
12355
12361
  * port is generic so a typo in a tool name fails typecheck inside the plugin.
@@ -12385,8 +12391,8 @@ declare type VendoredMcpOutcome =
12385
12391
 
12386
12392
  /**
12387
12393
  * One vendored MCP server, as the plugin that declares it calls it. `invoke` calls any tool the
12388
- * server has, whether it reads or writes. The port checks no permission of its own: a call is as
12389
- * permitted as the plugin tool or URI verb that makes it.
12394
+ * server has, whether it reads or writes. The resource caller checks the target's exact
12395
+ * `uri.call` grant, and MCP checks the host-stamped caller against server ownership.
12390
12396
  */
12391
12397
  declare interface VendoredMcpPort<TTool extends string = string> {
12392
12398
  invoke(input: {
package/dist/kay-agent.js CHANGED
@@ -422,6 +422,7 @@ var UriProviderError = class extends UriError {
422
422
  uri;
423
423
  status;
424
424
  providerCode;
425
+ serviceId;
425
426
  retryable;
426
427
  /** Provider-directed delay before retrying, in milliseconds. */
427
428
  retryAfterMs;
@@ -439,6 +440,8 @@ var UriProviderError = class extends UriError {
439
440
  this.status = options.status;
440
441
  if (options.providerCode !== void 0)
441
442
  this.providerCode = options.providerCode;
443
+ if (options.serviceId !== void 0)
444
+ this.serviceId = options.serviceId;
442
445
  if (options.retryable !== void 0)
443
446
  this.retryable = options.retryable;
444
447
  if (options.retryAfterMs !== void 0) {
package/dist/kay-app.d.ts CHANGED
@@ -153,6 +153,80 @@ declare const AgentRuntimeAutomationFactSource: z.ZodEnum<{
153
153
 
154
154
  declare type AgentRuntimeAutomationFactSource = z.infer<typeof AgentRuntimeAutomationFactSource>;
155
155
 
156
+ /**
157
+ * The failure record the automation lane mints for a failure it observed itself (domain
158
+ * `automation`). A producer passes `AgentRuntimeAutomationFailureCode`, so an undeclared code
159
+ * fails to compile; the reader takes any `automation.` code, so a stored run still reads after
160
+ * a code is renamed.
161
+ */
162
+ declare type AgentRuntimeAutomationFailure<Code extends string = string> = FailureOccurrence<AgentRuntimeAutomationFailureCause, Code, "automation">;
163
+
164
+ declare const AgentRuntimeAutomationFailure: z.ZodType<AgentRuntimeAutomationFailure>;
165
+
166
+ /**
167
+ * The cause on a failure the automation lane observes itself: the closed facts the daemon saw,
168
+ * keyed by the same `kind` as its `AutomationFailureCause`, plus the engine's own
169
+ * ({@link AgentRuntimeAutomationEngineFailureCause}). No message, stderr, URI or path: these
170
+ * records are exported with the run's log rows. A script's stderr tail stays on the outcome's
171
+ * `message`.
172
+ */
173
+ declare const AgentRuntimeAutomationFailureCause: z.ZodDiscriminatedUnion<[z.ZodObject<{
174
+ kind: z.ZodLiteral<"effect_threw">;
175
+ }, z.core.$strict>, z.ZodObject<{
176
+ kind: z.ZodLiteral<"effect_unsupported">;
177
+ }, z.core.$strict>, z.ZodObject<{
178
+ kind: z.ZodLiteral<"switched_off_across_restart">;
179
+ }, z.core.$strict>, z.ZodObject<{
180
+ kind: z.ZodLiteral<"probe_failed">;
181
+ consecutiveFailures: z.ZodNumber;
182
+ retired: z.ZodBoolean;
183
+ }, z.core.$strict>, z.ZodObject<{
184
+ kind: z.ZodLiteral<"run_start_failed">;
185
+ }, z.core.$strict>, z.ZodObject<{
186
+ kind: z.ZodLiteral<"plugin_revision_moving">;
187
+ }, z.core.$strict>, z.ZodObject<{
188
+ kind: z.ZodLiteral<"owner_unsupported">;
189
+ }, z.core.$strict>, z.ZodObject<{
190
+ kind: z.ZodLiteral<"turn_target_missing">;
191
+ }, z.core.$strict>, z.ZodObject<{
192
+ kind: z.ZodLiteral<"target_missing">;
193
+ }, z.core.$strict>, z.ZodObject<{
194
+ kind: z.ZodLiteral<"declaration_gone">;
195
+ }, z.core.$strict>, z.ZodObject<{
196
+ kind: z.ZodLiteral<"turn_delivery_failed">;
197
+ }, z.core.$strict>, z.ZodObject<{
198
+ kind: z.ZodLiteral<"handler_failed">;
199
+ }, z.core.$strict>, z.ZodObject<{
200
+ kind: z.ZodLiteral<"network_unreachable">;
201
+ }, z.core.$strict>, z.ZodObject<{
202
+ kind: z.ZodLiteral<"script_timed_out">;
203
+ }, z.core.$strict>, z.ZodObject<{
204
+ kind: z.ZodLiteral<"script_cwd_unset">;
205
+ }, z.core.$strict>, z.ZodObject<{
206
+ kind: z.ZodLiteral<"script_result_invalid">;
207
+ }, z.core.$strict>, z.ZodObject<{
208
+ kind: z.ZodLiteral<"script_failed">;
209
+ }, z.core.$strict>, z.ZodObject<{
210
+ kind: z.ZodLiteral<"turn_failed">;
211
+ }, z.core.$strict>, z.ZodObject<{
212
+ kind: z.ZodLiteral<"workflow_failed">;
213
+ }, z.core.$strict>, z.ZodObject<{
214
+ kind: z.ZodLiteral<"surface_unavailable">;
215
+ why: z.ZodEnum<{
216
+ starting: "starting";
217
+ script_unreadable: "script_unreadable";
218
+ }>;
219
+ }, z.core.$strict>, z.ZodObject<{
220
+ kind: z.ZodLiteral<"script_exited">;
221
+ signal: z.ZodNullable<z.ZodString>;
222
+ }, z.core.$strict>, z.ZodObject<{
223
+ kind: z.ZodLiteral<"script_process_error">;
224
+ errorCode: z.ZodOptional<z.ZodString>;
225
+ cwdExists: z.ZodBoolean;
226
+ }, z.core.$strict>], "kind">;
227
+
228
+ declare type AgentRuntimeAutomationFailureCause = z.infer<typeof AgentRuntimeAutomationFailureCause>;
229
+
156
230
  /** One automation run's provenance detail (RFC Release 2). */
157
231
  declare const AgentRuntimeAutomationRunDetail: z.ZodObject<{
158
232
  run: z.ZodObject<{
@@ -324,7 +398,7 @@ declare const AgentRuntimeAutomationRunDetail: z.ZodObject<{
324
398
  retryable: z.ZodBoolean;
325
399
  retryAfterMs: z.ZodOptional<z.ZodNumber>;
326
400
  }, z.core.$strict>>;
327
- failure: z.ZodOptional<z.ZodType<RunFailureOccurrence<string, string>, unknown, z.core.$ZodTypeInternals<RunFailureOccurrence<string, string>, unknown>>>;
401
+ failure: z.ZodOptional<z.ZodUnion<readonly [z.ZodType<RunFailureOccurrence<string, string>, unknown, z.core.$ZodTypeInternals<RunFailureOccurrence<string, string>, unknown>>, z.ZodType<AgentRuntimeAutomationFailure<string>, unknown, z.core.$ZodTypeInternals<AgentRuntimeAutomationFailure<string>, unknown>>]>>;
328
402
  sessionId: z.ZodOptional<z.ZodString>;
329
403
  retryAtMs: z.ZodOptional<z.ZodNumber>;
330
404
  tries: z.ZodOptional<z.ZodNumber>;
@@ -2511,7 +2585,7 @@ declare interface Decision<Q extends DecideQuestions = DecideQuestions> {
2511
2585
  }
2512
2586
 
2513
2587
  /** Builds an app tool from manifest facts, retaining its execution, availability and preview callbacks. */
2514
- export declare function declaredAppToolDefinition(tool: ToolContribution, implementation: KayAppToolImplementation): KayAppToolDefinition;
2588
+ export declare function declaredAppToolDefinition(tool: ToolContribution, implementation: KayAppToolImplementation, environment?: ToolPermissionEnvironment): KayAppToolDefinition;
2515
2589
 
2516
2590
  /** Createsa uni-directional Codec with Decode only. The Decode function is called on Value.Decode */
2517
2591
  declare function Decode<Type extends TSchema, Callback extends TDecodeCallback<StaticDecode<Type>, unknown>>(type: Type, callback: Callback): TCodec<Type, ReturnType<Callback>>;
@@ -4309,8 +4383,15 @@ declare const KAY_PLUGIN_PROTOCOL_VERSION: "kay.plugin.v1";
4309
4383
  * optional `attachments`: each file's name, media type, size and the address its bytes are read
4310
4384
  * from, never the bytes. Gmail lists a mail's attachments there, so Flows can file them. Additive:
4311
4385
  * a plugin that publishes attachments declares ^0.54.2.
4386
+ *
4387
+ * 0.55.0 carries managed MCP calls over resource writes in both daemon and plugin-host code.
4388
+ * Existing agent and background MCP call shapes remain supported.
4389
+ *
4390
+ * 0.55.1 adds canonical path resources on declared tool permissions, asynchronous display-only
4391
+ * tool previews with cancellation, app-scoped availability snapshots and app-target output
4392
+ * artifact usage declarations. A plugin using these declares ^0.55.1.
4312
4393
  */
4313
- declare const KAY_PLUGIN_SDK_VERSION: "0.54.2";
4394
+ declare const KAY_PLUGIN_SDK_VERSION: "0.55.1";
4314
4395
 
4315
4396
  declare const KAY_PLUGIN_TARGET_NAMES: readonly ["kay.agent", "kay.app", "kay.ui"];
4316
4397
 
@@ -15925,6 +16006,9 @@ export declare const ToolContribution: z.ZodObject<{
15925
16006
  resourcePrefix: z.ZodOptional<z.ZodString>;
15926
16007
  resourceArgs: z.ZodOptional<z.ZodArray<z.ZodString>>;
15927
16008
  resourceTrim: z.ZodOptional<z.ZodLiteral<true>>;
16009
+ resourcePathNormalization: z.ZodOptional<z.ZodObject<{
16010
+ preserveUriSchemes: z.ZodOptional<z.ZodArray<z.ZodString>>;
16011
+ }, z.core.$strict>>;
15928
16012
  defaultPermission: z.ZodOptional<z.ZodEnum<{
15929
16013
  never: "never";
15930
16014
  allow: "allow";
@@ -15947,6 +16031,9 @@ export declare const ToolContribution: z.ZodObject<{
15947
16031
  ruleKey: z.ZodOptional<z.ZodString>;
15948
16032
  }, z.core.$strict>>>;
15949
16033
  }, z.core.$strict>>;
16034
+ availability: z.ZodOptional<z.ZodObject<{
16035
+ scope: z.ZodLiteral<"app">;
16036
+ }, z.core.$strict>>;
15950
16037
  localId: z.ZodString;
15951
16038
  toolName: z.ZodString;
15952
16039
  displayName: z.ZodOptional<z.ZodString>;
@@ -16073,6 +16160,11 @@ declare interface ToolExecutionPolicyDefaults {
16073
16160
  */
16074
16161
  export declare type ToolParametersWire = unknown;
16075
16162
 
16163
+ /** Explicit host environment used to expand manifest permission paths. */
16164
+ export declare interface ToolPermissionEnvironment {
16165
+ readonly homeDirectory: string;
16166
+ }
16167
+
16076
16168
  /**
16077
16169
  * A tool's own declaration of how a call to it should classify, supplied on the tool
16078
16170
  * definition (the same shape of author-declared, kernel-read fact as `requiresAuth` and
@@ -16096,9 +16188,10 @@ export declare interface ToolPermissionHint {
16096
16188
  * labelled facts. Render-only: never matched against a rule, never one-tap approval grounds.
16097
16189
  * The tool says what its input claims, so the gate shows it as the tool's claim, not as the
16098
16190
  * daemon's attestation. The host admits only display-safe text; a throw, a blank title or an
16099
- * unsafe string costs the description and the call is asked about as before.
16191
+ * unsafe string costs the description and the call is asked about as before. A promise is
16192
+ * bounded by the host description timeout; forward its optional cancellation signal to reads.
16100
16193
  */
16101
- readonly describePreview?: (input: Record<string, unknown>) => ToolPreviewDescription | undefined;
16194
+ readonly describePreview?: (input: Record<string, unknown>, signal?: AbortSignal) => ToolPreviewDescription | undefined | Promise<ToolPreviewDescription | undefined>;
16102
16195
  readonly kind?: ToolActionKind;
16103
16196
  readonly scheme?: string;
16104
16197
  readonly ruleKey?: string;
@@ -18270,6 +18363,7 @@ export declare class UriProviderError extends UriError {
18270
18363
  readonly uri: string;
18271
18364
  readonly status?: number;
18272
18365
  readonly providerCode?: string;
18366
+ readonly serviceId?: string;
18273
18367
  readonly retryable?: boolean;
18274
18368
  /** Provider-directed delay before retrying, in milliseconds. */
18275
18369
  readonly retryAfterMs?: number;
@@ -18287,6 +18381,8 @@ export declare interface UriProviderErrorOptions {
18287
18381
  readonly message: string;
18288
18382
  readonly status?: number;
18289
18383
  readonly providerCode?: string;
18384
+ /** Service to reconnect when the provider requires sign-in. */
18385
+ readonly serviceId?: string;
18290
18386
  readonly retryable?: boolean;
18291
18387
  /** Provider-directed delay before retrying, in milliseconds. */
18292
18388
  readonly retryAfterMs?: number;
@@ -18799,7 +18895,7 @@ declare type UserShellCommandMessage = CustomMessage<UserShellCommandDetails> &
18799
18895
  /**
18800
18896
  * ---
18801
18897
  * responsibility: The one interface through which a vendored-MCP plugin's code calls a tool on its
18802
- * own vendored MCP server, so every read-through plugin and the daemon that implements the port
18898
+ * own vendored MCP server, so every read-through plugin and the SDK that implements the port
18803
18899
  * agree on the same shape by construction.
18804
18900
  * owns:
18805
18901
  * - {@link VendoredMcpPort} - what a plugin's URI module and tools call.
@@ -18807,8 +18903,8 @@ declare type UserShellCommandMessage = CustomMessage<UserShellCommandDetails> &
18807
18903
  * must surface to the agent rather than swallow.
18808
18904
  * does_not_own:
18809
18905
  * - The implementation. A plugin never dials MCP itself: it takes the port from
18810
- * `api.vendoredMcp.port(server)` (`@bonsai/plugin-sdk/kay-agent`), which the daemon binds to the
18811
- * plugin's own hosted server over its MCP host. Plugin source stays host-free, and a plugin
18906
+ * `api.vendoredMcp.port(server)` (`@bonsai/plugin-sdk/kay-agent`), which the SDK binds through
18907
+ * resource calls to the plugin's own hosted server. Plugin source stays host-free, and a plugin
18812
18908
  * under test substitutes a fake port.
18813
18909
  * - Which tool names exist. `TTool` is the plugin's own union of the vendor tools it calls; the
18814
18910
  * port is generic so a typo in a tool name fails typecheck inside the plugin.
package/dist/kay-app.js CHANGED
@@ -55,6 +55,7 @@ var UriProviderError = class extends UriError {
55
55
  uri;
56
56
  status;
57
57
  providerCode;
58
+ serviceId;
58
59
  retryable;
59
60
  /** Provider-directed delay before retrying, in milliseconds. */
60
61
  retryAfterMs;
@@ -72,6 +73,8 @@ var UriProviderError = class extends UriError {
72
73
  this.status = options.status;
73
74
  if (options.providerCode !== void 0)
74
75
  this.providerCode = options.providerCode;
76
+ if (options.serviceId !== void 0)
77
+ this.serviceId = options.serviceId;
75
78
  if (options.retryable !== void 0)
76
79
  this.retryable = options.retryable;
77
80
  if (options.retryAfterMs !== void 0) {
@@ -1004,8 +1007,12 @@ function refusedCountOf(error) {
1004
1007
  }
1005
1008
 
1006
1009
  // packages/plugin-sdk/dist/kay-agent/declared-tool.js
1007
- function declaredToolPermission(permission) {
1008
- const { kind, ruleKey, resourceArgs, scheme, resourcePrefix, resourceTrim, defaultPermission, classificationRules } = permission;
1010
+ import { isAbsolute, join, normalize, resolve } from "node:path";
1011
+ function declaredToolPermission(permission, environment) {
1012
+ const { kind, ruleKey, resourceArgs, scheme, resourcePrefix, resourceTrim, resourcePathNormalization, defaultPermission, classificationRules } = permission;
1013
+ if (resourcePathNormalization !== void 0 && environment === void 0) {
1014
+ throw new Error("Path-normalized tool resources require a home directory.");
1015
+ }
1009
1016
  return {
1010
1017
  kind,
1011
1018
  ...scheme === void 0 ? {} : { scheme },
@@ -1026,16 +1033,24 @@ function declaredToolPermission(permission) {
1026
1033
  ...resourceArgs === void 0 ? {} : {
1027
1034
  deriveResources: (input) => resourceArgs.flatMap((arg) => {
1028
1035
  const value = input[arg];
1029
- return typeof value === "string" ? [
1030
- `${resourcePrefix ?? ""}${resourceTrim === true ? value.trim() : value}`
1031
- ] : [];
1036
+ if (typeof value !== "string")
1037
+ return [];
1038
+ let resource = resourceTrim === true ? value.trim() : value;
1039
+ if (resourcePathNormalization !== void 0 && environment !== void 0) {
1040
+ const preserved = resourcePathNormalization.preserveUriSchemes?.some((preservedScheme) => resource.toLowerCase().startsWith(`${preservedScheme.toLowerCase()}://`)) ?? false;
1041
+ if (!preserved) {
1042
+ const expanded = resource === "~" ? environment.homeDirectory : resource.startsWith("~/") ? join(environment.homeDirectory, resource.slice(2)) : resource;
1043
+ resource = isAbsolute(expanded) ? resolve(expanded) : normalize(expanded);
1044
+ }
1045
+ }
1046
+ return [`${resourcePrefix ?? ""}${resource}`];
1032
1047
  })
1033
1048
  }
1034
1049
  };
1035
1050
  }
1036
1051
 
1037
1052
  // packages/plugin-sdk/dist/kay-app/declared-tool.js
1038
- function declaredAppToolDefinition(tool, implementation) {
1053
+ function declaredAppToolDefinition(tool, implementation, environment) {
1039
1054
  if (tool.parameters === void 0 || tool.description === void 0) {
1040
1055
  throw new Error(`Tool '${tool.toolName}' declares no parameters or description.`);
1041
1056
  }
@@ -1050,7 +1065,7 @@ function declaredAppToolDefinition(tool, implementation) {
1050
1065
  ...tool.promptGuidelines === void 0 ? {} : { promptGuidelines: tool.promptGuidelines },
1051
1066
  ...tool.permission === void 0 ? {} : {
1052
1067
  permission: {
1053
- ...declaredToolPermission(tool.permission),
1068
+ ...declaredToolPermission(tool.permission, environment),
1054
1069
  ...preview?.describePreview === void 0 ? {} : { describePreview: preview.describePreview }
1055
1070
  }
1056
1071
  }
@@ -1,3 +1,9 @@
1
+ export declare function createKayAgentVendoredMcp(pluginId: string, servers: readonly string[], uri: PluginUriCaller): KayAgentVendoredMcp;
2
+
3
+ export declare function createKayAppMcp(pluginId: string, servers: readonly string[], uri: PluginUriCaller): KayAppMcp;
4
+
5
+ export declare function createVendoredMcpPort(server: string, uri: PluginUriCaller): VendoredMcpPort;
6
+
1
7
  /**
2
8
  * The URI module for one vendored-MCP integration plugin.
3
9
  *
@@ -44,6 +50,35 @@ declare type JsonValue = JsonPrimitive | {
44
50
  readonly [key: string]: JsonValue;
45
51
  } | readonly JsonValue[];
46
52
 
53
+ /**
54
+ * Calls tools on the plugin's own vendored MCP servers. See {@link KayAgentPluginApi.vendoredMcp}.
55
+ */
56
+ declare interface KayAgentVendoredMcp {
57
+ /**
58
+ * The port to `server`, a name this plugin declares in its `kay.app` target's
59
+ * `contributes.mcpServers`. Throws when the plugin declares no server of that name. A declared
60
+ * server that Bonsai does not host for this plugin still returns a port; each call then answers
61
+ * `unknown-server`. Each invocation uses the resource caller's exact `kay.agent` `uri.call`
62
+ * grant for `mcp` / `write`; MCP checks the host-stamped caller against server ownership.
63
+ */
64
+ port<TTool extends string = string>(server: string): VendoredMcpPort<TTool>;
65
+ }
66
+
67
+ /** Calls tools on this plugin's manifest-declared MCP servers through Bonsai's managed connection. */
68
+ declare interface KayAppMcp {
69
+ /**
70
+ * Uses the current app-wide server set, without a session or working directory.
71
+ * Refuses undeclared or no-longer-hosted servers. Sign-in and tool failures return typed outcomes;
72
+ * credentials remain with the host. Like the agent-side vendored MCP API, calls can read or write
73
+ * and do not use the agent's per-tool approval path.
74
+ */
75
+ callTool(input: {
76
+ readonly server: string;
77
+ readonly tool: string;
78
+ readonly args: Readonly<Record<string, unknown>>;
79
+ }): Promise<VendoredMcpOutcome>;
80
+ }
81
+
47
82
  declare interface LineRange {
48
83
  readonly start: number;
49
84
  readonly end: number;
@@ -127,6 +162,38 @@ declare interface ObservationMaterial {
127
162
  readonly retainForMs?: number;
128
163
  }
129
164
 
165
+ /**
166
+ * Sessionless resource calls. Each call checks the calling target's exact scheme and verb grant.
167
+ * Results retain provider bytes, metadata and revisions; provider failures retain their URI tags.
168
+ * A cancellation or lost response does not prove a mutation was not applied.
169
+ */
170
+ declare interface PluginUriCaller {
171
+ read(input: string, options?: PluginUriReadOptions): Promise<RawResource>;
172
+ list(input: string, params?: UriListParams, options?: PluginUriCallOptions): Promise<UriListResult>;
173
+ search(input: string, params: UriSearchParams, options?: PluginUriCallOptions): Promise<UriSearchResult>;
174
+ write(input: string, content?: UriPayload | Uint8Array, options?: PluginUriWriteOptions): Promise<UriMutationResult | void>;
175
+ edit(input: string, payload: UriPayload, options?: PluginUriMutationOptions): Promise<UriMutationResult>;
176
+ delete(input: string, options?: PluginUriMutationOptions): Promise<UriMutationResult>;
177
+ }
178
+
179
+ declare interface PluginUriCallOptions {
180
+ readonly cwd?: string;
181
+ readonly signal?: AbortSignal;
182
+ }
183
+
184
+ declare interface PluginUriMutationOptions extends PluginUriCallOptions {
185
+ readonly expectedRevision?: string;
186
+ }
187
+
188
+ declare interface PluginUriReadOptions extends PluginUriCallOptions {
189
+ readonly knownRevision?: string;
190
+ }
191
+
192
+ declare interface PluginUriWriteOptions extends PluginUriMutationOptions {
193
+ readonly sources?: readonly UriWriteSource[];
194
+ readonly writeSource?: UriSourceDescriptor;
195
+ }
196
+
130
197
  declare interface RawResource {
131
198
  readonly uri: string;
132
199
  readonly content: string | Uint8Array;
@@ -1246,7 +1313,7 @@ declare type VendoredMcpFactClaim = {
1246
1313
  /**
1247
1314
  * ---
1248
1315
  * responsibility: The one interface through which a vendored-MCP plugin's code calls a tool on its
1249
- * own vendored MCP server, so every read-through plugin and the daemon that implements the port
1316
+ * own vendored MCP server, so every read-through plugin and the SDK that implements the port
1250
1317
  * agree on the same shape by construction.
1251
1318
  * owns:
1252
1319
  * - {@link VendoredMcpPort} - what a plugin's URI module and tools call.
@@ -1254,8 +1321,8 @@ declare type VendoredMcpFactClaim = {
1254
1321
  * must surface to the agent rather than swallow.
1255
1322
  * does_not_own:
1256
1323
  * - The implementation. A plugin never dials MCP itself: it takes the port from
1257
- * `api.vendoredMcp.port(server)` (`@bonsai/plugin-sdk/kay-agent`), which the daemon binds to the
1258
- * plugin's own hosted server over its MCP host. Plugin source stays host-free, and a plugin
1324
+ * `api.vendoredMcp.port(server)` (`@bonsai/plugin-sdk/kay-agent`), which the SDK binds through
1325
+ * resource calls to the plugin's own hosted server. Plugin source stays host-free, and a plugin
1259
1326
  * under test substitutes a fake port.
1260
1327
  * - Which tool names exist. `TTool` is the plugin's own union of the vendor tools it calls; the
1261
1328
  * port is generic so a typo in a tool name fails typecheck inside the plugin.
@@ -1291,8 +1358,8 @@ export declare type VendoredMcpOutcome =
1291
1358
 
1292
1359
  /**
1293
1360
  * One vendored MCP server, as the plugin that declares it calls it. `invoke` calls any tool the
1294
- * server has, whether it reads or writes. The port checks no permission of its own: a call is as
1295
- * permitted as the plugin tool or URI verb that makes it.
1361
+ * server has, whether it reads or writes. The resource caller checks the target's exact
1362
+ * `uri.call` grant, and MCP checks the host-stamped caller against server ownership.
1296
1363
  */
1297
1364
  export declare interface VendoredMcpPort<TTool extends string = string> {
1298
1365
  invoke(input: {
@@ -480,6 +480,30 @@ var UriPayloadContractSchema = z2.strictObject({
480
480
  examples: z2.array(z2.unknown()).optional()
481
481
  });
482
482
 
483
+ // packages/herd-uri/dist/payloads.js
484
+ function isJsonPrimitive(value) {
485
+ return value === null || typeof value === "string" || typeof value === "boolean" || typeof value === "number" && Number.isFinite(value);
486
+ }
487
+ function isJsonValue(value) {
488
+ return isJsonValueInner(value, /* @__PURE__ */ new WeakSet());
489
+ }
490
+ function isJsonValueInner(value, seen) {
491
+ if (isJsonPrimitive(value))
492
+ return true;
493
+ if (typeof value !== "object" || value === null)
494
+ return false;
495
+ if (seen.has(value))
496
+ return false;
497
+ seen.add(value);
498
+ if (Array.isArray(value)) {
499
+ return value.every((item) => isJsonValueInner(item, seen));
500
+ }
501
+ const prototype = Object.getPrototypeOf(value);
502
+ if (prototype !== Object.prototype && prototype !== null)
503
+ return false;
504
+ return Object.values(value).every((item) => isJsonValueInner(item, seen));
505
+ }
506
+
483
507
  // packages/herd-uri/dist/text-edit.js
484
508
  import { z as z3 } from "zod";
485
509
  var findReplaceArm = z3.strictObject({
@@ -664,7 +688,70 @@ function createVendoredMcpUriModule(descriptor) {
664
688
  }
665
689
  };
666
690
  }
691
+
692
+ // packages/plugin-sdk/dist/vendored-mcp.js
693
+ function createVendoredMcpPort(server, uri) {
694
+ return {
695
+ async invoke({ cwd, tool, args }) {
696
+ try {
697
+ if (!isJsonValue(args)) {
698
+ return {
699
+ ok: false,
700
+ reason: "failed",
701
+ message: "MCP tool arguments must be JSON-compatible."
702
+ };
703
+ }
704
+ const result = await uri.write(`mcp://${encodeURIComponent(server)}/tools/${encodeURIComponent(tool)}`, args, { cwd });
705
+ if (result === void 0 || result.resource === void 0) {
706
+ return {
707
+ ok: false,
708
+ reason: "failed",
709
+ message: `MCP server '${server}' returned no tool result.`
710
+ };
711
+ }
712
+ return { ok: true, value: result.resource };
713
+ } catch (error) {
714
+ const failure = error;
715
+ const code = failure?.providerCode;
716
+ const reason = failure?._tag === "UriProviderError" && failure.moduleId === "mcp.servers" && (code === "needs-auth" || code === "unknown-server" || code === "unknown-tool" || code === "failed") ? code : "failed";
717
+ return {
718
+ ok: false,
719
+ reason,
720
+ message: error instanceof Error ? error.message : String(error),
721
+ ...typeof failure?.serviceId === "string" ? { serviceId: failure.serviceId } : {}
722
+ };
723
+ }
724
+ }
725
+ };
726
+ }
727
+ function createKayAgentVendoredMcp(pluginId, servers, uri) {
728
+ return {
729
+ port(server) {
730
+ if (!servers.includes(server)) {
731
+ throw new Error(`Plugin '${pluginId}' declares no MCP server '${server}' in its kay.app target's contributes.mcpServers, so api.vendoredMcp has no port to it.`);
732
+ }
733
+ return createVendoredMcpPort(server, uri);
734
+ }
735
+ };
736
+ }
737
+ function createKayAppMcp(pluginId, servers, uri) {
738
+ return {
739
+ async callTool({ server, tool, args }) {
740
+ if (!servers.includes(server)) {
741
+ return {
742
+ ok: false,
743
+ reason: "unknown-server",
744
+ message: `Plugin '${pluginId}' does not declare MCP server '${server}'.`
745
+ };
746
+ }
747
+ return createVendoredMcpPort(server, uri).invoke({ cwd: "", tool, args });
748
+ }
749
+ };
750
+ }
667
751
  export {
752
+ createKayAgentVendoredMcp,
753
+ createKayAppMcp,
754
+ createVendoredMcpPort,
668
755
  createVendoredMcpUriModule,
669
756
  vendoredMcpUriModuleFacts
670
757
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yaklabs-ai/plugin-sdk",
3
- "version": "0.54.2",
3
+ "version": "0.55.1",
4
4
  "description": "The Kay plugin SDK — types and helpers for authoring a Kay directory plugin.",
5
5
  "author": "YakLabs",
6
6
  "type": "module",