@datalayer/agent-runtimes 1.5.45 → 1.5.46

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 (52) hide show
  1. package/dist-embed/datalayer-app-main.js +3 -3
  2. package/lib/portable/hosts/__tests__/fixtures/lock-with-jupyter.json +40 -0
  3. package/lib/portable/hosts/__tests__/fixtures/npm-ls-with-jupyter.json +21 -0
  4. package/lib/portable/hosts/agentClient.d.ts +167 -0
  5. package/lib/portable/hosts/agentClient.js +346 -0
  6. package/lib/portable/hosts/approvalRenderers.d.ts +43 -0
  7. package/lib/portable/hosts/approvalRenderers.js +180 -0
  8. package/lib/portable/hosts/client.d.ts +95 -0
  9. package/lib/portable/hosts/client.js +174 -0
  10. package/lib/portable/hosts/contracts.d.ts +199 -0
  11. package/lib/portable/hosts/contracts.js +235 -0
  12. package/lib/portable/hosts/contracts.json +381 -0
  13. package/lib/portable/hosts/deadLetters.d.ts +41 -0
  14. package/lib/portable/hosts/deadLetters.js +124 -0
  15. package/lib/portable/hosts/deliveries.d.ts +76 -0
  16. package/lib/portable/hosts/deliveries.js +147 -0
  17. package/lib/portable/hosts/forbiddenGraph.d.ts +77 -0
  18. package/lib/portable/hosts/forbiddenGraph.js +174 -0
  19. package/lib/portable/hosts/health.d.ts +35 -0
  20. package/lib/portable/hosts/health.js +92 -0
  21. package/lib/portable/hosts/index.d.ts +40 -0
  22. package/lib/portable/hosts/index.js +45 -0
  23. package/lib/portable/hosts/observability.d.ts +67 -0
  24. package/lib/portable/hosts/observability.js +171 -0
  25. package/lib/portable/hosts/postgresQueue.d.ts +55 -0
  26. package/lib/portable/hosts/postgresQueue.js +217 -0
  27. package/lib/portable/hosts/queue.d.ts +47 -0
  28. package/lib/portable/hosts/queue.js +63 -0
  29. package/lib/portable/hosts/quotas.d.ts +81 -0
  30. package/lib/portable/hosts/quotas.js +147 -0
  31. package/lib/portable/hosts/rateLimit.d.ts +13 -0
  32. package/lib/portable/hosts/rateLimit.js +8 -0
  33. package/lib/portable/hosts/resolution.d.ts +54 -0
  34. package/lib/portable/hosts/resolution.js +46 -0
  35. package/lib/portable/hosts/schema.d.ts +19 -0
  36. package/lib/portable/hosts/schema.js +81 -0
  37. package/lib/portable/hosts/secretCipher.d.ts +23 -0
  38. package/lib/portable/hosts/secretCipher.js +45 -0
  39. package/lib/portable/hosts/sessionUid.d.ts +7 -0
  40. package/lib/portable/hosts/sessionUid.js +28 -0
  41. package/lib/portable/hosts/turnState.d.ts +45 -0
  42. package/lib/portable/hosts/turnState.js +113 -0
  43. package/lib/portable/index.d.ts +2 -0
  44. package/lib/portable/index.js +3 -0
  45. package/lib/portable/semantic-events.json +480 -0
  46. package/lib/portable/semantic.d.ts +78 -0
  47. package/lib/portable/semantic.js +100 -0
  48. package/lib/portable/semanticOfAgUi.d.ts +41 -0
  49. package/lib/portable/semanticOfAgUi.js +108 -0
  50. package/lib/protocols/A2AAdapter.js +19 -0
  51. package/lib/test-setup.js +17 -14
  52. package/package.json +1 -1
