codex-overleaf-link 2.2.0 → 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 (72) 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/mirrorWorkspace.js +15 -8
  70. package/native-host/src/nativeTransportEnvelope.js +148 -0
  71. package/native-host/src/taskRunnerRuntime.js +3 -3
  72. package/package.json +13 -5
@@ -19,13 +19,6 @@
19
19
  const SLOW_IDLE_RETRY_MS = 15000;
20
20
  const FAST_IDLE_RETRY_BLOCKERS = new Set(['recent_user_activity', 'save_state_not_stable']);
21
21
  const WATCHDOG_INTERVAL_MINUTES = 1;
22
- const PHASE_TIMEOUT_MS = Object.freeze({
23
- checking: 30 * 1000,
24
- downloading: 2 * 60 * 1000,
25
- applying: 90 * 1000,
26
- awaiting_health: 2 * 60 * 1000,
27
- rolling_back: 60 * 1000
28
- });
29
22
  const LEGACY_GUARD = Number.MAX_SAFE_INTEGER;
30
23
  const OVERLEAF_MATCHES = [
31
24
  'https://www.overleaf.com/project',
@@ -42,6 +35,7 @@
42
35
  ]);
43
36
 
44
37
  const policy = root.CodexOverleafUpdateConsent;
38
+ const revocation = root.CodexOverleafUpdateRevocation;
45
39
  let nativeBridge = null;
46
40
  let initialized = false;
47
41
  let policyTail = Promise.resolve();
@@ -49,7 +43,7 @@
49
43
  let activationPromise = null;
50
44
 
51
45
  function init(options = {}) {
52
- if (initialized || !policy) return;
46
+ if (initialized || !policy || !revocation) return;
53
47
  initialized = true;
54
48
  nativeBridge = options.nativeBridge || root.CodexOverleafNativeBridge;
55
49
 
@@ -70,7 +64,7 @@
70
64
  void enqueuePolicyAction(() => checkOnly({ manual: false })).catch(() => {});
71
65
  }
72
66
  if (alarm?.name === WATCHDOG_ALARM) {
73
- void enqueuePolicyAction(() => reconcileExpiredPhase()).catch(() => {});
67
+ void enqueuePolicyAction(() => reconcileRecoveryState()).catch(() => {});
74
68
  }
75
69
  if (alarm?.name === IDLE_ALARM) {
76
70
  void enqueuePolicyAction(() => tryActivateStagedUpdate()).catch(() => {});
@@ -95,7 +89,7 @@
95
89
  periodInMinutes: WATCHDOG_INTERVAL_MINUTES
96
90
  });
97
91
  await recoverInterruptedCheck();
98
- await reconcileExpiredPhase();
92
+ await reconcileRecoveryState();
99
93
  await settleTerminalConsent();
100
94
  await armLegacyGuard();
101
95
  await publishView();
@@ -274,20 +268,27 @@
274
268
  }
275
269
 
276
270
  const authorizationId = crypto.randomUUID();
