codex-overleaf-link 2.3.5 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/README.md +63 -31
  2. package/README.zh-CN.md +543 -0
  3. package/extension/bootstrap/manifest.template.json +1 -1
  4. package/extension/src/background.js +7 -0
  5. package/extension/src/backgroundUpdateCoordinator.js +88 -4
  6. package/extension/src/content/activeTurnControl.js +1 -0
  7. package/extension/src/content/assetTransferBroker.js +2 -0
  8. package/extension/src/content/contentRuntime.js +77 -57
  9. package/extension/src/content/contextTray.js +4 -0
  10. package/extension/src/content/diagnosticsController.js +17 -3
  11. package/extension/src/content/diagnosticsPanel.js +129 -5
  12. package/extension/src/content/generated/content.bundle.js +1422 -348
  13. package/extension/src/content/generated/content.bundle.meta.json +7 -3
  14. package/extension/src/content/legacyGlobalRegistry.js +3 -0
  15. package/extension/src/content/modelPicker.js +21 -9
  16. package/extension/src/content/modelPickerSupport.js +38 -1
  17. package/extension/src/content/nativeCompatibilityController.js +4 -1
  18. package/extension/src/content/otWarmMirror.js +126 -92
  19. package/extension/src/content/otWarmMirrorController.js +122 -1
  20. package/extension/src/content/pageBridgeClient.js +2 -0
  21. package/extension/src/content/panelMaintenance.js +26 -15
  22. package/extension/src/content/providerSettingsCoordinator.js +1 -0
  23. package/extension/src/content/runFailureNotice.js +104 -0
  24. package/extension/src/content/runResultActions.js +16 -1
  25. package/extension/src/content/runScrollLayout.js +124 -0
  26. package/extension/src/content/runTimelineView.js +37 -11
  27. package/extension/src/content/sessionForkController.js +20 -11
  28. package/extension/src/content/settingsPanel.js +28 -11
  29. package/extension/src/content/trackedChangeCaptureController.js +153 -0
  30. package/extension/src/content/updateNotice.js +24 -4
  31. package/extension/src/content/writebackOrchestrator.js +31 -0
  32. package/extension/src/page/binaryAssetUploader.js +101 -29
  33. package/extension/src/page/overleafRealtimeObserver.js +120 -25
  34. package/extension/src/page/textFileCreator.js +493 -0
  35. package/extension/src/page/trackedChangeCapture.js +342 -0
  36. package/extension/src/page/trackedChangesLifecycle.js +145 -149
  37. package/extension/src/page/treeOperations.js +30 -2
  38. package/extension/src/page/writebackRouter.js +94 -48
  39. package/extension/src/pageBridge.js +62 -25
  40. package/extension/src/shared/compatibility.js +1 -1
  41. package/extension/src/shared/i18n.js +77 -37
  42. package/extension/src/shared/managedUpdateProjection.js +1 -1
  43. package/extension/src/shared/otText.js +0 -1
  44. package/extension/src/shared/pageRpcContract.js +2 -1
  45. package/extension/src/shared/sessionState.js +23 -23
  46. package/extension/src/shared/sessionTitle.js +120 -0
  47. package/extension/src/shared/storageDb.js +10 -12
  48. package/extension/src/shared/storageRunActions.js +29 -1
  49. package/extension/src/shared/updateRuntimeIdentity.js +32 -0
  50. package/extension/src/shared/writebackSettlement.js +19 -5
  51. package/extension/styles/panel.css +128 -24
  52. package/native-host/src/mirrorWorkspace.js +20 -1
  53. package/native-host/src/releaseProxy.js +177 -0
  54. package/native-host/src/releaseTransport.js +154 -0
  55. package/native-host/src/taskRunnerRuntime.js +25 -0
  56. package/native-host/src/updateManager.js +40 -39
  57. package/package.json +5 -1
@@ -5,12 +5,13 @@
5
5
  require('./settlementFacts'),
6
6
  require('./runInputQueue'),
7
7
  require('./lineReferences'),
8
- require('./pathRedaction')
8
+ require('./pathRedaction'),
9
+ require('./sessionTitle')
9
10
  );
