codex-overleaf-link 2.2.1 → 2.3.0

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.
Files changed (71) hide show
  1. package/README.md +25 -23
  2. package/extension/bootstrap/manifest.template.json +1 -1
  3. package/extension/runtime-manifest.json +5 -71
  4. package/extension/src/background.js +53 -69
  5. package/extension/src/backgroundUpdateCoordinator.js +128 -66
  6. package/extension/src/content/applyResultFormatters.js +2 -3
  7. package/extension/src/content/contentRuntime.js +650 -1449
  8. package/extension/src/content/contextTray.js +4 -6
  9. package/extension/src/content/diagnosticsController.js +1 -2
  10. package/extension/src/content/diffReviewPanel.js +14 -13
  11. package/extension/src/content/generated/content.bundle.js +41370 -0
  12. package/extension/src/content/generated/content.bundle.meta.json +102 -0
  13. package/extension/src/content/legacyGlobalRegistry.js +129 -0
  14. package/extension/src/content/markdownText.js +5 -7
  15. package/extension/src/content/modelPicker.js +1 -1
  16. package/extension/src/content/modelPickerSupport.js +10 -5
  17. package/extension/src/content/moduleRegistryKernel.js +125 -0
  18. package/extension/src/content/nativeCompatibilityController.js +313 -0
  19. package/extension/src/content/otWarmMirror.js +7 -5
  20. package/extension/src/content/pageBridgeClient.js +224 -0
  21. package/extension/src/content/panelMaintenance.js +8 -3
  22. package/extension/src/content/postNavigationSettlementPersistence.js +100 -0
  23. package/extension/src/content/projectProviderSelection.js +4 -12
  24. package/extension/src/content/projectSessionCleanup.js +3 -3
  25. package/extension/src/content/providerSettingsCoordinator.js +59 -26
  26. package/extension/src/content/providerSettingsDialog.js +30 -18
  27. package/extension/src/content/recentProjects.js +15 -23
  28. package/extension/src/content/runGuidanceController.js +1 -1
  29. package/extension/src/content/runQueueScheduler.js +129 -29
  30. package/extension/src/content/runSettlementPersistence.js +51 -0
  31. package/extension/src/content/runTimelineView.js +11 -8
  32. package/extension/src/content/scopedPersistenceCoordinator.js +100 -0
  33. package/extension/src/content/scopedPersistenceDetachedCommit.js +47 -0
  34. package/extension/src/content/scopedPersistencePanelState.js +111 -0
  35. package/extension/src/content/sessionManager.js +15 -12
  36. package/extension/src/content/sessionPersistence.js +67 -8
  37. package/extension/src/content/updateNotice.js +10 -4
  38. package/extension/src/content/writebackOrchestrator.js +91 -23
  39. package/extension/src/contentScript.js +5 -1
  40. package/extension/src/page/trackedChangesLifecycle.js +6 -1
  41. package/extension/src/pageBridge.js +47 -98
  42. package/extension/src/shared/agentTranscript.js +8 -5
  43. package/extension/src/shared/compatibility.js +1 -1
  44. package/extension/src/shared/governanceRules.js +1 -1
  45. package/extension/src/shared/managedUpdateProjection.js +337 -0
  46. package/extension/src/shared/nativeRequestIdentity.js +60 -0
  47. package/extension/src/shared/otText.js +0 -19
  48. package/extension/src/shared/pageRpcContract.js +136 -0
  49. package/extension/src/shared/runExecutionSnapshot.js +177 -0
  50. package/extension/src/shared/runExecutionSnapshotCodec.js +168 -0
  51. package/extension/src/shared/runInputQueue.js +81 -34
  52. package/extension/src/shared/scopedPersistenceBrowserAdapter.js +79 -0
  53. package/extension/src/shared/scopedPersistenceQueuePolicy.js +181 -0
  54. package/extension/src/shared/scopedPersistenceTransaction.js +238 -0
  55. package/extension/src/shared/sessionState.js +64 -48
  56. package/extension/src/shared/settlementFacts.js +351 -0
  57. package/extension/src/shared/storageDb.js +19 -5
  58. package/extension/src/shared/storageMigration.js +275 -61
  59. package/extension/src/shared/storageSessionClaims.js +60 -0
  60. package/extension/src/shared/updateConsent.js +4 -0
  61. package/extension/src/shared/updateRevocationIntent.js +109 -0
  62. package/extension/src/shared/writebackEvidenceProjection.js +150 -0
  63. package/extension/src/shared/writebackOperationEvidence.js +143 -0
  64. package/extension/src/shared/writebackSettlement.js +705 -0
  65. package/native-host/src/codexRuntimeIdentity.js +1 -1
  66. package/native-host/src/codexSessionRunner.js +7 -13
  67. package/native-host/src/index.js +3 -69
  68. package/native-host/src/managedInstall.js +50 -8
  69. package/native-host/src/nativeTransportEnvelope.js +148 -0
  70. package/native-host/src/taskRunnerRuntime.js +3 -3
  71. package/package.json +13 -5
