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
@@ -403,6 +403,16 @@
403
403
  vertical-align: top;
404
404
  }
405
405
 
406
+ #codex-overleaf-panel :is(.run-stream-text, .run-final-answer) .run-markdown-table :is(th, td) {
407
+ overflow-wrap: break-word;
408
+ min-width: 4em;
409
+ }
410
+
411
+ #codex-overleaf-panel :is(.run-stream-text, .run-final-answer) .run-markdown-table .codex-line-reference {
412
+ overflow-wrap: normal;
413
+ white-space: nowrap;
414
+ }
415
+
406
416
  #codex-overleaf-panel :is(.run-stream-text, .run-final-answer) .run-markdown-table th {
407
417
  background: var(--tl-surface-2);
408
418
  color: var(--tl-fg-1);
@@ -1813,7 +1823,6 @@
1813
1823
  #codex-overleaf-panel .codex-diagnostics-checks {
1814
1824
  display: grid;
1815
1825
  gap: 0;
1816
- margin-top: 4px;
1817
1826
  }
1818
1827
 
1819
1828
  #codex-overleaf-panel .codex-diagnostics-check {
@@ -1827,7 +1836,7 @@
1827
1836
 
1828
1837
  #codex-overleaf-panel .codex-diagnostics-check > summary {
1829
1838
  display: grid;
1830
- grid-template-columns: 16px minmax(0, 1fr) 10px;
1839
+ grid-template-columns: 8px minmax(0, 1fr) 10px;
1831
1840
  gap: 8px;
1832
1841
  align-items: center;
1833
1842
  padding: 10px 0;
@@ -1852,37 +1861,39 @@
1852
1861
  transform: rotate(45deg);
1853
1862
  }
1854
1863
 
1855
- #codex-overleaf-panel .codex-diagnostics-check-text {
1856
- margin: 0 0 10px 24px;
1864
+ #codex-overleaf-panel .codex-diagnostics-check-dot {
1865
+ width: 8px;
1866
+ height: 8px;
1867
+ border-radius: 50%;
1868
+ background: var(--tl-fg-4);
1857
1869
  }
1858
1870
 
1859
- #codex-overleaf-panel .codex-diagnostics-check-glyph::before {
1860
- content: "·";
1861
- color: var(--tl-fg-4);
1862
- font-weight: 700;
1863
- }
1871
+ #codex-overleaf-panel .codex-diagnostics-check[data-status="ok"] .codex-diagnostics-check-dot { background: var(--tl-ok); }
1872
+ #codex-overleaf-panel .codex-diagnostics-check[data-status="warn"] .codex-diagnostics-check-dot { background: var(--tl-review); }
1873
+ #codex-overleaf-panel .codex-diagnostics-check[data-status="fail"] .codex-diagnostics-check-dot { background: var(--tl-fail); }
1864
1874
 
1865
- #codex-overleaf-panel .codex-diagnostics-check[data-status="ok"] .codex-diagnostics-check-glyph::before { content: "✓"; color: var(--tl-ok); }
1866
- #codex-overleaf-panel .codex-diagnostics-check[data-status="warn"] .codex-diagnostics-check-glyph::before { content: "!"; color: var(--tl-review); }
1867
- #codex-overleaf-panel .codex-diagnostics-check[data-status="fail"] .codex-diagnostics-check-glyph::before { content: "✕"; color: var(--tl-fail); }
1875
+ #codex-overleaf-panel .codex-diagnostics-check-text {
1876
+ display: grid;
1877
+ gap: 6px;
1878
+ margin: 0 0 10px 16px;
1879
+ }
1868
1880
 
1869
1881
  #codex-overleaf-panel .codex-diagnostics-check-title {
1870
1882
  color: var(--tl-fg-1);
1871
1883
  font-size: var(--tl-text-sm, 12px);
1872
- font-weight: 600;
1884
+ font-weight: 500;
1873
1885
  }
1874
1886
 
1875
1887
  #codex-overleaf-panel .codex-diagnostics-check-summary {
