codex-overleaf-link 2.5.0 → 2.5.1

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 (168) hide show
  1. package/README.md +38 -26
  2. package/README.zh-CN.md +39 -27
  3. package/extension/bootstrap/background.js +52 -9
  4. package/extension/bootstrap/manifest.template.json +1 -1
  5. package/extension/bootstrap/popup.html +1 -0
  6. package/extension/bootstrap/popupFallback.js +55 -0
  7. package/extension/bootstrap/updateStatus.js +1 -0
  8. package/extension/src/background.js +206 -53
  9. package/extension/src/backgroundBootstrapMigration.js +181 -0
  10. package/extension/src/backgroundSharedSessions.js +74 -14
  11. package/extension/src/backgroundUpdateCoordinator.js +118 -21
  12. package/extension/src/backgroundUpdateSafety.js +173 -0
  13. package/extension/src/content/activeTurnControl.js +8 -2
  14. package/extension/src/content/applyResultFormatters.js +46 -73
  15. package/extension/src/content/assetTransferBroker.js +72 -14
  16. package/extension/src/content/composerAttachments.js +93 -45
  17. package/extension/src/content/composerPanel.js +18 -0
  18. package/extension/src/content/contentRuntime.js +661 -676
  19. package/extension/src/content/diagnosticsController.js +122 -96
  20. package/extension/src/content/diagnosticsExport.js +19 -5
  21. package/extension/src/content/diagnosticsPanel.js +30 -0
  22. package/extension/src/content/directoryCleanupRecovery.js +54 -0
  23. package/extension/src/content/generated/content.bundle.js +6879 -2786
  24. package/extension/src/content/generated/content.bundle.meta.json +11 -3
  25. package/extension/src/content/globalPreferencesController.js +1 -1
  26. package/extension/src/content/legacyGlobalRegistry.js +6 -0
  27. package/extension/src/content/localSkillsPanel.js +102 -12
  28. package/extension/src/content/markdownText.js +5 -6
  29. package/extension/src/content/modelPicker.js +7 -4
  30. package/extension/src/content/nativeCompatibilityController.js +45 -20
  31. package/extension/src/content/otWarmMirror.js +4 -5
  32. package/extension/src/content/otWarmMirrorController.js +17 -20
  33. package/extension/src/content/pageBridgeClient.js +15 -2
  34. package/extension/src/content/panelMaintenance.js +40 -12
  35. package/extension/src/content/panelRenderer.js +6 -0
  36. package/extension/src/content/projectProviderSelection.js +13 -4
  37. package/extension/src/content/projectSessionCleanup.js +109 -62
  38. package/extension/src/content/projectSettingsCoordinator.js +39 -2
  39. package/extension/src/content/providerSettingsCoordinator.js +46 -8
  40. package/extension/src/content/providerSettingsDialog.js +71 -13
  41. package/extension/src/content/recentProjects.js +11 -5
  42. package/extension/src/content/runActivitySummary.js +40 -6
  43. package/extension/src/content/runCompletionMeta.js +344 -0
  44. package/extension/src/content/runController.js +37 -14
  45. package/extension/src/content/runFailureNotice.js +21 -12
  46. package/extension/src/content/runGuidanceController.js +72 -27
  47. package/extension/src/content/runGuidanceView.js +59 -10
  48. package/extension/src/content/runQueueScheduler.js +148 -42
  49. package/extension/src/content/runRecoveryActions.js +146 -0
  50. package/extension/src/content/runResultActions.js +8 -125
  51. package/extension/src/content/runReviewController.js +574 -0
  52. package/extension/src/content/runScrollLayout.js +21 -2
  53. package/extension/src/content/runTimelineView.js +146 -153
  54. package/extension/src/content/scopedPersistencePanelState.js +51 -6
  55. package/extension/src/content/selectionContextView.js +1 -1
  56. package/extension/src/content/sensitivePreflight.js +140 -0
  57. package/extension/src/content/sessionForkController.js +80 -17
  58. package/extension/src/content/sessionManager.js +1 -1
  59. package/extension/src/content/settingsPanel.js +7 -3
  60. package/extension/src/content/settingsWorkbench.js +5 -0
  61. package/extension/src/content/sharedSessionViewSync.js +45 -8
  62. package/extension/src/content/updateIdle.js +4 -1
  63. package/extension/src/content/updateNotice.js +60 -8
  64. package/extension/src/content/updateSafetyReceiver.js +58 -0
  65. package/extension/src/content/writebackController.js +53 -4
  66. package/extension/src/content/writebackOrchestrator.js +82 -73
  67. package/extension/src/content/writingStyleSettings.js +35 -11
  68. package/extension/src/page/binaryAssetUploader.js +136 -14
  69. package/extension/src/page/fileMover.js +256 -0
  70. package/extension/src/page/ownedDirectoryCleanup.js +94 -0
  71. package/extension/src/page/referenceProjects.js +1 -0
  72. package/extension/src/page/reviewContext.js +113 -0
  73. package/extension/src/page/saveState.js +6 -4
  74. package/extension/src/page/snapshotRouter.js +84 -39
  75. package/extension/src/page/textFileCreator.js +105 -25
  76. package/extension/src/page/trackedChangeCapture.js +12 -2
  77. package/extension/src/page/trackedChangeOwnership.js +19 -1
  78. package/extension/src/page/trackedChangeReplay.js +41 -21
  79. package/extension/src/page/trackedChangesLifecycle.js +217 -199
  80. package/extension/src/page/treeOperations.js +7 -10
  81. package/extension/src/page/updateSafetyProbe.js +41 -0
  82. package/extension/src/page/writebackReceiptJournal.js +2 -0
  83. package/extension/src/page/writebackRouter.js +80 -79
  84. package/extension/src/pageBridge.js +19 -3
  85. package/extension/src/popup.js +62 -10
  86. package/extension/src/shared/agentTranscript.js +23 -0
  87. package/extension/src/shared/auditRecords.js +2 -0
  88. package/extension/src/shared/compatibility.js +22 -6
  89. package/extension/src/shared/compileAdapter.js +79 -57
  90. package/extension/src/shared/composerAttachmentDraft.js +151 -0
  91. package/extension/src/shared/directoryCleanupProof.js +43 -0
  92. package/extension/src/shared/failureReasons.js +309 -3
  93. package/extension/src/shared/globalPreferences.js +30 -13
  94. package/extension/src/shared/governanceRules.js +4 -37
  95. package/extension/src/shared/i18n.js +298 -2
  96. package/extension/src/shared/managedUpdateProjection.js +2 -2
  97. package/extension/src/shared/nativeRequestIdentity.js +1 -0
  98. package/extension/src/shared/pageRpcContract.js +5 -4
  99. package/extension/src/shared/projectFiles.js +21 -44
  100. package/extension/src/shared/providerProfiles.js +5 -3
  101. package/extension/src/shared/runInputQueue.js +48 -5
  102. package/extension/src/shared/selectionContext.js +12 -3
  103. package/extension/src/shared/sessionState.js +72 -27
  104. package/extension/src/shared/settlementFacts.js +10 -2
  105. package/extension/src/shared/sharedSessionBridge.js +37 -14
  106. package/extension/src/shared/sharedSessionTransfer.js +140 -0
  107. package/extension/src/shared/staleGuard.js +118 -13
  108. package/extension/src/shared/storageDb.js +58 -26
  109. package/extension/src/shared/storageMigration.js +71 -111
  110. package/extension/src/shared/storageRunActions.js +209 -26
  111. package/extension/src/shared/subagentActivityModel.js +1 -1
  112. package/extension/src/shared/undoOperations.js +136 -13
  113. package/extension/src/shared/updateConsent.js +6 -4
  114. package/extension/src/shared/updateRuntimeIdentity.js +1 -1
  115. package/extension/src/shared/writebackIntent.js +16 -0
  116. package/extension/src/shared/writebackOperationEvidence.js +4 -15
  117. package/extension/src/shared/writebackSettlement.js +38 -38
  118. package/extension/styles/panel.css +19 -5
  119. package/native-host/src/anthropicBridgeRequest.js +4 -2
  120. package/native-host/src/anthropicBridgeResponse.js +6 -2
  121. package/native-host/src/anthropicMessagesBridge.js +24 -16
  122. package/native-host/src/bootstrapMigration.js +296 -0
  123. package/native-host/src/bootstrapMigrationContract.js +101 -0
  124. package/native-host/src/bootstrapUpdate.js +233 -0
  125. package/native-host/src/bootstrapUpdateWatchdog.js +44 -0
  126. package/native-host/src/chatBridgeResponse.js +10 -0
  127. package/native-host/src/chatCompletionsBridge.js +21 -15
  128. package/native-host/src/codexConfigPaths.js +106 -0
  129. package/native-host/src/codexHome.js +66 -73
  130. package/native-host/src/codexHomeScope.js +124 -0
  131. package/native-host/src/codexInheritedAuth.js +90 -0
  132. package/native-host/src/codexLocalSkills.js +301 -0
  133. package/native-host/src/codexPromptAssembly.js +31 -12
  134. package/native-host/src/codexProviderTest.js +24 -26
  135. package/native-host/src/codexSessionRunner.js +110 -183
  136. package/native-host/src/codexSessionTiming.js +18 -8
  137. package/native-host/src/codexSkillIsolation.js +170 -0
  138. package/native-host/src/codexThreadFork.js +19 -3
  139. package/native-host/src/index.js +19 -7
  140. package/native-host/src/managedInstall.js +110 -35
  141. package/native-host/src/managedInstallRecovery.js +140 -0
  142. package/native-host/src/managedRuntimeEvidence.js +57 -0
  143. package/native-host/src/mirrorWorkspace.js +92 -22
  144. package/native-host/src/nativeDoctor.js +15 -0
  145. package/native-host/src/nativeHelperLifecycle.js +21 -0
  146. package/native-host/src/nativeHostRegistration.js +33 -0
  147. package/native-host/src/nativeRunLifecycle.js +71 -0
  148. package/native-host/src/processStopBarrier.js +42 -7
  149. package/native-host/src/projectPathPlatform.js +30 -0
  150. package/native-host/src/providerHttp.js +80 -0
  151. package/native-host/src/providerProfile.js +4 -2
  152. package/native-host/src/providerReasoning.js +2 -7
  153. package/native-host/src/providerRuntime.js +1 -0
  154. package/native-host/src/providerStore.js +72 -14
  155. package/native-host/src/responsesPassthroughBridge.js +266 -52
  156. package/native-host/src/runtimeInstaller.js +21 -1
  157. package/native-host/src/selectionScope.js +36 -8
  158. package/native-host/src/subagentAdmission.js +26 -0
  159. package/native-host/src/subagentBroker.js +79 -66
  160. package/native-host/src/subagentTelemetry.js +19 -10
  161. package/native-host/src/subagentWorkspacePath.js +5 -2
  162. package/native-host/src/taskRunnerRuntime.js +77 -54
  163. package/native-host/src/tomlStatements.js +140 -0
  164. package/native-host/src/updateManager.js +297 -39
  165. package/native-host/src/writingStyleRuntime.js +57 -29
  166. package/native-host/src/writingStyleStore.js +73 -0
  167. package/package.json +1 -1
  168. package/scripts/install-managed.mjs +1 -0