@@ -0,0 +1,100 @@
1
+ (function initPostNavigationSettlementPersistence(root, factory) {
2
+ const api = factory();
3
+ if (typeof module === 'object' && module.exports) module.exports = api;
4
+ if (root) root.CodexOverleafPostNavigationSettlementPersistence = api;
5
+ })(typeof window !== 'undefined' ? window : globalThis, function postNavigationSettlementPersistenceFactory() {
6
+ 'use strict';
7
+
8
+ async function persist(options = {}) {
9
+ if (options.__insideScopedTransaction !== true) {
10
+ const accountScopeId = String(options.accountScopeId || '').trim();
11
+ const projectId = String(options.projectId || '').trim();
12
+ if (!accountScopeId) {
13
+ return { ok: false, reason: 'account_scope_unavailable' };
14
+ }
15
+ if (!projectId) {
16
+ return { ok: false, reason: 'project_scope_unavailable' };
17
+ }
18
+ if (!options.PersistenceCoordinator
19
+ || typeof options.PersistenceCoordinator.commitDetached !== 'function') {
20
+ return { ok: false, reason: 'scoped_persistence_unavailable' };
21
+ }
22
+ const committed = await options.PersistenceCoordinator.commitDetached(
23
+ projectId,
24
+ accountScopeId,
25
+ { rebase: true },
26
+ () => persist({
27
+ ...options,
28
+ __insideScopedTransaction: true
29
+ })
30
+ );
31
+ if (!committed || typeof committed !== 'object') {
32
+ return { ok: false, reason: 'scoped_persistence_invalid_result' };
33
+ }
34
+ if (committed.ok !== true) return committed;
35
+ if (!Object.prototype.hasOwnProperty.call(committed, 'value')) return committed;
36
+ return committed.value && typeof committed.value === 'object'
37
+ ? committed.value
38
+ : { ok: false, reason: 'scoped_persistence_action_result_unavailable' };
39
+ }
40
+ const {
41
+ StorageDb,
42
+ WritebackSettlement,
43
+ sessionId,
44
+ runId,
45
+ status,
46
+ statusText,
47
+ finishedAt,
48
+ settlementResult
49
+ } = options;
50
+ if (!StorageDb || !WritebackSettlement || !sessionId || !runId || !status) {
51
+ return { ok: false, reason: 'invalid_post_navigation_settlement' };
52
+ }
53
+ const record = await StorageDb.getRecord('sessions', sessionId);
54
+ if (!record) return { ok: false, reason: 'session_not_found' };
55
+ if (String(record.accountScopeId || '').trim() !== String(options.accountScopeId || '').trim()) {
56
+ return { ok: false, reason: 'account_scope_mismatch' };
57
+ }
58
+ if (String(record.projectId || '').trim() !== String(options.projectId || '').trim()) {
59
+ return { ok: false, reason: 'project_scope_mismatch' };
60
+ }
61
+ const runs = Array.isArray(record.runs) ? [...record.runs] : [];
62
+ const runIndex = runs.findIndex(run => run && run.id === runId);
63
+ if (runIndex < 0) return { ok: false, reason: 'run_not_found' };
64
+
65
+ let transitioned = settlementResult
66
+ ? WritebackSettlement.applySettlementTransition(runs[runIndex], settlementResult)
67
+ : { ...runs[runIndex] };
68
+ transitioned = WritebackSettlement.applySettlementTransition(
69
+ transitioned,
70
+ WritebackSettlement.transitionPostNavigationStatus({
71
+ status,
72
+ statusText,
73
+ finishedAt: finishedAt || transitioned.finishedAt || new Date().toISOString()
74
+ })
75
+ );
76
+ runs[runIndex] = transitioned;
77
+
78
+ const now = options.now || new Date().toISOString();
79
+ record.runs = runs;
80
+ record.lastActivityAt = now;
81
+ record.updatedAt = now;
82
+ await StorageDb.putRecord('sessions', record);
83
+ return { ok: true, run: transitioned };
84
+ }
85
+
86
+ async function persistRequired(options = {}) {
87
+ const result = await persist(options);
88
+ if (result?.ok === true) return result;
89
+ const reason = String(result?.reason || 'unknown_failure');
90
+ throw Object.assign(
91
+ new Error(`Post-navigation settlement persistence failed: ${reason}`),
92
+ {
93
+ code: 'post_navigation_settlement_persistence_failed',
94
+ reason
95
+ }
96
+ );
97
+ }
98
+
99
+ return Object.freeze({ persist, persistRequired });
100
+ });
@@ -30,24 +30,16 @@
30
30
  const nextSpeedTier = deps.readSelectedSpeed?.() || state.speedTier;
