@frockbot/applet-sdk 0.7.108 → 0.7.110

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frockbot/applet-sdk",
3
- "version": "0.7.108",
3
+ "version": "0.7.110",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Authoring SDK for FrockBot Applets: schema-first Durable Object server, TanStack DB client, component kit, linter, and the build pipeline.",
package/plugin/index.d.ts CHANGED
@@ -261,6 +261,34 @@ export interface PluginContext {
261
261
  readonly connection?: (
262
262
  connectionId: string,
263
263
  ) => Promise<ConnectionLease | CapabilityFailure>;
264
+ /**
265
+ * The `http` grant, second half: the deployment's own sender, sending for
266
+ * this Bot. The Plugin holds no credential and names no provider; a
267
+ * deployment that has bound no sender answers unavailable.
268
+ */
269
+ readonly email?: (request: {
270
+ /**
271
+ * The Approval whose decision authorizes this send. The kernel refuses a
272
+ * send whose Approval is missing, undecided, denied, expired or already
273
+ * spent, so one decision sends at most one message.
274
+ */
275
+ approvalId: string;
276
+ /**
277
+ * The Card that decision was given on. The Approval is bound to the
278
+ * surface and to the values it was showing, so a send whose message is
279
+ * not the one that was approved is refused.
280
+ */
281
+ surfaceId: string;
282
+ to: string[];
283
+ cc?: string[];
284
+ subject: string;
285
+ body: string;
286
+ /** The `Message-Id` this answers, when it answers one. */
287
+ inReplyTo?: string;
288
+ }) => Promise<
289
+ | { status: "sent"; messageId: string; undelivered?: string[] }
290
+ | CapabilityFailure
291
+ >;
264
292
  /** The `schedule` grant: a durable Routine operation attributed to this call. */
265
293
  readonly schedule?: (request: {
266
294
  callId: string;
@@ -429,6 +457,119 @@ export type PluginView = (
429
457
  | undefined
430
458
  | void;
431
459
 
460
+ /**
461
+ * One A2UI message a Card is made of: the envelope plus exactly one of
462
+ * `createSurface`, `updateComponents`, `updateDataModel` or `deleteSurface`.
463
+ * The kernel decodes and bounds it, so it is carried loosely here.
464
+ */
465
+ export interface CardMessage {
466
+ version: "v1.0";
467
+ [key: string]: unknown;
468
+ }
469
+
470
+ /** What a card's `render` is handed: the surface the kernel minted and the Bot's values. */
471
+ export interface PluginCardRender {
472
+ /** The kernel's own surface id. A Plugin never chooses one. */
473
+ surfaceId: string;
474
+ /** The values the Bot sent, already validated against the card's `dataSchema`. */
475
+ data: { [key: string]: unknown };
476
+ }
477
+
478
+ /** What a card action handler is handed: the press, as the person made it. */
479
+ export interface PluginCardPress {
480
+ /** The card this press is on — the one the pressed surface was drawn from. */
481
+ cardId: string;
482
+ surfaceId: string;
483
+ /** The `<action>` half of the `plugin/<pluginId>/<action>` that was pressed. */
484
+ action: string;
485
+ context?: { [key: string]: unknown };
486
+ /** The surface's data model, when the surface was created asking for it. */
487
+ dataModel?: { [key: string]: unknown };
488
+ /**
489
+ * The Card's data model as the kernel stores it: what the surface is made
490
+ * of, rather than what the client sent back. A handler reads the state of
491
+ * its own card here instead of keeping a second copy keyed by surface id.
492
+ */
493
+ record?: { [key: string]: unknown };
494
+ }
495
+
496
+ /** A card handler's refusal: the Card is left exactly as it was. */
497
+ export interface PluginCardDrop {
498
+ drop: true;
499
+ reason?: string;
500
+ }
501
+
502
+ /**
503
+ * What a card handler answers with: the messages the kernel folds into the
504
+ * Card, on their own or with `input` — one line for the Bot's next Turn, the
505
+ * only thing a press may say to the Bot rather than to the card. `render`
506
+ * never carries `input`; a draw is not a press.
507
+ */
508
+ export type PluginCardAnswer =
509
+ | CardMessage[]
510
+ | { messages: CardMessage[]; input?: string }
511
+ | PluginCardDrop
512
+ | undefined
513
+ | void;
514
+
515
+ /**
516
+ * What a card's `render` answers with. The same messages a press answers
517
+ * with, and beside them `covers`: the canonical values a decision on this
518
+ * card would authorize. The Plugin states them because the Plugin, not the
519
+ * model, decides what the card draws — a redraw that ignores the Bot's values
520
+ * and shows the draft it is holding covers that draft. A draw that puts an
521
+ * `ApprovalActions` on the card and declares no `covers`, or no `decision`,
522
+ * is refused, so a decision bound to nothing — or asked in no words — cannot
523
+ * exist.
524
+ */
525
+ export type PluginCardDraw =
526
+ | CardMessage[]
527
+ | {
528
+ messages: CardMessage[];
529
+ covers?: { [key: string]: unknown };
530
+ decision?: PluginCardDecision;
531
+ }
532
+ | PluginCardDrop
533
+ | undefined
534
+ | void;
535
+
536
+ /**
537
+ * The words the decision a card asks for is recorded with. They are stated
538
+ * here rather than on the `ApprovalActions` component because the Frock
539
+ * catalog allows that component an `approvalId` and its two labels and
540
+ * nothing else: the host draws the labels, the kernel records the Approval
541
+ * with these, and a draw that asks for a decision and states none is refused.
542
+ */
543
+ export interface PluginCardDecision {
544
+ /** What the person is asked, in their words: "Send an email to …". */
545
+ action: string;
546
+ /** What getting it wrong costs. */
547
+ risk: "low" | "medium" | "high";
548
+ /** Why, when the action does not say. */
549
+ rationale?: string;
550
+ }
551
+
552
+ /**
553
+ * One Card the Plugin draws (ADR 0030). `render` composes the surface from
554
+ * the catalogs the client compiled in; `actions` are the handlers behind the
555
+ * names the surface's components raise. An action name is the Plugin's, not
556
+ * one card's — the namespace is `plugin/<pluginId>/<action>` — so two cards
557
+ * may not declare the same one.
558
+ */
559
+ export interface PluginCard {
560
+ render(
561
+ payload: PluginCardRender,
562
+ ctx: PluginContext,
563
+ ): Promise<PluginCardDraw> | PluginCardDraw;
564
+ actions?: Record<
565
+ string,
566
+ (
567
+ press: PluginCardPress,
568
+ ctx: PluginContext,
569
+ ) => Promise<PluginCardAnswer> | PluginCardAnswer
570
+ >;
571
+ }
572
+
432
573
  /**
433
574
  * A tool call's answer. A string is handed to the Bot as it is; anything else
434
575
  * is JSON-serialized. Throw to answer with an error the Bot can read — the
@@ -464,4 +605,10 @@ export interface PluginModule {
464
605
  * with slot `settings.sections`: a section drawn on this Plugin's card.
465
606
  */
466
607
  views?: Record<string, PluginView>;
608
+ /**
609
+ * One entry per card id declared under `cards` in `plugin.json`. The Bot
610
+ * calls the card's tool with the values, the kernel validates them against
611
+ * the card's `dataSchema` and `render` answers with the surface.
612
+ */
613
+ cards?: Record<string, PluginCard>;
467
614
  }
@@ -2,7 +2,7 @@
2
2
  "id": "__PLUGIN_ID__",
3
3
  "displayName": "__PLUGIN_NAME__",
4
4
  "version": "1",
5
- "contractVersion": 4,
5
+ "contractVersion": 5,
6
6
  "tools": [
7
7
  {
8
8
  "name": "note_count",
@@ -52,6 +52,8 @@ export interface PluginDescriptionV1 {
52
52
  services: string[];
53
53
  triggers: string[];
54
54
  views: string[];
55
+ /** The cards the module draws, by id (ADR 0030). */
56
+ cards: string[];
55
57
  }
56
58
 
57
59
  export interface PluginBuildManifestV1 extends PluginDescriptionV1 {
@@ -277,6 +279,20 @@ function names(value, label) {
277
279
  });
278
280
  }
279
281
 
282
+ function cardNames(value) {
283
+ if (value === undefined) return [];
284
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
285
+ throw new Error('"cards" must be an object');
286
+ }
287
+ return Object.keys(value).map(function (cardId) {
288
+ var card = value[cardId];
289
+ if (!card || typeof card.render !== "function") {
290
+ throw new Error('card "' + cardId + '" must export a render function');
291
+ }
292
+ return cardId;
293
+ });
294
+ }
295
+
280
296
  function describe() {
281
297
  if (!Array.isArray(plugin.tools)) {
282
298
  throw new Error('the module must export a "tools" array');
@@ -303,6 +319,7 @@ function describe() {
303
319
  services: names(plugin.services, "services"),
304
320
  triggers: names(plugin.triggers, "triggers"),
305
321
  views: names(plugin.views, "views"),
322
+ cards: cardNames(plugin.cards),
306
323
  };
307
324
  }
308
325
 
@@ -396,6 +413,7 @@ function validateDescription(input: PluginDescriptionV1): PluginDescriptionV1 {
396
413
  services: [...input.services],
397
414
  triggers: [...input.triggers],
398
415
  views: [...input.views],
416
+ cards: [...(input.cards ?? [])],
399
417
  };
400
418
  }
401
419