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
@@ -1,10 +1,10 @@
1
1
  (function initUndoOperations(root, factory) {
2
2
  if (typeof module === 'object' && module.exports) {
3
- module.exports = factory();
3
+ module.exports = factory(require('./staleGuard'));
4
4
  } else {
5
- root.CodexOverleafUndoOperations = factory();
5
+ root.CodexOverleafUndoOperations = factory(root.CodexOverleafStaleGuard);
6
6
  }
7
- })(typeof globalThis !== 'undefined' ? globalThis : window, function undoOperationsFactory() {
7
+ })(typeof globalThis !== 'undefined' ? globalThis : window, function undoOperationsFactory(StaleGuard) {
8
8
  'use strict';
9
9
 
10
10
  function buildUndoOperations(project, appliedOperations) {
@@ -15,6 +15,7 @@
15
15
  }
16
16
  }
17
17
 
18
+ const sourceFilesByPath = new Map(workingFilesByPath);
18
19
  const undo = [];
19
20
  const fullRestoreEditPaths = new Set();
20
21
 
@@ -29,7 +30,8 @@
29
30
  }
30
31
  const currentBeforeEdit = workingFilesByPath.get(operation.path);
31
32
  if (!fullRestoreEditPaths.has(operation.path)) {
32
- const inversePatches = buildInverseTextPatches(currentBeforeEdit, operation.patches);
33
+ const inversePatches = buildInverseTextPatches(sourceFilesByPath.get(operation.path), operation.patches,
34
+ operation.verifiedContent, operation.path);
33
35
  if (inversePatches.length) {
34
36
  undo.push(normalizeUndoOperation({
35
37
  type: 'edit',
@@ -70,10 +72,13 @@
70
72
  if (!operation.path || !operation.to) {
71
73
  continue;
72
74
  }
75
+ if (operation.moveProof && !operation.moveProof.targetEntityId) continue;
73
76
  undo.push(normalizeUndoOperation({
74
77
  type: 'move',
75
78
  path: operation.to,
76
79
  to: operation.path,
80
+ ...(operation.moveProof ? { moveProof: { ...operation.moveProof,
81
+ sourceEntityId: operation.moveProof.targetEntityId, targetEntityId: operation.moveProof.sourceEntityId } } : {}),
77
82
  reason: 'Undo move'
78
83
  }));
79
84
  } else if (operation.type === 'delete') {
@@ -89,12 +94,13 @@
89
94
  }
90
95
 
91
96
  applyOperationToFiles(workingFilesByPath, operation);
97
+ applyOperationToFiles(sourceFilesByPath, { ...operation, verifiedContent: undefined });
92
98
  }
93
99
 
94
100
  return undo.reverse();
95
101
  }
96
102
 
97
- function buildInverseTextPatches(textBeforeEdit, patches) {
103
+ function buildInverseTextPatches(textBeforeEdit, patches, verifiedContent, path) {
98
104
  if (!Array.isArray(patches) || !patches.length) {
99
105
  return [];
100
106
  }
@@ -108,7 +114,7 @@
108
114
  }
109
115
 
110
116
  let offset = 0;
111
- return normalized.patches.map(patch => {
117
+ const inverse = normalized.patches.map(patch => {
112
118
  const from = patch.from + offset;
113
119
  const to = from + patch.insert.length;
114
120
  offset += patch.insert.length - (patch.to - patch.from);
@@ -119,6 +125,41 @@
119
125
  insert: patch.expected
120
126
  };
121
127
  });
128
+ if (typeof verifiedContent !== 'string' || verifiedContent === applied.text) return inverse;
129
+ // The stored checkpoint uses the verified editor post-image. Its inverse
130
+ // must use those same offsets, while Native patches retain their source
131
+ // representation for later operations in the batch.
132
+ const prepared = StaleGuard.prepareOperationPatches({ type: 'edit', path, patches: inverse }, verifiedContent,
133
+ StaleGuard.buildBaseFileLookup([{ path, content: applied.text }]));
134
+ return prepared.ok && applyTextPatches(verifiedContent, prepared.patches).ok ? prepared.patches : [];
135
+ }
136
+
137
+ function selectExpectedFilesForTrackedUndo(project, operations = [], trackedChanges = [], previousExpectedFiles = []) {
138
+ const paths = new Set();
139
+ for (const change of trackedChanges || []) if (change?.path) paths.add(change.path);
140
+ for (const operation of operations || []) {
141
+ if (operation?.path) paths.add(operation.path);
142
+ if (operation?.to) paths.add(operation.to);
143
+ }
144
+ const expectedByPath = new Map();
145
+ for (const file of previousExpectedFiles || []) {
146
+ if (paths.has(file?.path) && typeof file?.content === 'string' && !expectedByPath.has(file.path)) {
147
+ expectedByPath.set(file.path, { path: file.path, content: file.content });
148
+ }
149
+ }
150
+ // Reconstruct the pre-image with the same inverse patches as plain Undo.
151
+ // This preserves the editor's unchanged line endings and collaborator
152
+ // content, including when the ZIP baseline used a different representation.
153
+ const hasVerifiedPatches = operations.some(operation => typeof operation?.verifiedContent === 'string'
154
+ && Array.isArray(operation.patches) && operation.patches.length);
155
+ const checkpoint = hasVerifiedPatches ? buildUndoCheckpoint(project, operations) : null;
156
+ const restored = checkpoint ? buildExpectedFilesAfterOperations({ files: checkpoint.undoBaseFiles }, checkpoint.undoOperations) : new Map();
157
+ for (const file of project?.files || []) {
158
+ if (paths.has(file?.path) && typeof file?.content === 'string' && !expectedByPath.has(file.path)) {
159
+ expectedByPath.set(file.path, { path: file.path, content: restored.get(file.path) ?? file.content });
160
+ }
161
+ }
162
+ return Array.from(expectedByPath.values());
122
163
  }
123
164
 
124
165
  function buildUndoCheckpoint(project, appliedOperations) {
@@ -140,9 +181,9 @@
140
181
  const proof = operation?.undoCreatedFile;
141
182
  if (operation?.type !== 'delete' || proof?.v !== 1) continue;
142
183
  if (proof.kind === 'text' && priorFiles.has(operation.path)) {
143
- created.set(operation.path, { kind: 'text', content: priorFiles.get(operation.path) });
184
+ created.set(operation.path, { kind: 'text', content: priorFiles.get(operation.path), createdDirectories: proof.createdDirectories });
144
185
  } else if (proof.kind === 'binary' && /^[a-f0-9]{64}$/i.test(proof.sha256 || '')) {
145
- created.set(operation.path, { kind: 'binary', sha256: proof.sha256.toLowerCase() });
186
+ created.set(operation.path, { kind: 'binary', sha256: proof.sha256.toLowerCase(), createdDirectories: proof.createdDirectories });
146
187
  }
147
188
  }
148
189
  for (const operation of appliedOperations || []) {
@@ -152,17 +193,17 @@
152
193
  if (originalPaths.has(path) && !created.has(path)) continue;
153
194
  if (operation.type === 'create' && typeof operation.content === 'string') {
154
195
  created.set(path, { kind: 'text', content: typeof operation.verifiedContent === 'string'
155
- ? operation.verifiedContent : operation.content });
196
+ ? operation.verifiedContent : operation.content, createdDirectories: operation.createdDirectories });
156
197
  } else {
157
198
  const hash = operation.sha256 || operation.assetRef?.sha256;
158
199
  if (operation.previousExists !== true && /^[a-f0-9]{64}$/i.test(hash || '')) {
159
- created.set(path, { kind: 'binary', sha256: hash.toLowerCase() });
200
+ created.set(path, { kind: 'binary', sha256: hash.toLowerCase(), createdDirectories: operation.createdDirectories });
160
201
  }
161
202
  }
162
203
  } else if (operation.type === 'edit' && created.get(path)?.kind === 'text') {
163
204
  const files = new Map([[path, created.get(path).content]]);
164
205
  applyOperationToFiles(files, operation);
165
- created.set(path, { kind: 'text', content: files.get(path) });
206
+ created.set(path, { ...created.get(path), kind: 'text', content: files.get(path) });
166
207
  } else if (['delete', 'rename', 'move', 'overwrite-binary'].includes(operation.type)) {
167
208
  // Existing rename/overwrite recovery retains ownership of these cases.
168
209
  created.delete(path);
@@ -173,17 +214,78 @@
173
214
  for (const [path, proof] of Array.from(created).reverse()) {
174
215
  const undoCreatedFile = proof.kind === 'binary'
175
216
  ? { v: 1, kind: 'binary', sha256: proof.sha256 } : { v: 1, kind: 'text' };
217
+ if (Array.isArray(proof.createdDirectories) && proof.createdDirectories.length) {
218
+ undoCreatedFile.createdDirectories = proof.createdDirectories.map(item => ({ ...item }));
219
+ }
176
220
  undoOperations.push({ ...normalizeUndoOperation({ type: 'delete', path, reason: 'Undo create' }), undoCreatedFile });
177
221
  if (proof.kind === 'text') undoBaseFiles.push({ path, content: proof.content });
178
222
  }
223
+ // Reviewing has no text insertion/deletion refs for a native metadata move.
224
+ // Keep its identity-bound inverse with the other file-level proofs.
225
+ undoOperations.push(...buildUndoOperations(project, appliedOperations).filter(operation => operation.type === 'move' && operation.moveProof));
179
226
  return { undoOperations, undoBaseFiles };
180
227
  }
181
228
 
229
+ function isFileUndoOperation(operation) {
230
+ return operation?.undoCreatedFile?.v === 1
231
+ || (operation?.type === 'move' && operation.moveProof?.v === 1)
232
+ || (operation?.type === 'create' && typeof operation.content === 'string');
233
+ }
234
+
235
+ // A deleted original has no editor post-image. Its inverse is a guarded
236
+ // file creation, backed by both the forward delete and the captured original.
237
+ // This also recovers older Track-on records that omitted that inverse.
238
+ function getFileUndoOperations(run = {}) {
239
+ const operations = (run.undoOperations || []).filter(isFileUndoOperation);
240
+ const before = filesListToMap(run.undoExpectedFiles);
241
+ const after = buildExpectedFilesAfterOperations({ files: run.undoExpectedFiles }, run.appliedOperations);
242
+ const deleted = new Set((run.appliedOperations || []).filter(operation => operation?.type === 'delete'
243
+ && before.has(operation.path) && !after.has(operation.path)).map(operation => operation.path));
244
+ return [...operations.filter(operation => !deleted.has(operation.path)), ...Array.from(deleted, path =>
245
+ normalizeUndoOperation({ type: 'create', path, content: before.get(path), reason: 'Undo delete' }))];
246
+ }
247
+
248
+ function prepareRunUndo(run = {}) {
249
+ const files = getFileUndoOperations(run);
250
+ const restored = new Set(files.filter(operation => operation.type === 'create').map(operation => operation.path));
251
+ const pending = [...files], operations = [];
252
+ for (const operation of run.undoOperations || []) {
253
+ if (isFileUndoOperation(operation)) {
254
+ const index = pending.findIndex(file => file.path === operation.path && file.type === operation.type);
255
+ if (index >= 0) operations.push(...pending.splice(index, 1));
256
+ } else if (!restored.has(operation.path)) operations.push(operation);
257
+ }
258
+ return { ...run, undoOperations: [...operations, ...pending] };
259
+ }
260
+
261
+ function textUndoRun(run = {}) {
262
+ const restored = new Set(getFileUndoOperations(run).flatMap(operation => operation.type === 'create' ? [operation.path]
263
+ : operation.type === 'move' ? [operation.path, operation.to] : []));
264
+ return { ...run,
265
+ undoExpectedFiles: (run.undoExpectedFiles || []).filter(file => !restored.has(file.path)),
266
+ undoTrackedChanges: (run.undoTrackedChanges || []).filter(ref => !restored.has(ref.path))
267
+ };
268
+ }
269
+
270
+ function mergeFileUndoResult(textResult, fileResult, operations) {
271
+ const applied = [...(textResult.applied || []), ...(fileResult.applied || [])];
272
+ const skipped = [...(textResult.skipped || []), ...(fileResult.skipped || [])];
273
+ for (const operation of operations) {
274
+ if (!(fileResult.applied || []).some(entry => entry.operation?.path === operation.path
275
+ && entry.operation?.type === operation.type && entry.result?.ok === true && entry.result?.verified === true
276
+ && entry.result.verification === 'overleaf-zip')
277
+ && !(fileResult.skipped || []).some(entry => entry.operation?.path === operation.path)) {
278
+ skipped.push({ operation, result: { ok: false, code: 'undo_operation_failed',
279
+ reason: 'The file restoration has no verified server receipt.' } });
280
+ }
281
+ }
282
+ return { ...textResult, ok: textResult.ok !== false && fileResult.ok !== false && !skipped.length, applied, skipped };
283
+ }
284
+
182
285
  // Consume only independently verified rollback receipts. Forward writes
183
286
  // remain audit evidence; recovery arrays describe only unfinished work.
184
287
  function advanceUndoCheckpoint(run = {}, result = {}, verifyText = () => false) {
185
288
  const operations = Array.isArray(run?.undoOperations) ? run.undoOperations : [];
186
- if (!operations.some(operation => operation?.undoCreatedFile?.v === 1)) return run;
187
289
  const applied = Array.isArray(result.applied) ? result.applied : [];
188
290
  const skipped = Array.isArray(result.skipped) ? result.skipped : [];
189
291
  const failed = path => skipped.some(entry => {
@@ -201,8 +303,22 @@
201
303
  && JSON.stringify(entry.operation.undoCreatedFile) === JSON.stringify(operation.undoCreatedFile)
202
304
  && entry.result?.ok === true && entry.result.verified === true
203
305
  && entry.result.verification === 'overleaf-zip')).map(operation => operation.path));
306
+ const restoredPaths = new Set(operations.filter(operation => operation?.type === 'create'
307
+ && !failed(operation.path) && applied.some(entry => entry?.operation?.type === 'create'
308
+ && entry.operation.path === operation.path && entry.operation.content === operation.content
309
+ && entry.result?.ok === true && entry.result.verified === true
310
+ && entry.result.verification === 'overleaf-zip')).map(operation => operation.path));
311
+ const movedPaths = new Set(operations.filter(operation => operation?.type === 'move' && operation.moveProof?.v === 1
312
+ && !failed(operation.path) && applied.some(entry => entry?.operation?.type === 'move'
313
+ && entry.operation.path === operation.path && entry.operation.to === operation.to
314
+ && JSON.stringify(entry.operation.moveProof) === JSON.stringify(operation.moveProof)
315
+ && entry.result?.ok === true && entry.result.verified === true && entry.result.verification === 'overleaf-zip'))
316
+ .flatMap(operation => [operation.path, operation.to]));
317
+ for (const path of restoredPaths) textPaths.add(path);
318
+ for (const path of movedPaths) textPaths.add(path);
204
319
  if (!textPaths.size && !deletedPaths.size) return run;
205
- const pending = operations.filter(operation => !(operation?.type === 'edit' && textPaths.has(operation.path))
320
+ const pending = operations.filter(operation => !restoredPaths.has(operation.path) && !(operation?.type === 'edit' && textPaths.has(operation.path))
321
+ && !(operation?.type === 'move' && movedPaths.has(operation.path))
206
322
  && !(operation?.type === 'delete' && operation.undoCreatedFile?.v === 1 && deletedPaths.has(operation.path)));
207
323
  const needed = new Set(pending.flatMap(operation => [operation?.path, operation?.to]).filter(Boolean));
208
324
  return { ...run,
@@ -396,6 +512,7 @@
396
512
  insert: String(patch.insert ?? '')
397
513
  }));