1876
- color: var(--tl-fg-3);
1877
- font-size: var(--tl-text-xs, 11px);
1878
- line-height: 1.4;
1888
+ color: var(--tl-fg-2);
1889
+ font-size: var(--tl-text-sm, 12px);
1890
+ line-height: 1.5;
1879
1891
  }
1880
1892
 
1881
1893
  #codex-overleaf-panel .codex-diagnostics-check-next {
1882
- color: var(--tl-accent);
1883
- font-size: var(--tl-text-xs, 11px);
1884
- line-height: 1.4;
1885
- margin-top: 2px;
1894
+ color: var(--tl-fg-2);
1895
+ font-size: var(--tl-text-sm, 12px);
1896
+ line-height: 1.5;
1886
1897
  }
1887
1898
 
1888
1899
  #codex-overleaf-panel .codex-toast-region {
@@ -1975,7 +1986,7 @@
1975
1986
  border: 1px solid var(--tl-border-strong);
1976
1987
  border-radius: 6px;
1977
1988
  background: var(--tl-surface-2);
1978
- box-shadow: 0 8px 22px rgba(0, 0, 0, 0.38);
1989
+ box-shadow: var(--tl-shadow-pop);
1979
1990
  padding: 6px;
1980
1991
  }
1981
1992
 
@@ -2042,7 +2053,7 @@
2042
2053
  border: 1px solid var(--tl-border-strong);
2043
2054
  border-radius: 8px;
2044
2055
  background: var(--tl-surface-1);
2045
- box-shadow: 0 14px 34px rgba(0, 0, 0, 0.48);
2056
+ box-shadow: var(--tl-shadow-pop);
2046
2057
  }
2047
2058
 
2048
2059
  #codex-overleaf-panel .codex-diagnostics-menu::backdrop,
@@ -2059,14 +2070,6 @@
2059
2070
  border-color: var(--tl-border-strong);
2060
2071
  }
2061
2072
 
2062
- #codex-overleaf-panel .codex-diagnostics-result[data-status="warning"] {
2063
- border-color: var(--tl-review);
2064
- }
2065
-
2066
- #codex-overleaf-panel .codex-diagnostics-result[data-status="failed"] {
2067
- border-color: var(--tl-fail);
2068
- }
2069
-
2070
2073
  /* Only embedded settings replace the session view. The modal workbench keeps
2071
2074
  the complete sidebar behind its backdrop, without reflowing the conversation. */
2072
2075
  #codex-overleaf-panel:not([data-settings-workbench="true"])[data-view="settings"] .codex-vscode-head,
@@ -2663,13 +2666,44 @@
2663
2666
  }
2664
2667
 
2665
2668
  #codex-overleaf-panel .codex-diagnostics-result-title {
2669
+ display: flex;
2670
+ align-items: flex-start;
2671
+ gap: 8px;
2666
2672
  color: var(--tl-fg-1);
2667
2673
  font-size: var(--tl-text-base, 14px);
2668
2674
  font-weight: 650;
2669
2675
  line-height: 1.35;
2670
2676
  }
2671
2677
 
2678
+ #codex-overleaf-panel .codex-diagnostics-status-dot {
2679
+ flex: 0 0 8px;
2680
+ width: 8px;
2681
+ height: 8px;
2682
+ margin-top: calc((1.35em - 8px) / 2);
2683
+ border-radius: 50%;
2684
+ background: var(--tl-fg-4);
2685
+ }
2686
+
2687
+ #codex-overleaf-panel .codex-diagnostics-result[data-status="completed"] .codex-diagnostics-status-dot { background: var(--tl-ok); }
2688
+ #codex-overleaf-panel .codex-diagnostics-result[data-status="warning"] .codex-diagnostics-status-dot { background: var(--tl-review); }
2689
+ #codex-overleaf-panel .codex-diagnostics-result[data-status="failed"] .codex-diagnostics-status-dot { background: var(--tl-fail); }
2690
+ #codex-overleaf-panel .codex-diagnostics-result[data-status="running"] .codex-diagnostics-status-dot { animation: codex-diagnostics-pulse 1.2s ease-in-out infinite; }
2691
+
2692
+ @keyframes codex-diagnostics-pulse {
2693
+ 50% {
2694
+ opacity: 0.3;
2695
+ }
2696
+ }
2697
+
2698
+ @media (prefers-reduced-motion: reduce) {
2699
+ #codex-overleaf-panel .codex-diagnostics-result[data-status="running"] .codex-diagnostics-status-dot {
2700
+ animation: none;
2701
+ }
2702
+ }
2703
+
2672
2704
  #codex-overleaf-panel .codex-diagnostics-result-subtitle {