31
31
  const runSelection = deps.getRunSelection?.(nextProviderId);
32
32
  const nextProviderRevision = Number(runSelection?.providerRevision || 0);
33
+ const providerConfigurationChanged = nextProviderId === (state.providerId || 'builtin')
34
+ && nextProviderRevision !== Number(state.providerRevision || 0);
35
+ const resetProviderThread = providerChanged || providerConfigurationChanged;
33
36
  const sessions = (state.sessions || []).map(session => ({
34
37
  ...session,
35
38
  providerId: nextProviderId,
36
39
  model: nextModel,
37
40
  reasoningEffort: nextReasoningEffort,
38
41
  speedTier: nextSpeedTier,
39
- codexThreadId: providerChanged ? '' : session.codexThreadId,
40
- pendingInputs: (session.pendingInputs || []).map(item => ({
41
- ...item,
42
- payload: {
43
- ...(item.payload || {}),
44
- providerId: nextProviderId,
45
- providerRevision: nextProviderRevision,
46
- model: nextModel,
47
- reasoningEffort: nextReasoningEffort,
48
- speedTier: nextSpeedTier
49
- }
50
- }))
42
+ codexThreadId: resetProviderThread ? '' : session.codexThreadId
51
43
  }));
52
44
  deps.setState?.(deps.normalizeState({
53
45
  ...state,
@@ -23,8 +23,8 @@
23
23
  }
24
24
 
25
25
  async function clearProjectSessions(projectId, projectName) {
26
- const StorageDb = window.CodexOverleafStorageDb;
27
- const SessionState = window.CodexOverleafSessionState;
26
+ const StorageDb = deps.StorageDb;
27
+ const SessionState = deps.SessionState;
28
28
  if (!StorageDb || !SessionState) {
29
29
  return;
30
30
  }
@@ -49,7 +49,7 @@
49
49
  if (!approved) {
50
50
  return;
51
51
  }
52
- const Migration = window.CodexOverleafStorageMigration;
52
+ const Migration = deps.StorageMigration;
53
53
  if (Migration?.addSessionTombstones) {
54
54
  await Migration.addSessionTombstones(projectId, records.map(record => record.id));
55
55
  }
@@ -2,8 +2,13 @@
2
2
  'use strict';
3
3
 
4
4
  function create(options = {}) {
5
- const Profiles = window.CodexOverleafProviderProfiles;
5
+ const Profiles = options.ProviderProfiles;
6
+ const Dialog = options.ProviderSettingsDialog;
7
+ if (!Profiles || !Dialog?.create) {
8
+ throw new Error('Provider settings require explicit profile and dialog dependencies.');
9
+ }
6
10
  const instance = {
11
+ Profiles,
7
12
  tx: options.tx || ((english) => english),
8
13
  sendBackgroundNative: options.sendBackgroundNative,
9
14
  getSettingsPanelInstance: options.getSettingsPanelInstance || (() => null),
@@ -21,12 +26,24 @@
21
26
  channel: null,
22
27
  dialog: null
23
28
  };
24
- instance.onProviderChanged = options.onProviderChanged
29
+ const projectProviderChanged = options.onProviderChanged
25
30
  || ((catalog, change) => reconcileProviderSelection(instance, catalog, change));
26
- instance.dialog = window.CodexOverleafProviderSettingsDialog.create({
31
+ instance.onProviderChanged = async (catalog, change) => {
32
+ const previousProviderId = instance.getSelectedProviderId() || 'builtin';
33
+ const result = await projectProviderChanged(catalog, change);
34
+ const nextProviderId = instance.getSelectedProviderId() || 'builtin';
35
+ updateSettingsSummary(instance);
36
+ if (previousProviderId !== nextProviderId && instance.dialog?.isOpen?.()) {
37
+ instance.dialog.setCatalog(instance.catalog, nextProviderId);
38
+ }
39
+ return result;
40
+ };
41
+ instance.dialog = Dialog.create({
27
42
  document: options.document || document,
28
43
  tx: instance.tx,
44
+ ProviderProfiles: Profiles,
29
45
  callbacks: {
46
+ getCurrentProjectProviderId: () => instance.getSelectedProviderId() || 'builtin',
30
47
  onTest: context => testProvider(instance, context),
31
48
  onCancelTest: () => cancelProviderTest(instance),
32
49
  onSave: (context, action) => saveProvider(instance, context, action),
@@ -135,7 +152,7 @@
135
152
  await ensureLoadedWithRetry(instance);
136
153
  } catch (_error) {
137
154
  return instance.refreshModelOptions(
138
- window.CodexOverleafProviderProfiles.buildRunSelection(instance.catalog, instance.getSelectedProviderId())
155
+ instance.Profiles.buildRunSelection(instance.catalog, instance.getSelectedProviderId())
139
156
  );
140
157
  }
141
158
  return reconcileProviderSelection(instance, instance.catalog, { forceSessionRefresh: true });
@@ -143,12 +160,18 @@
143
160
 
144
161
  async function reconcileProviderSelection(instance, catalog, change = {}) {
145
162
  const previousProviderId = instance.getSelectedProviderId() || 'builtin';
146
- const requestedProviderId = typeof change.sessionProviderId === 'string' && change.sessionProviderId
163
+ let requestedProviderId = typeof change.sessionProviderId === 'string' && change.sessionProviderId
147
164
  ? change.sessionProviderId
148
165
  : previousProviderId;
166
+ const requestedProviderAvailable = requestedProviderId === 'builtin'
167
+ || Boolean(instance.Profiles.getProviderById(catalog, requestedProviderId));
168
+ const selectedProviderWasRemoved = !requestedProviderAvailable;
169
+ if (selectedProviderWasRemoved) {
170
+ requestedProviderId = 'builtin';
171
+ }
149
172
  const projectProviderChanged = requestedProviderId !== previousProviderId;
150
- if (projectProviderChanged && change.providerSwitchApproved !== true) {
151
- const requestedProvider = window.CodexOverleafProviderProfiles.getProviderById(catalog, requestedProviderId);
173
+ if (projectProviderChanged && !selectedProviderWasRemoved && change.providerSwitchApproved !== true) {
174
+ const requestedProvider = instance.Profiles.getProviderById(catalog, requestedProviderId);
152
175
  const approved = await instance.confirmProviderSwitch({
153
176
  providerId: requestedProviderId,
154
177
  providerName: requestedProvider?.name || requestedProviderId
@@ -158,13 +181,14 @@
158
181
  }
159
182
  }
160
183
  const changedProviderIds = Array.isArray(change.changedProviderIds) ? change.changedProviderIds : [];
161
- if (!change.forceSessionRefresh && !projectProviderChanged && !changedProviderIds.includes(requestedProviderId)) {
184
+ const providerConfigurationChanged = changedProviderIds.includes(requestedProviderId);
185
+ if (!change.forceSessionRefresh && !projectProviderChanged && !providerConfigurationChanged) {
162
186
  return;
163
187
  }
164
188
  const loadResult = change.modelsPreloaded === true
165
189
  ? { stale: false, selectedModel: change.preloadedModelId || '' }
166
190
  : await instance.refreshModelOptions(
167
- window.CodexOverleafProviderProfiles.buildRunSelection(catalog, requestedProviderId),
191
+ instance.Profiles.buildRunSelection(catalog, requestedProviderId),
168
192
  { persist: false }
169
193
  );
170
194
  if (loadResult?.stale || loadResult?.error) {
@@ -175,7 +199,7 @@
175
199
  error.details = loadResult?.error || {};
176
200
  throw error;
177
201
  }
178
- if (projectProviderChanged) {
202
+ if (projectProviderChanged || providerConfigurationChanged) {
179
203
  await instance.setSelectedProviderId(requestedProviderId, loadResult?.selectedModel || '');
180
204
  }
181
205
  await instance.persistInputs();
@@ -185,7 +209,7 @@
185
209
 
186
210
  async function prepareProviderActivation(instance, providerId) {
187
211
  const previousProviderId = instance.getSelectedProviderId() || 'builtin';
188
- const provider = window.CodexOverleafProviderProfiles.getProviderById(instance.catalog, providerId);
212
+ const provider = instance.Profiles.getProviderById(instance.catalog, providerId);
189
213
  if (providerId !== previousProviderId) {
190
214
  const approved = await instance.confirmProviderSwitch({
191
215
  providerId,
@@ -196,13 +220,13 @@
196
220
  }
197
221
  }
198
222
  const result = await instance.refreshModelOptions(
199
- window.CodexOverleafProviderProfiles.buildRunSelection(instance.catalog, providerId),
223
+ instance.Profiles.buildRunSelection(instance.catalog, providerId),
200
224
  { persist: false }
201
225
  );
202
226
  if (result?.stale || result?.error) {
203
227
  if (providerId !== previousProviderId) {
204
228
  await instance.refreshModelOptions(
205
- window.CodexOverleafProviderProfiles.buildRunSelection(instance.catalog, previousProviderId),
229
+ instance.Profiles.buildRunSelection(instance.catalog, previousProviderId),
206
230
  { persist: false }
207
231
  ).catch(() => {});
208
232
  }
@@ -293,7 +317,11 @@
293
317
  selectedId: result.savedProviderId
294
318
  });
295
319
  await notifyChanged(instance, applied.change, action.activate === true
296
- ? { sessionProviderId: result.savedProviderId }
320
+ ? {
321
+ sessionProviderId: result.savedProviderId,
322
+ providerSwitchApproved: true,
323
+ requireProjection: true
324
+ }
297
325
  : {});
298
326
  instance.dialog.setBusy('', '');
299
327
  const fellBackToBuiltin = previousCatalog.activeProviderId === result.savedProviderId
@@ -389,7 +417,7 @@
389
417
  } catch (error) {
390
418
  if (preparation?.previousProviderId && preparation.previousProviderId !== context.profileId) {
391
419
  await instance.refreshModelOptions(
392
- window.CodexOverleafProviderProfiles.buildRunSelection(instance.catalog, preparation.previousProviderId),
420
+ instance.Profiles.buildRunSelection(instance.catalog, preparation.previousProviderId),
393
421
  { persist: false }
394
422
  ).catch(() => {});
395
423
  }
@@ -423,7 +451,7 @@
423
451
  } catch (error) {
424
452
  if (preparation?.previousProviderId && preparation.previousProviderId !== 'builtin') {
425
453
  await instance.refreshModelOptions(
426
- window.CodexOverleafProviderProfiles.buildRunSelection(instance.catalog, preparation.previousProviderId),
454
+ instance.Profiles.buildRunSelection(instance.catalog, preparation.previousProviderId),
427
455
  { persist: false }
428
456
  ).catch(() => {});
429
457
  }
@@ -448,7 +476,7 @@
448
476
  }
449
477
 
450
478
  function applyCatalog(instance, result, options = {}) {
451
- const nextCatalog = window.CodexOverleafProviderProfiles.normalizeCatalog(result);
479
+ const nextCatalog = instance.Profiles.normalizeCatalog(result);
452
480
  if (instance.loaded && nextCatalog.storeRevision < instance.catalog.storeRevision) {
453
481
  return { applied: false, change: emptyCatalogChange() };
454
482
  }
@@ -463,19 +491,19 @@
463
491
  instance.dialog.setCatalog(instance.catalog, options.selectedId);
464
492
  }
465
493
  }
466
- return { applied: true, change: describeCatalogChange(previousCatalog, nextCatalog) };
494
+ return { applied: true, change: describeCatalogChange(instance, previousCatalog, nextCatalog) };
467
495
  }
468
496
 
469
497
  function updateSettingsSummary(instance, override = {}) {
470
498
  const selectedProviderId = instance.getSelectedProviderId() || 'builtin';
471
- const catalogProvider = window.CodexOverleafProviderProfiles.getProviderById(
499
+ const catalogProvider = instance.Profiles.getProviderById(
472
500
  instance.catalog,
473
501
  selectedProviderId
474
502
  );
475
503
  const selectedProvider = catalogProvider?.id === selectedProviderId
476
504
  ? catalogProvider
477
505
  : (selectedProviderId === 'builtin'
478
- ? window.CodexOverleafProviderProfiles.getActiveProvider(instance.catalog)
506
+ ? instance.Profiles.getActiveProvider(instance.catalog)
479
507
  : {
480
508
  id: selectedProviderId,
481
509
  name: selectedProviderId,
@@ -528,16 +556,16 @@
528
556
  }
529
557
  }
530
558
 
531
- function describeCatalogChange(previous, next) {
532
- const previousActive = window.CodexOverleafProviderProfiles.getActiveProvider(previous);
533
- const nextActive = window.CodexOverleafProviderProfiles.getActiveProvider(next);
559
+ function describeCatalogChange(instance, previous, next) {
560
+ const previousActive = instance.Profiles.getActiveProvider(previous);
561
+ const nextActive = instance.Profiles.getActiveProvider(next);
534
562
  const providerIds = new Set([
535
563
  ...(previous.providers || []).map(provider => provider.id),
536
564
  ...(next.providers || []).map(provider => provider.id)
537
565
  ]);
538
566
  const changedProviderIds = Array.from(providerIds).filter(providerId => {
539
- const previousProvider = window.CodexOverleafProviderProfiles.getProviderById(previous, providerId);
540
- const nextProvider = window.CodexOverleafProviderProfiles.getProviderById(next, providerId);
567
+ const previousProvider = instance.Profiles.getProviderById(previous, providerId);
568
+ const nextProvider = instance.Profiles.getProviderById(next, providerId);
541
569
  return modelCatalogSignature(previousProvider) !== modelCatalogSignature(nextProvider);
542
570
  });
543
571
  return {
@@ -548,8 +576,13 @@
548
576
  }
549
577
 
550
578
  function modelCatalogSignature(provider = {}) {
579
+ provider = provider || {};
551
580
  return JSON.stringify({
552
581
  id: provider.id || '',
582
+ revision: Number(provider.revision || 0),
583
+ baseUrl: provider.baseUrl || '',
584
+ wireApiPreference: provider.wireApiPreference || '',
585
+ resolvedWireApi: provider.resolvedWireApi || '',
553
586
  defaultModelId: provider.defaultModelId || '',
554
587
  reasoningAdapter: provider.reasoningAdapter || '',
555
588
  reasoningCapability: provider.reasoningCapability || '',
@@ -567,7 +600,7 @@
567
600
 
568
601
  function getProviderSnapshot(instance, providerId) {
569
602
  const requestedId = providerId || instance.getSelectedProviderId() || 'builtin';
570
- const provider = window.CodexOverleafProviderProfiles.getProviderById(instance.catalog, requestedId);
603
+ const provider = instance.Profiles.getProviderById(instance.catalog, requestedId);
571
604
  if (!provider) {
572
605
  return {
573
606
  providerId: requestedId,
@@ -5,11 +5,16 @@
5
5
  const CODEX_BLOSSOM_PATH = 'M13.798 23.976a5.7 5.7 0 0 1-2.26-.456 6.1 6.1 0 0 1-1.903-1.27 5.7 5.7 0 0 1-1.88.311 5.75 5.75 0 0 1-2.95-.79 6.2 6.2 0 0 1-2.188-2.159q-.81-1.366-.809-3.045 0-.695.19-1.51a6.4 6.4 0 0 1-1.475-2.038A5.95 5.95 0 0 1 0 10.573Q0 9.278.547 8.08q.547-1.2 1.523-2.062a5.5 5.5 0 0 1 2.307-1.223A5.7 5.7 0 0 1 5.472 2.35 6.1 6.1 0 0 1 7.565.623 5.8 5.8 0 0 1 10.206 0q1.19 0 2.26.456a6.1 6.1 0 0 1 1.903 1.27 5.7 5.7 0 0 1 1.88-.311q1.594 0 2.95.79a6 6 0 0 1 2.165 2.159q.832 1.366.832 3.045 0 .695-.19 1.51a6.3 6.3 0 0 1 1.475 2.062q.523 1.15.523 2.422a5.9 5.9 0 0 1-.547 2.493q-.547 1.2-1.546 2.086a5.4 5.4 0 0 1-2.284 1.199 5.56 5.56 0 0 1-1.118 2.445 5.9 5.9 0 0 1-2.07 1.727 5.8 5.8 0 0 1-2.64.623m-5.876-2.997q1.19 0 2.07-.504l4.472-2.589a.53.53 0 0 0 .238-.455v-2.062L8.945 18.7a.96.96 0 0 1-1.047 0l-4.496-2.613a.7.7 0 0 1-.024.168v.287q0 1.224.571 2.254a4.24 4.24 0 0 0 1.642 1.583q1.047.6 2.331.599m.238-3.908a.6.6 0 0 0 .262.072q.118 0 .238-.072l1.784-1.031-5.734-3.357q-.522-.312-.523-.935V6.545a4.3 4.3 0 0 0-1.903 1.63 4.25 4.25 0 0 0-.714 2.398q0 1.176.595 2.254.594 1.08 1.546 1.63zm5.638 5.323q1.26 0 2.284-.576a4.3 4.3 0 0 0 1.618-1.582q.595-1.008.595-2.254v-5.179a.47.47 0 0 0-.238-.431l-1.808-1.055v6.689q0 .624-.524.935l-4.496 2.613a4.3 4.3 0 0 0 2.57.84m.904-8.776v-3.26l-2.688-1.535-2.712 1.535v3.26l2.712 1.535zM7.756 5.97q0-.623.523-.935l4.496-2.613a4.3 4.3 0 0 0-2.569-.84q-1.26 0-2.284.576A4.3 4.3 0 0 0 6.304 3.74q-.57 1.008-.57 2.254v5.155q0 .287.237.455l1.785 1.055zM19.84 17.43a4.16 4.16 0 0 0 1.88-1.63 4.33 4.33 0 0 0 .713-2.397q0-1.176-.595-2.254-.594-1.08-1.546-1.63l-4.449-2.59q-.143-.096-.261-.072a.46.46 0 0 0-.238.072L13.56 7.936l5.758 3.38a.9.9 0 0 1 .38.384q.143.216.143.528zM15.059 5.25q.524-.335 1.047 0l4.52 2.662V7.48q0-1.15-.57-2.181A4.14 4.14 0 0 0 18.46 3.62q-1.023-.623-2.379-.623-1.19 0-2.07.503L9.54 6.09a.53.53 0 0 0-.238.455v2.062z';
6
6
 
7
7
  function create(options = {}) {
8
+ const Profiles = options.ProviderProfiles;
9
+ if (!Profiles) {
10
+ throw new Error('Provider settings dialog requires provider profiles.');
11
+ }
8
12
  const instance = {
13
+ Profiles,
9
14
  document: options.document || document,
10
15
  tx: options.tx || ((english) => english),
11
16
  callbacks: options.callbacks || {},
12
- catalog: window.CodexOverleafProviderProfiles.normalizeCatalog({}),
17
+ catalog: Profiles.normalizeCatalog({}),
13
18
  selectedId: 'builtin',
14
19
  draft: null,
15
20
  dirty: false,
@@ -108,7 +113,7 @@
108
113
  }
109
114
 
110
115
  function setCatalog(instance, catalog, selectedId) {
111
- instance.catalog = window.CodexOverleafProviderProfiles.normalizeCatalog(catalog || {});
116
+ instance.catalog = instance.Profiles.normalizeCatalog(catalog || {});
112
117
  const nextId = selectedId || (
113
118
  instance.catalog.providers.some(provider => provider.id === instance.selectedId)
114
119
  ? instance.selectedId
@@ -151,8 +156,8 @@
151
156
  '第三方实验性'
152
157
  );
153
158
  instance.root.querySelector('[data-provider-dialog-subtitle]').textContent = tx(
154
- 'Configure experimental third-party integrations used by future Codex runs. Compatibility varies by provider and gateway.',
155
- '配置后续 Codex 任务使用的实验性第三方集成;兼容性取决于具体模型服务与网关。'
159
+ "Configure experimental third-party integrations used by this project's future Codex runs. Compatibility varies by provider and gateway.",
160
+ '配置当前项目后续 Codex 任务使用的实验性第三方集成;兼容性取决于具体模型服务与网关。'
156
161
  );
157
162
  renderProviderList(instance);
158
163
  renderDetail(instance);
@@ -165,10 +170,10 @@
165
170
  const list = instance.root.querySelector('[data-provider-list]');
166
171
  const rows = instance.catalog.providers.map(provider => {
167
172
  const selected = provider.id === instance.selectedId;
168
- const active = provider.id === instance.catalog.activeProviderId;
173
+ const active = provider.id === getCurrentProjectProviderId(instance);
169
174
  const diagnosticCount = Object.values(provider.modelDiagnostics || {}).filter(item => item.status === 'tested').length;
170
175
  const status = active
171
- ? tx('Active', '使用中')
176
+ ? tx('Current project', '当前项目')
172
177
  : diagnosticCount
173
178
  ? tx(`Tested ${diagnosticCount}/${Math.max(1, provider.models.length)}`, `已测试 ${diagnosticCount}/${Math.max(1, provider.models.length)}`)
174
179
  : provider.kind === 'custom'
@@ -207,7 +212,7 @@
207
212
  .map(model => model.id)
208
213
  .filter(id => id !== draft.defaultModelId)
209
214
  .join('\n');
210
- const active = provider.id && provider.id === instance.catalog.activeProviderId;
215
+ const active = provider.id && provider.id === getCurrentProjectProviderId(instance);
211
216
  const acceptedHost = getEndpointHost(draft.baseUrl);
212
217
  const secretSavedAt = formatSecretSavedAt(instance, provider.secretUpdatedAt);
213
218
  const disclosureSatisfied = Boolean(
@@ -219,9 +224,9 @@
219
224
  <div class="codex-provider-detail-titleline">
220
225
  <div>
221
226
  <h3>${escapeHtml(draft.name || tx('Custom provider', '自定义模型服务'))}</h3>
222
- <p>${escapeHtml(active ? tx('Used by new Codex runs', '新 Codex 任务将使用此服务') : tx('Saved provider profile', '已保存的模型服务配置'))}</p>
227
+ <p>${escapeHtml(active ? tx('Used by new runs in this project', '当前项目的新任务将使用此服务') : tx('Saved provider profile', '已保存的模型服务配置'))}</p>
223
228
  </div>
224
- ${active ? `<span class="codex-provider-active-badge">${escapeHtml(tx('Active', '使用中'))}</span>` : ''}
229
+ ${active ? `<span class="codex-provider-active-badge">${escapeHtml(tx('Current project', '当前项目'))}</span>` : ''}
225
230
  </div>
226
231
  <div class="codex-provider-form-grid">
227
232
  <label class="codex-provider-field">
@@ -440,7 +445,7 @@
440
445
 
441
446
  function renderBuiltinDetail(instance, detail) {
442
447
  const tx = instance.tx;
443
- const active = instance.catalog.activeProviderId === 'builtin';
448
+ const active = getCurrentProjectProviderId(instance) === 'builtin';
444
449
  detail.innerHTML = `
445
450
  <div class="codex-provider-builtin">
446
451
  <span class="codex-provider-builtin-mark" aria-hidden="true">
@@ -452,12 +457,19 @@
452
457
  '使用本地 Codex CLI 管理的身份验证、模型目录和服务配置。'
453
458
  ))}</p>
454
459
  ${active
455
- ? `<span class="codex-provider-active-badge">${escapeHtml(tx('Active', '使用中'))}</span>`
456
- : `<button type="button" class="codex-provider-primary-button" data-provider-action="activate-builtin">${escapeHtml(tx('Use built-in Codex', '使用内置 Codex'))}</button>`}
460
+ ? `<span class="codex-provider-active-badge">${escapeHtml(tx('Current project', '当前项目'))}</span>`
461
+ : `<button type="button" class="codex-provider-primary-button" data-provider-action="activate-builtin">${escapeHtml(tx('Use for this project', '用于当前项目'))}</button>`}
457
462
  </div>
458
463
  `;
459
464
  }
460
465
 
466
+ function getCurrentProjectProviderId(instance) {
467
+ const providerId = instance.callbacks.getCurrentProjectProviderId?.();
468
+ return typeof providerId === 'string' && providerId.trim()
469
+ ? providerId.trim()
470
+ : 'builtin';
471
+ }
472
+
461
473
  function renderFooter(instance) {
462
474
  const actions = instance.root.querySelector('[data-provider-footer-actions]');
463
475
  const provider = getSelectedProvider(instance);
@@ -466,7 +478,7 @@
466
478
  actions.innerHTML = `<button type="button" class="codex-provider-secondary-button" data-provider-action="close">${escapeHtml(tx('Close', '关闭'))}</button>`;
467
479
  return;
468
480
  }
469
- const active = provider.id && provider.id === instance.catalog.activeProviderId;
481
+ const active = provider.id && provider.id === getCurrentProjectProviderId(instance);
470
482
  const isNew = !provider.id;
471
483
  const destinationChanged = active && getCurrentBaseUrl(instance) !== provider.endpointDisclosureBaseUrl;
472
484
  const actionState = getFooterActionState({
@@ -485,8 +497,8 @@
485
497
  <span class="codex-provider-footer-spacer"></span>
486
498
  <button type="button" class="codex-provider-secondary-button" data-provider-action="close">${escapeHtml(tx('Cancel', '取消'))}</button>
487
499
  ${actionState.showSave ? `<button type="button" class="codex-provider-secondary-button" data-provider-action="save"${saveDisabled}>${escapeHtml(tx('Save', '保存'))}</button>` : ''}
488
- ${actionState.showSaveAndUse ? `<button type="button" class="codex-provider-primary-button" data-provider-action="save-use"${saveDisabled}>${escapeHtml(tx('Save and use', '保存并使用'))}</button>` : ''}
489
- ${actionState.showUse ? `<button type="button" class="codex-provider-primary-button" data-provider-action="use"${useDisabled}>${escapeHtml(tx('Use', '使用'))}</button>` : ''}
500
+ ${actionState.showSaveAndUse ? `<button type="button" class="codex-provider-primary-button" data-provider-action="save-use"${saveDisabled}>${escapeHtml(tx('Save and use for this project', '保存并用于当前项目'))}</button>` : ''}
501
+ ${actionState.showUse ? `<button type="button" class="codex-provider-primary-button" data-provider-action="use"${useDisabled}>${escapeHtml(tx('Use for this project', '用于当前项目'))}</button>` : ''}
490
502
  `;
491
503
  }
492
504
 
@@ -677,7 +689,7 @@
677
689
  }
678
690
  instance.selectedId = id;
679
691
  instance.draft = id === '__new__'
680
- ? window.CodexOverleafProviderProfiles.buildEmptyDraft()
692
+ ? instance.Profiles.buildEmptyDraft()
681
693
  : null;
682
694
  instance.dirty = false;
683
695
  delete instance.root.dataset.dirty;
@@ -689,7 +701,7 @@
689
701
  }
690
702
 
691
703
  function readContext(instance) {
692
- const provider = getSelectedProvider(instance) || window.CodexOverleafProviderProfiles.buildEmptyDraft();
704
+ const provider = getSelectedProvider(instance) || instance.Profiles.buildEmptyDraft();
693
705
  const get = name => instance.root.querySelector(`[data-provider-field="${name}"]`);
694
706
  const defaultModelId = String(get('defaultModelId')?.value || '').trim();
695
707
  const modelIds = [defaultModelId, ...String(get('additionalModels')?.value || '').split(/\r?\n/)]
@@ -759,7 +771,7 @@
759
771
 
760
772
  function getSelectedProvider(instance) {
761
773
  if (instance.selectedId === '__new__') {
762
- return instance.draft || window.CodexOverleafProviderProfiles.buildEmptyDraft();
774
+ return instance.draft || instance.Profiles.buildEmptyDraft();
763
775
  }
764
776
  return instance.catalog.providers.find(provider => provider.id === instance.selectedId)
765
777
  || instance.catalog.providers.find(provider => provider.id === 'builtin');