codex-overleaf-link 2.3.5 → 2.3.6

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 (33) hide show
  1. package/README.md +31 -24
  2. package/extension/bootstrap/manifest.template.json +1 -1
  3. package/extension/src/background.js +4 -0
  4. package/extension/src/backgroundUpdateCoordinator.js +88 -4
  5. package/extension/src/content/contentRuntime.js +24 -7
  6. package/extension/src/content/diagnosticsController.js +13 -3
  7. package/extension/src/content/diagnosticsPanel.js +1 -1
  8. package/extension/src/content/generated/content.bundle.js +848 -245
  9. package/extension/src/content/generated/content.bundle.meta.json +6 -3
  10. package/extension/src/content/legacyGlobalRegistry.js +2 -0
  11. package/extension/src/content/otWarmMirror.js +95 -65
  12. package/extension/src/content/otWarmMirrorController.js +102 -1
  13. package/extension/src/content/panelMaintenance.js +26 -15
  14. package/extension/src/content/providerSettingsCoordinator.js +1 -0
  15. package/extension/src/content/runFailureNotice.js +104 -0
  16. package/extension/src/content/runScrollLayout.js +124 -0
  17. package/extension/src/content/runTimelineView.js +36 -10
  18. package/extension/src/content/settingsPanel.js +28 -11
  19. package/extension/src/content/updateNotice.js +24 -4
  20. package/extension/src/page/overleafRealtimeObserver.js +116 -24
  21. package/extension/src/shared/compatibility.js +1 -1
  22. package/extension/src/shared/i18n.js +77 -37
  23. package/extension/src/shared/managedUpdateProjection.js +1 -1
  24. package/extension/src/shared/otText.js +0 -1
  25. package/extension/src/shared/sessionState.js +17 -21
  26. package/extension/src/shared/sessionTitle.js +120 -0
  27. package/extension/src/shared/updateRuntimeIdentity.js +32 -0
  28. package/extension/styles/panel.css +73 -14
  29. package/native-host/src/mirrorWorkspace.js +20 -1
  30. package/native-host/src/releaseProxy.js +177 -0
  31. package/native-host/src/releaseTransport.js +154 -0
  32. package/native-host/src/updateManager.js +40 -39
  33. package/package.json +1 -1
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  <h1>Codex Overleaf Link</h1>
4
4
  <p><strong>Empower Overleaf with Codex.</strong></p>
5
5
  <p>
6
- <img src="https://img.shields.io/badge/version-2.3.5-blue" alt="version">
6
+ <img src="https://img.shields.io/badge/version-2.3.6-blue" alt="version">
7
7
  <img src="https://img.shields.io/badge/platform-macOS%20%2F%20Windows%20%2F%20Linux-lightgrey" alt="platform">
8
8
  <img src="https://img.shields.io/badge/chrome-MV3-green" alt="chrome manifest v3">
9
9
  <img src="https://img.shields.io/badge/node-%3E%3D20-brightgreen" alt="node version">
@@ -45,6 +45,8 @@ Codex Overleaf Link adds a Codex panel directly inside Overleaf and mirrors the
45
45
  - **Parallel subagents** — enable the `parallel-subagents` skill for decomposable tasks. The native host runs workers with assigned files; the skill can split a single file into section jobs. Worker progress appears in the timeline, and detected ownership violations are withheld from Overleaf writeback.
46
46
  - **OT warm mirror** — optional, read-only observation of active Overleaf text edits keeps focused mirror files warm. It is off by default and falls back to the normal snapshot path when unavailable, stale, or inconsistent. Overleaf writeback still uses the page bridge.
47
47
 
48
+ OT freshness expires after 30 seconds, and focused warm starts still verify current file content through the page bridge. OT observation never establishes whole-project freshness or replaces the Overleaf writeback path.
49
+
48
50
  ## Requirements
49
51
 
50
52
  | Requirement | Notes |
@@ -70,14 +72,14 @@ One command installs the managed native host **and** managed extension runtime.
70
72
  macOS / Linux:
71
73
 
72
74
  ```bash
73
- CODEX_OVERLEAF_REF=v2.3.5 bash -c "$(curl -fsSL https://raw.githubusercontent.com/Ghqqqq/codex-overleaf-link/v2.3.5/install.sh)"
75
+ CODEX_OVERLEAF_REF=v2.3.6 bash -c "$(curl -fsSL https://raw.githubusercontent.com/Ghqqqq/codex-overleaf-link/v2.3.6/install.sh)"
74
76
  ```
75
77
 
76
78
  Windows PowerShell:
77
79
 
78
80
  ```powershell
79
- iwr https://raw.githubusercontent.com/Ghqqqq/codex-overleaf-link/v2.3.5/install.ps1 -OutFile install.ps1
80
- $env:CODEX_OVERLEAF_REF='v2.3.5'
81
+ iwr https://raw.githubusercontent.com/Ghqqqq/codex-overleaf-link/v2.3.6/install.ps1 -OutFile install.ps1
82
+ $env:CODEX_OVERLEAF_REF='v2.3.6'
81
83
  powershell -ExecutionPolicy Bypass -File install.ps1
82
84
  ```
