@pingroom/sdk 0.2.0 → 0.3.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 CHANGED
@@ -20,12 +20,32 @@ import { PingRoom } from '@pingroom/sdk';
20
20
  const pr = new PingRoom({ token: process.env.PINGROOM_TOKEN });
21
21
 
22
22
  // Broadcast to a room you own
23
- await pr.broadcast('ab12cd', { message: 'Deploy shipped ✅', data: { version: '1.4.0' } });
23
+ await pr.broadcast('ab12cd', {
24
+ message: 'Deploy shipped ✅',
25
+ data: { version: '1.4.0' },
26
+ requires_ack: true,
27
+ ack_timeout_seconds: 300,
28
+ });
24
29
 
25
30
  // Ping another agent by handle
26
31
  await pr.agents.ping('agt_reviewer', { message: 'PR #42 ready', correlation_id: 'pr-42' });
27
32
  ```
28
33
 
34
+ ### Link pings
35
+
36
+ A ping becomes a tappable link button when its `data` carries the reserved
37
+ keys `url` (absolute http(s), ≤ 2048 chars) and optional `button_label`
38
+ (≤ 26 chars). The `linkPing()` helper builds and validates that fragment:
39
+
40
+ ```ts
41
+ import { PingRoom, linkPing } from '@pingroom/sdk';
42
+
43
+ await pr.broadcast('ab12cd', {
44
+ message: 'Build 512 ready',
45
+ data: { commit: 'abc123', ...linkPing({ url: 'https://ci.example.com/b/512', buttonLabel: 'Open build' }) },
46
+ });
47
+ ```
48
+
29
49
  > 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
50
 
31
51
  ## Authentication
@@ -57,7 +77,7 @@ Generic MCP clients (Cursor, Claude Desktop, Claude Code) should use the standar
57
77
  ```ts
58
78
  await pr.rooms.list();
59
79
  await pr.rooms.get('ab12cd');
60
- await pr.rooms.create({ name: 'Deploys', icon: 'rocket', color: '#e53d30' });
80
+ await pr.rooms.create({ name: 'Deploys', icon: 'rocket', color: '#e33122' });
61
81
  await pr.rooms.join({ invite_code: 'ab12cd' });
62
82
 
63
83
  await pr.actions.update('ab12cd', 1, { label: 'Approve', icon: 'check' });
@@ -83,6 +103,26 @@ Or a single long-poll:
83
103
  const { notifications, cursor } = await pr.notifications.wait({ after: lastCursor, timeout: 20 });
84
104
  ```
85
105
 
106
+ Read one ping and wait for its acknowledgement through the same namespace:
107
+
108
+ ```ts
109
+ const ping = await pr.notifications.getNotification(notificationId);
110
+ console.log(ping.action_state?.status); // open | acked | expired
111
+
112
+ const result = await pr.notifications.waitForAcknowledgement(notificationId, { timeout: 30 });
113
+ switch (result.action_state.status) {
114
+ case 'acked':
115
+ console.log(`Acknowledged by ${result.action_state.acked_by?.name ?? 'someone'}`);
116
+ break;
117
+ case 'expired':
118
+ console.log('Nobody acknowledged before the deadline');
119
+ break;
120
+ case 'open':
121
+ // This bounded hold timed out; call waitForAcknowledgement again to continue.
122
+ break;
123
+ }
124
+ ```
125
+
86
126
  ## Human-in-the-loop approvals
87
127
 
88
128
  Ask the human you act for to decide, and block until they tap an answer on their phone.
@@ -141,6 +181,62 @@ await pr.questions.cancel(q.id); // withdraw a pending one
141
181
 
142
182
  > `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
183
 
