codex-overleaf-link 2.6.0 → 2.6.5

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 (58) hide show
  1. package/README.md +9 -9
  2. package/README.zh-CN.md +9 -9
  3. package/extension/bootstrap/manifest.template.json +1 -1
  4. package/extension/src/background.js +70 -0
  5. package/extension/src/content/composerAttachments.js +35 -8
  6. package/extension/src/content/contentRuntime.js +200 -51
  7. package/extension/src/content/diagnosticsController.js +77 -34
  8. package/extension/src/content/diagnosticsExport.js +1 -1
  9. package/extension/src/content/diagnosticsPanel.js +129 -42
  10. package/extension/src/content/generated/content.bundle.js +1283 -331
  11. package/extension/src/content/generated/content.bundle.meta.json +3 -3
  12. package/extension/src/content/localSkillsPanel.js +4 -2
  13. package/extension/src/content/markdownText.js +11 -9
  14. package/extension/src/content/modelPicker.js +28 -19
  15. package/extension/src/content/nativeCompatibilityController.js +23 -9
  16. package/extension/src/content/pageBridgeClient.js +5 -5
  17. package/extension/src/content/panelMaintenance.js +4 -0
  18. package/extension/src/content/panelRenderer.js +4 -1
  19. package/extension/src/content/providerSettingsDialog.js +26 -18
  20. package/extension/src/content/recentProjects.js +26 -16
  21. package/extension/src/content/runQueueScheduler.js +19 -9
  22. package/extension/src/content/runResultActions.js +51 -4
  23. package/extension/src/content/runReviewController.js +9 -3
  24. package/extension/src/content/scopedPersistencePanelState.js +34 -25
  25. package/extension/src/content/selectionContextView.js +30 -3
  26. package/extension/src/content/sharedSessionViewSync.js +9 -1
  27. package/extension/src/content/writebackOrchestrator.js +266 -50
  28. package/extension/src/content/writingStyleSettings.js +70 -17
  29. package/extension/src/page/overleafEditor.js +12 -3
  30. package/extension/src/page/overleafProjectSnapshot.js +56 -3
  31. package/extension/src/page/snapshotRouter.js +75 -66
  32. package/extension/src/page/treeOperations.js +200 -19
  33. package/extension/src/page/writebackRouter.js +152 -40
  34. package/extension/src/pageBridge.js +17 -7
  35. package/extension/src/shared/agentTranscript.js +23 -2
  36. package/extension/src/shared/auditRecords.js +1 -0
  37. package/extension/src/shared/compatibility.js +1 -1
  38. package/extension/src/shared/composerAttachmentDraft.js +22 -7
  39. package/extension/src/shared/failureReasons.js +15 -0
  40. package/extension/src/shared/i18n.js +16 -0
  41. package/extension/src/shared/pageRpcContract.js +1 -1
  42. package/extension/src/shared/projectFiles.js +2 -1
  43. package/extension/src/shared/scopedPersistenceBrowserAdapter.js +34 -0
  44. package/extension/src/shared/staleGuard.js +58 -0
  45. package/extension/src/shared/storageMigration.js +104 -1
  46. package/extension/styles/panel.css +191 -63
  47. package/native-host/src/codexSessionRunner.js +1 -0
  48. package/native-host/src/debugLog.js +27 -3
  49. package/native-host/src/index.js +42 -11
  50. package/native-host/src/localSkills.js +1 -1
  51. package/native-host/src/mirrorWorkspace.js +47 -3
  52. package/native-host/src/nativeEnvironment.js +27 -5
  53. package/native-host/src/providerRuntime.js +1 -0
  54. package/native-host/src/skills/reviewer-response/SKILL.md +65 -0
  55. package/native-host/src/taskRunnerRuntime.js +11 -3
  56. package/native-host/src/updateManager.js +24 -6
  57. package/native-host/src/writingStyleRuntime.js +1 -1
  58. 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.6.0-blue" alt="version 2.6.0">