@@ -0,0 +1,140 @@
1
+ (function (root, factory) {
2
+ if (typeof module === 'object' && module.exports) module.exports = factory();
3
+ else root.CodexOverleafModuleRegistry.define('SharedSessionTransfer', [], factory);
4
+ })(typeof globalThis !== 'undefined' ? globalThis : window, function () {
5
+ 'use strict';
6
+ const MAX_WIRE_BYTES = 24 * 1024 * 1024;
7
+ // The old UTF-16 estimate admitted up to 3 UTF-8 bytes per code unit.
8
+ // Retain that finite Unicode window without enlarging ordinary ASCII writes.
9
+ const MAX_RECORD_BYTES = MAX_WIRE_BYTES * 3 / 2;
10
+ const MAX_RESPONSE_BYTES = MAX_RECORD_BYTES;
11
+ const MAX_DECODED_BYTES = 2 * MAX_RECORD_BYTES + 64 * 1024;
12
+ const error = (code, message) => Object.assign(new Error(message), { code });
13
+ const tooLarge = () => error('shared_session_too_large', 'The session batch exceeds the bounded shared storage transfer limit.');
14
+ const invalid = () => error('shared_session_transfer_invalid', 'The shared history transfer is invalid or incomplete.');
15
+ const byteLength = text => new TextEncoder().encode(text).byteLength;
16
+
17
+ function serialize(value, limit) {
18
+ // Count a lower bound during serialization, before a large aggregate string
19
+ // can be produced. The final UTF-8 check also includes commas and escaping.
20
+ let lowerBound = 0;
21
+ const text = JSON.stringify(value, function (key, item) {
22
+ if (key && !Array.isArray(this)) lowerBound += byteLength(JSON.stringify(key)) + 1;
23
+ if (typeof item === 'string') {
24
+ if (item.length > limit) throw tooLarge();
25
+ lowerBound += byteLength(JSON.stringify(item));
26
+ } else if (item === null) lowerBound += 4;
27
+ else if (typeof item === 'number') lowerBound += Number.isFinite(item) ? String(item).length : 4;
28
+ else if (typeof item === 'boolean') lowerBound += String(item).length;
29
+ else if (item && typeof item === 'object') lowerBound += 2;
30
+ if (lowerBound > limit) throw tooLarge();
31
+ return item;
32
+ });
33
+ if (typeof text !== 'string') throw invalid();
34
+ const bytes = byteLength(text);
35
+ if (bytes > limit) throw tooLarge();
36
+ return { text, bytes };
37
+ }
38
+
39
+ function validateRecord(record) {
40
+ const value = serialize(record, MAX_RECORD_BYTES);
41
+ if (value.bytes > MAX_WIRE_BYTES && value.text.length * 2 > MAX_WIRE_BYTES) throw tooLarge();
42
+ }
43
+
44
+ function validateResponse(records) { serialize(records, MAX_RESPONSE_BYTES); }
45
+
46
+ function validateEntries(entries) {
47
+ if (!Array.isArray(entries)) throw invalid();
48
+ for (const entry of entries) {
49
+ if (!entry || !entry.record || typeof entry.record !== 'object' || Array.isArray(entry.record)) throw invalid();
50
+ validateRecord(entry.record);
51
+ if (entry.base != null) {
52
+ if (typeof entry.base !== 'object' || Array.isArray(entry.base)) throw invalid();
53
+ // A previously stored merged baseline may be larger than new records;
54
+ // allow a bounded old base so a smaller candidate can repair it.
55
+ serialize(entry.base, MAX_DECODED_BYTES);
56
+ }
57
+ }
58
+ }
59
+
60
+ async function readLimited(stream, limit) {
61
+ const reader = stream.getReader(), chunks = [];
62
+ let length = 0;
63
+ try {
64
+ while (true) {
65
+ const result = await reader.read();
66
+ if (result.done) break;
67
+ length += result.value.byteLength;
68
+ if (length > limit) { await reader.cancel().catch(() => {}); throw tooLarge(); }
69
+ chunks.push(result.value);
70
+ }
71
+ } finally { reader.releaseLock(); }
72
+ const output = new Uint8Array(length); let offset = 0;
73
+ for (const chunk of chunks) { output.set(chunk, offset); offset += chunk.byteLength; }
74
+ return output;
75
+ }
76
+
77
+ function toBase64(bytes) {
78
+ let text = '';
79
+ for (let i = 0; i < bytes.length; i += 0x8000) text += String.fromCharCode(...bytes.subarray(i, i + 0x8000));
80
+ return btoa(text);
81
+ }
82
+ function fromBase64(value) {
83
+ if (typeof value !== 'string' || !value.length || value.length % 4
84
+ || !/^[A-Za-z0-9+/]*={0,2}$/.test(value)) throw invalid();
85
+ const text = atob(value);
86
+ const bytes = new Uint8Array(text.length);
87
+ for (let i = 0; i < text.length; i++) bytes[i] = text.charCodeAt(i);
88
+ return bytes;
89
+ }
90
+
91
+ async function encode(entries, environment = globalThis) {
92
+ validateEntries(entries);
93
+ const raw = serialize(entries, MAX_DECODED_BYTES);
94
+ if (raw.bytes <= MAX_WIRE_BYTES || (raw.bytes <= MAX_RECORD_BYTES && raw.text.length * 2 <= MAX_WIRE_BYTES)) {
95
+ return { method: 'put', payload: { entries } };
96
+ }
97
+ if (typeof environment.CompressionStream !== 'function') {
98
+ throw error('shared_session_compression_unavailable',
99
+ 'Lossless transfer compression is unavailable. History remains in memory; retry with a supported browser.');
100
+ }
101
+ const input = new Blob([raw.text]).stream();
102
+ const packed = await readLimited(input.pipeThrough(new environment.CompressionStream('gzip')),
103
+ Math.floor((MAX_WIRE_BYTES - 1024) * 3 / 4));
104
+ const payload = { encoding: 'gzip-base64', version: 1, decodedBytes: raw.bytes, data: toBase64(packed) };
105
+ serialize(payload, MAX_WIRE_BYTES);
106
+ // A distinct method makes an older worker reject rather than acknowledge
107
+ // an absent `entries` array as a successful empty write.
108
+ return { method: 'put-compressed', payload };
109
+ }
110
+
111
+ async function decode(message, environment = globalThis) {
112
+ if (message.method !== 'put-compressed') {
113
+ const entries = message.method === 'import' ? [{ record: message.record }] : message.entries || [];
114
+ const raw = serialize(entries, MAX_RECORD_BYTES);
115
+ if (raw.bytes > MAX_WIRE_BYTES && raw.text.length * 2 > MAX_WIRE_BYTES) throw tooLarge();
116
+ validateEntries(entries); return entries;
117
+ }
118
+ const payload = { encoding: message.encoding, version: message.version, decodedBytes: message.decodedBytes, data: message.data };
119
+ serialize(payload, MAX_WIRE_BYTES);
120
+ if (message.entries != null || payload.encoding !== 'gzip-base64' || payload.version !== 1
121
+ || !Number.isSafeInteger(payload.decodedBytes) || payload.decodedBytes < 1 || payload.decodedBytes > MAX_DECODED_BYTES) throw invalid();
122
+ if (typeof environment.DecompressionStream !== 'function') throw error('shared_session_compression_unavailable',
123
+ 'Lossless transfer decompression is unavailable. Reload the extension and retry.');
124
+ let bytes;
125
+ try {
126
+ bytes = await readLimited(new Blob([fromBase64(payload.data)]).stream()
127
+ .pipeThrough(new environment.DecompressionStream('gzip')), MAX_DECODED_BYTES);
128
+ } catch (failure) {
129
+ if (['shared_session_too_large', 'shared_session_transfer_invalid'].includes(failure?.code)) throw failure;
130
+ throw invalid();
131
+ }
132
+ if (bytes.byteLength !== payload.decodedBytes) throw invalid();
133
+ let entries;
134
+ try { entries = JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(bytes)); }
135
+ catch (_error) { throw invalid(); }
136
+ validateEntries(entries);
137
+ return entries;
138
+ }
139
+ return { MAX_WIRE_BYTES, MAX_RECORD_BYTES, MAX_RESPONSE_BYTES, MAX_DECODED_BYTES, byteLength, validateRecord, validateResponse, encode, decode };
140
+ });
@@ -17,7 +17,9 @@
17
17
  if (typeof file?.path === 'string') {
18
18
  const path = normalizePath(file.path);
19
19
  if (path) {
20
- lookup.set(path, normalizeText(file.content));
20
+ // Patch offsets refer to this exact UTF-16 string. Normalize only
21
+ // for comparisons, never discard the source coordinate system.
22
+ lookup.set(path, String(file.content ?? ''));
21
23
  }
22
24
  }
