@oar-sh/oar 0.9.15 → 0.9.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/extensions/web-relay/polling/polling-loop.mjs +14 -87
- package/CHANGELOG.md +305 -0
- package/README.md +12 -11
- package/package.json +2 -1
- package/server/claude-worker/claude-attachments.mjs +7 -4
- package/server/claude-worker/claude-remote-relay-tool.mjs +36 -1
- package/server/claude-worker/claude-sdk-adapter.mjs +167 -9
- package/server/claude-worker/claude-session-process.mjs +131 -33
- package/server/claude-worker/claude-session-worker-main.mjs +266 -0
- package/server/claude-worker/claude-session-worker.mjs +2 -253
- package/server/claude-worker/claude-turn-failure.mjs +8 -0
- package/server/claude-worker/claude-turn-publisher.mjs +282 -13
- package/server/claude-worker/claude-usage-totals.mjs +108 -0
- package/server/claude-worker/sdk-message-normalizer.mjs +55 -0
- package/server/copilot-worker/copilot-sdk-adapter.mjs +18 -4
- package/server/copilot-worker/copilot-sdk-session-process.mjs +73 -7
- package/server/db-schema.mjs +115 -0
- package/server/deepseek-worker/deepseek-claude-backend.mjs +433 -0
- package/server/deepseek-worker/deepseek-session-worker.mjs +37 -0
- package/server/migrations/0019-relay-event-subscription-options.mjs +106 -0
- package/server/migrations/0020-conversation-may-create-sessions.mjs +67 -0
- package/server/migrations/0021-conversation-group-agent-marks.mjs +77 -0
- package/server/migrations/0022-relay-schedules.mjs +142 -0
- package/server/migrations/0023-relay-asks.mjs +141 -0
- package/server/migrations/0024-deepseek-usage-totals.mjs +151 -0
- package/server/migrations/0025-deepseek-usage-requests-daily.mjs +179 -0
- package/server/migrations/0026-deepseek-usage-served-models.mjs +222 -0
- package/server/migrations/0027-deepseek-usage-checkpoint-runs.mjs +101 -0
- package/server/migrations/0028-deepseek-usage-checkpoint-request-runs.mjs +99 -0
- package/server/migrations/0029-deepseek-balance-rises.mjs +99 -0
- package/server/migrations/0030-deepseek-usage-actuals.mjs +123 -0
- package/server/public/app/api-client.js +53 -0
- package/server/public/app/ask-user-view.js +3 -0
- package/server/public/app/bootstrap.js +162 -16
- package/server/public/app/context-tier-options.mjs +4 -0
- package/server/public/app/context-usage-view.mjs +17 -0
- package/server/public/app/conversation-group-order.mjs +35 -0
- package/server/public/app/conversation-provider-indicator.mjs +7 -0
- package/server/public/app/conversation-view.js +2 -0
- package/server/public/app/deepseek-settings-ui.js +342 -0
- package/server/public/app/inbox-ui.js +367 -0
- package/server/public/app/inbox-view.mjs +370 -0
- package/server/public/app/journal-view.js +54 -5
- package/server/public/app/model-selector-options.mjs +11 -0
- package/server/public/app/model-vision-support.mjs +2 -0
- package/server/public/app/no-conversation-state.mjs +1 -1
- package/server/public/app/plan-usage-view.mjs +165 -13
- package/server/public/app/provider-readiness.mjs +4 -1
- package/server/public/app/relay-error-ctas.mjs +8 -0
- package/server/public/app/relay-event-subscriptions-ui.js +4 -2
- package/server/public/app/relay-event-view.mjs +33 -7
- package/server/public/app/relay-schedule-view.mjs +119 -0
- package/server/public/app/relay-schedules-ui.js +172 -0
- package/server/public/app/settings-modal.js +11 -0
- package/server/public/app/settings-tabs.js +1 -1
- package/server/public/app/socket-handlers.js +37 -3
- package/server/public/app/store.js +18 -5
- package/server/public/index.html +567 -35
- package/server/relay-tools.md +18 -3
- package/server/repositories/conversation-group-repository.mjs +110 -17
- package/server/repositories/remote-relay-repository.mjs +20 -2
- package/server/repositories/session-repository.mjs +7 -1
- package/server/routes/ask-user-routes.mjs +57 -3
- package/server/routes/cache-routes.mjs +6 -0
- package/server/routes/conversation-group-routes.mjs +19 -3
- package/server/routes/deepseek-usage-import-routes.mjs +72 -0
- package/server/routes/messages-routes.mjs +195 -18
- package/server/routes/relay-ask-routes.mjs +387 -0
- package/server/routes/relay-event-routes.mjs +3 -1
- package/server/routes/relay-schedule-routes.mjs +57 -0
- package/server/routes/sessions-routes.mjs +932 -97
- package/server/server-runtime.mjs +203 -11
- package/server/services/about-service.mjs +1 -0
- package/server/services/cache-rebuild-service.mjs +19 -2
- package/server/services/claude-session-leftovers.mjs +226 -0
- package/server/services/conversation-preferences-service.mjs +17 -0
- package/server/services/deepseek-balance-service.mjs +102 -0
- package/server/services/deepseek-settings-service.mjs +380 -0
- package/server/services/deepseek-usage-import.mjs +471 -0
- package/server/services/model-variant-catalog-service.mjs +1 -0
- package/server/services/plan-usage-contract.mjs +100 -5
- package/server/services/plan-usage-deepseek.mjs +931 -0
- package/server/services/plan-usage-service.mjs +431 -0
- package/server/services/provider-model-selection.mjs +1 -0
- package/server/services/provider-reasoning-effort.mjs +15 -2
- package/server/services/relay-ask-link-service.mjs +174 -0
- package/server/services/relay-ask-service.mjs +604 -0
- package/server/services/relay-event-service.mjs +589 -73
- package/server/services/relay-schedule-service.mjs +596 -0
- package/server/services/remote-relay-caller-context.mjs +89 -20
- package/server/services/remote-relay-dispatcher.mjs +958 -31
- package/server/services/session-worker-launch-service.mjs +233 -82
- package/server/services/worker-secret-redaction.mjs +318 -0
- package/shared/context-window-fallbacks.mjs +3 -0
- package/shared/deepseek-api.mjs +137 -0
- package/shared/deepseek-models.mjs +114 -0
- package/shared/deepseek-pricing.mjs +146 -0
- package/shared/host-claude-session-env.mjs +101 -0
- package/shared/model-id.mjs +7 -0
- package/shared/preview-tool-core.mjs +1 -1
- package/shared/provider-routing.mjs +2 -2
- package/shared/relay-asks.mjs +145 -0
- package/shared/relay-events.mjs +268 -18
- package/shared/relay-schedules.mjs +128 -0
- package/shared/remote-relay-contract.mjs +554 -26
- package/shared/remote-relay-tool-core.mjs +16 -5
- package/shared/steer-settle-failure.mjs +1 -0
- package/shared/worker-runtime/direct-openai-image.mjs +128 -0
- package/shared/worker-runtime/worker-log-file.mjs +60 -0
|
@@ -45,6 +45,9 @@ import { registerClaudeCloudRoutes } from './routes/claude-cloud-routes.mjs';
|
|
|
45
45
|
import { createPreviewScopeResolver, registerPreviewRoutes } from './routes/preview-routes.mjs';
|
|
46
46
|
import { registerRemoteRelayRoutes } from './routes/remote-relay-routes.mjs';
|
|
47
47
|
import { registerRelayEventRoutes } from './routes/relay-event-routes.mjs';
|
|
48
|
+
import { registerRelayScheduleRoutes } from './routes/relay-schedule-routes.mjs';
|
|
49
|
+
import { registerDeepSeekUsageImportRoutes } from './routes/deepseek-usage-import-routes.mjs';
|
|
50
|
+
import { registerRelayAskRoutes } from './routes/relay-ask-routes.mjs';
|
|
48
51
|
import { createRemoteRelayRepository } from './repositories/remote-relay-repository.mjs';
|
|
49
52
|
import { createConversationGroupRepository } from './repositories/conversation-group-repository.mjs';
|
|
50
53
|
import { createShareRepository } from './repositories/share-repository.mjs';
|
|
@@ -62,6 +65,12 @@ import { createClaudeCloudCredentials } from '../shared/claude-cloud/credentials
|
|
|
62
65
|
import { createClaudeCloudClient } from '../shared/claude-cloud/api-client.mjs';
|
|
63
66
|
import { resolveClaudeCloudBaseUrl } from '../shared/claude-cloud/base-url.mjs';
|
|
64
67
|
import { createClaudeCloudSettingsService } from './services/claude-cloud-settings-service.mjs';
|
|
68
|
+
import { createDeepSeekSettingsService } from './services/deepseek-settings-service.mjs';
|
|
69
|
+
import { createWorkerPostRedaction } from './services/worker-secret-redaction.mjs';
|
|
70
|
+
import { createDeepSeekBalanceService } from './services/deepseek-balance-service.mjs';
|
|
71
|
+
import { deepseekCardCurrency } from './services/plan-usage-deepseek.mjs';
|
|
72
|
+
import { createDeepSeekUsageImportService } from './services/deepseek-usage-import.mjs';
|
|
73
|
+
import { DEEPSEEK_DEFAULT_AUTO_COMPACT_WINDOW, DEFAULT_DEEPSEEK_MODEL, deepseekModelLabel } from '../shared/deepseek-models.mjs';
|
|
65
74
|
import { createClaudeCloudSessionService } from './services/claude-cloud-session-service.mjs';
|
|
66
75
|
import { createClaudeCloudRepoService, listRecentCloudSourcesFromStatements } from './services/claude-cloud-repo-service.mjs';
|
|
67
76
|
import { createClaudeAccountUsageService } from './services/claude-account-usage-service.mjs';
|
|
@@ -84,6 +93,9 @@ import {
|
|
|
84
93
|
} from './services/restart-resume-service.mjs';
|
|
85
94
|
import { createBackgroundWorkView } from './services/background-work-view.mjs';
|
|
86
95
|
import { createRelayEventService } from './services/relay-event-service.mjs';
|
|
96
|
+
import { createRelayScheduleService } from './services/relay-schedule-service.mjs';
|
|
97
|
+
import { createRelayAskService } from './services/relay-ask-service.mjs';
|
|
98
|
+
import { createRelayAskLinkService } from './services/relay-ask-link-service.mjs';
|
|
87
99
|
import { createRelayEventLinkService } from './services/relay-event-link-service.mjs';
|
|
88
100
|
import { buildClaudeAttributionSettings, normalizeClaudeAttributionMode, resolveClaudeAttributionMode, DEFAULT_CLAUDE_ATTRIBUTION_MODE } from '../shared/claude-attribution.mjs';
|
|
89
101
|
import {
|
|
@@ -133,7 +145,7 @@ import { createRelayCliLauncherService } from './services/relay-cli-launcher-ser
|
|
|
133
145
|
import { createSessionWorkerRegistry } from './services/session-worker-registry-service.mjs';
|
|
134
146
|
import { createSessionWorkerSupervisor } from './services/session-worker-supervisor-service.mjs';
|
|
135
147
|
import { createSessionWorkerProcessInspector } from './services/session-worker-process-service.mjs';
|
|
136
|
-
import { applyClaudeCloudProviderEnvironment, applyClaudeProviderEnvironment, applyCopilotSdkProviderEnvironment, applyCursorProviderEnvironment, applyGrokProviderEnvironment, applyOpenAIProviderEnvironment, copilotSdkEngineUnavailableReason, killTmuxSession, launchSessionCli } from './services/session-worker-launch-service.mjs';
|
|
148
|
+
import { applyClaudeCloudProviderEnvironment, applyClaudeProviderEnvironment, applyCopilotSdkProviderEnvironment, applyCursorProviderEnvironment, applyDeepSeekProviderEnvironment, applyGrokProviderEnvironment, applyOpenAIProviderEnvironment, copilotSdkEngineUnavailableReason, killTmuxSession, launchSessionCli } from './services/session-worker-launch-service.mjs';
|
|
137
149
|
import { createSshTunnelManager } from './services/ssh-tunnel-manager-service.mjs';
|
|
138
150
|
import { createCloudflaredTunnelManager } from './services/cloudflared-tunnel-service.mjs';
|
|
139
151
|
import {
|
|
@@ -2825,6 +2837,58 @@ const claudeCloudRepoService = createClaudeCloudRepoService({
|
|
|
2825
2837
|
isEnabled: () => claudeCloudSettingsService.getSettings().enabled === true,
|
|
2826
2838
|
listRecentCloudSources: () => listRecentCloudSourcesFromStatements(stmts),
|
|
2827
2839
|
});
|
|
2840
|
+
// What a session worker posts loses every key a provider saved in Settings
|
|
2841
|
+
// (worker-secret-redaction.mjs). Built here, at the start, so it reads the
|
|
2842
|
+
// saved keys now, and the DeepSeek save below notes each key it writes: one
|
|
2843
|
+
// changed before any worker has posted is still redacted.
|
|
2844
|
+
const workerPostRedaction = createWorkerPostRedaction({ readSetting: readAppSettingValue });
|
|
2845
|
+
// DeepSeek: one API key per relay, its models and its endpoint. The key stays
|
|
2846
|
+
// in the settings table; GET routes answer `configured`, never the key.
|
|
2847
|
+
const deepseekSettingsService = createDeepSeekSettingsService({
|
|
2848
|
+
readSetting: readAppSettingValue,
|
|
2849
|
+
writeSetting: (key, value) => {
|
|
2850
|
+
stmts.upsertAppSetting.run(key, String(value), new Date().toISOString());
|
|
2851
|
+
workerPostRedaction.noteSavedSetting(key, value);
|
|
2852
|
+
},
|
|
2853
|
+
deleteSetting: (key) => stmts.deleteAppSetting.run(key),
|
|
2854
|
+
listProviderCandidates: () => stmts.listRuntimeSessionProviderCandidates?.all?.() || [],
|
|
2855
|
+
});
|
|
2856
|
+
function getDeepSeekProviderSettings() {
|
|
2857
|
+
return deepseekSettingsService.getSettings();
|
|
2858
|
+
}
|
|
2859
|
+
// The DeepSeek balance for Check Usage: read with the saved key only while
|
|
2860
|
+
// DeepSeek is on, kept for a minute.
|
|
2861
|
+
const deepseekBalanceService = createDeepSeekBalanceService({ getSettings: getDeepSeekProviderSettings });
|
|
2862
|
+
// DeepSeek's own usage export, imported in Settings (or with
|
|
2863
|
+
// `npm run deepseek:import`): the usage card shows its figures for the days
|
|
2864
|
+
// it covers, for the saved key only, and the estimate after.
|
|
2865
|
+
const deepseekUsageImportService = createDeepSeekUsageImportService({
|
|
2866
|
+
db,
|
|
2867
|
+
getApiKey: () => getDeepSeekProviderSettings()?.apiKey || '',
|
|
2868
|
+
});
|
|
2869
|
+
// Where DeepSeek workers keep their Claude Code state (sessions, transcripts,
|
|
2870
|
+
// settings): a directory of the relay's own, so no Claude login is in reach
|
|
2871
|
+
// of a CLI that talks to DeepSeek and their sessions stay out of Claude's.
|
|
2872
|
+
const DEEPSEEK_CLAUDE_CONFIG_DIR = path.join(DATA_DIR, 'deepseek-claude');
|
|
2873
|
+
|
|
2874
|
+
/**
|
|
2875
|
+
* What a DeepSeek session's commits and PRs say: the DeepSeek tab's mode
|
|
2876
|
+
* (OAR or off), the folder override on top, the DeepSeek model's name in the
|
|
2877
|
+
* trailer. A folder set to vanilla gets OAR's: Claude Code's own lines would
|
|
2878
|
+
* credit Claude for DeepSeek's work.
|
|
2879
|
+
*/
|
|
2880
|
+
function deepseekAttributionForConversation(conv, modelId) {
|
|
2881
|
+
const root = String(conv?.runtime_workspace_root_path || conv?.configured_workspace_root_path || '').trim()
|
|
2882
|
+
|| currentWorkspaceRootPath();
|
|
2883
|
+
const resolved = resolveClaudeAttributionMode({
|
|
2884
|
+
providerMode: getDeepSeekProviderSettings().attribution,
|
|
2885
|
+
folderMode: workspaceRootAttributionMode(root),
|
|
2886
|
+
});
|
|
2887
|
+
return buildClaudeAttributionSettings({
|
|
2888
|
+
mode: resolved === 'vanilla' ? 'oar' : resolved,
|
|
2889
|
+
modelLabel: deepseekModelLabel(modelId),
|
|
2890
|
+
});
|
|
2891
|
+
}
|
|
2828
2892
|
// The account's live usage for the Check Usage modal. The Claude Cloud switch
|
|
2829
2893
|
// is the consent to use the Claude CLI's login, so nothing is read while it
|
|
2830
2894
|
// is off.
|
|
@@ -3053,7 +3117,7 @@ function buildSessionWorkerLaunchEnvForSession(targetSessionId) {
|
|
|
3053
3117
|
const providerType = String(runtimeSession?.provider_type || 'github').trim().toLowerCase();
|
|
3054
3118
|
// Clear every provider's variables first (the appliers' kind-guarded deletes
|
|
3055
3119
|
// keep the chained clears order-independent), then apply only the bound one.
|
|
3056
|
-
const cleared = applyClaudeCloudProviderEnvironment(applyCopilotSdkProviderEnvironment(applyGrokProviderEnvironment(applyCursorProviderEnvironment(applyClaudeProviderEnvironment(applyOpenAIProviderEnvironment(sessionWorkerLaunchEnv))))));
|
|
3120
|
+
const cleared = applyDeepSeekProviderEnvironment(applyClaudeCloudProviderEnvironment(applyCopilotSdkProviderEnvironment(applyGrokProviderEnvironment(applyCursorProviderEnvironment(applyClaudeProviderEnvironment(applyOpenAIProviderEnvironment(sessionWorkerLaunchEnv)))))));
|
|
3057
3121
|
// A Claude Cloud conversation gets its own worker and nothing else: it must
|
|
3058
3122
|
// never reach the Copilot CLI fall-through at the end of this function. The
|
|
3059
3123
|
// environment names the worker and the model, no credential.
|
|
@@ -3083,6 +3147,19 @@ function buildSessionWorkerLaunchEnvForSession(targetSessionId) {
|
|
|
3083
3147
|
dataDir: DATA_DIR,
|
|
3084
3148
|
});
|
|
3085
3149
|
}
|
|
3150
|
+
// A DeepSeek conversation gets its own worker and must never reach the
|
|
3151
|
+
// Copilot fall-through below. Without a key the applier throws, and the
|
|
3152
|
+
// conversation gets the launch note.
|
|
3153
|
+
if (providerType === 'deepseek') {
|
|
3154
|
+
const deepseekSettings = getDeepSeekProviderSettings();
|
|
3155
|
+
return applyDeepSeekProviderEnvironment(cleared, {
|
|
3156
|
+
enabled: true,
|
|
3157
|
+
model: String(runtimeSession?.provider_model || runtimeSession?.model || deepseekSettings.model).trim(),
|
|
3158
|
+
apiKey: deepseekSettings.apiKey,
|
|
3159
|
+
baseUrl: deepseekSettings.baseUrl,
|
|
3160
|
+
configDir: DEEPSEEK_CLAUDE_CONFIG_DIR,
|
|
3161
|
+
});
|
|
3162
|
+
}
|
|
3086
3163
|
if (providerType === 'grok') {
|
|
3087
3164
|
const grokSettings = getGrokProviderSettings();
|
|
3088
3165
|
const grokModel = String(runtimeSession?.provider_model || runtimeSession?.model || grokSettings.model).trim();
|
|
@@ -3311,12 +3388,13 @@ async function stopSessionWorkerForProviderRebind(sdkSessionId) {
|
|
|
3311
3388
|
|
|
3312
3389
|
async function reconcileUnstartedConversationProviders({ enabled, model, provider = 'openai' } = {}) {
|
|
3313
3390
|
const normalizedProvider = String(provider || 'openai').trim().toLowerCase();
|
|
3314
|
-
const managedProvider = ['claude', 'cursor', 'grok'].includes(normalizedProvider) ? normalizedProvider : 'openai';
|
|
3391
|
+
const managedProvider = ['claude', 'cursor', 'grok', 'deepseek'].includes(normalizedProvider) ? normalizedProvider : 'openai';
|
|
3315
3392
|
const managedDefaultModel = {
|
|
3316
3393
|
openai: DEFAULT_OPENAI_MODEL,
|
|
3317
3394
|
claude: DEFAULT_CLAUDE_MODEL,
|
|
3318
3395
|
cursor: DEFAULT_CURSOR_MODEL,
|
|
3319
3396
|
grok: DEFAULT_GROK_MODEL,
|
|
3397
|
+
deepseek: DEFAULT_DEEPSEEK_MODEL,
|
|
3320
3398
|
}[managedProvider];
|
|
3321
3399
|
const providerType = enabled === true ? managedProvider : 'github';
|
|
3322
3400
|
const providerModel = enabled === true
|
|
@@ -3390,7 +3468,9 @@ async function reconcileUnstartedConversationProviders({ enabled, model, provide
|
|
|
3390
3468
|
? getCursorProviderSettings().configured === true
|
|
3391
3469
|
: (currentProvider === 'grok'
|
|
3392
3470
|
? getGrokProviderSettings().enabled === true
|
|
3393
|
-
: (currentProvider
|
|
3471
|
+
: (currentProvider === 'deepseek'
|
|
3472
|
+
? getDeepSeekProviderSettings().configured === true
|
|
3473
|
+
: (currentProvider !== 'openai' || getOpenAIProviderSettings().configured === true))));
|
|
3394
3474
|
if (providerWasUpdated && canRestorePreviousProvider) {
|
|
3395
3475
|
stmts.updateRuntimeSessionProvider.run(
|
|
3396
3476
|
currentProvider,
|
|
@@ -5128,6 +5208,11 @@ function createCompactedConversation(sourceConversationId) {
|
|
|
5128
5208
|
targetRuntimeSession = ensureRuntimeSessionBinding(targetConversationId, null, now);
|
|
5129
5209
|
});
|
|
5130
5210
|
tx();
|
|
5211
|
+
// Its schedules go on in the new conversation, both the ones that fire
|
|
5212
|
+
// into it and the ones it set: the archived source would end them.
|
|
5213
|
+
try { relayScheduleService.carryToCompacted(sourceConversationId, targetConversationId); } catch {}
|
|
5214
|
+
// And so do its questions that don't block, asked in it or by it.
|
|
5215
|
+
try { relayAskService.carryToCompacted(sourceConversationId, targetConversationId); } catch {}
|
|
5131
5216
|
|
|
5132
5217
|
return {
|
|
5133
5218
|
sourceConversationId,
|
|
@@ -5458,6 +5543,37 @@ relayEventService = createRelayEventService({
|
|
|
5458
5543
|
logger: console,
|
|
5459
5544
|
});
|
|
5460
5545
|
|
|
5546
|
+
// ─── Relay schedules ──────────────────────────────────────────────────────────
|
|
5547
|
+
// Prompts agents scheduled with remote_relay's `schedule`: kept in the
|
|
5548
|
+
// database, their timers rebuilt at boot (services/relay-schedule-service.mjs).
|
|
5549
|
+
const relayScheduleService = createRelayScheduleService({
|
|
5550
|
+
db,
|
|
5551
|
+
stmts,
|
|
5552
|
+
emit: (event, payload) => io.emit(event, payload),
|
|
5553
|
+
// The scheduled message is an ordinary pending one, like a relay event's.
|
|
5554
|
+
wake: () => {
|
|
5555
|
+
void primePendingSessionWorkers('relay-schedule');
|
|
5556
|
+
sessionWorkerWebSocketService?.emitQueueChanged?.('relay-schedule');
|
|
5557
|
+
},
|
|
5558
|
+
// A firing into another session is a prompt of the conversation that set
|
|
5559
|
+
// it: the agents' loop guard counts it (and holds it once it wants the user).
|
|
5560
|
+
admitFiring: ({ createdBy }) => remoteRelayDispatcher.admitScheduledFiring({ createdBy }),
|
|
5561
|
+
logger: console,
|
|
5562
|
+
});
|
|
5563
|
+
|
|
5564
|
+
// ─── Questions that don't block ───────────────────────────────────────────────
|
|
5565
|
+
// remote_relay's `ask`: a card in the session and the Inbox; the answer
|
|
5566
|
+
// reaches the askers as a relay event (services/relay-ask-service.mjs).
|
|
5567
|
+
const relayAskService = createRelayAskService({
|
|
5568
|
+
db,
|
|
5569
|
+
emit: (event, payload) => io.emit(event, payload),
|
|
5570
|
+
relayEvents: relayEventService,
|
|
5571
|
+
// urgency "now" only.
|
|
5572
|
+
pushQuestion: (question) => pushDispatchService.notifyQuestion?.(question),
|
|
5573
|
+
logger: console,
|
|
5574
|
+
});
|
|
5575
|
+
if (!relayAskService.isAvailable()) console.warn(`[relay-asks] not available this boot: ${relayAskService.problem()}`);
|
|
5576
|
+
|
|
5461
5577
|
/** A worker died: its tasks are gone, and whoever watches its sessions hears of it. */
|
|
5462
5578
|
function noteWorkerDeathForEvents(sessionId, conversationIds, reason) {
|
|
5463
5579
|
const ids = new Set((conversationIds || []).map((id) => String(id || '').trim()).filter(Boolean));
|
|
@@ -5943,6 +6059,14 @@ const readContextFromSessionEvents = contextSnapshotService.readContextFromSessi
|
|
|
5943
6059
|
// Claude-provider sessions live under the Agent SDK's project layout rather than
|
|
5944
6060
|
// ~/.copilot/session-state, so their browsable session folder resolves separately.
|
|
5945
6061
|
const claudeSessionRootResolver = createClaudeSessionRootResolver({ fs, path });
|
|
6062
|
+
// DeepSeek chats run Claude Code with a config dir of their own: only that
|
|
6063
|
+
// root, never the host's ~/.claude.
|
|
6064
|
+
const deepseekSessionRootResolver = createClaudeSessionRootResolver({
|
|
6065
|
+
fs,
|
|
6066
|
+
path,
|
|
6067
|
+
env: { CLAUDE_CONFIG_DIR: DEEPSEEK_CLAUDE_CONFIG_DIR },
|
|
6068
|
+
homedir: () => '',
|
|
6069
|
+
});
|
|
5946
6070
|
const cursorSessionRootResolver = createCursorSessionRootResolver({ fs, path, serverDir: __dirname, dataDir: DATA_DIR });
|
|
5947
6071
|
|
|
5948
6072
|
// ─── Express + Socket.io Setup ────────────────────────────────────────────────
|
|
@@ -6330,8 +6454,12 @@ async function requestSessionWorkerSocketDelivery({ sessionId, pid, reason = 'wo
|
|
|
6330
6454
|
// without a new queue column or push channel. null = Auto.
|
|
6331
6455
|
...((() => {
|
|
6332
6456
|
const conv = out.conversationId ? stmts.getConvAnyStatus.get(out.conversationId) : null;
|
|
6457
|
+
const deliveredProvider = String(out.providerType || '').trim().toLowerCase();
|
|
6333
6458
|
return {
|
|
6334
|
-
|
|
6459
|
+
// DeepSeek's window is 1M; its sessions compact at 400K unless the
|
|
6460
|
+
// conversation pins another window.
|
|
6461
|
+
autoCompactWindow: parseAutoCompactWindow(conv?.auto_compact_window)
|
|
6462
|
+
?? (deliveredProvider === 'deepseek' ? DEEPSEEK_DEFAULT_AUTO_COMPACT_WINDOW : null),
|
|
6335
6463
|
// Per-conversation thinking control, same delivery scoping. An
|
|
6336
6464
|
// explicit null on thinkingEnabled means Host default — the worker
|
|
6337
6465
|
// distinguishes it from an absent key (an older relay), so both
|
|
@@ -6343,7 +6471,14 @@ async function requestSessionWorkerSocketDelivery({ sessionId, pid, reason = 'wo
|
|
|
6343
6471
|
// attribution. Always present on such a delivery, so the worker can
|
|
6344
6472
|
// tell "left as it was" (absent) from "vanilla" (null).
|
|
6345
6473
|
...((() => {
|
|
6346
|
-
|
|
6474
|
+
// A DeepSeek session runs Claude Code on the host like a Claude
|
|
6475
|
+
// one: the same two settings, with DeepSeek's attribution.
|
|
6476
|
+
if (deliveredProvider === 'deepseek') {
|
|
6477
|
+
return {
|
|
6478
|
+
attribution: deepseekAttributionForConversation(conv, out.providerModel || out.model),
|
|
6479
|
+
claudeBackgroundCommandLimitMs: claudeBackgroundCommandLimitMinutesToMs(getClaudeBackgroundCommandLimitMinutes()),
|
|
6480
|
+
};
|
|
6481
|
+
}
|
|
6347
6482
|
if (deliveredProvider !== 'claude' && deliveredProvider !== 'claude-cloud') return {};
|
|
6348
6483
|
return {
|
|
6349
6484
|
attribution: claudeAttributionForConversation(conv, out.providerModel || out.model, {
|
|
@@ -6699,6 +6834,9 @@ app.use((req, _res, next) => {
|
|
|
6699
6834
|
stripRequestPathPrefix(req, remotePath);
|
|
6700
6835
|
next();
|
|
6701
6836
|
});
|
|
6837
|
+
// What a session worker posts loses every key a provider saved in Settings
|
|
6838
|
+
// before any route stores, broadcasts or logs it (worker-secret-redaction.mjs).
|
|
6839
|
+
app.use(workerPostRedaction);
|
|
6702
6840
|
function isApiRequestPath(requestPath) {
|
|
6703
6841
|
return /^\/api(?:\/|$)/.test(String(requestPath || ''));
|
|
6704
6842
|
}
|
|
@@ -6883,7 +7021,10 @@ const remoteRelayRegistry = createRemoteRelayRegistry({
|
|
|
6883
7021
|
platform: process.platform,
|
|
6884
7022
|
version: RUNNING_VERSION,
|
|
6885
7023
|
emit: (event, payload) => io.emit(event, payload),
|
|
6886
|
-
onRemove: (entry) => {
|
|
7024
|
+
onRemove: (entry) => {
|
|
7025
|
+
void relayEventLinkService?.noteRelayRemoved(entry);
|
|
7026
|
+
try { relayAskService.noteRelayRemoved(entry?.id); } catch {}
|
|
7027
|
+
},
|
|
6887
7028
|
logger: console,
|
|
6888
7029
|
});
|
|
6889
7030
|
relayEventLinkService = createRelayEventLinkService({
|
|
@@ -6892,6 +7033,13 @@ relayEventLinkService = createRelayEventLinkService({
|
|
|
6892
7033
|
client: remoteRelayClient,
|
|
6893
7034
|
logger: console,
|
|
6894
7035
|
});
|
|
7036
|
+
// The answers to asks this relay's agents put on paired relays.
|
|
7037
|
+
const relayAskLinkService = createRelayAskLinkService({
|
|
7038
|
+
asks: relayAskService,
|
|
7039
|
+
registry: remoteRelayRegistry,
|
|
7040
|
+
client: remoteRelayClient,
|
|
7041
|
+
logger: console,
|
|
7042
|
+
});
|
|
6895
7043
|
const remoteRelayPairing = createRemoteRelayPairing({
|
|
6896
7044
|
registry: remoteRelayRegistry,
|
|
6897
7045
|
client: remoteRelayClient,
|
|
@@ -6946,6 +7094,9 @@ const remoteRelayDispatcher = createRemoteRelayDispatcher({
|
|
|
6946
7094
|
resolveConversationId: (id) => resolveConversationRecord({ conversationId: id, sdkSessionId: id })?.id || id,
|
|
6947
7095
|
relayEvents: relayEventService,
|
|
6948
7096
|
relayEventLink: relayEventLinkService,
|
|
7097
|
+
relaySchedules: relayScheduleService,
|
|
7098
|
+
relayAsks: relayAskService,
|
|
7099
|
+
relayAskLink: relayAskLinkService,
|
|
6949
7100
|
logger: console,
|
|
6950
7101
|
});
|
|
6951
7102
|
|
|
@@ -7020,6 +7171,10 @@ const sharedRouteDeps = {
|
|
|
7020
7171
|
getClaudeCloudProviderSettings,
|
|
7021
7172
|
claudeCloudSettingsService,
|
|
7022
7173
|
claudeCloudSessionService,
|
|
7174
|
+
getDeepSeekProviderSettings,
|
|
7175
|
+
deepseekSettingsService,
|
|
7176
|
+
deepseekBalanceService,
|
|
7177
|
+
deepseekUsageImportService,
|
|
7023
7178
|
claudeCloudRepoService,
|
|
7024
7179
|
// The login's state for the workers' nudge route (never the token).
|
|
7025
7180
|
claudeCloudLoginStatus: { describe: () => claudeCloudCredentials.describe() },
|
|
@@ -7144,6 +7299,8 @@ const sharedRouteDeps = {
|
|
|
7144
7299
|
relayRestartOrchestrator,
|
|
7145
7300
|
resolveSessionStateRoot,
|
|
7146
7301
|
resolveClaudeSessionRoot: claudeSessionRootResolver.resolveClaudeSessionRoot,
|
|
7302
|
+
resolveDeepSeekSessionRoot: deepseekSessionRootResolver.resolveClaudeSessionRoot,
|
|
7303
|
+
deepseekClaudeConfigDir: DEEPSEEK_CLAUDE_CONFIG_DIR,
|
|
7147
7304
|
resolveCursorSessionRoot: cursorSessionRootResolver.resolveCursorSessionRoot,
|
|
7148
7305
|
getTurnCeilingMinutes,
|
|
7149
7306
|
setTurnCeilingMinutes,
|
|
@@ -7165,6 +7322,8 @@ const sharedRouteDeps = {
|
|
|
7165
7322
|
collectHostActivity,
|
|
7166
7323
|
usageLimitPauseService,
|
|
7167
7324
|
relayEventService,
|
|
7325
|
+
relayScheduleService,
|
|
7326
|
+
relayAskService,
|
|
7168
7327
|
sessionActivityTracker,
|
|
7169
7328
|
resolveConversationRecord,
|
|
7170
7329
|
markSharedViewerPresence,
|
|
@@ -7210,6 +7369,30 @@ registerRelayEventRoutes(app, {
|
|
|
7210
7369
|
relayEventService,
|
|
7211
7370
|
remoteRelayInbound,
|
|
7212
7371
|
});
|
|
7372
|
+
registerRelayScheduleRoutes(app, {
|
|
7373
|
+
auth,
|
|
7374
|
+
relayScheduleService,
|
|
7375
|
+
remoteRelayInbound,
|
|
7376
|
+
});
|
|
7377
|
+
registerDeepSeekUsageImportRoutes(app, {
|
|
7378
|
+
auth,
|
|
7379
|
+
deepseekUsageImportService,
|
|
7380
|
+
remoteRelayInbound,
|
|
7381
|
+
// The usage card's currency (the balance's), so the import's answer can
|
|
7382
|
+
// say whether the card uses the export's rows.
|
|
7383
|
+
getCardCurrency: async () => deepseekCardCurrency(await deepseekBalanceService.getBalance()),
|
|
7384
|
+
});
|
|
7385
|
+
registerRelayAskRoutes(app, {
|
|
7386
|
+
auth,
|
|
7387
|
+
db,
|
|
7388
|
+
stmts,
|
|
7389
|
+
formatQuestionRow,
|
|
7390
|
+
relayAskService,
|
|
7391
|
+
relayAskLinkService,
|
|
7392
|
+
remoteRelayInbound,
|
|
7393
|
+
remoteRelayRegistry,
|
|
7394
|
+
remoteRelayClient,
|
|
7395
|
+
});
|
|
7213
7396
|
registerRelayRestartRoutes(app, {
|
|
7214
7397
|
auth,
|
|
7215
7398
|
// Read only: a GET neither fires the restart nor asks the question.
|
|
@@ -7480,10 +7663,12 @@ function ensureRuntimeSessionBinding(
|
|
|
7480
7663
|
// default below and bind a GitHub (Copilot CLI) session.
|
|
7481
7664
|
: (normalizedRequestedProviderType === 'claude-cloud'
|
|
7482
7665
|
? 'claude-cloud'
|
|
7483
|
-
: (normalizedRequestedProviderType === '
|
|
7484
|
-
? '
|
|
7485
|
-
: (
|
|
7486
|
-
|
|
7666
|
+
: (normalizedRequestedProviderType === 'deepseek'
|
|
7667
|
+
? 'deepseek'
|
|
7668
|
+
: (normalizedRequestedProviderType === 'github'
|
|
7669
|
+
? 'github'
|
|
7670
|
+
: (openAISettings?.enabled ? 'openai' : 'github')))))));
|
|
7671
|
+
const resolvedProviderModel = (resolvedProviderType === 'openai' || resolvedProviderType === 'claude' || resolvedProviderType === 'cursor' || resolvedProviderType === 'grok' || resolvedProviderType === 'claude-cloud' || resolvedProviderType === 'deepseek')
|
|
7487
7672
|
? (String(providerModel || normalizedModel || '').trim() || null)
|
|
7488
7673
|
: null;
|
|
7489
7674
|
try {
|
|
@@ -7572,6 +7757,11 @@ void primePendingSessionWorkers('startup');
|
|
|
7572
7757
|
// heartbeat are swept from here on.
|
|
7573
7758
|
relayEventService.start();
|
|
7574
7759
|
relayEventLinkService.start();
|
|
7760
|
+
// Schedules get their timers back; one that came due while the relay was
|
|
7761
|
+
// down fires now, once.
|
|
7762
|
+
relayScheduleService.start();
|
|
7763
|
+
// Asks on paired relays still open there: look for their answers again.
|
|
7764
|
+
relayAskLinkService.start();
|
|
7575
7765
|
runtimeTimers.pendingWorkerPrime = setInterval(() => {
|
|
7576
7766
|
void primePendingSessionWorkers('maintenance');
|
|
7577
7767
|
detachedWorkerWatch.sweep().catch((error) => {
|
|
@@ -7892,6 +8082,8 @@ function shutdownRuntime(reason = 'unknown', { exitCode = 0 } = {}) {
|
|
|
7892
8082
|
try { relayEventService.noteRelayExit({ action: runtimeShutdownExitCode === RELAY_RESTART_EXIT_CODE ? 'restart' : 'shutdown', reason }); } catch {}
|
|
7893
8083
|
try { relayEventService.stop(); } catch {}
|
|
7894
8084
|
try { relayEventLinkService.stop(); } catch {}
|
|
8085
|
+
try { relayScheduleService.stop(); } catch {}
|
|
8086
|
+
try { relayAskLinkService.stop(); } catch {}
|
|
7895
8087
|
tmuxInspectorSocketService.stop();
|
|
7896
8088
|
stopWorkspaceFileWatcher();
|
|
7897
8089
|
sshTunnelManager.stop();
|
|
@@ -68,6 +68,9 @@ export function createCacheRebuildService({
|
|
|
68
68
|
// Session groups: a purged head leaves its group, and a wipe is followed
|
|
69
69
|
// by a repair of what the wiped rows headed. Absent in older wiring.
|
|
70
70
|
conversationGroups = null,
|
|
71
|
+
// (conversationId, { deleted }) => void: a conversation this service
|
|
72
|
+
// archived or removed (its relay schedules end). Absent in older wiring.
|
|
73
|
+
onConversationGone = null,
|
|
71
74
|
}) {
|
|
72
75
|
const legacyConversationList = buildLegacyConversationList(db);
|
|
73
76
|
const boundConversationList = buildBoundConversationList(db);
|
|
@@ -99,10 +102,19 @@ export function createCacheRebuildService({
|
|
|
99
102
|
db.prepare(`DELETE FROM conversations WHERE id = ?`).run(sid);
|
|
100
103
|
})();
|
|
101
104
|
closePreviews(sid);
|
|
105
|
+
noteGone(sid, { deleted: true });
|
|
102
106
|
deleteOrphanedUploads(orphanedUploads);
|
|
103
107
|
return orphanedUploads;
|
|
104
108
|
}
|
|
105
109
|
|
|
110
|
+
function noteGone(conversationId, options) {
|
|
111
|
+
try {
|
|
112
|
+
onConversationGone?.(conversationId, options);
|
|
113
|
+
} catch (error) {
|
|
114
|
+
console.warn(`[cache] the end of a conversation was not passed on: ${error?.message || error}`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
106
118
|
function closePreviews(conversationId) {
|
|
107
119
|
try {
|
|
108
120
|
closeConversationPreviews?.(conversationId);
|
|
@@ -115,7 +127,9 @@ export function createCacheRebuildService({
|
|
|
115
127
|
const sid = normalizeId(conversationId);
|
|
116
128
|
if (!sid) return false;
|
|
117
129
|
const result = db.prepare(`UPDATE conversations SET archived = 1, updated_at = ? WHERE id = ? AND status != 'deleted'`).run(new Date().toISOString(), sid);
|
|
118
|
-
|
|
130
|
+
const archived = Number(result?.changes || 0) > 0;
|
|
131
|
+
if (archived) noteGone(sid, { deleted: false });
|
|
132
|
+
return archived;
|
|
119
133
|
}
|
|
120
134
|
|
|
121
135
|
function fullWipeCache() {
|
|
@@ -176,7 +190,10 @@ export function createCacheRebuildService({
|
|
|
176
190
|
})();
|
|
177
191
|
|
|
178
192
|
for (const conversationId of conversationIds) {
|
|
179
|
-
if (!manualTitleConversationSet.has(conversationId))
|
|
193
|
+
if (!manualTitleConversationSet.has(conversationId)) {
|
|
194
|
+
closePreviews(conversationId);
|
|
195
|
+
noteGone(conversationId, { deleted: true });
|
|
196
|
+
}
|
|
180
197
|
}
|
|
181
198
|
|
|
182
199
|
deleteOrphanedUploads(uploadHashes);
|