@datalayer/agent-runtimes 1.5.89 → 1.5.90

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.
@@ -44,6 +44,7 @@ export class HostKeyCredentials {
44
44
  externalUserId: request.externalUserId,
45
45
  hostKey,
46
46
  place: this.shape.placeOf(request),
47
+ ...(request.visitor ? { visitor: true } : {}),
47
48
  });
48
49
  }
49
50
  /** The exchange for one host user and deployment, with a key in hand (a delivery, an action). */
@@ -53,6 +54,7 @@ export class HostKeyCredentials {
53
54
  deployment_uid: input.deploymentUid,
54
55
  external_user_id: input.externalUserId,
55
56
  ...(input.place ? { place: input.place } : {}),
57
+ ...(input.visitor ? { visitor: true } : {}),
56
58
  }),
57
59
  headers: {
58
60
  'content-type': 'application/json',
@@ -6,7 +6,9 @@ import type { ApprovalPresentation } from './contracts';
6
6
  * presented is named by the renderer, or the card fails closed. An unknown
7
7
  * tool, an argument the renderer does not know, an attachment, and every
8
8
  * action that is not mail (reads, writes, other sends, destructive, financial,
9
- * publishing, executing) are reviewed in Datalayer (decided by the user on 16 October 2026: mail only).
9
+ * publishing, executing) are reviewed in Datalayer (decided by the user on 16 October 2026: mail only),
10
+ * but for a company's `pap_confirm`, decided in a host with the operation's
11
+ * exact revision since 28 October 2026 (the user, WHATSAPP WA-43).
10
12
  * They render ai-agents' presentation v1, a different input
11
13
  * from `../approvals.ts` and `../approvalCard.ts`. Moved from the Slack
12
14
  * service (DC-004).
@@ -39,7 +41,13 @@ type Renderer = Readonly<{
39
41
  export declare const DATALAYER_ONLY: Set<"unknown" | "read" | "write" | "send" | "delete" | "publish" | "execute" | "financial">;
40
42
  /** The words of a tool's name that make a send mail: contracts.json `mail_words`. */
41
43
  export declare const MAIL_WORDS: ReadonlySet<string>;
42
- /** The renderers, the first that matches wins: Datalayer-only before any other. */
44
+ export declare const isPapConfirm: (toolName: string) => boolean;
45
+ /** What a `pap_confirm` approval carries: contracts.json `presentation.pap_confirm_keys`. */
46
+ export declare const PAP_CONFIRM_KEYS: readonly ["key", "company", "summary", "terms", "operation_ref", "revision", "digest", "class", "session", "only_the_person", "was"];
47
+ /**
48
+ * The renderers, the first that matches wins: a company's `pap_confirm`
49
+ * (whose name says no class), then Datalayer-only before any other.
50
+ */
43
51
  export declare const RENDERERS: readonly Renderer[];
44
52
  /**
45
53
  * How a host shows an approval, by the renderer that knows its tool; with
@@ -98,6 +98,82 @@ const mail = {
98
98
  return rendered('mail', presentation, fields, MAIL_KEYS, extra);
99
99
  },
100
100
  };
101
+ /** `pap_confirm`, as the gateway names it with or without a prefix. */
102
+ const PAP_CONFIRM = /(^|[_.:\-/])pap_confirm$/;
103
+ export const isPapConfirm = (toolName) => PAP_CONFIRM.test(toolName);
104
+ /** What a `pap_confirm` approval carries: contracts.json `presentation.pap_confirm_keys`. */
105
+ export const PAP_CONFIRM_KEYS = [
106
+ 'key',
107
+ 'company',
108
+ 'summary',
109
+ 'terms',
110
+ 'operation_ref',
111
+ 'revision',
112
+ 'digest',
113
+ 'class',
114
+ 'session',
115
+ 'only_the_person',
116
+ 'was',
117
+ ];
118
+ /**
119
+ * A company's operation the person confirms (WHATSAPP WA-43, decided by the
120
+ * user 28 October 2026): the company, its own summary, every term, the
121
+ * operation and its **exact revision** — a decision is on that revision and
122
+ * no other; a changed revision is another approval. A missing company,
123
+ * summary, operation, revision or digest fails the card closed.
124
+ */
125
+ const papConfirm = {
126
+ name: 'pap-confirm',
127
+ matches: presentation => isPapConfirm(presentation.tool_name),
128
+ render(presentation) {
129
+ const args = presentation.arguments;
130
+ const fields = [];
131
+ const missing = [];
132
+ const extra = [];
133
+ const company = typeof args.company === 'string' ? args.company : '';
134
+ const summary = typeof args.summary === 'string' ? args.summary : '';
135
+ const operation = typeof args.operation_ref === 'string' ? args.operation_ref : '';
136
+ const revision = Number.isInteger(args.revision)
137
+ ? args.revision
138
+ : undefined;
139
+ if (!company)
140
+ missing.push('the company');
141
+ else
142
+ fields.push({ label: 'Company', value: company });
143
+ if (!summary)
144
+ missing.push('what it does, in its words');
145
+ else
146
+ fields.push({ label: 'Summary', value: summary });
147
+ const termsAreFields = args.terms === undefined ||
148
+ args.terms === null ||
149
+ (typeof args.terms === 'object' && !Array.isArray(args.terms));
150
+ if (!termsAreFields)
151
+ extra.push('Its terms are not named one by one: review it in Datalayer.');
152
+ else
153
+ for (const [name, value] of Object.entries((args.terms ?? {})))
154
+ fields.push({ label: name, value: text(value) });
155
+ if (!operation)
156
+ missing.push('the operation');
157
+ else
158
+ fields.push({ label: 'Operation', value: operation });
159
+ if (revision === undefined)
160
+ missing.push('its exact revision');
161
+ else
162
+ fields.push({ label: 'Revision', value: String(revision) });
163
+ if (typeof args.digest !== 'string' || !args.digest)
164
+ missing.push('the digest of its terms');
165
+ if (typeof args.class === 'string' && args.class)
166
+ fields.push({ label: 'What it does', value: args.class });
167
+ const was = args.was && typeof args.was === 'object'
168
+ ? args.was
169
+ : undefined;
170
+ if (was && was.revision !== undefined)
171
+ fields.push({ label: 'Replaces revision', value: text(was.revision) });
172
+ if (missing.length)
173
+ extra.push(`The request does not say ${missing.join(', ')}: review it in Datalayer.`);
174
+ return rendered('pap-confirm', presentation, fields, PAP_CONFIRM_KEYS, extra);
175
+ },
176
+ };
101
177
  /** Everything but mail: shown, decided only in Datalayer. */
102
178
  const datalayerOnly = {
103
179
  name: 'datalayer-only',
@@ -122,8 +198,11 @@ const datalayerOnly = {
122
198
  };
123
199
  },
124
200
  };
