@datalayer/agent-runtimes 1.5.47 → 1.5.48

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.
@@ -5,8 +5,9 @@ import type { ApprovalPresentation } from './contracts';
5
5
  * tool proves that every material argument is shown: each argument the server
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
- * destructive, financial or publishing action are reviewed in Datalayer
9
- * (SLACK §15, 5). They render ai-agents' presentation v1, a different input
8
+ * action that is not mail (reads, writes, other sends, destructive, financial,
9
+ * publishing, executing) are reviewed in Datalayer (SLACK §15, 5, decided by
10
+ * the user on 16 October 2026: mail only). They render ai-agents' presentation v1, a different input
10
11
  * from `../approvals.ts` and `../approvalCard.ts`. Moved from the Slack
11
12
  * service (plans/DISCORD.md DC-004).
12
13
  *
@@ -31,8 +32,14 @@ type Renderer = Readonly<{
31
32
  matches(presentation: ApprovalPresentation): boolean;
32
33
  render(presentation: ApprovalPresentation): RenderedAction;
33
34
  }>;
34
- /** Action classes that are decided in Datalayer only (§15, 5). */
35
+ /**
36
+ * Action classes that are decided in Datalayer only: every class but `send`,
37
+ * since only mail is decided in a host (decided by the user on 16 October
38
+ * 2026, SLACK §15, 5).
39
+ */
35
40
  export declare const DATALAYER_ONLY: Set<"unknown" | "read" | "write" | "send" | "delete" | "publish" | "execute" | "financial">;
41
+ /** The words of a tool's name that make a send mail: contracts.json `mail_words`. */
42
+ export declare const MAIL_WORDS: ReadonlySet<string>;
36
43
  /** The renderers, the first that matches wins: Datalayer-only before any other. */
37
44
  export declare const RENDERERS: readonly Renderer[];
38
45
  /**
@@ -2,8 +2,14 @@
2
2
  * Copyright (c) 2025-2026 Datalayer, Inc.
3
3
  * Distributed under the terms of the Modified BSD License.
4
4
  */
