@polderlabs/bizar 4.4.13 → 4.5.1

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.
Files changed (118) hide show
  1. package/bizar-dash/CHANGELOG.md +37 -276
  2. package/bizar-dash/dist/assets/__vite-browser-external-BIHI7g3E.js +2 -0
  3. package/bizar-dash/dist/assets/__vite-browser-external-BIHI7g3E.js.map +1 -0
  4. package/bizar-dash/dist/assets/main-eWZ4NlCL.css +1 -0
  5. package/bizar-dash/dist/assets/main-usWhlPWa.js +362 -0
  6. package/bizar-dash/dist/assets/main-usWhlPWa.js.map +1 -0
  7. package/bizar-dash/dist/assets/{mobile-DSb-t42Y.js → mobile-DQLFCjwJ.js} +2 -2
  8. package/bizar-dash/dist/assets/{mobile-DSb-t42Y.js.map → mobile-DQLFCjwJ.js.map} +1 -1
  9. package/bizar-dash/dist/assets/mobile-O6ANdD4W.js +352 -0
  10. package/bizar-dash/dist/assets/mobile-O6ANdD4W.js.map +1 -0
  11. package/bizar-dash/dist/index.html +3 -3
  12. package/bizar-dash/dist/mobile.html +2 -2
  13. package/bizar-dash/skills/agent-baseline/SKILL.md +80 -0
  14. package/bizar-dash/skills/bizar/SKILL.md +116 -0
  15. package/bizar-dash/skills/chat/SKILL.md +74 -0
  16. package/bizar-dash/skills/headroom/SKILL.md +94 -0
  17. package/bizar-dash/skills/lightrag/SKILL.md +86 -0
  18. package/bizar-dash/skills/minimax/SKILL.md +80 -0
  19. package/bizar-dash/skills/obsidian/SKILL.md +68 -0
  20. package/bizar-dash/skills/providers/SKILL.md +75 -0
  21. package/bizar-dash/skills/sdk/SKILL.md +138 -0
  22. package/bizar-dash/skills/self-improvement/SKILL.md +53 -0
  23. package/bizar-dash/skills/skills-cli/SKILL.md +94 -0
  24. package/bizar-dash/skills/usage/SKILL.md +62 -0
  25. package/bizar-dash/src/server/api.mjs +18 -0
  26. package/bizar-dash/src/server/headroom.mjs +645 -0
  27. package/bizar-dash/src/server/memory-lightrag.mjs +272 -2
  28. package/bizar-dash/src/server/memory-obsidian.mjs +230 -0
  29. package/bizar-dash/src/server/memory-store.mjs +189 -0
  30. package/bizar-dash/src/server/minimax-usage-store.mjs +372 -0
  31. package/bizar-dash/src/server/minimax.mjs +196 -5
  32. package/bizar-dash/src/server/providers-store.mjs +956 -0
  33. package/bizar-dash/src/server/routes/_shared.mjs +17 -0
  34. package/bizar-dash/src/server/routes/config.mjs +52 -1
  35. package/bizar-dash/src/server/routes/env-vars.mjs +165 -0
  36. package/bizar-dash/src/server/routes/headroom.mjs +126 -0
  37. package/bizar-dash/src/server/routes/lightrag.mjs +154 -0
  38. package/bizar-dash/src/server/routes/memory.mjs +668 -1
  39. package/bizar-dash/src/server/routes/opencode-session-detail.mjs +14 -29
  40. package/bizar-dash/src/server/routes/opencode-sessions.mjs +205 -3
  41. package/bizar-dash/src/server/routes/providers.mjs +266 -5
  42. package/bizar-dash/src/server/routes/skills.mjs +32 -43
  43. package/bizar-dash/src/server/routes/update.mjs +340 -0
  44. package/bizar-dash/src/server/routes/usage.mjs +136 -0
  45. package/bizar-dash/src/server/serve-info.mjs +135 -4
  46. package/bizar-dash/src/server/server.mjs +20 -0
  47. package/bizar-dash/src/server/skills-store.mjs +152 -262
  48. package/bizar-dash/src/web/App.tsx +120 -29
  49. package/bizar-dash/src/web/components/EnvVarManager.tsx +247 -0
  50. package/bizar-dash/src/web/components/HeadroomSettings.tsx +418 -0
  51. package/bizar-dash/src/web/components/HeadroomStatus.tsx +158 -0
  52. package/bizar-dash/src/web/components/SettingsSearch.tsx +213 -0
  53. package/bizar-dash/src/web/components/Topbar.tsx +2 -1
  54. package/bizar-dash/src/web/components/UsageChart.tsx +250 -0
  55. package/bizar-dash/src/web/components/UsageTable.tsx +90 -0
  56. package/bizar-dash/src/web/components/chat/ChatComposer.tsx +21 -25
  57. package/bizar-dash/src/web/components/chat/ChatInfoPanel.tsx +199 -37
  58. package/bizar-dash/src/web/components/chat/ChatThread.tsx +29 -17
  59. package/bizar-dash/src/web/components/chat/FloatingComposer.tsx +7 -1
  60. package/bizar-dash/src/web/components/chat/InfoPanel.tsx +71 -6
  61. package/bizar-dash/src/web/components/chat/useChat.ts +751 -257
  62. package/bizar-dash/src/web/lib/api.ts +43 -0
  63. package/bizar-dash/src/web/lib/types.ts +16 -0
  64. package/bizar-dash/src/web/main.tsx +2 -0
  65. package/bizar-dash/src/web/mobile/views/MobileChat.tsx +110 -35
  66. package/bizar-dash/src/web/styles/chat.css +135 -1
  67. package/bizar-dash/src/web/styles/main.css +46 -0
  68. package/bizar-dash/src/web/styles/memory.css +955 -0
  69. package/bizar-dash/src/web/styles/minimax-usage.css +335 -0
  70. package/bizar-dash/src/web/styles/settings.css +418 -0
  71. package/bizar-dash/src/web/styles/skills.css +302 -0
  72. package/bizar-dash/src/web/styles/tasks.css +288 -0
  73. package/bizar-dash/src/web/views/Chat.tsx +276 -48
  74. package/bizar-dash/src/web/views/Config.tsx +3 -2065
  75. package/bizar-dash/src/web/views/Memory.tsx +140 -0
  76. package/bizar-dash/src/web/views/MiniMaxUsage.tsx +476 -461
  77. package/bizar-dash/src/web/views/Overview.tsx +3 -0
  78. package/bizar-dash/src/web/views/Settings.tsx +36 -0
  79. package/bizar-dash/src/web/views/Skills.tsx +208 -260
  80. package/bizar-dash/src/web/views/Tasks.tsx +348 -1119
  81. package/bizar-dash/src/web/views/memory/ConfigPanel.tsx +289 -0
  82. package/bizar-dash/src/web/views/memory/GitSyncPanel.tsx +220 -0
  83. package/bizar-dash/src/web/views/memory/LightragPanel.tsx +307 -0
  84. package/bizar-dash/src/web/views/memory/MemoryOverview.tsx +354 -0
  85. package/bizar-dash/src/web/views/memory/MemoryStatusCard.tsx +160 -0
  86. package/bizar-dash/src/web/views/memory/ObsidianPanel.tsx +642 -0
  87. package/bizar-dash/src/web/views/memory/SemanticSearchPanel.tsx +194 -0
  88. package/bizar-dash/tests/chat-session-create.test.mjs +391 -0
  89. package/bizar-dash/tests/chat-session-stream.test.mjs +308 -0
  90. package/bizar-dash/tests/env-vars-store.test.mjs +216 -0
  91. package/bizar-dash/tests/headroom-install.test.mjs +173 -0
  92. package/bizar-dash/tests/headroom-settings.test.mjs +126 -0
  93. package/bizar-dash/tests/headroom-status.test.mjs +117 -0
  94. package/bizar-dash/tests/lightrag-defaults.node.test.mjs +118 -0
  95. package/bizar-dash/tests/memory-lightrag-extended.test.mjs +162 -0
  96. package/bizar-dash/tests/memory-obsidian.test.mjs +269 -0
  97. package/bizar-dash/tests/memory-tab.test.mjs +322 -0
  98. package/bizar-dash/tests/minimax-chat-usage.test.mjs +178 -0
  99. package/bizar-dash/tests/minimax-usage-store.node.test.mjs +293 -0
  100. package/bizar-dash/tests/mod-upgrade.node.test.mjs +1 -1
  101. package/bizar-dash/tests/opencode-sessions-detail.test.mjs +12 -9
  102. package/bizar-dash/tests/providers-store-backup-keys.node.test.mjs +479 -3
  103. package/bizar-dash/tests/providers-store-search.node.test.mjs +166 -0
  104. package/bizar-dash/tests/skills-list.test.mjs +232 -0
  105. package/bizar-dash/tests/skills-search.test.mjs +222 -0
  106. package/bizar-dash/tests/submit-feedback.test.mjs +6 -6
  107. package/bizar-dash/tests/tasks-create.test.mjs +187 -0
  108. package/bizar-dash/tests/update-check.test.mjs +127 -0
  109. package/bizar-dash/tests/update-run.test.mjs +266 -0
  110. package/cli/bin.mjs +247 -1
  111. package/cli/provision.mjs +118 -4
  112. package/config/agents/_shared/SKILLS.md +109 -0
  113. package/package.json +1 -1
  114. package/bizar-dash/dist/assets/main-BB5mJurD.js +0 -352
  115. package/bizar-dash/dist/assets/main-BB5mJurD.js.map +0 -1
  116. package/bizar-dash/dist/assets/main-BsnQLXdh.css +0 -1
  117. package/bizar-dash/dist/assets/mobile-Dl1q7Cyq.js +0 -354
  118. package/bizar-dash/dist/assets/mobile-Dl1q7Cyq.js.map +0 -1
