@datalayer/agent-runtimes 1.4.0 → 1.5.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.
Files changed (134) hide show
  1. package/dist-embed/chunks/ChatView-CaBuPTyG.js +32 -0
  2. package/dist-embed/chunks/{DeckComponent-CuRWiARr.js → DeckComponent-cRJJMGgt.js} +1 -1
  3. package/dist-embed/chunks/{DocumentView-BYU2HREd.js → DocumentView-CiqN9ze6.js} +1 -1
  4. package/dist-embed/chunks/{EphemeralDocument-BjFSuQKH.js → EphemeralDocument-BjJfn7J-.js} +1 -1
  5. package/dist-embed/chunks/{ExcalidrawComponent-CFLnMPnf.js → ExcalidrawComponent-BtNXWUN8.js} +1 -1
  6. package/dist-embed/chunks/{ExcalidrawModal-CB9d5Vpk.js → ExcalidrawModal-C6SEKFlD.js} +1 -1
  7. package/dist-embed/chunks/{JupyterLexicalExtension-CYnZrelh.js → JupyterLexicalExtension-BuRLImy5.js} +5 -5
  8. package/dist-embed/chunks/{NotebookToolSurfaces-D62q-lbb.js → NotebookToolSurfaces-ktcGRm7d.js} +1 -1
  9. package/dist-embed/chunks/{Output-2KxoKu5F.js → Output-BscOPLqE.js} +10 -10
  10. package/dist-embed/chunks/{YouTubePlugin-CjODyxZ2.js → YouTubePlugin-xBjNr2Iu.js} +1 -1
  11. package/dist-embed/chunks/{ar-SA-G6X2FPQ2-C-zAYgA-.js → ar-SA-G6X2FPQ2-BB82iKLz.js} +1 -1
  12. package/dist-embed/chunks/{az-AZ-76LH7QW2-BHq5js6U.js → az-AZ-76LH7QW2-DsMRKXlw.js} +1 -1
  13. package/dist-embed/chunks/{bg-BG-XCXSNQG7-BTcNNcTc.js → bg-BG-XCXSNQG7-43Y1kPxa.js} +1 -1
  14. package/dist-embed/chunks/{bn-BD-2XOGV67Q-DXRk27u1.js → bn-BD-2XOGV67Q-CLqPJBll.js} +1 -1
  15. package/dist-embed/chunks/{ca-ES-6MX7JW3Y-D-0J9rqp.js → ca-ES-6MX7JW3Y-CDFGSEh3.js} +1 -1
  16. package/dist-embed/chunks/{cs-CZ-2BRQDIVT-CgN3I-57.js → cs-CZ-2BRQDIVT-ByNB8eZd.js} +1 -1
  17. package/dist-embed/chunks/{da-DK-5WZEPLOC-CHQloSoX.js → da-DK-5WZEPLOC-B7MrVwy8.js} +1 -1
  18. package/dist-embed/chunks/{de-DE-XR44H4JA-C-Hq84pe.js → de-DE-XR44H4JA-CN1cdk94.js} +1 -1
  19. package/dist-embed/chunks/{el-GR-BZB4AONW-MTzGcSsP.js → el-GR-BZB4AONW-DnaGhWD1.js} +1 -1
  20. package/dist-embed/chunks/{es-ES-U4NZUMDT-Thj-4Rwn.js → es-ES-U4NZUMDT-DE1vZzDE.js} +1 -1
  21. package/dist-embed/chunks/{eu-ES-A7QVB2H4-BEEyNQo-.js → eu-ES-A7QVB2H4-DmHB0zpm.js} +1 -1
  22. package/dist-embed/chunks/{fa-IR-HGAKTJCU-CtUGmNBZ.js → fa-IR-HGAKTJCU-CNzE8tJj.js} +1 -1
  23. package/dist-embed/chunks/{fi-FI-Z5N7JZ37-6GMgpkSu.js → fi-FI-Z5N7JZ37-Ca2vTFQC.js} +1 -1
  24. package/dist-embed/chunks/{fr-FR-RHASNOE6-BakNCbxj.js → fr-FR-RHASNOE6-B0yWFX3_.js} +1 -1
  25. package/dist-embed/chunks/{gl-ES-HMX3MZ6V-Cwkb9OPk.js → gl-ES-HMX3MZ6V-2_3FMVJq.js} +1 -1
  26. package/dist-embed/chunks/{he-IL-6SHJWFNN-BaMmvCkr.js → he-IL-6SHJWFNN-BW1lby00.js} +1 -1
  27. package/dist-embed/chunks/{hi-IN-IWLTKZ5I-1aWewhep.js → hi-IN-IWLTKZ5I-DTixfSwB.js} +1 -1
  28. package/dist-embed/chunks/{hu-HU-A5ZG7DT2-BIHI23BQ.js → hu-HU-A5ZG7DT2-DOfYwLWP.js} +1 -1
  29. package/dist-embed/chunks/{id-ID-SAP4L64H-CYpgmx8X.js → id-ID-SAP4L64H-Cwtdtkvg.js} +1 -1
  30. package/dist-embed/chunks/{index-BhSMzuKq.js → index--qMT5k1q.js} +1 -1
  31. package/dist-embed/chunks/{index-b1yzCiTq.js → index-B-UP9DlM.js} +1 -1
  32. package/dist-embed/chunks/{index-DMKsCn-y.js → index-BAwsCLGT.js} +1 -1
  33. package/dist-embed/chunks/{index-BUhqZVtg.js → index-BITOMOWN.js} +1 -1
  34. package/dist-embed/chunks/{index-1wTrAXI1.js → index-B_9UD0bt.js} +2 -2
  35. package/dist-embed/chunks/{index-DtlUttQj.js → index-BgFbP5P0.js} +1 -1
  36. package/dist-embed/chunks/{index-CC-Ht79i.js → index-CMUTS_sA.js} +1 -1
  37. package/dist-embed/chunks/{index-B7nmHsEo.js → index-Cgf7tlW3.js} +1 -1
  38. package/dist-embed/chunks/{index-CsZ2HycN.js → index-CrNfUmIU.js} +1 -1
  39. package/dist-embed/chunks/{index-DIOcQUrz.js → index-CzuLxFBK.js} +1 -1
  40. package/dist-embed/chunks/{index-EmZm7_bH.js → index-DEn5R18M.js} +1 -1
  41. package/dist-embed/chunks/{index-DeVZ-nmF.js → index-DFEBhySA.js} +1 -1
  42. package/dist-embed/chunks/{index-CMYyiALV.js → index-DYkwi0aX.js} +1 -1
  43. package/dist-embed/chunks/{index-Fzz2KjFM.js → index-DvpdAlVH.js} +1 -1
  44. package/dist-embed/chunks/{index-W7TVoWP5.js → index-Dyh5xopp.js} +1 -1
  45. package/dist-embed/chunks/{index-en9Yf1Yz.js → index-Jt4bWLoz.js} +1 -1
  46. package/dist-embed/chunks/{index-BbucLMre.js → index-S-sjhoD3.js} +1 -1
  47. package/dist-embed/chunks/{index-D18sf241.js → index-TRLGqZaF.js} +1 -1
  48. package/dist-embed/chunks/{index-ZZnjmFwm.js → index-YcqCoWJl.js} +1 -1
  49. package/dist-embed/chunks/{index-BFLtEBuP.js → index-sQy5cmWo.js} +1 -1
  50. package/dist-embed/chunks/{it-IT-JPQ66NNP-CLcqplyX.js → it-IT-JPQ66NNP-C3xZ61-6.js} +1 -1
  51. package/dist-embed/chunks/{ja-JP-DBVTYXUO-u8Qe6tR7.js → ja-JP-DBVTYXUO-CeTmglEb.js} +1 -1
  52. package/dist-embed/chunks/{kaa-6HZHGXH3-BG2kx50I.js → kaa-6HZHGXH3-D6Jr6B6Y.js} +1 -1
  53. package/dist-embed/chunks/{kab-KAB-ZGHBKWFO-CSYUpRxd.js → kab-KAB-ZGHBKWFO-pEXBqeuG.js} +1 -1
  54. package/dist-embed/chunks/{kk-KZ-P5N5QNE5-CpBMgHAz.js → kk-KZ-P5N5QNE5-BRJunmHm.js} +1 -1
  55. package/dist-embed/chunks/{km-KH-HSX4SM5Z-CoMUUQMb.js → km-KH-HSX4SM5Z--GJgT9rN.js} +1 -1
  56. package/dist-embed/chunks/{ko-KR-MTYHY66A-DwxY8CGI.js → ko-KR-MTYHY66A-Bjnez4qR.js} +1 -1
  57. package/dist-embed/chunks/{ku-TR-6OUDTVRD-UoNYUfm0.js → ku-TR-6OUDTVRD-DgOdYJrS.js} +1 -1
  58. package/dist-embed/chunks/{lt-LT-XHIRWOB4-Dr4uXaEj.js → lt-LT-XHIRWOB4-BMwhrtjM.js} +1 -1
  59. package/dist-embed/chunks/{lv-LV-5QDEKY6T-CNC56J-_.js → lv-LV-5QDEKY6T-0pxW7YE0.js} +1 -1
  60. package/dist-embed/chunks/{mr-IN-CRQNXWMA-BKWa9nzZ.js → mr-IN-CRQNXWMA-tndp8BXC.js} +1 -1
  61. package/dist-embed/chunks/{my-MM-5M5IBNSE-bwAzOWhV.js → my-MM-5M5IBNSE-sispDhO4.js} +1 -1
  62. package/dist-embed/chunks/{nb-NO-T6EIAALU-CkpsChor.js → nb-NO-T6EIAALU-CjBurOuS.js} +1 -1
  63. package/dist-embed/chunks/{nl-NL-IS3SIHDZ-COVk4rlb.js → nl-NL-IS3SIHDZ-B58EVmr3.js} +1 -1
  64. package/dist-embed/chunks/{nn-NO-6E72VCQL-DlyFF-C1.js → nn-NO-6E72VCQL-D1p7LZSq.js} +1 -1
  65. package/dist-embed/chunks/{oc-FR-POXYY2M6-CFDosV2C.js → oc-FR-POXYY2M6-C1M8sddP.js} +1 -1
  66. package/dist-embed/chunks/{pa-IN-N4M65BXN-BKRB3O0U.js → pa-IN-N4M65BXN-D8ESzASl.js} +1 -1
  67. package/dist-embed/chunks/{percentages-BXMCSKIN-D7pnqUsZ.js → percentages-BXMCSKIN-BihMTsFR.js} +3 -3
  68. package/dist-embed/chunks/{pl-PL-T2D74RX3-C5r2Tds7.js → pl-PL-T2D74RX3-DQGe6LHC.js} +1 -1
  69. package/dist-embed/chunks/{pt-BR-5N22H2LF-CDnT98bW.js → pt-BR-5N22H2LF-CvXBgZQs.js} +1 -1
  70. package/dist-embed/chunks/{pt-PT-UZXXM6DQ-C6c4SPk1.js → pt-PT-UZXXM6DQ-BMnnlIUJ.js} +1 -1
  71. package/dist-embed/chunks/{ro-RO-JPDTUUEW-CW6pbzEZ.js → ro-RO-JPDTUUEW-DZ-9UUH7.js} +1 -1
  72. package/dist-embed/chunks/{ru-RU-B4JR7IUQ-CbaAJG0w.js → ru-RU-B4JR7IUQ-JlRBwjob.js} +1 -1
  73. package/dist-embed/chunks/{si-LK-N5RQ5JYF-BZkUjpXF.js → si-LK-N5RQ5JYF-Bx8kHnkd.js} +1 -1
  74. package/dist-embed/chunks/{sk-SK-C5VTKIMK-DsfWgpPU.js → sk-SK-C5VTKIMK-xsfEfbZb.js} +1 -1
  75. package/dist-embed/chunks/{sl-SI-NN7IZMDC-Kx0FqxKU.js → sl-SI-NN7IZMDC-SQYUECnr.js} +1 -1
  76. package/dist-embed/chunks/{subset-shared.chunk-DM3l9OuH.js → subset-shared.chunk-Ch6j8nKC.js} +1 -1
  77. package/dist-embed/chunks/{subset-worker.chunk-4w4yibm3.js → subset-worker.chunk-pJjck5pR.js} +1 -1
  78. package/dist-embed/chunks/{sv-SE-XGPEYMSR-DY0ryO-L.js → sv-SE-XGPEYMSR-BxcjSHE2.js} +1 -1
  79. package/dist-embed/chunks/{ta-IN-2NMHFXQM-CWpIs0i0.js → ta-IN-2NMHFXQM-rItSkDUS.js} +1 -1
  80. package/dist-embed/chunks/{th-TH-HPSO5L25-Cwkcfb3u.js → th-TH-HPSO5L25-B7UzESFj.js} +1 -1
  81. package/dist-embed/chunks/{tr-TR-DEFEU3FU-BqJZZl-Y.js → tr-TR-DEFEU3FU-Ci6oNfEX.js} +1 -1
  82. package/dist-embed/chunks/{uk-UA-QMV73CPH-Dg213mG4.js → uk-UA-QMV73CPH-C9csqvO6.js} +1 -1
  83. package/dist-embed/chunks/{vi-VN-M7AON7JQ-DY94NKxF.js → vi-VN-M7AON7JQ-DRAj2kZj.js} +1 -1
  84. package/dist-embed/chunks/{zh-CN-LNUGB5OW-B6I9A7sA.js → zh-CN-LNUGB5OW-DLOyW8hH.js} +1 -1
  85. package/dist-embed/chunks/{zh-HK-E62DVLB3-D3oqgDOt.js → zh-HK-E62DVLB3-DwC6_2Ws.js} +1 -1
  86. package/dist-embed/chunks/{zh-TW-RAJ6MFWO-CYwgY0Jo.js → zh-TW-RAJ6MFWO-DEMugIwa.js} +1 -1
  87. package/dist-embed/datalayer-app-main.js +42 -42
  88. package/lib/apps/apps/appspec.js +22 -0
  89. package/lib/apps/core/index.d.ts +8 -0
  90. package/lib/apps/embed/AppEmbed.js +10 -13
  91. package/lib/apps/embed/hostBridge.d.ts +47 -13
  92. package/lib/apps/embed/hostBridge.js +135 -18
  93. package/lib/apps/index.d.ts +1 -1
  94. package/lib/apps/index.js +1 -1
  95. package/lib/apps/plugins/app-elements/QuestionView.d.ts +5 -3
  96. package/lib/apps/plugins/app-elements/QuestionView.js +15 -2
  97. package/lib/apps/plugins/app-page/appPageModel.d.ts +6 -8
  98. package/lib/apps/plugins/app-page/appPageModel.js +0 -2
  99. package/lib/apps/plugins/app-page/pageRun.d.ts +2 -2
  100. package/lib/apps/plugins/app-page/pageRun.js +3 -3
  101. package/lib/apps/plugins/chat/ChatView.js +22 -3
  102. package/lib/chat/base/ChatBase.js +33 -3
  103. package/lib/chat/base/uiFiles.d.ts +29 -0
  104. package/lib/chat/base/uiFiles.js +69 -0
  105. package/lib/chat/messages/ChatMessageList.d.ts +7 -2
  106. package/lib/chat/messages/ChatMessageList.js +2 -2
  107. package/lib/examples/ProtocolSignedRequestsExample.d.ts +10 -0
  108. package/lib/examples/ProtocolSignedRequestsExample.js +41 -0
  109. package/lib/examples/ProtocolUcpCheckoutExample.d.ts +10 -0
  110. package/lib/examples/ProtocolUcpCheckoutExample.js +59 -0
  111. package/lib/examples/example-selector.js +2 -0
  112. package/lib/examples/exampleGroups.d.ts +1 -1
  113. package/lib/examples/exampleGroups.js +4 -0
  114. package/lib/examples/index.d.ts +2 -0
  115. package/lib/examples/index.js +2 -0
  116. package/lib/examples/protocols/signedRequestsDemo.d.ts +27 -0
  117. package/lib/examples/protocols/signedRequestsDemo.js +59 -0
  118. package/lib/examples/protocols/ucpCheckoutDemo.d.ts +36 -0
  119. package/lib/examples/protocols/ucpCheckoutDemo.js +68 -0
  120. package/lib/protocols/a2a/index.js +1 -0
  121. package/lib/protocols/agentic-commerce/index.js +1 -0
  122. package/lib/protocols/core.d.ts +6 -0
  123. package/lib/protocols/mcp/index.js +1 -0
  124. package/lib/protocols/pact/index.js +1 -0
  125. package/lib/protocols/ucp/index.js +1 -0
  126. package/lib/protocols/ucp/merchant.d.ts +15 -0
  127. package/lib/protocols/ucp/merchant.js +257 -0
  128. package/lib/protocols/web-bot-auth/index.js +1 -0
  129. package/lib/specs/apps.js +87 -6
  130. package/lib/specs/appspecSchema.js +43 -0
  131. package/lib/types/agentspecs.d.ts +18 -0
  132. package/lib/types/protocol.d.ts +7 -0
  133. package/package.json +2 -2
  134. package/dist-embed/chunks/ChatView-BpKZPdcp.js +0 -32
