@runtypelabs/persona 4.5.0 → 4.6.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.
Files changed (40) hide show
  1. package/dist/animations/glyph-cycle.d.cts +1 -1
  2. package/dist/animations/glyph-cycle.d.ts +1 -1
  3. package/dist/animations/{types-C6tFDxKy.d.cts → types-8RICZWQe.d.cts} +6 -0
  4. package/dist/animations/{types-C6tFDxKy.d.ts → types-8RICZWQe.d.ts} +6 -0
  5. package/dist/animations/wipe.d.cts +1 -1
  6. package/dist/animations/wipe.d.ts +1 -1
  7. package/dist/chunk-DFBSCFYN.js +1 -0
  8. package/dist/codegen.cjs +1 -1
  9. package/dist/codegen.js +1 -1
  10. package/dist/index.cjs +51 -51
  11. package/dist/index.cjs.map +1 -1
  12. package/dist/index.d.cts +228 -2
  13. package/dist/index.d.ts +228 -2
  14. package/dist/index.global.js +37 -37
  15. package/dist/index.global.js.map +1 -1
  16. package/dist/index.js +51 -51
  17. package/dist/index.js.map +1 -1
  18. package/dist/launcher.global.js.map +1 -1
  19. package/dist/markdown-parsers-entry-NVFT3TE6.js +1 -0
  20. package/dist/runtype-tts-entry-HFUV2UF7.js +1 -0
  21. package/dist/session-reconnect-U77QFUR7.js +1 -0
  22. package/dist/smart-dom-reader.d.cts +90 -0
  23. package/dist/smart-dom-reader.d.ts +90 -0
  24. package/dist/theme-editor-preview.cjs +48 -48
  25. package/dist/theme-editor-preview.d.cts +130 -1
  26. package/dist/theme-editor-preview.d.ts +130 -1
  27. package/dist/theme-editor-preview.js +48 -48
  28. package/dist/theme-editor.d.cts +90 -0
  29. package/dist/theme-editor.d.ts +90 -0
  30. package/package.json +2 -2
  31. package/src/client.ts +48 -7
  32. package/src/generated/runtype-openapi-contract.ts +6 -0
  33. package/src/reconnect-wake.test.ts +162 -0
  34. package/src/reconnect.test.ts +430 -0
  35. package/src/session-reconnect.ts +282 -0
  36. package/src/session.ts +408 -5
  37. package/src/types.ts +107 -1
  38. package/src/ui.stream-animation-update.test.ts +99 -0
  39. package/src/ui.ts +71 -1
  40. package/src/utils/constants.ts +3 -1
