@cerebro-labs/agentnava-sdk 0.26.0 → 0.46.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/README.md +212 -23
  2. package/account.d.ts +154 -0
  3. package/account.js +219 -0
  4. package/account.js.map +1 -0
  5. package/agent.d.ts +229 -87
  6. package/agent.js +310 -125
  7. package/agent.js.map +1 -1
  8. package/apps.generated.d.ts +154 -0
  9. package/apps.generated.js +152 -0
  10. package/apps.generated.js.map +1 -0
  11. package/attach.d.ts +25 -26
  12. package/attach.js +53 -66
  13. package/attach.js.map +1 -1
  14. package/connection.d.ts +79 -82
  15. package/connection.js +67 -134
  16. package/connection.js.map +1 -1
  17. package/conversation.d.ts +55 -35
  18. package/conversation.js +70 -50
  19. package/conversation.js.map +1 -1
  20. package/database.d.ts +53 -0
  21. package/database.js +69 -0
  22. package/database.js.map +1 -0
  23. package/end-user.d.ts +58 -47
  24. package/end-user.js +62 -72
  25. package/end-user.js.map +1 -1
  26. package/errors.d.ts +2 -1
  27. package/errors.js +2 -1
  28. package/errors.js.map +1 -1
  29. package/http.d.ts +5 -0
  30. package/http.js +109 -8
  31. package/http.js.map +1 -1
  32. package/import-tools.d.ts +54 -33
  33. package/import-tools.js +526 -124
  34. package/import-tools.js.map +1 -1
  35. package/index.d.ts +5 -2
  36. package/index.js +9 -2
  37. package/index.js.map +1 -1
  38. package/load-agent-dir.js +28 -5
  39. package/load-agent-dir.js.map +1 -1
  40. package/package.json +5 -1
  41. package/secret.d.ts +1 -1
  42. package/secret.js +1 -1
  43. package/secret.js.map +1 -1
  44. package/templates-inline.js +1 -1
  45. package/templates-inline.js.map +1 -1
  46. package/turn.d.ts +1 -1
  47. package/turn.js +77 -29
  48. package/turn.js.map +1 -1
  49. package/types.d.ts +462 -83
  50. package/types.js.map +1 -1
  51. package/version.d.ts +1 -1
  52. package/version.js +1 -1
  53. package/version.js.map +1 -1
  54. package/workflows.d.ts +10 -0
  55. package/workflows.js +16 -0
  56. package/workflows.js.map +1 -1
  57. package/workspace.d.ts +80 -62
  58. package/workspace.js +92 -75
  59. package/workspace.js.map +1 -1
  60. package/connection-status.d.ts +0 -28
  61. package/connection-status.js +0 -61
  62. package/connection-status.js.map +0 -1
package/agent.d.ts CHANGED
@@ -1,43 +1,34 @@
1
- import { type Attachable } from './attach.ts';
1
+ import { type Account } from './account.ts';
2
2
  import { Conversation } from './conversation.ts';
3
3
  import type { Http } from './http.ts';