@@ -26,6 +26,12 @@ export interface ProtocolDescription {
26
26
  status: 'implemented' | 'partial' | 'planned';
27
27
  specification: string;
28
28
  plugin: string;
29
+ /**
30
+ * Its id in the agentspecs protocols catalogue (`agentspecs/protocols`,
31
+ * AGENT_PROTOCOLS PS-01): `web-bot-auth`, `acp`… — how Studio reads a
32
+ * runtime's answer against the catalogue (PR-50).
33
+ */
34
+ catalogue: string;
29
35
  /** The contribution points its plugin declares, its extensions' included. */
30
36
  points: string[];
31
37
  }
@@ -31,6 +31,7 @@ export const McpPlugin = definePlugin({
31
31
  status: 'implemented',
32
32
  specification: 'https://modelcontextprotocol.io/specification',
33
33
  plugin: MCP_PLUGIN,
34
+ catalogue: 'mcp',
34
35
  points: [],
35
36
  }, { id: 'mcp', order: DEFAULT_ORDER }),
36
37
  ],
@@ -69,6 +69,7 @@ export const PactPlugin = definePlugin({
69
69
  status: 'partial',
70
70
  specification: 'https://github.com/openpactprotocol/openpactprotocol/blob/main/docs/spec.md',
71
71
  plugin: PACT_PLUGIN,
72
+ catalogue: 'pact',
72
73
  points: POINTS.map(point => point.id),
73
74
  }, { id: 'pact', order: DEFAULT_ORDER }),