6
+ <img src="https://img.shields.io/badge/version-2.6.5-blue" alt="version 2.6.5">
7
7
  <img src="https://img.shields.io/badge/chrome-MV3-green" alt="chrome manifest v3">
8
8
  <img src="https://img.shields.io/badge/node-%3E%3D20-brightgreen" alt="node version">
9
9
  <a href="https://github.com/Ghqqqq/codex-overleaf-link/actions/workflows/test.yml"><img src="https://github.com/Ghqqqq/codex-overleaf-link/actions/workflows/test.yml/badge.svg" alt="tests"></a>
@@ -112,7 +112,7 @@ Install Codex Overleaf Link on this computer. Follow the install steps in https:
112
112
  ### Install with npm
113
113
 
114
114
  ```bash
115
- npm exec --yes codex-overleaf-link@2.6.0 -- install-managed
115
+ npm exec --yes codex-overleaf-link@2.6.5 -- install-managed
116
116
  ```
117
117
 
118
118
  The installer sets up the native host and a managed copy of the extension, then prints the extension's folder.
@@ -126,13 +126,13 @@ In `chrome://extensions`, turn on **Developer mode**, click **Load unpacked** an
126
126
 
127
127
  ```bash
128
128
  # macOS / Linux
129
- CODEX_OVERLEAF_REF=v2.6.0 bash -c "$(curl -fsSL https://raw.githubusercontent.com/Ghqqqq/codex-overleaf-link/v2.6.0/install.sh)"
129
+ CODEX_OVERLEAF_REF=v2.6.5 bash -c "$(curl -fsSL https://raw.githubusercontent.com/Ghqqqq/codex-overleaf-link/v2.6.5/install.sh)"
130
130
  ```
131
131
 
132
132
  ```powershell
133
133
  # Windows
134
- iwr https://raw.githubusercontent.com/Ghqqqq/codex-overleaf-link/v2.6.0/install.ps1 -OutFile install.ps1
135
- $env:CODEX_OVERLEAF_REF='v2.6.0'
134
+ iwr https://raw.githubusercontent.com/Ghqqqq/codex-overleaf-link/v2.6.5/install.ps1 -OutFile install.ps1
135
+ $env:CODEX_OVERLEAF_REF='v2.6.5'
136
136
  powershell -ExecutionPolicy Bypass -File install.ps1
137
137
  ```
138
138
 
@@ -210,10 +210,10 @@ Source checkouts and release-zip installs are unmanaged; update them by hand. Ea
210
210
 
211
211
  | Task | Command |
212
212
  |---|---|
213
- | Install, repair or migrate | `npm exec --yes codex-overleaf-link@2.6.0 -- install-managed` |
214
- | Diagnose | `npm exec --yes codex-overleaf-link@2.6.0 -- doctor` |
215
- | Uninstall | `npm exec --yes codex-overleaf-link@2.6.0 -- uninstall-managed` |
216
- | Native host only, for an unmanaged extension | `npm exec --yes codex-overleaf-link@2.6.0 -- install-native` / `uninstall-native` |
213
+ | Install, repair or migrate | `npm exec --yes codex-overleaf-link@2.6.5 -- install-managed` |
214
+ | Diagnose | `npm exec --yes codex-overleaf-link@2.6.5 -- doctor` |
215
+ | Uninstall | `npm exec --yes codex-overleaf-link@2.6.5 -- uninstall-managed` |
216
+ | Native host only, for an unmanaged extension | `npm exec --yes codex-overleaf-link@2.6.5 -- install-native` / `uninstall-native` |
217
217
 
218
218
  `uninstall-managed` removes the native host registration, the bridge, the managed extension and its runtimes. Your history, settings, mirrors, keys and skills stay. To remove everything, clear the history under **Settings → All projects → Local data → Clear local history…**, uninstall, remove the extension in `chrome://extensions`, and delete `~/.codex-overleaf` and the `~/Codex Overleaf Link Extension` shortcut (on Windows, `%USERPROFILE%\.codex-overleaf` and `%LOCALAPPDATA%\CodexOverleaf`).