23
25
  }
@@ -40,13 +42,7 @@
40
42
  };
41
43
  }
42
44
 
43
- const baseContent = baseFileLookup.get(filePath);
44
- if (!baseContent || baseContent.trim().length === 0) {
45
- if (typeof console !== 'undefined') {
46
- console.warn('[codex-overleaf] staleGuard: base empty for', filePath, '→ skipping check');
47
- }
48
- return { ok: true };
49
- }
45
+ const baseContent = normalizeText(baseFileLookup.get(filePath));
50
46
  const current = normalizeText(currentContent);
51
47
  if (current !== baseContent) {
52
48
  if (typeof console !== 'undefined') {
@@ -55,7 +51,7 @@
55
51
  'base[0..80]:', JSON.stringify(baseContent.slice(0, 80)),
56
52
  'current[0..80]:', JSON.stringify(current.slice(0, 80)));
57
53
  }
58
- const patchRangeFreshness = checkPatchRangeFreshness(operation, current);
54
+ const patchRangeFreshness = checkPatchRangeFreshness(operation, currentContent, baseFileLookup);
59
55
  if (patchRangeFreshness) {
60
56
  return patchRangeFreshness;
61
57
  }
@@ -71,13 +67,17 @@
71
67
  return { ok: true };
72
68
  }