10
11
  } else {
11
12
  root.CodexOverleafModuleRegistry.define(
12
13
  'SessionState',
13
- ['I18n', 'SettlementFacts', 'RunInputQueue', 'LineReferences', 'PathRedaction'],
14
+ ['I18n', 'SettlementFacts', 'RunInputQueue', 'LineReferences', 'PathRedaction', 'SessionTitle'],
14
15
  factory
15
16
  );
16
17
  }
@@ -19,7 +20,8 @@
19
20
  SettlementFacts,
20
21
  RunInputQueue,
21
22
  LineReferences,
22
- PathRedaction
23
+ PathRedaction,
24
+ SessionTitle
23
25
  ) {
24
26
  'use strict';
25
27
 
@@ -402,6 +404,12 @@
402
404
  ) {
403
405
  next.title = session.title;
404
406
  next.titleSource = 'manual';
407
+ } else if (session.titleSource === 'auto' && patch.titleSource !== 'manual'
408
+ && typeof patch.title === 'string' && patch.title.trim() && session.runs?.length
409
+ && SessionTitle.isGeneric(sanitizeAutoTitle(patch.title))
410
+ && !SessionTitle.isGeneric(sanitizeAutoTitle(session.title))) {
411
+ next.title = session.title;
412
+ next.titleSource = 'auto';
405
413
  }
406
414
  return normalizeSession({
407
415
  ...next
@@ -472,12 +480,7 @@
472
480
  }
473
481
 
474
482
  function deriveSessionTitle(runs, task) {
475
- const taskTitle = sanitizeAutoTitle(task);
476
- if (taskTitle) {
477
- return taskTitle;
478
- }
479
- const firstRunTask = Array.isArray(runs) && runs.length ? runs[0]?.task : '';
480
- return sanitizeAutoTitle(firstRunTask);
483
+ return SessionTitle.derive(runs, task, { sanitize: sanitizeAssistantVisibleText, maxChars: SESSION_AUTO_TITLE_CHARS });
481
484
  }
482
485
 
483
486
  // Rename a session with the placeholder/derived-title ghost guard (mirrors
@@ -519,21 +522,14 @@
519
522
  if (titleSource === 'manual') {
520
523
  return normalizeTextField(rawTitle === LEGACY_DEFAULT_SESSION_TITLE ? '' : rawTitle, STORAGE_DEFAULT_LIMITS.sessionTitleChars);
521
524
  }
522
- return sanitizeAutoTitle(rawTitle === LEGACY_DEFAULT_SESSION_TITLE ? '' : rawTitle) || derivedTitle || '';
525
+ const current = sanitizeAutoTitle(rawTitle === LEGACY_DEFAULT_SESSION_TITLE ? '' : rawTitle);
526
+ if (!current || SessionTitle.isGeneric(current)) return derivedTitle || current || '';
527
+ if (current.endsWith('…') && derivedTitle.startsWith(current.slice(0, -1))) return derivedTitle;
528
+ return current;
523
529
  }
524
530
 
525
531
  function sanitizeAutoTitle(value) {
526
- const title = sanitizeAssistantVisibleText(value)
527
- .replace(/@file:[^\s]+/g, ' ')
528
- .replace(/@(context|compile-log)\b/g, ' ')
529
- .replace(/\s+/g, ' ')
530
- .trim();
531
- if (!title) {
532
- return '';
533
- }
534
- return title.length > SESSION_AUTO_TITLE_CHARS
535
- ? `${title.slice(0, SESSION_AUTO_TITLE_CHARS - 1)}…`
536
- : title;
532
+ return SessionTitle.fromPrompt(value, { sanitize: sanitizeAssistantVisibleText, maxChars: SESSION_AUTO_TITLE_CHARS });
537
533
  }
538
534
 