184
+ ## Handoffs (Agent → one human)
185
+
186
+ 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:
187
+
188
+ - **`ack`** — "acknowledge this." Resolves the moment the human taps acknowledge. States: `open | acked | expired`.
189
+ - **`question`** — "pick one." 2–4 tappable options. States: `pending | answered | expired | cancelled`.
190
+
191
+ Requires the `pingroom:handoffs:create` scope.
192
+
193
+ On first connect, activate the human's private Agent Inbox. This is idempotent:
194
+ it creates at most one inbox and one onboarding test Question.
195
+
196
+ ```ts
197
+ const onboarding = await pr.inbox.ensure();
198
+ console.log(onboarding.room.invite_code, onboarding.question.state);
199
+ ```
200
+
201
+ ```ts
202
+ // Ask for an acknowledgement. Pass idempotencyKey so a retried create is a no-op.
203
+ const task = await pr.handoffs.requestAck({
204
+ prompt: 'Deploy 1.4.0 to prod is queued — ack to proceed.',
205
+ idempotencyKey: 'deploy-1.4.0', // reuse verbatim on network retries
206
+ });
207
+
208
+ // Block until the human acks (or it expires). Terminal states never throw.
209
+ const result = await pr.handoffs.waitForResult(task.id);
210
+ if (result.state === 'acked') {
211
+ console.log(`Acked by ${result.acked_by?.display_name} at ${result.acked_at}`);
212
+ }
213
+ ```
214
+
215
+ ```ts
216
+ // Ask a multi-option question. Bare strings normalize to { value, label: value }.
217
+ const q = await pr.handoffs.ask({
218
+ prompt: 'Ship or hold 1.4.0?',
219
+ options: ['deploy', 'hold'],
220
+ idempotencyKey: 'ship-1.4.0',
221
+ });
222
+
223
+ const answered = await pr.handoffs.waitForResult(q.id);
224
+ if (answered.state === 'answered') {
225
+ // A negative choice ('hold') is a SUCCESSFUL answered result — not an error.
226
+ console.log(`Human chose ${answered.answer?.value}`);
227
+ }
228
+ ```
229
+
230
+ `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`.
231
+
232
+ ```ts
233
+ await pr.handoffs.get(id); // current state (polling / audit)
234
+ await pr.handoffs.list({ state: 'open' }); // your open handoffs
235
+ await pr.handoffs.list({ state: 'all' }); // recent history (up to 200 per kind)
236
+ ```
237
+
238
+ 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`.
239
+
144
240
  ## Incoming webhooks (no token needed)
145
241
 
146
242
  A room's incoming-webhook URL carries its own secret — ideal for CI/deploy hooks:
@@ -151,21 +247,138 @@ import { sendIncomingWebhook } from '@pingroom/sdk';
151
247
  await sendIncomingWebhook(process.env.PINGROOM_WEBHOOK_URL, {
152
248
  message: 'Build #42 passed',
153
249
  data: { commit: 'abc123' },
250
+ requires_ack: true,
251
+ ack_timeout_seconds: 300,
154
252
  }, { idempotencyKey: 'build-42' });
155
253
  ```
156
254
 