73
69
 
74
- function checkPatchRangeFreshness(operation, currentContent) {
75
- const patches = Array.isArray(operation?.patches) ? operation.patches : [];
70
+ function checkPatchRangeFreshness(operation, currentContent, baseFileLookup) {
71
+ const prepared = prepareOperationPatches(operation, currentContent, baseFileLookup);
72
+ if (!prepared.ok) {
73
+ return { ...prepared, code: 'stale_patch_range', reasonKey: 'stalePatchConflict' };
74
+ }
75
+ const patches = prepared.patches;
76
76
  if (!patches.length) {
77
77
  return null;
78
78
  }
79
79
 
80
- const current = normalizeText(currentContent);
80
+ const current = String(currentContent ?? '');
81
81
  for (const rawPatch of patches) {
82
82
  const from = Number(rawPatch?.from);
83
83
  const to = Number(rawPatch?.to);
@@ -107,12 +107,116 @@
107
107
  };
108
108
  }
109
109
 
110
+ // Bridge the raw Native baseline to the editor's line-ending representation.
111
+ // Logical LF offsets are only an intermediate coordinate system: both the
112
+ // range check and the eventual editor dispatch use the same raw patches.
113
+ function prepareOperationPatches(operation, currentContent, baseFileLookup) {
114
+ const patches = Array.isArray(operation?.patches) ? operation.patches : [];
115
+ const current = String(currentContent ?? '');
116
+ const path = normalizePath(operation?.path);
117
+ const base = baseFileLookup?.get(path);
118
+ if (!patches.length || typeof base !== 'string' || base === current) {
119
+ return { ok: true, patches };
120
+ }
121
+ const baseMap = lineEndingMap(base);
122
+ const currentMap = lineEndingMap(current);
123
+ // With no CRLF on either side, retain the existing raw range semantics.
124
+ if (!baseMap.removed.length && !currentMap.removed.length) return { ok: true, patches };
125
+
126
+ const groups = [];
127
+ let previousTo = 0;
128
+ for (const patch of patches) {
129
+ const from = Number(patch?.from), to = Number(patch?.to);
130
+ if (!Number.isInteger(from) || !Number.isInteger(to) || from < previousTo || to < from || to > base.length) {
131
+ return patchMappingFailure('invalid_patch');
132
+ }
133
+ if (base.slice(from, to) !== String(patch.expected ?? '')) return patchMappingFailure('stale_patch');
134
+ previousTo = to;
135
+ // Expand only a split CRLF or a new pair formed across an edit edge.
136
+ // Other neighbouring newlines belong to the editor and stay untouched.
137
+ let start = from, end = to;
138
+ const insert = String(patch.insert ?? '');
139
+ if (base[from - 1] === '\r' && (base[from] === '\n' || insert.startsWith('\n') || (!insert && base[to] === '\n'))) start -= 1;
140
+ if (base[to] === '\n' && (base[to - 1] === '\r' || insert.endsWith('\r') || (!insert && base[from - 1] === '\r'))) end += 1;
141
+ const previous = groups[groups.length - 1];
142
+ if (previous && start <= previous.end) {
143
+ previous.end = Math.max(previous.end, end);
144
+ previous.patches.push({ ...patch, from, to });
145
+ } else groups.push({ start, end, patches: [{ ...patch, from, to }] });
146
+ }
147
+
148
+ const mapped = [], sourceChanges = [];
149
+ for (const group of groups) {
150
+ const logicalFrom = baseMap.toLogical(group.start), logicalTo = baseMap.toLogical(group.end);
151
+ if (logicalTo > currentMap.length) return patchMappingFailure('invalid_patch');
152
+ const from = currentMap.toRaw(logicalFrom), to = currentMap.toRaw(logicalTo);
153
+ const expected = current.slice(from, to);
154
+ if (normalizeText(expected) !== normalizeText(base.slice(group.start, group.end))) {
155
+ return patchMappingFailure('stale_patch');
156
+ }
157
+ let insert = base.slice(group.start, group.end);
158
+ for (const patch of group.patches.slice().sort((left, right) => right.from - left.from)) {
159
+ if (insert.slice(patch.from - group.start, patch.to - group.start) !== String(patch.expected ?? '')) {
160
+ return patchMappingFailure('stale_patch');
161
+ }
162
+ insert = insert.slice(0, patch.from - group.start) + String(patch.insert ?? '') + insert.slice(patch.to - group.start);
163
+ }
164
+ sourceChanges.push({ from: group.start, to: group.end, insert });
165
+ const sourceEnding = nearestLineEnding(base, group.start);
166
+ const editorEnding = nearestLineEnding(current, from);
167
+ if (sourceEnding && editorEnding && sourceEnding !== editorEnding) {
168
+ insert = normalizeText(insert).replace(/\n/g, editorEnding);
169
+ }
170
+ // Leave equal surrounding text out of the dispatch/Track range.
171
+ let prefix = 0, oldEnd = expected.length, newEnd = insert.length;
172
+ while (prefix < oldEnd && prefix < newEnd && expected[prefix] === insert[prefix]) prefix += 1;
173
+ while (oldEnd > prefix && newEnd > prefix && expected[oldEnd - 1] === insert[newEnd - 1]) { oldEnd -= 1; newEnd -= 1; }
174
+ mapped.push({ from: from + prefix, to: from + oldEnd, expected: expected.slice(prefix, oldEnd), insert: insert.slice(prefix, newEnd) });
175
+ }
176
+ // A later operation in the same batch still addresses the original
177
+ // representation, so advance its baseline separately from editor text.
178
+ let sourceContent = base;
179
+ for (const patch of sourceChanges.reverse()) {
180
+ sourceContent = sourceContent.slice(0, patch.from) + patch.insert + sourceContent.slice(patch.to);
181
+ }
182
+ return { ok: true, patches: mapped, sourceContent };
183
+ }
184
+
185
+ function patchMappingFailure(code) {
186
+ return { ok: false, code, reason: 'Codex 要修改的位置已经无法和当前 Overleaf 内容对齐,所以没有写入。请重新运行任务。' };
187
+ }
188
+
189
+ function lineEndingMap(text) {
190
+ const removed = [];
191
+ for (let index = 0; index < text.length - 1; index += 1) {
192
+ if (text[index] === '\r' && text[index + 1] === '\n') removed.push(index);
193
+ }
194
+ function countBefore(offset, logical) {
195
+ let low = 0, high = removed.length;
196
+ while (low < high) {
197
+ const middle = (low + high) >>> 1;
198
+ if (removed[middle] - (logical ? middle : 0) < offset) low = middle + 1;
199
+ else high = middle;
200
+ }
201
+ return low;
202
+ }
203
+ return { removed, length: text.length - removed.length,
204
+ toLogical: offset => offset - countBefore(offset, false),
205
+ toRaw: offset => offset + countBefore(offset, true) };
206
+ }
207
+
208
+ function nearestLineEnding(text, offset) {
209
+ let newline = text.indexOf('\n', offset);
210
+ if (newline < 0) newline = text.lastIndexOf('\n', offset - 1);
211
+ return newline < 0 ? '' : text[newline - 1] === '\r' ? '\r\n' : '\n';
212
+ }
213
+
110
214
  function updateExpectedFileContent(baseFileLookup, filePath, content) {
111
215
  const path = normalizePath(filePath);
112
216
  if (!baseFileLookup || !path) {
113
217
  return;
114
218
  }
115
- baseFileLookup.set(path, normalizeText(content));
219
+ baseFileLookup.set(path, String(content ?? ''));
116
220
  }
117
221
 
118
222
  function removeExpectedFile(baseFileLookup, filePath) {
@@ -152,6 +256,7 @@
152
256
  return {
153
257
  buildBaseFileLookup,
154
258
  checkOperationFreshness,
259
+ prepareOperationPatches,
155
260
  updateExpectedFileContent,
156
261
  removeExpectedFile,
157
262
  moveExpectedFile,
@@ -1,7 +1,7 @@
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'), require('./sessionState'), require('./runActivityModel'), require('./sharedSessionBridge'), require('./subagentActivityModel'), require('./storageValuePruning'));
4
+ require('./settlementFacts'), require('./storageSessionClaims'), require('./sessionState'), require('./runActivityModel'), require('./sharedSessionBridge'), require('./subagentActivityModel'), require('./storageValuePruning'), require('./composerAttachmentDraft'), require('./sharedSessionTransfer'));
5
5
  } else {
6
6
  root.CodexOverleafModuleRegistry.define('StorageDb', [
7
7
  'StorageRunActions',
@@ -12,10 +12,12 @@
12
12
  'RunActivityModel',
13
13
  'SharedSessionBridge',
14
14
  'SubagentActivityModel',
15
- 'StorageValuePruning'
15
+ 'StorageValuePruning',
16
+ 'ComposerAttachmentDraft',
17
+ 'SharedSessionTransfer'
16
18
  ], factory);
17
19
  }
18
- })(typeof globalThis !== 'undefined' ? globalThis : window, function storageDbFactory(StorageRunActions, RunInputQueue, SettlementFacts, StorageSessionClaims, SessionState, RunActivityModel, SharedSessionBridge, SubagentActivityModel, StorageValuePruning) {
20
+ })(typeof globalThis !== 'undefined' ? globalThis : window, function storageDbFactory(StorageRunActions, RunInputQueue, SettlementFacts, StorageSessionClaims, SessionState, RunActivityModel, SharedSessionBridge, SubagentActivityModel, StorageValuePruning, ComposerAttachmentDraft, SharedSessionTransfer) {
19
21
  'use strict';
20
22
  const { removeEmptySummaryFields, removeEmptyFields } = StorageValuePruning;
21
23
  if (!StorageRunActions?.compactRunsForStorage || !StorageRunActions?.compactRunActionPayload || !StorageRunActions?.compactProviderSnapshot || !StorageRunActions?.compactRunExecutionSnapshot || !StorageRunActions?.compactStructuredEventValue || !StorageRunActions?.hashString || !RunInputQueue?.compactForStorage || !SettlementFacts?.compactSettlementFacts || !StorageSessionClaims?.createSessionClaimer) {
@@ -140,6 +142,7 @@
140
142
  });
141
143
  }
