@cerebro-labs/agentnava-sdk 0.79.0 → 0.80.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -74,6 +74,30 @@ The builders make plain objects, `{ app, account: '<id>' }` and
74
74
  `App.GMAIL` is the string `'gmail'`, so a plain string works anywhere a constant
75
75
  does, including for an app added after your SDK version.
76
76
 
77
+ ## 0.80.0: an agent can remember each end user
78
+
79
+ Nothing changes until you turn it on, and nothing in 0.79 breaks.
80
+
81
+ ```ts
82
+ await agent.update({ memorySettings: { on: true } });
83
+ const conversation = await agent.start({ endUserId: 'u_ananya' });
84
+ ```
85
+
86
+ After a chat goes quiet, a background agent keeps a short memory about that end
87
+ user, on that agent only, and later chats use it. Your team can read and change
88
+ it. It needs a Team plan or higher.
89
+
90
+ | New in 0.80 | What it does |
91
+ |---|---|
92
+ | `memorySettings` on `agent.update` and `ws.agents.create` | Turn it on, add your own instructions, set how long it is kept |
93
+ | `(await agent.get()).memorySettings.notice` | The line to show near your message box, if you draw your own chat |
94
+ | `agent.endUser(id).memory` | `get`, `set`, `delete` and `stop` |
95
+ | `agent.memories()` | Who has a memory on this agent, newest change first |
96
+ | `agent.start({ endUserId, memory: false })` | One conversation that uses nothing remembered and adds nothing |
97
+
98
+ The id you pass decides whose memory it is, so use one stable id per person. The
99
+ guide, with when you may need consent: https://docs.agentnava.com/memory
100
+
77
101
  ## 0.79.0 is a breaking change: an end user belongs to one agent
78
102
 
79
103
  Each agent is its own app, with its own end users. The same id on two agents is
package/agent.d.ts CHANGED
@@ -5,7 +5,7 @@ import type { Http } from './http.ts';
5
5
  import type { Secret } from './secret.ts';
6
6
  import { type Turn } from './turn.ts';
7
7
  import { type Connection } from './connection.ts';
