@ours.network/notifications 0.1.1 → 0.3.1-nightly.1
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 +122 -225
- package/dist/chat-target.d.ts +9 -0
- package/dist/chat-target.js +12 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +13 -445
- package/dist/presence.d.ts +12 -0
- package/dist/presence.js +55 -145
- package/dist/server.d.ts +26 -0
- package/dist/server.js +145 -0
- package/dist/store.d.ts +63 -0
- package/dist/store.js +181 -0
- package/dist/validation.d.ts +9 -0
- package/dist/validation.js +60 -0
- package/package.json +14 -24
- package/dist/config.js +0 -104
- package/dist/daemon.js +0 -353
- package/dist/http.js +0 -31
- package/dist/log-store.js +0 -48
- package/dist/provision.js +0 -29
- package/dist/push.js +0 -6
- package/dist/web-push-sender.js +0 -41
- package/mufl_code/20E39399689579D5AFA5132C59DEDDD328C3BCA1A74D546F2193B36585DDD283.muflo +0 -0
- package/mufl_code/config.mufl +0 -21
- package/mufl_code/core/.github/FUNDING.yml +0 -3
- package/mufl_code/core/.github/workflows/docs.yml +0 -39
- package/mufl_code/core/.github/workflows/tests.yml +0 -66
- package/mufl_code/core/COMMERCIAL-LICENCE.md +0 -25
- package/mufl_code/core/COMPATIBILITY.md +0 -221
- package/mufl_code/core/CONTRIBUTING.md +0 -37
- package/mufl_code/core/LICENSE +0 -98
- package/mufl_code/core/NOTIFICATIONS_CONTRACT.md +0 -290
- package/mufl_code/core/README.md +0 -91
- package/mufl_code/core/SECURITY.md +0 -40
- package/mufl_code/core/TRADEMARKS.md +0 -24
- package/mufl_code/core/a2a_capabilities.mm +0 -449
- package/mufl_code/core/a2a_cluster.mm +0 -601
- package/mufl_code/core/a2a_control.mm +0 -90
- package/mufl_code/core/a2a_messaging.mm +0 -4817
- package/mufl_code/core/a2a_monitoring.mm +0 -94
- package/mufl_code/core/a2a_notification_integration.mm +0 -144
- package/mufl_code/core/a2a_notifications.mm +0 -1127
- package/mufl_code/core/a2a_protocol.mm +0 -246
- package/mufl_code/core/a2a_versions.mm +0 -801
- package/mufl_code/core/config.mufl +0 -29
- package/mufl_code/core/docs/.vitepress/config.mts +0 -58
- package/mufl_code/core/docs/citations.json +0 -688
- package/mufl_code/core/docs/guide/01-vendor-the-core.md +0 -50
- package/mufl_code/core/docs/guide/02-configure-and-compile.md +0 -100
- package/mufl_code/core/docs/guide/03-wire-the-host.md +0 -154
- package/mufl_code/core/docs/guide/04-connect-and-message.md +0 -156
- package/mufl_code/core/docs/guide/05-test-your-app.md +0 -75
- package/mufl_code/core/docs/guide/index.md +0 -63
- package/mufl_code/core/docs/how-it-works/capabilities-and-control.md +0 -77
- package/mufl_code/core/docs/how-it-works/cluster.md +0 -65
- package/mufl_code/core/docs/how-it-works/identity.md +0 -56
- package/mufl_code/core/docs/how-it-works/invites-and-contacts.md +0 -76
- package/mufl_code/core/docs/how-it-works/messaging.md +0 -55
- package/mufl_code/core/docs/how-it-works/monitoring-and-config.md +0 -72
- package/mufl_code/core/docs/how-it-works/overview.md +0 -45
- package/mufl_code/core/docs/how-it-works/versioning.md +0 -116
- package/mufl_code/core/docs/index.md +0 -27
- package/mufl_code/core/docs/package-lock.json +0 -3467
- package/mufl_code/core/docs/package.json +0 -15
- package/mufl_code/core/docs/reference/contributing.md +0 -26
- package/mufl_code/core/docs/reference/glossary.md +0 -108
- package/mufl_code/core/docs/reference/implementations.md +0 -23
- package/mufl_code/core/docs/reference/modules.md +0 -19
- package/mufl_code/core/docs/scripts/check-citations.mjs +0 -36
- package/mufl_code/core/docs/scripts/check-denylist.mjs +0 -30
- package/mufl_code/core/docs/scripts/gen-llms.mjs +0 -32
- package/mufl_code/core/docs/workflows/cluster.md +0 -89
- package/mufl_code/core/docs/workflows/contact-exchange.md +0 -60
- package/mufl_code/core/docs/workflows/contact-restore.md +0 -60
- package/mufl_code/core/docs/workflows/control-verbs.md +0 -70
- package/mufl_code/core/docs/workflows/index.md +0 -57
- package/mufl_code/core/docs/workflows/introductions.md +0 -60
- package/mufl_code/core/docs/workflows/messaging.md +0 -75
- package/mufl_code/core/docs/workflows/monitoring.md +0 -84
- package/mufl_code/core/docs/workflows/notifications.md +0 -109
- package/mufl_code/core/release-notes/0.1.md +0 -60
- package/mufl_code/core/release-notes/0.3.md +0 -22
- package/mufl_code/core/release-notes/0.4.md +0 -97
- package/mufl_code/core/tests/README.md +0 -83
- package/mufl_code/core/tests/compat/README.md +0 -82
- package/mufl_code/core/tests/compat/compat.mjs +0 -245
- package/mufl_code/core/tests/compat/compat_peer.mjs +0 -146
- package/mufl_code/core/tests/compat/run_compat.sh +0 -96
- package/mufl_code/core/tests/corpus.mjs +0 -139
- package/mufl_code/core/tests/corpus_actor.mu +0 -242
- package/mufl_code/core/tests/fatcompile_actor.mu +0 -52
- package/mufl_code/core/tests/mig.mjs +0 -805
- package/mufl_code/core/tests/mig_actor.mu +0 -518
- package/mufl_code/core/tests/migapp.mjs +0 -431
- package/mufl_code/core/tests/migapp_actor.mu +0 -294
- package/mufl_code/core/tests/mufl_semantics/run.sh +0 -69
- package/mufl_code/core/tests/mufl_semantics/t11_three_version.mufl +0 -47
- package/mufl_code/core/tests/mufl_semantics/t2_extra_fields.mufl +0 -11
- package/mufl_code/core/tests/mufl_semantics/t7_runtime.mufl +0 -30
- package/mufl_code/core/tests/mufl_semantics/t9_passthrough.mufl +0 -19
- package/mufl_code/core/tests/mufl_semantics/t_str_order.mufl +0 -19
- package/mufl_code/core/tests/notif.mjs +0 -755
- package/mufl_code/core/tests/notif_actor.mu +0 -450
- package/mufl_code/core/tests/protocol_container.mm +0 -18
- package/mufl_code/core/tests/run.sh +0 -65
- package/mufl_code/core/tests/run_corpus.sh +0 -51
- package/mufl_code/core/tests/run_fatcompile.sh +0 -40
- package/mufl_code/core/tests/run_mig.sh +0 -51
- package/mufl_code/core/tests/run_migapp.sh +0 -51
- package/mufl_code/core/tests/run_notif.sh +0 -57
- package/mufl_code/core/tests/test.mjs +0 -750
- package/mufl_code/core/tests/test_actor.mu +0 -564
- package/mufl_code/core/tests/test_common.mjs +0 -93
- package/mufl_code/core/version.mm +0 -24
- package/mufl_code/packet.mu +0 -170
- package/mufl_code/protocol_container.mm +0 -18
- package/scripts/gen-vapid.mjs +0 -16
package/README.md
CHANGED
|
@@ -1,231 +1,128 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
**Reach a person — or their agent — even when their app, tab, or daemon is
|
|
4
|
-
closed.** Give anything on the network a way to send real browser push
|
|
5
|
-
notifications, so an agent can get someone's attention out-of-band instead of
|
|
6
|
-
hoping they're watching.
|
|
7
|
-
|
|
8
|
-
The ours.network **Notifications-as-a-Service node**: an always-online packet
|
|
9
|
-
+ Node daemon that accepts token-authorized notification posts from anyone on
|
|
10
|
-
the network and pushes them to the recipient's browser(s) via **WebPush** —
|
|
11
|
-
even while the recipient's own agent/daemon/tab is not running.
|
|
12
|
-
|
|
13
|
-
The protocol surface lives in the shared core
|
|
14
|
-
(`ours-mufl-core/a2a_notifications.mm`, contract:
|
|
15
|
-
`ours-mufl-core/NOTIFICATIONS_CONTRACT.md`). This repo is the reference
|
|
16
|
-
service implementation: the packet wiring, persistence, WebPush egress, a
|
|
17
|
-
VAPID key tool, a browser demo, and integration tests.
|
|
18
|
-
|
|
19
|
-
## ⚠️ Trust model (v1 — read before operating or using an instance)
|
|
20
|
-
|
|
21
|
-
> **The service reads every notification payload (no E2E).** Payloads are
|
|
22
|
-
> encrypted in transit (wire envelopes; WebPush `aes128gcm`) but the service
|
|
23
|
-
> node decrypts, stores, and forwards them in plaintext. Never put secrets in
|
|
24
|
-
> a notification payload you would not show the service operator.
|
|
25
|
-
>
|
|
26
|
-
> **v1 has no anti-spam: anyone holding your token can notify you; rotate
|
|
27
|
-
> the token (`notify_rotate_token`) to cut them off.** Rotation invalidates
|
|
28
|
-
> the old handout instantly; you then redistribute the new one.
|
|
29
|
-
|
|
30
|
-
## Install & run as a service (`ours-notify`)
|
|
31
|
-
|
|
32
|
-
The daemon ships as an installable package with a full CLI that mirrors
|
|
33
|
-
`ours-mcp` / `ours-tg-connector`. It self-provisions its identity (`identity.key`
|
|
34
|
-
→ a stable cid across restarts) and a VAPID keypair on first start, so a fresh
|
|
35
|
-
install just works — point a DNS name at the discovery port and you have a
|
|
36
|
-
notifications service.
|
|
37
|
-
|
|
38
|
-
```sh
|
|
39
|
-
npm i -g @ours.network/notifications # installs the `ours-notify` bin
|
|
40
|
-
|
|
41
|
-
# configure (env, or a config.json — see below). BROKER_URL is the only required field.
|
|
42
|
-
export BROKER_URL=ws://broker.example.net:9000 # your ADAPT broker
|
|
43
|
-
export STATE_DIR=~/.ours-notifications # default; identity + VAPID + state live here
|
|
44
|
-
export DISCOVERY_PORT=8890 # default; the HTTP /v1/discovery port
|
|
45
|
-
export DISCOVERY_HOST=0.0.0.0 # bind publicly when behind DNS/a proxy
|
|
46
|
-
|
|
47
|
-
ours-notify start # spawn the background daemon (self-provisions on first run)
|
|
48
|
-
ours-notify status # running? port, cid, broker, state dir, version
|
|
49
|
-
ours-notify stop # graceful stop
|
|
50
|
-
ours-notify restart
|
|
51
|
-
|
|
52
|
-
# boot-persistent service (systemd user unit + linger on Linux; launchd on macOS):
|
|
53
|
-
ours-notify install-service # bakes the resolved config into the unit, enables + starts it
|
|
54
|
-
ours-notify uninstall-service
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
Point DNS at the host and expose `DISCOVERY_PORT`: clients fetch the service cid
|
|
58
|
-
+ VAPID public key from `https://<your-domain>/v1/discovery` at runtime, so a
|
|
59
|
-
client (e.g. the messenger panel) needs no rebuild when you deploy or rotate.
|
|
60
|
-
|
|
61
|
-
**Config precedence (per field): env > config.json > default.** `config.json`
|
|
62
|
-
lives at `$OURS_NOTIFY_CONFIG`, else `<HOME>/.ours-notifications/config.json`:
|
|
63
|
-
|
|
64
|
-
```json
|
|
65
|
-
{ "brokerUrl": "ws://broker.example.net:9000", "discoveryPort": 8890, "discoveryHost": "0.0.0.0" }
|
|
66
|
-
```
|
|
1
|
+
# Ours Notifications
|
|
67
2
|
|
|
68
|
-
|
|
3
|
+
Dedicated web-push delivery and a persistent notification inbox. Fleet and
|
|
4
|
+
Messenger submit events with producer credentials. Browsers register devices
|
|
5
|
+
and share unread/read state through their authenticated Fleet gateway. Native
|
|
6
|
+
Android/iOS transports are outside this package.
|
|
69
7
|
|
|
70
|
-
|
|
71
|
-
panel's default stays the hosted `notify.ours.network`, self-hosting is the
|
|
72
|
-
opt-out. Full walkthrough (VPS → TLS → verify → panel bind):
|
|
73
|
-
**[docs/SELF-HOSTING.md](docs/SELF-HOSTING.md)**.
|
|
8
|
+
## Run
|
|
74
9
|
|
|
75
|
-
|
|
10
|
+
Requires Node 22 or later. Build with `npm ci && npm run build`, then run
|
|
11
|
+
`node dist/cli.js /absolute/path/private-config.json`. The config must have mode
|
|
12
|
+
0600. The state directory must be private and backed up together with the VAPID
|
|
13
|
+
keys. Generate keys once with `web-push.generateVAPIDKeys()`; rotating them
|
|
14
|
+
requires each browser to subscribe again.
|
|
76
15
|
|
|
16
|
+
```json
|
|
17
|
+
{
|
|
18
|
+
"host": "127.0.0.1",
|
|
19
|
+
"port": 49677,
|
|
20
|
+
"stateFile": "/private/notifications/state.json",
|
|
21
|
+
"users": [{ "userId": "stable-owner-account", "token": "REPLACE_WITH_RANDOM_USER_TOKEN_AT_LEAST_32_CHARS" }],
|
|
22
|
+
"producers": [
|
|
23
|
+
{ "userId": "stable-owner-account", "source": "fleet", "token": "REPLACE_WITH_RANDOM_FLEET_TOKEN_AT_LEAST_32_CHARS" },
|
|
24
|
+
{ "userId": "stable-owner-account", "source": "messenger", "token": "REPLACE_WITH_RANDOM_MESSENGER_TOKEN_AT_LEAST_32_CHARS" }
|
|
25
|
+
],
|
|
26
|
+
"vapid": { "subject": "mailto:operator@example.com", "publicKey": "GENERATED_PUBLIC_KEY", "privateKey": "GENERATED_PRIVATE_KEY" }
|
|
27
|
+
}
|
|
77
28
|
```
|
|
78
|
-
sender S ──bare signed send (token authz)──▶ ┌────────────────────────────┐
|
|
79
|
-
│ service packet (MUFL) │
|
|
80
|
-
recipient R ◀─encrypted channel (contacts)─▶ │ a2a_notifications service │
|
|
81
|
-
register / bindings / rotate / mark_read │ half: validate + resolve │
|
|
82
|
-
└───────────┬────────────────┘
|
|
83
|
-
│ $notify_agent events
|
|
84
|
-
▼
|
|
85
|
-
┌────────────────────────────┐
|
|
86
|
-
│ daemon (Node, this repo) │
|
|
87
|
-
│ notifications.json log │
|
|
88
|
-
│ web-push VAPID egress ────┼──▶ browser push services
|
|
89
|
-
└────────────────────────────┘
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
- The packet validates registrations/tokens and hands
|
|
93
|
-
`(notification, bindings)` to the daemon; storage and HTTP stay app-side.
|
|
94
|
-
- The recipient↔service surface rides the encrypted channel (they are
|
|
95
|
-
contacts via the normal invite flow). The sender needs **no** contact and
|
|
96
|
-
no channel: the handout blob (service cid + per-contact scoped token) is
|
|
97
|
-
everything.
|
|
98
|
-
- Identity + state survive restarts: `identity.key` (0600, the packet's
|
|
99
|
-
Serialize-hex sign secret, reinjected via `__init` reseed) and
|
|
100
|
-
`state_data.bin` (written on every `$save_state`).
|
|
101
|
-
- The daemon serves two surfaces on `DISCOVERY_PORT` (default `8890`):
|
|
102
|
-
`GET /v1/discovery` (service cid + VAPID public key) and the `/v1/presence`
|
|
103
|
-
WebSocket. A client opens `/v1/presence` while its PWA is foreground so the
|
|
104
|
-
service can suppress a redundant WebPush while a live client is already
|
|
105
|
-
showing the conversation; closing/backgrounding drops the socket and
|
|
106
|
-
restores push delivery.
|
|
107
|
-
|
|
108
|
-
> **Must sit behind TLS.** `/v1/presence` authenticates each socket with the
|
|
109
|
-
> client's own WebPush `auth` secret, presented in the first frame. That
|
|
110
|
-
> credential (and all discovery traffic) crosses the wire in plaintext, so in
|
|
111
|
-
> any real deployment the daemon **must** be fronted by a TLS-terminating
|
|
112
|
-
> reverse proxy (`wss://` / `https://`) — never expose `DISCOVERY_PORT`
|
|
113
|
-
> directly to the network.
|
|
114
|
-
|
|
115
|
-
## Environment
|
|
116
|
-
|
|
117
|
-
| var | meaning |
|
|
118
|
-
|---|---|
|
|
119
|
-
| `BROKER_URL` | ADAPT broker websocket, e.g. `ws://127.0.0.1:9000` (**required**; env or config.json) |
|
|
120
|
-
| `STATE_DIR` | daemon state: `identity.key`, `state_data.bin`, `notifications.json`, `vapid.*` (default `<HOME>/.ours-notifications`) |
|
|
121
|
-
| `VAPID_PUBLIC_KEY` / `VAPID_PRIVATE_KEY` | inline VAPID keypair (or `*_FILE` variants). **Optional** — if unset, `ours-notify start` self-provisions a keypair into `STATE_DIR/vapid.{public,private}`. The private key never enters packet state. |
|
|
122
|
-
| `VAPID_SUBJECT` | `mailto:` or URL contact for push services (default `mailto:ops@ours.network`) |
|
|
123
|
-
| `UNIT_DIR` | compiled packet dir (default `mufl_code/`) |
|
|
124
|
-
| `PUSH_DISABLED` | `1` = log instead of pushing (dev) |
|
|
125
|
-
| `PUSH_TLS_INSECURE` | `1` = accept self-signed push endpoints (mock tests / lab ONLY) |
|
|
126
|
-
| `DISCOVERY_PORT` | port for the `GET /v1/discovery` HTTP endpoint (CLI default `8890`; running `dist/daemon.js` directly leaves it disabled unless set) |
|
|
127
|
-
| `DISCOVERY_HOST` | bind address for the discovery server (default `127.0.0.1`; use `0.0.0.0` behind DNS/a proxy) |
|
|
128
|
-
| `OURS_NOTIFY_CONFIG` | path to `config.json` (default `<HOME>/.ours-notifications/config.json`) |
|
|
129
|
-
|
|
130
|
-
## Run (local or any VPS — identical steps; WebPush needs no vendor accounts)
|
|
131
|
-
|
|
132
|
-
```sh
|
|
133
|
-
npm install
|
|
134
|
-
git submodule update --init
|
|
135
|
-
ADAPT_TOOLKIT=/path/to/adapt ./scripts/compile-mufl.sh
|
|
136
|
-
node scripts/gen-vapid.mjs ./vapid # once per instance
|
|
137
|
-
npm run build
|
|
138
|
-
|
|
139
|
-
# a broker (local dev; production points BROKER_URL at a deployed broker)
|
|
140
|
-
node scripts/dev-broker.mjs --host 127.0.0.1 --port 9000 --test_mode &
|
|
141
|
-
|
|
142
|
-
BROKER_URL=ws://127.0.0.1:9000 STATE_DIR=./state \
|
|
143
|
-
VAPID_PUBLIC_KEY=$(cat vapid/vapid.public) \
|
|
144
|
-
VAPID_PRIVATE_KEY=$(cat vapid/vapid.private) \
|
|
145
|
-
node dist/daemon.js
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
The daemon speaks JSON-lines on stdin/stdout:
|
|
149
|
-
`{"cmd":"invite","name":"alice"}` → invite blob (hex) for a recipient;
|
|
150
|
-
`{"cmd":"contacts"}`, `{"cmd":"notifications"}`, `{"cmd":"status"}`,
|
|
151
|
-
`{"cmd":"export_vis"}`.
|
|
152
|
-
|
|
153
|
-
## Browser demo (the human-visible proof)
|
|
154
|
-
|
|
155
|
-
1. Run broker + daemon (above), then `node demo/serve.mjs 8080`.
|
|
156
|
-
2. Register a recipient identity and connect it to the service:
|
|
157
|
-
```sh
|
|
158
|
-
echo '{"cmd":"invite","name":"demo-recipient"}' | # or drive the daemon stdin directly
|
|
159
|
-
node scripts/drive.mjs register --broker ws://127.0.0.1:9000 --state ./demo-recipient \
|
|
160
|
-
--invite <invite-hex-file>
|
|
161
|
-
```
|
|
162
|
-
3. Open `http://localhost:8080/`, paste `vapid/vapid.public`, **Subscribe**,
|
|
163
|
-
copy the binding JSON to a file, then:
|
|
164
|
-
```sh
|
|
165
|
-
node scripts/drive.mjs update-bindings --broker ws://127.0.0.1:9000 \
|
|
166
|
-
--state ./demo-recipient --binding binding.json
|
|
167
|
-
node scripts/drive.mjs export-address --broker ws://127.0.0.1:9000 \
|
|
168
|
-
--state ./demo-recipient --out handout.hex
|
|
169
|
-
```
|
|
170
|
-
4. From a **second, never-connected identity**, send:
|
|
171
|
-
```sh
|
|
172
|
-
node scripts/drive.mjs send --broker ws://127.0.0.1:9000 --state ./demo-sender \
|
|
173
|
-
--address handout.hex --payload "hello from the demo"
|
|
174
|
-
```
|
|
175
|
-
The notification pops in the browser — close the tab first to prove the
|
|
176
|
-
out-of-band wake-up.
|
|
177
|
-
|
|
178
|
-
## Tests
|
|
179
|
-
|
|
180
|
-
```sh
|
|
181
|
-
npm test # daemon boot/persistence/VAPID hygiene + mock-push e2e (Flow A→B→C + rejections)
|
|
182
|
-
```
|
|
183
|
-
|
|
184
|
-
The e2e test runs the full flow against a dev broker and a local HTTPS mock
|
|
185
|
-
push endpoint: register → browser-style subscription keys → non-contact
|
|
186
|
-
sender post → ONE `aes128gcm` WebPush POST with VAPID headers →
|
|
187
|
-
`notifications.json` unread → `mark_read` flips it → rotated-out and
|
|
188
|
-
unregistered handouts are rejected with no push and no log entry.
|
|
189
|
-
|
|
190
|
-
## Operational notes
|
|
191
|
-
|
|
192
|
-
- The notification log is **unbounded** in v1 (no GC) — rotate/purge
|
|
193
|
-
`notifications.json` operationally if needed.
|
|
194
|
-
- WebPush 404/410/5xx are logged and skipped (no binding auto-prune in v1).
|
|
195
|
-
- Duplicate posts are delivered twice (no dedup in v1).
|
|
196
|
-
|
|
197
|
-
## Learn more
|
|
198
|
-
|
|
199
|
-
- **How it works — the protocol, in depth:** the shared agent-to-agent core and
|
|
200
|
-
wire format (including the notifications contract) is documented in
|
|
201
|
-
**[ours-mufl-core](https://github.com/adapt-toolkit/ours-mufl-core)**.
|
|
202
|
-
- **The whole project:** [ours.network](https://ours.network) ·
|
|
203
|
-
[umbrella repo](https://github.com/adapt-toolkit/ours-network)
|
|
204
|
-
|
|
205
|
-
## Support ours.network
|
|
206
|
-
|
|
207
|
-
ours.network is built by a small, independent team who believe agents — and the people behind them — deserve communication that's private by construction: self-sovereign identity, end-to-end encryption, and no central party that can read, throttle, or cut you off. We release everything as free, FSL source-available software, and we run the broker and relay services that actually connect agents at our own cost.
|
|
208
|
-
|
|
209
|
-
We're at the alpha stage: we have a clear roadmap and, if this stage proves itself, proper funding will come later — but right now there is no funding and no monetization behind the project. We pay for the servers and build everything on our own time, which makes this exactly the moment when support matters most. Every contribution, even a single dollar, goes straight to keeping the servers running, the software free, and development moving. If ours.network is useful to you — or you simply want an open, encrypted network for agents to exist — please consider chipping in.
|
|
210
|
-
|
|
211
|
-
**Like it? Star this repo** ⭐ — it's free and it genuinely helps: every star lifts the project's visibility and brings more builders to the network.
|
|
212
|
-
|
|
213
|
-
**→ https://github.com/adapt-toolkit/ours-donate**
|
|
214
|
-
|
|
215
|
-
Thank you for helping keep it free, open, and alive.
|
|
216
|
-
|
|
217
|
-
## Licence, status & warranty
|
|
218
|
-
|
|
219
|
-
> **Alpha software.** ours-notifications is part of **ours.network**, which is early, experimental, **alpha-stage** software — under active development, subject to change without notice, and **not production-ready**.
|
|
220
|
-
|
|
221
|
-
> **No warranty / not security-audited.** ours.network has **not** been independently security-audited. It is provided **"as is", without warranty of any kind**, and you use it **at your own risk**. See [`LICENSE`](./LICENSE) and [`SECURITY.md`](./SECURITY.md).
|
|
222
|
-
|
|
223
|
-
**ours.network** is owned and licensed by **Adapt Framework Solutions Ltd**. It is released under the **Functional Source License, Version 1.1 ([FSL-1.1-Apache-2.0](./LICENSE))** — **source-available, not open source** during the FSL period. Each release **converts to Apache 2.0 two years after it is published**.
|
|
224
|
-
|
|
225
|
-
The FSL permits any use **except a Competing Use** — broadly, offering a commercial product or service that substitutes for, or provides substantially the same functionality as, ours.network. Competing/commercial use requires a separate **commercial licence** from Adapt Framework Solutions Ltd — see [`COMMERCIAL-LICENCE.md`](./COMMERCIAL-LICENCE.md) (contact: **license@adaptframework.solutions**).
|
|
226
|
-
|
|
227
|
-
**Built on Adapt.** ours.network runs on Adapt's publicly available binaries — the `@adapt-toolkit` packages (the SDK under FSL; the other binaries free to use). Adapt's own source code (its C++ core) is not open yet — it will be opened later.
|
|
228
|
-
|
|
229
|
-
Third-party notices: [`THIRD_PARTY_NOTICES.md`](./THIRD_PARTY_NOTICES.md).
|
|
230
29
|
|
|
231
|
-
|
|
30
|
+
Each credential fixes its user principal; each producer also fixes its source.
|
|
31
|
+
User IDs and sources in producer request bodies are rejected. Keep separate
|
|
32
|
+
producer and user credentials, never deliver any credential to browser code.
|
|
33
|
+
Fleet's password console currently represents one owner account: all trusted
|
|
34
|
+
browser sessions use the same configured stable user token. Separate user
|
|
35
|
+
accounts require separate trusted gateway/account mappings; arbitrary browser
|
|
36
|
+
cookies or claimed IDs cannot create one.
|
|
37
|
+
|
|
38
|
+
Configure Fleet's generic prefix gateway with `/notifications`, service origin,
|
|
39
|
+
`headers: { "authorization": "Bearer USER_TOKEN" }`, and
|
|
40
|
+
`stripBrowserContext: true`. Fleet checks the browser session, exact origin and
|
|
41
|
+
mutation CSRF before stripping cookies/browser headers and injecting its user
|
|
42
|
+
credential. This user token cannot call the producer API. Expose only the
|
|
43
|
+
gateway publicly; the notification listener rejects direct browser context.
|
|
44
|
+
|
|
45
|
+
## API
|
|
46
|
+
|
|
47
|
+
All operations require a credential in `Authorization: Bearer TOKEN`. JSON
|
|
48
|
+
mutations require `Content-Type: application/json`, with a 16 KiB body limit.
|
|
49
|
+
Responses are never cached. User and producer credentials have separate scopes.
|
|
50
|
+
|
|
51
|
+
| Method/path | Credential | Request/result |
|
|
52
|
+
| --- | --- | --- |
|
|
53
|
+
| GET `/api/v1/push/config` | User | Public VAPID key |
|
|
54
|
+
| POST `/api/v1/devices` | User | `{ "subscription": PushSubscriptionJSON }` → device `id` |
|
|
55
|
+
| POST `/api/v1/devices/delete` | User | `{ "id": "device-id" }`, only that user's device |
|
|
56
|
+
| GET `/api/v1/devices/status?id=...` | User | `{ "active": true }` |
|
|
57
|
+
| GET `/api/v1/inbox?limit=50&before=...` | User | Items, shared unread count, revision, next cursor |
|
|
58
|
+
| GET `/api/v1/summary` | User | Shared unread count, revision, and per-chat source/identity/count |
|
|
59
|
+
| POST `/api/v1/read-target` | User | `{ "url": "/fleet/chats?chat=agent", "revision": OBSERVED_REVISION }`, reads only this chat up to the revision captured before its history load |
|
|
60
|
+
| POST `/api/v1/read` | User | `{ "ids": ["notification-id"] }`, idempotent shared read |
|
|
61
|
+
| POST `/api/v1/send` | Producer | `{ "eventId": "stable-event-id", "title": "Agent completed", "body": "Open the chat", "url": "/fleet/chats?chat=agent&detail=1" }` |
|
|
62
|
+
|
|
63
|
+
Inbox admission and queued delivery are one fsynced atomic snapshot. The service
|
|
64
|
+
deduplicates by user/source/event ID; replay must retain identical title/body/URL
|
|
65
|
+
or it receives 409. Inbox entries survive failed pushes and service restart.
|
|
66
|
+
Reading stops remaining queued deliveries. Device endpoints cannot transfer
|
|
67
|
+
between users, and only supported HTTPS push-provider hosts and valid P-256
|
|
68
|
+
subscription keys are accepted. Navigation stays within `/fleet`.
|
|
69
|
+
|
|
70
|
+
The service holds an exclusive state-file lock. Run one writer per state file.
|
|
71
|
+
After a crash, inspect the lock PID and prove no service is using this state
|
|
72
|
+
before manually removing its `.lock`; no automatic stale-lock deletion occurs.
|
|
73
|
+
Malformed state fails closed. Restore a known backup rather than resetting it.
|
|
74
|
+
Provider 404/410 removes the expired binding; other failures retry with capped
|
|
75
|
+
exponential backoff (eight attempts). The inbox remains after delivery exhaustion.
|
|
76
|
+
Limits are 32 devices/user, 10,000 inbox entries/user, 20,000 queued deliveries,
|
|
77
|
+
180 user API requests/minute and 600 producer requests/minute. Capacity failure
|
|
78
|
+
returns 503 so durable producers can retain/retry events. This first version
|
|
79
|
+
requires operator archival/reset after inbox capacity is reached.
|
|
80
|
+
|
|
81
|
+
## Producers and PWA
|
|
82
|
+
|
|
83
|
+
Fleet web and Messenger use `OURS_NOTIFICATIONS_ORIGIN` and their distinct
|
|
84
|
+
`OURS_NOTIFICATIONS_PRODUCER_TOKEN`. Fleet web observes durable supervisor
|
|
85
|
+
ledgers incrementally without restarting agents or attaching a controller.
|
|
86
|
+
Owner browser/channel prompt completions notify; monitor/startup turns do not.
|
|
87
|
+
Existing history is skipped at first install. Persist Fleet web state to retain
|
|
88
|
+
offsets and pending prompt correlation. Messenger uses its canonical incoming
|
|
89
|
+
message/file watcher and persists payloads before sending. Both outboxes retry
|
|
90
|
+
without requiring a locally registered push binding.
|
|
91
|
+
|
|
92
|
+
In ours-app, open Settings → Notifications → Enable notifications. Permission
|
|
93
|
+
is requested from that click. The service worker subscribes this device, repairs
|
|
94
|
+
its registration on login/focus when enabled, and opens in-app targets. Disable
|
|
95
|
+
or sign out revokes this device only. The Notifications launcher opens the
|
|
96
|
+
backend inbox; marking a record read synchronizes with other sessions on their
|
|
97
|
+
next visible refresh (five seconds). Inbox polling is paginated and does not
|
|
98
|
+
load chat transcripts.
|
|
99
|
+
|
|
100
|
+
On iPhone/iPad, install the HTTPS app on the Home Screen and open the installed
|
|
101
|
+
app before enabling push; WebKit documents this requirement and the explicit
|
|
102
|
+
interaction for permission in its [Web Push announcement](https://webkit.org/blog/13878/web-push-for-web-apps-on-ios-and-ipados/).
|
|
103
|
+
Automated browser permission/subscription fixtures cannot establish physical
|
|
104
|
+
phone push delivery; test a real installed device separately.
|
|
105
|
+
|
|
106
|
+
## Release channels
|
|
107
|
+
|
|
108
|
+
Repository history is preserved. The legacy MUFL implementation is replaced by
|
|
109
|
+
this service, with community/legal files from `ours-shared`. Version 0.2.0 marks
|
|
110
|
+
the new service API; the previous package API is incompatible.
|
|
111
|
+
|
|
112
|
+
CI gates typecheck, build, tests and pack on PRs to `main`/`prerelease`. Only a
|
|
113
|
+
push to `main` may bump a stable version using conventional commit subjects and
|
|
114
|
+
publish `latest`. Only a push to `prerelease` may calculate an ephemeral
|
|
115
|
+
patch-base `-nightly.N` version and publish `nightly`; the nightly version is
|
|
116
|
+
never committed. Guard scripts refuse branch/version/tag mismatches. Publication
|
|
117
|
+
needs the same NPM/version-bump app secrets as sibling repositories. Opening a
|
|
118
|
+
feature PR performs no publication or merge. Run `npm test` for local gates.
|
|
119
|
+
|
|
120
|
+
Foreground presence uses the authenticated gateway WebSocket at
|
|
121
|
+
`/api/v1/presence`. Its first frame is `{type:"hello",id,endpoint,auth}`;
|
|
122
|
+
`id` must be an active device owned by the credential's user, with matching
|
|
123
|
+
subscription endpoint and auth key. Visible clients send `{type:"heartbeat"}`
|
|
124
|
+
every 15 seconds. Transport ping/pong and a 45-second application freshness
|
|
125
|
+
limit remove frozen/disconnected clients. Only that foreground device's push
|
|
126
|
+
is suppressed; its inbox item remains unread until explicitly marked read.
|
|
127
|
+
Other devices still receive push. `inbox.changed` invalidates account-wide inbox
|
|
128
|
+
views after producer admission or reads. Presence is ephemeral across restart.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Stable conversation identity, independent of list filters and mobile detail state. */
|
|
2
|
+
export declare function chatTarget(path: string): {
|
|
3
|
+
source: 'work' | 'messenger';
|
|
4
|
+
chat: string;
|
|
5
|
+
} | null;
|
|
6
|
+
export declare function chatKey(value: {
|
|
7
|
+
source: string;
|
|
8
|
+
chat: string;
|
|
9
|
+
}): string;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** Stable conversation identity, independent of list filters and mobile detail state. */
|
|
2
|
+
export function chatTarget(path) {
|
|
3
|
+
const url = new URL(path, 'https://ours.invalid');
|
|
4
|
+
if (url.pathname === '/fleet/chats') {
|
|
5
|
+
const chat = url.searchParams.get('chat');
|
|
6
|
+
if (!chat || chat.length > 512)
|
|
7
|
+
return null;
|
|
8
|
+
return { source: url.searchParams.get('source') === 'messenger' ? 'messenger' : 'work', chat };
|
|
9
|
+
}
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
export function chatKey(value) { return JSON.stringify([value.source, value.chat]); }
|
package/dist/cli.d.ts
ADDED