@arbidocs/react 0.5.72 → 0.5.74

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.
@@ -6,6 +6,93 @@ import { getUserEncryptionKeypair, sealedBoxDecrypt, sealedBoxEncrypt } from '@a
6
6
  import { useSyncExternalStore, useCallback, useMemo, useRef, useEffect } from 'react';
7
7
  import { useQuery, useMutation, useQueryClient, useInfiniteQuery, useQueries } from '@tanstack/react-query';
8
8
 
9
+ // src/chat/run-state.ts
10
+ var MIN_QUEUED_MESSAGE_VISIBILITY_MS = 800;
11
+ var mapQueues = (queues, update) => Object.fromEntries(Object.entries(queues).map(([id, messages]) => [id, update(messages)]));
12
+ function findConsumedMessages(messages, combinedContent) {
13
+ const text = combinedContent.trim();
14
+ const taken = /* @__PURE__ */ new Set();
15
+ let cursor = 0;
16
+ for (const message of messages) {
17
+ if (cursor >= text.length) break;
18
+ const content = message.content.trim();
19
+ if (!content) continue;
20
+ const separator = cursor === 0 ? "" : "\n\n";
21
+ if (text.startsWith(separator + content, cursor)) {
22
+ taken.add(message.clientId);
23
+ cursor += separator.length + content.length;
24
+ }
25
+ }
26
+ return cursor === text.length ? taken : /* @__PURE__ */ new Set();
27
+ }
28
+ var queuedMessages = {
29
+ add: (q, responseId, message) => ({
30
+ ...q,
31
+ [responseId]: [...q[responseId] ?? [], message]
32
+ }),
33
+ acknowledge: (q, clientId, serverId) => mapQueues(
34
+ q,
35
+ (messages) => messages.map((m) => m.clientId === clientId ? { ...m, serverId, acknowledged: true } : m)
36
+ ),
37
+ remove: (q, clientId) => mapQueues(q, (messages) => messages.filter((m) => m.clientId !== clientId)),
38
+ removeByServerId: (q, serverId) => mapQueues(q, (messages) => messages.filter((m) => m.serverId !== serverId)),
39
+ removeByContent: (q, responseId, content) => {
40
+ let removed = false;
41
+ return {
42
+ ...q,
43
+ [responseId]: (q[responseId] ?? []).filter((m) => {
44
+ if (!removed && m.content === content) {
45
+ removed = true;
46
+ return false;
47
+ }
48
+ return true;
49
+ })
50
+ };
51
+ },
52
+ /** Take out the prompts a steering interjection delivered into the run. */
53
+ consume: (q, responseId, combinedContent) => {
54
+ const messages = q[responseId] ?? [];
55
+ const consumed = findConsumedMessages(messages, combinedContent);
56
+ return { ...q, [responseId]: messages.filter((m) => !consumed.has(m.clientId)) };
57
+ },
58
+ clear: (q, responseId) => ({ ...q, [responseId]: [] }),
59
+ /** Move the entries still waiting on a finished turn to the turn that is now running. */
60
+ move: (q, fromResponseId, toResponseId) => {
61
+ const waiting = q[fromResponseId] ?? [];
62
+ if (fromResponseId === toResponseId || waiting.length === 0) return q;
63
+ return { ...q, [fromResponseId]: [], [toResponseId]: [...q[toResponseId] ?? [], ...waiting] };
64
+ },
65
+ /** Find a queued follow-up by its client id, under whichever turn holds it. */
66
+ find: (q, clientId) => Object.values(q).flat().find((m) => m.clientId === clientId)
67
+ };
68
+ var without = (record, key) => {
69
+ const next = { ...record };
70
+ delete next[key];
71
+ return next;
72
+ };
73
+ var agentControl = {
74
+ setState: (c, id, state) => ({
75
+ ...c,
76
+ agentControlStates: { ...c.agentControlStates, [id]: state }
77
+ }),
78
+ /** A probe answered: the latest answer, and nothing pending any more. */
79
+ setProbeAnswer: (c, id, probe) => ({
80
+ ...c,
81
+ agentProbeAnswers: { ...c.agentProbeAnswers, [id]: probe },
82
+ agentProbePending: { ...c.agentProbePending, [id]: false }
83
+ }),
84
+ setProbePending: (c, id, pending) => ({
85
+ ...c,
86
+ agentProbePending: { ...c.agentProbePending, [id]: pending }
87
+ }),
88
+ /** The turn is over: drop its control state. */
89
+ clear: (c, id) => ({
90
+ agentControlStates: without(c.agentControlStates, id),
91
+ agentProbeAnswers: without(c.agentProbeAnswers, id),
92
+ agentProbePending: without(c.agentProbePending, id)
93
+ })
94
+ };
95
+
9
96
  // src/session/thread-tabs.ts
10
97
  var threadTabId = (tab) => tab.conversationId ?? "new";
11
98
  var MAX_THREAD_TABS = 12;
@@ -84,16 +171,6 @@ function renameThreadTabIn(tabs, fromId, toId) {
84
171
  )
85
172
  };
86
173
  }
87
- function withThreadDrafts(threads, drafts) {
88
- let changed = false;
89
- const next = threads.map((tab) => {
90
- const draft = drafts[threadTabId(tab)];
91
- if (!draft || !tab.view || tab.view.draft) return tab;
92
- changed = true;
93
- return { ...tab, view: { ...tab.view, draft } };
94
- });
95
- return changed ? next : threads;
96
- }
97
174
  var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
98
175
  var stringOrNull = (v) => typeof v === "string" ? v : null;
99
176
  var stringOrUndefined = (v) => typeof v === "string" ? v : void 0;
