codex-overleaf-link 2.3.1 → 2.3.3

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 (39) hide show
  1. package/README.md +23 -23
  2. package/extension/bootstrap/manifest.template.json +1 -1
  3. package/extension/src/content/assetTransferBroker.js +169 -0
  4. package/extension/src/content/contentRuntime.js +28 -1
  5. package/extension/src/content/generated/content.bundle.js +630 -68
  6. package/extension/src/content/generated/content.bundle.meta.json +6 -3
  7. package/extension/src/content/legacyGlobalRegistry.js +3 -0
  8. package/extension/src/content/markdownDomRenderer.js +70 -0
  9. package/extension/src/content/modelPicker.js +2 -2
  10. package/extension/src/content/modelPickerSupport.js +3 -3
  11. package/extension/src/content/pageBridgeClient.js +1 -0
  12. package/extension/src/content/pendingInputView.js +16 -3
  13. package/extension/src/content/providerSettingsDialog.js +1 -0
  14. package/extension/src/content/runResultActions.js +114 -0
  15. package/extension/src/content/runTimelineView.js +20 -0
  16. package/extension/src/content/sessionForkController.js +120 -0
  17. package/extension/src/content/writebackController.js +3 -0
  18. package/extension/src/content/writebackOrchestrator.js +3 -1
  19. package/extension/src/page/binaryAssetUploader.js +343 -0
  20. package/extension/src/page/trackedChangesLifecycle.js +2 -0
  21. package/extension/src/pageBridge.js +30 -1
  22. package/extension/src/shared/compatibility.js +4 -2
  23. package/extension/src/shared/i18n.js +16 -0
  24. package/extension/src/shared/models.js +13 -2
  25. package/extension/src/shared/pageRpcContract.js +5 -1
  26. package/extension/src/shared/sessionState.js +12 -4
  27. package/extension/src/shared/storageDb.js +1 -1
  28. package/extension/src/shared/storageRunActions.js +11 -1
  29. package/extension/styles/panel.css +232 -31
  30. package/native-host/src/codexModels.js +6 -2
  31. package/native-host/src/codexRuntimeIdentity.js +79 -4
  32. package/native-host/src/codexSessionRunner.js +5 -1
  33. package/native-host/src/codexThreadFork.js +103 -0
  34. package/native-host/src/mirrorWorkspace.js +5 -19
  35. package/native-host/src/nativeAssetTransfer.js +183 -0
  36. package/native-host/src/nativeEnvironment.js +4 -1
  37. package/native-host/src/taskRunnerRuntime.js +22 -0
  38. package/native-host/src/textPatch.js +707 -259
  39. package/package.json +1 -1
@@ -177,19 +177,51 @@
177
177
  --provider-muted: #a0a59f;
178
178
  --provider-accent: #79d58a;
179
179
  --provider-danger: #ef8176;
180
+ --provider-text: var(--provider-panel-color, #eef0ec);
181
+ --provider-field-label: #dfe3dd;
182
+ --provider-control-bg: rgba(255, 255, 255, 0.035);
183
+ --provider-hover: rgba(255, 255, 255, 0.07);
184
+ --provider-sidebar-bg: rgba(0, 0, 0, 0.09);
185
+ --provider-footer-bg: rgba(0, 0, 0, 0.08);
186
+ --provider-select-bg: rgba(7, 12, 10, 0.58);
187
+ --provider-backdrop: rgba(4, 7, 5, 0.68);
188
+ --provider-glow: rgba(83, 177, 105, 0.13);
189
+ --provider-shadow: 0 28px 80px rgba(0, 0, 0, 0.48);
190
+ --provider-disabled-opacity: 0.5;
180
191
  position: fixed;
181
192
  inset: 0;
182
193
  z-index: 2147483600;
183
- color: var(--provider-panel-color, #eef0ec);
194
+ color: var(--provider-text);
184
195
  font-family: "Aptos", "SF Pro Text", sans-serif;
185
196
  }
186
197
 
198
+ .codex-provider-dialog-root[data-theme="light"] {
199
+ --provider-bg: #f4f6f7;
200
+ --provider-surface: #ffffff;
201
+ --provider-surface-raised: #e9edf0;
202
+ --provider-line: rgba(31, 35, 40, 0.16);
203
+ --provider-muted: #5f6873;
204
+ --provider-accent: #247d3b;
205
+ --provider-danger: #bd352f;
206
+ --provider-text: #252a30;
207
+ --provider-field-label: #343b44;
208
+ --provider-control-bg: #ffffff;
209
+ --provider-hover: rgba(31, 35, 40, 0.065);
210
+ --provider-sidebar-bg: #e8ebee;
211
+ --provider-footer-bg: #eceff1;
212
+ --provider-select-bg: #ffffff;
213
+ --provider-backdrop: rgba(31, 35, 40, 0.38);
214
+ --provider-glow: rgba(53, 139, 74, 0.09);
215
+ --provider-shadow: 0 24px 64px rgba(31, 35, 40, 0.24);
216
+ --provider-disabled-opacity: 0.68;
217
+ }
218
+
187
219
  .codex-provider-dialog-root[hidden] { display: none !important; }
188
220
 
189
221
  .codex-provider-dialog-backdrop {
190
222
  position: absolute;
191
223
  inset: 0;
192
- background: rgba(4, 7, 5, 0.68);
224
+ background: var(--provider-backdrop);
193
225
  backdrop-filter: blur(3px);
194
226
  }
195
227
 
@@ -207,9 +239,9 @@
207
239
  border: 1px solid var(--provider-line);
208
240
  border-radius: 16px;
209
241
  background:
210
- radial-gradient(circle at 92% -8%, rgba(83, 177, 105, 0.13), transparent 34%),
242
+ radial-gradient(circle at 92% -8%, var(--provider-glow), transparent 34%),
211
243
  var(--provider-bg);
212
- box-shadow: 0 28px 80px rgba(0, 0, 0, 0.48);
244
+ box-shadow: var(--provider-shadow);
213
245
  }
