@oar-sh/oar 0.9.22 → 0.9.24
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 +258 -0
- package/README.md +9 -9
- package/bin/oar.js +82 -4
- package/package.json +16 -15
- package/server/agent-sdk-loader.mjs +116 -0
- package/server/agent-sdks/claude/entry.mjs +4 -0
- package/server/agent-sdks/claude/package-lock.json +1438 -0
- package/server/agent-sdks/claude/package.json +13 -0
- package/server/agent-sdks/cursor/entry-sqlite.mjs +2 -0
- package/server/agent-sdks/cursor/entry.mjs +3 -0
- package/server/agent-sdks/cursor/package-lock.json +171 -0
- package/server/agent-sdks/cursor/package.json +11 -0
- package/server/agent-sdks/koffi/entry.cjs +2 -0
- package/server/agent-sdks/koffi/package-lock.json +368 -0
- package/server/agent-sdks/koffi/package.json +10 -0
- package/server/claude-worker/claude-cli-pinned-env.mjs +4 -1
- package/server/claude-worker/claude-preview-tool.mjs +5 -2
- package/server/claude-worker/claude-remote-relay-tool.mjs +5 -2
- package/server/claude-worker/claude-sdk-adapter.mjs +4 -1
- package/server/copilot-worker/copilot-sdk-session-process.mjs +7 -4
- package/server/cursor-worker/cursor-sdk-adapter.mjs +5 -3
- package/server/public/app/agent-sdk-ui.js +349 -0
- package/server/public/app/api-client.js +91 -8
- package/server/public/app/bootstrap.js +204 -69
- package/server/public/app/model-refresh-ui.mjs +413 -0
- package/server/public/app/no-conversation-state.mjs +5 -2
- package/server/public/app/provider-readiness.mjs +67 -4
- package/server/public/app/settings-modal.js +167 -0
- package/server/public/app/socket-handlers.js +21 -0
- package/server/public/index.html +95 -2
- package/server/routes/agent-sdk-routes.mjs +73 -0
- package/server/routes/messages-routes.mjs +102 -37
- package/server/routes/sessions-routes.mjs +236 -110
- package/server/server-runtime.mjs +416 -121
- package/server/services/about-service.mjs +26 -6
- package/server/services/agent-sdk-launch-gate.mjs +124 -0
- package/server/services/agent-sdk-paths.mjs +307 -0
- package/server/services/agent-sdk-store.mjs +1211 -0
- package/server/services/claude-api-settings-service.mjs +54 -17
- package/server/services/cli-install-service.mjs +3 -2
- package/server/services/copilot-model-discovery-service.mjs +7 -1
- package/server/services/database-file.mjs +17 -1
- package/server/services/deepseek-settings-service.mjs +58 -20
- package/server/services/earlier-database-service.mjs +5 -3
- package/server/services/file-serving.mjs +13 -0
- package/server/services/model-refresh-scheduler.mjs +153 -0
- package/server/services/model-variant-catalog-service.mjs +141 -9
- package/server/services/native-addon-build-service.mjs +224 -0
- package/server/services/oar-cli-helpers.mjs +76 -10
- package/server/services/oar-state-migration-service.mjs +2 -12
- package/server/services/oar-state-root.mjs +17 -0
- package/server/services/provider-model-refresh.mjs +326 -0
- package/server/services/provider-model-state.mjs +395 -0
- package/server/services/relay-console-log.mjs +2 -3
- package/server/services/relay-singleton-guard.mjs +72 -10
- package/server/services/remote-relay-loopback.mjs +3 -3
- package/server/services/sdk-session-sync-service.mjs +1 -1
- package/server/services/session-worker-launch-service.mjs +13 -0
- package/server/services/session-worker-supervisor-service.mjs +19 -1
- package/server/services/session-worker-websocket-service.mjs +15 -0
- package/server/services/update-install-service.mjs +34 -3
- package/server/services/worker-launch-note-service.mjs +14 -2
- package/server/services/worker-recycle-service.mjs +145 -11
- package/shared/model-refresh-schedule.mjs +120 -0
- package/shared/worker-runtime/direct-openai-image.mjs +8 -4
- package/server/services/package-data-rescue-preinstall.mjs +0 -22
- package/server/services/package-data-rescue.mjs +0 -139
- package/server/tty-console-bootstrap.mjs +0 -75
|
@@ -37,8 +37,16 @@ import {
|
|
|
37
37
|
updateSubagentRecordRetentionSetting as requestSubagentRecordRetentionSetting,
|
|
38
38
|
loadFeatureFlagSettings,
|
|
39
39
|
updateFeatureFlagSetting as requestFeatureFlagSetting,
|
|
40
|
+
loadModelRefreshSettings,
|
|
41
|
+
updateModelRefreshSettings as requestModelRefreshSettings,
|
|
40
42
|
requestRelayRestart,
|
|
41
43
|
} from './api-client.js';
|
|
44
|
+
import {
|
|
45
|
+
formatModelRefreshIntervalLabel,
|
|
46
|
+
formatModelRefreshLastChecked,
|
|
47
|
+
nextModelRefreshExcluded,
|
|
48
|
+
normalizeModelRefreshSettings,
|
|
49
|
+
} from './model-refresh-ui.mjs';
|
|
42
50
|
import { DEFAULT_BACKGROUND_TASK_TIMEOUT_MINUTES } from './background-task-timeout-options.mjs';
|
|
43
51
|
import {
|
|
44
52
|
DEFAULT_CLAUDE_BACKGROUND_COMMAND_LIMIT_MINUTES,
|
|
@@ -77,6 +85,7 @@ export {
|
|
|
77
85
|
} from './claude-api-settings-ui.js';
|
|
78
86
|
import { openClaudeCloudSettingsSection } from './claude-cloud-settings-ui.js';
|
|
79
87
|
import { refreshCliInstallSections } from './cli-install-ui.js';
|
|
88
|
+
import { refreshAgentSdkRows } from './agent-sdk-ui.js';
|
|
80
89
|
import { refreshUpdateSection } from './update-ui.js';
|
|
81
90
|
import { refreshRemoteRelaysSection } from './remote-relays-settings.js';
|
|
82
91
|
import { refreshAgentSessionsSection } from './agent-sessions-settings-ui.js';
|
|
@@ -399,6 +408,161 @@ export async function updateSubagentRecordRetentionSetting(value) {
|
|
|
399
408
|
}
|
|
400
409
|
}
|
|
401
410
|
|
|
411
|
+
// "Check for new models" (Settings › General › Models): a relay setting, on
|
|
412
|
+
// every 24 h by default, with one checkbox per enabled provider to leave it
|
|
413
|
+
// out (server/services/model-refresh-scheduler.mjs). Starts at the relay's
|
|
414
|
+
// defaults until its answer loads.
|
|
415
|
+
let modelRefreshSettings = normalizeModelRefreshSettings(null);
|
|
416
|
+
let modelRefreshUpdateInFlight = false;
|
|
417
|
+
// The provider list waits for the relay's answer: "Loading…" until one
|
|
418
|
+
// arrived, the reason while the last read failed.
|
|
419
|
+
let modelRefreshProvidersLoaded = false;
|
|
420
|
+
let modelRefreshLoadError = '';
|
|
421
|
+
|
|
422
|
+
function syncModelRefreshIntervalOptions(select) {
|
|
423
|
+
const { intervalChoices, intervalHours } = modelRefreshSettings;
|
|
424
|
+
// A stored value outside today's choices still shows as itself.
|
|
425
|
+
const choices = intervalChoices.includes(intervalHours)
|
|
426
|
+
? intervalChoices
|
|
427
|
+
: [...intervalChoices, intervalHours].sort((a, b) => a - b);
|
|
428
|
+
const current = Array.from(select.options).map((option) => option.value).join(',');
|
|
429
|
+
if (current === choices.join(',')) return;
|
|
430
|
+
select.replaceChildren(...choices.map((hours) => {
|
|
431
|
+
const option = document.createElement('option');
|
|
432
|
+
option.value = String(hours);
|
|
433
|
+
option.textContent = formatModelRefreshIntervalLabel(hours);
|
|
434
|
+
return option;
|
|
435
|
+
}));
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
function renderModelRefreshProviderRows(list) {
|
|
439
|
+
const excluded = new Set(modelRefreshSettings.excluded);
|
|
440
|
+
const disabled = modelRefreshUpdateInFlight || !modelRefreshSettings.auto;
|
|
441
|
+
const now = Date.now();
|
|
442
|
+
const status = (text, className) => {
|
|
443
|
+
const line = document.createElement('div');
|
|
444
|
+
line.className = className;
|
|
445
|
+
line.textContent = text;
|
|
446
|
+
return line;
|
|
447
|
+
};
|
|
448
|
+
let statusLine = null;
|
|
449
|
+
if (modelRefreshLoadError) {
|
|
450
|
+
statusLine = status(`Could not load the providers: ${modelRefreshLoadError}`, 'settings-help model-refresh-providers-error');
|
|
451
|
+
} else if (!modelRefreshProvidersLoaded) {
|
|
452
|
+
statusLine = status('Loading…', 'settings-help model-refresh-providers-loading');
|
|
453
|
+
} else if (!modelRefreshSettings.providers.length) {
|
|
454
|
+
statusLine = status('No provider is switched on.', 'settings-help');
|
|
455
|
+
}
|
|
456
|
+
// After a failed read, the providers of an earlier answer stay below it.
|
|
457
|
+
const providers = modelRefreshProvidersLoaded ? modelRefreshSettings.providers : [];
|
|
458
|
+
list.replaceChildren(...(statusLine ? [statusLine] : []), ...providers.map((provider) => {
|
|
459
|
+
const inputId = `model-refresh-provider-${provider.id.toLowerCase().replace(/[^a-z0-9-]/g, '-')}`;
|
|
460
|
+
const row = document.createElement('div');
|
|
461
|
+
row.className = 'settings-row model-refresh-provider-row';
|
|
462
|
+
const label = document.createElement('label');
|
|
463
|
+
label.htmlFor = inputId;
|
|
464
|
+
const name = document.createElement('span');
|
|
465
|
+
name.textContent = `Check ${provider.label}`;
|
|
466
|
+
const last = document.createElement('span');
|
|
467
|
+
last.className = 'model-refresh-provider-last';
|
|
468
|
+
// An enabled provider without its key or CLI is not checked until it is.
|
|
469
|
+
last.textContent = provider.setUp ? formatModelRefreshLastChecked(provider.last, now) : 'Not set up';
|
|
470
|
+
label.append(name, last);
|
|
471
|
+
const input = document.createElement('input');
|
|
472
|
+
input.id = inputId;
|
|
473
|
+
input.type = 'checkbox';
|
|
474
|
+
input.dataset.modelRefreshProvider = provider.id;
|
|
475
|
+
input.setAttribute('aria-label', `Check ${provider.label} for new models`);
|
|
476
|
+
input.checked = !excluded.has(provider.id);
|
|
477
|
+
input.disabled = disabled;
|
|
478
|
+
input.addEventListener('change', () => {
|
|
479
|
+
void updateModelRefreshProviderSetting(provider.id, input.checked);
|
|
480
|
+
});
|
|
481
|
+
row.append(label, input);
|
|
482
|
+
return row;
|
|
483
|
+
}));
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
function syncModelRefreshSection() {
|
|
487
|
+
const toggle = document.getElementById('model-refresh-auto-toggle');
|
|
488
|
+
if (toggle instanceof HTMLInputElement) {
|
|
489
|
+
toggle.checked = modelRefreshSettings.auto;
|
|
490
|
+
toggle.disabled = modelRefreshUpdateInFlight;
|
|
491
|
+
}
|
|
492
|
+
const select = document.getElementById('model-refresh-interval-select');
|
|
493
|
+
if (select && select.tagName === 'SELECT') {
|
|
494
|
+
syncModelRefreshIntervalOptions(select);
|
|
495
|
+
select.value = String(modelRefreshSettings.intervalHours);
|
|
496
|
+
// Nothing to time while the check is off.
|
|
497
|
+
select.disabled = modelRefreshUpdateInFlight || !modelRefreshSettings.auto;
|
|
498
|
+
}
|
|
499
|
+
const list = document.getElementById('model-refresh-providers');
|
|
500
|
+
if (list) renderModelRefreshProviderRows(list);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
export async function refreshModelRefreshSettings() {
|
|
504
|
+
// A new read: "Loading…" again until it answers, or the earlier list.
|
|
505
|
+
modelRefreshLoadError = '';
|
|
506
|
+
syncModelRefreshSection();
|
|
507
|
+
try {
|
|
508
|
+
const result = await loadModelRefreshSettings();
|
|
509
|
+
if (!result || typeof result !== 'object') throw new Error('The relay did not answer.');
|
|
510
|
+
modelRefreshSettings = normalizeModelRefreshSettings(result);
|
|
511
|
+
modelRefreshProvidersLoaded = true;
|
|
512
|
+
modelRefreshLoadError = '';
|
|
513
|
+
} catch (error) {
|
|
514
|
+
// Keep the last known value; the setting is not critical.
|
|
515
|
+
modelRefreshLoadError = String(error?.message || '').trim() || 'The relay did not answer.';
|
|
516
|
+
} finally {
|
|
517
|
+
syncModelRefreshSection();
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
async function saveModelRefreshSettings(patch, describe) {
|
|
522
|
+
if (modelRefreshUpdateInFlight) {
|
|
523
|
+
syncModelRefreshSection();
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
modelRefreshUpdateInFlight = true;
|
|
527
|
+
syncModelRefreshSection();
|
|
528
|
+
try {
|
|
529
|
+
const result = await requestModelRefreshSettings(patch);
|
|
530
|
+
if (!result) throw new Error('Failed to update the check for new models.');
|
|
531
|
+
modelRefreshSettings = normalizeModelRefreshSettings(result);
|
|
532
|
+
modelRefreshProvidersLoaded = true;
|
|
533
|
+
modelRefreshLoadError = '';
|
|
534
|
+
showTransientRelayNotice(describe(modelRefreshSettings));
|
|
535
|
+
} catch (error) {
|
|
536
|
+
alert(error?.message || 'Failed to update the check for new models.');
|
|
537
|
+
} finally {
|
|
538
|
+
modelRefreshUpdateInFlight = false;
|
|
539
|
+
syncModelRefreshSection();
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
export function updateModelRefreshAutoSetting(checked) {
|
|
544
|
+
return saveModelRefreshSettings({ auto: checked === true }, (settings) => (settings.auto
|
|
545
|
+
? `The relay checks for new models ${formatModelRefreshIntervalLabel(settings.intervalHours).toLowerCase()}.`
|
|
546
|
+
: 'The relay no longer checks for new models by itself.'));
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
export function updateModelRefreshIntervalSetting(value) {
|
|
550
|
+
return saveModelRefreshSettings({ intervalHours: Math.round(Number(value)) }, (settings) => (
|
|
551
|
+
`The relay checks for new models ${formatModelRefreshIntervalLabel(settings.intervalHours).toLowerCase()}.`
|
|
552
|
+
));
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
export function updateModelRefreshProviderSetting(providerId, checked) {
|
|
556
|
+
const id = String(providerId || '').trim();
|
|
557
|
+
const label = modelRefreshSettings.providers.find((provider) => provider.id === id)?.label || id;
|
|
558
|
+
return saveModelRefreshSettings(
|
|
559
|
+
{ excluded: nextModelRefreshExcluded(modelRefreshSettings.excluded, id, checked === true) },
|
|
560
|
+
(settings) => (settings.excluded.includes(id)
|
|
561
|
+
? `${label} is no longer checked for new models.`
|
|
562
|
+
: `${label} is checked for new models.`),
|
|
563
|
+
);
|
|
564
|
+
}
|
|
565
|
+
|
|
402
566
|
export async function updateBackgroundTaskTimeoutSetting(value) {
|
|
403
567
|
if (backgroundTaskTimeoutUpdateInFlight) {
|
|
404
568
|
syncBackgroundTaskTimeoutSlider();
|
|
@@ -1966,6 +2130,8 @@ export function openSettingsModal(tab, providerTab) {
|
|
|
1966
2130
|
// in the meantime should show up the moment the panel opens. Sockets keep it
|
|
1967
2131
|
// current from here on.
|
|
1968
2132
|
void refreshCliInstallSections({ force: true });
|
|
2133
|
+
// The agent SDK rows: a read of the store's state, nothing spawned.
|
|
2134
|
+
void refreshAgentSdkRows();
|
|
1969
2135
|
grokSettingsInputsDirty = false;
|
|
1970
2136
|
ensureGrokSettingsInputTracking();
|
|
1971
2137
|
void syncGrokSettingsInputs();
|
|
@@ -1989,6 +2155,7 @@ export function openSettingsModal(tab, providerTab) {
|
|
|
1989
2155
|
void refreshRestartResumeSetting();
|
|
1990
2156
|
syncSubagentRecordRetentionSelect();
|
|
1991
2157
|
void refreshSubagentRecordRetentionSetting();
|
|
2158
|
+
void refreshModelRefreshSettings();
|
|
1992
2159
|
void refreshRelayRestartMaxWaitSetting();
|
|
1993
2160
|
void refreshPushSettingsSection();
|
|
1994
2161
|
void refreshPreviewsSection();
|
|
@@ -31,7 +31,9 @@ import {
|
|
|
31
31
|
refreshSummaryModal,
|
|
32
32
|
summaryModalState,
|
|
33
33
|
IS_SHARED_VIEW,
|
|
34
|
+
showTransientRelayNotice,
|
|
34
35
|
} from './store.js';
|
|
36
|
+
import { newModelsToastFor } from './model-refresh-ui.mjs';
|
|
35
37
|
import { scheduleContextUsageRefresh } from './api-client.js';
|
|
36
38
|
import { capFinishedActivityEntries, capLiveActivityEntries, normalizeTurnSeq } from './activity-replay-state.mjs';
|
|
37
39
|
import { liveActivityCap } from './live-activity-cap.mjs';
|
|
@@ -66,6 +68,7 @@ import { applyClaudeCloudSettingsState, refreshClaudeCloudSettingsState } from '
|
|
|
66
68
|
import { applyClaudeCloudSessionEvent } from './claude-cloud-conversation-ui.js';
|
|
67
69
|
import { updateProviderReadinessInputs } from './provider-readiness.mjs';
|
|
68
70
|
import { applyCliInstallState } from './cli-install-ui.js';
|
|
71
|
+
import { applyAgentSdkState } from './agent-sdk-ui.js';
|
|
69
72
|
import { renderUpdateSection } from './update-ui.js';
|
|
70
73
|
import { applyHostSuspendState, applyRelayShutdownState } from './host-suspend-ui.js';
|
|
71
74
|
import { applyClaudeUsageLimit, applyConversationBilling, applyUsageLimitPause } from './usage-limit-ui.js';
|
|
@@ -150,6 +153,7 @@ let deps = null;
|
|
|
150
153
|
* @property {(force?: boolean) => (void | Promise<void>)} refreshModelCatalog
|
|
151
154
|
* @property {(payload?: object) => void} updateModelCatalogState
|
|
152
155
|
* @property {() => (void | Promise<void>)} reconcileOpenModelVariantModal
|
|
156
|
+
* @property {(tab?: string) => (void | Promise<void>)} [openSelectModelsModal]
|
|
153
157
|
* @property {(payload?: object) => void} applyConversationWorkspaceRootUpdate
|
|
154
158
|
* @property {(conversationId: string, title: string, updatedAt?: string | number | null) => void} applyConversationTitleUpdate
|
|
155
159
|
* @property {() => void} syncChatTitleControls
|
|
@@ -414,6 +418,18 @@ export async function connectSocket(overrideDeps) {
|
|
|
414
418
|
socket.on('models_updated', (payload) => {
|
|
415
419
|
updateModelCatalogState(payload || {});
|
|
416
420
|
void reconcileOpenModelVariantModal();
|
|
421
|
+
// A refresh that found models never seen before: one toast per event, on
|
|
422
|
+
// the clients connected now (the others get the menu dot and the tags).
|
|
423
|
+
const toast = newModelsToastFor(payload, { sharedView: IS_SHARED_VIEW });
|
|
424
|
+
if (toast) {
|
|
425
|
+
const { tab } = toast;
|
|
426
|
+
showTransientRelayNotice(toast.text, 10000, {
|
|
427
|
+
actionLabel: 'Show',
|
|
428
|
+
onAction: () => {
|
|
429
|
+
void Promise.resolve(deps?.openSelectModelsModal?.(tab)).catch(() => {});
|
|
430
|
+
},
|
|
431
|
+
});
|
|
432
|
+
}
|
|
417
433
|
});
|
|
418
434
|
// Each settings event also feeds provider readiness (provider-readiness.mjs),
|
|
419
435
|
// so a Settings save changes the empty screen without a reload.
|
|
@@ -457,6 +473,11 @@ export async function connectSocket(overrideDeps) {
|
|
|
457
473
|
socket.on('cli_install_state', (payload) => {
|
|
458
474
|
applyCliInstallState(payload || null);
|
|
459
475
|
});
|
|
476
|
+
// Agent SDK installs, the same whole-buffer contract. It also feeds
|
|
477
|
+
// provider readiness (agent-sdk-ui.js passes the snapshot on).
|
|
478
|
+
socket.on('agent_sdk_state', (payload) => {
|
|
479
|
+
applyAgentSdkState(payload || null);
|
|
480
|
+
});
|
|
460
481
|
// Self-update transitions AND npm log chunks; same whole-buffer contract as
|
|
461
482
|
// cli_install_state, so a client connecting mid-update renders the full log.
|
|
462
483
|
socket.on('update_state', (payload) => {
|
package/server/public/index.html
CHANGED
|
@@ -1673,6 +1673,15 @@
|
|
|
1673
1673
|
background: transparent;
|
|
1674
1674
|
border-color: transparent;
|
|
1675
1675
|
}
|
|
1676
|
+
/* Select Models: new models found that the modal has not shown yet. */
|
|
1677
|
+
.chat-menu-new-dot {
|
|
1678
|
+
width: calc(8px * var(--ui-scale));
|
|
1679
|
+
height: calc(8px * var(--ui-scale));
|
|
1680
|
+
border-radius: 50%;
|
|
1681
|
+
background: var(--accent);
|
|
1682
|
+
flex-shrink: 0;
|
|
1683
|
+
}
|
|
1684
|
+
.chat-menu-new-dot[hidden] { display: none; }
|
|
1676
1685
|
.chat-actions-menu-meta {
|
|
1677
1686
|
display: inline-flex;
|
|
1678
1687
|
align-items: center;
|
|
@@ -1954,6 +1963,17 @@
|
|
|
1954
1963
|
.settings-auth-row { gap: calc(5px * var(--ui-scale)); align-items: center; width: 100%; margin-top: calc(4px * var(--ui-scale)); }
|
|
1955
1964
|
.settings-auth-row-wrap { flex-wrap: wrap; row-gap: calc(4px * var(--ui-scale)); }
|
|
1956
1965
|
.settings-radio-stack { display: flex; flex-direction: column; gap: calc(4px * var(--ui-scale)); }
|
|
1966
|
+
/* Models: one "Check <provider>" row per provider, its last check under
|
|
1967
|
+
the name in the help text's colour. */
|
|
1968
|
+
#settings-modal .settings-row.model-refresh-provider-row:has(> input[type="checkbox"]) > label {
|
|
1969
|
+
flex-direction: column; align-items: flex-start; justify-content: center;
|
|
1970
|
+
}
|
|
1971
|
+
.model-refresh-provider-last {
|
|
1972
|
+
color: var(--muted);
|
|
1973
|
+
font-size: var(--fs-meta-sm);
|
|
1974
|
+
font-weight: normal;
|
|
1975
|
+
overflow-wrap: anywhere;
|
|
1976
|
+
}
|
|
1957
1977
|
/* These rows are laid out as flex and hidden from script via `.hidden = true`.
|
|
1958
1978
|
Their `display: flex` lives HERE rather than in a style="" attribute on
|
|
1959
1979
|
purpose: an inline declaration outranks every selector-based rule (short of
|
|
@@ -5933,6 +5953,42 @@
|
|
|
5933
5953
|
border-color: var(--warn, #f7c873);
|
|
5934
5954
|
color: var(--warn, #f7c873);
|
|
5935
5955
|
}
|
|
5956
|
+
.model-reasoning-chip-new {
|
|
5957
|
+
border-color: var(--accent);
|
|
5958
|
+
color: var(--accent);
|
|
5959
|
+
}
|
|
5960
|
+
/* "Turn new models on automatically" at the top of a provider tab. */
|
|
5961
|
+
.model-auto-enable-row {
|
|
5962
|
+
display: flex;
|
|
5963
|
+
align-items: center;
|
|
5964
|
+
gap: calc(5px * var(--ui-scale));
|
|
5965
|
+
padding: calc(4px * var(--ui-scale)) calc(7px * var(--ui-scale));
|
|
5966
|
+
border: 1px solid var(--border);
|
|
5967
|
+
border-radius: calc(10px * var(--ui-scale));
|
|
5968
|
+
background: var(--bg3);
|
|
5969
|
+
font-size: 0.84rem;
|
|
5970
|
+
min-height: calc(30px * var(--ui-scale));
|
|
5971
|
+
cursor: pointer;
|
|
5972
|
+
}
|
|
5973
|
+
.model-auto-enable-toggle {
|
|
5974
|
+
width: calc(18px * var(--ui-scale));
|
|
5975
|
+
height: calc(18px * var(--ui-scale));
|
|
5976
|
+
flex-shrink: 0;
|
|
5977
|
+
accent-color: var(--accent);
|
|
5978
|
+
}
|
|
5979
|
+
.model-auto-enable-copy {
|
|
5980
|
+
display: flex;
|
|
5981
|
+
flex-direction: column;
|
|
5982
|
+
gap: calc(2px * var(--ui-scale));
|
|
5983
|
+
min-width: 0;
|
|
5984
|
+
}
|
|
5985
|
+
.model-auto-enable-title {
|
|
5986
|
+
font-weight: 600;
|
|
5987
|
+
}
|
|
5988
|
+
.model-auto-enable-help {
|
|
5989
|
+
color: var(--muted);
|
|
5990
|
+
font-size: 0.78rem;
|
|
5991
|
+
}
|
|
5936
5992
|
.model-variant-list {
|
|
5937
5993
|
display: flex;
|
|
5938
5994
|
flex-direction: column;
|
|
@@ -7907,7 +7963,8 @@
|
|
|
7907
7963
|
min-height: calc(32px * var(--ui-scale));
|
|
7908
7964
|
padding: calc(5px * var(--ui-scale)) calc(4px * var(--ui-scale));
|
|
7909
7965
|
}
|
|
7910
|
-
.model-variant-checkbox
|
|
7966
|
+
.model-variant-checkbox,
|
|
7967
|
+
.model-auto-enable-toggle {
|
|
7911
7968
|
width: calc(22px * var(--ui-scale));
|
|
7912
7969
|
height: calc(22px * var(--ui-scale));
|
|
7913
7970
|
}
|
|
@@ -8226,7 +8283,7 @@
|
|
|
8226
8283
|
<button id="chat-menu-group" class="chat-actions-menu-item" type="button" role="menuitem">🧩 Session group…</button>
|
|
8227
8284
|
<button id="chat-menu-refresh-history" class="chat-actions-menu-item" type="button" role="menuitem" hidden disabled aria-hidden="true" tabindex="-1" style="display:none">🔄️ Refresh history</button>
|
|
8228
8285
|
<button id="chat-menu-inspect-tmux" class="chat-actions-menu-item" type="button" role="menuitem">🖥️ Inspect tmux console</button>
|
|
8229
|
-
<button id="chat-menu-select-models" class="chat-actions-menu-item" type="button" role="menuitem">🤗 Select Models
|
|
8286
|
+
<button id="chat-menu-select-models" class="chat-actions-menu-item" type="button" role="menuitem">🤗 Select Models<span id="chat-menu-select-models-dot" class="chat-menu-new-dot" aria-hidden="true" hidden></span></button>
|
|
8230
8287
|
<button id="chat-menu-change-cwd" class="chat-actions-menu-item" type="button" role="menuitem">🗂️ Change CWD</button>
|
|
8231
8288
|
<button id="chat-menu-git-changes" class="chat-actions-menu-item" type="button" role="menuitem">🌿 Git changes</button>
|
|
8232
8289
|
<button id="chat-menu-open-cloud-session" class="chat-actions-menu-item" type="button" role="menuitem" hidden>☁️ Open on claude.ai</button>
|
|
@@ -8663,6 +8720,31 @@
|
|
|
8663
8720
|
</div>
|
|
8664
8721
|
<div class="settings-help settings-gap-xs">A sub-agent's record keeps every tool call's full input and the first 4 KB of each result. After this many days the relay removes them from finished agents, once a day; the thoughts, the reply, notes and the one-line summary of each call stay. Always keeps everything. A setting of this relay.</div>
|
|
8665
8722
|
</section>
|
|
8723
|
+
<section class="settings-section" id="settings-section-models" aria-labelledby="settings-section-models-title">
|
|
8724
|
+
<h3 class="settings-section-title" id="settings-section-models-title">Models</h3>
|
|
8725
|
+
<div class="settings-row settings-gap">
|
|
8726
|
+
<label for="model-refresh-auto-toggle">Check for new models</label>
|
|
8727
|
+
<input
|
|
8728
|
+
id="model-refresh-auto-toggle"
|
|
8729
|
+
type="checkbox"
|
|
8730
|
+
onchange="updateModelRefreshAutoSetting(this.checked)"
|
|
8731
|
+
aria-label="Check for new models"
|
|
8732
|
+
checked
|
|
8733
|
+
>
|
|
8734
|
+
</div>
|
|
8735
|
+
<div class="settings-row settings-gap">
|
|
8736
|
+
<label for="model-refresh-interval-select">How often</label>
|
|
8737
|
+
<select id="model-refresh-interval-select" onchange="updateModelRefreshIntervalSetting(this.value)" aria-label="How often to check for new models">
|
|
8738
|
+
<option value="6">Every 6 h</option>
|
|
8739
|
+
<option value="12">Every 12 h</option>
|
|
8740
|
+
<option value="24" selected>Every 24 h</option>
|
|
8741
|
+
<option value="72">Every 3 days</option>
|
|
8742
|
+
<option value="168">Every 7 days</option>
|
|
8743
|
+
</select>
|
|
8744
|
+
</div>
|
|
8745
|
+
<div id="model-refresh-providers" class="settings-gap" role="group" aria-label="Providers checked for new models"></div>
|
|
8746
|
+
<div class="settings-help settings-gap-xs">The relay asks each switched-on provider for its model list this often. A provider whose list was refreshed in the meantime (Refresh in Select Models, a settings save, a sign-in) waits a full interval again, and a failed check is tried again after an hour at the earliest. Whether a new model starts on is set per provider in Select Models. A setting of this relay.</div>
|
|
8747
|
+
</section>
|
|
8666
8748
|
<section class="settings-section" id="settings-section-relay" aria-labelledby="settings-section-relay-title">
|
|
8667
8749
|
<h3 class="settings-section-title" id="settings-section-relay-title">This relay</h3>
|
|
8668
8750
|
<div class="settings-row settings-row-column settings-gap">
|
|
@@ -8734,6 +8816,9 @@
|
|
|
8734
8816
|
<code id="windows-autostart-manual-command" style="display: block; margin-top: calc(4px * var(--ui-scale)); user-select: all;"></code>
|
|
8735
8817
|
</div>
|
|
8736
8818
|
</div>
|
|
8819
|
+
<!-- The Windows helper (koffi) the relay's lock check reads process start
|
|
8820
|
+
times with; agent-sdk-ui.js draws the row, on a Windows relay only. -->
|
|
8821
|
+
<div id="agent-sdk-koffi-row" class="settings-gap" data-agent-sdk="koffi" hidden></div>
|
|
8737
8822
|
</section>
|
|
8738
8823
|
<section class="settings-section" id="settings-section-updates" aria-labelledby="settings-section-updates-title">
|
|
8739
8824
|
<h3 class="settings-section-title" id="settings-section-updates-title">Updates</h3>
|
|
@@ -8980,6 +9065,8 @@
|
|
|
8980
9065
|
<pre id="cli-claude-log"></pre>
|
|
8981
9066
|
</details>
|
|
8982
9067
|
</div>
|
|
9068
|
+
<!-- Agent SDK row; agent-sdk-ui.js draws it. -->
|
|
9069
|
+
<div id="agent-sdk-claude-row" data-agent-sdk="claude" style="margin-bottom: calc(6px * var(--ui-scale));"></div>
|
|
8983
9070
|
<!-- Claude account auth UI (Relogin / Logout); claude-auth-ui.js drives the state. -->
|
|
8984
9071
|
<div id="claude-auth-section">
|
|
8985
9072
|
<div class="settings-row" style="margin-bottom: 0;">
|
|
@@ -9069,6 +9156,8 @@
|
|
|
9069
9156
|
data-settings-provider-panel="claude-api"
|
|
9070
9157
|
hidden
|
|
9071
9158
|
>
|
|
9159
|
+
<!-- Claude API chats run on the Claude Agent SDK; agent-sdk-ui.js draws the row. -->
|
|
9160
|
+
<div id="agent-sdk-claude-api-row" data-agent-sdk="claude"></div>
|
|
9072
9161
|
<div class="settings-row settings-row-column settings-gap">
|
|
9073
9162
|
<label for="claude-api-key-input">Claude API</label>
|
|
9074
9163
|
<div class="settings-row settings-row-full settings-row-lead">
|
|
@@ -9274,6 +9363,8 @@
|
|
|
9274
9363
|
data-settings-provider-panel="cursor"
|
|
9275
9364
|
hidden
|
|
9276
9365
|
>
|
|
9366
|
+
<!-- Agent SDK row; agent-sdk-ui.js draws it. -->
|
|
9367
|
+
<div id="agent-sdk-cursor-row" data-agent-sdk="cursor"></div>
|
|
9277
9368
|
<div class="settings-row settings-row-column settings-gap">
|
|
9278
9369
|
<label for="cursor-api-key-input">Cursor SDK</label>
|
|
9279
9370
|
<div class="settings-row settings-row-full settings-row-lead">
|
|
@@ -9367,6 +9458,8 @@
|
|
|
9367
9458
|
data-settings-provider-panel="deepseek"
|
|
9368
9459
|
hidden
|
|
9369
9460
|
>
|
|
9461
|
+
<!-- DeepSeek chats run on the Claude Agent SDK; agent-sdk-ui.js draws the row. -->
|
|
9462
|
+
<div id="agent-sdk-deepseek-row" data-agent-sdk="claude"></div>
|
|
9370
9463
|
<div class="settings-row settings-row-column settings-gap">
|
|
9371
9464
|
<label for="deepseek-api-key-input">DeepSeek</label>
|
|
9372
9465
|
<div class="settings-row settings-row-full settings-row-lead">
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// The agent SDKs the relay installs on demand (agent-sdk-store.mjs): their
|
|
4
|
+
// status, and Install / Reinstall / Cancel from Settings. A request body only
|
|
5
|
+
// ever carries an SDK id, looked up through the store's frozen table; an
|
|
6
|
+
// unknown id is a 400 before anything runs. The store itself refuses a spawn
|
|
7
|
+
// while COPILOT_WEB_RELAY_DISABLE_CLI_SPAWN is set (503), and that answer is
|
|
8
|
+
// passed through as it is.
|
|
9
|
+
|
|
10
|
+
const UNKNOWN_SDK_ERROR = 'Unknown agent SDK';
|
|
11
|
+
|
|
12
|
+
export function registerAgentSdkRoutes(app, { auth, io = null, agentSdkStore = null } = {}) {
|
|
13
|
+
const store = agentSdkStore;
|
|
14
|
+
|
|
15
|
+
// Every transition and log flush (four a second at most, the store
|
|
16
|
+
// coalesces them) goes to every client, as cli_install_state does.
|
|
17
|
+
if (store && io && typeof store.subscribe === 'function') {
|
|
18
|
+
store.subscribe((snapshot) => {
|
|
19
|
+
io.emit('agent_sdk_state', snapshot);
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const unavailable = (res) => res.status(503).json({ error: 'Agent SDKs are unavailable on this relay' });
|
|
24
|
+
|
|
25
|
+
// A string the store knows, or null. Only a string: String(['claude'])
|
|
26
|
+
// would otherwise read as a valid id.
|
|
27
|
+
const knownId = (raw) => (typeof raw === 'string' && store.status(raw) ? raw : null);
|
|
28
|
+
|
|
29
|
+
app.get('/api/agent-sdks/status', auth, (_req, res) => {
|
|
30
|
+
if (!store) return unavailable(res);
|
|
31
|
+
return res.json(store.snapshot());
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
app.post('/api/agent-sdks/install', auth, (req, res) => {
|
|
35
|
+
if (!store) return unavailable(res);
|
|
36
|
+
const sdk = knownId(req.body?.sdk);
|
|
37
|
+
if (!sdk) return res.status(400).json({ error: UNKNOWN_SDK_ERROR, ...store.snapshot() });
|
|
38
|
+
let result;
|
|
39
|
+
try {
|
|
40
|
+
result = store.install(sdk, { force: req.body?.reinstall === true });
|
|
41
|
+
} catch (error) {
|
|
42
|
+
return res.status(500).json({ error: `The install could not start: ${String(error?.message || error)}`, ...store.snapshot() });
|
|
43
|
+
}
|
|
44
|
+
if (!result?.ok) {
|
|
45
|
+
return res.status(Number(result?.statusCode) || 400).json({
|
|
46
|
+
error: result?.error || 'Failed to start the agent SDK install',
|
|
47
|
+
...store.snapshot(),
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
// The rows ride along so the caller renders without a second round trip;
|
|
51
|
+
// the install itself finishes over the socket.
|
|
52
|
+
return res.json({
|
|
53
|
+
ok: true,
|
|
54
|
+
reused: result.reused === true,
|
|
55
|
+
ready: result.ready === true,
|
|
56
|
+
...store.snapshot(),
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
app.post('/api/agent-sdks/install/cancel', auth, (req, res) => {
|
|
61
|
+
if (!store) return unavailable(res);
|
|
62
|
+
const sdk = knownId(req.body?.sdk);
|
|
63
|
+
if (!sdk) return res.status(400).json({ error: UNKNOWN_SDK_ERROR, ...store.snapshot() });
|
|
64
|
+
const result = store.cancel(sdk);
|
|
65
|
+
if (!result?.ok) {
|
|
66
|
+
return res.status(Number(result?.statusCode) || 400).json({
|
|
67
|
+
error: result?.error || 'Failed to cancel the agent SDK install',
|
|
68
|
+
...store.snapshot(),
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
return res.json({ ok: true, ...store.snapshot() });
|
|
72
|
+
});
|
|
73
|
+
}
|