dooers-agents-client 0.1.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.
package/dist/main.js ADDED
@@ -0,0 +1,927 @@
1
+ import { createContext, useRef, useEffect, useMemo, useCallback, useContext } from 'react';
2
+ import { useStore as useStore$1 } from 'zustand';
3
+ import { useShallow } from 'zustand/shallow';
4
+ import { createStore } from 'zustand/vanilla';
5
+ import { jsx } from 'react/jsx-runtime';
6
+
7
+ // src/hooks/use-analytics.ts
8
+
9
+ // src/types.ts
10
+ function isSettingsFieldGroup(item) {
11
+ return "fields" in item && Array.isArray(item.fields);
12
+ }
13
+ function toUser(w) {
14
+ if (!w) return void 0;
15
+ return {
16
+ userId: w.user_id,
17
+ userName: w.user_name,
18
+ userEmail: w.user_email,
19
+ systemRole: w.system_role,
20
+ organizationRole: w.organization_role,
21
+ workspaceRole: w.workspace_role
22
+ };
23
+ }
24
+ function toThread(w) {
25
+ return {
26
+ id: w.id,
27
+ workerId: w.worker_id,
28
+ organizationId: w.organization_id,
29
+ workspaceId: w.workspace_id,
30
+ owner: toUser(w.owner),
31
+ users: w.users.map((u) => toUser(u)),
32
+ title: w.title,
33
+ createdAt: w.created_at,
34
+ updatedAt: w.updated_at,
35
+ lastEventAt: w.last_event_at
36
+ };
37
+ }
38
+ function toContentPart(w) {
39
+ switch (w.type) {
40
+ case "text":
41
+ return { type: "text", text: w.text };
42
+ case "image":
43
+ return { type: "image", url: w.url, mimeType: w.mime_type, alt: w.alt };
44
+ case "document":
45
+ return { type: "document", url: w.url, filename: w.filename, mimeType: w.mime_type };
46
+ }
47
+ }
48
+ function toThreadEvent(w) {
49
+ return {
50
+ id: w.id,
51
+ threadId: w.thread_id,
52
+ runId: w.run_id,
53
+ type: w.type,
54
+ actor: w.actor,
55
+ author: w.author,
56
+ user: toUser(w.user),
57
+ content: w.content?.map(toContentPart),
58
+ data: w.data,
59
+ createdAt: w.created_at,
60
+ clientEventId: w.client_event_id
61
+ };
62
+ }
63
+ function toRun(w) {
64
+ return {
65
+ id: w.id,
66
+ threadId: w.thread_id,
67
+ agentId: w.agent_id,
68
+ status: w.status,
69
+ startedAt: w.started_at,
70
+ endedAt: w.ended_at,
71
+ error: w.error
72
+ };
73
+ }
74
+ function toSettingsField(w) {
75
+ return {
76
+ id: w.id,
77
+ type: w.type,
78
+ label: w.label,
79
+ required: w.required,
80
+ readonly: w.readonly,
81
+ value: w.value,
82
+ placeholder: w.placeholder,
83
+ options: w.options,
84
+ min: w.min,
85
+ max: w.max,
86
+ rows: w.rows,
87
+ src: w.src,
88
+ width: w.width,
89
+ height: w.height
90
+ };
91
+ }
92
+ function toSettingsItem(w) {
93
+ if ("fields" in w && Array.isArray(w.fields)) {
94
+ const g = w;
95
+ return {
96
+ id: g.id,
97
+ label: g.label,
98
+ fields: g.fields.map(toSettingsField),
99
+ collapsible: g.collapsible
100
+ };
101
+ }
102
+ return toSettingsField(w);
103
+ }
104
+ function toAnalyticsEvent(w) {
105
+ return {
106
+ event: w.event,
107
+ timestamp: w.timestamp,
108
+ workerId: w.worker_id,
109
+ threadId: w.thread_id,
110
+ userId: w.user_id,
111
+ runId: w.run_id,
112
+ eventId: w.event_id,
113
+ data: w.data
114
+ };
115
+ }
116
+ function toWireContentPart(p) {
117
+ switch (p.type) {
118
+ case "text":
119
+ return { type: "text", text: p.text };
120
+ case "image":
121
+ return { type: "image", url: p.url, mime_type: p.mimeType, alt: p.alt };
122
+ case "document":
123
+ return { type: "document", url: p.url, filename: p.filename, mime_type: p.mimeType };
124
+ }
125
+ }
126
+
127
+ // src/client.ts
128
+ var MAX_RECONNECT_ATTEMPTS = 5;
129
+ var RECONNECT_DELAYS = [1e3, 2e3, 4e3, 8e3, 16e3];
130
+ var SEND_MESSAGE_TIMEOUT = 3e4;
131
+ var WorkerClient = class {
132
+ ws = null;
133
+ callbacks;
134
+ onError = null;
135
+ url = "";
136
+ workerId = "";
137
+ config = {
138
+ organizationId: "",
139
+ workspaceId: "",
140
+ userId: ""
141
+ };
142
+ connectFrameId = "";
143
+ isIntentionallyClosed = false;
144
+ reconnectAttempts = 0;
145
+ reconnectTimer = null;
146
+ // Refcounted subscriptions
147
+ subscriptionRefs = /* @__PURE__ */ new Map();
148
+ // Track optimistic events for reconciliation
149
+ pendingOptimistic = /* @__PURE__ */ new Map();
150
+ // Pending message promises — resolved when event.append arrives with matching clientEventId
151
+ pendingMessages = /* @__PURE__ */ new Map();
152
+ // Track last event ID per thread for gap recovery on reconnect
153
+ lastEventIds = /* @__PURE__ */ new Map();
154
+ // Pagination
155
+ lastThreadListCursor = null;
156
+ isLoadingMore = false;
157
+ // Event pagination cursors per thread
158
+ eventPaginationCursors = /* @__PURE__ */ new Map();
159
+ constructor(callbacks) {
160
+ this.callbacks = callbacks;
161
+ }
162
+ setOnError(cb) {
163
+ this.onError = cb;
164
+ }
165
+ connect(url, workerId, config) {
166
+ this.url = url;
167
+ this.workerId = workerId;
168
+ this.config = config ?? { organizationId: "", workspaceId: "", userId: "" };
169
+ this.isIntentionallyClosed = false;
170
+ this.callbacks.setConnectionStatus("connecting");
171
+ this.createConnection();
172
+ }
173
+ disconnect() {
174
+ this.isIntentionallyClosed = true;
175
+ if (this.reconnectTimer) {
176
+ clearTimeout(this.reconnectTimer);
177
+ this.reconnectTimer = null;
178
+ }
179
+ for (const [, pending] of this.pendingMessages) {
180
+ clearTimeout(pending.timer);
181
+ }
182
+ this.pendingMessages.clear();
183
+ if (this.ws) {
184
+ this.ws.onopen = null;
185
+ this.ws.onmessage = null;
186
+ this.ws.onclose = null;
187
+ this.ws.onerror = null;
188
+ this.ws.close();
189
+ this.ws = null;
190
+ }
191
+ this.callbacks.setConnectionStatus("disconnected");
192
+ }
193
+ retry() {
194
+ this.reconnectAttempts = 0;
195
+ this.isIntentionallyClosed = false;
196
+ this.callbacks.setConnectionStatus("connecting");
197
+ this.createConnection();
198
+ }
199
+ // --- Thread operations ---
200
+ requestThreadList(cursor, limit) {
201
+ this.send("thread.list", { cursor, limit });
202
+ }
203
+ subscribe(threadId) {
204
+ const refs = this.subscriptionRefs.get(threadId) ?? 0;
205
+ this.subscriptionRefs.set(threadId, refs + 1);
206
+ if (refs === 0) {
207
+ const afterEventId = this.lastEventIds.get(threadId) ?? null;
208
+ this.send("thread.subscribe", { thread_id: threadId, after_event_id: afterEventId });
209
+ this.callbacks.addSubscription(threadId);
210
+ }
211
+ }
212
+ unsubscribe(threadId) {
213
+ const refs = this.subscriptionRefs.get(threadId) ?? 0;
214
+ if (refs <= 1) {
215
+ this.subscriptionRefs.delete(threadId);
216
+ this.lastEventIds.delete(threadId);
217
+ this.eventPaginationCursors.delete(threadId);
218
+ this.send("thread.unsubscribe", { thread_id: threadId });
219
+ this.callbacks.removeSubscription(threadId);
220
+ } else {
221
+ this.subscriptionRefs.set(threadId, refs - 1);
222
+ }
223
+ }
224
+ deleteThread(threadId) {
225
+ this.send("thread.delete", { thread_id: threadId });
226
+ }
227
+ loadMoreThreads(limit) {
228
+ const cursor = this.lastThreadListCursor;
229
+ if (!cursor) return;
230
+ this.isLoadingMore = true;
231
+ this.send("thread.list", { cursor, limit });
232
+ }
233
+ loadOlderEvents(threadId, limit) {
234
+ const cursor = this.eventPaginationCursors.get(threadId);
235
+ this.send("event.list", {
236
+ thread_id: threadId,
237
+ before_event_id: cursor ?? null,
238
+ limit
239
+ });
240
+ }
241
+ // --- Settings ---
242
+ subscribeSettings() {
243
+ this.send("settings.subscribe", { worker_id: this.workerId });
244
+ }
245
+ unsubscribeSettings() {
246
+ this.send("settings.unsubscribe", { worker_id: this.workerId });
247
+ }
248
+ patchSetting(fieldId, value) {
249
+ this.send("settings.patch", { field_id: fieldId, value });
250
+ }
251
+ // --- Feedback ---
252
+ sendFeedback(targetType, targetId, feedback, reason) {
253
+ this.send("feedback", {
254
+ target_type: targetType,
255
+ target_id: targetId,
256
+ feedback,
257
+ reason
258
+ });
259
+ }
260
+ // --- Analytics ---
261
+ subscribeAnalytics() {
262
+ this.send("analytics.subscribe", { worker_id: this.workerId });
263
+ }
264
+ unsubscribeAnalytics() {
265
+ this.send("analytics.unsubscribe", { worker_id: this.workerId });
266
+ }
267
+ // --- Messaging ---
268
+ sendMessage(params) {
269
+ const clientEventId = crypto.randomUUID();
270
+ const content = params.content ? params.content.map(toWireContentPart) : [{ type: "text", text: params.text ?? "" }];
271
+ const optimisticEvent = {
272
+ id: `optimistic-${clientEventId}`,
273
+ threadId: params.threadId ?? "",
274
+ runId: null,
275
+ type: "message",
276
+ actor: "user",
277
+ author: null,
278
+ user: {
279
+ userId: this.config.userId ?? "",
280
+ userName: this.config.userName,
281
+ userEmail: this.config.userEmail,
282
+ systemRole: this.config.systemRole ?? "user",
283
+ organizationRole: this.config.organizationRole ?? "member",
284
+ workspaceRole: this.config.workspaceRole ?? "member"
285
+ },
286
+ content: params.content ?? [{ type: "text", text: params.text ?? "" }],
287
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
288
+ };
289
+ if (params.threadId) {
290
+ this.callbacks.addOptimistic(params.threadId, optimisticEvent, clientEventId);
291
+ }
292
+ this.pendingOptimistic.set(clientEventId, {
293
+ threadId: params.threadId ?? "",
294
+ clientEventId
295
+ });
296
+ const promise = new Promise((resolve, reject) => {
297
+ const timer = setTimeout(() => {
298
+ this.pendingMessages.delete(clientEventId);
299
+ reject(new Error("sendMessage timed out waiting for server response"));
300
+ }, SEND_MESSAGE_TIMEOUT);
301
+ this.pendingMessages.set(clientEventId, { resolve, timer });
302
+ });
303
+ this.send("event.create", {
304
+ thread_id: params.threadId,
305
+ client_event_id: clientEventId,
306
+ event: {
307
+ type: "message",
308
+ actor: "user",
309
+ content
310
+ }
311
+ });
312
+ return promise;
313
+ }
314
+ // --- Private ---
315
+ createConnection() {
316
+ if (this.ws) {
317
+ this.ws.onopen = null;
318
+ this.ws.onmessage = null;
319
+ this.ws.onclose = null;
320
+ this.ws.onerror = null;
321
+ this.ws.close();
322
+ }
323
+ this.ws = new WebSocket(this.url);
324
+ this.ws.onopen = () => {
325
+ this.authenticate();
326
+ };
327
+ this.ws.onmessage = (e) => {
328
+ try {
329
+ const frame = JSON.parse(e.data);
330
+ this.route(frame);
331
+ } catch {
332
+ }
333
+ };
334
+ this.ws.onclose = () => {
335
+ if (!this.isIntentionallyClosed) {
336
+ this.scheduleReconnect();
337
+ }
338
+ };
339
+ this.ws.onerror = () => {
340
+ };
341
+ }
342
+ authenticate() {
343
+ this.connectFrameId = crypto.randomUUID();
344
+ this.sendRaw({
345
+ id: this.connectFrameId,
346
+ type: "connect",
347
+ payload: {
348
+ worker_id: this.workerId,
349
+ organization_id: this.config.organizationId ?? "",
350
+ workspace_id: this.config.workspaceId ?? "",
351
+ user: {
352
+ user_id: this.config.userId ?? "",
353
+ user_name: this.config.userName ?? null,
354
+ user_email: this.config.userEmail ?? null,
355
+ system_role: this.config.systemRole ?? "user",
356
+ organization_role: this.config.organizationRole ?? "member",
357
+ workspace_role: this.config.workspaceRole ?? "member"
358
+ },
359
+ auth_token: this.config.authToken,
360
+ client: { name: "dooers-agents-client", version: "0.1.0" }
361
+ }
362
+ });
363
+ }
364
+ route(frame) {
365
+ switch (frame.type) {
366
+ case "ack": {
367
+ if (frame.payload.ack_id === this.connectFrameId) {
368
+ if (frame.payload.ok) {
369
+ this.callbacks.setConnectionStatus("connected");
370
+ this.callbacks.resetReconnect();
371
+ this.requestThreadList();
372
+ for (const threadId of this.subscriptionRefs.keys()) {
373
+ const afterEventId = this.lastEventIds.get(threadId) ?? null;
374
+ this.send("thread.subscribe", {
375
+ thread_id: threadId,
376
+ after_event_id: afterEventId
377
+ });
378
+ }
379
+ } else {
380
+ this.callbacks.setConnectionStatus("error", frame.payload.error?.message);
381
+ }
382
+ } else if (!frame.payload.ok && frame.payload.error) {
383
+ this.onError?.({
384
+ code: frame.payload.error.code,
385
+ message: frame.payload.error.message,
386
+ frameType: "ack"
387
+ });
388
+ }
389
+ break;
390
+ }
391
+ case "thread.list.result": {
392
+ const threads = frame.payload.threads.map(toThread);
393
+ const cursor = frame.payload.cursor ?? null;
394
+ const totalCount = frame.payload.total_count;
395
+ this.lastThreadListCursor = cursor;
396
+ if (this.isLoadingMore) {
397
+ this.callbacks.onThreadListAppend(threads, cursor, totalCount);
398
+ } else {
399
+ this.callbacks.onThreadList(threads, cursor, totalCount);
400
+ }
401
+ this.isLoadingMore = false;
402
+ break;
403
+ }
404
+ case "thread.snapshot": {
405
+ const thread = toThread(frame.payload.thread);
406
+ const events = frame.payload.events.map(toThreadEvent);
407
+ const runs = (frame.payload.runs ?? []).map(toRun);
408
+ const lastEvent = events[events.length - 1];
409
+ if (lastEvent) {
410
+ this.lastEventIds.set(thread.id, lastEvent.id);
411
+ }
412
+ const oldestEvent = events[0];
413
+ if (oldestEvent) {
414
+ this.eventPaginationCursors.set(thread.id, oldestEvent.id);
415
+ }
416
+ this.callbacks.onThreadSnapshot(thread, events, runs);
417
+ break;
418
+ }
419
+ case "event.append": {
420
+ const events = frame.payload.events.map(toThreadEvent);
421
+ for (const event of events) {
422
+ if (event.clientEventId && this.pendingOptimistic.has(event.clientEventId)) {
423
+ const pending = this.pendingOptimistic.get(event.clientEventId);
424
+ if (pending) {
425
+ this.callbacks.removeOptimistic(pending.threadId, event.clientEventId);
426
+ this.pendingOptimistic.delete(event.clientEventId);
427
+ }
428
+ }
429
+ if (event.clientEventId && this.pendingMessages.has(event.clientEventId)) {
430
+ const pendingMessage = this.pendingMessages.get(event.clientEventId);
431
+ if (pendingMessage) {
432
+ clearTimeout(pendingMessage.timer);
433
+ pendingMessage.resolve({ threadId: frame.payload.thread_id });
434
+ this.pendingMessages.delete(event.clientEventId);
435
+ }
436
+ }
437
+ }
438
+ const lastEvent = events[events.length - 1];
439
+ if (lastEvent) {
440
+ this.lastEventIds.set(frame.payload.thread_id, lastEvent.id);
441
+ }
442
+ this.callbacks.onEventAppend(frame.payload.thread_id, events);
443
+ break;
444
+ }
445
+ case "event.list.result": {
446
+ const events = frame.payload.events.map(toThreadEvent);
447
+ this.eventPaginationCursors.set(frame.payload.thread_id, frame.payload.cursor);
448
+ this.callbacks.onEventListResult(
449
+ frame.payload.thread_id,
450
+ events,
451
+ frame.payload.cursor,
452
+ frame.payload.has_more
453
+ );
454
+ break;
455
+ }
456
+ case "thread.upsert":
457
+ this.callbacks.onThreadUpsert(toThread(frame.payload.thread));
458
+ break;
459
+ case "thread.deleted":
460
+ this.callbacks.onThreadDeleted(frame.payload.thread_id);
461
+ this.lastEventIds.delete(frame.payload.thread_id);
462
+ break;
463
+ case "run.upsert":
464
+ this.callbacks.onRunUpsert(toRun(frame.payload.run));
465
+ break;
466
+ case "settings.snapshot": {
467
+ const fields = frame.payload.fields.map(toSettingsItem);
468
+ this.callbacks.onSettingsSnapshot(fields, frame.payload.updated_at);
469
+ break;
470
+ }
471
+ case "settings.patch":
472
+ this.callbacks.onSettingsPatch(
473
+ frame.payload.field_id,
474
+ frame.payload.value,
475
+ frame.payload.updated_at
476
+ );
477
+ break;
478
+ case "feedback.ack":
479
+ if (frame.payload.ok) {
480
+ this.callbacks.onFeedbackAck(
481
+ frame.payload.target_id,
482
+ frame.payload.feedback
483
+ );
484
+ }
485
+ break;
486
+ case "analytics.event":
487
+ this.callbacks.onAnalyticsEvent(toAnalyticsEvent(frame.payload));
488
+ break;
489
+ }
490
+ }
491
+ scheduleReconnect() {
492
+ if (this.reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
493
+ this.callbacks.setReconnectFailed();
494
+ this.callbacks.setConnectionStatus("error", "Connection lost after maximum retries");
495
+ return;
496
+ }
497
+ this.callbacks.setConnectionStatus("disconnected");
498
+ const delay = RECONNECT_DELAYS[this.reconnectAttempts] ?? 16e3;
499
+ this.reconnectAttempts++;
500
+ this.reconnectTimer = setTimeout(() => {
501
+ this.callbacks.setConnectionStatus("connecting");
502
+ this.createConnection();
503
+ }, delay);
504
+ }
505
+ send(type, payload) {
506
+ this.sendRaw({ id: crypto.randomUUID(), type, payload });
507
+ }
508
+ sendRaw(frame) {
509
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return;
510
+ this.ws.send(JSON.stringify(frame));
511
+ }
512
+ };
513
+ function createWorkerStore() {
514
+ return createStore()((set) => ({
515
+ connection: {
516
+ status: "idle",
517
+ error: null,
518
+ reconnectAttempts: 0,
519
+ reconnectFailed: false
520
+ },
521
+ threads: {},
522
+ threadOrder: [],
523
+ threadListCursor: null,
524
+ threadListHasMore: false,
525
+ threadListTotalCount: 0,
526
+ events: {},
527
+ eventPagination: {},
528
+ optimistic: {},
529
+ optimisticKeys: {},
530
+ runs: {},
531
+ settings: {
532
+ fields: [],
533
+ updatedAt: null,
534
+ isLoading: true
535
+ },
536
+ feedback: {},
537
+ analytics: {
538
+ events: [],
539
+ counters: { totalRequests: 0, feedbackLikes: 0, feedbackDislikes: 0 }
540
+ },
541
+ subscriptions: /* @__PURE__ */ new Set(),
542
+ loadingThreads: /* @__PURE__ */ new Set(),
543
+ actions: {
544
+ setConnectionStatus: (status, error) => set((s) => ({
545
+ connection: { ...s.connection, status, error: error ?? null }
546
+ })),
547
+ setReconnectFailed: () => set((s) => ({
548
+ connection: { ...s.connection, reconnectFailed: true }
549
+ })),
550
+ resetReconnect: () => set((s) => ({
551
+ connection: { ...s.connection, reconnectAttempts: 0, reconnectFailed: false }
552
+ })),
553
+ onThreadList: (threads, cursor, totalCount) => set(() => {
554
+ const map = {};
555
+ const order = [];
556
+ for (const t of threads) {
557
+ map[t.id] = t;
558
+ order.push(t.id);
559
+ }
560
+ return {
561
+ threads: map,
562
+ threadOrder: order,
563
+ threadListCursor: cursor ?? null,
564
+ threadListHasMore: cursor != null,
565
+ threadListTotalCount: totalCount ?? 0
566
+ };
567
+ }),
568
+ onThreadListAppend: (threads, cursor) => set((s) => {
569
+ const map = { ...s.threads };
570
+ const order = [...s.threadOrder];
571
+ for (const t of threads) {
572
+ if (!map[t.id]) {
573
+ order.push(t.id);
574
+ }
575
+ map[t.id] = t;
576
+ }
577
+ return {
578
+ threads: map,
579
+ threadOrder: order,
580
+ threadListCursor: cursor ?? null,
581
+ threadListHasMore: cursor != null
582
+ };
583
+ }),
584
+ onThreadUpsert: (thread) => set((s) => {
585
+ const threads = { ...s.threads, [thread.id]: thread };
586
+ const threadOrder = s.threadOrder.includes(thread.id) ? s.threadOrder : [thread.id, ...s.threadOrder];
587
+ return { threads, threadOrder };
588
+ }),
589
+ onThreadDeleted: (threadId) => set((s) => {
590
+ const { [threadId]: _, ...threads } = s.threads;
591
+ const { [threadId]: _e, ...events } = s.events;
592
+ const { [threadId]: _r, ...runs } = s.runs;
593
+ const { [threadId]: _o, ...optimistic } = s.optimistic;
594
+ const { [threadId]: _k, ...optimisticKeys } = s.optimisticKeys;
595
+ const { [threadId]: _p, ...eventPagination } = s.eventPagination;
596
+ const threadOrder = s.threadOrder.filter((id) => id !== threadId);
597
+ const subscriptions = new Set(s.subscriptions);
598
+ subscriptions.delete(threadId);
599
+ const loadingThreads = new Set(s.loadingThreads);
600
+ loadingThreads.delete(threadId);
601
+ return {
602
+ threads,
603
+ threadOrder,
604
+ events,
605
+ runs,
606
+ optimistic,
607
+ optimisticKeys,
608
+ eventPagination,
609
+ subscriptions,
610
+ loadingThreads
611
+ };
612
+ }),
613
+ onThreadSnapshot: (thread, events, runs) => set((s) => {
614
+ const threads = { ...s.threads, [thread.id]: thread };
615
+ const threadOrder = s.threadOrder.includes(thread.id) ? s.threadOrder : [thread.id, ...s.threadOrder];
616
+ const loadingThreads = new Set(s.loadingThreads);
617
+ loadingThreads.delete(thread.id);
618
+ return {
619
+ threads,
620
+ threadOrder,
621
+ events: { ...s.events, [thread.id]: events },
622
+ runs: { ...s.runs, [thread.id]: runs },
623
+ loadingThreads
624
+ };
625
+ }),
626
+ onEventAppend: (threadId, newEvents) => set((s) => {
627
+ const existing = s.events[threadId] ?? [];
628
+ const existingIds = new Set(existing.map((e) => e.id));
629
+ const unique = newEvents.filter((e) => !existingIds.has(e.id));
630
+ return {
631
+ events: { ...s.events, [threadId]: [...existing, ...unique] }
632
+ };
633
+ }),
634
+ onEventListResult: (threadId, olderEvents, cursor, hasMore) => set((s) => {
635
+ const existing = s.events[threadId] ?? [];
636
+ const existingIds = new Set(existing.map((e) => e.id));
637
+ const unique = olderEvents.filter((e) => !existingIds.has(e.id));
638
+ return {
639
+ events: { ...s.events, [threadId]: [...unique, ...existing] },
640
+ eventPagination: {
641
+ ...s.eventPagination,
642
+ [threadId]: { cursor, hasMore }
643
+ }
644
+ };
645
+ }),
646
+ onRunUpsert: (run) => set((s) => {
647
+ const existing = s.runs[run.threadId] ?? [];
648
+ const idx = existing.findIndex((r) => r.id === run.id);
649
+ const updated = idx >= 0 ? existing.map((r) => r.id === run.id ? run : r) : [...existing, run];
650
+ return { runs: { ...s.runs, [run.threadId]: updated } };
651
+ }),
652
+ addOptimistic: (threadId, event, clientEventId) => set((s) => ({
653
+ optimistic: {
654
+ ...s.optimistic,
655
+ [threadId]: [...s.optimistic[threadId] ?? [], event]
656
+ },
657
+ optimisticKeys: {
658
+ ...s.optimisticKeys,
659
+ [threadId]: [...s.optimisticKeys[threadId] ?? [], clientEventId]
660
+ }
661
+ })),
662
+ removeOptimistic: (threadId, clientEventId) => set((s) => {
663
+ const keys = s.optimisticKeys[threadId] ?? [];
664
+ const idx = keys.indexOf(clientEventId);
665
+ if (idx < 0) return s;
666
+ const newKeys = [...keys];
667
+ newKeys.splice(idx, 1);
668
+ const newEvents = [...s.optimistic[threadId] ?? []];
669
+ newEvents.splice(idx, 1);
670
+ return {
671
+ optimistic: { ...s.optimistic, [threadId]: newEvents },
672
+ optimisticKeys: { ...s.optimisticKeys, [threadId]: newKeys }
673
+ };
674
+ }),
675
+ addSubscription: (threadId) => set((s) => {
676
+ const subscriptions = new Set(s.subscriptions);
677
+ subscriptions.add(threadId);
678
+ const loadingThreads = new Set(s.loadingThreads);
679
+ loadingThreads.add(threadId);
680
+ return { subscriptions, loadingThreads };
681
+ }),
682
+ removeSubscription: (threadId) => set((s) => {
683
+ const subscriptions = new Set(s.subscriptions);
684
+ subscriptions.delete(threadId);
685
+ const loadingThreads = new Set(s.loadingThreads);
686
+ loadingThreads.delete(threadId);
687
+ return { subscriptions, loadingThreads };
688
+ }),
689
+ onSettingsSnapshot: (fields, updatedAt) => set(() => ({
690
+ settings: { fields, updatedAt, isLoading: false }
691
+ })),
692
+ onSettingsPatch: (fieldId, value, updatedAt) => set((s) => ({
693
+ settings: {
694
+ ...s.settings,
695
+ updatedAt,
696
+ fields: s.settings.fields.map((item) => {
697
+ if (isSettingsFieldGroup(item)) {
698
+ const hasField = item.fields.some((f) => f.id === fieldId);
699
+ if (hasField) {
700
+ return {
701
+ ...item,
702
+ fields: item.fields.map((f) => f.id === fieldId ? { ...f, value } : f)
703
+ };
704
+ }
705
+ return item;
706
+ }
707
+ return item.id === fieldId ? { ...item, value } : item;
708
+ })
709
+ }
710
+ })),
711
+ onFeedbackAck: (targetId, feedback) => set((s) => ({
712
+ feedback: { ...s.feedback, [targetId]: feedback }
713
+ })),
714
+ onAnalyticsEvent: (event) => set((s) => {
715
+ const events = [...s.analytics.events, event];
716
+ const trimmed = events.length > 50 ? events.slice(-50) : events;
717
+ const counters = { ...s.analytics.counters };
718
+ if (event.event === "feedback.like") {
719
+ counters.feedbackLikes += 1;
720
+ } else if (event.event === "feedback.dislike") {
721
+ counters.feedbackDislikes += 1;
722
+ } else {
723
+ counters.totalRequests += 1;
724
+ }
725
+ return { analytics: { events: trimmed, counters } };
726
+ }),
727
+ resetAnalytics: () => set(() => ({
728
+ analytics: {
729
+ events: [],
730
+ counters: { totalRequests: 0, feedbackLikes: 0, feedbackDislikes: 0 }
731
+ }
732
+ }))
733
+ }
734
+ }));
735
+ }
736
+ var WorkerContext = createContext(null);
737
+ function WorkerProvider({
738
+ url,
739
+ workerId,
740
+ organizationId,
741
+ workspaceId,
742
+ userId,
743
+ userName,
744
+ userEmail,
745
+ systemRole,
746
+ organizationRole,
747
+ workspaceRole,
748
+ authToken,
749
+ onError,
750
+ children
751
+ }) {
752
+ const storeRef = useRef(void 0);
753
+ const clientRef = useRef(void 0);
754
+ if (!storeRef.current) {
755
+ storeRef.current = createWorkerStore();
756
+ clientRef.current = new WorkerClient(storeRef.current.getState().actions);
757
+ }
758
+ useEffect(() => {
759
+ clientRef.current?.setOnError(onError ?? null);
760
+ }, [onError]);
761
+ useEffect(() => {
762
+ if (!url || !workerId) return;
763
+ clientRef.current?.connect(url, workerId, {
764
+ organizationId,
765
+ workspaceId,
766
+ userId,
767
+ userName,
768
+ userEmail,
769
+ systemRole,
770
+ organizationRole,
771
+ workspaceRole,
772
+ authToken
773
+ });
774
+ return () => clientRef.current?.disconnect();
775
+ }, [
776
+ url,
777
+ workerId,
778
+ organizationId,
779
+ workspaceId,
780
+ userId,
781
+ userName,
782
+ userEmail,
783
+ systemRole,
784
+ organizationRole,
785
+ workspaceRole,
786
+ authToken
787
+ ]);
788
+ const contextValue = useMemo(
789
+ () => ({ store: storeRef.current, client: clientRef.current }),
790
+ []
791
+ );
792
+ return /* @__PURE__ */ jsx(WorkerContext.Provider, { value: contextValue, children });
793
+ }
794
+ function useWorkerContext() {
795
+ const ctx = useContext(WorkerContext);
796
+ if (!ctx) {
797
+ throw new Error("useWorkerContext must be used within a <WorkerProvider>");
798
+ }
799
+ return ctx;
800
+ }
801
+ function useStore(selector) {
802
+ const { store } = useWorkerContext();
803
+ return useStore$1(store, selector);
804
+ }
805
+ function useShallowStore(selector) {
806
+ const { store } = useWorkerContext();
807
+ return useStore$1(store, useShallow(selector));
808
+ }
809
+
810
+ // src/hooks/use-analytics.ts
811
+ function useAnalytics() {
812
+ const { client } = useWorkerContext();
813
+ const events = useShallowStore((s) => s.analytics.events);
814
+ const counters = useShallowStore((s) => s.analytics.counters);
815
+ const subscribe = useCallback(() => client.subscribeAnalytics(), [client]);
816
+ const unsubscribe = useCallback(() => client.unsubscribeAnalytics(), [client]);
817
+ return { events, counters, subscribe, unsubscribe };
818
+ }
819
+ function useConnection() {
820
+ const { client } = useWorkerContext();
821
+ const status = useStore((s) => s.connection.status);
822
+ const error = useStore((s) => s.connection.error);
823
+ const reconnectFailed = useStore((s) => s.connection.reconnectFailed);
824
+ const reconnect = useCallback(() => client.retry(), [client]);
825
+ return { status, error, reconnectFailed, reconnect };
826
+ }
827
+ function useFeedback(targetId, targetType = "event") {
828
+ const { client } = useWorkerContext();
829
+ const feedback = useStore((s) => s.feedback[targetId] ?? null);
830
+ const like = useCallback(
831
+ (reason) => client.sendFeedback(targetType, targetId, "like", reason),
832
+ [client, targetType, targetId]
833
+ );
834
+ const dislike = useCallback(
835
+ (reason) => client.sendFeedback(targetType, targetId, "dislike", reason),
836
+ [client, targetType, targetId]
837
+ );
838
+ return { feedback, like, dislike };
839
+ }
840
+ function useMessage() {
841
+ const { client } = useWorkerContext();
842
+ const send = useCallback(
843
+ (params) => {
844
+ return client.sendMessage(params);
845
+ },
846
+ [client]
847
+ );
848
+ return { send };
849
+ }
850
+ function useSettings() {
851
+ const { client } = useWorkerContext();
852
+ const fields = useShallowStore((s) => s.settings.fields);
853
+ const updatedAt = useStore((s) => s.settings.updatedAt);
854
+ const isLoading = useStore((s) => s.settings.isLoading);
855
+ const subscribe = useCallback(() => client.subscribeSettings(), [client]);
856
+ const unsubscribe = useCallback(() => client.unsubscribeSettings(), [client]);
857
+ const patchField = useCallback(
858
+ (fieldId, value) => client.patchSetting(fieldId, value),
859
+ [client]
860
+ );
861
+ return { fields, updatedAt, isLoading, subscribe, unsubscribe, patchField };
862
+ }
863
+ var EMPTY_ARRAY = [];
864
+ var EMPTY_RUNS = [];
865
+ function useThreadDetails(threadId) {
866
+ const { client } = useWorkerContext();
867
+ useEffect(() => {
868
+ if (!threadId) return;
869
+ client.subscribe(threadId);
870
+ return () => client.unsubscribe(threadId);
871
+ }, [threadId, client]);
872
+ const thread = useStore((s) => threadId ? s.threads[threadId] ?? null : null);
873
+ const isLoading = useStore((s) => threadId ? s.loadingThreads.has(threadId) : false);
874
+ const isWaiting = useStore((s) => {
875
+ if (!threadId) return false;
876
+ const r = s.runs[threadId];
877
+ return r ? r.some((run) => run.status === "running") : false;
878
+ });
879
+ const optimistic = useStore(
880
+ (s) => threadId ? s.optimistic[threadId] ?? EMPTY_ARRAY : EMPTY_ARRAY
881
+ );
882
+ const confirmed = useStore((s) => threadId ? s.events[threadId] ?? EMPTY_ARRAY : EMPTY_ARRAY);
883
+ const runs = useStore((s) => threadId ? s.runs[threadId] ?? EMPTY_RUNS : EMPTY_RUNS);
884
+ const events = useMemo(
885
+ () => optimistic.length ? [...optimistic, ...confirmed] : confirmed,
886
+ [optimistic, confirmed]
887
+ );
888
+ return { thread, events, runs, isLoading, isWaiting };
889
+ }
890
+ function useThreadEvents(threadId) {
891
+ const { client } = useWorkerContext();
892
+ const hasOlderEvents = useStore(
893
+ (s) => threadId ? s.eventPagination[threadId]?.hasMore ?? true : false
894
+ );
895
+ const loadOlderEvents = useCallback(
896
+ (limit) => {
897
+ if (threadId) client.loadOlderEvents(threadId, limit);
898
+ },
899
+ [threadId, client]
900
+ );
901
+ return { hasOlderEvents, loadOlderEvents };
902
+ }
903
+ function useThreadsList() {
904
+ const threadOrder = useStore((s) => s.threadOrder);
905
+ const threadMap = useStore((s) => s.threads);
906
+ const threads = useMemo(
907
+ () => threadOrder.flatMap((id) => {
908
+ const t = threadMap[id];
909
+ return t ? [t] : [];
910
+ }),
911
+ [threadOrder, threadMap]
912
+ );
913
+ const isLoading = useStore((s) => s.connection.status !== "connected");
914
+ const hasMore = useStore((s) => s.threadListHasMore);
915
+ const totalCount = useStore((s) => s.threadListTotalCount);
916
+ return { threads, isLoading, hasMore, totalCount };
917
+ }
918
+ function useThreadsActions() {
919
+ const { client } = useWorkerContext();
920
+ const deleteThread = useCallback((threadId) => client.deleteThread(threadId), [client]);
921
+ const loadMore = useCallback((limit) => client.loadMoreThreads(limit), [client]);
922
+ return { deleteThread, loadMore };
923
+ }
924
+
925
+ export { WorkerProvider, isSettingsFieldGroup, useAnalytics, useConnection, useFeedback, useMessage, useSettings, useThreadDetails, useThreadEvents, useThreadsActions, useThreadsList };
926
+ //# sourceMappingURL=main.js.map
927
+ //# sourceMappingURL=main.js.map