package/dist/index.d.cts CHANGED
@@ -1082,12 +1082,15 @@ type RuntypeExecutionStreamEvent = ({
1082
1082
  seq: number;
1083
1083
  type: "approval_complete";
1084
1084
  }) | ({
1085
+ awaitReason?: string;
1085
1086
  awaitedAt?: string;
1087
+ crawlId?: string;
1086
1088
  executionId: string;
1087
1089
  origin?: "webmcp" | "sdk";
1088
1090
  pageOrigin?: string;
1089
1091
  parameters?: Record<string, unknown>;
1090
1092
  seq: number;
1093
+ stepId?: string;
1091
1094
  toolCallId?: string;
1092
1095
  toolId?: string;
1093
1096
  toolName?: string;
@@ -1176,13 +1179,16 @@ type RuntypeFlowSSEEvent = {
1176
1179
  type: "flow_error";
1177
1180
  upgradeUrl?: string;
1178
1181
  }) | ({
1182
+ awaitReason?: string;
1179
1183
  awaitedAt: string;
1184
+ crawlId?: string;
1180
1185
  executionId?: string;
1181
1186
  flowId: string;
1182
1187
  origin?: "webmcp" | "sdk";
1183
1188
  pageOrigin?: string;
1184
1189
  parameters?: Record<string, unknown>;
1185
1190
  seq?: number;
1191
+ stepId?: string;
1186
1192
  toolCallId?: string;
1187
1193
  toolId?: string;
1188
1194
  toolName?: string;
@@ -1888,6 +1894,27 @@ type AgentExecutionState = {
1888
1894
  completedAt?: number;
1889
1895
  stopReason?: 'complete' | 'end_turn' | 'max_turns' | 'max_cost' | 'timeout' | 'error';
1890
1896
  };
1897
+ /**
1898
+ * The coordinates needed to resume a durable agent turn after the SSE
1899
+ * connection drops (any resumable, server-persisted execution, e.g. Claude
1900
+ * Managed agents or async/background runs). Held on the session while a
1901
+ * resumable stream is in flight
1902
+ * and surfaced to the host via {@link AgentWidgetConfig.onExecutionState} so it
1903
+ * can persist `{ executionId, lastEventId }` next to its own `conversationId`
1904
+ * for the tab-reload path.
1905
+ *
1906
+ * `conversationId` is intentionally absent: it is host-owned (it lives in the
1907
+ * host's `reconnectStream`/`customFetch` closure; the widget never sees it).
1908
+ */
1909
+ type ResumableHandle = {
1910
+ /** The durable turn to reconnect to (from `agentMetadata.executionId`). */
1911
+ executionId: string;
1912
+ /** Highest SSE `id:` seq applied: the `?after=` reconnect cursor. */
1913
+ lastEventId: string;
1914
+ /** The open assistant message being streamed into, kept open on resume. */
1915
+ assistantMessageId: string;
1916
+ status: 'running';
1917
+ };
1891
1918
  /**
1892
1919
  * Metadata attached to messages created during agent execution.
1893
1920
  */
@@ -2170,6 +2197,22 @@ type AgentWidgetControllerEventMap = {
2170
2197
  approval: AgentWidgetApproval;
2171
2198
  decision: string;
2172
2199
  };
2200
+ /** A durable stream dropped; the widget is about to reconnect. */
2201
+ "stream:paused": {
2202
+ executionId: string;
2203
+ after: string;
2204
+ };
2205
+ /** A reconnect attempt is in flight (`attempt` is 1-based). */
2206
+ "stream:resuming": {
2207
+ executionId: string;
2208
+ after: string;
2209
+ attempt: number;
2210
+ };
2211
+ /** The durable turn resumed and reached its terminal after a reconnect. */
2212
+ "stream:resumed": {
2213
+ executionId: string;
2214
+ after: string;
2215
+ };
2173
2216
  };
2174
2217
  /**
2175
2218
  * Layout for the artifact split / drawer (CSS lengths unless noted).
@@ -3278,6 +3321,10 @@ type AgentWidgetStatusIndicatorConfig = {
3278
3321
  connectingText?: string;
3279
3322
  connectedText?: string;
3280
3323
  errorText?: string;
3324
+ /** Status text while a dropped durable stream is awaiting reconnect. */
3325
+ pausedText?: string;
3326
+ /** Status text while a reconnect attempt is in flight. */
3327
+ resumingText?: string;
3281
3328
  };
3282
3329
  type AgentWidgetVoiceRecognitionConfig = {
3283
3330
  enabled?: boolean;
@@ -5469,6 +5516,65 @@ type AgentWidgetConfig = {
5469
5516
  * ```
5470
5517
  */
5471
5518
  customFetch?: AgentWidgetCustomFetch;
5519
+ /**
5520
+ * Durable-session reconnect transport (host-owned, symmetric to
5521
+ * {@link customFetch}). When a durable agent stream drops mid-turn (any
5522
+ * resumable, server-persisted execution, e.g. Claude Managed agents or
5523
+ * async/background runs), the widget calls this to fetch the read-only
5524
+ * reconnect stream and pipes its body through the normal event pipeline to
5525
+ * resume from where it left off.
5526
+ *
5527
+ * The host closure owns `agentId` / `conversationId` / base URL / auth and
5528
+ * builds the events request, e.g.:
5529
+ *
5530
+ * ```typescript
5531
+ * reconnectStream: ({ executionId, after, signal }) =>
5532
+ * fetch(
5533
+ * `${baseUrl}/v1/agents/${agentId}/executions/${executionId}` +
5534
+ * `/events?conversationId=${conversationId}&after=${after}`,
5535
+ * { headers: { Authorization: `Bearer ${token}`, "X-Persona-Version": ver }, signal }
5536
+ * )
5537
+ * ```
5538
+ *
5539
+ * Resolve with a `text/event-stream` `Response`. Throw or resolve non-ok to
5540
+ * signal "this attempt failed" (the widget backs off and retries, then gives
5541
+ * up after the bounded attempts). Reconnect only ever arms on the durable
5542
+ * lane (streams carrying SSE `id:` lines); without this hook a drop finalizes
5543
+ * exactly as before.
5544
+ */
5545
+ reconnectStream?: (ctx: {
5546
+ executionId: string;
5547
+ after: string;
5548
+ signal: AbortSignal;
5549
+ }) => Promise<Response>;
5550
+ /**
5551
+ * Tuning for the auto-reconnect backoff. Defaults to ~5 attempts over ~30s
5552
+ * (`backoffMs: [1000, 2000, 4000, 8000, 8000]`).
5553
+ */
5554
+ reconnect?: {
5555
+ /** Max reconnect attempts before giving up and finalizing. @default backoffMs.length */
5556
+ maxAttempts?: number;
5557
+ /** Per-attempt delay (ms) before each retry. @default [1000, 2000, 4000, 8000, 8000] */
5558
+ backoffMs?: number[];
5559
+ };
5560
+ /**
5561
+ * Called whenever the durable resume handle changes: created when a durable
5562
+ * turn starts streaming, updated as the cursor advances (throttled), and
5563
+ * `null` when the turn finishes, errors, or is torn down. Persist
5564
+ * `{ executionId, lastEventId }` next to your `conversationId` and pass it
5565
+ * back via {@link resume} on the next mount to survive a tab reload.
5566
+ */
5567
+ onExecutionState?: (handle: ResumableHandle | null) => void;
5568
+ /**
5569
+ * Resume a durable turn on boot (tab-reload path). When present alongside
5570
+ * {@link reconnectStream}, the widget enters `resuming` immediately on mount
5571
+ * and reconnects from `after`, replaying everything past the cursor into the
5572
+ * restored conversation.
5573
+ */
5574
+ resume?: {
5575
+ executionId: string;
5576
+ after: string;
5577
+ };
5472
5578
  /**
5473
5579
  * Custom SSE event parser for non-standard streaming response formats.
5474
5580
  *
@@ -6011,9 +6117,27 @@ type AgentWidgetEvent = {
6011
6117
  } | {
6012
6118
  type: "status";
6013
6119
  status: "connecting" | "connected" | "error" | "idle";
6120
+ /**
6121
+ * Set on the `idle` emitted from a graceful execution terminal
6122
+ * (`execution_complete`). Distinguishes a real finish from the plain
6123
+ * `idle` the dispatch wrappers emit in their `finally` on a dropped
6124
+ * connection. The durable-reconnect drop detection keys off this.
6125
+ */
6126
+ terminal?: boolean;
6014
6127
  } | {
6015
6128
  type: "error";
6016
6129
  error: Error;
6130
+ }
6131
+ /**
6132
+ * Durable-session reconnect cursor. Carries the SSE `id:` line (the durable
6133
+ * row seq) of a fully-parsed frame so the session can track `lastEventId`
6134
+ * and resume from `?after=<id>` after a drop. Only emitted on the durable
6135
+ * lane (any resumable execution that stamps `id:` lines, e.g. Claude Managed
6136
+ * agents or async/background runs).
6137
+ */
6138
+ | {
6139
+ type: "cursor";
6140
+ id: string;
6017
6141
  } | {
6018
6142
  type: "artifact_start";
6019
6143
  id: string;
@@ -6243,7 +6367,7 @@ declare class AgentWidgetClient {
6243
6367
  * This allows piping responses from endpoints like agent approval
6244
6368
  * through the same message/tool/reasoning handling as dispatch().
6245
6369
  */
6246
- processStream(body: ReadableStream<Uint8Array>, onEvent: SSEHandler, assistantMessageId?: string): Promise<void>;
6370
+ processStream(body: ReadableStream<Uint8Array>, onEvent: SSEHandler, assistantMessageId?: string, seedContent?: string): Promise<void>;
6247
6371
  /**
6248
6372
  * Send an approval decision to the API and return the response
6249
6373
  * for streaming continuation.
@@ -6358,7 +6482,7 @@ declare class ReadAloudController {
6358
6482
  private set;
6359
6483
  }
6360
6484
 
6361
- type AgentWidgetSessionStatus = "idle" | "connecting" | "connected" | "error";
6485
+ type AgentWidgetSessionStatus = "idle" | "connecting" | "connected" | "error" | "paused" | "resuming";
6362
6486
  type SessionCallbacks = {
6363
6487
  onMessagesChanged: (messages: AgentWidgetMessage[]) => void;
6364
6488
  onStatusChanged: (status: AgentWidgetSessionStatus) => void;
@@ -6369,6 +6493,17 @@ type SessionCallbacks = {
6369
6493
  artifacts: PersonaArtifactRecord[];
6370
6494
  selectedId: string | null;
6371
6495
  }) => void;
6496
+ /**
6497
+ * Durable-session reconnect lifecycle, surfaced so the UI can map it to the
6498
+ * public `stream:paused` / `stream:resuming` / `stream:resumed` controller
6499
+ * events. `paused` fires once on the drop, `resuming` once per attempt,
6500
+ * `resumed` when a reconnect reaches its terminal.
6501
+ */
6502
+ onReconnect?: (event: {
6503
+ phase: "paused" | "resuming" | "resumed";
6504
+ handle: ResumableHandle;
6505
+ attempt?: number;
6506
+ }) => void;
6372
6507
  };
6373
6508
  declare class AgentWidgetSession {
6374
6509
  private config;
@@ -6381,6 +6516,12 @@ declare class AgentWidgetSession {
6381
6516
  private sequenceCounter;
6382
6517
  private clientSession;
6383
6518
  private agentExecution;
6519
+ private resumable;
6520
+ private activeAssistantMessageId;
6521
+ private reconnecting;
6522
+ private reconnectController;
6523
+ private reconnectControllerPromise;
6524
+ private executionStateTimer;
6384
6525
  private artifacts;
6385
6526
  private selectedArtifactId;
6386
6527
  private webMcpInflightKeys;
@@ -6652,6 +6793,19 @@ declare class AgentWidgetSession {
6652
6793
  connectStream(stream: ReadableStream<Uint8Array>, options?: {
6653
6794
  assistantMessageId?: string;
6654
6795
  allowReentry?: boolean;
6796
+ /**
6797
+ * Durable reconnect: keep the assistant message identified by
6798
+ * `assistantMessageId` OPEN so the replayed deltas keep filling it. The
6799
+ * default stale-finalize pass would otherwise seal the very bubble we're
6800
+ * resuming into.
6801
+ */
6802
+ preserveAssistantId?: boolean;
6803
+ /**
6804
+ * Durable reconnect: text already shown in the resumed bubble, so the
6805
+ * fresh stream's accumulator continues from it (the replay carries only
6806
+ * post-cursor deltas, not the full text).
6807
+ */
6808
+ seedContent?: string;
6655
6809
  }): Promise<void>;
6656
6810
  /**
6657
6811
  * Install the native approval-bubble confirm handler on the WebMCP bridge
@@ -6835,6 +6989,73 @@ declare class AgentWidgetSession {
6835
6989
  hydrateMessages(messages: AgentWidgetMessage[]): void;
6836
6990
  hydrateArtifacts(artifacts: PersonaArtifactRecord[], selectedId?: string | null): void;
6837
6991
  private handleEvent;
6992
+ /**
6993
+ * Record the latest SSE `id:` cursor and (lazily) form/advance the resume
6994
+ * handle. Needs an executionId (lifted into `agentExecution` from the same
6995
+ * frame's metadata, handled before this trailing cursor event) and the active
6996
+ * assistant message id.
6997
+ */
6998
+ private trackCursor;
6999
+ /**
7000
+ * The drop gate: is this stream-end a dropped connection we should reconnect,
7001
+ * rather than a graceful finish or an intentional `step_await` pause? ALL must
7002
+ * hold (see plan §Design overview keystone 3).
7003
+ */
7004
+ private isDurableDrop;
7005
+ /**
7006
+ * True when the run is parked awaiting user input (an unanswered
7007
+ * `ask_user_question` / local-tool await, or a pending approval). Those end
7008
+ * the stream with `idle` and no terminal too, but are NOT drops.
7009
+ */
7010
+ private isAwaitPending;
7011
+ /**
7012
+ * Enter the reconnect flow. Keeps the assistant bubble open and `streaming`
7013
+ * true, flips to `resuming`, and kicks the bounded backoff loop. Re-entrant
7014
+ * calls (a second drop while reconnecting) are no-ops.
7015
+ */
7016
+ private beginReconnect;
7017
+ /**
7018
+ * Lazily import and instantiate the reconnect controller. Cached so repeated
7019
+ * reconnects reuse one controller (and one module load). Deliberately kept off
7020
+ * every hot path (`sendMessage` / `cancel` / `teardownReconnect`) so a widget
7021
+ * that never reconnects never pulls the chunk.
7022
+ */
7023
+ private loadReconnectController;
7024
+ /** The narrow surface the lazily-loaded reconnect loop drives. */
7025
+ private buildReconnectHost;
7026
+ /** Public manual retry (e.g. a "Reconnect" button). */
7027
+ reconnectNow(): void;
7028
+ /**
7029
+ * Resume a durable turn on boot (tab-reload path). Re-opens the trailing
7030
+ * assistant bubble (or mints one), restores the execution/handle, and
7031
+ * reconnects from the persisted cursor. No-op without `reconnectStream`.
7032
+ */
7033
+ resumeFromHandle(resume: {
7034
+ executionId: string;
7035
+ after: string;
7036
+ }): void;
7037
+ /**
7038
+ * Reopen the trailing assistant bubble (the persisted partial) so replayed
7039
+ * deltas append to it. Returns its id, or null if the last turn doesn't end
7040
+ * in a plain assistant message.
7041
+ */
7042
+ private reopenTrailingAssistant;
7043
+ /**
7044
+ * Tear down any in-flight/pending reconnect and clear the resume handle. Runs
7045
+ * on every new turn / cancel / hydrate, so it must NOT pull the reconnect
7046
+ * chunk: it only delegates to the controller if one was already created.
7047
+ */
7048
+ private teardownReconnect;
7049
+ /** Drop the resume handle and notify the host (`onExecutionState(null)`). */
7050
+ private clearResumable;
7051
+ /**
7052
+ * Surface the resume handle to the host for persistence. Immediate on
7053
+ * create/clear; trailing-edge throttled on cursor advances so it isn't called
7054
+ * per delta.
7055
+ */
7056
+ private notifyExecutionState;
7057
+ /** The current durable resume handle, if any (read-only). */
7058
+ getResumableHandle(): ResumableHandle | null;
6838
7059
  private setStatus;
6839
7060
  private setStreaming;
6840
7061
  /**
@@ -7013,6 +7234,11 @@ type Controller = {
7013
7234
  clearChat: () => void;
7014
7235
  setMessage: (message: string) => boolean;
7015
7236
  submitMessage: (message?: string) => boolean;
7237
+ /**
7238
+ * Manually retry a dropped durable stream (e.g. from a "Reconnect" button).
7239
+ * No-op unless a resumable durable turn dropped and `reconnectStream` is set.
7240
+ */
7241
+ reconnect: () => void;
7016
7242
  startVoiceRecognition: () => boolean;
7017
7243
  stopVoiceRecognition: () => boolean;
7018
7244
  /**
package/dist/index.d.ts CHANGED
@@ -1082,12 +1082,15 @@ type RuntypeExecutionStreamEvent = ({
1082
1082
  seq: number;
1083
1083
  type: "approval_complete";
1084
1084
  }) | ({
1085
+ awaitReason?: string;
1085
1086
  awaitedAt?: string;
1087
+ crawlId?: string;
1086
1088
  executionId: string;
1087
1089
  origin?: "webmcp" | "sdk";
1088
1090
  pageOrigin?: string;
1089
1091
  parameters?: Record<string, unknown>;
1090
1092
  seq: number;
1093
+ stepId?: string;
1091
1094
  toolCallId?: string;
1092
1095
  toolId?: string;
1093
1096
  toolName?: string;
@@ -1176,13 +1179,16 @@ type RuntypeFlowSSEEvent = {
1176
1179
  type: "flow_error";
1177
1180
  upgradeUrl?: string;
1178
1181
  }) | ({
1182
+ awaitReason?: string;
1179
1183
  awaitedAt: string;
1184
+ crawlId?: string;
1180
1185
  executionId?: string;
1181
1186
  flowId: string;
1182
1187
  origin?: "webmcp" | "sdk";
1183
1188
  pageOrigin?: string;
1184
1189
  parameters?: Record<string, unknown>;
1185
1190
  seq?: number;
1191
+ stepId?: string;
1186
1192
  toolCallId?: string;
1187
1193
  toolId?: string;
1188
1194
  toolName?: string;
@@ -1888,6 +1894,27 @@ type AgentExecutionState = {
1888
1894
  completedAt?: number;
1889
1895
  stopReason?: 'complete' | 'end_turn' | 'max_turns' | 'max_cost' | 'timeout' | 'error';
1890
1896
  };
1897
+ /**
1898
+ * The coordinates needed to resume a durable agent turn after the SSE
1899
+ * connection drops (any resumable, server-persisted execution, e.g. Claude
1900
+ * Managed agents or async/background runs). Held on the session while a
1901
+ * resumable stream is in flight
1902
+ * and surfaced to the host via {@link AgentWidgetConfig.onExecutionState} so it
1903
+ * can persist `{ executionId, lastEventId }` next to its own `conversationId`
1904
+ * for the tab-reload path.
1905
+ *
1906
+ * `conversationId` is intentionally absent: it is host-owned (it lives in the
1907
+ * host's `reconnectStream`/`customFetch` closure; the widget never sees it).
1908
+ */
1909
+ type ResumableHandle = {
1910
+ /** The durable turn to reconnect to (from `agentMetadata.executionId`). */
1911
+ executionId: string;
1912
+ /** Highest SSE `id:` seq applied: the `?after=` reconnect cursor. */
1913
+ lastEventId: string;
1914
+ /** The open assistant message being streamed into, kept open on resume. */
1915
+ assistantMessageId: string;
1916
+ status: 'running';
1917
+ };
1891
1918
  /**
1892
1919
  * Metadata attached to messages created during agent execution.
1893
1920
  */
@@ -2170,6 +2197,22 @@ type AgentWidgetControllerEventMap = {
2170
2197
  approval: AgentWidgetApproval;
2171
2198
  decision: string;
2172
2199
  };
2200
+ /** A durable stream dropped; the widget is about to reconnect. */
2201
+ "stream:paused": {
2202
+ executionId: string;
2203
+ after: string;
2204
+ };
2205
+ /** A reconnect attempt is in flight (`attempt` is 1-based). */
2206
+ "stream:resuming": {
2207
+ executionId: string;
2208
+ after: string;
2209
+ attempt: number;
2210
+ };
2211
+ /** The durable turn resumed and reached its terminal after a reconnect. */
2212
+ "stream:resumed": {
2213
+ executionId: string;
2214
+ after: string;
2215
+ };
2173
2216
  };
2174
2217
  /**
2175
2218
  * Layout for the artifact split / drawer (CSS lengths unless noted).
@@ -3278,6 +3321,10 @@ type AgentWidgetStatusIndicatorConfig = {
3278
3321
  connectingText?: string;
3279
3322
  connectedText?: string;
3280
3323
  errorText?: string;
3324
+ /** Status text while a dropped durable stream is awaiting reconnect. */
3325
+ pausedText?: string;
3326
+ /** Status text while a reconnect attempt is in flight. */
3327
+ resumingText?: string;
3281
3328
  };
3282
3329
  type AgentWidgetVoiceRecognitionConfig = {
3283
3330
  enabled?: boolean;
@@ -5469,6 +5516,65 @@ type AgentWidgetConfig = {
5469
5516
  * ```
5470
5517
  */
5471
5518
  customFetch?: AgentWidgetCustomFetch;
5519
+ /**
5520
+ * Durable-session reconnect transport (host-owned, symmetric to
5521
+ * {@link customFetch}). When a durable agent stream drops mid-turn (any
5522
+ * resumable, server-persisted execution, e.g. Claude Managed agents or
5523
+ * async/background runs), the widget calls this to fetch the read-only
5524
+ * reconnect stream and pipes its body through the normal event pipeline to
5525
+ * resume from where it left off.
5526
+ *
5527
+ * The host closure owns `agentId` / `conversationId` / base URL / auth and
5528
+ * builds the events request, e.g.:
5529
+ *
5530
+ * ```typescript
5531
+ * reconnectStream: ({ executionId, after, signal }) =>
5532
+ * fetch(
5533
+ * `${baseUrl}/v1/agents/${agentId}/executions/${executionId}` +
5534
+ * `/events?conversationId=${conversationId}&after=${after}`,
5535
+ * { headers: { Authorization: `Bearer ${token}`, "X-Persona-Version": ver }, signal }
5536
+ * )
5537
+ * ```
5538
+ *
5539
+ * Resolve with a `text/event-stream` `Response`. Throw or resolve non-ok to
5540
+ * signal "this attempt failed" (the widget backs off and retries, then gives
5541
+ * up after the bounded attempts). Reconnect only ever arms on the durable
5542
+ * lane (streams carrying SSE `id:` lines); without this hook a drop finalizes
5543
+ * exactly as before.
5544
+ */
5545
+ reconnectStream?: (ctx: {
5546
+ executionId: string;
5547
+ after: string;
5548
+ signal: AbortSignal;
5549
+ }) => Promise<Response>;
5550
+ /**
5551
+ * Tuning for the auto-reconnect backoff. Defaults to ~5 attempts over ~30s
5552
+ * (`backoffMs: [1000, 2000, 4000, 8000, 8000]`).
5553
+ */
5554
+ reconnect?: {
5555
+ /** Max reconnect attempts before giving up and finalizing. @default backoffMs.length */
5556
+ maxAttempts?: number;
5557
+ /** Per-attempt delay (ms) before each retry. @default [1000, 2000, 4000, 8000, 8000] */
5558
+ backoffMs?: number[];
5559
+ };
5560
+ /**
5561
+ * Called whenever the durable resume handle changes: created when a durable
5562
+ * turn starts streaming, updated as the cursor advances (throttled), and
5563
+ * `null` when the turn finishes, errors, or is torn down. Persist
5564
+ * `{ executionId, lastEventId }` next to your `conversationId` and pass it
5565
+ * back via {@link resume} on the next mount to survive a tab reload.
5566
+ */
5567
+ onExecutionState?: (handle: ResumableHandle | null) => void;
5568
+ /**
5569
+ * Resume a durable turn on boot (tab-reload path). When present alongside
5570
+ * {@link reconnectStream}, the widget enters `resuming` immediately on mount
5571
+ * and reconnects from `after`, replaying everything past the cursor into the
5572
+ * restored conversation.
5573
+ */
5574
+ resume?: {
5575
+ executionId: string;
5576
+ after: string;
5577
+ };
5472
5578
  /**
5473
5579
  * Custom SSE event parser for non-standard streaming response formats.
5474
5580
  *
@@ -6011,9 +6117,27 @@ type AgentWidgetEvent = {
6011
6117
  } | {
6012
6118
  type: "status";
6013
6119
  status: "connecting" | "connected" | "error" | "idle";
6120
+ /**
6121
+ * Set on the `idle` emitted from a graceful execution terminal
6122
+ * (`execution_complete`). Distinguishes a real finish from the plain
6123
+ * `idle` the dispatch wrappers emit in their `finally` on a dropped
6124
+ * connection. The durable-reconnect drop detection keys off this.
6125
+ */
6126
+ terminal?: boolean;
6014
6127
  } | {
6015
6128
  type: "error";
6016
6129
  error: Error;
6130
+ }
6131
+ /**
6132
+ * Durable-session reconnect cursor. Carries the SSE `id:` line (the durable
6133
+ * row seq) of a fully-parsed frame so the session can track `lastEventId`
6134
+ * and resume from `?after=<id>` after a drop. Only emitted on the durable
6135
+ * lane (any resumable execution that stamps `id:` lines, e.g. Claude Managed
6136
+ * agents or async/background runs).
6137
+ */
6138
+ | {
6139
+ type: "cursor";
6140
+ id: string;
6017
6141
  } | {
6018
6142
  type: "artifact_start";
6019
6143
  id: string;
@@ -6243,7 +6367,7 @@ declare class AgentWidgetClient {
6243
6367
  * This allows piping responses from endpoints like agent approval
6244
6368
  * through the same message/tool/reasoning handling as dispatch().
6245
6369
  */
6246
- processStream(body: ReadableStream<Uint8Array>, onEvent: SSEHandler, assistantMessageId?: string): Promise<void>;
6370
+ processStream(body: ReadableStream<Uint8Array>, onEvent: SSEHandler, assistantMessageId?: string, seedContent?: string): Promise<void>;
6247
6371
  /**
6248
6372
  * Send an approval decision to the API and return the response
6249
6373
  * for streaming continuation.
@@ -6358,7 +6482,7 @@ declare class ReadAloudController {
6358
6482
  private set;
6359
6483
  }
6360
6484
 
6361
- type AgentWidgetSessionStatus = "idle" | "connecting" | "connected" | "error";
6485
+ type AgentWidgetSessionStatus = "idle" | "connecting" | "connected" | "error" | "paused" | "resuming";
6362
6486
  type SessionCallbacks = {
6363
6487
  onMessagesChanged: (messages: AgentWidgetMessage[]) => void;
6364
6488
  onStatusChanged: (status: AgentWidgetSessionStatus) => void;
@@ -6369,6 +6493,17 @@ type SessionCallbacks = {
6369
6493
  artifacts: PersonaArtifactRecord[];
6370
6494
  selectedId: string | null;
6371
6495
  }) => void;
6496
+ /**
6497
+ * Durable-session reconnect lifecycle, surfaced so the UI can map it to the
6498
+ * public `stream:paused` / `stream:resuming` / `stream:resumed` controller
6499
+ * events. `paused` fires once on the drop, `resuming` once per attempt,
6500
+ * `resumed` when a reconnect reaches its terminal.
6501
+ */
6502
+ onReconnect?: (event: {
6503
+ phase: "paused" | "resuming" | "resumed";
6504
+ handle: ResumableHandle;
6505
+ attempt?: number;
6506
+ }) => void;
6372
6507
  };
6373
6508
  declare class AgentWidgetSession {
6374
6509
  private config;
@@ -6381,6 +6516,12 @@ declare class AgentWidgetSession {
6381
6516
  private sequenceCounter;
6382
6517
  private clientSession;
6383
6518
  private agentExecution;
6519
+ private resumable;
6520
+ private activeAssistantMessageId;
6521
+ private reconnecting;
6522
+ private reconnectController;
6523
+ private reconnectControllerPromise;
6524
+ private executionStateTimer;
6384
6525
  private artifacts;
6385
6526
  private selectedArtifactId;
6386
6527
  private webMcpInflightKeys;
@@ -6652,6 +6793,19 @@ declare class AgentWidgetSession {
6652
6793
  connectStream(stream: ReadableStream<Uint8Array>, options?: {
6653
6794
  assistantMessageId?: string;
6654
6795
  allowReentry?: boolean;
6796
+ /**
6797
+ * Durable reconnect: keep the assistant message identified by
6798
+ * `assistantMessageId` OPEN so the replayed deltas keep filling it. The
6799
+ * default stale-finalize pass would otherwise seal the very bubble we're
6800
+ * resuming into.
6801
+ */
6802
+ preserveAssistantId?: boolean;
6803
+ /**
6804
+ * Durable reconnect: text already shown in the resumed bubble, so the
6805
+ * fresh stream's accumulator continues from it (the replay carries only
6806
+ * post-cursor deltas, not the full text).
6807
+ */
6808
+ seedContent?: string;
6655
6809
  }): Promise<void>;
6656
6810
  /**
6657
6811
  * Install the native approval-bubble confirm handler on the WebMCP bridge
@@ -6835,6 +6989,73 @@ declare class AgentWidgetSession {
6835
6989
  hydrateMessages(messages: AgentWidgetMessage[]): void;
6836
6990
  hydrateArtifacts(artifacts: PersonaArtifactRecord[], selectedId?: string | null): void;
6837
6991
  private handleEvent;
6992
+ /**
6993
+ * Record the latest SSE `id:` cursor and (lazily) form/advance the resume
6994
+ * handle. Needs an executionId (lifted into `agentExecution` from the same
6995
+ * frame's metadata, handled before this trailing cursor event) and the active
6996
+ * assistant message id.
6997
+ */
6998
+ private trackCursor;
6999
+ /**
7000
+ * The drop gate: is this stream-end a dropped connection we should reconnect,
7001
+ * rather than a graceful finish or an intentional `step_await` pause? ALL must
7002
+ * hold (see plan §Design overview keystone 3).
7003
+ */
7004
+ private isDurableDrop;
7005
+ /**
7006
+ * True when the run is parked awaiting user input (an unanswered
7007
+ * `ask_user_question` / local-tool await, or a pending approval). Those end
7008
+ * the stream with `idle` and no terminal too, but are NOT drops.
7009
+ */
7010
+ private isAwaitPending;
7011
+ /**
7012
+ * Enter the reconnect flow. Keeps the assistant bubble open and `streaming`
7013
+ * true, flips to `resuming`, and kicks the bounded backoff loop. Re-entrant
7014
+ * calls (a second drop while reconnecting) are no-ops.
7015
+ */
7016
+ private beginReconnect;
7017
+ /**
7018
+ * Lazily import and instantiate the reconnect controller. Cached so repeated
7019
+ * reconnects reuse one controller (and one module load). Deliberately kept off
7020
+ * every hot path (`sendMessage` / `cancel` / `teardownReconnect`) so a widget
7021
+ * that never reconnects never pulls the chunk.
7022
+ */
7023
+ private loadReconnectController;
7024
+ /** The narrow surface the lazily-loaded reconnect loop drives. */
7025
+ private buildReconnectHost;
7026
+ /** Public manual retry (e.g. a "Reconnect" button). */
7027
+ reconnectNow(): void;
7028
+ /**
7029
+ * Resume a durable turn on boot (tab-reload path). Re-opens the trailing
7030
+ * assistant bubble (or mints one), restores the execution/handle, and
7031
+ * reconnects from the persisted cursor. No-op without `reconnectStream`.
7032
+ */
7033
+ resumeFromHandle(resume: {
7034
+ executionId: string;
7035
+ after: string;
7036
+ }): void;
7037
+ /**
7038
+ * Reopen the trailing assistant bubble (the persisted partial) so replayed
7039
+ * deltas append to it. Returns its id, or null if the last turn doesn't end
7040
+ * in a plain assistant message.
7041
+ */
7042
+ private reopenTrailingAssistant;
7043
+ /**
7044
+ * Tear down any in-flight/pending reconnect and clear the resume handle. Runs
7045
+ * on every new turn / cancel / hydrate, so it must NOT pull the reconnect
7046
+ * chunk: it only delegates to the controller if one was already created.
7047
+ */
7048
+ private teardownReconnect;
7049
+ /** Drop the resume handle and notify the host (`onExecutionState(null)`). */
7050
+ private clearResumable;
7051
+ /**
7052
+ * Surface the resume handle to the host for persistence. Immediate on
7053
+ * create/clear; trailing-edge throttled on cursor advances so it isn't called
7054
+ * per delta.
7055
+ */
7056
+ private notifyExecutionState;
7057
+ /** The current durable resume handle, if any (read-only). */
7058
+ getResumableHandle(): ResumableHandle | null;
6838
7059
  private setStatus;
6839
7060
  private setStreaming;
6840
7061
  /**
@@ -7013,6 +7234,11 @@ type Controller = {
7013
7234
  clearChat: () => void;
7014
7235
  setMessage: (message: string) => boolean;
7015
7236
  submitMessage: (message?: string) => boolean;
7237
+ /**
7238
+ * Manually retry a dropped durable stream (e.g. from a "Reconnect" button).
7239
+ * No-op unless a resumable durable turn dropped and `reconnectStream` is set.
7240
+ */
7241
+ reconnect: () => void;
7016
7242
  startVoiceRecognition: () => boolean;
7017
7243
  stopVoiceRecognition: () => boolean;
7018
7244
  /**