@opengeni/react 1.0.1 → 2.3.0-canary.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 (174) hide show
  1. package/README.md +33 -10
  2. package/dist/artifacts-spreadsheet.js +1 -1
  3. package/dist/artifacts.js +1 -1
  4. package/dist/browser-viewer-GXUICIAG.js +14 -0
  5. package/dist/{chunk-R3AZOAB5.js → chunk-4MDLEHXL.js} +9 -115
  6. package/dist/chunk-4MDLEHXL.js.map +1 -0
  7. package/dist/chunk-5YK2ZDIG.js +116 -0
  8. package/dist/chunk-5YK2ZDIG.js.map +1 -0
  9. package/dist/chunk-H5YGYNXS.js +329 -0
  10. package/dist/chunk-H5YGYNXS.js.map +1 -0
  11. package/dist/{chunk-7WCLAEFV.js → chunk-ICA6R6TW.js} +2 -2
  12. package/dist/{chunk-7WCLAEFV.js.map → chunk-ICA6R6TW.js.map} +1 -1
  13. package/dist/{chunk-IGC6A2ZI.js → chunk-JOAVA2TW.js} +589 -217
  14. package/dist/chunk-JOAVA2TW.js.map +1 -0
  15. package/dist/{chunk-Y7OEDUJ5.js → chunk-LS4POUTY.js} +1057 -426
  16. package/dist/chunk-LS4POUTY.js.map +1 -0
  17. package/dist/{chunk-5UQPNLIO.js → chunk-LTCHJ4MS.js} +9 -9
  18. package/dist/chunk-LTCHJ4MS.js.map +1 -0
  19. package/dist/{chunk-C5ZPSSS5.js → chunk-NEXLLTP4.js} +2 -1
  20. package/dist/{chunk-C5ZPSSS5.js.map → chunk-NEXLLTP4.js.map} +1 -1
  21. package/dist/chunk-QGBJ6GHG.js +2300 -0
  22. package/dist/chunk-QGBJ6GHG.js.map +1 -0
  23. package/dist/{chunk-6KLX2RH5.js → chunk-RSXJN73N.js} +365 -21
  24. package/dist/chunk-RSXJN73N.js.map +1 -0
  25. package/dist/{chunk-B6WPZSTT.js → chunk-SE4W4WQ3.js} +78 -16
  26. package/dist/chunk-SE4W4WQ3.js.map +1 -0
  27. package/dist/{chunk-MKPIAGVL.js → chunk-W6NFJXSA.js} +8 -19
  28. package/dist/chunk-W6NFJXSA.js.map +1 -0
  29. package/dist/{chunk-XBZWOM2K.js → chunk-X4A66EWZ.js} +8 -3
  30. package/dist/chunk-X4A66EWZ.js.map +1 -0
  31. package/dist/chunk-X4JAFD5R.js +3907 -0
  32. package/dist/chunk-X4JAFD5R.js.map +1 -0
  33. package/dist/{chunk-HA46CYQK.js → chunk-XXWH2JLC.js} +792 -206
  34. package/dist/chunk-XXWH2JLC.js.map +1 -0
  35. package/dist/client.d.ts +2 -2
  36. package/dist/components/browser-viewer.d.ts +7 -1
  37. package/dist/components/computer-viewer.d.ts +4 -1
  38. package/dist/components/enrollment-consent.d.ts +6 -3
  39. package/dist/components/file-browser.d.ts +7 -1
  40. package/dist/components/human-input-form.d.ts +1 -1
  41. package/dist/components/human-input-surface.d.ts +1 -1
  42. package/dist/components/machine-card.d.ts +4 -1
  43. package/dist/components/machines/machine-detail.d.ts +4 -1
  44. package/dist/components/machines/machine-operation-policy.d.ts +11 -0
  45. package/dist/components/machines-dashboard.d.ts +3 -1
  46. package/dist/components/markdown.d.ts +20 -1
  47. package/dist/components/message-timeline.d.ts +1 -1
  48. package/dist/components/model-policy-picker.d.ts +2 -0
  49. package/dist/components/pierre-file.d.ts +1 -1
  50. package/dist/components/sandbox-files.d.ts +8 -2
  51. package/dist/components/sandbox-workspace.d.ts +33 -3
  52. package/dist/components/session-chrome.d.ts +15 -3
  53. package/dist/components/workspace-dock.d.ts +6 -2
  54. package/dist/composer.js +5 -5
  55. package/dist/computer-viewer-ZOP4Q7A7.js +16 -0
  56. package/dist/computer-viewer-ZOP4Q7A7.js.map +1 -0
  57. package/dist/hooks/channel-cache.d.ts +23 -0
  58. package/dist/hooks/shared-machines-list.d.ts +14 -0
  59. package/dist/hooks/use-attached-browsers.d.ts +2 -1
  60. package/dist/hooks/use-channels.d.ts +1 -0
  61. package/dist/hooks/use-composer.d.ts +37 -9
  62. package/dist/hooks/use-machine-chip.d.ts +3 -3
  63. package/dist/hooks/use-machines.d.ts +19 -1
  64. package/dist/hooks/use-sandbox-files.d.ts +1 -0
  65. package/dist/hooks/use-session-background-commands.d.ts +14 -0
  66. package/dist/hooks/use-turn-queue.d.ts +12 -0
  67. package/dist/hooks/use-workspace-sessions.d.ts +1 -0
  68. package/dist/index.d.ts +7 -5
  69. package/dist/index.js +1096 -623
  70. package/dist/index.js.map +1 -1
  71. package/dist/interaction.js +29 -181
  72. package/dist/interaction.js.map +1 -1
  73. package/dist/lib/interaction-errors.d.ts +3 -0
  74. package/dist/machines.d.ts +4 -2
  75. package/dist/machines.js +10 -2
  76. package/dist/model-policy.d.ts +1 -1
  77. package/dist/model-policy.js +1 -1
  78. package/dist/platform-activity-row-BJHUHXAT.js +169 -0
  79. package/dist/platform-activity-row-BJHUHXAT.js.map +1 -0
  80. package/dist/{queue-surface-implementation-2KVJTYXH.js → queue-surface-implementation-BSKUWHRI.js} +76 -16
  81. package/dist/queue-surface-implementation-BSKUWHRI.js.map +1 -0
  82. package/dist/realtime/realtime-control.d.ts +6 -3
  83. package/dist/realtime.js +36 -14
  84. package/dist/realtime.js.map +1 -1
  85. package/dist/session-ui.d.ts +1 -1
  86. package/dist/session-ui.js +6 -2
  87. package/dist/session.d.ts +2 -2
  88. package/dist/session.js +4 -4
  89. package/dist/timeline/index.d.ts +1 -1
  90. package/dist/timeline/platform-activity-row.d.ts +15 -0
  91. package/dist/timeline/screenshot-lightbox.d.ts +1 -1
  92. package/dist/timeline/shared.d.ts +4 -2
  93. package/dist/timeline/types.d.ts +50 -5
  94. package/dist/types/machines.d.ts +2 -2
  95. package/package.json +3 -3
  96. package/src/client.ts +12 -1
  97. package/src/components/artifacts/editable-spreadsheet.tsx +1 -1
  98. package/src/components/browser-viewer.tsx +361 -58
  99. package/src/components/chat-composer.tsx +1 -1
  100. package/src/components/composer.tsx +6 -6
  101. package/src/components/computer-viewer.tsx +368 -115
  102. package/src/components/enrollment-consent.tsx +64 -6
  103. package/src/components/file-browser.tsx +117 -2
  104. package/src/components/human-input-form.tsx +103 -59
  105. package/src/components/human-input-surface.tsx +1 -1
  106. package/src/components/machine-card.tsx +111 -0
  107. package/src/components/machine-input-display.ts +15 -0
  108. package/src/components/machines/machine-detail.tsx +47 -0
  109. package/src/components/machines/machine-operation-policy.tsx +179 -0
  110. package/src/components/machines-dashboard.tsx +6 -0
  111. package/src/components/markdown.tsx +174 -20
  112. package/src/components/message-timeline.tsx +215 -18
  113. package/src/components/model-policy-picker.tsx +9 -17
  114. package/src/components/pierre-file.tsx +1 -1
  115. package/src/components/queue-surface-implementation.tsx +82 -16
  116. package/src/components/sandbox-files.tsx +135 -19
  117. package/src/components/sandbox-workspace.tsx +204 -92
  118. package/src/components/session-chrome.tsx +415 -208
  119. package/src/components/session-status.tsx +1 -1
  120. package/src/components/user-message-body.tsx +168 -48
  121. package/src/components/workspace-dock.tsx +141 -175
  122. package/src/hooks/channel-cache.ts +104 -0
  123. package/src/hooks/shared-machines-list.ts +239 -0
  124. package/src/hooks/use-attached-browsers.ts +5 -1
  125. package/src/hooks/use-browser-frame-stream.ts +6 -1
  126. package/src/hooks/use-browser-session.ts +6 -4
  127. package/src/hooks/use-browser-sessions.ts +7 -1
  128. package/src/hooks/use-channels.ts +32 -10
  129. package/src/hooks/use-composer.ts +760 -291
  130. package/src/hooks/use-computer-frame-stream.ts +14 -9
  131. package/src/hooks/use-computer-session.ts +16 -14
  132. package/src/hooks/use-computer-sessions.ts +8 -1
  133. package/src/hooks/use-last-started-turn-policy.ts +1 -1
  134. package/src/hooks/use-machine-chip.ts +23 -6
  135. package/src/hooks/use-machines.ts +104 -6
  136. package/src/hooks/use-sandbox-files.ts +195 -140
  137. package/src/hooks/use-sandbox-git.ts +58 -29
  138. package/src/hooks/use-session-background-commands.ts +56 -0
  139. package/src/hooks/use-session-capabilities.ts +18 -0
  140. package/src/hooks/use-turn-queue.ts +111 -12
  141. package/src/hooks/use-workspace-sessions.ts +5 -1
  142. package/src/index.ts +26 -4
  143. package/src/lib/interaction-errors.ts +18 -0
  144. package/src/machines.ts +9 -1
  145. package/src/model-policy.ts +10 -2
  146. package/src/realtime/realtime-control.tsx +53 -17
  147. package/src/session-context.ts +1 -0
  148. package/src/session-ui.ts +6 -1
  149. package/src/session.ts +9 -1
  150. package/src/timeline/activity-rail.tsx +20 -75
  151. package/src/timeline/index.ts +4 -0
  152. package/src/timeline/platform-activity-row.tsx +201 -0
  153. package/src/timeline/projection.ts +529 -25
  154. package/src/timeline/screenshot-lightbox.tsx +48 -14
  155. package/src/timeline/shared.tsx +8 -2
  156. package/src/timeline/tool-renderers.tsx +104 -6
  157. package/src/timeline/turn-summary.tsx +6 -1
  158. package/src/timeline/types.ts +77 -2
  159. package/src/types/machines.ts +10 -0
  160. package/styles/compiled.css +115 -104
  161. package/dist/chunk-5UQPNLIO.js.map +0 -1
  162. package/dist/chunk-6KLX2RH5.js.map +0 -1
  163. package/dist/chunk-B6WPZSTT.js.map +0 -1
  164. package/dist/chunk-HA46CYQK.js.map +0 -1
  165. package/dist/chunk-IGC6A2ZI.js.map +0 -1
  166. package/dist/chunk-LLOLUHVG.js +0 -5937
  167. package/dist/chunk-LLOLUHVG.js.map +0 -1
  168. package/dist/chunk-MKPIAGVL.js.map +0 -1
  169. package/dist/chunk-R3AZOAB5.js.map +0 -1
  170. package/dist/chunk-XBZWOM2K.js.map +0 -1
  171. package/dist/chunk-Y7OEDUJ5.js.map +0 -1
  172. package/dist/interactive-workbench-viewers-TOHBXJEE.js +0 -15
  173. package/dist/queue-surface-implementation-2KVJTYXH.js.map +0 -1
  174. /package/dist/{interactive-workbench-viewers-TOHBXJEE.js.map → browser-viewer-GXUICIAG.js.map} +0 -0
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-KZ73RL76.js";
4
4
  import {
5
5
  useEmbeddedSession
6
- } from "./chunk-C5ZPSSS5.js";
6
+ } from "./chunk-NEXLLTP4.js";
7
7
 