219
219
 
package/README.zh-CN.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.6.0-blue" alt="version 2.6.0">
6
+ <img src="https://img.shields.io/badge/version-2.6.5-blue" alt="version 2.6.5">
7
7
  <img src="https://img.shields.io/badge/chrome-MV3-green" alt="chrome manifest v3">
8
8
  <img src="https://img.shields.io/badge/node-%3E%3D20-brightgreen" alt="node version">
9
9
  <a href="https://github.com/Ghqqqq/codex-overleaf-link/actions/workflows/test.yml"><img src="https://github.com/Ghqqqq/codex-overleaf-link/actions/workflows/test.yml/badge.svg" alt="tests"></a>
@@ -112,7 +112,7 @@ Codex Overleaf Link 在 Overleaf 编辑器旁边加了一个 Codex 面板。你
112
112
  ### 用 npm 安装
113
113
 
114
114
  ```bash
115
- npm exec --yes codex-overleaf-link@2.6.0 -- install-managed
115
+ npm exec --yes codex-overleaf-link@2.6.5 -- install-managed
116
116
  ```
117
117
 
118
118
  安装器会装好 native host 和一份托管的扩展,最后输出扩展所在的文件夹。
@@ -126,13 +126,13 @@ npm exec --yes codex-overleaf-link@2.6.0 -- install-managed
126
126
 
127
127
  ```bash
128
128
  # macOS / Linux
129
- CODEX_OVERLEAF_REF=v2.6.0 bash -c "$(curl -fsSL https://raw.githubusercontent.com/Ghqqqq/codex-overleaf-link/v2.6.0/install.sh)"
129
+ CODEX_OVERLEAF_REF=v2.6.5 bash -c "$(curl -fsSL https://raw.githubusercontent.com/Ghqqqq/codex-overleaf-link/v2.6.5/install.sh)"
130
130
  ```
131
131
 
132
132
  ```powershell
133
133
  # Windows
134
- iwr https://raw.githubusercontent.com/Ghqqqq/codex-overleaf-link/v2.6.0/install.ps1 -OutFile install.ps1
135
- $env:CODEX_OVERLEAF_REF='v2.6.0'
134
+ iwr https://raw.githubusercontent.com/Ghqqqq/codex-overleaf-link/v2.6.5/install.ps1 -OutFile install.ps1
135
+ $env:CODEX_OVERLEAF_REF='v2.6.5'
136
136
  powershell -ExecutionPolicy Bypass -File install.ps1
137
137
  ```
138
138
 
@@ -210,10 +210,10 @@ flowchart TD
210
210
 
211
211
  | 用途 | 命令 |
212
212
  |---|---|
213
- | 安装、修复或迁移 | `npm exec --yes codex-overleaf-link@2.6.0 -- install-managed` |
214
- | 诊断 | `npm exec --yes codex-overleaf-link@2.6.0 -- doctor` |
215
- | 卸载 | `npm exec --yes codex-overleaf-link@2.6.0 -- uninstall-managed` |
216
- | 只装 native host(扩展自行管理时) | `npm exec --yes codex-overleaf-link@2.6.0 -- install-native` / `uninstall-native` |
213
+ | 安装、修复或迁移 | `npm exec --yes codex-overleaf-link@2.6.5 -- install-managed` |
214
+ | 诊断 | `npm exec --yes codex-overleaf-link@2.6.5 -- doctor` |
215
+ | 卸载 | `npm exec --yes codex-overleaf-link@2.6.5 -- uninstall-managed` |
216
+ | 只装 native host(扩展自行管理时) | `npm exec --yes codex-overleaf-link@2.6.5 -- install-native` / `uninstall-native` |
217
217
 