4
- import type { AgentData, AgentFields, Bindings, ConnectionStatus, FileEntry, Trigger, Workflow } from './types.ts';
5
- /**
6
- * Turn the accounts a caller is holding into the `{ provider: id }` map the
7
- * wire speaks.
8
- *
9
- * Shared by `start`, `agent.attach` and `conversation.attach` so the three
10
- * cannot drift. A handle you created carries its provider already; one you
11
- * addressed by id has to read it, which is the only request here and only when
12
- * it is unavoidable.
13
- */
4
+ import type { Secret } from './secret.ts';
5
+ import { type Connection } from './connection.ts';
6
+ import type { AgentData, AgentFields, Bindings, ConnectionStatus, FileEntry, Trigger, WebsiteChat, Workflow, WebhookDelivery } from './types.ts';
14
7
  export interface StartOptions {
15
8
  /**
16
- * Accounts to attach AS THE CONVERSATION STARTS.
9
+ * Stored secrets to attach AS THE CONVERSATION STARTS.
17
10
  *
18
- * The common shape for an application with many end users: the person
19
- * connected Gmail once, and this thread should act as them from its first
20
- * turn.
11
+ * A `Secret` from `ws.endUser(id).secrets.create(...)` attaches by id, which
12
+ * is the only way a stored per-person credential can reach a turn: you never
13
+ * hold its value.
21
14
  *
22
15
  * ```ts
23
- * const gmail = await ws.endUser(row.userId).connections.list();
24
- * const c = await agent.start({ endUserId: row.userId, attach: gmail });
16
+ * const key = await ws.endUser(row.userId).secrets.create({ name: 'ZENDESK_TOKEN', value });
17
+ * const conversation = await agent.start({ endUserId: row.userId, attach: key });
25
18
  * ```
26
19
  *
27
20
  * Doing it here rather than with a second `conversation.attach()` call
28
21
  * closes a real window: between `start` and the attach, the conversation
29
- * exists and can be asked something, and it would answer without the
30
- * account.
31
- *
32
- * STILL EXPLICIT. Naming `endUserId` does not attach their accounts; this
33
- * line does, and you can read it.
34
- *
35
- * TAKES SECRETS TOO. A `Secret` from `ws.endUser(id).secrets.create(...)`
36
- * attaches by id, which is the only way a stored per-person credential can
37
- * reach a turn: you never hold its value. Mix them freely, they are split by
38
- * type before the launch.
22
+ * exists and can be asked something, and it would answer without it.
23
+ *
24
+ * ACCOUNTS ARE NOT ATTACHED HERE any more (0.44), and one handed in is
25
+ * refused. The agent's setup names the account each connection uses; for a
26
+ * connection to the end user's account (`Connection.endUserAccount`), the
27
+ * turn uses the account of the person this conversation is for, found by
28
+ * `endUserId`. Ask
29
+ * `conversation.accountsNeeded()` for the ones they have not connected yet.
39
30
  */
40
- attach?: Attachable | Attachable[];
31
+ attach?: Secret | Secret[];
41
32
  /**
42
33
  * Secret VALUES for this conversation only.
43
34
  *
@@ -49,24 +40,32 @@ export interface StartOptions {
49
40
  /** Pin a specific version. Defaults to the latest at the moment of starting.
50
41
  *
51
42
  * A conversation pins its version for life: publishing afterwards does not
52
- * move a conversation that is already open. */
43
+ * move a conversation that is already open. The one exception is a schedule
44
+ * or webhook that names the conversation (`conversationId`): each firing
45
+ * moves it to the latest version, so a fix you publish reaches it. */
53
46
  version?: number;
54
47
  parameters?: Record<string, string | number>;
55
48
  /**
56
49
  * WHO this conversation is for: one of YOUR users, not one of ours.
57
50
  *
58
- * REQUIRED, and nullable. You must say, and `null` is a real answer: a
59
- * nightly report belongs to nobody. Optional would let it be forgotten,
60
- * which is the mistake worth designing against, because a conversation
61
- * without an owner cannot be listed back to the person it was for.
62
- *
63
- * It is a LABEL on the conversation, and nothing else. It does not decide
64
- * which conversation you get, because `start` always starts a new one.
65
- * Later you can ask for everything belonging to that person:
51
+ * REQUIRED, and never null. Every conversation belongs to someone. The id
52
+ * is yours to choose: a user's row id, an anonymous visitor's random id, or
53
+ * a name for a job such as `'nightly-report'`. Only a non-empty string is
54
+ * accepted; anything else throws before a request is sent. (Decided
55
+ * 2026-09-26, AGE-161: a conversation with no owner cannot be listed back to
56
+ * the person it was for, and the accounts connected inside it could not be
57
+ * revoked with that person.)
58
+ *
59
+ * It LABELS the conversation, and it decides one thing more: a connection
60
+ * to the end user's account (`Connection.endUserAccount`) uses THIS
61
+ * person's account for that app.
62
+ * It does not decide which conversation you get, because `start` always
63
+ * starts a new one. Later you can ask for everything belonging to that
64
+ * person:
66
65
  *
67
66
  * ```ts
68
67
  * const conversation = await agent.start({ endUserId: row.userId });
69
- * const nobody = await agent.start({ endUserId: null }); // a nightly job
68
+ * const nightly = await agent.start({ endUserId: 'nightly-report' }); // a job has an id too
70
69
  * const theirs = await agent.conversations({ endUser: row.userId });
71
70
  * ```
72
71
  *
@@ -81,6 +80,13 @@ export interface StartOptions {
81
80
  * email and not something we authenticate. We never verify it, so it is
82
81
  * exactly as trustworthy as whatever holds your API key.
83
82
  *
83
+ * The platform's prefixes are refused with a 400 (`end_user_reserved`):
84
+ * `member:`, `visitor:` and `job:`, which name a member in the web app, an
85
+ * embed visitor and work nobody is typing, and the older `webhook:`,
86
+ * `builder:` and `librarian:`. Case and spacing do not matter to the check
87
+ * (`MEMBER:x` is refused too). An id you chose must never be mistaken for
88
+ * one of ours.
89
+ *
84
90
  * NOT A WORKSPACE MEMBER'S ID. `ws.members()` lists the colleagues who share
85
91
  * this account, and their `userId` is a platform id we issued. This field is
86
92
  * a different namespace entirely. Both were once spelled `userId`, so
@@ -88,7 +94,7 @@ export interface StartOptions {
88
94
  * for an end-user id to be checked against, so it just labelled the
89
95
  * conversation with a colleague's internal id and looked fine.
90
96
  */
91
- endUserId: string | null;
97
+ endUserId: string;
92
98
  }
