@datalayer/agent-runtimes 1.5.4 → 1.5.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -99,7 +99,7 @@ export declare function uiProblems(app: Pick<AppSpec, 'ui' | 'enabled'>): string
99
99
  * know or said twice, a planned protocol while it is offered, and a `pap`,
100
100
  * `pact` or `a2a` block configuring a protocol it does not list.
101
101
  */
102
- export declare function protocolsProblems(app: Pick<AppSpec, 'protocols' | 'enabled' | 'pap' | 'pact' | 'a2a'>): string[];
102
+ export declare function protocolsProblems(app: Pick<AppSpec, 'protocols' | 'enabled' | 'pap' | 'pact' | 'a2a' | 'commerce'>): string[];
103
103
  /**
104
104
  * An application as the document its file holds.
105
105
  *
@@ -204,6 +204,7 @@ const KNOWN_KEYS = [
204
204
  'ui',
205
205
  'pap',
206
206
  'pact',
207
+ 'commerce',
207
208
  'a2a',
208
209
  'rules',
209
210
  'permissions',
@@ -333,6 +334,41 @@ function parsePact(data) {
333
334
  })),
334
335
  };
335
336
  }
337
+ /** Commerce (AGENT_PROTOCOLS PI-01): the merchants it shops at for each person. */
338
+ function parseCommerce(data) {
339
+ return {
340
+ merchants: records(data.merchants).map(merchant => ({
341
+ id: text(merchant.id),
342
+ protocol: oneOf(merchant.protocol, ['ucp', 'acp'], 'ucp'),
343
+ merchant: text(merchant.merchant),
344
+ access: oneOf(merchant.access, ['read', 'write'], 'read'),
345
+ ...(isData(merchant.limit)
346
+ ? {
347
+ limit: {
348
+ amount: text(merchant.limit.amount),
349
+ currency: text(merchant.limit.currency),
350
+ },
351
+ }
352
+ : {}),
353
+ })),
354
+ };
355
+ }
356
+ /** Commerce as its document says it, nothing at its default. */
357
+ function dumpCommerce(commerce) {
358
+ if (!commerce)
359
+ return {};
360
+ return new Writer().list('merchants', commerce.merchants.map(merchant => new Writer()
361
+ .text('id', merchant.id, '\u0000')
362
+ .text('protocol', merchant.protocol, '\u0000')
363
+ .text('merchant', merchant.merchant, '\u0000')
364
+ .value('access', merchant.access, 'read')
365
+ .part('limit', merchant.limit
366
+ ? {
367
+ amount: merchant.limit.amount,
368
+ currency: merchant.limit.currency,
369
+ }
370
+ : {}).data)).data;
371
+ }
336
372
  /** PACT as its document says it, nothing at its default. */
337
373
  function dumpPact(pact) {
338
374
  if (!pact)
@@ -912,6 +948,9 @@ export function parseAppspec(document) {
912
948
  if (isData(data.pact)) {
913
949
  app.pact = parsePact(data.pact);
914
950
  }
951
+ if (isData(data.commerce)) {
952
+ app.commerce = parseCommerce(data.commerce);
953
+ }
915
954
  if (isData(data.a2a)) {
916
955
  app.a2a = parseA2A(data.a2a);
917
956
  }
@@ -976,6 +1015,12 @@ export function protocolsProblems(app) {
976
1015
  problems.push(`Its \`${block}\` block configures ${spec.name}, which it does not list: add '${spec.id}' to \`protocols\`.`);
977
1016
  }
978
1017
  }
1018
+ // The `commerce` block configures UCP and ACP, by each merchant's protocol.
1019
+ for (const id of new Set((app.commerce?.merchants ?? []).map(merchant => merchant.protocol))) {
1020
+ if (!app.protocols.includes(id)) {
1021
+ problems.push(`Its \`commerce\` block shops over ${id.toUpperCase()}, which it does not list: add '${id}' to \`protocols\`.`);
1022
+ }
1023
+ }
979
1024
  return problems;
980
1025
  }
