@agentcomms/events-daemon 0.0.0-stage → 0.17.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/LICENSE +21 -0
- package/README.md +267 -2
- package/THIRD_PARTY_LICENSES +499 -0
- package/dist/b3-contract-DFxcHY0I.mjs +2486 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.mjs +21675 -0
- package/dist/index.d.mts +12751 -0
- package/dist/index.mjs +2 -0
- package/dist/mcp-DXXb3Vv3-B0sHZfig.mjs +22131 -0
- package/dist/records-DjE5a4_H.mjs +2 -0
- package/dist/records-DsUxYBwb.mjs +1143 -0
- package/dist/stdio-CD7QQBAh.mjs +102 -0
- package/dist/webhook-target-DyDTwWhV.mjs +673 -0
- package/package.json +71 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Criss Moldovan
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,268 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @agentcomms/events-daemon
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
`agent-events` watches the Gmail, Slack, Resend and WhatsApp accounts you have already connected with
|
|
4
|
+
agent-communications. When an event matches a rule you approved, it delivers that event to the rule's targets:
|
|
5
|
+
- a signed HTTPS webhook;
|
|
6
|
+
- a local live stream (server-sent events) for your own tools;
|
|
7
|
+
- a dry-run log you read at your terminal.
|
|
8
|
+
|
|
9
|
+
Nothing is collected and nothing leaves this machine until you approve the rule yourself, at your terminal.
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm i -g @agentcomms/events-daemon # or run any command with: npx -y @agentcomms/events-daemon <command>
|
|
13
|
+
agent-events run # in a terminal you keep open: the owner of all event state
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## What it needs
|
|
17
|
+
|
|
18
|
+
- **macOS or Linux.** Windows is refused for now: Node cannot yet make a control pipe that only your account can open.
|
|
19
|
+
- **Node 22.16.0 or newer**, the first release with Node's built-in SQLite. An older Node is refused, and the message
|
|
20
|
+
says what to install.
|
|
21
|
+
- **The accounts you want to watch, already connected** with `agent-gmail`, `agent-slack`, `agent-resend` or
|
|
22
|
+
`agent-whatsapp`. The daemon reads through those packages and never sends mail or messages through them.
|
|
23
|
+
|
|
24
|
+
## How it runs
|
|
25
|
+
|
|
26
|
+
`agent-events run` starts the **foreground owner**. It owns the event database while that terminal runs, and stops
|
|
27
|
+
cleanly on `agent-events stop` or when the terminal closes. There is no background service or autostart in this
|
|
28
|
+
release; the desktop app will supervise it later. Its state lives in an owner-only directory. A state directory so
|
|
29
|
+
deep that its control socket's path would pass the system limit (103 bytes on macOS, 107 on Linux) is refused, with
|
|
30
|
+
a hint to pin a shorter one.
|
|
31
|
+
|
|
32
|
+
## A first rule: Gmail into the dry-run log
|
|
33
|
+
|
|
34
|
+
The dry-run target keeps what matched in an encrypted local log that only a person at a terminal can read. It is the
|
|
35
|
+
safe way to see what a rule would send before you point it at a webhook.
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
# 1. a target: the local dry-run log, keeping records for a day
|
|
39
|
+
agent-events target add '{"targetId":"log","version":1,"kind":"dry-run","retentionMs":86400000}'
|
|
40
|
+
|
|
41
|
+
# 2. a rule: every received Gmail message with a subject, mapped to its subject only
|
|
42
|
+
agent-events sources list # the Gmail account ids you can name in a rule
|
|
43
|
+
agent-events rule create "$(cat rule.json)"
|
|
44
|
+
|
|
45
|
+
# 3. turn it on: each step prints an approval, which you approve by typing its challenge
|
|
46
|
+
agent-events rule enable mail-subjects 1
|
|
47
|
+
agent-events approve <approval-id>
|
|
48
|
+
agent-events enable-all # the standing approval that starts collection
|
|
49
|
+
agent-events approve <approval-id>
|
|
50
|
+
|
|
51
|
+
# 4. read what matched
|
|
52
|
+
agent-events dryrun list
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
`rule.json`:
|
|
56
|
+
|
|
57
|
+
```json
|
|
58
|
+
{
|
|
59
|
+
"ruleId": "mail-subjects",
|
|
60
|
+
"version": 1,
|
|
61
|
+
"source": {
|
|
62
|
+
"channel": "gmail",
|
|
63
|
+
"accountIds": ["<an account id from sources list>"],
|
|
64
|
+
"options": { "channel": "gmail", "labels": "inbox", "includeSpamTrash": false }
|
|
65
|
+
},
|
|
66
|
+
"event": { "type": "gmail.message.received", "version": 1 },
|
|
67
|
+
"condition": { "path": "/subject", "op": "exists" },
|
|
68
|
+
"mapping": { "subject": { "$path": "/subject" } },
|
|
69
|
+
"targets": [{ "targetId": "log", "version": 1, "kind": "dry-run", "retentionMs": 86400000 }],
|
|
70
|
+
"subscribers": [],
|
|
71
|
+
"judges": [],
|
|
72
|
+
"deliveryRateCap": 1,
|
|
73
|
+
"retention": {
|
|
74
|
+
"ingestMs": 86400000,
|
|
75
|
+
"holdMs": 86400000,
|
|
76
|
+
"deliveryMs": 86400000,
|
|
77
|
+
"dryrunMs": 86400000,
|
|
78
|
+
"sseReplayMs": 86400000,
|
|
79
|
+
"deadLetterMs": 86400000,
|
|
80
|
+
"decisionMetadataMs": 604800000
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Rules, targets and subscribers are immutable versions. To change one, create its next version and enable it. The
|
|
86
|
+
approval preview then shows exactly what changes.
|
|
87
|
+
|
|
88
|
+
## What you can do with it
|
|
89
|
+
|
|
90
|
+
- **Sources:**
|
|
91
|
+
- Gmail, received and sent mail, and labels;
|
|
92
|
+
- Slack messages and thread replies;
|
|
93
|
+
- Resend received mail and sent-mail status;
|
|
94
|
+
- WhatsApp messages from the chats WhatsApp for Mac lists. A hidden chat never reaches a target.
|
|
95
|
+
|
|
96
|
+
The event catalogue, `agent-events catalogue list`, gives every event type and field.
|
|
97
|
+
- **Conditions and mapping:** checks on an event's fields, and a mapping that names exactly the fields a target
|
|
98
|
+
receives. Nothing else leaves. Once you enable a judge, a rule can also ask a model one yes/no question
|
|
99
|
+
([Judges](#judges)).
|
|
100
|
+
- **Targets:**
|
|
101
|
+
- **Webhooks:** HTTPS, or plain HTTP to the literal loopback address only. Addresses are pinned, and every attempt
|
|
102
|
+
is signed (Standard Webhooks). Each delivery can be retried or dropped (`delivery retry`, `delivery drop`).
|
|
103
|
+
- **A live stream:** one listener on the loopback address, for tools on this machine. Each subscriber has its own
|
|
104
|
+
token, and its origin is checked.
|
|
105
|
+
- **The dry-run log.**
|
|
106
|
+
- **Control:**
|
|
107
|
+
- `pause` / `resume`;
|
|
108
|
+
- `disable-all`, which stops collection and purges pending work at once;
|
|
109
|
+
- `doctor`, a content-free health report.
|
|
110
|
+
|
|
111
|
+
Every command takes `--json`. The [CLI reference](../../docs/reference/events-daemon-cli.md) lists them all.
|
|
112
|
+
|
|
113
|
+
## Agents and approvals
|
|
114
|
+
|
|
115
|
+
`agent-events mcp` serves the same operations to a coding agent over MCP. Register it with your client as you would
|
|
116
|
+
any MCP server, with `npx -y @agentcomms/events-daemon mcp` as the command. The
|
|
117
|
+
[`agentcomms-events` skill](../../skills/agentcomms-events/SKILL.md) tells an agent how to use it.
|
|
118
|
+
|
|
119
|
+
- **An agent can:** read the catalogue and your rules, propose rules, targets, judges and a judge budget, test a rule
|
|
120
|
+
against a sample event, test an active judge on the daemon's synthetic event, prepare a judge kind's enablement,
|
|
121
|
+
and turn a judge kind off.
|
|
122
|
+
- **An agent can never:**
|
|
123
|
+
- approve anything;
|
|
124
|
+
- read what the dry-run log kept;
|
|
125
|
+
- see, set or rotate a webhook secret or stream token;
|
|
126
|
+
- see, set or rotate a hosted judge's API key;
|
|
127
|
+
- resume a target;
|
|
128
|
+
- decide a held event.
|
|
129
|
+
|
|
130
|
+
Those are human-only, at your terminal. Every event's content is marked as untrusted wherever it is shown. So a
|
|
131
|
+
message that tries to instruct an agent reaches it as quoted data, never as an instruction.
|
|
132
|
+
|
|
133
|
+
## Judges
|
|
134
|
+
|
|
135
|
+
A rule can ask a model one yes/no question about an event, after the rule's own conditions on its fields have matched.
|
|
136
|
+
Two kinds of judge are available:
|
|
137
|
+
- **`typesafe`:** TypeSafe's hosted **Jev** (`jev-1.13.0`), through its System One API at
|
|
138
|
+
`https://api.typesafe.ai/v1/systemone`. The fields the rule names leave this machine for TypeSafe, sent with your
|
|
139
|
+
TypeSafe API key.
|
|
140
|
+
- **`local-endpoint`:** a model server you run on this machine, at a plain-HTTP URL whose host is the literal
|
|
141
|
+
`127.0.0.1` or `::1`; hostnames, HTTPS and any other address are refused. It is sent the same System One request
|
|
142
|
+
as Jev. A server that answers in its own JSON says where its number is, with `"protocol": "json-score"` and a
|
|
143
|
+
`scorePointer`, and that number is labelled an uncalibrated score. Nothing leaves this machine.
|
|
144
|
+
|
|
145
|
+
The third kind, **`laya`**, runs the Laya model inside the daemon. It is not available yet: `agent-events judge kinds
|
|
146
|
+
list` shows it as unavailable, and a judge naming it is refused.
|
|
147
|
+
|
|
148
|
+
**Every judge kind is off until you enable it, at your terminal.** Installing, updating, a rule or a budget never
|
|
149
|
+
turns one on. To ask Jev about received mail:
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
# 1. turn the kind on: you approve its exact enablement at this terminal
|
|
153
|
+
agent-events judge kind enable typesafe
|
|
154
|
+
agent-events approve <approval-id>
|
|
155
|
+
|
|
156
|
+
# 2. the daemon-wide judge budget: no judge is called without one
|
|
157
|
+
agent-events budget update "$(cat budget.json)"
|
|
158
|
+
agent-events approve <approval-id>
|
|
159
|
+
|
|
160
|
+
# 3. the judge, then its TypeSafe API key, which you enter at this terminal
|
|
161
|
+
agent-events judge add "$(cat judge.json)"
|
|
162
|
+
agent-events judge key set jev --version 1
|
|
163
|
+
|
|
164
|
+
# 4. a rule that asks it; its approval shows the question, the fields the judge sees and where they go
|
|
165
|
+
agent-events rule create "$(cat rule.json)"
|
|
166
|
+
agent-events rule enable mail-asks 1
|
|
167
|
+
agent-events approve <approval-id>
|
|
168
|
+
|
|
169
|
+
# 5. try the judge on the daemon's own synthetic event, never one of yours
|
|
170
|
+
agent-events judge test jev --version 1
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
`judge.json`:
|
|
174
|
+
|
|
175
|
+
```json
|
|
176
|
+
{
|
|
177
|
+
"judgeId": "jev",
|
|
178
|
+
"version": 1,
|
|
179
|
+
"kind": "typesafe",
|
|
180
|
+
"provider": "typesafe",
|
|
181
|
+
"model": "jev-1.13.0",
|
|
182
|
+
"endpoint": "https://api.typesafe.ai/v1/systemone",
|
|
183
|
+
"promptTemplate": "typesafe-jev-noul-v1",
|
|
184
|
+
"protocol": "noul"
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
`budget.json`, the ceilings for every judge call, overall and per provider:
|
|
189
|
+
|
|
190
|
+
```json
|
|
191
|
+
{
|
|
192
|
+
"budgetId": "judge-budget",
|
|
193
|
+
"version": 1,
|
|
194
|
+
"global": { "callsPerRollingHour": 60, "inputTokensPerRolling30Days": 1000000, "concurrency": 2 },
|
|
195
|
+
"providers": [
|
|
196
|
+
{ "provider": "typesafe", "callsPerRollingHour": 60, "inputTokensPerRolling30Days": 1000000, "concurrency": 2 }
|
|
197
|
+
]
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
`rule.json` is the first rule with `"ruleId": "mail-asks"` and an `agentic` part beside its `condition`. Its `judge`
|
|
202
|
+
is the judge document exactly as you added it:
|
|
203
|
+
|
|
204
|
+
```json
|
|
205
|
+
"agentic": {
|
|
206
|
+
"judge": {
|
|
207
|
+
"judgeId": "jev",
|
|
208
|
+
"version": 1,
|
|
209
|
+
"kind": "typesafe",
|
|
210
|
+
"provider": "typesafe",
|
|
211
|
+
"model": "jev-1.13.0",
|
|
212
|
+
"endpoint": "https://api.typesafe.ai/v1/systemone",
|
|
213
|
+
"promptTemplate": "typesafe-jev-noul-v1",
|
|
214
|
+
"protocol": "noul"
|
|
215
|
+
},
|
|
216
|
+
"question": "Does this message ask me to do something today?",
|
|
217
|
+
"inputs": ["/subject"],
|
|
218
|
+
"threshold": 0.8,
|
|
219
|
+
"onUncertain": "hold",
|
|
220
|
+
"callsPerRollingHour": 20,
|
|
221
|
+
"inputTokensPerRolling30Days": 100000,
|
|
222
|
+
"concurrency": 1,
|
|
223
|
+
"maxInputTokens": 512,
|
|
224
|
+
"maxOutputTokens": 64
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
A local judge has `"kind": "local-endpoint"`, your server's `provider`, `model` and `endpoint`, and that endpoint's
|
|
229
|
+
address in `"approvedAddressSet": ["127.0.0.1"]`. It needs no key.
|
|
230
|
+
|
|
231
|
+
**What a judge sees, and what it decides:**
|
|
232
|
+
- Only the fields in `inputs`, sanitised and marked as untrusted inside a fixed prompt. The rule's `condition` must
|
|
233
|
+
check a content field too (`/subject` above), so a judge is never asked about every event.
|
|
234
|
+
- It has no tools, secrets or authority. Its answer is one number from 0 to 1, and the rule matches when that number
|
|
235
|
+
reaches `threshold`. A message written to talk the judge into a match can move only that number; it cannot change
|
|
236
|
+
the rule, its fields or its targets.
|
|
237
|
+
- An answer up to 0.1 below the threshold is uncertain. It is no match or, with `"onUncertain": "hold"`, a held
|
|
238
|
+
decision that waits for you: `agent-events held list` shows them, and `agent-events held decide <decision-id> accept`
|
|
239
|
+
(or `reject`) settles one, at your terminal.
|
|
240
|
+
- A timeout, a malformed answer or a spent budget is no match. Retrying a delivery never asks the judge again.
|
|
241
|
+
|
|
242
|
+
**Budgets.** Each rule sets its own ceilings: calls per rolling hour, input tokens per rolling 30 days, concurrent
|
|
243
|
+
calls, and the most input and output tokens per call. The daemon-wide budget caps every rule, overall and per
|
|
244
|
+
provider, and a rule cannot raise it. A rule's new version inherits the charges of the versions before it, so a new
|
|
245
|
+
version never resets a budget. `judge test` runs at most ten times an hour per judge version, charged to the overall
|
|
246
|
+
budget.
|
|
247
|
+
|
|
248
|
+
**Turning a kind off.** `agent-events judge kind disable typesafe` takes effect at once: decisions waiting on its
|
|
249
|
+
judges, and the deliveries, dry-run records and stream entries bound to them, are cancelled and purged. Rules that
|
|
250
|
+
name its judges stay, but cannot call them; turning the kind back on needs a new approval. `disable-all` stops judges
|
|
251
|
+
along with everything else.
|
|
252
|
+
|
|
253
|
+
`doctor` shows each kind's state, the active budget version and its reservations, how many decisions are held and
|
|
254
|
+
when the next one expires, any hosted judge whose key is missing, and Laya as not available yet, all without content.
|
|
255
|
+
|
|
256
|
+
## For desktop and tool builders
|
|
257
|
+
|
|
258
|
+
The data-only control contract is
|
|
259
|
+
[`b3-control-v1.schema.json`](src/operations/contracts/b3-control-v1.schema.json). Its content-free
|
|
260
|
+
request, result and error vectors are in [`test/fixtures/b3-control-contract-v1`](test/fixtures/b3-control-contract-v1),
|
|
261
|
+
and the terminal approval-preview vectors are in [`test/fixtures/b3-approval-preview-v1`](test/fixtures/b3-approval-preview-v1).
|
|
262
|
+
It covers every B3 capability a desktop app may call, and never contains a secret value. Its frozen doctor result is
|
|
263
|
+
the closed [`doctor-v1.schema.json`](src/operations/contracts/doctor-v1.schema.json); the released daemon reports the
|
|
264
|
+
additive Phase-E [`doctor-v2.schema.json`](src/operations/contracts/doctor-v2.schema.json).
|
|
265
|
+
|
|
266
|
+
## Licence
|
|
267
|
+
|
|
268
|
+
MIT. See [LICENSE](LICENSE) and [THIRD_PARTY_LICENSES](THIRD_PARTY_LICENSES).
|