142
144
  var claimSessionsForAccount = StorageSessionClaims.createSessionClaimer(openDb, function () { return IDBKeyRange; });
145
+ var composerDraftStore = ComposerAttachmentDraft.createStore(openDb);
143
146
 
144
147
  function putRecord(storeName, record) {
145
148
  return openDb().then(function (db) {
@@ -237,17 +240,7 @@
237
240
  }
238
241
 
239
242
  function clearAllStores() {
240
- return openDb().then(function (db) {
241
- var storeNames = Object.keys(STORES);
242
- return new Promise(function (resolve, reject) {
243
- var tx = db.transaction(storeNames, 'readwrite');
244
- for (var i = 0; i < storeNames.length; i++) {
245
- tx.objectStore(storeNames[i]).clear();
246
- }
247
- tx.oncomplete = function () { resolve(); };
248
- tx.onerror = function (event) { reject(event.target.error); };
249
- });
250
- });
243
+ return composerDraftStore.clear(Object.keys(STORES));
251
244
  }
252
245
 
253
246
  // --- Record builders ---
@@ -277,12 +270,14 @@
277
270
  var lastActivityAt = typeof input.lastActivityAt === 'string' && input.lastActivityAt
278
271
  ? input.lastActivityAt
279
272
  : updatedAt;
280
- return {
273
+ var record = {
281
274
  id: input.id || generateId('ses'),
282
275
  projectId: input.projectId || '',
283
276
  title: normalizeSessionTitleForStorage(input.title, titleSource),
284
277
  titleSource: titleSource,
285
278
  codexThreadId: typeof input.codexThreadId === 'string' ? input.codexThreadId : '',
279
+ forkedFromSessionId: normalizeTextField(input.forkedFromSessionId, 160),
280
+ forkedFromRunId: normalizeTextField(input.forkedFromRunId, 160),
286
281
  status: typeof input.status === 'string' && input.status ? input.status : 'active',
287
282
  focusFiles: normalizePathList(input.focusFiles),
288
283
  selectionContext: SessionState.normalizeSelectionContext(input.selectionContext),
@@ -293,7 +288,8 @@
293
288
  normalizePaths: normalizePathList
294
289
  }),
295
290
  history: compactHistoryForStorage(input.history),
296
- runs: compactRunsForStorage(input.runs, options),
291
+ runs: compactRunsForStorage(input.runs, { preserveRunActionPayload: false,
292
+ deferDirectoryCleanup: options.preserveRunActionPayload === true || options.deferDirectoryCleanup === true }),
297
293
  task: normalizeDisplayTextForStorage(input.task, SESSION_STORAGE_LIMITS.taskChars),
298
294
  mode: normalizeCurrentMode(input.mode), providerId: typeof input.providerId === 'string' && input.providerId ? input.providerId : 'builtin',
299
295
  model: typeof input.model === 'string' ? input.model : '',
@@ -307,6 +303,23 @@
307
303
  accountScopeUnavailable: accountScopeUnavailable,
308
304
  safeTaskSummary: safeTaskSummary
309
305
  };
306
+ if (options.preserveRunActionPayload === true) {
307
+ // Count the real display record before allocating recovery space. Adding
308
+ // older checkpoints must not make an otherwise valid history unwritable.
309
+ var metadataText = JSON.stringify(record), metadataBytes = SharedSessionTransfer.byteLength(metadataText);
310
+ record.runs = compactRunsForStorage(input.runs, { ...options,
311
+ canRetainActionPayload: function (bytes, chars) {
312
+ var totalBytes = metadataBytes + bytes + 4096, totalChars = metadataText.length + chars + 4096;
313
+ return totalBytes <= SharedSessionTransfer.MAX_WIRE_BYTES
314
+ || (totalBytes <= SharedSessionTransfer.MAX_RECORD_BYTES && totalChars * 2 <= SharedSessionTransfer.MAX_WIRE_BYTES);
315
+ } });
316
+ }
317
+ for (var run of record.runs) SessionState.pruneRunRecoveryScope(run, record);
318
+ return record;
319
+ }
320
+
321
+ function compactSessionBatchActions(records, options) {
322
+ return StorageRunActions.compactSessionBatchActions(records, options, buildSessionRecord, SharedSessionTransfer);
310
323
  }