@@ -1,16 +1,25 @@
1
- // src/views/Chat.tsx — Gemini-inspired dark chat orchestrator (v3.22).
1
+ // src/views/Chat.tsx — desktop chat view (v4.2.5 design).
2
2
  //
3
- // v3.22redesigned layout: 3-column grid (rail / thread / info).
4
- // The legacy `.chat-shell / .chat-body / .chat-sessions / .chat-main /
5
- // .chat-info` wrapper classes are still available for MobileChat (which
6
- // uses its own view); this file uses the new `.chat-page` grid.
3
+ // v4.2.5chat overhaul:
4
+ // * 3-column grid preserved (rail / thread / info).
5
+ // * Composer goes through `chat.onSend`, which transparently routes
6
+ // to the right backend endpoint based on `activeSource`:
7
+ // - opencode session active → POST /api/opencode-sessions/:id/send
8
+ // - otherwise → POST /api/chat
9
+ // * "New session" button calls `chat.onCreateSession` which tries
10
+ // POST /api/opencode-sessions/new first (so the new session is a
11
+ // fully-fledged opencode session, scoped to the active worktree),
12
+ // falling back to the local jsonl store when the opencode plugin
13
+ // is offline.
14
+ // * Top-of-thread badge shows the active source ("opencode" or
15
+ // "bizar chat") so the user always knows where messages are going.
16
+ // * Info panel gets session metadata + rename/delete actions.
7
17
  //
