codex-overleaf-link 2.2.1 → 2.3.1

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 (79) 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 +67 -63
  5. package/extension/src/backgroundUpdateCoordinator.js +128 -66
  6. package/extension/src/content/applyResultFormatters.js +2 -3
  7. package/extension/src/content/composerPanel.js +0 -2
  8. package/extension/src/content/contentRuntime.js +757 -1558
  9. package/extension/src/content/contextTray.js +4 -6
  10. package/extension/src/content/diagnosticsController.js +1 -2
  11. package/extension/src/content/diffReviewPanel.js +14 -13
  12. package/extension/src/content/generated/content.bundle.js +41605 -0
  13. package/extension/src/content/generated/content.bundle.meta.json +103 -0
  14. package/extension/src/content/legacyGlobalRegistry.js +129 -0
  15. package/extension/src/content/markdownText.js +17 -17
  16. package/extension/src/content/mathText.js +155 -9
  17. package/extension/src/content/modelPicker.js +1 -1
  18. package/extension/src/content/modelPickerSupport.js +10 -5
  19. package/extension/src/content/moduleRegistryKernel.js +125 -0
  20. package/extension/src/content/nativeCompatibilityController.js +313 -0
  21. package/extension/src/content/otWarmMirror.js +8 -6
  22. package/extension/src/content/pageBridgeClient.js +224 -0
  23. package/extension/src/content/panelMaintenance.js +8 -3
  24. package/extension/src/content/postNavigationSettlementPersistence.js +100 -0
  25. package/extension/src/content/projectProviderSelection.js +4 -12
  26. package/extension/src/content/projectSessionCleanup.js +3 -3
  27. package/extension/src/content/providerSettingsCoordinator.js +59 -26
  28. package/extension/src/content/providerSettingsDialog.js +30 -18
  29. package/extension/src/content/recentProjects.js +15 -23
  30. package/extension/src/content/runGuidanceController.js +1 -1
  31. package/extension/src/content/runQueueScheduler.js +129 -29
  32. package/extension/src/content/runSettlementPersistence.js +51 -0
  33. package/extension/src/content/runTimelineView.js +17 -8
  34. package/extension/src/content/scopedPersistenceCoordinator.js +100 -0
  35. package/extension/src/content/scopedPersistenceDetachedCommit.js +47 -0
  36. package/extension/src/content/scopedPersistencePanelState.js +111 -0
  37. package/extension/src/content/sessionManager.js +15 -12
  38. package/extension/src/content/sessionPersistence.js +67 -8
  39. package/extension/src/content/updateNotice.js +10 -4
  40. package/extension/src/content/writebackOrchestrator.js +89 -68
  41. package/extension/src/contentScript.js +5 -1
  42. package/extension/src/page/trackedChangesLifecycle.js +16 -5
  43. package/extension/src/page/writebackRouter.js +6 -2
  44. package/extension/src/pageBridge.js +47 -98
  45. package/extension/src/shared/agentTranscript.js +18 -8
  46. package/extension/src/shared/compatibility.js +1 -1
  47. package/extension/src/shared/governanceRules.js +1 -1
  48. package/extension/src/shared/i18n.js +2 -4
  49. package/extension/src/shared/managedUpdateProjection.js +337 -0
  50. package/extension/src/shared/nativeRequestIdentity.js +60 -0
  51. package/extension/src/shared/otText.js +0 -19
  52. package/extension/src/shared/pageRpcContract.js +136 -0
  53. package/extension/src/shared/runExecutionSnapshot.js +187 -0
  54. package/extension/src/shared/runExecutionSnapshotCodec.js +171 -0
  55. package/extension/src/shared/runInputQueue.js +81 -34
  56. package/extension/src/shared/scopedPersistenceBrowserAdapter.js +79 -0
  57. package/extension/src/shared/scopedPersistenceQueuePolicy.js +200 -0
  58. package/extension/src/shared/scopedPersistenceReceipt.js +60 -0
  59. package/extension/src/shared/scopedPersistenceTransaction.js +241 -0
  60. package/extension/src/shared/sessionState.js +110 -59
  61. package/extension/src/shared/settlementFacts.js +351 -0
  62. package/extension/src/shared/storageDb.js +25 -10
  63. package/extension/src/shared/storageMigration.js +275 -61
  64. package/extension/src/shared/storageSessionClaims.js +60 -0
  65. package/extension/src/shared/updateConsent.js +4 -0
  66. package/extension/src/shared/updateRevocationIntent.js +109 -0
  67. package/extension/src/shared/writebackEvidenceProjection.js +150 -0
  68. package/extension/src/shared/writebackOperationEvidence.js +153 -0
  69. package/extension/src/shared/writebackSettlement.js +727 -0
  70. package/extension/styles/panel.css +18 -8
  71. package/native-host/src/codexPromptAssembly.js +1 -1
  72. package/native-host/src/codexRuntimeIdentity.js +1 -1
  73. package/native-host/src/codexSessionRunner.js +7 -13
  74. package/native-host/src/index.js +3 -69
  75. package/native-host/src/managedInstall.js +50 -8
  76. package/native-host/src/nativeTransportEnvelope.js +148 -0
  77. package/native-host/src/taskRunnerRuntime.js +14 -84
  78. package/native-host/src/updateManager.js +36 -1
  79. package/package.json +13 -5
