@cerebro-labs/agentnava-sdk 0.26.0 → 0.44.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 +204 -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 +217 -88
  6. package/agent.js +295 -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 +48 -33
  33. package/import-tools.js +281 -127
  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 +409 -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/account.js ADDED
@@ -0,0 +1,219 @@
1
+ // An account: one saved sign-in to one app.
2
+ import { AgentNavaError } from "./errors.js";
3
+ /**
4
+ * One saved sign-in to one app, that you hold and act on.
5
+ *
6
+ * AN ACCOUNT IS NOT A CONNECTION, and the two words used to be one, which is
7
+ * how an agent came to own a Gmail login that nothing else could see. An
8
+ * ACCOUNT is a sign-in: Gmail, signed in as support@example.com. A CONNECTION is
9
+ * an entry in an agent's setup that names one: "this agent uses Gmail, through
10
+ * that account". Many agents can name one account, and reconnecting it once
11
+ * fixes all of them.
12
+ *
13
+ * ```ts
14
+ * const { account, url } = await ws.accounts.connect({ app: App.GMAIL, nickname: 'Support inbox' });
15
+ * showToUser(url); // they sign in on Google's own page
16
+ * await account.waitUntilConnected();
17
+ * await agent.connections.add(Connection.creatorAccount(App.GMAIL, account));
18
+ * ```
19
+ *
20
+ * Nobody handles a token, you included: the person signs in on the app's own
21
+ * page, and this object says who they signed in as, never how.
22
+ *
23
+ * ONE KIND OF OBJECT FOR BOTH OWNERS. The agent creator's account and one of
24
+ * your end users' are the same class, reached by the same routes: every method
25
+ * here works on any account in the workspace, and `data.owner` says whose it
26
+ * is. So `ws.account(id).waitUntilConnected()` is how you wait for an end user
27
+ * to finish signing in, too.
28
+ *
29
+ * A CLASS, like `Database` and `Secret`: you hold it and act on it, rather than
30
+ * threading an id through calls. Holding one makes no request. `ws.account(id)`
31
+ * is free and the first call on it is the first round trip.
32
+ */
33
+ export class Account {
34
+ id;
35
+ #http;
36
+ snapshot;
37
+ constructor(http, id, snapshot) {
38
+ this.#http = http;
39
+ this.id = id;
40
+ if (snapshot)
41
+ this.snapshot = snapshot;
42
+ }
43
+ get base() {
44
+ return `/v1/accounts/${encodeURIComponent(this.id)}`;
45
+ }
46
+ /** The app's catalog id: `'gmail'`, `'hubspot'`. Named for what the person
47
+ * signing in recognises, never for whoever we buy the sign-in from. */
48
+ get app() {
49
+ return this.snapshot?.app;
50
+ }
51
+ /** Who it is signed in as: the email or username the app reports. Null until
52
+ * somebody has finished signing in. */
53
+ get label() {
54
+ return this.snapshot?.label;
55
+ }
56
+ /** Your own name for it, such as `'Support inbox'`. */
57
+ get nickname() {
58
+ return this.snapshot?.nickname;
59
+ }
60
+ /** Where the sign-in stands, from the last read: `'pending'`, `'connected'`,
61
+ * `'needs_sign_in'` or `'error'`.
62
+ *
63
+ * Reads the snapshot rather than the network. Undefined means this handle
64
+ * has not read yet, NOT that the account is disconnected: call `get()`. */
65
+ get status() {
66
+ return this.snapshot?.status;
67
+ }
68
+ /** The agents whose setup names this account, from the last read. The
69
+ * question to ask before disconnecting it: who loses it? */
70
+ get usedBy() {
71
+ return this.snapshot?.usedBy;
72
+ }
73
+ /** The last-known record, from whatever call produced this object. */
74
+ get data() {
75
+ return this.snapshot;
76
+ }
77
+ /**
78
+ * An account goes over the wire as its id, the way a `Date` goes as its ISO
79
+ * string: `JSON.stringify({ account })` sends `"acc_..."`.
80
+ *
81
+ * That is what lets a connection written as a plain object,
82
+ * `{ app, account }`, take the Account you are holding in
83
+ * `ws.agents.create()` and `agent.update()`. It also matches the rule for
84
+ * every handle in this SDK: store the id, not the object.
85
+ */
86
+ toJSON() {
87
+ return this.id;
88
+ }
89
+ /**
90
+ * Read it back.
91
+ *
92
+ * A MISSING ACCOUNT THROWS (`account_not_found`, 404), and never comes back
93
+ * as an empty record. An account that does not exist and one that is not
94
+ * signed in yet are different answers. The connection this replaced once
95
+ * answered a miss with a made-up blank, so storing an id and reading it back
96
+ * gave you something that did not know what it was.
97
+ */
98
+ async get() {
99
+ const res = await this.#http.get(this.base);
100
+ this.snapshot = accountIn(res, this.base);
101
+ return this.snapshot;
102
+ }
103
+ /**
104
+ * Wait until the person has finished signing in.
105
+ *
106
+ * The companion to `ws.accounts.connect()`, `ws.endUser(id).accounts
107
+ * .connect()` and `reconnect()`. Those hand you a url; this tells you when
108
+ * the person is done, so you do not write a polling loop or guess which
109
+ * field to believe.
110
+ *
111
+ * DONE MEANS CONNECTED AND NOT RECONNECTING. During a reconnect the account
112
+ * keeps working (`status` stays `'connected'`) while `reconnecting` is true,
113
+ * so a check on `status` alone would return before the person had signed in
114
+ * again. This waits for both.
115
+ *
116
+ * THROWS on the deadline (`account_not_connected`), naming the account and
117
+ * where it stands, rather than returning an account that is still pending.
118
+ * Safe to call again: somebody who has not finished may just need longer.
119
+ *
120
+ * Polls rather than subscribing: there is a person in the middle, so a few
121
+ * seconds costs nothing, and it works from any runtime without a socket.
122
+ */
123
+ async waitUntilConnected(options = {}) {
124
+ const timeoutMs = options.timeoutMs ?? 120_000;
125
+ const intervalMs = options.intervalMs ?? 2_000;
126
+ const deadline = Date.now() + timeoutMs;
127
+ // Checked before the first sleep: an account that is already connected
128
+ // should cost one request, not one request plus an interval.
129
+ let state = await this.get();
130
+ while (state.status !== 'connected' || state.reconnecting) {
131
+ if (Date.now() >= deadline) {
132
+ throw new AgentNavaError({
133
+ message: `Account ${this.id} (${state.app}) was not connected within ${timeoutMs}ms; it is still ${state.status}${state.reconnecting ? ', with a reconnect waiting for the person' : ''}`,
134
+ code: 'account_not_connected',
135
+ status: 0,
136
+ });
137
+ }
138
+ await new Promise((r) => setTimeout(r, Math.min(intervalMs, Math.max(0, deadline - Date.now()))));
139
+ state = await this.get();
140
+ }
141
+ return this;
142
+ }
143
+ /**
144
+ * Send the person to sign in again, and get the link.
145
+ *
146
+ * What to call when an account `needs_sign_in`. Every agent that names it
147
+ * follows, so reconnecting once fixes all of them. The sign-in that worked is
148
+ * kept until the new one succeeds, so an abandoned link breaks nothing:
149
+ * meanwhile `data.reconnecting` is true, and `waitUntilConnected()` waits
150
+ * for it to clear.
151
+ *
152
+ * If the person signs in as SOMEBODY ELSE, that becomes a NEW account and
153
+ * this one is left as it was, so no agent switches mailbox without anyone
154
+ * deciding it should. List accounts to find the new one, then change the
155
+ * connections that should use it.
156
+ */
157
+ async reconnect() {
158
+ const res = await this.#http.post(`${this.base}/reconnect`);
159
+ return { url: res.url };
160
+ }
161
+ /** Give it your own name, such as `'Support inbox'`, shown beside who it is
162
+ * signed in as. The app and the sign-in do not change. */
163
+ async rename(nickname) {
164
+ const res = await this.#http.patch(this.base, { nickname });
165
+ this.snapshot = accountIn(res, this.base);
166
+ return this;
167
+ }
168
+ /** Ask the app whether the sign-in still works, and read the answer. A
169
+ * revoked sign-in comes back as `needs_sign_in`, not as an exception: it is
170
+ * the answer this call exists to get. */
171
+ async check() {
172
+ const res = await this.#http.post(`${this.base}/check`);
173
+ this.snapshot = accountIn(res, `${this.base}/check`);
174
+ return this.snapshot;
175
+ }
176
+ /**
177
+ * Sign it out at the app and remove it here.
178
+ *
179
+ * ALWAYS ALLOWED, unlike deleting a database that an agent still names. It
180
+ * returns the agents whose setup names the account, so you can tell whoever
181
+ * owns them: from their next turn those connections report that the account
182
+ * was disconnected. Nothing switches to another account in its place.
183
+ *
184
+ * Removing a connection from an agent is the other thing, and it never
185
+ * touches the account: `agent.connections.remove(name)`.
186
+ */
187
+ async disconnect() {
188
+ // The same route for either owner. An end user's account is never named
189
+ // by an agent's setup (that says `endUser: true`), so for one of those
190
+ // the list of agents is empty, which is the true answer.
191
+ const res = await this.#http.delete(this.base);
192
+ return { agents: res?.agents ?? [] };
193
+ }
194
+ }
195
+ /**
196
+ * The account a response carries, or a thrown error. Never a blank.
197
+ *
198
+ * A 404 already throws in the transport, with the API's `account_not_found`.
199
+ * This covers the other way to end up with nothing: a 200 whose body has no
200
+ * account in it, which would otherwise become a snapshot of `undefined` that
201
+ * reads, at every call site, exactly like an account nobody has read yet.
202
+ */
203
+ export function accountIn(res, path) {
204
+ const account = res?.account;
205
+ if (!account || typeof account.id !== 'string') {
206
+ throw new AgentNavaError({
207
+ message: `Expected an account from ${path}, got: ${JSON.stringify(res ?? null).slice(0, 200)}`,
208
+ code: 'bad_response',
209
+ status: 200,
210
+ });
211
+ }
212
+ return account;
213
+ }
214
+ /** The id a connection sends for its account: an `Account` by its id, an id
215
+ * as it is. */
216
+ export function accountIdOf(account) {
217
+ return typeof account === 'string' ? account : account?.id;
218
+ }
219
+ //# sourceMappingURL=account.js.map
package/account.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"account.js","sourceRoot":"","sources":["../src/account.ts"],"names":[],"mappings":"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","sourcesContent":["// An account: one saved sign-in to one app.\n\nimport { AgentNavaError } from './errors.ts';\nimport type { Http } from './http.ts';\nimport type { AccountData, AccountStatus, UsedBy } from './types.ts';\n\n/**\n * One saved sign-in to one app, that you hold and act on.\n *\n * AN ACCOUNT IS NOT A CONNECTION, and the two words used to be one, which is\n * how an agent came to own a Gmail login that nothing else could see. An\n * ACCOUNT is a sign-in: Gmail, signed in as support@example.com. A CONNECTION is\n * an entry in an agent's setup that names one: \"this agent uses Gmail, through\n * that account\". Many agents can name one account, and reconnecting it once\n * fixes all of them.\n *\n * ```ts\n * const { account, url } = await ws.accounts.connect({ app: App.GMAIL, nickname: 'Support inbox' });\n * showToUser(url); // they sign in on Google's own page\n * await account.waitUntilConnected();\n * await agent.connections.add(Connection.creatorAccount(App.GMAIL, account));\n * ```\n *\n * Nobody handles a token, you included: the person signs in on the app's own\n * page, and this object says who they signed in as, never how.\n *\n * ONE KIND OF OBJECT FOR BOTH OWNERS. The agent creator's account and one of\n * your end users' are the same class, reached by the same routes: every method\n * here works on any account in the workspace, and `data.owner` says whose it\n * is. So `ws.account(id).waitUntilConnected()` is how you wait for an end user\n * to finish signing in, too.\n *\n * A CLASS, like `Database` and `Secret`: you hold it and act on it, rather than\n * threading an id through calls. Holding one makes no request. `ws.account(id)`\n * is free and the first call on it is the first round trip.\n */\nexport class Account {\n\treadonly id: string;\n\treadonly #http: Http;\n\tprivate snapshot?: AccountData;\n\n\tconstructor(http: Http, id: string, snapshot?: AccountData) {\n\t\tthis.#http = http;\n\t\tthis.id = id;\n\t\tif (snapshot) this.snapshot = snapshot;\n\t}\n\n\tprivate get base(): string {\n\t\treturn `/v1/accounts/${encodeURIComponent(this.id)}`;\n\t}\n\n\t/** The app's catalog id: `'gmail'`, `'hubspot'`. Named for what the person\n\t * signing in recognises, never for whoever we buy the sign-in from. */\n\tget app(): string | undefined {\n\t\treturn this.snapshot?.app;\n\t}\n\n\t/** Who it is signed in as: the email or username the app reports. Null until\n\t * somebody has finished signing in. */\n\tget label(): string | null | undefined {\n\t\treturn this.snapshot?.label;\n\t}\n\n\t/** Your own name for it, such as `'Support inbox'`. */\n\tget nickname(): string | null | undefined {\n\t\treturn this.snapshot?.nickname;\n\t}\n\n\t/** Where the sign-in stands, from the last read: `'pending'`, `'connected'`,\n\t * `'needs_sign_in'` or `'error'`.\n\t *\n\t * Reads the snapshot rather than the network. Undefined means this handle\n\t * has not read yet, NOT that the account is disconnected: call `get()`. */\n\tget status(): AccountStatus | undefined {\n\t\treturn this.snapshot?.status;\n\t}\n\n\t/** The agents whose setup names this account, from the last read. The\n\t * question to ask before disconnecting it: who loses it? */\n\tget usedBy(): UsedBy[] | undefined {\n\t\treturn this.snapshot?.usedBy;\n\t}\n\n\t/** The last-known record, from whatever call produced this object. */\n\tget data(): AccountData | undefined {\n\t\treturn this.snapshot;\n\t}\n\n\t/**\n\t * An account goes over the wire as its id, the way a `Date` goes as its ISO\n\t * string: `JSON.stringify({ account })` sends `\"acc_...\"`.\n\t *\n\t * That is what lets a connection written as a plain object,\n\t * `{ app, account }`, take the Account you are holding in\n\t * `ws.agents.create()` and `agent.update()`. It also matches the rule for\n\t * every handle in this SDK: store the id, not the object.\n\t */\n\ttoJSON(): string {\n\t\treturn this.id;\n\t}\n\n\t/**\n\t * Read it back.\n\t *\n\t * A MISSING ACCOUNT THROWS (`account_not_found`, 404), and never comes back\n\t * as an empty record. An account that does not exist and one that is not\n\t * signed in yet are different answers. The connection this replaced once\n\t * answered a miss with a made-up blank, so storing an id and reading it back\n\t * gave you something that did not know what it was.\n\t */\n\tasync get(): Promise<AccountData> {\n\t\tconst res = await this.#http.get<{ account?: AccountData }>(this.base);\n\t\tthis.snapshot = accountIn(res, this.base);\n\t\treturn this.snapshot;\n\t}\n\n\t/**\n\t * Wait until the person has finished signing in.\n\t *\n\t * The companion to `ws.accounts.connect()`, `ws.endUser(id).accounts\n\t * .connect()` and `reconnect()`. Those hand you a url; this tells you when\n\t * the person is done, so you do not write a polling loop or guess which\n\t * field to believe.\n\t *\n\t * DONE MEANS CONNECTED AND NOT RECONNECTING. During a reconnect the account\n\t * keeps working (`status` stays `'connected'`) while `reconnecting` is true,\n\t * so a check on `status` alone would return before the person had signed in\n\t * again. This waits for both.\n\t *\n\t * THROWS on the deadline (`account_not_connected`), naming the account and\n\t * where it stands, rather than returning an account that is still pending.\n\t * Safe to call again: somebody who has not finished may just need longer.\n\t *\n\t * Polls rather than subscribing: there is a person in the middle, so a few\n\t * seconds costs nothing, and it works from any runtime without a socket.\n\t */\n\tasync waitUntilConnected(options: { timeoutMs?: number; intervalMs?: number } = {}): Promise<Account> {\n\t\tconst timeoutMs = options.timeoutMs ?? 120_000;\n\t\tconst intervalMs = options.intervalMs ?? 2_000;\n\t\tconst deadline = Date.now() + timeoutMs;\n\t\t// Checked before the first sleep: an account that is already connected\n\t\t// should cost one request, not one request plus an interval.\n\t\tlet state = await this.get();\n\t\twhile (state.status !== 'connected' || state.reconnecting) {\n\t\t\tif (Date.now() >= deadline) {\n\t\t\t\tthrow new AgentNavaError({\n\t\t\t\t\tmessage: `Account ${this.id} (${state.app}) was not connected within ${timeoutMs}ms; it is still ${state.status}${\n\t\t\t\t\t\tstate.reconnecting ? ', with a reconnect waiting for the person' : ''\n\t\t\t\t\t}`,\n\t\t\t\t\tcode: 'account_not_connected',\n\t\t\t\t\tstatus: 0,\n\t\t\t\t});\n\t\t\t}\n\t\t\tawait new Promise((r) => setTimeout(r, Math.min(intervalMs, Math.max(0, deadline - Date.now()))));\n\t\t\tstate = await this.get();\n\t\t}\n\t\treturn this;\n\t}\n\n\t/**\n\t * Send the person to sign in again, and get the link.\n\t *\n\t * What to call when an account `needs_sign_in`. Every agent that names it\n\t * follows, so reconnecting once fixes all of them. The sign-in that worked is\n\t * kept until the new one succeeds, so an abandoned link breaks nothing:\n\t * meanwhile `data.reconnecting` is true, and `waitUntilConnected()` waits\n\t * for it to clear.\n\t *\n\t * If the person signs in as SOMEBODY ELSE, that becomes a NEW account and\n\t * this one is left as it was, so no agent switches mailbox without anyone\n\t * deciding it should. List accounts to find the new one, then change the\n\t * connections that should use it.\n\t */\n\tasync reconnect(): Promise<{ url: string }> {\n\t\tconst res = await this.#http.post<{ url: string }>(`${this.base}/reconnect`);\n\t\treturn { url: res.url };\n\t}\n\n\t/** Give it your own name, such as `'Support inbox'`, shown beside who it is\n\t * signed in as. The app and the sign-in do not change. */\n\tasync rename(nickname: string): Promise<Account> {\n\t\tconst res = await this.#http.patch<{ account?: AccountData }>(this.base, { nickname });\n\t\tthis.snapshot = accountIn(res, this.base);\n\t\treturn this;\n\t}\n\n\t/** Ask the app whether the sign-in still works, and read the answer. A\n\t * revoked sign-in comes back as `needs_sign_in`, not as an exception: it is\n\t * the answer this call exists to get. */\n\tasync check(): Promise<AccountData> {\n\t\tconst res = await this.#http.post<{ account?: AccountData }>(`${this.base}/check`);\n\t\tthis.snapshot = accountIn(res, `${this.base}/check`);\n\t\treturn this.snapshot;\n\t}\n\n\t/**\n\t * Sign it out at the app and remove it here.\n\t *\n\t * ALWAYS ALLOWED, unlike deleting a database that an agent still names. It\n\t * returns the agents whose setup names the account, so you can tell whoever\n\t * owns them: from their next turn those connections report that the account\n\t * was disconnected. Nothing switches to another account in its place.\n\t *\n\t * Removing a connection from an agent is the other thing, and it never\n\t * touches the account: `agent.connections.remove(name)`.\n\t */\n\tasync disconnect(): Promise<{ agents: UsedBy[] }> {\n\t\t// The same route for either owner. An end user's account is never named\n\t\t// by an agent's setup (that says `endUser: true`), so for one of those\n\t\t// the list of agents is empty, which is the true answer.\n\t\tconst res = await this.#http.delete<{ disconnected?: boolean; agents?: UsedBy[] }>(this.base);\n\t\treturn { agents: res?.agents ?? [] };\n\t}\n}\n\n/**\n * The account a response carries, or a thrown error. Never a blank.\n *\n * A 404 already throws in the transport, with the API's `account_not_found`.\n * This covers the other way to end up with nothing: a 200 whose body has no\n * account in it, which would otherwise become a snapshot of `undefined` that\n * reads, at every call site, exactly like an account nobody has read yet.\n */\nexport function accountIn(res: { account?: AccountData } | undefined, path: string): AccountData {\n\tconst account = res?.account;\n\tif (!account || typeof account.id !== 'string') {\n\t\tthrow new AgentNavaError({\n\t\t\tmessage: `Expected an account from ${path}, got: ${JSON.stringify(res ?? null).slice(0, 200)}`,\n\t\t\tcode: 'bad_response',\n\t\t\tstatus: 200,\n\t\t});\n\t}\n\treturn account;\n}\n\n/** The id a connection sends for its account: an `Account` by its id, an id\n * as it is. */\nexport function accountIdOf(account: Account | string): string {\n\treturn typeof account === 'string' ? account : account?.id;\n}\n"]}
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 } 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,60 @@ 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
+ /**
153
+ * The agent's website chat: a public chat page, and the embed script that
154
+ * puts it on your website. ON from the moment the agent is created, for
155
+ * every agent, however it was made (here, through the API, or in the portal),
156
+ * until you turn it off. The one exception is an agent that needs each
157
+ * user's own account or secret, which a visitor cannot bring: it has none.
158
+ *
159
+ * You never need it to use the agent from code: the agent answers your API
160
+ * key, and the portal's Test, as soon as it exists.
161
+ *
162
+ * Anyone with the page link can talk to the agent, and each of their turns is
163
+ * charged to your workspace, so turn it off for an agent only your own code
164
+ * should reach. `turnOn()` on a new agent changes nothing and hands back what
165
+ * to put on a website. The portal has no switch for it; its Website chat panel
166
+ * holds the share link and the allowed websites (`allowedWebsites` below).
167
+ * Publishing a newer version does not move the page: call `turnOn()` while it
168
+ * is on to serve the newer one. Turned off and on again, it serves the
169
+ * version it served before.
170
+ */
171
+ readonly websiteChat: {
172
+ /** Turn it on and get what to put on a website. The first time, and when
173
+ * it is already on, the page serves the current version; after
174
+ * `turnOff()`, the version it served before. Refused (400) for an agent
175
+ * that needs each user's own account or secret, which a visitor cannot
176
+ * bring. */
177
+ turnOn: () => Promise<WebsiteChat>;
178
+ /** Turn it off. Visitors are refused from the next message, follow-ups a
179
+ * visitor's conversation booked are cancelled, and nothing more is
180
+ * charged for them. The agent keeps answering your API key, its schedules
181
+ * and its webhooks. The publishable token is kept, so turning it on again
182
+ * revives a snippet already on a website. Turning off a chat that is
183
+ * already off is not an error. */
184
+ turnOff: () => Promise<void>;
185
+ /** The websites the chat may be shown on through the embed script. A
186
+ * domain covers its subdomains; an empty list means no other website.
187
+ * The share link opened directly always works. */
188
+ allowedWebsites: {
189
+ /** The list as it stands. */
190
+ get: () => Promise<string[]>;
191
+ /** Replace the list. Each entry is normalised ("https://acme.com/page"
192
+ * becomes "acme.com"; "localhost" is allowed for testing), and one that
193
+ * is not a website is refused (400). Returns the list as stored. */
194
+ set: (websites: string[]) => Promise<string[]>;
195
+ };
196
+ };
135
197
  /**
136
198
  * The agent's knowledge: files every conversation can read.
137
199
  *
@@ -143,6 +205,10 @@ export declare class Agent {
143
205
  */
