@oar-sh/oar 0.9.23 → 0.9.25
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/skills/reasoning-stream.mjs +3 -1
- package/CHANGELOG.md +307 -0
- package/README.md +12 -11
- package/bin/oar.js +54 -4
- package/package.json +11 -7
- 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-preview-tool.mjs +5 -2
- package/server/claude-worker/claude-remote-relay-tool.mjs +20 -2
- package/server/claude-worker/claude-sdk-adapter.mjs +4 -1
- package/server/claude-worker/sdk-message-normalizer.mjs +29 -1
- package/server/copilot-worker/copilot-sdk-session-process.mjs +13 -8
- package/server/cursor-worker/cursor-sdk-adapter.mjs +5 -3
- package/server/cursor-worker/sdk-message-normalizer.mjs +13 -0
- package/server/db-schema.mjs +19 -0
- package/server/grok-worker/sdk-message-normalizer.mjs +3 -0
- package/server/migrations/0039-media-index.mjs +167 -0
- package/server/migrations/0040-image-session-defaults.mjs +93 -0
- package/server/public/app/agent-sdk-ui.js +349 -0
- package/server/public/app/api-client.js +149 -8
- package/server/public/app/ask-user-view.js +2 -1
- package/server/public/app/attachments-view.js +66 -9
- package/server/public/app/bootstrap.js +289 -94
- package/server/public/app/chat-actions-menu-state.mjs +1 -0
- package/server/public/app/context-media-tab.js +143 -0
- package/server/public/app/conversation-view.js +90 -5
- package/server/public/app/image-composer-ui.js +518 -0
- package/server/public/app/image-turn-placeholder.mjs +233 -0
- package/server/public/app/journal-view.js +12 -2
- package/server/public/app/media-gallery-model.mjs +202 -0
- package/server/public/app/media-gallery-ui.js +524 -0
- package/server/public/app/media-view.js +79 -0
- package/server/public/app/model-refresh-ui.mjs +413 -0
- package/server/public/app/no-conversation-state.mjs +5 -2
- package/server/public/app/openai-image-composer.mjs +492 -0
- package/server/public/app/openai-image-cost-ui.js +426 -0
- package/server/public/app/openai-image-spend.mjs +443 -0
- package/server/public/app/plan-usage-view.mjs +38 -6
- package/server/public/app/provider-readiness.mjs +67 -4
- package/server/public/app/relay-error-ctas.mjs +5 -0
- package/server/public/app/session-history-ui.js +2 -0
- package/server/public/app/settings-modal.js +171 -0
- package/server/public/app/socket-handlers.js +47 -0
- package/server/public/app/tab-strip-fade.mjs +1 -1
- package/server/public/app/viewer-gallery.mjs +23 -1
- package/server/public/index.html +478 -9
- package/server/relay-tools.md +1 -0
- package/server/repositories/image-conversation-repository.mjs +69 -0
- package/server/repositories/media-repository.mjs +305 -0
- package/server/routes/agent-sdk-routes.mjs +73 -0
- package/server/routes/media-routes.mjs +361 -0
- package/server/routes/messages-routes.mjs +1467 -343
- package/server/routes/sessions-routes.mjs +596 -123
- package/server/server-runtime.mjs +414 -81
- 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 +23 -5
- package/server/services/deepseek-settings-service.mjs +58 -20
- package/server/services/earlier-database-service.mjs +4 -2
- package/server/services/generated-image-cleanup-service.mjs +2 -1
- package/server/services/generated-image-files.mjs +110 -0
- package/server/services/image-input-paths.mjs +389 -0
- package/server/services/image-operation-service.mjs +6 -2
- package/server/services/media-extract.mjs +653 -0
- package/server/services/media-index-service.mjs +221 -0
- package/server/services/model-refresh-scheduler.mjs +153 -0
- package/server/services/model-variant-catalog-service.mjs +141 -9
- package/server/services/oar-cli-helpers.mjs +72 -9
- package/server/services/oar-state-migration-service.mjs +2 -12
- package/server/services/oar-state-root.mjs +17 -0
- package/server/services/openai-image-preview-turns.mjs +330 -0
- package/server/services/openai-image-spend-service.mjs +516 -0
- package/server/services/openai-image-stream.mjs +170 -0
- package/server/services/plan-usage-openai-image.mjs +148 -0
- package/server/services/provider-model-refresh.mjs +326 -0
- package/server/services/provider-model-state.mjs +395 -0
- package/server/services/relay-singleton-guard.mjs +72 -10
- package/server/services/remote-relay-caller-context.mjs +6 -0
- package/server/services/remote-relay-dispatcher.mjs +386 -22
- package/server/services/remote-relay-registry-service.mjs +3 -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/token-usage-service.mjs +15 -0
- package/server/services/update-install-service.mjs +2 -2
- package/server/services/worker-launch-note-service.mjs +14 -2
- package/server/services/worker-recycle-service.mjs +145 -11
- package/shared/image-header.mjs +90 -0
- package/shared/model-refresh-schedule.mjs +120 -0
- package/shared/openai-image-options.mjs +299 -0
- package/shared/openai-image-presets.mjs +175 -0
- package/shared/openai-image-previews.mjs +40 -0
- package/shared/openai-image-prices.mjs +291 -0
- package/shared/openai-image-spend.mjs +90 -0
- package/shared/remote-relay-contract.mjs +126 -4
- package/shared/worker-runtime/direct-openai-image.mjs +98 -48
|
@@ -68,12 +68,15 @@ import { createClaudeCloudCredentials } from '../shared/claude-cloud/credentials
|
|
|
68
68
|
import { createClaudeCloudClient } from '../shared/claude-cloud/api-client.mjs';
|
|
69
69
|
import { resolveClaudeCloudBaseUrl } from '../shared/claude-cloud/base-url.mjs';
|
|
70
70
|
import { createClaudeCloudSettingsService } from './services/claude-cloud-settings-service.mjs';
|
|
71
|
-
import { createDeepSeekSettingsService } from './services/deepseek-settings-service.mjs';
|
|
71
|
+
import { DEEPSEEK_API_KEY_SETTING_KEY, createDeepSeekSettingsService } from './services/deepseek-settings-service.mjs';
|
|
72
72
|
import { createWorkerPostRedaction } from './services/worker-secret-redaction.mjs';
|
|
73
73
|
import { createDeepSeekBalanceService } from './services/deepseek-balance-service.mjs';
|
|
74
74
|
import { deepseekCardCurrency } from './services/plan-usage-deepseek.mjs';
|
|
75
75
|
import { createDeepSeekUsageImportService } from './services/deepseek-usage-import.mjs';
|
|
76
|
-
import { createClaudeApiSettingsService } from './services/claude-api-settings-service.mjs';
|
|
76
|
+
import { CLAUDE_API_KEY_SETTING_KEY, createClaudeApiSettingsService } from './services/claude-api-settings-service.mjs';
|
|
77
|
+
import { createProviderModelStateStore } from './services/provider-model-state.mjs';
|
|
78
|
+
import { buildProviderRefreshTable, createModelRefreshLedger, createModelRefreshSettings, refreshProviderModels } from './services/provider-model-refresh.mjs';
|
|
79
|
+
import { createModelRefreshScheduler } from './services/model-refresh-scheduler.mjs';
|
|
77
80
|
import { createClaudeBillingSwitchService, normalizeClaudeBilling } from './services/claude-billing-switch-service.mjs';
|
|
78
81
|
import { createClaudeBillingSwapService } from './services/claude-billing-swap-service.mjs';
|
|
79
82
|
import { createWorkerReplacement, createWorkerReplacementFence } from './services/worker-replacement.mjs';
|
|
@@ -81,6 +84,7 @@ import { sessionLaunchMutex } from './services/session-launch-mutex-service.mjs'
|
|
|
81
84
|
import { createWorkerRecycleService, sweepArchivedWorkers } from './services/worker-recycle-service.mjs';
|
|
82
85
|
import { createClaudeApiUsageService } from './services/claude-api-usage-service.mjs';
|
|
83
86
|
import { createTokenUsageService } from './services/token-usage-service.mjs';
|
|
87
|
+
import { createOpenAIImageSpendService } from './services/openai-image-spend-service.mjs';
|
|
84
88
|
import { createClaudeApiCapWatch, createClaudeApiPlanUsage, createClaudeApiStopNoteLedger } from './services/plan-usage-claude-api.mjs';
|
|
85
89
|
import { DEEPSEEK_DEFAULT_AUTO_COMPACT_WINDOW, DEFAULT_DEEPSEEK_MODEL, deepseekModelLabel } from '../shared/deepseek-models.mjs';
|
|
86
90
|
import { createClaudeCloudSessionService } from './services/claude-cloud-session-service.mjs';
|
|
@@ -88,6 +92,10 @@ import { createClaudeCloudRepoService, listRecentCloudSourcesFromStatements } fr
|
|
|
88
92
|
import { createClaudeAccountUsageService } from './services/claude-account-usage-service.mjs';
|
|
89
93
|
import { createGitRemoteService } from './services/git-remote-service.mjs';
|
|
90
94
|
import { createCliInstallService, writeCliBinariesToConfigFile } from './services/cli-install-service.mjs';
|
|
95
|
+
import { createAgentSdkStore } from './services/agent-sdk-store.mjs';
|
|
96
|
+
import { createAgentSdkLaunchGate, staleWorkerLosesAgentSdk } from './services/agent-sdk-launch-gate.mjs';
|
|
97
|
+
import { agentSdkFromCheckout } from './services/agent-sdk-paths.mjs';
|
|
98
|
+
import { agentSdkDirEnvName, AgentSdkMissingError, loadAgentSdk, resolveAgentSdkSource, setAgentSdkStore } from './agent-sdk-loader.mjs';
|
|
91
99
|
import { createGrokAuthService } from './services/grok-auth-service.mjs';
|
|
92
100
|
import { sweepUnreferencedUploads, UNREFERENCED_UPLOADS_QUERY } from './services/upload-sweep.mjs';
|
|
93
101
|
import webpush from 'web-push';
|
|
@@ -123,6 +131,7 @@ import { createActiveDeviceTracker } from './services/push-active-device-service
|
|
|
123
131
|
import { registerPushRoutes } from './routes/push-routes.mjs';
|
|
124
132
|
import { registerAboutRoutes } from './routes/about-routes.mjs';
|
|
125
133
|
import { registerRelayRestartRoutes } from './routes/relay-restart-routes.mjs';
|
|
134
|
+
import { registerAgentSdkRoutes } from './routes/agent-sdk-routes.mjs';
|
|
126
135
|
import { createAboutService } from './services/about-service.mjs';
|
|
127
136
|
import { resolveBuildId } from '../shared/build-id.mjs';
|
|
128
137
|
import { createImageOperationService } from './services/image-operation-service.mjs';
|
|
@@ -137,7 +146,7 @@ import { createPlanUsageService } from './services/plan-usage-service.mjs';
|
|
|
137
146
|
import { splitFreeAndBufferTokens } from './services/context-usage-view.mjs';
|
|
138
147
|
import { normalizeCursorAllowanceSettings } from './services/plan-usage-cursor.mjs';
|
|
139
148
|
import { normalizeGrokAllowanceSettings } from './services/plan-usage-grok.mjs';
|
|
140
|
-
import { createGrokBillingUsageFetcher } from './services/grok-billing-usage.mjs';
|
|
149
|
+
import { createGrokBillingUsageFetcher, readGrokCliAuthKey } from './services/grok-billing-usage.mjs';
|
|
141
150
|
import { createCursorDashboardUsageFetcher, readCursorIdeSessionToken } from './services/cursor-dashboard-usage.mjs';
|
|
142
151
|
import { fetchPersonalBillingUsage } from './services/github-billing-usage.mjs';
|
|
143
152
|
import { createSessionTranscriptService } from './services/session-transcript-service.mjs';
|
|
@@ -163,7 +172,7 @@ import { createRelayCliLauncherService } from './services/relay-cli-launcher-ser
|
|
|
163
172
|
import { createSessionWorkerRegistry, normalizeWorkerKind } from './services/session-worker-registry-service.mjs';
|
|
164
173
|
import { createSessionWorkerSupervisor } from './services/session-worker-supervisor-service.mjs';
|
|
165
174
|
import { createSessionWorkerProcessInspector } from './services/session-worker-process-service.mjs';
|
|
166
|
-
import { applyClaudeApiProviderEnvironment, applyClaudeCloudProviderEnvironment, applyClaudeProviderEnvironment, applyCopilotSdkProviderEnvironment, applyCursorProviderEnvironment, applyDeepSeekProviderEnvironment, applyGrokProviderEnvironment, applyOpenAIProviderEnvironment, copilotSdkEngineUnavailableReason, killTmuxSession, launchSessionCli } from './services/session-worker-launch-service.mjs';
|
|
175
|
+
import { agentSdkForWorkerKind, applyClaudeApiProviderEnvironment, applyClaudeCloudProviderEnvironment, applyClaudeProviderEnvironment, applyCopilotSdkProviderEnvironment, applyCursorProviderEnvironment, applyDeepSeekProviderEnvironment, applyGrokProviderEnvironment, applyOpenAIProviderEnvironment, copilotSdkEngineUnavailableReason, killTmuxSession, launchSessionCli, resolveWorkerKind } from './services/session-worker-launch-service.mjs';
|
|
167
176
|
import { createSshTunnelManager } from './services/ssh-tunnel-manager-service.mjs';
|
|
168
177
|
import { createCloudflaredTunnelManager } from './services/cloudflared-tunnel-service.mjs';
|
|
169
178
|
import {
|
|
@@ -375,6 +384,29 @@ for (const dir of UPLOAD_READ_DIRS.slice(1)) {
|
|
|
375
384
|
} catch {}
|
|
376
385
|
}
|
|
377
386
|
|
|
387
|
+
// Which models of OpenAI, Claude, Cursor and Grok are on (the DeepSeek and
|
|
388
|
+
// Claude API services keep their maps the same way).
|
|
389
|
+
const providerModelState = createProviderModelStateStore({
|
|
390
|
+
readSetting: readAppSettingValue,
|
|
391
|
+
writeSetting: (key, value, stamp) => stmts.upsertAppSetting.run(key, value, stamp),
|
|
392
|
+
});
|
|
393
|
+
// When each provider's models were last refreshed, by anything, and the new
|
|
394
|
+
// models the next models_updated announces (provider-model-refresh.mjs).
|
|
395
|
+
const modelRefreshLedger = createModelRefreshLedger({
|
|
396
|
+
readSetting: readAppSettingValue,
|
|
397
|
+
writeSetting: (key, value, stamp) => stmts.upsertAppSetting.run(key, value, stamp),
|
|
398
|
+
// What a provider's refresh error may echo: its key or token, cut out
|
|
399
|
+
// before the error is stored, shown in Settings or logged.
|
|
400
|
+
secretsFor: (provider) => ({
|
|
401
|
+
openai: () => [readAppSettingValue(OPENAI_API_KEY_SETTING_KEY)],
|
|
402
|
+
cursor: () => [readAppSettingValue(CURSOR_API_KEY_SETTING_KEY), readCursorDashboardSessionToken()],
|
|
403
|
+
deepseek: () => [readAppSettingValue(DEEPSEEK_API_KEY_SETTING_KEY)],
|
|
404
|
+
'claude-api': () => [readAppSettingValue(CLAUDE_API_KEY_SETTING_KEY)],
|
|
405
|
+
grok: () => [process.env.XAI_API_KEY, readGrokCliAuthKey()?.key],
|
|
406
|
+
claude: () => [process.env.ANTHROPIC_API_KEY],
|
|
407
|
+
}[provider]?.() || []),
|
|
408
|
+
});
|
|
409
|
+
|
|
378
410
|
async function refreshOpenAIProviderModels() {
|
|
379
411
|
const settings = getOpenAIProviderSettings();
|
|
380
412
|
if (!settings.enabled) return { ok: false, models: [], error: 'OpenAI API key is not configured' };
|
|
@@ -410,7 +442,8 @@ async function refreshOpenAIProviderModels() {
|
|
|
410
442
|
.filter((modelId) => isOpenAIModelId(modelId)),
|
|
411
443
|
])).filter(Boolean);
|
|
412
444
|
stmts.upsertAppSetting.run(OPENAI_MODELS_SETTING_KEY, JSON.stringify(models), new Date().toISOString());
|
|
413
|
-
|
|
445
|
+
const { added } = providerModelState.reconcile('openai', { availableModels: getOpenAIProviderSettings().availableModels });
|
|
446
|
+
return { ok: true, models, newModels: added, error: null };
|
|
414
447
|
}
|
|
415
448
|
|
|
416
449
|
function getOpenAIProviderSettings() {
|
|
@@ -427,12 +460,21 @@ function getOpenAIProviderSettings() {
|
|
|
427
460
|
} catch {
|
|
428
461
|
models = [];
|
|
429
462
|
}
|
|
463
|
+
const availableModels = Array.from(new Set([model, ...models]));
|
|
464
|
+
// Every discovered model was offered before the model map: an upgrade
|
|
465
|
+
// keeps them all on.
|
|
466
|
+
const enabledModels = providerModelState.resolve('openai', { availableModels, defaultModel: model });
|
|
430
467
|
return {
|
|
431
468
|
configured: !!apiKey,
|
|
432
469
|
enabled,
|
|
433
470
|
apiKey,
|
|
434
471
|
model,
|
|
435
|
-
models:
|
|
472
|
+
models: enabledModels.models,
|
|
473
|
+
availableModels,
|
|
474
|
+
enabledModels: enabledModels.models,
|
|
475
|
+
autoEnableNew: enabledModels.autoEnableNew,
|
|
476
|
+
fallback: enabledModels.fallback,
|
|
477
|
+
unseenModels: enabledModels.unseen,
|
|
436
478
|
baseUrl: readAppSettingValue(OPENAI_BASE_URL_SETTING_KEY) || DEFAULT_OPENAI_BASE_URL,
|
|
437
479
|
providerType: 'openai',
|
|
438
480
|
};
|
|
@@ -443,6 +485,7 @@ function setOpenAIProviderSettings(update = {}) {
|
|
|
443
485
|
const apiKey = payload.apiKey;
|
|
444
486
|
const model = payload.model;
|
|
445
487
|
const enabled = payload.enabled;
|
|
488
|
+
const enabledModels = payload.enabledModels;
|
|
446
489
|
const remove = payload.remove === true;
|
|
447
490
|
const hasBaseUrl = Object.prototype.hasOwnProperty.call(payload, 'baseUrl');
|
|
448
491
|
const baseUrl = hasBaseUrl ? payload.baseUrl : '';
|
|
@@ -481,6 +524,7 @@ function setOpenAIProviderSettings(update = {}) {
|
|
|
481
524
|
activeQueueConversationCount,
|
|
482
525
|
};
|
|
483
526
|
}
|
|
527
|
+
// The model map stays: a key added again finds its models as they were.
|
|
484
528
|
stmts.deleteAppSetting.run(OPENAI_API_KEY_SETTING_KEY);
|
|
485
529
|
stmts.deleteAppSetting.run(OPENAI_MODELS_SETTING_KEY);
|
|
486
530
|
stmts.upsertAppSetting.run(OPENAI_ENABLED_SETTING_KEY, 'false', nowIso);
|
|
@@ -504,6 +548,21 @@ function setOpenAIProviderSettings(update = {}) {
|
|
|
504
548
|
stmts.deleteAppSetting.run(OPENAI_BASE_URL_SETTING_KEY);
|
|
505
549
|
}
|
|
506
550
|
}
|
|
551
|
+
if (Array.isArray(enabledModels) || typeof payload.autoEnableNew === 'boolean') {
|
|
552
|
+
// The models Select Models showed are set as ticked; an empty list
|
|
553
|
+
// leaves the default model alone, never all.
|
|
554
|
+
providerModelState.select('openai', {
|
|
555
|
+
enabledModels: Array.isArray(enabledModels)
|
|
556
|
+
? enabledModels.map((value) => String(value || '').trim()).filter((value) => value && isSafeProviderModelId(value))
|
|
557
|
+
: undefined,
|
|
558
|
+
// The rows the browser drew; an older page sends none.
|
|
559
|
+
shownModels: Array.isArray(payload.shownModels)
|
|
560
|
+
? payload.shownModels.map((value) => String(value || '').trim()).filter((value) => value && isSafeProviderModelId(value))
|
|
561
|
+
: getOpenAIProviderSettings().availableModels,
|
|
562
|
+
defaultModel: normalizedModel,
|
|
563
|
+
autoEnableNew: payload.autoEnableNew,
|
|
564
|
+
});
|
|
565
|
+
}
|
|
507
566
|
}
|
|
508
567
|
const current = getOpenAIProviderSettings();
|
|
509
568
|
return {
|
|
@@ -511,6 +570,9 @@ function setOpenAIProviderSettings(update = {}) {
|
|
|
511
570
|
configured: current.configured,
|
|
512
571
|
enabled: current.enabled,
|
|
513
572
|
model: current.model,
|
|
573
|
+
models: current.models,
|
|
574
|
+
availableModels: current.availableModels,
|
|
575
|
+
autoEnableNew: current.autoEnableNew,
|
|
514
576
|
baseUrl: current.baseUrl,
|
|
515
577
|
};
|
|
516
578
|
}
|
|
@@ -554,15 +616,14 @@ function getClaudeProviderSettings() {
|
|
|
554
616
|
model,
|
|
555
617
|
...(discovered.length ? discovered : DEFAULT_CLAUDE_MODELS),
|
|
556
618
|
])).filter(Boolean);
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
])).filter(Boolean);
|
|
619
|
+
// The models that are on (Select Models and the model map) drive the
|
|
620
|
+
// catalog and composers; the default model always is.
|
|
621
|
+
const enabledModels = providerModelState.resolve('claude', {
|
|
622
|
+
availableModels,
|
|
623
|
+
defaultModel: model,
|
|
624
|
+
legacyAllowlist: () => readClaudeModelListSetting(CLAUDE_ENABLED_MODELS_SETTING_KEY),
|
|
625
|
+
});
|
|
626
|
+
const models = enabledModels.models;
|
|
566
627
|
const storedEfforts = readClaudeModelEffortsSetting();
|
|
567
628
|
const effortsByModel = {};
|
|
568
629
|
for (const availableModel of availableModels) {
|
|
@@ -586,6 +647,9 @@ function getClaudeProviderSettings() {
|
|
|
586
647
|
models,
|
|
587
648
|
availableModels,
|
|
588
649
|
enabledModels: models,
|
|
650
|
+
autoEnableNew: enabledModels.autoEnableNew,
|
|
651
|
+
fallback: enabledModels.fallback,
|
|
652
|
+
unseenModels: enabledModels.unseen,
|
|
589
653
|
effortsByModel,
|
|
590
654
|
modelsDiscovered,
|
|
591
655
|
attributionMode: normalizeClaudeAttributionMode(readAppSettingValue(CLAUDE_ATTRIBUTION_SETTING_KEY)) || DEFAULT_CLAUDE_ATTRIBUTION_MODE,
|
|
@@ -644,15 +708,20 @@ function setClaudeProviderSettings(update = {}) {
|
|
|
644
708
|
stmts.deleteAppSetting.run(CLAUDE_MODELS_SETTING_KEY);
|
|
645
709
|
}
|
|
646
710
|
}
|
|
647
|
-
if (Array.isArray(enabledModels)) {
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
711
|
+
if (Array.isArray(enabledModels) || typeof payload.autoEnableNew === 'boolean') {
|
|
712
|
+
// The models Select Models showed are set as ticked; an empty list
|
|
713
|
+
// leaves the default model alone, never all.
|
|
714
|
+
providerModelState.select('claude', {
|
|
715
|
+
enabledModels: Array.isArray(enabledModels)
|
|
716
|
+
? enabledModels.map((value) => String(value || '').trim()).filter((value) => value && isSafeClaudeModelId(value))
|
|
717
|
+
: undefined,
|
|
718
|
+
// The rows the browser drew; an older page sends none.
|
|
719
|
+
shownModels: Array.isArray(payload.shownModels)
|
|
720
|
+
? payload.shownModels.map((value) => String(value || '').trim()).filter((value) => value && isSafeClaudeModelId(value))
|
|
721
|
+
: getClaudeProviderSettings().availableModels,
|
|
722
|
+
defaultModel: normalizedModel,
|
|
723
|
+
autoEnableNew: payload.autoEnableNew,
|
|
724
|
+
});
|
|
656
725
|
}
|
|
657
726
|
const current = getClaudeProviderSettings();
|
|
658
727
|
return {
|
|
@@ -662,6 +731,7 @@ function setClaudeProviderSettings(update = {}) {
|
|
|
662
731
|
model: current.model,
|
|
663
732
|
models: current.models,
|
|
664
733
|
availableModels: current.availableModels,
|
|
734
|
+
autoEnableNew: current.autoEnableNew,
|
|
665
735
|
attributionMode: current.attributionMode,
|
|
666
736
|
};
|
|
667
737
|
}
|
|
@@ -709,7 +779,7 @@ async function refreshClaudeProviderModels() {
|
|
|
709
779
|
let claudeQuery = null;
|
|
710
780
|
let releaseInputGate = () => {};
|
|
711
781
|
try {
|
|
712
|
-
const { query } = await
|
|
782
|
+
const { query } = await loadAgentSdk('claude');
|
|
713
783
|
const inputGate = new Promise((resolve) => { releaseInputGate = resolve; });
|
|
714
784
|
claudeQuery = query({
|
|
715
785
|
// Streaming-input mode with a gated (never-yielding) prompt keeps the
|
|
@@ -762,8 +832,13 @@ async function refreshClaudeProviderModels() {
|
|
|
762
832
|
const mergedEfforts = mergeClaudeModelEfforts(readClaudeModelEffortsSetting(), effortsByModel);
|
|
763
833
|
stmts.upsertAppSetting.run(CLAUDE_MODELS_SETTING_KEY, JSON.stringify(models), nowIso);
|
|
764
834
|
stmts.upsertAppSetting.run(CLAUDE_MODEL_EFFORTS_SETTING_KEY, JSON.stringify(mergedEfforts), nowIso);
|
|
765
|
-
|
|
835
|
+
const { added } = providerModelState.reconcile('claude', { availableModels: getClaudeProviderSettings().availableModels });
|
|
836
|
+
return { ok: true, models, newModels: added, error: null };
|
|
766
837
|
} catch (error) {
|
|
838
|
+
// No SDK installed yet: nothing is asked, the stored catalogue stays.
|
|
839
|
+
if (error instanceof AgentSdkMissingError) {
|
|
840
|
+
return { ok: false, models: settings.models, sdkMissing: true, error: `Claude model discovery waits for the SDK: ${error.message}` };
|
|
841
|
+
}
|
|
767
842
|
return {
|
|
768
843
|
ok: false,
|
|
769
844
|
models: settings.models,
|
|
@@ -827,24 +902,19 @@ function getCursorProviderSettings() {
|
|
|
827
902
|
models = [];
|
|
828
903
|
}
|
|
829
904
|
const availableModels = Array.from(new Set([model, ...(models.length ? models : DEFAULT_CURSOR_MODELS)]));
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
}
|
|
842
|
-
|
|
843
|
-
// catalog and composers; an empty selection means "all available".
|
|
844
|
-
const resolvedModels = Array.from(new Set([
|
|
845
|
-
model,
|
|
846
|
-
...(enabledSelection.length ? enabledSelection : availableModels),
|
|
847
|
-
])).filter(Boolean);
|
|
905
|
+
// The models that are on (Select Models and the model map) drive the
|
|
906
|
+
// catalog and composers; the default model always is.
|
|
907
|
+
const enabledModels = providerModelState.resolve('cursor', {
|
|
908
|
+
availableModels,
|
|
909
|
+
defaultModel: model,
|
|
910
|
+
legacyAllowlist: () => {
|
|
911
|
+
const parsed = JSON.parse(readAppSettingValue(CURSOR_ENABLED_MODELS_SETTING_KEY) || '[]');
|
|
912
|
+
return Array.isArray(parsed)
|
|
913
|
+
? parsed.map((value) => String(value || '').trim()).filter((modelId) => isSafeCursorModelId(modelId))
|
|
914
|
+
: [];
|
|
915
|
+
},
|
|
916
|
+
});
|
|
917
|
+
const resolvedModels = enabledModels.models;
|
|
848
918
|
const storedEfforts = readCursorModelEffortsSetting();
|
|
849
919
|
const storedReasoningOff = readCursorModelReasoningOffSetting();
|
|
850
920
|
const effortsByModel = {};
|
|
@@ -868,6 +938,9 @@ function getCursorProviderSettings() {
|
|
|
868
938
|
models: resolvedModels,
|
|
869
939
|
availableModels,
|
|
870
940
|
enabledModels: resolvedModels,
|
|
941
|
+
autoEnableNew: enabledModels.autoEnableNew,
|
|
942
|
+
fallback: enabledModels.fallback,
|
|
943
|
+
unseenModels: enabledModels.unseen,
|
|
871
944
|
effortsByModel,
|
|
872
945
|
reasoningOffByModel,
|
|
873
946
|
// Pre-effort installs have models but no efforts setting; the settings
|
|
@@ -1109,9 +1182,9 @@ function setCursorProviderSettings(update = {}) {
|
|
|
1109
1182
|
activeQueueConversationCount,
|
|
1110
1183
|
};
|
|
1111
1184
|
}
|
|
1185
|
+
// The model map stays: a key added again finds its models as they were.
|
|
1112
1186
|
stmts.deleteAppSetting.run(CURSOR_API_KEY_SETTING_KEY);
|
|
1113
1187
|
stmts.deleteAppSetting.run(CURSOR_MODELS_SETTING_KEY);
|
|
1114
|
-
stmts.deleteAppSetting.run(CURSOR_ENABLED_MODELS_SETTING_KEY);
|
|
1115
1188
|
stmts.deleteAppSetting.run(CURSOR_MODEL_EFFORTS_SETTING_KEY);
|
|
1116
1189
|
stmts.deleteAppSetting.run(CURSOR_MODEL_REASONING_OFF_SETTING_KEY);
|
|
1117
1190
|
stmts.upsertAppSetting.run(CURSOR_ENABLED_SETTING_KEY, 'false', nowIso);
|
|
@@ -1129,15 +1202,20 @@ function setCursorProviderSettings(update = {}) {
|
|
|
1129
1202
|
if (nextEnabled && !hasApiKey) return { ok: false, error: 'Cursor API key is not configured' };
|
|
1130
1203
|
stmts.upsertAppSetting.run(CURSOR_ENABLED_SETTING_KEY, nextEnabled ? 'true' : 'false', nowIso);
|
|
1131
1204
|
stmts.upsertAppSetting.run(CURSOR_MODEL_SETTING_KEY, normalizedModel, nowIso);
|
|
1132
|
-
if (Array.isArray(enabledModels)) {
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1205
|
+
if (Array.isArray(enabledModels) || typeof payload.autoEnableNew === 'boolean') {
|
|
1206
|
+
// The models Select Models showed are set as ticked; an empty list
|
|
1207
|
+
// leaves the default model alone, never all.
|
|
1208
|
+
providerModelState.select('cursor', {
|
|
1209
|
+
enabledModels: Array.isArray(enabledModels)
|
|
1210
|
+
? enabledModels.map((value) => String(value || '').trim()).filter((value) => value && isSafeCursorModelId(value))
|
|
1211
|
+
: undefined,
|
|
1212
|
+
// The rows the browser drew; an older page sends none.
|
|
1213
|
+
shownModels: Array.isArray(payload.shownModels)
|
|
1214
|
+
? payload.shownModels.map((value) => String(value || '').trim()).filter((value) => value && isSafeCursorModelId(value))
|
|
1215
|
+
: getCursorProviderSettings().availableModels,
|
|
1216
|
+
defaultModel: normalizedModel,
|
|
1217
|
+
autoEnableNew: payload.autoEnableNew,
|
|
1218
|
+
});
|
|
1141
1219
|
}
|
|
1142
1220
|
}
|
|
1143
1221
|
const current = getCursorProviderSettings();
|
|
@@ -1148,6 +1226,7 @@ function setCursorProviderSettings(update = {}) {
|
|
|
1148
1226
|
model: current.model,
|
|
1149
1227
|
models: current.models,
|
|
1150
1228
|
availableModels: current.availableModels,
|
|
1229
|
+
autoEnableNew: current.autoEnableNew,
|
|
1151
1230
|
};
|
|
1152
1231
|
}
|
|
1153
1232
|
|
|
@@ -1204,7 +1283,7 @@ async function refreshCursorProviderModels() {
|
|
|
1204
1283
|
return { ok: false, models: settings.models, error: 'Cursor API key is not configured' };
|
|
1205
1284
|
}
|
|
1206
1285
|
try {
|
|
1207
|
-
const mod = await
|
|
1286
|
+
const mod = await loadAgentSdk('cursor');
|
|
1208
1287
|
// The namespace form is the only working call shape: `Cursor` has a
|
|
1209
1288
|
// private constructor and `models` is static, so the old client-instance
|
|
1210
1289
|
// fallback could only throw a TypeError that replaced the real error.
|
|
@@ -1243,8 +1322,12 @@ async function refreshCursorProviderModels() {
|
|
|
1243
1322
|
stmts.upsertAppSetting.run(CURSOR_MODELS_SETTING_KEY, JSON.stringify(models), nowIso);
|
|
1244
1323
|
stmts.upsertAppSetting.run(CURSOR_MODEL_EFFORTS_SETTING_KEY, JSON.stringify(effortsByModel), nowIso);
|
|
1245
1324
|
stmts.upsertAppSetting.run(CURSOR_MODEL_REASONING_OFF_SETTING_KEY, JSON.stringify(reasoningOffByModel), nowIso);
|
|
1246
|
-
|
|
1325
|
+
const { added } = providerModelState.reconcile('cursor', { availableModels: getCursorProviderSettings().availableModels });
|
|
1326
|
+
return { ok: true, models, newModels: added, error: null };
|
|
1247
1327
|
} catch (error) {
|
|
1328
|
+
if (error instanceof AgentSdkMissingError) {
|
|
1329
|
+
return { ok: false, models: settings.models, sdkMissing: true, error: `Cursor model discovery waits for the SDK: ${error.message}` };
|
|
1330
|
+
}
|
|
1248
1331
|
return {
|
|
1249
1332
|
ok: false,
|
|
1250
1333
|
models: settings.models,
|
|
@@ -1294,13 +1377,12 @@ function getGrokProviderSettings() {
|
|
|
1294
1377
|
model,
|
|
1295
1378
|
...(discovered.length ? discovered : DEFAULT_GROK_MODELS),
|
|
1296
1379
|
])).filter(Boolean);
|
|
1297
|
-
const
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
])).filter(Boolean);
|
|
1380
|
+
const enabledModels = providerModelState.resolve('grok', {
|
|
1381
|
+
availableModels,
|
|
1382
|
+
defaultModel: model,
|
|
1383
|
+
legacyAllowlist: () => readGrokModelListSetting(GROK_ENABLED_MODELS_SETTING_KEY),
|
|
1384
|
+
});
|
|
1385
|
+
const models = enabledModels.models;
|
|
1304
1386
|
const storedEfforts = readGrokModelEffortsSetting();
|
|
1305
1387
|
const effortsByModel = {};
|
|
1306
1388
|
for (const availableModel of availableModels) {
|
|
@@ -1319,6 +1401,9 @@ function getGrokProviderSettings() {
|
|
|
1319
1401
|
models,
|
|
1320
1402
|
availableModels,
|
|
1321
1403
|
enabledModels: models,
|
|
1404
|
+
autoEnableNew: enabledModels.autoEnableNew,
|
|
1405
|
+
fallback: enabledModels.fallback,
|
|
1406
|
+
unseenModels: enabledModels.unseen,
|
|
1322
1407
|
effortsByModel,
|
|
1323
1408
|
modelsDiscovered,
|
|
1324
1409
|
providerType: 'grok',
|
|
@@ -1353,15 +1438,20 @@ function setGrokProviderSettings(update = {}) {
|
|
|
1353
1438
|
stmts.deleteAppSetting?.run?.(GROK_MODELS_SETTING_KEY);
|
|
1354
1439
|
}
|
|
1355
1440
|
}
|
|
1356
|
-
if (Array.isArray(enabledModels)) {
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1441
|
+
if (Array.isArray(enabledModels) || typeof payload.autoEnableNew === 'boolean') {
|
|
1442
|
+
// The models Select Models showed are set as ticked; an empty list
|
|
1443
|
+
// leaves the default model alone, never all.
|
|
1444
|
+
providerModelState.select('grok', {
|
|
1445
|
+
enabledModels: Array.isArray(enabledModels)
|
|
1446
|
+
? enabledModels.map((value) => String(value || '').trim()).filter((value) => value && isSafeGrokModelId(value))
|
|
1447
|
+
: undefined,
|
|
1448
|
+
// The rows the browser drew; an older page sends none.
|
|
1449
|
+
shownModels: Array.isArray(payload.shownModels)
|
|
1450
|
+
? payload.shownModels.map((value) => String(value || '').trim()).filter((value) => value && isSafeGrokModelId(value))
|
|
1451
|
+
: getGrokProviderSettings().availableModels,
|
|
1452
|
+
defaultModel: normalizedModel,
|
|
1453
|
+
autoEnableNew: payload.autoEnableNew,
|
|
1454
|
+
});
|
|
1365
1455
|
}
|
|
1366
1456
|
const current = getGrokProviderSettings();
|
|
1367
1457
|
return {
|
|
@@ -1371,6 +1461,7 @@ function setGrokProviderSettings(update = {}) {
|
|
|
1371
1461
|
model: current.model,
|
|
1372
1462
|
models: current.models,
|
|
1373
1463
|
availableModels: current.availableModels,
|
|
1464
|
+
autoEnableNew: current.autoEnableNew,
|
|
1374
1465
|
};
|
|
1375
1466
|
}
|
|
1376
1467
|
|
|
@@ -1452,7 +1543,8 @@ async function refreshGrokProviderModels() {
|
|
|
1452
1543
|
const models = Array.from(new Set([settings.model, ...discovered])).filter(Boolean);
|
|
1453
1544
|
stmts.upsertAppSetting.run(GROK_MODELS_SETTING_KEY, JSON.stringify(models), nowIso);
|
|
1454
1545
|
stmts.upsertAppSetting.run(GROK_MODEL_EFFORTS_SETTING_KEY, JSON.stringify(effortsByModel), nowIso);
|
|
1455
|
-
|
|
1546
|
+
const { added } = providerModelState.reconcile('grok', { availableModels: getGrokProviderSettings().availableModels });
|
|
1547
|
+
return { ok: true, models, newModels: added, error: null };
|
|
1456
1548
|
} catch (error) {
|
|
1457
1549
|
return {
|
|
1458
1550
|
ok: false,
|
|
@@ -1509,6 +1601,8 @@ const FALLBACK_COPILOT_ENGINE = 'extension';
|
|
|
1509
1601
|
const CLAUDE_ENABLED_SETTING_KEY = 'claude_enabled';
|
|
1510
1602
|
const CLAUDE_MODEL_SETTING_KEY = 'claude_model';
|
|
1511
1603
|
const CLAUDE_MODELS_SETTING_KEY = 'claude_models';
|
|
1604
|
+
// The allowlists before 0.9.24 (Claude, Cursor, Grok): read once to build
|
|
1605
|
+
// the model map, then left as they are so an older version still finds them.
|
|
1512
1606
|
const CLAUDE_ENABLED_MODELS_SETTING_KEY = 'claude_enabled_models';
|
|
1513
1607
|
const CLAUDE_MODEL_EFFORTS_SETTING_KEY = 'claude_model_efforts';
|
|
1514
1608
|
// oar | vanilla | off — what commits and PRs made in Claude sessions say
|
|
@@ -2678,6 +2772,10 @@ const modelVariantCatalog = createModelVariantCatalogService({
|
|
|
2678
2772
|
curatedModelIds: CURATED_MODEL_IDS,
|
|
2679
2773
|
autoModelSentinel: AUTO_MODEL_SENTINEL,
|
|
2680
2774
|
runCopilotCliCommand,
|
|
2775
|
+
modelStateStore: providerModelState,
|
|
2776
|
+
onNewModels: (baseModelIds, on) => modelRefreshLedger.noteNewModels('github', baseModelIds.map((id) => ({ id, on }))),
|
|
2777
|
+
// A worker's snapshot read the runtime's list: a Copilot model refresh.
|
|
2778
|
+
onAuthoritativeSnapshot: () => modelRefreshLedger.record('github', { ok: true }),
|
|
2681
2779
|
});
|
|
2682
2780
|
const {
|
|
2683
2781
|
SUPPORTED_REASONING_EFFORTS,
|
|
@@ -2990,6 +3088,7 @@ const deepseekSettingsService = createDeepSeekSettingsService({
|
|
|
2990
3088
|
},
|
|
2991
3089
|
deleteSetting: (key) => stmts.deleteAppSetting.run(key),
|
|
2992
3090
|
listProviderCandidates: () => stmts.listRuntimeSessionProviderCandidates?.all?.() || [],
|
|
3091
|
+
onModelsRefreshed: (result) => modelRefreshLedger.record('deepseek', result),
|
|
2993
3092
|
});
|
|
2994
3093
|
function getDeepSeekProviderSettings() {
|
|
2995
3094
|
return deepseekSettingsService.getSettings();
|
|
@@ -3007,6 +3106,7 @@ const claudeApiSettingsService = createClaudeApiSettingsService({
|
|
|
3007
3106
|
listProviderCandidates: () => stmts.listRuntimeSessionProviderCandidates?.all?.() || [],
|
|
3008
3107
|
countLiveKeyWorkers: () => (sessionWorkerRegistry?.listWorkers?.() || []).filter((worker) => worker?.kind === 'claude-api').length,
|
|
3009
3108
|
getClaudeProviderSettings: () => getClaudeProviderSettings(),
|
|
3109
|
+
onModelsRefreshed: (result) => modelRefreshLedger.record('claude-api', result),
|
|
3010
3110
|
});
|
|
3011
3111
|
function getClaudeApiProviderSettings() {
|
|
3012
3112
|
return claudeApiSettingsService.getSettings();
|
|
@@ -3096,6 +3196,44 @@ try {
|
|
|
3096
3196
|
console.warn(`[cli-install] failed to apply persisted CLI bindings: ${error?.message || error}`);
|
|
3097
3197
|
}
|
|
3098
3198
|
|
|
3199
|
+
// The agent SDKs (and koffi on Windows) the relay installs on demand
|
|
3200
|
+
// (agent-sdk-store.mjs). Registered with the loader, so this process takes
|
|
3201
|
+
// them from the store, never from a worker folder in its environment.
|
|
3202
|
+
const agentSdkStore = createAgentSdkStore({
|
|
3203
|
+
fromCheckout: (sdk) => agentSdkFromCheckout(sdk),
|
|
3204
|
+
readSetting: readAppSettingValue,
|
|
3205
|
+
writeSetting: (key, value) => stmts.upsertAppSetting.run(key, value, new Date().toISOString()),
|
|
3206
|
+
});
|
|
3207
|
+
setAgentSdkStore(agentSdkStore);
|
|
3208
|
+
// A provider that was just set up gets its SDK at once, in the
|
|
3209
|
+
// background; a failure shows in its Settings tab and on the first chat.
|
|
3210
|
+
function installAgentSdkForSetup(sdk, reason) {
|
|
3211
|
+
if (resolveAgentSdkSource(sdk)) return;
|
|
3212
|
+
void agentSdkStore.ensure(sdk, { reason, retryFailed: true }).catch((error) => {
|
|
3213
|
+
console.warn(`[agent-sdk] ${sdk} install after ${reason} failed: ${error?.message || error}`);
|
|
3214
|
+
});
|
|
3215
|
+
}
|
|
3216
|
+
claudeAuthService.onLoginSuccess?.(() => installAgentSdkForSetup('claude', 'Claude login'));
|
|
3217
|
+
// The SDKs whose providers are in use: enabled, logged in, or with a
|
|
3218
|
+
// conversation that is not archived. koffi wherever the store supports it.
|
|
3219
|
+
function agentSdksInUse() {
|
|
3220
|
+
const inUse = new Set(['koffi']);
|
|
3221
|
+
const providerSdk = { claude: 'claude', 'claude-api': 'claude', deepseek: 'claude', cursor: 'cursor' };
|
|
3222
|
+
try {
|
|
3223
|
+
if (getClaudeProviderSettings()?.enabled === true || claudeAuthService.getCachedStatus?.()?.loggedIn === true) inUse.add('claude');
|
|
3224
|
+
if (claudeApiSettingsService.getSettings?.()?.enabled === true || deepseekSettingsService.getSettings?.()?.enabled === true) inUse.add('claude');
|
|
3225
|
+
if (getCursorProviderSettings()?.enabled === true) inUse.add('cursor');
|
|
3226
|
+
const rows = db.prepare(`SELECT DISTINCT r.provider_type AS provider FROM runtime_sessions r JOIN conversations c ON c.id = r.conversation_id WHERE c.archived = 0`).all();
|
|
3227
|
+
for (const row of rows) {
|
|
3228
|
+
const sdk = providerSdk[String(row?.provider || '').trim().toLowerCase()];
|
|
3229
|
+
if (sdk) inUse.add(sdk);
|
|
3230
|
+
}
|
|
3231
|
+
} catch (error) {
|
|
3232
|
+
console.warn(`[agent-sdk] could not tell which providers are in use: ${error?.message || error}`);
|
|
3233
|
+
}
|
|
3234
|
+
return [...inUse];
|
|
3235
|
+
}
|
|
3236
|
+
|
|
3099
3237
|
// ─── Web Push ─────────────────────────────────────────────────────────────────
|
|
3100
3238
|
// VAPID keys are generated once and persisted in app_settings; regenerating
|
|
3101
3239
|
// them would invalidate every stored subscription.
|
|
@@ -3383,6 +3521,20 @@ const relayCliLauncherService = createRelayCliLauncherService({
|
|
|
3383
3521
|
workerScope,
|
|
3384
3522
|
});
|
|
3385
3523
|
|
|
3524
|
+
// The launch gate (agent-sdk-launch-gate.mjs): never waits for an install.
|
|
3525
|
+
// A session it held back gets its launch back when the install ends or the
|
|
3526
|
+
// SDK turns ready later: the restart schedule cleared, the queue woken.
|
|
3527
|
+
const agentSdkLaunchGate = createAgentSdkLaunchGate({
|
|
3528
|
+
store: agentSdkStore,
|
|
3529
|
+
sdkForWorkerKind: agentSdkForWorkerKind,
|
|
3530
|
+
resolveSource: (sdk) => resolveAgentSdkSource(sdk),
|
|
3531
|
+
noteInstalling: (sdkSessionId, { label }) => workerLaunchNoteService.handle({ type: 'installing', sdkSessionId, lifecycle: { sdkLabel: label } }),
|
|
3532
|
+
onSessionsReleased: ({ sessions }) => {
|
|
3533
|
+
for (const sdkSessionId of sessions) sessionWorkerSupervisor.clearRestartSchedule?.(sdkSessionId);
|
|
3534
|
+
wakeSessionWorkerQueue('agent-sdk-ready');
|
|
3535
|
+
},
|
|
3536
|
+
});
|
|
3537
|
+
|
|
3386
3538
|
async function spawnSessionWorkerCli(targetSessionId, { allowProcessReuse = true } = {}) {
|
|
3387
3539
|
const normalizedTargetSessionId = String(targetSessionId || '').trim();
|
|
3388
3540
|
if (!normalizedTargetSessionId) {
|
|
@@ -3413,6 +3565,11 @@ async function spawnSessionWorkerCli(targetSessionId, { allowProcessReuse = true
|
|
|
3413
3565
|
}
|
|
3414
3566
|
const resolvedWorkspaceRoot = resolveLaunchWorkspaceRootForSession(normalizedTargetSessionId);
|
|
3415
3567
|
const workerLaunchEnv = buildSessionWorkerLaunchEnvForSession(normalizedTargetSessionId);
|
|
3568
|
+
// Synchronous: nothing between the gate and the launch can start a
|
|
3569
|
+
// second worker for the session.
|
|
3570
|
+
const agentSdkDir = agentSdkLaunchGate.check(normalizedTargetSessionId, resolveWorkerKind(workerLaunchEnv));
|
|
3571
|
+
if (agentSdkDir.deferred) return { deferred: agentSdkDir.deferred };
|
|
3572
|
+
if (agentSdkDir.dir) workerLaunchEnv[agentSdkDirEnvName(agentSdkDir.sdk)] = agentSdkDir.dir;
|
|
3416
3573
|
const launched = await launchSessionCli({
|
|
3417
3574
|
workerScope,
|
|
3418
3575
|
targetSessionId: normalizedTargetSessionId,
|
|
@@ -3427,6 +3584,9 @@ async function spawnSessionWorkerCli(targetSessionId, { allowProcessReuse = true
|
|
|
3427
3584
|
log: (message) => console.warn(`${runtimeLogPrefix()}${message}`),
|
|
3428
3585
|
});
|
|
3429
3586
|
const workerPid = Number.isInteger(Number(launched?.pid)) ? Number(launched.pid) : null;
|
|
3587
|
+
// The worker leases its folder when it loads the SDK; this covers the
|
|
3588
|
+
// seconds before, so no clean-up takes it in between.
|
|
3589
|
+
if (agentSdkDir.dir && workerPid) agentSdkStore.lease(agentSdkDir.dir, workerPid);
|
|
3430
3590
|
const workerId = `worker-${normalizedTargetSessionId.slice(0, 8)}`;
|
|
3431
3591
|
const launchMode = String(launched?.launchMode || 'detached').trim();
|
|
3432
3592
|
const tmuxSessionName = String(launched?.tmuxSessionName || '').trim();
|
|
@@ -5648,6 +5808,16 @@ const claudeApiUsageService = createClaudeApiUsageService({ db, logger: console
|
|
|
5648
5808
|
// Tokens per turn and per day for every provider, booked from the
|
|
5649
5809
|
// workers' reports; the Tokens sections in Check Usage and About read it.
|
|
5650
5810
|
const tokenUsageService = createTokenUsageService({ db, logger: console });
|
|
5811
|
+
// OpenAI image spend against its daily cap: one ledger of the tries in
|
|
5812
|
+
// flight for the image routes and the settings route alike. Its events go
|
|
5813
|
+
// to the owner's clients once the socket server is up.
|
|
5814
|
+
const openAIImageSpend = createOpenAIImageSpendService({
|
|
5815
|
+
tokenUsageService,
|
|
5816
|
+
readSetting: readAppSettingValue,
|
|
5817
|
+
db,
|
|
5818
|
+
emit: (event, payload) => io?.emit?.(event, payload),
|
|
5819
|
+
logger: console,
|
|
5820
|
+
});
|
|
5651
5821
|
// This month's spend on the key against the cap, and the Check Usage card
|
|
5652
5822
|
// (services/plan-usage-claude-api.mjs), over the same local days.
|
|
5653
5823
|
const claudeApiPlanUsage = createClaudeApiPlanUsage({
|
|
@@ -6259,6 +6429,7 @@ const copilotModelDiscoveryService = createCopilotModelDiscoveryService({
|
|
|
6259
6429
|
}),
|
|
6260
6430
|
resolveInstalledPaths: () => resolveInstalledCopilotPaths({ config }),
|
|
6261
6431
|
updateModelCatalog,
|
|
6432
|
+
onRefreshed: (result) => modelRefreshLedger.record('github', result),
|
|
6262
6433
|
logger: console,
|
|
6263
6434
|
});
|
|
6264
6435
|
const contextSnapshotService = createContextSnapshotService({
|
|
@@ -7085,9 +7256,51 @@ const workerRecycleService = createWorkerRecycleService({
|
|
|
7085
7256
|
// The stop and the launch under the per-session launch mutex the Change
|
|
7086
7257
|
// CWD relaunch and the launch routes take (sessions-routes.mjs).
|
|
7087
7258
|
runExclusive: (sid, fn) => sessionLaunchMutex.runExclusive(sid, fn),
|
|
7259
|
+
// The kinds the launch gate gives an agent SDK: once stale, recycled
|
|
7260
|
+
// without waiting for their CLI child, and fenced until then.
|
|
7261
|
+
isAgentSdkWorker: (worker) => staleWorkerLosesAgentSdk(worker?.kind, { sdkForWorkerKind: agentSdkForWorkerKind, fromCheckout: (sdk) => agentSdkFromCheckout(sdk) }),
|
|
7088
7262
|
logger: console,
|
|
7089
7263
|
});
|
|
7090
7264
|
|
|
7265
|
+
// A worker's hello, before the hello's own request for work: the registry
|
|
7266
|
+
// learns the process, its kind and its build id (a relay that just started
|
|
7267
|
+
// has no entry yet, and the delivery would register the process without its
|
|
7268
|
+
// build id), and the recycle sweeps, so a stale worker on an agent SDK is
|
|
7269
|
+
// fenced before anything is handed to it.
|
|
7270
|
+
function noteSessionWorkerHello({ sessionId, pid, kind = null, buildId = null } = {}) {
|
|
7271
|
+
if (featureFlags?.SESSION_WORKER_ROUTING_ENABLED !== true || runtimeShutdownStarted) return;
|
|
7272
|
+
const sid = String(sessionId || '').trim();
|
|
7273
|
+
if (!sid) return;
|
|
7274
|
+
// A killed session's worker is registered by nothing, as the delivery
|
|
7275
|
+
// leaves it unregistered.
|
|
7276
|
+
if (sessionWorkerSupervisor?.isKillBlocked?.(sid) !== true) {
|
|
7277
|
+
const statedPid = Number.isInteger(Number(pid)) && Number(pid) > 0 ? Number(pid) : null;
|
|
7278
|
+
let worker = sessionWorkerRegistry?.getWorker?.(sid) || null;
|
|
7279
|
+
// No entry, or one with no process behind it (a launch the agent SDK
|
|
7280
|
+
// gate deferred leaves 'new'): the process saying hello is the session's
|
|
7281
|
+
// worker, ready, as its first heartbeat would make it (/api/heartbeat).
|
|
7282
|
+
if (!worker || worker.status === 'new' || (!worker.workerId && !worker.pid)) {
|
|
7283
|
+
worker = sessionWorkerRegistry?.upsertWorker?.({
|
|
7284
|
+
...(worker || {}),
|
|
7285
|
+
sdkSessionId: sid,
|
|
7286
|
+
workerId: worker?.workerId || `worker-${sid.slice(0, 8)}`,
|
|
7287
|
+
status: 'ready',
|
|
7288
|
+
pid: statedPid || worker?.pid || null,
|
|
7289
|
+
queueDepth: Math.max(0, Number(queueCounts?.().pendingCount || 0)),
|
|
7290
|
+
}) || null;
|
|
7291
|
+
} else if (statedPid && worker.pid !== statedPid) {
|
|
7292
|
+
worker = sessionWorkerRegistry?.upsertWorker?.({ ...worker, sdkSessionId: sid, pid: statedPid, status: worker.status || 'ready' }) || null;
|
|
7293
|
+
}
|
|
7294
|
+
// Stated for the process just registered, in an upsert of its own: the
|
|
7295
|
+
// registry drops a build id that comes with a new pid and equals the old
|
|
7296
|
+
// entry's (the old process's, spread in).
|
|
7297
|
+
if (worker && ((kind && worker.kind !== kind) || (buildId && worker.buildId !== buildId))) {
|
|
7298
|
+
sessionWorkerRegistry?.upsertWorker?.({ ...worker, ...(kind ? { kind } : {}), ...(buildId ? { buildId } : {}) });
|
|
7299
|
+
}
|
|
7300
|
+
}
|
|
7301
|
+
void workerRecycleService.sweepNow();
|
|
7302
|
+
}
|
|
7303
|
+
|
|
7091
7304
|
// Once at start, after the restart's own worker reconnects settled: what
|
|
7092
7305
|
// still runs for an archived conversation is stopped and stays stopped.
|
|
7093
7306
|
// Through the recycle's replacement, one session at a time; never throws.
|
|
@@ -7322,6 +7535,9 @@ const sessionWorkerWebSocketService = createSessionWorkerWebSocketService({
|
|
|
7322
7535
|
const worker = sessionWorkerRegistry?.getWorker?.(sid) || null;
|
|
7323
7536
|
if (worker && worker.buildId !== buildId) sessionWorkerRegistry?.upsertWorker?.({ ...worker, buildId });
|
|
7324
7537
|
},
|
|
7538
|
+
// Registers the process and runs the recycle's sweep before the hello's
|
|
7539
|
+
// request for work (noteSessionWorkerHello).
|
|
7540
|
+
onWorkerHello: noteSessionWorkerHello,
|
|
7325
7541
|
// Sent in server.hello, so a worker on other code can say so in its log.
|
|
7326
7542
|
buildId: BUILD_ID,
|
|
7327
7543
|
isWorkerProcessAlive: (pid) => isProcessAlive(Number(pid)),
|
|
@@ -7710,6 +7926,85 @@ const remoteRelayDispatcher = createRemoteRelayDispatcher({
|
|
|
7710
7926
|
logger: console,
|
|
7711
7927
|
});
|
|
7712
7928
|
|
|
7929
|
+
// Every refresh of a provider's models resets its clock in the ledger,
|
|
7930
|
+
// whoever asks; the background check refreshes what is due through the same
|
|
7931
|
+
// table as the Select Models Refresh button.
|
|
7932
|
+
const trackedModelRefreshers = {
|
|
7933
|
+
refreshOpenAIProviderModels: modelRefreshLedger.track('openai', refreshOpenAIProviderModels),
|
|
7934
|
+
refreshClaudeProviderModels: modelRefreshLedger.track('claude', refreshClaudeProviderModels),
|
|
7935
|
+
refreshCursorProviderModels: modelRefreshLedger.track('cursor', refreshCursorProviderModels),
|
|
7936
|
+
refreshGrokProviderModels: modelRefreshLedger.track('grok', refreshGrokProviderModels),
|
|
7937
|
+
};
|
|
7938
|
+
const modelRefreshSettings = createModelRefreshSettings({
|
|
7939
|
+
readSetting: readAppSettingValue,
|
|
7940
|
+
writeSetting: (key, value, stamp) => stmts.upsertAppSetting.run(key, value, stamp),
|
|
7941
|
+
});
|
|
7942
|
+
// Claude and Grok are set up when their CLI is installed or their login is
|
|
7943
|
+
// there: a file lookup and the last status, never a CLI started to ask.
|
|
7944
|
+
function isClaudeModelRefreshSetUp() {
|
|
7945
|
+
// Discovery runs on the SDK: none installed yet, nothing to ask.
|
|
7946
|
+
if (!resolveAgentSdkSource('claude')) return false;
|
|
7947
|
+
return !!cliInstallService.resolveCliBinary('claude')
|
|
7948
|
+
|| claudeCloudCredentials.describe().hasToken === true
|
|
7949
|
+
|| claudeAuthService.getCachedStatus?.()?.loggedIn === true;
|
|
7950
|
+
}
|
|
7951
|
+
function isGrokModelRefreshSetUp() {
|
|
7952
|
+
return !!String(process.env.GROK_CLI_COMMAND || process.env.GROK_COMMAND || '').trim()
|
|
7953
|
+
|| !!cliInstallService.resolveCliBinary('grok')
|
|
7954
|
+
|| !!readGrokCliAuthKey();
|
|
7955
|
+
}
|
|
7956
|
+
const modelRefreshTable = buildProviderRefreshTable({
|
|
7957
|
+
getOpenAIProviderSettings,
|
|
7958
|
+
getClaudeProviderSettings,
|
|
7959
|
+
getCursorProviderSettings,
|
|
7960
|
+
getGrokProviderSettings,
|
|
7961
|
+
...trackedModelRefreshers,
|
|
7962
|
+
deepseekSettingsService,
|
|
7963
|
+
claudeApiSettingsService,
|
|
7964
|
+
copilotModelDiscoveryService,
|
|
7965
|
+
isClaudeSetUp: isClaudeModelRefreshSetUp,
|
|
7966
|
+
isGrokSetUp: isGrokModelRefreshSetUp,
|
|
7967
|
+
});
|
|
7968
|
+
// A freshly installed SDK fills its provider's catalogue at once, as a
|
|
7969
|
+
// fresh CLI install does.
|
|
7970
|
+
agentSdkStore.onReady(({ id } = {}) => {
|
|
7971
|
+
// A session the gate held back, or whose launch gave up on a failed
|
|
7972
|
+
// install, delivers its queued message now.
|
|
7973
|
+
agentSdkLaunchGate.onReady(id);
|
|
7974
|
+
const providers = { claude: ['claude'], cursor: ['cursor'] }[id];
|
|
7975
|
+
if (!providers) return;
|
|
7976
|
+
void refreshProviderModels({ table: modelRefreshTable, providers, reason: 'agent-sdk-installed', gate: 'isSetUp' }).catch((error) => {
|
|
7977
|
+
console.warn(`[agent-sdk] model refresh after the ${id} install failed: ${error?.message || error}`);
|
|
7978
|
+
});
|
|
7979
|
+
});
|
|
7980
|
+
const modelRefreshScheduler = createModelRefreshScheduler({
|
|
7981
|
+
getSettings: () => modelRefreshSettings.get(),
|
|
7982
|
+
getLast: () => modelRefreshLedger.readLast(),
|
|
7983
|
+
getTable: () => modelRefreshTable,
|
|
7984
|
+
refreshProviders: ({ providers, reason }) => refreshProviderModels({ table: modelRefreshTable, providers, reason, gate: 'isSetUp' }),
|
|
7985
|
+
logger: console,
|
|
7986
|
+
});
|
|
7987
|
+
|
|
7988
|
+
// The same for the settings routes: a save that leaves the provider enabled
|
|
7989
|
+
// installs its SDK in the background.
|
|
7990
|
+
function withAgentSdkSetup(setter, sdk, isEnabled, reason) {
|
|
7991
|
+
return (...args) => {
|
|
7992
|
+
const result = setter(...args);
|
|
7993
|
+
try { if (result?.ok !== false && isEnabled()) installAgentSdkForSetup(sdk, reason); } catch {}
|
|
7994
|
+
return result;
|
|
7995
|
+
};
|
|
7996
|
+
}
|
|
7997
|
+
function withAgentSdkUpdate(service, reason) {
|
|
7998
|
+
if (!service) return service;
|
|
7999
|
+
return Object.assign(Object.create(service), {
|
|
8000
|
+
update: (...args) => {
|
|
8001
|
+
const result = service.update(...args);
|
|
8002
|
+
try { if (result?.ok !== false && service.getSettings?.()?.enabled === true) installAgentSdkForSetup('claude', reason); } catch {}
|
|
8003
|
+
return result;
|
|
8004
|
+
},
|
|
8005
|
+
});
|
|
8006
|
+
}
|
|
8007
|
+
|
|
7713
8008
|
const sharedRouteDeps = {
|
|
7714
8009
|
auth,
|
|
7715
8010
|
isTrulyLocalRequest: localRequestCheck.isTrulyLocal,
|
|
@@ -7766,27 +8061,27 @@ const sharedRouteDeps = {
|
|
|
7766
8061
|
setDefaultSessionWorkspaceRootPath,
|
|
7767
8062
|
getOpenAIProviderSettings,
|
|
7768
8063
|
setOpenAIProviderSettings,
|
|
7769
|
-
refreshOpenAIProviderModels,
|
|
8064
|
+
refreshOpenAIProviderModels: trackedModelRefreshers.refreshOpenAIProviderModels,
|
|
7770
8065
|
getCopilotProviderSettings,
|
|
7771
8066
|
setCopilotProviderSettings,
|
|
7772
8067
|
getClaudeProviderSettings,
|
|
7773
|
-
setClaudeProviderSettings,
|
|
7774
|
-
refreshClaudeProviderModels,
|
|
8068
|
+
setClaudeProviderSettings: withAgentSdkSetup(setClaudeProviderSettings, 'claude', () => getClaudeProviderSettings()?.enabled === true, 'Claude enabled'),
|
|
8069
|
+
refreshClaudeProviderModels: trackedModelRefreshers.refreshClaudeProviderModels,
|
|
7775
8070
|
getCursorProviderSettings,
|
|
7776
|
-
setCursorProviderSettings,
|
|
7777
|
-
refreshCursorProviderModels,
|
|
8071
|
+
setCursorProviderSettings: withAgentSdkSetup(setCursorProviderSettings, 'cursor', () => getCursorProviderSettings()?.enabled === true, 'Cursor key saved'),
|
|
8072
|
+
refreshCursorProviderModels: trackedModelRefreshers.refreshCursorProviderModels,
|
|
7778
8073
|
getGrokProviderSettings,
|
|
7779
8074
|
setGrokProviderSettings,
|
|
7780
|
-
refreshGrokProviderModels,
|
|
8075
|
+
refreshGrokProviderModels: trackedModelRefreshers.refreshGrokProviderModels,
|
|
7781
8076
|
getClaudeCloudProviderSettings,
|
|
7782
8077
|
claudeCloudSettingsService,
|
|
7783
8078
|
claudeCloudSessionService,
|
|
7784
8079
|
getDeepSeekProviderSettings,
|
|
7785
|
-
deepseekSettingsService,
|
|
8080
|
+
deepseekSettingsService: withAgentSdkUpdate(deepseekSettingsService, 'DeepSeek set up'),
|
|
7786
8081
|
deepseekBalanceService,
|
|
7787
8082
|
deepseekUsageImportService,
|
|
7788
8083
|
getClaudeApiProviderSettings,
|
|
7789
|
-
claudeApiSettingsService,
|
|
8084
|
+
claudeApiSettingsService: withAgentSdkUpdate(claudeApiSettingsService, 'Claude API set up'),
|
|
7790
8085
|
claudeCloudRepoService,
|
|
7791
8086
|
// The login's state for the workers' nudge route (never the token).
|
|
7792
8087
|
claudeCloudLoginStatus: { describe: () => claudeCloudCredentials.describe() },
|
|
@@ -7807,6 +8102,15 @@ const sharedRouteDeps = {
|
|
|
7807
8102
|
listModelVariantRows,
|
|
7808
8103
|
refreshModelVariantCatalogFromCli,
|
|
7809
8104
|
setEnabledModelVariants,
|
|
8105
|
+
getCopilotModelState: modelVariantCatalog.getCopilotModelState,
|
|
8106
|
+
setCopilotAutoEnableNew: modelVariantCatalog.setCopilotAutoEnableNew,
|
|
8107
|
+
markCopilotModelsSeen: modelVariantCatalog.markCopilotModelsSeen,
|
|
8108
|
+
markProviderModelsSeen: (provider) => providerModelState.markSeen(provider),
|
|
8109
|
+
modelRefreshLedger,
|
|
8110
|
+
modelRefreshSettings,
|
|
8111
|
+
modelRefreshScheduler,
|
|
8112
|
+
isClaudeModelRefreshSetUp,
|
|
8113
|
+
isGrokModelRefreshSetUp,
|
|
7810
8114
|
parseModelVariantSelection,
|
|
7811
8115
|
SUPPORTED_REASONING_EFFORTS,
|
|
7812
8116
|
buildRelayReadyBannerData,
|
|
@@ -7882,6 +8186,11 @@ const sharedRouteDeps = {
|
|
|
7882
8186
|
path,
|
|
7883
8187
|
uploadsDir: UPLOAD_DIR,
|
|
7884
8188
|
uploadReadDirs: UPLOAD_READ_DIRS,
|
|
8189
|
+
// Never read for a send's imagePaths, even inside the session's folder.
|
|
8190
|
+
imageInputDenyPaths: {
|
|
8191
|
+
dirs: [DATA_DIR],
|
|
8192
|
+
files: [DB_PATH, `${DB_PATH}-wal`, `${DB_PATH}-shm`, `${DB_PATH}-journal`, CONFIG_PATH],
|
|
8193
|
+
},
|
|
7885
8194
|
fetchUsageSummary,
|
|
7886
8195
|
fetchCopilotBillingUsage,
|
|
7887
8196
|
fetchGrokBillingUsage: createGrokBillingUsageFetcher(),
|
|
@@ -7938,6 +8247,9 @@ const sharedRouteDeps = {
|
|
|
7938
8247
|
claudeBillingSwitchService,
|
|
7939
8248
|
claudeApiUsageService,
|
|
7940
8249
|
tokenUsageService,
|
|
8250
|
+
openAIImageSpend,
|
|
8251
|
+
getOpenAIImageSpendState: (options) => openAIImageSpend.buildState(options),
|
|
8252
|
+
notifyOpenAIImageSpendChanged: () => openAIImageSpend.notifyImageSpendChanged(),
|
|
7941
8253
|
claudeApiClaimCheck,
|
|
7942
8254
|
onClaudeBillingFence: handleClaudeBillingFence,
|
|
7943
8255
|
claudeApiPlanUsage,
|
|
@@ -7968,6 +8280,7 @@ const sharedRouteDeps = {
|
|
|
7968
8280
|
shareRepository,
|
|
7969
8281
|
};
|
|
7970
8282
|
registerMessagesRoutes(app, sharedRouteDeps);
|
|
8283
|
+
registerAgentSdkRoutes(app, { auth, io, agentSdkStore });
|
|
7971
8284
|
registerSessionsRoutes(app, sharedRouteDeps);
|
|
7972
8285
|
registerConversationGroupRoutes(app, sharedRouteDeps);
|
|
7973
8286
|
registerAskUserRoutes(app, sharedRouteDeps);
|
|
@@ -8054,6 +8367,7 @@ registerAboutRoutes(app, {
|
|
|
8054
8367
|
isRelayCliOnline: () => runtimeState.cliOnline === true,
|
|
8055
8368
|
listWorkers: () => sessionWorkerRegistry.listWorkers(),
|
|
8056
8369
|
getUpdateState: () => buildUpdateStatePayload(),
|
|
8370
|
+
getAgentSdkStatus: () => agentSdkStore.snapshot(),
|
|
8057
8371
|
describeBackups: () => describeBackups({ layout: STATE_LAYOUT, serverDir: __dirname, dataDir: DATA_DIR, config, sizes: false }),
|
|
8058
8372
|
}),
|
|
8059
8373
|
});
|
|
@@ -8700,12 +9014,17 @@ function shutdownRuntime(reason = 'unknown', { exitCode = 0 } = {}) {
|
|
|
8700
9014
|
const discoveryShutdown = copilotModelDiscoveryService.dispose().catch((error) => {
|
|
8701
9015
|
console.warn(`${runtimeLogPrefix()}Copilot model discovery shutdown failed: ${error?.message || error}`);
|
|
8702
9016
|
});
|
|
9017
|
+
// Stops the timer at once; a check in flight ends with the discoveries above.
|
|
9018
|
+
void modelRefreshScheduler.dispose();
|
|
8703
9019
|
try { claudeAuthService.dispose(); } catch (error) {
|
|
8704
9020
|
console.warn(`${runtimeLogPrefix()}Claude auth service shutdown failed: ${error?.message || error}`);
|
|
8705
9021
|
}
|
|
8706
9022
|
try { cliInstallService.dispose(); } catch (error) {
|
|
8707
9023
|
console.warn(`${runtimeLogPrefix()}CLI install service shutdown failed: ${error?.message || error}`);
|
|
8708
9024
|
}
|
|
9025
|
+
try { agentSdkStore.dispose(); } catch (error) {
|
|
9026
|
+
console.warn(`${runtimeLogPrefix()}agent SDK store shutdown failed: ${error?.message || error}`);
|
|
9027
|
+
}
|
|
8709
9028
|
try { grokAuthService.dispose(); } catch (error) {
|
|
8710
9029
|
console.warn(`${runtimeLogPrefix()}Grok auth service shutdown failed: ${error?.message || error}`);
|
|
8711
9030
|
}
|
|
@@ -8880,6 +9199,20 @@ httpServer.listen(config.port, listenHost, () => {
|
|
|
8880
9199
|
// Deferred and unref'd (a few seconds behind the startup import's own CLI
|
|
8881
9200
|
// spawn); a relay without an installed Copilot CLI skips it entirely.
|
|
8882
9201
|
copilotModelDiscoveryService.scheduleStartupRefresh();
|
|
9202
|
+
// "Check for new models": a first look a minute in (overdue providers),
|
|
9203
|
+
// then every quarter of an hour; unref'd.
|
|
9204
|
+
modelRefreshScheduler.start();
|
|
9205
|
+
// At start: the SDKs of the providers in use, and koffi on
|
|
9206
|
+
// Windows, installed in the background when their pinned folder is
|
|
9207
|
+
// missing (after an update: the new pins); the start never waits for it.
|
|
9208
|
+
// A git checkout runs the devDependencies it has installed and leaves
|
|
9209
|
+
// the store alone for those.
|
|
9210
|
+
const storeSdks = agentSdkStore.ids().filter((sdk) => !agentSdkFromCheckout(sdk));
|
|
9211
|
+
if (storeSdks.length) {
|
|
9212
|
+
void agentSdkStore.reconcile({ inUse: agentSdksInUse().filter((sdk) => storeSdks.includes(sdk)) }).catch((error) => {
|
|
9213
|
+
console.warn(`[agent-sdk] start-up check failed: ${error?.message || error}`);
|
|
9214
|
+
});
|
|
9215
|
+
}
|
|
8883
9216
|
// Archived conversations' workers: stopped once, 30 s in, so the workers
|
|
8884
9217
|
// that reconnect after the restart and the first prime have settled. With
|
|
8885
9218
|
// routing off the relay launches no workers, so there is nothing to sweep.
|