271
+ let authorizationIntent = revocation.prepareAuthorization(
272
+ consent,
273
+ authorizationId,
274
+ state.latestVersion,
275
+ Date.now()
276
+ );
277
277
  try {
278
+ authorizationIntent = await setConsentState(authorizationIntent);
278
279
  await requestNative('update.authorize', {
279
280
  authorizationId,
280
281
  targetVersion: state.latestVersion,
281
282
  currentVersion: currentVersion()
282
283
  });
283
- await setConsentState({
284
- ...consent,
284
+ await setConsentState(revocation.clear({
285
+ ...authorizationIntent,
285
286
  snoozedVersion: '',
286
287
  snoozedUntil: 0,
287
288
  authorizedVersion: state.latestVersion,
288
289
  authorizationId,
289
290
  authorizedAt: Date.now()
290
- });
291
+ }));
291
292
  await setUpdateState({ ...state, postponeUntil: 0, code: '', message: '' });
292
293
  const executor = globalThis.CodexOverleafManagedUpdateExecutor;
293
294
  if (!executor || typeof executor.installAuthorizedUpdate !== 'function') {
@@ -299,17 +300,31 @@
299
300
  await executor.installAuthorizedUpdate();
300
301
  return getView();
301
302
  } catch (error) {
302
- await bestEffortRevoke({
303
+ const observedConsent = await getConsentState().catch(() => consent);
304
+ const pendingConsent = revocation.prepareAuthorization({
305
+ ...observedConsent,
306
+ authorizedVersion: state.latestVersion,
303
307
  authorizationId,
304
- targetVersion: state.latestVersion,
305
- transactionId: ''
306
- });
307
- await setConsentState({
308
- ...consent,
309
- authorizedVersion: '',
310
- authorizationId: '',
311
- authorizedAt: 0
312
- });
308
+ authorizedAt: observedConsent.authorizedAt || Date.now()
309
+ }, authorizationId, state.latestVersion, Date.now());
310
+ await setConsentState(pendingConsent);
311
+ try {
312
+ await requestNative('update.revoke', {
313
+ authorizationId: pendingConsent.revokingAuthorizationId,
314
+ targetVersion: pendingConsent.revokingVersion,
315
+ transactionId: pendingConsent.revokingTransactionId
316
+ });
317
+ await setConsentState(revocation.clear({
318
+ ...pendingConsent,
319
+ authorizedVersion: '',
320
+ authorizationId: '',
321
+ authorizedAt: 0
322
+ }));
323
+ } catch (revokeError) {
324
+ if (error && typeof error === 'object') {
325
+ error.revocationError = safeMessage(revokeError);
326
+ }
327
+ }
313
328
  const observed = await getUpdateState().catch(() => state);
314
329
  if (!['awaiting_health', 'committed', 'rolled_back'].includes(observed.state)) {
315
330
  await setUpdateState({
@@ -486,15 +501,19 @@
486
501
  if (['applying', 'awaiting_health'].includes(state.state)) {
487
502
  throw codedError('update_revoke_too_late', 'The update is already being installed.');
488
503
  }
504
+ let pendingConsent = consent;
489
505
  if (consent.authorizationId) {
506
+ pendingConsent = revocation.begin(consent, state, Date.now());
507
+ await setConsentState(pendingConsent);
490
508
  try {
491
509
  await requestNative('update.revoke', {
492
- authorizationId: consent.authorizationId,
493
- targetVersion: consent.authorizedVersion || state.latestVersion,
494
- transactionId: state.transactionId || ''
510
+ authorizationId: pendingConsent.revokingAuthorizationId,
511
+ targetVersion: pendingConsent.revokingVersion,
512
+ transactionId: pendingConsent.revokingTransactionId
495
513
  });
496
514
  } catch (error) {
497
515
  if (error?.code === 'update_revoke_too_late') {
516
+ await setConsentState(revocation.clear(pendingConsent));
498
517
  return getView();
499
518
  }
500
519
  throw error;
@@ -503,31 +522,56 @@
503
522
  throw codedError('update_consent_mismatch', 'The staged update has no matching runtime authorization.');
504
523
  }
505
524
 
506
- const snoozedUntil = Date.now() + SNOOZE_MS;
507
525
  clearIdleRetryTimer();
508
526
  await chrome.alarms?.clear?.(IDLE_ALARM).catch(() => {});
509
- await setUpdateState({
510
- ...state,
511
- state: 'update_available',
512
- transactionId: '',
513
- stagedAt: 0,
514
- blocker: '',
515
- blockers: [],
516
- postponeUntil: LEGACY_GUARD,
517
- code: '',
518
- message: ''
519
- });
520
- await setConsentState({
521
- ...consent,
522
- snoozedVersion: state.latestVersion,
523
- snoozedUntil,
524
- authorizedVersion: '',
525
- authorizationId: '',
526
- authorizedAt: 0
527
+ const completed = revocation.complete(state, pendingConsent, {
528
+ now: Date.now(),
529
+ snoozeMs: SNOOZE_MS,
530
+ postponeUntil: LEGACY_GUARD
527
531
  });
532
+ await setUpdateAndConsentState(completed.updateState, completed.consentState);
528
533
  return getView();
529
534
  }
530
535
 
536
+ async function reconcileRecoveryState() {
537
+ await reconcilePendingRevocation();
538
+ return reconcileExpiredPhase();
539
+ }
540
+
541
+ async function reconcilePendingRevocation() {
542
+ const [state, consent] = await Promise.all([getUpdateState(), getConsentState()]);
543
+ if (!revocation.hasPending(consent)) return state;
544
+
545
+ try {
546
+ await requestNative('update.revoke', {
547
+ authorizationId: consent.revokingAuthorizationId,
548
+ targetVersion: consent.revokingVersion,
549
+ transactionId: consent.revokingTransactionId
550
+ });
551
+ } catch (error) {
552
+ if (error?.code === 'update_revoke_too_late') {
553
+ await setConsentState(revocation.clear(consent));
554
+ return state;
555
+ }
556
+ if (!['update_already_revoked', 'update_authorization_revoked'].includes(error?.code)) {
557
+ return state;
558
+ }
559
+ }
560
+
561
+ clearIdleRetryTimer();
562
+ await chrome.alarms?.clear?.(IDLE_ALARM).catch(() => {});
563
+ const completed = revocation.complete(state, consent, {
564
+ now: Date.now(),
565
+ snoozeMs: SNOOZE_MS,
566
+ postponeUntil: LEGACY_GUARD
567
+ });
568
+ const settled = await setUpdateAndConsentState(
569
+ completed.updateState,
570
+ completed.consentState
571
+ );
572
+ return settled.updateState;
573
+ }
574
+
531
575
  async function settleTerminalConsent() {
532
576
  const [state, consent] = await Promise.all([getUpdateState(), getConsentState()]);
533
577
  if (!policy.isTerminalState(state.state) || !consent.authorizationId) return;
@@ -558,7 +602,7 @@
558
602
  currentVersion: transaction.sourceVersion || state.currentVersion,
559
603
  code: 'update_health_timeout',
560
604
  message: 'The updated runtime did not complete its health check and was rolled back. Retry, or use the manual update command.'
561
- });
605
+ }, { observed: true });
562
606
  }
563
607
  if (transaction?.state === 'committed') {
564
608
  return setUpdateState({
@@ -568,7 +612,7 @@
568
612
  latestVersion: transaction.targetVersion,
569
613
  code: '',
570
614
  message: ''
571
- });
615
+ }, { observed: true });
572
616
  }
573
617
  if (transaction?.state === 'rolled_back') {
574
618
  return setUpdateState({
@@ -577,7 +621,7 @@
577
621
  currentVersion: transaction.sourceVersion || state.currentVersion,
578
622
  code: transaction.reasonCode || 'update_rolled_back',
579
623
  message: 'The managed update was rolled back after activation did not complete.'
580
- });
624
+ }, { observed: true });
581
625
  }