2705
+ text-wrap: pretty;
2706
+ margin-left: 16px;
2673
2707
  margin-top: 2px;
2674
2708
  color: var(--tl-fg-3);
2675
2709
  font-size: var(--tl-text-xs, 11px);
@@ -2707,69 +2741,127 @@
2707
2741
  }
2708
2742
 
2709
2743
  #codex-overleaf-panel .codex-diagnostics-result-body {
2710
- padding: 10px 12px 12px;
2744
+ display: grid;
2745
+ gap: 12px;
2746
+ padding: 12px;
2711
2747
  color: var(--tl-fg-2);
2712
2748
  font-size: var(--tl-text-sm, 12px);
2713
- line-height: 1.45;
2749
+ line-height: 1.5;
2750
+ text-wrap: pretty;
2714
2751
  }
2715
2752
 
2716
- #codex-overleaf-panel .codex-diagnostics-result-body p {
2717
- margin: 0 0 8px;
2753
+ #codex-overleaf-panel .codex-diagnostics-result-body > * {
2754
+ margin: 0;
2718
2755
  }
2719
2756
 
2720
- #codex-overleaf-panel .codex-diagnostics-result-body ul {
2721
- margin: 8px 0;
2722
- padding-left: 18px;
2757
+ #codex-overleaf-panel .codex-diagnostics-result[data-status="running"] .codex-diagnostics-summary { color: var(--tl-fg-3); }
2758
+
2759
+ #codex-overleaf-panel .codex-diagnostics-facts {
2760
+ display: grid;
2761
+ grid-template-columns: fit-content(40%) minmax(0, 1fr);
2762
+ column-gap: 16px;
2763
+ row-gap: 6px;
2723
2764
  }
2724
2765
 
2725
- #codex-overleaf-panel .codex-diagnostics-result-body li {
2726
- margin: 4px 0;
2766
+ #codex-overleaf-panel .codex-diagnostics-facts dt {
2767
+ color: var(--tl-fg-3);
2768
+ overflow-wrap: normal;
2769
+ }
2770
+
2771
+ #codex-overleaf-panel .codex-diagnostics-facts dd {
2772
+ margin: 0;
2773
+ min-width: 0;
2774
+ color: var(--tl-fg-1);
2775
+ }
2776
+
2777
+ #codex-overleaf-panel .codex-diagnostics-facts dd[data-tone="warn"] { color: var(--tl-review); }
2778
+ #codex-overleaf-panel .codex-diagnostics-facts dd[data-tone="fail"] { color: var(--tl-fail); }
2779
+
2780
+ #codex-overleaf-panel .codex-diagnostics-notes {
2781
+ display: grid;
2782
+ gap: 4px;
2783
+ padding: 0;
2784
+ list-style: none;
2785
+ }
2786
+
2787
+ #codex-overleaf-panel .codex-diagnostics-notes li {
2788
+ position: relative;
2789
+ padding-left: 12px;
2790
+ }
2791
+
2792
+ #codex-overleaf-panel .codex-diagnostics-notes li::before {
2793
+ content: "";
2794
+ position: absolute;
2795
+ left: 2px;
2796
+ top: calc(0.75em - 2px);
2797
+ width: 4px;
2798
+ height: 4px;
2799
+ border-radius: 50%;
2800
+ background: var(--tl-fg-4);
2727
2801
  }
2728
2802
 
2729
2803
  #codex-overleaf-panel .codex-diagnostics-next-step {
2730
- color: var(--tl-accent);
2804
+ color: var(--tl-fg-2);
2805
+ }
2806
+
2807
+ #codex-overleaf-panel .codex-diagnostics-next-label {
2808
+ color: var(--tl-fg-1);
2809
+ font-weight: 600;
2731
2810
  }
2732
2811
 