218
218
  `uninstall-managed` 会删除 native host 注册、bridge、托管的扩展和运行时,历史、设置、镜像、密钥和技能都会保留。要彻底清理:先在**设置 → 所有项目 → 本地数据 → 清理本地历史…**中清掉历史,再卸载,然后在 `chrome://extensions` 里移除扩展,最后删除 `~/.codex-overleaf` 和 `~/Codex Overleaf Link Extension` 快捷方式(Windows 上是 `%USERPROFILE%\.codex-overleaf` 和 `%LOCALAPPDATA%\CodexOverleaf`)。
219
219
 
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "manifest_version": 3,
3
3
  "name": "Codex Overleaf Link",
4
- "version": "2.6.0",
4
+ "version": "2.6.5",
5
5
  "description": "Connect the active Overleaf project to a local Codex bridge.",
6
6
  "icons": {
7
7
  "16": "assets/icons/icon16.png",
@@ -66,6 +66,8 @@ if (!globalThis.CodexOverleafBackgroundUpdateSafety) {
66
66
  'use strict';
67
67
 
68
68
  const HOST_NAME = 'com.codex.overleaf';
69
+ const NATIVE_PING_TIMEOUT_MS = 20000;
70
+ const NATIVE_REQUEST_TIMEOUT_MS = 60000;
69
71
  const MANAGED_UPDATE_STATE_KEY = 'codex-overleaf-managed-update-state-v1';
70
72
  const MANAGED_UPDATE_CONSENT_KEY = 'codex-overleaf-update-consent-v1';
71
73
  const MANAGED_UPDATE_TABS_KEY = 'codex-overleaf-managed-update-tabs-v1';
@@ -398,11 +400,18 @@ if (!globalThis.CodexOverleafBackgroundUpdateSafety) {
398
400
  method: request.method,
399
401
  retryClass: getNativeRetryClass(request.method),
400
402
  retryCount: 0,
403
+ deadlineTimer: null,
401
404
  nativePort: null,
402
405
  finalResponseReceived: false,
403
406
  eventForwarded: false
404
407
  };
405
408
  pending.set(id, pendingRequest);
409
+ const timeoutMs = getNativeRequestTimeoutMs(request.method);
410
+ if (timeoutMs) {
411
+ pendingRequest.deadlineTimer = setTimeout(() => {
412
+ expireNativeRequest(id, pendingRequest);
413
+ }, timeoutMs);
414
+ }
406
415
  if (request.method === 'codex.run') {
407
416
  createRunJournal(id, pendingRequest);
408
417
  }
@@ -415,6 +424,64 @@ if (!globalThis.CodexOverleafBackgroundUpdateSafety) {
415
424
  });
416
425
  }
417
426
 
