@opengeni/react 0.25.0 → 0.25.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-AVPU5PMC.js → chunk-F4CFWKVI.js} +332 -64
- package/dist/chunk-F4CFWKVI.js.map +1 -0
- package/dist/chunk-GQN2QIR2.js +3767 -0
- package/dist/chunk-GQN2QIR2.js.map +1 -0
- package/dist/{chunk-I3BJZIG5.js → chunk-HIWPQYWI.js} +2 -120
- package/dist/chunk-HIWPQYWI.js.map +1 -0
- package/dist/chunk-M7X4JZOD.js +121 -0
- package/dist/chunk-M7X4JZOD.js.map +1 -0
- package/dist/{chunk-SHOFILHJ.js → chunk-MP6237JB.js} +570 -984
- package/dist/chunk-MP6237JB.js.map +1 -0
- package/dist/chunk-OZDLELJQ.js +937 -0
- package/dist/chunk-OZDLELJQ.js.map +1 -0
- package/dist/{chunk-RDPDU4TA.js → chunk-TMH6HZWF.js} +3 -3
- package/dist/{chunk-6XUS5VFM.js → chunk-YU5PGUK7.js} +6 -4
- package/dist/{chunk-6XUS5VFM.js.map → chunk-YU5PGUK7.js.map} +1 -1
- package/dist/{composer-BXb0Q1HF.d.ts → composer-DoC4veX1.d.ts} +4 -75
- package/dist/composer.d.ts +2 -1
- package/dist/composer.js +4 -3
- package/dist/index.d.ts +14 -259
- package/dist/index.js +1016 -5140
- package/dist/index.js.map +1 -1
- package/dist/machines.js +3 -2
- package/dist/session-Du_FsrZ1.d.ts +264 -0
- package/dist/session-ui-BqQDH7YV.d.ts +183 -0
- package/dist/session-ui.d.ts +7 -0
- package/dist/session-ui.js +17 -0
- package/dist/session-ui.js.map +1 -0
- package/dist/session.d.ts +3 -1
- package/dist/session.js +26 -9
- package/dist/use-file-attachments-C0kpHrs9.d.ts +76 -0
- package/dist/{session-BEvtFWhe.d.ts → use-turn-queue-3rkJwjFv.d.ts} +3 -185
- package/package.json +6 -2
- package/src/components/message-timeline.tsx +92 -52
- package/src/hooks/use-composer.ts +456 -69
- package/src/hooks/use-file-attachments.ts +1 -1
- package/src/hooks/use-goal.ts +1 -1
- package/src/hooks/use-session-events.ts +5 -0
- package/src/hooks/use-session-lineage.ts +1 -1
- package/src/hooks/use-session.ts +9 -6
- package/src/session-ui.ts +13 -0
- package/src/session.ts +15 -0
- package/src/timeline/activity-rail.tsx +1 -1
- package/dist/chunk-AVPU5PMC.js.map +0 -1
- package/dist/chunk-I3BJZIG5.js.map +0 -1
- package/dist/chunk-SHOFILHJ.js.map +0 -1
- /package/dist/{chunk-RDPDU4TA.js.map → chunk-TMH6HZWF.js.map} +0 -0
|
@@ -1,15 +1,116 @@
|
|
|
1
1
|
import {
|
|
2
2
|
useEmbeddedSession,
|
|
3
3
|
useSessionEventTrigger
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-HIWPQYWI.js";
|
|
5
5
|
|
|
6
6
|
// src/hooks/use-composer.ts
|
|
7
7
|
import { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";
|
|
8
|
+
var PENDING_COMPOSER_STORAGE_PREFIX = "opengeni.pending-composer.v1:";
|
|
9
|
+
var pendingComposerOperations = /* @__PURE__ */ new Map();
|
|
10
|
+
function pendingComposerOperationKey(workspaceId, sessionId) {
|
|
11
|
+
return sessionId ? `${workspaceId}\0${sessionId}` : null;
|
|
12
|
+
}
|
|
13
|
+
function pendingComposerStorage() {
|
|
14
|
+
try {
|
|
15
|
+
return typeof window === "undefined" ? null : window.sessionStorage;
|
|
16
|
+
} catch {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function pendingComposerStorageKey(key) {
|
|
21
|
+
return `${PENDING_COMPOSER_STORAGE_PREFIX}${encodeURIComponent(key)}`;
|
|
22
|
+
}
|
|
23
|
+
function resourceList(value) {
|
|
24
|
+
return Array.isArray(value) && value.every((candidate) => {
|
|
25
|
+
if (typeof candidate !== "object" || candidate === null) return false;
|
|
26
|
+
const resource = candidate;
|
|
27
|
+
return resource.kind === "file" ? typeof resource.fileId === "string" : resource.kind === "repository" && typeof resource.uri === "string" && typeof resource.ref === "string";
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
function readStoredPendingComposerOperation(key) {
|
|
31
|
+
const storage = key ? pendingComposerStorage() : null;
|
|
32
|
+
if (!storage || !key) return null;
|
|
33
|
+
try {
|
|
34
|
+
const parsed = JSON.parse(storage.getItem(pendingComposerStorageKey(key)) ?? "null");
|
|
35
|
+
if (typeof parsed !== "object" || parsed === null) return null;
|
|
36
|
+
const record = parsed;
|
|
37
|
+
const input = record.input;
|
|
38
|
+
const shadow = record.newerShadow;
|
|
39
|
+
if (typeof input !== "object" || input === null || typeof shadow !== "object" || shadow === null) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
const inputRecord = input;
|
|
43
|
+
const shadowRecord = shadow;
|
|
44
|
+
if (record.delivery !== "send" && record.delivery !== "steer" || typeof record.draftAtSend !== "string" || !resourceList(record.resourcesAtSend) || typeof shadowRecord.text !== "string" || !resourceList(shadowRecord.resources) || typeof record.clearDraftOnAccept !== "boolean" || typeof record.hasMcpCredentialUpdates !== "boolean" || typeof inputRecord.text !== "string" || typeof inputRecord.clientEventId !== "string" || "mcpCredentialUpdates" in inputRecord || "resources" in inputRecord && !resourceList(inputRecord.resources)) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
return record;
|
|
48
|
+
} catch {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function writePendingComposerOperation(key, operation) {
|
|
53
|
+
const storage = pendingComposerStorage();
|
|
54
|
+
if (!storage) return;
|
|
55
|
+
try {
|
|
56
|
+
storage.setItem(pendingComposerStorageKey(key), JSON.stringify(operation));
|
|
57
|
+
} catch {
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function restorePendingComposerOperation(key) {
|
|
61
|
+
const stored = (key && pendingComposerOperations.get(key)) ?? readStoredPendingComposerOperation(key);
|
|
62
|
+
if (!stored) return null;
|
|
63
|
+
return {
|
|
64
|
+
...stored,
|
|
65
|
+
input: stored.input,
|
|
66
|
+
newerShadow: stored.newerShadow ?? {
|
|
67
|
+
text: stored.draftAtSend,
|
|
68
|
+
resources: stored.resourcesAtSend
|
|
69
|
+
},
|
|
70
|
+
canRetry: !stored.hasMcpCredentialUpdates
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
function rememberPendingComposerOperation(key, operation) {
|
|
74
|
+
if (!key) return;
|
|
75
|
+
const { canRetry: _retry, ...safeOperation } = operation;
|
|
76
|
+
const { input: originalInput, ...withoutInput } = safeOperation;
|
|
77
|
+
const { mcpCredentialUpdates: _storedMcp, ...safeInput } = originalInput;
|
|
78
|
+
const stored = {
|
|
79
|
+
...withoutInput,
|
|
80
|
+
input: safeInput,
|
|
81
|
+
hasMcpCredentialUpdates: operation.input.mcpCredentialUpdates !== void 0,
|
|
82
|
+
newerShadow: {
|
|
83
|
+
text: operation.newerShadow.text,
|
|
84
|
+
resources: [...operation.newerShadow.resources]
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
pendingComposerOperations.set(key, stored);
|
|
88
|
+
writePendingComposerOperation(key, stored);
|
|
89
|
+
}
|
|
90
|
+
function forgetPendingComposerOperation(key) {
|
|
91
|
+
if (!key) return;
|
|
92
|
+
pendingComposerOperations.delete(key);
|
|
93
|
+
const storage = pendingComposerStorage();
|
|
94
|
+
if (!storage) return;
|
|
95
|
+
try {
|
|
96
|
+
storage.removeItem(pendingComposerStorageKey(key));
|
|
97
|
+
} catch {
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function updatePendingComposerShadow(key, operation, shadow) {
|
|
101
|
+
if (!key || !operation) return operation;
|
|
102
|
+
const next = { ...operation, newerShadow: { ...shadow, resources: [...shadow.resources] } };
|
|
103
|
+
rememberPendingComposerOperation(key, next);
|
|
104
|
+
return next;
|
|
105
|
+
}
|
|
8
106
|
function useComposer(sessionId, options = {}) {
|
|
9
107
|
const { client, workspaceId, registerSessionReconciler } = useEmbeddedSession(options);
|
|
10
108
|
const durableDrafts = options.draftPersistence !== "disabled";
|
|
11
109
|
const targetKey = `${workspaceId}\0${sessionId ?? ""}\0${durableDrafts ? "durable" : "disabled"}`;
|
|
12
|
-
const
|
|
110
|
+
const pendingOperationKey = pendingComposerOperationKey(workspaceId, sessionId);
|
|
111
|
+
const initialPendingOperation = restorePendingComposerOperation(pendingOperationKey);
|
|
112
|
+
const initialShadow = initialPendingOperation?.newerShadow;
|
|
113
|
+
const [value, setValue] = useState(() => initialShadow?.text ?? "");
|
|
13
114
|
const [stateTargetKey, setStateTargetKey] = useState(targetKey);
|
|
14
115
|
const [sending, setSending] = useState(false);
|
|
15
116
|
const [pausing, setPausing] = useState(false);
|
|
@@ -19,18 +120,27 @@ function useComposer(sessionId, options = {}) {
|
|
|
19
120
|
const [draftLoading, setDraftLoading] = useState(Boolean(sessionId) && durableDrafts);
|
|
20
121
|
const [draftSaving, setDraftSaving] = useState(false);
|
|
21
122
|
const [draftConflict, setDraftConflict] = useState(null);
|
|
22
|
-
const [restoredResources, setRestoredResources] = useState(
|
|
23
|
-
|
|
24
|
-
|
|
123
|
+
const [restoredResources, setRestoredResources] = useState(
|
|
124
|
+
() => initialShadow?.resources ?? []
|
|
125
|
+
);
|
|
126
|
+
const pendingOperationRef = useRef(initialPendingOperation);
|
|
127
|
+
const pendingClientEventId = useRef(
|
|
128
|
+
initialPendingOperation?.input.clientEventId ?? null
|
|
129
|
+
);
|
|
130
|
+
const valueRef = useRef(initialShadow?.text ?? "");
|
|
25
131
|
const draftRef = useRef(null);
|
|
26
|
-
const restoredResourcesRef = useRef([]);
|
|
27
|
-
const localEditRevision = useRef(0);
|
|
132
|
+
const restoredResourcesRef = useRef(initialShadow?.resources ?? []);
|
|
133
|
+
const localEditRevision = useRef(initialShadow ? 1 : 0);
|
|
28
134
|
const targetGeneration = useRef(0);
|
|
29
135
|
const draftReadGeneration = useRef(0);
|
|
30
136
|
const lastSavedSignature = useRef(null);
|
|
31
137
|
const saveChain = useRef(Promise.resolve());
|
|
32
138
|
const onSent = options.onSent;
|
|
33
139
|
const onDraftApplied = options.onDraftApplied;
|
|
140
|
+
const onDraftAppliedRef = useRef(onDraftApplied);
|
|
141
|
+
useLayoutEffect(() => {
|
|
142
|
+
onDraftAppliedRef.current = onDraftApplied;
|
|
143
|
+
}, [onDraftApplied]);
|
|
34
144
|
const sendExtrasRef = useRef(options.sendExtras);
|
|
35
145
|
sendExtrasRef.current = options.sendExtras;
|
|
36
146
|
const sendBlockedRef = useRef(options.sendBlocked);
|
|
@@ -42,15 +152,17 @@ function useComposer(sessionId, options = {}) {
|
|
|
42
152
|
targetKeyRef.current = targetKey;
|
|
43
153
|
targetGeneration.current += 1;
|
|
44
154
|
draftReadGeneration.current += 1;
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
155
|
+
pendingOperationRef.current = restorePendingComposerOperation(pendingOperationKey);
|
|
156
|
+
pendingClientEventId.current = pendingOperationRef.current?.input.clientEventId ?? null;
|
|
157
|
+
const shadow = pendingOperationRef.current?.newerShadow;
|
|
158
|
+
localEditRevision.current = shadow ? 1 : 0;
|
|
159
|
+
valueRef.current = shadow?.text ?? "";
|
|
48
160
|
draftRef.current = null;
|
|
49
|
-
restoredResourcesRef.current = [];
|
|
161
|
+
restoredResourcesRef.current = shadow?.resources ?? [];
|
|
50
162
|
lastSavedSignature.current = null;
|
|
51
163
|
saveChain.current = Promise.resolve();
|
|
52
164
|
setStateTargetKey(targetKey);
|
|
53
|
-
setValue("");
|
|
165
|
+
setValue(shadow?.text ?? "");
|
|
54
166
|
setSending(false);
|
|
55
167
|
setPausing(false);
|
|
56
168
|
setResuming(false);
|
|
@@ -59,13 +171,12 @@ function useComposer(sessionId, options = {}) {
|
|
|
59
171
|
setDraftLoading(Boolean(sessionId) && durableDrafts);
|
|
60
172
|
setDraftSaving(false);
|
|
61
173
|
setDraftConflict(null);
|
|
62
|
-
setRestoredResources([]);
|
|
63
|
-
}, [durableDrafts, sessionId, targetKey]);
|
|
174
|
+
setRestoredResources(shadow?.resources ?? []);
|
|
175
|
+
}, [durableDrafts, pendingOperationKey, sessionId, targetKey]);
|
|
64
176
|
const applyDraft = useCallback(
|
|
65
177
|
(next) => {
|
|
66
178
|
if (targetKeyRef.current !== targetKey) return;
|
|
67
179
|
if (!durableDrafts) {
|
|
68
|
-
pendingClientEventId.current = null;
|
|
69
180
|
localEditRevision.current += 1;
|
|
70
181
|
valueRef.current = next.text;
|
|
71
182
|
restoredResourcesRef.current = next.resources;
|
|
@@ -74,6 +185,17 @@ function useComposer(sessionId, options = {}) {
|
|
|
74
185
|
setDraft(null);
|
|
75
186
|
setValue(next.text);
|
|
76
187
|
setRestoredResources(next.resources);
|
|
188
|
+
pendingOperationRef.current = updatePendingComposerShadow(
|
|
189
|
+
pendingOperationKey,
|
|
190
|
+
pendingOperationRef.current,
|
|
191
|
+
{
|
|
192
|
+
text: next.text,
|
|
193
|
+
resources: mergeResources(
|
|
194
|
+
next.resources,
|
|
195
|
+
resolveSendExtras(sendExtrasRef.current).resources ?? []
|
|
196
|
+
)
|
|
197
|
+
}
|
|
198
|
+
);
|
|
77
199
|
setDraftConflict(null);
|
|
78
200
|
return;
|
|
79
201
|
}
|
|
@@ -82,14 +204,24 @@ function useComposer(sessionId, options = {}) {
|
|
|
82
204
|
restoredResourcesRef.current = next.resources;
|
|
83
205
|
lastSavedSignature.current = draftSignature(draftPayload(next));
|
|
84
206
|
localEditRevision.current += 1;
|
|
85
|
-
pendingClientEventId.current = null;
|
|
86
207
|
setDraft(next);
|
|
87
208
|
setValue(next.text);
|
|
88
209
|
setRestoredResources(next.resources);
|
|
210
|
+
pendingOperationRef.current = updatePendingComposerShadow(
|
|
211
|
+
pendingOperationKey,
|
|
212
|
+
pendingOperationRef.current,
|
|
213
|
+
{
|
|
214
|
+
text: next.text,
|
|
215
|
+
resources: mergeResources(
|
|
216
|
+
next.resources,
|
|
217
|
+
resolveSendExtras(sendExtrasRef.current).resources ?? []
|
|
218
|
+
)
|
|
219
|
+
}
|
|
220
|
+
);
|
|
89
221
|
setDraftConflict(null);
|
|
90
|
-
|
|
222
|
+
onDraftAppliedRef.current?.(next);
|
|
91
223
|
},
|
|
92
|
-
[durableDrafts,
|
|
224
|
+
[durableDrafts, pendingOperationKey, targetKey]
|
|
93
225
|
);
|
|
94
226
|
const loadDraft = useCallback(
|
|
95
227
|
async (replaceLocal) => {
|
|
@@ -123,13 +255,20 @@ function useComposer(sessionId, options = {}) {
|
|
|
123
255
|
draftRef.current = fetched;
|
|
124
256
|
setDraft(fetched);
|
|
125
257
|
setDraftConflict(null);
|
|
126
|
-
|
|
258
|
+
const shadow = pendingOperationRef.current?.newerShadow;
|
|
259
|
+
if (shadow) {
|
|
260
|
+
valueRef.current = shadow.text;
|
|
261
|
+
restoredResourcesRef.current = shadow.resources;
|
|
262
|
+
localEditRevision.current ||= 1;
|
|
263
|
+
setValue(shadow.text);
|
|
264
|
+
setRestoredResources(shadow.resources);
|
|
265
|
+
} else if (replaceLocal || !localWasDirtyAtStart && localAtStart === localEditRevision.current) {
|
|
127
266
|
valueRef.current = fetched.text;
|
|
128
267
|
restoredResourcesRef.current = fetched.resources;
|
|
129
268
|
lastSavedSignature.current = draftSignature(draftPayload(fetched));
|
|
130
269
|
setValue(fetched.text);
|
|
131
270
|
setRestoredResources(fetched.resources);
|
|
132
|
-
|
|
271
|
+
onDraftAppliedRef.current?.(fetched);
|
|
133
272
|
}
|
|
134
273
|
}
|
|
135
274
|
} catch (cause) {
|
|
@@ -142,7 +281,7 @@ function useComposer(sessionId, options = {}) {
|
|
|
142
281
|
}
|
|
143
282
|
}
|
|
144
283
|
},
|
|
145
|
-
[client, durableDrafts,
|
|
284
|
+
[client, durableDrafts, sessionId, targetKey, workspaceId]
|
|
146
285
|
);
|
|
147
286
|
useEffect(() => {
|
|
148
287
|
if (!sessionId || !durableDrafts) {
|
|
@@ -207,7 +346,7 @@ function useComposer(sessionId, options = {}) {
|
|
|
207
346
|
} catch (cause) {
|
|
208
347
|
if (targetKeyRef.current === ownedTargetKey && targetGeneration.current === ownedGeneration) {
|
|
209
348
|
const problem = asError(cause);
|
|
210
|
-
setDraftConflict(problem);
|
|
349
|
+
if (isDraftConflictError(problem)) setDraftConflict(problem);
|
|
211
350
|
setError(problem);
|
|
212
351
|
}
|
|
213
352
|
} finally {
|
|
@@ -223,6 +362,24 @@ function useComposer(sessionId, options = {}) {
|
|
|
223
362
|
[client, durableDrafts, sessionId, targetKey, workspaceId]
|
|
224
363
|
);
|
|
225
364
|
useEffect(() => {
|
|
365
|
+
const pending = pendingOperationRef.current;
|
|
366
|
+
if (pending) {
|
|
367
|
+
const shadow = {
|
|
368
|
+
text: valueRef.current,
|
|
369
|
+
resources: mergeResources(
|
|
370
|
+
restoredResourcesRef.current,
|
|
371
|
+
resolveSendExtras(sendExtrasRef.current).resources ?? []
|
|
372
|
+
)
|
|
373
|
+
};
|
|
374
|
+
if (pending.newerShadow.text !== shadow.text || JSON.stringify(pending.newerShadow.resources) !== JSON.stringify(shadow.resources)) {
|
|
375
|
+
pendingOperationRef.current = updatePendingComposerShadow(
|
|
376
|
+
pendingOperationKey,
|
|
377
|
+
pending,
|
|
378
|
+
shadow
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
226
383
|
if (!durableDrafts || !sessionId || draftLoading || sending || !draftRef.current || draftConflict)
|
|
227
384
|
return;
|
|
228
385
|
const payload = currentDraftPayload();
|
|
@@ -235,6 +392,7 @@ function useComposer(sessionId, options = {}) {
|
|
|
235
392
|
draftConflict,
|
|
236
393
|
draftLoading,
|
|
237
394
|
liveExtrasVersion,
|
|
395
|
+
pendingOperationKey,
|
|
238
396
|
persistPayload,
|
|
239
397
|
sending,
|
|
240
398
|
sessionId
|
|
@@ -243,18 +401,110 @@ function useComposer(sessionId, options = {}) {
|
|
|
243
401
|
async (delivery, explicit) => {
|
|
244
402
|
const ownedTargetKey = targetKey;
|
|
245
403
|
const ownedGeneration = targetGeneration.current;
|
|
404
|
+
const operationKey = pendingComposerOperationKey(workspaceId, sessionId);
|
|
405
|
+
const pending = pendingOperationRef.current ?? restorePendingComposerOperation(operationKey);
|
|
406
|
+
if (pending && !pendingOperationRef.current) {
|
|
407
|
+
pendingOperationRef.current = pending;
|
|
408
|
+
pendingClientEventId.current = pending.input.clientEventId ?? null;
|
|
409
|
+
}
|
|
246
410
|
const draftAtSend = value;
|
|
247
411
|
const rawText = explicit ?? draftAtSend;
|
|
248
412
|
const hasText = rawText.trim().length > 0;
|
|
249
|
-
const extras = resolveSendExtras(sendExtrasRef.current);
|
|
413
|
+
const extras = pending ? {} : resolveSendExtras(sendExtrasRef.current);
|
|
250
414
|
const hasResources = restoredResources.length > 0 || (extras.resources?.length ?? 0) > 0;
|
|
251
|
-
if (!hasText && !hasResources || !sessionId || sending || sendBlockedRef.current?.() === true || targetKeyRef.current !== ownedTargetKey) {
|
|
415
|
+
if (!pending && !hasText && !hasResources || !sessionId || sending || sendBlockedRef.current?.() === true || targetKeyRef.current !== ownedTargetKey) {
|
|
252
416
|
return false;
|
|
253
417
|
}
|
|
254
|
-
|
|
418
|
+
const clearPending = () => {
|
|
419
|
+
pendingOperationRef.current = null;
|
|
420
|
+
pendingClientEventId.current = null;
|
|
421
|
+
forgetPendingComposerOperation(operationKey);
|
|
422
|
+
};
|
|
423
|
+
const settleAccepted = (operation) => {
|
|
424
|
+
clearPending();
|
|
425
|
+
const draftWasUnchanged = valueRef.current === operation.draftAtSend;
|
|
426
|
+
const resourcesWereUnchanged = JSON.stringify(restoredResourcesRef.current) === JSON.stringify(operation.resourcesAtSend);
|
|
427
|
+
const previousDraft = draftRef.current;
|
|
428
|
+
if (previousDraft) {
|
|
429
|
+
const cleared = {
|
|
430
|
+
...previousDraft,
|
|
431
|
+
revision: 0,
|
|
432
|
+
text: "",
|
|
433
|
+
resources: [],
|
|
434
|
+
sourceTurnId: null,
|
|
435
|
+
sourceTurnVersion: null,
|
|
436
|
+
updatedAt: null
|
|
437
|
+
};
|
|
438
|
+
draftRef.current = cleared;
|
|
439
|
+
setDraft(cleared);
|
|
440
|
+
lastSavedSignature.current = draftSignature(draftPayload(cleared));
|
|
441
|
+
}
|
|
442
|
+
if (resourcesWereUnchanged) {
|
|
443
|
+
restoredResourcesRef.current = [];
|
|
444
|
+
setRestoredResources([]);
|
|
445
|
+
}
|
|
446
|
+
if (operation.clearDraftOnAccept && draftWasUnchanged) {
|
|
447
|
+
valueRef.current = "";
|
|
448
|
+
setValue("");
|
|
449
|
+
}
|
|
450
|
+
onSent?.(operation.input.text, operation.input);
|
|
451
|
+
};
|
|
452
|
+
const deliver = async (operation) => {
|
|
453
|
+
if (operation.delivery === "steer") {
|
|
454
|
+
await client.steerMessage(workspaceId, sessionId, operation.input);
|
|
455
|
+
} else {
|
|
456
|
+
await client.sendMessage(workspaceId, sessionId, operation.input);
|
|
457
|
+
}
|
|
458
|
+
};
|
|
255
459
|
setSending(true);
|
|
256
460
|
setError(null);
|
|
257
461
|
try {
|
|
462
|
+
if (pending) {
|
|
463
|
+
let accepted = false;
|
|
464
|
+
try {
|
|
465
|
+
const events = await client.listEvents(workspaceId, sessionId, {
|
|
466
|
+
includeTypes: ["user.message"],
|
|
467
|
+
limit: 100,
|
|
468
|
+
payloadMode: "none"
|
|
469
|
+
});
|
|
470
|
+
accepted = events.some(
|
|
471
|
+
(event) => event.type === "user.message" && event.clientEventId === pending.input.clientEventId
|
|
472
|
+
);
|
|
473
|
+
} catch (cause) {
|
|
474
|
+
if (targetKeyRef.current === ownedTargetKey && targetGeneration.current === ownedGeneration) {
|
|
475
|
+
setError(asError(cause));
|
|
476
|
+
}
|
|
477
|
+
return false;
|
|
478
|
+
}
|
|
479
|
+
if (targetKeyRef.current !== ownedTargetKey || targetGeneration.current !== ownedGeneration) {
|
|
480
|
+
return false;
|
|
481
|
+
}
|
|
482
|
+
if (accepted) {
|
|
483
|
+
settleAccepted(pending);
|
|
484
|
+
return true;
|
|
485
|
+
}
|
|
486
|
+
if (!pending.canRetry) {
|
|
487
|
+
setError(
|
|
488
|
+
new Error(
|
|
489
|
+
"OpenGeni cannot safely retry this uncertain request after remount; reconcile the session before sending again."
|
|
490
|
+
)
|
|
491
|
+
);
|
|
492
|
+
return false;
|
|
493
|
+
}
|
|
494
|
+
try {
|
|
495
|
+
await deliver(pending);
|
|
496
|
+
} catch (cause) {
|
|
497
|
+
if (targetKeyRef.current === ownedTargetKey && targetGeneration.current === ownedGeneration) {
|
|
498
|
+
setError(asError(cause));
|
|
499
|
+
}
|
|
500
|
+
return false;
|
|
501
|
+
}
|
|
502
|
+
if (targetKeyRef.current !== ownedTargetKey || targetGeneration.current !== ownedGeneration) {
|
|
503
|
+
return false;
|
|
504
|
+
}
|
|
505
|
+
settleAccepted(pending);
|
|
506
|
+
return true;
|
|
507
|
+
}
|
|
258
508
|
const sendText = hasText ? rawText : FILE_ONLY_MESSAGE_TEXT;
|
|
259
509
|
const currentPayload = currentDraftPayload();
|
|
260
510
|
const payload = currentPayload ? { ...currentPayload, text: sendText } : null;
|
|
@@ -264,50 +514,42 @@ function useComposer(sessionId, options = {}) {
|
|
|
264
514
|
}
|
|
265
515
|
const acknowledgedDraft = draftRef.current;
|
|
266
516
|
const sendExtras = acknowledgedDraft?.toolsProvided === true && !Object.prototype.hasOwnProperty.call(extras, "tools") ? { ...extras, tools: acknowledgedDraft.tools } : extras;
|
|
517
|
+
pendingClientEventId.current ??= generateClientEventId();
|
|
267
518
|
const input = composeSendInput(sendText, pendingClientEventId.current, sendExtras, {
|
|
268
519
|
...options.effectiveControl?.controlEtag ? { controlEtag: options.effectiveControl.controlEtag } : {},
|
|
269
520
|
...durableDrafts && draftRef.current ? { expectedDraftRevision: draftRef.current.revision } : {},
|
|
270
521
|
resources: mergeResources(restoredResources, extras.resources ?? [])
|
|
271
522
|
});
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
523
|
+
const operation = {
|
|
524
|
+
delivery,
|
|
525
|
+
input,
|
|
526
|
+
draftAtSend,
|
|
527
|
+
resourcesAtSend: [...restoredResources],
|
|
528
|
+
newerShadow: {
|
|
529
|
+
text: draftAtSend,
|
|
530
|
+
resources: mergeResources(restoredResources, extras.resources ?? [])
|
|
531
|
+
},
|
|
532
|
+
clearDraftOnAccept: explicit === void 0,
|
|
533
|
+
canRetry: true
|
|
534
|
+
};
|
|
535
|
+
pendingOperationRef.current = operation;
|
|
536
|
+
rememberPendingComposerOperation(operationKey, operation);
|
|
537
|
+
try {
|
|
538
|
+
await deliver(operation);
|
|
539
|
+
} catch (cause) {
|
|
540
|
+
if (!isOutcomeUnknownError(cause)) {
|
|
541
|
+
clearPending();
|
|
542
|
+
}
|
|
543
|
+
if (targetKeyRef.current === ownedTargetKey && targetGeneration.current === ownedGeneration) {
|
|
544
|
+
setError(asError(cause));
|
|
545
|
+
}
|
|
546
|
+
return false;
|
|
276
547
|
}
|
|
277
548
|
if (targetKeyRef.current !== ownedTargetKey || targetGeneration.current !== ownedGeneration) {
|
|
278
549
|
return false;
|
|
279
550
|
}
|
|
280
|
-
|
|
281
|
-
const previousDraft = draftRef.current;
|
|
282
|
-
if (previousDraft) {
|
|
283
|
-
const cleared = {
|
|
284
|
-
...previousDraft,
|
|
285
|
-
revision: 0,
|
|
286
|
-
text: "",
|
|
287
|
-
resources: [],
|
|
288
|
-
sourceTurnId: null,
|
|
289
|
-
sourceTurnVersion: null,
|
|
290
|
-
updatedAt: null
|
|
291
|
-
};
|
|
292
|
-
draftRef.current = cleared;
|
|
293
|
-
restoredResourcesRef.current = [];
|
|
294
|
-
setDraft(cleared);
|
|
295
|
-
setRestoredResources([]);
|
|
296
|
-
lastSavedSignature.current = draftSignature(draftPayload(cleared));
|
|
297
|
-
}
|
|
298
|
-
if (explicit === void 0) {
|
|
299
|
-
if (valueRef.current === draftAtSend) {
|
|
300
|
-
valueRef.current = "";
|
|
301
|
-
setValue("");
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
onSent?.(sendText, input);
|
|
551
|
+
settleAccepted(operation);
|
|
305
552
|
return true;
|
|
306
|
-
} catch (cause) {
|
|
307
|
-
if (targetKeyRef.current === ownedTargetKey && targetGeneration.current === ownedGeneration) {
|
|
308
|
-
setError(cause instanceof Error ? cause : new Error(String(cause)));
|
|
309
|
-
}
|
|
310
|
-
return false;
|
|
311
553
|
} finally {
|
|
312
554
|
if (targetKeyRef.current === ownedTargetKey && targetGeneration.current === ownedGeneration) {
|
|
313
555
|
setSending(false);
|
|
@@ -332,6 +574,7 @@ function useComposer(sessionId, options = {}) {
|
|
|
332
574
|
const send = useCallback(async (text) => await dispatch("send", text), [dispatch]);
|
|
333
575
|
const steer = useCallback(async (text) => await dispatch("steer", text), [dispatch]);
|
|
334
576
|
const hasReadyResources = restoredResources.length > 0 || (resolveSendExtras(sendExtrasRef.current).resources?.length ?? 0) > 0;
|
|
577
|
+
const hasPendingOperation = pendingOperationRef.current !== null;
|
|
335
578
|
const pause = useCallback(
|
|
336
579
|
async (reason) => {
|
|
337
580
|
const ownedTargetKey = targetKey;
|
|
@@ -432,12 +675,22 @@ function useComposer(sessionId, options = {}) {
|
|
|
432
675
|
const updateValue = useCallback(
|
|
433
676
|
(next) => {
|
|
434
677
|
if (targetKeyRef.current !== targetKey) return;
|
|
435
|
-
pendingClientEventId.current = null;
|
|
436
678
|
localEditRevision.current += 1;
|
|
437
679
|
valueRef.current = next;
|
|
680
|
+
pendingOperationRef.current = updatePendingComposerShadow(
|
|
681
|
+
pendingOperationKey,
|
|
682
|
+
pendingOperationRef.current,
|
|
683
|
+
{
|
|
684
|
+
text: next,
|
|
685
|
+
resources: mergeResources(
|
|
686
|
+
restoredResourcesRef.current,
|
|
687
|
+
resolveSendExtras(sendExtrasRef.current).resources ?? []
|
|
688
|
+
)
|
|
689
|
+
}
|
|
690
|
+
);
|
|
438
691
|
setValue(next);
|
|
439
692
|
},
|
|
440
|
-
[targetKey]
|
|
693
|
+
[pendingOperationKey, targetKey]
|
|
441
694
|
);
|
|
442
695
|
const removeRestoredResource = useCallback(
|
|
443
696
|
(index) => {
|
|
@@ -445,9 +698,17 @@ function useComposer(sessionId, options = {}) {
|
|
|
445
698
|
localEditRevision.current += 1;
|
|
446
699
|
const next = restoredResourcesRef.current.filter((_, candidate) => candidate !== index);
|
|
447
700
|
restoredResourcesRef.current = next;
|
|
701
|
+
pendingOperationRef.current = updatePendingComposerShadow(
|
|
702
|
+
pendingOperationKey,
|
|
703
|
+
pendingOperationRef.current,
|
|
704
|
+
{
|
|
705
|
+
text: valueRef.current,
|
|
706
|
+
resources: mergeResources(next, resolveSendExtras(sendExtrasRef.current).resources ?? [])
|
|
707
|
+
}
|
|
708
|
+
);
|
|
448
709
|
setRestoredResources(next);
|
|
449
710
|
},
|
|
450
|
-
[targetKey]
|
|
711
|
+
[pendingOperationKey, targetKey]
|
|
451
712
|
);
|
|
452
713
|
const hasDraftContent = useCallback(() => {
|
|
453
714
|
const current = draftRef.current;
|
|
@@ -500,7 +761,7 @@ function useComposer(sessionId, options = {}) {
|
|
|
500
761
|
send,
|
|
501
762
|
steer,
|
|
502
763
|
sending: identityMatches ? sending : false,
|
|
503
|
-
canSend: identityMatches && Boolean(sessionId) && !sending && sendBlockedRef.current?.() !== true && (value.trim().length > 0 || hasReadyResources),
|
|
764
|
+
canSend: identityMatches && Boolean(sessionId) && !sending && sendBlockedRef.current?.() !== true && (hasPendingOperation || value.trim().length > 0 || hasReadyResources),
|
|
504
765
|
pause,
|
|
505
766
|
pausing: identityMatches ? pausing : false,
|
|
506
767
|
resume,
|
|
@@ -543,9 +804,16 @@ function shouldSteerOnKey(event) {
|
|
|
543
804
|
function generateClientEventId() {
|
|
544
805
|
return globalThis.crypto.randomUUID();
|
|
545
806
|
}
|
|
807
|
+
function isOutcomeUnknownError(cause) {
|
|
808
|
+
return typeof cause === "object" && cause !== null && cause.outcomeUnknown === true;
|
|
809
|
+
}
|
|
546
810
|
function asError(cause) {
|
|
547
811
|
return cause instanceof Error ? cause : new Error(String(cause));
|
|
548
812
|
}
|
|
813
|
+
function isDraftConflictError(error) {
|
|
814
|
+
const apiError = error;
|
|
815
|
+
return apiError.status === 409 && apiError.outcomeUnknown === false && (apiError.code === void 0 || apiError.code === "conflict" || apiError.code === "idempotency_conflict");
|
|
816
|
+
}
|
|
549
817
|
function draftPayload(draft) {
|
|
550
818
|
return {
|
|
551
819
|
expectedRevision: draft.revision,
|
|
@@ -590,4 +858,4 @@ export {
|
|
|
590
858
|
shouldSubmitOnKey,
|
|
591
859
|
shouldSteerOnKey
|
|
592
860
|
};
|
|
593
|
-
//# sourceMappingURL=chunk-
|
|
861
|
+
//# sourceMappingURL=chunk-F4CFWKVI.js.map
|