@@ -0,0 +1,180 @@
1
+ /*
2
+ * Copyright (c) 2025-2026 Datalayer, Inc.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ /** Action classes that are decided in Datalayer only (§15, 5). */
6
+ export const DATALAYER_ONLY = new Set([
7
+ 'delete',
8
+ 'publish',
9
+ 'financial',
10
+ 'execute',
11
+ 'unknown',
12
+ ]);
13
+ const text = (value) => typeof value === 'string' ? value : JSON.stringify(value, null, 2);
14
+ const list = (value) => Array.isArray(value) ? value.map(text).join(', ') : text(value);
15
+ /** The arguments a renderer did not account for: each fails the card closed. */
16
+ function unknownKeys(presentation, known) {
17
+ return Object.keys(presentation.arguments).filter(key => !known.includes(key));
18
+ }
19
+ function rendered(name, presentation, fields, known, extra = []) {
20
+ const unknown = unknownKeys(presentation, known);
21
+ const reasons = [
22
+ ...extra,
23
+ ...(unknown.length
24
+ ? [
25
+ `@Datalayer does not know how to show ${unknown.join(', ')} of this action: review it in Datalayer.`,
26
+ ]
27
+ : []),
28
+ ];
29
+ return { complete: reasons.length === 0, fields, reasons, renderer: name };
30
+ }
31
+ const MAIL_TOOL = /(^|[_.-])(send|draft)[_.-]?(e?mail|message)($|[_.-])|^gmail[_.-]send|^mail[_.-]send$/i;
32
+ const MAIL_KEYS = [
33
+ 'to',
34
+ 'cc',
35
+ 'bcc',
36
+ 'subject',
37
+ 'body',
38
+ 'html',
39
+ 'attachments',
40
+ 'reply_to',
41
+ ];
42
+ /** Mail: every recipient, the subject, the whole body; an attachment goes to Datalayer. */
43
+ const mail = {
44
+ name: 'mail',
45
+ matches: presentation => presentation.action_class === 'send' &&
46
+ MAIL_TOOL.test(presentation.tool_name),
47
+ render(presentation) {
48
+ const args = presentation.arguments;
49
+ const fields = [];
50
+ const missing = [];
51
+ const extra = [];
52
+ for (const [key, label] of [
53
+ ['to', 'To'],
54
+ ['cc', 'Cc'],
55
+ ['bcc', 'Bcc'],
56
+ ['reply_to', 'Reply to'],
57
+ ['subject', 'Subject'],
58
+ ])
59
+ if (args[key] !== undefined && args[key] !== null && args[key] !== '')
60
+ fields.push({ label, value: list(args[key]) });
61
+ if (args.to === undefined || args.to === '' || args.to === null)
62
+ missing.push('its recipients');
63
+ if (args.subject === undefined)
64
+ missing.push('its subject');
65
+ if (args.html !== undefined && args.html !== null && args.html !== '')
66
+ extra.push('It sends formatted mail, which a chat cannot show as it will be read: review it in Datalayer.');
67
+ if (args.body === undefined && args.html === undefined)
68
+ missing.push('its body');
69
+ else if (args.body !== undefined)
70
+ fields.push({ label: 'Body', value: text(args.body) });
71
+ const attachments = Array.isArray(args.attachments)
72
+ ? args.attachments
73
+ : args.attachments
74
+ ? [args.attachments]
75
+ : [];
76
+ if (attachments.length > 0) {
77
+ fields.push({ label: 'Attachments', value: list(attachments) });
78
+ extra.push('It sends attachments, which are reviewed in Datalayer, where you can open them.');
79
+ }
80
+ if (missing.length)
81
+ extra.push(`The request does not say ${missing.join(', ')}: review it in Datalayer.`);
82
+ return rendered('mail', presentation, fields, MAIL_KEYS, extra);
83
+ },
84
+ };
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). */
127
+ const datalayerOnly = {
128
+ name: 'datalayer-only',
129
+ matches: presentation => DATALAYER_ONLY.has(presentation.action_class),
130
+ render(presentation) {
131
+ const words = {
132
+ delete: 'Deleting is decided in Datalayer.',
133
+ execute: 'Running code is decided in Datalayer.',
134
+ financial: 'Moving money is decided in Datalayer.',
135
+ publish: 'Publishing is decided in Datalayer.',
136
+ unknown: '@Datalayer does not know this action: it is decided in Datalayer.',
137
+ };
138
+ return {
139
+ complete: false,
140
+ fields: presentation.fields.map(({ label, value }) => ({ label, value })),
141
+ reasons: [
142
+ words[presentation.action_class] ?? 'It is decided in Datalayer.',
143
+ ],
144
+ renderer: 'datalayer-only',
145
+ };
146
+ },
147
+ };
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
+ /** 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
+ ];
164
+ /**
165
+ * How a host shows an approval, by the renderer that knows its tool; with
166
+ * none, nothing is decided in the host.
167
+ */
168
+ export function renderApproval(presentation) {
169
+ const renderer = RENDERERS.find(one => one.matches(presentation));
170
+ if (!renderer)
171
+ return {
172
+ complete: false,
173
+ fields: presentation.fields.map(({ label, value }) => ({ label, value })),
174
+ reasons: [
175
+ `@Datalayer has no way yet to show ${presentation.tool_name} whole: review it in Datalayer.`,
176
+ ],
177
+ renderer: 'none',
178
+ };
179
+ return renderer.render(presentation);
180
+ }
@@ -0,0 +1,95 @@
1
+ import { type ApprovalPresentation, type HostDelivery } from './contracts';
2
+ /** What ai-agents refused, with its status and its sentence. */
3
+ export declare class HostRefusedError extends Error {
4
+ readonly status: number;
5
+ readonly detail: string;
6
+ readonly what: string;
7
+ constructor(status: number, detail: string, what: string);
8
+ }
9
+ export type HostSelf = Readonly<{
10
+ accountUid: string;
11
+ channels: ReadonlyArray<{
12
+ deployment: string;
13
+ id: string;
14
+ state: 'allow' | 'deny';
15
+ }>;
16
+ defaultDeployment?: string;
17
+ deployments: ReadonlyArray<{
18
+ appName: string;
19
+ kept: boolean;
20
+ uid: string;
21
+ }>;
22
+ directOnly: readonly string[];
23
+ paused: boolean;
24
+ }>;
25
+ export type BoundHost = Readonly<{
26
+ defaultDeployment?: string;
27
+ hostKey: string;
28
+ hostUid: string;
29
+ name?: string;
30
+ }>;
31
+ /**
32
+ * The generic host routes of ai-agents (plans/SLACK.md §4.3, plans/DISCORD.md
33
+ * §4.3) a host calls besides the credential exchange: as the installation,
34
+ * with its key in `X-Datalayer-Host-Key`, or as a person, with the host token
35
+ * the exchange answered. Any host speaks them.
36
+ */
37
+ export declare class DatalayerHostClient {
38
+ #private;
39
+ readonly apiUrl: string;
40
+ readonly fetchImplementation: typeof fetch;
41
+ constructor(apiUrl: string, fetchImplementation?: typeof fetch);
42
+ /** Bind the installation its owner set up, with the code their browser carried (§3.1). */
43
+ bind(input: {
44
+ code: string;
45
+ externalId: string;
46
+ name?: string;
47
+ }): Promise<BoundHost>;
48
+ /** The installation was removed from its host: it ends at Datalayer (revocation). */
49
+ uninstalled(hostKey: string): Promise<void>;
50
+ /**
51
+ * The installation as Datalayer keeps it: the deployments it allows (with
52
+ * their application's name and whether each is kept on), the default, the
53
+ * channel policy and whether it is paused.
54
+ */
55
+ self(hostKey: string): Promise<HostSelf>;
56
+ /** The host unlinks one of its users: how many links ended. */
57
+ unlink(hostKey: string, externalUserId: string): Promise<number>;
58
+ /** A one-use code a host user redeems, signed in to Datalayer, to link their account. */
59
+ linkCode(hostKey: string, externalUserId: string, externalUserName?: string): Promise<{
60
+ code: string;
61
+ expiresIn: number;
62
+ }>;
63
+ /** Whether a host user is linked, and to which principal. */
64
+ linkState(hostKey: string, externalUserId: string): Promise<{
65
+ linked: boolean;
66
+ principalUid?: string;
67
+ }>;
68
+ /** Where deliveries are pushed: answered the secret they are signed with, once. */
69
+ setWebhook(hostKey: string, url: string): Promise<{
70
+ signingSecret: string;
71
+ version: number;
72
+ }>;
73
+ /** What the installation has not acknowledged, oldest first: pushed or not. */
74
+ outbox(hostKey: string, limit?: number): Promise<HostDelivery[]>;
75
+ acknowledge(hostKey: string, deliveryUid: string): Promise<void>;
76
+ /**
77
+ * *Open in Datalayer* (S-305): a one-use code for the host token's person
78
+ * that opens one session or approval after sign-in. Answers the landing's
79
+ * path that redeems it; never a bearer token in a URL.
80
+ */
81
+ handoff(token: string, kind: 'session' | 'approval', target: string): Promise<{
82
+ path: string;
83
+ expiresIn: number;
84
+ }>;
85
+ /** How an approval is shown, read as the person it waits on (their host token). */
86
+ presentation(token: string, approvalId: string): Promise<ApprovalPresentation>;
87
+ /** Decide an approval as its person, on the very presentation shown (its digest). */
88
+ decide(token: string, approvalId: string, decision: {
89
+ approve: boolean;
90
+ digest: string;
91
+ note?: string;
92
+ }): Promise<{
93
+ status: string;
94
+ }>;
95
+ }
@@ -0,0 +1,174 @@
1
+ /*
2
+ * Copyright (c) 2025-2026 Datalayer, Inc.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ /**
6
+ * The ai-agents host client every chat host calls (moved from the Slack
7
+ * service, plans/DISCORD.md DC-004).
8
+ *
9
+ * @module portable/hosts/client
10
+ */
11
+ import { z } from 'zod';
12
+ import { bindSchema, decisionSchema, deliveryOfOutbox, HOST_KEY_HEADER, linkCodeSchema, linkStateSchema, handoffSchema, presentationSchema, selfSchema, webhookSchema, } from './contracts';
13
+ /** What ai-agents refused, with its status and its sentence. */
14
+ export class HostRefusedError extends Error {
15
+ status;
16
+ detail;
17
+ what;
18
+ constructor(status, detail, what) {
19
+ super(`${what} (${status}): ${detail}`);
20
+ this.status = status;
21
+ this.detail = detail;
22
+ this.what = what;
23
+ }
24
+ }
25
+ /**
26
+ * The generic host routes of ai-agents (plans/SLACK.md §4.3, plans/DISCORD.md
27
+ * §4.3) a host calls besides the credential exchange: as the installation,
28
+ * with its key in `X-Datalayer-Host-Key`, or as a person, with the host token
29
+ * the exchange answered. Any host speaks them.
30
+ */
31
+ export class DatalayerHostClient {
32
+ apiUrl;
33
+ fetchImplementation;
34
+ constructor(apiUrl, fetchImplementation = fetch) {
35
+ this.apiUrl = apiUrl;
36
+ this.fetchImplementation = fetchImplementation;
37
+ }
38
+ #url(path) {
39
+ return `${this.apiUrl}/api/ai-agents/v1${path}`;
40
+ }
41
+ async #call(what, path, init, schema) {
42
+ const headers = {};
43
+ if (init.body !== undefined)
44
+ headers['content-type'] = 'application/json';
45
+ if (init.hostKey)
46
+ headers[HOST_KEY_HEADER] = init.hostKey;
47
+ if (init.token)
48
+ headers.authorization = `Bearer ${init.token}`;
49
+ const response = await this.fetchImplementation(this.#url(path), {
50
+ body: init.body === undefined ? undefined : JSON.stringify(init.body),
51
+ headers,
52
+ method: init.method ?? 'GET',
53
+ });
54
+ const body = (await response.json().catch(() => ({})));
55
+ if (!response.ok || body.success === false)
56
+ throw new HostRefusedError(response.status, typeof body.detail === 'string' ? body.detail : 'refused', what);
57
+ return schema.parse(body);
58
+ }
59
+ /** Bind the installation its owner set up, with the code their browser carried (§3.1). */
60
+ async bind(input) {
61
+ const bound = await this.#call('Datalayer did not bind the installation', '/apps/hosts/bind', {
62
+ body: {
63
+ code: input.code,
64
+ external_id: input.externalId,
65
+ name: input.name ?? '',
66
+ },
67
+ method: 'POST',
68
+ }, bindSchema);
69
+ return {
70
+ defaultDeployment: bound.host.default_deployment || undefined,
71
+ hostKey: bound.host_key,
72
+ hostUid: bound.host.uid,
73
+ name: bound.host.name,
74
+ };
75
+ }
76
+ /** The installation was removed from its host: it ends at Datalayer (revocation). */
77
+ async uninstalled(hostKey) {
78
+ await this.#call('Datalayer did not end the installation', '/apps/hosts/self', { hostKey, method: 'DELETE' }, z.unknown());
79
+ }
80
+ /**
81
+ * The installation as Datalayer keeps it: the deployments it allows (with
82
+ * their application's name and whether each is kept on), the default, the
83
+ * channel policy and whether it is paused.
84
+ */
85
+ async self(hostKey) {
86
+ const answered = await this.#call('Datalayer did not answer the installation', '/apps/hosts/self', { hostKey }, selfSchema);
87
+ const host = answered.host;
88
+ return {
89
+ accountUid: host.account_uid ?? '',
90
+ channels: (host.channels ?? []).map(one => ({
91
+ deployment: one.deployment ?? '',
92
+ id: one.id,
93
+ state: one.state,
94
+ })),
95
+ defaultDeployment: host.default_deployment || undefined,
96
+ deployments: host.deployments.map(one => ({
97
+ appName: one.app_name ?? '',
98
+ kept: one.kept ?? false,
99
+ uid: one.uid,
100
+ })),
101
+ directOnly: host.direct_only ?? [],
102
+ paused: host.paused ?? false,
103
+ };
104
+ }
105
+ /** The host unlinks one of its users: how many links ended. */
106
+ async unlink(hostKey, externalUserId) {
107
+ const answered = await this.#call('Datalayer did not unlink the account', `/apps/hosts/self/links/${encodeURIComponent(externalUserId)}`, { hostKey, method: 'DELETE' }, z.object({ revoked: z.number().int() }));
108
+ return answered.revoked;
109
+ }
110
+ /** A one-use code a host user redeems, signed in to Datalayer, to link their account. */
111
+ async linkCode(hostKey, externalUserId, externalUserName = '') {
112
+ const answered = await this.#call('Datalayer did not answer a link code', '/apps/hosts/self/link-codes', {
113
+ body: {
114
+ external_user_id: externalUserId,
115
+ external_user_name: externalUserName,
116
+ },
117
+ hostKey,
118
+ method: 'POST',
119
+ }, linkCodeSchema);
120
+ return { code: answered.code, expiresIn: answered.expires_in };
121
+ }
122
+ /** Whether a host user is linked, and to which principal. */
123
+ async linkState(hostKey, externalUserId) {
124
+ const state = await this.#call('Datalayer did not say whether the account is linked', `/apps/hosts/self/links/${encodeURIComponent(externalUserId)}`, { hostKey }, linkStateSchema);
125
+ return { linked: state.linked, principalUid: state.principal_uid };
126
+ }
127
+ /** Where deliveries are pushed: answered the secret they are signed with, once. */
128
+ async setWebhook(hostKey, url) {
129
+ const hook = await this.#call('Datalayer did not register the delivery address', '/apps/hosts/self/delivery', {
130
+ body: {
131
+ events: ['approval.requested', 'approval.decided', 'turn.completed'],
132
+ url,
133
+ },
134
+ hostKey,
135
+ method: 'PUT',
136
+ }, webhookSchema);
137
+ return { signingSecret: hook.signing_secret, version: hook.version };
138
+ }
139
+ /** What the installation has not acknowledged, oldest first: pushed or not. */
140
+ async outbox(hostKey, limit = 100) {
141
+ const listed = await this.#call('Datalayer did not answer the outbox', `/apps/hosts/self/deliveries?limit=${limit}`, { hostKey }, z.object({ deliveries: z.array(z.unknown()) }));
142
+ return listed.deliveries.map(deliveryOfOutbox);
143
+ }
144
+ async acknowledge(hostKey, deliveryUid) {
145
+ await this.#call('Datalayer did not take the acknowledgement', `/apps/hosts/self/deliveries/${encodeURIComponent(deliveryUid)}/ack`, { hostKey, method: 'POST' }, z.unknown());
146
+ }
147
+ /**
148
+ * *Open in Datalayer* (S-305): a one-use code for the host token's person
149
+ * that opens one session or approval after sign-in. Answers the landing's
150
+ * path that redeems it; never a bearer token in a URL.
151
+ */
152
+ async handoff(token, kind, target) {
153
+ const made = await this.#call('Datalayer did not make the link', '/apps/hosts/handoffs', { body: { kind, target }, method: 'POST', token }, handoffSchema);
154
+ return { expiresIn: made.expires_in, path: made.path };
155
+ }
156
+ /** How an approval is shown, read as the person it waits on (their host token). */
157
+ async presentation(token, approvalId) {
158
+ const answered = await this.#call('Datalayer did not answer the approval', `/tool-approvals/${encodeURIComponent(approvalId)}/presentation`, { token }, z.object({ presentation: presentationSchema }));
159
+ return answered.presentation;
160
+ }
161
+ /** Decide an approval as its person, on the very presentation shown (its digest). */
162
+ async decide(token, approvalId, decision) {
163
+ const answered = await this.#call('Datalayer did not take the decision', `/apps/hosts/approvals/${encodeURIComponent(approvalId)}/decision`, {
164
+ body: {
165
+ approve: decision.approve,
166
+ digest: decision.digest,
167
+ note: decision.note || null,
168
+ },
169
+ method: 'POST',
170
+ token,
171
+ }, decisionSchema);
172
+ return { status: answered.approval.status };
173
+ }
174
+ }
@@ -0,0 +1,199 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * The generic Datalayer host contracts (plans/SLACK.md §4.3, plans/DISCORD.md
4
+ * §4.3), as ai-agents answers them, for every chat host (Slack, Discord).
5
+ * These shapes are shared with ai-agents through `contracts.json`, beside
6
+ * this file and byte-identical to ai-agents' `tests/fixtures/hosts/`, which
7
+ * both sides test. A delivery's `target` stays opaque here: each host
8
+ * validates its own.
9
+ *
10
+ * @module portable/hosts/contracts
11
+ */
12
+ export declare const HOST_KEY_HEADER = "X-Datalayer-Host-Key";
13
+ export declare const SIGNATURE_HEADER = "X-Datalayer-Signature";
14
+ export declare const TIMESTAMP_HEADER = "X-Datalayer-Timestamp";
15
+ export declare const DELIVERY_HEADER = "X-Datalayer-Delivery";
16
+ export declare const IDEMPOTENCY_HEADER = "Idempotency-Key";
17
+ /** The presentation version the hosts render; another is reviewed in Datalayer. */
18
+ export declare const PRESENTATION_VERSION = 1;
19
+ export declare const bindSchema: z.ZodObject<{
20
+ host: z.ZodObject<{
21
+ uid: z.ZodString;
22
+ state: z.ZodString;
23
+ name: z.ZodOptional<z.ZodString>;
24
+ default_deployment: z.ZodOptional<z.ZodString>;
25
+ }, z.core.$loose>;
26
+ host_key: z.ZodString;
27
+ }, z.core.$strip>;
28
+ /** What a link code is redeemed at: the landing's confirm page. */
29
+ export declare const LINK_PAGE_PATH = "/hosts/link";
30
+ /**
31
+ * `GET /apps/hosts/self`: the installation with its allowed deployments.
32
+ * `channels`, `direct_only` and `paused` are the installation's channel
33
+ * policy and pause (S-104, S-105, S-403).
34
+ */
35
+ export declare const selfSchema: z.ZodObject<{
36
+ host: z.ZodObject<{
37
+ uid: z.ZodString;
38
+ account_uid: z.ZodOptional<z.ZodString>;
39
+ default_deployment: z.ZodOptional<z.ZodString>;
40
+ deployments: z.ZodArray<z.ZodObject<{
41
+ uid: z.ZodString;
42
+ app_name: z.ZodOptional<z.ZodString>;
43
+ kept: z.ZodOptional<z.ZodBoolean>;
44
+ }, z.core.$loose>>;
45
+ channels: z.ZodOptional<z.ZodArray<z.ZodObject<{
46
+ id: z.ZodString;
47
+ state: z.ZodEnum<{
48
+ allow: "allow";
49
+ deny: "deny";
50
+ }>;
51
+ deployment: z.ZodOptional<z.ZodString>;
52
+ }, z.core.$strip>>>;
53
+ direct_only: z.ZodOptional<z.ZodArray<z.ZodString>>;
54
+ paused: z.ZodOptional<z.ZodBoolean>;
55
+ }, z.core.$loose>;
56
+ }, z.core.$strip>;
57
+ /** `POST /apps/hosts/handoffs`: a one-use code that opens one session or approval (S-305). */
58
+ export declare const handoffSchema: z.ZodObject<{
59
+ code: z.ZodString;
60
+ expires_in: z.ZodNumber;
61
+ path: z.ZodString;
62
+ }, z.core.$strip>;
63
+ export declare const linkStateSchema: z.ZodObject<{
64
+ linked: z.ZodBoolean;
65
+ principal_uid: z.ZodOptional<z.ZodString>;
66
+ }, z.core.$strip>;
67
+ export declare const webhookSchema: z.ZodObject<{
68
+ url: z.ZodURL;
69
+ events: z.ZodArray<z.ZodString>;
70
+ version: z.ZodNumber;
71
+ signing_secret: z.ZodString;
72
+ }, z.core.$strip>;
73
+ /** One delivery of the outbox, as `GET …/self/deliveries` lists it. */
74
+ export declare const outboxItemSchema: z.ZodObject<{
75
+ uid: z.ZodString;
76
+ event: z.ZodString;
77
+ payload: z.ZodRecord<z.ZodString, z.ZodUnknown>;
78
+ state: z.ZodString;
79
+ }, z.core.$loose>;
80
+ export declare const decisionSchema: z.ZodObject<{
81
+ approval: z.ZodObject<{
82
+ id: z.ZodOptional<z.ZodNullable<z.ZodString>>;
83
+ status: z.ZodString;
84
+ }, z.core.$loose>;
85
+ }, z.core.$strip>;
86
+ export declare const linkCodeSchema: z.ZodObject<{
87
+ code: z.ZodString;
88
+ expires_in: z.ZodNumber;
89
+ confirm: z.ZodString;
90
+ }, z.core.$strip>;
91
+ export declare const exchangeSchema: z.ZodObject<{
92
+ access_token: z.ZodString;
93
+ token_type: z.ZodLiteral<"Bearer">;
94
+ expires_in: z.ZodNumber;
95
+ exp: z.ZodNumber;
96
+ principal_uid: z.ZodString;
97
+ host_uid: z.ZodString;
98
+ deployment: z.ZodObject<{
99
+ uid: z.ZodString;
100
+ app_uid: z.ZodString;
101
+ app_name: z.ZodString;
102
+ version: z.ZodNumber;
103
+ }, z.core.$strip>;
104
+ session_api: z.ZodObject<{
105
+ url: z.ZodURL;
106
+ agent: z.ZodString;
107
+ }, z.core.$strip>;
108
+ user_token: z.ZodOptional<z.ZodString>;
109
+ }, z.core.$strip>;
110
+ export type HostExchange = z.infer<typeof exchangeSchema>;
111
+ export declare const presentationSchema: z.ZodObject<{
112
+ version: z.ZodNumber;
113
+ approval_id: z.ZodString;
114
+ status: z.ZodString;
115
+ owner_uid: z.ZodString;
116
+ tool_name: z.ZodString;
117
+ action_class: z.ZodEnum<{
118
+ unknown: "unknown";
119
+ read: "read";
120
+ write: "write";
121
+ send: "send";
122
+ delete: "delete";
123
+ publish: "publish";
124
+ execute: "execute";
125
+ financial: "financial";
126
+ }>;
127
+ session_uid: z.ZodString;
128
+ created_at: z.ZodOptional<z.ZodNullable<z.ZodString>>;
129
+ expires_at: z.ZodOptional<z.ZodNullable<z.ZodString>>;
130
+ fields: z.ZodArray<z.ZodObject<{
131
+ name: z.ZodString;
132
+ label: z.ZodString;
133
+ value: z.ZodString;
134
+ }, z.core.$strip>>;
135
+ arguments: z.ZodRecord<z.ZodString, z.ZodUnknown>;
136
+ secret_fields: z.ZodArray<z.ZodString>;
137
+ complete: z.ZodBoolean;
138
+ host_may_decide: z.ZodBoolean;
139
+ reasons: z.ZodArray<z.ZodString>;
140
+ digest: z.ZodString;
141
+ }, z.core.$strip>;
142
+ export type ApprovalPresentation = z.infer<typeof presentationSchema>;
143
+ export declare const deliverySchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
144
+ delivery: z.ZodString;
145
+ event: z.ZodLiteral<"approval.requested">;
146
+ payload: z.ZodObject<{
147
+ approval_id: z.ZodString;
148
+ tool_name: z.ZodString;
149
+ presentation: z.ZodString;
150
+ session_uid: z.ZodString;
151
+ deployment_uid: z.ZodString;
152
+ target: z.ZodRecord<z.ZodString, z.ZodUnknown>;
153
+ }, z.core.$loose>;
154
+ }, z.core.$strip>, z.ZodObject<{
155
+ delivery: z.ZodString;
156
+ event: z.ZodLiteral<"approval.decided">;
157
+ payload: z.ZodObject<{
158
+ approval_id: z.ZodString;
159
+ status: z.ZodString;
160
+ decided_by: z.ZodString;
161
+ session_uid: z.ZodString;
162
+ deployment_uid: z.ZodString;
163
+ target: z.ZodRecord<z.ZodString, z.ZodUnknown>;
164
+ }, z.core.$loose>;
165
+ }, z.core.$strip>, z.ZodObject<{
166
+ delivery: z.ZodString;
167
+ event: z.ZodLiteral<"turn.completed">;
168
+ payload: z.ZodObject<{
169
+ record_entry: z.ZodString;
170
+ session_uid: z.ZodString;
171
+ deployment_uid: z.ZodString;
172
+ target: z.ZodRecord<z.ZodString, z.ZodUnknown>;
173
+ }, z.core.$loose>;
174
+ }, z.core.$strip>], "event">;
175
+ export type HostDelivery = z.infer<typeof deliverySchema>;
176
+ /** An outbox item read as the delivery a push would have carried. */
177
+ export declare function deliveryOfOutbox(item: unknown): HostDelivery;
178
+ /**
179
+ * Whether a host may offer Approve and Reject: only what the server says a
180
+ * host may decide, in a version the hosts render, shown whole, pending, with
181
+ * no secret field. Anything else gets *Review in Datalayer* (SLACK §2 rule 6,
182
+ * DISCORD §2 rule 6). Each host also checks the card fits its own limits.
183
+ */
184
+ export declare function hostMayDecide(presentation: ApprovalPresentation): boolean;
185
+ /** `v1=` HMAC-SHA256 of `"<timestamp>.<raw body>"`, as ai-agents signs a push. */
186
+ export declare function deliverySignature(secret: string, timestamp: number, rawBody: string | Buffer): string;
187
+ /**
188
+ * Verify a pushed delivery before trusting it: its signature in constant
189
+ * time, and a timestamp within five minutes, so a captured push cannot be
190
+ * replayed later.
191
+ */
192
+ export declare function verifyDelivery(input: {
193
+ nowSeconds?: number;
194
+ rawBody: string | Buffer;
195
+ secret: string;
196
+ signature: string | undefined;
197
+ timestamp: string | undefined;
198
+ toleranceSeconds?: number;
199
+ }): HostDelivery;