@oar-sh/oar 0.9.15 → 0.9.17
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/.github/extensions/web-relay/polling/polling-loop.mjs +14 -87
- package/CHANGELOG.md +305 -0
- package/README.md +12 -11
- package/package.json +2 -1
- package/server/claude-worker/claude-attachments.mjs +7 -4
- package/server/claude-worker/claude-remote-relay-tool.mjs +36 -1
- package/server/claude-worker/claude-sdk-adapter.mjs +167 -9
- package/server/claude-worker/claude-session-process.mjs +131 -33
- package/server/claude-worker/claude-session-worker-main.mjs +266 -0
- package/server/claude-worker/claude-session-worker.mjs +2 -253
- package/server/claude-worker/claude-turn-failure.mjs +8 -0
- package/server/claude-worker/claude-turn-publisher.mjs +282 -13
- package/server/claude-worker/claude-usage-totals.mjs +108 -0
- package/server/claude-worker/sdk-message-normalizer.mjs +55 -0
- package/server/copilot-worker/copilot-sdk-adapter.mjs +18 -4
- package/server/copilot-worker/copilot-sdk-session-process.mjs +73 -7
- package/server/db-schema.mjs +115 -0
- package/server/deepseek-worker/deepseek-claude-backend.mjs +433 -0
- package/server/deepseek-worker/deepseek-session-worker.mjs +37 -0
- package/server/migrations/0019-relay-event-subscription-options.mjs +106 -0
- package/server/migrations/0020-conversation-may-create-sessions.mjs +67 -0
- package/server/migrations/0021-conversation-group-agent-marks.mjs +77 -0
- package/server/migrations/0022-relay-schedules.mjs +142 -0
- package/server/migrations/0023-relay-asks.mjs +141 -0
- package/server/migrations/0024-deepseek-usage-totals.mjs +151 -0
- package/server/migrations/0025-deepseek-usage-requests-daily.mjs +179 -0
- package/server/migrations/0026-deepseek-usage-served-models.mjs +222 -0
- package/server/migrations/0027-deepseek-usage-checkpoint-runs.mjs +101 -0
- package/server/migrations/0028-deepseek-usage-checkpoint-request-runs.mjs +99 -0
- package/server/migrations/0029-deepseek-balance-rises.mjs +99 -0
- package/server/migrations/0030-deepseek-usage-actuals.mjs +123 -0
- package/server/public/app/api-client.js +53 -0
- package/server/public/app/ask-user-view.js +3 -0
- package/server/public/app/bootstrap.js +162 -16
- package/server/public/app/context-tier-options.mjs +4 -0
- package/server/public/app/context-usage-view.mjs +17 -0
- package/server/public/app/conversation-group-order.mjs +35 -0
- package/server/public/app/conversation-provider-indicator.mjs +7 -0
- package/server/public/app/conversation-view.js +2 -0
- package/server/public/app/deepseek-settings-ui.js +342 -0
- package/server/public/app/inbox-ui.js +367 -0
- package/server/public/app/inbox-view.mjs +370 -0
- package/server/public/app/journal-view.js +54 -5
- package/server/public/app/model-selector-options.mjs +11 -0
- package/server/public/app/model-vision-support.mjs +2 -0
- package/server/public/app/no-conversation-state.mjs +1 -1
- package/server/public/app/plan-usage-view.mjs +165 -13
- package/server/public/app/provider-readiness.mjs +4 -1
- package/server/public/app/relay-error-ctas.mjs +8 -0
- package/server/public/app/relay-event-subscriptions-ui.js +4 -2
- package/server/public/app/relay-event-view.mjs +33 -7
- package/server/public/app/relay-schedule-view.mjs +119 -0
- package/server/public/app/relay-schedules-ui.js +172 -0
- package/server/public/app/settings-modal.js +11 -0
- package/server/public/app/settings-tabs.js +1 -1
- package/server/public/app/socket-handlers.js +37 -3
- package/server/public/app/store.js +18 -5
- package/server/public/index.html +567 -35
- package/server/relay-tools.md +18 -3
- package/server/repositories/conversation-group-repository.mjs +110 -17
- package/server/repositories/remote-relay-repository.mjs +20 -2
- package/server/repositories/session-repository.mjs +7 -1
- package/server/routes/ask-user-routes.mjs +57 -3
- package/server/routes/cache-routes.mjs +6 -0
- package/server/routes/conversation-group-routes.mjs +19 -3
- package/server/routes/deepseek-usage-import-routes.mjs +72 -0
- package/server/routes/messages-routes.mjs +195 -18
- package/server/routes/relay-ask-routes.mjs +387 -0
- package/server/routes/relay-event-routes.mjs +3 -1
- package/server/routes/relay-schedule-routes.mjs +57 -0
- package/server/routes/sessions-routes.mjs +932 -97
- package/server/server-runtime.mjs +203 -11
- package/server/services/about-service.mjs +1 -0
- package/server/services/cache-rebuild-service.mjs +19 -2
- package/server/services/claude-session-leftovers.mjs +226 -0
- package/server/services/conversation-preferences-service.mjs +17 -0
- package/server/services/deepseek-balance-service.mjs +102 -0
- package/server/services/deepseek-settings-service.mjs +380 -0
- package/server/services/deepseek-usage-import.mjs +471 -0
- package/server/services/model-variant-catalog-service.mjs +1 -0
- package/server/services/plan-usage-contract.mjs +100 -5
- package/server/services/plan-usage-deepseek.mjs +931 -0
- package/server/services/plan-usage-service.mjs +431 -0
- package/server/services/provider-model-selection.mjs +1 -0
- package/server/services/provider-reasoning-effort.mjs +15 -2
- package/server/services/relay-ask-link-service.mjs +174 -0
- package/server/services/relay-ask-service.mjs +604 -0
- package/server/services/relay-event-service.mjs +589 -73
- package/server/services/relay-schedule-service.mjs +596 -0
- package/server/services/remote-relay-caller-context.mjs +89 -20
- package/server/services/remote-relay-dispatcher.mjs +958 -31
- package/server/services/session-worker-launch-service.mjs +233 -82
- package/server/services/worker-secret-redaction.mjs +318 -0
- package/shared/context-window-fallbacks.mjs +3 -0
- package/shared/deepseek-api.mjs +137 -0
- package/shared/deepseek-models.mjs +114 -0
- package/shared/deepseek-pricing.mjs +146 -0
- package/shared/host-claude-session-env.mjs +101 -0
- package/shared/model-id.mjs +7 -0
- package/shared/preview-tool-core.mjs +1 -1
- package/shared/provider-routing.mjs +2 -2
- package/shared/relay-asks.mjs +145 -0
- package/shared/relay-events.mjs +268 -18
- package/shared/relay-schedules.mjs +128 -0
- package/shared/remote-relay-contract.mjs +554 -26
- package/shared/remote-relay-tool-core.mjs +16 -5
- package/shared/steer-settle-failure.mjs +1 -0
- package/shared/worker-runtime/direct-openai-image.mjs +128 -0
- package/shared/worker-runtime/worker-log-file.mjs +60 -0
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
// Relay schedules on the page: the header's "N scheduled" chip with its list
|
|
2
|
+
// and Cancel buttons (on a conversation a schedule fires into or that set
|
|
3
|
+
// one), and the "scheduled" marker on the sidebar row of a conversation a
|
|
4
|
+
// schedule fires into. The relay sends the whole active list on every change
|
|
5
|
+
// (socket `relay_schedules`); the page loads it once at start.
|
|
6
|
+
|
|
7
|
+
import { apiFetch } from './api-client.js';
|
|
8
|
+
import { conversations, currentConvId, escHtml, IS_SHARED_VIEW, showTransientRelayNotice } from './store.js';
|
|
9
|
+
import {
|
|
10
|
+
nextScheduledAt,
|
|
11
|
+
renderScheduledBadgeHtml,
|
|
12
|
+
renderSchedulePanelHtml,
|
|
13
|
+
scheduleChipText,
|
|
14
|
+
schedulesOf,
|
|
15
|
+
} from './relay-schedule-view.mjs';
|
|
16
|
+
|
|
17
|
+
let schedules = [];
|
|
18
|
+
let renderedChipKey = '';
|
|
19
|
+
let panelOpen = false;
|
|
20
|
+
let panelConversationId = '';
|
|
21
|
+
const changeListeners = new Set();
|
|
22
|
+
|
|
23
|
+
/** Runs after every change of the list (the sidebar re-renders its markers). */
|
|
24
|
+
export function onRelaySchedulesChanged(listener) {
|
|
25
|
+
changeListeners.add(listener);
|
|
26
|
+
return () => changeListeners.delete(listener);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function applyRelaySchedules(list) {
|
|
30
|
+
schedules = Array.isArray(list) ? list.filter((schedule) => schedule && typeof schedule === 'object') : [];
|
|
31
|
+
syncRelayScheduleHeader();
|
|
32
|
+
for (const listener of changeListeners) {
|
|
33
|
+
try { listener(schedules); } catch {}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export async function loadRelaySchedules() {
|
|
38
|
+
if (IS_SHARED_VIEW) return;
|
|
39
|
+
const response = await apiFetch('/api/relay-schedules');
|
|
40
|
+
if (response && Array.isArray(response.schedules)) applyRelaySchedules(response.schedules);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** The sidebar's marker for one conversation. */
|
|
44
|
+
export function relayScheduleMarkerHtml(conversationId) {
|
|
45
|
+
if (IS_SHARED_VIEW) return '';
|
|
46
|
+
return renderScheduledBadgeHtml(schedules, conversationId, { escHtml });
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function cancelSchedule(scheduleId, button) {
|
|
50
|
+
if (!scheduleId) return;
|
|
51
|
+
if (button) button.disabled = true;
|
|
52
|
+
const response = await apiFetch(`/api/relay-schedules/${encodeURIComponent(scheduleId)}`, { method: 'DELETE' });
|
|
53
|
+
if (!response?.ok) {
|
|
54
|
+
if (button) button.disabled = false;
|
|
55
|
+
showTransientRelayNotice?.('The schedule could not be cancelled. Try again in a moment.');
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
// The socket brings the new list; this page does not wait for it.
|
|
59
|
+
applyRelaySchedules(schedules.filter((schedule) => schedule.id !== scheduleId));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// A fixed popover on <body>, placed under the chip, like the subscriptions'
|
|
63
|
+
// list: the title slot clips its overflow.
|
|
64
|
+
function ensurePanel() {
|
|
65
|
+
let panel = document.getElementById('relay-schedule-panel');
|
|
66
|
+
if (panel) return panel;
|
|
67
|
+
panel = document.createElement('div');
|
|
68
|
+
panel.id = 'relay-schedule-panel';
|
|
69
|
+
panel.className = 'relay-watch-panel';
|
|
70
|
+
panel.hidden = true;
|
|
71
|
+
document.body.appendChild(panel);
|
|
72
|
+
panel.addEventListener('click', (event) => {
|
|
73
|
+
const target = event.target instanceof Element ? event.target : null;
|
|
74
|
+
const cancelButton = target?.closest?.('.relay-schedule-cancel');
|
|
75
|
+
if (!cancelButton) return;
|
|
76
|
+
void cancelSchedule(cancelButton.getAttribute('data-schedule-id'), cancelButton);
|
|
77
|
+
});
|
|
78
|
+
document.addEventListener('click', (event) => {
|
|
79
|
+
if (!panelOpen) return;
|
|
80
|
+
// composedPath: a Cancel click may have re-rendered the panel already.
|
|
81
|
+
const path = event.composedPath();
|
|
82
|
+
if (path.includes(panel) || path.some((node) => node instanceof Element && node.classList.contains('relay-schedule-chip'))) return;
|
|
83
|
+
setPanelOpen(false);
|
|
84
|
+
});
|
|
85
|
+
document.addEventListener('keydown', (event) => {
|
|
86
|
+
if (!panelOpen || event.key !== 'Escape') return;
|
|
87
|
+
event.stopPropagation();
|
|
88
|
+
setPanelOpen(false);
|
|
89
|
+
document.querySelector('#chat-title-relay-schedules .relay-schedule-chip')?.focus?.();
|
|
90
|
+
}, true);
|
|
91
|
+
window.addEventListener('resize', () => { if (panelOpen) placePanel(); });
|
|
92
|
+
return panel;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function setPanelOpen(open) {
|
|
96
|
+
panelOpen = Boolean(open);
|
|
97
|
+
renderedChipKey = '';
|
|
98
|
+
syncRelayScheduleHeader();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function placePanel() {
|
|
102
|
+
const panel = document.getElementById('relay-schedule-panel');
|
|
103
|
+
const chip = document.querySelector('#chat-title-relay-schedules .relay-schedule-chip');
|
|
104
|
+
if (!panel || panel.hidden) return;
|
|
105
|
+
const rect = chip?.getBoundingClientRect?.();
|
|
106
|
+
if (!rect || (!rect.width && !rect.height)) {
|
|
107
|
+
panel.hidden = true;
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
const margin = 8;
|
|
111
|
+
const viewportWidth = document.documentElement.clientWidth || window.innerWidth;
|
|
112
|
+
const top = Math.round(rect.bottom + 4);
|
|
113
|
+
panel.style.top = `${top}px`;
|
|
114
|
+
panel.style.maxHeight = `${Math.max(120, window.innerHeight - top - margin)}px`;
|
|
115
|
+
panel.style.left = '0px';
|
|
116
|
+
const width = panel.offsetWidth;
|
|
117
|
+
const left = Math.max(margin, Math.min(rect.left, viewportWidth - width - margin));
|
|
118
|
+
panel.style.left = `${Math.round(left)}px`;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function ensureHeaderElements() {
|
|
122
|
+
const slot = document.getElementById('chat-title-slot');
|
|
123
|
+
if (!slot) return null;
|
|
124
|
+
let line = document.getElementById('chat-title-relay-schedules');
|
|
125
|
+
if (!line) {
|
|
126
|
+
line = document.createElement('span');
|
|
127
|
+
line.id = 'chat-title-relay-schedules';
|
|
128
|
+
line.hidden = true;
|
|
129
|
+
const after = document.getElementById('chat-title-relay-events') || document.getElementById('chat-title-origin');
|
|
130
|
+
if (after?.nextSibling) slot.insertBefore(line, after.nextSibling);
|
|
131
|
+
else slot.appendChild(line);
|
|
132
|
+
line.addEventListener('click', (event) => {
|
|
133
|
+
const target = event.target instanceof Element ? event.target : null;
|
|
134
|
+
if (target?.closest?.('.relay-schedule-chip')) {
|
|
135
|
+
event.stopPropagation();
|
|
136
|
+
setPanelOpen(!panelOpen);
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
return line;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** The header's chip for the conversation on screen. Idempotent. */
|
|
144
|
+
export function syncRelayScheduleHeader() {
|
|
145
|
+
const line = ensureHeaderElements();
|
|
146
|
+
if (!line) return;
|
|
147
|
+
const panel = ensurePanel();
|
|
148
|
+
const id = String(currentConvId || '').trim();
|
|
149
|
+
// Another conversation on screen closes the list.
|
|
150
|
+
if (id !== panelConversationId) panelOpen = false;
|
|
151
|
+
panelConversationId = id;
|
|
152
|
+
const mine = IS_SHARED_VIEW || !id ? [] : schedulesOf(schedules, id);
|
|
153
|
+
const chipText = scheduleChipText(mine);
|
|
154
|
+
if (!chipText) panelOpen = false;
|
|
155
|
+
const key = JSON.stringify([id, chipText, panelOpen, mine.map((schedule) => [schedule.id, schedule.nextFireAt, schedule.skippedCount || 0])]);
|
|
156
|
+
if (key === renderedChipKey) return;
|
|
157
|
+
renderedChipKey = key;
|
|
158
|
+
if (!chipText) {
|
|
159
|
+
line.hidden = true;
|
|
160
|
+
line.innerHTML = '';
|
|
161
|
+
panel.hidden = true;
|
|
162
|
+
panel.innerHTML = '';
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
const next = nextScheduledAt(mine);
|
|
166
|
+
const nextText = next ? new Date(next).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) : '';
|
|
167
|
+
line.innerHTML = `<button type="button" class="relay-watch-chip relay-schedule-chip" aria-expanded="${panelOpen ? 'true' : 'false'}" aria-controls="relay-schedule-panel" title="Prompts the relay queues at set times (agents set them with remote_relay schedule)">⏰ ${escHtml(chipText)}${nextText ? ` <span class="relay-watch-hint">next ${escHtml(nextText)}</span>` : ''}</button>`;
|
|
168
|
+
line.hidden = false;
|
|
169
|
+
panel.innerHTML = panelOpen ? renderSchedulePanelHtml(schedules, id, { conversations, escHtml }) : '';
|
|
170
|
+
panel.hidden = !panelOpen;
|
|
171
|
+
if (panelOpen) placePanel();
|
|
172
|
+
}
|
|
@@ -55,6 +55,16 @@ import { refreshPushSettingsSection } from './push-settings.js';
|
|
|
55
55
|
import { selectSettingsTab } from './settings-tabs.js';
|
|
56
56
|
import { refreshClaudeAuthSection } from './claude-auth-ui.js';
|
|
57
57
|
import { refreshGrokAuthSection } from './grok-auth-ui.js';
|
|
58
|
+
import { syncDeepSeekSettingsInputs } from './deepseek-settings-ui.js';
|
|
59
|
+
|
|
60
|
+
export {
|
|
61
|
+
applyDeepSeekSettingsState,
|
|
62
|
+
importDeepSeekUsageExport,
|
|
63
|
+
refreshDeepSeekSettingsState,
|
|
64
|
+
removeDeepSeekSettings,
|
|
65
|
+
saveDeepSeekSettings,
|
|
66
|
+
toggleDeepSeekProvider,
|
|
67
|
+
} from './deepseek-settings-ui.js';
|
|
58
68
|
import { openClaudeCloudSettingsSection } from './claude-cloud-settings-ui.js';
|
|
59
69
|
import { refreshCliInstallSections } from './cli-install-ui.js';
|
|
60
70
|
import { refreshUpdateSection } from './update-ui.js';
|
|
@@ -1949,6 +1959,7 @@ export function openSettingsModal(tab, providerTab) {
|
|
|
1949
1959
|
cursorSettingsInputsDirty = false;
|
|
1950
1960
|
ensureCursorSettingsInputTracking();
|
|
1951
1961
|
void syncCursorSettingsInputs();
|
|
1962
|
+
void syncDeepSeekSettingsInputs();
|
|
1952
1963
|
void refreshCursorAllowanceState();
|
|
1953
1964
|
void refreshCursorDashboardTokenState();
|
|
1954
1965
|
void refreshGrokAllowanceState();
|
|
@@ -4,7 +4,7 @@ const SETTINGS_PROVIDER_TAB_STORAGE_KEY = 'copilot_settings_provider_tab';
|
|
|
4
4
|
const SETTINGS_TABS = ['general', 'providers', 'relays', 'previews', 'shares', 'notifications', 'features'];
|
|
5
5
|
// Copilot leads: it is the default provider, and `SETTINGS_PROVIDER_TABS[0]`
|
|
6
6
|
// is also the sub-tab a first-time visitor lands on.
|
|
7
|
-
const SETTINGS_PROVIDER_TABS = ['copilot', 'openai', 'claude', 'claude-cloud', 'grok', 'cursor'];
|
|
7
|
+
const SETTINGS_PROVIDER_TABS = ['copilot', 'openai', 'claude', 'claude-cloud', 'grok', 'cursor', 'deepseek'];
|
|
8
8
|
|
|
9
9
|
let settingsTabsBound = false;
|
|
10
10
|
|
|
@@ -38,6 +38,9 @@ import { publishStatusEvent, recordStatusEvent } from './status-store.mjs';
|
|
|
38
38
|
import { clearOpenConversation, renderConvList, refreshConversations, scheduleLoadedConversationsRefresh, applyConversationGroupEvent, openConversation } from './journal-view.js';
|
|
39
39
|
import { applyBackgroundTasksToConversation, applyTurnStatusToConversation } from './conversation-turn-state.mjs';
|
|
40
40
|
import { applyRelayEventSubscriptions, loadRelayEventSubscriptions } from './relay-event-subscriptions-ui.js';
|
|
41
|
+
import { applyRelaySchedules, loadRelaySchedules } from './relay-schedules-ui.js';
|
|
42
|
+
import { noteInboxQuestionsChanged, noteRelayAskUpdated, refreshInbox } from './inbox-ui.js';
|
|
43
|
+
import { RELAY_ASK_SOCKET_EVENT } from './inbox-view.mjs';
|
|
41
44
|
import {
|
|
42
45
|
upsertRelayQuestion,
|
|
43
46
|
loadRelayQuestions,
|
|
@@ -150,6 +153,7 @@ let deps = null;
|
|
|
150
153
|
* @property {(payload?: object) => void} applyOpenAISettingsState
|
|
151
154
|
* @property {(payload?: object) => void} applyClaudeSettingsState
|
|
152
155
|
* @property {(payload?: object) => void} applyGrokSettingsState
|
|
156
|
+
* @property {(payload?: object) => void} applyDeepSeekSettingsState
|
|
153
157
|
* @property {(payload?: object) => void} applyCursorSettingsState
|
|
154
158
|
*/
|
|
155
159
|
|
|
@@ -368,6 +372,10 @@ export async function connectSocket(overrideDeps) {
|
|
|
368
372
|
void handleTaskConsoleReconnect();
|
|
369
373
|
// Subscriptions that changed while away.
|
|
370
374
|
void loadRelayEventSubscriptions();
|
|
375
|
+
// Schedules that changed or fired while away.
|
|
376
|
+
void loadRelaySchedules();
|
|
377
|
+
// Questions asked or answered while away.
|
|
378
|
+
void refreshInbox();
|
|
371
379
|
// Shares made or stopped while away.
|
|
372
380
|
void refreshShares().catch(() => {});
|
|
373
381
|
});
|
|
@@ -490,6 +498,15 @@ export async function connectSocket(overrideDeps) {
|
|
|
490
498
|
.catch(() => {});
|
|
491
499
|
}
|
|
492
500
|
});
|
|
501
|
+
socket.on('deepseek_settings_updated', (payload) => {
|
|
502
|
+
const state = deps?.applyDeepSeekSettingsState?.(payload || {});
|
|
503
|
+
updateProviderReadinessInputs({ deepseek: state || payload || null });
|
|
504
|
+
if (Number(payload?.reconciliation?.updatedUnstartedConversations || 0) > 0) {
|
|
505
|
+
void Promise.resolve()
|
|
506
|
+
.then(() => deps?.refreshCurrentView?.())
|
|
507
|
+
.catch(() => {});
|
|
508
|
+
}
|
|
509
|
+
});
|
|
493
510
|
socket.on('cursor_settings_updated', (payload) => {
|
|
494
511
|
const state = deps?.applyCursorSettingsState?.(payload || {});
|
|
495
512
|
updateProviderReadinessInputs({ cursor: state || payload || null });
|
|
@@ -618,11 +635,20 @@ export async function connectSocket(overrideDeps) {
|
|
|
618
635
|
noteTranscriptLiveRow();
|
|
619
636
|
keepLiveRow(conversationId, message, messageId);
|
|
620
637
|
});
|
|
621
|
-
socket.on('relay_question', ({ question }) =>
|
|
622
|
-
|
|
638
|
+
socket.on('relay_question', ({ question }) => {
|
|
639
|
+
upsertRelayQuestion(question);
|
|
640
|
+
noteInboxQuestionsChanged();
|
|
641
|
+
});
|
|
642
|
+
socket.on('relay_question_updated', ({ question }) => {
|
|
643
|
+
upsertRelayQuestion(question);
|
|
644
|
+
noteInboxQuestionsChanged();
|
|
645
|
+
});
|
|
623
646
|
socket.on('relay_question_changed', () => {
|
|
624
647
|
loadRelayQuestions(currentConvId);
|
|
648
|
+
noteInboxQuestionsChanged();
|
|
625
649
|
});
|
|
650
|
+
// Questions that don't block: one ask, after every change of it.
|
|
651
|
+
socket.on(RELAY_ASK_SOCKET_EVENT, (payload) => noteRelayAskUpdated(payload));
|
|
626
652
|
socket.on('relay_board', ({ board }) => upsertRelayBoard(board));
|
|
627
653
|
socket.on('relay_board_updated', ({ board }) => upsertRelayBoard(board));
|
|
628
654
|
socket.on('relay_board_changed', () => {
|
|
@@ -632,6 +658,10 @@ export async function connectSocket(overrideDeps) {
|
|
|
632
658
|
socket.on('relay_event_subscriptions', ({ subscriptions }) => {
|
|
633
659
|
applyRelayEventSubscriptions(subscriptions);
|
|
634
660
|
});
|
|
661
|
+
// Relay schedules: the whole active list, after every change.
|
|
662
|
+
socket.on('relay_schedules', ({ schedules }) => {
|
|
663
|
+
applyRelaySchedules(schedules);
|
|
664
|
+
});
|
|
635
665
|
socket.on('background_tasks', ({ conversationId, tasks, endedTasks }) => {
|
|
636
666
|
setConversationBackgroundTasks(conversationId, tasks, { endedTasks, event: true });
|
|
637
667
|
// The sidebar's row (and its group's head) count background work as
|
|
@@ -799,7 +829,7 @@ export async function connectSocket(overrideDeps) {
|
|
|
799
829
|
applyConversationTitleUpdate(conversationId, title, updatedAt);
|
|
800
830
|
syncChatTitleControls();
|
|
801
831
|
});
|
|
802
|
-
socket.on('conversation_preferences_updated', ({ conversationId, preferredRelayMode, preferredModel, preferredReasoningEffort, autoCompactWindow, thinkingEnabled, thinkingDisplay, senderClientId }) => {
|
|
832
|
+
socket.on('conversation_preferences_updated', ({ conversationId, preferredRelayMode, preferredModel, preferredReasoningEffort, autoCompactWindow, thinkingEnabled, thinkingDisplay, mayCreateSessions, senderClientId }) => {
|
|
803
833
|
if (senderClientId && senderClientId === CLIENT_ID) return;
|
|
804
834
|
const id = String(conversationId || '').trim();
|
|
805
835
|
if (!id || !conversations[id]) return;
|
|
@@ -821,6 +851,10 @@ export async function connectSocket(overrideDeps) {
|
|
|
821
851
|
thinkingDisplay: thinkingDisplay === undefined
|
|
822
852
|
? (conversations[id].thinkingDisplay ?? null)
|
|
823
853
|
: (thinkingDisplay ?? null),
|
|
854
|
+
// The owner's "agents may start sessions"; an older relay omits it.
|
|
855
|
+
mayCreateSessions: typeof mayCreateSessions === 'boolean'
|
|
856
|
+
? mayCreateSessions
|
|
857
|
+
: conversations[id].mayCreateSessions === true,
|
|
824
858
|
};
|
|
825
859
|
if (String(currentConvId || '').trim() === id) {
|
|
826
860
|
applyConversationPreferencesForConversation(id, {
|
|
@@ -222,6 +222,16 @@ export function setCurrentConv(id) {
|
|
|
222
222
|
updateCompactButton();
|
|
223
223
|
}
|
|
224
224
|
|
|
225
|
+
/**
|
|
226
|
+
* Forgets the kept empty groups before the list's first page names the ones
|
|
227
|
+
* still kept (one the owner dissolved meanwhile is not named again).
|
|
228
|
+
*/
|
|
229
|
+
export function forgetKeptEmptyConversationGroups() {
|
|
230
|
+
for (const [id, group] of Object.entries(conversationGroups)) {
|
|
231
|
+
if (group?.keptEmptyAt) delete conversationGroups[id];
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
225
235
|
/** Merges a list payload's `groups` (by id) into the known session groups. */
|
|
226
236
|
export function mergeConversationGroups(groups) {
|
|
227
237
|
if (!groups || typeof groups !== 'object') return;
|
|
@@ -240,13 +250,15 @@ export function forgetConversationGroup(groupId) {
|
|
|
240
250
|
/**
|
|
241
251
|
* Keeps only the groups that a loaded row still belongs to: after a list
|
|
242
252
|
* refresh a dissolved group has no row left, and the picker must not offer
|
|
243
|
-
* it.
|
|
253
|
+
* it. An owner's group an agent emptied (`keptEmptyAt`) has no row and
|
|
254
|
+
* stays: the list's first page names it while it is kept. Returns the ids
|
|
255
|
+
* dropped.
|
|
244
256
|
*/
|
|
245
257
|
export function pruneConversationGroups(keepGroupIds) {
|
|
246
258
|
const keep = new Set((Array.isArray(keepGroupIds) ? keepGroupIds : []).map((id) => String(id || '').trim()).filter(Boolean));
|
|
247
259
|
const dropped = [];
|
|
248
260
|
for (const id of Object.keys(conversationGroups)) {
|
|
249
|
-
if (keep.has(id)) continue;
|
|
261
|
+
if (keep.has(id) || conversationGroups[id]?.keptEmptyAt) continue;
|
|
250
262
|
delete conversationGroups[id];
|
|
251
263
|
dropped.push(id);
|
|
252
264
|
}
|
|
@@ -1126,9 +1138,10 @@ function measureSidebarHeaderMinWidthPx() {
|
|
|
1126
1138
|
sectionCount += 1;
|
|
1127
1139
|
}
|
|
1128
1140
|
|
|
1129
|
-
const
|
|
1130
|
-
|
|
1131
|
-
|
|
1141
|
+
for (const id of ['status-btn', 'inbox-btn']) {
|
|
1142
|
+
const headerButton = document.getElementById(id);
|
|
1143
|
+
if (!isVisibleElement(headerButton)) continue;
|
|
1144
|
+
const buttonStyle = window.getComputedStyle(headerButton);
|
|
1132
1145
|
neededWidth += Math.ceil(parseCssPx(buttonStyle.minWidth));
|
|
1133
1146
|
sectionCount += 1;
|
|
1134
1147
|
}
|