125
- /** The renderers, the first that matches wins: Datalayer-only before any other. */
126
- export const RENDERERS = [datalayerOnly, mail];
201
+ /**
202
+ * The renderers, the first that matches wins: a company's `pap_confirm`
203
+ * (whose name says no class), then Datalayer-only before any other.
204
+ */
205
+ export const RENDERERS = [papConfirm, datalayerOnly, mail];
127
206
  /**
128
207
  * How a host shows an approval, by the renderer that knows its tool; with
129
208
  * none, nothing is decided in the host.
@@ -93,6 +93,7 @@ export declare const exchangeSchema: z.ZodObject<{
93
93
  expires_in: z.ZodNumber;
94
94
  exp: z.ZodNumber;
95
95
  principal_uid: z.ZodString;
96
+ visitor: z.ZodOptional<z.ZodBoolean>;
96
97
  host_uid: z.ZodString;
97
98
  deployment: z.ZodObject<{
98
99
  uid: z.ZodString;
@@ -104,6 +104,8 @@ export const exchangeSchema = z.object({
104
104
  expires_in: z.number().int().nonnegative(),
105
105
  exp: z.number().int(),
106
106
  principal_uid: z.string().min(1),
107
+ /** A person who never linked, answered as a visitor (WHATSAPP WA-03): `principal_uid` is `visitor:<hash>`. */
108
+ visitor: z.boolean().optional(),
107
109
  host_uid: z.string().min(1),
108
110
  deployment: z.object({
109
111
  uid: z.string().min(1),
@@ -114,6 +114,45 @@
114
114
  "direct"
115
115
  ]
116
116
  },