74
75
  contribution(PactDelegatedProfiles, { grantType: DEVICE_CODE_GRANT }, { id: 'device-code', order: DEFAULT_ORDER }),
@@ -64,6 +64,7 @@ export const UcpPlugin = definePlugin({
64
64
  status: 'partial',
65
65
  specification: `https://ucp.dev/${UCP_VERSION}/specification/overview`,
66
66
  plugin: UCP_PLUGIN,
67
+ catalogue: 'ucp',
67
68
  points: UCP_POINTS.map(point => point.id),
68
69
  }, { id: 'ucp', order: DEFAULT_ORDER }),
69
70
  ],
@@ -0,0 +1,15 @@
1
+ /** The store's products, as the Python store lists them. */
2
+ export declare const EXAMPLE_STORE_CATALOG: Record<string, {
3
+ title: string;
4
+ price: number;
5
+ }>;
6
+ /** The one payment handler it takes, and the prefix of its test tokens. */
7
+ export declare const EXAMPLE_STORE_HANDLER_ID = "test_pay_1";
8
+ /** The store: its `fetch`, and the orders it took. */
9
+ export declare function exampleStore(base?: string): {
10
+ fetch: typeof fetch;
11
+ orders: Array<{
12
+ order: string;
13
+ checkout: string;
14
+ }>;
15
+ };
@@ -0,0 +1,257 @@
1
+ /*
2
+ * Copyright (c) 2025-2026 Datalayer, Inc.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ /**
6
+ * *Example Store*, the local UCP test merchant, as a `fetch` (AGENT_PROTOCOLS
7
+ * EX-04, EX-10): the same catalogue, totals, email gate, idempotency and
8
+ * test payment as `agent_runtimes.protocols.ucp.merchant.ExampleStore`, so a
9
+ * page or a test buys from it with no network. A test merchant only: it keeps
10
+ * nothing beyond the page, and refuses any payment but its test handler's.
11
+ *
12
+ * @module protocols/ucp/merchant
13
+ */
14
+ import { CHECKOUT_CAPABILITY, SHOPPING_SERVICE, UCP_VERSION } from './index';
15
+ /** The store's products, as the Python store lists them. */
16
+ export const EXAMPLE_STORE_CATALOG = {
17
+ tea_sencha: { title: 'Sencha green tea, 100 g', price: 1250 },
18
+ tea_assam: { title: 'Assam black tea, 250 g', price: 1800 },
19
+ mug: { title: 'Stoneware mug', price: 2400 },
20
+ };
21
+ /** The one payment handler it takes, and the prefix of its test tokens. */
22
+ export const EXAMPLE_STORE_HANDLER_ID = 'test_pay_1';
23
+ const TEST_TOKEN_PREFIX = 'tok_test_';
24
+ const json = (body, status = 200) => new Response(JSON.stringify(body), {
25
+ status,
26
+ headers: { 'content-type': 'application/json' },
27
+ });
28
+ const refuse = (status, code, content) => json({
29
+ ucp: { version: UCP_VERSION, status: 'error' },
30
+ messages: [{ type: 'error', code, content, severity: 'recoverable' }],
31
+ }, status);
32
+ const meta = () => ({
33
+ version: UCP_VERSION,
34
+ status: 'success',
35
+ capabilities: { [CHECKOUT_CAPABILITY]: [{ version: UCP_VERSION }] },
36
+ payment_handlers: {
37
+ 'com.example.test_pay': [
38
+ {
39
+ id: EXAMPLE_STORE_HANDLER_ID,
40
+ version: UCP_VERSION,
41
+ available_instruments: [{ type: 'card' }],
42
+ config: {},
43
+ },
44
+ ],
45
+ },
46
+ });
47
+ async function fingerprint(path, body) {
48
+ const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(path + body));
49
+ return Array.from(new Uint8Array(digest), b => b.toString(16).padStart(2, '0')).join('');
50
+ }
51
+ /** The store: its `fetch`, and the orders it took. */
52
+ export function exampleStore(base = 'https://store.example') {
53
+ const checkouts = new Map();
54
+ const replies = new Map();
55
+ const orders = [];
56
+ const render = (checkout) => {
57
+ let subtotal = 0;
58
+ const lines = checkout.lines.map((line, index) => {
59
+ const product = EXAMPLE_STORE_CATALOG[line.id];
60
+ const amount = product.price * line.quantity;
61
+ subtotal += amount;
62
+ return {
63
+ id: `li_${index + 1}`,
64
+ item: { id: line.id, title: product.title, price: product.price },
65
+ quantity: line.quantity,
66
+ totals: [
67
+ { type: 'subtotal', amount },
68
+ { type: 'total', amount },
69
+ ],
70
+ };
71
+ });
72
+ const tax = Math.round(subtotal * 0.08);
73
+ const messages = [];
74
+ if (['incomplete', 'ready_for_complete'].includes(checkout.status)) {
75
+ checkout.status = checkout.buyer?.email
76
+ ? 'ready_for_complete'
77
+ : 'incomplete';
78
+ if (!checkout.buyer?.email) {
79
+ messages.push({
80
+ type: 'error',
81
+ code: 'missing',
82
+ path: '$.buyer.email',
83
+ content: 'Buyer email is required',
84
+ severity: 'recoverable',
85
+ });
86
+ }
87
+ }
88
+ return {
89
+ ucp: meta(),
90
+ id: checkout.id,
91
+ status: checkout.status,
92
+ currency: 'USD',
93
+ line_items: lines,
94
+ totals: [
95
+ { type: 'subtotal', amount: subtotal },
96
+ { type: 'tax', amount: tax },
97
+ { type: 'total', amount: subtotal + tax },
98
+ ],
99
+ links: [{ type: 'terms_of_service', url: `${base}/terms` }],
100
+ expires_at: checkout.expiresAt,
101
+ ...(messages.length ? { messages } : {}),
102
+ ...(checkout.buyer?.email ? { buyer: checkout.buyer } : {}),
103
+ ...(checkout.order ? { order: checkout.order } : {}),
104
+ };
105
+ };
106
+ const linesOf = (body) => {
107
+ const lines = [];
108
+ for (const line of body.line_items ?? []) {
109
+ const id = line?.item?.id;
110
+ if (!(id in EXAMPLE_STORE_CATALOG)) {
111
+ return `No product ${id}`;
112
+ }
113
+ lines.push({ id, quantity: Number(line.quantity ?? 1) });
114
+ }
115
+ return lines;
116
+ };
117
+ const handle = (method, path, body) => {
118
+ if (method === 'POST' && path === '/checkout-sessions') {
119
+ const lines = linesOf(body);
120
+ if (typeof lines === 'string') {
121
+ return { status: 404, body: { messages: [{ content: lines }] } };
122
+ }
123
+ const checkout = {
124
+ id: `chk_${crypto.randomUUID().slice(0, 12)}`,
125
+ status: 'incomplete',
126
+ lines,
127
+ buyer: body.buyer ?? {},
128
+ expiresAt: new Date(Date.now() + 60 * 60 * 1000).toISOString(),
129
+ };
130
+ checkouts.set(checkout.id, checkout);
131
+ return { status: 201, body: render(checkout) };
132
+ }
133
+ const [, , id, action] = path.split('/');
134
+ const checkout = checkouts.get(id);
135
+ if (!checkout) {
136
+ return { status: 404, body: { messages: [{ content: 'No checkout' }] } };
137
+ }
138
+ if (method === 'PUT' && !action) {
139
+ if (!['incomplete', 'ready_for_complete'].includes(checkout.status)) {
140
+ return { status: 409, body: render(checkout) };
141
+ }
142
+ if (body.buyer) {
143
+ checkout.buyer = body.buyer;
144
+ }
145
+ if (body.line_items) {
146
+ const lines = linesOf(body);
147
+ if (typeof lines === 'string') {
148
+ return { status: 404, body: { messages: [{ content: lines }] } };
149
+ }
150
+ checkout.lines = lines;
151
+ }
152
+ return { status: 200, body: render(checkout) };
153
+ }
154
+ if (method === 'POST' && action === 'complete') {
155
+ if (checkout.status !== 'ready_for_complete') {
156
+ return { status: 200, body: render(checkout) };
157
+ }
158
+ const [instrument] = body.payment?.instruments ?? [{}];
159
+ const token = String(instrument?.credential?.token ?? '');
160
+ if (instrument?.handler_id !== EXAMPLE_STORE_HANDLER_ID ||
161
+ !token.startsWith(TEST_TOKEN_PREFIX)) {
162
+ return {
163
+ status: 402,
164
+ body: {
165
+ ucp: { version: UCP_VERSION, status: 'error' },
166
+ messages: [
167
+ {
168
+ type: 'error',
169
+ code: 'payment_declined',
170
+ content: 'The payment was declined',
171
+ },
172
+ ],
173
+ },
174
+ };
175
+ }
176
+ const order = {
177
+ id: `ord_${crypto.randomUUID().slice(0, 10)}`,
178
+ permalink_url: `${base}/orders/${checkout.id}`,
179
+ };
180
+ checkout.status = 'completed';
181
+ checkout.order = order;
182
+ orders.push({ order: order.id, checkout: checkout.id });
183
+ return { status: 200, body: render(checkout) };
184
+ }
185
+ if (method === 'POST' && action === 'cancel') {
186
+ if (checkout.status !== 'completed') {
187
+ checkout.status = 'canceled';
188
+ }
189
+ return { status: 200, body: render(checkout) };
190
+ }
191
+ if (method === 'GET' && !action) {
192
+ return { status: 200, body: render(checkout) };
193
+ }
194
+ return { status: 405, body: {} };
195
+ };
196
+ const fetchImpl = (async (input, init) => {
197
+ const url = new URL(String(input));
198
+ if (url.pathname === '/.well-known/ucp') {
199
+ return json({
200
+ ucp: {
201
+ version: UCP_VERSION,
202
+ services: {
203
+ [SHOPPING_SERVICE]: [
204
+ {
205
+ version: UCP_VERSION,
206
+ transport: 'rest',
207
+ endpoint: `${base}/ucp/v1`,
208
+ },
209
+ ],
210
+ },
211
+ capabilities: { [CHECKOUT_CAPABILITY]: [{ version: UCP_VERSION }] },
212
+ payment_handlers: meta().payment_handlers,
213
+ },
214
+ });
215
+ }
216
+ const headers = new Headers(init?.headers);
217
+ const agent = headers.get('UCP-Agent') ?? '';
218
+ if (!/^profile="https:\/\/[^"]+"$/.test(agent)) {
219
+ return refuse(400, 'invalid_ucp_agent', 'UCP-Agent names its profile over https');
220
+ }
221
+ if (!headers.get('Request-Id')) {
222
+ return refuse(400, 'missing_request_id', 'Request-Id is required');
223
+ }
224
+ const method = (init?.method ?? 'GET').toUpperCase();
225
+ const path = url.pathname.replace(/^\/ucp\/v1/, '');
226
+ const raw = typeof init?.body === 'string' ? init.body : '';
227
+ const body = raw ? JSON.parse(raw) : {};
228
+ if (path === '/catalog/search') {
229
+ const query = String(body.query ?? '').toLowerCase();
230
+ return json({
231
+ ucp: { version: UCP_VERSION },
232
+ products: Object.entries(EXAMPLE_STORE_CATALOG)
233
+ .filter(([, product]) => product.title.toLowerCase().includes(query))
234
+ .map(([id, product]) => ({ id, title: product.title })),
235
+ });
236
+ }
237
+ if (method === 'GET') {
238
+ const answer = handle(method, path, body);
239
+ return json(answer.body, answer.status);
240
+ }
241
+ const key = headers.get('Idempotency-Key');
242
+ if (!key) {
243
+ return refuse(400, 'missing_idempotency_key', 'Idempotency-Key is required');
244
+ }
245
+ const seen = await fingerprint(url.pathname, raw);
246
+ const replied = replies.get(key);
247
+ if (replied) {
248
+ return replied.seen === seen
249
+ ? json(replied.body, replied.status)
250
+ : refuse(409, 'idempotency_conflict', 'the key was used for another request');
251
+ }
252
+ const answer = handle(method, path, body);
253
+ replies.set(key, { seen, ...answer });
254
+ return json(answer.body, answer.status);
255
+ });
256
+ return { fetch: fetchImpl, orders };
257
+ }
@@ -324,6 +324,7 @@ export const WebBotAuthPlugin = definePlugin({
324
324
  status: 'implemented',
325
325
  specification: 'https://datatracker.ietf.org/doc/draft-ietf-webbotauth-httpsig-protocol/',
326
326
  plugin: WEB_BOT_AUTH_PLUGIN,
327
+ catalogue: 'web-bot-auth',
327
328
  points: WEB_BOT_AUTH_POINTS.map(point => point.id),
328
329
  }, { id: 'web_bot_auth', order: DEFAULT_ORDER }),
