codex-overleaf-link 1.9.13 → 2.1.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.
- package/README.md +24 -23
- package/extension/bootstrap/manifest.template.json +2 -1
- package/extension/runtime-manifest.json +13 -6
- package/extension/src/background.js +7 -0
- package/extension/src/content/composerPanel.js +13 -3
- package/extension/src/content/contentRuntime.js +130 -65
- package/extension/src/content/contextTray.js +173 -9
- package/extension/src/content/markdownText.js +16 -1
- package/extension/src/content/mathText.js +233 -0
- package/extension/src/content/modelPicker.js +117 -106
- package/extension/src/content/modelPickerSupport.js +115 -0
- package/extension/src/content/nativeChannel.js +20 -1
- package/extension/src/content/panelMaintenance.js +2 -0
- package/extension/src/content/projectSettingsCoordinator.js +2 -0
- package/extension/src/content/providerSettingsCoordinator.js +413 -0
- package/extension/src/content/providerSettingsDialog.js +981 -0
- package/extension/src/content/runTimelineView.js +24 -5
- package/extension/src/content/settingsPanel.js +33 -2
- package/extension/src/content/writebackOrchestrator.js +3 -1
- package/extension/src/page/overleafProjectSnapshot.js +29 -33
- package/extension/src/page/snapshotRouter.js +75 -8
- package/extension/src/page/trackedChangesLifecycle.js +154 -26
- package/extension/src/page/writebackRouter.js +31 -60
- package/extension/src/pageBridge.js +1 -1
- package/extension/src/shared/agentTranscript.js +130 -0
- package/extension/src/shared/compatibility.js +10 -7
- package/extension/src/shared/failureReasons.js +75 -0
- package/extension/src/shared/i18n.js +22 -0
- package/extension/src/shared/models.js +1 -0
- package/extension/src/shared/providerProfiles.js +283 -0
- package/extension/src/shared/sessionState.js +12 -2
- package/extension/src/shared/storageDb.js +27 -19
- package/extension/src/shared/storageRunActions.js +99 -0
- package/extension/styles/panel.css +587 -1
- package/extension/vendor/katex/LICENSE +21 -0
- package/extension/vendor/katex/VERSION +2 -0
- package/extension/vendor/katex/fonts/KaTeX_AMS-Regular.ttf +0 -0
- package/extension/vendor/katex/fonts/KaTeX_AMS-Regular.woff +0 -0
- package/extension/vendor/katex/fonts/KaTeX_AMS-Regular.woff2 +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Caligraphic-Bold.ttf +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Caligraphic-Bold.woff +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Caligraphic-Regular.ttf +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Caligraphic-Regular.woff +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Fraktur-Bold.ttf +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Fraktur-Bold.woff +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Fraktur-Regular.ttf +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Fraktur-Regular.woff +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Main-Bold.ttf +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Main-Bold.woff +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Main-Bold.woff2 +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Main-BoldItalic.ttf +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Main-BoldItalic.woff +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Main-Italic.ttf +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Main-Italic.woff +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Main-Italic.woff2 +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Main-Regular.ttf +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Main-Regular.woff +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Main-Regular.woff2 +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Math-BoldItalic.ttf +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Math-BoldItalic.woff +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Math-Italic.ttf +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Math-Italic.woff +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Math-Italic.woff2 +0 -0
- package/extension/vendor/katex/fonts/KaTeX_SansSerif-Bold.ttf +0 -0
- package/extension/vendor/katex/fonts/KaTeX_SansSerif-Bold.woff +0 -0
- package/extension/vendor/katex/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
- package/extension/vendor/katex/fonts/KaTeX_SansSerif-Italic.ttf +0 -0
- package/extension/vendor/katex/fonts/KaTeX_SansSerif-Italic.woff +0 -0
- package/extension/vendor/katex/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
- package/extension/vendor/katex/fonts/KaTeX_SansSerif-Regular.ttf +0 -0
- package/extension/vendor/katex/fonts/KaTeX_SansSerif-Regular.woff +0 -0
- package/extension/vendor/katex/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Script-Regular.ttf +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Script-Regular.woff +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Script-Regular.woff2 +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Size1-Regular.ttf +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Size1-Regular.woff +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Size1-Regular.woff2 +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Size2-Regular.ttf +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Size2-Regular.woff +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Size2-Regular.woff2 +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Size3-Regular.ttf +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Size3-Regular.woff +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Size3-Regular.woff2 +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Size4-Regular.ttf +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Size4-Regular.woff +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Size4-Regular.woff2 +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Typewriter-Regular.ttf +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Typewriter-Regular.woff +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
- package/extension/vendor/katex/katex.min.css +1 -0
- package/extension/vendor/katex/katex.min.js +1 -0
- package/native-host/src/anthropicBridgeRequest.js +435 -0
- package/native-host/src/anthropicBridgeResponse.js +431 -0
- package/native-host/src/anthropicMessagesBridge.js +281 -0
- package/native-host/src/chatBridgeRequest.js +274 -0
- package/native-host/src/chatBridgeResponse.js +594 -0
- package/native-host/src/chatCompletionsBridge.js +485 -0
- package/native-host/src/codexCommand.js +25 -0
- package/native-host/src/codexPromptAssembly.js +9 -0
- package/native-host/src/codexProviderLaunch.js +213 -0
- package/native-host/src/codexProviderTest.js +196 -0
- package/native-host/src/codexSessionRunner.js +29 -25
- package/native-host/src/managedInstall.js +1 -0
- package/native-host/src/providerBridgeRoutes.js +11 -0
- package/native-host/src/providerModelCatalog.js +107 -0
- package/native-host/src/providerProfile.js +454 -0
- package/native-host/src/providerReasoning.js +235 -0
- package/native-host/src/providerRedaction.js +18 -0
- package/native-host/src/providerRuntime.js +365 -0
- package/native-host/src/providerStore.js +691 -0
- package/native-host/src/readProgressGuard.js +360 -0
- package/native-host/src/taskRunnerRuntime.js +40 -4
- package/native-host/src/updateArchive.js +1 -0
- package/native-host/src/updateTrust.js +2 -1
- package/package.json +2 -1
|
@@ -88,7 +88,9 @@
|
|
|
88
88
|
skipped
|
|
89
89
|
};
|
|
90
90
|
}
|
|
91
|
-
const snapshotUndo = await restoreExpectedFilesWithNoTraceUndo(expectedFiles, postFiles
|
|
91
|
+
const snapshotUndo = await restoreExpectedFilesWithNoTraceUndo(expectedFiles, postFiles, {
|
|
92
|
+
runProjectId: params.runProjectId
|
|
93
|
+
});
|
|
92
94
|
if (snapshotUndo.attempted) {
|
|
93
95
|
applied.push(...snapshotUndo.applied);
|
|
94
96
|
skipped.push(...snapshotUndo.skipped);
|
|
@@ -291,6 +293,7 @@
|
|
|
291
293
|
.filter(file => file?.path && typeof file.content === 'string')
|
|
292
294
|
.map(file => [normalizeSafeProjectPath(file.path), file.content])
|
|
293
295
|
.filter(([path]) => path));
|
|
296
|
+
let snapshotContextRebased = false;
|
|
294
297
|
const paths = Array.from(expectedByPath.keys()).filter(path => postByPath.has(path));
|
|
295
298
|
if (!paths.length) {
|
|
296
299
|
return {
|
|
@@ -313,30 +316,63 @@
|
|
|
313
316
|
// the reject path relies on (its primary path).
|
|
314
317
|
const appliedBeforeUndo = applied.length;
|
|
315
318
|
const editorUndo = await rejectTrackedChangesViaEditorUndo(expectedFiles, postFiles, applied);
|
|
319
|
+
let undoReady = editorUndo.ok === true;
|
|
320
|
+
let snapshotUndo = null;
|
|
321
|
+
// A page refresh preserves the run checkpoint but clears CodeMirror's
|
|
322
|
+
// in-memory undo history. When the native editor undo made no progress,
|
|
323
|
+
// reuse Undo's guarded snapshot path: it first verifies that every file is
|
|
324
|
+
// still exactly at this run's post-write content, then restores the
|
|
325
|
+
// persisted pre-write content with Track Changes disabled. The normal
|
|
326
|
+
// Accept replay below can then apply the post-write patch untracked.
|
|
327
|
+
if (!undoReady && applied.length === appliedBeforeUndo) {
|
|
328
|
+
snapshotUndo = await restoreExpectedFilesWithNoTraceUndo(expectedFiles, postFiles, {
|
|
329
|
+
runProjectId: params.runProjectId
|
|
330
|
+
});
|
|
331
|
+
if (snapshotUndo.ok) {
|
|
332
|
+
applied.push(...snapshotUndo.applied);
|
|
333
|
+
for (const file of snapshotUndo.rebasedExpectedFiles || []) {
|
|
334
|
+
expectedByPath.set(file.path, file.content);
|
|
335
|
+
}
|
|
336
|
+
for (const file of snapshotUndo.rebasedPostFiles || []) {
|
|
337
|
+
if (postByPath.get(file.path) !== file.content) {
|
|
338
|
+
snapshotContextRebased = true;
|
|
339
|
+
}
|
|
340
|
+
postByPath.set(file.path, file.content);
|
|
341
|
+
}
|
|
342
|
+
undoReady = true;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
316
345
|
pushDiagnostic('editorUndo', {
|
|
317
|
-
ok:
|
|
346
|
+
ok: undoReady,
|
|
318
347
|
attempted: editorUndo.attempted === true,
|
|
319
348
|
code: editorUndo.code || '',
|
|
320
349
|
reason: editorUndo.reason || '',
|
|
321
|
-
pathsProcessed: applied.length - appliedBeforeUndo
|
|
350
|
+
pathsProcessed: applied.length - appliedBeforeUndo,
|
|
351
|
+
snapshotFallbackAttempted: snapshotUndo?.attempted === true,
|
|
352
|
+
snapshotFallbackOk: snapshotUndo?.ok === true,
|
|
353
|
+
snapshotFallbackApplied: snapshotUndo?.applied?.length || 0,
|
|
354
|
+
snapshotFallbackSkipped: snapshotUndo?.skipped?.length || 0
|
|
322
355
|
});
|
|
323
|
-
if (!
|
|
324
|
-
// Drift
|
|
325
|
-
//
|
|
326
|
-
//
|
|
356
|
+
if (!undoReady) {
|
|
357
|
+
// Drift or a partial editor undo still bails. The snapshot fallback is
|
|
358
|
+
// allowed only when editor undo made no progress, and its own post-state
|
|
359
|
+
// verification prevents overwriting edits made after this run.
|
|
360
|
+
const snapshotFailure = snapshotUndo?.skipped?.[0]?.result;
|
|
327
361
|
return {
|
|
328
362
|
ok: false,
|
|
329
|
-
applied: [],
|
|
330
|
-
skipped:
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
:
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
363
|
+
applied: snapshotUndo?.applied || [],
|
|
364
|
+
skipped: snapshotUndo?.skipped?.length
|
|
365
|
+
? snapshotUndo.skipped
|
|
366
|
+
: [{
|
|
367
|
+
trackedChange: null,
|
|
368
|
+
result: snapshotFailure || (editorUndo.attempted || editorUndo.code
|
|
369
|
+
? editorUndo
|
|
370
|
+
: {
|
|
371
|
+
ok: false,
|
|
372
|
+
code: 'accept_editor_undo_unavailable',
|
|
373
|
+
reason: '没有可执行的 Overleaf 原生撤销或安全快照回滚来清掉本轮留痕;Codex 没有接受这轮改动。'
|
|
374
|
+
})
|
|
375
|
+
}],
|
|
340
376
|
diagnostics
|
|
341
377
|
};
|
|
342
378
|
}
|
|
@@ -417,7 +453,12 @@
|
|
|
417
453
|
// applyOperationsCore — never a whole-file replaceAll, which would clobber
|
|
418
454
|
// any unrelated content and produce one giant tracked change if anything
|
|
419
455
|
// about the mode switch were imperfect.
|
|
420
|
-
const operationsResult = buildAcceptReplayOperations(
|
|
456
|
+
const operationsResult = buildAcceptReplayOperations(
|
|
457
|
+
paths,
|
|
458
|
+
expectedByPath,
|
|
459
|
+
postByPath,
|
|
460
|
+
snapshotContextRebased ? [] : params.appliedOperations
|
|
461
|
+
);
|
|
421
462
|
if (!operationsResult.ok) {
|
|
422
463
|
if (weToggledOff) {
|
|
423
464
|
await setReviewingEnabled(true, { waitMs: 1800 });
|
|
@@ -975,7 +1016,7 @@
|
|
|
975
1016
|
return { ok: true, attempted: true };
|
|
976
1017
|
}
|
|
977
1018
|
|
|
978
|
-
async function restoreExpectedFilesWithNoTraceUndo(expectedFiles, postFiles) {
|
|
1019
|
+
async function restoreExpectedFilesWithNoTraceUndo(expectedFiles, postFiles, options = {}) {
|
|
979
1020
|
const expectedByPath = new Map((expectedFiles || [])
|
|
980
1021
|
.filter(file => file?.path && typeof file.content === 'string')
|
|
981
1022
|
.map(file => [normalizeSafeProjectPath(file.path), file.content])
|
|
@@ -985,12 +1026,16 @@
|
|
|
985
1026
|
.map(file => [normalizeSafeProjectPath(file.path), file.content])
|
|
986
1027
|
.filter(([path]) => path));
|
|
987
1028
|
const paths = Array.from(expectedByPath.keys()).filter(path => postByPath.has(path));
|
|
1029
|
+
const rebasedExpectedByPath = new Map();
|
|
1030
|
+
const rebasedPostByPath = new Map();
|
|
988
1031
|
if (!paths.length) {
|
|
989
1032
|
return {
|
|
990
1033
|
ok: false,
|
|
991
1034
|
attempted: false,
|
|
992
1035
|
applied: [],
|
|
993
|
-
skipped: []
|
|
1036
|
+
skipped: [],
|
|
1037
|
+
rebasedExpectedFiles: [],
|
|
1038
|
+
rebasedPostFiles: []
|
|
994
1039
|
};
|
|
995
1040
|
}
|
|
996
1041
|
|
|
@@ -1008,30 +1053,40 @@
|
|
|
1008
1053
|
};
|
|
1009
1054
|
}
|
|
1010
1055
|
}
|
|
1011
|
-
const ready = await
|
|
1056
|
+
const ready = await waitForActiveEditorCheckpoint(
|
|
1057
|
+
path,
|
|
1058
|
+
expectedByPath.get(path),
|
|
1059
|
+
postByPath.get(path),
|
|
1060
|
+
1500
|
|
1061
|
+
);
|
|
1012
1062
|
if (!ready.ok) {
|
|
1013
1063
|
return {
|
|
1014
1064
|
ok: false,
|
|
1015
1065
|
attempted: false,
|
|
1016
1066
|
applied: [],
|
|
1017
1067
|
skipped: [],
|
|
1068
|
+
rebasedExpectedFiles: [],
|
|
1069
|
+
rebasedPostFiles: [],
|
|
1018
1070
|
code: 'snapshot_undo_current_mismatch',
|
|
1019
1071
|
reason: `${path} 当前内容已经不是本轮写入后的内容;为避免覆盖你的后续修改,Codex 不执行快照撤销。`
|
|
1020
1072
|
};
|
|
1021
1073
|
}
|
|
1074
|
+
rebasedExpectedByPath.set(path, ready.expectedContent);
|
|
1075
|
+
rebasedPostByPath.set(path, ready.postContent);
|
|
1022
1076
|
}
|
|
1023
1077
|
|
|
1024
1078
|
const operations = paths.map(path => ({
|
|
1025
1079
|
type: 'edit',
|
|
1026
1080
|
path,
|
|
1027
|
-
replaceAll:
|
|
1081
|
+
replaceAll: rebasedExpectedByPath.get(path),
|
|
1028
1082
|
reason: 'Undo tracked edit'
|
|
1029
1083
|
}));
|
|
1030
1084
|
const result = await applyOperationsWithNoTraceUndo(operations, {
|
|
1031
1085
|
baseFiles: paths.map(path => ({
|
|
1032
1086
|
path,
|
|
1033
|
-
content:
|
|
1034
|
-
}))
|
|
1087
|
+
content: rebasedPostByPath.get(path)
|
|
1088
|
+
})),
|
|
1089
|
+
runProjectId: typeof options.runProjectId === 'string' ? options.runProjectId : ''
|
|
1035
1090
|
});
|
|
1036
1091
|
const toTrackedResult = item => ({
|
|
1037
1092
|
trackedChange: {
|
|
@@ -1046,7 +1101,66 @@
|
|
|
1046
1101
|
ok: !(result.skipped || []).length,
|
|
1047
1102
|
attempted: true,
|
|
1048
1103
|
applied: (result.applied || []).map(toTrackedResult),
|
|
1049
|
-
skipped: (result.skipped || []).map(toTrackedResult)
|
|
1104
|
+
skipped: (result.skipped || []).map(toTrackedResult),
|
|
1105
|
+
rebasedExpectedFiles: paths.map(path => ({
|
|
1106
|
+
path,
|
|
1107
|
+
content: rebasedExpectedByPath.get(path)
|
|
1108
|
+
})),
|
|
1109
|
+
rebasedPostFiles: paths.map(path => ({
|
|
1110
|
+
path,
|
|
1111
|
+
content: rebasedPostByPath.get(path)
|
|
1112
|
+
}))
|
|
1113
|
+
};
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
async function waitForActiveEditorCheckpoint(filePath, expectedContent, postContent, timeoutMs) {
|
|
1117
|
+
const deadline = Date.now() + Math.max(0, Number(timeoutMs) || 0);
|
|
1118
|
+
let actual = readActiveEditorText();
|
|
1119
|
+
while (Date.now() < deadline) {
|
|
1120
|
+
const activeFileMatches = !filePath || getActiveFilePath() === filePath;
|
|
1121
|
+
actual = readActiveEditorText();
|
|
1122
|
+
if (activeFileMatches) {
|
|
1123
|
+
const rebased = rebaseCheckpointPair(actual, expectedContent, postContent);
|
|
1124
|
+
if (rebased.ok) {
|
|
1125
|
+
return rebased;
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
await delay(60);
|
|
1129
|
+
}
|
|
1130
|
+
return {
|
|
1131
|
+
ok: false,
|
|
1132
|
+
text: actual
|
|
1133
|
+
};
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
function rebaseCheckpointPair(actualContent, expectedContent, postContent) {
|
|
1137
|
+
const actual = String(actualContent ?? '');
|
|
1138
|
+
const expected = String(expectedContent ?? '');
|
|
1139
|
+
const post = String(postContent ?? '');
|
|
1140
|
+
if (actual === post) {
|
|
1141
|
+
return {
|
|
1142
|
+
ok: true,
|
|
1143
|
+
expectedContent: expected,
|
|
1144
|
+
postContent: post,
|
|
1145
|
+
prefixLength: 0,
|
|
1146
|
+
suffixLength: 0
|
|
1147
|
+
};
|
|
1148
|
+
}
|
|
1149
|
+
if (!post) {
|
|
1150
|
+
return { ok: false };
|
|
1151
|
+
}
|
|
1152
|
+
const matchIndex = actual.indexOf(post);
|
|
1153
|
+
if (matchIndex < 0 || actual.indexOf(post, matchIndex + 1) >= 0) {
|
|
1154
|
+
return { ok: false };
|
|
1155
|
+
}
|
|
1156
|
+
const prefix = actual.slice(0, matchIndex);
|
|
1157
|
+
const suffix = actual.slice(matchIndex + post.length);
|
|
1158
|
+
return {
|
|
1159
|
+
ok: true,
|
|
1160
|
+
expectedContent: prefix + expected + suffix,
|
|
1161
|
+
postContent: actual,
|
|
1162
|
+
prefixLength: prefix.length,
|
|
1163
|
+
suffixLength: suffix.length
|
|
1050
1164
|
};
|
|
1051
1165
|
}
|
|
1052
1166
|
|
|
@@ -1319,6 +1433,20 @@
|
|
|
1319
1433
|
|
|
1320
1434
|
|
|
1321
1435
|
|
|
1436
|
+
function diffTrackedChangeRefs(before = [], after = []) {
|
|
1437
|
+
const beforeKeys = new Set((before || []).map(ref => ref.key).filter(Boolean));
|
|
1438
|
+
const seen = new Set();
|
|
1439
|
+
const added = [];
|
|
1440
|
+
for (const ref of after || []) {
|
|
1441
|
+
if (!ref?.key || beforeKeys.has(ref.key) || seen.has(ref.key)) {
|
|
1442
|
+
continue;
|
|
1443
|
+
}
|
|
1444
|
+
seen.add(ref.key);
|
|
1445
|
+
added.push(ref);
|
|
1446
|
+
}
|
|
1447
|
+
return added;
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1322
1450
|
async function waitForTrackedChangeDiff(trackedBefore, paths, options = {}) {
|
|
1323
1451
|
const waitMs = Number.isFinite(Number(options.waitMs)) ? Number(options.waitMs) : 3000;
|
|
1324
1452
|
const intervalMs = Number.isFinite(Number(options.intervalMs)) ? Number(options.intervalMs) : 180;
|
|
@@ -791,19 +791,17 @@
|
|
|
791
791
|
}
|
|
792
792
|
const trackedChanges = [];
|
|
793
793
|
if (observeTrackedChanges) {
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
trackedChanges.push(...diffTrackedChangeRefs(trackedBefore, trackedAfter));
|
|
806
|
-
}
|
|
794
|
+
// Overleaf renders review markers asynchronously after the editor text
|
|
795
|
+
// has already changed. A single 120 ms snapshot intermittently missed
|
|
796
|
+
// those markers, leaving a Reviewing write with Undo but no Accept.
|
|
797
|
+
// Poll both paths: normal Reviewing writes get a shorter capture window,
|
|
798
|
+
// while Accept replay keeps the longer safety window used to prove that
|
|
799
|
+
// it did not create fresh tracked changes.
|
|
800
|
+
const trackedDiff = await waitForTrackedChangeDiff(trackedBefore, operationPaths, {
|
|
801
|
+
waitMs: forbidTrackedChanges ? 3600 : 1800,
|
|
802
|
+
intervalMs: 180
|
|
803
|
+
});
|
|
804
|
+
trackedChanges.push(...trackedDiff.trackedChanges);
|
|
807
805
|
if (forbidTrackedChanges && trackedChanges.length > 0) {
|
|
808
806
|
return {
|
|
809
807
|
ok: false,
|
|
@@ -881,55 +879,42 @@
|
|
|
881
879
|
const previousEditorIdentity = getActiveEditorIdentity();
|
|
882
880
|
const previousSignature = contentSignature(currentText);
|
|
883
881
|
|
|
884
|
-
// §9.1 target_file_not_found: if the target path is not visible in the
|
|
885
|
-
// Overleaf project file tree, refuse the write before attempting to open.
|
|
886
|
-
// openFileByPath itself would fail anyway, but emitting the canonical
|
|
887
|
-
// path-not-found code (rather than a generic open failure) gives the run
|
|
888
|
-
// card / final report a more actionable presentation per §17.3.1.
|
|
889
|
-
// Only fires when the tree-operations layer actually exposes a
|
|
890
|
-
// projectPathExists hook; older harnesses without the hook fall through
|
|
891
|
-
// to the openFileByPath path and surface a generic open failure.
|
|
892
|
-
if (operation?.type === 'edit' && hasProjectPathExistsHook() && !projectPathExists(filePath)) {
|
|
893
|
-
return {
|
|
894
|
-
ok: false,
|
|
895
|
-
code: 'path_not_found',
|
|
896
|
-
reason: `Cannot edit ${filePath}; the path is not in the Overleaf project file tree.`,
|
|
897
|
-
failure: buildPageFailure('target_file_not_found', {
|
|
898
|
-
file: filePath,
|
|
899
|
-
activeFile: initialActivePath || '',
|
|
900
|
-
operationType: operation?.type || 'edit',
|
|
901
|
-
changedDocument: false,
|
|
902
|
-
userMessage: `Codex could not find ${filePath} in this Overleaf project.`,
|
|
903
|
-
evidence: {
|
|
904
|
-
originalCode: 'path_not_found',
|
|
905
|
-
initialActivePath: initialActivePath || '',
|
|
906
|
-
writeStarted: false
|
|
907
|
-
}
|
|
908
|
-
})
|
|
909
|
-
};
|
|
910
|
-
}
|
|
911
|
-
|
|
912
882
|
const opened = await openFileByPath(filePath, { force: initialActivePath === filePath });
|
|
913
883
|
if (!opened.ok) {
|
|
914
|
-
|
|
884
|
+
// A collapsed folder can make a real nested file temporarily invisible
|
|
885
|
+
// to projectPathExists(). openFileByPath() owns the side-effecting
|
|
886
|
+
// expand-and-open attempt, so only classify the path as missing after
|
|
887
|
+
// that attempt has failed and the path still cannot be observed.
|
|
888
|
+
const pathMissing = operation?.type === 'edit'
|
|
889
|
+
&& hasProjectPathExistsHook()
|
|
890
|
+
&& !projectPathExists(filePath);
|
|
891
|
+
const resultCode = pathMissing ? 'path_not_found' : 'file_open_failed';
|
|
892
|
+
const failureCode = pathMissing ? 'target_file_not_found' : 'target_file_open_failed';
|
|
893
|
+
const reasonText = pathMissing
|
|
894
|
+
? `Cannot edit ${filePath}; the path was not found after Overleaf tried to open it.`
|
|
895
|
+
: `Cannot edit ${filePath}; active file is ${initialActivePath || 'unknown'}; ${opened.reason}`;
|
|
915
896
|
return {
|
|
916
897
|
ok: false,
|
|
917
|
-
code:
|
|
918
|
-
debug: buildWritebackDebug('open_file_failed', operation, baseFileLookup, currentText, {
|
|
898
|
+
code: resultCode,
|
|
899
|
+
debug: buildWritebackDebug(pathMissing ? 'open_file_path_not_found' : 'open_file_failed', operation, baseFileLookup, currentText, {
|
|
919
900
|
initialActivePath,
|
|
920
901
|
opened
|
|
921
902
|
}),
|
|
922
903
|
reason: reasonText,
|
|
923
|
-
failure: buildPageFailure(
|
|
904
|
+
failure: buildPageFailure(failureCode, {
|
|
924
905
|
file: filePath,
|
|
925
906
|
activeFile: initialActivePath || '',
|
|
926
907
|
operationType: operation?.type || 'edit',
|
|
927
908
|
changedDocument: false,
|
|
928
|
-
userMessage:
|
|
909
|
+
userMessage: pathMissing
|
|
910
|
+
? `Codex could not find ${filePath} in this Overleaf project.`
|
|
911
|
+
: `Codex could not open ${filePath} in Overleaf.`,
|
|
929
912
|
technicalMessage: opened.reason || '',
|
|
930
913
|
evidence: {
|
|
914
|
+
originalCode: resultCode,
|
|
931
915
|
initialActivePath: initialActivePath || '',
|
|
932
916
|
openMethod: opened.method || '',
|
|
917
|
+
openAttempted: true,
|
|
933
918
|
writeStarted: false
|
|
934
919
|
}
|
|
935
920
|
})
|
|
@@ -1707,20 +1692,6 @@
|
|
|
1707
1692
|
}
|
|
1708
1693
|
|
|
1709
1694
|
|
|
1710
|
-
function diffTrackedChangeRefs(before = [], after = []) {
|
|
1711
|
-
const beforeKeys = new Set((before || []).map(ref => ref.key).filter(Boolean));
|
|
1712
|
-
const seen = new Set();
|
|
1713
|
-
const added = [];
|
|
1714
|
-
for (const ref of after || []) {
|
|
1715
|
-
if (!ref?.key || beforeKeys.has(ref.key) || seen.has(ref.key)) {
|
|
1716
|
-
continue;
|
|
1717
|
-
}
|
|
1718
|
-
seen.add(ref.key);
|
|
1719
|
-
added.push(ref);
|
|
1720
|
-
}
|
|
1721
|
-
return added;
|
|
1722
|
-
}
|
|
1723
|
-
|
|
1724
1695
|
function mergeTrackedChangeRefs(refs = []) {
|
|
1725
1696
|
return normalizeTrackedChangeRefs(refs);
|
|
1726
1697
|
}
|
|
@@ -202,7 +202,7 @@
|
|
|
202
202
|
return projectSnapshotBridge.getProjectFileList(params);
|
|
203
203
|
}
|
|
204
204
|
if (method === 'invalidateProjectSnapshot') {
|
|
205
|
-
projectSnapshotBridge.invalidateProjectSnapshot();
|
|
205
|
+
projectSnapshotBridge.invalidateProjectSnapshot(params);
|
|
206
206
|
return { ok: true };
|
|
207
207
|
}
|
|
208
208
|
if (method === 'createCheckpoint') {
|
|
@@ -584,6 +584,132 @@
|
|
|
584
584
|
};
|
|
585
585
|
}
|
|
586
586
|
|
|
587
|
+
function translateProviderError(text, locale) {
|
|
588
|
+
const rules = [
|
|
589
|
+
{
|
|
590
|
+
pattern: /provider_revision_conflict|active provider changed|provider changed in another tab/i,
|
|
591
|
+
conclusionZh: '这轮没有启动:模型服务配置已在另一个标签页发生变化。',
|
|
592
|
+
conclusionEn: 'This run did not start: the model provider configuration changed in another tab.',
|
|
593
|
+
nextZh: '请刷新模型服务配置,然后重新运行任务。',
|
|
594
|
+
nextEn: 'Refresh the provider configuration, then run the task again.',
|
|
595
|
+
code: 'provider_revision_conflict'
|
|
596
|
+
},
|
|
597
|
+
{
|
|
598
|
+
pattern: /provider_auth_rejected|rejected the API key|denied access for this credential/i,
|
|
599
|
+
conclusionZh: '这轮没有继续:第三方模型服务拒绝了当前 API 密钥。',
|
|
600
|
+
conclusionEn: 'This run did not continue: the third-party provider rejected the current API key.',
|
|
601
|
+
nextZh: '请在 Settings 的模型服务配置中替换 API 密钥并测试连接。',
|
|
602
|
+
nextEn: 'Replace the API key in Model Providers and run Test connection.',
|
|
603
|
+
code: 'provider_auth_rejected'
|
|
604
|
+
},
|
|
605
|
+
{
|
|
606
|
+
pattern: /provider_model_not_found|configured model was not found/i,
|
|
607
|
+
conclusionZh: '这轮没有继续:第三方模型服务中找不到所选模型。',
|
|
608
|
+
conclusionEn: 'This run did not continue: the selected model was not found by the third-party provider.',
|
|
609
|
+
nextZh: '请检查模型 ID,测试连接后重试。',
|
|
610
|
+
nextEn: 'Check the model ID, test the connection, and retry.',
|
|
611
|
+
code: 'provider_model_not_found'
|
|
612
|
+
},
|
|
613
|
+
{
|
|
614
|
+
pattern: /provider_model_not_configured|selected model is not configured|at least one model ID is required/i,
|
|
615
|
+
conclusionZh: '这轮没有启动:当前模型没有配置在启用的模型服务中。',
|
|
616
|
+
conclusionEn: 'This run did not start: the current model is not configured for the active provider.',
|
|
617
|
+
nextZh: '请打开模型服务配置,添加或选择一个可用模型。',
|
|
618
|
+
nextEn: 'Open Model Providers and add or select an available model.',
|
|
619
|
+
code: 'provider_model_not_configured'
|
|
620
|
+
},
|
|
621
|
+
{
|
|
622
|
+
pattern: /provider_protocol_unverified|test connection before (activating|using)/i,
|
|
623
|
+
conclusionZh: '这轮没有启动:自动 API 协议尚未完成验证。',
|
|
624
|
+
conclusionEn: 'This run did not start: the automatic API protocol has not been verified.',
|
|
625
|
+
nextZh: '请在模型服务配置中运行“测试连接”,保存后重试。',
|
|
626
|
+
nextEn: 'Run Test connection in Model Providers, save, and retry.',
|
|
627
|
+
code: 'provider_protocol_unverified'
|
|
628
|
+
},
|
|
629
|
+
{
|
|
630
|
+
pattern: /provider_protocol_incompatible|incompatible with this API protocol/i,
|
|
631
|
+
conclusionZh: '这轮没有继续:端点与所选 API 协议不兼容。',
|
|
632
|
+
conclusionEn: 'This run did not continue: the endpoint is incompatible with the selected API protocol.',
|
|
633
|
+
nextZh: '请切换 Responses 或 Chat Completions,或使用自动检测重新测试。',
|
|
634
|
+
nextEn: 'Switch between Responses and Chat Completions, or test again with Auto detection.',
|
|
635
|
+
code: 'provider_protocol_incompatible'
|
|
636
|
+
},
|
|
637
|
+
{
|
|
638
|
+
pattern: /provider_rate_limited|rate limit was reached/i,
|
|
639
|
+
conclusionZh: '这轮没有完成:第三方模型服务触发了限流。',
|
|
640
|
+
conclusionEn: 'This run did not finish: the third-party provider rate limit was reached.',
|
|
641
|
+
nextZh: '请等待限流窗口恢复后重试。',
|
|
642
|
+
nextEn: 'Wait for the provider rate-limit window to recover, then retry.',
|
|
643
|
+
code: 'provider_rate_limited'
|
|
644
|
+
},
|
|
645
|
+
{
|
|
646
|
+
pattern: /provider_connection_timeout|provider connection.*timed out/i,
|
|
647
|
+
conclusionZh: '这轮没有完成:连接第三方模型服务超时。',
|
|
648
|
+
conclusionEn: 'This run did not finish: the third-party provider connection timed out.',
|
|
649
|
+
nextZh: '请检查端点和网络,或提高模型服务的请求超时后重试。',
|
|
650
|
+
nextEn: 'Check the endpoint and network, or increase the provider timeout before retrying.',
|
|
651
|
+
code: 'provider_connection_timeout'
|
|
652
|
+
},
|
|
653
|
+
{
|
|
654
|
+
pattern: /provider_dns_failed|hostname could not be resolved/i,
|
|
655
|
+
conclusionZh: '这轮没有启动:无法解析第三方模型服务的主机名。',
|
|
656
|
+
conclusionEn: 'This run did not start: the third-party provider hostname could not be resolved.',
|
|
657
|
+
nextZh: '请检查 Base URL 和本机 DNS 后重试。',
|
|
658
|
+
nextEn: 'Check the Base URL and local DNS, then retry.',
|
|
659
|
+
code: 'provider_dns_failed'
|
|
660
|
+
},
|
|
661
|
+
{
|
|
662
|
+
pattern: /provider_tls_failed|TLS verification failed/i,
|
|
663
|
+
conclusionZh: '这轮没有启动:第三方模型服务的 TLS 验证失败。',
|
|
664
|
+
conclusionEn: 'This run did not start: TLS verification failed for the third-party provider.',
|
|
665
|
+
nextZh: '请检查端点证书和 Base URL,修复后重试。',
|
|
666
|
+
nextEn: 'Check the endpoint certificate and Base URL, then retry.',
|
|
667
|
+
code: 'provider_tls_failed'
|
|
668
|
+
},
|
|
669
|
+
{
|
|
670
|
+
pattern: /provider_not_found|active provider no longer exists/i,
|
|
671
|
+
conclusionZh: '这轮没有启动:启用的模型服务已经不存在。',
|
|
672
|
+
conclusionEn: 'This run did not start: the active model provider no longer exists.',
|
|
673
|
+
nextZh: '请在 Settings 中选择内置 Codex 或另一个模型服务。',
|
|
674
|
+
nextEn: 'Select Built-in Codex or another provider in Settings.',
|
|
675
|
+
code: 'provider_not_found'
|
|
676
|
+
},
|
|
677
|
+
{
|
|
678
|
+
pattern: /provider_store_corrupt|provider stores are out of sync|provider store.*invalid/i,
|
|
679
|
+
conclusionZh: '这轮没有启动:本地模型服务配置存储已损坏或不同步。',
|
|
680
|
+
conclusionEn: 'This run did not start: the local provider configuration store is corrupt or out of sync.',
|
|
681
|
+
nextZh: '请打开技术详情,修复或重建本地模型服务配置。',
|
|
682
|
+
nextEn: 'Open Technical Details and repair or rebuild the local provider configuration.',
|
|
683
|
+
code: 'provider_store_corrupt'
|
|
684
|
+
},
|
|
685
|
+
{
|
|
686
|
+
pattern: /provider_store_unavailable|provider settings could not be saved/i,
|
|
687
|
+
conclusionZh: '模型服务操作失败:Native Host 无法访问本地配置存储。',
|
|
688
|
+
conclusionEn: 'The provider operation failed: the Native Host could not access local provider storage.',
|
|
689
|
+
nextZh: '请检查本地目录权限和 Native Host 状态后重试。',
|
|
690
|
+
nextEn: 'Check local directory permissions and Native Host status, then retry.',
|
|
691
|
+
code: 'provider_store_unavailable'
|
|
692
|
+
},
|
|
693
|
+
{
|
|
694
|
+
pattern: /provider_response_invalid|provider connection test failed/i,
|
|
695
|
+
conclusionZh: '这轮没有继续:第三方模型服务返回了不兼容或无效的响应。',
|
|
696
|
+
conclusionEn: 'This run did not continue: the third-party provider returned an incompatible or invalid response.',
|
|
697
|
+
nextZh: '请测试连接并确认服务兼容所选 API 协议。',
|
|
698
|
+
nextEn: 'Test the connection and confirm that the endpoint supports the selected API protocol.',
|
|
699
|
+
code: 'provider_response_invalid'
|
|
700
|
+
}
|
|
701
|
+
];
|
|
702
|
+
const rule = rules.find(candidate => candidate.pattern.test(text));
|
|
703
|
+
if (!rule) {
|
|
704
|
+
return null;
|
|
705
|
+
}
|
|
706
|
+
return {
|
|
707
|
+
conclusion: textFor(locale, rule.conclusionZh, rule.conclusionEn),
|
|
708
|
+
nextStep: textFor(locale, rule.nextZh, rule.nextEn),
|
|
709
|
+
failureCode: rule.code
|
|
710
|
+
};
|
|
711
|
+
}
|
|
712
|
+
|
|
587
713
|
function translateRawError(message, context = {}) {
|
|
588
714
|
const locale = normalizeLocale(context);
|
|
589
715
|
const text = String(message || '');
|
|
@@ -594,6 +720,10 @@
|
|
|
594
720
|
// failure data alongside the human-readable text. The string text is kept
|
|
595
721
|
// verbatim because it has mode-aware nuance (e.g. "No files were written"
|
|
596
722
|
// vs "This run did not start") the catalog's fallback strings don't capture.
|
|
723
|
+
const providerError = translateProviderError(text, locale);
|
|
724
|
+
if (providerError) {
|
|
725
|
+
return providerError;
|
|
726
|
+
}
|
|
597
727
|
if (/Mode must be "confirm" or "auto"/i.test(text)) {
|
|
598
728
|
return {
|
|
599
729
|
conclusion: textFor(locale, '这轮没有写入:当前是“只问不改”,但这个任务需要写入权限。', 'No files were written: this task needs write access, but the current mode is Ask.'),
|
|
@@ -7,12 +7,12 @@
|
|
|
7
7
|
})(typeof globalThis !== 'undefined' ? globalThis : window, function compatibilityFactory() {
|
|
8
8
|
'use strict';
|
|
9
9
|
|
|
10
|
-
const EXTENSION_PROTOCOL_VERSION =
|
|
11
|
-
const SUPPORTED_NATIVE_PROTOCOL = Object.freeze({ min: 1, max:
|
|
10
|
+
const EXTENSION_PROTOCOL_VERSION = 2;
|
|
11
|
+
const SUPPORTED_NATIVE_PROTOCOL = Object.freeze({ min: 1, max: 2 });
|
|
12
12
|
const MIN_NATIVE_VERSION = '1.0.0';
|
|
13
13
|
const MIN_COMPATIBLE_NATIVE_VERSION = '1.0.0';
|
|
14
14
|
const MIN_COMPATIBLE_EXTENSION_VERSION = '1.0.0';
|
|
15
|
-
const BUILD_TARGET_VERSION = '1.
|
|
15
|
+
const BUILD_TARGET_VERSION = '2.1.0';
|
|
16
16
|
const DEFAULT_CHROME_EXTENSION_ID = 'illdpneeeopfffmiepaejglgmhpmdhdc';
|
|
17
17
|
const REQUIRED_CAPABILITIES = Object.freeze([
|
|
18
18
|
'bridgePing',
|
|
@@ -24,7 +24,8 @@
|
|
|
24
24
|
'codexModels',
|
|
25
25
|
'historyClearPlugin',
|
|
26
26
|
'localSkills',
|
|
27
|
-
'mirrorSensitiveScan'
|
|
27
|
+
'mirrorSensitiveScan',
|
|
28
|
+
'providerProfiles'
|
|
28
29
|
]);
|
|
29
30
|
const UPDATE_AVAILABLE_CAPABILITIES = Object.freeze([
|
|
30
31
|
'bridgePing',
|
|
@@ -153,7 +154,7 @@
|
|
|
153
154
|
return { status: 'native_too_old', classification: 'incompatible' };
|
|
154
155
|
}
|
|
155
156
|
|
|
156
|
-
if (!
|
|
157
|
+
if (!reportsSupportedProtocol(native)) {
|
|
157
158
|
return { status: 'protocol_unsupported', classification: 'incompatible' };
|
|
158
159
|
}
|
|
159
160
|
|
|
@@ -233,8 +234,10 @@
|
|
|
233
234
|
return requiredCapabilities.filter(capability => capabilities[capability] !== true);
|
|
234
235
|
}
|
|
235
236
|
|
|
236
|
-
function
|
|
237
|
-
if (native.protocolVersion
|
|
237
|
+
function reportsSupportedProtocol(native) {
|
|
238
|
+
if (!Number.isInteger(native.protocolVersion) ||
|
|
239
|
+
native.protocolVersion < SUPPORTED_NATIVE_PROTOCOL.min ||
|
|
240
|
+
native.protocolVersion > SUPPORTED_NATIVE_PROTOCOL.max) {
|
|
238
241
|
return false;
|
|
239
242
|
}
|
|
240
243
|
if (native.supportedProtocol === undefined || native.supportedProtocol === null) {
|