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