salt-agent-sdk 0.1.0 → 0.18.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.
Files changed (103) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +941 -30
  3. package/dist/actions.d.ts +21 -5
  4. package/dist/actions.d.ts.map +1 -1
  5. package/dist/actions.js +673 -54
  6. package/dist/actions.js.map +1 -1
  7. package/dist/ask.d.ts +83 -0
  8. package/dist/ask.d.ts.map +1 -0
  9. package/dist/ask.js +215 -0
  10. package/dist/ask.js.map +1 -0
  11. package/dist/chatPrivacy.d.ts +34 -0
  12. package/dist/chatPrivacy.d.ts.map +1 -0
  13. package/dist/chatPrivacy.js +60 -0
  14. package/dist/chatPrivacy.js.map +1 -0
  15. package/dist/client.d.ts +2783 -35
  16. package/dist/client.d.ts.map +1 -1
  17. package/dist/client.js +1200 -143
  18. package/dist/client.js.map +1 -1
  19. package/dist/config.d.ts +16 -6
  20. package/dist/config.d.ts.map +1 -1
  21. package/dist/config.js +16 -5
  22. package/dist/config.js.map +1 -1
  23. package/dist/crypto.d.ts +62 -0
  24. package/dist/crypto.d.ts.map +1 -1
  25. package/dist/crypto.js +121 -0
  26. package/dist/crypto.js.map +1 -1
  27. package/dist/delegations.d.ts +25 -8
  28. package/dist/delegations.d.ts.map +1 -1
  29. package/dist/delegations.js +67 -11
  30. package/dist/delegations.js.map +1 -1
  31. package/dist/device/client.d.ts +161 -0
  32. package/dist/device/client.d.ts.map +1 -0
  33. package/dist/device/client.js +601 -0
  34. package/dist/device/client.js.map +1 -0
  35. package/dist/device/host.d.ts +222 -0
  36. package/dist/device/host.d.ts.map +1 -0
  37. package/dist/device/host.js +891 -0
  38. package/dist/device/host.js.map +1 -0
  39. package/dist/device/http.d.ts +12 -0
  40. package/dist/device/http.d.ts.map +1 -0
  41. package/dist/device/http.js +124 -0
  42. package/dist/device/http.js.map +1 -0
  43. package/dist/device/index.d.ts +5 -0
  44. package/dist/device/index.d.ts.map +1 -0
  45. package/dist/device/index.js +27 -0
  46. package/dist/device/index.js.map +1 -0
  47. package/dist/device/protocol.d.ts +312 -0
  48. package/dist/device/protocol.d.ts.map +1 -0
  49. package/dist/device/protocol.js +435 -0
  50. package/dist/device/protocol.js.map +1 -0
  51. package/dist/identities.d.ts +9 -2
  52. package/dist/identities.d.ts.map +1 -1
  53. package/dist/identities.js +33 -3
  54. package/dist/identities.js.map +1 -1
  55. package/dist/identity.d.ts +155 -0
  56. package/dist/identity.d.ts.map +1 -0
  57. package/dist/identity.js +196 -0
  58. package/dist/identity.js.map +1 -0
  59. package/dist/identityShare.d.ts +129 -0
  60. package/dist/identityShare.d.ts.map +1 -0
  61. package/dist/identityShare.js +308 -0
  62. package/dist/identityShare.js.map +1 -0
  63. package/dist/ids.d.ts +11 -0
  64. package/dist/ids.d.ts.map +1 -0
  65. package/dist/ids.js +17 -0
  66. package/dist/ids.js.map +1 -0
  67. package/dist/index.d.ts +13 -0
  68. package/dist/index.d.ts.map +1 -1
  69. package/dist/index.js +13 -0
  70. package/dist/index.js.map +1 -1
  71. package/dist/reconcile.d.ts +25 -0
  72. package/dist/reconcile.d.ts.map +1 -0
  73. package/dist/reconcile.js +60 -0
  74. package/dist/reconcile.js.map +1 -0
  75. package/dist/recovery.d.ts +51 -0
  76. package/dist/recovery.d.ts.map +1 -0
  77. package/dist/recovery.js +59 -0
  78. package/dist/recovery.js.map +1 -0
  79. package/dist/register.d.ts +32 -0
  80. package/dist/register.d.ts.map +1 -0
  81. package/dist/register.js +65 -0
  82. package/dist/register.js.map +1 -0
  83. package/dist/scriptJob.d.ts +92 -0
  84. package/dist/scriptJob.d.ts.map +1 -0
  85. package/dist/scriptJob.js +270 -0
  86. package/dist/scriptJob.js.map +1 -0
  87. package/dist/sessions.d.ts +131 -0
  88. package/dist/sessions.d.ts.map +1 -0
  89. package/dist/sessions.js +240 -0
  90. package/dist/sessions.js.map +1 -0
  91. package/dist/socket.d.ts +115 -0
  92. package/dist/socket.d.ts.map +1 -0
  93. package/dist/socket.js +1038 -0
  94. package/dist/socket.js.map +1 -0
  95. package/dist/webhook.d.ts +613 -24
  96. package/dist/webhook.d.ts.map +1 -1
  97. package/dist/webhook.js +1384 -83
  98. package/dist/webhook.js.map +1 -1
  99. package/dist/work.d.ts +57 -0
  100. package/dist/work.d.ts.map +1 -0
  101. package/dist/work.js +213 -0
  102. package/dist/work.js.map +1 -0
  103. package/package.json +26 -4
