@ai-matrx/agents 0.23.0 → 0.24.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -310,22 +310,27 @@ declare function assertQueryOrganizationMatchesContext(queryParams: Record<strin
310
310
  /**
311
311
  * Resume-or-rejoin — the ONE client behavior for "pick a run back up".
312
312
  *
313
- * Server truth (verified against aidream source):
314
- * - A resume door asked to resume a run that is STILL RUNNING answers
315
- * `409` with `code: "run_in_progress"` and `live_request_id`
316
- * (`aidream/api/routers/podcast_generator.py` `resume_podcast_endpoint`;
317
- * other doors adopt the same contract). The platform error envelope
318
- * (`aidream/api/errors.py`) hoists the detail's non-reserved keys to the top
319
- * level and RESERVES `request_id` for the API call's own id — so the live
320
- * run's id is read from `live_request_id`, never `request_id` (reading
321
- * `request_id` rejoined a request that did not exist: 404, 2026-10-01).
322
- * - The client then follows `POST /runtime/operations/{live_request_id}/rejoin`
323
- * (NDJSON replay-then-follow). That answers `409 live_stream_unavailable`
324
- * when the live journal is gone — the durable lifecycle stream
325
- * (`followRuntimeOperationToEnd`) is the fallback.
326
- * - Conversations answer `409 resume_conflict` (retryable: the suspending run
327
- * has not persisted yet, or a duplicate lost the claim). Retrying is host
328
- * policy — this module reports it as an outcome, never retries itself.
313
+ * Server truth (aidream `services/runtime/FEATURE.md` § "A live run is
314
+ * REJOINED, never resumed beside itself"; `reconnect.run_in_progress_error`):
315
+ * - Every resume / retry / continue door asked to resume a run that is STILL
316
+ * LIVE answers `409` with, at the ROOT of the error body,
317
+ * `{error, code: "run_in_progress", live_request_id, rejoin_path, message,
318
+ * …extras}` (extras: `run_id` for workflow / SEO / comparison, `arm_index` /
319
+ * `arm`, `analysis_id`). The client follows `rejoin_path` — never a URL it
320
+ * builds — because a door outside the spine (SEO collections) answers
321
+ * `live_request_id: null` with its own `/seo/collections/{run_id}/rejoin`.
322
+ * - Chat/conversation resume keeps `code: "resume_conflict"` for older clients
323
+ * but ALSO carries `live_request_id` + `rejoin_path` (+
324
+ * `details.live_request_id`) when the run is live — then rejoin, never the
325
+ * retry loop. A bare `resume_conflict` (no rejoin target) stays host retry.
326
+ * - The envelope RESERVES `request_id` for the refusing call's own id — the
327
+ * live run is read from `live_request_id`, never `request_id` (reading it
328
+ * rejoined a request that did not exist: 404, 2026-10-01).
329
+ * - A rejoin answers `409 live_stream_unavailable` when there is no journal to
330
+ * replay (resumed workflow legs, comparison arms). Then: a body naming a
331
+ * workflow `run_id` → follow `GET /runs/{run_id}/events/stream` (the workflow
332
+ * SSE, which also carries the node_stream media frames); otherwise follow the
333
+ * durable lifecycle (`followRuntimeOperationToEnd`).
329
334
  *
330
335
  * Resume and rejoin are work on an EXISTING run, so they carry THAT run's
331
336
  * organization (`organizationId`), never whatever the session has selected.
@@ -347,14 +352,40 @@ interface MatrxHttpErrorLike {
347
352
  * `error`) from `detail`, `details`, or the top level. Null when absent.
348
353
  */
349
354
  declare function readMatrxErrorCode(error: unknown): string | null;
355
+ /** Where to rejoin a live run, read from a resume door's 409. */
356
+ interface MatrxLiveRunRejoin {
357
+ /** The live request (`GET /runtime/operations/{id}`); null for doors outside the spine (SEO). */
358
+ liveRequestId: string | null;
359
+ /** Server-relative path to `POST` for the NDJSON replay-then-follow. Always from the body when present. */
360
+ rejoinPath: string;
361
+ /** `run_id` extra (workflow / SEO / comparison), when present. */
362
+ runId: string | null;
363
+ /** The refusal's code: `run_in_progress`, or chat's `resume_conflict` carrying a rejoin target. */
364
+ code: string;
365
+ /** The refusal body (root of the envelope), for door-specific extras (`arm_index`, `analysis_id`, …). */
366
+ body: Record<string, unknown>;
367
+ }
368
+ /**
369
+ * The rejoin target of a live-run refusal: `409` with `code: run_in_progress`,
370
+ * or chat's `409 resume_conflict` that names a live run. Null for every other
371
+ * error (a genuine failure is never mistaken for a live run). Reads
372
+ * `rejoin_path` from the body first; derives the runtime rejoin from
373
+ * `live_request_id` only when the body names no path. Never reads
374
+ * `request_id`.
375
+ */
376
+ declare function readLiveRunRejoin(error: unknown): MatrxLiveRunRejoin | null;
350
377
  /**
351
- * The request id of a run that is STILL RUNNING, read from a resume door's
352
- * `409 run_in_progress` refusal. Null for every other error, so a genuine
353
- * failure is never mistaken for a live run. Reads `live_request_id` only —
354
- * the envelope's `request_id` is the refusing API call's own id.
378
+ * The request id of a run that is STILL LIVE, read from a resume door's
379
+ * refusal (`live_request_id` — never the envelope's own `request_id`). Null
380
+ * for every other error, and for a live refusal outside the spine (SEO) —
381
+ * follow `readLiveRunRejoin(error).rejoinPath` instead.
355
382
  */
356
383
  declare function readLiveRunRequestId(error: unknown): string | null;
357
- /** `409 resume_conflict` — another resume holds the run claim; retry is host policy. */
384
+ /**
385
+ * A bare `409 resume_conflict` — another resume holds the claim and NO live
386
+ * run is named; retry is host policy. (A `resume_conflict` that names a live
387
+ * run is a rejoin — `readLiveRunRejoin`.)
388
+ */
358
389
  declare function isResumeConflict(error: unknown): boolean;
359
390
  /** `409 live_stream_unavailable` — the rejoin journal is gone; follow the durable lifecycle instead. */
360
391
  declare function isLiveStreamUnavailable(error: unknown): boolean;
@@ -388,30 +419,52 @@ declare function openMatrxStream(transport: MatrxTransport, path: string, option
388
419
  * resume/rejoin transport.
389
420
  */
390
421
  declare function withRunOrganization(transport: MatrxTransport, organizationId: string): MatrxTransport;
422
+ /**
423
+ * Follow a workflow run's SSE (`GET /runs/{run_id}/events/stream`) — the
424
+ * rejoin fallback for workflow legs with no stream journal. Delivers every
425
+ * parsed event (durable run events with their seq; ephemeral `node_stream`
426
+ * frames — including `kind: "media"` — with seq null). One connection, no
427
+ * reconnect policy: resolves `{ended: true}` on the server's `end` frame,
428
+ * `{ended: false}` when the wire closes or the caller aborts.
429
+ */
430
+ declare function followWorkflowRunEvents(transport: MatrxTransport, runId: string, options: {
431
+ onEvent: (event: Record<string, unknown>, seq: number | null) => void;
432
+ lastEventId?: number | null;
433
+ signal?: AbortSignal;
434
+ }): Promise<{
435
+ ended: boolean;
436
+ }>;
391
437
  type ResumeOrRejoinOutcome =
392
438
  /** The resume door ran the run; its stream was delivered to `onEnvelope`. */
393
439
  {
394
440
  kind: "resumed";
395
441
  requestId: string | null;
396
442
  }
397
- /** The run was still live; its original stream was replayed + followed into `onEnvelope`. */
443
+ /** The run was live; its stream (from `rejoin_path`) was replayed + followed into `onEnvelope`. */
398
444
  | {
399
445
  kind: "rejoined";
400
- liveRequestId: string;
446
+ rejoin: MatrxLiveRunRejoin;
401
447
  }
402
448
  /**
403
- * The run was still live but its stream could not be replayed; the durable
404
- * lifecycle was followed instead. Re-query the feature's record for the
405
- * result. `ended: false` = the follow gave up (or no operation was found).
449
+ * The run was live but had no journal to replay; the durable lifecycle was
450
+ * followed instead. Re-query the feature's record for the result.
451
+ * `ended: false` = the follow gave up (or no operation was found).
406
452
  */
407
453
  | {
408
454
  kind: "followed";
409
- liveRequestId: string;
455
+ rejoin: MatrxLiveRunRejoin;
410
456
  executionId: string | null;
411
457
  ended: boolean;
412
458
  status: MatrxRuntimeExecutionStatus | null;
413
459
  }
414
- /** `409 resume_conflict` — retry with the host's backoff policy. */
460
+ /** No journal, and the run is a workflow: its run SSE was followed into `onWorkflowEvent`. */
461
+ | {
462
+ kind: "followed_workflow";
463
+ rejoin: MatrxLiveRunRejoin;
464
+ runId: string;
465
+ ended: boolean;
466
+ }
467
+ /** A bare `409 resume_conflict` (no live run named) — retry with the host's backoff policy. */
415
468
  | {
416
469
  kind: "resume_conflict";
417
470
  error: MatrxApiError;
@@ -422,22 +475,26 @@ interface ResumeOrRejoinOptions extends Omit<MatrxStreamCallOptions, "signal"> {
422
475
  /** THE RUN'S organization (from its durable record) — bound on every call. */
423
476
  organizationId?: string | null;
424
477
  signal?: AbortSignal;
425
- /** Fired the moment a live run is detected, before the rejoin opens. */
426
- onRejoin?: (liveRequestId: string) => void;
427
- /** Durable lifecycle events while following (the no-replay fallback). */
478
+ /** Fired the moment a live run is detected, before the rejoin opens (reset replay-sensitive state). */
479
+ onRejoin?: (rejoin: MatrxLiveRunRejoin) => void;
480
+ /** Durable lifecycle events while following (the no-journal fallback). */
428
481
  onOperationEvent?: (event: MatrxRuntimeOperationEvent) => void;
429
- /** Tuning for the fallback follow (stall / reconnect policy). */
482
+ /** Workflow run SSE events (the no-journal fallback for a workflow `run_id`). */
483
+ onWorkflowEvent?: (event: Record<string, unknown>, seq: number | null) => void;
484
+ /** Tuning for the durable follow (stall / reconnect policy). */
430
485
  follow?: Omit<FollowRuntimeOperationToEndOptions, "onEvent" | "signal" | "lastEventSeq">;
431
486
  }
432
487
  /**
433
- * Resume a run — or, when the server says it is still running, REJOIN it.
488
+ * Resume a run — or, when the server says it is still live, REJOIN it.
434
489
  *
435
490
  * Calls `resumeCall` (the feature's resume door) and pipes its stream into
436
- * `onEnvelope`. On `409 run_in_progress` it follows
437
- * `POST /runtime/operations/{live_request_id}/rejoin` into the SAME handler
438
- * under the same organization; when replay is unavailable it follows the
439
- * durable lifecycle to its end. Never runs a live run a second time.
440
- * `resume_conflict` comes back as an outcome; any other error is thrown.
491
+ * `onEnvelope`. On a live-run refusal (`run_in_progress`, or chat's
492
+ * `resume_conflict` naming a live run) it follows the body's `rejoin_path`
493
+ * into the SAME handler under the same organization; when the rejoin has no
494
+ * journal (`live_stream_unavailable`) it follows the workflow run SSE (body
495
+ * names a workflow `run_id`) or the durable lifecycle. Never runs a live run a
496
+ * second time. A bare `resume_conflict` comes back as an outcome; any other
497
+ * error is thrown.
441
498
  */
442
499
  declare function resumeOrRejoin(transport: MatrxTransport, resumeCall: (transport: MatrxTransport, options: MatrxStreamCallOptions) => Promise<MatrxRunHandle>, options: ResumeOrRejoinOptions): Promise<ResumeOrRejoinOutcome>;
443
500
 
@@ -580,4 +637,4 @@ declare function listUserPendingToolCalls(transport: MatrxTransport, options?: {
580
637
  signal?: AbortSignal;
581
638
  }): Promise<MatrxPendingCallSummary[]>;
582
639
 
583
- export { type CreateMatrxTransportOptions, FollowRuntimeOperationToEndOptions, MATRX_AI_API_VERSION_DEFAULT, MATRX_LIVE_STREAM_UNAVAILABLE, MATRX_RESUME_CONFLICT, MATRX_RUN_IN_PROGRESS, type MatrxAiApiVersion, MatrxApiError, type MatrxCallError, type MatrxClientSessionProvider, type MatrxClientToolResult, type MatrxHttpErrorLike, MatrxJsonObject, MatrxJsonValue, type MatrxPendingCallSummary, type MatrxProtocolDowngrade, type MatrxProtocolFallbackOptions, type MatrxRequestInfo, MatrxRunHandle, MatrxRuntimeExecutionStatus, MatrxRuntimeOperationEvent, MatrxStreamCallOptions, type MatrxToolResultsResponse, MatrxTransport, type MatrxTransportDiagnostics, type MatrxTransportTarget, OrganizationContextError, type OrganizationContextErrorCode, type OrganizationOperation, type ProviderSessionFailure, type ProviderSessionFailureVerdict, type ResumeOrRejoinOptions, type ResumeOrRejoinOutcome, V2_COVERED_AI_PATH_TEMPLATES, applyAiApiVersion, applyOrganizationContextHeader, assertOrganizationMatchesOperation, assertQueryOrganizationMatchesContext, createMatrxTransport, createOrganizationOperation, fetchWithMatrxProtocolFallback, isCoveredAiPath, isLiveStreamUnavailable, isResumeConflict, isV2Path, listConversationPendingToolCalls, listUserPendingToolCalls, normalizeMatrxError, openMatrxStream, providerSessionFailureBody, readLiveRunRequestId, readMatrxErrorCode, reportProviderSessionFailure, requireOrganizationContext, resumeOrRejoin, runtimeOperationRejoinPath, streamErrorText, submitAgentToolResults, toV1FallbackUrl, toV2Path, withRunOrganization };
640
+ export { type CreateMatrxTransportOptions, FollowRuntimeOperationToEndOptions, MATRX_AI_API_VERSION_DEFAULT, MATRX_LIVE_STREAM_UNAVAILABLE, MATRX_RESUME_CONFLICT, MATRX_RUN_IN_PROGRESS, type MatrxAiApiVersion, MatrxApiError, type MatrxCallError, type MatrxClientSessionProvider, type MatrxClientToolResult, type MatrxHttpErrorLike, MatrxJsonObject, MatrxJsonValue, type MatrxLiveRunRejoin, type MatrxPendingCallSummary, type MatrxProtocolDowngrade, type MatrxProtocolFallbackOptions, type MatrxRequestInfo, MatrxRunHandle, MatrxRuntimeExecutionStatus, MatrxRuntimeOperationEvent, MatrxStreamCallOptions, type MatrxToolResultsResponse, MatrxTransport, type MatrxTransportDiagnostics, type MatrxTransportTarget, OrganizationContextError, type OrganizationContextErrorCode, type OrganizationOperation, type ProviderSessionFailure, type ProviderSessionFailureVerdict, type ResumeOrRejoinOptions, type ResumeOrRejoinOutcome, V2_COVERED_AI_PATH_TEMPLATES, applyAiApiVersion, applyOrganizationContextHeader, assertOrganizationMatchesOperation, assertQueryOrganizationMatchesContext, createMatrxTransport, createOrganizationOperation, fetchWithMatrxProtocolFallback, followWorkflowRunEvents, isCoveredAiPath, isLiveStreamUnavailable, isResumeConflict, isV2Path, listConversationPendingToolCalls, listUserPendingToolCalls, normalizeMatrxError, openMatrxStream, providerSessionFailureBody, readLiveRunRejoin, readLiveRunRequestId, readMatrxErrorCode, reportProviderSessionFailure, requireOrganizationContext, resumeOrRejoin, runtimeOperationRejoinPath, streamErrorText, submitAgentToolResults, toV1FallbackUrl, toV2Path, withRunOrganization };
@@ -310,22 +310,27 @@ declare function assertQueryOrganizationMatchesContext(queryParams: Record<strin
310
310
  /**
311
311
  * Resume-or-rejoin — the ONE client behavior for "pick a run back up".
312
312
  *
313
- * Server truth (verified against aidream source):
314
- * - A resume door asked to resume a run that is STILL RUNNING answers
315
- * `409` with `code: "run_in_progress"` and `live_request_id`
316
- * (`aidream/api/routers/podcast_generator.py` `resume_podcast_endpoint`;
317
- * other doors adopt the same contract). The platform error envelope
318
- * (`aidream/api/errors.py`) hoists the detail's non-reserved keys to the top
319
- * level and RESERVES `request_id` for the API call's own id — so the live
320
- * run's id is read from `live_request_id`, never `request_id` (reading
321
- * `request_id` rejoined a request that did not exist: 404, 2026-10-01).
322
- * - The client then follows `POST /runtime/operations/{live_request_id}/rejoin`
323
- * (NDJSON replay-then-follow). That answers `409 live_stream_unavailable`
324
- * when the live journal is gone — the durable lifecycle stream
325
- * (`followRuntimeOperationToEnd`) is the fallback.
326
- * - Conversations answer `409 resume_conflict` (retryable: the suspending run
327
- * has not persisted yet, or a duplicate lost the claim). Retrying is host
328
- * policy — this module reports it as an outcome, never retries itself.
313
+ * Server truth (aidream `services/runtime/FEATURE.md` § "A live run is
314
+ * REJOINED, never resumed beside itself"; `reconnect.run_in_progress_error`):
315
+ * - Every resume / retry / continue door asked to resume a run that is STILL
316
+ * LIVE answers `409` with, at the ROOT of the error body,
317
+ * `{error, code: "run_in_progress", live_request_id, rejoin_path, message,
318
+ * …extras}` (extras: `run_id` for workflow / SEO / comparison, `arm_index` /
319
+ * `arm`, `analysis_id`). The client follows `rejoin_path` — never a URL it
320
+ * builds — because a door outside the spine (SEO collections) answers
321
+ * `live_request_id: null` with its own `/seo/collections/{run_id}/rejoin`.
322
+ * - Chat/conversation resume keeps `code: "resume_conflict"` for older clients
323
+ * but ALSO carries `live_request_id` + `rejoin_path` (+
324
+ * `details.live_request_id`) when the run is live — then rejoin, never the
325
+ * retry loop. A bare `resume_conflict` (no rejoin target) stays host retry.
326
+ * - The envelope RESERVES `request_id` for the refusing call's own id — the
327
+ * live run is read from `live_request_id`, never `request_id` (reading it
328
+ * rejoined a request that did not exist: 404, 2026-10-01).
329
+ * - A rejoin answers `409 live_stream_unavailable` when there is no journal to
330
+ * replay (resumed workflow legs, comparison arms). Then: a body naming a
331
+ * workflow `run_id` → follow `GET /runs/{run_id}/events/stream` (the workflow
332
+ * SSE, which also carries the node_stream media frames); otherwise follow the
333
+ * durable lifecycle (`followRuntimeOperationToEnd`).
329
334
  *
330
335
  * Resume and rejoin are work on an EXISTING run, so they carry THAT run's
331
336
  * organization (`organizationId`), never whatever the session has selected.
@@ -347,14 +352,40 @@ interface MatrxHttpErrorLike {
347
352
  * `error`) from `detail`, `details`, or the top level. Null when absent.
348
353
  */
349
354
  declare function readMatrxErrorCode(error: unknown): string | null;
355
+ /** Where to rejoin a live run, read from a resume door's 409. */
356
+ interface MatrxLiveRunRejoin {
357
+ /** The live request (`GET /runtime/operations/{id}`); null for doors outside the spine (SEO). */
358
+ liveRequestId: string | null;
359
+ /** Server-relative path to `POST` for the NDJSON replay-then-follow. Always from the body when present. */
360
+ rejoinPath: string;
361
+ /** `run_id` extra (workflow / SEO / comparison), when present. */
362
+ runId: string | null;
363
+ /** The refusal's code: `run_in_progress`, or chat's `resume_conflict` carrying a rejoin target. */
364
+ code: string;
365
+ /** The refusal body (root of the envelope), for door-specific extras (`arm_index`, `analysis_id`, …). */
366
+ body: Record<string, unknown>;
367
+ }
368
+ /**
369
+ * The rejoin target of a live-run refusal: `409` with `code: run_in_progress`,
370
+ * or chat's `409 resume_conflict` that names a live run. Null for every other
371
+ * error (a genuine failure is never mistaken for a live run). Reads
372
+ * `rejoin_path` from the body first; derives the runtime rejoin from
373
+ * `live_request_id` only when the body names no path. Never reads
374
+ * `request_id`.
375
+ */
376
+ declare function readLiveRunRejoin(error: unknown): MatrxLiveRunRejoin | null;
350
377
  /**
351
- * The request id of a run that is STILL RUNNING, read from a resume door's
352
- * `409 run_in_progress` refusal. Null for every other error, so a genuine
353
- * failure is never mistaken for a live run. Reads `live_request_id` only —
354
- * the envelope's `request_id` is the refusing API call's own id.
378
+ * The request id of a run that is STILL LIVE, read from a resume door's
379
+ * refusal (`live_request_id` — never the envelope's own `request_id`). Null
380
+ * for every other error, and for a live refusal outside the spine (SEO) —
381
+ * follow `readLiveRunRejoin(error).rejoinPath` instead.
355
382
  */
356
383
  declare function readLiveRunRequestId(error: unknown): string | null;
357
- /** `409 resume_conflict` — another resume holds the run claim; retry is host policy. */
384
+ /**
385
+ * A bare `409 resume_conflict` — another resume holds the claim and NO live
386
+ * run is named; retry is host policy. (A `resume_conflict` that names a live
387
+ * run is a rejoin — `readLiveRunRejoin`.)
388
+ */
358
389
  declare function isResumeConflict(error: unknown): boolean;
359
390
  /** `409 live_stream_unavailable` — the rejoin journal is gone; follow the durable lifecycle instead. */
360
391
  declare function isLiveStreamUnavailable(error: unknown): boolean;
@@ -388,30 +419,52 @@ declare function openMatrxStream(transport: MatrxTransport, path: string, option
388
419
  * resume/rejoin transport.
389
420
  */
390
421
  declare function withRunOrganization(transport: MatrxTransport, organizationId: string): MatrxTransport;
422
+ /**
423
+ * Follow a workflow run's SSE (`GET /runs/{run_id}/events/stream`) — the
424
+ * rejoin fallback for workflow legs with no stream journal. Delivers every
425
+ * parsed event (durable run events with their seq; ephemeral `node_stream`
426
+ * frames — including `kind: "media"` — with seq null). One connection, no
427
+ * reconnect policy: resolves `{ended: true}` on the server's `end` frame,
428
+ * `{ended: false}` when the wire closes or the caller aborts.
429
+ */
430
+ declare function followWorkflowRunEvents(transport: MatrxTransport, runId: string, options: {
431
+ onEvent: (event: Record<string, unknown>, seq: number | null) => void;
432
+ lastEventId?: number | null;
433
+ signal?: AbortSignal;
434
+ }): Promise<{
435
+ ended: boolean;
436
+ }>;
391
437
  type ResumeOrRejoinOutcome =
392
438
  /** The resume door ran the run; its stream was delivered to `onEnvelope`. */
393
439
  {
394
440
  kind: "resumed";
395
441
  requestId: string | null;
396
442
  }
397
- /** The run was still live; its original stream was replayed + followed into `onEnvelope`. */
443
+ /** The run was live; its stream (from `rejoin_path`) was replayed + followed into `onEnvelope`. */
398
444
  | {
399
445
  kind: "rejoined";
400
- liveRequestId: string;
446
+ rejoin: MatrxLiveRunRejoin;
401
447
  }
402
448
  /**
403
- * The run was still live but its stream could not be replayed; the durable
404
- * lifecycle was followed instead. Re-query the feature's record for the
405
- * result. `ended: false` = the follow gave up (or no operation was found).
449
+ * The run was live but had no journal to replay; the durable lifecycle was
450
+ * followed instead. Re-query the feature's record for the result.
451
+ * `ended: false` = the follow gave up (or no operation was found).
406
452
  */
407
453
  | {
408
454
  kind: "followed";
409
- liveRequestId: string;
455
+ rejoin: MatrxLiveRunRejoin;
410
456
  executionId: string | null;
411
457
  ended: boolean;
412
458
  status: MatrxRuntimeExecutionStatus | null;
413
459
  }
414
- /** `409 resume_conflict` — retry with the host's backoff policy. */
460
+ /** No journal, and the run is a workflow: its run SSE was followed into `onWorkflowEvent`. */
461
+ | {
462
+ kind: "followed_workflow";
463
+ rejoin: MatrxLiveRunRejoin;
464
+ runId: string;
465
+ ended: boolean;
466
+ }
467
+ /** A bare `409 resume_conflict` (no live run named) — retry with the host's backoff policy. */
415
468
  | {
416
469
  kind: "resume_conflict";
417
470
  error: MatrxApiError;
@@ -422,22 +475,26 @@ interface ResumeOrRejoinOptions extends Omit<MatrxStreamCallOptions, "signal"> {
422
475
  /** THE RUN'S organization (from its durable record) — bound on every call. */
423
476
  organizationId?: string | null;
424
477
  signal?: AbortSignal;
425
- /** Fired the moment a live run is detected, before the rejoin opens. */
426
- onRejoin?: (liveRequestId: string) => void;
427
- /** Durable lifecycle events while following (the no-replay fallback). */
478
+ /** Fired the moment a live run is detected, before the rejoin opens (reset replay-sensitive state). */
479
+ onRejoin?: (rejoin: MatrxLiveRunRejoin) => void;
480
+ /** Durable lifecycle events while following (the no-journal fallback). */
428
481
  onOperationEvent?: (event: MatrxRuntimeOperationEvent) => void;
429
- /** Tuning for the fallback follow (stall / reconnect policy). */
482
+ /** Workflow run SSE events (the no-journal fallback for a workflow `run_id`). */
483
+ onWorkflowEvent?: (event: Record<string, unknown>, seq: number | null) => void;
484
+ /** Tuning for the durable follow (stall / reconnect policy). */
430
485
  follow?: Omit<FollowRuntimeOperationToEndOptions, "onEvent" | "signal" | "lastEventSeq">;
431
486
  }
432
487
  /**
433
- * Resume a run — or, when the server says it is still running, REJOIN it.
488
+ * Resume a run — or, when the server says it is still live, REJOIN it.
434
489
  *
435
490
  * Calls `resumeCall` (the feature's resume door) and pipes its stream into
436
- * `onEnvelope`. On `409 run_in_progress` it follows
437
- * `POST /runtime/operations/{live_request_id}/rejoin` into the SAME handler
438
- * under the same organization; when replay is unavailable it follows the
439
- * durable lifecycle to its end. Never runs a live run a second time.
440
- * `resume_conflict` comes back as an outcome; any other error is thrown.
491
+ * `onEnvelope`. On a live-run refusal (`run_in_progress`, or chat's
492
+ * `resume_conflict` naming a live run) it follows the body's `rejoin_path`
493
+ * into the SAME handler under the same organization; when the rejoin has no
494
+ * journal (`live_stream_unavailable`) it follows the workflow run SSE (body
495
+ * names a workflow `run_id`) or the durable lifecycle. Never runs a live run a
496
+ * second time. A bare `resume_conflict` comes back as an outcome; any other
497
+ * error is thrown.
441
498
  */
442
499
  declare function resumeOrRejoin(transport: MatrxTransport, resumeCall: (transport: MatrxTransport, options: MatrxStreamCallOptions) => Promise<MatrxRunHandle>, options: ResumeOrRejoinOptions): Promise<ResumeOrRejoinOutcome>;
443
500
 
@@ -580,4 +637,4 @@ declare function listUserPendingToolCalls(transport: MatrxTransport, options?: {
580
637
  signal?: AbortSignal;
581
638
  }): Promise<MatrxPendingCallSummary[]>;
582
639
 
583
- export { type CreateMatrxTransportOptions, FollowRuntimeOperationToEndOptions, MATRX_AI_API_VERSION_DEFAULT, MATRX_LIVE_STREAM_UNAVAILABLE, MATRX_RESUME_CONFLICT, MATRX_RUN_IN_PROGRESS, type MatrxAiApiVersion, MatrxApiError, type MatrxCallError, type MatrxClientSessionProvider, type MatrxClientToolResult, type MatrxHttpErrorLike, MatrxJsonObject, MatrxJsonValue, type MatrxPendingCallSummary, type MatrxProtocolDowngrade, type MatrxProtocolFallbackOptions, type MatrxRequestInfo, MatrxRunHandle, MatrxRuntimeExecutionStatus, MatrxRuntimeOperationEvent, MatrxStreamCallOptions, type MatrxToolResultsResponse, MatrxTransport, type MatrxTransportDiagnostics, type MatrxTransportTarget, OrganizationContextError, type OrganizationContextErrorCode, type OrganizationOperation, type ProviderSessionFailure, type ProviderSessionFailureVerdict, type ResumeOrRejoinOptions, type ResumeOrRejoinOutcome, V2_COVERED_AI_PATH_TEMPLATES, applyAiApiVersion, applyOrganizationContextHeader, assertOrganizationMatchesOperation, assertQueryOrganizationMatchesContext, createMatrxTransport, createOrganizationOperation, fetchWithMatrxProtocolFallback, isCoveredAiPath, isLiveStreamUnavailable, isResumeConflict, isV2Path, listConversationPendingToolCalls, listUserPendingToolCalls, normalizeMatrxError, openMatrxStream, providerSessionFailureBody, readLiveRunRequestId, readMatrxErrorCode, reportProviderSessionFailure, requireOrganizationContext, resumeOrRejoin, runtimeOperationRejoinPath, streamErrorText, submitAgentToolResults, toV1FallbackUrl, toV2Path, withRunOrganization };
640
+ export { type CreateMatrxTransportOptions, FollowRuntimeOperationToEndOptions, MATRX_AI_API_VERSION_DEFAULT, MATRX_LIVE_STREAM_UNAVAILABLE, MATRX_RESUME_CONFLICT, MATRX_RUN_IN_PROGRESS, type MatrxAiApiVersion, MatrxApiError, type MatrxCallError, type MatrxClientSessionProvider, type MatrxClientToolResult, type MatrxHttpErrorLike, MatrxJsonObject, MatrxJsonValue, type MatrxLiveRunRejoin, type MatrxPendingCallSummary, type MatrxProtocolDowngrade, type MatrxProtocolFallbackOptions, type MatrxRequestInfo, MatrxRunHandle, MatrxRuntimeExecutionStatus, MatrxRuntimeOperationEvent, MatrxStreamCallOptions, type MatrxToolResultsResponse, MatrxTransport, type MatrxTransportDiagnostics, type MatrxTransportTarget, OrganizationContextError, type OrganizationContextErrorCode, type OrganizationOperation, type ProviderSessionFailure, type ProviderSessionFailureVerdict, type ResumeOrRejoinOptions, type ResumeOrRejoinOutcome, V2_COVERED_AI_PATH_TEMPLATES, applyAiApiVersion, applyOrganizationContextHeader, assertOrganizationMatchesOperation, assertQueryOrganizationMatchesContext, createMatrxTransport, createOrganizationOperation, fetchWithMatrxProtocolFallback, followWorkflowRunEvents, isCoveredAiPath, isLiveStreamUnavailable, isResumeConflict, isV2Path, listConversationPendingToolCalls, listUserPendingToolCalls, normalizeMatrxError, openMatrxStream, providerSessionFailureBody, readLiveRunRejoin, readLiveRunRequestId, readMatrxErrorCode, reportProviderSessionFailure, requireOrganizationContext, resumeOrRejoin, runtimeOperationRejoinPath, streamErrorText, submitAgentToolResults, toV1FallbackUrl, toV2Path, withRunOrganization };
@@ -965,17 +965,37 @@ function readMatrxErrorCode(error) {
965
965
  }
966
966
  return null;
967
967
  }
968
- function readLiveRunRequestId(error) {
969
- if (statusOf(error) !== 409) return null;
970
- for (const candidate of detailCandidates(bodyOf(error))) {
971
- if (candidate.code !== MATRX_RUN_IN_PROGRESS) continue;
972
- const live = nonBlank(candidate.live_request_id);
973
- if (live) return live.trim();
968
+ function firstString(candidates, key) {
969
+ for (const candidate of candidates) {
970
+ const value = nonBlank(candidate[key]);
971
+ if (value) return value.trim();
974
972
  }
975
973
  return null;
976
974
  }
975
+ function readLiveRunRejoin(error) {
976
+ if (statusOf(error) !== 409) return null;
977
+ const candidates = detailCandidates(bodyOf(error));
978
+ if (candidates.length === 0) return null;
979
+ const code = readMatrxErrorCode(error);
980
+ if (code !== MATRX_RUN_IN_PROGRESS && code !== MATRX_RESUME_CONFLICT) return null;
981
+ const liveRequestId = firstString(candidates, "live_request_id");
982
+ const pathFromBody = firstString(candidates, "rejoin_path");
983
+ const rejoinPath = pathFromBody && pathFromBody.startsWith("/") ? pathFromBody : liveRequestId ? runtimeOperationRejoinPath(liveRequestId) : null;
984
+ if (!rejoinPath) return null;
985
+ const root = bodyOf(error);
986
+ return {
987
+ liveRequestId,
988
+ rejoinPath,
989
+ runId: firstString(candidates, "run_id"),
990
+ code,
991
+ body: isRecord3(root) ? root : {}
992
+ };
993
+ }
994
+ function readLiveRunRequestId(error) {
995
+ return readLiveRunRejoin(error)?.liveRequestId ?? null;
996
+ }
977
997
  function isResumeConflict(error) {
978
- return statusOf(error) === 409 && readMatrxErrorCode(error) === MATRX_RESUME_CONFLICT;
998
+ return statusOf(error) === 409 && readMatrxErrorCode(error) === MATRX_RESUME_CONFLICT && readLiveRunRejoin(error) === null;
979
999
  }
980
1000
  function isLiveStreamUnavailable(error) {
981
1001
  return statusOf(error) === 409 && readMatrxErrorCode(error) === MATRX_LIVE_STREAM_UNAVAILABLE;
@@ -1005,54 +1025,110 @@ function withRunOrganization(transport, organizationId) {
1005
1025
  })
1006
1026
  };
1007
1027
  }
1028
+ async function followWorkflowRunEvents(transport, runId, options) {
1029
+ const headers = { Accept: "text/event-stream" };
1030
+ if (options.lastEventId) headers["Last-Event-ID"] = String(options.lastEventId);
1031
+ let response;
1032
+ try {
1033
+ response = await requestStream(
1034
+ transport,
1035
+ `/runs/${encodePathSegment(runId)}/events/stream`,
1036
+ {
1037
+ method: "GET",
1038
+ headers,
1039
+ ...options.signal ? { signal: options.signal } : {}
1040
+ }
1041
+ );
1042
+ } catch (error) {
1043
+ if (options.signal?.aborted) return { ended: false };
1044
+ throw error;
1045
+ }
1046
+ try {
1047
+ for await (const frame of readMatrxSseStream(
1048
+ response.body
1049
+ )) {
1050
+ if (frame.event === "end") return { ended: true };
1051
+ if (frame.data === null) continue;
1052
+ let parsed;
1053
+ try {
1054
+ parsed = JSON.parse(frame.data);
1055
+ } catch {
1056
+ continue;
1057
+ }
1058
+ if (!isRecord3(parsed)) continue;
1059
+ options.onEvent(parsed, parsed.event === "node_stream" ? null : frame.seq);
1060
+ }
1061
+ } catch (error) {
1062
+ if (!options.signal?.aborted) throw error;
1063
+ }
1064
+ return { ended: false };
1065
+ }
1008
1066
  async function drain(handle, onEnvelope) {
1009
1067
  for await (const envelope of handle.events) onEnvelope(envelope);
1010
1068
  }
1011
1069
  async function resumeOrRejoin(transport, resumeCall, options) {
1012
- const { onEnvelope, organizationId, onRejoin, onOperationEvent, follow, ...rest } = options;
1070
+ const {
1071
+ onEnvelope,
1072
+ organizationId,
1073
+ onRejoin,
1074
+ onOperationEvent,
1075
+ onWorkflowEvent,
1076
+ follow,
1077
+ ...rest
1078
+ } = options;
1013
1079
  const bound = organizationId ? withRunOrganization(transport, organizationId) : transport;
1014
1080
  const streamOptions = {
1015
1081
  ...rest,
1016
1082
  ...options.signal ? { signal: options.signal } : {}
1017
1083
  };
1018
- let liveRequestId;
1084
+ const signalOnly = options.signal ? { signal: options.signal } : {};
1085
+ let rejoin;
1019
1086
  try {
1020
1087
  const handle = await resumeCall(bound, streamOptions);
1021
1088
  await drain(handle, onEnvelope);
1022
1089
  return { kind: "resumed", requestId: handle.requestId };
1023
1090
  } catch (error) {
1024
- if (error instanceof MatrxApiError && isResumeConflict(error)) {
1025
- return { kind: "resume_conflict", error };
1091
+ const target = readLiveRunRejoin(error);
1092
+ if (!target) {
1093
+ if (error instanceof MatrxApiError && isResumeConflict(error)) {
1094
+ return { kind: "resume_conflict", error };
1095
+ }
1096
+ throw error;
1026
1097
  }
1027
- const live = readLiveRunRequestId(error);
1028
- if (!live) throw error;
1029
- liveRequestId = live;
1098
+ rejoin = target;
1030
1099
  }
1031
- onRejoin?.(liveRequestId);
1100
+ onRejoin?.(rejoin);
1101
+ let unavailableBody;
1032
1102
  try {
1033
- const handle = await rejoinRuntimeOperation(bound, liveRequestId, streamOptions);
1103
+ const handle = await openMatrxStream(bound, rejoin.rejoinPath, {
1104
+ ...streamOptions,
1105
+ // The rejoin routes take no body model; the reference client posts {}.
1106
+ body: {}
1107
+ });
1034
1108
  await drain(handle, onEnvelope);
1035
- return { kind: "rejoined", liveRequestId };
1109
+ return { kind: "rejoined", rejoin };
1036
1110
  } catch (error) {
1037
1111
  if (!isLiveStreamUnavailable(error)) throw error;
1112
+ unavailableBody = bodyOf(error);
1038
1113
  }
1039
- const statusView = await getRuntimeOperationStatus(bound, liveRequestId, {
1040
- ...options.signal ? { signal: options.signal } : {}
1041
- });
1114
+ const runId = firstString(detailCandidates(unavailableBody), "run_id") ?? rejoin.runId;
1115
+ if (runId && rejoin.rejoinPath.startsWith("/runtime/operations/")) {
1116
+ const result2 = await followWorkflowRunEvents(bound, runId, {
1117
+ ...signalOnly,
1118
+ onEvent: (event, seq) => onWorkflowEvent?.(event, seq)
1119
+ });
1120
+ return { kind: "followed_workflow", rejoin, runId, ended: result2.ended };
1121
+ }
1122
+ const liveRequestId = rejoin.liveRequestId;
1123
+ const statusView = liveRequestId ? await getRuntimeOperationStatus(bound, liveRequestId, signalOnly) : null;
1042
1124
  const operation = statusView?.operations.find((op) => !op.is_terminal) ?? statusView?.operations[0] ?? null;
1043
1125
  if (!operation) {
1044
- return {
1045
- kind: "followed",
1046
- liveRequestId,
1047
- executionId: null,
1048
- ended: false,
1049
- status: null
1050
- };
1126
+ return { kind: "followed", rejoin, executionId: null, ended: false, status: null };
1051
1127
  }
1052
1128
  if (operation.is_terminal) {
1053
1129
  return {
1054
1130
  kind: "followed",
1055
- liveRequestId,
1131
+ rejoin,
1056
1132
  executionId: operation.execution_id,
1057
1133
  ended: true,
1058
1134
  status: operation.status
@@ -1061,12 +1137,12 @@ async function resumeOrRejoin(transport, resumeCall, options) {
1061
1137
  const result = await followRuntimeOperationToEnd(bound, operation.execution_id, {
1062
1138
  ...follow,
1063
1139
  lastEventSeq: operation.last_event_seq,
1064
- ...options.signal ? { signal: options.signal } : {},
1140
+ ...signalOnly,
1065
1141
  onEvent: (event) => onOperationEvent?.(event)
1066
1142
  });
1067
1143
  return {
1068
1144
  kind: "followed",
1069
- liveRequestId,
1145
+ rejoin,
1070
1146
  executionId: operation.execution_id,
1071
1147
  ended: result.ended,
1072
1148
  status: result.status
@@ -1301,6 +1377,7 @@ export {
1301
1377
  fetchWithMatrxProtocolFallback,
1302
1378
  followRuntimeOperationEvents,
1303
1379
  followRuntimeOperationToEnd,
1380
+ followWorkflowRunEvents,
1304
1381
  getRuntimeOperationStatus,
1305
1382
  getRuntimeOperationsByLink,
1306
1383
  isCoveredAiPath,
@@ -1316,6 +1393,7 @@ export {
1316
1393
  normalizeMatrxError,
1317
1394
  openMatrxStream,
1318
1395
  providerSessionFailureBody,
1396
+ readLiveRunRejoin,
1319
1397
  readLiveRunRequestId,
1320
1398
  readMatrxErrorCode,
1321
1399
  rejoinRuntimeOperation,