214
246
 
215
247
  .codex-provider-dialog-head,
@@ -259,7 +291,7 @@
259
291
  cursor: pointer;
260
292
  }
261
293
 
262
- .codex-provider-dialog-close:hover { background: rgba(255, 255, 255, 0.07); color: inherit; }
294
+ .codex-provider-dialog-close:hover { background: var(--provider-hover); color: inherit; }
263
295
 
264
296
  .codex-provider-dialog-body {
265
297
  min-height: 0;
@@ -273,7 +305,7 @@
273
305
  padding: 14px 10px;
274
306
  overflow: auto;
275
307
  border-right: 1px solid var(--provider-line);
276
- background: rgba(0, 0, 0, 0.09);
308
+ background: var(--provider-sidebar-bg);
277
309
  }
278
310
 
279
311
  .codex-provider-row,
@@ -297,7 +329,7 @@
297
329
  }
298
330
 
299
331
  .codex-provider-row:hover,
300
- .codex-provider-row[aria-current="true"] { background: rgba(255, 255, 255, 0.07); }
332
+ .codex-provider-row[aria-current="true"] { background: var(--provider-hover); }
301
333
  .codex-provider-row[aria-current="true"] { box-shadow: inset 2px 0 var(--provider-accent); }
302
334
  .codex-provider-row-main { display: block; width: 100%; min-width: 0; overflow: hidden; text-overflow: ellipsis; font-size: 13px; font-weight: 650; white-space: nowrap; }
303
335
  .codex-provider-row-status { color: var(--provider-muted); font-size: 11px; }
@@ -341,7 +373,7 @@
341
373
  display: flex;
342
374
  flex-direction: column;
343
375
  gap: 7px;
344
- color: #dfe3dd;
376
+ color: var(--provider-field-label);
345
377
  font-size: 12px;
346
378
  font-weight: 650;
347
379
  }
@@ -358,7 +390,7 @@
358
390
  border: 1px solid var(--provider-line);
359
391
  border-radius: 8px;
360
392
  outline: 0;
361
- background: rgba(255, 255, 255, 0.035);
393
+ background: var(--provider-control-bg);
362
394
  color: inherit;
363
395
  font: inherit;
364
396
  font-weight: 450;
@@ -438,7 +470,7 @@
438
470
  border-radius: 9px;
439
471
  appearance: none;
440
472
  -webkit-appearance: none;
441
- background: rgba(7, 12, 10, 0.58);
473
+ background: var(--provider-select-bg);
442
474
  color: var(--provider-text);
443
475
  color-scheme: dark;
444
476
  font: inherit;
@@ -506,7 +538,7 @@
506
538
  padding-bottom: 14px;
507
539
  border-top: 1px solid var(--provider-line);
508
540
  border-bottom: 0;
509
- background: rgba(0, 0, 0, 0.08);
541
+ background: var(--provider-footer-bg);
510
542
  }
511
543
 
512
544
  .codex-provider-operation-status { min-width: 0; color: var(--provider-muted); font-size: 11px; }