8
- // The `chat-thread` section is a vertical grid (head / scroll / composer).
9
- // The scrollable message list lives inside the legacy `ChatThread`
10
- // component (still wrapped in its own `.chat-thread` div with className
11
- // `legacy` to opt into the original styles — see chat.css).
18
+ // The page-level grid and CSS classes are unchanged so the rail/info
19
+ // widths in the .chat-page grid continue to apply.
12
20
 
13
21
  import { useEffect, useMemo, useRef, useState } from 'react';
22
+ import { Trash2, Pencil } from 'lucide-react';
14
23
  import { ChatTopBar } from '../components/chat/ChatTopBar';
15
24
  import { ChatRail } from '../components/chat/ChatRail';
16
25
  import { ChatThread } from '../components/chat/ChatThread';
@@ -33,18 +42,33 @@ interface Props {
33
42
  onClearTaskId?: () => void;
34
43
  }
35
44
 
36
- export function Chat({ snapshot, settings, setActiveTab, initialTaskId, onClearTaskId }: Props) {
45
+ export function Chat({
46
+ snapshot,
47
+ settings,
48
+ setActiveTab,
49
+ initialTaskId,
50
+ onClearTaskId,
51
+ }: Props) {
37
52
  const toast = useToast();
38
53
  const modal = useModal();
39
54
 
40
55
  const chat = useChat(snapshot, settings, initialTaskId ?? '');
56
+ // Wire toast + modal surfaces to useChat so async ops can surface
57
+ // feedback without each view passing handlers down.
58
+ useEffect(() => {
59
+ chat.setToast({
60
+ error: (msg: string) => toast.error(msg),
61
+ success: (msg: string) => toast.success(msg),
62
+ info: (msg: string) => toast.info(msg),
63
+ warning: (msg: string) => toast.warning(msg),
64
+ });
65
+ }, [chat, toast]);
41
66
 
42
67
  // ── Composer state (local) ───────────────────────────────────────────────
43
68
  const [text, setText] = useState('');
44
69
  const [agent, setAgent] = useState(settings.defaultAgent || 'odin');
45
70
  const [model, setModel] = useState(settings.defaultModel || '');
46
71
  const [attachments, setAttachments] = useState<string[]>([]);
47
- const [creating, setCreating] = useState(false);
48
72
 
49
73
  const fileInputRef = useRef<HTMLInputElement>(null);
50
74
 
@@ -57,7 +81,6 @@ export function Chat({ snapshot, settings, setActiveTab, initialTaskId, onClearT
57
81
 
58
82
  // ── File attach ──────────────────────────────────────────────────────────
59
83
  const onAttach = () => fileInputRef.current?.click();
60
-
61
84
  const onFiles = (e: React.ChangeEvent<HTMLInputElement>) => {
62
85
  const files = e.target.files;
63
86
  if (!files) return;
@@ -71,32 +94,19 @@ export function Chat({ snapshot, settings, setActiveTab, initialTaskId, onClearT
71
94
  };
72
95
 
73
96
  // ── Send ─────────────────────────────────────────────────────────────────
74
- const handleSend = () => {
97
+ const handleSend = async () => {
75
98
  const msg = text.trim();
76
99
  if (!msg) return;
77
100
  setText('');
78
101
  setQuery('');
79
- chat.onSend(msg, agent, model, attachments);
80
- chat.jumpToLatest();
102
+ const result = await chat.onSend(msg, agent, model, attachments);
103
+ if (result.ok) chat.jumpToLatest();
81
104
  };
82
105
 
83
106
  // ── Create session ───────────────────────────────────────────────────────
84
107
  const handleCreateSession = async () => {
85
- if (creating) return;
86
- if (!snapshot.activeProject) {
87
- toast.warning('Pick a project in Overview to scope chat sessions.', 4000);
88
- return;
89
- }
90
- setCreating(true);
91
- try {
92
- const created = await fetch('/chat/sessions', { method: 'POST' }).then((r) => r.json());
93
- chat.loadChat(created.id);
94
- toast.success(`Session ${created.id} created.`);
95
- } catch (err) {
96
- toast.error(`Create failed: ${(err as Error).message}`);
97
- } finally {
98
- setCreating(false);
99
- }
108
+ if (chat.busy.create) return;
109
+ await chat.onCreateSession();
100
110
  };
101
111
 
102
112
  // ── Delete message (via modal) ───────────────────────────────────────────
@@ -124,6 +134,121 @@ export function Chat({ snapshot, settings, setActiveTab, initialTaskId, onClearT
124
134
  });
125
135
  };
126
136
 
137
+ // ── Rename session (info panel action) ───────────────────────────────────
138
+ const handleRename = (id: string, currentTitle: string) => {
139
+ let next = '';
140
+ modal.open({
141
+ title: 'Rename session',
142
+ children: (
143
+ <input
144
+ autoFocus
145
+ defaultValue={currentTitle}
146
+ onChange={(e) => {
147
+ next = e.target.value;
148
+ }}
149
+ onKeyDown={(e) => {
150
+ if (e.key === 'Enter') {
151
+ e.preventDefault();
152
+ modal.close();
153
+ chat.renameSession(id, next).then((ok) => {
154
+ if (ok) {
155
+ toast.success('Renamed.');
156
+ }
157
+ });
158
+ }
159
+ }}
160
+ style={{
161
+ width: '100%',
162
+ padding: '8px 10px',
163
+ background: 'var(--bg)',
164
+ border: '1px solid var(--border)',
165
+ borderRadius: 'var(--radius-sm)',
166
+ color: 'var(--text)',
167
+ font: '13px/1.4 var(--font-sans)',
168
+ marginTop: 6,
169
+ }}
170
+ />
171
+ ),
172
+ footer: (
173
+ <div style={{ display: 'flex', gap: 8, justifyContent: 'flex-end' }}>
174
+ <Button variant="secondary" size="sm" onClick={() => modal.close()}>
175
+ Cancel
176
+ </Button>
177
+ <Button
178
+ variant="primary"
179
+ size="sm"
180
+ onClick={() => {
181
+ modal.close();
182
+ chat.renameSession(id, next);
183
+ }}
184
+ >
185
+ Save
186
+ </Button>
187
+ </div>
188
+ ),
189
+ });
190
+ };
191
+
192
+ // ── Delete session (info panel action) ───────────────────────────────────
193
+ const handleDeleteSession = (id: string, title: string) => {
194
+ modal.open({
195
+ title: 'Delete session?',
196
+ children: (
197
+ <p style={{ margin: 0 }}>
198
+ Delete <strong>{title}</strong>? Messages on the opencode serve
199
+ will be removed. This cannot be undone.
200
+ </p>
201
+ ),
202
+ footer: (
203
+ <div style={{ display: 'flex', gap: 8, justifyContent: 'flex-end' }}>
204
+ <Button variant="secondary" size="sm" onClick={() => modal.close()}>
205
+ Cancel
206
+ </Button>
207
+ <Button
208
+ variant="danger"
209
+ size="sm"
210
+ onClick={async () => {
211
+ modal.close();
212
+ await chat.deleteSession(id);
213
+ }}
214
+ >
215
+ Delete
216
+ </Button>
217
+ </div>
218
+ ),
219
+ });
220
+ };
221
+
222
+ // ── Export transcript ────────────────────────────────────────────────────
223
+ const handleExport = () => {
224
+ const sessionId =
225
+ chat.activeSource === 'opencode'
226
+ ? chat.activeOpencodeSessionId
227
+ : chat.sessionId;
228
+ if (!sessionId) return;
229
+ const msgs =
230
+ chat.activeSource === 'opencode'
231
+ ? chat.opencodeMessages
232
+ : chat.bizarMessages;
233
+ const text = msgs
234
+ .map((m) => {
235
+ const ts = m.ts ?? '';
236
+ const who = (m.role || 'unknown').toUpperCase();
237
+ const c = m.content || m.message || '';
238
+ return `[${ts}] ${who}: ${c}`;
239
+ })
240
+ .join('\n\n');
241
+ const blob = new Blob([text], { type: 'text/plain;charset=utf-8' });
242
+ const url = URL.createObjectURL(blob);
243
+ const a = document.createElement('a');
244
+ a.href = url;
245
+ a.download = `${sessionId}.txt`;
246
+ document.body.appendChild(a);
247
+ a.click();
248
+ document.body.removeChild(a);
249
+ URL.revokeObjectURL(url);
250
+ };
251
+
127
252
  // ── Derived: sessions with display state overlaid ────────────────────────
128
253
  const displaySessions = useMemo(
129
254
  () => chat.sessions.map((s) => chat.getSessionDisplay(s)),
@@ -136,9 +261,10 @@ export function Chat({ snapshot, settings, setActiveTab, initialTaskId, onClearT
136
261
 
137
262
  // ── Current session for the thread head subtitle ────────────────────────
138
263
  const activeSessionDisplay = useMemo(() => {
139
- const id = chat.activeSource === 'opencode'
140
- ? chat.activeOpencodeSessionId ?? ''
141
- : chat.sessionId;
264
+ const id =
265
+ chat.activeSource === 'opencode'
266
+ ? chat.activeOpencodeSessionId ?? ''
267
+ : chat.sessionId;
142
268
  if (!id) return null;
143
269
  return (
144
270
  displaySessions.find((s) => s.id === id) ??
@@ -161,6 +287,10 @@ export function Chat({ snapshot, settings, setActiveTab, initialTaskId, onClearT
161
287
  return `${base} · idle`;
162
288
  })();
163
289
 
290
+ // The badge in the thread head: "opencode" or "bizar chat".
291
+ const sourceLabel =
292
+ chat.activeSource === 'opencode' ? 'opencode' : 'bizar chat';
293
+
164
294
  return (
165
295
  <div className="chat-shell">
166
296
  <ChatTopBar
@@ -180,48 +310,122 @@ export function Chat({ snapshot, settings, setActiveTab, initialTaskId, onClearT
180
310
  activeSessionId={chat.sessionId}
181
311
  activeOpencodeSessionId={chat.activeOpencodeSessionId}
182
312
  activeProject={snapshot.activeProject}
183
- creating={creating}
313
+ creating={chat.busy.create}
184
314
  onCreateSession={handleCreateSession}
185
315
  onSelectSession={chat.selectBizarSession}
186
316
  onSelectOpencodeSession={(s) => chat.loadOpencodeSession(s.id)}
187
- onRenameSession={(id, title) => chat.renameSession(id, title)}
188
- onDeleteSession={(id) => chat.deleteSession(id)}
317
+ onRenameSession={handleRename}
318
+ onDeleteSession={(id) => {
319
+ const s = chat.sessions.find((s) => s.id === id) ?? chat.opencodeSessions.find((s) => s.id === id);
320
+ handleDeleteSession(id, s?.title ?? '');
321
+ }}
189
322
  />
190
323
 
191
324
  {/* ── thread ───────────────────────────────────────────────────── */}
192
325
  <section className="chat-thread-section">
193
326
  <div className="chat-thread-head">
194
327
  <div>
195
- <div className="chat-thread-title">
196
- {activeSessionDisplay?.title ?? chat.sessionId ?? 'New chat'}
328
+ <div className="chat-thread-title-row">
329
+ <div className="chat-thread-title">
330
+ {activeSessionDisplay?.title ?? chat.sessionId ?? 'New chat'}
331
+ </div>
332
+ <span
333
+ className={`chat-source-badge chat-source-${
334
+ chat.activeSource ?? 'none'
335
+ }`}
336
+ title={
337
+ chat.activeSource === 'opencode'
338
+ ? 'Messages go to the opencode serve child'
339
+ : 'Messages go to the local chat store'
340
+ }
341
+ >
342
+ {sourceLabel}
343
+ </span>
197
344
  </div>
198
345
  <div
199
- className={`chat-thread-sub chat-muted state-${activeSessionDisplay?.state ?? 'idle'}`}
346
+ className={`chat-thread-sub chat-muted state-${
347
+ activeSessionDisplay?.state ?? 'idle'
348
+ }`}
200
349
  >
201
350
  <span className="chat-thread-dot" />
202
351
  {threadSubtitle}
203
352
  </div>
353
+ {chat.opencodeError && (
354
+ <div className="chat-thread-error" role="alert">
355
+ {chat.opencodeError}
356
+ </div>
357
+ )}
204
358
  </div>
205
359
  <div className="chat-thread-actions">
206
- <button className="btn btn-ghost" title="Rename" type="button">
207
- <span className="mono">rename</span>
360
+ {activeSessionDisplay && (
361
+ <button
362
+ className="btn btn-ghost"
363
+ title="Rename session"
364
+ type="button"
365
+ disabled={chat.busy.rename}
366
+ onClick={() =>
367
+ handleRename(
368
+ activeSessionDisplay.id,
369
+ activeSessionDisplay.title ?? '',
370
+ )
371
+ }
372
+ >
373
+ <Pencil size={12} aria-hidden />{' '}
374
+ <span className="mono">rename</span>
375
+ </button>
376
+ )}
377
+ {activeSessionDisplay && (
378
+ <button
379
+ className="btn btn-ghost btn-danger"
380
+ title="Delete session"
381
+ type="button"
382
+ disabled={chat.busy.delete}
383
+ onClick={() =>
384
+ handleDeleteSession(
385
+ activeSessionDisplay.id,
386
+ activeSessionDisplay.title ?? activeSessionDisplay.id,
387
+ )
388
+ }
389
+ >
390
+ <Trash2 size={12} aria-hidden />{' '}
391
+ <span className="mono">delete</span>
392
+ </button>
393
+ )}
394
+ <button
395
+ className="btn btn-ghost"
396
+ title="Export transcript"
397
+ type="button"
398
+ onClick={handleExport}
399
+ >
400
+ <span className="mono">export</span>
208
401
  </button>
209
402
  </div>
210
403
  </div>
211
404
 
212
- <div className="chat-thread-scroll" ref={chat.listRef} onScroll={chat.handleScroll}>
405
+ <div
406
+ className="chat-thread-scroll"
407
+ ref={chat.listRef}
408
+ onScroll={chat.handleScroll}
409
+ >
213
410
  <ChatThread
214
- messages={chat.activeSource === 'opencode' ? chat.opencodeMessages : chat.bizarMessages}
411
+ messages={
412
+ chat.activeSource === 'opencode'
413
+ ? chat.opencodeMessages
414
+ : chat.bizarMessages
415
+ }
215
416
  loading={chat.loading}
216
417
  activeProject={snapshot.activeProject}
217
418
  sessionId={
218
419
  chat.activeSource === 'opencode'
219
- ? (chat.activeOpencodeSessionId ?? chat.sessionId)
420
+ ? chat.activeOpencodeSessionId ?? chat.sessionId
220
421
  : chat.sessionId
221
422
  }
222
423
  pinned={chat.pinned}
424
+ activeSource={chat.activeSource}
223
425
  onPickSuggestion={(t) => setText(t)}
224
- onCopy={(m) => chat.copyMessage(m as Parameters<typeof chat.copyMessage>[0])}
426
+ onCopy={(m) =>
427
+ chat.copyMessage(m as Parameters<typeof chat.copyMessage>[0])
428
+ }
225
429
  onDelete={handleDelete}
226
430
  onTogglePin={chat.togglePin}
227
431
  onRegenerate={chat.onRegenerate}
@@ -244,6 +448,7 @@ export function Chat({ snapshot, settings, setActiveTab, initialTaskId, onClearT
244
448
  text={text}
245
449
  setText={setText}
246
450
  sending={chat.sending}
451
+ activeSource={chat.activeSource}
247
452
  onSend={handleSend}
248
453
  attachments={attachments}
249
454
  setAttachments={setAttachments}
@@ -265,18 +470,41 @@ export function Chat({ snapshot, settings, setActiveTab, initialTaskId, onClearT
265
470
  <ChatInfoPanel
266
471
  sessionId={
267
472
  chat.activeSource === 'opencode'
268
- ? (chat.activeOpencodeSessionId ?? chat.sessionId)
473
+ ? chat.activeOpencodeSessionId ?? chat.sessionId
269
474
  : chat.sessionId
270
475
  }
271
- messages={chat.activeSource === 'opencode' ? chat.opencodeMessages : chat.bizarMessages}
476
+ messages={
477
+ chat.activeSource === 'opencode'
478
+ ? chat.opencodeMessages
479
+ : chat.bizarMessages
480
+ }
272
481
  pinned={chat.pinned}
273
482
  agent={agent}
274
483
  model={model}
275
484
  agents={snapshot.agents || []}
276
485
  mcps={snapshot.mcps || []}
277
486
  allCommands={allCommands}
487
+ activeSource={chat.activeSource}
488
+ onRename={() => {
489
+ if (activeSessionDisplay) {
490
+ handleRename(
491
+ activeSessionDisplay.id,
492
+ activeSessionDisplay.title ?? '',
493
+ );
494
+ }
495
+ }}
496
+ onDelete={() => {
497
+ if (activeSessionDisplay) {
498
+ handleDeleteSession(
499
+ activeSessionDisplay.id,
500
+ activeSessionDisplay.title ?? activeSessionDisplay.id,
501
+ );
502
+ }
503
+ }}
504
+ onExport={handleExport}
505
+ busy={chat.busy}
278
506
  />
279
507
  </div>
280
508
  </div>
281
509
  );
282
- }
510
+ }