@oar-sh/oar 0.9.21 โ 0.9.22
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 +113 -0
- package/README.md +9 -9
- package/package.json +1 -1
- package/server/claude-cloud-worker/claude-cloud-session-worker.mjs +9 -1
- package/server/claude-worker/claude-sdk-adapter.mjs +149 -4
- package/server/claude-worker/claude-session-process.mjs +9 -4
- package/server/claude-worker/claude-session-worker-main.mjs +10 -1
- package/server/copilot-worker/copilot-sdk-session-process.mjs +1 -0
- package/server/copilot-worker/copilot-sdk-session-worker.mjs +10 -1
- package/server/cursor-worker/cursor-session-worker.mjs +9 -1
- package/server/db-schema.mjs +19 -0
- package/server/grok-worker/grok-background-tasks.mjs +1 -0
- package/server/grok-worker/grok-session-worker.mjs +10 -1
- package/server/grok-worker/grok-turn-runner.mjs +1 -0
- package/server/migrations/0037-relay-asks-queue-id.mjs +66 -0
- package/server/migrations/0038-turn-seq.mjs +92 -0
- package/server/public/app/activity-replay-state.mjs +160 -42
- package/server/public/app/api-client.js +7 -0
- package/server/public/app/ask-user-view.js +4 -3
- package/server/public/app/bootstrap.js +62 -4
- package/server/public/app/chat-actions-menu-state.mjs +2 -0
- package/server/public/app/context-usage-view.mjs +68 -0
- package/server/public/app/conversation-view.js +306 -49
- package/server/public/app/header-priority-actions.mjs +5 -1
- package/server/public/app/held-details.mjs +202 -0
- package/server/public/app/inbox-ui.js +70 -79
- package/server/public/app/inbox-view.mjs +35 -19
- package/server/public/app/journal-view.js +17 -0
- package/server/public/app/live-activity-cap.mjs +103 -0
- package/server/public/app/questions-panel-ui.js +634 -0
- package/server/public/app/questions-stub-view.mjs +40 -0
- package/server/public/app/questions-view.mjs +89 -0
- package/server/public/app/relay-board-view.js +12 -2
- package/server/public/app/relay-thoughts.mjs +15 -1
- package/server/public/app/session-history-ui.js +1 -0
- package/server/public/app/settings-modal.js +6 -0
- package/server/public/app/socket-handlers.js +42 -11
- package/server/public/app/stream-view-preference.mjs +60 -0
- package/server/public/app/subagent-fold.mjs +6 -3
- package/server/public/app/subagent-timeline-view.mjs +12 -5
- package/server/public/app/subagent-viewer.mjs +8 -6
- package/server/public/app/thoughts-preference.mjs +79 -0
- package/server/public/app/turn-stream-view.mjs +124 -0
- package/server/public/index.html +391 -214
- package/server/repositories/message-repository.mjs +10 -0
- package/server/repositories/question-repository.mjs +51 -7
- package/server/repositories/relay-turn-rows.mjs +56 -0
- package/server/routes/ask-user-routes.mjs +34 -5
- package/server/routes/messages-routes.mjs +35 -14
- package/server/routes/relay-ask-routes.mjs +15 -5
- package/server/routes/sessions-routes.mjs +76 -5
- package/server/server-runtime.mjs +178 -131
- package/server/services/cloudflared-tunnel-service.mjs +7 -0
- package/server/services/idle-timers.mjs +184 -0
- package/server/services/queue-wake-hook.mjs +145 -0
- package/server/services/queue-waker.mjs +127 -0
- package/server/services/relay-ask-service.mjs +64 -5
- package/server/services/relay-event-service.mjs +4 -1
- package/server/services/remote-relay-dispatcher.mjs +23 -4
- package/server/services/session-history-refresh-service.mjs +4 -0
- package/server/services/session-worker-supervisor-service.mjs +36 -0
- package/server/services/session-worker-websocket-service.mjs +143 -15
- package/server/services/ssh-tunnel-manager-service.mjs +7 -0
- package/shared/ask-user-bridge.mjs +9 -6
- package/shared/relay-asks.mjs +10 -0
- package/shared/relay-events.mjs +1 -0
- package/shared/worker-runtime/heartbeat.mjs +26 -0
- package/shared/worker-runtime/worker-keepalive.mjs +108 -0
- package/shared/worker-runtime/worker-websocket-link.mjs +21 -10
|
@@ -63,6 +63,9 @@ import {
|
|
|
63
63
|
} from './transcript-viewport-anchor.mjs';
|
|
64
64
|
import { initTranscriptJumpToBottom, onTranscriptEndLeft } from './transcript-jump-bottom-view.mjs';
|
|
65
65
|
import { syncScrollOnSendToggle, updateScrollOnSendSetting } from './transcript-jump-bottom.mjs';
|
|
66
|
+
import { isThoughtsExpanded, syncThoughtsExpandedToggle, updateThoughtsExpandedSetting } from './thoughts-preference.mjs';
|
|
67
|
+
import { previewLiveActivityCap, syncLiveActivityCapSlider, updateLiveActivityCap } from './live-activity-cap.mjs';
|
|
68
|
+
import { STREAM_VIEW_CHANGE_EVENT, isStreamViewEnabled, syncStreamViewToggle, updateStreamViewSetting } from './stream-view-preference.mjs';
|
|
66
69
|
import { previewWindowRetention, syncWindowRetentionSlider, updateWindowRetention } from './conversation-window-retention.mjs';
|
|
67
70
|
import { conversationPayloadCache, mergeReloadIntoWindow } from './conversation-payload-cache.mjs';
|
|
68
71
|
import {
|
|
@@ -93,7 +96,7 @@ import {
|
|
|
93
96
|
reportSharedViewerPresence,
|
|
94
97
|
setNetworkRequestsEnabled,
|
|
95
98
|
} from './api-client.js';
|
|
96
|
-
import { loadConversations, refreshConversations, openConversation, renderConvList, applyLoadedConversationState, initConversationListLazyLoading, initConversationFilter } from './journal-view.js';
|
|
99
|
+
import { loadConversations, refreshConversations, openConversation, renderConvList, applyLoadedConversationState, heldDetailsStore, initConversationListLazyLoading, initConversationFilter } from './journal-view.js';
|
|
97
100
|
import {
|
|
98
101
|
newConversation,
|
|
99
102
|
confirmNewConversationModel,
|
|
@@ -150,6 +153,7 @@ import {
|
|
|
150
153
|
getHeldBackLiveRowIds,
|
|
151
154
|
captureConversationReadingAnchor,
|
|
152
155
|
canMergeIntoTranscript,
|
|
156
|
+
refreshStreamViewLayout,
|
|
153
157
|
} from './conversation-view.js';
|
|
154
158
|
import { loadKeepingLiveRows } from './transcript-reload-freshness.mjs';
|
|
155
159
|
import { backgroundTasksSnapshotMark } from './background-tasks-view.mjs';
|
|
@@ -250,6 +254,8 @@ import { closeAnnotateEditor } from './annotate-editor.js';
|
|
|
250
254
|
import { closeEmojiPicker } from './emoji-view.js';
|
|
251
255
|
import { closeChangeCwdMenu } from './cwd-picker.js';
|
|
252
256
|
import { closeRelayWatchPanel } from './relay-event-subscriptions-ui.js';
|
|
257
|
+
import { closeQuestionsPanel, initQuestionsPanel, openQuestionsPanel, setQuestionsConversation } from './questions-panel-ui.js';
|
|
258
|
+
import { questionsTwinLabel } from './questions-view.mjs';
|
|
253
259
|
import { closeRelaySchedulePanel } from './relay-schedules-ui.js';
|
|
254
260
|
import { isInboxViewActive } from './inbox-ui.js';
|
|
255
261
|
import { initScrollbarHover } from './scrollbar-hover.mjs';
|
|
@@ -261,6 +267,8 @@ import {
|
|
|
261
267
|
renderAutoCompactNotesHtml,
|
|
262
268
|
renderContextUsageHtml,
|
|
263
269
|
renderThinkingControlHtml,
|
|
270
|
+
renderThoughtsPreferenceHtml,
|
|
271
|
+
renderStreamViewPreferenceHtml,
|
|
264
272
|
thinkingEnabledFromKey,
|
|
265
273
|
thinkingEnabledToKey,
|
|
266
274
|
} from './context-usage-view.mjs';
|
|
@@ -3702,6 +3710,18 @@ function initThinkingControl() {
|
|
|
3702
3710
|
if (!body || body.dataset.thinkingBound === '1') return;
|
|
3703
3711
|
body.dataset.thinkingBound = '1';
|
|
3704
3712
|
body.addEventListener('click', (event) => {
|
|
3713
|
+
// ๐งต Stream view and ๐ญ Thoughts: settings of this browser, nothing goes
|
|
3714
|
+
// to the relay. (The stream view's buttons share the thoughts' look.)
|
|
3715
|
+
const streamViewBtn = event.target?.closest?.('.ctx-stream-view-btn');
|
|
3716
|
+
if (streamViewBtn && body.contains(streamViewBtn)) {
|
|
3717
|
+
updateStreamViewSetting(streamViewBtn.dataset.streamView === 'on');
|
|
3718
|
+
return;
|
|
3719
|
+
}
|
|
3720
|
+
const thoughtsBtn = event.target?.closest?.('.ctx-thoughts-btn[data-thoughts-expanded]');
|
|
3721
|
+
if (thoughtsBtn && body.contains(thoughtsBtn)) {
|
|
3722
|
+
updateThoughtsExpandedSetting(thoughtsBtn.dataset.thoughtsExpanded !== 'off');
|
|
3723
|
+
return;
|
|
3724
|
+
}
|
|
3705
3725
|
const btn = event.target?.closest?.('.ctx-thinking-btn');
|
|
3706
3726
|
if (!btn || !body.contains(btn)) return;
|
|
3707
3727
|
void applyThinkingSelection(btn.dataset.thinkingAxis, btn.dataset.thinkingValue);
|
|
@@ -3861,6 +3881,10 @@ async function loadContextSummaryAndRender(convId) {
|
|
|
3861
3881
|
thinkingDisplay: payload.thinkingDisplay ?? null,
|
|
3862
3882
|
})
|
|
3863
3883
|
: '';
|
|
3884
|
+
// Every provider streams thoughts somewhere; the row mirrors the Settings
|
|
3885
|
+
// toggle and is this browser's own.
|
|
3886
|
+
const thoughtsHtml = renderThoughtsPreferenceHtml({ expanded: isThoughtsExpanded() })
|
|
3887
|
+
+ renderStreamViewPreferenceHtml({ on: isStreamViewEnabled() });
|
|
3864
3888
|
// What this folder's commits say, with the folder override: Claude (on
|
|
3865
3889
|
// either billing), and a Claude Cloud chat that was started from a folder
|
|
3866
3890
|
// (no path, no row).
|
|
@@ -3885,10 +3909,10 @@ async function loadContextSummaryAndRender(convId) {
|
|
|
3885
3909
|
// The structured breakdown is the answer; the runtime's own text dump stays
|
|
3886
3910
|
// available underneath for the details it carries that categories don't.
|
|
3887
3911
|
const bodyHtml = usageHtml
|
|
3888
|
-
? `${usageHtml}${autoCompactHtml}${thinkingHtml}${attributionHtml}${agentSessionsHtml}${detailText
|
|
3912
|
+
? `${usageHtml}${autoCompactHtml}${thinkingHtml}${thoughtsHtml}${attributionHtml}${agentSessionsHtml}${detailText
|
|
3889
3913
|
? `<details class="ctx-usage-raw"><summary>Raw details</summary><pre>${escHtml(detailText)}</pre></details>`
|
|
3890
3914
|
: ''}`
|
|
3891
|
-
: `${autoCompactHtml}${thinkingHtml}${attributionHtml}${agentSessionsHtml}<pre>${escHtml(detailText || 'No context data available.')}</pre>`;
|
|
3915
|
+
: `${autoCompactHtml}${thinkingHtml}${thoughtsHtml}${attributionHtml}${agentSessionsHtml}<pre>${escHtml(detailText || 'No context data available.')}</pre>`;
|
|
3892
3916
|
|
|
3893
3917
|
renderSummaryModalContent({
|
|
3894
3918
|
title: 'Context usage',
|
|
@@ -4130,12 +4154,17 @@ async function pollAuthenticatedCurrentConversationLive() {
|
|
|
4130
4154
|
const requestLimit = Math.max(20, getConversationLoadedMessageCount() || 0, savedLoadedCount || 0);
|
|
4131
4155
|
const tasksMark = backgroundTasksSnapshotMark(currentId);
|
|
4132
4156
|
const jumpEpoch = getTranscriptJumpEpoch();
|
|
4157
|
+
// The finished replies this page already holds go out without their
|
|
4158
|
+
// tool lines and thoughts (held-details.mjs); the answer gets them back
|
|
4159
|
+
// from the store, or is dropped if it names one the store lacks.
|
|
4160
|
+
const heldDetails = heldDetailsStore.param(currentId);
|
|
4133
4161
|
const { response, stale } = await loadKeepingLiveRows(
|
|
4134
|
-
() => loadConversation(currentId, { limit: requestLimit }),
|
|
4162
|
+
() => loadConversation(currentId, { limit: requestLimit, heldDetails }),
|
|
4135
4163
|
getTranscriptLiveRowEpoch,
|
|
4136
4164
|
);
|
|
4137
4165
|
if (!response) return;
|
|
4138
4166
|
if (String(currentConvId || '').trim() !== currentId) return;
|
|
4167
|
+
if (!heldDetailsStore.restore(currentId, response)) return;
|
|
4139
4168
|
// The user jumped into the history while this request was on its way: the
|
|
4140
4169
|
// answer is the end of the conversation as it was asked for before that,
|
|
4141
4170
|
// and would overwrite the window they jumped to. The next round asks again.
|
|
@@ -4155,6 +4184,7 @@ async function pollAuthenticatedCurrentConversationLive() {
|
|
|
4155
4184
|
followLiveUpdates: preserveBottom,
|
|
4156
4185
|
mergeFrom: merge?.mergeFrom || null,
|
|
4157
4186
|
backgroundTasksAsOf: tasksMark,
|
|
4187
|
+
fromPoll: true,
|
|
4158
4188
|
});
|
|
4159
4189
|
// A merge does not scroll as the full render does: a reader at the end
|
|
4160
4190
|
// is taken to the new end here.
|
|
@@ -4600,6 +4630,7 @@ function headerPriorityActionAvailability() {
|
|
|
4600
4630
|
const fullscreen = document.getElementById('fullscreen-btn');
|
|
4601
4631
|
return resolveHeaderActionAvailability({
|
|
4602
4632
|
sharedMode: isSharedReaderMode(),
|
|
4633
|
+
questionCount: Number(document.getElementById('questions-btn')?.dataset.count || 0),
|
|
4603
4634
|
pinCount: Number(pins?.dataset.count || 0),
|
|
4604
4635
|
hiddenCount: Number(hiddenBtn?.dataset.count || 0),
|
|
4605
4636
|
fullscreenAvailable: fullscreen?.dataset.headerAvailable === '1',
|
|
@@ -4676,6 +4707,8 @@ function headerFitSignature(header, availability) {
|
|
|
4676
4707
|
banner?.className, banner?.hidden, banner?.textContent,
|
|
4677
4708
|
document.getElementById('sidebar-toggle')?.hidden,
|
|
4678
4709
|
document.getElementById('chat-actions-menu-btn')?.hidden,
|
|
4710
|
+
document.getElementById('questions-btn')?.dataset.count,
|
|
4711
|
+
document.getElementById('questions-btn')?.dataset.urgent,
|
|
4679
4712
|
document.getElementById('pinned-messages-btn')?.dataset.count,
|
|
4680
4713
|
document.getElementById('hidden-messages-btn')?.dataset.count,
|
|
4681
4714
|
document.getElementById('fullscreen-btn')?.textContent,
|
|
@@ -4782,6 +4815,14 @@ function syncHeaderPriorityTwinLabels() {
|
|
|
4782
4815
|
const count = Number(document.getElementById(buttonId)?.dataset.count || 0);
|
|
4783
4816
|
twin.textContent = count ? `${icon} ${text} (${count})` : `${icon} ${text}`;
|
|
4784
4817
|
};
|
|
4818
|
+
const questions = document.getElementById('questions-btn');
|
|
4819
|
+
const questionsTwin = document.getElementById('chat-menu-questions');
|
|
4820
|
+
if (questionsTwin) {
|
|
4821
|
+
questionsTwin.textContent = questionsTwinLabel({
|
|
4822
|
+
pending: Number(questions?.dataset.count || 0),
|
|
4823
|
+
urgent: Number(questions?.dataset.urgent || 0),
|
|
4824
|
+
});
|
|
4825
|
+
}
|
|
4785
4826
|
label('pinned-messages-btn', 'chat-menu-pins', 'Pinned messages', '๐');
|
|
4786
4827
|
label('hidden-messages-btn', 'chat-menu-hidden', 'Hidden from shared viewers', '๐');
|
|
4787
4828
|
}
|
|
@@ -4812,6 +4853,11 @@ function scheduleHeaderPrioritySync({ force = false } = {}) {
|
|
|
4812
4853
|
// syncChatTitleControls.
|
|
4813
4854
|
function initHeaderPriorityActions({ sharedMode = false } = {}) {
|
|
4814
4855
|
if (!sharedMode) {
|
|
4856
|
+
// ๐'s own button toggles the list under it (questions-panel-ui.js); the
|
|
4857
|
+
// twin opens it under ๏ธ.
|
|
4858
|
+
bindHeaderPriorityAction('questions', () => {
|
|
4859
|
+
openQuestionsPanel({ anchor: document.getElementById('chat-actions-menu-btn') });
|
|
4860
|
+
}, { inline: false });
|
|
4815
4861
|
bindHeaderPriorityAction('pins', () => openPinnedMessagesModal(), { inline: false });
|
|
4816
4862
|
bindHeaderPriorityAction('hidden', () => openHiddenMessagesModal(), { inline: false });
|
|
4817
4863
|
bindHeaderPriorityAction('search', () => openMessageSearchModal(), { inline: false });
|
|
@@ -5052,6 +5098,7 @@ function syncChatTitleControls() {
|
|
|
5052
5098
|
if (!convId) {
|
|
5053
5099
|
setPinsConversation(null);
|
|
5054
5100
|
setHiddenMessagesConversation(null);
|
|
5101
|
+
setQuestionsConversation(null);
|
|
5055
5102
|
}
|
|
5056
5103
|
const conversation = convId ? (conversations[convId] || null) : null;
|
|
5057
5104
|
const sdkSessionId = String(conversation?.sdkSessionId || '').trim();
|
|
@@ -5333,6 +5380,9 @@ async function initApp() {
|
|
|
5333
5380
|
if (!sharedMode) {
|
|
5334
5381
|
syncSuspendHostVisibility();
|
|
5335
5382
|
syncScrollOnSendToggle();
|
|
5383
|
+
syncThoughtsExpandedToggle();
|
|
5384
|
+
syncLiveActivityCapSlider();
|
|
5385
|
+
syncStreamViewToggle();
|
|
5336
5386
|
syncWindowRetentionSlider();
|
|
5337
5387
|
initHostSuspendUi();
|
|
5338
5388
|
initUsageLimitUi();
|
|
@@ -5734,6 +5784,7 @@ async function initApp() {
|
|
|
5734
5784
|
void loadRelaySchedules();
|
|
5735
5785
|
// The Inbox's count, and the cards of questions that don't block.
|
|
5736
5786
|
startInbox();
|
|
5787
|
+
initQuestionsPanel();
|
|
5737
5788
|
if (pushConversationId) {
|
|
5738
5789
|
await openConversation(pushConversationId, { history: 'replace' }).catch(() => {});
|
|
5739
5790
|
}
|
|
@@ -5812,6 +5863,7 @@ async function bootstrap() {
|
|
|
5812
5863
|
closeTmuxInspector: closeTmuxInspectorView,
|
|
5813
5864
|
closeAnnotateEditor,
|
|
5814
5865
|
closeRelayWatchPanel,
|
|
5866
|
+
closeQuestionsPanel,
|
|
5815
5867
|
closeRelaySchedulePanel,
|
|
5816
5868
|
closeEmojiPicker,
|
|
5817
5869
|
});
|
|
@@ -5900,6 +5952,12 @@ window.resetGrokAllowanceAccounting = resetGrokAllowanceAccounting;
|
|
|
5900
5952
|
window.toggleCursorProvider = toggleCursorProvider;
|
|
5901
5953
|
window.updateShowSuspendHostSetting = updateShowSuspendHostSetting;
|
|
5902
5954
|
window.updateScrollOnSendSetting = (enabled) => updateScrollOnSendSetting(enabled);
|
|
5955
|
+
window.updateThoughtsExpandedSetting = (expanded) => updateThoughtsExpandedSetting(expanded);
|
|
5956
|
+
window.updateStreamViewSetting = (on) => updateStreamViewSetting(on);
|
|
5957
|
+
// The transcript and the live bubble take the other layout at once.
|
|
5958
|
+
window.addEventListener(STREAM_VIEW_CHANGE_EVENT, () => refreshStreamViewLayout());
|
|
5959
|
+
window.previewLiveActivityCap = (position) => previewLiveActivityCap(position);
|
|
5960
|
+
window.updateLiveActivityCap = (position) => updateLiveActivityCap(position);
|
|
5903
5961
|
window.previewConversationWindowRetention = (position) => previewWindowRetention(position);
|
|
5904
5962
|
// A lower cap cuts the kept windows down now; the open conversation's is cut
|
|
5905
5963
|
// when it is left.
|
|
@@ -47,6 +47,7 @@ export const CHAT_ACTIONS_MENU_ENTRY_IDS = Object.freeze([
|
|
|
47
47
|
'chat-menu-schedules',
|
|
48
48
|
'chat-menu-origin',
|
|
49
49
|
'chat-menu-edit-title',
|
|
50
|
+
'chat-menu-questions',
|
|
50
51
|
'chat-menu-pins',
|
|
51
52
|
'chat-menu-hidden',
|
|
52
53
|
'chat-menu-search',
|
|
@@ -82,6 +83,7 @@ const ALWAYS_HIDDEN_IDS = new Set(['chat-menu-compact', 'chat-menu-refresh-histo
|
|
|
82
83
|
* with HEADER_PRIORITY_MENU_IDS by the tests.
|
|
83
84
|
*/
|
|
84
85
|
export const CHAT_ACTIONS_MENU_TWIN_IDS = Object.freeze([
|
|
86
|
+
'chat-menu-questions',
|
|
85
87
|
'chat-menu-pins',
|
|
86
88
|
'chat-menu-hidden',
|
|
87
89
|
'chat-menu-search',
|
|
@@ -350,6 +350,74 @@ ${renderThinkingButtons('display', THINKING_DISPLAY_CHOICES, displayKey)}
|
|
|
350
350
|
`;
|
|
351
351
|
}
|
|
352
352
|
|
|
353
|
+
const THOUGHTS_EXPANDED_CHOICES = Object.freeze([
|
|
354
|
+
{ key: 'on', label: 'Expanded' },
|
|
355
|
+
{ key: 'off', label: 'Collapsed' },
|
|
356
|
+
]);
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* The mirror of Settings โบ General "Expand thoughts while an agent works", for
|
|
360
|
+
* every provider. A setting of this browser, so nothing goes to the relay:
|
|
361
|
+
* the buttons write thoughts-preference.mjs and both places follow it.
|
|
362
|
+
*
|
|
363
|
+
* @returns {string} HTML
|
|
364
|
+
*/
|
|
365
|
+
export function renderThoughtsPreferenceHtml({ expanded = true } = {}) {
|
|
366
|
+
const activeKey = expanded === false ? 'off' : 'on';
|
|
367
|
+
const buttons = THOUGHTS_EXPANDED_CHOICES.map(({ key, label }) => `
|
|
368
|
+
<button
|
|
369
|
+
type="button"
|
|
370
|
+
class="ctx-thoughts-btn${key === activeKey ? ' is-active' : ''}"
|
|
371
|
+
data-thoughts-expanded="${key}"
|
|
372
|
+
aria-pressed="${key === activeKey ? 'true' : 'false'}"
|
|
373
|
+
>${escapeHtml(label)}</button>`).join('');
|
|
374
|
+
return `
|
|
375
|
+
<div class="ctx-thinking ctx-thoughts">
|
|
376
|
+
<div class="ctx-thinking-row">
|
|
377
|
+
<span class="ctx-thinking-label">๐ญ Thoughts</span>
|
|
378
|
+
<div class="ctx-thinking-group" role="group" aria-label="Thoughts while an agent works">
|
|
379
|
+
${buttons}
|
|
380
|
+
</div>
|
|
381
|
+
</div>
|
|
382
|
+
<div class="ctx-thinking-note">Whether thoughts open while an agent works, in sub-agent bubbles and in the sub-agent viewer. A thought you fold or open by hand stays that way. Saved in this browser.</div>
|
|
383
|
+
</div>
|
|
384
|
+
`;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
const STREAM_VIEW_CHOICES = Object.freeze([
|
|
388
|
+
{ key: 'on', label: 'On' },
|
|
389
|
+
{ key: 'off', label: 'Off' },
|
|
390
|
+
]);
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* The mirror of Settings โบ General "Stream view", for every provider. A
|
|
394
|
+
* setting of this browser (stream-view-preference.mjs); nothing goes to the
|
|
395
|
+
* relay.
|
|
396
|
+
*
|
|
397
|
+
* @returns {string} HTML
|
|
398
|
+
*/
|
|
399
|
+
export function renderStreamViewPreferenceHtml({ on = false } = {}) {
|
|
400
|
+
const activeKey = on === true ? 'on' : 'off';
|
|
401
|
+
const buttons = STREAM_VIEW_CHOICES.map(({ key, label }) => `
|
|
402
|
+
<button
|
|
403
|
+
type="button"
|
|
404
|
+
class="ctx-thoughts-btn ctx-stream-view-btn${key === activeKey ? ' is-active' : ''}"
|
|
405
|
+
data-stream-view="${key}"
|
|
406
|
+
aria-pressed="${key === activeKey ? 'true' : 'false'}"
|
|
407
|
+
>${escapeHtml(label)}</button>`).join('');
|
|
408
|
+
return `
|
|
409
|
+
<div class="ctx-thinking ctx-stream-view">
|
|
410
|
+
<div class="ctx-thinking-row">
|
|
411
|
+
<span class="ctx-thinking-label">๐งต Stream view</span>
|
|
412
|
+
<div class="ctx-thinking-group" role="group" aria-label="Stream view">
|
|
413
|
+
${buttons}
|
|
414
|
+
</div>
|
|
415
|
+
</div>
|
|
416
|
+
<div class="ctx-thinking-note">Thoughts, tool calls and the reply of a turn as one list in the order they happened. Turns recorded before this version keep the grouped layout. Saved in this browser.</div>
|
|
417
|
+
</div>
|
|
418
|
+
`;
|
|
419
|
+
}
|
|
420
|
+
|
|
353
421
|
/**
|
|
354
422
|
* The owner's switch for a session an agent started: may its agents start
|
|
355
423
|
* sessions themselves (the relay_tool's one-level rule lifted for it)?
|