@datalayer/agent-runtimes 1.5.3 → 1.5.6

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
  /**
@@ -2,7 +2,9 @@
2
2
  * *Sign in at a company*, in the chat (AGENT_PROTOCOLS PI-56): where the
3
3
  * gateway's `pap_sign_in` answers, the company's page as a button opening a
4
4
  * new tab, the code large and copyable with a code to scan for Device
5
- * Sign-In, until when, and *I have signed in: check again* — sent as the
5
+ * Sign-In, Datalayer's own page for Mediated Sign-In (where the person types
6
+ * what the company asks, never in the chat) with where a code was sent,
7
+ * until when, and *I have signed in: check again* — sent as the
6
8
  * person's next message — when the chat can send one, otherwise a line
7
9
  * asking them to say so. What it reads is `signIn`'s.
8
10
  *
@@ -25,9 +25,14 @@ export function PapSignInCard({ context, send, }) {
25
25
  setCopied(false);
26
26
  }
27
27
  };
28
- return (_jsxs(Box, { as: "section", "aria-label": PAP_SIGN_IN_WORDS.title(read.host), my: 2, p: 3, border: "1px solid", borderColor: "border.default", borderRadius: 2, bg: "canvas.subtle", maxWidth: 480, children: [_jsx(Heading, { as: "h4", sx: { fontSize: 2, mb: 1 }, children: PAP_SIGN_IN_WORDS.title(read.host) }), _jsx(Text, { as: "p", sx: { mt: 0, mb: 2, color: 'fg.muted', fontSize: 1 }, children: read.mode === 'device'
28
+ const title = read.mode === 'mediated'
29
+ ? PAP_SIGN_IN_WORDS.mediatedTitle(read.host)
30
+ : PAP_SIGN_IN_WORDS.title(read.host);
31
+ return (_jsxs(Box, { as: "section", "aria-label": title, my: 2, p: 3, border: "1px solid", borderColor: "border.default", borderRadius: 2, bg: "canvas.subtle", maxWidth: 480, children: [_jsx(Heading, { as: "h4", sx: { fontSize: 2, mb: 1 }, children: title }), _jsx(Text, { as: "p", sx: { mt: 0, mb: 2, color: 'fg.muted', fontSize: 1 }, children: read.mode === 'device'
29
32
  ? PAP_SIGN_IN_WORDS.device
30
- : PAP_SIGN_IN_WORDS.direct }), read.code ? (_jsxs(Box, { display: "flex", gap: 3, alignItems: "center", flexWrap: "wrap", mb: 2, children: [_jsxs(Box, { children: [_jsx(Text, { as: "div", sx: { fontSize: 0, color: 'fg.muted' }, children: PAP_SIGN_IN_WORDS.code }), _jsx(Text, { as: "div", "aria-live": "polite", sx: {
33
+ : read.mode === 'mediated'
34
+ ? PAP_SIGN_IN_WORDS.mediated
35
+ : PAP_SIGN_IN_WORDS.direct }), read.sentTo ? (_jsx(Text, { as: "p", sx: { mt: 0, mb: 2, fontSize: 1 }, children: PAP_SIGN_IN_WORDS.sentTo(read.sentTo) })) : null, read.code ? (_jsxs(Box, { display: "flex", gap: 3, alignItems: "center", flexWrap: "wrap", mb: 2, children: [_jsxs(Box, { children: [_jsx(Text, { as: "div", sx: { fontSize: 0, color: 'fg.muted' }, children: PAP_SIGN_IN_WORDS.code }), _jsx(Text, { as: "div", "aria-live": "polite", sx: {
31
36
  fontFamily: 'mono',
32
37
  fontSize: 5,
33
38
  fontWeight: 'bold',
@@ -1,9 +1,12 @@
1
1
  /**
2
2
  * What the gateway's `pap_sign_in` tool answers, read and checked
3
3
  * (AGENT_PROTOCOLS PI-56, PAP_DATALAYER P-03): the person signs in at a
4
- * company themselves — on the company's page (`direct`), or by entering a
5
- * code there (`device`). The chat shows the link, the code and until when;
6
- * the agent never sees what the person types.
4
+ * company themselves — on the company's page (`direct`), by entering a
5
+ * code there (`device`), or by typing what the company asks on Datalayer's
6
+ * own page (`mediated`), never in the conversation. The chat shows the link,
7
+ * the code and until when; the agent never sees what the person types. A
8
+ * business over PACT answers its `pact_sign_in` the same way (a `device`
9
+ * sign-in at the business).
7
10
  *
8
11
  * Pure: no React. A test reaches it without a chat around it.
9
12
  *
@@ -11,13 +14,15 @@
11
14
  */