83
85
 
@@ -88,7 +90,7 @@ Then open `chrome://extensions`, enable **Developer mode**, click **Load unpacke
88
90
  `npm exec` installs the same managed native host and extension runtime without keeping a source checkout. Use it if you prefer a pinned npm package.
89
91
 
90
92
  ```bash
91
- npm exec --yes codex-overleaf-link@2.3.5 -- install-managed
93
+ npm exec --yes codex-overleaf-link@2.3.6 -- install-managed
92
94
  ```
93
95
 
94
96
  Then, in `chrome://extensions`, enable **Developer mode**, click **Load unpacked**, and select the managed extension path printed by the command. The Release extension zip remains available for explicitly unmanaged/manual installations.
@@ -185,6 +187,11 @@ Generated binary writeback is a separate operation: supported assets up to **10
185
187
 
186
188
  ## Update
187
189
 
190
+ Update downloads retry transient network failures within a bounded time budget, including stalled response bodies. The updater respects HTTP/HTTPS environment proxies and supported macOS/Windows system proxy settings. TLS certificate and release-signature failures remain blocking errors. SOCKS/PAC-only configurations require an HTTP proxy endpoint.
191
+
192
+ After a manual reinstall, installed files and running components are shown separately. Use the reload action once Overleaf is saved and idle; pending Overleaf tabs can then refresh. A disk replacement alone is not reported as a successfully health-confirmed update.
193
+
194
+
188
195
  Managed installations **check** for signed stable updates automatically. When an update is available, choose **Update now** in the update notice or **Settings → Software updates** to authorize that version. The updater then downloads and verifies the coordinated extension/native bundle, waits until connected Overleaf tabs are saved and idle and the native host has no active work, and applies both components together. A failed health check restores the previous version. The update notice also offers postponement and progress details.
189
196
 
190
197
  Stable updates use signed release metadata and artifact hashes; draft and prerelease versions are not selected. Releases that require a different Bootstrap protocol need a managed reinstall. The updater does not silently add Chrome permissions.
@@ -201,9 +208,9 @@ npm installs, updates, and uninstalls the coordinated managed extension/native p
201
208
 
202
209
  | Action | Command |
203
210
  |--------|---------|
204
- | Install / recover / migrate | `npm exec --yes codex-overleaf-link@2.3.5 -- install-managed` |
205
- | Diagnose | `npm exec --yes codex-overleaf-link@2.3.5 -- doctor` |
206
- | Uninstall | `npm exec --yes codex-overleaf-link@2.3.5 -- uninstall-managed` |
211
+ | Install / recover / migrate | `npm exec --yes codex-overleaf-link@2.3.6 -- install-managed` |
212
+ | Diagnose | `npm exec --yes codex-overleaf-link@2.3.6 -- doctor` |
213
+ | Uninstall | `npm exec --yes codex-overleaf-link@2.3.6 -- uninstall-managed` |
207
214
 
208
215
  Use `--extension-id <chrome-extension-id>` only for a custom/dev unpacked extension id that differs from the official bundled id.
209
216
 
@@ -214,7 +221,7 @@ Use `--extension-id <chrome-extension-id>` only for a custom/dev unpacked extens
214
221
  Remove the managed extension/native installation (append `--browser chromium` on Linux Chromium):
215
222
 
216
223
  ```bash
217
- npm exec --yes codex-overleaf-link@2.3.5 -- uninstall-managed
224
+ npm exec --yes codex-overleaf-link@2.3.6 -- uninstall-managed
218
225
  ```
219
226
 
220
227
  The same command works in Windows PowerShell. It also applies to current `install.sh` / `install.ps1` installations, which install the managed pair.
@@ -222,7 +229,7 @@ The same command works in Windows PowerShell. It also applies to current `instal
222
229
  For an unmanaged checkout or native-only installation, use `npm run uninstall:native` from the checkout, or:
223
230
 
224
231
  ```bash