311
324
 
312
325
  function resolveAccountScopeId(input) {
@@ -417,6 +430,7 @@
417
430
  var now = new Date().toISOString();
418
431
  return {
419
432
  id: input.id || generateId('aud'),
433
+ accountScopeId: typeof input.accountScopeId === 'string' ? input.accountScopeId : '',
420
434
  projectId: typeof input.projectId === 'string' ? input.projectId : '',
421
435
  sessionId: typeof input.sessionId === 'string' ? input.sessionId : '',
422
436
  turnId: typeof input.turnId === 'string' ? input.turnId : '',
@@ -466,7 +480,8 @@
466
480
  customInstructionsByProject: normalizeStringMap(state.customInstructionsByProject),
467
481
  governanceRulesByProject: normalizeGovernanceRulesMap(state.governanceRulesByProject),
468
482
  selectedLocalSkillIdsByProject: normalizeStringListMap(state.selectedLocalSkillIdsByProject),
469
- codexOverleafSkillEnabled: normalizeCodexOverleafSkillEnabledMap(state.codexOverleafSkillEnabled)
483
+ codexOverleafSkillEnabled: normalizeCodexOverleafSkillEnabledMap(state.codexOverleafSkillEnabled),
484
+ codexLocalSkillEnabled: SessionState.normalizeCodexLocalSkillEnabled(state.codexLocalSkillEnabled)
470
485
  };
471
486
  return prefs;
472
487
  }