144
206
  readonly knowledge: {
145
207
  put: (path: string, bytes: Uint8Array | ArrayBuffer, contentType?: string) => Promise<FileEntry>;
208
+ /** Read an agent-owned knowledge file as bytes, without starting a turn. */
209
+ get: (path: string) => Promise<Uint8Array>;
210
+ /** Read UTF-8 text. JSON files remain text rather than being parsed. */
211
+ text: (path: string) => Promise<string>;
146
212
  list: (path?: string) => Promise<FileEntry[]>;
147
213
  remove: (path: string) => Promise<void>;
148
214
  };
@@ -183,63 +249,128 @@ export declare class Agent {
183
249
  list: () => Promise<string[]>;
184
250
  };
185
251
  /**
186
- * Give this agent a credential, so every conversation with it can use one.
252
+ * The WORKSPACE databases this agent may query, by key.
253
+ *
254
+ * Register a database first with `ws.databases.create()`, which gives it its
255
+ * key; attaching names that key in this agent's configuration. Naming it is
256
+ * the grant: the database answers this agent's `sql_*` calls and no agent
257
+ * that does not name it, and the agent's prompt lists it by key.
258
+ *
259
+ * ATTACHING PUBLISHES A VERSION, like a knowledge base: which databases an
260
+ * agent reaches is part of its configuration, so the change is diffable and
261
+ * reversible. Detaching takes effect on the agent's next query, including in
262
+ * a conversation already under way.
263
+ */
264
+ readonly databases: {
265
+ /** Attach one database by key. The server appends, so two attaches cannot
266
+ * lose each other. A key that names no database is refused; one already
267
+ * attached is a no-op. */
268
+ attach: (key: string) => Promise<Agent>;
269
+ /** Stop this agent querying one database. The database stays registered
270
+ * for every other agent that names it. */
271
+ detach: (key: string) => Promise<Agent>;
272
+ /** The keys this agent may query, in configuration order. */
273
+ list: () => Promise<string[]>;
274
+ };
275
+ /**
276
+ * The apps this agent uses: each a NAMED connection that says WHOSE ACCOUNT
277
+ * it uses, built the way the portal's Add a connection screen asks.
278
+ *
279
+ * | built with | whose account | the person signs in |
280
+ * |---|---|---|
281
+ * | `Connection.creatorAccount(app, account)` | the agent creator's, from `ws.accounts.connect()` | once, and every conversation uses it |
282
+ * | `Connection.endUserAccount(app)` | the end user's: each person talking to the agent | once each, remembered for them |
187
283
  *
188
284
  * ```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
285
+ * const { account: work } = await ws.accounts.connect({ app: App.GMAIL, nickname: 'Work' });
286
+ * await agent.connections.add(Connection.creatorAccount(App.GMAIL, work, { name: 'work-gmail' }));
287
+ * await agent.connections.add(Connection.endUserAccount(App.GOOGLE_CALENDAR));
192
288
  * ```
193
289
  *
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.
290
+ * A turn uses EXACTLY the account the setup names, and never another one.
291
+ * There is no fallback to whichever account of that app happens to exist:
292
+ * that fallback is what this replaced, and it meant reading an agent's code
293
+ * told you nothing about whose mailbox it read.
198
294
  *
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.
295
+ * EVERY CHANGE PUBLISHES A VERSION, like `databases`: which accounts an
296
+ * agent reaches is part of its configuration, so the change is diffable and
297
+ * reversible. A conversation uses the connections of the version it runs.
298
+ * The ACCOUNTS are not versioned: signing one in again reaches every agent
299
+ * that names it, from the next turn.
202
300
  *
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.
207
- *
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.
301
+ * Removing a connection never touches the account. Disconnecting an account
302
+ * is the other operation, on the account: `account.disconnect()`.
211
303
  */
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[]>;
304
+ readonly connections: {
305
+ /**
306
+ * Add one connection. One call that publishes, not read-modify-write, so
307
+ * two adds racing cannot lose each other.
308
+ *
309
+ * Takes what `Connection.creatorAccount()` or `Connection.endUserAccount()`
310
+ * builds, or the same plain object. Refused, naming the problem, when the
311
+ * name is taken on this agent (`connection_name_taken`), when the account
312
+ * is of a different app (`account_app_mismatch`), or when no account has
313
+ * that id (`account_not_found`).
314
+ */
315
+ add: (connection: Connection) => Promise<Agent>;
316
+ /**
317
+ * Point one connection at another account: `{ account }` for the agent
318
+ * creator's (the `Account` you hold, or its id), or `{ endUser: true }`
319
+ * for each end user's own. The name and the app stay; an account of a
320
+ * different app is refused (`account_app_mismatch`).
321
+ *
322
+ * READ-MODIFY-WRITE, unlike `add` and `remove`, because there is no
323
+ * route that changes one connection in place: this reads the current
324
+ * version and publishes it again with the one connection changed. Two
325
+ * changes racing can therefore lose one, so make them one after the
326
+ * other. A name the agent does not have throws
327
+ * (`connection_not_found`) before anything is sent.
328
+ */
329
+ change: (name: string, change: {
330
+ account: Account | string;
331
+ endUser?: never;
332
+ } | {
333
+ endUser: true;
334
+ account?: never;
335
+ }) => Promise<Agent>;
336
+ /** Remove one connection by name. Publishes a version. NEVER touches the
337
+ * account: it stays connected for every other agent that names it, and
338
+ * for this one if you add it back. */
339
+ remove: (name: string) => Promise<Agent>;
340
+ /**
341
+ * Each connection with the account it uses and whether that account works
342
+ * now: DECLARED IS NOT CONNECTED. An agent can name an account nobody has
343
+ * finished signing in to, or one that was disconnected since, and that is
344
+ * the state a setup checklist has to show.
345
+ *
346
+ * Includes what an unpublished draft adds (`inDraft`) and removes
347
+ * (`removedInDraft`), so it matches the portal.
348
+ */
349
+ list: () => Promise<ConnectionStatus[]>;
350
+ };
219
351
  /**
220
- * Attach a third-party account to this agent, at `fixed` scope: one account,
221
- * used by every conversation.
352
+ * Give this agent secret values, so every conversation with it can use them.
222
353
  *
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.
354
+ * ```ts
355
+ * await agent.attach({ secrets: { APOLLO_API_KEY: value } });
356
+ * ```
227
357
  *
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.
358
+ * SECRETS ONLY, since 0.44. Accounts used to be attached here, and a turn
359
+ * then had to guess which of the accounts attached and owned around it to
360
+ * use. The agent's setup now names them: `agent.connections.add()`. One
361
+ * handed in here is refused by name (`not_a_secret`) rather than dropped.
362
+ *
363
+ * A stored `Secret` is not attached to an agent either. It never was: one
364
+ * was accepted here and nothing was done with it. Store one for the agent
365
+ * with `ws.secrets.create({ name, value, agentId })`, or attach one of your
366
+ * end user's to their conversation with `conversation.attach(secret)`.
231
367
  */
232
- authorize(provider: string): Promise<{
233
- url: string;
234
- connectionId: string;
235
- }>;
236
- /** Detach the account connected for this provider.
237
- *
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. */
368
+ attach(bindings: Bindings): Promise<Agent>;
369
+ /** Start a conversation. It pins the agent's version now and keeps it.
370
+ *
371
+ * Nothing about accounts is passed here: the version's connections name
372
+ * them, and a connection to the end user's account uses the account of
373
+ * the person named by `endUserId`. */
243
374
  start(options: StartOptions): Promise<Conversation>;
244
375
  /**
245
376
  * Address one of this agent's conversations by id. No request.
@@ -281,5 +412,3 @@ export declare class Agent {
281
412
  endUser?: string;
282
413
  }): Promise<Conversation[]>;
283
414
  }
284
- /** A connection row as `GET /v1/connections` returns it (toConnectionMeta in
285
- * apps/api/src/connections/store.ts). */