329
330
  contribution(WebBotAuthDirectoryDiscoveries, directoryDiscovery(), {
package/lib/specs/apps.js CHANGED
@@ -69,6 +69,7 @@ export const ACCOUNTING_APP_0_0_1 = {
69
69
  ],
70
70
  commands: [],
71
71
  modes: [],
72
+ actions: [],
72
73
  profiles: [],
73
74
  language: 'en',
74
75
  translations: {},
@@ -224,6 +225,7 @@ export const CHANGE_DETECTION_APP_0_0_1 = {
224
225
  ],
225
226
  commands: [],
226
227
  modes: [],
228
+ actions: [],
227
229
  profiles: [],
228
230
  language: 'en',
229
231
  translations: {},
@@ -386,6 +388,7 @@ export const CROP_MONITORING_APP_0_0_1 = {
386
388
  ],
387
389
  commands: [],
388
390
  modes: [],
391
+ actions: [],
389
392
  profiles: [],
390
393
  language: 'en',
391
394
  translations: {},
@@ -528,6 +531,36 @@ export const CUSTOMER_INTERVIEW_APP_0_0_1 = {
528
531
  ],
529
532
  commands: [],
530
533
  modes: [],
534
+ actions: [
535
+ {
536
+ name: 'save',
537
+ description: 'Save an insight, with the words it cites.',
538
+ payload: {
539
+ description: "An insight, in the interviewer's words, and the interviewee's own that it cites.",
540
+ properties: {
541
+ insight: {
542
+ description: 'What was learned',
543
+ minLength: 1,
544
+ title: 'Insight',
545
+ type: 'string',
546
+ },
547
+ quote: {
548
+ description: "The interviewee's words it cites",
549
+ minLength: 1,
550
+ title: 'Quote',
551
+ type: 'string',
552
+ },
553
+ },
554
+ required: ['insight', 'quote'],
555
+ title: 'Insight',
556
+ type: 'object',
557
+ },
558
+ },
559
+ {
560
+ name: 'finish',
561
+ description: 'End the interview, and record its result.',
562
+ },
563
+ ],
531
564
  profiles: [],
532
565
  settings: {
533
566
  type: 'object',
@@ -723,6 +756,7 @@ export const DATA_QUALITY_APP_0_0_1 = {
723
756
  starters: [],
724
757
  commands: [],
725
758
  modes: [],
759
+ actions: [],
726
760
  profiles: [],
727
761
  language: 'en',
728
762
  translations: {},
@@ -935,6 +969,7 @@ export const DECIDE_APP_0_0_1 = {
935
969
  ],
936
970
  commands: [],
937
971
  modes: [],
972
+ actions: [],
938
973
  profiles: [],
939
974
  language: 'en',
940
975
  translations: {},
@@ -1077,6 +1112,7 @@ export const DISASTER_ASSESSMENT_APP_0_0_1 = {
1077
1112
  ],
1078
1113
  commands: [],
1079
1114
  modes: [],
1115
+ actions: [],
1080
1116
  profiles: [],
1081
1117
  language: 'en',
1082
1118
  translations: {},
@@ -1221,6 +1257,7 @@ export const EVENT_RESPONSE_APP_0_0_1 = {
1221
1257
  ],
1222
1258
  commands: [],
1223
1259
  modes: [],
1260
+ actions: [],
1224
1261
  profiles: [],
1225
1262
  language: 'en',
1226
1263
  translations: {},
@@ -1388,6 +1425,7 @@ export const EXCHANGE_DESK_APP_0_0_1 = {
1388
1425
  starters: [],
1389
1426
  commands: [],
1390
1427
  modes: [],
1428
+ actions: [],
1391
1429
  profiles: [],
1392
1430
  language: 'en',
1393
1431
  translations: {},
@@ -1549,6 +1587,7 @@ export const INBOX_TRIAGE_APP_0_0_1 = {
1549
1587
  ],
1550
1588
  commands: [],
1551
1589
  modes: [],
1590
+ actions: [],
1552
1591
  profiles: [],
1553
1592
  language: 'en',
1554
1593
  translations: {},
@@ -1695,6 +1734,7 @@ export const MODEL_CHOICE_APP_0_0_1 = {
1695
1734
  starters: [],
1696
1735
  commands: [],
1697
1736
  modes: [],
1737
+ actions: [],
1698
1738
  profiles: [],
1699
1739
  language: 'en',
1700
1740
  translations: {},
@@ -1974,6 +2014,7 @@ export const MONTH_END_CLOSE_APP_0_0_1 = {
1974
2014
  ],
1975
2015
  commands: [],
1976
2016
  modes: [],
2017
+ actions: [],
1977
2018
  profiles: [],
1978
2019
  language: 'en',
1979
2020
  translations: {},
@@ -2150,6 +2191,7 @@ export const ORDER_DESK_APP_0_0_1 = {
2150
2191
  ],
2151
2192
  commands: [],
2152
2193
  modes: [],
2194
+ actions: [],
2153
2195
  profiles: [],
2154
2196
  language: 'en',
2155
2197
  translations: {},
@@ -2187,7 +2229,7 @@ export const ORDER_DESK_APP_0_0_1 = {
2187
2229
  "The journey it takes is the Personal Agent Protocol SDK's demonstration with Manzanita Outfitters, run in the SDK's own tests (personal-agent-protocol 0.10.0).",
2188
2230
  ],
2189
2231
  unverified: [
2190
- "No company answers on Datalayer yet: the gateway's company tools are not shipped, so it has not run in the Studio.",
2232
+ 'No company answers on Datalayer yet: no test company is served on r1, so it has not run in the Studio against one.',
2191
2233
  ],
2192
2234
  },
2193
2235
  },
@@ -2287,6 +2329,7 @@ export const PIPELINE_REPORT_APP_0_0_1 = {
2287
2329
  ],
2288
2330
  commands: [],
2289
2331
  modes: [],
2332
+ actions: [],
2290
2333
  profiles: [],
2291
2334
  language: 'en',
2292
2335
  translations: {},
@@ -2558,6 +2601,7 @@ export const QUOTE_CALCULATOR_APP_0_0_1 = {
2558
2601
  starters: [],
2559
2602
  commands: [],
2560
2603
  modes: [],
2604
+ actions: [],
2561
2605
  profiles: [],
2562
2606
  settings: {
2563
2607
  type: 'object',
@@ -2836,6 +2880,7 @@ export const REPORT_FROM_A_FILE_APP_0_0_1 = {
2836
2880
  starters: [],
2837
2881
  commands: [],
2838
2882
  modes: [],
2883
+ actions: [],
2839
2884
  profiles: [],
2840
2885
  settings: {
2841
2886
  type: 'object',
@@ -3123,6 +3168,7 @@ export const SALES_APP_0_0_1 = {
3123
3168
  ],
3124
3169
  commands: [],
3125
3170
  modes: [],
3171
+ actions: [],
3126
3172
  profiles: [],
3127
3173
  language: 'en',
3128
3174
  translations: {},
@@ -3241,6 +3287,7 @@ export const SHIP_OR_FIX_APP_0_0_1 = {
3241
3287
  starters: [],
3242
3288
  commands: [],
3243
3289
  modes: [],
3290
+ actions: [],
3244
3291
  profiles: [],
3245
3292
  language: 'en',
3246
3293
  translations: {},
@@ -3458,6 +3505,7 @@ export const SUPPLIER_COMPARISON_APP_0_0_1 = {
3458
3505
  starters: [],
3459
3506
  commands: [],
3460
3507
  modes: [],
3508
+ actions: [],
3461
3509
  profiles: [],
3462
3510
  language: 'en',
3463
3511
  translations: {},
@@ -3678,6 +3726,7 @@ export const SUPPORT_DESK_APP_0_0_1 = {
3678
3726
  ],
3679
3727
  commands: [],
3680
3728
  modes: [],
3729
+ actions: [],
3681
3730
  profiles: [],
3682
3731
  settings: {
3683
3732
  type: 'object',
@@ -3889,7 +3938,7 @@ export const SUPPORT_DESK_APP_0_0_1 = {
3889
3938
  recorded: [],
3890
3939
  unverified: [
3891
3940
  'No conversation has run: its agent was switched on in the catalogue on 2026-10-06 and has not answered yet.',
3892
- "Its two documents are Datalayer's samples, for a made-up product: a builder gives their own on What it knows.",
3941
+ "Its two documents are Datalayer's own examples, for a made-up product: a builder gives their own on What it knows.",
3893
3942
  'Its tests have not been run.',
3894
3943
  ],
3895
3944
  },
@@ -4011,6 +4060,7 @@ export const TRAVEL_DESK_APP_0_0_1 = {
4011
4060
  ],
4012
4061
  commands: [],
4013
4062
  modes: [],
4063
+ actions: [],
4014
4064
  profiles: [],
4015
4065
  language: 'en',
4016
4066
  translations: {},
@@ -4045,7 +4095,7 @@ export const TRAVEL_DESK_APP_0_0_1 = {
4045
4095
  verified: {
4046
4096
  live: [],
4047
4097
  recorded: [
4048
- "The journey is agent-runtimes' PACT example (examples/protocols/pact/personal_agent.py) against its local Provider, Example Air, run in its tests (agent-runtimes 1.3.112).",
4098
+ "The journey is the developers' PACT example (examples/protocols/pact/personal_agent.py) against its local Provider, Example Air, run in its own tests (version 1.3.112).",
4049
4099
  ],
4050
4100
  unverified: [
4051
4101
  "No airline answers PACT on Datalayer yet: the gateway's pact toolset is built, not deployed.",
@@ -4143,6 +4193,7 @@ export const WEB_RESEARCH_APP_0_0_1 = {
4143
4193
  ],
4144
4194
  commands: [],
4145
4195
  modes: [],
4196
+ actions: [],
4146
4197
  profiles: [],
4147
4198
  settings: {
4148
4199
  type: 'object',
@@ -4584,6 +4635,36 @@ export const APP_SOURCES = {
4584
4635
  message: 'Interview me about my first week with the product.',
4585
4636
  },
4586
4637
  ],
4638
+ actions: [
4639
+ {
4640
+ name: 'save',
4641
+ description: 'Save an insight, with the words it cites.',
4642
+ payload: {
4643
+ description: "An insight, in the interviewer's words, and the interviewee's own that it cites.",
4644
+ properties: {
4645
+ insight: {
4646
+ description: 'What was learned',
4647
+ minLength: 1,
4648
+ title: 'Insight',
4649
+ type: 'string',
4650
+ },
4651
+ quote: {
4652
+ description: "The interviewee's words it cites",
4653
+ minLength: 1,
4654
+ title: 'Quote',
4655
+ type: 'string',
4656
+ },
4657
+ },
4658
+ required: ['insight', 'quote'],
4659
+ title: 'Insight',
4660
+ type: 'object',
4661
+ },
4662
+ },
4663
+ {
4664
+ name: 'finish',
4665
+ description: 'End the interview, and record its result.',
4666
+ },
4667
+ ],
4587
4668
  settings: {
4588
4669
  type: 'object',
4589
4670
  properties: {
@@ -5606,7 +5687,7 @@ export const APP_SOURCES = {
5606
5687
  "The journey it takes is the Personal Agent Protocol SDK's demonstration with Manzanita Outfitters, run in the SDK's own tests (personal-agent-protocol 0.10.0).",
5607
5688
  ],
5608
5689
  unverified: [
5609
- "No company answers on Datalayer yet: the gateway's company tools are not shipped, so it has not run in the Studio.",
5690
+ 'No company answers on Datalayer yet: no test company is served on r1, so it has not run in the Studio against one.',
5610
5691
  ],
5611
5692
  },
5612
5693
  },
@@ -6831,7 +6912,7 @@ export const APP_SOURCES = {
6831
6912
  verified: {
6832
6913
  unverified: [
6833
6914
  'No conversation has run: its agent was switched on in the catalogue on 2026-10-06 and has not answered yet.',
6834
- "Its two documents are Datalayer's samples, for a made-up product: a builder gives their own on What it knows.",
6915
+ "Its two documents are Datalayer's own examples, for a made-up product: a builder gives their own on What it knows.",
6835
6916
  'Its tests have not been run.',
6836
6917
  ],
6837
6918
  },
@@ -6927,7 +7008,7 @@ export const APP_SOURCES = {
6927
7008
  ],
6928
7009
  verified: {
6929
7010
  recorded: [
6930
- "The journey is agent-runtimes' PACT example (examples/protocols/pact/personal_agent.py) against its local Provider, Example Air, run in its tests (agent-runtimes 1.3.112).",
7011
+ "The journey is the developers' PACT example (examples/protocols/pact/personal_agent.py) against its local Provider, Example Air, run in its own tests (version 1.3.112).",
6931
7012
  ],
6932
7013
  unverified: [
6933
7014
  "No airline answers PACT on Datalayer yet: the gateway's pact toolset is built, not deployed.",
@@ -76,6 +76,41 @@ export const APPSPEC_SCHEMA = {
76
76
  title: 'AppA2AExtension',
77
77
  type: 'object',
78
78
  },
79
+ AppAction: {
80
+ additionalProperties: false,
81
+ description: 'An action of its code a person presses (`@app.action`, REACTOR_UI RU-5).\n\nWhat its payload is, typed: the JSON Schema of an object, written from the\npydantic model its code declares (`@app.action(name, payload=Model)`); an\naction declaring none takes nothing. The runtime offers it only where a\ndisplay showed it, or to the session that runs it, each press with a\ntoken; a press of an action not declared here is refused, and its payload\nis checked against this schema before its code runs.',
82
+ properties: {
83
+ name: {
84
+ description: 'Its name, as its button presses it',
85
+ pattern: '^[A-Za-z_][A-Za-z0-9_]*$',
86
+ title: 'Name',
87
+ type: 'string',
88
+ },
89
+ description: {
90
+ default: '',
91
+ description: 'What it does, in a sentence',
92
+ title: 'Description',
93
+ type: 'string',
94
+ },
95
+ payload: {
96
+ anyOf: [
97
+ {
98
+ additionalProperties: true,
99
+ type: 'object',
100
+ },
101
+ {
102
+ type: 'null',
103
+ },
104
+ ],
105
+ default: null,
106
+ description: 'The JSON Schema of what it is pressed with, an object; none when it takes nothing (a press with anything is then refused)',
107
+ title: 'Payload',
108
+ },
109
+ },
110
+ required: ['name'],
111
+ title: 'AppAction',
112
+ type: 'object',
113
+ },
79
114
  AppChecks: {
80
115
  additionalProperties: false,
81
116
  description: "Optional: checks from the catalogue, for a builder who wants them — and its code's own.",
@@ -536,6 +571,14 @@ export const APPSPEC_SCHEMA = {
536
571
  title: 'Modes',
537
572
  type: 'array',
538
573
  },
574
+ actions: {
575
+ description: 'The actions of its code a person presses (`@app.action`, REACTOR_UI RU-5), each with the JSON Schema of its payload: what its buttons offer, with a token, and nothing else',
576
+ items: {
577
+ $ref: '#/$defs/AppAction',
578
+ },
579
+ title: 'Actions',
580
+ type: 'array',
581
+ },
539
582
  profiles: {
540
583
  description: 'Several assistants in one application, two at least: the person picks one before the conversation starts — the first unless they do — and keeps it to the end; its instructions, model and starters go with every run (LOOP P-20). None when unsaid',
541
584
  items: {