@@ -101,7 +178,6 @@ function parseView(value) {
101
178
  if (!isRecord(value)) return void 0;
102
179
  return {
103
180
  cursor: stringOrNull(value.cursor),
104
- draft: "",
105
181
  editingMessageId: stringOrNull(value.editingMessageId),
106
182
  preEditDraft: null,
107
183
  preEditCursor: stringOrNull(value.preEditCursor),
@@ -126,181 +202,681 @@ function parseSavedThreadTabs(threads, activeThread) {
126
202
  activeThread: active && tabs.some((tab) => threadTabId(tab) === active) ? active : null
127
203
  };
128
204
  }
129
- var savedThreadTabs = (threads) => threads.map(
130
- (tab) => tab.view ? { ...tab, view: { ...tab.view, draft: "", preEditDraft: null } } : tab
131
- );
205
+ var savedThreadTabs = (threads) => threads.map((tab) => tab.view ? { ...tab, view: { ...tab.view, preEditDraft: null } } : tab);
132
206
 
133
- // src/session/layout.ts
134
- var PANEL_MODES = ["main", "docked", "overlay", "popout"];
135
- var defaultPinned = () => typeof window === "undefined" || window.innerWidth >= 640;
136
- function defaultLayoutState() {
137
- return {
138
- shell: {
139
- page: "home",
140
- sidebar: { pinned: defaultPinned(), hovering: false, drawerOpen: false },
141
- panels: { main: null, docked: null, overlay: null, popout: null, modes: {}, sizes: {} },
142
- focusedArea: "main"
143
- },
144
- tabs: {
145
- threads: [],
146
- activeThread: null,
147
- threadScrollRestore: null,
148
- dm: [],
149
- activeDm: null,
150
- dmMessageId: null,
151
- projects: [],
152
- activeProject: null
153
- },
154
- views: { tabs: {}, filters: {}, values: {}, transient: {} },
155
- grid: { states: {}, rowHeights: {}, labelWidths: {}, savedViews: {} }
156
- };
157
- }
207
+ // src/session/chat.ts
208
+ var EMPTY_CHAT_STATE = {
209
+ conversationId: null,
210
+ generating: {},
211
+ paMode: false,
212
+ conversationDocuments: {},
213
+ conversationDocumentAutoAdd: {},
214
+ conversationFacts: {},
215
+ cursor: null,
216
+ suggestedQuery: null,
217
+ queuedMessages: {},
218
+ agentControlStates: {},
219
+ agentProbeAnswers: {},
220
+ agentProbePending: {},
221
+ drafts: {},
222
+ editingMessageId: null,
223
+ deepLinkMessageId: null,
224
+ activeCitationKey: null,
225
+ chatModel: void 0,
226
+ chatModelName: void 0,
227
+ newConversationAssistantMode: "always",
228
+ pendingTools: null,
229
+ autoSendDraft: false,
230
+ agentChatOwners: {},
231
+ newAgentChat: null,
232
+ newChatDocumentId: null,
233
+ messageTimers: {}
234
+ };
158
235
  var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
159
- var recordOf = (value, keep) => isRecord2(value) ? Object.fromEntries(Object.entries(value).filter(([, v]) => keep(v))) : {};
160
236
  var isString = (v) => typeof v === "string";
161
- var isNumber = (v) => typeof v === "number" && Number.isFinite(v);
162
- var isAny = (v) => v !== void 0;
163
- var isMode = (v) => PANEL_MODES.includes(v);
164
- var isSize = (v) => isRecord2(v) && isNumber(v.width) && isNumber(v.height);
165
- var isList = (v) => Array.isArray(v);
166
237
  var stringOrNull2 = (v) => isString(v) ? v : null;
167
- function parseSavedLayout(saved) {
238
+ var recordOf = (value, read) => {
239
+ if (!isRecord2(value)) return {};
240
+ const out = {};
241
+ for (const [key, entry] of Object.entries(value)) {
242
+ const parsed = read(entry);
243
+ if (parsed !== null) out[key] = parsed;
244
+ }
245
+ return out;
246
+ };
247
+ var readDocuments = (value) => Array.isArray(value) ? value.filter((doc) => isRecord2(doc) && isString(doc.docId)).map((doc) => ({ docId: doc.docId, isPinned: doc.isPinned === true })) : null;
248
+ var readFacts = (value) => Array.isArray(value) ? Array.from(new Set(value.filter(isString))) : null;
249
+ function parseSavedChat(saved) {
168
250
  if (!isRecord2(saved) || saved.version !== 1) return null;
169
- const shell = isRecord2(saved.shell) ? saved.shell : {};
170
- const panels = isRecord2(shell.panels) ? shell.panels : {};
171
- const tabs = isRecord2(saved.tabs) ? saved.tabs : {};
172
- const views = isRecord2(saved.views) ? saved.views : {};
173
- const grid = isRecord2(saved.grid) ? saved.grid : {};
174
- const defaults = defaultLayoutState();
175
251
  return {
176
252
  version: 1,
177
- migrated: isList(saved.migrated) ? saved.migrated.filter(isString) : [],
178
- shell: {
179
- page: isString(shell.page) ? shell.page : defaults.shell.page,
180
- sidebarPinned: typeof shell.sidebarPinned === "boolean" ? shell.sidebarPinned : defaults.shell.sidebar.pinned,
181
- panels: {
182
- main: stringOrNull2(panels.main),
183
- docked: stringOrNull2(panels.docked),
184
- modes: recordOf(panels.modes, isMode),
185
- sizes: recordOf(panels.sizes, isSize)
186
- }
187
- },
188
- tabs: parseSavedThreadTabs(tabs.threads, tabs.activeThread),
189
- views: {
190
- tabs: recordOf(views.tabs, isString),
191
- filters: recordOf(views.filters, isAny),
192
- values: recordOf(views.values, isAny)
193
- },
194
- grid: {
195
- states: recordOf(grid.states, isRecord2),
196
- rowHeights: recordOf(grid.rowHeights, isNumber),
197
- labelWidths: recordOf(grid.labelWidths, isNumber),
198
- savedViews: recordOf(grid.savedViews, isList)
199
- }
253
+ migrated: Array.isArray(saved.migrated) ? saved.migrated.filter(isString) : [],
254
+ conversationId: stringOrNull2(saved.conversationId),
255
+ cursor: stringOrNull2(saved.cursor),
256
+ conversationDocuments: recordOf(saved.conversationDocuments, readDocuments),
257
+ conversationDocumentAutoAdd: recordOf(
258
+ saved.conversationDocumentAutoAdd,
259
+ (v) => typeof v === "boolean" ? v : null
260
+ ),
261
+ conversationFacts: recordOf(saved.conversationFacts, readFacts),
262
+ agentChatOwners: recordOf(saved.agentChatOwners, (v) => isString(v) ? v : null),
263
+ agentChatOwnersUser: stringOrNull2(saved.agentChatOwnersUser),
264
+ chatModel: stringOrNull2(saved.chatModel)
200
265
  };
201
266
  }
202
- function savedLayoutOf(layout, migrated) {
203
- const { shell, tabs, views, grid } = layout;
267
+ function savedChatOf(state, migrated, agentChatOwnersUser = null) {
204
268
  return {
205
269
  version: 1,
206
270
  migrated,
207
- shell: {
208
- page: shell.page,
209
- sidebarPinned: shell.sidebar.pinned,
210
- // A panel over the page or in its own window does not come back after a reload.
211
- panels: {
212
- main: shell.panels.main,
213
- docked: shell.panels.docked,
214
- modes: shell.panels.modes,
215
- sizes: shell.panels.sizes
216
- }
217
- },
218
- tabs: { threads: savedThreadTabs(tabs.threads), activeThread: tabs.activeThread },
219
- views: { tabs: views.tabs, filters: views.filters, values: views.values },
220
- grid
221
- };
222
- }
223
- function restoreLayout(current, saved) {
224
- return {
225
- ...current,
226
- shell: {
227
- ...current.shell,
228
- page: saved.shell.page,
229
- sidebar: { ...current.shell.sidebar, pinned: saved.shell.sidebarPinned },
230
- panels: { ...current.shell.panels, ...saved.shell.panels }
231
- },
232
- tabs: { ...current.tabs, ...saved.tabs },
233
- views: { ...saved.views, transient: current.views.transient },
234
- grid: saved.grid
235
- };
236
- }
237
- function openPanelIn(panels, panelId, mode) {
238
- const leave = (slot) => slot === panelId ? null : slot;
239
- const next = {
240
- ...panels,
241
- main: leave(panels.main),
242
- docked: leave(panels.docked),
243
- overlay: leave(panels.overlay),
244
- popout: leave(panels.popout),
245
- modes: { ...panels.modes, [panelId]: mode }
271
+ conversationId: state.conversationId,
272
+ cursor: state.cursor,
273
+ conversationDocuments: state.conversationDocuments,
274
+ conversationDocumentAutoAdd: { ...state.conversationDocumentAutoAdd },
275
+ conversationFacts: state.conversationFacts,
276
+ agentChatOwners: { ...state.agentChatOwners },
277
+ agentChatOwnersUser,
278
+ chatModel: state.chatModel ?? null
246
279
  };
247
- if (mode === "docked") return { ...next, docked: panelId, overlay: null };
248
- if (mode === "overlay") return { ...next, overlay: panelId, docked: null };
249
- return { ...next, [mode]: panelId };
250
280
  }
251
- function closePanelIn(panels, panelId) {
252
- const leave = (slot) => slot === panelId ? null : slot;
253
- return {
254
- ...panels,
255
- main: leave(panels.main),
256
- docked: leave(panels.docked),
257
- overlay: leave(panels.overlay),
258
- popout: leave(panels.popout)
259
- };
281
+ var SAVED_FIELDS = [
282
+ "conversationId",
283
+ "cursor",
284
+ "conversationDocuments",
285
+ "conversationDocumentAutoAdd",
286
+ "conversationFacts",
287
+ "agentChatOwners",
288
+ "chatModel"
289
+ ];
290
+ function withoutNew(record) {
291
+ const saved = { ...record };
292
+ delete saved.new;
293
+ return saved;
260
294
  }
261
- function panelPlace(panels, panelId) {
262
- return PANEL_MODES.find((mode) => panels[mode] === panelId) ?? null;
295
+ function moveKey(record, fromId, toId) {
296
+ if (!(fromId in record)) return record;
297
+ const next = { ...record, [toId]: record[fromId] };
298
+ delete next[fromId];
299
+ return next;
263
300
  }
264
- var isPanelOpen = (state, panelId) => panelPlace(state.shell.panels, panelId) !== null;
265
- var dmTabId = (tab) => tab.recipientId ?? "new";
266
- function createArbiLayoutStore({
301
+ var NO_EDIT = { draft: "", cursor: null };
302
+ function createArbiChatStore({
303
+ layout,
267
304
  persist,
305
+ draftStorage,
306
+ scrollTop = () => null,
307
+ user = () => draftStorage?.owner() ?? null,
268
308
  initial
269
- } = {}) {
270
- let layout = { ...defaultLayoutState(), ...initial };
309
+ }) {
310
+ let state = { ...EMPTY_CHAT_STATE, ...initial };
271
311
  let migrated = [];
312
+ let ownersUser = null;
272
313
  let isHydrated = !persist;
273
314
  const listeners = /* @__PURE__ */ new Set();
315
+ const waiting = /* @__PURE__ */ new Set();
316
+ let depth = 0;
317
+ const settle = (listener) => depth > 0 ? waiting.add(listener) : listener();
318
+ const notify = () => listeners.forEach(settle);
319
+ const batch = (change) => {
320
+ depth += 1;
321
+ try {
322
+ return change();
323
+ } finally {
324
+ depth -= 1;
325
+ if (depth === 0) {
326
+ const told = [...waiting];
327
+ waiting.clear();
328
+ told.forEach((listener) => listener());
329
+ }
330
+ }
331
+ };
274
332
  const save = () => {
275
- if (persist && isHydrated) persist.save(savedLayoutOf(layout, migrated));
333
+ if (persist && isHydrated) persist.save(savedChatOf(state, migrated, ownersUser));
276
334
  };
277
- const update = (next) => {
278
- if (next === layout) return;
279
- const persisted = next.tabs.threads !== layout.tabs.threads || next.tabs.activeThread !== layout.tabs.activeThread || next.views.tabs !== layout.views.tabs || next.views.filters !== layout.views.filters || next.views.values !== layout.views.values || next.grid !== layout.grid || next.shell.page !== layout.shell.page || next.shell.sidebar.pinned !== layout.shell.sidebar.pinned || next.shell.panels !== layout.shell.panels;
280
- layout = next;
281
- listeners.forEach((listener) => listener());
282
- if (persisted) save();
335
+ const update = (patch) => {
336
+ const next = { ...state, ...patch };
337
+ const saved = SAVED_FIELDS.some((key) => next[key] !== state[key]);
338
+ state = next;
339
+ notify();
340
+ if (saved) save();
283
341
  };
284
- const shell = (change) => update({ ...layout, shell: { ...layout.shell, ...change } });
285
- const tabs = (change) => update({ ...layout, tabs: { ...layout.tabs, ...change } });
286
- const views = (change) => update({ ...layout, views: { ...layout.views, ...change } });
287
- const grid = (change) => update({ ...layout, grid: { ...layout.grid, ...change } });
288
- const hydration = persist ? persist.load().then(
289
- (saved) => {
290
- const parsed = parseSavedLayout(saved);
291
- if (parsed) {
292
- migrated = parsed.migrated;
293
- layout = restoreLayout(layout, parsed);
294
- }
295
- isHydrated = true;
296
- listeners.forEach((listener) => listener());
297
- },
298
- () => {
299
- isHydrated = true;
300
- listeners.forEach((listener) => listener());
301
- }
302
- ) : Promise.resolve();
303
- return {
342
+ let preEdit = NO_EDIT;
343
+ let draftsRestored = !draftStorage;
344
+ let restoring = false;
345
+ let draftsOwner = draftStorage?.owner() ?? null;
346
+ const setDrafts = (drafts) => {
347
+ if (drafts === state.drafts) return;
348
+ update({ drafts });
349
+ if (draftsRestored) draftStorage?.save(drafts);
350
+ };
351
+ const setDraftOf = (key, text) => {
352
+ if ((state.drafts[key] ?? "") === text) return;
353
+ const drafts = { ...state.drafts };
354
+ if (text) drafts[key] = text;
355
+ else delete drafts[key];
356
+ setDrafts(drafts);
357
+ };
358
+ const keptDrafts = (drafts) => {
359
+ const open = new Set(layout.getLayout().tabs.threads.map(threadTabId));
360
+ open.add(state.conversationId ?? "new");
361
+ const keys = Object.keys(drafts);
362
+ if (keys.every((key) => open.has(key))) return drafts;
363
+ return Object.fromEntries(keys.filter((key) => open.has(key)).map((key) => [key, drafts[key]]));
364
+ };
365
+ const restoreDrafts = () => {
366
+ if (!draftStorage) return;
367
+ const owner = draftStorage.owner();
368
+ if (owner !== draftsOwner) {
369
+ if (draftsOwner !== null) update({ drafts: {} });
370
+ draftsOwner = owner;
371
+ draftsRestored = false;
372
+ }
373
+ if (draftsRestored || restoring || owner === null) return;
374
+ restoring = true;
375
+ void Promise.all([layout.hydrated(), hydration]).then(() => draftStorage.load()).then(
376
+ (saved) => {
377
+ restoring = false;
378
+ if (!saved || draftStorage.owner() !== owner) return;
379
+ draftsRestored = true;
380
+ setDrafts(keptDrafts({ ...saved, ...state.drafts }));
381
+ draftStorage.save(state.drafts);
382
+ },
383
+ () => {
384
+ restoring = false;
385
+ }
386
+ );
387
+ };
388
+ draftStorage?.onClear(() => {
389
+ draftsRestored = false;
390
+ draftsOwner = null;
391
+ preEdit = NO_EDIT;
392
+ if (Object.keys(state.drafts).length > 0) update({ drafts: {} });
393
+ });
394
+ const composer = () => ({
395
+ conversationId: state.conversationId,
396
+ view: {
397
+ cursor: state.cursor,
398
+ editingMessageId: state.editingMessageId,
399
+ preEditDraft: state.editingMessageId ? preEdit.draft : null,
400
+ preEditCursor: state.editingMessageId ? preEdit.cursor : null,
401
+ chatModel: state.chatModel,
402
+ chatModelName: state.chatModelName,
403
+ scrollTop: scrollTop()
404
+ }
405
+ });
406
+ const show = (next) => {
407
+ if (next === null) return;
408
+ const target = next === "list" ? { conversationId: null, view: void 0 } : next;
409
+ const view = target.view;
410
+ preEdit = view ? { draft: view.preEditDraft ?? "", cursor: view.preEditCursor ?? null } : NO_EDIT;
411
+ update({
412
+ conversationId: target.conversationId,
413
+ cursor: view?.cursor ?? null,
414
+ editingMessageId: view?.editingMessageId ?? null,
415
+ chatModel: view?.chatModel,
416
+ chatModelName: view?.chatModelName
417
+ });
418
+ if (!view) setDraftOf(target.conversationId ?? "new", "");
419
+ };
420
+ const afterTabs = () => setDrafts(keptDrafts(state.drafts));
421
+ const openThreadTab = (conversationId) => batch(() => {
422
+ update(
423
+ conversationId === null ? { newAgentChat: null, newChatDocumentId: null } : { newAgentChat: null }
424
+ );
425
+ const { tabId, show: next } = layout.threads.open(conversationId, composer());
426
+ show(next);
427
+ layout.setPageTab("conversations", tabId);
428
+ afterTabs();
429
+ return tabId;
430
+ });
431
+ const hydration = persist ? persist.load().then(
432
+ (saved) => {
433
+ const parsed = parseSavedChat(saved);
434
+ if (parsed) {
435
+ migrated = parsed.migrated;
436
+ ownersUser ??= parsed.agentChatOwnersUser;
437
+ const conversationId = state.conversationId ?? parsed.conversationId;
438
+ const savedOpen = conversationId === parsed.conversationId;
439
+ state = {
440
+ ...state,
441
+ conversationId,
442
+ cursor: state.cursor ?? (savedOpen ? parsed.cursor : null),
443
+ chatModel: state.chatModel ?? (savedOpen ? parsed.chatModel ?? void 0 : void 0),
444
+ conversationDocuments: {
445
+ ...parsed.conversationDocuments,
446
+ ...state.conversationDocuments
447
+ },
448
+ conversationDocumentAutoAdd: {
449
+ ...parsed.conversationDocumentAutoAdd,
450
+ ...state.conversationDocumentAutoAdd
451
+ },
452
+ conversationFacts: { ...parsed.conversationFacts, ...state.conversationFacts },
453
+ agentChatOwners: { ...parsed.agentChatOwners, ...state.agentChatOwners }
454
+ };
455
+ }
456
+ isHydrated = true;
457
+ notify();
458
+ },
459
+ () => {
460
+ isHydrated = true;
461
+ notify();
462
+ }
463
+ ) : Promise.resolve();
464
+ void Promise.all([layout.hydrated(), hydration]).then(() => {
465
+ const { threads, activeThread } = layout.getLayout().tabs;
466
+ const tab = activeThread ? threads.find((t) => threadTabId(t) === activeThread) : null;
467
+ if (tab && state.conversationId !== tab.conversationId)
468
+ update({ conversationId: tab.conversationId });
469
+ });
470
+ const generationOf = (conversationId) => state.generating[conversationId];
471
+ const setGeneration = (conversationId, generation) => update({ generating: { ...state.generating, [conversationId]: generation } });
472
+ const stopAll = () => Object.values(state.generating).forEach((generation) => generation.abortController?.abort());
473
+ return {
474
+ getState: () => state,
475
+ subscribe: (listener) => {
476
+ listeners.add(listener);
477
+ return () => {
478
+ listeners.delete(listener);
479
+ waiting.delete(listener);
480
+ };
481
+ },
482
+ settled: (listener) => () => settle(listener),
483
+ forget: (listener) => void waiting.delete(listener),
484
+ batch,
485
+ hydrated: () => hydration,
486
+ isHydrated: () => isHydrated,
487
+ restoreDrafts,
488
+ leave: () => batch(() => {
489
+ stopAll();
490
+ preEdit = NO_EDIT;
491
+ update({
492
+ conversationId: null,
493
+ cursor: null,
494
+ editingMessageId: null,
495
+ chatModel: void 0,
496
+ chatModelName: void 0,
497
+ newConversationAssistantMode: "always",
498
+ paMode: false,
499
+ generating: {},
500
+ // Saved conversations keep their documents; the unsent new chat's belong to this workspace.
501
+ conversationDocuments: withoutNew(state.conversationDocuments),
502
+ conversationDocumentAutoAdd: withoutNew(state.conversationDocumentAutoAdd),
503
+ conversationFacts: {},
504
+ newAgentChat: null,
505
+ newChatDocumentId: null,
506
+ messageTimers: {}
507
+ });
508
+ layout.threads.clear();
509
+ setDrafts({});
510
+ }),
511
+ reset: () => batch(() => {
512
+ stopAll();
513
+ preEdit = NO_EDIT;
514
+ const signedIn = user();
515
+ const keepOwners = signedIn === null || ownersUser === null || signedIn === ownersUser;
516
+ if (signedIn !== null) ownersUser = signedIn;
517
+ update({ ...EMPTY_CHAT_STATE, agentChatOwners: keepOwners ? state.agentChatOwners : {} });
518
+ }),
519
+ setConversationId: (id) => void openThreadTab(id),
520
+ openThreadTab,
521
+ closeThreadTab: (tabId) => batch(() => {
522
+ show(layout.threads.close(tabId));
523
+ layout.setPageTab("conversations", layout.getLayout().tabs.activeThread ?? "history");
524
+ afterTabs();
525
+ }),
526
+ selectThreadTab: (tabId) => batch(() => {
527
+ if (tabId !== null) {
528
+ show(layout.threads.select(tabId, composer()));
529
+ layout.setPageTab("conversations", tabId);
530
+ return;
531
+ }
532
+ layout.threads.showList(composer());
533
+ preEdit = NO_EDIT;
534
+ update({ conversationId: null, cursor: null, editingMessageId: null });
535
+ layout.setPageTab("conversations", "history");
536
+ afterTabs();
537
+ }),
538
+ moveConversation: (fromId, toId) => batch(() => {
539
+ update({
540
+ generating: moveKey(state.generating, fromId, toId),
541
+ conversationDocuments: moveKey(state.conversationDocuments, fromId, toId),
542
+ conversationDocumentAutoAdd: moveKey(state.conversationDocumentAutoAdd, fromId, toId),
543
+ conversationFacts: moveKey(state.conversationFacts, fromId, toId),
544
+ conversationId: state.conversationId ?? toId
545
+ });
546
+ layout.threads.rename(fromId, toId);
547
+ setDrafts(moveKey(state.drafts, fromId, toId));
548
+ }),
549
+ setConversationGenerating: (conversationId, isGenerating, abortController) => {
550
+ const current = generationOf(conversationId);
551
+ setGeneration(conversationId, {
552
+ ...current,
553
+ isGenerating,
554
+ error: isGenerating ? null : current?.error ?? null,
555
+ // Starting with an explicit null means generation this client does not own (an attach to a
556
+ // run started elsewhere): it must not inherit a finished local turn's controller, or the
557
+ // view takes it for a local stream and detaches, which makes it attachable again (an
558
+ // endless loop that froze the tab after Stop). Stopping keeps the controller: the finished
559
+ // turn's bookkeeping still matches on it.
560
+ abortController: isGenerating && abortController === null ? null : abortController ?? current?.abortController ?? null,
561
+ // The run's start is set when the server names its answer (setStreamingAssistantId).
562
+ startTime: null,
563
+ streamingAssistantId: null,
564
+ ...isGenerating ? { lastFailedMessage: null, backendElapsed: null } : {}
565
+ });
566
+ },
567
+ setStreamingAssistantId: (conversationId, streamingAssistantId, startTime = Date.now()) => setGeneration(conversationId, {
568
+ ...generationOf(conversationId) ?? { isGenerating: false, error: null },
569
+ streamingAssistantId,
570
+ startTime
571
+ }),
572
+ setBackendElapsed: (conversationId, backendElapsed) => setGeneration(conversationId, {
573
+ ...generationOf(conversationId) ?? { isGenerating: false, error: null, startTime: null },
574
+ backendElapsed
575
+ }),
576
+ setConversationError: (conversationId, error, lastFailedMessage) => {
577
+ const current = generationOf(conversationId);
578
+ setGeneration(conversationId, {
579
+ ...current,
580
+ isGenerating: false,
581
+ error,
582
+ startTime: current?.startTime ?? null,
583
+ lastFailedMessage: lastFailedMessage ?? current?.lastFailedMessage ?? null
584
+ });
585
+ },
586
+ clearConversationError: (conversationId) => setGeneration(conversationId, {
587
+ ...generationOf(conversationId) ?? { isGenerating: false, startTime: null },
588
+ error: null,
589
+ lastFailedMessage: null
590
+ }),
591
+ abortConversationGeneration: (conversationId) => {
592
+ const current = generationOf(conversationId);
593
+ current?.abortController?.abort();
594
+ setGeneration(conversationId, {
595
+ ...current ?? { error: null, startTime: null },
596
+ isGenerating: false,
597
+ abortController: null
598
+ });
599
+ },
600
+ setPaMode: (paMode) => {
601
+ if (state.paMode !== paMode) update({ paMode });
602
+ },
603
+ setConversationDocuments: (conversationId, documents, autoAdd = false) => update({
604
+ conversationDocuments: { ...state.conversationDocuments, [conversationId]: documents },
605
+ conversationDocumentAutoAdd: {
606
+ ...state.conversationDocumentAutoAdd,
607
+ [conversationId]: autoAdd
608
+ }
609
+ }),
610
+ setConversationFacts: (conversationId, factIds) => update({
611
+ conversationFacts: {
612
+ ...state.conversationFacts,
613
+ [conversationId]: Array.from(new Set(factIds))
614
+ }
615
+ }),
616
+ setCursor: (cursor) => {
617
+ if (state.cursor !== cursor) update({ cursor });
618
+ },
619
+ setSuggestedQuery: (suggestedQuery) => update({ suggestedQuery }),
620
+ addQueuedMessage: (id, message) => update({ queuedMessages: queuedMessages.add(state.queuedMessages, id, message) }),
621
+ acknowledgeQueuedMessage: (clientId, serverId) => update({
622
+ queuedMessages: queuedMessages.acknowledge(state.queuedMessages, clientId, serverId)
623
+ }),
624
+ removeQueuedMessage: (clientId) => update({ queuedMessages: queuedMessages.remove(state.queuedMessages, clientId) }),
625
+ removeQueuedMessageByServerId: (serverId) => update({ queuedMessages: queuedMessages.removeByServerId(state.queuedMessages, serverId) }),
626
+ consumeQueuedMessage: (id, content) => update({ queuedMessages: queuedMessages.consume(state.queuedMessages, id, content) }),
627
+ clearQueuedMessages: (id) => update({ queuedMessages: queuedMessages.clear(state.queuedMessages, id) }),
628
+ moveQueuedMessages: (from, to) => update({ queuedMessages: queuedMessages.move(state.queuedMessages, from, to) }),
629
+ setAgentControlState: (id, control) => update(agentControl.setState(state, id, control)),
630
+ setAgentProbeAnswer: (id, probe) => update(agentControl.setProbeAnswer(state, id, probe)),
631
+ setAgentProbePending: (id, pending) => update(agentControl.setProbePending(state, id, pending)),
632
+ clearAgentControl: (id) => update(agentControl.clear(state, id)),
633
+ setDraft: (text) => setDraftOf(state.conversationId ?? "new", text),
634
+ setChatModel: (chatModel) => {
635
+ if (state.chatModel !== chatModel) update({ chatModel });
636
+ },
637
+ setChatModelName: (chatModelName) => {
638
+ if (state.chatModelName !== chatModelName) update({ chatModelName });
639
+ },
640
+ setNewConversationAssistantMode: (newConversationAssistantMode) => {
641
+ if (state.newConversationAssistantMode !== newConversationAssistantMode)
642
+ update({ newConversationAssistantMode });
643
+ },
644
+ setNewAgentChat: (newAgentChat) => {
645
+ const current = state.newAgentChat;
646
+ if (current?.agentId !== newAgentChat?.agentId || current?.workspaceId !== newAgentChat?.workspaceId)
647
+ update({ newAgentChat });
648
+ },
649
+ setNewChatDocumentId: (newChatDocumentId) => {
650
+ if (state.newChatDocumentId !== newChatDocumentId) update({ newChatDocumentId });
651
+ },
652
+ startEditing: (messageId, content) => batch(() => {
653
+ const key = state.conversationId ?? "new";
654
+ preEdit = { draft: state.drafts[key] ?? "", cursor: state.cursor };
655
+ update({ editingMessageId: messageId });
656
+ setDraftOf(key, content);
657
+ }),
658
+ cancelEditing: () => {
659
+ if (!state.editingMessageId) return;
660
+ const restore = preEdit;
661
+ preEdit = NO_EDIT;
662
+ batch(() => {
663
+ update({ editingMessageId: null, cursor: restore.cursor });
664
+ setDraftOf(state.conversationId ?? "new", restore.draft);
665
+ });
666
+ },
667
+ completeEditing: () => {
668
+ preEdit = NO_EDIT;
669
+ if (state.editingMessageId) update({ editingMessageId: null });
670
+ },
671
+ setDeepLinkMessageId: (deepLinkMessageId) => {
672
+ if (state.deepLinkMessageId !== deepLinkMessageId) update({ deepLinkMessageId });
673
+ },
674
+ setActiveCitationKey: (activeCitationKey) => {
675
+ if (state.activeCitationKey !== activeCitationKey) update({ activeCitationKey });
676
+ },
677
+ setPendingTools: (pendingTools) => update({ pendingTools }),
678
+ setAutoSendDraft: (autoSendDraft) => {
679
+ if (state.autoSendDraft !== autoSendDraft) update({ autoSendDraft });
680
+ },
681
+ setAgentChatOwner: (conversationId, agentId) => {
682
+ const signedIn = user();
683
+ ownersUser = signedIn ?? ownersUser;
684
+ update({ agentChatOwners: { ...state.agentChatOwners, [conversationId]: agentId } });
685
+ },
686
+ setMessageTimer: (key, timer) => update({ messageTimers: { ...state.messageTimers, [key]: timer } }),
687
+ clearMessageTimer: (key) => {
688
+ if (!(key in state.messageTimers)) return;
689
+ const messageTimers = { ...state.messageTimers };
690
+ delete messageTimers[key];
691
+ update({ messageTimers });
692
+ },
693
+ startNewConversation: () => batch(() => {
694
+ preEdit = NO_EDIT;
695
+ update({
696
+ conversationId: null,
697
+ cursor: null,
698
+ editingMessageId: null,
699
+ chatModel: void 0,
700
+ chatModelName: void 0,
701
+ newConversationAssistantMode: "always",
702
+ paMode: false,
703
+ newChatDocumentId: null
704
+ });
705
+ })
706
+ };
707
+ }
708
+
709
+ // src/session/layout.ts
710
+ var PANEL_MODES = ["main", "docked", "overlay", "popout"];
711
+ var defaultPinned = () => typeof window === "undefined" || window.innerWidth >= 640;
712
+ function defaultLayoutState() {
713
+ return {
714
+ shell: {
715
+ page: "home",
716
+ sidebar: { pinned: defaultPinned(), hovering: false, drawerOpen: false },
717
+ panels: { main: null, docked: null, overlay: null, popout: null, modes: {}, sizes: {} },
718
+ focusedArea: "main"
719
+ },
720
+ tabs: {
721
+ threads: [],
722
+ activeThread: null,
723
+ threadScrollRestore: null,
724
+ dm: [],
725
+ activeDm: null,
726
+ dmMessageId: null,
727
+ projects: [],
728
+ activeProject: null
729
+ },
730
+ views: { tabs: {}, filters: {}, values: {}, transient: {} },
731
+ grid: { states: {}, rowHeights: {}, labelWidths: {}, savedViews: {} }
732
+ };
733
+ }
734
+ var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
735
+ var recordOf2 = (value, keep) => isRecord3(value) ? Object.fromEntries(Object.entries(value).filter(([, v]) => keep(v))) : {};
736
+ var isString2 = (v) => typeof v === "string";
737
+ var isNumber = (v) => typeof v === "number" && Number.isFinite(v);
738
+ var isAny = (v) => v !== void 0;
739
+ var isMode = (v) => PANEL_MODES.includes(v);
740
+ var isSize = (v) => isRecord3(v) && isNumber(v.width) && isNumber(v.height);
741
+ var isList = (v) => Array.isArray(v);
742
+ var stringOrNull3 = (v) => isString2(v) ? v : null;
743
+ function parseSavedLayout(saved) {
744
+ if (!isRecord3(saved) || saved.version !== 1) return null;
745
+ const shell = isRecord3(saved.shell) ? saved.shell : {};
746
+ const panels = isRecord3(shell.panels) ? shell.panels : {};
747
+ const tabs = isRecord3(saved.tabs) ? saved.tabs : {};
748
+ const views = isRecord3(saved.views) ? saved.views : {};
749
+ const grid = isRecord3(saved.grid) ? saved.grid : {};
750
+ const defaults = defaultLayoutState();
751
+ return {
752
+ version: 1,
753
+ migrated: isList(saved.migrated) ? saved.migrated.filter(isString2) : [],
754
+ shell: {
755
+ page: isString2(shell.page) ? shell.page : defaults.shell.page,
756
+ sidebarPinned: typeof shell.sidebarPinned === "boolean" ? shell.sidebarPinned : defaults.shell.sidebar.pinned,
757
+ panels: {
758
+ main: stringOrNull3(panels.main),
759
+ docked: stringOrNull3(panels.docked),
760
+ modes: recordOf2(panels.modes, isMode),
761
+ sizes: recordOf2(panels.sizes, isSize)
762
+ }
763
+ },
764
+ tabs: parseSavedThreadTabs(tabs.threads, tabs.activeThread),
765
+ views: {
766
+ tabs: recordOf2(views.tabs, isString2),
767
+ filters: recordOf2(views.filters, isAny),
768
+ values: recordOf2(views.values, isAny)
769
+ },
770
+ grid: {
771
+ states: recordOf2(grid.states, isRecord3),
772
+ rowHeights: recordOf2(grid.rowHeights, isNumber),
773
+ labelWidths: recordOf2(grid.labelWidths, isNumber),
774
+ savedViews: recordOf2(grid.savedViews, isList)
775
+ }
776
+ };
777
+ }
778
+ function savedLayoutOf(layout, migrated) {
779
+ const { shell, tabs, views, grid } = layout;
780
+ return {
781
+ version: 1,
782
+ migrated,
783
+ shell: {
784
+ page: shell.page,
785
+ sidebarPinned: shell.sidebar.pinned,
786
+ // A panel over the page or in its own window does not come back after a reload.
787
+ panels: {
788
+ main: shell.panels.main,
789
+ docked: shell.panels.docked,
790
+ modes: shell.panels.modes,
791
+ sizes: shell.panels.sizes
792
+ }
793
+ },
794
+ tabs: { threads: savedThreadTabs(tabs.threads), activeThread: tabs.activeThread },
795
+ views: { tabs: views.tabs, filters: views.filters, values: views.values },
796
+ grid
797
+ };
798
+ }
799
+ function restoreLayout(current, saved) {
800
+ return {
801
+ ...current,
802
+ shell: {
803
+ ...current.shell,
804
+ page: saved.shell.page,
805
+ sidebar: { ...current.shell.sidebar, pinned: saved.shell.sidebarPinned },
806
+ panels: { ...current.shell.panels, ...saved.shell.panels }
807
+ },
808
+ tabs: { ...current.tabs, ...saved.tabs },
809
+ views: { ...saved.views, transient: current.views.transient },
810
+ grid: saved.grid
811
+ };
812
+ }
813
+ function openPanelIn(panels, panelId, mode) {
814
+ const leave = (slot) => slot === panelId ? null : slot;
815
+ const next = {
816
+ ...panels,
817
+ main: leave(panels.main),
818
+ docked: leave(panels.docked),
819
+ overlay: leave(panels.overlay),
820
+ popout: leave(panels.popout),
821
+ modes: { ...panels.modes, [panelId]: mode }
822
+ };
823
+ if (mode === "docked") return { ...next, docked: panelId, overlay: null };
824
+ if (mode === "overlay") return { ...next, overlay: panelId, docked: null };
825
+ return { ...next, [mode]: panelId };
826
+ }
827
+ function closePanelIn(panels, panelId) {
828
+ const leave = (slot) => slot === panelId ? null : slot;
829
+ return {
830
+ ...panels,
831
+ main: leave(panels.main),
832
+ docked: leave(panels.docked),
833
+ overlay: leave(panels.overlay),
834
+ popout: leave(panels.popout)
835
+ };
836
+ }
837
+ function panelPlace(panels, panelId) {
838
+ return PANEL_MODES.find((mode) => panels[mode] === panelId) ?? null;
839
+ }
840
+ var isPanelOpen = (state, panelId) => panelPlace(state.shell.panels, panelId) !== null;
841
+ var dmTabId = (tab) => tab.recipientId ?? "new";
842
+ function createArbiLayoutStore({
843
+ persist,
844
+ initial
845
+ } = {}) {
846
+ let layout = { ...defaultLayoutState(), ...initial };
847
+ let migrated = [];
848
+ let isHydrated = !persist;
849
+ const listeners = /* @__PURE__ */ new Set();
850
+ const save = () => {
851
+ if (persist && isHydrated) persist.save(savedLayoutOf(layout, migrated));
852
+ };
853
+ const update = (next) => {
854
+ if (next === layout) return;
855
+ const persisted = next.tabs.threads !== layout.tabs.threads || next.tabs.activeThread !== layout.tabs.activeThread || next.views.tabs !== layout.views.tabs || next.views.filters !== layout.views.filters || next.views.values !== layout.views.values || next.grid !== layout.grid || next.shell.page !== layout.shell.page || next.shell.sidebar.pinned !== layout.shell.sidebar.pinned || next.shell.panels !== layout.shell.panels;
856
+ layout = next;
857
+ listeners.forEach((listener) => listener());
858
+ if (persisted) save();
859
+ };
860
+ const shell = (change) => update({ ...layout, shell: { ...layout.shell, ...change } });
861
+ const tabs = (change) => update({ ...layout, tabs: { ...layout.tabs, ...change } });
862
+ const views = (change) => update({ ...layout, views: { ...layout.views, ...change } });
863
+ const grid = (change) => update({ ...layout, grid: { ...layout.grid, ...change } });
864
+ const hydration = persist ? persist.load().then(
865
+ (saved) => {
866
+ const parsed = parseSavedLayout(saved);
867
+ if (parsed) {
868
+ migrated = parsed.migrated;
869
+ layout = restoreLayout(layout, parsed);
870
+ }
871
+ isHydrated = true;
872
+ listeners.forEach((listener) => listener());
873
+ },
874
+ () => {
875
+ isHydrated = true;
876
+ listeners.forEach((listener) => listener());
877
+ }
878
+ ) : Promise.resolve();
879
+ return {
304
880
  getLayout: () => layout,
305
881
  reset: () => update(defaultLayoutState()),
306
882
  subscribe: (listener) => {
@@ -334,10 +910,6 @@ function createArbiLayoutStore({
334
910
  if (layout.tabs.threads.length > 0 || layout.tabs.activeThread !== null)
335
911
  tabs({ threads: [], activeThread: null });
336
912
  },
337
- restoreDrafts: (drafts) => {
338
- const threads = withThreadDrafts(layout.tabs.threads, drafts);
339
- if (threads !== layout.tabs.threads) tabs({ threads });
340
- },
341
913
  consumeScrollRestore: () => {
342
914
  const value = layout.tabs.threadScrollRestore;
343
915
  if (value !== null) tabs({ threadScrollRestore: null });
@@ -520,135 +1092,27 @@ function readWorkspaceLastOpened(lastOpenedKey = WORKSPACE_LAST_OPENED_KEY) {
520
1092
  } catch {
521
1093
  return {};
522
1094
  }
523
- }
524
-
525
- // src/chat/run-state.ts
526
- var MIN_QUEUED_MESSAGE_VISIBILITY_MS = 800;
527
- var mapQueues = (queues, update) => Object.fromEntries(Object.entries(queues).map(([id, messages]) => [id, update(messages)]));
528
- function findConsumedMessages(messages, combinedContent) {
529
- const text = combinedContent.trim();
530
- const taken = /* @__PURE__ */ new Set();
531
- let cursor = 0;
532
- for (const message of messages) {
533
- if (cursor >= text.length) break;
534
- const content = message.content.trim();
535
- if (!content) continue;
536
- const separator = cursor === 0 ? "" : "\n\n";
537
- if (text.startsWith(separator + content, cursor)) {
538
- taken.add(message.clientId);
539
- cursor += separator.length + content.length;
540
- }
541
- }
542
- return cursor === text.length ? taken : /* @__PURE__ */ new Set();
543
- }
544
- var queuedMessages = {
545
- add: (q, responseId, message) => ({
546
- ...q,
547
- [responseId]: [...q[responseId] ?? [], message]
548
- }),
549
- acknowledge: (q, clientId, serverId) => mapQueues(
550
- q,
551
- (messages) => messages.map((m) => m.clientId === clientId ? { ...m, serverId, acknowledged: true } : m)
552
- ),
553
- remove: (q, clientId) => mapQueues(q, (messages) => messages.filter((m) => m.clientId !== clientId)),
554
- removeByServerId: (q, serverId) => mapQueues(q, (messages) => messages.filter((m) => m.serverId !== serverId)),
555
- removeByContent: (q, responseId, content) => {
556
- let removed = false;
557
- return {
558
- ...q,
559
- [responseId]: (q[responseId] ?? []).filter((m) => {
560
- if (!removed && m.content === content) {
561
- removed = true;
562
- return false;
563
- }
564
- return true;
565
- })
566
- };
567
- },
568
- /** Take out the prompts a steering interjection delivered into the run. */
569
- consume: (q, responseId, combinedContent) => {
570
- const messages = q[responseId] ?? [];
571
- const consumed = findConsumedMessages(messages, combinedContent);
572
- return { ...q, [responseId]: messages.filter((m) => !consumed.has(m.clientId)) };
573
- },
574
- clear: (q, responseId) => ({ ...q, [responseId]: [] }),
575
- /** Move the entries still waiting on a finished turn to the turn that is now running. */
576
- move: (q, fromResponseId, toResponseId) => {
577
- const waiting = q[fromResponseId] ?? [];
578
- if (fromResponseId === toResponseId || waiting.length === 0) return q;
579
- return { ...q, [fromResponseId]: [], [toResponseId]: [...q[toResponseId] ?? [], ...waiting] };
580
- },
581
- /** Find a queued follow-up by its client id, under whichever turn holds it. */
582
- find: (q, clientId) => Object.values(q).flat().find((m) => m.clientId === clientId)
583
- };
584
- var without = (record, key) => {
585
- const next = { ...record };
586
- delete next[key];
587
- return next;
588
- };
589
- var agentControl = {
590
- setState: (c, id, state) => ({
591
- ...c,
592
- agentControlStates: { ...c.agentControlStates, [id]: state }
593
- }),
594
- /** A probe answered: the latest answer, and nothing pending any more. */
595
- setProbeAnswer: (c, id, probe) => ({
596
- ...c,
597
- agentProbeAnswers: { ...c.agentProbeAnswers, [id]: probe },
598
- agentProbePending: { ...c.agentProbePending, [id]: false }
599
- }),
600
- setProbePending: (c, id, pending) => ({
601
- ...c,
602
- agentProbePending: { ...c.agentProbePending, [id]: pending }
603
- }),
604
- /** The turn is over: drop its control state. */
605
- clear: (c, id) => ({
606
- agentControlStates: without(c.agentControlStates, id),
607
- agentProbeAnswers: without(c.agentProbeAnswers, id),
608
- agentProbePending: without(c.agentProbePending, id)
609
- })
610
- };
611
-
612
- // src/session/store.ts
613
- var DEFAULT_DOCUMENT_VIEWER_STATE = {
614
- tab: "pdf",
615
- compareId: null,
616
- tabRequest: null,
617
- textScale: 1,
618
- phonePdfChosen: false
619
- };
620
- var EMPTY_SESSION_STATE = {
621
- workspaceId: null,
622
- workspaceStatus: "closed",
623
- conversationId: null,
624
- viewingDocument: null,
625
- documentViewer: DEFAULT_DOCUMENT_VIEWER_STATE,
626
- selectedDocumentIds: [],
627
- generating: {},
628
- paMode: false,
629
- conversationDocuments: {},
630
- conversationDocumentAutoAdd: {},
631
- conversationFacts: {},
632
- pinnedDocumentId: null,
633
- cursor: null,
634
- suggestedQuery: null,
635
- queuedMessages: {},
636
- agentControlStates: {},
637
- agentProbeAnswers: {},
638
- agentProbePending: {},
639
- drafts: {},
640
- editingMessageId: null,
641
- deepLinkMessageId: null,
642
- activeCitationKey: null,
643
- chatModel: void 0,
644
- chatModelName: void 0,
645
- newConversationAssistantMode: "always",
646
- pendingTools: null,
647
- autoSendDraft: false,
648
- agentChatOwners: {},
649
- newAgentChat: null,
650
- newChatDocumentId: null,
651
- messageTimers: {},
1095
+ }
1096
+
1097
+ // src/session/store.ts
1098
+ var DEFAULT_DOCUMENT_VIEWER_STATE = {
1099
+ tab: "pdf",
1100
+ compareId: null,
1101
+ tabRequest: null,
1102
+ textScale: 1,
1103
+ phonePdfChosen: false
1104
+ };
1105
+ var EMPTY_DOCUMENT_STATE = {
1106
+ viewingDocument: null,
1107
+ documentViewer: DEFAULT_DOCUMENT_VIEWER_STATE,
1108
+ selectedDocumentIds: [],
1109
+ pinnedDocumentId: null
1110
+ };
1111
+ var EMPTY_SESSION_STATE = {
1112
+ workspaceId: null,
1113
+ workspaceStatus: "closed",
1114
+ ...EMPTY_CHAT_STATE,
1115
+ ...EMPTY_DOCUMENT_STATE,
652
1116
  ...defaultLayoutState()
653
1117
  };
654
1118
  var selectViewingDocumentId = (state) => state.viewingDocument ? parseVersionRef(state.viewingDocument.id).docId : null;
@@ -664,155 +1128,19 @@ function nextViewingDocument(current, input) {
664
1128
  passageId: input.passageId !== void 0 ? input.passageId : current?.passageId ?? null
665
1129
  };
666
1130
  }
667
- function layoutActionsOf(layout) {
668
- const actions = { ...layout };
669
- delete actions.getLayout;
670
- delete actions.subscribe;
671
- delete actions.hydrated;
672
- delete actions.isHydrated;
673
- delete actions.reset;
674
- delete actions.threads;
675
- return actions;
676
- }
677
- function withoutNew(record) {
678
- const { new: _new, ...saved } = record;
679
- return saved;
680
- }
681
- function createArbiSessionStore(initial = {}, {
682
- draftStorage,
683
- persist,
684
- workspace = createArbiWorkspaceStore({ workspaceId: initial.workspaceId ?? null })
685
- } = {}) {
686
- const { shell, tabs, views, grid, ...rest } = initial;
687
- const layout = createArbiLayoutStore({
688
- persist,
689
- initial: Object.fromEntries(
690
- Object.entries({ shell, tabs, views, grid }).filter(([, part]) => part !== void 0)
691
- )
692
- });
693
- if (initial.workspaceStatus) workspace.setWorkspaceStatus(initial.workspaceStatus);
694
- let state = {
695
- ...EMPTY_SESSION_STATE,
696
- ...rest,
697
- ...layout.getLayout(),
698
- ...workspace.getState()
699
- };
1131
+ function createArbiDocumentSessionStore(initial = {}) {
1132
+ let state = { ...EMPTY_DOCUMENT_STATE, ...initial };
700
1133
  const listeners = /* @__PURE__ */ new Set();
701
1134
  const update = (patch) => {
702
1135
  state = { ...state, ...patch };
703
1136
  listeners.forEach((listener) => listener());
704
1137
  };
705
- layout.subscribe(() => update(layout.getLayout()));
706
- workspace.subscribe(() => update(workspace.getState()));
707
- let preEdit = { draft: "", cursor: null };
708
- let draftsRestored = !draftStorage;
709
- let draftsOwner = draftStorage?.owner() ?? null;
710
- const restoreDrafts = () => {
711
- if (!draftStorage) return;
712
- const owner = draftStorage.owner();
713
- if (owner !== draftsOwner) {
714
- if (draftsOwner !== null) update({ drafts: {} });
715
- draftsOwner = owner;
716
- draftsRestored = false;
717
- }
718
- if (draftsRestored) return;
719
- draftsRestored = true;
720
- void draftStorage.load().then(
721
- (saved) => {
722
- if (saved && draftStorage.owner() === owner)
723
- update({ drafts: { ...saved, ...state.drafts } });
724
- else draftsRestored = false;
725
- },
726
- () => {
727
- draftsRestored = false;
728
- }
729
- );
730
- };
731
- draftStorage?.onClear(() => {
732
- draftsRestored = false;
733
- draftsOwner = null;
734
- preEdit = { draft: "", cursor: null };
735
- if (Object.keys(state.drafts).length > 0) update({ drafts: {} });
736
- });
737
- restoreDrafts();
738
- const setDrafts = (drafts) => {
739
- update({ drafts });
740
- draftStorage?.save(drafts);
741
- };
742
- const setDraftOf = (key, text) => {
743
- if ((state.drafts[key] ?? "") === text) return;
744
- const drafts = { ...state.drafts };
745
- if (text) drafts[key] = text;
746
- else delete drafts[key];
747
- setDrafts(drafts);
748
- };
749
- const composer = () => ({
750
- conversationId: state.conversationId,
751
- view: {
752
- cursor: state.cursor,
753
- draft: selectDraft(state),
754
- editingMessageId: state.editingMessageId,
755
- preEditDraft: state.editingMessageId ? preEdit.draft : null,
756
- preEditCursor: state.editingMessageId ? preEdit.cursor : null,
757
- chatModel: state.chatModel,
758
- chatModelName: state.chatModelName,
759
- scrollTop: null
760
- }
761
- });
762
- const show = (next) => {
763
- if (next === null) return;
764
- if (next === "list") {
765
- preEdit = { draft: "", cursor: null };
766
- return update({ conversationId: null, cursor: null, editingMessageId: null });
767
- }
768
- const view = next.view;
769
- preEdit = { draft: view?.preEditDraft ?? "", cursor: view?.preEditCursor ?? null };
770
- update({
771
- conversationId: next.conversationId,
772
- cursor: view?.cursor ?? null,
773
- editingMessageId: view?.editingMessageId ?? null,
774
- chatModel: view?.chatModel,
775
- chatModelName: view?.chatModelName
776
- });
777
- };
778
1138
  return {
779
- ...layoutActionsOf(layout),
780
- layoutHydrated: () => layout.hydrated(),
781
- isLayoutHydrated: () => layout.isHydrated(),
782
1139
  getState: () => state,
783
1140
  subscribe: (listener) => {
784
1141
  listeners.add(listener);
785
1142
  return () => listeners.delete(listener);
786
1143
  },
787
- setWorkspaceId: (workspaceId) => {
788
- workspace.setWorkspaceId(workspaceId);
789
- restoreDrafts();
790
- },
791
- setWorkspaceStatus: (workspaceStatus) => workspace.setWorkspaceStatus(workspaceStatus),
792
- leaveWorkspace: () => {
793
- Object.values(state.generating).forEach((generation) => generation.abortController?.abort());
794
- workspace.setWorkspaceStatus("closed");
795
- update({
796
- conversationId: null,
797
- cursor: null,
798
- viewingDocument: null,
799
- selectedDocumentIds: [],
800
- generating: {},
801
- // Saved conversations keep their documents; the unsent new chat's belong to this workspace.
802
- conversationDocuments: withoutNew(state.conversationDocuments),
803
- conversationDocumentAutoAdd: withoutNew(state.conversationDocumentAutoAdd),
804
- conversationFacts: {},
805
- pinnedDocumentId: null,
806
- newAgentChat: null,
807
- newChatDocumentId: null,
808
- messageTimers: {}
809
- });
810
- layout.threads.clear();
811
- },
812
- setConversationId: (conversationId) => {
813
- if (state.conversationId !== conversationId)
814
- update({ conversationId, cursor: null, editingMessageId: null });
815
- },
816
1144
  setViewingDocument: (input) => update({ viewingDocument: nextViewingDocument(state.viewingDocument, input) }),
817
1145
  setDocumentViewer: (change) => {
818
1146
  const next = { ...state.documentViewer, ...change };
@@ -821,209 +1149,117 @@ function createArbiSessionStore(initial = {}, {
821
1149
  update({ documentViewer: next });
822
1150
  },
823
1151
  setSelectedDocumentIds: (ids) => update({ selectedDocumentIds: ids }),
824
- setConversationGenerating: (conversationId, isGenerating, abortController = null) => {
825
- const current = state.generating[conversationId];
826
- update({
827
- generating: {
828
- ...state.generating,
829
- [conversationId]: {
830
- ...current,
831
- isGenerating,
832
- error: isGenerating ? null : current?.error ?? null,
833
- startTime: isGenerating ? Date.now() : null,
834
- streamingAssistantId: isGenerating ? current?.streamingAssistantId ?? null : null,
835
- backendElapsed: isGenerating ? current?.backendElapsed ?? null : null,
836
- abortController
837
- }
838
- }
839
- });
840
- },
841
- setStreamingAssistantId: (conversationId, streamingAssistantId, startTime = Date.now()) => {
842
- const current = state.generating[conversationId];
843
- update({
844
- generating: {
845
- ...state.generating,
846
- [conversationId]: {
847
- ...current ?? { isGenerating: false, error: null },
848
- streamingAssistantId,
849
- startTime
850
- }
851
- }
852
- });
853
- },
854
- setBackendElapsed: (conversationId, backendElapsed) => {
855
- const current = state.generating[conversationId] ?? {
856
- isGenerating: false,
857
- error: null,
858
- startTime: null
859
- };
860
- update({
861
- generating: { ...state.generating, [conversationId]: { ...current, backendElapsed } }
862
- });
863
- },
864
- moveConversation: (fromId, toId) => {
865
- const move = (record) => {
866
- const next = { ...record };
867
- if (fromId in next) {
868
- next[toId] = next[fromId];
869
- delete next[fromId];
870
- }
871
- return next;
872
- };
873
- update({
874
- generating: move(state.generating),
875
- conversationDocuments: move(state.conversationDocuments),
876
- conversationDocumentAutoAdd: move(state.conversationDocumentAutoAdd),
877
- conversationFacts: move(state.conversationFacts),
878
- conversationId: state.conversationId ?? toId
879
- });
880
- layout.threads.rename(fromId, toId);
881
- if (fromId in state.drafts) setDrafts(move(state.drafts));
882
- },
883
- setConversationFacts: (conversationId, factIds) => update({ conversationFacts: { ...state.conversationFacts, [conversationId]: factIds } }),
884
1152
  setPinnedDocumentId: (pinnedDocumentId) => update({ pinnedDocumentId }),
885
- setCursor: (cursor) => {
886
- if (state.cursor !== cursor) update({ cursor });
887
- },
888
- setSuggestedQuery: (suggestedQuery) => update({ suggestedQuery }),
889
- addQueuedMessage: (id, message) => update({ queuedMessages: queuedMessages.add(state.queuedMessages, id, message) }),
890
- acknowledgeQueuedMessage: (clientId, serverId) => update({
891
- queuedMessages: queuedMessages.acknowledge(state.queuedMessages, clientId, serverId)
892
- }),
893
- removeQueuedMessage: (clientId) => update({ queuedMessages: queuedMessages.remove(state.queuedMessages, clientId) }),
894
- removeQueuedMessageByServerId: (serverId) => update({ queuedMessages: queuedMessages.removeByServerId(state.queuedMessages, serverId) }),
895
- consumeQueuedMessage: (id, content) => update({ queuedMessages: queuedMessages.consume(state.queuedMessages, id, content) }),
896
- clearQueuedMessages: (id) => update({ queuedMessages: queuedMessages.clear(state.queuedMessages, id) }),
897
- moveQueuedMessages: (from, to) => update({ queuedMessages: queuedMessages.move(state.queuedMessages, from, to) }),
898
- setAgentControlState: (id, control) => update(agentControl.setState(state, id, control)),
899
- setAgentProbeAnswer: (id, probe) => update(agentControl.setProbeAnswer(state, id, probe)),
900
- setAgentProbePending: (id, pending) => update(agentControl.setProbePending(state, id, pending)),
901
- clearAgentControl: (id) => update(agentControl.clear(state, id)),
902
- setDraft: (text) => setDraftOf(conversationKey(state), text),
903
- setChatModel: (chatModel) => {
904
- if (state.chatModel !== chatModel) update({ chatModel });
905
- },
906
- openThreadTab: (conversationId) => {
907
- if (conversationId === null) update({ newAgentChat: null, newChatDocumentId: null });
908
- else if (state.newAgentChat) update({ newAgentChat: null });
909
- const { tabId, show: next } = layout.threads.open(conversationId, composer());
910
- show(next);
911
- return tabId;
912
- },
913
- closeThreadTab: (tabId) => show(layout.threads.close(tabId)),
914
- selectThreadTab: (tabId) => {
915
- if (tabId !== null) return show(layout.threads.select(tabId, composer()));
916
- layout.threads.showList(composer());
917
- show("list");
918
- },
919
- setChatModelName: (chatModelName) => {
920
- if (state.chatModelName !== chatModelName) update({ chatModelName });
921
- },
922
- setNewConversationAssistantMode: (newConversationAssistantMode) => {
923
- if (state.newConversationAssistantMode !== newConversationAssistantMode)
924
- update({ newConversationAssistantMode });
925
- },
926
- setNewAgentChat: (newAgentChat) => {
927
- const current = state.newAgentChat;
928
- if (current?.agentId !== newAgentChat?.agentId || current?.workspaceId !== newAgentChat?.workspaceId)
929
- update({ newAgentChat });
930
- },
931
- setNewChatDocumentId: (newChatDocumentId) => {
932
- if (state.newChatDocumentId !== newChatDocumentId) update({ newChatDocumentId });
933
- },
934
- startEditing: (messageId, content) => {
935
- preEdit = { draft: selectDraft(state), cursor: state.cursor };
936
- update({ editingMessageId: messageId });
937
- setDraftOf(conversationKey(state), content);
938
- },
939
- cancelEditing: () => {
940
- if (!state.editingMessageId) return;
941
- update({ editingMessageId: null, cursor: preEdit.cursor });
942
- setDraftOf(conversationKey(state), preEdit.draft);
943
- preEdit = { draft: "", cursor: null };
944
- },
945
- completeEditing: () => {
946
- preEdit = { draft: "", cursor: null };
947
- if (state.editingMessageId) update({ editingMessageId: null });
948
- },
949
- setDeepLinkMessageId: (deepLinkMessageId) => {
950
- if (state.deepLinkMessageId !== deepLinkMessageId) update({ deepLinkMessageId });
951
- },
952
- setActiveCitationKey: (activeCitationKey) => {
953
- if (state.activeCitationKey !== activeCitationKey) update({ activeCitationKey });
954
- },
955
- setPendingTools: (pendingTools) => update({ pendingTools }),
956
- setAutoSendDraft: (autoSendDraft) => {
957
- if (state.autoSendDraft !== autoSendDraft) update({ autoSendDraft });
958
- },
959
- setAgentChatOwner: (conversationId, agentId) => update({ agentChatOwners: { ...state.agentChatOwners, [conversationId]: agentId } }),
960
- setMessageTimer: (key, timer) => update({ messageTimers: { ...state.messageTimers, [key]: timer } }),
961
- clearMessageTimer: (key) => {
962
- if (!(key in state.messageTimers)) return;
963
- const messageTimers = { ...state.messageTimers };
964
- delete messageTimers[key];
965
- update({ messageTimers });
966
- },
967
- setConversationError: (conversationId, error, lastFailedMessage) => {
968
- const current = state.generating[conversationId];
969
- update({
970
- generating: {
971
- ...state.generating,
972
- [conversationId]: {
973
- ...current,
974
- isGenerating: false,
975
- error,
976
- startTime: current?.startTime ?? null,
977
- lastFailedMessage: lastFailedMessage ?? current?.lastFailedMessage ?? null
978
- }
979
- }
980
- });
981
- },
982
- clearConversationError: (conversationId) => {
983
- const current = state.generating[conversationId];
984
- update({
985
- generating: {
986
- ...state.generating,
987
- [conversationId]: {
988
- ...current ?? { isGenerating: false, startTime: null },
989
- error: null,
990
- lastFailedMessage: null
991
- }
992
- }
993
- });
994
- },
995
- abortConversationGeneration: (conversationId) => {
996
- const current = state.generating[conversationId];
997
- current?.abortController?.abort();
998
- update({
999
- generating: {
1000
- ...state.generating,
1001
- [conversationId]: {
1002
- ...current ?? { error: null, startTime: null },
1003
- isGenerating: false,
1004
- abortController: null
1005
- }
1006
- }
1007
- });
1153
+ leave: () => update({ viewingDocument: null, selectedDocumentIds: [], pinnedDocumentId: null })
1154
+ };
1155
+ }
1156
+ function layoutActionsOf(layout) {
1157
+ const actions = { ...layout };
1158
+ delete actions.getLayout;
1159
+ delete actions.subscribe;
1160
+ delete actions.hydrated;
1161
+ delete actions.isHydrated;
1162
+ delete actions.reset;
1163
+ delete actions.threads;
1164
+ return actions;
1165
+ }
1166
+ function chatActionsOf(chat) {
1167
+ const actions = { ...chat };
1168
+ delete actions.getState;
1169
+ delete actions.subscribe;
1170
+ delete actions.settled;
1171
+ delete actions.forget;
1172
+ delete actions.batch;
1173
+ delete actions.hydrated;
1174
+ delete actions.isHydrated;
1175
+ delete actions.restoreDrafts;
1176
+ delete actions.leave;
1177
+ delete actions.reset;
1178
+ return actions;
1179
+ }
1180
+ function composeArbiSessionStore({
1181
+ layout,
1182
+ workspace,
1183
+ chat,
1184
+ documents
1185
+ }) {
1186
+ let last = null;
1187
+ const getState = () => {
1188
+ const parts = [
1189
+ layout.getLayout(),
1190
+ workspace.getState(),
1191
+ documents.getState(),
1192
+ chat.getState()
1193
+ ];
1194
+ if (last && parts.every((part, i) => part === last.parts[i])) return last.state;
1195
+ const [l, w, d, c] = parts;
1196
+ last = { parts, state: { ...l, ...w, ...d, ...c } };
1197
+ return last.state;
1198
+ };
1199
+ chat.restoreDrafts();
1200
+ workspace.subscribe(() => chat.restoreDrafts());
1201
+ return {
1202
+ ...layoutActionsOf(layout),
1203
+ ...chatActionsOf(chat),
1204
+ getState,
1205
+ layoutHydrated: () => layout.hydrated(),
1206
+ isLayoutHydrated: () => layout.isHydrated(),
1207
+ subscribe: (onChange) => {
1208
+ const told = chat.settled(onChange);
1209
+ const unsubscribe = [
1210
+ chat.subscribe(onChange),
1211
+ layout.subscribe(told),
1212
+ workspace.subscribe(told),
1213
+ documents.subscribe(told)
1214
+ ];
1215
+ return () => {
1216
+ chat.forget(onChange);
1217
+ unsubscribe.forEach((stop) => stop());
1218
+ };
1008
1219
  },
1009
- startNewConversation: () => update({
1010
- conversationId: null,
1011
- cursor: null,
1012
- editingMessageId: null,
1013
- newChatDocumentId: null
1220
+ setWorkspaceId: (id) => workspace.setWorkspaceId(id),
1221
+ setWorkspaceStatus: (status) => workspace.setWorkspaceStatus(status),
1222
+ leaveWorkspace: () => chat.batch(() => {
1223
+ workspace.setWorkspaceStatus("closed");
1224
+ chat.leave();
1225
+ documents.leave();
1014
1226
  }),
1015
- setPaMode: (paMode) => {
1016
- if (state.paMode !== paMode) update({ paMode });
1017
- },
1018
- setConversationDocuments: (conversationId, documents, autoAdd = false) => update({
1019
- conversationDocuments: { ...state.conversationDocuments, [conversationId]: documents },
1020
- conversationDocumentAutoAdd: {
1021
- ...state.conversationDocumentAutoAdd,
1022
- [conversationId]: autoAdd
1023
- }
1024
- })
1227
+ setViewingDocument: (input) => documents.setViewingDocument(input),
1228
+ setDocumentViewer: (change) => documents.setDocumentViewer(change),
1229
+ setSelectedDocumentIds: (ids) => documents.setSelectedDocumentIds(ids),
1230
+ setPinnedDocumentId: (id) => documents.setPinnedDocumentId(id)
1025
1231
  };
1026
1232
  }
1233
+ function createArbiSessionStore(initial = {}, {
1234
+ draftStorage,
1235
+ persist,
1236
+ workspace = createArbiWorkspaceStore({ workspaceId: initial.workspaceId ?? null })
1237
+ } = {}) {
1238
+ const {
1239
+ shell,
1240
+ tabs,
1241
+ views,
1242
+ grid,
1243
+ workspaceStatus,
1244
+ viewingDocument,
1245
+ documentViewer,
1246
+ selectedDocumentIds,
1247
+ pinnedDocumentId,
1248
+ ...chatInitial
1249
+ } = initial;
1250
+ delete chatInitial.workspaceId;
1251
+ const defined = (parts) => Object.fromEntries(Object.entries(parts).filter(([, part]) => part !== void 0));
1252
+ const layout = createArbiLayoutStore({ persist, initial: defined({ shell, tabs, views, grid }) });
1253
+ if (workspaceStatus) workspace.setWorkspaceStatus(workspaceStatus);
1254
+ return composeArbiSessionStore({
1255
+ layout,
1256
+ workspace,
1257
+ chat: createArbiChatStore({ layout, draftStorage, initial: chatInitial }),
1258
+ documents: createArbiDocumentSessionStore(
1259
+ defined({ viewingDocument, documentViewer, selectedDocumentIds, pinnedDocumentId })
1260
+ )
1261
+ });
1262
+ }
1027
1263
  var SAVE_DELAY_MS = 500;
1028
1264
  var encoder = new TextEncoder();
1029
1265
  var decoder = new TextDecoder();
@@ -1663,6 +1899,6 @@ function useUserUsageToday(options) {
1663
1899
  });
1664
1900
  }
1665
1901
 
1666
- export { DEFAULT_DOCUMENT_VIEWER_STATE, EMPTY_SESSION_STATE, MAX_THREAD_TABS, MIN_QUEUED_MESSAGE_VISIBILITY_MS, PROJECT_CREATION_DENIED_REASON, WORKSPACE_LAST_OPENED_KEY, WORKSPACE_STORAGE_KEY, agentControl, browserWorkspaceStorage, clearSealedDrafts, closePanelIn, conversationKey, createArbiLayoutStore, createArbiSessionStore, createArbiWorkspaceStore, createDebouncedWriter, createSealedDraftStorage, createStoragePersist, defaultLayoutState, dmTabId, flushOnPageExit, isPanelOpen, isUsageMetered, layoutActionsOf, nextViewingDocument, openPanelIn, panelPlace, parseSavedLayout, queuedMessages, readWorkspaceLastOpened, restoreLayout, savedLayoutOf, selectDraft, selectIsGenerating, selectViewingDocumentId, threadTabId, useActivity, useAgents, useArbiSession, useArbiSessionState, useArbiSessionStore, useAssistantQuery, useAvailableModels, useBoxRole, useBuiltinCommands, useCanCreateProjects, useConfig, useConfigs, useConversationThreads, useConversations, useCurrentUser, useDeleteAgents, useDocumentVersionList, useFirmPool, useHealth, useMessageDetails, useParsedDocument, useProjectInvoices, useProjectUsage, useProjects, useProjectsUsage, useSessionPageTab, useSessionView, useSignedInSession, useTags, useThumbnails, useTopUpFirmPool, useTransientView, useUserUsageToday, useWorkspaceActivity, useWorkspaces };
1667
- //# sourceMappingURL=chunk-QGOYIYLI.js.map
1668
- //# sourceMappingURL=chunk-QGOYIYLI.js.map
1902
+ export { DEFAULT_DOCUMENT_VIEWER_STATE, EMPTY_CHAT_STATE, EMPTY_SESSION_STATE, MAX_THREAD_TABS, MIN_QUEUED_MESSAGE_VISIBILITY_MS, PROJECT_CREATION_DENIED_REASON, WORKSPACE_LAST_OPENED_KEY, WORKSPACE_STORAGE_KEY, agentControl, browserWorkspaceStorage, clearSealedDrafts, closePanelIn, composeArbiSessionStore, conversationKey, createArbiChatStore, createArbiDocumentSessionStore, createArbiLayoutStore, createArbiSessionStore, createArbiWorkspaceStore, createDebouncedWriter, createSealedDraftStorage, createStoragePersist, defaultLayoutState, dmTabId, flushOnPageExit, isPanelOpen, isUsageMetered, layoutActionsOf, nextViewingDocument, openPanelIn, panelPlace, parseSavedChat, parseSavedLayout, queuedMessages, readWorkspaceLastOpened, restoreLayout, savedChatOf, savedLayoutOf, selectDraft, selectIsGenerating, selectViewingDocumentId, threadTabId, useActivity, useAgents, useArbiSession, useArbiSessionState, useArbiSessionStore, useAssistantQuery, useAvailableModels, useBoxRole, useBuiltinCommands, useCanCreateProjects, useConfig, useConfigs, useConversationThreads, useConversations, useCurrentUser, useDeleteAgents, useDocumentVersionList, useFirmPool, useHealth, useMessageDetails, useParsedDocument, useProjectInvoices, useProjectUsage, useProjects, useProjectsUsage, useSessionPageTab, useSessionView, useSignedInSession, useTags, useThumbnails, useTopUpFirmPool, useTransientView, useUserUsageToday, useWorkspaceActivity, useWorkspaces };
1903
+ //# sourceMappingURL=chunk-YWU7HEHL.js.map
1904
+ //# sourceMappingURL=chunk-YWU7HEHL.js.map