@ai-matrx/agents 0.22.0 → 0.23.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/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.23.0
4
+
5
+ - **Resume never runs a live run twice — every client rejoins it** (`./matrx`). New `resumeOrRejoin(transport, resumeCall, { organizationId, onEnvelope, … })`: runs the feature's resume door and pipes its NDJSON into `onEnvelope`; on `409 run_in_progress` it follows `POST /runtime/operations/{live_request_id}/rejoin` into the SAME handler under the run's own organization; on `409 live_stream_unavailable` it follows the durable lifecycle (`followRuntimeOperationToEnd`) and says so (`kind: "followed"`); `409 resume_conflict` comes back as `kind: "resume_conflict"` (retry is host policy). Building blocks exported: `readLiveRunRequestId` (reads `live_request_id`, NEVER the envelope's own `request_id`), `readMatrxErrorCode`, `isResumeConflict`, `isLiveStreamUnavailable`, `runtimeOperationRejoinPath`, `openMatrxStream` (any NDJSON door by path), `withRunOrganization`. Guard: `matrx/rejoin.test.ts` (real envelopes with the `request_id` decoy; proven red on a mutant reading `request_id` and on one dropping the run org).
6
+ - **`streamErrorText(event)`** — the text for a stream `error` event: `user_message` first (e.g. "OpenAI refused this request: the platform's OpenAI account is out of credit."), then `message`, else null. A host fallback must never replace a server message. `runAgentToCompletion` now uses it.
7
+ - **`reportProviderSessionFailure(transport, failure)`** — browser-held provider sessions (OpenAI/xAI realtime voice, Cartesia TTS) report their failures to `POST /broker/provider-failures` and get `{ error_type, retryable, user_message }` back; the body is bounded exactly as the server model bounds it; never throws (null + warn when the report itself fails). Guard: `matrx/provider-failures.test.ts`.
8
+ - Consumer action: replace hand-written `409 run_in_progress` → rejoin code with `resumeOrRejoin`; render stream `error` events with `streamErrorText`; call `reportProviderSessionFailure` from every realtime/Cartesia session error handler and show its `user_message`.
9
+
3
10
  ## 0.22.0
4
11
 
5
12
  - **A signed-out visitor never reads the agent catalogue** (live 2026-10-01: every guest page that mounted a picker — `/education/progress`, `/demos/spatial` — fired `agx_get_list_full`, which `anon` cannot execute, and captured `permission denied for function agx_get_list_full` plus `identity.requireUserId() threw`). `ensureLoaded` now answers the empty catalogue with no read when there is no user, and does not stamp freshness, so the first load after sign-in reads for real. The pickers (web + native) show `labels.emptySignedOut` ("Sign in to see your agents") instead of "No agents found". Guard: `catalog/__tests__/store.test.ts` › "signed out never reads the catalogue".
@@ -1019,39 +1019,6 @@ async function requestStream(transport, path, options) {
1019
1019
  return response;
1020
1020
  }
1021
1021
 
1022
- // matrx/run.ts
1023
- async function streamCall(transport, path, body, options) {
1024
- const response = await requestStream(transport, path, {
1025
- method: "POST",
1026
- // This client IS the streaming path — `stream: true` always, last so a
1027
- // caller-supplied value can never flip the response off NDJSON.
1028
- body: { ...body, stream: true },
1029
- ...options.signal ? { signal: options.signal } : {}
1030
- });
1031
- return toRunHandle(response, options);
1032
- }
1033
- function startMandateRun(transport, mandateKey, request, options = {}) {
1034
- return streamCall(
1035
- transport,
1036
- `/ai/mandates/${encodePathSegment(mandateKey)}`,
1037
- request,
1038
- options
1039
- );
1040
- }
1041
- function cancelAgentRun(transport, requestId, options = {}) {
1042
- const query = buildQuery(
1043
- options.mode === "interrupt" ? { mode: "interrupt" } : {}
1044
- );
1045
- return requestJson(
1046
- transport,
1047
- `/ai/cancel/${encodePathSegment(requestId)}${query}`,
1048
- {
1049
- method: "POST",
1050
- ...options.signal ? { signal: options.signal } : {}
1051
- }
1052
- );
1053
- }
1054
-
1055
1022
  // stream/sse.ts
1056
1023
  var FRAME_SEPARATOR = /\r\n\r\n|\n\n|\r\r/;
1057
1024
  var LINE_SEPARATOR = /\r\n|\n|\r/;
@@ -1250,6 +1217,39 @@ async function rejoinRuntimeOperation(transport, requestId, options = {}) {
1250
1217
  return toRunHandle(response, options);
1251
1218
  }
1252
1219
 
1220
+ // matrx/run.ts
1221
+ async function streamCall(transport, path, body, options) {
1222
+ const response = await requestStream(transport, path, {
1223
+ method: "POST",
1224
+ // This client IS the streaming path — `stream: true` always, last so a
1225
+ // caller-supplied value can never flip the response off NDJSON.
1226
+ body: { ...body, stream: true },
1227
+ ...options.signal ? { signal: options.signal } : {}
1228
+ });
1229
+ return toRunHandle(response, options);
1230
+ }
1231
+ function startMandateRun(transport, mandateKey, request, options = {}) {
1232
+ return streamCall(
1233
+ transport,
1234
+ `/ai/mandates/${encodePathSegment(mandateKey)}`,
1235
+ request,
1236
+ options
1237
+ );
1238
+ }
1239
+ function cancelAgentRun(transport, requestId, options = {}) {
1240
+ const query = buildQuery(
1241
+ options.mode === "interrupt" ? { mode: "interrupt" } : {}
1242
+ );
1243
+ return requestJson(
1244
+ transport,
1245
+ `/ai/cancel/${encodePathSegment(requestId)}${query}`,
1246
+ {
1247
+ method: "POST",
1248
+ ...options.signal ? { signal: options.signal } : {}
1249
+ }
1250
+ );
1251
+ }
1252
+
1253
1253
  // content-transfer/destination-ports.ts
1254
1254
  var success = (message, target) => ({ status: "success", delivered: "action", mimeTypes: [], message, ...target ? { target } : {} });
1255
1255
  var active = (bindings, content, expected) => {