2733
2812
  #codex-overleaf-panel .codex-install-command {
2734
- margin-top: 10px;
2735
- padding: 9px;
2736
- border: 1px solid var(--tl-border-strong);
2737
- border-radius: 7px;
2738
- background: var(--tl-surface-0);
2813
+ display: grid;
2814
+ gap: 6px;
2739
2815
  }
2740
2816
 
2741
- #codex-overleaf-panel .codex-diagnostics-result[data-status="failed"] .codex-install-command {
2742
- border-color: var(--tl-review);
2817
+ #codex-overleaf-panel .codex-install-command-head {
2818
+ display: flex;
2819
+ align-items: center;
2820
+ justify-content: space-between;
2821
+ gap: 8px;
2743
2822
  }
2744
2823
 
2745
2824
  #codex-overleaf-panel .codex-install-command-label {
2746
- margin-bottom: 6px;
2747
2825
  color: var(--tl-fg-3);
2748
2826
  font-size: var(--tl-text-xs, 11px);
2749
2827
  }
2750
2828
 
2829
+ #codex-overleaf-panel .codex-install-command button {
2830
+ flex: 0 0 auto;
2831
+ width: auto;
2832
+ height: 22px;
2833
+ margin: 0;
2834
+ padding: 0 8px;
2835
+ border: 1px solid var(--tl-border-strong);
2836
+ border-radius: 5px;
2837
+ background: transparent;
2838
+ color: var(--tl-fg-1);
2839
+ font-size: var(--tl-text-xs, 11px);
2840
+ line-height: 20px;
2841
+ cursor: pointer;
2842
+ }
2843
+
2844
+ #codex-overleaf-panel .codex-install-command button:hover {
2845
+ background: var(--tl-hover);
2846
+ }
2847
+
2848
+ #codex-overleaf-panel .codex-install-command button:focus-visible {
2849
+ outline: 2px solid var(--tl-accent);
2850
+ outline-offset: 2px;
2851
+ }
2852
+
2751
2853
  #codex-overleaf-panel .codex-install-command code {
2752
2854
  display: block;
2753
2855
  overflow: auto;
2754
2856
  max-height: 90px;
2755
- padding: 7px;
2857
+ padding: 8px 10px;
2756
2858
  border-radius: 6px;
2757
2859
  background: var(--tl-surface-2);
2758
- color: var(--tl-accent);
2860
+ color: var(--tl-fg-1);
2759
2861
  white-space: pre-wrap;
2760
- word-break: break-word;
2761
- font: var(--tl-text-xs, 11px)/1.45 ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
2762
- }
2763
-
2764
- #codex-overleaf-panel .codex-install-command button {
2765
- width: 100%;
2766
- margin-top: 8px;
2767
- border: 1px solid var(--tl-ok);
2768
- border-radius: 6px;
2769
- background: var(--tl-ok-wash);
2770
- color: var(--tl-ok);
2771
- padding: 6px 8px;
2772
- font-size: var(--tl-text-sm, 12px);
2862
+ word-break: normal;
2863
+ overflow-wrap: anywhere;
2864
+ font: var(--tl-text-xs, 11px)/1.5 ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
2773
2865
  }
2774
2866
 
2775
2867
  #codex-overleaf-panel .codex-diagnostics-technical {
@@ -2782,11 +2874,41 @@
2782
2874
  }
2783
2875
 
2784
2876
  #codex-overleaf-panel .codex-diagnostics-technical summary {
2877
+ display: inline-flex;
2878
+ align-items: center;
2879
+ gap: 6px;
2785
2880
  cursor: pointer;
2881
+ list-style: none;
2786
2882
  color: var(--tl-fg-3);
2787
2883
  font-size: var(--tl-text-xs, 11px);
2788
2884
  }
2789
2885
 