117
+ "visitor_exchange": {
118
+ "about": "WHATSAPP WA-03 (decided 28 October 2026): a host's user who never linked, answered as a visitor when the installation's owner turned its visitors switch on and the deployment opens to somebody signed out. sub is visitor:<hash of the host's id with the installation's salt>; no link; a daily cap per visitor at the exchange (429).",
119
+ "request": {
120
+ "external_user_id": "US.13491208655302741918",
121
+ "deployment_uid": "dep-1",
122
+ "visitor": true
123
+ },
124
+ "response_keys": [
125
+ "success",
126
+ "access_token",
127
+ "token_type",
128
+ "expires_in",
129
+ "exp",
130
+ "principal_uid",
131
+ "visitor",
132
+ "host_uid",
133
+ "deployment",
134
+ "session_api"
135
+ ],
136
+ "token_claims": [
137
+ "jti",
138
+ "iss",
139
+ "iat",
140
+ "exp",
141
+ "aud",
142
+ "sub",
143
+ "azp",
144
+ "host_kind",
145
+ "deployment_uid",
146
+ "app_uid",
147
+ "visitor",
148
+ "scope"
149
+ ],
150
+ "sub_prefix": "visitor:",
151
+ "refusals": {
152
+ "visitors_off": 403,
153
+ "capped": 429
154
+ }
155
+ },
117
156
  "whoami": {
118
157
  "response_keys": [
119
158
  "success",
@@ -124,7 +163,8 @@
124
163
  "app_uid",
125
164
  "link",
126
165
  "scope",
127
- "expires_in"
166
+ "expires_in",
167
+ "visitor"
128
168
  ]
129
169
  },
130
170
  "bind_session": {
@@ -328,7 +368,21 @@
328
368
  "host_may_decide": true,
329
369
  "reasons": [],
330
370
  "digest": "0000000000000000000000000000000000000000000000000000000000000000"
331
- }
371
+ },
372
+ "pap_confirm_keys": [
373
+ "key",
374
+ "company",
375
+ "summary",
376
+ "terms",
377
+ "operation_ref",
378
+ "revision",
379
+ "digest",
380
+ "class",
381
+ "session",
382
+ "only_the_person",
383
+ "was"
384
+ ],
385
+ "pap_confirm_about": "WHATSAPP WA-43 (decided 28 October 2026): a company's pap_confirm (any gateway prefix) is decided in a host, on its exact revision; every other non-mail action stays in Datalayer."
332
386
  },
333
387
  "decision": {
334
388
  "request": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@datalayer/agent-runtimes",
3
- "version": "1.5.89",
3
+ "version": "1.5.90",
4
4
  "type": "module",
5
5
  "workspaces": [
6
6
  ".",
@@ -356,18 +356,6 @@ def generate_typescript_code(specs: list[dict[str, Any]]) -> str:
356
356
 
357
357
  def update_init_file(specs: list[dict[str, Any]], init_file: Path) -> None:
358
358
  """Update the __init__.py file with correct imports based on generated specs."""
359
- # Generate list of MCP server constant names
360
- server_constants = []
361
- for spec in specs:
362
- server_id = spec["id"]
363
- version = spec.get("version", "0.0.1")
364
- const_name = (
365
- server_id.upper().replace("-", "_")
366
- + "_MCP_SERVER"
367
- + version_suffix(version)
368
- )
369
- server_constants.append(const_name)
370
-
371
359
  # Read the current __init__.py
372
360
  init_content = init_file.read_text()
373
361
 
@@ -386,9 +374,12 @@ def update_init_file(specs: list[dict[str, Any]], init_file: Path) -> None:
386
374
  return
387
375
 
388
376
  # Generate new import lines - all names sorted alphabetically (ruff/isort order)
377
+ # Only the catalogue and its readers are re-exported: entries are read by
378
+ # id, never by a version-named constant (decided 14 October 2026), so the
379
+ # *_MCP_SERVER_<version> constants stay in catalog_mcp_servers alone and a
380
+ # version bump touches no caller (test_catalogue_by_id.py holds it).
389
381
  all_names = sorted(
390
- server_constants
391
- + [
382
+ [
392
383
  "MCP_SERVER_CATALOG",
393
384
  "check_env_vars_available",
394
385
  "get_catalog_server",
@@ -423,8 +414,6 @@ def update_init_file(specs: list[dict[str, Any]], init_file: Path) -> None:
423
414
  ' "get_catalog_server",',
424
415
  ' "list_catalog_servers",',
425
416
  ]
426
- for const in sorted(server_constants):
427
- all_entries.append(f' "{const}",')
428
417
  all_entries.append("]")
429
418
 
430
419
  # Replace the section