@oar-sh/oar 0.9.10 → 0.9.12
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/CHANGELOG.md +440 -1
- package/README.md +156 -972
- package/package.json +13 -13
- package/server/README.md +112 -25
- package/server/claude-worker/claude-remote-relay-tool.mjs +2 -0
- package/server/claude-worker/claude-sdk-adapter.mjs +57 -4
- package/server/claude-worker/claude-session-process.mjs +217 -11
- package/server/claude-worker/claude-session-worker.mjs +26 -1
- package/server/claude-worker/task-output-discovery.mjs +126 -0
- package/server/copilot-worker/copilot-sdk-session-process.mjs +235 -14
- package/server/copilot-worker/copilot-sdk-session-worker.mjs +8 -0
- package/server/copilot-worker/copilot-shell-output-mirror.mjs +94 -0
- package/server/copilot-worker/copilot-task-settle.mjs +96 -0
- package/server/db-schema.mjs +68 -2
- package/server/grok-worker/acp-client.mjs +2 -1
- package/server/grok-worker/acp-host-services.mjs +46 -6
- package/server/grok-worker/grok-background-tasks.mjs +241 -0
- package/server/grok-worker/grok-sdk-adapter.mjs +3 -1
- package/server/grok-worker/grok-session-worker.mjs +33 -1
- package/server/grok-worker/grok-turn-runner.mjs +11 -0
- package/server/migrations/0013-previews.mjs +130 -0
- package/server/migrations/0014-conversation-groups.mjs +89 -0
- package/server/migrations/0015-conversation-group-position.mjs +76 -0
- package/server/migrations/0016-share-access-log.mjs +110 -0
- package/server/migrations/0017-conversation-history-indexes.mjs +89 -0
- package/server/public/app/about-ui.js +73 -0
- package/server/public/app/about-view.mjs +234 -0
- package/server/public/app/action-confirmations.js +31 -14
- package/server/public/app/agent-exchange-fold-view.mjs +40 -101
- package/server/public/app/api-client.js +264 -16
- package/server/public/app/ask-user-view.js +17 -15
- package/server/public/app/background-tasks-view.mjs +285 -16
- package/server/public/app/bootstrap.js +253 -37
- package/server/public/app/chat-actions-menu-state.mjs +20 -10
- package/server/public/app/claude-background-command-limit-options.mjs +21 -0
- package/server/public/app/context-menu.mjs +90 -6
- package/server/public/app/conversation-group-drag.mjs +191 -0
- package/server/public/app/conversation-group-order.mjs +265 -0
- package/server/public/app/conversation-group-state.mjs +100 -0
- package/server/public/app/conversation-list-drag.mjs +449 -0
- package/server/public/app/conversation-payload-cache.mjs +357 -0
- package/server/public/app/conversation-turn-state.mjs +81 -0
- package/server/public/app/conversation-view.js +567 -94
- package/server/public/app/conversation-window-retention.mjs +96 -0
- package/server/public/app/cwd-picker.js +1 -1
- package/server/public/app/hidden-messages-view.mjs +312 -0
- package/server/public/app/host-suspend-ui.js +7 -1
- package/server/public/app/journal-view.js +1033 -82
- package/server/public/app/preview-cards.mjs +117 -31
- package/server/public/app/preview-command.mjs +10 -1
- package/server/public/app/relay-board-view.js +19 -6
- package/server/public/app/relay-prompt-sanitizer.mjs +11 -4
- package/server/public/app/relay-restart-error.mjs +16 -0
- package/server/public/app/settings-modal.js +150 -10
- package/server/public/app/settings-tabs.js +1 -1
- package/server/public/app/share-format.mjs +49 -0
- package/server/public/app/share-preview-marks.mjs +167 -0
- package/server/public/app/share-state.js +109 -0
- package/server/public/app/shared-conversations-settings.js +480 -0
- package/server/public/app/socket-handlers.js +138 -20
- package/server/public/app/store.js +121 -30
- package/server/public/app/subagent-row.mjs +3 -3
- package/server/public/app/subagent-viewer.mjs +37 -7
- package/server/public/app/task-console-render.mjs +237 -0
- package/server/public/app/task-console-watch.mjs +42 -0
- package/server/public/app/task-console.mjs +630 -0
- package/server/public/app/task-output-text.mjs +234 -0
- package/server/public/app/thinking-follow.js +0 -4
- package/server/public/app/transcript-island.mjs +73 -0
- package/server/public/app/transcript-jump-bottom-view.mjs +230 -26
- package/server/public/app/transcript-jump-bottom.mjs +170 -22
- package/server/public/app/transcript-reading-anchor.mjs +256 -0
- package/server/public/app/transcript-reload-freshness.mjs +17 -1
- package/server/public/app/transcript-viewport-anchor.mjs +159 -0
- package/server/public/index.html +625 -21
- package/server/relay-tools.md +3 -1
- package/server/relay.mjs +2 -1
- package/server/repositories/conversation-group-repository.mjs +444 -0
- package/server/repositories/message-repository.mjs +9 -0
- package/server/repositories/question-repository.mjs +4 -0
- package/server/repositories/session-repository.mjs +12 -37
- package/server/repositories/share-repository.mjs +322 -0
- package/server/repositories/share-visibility-statements.mjs +10 -0
- package/server/routes/about-routes.mjs +15 -0
- package/server/routes/cache-routes.mjs +4 -0
- package/server/routes/conversation-group-routes.mjs +171 -0
- package/server/routes/messages-routes.mjs +433 -68
- package/server/routes/preview-routes.mjs +9 -1
- package/server/routes/sessions-routes.mjs +901 -253
- package/server/server-runtime.mjs +392 -99
- package/server/services/about-service.mjs +461 -0
- package/server/services/background-task-store.mjs +365 -0
- package/server/services/background-work-view.mjs +228 -0
- package/server/services/cache-rebuild-service.mjs +30 -0
- package/server/services/cli-process-runner.mjs +4 -21
- package/server/services/conversation-history-page.mjs +199 -0
- package/server/services/delete-archive-service.mjs +4 -1
- package/server/services/detached-worker-watch-service.mjs +150 -0
- package/server/services/hidden-message-list.mjs +21 -0
- package/server/services/host-suspend-command.mjs +2 -1
- package/server/services/message-pin-list.mjs +1 -1
- package/server/services/preview-health-probe.mjs +8 -1
- package/server/services/preview-proxy-server.mjs +8 -4
- package/server/services/preview-registry-service.mjs +676 -26
- package/server/services/preview-static-handler.mjs +40 -7
- package/server/services/preview-store.mjs +274 -0
- package/server/services/rate-limit-service.mjs +12 -1
- package/server/services/relay-auth.mjs +183 -0
- package/server/services/relay-cli-launcher-service.mjs +3 -1
- package/server/services/relay-event-service.mjs +17 -0
- package/server/services/relay-prompt-sanitizer.mjs +11 -4
- package/server/services/remote-relay-dispatcher.mjs +187 -7
- package/server/services/request-boundary.mjs +183 -0
- package/server/services/session-activity-tracker.mjs +231 -19
- package/server/services/session-worker-launch-service.mjs +190 -6
- package/server/services/session-worker-process-service.mjs +10 -6
- package/server/services/share-access-log-service.mjs +123 -0
- package/server/services/share-path-redaction.mjs +424 -0
- package/server/services/status-event-service.mjs +7 -3
- package/server/services/task-output-rooms.mjs +259 -0
- package/server/services/task-output-service.mjs +562 -0
- package/server/services/tunnel-worker-path-guard.mjs +24 -41
- package/server/services/windows-autostart-service.mjs +6 -1
- package/server/services/windows-boot-autostart-service.mjs +207 -23
- package/shared/claude-background-command-limit.mjs +83 -0
- package/shared/preview-tool-core.mjs +15 -5
- package/shared/remote-relay-contract.mjs +35 -3
- package/shared/share-path-privacy.mjs +30 -0
- package/shared/task-output-text.mjs +737 -0
- package/shared/windows-system-tools.mjs +106 -0
- package/shared/worker-runtime/background-task-digest.mjs +30 -0
- package/shared/worker-runtime/heartbeat.mjs +24 -0
- package/shared/worker-runtime/kept-posts.mjs +83 -0
- package/shared/worker-runtime/task-output-spool.mjs +181 -0
- package/shared/worker-runtime/worker-websocket-link.mjs +47 -15
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
BASE,
|
|
2
3
|
CLIENT_ID,
|
|
3
4
|
currentConvId,
|
|
4
5
|
conversations,
|
|
@@ -24,7 +25,7 @@ import {
|
|
|
24
25
|
showTransientRelayNotice,
|
|
25
26
|
applyContextUsageBar,
|
|
26
27
|
scrollBottom,
|
|
27
|
-
|
|
28
|
+
isMessagesNearBottom,
|
|
28
29
|
setHistoryRefreshInFlight,
|
|
29
30
|
isHistoryRefreshInFlight,
|
|
30
31
|
setSummaryModalLoading,
|
|
@@ -42,18 +43,28 @@ import {
|
|
|
42
43
|
loadConversationScrollTop,
|
|
43
44
|
loadConversationLoadedMessageCount,
|
|
44
45
|
saveConversationScrollTop,
|
|
46
|
+
loadConversationReadingAnchor,
|
|
47
|
+
saveConversationReadingAnchor,
|
|
45
48
|
getSessionWorkerState,
|
|
46
49
|
resolveConversationUiState,
|
|
47
50
|
summaryModalState,
|
|
48
51
|
IS_SHARED_VIEW,
|
|
52
|
+
IS_SHARE_PREVIEW,
|
|
49
53
|
SHARED_CONVERSATION_TOKEN,
|
|
50
54
|
getConversationWatcherCount,
|
|
51
55
|
generateId,
|
|
52
56
|
imageEditTarget,
|
|
53
57
|
hasPendingUserMessageForConversation,
|
|
54
58
|
} from './store.js';
|
|
55
|
-
import {
|
|
56
|
-
|
|
59
|
+
import {
|
|
60
|
+
captureReadingBlock,
|
|
61
|
+
createTranscriptResizeKeeper,
|
|
62
|
+
restoreReadingBlock,
|
|
63
|
+
} from './transcript-viewport-anchor.mjs';
|
|
64
|
+
import { initTranscriptJumpToBottom, onTranscriptEndLeft } from './transcript-jump-bottom-view.mjs';
|
|
65
|
+
import { syncScrollOnSendToggle, updateScrollOnSendSetting } from './transcript-jump-bottom.mjs';
|
|
66
|
+
import { previewWindowRetention, syncWindowRetentionSlider, updateWindowRetention } from './conversation-window-retention.mjs';
|
|
67
|
+
import { conversationPayloadCache, mergeReloadIntoWindow } from './conversation-payload-cache.mjs';
|
|
57
68
|
import {
|
|
58
69
|
verifyExistingSession,
|
|
59
70
|
verifyToken,
|
|
@@ -92,6 +103,9 @@ import {
|
|
|
92
103
|
isArchivedConversationsView,
|
|
93
104
|
refreshNewConversationCloudRepoList,
|
|
94
105
|
openOriginConversation,
|
|
106
|
+
openConversationGroupPicker,
|
|
107
|
+
toggleConversationGroupFold,
|
|
108
|
+
isConversationWorking,
|
|
95
109
|
} from './journal-view.js';
|
|
96
110
|
import {
|
|
97
111
|
loadRelayQuestions,
|
|
@@ -126,13 +140,18 @@ import {
|
|
|
126
140
|
conversationHasActiveTurn,
|
|
127
141
|
focusConversationMessageById,
|
|
128
142
|
syncRenderedPinState,
|
|
143
|
+
syncRenderedShareVisibility,
|
|
129
144
|
isConversationWindowAtTail,
|
|
130
145
|
getTranscriptJumpEpoch,
|
|
131
146
|
getTranscriptLiveRowEpoch,
|
|
132
|
-
|
|
147
|
+
getHeldBackLiveRowIds,
|
|
148
|
+
captureConversationReadingAnchor,
|
|
149
|
+
canMergeIntoTranscript,
|
|
133
150
|
} from './conversation-view.js';
|
|
134
151
|
import { loadKeepingLiveRows } from './transcript-reload-freshness.mjs';
|
|
152
|
+
import { newerPageReachesEnd } from './transcript-island.mjs';
|
|
135
153
|
import { initPinnedMessagesView, setPinsConversation } from './pinned-messages-view.mjs';
|
|
154
|
+
import { initHiddenMessagesView, setHiddenMessagesConversation } from './hidden-messages-view.mjs';
|
|
136
155
|
import { bindChatSelectionGuard, chatSelectionGuard, isChatInteractionHeld } from './selection-guard.mjs';
|
|
137
156
|
import { loadRepoBrowserTree, openRepoBrowser, closeRepoBrowser, setRepoBrowserSessionInfo, resetWorkspaceRepoBrowserForRootChange } from './attachments-view.js';
|
|
138
157
|
import { handleAttachmentInput, retryAttachmentUpload, openAnnotateEditorForPending, openAnnotateEditorForPreview, handleComposerPaste, handleComposerDrop, refreshComposerAttachmentWarning, removeAttachment, clearAttachments, openUploadedAttachmentViewer, setFilePreviewMode, toggleFilePreviewHtml, copyFilePreviewImage, closeFilePreview, goBackFilePreview, openWorkspaceFilePreview, openWorkspaceFilePreviewFromRepo, setRepoBrowserRoot, setRepoBrowserViewMode, toggleRepoBrowserHidden, toggleRepoBrowserHeavy, refreshRepoBrowser, focusRepoTree, setRepoCurrentPath, confirmRepoBrowserCwdPick, startRepoNewFolder } from './attachments-view.js';
|
|
@@ -179,6 +198,8 @@ import {
|
|
|
179
198
|
closeMessageSearchModal,
|
|
180
199
|
} from './message-search-view.js';
|
|
181
200
|
import { initSubagentViewer } from './subagent-viewer.mjs';
|
|
201
|
+
import { initTaskConsole } from './task-console.mjs';
|
|
202
|
+
import { watchPreviewMarks } from './share-preview-marks.mjs';
|
|
182
203
|
import {
|
|
183
204
|
initGitChangesView,
|
|
184
205
|
openGitChangesModal,
|
|
@@ -316,6 +337,8 @@ import {
|
|
|
316
337
|
updateTurnCeilingSetting,
|
|
317
338
|
previewBackgroundTaskTimeoutSetting,
|
|
318
339
|
updateBackgroundTaskTimeoutSetting,
|
|
340
|
+
previewTaskStallMinutesSetting,
|
|
341
|
+
updateTaskStallMinutesSetting,
|
|
319
342
|
updateSubagentRecordRetentionSetting,
|
|
320
343
|
} from './settings-modal.js';
|
|
321
344
|
import {
|
|
@@ -371,6 +394,9 @@ import {
|
|
|
371
394
|
resolveChatActionsMenuState,
|
|
372
395
|
} from './chat-actions-menu-state.mjs';
|
|
373
396
|
import { subscribeProviderReadiness, updateProviderReadinessInputs } from './provider-readiness.mjs';
|
|
397
|
+
import { hasActiveShare, refreshShares, subscribeShares } from './share-state.js';
|
|
398
|
+
import { openStopSharingConfirmation } from './shared-conversations-settings.js';
|
|
399
|
+
import { showAbout, syncAboutMenuLabel } from './about-ui.js';
|
|
374
400
|
import { syncConversationOriginHeader } from './conversation-origin-ui.js';
|
|
375
401
|
import { loadRelayEventSubscriptions, onRelayEventSubscriptionsChanged, syncRelayEventHeader } from './relay-event-subscriptions-ui.js';
|
|
376
402
|
import {
|
|
@@ -458,6 +484,8 @@ let liveConversationPollInFlight = false;
|
|
|
458
484
|
let sharedViewerIdFallback = '';
|
|
459
485
|
let sharedConversationRenderKey = '';
|
|
460
486
|
let sharedConversationLastError = '';
|
|
487
|
+
// The share was stopped or renewed while the page was open: the reader stops.
|
|
488
|
+
let sharedConversationEnded = false;
|
|
461
489
|
let networkLifecycleBound = false;
|
|
462
490
|
let foregroundRecoveryTimer = null;
|
|
463
491
|
let foregroundRecoveryInFlight = false;
|
|
@@ -876,14 +904,14 @@ function initNetworkLifecycleHandling() {
|
|
|
876
904
|
}
|
|
877
905
|
|
|
878
906
|
function syncThemeMenuLabel() {
|
|
879
|
-
const button = document.getElementById('chat-menu-
|
|
907
|
+
const button = document.getElementById('chat-menu-theme-toggle');
|
|
880
908
|
if (!button) return;
|
|
881
909
|
const isLight = document.documentElement.getAttribute('data-theme') === 'light';
|
|
882
910
|
button.textContent = isLight ? '🌙 Night mode' : '☀️ Day mode';
|
|
883
911
|
}
|
|
884
912
|
|
|
885
913
|
function initThemeMenuToggle() {
|
|
886
|
-
const themeBtn = document.getElementById('chat-menu-
|
|
914
|
+
const themeBtn = document.getElementById('chat-menu-theme-toggle');
|
|
887
915
|
if (!themeBtn) return;
|
|
888
916
|
themeBtn.hidden = false;
|
|
889
917
|
syncThemeMenuLabel();
|
|
@@ -940,7 +968,7 @@ function applySharedReaderUi() {
|
|
|
940
968
|
const modeSelect = document.getElementById('mode-select');
|
|
941
969
|
const chatMenuBtn = document.getElementById('chat-actions-menu-btn');
|
|
942
970
|
const chatMenu = document.getElementById('chat-actions-menu');
|
|
943
|
-
const
|
|
971
|
+
const themeBtn = document.getElementById('chat-menu-theme-toggle');
|
|
944
972
|
const installBtn = document.getElementById('install-btn');
|
|
945
973
|
const activeElement = document.activeElement;
|
|
946
974
|
if (chatMenu && activeElement instanceof Element && chatMenu.contains(activeElement)) {
|
|
@@ -957,7 +985,7 @@ function applySharedReaderUi() {
|
|
|
957
985
|
if (input) {
|
|
958
986
|
input.readOnly = true;
|
|
959
987
|
input.disabled = true;
|
|
960
|
-
input.placeholder = 'Shared conversation (read-only)';
|
|
988
|
+
input.placeholder = IS_SHARE_PREVIEW ? 'Preview as shared (read-only)' : 'Shared conversation (read-only)';
|
|
961
989
|
}
|
|
962
990
|
disableSharedControl(sendBtn);
|
|
963
991
|
disableSharedControl(attachBtn);
|
|
@@ -973,19 +1001,19 @@ function applySharedReaderUi() {
|
|
|
973
1001
|
// trigger usable). The loop below stays as a guard for entries the resolver
|
|
974
1002
|
// does not know.
|
|
975
1003
|
applyChatActionsMenuState(resolveChatActionsMenuState({ sharedMode: true }));
|
|
976
|
-
if (chatMenu &&
|
|
1004
|
+
if (chatMenu && themeBtn) {
|
|
977
1005
|
for (const child of Array.from(chatMenu.children)) {
|
|
978
|
-
if (child !==
|
|
1006
|
+
if (child !== themeBtn) hideSharedMenuEntry(child);
|
|
979
1007
|
}
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
1008
|
+
themeBtn.hidden = false;
|
|
1009
|
+
themeBtn.removeAttribute('aria-hidden');
|
|
1010
|
+
themeBtn.disabled = false;
|
|
1011
|
+
themeBtn.tabIndex = 0;
|
|
984
1012
|
syncThemeMenuLabel();
|
|
985
1013
|
} else if (chatMenuBtn) {
|
|
986
1014
|
chatMenuBtn.hidden = true;
|
|
987
1015
|
}
|
|
988
|
-
if (chatMenuBtn && !
|
|
1016
|
+
if (chatMenuBtn && !themeBtn) chatMenuBtn.hidden = true;
|
|
989
1017
|
}
|
|
990
1018
|
|
|
991
1019
|
function syncChatTitleWatcherIndicator() {
|
|
@@ -1032,25 +1060,42 @@ function resolveStableScrollTopForLiveRefresh(messagesEl, capturedScrollTop) {
|
|
|
1032
1060
|
function resolveLiveRefreshAtBottom(messagesEl, capturedScrollTop, capturedAtBottom) {
|
|
1033
1061
|
const current = Number(messagesEl?.scrollTop);
|
|
1034
1062
|
if (!Number.isFinite(current) || Math.abs(current - Number(capturedScrollTop)) <= 2) return capturedAtBottom;
|
|
1035
|
-
return
|
|
1063
|
+
return isMessagesNearBottom();
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
// A stopped or renewed share answers 404. The page stops polling at once and
|
|
1067
|
+
// keeps nothing of the conversation on screen; one neutral line stays.
|
|
1068
|
+
function endSharedConversationReader() {
|
|
1069
|
+
if (sharedConversationEnded) return;
|
|
1070
|
+
sharedConversationEnded = true;
|
|
1071
|
+
stopSharedModeTimers();
|
|
1072
|
+
document.getElementById('messages')?.replaceChildren();
|
|
1073
|
+
const titleEl = document.getElementById('chat-title');
|
|
1074
|
+
if (titleEl) titleEl.textContent = 'Shared conversation';
|
|
1075
|
+
sharedConversationLastError = '';
|
|
1076
|
+
setModelBanner('This shared conversation is not available.');
|
|
1036
1077
|
}
|
|
1037
1078
|
|
|
1038
|
-
async function refreshSharedConversation() {
|
|
1039
|
-
if (sharedConversationPollInFlight) return;
|
|
1079
|
+
async function refreshSharedConversation({ open = false } = {}) {
|
|
1080
|
+
if (sharedConversationPollInFlight || sharedConversationEnded) return;
|
|
1040
1081
|
const sharedToken = resolveSharedTokenFromLocation();
|
|
1041
|
-
if (!sharedToken) return;
|
|
1082
|
+
if (!sharedToken && !IS_SHARE_PREVIEW) return;
|
|
1042
1083
|
sharedConversationPollInFlight = true;
|
|
1043
1084
|
const requestSeq = ++sharedConversationRequestSeq;
|
|
1044
1085
|
const messagesEl = document.getElementById('messages');
|
|
1045
|
-
const wasAtBottom =
|
|
1086
|
+
const wasAtBottom = isMessagesNearBottom();
|
|
1046
1087
|
const savedScrollTop = messagesEl?.scrollTop || 0;
|
|
1047
1088
|
try {
|
|
1048
1089
|
// The reader has no socket today, so nothing moves the count; the guard
|
|
1049
1090
|
// keeps a rebuild from dropping a live row should one ever arrive.
|
|
1050
1091
|
const { response, stale } = await loadKeepingLiveRows(
|
|
1051
|
-
() => loadSharedConversation(sharedToken, { limit: 120 }),
|
|
1092
|
+
() => loadSharedConversation(sharedToken, { limit: 120, open }),
|
|
1052
1093
|
getTranscriptLiveRowEpoch,
|
|
1053
1094
|
);
|
|
1095
|
+
if (response?.status === 404) {
|
|
1096
|
+
endSharedConversationReader();
|
|
1097
|
+
return;
|
|
1098
|
+
}
|
|
1054
1099
|
if (requestSeq < sharedConversationAppliedSeq) return;
|
|
1055
1100
|
if (stale) return;
|
|
1056
1101
|
if (!response?.ok) {
|
|
@@ -1101,15 +1146,29 @@ async function refreshSharedConversation() {
|
|
|
1101
1146
|
|
|
1102
1147
|
async function pulseSharedViewerPresence() {
|
|
1103
1148
|
const sharedToken = resolveSharedTokenFromLocation();
|
|
1104
|
-
if (!sharedToken) return;
|
|
1149
|
+
if (!sharedToken || sharedConversationEnded) return;
|
|
1105
1150
|
await reportSharedViewerPresence(sharedToken, sharedViewerId()).catch(() => {});
|
|
1106
1151
|
}
|
|
1107
1152
|
|
|
1153
|
+
// "Preview as shared": the owner's own page, so it says what it is and what
|
|
1154
|
+
// the highlights mean; the marks come with the preview payload.
|
|
1155
|
+
function initSharePreviewUi() {
|
|
1156
|
+
if (!IS_SHARE_PREVIEW) return;
|
|
1157
|
+
document.body.classList.add('share-preview-mode');
|
|
1158
|
+
const banner = document.getElementById('share-preview-banner');
|
|
1159
|
+
if (banner) {
|
|
1160
|
+
banner.textContent = 'Preview as shared: this is what a share link shows. Green marks what viewers get instead of a path; amber marks what still looks like one. Free text is filtered on a best-effort basis.';
|
|
1161
|
+
}
|
|
1162
|
+
watchPreviewMarks(document.body);
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1108
1165
|
async function initSharedConversationReader() {
|
|
1109
1166
|
applySharedReaderUi();
|
|
1110
|
-
|
|
1167
|
+
initSharePreviewUi();
|
|
1168
|
+
await refreshSharedConversation({ open: true });
|
|
1111
1169
|
await pulseSharedViewerPresence();
|
|
1112
1170
|
stopSharedModeTimers();
|
|
1171
|
+
if (sharedConversationEnded) return;
|
|
1113
1172
|
sharedConversationPollTimer = setInterval(() => {
|
|
1114
1173
|
if (!shouldRunForegroundNetworkWork()) return;
|
|
1115
1174
|
void refreshSharedConversation();
|
|
@@ -1126,6 +1185,12 @@ function showAuthError(msg) {
|
|
|
1126
1185
|
|
|
1127
1186
|
function resolveAuthErrorMessage(result = null) {
|
|
1128
1187
|
if (result?.status === 401) return 'Invalid token';
|
|
1188
|
+
if (result?.status === 429) {
|
|
1189
|
+
const seconds = Number(result?.payload?.retryAfterSeconds);
|
|
1190
|
+
return Number.isFinite(seconds) && seconds > 0
|
|
1191
|
+
? `Too many failed sign-in attempts. Try again in ${Math.ceil(seconds)} seconds.`
|
|
1192
|
+
: 'Too many failed sign-in attempts. Try again in a minute.';
|
|
1193
|
+
}
|
|
1129
1194
|
if (result?.status === 403) return 'Access denied';
|
|
1130
1195
|
if (result?.status > 0) return `Authentication failed (${result.status})`;
|
|
1131
1196
|
return String(result?.error || 'Could not reach the relay').trim() || 'Could not reach the relay';
|
|
@@ -3650,6 +3715,23 @@ function transcriptIsInHistory(conversationId) {
|
|
|
3650
3715
|
return !isConversationWindowAtTail() && !hasPendingUserMessageForConversation(conversationId);
|
|
3651
3716
|
}
|
|
3652
3717
|
|
|
3718
|
+
// A reload of the end of the conversation on screen, when the page holds
|
|
3719
|
+
// more rows than the answer can (the relay caps a page at 100): the answer
|
|
3720
|
+
// is merged into the window (mergeReloadIntoWindow) rather than rendered,
|
|
3721
|
+
// which would cut the page and its kept window down to the answer. Null
|
|
3722
|
+
// means the plain render, as before: nothing to merge, or a transcript that
|
|
3723
|
+
// cannot take a merge (a bubble waiting for its send, the status view, a
|
|
3724
|
+
// held selection, a row missing from the page). Both callers merge only an
|
|
3725
|
+
// answer no live row overtook (loadKeepingLiveRows not stale), so the window
|
|
3726
|
+
// predates it: a newer row the answer lacks was deleted (a cancelled
|
|
3727
|
+
// follow-up) and leaves the page.
|
|
3728
|
+
function reloadMergeFor(conversationId, response) {
|
|
3729
|
+
if (IS_SHARED_VIEW) return null;
|
|
3730
|
+
const merge = mergeReloadIntoWindow(conversationPayloadCache.peekWindow(conversationId), response, { windowPredatesPage: true });
|
|
3731
|
+
if (!merge || !canMergeIntoTranscript(merge.mergeFrom, conversationId)) return null;
|
|
3732
|
+
return merge;
|
|
3733
|
+
}
|
|
3734
|
+
|
|
3653
3735
|
async function pollAuthenticatedCurrentConversationLive() {
|
|
3654
3736
|
if (liveConversationPollInFlight) return;
|
|
3655
3737
|
if (isSharedReaderMode()) return;
|
|
@@ -3672,7 +3754,7 @@ async function pollAuthenticatedCurrentConversationLive() {
|
|
|
3672
3754
|
liveConversationPollInFlight = true;
|
|
3673
3755
|
try {
|
|
3674
3756
|
const messagesEl = document.getElementById('messages');
|
|
3675
|
-
const wasAtBottom =
|
|
3757
|
+
const wasAtBottom = isMessagesNearBottom();
|
|
3676
3758
|
const savedScrollTop = messagesEl?.scrollTop || 0;
|
|
3677
3759
|
const savedLoadedCount = loadConversationLoadedMessageCount(currentId);
|
|
3678
3760
|
const requestLimit = Math.max(20, getConversationLoadedMessageCount() || 0, savedLoadedCount || 0);
|
|
@@ -3692,11 +3774,20 @@ async function pollAuthenticatedCurrentConversationLive() {
|
|
|
3692
3774
|
if (stale) return;
|
|
3693
3775
|
const preserveBottom = resolveLiveRefreshAtBottom(messagesEl, savedScrollTop, wasAtBottom);
|
|
3694
3776
|
const stableSavedScrollTop = resolveStableScrollTopForLiveRefresh(messagesEl, savedScrollTop);
|
|
3695
|
-
|
|
3777
|
+
// The rows are rebuilt: a reader who is not at the end keeps the text
|
|
3778
|
+
// they read where it is, whatever the rows above it measure now.
|
|
3779
|
+
const reading = preserveBottom ? null : captureReadingBlock(messagesEl);
|
|
3780
|
+
const merge = reloadMergeFor(currentId, response);
|
|
3781
|
+
applyLoadedConversationState(currentId, merge?.payload || response, {
|
|
3696
3782
|
restoreScroll: !preserveBottom,
|
|
3697
3783
|
savedScrollTop: preserveBottom ? null : stableSavedScrollTop,
|
|
3698
3784
|
followLiveUpdates: preserveBottom,
|
|
3785
|
+
mergeFrom: merge?.mergeFrom || null,
|
|
3699
3786
|
});
|
|
3787
|
+
// A merge does not scroll as the full render does: a reader at the end
|
|
3788
|
+
// is taken to the new end here.
|
|
3789
|
+
if (merge && preserveBottom && messagesEl) messagesEl.scrollTop = messagesEl.scrollHeight;
|
|
3790
|
+
if (reading) restoreReadingBlock(messagesEl, reading);
|
|
3700
3791
|
} finally {
|
|
3701
3792
|
liveConversationPollInFlight = false;
|
|
3702
3793
|
}
|
|
@@ -3713,6 +3804,7 @@ function startLiveConversationPolling() {
|
|
|
3713
3804
|
const transcriptResizeKeeper = createTranscriptResizeKeeper({
|
|
3714
3805
|
getElement: () => document.getElementById('messages'),
|
|
3715
3806
|
});
|
|
3807
|
+
onTranscriptEndLeft(() => transcriptResizeKeeper.recordEndLeft());
|
|
3716
3808
|
|
|
3717
3809
|
function setupViewportTracking() {
|
|
3718
3810
|
syncViewportMetrics();
|
|
@@ -3830,6 +3922,21 @@ function initComposerAttachmentInput() {
|
|
|
3830
3922
|
window.addEventListener('dragend', clearHighlight);
|
|
3831
3923
|
}
|
|
3832
3924
|
|
|
3925
|
+
// The place read is noted once per frame: it measures rows, a scroll event
|
|
3926
|
+
// can come many times a frame. It is noted for the conversation whose rows
|
|
3927
|
+
// are on the page, and only while that is the one open: during a switch the
|
|
3928
|
+
// page still shows the conversation left.
|
|
3929
|
+
let readingAnchorSaveFrame = 0;
|
|
3930
|
+
function scheduleReadingAnchorSave() {
|
|
3931
|
+
if (readingAnchorSaveFrame || IS_SHARED_VIEW) return;
|
|
3932
|
+
readingAnchorSaveFrame = requestAnimationFrame(() => {
|
|
3933
|
+
readingAnchorSaveFrame = 0;
|
|
3934
|
+
const captured = captureConversationReadingAnchor();
|
|
3935
|
+
if (!captured || captured.conversationId !== String(currentConvId || '').trim()) return;
|
|
3936
|
+
saveConversationReadingAnchor(captured.conversationId, captured.anchor);
|
|
3937
|
+
});
|
|
3938
|
+
}
|
|
3939
|
+
|
|
3833
3940
|
function initMessageScrollPersistence() {
|
|
3834
3941
|
const el = document.getElementById('messages');
|
|
3835
3942
|
if (!el || el.dataset.scrollPersistenceBound === '1') return;
|
|
@@ -3839,19 +3946,43 @@ function initMessageScrollPersistence() {
|
|
|
3839
3946
|
const convId = String(currentConvId || '').trim();
|
|
3840
3947
|
if (!convId) return;
|
|
3841
3948
|
saveConversationScrollTop(convId, el.scrollTop);
|
|
3949
|
+
scheduleReadingAnchorSave();
|
|
3842
3950
|
}, { passive: true });
|
|
3843
3951
|
}
|
|
3844
3952
|
|
|
3845
|
-
// The
|
|
3846
|
-
//
|
|
3953
|
+
// The end's answer can predate a row held back below the window while it
|
|
3954
|
+
// was on its way (stored after the request read the conversation), and its
|
|
3955
|
+
// render lets the held rows go. The newest row held is the one to look for:
|
|
3956
|
+
// older ones can lie before a page of the end and are not missing.
|
|
3957
|
+
function endAnswerLacksHeldBackRow(response) {
|
|
3958
|
+
const newestHeld = getHeldBackLiveRowIds().slice(-1);
|
|
3959
|
+
if (!newestHeld.length) return false;
|
|
3960
|
+
return !newerPageReachesEnd({
|
|
3961
|
+
pageHasMore: false,
|
|
3962
|
+
pageMessageIds: (Array.isArray(response?.messages) ? response.messages : []).map((message) => message?.id),
|
|
3963
|
+
heldBackIds: newestHeld,
|
|
3964
|
+
});
|
|
3965
|
+
}
|
|
3966
|
+
|
|
3967
|
+
// The jump-to-bottom button on a window in the middle of the history (and an
|
|
3968
|
+
// own send from one): the end of the conversation replaces it, scrolled to
|
|
3969
|
+
// the bottom.
|
|
3847
3970
|
async function loadCurrentConversationEnd() {
|
|
3848
3971
|
const currentId = String(currentConvId || '').trim();
|
|
3849
3972
|
if (!currentId) return;
|
|
3850
3973
|
const jumpEpoch = getTranscriptJumpEpoch();
|
|
3851
|
-
const
|
|
3974
|
+
const load = () => loadConversation(currentId, { limit: 20 });
|
|
3975
|
+
let response = await load();
|
|
3852
3976
|
if (!response || String(currentConvId || '').trim() !== currentId) return;
|
|
3853
3977
|
// A jump into the history while it was on its way wins.
|
|
3854
3978
|
if (getTranscriptJumpEpoch() !== jumpEpoch) return;
|
|
3979
|
+
// Asked once more, not until it has them: a held row deleted since would
|
|
3980
|
+
// never come.
|
|
3981
|
+
if (endAnswerLacksHeldBackRow(response)) {
|
|
3982
|
+
const again = await load();
|
|
3983
|
+
if (String(currentConvId || '').trim() !== currentId || getTranscriptJumpEpoch() !== jumpEpoch) return;
|
|
3984
|
+
if (again) response = again;
|
|
3985
|
+
}
|
|
3855
3986
|
applyLoadedConversationState(currentId, response, { restoreScroll: false });
|
|
3856
3987
|
}
|
|
3857
3988
|
|
|
@@ -3913,7 +4044,7 @@ async function refreshCurrentView() {
|
|
|
3913
4044
|
const capturedVersion = ++refreshViewVersion;
|
|
3914
4045
|
const jumpEpoch = getTranscriptJumpEpoch();
|
|
3915
4046
|
const messagesEl = document.getElementById('messages');
|
|
3916
|
-
const wasAtBottom =
|
|
4047
|
+
const wasAtBottom = isMessagesNearBottom();
|
|
3917
4048
|
const scrollTop = messagesEl?.scrollTop || 0;
|
|
3918
4049
|
|
|
3919
4050
|
await refreshConversations();
|
|
@@ -3943,13 +4074,23 @@ async function refreshCurrentView() {
|
|
|
3943
4074
|
const keepTranscript = getTranscriptJumpEpoch() !== jumpEpoch || transcriptIsInHistory(currentId) || stale;
|
|
3944
4075
|
const preserveBottom = resolveLiveRefreshAtBottom(messagesEl, scrollTop, wasAtBottom);
|
|
3945
4076
|
const stableSavedScrollTop = resolveStableScrollTopForLiveRefresh(messagesEl, scrollTop);
|
|
4077
|
+
// As in the live poll: the text the reader is at stays where it is.
|
|
4078
|
+
const reading = preserveBottom || keepTranscript ? null : captureReadingBlock(messagesEl);
|
|
4079
|
+
let restoredAt = null;
|
|
3946
4080
|
if (r) {
|
|
3947
|
-
|
|
4081
|
+
const merge = keepTranscript ? null : reloadMergeFor(currentId, r);
|
|
4082
|
+
applyLoadedConversationState(currentId, merge?.payload || r, {
|
|
3948
4083
|
restoreScroll: !preserveBottom,
|
|
3949
4084
|
savedScrollTop: preserveBottom ? null : stableSavedScrollTop,
|
|
3950
4085
|
followLiveUpdates: preserveBottom && !keepTranscript,
|
|
3951
4086
|
keepTranscript,
|
|
4087
|
+
mergeFrom: merge?.mergeFrom || null,
|
|
3952
4088
|
});
|
|
4089
|
+
// As in the live poll: a merge leaves the scrolling to the caller.
|
|
4090
|
+
if (merge && preserveBottom && messagesEl) messagesEl.scrollTop = messagesEl.scrollHeight;
|
|
4091
|
+
// At once, and again below once the cards are back on the page.
|
|
4092
|
+
restoreReadingBlock(messagesEl, reading);
|
|
4093
|
+
restoredAt = { scrollTop: messagesEl?.scrollTop };
|
|
3953
4094
|
} else {
|
|
3954
4095
|
setRepoBrowserSessionInfo('', '');
|
|
3955
4096
|
restoreInFlightThinking(null);
|
|
@@ -3958,7 +4099,12 @@ async function refreshCurrentView() {
|
|
|
3958
4099
|
await loadRelayBoards();
|
|
3959
4100
|
scheduleContextUsageRefresh(currentId, 0);
|
|
3960
4101
|
if (messagesEl && !keepTranscript && String(currentConvId || '').trim() === currentId) {
|
|
3961
|
-
|
|
4102
|
+
// Two requests later: a reader who has scrolled, pressed the jump
|
|
4103
|
+
// button or sent something meanwhile is where they want to be.
|
|
4104
|
+
const readerMoved = !restoredAt
|
|
4105
|
+
|| Math.abs(Number(messagesEl.scrollTop) - Number(restoredAt.scrollTop)) > 2;
|
|
4106
|
+
if (!preserveBottom && !readerMoved && !restoreReadingBlock(messagesEl, reading)
|
|
4107
|
+
&& Number.isFinite(stableSavedScrollTop) && stableSavedScrollTop >= 0) {
|
|
3962
4108
|
messagesEl.scrollTop = stableSavedScrollTop;
|
|
3963
4109
|
}
|
|
3964
4110
|
saveConversationScrollTop(currentId, messagesEl.scrollTop);
|
|
@@ -4078,11 +4224,15 @@ function syncChatActionsMenuState() {
|
|
|
4078
4224
|
cloudSessionUrl: isCloud ? buildCloudLineModel(conversation?.cloud)?.sessionUrl || null : null,
|
|
4079
4225
|
editing: !!convId && chatTitleEditingConversationId === convId,
|
|
4080
4226
|
suspendVisible: isSuspendHostActionVisible(),
|
|
4227
|
+
hasActiveShare: hasActiveShare(convId),
|
|
4081
4228
|
});
|
|
4082
4229
|
applyChatActionsMenuState(state);
|
|
4083
4230
|
return state;
|
|
4084
4231
|
}
|
|
4085
4232
|
|
|
4233
|
+
// The menu follows the relay's share list (made, renewed, stopped anywhere).
|
|
4234
|
+
subscribeShares(() => syncChatActionsMenuState());
|
|
4235
|
+
|
|
4086
4236
|
function toggleChatActionsMenu() {
|
|
4087
4237
|
const menu = document.getElementById('chat-actions-menu');
|
|
4088
4238
|
const trigger = document.getElementById('chat-actions-menu-btn');
|
|
@@ -4092,6 +4242,7 @@ function toggleChatActionsMenu() {
|
|
|
4092
4242
|
if (trigger.hidden || trigger.disabled) return;
|
|
4093
4243
|
syncRefreshHistoryMenuState();
|
|
4094
4244
|
syncThemeMenuLabel();
|
|
4245
|
+
syncAboutMenuLabel();
|
|
4095
4246
|
const willOpen = !!menu.hidden;
|
|
4096
4247
|
menu.hidden = !willOpen;
|
|
4097
4248
|
trigger.setAttribute('aria-expanded', willOpen ? 'true' : 'false');
|
|
@@ -4170,13 +4321,17 @@ function lockChatActionsMenuShield(ms = 300) {
|
|
|
4170
4321
|
function syncChatTitleControls() {
|
|
4171
4322
|
const { title, editor, input } = getChatTitleElements();
|
|
4172
4323
|
const convId = String(currentConvId || '').trim();
|
|
4173
|
-
// No conversation open (deleted, new chat): its 📍
|
|
4174
|
-
if (!convId)
|
|
4324
|
+
// No conversation open (deleted, new chat): its 📍 and 🙈 buttons go with it.
|
|
4325
|
+
if (!convId) {
|
|
4326
|
+
setPinsConversation(null);
|
|
4327
|
+
setHiddenMessagesConversation(null);
|
|
4328
|
+
}
|
|
4175
4329
|
const conversation = convId ? (conversations[convId] || null) : null;
|
|
4176
4330
|
const sdkSessionId = String(conversation?.sdkSessionId || '').trim();
|
|
4177
4331
|
if (title) {
|
|
4178
4332
|
if (!convId || !conversation) {
|
|
4179
4333
|
delete title.dataset.uiState;
|
|
4334
|
+
delete title.dataset.working;
|
|
4180
4335
|
} else {
|
|
4181
4336
|
const pendingByConversation = getPendingQuestionCountsByConversation();
|
|
4182
4337
|
const pendingCount = Number(pendingByConversation[convId] || 0);
|
|
@@ -4187,6 +4342,8 @@ function syncChatTitleControls() {
|
|
|
4187
4342
|
hasPendingQuestion: pendingCount > 0,
|
|
4188
4343
|
});
|
|
4189
4344
|
title.dataset.uiState = uiState;
|
|
4345
|
+
// Bright green while it works, the darker green while idle, as its row.
|
|
4346
|
+
title.toggleAttribute('data-working', isConversationWorking(conversation));
|
|
4190
4347
|
}
|
|
4191
4348
|
}
|
|
4192
4349
|
if (chatTitleEditingConversationId && chatTitleEditingConversationId !== convId) {
|
|
@@ -4429,19 +4586,20 @@ async function initApp() {
|
|
|
4429
4586
|
appSharedMode = sharedMode;
|
|
4430
4587
|
initNetworkLifecycleHandling();
|
|
4431
4588
|
setForegroundNetworkWorkEnabled(document.visibilityState === 'visible');
|
|
4432
|
-
const sharedThemeBtn = document.getElementById('chat-menu-shared-theme-toggle');
|
|
4433
4589
|
initTheme();
|
|
4434
4590
|
initScrollbarHover();
|
|
4435
4591
|
initThemeMenuToggle();
|
|
4436
|
-
if (sharedThemeBtn && sharedMode) sharedThemeBtn.hidden = false;
|
|
4437
4592
|
initFontScaling();
|
|
4438
4593
|
if (!sharedMode) {
|
|
4439
4594
|
clearLegacyKnownCwdHistoryStorage();
|
|
4440
4595
|
}
|
|
4441
4596
|
syncPwaVersionMenuEntry();
|
|
4442
4597
|
syncQueueStatusMenuEntry();
|
|
4598
|
+
if (!sharedMode) syncAboutMenuLabel();
|
|
4443
4599
|
if (!sharedMode) {
|
|
4444
4600
|
syncSuspendHostVisibility();
|
|
4601
|
+
syncScrollOnSendToggle();
|
|
4602
|
+
syncWindowRetentionSlider();
|
|
4445
4603
|
initHostSuspendUi();
|
|
4446
4604
|
initUsageLimitUi();
|
|
4447
4605
|
}
|
|
@@ -4506,6 +4664,16 @@ async function initApp() {
|
|
|
4506
4664
|
});
|
|
4507
4665
|
});
|
|
4508
4666
|
}
|
|
4667
|
+
// "Session group…": the picker for the open conversation, reachable on
|
|
4668
|
+
// the phone with the drawer closed.
|
|
4669
|
+
bindMenuAction(document.getElementById('chat-menu-group'), (event) => {
|
|
4670
|
+
event.preventDefault();
|
|
4671
|
+
event.stopPropagation();
|
|
4672
|
+
lockChatActionsMenuShield(350);
|
|
4673
|
+
closeChatActionsMenu();
|
|
4674
|
+
const convId = String(currentConvId || '').trim();
|
|
4675
|
+
if (convId) openConversationGroupPicker(convId);
|
|
4676
|
+
});
|
|
4509
4677
|
const chatMenuArchiveBtn = document.getElementById('chat-menu-archive');
|
|
4510
4678
|
bindMenuAction(chatMenuArchiveBtn, (event) => {
|
|
4511
4679
|
event.preventDefault();
|
|
@@ -4536,10 +4704,35 @@ async function initApp() {
|
|
|
4536
4704
|
}
|
|
4537
4705
|
const copied = await copyTextToClipboard(shareUrl);
|
|
4538
4706
|
showTransientRelayNotice(copied ? 'Share link copied to clipboard.' : shareUrl);
|
|
4707
|
+
void refreshShares().catch(() => {});
|
|
4539
4708
|
}).catch((error) => {
|
|
4540
4709
|
showTransientRelayNotice(error?.message || 'Could not create share link.');
|
|
4541
4710
|
});
|
|
4542
4711
|
});
|
|
4712
|
+
// "Stop sharing": only shown while the open conversation has a live link.
|
|
4713
|
+
bindMenuAction(document.getElementById('chat-menu-stop-sharing'), (event) => {
|
|
4714
|
+
event.preventDefault();
|
|
4715
|
+
event.stopPropagation();
|
|
4716
|
+
lockChatActionsMenuShield(350);
|
|
4717
|
+
closeChatActionsMenu();
|
|
4718
|
+
const convId = String(currentConvId || '').trim();
|
|
4719
|
+
if (!convId) return;
|
|
4720
|
+
openStopSharingConfirmation(convId, conversations[convId]?.title || '');
|
|
4721
|
+
});
|
|
4722
|
+
const chatMenuPreviewSharedBtn = document.getElementById('chat-menu-preview-shared');
|
|
4723
|
+
bindMenuAction(chatMenuPreviewSharedBtn, (event) => {
|
|
4724
|
+
event.preventDefault();
|
|
4725
|
+
event.stopPropagation();
|
|
4726
|
+
lockChatActionsMenuShield(350);
|
|
4727
|
+
closeChatActionsMenu();
|
|
4728
|
+
const convId = String(currentConvId || '').trim();
|
|
4729
|
+
if (!convId) {
|
|
4730
|
+
showTransientRelayNotice('Select a conversation first.');
|
|
4731
|
+
return;
|
|
4732
|
+
}
|
|
4733
|
+
// Never creates a share link: the preview reads through the owner's route.
|
|
4734
|
+
window.open(`${BASE}/share-preview/${encodeURIComponent(convId)}`, '_blank', 'noopener');
|
|
4735
|
+
});
|
|
4543
4736
|
const chatMenuRefreshHistoryBtn = document.getElementById('chat-menu-refresh-history');
|
|
4544
4737
|
if (chatMenuRefreshHistoryBtn && !chatMenuRefreshHistoryBtn.hidden && !chatMenuRefreshHistoryBtn.disabled) {
|
|
4545
4738
|
bindMenuAction(chatMenuRefreshHistoryBtn, (event) => {
|
|
@@ -4592,6 +4785,14 @@ async function initApp() {
|
|
|
4592
4785
|
closeChatActionsMenu();
|
|
4593
4786
|
openSettingsModal();
|
|
4594
4787
|
});
|
|
4788
|
+
const chatMenuAboutBtn = document.getElementById('chat-menu-about');
|
|
4789
|
+
bindMenuAction(chatMenuAboutBtn, (event) => {
|
|
4790
|
+
event.preventDefault();
|
|
4791
|
+
event.stopPropagation();
|
|
4792
|
+
lockChatActionsMenuShield(350);
|
|
4793
|
+
closeChatActionsMenu();
|
|
4794
|
+
void showAbout();
|
|
4795
|
+
});
|
|
4595
4796
|
const chatMenuRestartRelayBtn = document.getElementById('chat-menu-restart-relay');
|
|
4596
4797
|
bindMenuAction(chatMenuRestartRelayBtn, (event) => {
|
|
4597
4798
|
event.preventDefault();
|
|
@@ -4669,6 +4870,7 @@ async function initApp() {
|
|
|
4669
4870
|
initBubbleActionHandlers();
|
|
4670
4871
|
initMessageScrollPersistence();
|
|
4671
4872
|
initTranscriptJumpToBottom();
|
|
4873
|
+
initSubagentViewer();
|
|
4672
4874
|
syncChatTitleControls();
|
|
4673
4875
|
await initSharedConversationReader();
|
|
4674
4876
|
return;
|
|
@@ -4727,11 +4929,13 @@ async function initApp() {
|
|
|
4727
4929
|
initComposerAttachmentInput();
|
|
4728
4930
|
initMessageSearchView({ openConversation });
|
|
4729
4931
|
initSubagentViewer();
|
|
4932
|
+
initTaskConsole();
|
|
4730
4933
|
initPinnedMessagesView({
|
|
4731
4934
|
openConversation,
|
|
4732
4935
|
focusMessage: focusConversationMessageById,
|
|
4733
4936
|
syncRenderedPinState,
|
|
4734
4937
|
});
|
|
4938
|
+
initHiddenMessagesView({ syncRenderedShareVisibility });
|
|
4735
4939
|
initGitChangesView();
|
|
4736
4940
|
syncChatTitleControls();
|
|
4737
4941
|
// A provider turned on or off (a Settings save, a catalog change) redraws
|
|
@@ -4795,7 +4999,9 @@ async function doAuth() {
|
|
|
4795
4999
|
async function bootstrap() {
|
|
4796
5000
|
if (ensureTrailingSlashPath()) return;
|
|
4797
5001
|
const sharedMode = isSharedReaderMode();
|
|
4798
|
-
|
|
5002
|
+
// The preview runs in the owner's own browser: their app's service worker
|
|
5003
|
+
// (and its push subscription) stays.
|
|
5004
|
+
if (sharedMode && !IS_SHARE_PREVIEW && navigator.serviceWorker?.getRegistrations) {
|
|
4799
5005
|
navigator.serviceWorker.getRegistrations()
|
|
4800
5006
|
.then((registrations) => Promise.all(registrations.map((registration) => registration.unregister().catch(() => false))))
|
|
4801
5007
|
.catch(() => {});
|
|
@@ -4882,11 +5088,20 @@ window.saveGrokAllowanceSettings = saveGrokAllowanceSettings;
|
|
|
4882
5088
|
window.resetGrokAllowanceAccounting = resetGrokAllowanceAccounting;
|
|
4883
5089
|
window.toggleCursorProvider = toggleCursorProvider;
|
|
4884
5090
|
window.updateShowSuspendHostSetting = updateShowSuspendHostSetting;
|
|
5091
|
+
window.updateScrollOnSendSetting = (enabled) => updateScrollOnSendSetting(enabled);
|
|
5092
|
+
window.previewConversationWindowRetention = (position) => previewWindowRetention(position);
|
|
5093
|
+
// A lower cap cuts the kept windows down now; the open conversation's is cut
|
|
5094
|
+
// when it is left.
|
|
5095
|
+
window.updateConversationWindowRetention = (position) => updateWindowRetention(position, {
|
|
5096
|
+
onChange: (cap) => conversationPayloadCache.trimAll(cap, loadConversationReadingAnchor, { except: currentConvId }),
|
|
5097
|
+
});
|
|
4885
5098
|
window.updateWindowsAutostartMode = updateWindowsAutostartMode;
|
|
4886
5099
|
window.previewTurnCeilingSetting = previewTurnCeilingSetting;
|
|
4887
5100
|
window.updateTurnCeilingSetting = updateTurnCeilingSetting;
|
|
4888
5101
|
window.previewBackgroundTaskTimeoutSetting = previewBackgroundTaskTimeoutSetting;
|
|
4889
5102
|
window.updateBackgroundTaskTimeoutSetting = updateBackgroundTaskTimeoutSetting;
|
|
5103
|
+
window.previewTaskStallMinutesSetting = previewTaskStallMinutesSetting;
|
|
5104
|
+
window.updateTaskStallMinutesSetting = updateTaskStallMinutesSetting;
|
|
4890
5105
|
window.updateSubagentRecordRetentionSetting = updateSubagentRecordRetentionSetting;
|
|
4891
5106
|
window.togglePushOnThisDevice = togglePushOnThisDevice;
|
|
4892
5107
|
window.updatePushPreferencesFromControls = updatePushPreferencesFromControls;
|
|
@@ -4936,6 +5151,8 @@ window.confirmNewConversationModel = confirmNewConversationModel;
|
|
|
4936
5151
|
window.closeNewConversationModelModal = closeNewConversationModelModal;
|
|
4937
5152
|
window.deleteConv = deleteConv;
|
|
4938
5153
|
window.archiveConv = archiveConv;
|
|
5154
|
+
window.openConversationGroupPicker = openConversationGroupPicker;
|
|
5155
|
+
window.toggleConversationGroupFold = toggleConversationGroupFold;
|
|
4939
5156
|
window.unarchiveConv = unarchiveConv;
|
|
4940
5157
|
window.toggleArchivedConversations = toggleArchivedConversations;
|
|
4941
5158
|
window.refreshNewConversationCloudRepoList = refreshNewConversationCloudRepoList;
|
|
@@ -4977,7 +5194,6 @@ window.setRelayQuestionMultiMode = setRelayQuestionMultiMode;
|
|
|
4977
5194
|
window.handleRelayQuestionKey = handleRelayQuestionKey;
|
|
4978
5195
|
window.openPendingQuestionFromBanner = openPendingQuestionFromBanner;
|
|
4979
5196
|
window.submitRelayBoardAction = submitRelayBoardAction;
|
|
4980
|
-
window.syncAgentExchangeFolds = syncAgentExchangeFoldsNow;
|
|
4981
5197
|
window.compactCurrentConversation = compactCurrentConversation;
|
|
4982
5198
|
window.sendMessage = sendMessage;
|
|
4983
5199
|
window.syncComposerButtonState = syncComposerButtonState;
|