@oar-sh/oar 0.9.6 → 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/CHANGELOG.md +172 -0
- package/README.md +176 -22
- package/package.json +1 -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 +33 -0
- package/server/claude-worker/claude-session-worker.mjs +6 -0
- package/server/db-schema.mjs +17 -0
- 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 +143 -4
- package/server/public/app/attachments-view.js +20 -1
- package/server/public/app/attribution-control.mjs +46 -0
- package/server/public/app/bootstrap.js +344 -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 +11 -0
- 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 +83 -4
- package/server/public/app/cwd-picker.js +12 -0
- package/server/public/app/journal-view.js +698 -28
- 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-error-ctas.mjs +16 -2
- 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 +51 -0
- package/server/public/index.html +381 -7
- package/server/relay-tools.md +13 -1
- package/server/repositories/remote-relay-repository.mjs +42 -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 +120 -15
- package/server/routes/remote-relay-routes.mjs +15 -1
- package/server/routes/sessions-routes.mjs +502 -72
- package/server/server-runtime.mjs +258 -25
- 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/provider-reasoning-effort.mjs +3 -0
- package/server/services/remote-relay-caller-context.mjs +49 -10
- package/server/services/remote-relay-client.mjs +9 -0
- package/server/services/remote-relay-dispatcher.mjs +383 -35
- package/server/services/remote-relay-inbound.mjs +49 -2
- 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 +348 -129
- package/server/services/session-worker-stop-service.mjs +24 -13
- package/server/services/session-worker-supervisor-service.mjs +56 -1
- package/server/services/usage-limit-pause-service.mjs +3 -1
- package/server/services/worker-launch-note-service.mjs +151 -0
- 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/provider-routing.mjs +4 -2
- package/shared/remote-relay-contract.mjs +153 -22
- package/shared/remote-relay-tool-core.mjs +11 -8
- package/shared/steer-settle-failure.mjs +1 -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,
|
|
@@ -329,6 +337,22 @@ import {
|
|
|
329
337
|
openGrokLogoutConfirmation,
|
|
330
338
|
confirmGrokLogout,
|
|
331
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';
|
|
332
356
|
import {
|
|
333
357
|
confirmCliInstall,
|
|
334
358
|
runCliInstall,
|
|
@@ -360,6 +384,7 @@ const PROVIDER_LABELS = {
|
|
|
360
384
|
openai: 'OpenAI',
|
|
361
385
|
'openai-byok': 'OpenAI (BYOK)',
|
|
362
386
|
claude: 'Claude SDK',
|
|
387
|
+
'claude-cloud': 'Claude Cloud',
|
|
363
388
|
cursor: 'Cursor SDK',
|
|
364
389
|
grok: 'Grok',
|
|
365
390
|
'github-copilot': 'GitHub Copilot',
|
|
@@ -1197,6 +1222,9 @@ function currentGrokModelLock() {
|
|
|
1197
1222
|
};
|
|
1198
1223
|
}
|
|
1199
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.
|
|
1200
1228
|
function currentRuntimeModelLock() {
|
|
1201
1229
|
return currentOpenAIModelLock() || currentGrokModelLock();
|
|
1202
1230
|
}
|
|
@@ -1351,6 +1379,7 @@ function normalizeModelSelectorProviderType(providerType = '') {
|
|
|
1351
1379
|
const normalized = String(providerType || '').trim().toLowerCase();
|
|
1352
1380
|
if (normalized === 'openai' || normalized === 'openai-byok') return 'openai';
|
|
1353
1381
|
if (normalized === 'claude') return 'claude';
|
|
1382
|
+
if (normalized === CLAUDE_CLOUD_PROVIDER) return CLAUDE_CLOUD_PROVIDER;
|
|
1354
1383
|
if (normalized === 'cursor') return 'cursor';
|
|
1355
1384
|
if (normalized === 'grok') return 'grok';
|
|
1356
1385
|
return 'github';
|
|
@@ -1380,18 +1409,43 @@ function modelVisibleForActiveProvider(modelId, activeProviderType, providersByM
|
|
|
1380
1409
|
const hasClaude = providers.includes('claude');
|
|
1381
1410
|
const hasCursor = providers.includes('cursor');
|
|
1382
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);
|
|
1383
1415
|
const hasNonExclusiveProvider = providers.some((provider) => (
|
|
1384
1416
|
provider !== 'openai-byok'
|
|
1385
1417
|
&& provider !== 'claude'
|
|
1386
1418
|
&& provider !== 'cursor'
|
|
1387
1419
|
&& provider !== 'grok'
|
|
1420
|
+
&& provider !== CLAUDE_CLOUD_PROVIDER
|
|
1388
1421
|
));
|
|
1389
1422
|
const activeProvider = normalizeModelSelectorProviderType(activeProviderType);
|
|
1390
1423
|
if (activeProvider === 'openai') return hasOpenAIByok;
|
|
1391
1424
|
if (activeProvider === 'claude') return hasClaude;
|
|
1392
1425
|
if (activeProvider === 'cursor') return hasCursor;
|
|
1393
1426
|
if (activeProvider === 'grok') return hasGrok;
|
|
1394
|
-
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);
|
|
1395
1449
|
}
|
|
1396
1450
|
|
|
1397
1451
|
// metadataByModel is a parameter because the catalog refresh builds the next
|
|
@@ -1403,6 +1457,9 @@ function buildModelSelectorOptions(
|
|
|
1403
1457
|
activeProviderType = '',
|
|
1404
1458
|
metadataByModel = modelCatalogState.modelMetadataByModel,
|
|
1405
1459
|
) {
|
|
1460
|
+
if (normalizeModelSelectorProviderType(activeProviderType) === CLAUDE_CLOUD_PROVIDER) {
|
|
1461
|
+
return buildClaudeCloudModelSelectorOptions(models, providersByModel, metadataByModel);
|
|
1462
|
+
}
|
|
1406
1463
|
const normalizedOptions = buildCatalogModelOptions(models.length ? models : [FALLBACK_MODEL], {
|
|
1407
1464
|
autoValue: AUTO_MODEL_OPTION,
|
|
1408
1465
|
metadataByModel,
|
|
@@ -1419,6 +1476,11 @@ function reasoningOptionsForModel(modelId = '') {
|
|
|
1419
1476
|
if (!isModelMetadataHealthy()) return [];
|
|
1420
1477
|
const key = String(modelId || '').trim().toLowerCase();
|
|
1421
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'];
|
|
1422
1484
|
const providerOptions = modelCatalogState.reasoningByProvider?.[provider]?.[key];
|
|
1423
1485
|
if (Array.isArray(providerOptions)) return normalizeReasoningEffortList(providerOptions);
|
|
1424
1486
|
return normalizeReasoningEffortList(modelCatalogState.reasoningByModel?.[key] || []);
|
|
@@ -1757,7 +1819,11 @@ function updateModelPricingDetails(modelId, tier) {
|
|
|
1757
1819
|
const grid = document.getElementById('model-pricing-grid');
|
|
1758
1820
|
if (!details || !summary || !grid) return;
|
|
1759
1821
|
const metadata = modelCatalogState.modelMetadataByModel?.[modelId] || {};
|
|
1760
|
-
|
|
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'];
|
|
1761
1827
|
if (!pricing || typeof pricing !== 'object') {
|
|
1762
1828
|
details.open = false;
|
|
1763
1829
|
details.style.display = 'none';
|
|
@@ -1784,12 +1850,14 @@ function updateModelPricingDetails(modelId, tier) {
|
|
|
1784
1850
|
details.style.display = grid.childElementCount ? '' : 'none';
|
|
1785
1851
|
}
|
|
1786
1852
|
|
|
1787
|
-
// Relay modes each provider backend actually honors.
|
|
1788
|
-
//
|
|
1853
|
+
// Relay modes each provider backend actually honors. The local providers
|
|
1854
|
+
// support the full set — Copilot/OpenAI via prompt-context
|
|
1789
1855
|
// injection, Claude via permissionMode + system-prompt appends, Cursor via
|
|
1790
1856
|
// native plan mode plus message-text nudges for ask/autopilot — but the
|
|
1791
1857
|
// composer builds its options from this table so a provider that loses (or
|
|
1792
|
-
// 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).
|
|
1793
1861
|
const RELAY_MODE_LABELS = { agent: 'Agent', ask: 'Ask', plan: 'Plan', autopilot: 'Autopilot' };
|
|
1794
1862
|
const RELAY_MODES_BY_PROVIDER = {
|
|
1795
1863
|
github: ['agent', 'ask', 'plan', 'autopilot'],
|
|
@@ -1797,6 +1865,7 @@ const RELAY_MODES_BY_PROVIDER = {
|
|
|
1797
1865
|
claude: ['agent', 'ask', 'plan', 'autopilot'],
|
|
1798
1866
|
cursor: ['agent', 'ask', 'plan', 'autopilot'],
|
|
1799
1867
|
grok: ['agent', 'ask', 'plan', 'autopilot'],
|
|
1868
|
+
'claude-cloud': ['agent'],
|
|
1800
1869
|
};
|
|
1801
1870
|
|
|
1802
1871
|
function relayModesForProvider(providerType = '') {
|
|
@@ -1848,8 +1917,9 @@ async function persistCurrentConversationPreferences() {
|
|
|
1848
1917
|
// record an effort the user never picked when they only changed the mode.
|
|
1849
1918
|
const reasoningEffort = String(document.getElementById('reasoning-effort-select')?.value || '').trim().toLowerCase();
|
|
1850
1919
|
// These keys hold the last explicit choice, so they are not rolled back when
|
|
1851
|
-
// the write below fails: the user did make the choice either way.
|
|
1852
|
-
|
|
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);
|
|
1853
1923
|
if (model) localStorage.setItem(MODEL_STORAGE_KEY, model);
|
|
1854
1924
|
if (reasoningEffort) localStorage.setItem(REASONING_STORAGE_KEY, reasoningEffort);
|
|
1855
1925
|
|
|
@@ -2337,6 +2407,9 @@ function renderModelVariantCatalogBody() {
|
|
|
2337
2407
|
// shows Claude-SDK rows exclusively, the Cursor SDK tab Cursor rows, the
|
|
2338
2408
|
// Grok tab Grok rows, and Copilot never shows rows that a different runtime
|
|
2339
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;
|
|
2340
2413
|
if (activeTab === 'anthropic') {
|
|
2341
2414
|
return entryProvider === 'claude';
|
|
2342
2415
|
}
|
|
@@ -2360,6 +2433,7 @@ function renderModelVariantCatalogBody() {
|
|
|
2360
2433
|
&& provider !== 'claude'
|
|
2361
2434
|
&& provider !== 'cursor'
|
|
2362
2435
|
&& provider !== 'grok'
|
|
2436
|
+
&& provider !== CLAUDE_CLOUD_PROVIDER
|
|
2363
2437
|
));
|
|
2364
2438
|
}
|
|
2365
2439
|
return true;
|
|
@@ -2786,6 +2860,66 @@ async function saveSelectedModelsFromModal() {
|
|
|
2786
2860
|
// click pins a provider so the modal's Refresh re-renders onto the same tab
|
|
2787
2861
|
// instead of snapping back. Reset on every fresh open of the modal.
|
|
2788
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
|
+
}
|
|
2789
2923
|
|
|
2790
2924
|
/**
|
|
2791
2925
|
* Delegated because #summary-modal-body's innerHTML is replaced on every render
|
|
@@ -2803,7 +2937,7 @@ function initUsageTabControl() {
|
|
|
2803
2937
|
});
|
|
2804
2938
|
}
|
|
2805
2939
|
|
|
2806
|
-
/**
|
|
2940
|
+
/** DOM toggle first; a tab whose card is older than a minute is then read live. */
|
|
2807
2941
|
function selectUsageTab(root, provider) {
|
|
2808
2942
|
const target = String(provider || '').trim();
|
|
2809
2943
|
if (!target) return;
|
|
@@ -2819,6 +2953,78 @@ function selectUsageTab(root, provider) {
|
|
|
2819
2953
|
panel.hidden = panel.dataset.usagePanel !== target;
|
|
2820
2954
|
}
|
|
2821
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);
|
|
2822
3028
|
}
|
|
2823
3029
|
|
|
2824
3030
|
/**
|
|
@@ -2848,11 +3054,18 @@ function withClaudeAccount(report, account) {
|
|
|
2848
3054
|
};
|
|
2849
3055
|
}
|
|
2850
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
|
+
*/
|
|
2851
3062
|
async function loadUsageSummaryAndRender() {
|
|
3063
|
+
const activeProvider = usageTabProviderOverride
|
|
3064
|
+
|| normalizeUsageProvider(activeComposerProviderType());
|
|
2852
3065
|
// Usage is the point of the modal; the account line is a garnish, so the two
|
|
2853
3066
|
// are settled independently and a failing auth probe cannot blank the cards.
|
|
2854
3067
|
const [usageResult, authResult] = await Promise.allSettled([
|
|
2855
|
-
loadUsageSummary(),
|
|
3068
|
+
loadUsageSummary({ providers: activeProvider ? [activeProvider] : null }),
|
|
2856
3069
|
getClaudeAuthStatus(),
|
|
2857
3070
|
]);
|
|
2858
3071
|
if (usageResult.status === 'rejected') throw usageResult.reason;
|
|
@@ -2861,20 +3074,7 @@ async function loadUsageSummaryAndRender() {
|
|
|
2861
3074
|
const account = authResult.status === 'fulfilled'
|
|
2862
3075
|
? claudeAccountFromAuthStatus(authResult.value)
|
|
2863
3076
|
: null;
|
|
2864
|
-
|
|
2865
|
-
|| normalizeUsageProvider(activeComposerProviderType());
|
|
2866
|
-
const planHtml = renderPlanUsageHtml(withClaudeAccount(d, account), { activeProvider });
|
|
2867
|
-
if (planHtml) {
|
|
2868
|
-
initUsageTabControl();
|
|
2869
|
-
renderSummaryModalContent({
|
|
2870
|
-
title: 'Plan usage',
|
|
2871
|
-
subtitle: planUsageSubtitle(d),
|
|
2872
|
-
bodyHtml: planHtml,
|
|
2873
|
-
refresh: loadUsageSummaryAndRender,
|
|
2874
|
-
kind: 'usage',
|
|
2875
|
-
});
|
|
2876
|
-
return;
|
|
2877
|
-
}
|
|
3077
|
+
if (Array.isArray(d.providers) && renderUsageReport(mergeUsageReports(lastUsageReport, d), account)) return;
|
|
2878
3078
|
// Legacy relay (or a relay without the plan-usage service): fall back to the
|
|
2879
3079
|
// Copilot-only text summary rather than showing an empty modal.
|
|
2880
3080
|
const pct = d.premiumInteractions?.percentRemaining != null
|
|
@@ -2954,6 +3154,36 @@ function setThinkingControlValue({ enabled, display }) {
|
|
|
2954
3154
|
}
|
|
2955
3155
|
}
|
|
2956
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
|
+
|
|
2957
3187
|
function initThinkingControl() {
|
|
2958
3188
|
const body = document.getElementById('summary-modal-body');
|
|
2959
3189
|
if (!body || body.dataset.thinkingBound === '1') return;
|
|
@@ -3055,9 +3285,11 @@ async function loadContextSummaryAndRender(convId) {
|
|
|
3055
3285
|
const refreshLookupId = sessionId || trimmedConvId || null;
|
|
3056
3286
|
const providerLabel = payload.providerType === 'claude'
|
|
3057
3287
|
? 'Claude'
|
|
3058
|
-
: (payload.providerType ===
|
|
3059
|
-
? '
|
|
3060
|
-
: (payload.providerType === '
|
|
3288
|
+
: (payload.providerType === CLAUDE_CLOUD_PROVIDER
|
|
3289
|
+
? 'Claude Cloud'
|
|
3290
|
+
: (payload.providerType === 'cursor'
|
|
3291
|
+
? 'Cursor'
|
|
3292
|
+
: (payload.providerType === 'grok' ? 'Grok' : 'Copilot')));
|
|
3061
3293
|
const subtitle = sessionId
|
|
3062
3294
|
? `${providerLabel} session ${sessionId.slice(0, 8)}`
|
|
3063
3295
|
: (trimmedConvId ? `Conversation ${trimmedConvId.slice(0, 8)}` : 'No conversation selected');
|
|
@@ -3086,6 +3318,11 @@ async function loadContextSummaryAndRender(convId) {
|
|
|
3086
3318
|
thinkingDisplay: payload.thinkingDisplay ?? null,
|
|
3087
3319
|
})
|
|
3088
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
|
+
: '';
|
|
3089
3326
|
// The slider's writes need the conversation id even when the modal was
|
|
3090
3327
|
// opened through an sdk_session_id.
|
|
3091
3328
|
autoCompactControlConversationId = String(
|
|
@@ -3102,10 +3339,10 @@ async function loadContextSummaryAndRender(convId) {
|
|
|
3102
3339
|
// The structured breakdown is the answer; the runtime's own text dump stays
|
|
3103
3340
|
// available underneath for the details it carries that categories don't.
|
|
3104
3341
|
const bodyHtml = usageHtml
|
|
3105
|
-
? `${usageHtml}${autoCompactHtml}${thinkingHtml}${detailText
|
|
3342
|
+
? `${usageHtml}${autoCompactHtml}${thinkingHtml}${attributionHtml}${detailText
|
|
3106
3343
|
? `<details class="ctx-usage-raw"><summary>Raw details</summary><pre>${escHtml(detailText)}</pre></details>`
|
|
3107
3344
|
: ''}`
|
|
3108
|
-
: `${autoCompactHtml}${thinkingHtml}<pre>${escHtml(detailText || 'No context data available.')}</pre>`;
|
|
3345
|
+
: `${autoCompactHtml}${thinkingHtml}${attributionHtml}<pre>${escHtml(detailText || 'No context data available.')}</pre>`;
|
|
3109
3346
|
|
|
3110
3347
|
renderSummaryModalContent({
|
|
3111
3348
|
title: 'Context usage',
|
|
@@ -3125,15 +3362,27 @@ async function showUsage() {
|
|
|
3125
3362
|
btn.textContent = '⏳';
|
|
3126
3363
|
btn.disabled = true;
|
|
3127
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());
|
|
3128
3369
|
openSummaryModal({
|
|
3129
3370
|
title: 'Plan usage',
|
|
3130
3371
|
subtitle: 'Loading…',
|
|
3131
|
-
bodyHtml: '<div class="summary-loading">Fetching plan usage
|
|
3132
|
-
refresh:
|
|
3372
|
+
bodyHtml: '<div class="summary-loading">Fetching plan usage…</div>',
|
|
3373
|
+
refresh: refreshOpenUsageTab,
|
|
3133
3374
|
kind: 'usage',
|
|
3134
3375
|
});
|
|
3135
|
-
setSummaryModalLoading(true);
|
|
3136
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);
|
|
3137
3386
|
await loadUsageSummaryAndRender();
|
|
3138
3387
|
} catch (e) {
|
|
3139
3388
|
renderSummaryModalContent({
|
|
@@ -3747,6 +3996,11 @@ function syncChatTitleControls() {
|
|
|
3747
3996
|
killBtn.disabled = sharedMode || !convId || !sdkSessionId;
|
|
3748
3997
|
killBtn.hidden = sharedMode || !convId;
|
|
3749
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
|
+
}
|
|
3750
4004
|
if (editor) {
|
|
3751
4005
|
editor.hidden = !editing;
|
|
3752
4006
|
}
|
|
@@ -3762,6 +4016,11 @@ function syncChatTitleControls() {
|
|
|
3762
4016
|
if (!isStatusViewActive()) {
|
|
3763
4017
|
syncChatHeaderWorkspaceLabel();
|
|
3764
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();
|
|
3765
4024
|
syncChatTitleWatcherIndicator();
|
|
3766
4025
|
}
|
|
3767
4026
|
|
|
@@ -3919,6 +4178,21 @@ initTmuxInspectorView({
|
|
|
3919
4178
|
closeChatActionsMenu,
|
|
3920
4179
|
});
|
|
3921
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
|
+
|
|
3922
4196
|
function showAuthGate(error = '') {
|
|
3923
4197
|
document.getElementById('startup-loading')?.remove();
|
|
3924
4198
|
document.getElementById('auth-gate').style.display = 'flex';
|
|
@@ -4023,6 +4297,17 @@ async function initApp() {
|
|
|
4023
4297
|
});
|
|
4024
4298
|
});
|
|
4025
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
|
+
});
|
|
4026
4311
|
const chatMenuShareConversationBtn = document.getElementById('chat-menu-share-conversation');
|
|
4027
4312
|
bindMenuAction(chatMenuShareConversationBtn, (event) => {
|
|
4028
4313
|
event.preventDefault();
|
|
@@ -4082,6 +4367,14 @@ async function initApp() {
|
|
|
4082
4367
|
closeChatActionsMenu();
|
|
4083
4368
|
openGitChangesModal();
|
|
4084
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
|
+
});
|
|
4085
4378
|
const chatMenuSettingsBtn = document.getElementById('chat-menu-settings');
|
|
4086
4379
|
bindMenuAction(chatMenuSettingsBtn, (event) => {
|
|
4087
4380
|
event.preventDefault();
|
|
@@ -4172,6 +4465,7 @@ async function initApp() {
|
|
|
4172
4465
|
}
|
|
4173
4466
|
initAutoCompactWindowControl();
|
|
4174
4467
|
initThinkingControl();
|
|
4468
|
+
initAttributionControl();
|
|
4175
4469
|
initModeSelector();
|
|
4176
4470
|
initModelSelector();
|
|
4177
4471
|
initReasoningSelector();
|
|
@@ -4190,6 +4484,11 @@ async function initApp() {
|
|
|
4190
4484
|
await refreshOpenAISettingsState();
|
|
4191
4485
|
await refreshCursorSettingsState();
|
|
4192
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();
|
|
4193
4492
|
await refreshModelCatalog(true);
|
|
4194
4493
|
initFullscreenButton();
|
|
4195
4494
|
initInstallButton();
|
|
@@ -4311,6 +4610,9 @@ window.dismissAvailableUpdate = dismissAvailableUpdate;
|
|
|
4311
4610
|
window.dismissUpdateOutcome = dismissUpdateOutcome;
|
|
4312
4611
|
window.saveGrokSettings = saveGrokSettings;
|
|
4313
4612
|
window.toggleGrokProvider = toggleGrokProvider;
|
|
4613
|
+
window.saveClaudeCloudSettings = saveClaudeCloudSettings;
|
|
4614
|
+
window.toggleClaudeCloudProvider = toggleClaudeCloudProvider;
|
|
4615
|
+
window.openClaudeCloudLoginTab = openClaudeCloudLoginTab;
|
|
4314
4616
|
window.saveCursorSettings = saveCursorSettings;
|
|
4315
4617
|
window.removeCursorSettings = removeCursorSettings;
|
|
4316
4618
|
window.saveCursorAllowanceSettings = saveCursorAllowanceSettings;
|
|
@@ -4334,6 +4636,9 @@ window.addRemoteRelayFromForm = addRemoteRelayFromForm;
|
|
|
4334
4636
|
window.showRemoteRelayTokenField = showRemoteRelayTokenField;
|
|
4335
4637
|
window.saveRemoteRelayPublicUrl = saveRemoteRelayPublicUrl;
|
|
4336
4638
|
window.toggleRemoteRelayInbound = toggleRemoteRelayInbound;
|
|
4639
|
+
window.toggleAgentSessions = toggleAgentSessions;
|
|
4640
|
+
window.previewAgentSessionsMaxWait = previewAgentSessionsMaxWait;
|
|
4641
|
+
window.saveAgentSessionsMaxWait = saveAgentSessionsMaxWait;
|
|
4337
4642
|
window.doAuth = doAuth;
|
|
4338
4643
|
window.initApp = initApp;
|
|
4339
4644
|
window.connectSocket = connectSocket;
|
|
@@ -4370,7 +4675,12 @@ window.newConversation = newConversation;
|
|
|
4370
4675
|
window.confirmNewConversationModel = confirmNewConversationModel;
|
|
4371
4676
|
window.closeNewConversationModelModal = closeNewConversationModelModal;
|
|
4372
4677
|
window.deleteConv = deleteConv;
|
|
4678
|
+
window.archiveConv = archiveConv;
|
|
4679
|
+
window.unarchiveConv = unarchiveConv;
|
|
4680
|
+
window.toggleArchivedConversations = toggleArchivedConversations;
|
|
4681
|
+
window.refreshNewConversationCloudRepoList = refreshNewConversationCloudRepoList;
|
|
4373
4682
|
window.openConversation = openConversation;
|
|
4683
|
+
window.openOriginConversation = openOriginConversation;
|
|
4374
4684
|
window.toggleStatusView = toggleStatusView;
|
|
4375
4685
|
window.refreshConversations = refreshConversations;
|
|
4376
4686
|
window.renderConvList = renderConvList;
|
|
@@ -4428,6 +4738,9 @@ window.syncChatTitleControls = syncChatTitleControls;
|
|
|
4428
4738
|
window.closeChatActionsMenu = closeChatActionsMenu;
|
|
4429
4739
|
window.openSuspendHostConfirmation = openSuspendHostConfirmation;
|
|
4430
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;
|
|
4431
4744
|
window.confirmRestartWebRelay = confirmRestartWebRelay;
|
|
4432
4745
|
window.confirmSuspendHost = confirmSuspendHost;
|
|
4433
4746
|
window.cancelQueuedHostSuspend = cancelQueuedHostSuspend;
|