225
- npm exec --yes codex-overleaf-link@2.3.5 -- uninstall-native
232
+ npm exec --yes codex-overleaf-link@2.3.6 -- uninstall-native
226
233
  ```
227
234
 
228
235
  If you are removing an older native-only source installation and still have its source checkout, its bundled uninstaller can also be invoked directly:
@@ -248,7 +255,7 @@ Remove the extension entry from `chrome://extensions` as well. To erase saved Co
248
255
  For a managed installation, rerun the [managed installer](#install), reload the extension in `chrome://extensions`, then refresh the Overleaf tab. This recovers the coordinated extension/native pair after an incomplete installation or incompatible runtime update.
249
256
 
250
257
  ```bash
251
- npm exec --yes codex-overleaf-link@2.3.5 -- install-managed
258
+ npm exec --yes codex-overleaf-link@2.3.6 -- install-managed
252
259
  ```
253
260
 
254
261
  For an unmanaged checkout, rebuild the extension and reinstall its native host from the same checkout. Use PowerShell installation commands on Windows.
@@ -369,13 +376,13 @@ The shipped extension targets `https://overleaf.com/project` and `https://www.ov
369
376
  Linux Chromium install or update:
370
377
 
371
378
  ```bash
372
- CODEX_OVERLEAF_REF=v2.3.5 bash -c "$(curl -fsSL https://raw.githubusercontent.com/Ghqqqq/codex-overleaf-link/v2.3.5/install.sh)" -- --browser chromium
379
+ CODEX_OVERLEAF_REF=v2.3.6 bash -c "$(curl -fsSL https://raw.githubusercontent.com/Ghqqqq/codex-overleaf-link/v2.3.6/install.sh)" -- --browser chromium
373
380
  ```
374
381
 
375
382
  Linux Chromium uninstall:
376
383
 
377
384
  ```bash
378
- npm exec --yes codex-overleaf-link@2.3.5 -- uninstall-managed --browser chromium
385
+ npm exec --yes codex-overleaf-link@2.3.6 -- uninstall-managed --browser chromium
379
386
  ```
380
387
 
381
388
  ## Extension ID
@@ -389,27 +396,27 @@ illdpneeeopfffmiepaejglgmhpmdhdc
389
396
  The installer uses this id by default. For a managed installation with a custom id, rerun the managed installer with the id shown in `chrome://extensions`:
390
397
 
391
398
  ```bash
392
- npm exec --yes codex-overleaf-link@2.3.5 -- install-managed --extension-id <your-chrome-extension-id>
399
+ npm exec --yes codex-overleaf-link@2.3.6 -- install-managed --extension-id <your-chrome-extension-id>
393
400
  ```
394
401
 
395
402
  For an unmanaged extension, use the native-only installer:
396
403
 
397
404
  ```bash
398
- npm exec --yes codex-overleaf-link@2.3.5 -- install-native --extension-id <your-chrome-extension-id>
405
+ npm exec --yes codex-overleaf-link@2.3.6 -- install-native --extension-id <your-chrome-extension-id>
399
406
  ```
400
407
 
401
408
  Both npm commands work in PowerShell. Source installers also accept the `CODEX_OVERLEAF_EXTENSION_ID` environment variable. The Native Messaging manifest's `allowed_origins` must match the loaded extension id.
402
409
 
403
410
  ## GitHub Release Artifacts
404
411
 
405
- The v2.3.5 GitHub Release contains:
412
+ The v2.3.6 GitHub Release contains:
406
413
 
407
- - `codex-overleaf-link-extension-v2.3.5.zip`: loadable Chrome extension package for manual unpacked installation.
408
- - `codex-overleaf-native-host-v2.3.5.tar.gz`: native host runtime files used by the installer and release verification.
409
- - `codex-overleaf-update-v2.3.5.tar.gz`: coordinated extension/native bundle used by the managed updater.
410
- - `codex-overleaf-link-2.3.5.tgz`: npm native host CLI package for pinned install, doctor, and uninstall flows.
411
- - `install.sh`: release-pinned macOS / Linux installer that defaults to `v2.3.5` when run directly from the release artifact.
412
- - `install.ps1`: release-pinned Windows PowerShell installer that defaults to `v2.3.5` when run directly from the release artifact.
414
+ - `codex-overleaf-link-extension-v2.3.6.zip`: loadable Chrome extension package for manual unpacked installation.
415
+ - `codex-overleaf-native-host-v2.3.6.tar.gz`: native host runtime files used by the installer and release verification.
416
+ - `codex-overleaf-update-v2.3.6.tar.gz`: coordinated extension/native bundle used by the managed updater.
417
+ - `codex-overleaf-link-2.3.6.tgz`: npm native host CLI package for pinned install, doctor, and uninstall flows.
418
+ - `install.sh`: release-pinned macOS / Linux installer that defaults to `v2.3.6` when run directly from the release artifact.
419
+ - `install.ps1`: release-pinned Windows PowerShell installer that defaults to `v2.3.6` when run directly from the release artifact.
413
420
  - `uninstall-native-host.mjs`: native host uninstaller that removes the Chrome Native Messaging manifest, bridge executable, and runtime copy.
414
421
  - `nativeHostPlatform.js`, `manifest.js`, `runtimeInstaller.js`: helper files required by the loose uninstaller asset.
415
422
  - `SHA256SUMS`, `release-manifest.json`, and `release-manifest.sig`: checksums, release metadata, and its Ed25519 signature.
@@ -484,8 +491,8 @@ Use this matrix for release-candidate signoff and compatibility reports. Record
484
491
  | Browser/channel/version | Google Chrome channel and version. | Google Chrome channel and version. | Google Chrome channel and version. | Chromium channel/package and version. |
485
492
  | Install mode | Managed pair recommended; unmanaged Release zip or checkout also available. | Same as macOS Chrome. | Same as macOS Chrome. | Managed or unmanaged; register with `--browser chromium`. |
486
493
  | Extension id | Bundled id `illdpneeeopfffmiepaejglgmhpmdhdc`, or actual custom id passed with `--extension-id`. | Bundled id `illdpneeeopfffmiepaejglgmhpmdhdc`, or actual custom id passed with `--extension-id`. | Bundled id `illdpneeeopfffmiepaejglgmhpmdhdc`, or actual custom id passed with `--extension-id`. | Bundled id `illdpneeeopfffmiepaejglgmhpmdhdc`, or actual custom id passed with `--extension-id`. |
487
- | Installer/update command | `npm exec --yes codex-overleaf-link@2.3.5 -- install-managed` | `npm exec --yes codex-overleaf-link@2.3.5 -- install-managed` | `npm exec --yes codex-overleaf-link@2.3.5 -- install-managed` | `npm exec --yes codex-overleaf-link@2.3.5 -- install-managed --browser chromium` |
488
- | Uninstall command | `npm exec --yes codex-overleaf-link@2.3.5 -- uninstall-managed` | `npm exec --yes codex-overleaf-link@2.3.5 -- uninstall-managed` | `npm exec --yes codex-overleaf-link@2.3.5 -- uninstall-managed` | `npm exec --yes codex-overleaf-link@2.3.5 -- uninstall-managed --browser chromium` |
494
+ | Installer/update command | `npm exec --yes codex-overleaf-link@2.3.6 -- install-managed` | `npm exec --yes codex-overleaf-link@2.3.6 -- install-managed` | `npm exec --yes codex-overleaf-link@2.3.6 -- install-managed` | `npm exec --yes codex-overleaf-link@2.3.6 -- install-managed --browser chromium` |
495
+ | Uninstall command | `npm exec --yes codex-overleaf-link@2.3.6 -- uninstall-managed` | `npm exec --yes codex-overleaf-link@2.3.6 -- uninstall-managed` | `npm exec --yes codex-overleaf-link@2.3.6 -- uninstall-managed` | `npm exec --yes codex-overleaf-link@2.3.6 -- uninstall-managed --browser chromium` |
489
496
  | Manifest/registry path | `~/Library/Application Support/Google/Chrome/NativeMessagingHosts/com.codex.overleaf.json` | `HKCU\Software\Google\Chrome\NativeMessagingHosts\com.codex.overleaf` -> `%LOCALAPPDATA%\CodexOverleaf\native-host-runtime\com.codex.overleaf.json` | `~/.config/google-chrome/NativeMessagingHosts/com.codex.overleaf.json` | `~/.config/chromium/NativeMessagingHosts/com.codex.overleaf.json` |
490
497
  | Managed runtime paths | `~/.codex-overleaf/managed/extension` and `~/.codex-overleaf/managed/native`. | `%LOCALAPPDATA%\CodexOverleaf\managed\extension` and `%LOCALAPPDATA%\CodexOverleaf\managed\native`. | Same as macOS Chrome. | Same as macOS Chrome. |
491
498
  | Node/Git/Codex/TeX | Record exact versions; see [Requirements](#requirements) for installation and provider prerequisites. | Same as macOS Chrome. | Same as macOS Chrome. | Same as macOS Chrome. |
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "manifest_version": 3,
3
3
  "name": "Codex Overleaf Link",
4
- "version": "2.3.5",
4
+ "version": "2.3.6",
5
5
  "description": "Connect the active Overleaf project to a local Codex bridge.",
6
6
  "icons": {
7
7
  "16": "assets/icons/icon16.png",
@@ -20,6 +20,10 @@ if (!globalThis.CodexOverleafGlobalPreferences) {
20
20
  importScripts(runtimeBase ? chrome.runtime.getURL(`${runtimeBase}/src/shared/globalPreferences.js`) : 'shared/globalPreferences.js');
21
21
  }
22
22
  globalThis.CodexOverleafGlobalPreferences?.installBackground(chrome);
23
+ if (!globalThis.CodexOverleafUpdateRuntimeIdentity) {
24
+ const runtimeBase = String(globalThis.__CODEX_OVERLEAF_RUNTIME_BASE__ || '');
25
+ importScripts(runtimeBase ? chrome.runtime.getURL(`${runtimeBase}/src/shared/updateRuntimeIdentity.js`) : 'shared/updateRuntimeIdentity.js');
26
+ }
23
27
 
24
28
  (function initBackground() {
25
29
  'use strict';
@@ -4,6 +4,7 @@
4
4
  const UPDATE_STATE_KEY = 'codex-overleaf-managed-update-state-v1';
5
5
  const CONSENT_STATE_KEY = 'codex-overleaf-update-consent-v1';
6
6
  const UPDATE_RELOAD_TABS_KEY = 'codex-overleaf-managed-update-tabs-v1';
7
+ const MANUAL_RELOAD_TABS_KEY = 'codex-overleaf-manual-runtime-reload-v1';
7
8
  const CHECK_ALARM = 'codex-overleaf-consent-update-check';
8
9
  const IDLE_ALARM = 'codex-overleaf-consent-update-idle';
9
10
  const WATCHDOG_ALARM = 'codex-overleaf-consent-update-watchdog';
@@ -12,7 +13,7 @@
12
13
  const CHECK_INTERVAL_MINUTES = 24 * 60;
13
14
  const SNOOZE_MS = 24 * 60 * 60 * 1000;
14
15
  const CANDIDATE_MAX_AGE_MS = 5 * 60 * 1000;
15
- const CHECK_REQUEST_TIMEOUT_MS = 15 * 1000;
16
+ const CHECK_REQUEST_TIMEOUT_MS = 55 * 1000;
16
17
  const ACTIVATION_TIMEOUT_MS = 20 * 1000;
17
18
  const ACTIVATION_POLL_MS = 250;
18
19
  const FAST_IDLE_RETRY_MS = 4000;
@@ -31,7 +32,8 @@
31
32
  'codex-overleaf/consent-update-check',
32
33
  'codex-overleaf/consent-update-install',
33
34
  'codex-overleaf/consent-update-later',
34
- 'codex-overleaf/consent-update-dismiss'
35
+ 'codex-overleaf/consent-update-dismiss',
36
+ 'codex-overleaf/consent-update-reload'
35
37
  ]);
36
38
 
37
39
  const policy = root.CodexOverleafUpdateConsent;
@@ -41,6 +43,10 @@
41
43
  let policyTail = Promise.resolve();
42
44
  let idleRetryTimer = null;
43
45
  let activationPromise = null;
46
+ let runtimeStatus = null;
47
+ let runtimeStatusAt = 0;
48
+ let runtimeStatusRead = null;
49
+ let runtimeReloadMessage = '';
44
50
 
45
51
  function init(options = {}) {
46
52
  if (initialized || !policy || !revocation) return;
@@ -92,6 +98,7 @@
92
98
  await reconcileRecoveryState();
93
99
  await settleTerminalConsent();
94
100
  await armLegacyGuard();
101
+ await finishManualRuntimeReload();
95
102
  await publishView();
96
103
  const updateState = await getUpdateState();
97
104
  if (['staged', 'waiting_for_idle'].includes(updateState.state)) {
@@ -129,6 +136,8 @@
129
136
  return postponeUpdate();
130
137
  case 'codex-overleaf/consent-update-dismiss':
131
138
  return dismissCompletedUpdate();
139
+ case 'codex-overleaf/consent-update-reload':
140
+ return reloadInstalledRuntime();
132
141
  default:
133
142
  throw codedError('unknown_update_action', 'Unknown update action.');
134
143
  }
@@ -648,11 +657,86 @@
648
657
  }
649
658
 
650
659
  async function getView() {
651
- const [state, consent] = await Promise.all([getUpdateState(), getConsentState()]);
652
- return policy.deriveViewModel(state, consent, {
660
+ let [state, consent] = await Promise.all([getUpdateState(), getConsentState()]);
661
+ const busy = state.state === 'checking' || policy.isExecutionState(state.state) || consent.authorizationId;
662
+ const runtime = busy ? { state: 'transaction_active' } : await readRuntimeStatus();
663
+ if (runtime.state === 'aligned' && ['failed', 'rolled_back'].includes(state.state) &&
664
+ policy.compareStableVersions(state.latestVersion || state.currentVersion, runtime.installedVersion) <= 0) {
665
+ state = await setUpdateState({ ...state, state: 'idle', currentVersion: runtime.installedVersion,
666
+ latestVersion: runtime.installedVersion, code: '', message: '', transactionId: '', blocker: '', blockers: [] });
667
+ }
668
+ const view = policy.deriveViewModel(state, consent, {
653
669
  currentVersion: currentVersion(),
654
670
  now: Date.now()
655
671
  });
672
+ view.runtime = runtime;
673
+ const pending = (await chrome.storage.local.get(MANUAL_RELOAD_TABS_KEY))?.[MANUAL_RELOAD_TABS_KEY];
674
+ const pagesPending = runtime.state === 'aligned' && pending?.targetVersion === runtime.installedVersion && pending.tabIds?.length;
675
+ if (runtime.state === 'reload_required' || pagesPending) {
676
+ view.showPanel = true;
677
+ view.state = { ...state, state: pagesPending ? 'reload_tabs_required' : 'reload_required',
678
+ currentVersion: runtime.extensionVersion, latestVersion: runtime.installedVersion,
679
+ code: 'update_reload_required', message: runtimeReloadMessage };
680
+ view.badge = { text: 'UP', color: '#3578bd' };
681
+ }
682
+ return view;
683
+ }
684
+
685
+ async function readRuntimeStatus(force = false) {
686
+ if (!root.CodexOverleafUpdateRuntimeIdentity) return { state: 'unknown' };
687
+ if (!force && runtimeStatus && Date.now() - runtimeStatusAt < 5000) return runtimeStatus;
688
+ if (runtimeStatusRead) return runtimeStatusRead;
689
+ runtimeStatusRead = withTimeout(requestNative('update.status'), 4000,
690
+ codedError('update_status_timeout', 'Installed version inspection timed out.')).then(status => {
691
+ runtimeStatusAt = Date.now();
692
+ runtimeStatus = root.CodexOverleafUpdateRuntimeIdentity.inspectInstalledRuntime(status, {
693
+ extensionVersion: chrome.runtime.getManifest().version, runtimeVersion: currentVersion()
694
+ });
695
+ return runtimeStatus;
696
+ }).catch(() => ({ state: 'unknown' })).finally(() => { runtimeStatusRead = null; });
697
+ return runtimeStatusRead;
698
+ }
699
+
700
+ async function requireReloadSafePoint() {
701
+ const tabs = await chrome.tabs.query({ url: OVERLEAF_MATCHES });
702
+ const probes = await Promise.all(tabs.filter(isUsableEditorTab).map(tab => probeTabIdle(tab.id)));
703
+ const nativeGate = await requestNative('update.canApply').then(result => ({ ok: true, result }))
704
+ .catch(error => ({ ok: false, error: safeError(error) }));
705
+ const blockers = root.CodexOverleafUpdateStatus?.collectBlockers(probes, nativeGate) || ['busy'];
706
+ if (nativeBridge?.getPendingState?.().executionRequests > 0) blockers.push('background_execution_pending');
707
+ if (blockers.length) {
708
+ runtimeReloadMessage = 'Reload is waiting for Overleaf to be saved and idle (' + blockers.join(', ') + ').';
709
+ throw codedError('update_reload_busy', runtimeReloadMessage);
710
+ }
711
+ runtimeReloadMessage = '';
712
+ return tabs.filter(tab => Number.isInteger(tab.id) && !tab.discarded && tab.status !== 'unloaded');
713
+ }
714
+
715
+ async function reloadInstalledRuntime() {
716
+ const runtime = await readRuntimeStatus(true);
717
+ if (runtime.state === 'aligned') { await finishManualRuntimeReload(); return getView(); }
718
+ if (runtime.state !== 'reload_required') throw codedError('update_reload_unavailable', 'No verified installed update is awaiting reload.');
719
+ const tabs = await requireReloadSafePoint();
720
+ await chrome.storage.local.set({ [MANUAL_RELOAD_TABS_KEY]: {
721
+ targetVersion: runtime.installedVersion, tabIds: tabs.map(tab => tab.id)
722
+ } });
723
+ setTimeout(() => chrome.runtime.reload(), 0);
724
+ return getView();
725
+ }
726
+
727
+ async function finishManualRuntimeReload() {
728
+ const pending = (await chrome.storage.local.get(MANUAL_RELOAD_TABS_KEY))?.[MANUAL_RELOAD_TABS_KEY];
729
+ if (!pending || !Array.isArray(pending.tabIds)) return;
730
+ const runtime = await readRuntimeStatus(true);
731
+ if (runtime.state !== 'aligned' || runtime.installedVersion !== pending.targetVersion) return;
732
+ let tabs;
733
+ try { tabs = await requireReloadSafePoint(); } catch (_error) { return; }
734
+ const remaining = [];
735
+ for (const tab of tabs.filter(tab => pending.tabIds.includes(tab.id))) {
736
+ try { await chrome.tabs.reload(tab.id); } catch (_error) { remaining.push(tab.id); }
737
+ }
738
+ if (remaining.length) await chrome.storage.local.set({ [MANUAL_RELOAD_TABS_KEY]: { ...pending, tabIds: remaining } });
739
+ else await chrome.storage.local.remove(MANUAL_RELOAD_TABS_KEY);
656
740
  }
657
741
 
658
742
  async function publishView() {
@@ -418,6 +418,8 @@
418
418
  const runTimelineView = Modules.RunTimelineView.create({
419
419
  RunGuidanceView: Modules.RunGuidanceView,
420
420
  RunResultActions: runResultActions,
421
+ RunScrollLayout: Modules.RunScrollLayout,
422
+ RunFailureNotice: Modules.RunFailureNotice,
421
423
  tr,
422
424
  tx,
423
425
  getLocale,
@@ -835,6 +837,7 @@
835
837
  });
836
838
 
837
839
  let panel = null;
840
+ let probeStatusSnapshot = null;
838
841
  let panelRendererInstance = null;
839
842
  let sessionPanelInstance = null;
840
843
  let settingsPanelInstance = null;
@@ -1031,7 +1034,7 @@
1031
1034
  providerSettingsCoordinator.syncSessionProvider().catch(() => {});
1032
1035
  syncOtWarmMirrorController().catch(error => {
1033
1036
  updateOtStatusDisplay('unavailable');
1034
- appendPlainLog(tx(`Failed to sync experimental OT warm mirror: ${error.message}`, `同步实验性 OT 预热镜像失败:${error.message}`));
1037
+ appendPlainLog(tx(`Failed to sync OT warm mirror: ${error.message}`, `同步 OT 预热镜像失败:${error.message}`));
1035
1038
  });
1036
1039
  await refreshProbe({ quiet: true });
1037
1040
  }
@@ -1274,6 +1277,7 @@
1274
1277
  onProvidersOpen: () => providerSettingsCoordinator.open(),
1275
1278
  onClearAllHistory: () => clearAllHistoryWithConfirm(),
1276
1279
  onHistoryOpen: () => renderAuditHistoryPanel(),
1280
+ onStorageOpen: () => refreshStorageUsageSummary(),
1277
1281
  onHistoryFilter: () => applyAuditHistoryFilter(),
1278
1282
  // Experimental OT mirror: a single visible switch whose click is
1279
1283
  // intercepted so the confirm-before-enable flow still runs.
@@ -1444,9 +1448,6 @@
1444
1448
  function renderAttachmentPreviewList(attachments = [], container, options = {}) {
1445
1449
  return composerAttachmentController.renderAttachmentPreviewList(attachments, container, options);
1446
1450
  }
1447
-
1448
-
1449
-
1450
1451
  function applyLocaleToPanel() {
1451
1452
  if (!panel) {
1452
1453
  return;
@@ -1535,6 +1536,17 @@
1535
1536
  updateModelDisplay();
1536
1537
  renderSessionList();
1537
1538
  renderContextSelection();
1539
+ const probeStatus = panel.querySelector('[data-probe-status]');
1540
+ if (probeStatus?.dataset.refreshing === 'true') probeStatus.textContent = tr('refreshProbeLoading');
1541
+ else if (probeStatus && probeStatusSnapshot?.projectId === getCurrentProjectId()) {
1542
+ const { failed, probe, userInitiated } = probeStatusSnapshot;
1543
+ const text = failed ? tr('refreshProbeFailed') : formatProbeStatusBar(probe);
1544
+ probeStatus.textContent = userInitiated && !failed ? tr('refreshProbeDone', { status: text }) : text;
1545
+ if (!failed) updateExistingProbeNotice(probe);
1546
+ }
1547
+ updateSkillsEntrySummary();
1548
+ providerSettingsCoordinator.renderSummary();
1549
+ if (settingsPanelInstance?.container?.querySelector('[data-storage-card]')?.open) refreshStorageUsageSummary();
1538
1550
  }
1539
1551
 
1540
1552
  function setElementTitleAndAria(selector, title, ariaLabel) {
@@ -2393,7 +2405,7 @@
2393
2405
  }
2394
2406
 
2395
2407
  await saveState();
2396
- applyStateToPanel();
2408
+ applyStateToPanel({ preserveScroll: true });
2397
2409
  } catch (persistenceError) {
2398
2410
  appendRunEvent({
2399
2411
  title: tx('Codex result was generated, but saving local session history failed.', 'Codex 结果已生成,但保存本地会话记录失败。'),
@@ -4943,6 +4955,7 @@
4943
4955
  }
4944
4956
 
4945
4957
  async function refreshProbe(options = {}) {
4958
+ const projectId = getCurrentProjectId();
4946
4959
  const userInitiated = options.userInitiated === true;
4947
4960
  if (userInitiated) {
4948
4961
  setRefreshProbeLoading(true);
@@ -4958,6 +4971,8 @@
4958
4971
  const probe = await callPageBridge('probe', {
4959
4972
  manualOverride: state?.requireReviewing === false
4960
4973
  });
4974
+ if (projectId !== getCurrentProjectId()) return null;
4975
+ probeStatusSnapshot = { projectId, probe, userInitiated };
4961
4976
  const status = panel?.querySelector('[data-probe-status]');
4962
4977
  if (status) {
4963
4978
  status.textContent = userInitiated
@@ -4976,6 +4991,8 @@
4976
4991
  if (!userInitiated) {
4977
4992
  throw error;
4978
4993
  }
4994
+ if (projectId !== getCurrentProjectId()) return null;
4995
+ probeStatusSnapshot = { projectId, failed: true };
4979
4996
  const status = panel?.querySelector('[data-probe-status]');
4980
4997
  if (status) {
4981
4998
  status.textContent = tr('refreshProbeFailed');
@@ -6069,7 +6086,7 @@
6069
6086
  saveStateSoon();
6070
6087
  }
6071
6088
 
6072
- function applyStateToPanel() {
6089
+ function applyStateToPanel(options = {}) {
6073
6090
  applyPanelTheme(getThemePreference());
6074
6091
  if ((state.providerId || 'builtin') === 'builtin' && !modelSelectHasOption(state.model)) {
6075
6092
  renderModelOptions(getModelCatalog().FALLBACK_MODELS, state.model);
@@ -6105,7 +6122,7 @@
6105
6122
  syncModeControls();
6106
6123
  applySessionLabel();
6107
6124
  renderSessionList();
6108
- renderRunHistory();
6125
+ renderRunHistory(options);
6109
6126
  renderContextSelection();
6110
6127
  renderContextSummary();
6111
6128
  // v1.8.0 C4: refresh recovery — small attachments persisted in state are
@@ -359,7 +359,7 @@
359
359
  if (!options.collectOnly) {
360
360
  showDiagnosticsLoading(tr('diagnosticsOtTitle'), tr('diagnosticsLoading'));
361
361
  }
362
- // OT warm mirror is experimental and off by default. When it is disabled
362
+ // OT warm mirror is opt-in and off by default. When it is disabled
363
363
  // there is nothing live to probe, so report a clean, healthy "disabled"
364
364
  // state instead of querying (then warning about) absent OT metadata — or
365
365
  // degrading to the generic "could not run" error if a probe throws. A
@@ -374,6 +374,14 @@
374
374
  nextStep: '',
375
375
  technical: ''
376
376
  };
377
+ const mirrorState = getOtWarmMirrorState?.() || {};
378
+ if (mirrorState.failureActive && mirrorState.lastFailure) {
379
+ const failure = mirrorState.lastFailure;
380
+ disabledResult.status = 'warning';
381
+ disabledResult.summary = tr(otWarmMirrorController.getFailureMessageKey(failure.code));
382
+ disabledResult.nextStep = tr('otFailureNextStep');
383
+ disabledResult.technical = `lastErrorCode: ${failure.code}\nphase: ${failure.phase}\nfailedAt: ${new Date(failure.at).toISOString()}`;
384
+ }
377
385
  if (options.collectOnly) {
378
386
  return disabledResult;
379
387
  }
@@ -434,6 +442,7 @@
434
442
  const queuedEventCount = normalizeOtDiagnosticsCount(otStatus?.queuedEventCount);
435
443
  const lastEventAt = formatOtDiagnosticValue(otStatus?.lastEventAt, tr('noneValue'));
436
444
  const warmMirrorState = getOtWarmMirrorState() || {};
445
+ const failureActive = warmMirrorState.failureActive === true;
437
446
  const lastOtPatchAt = formatOtDiagnosticValue(mirrorStatus?.lastOtPatchAt || warmMirrorState.lastPatchAt, tr('noneValue'));
438
447
  const lastOtErrorCode = formatOtDiagnosticValue(mirrorStatus?.lastOtErrorCode || warmMirrorState.lastErrorCode, tr('noneValue'));
439
448
  const lastErrorCode = formatOtDiagnosticValue(otStatus?.lastErrorCode || otStatus?.reason || otStatus?.error, tr('noneValue'));
@@ -445,13 +454,14 @@
445
454
  const bridgeFailed = otStatus?.ok === false;
446
455
  const focusFiles = getActiveFocusFiles();
447
456
  const otWarmStart = otWarmMirrorController?.canUseOtWarmStart?.({ enabled, focusFiles, mirrorStatus }) || { ok: false };
448
- const fallback = !enabled || statusValue !== 'observing' || bridgeFailed || otWarmStart?.ok !== true;
457
+ const fallback = !enabled || failureActive || statusValue !== 'observing' || bridgeFailed || otWarmStart?.ok !== true;
449
458
 
450
459
  return {
451
460
  title: tr('diagnosticsOtTitle'),
452
461
  subtitle: tr('diagnosticsOtSubtitle'),
453
462
  status: enabled && !fallback && !bridgeFailed ? 'completed' : (enabled || bridgeFailed ? 'warning' : 'completed'),
454
- summary: tr(enabled ? 'diagnosticsOtSummaryEnabled' : 'diagnosticsOtSummaryDisabled'),
463
+ summary: tr(failureActive ? otWarmMirrorController.getFailureMessageKey(warmMirrorState.lastErrorCode)
464
+ : enabled ? 'diagnosticsOtSummaryEnabled' : 'diagnosticsOtSummaryDisabled'),
455
465
  bullets: [
456
466
  `${tr('otStatus')}: ${formatOtStatusLabel(statusValue)}`,
457
467
  `${tr('otFreshFiles')}: ${otFreshFileCount}`,
@@ -38,7 +38,7 @@
38
38
  <small data-i18n="diagnosticsSnapshotSubtitle">Full project, assets, read source</small>
39
39
  </button>
40
40
  <button type="button" data-diagnostics-ot>
41
- <span data-i18n="diagnosticsOtTitle">Check Experimental OT Mirror</span>
41
+ <span data-i18n="diagnosticsOtTitle">Check OT Mirror</span>
42
42
  <small data-i18n="diagnosticsOtSubtitle">Status, fresh files, fallback</small>
43
43
  </button>
44
44
  <button type="button" data-diagnostics-export>