@pingroom/sdk 0.2.0 → 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 +339 -12
- package/dist/client.d.ts +130 -2
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +517 -2
- package/dist/client.js.map +1 -1
- package/dist/errors.d.ts +18 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +16 -0
- package/dist/errors.js.map +1 -1
- package/dist/http.d.ts +7 -0
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js +29 -5
- package/dist/http.js.map +1 -1
- package/dist/index.d.ts +10 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -2
- package/dist/index.js.map +1 -1
- package/dist/linkPing.d.ts +27 -0
- package/dist/linkPing.d.ts.map +1 -0
- package/dist/linkPing.js +39 -0
- package/dist/linkPing.js.map +1 -0
- package/dist/liveActivity.d.ts +101 -0
- package/dist/liveActivity.d.ts.map +1 -0
- package/dist/liveActivity.js +83 -0
- package/dist/liveActivity.js.map +1 -0
- package/dist/liveStatus.d.ts +168 -0
- package/dist/liveStatus.d.ts.map +1 -0
- package/dist/liveStatus.js +82 -0
- package/dist/liveStatus.js.map +1 -0
- package/dist/mcp.d.ts +23 -1
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +54 -6
- package/dist/mcp.js.map +1 -1
- package/dist/types.d.ts +292 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/webhooks.d.ts +53 -6
- package/dist/webhooks.d.ts.map +1 -1
- package/dist/webhooks.js +93 -25
- package/dist/webhooks.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -2,12 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
Official TypeScript/JavaScript SDK for the [PingRoom](https://pingroom.io) agent API — the push-native notification fabric for agents.
|
|
4
4
|
|
|
5
|
-
One typed client for everything an agent does: authenticate, create and join rooms, send pings,
|
|
5
|
+
One typed client for everything an agent does: authenticate, create and join rooms, send pings, ask a human to approve something — or answer a multi-option question — and block until they tap, listen for inbound pings in real time, drive the MCP endpoint, and verify outgoing-webhook signatures.
|
|
6
6
|
|
|
7
7
|
- **Zero runtime dependencies** — built on the platform `fetch`. Works in Node ≥ 20, browsers, Cloudflare Workers, Deno, and Bun.
|
|
8
8
|
- **Fully typed** — ships `.d.ts`; request fields mirror the HTTP API verbatim.
|
|
9
9
|
- **Secure by default** — refuses to send credentials over plain http, never logs or serializes the token, and verifies webhook signatures in constant time.
|
|
10
10
|
|
|
11
|
+
> **Release status:** npm currently serves 0.3.1. App pairing and
|
|
12
|
+
> `pr.inbox.activate()` are in the tested 0.4.0 release candidate on `main` and
|
|
13
|
+
> must not be presented as installed behavior until 0.4.0 is published and
|
|
14
|
+
> clean-install verified.
|
|
15
|
+
|
|
11
16
|
```bash
|
|
12
17
|
npm install @pingroom/sdk
|
|
13
18
|
```
|
|
@@ -20,17 +25,66 @@ import { PingRoom } from '@pingroom/sdk';
|
|
|
20
25
|
const pr = new PingRoom({ token: process.env.PINGROOM_TOKEN });
|
|
21
26
|
|
|
22
27
|
// Broadcast to a room you own
|
|
23
|
-
await pr.broadcast('ab12cd', {
|
|
28
|
+
await pr.broadcast('ab12cd', {
|
|
29
|
+
message: 'Deploy shipped ✅',
|
|
30
|
+
data: { version: '1.4.0' },
|
|
31
|
+
requires_ack: true,
|
|
32
|
+
ack_timeout_seconds: 300,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// Listen for what comes back
|
|
36
|
+
const { notifications, cursor } = await pr.notifications.wait({ timeout: 20 });
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
> **`pr.agents.ping()` is retired.** Handle-addressed pings across accounts
|
|
40
|
+
> always fail with `410 cross_account_ping_retired`. An agent reaches only the
|
|
41
|
+
> account that connected it — to reach another agent or person, share a room
|
|
42
|
+
> (invite them, or join one they post in) and `pr.broadcast()` into it.
|
|
43
|
+
|
|
44
|
+
### Link pings
|
|
45
|
+
|
|
46
|
+
A ping becomes a tappable link button when its `data` carries the reserved
|
|
47
|
+
keys `url` (absolute http(s), ≤ 2048 chars) and optional `button_label`
|
|
48
|
+
(≤ 26 chars). The `linkPing()` helper builds and validates that fragment:
|
|
24
49
|
|
|
25
|
-
|
|
26
|
-
|
|
50
|
+
```ts
|
|
51
|
+
import { PingRoom, linkPing } from '@pingroom/sdk';
|
|
52
|
+
|
|
53
|
+
await pr.broadcast('ab12cd', {
|
|
54
|
+
message: 'Build 512 ready',
|
|
55
|
+
data: { commit: 'abc123', ...linkPing({ url: 'https://ci.example.com/b/512', buttonLabel: 'Open build' }) },
|
|
56
|
+
});
|
|
27
57
|
```
|
|
28
58
|
|
|
29
59
|
> Security note: an agent credential is a bearer token. Keep it server-side. Do not embed a long-lived token in a browser bundle or a public client.
|
|
30
60
|
|
|
31
61
|
## Authentication
|
|
32
62
|
|
|
33
|
-
|
|
63
|
+
For local tools, pair through the PingRoom app. No copied token or room code:
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
const pr = new PingRoom();
|
|
67
|
+
|
|
68
|
+
const pairing = await pr.auth.startPairing({
|
|
69
|
+
agent_label: 'Deploy bot',
|
|
70
|
+
scopes: ['pingroom:rooms:read', 'pingroom:broadcast:send', 'pingroom:handoffs:create'],
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
console.log(`Open in PingRoom: ${pairing.pair_url}`);
|
|
74
|
+
const active = await pr.auth.waitForPairing(pairing);
|
|
75
|
+
|
|
76
|
+
// The approver chose this room, and the client now uses the active credential.
|
|
77
|
+
await pr.broadcast(active.room.invite_code, { message: 'SDK connected ✅' });
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
`waitForPairing()` tolerates short network outages and stops when the app link
|
|
81
|
+
expires. Pass an `AbortSignal` when your process needs its own cancellation.
|
|
82
|
+
If `scopes` is omitted, pairing requests room lookup, broadcast access, and
|
|
83
|
+
`pingroom:handoffs:create` for private Handoffs and Agent Inbox activation. The
|
|
84
|
+
approval screen shows every grant before the user connects.
|
|
85
|
+
|
|
86
|
+
If you already manage credentials, bring an existing agent token or run the
|
|
87
|
+
lower-level auth.md email flow:
|
|
34
88
|
|
|
35
89
|
```ts
|
|
36
90
|
const pr = new PingRoom();
|
|
@@ -57,13 +111,47 @@ Generic MCP clients (Cursor, Claude Desktop, Claude Code) should use the standar
|
|
|
57
111
|
```ts
|
|
58
112
|
await pr.rooms.list();
|
|
59
113
|
await pr.rooms.get('ab12cd');
|
|
60
|
-
await pr.rooms.create({ name: 'Deploys', icon: 'rocket', color: '#
|
|
114
|
+
await pr.rooms.create({ name: 'Deploys', icon: 'rocket', color: '#e33122' });
|
|
61
115
|
await pr.rooms.join({ invite_code: 'ab12cd' });
|
|
62
116
|
|
|
63
117
|
await pr.actions.update('ab12cd', 1, { label: 'Approve', icon: 'check' });
|
|
64
118
|
await pr.actions.trigger('ab12cd', 1);
|
|
65
119
|
```
|
|
66
120
|
|
|
121
|
+
## Attachments
|
|
122
|
+
|
|
123
|
+
Send the file itself, not a link to it. Upload first, then pass the ids to any
|
|
124
|
+
send. Bytes never travel over MCP or a JSON ping body, and the returned metadata
|
|
125
|
+
never carries a permanent URL — recipients read the content back through an
|
|
126
|
+
authenticated endpoint.
|
|
127
|
+
|
|
128
|
+
```ts
|
|
129
|
+
const report = await pr.attachments.upload({
|
|
130
|
+
content: await readFile('report.pdf'),
|
|
131
|
+
filename: 'report.pdf',
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
await pr.broadcast('ab12cd', {
|
|
135
|
+
message: 'Nightly report',
|
|
136
|
+
attachment_ids: [report.id],
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// Read one back (raw Response — stream it, or .arrayBuffer() / .text() it)
|
|
140
|
+
const res = await pr.attachments.content(report.id);
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
`content` accepts a `Blob`/`File`, a `Uint8Array`, or a UTF-8 string. Accepted
|
|
144
|
+
types are `md`, `pdf`, `html`, `txt`, `jpg`, `jpeg`, `png`, up to 20 MiB each
|
|
145
|
+
and 10 per ping; `questions.ask()` takes `attachment_ids` too.
|
|
146
|
+
|
|
147
|
+
Ids are single-use: the send claims them, and a claimed or expired id fails the
|
|
148
|
+
whole ping. An upload you never attach expires by itself after 24 hours, or you
|
|
149
|
+
can `pr.attachments.delete(id)` it.
|
|
150
|
+
|
|
151
|
+
Uploading needs the `pingroom:attachments:write` scope **and** a Pro account —
|
|
152
|
+
otherwise the API answers `402 pro_required`. Reading and deleting are not gated,
|
|
153
|
+
so a lapsed subscription never locks you out of files you already sent.
|
|
154
|
+
|
|
67
155
|
## Listening for pings (real time)
|
|
68
156
|
|
|
69
157
|
`listen()` is an async iterator that long-polls and advances the cursor for you — no poll-spam, no missed or duplicated pings. Stop it with an `AbortSignal`.
|
|
@@ -83,6 +171,26 @@ Or a single long-poll:
|
|
|
83
171
|
const { notifications, cursor } = await pr.notifications.wait({ after: lastCursor, timeout: 20 });
|
|
84
172
|
```
|
|
85
173
|
|
|
174
|
+
Read one ping and wait for its acknowledgement through the same namespace:
|
|
175
|
+
|
|
176
|
+
```ts
|
|
177
|
+
const ping = await pr.notifications.getNotification(notificationId);
|
|
178
|
+
console.log(ping.action_state?.status); // open | acked | expired
|
|
179
|
+
|
|
180
|
+
const result = await pr.notifications.waitForAcknowledgement(notificationId, { timeout: 30 });
|
|
181
|
+
switch (result.action_state.status) {
|
|
182
|
+
case 'acked':
|
|
183
|
+
console.log(`Acknowledged by ${result.action_state.acked_by?.name ?? 'someone'}`);
|
|
184
|
+
break;
|
|
185
|
+
case 'expired':
|
|
186
|
+
console.log('Nobody acknowledged before the deadline');
|
|
187
|
+
break;
|
|
188
|
+
case 'open':
|
|
189
|
+
// This bounded hold timed out; call waitForAcknowledgement again to continue.
|
|
190
|
+
break;
|
|
191
|
+
}
|
|
192
|
+
```
|
|
193
|
+
|
|
86
194
|
## Human-in-the-loop approvals
|
|
87
195
|
|
|
88
196
|
Ask the human you act for to decide, and block until they tap an answer on their phone.
|
|
@@ -141,6 +249,102 @@ await pr.questions.cancel(q.id); // withdraw a pending one
|
|
|
141
249
|
|
|
142
250
|
> `pr.approvals` is the ergonomic two-option shortcut and stays fully supported — it routes through the same machinery server-side. Use it for plain yes/no gates; use `pr.questions` for everything richer.
|
|
143
251
|
|
|
252
|
+
## Handoffs (Agent → one human)
|
|
253
|
+
|
|
254
|
+
A **handoff** is the direct, private version of the human-in-the-loop: it always targets **exactly one human** and is never readable by anyone else in the room. Reach for `pr.handoffs` when an agent needs to hand a task to *its* human (or one specific person) and block on the outcome — no room to pick, no room-scope answering. It's backed by the ack and question primitives, so a handoff is one of two `kind`s:
|
|
255
|
+
|
|
256
|
+
- **`ack`** — "acknowledge this." Resolves the moment the human taps acknowledge. States: `open | acked | expired`.
|
|
257
|
+
- **`question`** — "pick one." 2–4 tappable options. States: `pending | answered | expired | cancelled`.
|
|
258
|
+
|
|
259
|
+
Requires the `pingroom:handoffs:create` scope.
|
|
260
|
+
|
|
261
|
+
On first connect, explicitly activate the human's Agent Inbox. `activate()`
|
|
262
|
+
replays the viable test Question or creates its next numbered retry in the
|
|
263
|
+
designated delivery room, then waits for its terminal answer. It is deliberately
|
|
264
|
+
separate from `auth.waitForPairing()`: pairing adopts the approved credential
|
|
265
|
+
immediately and never silently blocks on the onboarding Question.
|
|
266
|
+
|
|
267
|
+
```ts
|
|
268
|
+
import { PingRoomActivationIncompleteError } from '@pingroom/sdk';
|
|
269
|
+
|
|
270
|
+
const controller = new AbortController();
|
|
271
|
+
try {
|
|
272
|
+
const activation = await pr.inbox.activate({
|
|
273
|
+
timeout: 20, // seconds per bounded server hold
|
|
274
|
+
overallTimeoutMs: 120_000, // ensure + all holds; this is the default
|
|
275
|
+
signal: controller.signal, // caller cancellation
|
|
276
|
+
});
|
|
277
|
+
// On supporting builds this proves native phone receipt before the answer,
|
|
278
|
+
// plus this agent observation—not just `question.state === 'answered'`.
|
|
279
|
+
console.log(activation.activation_completed); // true
|
|
280
|
+
console.log(`Agent Inbox ready: ${activation.question.answer.value}`);
|
|
281
|
+
} catch (error) {
|
|
282
|
+
if (error instanceof PingRoomActivationIncompleteError) {
|
|
283
|
+
console.log(`Activation incomplete: ${error.reason}`);
|
|
284
|
+
}
|
|
285
|
+
throw error;
|
|
286
|
+
}
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
Pending long-poll responses are observed internally and polled again. The
|
|
290
|
+
`timeout` option controls each bounded `GET /handoffs/{id}/wait` hold;
|
|
291
|
+
`overallTimeoutMs` limits the whole ensure-and-wait composition and defaults to
|
|
292
|
+
two minutes. Immediate observations are spaced by at least 2.1 seconds, keeping
|
|
293
|
+
the loop below the wait route's 30-request-per-minute limit.
|
|
294
|
+
|
|
295
|
+
A plain answer does not prove activation: `activate()` returns only when the
|
|
296
|
+
wait response carries `activation_completed: true`. An answered response with a
|
|
297
|
+
false or missing completion stamp is terminal and incomplete because a later
|
|
298
|
+
callback cannot rewrite the required receipt-before-answer sequence. It throws
|
|
299
|
+
`PingRoomActivationIncompleteError` immediately with reason
|
|
300
|
+
`answered_without_completion`. Expiry and cancellation throw the same domain
|
|
301
|
+
error with their corresponding reasons. Caller aborts reject with the signal's
|
|
302
|
+
reason. None of these paths revoke or change the client's active credential.
|
|
303
|
+
Network/API errors remain `PingRoomError`. Use
|
|
304
|
+
`pr.inbox.ensure()` directly when you only want to create or inspect the
|
|
305
|
+
onboarding Question without waiting. Call `activate()` again after an expired,
|
|
306
|
+
cancelled, or `answered_without_completion` result; the server replays a viable
|
|
307
|
+
attempt and creates one numbered retry for a terminal incomplete attempt.
|
|
308
|
+
|
|
309
|
+
```ts
|
|
310
|
+
// Ask for an acknowledgement. Pass idempotencyKey so a retried create is a no-op.
|
|
311
|
+
const task = await pr.handoffs.requestAck({
|
|
312
|
+
prompt: 'Deploy 1.4.0 to prod is queued — ack to proceed.',
|
|
313
|
+
idempotencyKey: 'deploy-1.4.0', // reuse verbatim on network retries
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
// Block until the human acks (or it expires). Terminal states never throw.
|
|
317
|
+
const result = await pr.handoffs.waitForResult(task.id);
|
|
318
|
+
if (result.state === 'acked') {
|
|
319
|
+
console.log(`Acked by ${result.acked_by?.display_name} at ${result.acked_at}`);
|
|
320
|
+
}
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
```ts
|
|
324
|
+
// Ask a multi-option question. Bare strings normalize to { value, label: value }.
|
|
325
|
+
const q = await pr.handoffs.ask({
|
|
326
|
+
prompt: 'Ship or hold 1.4.0?',
|
|
327
|
+
options: ['deploy', 'hold'],
|
|
328
|
+
idempotencyKey: 'ship-1.4.0',
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
const answered = await pr.handoffs.waitForResult(q.id);
|
|
332
|
+
if (answered.state === 'answered') {
|
|
333
|
+
// A negative choice ('hold') is a SUCCESSFUL answered result — not an error.
|
|
334
|
+
console.log(`Human chose ${answered.answer?.value}`);
|
|
335
|
+
}
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
`waitForResult` loops the bounded long-poll until a terminal state lands, so `expired`/`cancelled` and a negative answer all come back as normal results — nothing is thrown for the outcome. Full options: `prompt`, `target` (`'me'` — the default — or a user UUID), `expiresIn` (120–86400s, default 900), `urgency` (`'active'` | `'passive'`), `correlationId`, `replyTo`, `data`, `idempotencyKey`.
|
|
339
|
+
|
|
340
|
+
```ts
|
|
341
|
+
await pr.handoffs.get(id); // current state (polling / audit)
|
|
342
|
+
await pr.handoffs.list({ state: 'open' }); // your open handoffs
|
|
343
|
+
await pr.handoffs.list({ state: 'all' }); // recent history (up to 200 per kind)
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
Create/read map coded failures onto `PingRoomError` — branch on `error.code` (see [`HandoffErrorCode`](#errors)). The exported union includes target-policy, scope/quota, idempotency, feature, and capability failures; `capability_check_unavailable` is retryable. Authentication and schema-validation failures may have no code, so also inspect `error.status`.
|
|
347
|
+
|
|
144
348
|
## Incoming webhooks (no token needed)
|
|
145
349
|
|
|
146
350
|
A room's incoming-webhook URL carries its own secret — ideal for CI/deploy hooks:
|
|
@@ -151,21 +355,138 @@ import { sendIncomingWebhook } from '@pingroom/sdk';
|
|
|
151
355
|
await sendIncomingWebhook(process.env.PINGROOM_WEBHOOK_URL, {
|
|
152
356
|
message: 'Build #42 passed',
|
|
153
357
|
data: { commit: 'abc123' },
|
|
358
|
+
requires_ack: true,
|
|
359
|
+
ack_timeout_seconds: 300,
|
|
154
360
|
}, { idempotencyKey: 'build-42' });
|
|
155
361
|
```
|
|
156
362
|
|
|
363
|
+
### Live Activities from a webhook
|
|
364
|
+
|
|
365
|
+
See [Live Activities](#live-activities) below for the full picture. From a webhook, put `live_status` at the **top level** of the payload:
|
|
366
|
+
|
|
367
|
+
```ts
|
|
368
|
+
await sendIncomingWebhook(process.env.PINGROOM_WEBHOOK_URL, {
|
|
369
|
+
message: 'Deploying v1.4.0',
|
|
370
|
+
correlation_id: 'deploy-42',
|
|
371
|
+
live_status: { state: 'running', template: 'progress', progress: 0.4 },
|
|
372
|
+
});
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
> **Not inside `data`.** The server routes on a top-level `live_status` and *strips* the key from `data` so a legacy caller cannot spoof completion alerts. A nested `data.live_status` silently sends an ordinary ping and still answers `success: true`. (SDK versions before 0.3.1 dropped `live_status` from the request body entirely — upgrade.)
|
|
376
|
+
|
|
377
|
+
## Live Activities
|
|
378
|
+
|
|
379
|
+
A **live-status stream** is one thing being tracked, keyed by your `correlation_id`. The first ping starts a self-updating card on every room member's Lock Screen (iOS Live Activity / Dynamic Island, Android ongoing live update, and a full inline card in the app). Later `running` pings move it **silently** — no new notification. The first `done`/`failed` sends one completion alert and ends it.
|
|
380
|
+
|
|
381
|
+
Protocol: <https://pingroom.io/liveactivities.md> (v1.2).
|
|
382
|
+
|
|
383
|
+
### Two ways to send one
|
|
384
|
+
|
|
385
|
+
```ts
|
|
386
|
+
import { PingRoom, liveStatus, sendIncomingWebhook } from '@pingroom/sdk';
|
|
387
|
+
|
|
388
|
+
// A. Agent credential — needs the `pingroom:live:write` scope.
|
|
389
|
+
const pr = new PingRoom({ token: process.env.PINGROOM_TOKEN });
|
|
390
|
+
await pr.live.push('AB12CD', liveStatus.progress('deploy-42', {
|
|
391
|
+
state: 'running',
|
|
392
|
+
message: 'Building image',
|
|
393
|
+
progress: 0.4,
|
|
394
|
+
}));
|
|
395
|
+
|
|
396
|
+
// B. A room's incoming webhook (Pro) — no token. The builder returns
|
|
397
|
+
// { correlation_id, live_status }, which is exactly the webhook body.
|
|
398
|
+
await sendIncomingWebhook(
|
|
399
|
+
process.env.PINGROOM_WEBHOOK_URL,
|
|
400
|
+
liveStatus.progress('deploy-42', {
|
|
401
|
+
state: 'running',
|
|
402
|
+
message: 'Building image',
|
|
403
|
+
progress: 0.4,
|
|
404
|
+
}),
|
|
405
|
+
);
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
There is a third producer — a person driving a stream from the app composer over `POST /api/rooms/{code}/live` with their own JWT — but that is a Human JWT route and is not part of this agent SDK.
|
|
409
|
+
|
|
410
|
+
### Templates
|
|
411
|
+
|
|
412
|
+
`template` is fixed at stream creation; a later ping cannot re-template a running card. Content set at creation is **sticky**, so a sparse update carrying just `progress` keeps rendering the full template — and so does the terminal leg, so a bare `{ state: 'done' }` ends the activity on the finished card rather than an empty one.
|
|
413
|
+
|
|
414
|
+
| Builder | Renders |
|
|
415
|
+
|---|---|
|
|
416
|
+
| `liveStatus.status` | Title, message, optional progress |
|
|
417
|
+
| `liveStatus.steps` | Segmented tracker; `steps` (2–8 labels) required on the first ping and immutable after |
|
|
418
|
+
| `liveStatus.progress` | Determinate bar with optional `eta_at` |
|
|
419
|
+
| `liveStatus.metrics` | Up to 3 `{label, value}` counters |
|
|
420
|
+
| `liveStatus.countdown` | Large live timer to `deadline_at` |
|
|
421
|
+
| `liveStatus.question` | `prompt` + up to 4 `{value, label}` options on the lock screen |
|
|
422
|
+
| `liveStatus.matchup` | `left` / `center` / `right` — a score or A-vs-B |
|
|
423
|
+
|
|
424
|
+
```ts
|
|
425
|
+
await pr.live.push('AB12CD', liveStatus.steps('release-7', {
|
|
426
|
+
state: 'running',
|
|
427
|
+
steps: ['Build', 'Test', 'Deploy', 'Verify'],
|
|
428
|
+
current_step: 2,
|
|
429
|
+
message: 'Deploying to prod',
|
|
430
|
+
}));
|
|
431
|
+
|
|
432
|
+
await pr.live.push('AB12CD', liveStatus.matchup('game-3', {
|
|
433
|
+
state: 'running',
|
|
434
|
+
left: { label: 'ARS', value: '2' },
|
|
435
|
+
right: { label: 'CHE', value: '1' },
|
|
436
|
+
center: "68'",
|
|
437
|
+
}));
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
### Ending and reading back
|
|
441
|
+
|
|
442
|
+
```ts
|
|
443
|
+
// Always end a stream. `done`/`failed` is never rate-limited or quota-blocked,
|
|
444
|
+
// so a card can't be metered into hanging open on a lock screen.
|
|
445
|
+
await pr.live.push('AB12CD', liveStatus.done('deploy-42', 'Shipped v1.4.0'));
|
|
446
|
+
// ...or liveStatus.failed('deploy-42', 'Rollback triggered')
|
|
447
|
+
|
|
448
|
+
// Reconcile after a restart instead of opening a duplicate.
|
|
449
|
+
// Returns null (not a throw) when there is no such stream in the last 24 hours.
|
|
450
|
+
const snap = await pr.live.get('AB12CD', 'deploy-42');
|
|
451
|
+
if (snap) console.log(snap.template, snap.current_step, snap.updated_at);
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
`get()` returns **every** stored field (`state`, `progress`, `message`, `category`, `template`, `agent_id`, `accent_override`, `eta_at`, `deadline_at`, `metrics`, `prompt`, `options`, `left`, `right`, `center`, `steps`, `current_step`) plus `action_state` and `updated_at`. Fields you never set come back as `null`, not omitted.
|
|
455
|
+
|
|
456
|
+
### Ownership and limits
|
|
457
|
+
|
|
458
|
+
A stream belongs to the credential that started it. An agent can never advance a webhook's stream or another registration's, even under the same `correlation_id`. Updates are throttled to ~6/min per correlation and 10 new streams/min per producer; free agent accounts get 5 new streams/day (`402 free_limit_reached`). Creation is the only charged leg.
|
|
459
|
+
|
|
460
|
+
### `liveActivity.*` is not this
|
|
461
|
+
|
|
462
|
+
The exported `liveActivity.*` builders produce the frozen **native** `{ attributes, content_state }` push-layer shape for tests and tooling. No endpoint accepts them as input, and a `live_activity` block inside a `broadcast()` `data` object is carried as opaque structured data — it starts nothing. Use `liveStatus.*` to drive a real activity.
|
|
463
|
+
|
|
157
464
|
## Verifying outgoing webhooks
|
|
158
465
|
|
|
159
|
-
When PingRoom POSTs an event to *your* server, verify it before trusting it.
|
|
466
|
+
When PingRoom POSTs an event to *your* server, verify it before trusting it. New deliveries carry two lower-case hex HMAC-SHA256 headers:
|
|
467
|
+
|
|
468
|
+
- `X-PingRoom-Signature-V2` signs `` `v2\n${timestamp}\n${deliveryId}\n${rawBody}` `` and binds `X-PingRoom-Delivery` as well as the timestamp and body.
|
|
469
|
+
- `X-PingRoom-Signature` keeps the legacy `` `${timestamp}.${rawBody}` `` contract so existing receivers continue to work.
|
|
470
|
+
|
|
471
|
+
`timestamp` is the Unix-seconds value in `X-PingRoom-Timestamp`, and `deliveryId` is the exact `X-PingRoom-Delivery` value. **Verify over the exact raw request body; re-serializing parsed JSON can change the bytes and invalidate the signature.** If v2 is present, receivers MUST verify it and reject the request on failure. The helper accepts legacy v1 only when v2 is absent. It rejects timestamps outside a five-minute replay window by default; set `maxAgeSeconds` to customize that window.
|
|
160
472
|
|
|
161
473
|
```ts
|
|
162
|
-
import {
|
|
474
|
+
import {
|
|
475
|
+
verifyWebhookSignature,
|
|
476
|
+
WEBHOOK_SIGNATURE_HEADER,
|
|
477
|
+
WEBHOOK_SIGNATURE_V2_HEADER,
|
|
478
|
+
WEBHOOK_TIMESTAMP_HEADER,
|
|
479
|
+
WEBHOOK_DELIVERY_HEADER,
|
|
480
|
+
} from '@pingroom/sdk';
|
|
163
481
|
|
|
164
482
|
// Express example — capture the raw body (e.g. express.raw())
|
|
165
483
|
app.post('/pingroom', async (req, res) => {
|
|
166
484
|
const ok = await verifyWebhookSignature({
|
|
167
485
|
payload: req.body, // a Buffer/string of the RAW body
|
|
168
486
|
signature: req.get(WEBHOOK_SIGNATURE_HEADER),
|
|
487
|
+
signatureV2: req.get(WEBHOOK_SIGNATURE_V2_HEADER),
|
|
488
|
+
timestamp: req.get(WEBHOOK_TIMESTAMP_HEADER),
|
|
489
|
+
deliveryId: req.get(WEBHOOK_DELIVERY_HEADER),
|
|
169
490
|
secret: process.env.PINGROOM_SIGNING_SECRET,
|
|
170
491
|
});
|
|
171
492
|
if (!ok) return res.status(401).end();
|
|
@@ -176,13 +497,19 @@ app.post('/pingroom', async (req, res) => {
|
|
|
176
497
|
|
|
177
498
|
## MCP
|
|
178
499
|
|
|
179
|
-
Drive the MCP endpoint directly
|
|
500
|
+
Drive the MCP endpoint directly with a credential you already hold. The helper
|
|
501
|
+
performs the initialize handshake automatically, and tools are scope-filtered
|
|
502
|
+
server-side:
|
|
180
503
|
|
|
181
504
|
```ts
|
|
182
505
|
const { tools } = await pr.mcp.listTools();
|
|
183
|
-
const result = await pr.mcp.callTool('broadcast', {
|
|
506
|
+
const result = await pr.mcp.callTool('broadcast', { invite_code: 'ab12cd', message: 'hi' });
|
|
184
507
|
```
|
|
185
508
|
|
|
509
|
+
`pr.mcp` is a PingRoom-specific JSON-RPC convenience client, not a general MCP
|
|
510
|
+
host. Cursor, Claude, and other MCP hosts should connect to the hosted endpoint
|
|
511
|
+
directly and use OAuth instead: <https://pingroom.io/connect-mcp.md>.
|
|
512
|
+
|
|
186
513
|
## Agent directory (public)
|
|
187
514
|
|
|
188
515
|
No token required:
|
|
@@ -201,9 +528,9 @@ Every failure rejects with a `PingRoomError` carrying the HTTP `status` and the
|
|
|
201
528
|
import { PingRoomError } from '@pingroom/sdk';
|
|
202
529
|
|
|
203
530
|
try {
|
|
204
|
-
await pr.
|
|
531
|
+
await pr.broadcast('ab12cd', { message: 'hi' });
|
|
205
532
|
} catch (err) {
|
|
206
|
-
if (err instanceof PingRoomError && err.code === '
|
|
533
|
+
if (err instanceof PingRoomError && err.code === 'rate_limited') {
|
|
207
534
|
await new Promise((r) => setTimeout(r, (err.retryAfter ?? 1) * 1000));
|
|
208
535
|
}
|
|
209
536
|
}
|
package/dist/client.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { HttpClient } from './http.js';
|
|
2
|
+
import type { LiveStatusPing, LiveStatusResult, LiveStatusSnapshot } from './liveStatus.js';
|
|
2
3
|
import { McpClient } from './mcp.js';
|
|
3
|
-
import type { AgentNotification, Approval, ApprovalInput, CreatePublicRoomInput, CreateRoomInput, Credential, ClaimCompleteParams, ClaimStartParams, DirectoryEntry, DirectoryListInput, DirectoryProfile, DirectPingResult, JoinRoomInput, ListNotificationsInput, ListenInput, ListQuestionsInput, Paginated, PingInput, PingResult, PingRoomOptions, Question, QuestionInput, QuickAction, RegisterParams, Room, RotateHandleResult, TriggerInput, UpdateQuickActionInput, WaitApprovalInput, WaitInput, WaitQuestionInput, WaitResult } from './types.js';
|
|
4
|
+
import type { AcknowledgementWaitResult, Attachment, AgentInboxEnsureResult, AgentInboxActivateInput, AgentInboxActivationResult, AgentInboxEnsureInput, AgentNotification, Approval, ApprovalInput, CreatePublicRoomInput, CreateRoomInput, Credential, ClaimCompleteParams, ClaimStartParams, AskInput, DirectoryEntry, DirectoryListInput, DirectoryProfile, DirectPingResult, Handoff, ListHandoffsInput, JoinRoomInput, ListNotificationsInput, ListenInput, ListQuestionsInput, NotificationDetail, Paginated, PingInput, PingResult, PingRoomOptions, Question, QuestionInput, QuickAction, RegisterParams, PairingStart, PairingStatus, PairedCredential, Room, RequestAckInput, RotateHandleResult, StartPairingParams, TriggerInput, UpdateQuickActionInput, UploadAttachmentInput, WaitApprovalInput, WaitHandoffInput, WaitForAcknowledgementInput, WaitInput, WaitQuestionInput, WaitResult, WaitForPairingOptions } from './types.js';
|
|
4
5
|
/** Agent credential lifecycle (auth.md flow). */
|
|
5
6
|
declare class AuthApi {
|
|
6
7
|
private readonly http;
|
|
@@ -14,6 +15,23 @@ declare class AuthApi {
|
|
|
14
15
|
}>;
|
|
15
16
|
/** Complete the claim with the emailed OTP; returns an active credential + handle. */
|
|
16
17
|
claimComplete(params: ClaimCompleteParams): Promise<Credential>;
|
|
18
|
+
/**
|
|
19
|
+
* Register a pending agent and create a short-lived link that opens the
|
|
20
|
+
* PingRoom app. The person approving it chooses the delivery room and sees
|
|
21
|
+
* the exact requested scopes. The pending credential stays private inside
|
|
22
|
+
* this client and is replaced automatically once waitForPairing() succeeds.
|
|
23
|
+
*/
|
|
24
|
+
startPairing(params?: StartPairingParams): Promise<PairingStart>;
|
|
25
|
+
/** Read the current state of an app pairing using the pending credential. */
|
|
26
|
+
pairingStatus(options?: Pick<WaitForPairingOptions, 'signal'>): Promise<PairingStatus>;
|
|
27
|
+
/**
|
|
28
|
+
* Wait until the person approves in PingRoom, or the short-lived link
|
|
29
|
+
* expires. Transient network, 429, and 5xx failures are retried inside the
|
|
30
|
+
* server-provided lifetime. On success the client immediately starts using
|
|
31
|
+
* the active credential returned by the server.
|
|
32
|
+
*/
|
|
33
|
+
waitForPairing(pairing: PairingStart, options?: WaitForPairingOptions): Promise<PairedCredential>;
|
|
34
|
+
private throwIfPairingAborted;
|
|
17
35
|
/** Rotate to a fresh credential (same scopes/handle). */
|
|
18
36
|
refresh(): Promise<Credential>;
|
|
19
37
|
/** Revoke the current credential. */
|
|
@@ -40,6 +58,13 @@ declare class NotificationsApi {
|
|
|
40
58
|
constructor(http: HttpClient);
|
|
41
59
|
/** Paginated list (polling fallback). Prefer wait()/listen() for real-time. */
|
|
42
60
|
list(input?: ListNotificationsInput): Promise<Paginated<AgentNotification>>;
|
|
61
|
+
/** Fetch one visible notification, including its current acknowledgement state. */
|
|
62
|
+
getNotification(notificationId: string): Promise<NotificationDetail>;
|
|
63
|
+
/**
|
|
64
|
+
* One bounded long-poll for an ack-required ping. The returned state may
|
|
65
|
+
* still be `open` when the hold timeout elapses; call again to keep waiting.
|
|
66
|
+
*/
|
|
67
|
+
waitForAcknowledgement(notificationId: string, input?: WaitForAcknowledgementInput): Promise<AcknowledgementWaitResult>;
|
|
43
68
|
/** One long-poll: resolves as soon as new pings land, or with an empty list at timeout. */
|
|
44
69
|
wait(input?: WaitInput): Promise<WaitResult>;
|
|
45
70
|
/**
|
|
@@ -53,7 +78,11 @@ declare class NotificationsApi {
|
|
|
53
78
|
declare class AgentsApi {
|
|
54
79
|
private readonly http;
|
|
55
80
|
constructor(http: HttpClient);
|
|
56
|
-
/**
|
|
81
|
+
/**
|
|
82
|
+
* @deprecated Retired server-side — always fails with 410
|
|
83
|
+
* `cross_account_ping_retired`. An agent can only reach the account that
|
|
84
|
+
* connected it; to reach anyone else, post into a room you both belong to.
|
|
85
|
+
*/
|
|
57
86
|
ping(handle: string, ping: PingInput): Promise<DirectPingResult>;
|
|
58
87
|
}
|
|
59
88
|
declare class ApprovalsApi {
|
|
@@ -97,6 +126,56 @@ declare class QuestionsApi {
|
|
|
97
126
|
/** Withdraw a still-pending Question (409 if it already resolved). */
|
|
98
127
|
cancel(questionId: string): Promise<Question>;
|
|
99
128
|
}
|
|
129
|
+
/**
|
|
130
|
+
* The Handoff façade: the Agent → one-human loop. Always DIRECT (exactly one
|
|
131
|
+
* human) and PRIVATE (no other room member can read it). Backed by the ack and
|
|
132
|
+
* question primitives, so its {@link Handoff.state | state} space is their union
|
|
133
|
+
* — kept DISTINCT per {@link Handoff.kind | kind}. A negative answer to a
|
|
134
|
+
* question handoff (e.g. `hold`) is a SUCCESSFUL `answered` result, not an error.
|
|
135
|
+
*
|
|
136
|
+
* const task = await pr.handoffs.requestAck({ prompt, idempotencyKey });
|
|
137
|
+
* const result = await pr.handoffs.waitForResult(task.id);
|
|
138
|
+
*
|
|
139
|
+
* const q = await pr.handoffs.ask({ prompt, options: ['deploy', 'hold'], idempotencyKey });
|
|
140
|
+
*/
|
|
141
|
+
declare class HandoffsApi {
|
|
142
|
+
private readonly http;
|
|
143
|
+
constructor(http: HttpClient);
|
|
144
|
+
/** Ask a human to acknowledge (an `ack` handoff). Resolves on the first acknowledgement. */
|
|
145
|
+
requestAck(input: RequestAckInput): Promise<Handoff>;
|
|
146
|
+
/**
|
|
147
|
+
* Ask a human a multi-option question (a `question` handoff). Resolves on the
|
|
148
|
+
* first tapped answer. Bare-string options normalize to `{ value, label }`.
|
|
149
|
+
*/
|
|
150
|
+
ask(input: AskInput): Promise<Handoff>;
|
|
151
|
+
/** Fetch a handoff's current state. */
|
|
152
|
+
get(id: string): Promise<Handoff>;
|
|
153
|
+
/**
|
|
154
|
+
* Block until the handoff resolves (or expires/cancels), looping the bounded
|
|
155
|
+
* long-poll under the hood. `expired`/`cancelled` and a negative answer (e.g.
|
|
156
|
+
* `hold`) all surface as normal terminal results — nothing is thrown for them.
|
|
157
|
+
*/
|
|
158
|
+
waitForResult(id: string, input?: WaitHandoffInput): Promise<Handoff>;
|
|
159
|
+
/** List the agent's open handoffs or bounded recent history. */
|
|
160
|
+
list(input?: ListHandoffsInput): Promise<Handoff[]>;
|
|
161
|
+
/** One bounded long-poll; returns the (possibly still-open) handoff. */
|
|
162
|
+
private wait;
|
|
163
|
+
private create;
|
|
164
|
+
}
|
|
165
|
+
/** Setup and activation of the bound human's Agent Inbox delivery room. */
|
|
166
|
+
declare class InboxApi {
|
|
167
|
+
private readonly http;
|
|
168
|
+
constructor(http: HttpClient);
|
|
169
|
+
/** Replay the viable activation attempt or create its next numbered retry. */
|
|
170
|
+
ensure(input?: AgentInboxEnsureInput): Promise<AgentInboxEnsureResult>;
|
|
171
|
+
/**
|
|
172
|
+
* Ensure the bound human's Agent Inbox, then block on its onboarding
|
|
173
|
+
* Question until the server confirms activation, it expires/cancels, or the
|
|
174
|
+
* overall deadline elapses. Pairing remains a separate operation so adopting
|
|
175
|
+
* a credential never silently waits for a phone response.
|
|
176
|
+
*/
|
|
177
|
+
activate(input?: AgentInboxActivateInput): Promise<AgentInboxActivationResult>;
|
|
178
|
+
}
|
|
100
179
|
declare class ProfileApi {
|
|
101
180
|
private readonly http;
|
|
102
181
|
constructor(http: HttpClient);
|
|
@@ -105,6 +184,51 @@ declare class ProfileApi {
|
|
|
105
184
|
/** Rotate the agent's handle — the kill-switch for a leaked handle. */
|
|
106
185
|
rotateHandle(): Promise<RotateHandleResult>;
|
|
107
186
|
}
|
|
187
|
+
/**
|
|
188
|
+
* Live-status streams — a self-updating card on the room members' lock screen.
|
|
189
|
+
*
|
|
190
|
+
* Requires the `pingroom:live:write` scope. Free accounts get a small daily
|
|
191
|
+
* budget of NEW streams; updates and the terminal ping are never charged, so a
|
|
192
|
+
* card can never be quota-blocked into hanging open.
|
|
193
|
+
*/
|
|
194
|
+
declare class LiveApi {
|
|
195
|
+
private readonly http;
|
|
196
|
+
constructor(http: HttpClient);
|
|
197
|
+
/**
|
|
198
|
+
* Send one stream ping — start, update, or end, chosen by `live_status.state`
|
|
199
|
+
* and whether the correlation already exists.
|
|
200
|
+
*/
|
|
201
|
+
push(inviteCode: string, ping: LiveStatusPing): Promise<LiveStatusResult>;
|
|
202
|
+
/**
|
|
203
|
+
* Read back a stream this credential started, so a restarted producer can
|
|
204
|
+
* reconcile instead of opening a duplicate. Returns null when there is no
|
|
205
|
+
* such stream in the last 24 hours.
|
|
206
|
+
*/
|
|
207
|
+
get(inviteCode: string, correlationId: string): Promise<LiveStatusSnapshot | null>;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Private files that a ping can carry.
|
|
211
|
+
*
|
|
212
|
+
* Upload first, then pass the returned ids to `broadcast()` / `questions.ask()`
|
|
213
|
+
* as `attachment_ids`. Bytes never travel over MCP or a JSON ping body. An
|
|
214
|
+
* upload that is never attached expires on its own after 24 hours.
|
|
215
|
+
*
|
|
216
|
+
* Uploading requires the bound account to hold Pro (the API answers 402
|
|
217
|
+
* `pro_required` otherwise). Reading and deleting are not gated.
|
|
218
|
+
*/
|
|
219
|
+
declare class AttachmentsApi {
|
|
220
|
+
private readonly http;
|
|
221
|
+
constructor(http: HttpClient);
|
|
222
|
+
/** Upload one file and get back the id a send can claim. */
|
|
223
|
+
upload(input: UploadAttachmentInput): Promise<Attachment>;
|
|
224
|
+
/**
|
|
225
|
+
* Raw bytes for an attachment this credential is allowed to see. Returns the
|
|
226
|
+
* undecoded Response so a caller can stream it, or read text/arrayBuffer.
|
|
227
|
+
*/
|
|
228
|
+
content(attachmentId: string, signal?: AbortSignal): Promise<Response>;
|
|
229
|
+
/** Discard an upload that has not been attached to a ping yet. */
|
|
230
|
+
delete(attachmentId: string): Promise<void>;
|
|
231
|
+
}
|
|
108
232
|
/** Public, unauthenticated agent directory. */
|
|
109
233
|
declare class DirectoryApi {
|
|
110
234
|
private readonly http;
|
|
@@ -117,9 +241,13 @@ export declare class PingRoom {
|
|
|
117
241
|
readonly rooms: RoomsApi;
|
|
118
242
|
readonly actions: ActionsApi;
|
|
119
243
|
readonly notifications: NotificationsApi;
|
|
244
|
+
readonly attachments: AttachmentsApi;
|
|
120
245
|
readonly agents: AgentsApi;
|
|
121
246
|
readonly approvals: ApprovalsApi;
|
|
122
247
|
readonly questions: QuestionsApi;
|
|
248
|
+
readonly handoffs: HandoffsApi;
|
|
249
|
+
readonly inbox: InboxApi;
|
|
250
|
+
readonly live: LiveApi;
|
|
123
251
|
readonly profile: ProfileApi;
|
|
124
252
|
readonly directory: DirectoryApi;
|
|
125
253
|
readonly mcp: McpClient;
|
package/dist/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|