@@ -524,7 +556,7 @@
524
556
  padding: 0 13px;
525
557
  border: 1px solid var(--provider-line);
526
558
  border-radius: 8px;
527
- background: rgba(255, 255, 255, 0.04);
559
+ background: var(--provider-control-bg);
528
560
  color: inherit;
529
561
  font: inherit;
530
562
  font-size: 12px;
@@ -544,7 +576,7 @@
544
576
  }
545
577
  .codex-provider-secondary-button[data-provider-action="save"]:disabled {
546
578
  border-color: var(--provider-line);
547
- background: rgba(255, 255, 255, 0.04);
579
+ background: var(--provider-control-bg);
548
580
  color: var(--provider-muted);
549
581
  box-shadow: none;
550
582
  }
@@ -553,7 +585,22 @@
553
585
  .codex-provider-dialog-root button:disabled,
554
586
  .codex-provider-dialog-root input:disabled,
555
587
  .codex-provider-dialog-root select:disabled,
556
- .codex-provider-dialog-root textarea:disabled { cursor: not-allowed; opacity: 0.5; }
588
+ .codex-provider-dialog-root textarea:disabled { cursor: not-allowed; opacity: var(--provider-disabled-opacity); }
589
+
590
+ .codex-provider-dialog-root[data-theme="light"] .codex-provider-experimental-badge {
591
+ border-color: rgba(148, 102, 8, 0.34);
592
+ background: rgba(148, 102, 8, 0.08);
593
+ color: #855d09;
594
+ }
595
+
596
+ .codex-provider-dialog-root[data-theme="light"] .codex-provider-test-select-shell select {
597
+ color-scheme: light;
598
+ }
599
+
600
+ .codex-provider-dialog-root[data-theme="light"] .codex-provider-disclosure {
601
+ background: rgba(148, 102, 8, 0.08);
602
+ color: #72500b;
603
+ }
557
604
 
558
605
  .codex-provider-builtin { max-width: 430px; padding-top: 34px; }
559
606
  .codex-provider-builtin-mark { display: grid; place-items: center; width: 48px; height: 48px; margin-bottom: 18px; border-radius: 14px; background: linear-gradient(145deg, #3f8d51, #255932); color: #fff; font-size: 21px; font-weight: 800; }
@@ -2359,6 +2406,67 @@
2359
2406
  margin-left: auto;
2360
2407
  }
2361
2408
 
2409
+ #codex-overleaf-panel .run-result-actions {
2410
+ display: flex;
2411
+ justify-content: flex-start;
2412
+ align-items: center;
2413
+ gap: 2px;
2414
+ min-height: 30px;
2415
+ margin: 10px 0 0;
2416
+ }
2417
+
2418
+ #codex-overleaf-panel .run-result-actions[hidden] { display: none; }
2419
+
2420
+ #codex-overleaf-panel .run-copy-action,
2421
+ #codex-overleaf-panel .run-fork-action {
2422
+ display: inline-grid;
2423
+ place-items: center;
2424
+ width: 30px;
2425
+ height: 30px;
2426
+ border: 0;
2427
+ border-radius: 999px;
2428
+ background: transparent;
2429
+ color: var(--tl-fg-4);
2430
+ opacity: 1;
2431
+ transition: color 120ms ease, background 120ms ease, opacity 120ms ease;
2432
+ }
2433
+
2434
+ #codex-overleaf-panel .run-copy-action:hover:not(:disabled),
2435
+ #codex-overleaf-panel .run-fork-action:hover:not(:disabled) {
2436
+ background: var(--tl-hover);
2437
+ color: var(--tl-accent);
2438
+ }
2439
+
2440
+ #codex-overleaf-panel .run-copy-action:disabled,
2441
+ #codex-overleaf-panel .run-fork-action:disabled {
2442
+ cursor: not-allowed;
2443
+ opacity: 0.38;
2444
+ }
2445
+
2446
+ #codex-overleaf-panel .run-copy-action svg,
2447
+ #codex-overleaf-panel .run-fork-action svg {
2448
+ width: 16px;
2449
+ height: 16px;
2450
+ fill: none;
2451
+ stroke: currentColor;
2452
+ stroke-width: 1.7;
2453
+ stroke-linecap: round;
2454
+ stroke-linejoin: round;
2455
+ }
2456
+
2457
+ #codex-overleaf-panel .run-copy-action[hidden],
2458
+ #codex-overleaf-panel .run-fork-action[hidden] { display: none; }
2459
+
2460
+ #codex-overleaf-panel .run-completed-time {
2461
+ margin-left: 5px;
2462
+ color: var(--tl-fg-4);
2463
+ font-size: 12px;
2464
+ font-variant-numeric: tabular-nums;
2465
+ line-height: 30px;
2466
+ }
2467
+
2468
+ #codex-overleaf-panel .run-completed-time[hidden] { display: none; }
2469
+
2362
2470
  /* When Accept All is hidden, the Undo button takes the right-aligned slot. */