981
1026
  // --- writing --------------------------------------------------------------------------
@@ -1243,6 +1288,7 @@ export function dumpAppspec(app) {
1243
1288
  .list('ui', app.ui)
1244
1289
  .part('pap', dumpPap(app.pap))
1245
1290
  .part('pact', dumpPact(app.pact))
1291
+ .part('commerce', dumpCommerce(app.commerce))
1246
1292
  .part('a2a', dumpA2A(app.a2a))
1247
1293
  .list('rules', app.rules.map(rule => ({
1248
1294
  action: rule.action,
@@ -31,6 +31,7 @@ export const emptySceneSpec = () => ({
31
31
  schema: SCENE_SCHEMA,
32
32
  id: '',
33
33
  version: '0.0.1',
34
+ kind: 'team',
34
35
  name: '',
35
36
  description: '',
36
37
  tags: [],
@@ -57,6 +58,9 @@ export const emptySceneSpec = () => ({
57
58
  },
58
59
  deployment: { account: '', page: '', addresses: {} },
59
60
  setup: [],
61
+ actors: [],
62
+ lines: [],
63
+ live: '',
60
64
  });
61
65
  const isRecord = (value) => Boolean(value) && typeof value === 'object' && !Array.isArray(value);
62
66
  /**
@@ -208,7 +208,8 @@ function Scene({ scene }) {
208
208
  } }), _jsxs(Box, { display: "flex", gap: 2, mt: 2, children: [_jsx(Button, { variant: "primary", disabled: !team.ready || team.busy || !draft.trim(), onClick: () => send(draft), children: "Ask" }), team.busy && _jsx(Button, { onClick: team.stop, children: "Stop" })] }), _jsx(Box, { mt: 3, "data-team-conversation": "", children: team.turns.map((turn, index) => (_jsxs(Box, { p: 2, mb: 2, borderRadius: 2, bg: turn.role === 'user' ? 'accent.subtle' : 'canvas.subtle', children: [_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted', display: 'block' }, children: turn.role === 'user' ? PERSON : entry.app.name }), _jsx(Streamdown, { children: turn.text })] }, index))) })] }), _jsxs(Box, { flex: "1 1 360px", minWidth: 0, children: [!team.notebook && stage.peers.length > 0 && (_jsxs(_Fragment, { children: [_jsx(Heading, { as: "h3", sx: { fontSize: 2, mb: 2 }, children: "The report" }), _jsx(Box, { p: 3, border: "1px solid", borderColor: "border.default", borderRadius: 2, minHeight: 120, "data-team-report": "", children: team.report ? (_jsx(Streamdown, { children: team.report })) : (_jsx(Text, { sx: { color: 'fg.muted' }, children: "What the last member asked returns appears here, as it returned it, or as a notebook to run when it gives one." })) })] })), scene.setup.length > 0 && (_jsxs(Box, { mt: 3, "data-scene-setup": "", children: [_jsx(Heading, { as: "h3", sx: { fontSize: 1, mb: 1 }, children: "What this scene needs" }), scene.setup.map(line => (_jsx(Text, { as: "p", sx: { fontSize: 0, color: 'fg.muted', m: 0 }, children: line }, line)))] }))] })] })] }));
209
209
  }
210
210
  function Scenes() {
211
- const scenes = useMemo(() => listSceneSpecs(), []);
211
+ // A scripted scene is played as written on its page (PAP_DATALAYER X-02): here, the teams staged live.
212
+ const scenes = useMemo(() => listSceneSpecs().filter(scene => scene.kind === 'team'), []);
212
213
  const [selected, setSelected] = useState(scenes[0]?.id ?? '');
213
214
  const scene = scenes.find(one => one.id === selected) ?? scenes[0];
214
215
  return (_jsxs(Box, { p: 4, maxWidth: 1080, mx: "auto", children: [_jsx(Heading, { as: "h2", sx: { fontSize: 4, mb: 1 }, children: "Scenes" }), _jsxs(Text, { as: "p", sx: { color: 'fg.muted', mt: 0 }, children: ["The catalogue\u2019s scenes, played against the local servers: each member on a runtime is served over A2A by ", _jsx("code", { children: "serve_scenes.py" }), "; the entry in your browser asks on your token or a visitor\u2019s trial key."] }), _jsx(UnderlineNav, { "aria-label": "Scenes", sx: { mb: 3 }, children: scenes.map(one => (_jsxs(UnderlineNav.Item, { "aria-current": one.id === scene?.id ? 'page' : undefined, onSelect: event => {
@@ -21,13 +21,21 @@ import { PROTOCOL_TOOL_CLASSES } from '../specs/actions';
21
21
  * thing: one with several classes, or none, throws rather than guess.
22
22
  */
23
23
  export function operationClasses(protocol) {
24
+ const table = PROTOCOL_TOOL_CLASSES[protocol];
25
+ // The table names the gateway's tools (`ucp_search`): an operation is the
26
+ // tool's name without its connection's prefix.
27
+ const prefix = `${table.connection}_`;
24
28
  const out = {};
25
- for (const [name, classes] of Object.entries(PROTOCOL_TOOL_CLASSES[protocol].tools)) {
29
+ for (const [name, classes] of Object.entries(table.tools)) {
26
30
  if (classes.length !== 1) {
27
31
  throw new Error(`${protocol} ${name} is classed ${classes}: one class each`);
28
32
  }
29
- out[name.replace(/_([a-z])/g, (_, c) => c.toUpperCase())] =
30
- classes[0];
33
+ if (!name.startsWith(prefix)) {
34
+ throw new Error(`${protocol} ${name} is not one of its \`${prefix}\` tools`);
35
+ }
36
+ out[name
37
+ .slice(prefix.length)
38
+ .replace(/_([a-z])/g, (_, c) => c.toUpperCase())] = classes[0];
31
39
  }
32
40
  return out;
33
41
  }
@@ -47,6 +47,8 @@ export declare function negotiateCapabilities(profile: Json, reactor: ReactorPla
47
47
  export declare function revisionOf(checkout: Json): Promise<string>;
48
48
  /** What an agent may see of a checkout: no payment credential, no token. */
49
49
  export declare function checkoutSummary(checkout: Json): Json;
50
+ /** What an agent may see of an order: its lines, how far each is fulfilled, its totals and link. */
51
+ export declare function orderSummary(order: Json): Json;
50
52
  /** A platform's client of one business, over the REST binding. */
51
53
  export declare class UcpClient {
52
54
  private readonly business;
@@ -67,6 +69,8 @@ export declare class UcpClient {
67
69
  updateCheckout(id: string, update: Json): Promise<Json>;
68
70
  getCheckout(id: string): Promise<Json>;
69
71
  cancelCheckout(id: string): Promise<Json>;
72
+ /** After the sale: an order the business placed, its lines and how far each is fulfilled (`read`). */
73
+ getOrder(id: string): Promise<Json>;
70
74
  /** The exact purchase a `ready_for_complete` checkout would make. */
71
75
  proposeCompletion(id: string): Promise<CheckoutProposal>;
72
76
  /** Complete an approved proposal, once: refused if unapproved, spent, changed or expired. */
@@ -16,7 +16,7 @@
16
16
  * @module protocols/ucp
17
17
  */
18
18
  import { contribution, defineContributionPoint, definePlugin, } from '@datalayer/reactor';
19
- import { ExtendedCheckoutResponseSchema } from '@ucp-js/sdk';
19
+ import { ExtendedCheckoutResponseSchema, OrderSchema } from '@ucp-js/sdk';
20
20
  import { InMemoryProposalStore, digest, operationClasses, } from '../commerce';
21
21
  import { DEFAULT_ORDER, ProtocolDescriptions, firstContribution, } from '../core';
22
22
  export const UCP_VERSION = '2026-08-25';
@@ -131,6 +131,32 @@ export function checkoutSummary(checkout) {
131
131
  ...(checkout.order ? { order: { id: checkout.order.id } } : {}),
132
132
  };
133
133
  }
134
+ function readOrder(body) {
135
+ if (body?.ucp?.status === 'error') {
136
+ const said = (body.messages ?? []).map((m) => m.content).join('; ');
137
+ throw new UcpError(`the business refused: ${said}`);
138
+ }
139
+ OrderSchema.parse(body);
140
+ return body;
141
+ }
142
+ /** What an agent may see of an order: its lines, how far each is fulfilled, its totals and link. */
143
+ export function orderSummary(order) {
144
+ return {
145
+ id: order.id,
146
+ checkoutId: order.checkout_id,
147
+ permalinkUrl: order.permalink_url,
148
+ currency: order.currency,
149
+ lines: (order.line_items ?? []).map((line) => ({
150
+ title: line.item?.title ?? line.item?.id,
151
+ quantity: line.quantity?.total,
152
+ fulfilled: line.quantity?.fulfilled,
153
+ status: line.status,
154
+ })),
155
+ totals: Object.fromEntries((order.totals ?? []).map((t) => [t.type, t.amount])),
156
+ events: (order.fulfillment?.events ?? []).map((event) => event.type),
157
+ messages: (order.messages ?? []).map((m) => m.content),
158
+ };
159
+ }
134
160
  /** A platform's client of one business, over the REST binding. */
135
161
  export class UcpClient {
136
162
  business;
@@ -218,6 +244,10 @@ export class UcpClient {
218
244
  async cancelCheckout(id) {
219
245
  return readCheckout(await this.call('POST', `/checkout-sessions/${id}/cancel`, {}, crypto.randomUUID()));
220
246
  }
247
+ /** After the sale: an order the business placed, its lines and how far each is fulfilled (`read`). */
248
+ async getOrder(id) {
249
+ return readOrder(await this.call('GET', `/orders/${id}`));
250
+ }
221
251
  /** The exact purchase a `ready_for_complete` checkout would make. */
222
252
  async proposeCompletion(id) {
223
253
  const checkout = await this.getCheckout(id);
@@ -53,6 +53,8 @@ export function exampleStore(base = 'https://store.example') {
53
53
  const checkouts = new Map();
54
54
  const replies = new Map();
55
55
  const orders = [];
56
+ // Each order placed, by id: what was bought, as the order answers it.
57
+ const placed = new Map();
56
58
  const render = (checkout) => {
57
59
  let subtotal = 0;
58
60
  const lines = checkout.lines.map((line, index) => {
@@ -130,7 +132,13 @@ export function exampleStore(base = 'https://store.example') {
130
132
  checkouts.set(checkout.id, checkout);
131
133
  return { status: 201, body: render(checkout) };
132
134
  }
133
- const [, , id, action] = path.split('/');
135
+ const [, collection, id, action] = path.split('/');
136
+ if (collection === 'orders') {
137
+ const order = method === 'GET' && !action ? placed.get(id) : undefined;
138
+ return order
139
+ ? { status: 200, body: order }
140
+ : { status: 404, body: { messages: [{ content: 'No such order' }] } };
141
+ }
134
142
  const checkout = checkouts.get(id);
135
143
  if (!checkout) {
136
144
  return { status: 404, body: { messages: [{ content: 'No checkout' }] } };
@@ -180,7 +188,24 @@ export function exampleStore(base = 'https://store.example') {
180
188
  checkout.status = 'completed';
181
189
  checkout.order = order;
182
190
  orders.push({ order: order.id, checkout: checkout.id });
183
- return { status: 200, body: render(checkout) };
191
+ const rendered = render(checkout);
192
+ placed.set(order.id, {
193
+ ucp: { version: UCP_VERSION, status: 'success' },
194
+ id: order.id,
195
+ checkout_id: checkout.id,
196
+ permalink_url: order.permalink_url,
197
+ line_items: rendered.line_items.map((line) => ({
198
+ id: line.id,
199
+ item: line.item,
200
+ quantity: { total: line.quantity, fulfilled: 0 },
201
+ totals: line.totals,
202
+ status: 'processing',
203
+ })),
204
+ fulfillment: { events: [] },
205
+ currency: rendered.currency,
206
+ totals: rendered.totals,
207
+ });
208
+ return { status: 200, body: rendered };
184
209
  }
185
210
  if (method === 'POST' && action === 'cancel') {
186
211
  if (checkout.status !== 'completed') {
@@ -671,12 +671,11 @@ export const PROTOCOL_TOOL_CLASSES = {
671
671
  'agentic-commerce': {
672
672
  connection: 'acp',
673
673
  tools: {
674
- propose_completion: ['read'],
675
- get_checkout: ['read'],
676
- create_checkout: ['write'],
677
- update_checkout: ['write'],
678
- cancel_checkout: ['write'],
679
- complete: ['buy'],
674
+ acp_get_checkout: ['read'],
675
+ acp_create_checkout: ['write'],
676
+ acp_update_checkout: ['write'],
677
+ acp_cancel_checkout: ['write'],
678
+ acp_complete: ['buy'],
680
679
  },
681
680
  },
682
681
  pact: {
@@ -694,8 +693,8 @@ export const PROTOCOL_TOOL_CLASSES = {
694
693
  connection: 'pap',
695
694
  tools: {
696
695
  pap_company: ['read'],
697
- pap_ask: ['read'],
698
- pap_handoff: ['read'],
696
+ pap_ask: ['send'],
697
+ pap_handoff: ['send'],
699
698
  pap_sign_in: ['read'],
700
699
  pap_sign_in_status: ['read'],
701
700
  pap_read: ['read'],
@@ -708,13 +707,13 @@ export const PROTOCOL_TOOL_CLASSES = {
708
707
  ucp: {
709
708
  connection: 'ucp',
710
709
  tools: {
711
- search: ['read'],
712
- propose_completion: ['read'],
713
- get_checkout: ['read'],
714
- create_checkout: ['write'],
715
- update_checkout: ['write'],
716
- cancel_checkout: ['write'],
717
- complete: ['buy'],
710
+ ucp_search: ['read'],
711
+ ucp_get_checkout: ['read'],
712
+ ucp_create_checkout: ['write'],
713
+ ucp_update_checkout: ['write'],
714
+ ucp_cancel_checkout: ['write'],
715
+ ucp_complete: ['buy'],
716
+ ucp_get_order: ['read'],
718
717
  },
719
718
  },
720
719
  };
@@ -775,6 +774,7 @@ export const APP_BEHAVIOURS = {
775
774
  'odoo-accounting.odoo_accounting_trial_balance': 'do_it',
776
775
  'odoo-accounting.odoo_accounting_unreconcile_lines': 'leave_to_me',
777
776
  },
777
+ 'bookkeeping-desk': {},
778
778
  'change-detection': {
779
779
  'earthdata.download_earth_data_granules': 'leave_to_me',
780
780
  'earthdata.search_earth_datagranules': 'do_it',
@@ -917,6 +917,7 @@ export const APP_BEHAVIOURS = {
917
917
  'google-workspace.update_drive_file': 'leave_to_me',
918
918
  'google-workspace.update_paragraph_style': 'leave_to_me',
919
919
  },
920
+ 'live-shop-chat': {},
920
921
  'model-choice': {},
921
922
  'month-end-close': {
922
923
  'odoo-accounting.odoo_accounting_aged_balance': 'do_it',
@@ -970,11 +971,16 @@ export const APP_BEHAVIOURS = {
970
971
  'odoo-accounting.odoo_accounting_unreconcile_lines': 'leave_to_me',
971
972
  },
972
973
  'order-desk': {},
974
+ 'order-tracker': {},
973
975
  'pipeline-report': {},
974
976
  'quote-calculator': {},
975
977
  'report-from-a-file': {},
978
+ 'returns-desk': {},
976
979
  sales: {},
977
980
  'ship-or-fix': {},
981
+ 'shop-concierge': {},
982
+ 'shopping-desk': {},
983
+ 'sign-in-desk': {},
978
984
  'supplier-comparison': {},
979
985
  'support-desk': {},
980
986
  'travel-desk': {},
@@ -992,6 +998,7 @@ export const APP_BEHAVIOURS = {
992
998
  */
993
999
  export const APP_ESCALATIONS = {
994
1000
  accounting: {},
1001
+ 'bookkeeping-desk': {},
995
1002
  'change-detection': {},
996
1003
  'crop-monitoring': {},
997
1004
  'customer-interview': {},
@@ -1034,14 +1041,20 @@ export const APP_ESCALATIONS = {
1034
1041
  },
1035
1042
  ],
1036
1043
  },
1044
+ 'live-shop-chat': {},
1037
1045
  'model-choice': {},
1038
1046
  'month-end-close': {},
1039
1047
  'order-desk': {},
1048
+ 'order-tracker': {},
1040
1049
  'pipeline-report': {},
1041
1050
  'quote-calculator': {},
1042
1051
  'report-from-a-file': {},
1052
+ 'returns-desk': {},
1043
1053
  sales: {},
1044
1054
  'ship-or-fix': {},
1055
+ 'shop-concierge': {},
1056
+ 'shopping-desk': {},
1057
+ 'sign-in-desk': {},
1045
1058
  'supplier-comparison': {},
1046
1059
  'support-desk': {},
1047
1060
  'travel-desk': {},
@@ -9,6 +9,7 @@
9
9
  */
10
10
  import type { AppBuilt, AppKind, AppSpec } from '../types/agentspecs';
11
11
  export declare const ACCOUNTING_APP_0_0_1: AppSpec;
12
+ export declare const BOOKKEEPING_DESK_APP_0_0_1: AppSpec;
12
13
  export declare const CHANGE_DETECTION_APP_0_0_1: AppSpec;
13
14
  export declare const CROP_MONITORING_APP_0_0_1: AppSpec;
14
15
  export declare const CUSTOMER_INTERVIEW_APP_0_0_1: AppSpec;
@@ -18,14 +19,20 @@ export declare const DISASTER_ASSESSMENT_APP_0_0_1: AppSpec;
18
19
  export declare const EVENT_RESPONSE_APP_0_0_1: AppSpec;
19
20
  export declare const EXCHANGE_DESK_APP_0_0_1: AppSpec;
20
21
  export declare const INBOX_TRIAGE_APP_0_0_1: AppSpec;
22
+ export declare const LIVE_SHOP_CHAT_APP_0_0_1: AppSpec;
21
23
  export declare const MODEL_CHOICE_APP_0_0_1: AppSpec;
22
24
  export declare const MONTH_END_CLOSE_APP_0_0_1: AppSpec;
23
25
  export declare const ORDER_DESK_APP_0_0_1: AppSpec;
26
+ export declare const ORDER_TRACKER_APP_0_0_1: AppSpec;
24
27
  export declare const PIPELINE_REPORT_APP_0_0_1: AppSpec;
25
28
  export declare const QUOTE_CALCULATOR_APP_0_0_1: AppSpec;
26
29
  export declare const REPORT_FROM_A_FILE_APP_0_0_1: AppSpec;
30
+ export declare const RETURNS_DESK_APP_0_0_1: AppSpec;
27
31
  export declare const SALES_APP_0_0_1: AppSpec;
28
32
  export declare const SHIP_OR_FIX_APP_0_0_1: AppSpec;
33
+ export declare const SHOP_CONCIERGE_APP_0_0_1: AppSpec;
34
+ export declare const SHOPPING_DESK_APP_0_0_1: AppSpec;
35
+ export declare const SIGN_IN_DESK_APP_0_0_1: AppSpec;
29
36
  export declare const SUPPLIER_COMPARISON_APP_0_0_1: AppSpec;
30
37
  export declare const SUPPORT_DESK_APP_0_0_1: AppSpec;
31
38
  export declare const TRAVEL_DESK_APP_0_0_1: AppSpec;