8
8
  // src/hooks/use-composer.ts
9
9
  import {
@@ -46,6 +46,8 @@ function restoreOptimisticSendOperations(key) {
46
46
  }
47
47
  return stored.map(({ hasMcpCredentialUpdates, ...operation }) => ({
48
48
  ...operation,
49
+ delivery: "send",
50
+ destination: operation.destination === "queue" ? "queue" : "chat",
49
51
  newerShadow: operation.newerShadow ?? { text: "", resources: [], annotations: [] },
50
52
  state: operation.state === "sending" || operation.state === "queued" ? "sending" : operation.state,
51
53
  outcomeUnknown: operation.state === "sending" || operation.state === "queued" ? true : operation.outcomeUnknown,
@@ -65,6 +67,14 @@ function rememberOptimisticSendOperations(key, operations) {
65
67
  hasMcpCredentialUpdates: input.mcpCredentialUpdates !== void 0
66
68
  };
67
69
  });
70
+ if (stored.length === 0) {
71
+ optimisticSendOperations.delete(key);
72
+ try {
73
+ pendingComposerStorage()?.removeItem(optimisticSendStorageKey(key));
74
+ } catch {
75
+ }
76
+ return;
77
+ }
68
78
  optimisticSendOperations.set(key, stored);
69
79
  try {
70
80
  pendingComposerStorage()?.setItem(optimisticSendStorageKey(key), JSON.stringify(stored));
@@ -157,7 +167,8 @@ function rememberPendingComposerOperation(key, operation) {
157
167
  newerShadow: {
158
168
  text: operation.newerShadow.text,
159
169
  resources: [...operation.newerShadow.resources],
160
- annotations: cloneAnnotations(operation.newerShadow.annotations)
170
+ annotations: cloneAnnotations(operation.newerShadow.annotations),
171
+ ...operation.newerShadow.policy ? { policy: { ...operation.newerShadow.policy } } : {}
161
172
  }
162
173
  };
163
174
  pendingComposerOperations.set(key, stored);
@@ -201,6 +212,18 @@ function steeringAcceptedEvent(steering, events) {
201
212
  (event) => event.type === "user.message" && steering.clientEventId !== null && event.clientEventId === steering.clientEventId
202
213
  );
203
214
  }
215
+ function promptTurnIdForTrigger(triggerEventId, events) {
216
+ if (!triggerEventId) return null;
217
+ const queued = events.find((event) => {
218
+ if (event.type !== "turn.queued") return false;
219
+ const payload2 = event.payload;
220
+ return typeof payload2 === "object" && payload2 !== null && "triggerEventId" in payload2 && payload2.triggerEventId === triggerEventId;
221
+ });
222
+ if (!queued) return null;
223
+ if (queued.turnId) return queued.turnId;
224
+ const payload = queued.payload;
225
+ return typeof payload === "object" && payload !== null && "turnId" in payload && typeof payload.turnId === "string" ? payload.turnId : null;
226
+ }
204
227
  function steeringSettledByEvents(steering, events) {
205
228
  const acceptedEventId = steering.triggerEventId ?? steeringAcceptedEvent(steering, events)?.id ?? null;
206
229
  return events.some((event) => {
@@ -212,9 +235,53 @@ function steeringSettledByEvents(steering, events) {
212
235
  return typeof payload === "object" && payload !== null && "triggerEventId" in payload && payload.triggerEventId === acceptedEventId;
213
236
  });
214
237
  }
238
+ function reconcileOptimisticSendFromEvents(operation, events) {
239
+ const accepted = events.find(
240
+ (event) => event.type === "user.message" && event.clientEventId === operation.clientEventId
241
+ );
242
+ const triggerEventId = operation.triggerEventId ?? accepted?.id ?? null;
243
+ const turnId = operation.turnId ?? promptTurnIdForTrigger(triggerEventId, events);
244
+ const needsAdmissionUpdate = accepted !== void 0 && (operation.state !== "queued" || operation.triggerEventId !== triggerEventId || operation.turnId !== turnId || operation.error !== void 0 || operation.outcomeUnknown !== false);
245
+ const reconciled = needsAdmissionUpdate ? {
246
+ ...operation,
247
+ state: "queued",
248
+ triggerEventId,
249
+ turnId,
250
+ error: void 0,
251
+ outcomeUnknown: false
252
+ } : operation;
253
+ const withdrawnFromQueue = events.some((event) => {
254
+ if (event.type !== "session.queue.changed" || !turnId) return false;
255
+ const payload = event.payload;
256
+ if (typeof payload !== "object" || payload === null || Array.isArray(payload)) return false;
257
+ const record = payload;
258
+ return record.turnId === turnId && (record.operation === "edit" || record.operation === "delete");
259
+ });
260
+ return {
261
+ operation: reconciled,
262
+ admitted: accepted !== void 0,
263
+ // Edit/Delete atomically withdraw the queued turn. Keeping its optimistic
264
+ // send alive after that point lets its old `newerShadow` become the newest
265
+ // composer draft again when a later turn settles, duplicating submitted
266
+ // text. The queue mutation is the durable terminal fact for this send.
267
+ settled: withdrawnFromQueue || steeringSettledByEvents(
268
+ {
269
+ phase: "accepted",
270
+ text: reconciled.text,
271
+ clientEventId: reconciled.clientEventId,
272
+ triggerEventId: reconciled.triggerEventId ?? null,
273
+ turnId: reconciled.turnId ?? null
274
+ },
275
+ events
276
+ )
277
+ };
278
+ }
215
279
  function useComposer(sessionId, options = {}) {
216
280
  const { client, workspaceId, registerSessionReconciler } = useEmbeddedSession(options);
217
281
  const durableDrafts = options.draftPersistence !== "disabled";
282
+ if (!durableDrafts && !options.initialPolicy) {
283
+ throw new Error("useComposer requires initialPolicy when draft persistence is disabled");
284
+ }
218
285
  const targetKey = `${workspaceId}\0${sessionId ?? ""}\0${durableDrafts ? "durable" : "disabled"}`;
219
286
  const pendingOperationKey = pendingComposerOperationKey(workspaceId, sessionId);
220
287
  const initialPendingOperation = restorePendingComposerOperation(pendingOperationKey);
@@ -230,21 +297,26 @@ function useComposer(sessionId, options = {}) {
230
297
  const [optimisticSends, setOptimisticSends] = useState(initialOptimisticSends);
231
298
  const [steering, setSteering] = useState(
232
299
  () => initialPendingOperation?.delivery === "steer" ? {
233
- phase: "submitting",
300
+ phase: "failed",
234
301
  text: initialPendingOperation.input.text,
235
302
  clientEventId: initialPendingOperation.input.clientEventId ?? null,
236
303
  triggerEventId: null,
237
- turnId: null
304
+ turnId: null,
305
+ error: "Not confirmed. Retry to check the same direction change.",
306
+ outcomeUnknown: true
238
307
  } : null
239
308
  );
240
309
  const [pausing, setPausing] = useState(false);
241
- const [controlStopping, setControlStopping] = useState(null);
310
+ const [acceptedControl, setAcceptedControl] = useState(null);
242
311
  const [resuming, setResuming] = useState(false);
243
312
  const [error, setError] = useState(null);
244
313
  const [draft, setDraft] = useState(null);
245
314
  const [draftLoading, setDraftLoading] = useState(Boolean(sessionId) && durableDrafts);
246
315
  const [draftSaving, setDraftSaving] = useState(false);
247
316
  const [draftConflict, setDraftConflict] = useState(null);
317
+ const [policy, setPolicy] = useState(
318
+ () => initialShadow?.policy ?? options.initialPolicy ?? null
319
+ );
248
320
  const [restoredResources, setRestoredResources] = useState(
249
321
  () => initialShadow?.resources ?? []
250
322
  );
@@ -254,11 +326,15 @@ function useComposer(sessionId, options = {}) {
254
326
  const optimisticCallbackIdsRef = useRef(/* @__PURE__ */ new Set());
255
327
  const steeringSettlementEventsRef = useRef([]);
256
328
  const steeringRef = useRef(steering);
329
+ const steeringOccurredAtRef = useRef((/* @__PURE__ */ new Date()).toISOString());
257
330
  const pendingClientEventId = useRef(
258
331
  initialPendingOperation?.input.clientEventId ?? null
259
332
  );
260
333
  const valueRef = useRef(initialShadow?.text ?? "");
261
334
  const annotationsRef = useRef(initialShadow?.annotations ?? []);
335
+ const policyRef = useRef(
336
+ initialShadow?.policy ?? options.initialPolicy ?? null
337
+ );
262
338
  const draftRef = useRef(null);
263
339
  const restoredResourcesRef = useRef(initialShadow?.resources ?? []);
264
340
  const localEditRevision = useRef(initialShadow ? 1 : 0);
@@ -268,18 +344,21 @@ function useComposer(sessionId, options = {}) {
268
344
  const saveChain = useRef(Promise.resolve());
269
345
  const onSent = options.onSent;
270
346
  const onSubmitted = options.onSubmitted;
271
- const onDraftApplied = options.onDraftApplied;
272
- const onDraftAppliedRef = useRef(onDraftApplied);
273
- useLayoutEffect(() => {
274
- onDraftAppliedRef.current = onDraftApplied;
275
- }, [onDraftApplied]);
347
+ const onDeliveryErrorRef = useRef(options.onDeliveryError);
348
+ onDeliveryErrorRef.current = options.onDeliveryError;
276
349
  useLayoutEffect(() => {
277
350
  steeringRef.current = steering;
278
351
  }, [steering]);
279
352
  const sendExtrasRef = useRef(options.sendExtras);
280
353
  sendExtrasRef.current = options.sendExtras;
354
+ const sendDestinationRef = useRef(options.sendDestination);
355
+ sendDestinationRef.current = options.sendDestination;
281
356
  const sendBlockedRef = useRef(options.sendBlocked);
282
357
  sendBlockedRef.current = options.sendBlocked;
358
+ const effectiveControlRef = useRef(
359
+ acceptedControl ?? options.effectiveControl ?? null
360
+ );
361
+ effectiveControlRef.current = acceptedControl ?? options.effectiveControl ?? null;
283
362
  const liveExtrasVersion = JSON.stringify(resolveSendExtras(options.sendExtras));
284
363
  const targetKeyRef = useRef(targetKey);
285
364
  useLayoutEffect(() => {
@@ -298,6 +377,7 @@ function useComposer(sessionId, options = {}) {
298
377
  localEditRevision.current = shadow ? 1 : 0;
299
378
  valueRef.current = shadow?.text ?? "";
300
379
  annotationsRef.current = shadow?.annotations ?? [];
380
+ policyRef.current = shadow?.policy ?? options.initialPolicy ?? null;
301
381
  draftRef.current = null;
302
382
  restoredResourcesRef.current = shadow?.resources ?? [];
303
383
  lastSavedSignature.current = null;
@@ -305,20 +385,23 @@ function useComposer(sessionId, options = {}) {
305
385
  setStateTargetKey(targetKey);
306
386
  setValue(shadow?.text ?? "");
307
387
  setAnnotations(shadow?.annotations ?? []);
388
+ setPolicy(policyRef.current);
308
389
  setAnnotationReviewTargetId(null);
309
390
  setSending(false);
310
391
  setOptimisticSends(restoredOptimisticSends);
311
392
  setSteering(
312
393
  pendingOperationRef.current?.delivery === "steer" ? {
313
- phase: "submitting",
394
+ phase: "failed",
314
395
  text: pendingOperationRef.current.input.text,
315
396
  clientEventId: pendingOperationRef.current.input.clientEventId ?? null,
316
397
  triggerEventId: null,
317
- turnId: null
398
+ turnId: null,
399
+ error: "Not confirmed. Retry to check the same direction change.",
400
+ outcomeUnknown: true
318
401
  } : null
319
402
  );
320
403
  setPausing(false);
321
- setControlStopping(null);
404
+ setAcceptedControl(null);
322
405
  setResuming(false);
323
406
  setError(null);
324
407
  setDraft(null);
@@ -326,7 +409,7 @@ function useComposer(sessionId, options = {}) {
326
409
  setDraftSaving(false);
327
410
  setDraftConflict(null);
328
411
  setRestoredResources(shadow?.resources ?? []);
329
- }, [durableDrafts, pendingOperationKey, sessionId, targetKey]);
412
+ }, [durableDrafts, options.initialPolicy, pendingOperationKey, sessionId, targetKey]);
330
413
  const setOptimisticDraftShadow = useCallback(
331
414
  (shadow) => {
332
415
  if (optimisticSendsRef.current.length === 0) return;
@@ -335,7 +418,8 @@ function useComposer(sessionId, options = {}) {
335
418
  newerShadow: {
336
419
  text: shadow.text,
337
420
  resources: [...shadow.resources],
338
- annotations: cloneAnnotations(shadow.annotations)
421
+ annotations: cloneAnnotations(shadow.annotations),
422
+ ...shadow.policy ? { policy: { ...shadow.policy } } : {}
339
423
  }
340
424
  }));
341
425
  optimisticSendsRef.current = next;
@@ -348,15 +432,18 @@ function useComposer(sessionId, options = {}) {
348
432
  (next) => {
349
433
  if (targetKeyRef.current !== targetKey) return;
350
434
  if (!durableDrafts) {
435
+ const nextPolicy2 = policyFromDraft(next);
351
436
  localEditRevision.current += 1;
352
437
  valueRef.current = next.text;
353
438
  annotationsRef.current = next.annotations ?? [];
439
+ policyRef.current = nextPolicy2;
354
440
  restoredResourcesRef.current = next.resources;
355
441
  draftRef.current = null;
356
442
  lastSavedSignature.current = null;
357
443
  setDraft(null);
358
444
  setValue(next.text);
359
445
  setAnnotations(next.annotations ?? []);
446
+ setPolicy(nextPolicy2);
360
447
  setRestoredResources(next.resources);
361
448
  pendingOperationRef.current = updatePendingComposerShadow(
362
449
  pendingOperationKey,
@@ -367,7 +454,8 @@ function useComposer(sessionId, options = {}) {
367
454
  next.resources,
368
455
  resolveSendExtras(sendExtrasRef.current).resources ?? []
369
456
  ),
370
- annotations: next.annotations ?? []
457
+ annotations: next.annotations ?? [],
458
+ policy: nextPolicy2
371
459
  }
372
460
  );
373
461
  setOptimisticDraftShadow({
@@ -376,13 +464,16 @@ function useComposer(sessionId, options = {}) {
376
464
  next.resources,
377
465
  resolveSendExtras(sendExtrasRef.current).resources ?? []
378
466
  ),
379
- annotations: next.annotations ?? []
467
+ annotations: next.annotations ?? [],
468
+ policy: nextPolicy2
380
469
  });
381
470
  setDraftConflict(null);
382
471
  return;
383
472
  }
473
+ const nextPolicy = policyFromDraft(next);
384
474
  valueRef.current = next.text;
385
475
  annotationsRef.current = next.annotations ?? [];
476
+ policyRef.current = nextPolicy;
386
477
  draftRef.current = next;
387
478
  restoredResourcesRef.current = next.resources;
388
479
  lastSavedSignature.current = draftSignature(draftPayload(next));
@@ -390,6 +481,7 @@ function useComposer(sessionId, options = {}) {
390
481
  setDraft(next);
391
482
  setValue(next.text);
392
483
  setAnnotations(next.annotations ?? []);
484
+ setPolicy(nextPolicy);
393
485
  setRestoredResources(next.resources);
394
486
  pendingOperationRef.current = updatePendingComposerShadow(
395
487
  pendingOperationKey,
@@ -400,7 +492,8 @@ function useComposer(sessionId, options = {}) {
400
492
  next.resources,
401
493
  resolveSendExtras(sendExtrasRef.current).resources ?? []
402
494
  ),
403
- annotations: next.annotations ?? []
495
+ annotations: next.annotations ?? [],
496
+ policy: nextPolicy
404
497
  }
405
498
  );
406
499
  setOptimisticDraftShadow({
@@ -409,10 +502,10 @@ function useComposer(sessionId, options = {}) {
409
502
  next.resources,
410
503
  resolveSendExtras(sendExtrasRef.current).resources ?? []
411
504
  ),
412
- annotations: next.annotations ?? []
505
+ annotations: next.annotations ?? [],
506
+ policy: nextPolicy
413
507
  });
414
508
  setDraftConflict(null);
415
- onDraftAppliedRef.current?.(next);
416
509
  },
417
510
  [durableDrafts, pendingOperationKey, setOptimisticDraftShadow, targetKey]
418
511
  );
@@ -427,16 +520,17 @@ function useComposer(sessionId, options = {}) {
427
520
  const readTicket = ++draftReadGeneration.current;
428
521
  const localAtStart = localEditRevision.current;
429
522
  const baseAtStart = draftRef.current;
430
- const extrasAtStart = resolveSendExtras(sendExtrasRef.current);
431
- const localSignatureAtStart = baseAtStart ? draftSignature(
523
+ const policyAtStart = policyRef.current;
524
+ const localSignatureAtStart = baseAtStart ? policyAtStart ? draftSignature(
432
525
  composerDraftPayload(
433
526
  baseAtStart,
434
527
  valueRef.current,
435
528
  restoredResourcesRef.current,
436
529
  annotationsRef.current,
437
- extrasAtStart
530
+ policyAtStart,
531
+ resolveSendExtras(sendExtrasRef.current).resources ?? []
438
532
  )
439
- ) : null;
533
+ ) : null : null;
440
534
  const localWasDirtyAtStart = localSignatureAtStart === null ? localAtStart !== 0 : localSignatureAtStart !== lastSavedSignature.current;
441
535
  const showLoading = replaceLocal || draftRef.current === null;
442
536
  if (showLoading) {
@@ -457,22 +551,29 @@ function useComposer(sessionId, options = {}) {
457
551
  valueRef.current = shadow.text;
458
552
  restoredResourcesRef.current = shadow.resources;
459
553
  annotationsRef.current = shadow.annotations;
554
+ if (shadow.policy) policyRef.current = shadow.policy;
460
555
  localEditRevision.current ||= 1;
461
556
  setValue(shadow.text);
462
557
  setRestoredResources(shadow.resources);
463
558
  setAnnotations(shadow.annotations);
559
+ if (shadow.policy) setPolicy(shadow.policy);
464
560
  } else if (replaceLocal || !localWasDirtyAtStart && localAtStart === localEditRevision.current) {
465
- const extrasNow = resolveSendExtras(sendExtrasRef.current);
466
- const pickerChangedDuringFetch = extrasNow.model !== extrasAtStart.model || extrasNow.reasoningEffort !== extrasAtStart.reasoningEffort || extrasNow.latencyMode !== extrasAtStart.latencyMode;
561
+ const fetchedPolicy = policyFromDraft(fetched);
467
562
  valueRef.current = fetched.text;
468
563
  restoredResourcesRef.current = fetched.resources;
469
564
  annotationsRef.current = fetched.annotations ?? [];
565
+ policyRef.current = fetchedPolicy;
470
566
  lastSavedSignature.current = draftSignature(draftPayload(fetched));
471
567
  setValue(fetched.text);
472
568
  setRestoredResources(fetched.resources);
473
569
  setAnnotations(fetched.annotations ?? []);
474
- if (replaceLocal || !pickerChangedDuringFetch) {
475
- onDraftAppliedRef.current?.(fetched);
570
+ setPolicy(fetchedPolicy);
571
+ } else {
572
+ lastSavedSignature.current = draftSignature(draftPayload(fetched));
573
+ if (!policyRef.current) {
574
+ const fetchedPolicy = policyFromDraft(fetched);
575
+ policyRef.current = fetchedPolicy;
576
+ setPolicy(fetchedPolicy);
476
577
  }
477
578
  }
478
579
  }
@@ -580,23 +681,38 @@ function useComposer(sessionId, options = {}) {
580
681
  setSteering(null);
581
682
  }, [options.events, steering]);
582
683
  useEffect(() => {
583
- if (!controlStopping) return;
684
+ if (!acceptedControl) return;
584
685
  const effectiveControl = options.effectiveControl;
585
- if (!effectiveControl || effectiveControl.controlVersion < controlStopping.controlVersion) {
686
+ if (!effectiveControl || effectiveControl.controlVersion < acceptedControl.controlVersion) {
586
687
  return;
587
688
  }
588
- if (effectiveControl.controlVersion === controlStopping.controlVersion && effectiveControl.controlEtag === controlStopping.controlEtag && effectiveControl.settlement !== null) {
589
- return;
590
- }
591
- setControlStopping((current) => current === controlStopping ? null : current);
592
- }, [controlStopping, options.effectiveControl]);
689
+ setAcceptedControl((current) => current === acceptedControl ? null : current);
690
+ }, [acceptedControl, options.effectiveControl]);
593
691
  const currentDraftPayload = useCallback(() => {
594
692
  if (!durableDrafts || targetKeyRef.current !== targetKey) return null;
595
693
  const base = draftRef.current;
596
- if (!base) return null;
597
- const extras = resolveSendExtras(sendExtrasRef.current);
598
- return composerDraftPayload(base, value, restoredResources, annotations, extras);
694
+ const currentPolicy = policyRef.current;
695
+ if (!base || !currentPolicy) return null;
696
+ return composerDraftPayload(
697
+ base,
698
+ value,
699
+ restoredResources,
700
+ annotations,
701
+ currentPolicy,
702
+ resolveSendExtras(sendExtrasRef.current).resources ?? []
703
+ );
599
704
  }, [annotations, durableDrafts, restoredResources, targetKey, value]);
705
+ const adoptDraftBase = useCallback(
706
+ (next) => {
707
+ if (targetKeyRef.current !== targetKey) return;
708
+ draftRef.current = next;
709
+ setDraft(next);
710
+ lastSavedSignature.current = draftSignature(draftPayload(next));
711
+ setDraftConflict(null);
712
+ setError(null);
713
+ },
714
+ [targetKey]
715
+ );
600
716
  const persistPayload = useCallback(
601
717
  async (payload) => {
602
718
  const ownedTargetKey = targetKey;
@@ -619,27 +735,11 @@ function useComposer(sessionId, options = {}) {
619
735
  }
620
736
  setDraftSaving(true);
621
737
  try {
622
- const saved = await saveComposerDraftWithStaleRetry({
623
- client,
624
- workspaceId,
625
- sessionId,
626
- request,
627
- onAdoptRemote: (remote) => {
628
- draftRef.current = remote;
629
- setDraft(remote);
630
- }
631
- });
738
+ const saved = await client.saveComposerDraft(workspaceId, sessionId, request);
632
739
  if (targetKeyRef.current !== ownedTargetKey || targetGeneration.current !== ownedGeneration) {
633
740
  return;
634
741
  }
635
- draftRef.current = saved;
636
- setDraft(saved);
637
- lastSavedSignature.current = draftSignature({
638
- ...request,
639
- expectedRevision: saved.revision
640
- });
641
- setDraftConflict(null);
642
- setError(null);
742
+ adoptDraftBase(saved);
643
743
  success = true;
644
744
  } catch (cause) {
645
745
  if (targetKeyRef.current === ownedTargetKey && targetGeneration.current === ownedGeneration) {
@@ -657,7 +757,7 @@ function useComposer(sessionId, options = {}) {
657
757
  await saveChain.current;
658
758
  return success;
659
759
  },
660
- [client, durableDrafts, sessionId, targetKey, workspaceId]
760
+ [adoptDraftBase, client, durableDrafts, sessionId, targetKey, workspaceId]
661
761
  );
662
762
  const replaceOptimisticSends = useCallback(
663
763
  (update) => {
@@ -682,15 +782,40 @@ function useComposer(sessionId, options = {}) {
682
782
  [onSent, replaceOptimisticSends]
683
783
  );
684
784
  useEffect(() => {
685
- const acceptedIds = new Set(
686
- (options.events ?? []).filter((event) => event.type === "user.message" && event.clientEventId).map((event) => event.clientEventId)
785
+ const events = options.events ?? [];
786
+ if (events.length === 0 || optimisticSendsRef.current.length === 0) return;
787
+ const reconciled = optimisticSendsRef.current.map(
788
+ (operation) => reconcileOptimisticSendFromEvents(operation, events)
789
+ );
790
+ const settledIds = new Set(
791
+ reconciled.filter((item) => item.settled).map((item) => item.operation.clientEventId)
687
792
  );
688
- if (acceptedIds.size === 0) return;
689
- const accepted = optimisticSendsRef.current.filter(
690
- (operation) => acceptedIds.has(operation.clientEventId)
793
+ const changed = reconciled.some(
794
+ (item, index) => item.operation !== optimisticSendsRef.current[index] && !settledIds.has(item.operation.clientEventId)
691
795
  );
692
- for (const operation of accepted) markOptimisticAccepted(operation);
693
- }, [markOptimisticAccepted, options.events]);
796
+ for (const item of reconciled) {
797
+ if (item.admitted && !optimisticCallbackIdsRef.current.has(item.operation.clientEventId)) {
798
+ optimisticCallbackIdsRef.current.add(item.operation.clientEventId);
799
+ onSent?.(item.operation.input.text, item.operation.input);
800
+ }
801
+ if (item.settled) markOptimisticAccepted(item.operation);
802
+ }
803
+ if (changed) {
804
+ replaceOptimisticSends(
805
+ (current) => current.map(
806
+ (operation) => reconciled.find((item) => item.operation.clientEventId === operation.clientEventId)?.operation ?? operation
807
+ ).filter((operation) => !settledIds.has(operation.clientEventId))
808
+ );
809
+ }
810
+ if (durableDrafts && reconciled.some((item) => item.admitted)) void loadDraft(false);
811
+ }, [
812
+ durableDrafts,
813
+ loadDraft,
814
+ markOptimisticAccepted,
815
+ onSent,
816
+ options.events,
817
+ replaceOptimisticSends
818
+ ]);
694
819
  const processOptimisticSends = useCallback(() => {
695
820
  if (!sessionId || optimisticProcessorBusyRef.current) return;
696
821
  const currentOperations = optimisticSendsRef.current;
@@ -703,17 +828,59 @@ function useComposer(sessionId, options = {}) {
703
828
  const ownedGeneration = targetGeneration.current;
704
829
  optimisticProcessorBusyRef.current = true;
705
830
  void (async () => {
831
+ let mutationFailureObserved = false;
832
+ const markFailed = (problem, outcomeUnknown) => {
833
+ if (targetKeyRef.current !== ownedTargetKey || targetGeneration.current !== ownedGeneration) {
834
+ return;
835
+ }
836
+ replaceOptimisticSends(
837
+ (current) => current.map(
838
+ (candidate) => candidate.clientEventId === operation.clientEventId ? {
839
+ ...candidate,
840
+ state: "failed",
841
+ error: problem.message,
842
+ outcomeUnknown
843
+ } : candidate
844
+ )
845
+ );
846
+ };
706
847
  try {
707
848
  if (operation.outcomeUnknown) {
708
- const events = await client.listEvents(workspaceId, sessionId, {
709
- includeTypes: ["user.message"],
710
- limit: 100,
711
- payloadMode: "none"
712
- });
713
- if (events.some(
714
- (event) => event.type === "user.message" && event.clientEventId === operation.clientEventId
715
- )) {
716
- markOptimisticAccepted(operation);
849
+ let events;
850
+ try {
851
+ events = await client.listEvents(workspaceId, sessionId, {
852
+ includeTypes: [
853
+ "user.message",
854
+ "turn.queued",
855
+ "turn.started",
856
+ "turn.completed",
857
+ "turn.failed",
858
+ "turn.cancelled",
859
+ "turn.superseded"
860
+ ],
861
+ limit: 250,
862
+ payloadMode: "full"
863
+ });
864
+ } catch (cause) {
865
+ markFailed(asError(cause), true);
866
+ return;
867
+ }
868
+ const reconciled = reconcileOptimisticSendFromEvents(operation, events);
869
+ if (reconciled.admitted) {
870
+ if (durableDrafts) await loadDraft(false);
871
+ if (reconciled.settled) {
872
+ markOptimisticAccepted(reconciled.operation);
873
+ } else {
874
+ replaceOptimisticSends(
875
+ (current) => current.map(
876
+ (candidate) => candidate.clientEventId === operation.clientEventId ? reconciled.operation : candidate
877
+ )
878
+ );
879
+ if (!optimisticCallbackIdsRef.current.has(operation.clientEventId)) {
880
+ optimisticCallbackIdsRef.current.add(operation.clientEventId);
881
+ onSent?.(reconciled.operation.input.text, reconciled.operation.input);
882
+ }
883
+ }
717
884
  return;
718
885
  }
719
886
  if (!operation.canRetry) {
@@ -736,16 +903,58 @@ function useComposer(sessionId, options = {}) {
736
903
  ...operation.input,
737
904
  ...expectedDraftRevision !== void 0 ? { expectedDraftRevision } : {}
738
905
  };
739
- await client.sendMessage(workspaceId, sessionId, wireInput);
906
+ let acceptedResult = null;
907
+ let acceptedEvent = null;
908
+ if (durableDrafts) {
909
+ if (!operation.draftPayload || expectedDraftRevision === void 0) {
910
+ throw new Error("The durable composer draft is not ready for delivery.");
911
+ }
912
+ const result = await client.submitComposerDraft(workspaceId, sessionId, {
913
+ text: operation.draftPayload.text,
914
+ annotations: operation.draftPayload.annotations,
915
+ resources: operation.draftPayload.resources,
916
+ model: operation.draftPayload.model,
917
+ reasoningEffort: operation.draftPayload.reasoningEffort,
918
+ latencyMode: operation.draftPayload.latencyMode,
919
+ expectedDraftRevision,
920
+ clientEventId: operation.clientEventId,
921
+ delivery: "send",
922
+ ...wireInput.controlEtag ? { controlEtag: wireInput.controlEtag } : {},
923
+ ...wireInput.modelContext ? { modelContext: wireInput.modelContext } : {},
924
+ ...wireInput.mcpCredentialUpdates ? { mcpCredentialUpdates: wireInput.mcpCredentialUpdates } : {},
925
+ ...wireInput.connectionAuthorities ? { connectionAuthorities: wireInput.connectionAuthorities } : {},
926
+ ...wireInput.personalResourceAttachment ? { personalResourceAttachment: wireInput.personalResourceAttachment } : {}
927
+ }).catch((cause) => {
928
+ mutationFailureObserved = true;
929
+ throw cause;
930
+ });
931
+ acceptedResult = result;
932
+ adoptDraftBase(result.draft);
933
+ } else {
934
+ acceptedEvent = await client.sendMessage(workspaceId, sessionId, wireInput).catch((cause) => {
935
+ mutationFailureObserved = true;
936
+ throw cause;
937
+ });
938
+ }
740
939
  if (targetKeyRef.current !== ownedTargetKey || targetGeneration.current !== ownedGeneration) {
741
940
  return;
742
941
  }
743
- if (options.events === void 0) {
942
+ const acceptedDestination = acceptedResult?.routing === "queued_for_execution" ? "queue" : acceptedResult?.routing === "accepted_for_execution" ? "chat" : operation.destination;
943
+ if (options.events === void 0 && acceptedDestination === "chat") {
744
944
  markOptimisticAccepted({ ...operation, input: wireInput });
745
945
  } else {
746
946
  replaceOptimisticSends(
747
947
  (current) => current.map(
748
- (candidate) => candidate.clientEventId === operation.clientEventId ? { ...candidate, input: wireInput, state: "queued", error: void 0 } : candidate
948
+ (candidate) => candidate.clientEventId === operation.clientEventId ? {
949
+ ...candidate,
950
+ input: wireInput,
951
+ destination: acceptedDestination,
952
+ turnId: acceptedResult?.turn.id ?? candidate.turnId,
953
+ triggerEventId: acceptedResult?.accepted.id ?? acceptedEvent?.id ?? candidate.triggerEventId,
954
+ appliedQueueVersion: acceptedResult?.receipt?.appliedQueueVersion ?? candidate.appliedQueueVersion,
955
+ state: "queued",
956
+ error: void 0
957
+ } : candidate
749
958
  )
750
959
  );
751
960
  }
@@ -753,36 +962,17 @@ function useComposer(sessionId, options = {}) {
753
962
  optimisticCallbackIdsRef.current.add(operation.clientEventId);
754
963
  onSent?.(wireInput.text, wireInput);
755
964
  }
756
- if (durableDrafts && draftRef.current) {
757
- const cleared = {
758
- ...draftRef.current,
759
- revision: 0,
760
- text: "",
761
- resources: [],
762
- annotations: [],
763
- sourceTurnId: null,
764
- sourceTurnVersion: null,
765
- updatedAt: null
766
- };
767
- draftRef.current = cleared;
768
- setDraft(cleared);
769
- lastSavedSignature.current = draftSignature(draftPayload(cleared));
770
- }
771
965
  } catch (cause) {
772
- const problem = asError(cause);
773
- const outcomeUnknown = isOutcomeUnknownError(cause) || operation.outcomeUnknown === true;
774
- if (targetKeyRef.current === ownedTargetKey && targetGeneration.current === ownedGeneration) {
775
- replaceOptimisticSends(
776
- (current) => current.map(
777
- (candidate) => candidate.clientEventId === operation.clientEventId ? {
778
- ...candidate,
779
- state: "failed",
780
- error: problem.message,
781
- outcomeUnknown
782
- } : candidate
783
- )
784
- );
966
+ const current = optimisticSendsRef.current.find(
967
+ (candidate) => candidate.clientEventId === operation.clientEventId
968
+ );
969
+ if (!current || current.state === "queued" && current.outcomeUnknown === false) {
970
+ return;
785
971
  }
972
+ const problem = asError(cause);
973
+ onDeliveryErrorRef.current?.(problem, operation.input, "send");
974
+ const outcomeUnknown = operation.outcomeUnknown && !mutationFailureObserved ? true : isOutcomeUnknownError(cause);
975
+ markFailed(problem, outcomeUnknown);
786
976
  } finally {
787
977
  optimisticProcessorBusyRef.current = false;
788
978
  if (targetKeyRef.current === ownedTargetKey) {
@@ -791,9 +981,11 @@ function useComposer(sessionId, options = {}) {
791
981
  }
792
982
  })();
793
983
  }, [
984
+ adoptDraftBase,
794
985
  client,
795
986
  durableDrafts,
796
987
  markOptimisticAccepted,
988
+ loadDraft,
797
989
  onSent,
798
990
  options.events,
799
991
  persistPayload,
@@ -814,9 +1006,10 @@ function useComposer(sessionId, options = {}) {
814
1006
  restoredResourcesRef.current,
815
1007
  resolveSendExtras(sendExtrasRef.current).resources ?? []
816
1008
  ),
817
- annotations: annotationsRef.current
1009
+ annotations: annotationsRef.current,
1010
+ ...policyRef.current ? { policy: policyRef.current } : {}
818
1011
  };
819
- if (pending.newerShadow.text !== shadow.text || JSON.stringify(pending.newerShadow.resources) !== JSON.stringify(shadow.resources) || JSON.stringify(pending.newerShadow.annotations) !== JSON.stringify(shadow.annotations)) {
1012
+ if (pending.newerShadow.text !== shadow.text || JSON.stringify(pending.newerShadow.resources) !== JSON.stringify(shadow.resources) || JSON.stringify(pending.newerShadow.annotations) !== JSON.stringify(shadow.annotations) || JSON.stringify(pending.newerShadow.policy) !== JSON.stringify(shadow.policy)) {
820
1013
  pendingOperationRef.current = updatePendingComposerShadow(
821
1014
  pendingOperationKey,
822
1015
  pending,
@@ -833,9 +1026,10 @@ function useComposer(sessionId, options = {}) {
833
1026
  restoredResourcesRef.current,
834
1027
  resolveSendExtras(sendExtrasRef.current).resources ?? []
835
1028
  ),
836
- annotations: annotationsRef.current
1029
+ annotations: annotationsRef.current,
1030
+ ...policyRef.current ? { policy: policyRef.current } : {}
837
1031
  };
838
- if (optimistic.newerShadow.text !== shadow.text || JSON.stringify(optimistic.newerShadow.resources) !== JSON.stringify(shadow.resources) || JSON.stringify(optimistic.newerShadow.annotations) !== JSON.stringify(shadow.annotations)) {
1032
+ if (optimistic.newerShadow.text !== shadow.text || JSON.stringify(optimistic.newerShadow.resources) !== JSON.stringify(shadow.resources) || JSON.stringify(optimistic.newerShadow.annotations) !== JSON.stringify(shadow.annotations) || JSON.stringify(optimistic.newerShadow.policy) !== JSON.stringify(shadow.policy)) {
839
1033
  setOptimisticDraftShadow(shadow);
840
1034
  }
841
1035
  }
@@ -875,12 +1069,13 @@ function useComposer(sessionId, options = {}) {
875
1069
  const rawText = explicit ?? draftAtSend;
876
1070
  const hasText = rawText.trim().length > 0;
877
1071
  const hasAnnotations = annotationsAtSend.length > 0;
1072
+ const currentPolicy = policyRef.current;
878
1073
  const annotationsComplete2 = annotationsAtSend.every(
879
1074
  (annotation) => annotation.note.trim().length > 0
880
1075
  );
881
1076
  const extras = pending ? {} : resolveSendExtras(sendExtrasRef.current);
882
1077
  const hasResources = restoredResources.length > 0 || (extras.resources?.length ?? 0) > 0;
883
- if (!pending && (!annotationsComplete2 || !hasText && !hasResources && !hasAnnotations) || !sessionId || sending || sendBlockedRef.current?.() === true || targetKeyRef.current !== ownedTargetKey) {
1078
+ if (!pending && (!annotationsComplete2 || !hasText && !hasResources && !hasAnnotations) || !sessionId || !currentPolicy || sending || sendBlockedRef.current?.() === true || targetKeyRef.current !== ownedTargetKey) {
884
1079
  return false;
885
1080
  }
886
1081
  const clearPending = () => {
@@ -894,22 +1089,6 @@ function useComposer(sessionId, options = {}) {
894
1089
  const draftWasUnchanged = valueRef.current === operation.draftAtSend;
895
1090
  const resourcesWereUnchanged = JSON.stringify(restoredResourcesRef.current) === JSON.stringify(operation.resourcesAtSend);
896
1091
  const annotationsWereUnchanged = JSON.stringify(annotationsRef.current) === JSON.stringify(operation.annotationsAtSend);
897
- const previousDraft = draftRef.current;
898
- if (previousDraft) {
899
- const cleared = {
900
- ...previousDraft,
901
- revision: 0,
902
- text: "",
903
- resources: [],
904
- annotations: [],
905
- sourceTurnId: null,
906
- sourceTurnVersion: null,
907
- updatedAt: null
908
- };
909
- draftRef.current = cleared;
910
- setDraft(cleared);
911
- lastSavedSignature.current = draftSignature(draftPayload(cleared));
912
- }
913
1092
  if (resourcesWereUnchanged) {
914
1093
  restoredResourcesRef.current = [];
915
1094
  setRestoredResources([]);
@@ -926,6 +1105,30 @@ function useComposer(sessionId, options = {}) {
926
1105
  onSent?.(operation.input.text, operation.input);
927
1106
  };
928
1107
  const deliver = async (operation) => {
1108
+ if (durableDrafts) {
1109
+ const input = operation.input;
1110
+ if (input.expectedDraftRevision === void 0 || input.clientEventId === void 0 || input.model === void 0 || input.reasoningEffort === void 0 || input.latencyMode === void 0) {
1111
+ throw new Error("The durable composer draft is not ready for delivery.");
1112
+ }
1113
+ const result = await client.submitComposerDraft(workspaceId, sessionId, {
1114
+ text: input.text,
1115
+ annotations: input.annotations ?? [],
1116
+ resources: input.resources ?? [],
1117
+ model: input.model,
1118
+ reasoningEffort: input.reasoningEffort,
1119
+ latencyMode: input.latencyMode,
1120
+ expectedDraftRevision: input.expectedDraftRevision,
1121
+ clientEventId: input.clientEventId,
1122
+ delivery: operation.delivery,
1123
+ ...input.controlEtag ? { controlEtag: input.controlEtag } : {},
1124
+ ...input.modelContext ? { modelContext: input.modelContext } : {},
1125
+ ...input.mcpCredentialUpdates ? { mcpCredentialUpdates: input.mcpCredentialUpdates } : {},
1126
+ ...input.connectionAuthorities ? { connectionAuthorities: input.connectionAuthorities } : {},
1127
+ ...input.personalResourceAttachment ? { personalResourceAttachment: input.personalResourceAttachment } : {}
1128
+ });
1129
+ adoptDraftBase(result.draft);
1130
+ return result;
1131
+ }
929
1132
  if (operation.delivery === "steer") {
930
1133
  return await client.steerMessage(workspaceId, sessionId, operation.input);
931
1134
  }
@@ -933,6 +1136,9 @@ function useComposer(sessionId, options = {}) {
933
1136
  return null;
934
1137
  };
935
1138
  if (delivery === "steer") {
1139
+ if (steeringRef.current?.clientEventId !== pending?.input.clientEventId) {
1140
+ steeringOccurredAtRef.current = (/* @__PURE__ */ new Date()).toISOString();
1141
+ }
936
1142
  setSteering({
937
1143
  phase: "submitting",
938
1144
  text: rawText,
@@ -957,7 +1163,20 @@ function useComposer(sessionId, options = {}) {
957
1163
  ) ?? null;
958
1164
  } catch (cause) {
959
1165
  if (targetKeyRef.current === ownedTargetKey && targetGeneration.current === ownedGeneration) {
960
- setError(asError(cause));
1166
+ const problem = asError(cause);
1167
+ setError(problem);
1168
+ if (pending.delivery === "steer") {
1169
+ keepSteering = true;
1170
+ setSteering({
1171
+ phase: "failed",
1172
+ text: pending.input.text,
1173
+ clientEventId: pending.input.clientEventId ?? null,
1174
+ triggerEventId: null,
1175
+ turnId: null,
1176
+ error: "Not confirmed. Retry.",
1177
+ outcomeUnknown: true
1178
+ });
1179
+ }
961
1180
  }
962
1181
  return false;
963
1182
  }
@@ -965,6 +1184,7 @@ function useComposer(sessionId, options = {}) {
965
1184
  return false;
966
1185
  }
967
1186
  if (acceptedEvent) {
1187
+ if (durableDrafts) await loadDraft(false);
968
1188
  if (pending.delivery === "steer") {
969
1189
  keepSteering = true;
970
1190
  setSteering({
@@ -1000,9 +1220,26 @@ function useComposer(sessionId, options = {}) {
1000
1220
  });
1001
1221
  }
1002
1222
  } catch (cause) {
1003
- if (pending.delivery === "steer") keepSteering = true;
1223
+ const problem = asError(cause);
1224
+ const outcomeUnknown = isOutcomeUnknownError(cause);
1225
+ onDeliveryErrorRef.current?.(problem, pending.input, pending.delivery);
1226
+ if (!outcomeUnknown) {
1227
+ clearPending();
1228
+ keepSteering = false;
1229
+ } else if (pending.delivery === "steer") {
1230
+ keepSteering = true;
1231
+ setSteering({
1232
+ phase: "failed",
1233
+ text: pending.input.text,
1234
+ clientEventId: pending.input.clientEventId ?? null,
1235
+ triggerEventId: null,
1236
+ turnId: null,
1237
+ error: "Not confirmed. Retry.",
1238
+ outcomeUnknown: true
1239
+ });
1240
+ }
1004
1241
  if (targetKeyRef.current === ownedTargetKey && targetGeneration.current === ownedGeneration) {
1005
- setError(asError(cause));
1242
+ setError(problem);
1006
1243
  }
1007
1244
  return false;
1008
1245
  }
@@ -1020,8 +1257,10 @@ function useComposer(sessionId, options = {}) {
1020
1257
  return false;
1021
1258
  }
1022
1259
  pendingClientEventId.current ??= generateClientEventId();
1260
+ const effectiveControl = effectiveControlRef.current;
1023
1261
  const input = composeSendInput(sendText, pendingClientEventId.current, extras, {
1024
- ...options.effectiveControl?.controlEtag ? { controlEtag: options.effectiveControl.controlEtag } : {},
1262
+ ...currentPolicy,
1263
+ ...effectiveControl?.controlEtag ? { controlEtag: effectiveControl.controlEtag } : {},
1025
1264
  ...durableDrafts && draftRef.current ? { expectedDraftRevision: draftRef.current.revision } : {},
1026
1265
  resources: mergeResources(restoredResources, extras.resources ?? []),
1027
1266
  annotations: cloneAnnotations(annotationsAtSend)
@@ -1035,7 +1274,8 @@ function useComposer(sessionId, options = {}) {
1035
1274
  newerShadow: {
1036
1275
  text: draftAtSend,
1037
1276
  resources: mergeResources(restoredResources, extras.resources ?? []),
1038
- annotations: cloneAnnotations(annotationsAtSend)
1277
+ annotations: cloneAnnotations(annotationsAtSend),
1278
+ policy: currentPolicy
1039
1279
  },
1040
1280
  clearDraftOnAccept: explicit === void 0,
1041
1281
  canRetry: true
@@ -1065,10 +1305,20 @@ function useComposer(sessionId, options = {}) {
1065
1305
  });
1066
1306
  }
1067
1307
  } catch (cause) {
1308
+ onDeliveryErrorRef.current?.(asError(cause), operation.input, operation.delivery);
1068
1309
  if (!isOutcomeUnknownError(cause)) {
1069
1310
  clearPending();
1070
1311
  } else if (delivery === "steer") {
1071
1312
  keepSteering = true;
1313
+ setSteering({
1314
+ phase: "failed",
1315
+ text: operation.input.text,
1316
+ clientEventId: operation.input.clientEventId ?? null,
1317
+ triggerEventId: null,
1318
+ turnId: null,
1319
+ error: "Not confirmed. Retry.",
1320
+ outcomeUnknown: true
1321
+ });
1072
1322
  }
1073
1323
  if (targetKeyRef.current === ownedTargetKey && targetGeneration.current === ownedGeneration) {
1074
1324
  setError(asError(cause));
@@ -1092,15 +1342,16 @@ function useComposer(sessionId, options = {}) {
1092
1342
  currentDraftPayload,
1093
1343
  durableDrafts,
1094
1344
  onSent,
1095
- options.effectiveControl?.controlEtag,
1096
1345
  persistPayload,
1097
1346
  restoredResources,
1098
1347
  annotations,
1348
+ adoptDraftBase,
1099
1349
  sending,
1100
1350
  sessionId,
1101
1351
  targetKey,
1102
1352
  value,
1103
- workspaceId
1353
+ workspaceId,
1354
+ loadDraft
1104
1355
  ]
1105
1356
  );
1106
1357
  const send = useCallback(
@@ -1110,6 +1361,7 @@ function useComposer(sessionId, options = {}) {
1110
1361
  }
1111
1362
  const rawText = explicit ?? valueRef.current;
1112
1363
  const annotationsAtSend = cloneAnnotations(annotationsRef.current);
1364
+ const currentPolicy = policyRef.current;
1113
1365
  const hasText = rawText.trim().length > 0;
1114
1366
  const hasAnnotations = annotationsAtSend.length > 0;
1115
1367
  const annotationsComplete2 = annotationsAtSend.every(
@@ -1117,19 +1369,26 @@ function useComposer(sessionId, options = {}) {
1117
1369
  );
1118
1370
  const extras = resolveSendExtras(sendExtrasRef.current);
1119
1371
  const resources = mergeResources(restoredResourcesRef.current, extras.resources ?? []);
1120
- if (!sessionId || sending || !annotationsComplete2 || !hasText && !hasAnnotations && resources.length === 0 || sendBlockedRef.current?.() === true || targetKeyRef.current !== targetKey) {
1372
+ if (!sessionId || !currentPolicy || sending || !annotationsComplete2 || !hasText && !hasAnnotations && resources.length === 0 || sendBlockedRef.current?.() === true || targetKeyRef.current !== targetKey) {
1121
1373
  return false;
1122
1374
  }
1123
1375
  const sendText = hasText ? rawText : hasAnnotations ? "" : FILE_ONLY_MESSAGE_TEXT;
1124
1376
  const clientEventId = generateClientEventId();
1377
+ const effectiveControl = effectiveControlRef.current;
1125
1378
  const input = composeSendInput(sendText, clientEventId, extras, {
1126
- ...options.effectiveControl?.controlEtag ? { controlEtag: options.effectiveControl.controlEtag } : {},
1379
+ ...currentPolicy,
1380
+ ...effectiveControl?.controlEtag ? { controlEtag: effectiveControl.controlEtag } : {},
1127
1381
  resources,
1128
1382
  annotations: annotationsAtSend
1129
1383
  });
1130
1384
  const currentPayload = currentDraftPayload();
1385
+ const hasEarlierUnsettledSend = optimisticSendsRef.current.some(
1386
+ (candidate) => candidate.state !== "failed" || candidate.outcomeUnknown === true
1387
+ );
1131
1388
  const operation = {
1132
1389
  clientEventId,
1390
+ delivery: "send",
1391
+ destination: effectiveControl?.state === "paused" || hasEarlierUnsettledSend ? "queue" : sendDestinationRef.current?.() ?? "chat",
1133
1392
  text: sendText,
1134
1393
  annotations: annotationsAtSend,
1135
1394
  resources,
@@ -1140,7 +1399,8 @@ function useComposer(sessionId, options = {}) {
1140
1399
  newerShadow: {
1141
1400
  text: explicit === void 0 ? "" : valueRef.current,
1142
1401
  resources: explicit === void 0 ? [] : [...restoredResourcesRef.current],
1143
- annotations: explicit === void 0 ? [] : cloneAnnotations(annotationsRef.current)
1402
+ annotations: explicit === void 0 ? [] : cloneAnnotations(annotationsRef.current),
1403
+ policy: currentPolicy
1144
1404
  },
1145
1405
  canRetry: true
1146
1406
  };
@@ -1164,7 +1424,6 @@ function useComposer(sessionId, options = {}) {
1164
1424
  currentDraftPayload,
1165
1425
  dispatch,
1166
1426
  onSubmitted,
1167
- options.effectiveControl?.controlEtag,
1168
1427
  processOptimisticSends,
1169
1428
  replaceOptimisticSends,
1170
1429
  sending,
@@ -1175,6 +1434,12 @@ function useComposer(sessionId, options = {}) {
1175
1434
  const steer = useCallback(async (text) => await dispatch("steer", text), [dispatch]);
1176
1435
  const retryOptimisticMessage = useCallback(
1177
1436
  (clientEventId) => {
1437
+ if (sendBlockedRef.current?.() === true) return;
1438
+ const currentSteering = steeringRef.current;
1439
+ if (currentSteering?.phase === "failed" && currentSteering.clientEventId === clientEventId) {
1440
+ void dispatch("steer");
1441
+ return;
1442
+ }
1178
1443
  replaceOptimisticSends(
1179
1444
  (current) => current.map((operation) => {
1180
1445
  if (operation.clientEventId !== clientEventId || operation.state !== "failed") {
@@ -1184,22 +1449,27 @@ function useComposer(sessionId, options = {}) {
1184
1449
  return { ...operation, state: "sending", error: void 0 };
1185
1450
  }
1186
1451
  const nextClientEventId = generateClientEventId();
1187
- const retryExtras = resolveSendExtras(sendExtrasRef.current);
1188
- const retryInput = composeSendInput(operation.text, nextClientEventId, retryExtras, {
1189
- ...options.effectiveControl?.controlEtag ? { controlEtag: options.effectiveControl.controlEtag } : {},
1190
- resources: operation.resources,
1191
- annotations: operation.annotations
1192
- });
1452
+ const currentPersonalResourceAttachment = resolveSendExtras(
1453
+ sendExtrasRef.current
1454
+ ).personalResourceAttachment;
1455
+ const retryInput = composeSendInput(
1456
+ operation.text,
1457
+ nextClientEventId,
1458
+ {
1459
+ ...operation.input,
1460
+ ...currentPersonalResourceAttachment ? { personalResourceAttachment: currentPersonalResourceAttachment } : {}
1461
+ },
1462
+ {
1463
+ ...effectiveControlRef.current?.controlEtag ? { controlEtag: effectiveControlRef.current.controlEtag } : {},
1464
+ resources: operation.resources,
1465
+ annotations: operation.annotations
1466
+ }
1467
+ );
1193
1468
  return {
1194
1469
  ...operation,
1195
1470
  clientEventId: nextClientEventId,
1196
1471
  input: retryInput,
1197
- draftPayload: operation.draftPayload ? {
1198
- ...operation.draftPayload,
1199
- model: retryExtras.model ?? operation.draftPayload.model,
1200
- reasoningEffort: retryExtras.reasoningEffort ?? operation.draftPayload.reasoningEffort,
1201
- latencyMode: retryExtras.latencyMode ?? operation.draftPayload.latencyMode
1202
- } : null,
1472
+ draftPayload: operation.draftPayload,
1203
1473
  occurredAt: (/* @__PURE__ */ new Date()).toISOString(),
1204
1474
  state: "sending",
1205
1475
  error: void 0,
@@ -1210,15 +1480,23 @@ function useComposer(sessionId, options = {}) {
1210
1480
  );
1211
1481
  queueMicrotask(processOptimisticSends);
1212
1482
  },
1213
- [options.effectiveControl?.controlEtag, processOptimisticSends, replaceOptimisticSends]
1483
+ [dispatch, processOptimisticSends, replaceOptimisticSends]
1214
1484
  );
1215
1485
  const removeOptimisticMessage = useCallback(
1216
1486
  (clientEventId) => {
1487
+ if (steeringRef.current?.clientEventId === clientEventId) {
1488
+ pendingOperationRef.current = null;
1489
+ pendingClientEventId.current = null;
1490
+ forgetPendingComposerOperation(pendingComposerOperationKey(workspaceId, sessionId));
1491
+ setSteering(null);
1492
+ setError(null);
1493
+ return;
1494
+ }
1217
1495
  replaceOptimisticSends(
1218
1496
  (current) => current.filter((operation) => operation.clientEventId !== clientEventId)
1219
1497
  );
1220
1498
  },
1221
- [replaceOptimisticSends]
1499
+ [replaceOptimisticSends, sessionId, workspaceId]
1222
1500
  );
1223
1501
  const hasReadyResources = restoredResources.length > 0 || (resolveSendExtras(sendExtrasRef.current).resources?.length ?? 0) > 0;
1224
1502
  const hasPendingOperation = pendingOperationRef.current !== null;
@@ -1233,17 +1511,17 @@ function useComposer(sessionId, options = {}) {
1233
1511
  setPausing(true);
1234
1512
  setError(null);
1235
1513
  try {
1236
- const result = await client.pauseSession(workspaceId, sessionId, {
1237
- ...reason !== void 0 ? { reason } : {},
1238
- ...options.effectiveControl?.controlEtag ? { expectedControlEtag: options.effectiveControl.controlEtag } : {}
1239
- });
1514
+ const clientEventId = generateClientEventId();
1515
+ const effectiveControl = effectiveControlRef.current;
1516
+ const result = await replayOutcomeUnknown(
1517
+ () => client.pauseSession(workspaceId, sessionId, {
1518
+ clientEventId,
1519
+ ...reason !== void 0 ? { reason } : {},
1520
+ ...effectiveControl?.controlEtag ? { expectedControlEtag: effectiveControl.controlEtag } : {}
1521
+ })
1522
+ );
1240
1523
  if (targetKeyRef.current === ownedTargetKey && targetGeneration.current === ownedGeneration) {
1241
- setControlStopping(
1242
- result.interruptionCount > 0 && result.effectiveControl.state === "paused" && result.effectiveControl.settlement !== null ? {
1243
- controlVersion: result.effectiveControl.controlVersion,
1244
- controlEtag: result.effectiveControl.controlEtag
1245
- } : null
1246
- );
1524
+ setAcceptedControl(result.effectiveControl);
1247
1525
  }
1248
1526
  } catch (cause) {
1249
1527
  if (targetKeyRef.current === ownedTargetKey && targetGeneration.current === ownedGeneration) {
@@ -1255,7 +1533,7 @@ function useComposer(sessionId, options = {}) {
1255
1533
  }
1256
1534
  }
1257
1535
  },
1258
- [client, workspaceId, sessionId, pausing, options.effectiveControl?.controlEtag, targetKey]
1536
+ [client, workspaceId, sessionId, pausing, targetKey]
1259
1537
  );
1260
1538
  const resume = useCallback(
1261
1539
  async (reason) => {
@@ -1265,10 +1543,18 @@ function useComposer(sessionId, options = {}) {
1265
1543
  setResuming(true);
1266
1544
  setError(null);
1267
1545
  try {
1268
- await client.resumeSession(workspaceId, sessionId, {
1269
- ...reason !== void 0 ? { reason } : {},
1270
- ...options.effectiveControl?.controlEtag ? { expectedControlEtag: options.effectiveControl.controlEtag } : {}
1271
- });
1546
+ const clientEventId = generateClientEventId();
1547
+ const effectiveControl = effectiveControlRef.current;
1548
+ const result = await replayOutcomeUnknown(
1549
+ () => client.resumeSession(workspaceId, sessionId, {
1550
+ clientEventId,
1551
+ ...reason !== void 0 ? { reason } : {},
1552
+ ...effectiveControl?.controlEtag ? { expectedControlEtag: effectiveControl.controlEtag } : {}
1553
+ })
1554
+ );
1555
+ if (targetKeyRef.current === ownedTargetKey && targetGeneration.current === ownedGeneration) {
1556
+ setAcceptedControl(result.effectiveControl);
1557
+ }
1272
1558
  } catch (cause) {
1273
1559
  if (targetKeyRef.current === ownedTargetKey && targetGeneration.current === ownedGeneration) {
1274
1560
  setError(cause instanceof Error ? cause : new Error(String(cause)));
@@ -1279,7 +1565,7 @@ function useComposer(sessionId, options = {}) {
1279
1565
  }
1280
1566
  }
1281
1567
  },
1282
- [client, workspaceId, sessionId, resuming, options.effectiveControl?.controlEtag, targetKey]
1568
+ [client, workspaceId, sessionId, resuming, targetKey]
1283
1569
  );
1284
1570
  const resumeScope = useCallback(
1285
1571
  async (option) => {
@@ -1290,7 +1576,7 @@ function useComposer(sessionId, options = {}) {
1290
1576
  setError(null);
1291
1577
  try {
1292
1578
  if (option.scope === "workspace") {
1293
- const workspaceBlocker = options.effectiveControl?.blockers.find(
1579
+ const workspaceBlocker = effectiveControlRef.current?.blockers.find(
1294
1580
  (blocker) => blocker.kind === "workspace"
1295
1581
  );
1296
1582
  if (!client.setWorkspaceInferenceState) {
@@ -1298,23 +1584,38 @@ function useComposer(sessionId, options = {}) {
1298
1584
  "@opengeni/react: workspace-scoped resume requires setWorkspaceInferenceState."
1299
1585
  );
1300
1586
  }
1301
- await client.setWorkspaceInferenceState(workspaceId, {
1302
- action: "resume",
1303
- clientEventId: generateClientEventId(),
1304
- ...workspaceBlocker ? { expectedRevision: workspaceBlocker.revision } : {}
1305
- });
1587
+ const clientEventId = generateClientEventId();
1588
+ await replayOutcomeUnknown(
1589
+ () => client.setWorkspaceInferenceState(workspaceId, {
1590
+ action: "resume",
1591
+ clientEventId,
1592
+ ...workspaceBlocker ? { expectedRevision: workspaceBlocker.revision } : {}
1593
+ })
1594
+ );
1306
1595
  } else if (option.scope === "session" && option.targetId) {
1307
1596
  const target = await client.getQueue(workspaceId, option.targetId);
1308
1597
  if (targetKeyRef.current !== ownedTargetKey || targetGeneration.current !== ownedGeneration) {
1309
1598
  return;
1310
1599
  }
1311
- await client.resumeSession(workspaceId, option.targetId, {
1312
- expectedControlEtag: target.effectiveControl.controlEtag
1313
- });
1600
+ const clientEventId = generateClientEventId();
1601
+ await replayOutcomeUnknown(
1602
+ () => client.resumeSession(workspaceId, option.targetId, {
1603
+ clientEventId,
1604
+ expectedControlEtag: target.effectiveControl.controlEtag
1605
+ })
1606
+ );
1314
1607
  } else {
1315
- await client.resumeSession(workspaceId, sessionId, {
1316
- ...options.effectiveControl?.controlEtag ? { expectedControlEtag: options.effectiveControl.controlEtag } : {}
1317
- });
1608
+ const clientEventId = generateClientEventId();
1609
+ const effectiveControl = effectiveControlRef.current;
1610
+ const result = await replayOutcomeUnknown(
1611
+ () => client.resumeSession(workspaceId, sessionId, {
1612
+ clientEventId,
1613
+ ...effectiveControl?.controlEtag ? { expectedControlEtag: effectiveControl.controlEtag } : {}
1614
+ })
1615
+ );
1616
+ if (targetKeyRef.current === ownedTargetKey && targetGeneration.current === ownedGeneration) {
1617
+ setAcceptedControl(result.effectiveControl);
1618
+ }
1318
1619
  }
1319
1620
  } catch (cause) {
1320
1621
  if (targetKeyRef.current === ownedTargetKey && targetGeneration.current === ownedGeneration) {
@@ -1326,7 +1627,7 @@ function useComposer(sessionId, options = {}) {
1326
1627
  }
1327
1628
  }
1328
1629
  },
1329
- [client, options.effectiveControl, resuming, sessionId, targetKey, workspaceId]
1630
+ [client, resuming, sessionId, targetKey, workspaceId]
1330
1631
  );
1331
1632
  const updateValue = useCallback(
1332
1633
  (next) => {
@@ -1342,7 +1643,8 @@ function useComposer(sessionId, options = {}) {
1342
1643
  restoredResourcesRef.current,
1343
1644
  resolveSendExtras(sendExtrasRef.current).resources ?? []
1344
1645
  ),
1345
- annotations: annotationsRef.current
1646
+ annotations: annotationsRef.current,
1647
+ policy: policyRef.current ?? void 0
1346
1648
  }
1347
1649
  );
1348
1650
  setOptimisticDraftShadow({
@@ -1351,7 +1653,8 @@ function useComposer(sessionId, options = {}) {
1351
1653
  restoredResourcesRef.current,
1352
1654
  resolveSendExtras(sendExtrasRef.current).resources ?? []
1353
1655
  ),
1354
- annotations: annotationsRef.current
1656
+ annotations: annotationsRef.current,
1657
+ policy: policyRef.current ?? void 0
1355
1658
  });
1356
1659
  setValue(next);
1357
1660
  },
@@ -1371,7 +1674,8 @@ function useComposer(sessionId, options = {}) {
1371
1674
  restoredResourcesRef.current,
1372
1675
  resolveSendExtras(sendExtrasRef.current).resources ?? []
1373
1676
  ),
1374
- annotations: next
1677
+ annotations: next,
1678
+ policy: policyRef.current ?? void 0
1375
1679
  }
1376
1680
  );
1377
1681
  setOptimisticDraftShadow({
@@ -1380,7 +1684,8 @@ function useComposer(sessionId, options = {}) {
1380
1684
  restoredResourcesRef.current,
1381
1685
  resolveSendExtras(sendExtrasRef.current).resources ?? []
1382
1686
  ),
1383
- annotations: next
1687
+ annotations: next,
1688
+ policy: policyRef.current ?? void 0
1384
1689
  });
1385
1690
  setAnnotations(next);
1386
1691
  setAnnotationReviewTargetId(reviewTargetId);
@@ -1417,6 +1722,51 @@ function useComposer(sessionId, options = {}) {
1417
1722
  const clearAnnotationReviewTarget = useCallback(() => {
1418
1723
  setAnnotationReviewTargetId(null);
1419
1724
  }, []);
1725
+ const updatePolicy = useCallback(
1726
+ (next) => {
1727
+ if (targetKeyRef.current !== targetKey) return;
1728
+ localEditRevision.current += 1;
1729
+ policyRef.current = next;
1730
+ const shadow = {
1731
+ text: valueRef.current,
1732
+ resources: mergeResources(
1733
+ restoredResourcesRef.current,
1734
+ resolveSendExtras(sendExtrasRef.current).resources ?? []
1735
+ ),
1736
+ annotations: annotationsRef.current,
1737
+ policy: next
1738
+ };
1739
+ pendingOperationRef.current = updatePendingComposerShadow(
1740
+ pendingOperationKey,
1741
+ pendingOperationRef.current,
1742
+ shadow
1743
+ );
1744
+ setOptimisticDraftShadow(shadow);
1745
+ setPolicy(next);
1746
+ },
1747
+ [pendingOperationKey, setOptimisticDraftShadow, targetKey]
1748
+ );
1749
+ const updateModel = useCallback(
1750
+ (model) => {
1751
+ const current = policyRef.current;
1752
+ if (current) updatePolicy({ ...current, model });
1753
+ },
1754
+ [updatePolicy]
1755
+ );
1756
+ const updateReasoningEffort = useCallback(
1757
+ (reasoningEffort) => {
1758
+ const current = policyRef.current;
1759
+ if (current) updatePolicy({ ...current, reasoningEffort });
1760
+ },
1761
+ [updatePolicy]
1762
+ );
1763
+ const updateLatencyMode = useCallback(
1764
+ (latencyMode) => {
1765
+ const current = policyRef.current;
1766
+ if (current) updatePolicy({ ...current, latencyMode });
1767
+ },
1768
+ [updatePolicy]
1769
+ );
1420
1770
  const removeRestoredResource = useCallback(
1421
1771
  (index) => {
1422
1772
  if (targetKeyRef.current !== targetKey) return;
@@ -1429,13 +1779,15 @@ function useComposer(sessionId, options = {}) {
1429
1779
  {
1430
1780
  text: valueRef.current,
1431
1781
  resources: mergeResources(next, resolveSendExtras(sendExtrasRef.current).resources ?? []),
1432
- annotations: annotationsRef.current
1782
+ annotations: annotationsRef.current,
1783
+ policy: policyRef.current ?? void 0
1433
1784
  }
1434
1785
  );
1435
1786
  setOptimisticDraftShadow({
1436
1787
  text: valueRef.current,
1437
1788
  resources: mergeResources(next, resolveSendExtras(sendExtrasRef.current).resources ?? []),
1438
- annotations: annotationsRef.current
1789
+ annotations: annotationsRef.current,
1790
+ policy: policyRef.current ?? void 0
1439
1791
  });
1440
1792
  setRestoredResources(next);
1441
1793
  },
@@ -1480,6 +1832,27 @@ function useComposer(sessionId, options = {}) {
1480
1832
  );
1481
1833
  const identityMatches = stateTargetKey === targetKey;
1482
1834
  const visibleSteering = identityMatches ? steering : null;
1835
+ const visibleOptimisticMessages = identityMatches ? [
1836
+ ...optimisticSends.map(
1837
+ ({ input: _input, draftPayload: _payload, canRetry: _retry, ...item }) => item
1838
+ ),
1839
+ ...visibleSteering?.clientEventId ? [
1840
+ {
1841
+ clientEventId: visibleSteering.clientEventId,
1842
+ delivery: "steer",
1843
+ destination: "chat",
1844
+ text: visibleSteering.text,
1845
+ annotations: pendingOperationRef.current?.delivery === "steer" ? pendingOperationRef.current.annotationsAtSend : [],
1846
+ resources: pendingOperationRef.current?.delivery === "steer" ? pendingOperationRef.current.resourcesAtSend : [],
1847
+ occurredAt: steeringOccurredAtRef.current,
1848
+ state: visibleSteering.phase === "submitting" ? "sending" : visibleSteering.phase === "failed" ? "failed" : "queued",
1849
+ turnId: visibleSteering.turnId,
1850
+ triggerEventId: visibleSteering.triggerEventId,
1851
+ ...visibleSteering.error ? { error: visibleSteering.error } : {},
1852
+ ...visibleSteering.outcomeUnknown !== void 0 ? { outcomeUnknown: visibleSteering.outcomeUnknown } : {}
1853
+ }
1854
+ ] : []
1855
+ ] : [];
1483
1856
  const reloadDraft = useCallback(async () => await loadDraft(true), [loadDraft]);
1484
1857
  const clearError = useCallback(() => {
1485
1858
  if (targetKeyRef.current !== targetKey) return;
@@ -1497,26 +1870,29 @@ function useComposer(sessionId, options = {}) {
1497
1870
  clearAnnotationReviewTarget,
1498
1871
  hasDraftContent,
1499
1872
  send,
1500
- optimisticMessages: identityMatches ? optimisticSends.map(
1501
- ({ input: _input, draftPayload: _payload, canRetry: _retry, ...item }) => item
1502
- ) : [],
1873
+ optimisticMessages: visibleOptimisticMessages,
1503
1874
  retryOptimisticMessage,
1504
1875
  removeOptimisticMessage,
1505
1876
  steer,
1506
1877
  steering: visibleSteering,
1507
- stoppingAttempt: identityMatches ? controlStopping ? "current" : visibleSteering?.phase === "accepted" && visibleSteering.stoppingPreviousAttempt === true ? "previous" : null : null,
1878
+ stoppingAttempt: identityMatches ? visibleSteering?.phase === "accepted" && visibleSteering.stoppingPreviousAttempt === true ? "previous" : null : null,
1508
1879
  sending: identityMatches ? sending : false,
1509
- canSend: identityMatches && Boolean(sessionId) && !sending && sendBlockedRef.current?.() !== true && annotationsComplete && (hasPendingOperation || value.trim().length > 0 || hasReadyResources || annotations.length > 0),
1880
+ canSend: identityMatches && Boolean(sessionId) && !sending && !draftLoading && policy !== null && sendBlockedRef.current?.() !== true && annotationsComplete && (hasPendingOperation || value.trim().length > 0 || hasReadyResources || annotations.length > 0),
1510
1881
  pause,
1511
1882
  pausing: identityMatches ? pausing : false,
1512
1883
  resume,
1513
1884
  resumeScope,
1514
1885
  resuming: identityMatches ? resuming : false,
1886
+ effectiveControl: identityMatches ? effectiveControlRef.current : null,
1515
1887
  draft: identityMatches ? draft : null,
1516
1888
  draftRevision: identityMatches ? draft?.revision ?? 0 : 0,
1517
1889
  draftLoading: identityMatches ? draftLoading : Boolean(sessionId) && durableDrafts,
1518
1890
  draftSaving: identityMatches ? draftSaving : false,
1519
1891
  draftConflict: identityMatches ? draftConflict : null,
1892
+ policy: identityMatches ? policy : null,
1893
+ setModel: updateModel,
1894
+ setReasoningEffort: updateReasoningEffort,
1895
+ setLatencyMode: updateLatencyMode,
1520
1896
  draftPersistence: durableDrafts ? "durable" : "disabled",
1521
1897
  applyDraft,
1522
1898
  reloadDraft,
@@ -1552,31 +1928,20 @@ function generateClientEventId() {
1552
1928
  function isOutcomeUnknownError(cause) {
1553
1929
  return typeof cause === "object" && cause !== null && cause.outcomeUnknown === true;
1554
1930
  }
1931
+ async function replayOutcomeUnknown(command) {
1932
+ try {
1933
+ return await command();
1934
+ } catch (cause) {
1935
+ if (!isOutcomeUnknownError(cause)) throw cause;
1936
+ return await command();
1937
+ }
1938
+ }
1555
1939
  function asError(cause) {
1556
1940
  return cause instanceof Error ? cause : new Error(String(cause));
1557
1941
  }
1558
1942
  function isDraftConflictError(error) {
1559
1943
  const apiError = error;
1560
- if (apiError.status !== 409 || apiError.outcomeUnknown === true) return false;
1561
- const code = apiError.code;
1562
- if (code === void 0 || code === "DRAFT_CHANGED" || code === "conflict" || code === "idempotency_conflict") {
1563
- return true;
1564
- }
1565
- return /draft changed/i.test(error.message);
1566
- }
1567
- async function saveComposerDraftWithStaleRetry(input) {
1568
- try {
1569
- return await input.client.saveComposerDraft(input.workspaceId, input.sessionId, input.request);
1570
- } catch (cause) {
1571
- const problem = asError(cause);
1572
- if (!isDraftConflictError(problem)) throw problem;
1573
- const remote = await input.client.getComposerDraft(input.workspaceId, input.sessionId);
1574
- input.onAdoptRemote(remote);
1575
- return await input.client.saveComposerDraft(input.workspaceId, input.sessionId, {
1576
- ...input.request,
1577
- expectedRevision: remote.revision
1578
- });
1579
- }
1944
+ return apiError.status === 409 && apiError.outcomeUnknown !== true && apiError.code === "DRAFT_CHANGED";
1580
1945
  }
1581
1946
  function draftPayload(draft) {
1582
1947
  return {
@@ -1586,18 +1951,25 @@ function draftPayload(draft) {
1586
1951
  annotations: draft.annotations ?? [],
1587
1952
  model: draft.model,
1588
1953
  reasoningEffort: draft.reasoningEffort,
1589
- latencyMode: draft.latencyMode ?? "standard"
1954
+ latencyMode: draft.latencyMode
1955
+ };
1956
+ }
1957
+ function policyFromDraft(draft) {
1958
+ return {
1959
+ model: draft.model,
1960
+ reasoningEffort: draft.reasoningEffort,
1961
+ latencyMode: draft.latencyMode
1590
1962
  };
1591
1963
  }
1592
- function composerDraftPayload(base, text, restoredResources, annotations, extras) {
1964
+ function composerDraftPayload(base, text, restoredResources, annotations, policy, additionalResources) {
1593
1965
  return {
1594
1966
  expectedRevision: base.revision,
1595
1967
  text,
1596
- resources: mergeResources(restoredResources, extras.resources ?? []),
1968
+ resources: mergeResources(restoredResources, additionalResources),
1597
1969
  annotations,
1598
- model: extras.model ?? base.model,
1599
- reasoningEffort: extras.reasoningEffort ?? base.reasoningEffort,
1600
- latencyMode: extras.latencyMode ?? base.latencyMode ?? "standard"
1970
+ model: policy.model,
1971
+ reasoningEffort: policy.reasoningEffort,
1972
+ latencyMode: policy.latencyMode
1601
1973
  };
1602
1974
  }
1603
1975
  function draftSignature(payload) {
@@ -1621,4 +1993,4 @@ export {
1621
1993
  shouldSubmitOnKey,
1622
1994
  shouldSteerOnKey
1623
1995
  };
1624
- //# sourceMappingURL=chunk-IGC6A2ZI.js.map
1996
+ //# sourceMappingURL=chunk-JOAVA2TW.js.map