398
514
  }
515
+ if (operation.type === 'move' && operation.moveProof?.v === 1) normalized.moveProof = JSON.parse(JSON.stringify(operation.moveProof));
399
516
  return normalized;
400
517
  }
401
518
 
@@ -454,11 +571,17 @@
454
571
  }
455
572
 
456
573
  return {
574
+ isFileUndoOperation,
575
+ getFileUndoOperations,
576
+ prepareRunUndo,
577
+ textUndoRun,
578
+ mergeFileUndoResult,
457
579
  buildUndoCheckpoint,
458
580
  buildCreatedFileUndoCheckpoint,
459
581
  advanceUndoCheckpoint,
460
582
  buildExpectedFilesAfterOperations,
461
583
  buildUndoOperations,
584
+ selectExpectedFilesForTrackedUndo,
462
585
  buildSnapshotRestoreUndo
463
586
  };
464
587
  });
@@ -78,6 +78,7 @@
78
78
  deadlineAt: finiteNumber(value.deadlineAt),
79
79
  heartbeatAt: finiteNumber(value.heartbeatAt),
80
80
  postponeUntil: finiteNumber(value.postponeUntil),
81
+ cooldownUntil: finiteNumber(value.cooldownUntil),
81
82
  code: safeToken(value.code),
82
83
  message: String(value.message || '').replace(/\s+/g, ' ').trim().slice(0, 300),