2363
2471
  #codex-overleaf-panel .run-turn-meta [data-run-accept][hidden] + [data-run-undo] {
2364
2472
  margin-left: auto;
@@ -2888,6 +2996,62 @@
2888
2996
  font-weight: 600;
2889
2997
  }
2890
2998
 
2999
+ #codex-overleaf-panel .run-final-answer .run-system-label {
3000
+ color: var(--tl-accent);
3001
+ font-weight: 650;
3002
+ letter-spacing: 0.01em;
3003
+ }
3004
+
3005
+ #codex-overleaf-panel .run-completion-report[data-status="failed"] .run-system-label[data-system-label="conclusion"] {
3006
+ color: var(--tl-fail);
3007
+ }
3008
+
3009
+ #codex-overleaf-panel .run-final-answer .run-change-summary {
3010
+ display: flex;
3011
+ align-items: center;
3012
+ gap: 6px;
3013
+ margin: 7px 0 8px;
3014
+ color: var(--tl-ok);
3015
+ font-size: 12px;
3016
+ }
3017
+
3018
+ #codex-overleaf-panel .run-final-answer .run-change-summary__check {
3019
+ font-weight: 650;
3020
+ }
3021
+
3022
+ #codex-overleaf-panel .run-final-answer .run-change-receipts {
3023
+ display: flex;
3024
+ flex-wrap: wrap;
3025
+ gap: 6px 13px;
3026
+ margin: 0;
3027
+ padding: 0;
3028
+ list-style: none;
3029
+ }
3030
+
3031
+ #codex-overleaf-panel .run-final-answer .run-change-receipt {
3032
+ display: flex;
3033
+ flex-wrap: wrap;
3034
+ align-items: baseline;
3035
+ gap: 0.32em;
3036
+ margin: 0;
3037
+ min-width: 0;
3038
+ }
3039
+
3040
+ #codex-overleaf-panel .run-final-answer .run-change-receipt + .run-change-receipt {
3041
+ margin-top: 0;
3042
+ }
3043
+
3044
+ #codex-overleaf-panel .run-final-answer .run-change-receipt__file {
3045
+ color: var(--tl-fg-1);
3046
+ font: 600 0.94em/1.4 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
3047
+ overflow-wrap: anywhere;
3048
+ }
3049
+
3050
+ #codex-overleaf-panel .run-final-answer .run-change-receipt__operation {
3051
+ color: var(--tl-fg-3);
3052
+ font-size: 0.88em;
3053
+ }
3054
+
2891
3055
  #codex-overleaf-panel .run-final-answer p + p,
2892
3056
  #codex-overleaf-panel .run-final-answer p + ul,
2893
3057
  #codex-overleaf-panel .run-final-answer p + ol,
@@ -3728,8 +3892,10 @@
3728
3892
 
3729
3893
  #codex-overleaf-panel .codex-pending-inputs {
3730
3894
  display: grid;
3731
- gap: 6px;
3732
- padding: 0 6px 7px;
3895
+ gap: 0;
3896
+ margin: 0 6px 7px;
3897
+ padding: 7px 4px 0;
3898
+ border-top: 1px solid var(--tl-border);
3733
3899
  }
3734
3900
 
3735
3901
  #codex-overleaf-panel .codex-pending-inputs[hidden] {
@@ -3738,38 +3904,56 @@
3738
3904
 
3739
3905
  #codex-overleaf-panel .codex-pending-input {
3740
3906
  display: grid;
3741
- grid-template-columns: minmax(0, 1fr) auto;
3907
+ grid-template-columns: 20px minmax(0, 1fr) auto;
3742
3908
  align-items: center;
3743
- gap: 8px;
3909
+ gap: 7px;
3744
3910
  min-width: 0;