93
99
  /**
94
100
  * An agent you can configure and start conversations from.
@@ -116,7 +122,9 @@ export declare class Agent {
116
122
  }): Promise<AgentData>;
117
123
  /** Every version, newest first. */
118
124
  versions(): Promise<AgentData[]>;
119
- /** Publish a new version with these fields changed and everything else kept. */
125
+ /** Publish a new version with these fields changed and everything else kept.
126
+ * `connections`, when sent, replaces the whole list; an `Account` in it goes
127
+ * over the wire as its id (`Account.toJSON`). */
120
128
  update(fields: AgentFields): Promise<Agent>;
121
129
  delete(): Promise<void>;
122
130
  /**
@@ -132,6 +140,73 @@ export declare class Agent {
132
140
  /** Replace the trigger list. Sending `[]` removes every trigger, including
133
141
  * `chat`, which is what leaves an agent that only runs on a schedule. */
134
142
  setTriggers(triggers: Trigger[]): Promise<Agent>;
143
+ /**
144
+ * Replace the webhook's URL and return the new one.
145
+ *
146
+ * The old URL stops working the moment this returns, so update the system
147
+ * that calls it. Use it when a URL leaked. The webhook's `message` and who it
148
+ * runs for are kept. Publishes a version. Throws `no_webhook` (409) when the
149
+ * agent has no webhook trigger.
150
+ */
151
+ rotateWebhookUrl(): Promise<string>;
152
+ /** What the webhook received, newest first: accepted, refused (and why), a
153
+ * sender's retry recognised as a repeat, or a test. Up to `limit` (200). */
154
+ webhookDeliveries(options?: {
155
+ limit?: number;
156
+ }): Promise<WebhookDelivery[]>;
157
+ /**
158
+ * Send the webhook a sample event and run the agent on it, the way a real
159
+ * delivery would (the signature check aside). A real turn, charged like
160
+ * one. Returns the conversation it ran in.
161
+ */
162
+ sendTestWebhook(): Promise<{
163
+ conversationId?: string;
164
+ }>;
165
+ /**
166
+ * The agent's website chat: a public chat page, and the embed script that
167
+ * puts it on your website. ON from the moment the agent is created, for
168
+ * every agent, however it was made (here, through the API, or in the portal),
169
+ * until you turn it off. The one exception is an agent that needs each
170
+ * user's own account or secret, which a visitor cannot bring: it has none.
171
+ *
172
+ * You never need it to use the agent from code: the agent answers your API
173
+ * key, and the portal's Test, as soon as it exists.
174
+ *
175
+ * Anyone with the page link can talk to the agent, and each of their turns is
176
+ * charged to your workspace, so turn it off for an agent only your own code
177
+ * should reach. `turnOn()` on a new agent changes nothing and hands back what
178
+ * to put on a website. The portal has no switch for it; its Website chat panel
179
+ * holds the share link and the allowed websites (`allowedWebsites` below).
180
+ * Publishing a newer version does not move the page: call `turnOn()` while it
181
+ * is on to serve the newer one. Turned off and on again, it serves the
182
+ * version it served before.
183
+ */
184
+ readonly websiteChat: {
185
+ /** Turn it on and get what to put on a website. The first time, and when
186
+ * it is already on, the page serves the current version; after
187
+ * `turnOff()`, the version it served before. Refused (400) for an agent
188
+ * that needs each user's own account or secret, which a visitor cannot
189
+ * bring. */
190
+ turnOn: () => Promise<WebsiteChat>;
191
+ /** Turn it off. Visitors are refused from the next message, follow-ups a
192
+ * visitor's conversation booked are cancelled, and nothing more is
193
+ * charged for them. The agent keeps answering your API key, its schedules
194
+ * and its webhooks. The publishable token is kept, so turning it on again
195
+ * revives a snippet already on a website. Turning off a chat that is
196
+ * already off is not an error. */
197
+ turnOff: () => Promise<void>;
198
+ /** The websites the chat may be shown on through the embed script. A
199
+ * domain covers its subdomains; an empty list means no other website.
200
+ * The share link opened directly always works. */
201
+ allowedWebsites: {
202
+ /** The list as it stands. */
203
+ get: () => Promise<string[]>;
204
+ /** Replace the list. Each entry is normalised ("https://acme.com/page"
205
+ * becomes "acme.com"; "localhost" is allowed for testing), and one that
206
+ * is not a website is refused (400). Returns the list as stored. */
207
+ set: (websites: string[]) => Promise<string[]>;
208
+ };
209
+ };
135
210
  /**
136
211
  * The agent's knowledge: files every conversation can read.
137
212
  *
@@ -143,6 +218,10 @@ export declare class Agent {
143
218
  */
