@personaai/runtime 0.15.1 → 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,12 +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.15.0.** `/memory` and `/memory/file` now accept/return `scope: "workspace"` — an Agent's own
9
- `/workspace/` files (the same persistent store its `write_file`/`read_file` tool calls use), scoped
10
- by Agent + Subject rather than by Thread. Also includes full Workflows engine support
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
11
13
  (`POST /workflows/:id/stream`, `GET /workflows`, `POST /workflows/runs/:runId/cancel`,
12
- `GET /workflows/runs/:runId/resume`), `workflowsWrite` gated authoring capability, and
13
- `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
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
14
15
  [Quickstart](#quickstart) for how to run it directly against raw Node `http`, and
15
16
  [Not yet implemented](#not-yet-implemented) for what's missing before it's a complete Level 2
16
17
  runtime.
@@ -176,6 +177,8 @@ how to enable them safely.
176
177
  | `GET` | `/workflows/:id/mermaid` | `workflowsWrite` | `client.workflows.getMermaid(id)` → `{mermaid}` |
177
178
  | `GET` | `/workflows/:id/runs` | `workflowsWrite` | `client.workflows.listRuns(id, params)` |
178
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. |
179
182
 
180
183
  A disabled capability's routes are simply absent from the route table — a request to one 404s
181
184
  (or, where an always-on route shares the same path with a different method, e.g. `POST /agents`
@@ -192,9 +195,15 @@ oversight:
192
195
  user `resolveUser` returns.
193
196
  - **Opt-in via `capabilities`**: Project-level configuration — LLM provider credentials, skill
194
197
  authoring, knowledge base and vector store management, security audit logs, an agent-building
195
- 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
196
200
  this package never silently exposes new surface to whoever `resolveUser` accepts.
197
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
+
198
207
  ```ts
199
208
  createRuntime({
200
209
  // ...
@@ -208,6 +217,7 @@ createRuntime({
208
217
  auditLogs: false, // default
209
218
  architect: false, // default
210
219
  workflowsWrite: false, // default — authoring and version snapshots
220
+ messages: false, // default — sends spend the Project's Twilio balance
211
221
  },
212
222
  });
213
223
  ```
@@ -420,7 +430,7 @@ modes (`mode: 'development' | 'production'`, default `'production'` unless
420
430
 
421
431
  ## Not yet implemented
422
432
 
423
- This is v0.15.0. 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
424
434
  genuine unclosed gap or an intentional package boundary, not an oversight:
425
435
 
426
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.15.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 },