83
84
  manualReason: normalizeManualReason(value.manualReason)
@@ -118,9 +119,6 @@
118
119
  const state = normalizeUpdateState(updateValue, options.currentVersion);
119
120
  const consent = normalizeConsentState(consentValue);
120
121
  const newer = compareStableVersions(state.latestVersion, state.currentVersion) > 0;
121
- const snoozed = newer &&
122
- consent.snoozedVersion === state.latestVersion &&
123
- consent.snoozedUntil > now;
124
122
  const execution = EXECUTION_STATES.has(state.state);
125
123
  const available = state.state === 'update_available' && newer;
126
124
  // A newer release that only a reinstall can apply. It is shown even after
@@ -129,7 +127,11 @@
129
127
  // still names the old one, and the notice must clear by itself.
130
128
  const runningVersion = safeVersion(options.currentVersion) || state.currentVersion;
131
129
  const manualInstall = state.state === 'manual_install_required'
132
- && compareStableVersions(state.latestVersion, runningVersion) > 0;
130
+ && (compareStableVersions(state.latestVersion, runningVersion) > 0
131
+ || (state.code === 'prerelease_promotion_requires_manual_update'
132
+ && state.latestVersion === runningVersion));
133
+ const snoozed = (newer || manualInstall) &&
134
+ consent.snoozedVersion === state.latestVersion && consent.snoozedUntil > now;
133
135
  const progress = getProgressModel(state);