144
219
  readonly knowledge: {
145
220
  put: (path: string, bytes: Uint8Array | ArrayBuffer, contentType?: string) => Promise<FileEntry>;
221
+ /** Read an agent-owned knowledge file as bytes, without starting a turn. */
222
+ get: (path: string) => Promise<Uint8Array>;
223
+ /** Read UTF-8 text. JSON files remain text rather than being parsed. */
224
+ text: (path: string) => Promise<string>;
146
225
  list: (path?: string) => Promise<FileEntry[]>;
147
226
  remove: (path: string) => Promise<void>;
148
227
  };
@@ -183,63 +262,128 @@ export declare class Agent {
183
262
  list: () => Promise<string[]>;
184
263
  };
185
264
  /**
186
- * Give this agent a credential, so every conversation with it can use one.
265
+ * The WORKSPACE databases this agent may query, by key.
266
+ *
267
+ * Register a database first with `ws.databases.create()`, which gives it its
268
+ * key; attaching names that key in this agent's configuration. Naming it is
269
+ * the grant: the database answers this agent's `sql_*` calls and no agent
270
+ * that does not name it, and the agent's prompt lists it by key.
271
+ *
272
+ * ATTACHING PUBLISHES A VERSION, like a knowledge base: which databases an
273
+ * agent reaches is part of its configuration, so the change is diffable and
274
+ * reversible. Detaching takes effect on the agent's next query, including in
275
+ * a conversation already under way.
276
+ */
277
+ readonly databases: {
278
+ /** Attach one database by key. The server appends, so two attaches cannot
279
+ * lose each other. A key that names no database is refused; one already
280
+ * attached is a no-op. */
281
+ attach: (key: string) => Promise<Agent>;
282
+ /** Stop this agent querying one database. The database stays registered
283
+ * for every other agent that names it. */
284
+ detach: (key: string) => Promise<Agent>;
285
+ /** The keys this agent may query, in configuration order. */
286
+ list: () => Promise<string[]>;
287
+ };
288
+ /**
289
+ * The apps this agent uses: each a NAMED connection that says WHOSE ACCOUNT
290
+ * it uses, built the way the portal's Add a connection screen asks.
291
+ *
292
+ * | built with | whose account | the person signs in |
293
+ * |---|---|---|
294
+ * | `Connection.creatorAccount(app, account)` | the agent creator's, from `ws.accounts.connect()` | once, and every conversation uses it |
295
+ * | `Connection.endUserAccount(app)` | the end user's: each person talking to the agent | once each, remembered for them |
187
296
  *
188
297
  * ```ts
189
- * await agent.attach(gmail); // one account
190
- * await agent.attach([gmail, calendar]); // several
191
- * await agent.attach({ secrets: { KEY: value } }); // a secret VALUE
298
+ * const { account: work } = await ws.accounts.connect({ app: App.GMAIL, nickname: 'Work' });
299
+ * await agent.connections.add(Connection.creatorAccount(App.GMAIL, work, { name: 'work-gmail' }));
300
+ * await agent.connections.add(Connection.endUserAccount(App.GOOGLE_CALENDAR));
192
301
  * ```
193
302
  *
194
- * ONE VERB, NOT TWO. This and `bind` used to both exist and do the same job
195
- * through different shapes, which is how a surface becomes hard to learn:
196
- * two names for one idea means every reader has to work out whether the
197
- * difference is meaningful. It is not, so `bind` is gone.
198
- *
199
- * "Attach" rather than "bind" because it is the plainer word, it is the one
200
- * that pairs with `detach`, and `knowledgeBases.attach/detach` already uses
201
- * it for the same shape of operation.
303
+ * A turn uses EXACTLY the account the setup names, and never another one.
304
+ * There is no fallback to whichever account of that app happens to exist:
305
+ * that fallback is what this replaced, and it meant reading an agent's code
306
+ * told you nothing about whose mailbox it read.
202
307
  *
203
- * ACCOUNTS TAKE THE OBJECT, SECRETS TAKE A VALUE, and that asymmetry is in
204
- * the product rather than in the naming. An account has an id you can hand
205
- * over; a secret does not, because binding one means supplying the value
206
- * itself. Hiding that behind one shape would not make it go away.
308
+ * EVERY CHANGE PUBLISHES A VERSION, like `databases`: which accounts an
309
+ * agent reaches is part of its configuration, so the change is diffable and
310
+ * reversible. A conversation uses the connections of the version it runs.
311
+ * The ACCOUNTS are not versioned: signing one in again reaches every agent
312
+ * that names it, from the next turn.
207
313
  *
208
- * The counterpart to `authorize()`, which creates an account AND attaches it
209
- * in one step. Use this when the account already exists: one team mailbox
210
- * connected once and attached to nine agents, rather than nine sign-ins.
314
+ * Removing a connection never touches the account. Disconnecting an account
315
+ * is the other operation, on the account: `account.disconnect()`.
211
316
  */