427
+ function getNativeRequestTimeoutMs(method) {
428
+ if (method === 'bridge.ping') return NATIVE_PING_TIMEOUT_MS;
429
+ switch (method) {
430
+ case 'mirror.status':
431
+ case 'codex.models':
432
+ case 'codex.providers.list':
433
+ case 'codex.providers.upsert':
434
+ case 'codex.providers.activate':
435
+ case 'codex.providers.clear-secret':
436
+ case 'codex.providers.delete':
437
+ case 'writing-style.get':
438
+ case 'writing-style.set':
439
+ case 'skills.list':
440
+ case 'asset.readChunk':
441
+ case 'update.status':
442
+ case 'update.check':
443
+ case 'update.canApply':
444
+ return NATIVE_REQUEST_TIMEOUT_MS;
445
+ default:
446
+ return 0;
447
+ }
448
+ }
449
+
450
+ function expireNativeRequest(id, pendingRequest) {
451
+ if (pending.get(id) !== pendingRequest || pendingRequest.finalResponseReceived) return;
452
+ pending.delete(id);
453
+ clearTimeout(pendingRequest.deadlineTimer);
454
+ pendingRequest.finalResponseReceived = true;
455
+ pendingRequest.resolve({
456
+ id,
457
+ ok: false,
458
+ error: {
459
+ code: 'native_request_timeout',
460
+ message: 'The local service did not respond.'
461
+ }
462
+ });
463
+ if (pendingRequest.method !== 'bridge.ping') return;
464
+ const failedPort = pendingRequest.nativePort;
465
+ if (!failedPort) return;
466
+ const hasActiveWork = Array.from(pending.values()).some(request => (
467
+ request.nativePort === failedPort && (
468
+ request.retryClass === 'safe_sync_retry' ||
469
+ (request.retryClass === 'no_silent_retry' && !getNativeRequestTimeoutMs(request.method))
470
+ )
471
+ ));
472
+ // A failed diagnostic ping must not abort execution or mirror writes.
473
+ if (hasActiveWork) return;
474
+ if (port === failedPort) port = null;
475
+ // A hung Host is retired without replaying requests or reconnecting until
476
+ // the next caller asks, only when no execution or mirror write owns it.
477
+ for (const [pendingId, interrupted] of Array.from(pending.entries())) {
478
+ if (interrupted.nativePort === failedPort) {
479
+ rejectInterruptedNativeRequest(pendingId, interrupted, 'The local service did not respond.');
480
+ }
481
+ }
482
+ try { failedPort.disconnect(); } catch (_error) { /* The port is already retired. */ }
483
+ }
484
+
418
485
  async function requestManagedInternal(payload) {
419
486
  if (managedBootstrapRuntime && ['update.status', 'update.confirm'].includes(payload?.method)) {
420
487
  payload = { ...payload, params: { ...payload.params,
@@ -840,6 +907,7 @@ if (!globalThis.CodexOverleafBackgroundUpdateSafety) {
840
907
  return;
841
908
  }
842
909
  pendingRequest.finalResponseReceived = true;
910
+ clearTimeout(pendingRequest.deadlineTimer);
843
911
  pending.delete(id);
844
912
  finalizeRunJournal(id, message).catch(() => {}).finally(() => {
845
913
  pendingRequest.resolve(message);
@@ -901,6 +969,7 @@ if (!globalThis.CodexOverleafBackgroundUpdateSafety) {
901
969
 
902
970
  function rejectInterruptedNativeRequest(pendingId, pendingRequest, errorMessage) {
903
971
  pending.delete(pendingId);
972
+ clearTimeout(pendingRequest.deadlineTimer);
904
973
  finalizeRunJournal(pendingId, {
905
974
  ok: false,
906
975
  error: {
@@ -937,6 +1006,7 @@ if (!globalThis.CodexOverleafBackgroundUpdateSafety) {
937
1006
  if (pending.size === 1) {
938
1007
  const [pendingId, pendingRequest] = pending.entries().next().value;
939
1008
  pendingRequest.finalResponseReceived = true;
1009
+ clearTimeout(pendingRequest.deadlineTimer);
940
1010
  pendingRequest.resolve({
941
1011
  ...message,
942
1012
  id: pendingId
@@ -15,7 +15,7 @@
15
15
  const maxPreviewDataUrlChars = Number(limits.maxPreviewDataUrlChars) || 768 * 1024;
16
16
 
17
17
  let attachments = [];
18
- const pendingAttachmentKeys = new Set();
18
+ const pendingAttachmentKeys = new Map();
19
19
  let pendingAttachmentBytes = 0;
20
20
  let attachmentGeneration = 0;
21
21
  let attachmentRevision = 0;
@@ -88,8 +88,15 @@
88
88
  continue;
89
89
  }
90
90
  attachmentRevision += 1;
91
- pendingAttachmentKeys.add(dedupeKey);
91
+ pendingAttachmentKeys.set(dedupeKey, {
92
+ name: normalizeAttachmentName(file.name),
93
+ mimeType: String(file.type || ''),
94
+ size: fileSize,
95
+ loading: true
96
+ });
92
97
  pendingAttachmentBytes += fileSize;
98
+ renderComposerAttachments();
99
+ onChange();
93
100
  try {
94
101
  const dataUrl = await readFileAsDataUrl(file, tx);
95
102
  if (generation !== attachmentGeneration) return;
@@ -118,6 +125,8 @@
118
125
  if (generation === attachmentGeneration) {
119
126
  pendingAttachmentBytes = Math.max(0, pendingAttachmentBytes - fileSize);
120
127
  pendingAttachmentKeys.delete(dedupeKey);
128
+ renderComposerAttachments();
129
+ onChange();
121
130
  }
122
131
  }
123
132
  }
@@ -155,8 +164,9 @@
155
164
  if (!strip) {
156
165
  return;
157
166
  }
158
- strip.hidden = attachments.length === 0;
159
- renderAttachmentPreviewList(attachments, strip, {
167
+ const visibleAttachments = [...attachments, ...pendingAttachmentKeys.values()];
168
+ strip.hidden = visibleAttachments.length === 0;
169
+ renderAttachmentPreviewList(visibleAttachments, strip, {
160
170
  root: getPanel() || document.body,
161
171
  tr,
162
172
  tx,
@@ -258,8 +268,8 @@
258
268
  }
259
269
 
260
270
  // v1.8.0 C4: survive a page refresh. Only small attachments persist
261
- // (single base64 <= 1MB, max 3) — oversized ones are dropped on restore
262
- // rather than bloating chrome.storage and every debounced saveState.
271
+ // (single base64 <= 1MB, max 3). Omitted names are saved separately so
272
+ // recovery can disclose files that need reattaching without saving bytes.
263
273
  const PERSIST_MAX_CONTENT_CHARS = 1024 * 1024;
264
274
  const PERSIST_MAX_COUNT = 3;
265
275
 
@@ -294,6 +304,15 @@
294
304
  return attachments.length;
295
305
  }
296
306
 
307
+ function getOmittedAttachmentNames() {
308
+ let persistedCount = 0;
309
+ return attachments.filter(attachment => {
310
+ if ((attachment.contentBase64 || '').length > PERSIST_MAX_CONTENT_CHARS || persistedCount >= PERSIST_MAX_COUNT) return true;
311
+ persistedCount += 1;
312
+ return false;
313
+ }).map(attachment => attachment.name);
314
+ }
315
+
297
316
  // Only a just-rejected local-skill submission uses this in-memory recovery.
298
317
  // It keeps the normal composer limits, without durable-draft truncation.
299
318
  function restoreSubmittedAttachments(value, expectedRevision) {
@@ -315,6 +334,8 @@
315
334
 
316
335
  return {
317
336
  serializePersistableAttachments,
337
+ getOmittedAttachmentNames,
338
+ hasPendingReads: () => pendingAttachmentKeys.size > 0,
318
339
  restorePersistedAttachments,
319
340
  restoreSubmittedAttachments,
320
341
  getRevision: () => attachmentRevision,
@@ -428,6 +449,10 @@
428
449
  const card = document.createElement('div');
429
450
  card.className = 'codex-attachment-preview-card';
430
451
  card.dataset.kind = isImage ? 'image' : 'file';
452
+ if (attachment.loading) {
453
+ card.dataset.loading = 'true';
454
+ card.setAttribute('aria-busy', 'true');
455
+ }
431
456
  card.title = label + ' · ' + formatFileSize(attachment.size);
432
457
  if (isImage) {
433
458
  const preview = document.createElement('button');
@@ -456,11 +481,13 @@
456
481
  if (!isImage) {
457
482
  const size = document.createElement('span');
458
483
  size.className = 'codex-attachment-preview-size';
459
- size.textContent = formatFileSize(attachment.size);
484
+ size.textContent = attachment.loading
485
+ ? (options.tx ? options.tx('Loading…', '读取中…') : 'Loading…')
486
+ : formatFileSize(attachment.size);
460
487
  meta.append(size);
461
488
  }
462
489
  card.append(meta);
463
- if (options.removable) {
490
+ if (options.removable && !attachment.loading) {
464
491
  const remove = document.createElement('button');
465
492
  remove.type = 'button';
466
493
  remove.className = 'codex-attachment-preview-remove';