134
136
  const quietFailure = state.state === 'failed' &&
135
137
  state.initiatedBy !== 'manual' &&
@@ -18,7 +18,7 @@
18
18
  if (!status.managed || !result.installedVersion || !result.nativeVersion ||
19
19
  !result.extensionVersion || !result.runtimeVersion) return result;
20
20
  if (['authorized', 'bound'].includes(status.authorization?.state) ||
21
- ['staged', 'activation_pending', 'applying', 'awaiting_health'].includes(status.transaction?.state)) {
21
+ ['staged', 'activation_pending', 'applying', 'bootstrap_pending', 'bootstrap_rolling_back', 'awaiting_health', 'rolling_back'].includes(status.transaction?.state)) {
22
22
  return { ...result, state: 'transaction_active' };
23
23
  }
24
24
  if (status.installedAligned !== true) return { ...result, state: 'repair_required' };
@@ -18,6 +18,22 @@
18
18
  if (typeof input[key] === 'string') op[key] = input[key];
19
19
  }
20
20
  if (Number.isSafeInteger(input.size)) op.size = input.size;
21
+ if (input.moveProof !== undefined) {
22
+ const proof = input.moveProof;
23
+ if (input.type !== 'move' || proof?.v !== 1 || !['text', 'binary'].includes(proof.kind)
24
+ || !/^[a-f0-9]{64}$/i.test(proof.sha256 || '')) return null;
25
+ op.moveProof = { v: 1, kind: proof.kind, sha256: proof.sha256.toLowerCase() };
26
+ if (proof.entityType !== undefined) {
27
+ if (!['doc', 'file'].includes(proof.entityType)) return null;
28
+ op.moveProof.entityType = proof.entityType;
29
+ }
30
+ for (const key of ['sourceEntityId', 'targetEntityId']) {
31
+ if (proof[key] !== undefined) {
32
+ if (typeof proof[key] !== 'string' || !proof[key] || proof[key].length > 160 || /[\0\r\n]/.test(proof[key])) return null;
33
+ op.moveProof[key] = proof[key];
34
+ }
35
+ }
36
+ }
21
37
  if (Array.isArray(input.patches)) {
22
38
  if (input.patches.length > 1000) return null;
23
39
  op.patches = input.patches.map(p => ({ from: p.from, to: p.to, expected: String(p.expected ?? ''), insert: String(p.insert ?? '') }));
@@ -1,10 +1,10 @@
1
1
  (function initCodexOverleafWritebackOperationEvidence(root, factory) {
2
2
  if (typeof module === 'object' && module.exports) {
3
- module.exports = factory();
3
+ module.exports = factory(require('./failureReasons'));
4
4
  } else {
5
- root.CodexOverleafModuleRegistry.define('WritebackOperationEvidence', [], factory);
5
+ root.CodexOverleafModuleRegistry.define('WritebackOperationEvidence', ['FailureReasons'], factory);
6
6
  }
7
- })(typeof window !== 'undefined' ? window : globalThis, function writebackOperationEvidenceFactory() {
7
+ })(typeof window !== 'undefined' ? window : globalThis, function writebackOperationEvidenceFactory(FailureReasons) {
8
8
  'use strict';
9
9
 
10
10
  function normalizeOperationEvidence(operations = [], applyResult = {}) {
@@ -99,18 +99,7 @@
99
99
  }
100
100
 
101
101
  function normalizeFailure(result, operation) {
102
- if (result?.failure && typeof result.failure === 'object') return cloneValue(result.failure);
103
- if (!result?.code) return null;
104
- return {
105
- code: result.code,
106
- stage: result.stage || 'write',
107
- severity: result.severity || 'error',
108
- userMessage: result.reason || result.message || result.code,
109
- retryable: result.retryable === true,
110
- file: operation?.path || operation?.to || '',
111
- changedDocument: result.changedDocument === true,
112
- terminalState: result.terminalState || ''
113
- };
102
+ return FailureReasons.normalizeWritebackFailure(result, operation);
114
103
  }
115
104
 
116
105
  function normalizeTrackedChanges(value) {
@@ -4,21 +4,24 @@
4
4
  require('./failureReasons'),
5
5
  require('./settlementFacts'),
6
6
  require('./writebackEvidenceProjection'),
7
- require('./writebackOperationEvidence')
7
+ require('./writebackOperationEvidence'),
8
+ require('./undoOperations')
8
9
  );
9
10
  } else {
10
11
  root.CodexOverleafModuleRegistry.define('WritebackSettlement', [
11
12
  'FailureReasons',
12
13
  'SettlementFacts',
13
14
  'WritebackEvidenceProjection',
14
- 'WritebackOperationEvidence'
15
+ 'WritebackOperationEvidence',
16
+ 'UndoOperations'
15
17
  ], factory);
16
18
  }
17
19
  })(typeof window !== 'undefined' ? window : globalThis, function writebackSettlementFactory(
18
20
  DefaultFailureReasons,
19
21
  SettlementFacts,
20
22
  EvidenceProjection,
21
- OperationEvidence
23
+ OperationEvidence,
24
+ UndoOperations
22
25
  ) {
23
26
  'use strict';
24
27
 
@@ -212,7 +215,7 @@
212
215
  }
213
216
 
214
217
  function deriveSettledEvidence(primaryFailure, fileSettlements = []) {
215
- if (primaryFailure?.terminalState === 'needs_review'
218
+ if (primaryFailure?.terminalState === 'needs_review' || DefaultFailureReasons.isUnconfirmedWriteFailure?.(primaryFailure)
216
219
  || fileSettlements.some(file => file.readBack === 'mismatch')) {
217
220
  return 'needs-review';
218
221
  }
@@ -336,7 +339,8 @@
336
339
  }
337
340
 
338
341
  function settleLegacyUndo(input = {}) {
339
- const status = input.status === 'applied' ? 'applied' : 'partial';
342
+ const status = input.status === 'applied'
343
+ && isUndoResultEffectivelyApplied(input.run, input.result, input.run?.undoOperations) ? 'applied' : 'partial';
340
344
  const recovery = preserveRecovery();
341
345
  return {
342
346
  statePatch: { undoStatus: status },
@@ -441,7 +445,8 @@
441
445
  }
442
446
 
443
447
  function isUndoResultEffectivelyApplied(run, result, undoOperations = []) {
444
- if (!result?.skipped?.length) return true;
448
+ if (result?.saveVerification?.ok === false || result?.receiptUnconfirmed === true) return false;
449
+ if (!result?.skipped?.length) return result?.ok === true;
445
450
  const expectedByPath = new Map((run?.undoExpectedFiles || [])
446
451
  .filter(file => file?.path && typeof file.content === 'string')
447
452
  .map(file => [normalizeSettlementPath(file.path), file.content])
@@ -467,10 +472,12 @@
467
472
  if (entry?.operation?.type === 'edit' && operationPath === normalizedPath) {
468
473
  return inner.verifiedContent === expectedContent;
469
474
  }
470
-
471
475
  const trackedPath = normalizeSettlementPath(entry?.trackedChange?.path);
472
476
  if (trackedPath !== normalizedPath) return false;
473
477
  const key = String(entry?.trackedChange?.key || '');
478
+ if (['overleaf-review-reject', 'overleaf-review-reject-sweep'].includes(inner.method))
479
+ return /^(?:id:|pos:|sig:)/.test(key) && inner.verified === true
480
+ && inner.verifiedContent === expectedContent;
474
481
  if (key === `editor-undo:${normalizedPath}`) {
475
482
  return inner.method === 'overleaf-editor-undo'
476
483
  && inner.verified === true
@@ -492,14 +499,12 @@
492
499
  }
493
500
  return false;
494
501
  }
495
-
496
502
  function normalizeSettlementPath(value) {
497
503
  return String(value || '')
498
504
  .replace(/\\/g, '/')
499
505
  .replace(/\/+/g, '/')
500
506
  .replace(/^\/+|\/+$/g, '');
501
507
  }
502
-
503
508
  function attachAcceptNotVerifiedFailure(run, result, options = {}) {
504
509
  if (!result || typeof result !== 'object' || result.ok === false) return result;
505
510
  if (Array.isArray(result.skipped) && result.skipped.length > 0) return result;
@@ -580,8 +585,9 @@
580
585
  const inner = entry?.result || entry;
581
586
  if (!inner || inner.ok === true) continue;
582
587
  const operation = entry?.operation || (entry?.trackedChange ? { path: entry.trackedChange.path } : undefined);
583
- failures.push(failureReasons?.normalizeFailureReason instanceof Function
584
- ? failureReasons.normalizeFailureReason(inner, operation)
588
+ const normalize = failureReasons?.normalizeWritebackFailure || failureReasons?.normalizeFailureReason;
589
+ failures.push(normalize instanceof Function
590
+ ? normalize(inner, operation)
585
591
  : normalizeFailure(inner, operation));
586
592
  }
587
593
  return failures.filter(Boolean);
@@ -630,9 +636,22 @@
630
636
 
631
637
  function attachVerifiedContentToOperation(operation, result) {
632
638
  if (!operation || typeof operation !== 'object') return operation;
633
- return operation.type === 'edit' && typeof result?.verifiedContent === 'string'
634
- ? { ...operation, verifiedContent: result.verifiedContent }
635
- : operation;
639
+ const createdDirectories = (Array.isArray(result?.createdDirectories) ? result.createdDirectories
640
+ : Array.isArray(result?.moveProof?.createdDirectories) ? result.moveProof.createdDirectories : [])
641
+ .filter(proof => proof?.v === 1 && typeof proof.projectId === 'string' && proof.projectId
642
+ && typeof proof.path === 'string' && proof.path && !proof.path.split('/').some(part => !part || part === '.' || part === '..')
643
+ && typeof proof.nativeFolderId === 'string')
644
+ .map(proof => ({ v: 1, projectId: proof.projectId, path: proof.path, nativeFolderId: proof.nativeFolderId,
645
+ ownershipConfirmed: false, observedAfterCreate: proof.observedAfterCreate === true }));
646
+ const rawMove = result?.moveProof;
647
+ const moveProof = rawMove?.v === 1 && ['text', 'binary'].includes(rawMove.kind) && /^[a-f0-9]{64}$/i.test(rawMove.sha256 || '')
648
+ && typeof rawMove.sourceEntityId === 'string' && rawMove.sourceEntityId
649
+ && typeof rawMove.targetEntityId === 'string' && rawMove.targetEntityId
650
+ ? { v: 1, kind: rawMove.kind, sha256: rawMove.sha256.toLowerCase(), sourceEntityId: rawMove.sourceEntityId,
651
+ targetEntityId: rawMove.targetEntityId, ...(['doc', 'file'].includes(rawMove.entityType) ? { entityType: rawMove.entityType } : {}), ...(createdDirectories.length ? { createdDirectories } : {}) } : null;
652
+ if (!(operation.type === 'edit' && typeof result?.verifiedContent === 'string') && !createdDirectories.length && !moveProof) return operation;
653
+ return { ...operation, ...(operation.type === 'edit' && typeof result?.verifiedContent === 'string' ? { verifiedContent: result.verifiedContent } : {}),
654
+ ...(createdDirectories.length ? { createdDirectories } : {}), ...(moveProof ? { moveProof } : {}) };
636
655
  }
637
656
 
638
657
  function selectExpectedFilesForTrackedUndo(
@@ -641,26 +660,7 @@
641
660
  trackedChanges = [],
642
661
  previousExpectedFiles = []
643
662
  ) {
644
- const paths = new Set();
645
- for (const change of trackedChanges || []) {
646
- if (change?.path) paths.add(change.path);
647
- }
648
- for (const operation of operations || []) {
649
- if (operation?.path) paths.add(operation.path);
650
- if (operation?.to) paths.add(operation.to);
651
- }
652
- const expectedByPath = new Map();
653
- for (const file of previousExpectedFiles || []) {
654
- if (paths.has(file?.path) && typeof file?.content === 'string' && !expectedByPath.has(file.path)) {
655
- expectedByPath.set(file.path, { path: file.path, content: file.content });
656
- }
657
- }
658
- for (const file of project?.files || []) {
659
- if (paths.has(file?.path) && typeof file?.content === 'string' && !expectedByPath.has(file.path)) {
660
- expectedByPath.set(file.path, { path: file.path, content: file.content });
661
- }
662
- }
663
- return Array.from(expectedByPath.values());
663
+ return UndoOperations.selectExpectedFilesForTrackedUndo(project, operations, trackedChanges, previousExpectedFiles);
664
664
  }
665
665
 
666
666
  function acceptDiagnosticStatus(step, info) {
@@ -674,10 +674,9 @@
674
674
  return 'info';
675
675
  }
676
676
 
677
- function selectPrimaryFailure(failures, failureReasons) {
678
- return failureReasons?.selectPrimaryFailure instanceof Function
679
- ? failureReasons.selectPrimaryFailure(failures)
680
- : failures[0] || null;
677
+ function selectPrimaryFailure(failures, failureReasons = DefaultFailureReasons) {
678
+ const select = failureReasons?.selectPrimaryWritebackFailure || failureReasons?.selectPrimaryFailure;
679
+ return select instanceof Function ? select(failures) : failures[0] || null;
681
680
  }
682
681
 
683
682
  function appendSyntheticFailure(result, failure, path, code, type) {
@@ -749,6 +748,7 @@
749
748
  normalizeOperationEvidence,
750
749
  normalizeSaveEvidence,
751
750
  projectRunSettlement,
751
+ selectPrimaryFailure,
752
752
  selectExpectedFilesForTrackedUndo,
753
753
  settle,
754
754
  settleLegacyUndo,
@@ -3420,7 +3420,6 @@
3420
3420
 
3421
3421
  #codex-overleaf-panel .run-guidance-message {
3422
3422
  display: block;
3423
- border-top: 2px solid var(--tl-accent, #55b9f3);
3424
3423
  margin: 7px 0 4px auto;
3425
3424
  animation: tl-slide-in 140ms ease-out;
3426
3425
  }
@@ -3432,6 +3431,13 @@
3432
3431
  overflow-wrap: anywhere;
3433
3432
  }
3434
3433
 
3434
+ #codex-overleaf-panel .run-guidance-label {
3435
+ margin-top: 3px;
3436
+ color: var(--tl-fg-3);
3437
+ font-size: 10px;
3438
+ line-height: 1.4;
3439
+ }
3440
+
3435
3441
  /* A right-aligned toolbar: buttons sit together at the end of the row. Each
3436
3442
  button used to claim margin-left:auto, so two visible buttons split the free
3437
3443
  space and Undo drifted into the middle. */
@@ -4037,9 +4043,8 @@
4037
4043
  }
4038
4044
  #codex-overleaf-panel .run-presentation-workflow .run-process:not([open]) + .run-activity-list { display: grid; }
4039
4045
  #codex-overleaf-panel .run-workflow-list { gap: 0; margin-top: 5px; }
4040
- #codex-overleaf-panel .run-workflow-list[data-expanded="false"] > .run-work-block:not([data-kind="guidance"]) { display: none; }
4041
- #codex-overleaf-panel .run-presentation-workflow .run-guidance-list:empty,
4042
- #codex-overleaf-panel .run-workflow-guidance:empty { display: none; }
4046
+ #codex-overleaf-panel .run-workflow-list[data-expanded="false"] > .run-work-block { display: none; }
4047
+ #codex-overleaf-panel .run-presentation-workflow .run-guidance-list:empty { display: none; }
4043
4048
  #codex-overleaf-panel .run-workflow-message {
4044
4049
  color: var(--tl-fg-1);
4045
4050
  font-size: 13px;
@@ -4133,7 +4138,6 @@
4133
4138
  #codex-overleaf-panel .run-workflow-thought-full > :first-child { margin-top: 0; }
4134
4139
  #codex-overleaf-panel .run-workflow-thought-full > :last-child { margin-bottom: 0; }
4135
4140
  #codex-overleaf-panel .run-workflow-message > :last-child { margin-bottom: 0; }
4136
- #codex-overleaf-panel .run-workflow-guidance { margin: 15px 0; min-width: 0; }
4137
4141
  #codex-overleaf-panel .run-workflow-tool { min-width: 0; max-width: 100%; margin: 3px 0; }
4138
4142
  #codex-overleaf-panel .run-workflow-tool-summary {
4139
4143
  display: flex;
@@ -4538,7 +4542,14 @@
4538
4542
  bottom: calc(100% + 6px);
4539
4543
  z-index: 2147483647;
4540
4544
  display: grid;
4545
+ grid-auto-rows: max-content;
4546
+ align-content: start;
4541
4547
  gap: 2px;
4548
+ box-sizing: border-box;
4549
+ max-height: min(360px, var(--codex-slash-menu-space, 50vh));
4550
+ overflow-y: auto;
4551
+ overflow-x: hidden;
4552
+ overscroll-behavior: contain;
4542
4553
  border: 1px solid var(--tl-border-strong);
4543
4554
  border-radius: 8px;
4544
4555
  background: var(--tl-surface-2);
@@ -4554,12 +4565,15 @@
4554
4565
  display: grid;
4555
4566
  gap: 2px;
4556
4567
  width: 100%;
4568
+ min-width: 0;
4557
4569
  border: 0;
4558
4570
  border-radius: 6px;
4559
4571
  background: transparent;
4560
4572
  color: var(--tl-fg-1);
4561
4573
  padding: 7px 8px;
4562
4574
  text-align: left;
4575
+ white-space: normal;
4576
+ overflow-wrap: anywhere;
4563
4577
  }
4564
4578
 
4565
4579
  #codex-overleaf-panel .codex-slash-menu button[data-active="true"],
@@ -34,7 +34,7 @@ function buildAnthropicRequest({ requestBody = {}, launch = {}, historyMessages
34
34
  model,
35
35
  max_tokens: maxTokens,
36
36
  messages,
37
- stream: requestBody.stream !== false
37
+ stream: requestBody.stream !== false && launch.upstreamResponseMode !== 'buffered'
38
38
  };
39
39
  if (systemParts.length) {
40
40
  body.system = launch.anthropicPromptCaching
@@ -218,8 +218,10 @@ function rectifyAnthropicRequestForRetry(body, errorText) {
218
218
  return changed;
219
219
  }
220
220
  if (/budget_tokens|thinking budget|must be less than max_tokens|at least 1024/i.test(message)) {
221
+ // Keep this existing compatibility retry only when its budget fits the
222
+ // caller's maximum. Otherwise expose the original upstream rejection.
223
+ if (!Number.isFinite(Number(body.max_tokens)) || Number(body.max_tokens) < 64000) return false;
221
224
  body.thinking = { type: 'enabled', budget_tokens: 32000 };
222
- body.max_tokens = Math.max(Number(body.max_tokens) || 0, 64000);
223
225
  delete body.output_config;
224
226
  return true;
225
227
  }