@yaklabs-ai/plugin-sdk 0.30.0 → 0.31.0

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.30.0"
16
+ "@bonsai/plugin-sdk": "npm:@yaklabs-ai/plugin-sdk@^0.31.0"
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.30.0" },
65
+ "compatibility": { "sdk": "^0.31.0" },
66
66
  "targets": {
67
67
  "kay": {
68
68
  "agent": {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "package": "@yaklabs-ai/plugin-sdk",
4
- "version": "0.30.0",
4
+ "version": "0.31.0",
5
5
  "target": "npm",
6
6
  "firstPartyLicense": "MIT",
7
7
  "bundled": [
@@ -1722,39 +1722,48 @@ declare type GrammarVariants = Partial<Record<GrammarFormat, string>>;
1722
1722
  */
1723
1723
  declare type HerdHarnessEvent = {
1724
1724
  readonly type: "agent_start";
1725
+ readonly kernelRunId?: string;
1725
1726
  } | {
1726
1727
  readonly type: "agent_end";
1727
1728
  readonly messages: readonly AgentMessage[];
1728
1729
  } | {
1729
1730
  readonly type: "turn_start";
1731
+ readonly kernelRunId?: string;
1730
1732
  } | {
1731
1733
  readonly type: "turn_end";
1734
+ readonly kernelRunId?: string;
1732
1735
  readonly message: AssistantMessage;
1733
1736
  readonly toolResults: readonly ToolResultMessage[];
1734
1737
  } | {
1735
1738
  readonly type: "message_start";
1739
+ readonly kernelRunId?: string;
1736
1740
  readonly message: AgentMessage;
1737
1741
  } | {
1738
1742
  readonly type: "message_update";
1743
+ readonly kernelRunId?: string;
1739
1744
  readonly message: AgentMessage;
1740
1745
  readonly assistantMessageEvent: AssistantMessageEvent;
1741
1746
  } | {
1742
1747
  readonly type: "message_end";
1748
+ readonly kernelRunId?: string;
1743
1749
  readonly message: AgentMessage;
1744
1750
  readonly entryId?: string;
1745
1751
  } | {
1746
1752
  readonly type: "tool_execution_start";
1753
+ readonly kernelRunId?: string;
1747
1754
  readonly toolCallId: string;
1748
1755
  readonly toolName: string;
1749
1756
  readonly args: unknown;
1750
1757
  } | {
1751
1758
  readonly type: "tool_execution_update";
1759
+ readonly kernelRunId?: string;
1752
1760
  readonly toolCallId: string;
1753
1761
  readonly toolName: string;
1754
1762
  readonly args: unknown;
1755
1763
  readonly partialResult: unknown;
1756
1764
  } | {
1757
1765
  readonly type: "tool_execution_end";
1766
+ readonly kernelRunId?: string;
1758
1767
  readonly toolCallId: string;
1759
1768
  readonly toolName: string;
1760
1769
  readonly result: unknown;
@@ -396,7 +396,7 @@ var boundedDeltaPlacement = z4.json().refine((delta) => {
396
396
  var HerdSessionProjection = z4.discriminatedUnion("type", [
397
397
  z4.strictObject({
398
398
  type: z4.literal("herd.message.delta"),
399
- runId: z4.string().min(1).optional(),
399
+ kernelRunId: z4.string().min(1).optional(),
400
400
  messageId: z4.string().min(1).optional(),
401
401
  timestamp: z4.iso.datetime(),
402
402
  delta: boundedDeltaPlacement
@@ -671,7 +671,17 @@ var TOOL_CALL_FAILURE_CODES = [
671
671
  "edit.text_not_unique",
672
672
  "edit.empty_old_text",
673
673
  "edit.overlapping_edits",
674
- "edit.no_change"
674
+ "edit.no_change",
675
+ // Situation codes for two lumped codes above. Each reads exactly as its
676
+ // parent (same category, retryable and action); the parent stays for every
677
+ // case without the evidence. An app older than these codes rejects a stored
678
+ // record that carries one.
679
+ "uri.not_connected.pick_account",
680
+ "uri.not_connected.account_not_allowed",
681
+ "uri.not_connected.sign_in_expired",
682
+ "tool.blocked.by_user",
683
+ "tool.blocked.by_rule",
684
+ "tool.blocked.by_bonsai"
675
685
  ];
676
686
  var count = z5.number().int().nonnegative();
677
687
  var toolCallFailureDetailsSchema = z5.strictObject({
@@ -2357,8 +2357,13 @@ declare const KAY_PLUGIN_PROTOCOL_VERSION: "kay.plugin.v1";
2357
2357
  * supplies what a run does: each run is handed the workflow's accounts and tokens, so the code
2358
2358
  * runs the same in the daemon and in the plugin host. Additive: `app.workflows.define` stays, and
2359
2359
  * a plugin that declares a workflow declares ^0.30.0.
2360
+ *
2361
+ * 0.31.0 lets a plugin declare its rail entries as data (#15896). `railEntries` under `kay.ui`
2362
+ * `contributes` names each entry's title, page, shortcut and icon file. It replaces `activityRail` on a
2363
+ * UI app, which is stripped from a manifest that still carries it, so that manifest loses only the
2364
+ * rail button. A plugin that declares a rail entry declares ^0.31.0.
2360
2365
  */
2361
- declare const KAY_PLUGIN_SDK_VERSION: "0.30.0";
2366
+ declare const KAY_PLUGIN_SDK_VERSION: "0.31.0";
2362
2367
 
2363
2368
  declare const KAY_PLUGIN_TARGET_NAMES: readonly ["kay.agent", "kay.app", "kay.ui"];
2364
2369
 
@@ -7611,7 +7616,7 @@ declare type TOmitInstantiate<Context extends TProperties, State extends TState,
7611
7616
  * classification (the engine records the rejection on an unexplained failure),
7612
7617
  * and a stored record with one reads as none.
7613
7618
  */
7614
- declare const TOOL_CALL_FAILURE_CODES: readonly ["tool.invalid_input", "tool.blocked", "tool.cancelled", "tool.timeout", "tool.not_found", "tool.unavailable", "tool.execution_failed", "tool.auth_unavailable", "tool.replies_owed", "provider.http", "provider.api", "provider.transport", "uri.not_found", "uri.unsupported_operation", "uri.not_connected", "uri.auth_unavailable", "permission.denied", "edit.empty_edits", "edit.text_not_found", "edit.text_not_unique", "edit.empty_old_text", "edit.overlapping_edits", "edit.no_change"];
7619
+ declare const TOOL_CALL_FAILURE_CODES: readonly ["tool.invalid_input", "tool.blocked", "tool.cancelled", "tool.timeout", "tool.not_found", "tool.unavailable", "tool.execution_failed", "tool.auth_unavailable", "tool.replies_owed", "provider.http", "provider.api", "provider.transport", "uri.not_found", "uri.unsupported_operation", "uri.not_connected", "uri.auth_unavailable", "permission.denied", "edit.empty_edits", "edit.text_not_found", "edit.text_not_unique", "edit.empty_old_text", "edit.overlapping_edits", "edit.no_change", "uri.not_connected.pick_account", "uri.not_connected.account_not_allowed", "uri.not_connected.sign_in_expired", "tool.blocked.by_user", "tool.blocked.by_rule", "tool.blocked.by_bonsai"];
7615
7620
 
7616
7621
  /**
7617
7622
  * ---
@@ -2411,8 +2411,13 @@ declare const KAY_PLUGIN_PROTOCOL_VERSION: "kay.plugin.v1";
2411
2411
  * supplies what a run does: each run is handed the workflow's accounts and tokens, so the code
2412
2412
  * runs the same in the daemon and in the plugin host. Additive: `app.workflows.define` stays, and
2413
2413
  * a plugin that declares a workflow declares ^0.30.0.
2414
+ *
2415
+ * 0.31.0 lets a plugin declare its rail entries as data (#15896). `railEntries` under `kay.ui`
2416
+ * `contributes` names each entry's title, page, shortcut and icon file. It replaces `activityRail` on a
2417
+ * UI app, which is stripped from a manifest that still carries it, so that manifest loses only the
2418
+ * rail button. A plugin that declares a rail entry declares ^0.31.0.
2414
2419
  */
2415
- declare const KAY_PLUGIN_SDK_VERSION: "0.30.0";
2420
+ declare const KAY_PLUGIN_SDK_VERSION: "0.31.0";
2416
2421
 
2417
2422
  declare const KAY_PLUGIN_TARGET_NAMES: readonly ["kay.agent", "kay.app", "kay.ui"];
2418
2423
 
@@ -5654,16 +5659,6 @@ declare const ResolvedPluginContractV1: z.ZodObject<{
5654
5659
  globalId: z.ZodString;
5655
5660
  displayName: z.ZodString;
5656
5661
  contextOnly: z.ZodOptional<z.ZodLiteral<true>>;
5657
- activityRail: z.ZodOptional<z.ZodObject<{
5658
- section: z.ZodOptional<z.ZodEnum<{
5659
- primary: "primary";
5660
- plugins: "plugins";
5661
- }>>;
5662
- order: z.ZodOptional<z.ZodNumber>;
5663
- icon: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
5664
- svg: z.ZodString;
5665
- }, z.core.$strict>]>>;
5666
- }, z.core.$strict>>;
5667
5662
  description: z.ZodOptional<z.ZodString>;
5668
5663
  icon: z.ZodOptional<z.ZodString>;
5669
5664
  runtime: z.ZodLiteral<"kay.react-app.v1">;
@@ -8687,7 +8682,7 @@ declare type TOmitInstantiate<Context extends TProperties, State extends TState,
8687
8682
  * classification (the engine records the rejection on an unexplained failure),
8688
8683
  * and a stored record with one reads as none.
8689
8684
  */
8690
- declare const TOOL_CALL_FAILURE_CODES: readonly ["tool.invalid_input", "tool.blocked", "tool.cancelled", "tool.timeout", "tool.not_found", "tool.unavailable", "tool.execution_failed", "tool.auth_unavailable", "tool.replies_owed", "provider.http", "provider.api", "provider.transport", "uri.not_found", "uri.unsupported_operation", "uri.not_connected", "uri.auth_unavailable", "permission.denied", "edit.empty_edits", "edit.text_not_found", "edit.text_not_unique", "edit.empty_old_text", "edit.overlapping_edits", "edit.no_change"];
8685
+ declare const TOOL_CALL_FAILURE_CODES: readonly ["tool.invalid_input", "tool.blocked", "tool.cancelled", "tool.timeout", "tool.not_found", "tool.unavailable", "tool.execution_failed", "tool.auth_unavailable", "tool.replies_owed", "provider.http", "provider.api", "provider.transport", "uri.not_found", "uri.unsupported_operation", "uri.not_connected", "uri.auth_unavailable", "permission.denied", "edit.empty_edits", "edit.text_not_found", "edit.text_not_unique", "edit.empty_old_text", "edit.overlapping_edits", "edit.no_change", "uri.not_connected.pick_account", "uri.not_connected.account_not_allowed", "uri.not_connected.sign_in_expired", "tool.blocked.by_user", "tool.blocked.by_rule", "tool.blocked.by_bonsai"];
8691
8686
 
8692
8687
  /**
8693
8688
  * ---
@@ -2686,8 +2686,13 @@ declare const KAY_PLUGIN_PROTOCOL_VERSION: "kay.plugin.v1";
2686
2686
  * supplies what a run does: each run is handed the workflow's accounts and tokens, so the code
2687
2687
  * runs the same in the daemon and in the plugin host. Additive: `app.workflows.define` stays, and
2688
2688
  * a plugin that declares a workflow declares ^0.30.0.
2689
+ *
2690
+ * 0.31.0 lets a plugin declare its rail entries as data (#15896). `railEntries` under `kay.ui`
2691
+ * `contributes` names each entry's title, page, shortcut and icon file. It replaces `activityRail` on a
2692
+ * UI app, which is stripped from a manifest that still carries it, so that manifest loses only the
2693
+ * rail button. A plugin that declares a rail entry declares ^0.31.0.
2689
2694
  */
2690
- declare const KAY_PLUGIN_SDK_VERSION: "0.30.0";
2695
+ declare const KAY_PLUGIN_SDK_VERSION: "0.31.0";
2691
2696
 
2692
2697
  declare const KAY_PLUGIN_TARGET_NAMES: readonly ["kay.agent", "kay.app", "kay.ui"];
2693
2698
 
@@ -8822,7 +8827,7 @@ declare type TOmitInstantiate<Context extends TProperties, State extends TState,
8822
8827
  * classification (the engine records the rejection on an unexplained failure),
8823
8828
  * and a stored record with one reads as none.
8824
8829
  */
8825
- declare const TOOL_CALL_FAILURE_CODES: readonly ["tool.invalid_input", "tool.blocked", "tool.cancelled", "tool.timeout", "tool.not_found", "tool.unavailable", "tool.execution_failed", "tool.auth_unavailable", "tool.replies_owed", "provider.http", "provider.api", "provider.transport", "uri.not_found", "uri.unsupported_operation", "uri.not_connected", "uri.auth_unavailable", "permission.denied", "edit.empty_edits", "edit.text_not_found", "edit.text_not_unique", "edit.empty_old_text", "edit.overlapping_edits", "edit.no_change"];
8830
+ declare const TOOL_CALL_FAILURE_CODES: readonly ["tool.invalid_input", "tool.blocked", "tool.cancelled", "tool.timeout", "tool.not_found", "tool.unavailable", "tool.execution_failed", "tool.auth_unavailable", "tool.replies_owed", "provider.http", "provider.api", "provider.transport", "uri.not_found", "uri.unsupported_operation", "uri.not_connected", "uri.auth_unavailable", "permission.denied", "edit.empty_edits", "edit.text_not_found", "edit.text_not_unique", "edit.empty_old_text", "edit.overlapping_edits", "edit.no_change", "uri.not_connected.pick_account", "uri.not_connected.account_not_allowed", "uri.not_connected.sign_in_expired", "tool.blocked.by_user", "tool.blocked.by_rule", "tool.blocked.by_bonsai"];
8826
8831
 
8827
8832
  /**
8828
8833
  * ---
package/dist/kay-app.d.ts CHANGED
@@ -1056,18 +1056,16 @@ declare interface DeferredHandle {
1056
1056
  }
1057
1057
 
1058
1058
  /**
1059
- * Register an ordinary workflow that polls connected accounts with independent JSON cursors.
1059
+ * Supply the run of a described workflow that polls its handed accounts with independent JSON
1060
+ * cursors. The manifest describes the workflow: its name, how often it runs, and which accounts.
1060
1061
  * First polls initialize silently. Failed or skipped accounts retain their cursor; removed
1061
1062
  * accounts are pruned. Publication finishes before replacement state is returned.
1062
1063
  *
1063
- * Each account's credential comes from `app.backgroundAuth` with no named scopes, so the host picks
1064
- * one from the manifest's `background-job` grant. Credential resolution and provider reads share a
1065
- * step so credentials are never step outputs.
1066
- * Poll callbacks must keep secrets out of their returned cursor, events, and summary. Resolution
1067
- * errors fail the run with a sanitized message; provider poll errors isolate the account.
1068
- * Key events at their source: a step can repeat before its result commits or after an engine update.
1064
+ * Poll callbacks must keep secrets out of their returned cursor, events, and summary. A failed poll
1065
+ * or a missing credential stays that account's.
1066
+ * Key events at their source: a run can repeat before its state commits.
1069
1067
  */
1070
- export declare function definePoller<Cursor extends JsonValue>(app: Pick<KayAppPluginApi, "workflows" | "auth" | "backgroundAuth" | "events">, localId: string, options: KayAppPollerOptions<Cursor>): void;
1068
+ export declare function definePoller<Cursor extends JsonValue>(app: Pick<KayAppPluginApi, "implementWorkflow" | "events">, localId: string, options: KayAppPollerOptions<Cursor>): void;
1071
1069
 
1072
1070
  /** Creates a Dependent type */
1073
1071
  declare function Dependent<If extends TSchema, Then extends TSchema, Else extends TSchema>(if_: If, then_: Then, else_: Else, options?: TSchemaOptions): TDependent<If, Then, Else>;
@@ -2643,8 +2641,13 @@ declare const KAY_PLUGIN_PROTOCOL_VERSION: "kay.plugin.v1";
2643
2641
  * supplies what a run does: each run is handed the workflow's accounts and tokens, so the code
2644
2642
  * runs the same in the daemon and in the plugin host. Additive: `app.workflows.define` stays, and
2645
2643
  * a plugin that declares a workflow declares ^0.30.0.
2644
+ *
2645
+ * 0.31.0 lets a plugin declare its rail entries as data (#15896). `railEntries` under `kay.ui`
2646
+ * `contributes` names each entry's title, page, shortcut and icon file. It replaces `activityRail` on a
2647
+ * UI app, which is stripped from a manifest that still carries it, so that manifest loses only the
2648
+ * rail button. A plugin that declares a rail entry declares ^0.31.0.
2646
2649
  */
2647
- declare const KAY_PLUGIN_SDK_VERSION: "0.30.0";
2650
+ declare const KAY_PLUGIN_SDK_VERSION: "0.31.0";
2648
2651
 
2649
2652
  declare const KAY_PLUGIN_TARGET_NAMES: readonly ["kay.agent", "kay.app", "kay.ui"];
2650
2653
 
@@ -4344,9 +4347,12 @@ export declare interface KayAppImageModelInfo {
4344
4347
  export declare interface KayAppKnowledge {
4345
4348
  registerSource(localId: string, source: KayAppKnowledgeSource): KayAppKnowledgeSourceHandle;
4346
4349
  /**
4347
- * Search the daemon's knowledge index within the schemes this plugin owns. Present only when
4348
- * the plugin's `kay.app` target declares the `knowledgeSearch` (`own-schemes`) permission and
4349
- * the daemon composed an index. A handle kept past disable or replacement never returns hits:
4350
+ * Search the daemon's knowledge index within the schemes this plugin owns, and within the URIs
4351
+ * its granted knowledge sources observe (an `output://` prefix while this plugin holds the
4352
+ * outputs grant, which `artifacts.outputs()` requests). A prefix outside both rejects the call;
4353
+ * a hit outside both is dropped; the host re-reads the source grant before and after the search, and a grant lost during the call answers `unavailable`.
4354
+ * Present only when the plugin's `kay.app` target declares the `knowledgeSearch` (`own-schemes`)
4355
+ * permission and the daemon composed an index. A handle kept past disable or replacement never returns hits:
4350
4356
  * the call rejects while the extension is disabled and resolves `unavailable` once a newer
4351
4357
  * activation has replaced it.
4352
4358
  */
@@ -4744,6 +4750,12 @@ export declare type KayAppModelSelection = {
4744
4750
  readonly kind: "tier";
4745
4751
  readonly tierId: KayAppModelTierId;
4746
4752
  readonly provider?: string | undefined;
4753
+ readonly constraints?: KayAppRouteConstraints | undefined;
4754
+ } | {
4755
+ /** One model by its bare id (`gpt-6-astra`), on whichever provider route serves it. */
4756
+ readonly kind: "model";
4757
+ readonly model: string;
4758
+ readonly constraints?: KayAppRouteConstraints | undefined;
4747
4759
  };
4748
4760
 
4749
4761
  /** `app.modelSelection`: registering the policy the daemon consults. */
@@ -5326,12 +5338,6 @@ export declare interface KayAppPollerEvent {
5326
5338
  }
5327
5339
 
5328
5340
  export declare interface KayAppPollerOptions<Cursor extends JsonValue> {
5329
- readonly name: string;
5330
- readonly description: string;
5331
- /** Interval in milliseconds; the workflow engine enforces its scheduling floor. */
5332
- readonly every: number;
5333
- readonly serviceId: string;
5334
- readonly schemeId?: string;
5335
5341
  /** Cursor, events, and summary must contain no credentials or other secrets. */
5336
5342
  readonly poll: (input: {
5337
5343
  readonly account: KayAppAccountSummary;
@@ -5538,6 +5544,16 @@ export declare interface KayAppResolvedCredential {
5538
5544
  readonly fields?: Readonly<Record<string, string>>;
5539
5545
  }
5540
5546
 
5547
+ /** Hard limits on the routes a `tier` or `model` selection may use. */
5548
+ export declare interface KayAppRouteConstraints {
5549
+ /** Only this provider (a family such as `openai` covers its routes). */
5550
+ readonly provider?: string | undefined;
5551
+ /** Only models this vendor made. */
5552
+ readonly vendor?: string | undefined;
5553
+ /** No models this vendor made; an unknown vendor is excluded too. */
5554
+ readonly notVendor?: string | undefined;
5555
+ }
5556
+
5541
5557
  /**
5542
5558
  * What one run's work came to: `{ result: "deferred", reason: "catalog_not_ready" }`. Optional;
5543
5559
  * Bonsai keeps it on the run's record and does not show it to a person.
@@ -8052,8 +8068,8 @@ export declare interface KayAppWhiteboardTypes {
8052
8068
 
8053
8069
  /**
8054
8070
  * One connected account of a declared workflow's service, and its credential for this run. An
8055
- * account that needs signing in again, or holds none of the declared scopes, arrives with that
8056
- * status instead of a token.
8071
+ * account that needs signing in again, or is missing a declared scope, arrives with that status
8072
+ * instead of a token. With no declared scopes, it needs one scope from the plugin's grant.
8057
8073
  */
8058
8074
  export declare interface KayAppWorkflowAccount {
8059
8075
  readonly account: KayAppAccountSummary;
@@ -13580,7 +13596,7 @@ declare interface Tool<TParameters extends TSchema = TSchema> {
13580
13596
  * classification (the engine records the rejection on an unexplained failure),
13581
13597
  * and a stored record with one reads as none.
13582
13598
  */
13583
- declare const TOOL_CALL_FAILURE_CODES: readonly ["tool.invalid_input", "tool.blocked", "tool.cancelled", "tool.timeout", "tool.not_found", "tool.unavailable", "tool.execution_failed", "tool.auth_unavailable", "tool.replies_owed", "provider.http", "provider.api", "provider.transport", "uri.not_found", "uri.unsupported_operation", "uri.not_connected", "uri.auth_unavailable", "permission.denied", "edit.empty_edits", "edit.text_not_found", "edit.text_not_unique", "edit.empty_old_text", "edit.overlapping_edits", "edit.no_change"];
13599
+ declare const TOOL_CALL_FAILURE_CODES: readonly ["tool.invalid_input", "tool.blocked", "tool.cancelled", "tool.timeout", "tool.not_found", "tool.unavailable", "tool.execution_failed", "tool.auth_unavailable", "tool.replies_owed", "provider.http", "provider.api", "provider.transport", "uri.not_found", "uri.unsupported_operation", "uri.not_connected", "uri.auth_unavailable", "permission.denied", "edit.empty_edits", "edit.text_not_found", "edit.text_not_unique", "edit.empty_old_text", "edit.overlapping_edits", "edit.no_change", "uri.not_connected.pick_account", "uri.not_connected.account_not_allowed", "uri.not_connected.sign_in_expired", "tool.blocked.by_user", "tool.blocked.by_rule", "tool.blocked.by_bonsai"];
13584
13600
 
13585
13601
  /**
13586
13602
  * ---
package/dist/kay-app.js CHANGED
@@ -634,9 +634,6 @@ function accountCursors(state) {
634
634
  };
635
635
  });
636
636
  }
637
- function ordinal(left, right) {
638
- return left < right ? -1 : left > right ? 1 : 0;
639
- }
640
637
  function plainText(value, max) {
641
638
  let text = "";
642
639
  for (const character of value) {
@@ -649,125 +646,70 @@ function plainText(value, max) {
649
646
  return text;
650
647
  }
651
648
  function definePoller(app, localId, options) {
652
- const { workflows, auth, backgroundAuth, events } = app;
653
- if (!workflows)
654
- throw new Error("Poller requires workflows");
655
- if (!Number.isSafeInteger(options.every) || options.every <= 0) {
656
- throw new TypeError("Poller interval must be a positive integer in milliseconds");
657
- }
658
- workflows.define(localId, {
659
- name: options.name,
660
- description: options.description,
661
- trigger: {
662
- kind: "time",
663
- schedule: { kind: "every", intervalMs: options.every, anchorAtMs: 0 }
664
- },
665
- async run(ctx) {
666
- if (!auth)
667
- throw new Error("Poller requires account listing");
668
- if (!backgroundAuth)
669
- throw new Error("Poller requires background auth");
670
- const previous = accountCursors(ctx.state);
671
- const accounts = await ctx.runStep(async () => {
672
- const listed = await auth.listAccounts({
673
- serviceId: options.serviceId,
674
- ...options.schemeId === void 0 ? {} : { schemeId: options.schemeId }
675
- });
676
- return [...listed].sort((a, b) => ordinal(a.schemeId, b.schemeId) || ordinal(a.accountId, b.accountId));
677
- }, { name: "list-poller-accounts" });
678
- const next = [];
679
- const details = [];
680
- let published = 0;
681
- let initialized = 0;
682
- let skipped = 0;
683
- let failed = 0;
684
- for (const account of accounts) {
685
- const saved = previous.find((entry) => entry.schemeId === account.schemeId && entry.accountId === account.accountId);
686
- const outcome = await ctx.runStep(async () => {
687
- if (account.status === "needs_reauth" || account.status === "refresh_failed" || account.status === "revoked") {
688
- return {
689
- status: "skipped",
690
- summary: `Sign-in required (${account.status})`
691
- };
692
- }
693
- let credential;
694
- try {
695
- credential = await backgroundAuth.resolveCredential({
696
- serviceId: options.serviceId,
697
- schemeId: account.schemeId,
698
- accountId: account.accountId,
699
- jobId: ctx.runId
700
- });
701
- } catch {
702
- throw new Error("Poller credential resolution failed");
703
- }
704
- if (credential.status !== "resolved") {
705
- return {
706
- status: "skipped",
707
- summary: credential.status === "requires-auth" ? "Sign-in or required scope missing" : "Credential missing"
708
- };
709
- }
710
- try {
711
- const result = await options.poll({
712
- account,
713
- cursor: structuredClone(saved?.cursor),
714
- credential,
715
- runId: ctx.runId
716
- });
717
- return {
718
- status: "polled",
719
- result: {
720
- cursor: result.cursor,
721
- ...saved === void 0 || result.events === void 0 ? {} : { events: result.events },
722
- ...result.summary === void 0 ? {} : { summary: plainText(result.summary, 600) }
723
- }
724
- };
725
- } catch {
726
- return {
727
- status: "failed",
728
- summary: "Provider poll failed; cursor retained"
729
- };
730
- }
731
- }, { name: "poll-account" });
732
- if (outcome.status !== "polled") {
733
- if (saved !== void 0)
734
- next.push(saved);
735
- if (outcome.status === "skipped")
736
- skipped++;
737
- else
738
- failed++;
739
- details.push(`${account.schemeId}/${account.accountId}: ${outcome.summary}`);
740
- continue;
741
- }
742
- await ctx.runStep(async () => {
743
- for (const event of outcome.result.events ?? []) {
744
- events.publish(event.localId, {
745
- ...event.input,
746
- source: { poller: localId, runId: ctx.runId }
747
- });
748
- }
749
- return null;
750
- }, { name: "publish-poller-events" });
751
- published += outcome.result.events?.length ?? 0;
752
- if (saved === void 0)
753
- initialized++;
754
- next.push({
755
- schemeId: account.schemeId,
756
- accountId: account.accountId,
757
- cursor: outcome.result.cursor
649
+ const { events } = app;
650
+ app.implementWorkflow(localId, async ({ state, runId, accounts, runStep }) => {
651
+ const previous = accountCursors(state);
652
+ const next = [];
653
+ const details = [];
654
+ let published = 0;
655
+ let initialized = 0;
656
+ let skipped = 0;
657
+ let failed = 0;
658
+ for (const { account, credential } of accounts) {
659
+ const saved = previous.find((entry) => entry.schemeId === account.schemeId && entry.accountId === account.accountId);
660
+ const name = `${account.schemeId}/${account.accountId}`;
661
+ const keepSaved = (summary) => {
662
+ if (saved !== void 0)
663
+ next.push(saved);
664
+ details.push(`${name}: ${summary}`);
665
+ };
666
+ if (credential.status !== "resolved") {
667
+ skipped++;
668
+ keepSaved(credential.status === "requires-auth" ? "Sign-in or required scope missing" : "Credential missing");
669
+ continue;
670
+ }
671
+ let result;
672
+ try {
673
+ result = await options.poll({
674
+ account,
675
+ cursor: structuredClone(saved?.cursor),
676
+ credential,
677
+ runId
758
678
  });
759
- if (outcome.result.summary)
760
- details.push(`${account.schemeId}/${account.accountId}: ${outcome.result.summary}`);
679
+ } catch {
680
+ failed++;
681
+ keepSaved("Provider poll failed; cursor retained");
682
+ continue;
761
683
  }
762
- return {
763
- state: { accounts: next },
764
- summary: {
765
- headline: plainText(`${published} events; ${initialized} initialized; ${skipped} skipped; ${failed} failed`, 100),
766
- ...details.length === 0 ? {} : { details: plainText(details.join("; "), 600) },
767
- author: "automation"
684
+ const toPublish = saved === void 0 ? [] : result.events ?? [];
685
+ await runStep(async () => {
686
+ for (const event of toPublish) {
687
+ events.publish(event.localId, {
688
+ ...event.input,
689
+ source: { poller: localId, runId }
690
+ });
768
691
  }
769
- };
692
+ });
693
+ published += toPublish.length;
694
+ if (saved === void 0)
695
+ initialized++;
696
+ next.push({
697
+ schemeId: account.schemeId,
698
+ accountId: account.accountId,
699
+ cursor: result.cursor
700
+ });
701
+ if (result.summary) {
702
+ details.push(`${name}: ${plainText(result.summary, 600)}`);
703
+ }
770
704
  }
705
+ return {
706
+ state: { accounts: next },
707
+ summary: {
708
+ headline: plainText(`${published} events; ${initialized} initialized; ${skipped} skipped; ${failed} failed`, 100),
709
+ ...details.length === 0 ? {} : { details: plainText(details.join("; "), 600) },
710
+ author: "automation"
711
+ }
712
+ };
771
713
  });
772
714
  }
773
715
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yaklabs-ai/plugin-sdk",
3
- "version": "0.30.0",
3
+ "version": "0.31.0",
4
4
  "description": "The Kay plugin SDK — types and helpers for authoring a Kay directory plugin.",
5
5
  "author": "YakLabs",
6
6
  "type": "module",
@@ -127,15 +127,18 @@ self-contained module: unlike the daemon targets, `ui.tsx` cannot import sibling
127
127
  plugin UI contributes through `api.slots` (`registerPage`, `registerSidebarItem`,
128
128
  `registerPaneAction`, and the rest of `buildSlots` in
129
129
  `packages/kay-renderer/src/renderer/src/extension-loader/build-extension-api.ts`). A page gets its own
130
- activity-rail button when a `registerSidebarItem({ pageId, placement: "activityRail" })` from the
131
- same plugin points at it.
130
+ activity-rail button when the manifest declares it: `railEntries` under `targets.kay.ui.contributes`,
131
+ with `id`, `title` and `page`, and optionally `icon`, `shortcut`, `order` and `section`. That
132
+ declaration works with no `ui.tsx` at all.
132
133
 
133
134
  **Icons: declare one plugin icon, override only where you mean it.** Set the manifest `icon` (a
134
135
  package-relative SVG) and every surface the plugin contributes inherits it: reference pills, its
135
136
  rail button, and its pages' tabs, pane headers and palette entries (shown as your full-colour mark
136
- on a small white tile). Set `icon` on a `registerSidebarItem` or `registerPage` descriptor only to
137
- show a monochrome glyph there instead (a glyph name such as `"git-branch"`, or `{ svg }`); a page
138
- and its rail item borrow each other's glyph, so one declaration covers the destination. Full
137
+ on a small white tile). Set `icon` on a rail entry to show a single-colour glyph on the rail: a path to an SVG in
138
+ the plugin (`"./assets/rail.svg"`). Only its shape is used, painted in the rail's colour, so colours
139
+ in the file are ignored, and it must be transparent where nothing should draw. A missing file is a
140
+ load diagnostic and the entry falls back to the plugin icon. The page the entry opens wears the same
141
+ icon unless `registerPage`'s `icon` says otherwise. Full
139
142
  precedence: `packages/kay-frontend-sdk/ARCHITECTURE.md`, "Plugin icon".
140
143
 
141
144
  `ui.tsx` has no `node_modules` of its own. The renderer bundles it and rewrites its `react` and