3745
- padding: 8px 9px;
3746
- border: 1px solid var(--tl-border);
3747
- border-radius: 9px;
3748
- background: color-mix(in srgb, var(--tl-surface-1) 86%, var(--tl-accent) 14%);
3911
+ padding: 8px 5px;
3912
+ border-top: 1px solid var(--tl-border);
3913
+ background: transparent;
3914
+ }
3915
+
3916
+ #codex-overleaf-panel .codex-pending-input:first-of-type {
3917
+ border-top: 0;
3918
+ }
3919
+
3920
+ #codex-overleaf-panel .codex-pending-input:hover,
3921
+ #codex-overleaf-panel .codex-pending-input:focus-within {
3922
+ border-radius: 8px;
3923
+ background: var(--tl-hover);
3924
+ }
3925
+
3926
+ #codex-overleaf-panel .codex-pending-input-marker {
3927
+ display: inline-grid;
3928
+ place-items: center;
3929
+ color: var(--tl-fg-3);
3930
+ }
3931
+
3932
+ #codex-overleaf-panel .codex-pending-input[data-status="paused"] .codex-pending-input-marker {
3933
+ color: var(--tl-warn);
3749
3934
  }
3750
3935
 
3751
- #codex-overleaf-panel .codex-pending-input[data-status="paused"] {
3752
- background: color-mix(in srgb, var(--tl-surface-1) 90%, var(--tl-warn) 10%);
3936
+ #codex-overleaf-panel .codex-pending-input[data-status="steering"] .codex-pending-input-marker,
3937
+ #codex-overleaf-panel .codex-pending-input[data-status="executing"] .codex-pending-input-marker {
3938
+ color: var(--tl-accent);
3753
3939
  }
3754
3940
 
3755
3941
  #codex-overleaf-panel .codex-pending-input-copy {
3756
3942
  display: grid;
3757
3943
  min-width: 0;
3758
- gap: 2px;
3944
+ gap: 3px;
3759
3945
  }
3760
3946
 
3761
3947
  #codex-overleaf-panel .codex-pending-input-status {
3762
3948
  color: var(--tl-fg-3);
3763
- font-size: 10px;
3764
- font-weight: 700;
3765
- letter-spacing: 0.06em;
3766
- text-transform: uppercase;
3949
+ font-size: 10.5px;
3950
+ font-weight: 500;
3767
3951
  }
3768
3952
 
3769
3953
  #codex-overleaf-panel .codex-pending-input-text {
3770
3954
  color: var(--tl-fg-1);
3771
- font-size: 12px;
3772
- line-height: 1.35;
3955
+ font-size: 13px;
3956
+ line-height: 1.4;
3773
3957
  overflow-wrap: anywhere;
3774
3958
  white-space: pre-wrap;
3775
3959
  }
@@ -3777,6 +3961,13 @@
3777
3961
  #codex-overleaf-panel .codex-pending-input-actions {
3778
3962
  display: inline-flex;
3779
3963
  gap: 4px;
3964
+ color: var(--tl-fg-3);
3965
+ opacity: 0.72;
3966
+ }
3967
+
3968
+ #codex-overleaf-panel .codex-pending-input:hover .codex-pending-input-actions,
3969
+ #codex-overleaf-panel .codex-pending-input:focus-within .codex-pending-input-actions {
3970
+ opacity: 1;
3780
3971
  }
3781
3972
 
3782
3973
  #codex-overleaf-panel .codex-pending-input-actions button {
@@ -3818,6 +4009,16 @@
3818
4009
  stroke-linejoin: round;
3819
4010
  }
3820
4011
 
4012
+ #codex-overleaf-panel .codex-pending-input-marker svg {
4013
+ width: 15px;
4014
+ height: 15px;
4015
+ fill: none;
4016
+ stroke: currentColor;
4017
+ stroke-width: 1.55;
4018
+ stroke-linecap: round;
4019
+ stroke-linejoin: round;
4020
+ }
4021
+
3821
4022
  #codex-overleaf-panel .codex-pending-input-icon[data-action="guide"]:hover {
3822
4023
  color: var(--tl-accent);
3823
4024
  }
