@opengeni/react 0.23.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-UQS7OQDE.js → chunk-F4CFWKVI.js} +334 -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-UAHPKLB6.js → chunk-MP6237JB.js} +571 -975
- 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-CATATY77.js → chunk-TMH6HZWF.js} +8 -7
- package/dist/chunk-TMH6HZWF.js.map +1 -0
- 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-CVm5uoUI.d.ts → composer-DoC4veX1.d.ts} +6 -63
- package/dist/composer.d.ts +3 -2
- package/dist/composer.js +4 -3
- package/dist/fleet-decision-projection-SLNZMS42.js +241 -0
- package/dist/fleet-decision-projection-SLNZMS42.js.map +1 -0
- package/dist/fleet-decision-row-GGCAT4E4.js +351 -0
- package/dist/fleet-decision-row-GGCAT4E4.js.map +1 -0
- package/dist/index.d.ts +17 -259
- package/dist/index.js +1042 -5058
- 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 +4 -2
- package/dist/session.js +26 -9
- package/dist/{use-composer-By1wn3MM.d.ts → use-composer-BnEzheSk.d.ts} +8 -2
- package/dist/use-file-attachments-C0kpHrs9.d.ts +76 -0
- package/dist/use-turn-queue-3rkJwjFv.d.ts +290 -0
- package/package.json +6 -2
- package/src/components/composer.tsx +7 -6
- package/src/components/message-timeline.tsx +94 -54
- package/src/hooks/use-composer.ts +470 -73
- package/src/hooks/use-file-attachments.ts +96 -3
- package/src/hooks/use-goal.ts +23 -5
- 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/hooks/use-workspace-sessions.ts +30 -5
- package/src/session-ui.ts +13 -0
- package/src/session.ts +15 -0
- package/src/timeline/activity-rail.tsx +18 -2
- package/src/timeline/fleet-decision-projection.ts +366 -0
- package/src/timeline/fleet-decision-row.tsx +412 -0
- package/src/timeline/index.ts +2 -0
- package/src/timeline/projection.ts +19 -8
- package/src/timeline/tool-display-name.ts +16 -0
- package/src/timeline/tool-renderers.tsx +1 -1
- package/src/timeline/types.ts +68 -1
- package/dist/chunk-CATATY77.js.map +0 -1
- package/dist/chunk-I3BJZIG5.js.map +0 -1
- package/dist/chunk-UAHPKLB6.js.map +0 -1
- package/dist/chunk-UQS7OQDE.js.map +0 -1
- package/dist/session-Db00TRwl.d.ts +0 -446
|
@@ -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,20 +120,31 @@ 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;
|
|
146
|
+
const sendBlockedRef = useRef(options.sendBlocked);
|
|
147
|
+
sendBlockedRef.current = options.sendBlocked;
|
|
36
148
|
const liveExtrasVersion = JSON.stringify(resolveSendExtras(options.sendExtras));
|
|
37
149
|
const targetKeyRef = useRef(targetKey);
|
|
38
150
|
useLayoutEffect(() => {
|
|
@@ -40,15 +152,17 @@ function useComposer(sessionId, options = {}) {
|
|
|
40
152
|
targetKeyRef.current = targetKey;
|
|
41
153
|
targetGeneration.current += 1;
|
|
42
154
|
draftReadGeneration.current += 1;
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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 ?? "";
|
|
46
160
|
draftRef.current = null;
|
|
47
|
-
restoredResourcesRef.current = [];
|
|
161
|
+
restoredResourcesRef.current = shadow?.resources ?? [];
|
|
48
162
|
lastSavedSignature.current = null;
|
|
49
163
|
saveChain.current = Promise.resolve();
|
|
50
164
|
setStateTargetKey(targetKey);
|
|
51
|
-
setValue("");
|
|
165
|
+
setValue(shadow?.text ?? "");
|
|
52
166
|
setSending(false);
|
|
53
167
|
setPausing(false);
|
|
54
168
|
setResuming(false);
|
|
@@ -57,13 +171,12 @@ function useComposer(sessionId, options = {}) {
|
|
|
57
171
|
setDraftLoading(Boolean(sessionId) && durableDrafts);
|
|
58
172
|
setDraftSaving(false);
|
|
59
173
|
setDraftConflict(null);
|
|
60
|
-
setRestoredResources([]);
|
|
61
|
-
}, [durableDrafts, sessionId, targetKey]);
|
|
174
|
+
setRestoredResources(shadow?.resources ?? []);
|
|
175
|
+
}, [durableDrafts, pendingOperationKey, sessionId, targetKey]);
|
|
62
176
|
const applyDraft = useCallback(
|
|
63
177
|
(next) => {
|
|
64
178
|
if (targetKeyRef.current !== targetKey) return;
|
|
65
179
|
if (!durableDrafts) {
|
|
66
|
-
pendingClientEventId.current = null;
|
|
67
180
|
localEditRevision.current += 1;
|
|
68
181
|
valueRef.current = next.text;
|
|
69
182
|
restoredResourcesRef.current = next.resources;
|
|
@@ -72,6 +185,17 @@ function useComposer(sessionId, options = {}) {
|
|
|
72
185
|
setDraft(null);
|
|
73
186
|
setValue(next.text);
|
|
74
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
|
+
);
|
|
75
199
|
setDraftConflict(null);
|
|
76
200
|
return;
|
|
77
201
|
}
|
|
@@ -80,14 +204,24 @@ function useComposer(sessionId, options = {}) {
|
|
|
80
204
|
restoredResourcesRef.current = next.resources;
|
|
81
205
|
lastSavedSignature.current = draftSignature(draftPayload(next));
|
|
82
206
|
localEditRevision.current += 1;
|
|
83
|
-
pendingClientEventId.current = null;
|
|
84
207
|
setDraft(next);
|
|
85
208
|
setValue(next.text);
|
|
86
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
|
+
);
|
|
87
221
|
setDraftConflict(null);
|
|
88
|
-
|
|
222
|
+
onDraftAppliedRef.current?.(next);
|
|
89
223
|
},
|
|
90
|
-
[durableDrafts,
|
|
224
|
+
[durableDrafts, pendingOperationKey, targetKey]
|
|
91
225
|
);
|
|
92
226
|
const loadDraft = useCallback(
|
|
93
227
|
async (replaceLocal) => {
|
|
@@ -121,13 +255,20 @@ function useComposer(sessionId, options = {}) {
|
|
|
121
255
|
draftRef.current = fetched;
|
|
122
256
|
setDraft(fetched);
|
|
123
257
|
setDraftConflict(null);
|
|
124
|
-
|
|
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) {
|
|
125
266
|
valueRef.current = fetched.text;
|
|
126
267
|
restoredResourcesRef.current = fetched.resources;
|
|
127
268
|
lastSavedSignature.current = draftSignature(draftPayload(fetched));
|
|
128
269
|
setValue(fetched.text);
|
|
129
270
|
setRestoredResources(fetched.resources);
|
|
130
|
-
|
|
271
|
+
onDraftAppliedRef.current?.(fetched);
|
|
131
272
|
}
|
|
132
273
|
}
|
|
133
274
|
} catch (cause) {
|
|
@@ -140,7 +281,7 @@ function useComposer(sessionId, options = {}) {
|
|
|
140
281
|
}
|
|
141
282
|
}
|
|
142
283
|
},
|
|
143
|
-
[client, durableDrafts,
|
|
284
|
+
[client, durableDrafts, sessionId, targetKey, workspaceId]
|
|
144
285
|
);
|
|
145
286
|
useEffect(() => {
|
|
146
287
|
if (!sessionId || !durableDrafts) {
|
|
@@ -205,7 +346,7 @@ function useComposer(sessionId, options = {}) {
|
|
|
205
346
|
} catch (cause) {
|
|
206
347
|
if (targetKeyRef.current === ownedTargetKey && targetGeneration.current === ownedGeneration) {
|
|
207
348
|
const problem = asError(cause);
|
|
208
|
-
setDraftConflict(problem);
|
|
349
|
+
if (isDraftConflictError(problem)) setDraftConflict(problem);
|
|
209
350
|
setError(problem);
|
|
210
351
|
}
|
|
211
352
|
} finally {
|
|
@@ -221,6 +362,24 @@ function useComposer(sessionId, options = {}) {
|
|
|
221
362
|
[client, durableDrafts, sessionId, targetKey, workspaceId]
|
|
222
363
|
);
|
|
223
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
|
+
}
|
|
224
383
|
if (!durableDrafts || !sessionId || draftLoading || sending || !draftRef.current || draftConflict)
|
|
225
384
|
return;
|
|
226
385
|
const payload = currentDraftPayload();
|
|
@@ -233,6 +392,7 @@ function useComposer(sessionId, options = {}) {
|
|
|
233
392
|
draftConflict,
|
|
234
393
|
draftLoading,
|
|
235
394
|
liveExtrasVersion,
|
|
395
|
+
pendingOperationKey,
|
|
236
396
|
persistPayload,
|
|
237
397
|
sending,
|
|
238
398
|
sessionId
|
|
@@ -241,18 +401,110 @@ function useComposer(sessionId, options = {}) {
|
|
|
241
401
|
async (delivery, explicit) => {
|
|
242
402
|
const ownedTargetKey = targetKey;
|
|
243
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
|
+
}
|
|
244
410
|
const draftAtSend = value;
|
|
245
411
|
const rawText = explicit ?? draftAtSend;
|
|
246
412
|
const hasText = rawText.trim().length > 0;
|
|
247
|
-
const extras = resolveSendExtras(sendExtrasRef.current);
|
|
413
|
+
const extras = pending ? {} : resolveSendExtras(sendExtrasRef.current);
|
|
248
414
|
const hasResources = restoredResources.length > 0 || (extras.resources?.length ?? 0) > 0;
|
|
249
|
-
if (!hasText && !hasResources || !sessionId || sending || targetKeyRef.current !== ownedTargetKey) {
|
|
415
|
+
if (!pending && !hasText && !hasResources || !sessionId || sending || sendBlockedRef.current?.() === true || targetKeyRef.current !== ownedTargetKey) {
|
|
250
416
|
return false;
|
|
251
417
|
}
|
|
252
|
-
|
|
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
|
+
};
|
|
253
459
|
setSending(true);
|
|
254
460
|
setError(null);
|
|
255
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
|
+
}
|
|
256
508
|
const sendText = hasText ? rawText : FILE_ONLY_MESSAGE_TEXT;
|
|
257
509
|
const currentPayload = currentDraftPayload();
|
|
258
510
|
const payload = currentPayload ? { ...currentPayload, text: sendText } : null;
|
|
@@ -262,50 +514,42 @@ function useComposer(sessionId, options = {}) {
|
|
|
262
514
|
}
|
|
263
515
|
const acknowledgedDraft = draftRef.current;
|
|
264
516
|
const sendExtras = acknowledgedDraft?.toolsProvided === true && !Object.prototype.hasOwnProperty.call(extras, "tools") ? { ...extras, tools: acknowledgedDraft.tools } : extras;
|
|
517
|
+
pendingClientEventId.current ??= generateClientEventId();
|
|
265
518
|
const input = composeSendInput(sendText, pendingClientEventId.current, sendExtras, {
|
|
266
519
|
...options.effectiveControl?.controlEtag ? { controlEtag: options.effectiveControl.controlEtag } : {},
|
|
267
520
|
...durableDrafts && draftRef.current ? { expectedDraftRevision: draftRef.current.revision } : {},
|
|
268
521
|
resources: mergeResources(restoredResources, extras.resources ?? [])
|
|
269
522
|
});
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
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;
|
|
274
547
|
}
|
|
275
548
|
if (targetKeyRef.current !== ownedTargetKey || targetGeneration.current !== ownedGeneration) {
|
|
276
549
|
return false;
|
|
277
550
|
}
|
|
278
|
-
|
|
279
|
-
const previousDraft = draftRef.current;
|
|
280
|
-
if (previousDraft) {
|
|
281
|
-
const cleared = {
|
|
282
|
-
...previousDraft,
|
|
283
|
-
revision: 0,
|
|
284
|
-
text: "",
|
|
285
|
-
resources: [],
|
|
286
|
-
sourceTurnId: null,
|
|
287
|
-
sourceTurnVersion: null,
|
|
288
|
-
updatedAt: null
|
|
289
|
-
};
|
|
290
|
-
draftRef.current = cleared;
|
|
291
|
-
restoredResourcesRef.current = [];
|
|
292
|
-
setDraft(cleared);
|
|
293
|
-
setRestoredResources([]);
|
|
294
|
-
lastSavedSignature.current = draftSignature(draftPayload(cleared));
|
|
295
|
-
}
|
|
296
|
-
if (explicit === void 0) {
|
|
297
|
-
if (valueRef.current === draftAtSend) {
|
|
298
|
-
valueRef.current = "";
|
|
299
|
-
setValue("");
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
onSent?.(sendText);
|
|
551
|
+
settleAccepted(operation);
|
|
303
552
|
return true;
|
|
304
|
-
} catch (cause) {
|
|
305
|
-
if (targetKeyRef.current === ownedTargetKey && targetGeneration.current === ownedGeneration) {
|
|
306
|
-
setError(cause instanceof Error ? cause : new Error(String(cause)));
|
|
307
|
-
}
|
|
308
|
-
return false;
|
|
309
553
|
} finally {
|
|
310
554
|
if (targetKeyRef.current === ownedTargetKey && targetGeneration.current === ownedGeneration) {
|
|
311
555
|
setSending(false);
|
|
@@ -330,6 +574,7 @@ function useComposer(sessionId, options = {}) {
|
|
|
330
574
|
const send = useCallback(async (text) => await dispatch("send", text), [dispatch]);
|
|
331
575
|
const steer = useCallback(async (text) => await dispatch("steer", text), [dispatch]);
|
|
332
576
|
const hasReadyResources = restoredResources.length > 0 || (resolveSendExtras(sendExtrasRef.current).resources?.length ?? 0) > 0;
|
|
577
|
+
const hasPendingOperation = pendingOperationRef.current !== null;
|
|
333
578
|
const pause = useCallback(
|
|
334
579
|
async (reason) => {
|
|
335
580
|
const ownedTargetKey = targetKey;
|
|
@@ -430,12 +675,22 @@ function useComposer(sessionId, options = {}) {
|
|
|
430
675
|
const updateValue = useCallback(
|
|
431
676
|
(next) => {
|
|
432
677
|
if (targetKeyRef.current !== targetKey) return;
|
|
433
|
-
pendingClientEventId.current = null;
|
|
434
678
|
localEditRevision.current += 1;
|
|
435
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
|
+
);
|
|
436
691
|
setValue(next);
|
|
437
692
|
},
|
|
438
|
-
[targetKey]
|
|
693
|
+
[pendingOperationKey, targetKey]
|
|
439
694
|
);
|
|
440
695
|
const removeRestoredResource = useCallback(
|
|
441
696
|
(index) => {
|
|
@@ -443,9 +698,17 @@ function useComposer(sessionId, options = {}) {
|
|
|
443
698
|
localEditRevision.current += 1;
|
|
444
699
|
const next = restoredResourcesRef.current.filter((_, candidate) => candidate !== index);
|
|
445
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
|
+
);
|
|
446
709
|
setRestoredResources(next);
|
|
447
710
|
},
|
|
448
|
-
[targetKey]
|
|
711
|
+
[pendingOperationKey, targetKey]
|
|
449
712
|
);
|
|
450
713
|
const hasDraftContent = useCallback(() => {
|
|
451
714
|
const current = draftRef.current;
|
|
@@ -498,7 +761,7 @@ function useComposer(sessionId, options = {}) {
|
|
|
498
761
|
send,
|
|
499
762
|
steer,
|
|
500
763
|
sending: identityMatches ? sending : false,
|
|
501
|
-
canSend: identityMatches && Boolean(sessionId) && !sending && (value.trim().length > 0 || hasReadyResources),
|
|
764
|
+
canSend: identityMatches && Boolean(sessionId) && !sending && sendBlockedRef.current?.() !== true && (hasPendingOperation || value.trim().length > 0 || hasReadyResources),
|
|
502
765
|
pause,
|
|
503
766
|
pausing: identityMatches ? pausing : false,
|
|
504
767
|
resume,
|
|
@@ -541,9 +804,16 @@ function shouldSteerOnKey(event) {
|
|
|
541
804
|
function generateClientEventId() {
|
|
542
805
|
return globalThis.crypto.randomUUID();
|
|
543
806
|
}
|
|
807
|
+
function isOutcomeUnknownError(cause) {
|
|
808
|
+
return typeof cause === "object" && cause !== null && cause.outcomeUnknown === true;
|
|
809
|
+
}
|
|
544
810
|
function asError(cause) {
|
|
545
811
|
return cause instanceof Error ? cause : new Error(String(cause));
|
|
546
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
|
+
}
|
|
547
817
|
function draftPayload(draft) {
|
|
548
818
|
return {
|
|
549
819
|
expectedRevision: draft.revision,
|
|
@@ -588,4 +858,4 @@ export {
|
|
|
588
858
|
shouldSubmitOnKey,
|
|
589
859
|
shouldSteerOnKey
|
|
590
860
|
};
|
|
591
|
-
//# sourceMappingURL=chunk-
|
|
861
|
+
//# sourceMappingURL=chunk-F4CFWKVI.js.map
|