539
535
  function isDisplayableSession(session) {
@@ -640,7 +636,7 @@
640
636
  undoExpectedFiles: normalizeRunFiles(run.undoExpectedFiles),
641
637
  undoStatus: sanitizeAssistantVisibleText(run.undoStatus),
642
638
  nativeRequestId: sanitizeAssistantVisibleText(run.nativeRequestId),
643
- codexTurnId: sanitizeAssistantVisibleText(run.codexTurnId),
639
+ codexThreadId: sanitizeAssistantVisibleText(run.codexThreadId), codexTurnId: sanitizeAssistantVisibleText(run.codexTurnId),
644
640
  forkSnapshot: run.forkSnapshot === true,
645
641
  forkSourceRunId: sanitizeAssistantVisibleText(run.forkSourceRunId),
646
642
  nativeEventSeq: Number.isFinite(Number(run.nativeEventSeq)) ? Number(run.nativeEventSeq) : 0,
@@ -660,6 +656,9 @@
660
656
  if (run.changedDocument === true) {
661
657
  normalized.changedDocument = true;
662
658
  }
659
+ if (Array.isArray(run.trackedChangeCaptures) && run.trackedChangeCaptures.length && !run.forkSnapshot) {
660
+ normalized.trackedChangeCaptures = sanitizeAssistantVisibleValue(run.trackedChangeCaptures);
661
+ }
663
662
  applyTrackedChangeStatus(normalized, run.trackedChangeStatus);
664
663
 
665
664
  return normalized;
@@ -1219,7 +1218,7 @@
1219
1218
  undoExpectedFiles: undoPayload.undoExpectedFiles,
1220
1219
  undoStatus: summarizeTextForStorage(run.undoStatus, 'undo status'),
1221
1220
  nativeRequestId: normalizeTextField(run.nativeRequestId, 160),
1222
- codexTurnId: normalizeTextField(run.codexTurnId, 160),
1221
+ codexThreadId: normalizeTextField(run.codexThreadId, 160), codexTurnId: normalizeTextField(run.codexTurnId, 160),
1223
1222
  forkSnapshot: run.forkSnapshot === true,
1224
1223
  forkSourceRunId: normalizeTextField(run.forkSourceRunId, 160),
1225
1224
  nativeEventSeq: Number.isFinite(Number(run.nativeEventSeq)) ? Number(run.nativeEventSeq) : 0,
@@ -1631,6 +1630,7 @@
1631
1630
  setActiveSession,
1632
1631
  updateActiveSession,
1633
1632
  normalizeRuns,
1633
+ normalizeProjectReferenceFiles,
1634
1634
  prepareStateForStorage,
1635
1635
  estimateJsonBytes,
1636
1636
  computeSafeTaskSummary
@@ -0,0 +1,120 @@
1
+ (function initSessionTitle(root, factory) {
2
+ if (typeof module === 'object' && module.exports) {
3
+ module.exports = factory();
4
+ } else {
5
+ root.CodexOverleafModuleRegistry.define('SessionTitle', [], factory);
6
+ }
7
+ })(typeof globalThis !== 'undefined' ? globalThis : window, function sessionTitleFactory() {
8
+ 'use strict';
9
+
10
+ const INPUT_LIMIT = 12000;
11
+ const STORAGE_LIMIT = 80;
12
+ const segmenter = typeof Intl !== 'undefined' && typeof Intl.Segmenter === 'function'
13
+ ? new Intl.Segmenter(undefined, { granularity: 'grapheme' }) : null;
14
+ const FILE_TOKEN = /@file:(?:"([^"]+)"|'([^']+)'|([^\s]+))/g;
15
+
16
+ function derive(runs, task, options = {}) {
17
+ let fallback = '';
18
+ for (const run of Array.isArray(runs) ? runs : []) {
19
+ const candidate = fromPrompt(run?.task, options);
20
+ fallback ||= candidate;
21
+ if (candidate && !isGeneric(candidate)) return candidate;
22
+ }
23
+ const draft = fromPrompt(task, options);
24
+ return draft && !isGeneric(draft) ? draft : fallback || draft;
25
+ }
26
+
27
+ function fromPrompt(value, { sanitize = text => text, maxChars = 24 } = {}) {
28
+ if (typeof value !== 'string' || !value.trim()) return '';
29
+ const source = String(sanitize(value.slice(0, INPUT_LIMIT)) || '');
30
+ let fence = null;
31
+ let fallback = '';
32
+ for (const rawLine of source.split(/\r?\n/)) {
33
+ const line = rawLine.trim();
34
+ const marker = /^(`{3,}|~{3,})(.*)$/.exec(line);
35
+ if (marker) {
36
+ if (!fence) {
37
+ fence = marker[1];
38
+ const language = marker[2].trim().match(/^[A-Za-z0-9_+-]{1,24}\b/)?.[0] || 'code';
39
+ fallback ||= `${language} snippet`;
40
+ } else if (marker[1][0] === fence[0] && marker[1].length >= fence.length && !marker[2].trim()) {
41
+ fence = null;
42
+ }
43
+ continue;
44
+ }
45
+ if (fence || !line || /^>/.test(line)) continue;
46
+ const withoutContext = line.replace(FILE_TOKEN, ' ').replace(/@(context|compile-log)\b/g, ' ').trim();
47
+ const cleaned = cleanLine(line);
48
+ if (!withoutContext || /^https?:\/\/\S+$/i.test(line)) {
49
+ fallback ||= cleaned;
50
+ continue;
51
+ }
52
+ for (const sentence of cleaned.split(/[\u3002\uff01\uff1f]+/u)) {
53
+ const candidate = sentence.trim().replace(/[,;:\uff0c\uff1b\uff1a]+$/u, '').trim();
54
+ if (!/[\p{L}\p{N}]/u.test(candidate) || isScaffolding(candidate)) continue;
55
+ fallback ||= candidate;
56
+ if (!isGeneric(candidate)) return shorten(candidate, maxChars);
57
+ }
58
+ }
59
+ return shorten(fallback, maxChars);
60
+ }
61
+
62
+ function cleanLine(value) {
63
+ return value
64
+ .replace(/^#{1,6}\s+/, '')
65
+ .replace(/^(?:[-*+]\s+|\d+\.\s+|\d+[)\u3001]\s*)/, '')
66
+ .replace(FILE_TOKEN, (_match, quoted, singleQuoted, bare) =>
67
+ String(quoted || singleQuoted || bare || '').replace(/\\/g, '/').split('/').pop())
68
+ .replace(/@(context|compile-log)\b/g, ' ')
69
+ .replace(/!?\[([^\]]*)\]\([^\n)]*\)/g, '$1')
70
+ .replace(/`([^`]+)`/g, '$1')
71
+ .replace(/\*\*([^*]+)\*\*/g, '$1')
72
+ .replace(/https?:\/\/\S+/gi, address => {
73
+ try { return new URL(address).hostname; } catch (_error) { return ''; }
74
+ })
75
+ .replace(/\[(?:REDACTED_SECRET|local path(?::\d+)?)\]/gi, ' ')
76
+ .replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, '')
77
+ .replace(/\s+/g, ' ').trim()
78
+ .replace(/^(?:\u8bf7(?:\u4f60)?\u5e2e\u6211|\u8bf7\u4f60|\u5e2e\u6211|\u9ebb\u70e6\u4f60(?:\u5e2e\u6211)?|\u9ebb\u70e6\u5e2e\u6211|please\s+)\s*/i, '');
79
+ }
80
+
81
+ function isScaffolding(value) {
82
+ return /^(?:my request(?: for [^:]+)?|request|task|context|files(?: mentioned by [^:]+)?|attachments|\u6211\u7684\u8bf7\u6c42|\u9700\u6c42|\u4efb\u52a1|\u4e0a\u4e0b\u6587|\u9644\u4ef6|\u6587\u4ef6)$/i.test(value);
83
+ }
84
+
85
+ function isGeneric(value) {
86
+ const text = String(value || '').trim().replace(/[\s.!?\u3002\uff1f\uff01\u2026]+$/u, '');
87
+ if (!text || Array.from(text).length < 2) return true;
88
+ return /^(?:ok(?:ay)?|yes|no|go|done|continue|proceed|thanks?|thank you|hello|hi|test|new task|untitled task|\u597d(?:\u7684)?|\u53ef\u4ee5|\u7ee7\u7eed|\u786e\u8ba4|\u786e\u5b9a|\u6536\u5230|\u8c22\u8c22|\u6d4b\u8bd5|\u6267\u884c|\u662f\u7684|\u4f60\u597d)$/i.test(text)
89
+ || /^[A-Za-z0-9_+-]{1,24} snippet$/i.test(text)
90
+ || /^[^\s]+\.(?:tex|bib|md|txt|pdf|svg|png|jpe?g|webp)(?::\d+)?$/i.test(text)
91
+ || /^(?:[A-Za-z0-9-]+\.)+[A-Za-z]{2,}$/i.test(text);
92
+ }
93
+
94
+ function shorten(value, maxChars) {
95
+ const title = String(value || '').trim();
96
+ if (!title) return '';
97
+ const chars = Number.isFinite(maxChars) && maxChars > 0 ? Math.min(40, Math.floor(maxChars)) : 24;
98
+ const maxUnits = chars * 2;
99
+ const glyphs = segmenter
100
+ ? Array.from(segmenter.segment(title), item => item.segment) : Array.from(title);
101
+ const width = glyph => /[^\u0000-\u00ff]/u.test(glyph) ? 2 : 1;
102
+ if (title.length <= STORAGE_LIMIT && glyphs.reduce((total, glyph) => total + width(glyph), 0) <= maxUnits) return title;
103
+ let prefix = '';
104
+ let units = 0;
105
+ let index = 0;
106
+ for (; index < glyphs.length; index++) {
107
+ const glyph = glyphs[index];
108
+ if (units + width(glyph) > maxUnits - 2 || prefix.length + glyph.length > STORAGE_LIMIT - 1) break;
109
+ prefix += glyph;
110
+ units += width(glyph);
111
+ }
112
+ const boundary = prefix.lastIndexOf(' ');
113
+ if (/[A-Za-z0-9]$/.test(prefix) && /^[A-Za-z0-9]/.test(glyphs[index] || '') && boundary >= prefix.length * 0.6) {
114
+ prefix = prefix.slice(0, boundary);
115
+ }
116
+ return prefix.trimEnd().replace(/[,;:\uff0c\uff1b\uff1a]+$/u, '') + '\u2026';
117
+ }
118
+
119
+ return Object.freeze({ derive, fromPrompt, isGeneric });
120
+ });
@@ -1,18 +1,19 @@
1
1
  (function initStorageDb(root, factory) {
2
2
  if (typeof module === 'object' && module.exports) {
3
3
  module.exports = factory(require('./storageRunActions'), require('./runInputQueue'),
4
- require('./settlementFacts'), require('./storageSessionClaims'));
4
+ require('./settlementFacts'), require('./storageSessionClaims'), require('./sessionState'));
5
5
  } else {
6
6
  root.CodexOverleafModuleRegistry.define('StorageDb', [
7
7
  'StorageRunActions',
8
8
  'RunInputQueue',
9
9
  'SettlementFacts',
10
- 'StorageSessionClaims'
10
+ 'StorageSessionClaims',
11
+ 'SessionState'
11
12
  ], factory);
12
13
  }
13
- })(typeof globalThis !== 'undefined' ? globalThis : window, function storageDbFactory(StorageRunActions, RunInputQueue, SettlementFacts, StorageSessionClaims) {
14
+ })(typeof globalThis !== 'undefined' ? globalThis : window, function storageDbFactory(StorageRunActions, RunInputQueue, SettlementFacts, StorageSessionClaims, SessionState) {
14
15
  'use strict';
15
- if (!StorageRunActions?.compactRunsForStorage || !StorageRunActions?.compactRunActionPayload || !StorageRunActions?.compactProviderSnapshot || !StorageRunActions?.compactStructuredEventValue || !StorageRunActions?.hashString || !RunInputQueue?.compactForStorage || !SettlementFacts?.compactSettlementFacts || !StorageSessionClaims?.createSessionClaimer) {
16
+ if (!StorageRunActions?.compactRunsForStorage || !StorageRunActions?.compactRunActionPayload || !StorageRunActions?.compactProviderSnapshot || !StorageRunActions?.compactRunExecutionSnapshot || !StorageRunActions?.compactStructuredEventValue || !StorageRunActions?.hashString || !RunInputQueue?.compactForStorage || !SettlementFacts?.compactSettlementFacts || !StorageSessionClaims?.createSessionClaimer) {
16
17
  throw new Error('Codex Overleaf storage run-action helpers are unavailable.');
17
18
  }
18
19
  // IndexedDB versions are monotonic; v2.0 RC profiles have already opened v3.
@@ -279,6 +280,7 @@
279
280
  codexThreadId: typeof input.codexThreadId === 'string' ? input.codexThreadId : '',
280
281
  status: typeof input.status === 'string' && input.status ? input.status : 'active',
281
282
  focusFiles: normalizePathList(input.focusFiles),
283
+ projectReferenceFiles: SessionState.normalizeProjectReferenceFiles(input.projectReferenceFiles),
282
284
  pendingInputs: RunInputQueue.compactForStorage(input.pendingInputs, {
283
285
  normalizeField: normalizeTextField,
284
286
  normalizeDisplay: normalizeDisplayTextForStorage,
@@ -650,20 +652,15 @@
650
652
  }
651
653
 
652
654
  function compactRunsForStorage(runs, options) {
653
- return StorageRunActions.compactRunsForStorage(
654
- runs,
655
- options,
656
- SESSION_STORAGE_LIMITS.maxRunsPerSession,
657
- compactRunForStorage
658
- );
655
+ return StorageRunActions.compactRunsForStorage(runs, options, SESSION_STORAGE_LIMITS.maxRunsPerSession, compactRunForStorage);
659
656
  }
660
-
661
657
  function compactRunForStorage(run, keepActionPayload) {
662
658
  var actionPayload = StorageRunActions.compactRunActionPayload(run, keepActionPayload);
663
659
  var compact = {
664
660
  id: run.id,
665
661
  task: normalizeDisplayTextForStorage(run.task || 'untitled task', SESSION_STORAGE_LIMITS.taskChars),
666
662
  mode: typeof run.mode === 'string' ? redactSecretLikeText(run.mode) : '', ...StorageRunActions.compactProviderSnapshot(run),
663
+ ...StorageRunActions.compactRunExecutionSnapshot(run, function (text) { return normalizeTextField(text, 4096); }),
667
664
  model: normalizeTextField(run.model, 80),
668
665
  reasoningEffort: typeof run.reasoningEffort === 'string' ? redactSecretLikeText(run.reasoningEffort) : '',
669
666
  speedTier: typeof run.speedTier === 'string' ? redactSecretLikeText(run.speedTier) : '',
@@ -679,9 +676,10 @@
679
676
  undoBaseFiles: actionPayload.undoBaseFiles,
680
677
  undoTrackedChanges: actionPayload.undoTrackedChanges,
681
678
  undoExpectedFiles: actionPayload.undoExpectedFiles,
679
+ ...(actionPayload.trackedChangeCaptures ? { trackedChangeCaptures: actionPayload.trackedChangeCaptures } : {}),
682
680
  undoStatus: normalizeDisplayTextForStorage(run.undoStatus, SESSION_STORAGE_LIMITS.statusTextChars),
683
681
  nativeRequestId: normalizeTextField(run.nativeRequestId, 160),
684
- codexTurnId: normalizeTextField(run.codexTurnId, 160),
682
+ codexThreadId: normalizeTextField(run.codexThreadId, 160), codexTurnId: normalizeTextField(run.codexTurnId, 160),
685
683
  nativeEventSeq: Number.isFinite(Number(run.nativeEventSeq)) ? Number(run.nativeEventSeq) : 0,
686
684
  queueItemId: normalizeTextField(run.queueItemId, 160)
687
685
  };
@@ -70,7 +70,13 @@
70
70
  } catch (_error) {
71
71
  return empty;
72
72
  }
73
- return getUtf8ByteLength(serialized) <= MAX_PERSISTED_ACTION_BYTES_PER_RUN ? payload : empty;
73
+ if (getUtf8ByteLength(serialized) > MAX_PERSISTED_ACTION_BYTES_PER_RUN) return empty;
74
+ const captures = cloneSerializableArray(run.trackedChangeCaptures);
75
+ if (captures.length) {
76
+ const extended = { ...payload, trackedChangeCaptures: captures };
77
+ if (getUtf8ByteLength(JSON.stringify(extended)) <= MAX_PERSISTED_ACTION_BYTES_PER_RUN) return extended;
78
+ }
79
+ return payload; // Optional capture evidence must never evict a mature Undo payload.
74
80
  }
75
81
 
76
82
  function emptyActionPayload() {
@@ -102,6 +108,27 @@
102
108
  return value.length * 2;
103
109
  }
104
110
 
111
+ // Persist only execution metadata; never infer a missing historical setting.
112
+ function compactRunExecutionSnapshot(run, normalizeString) {
113
+ var snapshot = run && run.executionSnapshot;
114
+ if (!snapshot || typeof snapshot !== 'object' || Array.isArray(snapshot)) return {};
115
+ var normalize = typeof normalizeString === 'function' ? normalizeString : String;
116
+ var compact = {};
117
+ var fields = ['schemaVersion', 'mode', 'providerId', 'providerRevision', 'model',
118
+ 'reasoningEffort', 'speedTier', 'autoRecompile', 'requireReviewing', 'capturedAt', 'source'];
119
+ for (var key of fields) {
120
+ if (!Object.prototype.hasOwnProperty.call(snapshot, key)) continue;
121
+ var value = snapshot[key];
122
+ if (typeof value === 'string') compact[key] = normalize(value);
123
+ else if (value === null || typeof value === 'number' || typeof value === 'boolean') compact[key] = value;
124
+ }
125
+ if (Array.isArray(snapshot.focusFiles)) {
126
+ compact.focusFiles = snapshot.focusFiles.slice(0, 100)
127
+ .filter(function (value) { return typeof value === 'string'; }).map(normalize);
128
+ }
129
+ return { executionSnapshot: compact };
130
+ }
131
+
105
132
  function compactProviderSnapshot(value) {
106
133
  return {
107
134
  providerId: typeof value.providerId === 'string' && value.providerId ? value.providerId : 'builtin',
@@ -147,6 +174,7 @@
147
174
  compactRunsForStorage: compactRunsForStorage,
148
175
  compactRunActionPayload: compactRunActionPayload,
149
176
  compactProviderSnapshot: compactProviderSnapshot,
177
+ compactRunExecutionSnapshot: compactRunExecutionSnapshot,
150
178
  hashString: hashString
151
179
  };
152
180
  });
@@ -0,0 +1,32 @@
1
+ (function (root, factory) {
2
+ const api = factory();
3
+ if (typeof module === 'object' && module.exports) module.exports = api;
4
+ if (root) root.CodexOverleafUpdateRuntimeIdentity = api;
5
+ })(typeof globalThis !== 'undefined' ? globalThis : this, function () {
6
+ 'use strict';
7
+
8
+ function version(value) { return /^\d+\.\d+\.\d+$/.test(String(value || '')) ? String(value) : ''; }
9
+ function compare(left, right) {
10
+ const a = left.split('.').map(Number);
11
+ const b = right.split('.').map(Number);
12
+ return a[0] - b[0] || a[1] - b[1] || a[2] - b[2];
13
+ }
14
+
15
+ function inspectInstalledRuntime(status = {}, options = {}) {
16
+ const result = { installedVersion: version(status.activeVersion), nativeVersion: version(status.runtimeVersion),
17
+ extensionVersion: version(options.extensionVersion), runtimeVersion: version(options.runtimeVersion), state: 'unknown' };
18
+ if (!status.managed || !result.installedVersion || !result.nativeVersion ||
19
+ !result.extensionVersion || !result.runtimeVersion) return result;
20
+ if (['authorized', 'bound'].includes(status.authorization?.state) ||
21
+ ['staged', 'activation_pending', 'applying', 'awaiting_health'].includes(status.transaction?.state)) {
22
+ return { ...result, state: 'transaction_active' };
23
+ }
24
+ if (status.installedAligned !== true) return { ...result, state: 'repair_required' };
25
+ const loaded = [result.nativeVersion, result.extensionVersion, result.runtimeVersion];
26
+ if (loaded.every(value => value === result.installedVersion)) return { ...result, state: 'aligned' };
27
+ return { ...result, state: loaded.every(value => compare(result.installedVersion, value) >= 0)
28
+ ? 'reload_required' : 'repair_required' };
29
+ }
30
+
31
+ return Object.freeze({ inspectInstalledRuntime });
32
+ });
@@ -397,7 +397,7 @@
397
397
  if (!result || typeof result !== 'object' || result.ok === false) return result;
398
398
  if (Array.isArray(result.skipped) && result.skipped.length > 0) return result;
399
399
  const expectedFiles = Array.isArray(run?.undoExpectedFiles) ? run.undoExpectedFiles : [];
400
- if (!expectedFiles.length || isUndoVerifiedContentMatching(run, result)) return result;
400
+ if (!expectedFiles.length || isUndoVerifiedContentMatching(run, result, options)) return result;
401
401
  const path = expectedFiles.find(entry => typeof entry?.path === 'string')?.path || '';
402
402
  appendSyntheticFailure(result, options.buildFailure?.('undo_not_verified', { path, type: 'undo' }, {
403
403
  changedDocument: true,
@@ -407,15 +407,18 @@
407
407
  return result;
408
408
  }
409
409
 
410
- function isUndoVerifiedContentMatching(run, result) {
410
+ function isUndoVerifiedContentMatching(run, result, options = {}) {
411
411
  const expectedByPath = new Map((run?.undoExpectedFiles || [])
412
412
  .filter(file => typeof file?.path === 'string' && typeof file?.content === 'string')
413
413
  .map(file => [normalizeSettlementPath(file.path), file.content])
414
414
  .filter(([path]) => path));
415
415
  if (!expectedByPath.size) return true;
416
+ const postByPath = new Map((Array.isArray(options?.postFiles) ? options.postFiles : [])
417
+ .filter(file => typeof file?.path === 'string' && typeof file?.content === 'string')
418
+ .map(file => [normalizeSettlementPath(file.path), file.content]));
416
419
  const applied = Array.isArray(result?.applied) ? result.applied : [];
417
420
  return Array.from(expectedByPath).every(([path, expected]) =>
418
- applied.some(entry => isVerifiedUndoEntryForPath(entry, path, expected))
421
+ applied.some(entry => isVerifiedUndoEntryForPath(entry, path, expected, postByPath.get(path)))
419
422
  );
420
423
  }
421
424
 
@@ -438,7 +441,7 @@
438
441
  ));
439
442
  }
440
443
 
441
- function isVerifiedUndoEntryForPath(entry, path, expectedContent) {
444
+ function isVerifiedUndoEntryForPath(entry, path, expectedContent, postContent) {
442
445
  const inner = entry?.result || {};
443
446
  if (inner.ok === false) return false;
444
447
  const normalizedPath = normalizeSettlementPath(path);
@@ -456,7 +459,18 @@
456
459
  && inner.verifiedContent === expectedContent;
457
460
  }
458
461
  if (key === `snapshot-undo:${normalizedPath}`) {
459
- return inner.verifiedContent === expectedContent;
462
+ if (inner.verifiedContent === expectedContent) return true;
463
+ const proof = inner.undoRebaseProof, before = proof?.beforeUndoContent;
464
+ if (inner.verified !== true || proof?.version !== 1
465
+ || normalizeSettlementPath(proof.path) !== normalizedPath
466
+ || typeof before !== 'string' || typeof postContent !== 'string' || !postContent
467
+ || typeof inner.verifiedContent !== 'string') return false;
468
+ // The page observed prefix + post + suffix before Undo. Prove that only
469
+ // this run's post-image became its pre-image, preserving both envelopes.
470
+ const start = before.indexOf(postContent);
471
+ if (start < 0 || before.indexOf(postContent, start + 1) >= 0) return false;
472
+ return inner.verifiedContent === before.slice(0, start) + expectedContent
473
+ + before.slice(start + postContent.length);
460
474
  }
461
475
  return false;
462
476
  }