@@ -519,7 +534,7 @@
519
534
  continue;
520
535
  }
521
536
  result[key] = typeof value[rawKey] === 'string'
522
- ? normalizeTextField(value[rawKey], CUSTOM_INSTRUCTIONS_MAX_CHARS)
537
+ ? normalizeDisplayTextForStorage(value[rawKey], CUSTOM_INSTRUCTIONS_MAX_CHARS)
523
538
  : '';
524
539
  }
525
540
  return result;
@@ -640,9 +655,9 @@
640
655
  function normalizeSessionTitleForStorage(value, titleSource) {
641
656
  var title = typeof value === 'string' ? value.trim() : '';
642
657
  if (titleSource === 'manual') {
643
- return normalizeTextField(title === LEGACY_DEFAULT_SESSION_TITLE ? '' : title, SESSION_STORAGE_LIMITS.sessionTitleChars);
658
+ return normalizeDisplayTextForStorage(title === LEGACY_DEFAULT_SESSION_TITLE ? '' : title, SESSION_STORAGE_LIMITS.sessionTitleChars);
644
659
  }
645
- return normalizeTextField(title === LEGACY_DEFAULT_SESSION_TITLE ? '' : title, SESSION_STORAGE_LIMITS.sessionTitleChars);
660
+ return normalizeDisplayTextForStorage(title === LEGACY_DEFAULT_SESSION_TITLE ? '' : title, SESSION_STORAGE_LIMITS.sessionTitleChars);
646
661
  }
647
662
 