255
+ ### Live Activities from a webhook
256
+
257
+ See [Live Activities](#live-activities) below for the full picture. From a webhook, put `live_status` at the **top level** of the payload:
258
+
259
+ ```ts
260
+ await sendIncomingWebhook(process.env.PINGROOM_WEBHOOK_URL, {
261
+ message: 'Deploying v1.4.0',
262
+ correlation_id: 'deploy-42',
263
+ live_status: { state: 'running', template: 'progress', progress: 0.4 },
264
+ });
265
+ ```
266
+
267
+ > **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.)
268
+
269
+ ## Live Activities
270
+
271
+ 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.
272
+
273
+ Protocol: <https://pingroom.io/liveactivities.md> (v1.2).
274
+
275
+ ### Two ways to send one
276
+
277
+ ```ts
278
+ import { PingRoom, liveStatus, sendIncomingWebhook } from '@pingroom/sdk';
279
+
280
+ // A. Agent credential — needs the `pingroom:live:write` scope.
281
+ const pr = new PingRoom({ token: process.env.PINGROOM_TOKEN });
282
+ await pr.live.push('AB12CD', liveStatus.progress('deploy-42', {
283
+ state: 'running',
284
+ message: 'Building image',
285
+ progress: 0.4,
286
+ }));
287
+
288
+ // B. A room's incoming webhook (Pro) — no token. The builder returns
289
+ // { correlation_id, live_status }, which is exactly the webhook body.
290
+ await sendIncomingWebhook(
291
+ process.env.PINGROOM_WEBHOOK_URL,
292
+ liveStatus.progress('deploy-42', {
293
+ state: 'running',
294
+ message: 'Building image',
295
+ progress: 0.4,
296
+ }),
297
+ );
298
+ ```
299
+
300
+ 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.
301
+
302
+ ### Templates
303
+
304
+ `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.
305
+
306
+ | Builder | Renders |
307
+ |---|---|
308
+ | `liveStatus.status` | Title, message, optional progress |
309
+ | `liveStatus.steps` | Segmented tracker; `steps` (2–8 labels) required on the first ping and immutable after |
310
+ | `liveStatus.progress` | Determinate bar with optional `eta_at` |
311
+ | `liveStatus.metrics` | Up to 3 `{label, value}` counters |
312
+ | `liveStatus.countdown` | Large live timer to `deadline_at` |
313
+ | `liveStatus.question` | `prompt` + up to 4 `{value, label}` options on the lock screen |
314
+ | `liveStatus.matchup` | `left` / `center` / `right` — a score or A-vs-B |
315
+
316
+ ```ts
317
+ await pr.live.push('AB12CD', liveStatus.steps('release-7', {
318
+ state: 'running',
319
+ steps: ['Build', 'Test', 'Deploy', 'Verify'],
320
+ current_step: 2,
321
+ message: 'Deploying to prod',
322
+ }));
323
+
324
+ await pr.live.push('AB12CD', liveStatus.matchup('game-3', {
325
+ state: 'running',
326
+ left: { label: 'ARS', value: '2' },
327
+ right: { label: 'CHE', value: '1' },
328
+ center: "68'",
329
+ }));
330
+ ```
331
+
332
+ ### Ending and reading back
333
+
334
+ ```ts
335
+ // Always end a stream. `done`/`failed` is never rate-limited or quota-blocked,
336
+ // so a card can't be metered into hanging open on a lock screen.
337
+ await pr.live.push('AB12CD', liveStatus.done('deploy-42', 'Shipped v1.4.0'));
338
+ // ...or liveStatus.failed('deploy-42', 'Rollback triggered')
339
+
340
+ // Reconcile after a restart instead of opening a duplicate.
341
+ // Returns null (not a throw) when there is no such stream in the last 24 hours.
342
+ const snap = await pr.live.get('AB12CD', 'deploy-42');
343
+ if (snap) console.log(snap.template, snap.current_step, snap.updated_at);
344
+ ```
345
+
346
+ `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.
347
+
348
+ ### Ownership and limits
349
+
350
+ 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.
351
+
352
+ ### `liveActivity.*` is not this
353
+
354
+ 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.
355
+
157
356
  ## Verifying outgoing webhooks
158
357
 
159
- When PingRoom POSTs an event to *your* server, verify it before trusting it. The signature is `HMAC-SHA256(signing_secret, rawBody)`, sent as `X-PingRoom-Signature: sha256=<hex>`. **Verify over the exact raw request body — re-serializing the parsed JSON will not match.**
358
+ When PingRoom POSTs an event to *your* server, verify it before trusting it. New deliveries carry two lower-case hex HMAC-SHA256 headers:
359
+
360
+ - `X-PingRoom-Signature-V2` signs `` `v2\n${timestamp}\n${deliveryId}\n${rawBody}` `` and binds `X-PingRoom-Delivery` as well as the timestamp and body.
361
+ - `X-PingRoom-Signature` keeps the legacy `` `${timestamp}.${rawBody}` `` contract so existing receivers continue to work.
362
+
363
+ `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
364
 
161
365
  ```ts
162
- import { verifyWebhookSignature, WEBHOOK_SIGNATURE_HEADER } from '@pingroom/sdk';
366
+ import {
367
+ verifyWebhookSignature,
368
+ WEBHOOK_SIGNATURE_HEADER,
369
+ WEBHOOK_SIGNATURE_V2_HEADER,
370
+ WEBHOOK_TIMESTAMP_HEADER,
371
+ WEBHOOK_DELIVERY_HEADER,
372
+ } from '@pingroom/sdk';
163
373
 
164
374
  // Express example — capture the raw body (e.g. express.raw())
