agentic-notify 0.2.1 → 0.4.0
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.
- package/README.md +56 -12
- package/dist/{chunk-PHY45IVQ.js → chunk-EMQ7EWWD.js} +2 -2
- package/dist/cli.js +1 -1
- package/dist/{dispatch-xe2IAqWe.d.ts → dispatch-CfflYJ-R.d.ts} +4 -4
- package/dist/{gdpr-BAg1U1w6.d.ts → gdpr-CUq5MUwo.d.ts} +1 -1
- package/dist/index.d.ts +7 -7
- package/dist/index.js +1 -1
- package/dist/mcp-bin.js +1 -1
- package/dist/mcp.d.ts +2 -2
- package/dist/mcp.js +21 -1
- package/dist/sdk.d.ts +8 -8
- package/dist/{testing-46doJP5Y.d.ts → testing-B5slvYDV.d.ts} +2 -2
- package/dist/testing.d.ts +3 -3
- package/dist/{types-BdX7UTFu.d.ts → types-DONKNIOo.d.ts} +1 -1
- package/dist/worker.d.ts +16 -33
- package/dist/worker.js +62 -60
- package/package.json +2 -2
- package/dist/{types-anA8DMQH.d.ts → types-C842YAMe.d.ts} +30 -30
package/README.md
CHANGED
|
@@ -5,16 +5,15 @@
|
|
|
5
5
|
|
|
6
6
|
Notifications for humans and agents: in-app inbox, email and webhook delivery, with a CLI, API, MCP server and SDK.
|
|
7
7
|
|
|
8
|
-
**Status: 0.
|
|
8
|
+
**Status: 0.4.0.** Breaking for Worker auth, see "Migrating from `NOTIFY_TOKENS`" below. Breaking for 0.1.0 code and databases, see the CHANGELOG upgrade steps. Works today: the notification model, preferences and mute, digest, a store port from agentic-identity with its memory and D1 adapters, signed webhook and email delivery through a dispatcher, an HTTP API in a Worker, an SDK, a CLI, MCP tools, per-recipient export and erase, and a retention sweep. Not done: a real email provider adapter, webhook endpoint management, and a web UI.
|
|
9
9
|
|
|
10
10
|
## Live demo
|
|
11
11
|
|
|
12
|
-
A demo Worker runs at https://agentic-notify-demo.hello-fd1.workers.dev. `GET /health` answers 200. The `/api/v1` routes need a
|
|
12
|
+
A demo Worker runs at https://agentic-notify-demo.hello-fd1.workers.dev. `GET /health` answers 200. The `/api/v1` routes need a key, so without one they answer 401. The demo keeps its data in the EU and sweeps notifications after 7 days.
|
|
13
13
|
|
|
14
|
-
Redeploy your own copy with `wrangler.demo.jsonc` (create the D1 database with `--location weur`, put its id in the file,
|
|
14
|
+
Redeploy your own copy with `wrangler.demo.jsonc` (create the D1 database with `--location weur`, put its id in the file, then add principals and keys, see Identity):
|
|
15
15
|
|
|
16
16
|
```sh
|
|
17
|
-
npx wrangler secret put NOTIFY_TOKENS --config wrangler.demo.jsonc
|
|
18
17
|
WRANGLER_SEND_METRICS=false npm run deploy:demo
|
|
19
18
|
```
|
|
20
19
|
|
|
@@ -42,16 +41,16 @@ await notifier.send({
|
|
|
42
41
|
const { items } = await notifier.list({ kind: "agent", id: "helper" });
|
|
43
42
|
```
|
|
44
43
|
|
|
45
|
-
Worker (D1 binding `DB
|
|
44
|
+
Worker (D1 binding `DB`):
|
|
46
45
|
|
|
47
46
|
```sh
|
|
48
47
|
# Create the database first: npx wrangler d1 create agentic-notify --location weur
|
|
49
|
-
# NOTIFY_TOKENS is JSON: { "<token of 32+ bytes>": { "kind": "human", "id": "ana", "canSend": true } }
|
|
50
|
-
npx wrangler secret put NOTIFY_TOKENS
|
|
51
48
|
npx wrangler deploy
|
|
52
49
|
```
|
|
53
50
|
|
|
54
|
-
|
|
51
|
+
The Worker reads keys and principals from the same D1 database, with `agentic-identity`. Issue a key for each caller with `agentic-identity` (for example `issueCredential`, or its Worker), into this database. See Identity below.
|
|
52
|
+
|
|
53
|
+
Without a D1 binding, every `/api/v1` call answers 503. A request without a valid key answers 401, on unknown paths too. Nobody gets in by default.
|
|
55
54
|
|
|
56
55
|
CLI, SDK and MCP talk to that Worker:
|
|
57
56
|
|
|
@@ -59,10 +58,13 @@ CLI, SDK and MCP talk to that Worker:
|
|
|
59
58
|
export NOTIFY_URL=https://notify.example.org NOTIFY_TOKEN=...
|
|
60
59
|
npx agentic-notify send --to human:ana --type mention --resource doc:d1 --body "Look at this"
|
|
61
60
|
npx agentic-notify list --unread
|
|
62
|
-
npx agentic-notify mcp # tools: list_notifications, read_notification, mark_read,
|
|
63
|
-
# mark_all_read, get_preferences, set_preferences,
|
|
61
|
+
npx agentic-notify mcp # tools: ping, list_notifications, read_notification, mark_read,
|
|
62
|
+
# mark_all_read, get_preferences, set_preferences, unread_count, digest,
|
|
63
|
+
# export_data, erase_data, send_notification
|
|
64
64
|
```
|
|
65
65
|
|
|
66
|
+
The MCP server needs `NOTIFY_URL` and `NOTIFY_TOKEN`. Without them only `ping` works. `erase_data` needs `confirm: true` and a key with `notify:admin`. `send_notification` needs `notify-send:write`. A tool that lacks its scope reports 403.
|
|
67
|
+
|
|
66
68
|
```ts
|
|
67
69
|
import { createClient } from "agentic-notify/sdk";
|
|
68
70
|
const client = createClient({ baseUrl: process.env.NOTIFY_URL!, token: process.env.NOTIFY_TOKEN! });
|
|
@@ -71,7 +73,16 @@ console.log(await client.unreadCount());
|
|
|
71
73
|
|
|
72
74
|
## HTTP API
|
|
73
75
|
|
|
74
|
-
All routes sit under `/api/v1` and need `Authorization: Bearer <
|
|
76
|
+
All routes sit under `/api/v1` and need `Authorization: Bearer <key>`. `GET /health` is open. A missing or bad key answers 401. A good key without the scope of the route answers 403. Each route needs one scope:
|
|
77
|
+
|
|
78
|
+
| Scope | Routes |
|
|
79
|
+
| --- | --- |
|
|
80
|
+
| `notify:read` | `GET /notifications`, `GET /notifications/:id`, `GET /unread-count`, `GET /digest`, `GET /preferences`, `GET /me/export` |
|
|
81
|
+
| `notify:write` | `POST /notifications/:id/read`, `POST /notifications/read-all`, `PUT /preferences` |
|
|
82
|
+
| `notify-send:write` | `POST /notifications` |
|
|
83
|
+
| `notify:admin` | `DELETE /me` |
|
|
84
|
+
|
|
85
|
+
A scope with a higher action covers the lower ones, so `notify:admin` covers `notify:write` and `notify:read`. `*:admin` covers all. `notify-send` is a separate resource, so a key that can only send cannot read.
|
|
75
86
|
|
|
76
87
|
| Route | Use |
|
|
77
88
|
| --- | --- |
|
|
@@ -84,7 +95,22 @@ All routes sit under `/api/v1` and need `Authorization: Bearer <token>`. `GET /h
|
|
|
84
95
|
|
|
85
96
|
## Identity
|
|
86
97
|
|
|
87
|
-
|
|
98
|
+
The Worker authenticates with `agentic-identity`. `requirePrincipal` from `agentic-identity/http` reads the credential, and the `Auth` port checks that it is not expired or revoked. The default `Auth` takes a bearer key, and also a `session` cookie that `agentic-identity` sets. A write with a cookie is refused, because no origin is allowed here. Pass your own `Auth` to accept bearer keys only. The default `Auth` is `createStoreAuth` over the same D1 store. Each route checks one scope (see HTTP API). A delegated agent holds only the scopes that its person also holds.
|
|
99
|
+
|
|
100
|
+
A person, and a `user_token` of that person, use the inbox of the person. An agent (`agent`, `agent_of_human` or `agent_of_agent`) uses the inbox of its own id. A caller reads and changes only its own notifications.
|
|
101
|
+
|
|
102
|
+
To use another identity system, call `createWorker({ auth: (env, store) => myAuth })`, where `auth` builds your own `Auth` from the Worker env and the store. Import `createWorker` from `agentic-notify/worker`.
|
|
103
|
+
|
|
104
|
+
### Migrating from `NOTIFY_TOKENS`
|
|
105
|
+
|
|
106
|
+
Before 0.4.0, the Worker read a static `NOTIFY_TOKENS` secret: a map of token to `{ kind, id, canSend }`. The tokens had no scopes. The Worker no longer reads it. Steps:
|
|
107
|
+
|
|
108
|
+
1. Delete the `NOTIFY_TOKENS` secret (`npx wrangler secret delete NOTIFY_TOKENS`).
|
|
109
|
+
2. Make each caller a principal in the D1 database, and issue it a key with `agentic-identity`. The old `{ "kind": "human" }` becomes kind `human`, and `{ "kind": "agent" }` becomes kind `agent`.
|
|
110
|
+
3. Give each principal scopes. An old token could read, change and erase its own inbox. To keep that, use `notify:admin`. For less, use `notify:write` (it covers `notify:read`) or `notify:read`. For the old `canSend: true`, add `notify-send:write`. It is a separate resource, so it does not cover `notify:read`: a caller that sends and reads needs both.
|
|
111
|
+
4. Give the new key to the CLI, SDK and MCP server as `NOTIFY_TOKEN`.
|
|
112
|
+
|
|
113
|
+
Library users: `createWorker` takes `auth` in place of `resolver`. `authorizeSend` still works as an extra check after the scope. Its first argument is now a `PrincipalContext` (`principalId` in place of `id`, no `canSend`). Its default changed: it was "only `canSend: true`", and now every caller with `notify-send:write` may send unless you pass your own.
|
|
88
114
|
|
|
89
115
|
## Privacy
|
|
90
116
|
|
|
@@ -94,6 +120,24 @@ Auth is a port: `PrincipalResolver` turns a request into `{ kind: "human" | "age
|
|
|
94
120
|
- Logs hold ids, channel names and error names only. They never hold bodies or addresses.
|
|
95
121
|
- Erase removes a recipient's own notifications and preferences. Notifications that name the person only as actor stay with their recipients.
|
|
96
122
|
|
|
123
|
+
## Testing
|
|
124
|
+
|
|
125
|
+
`agentic-notify/testing` helps you test a store adapter and your own code.
|
|
126
|
+
|
|
127
|
+
`runStoreContract(make)` runs the store contract suite. Call it at the top level of a vitest test
|
|
128
|
+
file, and install `vitest` 3 or later. The factory makes a new store for each call. A store made
|
|
129
|
+
without `shared` must start empty. Stores made with the same `shared` value must see the same data.
|
|
130
|
+
So the factory passes `options` to the store:
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
import { createMemoryStore } from "agentic-notify";
|
|
134
|
+
import { runStoreContract } from "agentic-notify/testing";
|
|
135
|
+
|
|
136
|
+
runStoreContract((options) => createMemoryStore(options));
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
`TestEmailAdapter` records each message in memory and can fail a set number of sends.
|
|
140
|
+
|
|
97
141
|
## Limits
|
|
98
142
|
|
|
99
143
|
- The default Worker serves the inbox. To fan out to webhook and email, compose `createApp` with `createDispatcher` in your own Worker.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// package.json
|
|
2
2
|
var package_default = {
|
|
3
3
|
name: "agentic-notify",
|
|
4
|
-
version: "0.
|
|
4
|
+
version: "0.4.0",
|
|
5
5
|
description: "Notifications for humans and agents: in-app inbox, webhook and email delivery, with SDK, CLI, MCP server and Worker API.",
|
|
6
6
|
keywords: [
|
|
7
7
|
"notifications",
|
|
@@ -67,7 +67,7 @@ var package_default = {
|
|
|
67
67
|
},
|
|
68
68
|
dependencies: {
|
|
69
69
|
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
70
|
-
"agentic-identity": "^0.
|
|
70
|
+
"agentic-identity": "^0.6.0",
|
|
71
71
|
hono: "^4.13.14",
|
|
72
72
|
zod: "^3.25.0"
|
|
73
73
|
},
|
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { R as RecipientExport } from './gdpr-
|
|
1
|
+
import { R as RecipientExport } from './gdpr-CUq5MUwo.js';
|
|
2
2
|
import { Store } from 'agentic-identity';
|
|
3
|
-
import { S as SendInput, N as Notification, C as Channel, R as Recipient, D as Digest, P as Preferences } from './types-
|
|
4
|
-
import { D as DeliveryChannel, L as Logger } from './types-
|
|
3
|
+
import { S as SendInput, N as Notification, C as Channel, R as Recipient, D as Digest, P as Preferences } from './types-C842YAMe.js';
|
|
4
|
+
import { D as DeliveryChannel, L as Logger } from './types-DONKNIOo.js';
|
|
5
5
|
|
|
6
6
|
interface NotifierOptions {
|
|
7
7
|
store: Store;
|
|
@@ -74,4 +74,4 @@ declare function createDispatcher(options: DispatcherOptions): {
|
|
|
74
74
|
notify(input: SendInput): Promise<DispatchResult>;
|
|
75
75
|
};
|
|
76
76
|
|
|
77
|
-
export { type DispatchResult as D, type ListOptions as L,
|
|
77
|
+
export { type DispatchResult as D, type ListOptions as L, type Notifier as N, type SendResult as S, type DispatcherOptions as a, type ListResult as b, createDispatcher as c, NotFoundError as d, type NotifierOptions as e, createNotifier as f };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
export { D as DispatchResult, a as DispatcherOptions, L as ListOptions, b as ListResult,
|
|
1
|
+
export { D as DispatchResult, a as DispatcherOptions, L as ListOptions, b as ListResult, d as NotFoundError, N as Notifier, e as NotifierOptions, S as SendResult, c as createDispatcher, f as createNotifier } from './dispatch-CfflYJ-R.js';
|
|
2
2
|
export { Doc, Page, Query, Scalar, Store, StoreOps, createMemoryStore } from 'agentic-identity';
|
|
3
|
-
import { R as Recipient } from './types-
|
|
4
|
-
export { A as Actor, b as CHANNELS, C as Channel, c as DEFAULT_CHANNELS, D as Digest, d as DigestGroup, N as Notification, P as Preferences, a as ResourceRef, S as SendInput, e as defaultPreferences, r as recipientKey } from './types-
|
|
5
|
-
import { D as DeliveryChannel } from './types-
|
|
6
|
-
export { a as DeliveryError, L as Logger } from './types-
|
|
7
|
-
export { E as EmailChannelOptions, a as EmailMessage, b as EmailPort, c as createEmailChannel } from './testing-
|
|
3
|
+
import { R as Recipient } from './types-C842YAMe.js';
|
|
4
|
+
export { A as Actor, b as CHANNELS, C as Channel, c as DEFAULT_CHANNELS, D as Digest, d as DigestGroup, N as Notification, P as Preferences, a as ResourceRef, S as SendInput, e as defaultPreferences, r as recipientKey } from './types-C842YAMe.js';
|
|
5
|
+
import { D as DeliveryChannel } from './types-DONKNIOo.js';
|
|
6
|
+
export { a as DeliveryError, L as Logger } from './types-DONKNIOo.js';
|
|
7
|
+
export { E as EmailChannelOptions, a as EmailMessage, b as EmailPort, c as createEmailChannel } from './testing-B5slvYDV.js';
|
|
8
8
|
export { D1Like, D1_SCHEMA, createD1Store } from 'agentic-identity/cloudflare';
|
|
9
|
-
export { R as RecipientExport } from './gdpr-
|
|
9
|
+
export { R as RecipientExport } from './gdpr-CUq5MUwo.js';
|
|
10
10
|
import 'zod';
|
|
11
11
|
import 'agentic-identity/testing';
|
|
12
12
|
|
package/dist/index.js
CHANGED
package/dist/mcp-bin.js
CHANGED
package/dist/mcp.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
2
|
import { Client } from './sdk.js';
|
|
3
|
-
import './types-
|
|
3
|
+
import './types-C842YAMe.js';
|
|
4
4
|
import 'zod';
|
|
5
|
-
import './gdpr-
|
|
5
|
+
import './gdpr-CUq5MUwo.js';
|
|
6
6
|
|
|
7
7
|
/** Without a client, only `ping` works and the other tools report that setup is missing. */
|
|
8
8
|
declare function createMcpServer(client?: Client): McpServer;
|
package/dist/mcp.js
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
} from "./chunk-ARL7FN6Y.js";
|
|
14
14
|
import {
|
|
15
15
|
VERSION
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-EMQ7EWWD.js";
|
|
17
17
|
|
|
18
18
|
// src/mcp.ts
|
|
19
19
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
@@ -72,6 +72,26 @@ function createMcpServer(client) {
|
|
|
72
72
|
inputSchema: preferencesSchema.shape,
|
|
73
73
|
annotations: write
|
|
74
74
|
}, (a) => need((c) => c.setPreferences(a))());
|
|
75
|
+
server.registerTool(
|
|
76
|
+
"unread_count",
|
|
77
|
+
{ description: "Count your unread notifications.", inputSchema: {}, annotations: read },
|
|
78
|
+
need((c) => c.unreadCount())
|
|
79
|
+
);
|
|
80
|
+
server.registerTool("digest", {
|
|
81
|
+
description: "Group your unread notifications by type. Optional since: epoch milliseconds.",
|
|
82
|
+
inputSchema: { since: z.number().int().min(0).optional() },
|
|
83
|
+
annotations: read
|
|
84
|
+
}, ({ since }) => need((c) => c.digest(since))());
|
|
85
|
+
server.registerTool(
|
|
86
|
+
"export_data",
|
|
87
|
+
{ description: "Export all data held about you.", inputSchema: {}, annotations: read },
|
|
88
|
+
need((c) => c.exportData())
|
|
89
|
+
);
|
|
90
|
+
server.registerTool("erase_data", {
|
|
91
|
+
description: "Permanently delete all data held about you. Pass confirm: true. This cannot be undone.",
|
|
92
|
+
inputSchema: { confirm: z.literal(true) },
|
|
93
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false }
|
|
94
|
+
}, () => need((c) => c.erase())());
|
|
75
95
|
server.registerTool("send_notification", {
|
|
76
96
|
description: "Send a notification. You must hold the send right. You are the actor.",
|
|
77
97
|
inputSchema: { recipient: recipientSchema, type: typeNameSchema, resource: resourceRefSchema, body: z.string().max(2e3) },
|
package/dist/sdk.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { N as Notification, D as Digest, P as Preferences, R as Recipient, a as ResourceRef } from './types-
|
|
2
|
-
import { R as RecipientExport } from './gdpr-
|
|
1
|
+
import { N as Notification, D as Digest, P as Preferences, R as Recipient, a as ResourceRef } from './types-C842YAMe.js';
|
|
2
|
+
import { R as RecipientExport } from './gdpr-CUq5MUwo.js';
|
|
3
3
|
import 'zod';
|
|
4
4
|
|
|
5
5
|
declare class ApiError extends Error {
|
|
@@ -46,17 +46,17 @@ declare function createClient(options: ClientOptions): {
|
|
|
46
46
|
type: string;
|
|
47
47
|
id: string;
|
|
48
48
|
recipient: {
|
|
49
|
-
kind: "human" | "agent";
|
|
50
49
|
id: string;
|
|
50
|
+
kind: "human" | "agent";
|
|
51
51
|
};
|
|
52
52
|
resource: {
|
|
53
|
-
kind: string;
|
|
54
53
|
id: string;
|
|
54
|
+
kind: string;
|
|
55
55
|
url?: string | undefined;
|
|
56
56
|
};
|
|
57
57
|
actor: {
|
|
58
|
-
kind: "human" | "agent";
|
|
59
58
|
id: string;
|
|
59
|
+
kind: "human" | "agent";
|
|
60
60
|
};
|
|
61
61
|
body: string;
|
|
62
62
|
createdAt: number;
|
|
@@ -66,17 +66,17 @@ declare function createClient(options: ClientOptions): {
|
|
|
66
66
|
type: string;
|
|
67
67
|
id: string;
|
|
68
68
|
recipient: {
|
|
69
|
-
kind: "human" | "agent";
|
|
70
69
|
id: string;
|
|
70
|
+
kind: "human" | "agent";
|
|
71
71
|
};
|
|
72
72
|
resource: {
|
|
73
|
-
kind: string;
|
|
74
73
|
id: string;
|
|
74
|
+
kind: string;
|
|
75
75
|
url?: string | undefined;
|
|
76
76
|
};
|
|
77
77
|
actor: {
|
|
78
|
-
kind: "human" | "agent";
|
|
79
78
|
id: string;
|
|
79
|
+
kind: "human" | "agent";
|
|
80
80
|
};
|
|
81
81
|
body: string;
|
|
82
82
|
createdAt: number;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import 'agentic-identity/testing';
|
|
2
|
-
import { R as Recipient } from './types-
|
|
3
|
-
import { D as DeliveryChannel } from './types-
|
|
2
|
+
import { R as Recipient } from './types-C842YAMe.js';
|
|
3
|
+
import { D as DeliveryChannel } from './types-DONKNIOo.js';
|
|
4
4
|
|
|
5
5
|
interface EmailMessage {
|
|
6
6
|
to: string;
|
package/dist/testing.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { runStoreContract } from 'agentic-identity/testing';
|
|
2
|
-
export { T as TestEmailAdapter } from './testing-
|
|
3
|
-
import './types-
|
|
2
|
+
export { T as TestEmailAdapter } from './testing-B5slvYDV.js';
|
|
3
|
+
import './types-C842YAMe.js';
|
|
4
4
|
import 'zod';
|
|
5
|
-
import './types-
|
|
5
|
+
import './types-DONKNIOo.js';
|
package/dist/worker.d.ts
CHANGED
|
@@ -1,56 +1,39 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { R as Recipient } from './types-
|
|
1
|
+
import { Auth, PrincipalContext, Store } from 'agentic-identity';
|
|
2
|
+
import { N as Notifier, c as createDispatcher } from './dispatch-CfflYJ-R.js';
|
|
3
|
+
import { R as Recipient } from './types-C842YAMe.js';
|
|
4
4
|
import { D1Like } from 'agentic-identity/cloudflare';
|
|
5
|
-
import './gdpr-
|
|
6
|
-
import '
|
|
7
|
-
import '
|
|
8
|
-
|
|
9
|
-
/** Who is calling. `kind` has no default: a missing kind is an error. */
|
|
10
|
-
declare const principalSchema: z.ZodObject<{
|
|
11
|
-
kind: z.ZodEnum<["human", "agent"]>;
|
|
12
|
-
id: z.ZodString;
|
|
13
|
-
/** Allowed to send notifications to others. */
|
|
14
|
-
canSend: z.ZodOptional<z.ZodBoolean>;
|
|
15
|
-
}, "strict", z.ZodTypeAny, {
|
|
16
|
-
kind: "human" | "agent";
|
|
17
|
-
id: string;
|
|
18
|
-
canSend?: boolean | undefined;
|
|
19
|
-
}, {
|
|
20
|
-
kind: "human" | "agent";
|
|
21
|
-
id: string;
|
|
22
|
-
canSend?: boolean | undefined;
|
|
23
|
-
}>;
|
|
24
|
-
type Principal = z.infer<typeof principalSchema>;
|
|
25
|
-
/** Port: turn a request into a principal, or null for "not signed in". Plug in any identity system. */
|
|
26
|
-
type PrincipalResolver = (request: Request) => Promise<Principal | null> | Principal | null;
|
|
5
|
+
import './gdpr-CUq5MUwo.js';
|
|
6
|
+
import './types-DONKNIOo.js';
|
|
7
|
+
import 'zod';
|
|
27
8
|
|
|
28
9
|
interface AppOptions {
|
|
29
10
|
notifier: Notifier;
|
|
30
11
|
/** When set, sends also go to webhook and email channels. */
|
|
31
12
|
dispatcher?: ReturnType<typeof createDispatcher>;
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
13
|
+
/** Reads the credential on a request. Each route then checks one scope. */
|
|
14
|
+
auth: Auth;
|
|
15
|
+
/** Extra check on a send, after the `notify-send:write` scope. Default: allow. */
|
|
16
|
+
authorizeSend?: (principal: PrincipalContext, recipient: Recipient) => Promise<boolean> | boolean;
|
|
35
17
|
}
|
|
36
18
|
|
|
37
19
|
interface WorkerEnv {
|
|
38
20
|
NOTIFY_NAME?: string;
|
|
39
21
|
/** D1 binding. */
|
|
40
22
|
DB?: D1Like;
|
|
41
|
-
/** Secret. JSON map of bearer token (32 bytes or more) to principal. */
|
|
42
|
-
NOTIFY_TOKENS?: string;
|
|
43
23
|
/** Data region of the store: "eu", "us" or "unknown". Default "eu". */
|
|
44
24
|
NOTIFY_REGION?: string;
|
|
45
25
|
/** Retention window in days. Unset keeps notifications. */
|
|
46
26
|
NOTIFY_RETENTION_DAYS?: string;
|
|
47
27
|
}
|
|
48
28
|
interface WorkerConfig {
|
|
49
|
-
/**
|
|
50
|
-
|
|
29
|
+
/**
|
|
30
|
+
* Build the `Auth` port. Default: `createStoreAuth` over the same store, so keys that
|
|
31
|
+
* agentic-identity issues into the database work here. Each route checks a scope.
|
|
32
|
+
*/
|
|
33
|
+
auth?(env: WorkerEnv, store: Store): Auth;
|
|
51
34
|
authorizeSend?: AppOptions["authorizeSend"];
|
|
52
35
|
}
|
|
53
|
-
/** Compose the Worker. Bring your own identity system through `
|
|
36
|
+
/** Compose the Worker. Bring your own identity system through `auth`. */
|
|
54
37
|
declare function createWorker(config: WorkerConfig): {
|
|
55
38
|
fetch(request: Request, env: WorkerEnv): Promise<Response>;
|
|
56
39
|
/** Delete notifications past the retention window. Wire to a cron trigger. */
|
package/dist/worker.js
CHANGED
|
@@ -9,58 +9,60 @@ import {
|
|
|
9
9
|
} from "./chunk-ARL7FN6Y.js";
|
|
10
10
|
import {
|
|
11
11
|
VERSION
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-EMQ7EWWD.js";
|
|
13
13
|
|
|
14
14
|
// src/api.ts
|
|
15
15
|
import { Hono } from "hono";
|
|
16
16
|
import { bodyLimit } from "hono/body-limit";
|
|
17
|
-
import { ZodError, z
|
|
17
|
+
import { ZodError, z } from "zod";
|
|
18
|
+
import { requirePrincipal } from "agentic-identity/http";
|
|
18
19
|
|
|
19
20
|
// src/auth.ts
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
21
|
+
var SCOPES = {
|
|
22
|
+
/** List, read, count, digest, read preferences, export. */
|
|
23
|
+
read: "notify:read",
|
|
24
|
+
/** Mark read, change preferences. */
|
|
25
|
+
write: "notify:write",
|
|
26
|
+
/** Erase all data held about the caller. */
|
|
27
|
+
erase: "notify:admin",
|
|
28
|
+
/** Notify someone else. */
|
|
29
|
+
send: "notify-send:write"
|
|
30
|
+
};
|
|
31
|
+
function recipientOf(context) {
|
|
32
|
+
switch (context.kind) {
|
|
33
|
+
case "human":
|
|
34
|
+
return { kind: "human", id: context.principalId };
|
|
35
|
+
case "user_token": {
|
|
36
|
+
const root = context.chain[0];
|
|
37
|
+
if (!root || root.kind !== "human") throw new RangeError("a user token needs a person at the root of its chain");
|
|
38
|
+
return { kind: "human", id: root.id };
|
|
39
|
+
}
|
|
40
|
+
case "agent":
|
|
41
|
+
case "agent_of_human":
|
|
42
|
+
case "agent_of_agent":
|
|
43
|
+
return { kind: "agent", id: context.principalId };
|
|
44
|
+
default:
|
|
45
|
+
throw new RangeError("unknown principal kind");
|
|
38
46
|
}
|
|
39
|
-
if (byDigest.size === 0) throw new Error("no tokens configured");
|
|
40
|
-
return async (request) => {
|
|
41
|
-
const m = /^Bearer ([^\s]+)$/.exec(request.headers.get("authorization") ?? "");
|
|
42
|
-
return m ? byDigest.get(await digest(m[1])) ?? null : null;
|
|
43
|
-
};
|
|
44
47
|
}
|
|
45
48
|
|
|
46
49
|
// src/api.ts
|
|
47
|
-
var sendBody =
|
|
48
|
-
var num =
|
|
50
|
+
var sendBody = z.object({ recipient: recipientSchema, type: typeNameSchema, resource: resourceRefSchema, body: z.string().max(2e3) }).strict();
|
|
51
|
+
var num = z.coerce.number().int().nonnegative();
|
|
49
52
|
function createApp(options) {
|
|
50
53
|
const { notifier } = options;
|
|
51
|
-
const authorizeSend = options.authorizeSend ?? ((
|
|
54
|
+
const authorizeSend = options.authorizeSend ?? (() => true);
|
|
52
55
|
const app = new Hono();
|
|
53
56
|
app.get("/health", (c) => c.json({ ok: true }));
|
|
54
57
|
const api = new Hono();
|
|
55
58
|
api.use("*", bodyLimit({ maxSize: 64 * 1024, onError: (c) => c.json({ error: "too_large" }, 413) }));
|
|
56
|
-
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
c.set("
|
|
61
|
-
c.set("me", { kind: parsed.data.kind, id: parsed.data.id });
|
|
59
|
+
const guard = (scope) => async (c, next) => {
|
|
60
|
+
const who = await requirePrincipal(c.req.raw, options.auth, { scope });
|
|
61
|
+
if (who instanceof Response) return who;
|
|
62
|
+
c.set("principal", who);
|
|
63
|
+
c.set("me", recipientOf(who));
|
|
62
64
|
await next();
|
|
63
|
-
}
|
|
65
|
+
};
|
|
64
66
|
async function json2(c) {
|
|
65
67
|
try {
|
|
66
68
|
return await c.req.json();
|
|
@@ -68,9 +70,9 @@ function createApp(options) {
|
|
|
68
70
|
throw new ZodError([{ code: "custom", path: [], message: "body must be JSON" }]);
|
|
69
71
|
}
|
|
70
72
|
}
|
|
71
|
-
api.get("/notifications", async (c) => {
|
|
73
|
+
api.get("/notifications", guard(SCOPES.read), async (c) => {
|
|
72
74
|
const q = c.req.query();
|
|
73
|
-
const limit = q.limit === void 0 ? void 0 :
|
|
75
|
+
const limit = q.limit === void 0 ? void 0 : z.coerce.number().int().min(1).parse(q.limit);
|
|
74
76
|
try {
|
|
75
77
|
return c.json(await notifier.list(c.var.me, { unreadOnly: q.unread === "1" || q.unread === "true", limit, cursor: q.cursor }));
|
|
76
78
|
} catch (error) {
|
|
@@ -78,7 +80,7 @@ function createApp(options) {
|
|
|
78
80
|
throw error;
|
|
79
81
|
}
|
|
80
82
|
});
|
|
81
|
-
api.post("/notifications", async (c) => {
|
|
83
|
+
api.post("/notifications", guard(SCOPES.send), async (c) => {
|
|
82
84
|
const input = sendBody.parse(await json2(c));
|
|
83
85
|
if (!await authorizeSend(c.var.principal, input.recipient)) return c.json({ error: "forbidden" }, 403);
|
|
84
86
|
const full = { ...input, actor: c.var.me };
|
|
@@ -87,18 +89,22 @@ function createApp(options) {
|
|
|
87
89
|
const deliveries = sent.channels.filter((channel) => channel !== "inbox").map((channel) => ({ channel, ok: false, skipped: "no_channel" }));
|
|
88
90
|
return c.json({ ...sent, deliveries }, 201);
|
|
89
91
|
});
|
|
90
|
-
api.post("/notifications/read-all", async (c) => c.json({ marked: await notifier.markAllRead(c.var.me) }));
|
|
91
|
-
api.get("/notifications/:id", async (c) => c.json(await notifier.get(c.var.me, c.req.param("id"))));
|
|
92
|
-
api.post("/notifications/:id/read", async (c) => c.json(await notifier.markRead(c.var.me, c.req.param("id"))));
|
|
93
|
-
api.get("/unread-count", async (c) => c.json({ count: await notifier.unreadCount(c.var.me) }));
|
|
94
|
-
api.get("/digest", async (c) => {
|
|
92
|
+
api.post("/notifications/read-all", guard(SCOPES.write), async (c) => c.json({ marked: await notifier.markAllRead(c.var.me) }));
|
|
93
|
+
api.get("/notifications/:id", guard(SCOPES.read), async (c) => c.json(await notifier.get(c.var.me, c.req.param("id"))));
|
|
94
|
+
api.post("/notifications/:id/read", guard(SCOPES.write), async (c) => c.json(await notifier.markRead(c.var.me, c.req.param("id"))));
|
|
95
|
+
api.get("/unread-count", guard(SCOPES.read), async (c) => c.json({ count: await notifier.unreadCount(c.var.me) }));
|
|
96
|
+
api.get("/digest", guard(SCOPES.read), async (c) => {
|
|
95
97
|
const since = c.req.query("since");
|
|
96
98
|
return c.json(await notifier.digest(c.var.me, since === void 0 ? 0 : num.parse(since)));
|
|
97
99
|
});
|
|
98
|
-
api.get("/preferences", async (c) => c.json(await notifier.getPreferences(c.var.me)));
|
|
99
|
-
api.put("/preferences", async (c) => c.json(await notifier.setPreferences(c.var.me, preferencesSchema.parse(await json2(c)))));
|
|
100
|
-
api.get("/me/export", async (c) => c.json(await notifier.exportData(c.var.me)));
|
|
101
|
-
api.delete("/me", async (c) => c.json(await notifier.erase(c.var.me)));
|
|
100
|
+
api.get("/preferences", guard(SCOPES.read), async (c) => c.json(await notifier.getPreferences(c.var.me)));
|
|
101
|
+
api.put("/preferences", guard(SCOPES.write), async (c) => c.json(await notifier.setPreferences(c.var.me, preferencesSchema.parse(await json2(c)))));
|
|
102
|
+
api.get("/me/export", guard(SCOPES.read), async (c) => c.json(await notifier.exportData(c.var.me)));
|
|
103
|
+
api.delete("/me", guard(SCOPES.erase), async (c) => c.json(await notifier.erase(c.var.me)));
|
|
104
|
+
api.all("*", async (c) => {
|
|
105
|
+
const who = await requirePrincipal(c.req.raw, options.auth);
|
|
106
|
+
return who instanceof Response ? who : c.json({ error: "not_found" }, 404);
|
|
107
|
+
});
|
|
102
108
|
app.route("/api/v1", api);
|
|
103
109
|
app.notFound((c) => c.json({ error: "not_found" }, 404));
|
|
104
110
|
app.onError((error, c) => {
|
|
@@ -113,7 +119,7 @@ function createApp(options) {
|
|
|
113
119
|
}
|
|
114
120
|
|
|
115
121
|
// src/worker.ts
|
|
116
|
-
import { parseRegion } from "agentic-identity";
|
|
122
|
+
import { createStoreAuth, parseRegion } from "agentic-identity";
|
|
117
123
|
import { createD1Store, D1_SCHEMA } from "agentic-identity/cloudflare";
|
|
118
124
|
function json(value, status = 200) {
|
|
119
125
|
return new Response(JSON.stringify(value), { status, headers: { "content-type": "application/json" } });
|
|
@@ -132,16 +138,17 @@ function createWorker(config) {
|
|
|
132
138
|
const days = env.NOTIFY_RETENTION_DAYS === void 0 ? void 0 : Number(env.NOTIFY_RETENTION_DAYS);
|
|
133
139
|
const store = createD1Store(env.DB, { region: parseRegion(env.NOTIFY_REGION ?? "eu") });
|
|
134
140
|
await store.query(NOTES, { limit: 1 });
|
|
135
|
-
|
|
141
|
+
const notifier = createNotifier({
|
|
136
142
|
store,
|
|
137
143
|
retentionMs: days === void 0 ? void 0 : days * 864e5
|
|
138
144
|
});
|
|
145
|
+
return { notifier, store };
|
|
139
146
|
}
|
|
140
147
|
async function build(env) {
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
return createApp({ notifier,
|
|
148
|
+
const built = await buildNotifier(env);
|
|
149
|
+
if (!built) return null;
|
|
150
|
+
const auth = config.auth ? config.auth(env, built.store) : createStoreAuth(built.store);
|
|
151
|
+
return createApp({ notifier: built.notifier, auth, authorizeSend: config.authorizeSend });
|
|
145
152
|
}
|
|
146
153
|
return {
|
|
147
154
|
async fetch(request, env) {
|
|
@@ -164,16 +171,11 @@ function createWorker(config) {
|
|
|
164
171
|
},
|
|
165
172
|
/** Delete notifications past the retention window. Wire to a cron trigger. */
|
|
166
173
|
async scheduled(_event, env) {
|
|
167
|
-
await (await buildNotifier(env))?.sweep();
|
|
174
|
+
await (await buildNotifier(env))?.notifier.sweep();
|
|
168
175
|
}
|
|
169
176
|
};
|
|
170
177
|
}
|
|
171
|
-
var worker_default = createWorker({
|
|
172
|
-
async resolver(env) {
|
|
173
|
-
if (!env.NOTIFY_TOKENS) return null;
|
|
174
|
-
return createStaticTokenResolver(JSON.parse(env.NOTIFY_TOKENS));
|
|
175
|
-
}
|
|
176
|
-
});
|
|
178
|
+
var worker_default = createWorker({});
|
|
177
179
|
export {
|
|
178
180
|
createWorker,
|
|
179
181
|
worker_default as default
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentic-notify",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Notifications for humans and agents: in-app inbox, webhook and email delivery, with SDK, CLI, MCP server and Worker API.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"notifications",
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
},
|
|
67
67
|
"dependencies": {
|
|
68
68
|
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
69
|
-
"agentic-identity": "^0.
|
|
69
|
+
"agentic-identity": "^0.6.0",
|
|
70
70
|
"hono": "^4.13.14",
|
|
71
71
|
"zod": "^3.25.0"
|
|
72
72
|
},
|
|
@@ -6,11 +6,11 @@ declare const recipientSchema: z.ZodObject<{
|
|
|
6
6
|
kind: z.ZodEnum<["human", "agent"]>;
|
|
7
7
|
id: z.ZodString;
|
|
8
8
|
}, "strict", z.ZodTypeAny, {
|
|
9
|
-
kind: "human" | "agent";
|
|
10
9
|
id: string;
|
|
11
|
-
}, {
|
|
12
10
|
kind: "human" | "agent";
|
|
11
|
+
}, {
|
|
13
12
|
id: string;
|
|
13
|
+
kind: "human" | "agent";
|
|
14
14
|
}>;
|
|
15
15
|
type Recipient = z.infer<typeof recipientSchema>;
|
|
16
16
|
declare const resourceRefSchema: z.ZodObject<{
|
|
@@ -18,12 +18,12 @@ declare const resourceRefSchema: z.ZodObject<{
|
|
|
18
18
|
id: z.ZodString;
|
|
19
19
|
url: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
20
20
|
}, "strict", z.ZodTypeAny, {
|
|
21
|
-
kind: string;
|
|
22
21
|
id: string;
|
|
22
|
+
kind: string;
|
|
23
23
|
url?: string | undefined;
|
|
24
24
|
}, {
|
|
25
|
-
kind: string;
|
|
26
25
|
id: string;
|
|
26
|
+
kind: string;
|
|
27
27
|
url?: string | undefined;
|
|
28
28
|
}>;
|
|
29
29
|
type ResourceRef = z.infer<typeof resourceRefSchema>;
|
|
@@ -31,11 +31,11 @@ declare const actorSchema: z.ZodObject<{
|
|
|
31
31
|
kind: z.ZodEnum<["human", "agent"]>;
|
|
32
32
|
id: z.ZodString;
|
|
33
33
|
}, "strict", z.ZodTypeAny, {
|
|
34
|
-
kind: "human" | "agent";
|
|
35
34
|
id: string;
|
|
36
|
-
}, {
|
|
37
35
|
kind: "human" | "agent";
|
|
36
|
+
}, {
|
|
38
37
|
id: string;
|
|
38
|
+
kind: "human" | "agent";
|
|
39
39
|
}>;
|
|
40
40
|
type Actor = z.infer<typeof actorSchema>;
|
|
41
41
|
declare const notificationSchema: z.ZodObject<{
|
|
@@ -44,11 +44,11 @@ declare const notificationSchema: z.ZodObject<{
|
|
|
44
44
|
kind: z.ZodEnum<["human", "agent"]>;
|
|
45
45
|
id: z.ZodString;
|
|
46
46
|
}, "strict", z.ZodTypeAny, {
|
|
47
|
-
kind: "human" | "agent";
|
|
48
47
|
id: string;
|
|
49
|
-
}, {
|
|
50
48
|
kind: "human" | "agent";
|
|
49
|
+
}, {
|
|
51
50
|
id: string;
|
|
51
|
+
kind: "human" | "agent";
|
|
52
52
|
}>;
|
|
53
53
|
type: z.ZodString;
|
|
54
54
|
resource: z.ZodObject<{
|
|
@@ -56,23 +56,23 @@ declare const notificationSchema: z.ZodObject<{
|
|
|
56
56
|
id: z.ZodString;
|
|
57
57
|
url: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
58
58
|
}, "strict", z.ZodTypeAny, {
|
|
59
|
-
kind: string;
|
|
60
59
|
id: string;
|
|
60
|
+
kind: string;
|
|
61
61
|
url?: string | undefined;
|
|
62
62
|
}, {
|
|
63
|
-
kind: string;
|
|
64
63
|
id: string;
|
|
64
|
+
kind: string;
|
|
65
65
|
url?: string | undefined;
|
|
66
66
|
}>;
|
|
67
67
|
actor: z.ZodObject<{
|
|
68
68
|
kind: z.ZodEnum<["human", "agent"]>;
|
|
69
69
|
id: z.ZodString;
|
|
70
70
|
}, "strict", z.ZodTypeAny, {
|
|
71
|
-
kind: "human" | "agent";
|
|
72
71
|
id: string;
|
|
73
|
-
}, {
|
|
74
72
|
kind: "human" | "agent";
|
|
73
|
+
}, {
|
|
75
74
|
id: string;
|
|
75
|
+
kind: "human" | "agent";
|
|
76
76
|
}>;
|
|
77
77
|
body: z.ZodString;
|
|
78
78
|
createdAt: z.ZodNumber;
|
|
@@ -81,17 +81,17 @@ declare const notificationSchema: z.ZodObject<{
|
|
|
81
81
|
type: string;
|
|
82
82
|
id: string;
|
|
83
83
|
recipient: {
|
|
84
|
-
kind: "human" | "agent";
|
|
85
84
|
id: string;
|
|
85
|
+
kind: "human" | "agent";
|
|
86
86
|
};
|
|
87
87
|
resource: {
|
|
88
|
-
kind: string;
|
|
89
88
|
id: string;
|
|
89
|
+
kind: string;
|
|
90
90
|
url?: string | undefined;
|
|
91
91
|
};
|
|
92
92
|
actor: {
|
|
93
|
-
kind: "human" | "agent";
|
|
94
93
|
id: string;
|
|
94
|
+
kind: "human" | "agent";
|
|
95
95
|
};
|
|
96
96
|
body: string;
|
|
97
97
|
createdAt: number;
|
|
@@ -100,17 +100,17 @@ declare const notificationSchema: z.ZodObject<{
|
|
|
100
100
|
type: string;
|
|
101
101
|
id: string;
|
|
102
102
|
recipient: {
|
|
103
|
-
kind: "human" | "agent";
|
|
104
103
|
id: string;
|
|
104
|
+
kind: "human" | "agent";
|
|
105
105
|
};
|
|
106
106
|
resource: {
|
|
107
|
-
kind: string;
|
|
108
107
|
id: string;
|
|
108
|
+
kind: string;
|
|
109
109
|
url?: string | undefined;
|
|
110
110
|
};
|
|
111
111
|
actor: {
|
|
112
|
-
kind: "human" | "agent";
|
|
113
112
|
id: string;
|
|
113
|
+
kind: "human" | "agent";
|
|
114
114
|
};
|
|
115
115
|
body: string;
|
|
116
116
|
createdAt: number;
|
|
@@ -122,11 +122,11 @@ declare const sendInputSchema: z.ZodObject<{
|
|
|
122
122
|
kind: z.ZodEnum<["human", "agent"]>;
|
|
123
123
|
id: z.ZodString;
|
|
124
124
|
}, "strict", z.ZodTypeAny, {
|
|
125
|
-
kind: "human" | "agent";
|
|
126
125
|
id: string;
|
|
127
|
-
}, {
|
|
128
126
|
kind: "human" | "agent";
|
|
127
|
+
}, {
|
|
129
128
|
id: string;
|
|
129
|
+
kind: "human" | "agent";
|
|
130
130
|
}>;
|
|
131
131
|
type: z.ZodString;
|
|
132
132
|
resource: z.ZodObject<{
|
|
@@ -134,55 +134,55 @@ declare const sendInputSchema: z.ZodObject<{
|
|
|
134
134
|
id: z.ZodString;
|
|
135
135
|
url: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
136
136
|
}, "strict", z.ZodTypeAny, {
|
|
137
|
-
kind: string;
|
|
138
137
|
id: string;
|
|
138
|
+
kind: string;
|
|
139
139
|
url?: string | undefined;
|
|
140
140
|
}, {
|
|
141
|
-
kind: string;
|
|
142
141
|
id: string;
|
|
142
|
+
kind: string;
|
|
143
143
|
url?: string | undefined;
|
|
144
144
|
}>;
|
|
145
145
|
actor: z.ZodObject<{
|
|
146
146
|
kind: z.ZodEnum<["human", "agent"]>;
|
|
147
147
|
id: z.ZodString;
|
|
148
148
|
}, "strict", z.ZodTypeAny, {
|
|
149
|
-
kind: "human" | "agent";
|
|
150
149
|
id: string;
|
|
151
|
-
}, {
|
|
152
150
|
kind: "human" | "agent";
|
|
151
|
+
}, {
|
|
153
152
|
id: string;
|
|
153
|
+
kind: "human" | "agent";
|
|
154
154
|
}>;
|
|
155
155
|
body: z.ZodString;
|
|
156
156
|
}, "strict", z.ZodTypeAny, {
|
|
157
157
|
type: string;
|
|
158
158
|
recipient: {
|
|
159
|
-
kind: "human" | "agent";
|
|
160
159
|
id: string;
|
|
160
|
+
kind: "human" | "agent";
|
|
161
161
|
};
|
|
162
162
|
resource: {
|
|
163
|
-
kind: string;
|
|
164
163
|
id: string;
|
|
164
|
+
kind: string;
|
|
165
165
|
url?: string | undefined;
|
|
166
166
|
};
|
|
167
167
|
actor: {
|
|
168
|
-
kind: "human" | "agent";
|
|
169
168
|
id: string;
|
|
169
|
+
kind: "human" | "agent";
|
|
170
170
|
};
|
|
171
171
|
body: string;
|
|
172
172
|
}, {
|
|
173
173
|
type: string;
|
|
174
174
|
recipient: {
|
|
175
|
-
kind: "human" | "agent";
|
|
176
175
|
id: string;
|
|
176
|
+
kind: "human" | "agent";
|
|
177
177
|
};
|
|
178
178
|
resource: {
|
|
179
|
-
kind: string;
|
|
180
179
|
id: string;
|
|
180
|
+
kind: string;
|
|
181
181
|
url?: string | undefined;
|
|
182
182
|
};
|
|
183
183
|
actor: {
|
|
184
|
-
kind: "human" | "agent";
|
|
185
184
|
id: string;
|
|
185
|
+
kind: "human" | "agent";
|
|
186
186
|
};
|
|
187
187
|
body: string;
|
|
188
188
|
}>;
|