648
663
  function compactHistoryForStorage(history) {
@@ -660,12 +675,14 @@
660
675
  function compactRunsForStorage(runs, options) {
661
676
  return StorageRunActions.compactRunsForStorage(runs, options, SESSION_STORAGE_LIMITS.maxRunsPerSession, compactRunForStorage);
662
677
  }
663
- function compactRunForStorage(run, keepActionPayload) {
678
+ function compactRunForStorage(run, keepActionPayload, preparedActionPayload) {
664
679
  run = SessionState.recoverRecordedRunFailure(run);
665
- var actionPayload = StorageRunActions.compactRunActionPayload(run, keepActionPayload);
680
+ var actionPayload = preparedActionPayload || StorageRunActions.compactRunActionPayload(run, keepActionPayload);
666
681
  var compact = {
667
682
  ...SessionState.pickWritebackRecovery(run),
668
683
  id: run.id,
684
+ forkSnapshot: run.forkSnapshot === true,
685
+ forkSourceRunId: normalizeTextField(run.forkSourceRunId, 160),
669
686
  task: normalizeDisplayTextForStorage(run.task || 'untitled task', SESSION_STORAGE_LIMITS.taskChars),
670
687
  mode: typeof run.mode === 'string' ? redactSecretLikeText(run.mode) : '', ...StorageRunActions.compactProviderSnapshot(run),
671
688
  ...StorageRunActions.compactRunExecutionSnapshot(run, function (text) { return normalizeTextField(text, 4096); }),
@@ -686,7 +703,10 @@
686
703
  undoTrackedChanges: actionPayload.undoTrackedChanges,
687
704
  undoExpectedFiles: actionPayload.undoExpectedFiles,
688
705
  ...(actionPayload.trackedChangeCaptures ? { trackedChangeCaptures: actionPayload.trackedChangeCaptures } : {}),
706
+ ...(actionPayload.pendingDirectoryCleanup ? { pendingDirectoryCleanup: actionPayload.pendingDirectoryCleanup } : {}),
707
+ ...(actionPayload.recoveryRetention ? { recoveryRetention: SessionState.normalizeRecoveryRetention(actionPayload.recoveryRetention) } : {}),
689
708
  undoStatus: normalizeDisplayTextForStorage(run.undoStatus, SESSION_STORAGE_LIMITS.statusTextChars),
709
+ ...(run.undoOwnerProtocol === 1 ? { undoOwnerProtocol: 1 } : {}),
690
710
  nativeRequestId: normalizeTextField(run.nativeRequestId, 160),
691
711
  codexThreadId: normalizeTextField(run.codexThreadId, 160), codexTurnId: normalizeTextField(run.codexTurnId, 160),
692
712
  nativeEventSeq: Number.isFinite(Number(run.nativeEventSeq)) ? Number(run.nativeEventSeq) : 0,
@@ -725,6 +745,7 @@
725
745
  status: normalizeEventStatus(event.status),
726
746
  timestamp: typeof event.timestamp === 'string' ? redactSecretLikeText(event.timestamp) : '',
727
747
  kind: typeof event.kind === 'string' ? redactSecretLikeText(event.kind) : 'activity',
748
+ guidanceId: normalizeTextField(event.guidanceId, 160),
728
749
  streamKey: typeof event.streamKey === 'string' ? redactSecretLikeText(event.streamKey) : '',
729
750
  streamRole: typeof event.streamRole === 'string' ? redactSecretLikeText(event.streamRole) : ''
730
751
  };
@@ -760,7 +781,7 @@
760
781
 
761
782
  // Deep-copies a known-safe structured event value (the completion-report
762
783
  // {conclusion, body, meta[]} payload, or a FailureReason object) preserving
763
- // its shape while redacting + truncating every string field. Bounded depth +
784
+ // its shape while applying display-path rules and string limits. Bounded depth +
764
785
  // breadth so a pathological value can't blow up the stored record.
765
786
  function compactStructuredEventValueForStorage(value, depth) {
766
787
  return StorageRunActions.compactStructuredEventValue(value, {
@@ -803,7 +824,11 @@
803
824
  }
804
825
 
805
826
  function normalizeDisplayTextForStorage(value, maxChars) {
806
- return normalizeTextField(value, maxChars);
827
+ var text = sanitizeAssistantVisibleText(value);
828
+ if (!Number.isFinite(maxChars) || maxChars <= 0 || text.length <= maxChars) {
829
+ return text;
830
+ }
831
+ return text.slice(0, Math.max(0, maxChars - 1)) + '…';
807
832
  }
808
833
 
809
834
  function summarizeStructuredValueForStorage(value, maxChars) {
@@ -1013,7 +1038,7 @@
1013
1038
  }
1014
1039
 
1015
1040
  function normalizeTextField(value, maxChars) {
1016
- var text = sanitizeAssistantVisibleText(value);
1041
+ var text = sanitizeAssistantVisibleText(redactSecretLikeText(value));
1017
1042
  if (!Number.isFinite(maxChars) || maxChars <= 0 || text.length <= maxChars) {
1018
1043
  return text;
1019
1044
  }
@@ -1024,7 +1049,8 @@
1024
1049
  if (typeof value !== 'string' || !value) {
1025
1050
  return '';
1026
1051
  }
1027
- var text = redactSecretLikeText(value);
1052
+ // Preserve conversation examples and quotations through save/reload.
1053
+ var text = value;
1028
1054
  if (!mightContainLocalReferenceText(text)) {
1029
1055
  return text;
1030
1056
  }
@@ -1309,7 +1335,13 @@
1309
1335
  deleteByIndex: deleteByIndex,
1310
1336
  clearStore: clearStore,
1311
1337
  clearAllStores: clearAllStores,
1338
+ clearAuxiliaryStores: function () { return composerDraftStore.clear(Object.keys(STORES).filter(name => name !== 'sessions')); },
1339
+ readComposerAttachmentDraft: composerDraftStore.read,
1340
+ writeComposerAttachmentDraft: composerDraftStore.write,
1312
1341
  buildSessionRecord: buildSessionRecord,
1342
+ compactSessionBatchActions: compactSessionBatchActions,
1343
+ projectRetentionWarnings: StorageRunActions.projectRetentionWarnings,
1344
+ projectRetentionUpdates: StorageRunActions.projectRetentionUpdates,
1313
1345
  mergeSessionReviewState: StorageRunActions.mergeSessionReviewState,
1314
1346
  buildTurnRecord: buildTurnRecord,
1315
1347
  buildEventRecord: buildEventRecord,