@@ -181,7 +181,7 @@ function buildModelEntry({
181
181
  defaultSpeedTier = 'standard'
182
182
  }) {
183
183
  const normalizedEfforts = normalizeReasoningEfforts(reasoningEfforts);
184
- const normalizedDefault = getString(defaultReasoningEffort) || 'medium';
184
+ const normalizedDefault = normalizeReasoningEffort(defaultReasoningEffort) || 'medium';
185
185
  const normalizedSpeedTiers = normalizeSpeedTiers(speedTiers);
186
186
  const normalizedDefaultSpeedTier = getString(defaultSpeedTier) || 'standard';
187
187
  return {
@@ -202,7 +202,7 @@ function normalizeReasoningEfforts(rawEfforts) {
202
202
  const seen = new Set();
203
203
  const result = [];
204
204
  for (const rawEffort of rawEfforts) {
205
- const effort = getString(typeof rawEffort === 'string' ? rawEffort : rawEffort?.effort);
205
+ const effort = normalizeReasoningEffort(typeof rawEffort === 'string' ? rawEffort : rawEffort?.effort);
206
206
  if (!effort || seen.has(effort)) {
207
207
  continue;
208
208
  }
@@ -212,6 +212,10 @@ function normalizeReasoningEfforts(rawEfforts) {
212
212
  return result;
213
213
  }
214
214
 
215
+ function normalizeReasoningEffort(rawEffort) {
216
+ return getString(rawEffort);
217
+ }
218
+
215
219
  function normalizeSpeedTiers(rawTiers) {
216
220
  const tiers = ['standard'];
217
221
  if (!Array.isArray(rawTiers)) {
@@ -22,24 +22,99 @@ function buildCodexRuntimeIdentity(options = {}) {
22
22
  const candidatePaths = selectedPath
23
23
  ? [selectedPath, ...discovered.filter(candidate => !sameExecutablePath(candidate, selectedPath, platform))]
24
24
  : discovered;
25
- const candidates = candidatePaths.slice(0, MAX_CODEX_CANDIDATES).map((candidatePath, index) => ({
25
+ const candidates = candidatePaths.slice(0, MAX_CODEX_CANDIDATES).map(candidatePath => ({
26
26
  path: candidatePath,
27
27
  displayPath: compactHomePath(candidatePath, env, platform),
28
28
  version: readCodexVersion(candidatePath, { env, platform }),
29
29
  source: classifyCodexSource(candidatePath),
30
- selected: index === 0 && Boolean(selectedPath)
30
+ selected: false
31
31
  }));
32
- const selected = candidates.find(candidate => candidate.selected) || null;
32
+ const selectedIndex = selectCodexCandidateIndex(candidates, {
33
+ selectedPath,
34
+ selectionPolicy: options.selectionPolicy,
35
+ platform
36
+ });
37
+ if (selectedIndex >= 0) {
38
+ candidates[selectedIndex].selected = true;
39
+ }
40
+ const selected = selectedIndex >= 0 ? candidates[selectedIndex] : null;
33
41
 
34
42
  return {
35
43
  schemaVersion: 1,
36
44
  selected,
37
45
  candidates,
38
46
  multipleInstallations: candidates.length > 1,
39
- selectedBy: selected ? 'path-order' : 'not-found'
47
+ selectedBy: selected
48
+ ? options.selectionPolicy === 'newest-version' ? 'newest-version' : options.selectionPolicy === 'explicit-path' ? 'explicit-path' : 'path-order'
49
+ : 'not-found'
40
50
  };
41
51
  }
42
52
 
53
+ function selectCodexCandidateIndex(candidates, options = {}) {
54
+ if (!candidates.length) return -1;
55
+ if (options.selectionPolicy !== 'newest-version') {
56
+ if (!options.selectedPath) return -1;
57
+ return candidates.findIndex(candidate => sameExecutablePath(
58
+ candidate.path,
59
+ options.selectedPath,
60
+ options.platform
61
+ ));
62
+ }
63
+
64
+ let selectedIndex = 0;
65
+ for (let index = 1; index < candidates.length; index += 1) {
66
+ if (compareCodexVersions(candidates[index].version, candidates[selectedIndex].version) > 0) {
67
+ selectedIndex = index;
68
+ }
69
+ }
70
+ return selectedIndex;
71
+ }
72
+
73
+ function compareCodexVersions(left, right) {
74
+ const leftVersion = parseCodexVersion(left);
75
+ const rightVersion = parseCodexVersion(right);
76
+ if (!leftVersion && !rightVersion) return 0;
77
+ if (!leftVersion) return -1;
78
+ if (!rightVersion) return 1;
79
+
80
+ for (let index = 0; index < 3; index += 1) {
81
+ if (leftVersion.core[index] !== rightVersion.core[index]) {
82
+ return leftVersion.core[index] - rightVersion.core[index];
83
+ }
84
+ }
85
+ if (!leftVersion.prerelease && rightVersion.prerelease) return 1;
86
+ if (leftVersion.prerelease && !rightVersion.prerelease) return -1;
87
+ if (!leftVersion.prerelease && !rightVersion.prerelease) return 0;
88
+ return comparePrerelease(leftVersion.prerelease, rightVersion.prerelease);
89
+ }
90
+
91
+ function parseCodexVersion(value) {
92
+ const match = String(value || '').trim().match(/^(\d+)\.(\d+)\.(\d+)(?:-([^+]+))?/);
93
+ if (!match) return null;
94
+ return {
95
+ core: [Number(match[1]), Number(match[2]), Number(match[3])],
96
+ prerelease: match[4] || ''
97
+ };
98
+ }
99
+
100
+ function comparePrerelease(left, right) {
101
+ const leftParts = left.split('.');
102
+ const rightParts = right.split('.');
103
+ const length = Math.max(leftParts.length, rightParts.length);
104
+ for (let index = 0; index < length; index += 1) {
105
+ if (leftParts[index] === undefined) return -1;
106
+ if (rightParts[index] === undefined) return 1;
107
+ if (leftParts[index] === rightParts[index]) continue;
108
+ const leftNumber = /^\d+$/.test(leftParts[index]) ? Number(leftParts[index]) : null;
109
+ const rightNumber = /^\d+$/.test(rightParts[index]) ? Number(rightParts[index]) : null;
110
+ if (leftNumber !== null && rightNumber !== null) return leftNumber - rightNumber;
111
+ if (leftNumber !== null) return -1;
112
+ if (rightNumber !== null) return 1;
113
+ return leftParts[index].localeCompare(rightParts[index]);
114
+ }
115
+ return 0;
116
+ }
117
+
43
118
  function discoverCodexCandidates(pathValue, options = {}) {
44
119
  const platform = options.platform || process.platform;
45
120
  const delimiter = options.delimiter || (platform === 'win32' ? ';' : path.delimiter);
@@ -22,6 +22,7 @@ const {
22
22
  loadSelectedProjectSkills
23
23
  } = require('./localSkills');
24
24
  const { createSubagentBroker } = require('./subagentBroker');
25
+ const { prepareBinaryAssetChanges } = require('./nativeAssetTransfer');
25
26
  const { resolveCodexCommand, shouldUseShellForCommand } = require('./codexCommand');
26
27
  const { applyProviderEnvironment, buildProviderConfigArgs, prepareProviderLaunch } = require('./codexProviderLaunch');
27
28
  const { buildReadProgressRules, createReadProgressController } = require('./readProgressGuard');
@@ -259,6 +260,9 @@ async function runCodexSession({ params = {}, env = process.env, emit = () => {}
259
260
  });
260
261
  }
261
262
 
263
+ const preparedAssets = prepareBinaryAssetChanges({ projectId, rootDir, changes: rawSyncChanges });
264
+ rawSyncChanges = preparedAssets.changes;
265
+ unsupportedChanges.push(...preparedAssets.unsupportedChanges);
262
266
  const syncChanges = rawSyncChanges.map(change => {
263
267
  if (change.type === 'write' && typeof change.previousContent === 'string') {
264
268
  return {
@@ -1461,7 +1465,7 @@ function extractCodexAppServerErrorMessage(params = {}) {
1461
1465
  }
1462
1466
 
1463
1467
  function normalizeReasoningEffort(value) {
1464
- return ['none', 'minimal', 'low', 'medium', 'high', 'xhigh'].includes(value) ? value : null;
1468
+ return ['none', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max', 'ultra'].includes(value) ? value : null;
1465
1469
  }
1466
1470
 
1467
1471
  function normalizeSpeedTier(value) {
@@ -0,0 +1,103 @@
1
+ 'use strict';
2
+
3
+ const { spawn } = require('node:child_process');
4
+ const { version: PACKAGE_VERSION } = require('../../package.json');
5
+ const { buildCodexHomeEnv } = require('./codexHome');
6
+ const { resolveCodexCommand, shouldUseShellForCommand } = require('./codexCommand');
7
+ const { buildCodexAppServerArgs } = require('./codexSessionRunner');
8
+
9
+ function forkCodexThread(params = {}, env = process.env) {
10
+ const threadId = cleanId(params.threadId, 'threadId');
11
+ const lastTurnId = cleanId(params.lastTurnId, 'lastTurnId');
12
+ const childEnv = buildCodexHomeEnv(env, {
13
+ loadCodexLocalSkills: params.loadCodexLocalSkills !== false,
14
+ loadCodexOverleafSkills: params.loadCodexOverleafSkills !== false
15
+ });
16
+ const command = resolveCodexCommand(childEnv);
17
+ if (!command) return Promise.reject(codedError('codex_not_found', 'Codex CLI was not found.'));
18
+
19
+ return new Promise((resolve, reject) => {
20
+ const child = spawn(command, buildCodexAppServerArgs({
21
+ speedTier: params.speedTier || 'standard',
22
+ loadCodexLocalSkills: params.loadCodexLocalSkills !== false
23
+ }), {
24
+ env: childEnv,
25
+ shell: shouldUseShellForCommand(command, childEnv),
26
+ stdio: ['pipe', 'pipe', 'pipe']
27
+ });
28
+ const pending = new Map();
29
+ let nextId = 1;
30
+ let buffer = '';
31
+ let stderr = '';
32
+ let settled = false;
33
+ const timeout = setTimeout(() => finish(codedError('codex_thread_fork_timeout', 'Codex thread fork timed out.')), 30000);
34
+
35
+ child.stdout.setEncoding('utf8');
36
+ child.stderr.setEncoding('utf8');
37
+ child.stdout.on('data', chunk => {
38
+ buffer += chunk;
39
+ const lines = buffer.split(/\r?\n/);
40
+ buffer = lines.pop() || '';
41
+ for (const line of lines) if (line.trim()) handleMessage(line);
42
+ });
43
+ child.stderr.on('data', chunk => { stderr += chunk; });
44
+ child.on('error', finish);
45
+ child.on('close', code => {
46
+ if (!settled) finish(codedError('codex_thread_fork_failed', stderr || `Codex app-server exited with code ${code}.`));
47
+ });
48
+
49
+ (async () => {
50
+ await request('initialize', {
51
+ clientInfo: { name: 'codex-overleaf-link', version: PACKAGE_VERSION },
52
+ capabilities: null
53
+ });
54
+ notify('initialized');
55
+ const result = await request('thread/fork', { threadId, lastTurnId });
56
+ const forkedThreadId = result?.thread?.id || result?.threadId || '';
57
+ if (!forkedThreadId || forkedThreadId === threadId) {
58
+ throw codedError('codex_thread_fork_invalid_response', 'Codex app-server did not return a new thread id.');
59
+ }
60
+ finish(null, { threadId: forkedThreadId, sourceThreadId: threadId, lastTurnId });
61
+ })().catch(finish);
62
+
63
+ function request(method, requestParams) {
64
+ const id = nextId++;
65
+ child.stdin.write(`${JSON.stringify({ id, method, params: requestParams })}\n`);
66
+ return new Promise((resolveRequest, rejectRequest) => pending.set(id, { resolve: resolveRequest, reject: rejectRequest }));
67
+ }
68
+ function notify(method, requestParams) {
69
+ child.stdin.write(`${JSON.stringify({ method, params: requestParams })}\n`);
70
+ }
71
+ function handleMessage(line) {
72
+ let message;
73
+ try { message = JSON.parse(line); } catch (_error) { return; }
74
+ if (Object.prototype.hasOwnProperty.call(message, 'id') && (message.result !== undefined || message.error)) {
75
+ const waiter = pending.get(message.id);
76
+ if (!waiter) return;
77
+ pending.delete(message.id);
78
+ if (message.error) waiter.reject(codedError('codex_thread_fork_failed', message.error.message || JSON.stringify(message.error)));
79
+ else waiter.resolve(message.result);
80
+ } else if (Object.prototype.hasOwnProperty.call(message, 'id') && message.method) {
81
+ child.stdin.write(`${JSON.stringify({ id: message.id, error: { code: -32601, message: 'Unsupported during thread fork' } })}\n`);
82
+ }
83
+ }
84
+ function finish(error, result) {
85
+ if (settled) return;
86
+ settled = true;
87
+ clearTimeout(timeout);
88
+ for (const waiter of pending.values()) waiter.reject(error || codedError('codex_thread_fork_closed', 'Fork process closed.'));
89
+ pending.clear();
90
+ try { child.stdin.end(); } catch (_error) { /* best effort */ }
91
+ try { child.kill(); } catch (_error) { /* best effort */ }
92
+ if (error) reject(error); else resolve(result);
93
+ }
94
+ });
95
+ }
96
+
97
+ function cleanId(value, field) {
98
+ const id = String(value || '').trim();
99
+ if (!id || id.length > 160) throw codedError('codex_thread_fork_invalid', `${field} is required.`);
100
+ return id;
101
+ }
102
+ function codedError(code, message) { const error = new Error(message); error.code = code; return error; }
103
+ module.exports = { forkCodexThread };