8
- import type { AgentData, AgentDraft, AgentFields, Bindings, ConnectionStatus, FileEntry, Trigger, WebsiteChat, WebsiteChatAccess, Workflow, WebhookDelivery } from './types.ts';
8
+ import type { AgentData, AgentDraft, AgentFields, Bindings, ConnectionStatus, FileEntry, Trigger, WebsiteChat, WebsiteChatAccess, Workflow, WebhookDelivery, MemoryData } from './types.ts';
9
9
  export interface StartOptions {
10
10
  /**
11
11
  * Stored secrets to attach AS THE CONVERSATION STARTS.
@@ -36,6 +36,13 @@ export interface StartOptions {
36
36
  * `conversation.accountsNeeded()` for the ones they have not connected yet.
37
37
  */
38
38
  attach?: Secret | Secret[];
39
+ /**
40
+ * `false` starts this conversation with end-user memory off, when the agent has
41
+ * memory on: it uses nothing remembered and adds nothing. Fixed for the life of the
42
+ * conversation. On by default. The team can still read the conversation, so this
43
+ * is not a private chat.
44
+ */
45
+ memory?: boolean;
39
46
  /**
40
47
  * Secret VALUES for this conversation only.
41
48
  *
@@ -495,6 +502,15 @@ export declare class Agent {
495
502
  * database that we store verbatim and never check.
496
503
  */
497
504
  endUser(endUserId: string): EndUser;
505
+ /**
506
+ * Who has a memory on this agent, newest change first, at most 200 a call. For
507
+ * the next page, pass `before`: the `updatedAt` of the last one you got. Someone
508
+ * who asked to stop being remembered is listed, with `stopped: true`.
509
+ */
510
+ memories(options?: {
511
+ limit?: number;
512
+ before?: string;
513
+ }): Promise<MemoryData[]>;
498
514
  /**
499
515
  * This agent's most recent conversations, newest first.
500
516
  *
package/agent.js CHANGED
@@ -537,6 +537,7 @@ export class Agent {
537
537
  agentId: this.id,
538
538
  ...(options.version !== undefined && { version: options.version }),
539
539
  ...(options.parameters && { parameters: options.parameters }),
540
+ ...(options.memory !== undefined && { memory: options.memory }),
540
541
  // `sessionBindings`, NOT `secrets` at the top level.
541
542
  //
542
543
  // This sent them at the top level and the launch route reads
@@ -616,6 +617,18 @@ export class Agent {
616
617
  endUser(endUserId) {
617
618
  return new EndUser(this.#http, this.id, endUserId);
618
619
  }
620
+ /**
621
+ * Who has a memory on this agent, newest change first, at most 200 a call. For
622
+ * the next page, pass `before`: the `updatedAt` of the last one you got. Someone
623
+ * who asked to stop being remembered is listed, with `stopped: true`.
624
+ */
625
+ async memories(options = {}) {
626
+ const res = await this.#http.get(`${this.base}/memories`, {
627
+ ...(options.limit !== undefined && { limit: options.limit }),
628
+ ...(options.before && { before: options.before }),
629
+ });
630
+ return res.memories ?? [];
631
+ }
619
632
  /**
620
633
  * This agent's most recent conversations, newest first.
621
634
  *
package/agent.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"agent.js","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"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","sourcesContent":["// An agent: a configuration you start conversations from.\n\nimport { type Account, accountIdOf } from './account.ts';\nimport { isBindings, refuseConnections, secretIdsOf, secretsIn } from './attach.ts';\nimport { Conversation } from './conversation.ts';\nimport { EndUser } from './end-user.ts';\nimport { AgentNavaError } from './errors.ts';\nimport type { Http } from './http.ts';\nimport type { Secret } from './secret.ts';\nimport { makeTurn, type Turn } from './turn.ts';\nimport { type Connection, connectionForWire } from './connection.ts';\nimport type {\n\tAgentConnection,\n\tAgentConversationData,\n\tAgentData,\n\tAgentDraft,\n\tAgentFields,\n\tBindings,\n\tConnectionStatus,\n\tFileEntry,\n\tTrigger,\n\tWebsiteChat,\n\tWebsiteChatAccess,\n\tWorkflow,\n\tWebhookDelivery,\n} from './types.ts';\n\nexport interface StartOptions {\n\t/**\n\t * Stored secrets to attach AS THE CONVERSATION STARTS.\n\t *\n\t * A `Secret` from `agent.endUser(id).secrets.create(...)` attaches by id, which\n\t * is the only way a stored per-person credential can reach a turn: you never\n\t * hold its value.\n\t *\n\t * ```ts\n\t * const key = await agent.endUser(row.userId).secrets.create({ name: 'ZENDESK_TOKEN', value });\n\t * const conversation = await agent.start({ endUserId: row.userId, attach: key });\n\t * ```\n\t *\n\t * Doing it here rather than with a second `conversation.attach()` call\n\t * closes a real window: between `start` and the attach, the conversation\n\t * exists and can be asked something, and it would answer without it.\n\t *\n\t * The conversation keeps a POINTER to the secret, never a copy. Replacing\n\t * its value (`secret.update(value)`) reaches the conversation on its next\n\t * tool call, and deleting the secret stops the conversation using it at\n\t * once.\n\t *\n\t * ACCOUNTS ARE NOT ATTACHED HERE any more (0.44), and one handed in is\n\t * refused. The agent's setup names the account each connection uses; for a\n\t * connection to the end user's account (`Connection.endUserAccount`), the\n\t * turn uses the account of the person this conversation is for, found by\n\t * `endUserId`. Ask\n\t * `conversation.accountsNeeded()` for the ones they have not connected yet.\n\t */\n\tattach?: Secret | Secret[];\n\n\t/**\n\t * Secret VALUES for this conversation only.\n\t *\n\t * Not `attach`, because a secret has no id you can hand over: binding one\n\t * takes the value itself. That asymmetry is in the product, not in this\n\t * naming, and hiding it behind the same word would not remove it.\n\t *\n\t * A value given here EXPIRES, after `secretsExpireIn` seconds (one hour by\n\t * default). After that the agent's tools say the sign-in expired, the\n\t * conversation lists the name in `requires`, and a turn starts with a\n\t * `credential_needed` event. Push a fresh value with\n\t * `conversation.ask(text, { secrets })` or `conversation.attach({ secrets })`.\n\t */\n\tsecrets?: Record<string, string>;\n\n\t/** How long the values in `secrets` stay usable, in seconds. Default 3600\n\t * (one hour), at most 86400 (one day). */\n\tsecretsExpireIn?: number;\n\n\t/** Pin a specific version. Defaults to the latest at the moment of starting.\n\t *\n\t * A conversation pins its version for life: publishing afterwards does not\n\t * move a conversation that is already open. The one exception is a schedule\n\t * or webhook that names the conversation (`conversationId`): each firing\n\t * moves it to the latest version, so a fix you publish reaches it. */\n\tversion?: number;\n\tparameters?: Record<string, string | number>;\n\n\t/**\n\t * WHO this conversation is for: one of YOUR users, not one of ours.\n\t *\n\t * REQUIRED, and never null. Every conversation belongs to someone. The id\n\t * is yours to choose: a user's row id, an anonymous visitor's random id, or\n\t * a name for a job such as `'nightly-report'`. Only a non-empty string is\n\t * accepted; anything else throws before a request is sent. (Decided\n\t * 2026-09-26, AGE-161: a conversation with no owner cannot be listed back to\n\t * the person it was for, and the accounts connected inside it could not be\n\t * revoked with that person.)\n\t *\n\t * It LABELS the conversation, and it decides one thing more: a connection\n\t * to the end user's account (`Connection.endUserAccount`) uses THIS\n\t * person's account for that app.\n\t * It does not decide which conversation you get, because `start` always\n\t * starts a new one. Later you can ask for everything belonging to that\n\t * person:\n\t *\n\t * ```ts\n\t * const conversation = await agent.start({ endUserId: row.userId });\n\t * const nightly = await agent.start({ endUserId: 'nightly-report' }); // a job has an id too\n\t * const theirs = await agent.conversations({ endUser: row.userId });\n\t * ```\n\t *\n\t * To CONTINUE a conversation, keep its id and address it directly. That is\n\t * the only way to continue one, on purpose:\n\t *\n\t * ```ts\n\t * await ws.agent(AGENT).conversation(row.conversationId).ask('and the rest?');\n\t * ```\n\t *\n\t * The id is YOURS and opaque to us: a row id from your database, not an\n\t * email and not something we authenticate. We never verify it, so it is\n\t * exactly as trustworthy as whatever holds your API key.\n\t *\n\t * The platform's prefixes are refused with a 400 (`end_user_reserved`):\n\t * `member:`, `visitor:` and `job:`, which name a member in the web app, an\n\t * embed visitor and work nobody is typing, and the older `webhook:`,\n\t * `builder:` and `librarian:`. Case and spacing do not matter to the check\n\t * (`MEMBER:x` is refused too). An id you chose must never be mistaken for\n\t * one of ours.\n\t *\n\t * NOT A WORKSPACE MEMBER'S ID. `ws.members()` lists the colleagues who share\n\t * this account, and their `userId` is a platform id we issued. This field is\n\t * a different namespace entirely. Both were once spelled `userId`, so\n\t * passing a member id here was a call that could not fail: there is nothing\n\t * for an end-user id to be checked against, so it just labelled the\n\t * conversation with a colleague's internal id and looked fine.\n\t */\n\tendUserId: string;\n\t/**\n\t * This conversation's screen can draw widgets: cards, tables and charts the\n\t * agent writes inside its replies, in Google's open A2UI format. Off unless set.\n\t * When on, each turn's stream carries `widget` events and `done.widgets`,\n\t * and `messages()` returns each reply's `widgets`. The agent draws only if\n\t * its own \"Answer with widgets\" setting is on (it is, unless turned off).\n\t * Draw them with `ReplyView` from `@cerebro-labs/agentnava-sdk/react`, or your own components.\n\t */\n\twidgets?: boolean;\n}\n\n/**\n * An agent you can configure and start conversations from.\n *\n * Holding one makes NO request: `ws.agent(id)` is free and the first call on it\n * is the first round trip.\n *\n * EVERY METHOD THAT CHANGES THE AGENT PUBLISHES A VERSION. There is no separate\n * save. What you do not send is inherited from the current version, which is the\n * only reason changing one field is safe: a resend that rebuilt the whole\n * configuration would drop whatever the caller forgot, and that bug has shipped\n * here four separate times.\n */\nexport class Agent {\n\treadonly id: string;\n\treadonly #http: Http;\n\tprivate snapshot?: AgentData;\n\n\tconstructor(http: Http, id: string, snapshot?: AgentData) {\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/agents/${encodeURIComponent(this.id)}`;\n\t}\n\n\t/** The last-known configuration, from whatever call produced this object.\n\t * Undefined on an agent you addressed by id and have not read. */\n\tget data(): AgentData | undefined {\n\t\treturn this.snapshot;\n\t}\n\n\tasync get(options: { version?: number } = {}): Promise<AgentData> {\n\t\tconst data = await this.#http.get<AgentData>(\n\t\t\tthis.base,\n\t\t\toptions.version !== undefined ? { version: options.version } : undefined,\n\t\t);\n\t\t// Only cache the CURRENT configuration. Caching a historical version would\n\t\t// make `agent.data` mean different things depending on the last call.\n\t\tif (options.version === undefined) this.snapshot = data;\n\t\treturn data;\n\t}\n\n\t/** Every version, newest first. */\n\tasync versions(): Promise<AgentData[]> {\n\t\tconst res = await this.#http.get<{ versions: AgentData[] }>(`${this.base}/versions`);\n\t\treturn res.versions ?? [];\n\t}\n\n\t/** Publish a new version with these fields changed and everything else kept.\n\t * `connections`, when sent, replaces the whole list; an `Account` in it goes\n\t * over the wire as its id (`Account.toJSON`). */\n\tasync update(fields: AgentFields): Promise<Agent> {\n\t\tconst data = await this.#http.post<AgentData>(`${this.base}/versions`, fields);\n\t\tthis.snapshot = data;\n\t\treturn new Agent(this.#http, this.id, data);\n\t}\n\n\tasync delete(): Promise<void> {\n\t\tawait this.#http.delete(this.base);\n\t}\n\n\t// ----------------------------------------------------- draft and builder\n\n\t/**\n\t * The Agent Builder, the one behind the portal's Configure chat, working on\n\t * this agent. Most of what it changes lands in the agent's DRAFT, read with\n\t * `draft()`; two of its tools act at once, renaming the agent and importing\n\t * a website into the agent's files. The draft is the one the portal edits,\n\t * shared with anyone changing the agent there, and each message joins the\n\t * agent's one Configure conversation. So to take only part of what it writes,\n\t * run it on an agent nobody else edits (a copy made for the purpose), read\n\t * that copy's draft, publish what you keep on the real agent with\n\t * `update()`, and delete the copy. Builder messages cost no credits and\n\t * count towards a daily limit per workspace.\n\t */\n\treadonly builder = {\n\t\t/** Send the Builder one message. Await the turn for its reply, or iterate it for the events. `signal` stops waiting for it. */\n\t\task: (message: string, options: { signal?: AbortSignal } = {}): Turn =>\n\t\t\tmakeTurn(this.#http.send(`${this.base}/build`, { method: 'POST', body: { message }, ...(options.signal && { signal: options.signal }) })),\n\t};\n\n\t/** The agent as its draft holds it, and whether that draft has changes not yet published. With no draft, the live version. */\n\tasync draft(): Promise<AgentDraft> {\n\t\tconst res = await this.#http.get<DraftWire>(`${this.base}/spec`);\n\t\treturn {\n\t\t\texists: res.isDraft === true,\n\t\t\thasUnpublishedChanges: res.hasUnpublishedChanges === true,\n\t\t\tconfig: res.spec,\n\t\t};\n\t}\n\n\t// --------------------------------------------------------------- workflows\n\n\t/**\n\t * Add a workflow, or replace the one with the same name.\n\t *\n\t * Reads the current list and sends it back with this one in place, because\n\t * the API replaces the whole list: a workflow set is a list, and there is no\n\t * \"merge one item\" that does not have to decide what a duplicate name means.\n\t * Deciding it here, once, is better than every caller deciding it.\n\t */\n\tasync setWorkflow(workflow: Workflow): Promise<Agent> {\n\t\tconst current = this.snapshot?.workflows ?? (await this.get()).workflows;\n\t\tconst next = current.filter((w) => w.name !== workflow.name).concat(workflow);\n\t\treturn this.update({ workflows: next });\n\t}\n\n\tasync removeWorkflow(name: string): Promise<Agent> {\n\t\tconst current = this.snapshot?.workflows ?? (await this.get()).workflows;\n\t\treturn this.update({ workflows: current.filter((w) => w.name !== name) });\n\t}\n\n\t/** Replace the trigger list. Sending `[]` removes every trigger, including\n\t * `chat`, which is what leaves an agent that only runs on a schedule. */\n\tasync setTriggers(triggers: Trigger[]): Promise<Agent> {\n\t\treturn this.update({ triggers });\n\t}\n\n\t/**\n\t * Replace the webhook's URL and return the new one.\n\t *\n\t * The old URL stops working the moment this returns, so update the system\n\t * that calls it. Use it when a URL leaked. The webhook's `message` and who it\n\t * runs for are kept. Publishes a version. Throws `no_webhook` (409) when the\n\t * agent has no webhook trigger.\n\t */\n\tasync rotateWebhookUrl(): Promise<string> {\n\t\tconst data = await this.#http.post<AgentData>(`${this.base}/webhook/rotate`, {});\n\t\tthis.snapshot = data;\n\t\tconst webhook = data.triggers.find((t) => t.kind === 'webhook');\n\t\tif (webhook?.kind !== 'webhook' || !webhook.url) {\n\t\t\tthrow new AgentNavaError({\n\t\t\t\tmessage: 'The webhook was rotated but no URL came back. Read it with agent.get().',\n\t\t\t\tcode: 'webhook_url_missing',\n\t\t\t\tstatus: 502,\n\t\t\t});\n\t\t}\n\t\treturn webhook.url;\n\t}\n\n\t/** What the webhook received, newest first: accepted, refused (and why), a\n\t * sender's retry recognised as a repeat, or a test. Up to `limit` (200). */\n\tasync webhookDeliveries(options: { limit?: number } = {}): Promise<WebhookDelivery[]> {\n\t\tconst res = await this.#http.get<{ deliveries: WebhookDelivery[] }>(\n\t\t\t`${this.base}/webhook/deliveries`,\n\t\t\toptions.limit !== undefined ? { limit: options.limit } : undefined,\n\t\t);\n\t\treturn res.deliveries ?? [];\n\t}\n\n\t/**\n\t * Send the webhook a sample event and run the agent on it, the way a real\n\t * delivery would (the signature check aside). A real turn, charged like\n\t * one. Returns the conversation it ran in.\n\t */\n\tasync sendTestWebhook(): Promise<{ conversationId?: string }> {\n\t\tconst res = await this.#http.post<{ conversationId?: string }>(`${this.base}/webhook/test`, {});\n\t\treturn res.conversationId ? { conversationId: res.conversationId } : {};\n\t}\n\n\t// ------------------------------------------------------------ website chat\n\n\t/**\n\t * The agent's website chat: its chat page on AgentNava (the share link),\n\t * and the embed script that puts it on your website. Every agent has one\n\t * from the moment it is created, however it was made (here, through the\n\t * API, or in the portal), except an agent that needs each user's own\n\t * account or secret, which a visitor cannot bring.\n\t *\n\t * Who can chat is one setting, `access`:\n\t *\n\t * 'restricted' the default. The share link works for members of your\n\t * workspace who are signed in to AgentNava, so your team can\n\t * pass it around to test. The chat shows on another website\n\t * only when that website is on `allowedWebsites`.\n\t * 'anyone' anyone with the link can chat, and any website can show\n\t * the chat. Each visitor's turns are charged to your\n\t * workspace.\n\t *\n\t * You never need it to use the agent from code: the agent answers your API\n\t * key, and the portal's Test, as soon as it exists. Publishing a new version\n\t * moves the website chat to it, as it moves new conversations.\n\t */\n\treadonly websiteChat = {\n\t\t/** Upload a logo for the chat's look: PNG, JPG, WebP or SVG, up to 1 MB\n\t\t * (an SVG carrying script is refused). Returns its id, to set as\n\t\t * `appearance.logo` with `update()`, and where it is served. The same\n\t\t * bytes always give the same id. */\n\t\tuploadLogo: async (bytes: Uint8Array | ArrayBuffer, contentType?: string): Promise<{ id: string; url: string }> =>\n\t\t\tthis.#http.request<{ id: string; url: string }>(`${this.base}/images`, {\n\t\t\t\tmethod: 'POST',\n\t\t\t\tbytes,\n\t\t\t\t...(contentType && { contentType }),\n\t\t\t}),\n\t\t/** Who can chat, the share link, and the id the embed script's\n\t\t * `data-agent-id` takes. */\n\t\tget: async (): Promise<WebsiteChat> => toWebsiteChat(\n\t\t\tawait this.#http.get<WebsiteChatWire>(`${this.base}/website-chat-access`),\n\t\t),\n\t\t/** Who can chat: 'restricted' (your workspace's signed-in members, and\n\t\t * the allowed websites) or 'anyone' (anyone with the link). */\n\t\taccess: {\n\t\t\t/** The setting as it stands. */\n\t\t\tget: async (): Promise<WebsiteChatAccess> =>\n\t\t\t\t(await this.#http.get<WebsiteChatWire>(`${this.base}/website-chat-access`)).access,\n\t\t\t/** Change it. Returns the setting as stored. Anything but 'restricted'\n\t\t\t * or 'anyone' is refused (400). */\n\t\t\tset: async (access: WebsiteChatAccess): Promise<WebsiteChatAccess> =>\n\t\t\t\t(await this.#http.put<WebsiteChatWire>(`${this.base}/website-chat-access`, { access })).access,\n\t\t},\n\t\t/** The websites a Restricted agent's chat may be shown on through the\n\t\t * embed script, where visitors chat without signing in. A domain covers\n\t\t * its subdomains; an empty list means no other website. While access is\n\t\t * 'anyone' the list is kept and not needed. */\n\t\tallowedWebsites: {\n\t\t\t/** The list as it stands. */\n\t\t\tget: async (): Promise<string[]> =>\n\t\t\t\t(await this.#http.get<{ websites: string[] }>(`${this.base}/allowed-websites`)).websites,\n\t\t\t/** Replace the list. Each entry is normalised (\"https://acme.com/page\"\n\t\t\t * becomes \"acme.com\"; \"localhost\" is allowed for testing), and one that\n\t\t\t * is not a website is refused (400). Returns the list as stored. */\n\t\t\tset: async (websites: string[]): Promise<string[]> =>\n\t\t\t\t(await this.#http.put<{ websites: string[] }>(`${this.base}/allowed-websites`, { websites })).websites,\n\t\t},\n\t};\n\n\t// --------------------------------------------------------------- knowledge\n\n\t#readKnowledgeResponse(path: string): Promise<Response> {\n\t\treturn this.#http.send(`${this.base}/knowledge/files/content`, { query: { path } });\n\t}\n\n\t/**\n\t * The agent's knowledge: files every conversation can read.\n\t *\n\t * BY REFERENCE, not by copy. A conversation reads the knowledge as it is now,\n\t * so replacing a file changes what open conversations see on their next turn.\n\t * That is the opposite of how a version works, and it is deliberate: a price\n\t * list that could not be corrected without republishing the agent would be\n\t * useless.\n\t */\n\treadonly knowledge = {\n\t\tput: async (path: string, bytes: Uint8Array | ArrayBuffer, contentType?: string): Promise<FileEntry> =>\n\t\t\tthis.#http.request<FileEntry>(`${this.base}/knowledge/files`, {\n\t\t\t\tmethod: 'PUT',\n\t\t\t\tbytes,\n\t\t\t\tquery: { path },\n\t\t\t\t...(contentType && { contentType }),\n\t\t\t}),\n\t\t/** Read an agent-owned knowledge file as bytes, without starting a turn. */\n\t\tget: async (path: string): Promise<Uint8Array> => {\n\t\t\tconst res = await this.#readKnowledgeResponse(path);\n\t\t\treturn new Uint8Array(await res.arrayBuffer());\n\t\t},\n\t\t/** Read UTF-8 text. JSON files remain text rather than being parsed. */\n\t\ttext: async (path: string): Promise<string> => {\n\t\t\tconst res = await this.#readKnowledgeResponse(path);\n\t\t\treturn res.text();\n\t\t},\n\t\tlist: async (path = '/'): Promise<FileEntry[]> => {\n\t\t\t// The route returns EVERY knowledge file for the agent; it takes no\n\t\t\t// directory parameter. Filtering here rather than sending one the server\n\t\t\t// ignores, because a call that looks filtered and is not is worse than\n\t\t\t// no filter at all. Knowledge sets are small, so one read is right.\n\t\t\tconst res = await this.#http.get<{ files: (FileEntry & { path: string })[] }>(\n\t\t\t\t`${this.base}/knowledge/files`,\n\t\t\t);\n\t\t\tconst prefix = path === '/' ? '/' : `${path.replace(/\\/+$/, '')}/`;\n\t\t\treturn (res.files ?? []).filter((f) => f.path?.startsWith(prefix));\n\t\t},\n\t\tremove: async (path: string): Promise<void> => {\n\t\t\tawait this.#http.delete(`${this.base}/knowledge/files`, { path });\n\t\t},\n\t};\n\n\t// -------------------------------------------------------- knowledge bases\n\n\t/**\n\t * The WORKSPACE knowledge bases this agent reads.\n\t *\n\t * Different from `knowledge` above, and the distinction is the whole point.\n\t * `agent.knowledge` is files this agent owns; nobody else has them. A\n\t * knowledge base is shared: one price list, attached to nine agents, corrected\n\t * in one place.\n\t *\n\t * Each attached base is mounted read-only at `/knowledge/<key>/` when a turn\n\t * starts, so the agent reads it with `ls`, `grep` and `cat`.\n\t *\n\t * ATTACHING PUBLISHES A VERSION. Which bases an agent reads is part of its\n\t * configuration, so the change is diffable and reversible like any other\n\t * configuration change. The CONTENTS of a base are not versioned: correcting\n\t * a file changes what every attached agent sees on its next turn.\n\t */\n\treadonly knowledgeBases = {\n\t\t/**\n\t\t * Attach one base by key.\n\t\t *\n\t\t * One call, not read-modify-write, deliberately. Two attaches racing\n\t\t * through a read of the current list and a write of the whole list would\n\t\t * lose one of them silently, and losing an attachment looks exactly like\n\t\t * an agent that cannot find its knowledge. The server appends.\n\t\t *\n\t\t * Attaching a key that names no base is refused, naming the key. Attaching\n\t\t * one that is already attached is a no-op, not an error.\n\t\t */\n\t\tattach: async (key: string): Promise<Agent> => {\n\t\t\tconst res = await this.#http.post<AgentData>(`${this.base}/knowledge-bases`, { key });\n\t\t\tthis.snapshot = res;\n\t\t\treturn this;\n\t\t},\n\n\t\t/** Detach one base. The base itself and its files are untouched: this only\n\t\t * stops THIS agent from reading it. Detaching one that is not attached is\n\t\t * a no-op. */\n\t\tdetach: async (key: string): Promise<Agent> => {\n\t\t\tconst res = await this.#http.delete<AgentData>(\n\t\t\t\t`${this.base}/knowledge-bases/${encodeURIComponent(key)}`,\n\t\t\t);\n\t\t\tif (res) this.snapshot = res;\n\t\t\treturn this;\n\t\t},\n\n\t\t/** The keys this agent currently reads, in configuration order. */\n\t\tlist: async (): Promise<string[]> => {\n\t\t\tconst current = this.snapshot ?? (await this.get());\n\t\t\treturn current.knowledgeBases ?? [];\n\t\t},\n\t};\n\n\t// -------------------------------------------------------------- databases\n\n\t/**\n\t * The WORKSPACE databases this agent may query, by key.\n\t *\n\t * Register a database first with `ws.databases.create()`, which gives it its\n\t * key; attaching names that key in this agent's configuration. Naming it is\n\t * the grant: the database answers this agent's `sql_*` calls and no agent\n\t * that does not name it, and the agent's prompt lists it by key.\n\t *\n\t * ATTACHING PUBLISHES A VERSION, like a knowledge base: which databases an\n\t * agent reaches is part of its configuration, so the change is diffable and\n\t * reversible. Detaching takes effect on the agent's next query, including in\n\t * a conversation already under way.\n\t */\n\treadonly databases = {\n\t\t/** Attach one database by key. The server appends, so two attaches cannot\n\t\t * lose each other. A key that names no database is refused; one already\n\t\t * attached is a no-op. */\n\t\tattach: async (key: string): Promise<Agent> => {\n\t\t\tconst res = await this.#http.post<AgentData>(`${this.base}/databases`, { key });\n\t\t\tthis.snapshot = res;\n\t\t\treturn this;\n\t\t},\n\n\t\t/** Stop this agent querying one database. The database stays registered\n\t\t * for every other agent that names it. */\n\t\tdetach: async (key: string): Promise<Agent> => {\n\t\t\tconst res = await this.#http.delete<AgentData>(`${this.base}/databases/${encodeURIComponent(key)}`);\n\t\t\tif (res) this.snapshot = res;\n\t\t\treturn this;\n\t\t},\n\n\t\t/** The keys this agent may query, in configuration order. */\n\t\tlist: async (): Promise<string[]> => {\n\t\t\tconst current = this.snapshot ?? (await this.get());\n\t\t\treturn current.databases ?? [];\n\t\t},\n\t};\n\n\t// ------------------------------------------------------------ connections\n\n\t/**\n\t * The apps this agent uses: each a NAMED connection that says WHOSE ACCOUNT\n\t * it uses, built the way the portal's Add a connection screen asks.\n\t *\n\t * | built with | whose account | the person signs in |\n\t * |---|---|---|\n\t * | `Connection.creatorAccount(app, account)` | the agent creator's, from `ws.accounts.connect()` | once, and every conversation uses it |\n\t * | `Connection.endUserAccount(app)` | the end user's: each person talking to the agent | once each, remembered for them |\n\t *\n\t * ```ts\n\t * const { account: work } = await ws.accounts.connect({ app: App.GMAIL, nickname: 'Work' });\n\t * await agent.connections.add(Connection.creatorAccount(App.GMAIL, work, { name: 'work-gmail' }));\n\t * await agent.connections.add(Connection.endUserAccount(App.GOOGLE_CALENDAR));\n\t * ```\n\t *\n\t * A turn uses EXACTLY the account the setup names, and never another one.\n\t * There is no fallback to whichever account of that app happens to exist:\n\t * that fallback is what this replaced, and it meant reading an agent's code\n\t * told you nothing about whose mailbox it read.\n\t *\n\t * EVERY CHANGE PUBLISHES A VERSION, like `databases`: which accounts an\n\t * agent reaches is part of its configuration, so the change is diffable and\n\t * reversible. A conversation uses the connections of the version it runs.\n\t * The ACCOUNTS are not versioned: signing one in again reaches every agent\n\t * that names it, from the next turn.\n\t *\n\t * Removing a connection never touches the account. Disconnecting an account\n\t * is the other operation, on the account: `account.disconnect()`.\n\t */\n\treadonly connections = {\n\t\t/**\n\t\t * Add one connection. One call that publishes, not read-modify-write, so\n\t\t * two adds racing cannot lose each other.\n\t\t *\n\t\t * Takes what `Connection.creatorAccount()` or `Connection.endUserAccount()`\n\t\t * builds, or the same plain object. Refused, naming the problem, when the\n\t\t * name is taken on this agent (`connection_name_taken`), when the account\n\t\t * is of a different app (`account_app_mismatch`), or when no account has\n\t\t * that id (`account_not_found`).\n\t\t */\n\t\tadd: async (connection: Connection): Promise<Agent> => {\n\t\t\tconst res = await this.#http.post<AgentData>(`${this.base}/connections`, connectionForWire(connection));\n\t\t\tthis.snapshot = res;\n\t\t\treturn this;\n\t\t},\n\n\t\t/**\n\t\t * Point one connection at another account: `{ account }` for the agent\n\t\t * creator's (the `Account` you hold, or its id), or `{ endUser: true }`\n\t\t * for each end user's own. The name and the app stay; an account of a\n\t\t * different app is refused (`account_app_mismatch`).\n\t\t *\n\t\t * `tools` changes which of the app's own tools the agent may use (a list\n\t\t * of ids, `[]` for none, `null` for the app's default), alone or with the\n\t\t * account. It is set for every connection of that app on the agent,\n\t\t * which share one list. Left out, the tools stay as they are.\n\t\t *\n\t\t * READ-MODIFY-WRITE, unlike `add` and `remove`, because there is no\n\t\t * route that changes one connection in place: this reads the current\n\t\t * version and publishes it again with the one connection changed. Two\n\t\t * changes racing can therefore lose one, so make them one after the\n\t\t * other. A name the agent does not have throws\n\t\t * (`connection_not_found`) before anything is sent.\n\t\t */\n\t\tchange: async (\n\t\t\tname: string,\n\t\t\tchange: (\n\t\t\t\t| { account: Account | string; endUser?: never }\n\t\t\t\t| { endUser: true; account?: never }\n\t\t\t\t| { account?: never; endUser?: never }\n\t\t\t) & { tools?: string[] | null },\n\t\t): Promise<Agent> => {\n\t\t\t// Read FRESH, never from the snapshot. A stale list sent back whole\n\t\t\t// would silently undo whatever changed since it was read.\n\t\t\tconst current = (await this.get()).connections ?? [];\n\t\t\tif (!current.some((c) => c.name === name)) {\n\t\t\t\tthrow new AgentNavaError({\n\t\t\t\t\tmessage: `This agent has no connection named \"${name}\". It has: ${\n\t\t\t\t\t\tcurrent.map((c) => c.name).join(', ') || 'none'\n\t\t\t\t\t}.`,\n\t\t\t\t\tcode: 'connection_not_found',\n\t\t\t\t\tstatus: 404,\n\t\t\t\t});\n\t\t\t}\n\t\t\tconst target = current.find((c) => c.name === name) as AgentConnection;\n\t\t\tconst whose =\n\t\t\t\tchange.endUser === true\n\t\t\t\t\t? { endUser: true as const }\n\t\t\t\t\t: change.account !== undefined\n\t\t\t\t\t\t? { account: accountIdOf(change.account) }\n\t\t\t\t\t\t: target.endUser === true\n\t\t\t\t\t\t\t? { endUser: true as const }\n\t\t\t\t\t\t\t: { account: target.account as string };\n\t\t\t// The tools as they were, unless this changes them; a change reaches\n\t\t\t// every connection of the app, which share one list.\n\t\t\tconst toolsOf = (c: AgentConnection) =>\n\t\t\t\tchange.tools === undefined\n\t\t\t\t\t? c.tools && { tools: c.tools }\n\t\t\t\t\t: c.app === target.app && change.tools !== null\n\t\t\t\t\t\t? { tools: [...change.tools] }\n\t\t\t\t\t\t: c.app === target.app\n\t\t\t\t\t\t\t? {}\n\t\t\t\t\t\t\t: c.tools && { tools: c.tools };\n\t\t\t// Every other connection goes back as it was read, its tools aside:\n\t\t\t// this changes one, and must not reinterpret the rest.\n\t\t\tconst connections = current.map((c) => {\n\t\t\t\tconst { tools: _tools, ...rest } = c;\n\t\t\t\tif (c.name === name) return { name: c.name, app: c.app, ...whose, ...toolsOf(c) };\n\t\t\t\treturn { ...rest, ...toolsOf(c) };\n\t\t\t});\n\t\t\tconst data = await this.#http.post<AgentData>(`${this.base}/versions`, { connections });\n\t\t\tthis.snapshot = data;\n\t\t\treturn this;\n\t\t},\n\n\t\t/** Remove one connection by name. Publishes a version. NEVER touches the\n\t\t * account: it stays connected for every other agent that names it, and\n\t\t * for this one if you add it back. */\n\t\tremove: async (name: string): Promise<Agent> => {\n\t\t\tconst res = await this.#http.delete<AgentData>(`${this.base}/connections/${encodeURIComponent(name)}`);\n\t\t\tif (res) this.snapshot = res;\n\t\t\treturn this;\n\t\t},\n\n\t\t/**\n\t\t * Each connection with the account it uses and whether that account works\n\t\t * now: DECLARED IS NOT CONNECTED. An agent can name an account nobody has\n\t\t * finished signing in to, or one that was disconnected since, and that is\n\t\t * the state a setup checklist has to show.\n\t\t *\n\t\t * Includes what an unpublished draft adds (`inDraft`) and removes\n\t\t * (`removedInDraft`), so it matches the portal.\n\t\t */\n\t\tlist: async (): Promise<ConnectionStatus[]> => {\n\t\t\tconst res = await this.#http.get<{ connections: ConnectionStatus[] }>(`${this.base}/connections`);\n\t\t\treturn res.connections ?? [];\n\t\t},\n\t};\n\n\t// ------------------------------------------------------ secrets, agent tier\n\n\t/**\n\t * Write secret VALUES to this agent's own scope: readable by this agent and\n\t * nothing else in the workspace, overriding a workspace secret of the same\n\t * name. Writing one twice replaces the value rather than failing, because\n\t * attaching means \"this is the credential now\".\n\t *\n\t * This used to POST the whole object to `/v1/agents/:id/bindings`, which\n\t * answers only to `{kind, decl}` and records a DECLARATION, never a value. So\n\t * the call 400'd, and had it not, it would have stored the fact that a secret\n\t * is wanted rather than the secret.\n\t */\n\tasync #applyBindings(bindings: Bindings): Promise<Agent> {\n\t\tfor (const [name, value] of Object.entries(bindings.secrets ?? {})) {\n\t\t\tconst mine = (await this.#http.get<{ secrets: { id: string; name: string; agentId?: string | null }[] }>(\n\t\t\t\t'/v1/secrets',\n\t\t\t\t{ agentId: this.id },\n\t\t\t)).secrets ?? [];\n\t\t\tconst existing = mine.find((s) => s.name === name && s.agentId);\n\t\t\tif (existing) await this.#http.patch(`/v1/secrets/${encodeURIComponent(existing.id)}`, { value });\n\t\t\telse await this.#http.post('/v1/secrets', { name, value, agentId: this.id });\n\t\t}\n\t\tconst data = await this.get();\n\t\tthis.snapshot = data;\n\t\treturn new Agent(this.#http, this.id, data);\n\t}\n\n\t/**\n\t * Give this agent secret values, so every conversation with it can use them.\n\t *\n\t * ```ts\n\t * await agent.attach({ secrets: { APOLLO_API_KEY: value } });\n\t * ```\n\t *\n\t * SECRETS ONLY, since 0.44. Accounts used to be attached here, and a turn\n\t * then had to guess which of the accounts attached and owned around it to\n\t * use. The agent's setup now names them: `agent.connections.add()`. One\n\t * handed in here is refused by name (`not_a_secret`) rather than dropped.\n\t *\n\t * A stored `Secret` is not attached to an agent either. It never was: one\n\t * was accepted here and nothing was done with it. Store one for the agent\n\t * with `ws.secrets.create({ name, value, agentId })`, or attach one of your\n\t * end user's to their conversation with `conversation.attach(secret)`.\n\t */\n\tasync attach(bindings: Bindings): Promise<Agent> {\n\t\tif (!isBindings(bindings)) {\n\t\t\tthrow new AgentNavaError({\n\t\t\t\tmessage:\n\t\t\t\t\t'agent.attach takes { secrets: { NAME: value } }. Accounts go in the agent\\'s setup ' +\n\t\t\t\t\t'(agent.connections.add), and a stored secret attaches to a conversation ' +\n\t\t\t\t\t'(conversation.attach).',\n\t\t\t\tcode: 'not_a_secret',\n\t\t\t\tstatus: 400,\n\t\t\t});\n\t\t}\n\t\trefuseConnections(bindings);\n\t\treturn this.#applyBindings(bindings);\n\t}\n\n\t// ----------------------------------------------------------- conversations\n\n\t/** Start a conversation. It pins the agent's version now and keeps it.\n\t *\n\t * Nothing about accounts is passed here: the version's connections name\n\t * them, and a connection to the end user's account uses the account of\n\t * the person named by `endUserId`. */\n\tasync start(options: StartOptions): Promise<Conversation> {\n\t\tconst endUserId = requireEndUserId(options?.endUserId);\n\t\t// Resolved BEFORE the launch: a Secret is named on the wire by the\n\t\t// declared NAME it fills, and a handle addressed by id may not know its\n\t\t// own yet. Anything that is not a Secret is refused here, by name.\n\t\tconst secretIds = await secretIdsOf(secretsIn(options.attach));\n\t\tconst bindings =\n\t\t\toptions.secrets || secretIds\n\t\t\t\t? {\n\t\t\t\t\t\t...(options.secrets && { secrets: options.secrets }),\n\t\t\t\t\t\t...(options.secrets && options.secretsExpireIn !== undefined && { expiresInSeconds: options.secretsExpireIn }),\n\t\t\t\t\t\t...(secretIds && { secretIds }),\n\t\t\t\t\t}\n\t\t\t\t: undefined;\n\t\tconst data = await this.#http.post<AgentConversationData>('/v1/conversations', {\n\t\t\tagentId: this.id,\n\t\t\t...(options.version !== undefined && { version: options.version }),\n\t\t\t...(options.parameters && { parameters: options.parameters }),\n\t\t\t// `sessionBindings`, NOT `secrets` at the top level.\n\t\t\t//\n\t\t\t// This sent them at the top level and the launch route reads\n\t\t\t// `body.sessionBindings`, so they were dropped in silence: the route's\n\t\t\t// own body type has no top-level `secrets` to read. A conversation\n\t\t\t// started with a credential began without it, and the first turn\n\t\t\t// failed somewhere else entirely.\n\t\t\t//\n\t\t\t// Third place this exact envelope mistake was found in one afternoon,\n\t\t\t// after `agent.bind` and `conversation.bind`. The shape of the bug is\n\t\t\t// always the same: the SDK agrees with itself, the route agrees with\n\t\t\t// itself, and nothing compares the two.\n\t\t\t...(bindings && { sessionBindings: bindings }),\n\t\t\t// Always present: requireEndUserId() above has already refused\n\t\t\t// anything that is not a non-empty string.\n\t\t\tuser: { id: endUserId },\n\t\t\t// ALWAYS a new conversation, even when a user is named.\n\t\t\t//\n\t\t\t// The route resumes that user's most recent conversation when it is\n\t\t\t// given an identity and not told otherwise. That default exists for\n\t\t\t// our own web app, where a page reload must not leave a trail of empty\n\t\t\t// conversations, and it is wrong for a method called `start`: a verb\n\t\t\t// that sometimes does not do the thing is worse than two verbs. It is\n\t\t\t// also ambiguous the moment a user has more than one conversation,\n\t\t\t// since it silently picks the newest.\n\t\t\t//\n\t\t\t// So the SDK does not rely on that default. `start` starts. To\n\t\t\t// continue a conversation, hold its id: `agent.conversation(id)`.\n\t\t\tfresh: true,\n\t\t});\n\t\treturn new Conversation(this.#http, data.id, data, { widgets: options.widgets === true });\n\t}\n\n\t/**\n\t * Address one of this agent's conversations by id. No request.\n\t *\n\t * Pass `{ widgets: true }` when the screen continuing it can draw widgets,\n\t * as on `start`: the choice belongs to the screen, so it is made per handle.\n\t *\n\t * Here rather than on the workspace because a conversation belongs to an\n\t * agent. You already hold the agent: it is a constant in your own code, not\n\t * a per-row value, so naming it costs nothing and the ownership is visible.\n\t *\n\t * ```ts\n\t * await ws.agent(AGENT).conversation(row.conversationId).ask('carry on');\n\t * ```\n\t */\n\tconversation(conversationId: string, options: { widgets?: boolean } = {}): Conversation {\n\t\treturn new Conversation(this.#http, conversationId, undefined, { widgets: options.widgets === true });\n\t}\n\n\t/**\n\t * One of YOUR end users of this agent. No request.\n\t *\n\t * The object everything about a person hangs off: their accounts, their\n\t * secrets, their conversations, and `erase()`. You say who once, here, instead\n\t * of threading an `endUserId` through each call and forgetting it in one of them.\n\t *\n\t * ```ts\n\t * const ananya = agent.endUser(row.userId);\n\t * const { url } = await ananya.accounts.connect({ app: App.GMAIL });\n\t * await ananya.conversations();\n\t * await ananya.erase();\n\t * ```\n\t *\n\t * EACH AGENT IS ITS OWN APP, WITH ITS OWN END USERS. The same id on another agent\n\t * is a different person: their own accounts, secrets and conversations. A person\n\t * using two of your agents signs in to each.\n\t *\n\t * A SCOPE, NOT A RECORD. `ws.agent(id)`, `ws.project(id)`, `ws.account(id)` and\n\t * `ws.secret(id)` point at rows we store and can list back. This points at\n\t * nothing. We do not create your end users and we cannot list them. We only write\n\t * the id you give us onto rows we already own.\n\t *\n\t * NOT a workspace member. `ws.members()` lists the colleagues who share this\n\t * account, with ids we issued and authenticate. This takes an id from YOUR\n\t * database that we store verbatim and never check.\n\t */\n\tendUser(endUserId: string): EndUser {\n\t\treturn new EndUser(this.#http, this.id, endUserId);\n\t}\n\n\t/**\n\t * This agent's most recent conversations, newest first.\n\t *\n\t * NOT ALL OF THEM, and the previous sentence here said \"every\", which was\n\t * wrong in the way that costs someone an afternoon. The server returns the\n\t * newest 50 unless you ask for more, and refuses to return more than 200 on\n\t * any single call (apps/api/src/state.ts listAgentSessions, and the Durable\n\t * Object twin beside it, clamp identically).\n\t *\n\t * For the next page, pass `before`: the `startedAt` of the last conversation\n\t * you got. `agent.endUser(id).conversations()` is the same list for one person.\n\t *\n\t * `endUser` narrows to one of YOUR end users, which is what you want for a\n\t * \"your conversations\" list. Leaving it out returns everyone's, so leave it\n\t * out only for an operator view.\n\t *\n\t * `of: 'customers'` keeps only your end users' conversations, leaving out\n\t * the team's own (members trying the agent, jobs, webhooks); `of: 'team'`\n\t * is the other half. Left out, both.\n\t *\n\t * ```ts\n\t * const mine = await agent.conversations({ endUser: row.userId });\n\t * const shoppers = await agent.conversations({ of: 'customers', limit: 5 });\n\t * ```\n\t */\n\tasync conversations(options: { limit?: number; endUser?: string; before?: string; of?: 'customers' | 'team' } = {}): Promise<Conversation[]> {\n\t\tconst res = await this.#http.get<{ conversations: AgentConversationData[] }>('/v1/conversations', {\n\t\t\tagentId: this.id,\n\t\t\t...(options.limit !== undefined && { limit: options.limit }),\n\t\t\t// The next page, newest first: pass the `startedAt` of the last one you got.\n\t\t\t...(options.before !== undefined && { before: options.before }),\n\t\t\t// One end user's history. Sending this used to be silently ignored on\n\t\t\t// this route, which answered with everyone's conversations, so an\n\t\t\t// application rendering \"your chats\" showed somebody else's.\n\t\t\t...(options.endUser !== undefined && { end_user_id: options.endUser }),\n\t\t\t...(options.of !== undefined && { of: options.of }),\n\t\t});\n\t\treturn (res.conversations ?? []).map((c) => new Conversation(this.#http, c.id, c));\n\t}\n}\n\n/** What GET /v1/agents/:id/spec answers, as far as `draft()` reads it. */\ninterface DraftWire {\n\tisDraft?: boolean;\n\thasUnpublishedChanges?: boolean;\n\tspec: AgentFields;\n}\n\n/** GET and PUT /v1/agents/:id/website-chat-access, as sent. */\ninterface WebsiteChatWire {\n\tagentId: string;\n\taccess: WebsiteChatAccess;\n\tchatUrl: string;\n}\n\nfunction toWebsiteChat(wire: WebsiteChatWire): WebsiteChat {\n\treturn { access: wire.access, chatUrl: wire.chatUrl, embedAgentId: wire.agentId };\n}\n\n/** The end user a conversation belongs to: a non-empty string, or a thrown error before any request. */\nfunction requireEndUserId(value: unknown): string {\n\t// Checked at RUNTIME as well as in the type: a JavaScript caller, or a\n\t// TypeScript one reading a nullable column, can still pass null, a number or\n\t// ''. Each used to start an ownerless conversation that looked fine.\n\tif (typeof value === 'string' && value.trim()) return value.trim();\n\tthrow new AgentNavaError({\n\t\tmessage: 'agent.start() needs endUserId: a non-empty string naming who the conversation is for (your user id, a visitor id, or a job name).',\n\t\tcode: 'end_user_required',\n\t\tstatus: 400,\n\t});\n}\n"]}
1
+ {"version":3,"file":"agent.js","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"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","sourcesContent":["// An agent: a configuration you start conversations from.\n\nimport { type Account, accountIdOf } from './account.ts';\nimport { isBindings, refuseConnections, secretIdsOf, secretsIn } from './attach.ts';\nimport { Conversation } from './conversation.ts';\nimport { EndUser } from './end-user.ts';\nimport { AgentNavaError } from './errors.ts';\nimport type { Http } from './http.ts';\nimport type { Secret } from './secret.ts';\nimport { makeTurn, type Turn } from './turn.ts';\nimport { type Connection, connectionForWire } from './connection.ts';\nimport type {\n\tAgentConnection,\n\tAgentConversationData,\n\tAgentData,\n\tAgentDraft,\n\tAgentFields,\n\tBindings,\n\tConnectionStatus,\n\tFileEntry,\n\tTrigger,\n\tWebsiteChat,\n\tWebsiteChatAccess,\n\tWorkflow,\n\tWebhookDelivery,\n\tMemoryData,\n} from './types.ts';\n\nexport interface StartOptions {\n\t/**\n\t * Stored secrets to attach AS THE CONVERSATION STARTS.\n\t *\n\t * A `Secret` from `agent.endUser(id).secrets.create(...)` attaches by id, which\n\t * is the only way a stored per-person credential can reach a turn: you never\n\t * hold its value.\n\t *\n\t * ```ts\n\t * const key = await agent.endUser(row.userId).secrets.create({ name: 'ZENDESK_TOKEN', value });\n\t * const conversation = await agent.start({ endUserId: row.userId, attach: key });\n\t * ```\n\t *\n\t * Doing it here rather than with a second `conversation.attach()` call\n\t * closes a real window: between `start` and the attach, the conversation\n\t * exists and can be asked something, and it would answer without it.\n\t *\n\t * The conversation keeps a POINTER to the secret, never a copy. Replacing\n\t * its value (`secret.update(value)`) reaches the conversation on its next\n\t * tool call, and deleting the secret stops the conversation using it at\n\t * once.\n\t *\n\t * ACCOUNTS ARE NOT ATTACHED HERE any more (0.44), and one handed in is\n\t * refused. The agent's setup names the account each connection uses; for a\n\t * connection to the end user's account (`Connection.endUserAccount`), the\n\t * turn uses the account of the person this conversation is for, found by\n\t * `endUserId`. Ask\n\t * `conversation.accountsNeeded()` for the ones they have not connected yet.\n\t */\n\tattach?: Secret | Secret[];\n\n\t/**\n\t * `false` starts this conversation with end-user memory off, when the agent has\n\t * memory on: it uses nothing remembered and adds nothing. Fixed for the life of the\n\t * conversation. On by default. The team can still read the conversation, so this\n\t * is not a private chat.\n\t */\n\tmemory?: boolean;\n\n\t/**\n\t * Secret VALUES for this conversation only.\n\t *\n\t * Not `attach`, because a secret has no id you can hand over: binding one\n\t * takes the value itself. That asymmetry is in the product, not in this\n\t * naming, and hiding it behind the same word would not remove it.\n\t *\n\t * A value given here EXPIRES, after `secretsExpireIn` seconds (one hour by\n\t * default). After that the agent's tools say the sign-in expired, the\n\t * conversation lists the name in `requires`, and a turn starts with a\n\t * `credential_needed` event. Push a fresh value with\n\t * `conversation.ask(text, { secrets })` or `conversation.attach({ secrets })`.\n\t */\n\tsecrets?: Record<string, string>;\n\n\t/** How long the values in `secrets` stay usable, in seconds. Default 3600\n\t * (one hour), at most 86400 (one day). */\n\tsecretsExpireIn?: number;\n\n\t/** Pin a specific version. Defaults to the latest at the moment of starting.\n\t *\n\t * A conversation pins its version for life: publishing afterwards does not\n\t * move a conversation that is already open. The one exception is a schedule\n\t * or webhook that names the conversation (`conversationId`): each firing\n\t * moves it to the latest version, so a fix you publish reaches it. */\n\tversion?: number;\n\tparameters?: Record<string, string | number>;\n\n\t/**\n\t * WHO this conversation is for: one of YOUR users, not one of ours.\n\t *\n\t * REQUIRED, and never null. Every conversation belongs to someone. The id\n\t * is yours to choose: a user's row id, an anonymous visitor's random id, or\n\t * a name for a job such as `'nightly-report'`. Only a non-empty string is\n\t * accepted; anything else throws before a request is sent. (Decided\n\t * 2026-09-26, AGE-161: a conversation with no owner cannot be listed back to\n\t * the person it was for, and the accounts connected inside it could not be\n\t * revoked with that person.)\n\t *\n\t * It LABELS the conversation, and it decides one thing more: a connection\n\t * to the end user's account (`Connection.endUserAccount`) uses THIS\n\t * person's account for that app.\n\t * It does not decide which conversation you get, because `start` always\n\t * starts a new one. Later you can ask for everything belonging to that\n\t * person:\n\t *\n\t * ```ts\n\t * const conversation = await agent.start({ endUserId: row.userId });\n\t * const nightly = await agent.start({ endUserId: 'nightly-report' }); // a job has an id too\n\t * const theirs = await agent.conversations({ endUser: row.userId });\n\t * ```\n\t *\n\t * To CONTINUE a conversation, keep its id and address it directly. That is\n\t * the only way to continue one, on purpose:\n\t *\n\t * ```ts\n\t * await ws.agent(AGENT).conversation(row.conversationId).ask('and the rest?');\n\t * ```\n\t *\n\t * The id is YOURS and opaque to us: a row id from your database, not an\n\t * email and not something we authenticate. We never verify it, so it is\n\t * exactly as trustworthy as whatever holds your API key.\n\t *\n\t * The platform's prefixes are refused with a 400 (`end_user_reserved`):\n\t * `member:`, `visitor:` and `job:`, which name a member in the web app, an\n\t * embed visitor and work nobody is typing, and the older `webhook:`,\n\t * `builder:` and `librarian:`. Case and spacing do not matter to the check\n\t * (`MEMBER:x` is refused too). An id you chose must never be mistaken for\n\t * one of ours.\n\t *\n\t * NOT A WORKSPACE MEMBER'S ID. `ws.members()` lists the colleagues who share\n\t * this account, and their `userId` is a platform id we issued. This field is\n\t * a different namespace entirely. Both were once spelled `userId`, so\n\t * passing a member id here was a call that could not fail: there is nothing\n\t * for an end-user id to be checked against, so it just labelled the\n\t * conversation with a colleague's internal id and looked fine.\n\t */\n\tendUserId: string;\n\t/**\n\t * This conversation's screen can draw widgets: cards, tables and charts the\n\t * agent writes inside its replies, in Google's open A2UI format. Off unless set.\n\t * When on, each turn's stream carries `widget` events and `done.widgets`,\n\t * and `messages()` returns each reply's `widgets`. The agent draws only if\n\t * its own \"Answer with widgets\" setting is on (it is, unless turned off).\n\t * Draw them with `ReplyView` from `@cerebro-labs/agentnava-sdk/react`, or your own components.\n\t */\n\twidgets?: boolean;\n}\n\n/**\n * An agent you can configure and start conversations from.\n *\n * Holding one makes NO request: `ws.agent(id)` is free and the first call on it\n * is the first round trip.\n *\n * EVERY METHOD THAT CHANGES THE AGENT PUBLISHES A VERSION. There is no separate\n * save. What you do not send is inherited from the current version, which is the\n * only reason changing one field is safe: a resend that rebuilt the whole\n * configuration would drop whatever the caller forgot, and that bug has shipped\n * here four separate times.\n */\nexport class Agent {\n\treadonly id: string;\n\treadonly #http: Http;\n\tprivate snapshot?: AgentData;\n\n\tconstructor(http: Http, id: string, snapshot?: AgentData) {\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/agents/${encodeURIComponent(this.id)}`;\n\t}\n\n\t/** The last-known configuration, from whatever call produced this object.\n\t * Undefined on an agent you addressed by id and have not read. */\n\tget data(): AgentData | undefined {\n\t\treturn this.snapshot;\n\t}\n\n\tasync get(options: { version?: number } = {}): Promise<AgentData> {\n\t\tconst data = await this.#http.get<AgentData>(\n\t\t\tthis.base,\n\t\t\toptions.version !== undefined ? { version: options.version } : undefined,\n\t\t);\n\t\t// Only cache the CURRENT configuration. Caching a historical version would\n\t\t// make `agent.data` mean different things depending on the last call.\n\t\tif (options.version === undefined) this.snapshot = data;\n\t\treturn data;\n\t}\n\n\t/** Every version, newest first. */\n\tasync versions(): Promise<AgentData[]> {\n\t\tconst res = await this.#http.get<{ versions: AgentData[] }>(`${this.base}/versions`);\n\t\treturn res.versions ?? [];\n\t}\n\n\t/** Publish a new version with these fields changed and everything else kept.\n\t * `connections`, when sent, replaces the whole list; an `Account` in it goes\n\t * over the wire as its id (`Account.toJSON`). */\n\tasync update(fields: AgentFields): Promise<Agent> {\n\t\tconst data = await this.#http.post<AgentData>(`${this.base}/versions`, fields);\n\t\tthis.snapshot = data;\n\t\treturn new Agent(this.#http, this.id, data);\n\t}\n\n\tasync delete(): Promise<void> {\n\t\tawait this.#http.delete(this.base);\n\t}\n\n\t// ----------------------------------------------------- draft and builder\n\n\t/**\n\t * The Agent Builder, the one behind the portal's Configure chat, working on\n\t * this agent. Most of what it changes lands in the agent's DRAFT, read with\n\t * `draft()`; two of its tools act at once, renaming the agent and importing\n\t * a website into the agent's files. The draft is the one the portal edits,\n\t * shared with anyone changing the agent there, and each message joins the\n\t * agent's one Configure conversation. So to take only part of what it writes,\n\t * run it on an agent nobody else edits (a copy made for the purpose), read\n\t * that copy's draft, publish what you keep on the real agent with\n\t * `update()`, and delete the copy. Builder messages cost no credits and\n\t * count towards a daily limit per workspace.\n\t */\n\treadonly builder = {\n\t\t/** Send the Builder one message. Await the turn for its reply, or iterate it for the events. `signal` stops waiting for it. */\n\t\task: (message: string, options: { signal?: AbortSignal } = {}): Turn =>\n\t\t\tmakeTurn(this.#http.send(`${this.base}/build`, { method: 'POST', body: { message }, ...(options.signal && { signal: options.signal }) })),\n\t};\n\n\t/** The agent as its draft holds it, and whether that draft has changes not yet published. With no draft, the live version. */\n\tasync draft(): Promise<AgentDraft> {\n\t\tconst res = await this.#http.get<DraftWire>(`${this.base}/spec`);\n\t\treturn {\n\t\t\texists: res.isDraft === true,\n\t\t\thasUnpublishedChanges: res.hasUnpublishedChanges === true,\n\t\t\tconfig: res.spec,\n\t\t};\n\t}\n\n\t// --------------------------------------------------------------- workflows\n\n\t/**\n\t * Add a workflow, or replace the one with the same name.\n\t *\n\t * Reads the current list and sends it back with this one in place, because\n\t * the API replaces the whole list: a workflow set is a list, and there is no\n\t * \"merge one item\" that does not have to decide what a duplicate name means.\n\t * Deciding it here, once, is better than every caller deciding it.\n\t */\n\tasync setWorkflow(workflow: Workflow): Promise<Agent> {\n\t\tconst current = this.snapshot?.workflows ?? (await this.get()).workflows;\n\t\tconst next = current.filter((w) => w.name !== workflow.name).concat(workflow);\n\t\treturn this.update({ workflows: next });\n\t}\n\n\tasync removeWorkflow(name: string): Promise<Agent> {\n\t\tconst current = this.snapshot?.workflows ?? (await this.get()).workflows;\n\t\treturn this.update({ workflows: current.filter((w) => w.name !== name) });\n\t}\n\n\t/** Replace the trigger list. Sending `[]` removes every trigger, including\n\t * `chat`, which is what leaves an agent that only runs on a schedule. */\n\tasync setTriggers(triggers: Trigger[]): Promise<Agent> {\n\t\treturn this.update({ triggers });\n\t}\n\n\t/**\n\t * Replace the webhook's URL and return the new one.\n\t *\n\t * The old URL stops working the moment this returns, so update the system\n\t * that calls it. Use it when a URL leaked. The webhook's `message` and who it\n\t * runs for are kept. Publishes a version. Throws `no_webhook` (409) when the\n\t * agent has no webhook trigger.\n\t */\n\tasync rotateWebhookUrl(): Promise<string> {\n\t\tconst data = await this.#http.post<AgentData>(`${this.base}/webhook/rotate`, {});\n\t\tthis.snapshot = data;\n\t\tconst webhook = data.triggers.find((t) => t.kind === 'webhook');\n\t\tif (webhook?.kind !== 'webhook' || !webhook.url) {\n\t\t\tthrow new AgentNavaError({\n\t\t\t\tmessage: 'The webhook was rotated but no URL came back. Read it with agent.get().',\n\t\t\t\tcode: 'webhook_url_missing',\n\t\t\t\tstatus: 502,\n\t\t\t});\n\t\t}\n\t\treturn webhook.url;\n\t}\n\n\t/** What the webhook received, newest first: accepted, refused (and why), a\n\t * sender's retry recognised as a repeat, or a test. Up to `limit` (200). */\n\tasync webhookDeliveries(options: { limit?: number } = {}): Promise<WebhookDelivery[]> {\n\t\tconst res = await this.#http.get<{ deliveries: WebhookDelivery[] }>(\n\t\t\t`${this.base}/webhook/deliveries`,\n\t\t\toptions.limit !== undefined ? { limit: options.limit } : undefined,\n\t\t);\n\t\treturn res.deliveries ?? [];\n\t}\n\n\t/**\n\t * Send the webhook a sample event and run the agent on it, the way a real\n\t * delivery would (the signature check aside). A real turn, charged like\n\t * one. Returns the conversation it ran in.\n\t */\n\tasync sendTestWebhook(): Promise<{ conversationId?: string }> {\n\t\tconst res = await this.#http.post<{ conversationId?: string }>(`${this.base}/webhook/test`, {});\n\t\treturn res.conversationId ? { conversationId: res.conversationId } : {};\n\t}\n\n\t// ------------------------------------------------------------ website chat\n\n\t/**\n\t * The agent's website chat: its chat page on AgentNava (the share link),\n\t * and the embed script that puts it on your website. Every agent has one\n\t * from the moment it is created, however it was made (here, through the\n\t * API, or in the portal), except an agent that needs each user's own\n\t * account or secret, which a visitor cannot bring.\n\t *\n\t * Who can chat is one setting, `access`:\n\t *\n\t * 'restricted' the default. The share link works for members of your\n\t * workspace who are signed in to AgentNava, so your team can\n\t * pass it around to test. The chat shows on another website\n\t * only when that website is on `allowedWebsites`.\n\t * 'anyone' anyone with the link can chat, and any website can show\n\t * the chat. Each visitor's turns are charged to your\n\t * workspace.\n\t *\n\t * You never need it to use the agent from code: the agent answers your API\n\t * key, and the portal's Test, as soon as it exists. Publishing a new version\n\t * moves the website chat to it, as it moves new conversations.\n\t */\n\treadonly websiteChat = {\n\t\t/** Upload a logo for the chat's look: PNG, JPG, WebP or SVG, up to 1 MB\n\t\t * (an SVG carrying script is refused). Returns its id, to set as\n\t\t * `appearance.logo` with `update()`, and where it is served. The same\n\t\t * bytes always give the same id. */\n\t\tuploadLogo: async (bytes: Uint8Array | ArrayBuffer, contentType?: string): Promise<{ id: string; url: string }> =>\n\t\t\tthis.#http.request<{ id: string; url: string }>(`${this.base}/images`, {\n\t\t\t\tmethod: 'POST',\n\t\t\t\tbytes,\n\t\t\t\t...(contentType && { contentType }),\n\t\t\t}),\n\t\t/** Who can chat, the share link, and the id the embed script's\n\t\t * `data-agent-id` takes. */\n\t\tget: async (): Promise<WebsiteChat> => toWebsiteChat(\n\t\t\tawait this.#http.get<WebsiteChatWire>(`${this.base}/website-chat-access`),\n\t\t),\n\t\t/** Who can chat: 'restricted' (your workspace's signed-in members, and\n\t\t * the allowed websites) or 'anyone' (anyone with the link). */\n\t\taccess: {\n\t\t\t/** The setting as it stands. */\n\t\t\tget: async (): Promise<WebsiteChatAccess> =>\n\t\t\t\t(await this.#http.get<WebsiteChatWire>(`${this.base}/website-chat-access`)).access,\n\t\t\t/** Change it. Returns the setting as stored. Anything but 'restricted'\n\t\t\t * or 'anyone' is refused (400). */\n\t\t\tset: async (access: WebsiteChatAccess): Promise<WebsiteChatAccess> =>\n\t\t\t\t(await this.#http.put<WebsiteChatWire>(`${this.base}/website-chat-access`, { access })).access,\n\t\t},\n\t\t/** The websites a Restricted agent's chat may be shown on through the\n\t\t * embed script, where visitors chat without signing in. A domain covers\n\t\t * its subdomains; an empty list means no other website. While access is\n\t\t * 'anyone' the list is kept and not needed. */\n\t\tallowedWebsites: {\n\t\t\t/** The list as it stands. */\n\t\t\tget: async (): Promise<string[]> =>\n\t\t\t\t(await this.#http.get<{ websites: string[] }>(`${this.base}/allowed-websites`)).websites,\n\t\t\t/** Replace the list. Each entry is normalised (\"https://acme.com/page\"\n\t\t\t * becomes \"acme.com\"; \"localhost\" is allowed for testing), and one that\n\t\t\t * is not a website is refused (400). Returns the list as stored. */\n\t\t\tset: async (websites: string[]): Promise<string[]> =>\n\t\t\t\t(await this.#http.put<{ websites: string[] }>(`${this.base}/allowed-websites`, { websites })).websites,\n\t\t},\n\t};\n\n\t// --------------------------------------------------------------- knowledge\n\n\t#readKnowledgeResponse(path: string): Promise<Response> {\n\t\treturn this.#http.send(`${this.base}/knowledge/files/content`, { query: { path } });\n\t}\n\n\t/**\n\t * The agent's knowledge: files every conversation can read.\n\t *\n\t * BY REFERENCE, not by copy. A conversation reads the knowledge as it is now,\n\t * so replacing a file changes what open conversations see on their next turn.\n\t * That is the opposite of how a version works, and it is deliberate: a price\n\t * list that could not be corrected without republishing the agent would be\n\t * useless.\n\t */\n\treadonly knowledge = {\n\t\tput: async (path: string, bytes: Uint8Array | ArrayBuffer, contentType?: string): Promise<FileEntry> =>\n\t\t\tthis.#http.request<FileEntry>(`${this.base}/knowledge/files`, {\n\t\t\t\tmethod: 'PUT',\n\t\t\t\tbytes,\n\t\t\t\tquery: { path },\n\t\t\t\t...(contentType && { contentType }),\n\t\t\t}),\n\t\t/** Read an agent-owned knowledge file as bytes, without starting a turn. */\n\t\tget: async (path: string): Promise<Uint8Array> => {\n\t\t\tconst res = await this.#readKnowledgeResponse(path);\n\t\t\treturn new Uint8Array(await res.arrayBuffer());\n\t\t},\n\t\t/** Read UTF-8 text. JSON files remain text rather than being parsed. */\n\t\ttext: async (path: string): Promise<string> => {\n\t\t\tconst res = await this.#readKnowledgeResponse(path);\n\t\t\treturn res.text();\n\t\t},\n\t\tlist: async (path = '/'): Promise<FileEntry[]> => {\n\t\t\t// The route returns EVERY knowledge file for the agent; it takes no\n\t\t\t// directory parameter. Filtering here rather than sending one the server\n\t\t\t// ignores, because a call that looks filtered and is not is worse than\n\t\t\t// no filter at all. Knowledge sets are small, so one read is right.\n\t\t\tconst res = await this.#http.get<{ files: (FileEntry & { path: string })[] }>(\n\t\t\t\t`${this.base}/knowledge/files`,\n\t\t\t);\n\t\t\tconst prefix = path === '/' ? '/' : `${path.replace(/\\/+$/, '')}/`;\n\t\t\treturn (res.files ?? []).filter((f) => f.path?.startsWith(prefix));\n\t\t},\n\t\tremove: async (path: string): Promise<void> => {\n\t\t\tawait this.#http.delete(`${this.base}/knowledge/files`, { path });\n\t\t},\n\t};\n\n\t// -------------------------------------------------------- knowledge bases\n\n\t/**\n\t * The WORKSPACE knowledge bases this agent reads.\n\t *\n\t * Different from `knowledge` above, and the distinction is the whole point.\n\t * `agent.knowledge` is files this agent owns; nobody else has them. A\n\t * knowledge base is shared: one price list, attached to nine agents, corrected\n\t * in one place.\n\t *\n\t * Each attached base is mounted read-only at `/knowledge/<key>/` when a turn\n\t * starts, so the agent reads it with `ls`, `grep` and `cat`.\n\t *\n\t * ATTACHING PUBLISHES A VERSION. Which bases an agent reads is part of its\n\t * configuration, so the change is diffable and reversible like any other\n\t * configuration change. The CONTENTS of a base are not versioned: correcting\n\t * a file changes what every attached agent sees on its next turn.\n\t */\n\treadonly knowledgeBases = {\n\t\t/**\n\t\t * Attach one base by key.\n\t\t *\n\t\t * One call, not read-modify-write, deliberately. Two attaches racing\n\t\t * through a read of the current list and a write of the whole list would\n\t\t * lose one of them silently, and losing an attachment looks exactly like\n\t\t * an agent that cannot find its knowledge. The server appends.\n\t\t *\n\t\t * Attaching a key that names no base is refused, naming the key. Attaching\n\t\t * one that is already attached is a no-op, not an error.\n\t\t */\n\t\tattach: async (key: string): Promise<Agent> => {\n\t\t\tconst res = await this.#http.post<AgentData>(`${this.base}/knowledge-bases`, { key });\n\t\t\tthis.snapshot = res;\n\t\t\treturn this;\n\t\t},\n\n\t\t/** Detach one base. The base itself and its files are untouched: this only\n\t\t * stops THIS agent from reading it. Detaching one that is not attached is\n\t\t * a no-op. */\n\t\tdetach: async (key: string): Promise<Agent> => {\n\t\t\tconst res = await this.#http.delete<AgentData>(\n\t\t\t\t`${this.base}/knowledge-bases/${encodeURIComponent(key)}`,\n\t\t\t);\n\t\t\tif (res) this.snapshot = res;\n\t\t\treturn this;\n\t\t},\n\n\t\t/** The keys this agent currently reads, in configuration order. */\n\t\tlist: async (): Promise<string[]> => {\n\t\t\tconst current = this.snapshot ?? (await this.get());\n\t\t\treturn current.knowledgeBases ?? [];\n\t\t},\n\t};\n\n\t// -------------------------------------------------------------- databases\n\n\t/**\n\t * The WORKSPACE databases this agent may query, by key.\n\t *\n\t * Register a database first with `ws.databases.create()`, which gives it its\n\t * key; attaching names that key in this agent's configuration. Naming it is\n\t * the grant: the database answers this agent's `sql_*` calls and no agent\n\t * that does not name it, and the agent's prompt lists it by key.\n\t *\n\t * ATTACHING PUBLISHES A VERSION, like a knowledge base: which databases an\n\t * agent reaches is part of its configuration, so the change is diffable and\n\t * reversible. Detaching takes effect on the agent's next query, including in\n\t * a conversation already under way.\n\t */\n\treadonly databases = {\n\t\t/** Attach one database by key. The server appends, so two attaches cannot\n\t\t * lose each other. A key that names no database is refused; one already\n\t\t * attached is a no-op. */\n\t\tattach: async (key: string): Promise<Agent> => {\n\t\t\tconst res = await this.#http.post<AgentData>(`${this.base}/databases`, { key });\n\t\t\tthis.snapshot = res;\n\t\t\treturn this;\n\t\t},\n\n\t\t/** Stop this agent querying one database. The database stays registered\n\t\t * for every other agent that names it. */\n\t\tdetach: async (key: string): Promise<Agent> => {\n\t\t\tconst res = await this.#http.delete<AgentData>(`${this.base}/databases/${encodeURIComponent(key)}`);\n\t\t\tif (res) this.snapshot = res;\n\t\t\treturn this;\n\t\t},\n\n\t\t/** The keys this agent may query, in configuration order. */\n\t\tlist: async (): Promise<string[]> => {\n\t\t\tconst current = this.snapshot ?? (await this.get());\n\t\t\treturn current.databases ?? [];\n\t\t},\n\t};\n\n\t// ------------------------------------------------------------ connections\n\n\t/**\n\t * The apps this agent uses: each a NAMED connection that says WHOSE ACCOUNT\n\t * it uses, built the way the portal's Add a connection screen asks.\n\t *\n\t * | built with | whose account | the person signs in |\n\t * |---|---|---|\n\t * | `Connection.creatorAccount(app, account)` | the agent creator's, from `ws.accounts.connect()` | once, and every conversation uses it |\n\t * | `Connection.endUserAccount(app)` | the end user's: each person talking to the agent | once each, remembered for them |\n\t *\n\t * ```ts\n\t * const { account: work } = await ws.accounts.connect({ app: App.GMAIL, nickname: 'Work' });\n\t * await agent.connections.add(Connection.creatorAccount(App.GMAIL, work, { name: 'work-gmail' }));\n\t * await agent.connections.add(Connection.endUserAccount(App.GOOGLE_CALENDAR));\n\t * ```\n\t *\n\t * A turn uses EXACTLY the account the setup names, and never another one.\n\t * There is no fallback to whichever account of that app happens to exist:\n\t * that fallback is what this replaced, and it meant reading an agent's code\n\t * told you nothing about whose mailbox it read.\n\t *\n\t * EVERY CHANGE PUBLISHES A VERSION, like `databases`: which accounts an\n\t * agent reaches is part of its configuration, so the change is diffable and\n\t * reversible. A conversation uses the connections of the version it runs.\n\t * The ACCOUNTS are not versioned: signing one in again reaches every agent\n\t * that names it, from the next turn.\n\t *\n\t * Removing a connection never touches the account. Disconnecting an account\n\t * is the other operation, on the account: `account.disconnect()`.\n\t */\n\treadonly connections = {\n\t\t/**\n\t\t * Add one connection. One call that publishes, not read-modify-write, so\n\t\t * two adds racing cannot lose each other.\n\t\t *\n\t\t * Takes what `Connection.creatorAccount()` or `Connection.endUserAccount()`\n\t\t * builds, or the same plain object. Refused, naming the problem, when the\n\t\t * name is taken on this agent (`connection_name_taken`), when the account\n\t\t * is of a different app (`account_app_mismatch`), or when no account has\n\t\t * that id (`account_not_found`).\n\t\t */\n\t\tadd: async (connection: Connection): Promise<Agent> => {\n\t\t\tconst res = await this.#http.post<AgentData>(`${this.base}/connections`, connectionForWire(connection));\n\t\t\tthis.snapshot = res;\n\t\t\treturn this;\n\t\t},\n\n\t\t/**\n\t\t * Point one connection at another account: `{ account }` for the agent\n\t\t * creator's (the `Account` you hold, or its id), or `{ endUser: true }`\n\t\t * for each end user's own. The name and the app stay; an account of a\n\t\t * different app is refused (`account_app_mismatch`).\n\t\t *\n\t\t * `tools` changes which of the app's own tools the agent may use (a list\n\t\t * of ids, `[]` for none, `null` for the app's default), alone or with the\n\t\t * account. It is set for every connection of that app on the agent,\n\t\t * which share one list. Left out, the tools stay as they are.\n\t\t *\n\t\t * READ-MODIFY-WRITE, unlike `add` and `remove`, because there is no\n\t\t * route that changes one connection in place: this reads the current\n\t\t * version and publishes it again with the one connection changed. Two\n\t\t * changes racing can therefore lose one, so make them one after the\n\t\t * other. A name the agent does not have throws\n\t\t * (`connection_not_found`) before anything is sent.\n\t\t */\n\t\tchange: async (\n\t\t\tname: string,\n\t\t\tchange: (\n\t\t\t\t| { account: Account | string; endUser?: never }\n\t\t\t\t| { endUser: true; account?: never }\n\t\t\t\t| { account?: never; endUser?: never }\n\t\t\t) & { tools?: string[] | null },\n\t\t): Promise<Agent> => {\n\t\t\t// Read FRESH, never from the snapshot. A stale list sent back whole\n\t\t\t// would silently undo whatever changed since it was read.\n\t\t\tconst current = (await this.get()).connections ?? [];\n\t\t\tif (!current.some((c) => c.name === name)) {\n\t\t\t\tthrow new AgentNavaError({\n\t\t\t\t\tmessage: `This agent has no connection named \"${name}\". It has: ${\n\t\t\t\t\t\tcurrent.map((c) => c.name).join(', ') || 'none'\n\t\t\t\t\t}.`,\n\t\t\t\t\tcode: 'connection_not_found',\n\t\t\t\t\tstatus: 404,\n\t\t\t\t});\n\t\t\t}\n\t\t\tconst target = current.find((c) => c.name === name) as AgentConnection;\n\t\t\tconst whose =\n\t\t\t\tchange.endUser === true\n\t\t\t\t\t? { endUser: true as const }\n\t\t\t\t\t: change.account !== undefined\n\t\t\t\t\t\t? { account: accountIdOf(change.account) }\n\t\t\t\t\t\t: target.endUser === true\n\t\t\t\t\t\t\t? { endUser: true as const }\n\t\t\t\t\t\t\t: { account: target.account as string };\n\t\t\t// The tools as they were, unless this changes them; a change reaches\n\t\t\t// every connection of the app, which share one list.\n\t\t\tconst toolsOf = (c: AgentConnection) =>\n\t\t\t\tchange.tools === undefined\n\t\t\t\t\t? c.tools && { tools: c.tools }\n\t\t\t\t\t: c.app === target.app && change.tools !== null\n\t\t\t\t\t\t? { tools: [...change.tools] }\n\t\t\t\t\t\t: c.app === target.app\n\t\t\t\t\t\t\t? {}\n\t\t\t\t\t\t\t: c.tools && { tools: c.tools };\n\t\t\t// Every other connection goes back as it was read, its tools aside:\n\t\t\t// this changes one, and must not reinterpret the rest.\n\t\t\tconst connections = current.map((c) => {\n\t\t\t\tconst { tools: _tools, ...rest } = c;\n\t\t\t\tif (c.name === name) return { name: c.name, app: c.app, ...whose, ...toolsOf(c) };\n\t\t\t\treturn { ...rest, ...toolsOf(c) };\n\t\t\t});\n\t\t\tconst data = await this.#http.post<AgentData>(`${this.base}/versions`, { connections });\n\t\t\tthis.snapshot = data;\n\t\t\treturn this;\n\t\t},\n\n\t\t/** Remove one connection by name. Publishes a version. NEVER touches the\n\t\t * account: it stays connected for every other agent that names it, and\n\t\t * for this one if you add it back. */\n\t\tremove: async (name: string): Promise<Agent> => {\n\t\t\tconst res = await this.#http.delete<AgentData>(`${this.base}/connections/${encodeURIComponent(name)}`);\n\t\t\tif (res) this.snapshot = res;\n\t\t\treturn this;\n\t\t},\n\n\t\t/**\n\t\t * Each connection with the account it uses and whether that account works\n\t\t * now: DECLARED IS NOT CONNECTED. An agent can name an account nobody has\n\t\t * finished signing in to, or one that was disconnected since, and that is\n\t\t * the state a setup checklist has to show.\n\t\t *\n\t\t * Includes what an unpublished draft adds (`inDraft`) and removes\n\t\t * (`removedInDraft`), so it matches the portal.\n\t\t */\n\t\tlist: async (): Promise<ConnectionStatus[]> => {\n\t\t\tconst res = await this.#http.get<{ connections: ConnectionStatus[] }>(`${this.base}/connections`);\n\t\t\treturn res.connections ?? [];\n\t\t},\n\t};\n\n\t// ------------------------------------------------------ secrets, agent tier\n\n\t/**\n\t * Write secret VALUES to this agent's own scope: readable by this agent and\n\t * nothing else in the workspace, overriding a workspace secret of the same\n\t * name. Writing one twice replaces the value rather than failing, because\n\t * attaching means \"this is the credential now\".\n\t *\n\t * This used to POST the whole object to `/v1/agents/:id/bindings`, which\n\t * answers only to `{kind, decl}` and records a DECLARATION, never a value. So\n\t * the call 400'd, and had it not, it would have stored the fact that a secret\n\t * is wanted rather than the secret.\n\t */\n\tasync #applyBindings(bindings: Bindings): Promise<Agent> {\n\t\tfor (const [name, value] of Object.entries(bindings.secrets ?? {})) {\n\t\t\tconst mine = (await this.#http.get<{ secrets: { id: string; name: string; agentId?: string | null }[] }>(\n\t\t\t\t'/v1/secrets',\n\t\t\t\t{ agentId: this.id },\n\t\t\t)).secrets ?? [];\n\t\t\tconst existing = mine.find((s) => s.name === name && s.agentId);\n\t\t\tif (existing) await this.#http.patch(`/v1/secrets/${encodeURIComponent(existing.id)}`, { value });\n\t\t\telse await this.#http.post('/v1/secrets', { name, value, agentId: this.id });\n\t\t}\n\t\tconst data = await this.get();\n\t\tthis.snapshot = data;\n\t\treturn new Agent(this.#http, this.id, data);\n\t}\n\n\t/**\n\t * Give this agent secret values, so every conversation with it can use them.\n\t *\n\t * ```ts\n\t * await agent.attach({ secrets: { APOLLO_API_KEY: value } });\n\t * ```\n\t *\n\t * SECRETS ONLY, since 0.44. Accounts used to be attached here, and a turn\n\t * then had to guess which of the accounts attached and owned around it to\n\t * use. The agent's setup now names them: `agent.connections.add()`. One\n\t * handed in here is refused by name (`not_a_secret`) rather than dropped.\n\t *\n\t * A stored `Secret` is not attached to an agent either. It never was: one\n\t * was accepted here and nothing was done with it. Store one for the agent\n\t * with `ws.secrets.create({ name, value, agentId })`, or attach one of your\n\t * end user's to their conversation with `conversation.attach(secret)`.\n\t */\n\tasync attach(bindings: Bindings): Promise<Agent> {\n\t\tif (!isBindings(bindings)) {\n\t\t\tthrow new AgentNavaError({\n\t\t\t\tmessage:\n\t\t\t\t\t'agent.attach takes { secrets: { NAME: value } }. Accounts go in the agent\\'s setup ' +\n\t\t\t\t\t'(agent.connections.add), and a stored secret attaches to a conversation ' +\n\t\t\t\t\t'(conversation.attach).',\n\t\t\t\tcode: 'not_a_secret',\n\t\t\t\tstatus: 400,\n\t\t\t});\n\t\t}\n\t\trefuseConnections(bindings);\n\t\treturn this.#applyBindings(bindings);\n\t}\n\n\t// ----------------------------------------------------------- conversations\n\n\t/** Start a conversation. It pins the agent's version now and keeps it.\n\t *\n\t * Nothing about accounts is passed here: the version's connections name\n\t * them, and a connection to the end user's account uses the account of\n\t * the person named by `endUserId`. */\n\tasync start(options: StartOptions): Promise<Conversation> {\n\t\tconst endUserId = requireEndUserId(options?.endUserId);\n\t\t// Resolved BEFORE the launch: a Secret is named on the wire by the\n\t\t// declared NAME it fills, and a handle addressed by id may not know its\n\t\t// own yet. Anything that is not a Secret is refused here, by name.\n\t\tconst secretIds = await secretIdsOf(secretsIn(options.attach));\n\t\tconst bindings =\n\t\t\toptions.secrets || secretIds\n\t\t\t\t? {\n\t\t\t\t\t\t...(options.secrets && { secrets: options.secrets }),\n\t\t\t\t\t\t...(options.secrets && options.secretsExpireIn !== undefined && { expiresInSeconds: options.secretsExpireIn }),\n\t\t\t\t\t\t...(secretIds && { secretIds }),\n\t\t\t\t\t}\n\t\t\t\t: undefined;\n\t\tconst data = await this.#http.post<AgentConversationData>('/v1/conversations', {\n\t\t\tagentId: this.id,\n\t\t\t...(options.version !== undefined && { version: options.version }),\n\t\t\t...(options.parameters && { parameters: options.parameters }),\n\t\t\t...(options.memory !== undefined && { memory: options.memory }),\n\t\t\t// `sessionBindings`, NOT `secrets` at the top level.\n\t\t\t//\n\t\t\t// This sent them at the top level and the launch route reads\n\t\t\t// `body.sessionBindings`, so they were dropped in silence: the route's\n\t\t\t// own body type has no top-level `secrets` to read. A conversation\n\t\t\t// started with a credential began without it, and the first turn\n\t\t\t// failed somewhere else entirely.\n\t\t\t//\n\t\t\t// Third place this exact envelope mistake was found in one afternoon,\n\t\t\t// after `agent.bind` and `conversation.bind`. The shape of the bug is\n\t\t\t// always the same: the SDK agrees with itself, the route agrees with\n\t\t\t// itself, and nothing compares the two.\n\t\t\t...(bindings && { sessionBindings: bindings }),\n\t\t\t// Always present: requireEndUserId() above has already refused\n\t\t\t// anything that is not a non-empty string.\n\t\t\tuser: { id: endUserId },\n\t\t\t// ALWAYS a new conversation, even when a user is named.\n\t\t\t//\n\t\t\t// The route resumes that user's most recent conversation when it is\n\t\t\t// given an identity and not told otherwise. That default exists for\n\t\t\t// our own web app, where a page reload must not leave a trail of empty\n\t\t\t// conversations, and it is wrong for a method called `start`: a verb\n\t\t\t// that sometimes does not do the thing is worse than two verbs. It is\n\t\t\t// also ambiguous the moment a user has more than one conversation,\n\t\t\t// since it silently picks the newest.\n\t\t\t//\n\t\t\t// So the SDK does not rely on that default. `start` starts. To\n\t\t\t// continue a conversation, hold its id: `agent.conversation(id)`.\n\t\t\tfresh: true,\n\t\t});\n\t\treturn new Conversation(this.#http, data.id, data, { widgets: options.widgets === true });\n\t}\n\n\t/**\n\t * Address one of this agent's conversations by id. No request.\n\t *\n\t * Pass `{ widgets: true }` when the screen continuing it can draw widgets,\n\t * as on `start`: the choice belongs to the screen, so it is made per handle.\n\t *\n\t * Here rather than on the workspace because a conversation belongs to an\n\t * agent. You already hold the agent: it is a constant in your own code, not\n\t * a per-row value, so naming it costs nothing and the ownership is visible.\n\t *\n\t * ```ts\n\t * await ws.agent(AGENT).conversation(row.conversationId).ask('carry on');\n\t * ```\n\t */\n\tconversation(conversationId: string, options: { widgets?: boolean } = {}): Conversation {\n\t\treturn new Conversation(this.#http, conversationId, undefined, { widgets: options.widgets === true });\n\t}\n\n\t/**\n\t * One of YOUR end users of this agent. No request.\n\t *\n\t * The object everything about a person hangs off: their accounts, their\n\t * secrets, their conversations, and `erase()`. You say who once, here, instead\n\t * of threading an `endUserId` through each call and forgetting it in one of them.\n\t *\n\t * ```ts\n\t * const ananya = agent.endUser(row.userId);\n\t * const { url } = await ananya.accounts.connect({ app: App.GMAIL });\n\t * await ananya.conversations();\n\t * await ananya.erase();\n\t * ```\n\t *\n\t * EACH AGENT IS ITS OWN APP, WITH ITS OWN END USERS. The same id on another agent\n\t * is a different person: their own accounts, secrets and conversations. A person\n\t * using two of your agents signs in to each.\n\t *\n\t * A SCOPE, NOT A RECORD. `ws.agent(id)`, `ws.project(id)`, `ws.account(id)` and\n\t * `ws.secret(id)` point at rows we store and can list back. This points at\n\t * nothing. We do not create your end users and we cannot list them. We only write\n\t * the id you give us onto rows we already own.\n\t *\n\t * NOT a workspace member. `ws.members()` lists the colleagues who share this\n\t * account, with ids we issued and authenticate. This takes an id from YOUR\n\t * database that we store verbatim and never check.\n\t */\n\tendUser(endUserId: string): EndUser {\n\t\treturn new EndUser(this.#http, this.id, endUserId);\n\t}\n\n\t/**\n\t * Who has a memory on this agent, newest change first, at most 200 a call. For\n\t * the next page, pass `before`: the `updatedAt` of the last one you got. Someone\n\t * who asked to stop being remembered is listed, with `stopped: true`.\n\t */\n\tasync memories(options: { limit?: number; before?: string } = {}): Promise<MemoryData[]> {\n\t\tconst res = await this.#http.get<{ memories: MemoryData[] }>(`${this.base}/memories`, {\n\t\t\t...(options.limit !== undefined && { limit: options.limit }),\n\t\t\t...(options.before && { before: options.before }),\n\t\t});\n\t\treturn res.memories ?? [];\n\t}\n\n\t/**\n\t * This agent's most recent conversations, newest first.\n\t *\n\t * NOT ALL OF THEM, and the previous sentence here said \"every\", which was\n\t * wrong in the way that costs someone an afternoon. The server returns the\n\t * newest 50 unless you ask for more, and refuses to return more than 200 on\n\t * any single call (apps/api/src/state.ts listAgentSessions, and the Durable\n\t * Object twin beside it, clamp identically).\n\t *\n\t * For the next page, pass `before`: the `startedAt` of the last conversation\n\t * you got. `agent.endUser(id).conversations()` is the same list for one person.\n\t *\n\t * `endUser` narrows to one of YOUR end users, which is what you want for a\n\t * \"your conversations\" list. Leaving it out returns everyone's, so leave it\n\t * out only for an operator view.\n\t *\n\t * `of: 'customers'` keeps only your end users' conversations, leaving out\n\t * the team's own (members trying the agent, jobs, webhooks); `of: 'team'`\n\t * is the other half. Left out, both.\n\t *\n\t * ```ts\n\t * const mine = await agent.conversations({ endUser: row.userId });\n\t * const shoppers = await agent.conversations({ of: 'customers', limit: 5 });\n\t * ```\n\t */\n\tasync conversations(options: { limit?: number; endUser?: string; before?: string; of?: 'customers' | 'team' } = {}): Promise<Conversation[]> {\n\t\tconst res = await this.#http.get<{ conversations: AgentConversationData[] }>('/v1/conversations', {\n\t\t\tagentId: this.id,\n\t\t\t...(options.limit !== undefined && { limit: options.limit }),\n\t\t\t// The next page, newest first: pass the `startedAt` of the last one you got.\n\t\t\t...(options.before !== undefined && { before: options.before }),\n\t\t\t// One end user's history. Sending this used to be silently ignored on\n\t\t\t// this route, which answered with everyone's conversations, so an\n\t\t\t// application rendering \"your chats\" showed somebody else's.\n\t\t\t...(options.endUser !== undefined && { end_user_id: options.endUser }),\n\t\t\t...(options.of !== undefined && { of: options.of }),\n\t\t});\n\t\treturn (res.conversations ?? []).map((c) => new Conversation(this.#http, c.id, c));\n\t}\n}\n\n/** What GET /v1/agents/:id/spec answers, as far as `draft()` reads it. */\ninterface DraftWire {\n\tisDraft?: boolean;\n\thasUnpublishedChanges?: boolean;\n\tspec: AgentFields;\n}\n\n/** GET and PUT /v1/agents/:id/website-chat-access, as sent. */\ninterface WebsiteChatWire {\n\tagentId: string;\n\taccess: WebsiteChatAccess;\n\tchatUrl: string;\n}\n\nfunction toWebsiteChat(wire: WebsiteChatWire): WebsiteChat {\n\treturn { access: wire.access, chatUrl: wire.chatUrl, embedAgentId: wire.agentId };\n}\n\n/** The end user a conversation belongs to: a non-empty string, or a thrown error before any request. */\nfunction requireEndUserId(value: unknown): string {\n\t// Checked at RUNTIME as well as in the type: a JavaScript caller, or a\n\t// TypeScript one reading a nullable column, can still pass null, a number or\n\t// ''. Each used to start an ownerless conversation that looked fine.\n\tif (typeof value === 'string' && value.trim()) return value.trim();\n\tthrow new AgentNavaError({\n\t\tmessage: 'agent.start() needs endUserId: a non-empty string naming who the conversation is for (your user id, a visitor id, or a job name).',\n\t\tcode: 'end_user_required',\n\t\tstatus: 400,\n\t});\n}\n"]}
package/end-user.d.ts CHANGED
@@ -3,6 +3,7 @@ import { Secret } from './secret.ts';
3
3
  import { Conversation } from './conversation.ts';
4
4
  import type { Http } from './http.ts';
5
5
  import type { AppId } from './apps.generated.ts';
6
+ import type { MemoryData } from './types.ts';
6
7
  /**
7
8
  * One of YOUR end users, of ONE agent, as this platform sees them.
8
9
  *
@@ -177,6 +178,27 @@ export declare class EndUser {
177
178
  * are not in here: `ws.secrets.list()` answers that. */
178
179
  list: () => Promise<Secret[]>;
179
180
  };
181
+ /**
182
+ * What this agent remembers about them. Nothing here runs during a chat: this
183
+ * is for your own screens and tools. Your whole team may read and edit it, and
184
+ * an API key may.
185
+ *
186
+ * Nothing in it is secret from them: the agent can repeat any line if they ask.
187
+ */
188
+ readonly memory: {
189
+ /** Their memory. Empty text if there is none yet. */
190
+ get: () => Promise<MemoryData>;
191
+ /**
192
+ * Replace it, one fact per line, at most 30 lines. Refused if it holds a card,
193
+ * key or id number, or is too long. Chats from before this save can no longer
194
+ * change it.
195
+ */
196
+ set: (text: string) => Promise<MemoryData>;
197
+ /** Delete it. No copy is kept, and their earlier chats cannot bring it back. A later chat can start a new one. */
198
+ delete: () => Promise<void>;
199
+ /** They asked to stop: delete it and keep nothing more about them. `false` undoes this, only if they ask. */
200
+ stop: (stopped?: boolean) => Promise<MemoryData>;
201
+ };
180
202
  /**
181
203
  * Everything this person has said to this agent, newest first. Pass `before`
182
204
  * from the previous page to keep going, rather than being capped at 200.
@@ -187,8 +209,8 @@ export declare class EndUser {
187
209
  }): Promise<Conversation[]>;
188
210
  /**
189
211
  * Delete everything about them on this agent, in one call: their accounts
190
- * (signed out at the app first), their secrets, their conversations, and the
191
- * wakeups those conversations set. What off-boarding and an erasure request call.
212
+ * (signed out at the app first), their secrets, their conversations, the wakeups
213
+ * those conversations set, and what the agent remembers about them. What off-boarding and an erasure request call.
192
214
  *
193
215
  * This agent only. The same id on another agent may be someone else, so there
194
216
  * is no workspace-wide erase: if one person uses two of your agents, erase them
package/end-user.js CHANGED
@@ -201,6 +201,29 @@ export class EndUser {
201
201
  return (res.secrets ?? []).map((m) => new Secret(this.#http, m.id, m));
202
202
  },
203
203
  };
204
+ /**
205
+ * What this agent remembers about them. Nothing here runs during a chat: this
206
+ * is for your own screens and tools. Your whole team may read and edit it, and
207
+ * an API key may.
208
+ *
209
+ * Nothing in it is secret from them: the agent can repeat any line if they ask.
210
+ */
211
+ memory = {
212
+ /** Their memory. Empty text if there is none yet. */
213
+ get: () => this.#http.get(`${this.base}/memory`),
214
+ /**
215
+ * Replace it, one fact per line, at most 30 lines. Refused if it holds a card,
216
+ * key or id number, or is too long. Chats from before this save can no longer
217
+ * change it.
218
+ */
219
+ set: (text) => this.#http.put(`${this.base}/memory`, { text }),
220
+ /** Delete it. No copy is kept, and their earlier chats cannot bring it back. A later chat can start a new one. */
221
+ delete: async () => {
222
+ await this.#http.delete(`${this.base}/memory`);
223
+ },
224
+ /** They asked to stop: delete it and keep nothing more about them. `false` undoes this, only if they ask. */
225
+ stop: (stopped = true) => this.#http.put(`${this.base}/memory/stop`, { stopped }),
226
+ };
204
227
  /**
205
228
  * Everything this person has said to this agent, newest first. Pass `before`
206
229
  * from the previous page to keep going, rather than being capped at 200.
@@ -216,8 +239,8 @@ export class EndUser {
216
239
  }
217
240
  /**
218
241
  * Delete everything about them on this agent, in one call: their accounts
219
- * (signed out at the app first), their secrets, their conversations, and the
220
- * wakeups those conversations set. What off-boarding and an erasure request call.
242
+ * (signed out at the app first), their secrets, their conversations, the wakeups
243
+ * those conversations set, and what the agent remembers about them. What off-boarding and an erasure request call.
221
244
  *
222
245
  * This agent only. The same id on another agent may be someone else, so there
223
246
  * is no workspace-wide erase: if one person uses two of your agents, erase them
package/end-user.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"end-user.js","sourceRoot":"","sources":["../src/end-user.ts"],"names":[],"mappings":"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","sourcesContent":["// One of YOUR end users: a person your application serves, who never signs in here.\n\nimport { Account, accountIn } from './account.ts';\nimport { Secret } from './secret.ts';\nimport { Conversation } from './conversation.ts';\nimport type { Http } from './http.ts';\nimport type { AppId } from './apps.generated.ts';\nimport type { AccountData, AgentConversationData, SecretMeta } from './types.ts';\n\n/**\n * One of YOUR end users, of ONE agent, as this platform sees them.\n *\n * EACH AGENT IS ITS OWN APP, WITH ITS OWN END USERS. The same id on two agents is\n * two different people, with separate accounts, secrets and conversations. A\n * person using two of your agents signs in to each. There is no workspace-wide\n * end user, and no way to erase one, because the same id on another agent may be\n * someone else.\n *\n * WHY THE NAME IS `EndUser` AND NOT `User`. There are two populations of people\n * in this product, and calling both of them \"user\" was a bug waiting to be\n * written:\n *\n * | | workspace member | END USER |\n * |---|---|---|\n * | who | your colleague, on a seat | the person your application serves |\n * | belongs to | the workspace | one agent |\n * | signs in to this platform | yes | NEVER |\n * | who issues the id | we do | YOUR database |\n * | do we authenticate it | yes | no, deliberately |\n * | in this SDK | `ws.members()` | `agent.endUser(id)` |\n *\n * They were both spelled `userId`, so `ws.members()` handed you an id that\n * `agent.start()` would accept without complaint and label a conversation with\n * a teammate's internal platform id. It could never have errored, because an\n * end-user id is never checked against anything. The two names now make that\n * call impossible to write by accident.\n *\n * NOT `Customer`, which was tried and reverted. Two reasons. \"Customer\" already\n * means the party that BUYS this platform, in our own strategy documents and in\n * `subscriptions.stripe_customer_id`, so the word points the other way. And it\n * is wrong for the common case: an enterprise deploying an internal workflow\n * agent serves its own EMPLOYEES, who are not anybody's customers.\n *\n * WHY THIS OBJECT EXISTS. You are building an application for many people.\n * Ananya has her own Gmail, her own conversations and her own history, and so\n * do the other ten thousand. Every call you make is on behalf of one of them.\n *\n * Without this, the person is an argument: `endUserId` threaded through\n * `accounts.connect`, `secrets.create`, `conversations` and `start`, and\n * forgotten in exactly one of them one day. With it, you say who once and the\n * rest follows:\n *\n * ```ts\n * // once, in the agent's setup: each person uses their own Gmail\n * await agent.connections.add(Connection.endUserAccount(App.GMAIL));\n *\n * const ananya = agent.endUser(row.userId); // no request\n * const { url } = await ananya.accounts.connect({ app: App.GMAIL });\n * showToUser(url); // she signs in ONCE, for this agent\n *\n * const conversation = await agent.start({ endUserId: ananya.id });\n * await conversation.ask('what did I miss this morning?');\n *\n * await ananya.conversations(); // everything of hers, with this agent\n * await ananya.erase(); // she left: all of it, on this agent\n * ```\n *\n * WHAT NAMING AN END USER DOES, exactly. `start({ endUserId })` labels the\n * conversation so it can be listed back to them, and it decides whose account\n * a connection to the end user's account uses: this person's, on this agent, for\n * that app, and nobody else's. The grant is the agent's setup, which you can read;\n * it never falls back to the workspace's account, or to anybody else's.\n *\n * THE ID IS YOURS. A row id from your database, opaque to us, and we never\n * authenticate it. We are not an identity provider and do not want to be. It\n * also means passing the wrong one hands that person somebody else's mailbox,\n * which is a sharper consequence than the same mistake has for a chat list.\n *\n * Holding one makes NO request.\n */\nexport class EndUser {\n\treadonly id: string;\n\t/** The agent this person belongs to, as you addressed it. */\n\treadonly agentId: string;\n\treadonly #http: Http;\n\n\tconstructor(http: Http, agentId: string, id: string) {\n\t\tthis.#http = http;\n\t\tthis.agentId = agentId;\n\t\tthis.id = id;\n\t}\n\n\tprivate get base(): string {\n\t\treturn `/v1/agents/${encodeURIComponent(this.agentId)}/end-users/${encodeURIComponent(this.id)}`;\n\t}\n\n\t/**\n\t * This person's own accounts on this agent: the \"end user's account\" that a\n\t * connection built with `Connection.endUserAccount(app)` uses.\n\t *\n\t * Connected ONCE, on the app's own page. After that the account is theirs\n\t * on this agent and stays theirs: every conversation of theirs with this agent\n\t * uses it, and nobody is sent through a sign-in again per thread. Nothing is\n\t * attached per conversation.\n\t *\n\t * An agent never reaches these any other way: not through the workspace's\n\t * own account of the same app, not in another person's conversation, and not\n\t * on another agent. The same id on another agent is another person, who signs\n\t * in there.\n\t */\n\treadonly accounts = {\n\t\t/** Begin connecting one of their accounts. Returns it, still `pending`,\n\t\t * and the url to send them to. It is an ordinary `Account`:\n\t\t * `account.waitUntilConnected()` tells you when they are done, and\n\t\t * `ws.account(id)` reaches it later. `conversation.accountsNeeded()`\n\t\t * hands you the same kind of link for whatever a conversation is still\n\t\t * missing. */\n\t\tconnect: async (input: { app: AppId }): Promise<{ account: Account; url: string }> => {\n\t\t\t// FIELDS PICKED: the agent and the person come from the path, never from the body.\n\t\t\tconst res = await this.#http.post<{ account?: AccountData; url: string }>(`${this.base}/accounts/connect`, {\n\t\t\t\tapp: input.app,\n\t\t\t});\n\t\t\tconst account = accountIn(res, `${this.base}/accounts/connect`);\n\t\t\treturn { account: new Account(this.#http, account.id, account), url: res.url };\n\t\t},\n\n\t\t/** Theirs on this agent alone. The agent creators' accounts are not here:\n\t\t * `ws.accounts.list()` is the view that shows those. */\n\t\tlist: async (): Promise<Account[]> => {\n\t\t\tconst res = await this.#http.get<{ accounts: AccountData[] }>(`${this.base}/accounts`);\n\t\t\treturn (res.accounts ?? []).map((a) => new Account(this.#http, a.id, a));\n\t\t},\n\n\t\t/**\n\t\t * Disconnect every account they connected on this agent, in one call, so it\n\t\t * is not a loop the caller has to remember to write. Every conversation of\n\t\t * theirs with this agent stops reaching those apps from the next turn, and\n\t\t * nothing is kept. Returns how many were disconnected. To remove everything\n\t\t * about them, use `erase()`.\n\t\t */\n\t\tdisconnectAll: async (): Promise<{ disconnected: number }> => {\n\t\t\tconst res = await this.#http.delete<{ disconnected?: number }>(`${this.base}/accounts`);\n\t\t\treturn { disconnected: res?.disconnected ?? 0 };\n\t\t},\n\t};\n\n\t/**\n\t * Credentials that belong to THIS PERSON: their API key for a system you\n\t * integrate with, their token for a service only they have an account on.\n\t *\n\t * THIS WAS ABSENT UNTIL THERE WAS A WAY TO USE IT. A secret reaches a turn\n\t * only by being ATTACHED, and for a long time it could not be, because\n\t * binding one took its VALUE, and you do not hold the value of a secret the\n\t * platform stored for you. So a per-person secret would have been a surface\n\t * that accepted writes, listed them back, and was read by nothing. It was\n\t * left out rather than shipped looking like a feature.\n\t *\n\t * Secrets are still attached; accounts are not (0.44). A person's account is\n\t * reached through the agent's setup (`Connection.endUserAccount`), so there is\n\t * nothing to attach for it.\n\t *\n\t * `sessionBindings.secretIds` is what closed it, and attaching is still an\n\t * explicit line:\n\t *\n\t * ```ts\n\t * const ananya = agent.endUser(row.userId);\n\t * const key = await ananya.secrets.create({ name: 'ZENDESK_TOKEN', value: token });\n\t *\n\t * const conversation = await agent.start({ endUserId: ananya.id });\n\t * await conversation.attach(key); // EXPLICIT. Nothing is implied.\n\t * ```\n\t *\n\t * TWO THINGS THE PLATFORM ENFORCES, neither of which you can turn off:\n\t * the agent must DECLARE that secret name at session scope, so attaching by\n\t * id is not a way around the allowlist; and a secret belonging to one end\n\t * user can only be attached to a conversation launched for that same person\n\t * with this same agent, so one mistyped id cannot hand Ananya's key to somebody\n\t * else's turn.\n\t * Both are checked again at every tool call, not only when you attach.\n\t *\n\t * THE CONVERSATION FOLLOWS THE SECRET. It keeps a pointer, never a copy, so\n\t * when your app gets a new token for this person, `key.update(newToken)`\n\t * reaches every conversation it is attached to on the next call, and\n\t * `key.delete()` stops them all at once.\n\t */\n\treadonly secrets = {\n\t\t/** Store a credential for this person. The value is encrypted on arrival\n\t\t * and never handed back, which is why attaching uses the id. */\n\t\tcreate: async (input: {\n\t\t\tname: string;\n\t\t\tvalue: string;\n\t\t\tdescription?: string;\n\t\t\t/** The only hosts a tool may send it to, checked at every call of a\n\t\t\t * conversation it is attached to. Left out, it goes only to the host\n\t\t\t * the agent's tool writes out in full. */\n\t\t\tallowedHosts?: string[];\n\t\t}): Promise<Secret> => {\n\t\t\t// FIELDS PICKED, never the input object forwarded. Removing a field\n\t\t\t// from a type does not stop a JavaScript caller sending it, and\n\t\t\t// forwarding wholesale is how `endUserId` kept reaching the API after\n\t\t\t// it had supposedly been removed.\n\t\t\tconst res = await this.#http.post<{ secret?: SecretMeta } & SecretMeta>('/v1/secrets', {\n\t\t\t\tname: input.name,\n\t\t\t\tvalue: input.value,\n\t\t\t\tendUserId: this.id,\n\t\t\t\tagentId: this.agentId,\n\t\t\t\t...(input.description !== undefined && { description: input.description }),\n\t\t\t\t...(input.allowedHosts !== undefined && { allowedHosts: input.allowedHosts }),\n\t\t\t});\n\t\t\tconst meta = res.secret ?? res;\n\t\t\treturn new Secret(this.#http, meta.id, meta);\n\t\t},\n\t\t/** This person's secrets on this agent, values excluded. The workspace's own\n\t\t * are not in here: `ws.secrets.list()` answers that. */\n\t\tlist: async (): Promise<Secret[]> => {\n\t\t\tconst res = await this.#http.get<{ secrets: SecretMeta[] }>('/v1/secrets', {\n\t\t\t\tend_user_id: this.id,\n\t\t\t\tagentId: this.agentId,\n\t\t\t});\n\t\t\treturn (res.secrets ?? []).map((m) => new Secret(this.#http, m.id, m));\n\t\t},\n\t};\n\n\t/**\n\t * Everything this person has said to this agent, newest first. Pass `before`\n\t * from the previous page to keep going, rather than being capped at 200.\n\t */\n\tasync conversations(options: { limit?: number; before?: string } = {}): Promise<Conversation[]> {\n\t\tconst res = await this.#http.get<{ conversations: AgentConversationData[] }>('/v1/conversations', {\n\t\t\tagentId: this.agentId,\n\t\t\tend_user_id: this.id,\n\t\t\t...(options.limit !== undefined && { limit: options.limit }),\n\t\t\t...(options.before && { before: options.before }),\n\t\t});\n\t\treturn (res.conversations ?? []).map((c) => new Conversation(this.#http, c.id, c));\n\t}\n\n\t/**\n\t * Delete everything about them on this agent, in one call: their accounts\n\t * (signed out at the app first), their secrets, their conversations, and the\n\t * wakeups those conversations set. What off-boarding and an erasure request call.\n\t *\n\t * This agent only. The same id on another agent may be someone else, so there\n\t * is no workspace-wide erase: if one person uses two of your agents, erase them\n\t * from each. A schedule you configured on the agent to name them stays, because\n\t * it is your setup. A project's conversations belong to the project and are\n\t * deleted by id.\n\t *\n\t * Safe to call again. An account that cannot be signed out fails the call with\n\t * the rest still in place, so a credential is never orphaned. A turn still running\n\t * when this lands can write after it, and a second call clears what it left.\n\t * Returns how many of each went.\n\t */\n\tasync erase(): Promise<{ accounts: number; secrets: number; conversations: number }> {\n\t\tconst total = { accounts: 0, secrets: 0, conversations: 0 };\n\t\tlet more = true;\n\t\twhile (more) {\n\t\t\tconst res = await this.#http.delete<{ accounts?: number; secrets?: number; conversations?: number; more?: boolean }>(this.base);\n\t\t\ttotal.accounts += res?.accounts ?? 0;\n\t\t\ttotal.secrets += res?.secrets ?? 0;\n\t\t\ttotal.conversations += res?.conversations ?? 0;\n\t\t\tmore = res?.more === true;\n\t\t}\n\t\treturn total;\n\t}\n}\n"]}
1
+ {"version":3,"file":"end-user.js","sourceRoot":"","sources":["../src/end-user.ts"],"names":[],"mappings":"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","sourcesContent":["// One of YOUR end users: a person your application serves, who never signs in here.\n\nimport { Account, accountIn } from './account.ts';\nimport { Secret } from './secret.ts';\nimport { Conversation } from './conversation.ts';\nimport type { Http } from './http.ts';\nimport type { AppId } from './apps.generated.ts';\nimport type { AccountData, AgentConversationData, MemoryData, SecretMeta } from './types.ts';\n\n/**\n * One of YOUR end users, of ONE agent, as this platform sees them.\n *\n * EACH AGENT IS ITS OWN APP, WITH ITS OWN END USERS. The same id on two agents is\n * two different people, with separate accounts, secrets and conversations. A\n * person using two of your agents signs in to each. There is no workspace-wide\n * end user, and no way to erase one, because the same id on another agent may be\n * someone else.\n *\n * WHY THE NAME IS `EndUser` AND NOT `User`. There are two populations of people\n * in this product, and calling both of them \"user\" was a bug waiting to be\n * written:\n *\n * | | workspace member | END USER |\n * |---|---|---|\n * | who | your colleague, on a seat | the person your application serves |\n * | belongs to | the workspace | one agent |\n * | signs in to this platform | yes | NEVER |\n * | who issues the id | we do | YOUR database |\n * | do we authenticate it | yes | no, deliberately |\n * | in this SDK | `ws.members()` | `agent.endUser(id)` |\n *\n * They were both spelled `userId`, so `ws.members()` handed you an id that\n * `agent.start()` would accept without complaint and label a conversation with\n * a teammate's internal platform id. It could never have errored, because an\n * end-user id is never checked against anything. The two names now make that\n * call impossible to write by accident.\n *\n * NOT `Customer`, which was tried and reverted. Two reasons. \"Customer\" already\n * means the party that BUYS this platform, in our own strategy documents and in\n * `subscriptions.stripe_customer_id`, so the word points the other way. And it\n * is wrong for the common case: an enterprise deploying an internal workflow\n * agent serves its own EMPLOYEES, who are not anybody's customers.\n *\n * WHY THIS OBJECT EXISTS. You are building an application for many people.\n * Ananya has her own Gmail, her own conversations and her own history, and so\n * do the other ten thousand. Every call you make is on behalf of one of them.\n *\n * Without this, the person is an argument: `endUserId` threaded through\n * `accounts.connect`, `secrets.create`, `conversations` and `start`, and\n * forgotten in exactly one of them one day. With it, you say who once and the\n * rest follows:\n *\n * ```ts\n * // once, in the agent's setup: each person uses their own Gmail\n * await agent.connections.add(Connection.endUserAccount(App.GMAIL));\n *\n * const ananya = agent.endUser(row.userId); // no request\n * const { url } = await ananya.accounts.connect({ app: App.GMAIL });\n * showToUser(url); // she signs in ONCE, for this agent\n *\n * const conversation = await agent.start({ endUserId: ananya.id });\n * await conversation.ask('what did I miss this morning?');\n *\n * await ananya.conversations(); // everything of hers, with this agent\n * await ananya.erase(); // she left: all of it, on this agent\n * ```\n *\n * WHAT NAMING AN END USER DOES, exactly. `start({ endUserId })` labels the\n * conversation so it can be listed back to them, and it decides whose account\n * a connection to the end user's account uses: this person's, on this agent, for\n * that app, and nobody else's. The grant is the agent's setup, which you can read;\n * it never falls back to the workspace's account, or to anybody else's.\n *\n * THE ID IS YOURS. A row id from your database, opaque to us, and we never\n * authenticate it. We are not an identity provider and do not want to be. It\n * also means passing the wrong one hands that person somebody else's mailbox,\n * which is a sharper consequence than the same mistake has for a chat list.\n *\n * Holding one makes NO request.\n */\nexport class EndUser {\n\treadonly id: string;\n\t/** The agent this person belongs to, as you addressed it. */\n\treadonly agentId: string;\n\treadonly #http: Http;\n\n\tconstructor(http: Http, agentId: string, id: string) {\n\t\tthis.#http = http;\n\t\tthis.agentId = agentId;\n\t\tthis.id = id;\n\t}\n\n\tprivate get base(): string {\n\t\treturn `/v1/agents/${encodeURIComponent(this.agentId)}/end-users/${encodeURIComponent(this.id)}`;\n\t}\n\n\t/**\n\t * This person's own accounts on this agent: the \"end user's account\" that a\n\t * connection built with `Connection.endUserAccount(app)` uses.\n\t *\n\t * Connected ONCE, on the app's own page. After that the account is theirs\n\t * on this agent and stays theirs: every conversation of theirs with this agent\n\t * uses it, and nobody is sent through a sign-in again per thread. Nothing is\n\t * attached per conversation.\n\t *\n\t * An agent never reaches these any other way: not through the workspace's\n\t * own account of the same app, not in another person's conversation, and not\n\t * on another agent. The same id on another agent is another person, who signs\n\t * in there.\n\t */\n\treadonly accounts = {\n\t\t/** Begin connecting one of their accounts. Returns it, still `pending`,\n\t\t * and the url to send them to. It is an ordinary `Account`:\n\t\t * `account.waitUntilConnected()` tells you when they are done, and\n\t\t * `ws.account(id)` reaches it later. `conversation.accountsNeeded()`\n\t\t * hands you the same kind of link for whatever a conversation is still\n\t\t * missing. */\n\t\tconnect: async (input: { app: AppId }): Promise<{ account: Account; url: string }> => {\n\t\t\t// FIELDS PICKED: the agent and the person come from the path, never from the body.\n\t\t\tconst res = await this.#http.post<{ account?: AccountData; url: string }>(`${this.base}/accounts/connect`, {\n\t\t\t\tapp: input.app,\n\t\t\t});\n\t\t\tconst account = accountIn(res, `${this.base}/accounts/connect`);\n\t\t\treturn { account: new Account(this.#http, account.id, account), url: res.url };\n\t\t},\n\n\t\t/** Theirs on this agent alone. The agent creators' accounts are not here:\n\t\t * `ws.accounts.list()` is the view that shows those. */\n\t\tlist: async (): Promise<Account[]> => {\n\t\t\tconst res = await this.#http.get<{ accounts: AccountData[] }>(`${this.base}/accounts`);\n\t\t\treturn (res.accounts ?? []).map((a) => new Account(this.#http, a.id, a));\n\t\t},\n\n\t\t/**\n\t\t * Disconnect every account they connected on this agent, in one call, so it\n\t\t * is not a loop the caller has to remember to write. Every conversation of\n\t\t * theirs with this agent stops reaching those apps from the next turn, and\n\t\t * nothing is kept. Returns how many were disconnected. To remove everything\n\t\t * about them, use `erase()`.\n\t\t */\n\t\tdisconnectAll: async (): Promise<{ disconnected: number }> => {\n\t\t\tconst res = await this.#http.delete<{ disconnected?: number }>(`${this.base}/accounts`);\n\t\t\treturn { disconnected: res?.disconnected ?? 0 };\n\t\t},\n\t};\n\n\t/**\n\t * Credentials that belong to THIS PERSON: their API key for a system you\n\t * integrate with, their token for a service only they have an account on.\n\t *\n\t * THIS WAS ABSENT UNTIL THERE WAS A WAY TO USE IT. A secret reaches a turn\n\t * only by being ATTACHED, and for a long time it could not be, because\n\t * binding one took its VALUE, and you do not hold the value of a secret the\n\t * platform stored for you. So a per-person secret would have been a surface\n\t * that accepted writes, listed them back, and was read by nothing. It was\n\t * left out rather than shipped looking like a feature.\n\t *\n\t * Secrets are still attached; accounts are not (0.44). A person's account is\n\t * reached through the agent's setup (`Connection.endUserAccount`), so there is\n\t * nothing to attach for it.\n\t *\n\t * `sessionBindings.secretIds` is what closed it, and attaching is still an\n\t * explicit line:\n\t *\n\t * ```ts\n\t * const ananya = agent.endUser(row.userId);\n\t * const key = await ananya.secrets.create({ name: 'ZENDESK_TOKEN', value: token });\n\t *\n\t * const conversation = await agent.start({ endUserId: ananya.id });\n\t * await conversation.attach(key); // EXPLICIT. Nothing is implied.\n\t * ```\n\t *\n\t * TWO THINGS THE PLATFORM ENFORCES, neither of which you can turn off:\n\t * the agent must DECLARE that secret name at session scope, so attaching by\n\t * id is not a way around the allowlist; and a secret belonging to one end\n\t * user can only be attached to a conversation launched for that same person\n\t * with this same agent, so one mistyped id cannot hand Ananya's key to somebody\n\t * else's turn.\n\t * Both are checked again at every tool call, not only when you attach.\n\t *\n\t * THE CONVERSATION FOLLOWS THE SECRET. It keeps a pointer, never a copy, so\n\t * when your app gets a new token for this person, `key.update(newToken)`\n\t * reaches every conversation it is attached to on the next call, and\n\t * `key.delete()` stops them all at once.\n\t */\n\treadonly secrets = {\n\t\t/** Store a credential for this person. The value is encrypted on arrival\n\t\t * and never handed back, which is why attaching uses the id. */\n\t\tcreate: async (input: {\n\t\t\tname: string;\n\t\t\tvalue: string;\n\t\t\tdescription?: string;\n\t\t\t/** The only hosts a tool may send it to, checked at every call of a\n\t\t\t * conversation it is attached to. Left out, it goes only to the host\n\t\t\t * the agent's tool writes out in full. */\n\t\t\tallowedHosts?: string[];\n\t\t}): Promise<Secret> => {\n\t\t\t// FIELDS PICKED, never the input object forwarded. Removing a field\n\t\t\t// from a type does not stop a JavaScript caller sending it, and\n\t\t\t// forwarding wholesale is how `endUserId` kept reaching the API after\n\t\t\t// it had supposedly been removed.\n\t\t\tconst res = await this.#http.post<{ secret?: SecretMeta } & SecretMeta>('/v1/secrets', {\n\t\t\t\tname: input.name,\n\t\t\t\tvalue: input.value,\n\t\t\t\tendUserId: this.id,\n\t\t\t\tagentId: this.agentId,\n\t\t\t\t...(input.description !== undefined && { description: input.description }),\n\t\t\t\t...(input.allowedHosts !== undefined && { allowedHosts: input.allowedHosts }),\n\t\t\t});\n\t\t\tconst meta = res.secret ?? res;\n\t\t\treturn new Secret(this.#http, meta.id, meta);\n\t\t},\n\t\t/** This person's secrets on this agent, values excluded. The workspace's own\n\t\t * are not in here: `ws.secrets.list()` answers that. */\n\t\tlist: async (): Promise<Secret[]> => {\n\t\t\tconst res = await this.#http.get<{ secrets: SecretMeta[] }>('/v1/secrets', {\n\t\t\t\tend_user_id: this.id,\n\t\t\t\tagentId: this.agentId,\n\t\t\t});\n\t\t\treturn (res.secrets ?? []).map((m) => new Secret(this.#http, m.id, m));\n\t\t},\n\t};\n\n\t/**\n\t * What this agent remembers about them. Nothing here runs during a chat: this\n\t * is for your own screens and tools. Your whole team may read and edit it, and\n\t * an API key may.\n\t *\n\t * Nothing in it is secret from them: the agent can repeat any line if they ask.\n\t */\n\treadonly memory = {\n\t\t/** Their memory. Empty text if there is none yet. */\n\t\tget: (): Promise<MemoryData> => this.#http.get<MemoryData>(`${this.base}/memory`),\n\t\t/**\n\t\t * Replace it, one fact per line, at most 30 lines. Refused if it holds a card,\n\t\t * key or id number, or is too long. Chats from before this save can no longer\n\t\t * change it.\n\t\t */\n\t\tset: (text: string): Promise<MemoryData> => this.#http.put<MemoryData>(`${this.base}/memory`, { text }),\n\t\t/** Delete it. No copy is kept, and their earlier chats cannot bring it back. A later chat can start a new one. */\n\t\tdelete: async (): Promise<void> => {\n\t\t\tawait this.#http.delete(`${this.base}/memory`);\n\t\t},\n\t\t/** They asked to stop: delete it and keep nothing more about them. `false` undoes this, only if they ask. */\n\t\tstop: (stopped = true): Promise<MemoryData> => this.#http.put<MemoryData>(`${this.base}/memory/stop`, { stopped }),\n\t};\n\n\t/**\n\t * Everything this person has said to this agent, newest first. Pass `before`\n\t * from the previous page to keep going, rather than being capped at 200.\n\t */\n\tasync conversations(options: { limit?: number; before?: string } = {}): Promise<Conversation[]> {\n\t\tconst res = await this.#http.get<{ conversations: AgentConversationData[] }>('/v1/conversations', {\n\t\t\tagentId: this.agentId,\n\t\t\tend_user_id: this.id,\n\t\t\t...(options.limit !== undefined && { limit: options.limit }),\n\t\t\t...(options.before && { before: options.before }),\n\t\t});\n\t\treturn (res.conversations ?? []).map((c) => new Conversation(this.#http, c.id, c));\n\t}\n\n\t/**\n\t * Delete everything about them on this agent, in one call: their accounts\n\t * (signed out at the app first), their secrets, their conversations, the wakeups\n\t * those conversations set, and what the agent remembers about them. What off-boarding and an erasure request call.\n\t *\n\t * This agent only. The same id on another agent may be someone else, so there\n\t * is no workspace-wide erase: if one person uses two of your agents, erase them\n\t * from each. A schedule you configured on the agent to name them stays, because\n\t * it is your setup. A project's conversations belong to the project and are\n\t * deleted by id.\n\t *\n\t * Safe to call again. An account that cannot be signed out fails the call with\n\t * the rest still in place, so a credential is never orphaned. A turn still running\n\t * when this lands can write after it, and a second call clears what it left.\n\t * Returns how many of each went.\n\t */\n\tasync erase(): Promise<{ accounts: number; secrets: number; conversations: number }> {\n\t\tconst total = { accounts: 0, secrets: 0, conversations: 0 };\n\t\tlet more = true;\n\t\twhile (more) {\n\t\t\tconst res = await this.#http.delete<{ accounts?: number; secrets?: number; conversations?: number; more?: boolean }>(this.base);\n\t\t\ttotal.accounts += res?.accounts ?? 0;\n\t\t\ttotal.secrets += res?.secrets ?? 0;\n\t\t\ttotal.conversations += res?.conversations ?? 0;\n\t\t\tmore = res?.more === true;\n\t\t}\n\t\treturn total;\n\t}\n}\n"]}
package/index.d.ts CHANGED
@@ -17,5 +17,5 @@ export { loadAgentDir } from './load-agent-dir.ts';
17
17
  export { loadManagerTemplate } from './templates.ts';
18
18
  export type { Turn } from './turn.ts';
19
19
  export type { ClientOptions } from './http.ts';
20
- export type { AppTool, AccountData, AccountNeeded, AccountStatus, AgentConnection, AgentConversationData, AgentData, AgentDraft, AgentFields, AgentStatus, WebsiteChatAppearance, Attachment, Bindings, ConnectionStatus, Credits, Guardrails, DatabaseColumn, DatabaseData, DatabaseEngine, DatabaseFields, DatabaseLogin, DatabaseTable, DatabaseTestResult, DatabaseTls, DatabaseUsedBy, FileEntry, Filesystem, Grade, HttpTool, HttpToolParam, McpServer, ProjectAgent, Message, SentAttachment, Parameter, ProjectConversationData, ProjectData, ProjectFields, Requirement, Scope, SeatPlan, SecretMeta, SecretRequirement, Step, Trigger, WebhookDelivery, WebhookVerify, TriggerTarget, TurnEvent, Widget, WidgetPart, UsedBy, WebsiteChat, WebsiteChatAccess, Workflow, WorkspaceInfo, WorkspaceMember, } from './types.ts';
20
+ export type { AppTool, AccountData, AccountNeeded, AccountStatus, AgentConnection, AgentConversationData, AgentData, AgentDraft, AgentFields, AgentStatus, WebsiteChatAppearance, Attachment, Bindings, ConnectionStatus, Credits, Guardrails, MemoryData, MemorySettings, DatabaseColumn, DatabaseData, DatabaseEngine, DatabaseFields, DatabaseLogin, DatabaseTable, DatabaseTestResult, DatabaseTls, DatabaseUsedBy, FileEntry, Filesystem, Grade, HttpTool, HttpToolParam, McpServer, ProjectAgent, Message, SentAttachment, Parameter, ProjectConversationData, ProjectData, ProjectFields, Requirement, Scope, SeatPlan, SecretMeta, SecretRequirement, Step, Trigger, WebhookDelivery, WebhookVerify, TriggerTarget, TurnEvent, Widget, WidgetPart, UsedBy, WebsiteChat, WebsiteChatAccess, Workflow, WorkspaceInfo, WorkspaceMember, } from './types.ts';
21
21
  export { placeWidgets, type ReplyPiece } from './widgets.ts';
package/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,8BAA8B;AAC9B,EAAE;AACF,0DAA0D;AAC1D,EAAE;AACF,6DAA6D;AAC7D,EAAE;AACF,yCAAyC;AACzC,2CAA2C;AAC3C,8BAA8B;AAC9B,8EAA8E;AAC9E,QAAQ;AACR,uEAAuE;AACvE,2EAA2E;AAC3E,EAAE;AACF,yDAAyD;AAEzD,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AACtD,OAAO,EAAE,UAAU,EAAgE,MAAM,kBAAkB,CAAC;AAC5G,OAAO,EAAE,KAAK,EAAqB,MAAM,YAAY,CAAC;AACtD,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,2EAA2E;AAC3E,iFAAiF;AACjF,OAAO,EACN,UAAU,GAGV,MAAM,iBAAiB,CAAC;AACzB,6EAA6E;AAC7E,wEAAwE;AACxE,OAAO,EAAE,GAAG,EAAc,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AACxC,OAAO,EAAE,YAAY,EAA4D,MAAM,mBAAmB,CAAC;AAC3G,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,OAAO,EAA4B,MAAM,cAAc,CAAC;AACjE,OAAO,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACnE,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC9D,yEAAyE;AACzE,8EAA8E;AAC9E,6EAA6E;AAC7E,2DAA2D;AAC3D,OAAO,EACN,WAAW,EACX,WAAW,EACX,cAAc,EACd,WAAW,EACX,QAAQ,EACR,YAAY,GASZ,MAAM,mBAAmB,CAAC;AAC3B,4EAA4E;AAC5E,kEAAkE;AAClE,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,+EAA+E;AAC/E,6EAA6E;AAC7E,2BAA2B;AAC3B,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AA8DrD,OAAO,EAAE,YAAY,EAAmB,MAAM,cAAc,CAAC","sourcesContent":["// @cerebro-labs/agentnava-sdk\n//\n// Configure an agent, start a conversation, work with it.\n//\n// import { AgentNava } from '@cerebro-labs/agentnava-sdk';\n//\n// const ws = AgentNava.getWorkspace();\n// const agent = await ws.agents.create({\n// name: 'Refund checker',\n// instructions: 'Decide whether a late delivery qualifies for a refund.',\n// });\n// const conversation = await agent.start({ endUserId: 'u_ananya' });\n// console.log(await conversation.ask('Order 4471 was nine days late.'));\n//\n// The reference this implements is design/sdk-design.md.\n\nexport { AgentNava, Workspace } from './workspace.ts';\nexport { PartnerApp, type PartnerAppOptions, type ExchangedCode, type CreditGrant } from './partner-app.ts';\nexport { Agent, type StartOptions } from './agent.ts';\nexport { Account } from './account.ts';\n// Builds one entry of an agent's setup: an app, and whose account. NOT the\n// 0.43 `Connection`, which was a handle on a signed-in account and is `Account`.\nexport {\n\tConnection,\n\ttype CreatorAccountConnection,\n\ttype EndUserAccountConnection,\n} from './connection.ts';\n// Every app the platform can connect, as constants: `App.GMAIL === 'gmail'`.\n// Generated from the connections catalog; any plain string still works.\nexport { App, type AppId } from './apps.generated.ts';\nexport { Secret } from './secret.ts';\nexport { EndUser } from './end-user.ts';\nexport { Conversation, type AskOptions, type AttachOptions, type FileUrlOptions } from './conversation.ts';\nexport { KnowledgeBase } from './knowledge-base.ts';\nexport { Database } from './database.ts';\nexport { Project, type ProjectStartOptions } from './project.ts';\nexport { AgentNavaError, SecretHostNotAllowed } from './errors.ts';\nexport { loadWorkflows, parseWorkflow } from './workflows.ts';\n// Draft an agent's tools from whatever a company already has: an OpenAPI\n// document, a Postman collection, or a working curl command. Runs entirely on\n// your machine: nothing about the source is sent to AgentNava, which matters\n// because those files routinely contain a live credential.\nexport {\n\timportTools,\n\tfromOpenApi,\n\tfromOpenApiUrl,\n\tfromPostman,\n\tfromCurl,\n\ttoolNameFrom,\n\ttype ImportSource,\n\ttype ImportOptions,\n\ttype ImportedTools,\n\ttype ImportedSecret,\n\ttype ImportWarning,\n\ttype ImportWarningKind,\n\ttype OpenApiDocument,\n\ttype PostmanCollection,\n} from './import-tools.ts';\n// Reads a whole Claude-Code-shaped agent directory (AGENT.md + workflows/ +\n// mcp-servers.json ...) into the fields ws.agents.create() takes.\nexport { loadAgentDir } from './load-agent-dir.ts';\n// The platform's built-in project-manager configuration. apps/api imports this\n// through the './templates' subpath; re-exported here so the package has one\n// surface rather than two.\nexport { loadManagerTemplate } from './templates.ts';\nexport type { Turn } from './turn.ts';\nexport type { ClientOptions } from './http.ts';\nexport type {\n\tAppTool,\n\tAccountData,\n\tAccountNeeded,\n\tAccountStatus,\n\tAgentConnection,\n\tAgentConversationData,\n\tAgentData,\n\tAgentDraft,\n\tAgentFields,\n\tAgentStatus,\n\tWebsiteChatAppearance,\n\tAttachment,\n\tBindings,\n\tConnectionStatus,\n\tCredits,\n\tGuardrails,\n\tDatabaseColumn,\n\tDatabaseData,\n\tDatabaseEngine,\n\tDatabaseFields,\n\tDatabaseLogin,\n\tDatabaseTable,\n\tDatabaseTestResult,\n\tDatabaseTls,\n\tDatabaseUsedBy,\n\tFileEntry,\n\tFilesystem,\n\tGrade,\n\tHttpTool,\n\tHttpToolParam,\n\tMcpServer,\n\tProjectAgent,\n\tMessage,\n\tSentAttachment,\n\tParameter,\n\tProjectConversationData,\n\tProjectData,\n\tProjectFields,\n\tRequirement,\n\tScope,\n\tSeatPlan,\n\tSecretMeta,\n\tSecretRequirement,\n\tStep,\n\tTrigger,\n\tWebhookDelivery,\n\tWebhookVerify,\n\tTriggerTarget,\n\tTurnEvent,\n\tWidget,\n\tWidgetPart,\n\tUsedBy,\n\tWebsiteChat,\n\tWebsiteChatAccess,\n\tWorkflow,\n\tWorkspaceInfo,\n\tWorkspaceMember,\n} from './types.ts';\nexport { placeWidgets, type ReplyPiece } from './widgets.ts';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,8BAA8B;AAC9B,EAAE;AACF,0DAA0D;AAC1D,EAAE;AACF,6DAA6D;AAC7D,EAAE;AACF,yCAAyC;AACzC,2CAA2C;AAC3C,8BAA8B;AAC9B,8EAA8E;AAC9E,QAAQ;AACR,uEAAuE;AACvE,2EAA2E;AAC3E,EAAE;AACF,yDAAyD;AAEzD,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AACtD,OAAO,EAAE,UAAU,EAAgE,MAAM,kBAAkB,CAAC;AAC5G,OAAO,EAAE,KAAK,EAAqB,MAAM,YAAY,CAAC;AACtD,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,2EAA2E;AAC3E,iFAAiF;AACjF,OAAO,EACN,UAAU,GAGV,MAAM,iBAAiB,CAAC;AACzB,6EAA6E;AAC7E,wEAAwE;AACxE,OAAO,EAAE,GAAG,EAAc,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AACxC,OAAO,EAAE,YAAY,EAA4D,MAAM,mBAAmB,CAAC;AAC3G,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,OAAO,EAA4B,MAAM,cAAc,CAAC;AACjE,OAAO,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACnE,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC9D,yEAAyE;AACzE,8EAA8E;AAC9E,6EAA6E;AAC7E,2DAA2D;AAC3D,OAAO,EACN,WAAW,EACX,WAAW,EACX,cAAc,EACd,WAAW,EACX,QAAQ,EACR,YAAY,GASZ,MAAM,mBAAmB,CAAC;AAC3B,4EAA4E;AAC5E,kEAAkE;AAClE,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,+EAA+E;AAC/E,6EAA6E;AAC7E,2BAA2B;AAC3B,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAgErD,OAAO,EAAE,YAAY,EAAmB,MAAM,cAAc,CAAC","sourcesContent":["// @cerebro-labs/agentnava-sdk\n//\n// Configure an agent, start a conversation, work with it.\n//\n// import { AgentNava } from '@cerebro-labs/agentnava-sdk';\n//\n// const ws = AgentNava.getWorkspace();\n// const agent = await ws.agents.create({\n// name: 'Refund checker',\n// instructions: 'Decide whether a late delivery qualifies for a refund.',\n// });\n// const conversation = await agent.start({ endUserId: 'u_ananya' });\n// console.log(await conversation.ask('Order 4471 was nine days late.'));\n//\n// The reference this implements is design/sdk-design.md.\n\nexport { AgentNava, Workspace } from './workspace.ts';\nexport { PartnerApp, type PartnerAppOptions, type ExchangedCode, type CreditGrant } from './partner-app.ts';\nexport { Agent, type StartOptions } from './agent.ts';\nexport { Account } from './account.ts';\n// Builds one entry of an agent's setup: an app, and whose account. NOT the\n// 0.43 `Connection`, which was a handle on a signed-in account and is `Account`.\nexport {\n\tConnection,\n\ttype CreatorAccountConnection,\n\ttype EndUserAccountConnection,\n} from './connection.ts';\n// Every app the platform can connect, as constants: `App.GMAIL === 'gmail'`.\n// Generated from the connections catalog; any plain string still works.\nexport { App, type AppId } from './apps.generated.ts';\nexport { Secret } from './secret.ts';\nexport { EndUser } from './end-user.ts';\nexport { Conversation, type AskOptions, type AttachOptions, type FileUrlOptions } from './conversation.ts';\nexport { KnowledgeBase } from './knowledge-base.ts';\nexport { Database } from './database.ts';\nexport { Project, type ProjectStartOptions } from './project.ts';\nexport { AgentNavaError, SecretHostNotAllowed } from './errors.ts';\nexport { loadWorkflows, parseWorkflow } from './workflows.ts';\n// Draft an agent's tools from whatever a company already has: an OpenAPI\n// document, a Postman collection, or a working curl command. Runs entirely on\n// your machine: nothing about the source is sent to AgentNava, which matters\n// because those files routinely contain a live credential.\nexport {\n\timportTools,\n\tfromOpenApi,\n\tfromOpenApiUrl,\n\tfromPostman,\n\tfromCurl,\n\ttoolNameFrom,\n\ttype ImportSource,\n\ttype ImportOptions,\n\ttype ImportedTools,\n\ttype ImportedSecret,\n\ttype ImportWarning,\n\ttype ImportWarningKind,\n\ttype OpenApiDocument,\n\ttype PostmanCollection,\n} from './import-tools.ts';\n// Reads a whole Claude-Code-shaped agent directory (AGENT.md + workflows/ +\n// mcp-servers.json ...) into the fields ws.agents.create() takes.\nexport { loadAgentDir } from './load-agent-dir.ts';\n// The platform's built-in project-manager configuration. apps/api imports this\n// through the './templates' subpath; re-exported here so the package has one\n// surface rather than two.\nexport { loadManagerTemplate } from './templates.ts';\nexport type { Turn } from './turn.ts';\nexport type { ClientOptions } from './http.ts';\nexport type {\n\tAppTool,\n\tAccountData,\n\tAccountNeeded,\n\tAccountStatus,\n\tAgentConnection,\n\tAgentConversationData,\n\tAgentData,\n\tAgentDraft,\n\tAgentFields,\n\tAgentStatus,\n\tWebsiteChatAppearance,\n\tAttachment,\n\tBindings,\n\tConnectionStatus,\n\tCredits,\n\tGuardrails,\n\tMemoryData,\n\tMemorySettings,\n\tDatabaseColumn,\n\tDatabaseData,\n\tDatabaseEngine,\n\tDatabaseFields,\n\tDatabaseLogin,\n\tDatabaseTable,\n\tDatabaseTestResult,\n\tDatabaseTls,\n\tDatabaseUsedBy,\n\tFileEntry,\n\tFilesystem,\n\tGrade,\n\tHttpTool,\n\tHttpToolParam,\n\tMcpServer,\n\tProjectAgent,\n\tMessage,\n\tSentAttachment,\n\tParameter,\n\tProjectConversationData,\n\tProjectData,\n\tProjectFields,\n\tRequirement,\n\tScope,\n\tSeatPlan,\n\tSecretMeta,\n\tSecretRequirement,\n\tStep,\n\tTrigger,\n\tWebhookDelivery,\n\tWebhookVerify,\n\tTriggerTarget,\n\tTurnEvent,\n\tWidget,\n\tWidgetPart,\n\tUsedBy,\n\tWebsiteChat,\n\tWebsiteChatAccess,\n\tWorkflow,\n\tWorkspaceInfo,\n\tWorkspaceMember,\n} from './types.ts';\nexport { placeWidgets, type ReplyPiece } from './widgets.ts';\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cerebro-labs/agentnava-sdk",
3
- "version": "0.79.0",
3
+ "version": "0.80.0",
4
4
  "description": "The AgentNava SDK. Configure an agent, start a conversation, and work with it.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
package/types.d.ts CHANGED
@@ -356,6 +356,59 @@ export interface Guardrails {
356
356
  * at once, and its replies and actions are still checked. */
357
357
  checkMessagesFirst?: boolean;
358
358
  }
359
+ /**
360
+ * What an agent keeps about each end user between conversations: the settings,
361
+ * not the memory itself. The memory is written after chats, never during one, and
362
+ * no call or tool asks for it. One memory per agent and end user, and agents never
363
+ * share one.
364
+ *
365
+ * await agent.update({ memorySettings: { on: true, instructions: 'Keep food allergies.' } });
366
+ *
367
+ * Part of the CONFIGURATION, like `guardrails`, so changing it publishes a version.
368
+ * It comes with Team and up. Some limits sit outside these settings: a memory never
369
+ * keeps a password, a key, or a card or id number, whatever `instructions` says.
370
+ */
371
+ export interface MemorySettings {
372
+ /** Keep a memory about each end user. OFF unless set to `true`. */
373
+ on?: boolean;
374
+ /** Added after our default, and wins where they differ. Up to 2,000 characters. */
375
+ instructions?: string;
376
+ /** Days to keep it, since the end user's last chat. 180 unless set. */
377
+ keepForDays?: 30 | 90 | 180 | 365;
378
+ /** Show end users a switch that turns memory off for a chat. ON unless `false`. */
379
+ endUserSwitch?: boolean;
380
+ /** Ask a yes or no on our chat surfaces before memory is used. OFF unless `true`. */
381
+ askFirst?: boolean;
382
+ /** The owner's privacy policy. The notice links to it. */
383
+ privacyUrl?: string;
384
+ }
385
+ /** One agent's memory about one end user. */
386
+ export interface MemoryData {
387
+ endUserId: string;
388
+ /** The whole memory, one fact per line. This is what the agent reads. */
389
+ text: string;
390
+ /** They asked to stop. `text` is empty, and nothing more is kept about them. */
391
+ stopped: boolean;
392
+ /** How far the memory has read their chats: nothing older can change it. Absent while no memory exists. */
393
+ readUpTo?: string;
394
+ /** When it last changed. Absent while no memory exists. Pass it as `before` to page through `agent.memories()`. */
395
+ updatedAt?: string;
396
+ /** When they asked to stop. */
397
+ stoppedAt?: string;
398
+ /** What the last update after a chat did to it. Counts only, none of their words. */
399
+ lastRun?: {
400
+ at: string;
401
+ added: number;
402
+ replaced: number;
403
+ removed: number;
404
+ };
405
+ /** When they first said yes, where the owner asks first. */
406
+ agreedAt?: string;
407
+ /** Their newest chat on this agent. */
408
+ lastChatAt?: string;
409
+ /** How many of their chats on this agent hold a message. */
410
+ conversations?: number;
411
+ }
359
412
  export interface AgentData {
360
413
  id: string;
361
414
  name: string;
@@ -367,6 +420,13 @@ export interface AgentData {
367
420
  rules: string[];
368
421
  checkMessagesFirst: boolean;
369
422
  };
423
+ /** Always present, with the defaults filled in. `notice` is the line to show near
424
+ * the message box if you draw your own chat. It is screen text, written by code,
425
+ * and the model never sees it. */
426
+ memorySettings: Required<Omit<MemorySettings, 'privacyUrl'>> & {
427
+ privacyUrl?: string;
428
+ notice: string;
429
+ };
370
430
  /** How its website chat looks. Absent: the default look, starting closed. */
371
431
  appearance?: WebsiteChatAppearance;
372
432
  grade: Grade;
@@ -579,6 +639,9 @@ export interface AgentConversationData {
579
639
  version: number;
580
640
  startedAt: string;
581
641
  title?: string;
642
+ /** Whether this conversation uses end-user memory, fixed when it started. Absent
643
+ * on one from before memory, which counts as off. */
644
+ memory?: 'on' | 'off';
582
645
  /** True when nothing REQUIRED is outstanding. An optional gap leaves this
583
646
  * true, because the conversation genuinely does work without it. */
584
647
  ready: boolean;
@@ -832,6 +895,8 @@ export interface AgentFields {
832
895
  greeting?: string;
833
896
  /** What the platform enforces on every incoming message. See `Guardrails`. */
834
897
  guardrails?: Guardrails;
898
+ /** What it keeps about each end user between conversations. See `MemorySettings`. */
899
+ memorySettings?: MemorySettings;
835
900
  /**
836
901
  * How the agent's website chat looks, on the share link and through the
837
902
  * embed script. Merged by setting: `{ accent: '#B4532A' }` changes the colour
package/types.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,4DAA4D;AAkB5D;;;mCAGmC;AACnC,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,CAAU,CAAC","sourcesContent":["// The shapes this SDK hands back. Transcribed from design/sdk-design.md's own\n// Types section so the reference and the code cannot drift.\n\n// Type-only: erased from the emitted JavaScript, so these add no runtime cycle\n// with the modules that import their own shapes from here.\nimport type { AppId } from './apps.generated.ts';\nimport type { Connection } from './connection.ts';\n\n/** The quality of model an agent's turns run on, and what each turn costs.\n *\n * `standard` is the default: the fastest model the platform runs. `lite` is\n * the lowest price. `premium` costs the same as `standard` and thinks harder,\n * answering more slowly. `best` is the strongest model.\n *\n * Renamed in 0.49.0: the grade that was `fast` is now `standard`, and the one\n * that was `standard` is now `lite`. The platform still accepts `fast` from\n * older code and treats it as `standard`. */\nexport type Grade = (typeof GRADE_NAMES)[number];\n\n/** Every grade, as a list a reply can be checked against. This SDK is\n * published and cannot import the platform's private price list, so it keeps\n * its own copy of the NAMES; a test in the platform's monorepo fails the\n * moment the two lists differ. */\nexport const GRADE_NAMES = ['lite', 'standard', 'premium', 'best'] as const;\nexport type Filesystem = 'drive' | 'none';\n\n/** Where a SECRET is supplied.\n *\n * 'fixed' is set once on the agent and used by every conversation. 'conversation'\n * is asked for when a conversation starts, so each user brings their own. The\n * two DO NOT substitute for each other: a value set on the agent does not\n * satisfy a declaration scoped 'conversation'.\n *\n * Connections stopped using this in 0.44. A connection says WHOSE ACCOUNT it\n * uses instead: the agent creator's (`account`) or each end user's own\n * (`endUser: true`). See `AgentConnection`. */\nexport type Scope = 'fixed' | 'conversation';\n\nexport interface WorkspaceInfo {\n\tid: string;\n\tname: string;\n\tcredits: number;\n\tcreatedAt: string;\n}\n\n/** One procedure the agent follows when its `when` matches what is in front of\n * it. `when` is one line and is not optional: it is what the agent reads to\n * decide the workflow applies, so a vague one makes a good workflow invisible. */\nexport interface Workflow {\n\tname: string;\n\twhen: string;\n\tcontent: string;\n}\n\n// `SessionMode` is deliberately NOT exposed.\n//\n// The API has one ('per-user' | 'per-call') and 'per-user' makes the server\n// resume a person's most recent conversation. That is the behaviour `start`\n// refuses to have: a verb that sometimes returns something you already had, and\n// which has to guess which one the moment a person has two.\n//\n// Its other effect, fanning a scheduled firing out across the people who have\n// conversations with an agent, is real and worth exposing one day. It should\n// arrive under a name that says THAT, not under a mode that also silently\n// changes what `start` returns.\n\nexport type Trigger =\n\t| { kind: 'chat' }\n\t| ({ kind: 'schedule'; enabled?: boolean; cron: string; timezone?: string; message: string } & TriggerTarget)\n\t| { kind: 'interval'; seconds: number; maxRuns?: number; message: string }\n\t| ({\n\t\t\tkind: 'webhook';\n\t\t\tmessage?: string;\n\t\t\t/**\n\t\t\t * Where your system POSTs, set by the platform. Read it from the agent\n\t\t\t * that `setTriggers()` returns: `agent.data?.triggers`. Anything you\n\t\t\t * send here is ignored, so passing back a list you read is safe and\n\t\t\t * keeps the same URL. One webhook per agent; `rotateWebhookUrl()`\n\t\t\t * replaces the URL.\n\t\t\t */\n\t\t\turl?: string;\n\t\t\t/**\n\t\t\t * Check WHO sent each delivery, beyond knowing the URL. See\n\t\t\t * WebhookVerify for the schemes and an example. Leave it out to keep\n\t\t\t * what the webhook has. The secret is stored encrypted and never read\n\t\t\t * back: a read says `secretSet: true` instead.\n\t\t\t */\n\t\t\tverify?: WebhookVerify;\n\t } & TriggerTarget);\n\n/**\n * How a webhook checks its sender. Platforms that send webhooks sign every\n * delivery with a secret they show you when you add the URL there; give it\n * here, and a delivery whose signature does not match is refused (401) and\n * never runs the agent.\n *\n * ```ts\n * { kind: 'webhook', verify: { kind: 'stripe', secret: 'whsec_...' } }\n * { kind: 'webhook', verify: { kind: 'header', header: 'X-Webhook-Secret', secret: '...' } }\n * ```\n *\n * The schemes: `stripe`, `github`, `shopify`, `slack` and\n * `standard` (the Standard Webhooks spec) check that platform's signature;\n * `header` checks a header your sender adds (Zapier, Make, n8n, your own code\n * can all add one); `none` relies on the URL alone.\n */\nexport type WebhookVerify =\n\t| { kind: 'none' }\n\t| { kind: 'stripe' | 'github' | 'shopify' | 'slack' | 'standard'; secret?: string; secretSet?: boolean }\n\t| { kind: 'header'; header: string; secret?: string; secretSet?: boolean };\n\n/** One delivery a webhook received, newest first from `agent.webhookDeliveries()`. */\nexport interface WebhookDelivery {\n\tid: string;\n\treceivedAt: string;\n\t/** `pending`: checked and starting. `repeat`: the sender retried a delivery\n\t * already accepted; the agent did not run again. */\n\toutcome: 'pending' | 'accepted' | 'refused' | 'repeat' | 'test';\n\t/** Why it was refused. */\n\treason?: string;\n\t/** The sender's own id for it, when it gave one. */\n\tdeliveryId?: string;\n\t/** The conversation it ran in. */\n\tconversationId?: string;\n}\n\n/**\n * WHERE a schedule or webhook runs. Name a conversation, or an end user, or\n * neither; never both.\n *\n * ```ts\n * const conversation = await agent.start({ endUserId: 'ops-app' });\n * await agent.setTriggers([\n * { kind: 'chat' },\n * // Every firing lands in THIS conversation: its history, its drive.\n * { kind: 'schedule', cron: '0 15 * * *', message: 'REFRESH', conversationId: conversation.id },\n * ]);\n * ```\n */\nexport type TriggerTarget =\n\t| {\n\t\t\t/**\n\t\t\t * The conversation every firing CONTINUES: it sees that conversation's\n\t\t\t * history and its drive, and its end user comes with it. Each firing\n\t\t\t * also moves the conversation to the agent's latest version, so a fix\n\t\t\t * you publish reaches it.\n\t\t\t *\n\t\t\t * This is the ONLY way a trigger continues one of your conversations.\n\t\t\t * One end user can have many, so naming the end user cannot say which.\n\t\t\t *\n\t\t\t * Must be a conversation of this agent, started for one of your end\n\t\t\t * users (the id `agent.start()` returned). Anything else is refused\n\t\t\t * when you set the trigger. If the conversation is deleted later, the\n\t\t\t * firings fail and say so; none is opened in its place.\n\t\t\t */\n\t\t\tconversationId: string;\n\t\t\tuserId?: undefined;\n\t }\n\t| {\n\t\t\tconversationId?: undefined;\n\t\t\t/**\n\t\t\t * The end user each firing is for, when it names no conversation. Each\n\t\t\t * firing STARTS A NEW CONVERSATION for them, with an empty drive, and it\n\t\t\t * is listed by `agent.conversations({ endUser })`. To keep adding to\n\t\t\t * one conversation instead, name it with `conversationId`.\n\t\t\t *\n\t\t\t * Your id, as in `agent.start({ endUserId })`; the platform's prefixes\n\t\t\t * (`member:`, `visitor:`, `job:`) are refused.\n\t\t\t *\n\t\t\t * Name neither and the trigger keeps one conversation of its own that\n\t\t\t * every firing continues.\n\t\t\t */\n\t\t\tuserId?: string;\n\t };\n\n/**\n * One connection in an agent's setup, as it reads back: a NAME, an APP, and\n * WHOSE ACCOUNT. Exactly one of these two:\n *\n * | the connection says | whose account | the person signs in |\n * |---|---|---|\n * | `account: '<id>'` | the agent creator's: one account in this workspace, from `ws.accounts.connect()` | once, and every conversation uses it |\n * | `endUser: true` | the end user's: each person talking to the agent | once each, remembered for them |\n *\n * A turn uses EXACTLY the account the setup names, and never another one.\n * There is no fallback to some other account of the same app: an account that\n * is not signed in leaves the connection unavailable, and the agent says so.\n *\n * `name` tells two connections of one app apart, such as `work-gmail` and\n * `personal-gmail`. 1 to 64 characters, lowercase letters, digits and dashes,\n * no dash at either end, unique on the agent. Left out when sent, it is the\n * app's id.\n *\n * Part of the agent's CONFIGURATION, so changing a connection publishes a\n * version. The account itself is not versioned: signing in again reaches every\n * agent that names it. To send one, build it with `Connection.creatorAccount()`\n * or `Connection.endUserAccount()`.\n */\nexport type AgentConnection =\n\t| {\n\t\t\tname: string;\n\t\t\t/** The app's id: `App.GMAIL` is `'gmail'`. */\n\t\t\tapp: AppId;\n\t\t\t/** An account id in this workspace whose app is `app`. */\n\t\t\taccount: string;\n\t\t\tendUser?: never;\n\t\t\t/** Which of the app's own tools the agent may use; absent for the app's default. */\n\t\t\ttools?: string[];\n\t }\n\t| {\n\t\t\tname: string;\n\t\t\tapp: AppId;\n\t\t\tendUser: true;\n\t\t\taccount?: never;\n\t\t\ttools?: string[];\n\t };\n\n/** What an agent NEEDS, not the stored value. `Secret` is the value. */\nexport interface SecretRequirement {\n\tname: string;\n\tscope: Scope;\n\trequired?: boolean;\n}\n\nexport interface Parameter {\n\tname: string;\n\ttype: 'string' | 'number' | 'date' | 'choice';\n\trequired?: boolean;\n\tdefault?: string;\n\tchoices?: string[];\n}\n\nexport interface McpServer {\n\tname: string;\n\turl: string;\n\ttransport?: 'http' | 'sse';\n\theaders?: Record<string, string>;\n\ttimeoutMs?: number;\n}\n\nexport interface HttpToolParam {\n\ttype: 'string' | 'number' | 'boolean' | 'object' | 'array';\n\tdescription?: string;\n\trequired?: boolean;\n\tchoices?: (string | number | boolean | null)[];\n\tnullable?: boolean;\n\tinteger?: boolean;\n\tproperties?: Record<string, HttpToolParam>;\n\titems?: HttpToolParam;\n\tadditionalProperties?: boolean;\n\t/**\n\t * A regular expression the whole value must match (strings only). Checked\n\t * before the call; a value that does not match is refused, so what a person\n\t * typed cannot change the request it is written into. Patterns that repeat a\n\t * repeating group, like `(a+)+`, are refused when saved.\n\t *\n\t * ```ts\n\t * order_number: { type: 'string', required: true, pattern: '[0-9]{1,10}' }\n\t * ```\n\t */\n\tpattern?: string;\n\t/** The longest value accepted (strings only). 2000 when a pattern is set\n\t * and this is not. */\n\tmaxLength?: number;\n}\n\n/** One check on an HTTP tool's answer: the value at `field` (a dotted path,\n * numbers for list positions) must equal `equals`, which may use\n * `{{params.NAME}}`. */\nexport interface HttpToolMatch {\n\tfield: string;\n\tequals: string;\n\tignoreCase?: boolean;\n}\n\n/** Something the agent may ask the website page to do, such as add to cart.\n * The page carries it out with `window.AgentNava.page.handle(name, fn)`, and\n * what `fn` returns goes back to the agent. Only a website chat has a page; in\n * any other conversation the agent is told it cannot. */\nexport interface PageAction {\n\t/** Lowercase letters, digits and `_`; never one of the agent's HTTP tool names. */\n\tname: string;\n\t/** What it does and when the agent should use it. */\n\tdescription: string;\n\t/** What the agent fills in; the page's handler receives these as its argument. */\n\tparams?: Record<string, HttpToolParam>;\n}\n\nexport interface HttpTool {\n\tname: string;\n\tdescription: string;\n\tmethod: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';\n\turl: string;\n\theaders?: Record<string, string>;\n\tbody?: string;\n\tparams?: Record<string, HttpToolParam>;\n\tsecrets?: string[];\n\ttimeoutMs?: number;\n\t/**\n\t * Checks on the answer, all of which must pass. If any fails, or the call\n\t * answers with an error or a body that is not JSON, the agent gets only\n\t * `notFound`, never the answer. Use it for \"show an order only when the\n\t * number and the email both match\":\n\t *\n\t * ```ts\n\t * requireMatch: [\n\t * { field: 'data.orders.nodes.0.name', equals: '#{{params.order_number}}' },\n\t * { field: 'data.orders.nodes.0.email', equals: '{{params.email}}', ignoreCase: true },\n\t * ],\n\t * notFound: 'No order matches that number and email.',\n\t * ```\n\t */\n\trequireMatch?: HttpToolMatch[];\n\t/** What the agent gets when a check fails. Default: \"Not found.\" */\n\tnotFound?: string;\n\t/**\n\t * Sign the request in with one of the agent's connections (its `name`)\n\t * instead of a stored key. The connection's provider adds a current\n\t * credential and keeps it fresh, so the credential never leaves it. The\n\t * address must start with `https://` and a written-out host, and that host\n\t * must be the connected account's own API; leave out `Authorization`.\n\t *\n\t * ```ts\n\t * { name: 'look_up_order', method: 'POST', signInWith: 'shopify',\n\t * url: 'https://my-store.myshopify.com/admin/api/2026-04/graphql.json', ... }\n\t * ```\n\t */\n\tsignInWith?: string;\n\t/**\n\t * Values the website page supplies for this visitor, used as\n\t * `{{pageEncoded.NAME}}` in the address, `{{pageJson.NAME}}` in a JSON body,\n\t * or `{{page.NAME}}` for a plain token (letters, digits, `- _ . ~` only).\n\t * On the page, the chat script's public call sets them:\n\t * `window.AgentNava.page.set('cart_id', value)`. They are kept per\n\t * conversation and filled in at call time; the model sees one only if the\n\t * API's answer repeats it, and logs redact them. Names: lowercase letters, digits, `_`.\n\t */\n\tpageValues?: string[];\n}\n\n/**\n * Guardrails: what the platform enforces on an agent, even when someone tries to\n * talk the agent out of it. An agent's `instructions` are what it TRIES to do;\n * its guardrails are checked by the platform before the agent reads a message,\n * so a person cannot argue it past them.\n *\n * ```ts\n * await agent.update({\n * guardrails: {\n * stayOnTopic: true,\n * rules: ['Never offer discounts or change prices.'],\n * },\n * });\n * ```\n *\n * A message that breaks one is declined before the agent reads it, and the agent\n * replies politely in its own voice instead; an action that breaks one does not\n * happen; a reply that breaks one is taken back and answered again. The turn's\n * stream says which rule fired (`{ type: 'guardrail' }`).\n *\n * Part of the CONFIGURATION, like `connections`, so changing it publishes a\n * version, and a conversation already in progress keeps the version it began on.\n */\nexport interface Guardrails {\n\t/** Decline anything outside this agent's job, judged from its own\n\t * instructions: other work (code, homework, poems), \"ignore your\n\t * instructions\", requests for its prompt or files, and \"I'm the owner\"\n\t * claims made to get special treatment. ON unless set to `false`. */\n\tstayOnTopic?: boolean;\n\t/** Your own rules, one plain-English sentence each (at most 20, each under\n\t * 500 characters). Each is checked on the message, on every action the agent\n\t * tries, and on its reply. A break is declined, blocked or taken back, and the\n\t * stream names the rule. */\n\trules?: string[];\n\t/** Check each message before the agent reads it, so one that breaks a\n\t * guardrail never reaches the agent and nothing it asked for is done. Costs\n\t * about 0.2 s per message. ON unless set to `false`: off, the agent answers\n\t * at once, and its replies and actions are still checked. */\n\tcheckMessagesFirst?: boolean;\n}\n\nexport interface AgentData {\n\tid: string;\n\tname: string;\n\tinstructions: string;\n\tgreeting?: string;\n\t/** Always present, with the defaults filled in: what is enforced now. */\n\tguardrails: { stayOnTopic: boolean; rules: string[]; checkMessagesFirst: boolean };\n\t/** How its website chat looks. Absent: the default look, starting closed. */\n\tappearance?: WebsiteChatAppearance;\n\tgrade: Grade;\n\tfilesystem: Filesystem;\n\ttools: string[];\n\tworkflows: Workflow[];\n\t/** The apps it uses, each saying whose account. See `AgentConnection`. */\n\tconnections: AgentConnection[];\n\tsecrets: SecretRequirement[];\n\tparameters: Parameter[];\n\ttriggers: Trigger[];\n\tmcpServers: McpServer[];\n\thttpTools: HttpTool[];\n\t/** Always a list, empty when the agent has none. */\n\tpageActions: PageAction[];\n\t/** Workspace knowledge bases this agent reads, by key. Always present, empty\n\t * when none are attached, so a caller never has to distinguish \"none\" from\n\t * \"an older server that did not send the field\". */\n\tknowledgeBases: string[];\n\t/** Workspace databases this agent may query, by key. Always present, like\n\t * `knowledgeBases`. */\n\tdatabases: string[];\n\tversion: number;\n\tcreatedAt: string;\n\tupdatedAt: string;\n\t/** Where it is live, as the portal shows it. The API works it out by the\n\t * same rule it runs the agent by, so `channels.api === 'on'` is exactly\n\t * \"a turn will be taken\". Read-only: nothing sets it. Sent by `agent.get()`\n\t * and `ws.agents.list()`; absent from what create and update return, so\n\t * call `get()` for it. */\n\treadonly status?: AgentStatus;\n}\n\n/** Who can chat with an agent's website chat. 'restricted' (the default):\n * members of your workspace signed in to AgentNava, and visitors on the\n * allowed websites. 'anyone': anyone with the link, on any website. */\nexport type WebsiteChatAccess = 'restricted' | 'anyone';\n\n/** What `agent.websiteChat.get()` gives back. */\nexport interface WebsiteChat {\n\t/** Who can chat. */\n\treadonly access: WebsiteChatAccess;\n\t/** The hosted chat page, the share link. Share it, or open it in an iframe. */\n\treadonly chatUrl: string;\n\t/** The id the embed script's `data-agent-id` takes. */\n\treadonly embedAgentId: string;\n}\n\n/** Where an agent is live. See `AgentData.status`. */\nexport interface AgentStatus {\n\t/** True when any channel is on. */\n\treadonly live: boolean;\n\t/** Paused by its owner: nothing runs it until it is resumed. */\n\treadonly paused: boolean;\n\t/** Archived: nothing runs it again. */\n\treadonly archived: boolean;\n\treadonly channels: {\n\t\t/** 'on' when the API takes new turns for it. */\n\t\treadonly api: 'on' | 'off';\n\t\t/** 'on' when its website chat page answers visitors. 'unavailable' when it\n\t\t * needs what each user brings (`eachUserBrings`), which a visitor cannot. */\n\t\treadonly websiteChat: 'on' | 'off' | 'unavailable';\n\t};\n\t/** Required accounts and secrets of its own that are not set up yet, on the\n\t * version a new conversation runs. It still runs: the tools that use them\n\t * fail until they are set up. Empty when nothing is missing. */\n\treadonly needs: readonly { kind: 'connection' | 'secret'; name: string }[];\n\t/** The accounts and secrets each user supplies for themselves. */\n\treadonly eachUserBrings: readonly { kind: 'connection' | 'secret'; name: string }[];\n}\n\n/**\n * One of an app's own ready-made tools, as `ws.apps.tools(app)` lists it: what\n * a connection's `tools` chooses from.\n */\nexport interface AppTool {\n\t/** What a connection's `tools` names: `SLACK_SEND_MESSAGE`. */\n\tid: string;\n\t/** The tool service's name for it: \"Send message\". */\n\tname: string;\n\t/** Its description's first sentence. */\n\tsummary: string;\n\t/** It removes something (a message, a file, a record). */\n\tdeletes?: true;\n}\n\n/**\n * One of an agent's connections as it stands right now: the account it names,\n * and whether that account works. What `agent.connections.list()` returns.\n *\n * DECLARED IS NOT CONNECTED, and this is the read that tells them apart. A\n * connection can name an account nobody has finished signing in to, or one that\n * was disconnected since; that is the state a setup checklist has to show, and\n * without this the only way to find it is to start a conversation and watch\n * the agent fail.\n *\n * Reshaped in 0.44: it used to be keyed by `provider`, with `declared`,\n * `required`, `scope` and `suppliedBy`. A connection is now keyed by its\n * `name`, and it says whose account it uses instead of where one might come\n * from.\n */\nexport interface ConnectionStatus {\n\tname: string;\n\tapp: AppId;\n\t/** The agent creator's account the connection names, read fresh. Absent on\n\t * an end user's connection, and on one whose account is gone. */\n\taccount?: AccountData;\n\t/** True when each person talking to the agent signs in with their own. */\n\tendUser?: true;\n\t/** Which of the app's own tools the agent may use; absent for the app's default. */\n\ttools?: string[];\n\t/** Whether the connection works now, as the API reports it. For an agent\n\t * creator's account it follows that account's status. */\n\tstatus: string;\n\t/** Only in the agent's unpublished draft, so no conversation uses it yet. */\n\tinDraft: boolean;\n\t/** Removed in the agent's unpublished draft, and still used until the draft\n\t * is published. */\n\tremovedInDraft: boolean;\n}\n\n/** Where one account's sign-in stands.\n *\n * 'pending' made, and nobody has finished signing in yet\n * 'connected' signed in and working\n * 'needs_sign_in' it worked, and the app now wants the person to sign in again\n * 'error' the app refused it for another reason\n */\nexport type AccountStatus = 'pending' | 'connected' | 'needs_sign_in' | 'error';\n\n/** An agent whose setup names an account, and the connection that names it.\n * Counted from the agent's latest published version and its draft, never\n * from older versions. */\nexport interface UsedBy {\n\t/** The agent's id: the same value as `Agent.id`, so compare this one. */\n\tid: string;\n\t/** AgentNava's internal handle for the same agent (`agt_...`), not `Agent.id`. */\n\tagentId: string;\n\tagentName: string;\n\tconnectionName: string;\n\t/** Named in the agent's unpublished draft. */\n\tinDraft: boolean;\n\t/** Named in the agent's latest published version. */\n\tpublished: boolean;\n}\n\n/**\n * One saved sign-in to one app.\n *\n * TWO OWNERS, and the difference is who signs in:\n *\n * | `owner` | whose | a connection uses it as |\n * |---|---|---|\n * | `'workspace'` | the agent creator's, signed in once, usable by any agent | `Connection.creatorAccount(app, account)` |\n * | `'end-user'` | one of YOUR end users', remembered for that person | `Connection.endUserAccount(app)`, in that person's conversations |\n *\n * Both owners are read and changed through the same routes and the same\n * `Account` handle; `owner` says which this is.\n *\n * Nobody handles a token: the person signs in on the app's own page, and this\n * record says who they signed in as, never how.\n */\nexport interface AccountData {\n\tid: string;\n\t/** The app's id: `App.GMAIL` is `'gmail'`. */\n\tapp: AppId;\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\tlabel: string | null;\n\t/** Your own name for it, such as `'Support inbox'`. Null when none was given. */\n\tnickname: string | null;\n\tstatus: AccountStatus;\n\t/** True while a reconnect waits for the person to sign in again. The\n\t * account keeps working meanwhile, so `status` can still say\n\t * `'connected'`; `waitUntilConnected()` waits for this to clear. */\n\treconnecting: boolean;\n\towner: 'workspace' | 'end-user';\n\t/** The end user it belongs to, when `owner` is `'end-user'`. An id from YOUR\n\t * database, never a workspace member's. */\n\tendUserId?: string;\n\t/** The agent that end user belongs to, when `owner` is `'end-user'`: the same id\n\t * on another agent is another person. */\n\tagentId?: string;\n\tconnectedAt: string | null;\n\tlastCheckedAt: string | null;\n\t/** The agents whose setup names this account. Empty for an end user's\n\t * account, which a setup never names by id. */\n\tusedBy: UsedBy[];\n}\n\n/** An end user's account a conversation still needs, with the link that\n * person signs in at. From `conversation.accountsNeeded()`. */\nexport interface AccountNeeded {\n\t/** The connection's name on the agent. */\n\tname: string;\n\tapp: AppId;\n\t/** Send the person here. They sign in once, and every later conversation\n\t * of theirs has it. */\n\turl: string;\n}\n\n/** Something a conversation is still missing before it can work. */\nexport interface Requirement {\n\tkind: 'secret' | 'connection';\n\tname: string;\n\trequired: boolean;\n}\n\nexport interface AgentConversationData {\n\tid: string;\n\tagentId: string;\n\t/** The number of the agent version this conversation runs, as `agent.version`\n\t * numbers them. Fixed at start, except that a schedule or webhook naming the\n\t * conversation moves it to the latest version at each firing. */\n\tversion: number;\n\tstartedAt: string;\n\ttitle?: string;\n\t/** True when nothing REQUIRED is outstanding. An optional gap leaves this\n\t * true, because the conversation genuinely does work without it. */\n\tready: boolean;\n\trequires: Requirement[];\n}\n\nexport interface ProjectConversationData {\n\tid: string;\n\tprojectId: string;\n\tstartedAt: string;\n\ttitle?: string;\n\tready: boolean;\n\trequires: Requirement[];\n}\n\n/** An AGENT on a project, not a person.\n *\n * Called `ProjectAgent` and not `Member`, which is what it was. `Member` is\n * the word this product uses for a HUMAN on the account, so a public type of\n * that name holding an `agentId` was the same trap as `userId` meaning two\n * populations: `import { Member }` handed you the agent one, and the human one\n * had to carry a prefix to get out of its way.\n *\n * The people are `WorkspaceMember`, from `ws.members()`. Nothing here is a\n * person. */\nexport interface ProjectAgent {\n\tagentId: string;\n\tname: string;\n\taddedAt: string;\n}\n\nexport interface ProjectData {\n\tid: string;\n\tname: string;\n\tgoal: string | null;\n\tmanagerInstructions: string | null;\n\tgrade: Grade;\n\tstatus: 'active' | 'archived';\n\tagents: ProjectAgent[];\n\tcreatedAt: string;\n\tupdatedAt: string;\n}\n\nexport interface Step {\n\tid: string;\n\tlabel: string;\n\tkind: 'read' | 'write' | 'edit' | 'search' | 'shell' | 'web' | 'data' | 'task' | 'plan' | 'other';\n\tstatus: 'active' | 'done' | 'error';\n}\n\nexport type TurnEvent =\n\t/** First, before anything else: the id the API gave this turn. */\n\t| { type: 'start'; turnId: string }\n\t/** Carries the WHOLE step list every time, not a change to it. Replace what\n\t * you are showing rather than merging; that is what makes reconnecting\n\t * safe. Sent after each tool event that changes the list, with the steps\n\t * and words the portal shows. */\n\t| { type: 'progress'; steps: Step[] }\n\t/** `name` is the tool's identifier, for code. `label` is what a person sees,\n\t * the portal's words (\"Searching the web: <query>\"). */\n\t| { type: 'tool'; name: string; state: 'start' | 'end'; label?: string; ok?: boolean }\n\t/** A fragment, not a line. Append; do not assemble. */\n\t| { type: 'text'; text: string }\n\t/** A file the agent wrote in the conversation's drive. Pass `path` to\n\t * conversation.readFile(). Sent once per file per turn. */\n\t| { type: 'file'; path: string }\n\t/** A widget the agent is drawing inside its reply, sent whole each time it\n\t * grows: replace the one with the same `surfaceId`. Only on a conversation\n\t * started with `widgets: true`. */\n\t| { type: 'widget'; widget: Widget }\n\t/** `widgets` is the reply's final widget list, present when it has any:\n\t * what to keep, whatever arrived live. */\n\t/** One of the agent's guardrails fired. `rule` is the one, as you wrote it,\n\t * or 'Stay on topic'.\n\t * - `message`: the person's message broke it and was declined. The agent\n\t * never read it; the words that follow are its polite refusal.\n\t * - `reply`: the agent's reply broke it and was taken back (a `reset` came\n\t * first); the words that follow are its answer within the rule.\n\t * - `tool`: an action the agent tried (`tool`) broke one of your rules and\n\t * did not happen; the agent explains in the words that follow. Which rule\n\t * is not reported for this stage. */\n\t| { type: 'guardrail'; stage: 'message'; rule: string; action: 'declined' }\n\t| { type: 'guardrail'; stage: 'reply'; rule: string; action: 'replaced' }\n\t| { type: 'guardrail'; stage: 'tool'; tool: string; action: 'blocked' }\n\t/** Replace the words you have shown for this reply with `text`, often empty:\n\t * earlier words were withdrawn (a guardrail) or a step was retried. Clear the\n\t * reply's widgets too; any that still belong are sent again. */\n\t| { type: 'reset'; text: string }\n\t/** Sent at the start of a turn, once per name: a secret the agent declared\n\t * for each conversation, and one of its tools uses, has no usable value\n\t * here. `expired`: the value you pushed has run out. `missing`: none was\n\t * given, or the stored secret attached under that name was deleted or no\n\t * longer belongs to this person. The turn still runs; the tool says it\n\t * cannot sign in. Push a fresh value (`ask(text, { secrets })`) and ask\n\t * again. */\n\t| { type: 'credential_needed'; name: string; reason: 'missing' | 'expired' }\n\t| { type: 'done'; reply: string; widgets?: Widget[] }\n\t| { type: 'error'; message: string; retryable: boolean };\n\n/**\n * One part of a widget, in Google's open A2UI v0.9 format with AgentNava's own\n * set of parts: an id, which kind of\n * part it is (`Column`, `Row`, `Card`, `Text`, `Stat`, `KeyValue`, `Table`,\n * `Chart`, `Image`, `List`, `Note`, `Badge`, `Link`, `Button`, `Suggestions`,\n * `Divider`), and that kind's props. A container lists its children by id.\n */\nexport interface WidgetPart {\n\tid: string;\n\tcomponent: string;\n\t[prop: string]: unknown;\n}\n\n/**\n * A card, table or chart the agent drew inside a reply. Draw it with `WidgetView`\n * from `@cerebro-labs/agentnava-sdk/react`, or your own components starting\n * from the part with id `root`.\n * `at` is where it sits in the reply's text, as a character count, so it goes\n * between the right sentences.\n */\nexport interface Widget {\n\tsurfaceId: string;\n\tat: number;\n\tcomponents: WidgetPart[];\n\t/** Every part arrived. False only while it streams, or on a reply cut short. */\n\tclosed: boolean;\n}\n\nexport interface Message {\n\trole: 'user' | 'assistant';\n\tcontent: string;\n\tat: string;\n\t/** The widgets in an assistant reply, when it has any. */\n\twidgets?: Widget[];\n\t/** The files sent with a question, when it had any: name, kind and type,\n\t * never the bytes. `content` stays the words that were typed. */\n\tattachments?: SentAttachment[];\n}\n\n/** A file that was sent with a question, as a read of the conversation returns it. */\nexport interface SentAttachment {\n\tkind: 'image' | 'file';\n\tname?: string;\n\tmimeType: string;\n\t/** Decoded size, in bytes. */\n\tsizeBytes: number;\n}\n\n/** An image goes to the model as an image. Anything else is written into the\n * conversation's drive before the turn starts, so the agent can parse it,\n * compute over it, and still have it next turn. */\nexport interface Attachment {\n\tkind: 'image' | 'file';\n\tname?: string;\n\tmimeType: string;\n\t/** base64, without a data: prefix. */\n\tdata: string;\n}\n\n/** A knowledge base: a named folder of files at WORKSPACE scope that an agent\n * configuration attaches by key. */\nexport interface KnowledgeBaseData {\n\t/** Workspace-unique. This is what an agent configuration stores, and it is\n\t * not editable, because renaming it would move every attachment silently. */\n\tkey: string;\n\tname: string;\n\tdescription?: string;\n\t/** Counted when read, so it never disagrees with the files it counts. */\n\tfileCount: number;\n\tcreatedAt: string;\n\tupdatedAt: string;\n}\n\n/** One file in a knowledge base. PATH, not a flat name: a base is a folder, and\n * the session mount reproduces the shape verbatim. */\n/**\n * Whether an agent can read a file in a knowledge base.\n *\n * Text and markdown are readable as they stand. A PDF, a scan or a spreadsheet\n * has to be turned into text first, which happens when the file is uploaded\n * rather than when a question is asked, so an agent never waits on it.\n *\n * 'readable' an agent can read this now\n * 'processing' being prepared, check back shortly\n * 'unreadable' cannot be read, and `reason` says why\n */\nexport type KnowledgeFileStatus = 'readable' | 'processing' | 'unreadable';\n\nexport interface KnowledgeFileEntry {\n\tpath: string;\n\tmimeType: string;\n\tsize: number;\n\tcreatedAt: string;\n\tupdatedAt: string;\n\t/** Whether an agent can read this file. */\n\tstatus: KnowledgeFileStatus;\n\t/** Why it cannot be read, as a sentence you could show someone.\n\t *\n\t * The same text reaches the agent in place of the file, so a developer\n\t * reading this and a model reading the base get one explanation rather than\n\t * two that can drift apart. Absent unless `status` is 'unreadable'. */\n\treason?: string;\n}\n\nexport interface FileEntry {\n\tpath: string;\n\tkind: 'file' | 'directory';\n\tmimeType: string;\n\tsize: number;\n\tmodifiedAt: string;\n}\n\n/** Fields accepted when creating or updating an agent. Everything is optional on\n * an update: what you do not send is inherited from the current version, which\n * is the whole reason updating is safe. */\nexport interface AgentFields {\n\tid?: string;\n\tname?: string;\n\tinstructions?: string;\n\tgreeting?: string;\n\t/** What the platform enforces on every incoming message. See `Guardrails`. */\n\tguardrails?: Guardrails;\n\t/**\n\t * How the agent's website chat looks, on the share link and through the\n\t * embed script. Merged by setting: `{ accent: '#B4532A' }` changes the colour\n\t * and keeps the rest; `null` clears one setting, and `appearance: null` goes\n\t * back to the default look. See `WebsiteChatAppearance`.\n\t */\n\tappearance?: { [K in keyof WebsiteChatAppearance]?: WebsiteChatAppearance[K] | null } | null;\n\tgrade?: Grade;\n\tfilesystem?: Filesystem;\n\t/**\n\t * Knowledge bases this agent reads, by workspace-unique key.\n\t *\n\t * Mounted READ-ONLY at `/knowledge/<key>/` when a session starts, so the\n\t * agent finds things with `ls`, `grep` and `cat` rather than a retrieval API.\n\t *\n\t * Which bases an agent reads is part of its CONFIGURATION, so changing this\n\t * publishes a version you can diff and roll back. The CONTENTS of a base are\n\t * not versioned: correcting a file changes what every attached agent sees on\n\t * its next turn, which is the same by-reference promise `agent.knowledge`\n\t * already makes.\n\t *\n\t * A key that names no base is refused when the configuration is saved, and\n\t * deleting a base is refused while any agent attaches it, so an attachment\n\t * that was valid stays valid.\n\t */\n\tknowledgeBases?: string[];\n\t/**\n\t * Workspace databases this agent may query, by key.\n\t *\n\t * Register the database first with `ws.databases.create()`, which logs in\n\t * and gives it the key; then name the key here. Naming it is the grant: a\n\t * database answers only the agents whose configuration names it, and the\n\t * agent's prompt lists them so it knows what to query.\n\t *\n\t * Part of the CONFIGURATION, so changing it publishes a version. A key that\n\t * names no database is refused when the configuration is saved, and\n\t * removing a database is refused while any agent names it.\n\t */\n\tdatabases?: string[];\n\ttools?: string[];\n\t/**\n\t * \"Answer with widgets\": on unless `false`. When on, the agent draws cards,\n\t * tables and charts inside its answers, on a conversation started with\n\t * `widgets: true`. Set `false` for an agent that should answer in text only.\n\t */\n\twidgets?: boolean;\n\tworkflows?: Workflow[];\n\t/**\n\t * The apps this agent uses, each a NAMED connection that says whose account\n\t * it uses: the agent creator's account, or each end user's own. See\n\t * `Connection` and `AgentConnection`.\n\t *\n\t * ```ts\n\t * connections: [\n\t * Connection.creatorAccount(App.GMAIL, work, { name: 'work-gmail' }),\n\t * Connection.creatorAccount(App.GMAIL, personal, { name: 'personal-gmail' }),\n\t * Connection.endUserAccount(App.GOOGLE_CALENDAR),\n\t * ]\n\t * ```\n\t *\n\t * The builders make plain objects, `{ app, account: '<id>' }` and\n\t * `{ app, endUser: true }`, and those objects are accepted as they are.\n\t * `App.GMAIL` is the string `'gmail'`, so a plain app id works too.\n\t *\n\t * Part of the CONFIGURATION, like `databases`, so changing it publishes a\n\t * version. An unknown account id, an account of a different app, a repeated\n\t * name or a name of the wrong shape is refused when the configuration is\n\t * saved, naming the problem.\n\t */\n\tconnections?: Connection[];\n\tsecrets?: SecretRequirement[];\n\tparameters?: Parameter[];\n\ttriggers?: Trigger[];\n\tmcpServers?: McpServer[];\n\thttpTools?: HttpTool[];\n\t/** Things the agent may ask the website page to do (see PageAction). */\n\tpageActions?: PageAction[];\n}\n\nexport interface ProjectFields {\n\tname?: string;\n\tgoal?: string;\n\tmanagerInstructions?: string;\n\tgrade?: Grade;\n\tagents?: string[];\n}\n\n/** Secret VALUES supplied to an agent (`agent.attach`) or to one conversation\n * (`conversation.attach`): the secrets its agent declared.\n *\n * Accounts are no longer supplied this way (0.44). An agent's setup names the\n * account each connection uses, so there is nothing left to attach. */\nexport interface Bindings {\n\tsecrets?: Record<string, string>;\n}\n\n/** One markdown file read off disk: a workflow or a subagent.\n *\n * `when` is OPTIONAL here and required at publish. A file on disk has no `when`\n * until someone writes one, and refusing to read it would be the wrong place to\n * say so; the API says it at publish, with a message naming the file. */\nexport interface SpecFile {\n\tname: string;\n\tcontent: string;\n\twhen?: string;\n}\n\n/** What a loaded agent directory produces: the fields `ws.agents.create()` takes,\n * plus the id the caller chose and the few legacy names the loader still reads\n * off an existing directory. */\nexport interface SpecInput extends Omit<AgentFields, 'workflows'> {\n\tkey: string;\n\tworkflows?: SpecFile[];\n\tsubagents?: SpecFile[];\n\tsandbox?: Filesystem;\n\trole?: string;\n\t/** Retired in favour of `tools: ['web_search']`, still read off an existing\n\t * agent directory so one written before the rename still loads. */\n\twebSearch?: boolean;\n}\n\n/** A PERSON with access to this workspace: your colleague, on a seat.\n *\n * NOT one of your end users, and the difference is the sharpest naming trap in\n * this SDK. A workspace member signs in here, we issued their id, and we\n * authenticate it. A `EndUser` is the person YOUR application serves, never\n * signs in here, and carries an id from YOUR database that we store verbatim\n * and never check.\n *\n * `userId` below is therefore a PLATFORM id and is not interchangeable with\n * `agent.start({ endUserId })`. Both fields were once spelled `userId`, which\n * made passing one where the other belonged a call that could not fail: a\n * end-user id is never validated against anything, so it silently labelled a\n * conversation with a colleague's internal id. The two names now differ.\n *\n * Named WorkspaceMember rather than Member because a public `Member` type used\n * to mean an AGENT attached to a project. That one is now `ProjectAgent`, so\n * the prefix here is no longer load-bearing; it stays because it says which of\n * the two populations this is at every call site, and that is worth four\n * characters.\n *\n * Read-only, like everything else on the account surface: the SDK can see who\n * is on the account and what they may do, and cannot add, remove or re-role\n * anyone. Those are decisions a person makes while signed in, not something a\n * key should be able to do on their behalf. */\nexport interface WorkspaceMember {\n\t/** A PLATFORM user id (`usr_...`), issued by us. Never pass this to\n\t * `agent.start({ endUserId })` or `agent.endUser()`. */\n\tuserId: string;\n\temail: string;\n\tname: string | null;\n\t/** 'owner' is the account's creator; 'admin' can manage it; 'member' cannot. */\n\trole: 'owner' | 'admin' | 'member';\n\t/** Unix seconds. */\n\tjoinedAt: number;\n}\n\n/** What is left to spend.\n *\n * `credits` is the balance in credits, where one credit is one US dollar. The\n * turn counts are the same balance expressed in the unit people actually\n * reason with, because \"12.5 credits\" answers nothing on its own. */\nexport interface Credits {\n\tcredits: number;\n\tstandardTurnsRemaining: number;\n\tpremiumTurnsRemaining: number;\n}\n\n/** The seat plan this workspace is on.\n *\n * Seats are a SEPARATE purchase from credits: a plan says how many people may\n * share the account, credits pay for the work their agents do. Being on the\n * free plan does not make turns free. */\nexport interface SeatPlan {\n\tkey: string;\n\tlabel: string;\n\t/** People allowed. `null` means negotiated, which is NOT unlimited and\n\t * should never be shown to anyone as a number. */\n\tseats: number | null;\n\t/** Monthly price in US cents. `null` means there is no published price and\n\t * the answer is a conversation, which is the opposite fact from 0 (free). */\n\tusdCentsPerMonth: number | null;\n}\n\n/** A stored secret, WITHOUT its value.\n *\n * The value is never returned by any read. It is written once, encrypted at\n * rest, and substituted into an HTTP tool's url, headers or body server-side at\n * call time, so it is never part of anything the model can see or repeat. To\n * change one, write it again; to be sure of one, replace it. */\nexport interface SecretMeta {\n\tid: string;\n\tname: string;\n\tdescription?: string | null;\n\t/** The agent this secret is scoped to, or null when the whole workspace\n\t * reads it. An agent-scoped secret of the same name wins. */\n\tagentId?: string | null;\n\t/** The end user this secret belongs to, or null when it is the workspace's.\n\t * One of YOUR user ids, stored verbatim: we never resolve or authenticate\n\t * it. A secret with one can only be attached to a conversation launched for\n\t * that same person. */\n\tendUserId?: string | null;\n\t/** The only hosts a tool may send this key to. Empty until the first tool\n\t * saved with it pins its own host; add one on purpose with\n\t * `secret.setAllowedHosts([...])`. */\n\tallowedHosts?: string[];\n\t/** Who created it and who last changed its value or hosts: a member's id,\n\t * `api_key:<id>`, or `system`. A tool save that pins its hosts is not a\n\t * change to it. Null on a secret from before these were kept. */\n\tcreatedBy?: string | null;\n\tupdatedBy?: string | null;\n\t/** The same, in words: a member's first name or \"API key <name>\"; null\n\t * for our own system or someone no longer in the workspace. */\n\tcreatedByName?: string | null;\n\tupdatedByName?: string | null;\n\tcreatedAt?: string;\n\tupdatedAt?: string;\n\tlastUsedAt?: string | null;\n}\n\n/** A SQL engine AgentNava can query. */\nexport type DatabaseEngine = 'postgres' | 'mysql' | 'sqlserver';\n\n/** How the connection to a database is encrypted. `verify` (the default)\n * encrypts and always checks the server's certificate: against public and\n * cloud CAs and the host name, or, for a database with its own home-made CA,\n * against the CA AgentNava trusted the first time it connected. `disable` is\n * for a server with no SSL at all. */\nexport type DatabaseTls = 'verify' | 'disable';\n\n/**\n * How to log in to a database: a connection string, OR the same login as\n * separate fields. Never both; a request with both is refused.\n *\n * Read-only unless `writable: true`, which no SQL the agent sends can override.\n */\nexport type DatabaseLogin =\n\t| {\n\t\t\t/** `postgres://`, `postgresql://` or `mysql://`, with the user (and the\n\t\t\t * password, when there is one) in it. An `sslmode` in the string sets `tls`. */\n\t\t\turl: string;\n\t\t\tengine?: DatabaseEngine;\n\t\t\t/** Overrides the string's `sslmode`. */\n\t\t\ttls?: DatabaseTls;\n\t\t\twritable?: boolean;\n\t }\n\t| {\n\t\t\tengine: DatabaseEngine;\n\t\t\thost: string;\n\t\t\tport: number;\n\t\t\tdatabase: string;\n\t\t\tusername: string;\n\t\t\t/** Leave it out for a database that takes a user name alone. */\n\t\t\tpassword?: string;\n\t\t\ttls?: DatabaseTls;\n\t\t\twritable?: boolean;\n\t };\n\n/** What `ws.databases.create()` takes: the key agents will name it by, an\n * optional label, and a login. */\nexport type DatabaseFields = DatabaseLogin & {\n\t/** Lowercase letters, digits and dashes, like `shop` or `sales-db`. What an\n\t * agent configuration stores, so it is not editable afterwards. */\n\tkey: string;\n\t/** A label for people. Defaults to the key. */\n\tname?: string;\n};\n\n/** A registered database as AgentNava reports it: where it is, never how to\n * log in. Neither the password nor the connection string is ever returned. */\nexport interface DatabaseData {\n\tkey: string;\n\t/** The underlying connection's id. */\n\tid: string;\n\tname: string;\n\tengine: DatabaseEngine | null;\n\thost: string | null;\n\tport: number | null;\n\tdatabase: string | null;\n\ttls: DatabaseTls | null;\n\twritable: boolean;\n\tstatus: string;\n\tcreatedAt: string;\n\t/** The agents whose configuration names this database. */\n\tusedBy: DatabaseUsedBy[];\n}\n\n/** An agent whose configuration names a database: in its latest published\n * version, in its draft, or in both. */\nexport interface DatabaseUsedBy {\n\t/** The agent's id: the same value as `Agent.id`, so compare this one. */\n\tid: string;\n\t/** AgentNava's internal handle for the same agent (`agt_...`), not `Agent.id`. */\n\tagentId: string;\n\t/** The agent's display name. */\n\tname: string;\n\t/** Named in the agent's latest published version, so its conversations can\n\t * query the database now. */\n\tpublished: boolean;\n\t/** Named in the configuration being edited: the draft when there is one,\n\t * else the published version itself. */\n\tdraft: boolean;\n}\n\n/** One table or view in a database. */\nexport interface DatabaseTable {\n\tschema: string;\n\tname: string;\n\tkind: 'table' | 'view';\n}\n\n/** One column of a table. */\nexport interface DatabaseColumn {\n\tname: string;\n\tdataType: string;\n\tnullable: boolean;\n\tisPrimaryKey: boolean;\n}\n\n/** What `ws.databases.test()` reports. A refused login is an answer, not an\n * exception, because it is the answer the call exists to get. */\nexport type DatabaseTestResult =\n\t| { ok: true; engine: DatabaseEngine; tableCount: number; tables: DatabaseTable[] }\n\t| { ok: false; error: { code: string; message: string } };\n\n/** An agent as its draft holds it, from `agent.draft()`. */\nexport interface AgentDraft {\n\t/** An unpublished draft exists. */\n\texists: boolean;\n\t/** The draft differs from the live version. */\n\thasUnpublishedChanges: boolean;\n\t/** The configuration the draft holds, or the live one when there is no draft. */\n\tconfig: AgentFields;\n}\n\n/**\n * How an agent's website chat looks (the share link, and the chat the embed\n * script opens on a website). Every setting is optional; one left out keeps the\n * default. Part of the agent's configuration, so a change is published with a\n * version like any other field.\n */\nexport interface WebsiteChatAppearance {\n\t/** An image id from `agent.websiteChat.uploadLogo()`, shown in a circle in\n\t * the chat's header. Without one, the name's first letter. */\n\tlogo?: string;\n\t/** The name the chat shows, up to 40 characters. Without one, the agent's name. */\n\ttitle?: string;\n\ttheme?: 'light' | 'dark';\n\t/** One colour, written like `#B4532A`: the avatar, the visitor's messages,\n\t * the send button, chips, prices on cards, and the round button. Where it\n\t * is too light to read as text, words use a darker shade of it. */\n\taccent?: string;\n\t/** `clean` is the default. */\n\tfont?: 'clean' | 'friendly' | 'classic' | 'elegant';\n\t/** Whether the embed script opens the chat when a page loads, on screens\n\t * wider than a phone. New agents start `open`; absent means closed. */\n\topens?: 'open' | 'closed';\n\t/** Up to three short ways in (60 characters each), shown on the chat's\n\t * welcome card before a visitor's first message, such as \"Book a visit\".\n\t * A tap sends its words as the visitor's message. The Agent Builder writes\n\t * them from the agent's job; without them the card shows the greeting. */\n\tstarters?: string[];\n\t/** `false` hides the \"Made with AgentNava\" link under the chat; the\n\t * \"<name> is an AI assistant.\" notice always stays. Shown by default.\n\t * Hiding it needs a paid plan (Team and up): on a free plan the update is\n\t * refused with 403 `plan_required`. A workspace a partner app works in whose\n\t * marketplace forbids \"powered by\" marks (the Wix App Market) may hide it on\n\t * every plan. When a paid plan ends, the link shows again and this choice is\n\t * kept. */\n\tshowMadeWith?: boolean;\n}\n"]}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,4DAA4D;AAkB5D;;;mCAGmC;AACnC,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,CAAU,CAAC","sourcesContent":["// The shapes this SDK hands back. Transcribed from design/sdk-design.md's own\n// Types section so the reference and the code cannot drift.\n\n// Type-only: erased from the emitted JavaScript, so these add no runtime cycle\n// with the modules that import their own shapes from here.\nimport type { AppId } from './apps.generated.ts';\nimport type { Connection } from './connection.ts';\n\n/** The quality of model an agent's turns run on, and what each turn costs.\n *\n * `standard` is the default: the fastest model the platform runs. `lite` is\n * the lowest price. `premium` costs the same as `standard` and thinks harder,\n * answering more slowly. `best` is the strongest model.\n *\n * Renamed in 0.49.0: the grade that was `fast` is now `standard`, and the one\n * that was `standard` is now `lite`. The platform still accepts `fast` from\n * older code and treats it as `standard`. */\nexport type Grade = (typeof GRADE_NAMES)[number];\n\n/** Every grade, as a list a reply can be checked against. This SDK is\n * published and cannot import the platform's private price list, so it keeps\n * its own copy of the NAMES; a test in the platform's monorepo fails the\n * moment the two lists differ. */\nexport const GRADE_NAMES = ['lite', 'standard', 'premium', 'best'] as const;\nexport type Filesystem = 'drive' | 'none';\n\n/** Where a SECRET is supplied.\n *\n * 'fixed' is set once on the agent and used by every conversation. 'conversation'\n * is asked for when a conversation starts, so each user brings their own. The\n * two DO NOT substitute for each other: a value set on the agent does not\n * satisfy a declaration scoped 'conversation'.\n *\n * Connections stopped using this in 0.44. A connection says WHOSE ACCOUNT it\n * uses instead: the agent creator's (`account`) or each end user's own\n * (`endUser: true`). See `AgentConnection`. */\nexport type Scope = 'fixed' | 'conversation';\n\nexport interface WorkspaceInfo {\n\tid: string;\n\tname: string;\n\tcredits: number;\n\tcreatedAt: string;\n}\n\n/** One procedure the agent follows when its `when` matches what is in front of\n * it. `when` is one line and is not optional: it is what the agent reads to\n * decide the workflow applies, so a vague one makes a good workflow invisible. */\nexport interface Workflow {\n\tname: string;\n\twhen: string;\n\tcontent: string;\n}\n\n// `SessionMode` is deliberately NOT exposed.\n//\n// The API has one ('per-user' | 'per-call') and 'per-user' makes the server\n// resume a person's most recent conversation. That is the behaviour `start`\n// refuses to have: a verb that sometimes returns something you already had, and\n// which has to guess which one the moment a person has two.\n//\n// Its other effect, fanning a scheduled firing out across the people who have\n// conversations with an agent, is real and worth exposing one day. It should\n// arrive under a name that says THAT, not under a mode that also silently\n// changes what `start` returns.\n\nexport type Trigger =\n\t| { kind: 'chat' }\n\t| ({ kind: 'schedule'; enabled?: boolean; cron: string; timezone?: string; message: string } & TriggerTarget)\n\t| { kind: 'interval'; seconds: number; maxRuns?: number; message: string }\n\t| ({\n\t\t\tkind: 'webhook';\n\t\t\tmessage?: string;\n\t\t\t/**\n\t\t\t * Where your system POSTs, set by the platform. Read it from the agent\n\t\t\t * that `setTriggers()` returns: `agent.data?.triggers`. Anything you\n\t\t\t * send here is ignored, so passing back a list you read is safe and\n\t\t\t * keeps the same URL. One webhook per agent; `rotateWebhookUrl()`\n\t\t\t * replaces the URL.\n\t\t\t */\n\t\t\turl?: string;\n\t\t\t/**\n\t\t\t * Check WHO sent each delivery, beyond knowing the URL. See\n\t\t\t * WebhookVerify for the schemes and an example. Leave it out to keep\n\t\t\t * what the webhook has. The secret is stored encrypted and never read\n\t\t\t * back: a read says `secretSet: true` instead.\n\t\t\t */\n\t\t\tverify?: WebhookVerify;\n\t } & TriggerTarget);\n\n/**\n * How a webhook checks its sender. Platforms that send webhooks sign every\n * delivery with a secret they show you when you add the URL there; give it\n * here, and a delivery whose signature does not match is refused (401) and\n * never runs the agent.\n *\n * ```ts\n * { kind: 'webhook', verify: { kind: 'stripe', secret: 'whsec_...' } }\n * { kind: 'webhook', verify: { kind: 'header', header: 'X-Webhook-Secret', secret: '...' } }\n * ```\n *\n * The schemes: `stripe`, `github`, `shopify`, `slack` and\n * `standard` (the Standard Webhooks spec) check that platform's signature;\n * `header` checks a header your sender adds (Zapier, Make, n8n, your own code\n * can all add one); `none` relies on the URL alone.\n */\nexport type WebhookVerify =\n\t| { kind: 'none' }\n\t| { kind: 'stripe' | 'github' | 'shopify' | 'slack' | 'standard'; secret?: string; secretSet?: boolean }\n\t| { kind: 'header'; header: string; secret?: string; secretSet?: boolean };\n\n/** One delivery a webhook received, newest first from `agent.webhookDeliveries()`. */\nexport interface WebhookDelivery {\n\tid: string;\n\treceivedAt: string;\n\t/** `pending`: checked and starting. `repeat`: the sender retried a delivery\n\t * already accepted; the agent did not run again. */\n\toutcome: 'pending' | 'accepted' | 'refused' | 'repeat' | 'test';\n\t/** Why it was refused. */\n\treason?: string;\n\t/** The sender's own id for it, when it gave one. */\n\tdeliveryId?: string;\n\t/** The conversation it ran in. */\n\tconversationId?: string;\n}\n\n/**\n * WHERE a schedule or webhook runs. Name a conversation, or an end user, or\n * neither; never both.\n *\n * ```ts\n * const conversation = await agent.start({ endUserId: 'ops-app' });\n * await agent.setTriggers([\n * { kind: 'chat' },\n * // Every firing lands in THIS conversation: its history, its drive.\n * { kind: 'schedule', cron: '0 15 * * *', message: 'REFRESH', conversationId: conversation.id },\n * ]);\n * ```\n */\nexport type TriggerTarget =\n\t| {\n\t\t\t/**\n\t\t\t * The conversation every firing CONTINUES: it sees that conversation's\n\t\t\t * history and its drive, and its end user comes with it. Each firing\n\t\t\t * also moves the conversation to the agent's latest version, so a fix\n\t\t\t * you publish reaches it.\n\t\t\t *\n\t\t\t * This is the ONLY way a trigger continues one of your conversations.\n\t\t\t * One end user can have many, so naming the end user cannot say which.\n\t\t\t *\n\t\t\t * Must be a conversation of this agent, started for one of your end\n\t\t\t * users (the id `agent.start()` returned). Anything else is refused\n\t\t\t * when you set the trigger. If the conversation is deleted later, the\n\t\t\t * firings fail and say so; none is opened in its place.\n\t\t\t */\n\t\t\tconversationId: string;\n\t\t\tuserId?: undefined;\n\t }\n\t| {\n\t\t\tconversationId?: undefined;\n\t\t\t/**\n\t\t\t * The end user each firing is for, when it names no conversation. Each\n\t\t\t * firing STARTS A NEW CONVERSATION for them, with an empty drive, and it\n\t\t\t * is listed by `agent.conversations({ endUser })`. To keep adding to\n\t\t\t * one conversation instead, name it with `conversationId`.\n\t\t\t *\n\t\t\t * Your id, as in `agent.start({ endUserId })`; the platform's prefixes\n\t\t\t * (`member:`, `visitor:`, `job:`) are refused.\n\t\t\t *\n\t\t\t * Name neither and the trigger keeps one conversation of its own that\n\t\t\t * every firing continues.\n\t\t\t */\n\t\t\tuserId?: string;\n\t };\n\n/**\n * One connection in an agent's setup, as it reads back: a NAME, an APP, and\n * WHOSE ACCOUNT. Exactly one of these two:\n *\n * | the connection says | whose account | the person signs in |\n * |---|---|---|\n * | `account: '<id>'` | the agent creator's: one account in this workspace, from `ws.accounts.connect()` | once, and every conversation uses it |\n * | `endUser: true` | the end user's: each person talking to the agent | once each, remembered for them |\n *\n * A turn uses EXACTLY the account the setup names, and never another one.\n * There is no fallback to some other account of the same app: an account that\n * is not signed in leaves the connection unavailable, and the agent says so.\n *\n * `name` tells two connections of one app apart, such as `work-gmail` and\n * `personal-gmail`. 1 to 64 characters, lowercase letters, digits and dashes,\n * no dash at either end, unique on the agent. Left out when sent, it is the\n * app's id.\n *\n * Part of the agent's CONFIGURATION, so changing a connection publishes a\n * version. The account itself is not versioned: signing in again reaches every\n * agent that names it. To send one, build it with `Connection.creatorAccount()`\n * or `Connection.endUserAccount()`.\n */\nexport type AgentConnection =\n\t| {\n\t\t\tname: string;\n\t\t\t/** The app's id: `App.GMAIL` is `'gmail'`. */\n\t\t\tapp: AppId;\n\t\t\t/** An account id in this workspace whose app is `app`. */\n\t\t\taccount: string;\n\t\t\tendUser?: never;\n\t\t\t/** Which of the app's own tools the agent may use; absent for the app's default. */\n\t\t\ttools?: string[];\n\t }\n\t| {\n\t\t\tname: string;\n\t\t\tapp: AppId;\n\t\t\tendUser: true;\n\t\t\taccount?: never;\n\t\t\ttools?: string[];\n\t };\n\n/** What an agent NEEDS, not the stored value. `Secret` is the value. */\nexport interface SecretRequirement {\n\tname: string;\n\tscope: Scope;\n\trequired?: boolean;\n}\n\nexport interface Parameter {\n\tname: string;\n\ttype: 'string' | 'number' | 'date' | 'choice';\n\trequired?: boolean;\n\tdefault?: string;\n\tchoices?: string[];\n}\n\nexport interface McpServer {\n\tname: string;\n\turl: string;\n\ttransport?: 'http' | 'sse';\n\theaders?: Record<string, string>;\n\ttimeoutMs?: number;\n}\n\nexport interface HttpToolParam {\n\ttype: 'string' | 'number' | 'boolean' | 'object' | 'array';\n\tdescription?: string;\n\trequired?: boolean;\n\tchoices?: (string | number | boolean | null)[];\n\tnullable?: boolean;\n\tinteger?: boolean;\n\tproperties?: Record<string, HttpToolParam>;\n\titems?: HttpToolParam;\n\tadditionalProperties?: boolean;\n\t/**\n\t * A regular expression the whole value must match (strings only). Checked\n\t * before the call; a value that does not match is refused, so what a person\n\t * typed cannot change the request it is written into. Patterns that repeat a\n\t * repeating group, like `(a+)+`, are refused when saved.\n\t *\n\t * ```ts\n\t * order_number: { type: 'string', required: true, pattern: '[0-9]{1,10}' }\n\t * ```\n\t */\n\tpattern?: string;\n\t/** The longest value accepted (strings only). 2000 when a pattern is set\n\t * and this is not. */\n\tmaxLength?: number;\n}\n\n/** One check on an HTTP tool's answer: the value at `field` (a dotted path,\n * numbers for list positions) must equal `equals`, which may use\n * `{{params.NAME}}`. */\nexport interface HttpToolMatch {\n\tfield: string;\n\tequals: string;\n\tignoreCase?: boolean;\n}\n\n/** Something the agent may ask the website page to do, such as add to cart.\n * The page carries it out with `window.AgentNava.page.handle(name, fn)`, and\n * what `fn` returns goes back to the agent. Only a website chat has a page; in\n * any other conversation the agent is told it cannot. */\nexport interface PageAction {\n\t/** Lowercase letters, digits and `_`; never one of the agent's HTTP tool names. */\n\tname: string;\n\t/** What it does and when the agent should use it. */\n\tdescription: string;\n\t/** What the agent fills in; the page's handler receives these as its argument. */\n\tparams?: Record<string, HttpToolParam>;\n}\n\nexport interface HttpTool {\n\tname: string;\n\tdescription: string;\n\tmethod: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';\n\turl: string;\n\theaders?: Record<string, string>;\n\tbody?: string;\n\tparams?: Record<string, HttpToolParam>;\n\tsecrets?: string[];\n\ttimeoutMs?: number;\n\t/**\n\t * Checks on the answer, all of which must pass. If any fails, or the call\n\t * answers with an error or a body that is not JSON, the agent gets only\n\t * `notFound`, never the answer. Use it for \"show an order only when the\n\t * number and the email both match\":\n\t *\n\t * ```ts\n\t * requireMatch: [\n\t * { field: 'data.orders.nodes.0.name', equals: '#{{params.order_number}}' },\n\t * { field: 'data.orders.nodes.0.email', equals: '{{params.email}}', ignoreCase: true },\n\t * ],\n\t * notFound: 'No order matches that number and email.',\n\t * ```\n\t */\n\trequireMatch?: HttpToolMatch[];\n\t/** What the agent gets when a check fails. Default: \"Not found.\" */\n\tnotFound?: string;\n\t/**\n\t * Sign the request in with one of the agent's connections (its `name`)\n\t * instead of a stored key. The connection's provider adds a current\n\t * credential and keeps it fresh, so the credential never leaves it. The\n\t * address must start with `https://` and a written-out host, and that host\n\t * must be the connected account's own API; leave out `Authorization`.\n\t *\n\t * ```ts\n\t * { name: 'look_up_order', method: 'POST', signInWith: 'shopify',\n\t * url: 'https://my-store.myshopify.com/admin/api/2026-04/graphql.json', ... }\n\t * ```\n\t */\n\tsignInWith?: string;\n\t/**\n\t * Values the website page supplies for this visitor, used as\n\t * `{{pageEncoded.NAME}}` in the address, `{{pageJson.NAME}}` in a JSON body,\n\t * or `{{page.NAME}}` for a plain token (letters, digits, `- _ . ~` only).\n\t * On the page, the chat script's public call sets them:\n\t * `window.AgentNava.page.set('cart_id', value)`. They are kept per\n\t * conversation and filled in at call time; the model sees one only if the\n\t * API's answer repeats it, and logs redact them. Names: lowercase letters, digits, `_`.\n\t */\n\tpageValues?: string[];\n}\n\n/**\n * Guardrails: what the platform enforces on an agent, even when someone tries to\n * talk the agent out of it. An agent's `instructions` are what it TRIES to do;\n * its guardrails are checked by the platform before the agent reads a message,\n * so a person cannot argue it past them.\n *\n * ```ts\n * await agent.update({\n * guardrails: {\n * stayOnTopic: true,\n * rules: ['Never offer discounts or change prices.'],\n * },\n * });\n * ```\n *\n * A message that breaks one is declined before the agent reads it, and the agent\n * replies politely in its own voice instead; an action that breaks one does not\n * happen; a reply that breaks one is taken back and answered again. The turn's\n * stream says which rule fired (`{ type: 'guardrail' }`).\n *\n * Part of the CONFIGURATION, like `connections`, so changing it publishes a\n * version, and a conversation already in progress keeps the version it began on.\n */\nexport interface Guardrails {\n\t/** Decline anything outside this agent's job, judged from its own\n\t * instructions: other work (code, homework, poems), \"ignore your\n\t * instructions\", requests for its prompt or files, and \"I'm the owner\"\n\t * claims made to get special treatment. ON unless set to `false`. */\n\tstayOnTopic?: boolean;\n\t/** Your own rules, one plain-English sentence each (at most 20, each under\n\t * 500 characters). Each is checked on the message, on every action the agent\n\t * tries, and on its reply. A break is declined, blocked or taken back, and the\n\t * stream names the rule. */\n\trules?: string[];\n\t/** Check each message before the agent reads it, so one that breaks a\n\t * guardrail never reaches the agent and nothing it asked for is done. Costs\n\t * about 0.2 s per message. ON unless set to `false`: off, the agent answers\n\t * at once, and its replies and actions are still checked. */\n\tcheckMessagesFirst?: boolean;\n}\n\n/**\n * What an agent keeps about each end user between conversations: the settings,\n * not the memory itself. The memory is written after chats, never during one, and\n * no call or tool asks for it. One memory per agent and end user, and agents never\n * share one.\n *\n * await agent.update({ memorySettings: { on: true, instructions: 'Keep food allergies.' } });\n *\n * Part of the CONFIGURATION, like `guardrails`, so changing it publishes a version.\n * It comes with Team and up. Some limits sit outside these settings: a memory never\n * keeps a password, a key, or a card or id number, whatever `instructions` says.\n */\nexport interface MemorySettings {\n\t/** Keep a memory about each end user. OFF unless set to `true`. */\n\ton?: boolean;\n\t/** Added after our default, and wins where they differ. Up to 2,000 characters. */\n\tinstructions?: string;\n\t/** Days to keep it, since the end user's last chat. 180 unless set. */\n\tkeepForDays?: 30 | 90 | 180 | 365;\n\t/** Show end users a switch that turns memory off for a chat. ON unless `false`. */\n\tendUserSwitch?: boolean;\n\t/** Ask a yes or no on our chat surfaces before memory is used. OFF unless `true`. */\n\taskFirst?: boolean;\n\t/** The owner's privacy policy. The notice links to it. */\n\tprivacyUrl?: string;\n}\n\n/** One agent's memory about one end user. */\nexport interface MemoryData {\n\tendUserId: string;\n\t/** The whole memory, one fact per line. This is what the agent reads. */\n\ttext: string;\n\t/** They asked to stop. `text` is empty, and nothing more is kept about them. */\n\tstopped: boolean;\n\t/** How far the memory has read their chats: nothing older can change it. Absent while no memory exists. */\n\treadUpTo?: string;\n\t/** When it last changed. Absent while no memory exists. Pass it as `before` to page through `agent.memories()`. */\n\tupdatedAt?: string;\n\t/** When they asked to stop. */\n\tstoppedAt?: string;\n\t/** What the last update after a chat did to it. Counts only, none of their words. */\n\tlastRun?: { at: string; added: number; replaced: number; removed: number };\n\t/** When they first said yes, where the owner asks first. */\n\tagreedAt?: string;\n\t/** Their newest chat on this agent. */\n\tlastChatAt?: string;\n\t/** How many of their chats on this agent hold a message. */\n\tconversations?: number;\n}\n\nexport interface AgentData {\n\tid: string;\n\tname: string;\n\tinstructions: string;\n\tgreeting?: string;\n\t/** Always present, with the defaults filled in: what is enforced now. */\n\tguardrails: { stayOnTopic: boolean; rules: string[]; checkMessagesFirst: boolean };\n\t/** Always present, with the defaults filled in. `notice` is the line to show near\n\t * the message box if you draw your own chat. It is screen text, written by code,\n\t * and the model never sees it. */\n\tmemorySettings: Required<Omit<MemorySettings, 'privacyUrl'>> & { privacyUrl?: string; notice: string };\n\t/** How its website chat looks. Absent: the default look, starting closed. */\n\tappearance?: WebsiteChatAppearance;\n\tgrade: Grade;\n\tfilesystem: Filesystem;\n\ttools: string[];\n\tworkflows: Workflow[];\n\t/** The apps it uses, each saying whose account. See `AgentConnection`. */\n\tconnections: AgentConnection[];\n\tsecrets: SecretRequirement[];\n\tparameters: Parameter[];\n\ttriggers: Trigger[];\n\tmcpServers: McpServer[];\n\thttpTools: HttpTool[];\n\t/** Always a list, empty when the agent has none. */\n\tpageActions: PageAction[];\n\t/** Workspace knowledge bases this agent reads, by key. Always present, empty\n\t * when none are attached, so a caller never has to distinguish \"none\" from\n\t * \"an older server that did not send the field\". */\n\tknowledgeBases: string[];\n\t/** Workspace databases this agent may query, by key. Always present, like\n\t * `knowledgeBases`. */\n\tdatabases: string[];\n\tversion: number;\n\tcreatedAt: string;\n\tupdatedAt: string;\n\t/** Where it is live, as the portal shows it. The API works it out by the\n\t * same rule it runs the agent by, so `channels.api === 'on'` is exactly\n\t * \"a turn will be taken\". Read-only: nothing sets it. Sent by `agent.get()`\n\t * and `ws.agents.list()`; absent from what create and update return, so\n\t * call `get()` for it. */\n\treadonly status?: AgentStatus;\n}\n\n/** Who can chat with an agent's website chat. 'restricted' (the default):\n * members of your workspace signed in to AgentNava, and visitors on the\n * allowed websites. 'anyone': anyone with the link, on any website. */\nexport type WebsiteChatAccess = 'restricted' | 'anyone';\n\n/** What `agent.websiteChat.get()` gives back. */\nexport interface WebsiteChat {\n\t/** Who can chat. */\n\treadonly access: WebsiteChatAccess;\n\t/** The hosted chat page, the share link. Share it, or open it in an iframe. */\n\treadonly chatUrl: string;\n\t/** The id the embed script's `data-agent-id` takes. */\n\treadonly embedAgentId: string;\n}\n\n/** Where an agent is live. See `AgentData.status`. */\nexport interface AgentStatus {\n\t/** True when any channel is on. */\n\treadonly live: boolean;\n\t/** Paused by its owner: nothing runs it until it is resumed. */\n\treadonly paused: boolean;\n\t/** Archived: nothing runs it again. */\n\treadonly archived: boolean;\n\treadonly channels: {\n\t\t/** 'on' when the API takes new turns for it. */\n\t\treadonly api: 'on' | 'off';\n\t\t/** 'on' when its website chat page answers visitors. 'unavailable' when it\n\t\t * needs what each user brings (`eachUserBrings`), which a visitor cannot. */\n\t\treadonly websiteChat: 'on' | 'off' | 'unavailable';\n\t};\n\t/** Required accounts and secrets of its own that are not set up yet, on the\n\t * version a new conversation runs. It still runs: the tools that use them\n\t * fail until they are set up. Empty when nothing is missing. */\n\treadonly needs: readonly { kind: 'connection' | 'secret'; name: string }[];\n\t/** The accounts and secrets each user supplies for themselves. */\n\treadonly eachUserBrings: readonly { kind: 'connection' | 'secret'; name: string }[];\n}\n\n/**\n * One of an app's own ready-made tools, as `ws.apps.tools(app)` lists it: what\n * a connection's `tools` chooses from.\n */\nexport interface AppTool {\n\t/** What a connection's `tools` names: `SLACK_SEND_MESSAGE`. */\n\tid: string;\n\t/** The tool service's name for it: \"Send message\". */\n\tname: string;\n\t/** Its description's first sentence. */\n\tsummary: string;\n\t/** It removes something (a message, a file, a record). */\n\tdeletes?: true;\n}\n\n/**\n * One of an agent's connections as it stands right now: the account it names,\n * and whether that account works. What `agent.connections.list()` returns.\n *\n * DECLARED IS NOT CONNECTED, and this is the read that tells them apart. A\n * connection can name an account nobody has finished signing in to, or one that\n * was disconnected since; that is the state a setup checklist has to show, and\n * without this the only way to find it is to start a conversation and watch\n * the agent fail.\n *\n * Reshaped in 0.44: it used to be keyed by `provider`, with `declared`,\n * `required`, `scope` and `suppliedBy`. A connection is now keyed by its\n * `name`, and it says whose account it uses instead of where one might come\n * from.\n */\nexport interface ConnectionStatus {\n\tname: string;\n\tapp: AppId;\n\t/** The agent creator's account the connection names, read fresh. Absent on\n\t * an end user's connection, and on one whose account is gone. */\n\taccount?: AccountData;\n\t/** True when each person talking to the agent signs in with their own. */\n\tendUser?: true;\n\t/** Which of the app's own tools the agent may use; absent for the app's default. */\n\ttools?: string[];\n\t/** Whether the connection works now, as the API reports it. For an agent\n\t * creator's account it follows that account's status. */\n\tstatus: string;\n\t/** Only in the agent's unpublished draft, so no conversation uses it yet. */\n\tinDraft: boolean;\n\t/** Removed in the agent's unpublished draft, and still used until the draft\n\t * is published. */\n\tremovedInDraft: boolean;\n}\n\n/** Where one account's sign-in stands.\n *\n * 'pending' made, and nobody has finished signing in yet\n * 'connected' signed in and working\n * 'needs_sign_in' it worked, and the app now wants the person to sign in again\n * 'error' the app refused it for another reason\n */\nexport type AccountStatus = 'pending' | 'connected' | 'needs_sign_in' | 'error';\n\n/** An agent whose setup names an account, and the connection that names it.\n * Counted from the agent's latest published version and its draft, never\n * from older versions. */\nexport interface UsedBy {\n\t/** The agent's id: the same value as `Agent.id`, so compare this one. */\n\tid: string;\n\t/** AgentNava's internal handle for the same agent (`agt_...`), not `Agent.id`. */\n\tagentId: string;\n\tagentName: string;\n\tconnectionName: string;\n\t/** Named in the agent's unpublished draft. */\n\tinDraft: boolean;\n\t/** Named in the agent's latest published version. */\n\tpublished: boolean;\n}\n\n/**\n * One saved sign-in to one app.\n *\n * TWO OWNERS, and the difference is who signs in:\n *\n * | `owner` | whose | a connection uses it as |\n * |---|---|---|\n * | `'workspace'` | the agent creator's, signed in once, usable by any agent | `Connection.creatorAccount(app, account)` |\n * | `'end-user'` | one of YOUR end users', remembered for that person | `Connection.endUserAccount(app)`, in that person's conversations |\n *\n * Both owners are read and changed through the same routes and the same\n * `Account` handle; `owner` says which this is.\n *\n * Nobody handles a token: the person signs in on the app's own page, and this\n * record says who they signed in as, never how.\n */\nexport interface AccountData {\n\tid: string;\n\t/** The app's id: `App.GMAIL` is `'gmail'`. */\n\tapp: AppId;\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\tlabel: string | null;\n\t/** Your own name for it, such as `'Support inbox'`. Null when none was given. */\n\tnickname: string | null;\n\tstatus: AccountStatus;\n\t/** True while a reconnect waits for the person to sign in again. The\n\t * account keeps working meanwhile, so `status` can still say\n\t * `'connected'`; `waitUntilConnected()` waits for this to clear. */\n\treconnecting: boolean;\n\towner: 'workspace' | 'end-user';\n\t/** The end user it belongs to, when `owner` is `'end-user'`. An id from YOUR\n\t * database, never a workspace member's. */\n\tendUserId?: string;\n\t/** The agent that end user belongs to, when `owner` is `'end-user'`: the same id\n\t * on another agent is another person. */\n\tagentId?: string;\n\tconnectedAt: string | null;\n\tlastCheckedAt: string | null;\n\t/** The agents whose setup names this account. Empty for an end user's\n\t * account, which a setup never names by id. */\n\tusedBy: UsedBy[];\n}\n\n/** An end user's account a conversation still needs, with the link that\n * person signs in at. From `conversation.accountsNeeded()`. */\nexport interface AccountNeeded {\n\t/** The connection's name on the agent. */\n\tname: string;\n\tapp: AppId;\n\t/** Send the person here. They sign in once, and every later conversation\n\t * of theirs has it. */\n\turl: string;\n}\n\n/** Something a conversation is still missing before it can work. */\nexport interface Requirement {\n\tkind: 'secret' | 'connection';\n\tname: string;\n\trequired: boolean;\n}\n\nexport interface AgentConversationData {\n\tid: string;\n\tagentId: string;\n\t/** The number of the agent version this conversation runs, as `agent.version`\n\t * numbers them. Fixed at start, except that a schedule or webhook naming the\n\t * conversation moves it to the latest version at each firing. */\n\tversion: number;\n\tstartedAt: string;\n\ttitle?: string;\n\t/** Whether this conversation uses end-user memory, fixed when it started. Absent\n\t * on one from before memory, which counts as off. */\n\tmemory?: 'on' | 'off';\n\t/** True when nothing REQUIRED is outstanding. An optional gap leaves this\n\t * true, because the conversation genuinely does work without it. */\n\tready: boolean;\n\trequires: Requirement[];\n}\n\nexport interface ProjectConversationData {\n\tid: string;\n\tprojectId: string;\n\tstartedAt: string;\n\ttitle?: string;\n\tready: boolean;\n\trequires: Requirement[];\n}\n\n/** An AGENT on a project, not a person.\n *\n * Called `ProjectAgent` and not `Member`, which is what it was. `Member` is\n * the word this product uses for a HUMAN on the account, so a public type of\n * that name holding an `agentId` was the same trap as `userId` meaning two\n * populations: `import { Member }` handed you the agent one, and the human one\n * had to carry a prefix to get out of its way.\n *\n * The people are `WorkspaceMember`, from `ws.members()`. Nothing here is a\n * person. */\nexport interface ProjectAgent {\n\tagentId: string;\n\tname: string;\n\taddedAt: string;\n}\n\nexport interface ProjectData {\n\tid: string;\n\tname: string;\n\tgoal: string | null;\n\tmanagerInstructions: string | null;\n\tgrade: Grade;\n\tstatus: 'active' | 'archived';\n\tagents: ProjectAgent[];\n\tcreatedAt: string;\n\tupdatedAt: string;\n}\n\nexport interface Step {\n\tid: string;\n\tlabel: string;\n\tkind: 'read' | 'write' | 'edit' | 'search' | 'shell' | 'web' | 'data' | 'task' | 'plan' | 'other';\n\tstatus: 'active' | 'done' | 'error';\n}\n\nexport type TurnEvent =\n\t/** First, before anything else: the id the API gave this turn. */\n\t| { type: 'start'; turnId: string }\n\t/** Carries the WHOLE step list every time, not a change to it. Replace what\n\t * you are showing rather than merging; that is what makes reconnecting\n\t * safe. Sent after each tool event that changes the list, with the steps\n\t * and words the portal shows. */\n\t| { type: 'progress'; steps: Step[] }\n\t/** `name` is the tool's identifier, for code. `label` is what a person sees,\n\t * the portal's words (\"Searching the web: <query>\"). */\n\t| { type: 'tool'; name: string; state: 'start' | 'end'; label?: string; ok?: boolean }\n\t/** A fragment, not a line. Append; do not assemble. */\n\t| { type: 'text'; text: string }\n\t/** A file the agent wrote in the conversation's drive. Pass `path` to\n\t * conversation.readFile(). Sent once per file per turn. */\n\t| { type: 'file'; path: string }\n\t/** A widget the agent is drawing inside its reply, sent whole each time it\n\t * grows: replace the one with the same `surfaceId`. Only on a conversation\n\t * started with `widgets: true`. */\n\t| { type: 'widget'; widget: Widget }\n\t/** `widgets` is the reply's final widget list, present when it has any:\n\t * what to keep, whatever arrived live. */\n\t/** One of the agent's guardrails fired. `rule` is the one, as you wrote it,\n\t * or 'Stay on topic'.\n\t * - `message`: the person's message broke it and was declined. The agent\n\t * never read it; the words that follow are its polite refusal.\n\t * - `reply`: the agent's reply broke it and was taken back (a `reset` came\n\t * first); the words that follow are its answer within the rule.\n\t * - `tool`: an action the agent tried (`tool`) broke one of your rules and\n\t * did not happen; the agent explains in the words that follow. Which rule\n\t * is not reported for this stage. */\n\t| { type: 'guardrail'; stage: 'message'; rule: string; action: 'declined' }\n\t| { type: 'guardrail'; stage: 'reply'; rule: string; action: 'replaced' }\n\t| { type: 'guardrail'; stage: 'tool'; tool: string; action: 'blocked' }\n\t/** Replace the words you have shown for this reply with `text`, often empty:\n\t * earlier words were withdrawn (a guardrail) or a step was retried. Clear the\n\t * reply's widgets too; any that still belong are sent again. */\n\t| { type: 'reset'; text: string }\n\t/** Sent at the start of a turn, once per name: a secret the agent declared\n\t * for each conversation, and one of its tools uses, has no usable value\n\t * here. `expired`: the value you pushed has run out. `missing`: none was\n\t * given, or the stored secret attached under that name was deleted or no\n\t * longer belongs to this person. The turn still runs; the tool says it\n\t * cannot sign in. Push a fresh value (`ask(text, { secrets })`) and ask\n\t * again. */\n\t| { type: 'credential_needed'; name: string; reason: 'missing' | 'expired' }\n\t| { type: 'done'; reply: string; widgets?: Widget[] }\n\t| { type: 'error'; message: string; retryable: boolean };\n\n/**\n * One part of a widget, in Google's open A2UI v0.9 format with AgentNava's own\n * set of parts: an id, which kind of\n * part it is (`Column`, `Row`, `Card`, `Text`, `Stat`, `KeyValue`, `Table`,\n * `Chart`, `Image`, `List`, `Note`, `Badge`, `Link`, `Button`, `Suggestions`,\n * `Divider`), and that kind's props. A container lists its children by id.\n */\nexport interface WidgetPart {\n\tid: string;\n\tcomponent: string;\n\t[prop: string]: unknown;\n}\n\n/**\n * A card, table or chart the agent drew inside a reply. Draw it with `WidgetView`\n * from `@cerebro-labs/agentnava-sdk/react`, or your own components starting\n * from the part with id `root`.\n * `at` is where it sits in the reply's text, as a character count, so it goes\n * between the right sentences.\n */\nexport interface Widget {\n\tsurfaceId: string;\n\tat: number;\n\tcomponents: WidgetPart[];\n\t/** Every part arrived. False only while it streams, or on a reply cut short. */\n\tclosed: boolean;\n}\n\nexport interface Message {\n\trole: 'user' | 'assistant';\n\tcontent: string;\n\tat: string;\n\t/** The widgets in an assistant reply, when it has any. */\n\twidgets?: Widget[];\n\t/** The files sent with a question, when it had any: name, kind and type,\n\t * never the bytes. `content` stays the words that were typed. */\n\tattachments?: SentAttachment[];\n}\n\n/** A file that was sent with a question, as a read of the conversation returns it. */\nexport interface SentAttachment {\n\tkind: 'image' | 'file';\n\tname?: string;\n\tmimeType: string;\n\t/** Decoded size, in bytes. */\n\tsizeBytes: number;\n}\n\n/** An image goes to the model as an image. Anything else is written into the\n * conversation's drive before the turn starts, so the agent can parse it,\n * compute over it, and still have it next turn. */\nexport interface Attachment {\n\tkind: 'image' | 'file';\n\tname?: string;\n\tmimeType: string;\n\t/** base64, without a data: prefix. */\n\tdata: string;\n}\n\n/** A knowledge base: a named folder of files at WORKSPACE scope that an agent\n * configuration attaches by key. */\nexport interface KnowledgeBaseData {\n\t/** Workspace-unique. This is what an agent configuration stores, and it is\n\t * not editable, because renaming it would move every attachment silently. */\n\tkey: string;\n\tname: string;\n\tdescription?: string;\n\t/** Counted when read, so it never disagrees with the files it counts. */\n\tfileCount: number;\n\tcreatedAt: string;\n\tupdatedAt: string;\n}\n\n/** One file in a knowledge base. PATH, not a flat name: a base is a folder, and\n * the session mount reproduces the shape verbatim. */\n/**\n * Whether an agent can read a file in a knowledge base.\n *\n * Text and markdown are readable as they stand. A PDF, a scan or a spreadsheet\n * has to be turned into text first, which happens when the file is uploaded\n * rather than when a question is asked, so an agent never waits on it.\n *\n * 'readable' an agent can read this now\n * 'processing' being prepared, check back shortly\n * 'unreadable' cannot be read, and `reason` says why\n */\nexport type KnowledgeFileStatus = 'readable' | 'processing' | 'unreadable';\n\nexport interface KnowledgeFileEntry {\n\tpath: string;\n\tmimeType: string;\n\tsize: number;\n\tcreatedAt: string;\n\tupdatedAt: string;\n\t/** Whether an agent can read this file. */\n\tstatus: KnowledgeFileStatus;\n\t/** Why it cannot be read, as a sentence you could show someone.\n\t *\n\t * The same text reaches the agent in place of the file, so a developer\n\t * reading this and a model reading the base get one explanation rather than\n\t * two that can drift apart. Absent unless `status` is 'unreadable'. */\n\treason?: string;\n}\n\nexport interface FileEntry {\n\tpath: string;\n\tkind: 'file' | 'directory';\n\tmimeType: string;\n\tsize: number;\n\tmodifiedAt: string;\n}\n\n/** Fields accepted when creating or updating an agent. Everything is optional on\n * an update: what you do not send is inherited from the current version, which\n * is the whole reason updating is safe. */\nexport interface AgentFields {\n\tid?: string;\n\tname?: string;\n\tinstructions?: string;\n\tgreeting?: string;\n\t/** What the platform enforces on every incoming message. See `Guardrails`. */\n\tguardrails?: Guardrails;\n\t/** What it keeps about each end user between conversations. See `MemorySettings`. */\n\tmemorySettings?: MemorySettings;\n\t/**\n\t * How the agent's website chat looks, on the share link and through the\n\t * embed script. Merged by setting: `{ accent: '#B4532A' }` changes the colour\n\t * and keeps the rest; `null` clears one setting, and `appearance: null` goes\n\t * back to the default look. See `WebsiteChatAppearance`.\n\t */\n\tappearance?: { [K in keyof WebsiteChatAppearance]?: WebsiteChatAppearance[K] | null } | null;\n\tgrade?: Grade;\n\tfilesystem?: Filesystem;\n\t/**\n\t * Knowledge bases this agent reads, by workspace-unique key.\n\t *\n\t * Mounted READ-ONLY at `/knowledge/<key>/` when a session starts, so the\n\t * agent finds things with `ls`, `grep` and `cat` rather than a retrieval API.\n\t *\n\t * Which bases an agent reads is part of its CONFIGURATION, so changing this\n\t * publishes a version you can diff and roll back. The CONTENTS of a base are\n\t * not versioned: correcting a file changes what every attached agent sees on\n\t * its next turn, which is the same by-reference promise `agent.knowledge`\n\t * already makes.\n\t *\n\t * A key that names no base is refused when the configuration is saved, and\n\t * deleting a base is refused while any agent attaches it, so an attachment\n\t * that was valid stays valid.\n\t */\n\tknowledgeBases?: string[];\n\t/**\n\t * Workspace databases this agent may query, by key.\n\t *\n\t * Register the database first with `ws.databases.create()`, which logs in\n\t * and gives it the key; then name the key here. Naming it is the grant: a\n\t * database answers only the agents whose configuration names it, and the\n\t * agent's prompt lists them so it knows what to query.\n\t *\n\t * Part of the CONFIGURATION, so changing it publishes a version. A key that\n\t * names no database is refused when the configuration is saved, and\n\t * removing a database is refused while any agent names it.\n\t */\n\tdatabases?: string[];\n\ttools?: string[];\n\t/**\n\t * \"Answer with widgets\": on unless `false`. When on, the agent draws cards,\n\t * tables and charts inside its answers, on a conversation started with\n\t * `widgets: true`. Set `false` for an agent that should answer in text only.\n\t */\n\twidgets?: boolean;\n\tworkflows?: Workflow[];\n\t/**\n\t * The apps this agent uses, each a NAMED connection that says whose account\n\t * it uses: the agent creator's account, or each end user's own. See\n\t * `Connection` and `AgentConnection`.\n\t *\n\t * ```ts\n\t * connections: [\n\t * Connection.creatorAccount(App.GMAIL, work, { name: 'work-gmail' }),\n\t * Connection.creatorAccount(App.GMAIL, personal, { name: 'personal-gmail' }),\n\t * Connection.endUserAccount(App.GOOGLE_CALENDAR),\n\t * ]\n\t * ```\n\t *\n\t * The builders make plain objects, `{ app, account: '<id>' }` and\n\t * `{ app, endUser: true }`, and those objects are accepted as they are.\n\t * `App.GMAIL` is the string `'gmail'`, so a plain app id works too.\n\t *\n\t * Part of the CONFIGURATION, like `databases`, so changing it publishes a\n\t * version. An unknown account id, an account of a different app, a repeated\n\t * name or a name of the wrong shape is refused when the configuration is\n\t * saved, naming the problem.\n\t */\n\tconnections?: Connection[];\n\tsecrets?: SecretRequirement[];\n\tparameters?: Parameter[];\n\ttriggers?: Trigger[];\n\tmcpServers?: McpServer[];\n\thttpTools?: HttpTool[];\n\t/** Things the agent may ask the website page to do (see PageAction). */\n\tpageActions?: PageAction[];\n}\n\nexport interface ProjectFields {\n\tname?: string;\n\tgoal?: string;\n\tmanagerInstructions?: string;\n\tgrade?: Grade;\n\tagents?: string[];\n}\n\n/** Secret VALUES supplied to an agent (`agent.attach`) or to one conversation\n * (`conversation.attach`): the secrets its agent declared.\n *\n * Accounts are no longer supplied this way (0.44). An agent's setup names the\n * account each connection uses, so there is nothing left to attach. */\nexport interface Bindings {\n\tsecrets?: Record<string, string>;\n}\n\n/** One markdown file read off disk: a workflow or a subagent.\n *\n * `when` is OPTIONAL here and required at publish. A file on disk has no `when`\n * until someone writes one, and refusing to read it would be the wrong place to\n * say so; the API says it at publish, with a message naming the file. */\nexport interface SpecFile {\n\tname: string;\n\tcontent: string;\n\twhen?: string;\n}\n\n/** What a loaded agent directory produces: the fields `ws.agents.create()` takes,\n * plus the id the caller chose and the few legacy names the loader still reads\n * off an existing directory. */\nexport interface SpecInput extends Omit<AgentFields, 'workflows'> {\n\tkey: string;\n\tworkflows?: SpecFile[];\n\tsubagents?: SpecFile[];\n\tsandbox?: Filesystem;\n\trole?: string;\n\t/** Retired in favour of `tools: ['web_search']`, still read off an existing\n\t * agent directory so one written before the rename still loads. */\n\twebSearch?: boolean;\n}\n\n/** A PERSON with access to this workspace: your colleague, on a seat.\n *\n * NOT one of your end users, and the difference is the sharpest naming trap in\n * this SDK. A workspace member signs in here, we issued their id, and we\n * authenticate it. A `EndUser` is the person YOUR application serves, never\n * signs in here, and carries an id from YOUR database that we store verbatim\n * and never check.\n *\n * `userId` below is therefore a PLATFORM id and is not interchangeable with\n * `agent.start({ endUserId })`. Both fields were once spelled `userId`, which\n * made passing one where the other belonged a call that could not fail: a\n * end-user id is never validated against anything, so it silently labelled a\n * conversation with a colleague's internal id. The two names now differ.\n *\n * Named WorkspaceMember rather than Member because a public `Member` type used\n * to mean an AGENT attached to a project. That one is now `ProjectAgent`, so\n * the prefix here is no longer load-bearing; it stays because it says which of\n * the two populations this is at every call site, and that is worth four\n * characters.\n *\n * Read-only, like everything else on the account surface: the SDK can see who\n * is on the account and what they may do, and cannot add, remove or re-role\n * anyone. Those are decisions a person makes while signed in, not something a\n * key should be able to do on their behalf. */\nexport interface WorkspaceMember {\n\t/** A PLATFORM user id (`usr_...`), issued by us. Never pass this to\n\t * `agent.start({ endUserId })` or `agent.endUser()`. */\n\tuserId: string;\n\temail: string;\n\tname: string | null;\n\t/** 'owner' is the account's creator; 'admin' can manage it; 'member' cannot. */\n\trole: 'owner' | 'admin' | 'member';\n\t/** Unix seconds. */\n\tjoinedAt: number;\n}\n\n/** What is left to spend.\n *\n * `credits` is the balance in credits, where one credit is one US dollar. The\n * turn counts are the same balance expressed in the unit people actually\n * reason with, because \"12.5 credits\" answers nothing on its own. */\nexport interface Credits {\n\tcredits: number;\n\tstandardTurnsRemaining: number;\n\tpremiumTurnsRemaining: number;\n}\n\n/** The seat plan this workspace is on.\n *\n * Seats are a SEPARATE purchase from credits: a plan says how many people may\n * share the account, credits pay for the work their agents do. Being on the\n * free plan does not make turns free. */\nexport interface SeatPlan {\n\tkey: string;\n\tlabel: string;\n\t/** People allowed. `null` means negotiated, which is NOT unlimited and\n\t * should never be shown to anyone as a number. */\n\tseats: number | null;\n\t/** Monthly price in US cents. `null` means there is no published price and\n\t * the answer is a conversation, which is the opposite fact from 0 (free). */\n\tusdCentsPerMonth: number | null;\n}\n\n/** A stored secret, WITHOUT its value.\n *\n * The value is never returned by any read. It is written once, encrypted at\n * rest, and substituted into an HTTP tool's url, headers or body server-side at\n * call time, so it is never part of anything the model can see or repeat. To\n * change one, write it again; to be sure of one, replace it. */\nexport interface SecretMeta {\n\tid: string;\n\tname: string;\n\tdescription?: string | null;\n\t/** The agent this secret is scoped to, or null when the whole workspace\n\t * reads it. An agent-scoped secret of the same name wins. */\n\tagentId?: string | null;\n\t/** The end user this secret belongs to, or null when it is the workspace's.\n\t * One of YOUR user ids, stored verbatim: we never resolve or authenticate\n\t * it. A secret with one can only be attached to a conversation launched for\n\t * that same person. */\n\tendUserId?: string | null;\n\t/** The only hosts a tool may send this key to. Empty until the first tool\n\t * saved with it pins its own host; add one on purpose with\n\t * `secret.setAllowedHosts([...])`. */\n\tallowedHosts?: string[];\n\t/** Who created it and who last changed its value or hosts: a member's id,\n\t * `api_key:<id>`, or `system`. A tool save that pins its hosts is not a\n\t * change to it. Null on a secret from before these were kept. */\n\tcreatedBy?: string | null;\n\tupdatedBy?: string | null;\n\t/** The same, in words: a member's first name or \"API key <name>\"; null\n\t * for our own system or someone no longer in the workspace. */\n\tcreatedByName?: string | null;\n\tupdatedByName?: string | null;\n\tcreatedAt?: string;\n\tupdatedAt?: string;\n\tlastUsedAt?: string | null;\n}\n\n/** A SQL engine AgentNava can query. */\nexport type DatabaseEngine = 'postgres' | 'mysql' | 'sqlserver';\n\n/** How the connection to a database is encrypted. `verify` (the default)\n * encrypts and always checks the server's certificate: against public and\n * cloud CAs and the host name, or, for a database with its own home-made CA,\n * against the CA AgentNava trusted the first time it connected. `disable` is\n * for a server with no SSL at all. */\nexport type DatabaseTls = 'verify' | 'disable';\n\n/**\n * How to log in to a database: a connection string, OR the same login as\n * separate fields. Never both; a request with both is refused.\n *\n * Read-only unless `writable: true`, which no SQL the agent sends can override.\n */\nexport type DatabaseLogin =\n\t| {\n\t\t\t/** `postgres://`, `postgresql://` or `mysql://`, with the user (and the\n\t\t\t * password, when there is one) in it. An `sslmode` in the string sets `tls`. */\n\t\t\turl: string;\n\t\t\tengine?: DatabaseEngine;\n\t\t\t/** Overrides the string's `sslmode`. */\n\t\t\ttls?: DatabaseTls;\n\t\t\twritable?: boolean;\n\t }\n\t| {\n\t\t\tengine: DatabaseEngine;\n\t\t\thost: string;\n\t\t\tport: number;\n\t\t\tdatabase: string;\n\t\t\tusername: string;\n\t\t\t/** Leave it out for a database that takes a user name alone. */\n\t\t\tpassword?: string;\n\t\t\ttls?: DatabaseTls;\n\t\t\twritable?: boolean;\n\t };\n\n/** What `ws.databases.create()` takes: the key agents will name it by, an\n * optional label, and a login. */\nexport type DatabaseFields = DatabaseLogin & {\n\t/** Lowercase letters, digits and dashes, like `shop` or `sales-db`. What an\n\t * agent configuration stores, so it is not editable afterwards. */\n\tkey: string;\n\t/** A label for people. Defaults to the key. */\n\tname?: string;\n};\n\n/** A registered database as AgentNava reports it: where it is, never how to\n * log in. Neither the password nor the connection string is ever returned. */\nexport interface DatabaseData {\n\tkey: string;\n\t/** The underlying connection's id. */\n\tid: string;\n\tname: string;\n\tengine: DatabaseEngine | null;\n\thost: string | null;\n\tport: number | null;\n\tdatabase: string | null;\n\ttls: DatabaseTls | null;\n\twritable: boolean;\n\tstatus: string;\n\tcreatedAt: string;\n\t/** The agents whose configuration names this database. */\n\tusedBy: DatabaseUsedBy[];\n}\n\n/** An agent whose configuration names a database: in its latest published\n * version, in its draft, or in both. */\nexport interface DatabaseUsedBy {\n\t/** The agent's id: the same value as `Agent.id`, so compare this one. */\n\tid: string;\n\t/** AgentNava's internal handle for the same agent (`agt_...`), not `Agent.id`. */\n\tagentId: string;\n\t/** The agent's display name. */\n\tname: string;\n\t/** Named in the agent's latest published version, so its conversations can\n\t * query the database now. */\n\tpublished: boolean;\n\t/** Named in the configuration being edited: the draft when there is one,\n\t * else the published version itself. */\n\tdraft: boolean;\n}\n\n/** One table or view in a database. */\nexport interface DatabaseTable {\n\tschema: string;\n\tname: string;\n\tkind: 'table' | 'view';\n}\n\n/** One column of a table. */\nexport interface DatabaseColumn {\n\tname: string;\n\tdataType: string;\n\tnullable: boolean;\n\tisPrimaryKey: boolean;\n}\n\n/** What `ws.databases.test()` reports. A refused login is an answer, not an\n * exception, because it is the answer the call exists to get. */\nexport type DatabaseTestResult =\n\t| { ok: true; engine: DatabaseEngine; tableCount: number; tables: DatabaseTable[] }\n\t| { ok: false; error: { code: string; message: string } };\n\n/** An agent as its draft holds it, from `agent.draft()`. */\nexport interface AgentDraft {\n\t/** An unpublished draft exists. */\n\texists: boolean;\n\t/** The draft differs from the live version. */\n\thasUnpublishedChanges: boolean;\n\t/** The configuration the draft holds, or the live one when there is no draft. */\n\tconfig: AgentFields;\n}\n\n/**\n * How an agent's website chat looks (the share link, and the chat the embed\n * script opens on a website). Every setting is optional; one left out keeps the\n * default. Part of the agent's configuration, so a change is published with a\n * version like any other field.\n */\nexport interface WebsiteChatAppearance {\n\t/** An image id from `agent.websiteChat.uploadLogo()`, shown in a circle in\n\t * the chat's header. Without one, the name's first letter. */\n\tlogo?: string;\n\t/** The name the chat shows, up to 40 characters. Without one, the agent's name. */\n\ttitle?: string;\n\ttheme?: 'light' | 'dark';\n\t/** One colour, written like `#B4532A`: the avatar, the visitor's messages,\n\t * the send button, chips, prices on cards, and the round button. Where it\n\t * is too light to read as text, words use a darker shade of it. */\n\taccent?: string;\n\t/** `clean` is the default. */\n\tfont?: 'clean' | 'friendly' | 'classic' | 'elegant';\n\t/** Whether the embed script opens the chat when a page loads, on screens\n\t * wider than a phone. New agents start `open`; absent means closed. */\n\topens?: 'open' | 'closed';\n\t/** Up to three short ways in (60 characters each), shown on the chat's\n\t * welcome card before a visitor's first message, such as \"Book a visit\".\n\t * A tap sends its words as the visitor's message. The Agent Builder writes\n\t * them from the agent's job; without them the card shows the greeting. */\n\tstarters?: string[];\n\t/** `false` hides the \"Made with AgentNava\" link under the chat; the\n\t * \"<name> is an AI assistant.\" notice always stays. Shown by default.\n\t * Hiding it needs a paid plan (Team and up): on a free plan the update is\n\t * refused with 403 `plan_required`. A workspace a partner app works in whose\n\t * marketplace forbids \"powered by\" marks (the Wix App Market) may hide it on\n\t * every plan. When a paid plan ends, the link shows again and this choice is\n\t * kept. */\n\tshowMadeWith?: boolean;\n}\n"]}
package/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const SDK_VERSION = "0.79.0";
1
+ export declare const SDK_VERSION = "0.80.0";
package/version.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // GENERATED by scripts/regen-version.mjs. Do not edit: run `bun run regen:version`
2
2
  // after bumping the version in package.json. tests/version.test.ts fails if
3
3
  // this drifts.
4
- export const SDK_VERSION = '0.79.0';
4
+ export const SDK_VERSION = '0.80.0';
5
5
  //# sourceMappingURL=version.js.map
package/version.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,mFAAmF;AACnF,4EAA4E;AAC5E,eAAe;AACf,MAAM,CAAC,MAAM,WAAW,GAAG,QAAQ,CAAC","sourcesContent":["// GENERATED by scripts/regen-version.mjs. Do not edit: run `bun run regen:version`\n// after bumping the version in package.json. tests/version.test.ts fails if\n// this drifts.\nexport const SDK_VERSION = '0.79.0';\n"]}
1
+ {"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,mFAAmF;AACnF,4EAA4E;AAC5E,eAAe;AACf,MAAM,CAAC,MAAM,WAAW,GAAG,QAAQ,CAAC","sourcesContent":["// GENERATED by scripts/regen-version.mjs. Do not edit: run `bun run regen:version`\n// after bumping the version in package.json. tests/version.test.ts fails if\n// this drifts.\nexport const SDK_VERSION = '0.80.0';\n"]}