@bitseek/hermes-webui 0.1.0-beta.0 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/vendor/agent-frontend-shell/.bitseek-source.json +2 -2
- package/vendor/agent-frontend-shell/CHANGELOG.md +178 -1
- package/vendor/agent-frontend-shell/CONTRIBUTORS.md +5 -5
- package/vendor/agent-frontend-shell/api/agent_health.py +134 -0
- package/vendor/agent-frontend-shell/api/config.py +145 -104
- package/vendor/agent-frontend-shell/api/gateway_chat.py +56 -12
- package/vendor/agent-frontend-shell/api/helpers.py +4 -2
- package/vendor/agent-frontend-shell/api/models.py +202 -20
- package/vendor/agent-frontend-shell/api/paths.py +77 -0
- package/vendor/agent-frontend-shell/api/plugins.py +185 -0
- package/vendor/agent-frontend-shell/api/profiles.py +95 -16
- package/vendor/agent-frontend-shell/api/routes.py +831 -30
- package/vendor/agent-frontend-shell/api/run_journal.py +1 -0
- package/vendor/agent-frontend-shell/api/state_sync.py +5 -4
- package/vendor/agent-frontend-shell/api/streaming.py +211 -56
- package/vendor/agent-frontend-shell/api/todo_state.py +122 -0
- package/vendor/agent-frontend-shell/api/updates.py +30 -3
- package/vendor/agent-frontend-shell/api/upload.py +251 -18
- package/vendor/agent-frontend-shell/api/workspace.py +323 -65
- package/vendor/agent-frontend-shell/bitseek_docs/BitSeek_Claw_Operation_Manual_EN.docx +0 -0
- package/vendor/agent-frontend-shell/bitseek_docs/BitSeek_Claw_Operation_Manual_ZH.docx +0 -0
- package/vendor/agent-frontend-shell/bitseek_docs/en/00-Installation.md +174 -0
- package/vendor/agent-frontend-shell/bitseek_docs/en/01-Overview.md +128 -0
- package/vendor/agent-frontend-shell/bitseek_docs/en/02-Page-Operations.md +461 -0
- package/vendor/agent-frontend-shell/bitseek_docs/en/README.md +61 -0
- package/vendor/agent-frontend-shell/bitseek_docs/en/images/ai-colleagues.png +0 -0
- package/vendor/agent-frontend-shell/bitseek_docs/en/images/chat-area.png +0 -0
- package/vendor/agent-frontend-shell/bitseek_docs/en/images/kanban.png +0 -0
- package/vendor/agent-frontend-shell/bitseek_docs/en/images/main-page.png +0 -0
- package/vendor/agent-frontend-shell/bitseek_docs/en/images/memory-notes.png +0 -0
- package/vendor/agent-frontend-shell/bitseek_docs/en/images/memory-overview.png +0 -0
- package/vendor/agent-frontend-shell/bitseek_docs/en/images/memory-profile.png +0 -0
- package/vendor/agent-frontend-shell/bitseek_docs/en/images/memory-soul.png +0 -0
- package/vendor/agent-frontend-shell/bitseek_docs/en/images/memory.png +0 -0
- package/vendor/agent-frontend-shell/bitseek_docs/en/images/navigation-bar.png +0 -0
- package/vendor/agent-frontend-shell/bitseek_docs/en/images/settings-appearance.png +0 -0
- package/vendor/agent-frontend-shell/bitseek_docs/en/images/settings-conversation.png +0 -0
- package/vendor/agent-frontend-shell/bitseek_docs/en/images/settings-overview.png +0 -0
- package/vendor/agent-frontend-shell/bitseek_docs/en/images/settings-plugins.png +0 -0
- package/vendor/agent-frontend-shell/bitseek_docs/en/images/settings-preferences.png +0 -0
- package/vendor/agent-frontend-shell/bitseek_docs/en/images/settings-providers.png +0 -0
- package/vendor/agent-frontend-shell/bitseek_docs/en/images/settings-system.png +0 -0
- package/vendor/agent-frontend-shell/bitseek_docs/en/images/settings.png +0 -0
- package/vendor/agent-frontend-shell/bitseek_docs/en/images/sidebar.png +0 -0
- package/vendor/agent-frontend-shell/bitseek_docs/en/images/skills.png +0 -0
- package/vendor/agent-frontend-shell/bitseek_docs/en/images/tasks.png +0 -0
- package/vendor/agent-frontend-shell/bitseek_docs/en/images/workspace-panel.png +0 -0
- package/vendor/agent-frontend-shell/bitseek_docs/md_to_docx.py +351 -0
- package/vendor/agent-frontend-shell/bitseek_docs/zh/00-/345/256/211/350/243/205/345/220/257/345/212/250.md +174 -0
- package/vendor/agent-frontend-shell/bitseek_docs/zh/01-/346/225/264/344/275/223/346/246/202/350/247/210.md +128 -0
- package/vendor/agent-frontend-shell/bitseek_docs/zh/02-/351/241/265/351/235/242/346/223/215/344/275/234.md +463 -0
- package/vendor/agent-frontend-shell/bitseek_docs/zh/README.md +61 -0
- package/vendor/agent-frontend-shell/bitseek_docs/zh/images/ai-colleagues.png +0 -0
- package/vendor/agent-frontend-shell/bitseek_docs/zh/images/chat-area.png +0 -0
- package/vendor/agent-frontend-shell/bitseek_docs/zh/images/kanban.png +0 -0
- package/vendor/agent-frontend-shell/bitseek_docs/zh/images/main-page.png +0 -0
- package/vendor/agent-frontend-shell/bitseek_docs/zh/images/memory-notes.png +0 -0
- package/vendor/agent-frontend-shell/bitseek_docs/zh/images/memory-overview.png +0 -0
- package/vendor/agent-frontend-shell/bitseek_docs/zh/images/memory-profile.png +0 -0
- package/vendor/agent-frontend-shell/bitseek_docs/zh/images/memory-soul.png +0 -0
- package/vendor/agent-frontend-shell/bitseek_docs/zh/images/memory.png +0 -0
- package/vendor/agent-frontend-shell/bitseek_docs/zh/images/navigation-bar.png +0 -0
- package/vendor/agent-frontend-shell/bitseek_docs/zh/images/settings-appearance.png +0 -0
- package/vendor/agent-frontend-shell/bitseek_docs/zh/images/settings-conversation.png +0 -0
- package/vendor/agent-frontend-shell/bitseek_docs/zh/images/settings-overview.png +0 -0
- package/vendor/agent-frontend-shell/bitseek_docs/zh/images/settings-plugins.png +0 -0
- package/vendor/agent-frontend-shell/bitseek_docs/zh/images/settings-preferences.png +0 -0
- package/vendor/agent-frontend-shell/bitseek_docs/zh/images/settings-providers.png +0 -0
- package/vendor/agent-frontend-shell/bitseek_docs/zh/images/settings-system.png +0 -0
- package/vendor/agent-frontend-shell/bitseek_docs/zh/images/settings.png +0 -0
- package/vendor/agent-frontend-shell/bitseek_docs/zh/images/sidebar.png +0 -0
- package/vendor/agent-frontend-shell/bitseek_docs/zh/images/skills.png +0 -0
- package/vendor/agent-frontend-shell/bitseek_docs/zh/images/tasks.png +0 -0
- package/vendor/agent-frontend-shell/bitseek_docs/zh/images/workspace-panel.png +0 -0
- package/vendor/agent-frontend-shell/build-release.sh +62 -0
- package/vendor/agent-frontend-shell/ctl.sh +1 -0
- package/vendor/agent-frontend-shell/docker-compose.local.yml +33 -0
- package/vendor/agent-frontend-shell/docker-compose.yml +8 -0
- package/vendor/agent-frontend-shell/docker_init.bash +1 -0
- package/vendor/agent-frontend-shell/docs/rfcs/hermes-run-adapter-contract.md +74 -15
- package/vendor/agent-frontend-shell/extensions/common/index.css +6 -0
- package/vendor/agent-frontend-shell/extensions/manifest.json +6 -0
- package/vendor/agent-frontend-shell/extensions/pages/ai-teammates/page.js +60 -14
- package/vendor/agent-frontend-shell/readme-simple.md +103 -0
- package/vendor/agent-frontend-shell/requirements.txt +5 -0
- package/vendor/agent-frontend-shell/server.py +7 -0
- package/vendor/agent-frontend-shell/static/boot.js +53 -1
- package/vendor/agent-frontend-shell/static/commands.js +20 -10
- package/vendor/agent-frontend-shell/static/i18n.js +1142 -1016
- package/vendor/agent-frontend-shell/static/index.html +13 -3
- package/vendor/agent-frontend-shell/static/messages.js +48 -3
- package/vendor/agent-frontend-shell/static/panels.js +199 -30
- package/vendor/agent-frontend-shell/static/sessions.js +249 -39
- package/vendor/agent-frontend-shell/static/style.css +46 -2
- package/vendor/agent-frontend-shell/static/ui.js +323 -79
- package/vendor/agent-frontend-shell/static/workspace.js +185 -7
- package/vendor/agent-frontend-shell/README-CUSTOM.md +0 -76
- package/vendor/agent-frontend-shell/docker-compose.custom.yml +0 -26
|
@@ -10,6 +10,7 @@ const ICONS={
|
|
|
10
10
|
trash:'<svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.3"><path d="M3.5 4.5h9M6.5 4.5V3h3v1.5M4.5 4.5v8.5h7v-8.5"/><line x1="7" y1="7" x2="7" y2="11"/><line x1="9" y1="7" x2="9" y2="11"/></svg>',
|
|
11
11
|
more:'<svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor" stroke="none"><circle cx="8" cy="3" r="1.25"/><circle cx="8" cy="8" r="1.25"/><circle cx="8" cy="13" r="1.25"/></svg>',
|
|
12
12
|
edit:'<svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"><path d="M11.5 2.5l2 2L5 13H3v-2z"/><path d="M10 4l2 2"/></svg>',
|
|
13
|
+
spark:'<svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"><path d="M8 1.8l1.1 3.1 3.1 1.1-3.1 1.1L8 10.2 6.9 7.1 3.8 6l3.1-1.1z"/><path d="M12.5 9.5l.5 1.5 1.5.5-1.5.5-.5 1.5-.5-1.5-1.5-.5 1.5-.5z"/></svg>',
|
|
13
14
|
link:'<svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"><path d="M6.7 9.3a3 3 0 0 1 0-4.2l1.7-1.7a3 3 0 0 1 4.2 4.2l-1 1"/><path d="M9.3 6.7a3 3 0 0 1 0 4.2l-1.7 1.7a3 3 0 0 1-4.2-4.2l1-1"/></svg>',
|
|
14
15
|
};
|
|
15
16
|
|
|
@@ -17,6 +18,13 @@ const ICONS={
|
|
|
17
18
|
// responses from in-flight requests when the user switches sessions again
|
|
18
19
|
// before the first request completes (#1060).
|
|
19
20
|
let _loadingSessionId = null;
|
|
21
|
+
// #3306: Snapshot of S.messages captured by loadSession() right before it
|
|
22
|
+
// clears them on a force-reload of the active session. Consumed by
|
|
23
|
+
// _ensureMessagesLoaded() when calling _carryForwardEphemeralTurnFields so
|
|
24
|
+
// ephemeral fields (_turnUsage, _turnDuration, _turnTps, _gatewayRouting,
|
|
25
|
+
// _statusCard) survive the wholesale replace. null when there is nothing
|
|
26
|
+
// to carry forward (initial load, switch-to-different-session, etc.).
|
|
27
|
+
let _pendingCarryForwardSnapshot = null;
|
|
20
28
|
|
|
21
29
|
// ── Composer draft persistence ────────────────────────────────────────────────
|
|
22
30
|
|
|
@@ -589,6 +597,7 @@ async function loadSession(sid){
|
|
|
589
597
|
}
|
|
590
598
|
const forceReload = !!opts.force;
|
|
591
599
|
const currentSid = S.session ? S.session.session_id : null;
|
|
600
|
+
const sameSessionForceReload = forceReload && currentSid===sid;
|
|
592
601
|
// Clicking the already-open session in the sidebar is a no-op. Reloading it
|
|
593
602
|
// tears down active pane state and can reset the long-session scroll window
|
|
594
603
|
// to the top even though the user did not navigate anywhere. Explicit
|
|
@@ -611,6 +620,23 @@ async function loadSession(sid){
|
|
|
611
620
|
_saveComposerDraftNow(currentSid, ($('msg') || {}).value || '', S.pendingFiles ? [...S.pendingFiles] : []);
|
|
612
621
|
}
|
|
613
622
|
if (currentSid !== sid || forceReload) {
|
|
623
|
+
// #3306: When force-reloading the currently-active session (e.g. external
|
|
624
|
+
// poll triggering a refresh), snapshot the existing messages BEFORE we
|
|
625
|
+
// clear them. _ensureMessagesLoaded() runs the ephemeral-field
|
|
626
|
+
// carry-forward (_turnUsage, _turnDuration, _turnTps, _gatewayRouting,
|
|
627
|
+
// _statusCard) against S.messages, but by the time the API fetch returns
|
|
628
|
+
// S.messages has already been reset to [] here and the carry-forward is a
|
|
629
|
+
// no-op. The visible symptom is the token-usage badge vanishing ~10s
|
|
630
|
+
// after each assistant turn completes. Stash the snapshot so the
|
|
631
|
+
// carry-forward call can consume it.
|
|
632
|
+
_pendingCarryForwardSnapshot = (currentSid === sid && forceReload)
|
|
633
|
+
? (S.messages || []).slice()
|
|
634
|
+
: null;
|
|
635
|
+
// #3239: also capture a reload-width hint BEFORE clearing so the
|
|
636
|
+
// authoritative reload preserves the already-loaded transcript width
|
|
637
|
+
// instead of collapsing a long session back to the default tail window.
|
|
638
|
+
if (sameSessionForceReload) _captureSameSessionForceReloadHint(sid);
|
|
639
|
+
else _clearSameSessionForceReloadHint();
|
|
614
640
|
S.messages = [];
|
|
615
641
|
S.toolCalls = [];
|
|
616
642
|
_messagesTruncated = false;
|
|
@@ -652,12 +678,14 @@ async function loadSession(sid){
|
|
|
652
678
|
if(typeof showToast==='function') showToast('Failed to load session',3000,'error');
|
|
653
679
|
}
|
|
654
680
|
}
|
|
681
|
+
_clearSameSessionForceReloadHint(sid);
|
|
655
682
|
if (_loadingSessionId === sid) _loadingSessionId = null;
|
|
656
683
|
return;
|
|
657
684
|
}
|
|
658
685
|
// Guard: api() may have redirected (401) and returned undefined; in that case
|
|
659
686
|
// the browser is already navigating away, so abort the rest of this flow.
|
|
660
687
|
if (!data) {
|
|
688
|
+
_clearSameSessionForceReloadHint(sid);
|
|
661
689
|
if (_loadingSessionId === sid) _loadingSessionId = null;
|
|
662
690
|
return;
|
|
663
691
|
}
|
|
@@ -739,7 +767,7 @@ async function loadSession(sid){
|
|
|
739
767
|
// replaying persisted live tools so the compact Activity count survives
|
|
740
768
|
// switching away from and back to an active chat (#1715).
|
|
741
769
|
S.activeStreamId=activeStreamId;
|
|
742
|
-
syncTopbar();renderMessages();
|
|
770
|
+
syncTopbar();renderMessages(sameSessionForceReload?{preserveScroll:true}:undefined);
|
|
743
771
|
const restoredLiveTurn=typeof restoreLiveTurnHtmlForSession==='function'&&restoreLiveTurnHtmlForSession(sid);
|
|
744
772
|
if(!restoredLiveTurn){
|
|
745
773
|
appendThinking();
|
|
@@ -823,7 +851,7 @@ async function loadSession(sid){
|
|
|
823
851
|
updateSendBtn();
|
|
824
852
|
setStatus('');
|
|
825
853
|
setComposerStatus('');
|
|
826
|
-
syncTopbar();renderMessages();appendThinking();loadDir('.');
|
|
854
|
+
syncTopbar();renderMessages(sameSessionForceReload?{preserveScroll:true}:undefined);appendThinking();loadDir('.');
|
|
827
855
|
updateQueueBadge(sid);
|
|
828
856
|
startApprovalPolling(sid);
|
|
829
857
|
if(typeof startClarifyPolling==='function') startClarifyPolling(sid);
|
|
@@ -837,7 +865,7 @@ async function loadSession(sid){
|
|
|
837
865
|
setStatus('');
|
|
838
866
|
setComposerStatus('');
|
|
839
867
|
updateQueueBadge(sid);
|
|
840
|
-
syncTopbar();renderMessages();
|
|
868
|
+
syncTopbar();renderMessages(sameSessionForceReload?{preserveScroll:true}:undefined);
|
|
841
869
|
if(typeof resumeManualCompressionForSession==='function') resumeManualCompressionForSession(sid);
|
|
842
870
|
const _dirP=loadDir('.');
|
|
843
871
|
// Workspace refresh is guarded by session id inside loadDir(); do not
|
|
@@ -1317,6 +1345,57 @@ let _messagesTruncated = false;
|
|
|
1317
1345
|
// msg_limit (default 30): only fetch the last N messages for fast switching.
|
|
1318
1346
|
// Older messages are loaded on-demand via _loadOlderMessages().
|
|
1319
1347
|
const _INITIAL_MSG_LIMIT = 30;
|
|
1348
|
+
let _sameSessionForceReloadHint = null;
|
|
1349
|
+
|
|
1350
|
+
function _currentLoadedRenderableMessageCount(){
|
|
1351
|
+
if(typeof _messageRenderableMessageCount==='function'){
|
|
1352
|
+
try{return Math.max(0,Number(_messageRenderableMessageCount())||0);}
|
|
1353
|
+
catch(_){}
|
|
1354
|
+
}
|
|
1355
|
+
let count=0;
|
|
1356
|
+
for(const m of (S.messages||[])){
|
|
1357
|
+
if(m&&m.role&&m.role!=='tool') count++;
|
|
1358
|
+
}
|
|
1359
|
+
return count;
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
function _captureSameSessionForceReloadHint(sid){
|
|
1363
|
+
const loadedRenderableCount=_currentLoadedRenderableMessageCount();
|
|
1364
|
+
const loadedMessageCount=Array.isArray(S.messages)?S.messages.length:0;
|
|
1365
|
+
const knownMessageCount=Number(S.session&&S.session.session_id===sid&&S.session.message_count)||loadedMessageCount;
|
|
1366
|
+
if(!sid || (loadedRenderableCount<=0 && loadedMessageCount<=0)){
|
|
1367
|
+
_sameSessionForceReloadHint=null;
|
|
1368
|
+
return;
|
|
1369
|
+
}
|
|
1370
|
+
_sameSessionForceReloadHint={
|
|
1371
|
+
session_id:sid,
|
|
1372
|
+
loaded_renderable_count:loadedRenderableCount,
|
|
1373
|
+
loaded_message_count:loadedMessageCount,
|
|
1374
|
+
message_count:knownMessageCount,
|
|
1375
|
+
truncated:!!_messagesTruncated,
|
|
1376
|
+
};
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
function _clearSameSessionForceReloadHint(sid){
|
|
1380
|
+
if(!_sameSessionForceReloadHint) return;
|
|
1381
|
+
if(!sid || _sameSessionForceReloadHint.session_id===sid) _sameSessionForceReloadHint=null;
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
function _messageReloadLimitForSession(sid){
|
|
1385
|
+
const hint=_sameSessionForceReloadHint;
|
|
1386
|
+
if(hint&&hint.session_id===sid){
|
|
1387
|
+
const loadedRenderableCount=Math.max(0,Number(hint.loaded_renderable_count)||0);
|
|
1388
|
+
const loadedMessageCount=Math.max(0,Number(hint.loaded_message_count)||0);
|
|
1389
|
+
if(loadedRenderableCount>0 || loadedMessageCount>0){
|
|
1390
|
+
if(!hint.truncated) return null;
|
|
1391
|
+
const previousMessageCount=Math.max(0,Number(hint.message_count)||0);
|
|
1392
|
+
const currentMessageCount=Math.max(0,Number(S.session&&S.session.session_id===sid&&S.session.message_count)||0);
|
|
1393
|
+
const appendedMessageCount=Math.max(0,currentMessageCount-previousMessageCount);
|
|
1394
|
+
return Math.max(_INITIAL_MSG_LIMIT,loadedRenderableCount,loadedMessageCount+appendedMessageCount);
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
return _INITIAL_MSG_LIMIT;
|
|
1398
|
+
}
|
|
1320
1399
|
|
|
1321
1400
|
function _syncToolCallsForLoadedMessages(messages, sessionToolCalls){
|
|
1322
1401
|
const msgs=Array.isArray(messages)?messages:[];
|
|
@@ -1336,10 +1415,18 @@ function _syncToolCallsForLoadedMessages(messages, sessionToolCalls){
|
|
|
1336
1415
|
async function _ensureMessagesLoaded(sid) {
|
|
1337
1416
|
// Already have messages? (e.g. from INFLIGHT restore path, already set)
|
|
1338
1417
|
if (S.messages && S.messages.length > 0 && S.messages[0] && S.messages[0].role) {
|
|
1418
|
+
_clearSameSessionForceReloadHint(sid);
|
|
1339
1419
|
return;
|
|
1340
1420
|
}
|
|
1341
1421
|
// Fetch session messages with a tail window for fast initial load.
|
|
1342
|
-
const
|
|
1422
|
+
const reloadLimit = _messageReloadLimitForSession(sid); // defaults to _INITIAL_MSG_LIMIT
|
|
1423
|
+
const reloadLimitParam = reloadLimit ? `&msg_limit=${reloadLimit}` : '';
|
|
1424
|
+
let data;
|
|
1425
|
+
try {
|
|
1426
|
+
data = await api(`/api/session?session_id=${encodeURIComponent(sid)}&messages=1&resolve_model=0${reloadLimitParam}`);
|
|
1427
|
+
} finally {
|
|
1428
|
+
_clearSameSessionForceReloadHint(sid);
|
|
1429
|
+
}
|
|
1343
1430
|
// Guard: api() may have redirected (401) and returned undefined.
|
|
1344
1431
|
if (!data || !data.session) return;
|
|
1345
1432
|
_messagesTruncated = !!data.session._messages_truncated;
|
|
@@ -1355,11 +1442,21 @@ async function _ensureMessagesLoaded(sid) {
|
|
|
1355
1442
|
// #3018: preserve client-side ephemeral turn fields (_turnUsage, _turnDuration,
|
|
1356
1443
|
// _turnTps, _gatewayRouting, _statusCard) across the loadSession replace.
|
|
1357
1444
|
if(typeof window._carryForwardEphemeralTurnFields==='function'){
|
|
1358
|
-
|
|
1359
|
-
|
|
1445
|
+
// #3306: Prefer the pre-clear snapshot stashed by loadSession() on a
|
|
1446
|
+
// force-reload of the active session; S.messages was reset to [] there
|
|
1447
|
+
// and would otherwise yield an empty carry-forward.
|
|
1448
|
+
const _prev = (Array.isArray(_pendingCarryForwardSnapshot) && _pendingCarryForwardSnapshot.length)
|
|
1449
|
+
? _pendingCarryForwardSnapshot
|
|
1450
|
+
: (S.messages || []);
|
|
1451
|
+
msgs=window._carryForwardEphemeralTurnFields(_prev, msgs);
|
|
1452
|
+
_pendingCarryForwardSnapshot = null;
|
|
1453
|
+
}
|
|
1454
|
+
if(typeof clearVisibleMessageRowCache==='function') clearVisibleMessageRowCache();
|
|
1360
1455
|
S.messages = msgs;
|
|
1361
1456
|
if(S.session&&S.session.session_id===sid){
|
|
1362
1457
|
S.session.message_count=Number(data.session.message_count || msgs.length);
|
|
1458
|
+
if(Object.prototype.hasOwnProperty.call(data.session,'todo_state')) S.session.todo_state=data.session.todo_state;
|
|
1459
|
+
else delete S.session.todo_state;
|
|
1363
1460
|
S.lastUsage={...(data.session.last_usage||S.lastUsage||{})};
|
|
1364
1461
|
_setSessionViewedCount(sid, Number(S.session.message_count || msgs.length));
|
|
1365
1462
|
}
|
|
@@ -1512,6 +1609,12 @@ async function _loadOlderMessages() {
|
|
|
1512
1609
|
// Use $('messages') — the scrollable container (#msgInner is not scrollable).
|
|
1513
1610
|
const container = $('messages');
|
|
1514
1611
|
const prevScrollH = container ? container.scrollHeight : 0;
|
|
1612
|
+
// Carry forward ephemeral turn fields (_turnUsage/_turnDuration/_turnTps/
|
|
1613
|
+
// _gatewayRouting/_statusCard) before the wholesale replace so the badge
|
|
1614
|
+
// does not briefly appear and disappear during older-message expansion.
|
|
1615
|
+
if (typeof window._carryForwardEphemeralTurnFields === 'function') {
|
|
1616
|
+
nextMessages = window._carryForwardEphemeralTurnFields(S.messages || [], nextMessages);
|
|
1617
|
+
}
|
|
1515
1618
|
S.messages = nextMessages;
|
|
1516
1619
|
_syncToolCallsForLoadedMessages(nextMessages, responseSession.tool_calls);
|
|
1517
1620
|
// renderMessages() windows long transcripts from the end. If we do not
|
|
@@ -1595,7 +1698,15 @@ async function _ensureAllMessagesLoaded() {
|
|
|
1595
1698
|
// prefetch (whose snapshot was taken before this call's mutex
|
|
1596
1699
|
// acquisition) sees the new value and aborts.
|
|
1597
1700
|
_bumpMessagesGeneration();
|
|
1598
|
-
|
|
1701
|
+
// #3306: Same ephemeral-field carry-forward as _ensureMessagesLoaded.
|
|
1702
|
+
// Loading older messages also does a wholesale replace of S.messages
|
|
1703
|
+
// and would otherwise drop _turnUsage/_turnDuration/_turnTps/
|
|
1704
|
+
// _gatewayRouting/_statusCard on the existing turns.
|
|
1705
|
+
let _msgsToAssign = msgs;
|
|
1706
|
+
if (typeof window._carryForwardEphemeralTurnFields === 'function') {
|
|
1707
|
+
_msgsToAssign = window._carryForwardEphemeralTurnFields(S.messages || [], msgs);
|
|
1708
|
+
}
|
|
1709
|
+
S.messages = _msgsToAssign;
|
|
1599
1710
|
_messagesTruncated = false;
|
|
1600
1711
|
_oldestIdx = 0;
|
|
1601
1712
|
_syncToolCallsForLoadedMessages(msgs, data.session.tool_calls);
|
|
@@ -1991,12 +2102,28 @@ function _positionSessionActionMenu(anchorEl){
|
|
|
1991
2102
|
if(left+menuW>window.innerWidth-8) left=window.innerWidth-menuW-8;
|
|
1992
2103
|
_sessionActionMenu.style.left=left+'px';
|
|
1993
2104
|
_sessionActionMenu.style.top='8px';
|
|
2105
|
+
// Reset any prior clamp so we measure the menu's natural height.
|
|
2106
|
+
_sessionActionMenu.style.maxHeight='';
|
|
1994
2107
|
const menuH=_sessionActionMenu.offsetHeight || 0;
|
|
2108
|
+
const margin=8;
|
|
2109
|
+
const maxAvail=window.innerHeight-margin*2;
|
|
1995
2110
|
let top=rect.bottom+6;
|
|
1996
|
-
|
|
2111
|
+
// Prefer flipping above the row when the menu would overflow the bottom and
|
|
2112
|
+
// there's room above.
|
|
2113
|
+
if(top+menuH>window.innerHeight-margin && rect.top>menuH+12){
|
|
1997
2114
|
top=rect.top-menuH-6;
|
|
1998
2115
|
}
|
|
1999
|
-
|
|
2116
|
+
// If the menu is taller than the viewport, or still overflows after the flip
|
|
2117
|
+
// attempt (e.g. a top-anchored row with a tall menu and no room above), cap
|
|
2118
|
+
// its height to the viewport and let it scroll instead of clipping off-screen.
|
|
2119
|
+
if(menuH>maxAvail){
|
|
2120
|
+
_sessionActionMenu.style.maxHeight=maxAvail+'px';
|
|
2121
|
+
top=margin;
|
|
2122
|
+
} else {
|
|
2123
|
+
// Clamp vertically so the whole menu stays on-screen at both edges.
|
|
2124
|
+
if(top+menuH>window.innerHeight-margin) top=window.innerHeight-margin-menuH;
|
|
2125
|
+
if(top<margin) top=margin;
|
|
2126
|
+
}
|
|
2000
2127
|
_sessionActionMenu.style.top=top+'px';
|
|
2001
2128
|
}
|
|
2002
2129
|
|
|
@@ -2004,12 +2131,17 @@ function _buildSessionAction(label, meta, icon, onSelect, extraClass=''){
|
|
|
2004
2131
|
const opt=document.createElement('button');
|
|
2005
2132
|
opt.type='button';
|
|
2006
2133
|
opt.className='ws-opt session-action-opt'+(extraClass?` ${extraClass}`:'');
|
|
2134
|
+
// Compact context-menu shape (#3223 redesign, Nathan 2026-06-01): show only
|
|
2135
|
+
// icon + label, matching VS Code / browser / ChatGPT conversation menus. The
|
|
2136
|
+
// descriptive `meta` is preserved as a hover tooltip (title=) so the
|
|
2137
|
+
// information stays discoverable without consuming permanent vertical space —
|
|
2138
|
+
// this also keeps the menu short enough to avoid viewport clipping.
|
|
2139
|
+
if(meta) opt.title=meta;
|
|
2007
2140
|
opt.innerHTML=
|
|
2008
2141
|
`<span class="ws-opt-action">`
|
|
2009
2142
|
+ `<span class="ws-opt-icon">${icon}</span>`
|
|
2010
2143
|
+ `<span class="session-action-copy">`
|
|
2011
2144
|
+ `<span class="ws-opt-name">${esc(label)}</span>`
|
|
2012
|
-
+ (meta?`<span class="session-action-meta">${esc(meta)}</span>`:'')
|
|
2013
2145
|
+ `</span>`
|
|
2014
2146
|
+ `</span>`;
|
|
2015
2147
|
opt.onclick=async(e)=>{
|
|
@@ -2264,6 +2396,39 @@ function _openSessionActionMenu(session, anchorEl){
|
|
|
2264
2396
|
}
|
|
2265
2397
|
));
|
|
2266
2398
|
}
|
|
2399
|
+
// Title regeneration matches the backend guard (api/routes.py rejects
|
|
2400
|
+
// read_only OR is_imported with 403). read_only sessions already bailed at
|
|
2401
|
+
// the isReadOnly early-return above; skip imported sessions here so the
|
|
2402
|
+
// action is hidden rather than failing with a 403 toast. This keeps the
|
|
2403
|
+
// is_imported gate scoped to regenerate instead of broadening the shared
|
|
2404
|
+
// _isReadOnlySession() helper (which gates rename/pin/archive/etc.).
|
|
2405
|
+
if(!session.is_imported){
|
|
2406
|
+
menu.appendChild(_buildSessionAction(
|
|
2407
|
+
t('session_title_regenerate'),
|
|
2408
|
+
t('session_title_regenerate_desc'),
|
|
2409
|
+
ICONS.spark,
|
|
2410
|
+
async()=>{
|
|
2411
|
+
closeSessionActionMenu();
|
|
2412
|
+
try{
|
|
2413
|
+
if(typeof showToast==='function') showToast(t('session_title_regenerating'), 1600);
|
|
2414
|
+
const response=await api('/api/session/title/regenerate',{method:'POST',body:JSON.stringify({session_id:session.session_id})});
|
|
2415
|
+
const nextTitle=(response&&response.title)||(response&&response.session&&response.session.title)||'';
|
|
2416
|
+
if(nextTitle){
|
|
2417
|
+
session.title=nextTitle;
|
|
2418
|
+
const cached=(_allSessions||[]).find(item=>item&&item.session_id===session.session_id);
|
|
2419
|
+
if(cached) cached.title=nextTitle;
|
|
2420
|
+
if(S.session&&S.session.session_id===session.session_id){S.session.title=nextTitle;syncTopbar();}
|
|
2421
|
+
renderSessionListFromCache();
|
|
2422
|
+
}
|
|
2423
|
+
if(typeof showToast==='function') showToast(t('session_title_regenerated', nextTitle||t('untitled')), 2400);
|
|
2424
|
+
}catch(err){
|
|
2425
|
+
const msg=t('session_title_regenerate_failed')+(err&&err.message?err.message:String(err));
|
|
2426
|
+
setStatus(msg);
|
|
2427
|
+
if(typeof showToast==='function') showToast(msg,3000,'error');
|
|
2428
|
+
}
|
|
2429
|
+
}
|
|
2430
|
+
));
|
|
2431
|
+
}
|
|
2267
2432
|
if(!isExternalSession){
|
|
2268
2433
|
if(session.worktree_path){
|
|
2269
2434
|
menu.appendChild(_buildSessionAction(
|
|
@@ -2846,7 +3011,14 @@ function startGatewaySSE(){
|
|
|
2846
3011
|
const next = res.session.messages.filter(m => m && m.role);
|
|
2847
3012
|
if (next.length < prev) return;
|
|
2848
3013
|
if (prev > 0 && !_isCliImportRefreshPrefixMatch(S.messages, next)) return;
|
|
2849
|
-
|
|
3014
|
+
// Carry forward ephemeral turn fields (_turnUsage/
|
|
3015
|
+
// _turnDuration/_turnTps/_gatewayRouting/_statusCard) so
|
|
3016
|
+
// gateway-driven CLI refreshes do not drop the badge.
|
|
3017
|
+
let _nextToAssign = next;
|
|
3018
|
+
if (typeof window._carryForwardEphemeralTurnFields === 'function') {
|
|
3019
|
+
_nextToAssign = window._carryForwardEphemeralTurnFields(S.messages || [], next);
|
|
3020
|
+
}
|
|
3021
|
+
S.messages = _nextToAssign;
|
|
2850
3022
|
if(S.session && S.session.session_id === activeSid){
|
|
2851
3023
|
S.session.message_count = next.length;
|
|
2852
3024
|
const newest = next.length ? next[next.length - 1] : null;
|
|
@@ -3508,7 +3680,11 @@ function _collapseSessionLineageForSidebar(sessions){
|
|
|
3508
3680
|
}
|
|
3509
3681
|
|
|
3510
3682
|
function _sessionDisplayTitle(s){
|
|
3511
|
-
const
|
|
3683
|
+
const rawTitle=String((s&&(s.display_title||s._state_db_title||s.title))||'Untitled').trim();
|
|
3684
|
+
const strip=(typeof _stripAttachedFilesMarker==='function')
|
|
3685
|
+
? _stripAttachedFilesMarker
|
|
3686
|
+
: (text)=>String(text||'').replace(/\n\n\[Attached files: [^\]]+\]$/,'').trim();
|
|
3687
|
+
const title=strip(rawTitle);
|
|
3512
3688
|
return title||'Untitled';
|
|
3513
3689
|
}
|
|
3514
3690
|
|
|
@@ -4927,19 +5103,38 @@ function _showProjectPicker(session, anchorEl){
|
|
|
4927
5103
|
none.onclick=async()=>{
|
|
4928
5104
|
picker.remove();
|
|
4929
5105
|
document.removeEventListener('click',close);
|
|
4930
|
-
|
|
4931
|
-
|
|
4932
|
-
|
|
4933
|
-
|
|
4934
|
-
|
|
4935
|
-
|
|
4936
|
-
|
|
4937
|
-
|
|
4938
|
-
|
|
5106
|
+
try {
|
|
5107
|
+
await api('/api/session/move',{method:'POST',body:JSON.stringify({session_id:session.session_id,project_id:null})});
|
|
5108
|
+
// Sidebar rows are shallow copies of _allSessions entries (see
|
|
5109
|
+
// _attachChildSessionsToSidebarRows), so mutating `session` only updates
|
|
5110
|
+
// the discarded copy. Write into the authoritative cache so the next
|
|
5111
|
+
// renderSessionListFromCache() reflects the move. (#2551)
|
|
5112
|
+
const idx=_allSessions.findIndex(s=>s&&s.session_id===session.session_id);
|
|
5113
|
+
if(idx>=0) _allSessions[idx].project_id=null;
|
|
5114
|
+
renderSessionListFromCache();
|
|
5115
|
+
showToast('Removed from project');
|
|
5116
|
+
} catch(e) {
|
|
5117
|
+
showToast('Unassign failed: '+(e.message||e));
|
|
5118
|
+
}
|
|
4939
5119
|
};
|
|
4940
5120
|
picker.appendChild(none);
|
|
4941
|
-
// Project options
|
|
5121
|
+
// Project options — only show projects matching the session's profile.
|
|
5122
|
+
// #3331 follow-up (Codex gate): mirror the server's root-alias tolerance —
|
|
5123
|
+
// `_profiles_match` treats the literal 'default' and a renamed-root display
|
|
5124
|
+
// name as equivalent, so a server-approved `profile:'default'` project must
|
|
5125
|
+
// not be hidden for a session stamped with the renamed-root profile (and
|
|
5126
|
+
// vice versa). Only hide when BOTH sides are explicit, distinct, AND neither
|
|
5127
|
+
// is the 'default' alias; let the server's allowlist be authoritative for the
|
|
5128
|
+
// default/renamed-root case.
|
|
5129
|
+
const sessionProfile = session ? (session.profile || undefined) : undefined;
|
|
5130
|
+
const _profileHidesProject = (projProfile) => {
|
|
5131
|
+
if(!sessionProfile || !projProfile) return false;
|
|
5132
|
+
if(projProfile === sessionProfile) return false;
|
|
5133
|
+
if(projProfile === 'default' || sessionProfile === 'default') return false;
|
|
5134
|
+
return true;
|
|
5135
|
+
};
|
|
4942
5136
|
for(const p of _allProjects){
|
|
5137
|
+
if (_profileHidesProject(p.profile)) continue;
|
|
4943
5138
|
const item=document.createElement('div');
|
|
4944
5139
|
item.className='project-picker-item'+(session.project_id===p.project_id?' active':'');
|
|
4945
5140
|
if(p.color){
|
|
@@ -4954,12 +5149,14 @@ function _showProjectPicker(session, anchorEl){
|
|
|
4954
5149
|
item.onclick=async()=>{
|
|
4955
5150
|
picker.remove();
|
|
4956
5151
|
document.removeEventListener('click',close);
|
|
4957
|
-
|
|
4958
|
-
|
|
4959
|
-
|
|
4960
|
-
|
|
4961
|
-
|
|
4962
|
-
|
|
5152
|
+
try{
|
|
5153
|
+
await api('/api/session/move',{method:'POST',body:JSON.stringify({session_id:session.session_id,project_id:p.project_id})});
|
|
5154
|
+
// See #2551 — write to _allSessions, not the shallow sidebar copy.
|
|
5155
|
+
const idx=_allSessions.findIndex(s=>s&&s.session_id===session.session_id);
|
|
5156
|
+
if(idx>=0) _allSessions[idx].project_id=p.project_id;
|
|
5157
|
+
renderSessionListFromCache();
|
|
5158
|
+
showToast('Moved to '+p.name);
|
|
5159
|
+
}catch(e){showToast('Move failed: '+(e.message||e));}
|
|
4963
5160
|
};
|
|
4964
5161
|
picker.appendChild(item);
|
|
4965
5162
|
}
|
|
@@ -4977,7 +5174,8 @@ function _showProjectPicker(session, anchorEl){
|
|
|
4977
5174
|
});
|
|
4978
5175
|
if(!name||!name.trim()) return;
|
|
4979
5176
|
const color=PROJECT_COLORS[_allProjects.length%PROJECT_COLORS.length];
|
|
4980
|
-
const
|
|
5177
|
+
const profile = session.profile || undefined;
|
|
5178
|
+
const res=await api('/api/projects/create',{method:'POST',body:JSON.stringify({name:name.trim(),color,profile})});
|
|
4981
5179
|
if(res.project){
|
|
4982
5180
|
_allProjects.push(res.project);
|
|
4983
5181
|
// Now move session into it
|
|
@@ -5068,9 +5266,13 @@ function _startProjectRename(proj, chip){
|
|
|
5068
5266
|
inp.value=proj.name;
|
|
5069
5267
|
const finish=async(save)=>{
|
|
5070
5268
|
if(save&&inp.value.trim()&&inp.value.trim()!==proj.name){
|
|
5071
|
-
|
|
5072
|
-
|
|
5073
|
-
|
|
5269
|
+
try {
|
|
5270
|
+
await api('/api/projects/rename',{method:'POST',body:JSON.stringify({project_id:proj.project_id,name:inp.value.trim()})});
|
|
5271
|
+
await renderSessionList();
|
|
5272
|
+
showToast('Project renamed');
|
|
5273
|
+
} catch(e) {
|
|
5274
|
+
showToast('Rename failed: '+(e.message||e));
|
|
5275
|
+
}
|
|
5074
5276
|
}else{
|
|
5075
5277
|
renderSessionListFromCache();
|
|
5076
5278
|
}
|
|
@@ -5121,9 +5323,13 @@ function _showProjectContextMenu(e, proj, chip){
|
|
|
5121
5323
|
if(hex===(proj.color||'')) dot.style.outline='2px solid var(--text)';
|
|
5122
5324
|
dot.onclick=async()=>{
|
|
5123
5325
|
menu.remove();
|
|
5124
|
-
|
|
5125
|
-
|
|
5126
|
-
|
|
5326
|
+
try {
|
|
5327
|
+
await api('/api/projects/rename',{method:'POST',body:JSON.stringify({project_id:proj.project_id,name:proj.name,color:hex})});
|
|
5328
|
+
await renderSessionList();
|
|
5329
|
+
showToast('Color updated');
|
|
5330
|
+
} catch(e) {
|
|
5331
|
+
showToast('Color update failed: '+(e.message||e));
|
|
5332
|
+
}
|
|
5127
5333
|
};
|
|
5128
5334
|
colorRow.appendChild(dot);
|
|
5129
5335
|
});
|
|
@@ -5153,10 +5359,14 @@ async function _confirmDeleteProject(proj){
|
|
|
5153
5359
|
danger:true
|
|
5154
5360
|
});
|
|
5155
5361
|
if(!ok){return;}
|
|
5156
|
-
|
|
5157
|
-
|
|
5158
|
-
|
|
5159
|
-
|
|
5362
|
+
try {
|
|
5363
|
+
await api('/api/projects/delete',{method:'POST',body:JSON.stringify({project_id:proj.project_id})});
|
|
5364
|
+
if(_activeProject===proj.project_id) _activeProject=null;
|
|
5365
|
+
await renderSessionList();
|
|
5366
|
+
showToast('Project deleted');
|
|
5367
|
+
} catch(e) {
|
|
5368
|
+
showToast('Delete failed: '+(e.message||e));
|
|
5369
|
+
}
|
|
5160
5370
|
}
|
|
5161
5371
|
|
|
5162
5372
|
// Global Escape handler for batch select mode
|
|
@@ -1012,7 +1012,7 @@
|
|
|
1012
1012
|
.session-action-menu{display:block;position:fixed;left:0;top:0;right:auto;bottom:auto;min-width:220px;max-width:min(280px,calc(100vw - 16px));background:var(--surface);border:1px solid var(--border2);border-radius:10px;box-shadow:0 -4px 24px rgba(0,0,0,.4);z-index:999;overflow:hidden;max-height:calc(100vh - 16px);overflow-y:auto;transform-origin:top right;will-change:opacity,transform;}
|
|
1013
1013
|
.session-action-menu.open-animated{animation:session-menu-in .45s cubic-bezier(.2,.8,.2,1);}
|
|
1014
1014
|
.session-action-opt{width:100%;background:none;border:none;text-align:left;font:inherit;color:var(--text);flex-direction:row!important;gap:0!important;padding:0!important;}
|
|
1015
|
-
.session-action-opt .ws-opt-action{display:flex;flex-direction:row;align-items:center;gap:10px;width:100%;padding:
|
|
1015
|
+
.session-action-opt .ws-opt-action{display:flex;flex-direction:row;align-items:center;gap:10px;width:100%;padding:8px 14px;}
|
|
1016
1016
|
.session-action-opt .ws-opt-icon{color:var(--muted);transition:color .12s,opacity .12s;flex-shrink:0;display:flex;align-items:center;width:16px;}
|
|
1017
1017
|
.session-action-opt:hover .ws-opt-icon{color:var(--text);opacity:1;}
|
|
1018
1018
|
.session-action-copy{display:flex;flex-direction:column;gap:2px;min-width:0;}
|
|
@@ -1561,6 +1561,22 @@
|
|
|
1561
1561
|
.img-lightbox-counter{position:absolute;bottom:20px;left:50%;transform:translateX(-50%);background:rgba(0,0,0,.5);color:#fff;font-size:14px;padding:4px 14px;border-radius:12px;pointer-events:none;}
|
|
1562
1562
|
.msg-media-link{display:inline-flex;align-items:center;gap:5px;background:var(--accent-bg);border:1px solid var(--accent-bg-strong);border-radius:6px;padding:4px 10px;font-size:13px;color:var(--accent-text);text-decoration:none;}
|
|
1563
1563
|
.msg-media-link:hover{background:var(--accent-bg-strong);}
|
|
1564
|
+
/* Generated local image artifact (#3220): clean inline image with a
|
|
1565
|
+
hover/focus-revealed Download button overlaid top-right. The image keeps
|
|
1566
|
+
its .msg-media-img lightbox-on-click behavior; no permanent card chrome —
|
|
1567
|
+
matches the ChatGPT/Claude/Gemini pattern of letting the image be the hero. */
|
|
1568
|
+
.msg-artifact-image{position:relative;display:inline-block;width:fit-content;max-width:min(360px,100%);vertical-align:bottom;margin:6px 4px 6px 0;line-height:0;}
|
|
1569
|
+
/* Generated images are the subject of the message, not a small attachment
|
|
1570
|
+
thumbnail — render them prominently at natural aspect ratio (the base
|
|
1571
|
+
.msg-media-img is a cropped 120x90 upload thumbnail). Capped + responsive so
|
|
1572
|
+
they never overflow the message column or a mobile viewport. Lightbox-on-click
|
|
1573
|
+
(cursor:zoom-in) is preserved. */
|
|
1574
|
+
.msg-artifact-image .msg-media-img{margin:0;display:block;width:auto;height:auto;max-width:min(360px,100%);max-height:360px;object-fit:contain;border-radius:8px;cursor:zoom-in;}
|
|
1575
|
+
.msg-artifact-download{position:absolute;top:8px;right:8px;width:28px;height:28px;display:inline-flex;align-items:center;justify-content:center;border-radius:8px;background:rgba(0,0,0,.62);border:1px solid rgba(255,255,255,.25);box-shadow:0 1px 4px rgba(0,0,0,.35);backdrop-filter:blur(3px);-webkit-backdrop-filter:blur(3px);color:#fff;text-decoration:none;opacity:0;transform:translateY(-2px);transition:opacity .12s,transform .12s,background .12s;pointer-events:none;}
|
|
1576
|
+
.msg-artifact-image:hover .msg-artifact-download,.msg-artifact-image:focus-within .msg-artifact-download{opacity:1;transform:translateY(0);pointer-events:auto;}
|
|
1577
|
+
.msg-artifact-download:hover{background:rgba(0,0,0,.82);border-color:rgba(255,255,255,.4);}
|
|
1578
|
+
.msg-artifact-download:focus-visible{opacity:1;pointer-events:auto;outline:2px solid var(--accent-bg-strong);outline-offset:1px;}
|
|
1579
|
+
@media (hover:none){.msg-artifact-download{opacity:1;pointer-events:auto;transform:none;}}
|
|
1564
1580
|
|
|
1565
1581
|
/* ── Inline SVG rendering ── */
|
|
1566
1582
|
.msg-media-svg{display:block;max-width:100%;height:auto;border-radius:6px;margin:6px 0;border:1px solid var(--border);background:var(--surface);}
|
|
@@ -1946,6 +1962,7 @@
|
|
|
1946
1962
|
.breadcrumb-current{color:var(--text);font-weight:500;}
|
|
1947
1963
|
.breadcrumb-sep{color:var(--border);margin:0 1px;font-size:11px;}
|
|
1948
1964
|
.file-tree{flex:1;overflow-y:auto;padding:8px;}
|
|
1965
|
+
.file-tree.drag-over-upload{outline:2px dashed var(--blue,#3b82f6);outline-offset:-2px;border-radius:6px;}
|
|
1949
1966
|
.file-item{display:flex;align-items:center;gap:6px;padding:6px 10px;border-radius:8px;cursor:pointer;font-size:12px;color:var(--muted);transition:all .12s;min-width:0;}
|
|
1950
1967
|
.file-item:hover{background:var(--hover-bg);color:var(--text);}
|
|
1951
1968
|
.file-item.active{background:var(--accent-bg);color:var(--accent-text);}
|
|
@@ -1957,6 +1974,14 @@
|
|
|
1957
1974
|
.preview-area.visible{display:flex;opacity:1;}
|
|
1958
1975
|
.preview-path{font-size:11px;color:var(--muted);padding-bottom:8px;border-bottom:1px solid var(--border);flex-shrink:0;}
|
|
1959
1976
|
.preview-code{font-family:"SF Mono","Fira Code",ui-monospace,monospace;font-size:12px;line-height:1.6;white-space:pre-wrap;word-break:break-word;color:var(--pre-text);}
|
|
1977
|
+
/* Workspace preview syntax highlighting (#3337): Prism's prism-tomorrow theme
|
|
1978
|
+
styles BOTH pre[class*="language-"] and code[class*="language-"] with its own
|
|
1979
|
+
gray background + padding. Prism propagates the language-* class onto the
|
|
1980
|
+
<pre> (.preview-code), so without overriding the <pre> too you get a gray
|
|
1981
|
+
prism frame around the navy var(--code-bg) <code> — a jarring two-tone block
|
|
1982
|
+
on dark themes. Mirror the chat-code-block fix (.msg-body pre[class*=...]):
|
|
1983
|
+
force BOTH surfaces to var(--code-bg) so the code panel reads as one tone. */
|
|
1984
|
+
.preview-code[class*="language-"],.preview-code code[class*="language-"]{background:var(--code-bg) !important;}
|
|
1960
1985
|
/* Image preview */
|
|
1961
1986
|
.preview-img-wrap{display:flex;align-items:center;justify-content:center;flex:1;padding:8px 0;min-height:0;}
|
|
1962
1987
|
.preview-img{max-width:100%;max-height:100%;object-fit:contain;border-radius:6px;box-shadow:0 2px 12px rgba(0,0,0,.4);}
|
|
@@ -3074,7 +3099,7 @@ main.main > #mainWorkspaces,
|
|
|
3074
3099
|
main.main > #mainProfiles,
|
|
3075
3100
|
main.main > #mainInsights,
|
|
3076
3101
|
main.main > #mainLogs{display:none;}
|
|
3077
|
-
main.main:not(.showing-settings):not(.showing-skills):not(.showing-memory):not(.showing-tasks):not(.showing-kanban):not(.showing-workspaces):not(.showing-profiles):not(.showing-insights):not(.showing-logs) > #mainChat{display:flex;}
|
|
3102
|
+
main.main:not(.showing-settings):not(.showing-skills):not(.showing-memory):not(.showing-tasks):not(.showing-kanban):not(.showing-workspaces):not(.showing-profiles):not(.showing-insights):not(.showing-logs):not(.showing-plugin) > #mainChat{display:flex;}
|
|
3078
3103
|
main.main.showing-settings > #mainSettings{display:flex;overflow-y:auto;}
|
|
3079
3104
|
main.main.showing-skills > #mainSkills{display:flex;}
|
|
3080
3105
|
main.main.showing-memory > #mainMemory{display:flex;}
|
|
@@ -3083,6 +3108,10 @@ main.main.showing-kanban > #mainKanban{display:flex;overflow-y:auto;}
|
|
|
3083
3108
|
main.main.showing-workspaces > #mainWorkspaces{display:flex;}
|
|
3084
3109
|
main.main.showing-profiles > #mainProfiles{display:flex;}
|
|
3085
3110
|
main.main.showing-logs > #mainLogs{display:flex;}
|
|
3111
|
+
main.main.showing-plugin > #mainPlugin{display:flex;}
|
|
3112
|
+
main.main > #mainPlugin{display:none;}
|
|
3113
|
+
#mainPlugin .main-view-body{flex:1;display:flex;flex-direction:column;overflow:hidden;}
|
|
3114
|
+
#pluginPageContainer{flex:1;display:flex;flex-direction:column;min-height:0;}
|
|
3086
3115
|
#mainSettings{overflow-y:auto;}
|
|
3087
3116
|
|
|
3088
3117
|
/* Sidebar menu (lives in the left sidebar under the cog panel) */
|
|
@@ -3457,6 +3486,21 @@ main.main.showing-logs > #mainLogs{display:flex;}
|
|
|
3457
3486
|
.plugin-hook-list{display:flex;flex-wrap:wrap;gap:6px;margin-top:6px;}
|
|
3458
3487
|
.plugin-hook-badge{display:inline-flex;align-items:center;border:1px solid var(--border2);background:var(--code-bg);color:var(--text);border-radius:999px;padding:3px 8px;font-size:11px;font-family:var(--font-mono);}
|
|
3459
3488
|
.plugin-hook-empty{font-size:12px;color:var(--muted);font-style:italic;}
|
|
3489
|
+
.plugin-card-footer{margin-top:10px;padding-top:10px;border-top:1px solid var(--border);}
|
|
3490
|
+
.plugin-card-footer-row{display:flex;align-items:center;justify-content:space-between;margin-top:8px;padding-top:8px;border-top:1px solid var(--border);}
|
|
3491
|
+
.plugin-toggle-label{font-size:12px;color:var(--muted);}
|
|
3492
|
+
.plugin-toggle-switch{position:relative;display:inline-block;width:32px;height:18px;flex-shrink:0;}
|
|
3493
|
+
.plugin-toggle-switch input{opacity:0;width:0;height:0;}
|
|
3494
|
+
.plugin-toggle-slider{position:absolute;cursor:pointer;inset:0;background:var(--border2);border-radius:9px;transition:background .2s;}
|
|
3495
|
+
.plugin-toggle-slider::before{content:'';position:absolute;height:12px;width:12px;left:3px;bottom:3px;background:#fff;border-radius:50%;transition:transform .2s;box-shadow:0 1px 2px rgba(0,0,0,.45);}
|
|
3496
|
+
.plugin-toggle-switch input:checked+.plugin-toggle-slider{background:var(--accent);}
|
|
3497
|
+
.plugin-toggle-switch input:checked+.plugin-toggle-slider::before{transform:translateX(14px);}
|
|
3498
|
+
/* Ghost/outline button: --accent-text is not guaranteed to contrast against
|
|
3499
|
+
--accent in every theme (e.g. it equals --accent in the default gold theme,
|
|
3500
|
+
which renders invisible text on a filled block). Use the accent as the text +
|
|
3501
|
+
border colour on the card's own surface, which is always legible. */
|
|
3502
|
+
.plugin-open-btn{display:inline-flex;align-items:center;gap:6px;padding:6px 14px;background:transparent;color:var(--accent);border:1px solid var(--accent);border-radius:7px;font-size:13px;font-weight:600;cursor:pointer;text-decoration:none;}
|
|
3503
|
+
.plugin-open-btn:hover{background:var(--accent);color:var(--bg);}
|
|
3460
3504
|
|
|
3461
3505
|
/* ── Provider model tags ── */
|
|
3462
3506
|
.provider-card-models{
|