@personaai/runtime 0.15.0 → 0.16.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
@@ -5,7 +5,13 @@ shared engine every framework adapter (`@personaai/adapters/express`, `@personaa
5
5
  to be a thin translation layer over — see
6
6
  [the SDK Ecosystem plan](https://github.com/hasanraiyan/agent-marketplace/blob/feat/ai/product-research/11-sdk-new/package-ecosystem.md).
7
7
 
8
- **v0.10.0.** Added full Workflows engine support (`POST /workflows/:id/stream`, `GET /workflows`, `POST /workflows/runs/:runId/cancel`, `GET /workflows/runs/:runId/resume`), `workflowsWrite` gated authoring capability, and `beforeWorkflowNode`/`afterWorkflowNode` lifecycle hooks. Unified adapter [`@personaai/adapters`](https://persona.hasanraiyan.me/guides/express/quickstart) provides `express`/`nextjs`/`nestjs` bindings. For non-covered hosts, see
8
+ **v0.16.0.** New opt-in outbound messaging: `POST /messages` (plain SMS/WhatsApp send) and
9
+ `POST /messages/agent` (the Agent composes the reply), both behind `capabilities.messages` — off by
10
+ default because a send targets any E.164 number the caller names and spends this Project's Twilio
11
+ balance. Also includes `/memory` and `/memory/file`'s `scope: "workspace"` for an Agent's own
12
+ `/workspace/` files, full Workflows engine support
13
+ (`POST /workflows/:id/stream`, `GET /workflows`, `POST /workflows/runs/:runId/cancel`,
14
+ `GET /workflows/runs/:runId/resume`), and `beforeWorkflowNode`/`afterWorkflowNode` lifecycle hooks. Unified adapter [`@personaai/adapters`](https://persona.hasanraiyan.me/guides/express/quickstart) provides `express`/`nextjs`/`nestjs` bindings. For non-covered hosts, see
9
15
  [Quickstart](#quickstart) for how to run it directly against raw Node `http`, and
10
16
  [Not yet implemented](#not-yet-implemented) for what's missing before it's a complete Level 2
11
17
  runtime.
@@ -81,10 +87,10 @@ user; `resolveUser` returning `null` or throwing responds `401`.
81
87
  | --- | --- | --- |
82
88
  | `POST` | `/chat` | `client.chat.stream(agentId, {messages, threadId, resume, contextOverride})`, streamed out as SSE. `agentId`/`messages` required in the body. Response carries an `x-persona-run-id` header — see [Reconnect and resume](#reconnect-and-resume). |
83
89
  | `GET` | `/chat/:runId/resume` | Reattaches to the run started by the matching `POST /chat`. See [Reconnect and resume](#reconnect-and-resume). |
84
- | `GET` | `/threads` | `client.threads.list({page, limit})` |
90
+ | `GET` | `/threads` | `client.threads.list({page, limit})` — each item's `agentId` is populated as `{_id, name, avatar, slug, sandboxEnabled}` |
85
91
  | `POST` | `/threads` | `client.threads.create({agentId})` |
86
92
  | `POST` | `/threads/bulk-delete` | `client.threads.bulkDelete(ids)` |
87
- | `GET` | `/threads/:id` | `client.threads.get(id)` |
93
+ | `GET` | `/threads/:id` | `client.threads.get(id)` — `agentId` populated the same way as `list()`; check `sandboxEnabled` here to decide whether to show a Terminal-style UI for this Thread's Agent |
88
94
  | `PATCH` | `/threads/:id` | `client.threads.update(id, {title?, isArchived?})` |
89
95
  | `DELETE` | `/threads/:id` | `client.threads.delete(id)` → `204` |
90
96
  | `GET` | `/threads/:id/messages` | `client.threads.getMessages(id)` — full history + graph state, the same data `chat.stream()` resumes from; load a past conversation on page reopen. |
@@ -96,8 +102,8 @@ user; `resolveUser` returning `null` or throwing responds `401`.
96
102
  | `POST` | `/files/bulk-delete` | `client.files.bulkDelete(ids)` |
97
103
  | `GET` | `/files/:id` | `client.files.download(id)` — raw bytes, streamed through as `kind: 'binary'` |
98
104
  | `DELETE` | `/files/:id` | `client.files.delete(id)` → `204` |
99
- | `GET` | `/memory` | `client.memory.list()` |
100
- | `GET` | `/memory/file` | `client.memory.getFile({path, scope?, agentId?})` — `path` query param required |
105
+ | `GET` | `/memory` | `client.memory.list()` → `{userFiles, agentMemories, agentWorkspaces}` |
106
+ | `GET` | `/memory/file` | `client.memory.getFile({path, scope?, agentId?})` — `path` query param required. `scope` is `"user"` (default), `"agent"`, or `"workspace"` (an Agent's own `/workspace/` files); `"agent"`/`"workspace"` require `agentId`. |
101
107
  | `PUT` | `/memory/file` | `client.memory.writeFile({path, content, scope?, agentId?})` — creates or overwrites |
102
108
  | `DELETE` | `/memory/file` | `client.memory.deleteFile({path, scope?, agentId?})` → `204` |
103
109
  | `GET` | `/mcps/:id/oauth/owner/authorize` | `client.mcps.oauth.getOwnerAuthorizeUrl(id)` → `{url}` to redirect the Project owner to |
@@ -171,6 +177,8 @@ how to enable them safely.
171
177
  | `GET` | `/workflows/:id/mermaid` | `workflowsWrite` | `client.workflows.getMermaid(id)` → `{mermaid}` |
172
178
  | `GET` | `/workflows/:id/runs` | `workflowsWrite` | `client.workflows.listRuns(id, params)` |
173
179
  | `GET` | `/workflows/runs/:runId` | `workflowsWrite` | `client.workflows.getRun(runId)` |
180
+ | `POST` | `/messages` | `messages` | `client.messages.send({to, body?, channel?, mediaUrl?, contentSid?, contentVariables?})` → `{channel, to, from, messageSids, status, parts}`. `to` is any E.164 number; the channel is inferred from a `whatsapp:` prefix unless given. A body is required unless `contentSid` supplies an approved WhatsApp template. |
181
+ | `POST` | `/messages/agent` | `messages` | `client.messages.sendFromAgent({agentId, to, prompt, channel?, displayName?})` — runs the Agent on the conversation shared by all channels for that number, then delivers its reply. `422` for guarded (`interruptOn`) Agents, same as voice. |
174
182
 
175
183
  A disabled capability's routes are simply absent from the route table — a request to one 404s
176
184
  (or, where an always-on route shares the same path with a different method, e.g. `POST /agents`
@@ -187,9 +195,15 @@ oversight:
187
195
  user `resolveUser` returns.
188
196
  - **Opt-in via `capabilities`**: Project-level configuration — LLM provider credentials, skill
189
197
  authoring, knowledge base and vector store management, security audit logs, an agent-building
190
- co-pilot, workflow canvas builder/publishing, and full Agent/MCP-server CRUD. **Every one of these defaults to `false`.** Upgrading
198
+ co-pilot, workflow canvas builder/publishing, full Agent/MCP-server CRUD, and outbound
199
+ SMS/WhatsApp. **Every one of these defaults to `false`.** Upgrading
191
200
  this package never silently exposes new surface to whoever `resolveUser` accepts.
192
201
 
202
+ `messages` is the one opt-in flag that isn't configuration: it's a per-request action, like
203
+ `voice/sessions`. It's still off by default because a send reaches any number the caller names
204
+ and spends this Project's Twilio balance — whoever `resolveUser` accepts would be able to text
205
+ arbitrary people, which is a materially larger blast radius than an end-user-scoped chat run.
206
+
193
207
  ```ts
194
208
  createRuntime({
195
209
  // ...
@@ -203,6 +217,7 @@ createRuntime({
203
217
  auditLogs: false, // default
204
218
  architect: false, // default
205
219
  workflowsWrite: false, // default — authoring and version snapshots
220
+ messages: false, // default — sends spend the Project's Twilio balance
206
221
  },
207
222
  });
208
223
  ```
@@ -415,7 +430,7 @@ modes (`mode: 'development' | 'production'`, default `'production'` unless
415
430
 
416
431
  ## Not yet implemented
417
432
 
418
- This is v0.5.1. Every SDK resource now has a route (see [Routes](#routes)); what's left is either a
433
+ This is v0.16.0. Every SDK resource now has a route (see [Routes](#routes)); what's left is either a
419
434
  genuine unclosed gap or an intentional package boundary, not an oversight:
420
435
 
421
436
  - **Multi-instance reconnect/resume** — see
package/dist/index.cjs CHANGED
@@ -159,7 +159,7 @@ function evictStaleRuns(runs, now, graceMs = DEFAULT_RUN_GRACE_MS, maxTracked =
159
159
  }
160
160
 
161
161
  // src/version.ts
162
- var RUNTIME_VERSION = "0.10.0";
162
+ var RUNTIME_VERSION = "0.16.0";
163
163
 
164
164
  // src/routes/health.ts
165
165
  var alwaysOnCapabilities = {
@@ -878,6 +878,92 @@ var createVoiceSession = async (request, ctx) => {
878
878
  return json(201, ticket);
879
879
  };
880
880
 
881
+ // src/routes/messages.ts
882
+ var MESSAGE_CHANNELS = /* @__PURE__ */ new Set(["sms", "whatsapp"]);
883
+ function optionalChannel(body) {
884
+ const value = body.channel;
885
+ if (value === void 0) return void 0;
886
+ if (typeof value !== "string" || !MESSAGE_CHANNELS.has(value)) {
887
+ throw new RuntimeHttpError(400, "INVALID_REQUEST", '"channel" must be "sms" or "whatsapp".');
888
+ }
889
+ return value;
890
+ }
891
+ function optionalStringField(body, field) {
892
+ const value = body[field];
893
+ if (value === void 0) return void 0;
894
+ if (typeof value !== "string" || value.length === 0) {
895
+ throw new RuntimeHttpError(400, "INVALID_REQUEST", `"${field}" must be a non-empty string.`);
896
+ }
897
+ return value;
898
+ }
899
+ function optionalMediaUrl(body) {
900
+ const value = body.mediaUrl;
901
+ if (value === void 0) return void 0;
902
+ if (typeof value === "string" && value.length > 0) return value;
903
+ if (Array.isArray(value) && value.every((entry) => typeof entry === "string" && entry.length > 0)) {
904
+ return value;
905
+ }
906
+ throw new RuntimeHttpError(
907
+ 400,
908
+ "INVALID_REQUEST",
909
+ '"mediaUrl" must be a string or an array of strings.'
910
+ );
911
+ }
912
+ function optionalContentVariables(body) {
913
+ const value = body.contentVariables;
914
+ if (value === void 0) return void 0;
915
+ if (typeof value === "string") return value;
916
+ if (typeof value === "object" && value !== null && !Array.isArray(value)) {
917
+ return value;
918
+ }
919
+ throw new RuntimeHttpError(
920
+ 400,
921
+ "INVALID_REQUEST",
922
+ '"contentVariables" must be an object or a JSON string.'
923
+ );
924
+ }
925
+ var sendMessage = async (request, ctx) => {
926
+ const body = requireBodyObject(request.body);
927
+ const to = requireStringField(body, "to");
928
+ const channel = optionalChannel(body);
929
+ const text = optionalStringField(body, "body");
930
+ const mediaUrl = optionalMediaUrl(body);
931
+ const contentSid = optionalStringField(body, "contentSid");
932
+ const contentVariables = optionalContentVariables(body);
933
+ if (text === void 0 && contentSid === void 0) {
934
+ throw new RuntimeHttpError(
935
+ 400,
936
+ "INVALID_REQUEST",
937
+ '"body" is required unless "contentSid" is provided.'
938
+ );
939
+ }
940
+ const delivery = await ctx.client.messages.send({
941
+ to,
942
+ ...channel ? { channel } : {},
943
+ ...text ? { body: text } : {},
944
+ ...mediaUrl ? { mediaUrl } : {},
945
+ ...contentSid ? { contentSid } : {},
946
+ ...contentVariables ? { contentVariables } : {}
947
+ });
948
+ return json(200, delivery);
949
+ };
950
+ var sendAgentMessage = async (request, ctx) => {
951
+ const body = requireBodyObject(request.body);
952
+ const agentId = requireStringField(body, "agentId");
953
+ const to = requireStringField(body, "to");
954
+ const prompt = requireStringField(body, "prompt");
955
+ const channel = optionalChannel(body);
956
+ const displayName = optionalStringField(body, "displayName");
957
+ const delivery = await ctx.client.messages.sendFromAgent({
958
+ agentId,
959
+ to,
960
+ prompt,
961
+ ...channel ? { channel } : {},
962
+ ...displayName ? { displayName } : {}
963
+ });
964
+ return json(200, delivery);
965
+ };
966
+
881
967
  // src/routes/agents.ts
882
968
  var listAgents = async (request, ctx) => {
883
969
  const items = await ctx.client.agents.list({
@@ -1607,7 +1693,8 @@ function resolveCapabilities(capabilities) {
1607
1693
  rcpSources: capabilities?.rcpSources ?? false,
1608
1694
  auditLogs: capabilities?.auditLogs ?? false,
1609
1695
  architect: capabilities?.architect ?? false,
1610
- workflowsWrite: capabilities?.workflowsWrite ?? false
1696
+ workflowsWrite: capabilities?.workflowsWrite ?? false,
1697
+ messages: capabilities?.messages ?? false
1611
1698
  };
1612
1699
  }
1613
1700
  function buildRoutes(capabilities) {
@@ -1698,6 +1785,12 @@ function buildRoutes(capabilities) {
1698
1785
  handler: cancelWorkflowRun
1699
1786
  }
1700
1787
  ];
1788
+ if (capabilities.messages) {
1789
+ routes.push(
1790
+ { method: "POST", pattern: ["messages"], handler: sendMessage },
1791
+ { method: "POST", pattern: ["messages", "agent"], handler: sendAgentMessage }
1792
+ );
1793
+ }
1701
1794
  if (capabilities.agentsWrite) {
1702
1795
  routes.push(
1703
1796
  { method: "POST", pattern: ["agents"], handler: createAgent },