12
15
  /** A sign-in waiting for the person, as the card shows it. */
13
16
  export type PapSignInWaiting = {
14
- mode: 'direct' | 'device';
15
- /** The company's page, `https` only. */
17
+ mode: 'direct' | 'device' | 'mediated';
18
+ /** The company's page — Datalayer's own, for `mediated` — `https` only. */
16
19
  open: string;
17
- /** The company's domain, read from the page's address: what the person can trust. */
20
+ /** The page's domain, read from its address: what the person can trust. */
18
21
  host: string;
19
22
  /** The code to enter, for `device`. */
20
23
  code?: string;
24
+ /** Where the company sent a code, for `mediated`, when it did. */
25
+ sentTo?: string;
21
26
  /** Until when, ISO 8601. */
22
27
  expiresAt?: string;
23
28
  };
@@ -26,6 +31,10 @@ export declare const PAP_SIGN_IN_WORDS: {
26
31
  readonly title: (host: string) => string;
27
32
  readonly direct: "Open the company’s page, sign in there, and come back here.";
28
33
  readonly device: "Open the company’s page and enter this code there.";
34
+ /** `mediated`: the page is Datalayer's, which names the company and asks what it asks. */
35
+ readonly mediatedTitle: (host: string) => string;
36
+ readonly mediated: "Open this page: it names the company and asks what the company needs. Type it there, never in this conversation.";
37
+ readonly sentTo: (to: string) => string;
29
38
  readonly open: (host: string) => string;
30
39
  readonly code: "Your code";
31
40
  readonly copy: "Copy the code";
@@ -37,9 +46,12 @@ export declare const PAP_SIGN_IN_WORDS: {
37
46
  readonly refused: "This sign-in link cannot be opened safely, so it is not shown.";
38
47
  readonly qr: (host: string) => string;
39
48
  };
40
- /** `pap_sign_in`, as the gateway names it with or without a prefix. */
49
+ /**
50
+ * `pap_sign_in` — or a business's `pact_sign_in` (PI-56) — as the gateway
51
+ * names it with or without a prefix.
52
+ */
41
53
  export declare const PAP_SIGN_IN_TOOL: RegExp;
42
- /** Whether a tool is `pap_sign_in`, as the gateway names it with or without a prefix. */
54
+ /** Whether a tool is `pap_sign_in` or `pact_sign_in`, as the gateway names it with or without a prefix. */
43
55
  export declare const isPapSignInTool: (name: string) => boolean;
44
56
  /** The page's address, `https` only; `null` for anything else. */
45
57
  export declare function safeSignInUrl(open: unknown): URL | null;
@@ -7,6 +7,10 @@ export const PAP_SIGN_IN_WORDS = {
7
7
  title: (host) => `Sign in at ${host}`,
8
8
  direct: 'Open the company’s page, sign in there, and come back here.',
9
9
  device: 'Open the company’s page and enter this code there.',
10
+ /** `mediated`: the page is Datalayer's, which names the company and asks what it asks. */
11
+ mediatedTitle: (host) => `Sign in through ${host}`,
12
+ mediated: 'Open this page: it names the company and asks what the company needs. Type it there, never in this conversation.',
13
+ sentTo: (to) => `The company sent a code to ${to}.`,
10
14
  open: (host) => `Open ${host}`,
11
15
  code: 'Your code',
12
16
  copy: 'Copy the code',
@@ -18,9 +22,12 @@ export const PAP_SIGN_IN_WORDS = {
18
22
  refused: 'This sign-in link cannot be opened safely, so it is not shown.',
19
23
  qr: (host) => `A code to scan to open ${host} on your phone`,
20
24
  };
21
- /** `pap_sign_in`, as the gateway names it with or without a prefix. */
22
- export const PAP_SIGN_IN_TOOL = /(^|[_.:\-/])pap_sign_in$/;
23
- /** Whether a tool is `pap_sign_in`, as the gateway names it with or without a prefix. */
25
+ /**
26
+ * `pap_sign_in` — or a business's `pact_sign_in` (PI-56) — as the gateway
27
+ * names it with or without a prefix.
28
+ */
29
+ export const PAP_SIGN_IN_TOOL = /(^|[_.:\-/])pa(?:p|ct)_sign_in$/;
30
+ /** Whether a tool is `pap_sign_in` or `pact_sign_in`, as the gateway names it with or without a prefix. */
24
31
  export const isPapSignInTool = (name) => PAP_SIGN_IN_TOOL.test(name);
25
32
  /** An MCP result's own object: its structured content, or the JSON of its text. */
26
33
  function unwrapped(result) {
@@ -73,7 +80,9 @@ export function readPapSignIn(result) {
73
80
  if (!data || data.status !== 'waiting_for_the_person') {
74
81
  return null;
75
82
  }
76
- if (data.mode !== 'direct' && data.mode !== 'device') {
83
+ if (data.mode !== 'direct' &&
84
+ data.mode !== 'device' &&
85
+ data.mode !== 'mediated') {
77
86
  return null;
78
87
  }
79
88
  const url = safeSignInUrl(data.open);
@@ -88,6 +97,11 @@ export function readPapSignIn(result) {
88
97
  open: url.toString(),
89
98
  host: url.hostname,
90
99
  ...(typeof data.code === 'string' && data.code ? { code: data.code } : {}),
100
+ ...(data.mode === 'mediated' &&
101
+ typeof data.sent_to === 'string' &&
102
+ data.sent_to
103
+ ? { sentTo: data.sent_to }
104
+ : {}),
91
105
  ...(typeof data.expires_at === 'string' && data.expires_at
92
106
  ? { expiresAt: data.expires_at }
93
107
  : {}),
@@ -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
  }
@@ -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,7 +693,8 @@ export const PROTOCOL_TOOL_CLASSES = {
694
693
  connection: 'pap',
695
694
  tools: {
696
695
  pap_company: ['read'],
697
- pap_ask: ['read'],
696
+ pap_ask: ['send'],
697
+ pap_handoff: ['send'],
698
698
  pap_sign_in: ['read'],
699
699
  pap_sign_in_status: ['read'],
700
700
  pap_read: ['read'],
@@ -707,13 +707,12 @@ export const PROTOCOL_TOOL_CLASSES = {
707
707
  ucp: {
708
708
  connection: 'ucp',
709
709
  tools: {
710
- search: ['read'],
711
- propose_completion: ['read'],
712
- get_checkout: ['read'],
713
- create_checkout: ['write'],
714
- update_checkout: ['write'],
715
- cancel_checkout: ['write'],
716
- 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'],
717
716
  },
718
717
  },
719
718
  };
@@ -974,6 +973,7 @@ export const APP_BEHAVIOURS = {
974
973
  'report-from-a-file': {},
975
974
  sales: {},
976
975
  'ship-or-fix': {},
976
+ 'shopping-desk': {},
977
977
  'supplier-comparison': {},
978
978
  'support-desk': {},
979
979
  'travel-desk': {},
@@ -1041,6 +1041,7 @@ export const APP_ESCALATIONS = {
1041
1041
  'report-from-a-file': {},
1042
1042
  sales: {},
1043
1043
  'ship-or-fix': {},
1044
+ 'shopping-desk': {},
1044
1045
  'supplier-comparison': {},
1045
1046
  'support-desk': {},
1046
1047
  'travel-desk': {},
@@ -26,6 +26,7 @@ export declare const QUOTE_CALCULATOR_APP_0_0_1: AppSpec;
26
26
  export declare const REPORT_FROM_A_FILE_APP_0_0_1: AppSpec;
27
27
  export declare const SALES_APP_0_0_1: AppSpec;
28
28
  export declare const SHIP_OR_FIX_APP_0_0_1: AppSpec;
29
+ export declare const SHOPPING_DESK_APP_0_0_1: AppSpec;
29
30
  export declare const SUPPLIER_COMPARISON_APP_0_0_1: AppSpec;
30
31
  export declare const SUPPORT_DESK_APP_0_0_1: AppSpec;
31
32
  export declare const TRAVEL_DESK_APP_0_0_1: AppSpec;