codex-overleaf-link 2.5.1 → 2.6.5
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 +149 -398
- package/README.zh-CN.md +153 -404
- package/extension/bootstrap/manifest.template.json +1 -1
- package/extension/src/background.js +148 -3
- package/extension/src/backgroundSharedSessions.js +55 -2
- package/extension/src/backgroundUpdateCoordinator.js +128 -12
- package/extension/src/content/activeTurnControl.js +164 -7
- package/extension/src/content/composerAttachments.js +46 -10
- package/extension/src/content/composerPanel.js +92 -12
- package/extension/src/content/contentRuntime.js +1114 -321
- package/extension/src/content/contextTray.js +8 -2
- package/extension/src/content/contextUsageView.js +267 -0
- package/extension/src/content/diagnosticsController.js +77 -34
- package/extension/src/content/diagnosticsExport.js +1 -1
- package/extension/src/content/diagnosticsPanel.js +147 -42
- package/extension/src/content/generated/content.bundle.js +5761 -1237
- package/extension/src/content/generated/content.bundle.meta.json +6 -3
- package/extension/src/content/legacyGlobalRegistry.js +4 -0
- package/extension/src/content/localSkillsPanel.js +6 -4
- package/extension/src/content/markdownDomRenderer.js +5 -3
- package/extension/src/content/markdownText.js +53 -14
- package/extension/src/content/modelPicker.js +29 -19
- package/extension/src/content/nativeCompatibilityController.js +52 -19
- package/extension/src/content/otWarmMirror.js +1 -1
- package/extension/src/content/pageBridgeClient.js +7 -7
- package/extension/src/content/panelMaintenance.js +10 -2
- package/extension/src/content/panelRenderer.js +55 -28
- package/extension/src/content/pendingInputView.js +1 -1
- package/extension/src/content/projectSettingsCoordinator.js +30 -6
- package/extension/src/content/providerSettingsDialog.js +28 -19
- package/extension/src/content/recentProjects.js +30 -19
- package/extension/src/content/runActivitySummary.js +122 -14
- package/extension/src/content/runCompletionMeta.js +194 -6
- package/extension/src/content/runController.js +2 -1
- package/extension/src/content/runFailureNotice.js +8 -1
- package/extension/src/content/runPresence.js +18 -8
- package/extension/src/content/runQueueScheduler.js +27 -13
- package/extension/src/content/runRecoveryActions.js +2 -1
- package/extension/src/content/runResultActions.js +73 -24
- package/extension/src/content/runReviewController.js +102 -37
- package/extension/src/content/runTimelineView.js +224 -25
- package/extension/src/content/scopedPersistencePanelState.js +124 -46
- package/extension/src/content/selectionContextView.js +30 -3
- package/extension/src/content/sensitivePreflight.js +14 -1
- package/extension/src/content/sessionManager.js +49 -3
- package/extension/src/content/sessionMenuView.js +54 -18
- package/extension/src/content/sessionPersistence.js +25 -5
- package/extension/src/content/settingsPanel.js +15 -2
- package/extension/src/content/settingsWorkbench.js +127 -89
- package/extension/src/content/sharedSessionViewSync.js +13 -4
- package/extension/src/content/subagentActivityView.js +23 -7
- package/extension/src/content/updateNotice.js +60 -6
- package/extension/src/content/writebackOrchestrator.js +301 -72
- package/extension/src/content/writingStyleSettings.js +70 -17
- package/extension/src/page/compileBridge.js +193 -180
- package/extension/src/page/overleafCapabilities.js +4 -1
- package/extension/src/page/overleafEditor.js +12 -3
- package/extension/src/page/overleafProjectSnapshot.js +56 -3
- package/extension/src/page/snapshotRouter.js +75 -66
- package/extension/src/page/textCoordinates.js +17 -1
- package/extension/src/page/textFileCreator.js +9 -1
- package/extension/src/page/treeOperations.js +200 -19
- package/extension/src/page/writebackRouter.js +172 -113
- package/extension/src/pageBridge.js +124 -73
- package/extension/src/popup.js +1 -1
- package/extension/src/shared/agentTranscript.js +372 -18
- package/extension/src/shared/auditRecords.js +3 -0
- package/extension/src/shared/compatibility.js +1 -1
- package/extension/src/shared/compileAdapter.js +58 -17
- package/extension/src/shared/composerAttachmentDraft.js +27 -7
- package/extension/src/shared/contextUsage.js +125 -0
- package/extension/src/shared/failureReasons.js +57 -3
- package/extension/src/shared/i18n.js +315 -29
- package/extension/src/shared/lineReferences.js +38 -31
- package/extension/src/shared/pageRpcContract.js +1 -1
- package/extension/src/shared/panelIcons.js +39 -0
- package/extension/src/shared/projectFiles.js +2 -1
- package/extension/src/shared/reviewing.js +130 -1
- package/extension/src/shared/runActivityModel.js +70 -4
- package/extension/src/shared/runExecutionSnapshot.js +13 -0
- package/extension/src/shared/scopedPersistenceBrowserAdapter.js +34 -0
- package/extension/src/shared/sessionState.js +63 -4
- package/extension/src/shared/sessionTitle.js +14 -1
- package/extension/src/shared/sharedSessionBridge.js +81 -13
- package/extension/src/shared/staleGuard.js +130 -63
- package/extension/src/shared/storageDb.js +35 -3
- package/extension/src/shared/storageMigration.js +111 -2
- package/extension/src/shared/storageRunActions.js +62 -8
- package/extension/src/shared/undoOperations.js +126 -28
- package/extension/src/shared/updateRuntimeIdentity.js +16 -2
- package/extension/src/shared/writebackSettlement.js +21 -3
- package/extension/styles/panel.css +1293 -549
- package/native-host/src/bootstrapUpdate.js +52 -16
- package/native-host/src/chatBridgeRequest.js +60 -6
- package/native-host/src/chatBridgeResponse.js +10 -1
- package/native-host/src/chatCompletionsBridge.js +29 -2
- package/native-host/src/codexPromptAssembly.js +86 -118
- package/native-host/src/codexSessionRunner.js +127 -17
- package/native-host/src/debugLog.js +27 -3
- package/native-host/src/index.js +42 -11
- package/native-host/src/localSkills.js +1 -1
- package/native-host/src/mirrorWorkspace.js +47 -3
- package/native-host/src/nativeEnvironment.js +27 -5
- package/native-host/src/providerContextError.js +20 -0
- package/native-host/src/providerRuntime.js +1 -0
- package/native-host/src/skills/reviewer-response/SKILL.md +65 -0
- package/native-host/src/taskRunnerRuntime.js +28 -8
- package/native-host/src/textPatch.js +473 -377
- package/native-host/src/updateManager.js +144 -21
- package/native-host/src/writingStyleRuntime.js +1 -1
- package/package.json +5 -8
|
@@ -8,13 +8,21 @@
|
|
|
8
8
|
'use strict';
|
|
9
9
|
|
|
10
10
|
const COMPILE_TEMPLATE_TTL_MS = 5 * 60 * 1000;
|
|
11
|
+
const MAX_RECENT_COMPILE_CAPTURES = 8;
|
|
12
|
+
const COMPILE_LOG_MAX_AGE_MS = 30000;
|
|
11
13
|
const GLOBAL_STATE_KEY = '__codexOverleafCompileBridgeState';
|
|
14
|
+
const FETCH_WRAPPER_REVISION = '2026-10-17-compile-capture-v4';
|
|
12
15
|
|
|
13
16
|
function create(deps = {}) {
|
|
14
17
|
const pageWindow = deps.window || window;
|
|
15
18
|
const pageDocument = deps.document || document;
|
|
16
19
|
let installed = false;
|
|
17
|
-
const
|
|
20
|
+
const previousState = pageWindow[GLOBAL_STATE_KEY];
|
|
21
|
+
// A buried wrapper still closes over its old state. Isolate its counters and
|
|
22
|
+
// late responses while keeping the other script's fetch chain intact.
|
|
23
|
+
const replaceWrapper = previousState?.wrappedFetch
|
|
24
|
+
&& (previousState.wrapperRevision !== FETCH_WRAPPER_REVISION || pageWindow.fetch !== previousState.wrappedFetch);
|
|
25
|
+
const state = replaceWrapper ? { ...previousState, inputListenerInstalled: false } : previousState || {
|
|
18
26
|
capturedRequestTemplate: null,
|
|
19
27
|
lastCompileResponse: null,
|
|
20
28
|
lastCompileAt: 0,
|
|
@@ -24,6 +32,15 @@
|
|
|
24
32
|
wrappedFetch: null,
|
|
25
33
|
inputListenerInstalled: false
|
|
26
34
|
};
|
|
35
|
+
if (replaceWrapper && previousState.inputListener) {
|
|
36
|
+
pageDocument.removeEventListener?.('input', previousState.inputListener, true);
|
|
37
|
+
}
|
|
38
|
+
state.compileRequestSequence ||= 0;
|
|
39
|
+
// Keep completed captures isolated from a detached older wrapper.
|
|
40
|
+
state.recentCompileCaptures = (state.recentCompileCaptures || []).slice(0, MAX_RECENT_COMPILE_CAPTURES);
|
|
41
|
+
// Busy tokens alone survive reinjection: old requests can release their own
|
|
42
|
+
// tokens without advancing the new wrapper's capture counters or results.
|
|
43
|
+
state.inFlightCompileRequests ||= new Set();
|
|
27
44
|
pageWindow[GLOBAL_STATE_KEY] = state;
|
|
28
45
|
|
|
29
46
|
function install() {
|
|
@@ -34,24 +51,28 @@
|
|
|
34
51
|
interceptCompileRequests();
|
|
35
52
|
if (typeof pageDocument.addEventListener === 'function' && state.inputListenerInstalled !== true) {
|
|
36
53
|
pageDocument.addEventListener('input', markSourceEditFromInput, true);
|
|
54
|
+
state.inputListener = markSourceEditFromInput;
|
|
37
55
|
state.inputListenerInstalled = true;
|
|
38
56
|
}
|
|
39
57
|
}
|
|
40
58
|
|
|
41
59
|
function interceptCompileRequests() {
|
|
42
|
-
if (state.wrappedFetch && pageWindow.fetch === state.wrappedFetch) {
|
|
60
|
+
if (state.wrapperRevision === FETCH_WRAPPER_REVISION && state.wrappedFetch && pageWindow.fetch === state.wrappedFetch) {
|
|
43
61
|
return;
|
|
44
62
|
}
|
|
45
63
|
if (!state.originalFetch || pageWindow.fetch !== state.wrappedFetch) {
|
|
46
64
|
state.originalFetch = pageWindow.fetch;
|
|
47
65
|
}
|
|
48
66
|
const originalFetch = state.originalFetch;
|
|
67
|
+
state.wrapperRevision = FETCH_WRAPPER_REVISION;
|
|
49
68
|
state.wrappedFetch = async function codexOverleafCompileFetchWrapper(...args) {
|
|
50
69
|
const [resource, init] = args;
|
|
51
70
|
const url = typeof resource === 'string' ? resource : (resource?.url || '');
|
|
52
|
-
const method = (init?.method || 'GET').toUpperCase();
|
|
53
|
-
const isCompileRequest = /\/project\/[^/]+\/compile
|
|
71
|
+
const method = (init?.method || resource?.method || 'GET').toUpperCase();
|
|
72
|
+
const isCompileRequest = /\/project\/[^/]+\/compile(?:[?#]|$)/.test(url) && method === 'POST';
|
|
73
|
+
const captureId = isCompileRequest ? ++state.compileRequestSequence : 0;
|
|
54
74
|
const compileSourceChangeTimestamp = isCompileRequest ? state.lastKnownSourceEditTimestamp : 0;
|
|
75
|
+
const pendingRequest = isCompileRequest ? {} : null;
|
|
55
76
|
|
|
56
77
|
if (isCompileRequest) {
|
|
57
78
|
state.capturedRequestTemplate = {
|
|
@@ -67,29 +88,46 @@
|
|
|
67
88
|
};
|
|
68
89
|
}
|
|
69
90
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
91
|
+
if (pendingRequest) state.inFlightCompileRequests.add(pendingRequest);
|
|
92
|
+
try {
|
|
93
|
+
const response = await originalFetch.apply(this, args);
|
|
94
|
+
|
|
95
|
+
if (isCompileRequest && response.ok) {
|
|
96
|
+
try {
|
|
97
|
+
const clone = response.clone();
|
|
98
|
+
const json = await clone.json();
|
|
99
|
+
const CompileAdapter = pageWindow.CodexOverleafCompileAdapter;
|
|
100
|
+
if (CompileAdapter) {
|
|
101
|
+
const capture = {
|
|
102
|
+
compile: { ...CompileAdapter.parseCompileResponse(json), captureId },
|
|
103
|
+
compiledAt: Date.now(),
|
|
104
|
+
sourceChangeTimestamp: compileSourceChangeTimestamp
|
|
105
|
+
};
|
|
106
|
+
state.recentCompileCaptures = [capture, ...state.recentCompileCaptures]
|
|
107
|
+
.sort((a, b) => b.compile.captureId - a.compile.captureId).slice(0, MAX_RECENT_COMPILE_CAPTURES);
|
|
108
|
+
// Preserve request order even if an older response finishes last.
|
|
109
|
+
if (captureId > (state.lastCompileResponse?.captureId || 0)) {
|
|
110
|
+
state.lastCompileResponse = capture.compile;
|
|
111
|
+
state.lastCompileAt = capture.compiledAt;
|
|
112
|
+
state.lastCompileSourceChangeTimestamp = capture.sourceChangeTimestamp;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
} catch (_e) { /* ignore parse errors */ }
|
|
116
|
+
}
|
|
117
|
+
return response;
|
|
118
|
+
} finally {
|
|
119
|
+
if (pendingRequest) state.inFlightCompileRequests.delete(pendingRequest);
|
|
83
120
|
}
|
|
84
|
-
|
|
85
|
-
return response;
|
|
86
121
|
};
|
|
87
122
|
pageWindow.fetch = state.wrappedFetch;
|
|
88
123
|
}
|
|
89
124
|
|
|
90
125
|
async function triggerCompile(params = {}) {
|
|
126
|
+
const sourceAtTrigger = state.lastKnownSourceEditTimestamp;
|
|
91
127
|
const saveResult = await waitForCompileSaveState(params);
|
|
92
|
-
|
|
128
|
+
// Current Overleaf shows no save indicator while idle; a quiet editor is
|
|
129
|
+
// the same weaker evidence the update-idle check accepts.
|
|
130
|
+
if (params.requireVerifiedSave !== false && !['verified_saved', 'verified_quiet'].includes(saveResult.state)) {
|
|
93
131
|
return {
|
|
94
132
|
ok: false,
|
|
95
133
|
reason: saveResult.reason || 'Overleaf save state was not verified.',
|
|
@@ -98,74 +136,29 @@
|
|
|
98
136
|
};
|
|
99
137
|
}
|
|
100
138
|
const saveMetadata = buildSaveStateMetadata(saveResult);
|
|
101
|
-
const
|
|
102
|
-
if (params.preferUiClick === true) {
|
|
103
|
-
const clicked = clickOverleafCompileButton();
|
|
104
|
-
if (clicked) {
|
|
105
|
-
const captured = await waitForCapturedCompile(params.captureTimeoutMs || 60000);
|
|
106
|
-
if (captured.ok) {
|
|
107
|
-
return {
|
|
108
|
-
ok: true,
|
|
109
|
-
compile: state.lastCompileResponse,
|
|
110
|
-
...saveMetadata,
|
|
111
|
-
triggeredBy: 'overleaf-button'
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
|
-
return {
|
|
115
|
-
ok: true,
|
|
116
|
-
compile: { ok: true, status: 'triggered' },
|
|
117
|
-
resultCaptured: false,
|
|
118
|
-
reason: captured.reason,
|
|
119
|
-
...saveMetadata,
|
|
120
|
-
triggeredBy: 'overleaf-button'
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
|
-
}
|
|
139
|
+
const startCaptureId = state.compileRequestSequence;
|
|
124
140
|
const template = state.capturedRequestTemplate;
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
reason: 'No Overleaf Recompile button or compile request template is available.',
|
|
131
|
-
...saveMetadata
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
const captured = await waitForCapturedCompile(params.captureTimeoutMs || 60000);
|
|
135
|
-
if (!captured.ok) {
|
|
136
|
-
return {
|
|
137
|
-
...captured,
|
|
138
|
-
...saveMetadata
|
|
139
|
-
};
|
|
140
|
-
}
|
|
141
|
+
const button = findOverleafCompileButton();
|
|
142
|
+
// Busy UI or observed requests forbid the template fallback too. Waiting
|
|
143
|
+
// can reuse the in-flight result or click the main button once it is idle.
|
|
144
|
+
if (isCompileBusy(button) || (button && (params.preferUiClick === true || !isFreshCapturedRequestTemplate(template)))) {
|
|
145
|
+
const captured = await waitForCapturedCompile(params.captureTimeoutMs || 60000, sourceAtTrigger);
|
|
141
146
|
return {
|
|
142
147
|
ok: true,
|
|
143
|
-
compile:
|
|
148
|
+
compile: captured.compile || { ok: true, status: 'triggered' },
|
|
149
|
+
resultCaptured: captured.ok,
|
|
150
|
+
reason: captured.reason || '',
|
|
144
151
|
...saveMetadata,
|
|
145
|
-
triggeredBy: 'overleaf-button'
|
|
152
|
+
...(captured.clicked ? { triggeredBy: 'overleaf-button' } : {})
|
|
146
153
|
};
|
|
147
154
|
}
|
|
148
|
-
if (!isFreshCapturedRequestTemplate(template)) {
|
|
149
|
-
const clicked = clickOverleafCompileButton();
|
|
150
|
-
if (!clicked) {
|
|
151
|
-
return {
|
|
152
|
-
ok: false,
|
|
153
|
-
reason: 'Stored compile request template expired. Please click Overleaf Recompile once, then retry.',
|
|
154
|
-
...saveMetadata
|
|
155
|
-
};
|
|
156
|
-
}
|
|
157
|
-
const captured = await waitForCapturedCompile(params.captureTimeoutMs || 60000);
|
|
158
|
-
if (!captured.ok) {
|
|
159
|
-
return {
|
|
160
|
-
...captured,
|
|
161
|
-
...saveMetadata
|
|
162
|
-
};
|
|
163
|
-
}
|
|
155
|
+
if (!template || !isFreshCapturedRequestTemplate(template)) {
|
|
164
156
|
return {
|
|
165
|
-
ok:
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
157
|
+
ok: false,
|
|
158
|
+
reason: template
|
|
159
|
+
? 'Stored compile request template expired. Please click Overleaf Recompile once, then retry.'
|
|
160
|
+
: 'No Overleaf Recompile button or compile request template is available.',
|
|
161
|
+
...saveMetadata
|
|
169
162
|
};
|
|
170
163
|
}
|
|
171
164
|
|
|
@@ -183,17 +176,12 @@
|
|
|
183
176
|
...saveMetadata
|
|
184
177
|
};
|
|
185
178
|
}
|
|
186
|
-
const
|
|
187
|
-
const CompileAdapter = pageWindow.CodexOverleafCompileAdapter;
|
|
188
|
-
const parsed = CompileAdapter
|
|
189
|
-
? CompileAdapter.parseCompileResponse(json)
|
|
190
|
-
: { ok: false, reason: 'CompileAdapter not available' };
|
|
191
|
-
state.lastCompileResponse = parsed;
|
|
192
|
-
state.lastCompileAt = Date.now();
|
|
193
|
-
state.lastCompileSourceChangeTimestamp = sourceChangeTimestamp;
|
|
179
|
+
const captured = findEligibleCapture(sourceAtTrigger, startCaptureId)?.compile;
|
|
194
180
|
return {
|
|
195
181
|
ok: true,
|
|
196
|
-
compile:
|
|
182
|
+
compile: captured || { ok: false, status: 'triggered' },
|
|
183
|
+
resultCaptured: Boolean(captured),
|
|
184
|
+
reason: captured ? '' : 'compile_capture_missing',
|
|
197
185
|
...saveMetadata
|
|
198
186
|
};
|
|
199
187
|
} catch (error) {
|
|
@@ -215,7 +203,9 @@
|
|
|
215
203
|
}
|
|
216
204
|
try {
|
|
217
205
|
return normalizeSaveStateResult(
|
|
218
|
-
await deps.waitForSaveState({ deadlineMs: resolveWaitForSaveMs(params.waitForSaveMs)
|
|
206
|
+
await deps.waitForSaveState({ deadlineMs: resolveWaitForSaveMs(params.waitForSaveMs),
|
|
207
|
+
// Only a caller that requires save evidence may settle for a quiet editor.
|
|
208
|
+
allowQuietEditorFallback: params.requireVerifiedSave !== false, quietFallbackMs: 1000 })
|
|
219
209
|
);
|
|
220
210
|
} catch (error) {
|
|
221
211
|
return {
|
|
@@ -269,18 +259,16 @@
|
|
|
269
259
|
return { ok: false, reason: 'CompileAdapter not loaded' };
|
|
270
260
|
}
|
|
271
261
|
|
|
272
|
-
const maxAgeMs = params.maxAgeMs
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
state.
|
|
276
|
-
);
|
|
277
|
-
const needsFreshCompile = !state.lastCompileResponse?.ok
|
|
278
|
-
|| (Date.now() - state.lastCompileAt > maxAgeMs)
|
|
279
|
-
|| !compileLogFresh;
|
|
262
|
+
const maxAgeMs = params.maxAgeMs ?? COMPILE_LOG_MAX_AGE_MS;
|
|
263
|
+
let capture = params.captureId != null
|
|
264
|
+
? state.recentCompileCaptures.find(item => item.compile.captureId === params.captureId)
|
|
265
|
+
: state.recentCompileCaptures[0];
|
|
266
|
+
const needsFreshCompile = !capture?.compile.ok || Boolean(captureFreshnessReason(capture, maxAgeMs));
|
|
280
267
|
|
|
281
|
-
|
|
268
|
+
// A named capture must never silently switch to another compile's log.
|
|
269
|
+
if (needsFreshCompile && params.captureId == null && params.triggerIfStale !== false) {
|
|
282
270
|
const compileResult = await triggerCompile({ waitForSaveMs: resolveWaitForSaveMs(params.waitForSaveMs) });
|
|
283
|
-
if (!compileResult.ok) {
|
|
271
|
+
if (!compileResult.ok || !compileResult.resultCaptured) {
|
|
284
272
|
return {
|
|
285
273
|
ok: false,
|
|
286
274
|
reason: `Could not get fresh compile: ${compileResult.reason}`,
|
|
@@ -288,38 +276,70 @@
|
|
|
288
276
|
saveState: compileResult.saveState
|
|
289
277
|
};
|
|
290
278
|
}
|
|
279
|
+
capture = state.recentCompileCaptures.find(item => item.compile.captureId === compileResult.compile?.captureId);
|
|
291
280
|
}
|
|
292
281
|
|
|
293
|
-
|
|
282
|
+
if (!capture) return { ok: false, reason: 'compile_capture_missing' };
|
|
283
|
+
const { compile: compiled, compiledAt } = capture;
|
|
284
|
+
const freshnessReason = captureFreshnessReason(capture, maxAgeMs);
|
|
285
|
+
if (freshnessReason) return { ok: false, captureId: compiled.captureId, fresh: false, reason: freshnessReason };
|
|
294
286
|
if (!compiled?.ok || !compiled.logUrl) {
|
|
295
|
-
return { ok: false, reason: 'No compile log URL available' };
|
|
287
|
+
return { ok: false, reason: compiled?.reason || 'No compile log URL available' };
|
|
296
288
|
}
|
|
297
289
|
|
|
290
|
+
const candidates = [];
|
|
291
|
+
if (compiled.build && compiled.pdfDownloadDomain) {
|
|
292
|
+
// pdfDownloadDomain already includes Overleaf's outputUrlPrefix.
|
|
293
|
+
candidates.push({ route: 'download', url: `${compiled.pdfDownloadDomain}${compiled.logUrl}` });
|
|
294
|
+
}
|
|
298
295
|
try {
|
|
299
|
-
const
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
296
|
+
const url = new URL(compiled.logUrl, pageWindow.location.origin);
|
|
297
|
+
if (url.origin === pageWindow.location.origin) {
|
|
298
|
+
if (compiled.clsiServerId) url.searchParams.set('clsiserverid', compiled.clsiServerId);
|
|
299
|
+
if (!candidates.some(candidate => candidate.url === url.href)) candidates.push({ route: 'origin', url: url.href });
|
|
300
|
+
}
|
|
301
|
+
} catch (_error) { /* invalid log URL is reported below */ }
|
|
302
|
+
const failures = [];
|
|
303
|
+
for (const { route, url } of candidates) {
|
|
304
|
+
try {
|
|
305
|
+
const credentials = new URL(url).origin === pageWindow.location.origin ? 'same-origin' : 'omit';
|
|
306
|
+
const response = await pageWindow.fetch(url, { credentials, cache: 'no-store' });
|
|
307
|
+
if (!response.ok) {
|
|
308
|
+
failures.push(`${route} HTTP ${response.status}`);
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
const logContent = await response.text();
|
|
312
|
+
const parsed = CompileAdapter.parseLogErrors(logContent);
|
|
313
|
+
const reason = captureFreshnessReason(capture, maxAgeMs);
|
|
314
|
+
return {
|
|
315
|
+
ok: logContent.trim().length > 0,
|
|
316
|
+
captureId: compiled.captureId,
|
|
317
|
+
log: CompileAdapter.truncateLogForContext(logContent),
|
|
318
|
+
errors: parsed.errors,
|
|
319
|
+
warnings: parsed.warnings,
|
|
320
|
+
compiledAt,
|
|
321
|
+
fresh: !reason,
|
|
322
|
+
reason: reason || (logContent.trim() ? '' : 'compile_log_empty')
|
|
323
|
+
};
|
|
324
|
+
} catch (_error) {
|
|
325
|
+
failures.push(`${route} network error`);
|
|
305
326
|
}
|
|
306
|
-
const logContent = await response.text();
|
|
307
|
-
const truncated = CompileAdapter.truncateLogForContext(logContent);
|
|
308
|
-
const parsed = CompileAdapter.parseLogErrors(logContent);
|
|
309
|
-
return {
|
|
310
|
-
ok: true,
|
|
311
|
-
log: truncated,
|
|
312
|
-
errors: parsed.errors,
|
|
313
|
-
warnings: parsed.warnings,
|
|
314
|
-
compiledAt: state.lastCompileAt,
|
|
315
|
-
fresh: CompileAdapter.isCompileLogFresh(
|
|
316
|
-
{ sourceChangeTimestamp: state.lastCompileSourceChangeTimestamp },
|
|
317
|
-
state.lastKnownSourceEditTimestamp
|
|
318
|
-
)
|
|
319
|
-
};
|
|
320
|
-
} catch (error) {
|
|
321
|
-
return { ok: false, reason: `Log fetch error: ${error.message}` };
|
|
322
327
|
}
|
|
328
|
+
const reason = captureFreshnessReason(capture, maxAgeMs);
|
|
329
|
+
return { ok: false, captureId: compiled.captureId, fresh: !reason,
|
|
330
|
+
reason: reason || (failures.length ? `compile_log_fetch: ${failures.join('; ')}` : 'compile_log_url_invalid') };
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function captureFreshnessReason(capture, maxAgeMs = COMPILE_LOG_MAX_AGE_MS) {
|
|
334
|
+
if (!capture) return 'compile_capture_missing';
|
|
335
|
+
if (!(capture.sourceChangeTimestamp >= state.lastKnownSourceEditTimestamp)) return 'compile_log_stale_source';
|
|
336
|
+
if (!(Date.now() - capture.compiledAt <= maxAgeMs)) return 'compile_log_expired';
|
|
337
|
+
return '';
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function findEligibleCapture(sourceAtTrigger, afterCaptureId = 0) {
|
|
341
|
+
return state.recentCompileCaptures.find(capture => capture.compile.captureId > afterCaptureId
|
|
342
|
+
&& capture.sourceChangeTimestamp >= sourceAtTrigger && !captureFreshnessReason(capture));
|
|
323
343
|
}
|
|
324
344
|
|
|
325
345
|
function getCompileState() {
|
|
@@ -341,7 +361,7 @@
|
|
|
341
361
|
}
|
|
342
362
|
|
|
343
363
|
function markSourceEdited(timestamp = Date.now()) {
|
|
344
|
-
state.lastKnownSourceEditTimestamp = timestamp;
|
|
364
|
+
state.lastKnownSourceEditTimestamp = Math.max(timestamp, state.lastKnownSourceEditTimestamp + 1);
|
|
345
365
|
}
|
|
346
366
|
|
|
347
367
|
function markSourceEditFromInput(event) {
|
|
@@ -359,56 +379,50 @@
|
|
|
359
379
|
markSourceEdited();
|
|
360
380
|
}
|
|
361
381
|
|
|
362
|
-
function clickOverleafCompileButton() {
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
382
|
+
function clickOverleafCompileButton(button) {
|
|
383
|
+
if (!button || isCompileBusy(button)) return false;
|
|
384
|
+
if (typeof button.click === 'function') {
|
|
385
|
+
button.click();
|
|
386
|
+
return true;
|
|
387
|
+
}
|
|
388
|
+
if (typeof button.dispatchEvent === 'function') {
|
|
389
|
+
const EventCtor = pageWindow.MouseEvent || pageWindow.Event;
|
|
390
|
+
if (EventCtor) {
|
|
391
|
+
button.dispatchEvent(new EventCtor('click', { bubbles: true, cancelable: true }));
|
|
370
392
|
return true;
|
|
371
393
|
}
|
|
372
|
-
if (typeof candidate.dispatchEvent === 'function') {
|
|
373
|
-
const EventCtor = pageWindow.MouseEvent || pageWindow.Event;
|
|
374
|
-
if (EventCtor) {
|
|
375
|
-
candidate.dispatchEvent(new EventCtor('click', { bubbles: true, cancelable: true }));
|
|
376
|
-
return true;
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
394
|
}
|
|
380
395
|
return false;
|
|
381
396
|
}
|
|
382
397
|
|
|
383
|
-
function
|
|
384
|
-
if (typeof pageDocument.querySelectorAll !== 'function')
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
'[aria-label*="compile" i]',
|
|
391
|
-
'[title*="compile" i]',
|
|
392
|
-
'[data-testid*="compile" i]'
|
|
393
|
-
];
|
|
394
|
-
const nodes = [];
|
|
395
|
-
for (const selector of selectors) {
|
|
396
|
-
try {
|
|
397
|
-
nodes.push(...Array.from(pageDocument.querySelectorAll(selector)));
|
|
398
|
-
} catch (_e) { /* ignore unsupported selectors in tests/older browsers */ }
|
|
399
|
-
}
|
|
400
|
-
return uniqueNodes(nodes).filter(isCompileButton);
|
|
398
|
+
function findOverleafCompileButton() {
|
|
399
|
+
if (typeof pageDocument.querySelectorAll !== 'function') return null;
|
|
400
|
+
const nodes = Array.from(pageDocument.querySelectorAll('button, [role="button"]')).filter(isCompileControl);
|
|
401
|
+
// Keep the main button selected while busy; never fall through to the
|
|
402
|
+
// enabled split toggle, menu entries or another label containing "compile".
|
|
403
|
+
return nodes.find(node => node.classList?.contains('compile-button'))
|
|
404
|
+
|| nodes.find(node => /^(?:recompile|compile|重新编译|编译)$/i.test(compileButtonLabel(node))) || null;
|
|
401
405
|
}
|
|
402
406
|
|
|
403
|
-
function
|
|
404
|
-
const
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
return
|
|
407
|
+
function isCompileControl(node) {
|
|
408
|
+
const extensionRoot = node.closest?.('#codex-overleaf-panel, [data-codex-overleaf-root], [id^="codex-"], [class^="codex-"], [class*=" codex-"]');
|
|
409
|
+
// The extension's mounted-state classes on html/body are not UI roots.
|
|
410
|
+
if (extensionRoot && !['HTML', 'BODY'].includes(extensionRoot.tagName)) return false;
|
|
411
|
+
if (node.closest?.('.compile-dropdown-toggle, #pdf-recompile-dropdown, .dropdown-menu, [role="menu"], [role="menuitem"], [role="menuitemcheckbox"], [role="menuitemradio"], [hidden], [inert], [aria-hidden="true"]')) return false;
|
|
412
|
+
const popup = node.getAttribute?.('aria-haspopup');
|
|
413
|
+
if ((popup && popup !== 'false') || node.getAttribute?.('data-toggle') === 'dropdown'
|
|
414
|
+
|| node.getAttribute?.('data-bs-toggle') === 'dropdown') return false;
|
|
415
|
+
return !/^(?:stop\b|停止编译|终止编译)/i.test(compileButtonLabel(node));
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
function compileButtonLabel(node) {
|
|
419
|
+
return [node?.textContent, node?.innerText, node?.getAttribute?.('aria-label'), node?.getAttribute?.('title')]
|
|
420
|
+
.map(value => String(value || '').replace(/\s+/g, ' ').trim()).find(Boolean) || '';
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
function isCompileBusy(button) {
|
|
424
|
+
return state.inFlightCompileRequests.size > 0 || Boolean(button && (isDisabled(button)
|
|
425
|
+
|| /^(?:compiling|正在编译|编译中)\s*[.。…]*$/i.test(compileButtonLabel(button))));
|
|
412
426
|
}
|
|
413
427
|
|
|
414
428
|
function isDisabled(node) {
|
|
@@ -417,16 +431,19 @@
|
|
|
417
431
|
|| node.getAttribute?.('aria-disabled') === 'true');
|
|
418
432
|
}
|
|
419
433
|
|
|
420
|
-
async function waitForCapturedCompile(timeoutMs) {
|
|
421
|
-
const startCompileAt = state.lastCompileAt;
|
|
434
|
+
async function waitForCapturedCompile(timeoutMs, sourceAtTrigger) {
|
|
422
435
|
const deadline = Date.now() + timeoutMs;
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
}
|
|
427
|
-
|
|
436
|
+
let clicked = false;
|
|
437
|
+
while (true) {
|
|
438
|
+
const capture = findEligibleCapture(sourceAtTrigger);
|
|
439
|
+
if (capture) return { ok: true, compile: capture.compile, clicked };
|
|
440
|
+
const remainingMs = deadline - Date.now();
|
|
441
|
+
if (remainingMs <= 0) break;
|
|
442
|
+
if (!clicked) clicked = clickOverleafCompileButton(findOverleafCompileButton());
|
|
443
|
+
await delay(Math.min(100, remainingMs));
|
|
428
444
|
}
|
|
429
|
-
return { ok: false, reason:
|
|
445
|
+
return { ok: false, clicked, reason: clicked
|
|
446
|
+
? 'Clicked Overleaf Recompile, but no compile result was captured.' : 'compile_capture_missing' };
|
|
430
447
|
}
|
|
431
448
|
|
|
432
449
|
return {
|
|
@@ -448,10 +465,6 @@
|
|
|
448
465
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
449
466
|
}
|
|
450
467
|
|
|
451
|
-
function uniqueNodes(nodes) {
|
|
452
|
-
return Array.from(new Set(nodes.filter(Boolean)));
|
|
453
|
-
}
|
|
454
|
-
|
|
455
468
|
return {
|
|
456
469
|
COMPILE_TEMPLATE_TTL_MS,
|
|
457
470
|
create,
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
const checkpointWrite = hasCheckpointCapability(deps);
|
|
17
17
|
const compileCapture = Boolean(deps.compileState?.capturedRequestTemplate);
|
|
18
18
|
const reviewingControl = Boolean(deps.findReviewingActivationControl?.());
|
|
19
|
+
const upgradeModal = Boolean(deps.findUpgradeModal?.());
|
|
19
20
|
|
|
20
21
|
if (!editorRead) {
|
|
21
22
|
warnings.push('没有识别到当前 Overleaf 编辑器,Codex 不能读取当前文件。');
|
|
@@ -47,7 +48,9 @@
|
|
|
47
48
|
trigger: compileCapture
|
|
48
49
|
},
|
|
49
50
|
reviewing: {
|
|
50
|
-
control: reviewingControl
|
|
51
|
+
control: reviewingControl,
|
|
52
|
+
upgradeModal,
|
|
53
|
+
confirmed: !upgradeModal && deps.reviewingConfirmed === true
|
|
51
54
|
},
|
|
52
55
|
warnings
|
|
53
56
|
};
|
|
@@ -68,8 +68,15 @@
|
|
|
68
68
|
return '';
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
function
|
|
71
|
+
function captureMatches(view, capture) {
|
|
72
|
+
return !capture || (view === capture.view && view?.state === capture.state && view.state.doc === capture.doc
|
|
73
|
+
&& view.state.doc.toString() === capture.text);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function replaceActiveEditorText(text, options = {}) {
|
|
72
77
|
const editorView = deps.getCodeMirrorEditorView?.();
|
|
78
|
+
if (!captureMatches(editorView, options.expectedCapture))
|
|
79
|
+
return { ok: false, code: 'document_identity_mismatch', changedDocument: false };
|
|
73
80
|
if (editorView) {
|
|
74
81
|
const from = 0;
|
|
75
82
|
const to = deps.getCodeMirrorDocLength(editorView);
|
|
@@ -120,13 +127,15 @@
|
|
|
120
127
|
};
|
|
121
128
|
}
|
|
122
129
|
|
|
123
|
-
function replaceActiveEditorPatches(patches, nextContent) {
|
|
130
|
+
function replaceActiveEditorPatches(patches, nextContent, options = {}) {
|
|
124
131
|
const normalized = deps.normalizeTextPatches(patches, readActiveEditorText().length);
|
|
125
132
|
if (!normalized.ok) {
|
|
126
133
|
return normalized;
|
|
127
134
|
}
|
|
128
135
|
|
|
129
136
|
const editorView = deps.getCodeMirrorEditorView?.();
|
|
137
|
+
if (!captureMatches(editorView, options.expectedCapture))
|
|
138
|
+
return { ok: false, code: 'document_identity_mismatch', changedDocument: false };
|
|
130
139
|
if (editorView) {
|
|
131
140
|
editorView.dispatch({
|
|
132
141
|
changes: normalized.patches.map(patch => ({
|
|
@@ -159,7 +168,7 @@
|
|
|
159
168
|
};
|
|
160
169
|
}
|
|
161
170
|
|
|
162
|
-
const result = replaceActiveEditorText(nextContent);
|
|
171
|
+
const result = replaceActiveEditorText(nextContent, options);
|
|
163
172
|
return result.ok
|
|
164
173
|
? {
|
|
165
174
|
...result,
|