@yaklabs-ai/plugin-sdk 0.12.2 → 0.12.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.
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
- "@kay/plugin-sdk": "npm:@yaklabs-ai/plugin-sdk@^0.12.2"
16
+ "@kay/plugin-sdk": "npm:@yaklabs-ai/plugin-sdk@^0.12.3"
17
17
  }
18
18
  }
19
19
  ```
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "package": "@yaklabs-ai/plugin-sdk",
4
- "version": "0.12.2",
4
+ "version": "0.12.3",
5
5
  "target": "npm",
6
6
  "firstPartyLicense": "MIT",
7
7
  "bundled": [
@@ -1257,6 +1257,14 @@ declare interface KayAgentPluginApi {
1257
1257
  * guard it with `if (api.withJev !== undefined)`.
1258
1258
  */
1259
1259
  readonly withJev: WithJev;
1260
+ /**
1261
+ * The daemon's clock and the time zone the user is in, read together on each `snapshot()` call.
1262
+ * Always present and ungated: it confers no authority, since a plugin runs in the daemon's
1263
+ * process and can read the clock itself. Read the user's zone here, never from `Intl` or
1264
+ * `process.env.TZ`, and render user-facing times in it rather than an event's own zone. This is
1265
+ * the same zone-of-record port the session stamp uses; see {@link KayAppTime.snapshot}.
1266
+ */
1267
+ readonly time: KayAppTime;
1260
1268
  /**
1261
1269
  * Publish the plugin's declared events, for example so a default notification rule can tell the
1262
1270
  * user what the agent just did. Always present, like {@link withJev}: there is nothing to
@@ -1486,6 +1494,81 @@ declare interface KayAgentWhiteboardUpdateInput {
1486
1494
  readonly title?: string;
1487
1495
  }
1488
1496
 
1497
+ /**
1498
+ * `app.time` / `api.time`: host facts about time. Added to `kay.app` in SDK 0.12.1 and to
1499
+ * `kay.agent` with the calendar zone-of-record work: a plugin that calls it declares
1500
+ * `compatibility.sdk` `^0.12.1`, so an older Bonsai refuses a directory or repository plugin at
1501
+ * load instead of the call failing.
1502
+ */
1503
+ declare interface KayAppTime {
1504
+ /**
1505
+ * Reads the daemon's clock and the user's time zone now.
1506
+ *
1507
+ * Every call asks the host again and returns a new object; nothing is cached. Call it where you
1508
+ * need the value (inside a tool, a handler or a workflow step), not inside `activate()`: a value
1509
+ * read there is as old as the activation, and the host can run a bundled plugin's `activate()`
1510
+ * against a recording api that has no `time`, so a read there throws. The exception is a
1511
+ * directory plugin that declares a workflow with a cron `timezone`: `workflows.define` is legal
1512
+ * only during `activate()`, so it reads the zone there, and the schedule then keeps that zone.
1513
+ * Keep the result for as long as you use it, and not across a long wait (a sleep, a timer, a
1514
+ * queued run): the zone can change while you wait. On a daemon that runs on the user's machine,
1515
+ * a change of the process's `TZ` shows in the next call. A `TZ` that names a zone wins over the
1516
+ * operating system's zone, so a change of the operating-system zone does not show while the
1517
+ * process has one. When `TZ` is unset or does not name a zone, a change of the operating-system
1518
+ * zone is meant to show, but none has been seen on a real machine yet.
1519
+ *
1520
+ * A zone the host cannot determine is an answer (`zoneSource: "utc-fallback"`), not an error.
1521
+ *
1522
+ * Use this to find the zone, not `Intl.DateTimeFormat().resolvedOptions().timeZone`, which can
1523
+ * keep a zone the runtime read earlier, or `process.env.TZ`, which is often unset or not a zone.
1524
+ * Neither says that the zone is unknown.
1525
+ *
1526
+ * It does not use `this`, so it is safe to destructure.
1527
+ */
1528
+ readonly snapshot: () => KayAppTimeSnapshot;
1529
+ }
1530
+
1531
+ /**
1532
+ * The plugin-facing time contract: the daemon's clock and the time zone the user is in,
1533
+ * read together in one call.
1534
+ *
1535
+ * Ungated and always present, like `app.paths`. It confers no authority: a plugin runs in the
1536
+ * daemon's process and can already read the clock. What the door adds is the daemon's answer
1537
+ * for the USER's zone, and a label for the case where that answer is unknown.
1538
+ *
1539
+ * This is a leaf module. It lives beside both target SDKs (rather than inside `kay-app`) so the
1540
+ * `kay.agent` SDK can expose the same door without importing the `kay.app` surface, which pulls
1541
+ * that surface into every agent-facing bundle.
1542
+ */
1543
+ /** One reading of the daemon's clock and the user's time zone. Each call returns a new object. */
1544
+ declare interface KayAppTimeSnapshot {
1545
+ /** The daemon's clock when the snapshot was read: milliseconds since the Unix epoch. */
1546
+ readonly nowMs: number;
1547
+ /**
1548
+ * The time zone the user is in, as an IANA id in the spelling ICU resolves (`America/New_York`).
1549
+ * One zone can have two ids, and ICU picks one: `Asia/Kolkata` comes back as `Asia/Calcutta`. So
1550
+ * never compare zone ids with `===`. A first-party plugin compares with `sameZone` from
1551
+ * `@kay/time`. A plugin that imports only this SDK and node builtins resolves each id with
1552
+ * `new Intl.DateTimeFormat("en-US", { timeZone: id }).resolvedOptions().timeZone` and compares the
1553
+ * results. Pass `zone` as the `timeZone` of `Intl.DateTimeFormat` or `toLocaleString` to format a
1554
+ * time for the user.
1555
+ *
1556
+ * When `zoneSource` is `utc-fallback` the zone is unknown, and this value is `UTC` only because a
1557
+ * value is required.
1558
+ */
1559
+ readonly zone: string;
1560
+ /**
1561
+ * How the daemon found `zone`.
1562
+ * - `host`: the daemon runs on the user's machine, and `zone` is the zone the daemon process
1563
+ * runs in, read for this call: the process's `TZ` when that names a zone, otherwise the
1564
+ * operating system's zone.
1565
+ * - `utc-fallback`: the daemon does not know the user's zone. It runs on another machine (a remote
1566
+ * daemon), or the machine's zone could not be read or is not an IANA id. `zone` is `UTC`, which
1567
+ * here is a placeholder and not a fact about the user: do not show it to the user as their zone.
1568
+ */
1569
+ readonly zoneSource: "host" | "utc-fallback";
1570
+ }
1571
+
1489
1572
  declare type KnownApi = "openai-completions" | "mistral-conversations" | "openai-responses" | "azure-openai-responses" | "openai-codex-responses" | "anthropic-messages" | "bedrock-converse-stream" | "google-generative-ai" | "google-vertex" | "pi-messages";
1490
1573
 
1491
1574
  declare type KnownProvider = "amazon-bedrock" | "ant-ling" | "anthropic" | "google" | "google-vertex" | "openai" | "azure-openai-responses" | "openai-codex" | "radius" | "typesafe" | "nvidia" | "deepseek" | "github-copilot" | "xai" | "groq" | "cerebras" | "openrouter" | "vercel-ai-gateway" | "zai" | "zai-coding-cn" | "mistral" | "minimax" | "minimax-cn" | "moonshotai" | "moonshotai-cn" | "huggingface" | "fireworks" | "together" | "baseten" | "opencode" | "opencode-go" | "kimi-coding" | "meta" | "cloudflare-workers-ai" | "cloudflare-ai-gateway" | "qwen-token-plan" | "qwen-token-plan-cn" | "qwen-token-plan-individual" | "xiaomi" | "xiaomi-token-plan-cn" | "xiaomi-token-plan-ams" | "xiaomi-token-plan-sgp";
@@ -3146,8 +3229,9 @@ declare interface UriModule {
3146
3229
  /** Pure route/payload validation applied before a consequential permission ask. */
3147
3230
  readonly preflight?: UriOperationPreflightPolicy;
3148
3231
  /**
3149
- * Structured facts naming the WHOLE consequence of this call, or `undefined` when
3150
- * the route cannot honestly be described on a one-line approval surface. The module
3232
+ * Structured facts for approval surfaces, or `undefined` when unavailable.
3233
+ * A one-line description must name the whole consequence; `preview` instead
3234
+ * supplies expanded, literal-text detail and suppresses one-line approval. The module
3151
3235
  * owns the static action phrase, target resolution, payload decoding and declaration
3152
3236
  * of covered payload fields and query parameters. Unknown fields cost the summary.
3153
3237
  * The host owns framing, the whole-quote fit (no elision) and display-safety admission.
@@ -3305,6 +3389,18 @@ declare interface UriOperationDescription {
3305
3389
  readonly target: string;
3306
3390
  /** Model-authored content; the host flattens, budgets, elides and bounds it. */
3307
3391
  readonly quote?: string;
3392
+ /** Full, render-only detail. Hosts must not turn a rich preview into one-line approval. */
3393
+ readonly preview?: {
3394
+ readonly title: string;
3395
+ readonly description?: string;
3396
+ readonly fields?: readonly {
3397
+ readonly label: string;
3398
+ readonly value: string;
3399
+ readonly oldValue?: string;
3400
+ }[];
3401
+ };
3402
+ /** True only when every displayed fact comes from the input, independent of the executing account. */
3403
+ readonly accountIndependent?: true;
3308
3404
  /** What this description accounts for; the host refuses undeclared input. */
3309
3405
  readonly accounts: {
3310
3406
  /** The payload actually read by the module, after its own decoding. */
@@ -806,7 +806,12 @@ function createKayAgentProbeCapabilityHandles() {
806
806
  withJev: INERT_WITH_JEV,
807
807
  // Fire-and-forget on the real api too, so dropping is the inert shape: nothing to hang on.
808
808
  events: { publish: () => {
809
- } }
809
+ } },
810
+ // A probe has no user-time port, so it answers the zone as unknown — the same contract the
811
+ // real capability keeps for a zone the host cannot read.
812
+ time: {
813
+ snapshot: () => ({ nowMs: 0, zone: "UTC", zoneSource: "utc-fallback" })
814
+ }
810
815
  };
811
816
  }
812
817
 
@@ -531,7 +531,7 @@ declare interface ExtensionPaths {
531
531
  export declare function fauxAuthContext(overrides?: Partial<ExtensionAuthContext>): ExtensionAuthContext;
532
532
 
533
533
  /** The capability handles a `KayAgentPluginApi` always carries — authority is decided per call. */
534
- export declare type FauxKayAgentCapabilities = Pick<KayAgentPluginApi, "filesystem" | "network" | "webSession" | "process" | "whiteboard" | "interaction" | "interceptToolCall" | "withJev" | "events">;
534
+ export declare type FauxKayAgentCapabilities = Pick<KayAgentPluginApi, "filesystem" | "network" | "webSession" | "process" | "whiteboard" | "interaction" | "interceptToolCall" | "withJev" | "events" | "time">;
535
535
 
536
536
  export declare function fauxKayAgentCapabilities(): FauxKayAgentCapabilities;
537
537
 
@@ -1159,6 +1159,14 @@ declare interface KayAgentPluginApi {
1159
1159
  * guard it with `if (api.withJev !== undefined)`.
1160
1160
  */
1161
1161
  readonly withJev: WithJev;
1162
+ /**
1163
+ * The daemon's clock and the time zone the user is in, read together on each `snapshot()` call.
1164
+ * Always present and ungated: it confers no authority, since a plugin runs in the daemon's
1165
+ * process and can read the clock itself. Read the user's zone here, never from `Intl` or
1166
+ * `process.env.TZ`, and render user-facing times in it rather than an event's own zone. This is
1167
+ * the same zone-of-record port the session stamp uses; see {@link KayAppTime.snapshot}.
1168
+ */
1169
+ readonly time: KayAppTime;
1162
1170
  /**
1163
1171
  * Publish the plugin's declared events, for example so a default notification rule can tell the
1164
1172
  * user what the agent just did. Always present, like {@link withJev}: there is nothing to
@@ -1388,6 +1396,81 @@ declare interface KayAgentWhiteboardUpdateInput {
1388
1396
  readonly title?: string;
1389
1397
  }
1390
1398
 
1399
+ /**
1400
+ * `app.time` / `api.time`: host facts about time. Added to `kay.app` in SDK 0.12.1 and to
1401
+ * `kay.agent` with the calendar zone-of-record work: a plugin that calls it declares
1402
+ * `compatibility.sdk` `^0.12.1`, so an older Bonsai refuses a directory or repository plugin at
1403
+ * load instead of the call failing.
1404
+ */
1405
+ declare interface KayAppTime {
1406
+ /**
1407
+ * Reads the daemon's clock and the user's time zone now.
1408
+ *
1409
+ * Every call asks the host again and returns a new object; nothing is cached. Call it where you
1410
+ * need the value (inside a tool, a handler or a workflow step), not inside `activate()`: a value
1411
+ * read there is as old as the activation, and the host can run a bundled plugin's `activate()`
1412
+ * against a recording api that has no `time`, so a read there throws. The exception is a
1413
+ * directory plugin that declares a workflow with a cron `timezone`: `workflows.define` is legal
1414
+ * only during `activate()`, so it reads the zone there, and the schedule then keeps that zone.
1415
+ * Keep the result for as long as you use it, and not across a long wait (a sleep, a timer, a
1416
+ * queued run): the zone can change while you wait. On a daemon that runs on the user's machine,
1417
+ * a change of the process's `TZ` shows in the next call. A `TZ` that names a zone wins over the
1418
+ * operating system's zone, so a change of the operating-system zone does not show while the
1419
+ * process has one. When `TZ` is unset or does not name a zone, a change of the operating-system
1420
+ * zone is meant to show, but none has been seen on a real machine yet.
1421
+ *
1422
+ * A zone the host cannot determine is an answer (`zoneSource: "utc-fallback"`), not an error.
1423
+ *
1424
+ * Use this to find the zone, not `Intl.DateTimeFormat().resolvedOptions().timeZone`, which can
1425
+ * keep a zone the runtime read earlier, or `process.env.TZ`, which is often unset or not a zone.
1426
+ * Neither says that the zone is unknown.
1427
+ *
1428
+ * It does not use `this`, so it is safe to destructure.
1429
+ */
1430
+ readonly snapshot: () => KayAppTimeSnapshot;
1431
+ }
1432
+
1433
+ /**
1434
+ * The plugin-facing time contract: the daemon's clock and the time zone the user is in,
1435
+ * read together in one call.
1436
+ *
1437
+ * Ungated and always present, like `app.paths`. It confers no authority: a plugin runs in the
1438
+ * daemon's process and can already read the clock. What the door adds is the daemon's answer
1439
+ * for the USER's zone, and a label for the case where that answer is unknown.
1440
+ *
1441
+ * This is a leaf module. It lives beside both target SDKs (rather than inside `kay-app`) so the
1442
+ * `kay.agent` SDK can expose the same door without importing the `kay.app` surface, which pulls
1443
+ * that surface into every agent-facing bundle.
1444
+ */
1445
+ /** One reading of the daemon's clock and the user's time zone. Each call returns a new object. */
1446
+ declare interface KayAppTimeSnapshot {
1447
+ /** The daemon's clock when the snapshot was read: milliseconds since the Unix epoch. */
1448
+ readonly nowMs: number;
1449
+ /**
1450
+ * The time zone the user is in, as an IANA id in the spelling ICU resolves (`America/New_York`).
1451
+ * One zone can have two ids, and ICU picks one: `Asia/Kolkata` comes back as `Asia/Calcutta`. So
1452
+ * never compare zone ids with `===`. A first-party plugin compares with `sameZone` from
1453
+ * `@kay/time`. A plugin that imports only this SDK and node builtins resolves each id with
1454
+ * `new Intl.DateTimeFormat("en-US", { timeZone: id }).resolvedOptions().timeZone` and compares the
1455
+ * results. Pass `zone` as the `timeZone` of `Intl.DateTimeFormat` or `toLocaleString` to format a
1456
+ * time for the user.
1457
+ *
1458
+ * When `zoneSource` is `utc-fallback` the zone is unknown, and this value is `UTC` only because a
1459
+ * value is required.
1460
+ */
1461
+ readonly zone: string;
1462
+ /**
1463
+ * How the daemon found `zone`.
1464
+ * - `host`: the daemon runs on the user's machine, and `zone` is the zone the daemon process
1465
+ * runs in, read for this call: the process's `TZ` when that names a zone, otherwise the
1466
+ * operating system's zone.
1467
+ * - `utc-fallback`: the daemon does not know the user's zone. It runs on another machine (a remote
1468
+ * daemon), or the machine's zone could not be read or is not an IANA id. `zone` is `UTC`, which
1469
+ * here is a placeholder and not a fact about the user: do not show it to the user as their zone.
1470
+ */
1471
+ readonly zoneSource: "host" | "utc-fallback";
1472
+ }
1473
+
1391
1474
  declare type KnownApi = "openai-completions" | "mistral-conversations" | "openai-responses" | "azure-openai-responses" | "openai-codex-responses" | "anthropic-messages" | "bedrock-converse-stream" | "google-generative-ai" | "google-vertex" | "pi-messages";
1392
1475
 
1393
1476
  declare type KnownProvider = "amazon-bedrock" | "ant-ling" | "anthropic" | "google" | "google-vertex" | "openai" | "azure-openai-responses" | "openai-codex" | "radius" | "typesafe" | "nvidia" | "deepseek" | "github-copilot" | "xai" | "groq" | "cerebras" | "openrouter" | "vercel-ai-gateway" | "zai" | "zai-coding-cn" | "mistral" | "minimax" | "minimax-cn" | "moonshotai" | "moonshotai-cn" | "huggingface" | "fireworks" | "together" | "baseten" | "opencode" | "opencode-go" | "kimi-coding" | "meta" | "cloudflare-workers-ai" | "cloudflare-ai-gateway" | "qwen-token-plan" | "qwen-token-plan-cn" | "qwen-token-plan-individual" | "xiaomi" | "xiaomi-token-plan-cn" | "xiaomi-token-plan-ams" | "xiaomi-token-plan-sgp";
@@ -3045,8 +3128,9 @@ declare interface UriModule {
3045
3128
  /** Pure route/payload validation applied before a consequential permission ask. */
3046
3129
  readonly preflight?: UriOperationPreflightPolicy;
3047
3130
  /**
3048
- * Structured facts naming the WHOLE consequence of this call, or `undefined` when
3049
- * the route cannot honestly be described on a one-line approval surface. The module
3131
+ * Structured facts for approval surfaces, or `undefined` when unavailable.
3132
+ * A one-line description must name the whole consequence; `preview` instead
3133
+ * supplies expanded, literal-text detail and suppresses one-line approval. The module
3050
3134
  * owns the static action phrase, target resolution, payload decoding and declaration
3051
3135
  * of covered payload fields and query parameters. Unknown fields cost the summary.
3052
3136
  * The host owns framing, the whole-quote fit (no elision) and display-safety admission.
@@ -3204,6 +3288,18 @@ declare interface UriOperationDescription {
3204
3288
  readonly target: string;
3205
3289
  /** Model-authored content; the host flattens, budgets, elides and bounds it. */
3206
3290
  readonly quote?: string;
3291
+ /** Full, render-only detail. Hosts must not turn a rich preview into one-line approval. */
3292
+ readonly preview?: {
3293
+ readonly title: string;
3294
+ readonly description?: string;
3295
+ readonly fields?: readonly {
3296
+ readonly label: string;
3297
+ readonly value: string;
3298
+ readonly oldValue?: string;
3299
+ }[];
3300
+ };
3301
+ /** True only when every displayed fact comes from the input, independent of the executing account. */
3302
+ readonly accountIndependent?: true;
3207
3303
  /** What this description accounts for; the host refuses undeclared input. */
3208
3304
  readonly accounts: {
3209
3305
  /** The payload actually read by the module, after its own decoding. */
@@ -185,7 +185,13 @@ function fauxKayAgentCapabilities() {
185
185
  // Accepts and drops, like the registrations above: the real `publish` never throws, so a
186
186
  // rejecting double would fail tests for a reason production never has. A test asserting what
187
187
  // its plugin publishes overrides this handle after the spread.
188
- events: { publish: () => void 0 }
188
+ events: { publish: () => void 0 },
189
+ // A test double has no user-time port, so it answers the zone as unknown — the same contract
190
+ // the real capability keeps for a zone the host cannot read. A test that exercises a plugin's
191
+ // zone-of-record path overrides this handle after the spread.
192
+ time: {
193
+ snapshot: () => ({ nowMs: 0, zone: "UTC", zoneSource: "utc-fallback" })
194
+ }
189
195
  };
190
196
  }
191
197
  function defaultTestToolContext() {
@@ -1620,6 +1620,14 @@ export declare interface KayAgentPluginApi {
1620
1620
  * guard it with `if (api.withJev !== undefined)`.
1621
1621
  */
1622
1622
  readonly withJev: WithJev;
1623
+ /**
1624
+ * The daemon's clock and the time zone the user is in, read together on each `snapshot()` call.
1625
+ * Always present and ungated: it confers no authority, since a plugin runs in the daemon's
1626
+ * process and can read the clock itself. Read the user's zone here, never from `Intl` or
1627
+ * `process.env.TZ`, and render user-facing times in it rather than an event's own zone. This is
1628
+ * the same zone-of-record port the session stamp uses; see {@link KayAppTime.snapshot}.
1629
+ */
1630
+ readonly time: KayAppTime;
1623
1631
  /**
1624
1632
  * Publish the plugin's declared events, for example so a default notification rule can tell the
1625
1633
  * user what the agent just did. Always present, like {@link withJev}: there is nothing to
@@ -1863,6 +1871,81 @@ export declare interface KayAgentWhiteboardUpdateInput {
1863
1871
  readonly title?: string;
1864
1872
  }
1865
1873
 
1874
+ /**
1875
+ * `app.time` / `api.time`: host facts about time. Added to `kay.app` in SDK 0.12.1 and to
1876
+ * `kay.agent` with the calendar zone-of-record work: a plugin that calls it declares
1877
+ * `compatibility.sdk` `^0.12.1`, so an older Bonsai refuses a directory or repository plugin at
1878
+ * load instead of the call failing.
1879
+ */
1880
+ declare interface KayAppTime {
1881
+ /**
1882
+ * Reads the daemon's clock and the user's time zone now.
1883
+ *
1884
+ * Every call asks the host again and returns a new object; nothing is cached. Call it where you
1885
+ * need the value (inside a tool, a handler or a workflow step), not inside `activate()`: a value
1886
+ * read there is as old as the activation, and the host can run a bundled plugin's `activate()`
1887
+ * against a recording api that has no `time`, so a read there throws. The exception is a
1888
+ * directory plugin that declares a workflow with a cron `timezone`: `workflows.define` is legal
1889
+ * only during `activate()`, so it reads the zone there, and the schedule then keeps that zone.
1890
+ * Keep the result for as long as you use it, and not across a long wait (a sleep, a timer, a
1891
+ * queued run): the zone can change while you wait. On a daemon that runs on the user's machine,
1892
+ * a change of the process's `TZ` shows in the next call. A `TZ` that names a zone wins over the
1893
+ * operating system's zone, so a change of the operating-system zone does not show while the
1894
+ * process has one. When `TZ` is unset or does not name a zone, a change of the operating-system
1895
+ * zone is meant to show, but none has been seen on a real machine yet.
1896
+ *
1897
+ * A zone the host cannot determine is an answer (`zoneSource: "utc-fallback"`), not an error.
1898
+ *
1899
+ * Use this to find the zone, not `Intl.DateTimeFormat().resolvedOptions().timeZone`, which can
1900
+ * keep a zone the runtime read earlier, or `process.env.TZ`, which is often unset or not a zone.
1901
+ * Neither says that the zone is unknown.
1902
+ *
1903
+ * It does not use `this`, so it is safe to destructure.
1904
+ */
1905
+ readonly snapshot: () => KayAppTimeSnapshot;
1906
+ }
1907
+
1908
+ /**
1909
+ * The plugin-facing time contract: the daemon's clock and the time zone the user is in,
1910
+ * read together in one call.
1911
+ *
1912
+ * Ungated and always present, like `app.paths`. It confers no authority: a plugin runs in the
1913
+ * daemon's process and can already read the clock. What the door adds is the daemon's answer
1914
+ * for the USER's zone, and a label for the case where that answer is unknown.
1915
+ *
1916
+ * This is a leaf module. It lives beside both target SDKs (rather than inside `kay-app`) so the
1917
+ * `kay.agent` SDK can expose the same door without importing the `kay.app` surface, which pulls
1918
+ * that surface into every agent-facing bundle.
1919
+ */
1920
+ /** One reading of the daemon's clock and the user's time zone. Each call returns a new object. */
1921
+ declare interface KayAppTimeSnapshot {
1922
+ /** The daemon's clock when the snapshot was read: milliseconds since the Unix epoch. */
1923
+ readonly nowMs: number;
1924
+ /**
1925
+ * The time zone the user is in, as an IANA id in the spelling ICU resolves (`America/New_York`).
1926
+ * One zone can have two ids, and ICU picks one: `Asia/Kolkata` comes back as `Asia/Calcutta`. So
1927
+ * never compare zone ids with `===`. A first-party plugin compares with `sameZone` from
1928
+ * `@kay/time`. A plugin that imports only this SDK and node builtins resolves each id with
1929
+ * `new Intl.DateTimeFormat("en-US", { timeZone: id }).resolvedOptions().timeZone` and compares the
1930
+ * results. Pass `zone` as the `timeZone` of `Intl.DateTimeFormat` or `toLocaleString` to format a
1931
+ * time for the user.
1932
+ *
1933
+ * When `zoneSource` is `utc-fallback` the zone is unknown, and this value is `UTC` only because a
1934
+ * value is required.
1935
+ */
1936
+ readonly zone: string;
1937
+ /**
1938
+ * How the daemon found `zone`.
1939
+ * - `host`: the daemon runs on the user's machine, and `zone` is the zone the daemon process
1940
+ * runs in, read for this call: the process's `TZ` when that names a zone, otherwise the
1941
+ * operating system's zone.
1942
+ * - `utc-fallback`: the daemon does not know the user's zone. It runs on another machine (a remote
1943
+ * daemon), or the machine's zone could not be read or is not an IANA id. `zone` is `UTC`, which
1944
+ * here is a placeholder and not a fact about the user: do not show it to the user as their zone.
1945
+ */
1946
+ readonly zoneSource: "host" | "utc-fallback";
1947
+ }
1948
+
1866
1949
  declare const KayPluginManifestV1: z.ZodObject<{
1867
1950
  protocol: z.ZodLiteral<"kay.plugin.v1">;
1868
1951
  id: z.ZodString;
@@ -2188,6 +2271,9 @@ declare const KayPluginManifestV1: z.ZodObject<{
2188
2271
  kind: z.ZodLiteral<"working-document">;
2189
2272
  }, z.core.$strict>, z.ZodObject<{
2190
2273
  kind: z.ZodLiteral<"browser-pane">;
2274
+ }, z.core.$strict>, z.ZodObject<{
2275
+ kind: z.ZodLiteral<"page">;
2276
+ pageId: z.ZodString;
2191
2277
  }, z.core.$strict>], "kind">;
2192
2278
  variants: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
2193
2279
  resourceKind: z.ZodString;
@@ -4889,8 +4975,9 @@ export declare interface UriModule {
4889
4975
  /** Pure route/payload validation applied before a consequential permission ask. */
4890
4976
  readonly preflight?: UriOperationPreflightPolicy;
4891
4977
  /**
4892
- * Structured facts naming the WHOLE consequence of this call, or `undefined` when
4893
- * the route cannot honestly be described on a one-line approval surface. The module
4978
+ * Structured facts for approval surfaces, or `undefined` when unavailable.
4979
+ * A one-line description must name the whole consequence; `preview` instead
4980
+ * supplies expanded, literal-text detail and suppresses one-line approval. The module
4894
4981
  * owns the static action phrase, target resolution, payload decoding and declaration
4895
4982
  * of covered payload fields and query parameters. Unknown fields cost the summary.
4896
4983
  * The host owns framing, the whole-quote fit (no elision) and display-safety admission.
@@ -5065,6 +5152,18 @@ export declare interface UriOperationDescription {
5065
5152
  readonly target: string;
5066
5153
  /** Model-authored content; the host flattens, budgets, elides and bounds it. */
5067
5154
  readonly quote?: string;
5155
+ /** Full, render-only detail. Hosts must not turn a rich preview into one-line approval. */
5156
+ readonly preview?: {
5157
+ readonly title: string;
5158
+ readonly description?: string;
5159
+ readonly fields?: readonly {
5160
+ readonly label: string;
5161
+ readonly value: string;
5162
+ readonly oldValue?: string;
5163
+ }[];
5164
+ };
5165
+ /** True only when every displayed fact comes from the input, independent of the executing account. */
5166
+ readonly accountIndependent?: true;
5068
5167
  /** What this description accounts for; the host refuses undeclared input. */
5069
5168
  readonly accounts: {
5070
5169
  /** The payload actually read by the module, after its own decoding. */
package/dist/kay-app.d.ts CHANGED
@@ -3890,7 +3890,8 @@ export declare interface KayAppStorage {
3890
3890
  export declare type KayAppThinkingLevel = "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max";
3891
3891
 
3892
3892
  /**
3893
- * `app.time`: host facts about time. Added in SDK 0.12.1: a plugin that calls it declares
3893
+ * `app.time` / `api.time`: host facts about time. Added to `kay.app` in SDK 0.12.1 and to
3894
+ * `kay.agent` with the calendar zone-of-record work: a plugin that calls it declares
3894
3895
  * `compatibility.sdk` `^0.12.1`, so an older Bonsai refuses a directory or repository plugin at
3895
3896
  * load instead of the call failing.
3896
3897
  */
@@ -3923,12 +3924,16 @@ export declare interface KayAppTime {
3923
3924
  }
3924
3925
 
3925
3926
  /**
3926
- * The plugin-facing `kay.app` time contract: the daemon's clock and the time zone the user is in,
3927
+ * The plugin-facing time contract: the daemon's clock and the time zone the user is in,
3927
3928
  * read together in one call.
3928
3929
  *
3929
3930
  * Ungated and always present, like `app.paths`. It confers no authority: a plugin runs in the
3930
- * daemon's process and can already read the clock. What `app.time` adds is the daemon's answer
3931
+ * daemon's process and can already read the clock. What the door adds is the daemon's answer
3931
3932
  * for the USER's zone, and a label for the case where that answer is unknown.
3933
+ *
3934
+ * This is a leaf module. It lives beside both target SDKs (rather than inside `kay-app`) so the
3935
+ * `kay.agent` SDK can expose the same door without importing the `kay.app` surface, which pulls
3936
+ * that surface into every agent-facing bundle.
3932
3937
  */
3933
3938
  /** One reading of the daemon's clock and the user's time zone. Each call returns a new object. */
3934
3939
  export declare interface KayAppTimeSnapshot {
@@ -5122,7 +5127,7 @@ declare interface ResolveTokenSuccess {
5122
5127
  * failure.
5123
5128
  */
5124
5129
  /** Provider-neutral factual category for a failed model run. */
5125
- declare type RunFailureReason = "auth" | "budget_exceeded" | "content_blocked" | "context_overflow" | "invalid_request" | "model_unavailable" | "network" | "overloaded" | "quota_exhausted" | "rate_limit" | "safeguard_block" | "seat_unpaid" | "server_error" | "stream_interrupted" | "timeout" | "unknown";
5130
+ declare type RunFailureReason = "auth" | "content_blocked" | "context_overflow" | "invalid_request" | "model_unavailable" | "network" | "overloaded" | "quota_exhausted" | "rate_limit" | "safeguard_block" | "seat_unpaid" | "server_error" | "stream_interrupted" | "timeout" | "unknown";
5126
5131
 
5127
5132
  declare type SchemaInput = z.ZodType | JsonSchema;
5128
5133
 
@@ -6388,6 +6393,18 @@ declare interface UriOperationDescription {
6388
6393
  readonly target: string;
6389
6394
  /** Model-authored content; the host flattens, budgets, elides and bounds it. */
6390
6395
  readonly quote?: string;
6396
+ /** Full, render-only detail. Hosts must not turn a rich preview into one-line approval. */
6397
+ readonly preview?: {
6398
+ readonly title: string;
6399
+ readonly description?: string;
6400
+ readonly fields?: readonly {
6401
+ readonly label: string;
6402
+ readonly value: string;
6403
+ readonly oldValue?: string;
6404
+ }[];
6405
+ };
6406
+ /** True only when every displayed fact comes from the input, independent of the executing account. */
6407
+ readonly accountIndependent?: true;
6391
6408
  /** What this description accounts for; the host refuses undeclared input. */
6392
6409
  readonly accounts: {
6393
6410
  /** The payload actually read by the module, after its own decoding. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yaklabs-ai/plugin-sdk",
3
- "version": "0.12.2",
3
+ "version": "0.12.3",
4
4
  "description": "The Kay plugin SDK — types and helpers for authoring a Kay directory plugin.",
5
5
  "author": "YakLabs",
6
6
  "type": "module",