@yaklabs-ai/plugin-sdk 0.13.5 → 0.13.7

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.13.5"
16
+ "@bonsai/plugin-sdk": "npm:@yaklabs-ai/plugin-sdk@^0.13.7"
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.13.5" },
65
+ "compatibility": { "sdk": "^0.13.7" },
66
66
  "targets": {
67
67
  "kay": {
68
68
  "agent": {
@@ -116,12 +116,23 @@ naming both versions, instead of loading it and finding no `activate`.
116
116
 
117
117
  ## Entrypoints
118
118
 
119
- | Import | What it gives you |
120
- | -------------------------------------- | ---------------------------------------------------------------------------- |
121
- | `@bonsai/plugin-sdk/kay-agent` | Tools, URI modules, search providers, auth — the per-session agent surface |
122
- | `@bonsai/plugin-sdk/kay-app` | App-lifetime surface: storage, endpoints, app-scoped URI modules |
123
- | `@bonsai/plugin-sdk/kay-agent/testing` | `invokeToolForTest`, `fauxAuthContext` — for your own `node --test` runs |
124
- | `@bonsai/plugin-sdk/disclosure` | The conformance audit Bonsai gates your plugin with — run it before you ship |
119
+ | Import | What it gives you |
120
+ | --------------------------------------- | ------------------------------------------------------------------------------- |
121
+ | `@bonsai/plugin-sdk/kay-agent` | Tools, URI modules, search providers, auth — the per-session agent surface |
122
+ | `@bonsai/plugin-sdk/kay-app` | App-lifetime surface: storage, endpoints, app-scoped URI modules |
123
+ | `@bonsai/plugin-sdk/kay-agent/testing` | `invokeToolForTest`, `fauxAuthContext` — for your own `node --test` runs |
124
+ | `@bonsai/plugin-sdk/disclosure` | The conformance audit Bonsai gates your plugin with — run it before you ship |
125
+ | `@bonsai/plugin-sdk/time` | `Temporal` (Bonsai's own instance) and the time-zone helpers, from 0.13.6 |
126
+ | `@bonsai/plugin-sdk/transcript` | The active-branch walk; the turn walk and run records from 0.13.6 |
127
+ | `@bonsai/plugin-sdk/transcript-review` | The review fold: a session's spans, tool-call attempts and records, from 0.13.7 |
128
+ | `@bonsai/plugin-sdk/agent-conversation` | How a session's agent-to-agent traffic groups into conversations, from 0.13.7 |
129
+ | `@bonsai/plugin-sdk/app-data` | The app-data size caps, on an entry with no other import, from 0.13.7 |
130
+ | `@bonsai/plugin-sdk/vendored-mcp` | The kit for a plugin built on a vendor's hosted MCP server, from 0.13.7 |
131
+
132
+ A plugin that imports a subpath declares at least the version its row names (`^0.13.7` for the
133
+ last four). `vendored-mcp` builds the plugin's URI module (its link cards and help); hosting the
134
+ vendor's MCP server itself is a separate host decision, and Bonsai currently makes it only for
135
+ its own bundled plugins.
125
136
 
126
137
  `kay-agent/host` is intentionally not published; it is Bonsai's own loading
127
138
  machinery, not an authoring API.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "package": "@yaklabs-ai/plugin-sdk",
4
- "version": "0.13.5",
4
+ "version": "0.13.7",
5
5
  "target": "npm",
6
6
  "firstPartyLicense": "MIT",
7
7
  "bundled": [
@@ -50,6 +50,13 @@
50
50
  "source": "https://github.com/eemeli/yaml",
51
51
  "note": "Installed as a consumer dependency; it is not bundled into the staged artifacts.",
52
52
  "versionRange": "^2.9.0"
53
+ },
54
+ {
55
+ "name": "@js-temporal/polyfill",
56
+ "license": "ISC",
57
+ "source": "https://github.com/js-temporal/temporal-polyfill",
58
+ "note": "Installed as a consumer dependency; it is not bundled into the staged artifacts.",
59
+ "versionRange": "0.5.1"
53
60
  }
54
61
  ]
55
62
  }
@@ -0,0 +1,93 @@
1
+ /** The admitted inbox receipt, not a target guessed from tool arguments or prose. */
2
+ export declare function admittedSendSessionId(details: unknown): string | undefined;
3
+
4
+ /**
5
+ * The admitted-spawn stamp, or `undefined` when nothing was started.
6
+ *
7
+ * Two generations of stamp exist in durable transcripts and both must parse
8
+ * forever: version 2 (the messaging contract) writes
9
+ * `{ version: 2, sessionId, displayName, backgroundTaskId }` and exists only
10
+ * for admitted spawns (rejections are tool errors and stamp nothing);
11
+ * version 1 wrote `{ version: 1, accepted, childSessionId, label, … }`, where
12
+ * `accepted: false` carries no child session.
13
+ */
14
+ export declare function admittedSpawnSessionId(details: unknown): string | undefined;
15
+
16
+ export declare type AgentConversationBoundaryReason = "assistant_text" | "user" | "spawn" | "custom" | "surface" | "other";
17
+
18
+ export declare interface AgentConversationPeerGroup<TRef = unknown> {
19
+ readonly peerId: string;
20
+ readonly firstItemId: string;
21
+ readonly items: readonly AgentConversationSourceConversation<TRef>[];
22
+ }
23
+
24
+ export declare type AgentConversationResultStatus = RunSettlementOutcome;
25
+
26
+ export declare interface AgentConversationRun<TRef = unknown> {
27
+ /** The first conversation item's id: stable while the run grows. */
28
+ readonly id: string;
29
+ /** Every conversation item, in canonical source order. */
30
+ readonly items: readonly AgentConversationSourceConversation<TRef>[];
31
+ /** Peers in first-appearance order. */
32
+ readonly peers: readonly AgentConversationPeerGroup<TRef>[];
33
+ /** Work strictly between two of this run's conversation items. */
34
+ readonly work: readonly AgentConversationRunWork[];
35
+ }
36
+
37
+ /** Work a run can own: a tool attempt or a folded narration, in source order. */
38
+ declare type AgentConversationRunWork = AgentConversationSourceActivity | AgentConversationSourceNote;
39
+
40
+ declare interface AgentConversationSourceActivity {
41
+ readonly kind: "activity";
42
+ readonly attemptId: string;
43
+ readonly rowKey: string;
44
+ }
45
+
46
+ declare interface AgentConversationSourceBoundary {
47
+ readonly kind: "boundary";
48
+ readonly id: string;
49
+ readonly reason: AgentConversationBoundaryReason;
50
+ }
51
+
52
+ export declare interface AgentConversationSourceConversation<TRef = unknown> {
53
+ readonly kind: "conversation";
54
+ /** Source identity: a tool attempt id (outbound) or message item id. */
55
+ readonly id: string;
56
+ readonly peerId: string;
57
+ readonly direction: "inbound" | "outbound" | "result";
58
+ readonly status?: AgentConversationResultStatus;
59
+ readonly rowKey: string;
60
+ readonly ref: TRef;
61
+ }
62
+
63
+ export declare type AgentConversationSourceItem<TRef = unknown> = AgentConversationSourceConversation<TRef> | AgentConversationRunWork | AgentConversationSourceBoundary;
64
+
65
+ /** Narration that paints only inside a folded "Did work" rail. */
66
+ declare interface AgentConversationSourceNote {
67
+ readonly kind: "note";
68
+ readonly id: string;
69
+ readonly rowKey: string;
70
+ }
71
+
72
+ export declare function groupAgentConversationStream<TRef>(stream: readonly AgentConversationSourceItem<TRef>[]): readonly AgentConversationRun<TRef>[];
73
+
74
+ export declare function immediateFollowingAgentConversationWave<TAttempt extends {
75
+ readonly id: string;
76
+ }>(run: AgentConversationRun<unknown>, waves: readonly (readonly TAttempt[])[], stream: readonly AgentConversationSourceItem[], order: ReadonlyMap<string, number>, sources: ReadonlyMap<string, {
77
+ readonly rowKey: string;
78
+ }>, claimedWaveAttemptIds: ReadonlySet<string>): readonly TAttempt[] | undefined;
79
+
80
+ /** Whether a run changes today's rendering: a pair (2+ items with one peer) or 2+ peers. */
81
+ export declare function isFoldedAgentConversationRun(run: AgentConversationRun<unknown>): boolean;
82
+
83
+ /** The received metadata that both work ownership and the conversation renderer recognize. */
84
+ export declare function receivedConversation(customType: string | undefined, details: unknown): {
85
+ readonly peerId: string;
86
+ readonly direction: "inbound" | "result";
87
+ readonly status?: AgentConversationResultStatus;
88
+ } | undefined;
89
+
90
+ /** Terminal daemon-run outcomes shared by stored records and conversation results. */
91
+ declare type RunSettlementOutcome = "completed" | "interrupted" | "cancelled" | "failed";
92
+
93
+ export { }
@@ -0,0 +1,174 @@
1
+ // packages/plugin-protocol/dist/agent-conversation.js
2
+ function workId(work) {
3
+ return work.kind === "activity" ? work.attemptId : work.id;
4
+ }
5
+ function freezeRun(run) {
6
+ const peers = /* @__PURE__ */ new Map();
7
+ for (const item of run.items) {
8
+ const items = peers.get(item.peerId);
9
+ if (items === void 0)
10
+ peers.set(item.peerId, [item]);
11
+ else
12
+ items.push(item);
13
+ }
14
+ return {
15
+ id: run.items[0].id,
16
+ items: run.items,
17
+ peers: [...peers].map(([peerId, items]) => ({
18
+ peerId,
19
+ firstItemId: items[0].id,
20
+ items
21
+ })),
22
+ work: run.work
23
+ };
24
+ }
25
+ function groupAgentConversationStream(stream) {
26
+ const runs = [];
27
+ const claimed = /* @__PURE__ */ new Set();
28
+ let active;
29
+ let queued = [];
30
+ const close = () => {
31
+ if (active !== void 0)
32
+ runs.push(freezeRun(active));
33
+ active = void 0;
34
+ queued = [];
35
+ };
36
+ for (const item of stream) {
37
+ if (item.kind === "boundary") {
38
+ close();
39
+ continue;
40
+ }
41
+ if (item.kind === "activity" || item.kind === "note") {
42
+ if (active !== void 0)
43
+ queued.push(item);
44
+ continue;
45
+ }
46
+ if (active === void 0) {
47
+ active = { items: [item], work: [] };
48
+ continue;
49
+ }
50
+ for (const work of queued) {
51
+ if (claimed.has(workId(work)))
52
+ continue;
53
+ claimed.add(workId(work));
54
+ active.work.push(work);
55
+ }
56
+ queued = [];
57
+ active.items.push(item);
58
+ }
59
+ close();
60
+ return runs;
61
+ }
62
+ function immediateFollowingAgentConversationWave(run, waves, stream, order, sources, claimedWaveAttemptIds) {
63
+ if (run.work.length === 0)
64
+ return void 0;
65
+ const runIds = new Set(run.items.map((item) => item.id));
66
+ const end = Math.max(...run.items.map((item) => order.get(item.id) ?? -Infinity));
67
+ if (!Number.isFinite(end))
68
+ return void 0;
69
+ let candidate;
70
+ let candidateStart = Infinity;
71
+ for (const wave of waves) {
72
+ const followingAttempts = wave.filter((attempt) => {
73
+ const position = order.get(attempt.id);
74
+ return position !== void 0 && position > end && !claimedWaveAttemptIds.has(attempt.id);
75
+ });
76
+ if (followingAttempts.length === 0)
77
+ continue;
78
+ if (followingAttempts.some((attempt) => sources.get(attempt.id)?.rowKey !== run.items.at(-1)?.rowKey))
79
+ continue;
80
+ const start = Math.min(...followingAttempts.map((attempt) => order.get(attempt.id) ?? Infinity));
81
+ if (start < candidateStart) {
82
+ candidate = followingAttempts;
83
+ candidateStart = start;
84
+ }
85
+ }
86
+ if (candidate === void 0)
87
+ return void 0;
88
+ const waveIds = new Set(candidate.map((attempt) => attempt.id));
89
+ for (const item of stream) {
90
+ const position = order.get(item.kind === "activity" ? item.attemptId : item.id);
91
+ if (position === void 0 || position <= end || position >= candidateStart)
92
+ continue;
93
+ if (item.kind === "conversation" && !runIds.has(item.id))
94
+ return void 0;
95
+ if (item.kind !== "activity" && item.kind !== "conversation")
96
+ return void 0;
97
+ if (item.kind === "activity" && !waveIds.has(item.attemptId))
98
+ return void 0;
99
+ }
100
+ return candidate;
101
+ }
102
+ function isFoldedAgentConversationRun(run) {
103
+ return run.peers.length >= 2 || run.peers.some((peer) => peer.items.length >= 2);
104
+ }
105
+ function admittedSpawnSessionId(details) {
106
+ if (typeof details !== "object" || details === null)
107
+ return void 0;
108
+ const record = details;
109
+ const sessionId = typeof record["sessionId"] === "string" ? record["sessionId"] : record["accepted"] === true && typeof record["childSessionId"] === "string" ? record["childSessionId"] : void 0;
110
+ return sessionId === void 0 || sessionId.trim() === "" ? void 0 : sessionId.trim();
111
+ }
112
+ function admittedSendSessionId(details) {
113
+ if (typeof details !== "object" || details === null)
114
+ return void 0;
115
+ const record = details;
116
+ const sessionId = typeof record["sessionId"] === "string" ? record["sessionId"].trim() : "";
117
+ return sessionId && typeof record["rowId"] === "string" && record["rowId"] !== "" && typeof record["backgroundTaskId"] === "string" ? sessionId : void 0;
118
+ }
119
+ function receivedConversation(customType, details) {
120
+ if (typeof details !== "object" || details === null)
121
+ return void 0;
122
+ const record = details;
123
+ if (customType === "collaboration_message") {
124
+ const provenance2 = record["provenance"];
125
+ if (typeof record["senderSessionId"] !== "string" || record["kind"] !== "message" && record["kind"] !== "progress" || typeof provenance2 !== "object" || provenance2 === null)
126
+ return void 0;
127
+ const source = provenance2;
128
+ if (source["source"] !== "subagent" || typeof source["sourceId"] !== "string")
129
+ return void 0;
130
+ return { peerId: record["senderSessionId"], direction: "inbound" };
131
+ }
132
+ if (customType !== "subagent_result")
133
+ return void 0;
134
+ const provenance = subagentProvenanceOf(record);
135
+ const status = record["status"];
136
+ if (provenance === void 0 || provenance.sourceId === "" || status !== "completed" && status !== "failed" && status !== "cancelled" && status !== "interrupted")
137
+ return void 0;
138
+ return { peerId: provenance.sourceId, direction: "result", status };
139
+ }
140
+ function subagentProvenanceOf(record) {
141
+ const provenance = record["provenance"];
142
+ if (typeof provenance === "object" && provenance !== null) {
143
+ const p = provenance;
144
+ if (p["source"] === "subagent" && typeof p["sourceId"] === "string") {
145
+ const label = p["label"];
146
+ return {
147
+ source: "subagent",
148
+ sourceId: p["sourceId"],
149
+ ...typeof label === "string" ? { label } : {}
150
+ };
151
+ }
152
+ }
153
+ const attribution = record["attribution"];
154
+ if (typeof attribution === "object" && attribution !== null) {
155
+ const a = attribution;
156
+ if (a["kind"] === "subagent" && typeof a["childSessionId"] === "string") {
157
+ const label = a["label"];
158
+ return {
159
+ source: "subagent",
160
+ sourceId: a["childSessionId"],
161
+ ...typeof label === "string" ? { label } : {}
162
+ };
163
+ }
164
+ }
165
+ return void 0;
166
+ }
167
+ export {
168
+ admittedSendSessionId,
169
+ admittedSpawnSessionId,
170
+ groupAgentConversationStream,
171
+ immediateFollowingAgentConversationWave,
172
+ isFoldedAgentConversationRun,
173
+ receivedConversation
174
+ };
@@ -0,0 +1,7 @@
1
+ /** Maximum UTF-8 JSON size of one plugin app-data live change. */
2
+ export declare const KAY_APP_DATA_CHANGE_MAX_BYTES: number;
3
+
4
+ /** Maximum UTF-8 JSON size of one plugin app-data snapshot. */
5
+ export declare const KAY_APP_DATA_SNAPSHOT_MAX_BYTES: number;
6
+
7
+ export { }
@@ -0,0 +1,9 @@
1
+ // packages/plugin-protocol/dist/app-data.js
2
+ var KAY_APP_DATA_SNAPSHOT_MAX_BYTES = 16 * 1024 * 1024;
3
+ var KAY_APP_DATA_CHANGE_MAX_BYTES = 256 * 1024;
4
+ var KAY_APP_DATA_READ_REQUEST_MAX_BYTES = 64 * 1024;
5
+ var KAY_APP_DATA_READ_RESULT_MAX_BYTES = 16 * 1024 * 1024;
6
+ export {
7
+ KAY_APP_DATA_CHANGE_MAX_BYTES,
8
+ KAY_APP_DATA_SNAPSHOT_MAX_BYTES
9
+ };
@@ -2109,11 +2109,9 @@ declare interface KayAppTimeSnapshot {
2109
2109
  /**
2110
2110
  * The time zone the user is in, as an IANA id in the spelling ICU resolves (`America/New_York`).
2111
2111
  * One zone can have two ids, and ICU picks one: `Asia/Kolkata` comes back as `Asia/Calcutta`. So
2112
- * never compare zone ids with `===`. A first-party plugin compares with `sameZone` from
2113
- * `@bonsai/time`. A plugin that imports only this SDK and node builtins resolves each id with
2114
- * `new Intl.DateTimeFormat("en-US", { timeZone: id }).resolvedOptions().timeZone` and compares the
2115
- * results. Pass `zone` as the `timeZone` of `Intl.DateTimeFormat` or `toLocaleString` to format a
2116
- * time for the user.
2112
+ * never compare zone ids with `===`: compare with `sameZone` from `@bonsai/plugin-sdk/time`. Pass
2113
+ * `zone` as the `timeZone` of `Intl.DateTimeFormat` or `toLocaleString` to format a time for the
2114
+ * user.
2117
2115
  *
2118
2116
  * When `zoneSource` is `utc-fallback` the zone is unknown, and this value is `UTC` only because a
2119
2117
  * value is required.
@@ -3696,8 +3694,8 @@ declare function ReturnTypeDeferred<Type extends TSchema>(type: Type, options?:
3696
3694
  * owns:
3697
3695
  * - {@link RunFailureReason}.
3698
3696
  * does_not_own:
3699
- * - Classifying a failure into a reason (`@herd/herd-agent`'s
3700
- * `core/recovery/run-failure-reason.ts`).
3697
+ * - Classifying a failure into a reason (`./run-failure-classifier.ts`), or the engine's
3698
+ * run-error mapping and recovery policy (`@herd/herd-agent`).
3701
3699
  * ---
3702
3700
  */
3703
3701
  /** Provider-neutral factual category for a failed model run. */
@@ -481,6 +481,19 @@ declare interface ConvertedArtifact {
481
481
  */
482
482
  declare function Create(hidden: ObjectLike_2, enumerable: ObjectLike_2, options?: ObjectLike_2): ObjectLike_2;
483
483
 
484
+ /**
485
+ * A {@link TestClock} starting at `startMs`. Without a `zone` it answers the zone as unknown, like
486
+ * {@link fauxKayAgentCapabilities}; with one it answers that zone as the user's. Use it as
487
+ * `{ ...fauxKayAgentCapabilities(), time: createTestClock(0) }`.
488
+ *
489
+ * Throws `RangeError` for a non-finite time, and for a `zone` the host could never answer: the
490
+ * host answers an IANA id in the spelling ICU resolves (`Asia/Calcutta`, not `Asia/Kolkata`), so
491
+ * an alias, an abbreviation such as `EST` or a typo would make a test pass that production fails.
492
+ */
493
+ export declare function createTestClock(startMs: number, options?: {
494
+ readonly zone?: string;
495
+ }): TestClock;
496
+
484
497
  /** Creates a Cyclic type. */
485
498
  declare function Cyclic<Defs extends TProperties, Ref extends string>($defs: Defs, $ref: Ref, options?: TSchemaOptions): TCyclic<Defs, Ref>;
486
499
 
@@ -2010,11 +2023,9 @@ declare interface KayAppTimeSnapshot {
2010
2023
  /**
2011
2024
  * The time zone the user is in, as an IANA id in the spelling ICU resolves (`America/New_York`).
2012
2025
  * One zone can have two ids, and ICU picks one: `Asia/Kolkata` comes back as `Asia/Calcutta`. So
2013
- * never compare zone ids with `===`. A first-party plugin compares with `sameZone` from
2014
- * `@bonsai/time`. A plugin that imports only this SDK and node builtins resolves each id with
2015
- * `new Intl.DateTimeFormat("en-US", { timeZone: id }).resolvedOptions().timeZone` and compares the
2016
- * results. Pass `zone` as the `timeZone` of `Intl.DateTimeFormat` or `toLocaleString` to format a
2017
- * time for the user.
2026
+ * never compare zone ids with `===`: compare with `sameZone` from `@bonsai/plugin-sdk/time`. Pass
2027
+ * `zone` as the `timeZone` of `Intl.DateTimeFormat` or `toLocaleString` to format a time for the
2028
+ * user.
2018
2029
  *
2019
2030
  * When `zoneSource` is `utc-fallback` the zone is unknown, and this value is `UTC` only because a
2020
2031
  * value is required.
@@ -3594,8 +3605,8 @@ declare function ReturnTypeDeferred<Type extends TSchema>(type: Type, options?:
3594
3605
  * owns:
3595
3606
  * - {@link RunFailureReason}.
3596
3607
  * does_not_own:
3597
- * - Classifying a failure into a reason (`@herd/herd-agent`'s
3598
- * `core/recovery/run-failure-reason.ts`).
3608
+ * - Classifying a failure into a reason (`./run-failure-classifier.ts`), or the engine's
3609
+ * run-error mapping and recovery policy (`@herd/herd-agent`).
3599
3610
  * ---
3600
3611
  */
3601
3612
  /** Provider-neutral factual category for a failed model run. */
@@ -5077,6 +5088,18 @@ declare type TEscape0<Index extends string> = Index extends `${infer Left}~0${in
5077
5088
 
5078
5089
  declare type TEscape1<Index extends string> = Index extends `${infer Left}~1${infer Right}` ? `${Left}/${TEscape<Right>}` : Index;
5079
5090
 
5091
+ /**
5092
+ * A time door (`api.time` / `app.time`) whose clock moves only when the test moves it. It also
5093
+ * answers `nowMs()`, so it replaces `createTestClock` from `@bonsai/time/testing` unchanged.
5094
+ */
5095
+ export declare interface TestClock extends KayAppTime {
5096
+ nowMs(): number;
5097
+ /** Moves the clock forward (or backward, for a negative value) by `ms`. */
5098
+ advance(ms: number): void;
5099
+ /** Jumps the clock to an absolute epoch-millisecond value. */
5100
+ set(ms: number): void;
5101
+ }
5102
+
5080
5103
  /** Applies an Evaluate action to a type. */
5081
5104
  declare type TEvaluate<Type extends TSchema> = (TEvaluateAction<Type>);
5082
5105
 
@@ -193,6 +193,42 @@ function fauxKayAgentCapabilities() {
193
193
  familyFolder: { path: async () => void 0 }
194
194
  };
195
195
  }
196
+ function createTestClock(startMs, options = {}) {
197
+ let nowMs = finiteMs(startMs, "startMs");
198
+ const { zone } = options;
199
+ if (zone !== void 0)
200
+ requireResolvedZone(zone);
201
+ return {
202
+ nowMs: () => nowMs,
203
+ snapshot: () => zone === void 0 ? { nowMs, zone: "UTC", zoneSource: "utc-fallback" } : { nowMs, zone, zoneSource: "host" },
204
+ advance(ms) {
205
+ nowMs += finiteMs(ms, "ms");
206
+ },
207
+ set(ms) {
208
+ nowMs = finiteMs(ms, "ms");
209
+ }
210
+ };
211
+ }
212
+ function requireResolvedZone(zone) {
213
+ let resolved;
214
+ try {
215
+ resolved = new Intl.DateTimeFormat("en-US", {
216
+ timeZone: zone
217
+ }).resolvedOptions().timeZone;
218
+ } catch {
219
+ resolved = void 0;
220
+ }
221
+ const ianaShaped = zone.includes("/") || zone === "UTC";
222
+ if (!ianaShaped || resolved !== zone) {
223
+ throw new RangeError(`createTestClock: zone ${JSON.stringify(zone)} is not a zone the host answers` + (resolved !== void 0 && ianaShaped ? `; the host spells it ${JSON.stringify(resolved)}` : ""));
224
+ }
225
+ }
226
+ function finiteMs(value, name) {
227
+ if (!Number.isFinite(value)) {
228
+ throw new RangeError(`createTestClock: ${name} must be a finite number, got ${String(value)}`);
229
+ }
230
+ return value;
231
+ }
196
232
  function defaultTestToolContext() {
197
233
  return { cwd: "/tmp", model: void 0 };
198
234
  }
@@ -209,6 +245,7 @@ function producedResourcesForTest(result) {
209
245
  return read;
210
246
  }
211
247
  export {
248
+ createTestClock,
212
249
  fauxAuthContext,
213
250
  fauxKayAgentCapabilities,
214
251
  invokeToolForTest,
@@ -551,6 +551,8 @@ declare const CompareResultLeftInside = 2;
551
551
 
552
552
  declare const CompareResultRightInside = 3;
553
553
 
554
+ export declare function compileUriClaim(claim: UriClaim): UriCompiledClaim;
555
+
554
556
  /** Applies a Conditional action to the given types. */
555
557
  declare function Conditional<Left extends TSchema, Right extends TSchema, True extends TSchema, False extends TSchema>(left: Left, right: Right, true_: True, false_: False, options?: TSchemaOptions): TConditional<Left, Right, True, False>;
556
558
 
@@ -2478,11 +2480,9 @@ declare interface KayAppTimeSnapshot {
2478
2480
  /**
2479
2481
  * The time zone the user is in, as an IANA id in the spelling ICU resolves (`America/New_York`).
2480
2482
  * One zone can have two ids, and ICU picks one: `Asia/Kolkata` comes back as `Asia/Calcutta`. So
2481
- * never compare zone ids with `===`. A first-party plugin compares with `sameZone` from
2482
- * `@bonsai/time`. A plugin that imports only this SDK and node builtins resolves each id with
2483
- * `new Intl.DateTimeFormat("en-US", { timeZone: id }).resolvedOptions().timeZone` and compares the
2484
- * results. Pass `zone` as the `timeZone` of `Intl.DateTimeFormat` or `toLocaleString` to format a
2485
- * time for the user.
2483
+ * never compare zone ids with `===`: compare with `sameZone` from `@bonsai/plugin-sdk/time`. Pass
2484
+ * `zone` as the `timeZone` of `Intl.DateTimeFormat` or `toLocaleString` to format a time for the
2485
+ * user.
2486
2486
  *
2487
2487
  * When `zoneSource` is `utc-fallback` the zone is unknown, and this value is `UTC` only because a
2488
2488
  * value is required.
@@ -4919,6 +4919,30 @@ declare const PluginCapabilityRefusalV1: z.ZodObject<{
4919
4919
  envelope: z.ZodObject<{
4920
4920
  binary: z.ZodString;
4921
4921
  }, z.core.$strict>;
4922
+ }, z.core.$strict>, z.ZodObject<{
4923
+ target: z.ZodLiteral<"plugin">;
4924
+ slug: z.ZodLiteral<"mcp.host-server">;
4925
+ envelope: z.ZodObject<{
4926
+ name: z.ZodString;
4927
+ url: z.ZodString;
4928
+ }, z.core.$strict>;
4929
+ }, z.core.$strict>, z.ZodObject<{
4930
+ target: z.ZodLiteral<"plugin">;
4931
+ slug: z.ZodLiteral<"prompt.resident">;
4932
+ envelope: z.ZodObject<{}, z.core.$strict>;
4933
+ }, z.core.$strict>, z.ZodObject<{
4934
+ target: z.ZodLiteral<"plugin">;
4935
+ slug: z.ZodLiteral<"skills.declared-source">;
4936
+ envelope: z.ZodDiscriminatedUnion<[z.ZodObject<{
4937
+ channel: z.ZodLiteral<"git-clone">;
4938
+ locator: z.ZodString;
4939
+ ref: z.ZodOptional<z.ZodString>;
4940
+ subdir: z.ZodOptional<z.ZodString>;
4941
+ }, z.core.$strict>, z.ZodObject<{
4942
+ channel: z.ZodLiteral<"installer">;
4943
+ command: z.ZodString;
4944
+ args: z.ZodArray<z.ZodString>;
4945
+ }, z.core.$strict>], "channel">;
4922
4946
  }, z.core.$strict>, z.ZodObject<{
4923
4947
  target: z.ZodEnum<{
4924
4948
  "kay.agent": "kay.agent";
@@ -5518,8 +5542,8 @@ declare function ReturnTypeDeferred<Type extends TSchema>(type: Type, options?:
5518
5542
  * owns:
5519
5543
  * - {@link RunFailureReason}.
5520
5544
  * does_not_own:
5521
- * - Classifying a failure into a reason (`@herd/herd-agent`'s
5522
- * `core/recovery/run-failure-reason.ts`).
5545
+ * - Classifying a failure into a reason (`./run-failure-classifier.ts`), or the engine's
5546
+ * run-error mapping and recovery policy (`@herd/herd-agent`).
5523
5547
  * ---
5524
5548
  */
5525
5549
  /** Provider-neutral factual category for a failed model run. */
@@ -10114,7 +10138,7 @@ export declare interface UriCapabilitySpec {
10114
10138
  readonly returns?: UriPayloadContract;
10115
10139
  }
10116
10140
 
10117
- declare type UriClaim = UriSchemeClaim | UriPatternClaim | UrlPatternClaim;
10141
+ export declare type UriClaim = UriSchemeClaim | UriPatternClaim | UrlPatternClaim;
10118
10142
 
10119
10143
  declare interface UriCommonFlow {
10120
10144
  readonly label: string;
@@ -10122,6 +10146,17 @@ declare interface UriCommonFlow {
10122
10146
  readonly example: string;
10123
10147
  }
10124
10148
 
10149
+ export declare interface UriCompiledClaim {
10150
+ readonly claim: UriClaim;
10151
+ match(input: string): UriCompiledClaimMatch | undefined;
10152
+ }
10153
+
10154
+ export declare interface UriCompiledClaimMatch {
10155
+ readonly claimKind: UriClaim["kind"];
10156
+ readonly captures?: Record<string, string>;
10157
+ readonly normalizedUri?: string;
10158
+ }
10159
+
10125
10160
  /**
10126
10161
  * A source changed (or was never witnessed) before a mediated mutation. Thrown by a
10127
10162
  * module or by the host tool; the host renders it as a normal "not applied" tool