2886
+ #codex-overleaf-panel .codex-diagnostics-technical summary::-webkit-details-marker {
2887
+ display: none;
2888
+ }
2889
+
2890
+ #codex-overleaf-panel .codex-diagnostics-technical summary::after {
2891
+ content: "";
2892
+ width: 5px;
2893
+ height: 5px;
2894
+ border-right: 1px solid currentColor;
2895
+ border-bottom: 1px solid currentColor;
2896
+ transform: rotate(-45deg);
2897
+ }
2898
+
2899
+ #codex-overleaf-panel .codex-diagnostics-technical[open] summary::after {
2900
+ transform: rotate(45deg);
2901
+ }
2902
+
2903
+ #codex-overleaf-panel .codex-diagnostics-technical summary:hover {
2904
+ color: var(--tl-fg-1);
2905
+ }
2906
+
2907
+ #codex-overleaf-panel .codex-diagnostics-check .codex-diagnostics-technical {
2908
+ border-top: 0;
2909
+ padding: 0;
2910
+ }
2911
+
2790
2912
  #codex-overleaf-panel .codex-diagnostics-technical pre {
2791
2913
  overflow: auto;
2792
2914
  max-height: 220px;
@@ -3112,6 +3234,7 @@
3112
3234
  dark/light/auto). The whole panel reads these tokens so a theme is just a
3113
3235
  value swap — no selector depends on the literals. */
3114
3236
  #codex-overleaf-panel {
3237
+ --tl-shadow-pop: 0 14px 34px rgba(0, 0, 0, 0.48);
3115
3238
  --tl-fg-1: #e4e4e4; /* primary prose / answer / strong labels */
3116
3239
  --tl-fg-2: #b7b7b7; /* body text / activity titles / controls */
3117
3240
  --tl-fg-3: #8a8a8a; /* meta / summaries / captions */
@@ -3157,6 +3280,7 @@
3157
3280
  light via prefers-color-scheme). Dark needs no override (it is the baseline
3158
3281
  above). State colors are darkened for AA contrast on light surfaces. */
3159
3282
  #codex-overleaf-panel[data-theme="light"] {
3283
+ --tl-shadow-pop: 0 12px 32px rgba(31, 35, 40, 0.14), 0 2px 6px rgba(31, 35, 40, 0.08);
3160
3284
  --tl-fg-1: #1f2328;
3161
3285
  --tl-fg-2: #3d4651;
3162
3286
  --tl-fg-3: #6a7280;
@@ -5123,6 +5247,10 @@
5123
5247
  gap: 8px;
5124
5248
  padding: 10px 24px 10px 10px;
5125
5249
  }
5250
+ #codex-overleaf-panel .codex-attachment-preview-card[data-loading="true"] {
5251
+ border-style: dashed;
5252
+ background: var(--tl-surface-1);
5253
+ }
5126
5254
  #codex-overleaf-panel .codex-attachment-file-icon {
5127
5255
  display: grid;
5128
5256
  place-items: center;