582
626
  }
583
627
  return setUpdateState({
@@ -641,20 +685,34 @@
641
685
  return policy.normalizeUpdateState(stored?.[UPDATE_STATE_KEY], currentVersion());
642
686
  }
643
687
 
644
- async function setUpdateState(value) {
688
+ async function setUpdateState(value, options = {}) {
645
689
  const stored = await chrome.storage.local.get(UPDATE_STATE_KEY);
646
690
  const previous = policy.normalizeUpdateState(stored?.[UPDATE_STATE_KEY], currentVersion());
691
+ const next = projectUpdateState(previous, value, options);
692
+ await chrome.storage.local.set({ [UPDATE_STATE_KEY]: next });
693
+ return next;
694
+ }
695
+
696
+ function projectUpdateState(previous, value, options = {}) {
647
697
  const now = Date.now();
648
- const changedPhase = value.state && value.state !== previous.state;
649
- const timeoutMs = PHASE_TIMEOUT_MS[value.state] || 0;
650
- const next = policy.normalizeUpdateState({
698
+ const candidate = {
651
699
  ...value,
652
- initiatedBy: value.initiatedBy || previous.initiatedBy,
653
- phaseStartedAt: changedPhase ? now : (value.phaseStartedAt || previous.phaseStartedAt),
654
- deadlineAt: changedPhase ? (timeoutMs ? now + timeoutMs : 0) : (value.deadlineAt || previous.deadlineAt),
655
- heartbeatAt: now
656
- }, currentVersion());
657
- await chrome.storage.local.set({ [UPDATE_STATE_KEY]: next });
700
+ initiatedBy: value.initiatedBy || previous.initiatedBy
701
+ };
702
+ const projection = root.CodexOverleafManagedUpdateProjection;
703
+ let next;
704
+ if (projection) {
705
+ const transition = options.observed === true
706
+ ? projection.transition
707
+ : projection.transitionCommand;
708
+ next = transition(previous, candidate, {
709
+ merge: true,
710
+ currentVersion: currentVersion(),
711
+ now
712
+ });
713
+ } else {
714
+ next = policy.normalizeUpdateState(candidate, currentVersion());
715
+ }
658
716
  return next;
659
717
  }
660
718
 
@@ -669,6 +727,18 @@
669
727
  return next;
670
728
  }
671
729
 
730
+ async function setUpdateAndConsentState(updateValue, consentValue, options = {}) {
731
+ const stored = await chrome.storage.local.get([UPDATE_STATE_KEY, CONSENT_STATE_KEY]);
732
+ const previous = policy.normalizeUpdateState(stored?.[UPDATE_STATE_KEY], currentVersion());
733
+ const updateState = projectUpdateState(previous, updateValue, options);
734
+ const consentState = policy.normalizeConsentState(consentValue);
735
+ await chrome.storage.local.set({
736
+ [UPDATE_STATE_KEY]: updateState,
737
+ [CONSENT_STATE_KEY]: consentState
738
+ });
739
+ return { updateState, consentState };
740
+ }
741
+
672
742
  async function requestNative(method, params = {}) {
673
743
  const response = await nativeBridge?.requestInternal?.({
674
744
  id: crypto.randomUUID(),
@@ -684,14 +754,6 @@
684
754
  return response.result || {};
685
755
  }
686
756
 
687
- async function bestEffortRevoke(params) {
688
- try {
689
- await requestNative('update.revoke', params);
690
- } catch (_error) {
691
- // Native authorization remains the hard gate if cleanup cannot connect.
692
- }
693
- }
694
-
695
757
  function enqueuePolicyAction(action) {
696
758
  const result = policyTail.then(action, action);
697
759
  policyTail = result.catch(() => {});
@@ -1,15 +1,14 @@
1
1
  (function initCodexOverleafApplyResultFormatters() {
2
2
  'use strict';
3
3
 
4
- const i18n = window.CodexOverleafI18n;
5
- const FailureReasons = window.CodexOverleafFailureReasons;
6
-
7
4
  // Apply-result / failure-reason formatters carved out of contentRuntime.js
8
5
  // (v1.4.7 structural-debt phase 3): the skipped-detail renderer and the
9
6
  // bilingual apply/bridge reason formatters. Code moved verbatim; the few
10
7
  // runtime collaborators are factory-injected.
11
8
  function create(deps = {}) {
12
9
  const {
10
+ i18n,
11
+ FailureReasons,
13
12
  getLocale,
14
13
  tr,
15
14
  tx,