5
- /** Action classes that are decided in Datalayer only (§15, 5). */
5
+ /**
6
+ * Action classes that are decided in Datalayer only: every class but `send`,
7
+ * since only mail is decided in a host (decided by the user on 16 October
8
+ * 2026, SLACK §15, 5).
9
+ */
6
10
  export const DATALAYER_ONLY = new Set([
11
+ 'read',
12
+ 'write',
7
13
  'delete',
8
14
  'publish',
9
15
  'financial',
@@ -28,7 +34,19 @@ function rendered(name, presentation, fields, known, extra = []) {
28
34
  ];
29
35
  return { complete: reasons.length === 0, fields, reasons, renderer: name };
30
36
  }
31
- const MAIL_TOOL = /(^|[_.-])(send|draft)[_.-]?(e?mail|message)($|[_.-])|^gmail[_.-]send|^mail[_.-]send$/i;
37
+ /** The words of a tool's name that make a send mail: contracts.json `mail_words`. */
38
+ export const MAIL_WORDS = new Set([
39
+ 'email',
40
+ 'gmail',
41
+ 'mail',
42
+ ]);
43
+ /** A tool name's words, as ai-agents reads them (camelCase and separators split). */
44
+ const wordsOf = (name) => name
45
+ .replace(/([a-z0-9])([A-Z])/g, '$1 $2')
46
+ .toLowerCase()
47
+ .split(/[^a-z0-9]+/)
48
+ .filter(Boolean);
49
+ const isMail = (toolName) => wordsOf(toolName).some(word => MAIL_WORDS.has(word));
32
50
  const MAIL_KEYS = [
33
51
  'to',
34
52
  'cc',
@@ -42,8 +60,7 @@ const MAIL_KEYS = [
42
60
  /** Mail: every recipient, the subject, the whole body; an attachment goes to Datalayer. */
43
61
  const mail = {
44
62
  name: 'mail',
45
- matches: presentation => presentation.action_class === 'send' &&
46
- MAIL_TOOL.test(presentation.tool_name),
63
+ matches: presentation => presentation.action_class === 'send' && isMail(presentation.tool_name),
47
64
  render(presentation) {
48
65
  const args = presentation.arguments;
49
66
  const fields = [];
@@ -82,53 +99,14 @@ const mail = {
82
99
  return rendered('mail', presentation, fields, MAIL_KEYS, extra);
83
100
  },
84
101
  };
85
- const WRITE_TOOL = /(^|[_.-])(write|update|edit|append|create|save|set|rename|move)[_.-]?/i;
86
- const WRITE_KEYS = [
87
- 'path',
88
- 'file',
89
- 'filename',
90
- 'name',
91
- 'title',
92
- 'content',
93
- 'text',
94
- 'new_path',
95
- 'destination',
96
- 'mode',
97
- ];
98
- /** A write: where, and the whole of what is written. */
99
- const write = {
100
- name: 'write',
101
- matches: presentation => presentation.action_class === 'write' &&
102
- WRITE_TOOL.test(presentation.tool_name),
103
- render(presentation) {
104
- const args = presentation.arguments;
105
- const fields = [];
106
- for (const [key, label] of [
107
- ['path', 'Where'],
108
- ['file', 'Where'],
109
- ['filename', 'Where'],
110
- ['name', 'Name'],
111
- ['title', 'Title'],
112
- ['new_path', 'Moved to'],
113
- ['destination', 'Moved to'],
114
- ['mode', 'How'],
115
- ['content', 'What is written'],
116
- ['text', 'What is written'],
117
- ])
118
- if (args[key] !== undefined)
119
- fields.push({ label, value: text(args[key]) });
120
- const extra = fields.length === 0
121
- ? ['The request does not say what it writes: review it in Datalayer.']
122
- : [];
123
- return rendered('write', presentation, fields, WRITE_KEYS, extra);
124
- },
125
- };
126
- /** Delete, publish, money and running code: shown, decided only in Datalayer (§15, 5). */
102
+ /** Everything but mail: shown, decided only in Datalayer (§15, 5). */
127
103
  const datalayerOnly = {
128
104
  name: 'datalayer-only',
129
105
  matches: presentation => DATALAYER_ONLY.has(presentation.action_class),
130
106
  render(presentation) {
131
107
  const words = {
108
+ read: 'Only mail is decided outside Datalayer: reading is decided in Datalayer.',
109
+ write: 'Only mail is decided outside Datalayer: writing is decided in Datalayer.',
132
110
  delete: 'Deleting is decided in Datalayer.',
133
111
  execute: 'Running code is decided in Datalayer.',
134
112
  financial: 'Moving money is decided in Datalayer.',
@@ -145,22 +123,8 @@ const datalayerOnly = {
145
123
  };
146
124
  },
147
125
  };
148
- /** A read: every argument, by its name, whole — nothing is changed or sent. */
149
- const read = {
150
- name: 'read',
151
- matches: presentation => presentation.action_class === 'read',
152
- render(presentation) {
153
- const fields = Object.entries(presentation.arguments).map(([key, value]) => ({ label: key, value: text(value) }));
154
- return rendered('read', presentation, fields, Object.keys(presentation.arguments));
155
- },
156
- };
157
126
  /** The renderers, the first that matches wins: Datalayer-only before any other. */
158
- export const RENDERERS = [
159
- datalayerOnly,
160
- mail,
161
- write,
162
- read,
163
- ];
127
+ export const RENDERERS = [datalayerOnly, mail];
164
128
  /**
165
129
  * How a host shows an approval, by the renderer that knows its tool; with
166
130
  * none, nothing is decided in the host.
@@ -239,9 +239,12 @@
239
239
  "unknown"
240
240
  ],
241
241
  "host_decidable": [
242
- "read",
243
- "send",
244
- "write"
242
+ "send"
243
+ ],
244
+ "mail_words": [
245
+ "email",
246
+ "gmail",
247
+ "mail"
245
248
  ],
246
249
  "example": {
247
250
  "version": 1,
@@ -24,7 +24,7 @@ export * from './contracts';
24
24
  export { DatalayerHostClient, HostRefusedError, type BoundHost, type HostSelf, } from './client';
25
25
  export { agUiEventsOf, assertTrustedStreamUrl, DevelopmentCredentials, HostKeyCredentials, hostSessionOf, HostStarters, HttpAgentClient, InstallationCredentials, lastAnswer, type AgentClient, type HostCredentials, type HostKeyLookup, type HostSession, type HostShape, type HostTurnRequest, type Place, type Starter, type StarterSource, type TurnStream, } from './agentClient';
26
26
  export { hostSessionUid } from './sessionUid';
27
- export { DATALAYER_ONLY, RENDERERS, renderApproval, type RenderedAction, type RenderedField, } from './approvalRenderers';
27
+ export { DATALAYER_ONLY, MAIL_WORDS, RENDERERS, renderApproval, type RenderedAction, type RenderedField, } from './approvalRenderers';
28
28
  export { DELIVERY_PATH, DELIVERY_ROUTE, DeliveryPoller, deliveryRoute, receiveDelivery, type BoundHostKey, type DeliveryClaimStore, type DeliveryJob, type DeliverySink, } from './deliveries';
29
29
  export { TurnState, type TurnEnding, type TurnFile, type TurnTool, } from './turnState';
30
30
  export { resolveApplication, usableAllowances, type AllowanceView, type ApplicationRefusal, type ApplicationResolution, type ChannelRule, } from './resolution';
@@ -29,7 +29,7 @@ export * from './contracts';
29
29
  export { DatalayerHostClient, HostRefusedError, } from './client';
30
30
  export { agUiEventsOf, assertTrustedStreamUrl, DevelopmentCredentials, HostKeyCredentials, hostSessionOf, HostStarters, HttpAgentClient, InstallationCredentials, lastAnswer, } from './agentClient';
31
31
  export { hostSessionUid } from './sessionUid';
32
- export { DATALAYER_ONLY, RENDERERS, renderApproval, } from './approvalRenderers';
32
+ export { DATALAYER_ONLY, MAIL_WORDS, RENDERERS, renderApproval, } from './approvalRenderers';
33
33
  export { DELIVERY_PATH, DELIVERY_ROUTE, DeliveryPoller, deliveryRoute, receiveDelivery, } from './deliveries';
34
34
  export { TurnState, } from './turnState';
35
35
  export { resolveApplication, usableAllowances, } from './resolution';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@datalayer/agent-runtimes",
3
- "version": "1.5.47",
3
+ "version": "1.5.48",
4
4
  "type": "module",
5
5
  "workspaces": [
6
6
  ".",