@oar-sh/oar 0.9.5 → 0.9.7
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 +12 -32
- package/.github/extensions/web-relay/skills/question-bridge.mjs +14 -39
- package/CHANGELOG.md +340 -0
- package/README.md +196 -26
- package/package.json +2 -1
- package/server/README.md +174 -7
- package/server/claude-cloud-worker/claude-cloud-event-normalizer.mjs +340 -0
- package/server/claude-cloud-worker/claude-cloud-session-process.mjs +1325 -0
- package/server/claude-cloud-worker/claude-cloud-session-worker.mjs +155 -0
- package/server/claude-worker/claude-remote-relay-tool.mjs +2 -0
- package/server/claude-worker/claude-sdk-adapter.mjs +18 -2
- package/server/claude-worker/claude-session-process.mjs +151 -1
- package/server/claude-worker/claude-session-worker.mjs +6 -0
- package/server/claude-worker/claude-turn-failure.mjs +92 -0
- package/server/claude-worker/claude-turn-publisher.mjs +52 -3
- package/server/claude-worker/sdk-message-normalizer.mjs +13 -1
- package/server/copilot-worker/copilot-model-switch.mjs +29 -3
- package/server/copilot-worker/copilot-question-bridge.mjs +2 -4
- package/server/copilot-worker/copilot-runtime-tree.mjs +91 -0
- package/server/copilot-worker/copilot-sdk-adapter.mjs +48 -8
- package/server/copilot-worker/copilot-sdk-session-process.mjs +897 -166
- package/server/cursor-worker/cursor-sdk-adapter.mjs +80 -13
- package/server/cursor-worker/cursor-turn-runner.mjs +5 -2
- package/server/db-schema.mjs +24 -0
- package/server/grok-worker/acp-client.mjs +68 -12
- package/server/grok-worker/grok-sdk-adapter.mjs +4 -0
- package/server/grok-worker/grok-turn-runner.mjs +6 -2
- package/server/migrations/0006-workspace-root-settings.mjs +67 -0
- package/server/migrations/0007-claude-cloud.mjs +82 -0
- package/server/public/app/agent-sessions-settings-ui.js +197 -0
- package/server/public/app/agent-sessions-settings.mjs +134 -0
- package/server/public/app/api-client.js +160 -6
- package/server/public/app/attachments-view.js +20 -1
- package/server/public/app/attribution-control.mjs +46 -0
- package/server/public/app/bootstrap.js +347 -31
- package/server/public/app/claude-cloud-conversation-ui.js +110 -0
- package/server/public/app/claude-cloud-settings-ui.js +374 -0
- package/server/public/app/claude-cloud-ui.mjs +611 -0
- package/server/public/app/composer-control-state.mjs +15 -3
- package/server/public/app/context-menu.mjs +140 -0
- package/server/public/app/context-tier-options.mjs +4 -0
- package/server/public/app/conversation-origin-ui.js +27 -0
- package/server/public/app/conversation-provider-indicator.mjs +22 -4
- package/server/public/app/conversation-view.js +178 -8
- package/server/public/app/cwd-picker.js +12 -0
- package/server/public/app/journal-view.js +704 -28
- package/server/public/app/markdown-strikethrough.mjs +31 -0
- package/server/public/app/math-dollars.mjs +89 -0
- package/server/public/app/model-selector-options.mjs +1 -1
- package/server/public/app/plan-usage-view.mjs +36 -8
- package/server/public/app/relay-board-inline.mjs +70 -0
- package/server/public/app/relay-board-view.js +157 -61
- package/server/public/app/relay-error-ctas.mjs +28 -5
- package/server/public/app/remote-relay-origin-view.mjs +120 -4
- package/server/public/app/router.js +49 -12
- package/server/public/app/settings-modal.js +33 -2
- package/server/public/app/settings-tabs.js +1 -1
- package/server/public/app/socket-handlers.js +60 -0
- package/server/public/app/store.js +4 -1
- package/server/public/app/subagent-fold.mjs +61 -0
- package/server/public/app/usage-limit-ui.js +225 -0
- package/server/public/index.html +471 -8
- package/server/relay-tools.md +13 -1
- package/server/repositories/message-repository.mjs +10 -1
- package/server/repositories/question-repository.mjs +1 -0
- package/server/repositories/remote-relay-repository.mjs +72 -1
- package/server/repositories/session-repository.mjs +47 -1
- package/server/routes/claude-cloud-routes.mjs +140 -0
- package/server/routes/git-routes.mjs +37 -0
- package/server/routes/messages-routes.mjs +202 -26
- package/server/routes/relay-board-routes.mjs +56 -2
- package/server/routes/remote-relay-routes.mjs +15 -1
- package/server/routes/sessions-routes.mjs +538 -72
- package/server/server-runtime.mjs +291 -27
- package/server/services/claude-account-usage-service.mjs +176 -0
- package/server/services/claude-cloud-repo-service.mjs +408 -0
- package/server/services/claude-cloud-session-service.mjs +326 -0
- package/server/services/claude-cloud-settings-service.mjs +269 -0
- package/server/services/git-remote-service.mjs +115 -0
- package/server/services/plan-usage-claude-cloud.mjs +211 -0
- package/server/services/plan-usage-claude.mjs +202 -40
- package/server/services/plan-usage-contract.mjs +39 -3
- package/server/services/plan-usage-service.mjs +82 -2
- package/server/services/preview-proxy-server.mjs +64 -36
- package/server/services/process-list-service.mjs +122 -0
- package/server/services/provider-reasoning-effort.mjs +3 -0
- package/server/services/relay-console-log.mjs +199 -0
- package/server/services/remote-relay-caller-context.mjs +78 -19
- package/server/services/remote-relay-client.mjs +9 -0
- package/server/services/remote-relay-dispatcher.mjs +451 -39
- package/server/services/remote-relay-inbound.mjs +83 -1
- package/server/services/remote-relay-loopback.mjs +245 -0
- package/server/services/remote-relay-registry-service.mjs +52 -0
- package/server/services/session-worker-launch-service.mjs +100 -15
- package/server/services/session-worker-process-service.mjs +410 -138
- package/server/services/session-worker-registry-service.mjs +1 -1
- package/server/services/session-worker-stop-service.mjs +24 -13
- package/server/services/session-worker-supervisor-service.mjs +89 -2
- package/server/services/usage-limit-pause-service.mjs +443 -0
- package/server/services/worker-launch-note-service.mjs +151 -0
- package/shared/ask-user-bridge.mjs +14 -39
- package/shared/claude-attribution.mjs +117 -0
- package/shared/claude-cloud/api-client.mjs +809 -0
- package/shared/claude-cloud/base-url.mjs +47 -0
- package/shared/claude-cloud/credentials.mjs +221 -0
- package/shared/claude-cloud/repo-url.mjs +111 -0
- package/shared/claude-usage-limit.mjs +257 -0
- package/shared/control-poller.mjs +69 -20
- package/shared/failure-note-text.mjs +38 -0
- package/shared/provider-routing.mjs +4 -2
- package/shared/question-wait.mjs +108 -0
- package/shared/remote-relay-contract.mjs +167 -22
- package/shared/remote-relay-tool-core.mjs +11 -8
- package/shared/steer-settle-failure.mjs +17 -0
- package/shared/steer-settle-markers.mjs +8 -0
- package/shared/worker-runtime/process-tree.mjs +483 -0
- package/shared/worker-runtime/relay-errors.mjs +31 -0
- package/shared/worker-runtime/send-and-wait-errors.mjs +4 -4
- package/shared/worker-runtime/turn-liveness.mjs +249 -0
|
@@ -70,6 +70,7 @@ import {
|
|
|
70
70
|
refreshConversationHistory,
|
|
71
71
|
updateConversationTitle,
|
|
72
72
|
updateConversationPreferences,
|
|
73
|
+
updateWorkspaceRootAttribution,
|
|
73
74
|
createConversationShareLink,
|
|
74
75
|
updateDefaultSessionWorkspaceRoot,
|
|
75
76
|
scheduleContextUsageRefresh,
|
|
@@ -83,6 +84,12 @@ import {
|
|
|
83
84
|
confirmNewConversationModel,
|
|
84
85
|
closeNewConversationModelModal,
|
|
85
86
|
deleteConv,
|
|
87
|
+
archiveConv,
|
|
88
|
+
unarchiveConv,
|
|
89
|
+
toggleArchivedConversations,
|
|
90
|
+
isArchivedConversationsView,
|
|
91
|
+
refreshNewConversationCloudRepoList,
|
|
92
|
+
openOriginConversation,
|
|
86
93
|
} from './journal-view.js';
|
|
87
94
|
import {
|
|
88
95
|
loadRelayQuestions,
|
|
@@ -197,6 +204,7 @@ import {
|
|
|
197
204
|
thinkingEnabledFromKey,
|
|
198
205
|
thinkingEnabledToKey,
|
|
199
206
|
} from './context-usage-view.mjs';
|
|
207
|
+
import { renderAttributionControlHtml } from './attribution-control.mjs';
|
|
200
208
|
import {
|
|
201
209
|
autoCompactWindowFromIndex,
|
|
202
210
|
autoCompactWindowToIndex,
|
|
@@ -312,6 +320,7 @@ import {
|
|
|
312
320
|
cancelQueuedHostSuspend,
|
|
313
321
|
} from './action-confirmations.js';
|
|
314
322
|
import { applyPendingActionsFromStatus, initHostSuspendUi } from './host-suspend-ui.js';
|
|
323
|
+
import { applyUsageLimitFromStatus, initUsageLimitUi } from './usage-limit-ui.js';
|
|
315
324
|
import {
|
|
316
325
|
startClaudeRelogin,
|
|
317
326
|
submitClaudeLoginCodeFromInput,
|
|
@@ -328,6 +337,22 @@ import {
|
|
|
328
337
|
openGrokLogoutConfirmation,
|
|
329
338
|
confirmGrokLogout,
|
|
330
339
|
} from './grok-auth-ui.js';
|
|
340
|
+
import {
|
|
341
|
+
getClaudeCloudSettings,
|
|
342
|
+
openClaudeCloudLoginTab,
|
|
343
|
+
refreshClaudeCloudSettingsState,
|
|
344
|
+
saveClaudeCloudSettings,
|
|
345
|
+
subscribeClaudeCloudSettings,
|
|
346
|
+
toggleClaudeCloudProvider,
|
|
347
|
+
} from './claude-cloud-settings-ui.js';
|
|
348
|
+
import { openCurrentCloudSession, syncClaudeCloudConversationUi } from './claude-cloud-conversation-ui.js';
|
|
349
|
+
import { syncConversationOriginHeader } from './conversation-origin-ui.js';
|
|
350
|
+
import {
|
|
351
|
+
previewAgentSessionsMaxWait,
|
|
352
|
+
saveAgentSessionsMaxWait,
|
|
353
|
+
toggleAgentSessions,
|
|
354
|
+
} from './agent-sessions-settings-ui.js';
|
|
355
|
+
import { CLAUDE_CLOUD_PROVIDER, claudeCloudComposerModelIds } from './claude-cloud-ui.mjs';
|
|
331
356
|
import {
|
|
332
357
|
confirmCliInstall,
|
|
333
358
|
runCliInstall,
|
|
@@ -359,6 +384,7 @@ const PROVIDER_LABELS = {
|
|
|
359
384
|
openai: 'OpenAI',
|
|
360
385
|
'openai-byok': 'OpenAI (BYOK)',
|
|
361
386
|
claude: 'Claude SDK',
|
|
387
|
+
'claude-cloud': 'Claude Cloud',
|
|
362
388
|
cursor: 'Cursor SDK',
|
|
363
389
|
grok: 'Grok',
|
|
364
390
|
'github-copilot': 'GitHub Copilot',
|
|
@@ -1196,6 +1222,9 @@ function currentGrokModelLock() {
|
|
|
1196
1222
|
};
|
|
1197
1223
|
}
|
|
1198
1224
|
|
|
1225
|
+
// A cloud session's model is switched between turns like a local Claude
|
|
1226
|
+
// chat's (the worker sends set_model before the message), so there is no
|
|
1227
|
+
// cloud lock on the picker.
|
|
1199
1228
|
function currentRuntimeModelLock() {
|
|
1200
1229
|
return currentOpenAIModelLock() || currentGrokModelLock();
|
|
1201
1230
|
}
|
|
@@ -1350,6 +1379,7 @@ function normalizeModelSelectorProviderType(providerType = '') {
|
|
|
1350
1379
|
const normalized = String(providerType || '').trim().toLowerCase();
|
|
1351
1380
|
if (normalized === 'openai' || normalized === 'openai-byok') return 'openai';
|
|
1352
1381
|
if (normalized === 'claude') return 'claude';
|
|
1382
|
+
if (normalized === CLAUDE_CLOUD_PROVIDER) return CLAUDE_CLOUD_PROVIDER;
|
|
1353
1383
|
if (normalized === 'cursor') return 'cursor';
|
|
1354
1384
|
if (normalized === 'grok') return 'grok';
|
|
1355
1385
|
return 'github';
|
|
@@ -1379,18 +1409,43 @@ function modelVisibleForActiveProvider(modelId, activeProviderType, providersByM
|
|
|
1379
1409
|
const hasClaude = providers.includes('claude');
|
|
1380
1410
|
const hasCursor = providers.includes('cursor');
|
|
1381
1411
|
const hasGrok = providers.includes('grok');
|
|
1412
|
+
// Only present if a relay tags catalog rows for Claude Cloud; such a row is
|
|
1413
|
+
// then as exclusive as the other runtimes' rows.
|
|
1414
|
+
const hasClaudeCloud = providers.includes(CLAUDE_CLOUD_PROVIDER);
|
|
1382
1415
|
const hasNonExclusiveProvider = providers.some((provider) => (
|
|
1383
1416
|
provider !== 'openai-byok'
|
|
1384
1417
|
&& provider !== 'claude'
|
|
1385
1418
|
&& provider !== 'cursor'
|
|
1386
1419
|
&& provider !== 'grok'
|
|
1420
|
+
&& provider !== CLAUDE_CLOUD_PROVIDER
|
|
1387
1421
|
));
|
|
1388
1422
|
const activeProvider = normalizeModelSelectorProviderType(activeProviderType);
|
|
1389
1423
|
if (activeProvider === 'openai') return hasOpenAIByok;
|
|
1390
1424
|
if (activeProvider === 'claude') return hasClaude;
|
|
1391
1425
|
if (activeProvider === 'cursor') return hasCursor;
|
|
1392
1426
|
if (activeProvider === 'grok') return hasGrok;
|
|
1393
|
-
return !((hasOpenAIByok || hasClaude || hasCursor || hasGrok) && !hasNonExclusiveProvider);
|
|
1427
|
+
return !((hasOpenAIByok || hasClaude || hasCursor || hasGrok || hasClaudeCloud) && !hasNonExclusiveProvider);
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
// A Claude Cloud conversation does not pick from the shared catalog: its
|
|
1431
|
+
// models are the list of its settings tab (plain ids, no Auto), plus the model
|
|
1432
|
+
// the conversation is bound to.
|
|
1433
|
+
function buildClaudeCloudModelSelectorOptions(models, providersByModel, metadataByModel) {
|
|
1434
|
+
const conversation = currentConvId ? conversations[currentConvId] : null;
|
|
1435
|
+
const cloudModels = claudeCloudComposerModelIds({
|
|
1436
|
+
settings: getClaudeCloudSettings(),
|
|
1437
|
+
catalogModels: models,
|
|
1438
|
+
providersByModel,
|
|
1439
|
+
conversationModel: conversation?.runtimeProviderModel
|
|
1440
|
+
|| conversation?.runtime_provider_model
|
|
1441
|
+
|| conversation?.preferredModel
|
|
1442
|
+
|| '',
|
|
1443
|
+
});
|
|
1444
|
+
return buildCatalogModelOptions(cloudModels, {
|
|
1445
|
+
autoValue: AUTO_MODEL_OPTION,
|
|
1446
|
+
metadataByModel,
|
|
1447
|
+
annotateContextWindow: false,
|
|
1448
|
+
}).filter((option) => option.value.toLowerCase() !== AUTO_MODEL_OPTION);
|
|
1394
1449
|
}
|
|
1395
1450
|
|
|
1396
1451
|
// metadataByModel is a parameter because the catalog refresh builds the next
|
|
@@ -1402,6 +1457,9 @@ function buildModelSelectorOptions(
|
|
|
1402
1457
|
activeProviderType = '',
|
|
1403
1458
|
metadataByModel = modelCatalogState.modelMetadataByModel,
|
|
1404
1459
|
) {
|
|
1460
|
+
if (normalizeModelSelectorProviderType(activeProviderType) === CLAUDE_CLOUD_PROVIDER) {
|
|
1461
|
+
return buildClaudeCloudModelSelectorOptions(models, providersByModel, metadataByModel);
|
|
1462
|
+
}
|
|
1405
1463
|
const normalizedOptions = buildCatalogModelOptions(models.length ? models : [FALLBACK_MODEL], {
|
|
1406
1464
|
autoValue: AUTO_MODEL_OPTION,
|
|
1407
1465
|
metadataByModel,
|
|
@@ -1418,6 +1476,11 @@ function reasoningOptionsForModel(modelId = '') {
|
|
|
1418
1476
|
if (!isModelMetadataHealthy()) return [];
|
|
1419
1477
|
const key = String(modelId || '').trim().toLowerCase();
|
|
1420
1478
|
const provider = activeComposerProviderType();
|
|
1479
|
+
// A cloud session has no effort choice; the catalog's efforts for the same
|
|
1480
|
+
// model id belong to the local Claude and Copilot runtimes. The one value is
|
|
1481
|
+
// still listed: the (hidden) select must hold it, because a send without an
|
|
1482
|
+
// effort is refused as "model metadata missing".
|
|
1483
|
+
if (provider === CLAUDE_CLOUD_PROVIDER) return ['none'];
|
|
1421
1484
|
const providerOptions = modelCatalogState.reasoningByProvider?.[provider]?.[key];
|
|
1422
1485
|
if (Array.isArray(providerOptions)) return normalizeReasoningEffortList(providerOptions);
|
|
1423
1486
|
return normalizeReasoningEffortList(modelCatalogState.reasoningByModel?.[key] || []);
|
|
@@ -1756,7 +1819,11 @@ function updateModelPricingDetails(modelId, tier) {
|
|
|
1756
1819
|
const grid = document.getElementById('model-pricing-grid');
|
|
1757
1820
|
if (!details || !summary || !grid) return;
|
|
1758
1821
|
const metadata = modelCatalogState.modelMetadataByModel?.[modelId] || {};
|
|
1759
|
-
|
|
1822
|
+
// The catalog's prices are Copilot credits; a cloud session is billed to the
|
|
1823
|
+
// Claude account, so they would be the wrong numbers for it.
|
|
1824
|
+
const pricing = activeComposerProviderType() === CLAUDE_CLOUD_PROVIDER
|
|
1825
|
+
? null
|
|
1826
|
+
: metadata?.pricing?.[tier === 'long_context' ? 'longContext' : 'default'];
|
|
1760
1827
|
if (!pricing || typeof pricing !== 'object') {
|
|
1761
1828
|
details.open = false;
|
|
1762
1829
|
details.style.display = 'none';
|
|
@@ -1783,12 +1850,14 @@ function updateModelPricingDetails(modelId, tier) {
|
|
|
1783
1850
|
details.style.display = grid.childElementCount ? '' : 'none';
|
|
1784
1851
|
}
|
|
1785
1852
|
|
|
1786
|
-
// Relay modes each provider backend actually honors.
|
|
1787
|
-
//
|
|
1853
|
+
// Relay modes each provider backend actually honors. The local providers
|
|
1854
|
+
// support the full set — Copilot/OpenAI via prompt-context
|
|
1788
1855
|
// injection, Claude via permissionMode + system-prompt appends, Cursor via
|
|
1789
1856
|
// native plan mode plus message-text nudges for ask/autopilot — but the
|
|
1790
1857
|
// composer builds its options from this table so a provider that loses (or
|
|
1791
|
-
// gains) a mode only needs a change here.
|
|
1858
|
+
// gains) a mode only needs a change here. A Claude Cloud session runs in the
|
|
1859
|
+
// cloud's own permission mode and ignores relay modes, so it has the one
|
|
1860
|
+
// entry and its selector is hidden (body.claude-cloud-conversation).
|
|
1792
1861
|
const RELAY_MODE_LABELS = { agent: 'Agent', ask: 'Ask', plan: 'Plan', autopilot: 'Autopilot' };
|
|
1793
1862
|
const RELAY_MODES_BY_PROVIDER = {
|
|
1794
1863
|
github: ['agent', 'ask', 'plan', 'autopilot'],
|
|
@@ -1796,6 +1865,7 @@ const RELAY_MODES_BY_PROVIDER = {
|
|
|
1796
1865
|
claude: ['agent', 'ask', 'plan', 'autopilot'],
|
|
1797
1866
|
cursor: ['agent', 'ask', 'plan', 'autopilot'],
|
|
1798
1867
|
grok: ['agent', 'ask', 'plan', 'autopilot'],
|
|
1868
|
+
'claude-cloud': ['agent'],
|
|
1799
1869
|
};
|
|
1800
1870
|
|
|
1801
1871
|
function relayModesForProvider(providerType = '') {
|
|
@@ -1847,8 +1917,9 @@ async function persistCurrentConversationPreferences() {
|
|
|
1847
1917
|
// record an effort the user never picked when they only changed the mode.
|
|
1848
1918
|
const reasoningEffort = String(document.getElementById('reasoning-effort-select')?.value || '').trim().toLowerCase();
|
|
1849
1919
|
// These keys hold the last explicit choice, so they are not rolled back when
|
|
1850
|
-
// the write below fails: the user did make the choice either way.
|
|
1851
|
-
|
|
1920
|
+
// the write below fails: the user did make the choice either way. A cloud
|
|
1921
|
+
// chat has no mode to choose, so its fixed "agent" is not such a choice.
|
|
1922
|
+
if (activeComposerProviderType() !== CLAUDE_CLOUD_PROVIDER) localStorage.setItem(MODE_STORAGE_KEY, mode);
|
|
1852
1923
|
if (model) localStorage.setItem(MODEL_STORAGE_KEY, model);
|
|
1853
1924
|
if (reasoningEffort) localStorage.setItem(REASONING_STORAGE_KEY, reasoningEffort);
|
|
1854
1925
|
|
|
@@ -2336,6 +2407,9 @@ function renderModelVariantCatalogBody() {
|
|
|
2336
2407
|
// shows Claude-SDK rows exclusively, the Cursor SDK tab Cursor rows, the
|
|
2337
2408
|
// Grok tab Grok rows, and Copilot never shows rows that a different runtime
|
|
2338
2409
|
// contributed (there is no cross-runtime switching).
|
|
2410
|
+
// Claude Cloud has no rows of its own to select: its picker is fed by the
|
|
2411
|
+
// Claude Cloud settings tab, so a row tagged for it belongs to no tab.
|
|
2412
|
+
if (entryProvider === CLAUDE_CLOUD_PROVIDER) return false;
|
|
2339
2413
|
if (activeTab === 'anthropic') {
|
|
2340
2414
|
return entryProvider === 'claude';
|
|
2341
2415
|
}
|
|
@@ -2359,6 +2433,7 @@ function renderModelVariantCatalogBody() {
|
|
|
2359
2433
|
&& provider !== 'claude'
|
|
2360
2434
|
&& provider !== 'cursor'
|
|
2361
2435
|
&& provider !== 'grok'
|
|
2436
|
+
&& provider !== CLAUDE_CLOUD_PROVIDER
|
|
2362
2437
|
));
|
|
2363
2438
|
}
|
|
2364
2439
|
return true;
|
|
@@ -2785,6 +2860,66 @@ async function saveSelectedModelsFromModal() {
|
|
|
2785
2860
|
// click pins a provider so the modal's Refresh re-renders onto the same tab
|
|
2786
2861
|
// instead of snapping back. Reset on every fresh open of the modal.
|
|
2787
2862
|
let usageTabProviderOverride = null;
|
|
2863
|
+
// The last usage report, shown at once on the next open while the open tab's
|
|
2864
|
+
// provider is read live; kept across reloads in sessionStorage. `fetchedAt`
|
|
2865
|
+
// (per card id) decides whether a tab needs a live read when it is shown.
|
|
2866
|
+
const USAGE_REPORT_STORAGE_KEY = 'oar.usage.lastReport.v1';
|
|
2867
|
+
const USAGE_LIVE_FRESH_MS = 60_000;
|
|
2868
|
+
let lastUsageReport = null;
|
|
2869
|
+
let usageRefreshInFlight = null;
|
|
2870
|
+
|
|
2871
|
+
function readStoredUsageReport() {
|
|
2872
|
+
try {
|
|
2873
|
+
const parsed = JSON.parse(sessionStorage.getItem(USAGE_REPORT_STORAGE_KEY) || 'null');
|
|
2874
|
+
return parsed && Array.isArray(parsed.providers) ? parsed : null;
|
|
2875
|
+
} catch {
|
|
2876
|
+
return null;
|
|
2877
|
+
}
|
|
2878
|
+
}
|
|
2879
|
+
|
|
2880
|
+
function storeUsageReport(report) {
|
|
2881
|
+
lastUsageReport = report;
|
|
2882
|
+
try {
|
|
2883
|
+
sessionStorage.setItem(USAGE_REPORT_STORAGE_KEY, JSON.stringify(report));
|
|
2884
|
+
} catch {
|
|
2885
|
+
// Storage full or unavailable: the module copy still serves this page.
|
|
2886
|
+
}
|
|
2887
|
+
}
|
|
2888
|
+
|
|
2889
|
+
/**
|
|
2890
|
+
* The next report: cards the answer read live replace the kept ones; a card
|
|
2891
|
+
* the answer only served from its cache replaces nothing newer we hold (a
|
|
2892
|
+
* restarted relay has an empty cache and would otherwise blank good cards).
|
|
2893
|
+
*/
|
|
2894
|
+
function mergeUsageReports(kept, answer) {
|
|
2895
|
+
if (!kept || !Array.isArray(kept.providers)) return answer;
|
|
2896
|
+
const live = new Set(Array.isArray(answer?.live) ? answer.live : []);
|
|
2897
|
+
const fetchedAt = { ...(kept.fetchedAt || {}) };
|
|
2898
|
+
const keptById = new Map(kept.providers.map((card) => [normalizeUsageProvider(card?.provider), card]));
|
|
2899
|
+
const providers = (answer.providers || []).map((card) => {
|
|
2900
|
+
const id = normalizeUsageProvider(card?.provider);
|
|
2901
|
+
const previous = keptById.get(id);
|
|
2902
|
+
if (live.has(id) || !previous) {
|
|
2903
|
+
fetchedAt[id] = answer.fetchedAt?.[id] || new Date().toISOString();
|
|
2904
|
+
return card;
|
|
2905
|
+
}
|
|
2906
|
+
// Unavailable from the cache but known from before: keep what we had.
|
|
2907
|
+
return card?.status === 'unavailable' && previous.status !== 'unavailable' ? previous : card;
|
|
2908
|
+
});
|
|
2909
|
+
return { ...answer, providers, fetchedAt };
|
|
2910
|
+
}
|
|
2911
|
+
|
|
2912
|
+
function usageCardIsFresh(provider) {
|
|
2913
|
+
const at = Date.parse(String(lastUsageReport?.fetchedAt?.[provider] || ''));
|
|
2914
|
+
return Number.isFinite(at) && Date.now() - at < USAGE_LIVE_FRESH_MS;
|
|
2915
|
+
}
|
|
2916
|
+
|
|
2917
|
+
/** The "Updating…" marker on one card, while its live read runs. */
|
|
2918
|
+
function markUsageCardUpdating(provider, updating) {
|
|
2919
|
+
const body = document.getElementById('summary-modal-body');
|
|
2920
|
+
const card = body?.querySelector(`[data-usage-panel="${CSS.escape(provider)}"]`);
|
|
2921
|
+
if (card) card.toggleAttribute('data-updating', !!updating);
|
|
2922
|
+
}
|
|
2788
2923
|
|
|
2789
2924
|
/**
|
|
2790
2925
|
* Delegated because #summary-modal-body's innerHTML is replaced on every render
|
|
@@ -2802,7 +2937,7 @@ function initUsageTabControl() {
|
|
|
2802
2937
|
});
|
|
2803
2938
|
}
|
|
2804
2939
|
|
|
2805
|
-
/**
|
|
2940
|
+
/** DOM toggle first; a tab whose card is older than a minute is then read live. */
|
|
2806
2941
|
function selectUsageTab(root, provider) {
|
|
2807
2942
|
const target = String(provider || '').trim();
|
|
2808
2943
|
if (!target) return;
|
|
@@ -2818,6 +2953,78 @@ function selectUsageTab(root, provider) {
|
|
|
2818
2953
|
panel.hidden = panel.dataset.usagePanel !== target;
|
|
2819
2954
|
}
|
|
2820
2955
|
usageTabProviderOverride = target;
|
|
2956
|
+
if (!usageCardIsFresh(target)) {
|
|
2957
|
+
refreshUsageProvider(target).catch(() => {});
|
|
2958
|
+
}
|
|
2959
|
+
}
|
|
2960
|
+
|
|
2961
|
+
/**
|
|
2962
|
+
* Read one provider live and swap the result into the modal. Serialised: a
|
|
2963
|
+
* second request while one runs waits for it and is then made if still
|
|
2964
|
+
* needed (the tab may have changed).
|
|
2965
|
+
*/
|
|
2966
|
+
async function refreshUsageProvider(provider) {
|
|
2967
|
+
const target = String(provider || '').trim();
|
|
2968
|
+
if (!target) return;
|
|
2969
|
+
if (usageRefreshInFlight) {
|
|
2970
|
+
await usageRefreshInFlight.catch(() => {});
|
|
2971
|
+
if (usageCardIsFresh(target)) return;
|
|
2972
|
+
}
|
|
2973
|
+
markUsageCardUpdating(target, true);
|
|
2974
|
+
const run = (async () => {
|
|
2975
|
+
const [usageResult, authResult] = await Promise.allSettled([
|
|
2976
|
+
loadUsageSummary({ providers: [target] }),
|
|
2977
|
+
getClaudeAuthStatus(),
|
|
2978
|
+
]);
|
|
2979
|
+
if (usageResult.status === 'rejected') throw usageResult.reason;
|
|
2980
|
+
const answer = usageResult.value;
|
|
2981
|
+
if (!answer || !Array.isArray(answer.providers)) return;
|
|
2982
|
+
if (summaryModalState.kind !== 'usage') {
|
|
2983
|
+
storeUsageReport(mergeUsageReports(lastUsageReport, answer));
|
|
2984
|
+
return;
|
|
2985
|
+
}
|
|
2986
|
+
const account = authResult.status === 'fulfilled' ? claudeAccountFromAuthStatus(authResult.value) : null;
|
|
2987
|
+
renderUsageReport(mergeUsageReports(lastUsageReport, answer), account);
|
|
2988
|
+
})();
|
|
2989
|
+
usageRefreshInFlight = run;
|
|
2990
|
+
try {
|
|
2991
|
+
await run;
|
|
2992
|
+
} finally {
|
|
2993
|
+
if (usageRefreshInFlight === run) usageRefreshInFlight = null;
|
|
2994
|
+
markUsageCardUpdating(target, false);
|
|
2995
|
+
}
|
|
2996
|
+
}
|
|
2997
|
+
|
|
2998
|
+
/** Render a report into the open modal and keep it for the next open. */
|
|
2999
|
+
function renderUsageReport(report, account, { updating = '' } = {}) {
|
|
3000
|
+
storeUsageReport(report);
|
|
3001
|
+
const activeProvider = usageTabProviderOverride
|
|
3002
|
+
|| normalizeUsageProvider(activeComposerProviderType());
|
|
3003
|
+
const planHtml = renderPlanUsageHtml(withClaudeAccount(report, account), { activeProvider });
|
|
3004
|
+
if (!planHtml) return false;
|
|
3005
|
+
initUsageTabControl();
|
|
3006
|
+
renderSummaryModalContent({
|
|
3007
|
+
title: 'Plan usage',
|
|
3008
|
+
subtitle: `${planUsageSubtitle(report)}${updating ? ' · updating…' : ''}`,
|
|
3009
|
+
bodyHtml: planHtml,
|
|
3010
|
+
refresh: refreshOpenUsageTab,
|
|
3011
|
+
kind: 'usage',
|
|
3012
|
+
});
|
|
3013
|
+
if (updating) markUsageCardUpdating(updating, true);
|
|
3014
|
+
return true;
|
|
3015
|
+
}
|
|
3016
|
+
|
|
3017
|
+
/** The modal's Refresh button: the open tab live, nothing else. */
|
|
3018
|
+
async function refreshOpenUsageTab() {
|
|
3019
|
+
const body = document.getElementById('summary-modal-body');
|
|
3020
|
+
const active = body?.querySelector('[data-usage-tab].active')?.dataset.usageTab
|
|
3021
|
+
|| usageTabProviderOverride
|
|
3022
|
+
|| normalizeUsageProvider(activeComposerProviderType());
|
|
3023
|
+
if (!lastUsageReport || !active) {
|
|
3024
|
+
await loadUsageSummaryAndRender();
|
|
3025
|
+
return;
|
|
3026
|
+
}
|
|
3027
|
+
await refreshUsageProvider(active);
|
|
2821
3028
|
}
|
|
2822
3029
|
|
|
2823
3030
|
/**
|
|
@@ -2847,11 +3054,18 @@ function withClaudeAccount(report, account) {
|
|
|
2847
3054
|
};
|
|
2848
3055
|
}
|
|
2849
3056
|
|
|
3057
|
+
/**
|
|
3058
|
+
* The first open with nothing kept: the open tab's provider is read live and
|
|
3059
|
+
* the other cards come from the relay's cache (or are read once when it has
|
|
3060
|
+
* none). With a kept report, showUsage renders it first and only refreshes.
|
|
3061
|
+
*/
|
|
2850
3062
|
async function loadUsageSummaryAndRender() {
|
|
3063
|
+
const activeProvider = usageTabProviderOverride
|
|
3064
|
+
|| normalizeUsageProvider(activeComposerProviderType());
|
|
2851
3065
|
// Usage is the point of the modal; the account line is a garnish, so the two
|
|
2852
3066
|
// are settled independently and a failing auth probe cannot blank the cards.
|
|
2853
3067
|
const [usageResult, authResult] = await Promise.allSettled([
|
|
2854
|
-
loadUsageSummary(),
|
|
3068
|
+
loadUsageSummary({ providers: activeProvider ? [activeProvider] : null }),
|
|
2855
3069
|
getClaudeAuthStatus(),
|
|
2856
3070
|
]);
|
|
2857
3071
|
if (usageResult.status === 'rejected') throw usageResult.reason;
|
|
@@ -2860,20 +3074,7 @@ async function loadUsageSummaryAndRender() {
|
|
|
2860
3074
|
const account = authResult.status === 'fulfilled'
|
|
2861
3075
|
? claudeAccountFromAuthStatus(authResult.value)
|
|
2862
3076
|
: null;
|
|
2863
|
-
|
|
2864
|
-
|| normalizeUsageProvider(activeComposerProviderType());
|
|
2865
|
-
const planHtml = renderPlanUsageHtml(withClaudeAccount(d, account), { activeProvider });
|
|
2866
|
-
if (planHtml) {
|
|
2867
|
-
initUsageTabControl();
|
|
2868
|
-
renderSummaryModalContent({
|
|
2869
|
-
title: 'Plan usage',
|
|
2870
|
-
subtitle: planUsageSubtitle(d),
|
|
2871
|
-
bodyHtml: planHtml,
|
|
2872
|
-
refresh: loadUsageSummaryAndRender,
|
|
2873
|
-
kind: 'usage',
|
|
2874
|
-
});
|
|
2875
|
-
return;
|
|
2876
|
-
}
|
|
3077
|
+
if (Array.isArray(d.providers) && renderUsageReport(mergeUsageReports(lastUsageReport, d), account)) return;
|
|
2877
3078
|
// Legacy relay (or a relay without the plan-usage service): fall back to the
|
|
2878
3079
|
// Copilot-only text summary rather than showing an empty modal.
|
|
2879
3080
|
const pct = d.premiumInteractions?.percentRemaining != null
|
|
@@ -2953,6 +3154,36 @@ function setThinkingControlValue({ enabled, display }) {
|
|
|
2953
3154
|
}
|
|
2954
3155
|
}
|
|
2955
3156
|
|
|
3157
|
+
// The folder's commit attribution select in the 🧠 modal: one request per
|
|
3158
|
+
// change, then the modal is re-read so the effect text matches the relay.
|
|
3159
|
+
let attributionControlUpdateInFlight = false;
|
|
3160
|
+
function initAttributionControl() {
|
|
3161
|
+
const body = document.getElementById('summary-modal-body');
|
|
3162
|
+
if (!body || body.dataset.attributionControlBound === 'true') return;
|
|
3163
|
+
body.dataset.attributionControlBound = 'true';
|
|
3164
|
+
body.addEventListener('change', async (event) => {
|
|
3165
|
+
const select = event.target?.closest?.('#ctx-attribution-select');
|
|
3166
|
+
if (!select) return;
|
|
3167
|
+
const path = String(select.dataset.workspaceRoot || '').trim();
|
|
3168
|
+
if (!path || attributionControlUpdateInFlight) return;
|
|
3169
|
+
attributionControlUpdateInFlight = true;
|
|
3170
|
+
select.disabled = true;
|
|
3171
|
+
try {
|
|
3172
|
+
const response = await updateWorkspaceRootAttribution(path, select.value || null);
|
|
3173
|
+
if (!response || response.ok === false) throw new Error(String(response?.error || 'The relay refused the change'));
|
|
3174
|
+
showTransientRelayNotice(select.value
|
|
3175
|
+
? `Commit attribution for this folder: ${select.value}.`
|
|
3176
|
+
: 'Commit attribution for this folder follows the provider setting again.');
|
|
3177
|
+
await refreshSummaryModal();
|
|
3178
|
+
} catch (error) {
|
|
3179
|
+
showTransientRelayNotice(`Could not change the commit attribution: ${error?.message || error}`);
|
|
3180
|
+
select.disabled = false;
|
|
3181
|
+
} finally {
|
|
3182
|
+
attributionControlUpdateInFlight = false;
|
|
3183
|
+
}
|
|
3184
|
+
});
|
|
3185
|
+
}
|
|
3186
|
+
|
|
2956
3187
|
function initThinkingControl() {
|
|
2957
3188
|
const body = document.getElementById('summary-modal-body');
|
|
2958
3189
|
if (!body || body.dataset.thinkingBound === '1') return;
|
|
@@ -3054,9 +3285,11 @@ async function loadContextSummaryAndRender(convId) {
|
|
|
3054
3285
|
const refreshLookupId = sessionId || trimmedConvId || null;
|
|
3055
3286
|
const providerLabel = payload.providerType === 'claude'
|
|
3056
3287
|
? 'Claude'
|
|
3057
|
-
: (payload.providerType ===
|
|
3058
|
-
? '
|
|
3059
|
-
: (payload.providerType === '
|
|
3288
|
+
: (payload.providerType === CLAUDE_CLOUD_PROVIDER
|
|
3289
|
+
? 'Claude Cloud'
|
|
3290
|
+
: (payload.providerType === 'cursor'
|
|
3291
|
+
? 'Cursor'
|
|
3292
|
+
: (payload.providerType === 'grok' ? 'Grok' : 'Copilot')));
|
|
3060
3293
|
const subtitle = sessionId
|
|
3061
3294
|
? `${providerLabel} session ${sessionId.slice(0, 8)}`
|
|
3062
3295
|
: (trimmedConvId ? `Conversation ${trimmedConvId.slice(0, 8)}` : 'No conversation selected');
|
|
@@ -3085,6 +3318,11 @@ async function loadContextSummaryAndRender(convId) {
|
|
|
3085
3318
|
thinkingDisplay: payload.thinkingDisplay ?? null,
|
|
3086
3319
|
})
|
|
3087
3320
|
: '';
|
|
3321
|
+
// What this folder's commits say, with the folder override: Claude, and a
|
|
3322
|
+
// Claude Cloud chat that was started from a folder (no path, no row).
|
|
3323
|
+
const attributionHtml = payload.providerType === 'claude' || payload.providerType === 'claude-cloud'
|
|
3324
|
+
? renderAttributionControlHtml(payload.attribution || null)
|
|
3325
|
+
: '';
|
|
3088
3326
|
// The slider's writes need the conversation id even when the modal was
|
|
3089
3327
|
// opened through an sdk_session_id.
|
|
3090
3328
|
autoCompactControlConversationId = String(
|
|
@@ -3101,10 +3339,10 @@ async function loadContextSummaryAndRender(convId) {
|
|
|
3101
3339
|
// The structured breakdown is the answer; the runtime's own text dump stays
|
|
3102
3340
|
// available underneath for the details it carries that categories don't.
|
|
3103
3341
|
const bodyHtml = usageHtml
|
|
3104
|
-
? `${usageHtml}${autoCompactHtml}${thinkingHtml}${detailText
|
|
3342
|
+
? `${usageHtml}${autoCompactHtml}${thinkingHtml}${attributionHtml}${detailText
|
|
3105
3343
|
? `<details class="ctx-usage-raw"><summary>Raw details</summary><pre>${escHtml(detailText)}</pre></details>`
|
|
3106
3344
|
: ''}`
|
|
3107
|
-
: `${autoCompactHtml}${thinkingHtml}<pre>${escHtml(detailText || 'No context data available.')}</pre>`;
|
|
3345
|
+
: `${autoCompactHtml}${thinkingHtml}${attributionHtml}<pre>${escHtml(detailText || 'No context data available.')}</pre>`;
|
|
3108
3346
|
|
|
3109
3347
|
renderSummaryModalContent({
|
|
3110
3348
|
title: 'Context usage',
|
|
@@ -3124,15 +3362,27 @@ async function showUsage() {
|
|
|
3124
3362
|
btn.textContent = '⏳';
|
|
3125
3363
|
btn.disabled = true;
|
|
3126
3364
|
}
|
|
3365
|
+
// What was shown last time is shown again at once; only the open tab's
|
|
3366
|
+
// provider is read live, and its card is swapped in when the answer comes.
|
|
3367
|
+
const kept = lastUsageReport || readStoredUsageReport();
|
|
3368
|
+
const activeProvider = normalizeUsageProvider(activeComposerProviderType());
|
|
3127
3369
|
openSummaryModal({
|
|
3128
3370
|
title: 'Plan usage',
|
|
3129
3371
|
subtitle: 'Loading…',
|
|
3130
|
-
bodyHtml: '<div class="summary-loading">Fetching plan usage
|
|
3131
|
-
refresh:
|
|
3372
|
+
bodyHtml: '<div class="summary-loading">Fetching plan usage…</div>',
|
|
3373
|
+
refresh: refreshOpenUsageTab,
|
|
3132
3374
|
kind: 'usage',
|
|
3133
3375
|
});
|
|
3134
|
-
setSummaryModalLoading(true);
|
|
3135
3376
|
try {
|
|
3377
|
+
if (kept && renderUsageReport(kept, null, { updating: activeProvider })) {
|
|
3378
|
+
if (btn) {
|
|
3379
|
+
btn.textContent = btn.id === 'chat-menu-usage' ? '📊 Check Usage' : '📊';
|
|
3380
|
+
btn.disabled = false;
|
|
3381
|
+
}
|
|
3382
|
+
await refreshUsageProvider(activeProvider);
|
|
3383
|
+
return;
|
|
3384
|
+
}
|
|
3385
|
+
setSummaryModalLoading(true);
|
|
3136
3386
|
await loadUsageSummaryAndRender();
|
|
3137
3387
|
} catch (e) {
|
|
3138
3388
|
renderSummaryModalContent({
|
|
@@ -3250,6 +3500,7 @@ async function refreshSessionWorkerStatus() {
|
|
|
3250
3500
|
if (!status) return;
|
|
3251
3501
|
syncQueueStatusMenuEntry(status);
|
|
3252
3502
|
applyPendingActionsFromStatus(status);
|
|
3503
|
+
applyUsageLimitFromStatus(status);
|
|
3253
3504
|
if (setSessionWorkerStatesFromStatusPayload(status.sessionWorker)) {
|
|
3254
3505
|
renderConvList();
|
|
3255
3506
|
// A worker steering snapshot may have changed; the composer's Steer
|
|
@@ -3745,6 +3996,11 @@ function syncChatTitleControls() {
|
|
|
3745
3996
|
killBtn.disabled = sharedMode || !convId || !sdkSessionId;
|
|
3746
3997
|
killBtn.hidden = sharedMode || !convId;
|
|
3747
3998
|
}
|
|
3999
|
+
const archiveBtn = document.getElementById('chat-menu-archive');
|
|
4000
|
+
if (archiveBtn) {
|
|
4001
|
+
archiveBtn.hidden = sharedMode || !convId;
|
|
4002
|
+
archiveBtn.textContent = conversation?.archived === true ? '📂 Unarchive conversation' : '🗄 Archive conversation';
|
|
4003
|
+
}
|
|
3748
4004
|
if (editor) {
|
|
3749
4005
|
editor.hidden = !editing;
|
|
3750
4006
|
}
|
|
@@ -3760,6 +4016,11 @@ function syncChatTitleControls() {
|
|
|
3760
4016
|
if (!isStatusViewActive()) {
|
|
3761
4017
|
syncChatHeaderWorkspaceLabel();
|
|
3762
4018
|
}
|
|
4019
|
+
// Cloud line, hidden controls and the image-only attach button all follow
|
|
4020
|
+
// the conversation on screen, like the header does.
|
|
4021
|
+
syncClaudeCloudConversationUi();
|
|
4022
|
+
// "via agent": the conversation an agent on this relay started this one from.
|
|
4023
|
+
syncConversationOriginHeader();
|
|
3763
4024
|
syncChatTitleWatcherIndicator();
|
|
3764
4025
|
}
|
|
3765
4026
|
|
|
@@ -3917,6 +4178,21 @@ initTmuxInspectorView({
|
|
|
3917
4178
|
closeChatActionsMenu,
|
|
3918
4179
|
});
|
|
3919
4180
|
|
|
4181
|
+
// The composer's model list of a cloud conversation comes from the Claude
|
|
4182
|
+
// Cloud settings, which load (and change) independently of the catalog.
|
|
4183
|
+
function refreshClaudeCloudComposerModels() {
|
|
4184
|
+
if (isSharedReaderMode()) return;
|
|
4185
|
+
if (normalizeModelSelectorProviderType(activeComposerProviderType()) !== CLAUDE_CLOUD_PROVIDER) return;
|
|
4186
|
+
const select = document.getElementById('model-select');
|
|
4187
|
+
// Without a healthy catalog there is nothing to rebuild from (and a rebuild
|
|
4188
|
+
// would raise the metadata blocker); the next catalog load builds the list
|
|
4189
|
+
// with whatever settings have arrived by then.
|
|
4190
|
+
if (!select || !isModelMetadataHealthy()) return;
|
|
4191
|
+
// Dropping the scope makes syncAutoModelAvailability rebuild the options.
|
|
4192
|
+
delete select.dataset.providerScope;
|
|
4193
|
+
syncAutoModelAvailability();
|
|
4194
|
+
}
|
|
4195
|
+
|
|
3920
4196
|
function showAuthGate(error = '') {
|
|
3921
4197
|
document.getElementById('startup-loading')?.remove();
|
|
3922
4198
|
document.getElementById('auth-gate').style.display = 'flex';
|
|
@@ -3959,6 +4235,7 @@ async function initApp() {
|
|
|
3959
4235
|
if (!sharedMode) {
|
|
3960
4236
|
syncSuspendHostVisibility();
|
|
3961
4237
|
initHostSuspendUi();
|
|
4238
|
+
initUsageLimitUi();
|
|
3962
4239
|
}
|
|
3963
4240
|
setupViewportTracking();
|
|
3964
4241
|
bindChatSelectionGuard();
|
|
@@ -4020,6 +4297,17 @@ async function initApp() {
|
|
|
4020
4297
|
});
|
|
4021
4298
|
});
|
|
4022
4299
|
}
|
|
4300
|
+
const chatMenuArchiveBtn = document.getElementById('chat-menu-archive');
|
|
4301
|
+
bindMenuAction(chatMenuArchiveBtn, (event) => {
|
|
4302
|
+
event.preventDefault();
|
|
4303
|
+
event.stopPropagation();
|
|
4304
|
+
lockChatActionsMenuShield(350);
|
|
4305
|
+
closeChatActionsMenu();
|
|
4306
|
+
const convId = String(currentConvId || '').trim();
|
|
4307
|
+
if (!convId) return;
|
|
4308
|
+
const archived = conversations[convId]?.archived === true;
|
|
4309
|
+
void (archived ? unarchiveConv(null, convId) : archiveConv(null, convId));
|
|
4310
|
+
});
|
|
4023
4311
|
const chatMenuShareConversationBtn = document.getElementById('chat-menu-share-conversation');
|
|
4024
4312
|
bindMenuAction(chatMenuShareConversationBtn, (event) => {
|
|
4025
4313
|
event.preventDefault();
|
|
@@ -4079,6 +4367,14 @@ async function initApp() {
|
|
|
4079
4367
|
closeChatActionsMenu();
|
|
4080
4368
|
openGitChangesModal();
|
|
4081
4369
|
});
|
|
4370
|
+
const chatMenuOpenCloudSessionBtn = document.getElementById('chat-menu-open-cloud-session');
|
|
4371
|
+
bindMenuAction(chatMenuOpenCloudSessionBtn, (event) => {
|
|
4372
|
+
event.preventDefault();
|
|
4373
|
+
event.stopPropagation();
|
|
4374
|
+
lockChatActionsMenuShield(350);
|
|
4375
|
+
closeChatActionsMenu();
|
|
4376
|
+
openCurrentCloudSession(showExternalLinkFallback);
|
|
4377
|
+
});
|
|
4082
4378
|
const chatMenuSettingsBtn = document.getElementById('chat-menu-settings');
|
|
4083
4379
|
bindMenuAction(chatMenuSettingsBtn, (event) => {
|
|
4084
4380
|
event.preventDefault();
|
|
@@ -4169,6 +4465,7 @@ async function initApp() {
|
|
|
4169
4465
|
}
|
|
4170
4466
|
initAutoCompactWindowControl();
|
|
4171
4467
|
initThinkingControl();
|
|
4468
|
+
initAttributionControl();
|
|
4172
4469
|
initModeSelector();
|
|
4173
4470
|
initModelSelector();
|
|
4174
4471
|
initReasoningSelector();
|
|
@@ -4187,6 +4484,11 @@ async function initApp() {
|
|
|
4187
4484
|
await refreshOpenAISettingsState();
|
|
4188
4485
|
await refreshCursorSettingsState();
|
|
4189
4486
|
await refreshGrokSettingsState();
|
|
4487
|
+
// Not awaited: only a Claude Cloud conversation needs it (its model list),
|
|
4488
|
+
// and the subscription below rebuilds that list when the answer arrives. A
|
|
4489
|
+
// relay without the provider answers nothing and nothing changes.
|
|
4490
|
+
subscribeClaudeCloudSettings(refreshClaudeCloudComposerModels);
|
|
4491
|
+
void refreshClaudeCloudSettingsState();
|
|
4190
4492
|
await refreshModelCatalog(true);
|
|
4191
4493
|
initFullscreenButton();
|
|
4192
4494
|
initInstallButton();
|
|
@@ -4308,6 +4610,9 @@ window.dismissAvailableUpdate = dismissAvailableUpdate;
|
|
|
4308
4610
|
window.dismissUpdateOutcome = dismissUpdateOutcome;
|
|
4309
4611
|
window.saveGrokSettings = saveGrokSettings;
|
|
4310
4612
|
window.toggleGrokProvider = toggleGrokProvider;
|
|
4613
|
+
window.saveClaudeCloudSettings = saveClaudeCloudSettings;
|
|
4614
|
+
window.toggleClaudeCloudProvider = toggleClaudeCloudProvider;
|
|
4615
|
+
window.openClaudeCloudLoginTab = openClaudeCloudLoginTab;
|
|
4311
4616
|
window.saveCursorSettings = saveCursorSettings;
|
|
4312
4617
|
window.removeCursorSettings = removeCursorSettings;
|
|
4313
4618
|
window.saveCursorAllowanceSettings = saveCursorAllowanceSettings;
|
|
@@ -4331,6 +4636,9 @@ window.addRemoteRelayFromForm = addRemoteRelayFromForm;
|
|
|
4331
4636
|
window.showRemoteRelayTokenField = showRemoteRelayTokenField;
|
|
4332
4637
|
window.saveRemoteRelayPublicUrl = saveRemoteRelayPublicUrl;
|
|
4333
4638
|
window.toggleRemoteRelayInbound = toggleRemoteRelayInbound;
|
|
4639
|
+
window.toggleAgentSessions = toggleAgentSessions;
|
|
4640
|
+
window.previewAgentSessionsMaxWait = previewAgentSessionsMaxWait;
|
|
4641
|
+
window.saveAgentSessionsMaxWait = saveAgentSessionsMaxWait;
|
|
4334
4642
|
window.doAuth = doAuth;
|
|
4335
4643
|
window.initApp = initApp;
|
|
4336
4644
|
window.connectSocket = connectSocket;
|
|
@@ -4367,7 +4675,12 @@ window.newConversation = newConversation;
|
|
|
4367
4675
|
window.confirmNewConversationModel = confirmNewConversationModel;
|
|
4368
4676
|
window.closeNewConversationModelModal = closeNewConversationModelModal;
|
|
4369
4677
|
window.deleteConv = deleteConv;
|
|
4678
|
+
window.archiveConv = archiveConv;
|
|
4679
|
+
window.unarchiveConv = unarchiveConv;
|
|
4680
|
+
window.toggleArchivedConversations = toggleArchivedConversations;
|
|
4681
|
+
window.refreshNewConversationCloudRepoList = refreshNewConversationCloudRepoList;
|
|
4370
4682
|
window.openConversation = openConversation;
|
|
4683
|
+
window.openOriginConversation = openOriginConversation;
|
|
4371
4684
|
window.toggleStatusView = toggleStatusView;
|
|
4372
4685
|
window.refreshConversations = refreshConversations;
|
|
4373
4686
|
window.renderConvList = renderConvList;
|
|
@@ -4425,6 +4738,9 @@ window.syncChatTitleControls = syncChatTitleControls;
|
|
|
4425
4738
|
window.closeChatActionsMenu = closeChatActionsMenu;
|
|
4426
4739
|
window.openSuspendHostConfirmation = openSuspendHostConfirmation;
|
|
4427
4740
|
window.confirmKillCurrentSession = confirmKillCurrentSession;
|
|
4741
|
+
// The conversation list's context menu reaches these for the open conversation.
|
|
4742
|
+
window.openKillSessionConfirmation = openKillSessionConfirmation;
|
|
4743
|
+
window.openChatTitleEditor = openChatTitleEditor;
|
|
4428
4744
|
window.confirmRestartWebRelay = confirmRestartWebRelay;
|
|
4429
4745
|
window.confirmSuspendHost = confirmSuspendHost;
|
|
4430
4746
|
window.cancelQueuedHostSuspend = cancelQueuedHostSuspend;
|