165
375
  app.post('/pingroom', async (req, res) => {
166
376
  const ok = await verifyWebhookSignature({
167
377
  payload: req.body, // a Buffer/string of the RAW body
168
378
  signature: req.get(WEBHOOK_SIGNATURE_HEADER),
379
+ signatureV2: req.get(WEBHOOK_SIGNATURE_V2_HEADER),
380
+ timestamp: req.get(WEBHOOK_TIMESTAMP_HEADER),
381
+ deliveryId: req.get(WEBHOOK_DELIVERY_HEADER),
169
382
  secret: process.env.PINGROOM_SIGNING_SECRET,
170
383
  });
171
384
  if (!ok) return res.status(401).end();
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, AgentInboxEnsureResult, 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, Room, RequestAckInput, RotateHandleResult, TriggerInput, UpdateQuickActionInput, WaitApprovalInput, WaitHandoffInput, WaitForAcknowledgementInput, WaitInput, WaitQuestionInput, WaitResult } from './types.js';
4
5
  /** Agent credential lifecycle (auth.md flow). */
5
6
  declare class AuthApi {
6
7
  private readonly http;
@@ -40,6 +41,13 @@ declare class NotificationsApi {
40
41
  constructor(http: HttpClient);
41
42
  /** Paginated list (polling fallback). Prefer wait()/listen() for real-time. */
42
43
  list(input?: ListNotificationsInput): Promise<Paginated<AgentNotification>>;
44
+ /** Fetch one visible notification, including its current acknowledgement state. */
45
+ getNotification(notificationId: string): Promise<NotificationDetail>;
46
+ /**
47
+ * One bounded long-poll for an ack-required ping. The returned state may
48
+ * still be `open` when the hold timeout elapses; call again to keep waiting.
49
+ */
50
+ waitForAcknowledgement(notificationId: string, input?: WaitForAcknowledgementInput): Promise<AcknowledgementWaitResult>;
43
51
  /** One long-poll: resolves as soon as new pings land, or with an empty list at timeout. */
44
52
  wait(input?: WaitInput): Promise<WaitResult>;
45
53
  /**
@@ -53,7 +61,11 @@ declare class NotificationsApi {
53
61
  declare class AgentsApi {
54
62
  private readonly http;
55
63
  constructor(http: HttpClient);
56
- /** Direct agent-to-agent ping, addressed by the target's handle. */
64
+ /**
65
+ * @deprecated Retired server-side — always fails with 410
66
+ * `cross_account_ping_retired`. An agent can only reach the account that
67
+ * connected it; to reach anyone else, post into a room you both belong to.
68
+ */
57
69
  ping(handle: string, ping: PingInput): Promise<DirectPingResult>;
58
70
  }
59
71
  declare class ApprovalsApi {
@@ -97,6 +109,49 @@ declare class QuestionsApi {
97
109
  /** Withdraw a still-pending Question (409 if it already resolved). */
98
110
  cancel(questionId: string): Promise<Question>;
99
111
  }
112
+ /**
113
+ * The Handoff façade: the Agent → one-human loop. Always DIRECT (exactly one
114
+ * human) and PRIVATE (no other room member can read it). Backed by the ack and
115
+ * question primitives, so its {@link Handoff.state | state} space is their union
116
+ * — kept DISTINCT per {@link Handoff.kind | kind}. A negative answer to a
117
+ * question handoff (e.g. `hold`) is a SUCCESSFUL `answered` result, not an error.
118
+ *
119
+ * const task = await pr.handoffs.requestAck({ prompt, idempotencyKey });
120
+ * const result = await pr.handoffs.waitForResult(task.id);
121
+ *
122
+ * const q = await pr.handoffs.ask({ prompt, options: ['deploy', 'hold'], idempotencyKey });
123
+ */
124
+ declare class HandoffsApi {
125
+ private readonly http;
126
+ constructor(http: HttpClient);
127
+ /** Ask a human to acknowledge (an `ack` handoff). Resolves on the first acknowledgement. */
128
+ requestAck(input: RequestAckInput): Promise<Handoff>;
129
+ /**
130
+ * Ask a human a multi-option question (a `question` handoff). Resolves on the
131
+ * first tapped answer. Bare-string options normalize to `{ value, label }`.
132
+ */
133
+ ask(input: AskInput): Promise<Handoff>;
134
+ /** Fetch a handoff's current state. */
135
+ get(id: string): Promise<Handoff>;
136
+ /**
137
+ * Block until the handoff resolves (or expires/cancels), looping the bounded
138
+ * long-poll under the hood. `expired`/`cancelled` and a negative answer (e.g.
139
+ * `hold`) all surface as normal terminal results — nothing is thrown for them.
140
+ */
141
+ waitForResult(id: string, input?: WaitHandoffInput): Promise<Handoff>;
142
+ /** List the agent's open handoffs or bounded recent history. */
143
+ list(input?: ListHandoffsInput): Promise<Handoff[]>;
144
+ /** One bounded long-poll; returns the (possibly still-open) handoff. */
145
+ private wait;
146
+ private create;
147
+ }
148
+ /** One-call activation of the bound human's private Agent Inbox. */
149
+ declare class InboxApi {
150
+ private readonly http;
151
+ constructor(http: HttpClient);
152
+ /** Idempotently create/resolve the inbox and deliver its one-time test Question. */
153
+ ensure(): Promise<AgentInboxEnsureResult>;
154
+ }
100
155
  declare class ProfileApi {
101
156
  private readonly http;
102
157
  constructor(http: HttpClient);
@@ -105,6 +160,28 @@ declare class ProfileApi {
105
160
  /** Rotate the agent's handle — the kill-switch for a leaked handle. */
106
161
  rotateHandle(): Promise<RotateHandleResult>;
107
162
  }
163
+ /**
164
+ * Live-status streams — a self-updating card on the room members' lock screen.
165
+ *
166
+ * Requires the `pingroom:live:write` scope. Free accounts get a small daily
167
+ * budget of NEW streams; updates and the terminal ping are never charged, so a
168
+ * card can never be quota-blocked into hanging open.
169
+ */
170
+ declare class LiveApi {
171
+ private readonly http;
172
+ constructor(http: HttpClient);
173
+ /**
174
+ * Send one stream ping — start, update, or end, chosen by `live_status.state`
175
+ * and whether the correlation already exists.
176
+ */
177
+ push(inviteCode: string, ping: LiveStatusPing): Promise<LiveStatusResult>;
178
+ /**
179
+ * Read back a stream this credential started, so a restarted producer can
180
+ * reconcile instead of opening a duplicate. Returns null when there is no
181
+ * such stream in the last 24 hours.
182
+ */
183
+ get(inviteCode: string, correlationId: string): Promise<LiveStatusSnapshot | null>;
184
+ }
108
185
  /** Public, unauthenticated agent directory. */
109
186
  declare class DirectoryApi {
110
187
  private readonly http;
@@ -120,6 +197,9 @@ export declare class PingRoom {
120
197
  readonly agents: AgentsApi;
121
198
  readonly approvals: ApprovalsApi;
122
199
  readonly questions: QuestionsApi;
200
+ readonly handoffs: HandoffsApi;
201
+ readonly inbox: InboxApi;
202
+ readonly live: LiveApi;
123
203
  readonly profile: ProfileApi;
124
204
  readonly directory: DirectoryApi;
125
205
  readonly mcp: McpClient;
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
package/dist/client.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { PingRoomError } from './errors.js';
1
2
  import { HttpClient } from './http.js';
2
3
  import { sleep } from './internal/async.js';
3
4
  import { assertActionNumber, assertStructuredData, requireNonEmpty } from './internal/guards.js';
@@ -119,6 +120,23 @@ class NotificationsApi {
119
120
  list(input = {}) {
120
121
  return this.http.request('GET', '/api/agent/notifications', { query: dropUndefined({ ...input }) });
121
122
  }
123
+ /** Fetch one visible notification, including its current acknowledgement state. */
124
+ getNotification(notificationId) {
125
+ requireNonEmpty(notificationId, 'notificationId');
126
+ return this.http.request('GET', `/api/agent/notifications/${enc(notificationId)}`);
127
+ }
128
+ /**
129
+ * One bounded long-poll for an ack-required ping. The returned state may
130
+ * still be `open` when the hold timeout elapses; call again to keep waiting.
131
+ */
132
+ waitForAcknowledgement(notificationId, input = {}) {
133
+ requireNonEmpty(notificationId, 'notificationId');
134
+ return this.http.request('GET', `/api/agent/notifications/${enc(notificationId)}/ack/wait`, {
135
+ query: dropUndefined({ timeout: input.timeout }),
136
+ timeoutMs: waitTimeoutMs(input.timeout),
137
+ ...(input.signal ? { signal: input.signal } : {}),
138
+ });
139
+ }
122
140
  /** One long-poll: resolves as soon as new pings land, or with an empty list at timeout. */
123
141
  wait(input = {}) {
124
142
  return this.http.request('GET', '/api/agent/notifications/wait', {
@@ -176,7 +194,11 @@ class AgentsApi {
176
194
  constructor(http) {
177
195
  this.http = http;
178
196
  }
179
- /** Direct agent-to-agent ping, addressed by the target's handle. */
197
+ /**
198
+ * @deprecated Retired server-side — always fails with 410
199
+ * `cross_account_ping_retired`. An agent can only reach the account that
200
+ * connected it; to reach anyone else, post into a room you both belong to.
201
+ */
180
202
  ping(handle, ping) {
181
203
  requireNonEmpty(handle, 'handle');
182
204
  assertPing(ping);
@@ -279,6 +301,118 @@ class QuestionsApi {
279
301
  return this.http.request('POST', `/api/agent/questions/${enc(questionId)}/cancel`, { body: {} });
280
302
  }
281
303
  }
304
+ /** A bare string option is shorthand for `{ value, label: value }`. */
305
+ function normalizeHandoffOptions(options) {
306
+ return options.map((o) => (typeof o === 'string' ? { value: o, label: o } : o));
307
+ }
308
+ /** Terminal states never change again — the wait loop can stop as soon as one lands. */
309
+ function isHandoffTerminal(handoff) {
310
+ const s = handoff.state;
311
+ return s === 'acked' || s === 'answered' || s === 'expired' || s === 'cancelled';
312
+ }
313
+ /**
314
+ * The Handoff façade: the Agent → one-human loop. Always DIRECT (exactly one
315
+ * human) and PRIVATE (no other room member can read it). Backed by the ack and
316
+ * question primitives, so its {@link Handoff.state | state} space is their union
317
+ * — kept DISTINCT per {@link Handoff.kind | kind}. A negative answer to a
318
+ * question handoff (e.g. `hold`) is a SUCCESSFUL `answered` result, not an error.
319
+ *
320
+ * const task = await pr.handoffs.requestAck({ prompt, idempotencyKey });
321
+ * const result = await pr.handoffs.waitForResult(task.id);
322
+ *
323
+ * const q = await pr.handoffs.ask({ prompt, options: ['deploy', 'hold'], idempotencyKey });
324
+ */
325
+ class HandoffsApi {
326
+ http;
327
+ constructor(http) {
328
+ this.http = http;
329
+ }
330
+ /** Ask a human to acknowledge (an `ack` handoff). Resolves on the first acknowledgement. */
331
+ requestAck(input) {
332
+ requireNonEmpty(input.prompt, 'prompt');
333
+ assertStructuredData(input.data);
334
+ return this.create('ack', input);
335
+ }
336
+ /**
337
+ * Ask a human a multi-option question (a `question` handoff). Resolves on the
338
+ * first tapped answer. Bare-string options normalize to `{ value, label }`.
339
+ */
340
+ ask(input) {
341
+ requireNonEmpty(input.prompt, 'prompt');
342
+ assertStructuredData(input.data);
343
+ if (!Array.isArray(input.options) || input.options.length < 2 || input.options.length > 4) {
344
+ throw new PingRoomError('`options` must contain between 2 and 4 entries for a question handoff.', {
345
+ code: 'invalid_request',
346
+ });
347
+ }
348
+ return this.create('question', input, normalizeHandoffOptions(input.options));
349
+ }
350
+ /** Fetch a handoff's current state. */
351
+ get(id) {
352
+ requireNonEmpty(id, 'id');
353
+ return this.http.request('GET', `/api/agent/handoffs/${enc(id)}`);
354
+ }
355
+ /**
356
+ * Block until the handoff resolves (or expires/cancels), looping the bounded
357
+ * long-poll under the hood. `expired`/`cancelled` and a negative answer (e.g.
358
+ * `hold`) all surface as normal terminal results — nothing is thrown for them.
359
+ */
360
+ async waitForResult(id, input = {}) {
361
+ requireNonEmpty(id, 'id');
362
+ for (;;) {
363
+ if (input.signal?.aborted) {
364
+ throw input.signal.reason ?? new Error('aborted');
365
+ }
366
+ const handoff = await this.wait(id, input);
367
+ if (isHandoffTerminal(handoff)) {
368
+ return handoff;
369
+ }
370
+ }
371
+ }
372
+ /** List the agent's open handoffs or bounded recent history. */
373
+ async list(input = {}) {
374
+ const res = await this.http.request('GET', '/api/agent/handoffs', {
375
+ query: dropUndefined({ state: input.state }),
376
+ });
377
+ return res.handoffs ?? [];
378
+ }
379
+ /** One bounded long-poll; returns the (possibly still-open) handoff. */
380
+ wait(id, input) {
381
+ return this.http.request('GET', `/api/agent/handoffs/${enc(id)}/wait`, {
382
+ query: dropUndefined({ timeout: input.timeout }),
383
+ timeoutMs: waitTimeoutMs(input.timeout),
384
+ ...(input.signal ? { signal: input.signal } : {}),
385
+ });
386
+ }
387
+ create(kind, input, options) {
388
+ const body = dropUndefined({
389
+ kind,
390
+ prompt: input.prompt,
391
+ audience: { type: 'direct', user_id: input.target ?? 'me' },
392
+ expires_in: input.expiresIn,
393
+ urgency: input.urgency,
394
+ options,
395
+ correlation_id: input.correlationId,
396
+ reply_to: input.replyTo,
397
+ data: input.data,
398
+ });
399
+ return this.http.request('POST', '/api/agent/handoffs', {
400
+ body,
401
+ ...(input.idempotencyKey ? { headers: { 'Idempotency-Key': input.idempotencyKey } } : {}),
402
+ });
403
+ }
404
+ }
405
+ /** One-call activation of the bound human's private Agent Inbox. */
406
+ class InboxApi {
407
+ http;
408
+ constructor(http) {
409
+ this.http = http;
410
+ }
411
+ /** Idempotently create/resolve the inbox and deliver its one-time test Question. */
412
+ ensure() {
413
+ return this.http.request('POST', '/api/agent/inbox/ensure', { body: {} });
414
+ }
415
+ }
282
416
  class ProfileApi {
283
417
  http;
284
418
  constructor(http) {
@@ -294,6 +428,46 @@ class ProfileApi {
294
428
  return this.http.request('POST', '/api/agent/profile/handle/rotate', { body: {} });
295
429
  }
296
430
  }
431
+ /**
432
+ * Live-status streams — a self-updating card on the room members' lock screen.
433
+ *
434
+ * Requires the `pingroom:live:write` scope. Free accounts get a small daily
435
+ * budget of NEW streams; updates and the terminal ping are never charged, so a
436
+ * card can never be quota-blocked into hanging open.
437
+ */
438
+ class LiveApi {
439
+ http;
440
+ constructor(http) {
441
+ this.http = http;
442
+ }
443
+ /**
444
+ * Send one stream ping — start, update, or end, chosen by `live_status.state`
445
+ * and whether the correlation already exists.
446
+ */
447
+ push(inviteCode, ping) {
448
+ requireNonEmpty(inviteCode, 'inviteCode');
449
+ requireNonEmpty(ping.correlation_id, 'correlation_id');
450
+ assertStructuredData(ping.data);
451
+ return this.http.request('POST', `/api/agent/rooms/${enc(inviteCode)}/live`, { body: ping });
452
+ }
453
+ /**
454
+ * Read back a stream this credential started, so a restarted producer can
455
+ * reconcile instead of opening a duplicate. Returns null when there is no
456
+ * such stream in the last 24 hours.
457
+ */
458
+ async get(inviteCode, correlationId) {
459
+ requireNonEmpty(inviteCode, 'inviteCode');
460
+ requireNonEmpty(correlationId, 'correlationId');
461
+ try {
462
+ return await this.http.request('GET', `/api/agent/rooms/${enc(inviteCode)}/live/${enc(correlationId)}`);
463
+ }
464
+ catch (error) {
465
+ if (error instanceof PingRoomError && error.status === 404)
466
+ return null;
467
+ throw error;
468
+ }
469
+ }
470
+ }
297
471
  /** Public, unauthenticated agent directory. */
298
472
  class DirectoryApi {
299
473
  http;
@@ -319,6 +493,9 @@ export class PingRoom {
319
493
  agents;
320
494
  approvals;
321
495
  questions;
496
+ handoffs;
497
+ inbox;
498
+ live;
322
499
  profile;
323
500
  directory;
324
501
  mcp;
@@ -340,6 +517,9 @@ export class PingRoom {
340
517
  this.agents = new AgentsApi(this.http);
341
518
  this.approvals = new ApprovalsApi(this.http);
342
519
  this.questions = new QuestionsApi(this.http);
520
+ this.handoffs = new HandoffsApi(this.http);
521
+ this.inbox = new InboxApi(this.http);
522
+ this.live = new LiveApi(this.http);
343
523
  this.profile = new ProfileApi(this.http);
344
524
  this.directory = new DirectoryApi(this.http);
345
525
  this.mcp = new McpClient(this.http);