212
- attach(what: Attachable | Attachable[] | Bindings): Promise<Agent>;
213
- /** What each declared provider's actual state is: DECLARED IS NOT CONNECTED.
214
- *
215
- * An agent can require Gmail with nobody signed in, and that is the state a
216
- * setup checklist has to show. Without this the only way to find the gap is
217
- * to start a conversation and watch the agent fail. */
218
- connections(): Promise<ConnectionStatus[]>;
317
+ readonly connections: {
318
+ /**
319
+ * Add one connection. One call that publishes, not read-modify-write, so
320
+ * two adds racing cannot lose each other.
321
+ *
322
+ * Takes what `Connection.creatorAccount()` or `Connection.endUserAccount()`
323
+ * builds, or the same plain object. Refused, naming the problem, when the
324
+ * name is taken on this agent (`connection_name_taken`), when the account
325
+ * is of a different app (`account_app_mismatch`), or when no account has
326
+ * that id (`account_not_found`).
327
+ */
328
+ add: (connection: Connection) => Promise<Agent>;
329
+ /**
330
+ * Point one connection at another account: `{ account }` for the agent
331
+ * creator's (the `Account` you hold, or its id), or `{ endUser: true }`
332
+ * for each end user's own. The name and the app stay; an account of a
333
+ * different app is refused (`account_app_mismatch`).
334
+ *
335
+ * READ-MODIFY-WRITE, unlike `add` and `remove`, because there is no
336
+ * route that changes one connection in place: this reads the current
337
+ * version and publishes it again with the one connection changed. Two
338
+ * changes racing can therefore lose one, so make them one after the
339
+ * other. A name the agent does not have throws
340
+ * (`connection_not_found`) before anything is sent.
341
+ */
342
+ change: (name: string, change: {
343
+ account: Account | string;
344
+ endUser?: never;
345
+ } | {
346
+ endUser: true;
347
+ account?: never;
348
+ }) => Promise<Agent>;
349
+ /** Remove one connection by name. Publishes a version. NEVER touches the
350
+ * account: it stays connected for every other agent that names it, and
351
+ * for this one if you add it back. */
352
+ remove: (name: string) => Promise<Agent>;
353
+ /**
354
+ * Each connection with the account it uses and whether that account works
355
+ * now: DECLARED IS NOT CONNECTED. An agent can name an account nobody has
356
+ * finished signing in to, or one that was disconnected since, and that is
357
+ * the state a setup checklist has to show.
358
+ *
359
+ * Includes what an unpublished draft adds (`inDraft`) and removes
360
+ * (`removedInDraft`), so it matches the portal.
361
+ */
362
+ list: () => Promise<ConnectionStatus[]>;
363
+ };
219
364
  /**
220
- * Attach a third-party account to this agent, at `fixed` scope: one account,
221
- * used by every conversation.
365
+ * Give this agent secret values, so every conversation with it can use them.
222
366
  *
223
- * Returns a url to send a person to. They sign in there, in their own
224
- * browser, and the account attaches when they finish. YOU NEVER SEE A TOKEN,
225
- * which is the entire point: nobody should be pasting a Gmail credential into
226
- * a config field.
367
+ * ```ts
368
+ * await agent.attach({ secrets: { APOLLO_API_KEY: value } });
369
+ * ```
227
370
  *
228
- * This does NOT satisfy a declaration scoped 'conversation'. Use
229
- * `conversation.authorize()` for those, which attaches the account to that
230
- * one conversation and cannot overwrite this shared one.
371
+ * SECRETS ONLY, since 0.44. Accounts used to be attached here, and a turn
372
+ * then had to guess which of the accounts attached and owned around it to
373
+ * use. The agent's setup now names them: `agent.connections.add()`. One
374
+ * handed in here is refused by name (`not_a_secret`) rather than dropped.
375
+ *
376
+ * A stored `Secret` is not attached to an agent either. It never was: one
377
+ * was accepted here and nothing was done with it. Store one for the agent
378
+ * with `ws.secrets.create({ name, value, agentId })`, or attach one of your
379
+ * end user's to their conversation with `conversation.attach(secret)`.
231
380
  */
232
- authorize(provider: string): Promise<{
233
- url: string;
234
- connectionId: string;
235
- }>;
236
- /** Detach the account connected for this provider.
381
+ attach(bindings: Bindings): Promise<Agent>;
382
+ /** Start a conversation. It pins the agent's version now and keeps it.
237
383
  *
238
- * Reads the raw ROWS rather than the merged status, because revoking needs
239
- * the connection's id and the merged view is keyed by provider: it answers
240
- * "is Gmail connected", not "which row is it". */
241
- disconnect(provider: string): Promise<void>;
242
- /** Start a conversation. It pins the agent's version now and keeps it. */
384
+ * Nothing about accounts is passed here: the version's connections name
385
+ * them, and a connection to the end user's account uses the account of
386
+ * the person named by `endUserId`. */
243
387
  start(options: StartOptions): Promise<Conversation>;
244
388
  /**
245
389
  * Address one of this agent's conversations by id. No request.
@@ -281,5 +425,3 @@ export declare class Agent {
281
425
  endUser?: string;
282
426
  }): Promise<Conversation[]>;
283
427
  }
284
- /** A connection row as `GET /v1/connections` returns it (toConnectionMeta in
285
- * apps/api/src/connections/store.ts). */