@@ -102,6 +102,7 @@ async function runCodexSession({ params = {}, env = process.env, emit = () => {}
102
102
 
103
103
  mirror = await syncOverleafToMirror({
104
104
  projectId,
105
+ evidenceProjectId: params.projectId,
105
106
  project: params.project || { files: [] },
106
107
  rootDir
107
108
  });
@@ -8,6 +8,7 @@ const {
8
8
  } = require('./nativeHostPlatform');
9
9
 
10
10
  const MAX_FIELD_LENGTH = 8000;
11
+ const MAX_DEBUG_LOG_BYTES = 8 * 1024 * 1024;
11
12
 
12
13
  function getDebugLogDir(options = {}) {
13
14
  return getCodexOverleafHome(options);
@@ -22,12 +23,35 @@ function logDebug(event, detail = {}, options = {}) {
22
23
  try {
23
24
  const logDir = getDebugLogDir(options);
24
25
  const logPath = getDebugLogPath(options);
25
- fs.mkdirSync(logDir, { recursive: true });
26
- fs.appendFileSync(logPath, `${JSON.stringify({
26
+ const record = `${JSON.stringify({
27
27
  time: new Date().toISOString(),
28
28
  event,
29
29
  detail: sanitize(detail)
30
- })}\n`, 'utf8');
30
+ })}\n`;
31
+ const recordBytes = Buffer.byteLength(record, 'utf8');
32
+ if (recordBytes > MAX_DEBUG_LOG_BYTES) return;
33
+ fs.mkdirSync(logDir, { recursive: true });
34
+ try {
35
+ const size = fs.statSync(logPath).size;
36
+ if (size > MAX_DEBUG_LOG_BYTES) {
37
+ // Discard oversized legacy logs instead of retaining a huge .1 copy.
38
+ fs.truncateSync(logPath, 0);
39
+ } else if (size + recordBytes > MAX_DEBUG_LOG_BYTES) {
40
+ fs.renameSync(logPath, `${logPath}.1`);
41
+ }
42
+ } catch (error) {
43
+ if (error.code !== 'ENOENT') throw error;
44
+ }
45
+ fs.appendFileSync(logPath, record, 'utf8');
46
+ // Concurrent Hosts can cross the cap between stat and append/rename.
47
+ // Logging is best-effort: drop an oversized raced file rather than grow it.
48
+ for (const file of [logPath, `${logPath}.1`]) {
49
+ try {
50
+ if (fs.statSync(file).size > MAX_DEBUG_LOG_BYTES) fs.truncateSync(file, 0);
51
+ } catch (error) {
52
+ if (error.code !== 'ENOENT') throw error;
53
+ }
54
+ }
31
55
  } catch {
32
56
  // Debug logging must never break the native messaging host.
33
57
  }
@@ -11,6 +11,8 @@ const { buildNativeRuntimeEnv, summarizeNativeEnvironment } = require('./nativeE
11
11
  let buffered = Buffer.alloc(0);
12
12
  let stdoutUnavailable = false;
13
13
  let nativeDisconnectHandled = false;
14
+ let fatalShutdownStarted = false;
15
+ const FATAL_SHUTDOWN_TIMEOUT_MS = 3000;
14
16
  const runtimeEnv = buildNativeRuntimeEnv(process.env);
15
17
  Object.assign(process.env, runtimeEnv);
16
18
  logDebug('environment.ready', summarizeNativeEnvironment(runtimeEnv));
@@ -93,21 +95,46 @@ process.on('uncaughtException', error => {
93
95
  handleNativeDisconnect('uncaught_native_disconnect', error);
94
96
  return;
95
97
  }
96
- logDebug('process.uncaught_exception', {
97
- message: error.message,
98
- stack: error.stack
99
- });
100
- process.exit(1);
98
+ handleFatalError('process.uncaught_exception', error);
101
99
  });
102
100
 
103
101
  process.on('unhandledRejection', reason => {
104
- logDebug('process.unhandled_rejection', {
105
- message: reason?.message || String(reason),
106
- stack: reason?.stack
107
- });
108
- process.exit(1);
102
+ handleFatalError('process.unhandled_rejection', reason);
109
103
  });
110
104
 
105
+ function handleFatalError(event, error) {
106
+ if (fatalShutdownStarted) {
107
+ process.exit(1);
108
+ return;
109
+ }
110
+ fatalShutdownStarted = true;
111
+ nativeDisconnectHandled = true;
112
+ stdoutUnavailable = true;
113
+ process.exitCode = 1;
114
+ logDebug(event, { message: error?.message || String(error), stack: error?.stack });
115
+ const exitTimer = setTimeout(() => {
116
+ const failures = quarantineActiveOperations();
117
+ logDebug('process.fatal_shutdown_deadline', { persistenceFailures: failures });
118
+ process.exit(1);
119
+ }, FATAL_SHUTDOWN_TIMEOUT_MS);
120
+ try {
121
+ // Persist protection before aborting; stop confirmation owns its removal.
122
+ quarantineActiveOperations();
123
+ Promise.resolve(abortAllActiveOperations('Native Host stopped after a fatal error.')).then(() => {
124
+ clearTimeout(exitTimer);
125
+ process.exit(1);
126
+ }, failure => {
127
+ logDebug('process.fatal_shutdown_failed', { message: failure?.message });
128
+ clearTimeout(exitTimer);
129
+ process.exit(1);
130
+ });
131
+ } catch (failure) {
132
+ logDebug('process.fatal_shutdown_failed', { message: failure?.message });
133
+ clearTimeout(exitTimer);
134
+ process.exit(1);
135
+ }
136
+ }
137
+
111
138
  function writeResponse(response) {
112
139
  if (stdoutUnavailable || process.stdout.destroyed || process.stdout.writableEnded) {
113
140
  logDebug('stdout.write_skipped', { ok: response?.ok, code: response?.error?.code });
@@ -152,6 +179,7 @@ function handleNativeDisconnect(source, error) {
152
179
  // old owned process is still inside its interrupt/termination budget.
153
180
  quarantineActiveOperations();
154
181
  const exitTimer = setTimeout(() => {
182
+ if (fatalShutdownStarted) return;
155
183
  const failures = quarantineActiveOperations();
156
184
  logDebug('native_transport.shutdown_deadline', { persistenceFailures: failures });
157
185
  process.exit(failures.length ? 1 : 0);
@@ -160,7 +188,10 @@ function handleNativeDisconnect(source, error) {
160
188
  Promise.all([
161
189
  abortAllActiveOperations('Native messaging transport disconnected.'),
162
190
  new Promise(resolve => setTimeout(resolve, 1500))
163
- ]).then(() => { clearTimeout(exitTimer); process.exit(0); });
191
+ ]).then(() => {
192
+ clearTimeout(exitTimer);
193
+ if (!fatalShutdownStarted) process.exit(0);
194
+ });
164
195
  }
165
196
 
166
197
  function summarizeRequest(message) {
@@ -13,7 +13,7 @@ const MAX_SKILL_CONTENT_CHARS = MAX_SKILL_CONTENT_BYTES;
13
13
  const MAX_SKILL_PREVIEW_CHARS = 240;
14
14
  const PROJECT_SKILL_SCOPE = 'project';
15
15
  const CODEX_OVERLEAF_SKILL_SCOPE = 'codex-overleaf';
16
- const OFFICIAL_CODEX_OVERLEAF_SKILL_IDS = ['annotated-rewrite', 'parallel-subagents'];
16
+ const OFFICIAL_CODEX_OVERLEAF_SKILL_IDS = ['annotated-rewrite', 'parallel-subagents', 'reviewer-response'];
17
17
 
18
18
  function listProjectSkills({ projectId, rootDir } = {}) {
19
19
  const skillsDir = getProjectSkillsDir(projectId, { rootDir });
@@ -5,6 +5,7 @@ const fs = require('node:fs');
5
5
  const path = require('node:path');
6
6
  const { getHomeDir } = require('./nativeHostPlatform');
7
7
  const { assertProjectPathsForPlatform } = require('./projectPathPlatform');
8
+ const { assessDocumentProvenance } = require('../../extension/src/shared/staleGuard');
8
9
 
9
10
  const BASELINE_FILE = 'baseline.json';
10
11
  const MAX_BINARY_FILE_BYTES = 10 * 1024 * 1024;
@@ -34,11 +35,20 @@ function getDefaultMirrorRoot(options = {}) {
34
35
  return path.join(getHomeDir(options), '.codex-overleaf', 'projects');
35
36
  }
36
37
 
37
- async function syncOverleafToMirror({ projectId, project, rootDir }) {
38
+ async function syncOverleafToMirror({ projectId, project, rootDir, evidenceProjectId }) {
38
39
  const mirror = getProjectMirror(projectId, { rootDir });
39
40
  const normalized = normalizeProjectFilesDetailed(project?.files || []);
40
41
  const files = normalized.files;
41
42
  const previous = readBaseline(mirror.baselinePath);
43
+ // Page already admits evidence-free snapshot text against its own saved
44
+ // references, which may no longer be present after editor/writeback merging.
45
+ for (const file of project?.files || []) {
46
+ if (file.kind === 'binary' || typeof file.content !== 'string') continue;
47
+ if (file.documentIdentity !== undefined) {
48
+ assertDocumentProvenance(file.path, file.content, undefined, file.documentIdentity,
49
+ project?.id !== undefined && project.id !== evidenceProjectId ? undefined : evidenceProjectId);
50
+ }
51
+ }
42
52
  const fullProjectSnapshot = project?.capabilities?.fullProjectSnapshot !== false;
43
53
  const skippedFiles = resolveUnreadSnapshotFiles(project, normalized, previous, fullProjectSnapshot);
44
54
  // Runtime platform is authoritative; request fields cannot opt out of this check.
@@ -808,11 +818,20 @@ function buildOtStatusFields(baseline = {}, trusted) {
808
818
  };
809
819
  }
810
820
 
811
- async function applyFileOverlays({ projectId, overlays, rootDir }) {
821
+ async function applyFileOverlays({ projectId, overlays, rootDir, evidenceProjectId }) {
812
822
  const mirror = getProjectMirror(projectId, { rootDir });
813
823
  const baseline = readBaseline(mirror.baselinePath);
814
824
  const filesByPath = new Map((baseline.files || []).map(f => [f.path, f]));
815
825
  const unreadPaths = new Set((baseline.unreadFiles || []).map(file => file.path));
826
+ const references = baselineTextReferences(baseline);
827
+ // Validate the entire request against the unchanged baseline before any file
828
+ // or baseline write. An earlier overlay cannot hide a later misattribution.
829
+ for (const overlay of overlays || []) {
830
+ if (!overlay?.path || typeof overlay.content !== 'string') continue;
831
+ const normalizedPath = normalizeRelativePath(overlay.path);
832
+ if (!unreadPaths.has(normalizedPath))
833
+ assertDocumentProvenance(normalizedPath, overlay.content, references, overlay.documentIdentity, evidenceProjectId);
834
+ }
816
835
 
817
836
  for (const overlay of overlays || []) {
818
837
  if (!overlay?.path || typeof overlay.content !== 'string') {
@@ -828,7 +847,7 @@ async function applyFileOverlays({ projectId, overlays, rootDir }) {
828
847
  filesByPath.set(normalizedPath, {
829
848
  path: normalizedPath,
830
849
  kind: 'text',
831
- hash: overlay.hash || hashText(overlay.content),
850
+ hash: hashText(overlay.content),
832
851
  content: overlay.content
833
852
  });
834
853
  }
@@ -840,6 +859,24 @@ async function applyFileOverlays({ projectId, overlays, rootDir }) {
840
859
  });
841
860
  }
842
861
 
862
+ function baselineTextReferences(baseline) {
863
+ return new Map((baseline.files || []).filter(file => file.kind === 'text' && typeof file.content === 'string')
864
+ .map(file => [file.path, file.content]));
865
+ }
866
+
867
+ function assertDocumentProvenance(path, text, references, documentIdentity, projectId) {
868
+ const result = documentIdentity !== undefined
869
+ && (!documentIdentity || documentIdentity.contentSha256 !== hashText(text))
870
+ ? { ok: false, code: 'document_identity_mismatch' }
871
+ : assessDocumentProvenance({ path, text, references, documentIdentity, projectId });
872
+ if (result.ok) return;
873
+ const error = new Error('Codex could not confirm that the editor content belongs to the target file, so it was not used.');
874
+ error.code = result.code;
875
+ error.path = path;
876
+ error.matchedPath = result.matchedPath;
877
+ throw error;
878
+ }
879
+
843
880
  // v1.8.0: after a VERIFIED writeback, the workspace already holds the exact
844
881
  // content that landed in Overleaf only while its hashes match the page's
845
882
  // verified post-images. Check every target before updating any baseline.
@@ -932,6 +969,13 @@ async function patchMirrorFiles({ projectId, files, rootDir, source = 'ot' }) {
932
969
  let lastOtPatchAt = baseline.lastOtPatchAt || '';
933
970
  let lastOtErrorCode = '';
934
971
  let patchBatchAt = '';
972
+ const references = baselineTextReferences(baseline);
973
+ for (const patch of Array.isArray(files) ? files : []) {
974
+ const normalized = normalizePatchPath(patch, mirror.workspacePath);
975
+ if (normalized.ok && baselineByPath.get(normalized.path)?.kind === 'text' && typeof patch?.nextContent === 'string') {
976
+ assertDocumentProvenance(normalized.path, patch.nextContent, references);
977
+ }
978
+ }
935
979
 
936
980
  for (const patch of Array.isArray(files) ? files : []) {
937
981
  const normalized = normalizePatchPath(patch, mirror.workspacePath);