package/dist/client.d.ts CHANGED
@@ -1,5 +1,27 @@
1
+ import { type SaltId } from "./ids.js";
2
+ import { type IdentityClaims, type IdentitySections, type SignedCard } from "./identity.js";
3
+ import { type HeldRecoveryShare, type RecoveryGuardianInvitation, type RecoveryRequestForGuardian } from "./recovery.js";
4
+ export interface IdentityDisclosure {
5
+ id: string;
6
+ section_keys: string[];
7
+ scope: string;
8
+ chat_id: SaltId;
9
+ recipient_id: SaltId;
10
+ message_id?: SaltId | null;
11
+ created_at: string;
12
+ revoked_at?: string | null;
13
+ }
14
+ export interface PostIdentityDisclosureParams {
15
+ /** Client-generated (crypto.randomUUID()), ONE per share() call and posted for every recipient -- also what rides in the SLICE/DECLINE wire marker's `id=`. The server keys a row on (subject, recipient, id), so the same id with a DIFFERENT recipient_id creates its own row; the same id AND recipient_id together is idempotent (200 with the existing row instead of a second one). */
16
+ id: string;
17
+ section_keys: string[];
18
+ /** Optional since 0.86.1: omitted, salt-api records the narrowest real scope; named, it is held to per section. */
19
+ scope?: string;
20
+ chat_id: SaltId;
21
+ recipient_id: SaltId;
22
+ }
1
23
  export interface SaltUser {
2
- id: number;
24
+ id: SaltId;
3
25
  username: string;
4
26
  display_name: string;
5
27
  account_type: "User" | "Agent";
@@ -7,23 +29,96 @@ export interface SaltUser {
7
29
  public_fingerprint?: string;
8
30
  [key: string]: unknown;
9
31
  }
32
+ /** Who may start a 1:1 with an account without being refused or parked in Requests (`users.message_from`).
33
+ * `everyone`: anyone. `known`: strangers land in the recipient's Requests (the default for a person).
34
+ * `contacts`: strangers are refused with 403 `recipient_contacts_only`. An agent account defaults to `everyone`. */
35
+ export type MessageFrom = "everyone" | "known" | "contacts";
36
+ /** `request` means the other member's row in the 1:1 is a pending message request: the recipient has not
37
+ * accepted yet, so they get no push and read receipts are held back. Wait for them; do not re-send and
38
+ * do not open another chat. `chat` is a normal delivery. */
39
+ export type DeliveredAs = "chat" | "request";
10
40
  export interface SaltChat {
11
- id: number;
41
+ id: SaltId;
42
+ /** On a chat-create response: whether the other member received a normal chat or a pending request. */
43
+ delivered_as?: DeliveredAs;
44
+ /** `encrypted` is undefined on a server that predates open rooms, which
45
+ * always means an ordinary end-to-end encrypted chat -- see getChat's
46
+ * doc comment for the resolved, defaulted value callers actually use. */
12
47
  session?: {
13
48
  users?: SaltUser[];
49
+ encrypted?: boolean;
50
+ public?: boolean;
51
+ [key: string]: unknown;
14
52
  };
15
53
  messages?: unknown[];
16
54
  [key: string]: unknown;
17
55
  }
56
+ /** One row of `GET /api/v1/chats` (and `?requests=1`). There is NO top-level `id`: the chat's id is `session.id`.
57
+ * `recent_message` is `{}` for a chat with no messages. `session.request_state` is `"pending"` on a request. */
58
+ export interface SaltChatListRow {
59
+ session: {
60
+ id: SaltId;
61
+ request_state?: "pending" | null;
62
+ users?: SaltUser[];
63
+ [key: string]: unknown;
64
+ };
65
+ recent_message?: {
66
+ message?: unknown;
67
+ user?: unknown;
68
+ [key: string]: unknown;
69
+ };
70
+ [key: string]: unknown;
71
+ }
72
+ /** What `POST /api/v1/messages` answers: the stored message plus how it was delivered. */
73
+ export interface PostMessageResult {
74
+ delivered_as?: DeliveredAs;
75
+ [key: string]: unknown;
76
+ }
77
+ /** One row of `GET /api/v1/contacts/incoming`: an account that added the caller and that the caller has not
78
+ * added back. `added_at` is when they added you. */
79
+ export interface IncomingContact {
80
+ user: {
81
+ id: SaltId;
82
+ username: string;
83
+ display_name: string;
84
+ avatar_url: string | null;
85
+ account_type: "User" | "Agent";
86
+ /** Your own private nickname for them, when you set one. */
87
+ nickname?: string;
88
+ };
89
+ added_at: string;
90
+ }
91
+ export interface SendAttachmentParams {
92
+ bytes: Buffer;
93
+ filename: string;
94
+ /** Must be one of crypto.ts's ALLOWED_SEND_CONTENT_TYPES -- sendAttachment
95
+ * throws rather than send an unrecognised value another member's client
96
+ * will make a rendering decision on (see that list's own comment). */
97
+ contentType: string;
98
+ /** Defaults to "📎 <sanitized filename>", the same fallback salt-fe's
99
+ * chatbox.jsx sendOneAttachment uses, so a captionless attachment reads
100
+ * identically whether it came from a person or an agent. */
101
+ caption?: string;
102
+ }
103
+ /**
104
+ * Basename only, control characters and path separators stripped, capped at
105
+ * 200 characters, falling back to "attachment" when nothing usable is left.
106
+ * A filename lives only inside an attachment's PGP-encrypted metadata blob
107
+ * (see Attachment's header comment in salt-api) -- Salt itself never sees
108
+ * or sanitizes it -- so anything that later treats a DECRYPTED filename as
109
+ * a filesystem path (an agent's own read/save-to-disk tool) must sanitize
110
+ * on the way OUT, since it is exactly as attacker-controlled as any other
111
+ * chat member's input. Exported so both sendAttachment (outgoing) and a
112
+ * consumer's incoming-attachment handler can share one rule.
113
+ */
114
+ export declare function sanitizeAttachmentFilename(name: string | undefined | null): string;
18
115
  export interface CreateAgentParams {
19
116
  username: string;
20
117
  display_name: string;
21
118
  description?: string;
22
119
  webhook: string;
23
120
  public_key: string;
24
- private_key: string;
25
121
  public_fingerprint: string;
26
- revocation_cert?: string;
27
122
  category?: string;
28
123
  message_price?: string | number;
29
124
  link?: string;
@@ -32,37 +127,560 @@ export interface CardBlock {
32
127
  type: string;
33
128
  [key: string]: unknown;
34
129
  }
130
+ /** The `input` block (NEW): one text field. `block_id` must be unique
131
+ * across the whole card (including private blocks), `[a-z0-9_-]{1,40}`.
132
+ * Tapping any `default` button in the same card sends every input block's
133
+ * current value alongside the action -- see `CardInteractionContext.values`
134
+ * / `CardInteraction.values`, keyed by `block_id`. A plain object satisfies
135
+ * `CardBlock` already; this is here for callers who want the shape typed. */
136
+ export interface InputCardBlock extends CardBlock {
137
+ type: "input";
138
+ block_id: string;
139
+ label: string;
140
+ placeholder?: string;
141
+ multiline?: boolean;
142
+ /** 1..1000, defaults to 500 server-side when omitted. */
143
+ max_length?: number;
144
+ }
145
+ /** One recorded tap on a card's button, as `getCard` returns it -- newest
146
+ * first. `transfer_request_id`/`transfer_request_status` are present only
147
+ * for a tap that created a real payment request (a "pay" button), and the
148
+ * status is read LIVE by the server, never a snapshot from tap time.
149
+ * `values` (NEW) carries whatever the card's `input` blocks held at tap
150
+ * time, keyed by `block_id` -- `{}` when the card has none or the tapped
151
+ * button ignored them (pay/handoff buttons always do). */
152
+ export interface CardInteraction {
153
+ id: string;
154
+ user_id: SaltId;
155
+ action_id: string;
156
+ value?: unknown;
157
+ values?: Record<string, string>;
158
+ created_at: string;
159
+ transfer_request_id?: SaltId;
160
+ transfer_request_status?: string;
161
+ }
162
+ /** `getCard`'s response shape: the card's raw state (as its owner sees it --
163
+ * `state.private` included, unlike the per-viewer projection every other
164
+ * member gets in chat payloads) plus its tap history. */
165
+ export interface CardWithInteractions {
166
+ id: SaltId;
167
+ state: {
168
+ blocks: CardBlock[];
169
+ [key: string]: unknown;
170
+ };
171
+ owner_id: SaltId;
172
+ interactions: CardInteraction[];
173
+ }
174
+ export interface AppPerson {
175
+ id: SaltId;
176
+ username: string;
177
+ display_name: string;
178
+ }
179
+ /** One placeholder text field a quick action collects before it's sent -- omitted, the action has no input and its `label` alone is the ask's `text`. */
180
+ export interface AppQuickActionInput {
181
+ placeholder?: string;
182
+ }
183
+ /**
184
+ * A small "quick tap" a version declares alongside its html -- rendered as
185
+ * a chip/button beside the running app (e.g. `AppMessage.jsx`) that calls
186
+ * `salt.ask` bound to `id`, using `label` as the ask's text (or, with
187
+ * `input` set, whatever one line the person typed into that field).
188
+ * Versioned exactly like the html itself: a new version's `actions`
189
+ * replace the prior version's for any installation that later updates to
190
+ * it -- an installation still pinned to an older version keeps that
191
+ * version's own `actions`.
192
+ */
193
+ export interface AppQuickAction {
194
+ id: string;
195
+ label: string;
196
+ input?: AppQuickActionInput;
197
+ }
198
+ /** A version's identity as it rides on an app's metadata, a `newVersion` response, or a chat resource -- never the html itself (fetch that via the app_frames route, keyed by `id`). */
199
+ export interface AppVersionRef {
200
+ id: SaltId;
201
+ number: number;
202
+ /** This version's quick actions, if it declared any -- see AppQuickAction. */
203
+ actions?: AppQuickAction[];
204
+ }
205
+ /** One agent's recipe inside a team template. Never a key, memory, credential, grant, wallet or chat -- see `teams` below. */
206
+ export interface TeamAgentRecipe {
207
+ name: string;
208
+ purpose: string;
209
+ /** The agent's voice text. */
210
+ persona: string;
211
+ directives: string[];
212
+ skills: string[];
213
+ /** Connector provider NAMES the agent was given; never a credential. */
214
+ services: string[];
215
+ /** Sanitised drawn look, when the agent has one. */
216
+ look?: {
217
+ svg: string;
218
+ description?: string;
219
+ };
220
+ }
221
+ /** A shared agent team: a recipe each installer makes their own copies from. */
222
+ export interface TeamTemplate {
223
+ id: SaltId;
224
+ /** Short id used in share links; accepted wherever an id is. */
225
+ slug: string;
226
+ name: string;
227
+ description: string | null;
228
+ listed: boolean;
229
+ install_count: number;
230
+ agent_count: number;
231
+ needs: string[];
232
+ coordinator_note: string | null;
233
+ author: {
234
+ id: SaltId;
235
+ username: string;
236
+ display_name: string;
237
+ avatar_url: string | null;
238
+ account_type: string;
239
+ };
240
+ /** Absent from list responses. */
241
+ agents?: TeamAgentRecipe[];
242
+ [key: string]: unknown;
243
+ }
244
+ export interface ShareTeamParams {
245
+ /** One of the caller's own agent groups. Only the agents the caller owns are shared. */
246
+ groupId: SaltId;
247
+ name?: string;
248
+ description?: string;
249
+ /** Listed on the Teams shelf; default is unlisted (reachable by its link only). */
250
+ listed?: boolean;
251
+ /** Plain text each installed agent gets as a directive about who hands off to whom (at most 200 characters). */
252
+ coordinatorNote?: string;
253
+ }
254
+ /** An app's metadata -- `GET /api/v1/apps/:id`, the App Store listing, and `/mine`. */
255
+ export interface AppSummary {
256
+ id: SaltId;
257
+ name: string;
258
+ description?: string;
259
+ listed?: boolean;
260
+ installs_count?: number;
261
+ latest_version_id?: SaltId | null;
262
+ /** Present on the single-app GET -- the latest version's {id, number}. */
263
+ version?: AppVersionRef;
264
+ owner?: AppPerson;
265
+ built_by?: AppPerson | null;
266
+ [key: string]: unknown;
267
+ }
268
+ export interface CreateAppParams {
269
+ name: string;
270
+ description?: string;
271
+ html: string;
272
+ /** An agent building FOR a person -- that person must share a chat with the caller, else 422. Omit to build for the caller itself. */
273
+ forId?: SaltId;
274
+ /** Installs the new app into this chat in the same transaction (caller must be a member). */
275
+ chatId?: SaltId;
276
+ /** Version 1's quick actions -- see AppQuickAction. */
277
+ actions?: AppQuickAction[];
278
+ }
279
+ /** `AppInstallation#as_chat_resource` -- what a chat message's `resource` carries for `resource_type: "AppInstallation"`, and what `create`/`install` return. */
280
+ export interface AppInstallation {
281
+ id: SaltId;
282
+ app_id?: SaltId;
283
+ app_version_id?: SaltId;
284
+ installed_by_id?: SaltId;
285
+ /** Null for a personal ("Your apps") installation. */
286
+ chat_id?: SaltId | null;
287
+ /** The agent that answers this installation's `ask` -- null when it has none. */
288
+ agent_id?: SaltId | null;
289
+ removed_at?: string | null;
290
+ app?: {
291
+ id: SaltId;
292
+ name: string;
293
+ description?: string;
294
+ owner?: AppPerson;
295
+ built_by?: AppPerson | null;
296
+ };
297
+ version?: AppVersionRef;
298
+ latest_number?: number;
299
+ /** The PINNED version's quick actions, flattened here (same values as `version.actions`) so a chat bubble can render them without a nested lookup. */
300
+ actions?: AppQuickAction[];
301
+ [key: string]: unknown;
302
+ }
303
+ export interface CreateAppResult {
304
+ app: AppSummary;
305
+ version: AppVersionRef;
306
+ /** Present only when `chatId` was given -- installed in the same transaction. */
307
+ installation?: AppInstallation;
308
+ }
309
+ export interface InstallAppParams {
310
+ chatId?: SaltId;
311
+ /** Another installation of the SAME app the caller can read -- copies its doc once into the new installation's state. */
312
+ copyStateFrom?: SaltId;
313
+ }
314
+ /** `GET`/`PUT .../state`'s shape -- also `.../mine`'s (the viewer's private doc), same CAS rules. */
315
+ export interface AppStateDoc {
316
+ doc: Record<string, unknown>;
317
+ version: number;
318
+ }
319
+ /**
320
+ * Thrown by `setState` on a 409 -- the installation's shared state moved
321
+ * under the caller since `ifVersion`. Carries the CURRENT `{doc, version}`
322
+ * straight off the response body (salt-api's own 409 payload), so a caller
323
+ * can merge its change onto the fresh doc and retry with the new version
324
+ * rather than spending a second round trip just to re-fetch first.
325
+ */
326
+ export declare class AppStateConflictError extends Error {
327
+ doc: Record<string, unknown>;
328
+ version: number;
329
+ constructor(doc: Record<string, unknown>, version: number);
330
+ }
35
331
  export interface InvoiceLineItem {
36
332
  name: string;
37
333
  qty: number;
38
334
  unit_price: string | number;
39
335
  subtotal: string | number;
40
- product_id?: number;
336
+ product_id?: SaltId;
41
337
  }
42
338
  export interface CreateInvoiceParams {
43
- chatId: number;
44
- receiverId: number;
45
- walletId?: number;
339
+ chatId: SaltId;
340
+ receiverId: SaltId;
341
+ walletId?: SaltId;
46
342
  amount: string | number;
47
343
  lineItems: InvoiceLineItem[];
48
344
  message?: string;
49
345
  dueAt?: string;
346
+ /** See createInvoice's JSDoc for retry/reconciliation semantics. */
347
+ idempotencyKey?: string;
50
348
  }
51
349
  export interface AddUsageParams {
52
- productId: number;
53
- chatId: number;
350
+ productId: SaltId;
351
+ chatId: SaltId;
54
352
  qty: number;
55
353
  description?: string;
56
- payerId?: number;
354
+ payerId?: SaltId;
355
+ /** See addUsage's JSDoc for retry/reconciliation semantics. */
356
+ idempotencyKey?: string;
57
357
  }
58
358
  export interface SaltClientOptions {
59
359
  host: string;
60
360
  /** Override fetch (e.g. for tests). Defaults to global fetch. */
61
361
  fetchImpl?: typeof fetch;
62
362
  }
363
+ /** One chain's governed-spend budget, applied over a rolling period. */
364
+ export interface SpendingPolicyRule {
365
+ chain: string;
366
+ period: "daily" | "weekly" | "monthly";
367
+ /** HUMAN-DECIMAL string, e.g. "0.5" = half an ETH -- never base units (wei/satoshis). */
368
+ budget_amount: string;
369
+ /** HUMAN-DECIMAL string cap on a single send, if set. */
370
+ per_tx_max?: string;
371
+ /** Server-computed: HUMAN-DECIMAL string already spent in the current period. */
372
+ spent_this_period?: string;
373
+ /** Server-computed: HUMAN-DECIMAL string remaining in the current period. */
374
+ remaining?: string;
375
+ }
376
+ /**
377
+ * An agent's governed-spend policy, as returned by the API. `frozen: true`
378
+ * blocks every mainnet send regardless of `rules`; an agent with no policy
379
+ * at all is refused by default (salt-api default-denies agent mainnet
380
+ * sends until an owner sets one up) -- see getSpendingPolicy.
381
+ */
382
+ export interface SpendingPolicy {
383
+ frozen: boolean;
384
+ rules: SpendingPolicyRule[];
385
+ }
386
+ /** Payload for updateSpendingPolicy -- same shape as SpendingPolicyRule minus the server-computed fields. */
387
+ export interface UpdateSpendingPolicyParams {
388
+ frozen: boolean;
389
+ rules: Array<{
390
+ chain: string;
391
+ period: string;
392
+ /** HUMAN-DECIMAL string, e.g. "0.5" = half an ETH -- never base units. */
393
+ budget_amount: string;
394
+ /** HUMAN-DECIMAL string, if set. */
395
+ per_tx_max?: string;
396
+ }>;
397
+ }
398
+ export interface ReceiptParty {
399
+ id: SaltId;
400
+ username: string;
401
+ }
402
+ /** Only present when the settled transfer paid a TransferRequest (a regular send has no `request`). */
403
+ export interface ReceiptRequestSummary {
404
+ id: SaltId;
405
+ request_type: string;
406
+ status: string;
407
+ line_items: InvoiceLineItem[] | null;
408
+ }
409
+ export interface ReceiptPayload {
410
+ transfer_id: SaltId;
411
+ /** HUMAN-DECIMAL string, the declared send amount. */
412
+ amount: string;
413
+ /** HUMAN-DECIMAL string, the value the chain actually moved -- absent until/unless verified; authoritative over `amount` where they disagree. */
414
+ settled_amount?: string;
415
+ chain?: string;
416
+ testnet?: boolean;
417
+ token_contract_address?: string;
418
+ tx_id?: string;
419
+ sender?: ReceiptParty;
420
+ receiver?: ReceiptParty;
421
+ settled_at: string;
422
+ /**
423
+ * The sending agent's budget internals at enforcement time. Redacted
424
+ * (key absent) unless the caller is the sending agent itself or its root
425
+ * human owner -- a counterparty never sees the agent's per-tx/budget
426
+ * limits, only that the send was governed.
427
+ */
428
+ policy_snapshot?: unknown;
429
+ request?: ReceiptRequestSummary;
430
+ }
431
+ /** A tamper-evident settlement record, minted once a Transfer confirms on-chain. */
432
+ export interface Receipt {
433
+ transfer_id: SaltId;
434
+ payload: ReceiptPayload;
435
+ /** SHA-256 over sorted-key canonical JSON of the FULL (unredacted) payload -- still verifiable even when `policy_snapshot` was redacted for this viewer. */
436
+ digest: string;
437
+ created_at: string;
438
+ }
439
+ export interface BillingAccountParty {
440
+ id: SaltId;
441
+ username: string;
442
+ display_name: string;
443
+ account_type: "User" | "Agent";
444
+ }
445
+ export interface BillingAccountCreditEntry {
446
+ id: SaltId;
447
+ kind: "deposit" | "usage" | "refund";
448
+ /** HUMAN-DECIMAL string. */
449
+ amount: string;
450
+ qty?: string;
451
+ description?: string;
452
+ created_at: string;
453
+ }
454
+ /** A pair-scoped prepaid credit relationship: payer deposits with payee, payee's metered usage draws it down. */
455
+ export interface BillingAccount {
456
+ id: SaltId;
457
+ payer_id: SaltId;
458
+ payee_id: SaltId;
459
+ payer: BillingAccountParty;
460
+ payee: BillingAccountParty;
461
+ /** HUMAN-DECIMAL string: deposits + refunds - usage. This IS the payer's spend cap on this payee's metered products. */
462
+ balance: string;
463
+ /** Most recent 10 ledger entries, newest first. */
464
+ recent_entries: BillingAccountCreditEntry[];
465
+ }
466
+ /**
467
+ * Error codes salt-api can return for a governed-spend refusal. These ride
468
+ * in the response body as `{error: string, code: SpendGovernanceCode}` --
469
+ * check `SaltApiError.body.code` rather than parsing `.message`.
470
+ *
471
+ * - `spending_frozen` / `no_spending_policy` -- the agent's policy blocks all sends (see SpendingPolicy).
472
+ * - `per_tx_max_exceeded` / `budget_exceeded` -- the send exceeds a configured rule.
473
+ * - `spend_unconfirmed` -- the agent already has an unconfirmed governed send on this chain; wait for it to confirm, then retry.
474
+ * - `token_sends_ungoverned` / `ungoverned_wallet` -- this send path isn't covered by policy enforcement yet.
475
+ * - `invalid_amount` -- amount wasn't a valid human-decimal string.
476
+ * - `unverifiable_tx` -- the signed transaction couldn't be decoded at all; a governed send must be a standard signed transfer.
477
+ * - `declaration_mismatch` -- the signed transaction's destination, amount, or call data doesn't match what was declared.
478
+ * - `request_in_flight` / `request_failed_terminal` / `idempotency_key_reuse` -- see addUsage's JSDoc (the invoice rail does not implement these -- see createInvoice's JSDoc).
479
+ */
480
+ export type SpendGovernanceCode = "spending_frozen" | "no_spending_policy" | "per_tx_max_exceeded" | "budget_exceeded" | "spend_unconfirmed" | "token_sends_ungoverned" | "ungoverned_wallet" | "invalid_amount" | "unverifiable_tx" | "declaration_mismatch" | "request_in_flight" | "request_failed_terminal" | "idempotency_key_reuse";
481
+ export type MandateKind = "explicit" | "governance" | "connection";
482
+ export type MandateStatus = "proposed" | "active" | "paused" | "revoked" | "expired";
483
+ export type MandateCapabilityMode = "auto" | "ask" | "notify";
484
+ export type MandateExerciseDecision = "allowed" | "asked" | "approved" | "denied" | "refused" | "expired";
485
+ export interface MandateParty {
486
+ id: SaltId;
487
+ username: string;
488
+ display_name: string;
489
+ account_type: "User" | "Agent";
490
+ avatar_url?: string;
491
+ [key: string]: unknown;
492
+ }
493
+ export interface MandateCapabilityRow {
494
+ id: SaltId;
495
+ capability: string;
496
+ /** e.g. `{chat_ids: [...]}`, `{wallet_ids: [...]}`, `{product_ids: [...]}`, or `{}` for "every one". */
497
+ selector: Record<string, unknown>;
498
+ mode: MandateCapabilityMode;
499
+ /** e.g. `per_request_max`, `budget_amount`/`period`, `rate_limit` -- shape depends on `capability`. */
500
+ constraints: Record<string, unknown>;
501
+ }
502
+ /** One `{capability, selector, mode, constraints}` row as sent to `propose`/`update` -- same shape as
503
+ * MandateCapabilityRow minus the server-assigned `id`. */
504
+ export interface MandateCapabilityInput {
505
+ capability: string;
506
+ selector?: Record<string, unknown>;
507
+ mode: MandateCapabilityMode;
508
+ constraints?: Record<string, unknown>;
509
+ }
510
+ export interface MandateTrail {
511
+ count: number;
512
+ today_count: number;
513
+ last_at?: string;
514
+ open_asks: number;
515
+ }
516
+ export interface MandateChild {
517
+ id: SaltId;
518
+ grantee: MandateParty;
519
+ label?: string;
520
+ }
521
+ /** A mandate row as served by `GET /api/v1/mandates`/`:id` and carried on `mandate_offered`/
522
+ * `mandate_activated`/`mandate_paused`/`mandate_revoked` webhook events (see webhook.ts). */
523
+ export interface Mandate {
524
+ id: SaltId;
525
+ kind: MandateKind;
526
+ status: MandateStatus;
527
+ label?: string;
528
+ grantor: MandateParty;
529
+ grantee: MandateParty;
530
+ capabilities: MandateCapabilityRow[];
531
+ starts_at: string;
532
+ expires_at?: string;
533
+ standing: boolean;
534
+ grantee_fingerprint?: string;
535
+ /** True when the grantee's current key no longer matches `grantee_fingerprint` -- salt-api pauses
536
+ * the mandate the moment it notices (see `mandate_paused` with `pause_reason: "key_changed"`). */
537
+ key_changed?: boolean;
538
+ parent_id?: SaltId;
539
+ depth?: number;
540
+ version: number;
541
+ chat_id?: SaltId;
542
+ proposed_by_id?: SaltId;
543
+ accepted_at?: string;
544
+ paused_at?: string;
545
+ pause_reason?: string;
546
+ revoked_at?: string;
547
+ revoke_reason?: string;
548
+ children?: MandateChild[];
549
+ trail?: MandateTrail;
550
+ [key: string]: unknown;
551
+ }
552
+ /** One row of a mandate's trail -- an allowed act, an ask (settled or still open), or a refusal.
553
+ * `refusal` is only ever populated for the principal (or its governor) -- see salt-api's
554
+ * `MandateExercise#as_json_for`. */
555
+ export interface MandateExercise {
556
+ id: SaltId | number;
557
+ mandate_id: SaltId;
558
+ capability: string;
559
+ action: string;
560
+ decision: MandateExerciseDecision;
561
+ reason?: string;
562
+ refusal?: string;
563
+ summary: Record<string, unknown>;
564
+ target?: {
565
+ type?: string;
566
+ id?: SaltId;
567
+ };
568
+ chat_id?: SaltId;
569
+ message_id?: SaltId;
570
+ actor: {
571
+ id: SaltId;
572
+ username: string;
573
+ display_name: string;
574
+ account_type: "User" | "Agent";
575
+ };
576
+ principal: {
577
+ id: SaltId;
578
+ username: string;
579
+ };
580
+ created_at: string;
581
+ decided_at?: string;
582
+ expires_at?: string;
583
+ [key: string]: unknown;
584
+ }
585
+ export interface ProposeMandateParams {
586
+ grantee_id?: SaltId;
587
+ grantee_username?: string;
588
+ /** Only when proposing UP to a grantor rather than granting down (the caller becomes `proposed_by`). */
589
+ grantor_id?: SaltId;
590
+ label?: string;
591
+ capabilities: MandateCapabilityInput[];
592
+ standing?: boolean;
593
+ expires_at?: string;
594
+ parent_id?: SaltId;
595
+ chat_id?: SaltId;
596
+ }
597
+ export interface UpdateMandateParams {
598
+ /** Required: compare-and-swap against the mandate's current `version` -- a stale value is a 409. */
599
+ version: number;
600
+ label?: string;
601
+ capabilities?: MandateCapabilityInput[];
602
+ standing?: boolean;
603
+ expires_at?: string;
604
+ }
605
+ /**
606
+ * A 202 `{status: "asked", exercise_id, expires_at}` response, resolved rather than thrown --
607
+ * see `SaltClient.actFor` below. `isAsked` narrows an unknown result to this shape.
608
+ */
609
+ export interface AskedResult {
610
+ asked: true;
611
+ exerciseId: SaltId;
612
+ expiresAt?: string;
613
+ }
614
+ export declare function isAsked(value: unknown): value is AskedResult;
615
+ /** Maps a client method's `Promise<R>` to `Promise<R | AskedResult>` -- what every call through
616
+ * `actFor`'s returned client can resolve to, since any mapped action can come back an ask
617
+ * depending on how the grantor configured that capability's mode. Non-function properties (e.g.
618
+ * `mandates`) and non-Promise-returning ones (e.g. `trackEvent`) pass through unchanged. */
619
+ export type Acted<F> = F extends (...args: infer A) => Promise<infer R> ? (...args: A) => Promise<R | AskedResult> : F;
620
+ export interface ActForOptions {
621
+ /** Pin one specific mandate rather than letting salt-api pick the strictest match among every
622
+ * active mandate that grants the call. */
623
+ mandateId?: SaltId;
624
+ }
625
+ /** What the reactions endpoint answers: the message's reactions after your change. */
626
+ export interface ReactionSummary {
627
+ message_id: SaltId;
628
+ reactions: unknown;
629
+ }
630
+ /** The post a reply answers, as salt-api sends it on a reply row. `author` and `excerpt` are null
631
+ * when the original is gone or not visible to the caller. */
632
+ export interface PostReplyParent {
633
+ id: SaltId;
634
+ author: string | null;
635
+ excerpt: string | null;
636
+ }
637
+ /** A post's photo or video: image/jpeg|png|webp|gif|heic or video/mp4|webm|quicktime, up to 15 MB. */
638
+ export interface PostMediaInfo {
639
+ url: string;
640
+ content_type: string;
641
+ size: number;
642
+ }
643
+ /** The file to attach to a post. */
644
+ export interface PostMediaInput {
645
+ data: Uint8Array;
646
+ contentType: string;
647
+ }
648
+ /** A post in someone's profile room, as `POST /api/v1/posts/:id/reply` and `GET /api/v1/posts/:id/replies`
649
+ * serve it. A reply is its author's OWN post, linked to what it answers: `is_reply`, `parent` and
650
+ * `replying_to` are present only on replies. */
651
+ export interface PostRow {
652
+ id: SaltId;
653
+ room_id: SaltId;
654
+ created_at: string;
655
+ message: string;
656
+ author: unknown;
657
+ reply_count: number;
658
+ reactions: unknown;
659
+ following: boolean;
660
+ /** Present only on a post that carries a photo or video; `url` is public (GET /api/v1/posts/:id/media). */
661
+ media?: PostMediaInfo;
662
+ is_reply?: boolean;
663
+ parent?: PostReplyParent;
664
+ replying_to?: string | null;
665
+ }
666
+ export interface PostRepliesPage {
667
+ post: PostRow;
668
+ replies: PostRow[];
669
+ next_before: string | null;
670
+ }
63
671
  export declare class SaltApiError extends Error {
64
672
  status: number;
65
673
  body: unknown;
674
+ /** Salt's machine-readable refusal code (`{error, code}`), when the body carries one. */
675
+ code?: string;
676
+ constructor(method: string, url: string, status: number, body: unknown);
677
+ }
678
+ export declare const RECIPIENT_CONTACTS_ONLY = "recipient_contacts_only";
679
+ /** 403 `recipient_contacts_only`: the recipient only takes messages from their contacts and the caller is
680
+ * not one, so no chat was created. `message` ends with the server's own sentence ("<name> only takes
681
+ * messages from their contacts."), also on `sentence`. Do not retry; a request for contact is the only way in. */
682
+ export declare class RecipientContactsOnlyError extends SaltApiError {
683
+ sentence: string;
66
684
  constructor(method: string, url: string, status: number, body: unknown);
67
685
  }
68
686
  /**
@@ -71,71 +689,2201 @@ export declare class SaltApiError extends Error {
71
689
  * module-level header, since one process can legitimately act as several
72
690
  * Salt agents at once.
73
691
  */
692
+ /** Every client method's call shape -- `request<T>` itself, and `actingRequest`'s wrapper below,
693
+ * both match this so `buildMethods` (further down) can be built from either one interchangeably. */
694
+ type RequestFn = <T>(method: string, path: string, apiKey: string, body?: unknown, opts?: {
695
+ extraHeaders?: Record<string, string>;
696
+ idempotencyKey?: string;
697
+ }) => Promise<T>;
74
698
  export declare function createSaltClient(options: SaltClientOptions): {
699
+ actFor: (principalId: SaltId, opts?: ActForOptions) => { [K in keyof ReturnType<(request: RequestFn) => {
700
+ /** A chat's members (with public keys), for encrypting a reply to everyone who should read it. */
701
+ getChatMembers(apiKey: string, chatId: SaltId): Promise<SaltUser[]>;
702
+ /** Recent messages in a chat this identity is a member of (even as a silent observer). */
703
+ getChatMessages(apiKey: string, chatId: SaltId): Promise<unknown[]>;
704
+ /**
705
+ * One `GET /api/v1/chats/:id` call for a caller that needs BOTH the
706
+ * member list and whether the chat is end-to-end encrypted -- e.g.
707
+ * actions.ts's request_floor, which (unlike delegate_to_agent/
708
+ * consult_agent) has no other reason to fetch the chat at all.
709
+ * `encrypted` is already resolved to a real boolean (defaulted `true`
710
+ * when the field is absent, same convention as ChatOpenedContext.encrypted
711
+ * and MessageContext.encrypted) -- never undefined.
712
+ */
713
+ getChat(apiKey: string, chatId: SaltId): Promise<{
714
+ id: SaltId;
715
+ encrypted: boolean;
716
+ users: SaltUser[];
717
+ }>;
718
+ /**
719
+ * Salt's own record of a chat, exactly as `GET /api/v1/chats/:id` answers
720
+ * it (nothing flattened or defaulted, unlike getChat): `session.users`
721
+ * with each member's `observer` flag, `session.public`, `session.encrypted`.
722
+ * This is what `isPrivateChat(chat, speakerId, myId)` reads -- decide
723
+ * privacy-sensitive actions from THIS, fetched when you need it, never
724
+ * from a cache or a memory service.
725
+ */
726
+ getChatRecord(apiKey: string, chatId: SaltId): Promise<SaltChat>;
727
+ /** Download an attachment's ciphertext bytes. Decrypt separately (see crypto.ts). */
728
+ getAttachment(apiKey: string, messageId: SaltId): Promise<Buffer>;
729
+ /**
730
+ * Post a reply into a chat. `message` is the ciphertext every human
731
+ * recipient reads; `senderMessage` is the copy encrypted to the
732
+ * sender's own key. `delegations` (optional) is the provenance trail
733
+ * -- salt-api re-validates every entry against the sender's actual
734
+ * memberships before storing it. The result carries `delivered_as`:
735
+ * `"request"` means the recipient has not accepted a first message from
736
+ * you yet (see Message requests in AGENTS.md) -- wait, do not re-send.
737
+ */
738
+ postMessage(apiKey: string, chatId: SaltId, message: string, senderMessage?: string, delegations?: Array<{
739
+ agent_id: SaltId;
740
+ chat_id: SaltId;
741
+ username: string;
742
+ }>, mentions?: SaltId[], opts?: {
743
+ quiet?: boolean;
744
+ }): Promise<PostMessageResult>;
745
+ /**
746
+ * Sends a file into a chat, encrypted exactly the way salt-fe's
747
+ * chatbox.jsx sendOneAttachment encrypts one: a fresh one-time AES-256-
748
+ * GCM key encrypts the file bytes (crypto.ts's encryptAttachment,
749
+ * byte-identical wire format to the browser's Web Crypto output), and
750
+ * that key/iv plus the real filename/content_type/size are PGP-
751
+ * encrypted as one JSON blob for every current chat member's public
752
+ * key -- the SAME single recipient list the caption text is encrypted
753
+ * for, fetched fresh via GET /api/v1/chats/:id so a just-added or
754
+ * just-removed member is never missed or over-included. Unlike
755
+ * postMessage's reply() there is no separate sender_message: the web
756
+ * never excludes the sender from an attachment's recipient list (it
757
+ * has no reason to -- there's only one `attachment_encrypted_key`
758
+ * field, see Attachment#as_chat_resource), so a single PGP blob
759
+ * covering every member already covers the sender too.
760
+ *
761
+ * Throws before doing any crypto work on an oversized file (matches
762
+ * salt-api's own MAX_ATTACHMENT_BYTES so a doomed upload never even
763
+ * starts) or a content_type outside ALLOWED_SEND_CONTENT_TYPES.
764
+ */
765
+ sendAttachment(apiKey: string, chatId: SaltId, params: SendAttachmentParams): Promise<unknown>;
766
+ /**
767
+ * Post into an OPEN ROOM -- a chat with no end-to-end encryption, so
768
+ * `text` rides the wire and is stored as plain text rather than a PGP
769
+ * blob. There is no `senderMessage`/`delegations`/`mentions` shape here
770
+ * the way postMessage has: salt-api refuses this call 422 against a
771
+ * chat that isn't plain (never silently encrypts, never silently
772
+ * drops the call). Webhook/socket deliveries for a plain chat carry
773
+ * `message.encrypted === false` -- see webhook.ts's handleMessage,
774
+ * which hands the body straight through as ctx.text (ctx.encrypted
775
+ * false) instead of attempting PGP decrypt.
776
+ */
777
+ postPlainMessage(apiKey: string, chatId: SaltId, text: string): Promise<unknown>;
778
+ /**
779
+ * Open rooms (0.6x): declares what this identity wants delivered from
780
+ * a chat it isn't necessarily addressed in every message of --
781
+ * `"addressed"` (only a direct reply/@mention, the closest analogue to
782
+ * how a normal encrypted chat already gates agent delivery),
783
+ * `"keywords"` (any message containing one of `keywords`), or `"all"`
784
+ * (every message). Always this identity's OWN subscription for
785
+ * `chatId` -- same "acts on the caller, never someone else" shape as
786
+ * setDeliveryMode/setCallback.
787
+ */
788
+ setChatSubscription(apiKey: string, chatId: SaltId, params: {
789
+ mode: "addressed" | "keywords" | "all";
790
+ keywords?: string[];
791
+ }): Promise<unknown>;
792
+ /** Removes this identity's subscription record for `chatId` -- salt-api's
793
+ * default (unsubscribed) behavior applies again, same as never having
794
+ * called setChatSubscription. */
795
+ clearChatSubscription(apiKey: string, chatId: SaltId): Promise<unknown>;
796
+ /**
797
+ * Get-or-create the private lane (a sidechain) between this identity and
798
+ * `withId`, both members of `chatId`. Once per pair on Salt's side. Answers
799
+ * 422 when `chatId` is itself a lane. The lane's members come back with
800
+ * their public keys.
801
+ */
802
+ openSidechain(apiKey: string, chatId: SaltId, withId: SaltId): Promise<{
803
+ session: {
804
+ id: SaltId;
805
+ users?: SaltUser[];
806
+ coaching_for_chat_id?: SaltId;
807
+ [key: string]: unknown;
808
+ };
809
+ messages?: unknown[];
810
+ }>;
811
+ /**
812
+ * Opens (or reuses) a CONSULT lane between the caller and `withId`, both
813
+ * already members of `roomId` -- mechanically identical to
814
+ * openSidechain (same endpoint, same get-or-create-per-pair semantics),
815
+ * named separately because actions.ts's consult_agent reads more
816
+ * clearly calling "open the lane I'm about to consult in" than a
817
+ * generic sidechain call. salt-api tags the resulting chat
818
+ * `lane_kind: "consult"`, which webhook.ts reads back off the webhook
819
+ * body to raise the agent-to-agent reply cap for that lane and to gate
820
+ * request_floor.
821
+ */
822
+ openConsultLane(apiKey: string, roomId: SaltId, withId: SaltId): Promise<{
823
+ session: {
824
+ id: SaltId;
825
+ users?: SaltUser[];
826
+ coaching_for_chat_id?: SaltId;
827
+ lane_kind?: string;
828
+ [key: string]: unknown;
829
+ };
830
+ messages?: unknown[];
831
+ }>;
832
+ /** Register a new Salt agent, owned by whoever's api-key calls this. */
833
+ /**
834
+ * The create response carries the new agent's raw api key EXACTLY ONCE --
835
+ * salt-api stores only a digest, so no later endpoint can show it again.
836
+ * Capture `api_key` here; a lost key means rotateAgentApiKey, not re-reading.
837
+ *
838
+ * Key custody: `CreateAgentParams` deliberately has no `private_key` --
839
+ * salt-api rejects one with a 422 unconditionally (salt-api docs/
840
+ * KEY_CUSTODY.md Phase 5). The key stays with whoever generates it
841
+ * (pgp.generateKeypair, above the call site) and is registered with
842
+ * identities.register/a session store; salt-api never receives a copy in
843
+ * any form it can decrypt, so there is nothing to fetch back later --
844
+ * getAgentAdmin's `userkeys` never carries a usable key either.
845
+ */
846
+ createAgent(apiKey: string, params: CreateAgentParams): Promise<SaltUser & {
847
+ api_key?: string;
848
+ }>;
849
+ /** Owner-only: mint a new api key for an agent. The old one stops working immediately. */
850
+ rotateAgentApiKey(apiKey: string, agentId: SaltId): Promise<{
851
+ api_key: string;
852
+ }>;
853
+ /**
854
+ * This identity's own webhook signing key, used to verify that an incoming
855
+ * webhook really came from salt-api.
856
+ *
857
+ * Fetched with the identity's own api key rather than configured from the
858
+ * environment: salt-api mints one secret per agent, so shipping them through
859
+ * env/Secrets Manager would mean the deploy had to know values the API
860
+ * generates. Fetching at boot also means rotation takes effect on restart.
861
+ */
862
+ getWebhookSecret(apiKey: string): Promise<string | undefined>;
863
+ /**
864
+ * Which agent does this api key belong to, according to salt-api?
865
+ *
866
+ * The same endpoint as getWebhookSecret: alongside the secret it returns
867
+ * the canonical `agent_id` of the key's owner, which is the id salt-api
868
+ * puts in X-Salt-Agent-Id on every callback. An identity registered under
869
+ * any other id -- an env var, or a store entry that predates an id
870
+ * migration -- can never be found when a webhook arrives; that is what
871
+ * reconcileIdentityIds uses this to detect and repair.
872
+ */
873
+ whoAmI: (apiKey: string) => Promise<{
874
+ agent_id: SaltId;
875
+ webhook_secret?: string;
876
+ }>;
877
+ /**
878
+ * Owner-only: mint a new webhook signing secret for an agent. The old
879
+ * secret stops verifying immediately, so the agent must re-fetch via
880
+ * getWebhookSecret before it can validate any further deliveries.
881
+ */
882
+ rotateWebhookSecret(apiKey: string, agentId: SaltId): Promise<{
883
+ agent_id: SaltId;
884
+ webhook_secret: string;
885
+ }>;
886
+ /**
887
+ * The caller sets its OWN webhook callback -- api-key auth, no id
888
+ * parameter, same "acts on the caller, never someone else" shape as
889
+ * setDeliveryMode/setChatSubscription. Exists so the platform hosting
890
+ * an agent can point its callback at itself without the owner ever
891
+ * opening Salt's web console -- see AgentsController#set_callback's
892
+ * own comment. Same URL-safety rule as the owner's web form
893
+ * (User#callback_must_be_safe) applies server-side; a human caller
894
+ * (not an agent's own api-key) gets a 403.
895
+ */
896
+ setCallback(apiKey: string, webhook: string): Promise<{
897
+ agent_id: SaltId;
898
+ callback: string;
899
+ }>;
900
+ /**
901
+ * K2 socket mode: the caller sets its OWN delivery mode -- "webhook"
902
+ * (the default, POST to its registered callback) or "socket" (drain
903
+ * GET /api/v1/agent/updates or AgentUpdatesChannel instead; see
904
+ * socket.ts's createSocketClient). No id parameter, same reason
905
+ * getWebhookSecret has none -- an agent can only ever change its own.
906
+ * A blank callback is always socket mode regardless of this setting.
907
+ */
908
+ setDeliveryMode(apiKey: string, mode: "webhook" | "socket"): Promise<{
909
+ agent_id: SaltId;
910
+ delivery_mode: string;
911
+ socket_mode: boolean;
912
+ }>;
913
+ /**
914
+ * Owner-only agent admin record. `apikey` is metadata (hint, last-used) --
915
+ * the raw key rides only on the createAgent response, and after that only
916
+ * rotateAgentApiKey can produce a usable value.
917
+ */
918
+ getAgentAdmin(apiKey: string, agentId: SaltId): Promise<{
919
+ apikey: {
920
+ token_hint?: string;
921
+ last_used_at?: string;
922
+ } | null;
923
+ userkeys: unknown;
924
+ [key: string]: unknown;
925
+ }>;
926
+ /**
927
+ * This agent's own identity: every section (claim and proof) with
928
+ * `scope`/`editable_by`/`checked_by`, plus the public card URL it's
929
+ * served from. Cache-busted like getChatMembers -- a value just set
930
+ * with setIdentity should read back immediately.
931
+ */
932
+ identity(apiKey: string): Promise<IdentitySections>;
933
+ /**
934
+ * Set one or more of this agent's own CLAIM sections (see
935
+ * AGENT_CLAIM_SECTION_KEYS). `scope` is never accepted here -- section
936
+ * visibility is owner-authenticated through the agent-management API
937
+ * only (plan section 7: "setScope is never on this surface"), so a
938
+ * caller that tries to slip one in is refused BEFORE any request is
939
+ * made, the same way a bad tool call should fail loud and immediately
940
+ * rather than round-tripping to find out the server also says no.
941
+ * A 422 from salt-api ({errors: {key: "line"}} for an invalid value, or
942
+ * {error: "Scope comes later."} were scope to somehow reach it anyway)
943
+ * surfaces as an ordinary SaltApiError.
944
+ */
945
+ setIdentity(apiKey: string, claims: IdentityClaims): Promise<IdentitySections>;
946
+ /**
947
+ * Writes one disclosure ledger row -- metadata only, never a section
948
+ * VALUE (plan section 4). identityShare.ts's share() calls this once
949
+ * per recipient BEFORE sending any ciphertext, with the SAME `id` every
950
+ * time (the row is keyed on subject + recipient + id, so this creates
951
+ * one row per recipient under one shared id), and aborts the whole
952
+ * share on the first refusal (422, one plain sentence -- e.g. a key
953
+ * that isn't actually shared with that recipient) with nothing sent.
954
+ */
955
+ postIdentityDisclosure(apiKey: string, params: PostIdentityDisclosureParams): Promise<IdentityDisclosure>;
956
+ /**
957
+ * Records which chat message actually carried a share's slice, once
958
+ * it's been posted. Called ONCE per share() call (with the share's one
959
+ * `id`) right after the one encrypted SLICE message goes out
960
+ * (identityShare.ts's share()) -- since every recipient's row shares
961
+ * that id, one PATCH reaches all of them.
962
+ */
963
+ setIdentityDisclosureMessage(apiKey: string, id: string, messageId: SaltId): Promise<{
964
+ disclosures: IdentityDisclosure[];
965
+ }>;
966
+ /** This agent's own disclosure ledger, newest first. */
967
+ listIdentityDisclosures(apiKey: string, opts?: {
968
+ before?: string;
969
+ limit?: number;
970
+ }): Promise<{
971
+ disclosures: IdentityDisclosure[];
972
+ }>;
973
+ /**
974
+ * Revokes EVERY ledger row under `id` (every recipient of that share)
975
+ * in one call -- stops them being served again and is the signal
976
+ * identityShare.ts's revoke() sends a `[[SALT-IDENTITY-REVOKE]]` wire
977
+ * marker off of. Cannot reach a message already read; the rows
978
+ * themselves are the only thing this call changes.
979
+ */
980
+ revokeIdentityDisclosure(apiKey: string, id: string): Promise<{
981
+ disclosures: IdentityDisclosure[];
982
+ }>;
983
+ /**
984
+ * Fetches a person's or an agent's PUBLICLY SERVED signed card and
985
+ * verifies its signature before returning it -- this is a public,
986
+ * unauthenticated GET (no api-key), matching agent_cards_controller's
987
+ * `skip_before_action :auth_user`. Tries the agent path then the user
988
+ * path unless `opts.kind` says which one. A card whose signature this
989
+ * SDK cannot confirm (missing, unparseable, unknown key, tampered)
990
+ * throws IdentityCardInvalidError rather than ever coming back
991
+ * `verified: false` -- a card either verifies or this call fails.
992
+ */
993
+ card(handle: string, opts?: {
994
+ kind?: "agent" | "user";
995
+ }): Promise<{
996
+ card: SignedCard;
997
+ verified: true;
998
+ }>;
999
+ /** The public Agents directory. */
1000
+ listAgents(apiKey: string): Promise<SaltUser[]>;
1001
+ /** A single agent's directory entry. */
1002
+ getAgent(apiKey: string, agentId: SaltId): Promise<SaltUser>;
1003
+ /** Create-or-reuse a 1:1 chat with `contactId` -- idempotent on Salt's side. The result's `delivered_as` is
1004
+ * `"request"` when the contact has not accepted a first message from you yet: wait, and do not open another
1005
+ * chat. A recipient who only takes messages from their contacts throws `RecipientContactsOnlyError`. */
1006
+ createOrGetChat(apiKey: string, contactId: SaltId): Promise<SaltChat>;
1007
+ /** Your own `message_from` setting: who can start a chat with you without landing in Requests or being refused. */
1008
+ getMessageFrom(apiKey: string): Promise<MessageFrom>;
1009
+ /** Set your own `message_from` (an agent changes its OWN setting). An unknown value is a 422 with
1010
+ * `code: "invalid_message_from"`. Returns the updated account fields. */
1011
+ setMessageFrom(apiKey: string, value: MessageFrom): Promise<{
1012
+ message_from: MessageFrom;
1013
+ [key: string]: unknown;
1014
+ }>;
1015
+ /** Message requests waiting on you: 1:1 chats strangers started that you have not accepted. Same shape as
1016
+ * the chat list (rows are `{session, recent_message}`: the chat id is `row.session.id`); ordinary chats
1017
+ * are not in it. You can read a request through the API and decide. */
1018
+ listRequests(apiKey: string): Promise<SaltChatListRow[]>;
1019
+ /** Accept a message request: it becomes an ordinary chat. Idempotent. Returns the chat. */
1020
+ acceptRequest(apiKey: string, chatId: SaltId): Promise<SaltChat>;
1021
+ /** Decline a message request. The sender is not told: they still see the chat as sent, and anything more
1022
+ * they send you (a new chat, a group add) stays quiet. Blocking the sender (the block endpoint) also declines. */
1023
+ declineRequest(apiKey: string, chatId: SaltId): Promise<void>;
1024
+ /** Accounts that added you as a contact and that you have not added back ("Someone added you"). */
1025
+ listIncomingContacts(apiKey: string): Promise<IncomingContact[]>;
1026
+ /** Accept an incoming contact: add them back (`POST /api/v1/contacts {contact_id}`, the ordinary add-contact call).
1027
+ * This also follows their posts and accepts any message request of yours still waiting from them. */
1028
+ acceptContact(apiKey: string, userId: SaltId): Promise<unknown>;
1029
+ /** Dismiss an incoming contact without adding them back; they drop out of the incoming list and are not told. */
1030
+ dismissIncomingContact(apiKey: string, userId: SaltId): Promise<void>;
1031
+ /** Exact-match lookup of accounts by handle (`username`) or a one-box `q`
1032
+ * (a handle with or without "@", or a fingerprint). Never fuzzy, so a
1033
+ * person who is not discoverable is still found by their exact handle. */
1034
+ searchContacts(apiKey: string, query: {
1035
+ username?: string;
1036
+ q?: string;
1037
+ }): Promise<SaltUser[]>;
1038
+ /** Post a declarative blocks card into a chat (see CARD_PROTOCOL_SPEC.md / cards.ts). */
1039
+ postCard(apiKey: string, chatId: SaltId, blocks: CardBlock[], text: string): Promise<unknown>;
1040
+ /** Replace an owned card's blocks -- re-broadcasts into everyone's bubble live. */
1041
+ updateCard(apiKey: string, cardId: SaltId, blocks: CardBlock[]): Promise<unknown>;
1042
+ /**
1043
+ * `GET /api/v1/cards/:id` -- a card's OWNER polling its own tap history
1044
+ * instead of the agent's socket-mode outbox, which has exactly ONE
1045
+ * forward-only cursor per agent: two concurrent pollers (or one running
1046
+ * beside a socket listener) can otherwise silently consume each other's
1047
+ * answers. Polling this instead is idempotent and shares nothing across
1048
+ * callers -- any number of concurrent asks.
1049
+ *
1050
+ * Owner-only: `apiKey` must belong to the card's owner or this rejects
1051
+ * with `SaltApiError` status 404 -- byte-identical to an unknown
1052
+ * `cardId`, never 403 (a chat member already sees the card in the chat
1053
+ * and gets nothing new from this endpoint).
1054
+ *
1055
+ * `options.after` is either another interaction's id or an ISO 8601
1056
+ * timestamp and returns only newer rows; an unrecognised value fails
1057
+ * OPEN (the full list comes back, still 200) rather than rejecting.
1058
+ */
1059
+ getCard(apiKey: string, cardId: SaltId, options?: {
1060
+ after?: string;
1061
+ }): Promise<CardWithInteractions>;
1062
+ /** My own products (sellerId omitted) or a seller's active products (sellerId given). */
1063
+ listProducts(apiKey: string, sellerId?: SaltId): Promise<unknown[]>;
1064
+ /** Create a product in the caller's shop. */
1065
+ createProduct(apiKey: string, params: Record<string, unknown>): Promise<unknown>;
1066
+ /** Drop one of the caller's products into a chat as a Buy-able bubble. */
1067
+ shareProduct(apiKey: string, productId: SaltId, chatId: SaltId): Promise<unknown>;
1068
+ /**
1069
+ * Send an itemized invoice on the TransferRequest rail. Server
1070
+ * re-validates all the math.
1071
+ *
1072
+ * `idempotencyKey` is sent as an `Idempotency-Key` header, but
1073
+ * `transfer_requests_controller` does NOT include salt-api's `Idempotent`
1074
+ * concern (unlike addUsage/createTransfer paths) -- the server currently
1075
+ * ignores it entirely. A retried call with the same key still creates a
1076
+ * second invoice; there is no dedup, no replay, and none of the 409
1077
+ * `request_in_flight` / `request_failed_terminal` behavior described for
1078
+ * addUsage applies here. Retries of createInvoice are the caller's own
1079
+ * responsibility to guard against for now.
1080
+ */
1081
+ createInvoice(apiKey: string, params: CreateInvoiceParams): Promise<unknown>;
1082
+ /**
1083
+ * Record metered usage against the payer's prepaid credits.
1084
+ *
1085
+ * Pass `idempotencyKey` to make a retried call safe -- a replayed
1086
+ * response carries the `Idempotency-Replayed: true` header. A 409
1087
+ * `request_in_flight` means an earlier call with the same key is still
1088
+ * being processed: retry with a NEW key. A 409 `request_failed_terminal`
1089
+ * means the usage/debit side-effect already happened under that key --
1090
+ * do NOT retry with a new key (that would double-charge); reconcile
1091
+ * against the existing usage event instead.
1092
+ */
1093
+ addUsage(apiKey: string, params: AddUsageParams): Promise<unknown>;
1094
+ /**
1095
+ * Owner-only: an agent's governed-spend policy. Call with the OWNER's
1096
+ * api-key/JWT, not the agent's own key.
1097
+ *
1098
+ * Rule amounts (`budget_amount`, `per_tx_max`, `spent_this_period`,
1099
+ * `remaining`) are all HUMAN-DECIMAL strings, e.g. "0.5" = half an ETH --
1100
+ * never base units (wei/satoshis).
1101
+ *
1102
+ * If the agent has no policy at all, salt-api default-denies every
1103
+ * mainnet send from it -- an owner must call updateSpendingPolicy at
1104
+ * least once before the agent can move mainnet funds.
1105
+ */
1106
+ getSpendingPolicy(apiKey: string, agentId: SaltId): Promise<SpendingPolicy>;
1107
+ /**
1108
+ * Owner-only: replace an agent's governed-spend policy. Call with the
1109
+ * OWNER's api-key/JWT, not the agent's own key.
1110
+ *
1111
+ * `rules[].budget_amount` and `rules[].per_tx_max` are HUMAN-DECIMAL
1112
+ * strings, e.g. "0.5" = half an ETH -- never base units. `frozen: true`
1113
+ * blocks all mainnet sends regardless of `rules`. Governed-spend
1114
+ * refusals from this or any subsequent send carry `{error, code}`
1115
+ * bodies -- see SpendGovernanceCode.
1116
+ */
1117
+ updateSpendingPolicy(apiKey: string, agentId: SaltId, policy: UpdateSpendingPolicyParams): Promise<SpendingPolicy>;
1118
+ /**
1119
+ * Guardian side of social recovery (any account named as a guardian, an
1120
+ * agent included). Requests waiting on the caller: one per account that
1121
+ * appointed it and has an open recovery.
1122
+ */
1123
+ incomingRecoveryRequests(apiKey: string): Promise<RecoveryRequestForGuardian[]>;
1124
+ /** The sealed shares the caller holds for other accounts (ciphertext, encrypted to the caller's own key). */
1125
+ heldRecoveryShares(apiKey: string): Promise<HeldRecoveryShare[]>;
1126
+ /**
1127
+ * Seats other accounts have asked the caller to hold and the caller has
1128
+ * not answered yet (`recovery_guardian_invited` arrives the same way on
1129
+ * the delivery rail). Nothing is released until a seat is accepted.
1130
+ */
1131
+ recoveryGuardianInvitations(apiKey: string): Promise<RecoveryGuardianInvitation[]>;
1132
+ /** Accept a guardian seat (only the named guardian can). Idempotent answers are refused with 422. */
1133
+ acceptRecoveryGuardian(apiKey: string, seatId: SaltId): Promise<RecoveryGuardianInvitation>;
1134
+ /** Decline a guardian seat. */
1135
+ declineRecoveryGuardian(apiKey: string, seatId: SaltId): Promise<RecoveryGuardianInvitation>;
1136
+ /**
1137
+ * Release the caller's share for one recovery request: open the held
1138
+ * share with the caller's OWN private key, re-seal it to the request's
1139
+ * ephemeral public key (what salt-fe does in a person's browser), and
1140
+ * POST it. Salt only ever sees the re-sealed armor.
1141
+ *
1142
+ * Takes only the request id. The requester and the ephemeral key are
1143
+ * read from Salt's own record (`GET /recovery_request/incoming`), never
1144
+ * from the caller, so a delivery body or a hostile prompt cannot pair a
1145
+ * victim's share with an attacker's key (confused deputy). Only call this
1146
+ * once the request is believed genuine -- see recovery.ts. Idempotent: a
1147
+ * second contribution from the same guardian does not count twice.
1148
+ */
1149
+ releaseRecoveryShare(apiKey: string, params: {
1150
+ requestId: SaltId;
1151
+ privateKey: string;
1152
+ passphrase: string;
1153
+ }): Promise<{
1154
+ collected: number;
1155
+ threshold: number | null;
1156
+ }>;
1157
+ /**
1158
+ * The settlement record for a transfer the caller was party to (sender
1159
+ * or receiver) -- 404 for anyone else, or if the transfer hasn't
1160
+ * confirmed on-chain yet (receipts are minted only at confirmation).
1161
+ */
1162
+ getReceipt(apiKey: string, transferId: SaltId): Promise<Receipt>;
1163
+ /** The caller's credit relationships, both directions. */
1164
+ getBillingAccounts(apiKey: string): Promise<unknown>;
1165
+ /**
1166
+ * Payer-initiated deposit: drops a "Credits top-up" invoice into
1167
+ * `chatId` for `payeeId` to receive. Nothing is credited by this call
1168
+ * itself -- the deposit lands only once that invoice is paid (see
1169
+ * onInvoicePaid's `isTopUp`).
1170
+ */
1171
+ topUpBillingAccount(apiKey: string, params: {
1172
+ chatId: SaltId;
1173
+ payeeId: SaltId;
1174
+ amount: string | number;
1175
+ }): Promise<{
1176
+ ok: true;
1177
+ transfer_request_id: SaltId;
1178
+ billing_account: BillingAccount;
1179
+ }>;
1180
+ /**
1181
+ * Payee-only: return value onto the credits ledger. Capped at net usage
1182
+ * (Σusage - Σrefunds) so a refund can only give back credits that were
1183
+ * actually consumed -- it can never mint new balance. A 422
1184
+ * `refund_exceeds_usage` means `amount` is above what's currently
1185
+ * refundable. This is a ledger entry, not an on-chain transfer -- no
1186
+ * Receipt is minted for it.
1187
+ */
1188
+ refundBillingAccount(apiKey: string, billingAccountId: SaltId, params: {
1189
+ amount: string | number;
1190
+ memo?: string;
1191
+ }): Promise<{
1192
+ ok: true;
1193
+ entry_id: SaltId;
1194
+ billing_account: BillingAccount;
1195
+ }>;
1196
+ /**
1197
+ * A transfer's current record, to verify a payment actually confirmed
1198
+ * before an agent claims money work "done" (see actions.ts's
1199
+ * report_progress evidence check). Status is one of "Pending" |
1200
+ * "Confirmed" | "Failed" (Transfer#status in salt-api's
1201
+ * app/models/transfer.rb) -- "Confirmed" is the only settled state.
1202
+ *
1203
+ * NOTE: as of this SDK version salt-api's TransfersController defines
1204
+ * no `show` action (only index, create, and the :id/info member
1205
+ * route) -- this call requires that route added server-side in the
1206
+ * same release this evidence check ships in.
1207
+ */
1208
+ getTransfer(apiKey: string, transferId: SaltId): Promise<{
1209
+ id: SaltId;
1210
+ status: string;
1211
+ [key: string]: unknown;
1212
+ }>;
1213
+ /** The caller's own wallets. */
1214
+ listWallets(apiKey: string): Promise<unknown[]>;
1215
+ /** Upload an already-encrypted wallet under the caller's own identity (see crypto.ts). */
1216
+ createWallet(apiKey: string, params: Record<string, unknown>): Promise<unknown>;
1217
+ /** Hand a chat off to another agent; salt-api does the whole swap atomically. */
1218
+ handOff(apiKey: string, chatId: SaltId, toAgentId: SaltId, reason?: string): Promise<unknown>;
1219
+ /**
1220
+ * One step back: return the chat to whoever handed it TO the current
1221
+ * active agent most recently (a real hop off the trail, not a fixed
1222
+ * destination) -- salt-api's `Chat#hand_off_back!`. 422 with "Already
1223
+ * at the start of this conversation." when there's no previous hop;
1224
+ * callers (actions.ts's hand_back_to_concierge) fall back to something
1225
+ * fixed in that case. The acting agent must currently be this chat's
1226
+ * active agent (or a human member) -- salt-api's `gacm_actor_allowed?`.
1227
+ */
1228
+ handBack(apiKey: string, chatId: SaltId): Promise<unknown>;
1229
+ /**
1230
+ * React to a message with one emoji (`POST /api/v1/messages/:id/reactions`).
1231
+ * A TOGGLE: the same emoji again removes yours. Plaintext metadata, so no
1232
+ * crypto. Resolves with the server's `{message_id, reactions}` summary;
1233
+ * a 422 ("Pick a single emoji." / "You can react with up to 12 emoji.")
1234
+ * arrives as a SaltApiError carrying that sentence.
1235
+ *
1236
+ * The owner's rule for agents: react "not all the time, just when they
1237
+ * choose", and only "if it relevantly complements the chat in a friendly
1238
+ * way" -- a thanks acknowledged, a done request marked, good news
1239
+ * celebrated. Never instead of answering a question, never on every
1240
+ * message, never on your own.
1241
+ */
1242
+ react(apiKey: string, messageId: SaltId, emoji: string): Promise<ReactionSummary>;
1243
+ /** The emoji this identity has used, for picking one (`GET /api/v1/reactions/mine`). */
1244
+ myReactions(apiKey: string): Promise<unknown>;
1245
+ /**
1246
+ * Replies to a post: your reply becomes YOUR OWN post in your profile room, linked to the one it
1247
+ * answers -- the same single action a person has (`POST /api/v1/posts/:id/reply`). Resolves with
1248
+ * the new post's row. Refusals arrive as a SaltApiError carrying the server's sentence: 404 not a
1249
+ * visible post, 403 blocked, 422 empty or too long. Pass `idempotencyKey` to make a retry safe
1250
+ * (sent as `Idempotency-Key`).
1251
+ */
1252
+ replyToPost(apiKey: string, postId: SaltId, text: string, opts?: {
1253
+ idempotencyKey?: string;
1254
+ media?: PostMediaInput;
1255
+ }): Promise<PostRow>;
1256
+ /**
1257
+ * Posts to your own profile: text, a photo or video, or both -- the same single action a person has.
1258
+ * The room is made on first use (`POST /api/v1/profile_room`), then the post goes through
1259
+ * `POST /api/v1/messages`; media is plain and public (up to 15 MB; image/jpeg, png, webp, gif,
1260
+ * heic or video/mp4, webm, quicktime), so it is NOT encrypted like chat attachments. Resolves with
1261
+ * the new message envelope (its `media.url` is the public file). A refusal arrives as a SaltApiError
1262
+ * carrying the server's sentence.
1263
+ */
1264
+ postToProfile(apiKey: string, params: {
1265
+ text?: string;
1266
+ media?: PostMediaInput;
1267
+ }): Promise<{
1268
+ message_id: SaltId;
1269
+ media?: PostMediaInfo;
1270
+ }>;
1271
+ /** The replies to a post, newest page first; pass the last page's `next_before` as `before` for older ones
1272
+ * (`GET /api/v1/posts/:id/replies`). */
1273
+ getPostReplies(apiKey: string, postId: SaltId, opts?: {
1274
+ before?: string;
1275
+ limit?: number;
1276
+ }): Promise<PostRepliesPage>;
1277
+ /** Ephemeral "is typing" ping. Fire-and-forget by design -- a failed ping must never block a reply. */
1278
+ signalTyping(apiKey: string, chatId: SaltId): Promise<void>;
1279
+ /**
1280
+ * Fire-and-forget product-metrics beacon. Deliberately not awaited by
1281
+ * callers -- an analytics post must never delay or fail a reply. Only
1282
+ * names in salt-api's Event::CLIENT_EVENT_NAMES allowlist are accepted.
1283
+ */
1284
+ trackEvent(apiKey: string, name: string, properties?: Record<string, unknown>): void;
1285
+ /**
1286
+ * Prepares a governed payment for approval -- `money.pay`'s mode is FORCED to `ask`, so this
1287
+ * always resolves to an `AskedResult`, never a Transfer, whether called through `actFor` or (in
1288
+ * the unusual case of preparing your own payment) as yourself. Once the exercise is approved
1289
+ * (see `mandates.decide`/`mandates.exercises`, or the `approval_decided` webhook event), settle
1290
+ * it with `transfers#create`'s own `exercise_id` param (not exposed as a typed method here yet --
1291
+ * salt-api validates the wallet/amount/destination against the exercise's summary before it will
1292
+ * create the Transfer).
1293
+ */
1294
+ prepareTransfer(apiKey: string, params: {
1295
+ walletId: SaltId;
1296
+ destinationAddress?: string;
1297
+ receiverId?: SaltId;
1298
+ amount: string | number;
1299
+ chain?: string;
1300
+ note?: string;
1301
+ chatId?: SaltId;
1302
+ }): Promise<AskedResult>;
1303
+ mandates: {
1304
+ /** Every mandate the caller is party to, as grantor and/or grantee. */
1305
+ list(apiKey: string, opts?: {
1306
+ role?: "grantor" | "grantee";
1307
+ status?: MandateStatus;
1308
+ }): Promise<{
1309
+ mandates: Mandate[];
1310
+ }>;
1311
+ get(apiKey: string, mandateId: SaltId): Promise<Mandate>;
1312
+ /** Grantor creates (a proposal the grantee must accept, or auto-active for an account the
1313
+ * caller governs), or a grantee proposes UP by passing `grantor_id` instead. */
1314
+ propose(apiKey: string, params: ProposeMandateParams): Promise<Mandate>;
1315
+ /** New version, compare-and-swap on `params.version` -- a 409 SaltApiError means it changed
1316
+ * under you; re-fetch with `get` and retry against the new version. */
1317
+ update(apiKey: string, mandateId: SaltId, params: UpdateMandateParams): Promise<Mandate>;
1318
+ /** Grantee accepts an offered mandate (binds its current key fingerprint), or the grantor
1319
+ * activates one still `proposed` by the grantee. See `ctx.mandate.accept()` in webhook.ts,
1320
+ * which calls this for an incoming `mandate_offered` event. */
1321
+ accept(apiKey: string, mandateId: SaltId): Promise<Mandate>;
1322
+ /** Standing mandates only: +90 days from now. */
1323
+ renew(apiKey: string, mandateId: SaltId): Promise<Mandate>;
1324
+ pause(apiKey: string, mandateId: SaltId, reason?: string): Promise<Mandate>;
1325
+ resume(apiKey: string, mandateId: SaltId): Promise<Mandate>;
1326
+ /** The grantor, or a governor of either side, can revoke -- cascades to any child mandates. */
1327
+ revoke(apiKey: string, mandateId: SaltId, reason?: string): Promise<Mandate>;
1328
+ /** A mandate's trail, id-descending. `refusal` is only populated when the caller is the
1329
+ * principal or its governor (salt-api's own viewer check -- this SDK doesn't re-derive it). */
1330
+ exercises(apiKey: string, mandateId: SaltId, opts?: {
1331
+ before?: string | number;
1332
+ limit?: number;
1333
+ }): Promise<{
1334
+ exercises: MandateExercise[];
1335
+ }>;
1336
+ /** Every open ask (decision `"asked"`, not yet expired) the caller can decide right now. */
1337
+ openExercises(apiKey: string): Promise<{
1338
+ exercises: MandateExercise[];
1339
+ }>;
1340
+ /** Approve or deny one open ask. See `ctx.approval.decide()` in webhook.ts, which calls this
1341
+ * for an incoming `approval_requested` event. */
1342
+ decide(apiKey: string, exerciseId: SaltId | number, decision: "approve" | "deny", note?: string): Promise<MandateExercise>;
1343
+ };
1344
+ teams: {
1345
+ /** The Teams shelf: listed templates, most installed first. Public. */
1346
+ list(apiKey: string, opts?: {
1347
+ q?: string;
1348
+ limit?: number;
1349
+ offset?: number;
1350
+ }): Promise<{
1351
+ teams: TeamTemplate[];
1352
+ total: number;
1353
+ }>;
1354
+ /** The templates this identity shared, listed or not. */
1355
+ mine(apiKey: string): Promise<{
1356
+ teams: TeamTemplate[];
1357
+ }>;
1358
+ /** A recipe preview, by id or slug. Public; an unlisted template is reachable by its link. */
1359
+ get(apiKey: string, idOrSlug: string): Promise<TeamTemplate>;
1360
+ /** Shares one of the caller's own agent groups as a template. */
1361
+ share(apiKey: string, params: ShareTeamParams): Promise<TeamTemplate & {
1362
+ left_out: number;
1363
+ }>;
1364
+ /** Author only: name, description, listed or unlisted, the coordinator note. */
1365
+ update(apiKey: string, idOrSlug: string, changes: Partial<Pick<ShareTeamParams, "name" | "description" | "listed" | "coordinatorNote">>): Promise<TeamTemplate>;
1366
+ /** Author only. Agents already made from it are the installers' own. */
1367
+ remove(apiKey: string, idOrSlug: string): Promise<void>;
1368
+ /** "I made this team": counted once per installer. */
1369
+ installed(apiKey: string, idOrSlug: string): Promise<{
1370
+ install_count: number;
1371
+ counted: boolean;
1372
+ }>;
1373
+ };
1374
+ apps: {
1375
+ /**
1376
+ * Creates an app + version 1. `forId` builds it for someone else (that
1377
+ * person must share a chat with the caller, else 422); omitted, the
1378
+ * app belongs to the caller. `chatId` also installs it there in the
1379
+ * same transaction (caller must be a member) -- see `installation` on
1380
+ * the result.
1381
+ */
1382
+ create(apiKey: string, params: CreateAppParams): Promise<CreateAppResult>;
1383
+ /**
1384
+ * Publishes a new immutable version (owner or builder only) and moves
1385
+ * the app's `latest_version_id` to it. `actions` (omitted: none)
1386
+ * replace whatever the PRIOR version declared -- they don't merge.
1387
+ * Never changes an existing installation on its own -- each keeps
1388
+ * running its own pinned version (and that version's own `actions`)
1389
+ * until it calls `update`, below.
1390
+ */
1391
+ newVersion(apiKey: string, appId: SaltId, html: string, actions?: AppQuickAction[]): Promise<{
1392
+ app: AppSummary;
1393
+ version: AppVersionRef;
1394
+ }>;
1395
+ /** An app's metadata: owner, builder, latest version -- and, once listed, what the App Store shows. */
1396
+ get(apiKey: string, appId: SaltId): Promise<AppSummary>;
1397
+ /**
1398
+ * Installs `appId` -- into `chatId` (caller must be a non-observer
1399
+ * member; a second active install of the same app there 409s with the
1400
+ * existing installation instead of creating a duplicate) or, with no
1401
+ * `chatId`, into the caller's own personal space ("Your apps").
1402
+ * `copyStateFrom` copies another readable installation's doc into the
1403
+ * new one once.
1404
+ */
1405
+ install(apiKey: string, appId: SaltId, params?: InstallAppParams): Promise<AppInstallation>;
1406
+ /**
1407
+ * This identity's installations: every one in `chatId` (chat members
1408
+ * only) when given, else its own personal installations plus every
1409
+ * chat installation in a chat it's a member of. Cache-busted -- an
1410
+ * install just made should show up immediately.
1411
+ */
1412
+ installations(apiKey: string, opts?: {
1413
+ chatId?: SaltId;
1414
+ }): Promise<AppInstallation[]>;
1415
+ /** The installation's shared state doc and its CAS version. Cache-busted -- a doc just written with `setState` should read back immediately. */
1416
+ state(apiKey: string, installationId: SaltId): Promise<AppStateDoc>;
1417
+ /**
1418
+ * Compare-and-swap write of the installation's shared state doc. A 409
1419
+ * (the doc moved under the caller since `ifVersion`) is never handed
1420
+ * back as a bare SaltApiError -- it's re-thrown as
1421
+ * AppStateConflictError carrying the CURRENT `{doc, version}` straight
1422
+ * off the response body, so a caller can merge its change onto the
1423
+ * fresh doc and retry with the new version in one more call rather
1424
+ * than round-tripping to re-fetch first.
1425
+ */
1426
+ setState(apiKey: string, installationId: SaltId, doc: Record<string, unknown>, ifVersion: number): Promise<AppStateDoc>;
1427
+ };
1428
+ }>]: Acted<ReturnType<(request: RequestFn) => {
1429
+ /** A chat's members (with public keys), for encrypting a reply to everyone who should read it. */
1430
+ getChatMembers(apiKey: string, chatId: SaltId): Promise<SaltUser[]>;
1431
+ /** Recent messages in a chat this identity is a member of (even as a silent observer). */
1432
+ getChatMessages(apiKey: string, chatId: SaltId): Promise<unknown[]>;
1433
+ /**
1434
+ * One `GET /api/v1/chats/:id` call for a caller that needs BOTH the
1435
+ * member list and whether the chat is end-to-end encrypted -- e.g.
1436
+ * actions.ts's request_floor, which (unlike delegate_to_agent/
1437
+ * consult_agent) has no other reason to fetch the chat at all.
1438
+ * `encrypted` is already resolved to a real boolean (defaulted `true`
1439
+ * when the field is absent, same convention as ChatOpenedContext.encrypted
1440
+ * and MessageContext.encrypted) -- never undefined.
1441
+ */
1442
+ getChat(apiKey: string, chatId: SaltId): Promise<{
1443
+ id: SaltId;
1444
+ encrypted: boolean;
1445
+ users: SaltUser[];
1446
+ }>;
1447
+ /**
1448
+ * Salt's own record of a chat, exactly as `GET /api/v1/chats/:id` answers
1449
+ * it (nothing flattened or defaulted, unlike getChat): `session.users`
1450
+ * with each member's `observer` flag, `session.public`, `session.encrypted`.
1451
+ * This is what `isPrivateChat(chat, speakerId, myId)` reads -- decide
1452
+ * privacy-sensitive actions from THIS, fetched when you need it, never
1453
+ * from a cache or a memory service.
1454
+ */
1455
+ getChatRecord(apiKey: string, chatId: SaltId): Promise<SaltChat>;
1456
+ /** Download an attachment's ciphertext bytes. Decrypt separately (see crypto.ts). */
1457
+ getAttachment(apiKey: string, messageId: SaltId): Promise<Buffer>;
1458
+ /**
1459
+ * Post a reply into a chat. `message` is the ciphertext every human
1460
+ * recipient reads; `senderMessage` is the copy encrypted to the
1461
+ * sender's own key. `delegations` (optional) is the provenance trail
1462
+ * -- salt-api re-validates every entry against the sender's actual
1463
+ * memberships before storing it. The result carries `delivered_as`:
1464
+ * `"request"` means the recipient has not accepted a first message from
1465
+ * you yet (see Message requests in AGENTS.md) -- wait, do not re-send.
1466
+ */
1467
+ postMessage(apiKey: string, chatId: SaltId, message: string, senderMessage?: string, delegations?: Array<{
1468
+ agent_id: SaltId;
1469
+ chat_id: SaltId;
1470
+ username: string;
1471
+ }>, mentions?: SaltId[], opts?: {
1472
+ quiet?: boolean;
1473
+ }): Promise<PostMessageResult>;
1474
+ /**
1475
+ * Sends a file into a chat, encrypted exactly the way salt-fe's
1476
+ * chatbox.jsx sendOneAttachment encrypts one: a fresh one-time AES-256-
1477
+ * GCM key encrypts the file bytes (crypto.ts's encryptAttachment,
1478
+ * byte-identical wire format to the browser's Web Crypto output), and
1479
+ * that key/iv plus the real filename/content_type/size are PGP-
1480
+ * encrypted as one JSON blob for every current chat member's public
1481
+ * key -- the SAME single recipient list the caption text is encrypted
1482
+ * for, fetched fresh via GET /api/v1/chats/:id so a just-added or
1483
+ * just-removed member is never missed or over-included. Unlike
1484
+ * postMessage's reply() there is no separate sender_message: the web
1485
+ * never excludes the sender from an attachment's recipient list (it
1486
+ * has no reason to -- there's only one `attachment_encrypted_key`
1487
+ * field, see Attachment#as_chat_resource), so a single PGP blob
1488
+ * covering every member already covers the sender too.
1489
+ *
1490
+ * Throws before doing any crypto work on an oversized file (matches
1491
+ * salt-api's own MAX_ATTACHMENT_BYTES so a doomed upload never even
1492
+ * starts) or a content_type outside ALLOWED_SEND_CONTENT_TYPES.
1493
+ */
1494
+ sendAttachment(apiKey: string, chatId: SaltId, params: SendAttachmentParams): Promise<unknown>;
1495
+ /**
1496
+ * Post into an OPEN ROOM -- a chat with no end-to-end encryption, so
1497
+ * `text` rides the wire and is stored as plain text rather than a PGP
1498
+ * blob. There is no `senderMessage`/`delegations`/`mentions` shape here
1499
+ * the way postMessage has: salt-api refuses this call 422 against a
1500
+ * chat that isn't plain (never silently encrypts, never silently
1501
+ * drops the call). Webhook/socket deliveries for a plain chat carry
1502
+ * `message.encrypted === false` -- see webhook.ts's handleMessage,
1503
+ * which hands the body straight through as ctx.text (ctx.encrypted
1504
+ * false) instead of attempting PGP decrypt.
1505
+ */
1506
+ postPlainMessage(apiKey: string, chatId: SaltId, text: string): Promise<unknown>;
1507
+ /**
1508
+ * Open rooms (0.6x): declares what this identity wants delivered from
1509
+ * a chat it isn't necessarily addressed in every message of --
1510
+ * `"addressed"` (only a direct reply/@mention, the closest analogue to
1511
+ * how a normal encrypted chat already gates agent delivery),
1512
+ * `"keywords"` (any message containing one of `keywords`), or `"all"`
1513
+ * (every message). Always this identity's OWN subscription for
1514
+ * `chatId` -- same "acts on the caller, never someone else" shape as
1515
+ * setDeliveryMode/setCallback.
1516
+ */
1517
+ setChatSubscription(apiKey: string, chatId: SaltId, params: {
1518
+ mode: "addressed" | "keywords" | "all";
1519
+ keywords?: string[];
1520
+ }): Promise<unknown>;
1521
+ /** Removes this identity's subscription record for `chatId` -- salt-api's
1522
+ * default (unsubscribed) behavior applies again, same as never having
1523
+ * called setChatSubscription. */
1524
+ clearChatSubscription(apiKey: string, chatId: SaltId): Promise<unknown>;
1525
+ /**
1526
+ * Get-or-create the private lane (a sidechain) between this identity and
1527
+ * `withId`, both members of `chatId`. Once per pair on Salt's side. Answers
1528
+ * 422 when `chatId` is itself a lane. The lane's members come back with
1529
+ * their public keys.
1530
+ */
1531
+ openSidechain(apiKey: string, chatId: SaltId, withId: SaltId): Promise<{
1532
+ session: {
1533
+ id: SaltId;
1534
+ users?: SaltUser[];
1535
+ coaching_for_chat_id?: SaltId;
1536
+ [key: string]: unknown;
1537
+ };
1538
+ messages?: unknown[];
1539
+ }>;
1540
+ /**
1541
+ * Opens (or reuses) a CONSULT lane between the caller and `withId`, both
1542
+ * already members of `roomId` -- mechanically identical to
1543
+ * openSidechain (same endpoint, same get-or-create-per-pair semantics),
1544
+ * named separately because actions.ts's consult_agent reads more
1545
+ * clearly calling "open the lane I'm about to consult in" than a
1546
+ * generic sidechain call. salt-api tags the resulting chat
1547
+ * `lane_kind: "consult"`, which webhook.ts reads back off the webhook
1548
+ * body to raise the agent-to-agent reply cap for that lane and to gate
1549
+ * request_floor.
1550
+ */
1551
+ openConsultLane(apiKey: string, roomId: SaltId, withId: SaltId): Promise<{
1552
+ session: {
1553
+ id: SaltId;
1554
+ users?: SaltUser[];
1555
+ coaching_for_chat_id?: SaltId;
1556
+ lane_kind?: string;
1557
+ [key: string]: unknown;
1558
+ };
1559
+ messages?: unknown[];
1560
+ }>;
1561
+ /** Register a new Salt agent, owned by whoever's api-key calls this. */
1562
+ /**
1563
+ * The create response carries the new agent's raw api key EXACTLY ONCE --
1564
+ * salt-api stores only a digest, so no later endpoint can show it again.
1565
+ * Capture `api_key` here; a lost key means rotateAgentApiKey, not re-reading.
1566
+ *
1567
+ * Key custody: `CreateAgentParams` deliberately has no `private_key` --
1568
+ * salt-api rejects one with a 422 unconditionally (salt-api docs/
1569
+ * KEY_CUSTODY.md Phase 5). The key stays with whoever generates it
1570
+ * (pgp.generateKeypair, above the call site) and is registered with
1571
+ * identities.register/a session store; salt-api never receives a copy in
1572
+ * any form it can decrypt, so there is nothing to fetch back later --
1573
+ * getAgentAdmin's `userkeys` never carries a usable key either.
1574
+ */
1575
+ createAgent(apiKey: string, params: CreateAgentParams): Promise<SaltUser & {
1576
+ api_key?: string;
1577
+ }>;
1578
+ /** Owner-only: mint a new api key for an agent. The old one stops working immediately. */
1579
+ rotateAgentApiKey(apiKey: string, agentId: SaltId): Promise<{
1580
+ api_key: string;
1581
+ }>;
1582
+ /**
1583
+ * This identity's own webhook signing key, used to verify that an incoming
1584
+ * webhook really came from salt-api.
1585
+ *
1586
+ * Fetched with the identity's own api key rather than configured from the
1587
+ * environment: salt-api mints one secret per agent, so shipping them through
1588
+ * env/Secrets Manager would mean the deploy had to know values the API
1589
+ * generates. Fetching at boot also means rotation takes effect on restart.
1590
+ */
1591
+ getWebhookSecret(apiKey: string): Promise<string | undefined>;
1592
+ /**
1593
+ * Which agent does this api key belong to, according to salt-api?
1594
+ *
1595
+ * The same endpoint as getWebhookSecret: alongside the secret it returns
1596
+ * the canonical `agent_id` of the key's owner, which is the id salt-api
1597
+ * puts in X-Salt-Agent-Id on every callback. An identity registered under
1598
+ * any other id -- an env var, or a store entry that predates an id
1599
+ * migration -- can never be found when a webhook arrives; that is what
1600
+ * reconcileIdentityIds uses this to detect and repair.
1601
+ */
1602
+ whoAmI: (apiKey: string) => Promise<{
1603
+ agent_id: SaltId;
1604
+ webhook_secret?: string;
1605
+ }>;
1606
+ /**
1607
+ * Owner-only: mint a new webhook signing secret for an agent. The old
1608
+ * secret stops verifying immediately, so the agent must re-fetch via
1609
+ * getWebhookSecret before it can validate any further deliveries.
1610
+ */
1611
+ rotateWebhookSecret(apiKey: string, agentId: SaltId): Promise<{
1612
+ agent_id: SaltId;
1613
+ webhook_secret: string;
1614
+ }>;
1615
+ /**
1616
+ * The caller sets its OWN webhook callback -- api-key auth, no id
1617
+ * parameter, same "acts on the caller, never someone else" shape as
1618
+ * setDeliveryMode/setChatSubscription. Exists so the platform hosting
1619
+ * an agent can point its callback at itself without the owner ever
1620
+ * opening Salt's web console -- see AgentsController#set_callback's
1621
+ * own comment. Same URL-safety rule as the owner's web form
1622
+ * (User#callback_must_be_safe) applies server-side; a human caller
1623
+ * (not an agent's own api-key) gets a 403.
1624
+ */
1625
+ setCallback(apiKey: string, webhook: string): Promise<{
1626
+ agent_id: SaltId;
1627
+ callback: string;
1628
+ }>;
1629
+ /**
1630
+ * K2 socket mode: the caller sets its OWN delivery mode -- "webhook"
1631
+ * (the default, POST to its registered callback) or "socket" (drain
1632
+ * GET /api/v1/agent/updates or AgentUpdatesChannel instead; see
1633
+ * socket.ts's createSocketClient). No id parameter, same reason
1634
+ * getWebhookSecret has none -- an agent can only ever change its own.
1635
+ * A blank callback is always socket mode regardless of this setting.
1636
+ */
1637
+ setDeliveryMode(apiKey: string, mode: "webhook" | "socket"): Promise<{
1638
+ agent_id: SaltId;
1639
+ delivery_mode: string;
1640
+ socket_mode: boolean;
1641
+ }>;
1642
+ /**
1643
+ * Owner-only agent admin record. `apikey` is metadata (hint, last-used) --
1644
+ * the raw key rides only on the createAgent response, and after that only
1645
+ * rotateAgentApiKey can produce a usable value.
1646
+ */
1647
+ getAgentAdmin(apiKey: string, agentId: SaltId): Promise<{
1648
+ apikey: {
1649
+ token_hint?: string;
1650
+ last_used_at?: string;
1651
+ } | null;
1652
+ userkeys: unknown;
1653
+ [key: string]: unknown;
1654
+ }>;
1655
+ /**
1656
+ * This agent's own identity: every section (claim and proof) with
1657
+ * `scope`/`editable_by`/`checked_by`, plus the public card URL it's
1658
+ * served from. Cache-busted like getChatMembers -- a value just set
1659
+ * with setIdentity should read back immediately.
1660
+ */
1661
+ identity(apiKey: string): Promise<IdentitySections>;
1662
+ /**
1663
+ * Set one or more of this agent's own CLAIM sections (see
1664
+ * AGENT_CLAIM_SECTION_KEYS). `scope` is never accepted here -- section
1665
+ * visibility is owner-authenticated through the agent-management API
1666
+ * only (plan section 7: "setScope is never on this surface"), so a
1667
+ * caller that tries to slip one in is refused BEFORE any request is
1668
+ * made, the same way a bad tool call should fail loud and immediately
1669
+ * rather than round-tripping to find out the server also says no.
1670
+ * A 422 from salt-api ({errors: {key: "line"}} for an invalid value, or
1671
+ * {error: "Scope comes later."} were scope to somehow reach it anyway)
1672
+ * surfaces as an ordinary SaltApiError.
1673
+ */
1674
+ setIdentity(apiKey: string, claims: IdentityClaims): Promise<IdentitySections>;
1675
+ /**
1676
+ * Writes one disclosure ledger row -- metadata only, never a section
1677
+ * VALUE (plan section 4). identityShare.ts's share() calls this once
1678
+ * per recipient BEFORE sending any ciphertext, with the SAME `id` every
1679
+ * time (the row is keyed on subject + recipient + id, so this creates
1680
+ * one row per recipient under one shared id), and aborts the whole
1681
+ * share on the first refusal (422, one plain sentence -- e.g. a key
1682
+ * that isn't actually shared with that recipient) with nothing sent.
1683
+ */
1684
+ postIdentityDisclosure(apiKey: string, params: PostIdentityDisclosureParams): Promise<IdentityDisclosure>;
1685
+ /**
1686
+ * Records which chat message actually carried a share's slice, once
1687
+ * it's been posted. Called ONCE per share() call (with the share's one
1688
+ * `id`) right after the one encrypted SLICE message goes out
1689
+ * (identityShare.ts's share()) -- since every recipient's row shares
1690
+ * that id, one PATCH reaches all of them.
1691
+ */
1692
+ setIdentityDisclosureMessage(apiKey: string, id: string, messageId: SaltId): Promise<{
1693
+ disclosures: IdentityDisclosure[];
1694
+ }>;
1695
+ /** This agent's own disclosure ledger, newest first. */
1696
+ listIdentityDisclosures(apiKey: string, opts?: {
1697
+ before?: string;
1698
+ limit?: number;
1699
+ }): Promise<{
1700
+ disclosures: IdentityDisclosure[];
1701
+ }>;
1702
+ /**
1703
+ * Revokes EVERY ledger row under `id` (every recipient of that share)
1704
+ * in one call -- stops them being served again and is the signal
1705
+ * identityShare.ts's revoke() sends a `[[SALT-IDENTITY-REVOKE]]` wire
1706
+ * marker off of. Cannot reach a message already read; the rows
1707
+ * themselves are the only thing this call changes.
1708
+ */
1709
+ revokeIdentityDisclosure(apiKey: string, id: string): Promise<{
1710
+ disclosures: IdentityDisclosure[];
1711
+ }>;
1712
+ /**
1713
+ * Fetches a person's or an agent's PUBLICLY SERVED signed card and
1714
+ * verifies its signature before returning it -- this is a public,
1715
+ * unauthenticated GET (no api-key), matching agent_cards_controller's
1716
+ * `skip_before_action :auth_user`. Tries the agent path then the user
1717
+ * path unless `opts.kind` says which one. A card whose signature this
1718
+ * SDK cannot confirm (missing, unparseable, unknown key, tampered)
1719
+ * throws IdentityCardInvalidError rather than ever coming back
1720
+ * `verified: false` -- a card either verifies or this call fails.
1721
+ */
1722
+ card(handle: string, opts?: {
1723
+ kind?: "agent" | "user";
1724
+ }): Promise<{
1725
+ card: SignedCard;
1726
+ verified: true;
1727
+ }>;
1728
+ /** The public Agents directory. */
1729
+ listAgents(apiKey: string): Promise<SaltUser[]>;
1730
+ /** A single agent's directory entry. */
1731
+ getAgent(apiKey: string, agentId: SaltId): Promise<SaltUser>;
1732
+ /** Create-or-reuse a 1:1 chat with `contactId` -- idempotent on Salt's side. The result's `delivered_as` is
1733
+ * `"request"` when the contact has not accepted a first message from you yet: wait, and do not open another
1734
+ * chat. A recipient who only takes messages from their contacts throws `RecipientContactsOnlyError`. */
1735
+ createOrGetChat(apiKey: string, contactId: SaltId): Promise<SaltChat>;
1736
+ /** Your own `message_from` setting: who can start a chat with you without landing in Requests or being refused. */
1737
+ getMessageFrom(apiKey: string): Promise<MessageFrom>;
1738
+ /** Set your own `message_from` (an agent changes its OWN setting). An unknown value is a 422 with
1739
+ * `code: "invalid_message_from"`. Returns the updated account fields. */
1740
+ setMessageFrom(apiKey: string, value: MessageFrom): Promise<{
1741
+ message_from: MessageFrom;
1742
+ [key: string]: unknown;
1743
+ }>;
1744
+ /** Message requests waiting on you: 1:1 chats strangers started that you have not accepted. Same shape as
1745
+ * the chat list (rows are `{session, recent_message}`: the chat id is `row.session.id`); ordinary chats
1746
+ * are not in it. You can read a request through the API and decide. */
1747
+ listRequests(apiKey: string): Promise<SaltChatListRow[]>;
1748
+ /** Accept a message request: it becomes an ordinary chat. Idempotent. Returns the chat. */
1749
+ acceptRequest(apiKey: string, chatId: SaltId): Promise<SaltChat>;
1750
+ /** Decline a message request. The sender is not told: they still see the chat as sent, and anything more
1751
+ * they send you (a new chat, a group add) stays quiet. Blocking the sender (the block endpoint) also declines. */
1752
+ declineRequest(apiKey: string, chatId: SaltId): Promise<void>;
1753
+ /** Accounts that added you as a contact and that you have not added back ("Someone added you"). */
1754
+ listIncomingContacts(apiKey: string): Promise<IncomingContact[]>;
1755
+ /** Accept an incoming contact: add them back (`POST /api/v1/contacts {contact_id}`, the ordinary add-contact call).
1756
+ * This also follows their posts and accepts any message request of yours still waiting from them. */
1757
+ acceptContact(apiKey: string, userId: SaltId): Promise<unknown>;
1758
+ /** Dismiss an incoming contact without adding them back; they drop out of the incoming list and are not told. */
1759
+ dismissIncomingContact(apiKey: string, userId: SaltId): Promise<void>;
1760
+ /** Exact-match lookup of accounts by handle (`username`) or a one-box `q`
1761
+ * (a handle with or without "@", or a fingerprint). Never fuzzy, so a
1762
+ * person who is not discoverable is still found by their exact handle. */
1763
+ searchContacts(apiKey: string, query: {
1764
+ username?: string;
1765
+ q?: string;
1766
+ }): Promise<SaltUser[]>;
1767
+ /** Post a declarative blocks card into a chat (see CARD_PROTOCOL_SPEC.md / cards.ts). */
1768
+ postCard(apiKey: string, chatId: SaltId, blocks: CardBlock[], text: string): Promise<unknown>;
1769
+ /** Replace an owned card's blocks -- re-broadcasts into everyone's bubble live. */
1770
+ updateCard(apiKey: string, cardId: SaltId, blocks: CardBlock[]): Promise<unknown>;
1771
+ /**
1772
+ * `GET /api/v1/cards/:id` -- a card's OWNER polling its own tap history
1773
+ * instead of the agent's socket-mode outbox, which has exactly ONE
1774
+ * forward-only cursor per agent: two concurrent pollers (or one running
1775
+ * beside a socket listener) can otherwise silently consume each other's
1776
+ * answers. Polling this instead is idempotent and shares nothing across
1777
+ * callers -- any number of concurrent asks.
1778
+ *
1779
+ * Owner-only: `apiKey` must belong to the card's owner or this rejects
1780
+ * with `SaltApiError` status 404 -- byte-identical to an unknown
1781
+ * `cardId`, never 403 (a chat member already sees the card in the chat
1782
+ * and gets nothing new from this endpoint).
1783
+ *
1784
+ * `options.after` is either another interaction's id or an ISO 8601
1785
+ * timestamp and returns only newer rows; an unrecognised value fails
1786
+ * OPEN (the full list comes back, still 200) rather than rejecting.
1787
+ */
1788
+ getCard(apiKey: string, cardId: SaltId, options?: {
1789
+ after?: string;
1790
+ }): Promise<CardWithInteractions>;
1791
+ /** My own products (sellerId omitted) or a seller's active products (sellerId given). */
1792
+ listProducts(apiKey: string, sellerId?: SaltId): Promise<unknown[]>;
1793
+ /** Create a product in the caller's shop. */
1794
+ createProduct(apiKey: string, params: Record<string, unknown>): Promise<unknown>;
1795
+ /** Drop one of the caller's products into a chat as a Buy-able bubble. */
1796
+ shareProduct(apiKey: string, productId: SaltId, chatId: SaltId): Promise<unknown>;
1797
+ /**
1798
+ * Send an itemized invoice on the TransferRequest rail. Server
1799
+ * re-validates all the math.
1800
+ *
1801
+ * `idempotencyKey` is sent as an `Idempotency-Key` header, but
1802
+ * `transfer_requests_controller` does NOT include salt-api's `Idempotent`
1803
+ * concern (unlike addUsage/createTransfer paths) -- the server currently
1804
+ * ignores it entirely. A retried call with the same key still creates a
1805
+ * second invoice; there is no dedup, no replay, and none of the 409
1806
+ * `request_in_flight` / `request_failed_terminal` behavior described for
1807
+ * addUsage applies here. Retries of createInvoice are the caller's own
1808
+ * responsibility to guard against for now.
1809
+ */
1810
+ createInvoice(apiKey: string, params: CreateInvoiceParams): Promise<unknown>;
1811
+ /**
1812
+ * Record metered usage against the payer's prepaid credits.
1813
+ *
1814
+ * Pass `idempotencyKey` to make a retried call safe -- a replayed
1815
+ * response carries the `Idempotency-Replayed: true` header. A 409
1816
+ * `request_in_flight` means an earlier call with the same key is still
1817
+ * being processed: retry with a NEW key. A 409 `request_failed_terminal`
1818
+ * means the usage/debit side-effect already happened under that key --
1819
+ * do NOT retry with a new key (that would double-charge); reconcile
1820
+ * against the existing usage event instead.
1821
+ */
1822
+ addUsage(apiKey: string, params: AddUsageParams): Promise<unknown>;
1823
+ /**
1824
+ * Owner-only: an agent's governed-spend policy. Call with the OWNER's
1825
+ * api-key/JWT, not the agent's own key.
1826
+ *
1827
+ * Rule amounts (`budget_amount`, `per_tx_max`, `spent_this_period`,
1828
+ * `remaining`) are all HUMAN-DECIMAL strings, e.g. "0.5" = half an ETH --
1829
+ * never base units (wei/satoshis).
1830
+ *
1831
+ * If the agent has no policy at all, salt-api default-denies every
1832
+ * mainnet send from it -- an owner must call updateSpendingPolicy at
1833
+ * least once before the agent can move mainnet funds.
1834
+ */
1835
+ getSpendingPolicy(apiKey: string, agentId: SaltId): Promise<SpendingPolicy>;
1836
+ /**
1837
+ * Owner-only: replace an agent's governed-spend policy. Call with the
1838
+ * OWNER's api-key/JWT, not the agent's own key.
1839
+ *
1840
+ * `rules[].budget_amount` and `rules[].per_tx_max` are HUMAN-DECIMAL
1841
+ * strings, e.g. "0.5" = half an ETH -- never base units. `frozen: true`
1842
+ * blocks all mainnet sends regardless of `rules`. Governed-spend
1843
+ * refusals from this or any subsequent send carry `{error, code}`
1844
+ * bodies -- see SpendGovernanceCode.
1845
+ */
1846
+ updateSpendingPolicy(apiKey: string, agentId: SaltId, policy: UpdateSpendingPolicyParams): Promise<SpendingPolicy>;
1847
+ /**
1848
+ * Guardian side of social recovery (any account named as a guardian, an
1849
+ * agent included). Requests waiting on the caller: one per account that
1850
+ * appointed it and has an open recovery.
1851
+ */
1852
+ incomingRecoveryRequests(apiKey: string): Promise<RecoveryRequestForGuardian[]>;
1853
+ /** The sealed shares the caller holds for other accounts (ciphertext, encrypted to the caller's own key). */
1854
+ heldRecoveryShares(apiKey: string): Promise<HeldRecoveryShare[]>;
1855
+ /**
1856
+ * Seats other accounts have asked the caller to hold and the caller has
1857
+ * not answered yet (`recovery_guardian_invited` arrives the same way on
1858
+ * the delivery rail). Nothing is released until a seat is accepted.
1859
+ */
1860
+ recoveryGuardianInvitations(apiKey: string): Promise<RecoveryGuardianInvitation[]>;
1861
+ /** Accept a guardian seat (only the named guardian can). Idempotent answers are refused with 422. */
1862
+ acceptRecoveryGuardian(apiKey: string, seatId: SaltId): Promise<RecoveryGuardianInvitation>;
1863
+ /** Decline a guardian seat. */
1864
+ declineRecoveryGuardian(apiKey: string, seatId: SaltId): Promise<RecoveryGuardianInvitation>;
1865
+ /**
1866
+ * Release the caller's share for one recovery request: open the held
1867
+ * share with the caller's OWN private key, re-seal it to the request's
1868
+ * ephemeral public key (what salt-fe does in a person's browser), and
1869
+ * POST it. Salt only ever sees the re-sealed armor.
1870
+ *
1871
+ * Takes only the request id. The requester and the ephemeral key are
1872
+ * read from Salt's own record (`GET /recovery_request/incoming`), never
1873
+ * from the caller, so a delivery body or a hostile prompt cannot pair a
1874
+ * victim's share with an attacker's key (confused deputy). Only call this
1875
+ * once the request is believed genuine -- see recovery.ts. Idempotent: a
1876
+ * second contribution from the same guardian does not count twice.
1877
+ */
1878
+ releaseRecoveryShare(apiKey: string, params: {
1879
+ requestId: SaltId;
1880
+ privateKey: string;
1881
+ passphrase: string;
1882
+ }): Promise<{
1883
+ collected: number;
1884
+ threshold: number | null;
1885
+ }>;
1886
+ /**
1887
+ * The settlement record for a transfer the caller was party to (sender
1888
+ * or receiver) -- 404 for anyone else, or if the transfer hasn't
1889
+ * confirmed on-chain yet (receipts are minted only at confirmation).
1890
+ */
1891
+ getReceipt(apiKey: string, transferId: SaltId): Promise<Receipt>;
1892
+ /** The caller's credit relationships, both directions. */
1893
+ getBillingAccounts(apiKey: string): Promise<unknown>;
1894
+ /**
1895
+ * Payer-initiated deposit: drops a "Credits top-up" invoice into
1896
+ * `chatId` for `payeeId` to receive. Nothing is credited by this call
1897
+ * itself -- the deposit lands only once that invoice is paid (see
1898
+ * onInvoicePaid's `isTopUp`).
1899
+ */
1900
+ topUpBillingAccount(apiKey: string, params: {
1901
+ chatId: SaltId;
1902
+ payeeId: SaltId;
1903
+ amount: string | number;
1904
+ }): Promise<{
1905
+ ok: true;
1906
+ transfer_request_id: SaltId;
1907
+ billing_account: BillingAccount;
1908
+ }>;
1909
+ /**
1910
+ * Payee-only: return value onto the credits ledger. Capped at net usage
1911
+ * (Σusage - Σrefunds) so a refund can only give back credits that were
1912
+ * actually consumed -- it can never mint new balance. A 422
1913
+ * `refund_exceeds_usage` means `amount` is above what's currently
1914
+ * refundable. This is a ledger entry, not an on-chain transfer -- no
1915
+ * Receipt is minted for it.
1916
+ */
1917
+ refundBillingAccount(apiKey: string, billingAccountId: SaltId, params: {
1918
+ amount: string | number;
1919
+ memo?: string;
1920
+ }): Promise<{
1921
+ ok: true;
1922
+ entry_id: SaltId;
1923
+ billing_account: BillingAccount;
1924
+ }>;
1925
+ /**
1926
+ * A transfer's current record, to verify a payment actually confirmed
1927
+ * before an agent claims money work "done" (see actions.ts's
1928
+ * report_progress evidence check). Status is one of "Pending" |
1929
+ * "Confirmed" | "Failed" (Transfer#status in salt-api's
1930
+ * app/models/transfer.rb) -- "Confirmed" is the only settled state.
1931
+ *
1932
+ * NOTE: as of this SDK version salt-api's TransfersController defines
1933
+ * no `show` action (only index, create, and the :id/info member
1934
+ * route) -- this call requires that route added server-side in the
1935
+ * same release this evidence check ships in.
1936
+ */
1937
+ getTransfer(apiKey: string, transferId: SaltId): Promise<{
1938
+ id: SaltId;
1939
+ status: string;
1940
+ [key: string]: unknown;
1941
+ }>;
1942
+ /** The caller's own wallets. */
1943
+ listWallets(apiKey: string): Promise<unknown[]>;
1944
+ /** Upload an already-encrypted wallet under the caller's own identity (see crypto.ts). */
1945
+ createWallet(apiKey: string, params: Record<string, unknown>): Promise<unknown>;
1946
+ /** Hand a chat off to another agent; salt-api does the whole swap atomically. */
1947
+ handOff(apiKey: string, chatId: SaltId, toAgentId: SaltId, reason?: string): Promise<unknown>;
1948
+ /**
1949
+ * One step back: return the chat to whoever handed it TO the current
1950
+ * active agent most recently (a real hop off the trail, not a fixed
1951
+ * destination) -- salt-api's `Chat#hand_off_back!`. 422 with "Already
1952
+ * at the start of this conversation." when there's no previous hop;
1953
+ * callers (actions.ts's hand_back_to_concierge) fall back to something
1954
+ * fixed in that case. The acting agent must currently be this chat's
1955
+ * active agent (or a human member) -- salt-api's `gacm_actor_allowed?`.
1956
+ */
1957
+ handBack(apiKey: string, chatId: SaltId): Promise<unknown>;
1958
+ /**
1959
+ * React to a message with one emoji (`POST /api/v1/messages/:id/reactions`).
1960
+ * A TOGGLE: the same emoji again removes yours. Plaintext metadata, so no
1961
+ * crypto. Resolves with the server's `{message_id, reactions}` summary;
1962
+ * a 422 ("Pick a single emoji." / "You can react with up to 12 emoji.")
1963
+ * arrives as a SaltApiError carrying that sentence.
1964
+ *
1965
+ * The owner's rule for agents: react "not all the time, just when they
1966
+ * choose", and only "if it relevantly complements the chat in a friendly
1967
+ * way" -- a thanks acknowledged, a done request marked, good news
1968
+ * celebrated. Never instead of answering a question, never on every
1969
+ * message, never on your own.
1970
+ */
1971
+ react(apiKey: string, messageId: SaltId, emoji: string): Promise<ReactionSummary>;
1972
+ /** The emoji this identity has used, for picking one (`GET /api/v1/reactions/mine`). */
1973
+ myReactions(apiKey: string): Promise<unknown>;
1974
+ /**
1975
+ * Replies to a post: your reply becomes YOUR OWN post in your profile room, linked to the one it
1976
+ * answers -- the same single action a person has (`POST /api/v1/posts/:id/reply`). Resolves with
1977
+ * the new post's row. Refusals arrive as a SaltApiError carrying the server's sentence: 404 not a
1978
+ * visible post, 403 blocked, 422 empty or too long. Pass `idempotencyKey` to make a retry safe
1979
+ * (sent as `Idempotency-Key`).
1980
+ */
1981
+ replyToPost(apiKey: string, postId: SaltId, text: string, opts?: {
1982
+ idempotencyKey?: string;
1983
+ media?: PostMediaInput;
1984
+ }): Promise<PostRow>;
1985
+ /**
1986
+ * Posts to your own profile: text, a photo or video, or both -- the same single action a person has.
1987
+ * The room is made on first use (`POST /api/v1/profile_room`), then the post goes through
1988
+ * `POST /api/v1/messages`; media is plain and public (up to 15 MB; image/jpeg, png, webp, gif,
1989
+ * heic or video/mp4, webm, quicktime), so it is NOT encrypted like chat attachments. Resolves with
1990
+ * the new message envelope (its `media.url` is the public file). A refusal arrives as a SaltApiError
1991
+ * carrying the server's sentence.
1992
+ */
1993
+ postToProfile(apiKey: string, params: {
1994
+ text?: string;
1995
+ media?: PostMediaInput;
1996
+ }): Promise<{
1997
+ message_id: SaltId;
1998
+ media?: PostMediaInfo;
1999
+ }>;
2000
+ /** The replies to a post, newest page first; pass the last page's `next_before` as `before` for older ones
2001
+ * (`GET /api/v1/posts/:id/replies`). */
2002
+ getPostReplies(apiKey: string, postId: SaltId, opts?: {
2003
+ before?: string;
2004
+ limit?: number;
2005
+ }): Promise<PostRepliesPage>;
2006
+ /** Ephemeral "is typing" ping. Fire-and-forget by design -- a failed ping must never block a reply. */
2007
+ signalTyping(apiKey: string, chatId: SaltId): Promise<void>;
2008
+ /**
2009
+ * Fire-and-forget product-metrics beacon. Deliberately not awaited by
2010
+ * callers -- an analytics post must never delay or fail a reply. Only
2011
+ * names in salt-api's Event::CLIENT_EVENT_NAMES allowlist are accepted.
2012
+ */
2013
+ trackEvent(apiKey: string, name: string, properties?: Record<string, unknown>): void;
2014
+ /**
2015
+ * Prepares a governed payment for approval -- `money.pay`'s mode is FORCED to `ask`, so this
2016
+ * always resolves to an `AskedResult`, never a Transfer, whether called through `actFor` or (in
2017
+ * the unusual case of preparing your own payment) as yourself. Once the exercise is approved
2018
+ * (see `mandates.decide`/`mandates.exercises`, or the `approval_decided` webhook event), settle
2019
+ * it with `transfers#create`'s own `exercise_id` param (not exposed as a typed method here yet --
2020
+ * salt-api validates the wallet/amount/destination against the exercise's summary before it will
2021
+ * create the Transfer).
2022
+ */
2023
+ prepareTransfer(apiKey: string, params: {
2024
+ walletId: SaltId;
2025
+ destinationAddress?: string;
2026
+ receiverId?: SaltId;
2027
+ amount: string | number;
2028
+ chain?: string;
2029
+ note?: string;
2030
+ chatId?: SaltId;
2031
+ }): Promise<AskedResult>;
2032
+ mandates: {
2033
+ /** Every mandate the caller is party to, as grantor and/or grantee. */
2034
+ list(apiKey: string, opts?: {
2035
+ role?: "grantor" | "grantee";
2036
+ status?: MandateStatus;
2037
+ }): Promise<{
2038
+ mandates: Mandate[];
2039
+ }>;
2040
+ get(apiKey: string, mandateId: SaltId): Promise<Mandate>;
2041
+ /** Grantor creates (a proposal the grantee must accept, or auto-active for an account the
2042
+ * caller governs), or a grantee proposes UP by passing `grantor_id` instead. */
2043
+ propose(apiKey: string, params: ProposeMandateParams): Promise<Mandate>;
2044
+ /** New version, compare-and-swap on `params.version` -- a 409 SaltApiError means it changed
2045
+ * under you; re-fetch with `get` and retry against the new version. */
2046
+ update(apiKey: string, mandateId: SaltId, params: UpdateMandateParams): Promise<Mandate>;
2047
+ /** Grantee accepts an offered mandate (binds its current key fingerprint), or the grantor
2048
+ * activates one still `proposed` by the grantee. See `ctx.mandate.accept()` in webhook.ts,
2049
+ * which calls this for an incoming `mandate_offered` event. */
2050
+ accept(apiKey: string, mandateId: SaltId): Promise<Mandate>;
2051
+ /** Standing mandates only: +90 days from now. */
2052
+ renew(apiKey: string, mandateId: SaltId): Promise<Mandate>;
2053
+ pause(apiKey: string, mandateId: SaltId, reason?: string): Promise<Mandate>;
2054
+ resume(apiKey: string, mandateId: SaltId): Promise<Mandate>;
2055
+ /** The grantor, or a governor of either side, can revoke -- cascades to any child mandates. */
2056
+ revoke(apiKey: string, mandateId: SaltId, reason?: string): Promise<Mandate>;
2057
+ /** A mandate's trail, id-descending. `refusal` is only populated when the caller is the
2058
+ * principal or its governor (salt-api's own viewer check -- this SDK doesn't re-derive it). */
2059
+ exercises(apiKey: string, mandateId: SaltId, opts?: {
2060
+ before?: string | number;
2061
+ limit?: number;
2062
+ }): Promise<{
2063
+ exercises: MandateExercise[];
2064
+ }>;
2065
+ /** Every open ask (decision `"asked"`, not yet expired) the caller can decide right now. */
2066
+ openExercises(apiKey: string): Promise<{
2067
+ exercises: MandateExercise[];
2068
+ }>;
2069
+ /** Approve or deny one open ask. See `ctx.approval.decide()` in webhook.ts, which calls this
2070
+ * for an incoming `approval_requested` event. */
2071
+ decide(apiKey: string, exerciseId: SaltId | number, decision: "approve" | "deny", note?: string): Promise<MandateExercise>;
2072
+ };
2073
+ teams: {
2074
+ /** The Teams shelf: listed templates, most installed first. Public. */
2075
+ list(apiKey: string, opts?: {
2076
+ q?: string;
2077
+ limit?: number;
2078
+ offset?: number;
2079
+ }): Promise<{
2080
+ teams: TeamTemplate[];
2081
+ total: number;
2082
+ }>;
2083
+ /** The templates this identity shared, listed or not. */
2084
+ mine(apiKey: string): Promise<{
2085
+ teams: TeamTemplate[];
2086
+ }>;
2087
+ /** A recipe preview, by id or slug. Public; an unlisted template is reachable by its link. */
2088
+ get(apiKey: string, idOrSlug: string): Promise<TeamTemplate>;
2089
+ /** Shares one of the caller's own agent groups as a template. */
2090
+ share(apiKey: string, params: ShareTeamParams): Promise<TeamTemplate & {
2091
+ left_out: number;
2092
+ }>;
2093
+ /** Author only: name, description, listed or unlisted, the coordinator note. */
2094
+ update(apiKey: string, idOrSlug: string, changes: Partial<Pick<ShareTeamParams, "name" | "description" | "listed" | "coordinatorNote">>): Promise<TeamTemplate>;
2095
+ /** Author only. Agents already made from it are the installers' own. */
2096
+ remove(apiKey: string, idOrSlug: string): Promise<void>;
2097
+ /** "I made this team": counted once per installer. */
2098
+ installed(apiKey: string, idOrSlug: string): Promise<{
2099
+ install_count: number;
2100
+ counted: boolean;
2101
+ }>;
2102
+ };
2103
+ apps: {
2104
+ /**
2105
+ * Creates an app + version 1. `forId` builds it for someone else (that
2106
+ * person must share a chat with the caller, else 422); omitted, the
2107
+ * app belongs to the caller. `chatId` also installs it there in the
2108
+ * same transaction (caller must be a member) -- see `installation` on
2109
+ * the result.
2110
+ */
2111
+ create(apiKey: string, params: CreateAppParams): Promise<CreateAppResult>;
2112
+ /**
2113
+ * Publishes a new immutable version (owner or builder only) and moves
2114
+ * the app's `latest_version_id` to it. `actions` (omitted: none)
2115
+ * replace whatever the PRIOR version declared -- they don't merge.
2116
+ * Never changes an existing installation on its own -- each keeps
2117
+ * running its own pinned version (and that version's own `actions`)
2118
+ * until it calls `update`, below.
2119
+ */
2120
+ newVersion(apiKey: string, appId: SaltId, html: string, actions?: AppQuickAction[]): Promise<{
2121
+ app: AppSummary;
2122
+ version: AppVersionRef;
2123
+ }>;
2124
+ /** An app's metadata: owner, builder, latest version -- and, once listed, what the App Store shows. */
2125
+ get(apiKey: string, appId: SaltId): Promise<AppSummary>;
2126
+ /**
2127
+ * Installs `appId` -- into `chatId` (caller must be a non-observer
2128
+ * member; a second active install of the same app there 409s with the
2129
+ * existing installation instead of creating a duplicate) or, with no
2130
+ * `chatId`, into the caller's own personal space ("Your apps").
2131
+ * `copyStateFrom` copies another readable installation's doc into the
2132
+ * new one once.
2133
+ */
2134
+ install(apiKey: string, appId: SaltId, params?: InstallAppParams): Promise<AppInstallation>;
2135
+ /**
2136
+ * This identity's installations: every one in `chatId` (chat members
2137
+ * only) when given, else its own personal installations plus every
2138
+ * chat installation in a chat it's a member of. Cache-busted -- an
2139
+ * install just made should show up immediately.
2140
+ */
2141
+ installations(apiKey: string, opts?: {
2142
+ chatId?: SaltId;
2143
+ }): Promise<AppInstallation[]>;
2144
+ /** The installation's shared state doc and its CAS version. Cache-busted -- a doc just written with `setState` should read back immediately. */
2145
+ state(apiKey: string, installationId: SaltId): Promise<AppStateDoc>;
2146
+ /**
2147
+ * Compare-and-swap write of the installation's shared state doc. A 409
2148
+ * (the doc moved under the caller since `ifVersion`) is never handed
2149
+ * back as a bare SaltApiError -- it's re-thrown as
2150
+ * AppStateConflictError carrying the CURRENT `{doc, version}` straight
2151
+ * off the response body, so a caller can merge its change onto the
2152
+ * fresh doc and retry with the new version in one more call rather
2153
+ * than round-tripping to re-fetch first.
2154
+ */
2155
+ setState(apiKey: string, installationId: SaltId, doc: Record<string, unknown>, ifVersion: number): Promise<AppStateDoc>;
2156
+ };
2157
+ }>[K]>; };
75
2158
  /** A chat's members (with public keys), for encrypting a reply to everyone who should read it. */
76
- getChatMembers(apiKey: string, chatId: number | string): Promise<SaltUser[]>;
2159
+ getChatMembers(apiKey: string, chatId: SaltId): Promise<SaltUser[]>;
77
2160
  /** Recent messages in a chat this identity is a member of (even as a silent observer). */
78
- getChatMessages(apiKey: string, chatId: number | string): Promise<unknown[]>;
2161
+ getChatMessages(apiKey: string, chatId: SaltId): Promise<unknown[]>;
2162
+ /**
2163
+ * One `GET /api/v1/chats/:id` call for a caller that needs BOTH the
2164
+ * member list and whether the chat is end-to-end encrypted -- e.g.
2165
+ * actions.ts's request_floor, which (unlike delegate_to_agent/
2166
+ * consult_agent) has no other reason to fetch the chat at all.
2167
+ * `encrypted` is already resolved to a real boolean (defaulted `true`
2168
+ * when the field is absent, same convention as ChatOpenedContext.encrypted
2169
+ * and MessageContext.encrypted) -- never undefined.
2170
+ */
2171
+ getChat(apiKey: string, chatId: SaltId): Promise<{
2172
+ id: SaltId;
2173
+ encrypted: boolean;
2174
+ users: SaltUser[];
2175
+ }>;
2176
+ /**
2177
+ * Salt's own record of a chat, exactly as `GET /api/v1/chats/:id` answers
2178
+ * it (nothing flattened or defaulted, unlike getChat): `session.users`
2179
+ * with each member's `observer` flag, `session.public`, `session.encrypted`.
2180
+ * This is what `isPrivateChat(chat, speakerId, myId)` reads -- decide
2181
+ * privacy-sensitive actions from THIS, fetched when you need it, never
2182
+ * from a cache or a memory service.
2183
+ */
2184
+ getChatRecord(apiKey: string, chatId: SaltId): Promise<SaltChat>;
79
2185
  /** Download an attachment's ciphertext bytes. Decrypt separately (see crypto.ts). */
80
- getAttachment(apiKey: string, messageId: number | string): Promise<Buffer>;
2186
+ getAttachment(apiKey: string, messageId: SaltId): Promise<Buffer>;
81
2187
  /**
82
2188
  * Post a reply into a chat. `message` is the ciphertext every human
83
2189
  * recipient reads; `senderMessage` is the copy encrypted to the
84
2190
  * sender's own key. `delegations` (optional) is the provenance trail
85
2191
  * -- salt-api re-validates every entry against the sender's actual
86
- * memberships before storing it.
2192
+ * memberships before storing it. The result carries `delivered_as`:
2193
+ * `"request"` means the recipient has not accepted a first message from
2194
+ * you yet (see Message requests in AGENTS.md) -- wait, do not re-send.
87
2195
  */
88
- postMessage(apiKey: string, chatId: number | string, message: string, senderMessage?: string, delegations?: Array<{
89
- agent_id: number;
90
- chat_id: number;
2196
+ postMessage(apiKey: string, chatId: SaltId, message: string, senderMessage?: string, delegations?: Array<{
2197
+ agent_id: SaltId;
2198
+ chat_id: SaltId;
91
2199
  username: string;
92
- }>): Promise<unknown>;
2200
+ }>, mentions?: SaltId[], opts?: {
2201
+ quiet?: boolean;
2202
+ }): Promise<PostMessageResult>;
2203
+ /**
2204
+ * Sends a file into a chat, encrypted exactly the way salt-fe's
2205
+ * chatbox.jsx sendOneAttachment encrypts one: a fresh one-time AES-256-
2206
+ * GCM key encrypts the file bytes (crypto.ts's encryptAttachment,
2207
+ * byte-identical wire format to the browser's Web Crypto output), and
2208
+ * that key/iv plus the real filename/content_type/size are PGP-
2209
+ * encrypted as one JSON blob for every current chat member's public
2210
+ * key -- the SAME single recipient list the caption text is encrypted
2211
+ * for, fetched fresh via GET /api/v1/chats/:id so a just-added or
2212
+ * just-removed member is never missed or over-included. Unlike
2213
+ * postMessage's reply() there is no separate sender_message: the web
2214
+ * never excludes the sender from an attachment's recipient list (it
2215
+ * has no reason to -- there's only one `attachment_encrypted_key`
2216
+ * field, see Attachment#as_chat_resource), so a single PGP blob
2217
+ * covering every member already covers the sender too.
2218
+ *
2219
+ * Throws before doing any crypto work on an oversized file (matches
2220
+ * salt-api's own MAX_ATTACHMENT_BYTES so a doomed upload never even
2221
+ * starts) or a content_type outside ALLOWED_SEND_CONTENT_TYPES.
2222
+ */
2223
+ sendAttachment(apiKey: string, chatId: SaltId, params: SendAttachmentParams): Promise<unknown>;
2224
+ /**
2225
+ * Post into an OPEN ROOM -- a chat with no end-to-end encryption, so
2226
+ * `text` rides the wire and is stored as plain text rather than a PGP
2227
+ * blob. There is no `senderMessage`/`delegations`/`mentions` shape here
2228
+ * the way postMessage has: salt-api refuses this call 422 against a
2229
+ * chat that isn't plain (never silently encrypts, never silently
2230
+ * drops the call). Webhook/socket deliveries for a plain chat carry
2231
+ * `message.encrypted === false` -- see webhook.ts's handleMessage,
2232
+ * which hands the body straight through as ctx.text (ctx.encrypted
2233
+ * false) instead of attempting PGP decrypt.
2234
+ */
2235
+ postPlainMessage(apiKey: string, chatId: SaltId, text: string): Promise<unknown>;
2236
+ /**
2237
+ * Open rooms (0.6x): declares what this identity wants delivered from
2238
+ * a chat it isn't necessarily addressed in every message of --
2239
+ * `"addressed"` (only a direct reply/@mention, the closest analogue to
2240
+ * how a normal encrypted chat already gates agent delivery),
2241
+ * `"keywords"` (any message containing one of `keywords`), or `"all"`
2242
+ * (every message). Always this identity's OWN subscription for
2243
+ * `chatId` -- same "acts on the caller, never someone else" shape as
2244
+ * setDeliveryMode/setCallback.
2245
+ */
2246
+ setChatSubscription(apiKey: string, chatId: SaltId, params: {
2247
+ mode: "addressed" | "keywords" | "all";
2248
+ keywords?: string[];
2249
+ }): Promise<unknown>;
2250
+ /** Removes this identity's subscription record for `chatId` -- salt-api's
2251
+ * default (unsubscribed) behavior applies again, same as never having
2252
+ * called setChatSubscription. */
2253
+ clearChatSubscription(apiKey: string, chatId: SaltId): Promise<unknown>;
2254
+ /**
2255
+ * Get-or-create the private lane (a sidechain) between this identity and
2256
+ * `withId`, both members of `chatId`. Once per pair on Salt's side. Answers
2257
+ * 422 when `chatId` is itself a lane. The lane's members come back with
2258
+ * their public keys.
2259
+ */
2260
+ openSidechain(apiKey: string, chatId: SaltId, withId: SaltId): Promise<{
2261
+ session: {
2262
+ id: SaltId;
2263
+ users?: SaltUser[];
2264
+ coaching_for_chat_id?: SaltId;
2265
+ [key: string]: unknown;
2266
+ };
2267
+ messages?: unknown[];
2268
+ }>;
2269
+ /**
2270
+ * Opens (or reuses) a CONSULT lane between the caller and `withId`, both
2271
+ * already members of `roomId` -- mechanically identical to
2272
+ * openSidechain (same endpoint, same get-or-create-per-pair semantics),
2273
+ * named separately because actions.ts's consult_agent reads more
2274
+ * clearly calling "open the lane I'm about to consult in" than a
2275
+ * generic sidechain call. salt-api tags the resulting chat
2276
+ * `lane_kind: "consult"`, which webhook.ts reads back off the webhook
2277
+ * body to raise the agent-to-agent reply cap for that lane and to gate
2278
+ * request_floor.
2279
+ */
2280
+ openConsultLane(apiKey: string, roomId: SaltId, withId: SaltId): Promise<{
2281
+ session: {
2282
+ id: SaltId;
2283
+ users?: SaltUser[];
2284
+ coaching_for_chat_id?: SaltId;
2285
+ lane_kind?: string;
2286
+ [key: string]: unknown;
2287
+ };
2288
+ messages?: unknown[];
2289
+ }>;
93
2290
  /** Register a new Salt agent, owned by whoever's api-key calls this. */
94
- createAgent(apiKey: string, params: CreateAgentParams): Promise<SaltUser>;
95
- /** Owner-only: fetch an agent's own api-key + PGP keypair right after creating it. */
96
- getAgentAdmin(apiKey: string, agentId: number | string): Promise<{
2291
+ /**
2292
+ * The create response carries the new agent's raw api key EXACTLY ONCE --
2293
+ * salt-api stores only a digest, so no later endpoint can show it again.
2294
+ * Capture `api_key` here; a lost key means rotateAgentApiKey, not re-reading.
2295
+ *
2296
+ * Key custody: `CreateAgentParams` deliberately has no `private_key` --
2297
+ * salt-api rejects one with a 422 unconditionally (salt-api docs/
2298
+ * KEY_CUSTODY.md Phase 5). The key stays with whoever generates it
2299
+ * (pgp.generateKeypair, above the call site) and is registered with
2300
+ * identities.register/a session store; salt-api never receives a copy in
2301
+ * any form it can decrypt, so there is nothing to fetch back later --
2302
+ * getAgentAdmin's `userkeys` never carries a usable key either.
2303
+ */
2304
+ createAgent(apiKey: string, params: CreateAgentParams): Promise<SaltUser & {
2305
+ api_key?: string;
2306
+ }>;
2307
+ /** Owner-only: mint a new api key for an agent. The old one stops working immediately. */
2308
+ rotateAgentApiKey(apiKey: string, agentId: SaltId): Promise<{
2309
+ api_key: string;
2310
+ }>;
2311
+ /**
2312
+ * This identity's own webhook signing key, used to verify that an incoming
2313
+ * webhook really came from salt-api.
2314
+ *
2315
+ * Fetched with the identity's own api key rather than configured from the
2316
+ * environment: salt-api mints one secret per agent, so shipping them through
2317
+ * env/Secrets Manager would mean the deploy had to know values the API
2318
+ * generates. Fetching at boot also means rotation takes effect on restart.
2319
+ */
2320
+ getWebhookSecret(apiKey: string): Promise<string | undefined>;
2321
+ /**
2322
+ * Which agent does this api key belong to, according to salt-api?
2323
+ *
2324
+ * The same endpoint as getWebhookSecret: alongside the secret it returns
2325
+ * the canonical `agent_id` of the key's owner, which is the id salt-api
2326
+ * puts in X-Salt-Agent-Id on every callback. An identity registered under
2327
+ * any other id -- an env var, or a store entry that predates an id
2328
+ * migration -- can never be found when a webhook arrives; that is what
2329
+ * reconcileIdentityIds uses this to detect and repair.
2330
+ */
2331
+ whoAmI: (apiKey: string) => Promise<{
2332
+ agent_id: SaltId;
2333
+ webhook_secret?: string;
2334
+ }>;
2335
+ /**
2336
+ * Owner-only: mint a new webhook signing secret for an agent. The old
2337
+ * secret stops verifying immediately, so the agent must re-fetch via
2338
+ * getWebhookSecret before it can validate any further deliveries.
2339
+ */
2340
+ rotateWebhookSecret(apiKey: string, agentId: SaltId): Promise<{
2341
+ agent_id: SaltId;
2342
+ webhook_secret: string;
2343
+ }>;
2344
+ /**
2345
+ * The caller sets its OWN webhook callback -- api-key auth, no id
2346
+ * parameter, same "acts on the caller, never someone else" shape as
2347
+ * setDeliveryMode/setChatSubscription. Exists so the platform hosting
2348
+ * an agent can point its callback at itself without the owner ever
2349
+ * opening Salt's web console -- see AgentsController#set_callback's
2350
+ * own comment. Same URL-safety rule as the owner's web form
2351
+ * (User#callback_must_be_safe) applies server-side; a human caller
2352
+ * (not an agent's own api-key) gets a 403.
2353
+ */
2354
+ setCallback(apiKey: string, webhook: string): Promise<{
2355
+ agent_id: SaltId;
2356
+ callback: string;
2357
+ }>;
2358
+ /**
2359
+ * K2 socket mode: the caller sets its OWN delivery mode -- "webhook"
2360
+ * (the default, POST to its registered callback) or "socket" (drain
2361
+ * GET /api/v1/agent/updates or AgentUpdatesChannel instead; see
2362
+ * socket.ts's createSocketClient). No id parameter, same reason
2363
+ * getWebhookSecret has none -- an agent can only ever change its own.
2364
+ * A blank callback is always socket mode regardless of this setting.
2365
+ */
2366
+ setDeliveryMode(apiKey: string, mode: "webhook" | "socket"): Promise<{
2367
+ agent_id: SaltId;
2368
+ delivery_mode: string;
2369
+ socket_mode: boolean;
2370
+ }>;
2371
+ /**
2372
+ * Owner-only agent admin record. `apikey` is metadata (hint, last-used) --
2373
+ * the raw key rides only on the createAgent response, and after that only
2374
+ * rotateAgentApiKey can produce a usable value.
2375
+ */
2376
+ getAgentAdmin(apiKey: string, agentId: SaltId): Promise<{
97
2377
  apikey: {
98
- api_key: string;
99
- };
2378
+ token_hint?: string;
2379
+ last_used_at?: string;
2380
+ } | null;
100
2381
  userkeys: unknown;
101
2382
  [key: string]: unknown;
102
2383
  }>;
2384
+ /**
2385
+ * This agent's own identity: every section (claim and proof) with
2386
+ * `scope`/`editable_by`/`checked_by`, plus the public card URL it's
2387
+ * served from. Cache-busted like getChatMembers -- a value just set
2388
+ * with setIdentity should read back immediately.
2389
+ */
2390
+ identity(apiKey: string): Promise<IdentitySections>;
2391
+ /**
2392
+ * Set one or more of this agent's own CLAIM sections (see
2393
+ * AGENT_CLAIM_SECTION_KEYS). `scope` is never accepted here -- section
2394
+ * visibility is owner-authenticated through the agent-management API
2395
+ * only (plan section 7: "setScope is never on this surface"), so a
2396
+ * caller that tries to slip one in is refused BEFORE any request is
2397
+ * made, the same way a bad tool call should fail loud and immediately
2398
+ * rather than round-tripping to find out the server also says no.
2399
+ * A 422 from salt-api ({errors: {key: "line"}} for an invalid value, or
2400
+ * {error: "Scope comes later."} were scope to somehow reach it anyway)
2401
+ * surfaces as an ordinary SaltApiError.
2402
+ */
2403
+ setIdentity(apiKey: string, claims: IdentityClaims): Promise<IdentitySections>;
2404
+ /**
2405
+ * Writes one disclosure ledger row -- metadata only, never a section
2406
+ * VALUE (plan section 4). identityShare.ts's share() calls this once
2407
+ * per recipient BEFORE sending any ciphertext, with the SAME `id` every
2408
+ * time (the row is keyed on subject + recipient + id, so this creates
2409
+ * one row per recipient under one shared id), and aborts the whole
2410
+ * share on the first refusal (422, one plain sentence -- e.g. a key
2411
+ * that isn't actually shared with that recipient) with nothing sent.
2412
+ */
2413
+ postIdentityDisclosure(apiKey: string, params: PostIdentityDisclosureParams): Promise<IdentityDisclosure>;
2414
+ /**
2415
+ * Records which chat message actually carried a share's slice, once
2416
+ * it's been posted. Called ONCE per share() call (with the share's one
2417
+ * `id`) right after the one encrypted SLICE message goes out
2418
+ * (identityShare.ts's share()) -- since every recipient's row shares
2419
+ * that id, one PATCH reaches all of them.
2420
+ */
2421
+ setIdentityDisclosureMessage(apiKey: string, id: string, messageId: SaltId): Promise<{
2422
+ disclosures: IdentityDisclosure[];
2423
+ }>;
2424
+ /** This agent's own disclosure ledger, newest first. */
2425
+ listIdentityDisclosures(apiKey: string, opts?: {
2426
+ before?: string;
2427
+ limit?: number;
2428
+ }): Promise<{
2429
+ disclosures: IdentityDisclosure[];
2430
+ }>;
2431
+ /**
2432
+ * Revokes EVERY ledger row under `id` (every recipient of that share)
2433
+ * in one call -- stops them being served again and is the signal
2434
+ * identityShare.ts's revoke() sends a `[[SALT-IDENTITY-REVOKE]]` wire
2435
+ * marker off of. Cannot reach a message already read; the rows
2436
+ * themselves are the only thing this call changes.
2437
+ */
2438
+ revokeIdentityDisclosure(apiKey: string, id: string): Promise<{
2439
+ disclosures: IdentityDisclosure[];
2440
+ }>;
2441
+ /**
2442
+ * Fetches a person's or an agent's PUBLICLY SERVED signed card and
2443
+ * verifies its signature before returning it -- this is a public,
2444
+ * unauthenticated GET (no api-key), matching agent_cards_controller's
2445
+ * `skip_before_action :auth_user`. Tries the agent path then the user
2446
+ * path unless `opts.kind` says which one. A card whose signature this
2447
+ * SDK cannot confirm (missing, unparseable, unknown key, tampered)
2448
+ * throws IdentityCardInvalidError rather than ever coming back
2449
+ * `verified: false` -- a card either verifies or this call fails.
2450
+ */
2451
+ card(handle: string, opts?: {
2452
+ kind?: "agent" | "user";
2453
+ }): Promise<{
2454
+ card: SignedCard;
2455
+ verified: true;
2456
+ }>;
103
2457
  /** The public Agents directory. */
104
2458
  listAgents(apiKey: string): Promise<SaltUser[]>;
105
2459
  /** A single agent's directory entry. */
106
- getAgent(apiKey: string, agentId: number | string): Promise<SaltUser>;
107
- /** Create-or-reuse a 1:1 chat with `contactId` -- idempotent on Salt's side. */
108
- createOrGetChat(apiKey: string, contactId: number | string): Promise<SaltChat>;
2460
+ getAgent(apiKey: string, agentId: SaltId): Promise<SaltUser>;
2461
+ /** Create-or-reuse a 1:1 chat with `contactId` -- idempotent on Salt's side. The result's `delivered_as` is
2462
+ * `"request"` when the contact has not accepted a first message from you yet: wait, and do not open another
2463
+ * chat. A recipient who only takes messages from their contacts throws `RecipientContactsOnlyError`. */
2464
+ createOrGetChat(apiKey: string, contactId: SaltId): Promise<SaltChat>;
2465
+ /** Your own `message_from` setting: who can start a chat with you without landing in Requests or being refused. */
2466
+ getMessageFrom(apiKey: string): Promise<MessageFrom>;
2467
+ /** Set your own `message_from` (an agent changes its OWN setting). An unknown value is a 422 with
2468
+ * `code: "invalid_message_from"`. Returns the updated account fields. */
2469
+ setMessageFrom(apiKey: string, value: MessageFrom): Promise<{
2470
+ message_from: MessageFrom;
2471
+ [key: string]: unknown;
2472
+ }>;
2473
+ /** Message requests waiting on you: 1:1 chats strangers started that you have not accepted. Same shape as
2474
+ * the chat list (rows are `{session, recent_message}`: the chat id is `row.session.id`); ordinary chats
2475
+ * are not in it. You can read a request through the API and decide. */
2476
+ listRequests(apiKey: string): Promise<SaltChatListRow[]>;
2477
+ /** Accept a message request: it becomes an ordinary chat. Idempotent. Returns the chat. */
2478
+ acceptRequest(apiKey: string, chatId: SaltId): Promise<SaltChat>;
2479
+ /** Decline a message request. The sender is not told: they still see the chat as sent, and anything more
2480
+ * they send you (a new chat, a group add) stays quiet. Blocking the sender (the block endpoint) also declines. */
2481
+ declineRequest(apiKey: string, chatId: SaltId): Promise<void>;
2482
+ /** Accounts that added you as a contact and that you have not added back ("Someone added you"). */
2483
+ listIncomingContacts(apiKey: string): Promise<IncomingContact[]>;
2484
+ /** Accept an incoming contact: add them back (`POST /api/v1/contacts {contact_id}`, the ordinary add-contact call).
2485
+ * This also follows their posts and accepts any message request of yours still waiting from them. */
2486
+ acceptContact(apiKey: string, userId: SaltId): Promise<unknown>;
2487
+ /** Dismiss an incoming contact without adding them back; they drop out of the incoming list and are not told. */
2488
+ dismissIncomingContact(apiKey: string, userId: SaltId): Promise<void>;
2489
+ /** Exact-match lookup of accounts by handle (`username`) or a one-box `q`
2490
+ * (a handle with or without "@", or a fingerprint). Never fuzzy, so a
2491
+ * person who is not discoverable is still found by their exact handle. */
2492
+ searchContacts(apiKey: string, query: {
2493
+ username?: string;
2494
+ q?: string;
2495
+ }): Promise<SaltUser[]>;
109
2496
  /** Post a declarative blocks card into a chat (see CARD_PROTOCOL_SPEC.md / cards.ts). */
110
- postCard(apiKey: string, chatId: number | string, blocks: CardBlock[], text: string): Promise<unknown>;
2497
+ postCard(apiKey: string, chatId: SaltId, blocks: CardBlock[], text: string): Promise<unknown>;
111
2498
  /** Replace an owned card's blocks -- re-broadcasts into everyone's bubble live. */
112
- updateCard(apiKey: string, cardId: number | string, blocks: CardBlock[]): Promise<unknown>;
2499
+ updateCard(apiKey: string, cardId: SaltId, blocks: CardBlock[]): Promise<unknown>;
2500
+ /**
2501
+ * `GET /api/v1/cards/:id` -- a card's OWNER polling its own tap history
2502
+ * instead of the agent's socket-mode outbox, which has exactly ONE
2503
+ * forward-only cursor per agent: two concurrent pollers (or one running
2504
+ * beside a socket listener) can otherwise silently consume each other's
2505
+ * answers. Polling this instead is idempotent and shares nothing across
2506
+ * callers -- any number of concurrent asks.
2507
+ *
2508
+ * Owner-only: `apiKey` must belong to the card's owner or this rejects
2509
+ * with `SaltApiError` status 404 -- byte-identical to an unknown
2510
+ * `cardId`, never 403 (a chat member already sees the card in the chat
2511
+ * and gets nothing new from this endpoint).
2512
+ *
2513
+ * `options.after` is either another interaction's id or an ISO 8601
2514
+ * timestamp and returns only newer rows; an unrecognised value fails
2515
+ * OPEN (the full list comes back, still 200) rather than rejecting.
2516
+ */
2517
+ getCard(apiKey: string, cardId: SaltId, options?: {
2518
+ after?: string;
2519
+ }): Promise<CardWithInteractions>;
113
2520
  /** My own products (sellerId omitted) or a seller's active products (sellerId given). */
114
- listProducts(apiKey: string, sellerId?: number | string): Promise<unknown[]>;
2521
+ listProducts(apiKey: string, sellerId?: SaltId): Promise<unknown[]>;
115
2522
  /** Create a product in the caller's shop. */
116
2523
  createProduct(apiKey: string, params: Record<string, unknown>): Promise<unknown>;
117
2524
  /** Drop one of the caller's products into a chat as a Buy-able bubble. */
118
- shareProduct(apiKey: string, productId: number | string, chatId: number | string): Promise<unknown>;
119
- /** Send an itemized invoice on the TransferRequest rail. Server re-validates all the math. */
2525
+ shareProduct(apiKey: string, productId: SaltId, chatId: SaltId): Promise<unknown>;
2526
+ /**
2527
+ * Send an itemized invoice on the TransferRequest rail. Server
2528
+ * re-validates all the math.
2529
+ *
2530
+ * `idempotencyKey` is sent as an `Idempotency-Key` header, but
2531
+ * `transfer_requests_controller` does NOT include salt-api's `Idempotent`
2532
+ * concern (unlike addUsage/createTransfer paths) -- the server currently
2533
+ * ignores it entirely. A retried call with the same key still creates a
2534
+ * second invoice; there is no dedup, no replay, and none of the 409
2535
+ * `request_in_flight` / `request_failed_terminal` behavior described for
2536
+ * addUsage applies here. Retries of createInvoice are the caller's own
2537
+ * responsibility to guard against for now.
2538
+ */
120
2539
  createInvoice(apiKey: string, params: CreateInvoiceParams): Promise<unknown>;
121
- /** Record metered usage against the payer's prepaid credits. */
2540
+ /**
2541
+ * Record metered usage against the payer's prepaid credits.
2542
+ *
2543
+ * Pass `idempotencyKey` to make a retried call safe -- a replayed
2544
+ * response carries the `Idempotency-Replayed: true` header. A 409
2545
+ * `request_in_flight` means an earlier call with the same key is still
2546
+ * being processed: retry with a NEW key. A 409 `request_failed_terminal`
2547
+ * means the usage/debit side-effect already happened under that key --
2548
+ * do NOT retry with a new key (that would double-charge); reconcile
2549
+ * against the existing usage event instead.
2550
+ */
122
2551
  addUsage(apiKey: string, params: AddUsageParams): Promise<unknown>;
2552
+ /**
2553
+ * Owner-only: an agent's governed-spend policy. Call with the OWNER's
2554
+ * api-key/JWT, not the agent's own key.
2555
+ *
2556
+ * Rule amounts (`budget_amount`, `per_tx_max`, `spent_this_period`,
2557
+ * `remaining`) are all HUMAN-DECIMAL strings, e.g. "0.5" = half an ETH --
2558
+ * never base units (wei/satoshis).
2559
+ *
2560
+ * If the agent has no policy at all, salt-api default-denies every
2561
+ * mainnet send from it -- an owner must call updateSpendingPolicy at
2562
+ * least once before the agent can move mainnet funds.
2563
+ */
2564
+ getSpendingPolicy(apiKey: string, agentId: SaltId): Promise<SpendingPolicy>;
2565
+ /**
2566
+ * Owner-only: replace an agent's governed-spend policy. Call with the
2567
+ * OWNER's api-key/JWT, not the agent's own key.
2568
+ *
2569
+ * `rules[].budget_amount` and `rules[].per_tx_max` are HUMAN-DECIMAL
2570
+ * strings, e.g. "0.5" = half an ETH -- never base units. `frozen: true`
2571
+ * blocks all mainnet sends regardless of `rules`. Governed-spend
2572
+ * refusals from this or any subsequent send carry `{error, code}`
2573
+ * bodies -- see SpendGovernanceCode.
2574
+ */
2575
+ updateSpendingPolicy(apiKey: string, agentId: SaltId, policy: UpdateSpendingPolicyParams): Promise<SpendingPolicy>;
2576
+ /**
2577
+ * Guardian side of social recovery (any account named as a guardian, an
2578
+ * agent included). Requests waiting on the caller: one per account that
2579
+ * appointed it and has an open recovery.
2580
+ */
2581
+ incomingRecoveryRequests(apiKey: string): Promise<RecoveryRequestForGuardian[]>;
2582
+ /** The sealed shares the caller holds for other accounts (ciphertext, encrypted to the caller's own key). */
2583
+ heldRecoveryShares(apiKey: string): Promise<HeldRecoveryShare[]>;
2584
+ /**
2585
+ * Seats other accounts have asked the caller to hold and the caller has
2586
+ * not answered yet (`recovery_guardian_invited` arrives the same way on
2587
+ * the delivery rail). Nothing is released until a seat is accepted.
2588
+ */
2589
+ recoveryGuardianInvitations(apiKey: string): Promise<RecoveryGuardianInvitation[]>;
2590
+ /** Accept a guardian seat (only the named guardian can). Idempotent answers are refused with 422. */
2591
+ acceptRecoveryGuardian(apiKey: string, seatId: SaltId): Promise<RecoveryGuardianInvitation>;
2592
+ /** Decline a guardian seat. */
2593
+ declineRecoveryGuardian(apiKey: string, seatId: SaltId): Promise<RecoveryGuardianInvitation>;
2594
+ /**
2595
+ * Release the caller's share for one recovery request: open the held
2596
+ * share with the caller's OWN private key, re-seal it to the request's
2597
+ * ephemeral public key (what salt-fe does in a person's browser), and
2598
+ * POST it. Salt only ever sees the re-sealed armor.
2599
+ *
2600
+ * Takes only the request id. The requester and the ephemeral key are
2601
+ * read from Salt's own record (`GET /recovery_request/incoming`), never
2602
+ * from the caller, so a delivery body or a hostile prompt cannot pair a
2603
+ * victim's share with an attacker's key (confused deputy). Only call this
2604
+ * once the request is believed genuine -- see recovery.ts. Idempotent: a
2605
+ * second contribution from the same guardian does not count twice.
2606
+ */
2607
+ releaseRecoveryShare(apiKey: string, params: {
2608
+ requestId: SaltId;
2609
+ privateKey: string;
2610
+ passphrase: string;
2611
+ }): Promise<{
2612
+ collected: number;
2613
+ threshold: number | null;
2614
+ }>;
2615
+ /**
2616
+ * The settlement record for a transfer the caller was party to (sender
2617
+ * or receiver) -- 404 for anyone else, or if the transfer hasn't
2618
+ * confirmed on-chain yet (receipts are minted only at confirmation).
2619
+ */
2620
+ getReceipt(apiKey: string, transferId: SaltId): Promise<Receipt>;
123
2621
  /** The caller's credit relationships, both directions. */
124
2622
  getBillingAccounts(apiKey: string): Promise<unknown>;
2623
+ /**
2624
+ * Payer-initiated deposit: drops a "Credits top-up" invoice into
2625
+ * `chatId` for `payeeId` to receive. Nothing is credited by this call
2626
+ * itself -- the deposit lands only once that invoice is paid (see
2627
+ * onInvoicePaid's `isTopUp`).
2628
+ */
2629
+ topUpBillingAccount(apiKey: string, params: {
2630
+ chatId: SaltId;
2631
+ payeeId: SaltId;
2632
+ amount: string | number;
2633
+ }): Promise<{
2634
+ ok: true;
2635
+ transfer_request_id: SaltId;
2636
+ billing_account: BillingAccount;
2637
+ }>;
2638
+ /**
2639
+ * Payee-only: return value onto the credits ledger. Capped at net usage
2640
+ * (Σusage - Σrefunds) so a refund can only give back credits that were
2641
+ * actually consumed -- it can never mint new balance. A 422
2642
+ * `refund_exceeds_usage` means `amount` is above what's currently
2643
+ * refundable. This is a ledger entry, not an on-chain transfer -- no
2644
+ * Receipt is minted for it.
2645
+ */
2646
+ refundBillingAccount(apiKey: string, billingAccountId: SaltId, params: {
2647
+ amount: string | number;
2648
+ memo?: string;
2649
+ }): Promise<{
2650
+ ok: true;
2651
+ entry_id: SaltId;
2652
+ billing_account: BillingAccount;
2653
+ }>;
2654
+ /**
2655
+ * A transfer's current record, to verify a payment actually confirmed
2656
+ * before an agent claims money work "done" (see actions.ts's
2657
+ * report_progress evidence check). Status is one of "Pending" |
2658
+ * "Confirmed" | "Failed" (Transfer#status in salt-api's
2659
+ * app/models/transfer.rb) -- "Confirmed" is the only settled state.
2660
+ *
2661
+ * NOTE: as of this SDK version salt-api's TransfersController defines
2662
+ * no `show` action (only index, create, and the :id/info member
2663
+ * route) -- this call requires that route added server-side in the
2664
+ * same release this evidence check ships in.
2665
+ */
2666
+ getTransfer(apiKey: string, transferId: SaltId): Promise<{
2667
+ id: SaltId;
2668
+ status: string;
2669
+ [key: string]: unknown;
2670
+ }>;
125
2671
  /** The caller's own wallets. */
126
2672
  listWallets(apiKey: string): Promise<unknown[]>;
127
2673
  /** Upload an already-encrypted wallet under the caller's own identity (see crypto.ts). */
128
2674
  createWallet(apiKey: string, params: Record<string, unknown>): Promise<unknown>;
129
2675
  /** Hand a chat off to another agent; salt-api does the whole swap atomically. */
130
- handOff(apiKey: string, chatId: number | string, toAgentId: number | string, reason?: string): Promise<unknown>;
2676
+ handOff(apiKey: string, chatId: SaltId, toAgentId: SaltId, reason?: string): Promise<unknown>;
2677
+ /**
2678
+ * One step back: return the chat to whoever handed it TO the current
2679
+ * active agent most recently (a real hop off the trail, not a fixed
2680
+ * destination) -- salt-api's `Chat#hand_off_back!`. 422 with "Already
2681
+ * at the start of this conversation." when there's no previous hop;
2682
+ * callers (actions.ts's hand_back_to_concierge) fall back to something
2683
+ * fixed in that case. The acting agent must currently be this chat's
2684
+ * active agent (or a human member) -- salt-api's `gacm_actor_allowed?`.
2685
+ */
2686
+ handBack(apiKey: string, chatId: SaltId): Promise<unknown>;
2687
+ /**
2688
+ * React to a message with one emoji (`POST /api/v1/messages/:id/reactions`).
2689
+ * A TOGGLE: the same emoji again removes yours. Plaintext metadata, so no
2690
+ * crypto. Resolves with the server's `{message_id, reactions}` summary;
2691
+ * a 422 ("Pick a single emoji." / "You can react with up to 12 emoji.")
2692
+ * arrives as a SaltApiError carrying that sentence.
2693
+ *
2694
+ * The owner's rule for agents: react "not all the time, just when they
2695
+ * choose", and only "if it relevantly complements the chat in a friendly
2696
+ * way" -- a thanks acknowledged, a done request marked, good news
2697
+ * celebrated. Never instead of answering a question, never on every
2698
+ * message, never on your own.
2699
+ */
2700
+ react(apiKey: string, messageId: SaltId, emoji: string): Promise<ReactionSummary>;
2701
+ /** The emoji this identity has used, for picking one (`GET /api/v1/reactions/mine`). */
2702
+ myReactions(apiKey: string): Promise<unknown>;
2703
+ /**
2704
+ * Replies to a post: your reply becomes YOUR OWN post in your profile room, linked to the one it
2705
+ * answers -- the same single action a person has (`POST /api/v1/posts/:id/reply`). Resolves with
2706
+ * the new post's row. Refusals arrive as a SaltApiError carrying the server's sentence: 404 not a
2707
+ * visible post, 403 blocked, 422 empty or too long. Pass `idempotencyKey` to make a retry safe
2708
+ * (sent as `Idempotency-Key`).
2709
+ */
2710
+ replyToPost(apiKey: string, postId: SaltId, text: string, opts?: {
2711
+ idempotencyKey?: string;
2712
+ media?: PostMediaInput;
2713
+ }): Promise<PostRow>;
2714
+ /**
2715
+ * Posts to your own profile: text, a photo or video, or both -- the same single action a person has.
2716
+ * The room is made on first use (`POST /api/v1/profile_room`), then the post goes through
2717
+ * `POST /api/v1/messages`; media is plain and public (up to 15 MB; image/jpeg, png, webp, gif,
2718
+ * heic or video/mp4, webm, quicktime), so it is NOT encrypted like chat attachments. Resolves with
2719
+ * the new message envelope (its `media.url` is the public file). A refusal arrives as a SaltApiError
2720
+ * carrying the server's sentence.
2721
+ */
2722
+ postToProfile(apiKey: string, params: {
2723
+ text?: string;
2724
+ media?: PostMediaInput;
2725
+ }): Promise<{
2726
+ message_id: SaltId;
2727
+ media?: PostMediaInfo;
2728
+ }>;
2729
+ /** The replies to a post, newest page first; pass the last page's `next_before` as `before` for older ones
2730
+ * (`GET /api/v1/posts/:id/replies`). */
2731
+ getPostReplies(apiKey: string, postId: SaltId, opts?: {
2732
+ before?: string;
2733
+ limit?: number;
2734
+ }): Promise<PostRepliesPage>;
131
2735
  /** Ephemeral "is typing" ping. Fire-and-forget by design -- a failed ping must never block a reply. */
132
- signalTyping(apiKey: string, chatId: number | string): Promise<void>;
2736
+ signalTyping(apiKey: string, chatId: SaltId): Promise<void>;
133
2737
  /**
134
2738
  * Fire-and-forget product-metrics beacon. Deliberately not awaited by
135
2739
  * callers -- an analytics post must never delay or fail a reply. Only
136
2740
  * names in salt-api's Event::CLIENT_EVENT_NAMES allowlist are accepted.
137
2741
  */
138
2742
  trackEvent(apiKey: string, name: string, properties?: Record<string, unknown>): void;
2743
+ /**
2744
+ * Prepares a governed payment for approval -- `money.pay`'s mode is FORCED to `ask`, so this
2745
+ * always resolves to an `AskedResult`, never a Transfer, whether called through `actFor` or (in
2746
+ * the unusual case of preparing your own payment) as yourself. Once the exercise is approved
2747
+ * (see `mandates.decide`/`mandates.exercises`, or the `approval_decided` webhook event), settle
2748
+ * it with `transfers#create`'s own `exercise_id` param (not exposed as a typed method here yet --
2749
+ * salt-api validates the wallet/amount/destination against the exercise's summary before it will
2750
+ * create the Transfer).
2751
+ */
2752
+ prepareTransfer(apiKey: string, params: {
2753
+ walletId: SaltId;
2754
+ destinationAddress?: string;
2755
+ receiverId?: SaltId;
2756
+ amount: string | number;
2757
+ chain?: string;
2758
+ note?: string;
2759
+ chatId?: SaltId;
2760
+ }): Promise<AskedResult>;
2761
+ mandates: {
2762
+ /** Every mandate the caller is party to, as grantor and/or grantee. */
2763
+ list(apiKey: string, opts?: {
2764
+ role?: "grantor" | "grantee";
2765
+ status?: MandateStatus;
2766
+ }): Promise<{
2767
+ mandates: Mandate[];
2768
+ }>;
2769
+ get(apiKey: string, mandateId: SaltId): Promise<Mandate>;
2770
+ /** Grantor creates (a proposal the grantee must accept, or auto-active for an account the
2771
+ * caller governs), or a grantee proposes UP by passing `grantor_id` instead. */
2772
+ propose(apiKey: string, params: ProposeMandateParams): Promise<Mandate>;
2773
+ /** New version, compare-and-swap on `params.version` -- a 409 SaltApiError means it changed
2774
+ * under you; re-fetch with `get` and retry against the new version. */
2775
+ update(apiKey: string, mandateId: SaltId, params: UpdateMandateParams): Promise<Mandate>;
2776
+ /** Grantee accepts an offered mandate (binds its current key fingerprint), or the grantor
2777
+ * activates one still `proposed` by the grantee. See `ctx.mandate.accept()` in webhook.ts,
2778
+ * which calls this for an incoming `mandate_offered` event. */
2779
+ accept(apiKey: string, mandateId: SaltId): Promise<Mandate>;
2780
+ /** Standing mandates only: +90 days from now. */
2781
+ renew(apiKey: string, mandateId: SaltId): Promise<Mandate>;
2782
+ pause(apiKey: string, mandateId: SaltId, reason?: string): Promise<Mandate>;
2783
+ resume(apiKey: string, mandateId: SaltId): Promise<Mandate>;
2784
+ /** The grantor, or a governor of either side, can revoke -- cascades to any child mandates. */
2785
+ revoke(apiKey: string, mandateId: SaltId, reason?: string): Promise<Mandate>;
2786
+ /** A mandate's trail, id-descending. `refusal` is only populated when the caller is the
2787
+ * principal or its governor (salt-api's own viewer check -- this SDK doesn't re-derive it). */
2788
+ exercises(apiKey: string, mandateId: SaltId, opts?: {
2789
+ before?: string | number;
2790
+ limit?: number;
2791
+ }): Promise<{
2792
+ exercises: MandateExercise[];
2793
+ }>;
2794
+ /** Every open ask (decision `"asked"`, not yet expired) the caller can decide right now. */
2795
+ openExercises(apiKey: string): Promise<{
2796
+ exercises: MandateExercise[];
2797
+ }>;
2798
+ /** Approve or deny one open ask. See `ctx.approval.decide()` in webhook.ts, which calls this
2799
+ * for an incoming `approval_requested` event. */
2800
+ decide(apiKey: string, exerciseId: SaltId | number, decision: "approve" | "deny", note?: string): Promise<MandateExercise>;
2801
+ };
2802
+ teams: {
2803
+ /** The Teams shelf: listed templates, most installed first. Public. */
2804
+ list(apiKey: string, opts?: {
2805
+ q?: string;
2806
+ limit?: number;
2807
+ offset?: number;
2808
+ }): Promise<{
2809
+ teams: TeamTemplate[];
2810
+ total: number;
2811
+ }>;
2812
+ /** The templates this identity shared, listed or not. */
2813
+ mine(apiKey: string): Promise<{
2814
+ teams: TeamTemplate[];
2815
+ }>;
2816
+ /** A recipe preview, by id or slug. Public; an unlisted template is reachable by its link. */
2817
+ get(apiKey: string, idOrSlug: string): Promise<TeamTemplate>;
2818
+ /** Shares one of the caller's own agent groups as a template. */
2819
+ share(apiKey: string, params: ShareTeamParams): Promise<TeamTemplate & {
2820
+ left_out: number;
2821
+ }>;
2822
+ /** Author only: name, description, listed or unlisted, the coordinator note. */
2823
+ update(apiKey: string, idOrSlug: string, changes: Partial<Pick<ShareTeamParams, "name" | "description" | "listed" | "coordinatorNote">>): Promise<TeamTemplate>;
2824
+ /** Author only. Agents already made from it are the installers' own. */
2825
+ remove(apiKey: string, idOrSlug: string): Promise<void>;
2826
+ /** "I made this team": counted once per installer. */
2827
+ installed(apiKey: string, idOrSlug: string): Promise<{
2828
+ install_count: number;
2829
+ counted: boolean;
2830
+ }>;
2831
+ };
2832
+ apps: {
2833
+ /**
2834
+ * Creates an app + version 1. `forId` builds it for someone else (that
2835
+ * person must share a chat with the caller, else 422); omitted, the
2836
+ * app belongs to the caller. `chatId` also installs it there in the
2837
+ * same transaction (caller must be a member) -- see `installation` on
2838
+ * the result.
2839
+ */
2840
+ create(apiKey: string, params: CreateAppParams): Promise<CreateAppResult>;
2841
+ /**
2842
+ * Publishes a new immutable version (owner or builder only) and moves
2843
+ * the app's `latest_version_id` to it. `actions` (omitted: none)
2844
+ * replace whatever the PRIOR version declared -- they don't merge.
2845
+ * Never changes an existing installation on its own -- each keeps
2846
+ * running its own pinned version (and that version's own `actions`)
2847
+ * until it calls `update`, below.
2848
+ */
2849
+ newVersion(apiKey: string, appId: SaltId, html: string, actions?: AppQuickAction[]): Promise<{
2850
+ app: AppSummary;
2851
+ version: AppVersionRef;
2852
+ }>;
2853
+ /** An app's metadata: owner, builder, latest version -- and, once listed, what the App Store shows. */
2854
+ get(apiKey: string, appId: SaltId): Promise<AppSummary>;
2855
+ /**
2856
+ * Installs `appId` -- into `chatId` (caller must be a non-observer
2857
+ * member; a second active install of the same app there 409s with the
2858
+ * existing installation instead of creating a duplicate) or, with no
2859
+ * `chatId`, into the caller's own personal space ("Your apps").
2860
+ * `copyStateFrom` copies another readable installation's doc into the
2861
+ * new one once.
2862
+ */
2863
+ install(apiKey: string, appId: SaltId, params?: InstallAppParams): Promise<AppInstallation>;
2864
+ /**
2865
+ * This identity's installations: every one in `chatId` (chat members
2866
+ * only) when given, else its own personal installations plus every
2867
+ * chat installation in a chat it's a member of. Cache-busted -- an
2868
+ * install just made should show up immediately.
2869
+ */
2870
+ installations(apiKey: string, opts?: {
2871
+ chatId?: SaltId;
2872
+ }): Promise<AppInstallation[]>;
2873
+ /** The installation's shared state doc and its CAS version. Cache-busted -- a doc just written with `setState` should read back immediately. */
2874
+ state(apiKey: string, installationId: SaltId): Promise<AppStateDoc>;
2875
+ /**
2876
+ * Compare-and-swap write of the installation's shared state doc. A 409
2877
+ * (the doc moved under the caller since `ifVersion`) is never handed
2878
+ * back as a bare SaltApiError -- it's re-thrown as
2879
+ * AppStateConflictError carrying the CURRENT `{doc, version}` straight
2880
+ * off the response body, so a caller can merge its change onto the
2881
+ * fresh doc and retry with the new version in one more call rather
2882
+ * than round-tripping to re-fetch first.
2883
+ */
2884
+ setState(apiKey: string, installationId: SaltId, doc: Record<string, unknown>, ifVersion: number): Promise<AppStateDoc>;
2885
+ };
139
2886
  };
140
2887
  export type SaltClient = ReturnType<typeof createSaltClient>;
2888
+ export {};
141
2889
  //# sourceMappingURL=client.d.ts.map