@@ -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');
@@ -17,11 +17,13 @@
17
17
  sendBackgroundNative,
18
18
  PANEL_STATE_BASE_KEY,
19
19
  PROJECT_EDITOR_RESERVED_IDS,
20
- STATUS_BADGE_CLASS
20
+ STATUS_BADGE_CLASS,
21
+ ProjectSessionCleanup, SessionPersistence, SessionState, StorageDb, StorageKeys, StorageMigration
21
22
  } = deps;
22
- const projectSessionCleanup = window.CodexOverleafProjectSessionCleanup.create({
23
+ const projectSessionCleanup = ProjectSessionCleanup.create({
23
24
  tr, getAccountScopeId: getCachedAccountScopeId, showPluginConfirm, showPluginToast,
24
- sendBackgroundNative, mutateProjectPanelState, renderRecentProjectsVariant
25
+ sendBackgroundNative, mutateProjectPanelState, renderRecentProjectsVariant,
26
+ SessionState, StorageDb, StorageMigration
25
27
  });
26
28
 
27
29
  // chrome.storage.local cache key (spec §5.6.3). The cache is keyed by
@@ -496,7 +498,6 @@
496
498
  }
497
499
 
498
500
  function sessionRecordDisplayTitle(record) {
499
- var SessionState = window.CodexOverleafSessionState;
500
501
  if (record && typeof record.title === 'string' && record.title.trim()) {
501
502
  return record.title.trim();
502
503
  }
@@ -505,12 +506,11 @@
505
506
  }
506
507
 
507
508
  async function loadProjectSessionRecords(projectId) {
508
- var StorageDb = window.CodexOverleafStorageDb;
509
509
  if (!StorageDb) {
510
510
  return [];
511
511
  }
512
512
  var records = await StorageDb.getAllByIndex('sessions', 'projectId', projectId);
513
- var Persistence = window.CodexOverleafSessionPersistence;
513
+ var Persistence = SessionPersistence;
514
514
  var deletedIds = await Persistence.getDeletedSessionIds(projectId);
515
515
  var scope = getCachedAccountScopeId();
516
516
  return (records || [])
@@ -521,7 +521,6 @@
521
521
  }
522
522
 
523
523
  async function renderProjectSessions(sessionsEl, projectId) {
524
- var StorageDb = window.CodexOverleafStorageDb;
525
524
  sessionsEl.innerHTML = '';
526
525
  var sessionsLoading = document.createElement('div');
527
526
  sessionsLoading.className = 'recent-projects-loading';
@@ -544,7 +543,6 @@
544
543
  }
545
544
 
546
545
  function renderProjectSessionRow(sessionsEl, projectId, record) {
547
- var StorageDb = window.CodexOverleafStorageDb;
548
546
  var running = Boolean(StorageDb)
549
547
  && settleDashboardRunStatus(StorageDb.derivePrimaryStatusBadge(record), record.updatedAt || record.lastActivityAt) === 'running';
550
548
  var row = document.createElement('div');
@@ -614,17 +612,21 @@
614
612
  }
615
613
 
616
614
  async function activateSessionAndOpenProject(projectId, record) {
617
- var Migration = window.CodexOverleafStorageMigration;
615
+ var Migration = StorageMigration;
618
616
  try {
619
617
  if (Migration && Migration.loadPrefs && Migration.savePrefs) {
620
- var prefs = await Migration.loadPrefs();
618
+ var accountScopeId = getCachedAccountScopeId();
619
+ if (!accountScopeId) {
620
+ throw new Error('Account scope is unavailable.');
621
+ }
622
+ var prefs = await Migration.loadPrefs(accountScopeId, projectId);
621
623
  prefs = prefs && typeof prefs === 'object' ? prefs : {};
622
624
  var map = prefs.activeSessionByProject && typeof prefs.activeSessionByProject === 'object'
623
625
  ? prefs.activeSessionByProject
624
626
  : {};
625
627
  map[projectId] = record.id;
626
628
  prefs.activeSessionByProject = map;
627
- await Migration.savePrefs(prefs);
629
+ await Migration.savePrefs(prefs, accountScopeId, projectId);
628
630
  }
629
631
  } catch (_error) { /* fall through: opening the project still works */ }
630
632
  openProjectFromRow(projectId);
@@ -636,7 +638,6 @@
636
638
  // panel-state blob is deliberately left alone — an empty projectId would
637
639
  // map getProjectStorageKey onto the global legacy key.
638
640
  async function cleanupDeadProjectEntry(projectId) {
639
- var StorageDb = window.CodexOverleafStorageDb;
640
641
  if (!StorageDb) {
641
642
  return;
642
643
  }
@@ -684,7 +685,6 @@
684
685
  }
685
686
 
686
687
  function projectPanelStateKey(projectId) {
687
- var StorageKeys = window.CodexOverleafStorageKeys;
688
688
  return StorageKeys.getProjectStorageKey(PANEL_STATE_BASE_KEY, 'https://www.overleaf.com/project/' + projectId);
689
689
  }
690
690
 
@@ -694,7 +694,6 @@
694
694
  // NOTE: if the project is open in another tab, that tab's in-memory state
695
695
  // wins on its next save; the IndexedDB record mutation below still holds.
696
696
  async function mutateProjectPanelState(projectId, mutate) {
697
- var SessionState = window.CodexOverleafSessionState;
698
697
  var key = projectPanelStateKey(projectId);
699
698
  var stored = await chrome.storage.local.get(key);
700
699
  var blob = stored && stored[key];
@@ -709,8 +708,6 @@
709
708
  }
710
709
 
711
710
  async function deleteDashboardSession(sessionsEl, projectId, record) {
712
- var StorageDb = window.CodexOverleafStorageDb;
713
- var SessionState = window.CodexOverleafSessionState;
714
711
  // v1.8.1 P1b: ANY record whose stored badge is 'running' gets the
715
712
  // stronger confirm — even one the 30-minute heuristic settled to
716
713
  // interrupted. Activity timestamps are only bumped once a minute during
@@ -731,7 +728,7 @@
731
728
  if (!approved) {
732
729
  return;
733
730
  }
734
- await window.CodexOverleafSessionPersistence.addTombstones(projectId, [record.id]);
731
+ await SessionPersistence.addTombstones(projectId, [record.id]);
735
732
  try {
736
733
  await mutateProjectPanelState(projectId, function (state) {
737
734
  return SessionState.deleteSession(state, record.id);
@@ -773,8 +770,6 @@
773
770
  }
774
771
 
775
772
  function beginDashboardSessionRename(sessionsEl, projectId, record, row, titleEl) {
776
- var SessionState = window.CodexOverleafSessionState;
777
- var StorageDb = window.CodexOverleafStorageDb;
778
773
  if (row.querySelector('input')) {
779
774
  return;
780
775
  }
@@ -816,8 +811,6 @@
816
811
  }
817
812
 
818
813
  async function commitDashboardSessionRename(sessionsEl, projectId, record, rawTitle) {
819
- var SessionState = window.CodexOverleafSessionState;
820
- var StorageDb = window.CodexOverleafStorageDb;
821
814
  if (!SessionState) {
822
815
  return;
823
816
  }
@@ -963,9 +956,8 @@
963
956
  // there really is more than one page of projects.
964
957
  var pageLimit = 10;
965
958
  try {
966
- var StorageDb = window.CodexOverleafStorageDb;
967
959
  if (StorageDb) {
968
- var deletedSessionIdsByProject = await window.CodexOverleafSessionPersistence.loadTombstones();
960
+ var deletedSessionIdsByProject = await SessionPersistence.loadTombstones();
969
961
  rows = await StorageDb.listRecentProjectsAcrossAccount({
970
962
  accountScopeId: accountScopeId,
971
963
  limit: showAll ? 500 : pageLimit + 1,
@@ -47,7 +47,7 @@
47
47
  }
48
48
 
49
49
  function appendToView(event, view) {
50
- const text = window.CodexOverleafRunGuidanceView?.getGuidanceText(event);
50
+ const text = deps.RunGuidanceView?.getGuidanceText(event);
51
51
  if (!text) return false;
52
52
  const guidance = view.guidance || view.root?.querySelector('[data-run-guidance]');
53
53
  const target = view.terminalStatus ? guidance : view.events;
@@ -2,7 +2,7 @@
2
2
  if (typeof module === 'object' && module.exports) {
3
3
  module.exports = factory(require('../shared/runInputQueue'));
4
4
  } else {
5
- root.CodexOverleafRunQueueScheduler = factory(root.CodexOverleafRunInputQueue);
5
+ root.CodexOverleafModuleRegistry.define('RunQueueScheduler', ['RunInputQueue'], factory);
6
6
  }
7
7
  })(typeof window !== 'undefined' ? window : globalThis, function runQueueSchedulerFactory(Queue) {
8
8
  'use strict';
@@ -19,18 +19,49 @@
19
19
  }
20
20
 
21
21
  async function afterSettlement({ sessionId, status, queueItemId = '', continueQueue = false } = {}) {
22
- let queue = options.getQueue?.(sessionId) || [];
23
- if (status === 'completed') {
24
- queue = dequeue(queue, queueItemId, { sessionId, reason: 'completed' });
25
- } else if (continueQueue) {
26
- queue = dequeue(queue, queueItemId, { sessionId, reason: 'continue_queue' });
27
- } else if (queue.length) {
28
- queue = Queue.pauseAll(queue, status || 'run_did_not_complete');
22
+ const originalQueue = options.getQueue?.(sessionId) || [];
23
+ const shouldContinue = status === 'completed' || continueQueue;
24
+ const shouldRemove = Boolean(queueItemId && shouldContinue);
25
+ const claimedItem = shouldRemove
26
+ ? originalQueue.find(item => item?.id === queueItemId)
27
+ : null;
28
+ if (shouldRemove) {
29
+ await options.persist?.({
30
+ queueMutation: {
31
+ type: 'remove',
32
+ sessionId,
33
+ itemId: queueItemId,
34
+ claimToken: claimedItem?.claimToken
35
+ }
36
+ });
37
+ const latestQueue = options.getQueue?.(sessionId) || originalQueue;
38
+ options.setQueue?.(
39
+ sessionId,
40
+ dequeue(latestQueue, queueItemId, {
41
+ sessionId,
42
+ reason: status === 'completed' ? 'completed' : 'continue_queue'
43
+ })
44
+ );
45
+ } else if (!shouldContinue) {
46
+ const paused = originalQueue.length
47
+ ? Queue.pauseAll(originalQueue, status || 'run_did_not_complete')
48
+ : originalQueue;
49
+ options.setQueue?.(sessionId, paused);
50
+ try {
51
+ await options.persist?.({
52
+ queueMutation: { type: 'transition', sessionId }
53
+ });
54
+ } catch (error) {
55
+ const latestQueue = options.getQueue?.(sessionId) || paused;
56
+ options.setQueue?.(
57
+ sessionId,
58
+ Queue.pauseAll(latestQueue, status || 'run_did_not_complete')
59
+ );
60
+ throw error;
61
+ }
29
62
  }
30
- options.setQueue?.(sessionId, queue);
31
- await options.persist?.();
32
63
  options.onChange?.(sessionId);
33
- if (status === 'completed' || continueQueue) {
64
+ if (shouldContinue) {
34
65
  scheduleOne(sessionId);
35
66
  }
36
67
  }
@@ -39,15 +70,33 @@
39
70
  if (scheduling || options.isRunning?.() || options.canStart?.(sessionId) === false) {
40
71
  return false;
41
72
  }
42
- const claimed = Queue.claimNext(options.getQueue?.(sessionId) || [], {
43
- randomUUID: options.randomUUID
44
- });
45
- if (!claimed.ok) {
73
+ const initialQueue = options.getQueue?.(sessionId) || [];
74
+ if (!initialQueue.some(item => item?.status === 'queued')) {
46
75
  return false;
47
76
  }
48
77
  scheduling = true;
49
- options.setQueue?.(sessionId, claimed.queue);
50
- Promise.resolve(options.persist?.())
78
+ let claimed = null;
79
+ Promise.resolve(options.prepareClaim?.(sessionId))
80
+ .then(() => {
81
+ claimed = Queue.claimNext(options.getQueue?.(sessionId) || initialQueue, {
82
+ randomUUID: options.randomUUID,
83
+ resolveExecutionSnapshot: options.resolveExecutionSnapshot
84
+ });
85
+ if (!claimed.ok) {
86
+ const error = new Error(claimed.error?.message || claimed.error?.code || 'queue_claim_failed');
87
+ error.code = claimed.error?.code || 'queue_claim_failed';
88
+ throw error;
89
+ }
90
+ options.setQueue?.(sessionId, claimed.queue);
91
+ return options.persist?.({
92
+ queueMutation: {
93
+ type: 'claim',
94
+ sessionId,
95
+ itemId: claimed.item.id,
96
+ claimToken: claimed.item.claimToken
97
+ }
98
+ });
99
+ })
51
100
  .then(() => {
52
101
  options.onChange?.(sessionId);
53
102
  scheduling = false;
@@ -55,27 +104,64 @@
55
104
  })
56
105
  .catch(error => {
57
106
  scheduling = false;
58
- const paused = Queue.pauseAll(options.getQueue?.(sessionId) || claimed.queue, error?.code || 'start_failed');
107
+ const paused = Queue.pauseAll(
108
+ options.getQueue?.(sessionId) || claimed?.queue || initialQueue,
109
+ error?.code || 'start_failed'
110
+ );
59
111
  options.setQueue?.(sessionId, paused);
60
112
  options.onChange?.(sessionId);
61
- return options.persist?.();
113
+ return Promise.resolve(options.persist?.({
114
+ queueMutation: claimed?.item?.id
115
+ ? {
116
+ type: 'release',
117
+ sessionId,
118
+ itemId: claimed.item.id,
119
+ claimToken: claimed.item.claimToken
120
+ }
121
+ : { type: 'transition', sessionId }
122
+ })).catch(() => undefined);
62
123
  });
63
124
  return true;
64
125
  }
65
126
 
66
127
  async function markExecuting(sessionId, itemId, _runId) {
67
128
  const queue = options.getQueue?.(sessionId) || [];
129
+ const item = queue.find(entry => entry?.id === itemId);
130
+ try {
131
+ await options.persist?.({
132
+ queueMutation: { type: 'remove', sessionId, itemId, claimToken: item?.claimToken }
133
+ });
134
+ } catch (error) {
135
+ const latestQueue = options.getQueue?.(sessionId) || queue;
136
+ options.setQueue?.(
137
+ sessionId,
138
+ Queue.pauseAll(latestQueue, error?.code || 'queue_promotion_persistence_failed')
139
+ );
140
+ options.onChange?.(sessionId);
141
+ try {
142
+ await options.persist?.({
143
+ queueMutation: { type: 'restore', sessionId, itemId, claimToken: item?.claimToken }
144
+ });
145
+ } catch (recoveryError) {
146
+ if (error && typeof error === 'object') {
147
+ error.persistenceRecoveryError = recoveryError?.message || String(recoveryError);
148
+ }
149
+ }
150
+ throw error;
151
+ }
152
+ const latestQueue = options.getQueue?.(sessionId) || queue;
68
153
  options.setQueue?.(
69
154
  sessionId,
70
- dequeue(queue, itemId, { sessionId, runId: _runId, reason: 'executing' })
155
+ dequeue(latestQueue, itemId, { sessionId, runId: _runId, reason: 'executing' })
71
156
  );
72
- await options.persist?.();
73
157
  options.onChange?.(sessionId);
74
158
  }
75
159
 
76
160
  async function resume(sessionId) {
77
161
  options.setQueue?.(sessionId, Queue.resumeAll(options.getQueue?.(sessionId) || []));
78
- await options.persist?.();
162
+ await options.persist?.({
163
+ queueMutation: { type: 'transition', sessionId }
164
+ });
79
165
  options.onChange?.(sessionId);
80
166
  scheduleOne(sessionId);
81
167
  }
@@ -88,7 +174,7 @@
88
174
  const scheduler = create({
89
175
  getQueue: sessionId => options.getSession?.(sessionId)?.pendingInputs || [],
90
176
  setQueue: (sessionId, pendingInputs) => options.setQueue?.(sessionId, pendingInputs),
91
- persist: () => options.save?.(),
177
+ persist: persistenceOptions => options.save?.(persistenceOptions),
92
178
  isRunning: () => Boolean(options.getCurrentRun?.()),
93
179
  canStart: sessionId => options.canStart?.(sessionId) !== false,
94
180
  start: (item, sessionId) => startQueuedInput(item, sessionId),
@@ -97,6 +183,8 @@
97
183
  sessionId: context.sessionId,
98
184
  recordId: item.sourceRunId || ''
99
185
  }),
186
+ prepareClaim: options.prepareClaim,
187
+ resolveExecutionSnapshot: options.resolveExecutionSnapshot,
100
188
  randomUUID: options.randomUUID
101
189
  });
102
190
 
@@ -128,7 +216,9 @@
128
216
  // active turn, or until the scheduler promotes it to the next run.
129
217
  options.clearTask?.();
130
218
  render();
131
- options.saveSoon?.();
219
+ options.saveSoon?.({
220
+ queueMutation: { type: 'enqueue', sessionId: session.id, itemId: result.item.id }
221
+ });
132
222
  }
133
223
 
134
224
  async function guide(itemId) {
@@ -142,7 +232,9 @@
142
232
  }
143
233
  options.setQueue?.(session.id, Queue.markSteering(session.pendingInputs, itemId));
144
234
  render();
145
- await options.save?.();
235
+ await options.save?.({
236
+ queueMutation: { type: 'transition', sessionId: session.id, itemId }
237
+ });
146
238
  try {
147
239
  const response = await options.sendSteer?.({
148
240
  requestId: run.nativeRequestId,
@@ -174,7 +266,11 @@
174
266
  options.toast?.(response?.error?.message || options.tr?.('queuedInputGuideFailed'), 'warning');
175
267
  }
176
268
  render();
177
- await options.save?.();
269
+ await options.save?.({
270
+ queueMutation: response?.ok
271
+ ? { type: 'remove', sessionId: session.id, itemId }
272
+ : { type: 'transition', sessionId: session.id, itemId }
273
+ });
178
274
  } catch (error) {
179
275
  const latest = options.getSession?.(session.id);
180
276
  const paused = (latest?.pendingInputs || []).map(entry => entry.id === itemId
@@ -183,7 +279,9 @@
183
279
  options.setQueue?.(session.id, Queue.normalizeQueue(paused));
184
280
  options.toast?.(error?.message || options.tr?.('queuedInputGuideFailed'), 'warning');
185
281
  render();
186
- await options.save?.();
282
+ await options.save?.({
283
+ queueMutation: { type: 'transition', sessionId: session.id, itemId }
284
+ });
187
285
  }
188
286
  }
189
287
 
@@ -201,7 +299,9 @@
201
299
  }
202
300
  options.setQueue?.(session.id, Queue.remove(session.pendingInputs || [], itemId));
203
301
  render();
204
- options.saveSoon?.();
302
+ options.saveSoon?.({
303
+ queueMutation: { type: 'remove', sessionId: session.id, itemId }
304
+ });
205
305
  }
206
306
 
207
307
  function resume() {
@@ -242,7 +342,7 @@
242
342
  error.code = 'queue_session_not_active';
243
343
  throw error;
244
344
  }
245
- options.applyQueuedInput?.(item);
345
+ await options.applyQueuedInput?.(item);
246
346
  await options.run?.(item);
247
347
  }
248
348
 
@@ -0,0 +1,51 @@
1
+ (function initCodexOverleafRunSettlementPersistence(root, factory) {
2
+ const api = factory();
3
+ if (typeof module === 'object' && module.exports) module.exports = api;
4
+ if (root) root.CodexOverleafRunSettlementPersistence = api;
5
+ })(typeof window !== 'undefined' ? window : globalThis, function runSettlementPersistenceFactory() {
6
+ 'use strict';
7
+
8
+ function create(options = {}) {
9
+ const Settlement = options.writebackSettlement;
10
+
11
+ function findTarget(recordId, sessionId) {
12
+ return typeof options.findRunRecord === 'function'
13
+ ? options.findRunRecord(recordId, sessionId)
14
+ : null;
15
+ }
16
+
17
+ function applyToCurrentRun(settlement) {
18
+ const view = options.getCurrentRunView?.();
19
+ if (!settlement || !view?.recordId) return;
20
+ const record = findTarget(view.recordId, view.sessionId);
21
+ if (!record) return;
22
+ Object.assign(record, Settlement.applySettlementTransition(record, settlement));
23
+ }
24
+
25
+ function settleMirrorRefresh(result = {}) {
26
+ const record = findTarget(result.recordId, result.sessionId);
27
+ if (!record) return;
28
+ if (result.runProjectId && result.runProjectId !== options.getCurrentProjectId?.()) return;
29
+ const existing = record.settlement && typeof record.settlement === 'object'
30
+ ? record.settlement
31
+ : record.settlementFacts;
32
+ if (!existing || typeof existing !== 'object') return;
33
+ record.settlement = Settlement.compactSettlementFacts({
34
+ ...existing,
35
+ evidence: {
36
+ ...(existing.evidence || {}),
37
+ mirrored: result.state
38
+ }
39
+ });
40
+ delete record.settlementFacts;
41
+ options.saveStateSoon?.();
42
+ }
43
+
44
+ return Object.freeze({
45
+ applyToCurrentRun,
46
+ settleMirrorRefresh
47
+ });
48
+ }
49
+
50
+ return Object.freeze({ create });
51
+ });