codex-overleaf-link 2.3.4 → 2.3.6
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 +325 -211
- package/extension/bootstrap/manifest.template.json +1 -1
- package/extension/src/background.js +9 -0
- package/extension/src/backgroundUpdateCoordinator.js +88 -4
- package/extension/src/content/composerPanel.js +1 -1
- package/extension/src/content/contentRuntime.js +51 -35
- package/extension/src/content/diagnosticsController.js +13 -3
- package/extension/src/content/diagnosticsPanel.js +1 -1
- package/extension/src/content/generated/content.bundle.js +1233 -298
- package/extension/src/content/generated/content.bundle.meta.json +8 -3
- package/extension/src/content/globalPreferencesController.js +125 -0
- package/extension/src/content/legacyGlobalRegistry.js +4 -0
- package/extension/src/content/otWarmMirror.js +95 -65
- package/extension/src/content/otWarmMirrorController.js +102 -1
- package/extension/src/content/panelMaintenance.js +26 -15
- package/extension/src/content/panelRenderer.js +6 -1
- package/extension/src/content/projectSettingsCoordinator.js +1 -0
- package/extension/src/content/providerSettingsCoordinator.js +16 -0
- package/extension/src/content/providerSettingsDialog.js +24 -15
- package/extension/src/content/runFailureNotice.js +104 -0
- package/extension/src/content/runScrollLayout.js +124 -0
- package/extension/src/content/runTimelineView.js +36 -10
- package/extension/src/content/settingsPanel.js +32 -13
- package/extension/src/content/themeController.js +3 -0
- package/extension/src/content/updateNotice.js +24 -4
- package/extension/src/page/overleafRealtimeObserver.js +116 -24
- package/extension/src/shared/compatibility.js +1 -1
- package/extension/src/shared/globalPreferences.js +131 -0
- package/extension/src/shared/i18n.js +79 -39
- package/extension/src/shared/managedUpdateProjection.js +1 -1
- package/extension/src/shared/otText.js +0 -1
- package/extension/src/shared/sessionState.js +17 -21
- package/extension/src/shared/sessionTitle.js +120 -0
- package/extension/src/shared/updateRuntimeIdentity.js +32 -0
- package/extension/styles/panel.css +90 -16
- package/native-host/src/mirrorWorkspace.js +20 -1
- package/native-host/src/releaseProxy.js +177 -0
- package/native-host/src/releaseTransport.js +154 -0
- package/native-host/src/updateManager.js +40 -39
- package/package.json +1 -1
- package/scripts/install-managed.mjs +1 -1
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"entry": "extension/entries/content-entry.mjs",
|
|
4
4
|
"output": "extension/src/content/generated/content.bundle.js",
|
|
5
|
-
"sourceDigest": "
|
|
6
|
-
"outputDigest": "
|
|
7
|
-
"byteLength":
|
|
5
|
+
"sourceDigest": "15c3e103532c84dea57aeac33e02ec665fbf7518543e14daac2459c434b3e5d5",
|
|
6
|
+
"outputDigest": "5e1dba4bfcf4c595835312deabefa5ecdd2ee7c6d98f29d1b2bd9711ad5b195c",
|
|
7
|
+
"byteLength": 2375784,
|
|
8
8
|
"inputs": [
|
|
9
9
|
"extension/entries/content-entry.mjs",
|
|
10
10
|
"extension/src/content/activeTurnControl.js",
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
"extension/src/content/diagnosticsController.js",
|
|
18
18
|
"extension/src/content/diagnosticsPanel.js",
|
|
19
19
|
"extension/src/content/diffReviewPanel.js",
|
|
20
|
+
"extension/src/content/globalPreferencesController.js",
|
|
20
21
|
"extension/src/content/legacyGlobalRegistry.js",
|
|
21
22
|
"extension/src/content/localSkillsPanel.js",
|
|
22
23
|
"extension/src/content/markdownDomRenderer.js",
|
|
@@ -43,10 +44,12 @@
|
|
|
43
44
|
"extension/src/content/recentProjects.js",
|
|
44
45
|
"extension/src/content/reviewHunks.js",
|
|
45
46
|
"extension/src/content/runController.js",
|
|
47
|
+
"extension/src/content/runFailureNotice.js",
|
|
46
48
|
"extension/src/content/runGuidanceController.js",
|
|
47
49
|
"extension/src/content/runGuidanceView.js",
|
|
48
50
|
"extension/src/content/runQueueScheduler.js",
|
|
49
51
|
"extension/src/content/runResultActions.js",
|
|
52
|
+
"extension/src/content/runScrollLayout.js",
|
|
50
53
|
"extension/src/content/runSettlementPersistence.js",
|
|
51
54
|
"extension/src/content/runTimelineView.js",
|
|
52
55
|
"extension/src/content/scopedPersistenceCoordinator.js",
|
|
@@ -68,6 +71,7 @@
|
|
|
68
71
|
"extension/src/shared/compatibility.js",
|
|
69
72
|
"extension/src/shared/compileAdapter.js",
|
|
70
73
|
"extension/src/shared/failureReasons.js",
|
|
74
|
+
"extension/src/shared/globalPreferences.js",
|
|
71
75
|
"extension/src/shared/governanceRules.js",
|
|
72
76
|
"extension/src/shared/i18n.js",
|
|
73
77
|
"extension/src/shared/lineReferences.js",
|
|
@@ -88,6 +92,7 @@
|
|
|
88
92
|
"extension/src/shared/scopedPersistenceTransaction.js",
|
|
89
93
|
"extension/src/shared/sensitiveScan.js",
|
|
90
94
|
"extension/src/shared/sessionState.js",
|
|
95
|
+
"extension/src/shared/sessionTitle.js",
|
|
91
96
|
"extension/src/shared/settlementFacts.js",
|
|
92
97
|
"extension/src/shared/staleGuard.js",
|
|
93
98
|
"extension/src/shared/storageDb.js",
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
(function (root, factory) {
|
|
2
|
+
if (typeof module === 'object' && module.exports) module.exports = factory(require('../shared/globalPreferences'));
|
|
3
|
+
else root.CodexOverleafModuleRegistry.define('GlobalPreferencesController', ['GlobalPreferences'], factory);
|
|
4
|
+
})(typeof globalThis !== 'undefined' ? globalThis : this, function (Preferences) {
|
|
5
|
+
'use strict';
|
|
6
|
+
|
|
7
|
+
const CONTROLS = Object.freeze({ theme: '[data-theme-select]', locale: '[data-language-select]',
|
|
8
|
+
preloadProjectContext: '[data-preload-project-context]', loadCodexLocalSkills: '[data-load-codex-local-skills]',
|
|
9
|
+
loadCodexOverleafSkills: '[data-load-codex-overleaf-skills]' });
|
|
10
|
+
|
|
11
|
+
function create(options) {
|
|
12
|
+
const chromeApi = options.chromeApi;
|
|
13
|
+
const fallback = Preferences.pick(options.getState?.() || {});
|
|
14
|
+
let snapshot = null;
|
|
15
|
+
let loading = null;
|
|
16
|
+
let subscribed = false;
|
|
17
|
+
let disposed = false;
|
|
18
|
+
let lastError = null;
|
|
19
|
+
let tail = Promise.resolve();
|
|
20
|
+
const pending = [];
|
|
21
|
+
|
|
22
|
+
function view() {
|
|
23
|
+
return pending.reduce((values, operation) => Preferences.applyPatch(values, operation.patch), snapshot?.values || fallback);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function render(force = false) {
|
|
27
|
+
if (disposed) return;
|
|
28
|
+
const previous = options.getState?.() || {};
|
|
29
|
+
const values = view();
|
|
30
|
+
const changed = Preferences.changedKeys(Preferences.pick(previous), values);
|
|
31
|
+
options.setState?.({ ...previous, ...values });
|
|
32
|
+
const panel = options.getPanel?.();
|
|
33
|
+
if (!panel) return;
|
|
34
|
+
for (const [field, selector] of Object.entries(CONTROLS)) {
|
|
35
|
+
const control = panel.querySelector(selector);
|
|
36
|
+
if (!control) continue;
|
|
37
|
+
if (typeof values[field] === 'boolean') control.checked = values[field];
|
|
38
|
+
else if (control.value !== values[field]) control.value = values[field];
|
|
39
|
+
}
|
|
40
|
+
if (force || changed.includes('theme')) options.applyTheme?.(values.theme);
|
|
41
|
+
if (changed.includes('locale')) options.applyLocale?.();
|
|
42
|
+
if (force || changed.includes('preloadProjectContext')) options.onPreloadChange?.(values.preloadProjectContext);
|
|
43
|
+
if (changed.some(key => key === 'codexOverleafSkillEnabled' || key.startsWith('loadCodex'))) options.onSkillsChange?.();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function accept(value) {
|
|
47
|
+
const next = Preferences.validateSnapshot(value);
|
|
48
|
+
lastError = null;
|
|
49
|
+
if (!snapshot || next.revision > snapshot.revision) {
|
|
50
|
+
snapshot = next;
|
|
51
|
+
render();
|
|
52
|
+
}
|
|
53
|
+
return snapshot;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function request(type, patch) {
|
|
57
|
+
const response = await chromeApi.runtime.sendMessage({ type, ...(patch ? { patch } : {}) });
|
|
58
|
+
if (!response?.ok) throw new Error(response?.error?.message || 'Global preferences are unavailable.');
|
|
59
|
+
return response.result;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function onStorageChange(changes, area) {
|
|
63
|
+
if (disposed || area !== 'local' || !changes[Preferences.STORAGE_KEY]?.newValue) return;
|
|
64
|
+
try { accept(changes[Preferences.STORAGE_KEY].newValue); } catch (error) { reportError(error); }
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function reportError(error) {
|
|
68
|
+
lastError = error;
|
|
69
|
+
options.onError?.(error);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function initialize() {
|
|
73
|
+
if (!subscribed) {
|
|
74
|
+
chromeApi.storage.onChanged.addListener(onStorageChange);
|
|
75
|
+
subscribed = true;
|
|
76
|
+
}
|
|
77
|
+
if (snapshot) return snapshot;
|
|
78
|
+
if (!loading) {
|
|
79
|
+
loading = request(Preferences.READ_MESSAGE).then(accept).catch(error => {
|
|
80
|
+
reportError(error);
|
|
81
|
+
return null;
|
|
82
|
+
}).finally(() => { loading = null; });
|
|
83
|
+
}
|
|
84
|
+
return loading;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function update(input) {
|
|
88
|
+
const patch = Preferences.validatePatch(input);
|
|
89
|
+
if (!Preferences.changedKeys(view(), Preferences.applyPatch(view(), patch)).length) return pending.length ? tail : Promise.resolve(snapshot);
|
|
90
|
+
const operation = { patch };
|
|
91
|
+
pending.push(operation);
|
|
92
|
+
render();
|
|
93
|
+
const result = tail.catch(() => {}).then(() => request(Preferences.PATCH_MESSAGE, patch)).then(Preferences.validateSnapshot).then(value => {
|
|
94
|
+
accept(value);
|
|
95
|
+
pending.splice(pending.indexOf(operation), 1);
|
|
96
|
+
render();
|
|
97
|
+
return snapshot;
|
|
98
|
+
}, error => {
|
|
99
|
+
pending.splice(pending.indexOf(operation), 1);
|
|
100
|
+
render();
|
|
101
|
+
reportError(error);
|
|
102
|
+
throw error;
|
|
103
|
+
});
|
|
104
|
+
tail = result;
|
|
105
|
+
return result;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function handleInput(event) {
|
|
109
|
+
const field = Object.keys(CONTROLS).find(key => event?.target?.matches?.(CONTROLS[key]));
|
|
110
|
+
if (!field) return null;
|
|
111
|
+
return update({ [field]: ['theme', 'locale'].includes(field) ? event.target.value : Boolean(event.target.checked) });
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return Object.freeze({ initialize, update, handleInput,
|
|
115
|
+
overlay: state => snapshot || pending.length ? { ...state, ...view() } : state,
|
|
116
|
+
refreshView: () => { if (snapshot) render(true); if (lastError) options.onError?.(lastError); },
|
|
117
|
+
destroy() {
|
|
118
|
+
disposed = true;
|
|
119
|
+
if (subscribed) chromeApi.storage.onChanged.removeListener(onStorageChange);
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return Object.freeze({ create });
|
|
125
|
+
});
|
|
@@ -27,6 +27,8 @@
|
|
|
27
27
|
'DiffReviewPanel',
|
|
28
28
|
'FailureReasons',
|
|
29
29
|
'GovernanceRules',
|
|
30
|
+
'GlobalPreferences',
|
|
31
|
+
'GlobalPreferencesController',
|
|
30
32
|
'I18n',
|
|
31
33
|
'LineReferences',
|
|
32
34
|
'LocalSkillsPanel',
|
|
@@ -55,6 +57,8 @@
|
|
|
55
57
|
'RunInputQueue',
|
|
56
58
|
'RunQueueScheduler',
|
|
57
59
|
'RunResultActions',
|
|
60
|
+
'RunScrollLayout',
|
|
61
|
+
'RunFailureNotice',
|
|
58
62
|
'RunSettlementPersistence',
|
|
59
63
|
'RunTimelineView',
|
|
60
64
|
'ScopedPersistenceCoordinator',
|
|
@@ -50,6 +50,7 @@
|
|
|
50
50
|
} = deps;
|
|
51
51
|
|
|
52
52
|
let currentOtStatus = 'off';
|
|
53
|
+
let otTogglePending = false;
|
|
53
54
|
let otSyncRequestId = 0;
|
|
54
55
|
let otWarmMirrorProjectId = '';
|
|
55
56
|
let lastExperimentalOtProjectId = '';
|
|
@@ -61,7 +62,10 @@
|
|
|
61
62
|
patchQueue: [],
|
|
62
63
|
lastStatus: 'off',
|
|
63
64
|
lastPatchAt: 0,
|
|
64
|
-
lastErrorCode: ''
|
|
65
|
+
lastErrorCode: '',
|
|
66
|
+
failureActive: false,
|
|
67
|
+
lastFailure: null,
|
|
68
|
+
suspended: false
|
|
65
69
|
};
|
|
66
70
|
let mirrorPrefetchState = {
|
|
67
71
|
inFlight: null,
|
|
@@ -159,14 +163,14 @@
|
|
|
159
163
|
}
|
|
160
164
|
|
|
161
165
|
async function handleExperimentalOtToggleClick(event) {
|
|
162
|
-
//
|
|
163
|
-
//
|
|
164
|
-
// is the user's intended target. preventDefault then rolls the visual
|
|
165
|
-
// state back (synchronously, when dispatch completes), and the confirm
|
|
166
|
-
// flow applies the target only after approval.
|
|
166
|
+
// Capture the pre-activated target, then let cancelled click dispatch
|
|
167
|
+
// restore the old checkbox value before projecting the approved state.
|
|
167
168
|
const clicked = event?.currentTarget || event?.target;
|
|
168
169
|
const targetEnabled = clicked && typeof clicked.checked === 'boolean' ? clicked.checked : undefined;
|
|
170
|
+
const projectId = getCurrentProjectId();
|
|
169
171
|
event.preventDefault();
|
|
172
|
+
await new Promise(resolve => window.setTimeout(resolve, 0));
|
|
173
|
+
if (projectId !== getCurrentProjectId()) return;
|
|
170
174
|
await toggleExperimentalOtCheckbox(targetEnabled);
|
|
171
175
|
}
|
|
172
176
|
|
|
@@ -180,9 +184,12 @@
|
|
|
180
184
|
|
|
181
185
|
async function toggleExperimentalOtCheckbox(targetEnabled) {
|
|
182
186
|
const checkbox = getPanel()?.querySelector('[data-experimental-ot]');
|
|
183
|
-
if (!checkbox) {
|
|
187
|
+
if (!checkbox || otTogglePending) {
|
|
184
188
|
return;
|
|
185
189
|
}
|
|
190
|
+
const projectId = getCurrentProjectId();
|
|
191
|
+
otTogglePending = true;
|
|
192
|
+
try {
|
|
186
193
|
// Explicit target from the click interceptor wins; the keyboard/legacy
|
|
187
194
|
// path (no argument, un-flipped checkbox) falls back to inversion.
|
|
188
195
|
const nextEnabled = typeof targetEnabled === 'boolean' ? targetEnabled : !checkbox.checked;
|
|
@@ -200,11 +207,15 @@
|
|
|
200
207
|
} else {
|
|
201
208
|
closeDiagnosticsMenu();
|
|
202
209
|
}
|
|
210
|
+
if (projectId !== getCurrentProjectId() || checkbox !== getPanel()?.querySelector('[data-experimental-ot]')) return;
|
|
203
211
|
checkbox.checked = nextEnabled;
|
|
204
212
|
handleExperimentalOtToggleChange({ currentTarget: checkbox });
|
|
205
213
|
if (nextEnabled) {
|
|
206
214
|
showPluginToast(tr('experimentalOtEnabledToast'), { status: 'info' });
|
|
207
215
|
}
|
|
216
|
+
} finally {
|
|
217
|
+
otTogglePending = false;
|
|
218
|
+
}
|
|
208
219
|
}
|
|
209
220
|
|
|
210
221
|
function handleExperimentalOtToggleChange(event) {
|
|
@@ -213,6 +224,7 @@
|
|
|
213
224
|
return;
|
|
214
225
|
}
|
|
215
226
|
const projectId = getCurrentProjectId();
|
|
227
|
+
otWarmMirrorState.failureActive = false;
|
|
216
228
|
lastExperimentalOtProjectId = projectId;
|
|
217
229
|
setExperimentalOtEnabledForProject(projectId, checkbox.checked);
|
|
218
230
|
updateExperimentalOtToggleControl(checkbox.checked);
|
|
@@ -230,7 +242,7 @@
|
|
|
230
242
|
updateOtStatusDisplay(checkbox.checked ? (currentOtStatus === 'off' ? 'starting' : currentOtStatus) : 'off');
|
|
231
243
|
syncOtWarmMirrorController().catch(error => {
|
|
232
244
|
updateOtStatusDisplay('unavailable');
|
|
233
|
-
appendPlainLog(tx(`
|
|
245
|
+
appendPlainLog(tx(`OT warm mirror unavailable: ${error.message}`, `OT 预热镜像不可用:${error.message}`));
|
|
234
246
|
});
|
|
235
247
|
saveStateSoon();
|
|
236
248
|
}
|
|
@@ -252,6 +264,7 @@
|
|
|
252
264
|
otWarmMirrorProjectId = projectId;
|
|
253
265
|
ensureOtWarmMirrorStateProject(projectId);
|
|
254
266
|
if (enabled) {
|
|
267
|
+
otWarmMirrorState.suspended = false;
|
|
255
268
|
const pause = otWarmMirrorController?.shouldPauseOtWarmMirror?.({ running: Boolean(getCurrentRunView()) }) || { pause: false };
|
|
256
269
|
if (pause.pause) {
|
|
257
270
|
clearOtEventPolling({ clearPatchQueue: true });
|
|
@@ -263,9 +276,12 @@
|
|
|
263
276
|
} else {
|
|
264
277
|
clearOtEventPolling({ clearPatchQueue: true });
|
|
265
278
|
}
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
: await callPageBridge('stopOtObserver', {});
|
|
279
|
+
let response;
|
|
280
|
+
try {
|
|
281
|
+
response = enabled ? await callPageBridge('startOtObserver', { projectId }) : await callPageBridge('stopOtObserver', {});
|
|
282
|
+
} catch (error) {
|
|
283
|
+
response = { ok: false, error: otWarmMirrorController.normalizeFailureCode(error?.code || error?.message, 'ot_bridge_unavailable') };
|
|
284
|
+
}
|
|
269
285
|
if (!isCurrentOtSync(requestId, projectId) || getCurrentProjectId() !== projectId) {
|
|
270
286
|
if (enabled) {
|
|
271
287
|
await handleStaleOtStartResponse(projectId, requestId);
|
|
@@ -282,7 +298,7 @@
|
|
|
282
298
|
}
|
|
283
299
|
const status = readOtBridgeStatus(response);
|
|
284
300
|
if (!isSuccessfulOtBridgeResponse(response) || status === 'unavailable') {
|
|
285
|
-
handleFailedOtStart(projectId, requestId);
|
|
301
|
+
await handleFailedOtStart(projectId, requestId, response);
|
|
286
302
|
return response;
|
|
287
303
|
}
|
|
288
304
|
const pauseAfterStart = otWarmMirrorController?.shouldPauseOtWarmMirror?.({ running: Boolean(getCurrentRunView()) }) || { pause: false };
|
|
@@ -290,6 +306,7 @@
|
|
|
290
306
|
await pauseOtWarmMirror(pauseAfterStart.reason || 'paused');
|
|
291
307
|
return response;
|
|
292
308
|
}
|
|
309
|
+
otWarmMirrorState.failureActive = false;
|
|
293
310
|
updateOtStatusDisplay(status);
|
|
294
311
|
scheduleOtEventPolling(projectId, { immediate: true });
|
|
295
312
|
return response;
|
|
@@ -314,11 +331,13 @@
|
|
|
314
331
|
}
|
|
315
332
|
}
|
|
316
333
|
|
|
317
|
-
function handleFailedOtStart(projectId, requestId) {
|
|
334
|
+
async function handleFailedOtStart(projectId, requestId, response) {
|
|
318
335
|
if (!isCurrentOtSync(requestId, projectId) || getCurrentProjectId() !== projectId) {
|
|
319
336
|
return;
|
|
320
337
|
}
|
|
321
338
|
clearOtEventPolling({ clearPatchQueue: true });
|
|
339
|
+
recordOtFailure(readOtBridgeErrorCode(response, 'ot_start_failed'), 'start');
|
|
340
|
+
otWarmMirrorState.suspended = true;
|
|
322
341
|
setExperimentalOtEnabledForProject(projectId, false);
|
|
323
342
|
const experimentalOtCheckbox = getPanel()?.querySelector('[data-experimental-ot]');
|
|
324
343
|
if (experimentalOtCheckbox) {
|
|
@@ -326,13 +345,14 @@
|
|
|
326
345
|
}
|
|
327
346
|
updateOtStatusDisplay('unavailable');
|
|
328
347
|
saveStateSoon();
|
|
348
|
+
try { await callPageBridge('stopOtObserver', { projectId }); } catch (_error) { /* preserve the original failure */ }
|
|
329
349
|
}
|
|
330
350
|
|
|
331
351
|
function readOtBridgeStatus(response) {
|
|
332
352
|
if (!response || response.ok === false) {
|
|
333
353
|
return 'unavailable';
|
|
334
354
|
}
|
|
335
|
-
return response.
|
|
355
|
+
return response.state || response.status || response.result?.state || response.result?.status || 'unavailable';
|
|
336
356
|
}
|
|
337
357
|
|
|
338
358
|
function ensureOtWarmMirrorStateProject(projectId) {
|
|
@@ -343,6 +363,9 @@
|
|
|
343
363
|
otWarmMirrorState.projectId = projectId;
|
|
344
364
|
otWarmMirrorState.lastPatchAt = 0;
|
|
345
365
|
otWarmMirrorState.lastErrorCode = '';
|
|
366
|
+
otWarmMirrorState.failureActive = false;
|
|
367
|
+
otWarmMirrorState.lastFailure = null;
|
|
368
|
+
otWarmMirrorState.suspended = false;
|
|
346
369
|
otWarmMirrorState.lastStatus = currentOtStatus;
|
|
347
370
|
}
|
|
348
371
|
|
|
@@ -386,7 +409,8 @@
|
|
|
386
409
|
&& otWarmMirrorController?.buildPatchFilesRequest
|
|
387
410
|
&& isExperimentalOtEnabledForProject(projectId)
|
|
388
411
|
&& otWarmMirrorProjectId === projectId
|
|
389
|
-
&& getCurrentProjectId() === projectId
|
|
412
|
+
&& getCurrentProjectId() === projectId
|
|
413
|
+
&& !otWarmMirrorState.suspended);
|
|
390
414
|
}
|
|
391
415
|
|
|
392
416
|
async function pollOtEvents(projectId = otWarmMirrorState.projectId || getCurrentProjectId()) {
|
|
@@ -409,11 +433,12 @@
|
|
|
409
433
|
return { ok: false, skipped: true, reason: 'project_changed' };
|
|
410
434
|
}
|
|
411
435
|
if (!isSuccessfulOtBridgeResponse(statusResponse)) {
|
|
412
|
-
|
|
436
|
+
recordOtFailure(readOtBridgeErrorCode(statusResponse, 'get_ot_status_failed'), 'observe');
|
|
413
437
|
updateOtStatusDisplay('unavailable');
|
|
414
438
|
return statusResponse;
|
|
415
439
|
}
|
|
416
440
|
const status = readOtBridgeStatus(statusResponse);
|
|
441
|
+
if (['unavailable', 'inconsistent'].includes(status)) recordOtFailure(readOtBridgeErrorCode(statusResponse, 'ot_observer_unavailable'), 'observe');
|
|
417
442
|
if (status) {
|
|
418
443
|
updateOtStatusDisplay(status);
|
|
419
444
|
}
|
|
@@ -423,7 +448,7 @@
|
|
|
423
448
|
return { ok: false, skipped: true, reason: 'project_changed' };
|
|
424
449
|
}
|
|
425
450
|
if (!isSuccessfulOtBridgeResponse(drainResponse)) {
|
|
426
|
-
|
|
451
|
+
recordOtFailure(readOtBridgeErrorCode(drainResponse, 'drain_ot_events_failed'), 'observe');
|
|
427
452
|
updateOtStatusDisplay('unavailable');
|
|
428
453
|
return drainResponse;
|
|
429
454
|
}
|
|
@@ -433,7 +458,8 @@
|
|
|
433
458
|
}
|
|
434
459
|
return drainResponse;
|
|
435
460
|
} catch (error) {
|
|
436
|
-
|
|
461
|
+
if (!canPollOtWarmMirror(projectId)) return { ok: false, skipped: true, reason: 'project_changed' };
|
|
462
|
+
recordOtFailure(error?.code || error?.message || 'ot_poll_failed', 'observe');
|
|
437
463
|
updateOtStatusDisplay('unavailable');
|
|
438
464
|
return { ok: false, error };
|
|
439
465
|
} finally {
|
|
@@ -465,12 +491,28 @@
|
|
|
465
491
|
return typeof value === 'string' ? value : fallback;
|
|
466
492
|
}
|
|
467
493
|
|
|
494
|
+
function recordOtFailure(code, phase) {
|
|
495
|
+
code = otWarmMirrorController.normalizeFailureCode(code);
|
|
496
|
+
otWarmMirrorState.lastErrorCode = code;
|
|
497
|
+
otWarmMirrorState.failureActive = true;
|
|
498
|
+
otWarmMirrorState.lastFailure = { code, phase, at: Date.now() };
|
|
499
|
+
}
|
|
500
|
+
|
|
468
501
|
function queueOtPatchEvents(events, projectId = getCurrentProjectId()) {
|
|
469
502
|
if (!Array.isArray(events) || !events.length || !canPollOtWarmMirror(projectId)) {
|
|
470
503
|
return;
|
|
471
504
|
}
|
|
472
505
|
ensureOtWarmMirrorStateProject(projectId);
|
|
473
|
-
otWarmMirrorState.patchQueue
|
|
506
|
+
const queued = otWarmMirrorController.queuePatchEvents(otWarmMirrorState.patchQueue, events);
|
|
507
|
+
if (!queued.ok) {
|
|
508
|
+
recordOtFailure(queued.reason, 'queue');
|
|
509
|
+
clearOtEventPolling({ clearPatchQueue: true });
|
|
510
|
+
otWarmMirrorState.suspended = true;
|
|
511
|
+
updateOtStatusDisplay('inconsistent');
|
|
512
|
+
void callPageBridge('stopOtObserver', { projectId }).catch(() => {});
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
otWarmMirrorState.patchQueue = queued.queue;
|
|
474
516
|
scheduleOtPatchFlush(projectId);
|
|
475
517
|
}
|
|
476
518
|
|
|
@@ -510,11 +552,18 @@
|
|
|
510
552
|
return { ok: false, skipped: true, reason: pause.reason || 'paused' };
|
|
511
553
|
}
|
|
512
554
|
|
|
513
|
-
|
|
514
|
-
const batch = otWarmMirrorState.patchQueue.splice(0, maxBatch);
|
|
555
|
+
let batch = otWarmMirrorController.takePatchBatch(otWarmMirrorState.patchQueue);
|
|
515
556
|
if (!batch.length) {
|
|
516
557
|
return { ok: true, skipped: true, reason: 'empty_batch' };
|
|
517
558
|
}
|
|
559
|
+
const fresh = otWarmMirrorController.queuePatchEvents([], batch);
|
|
560
|
+
if (!fresh.ok) {
|
|
561
|
+
recordOtFailure(fresh.reason, 'queue');
|
|
562
|
+
clearOtEventPolling({ clearPatchQueue: true });
|
|
563
|
+
updateOtStatusDisplay('inconsistent');
|
|
564
|
+
return { ok: false, skipped: true, reason: fresh.reason };
|
|
565
|
+
}
|
|
566
|
+
batch = fresh.queue;
|
|
518
567
|
|
|
519
568
|
otWarmMirrorState.flushing = true;
|
|
520
569
|
try {
|
|
@@ -530,46 +579,37 @@
|
|
|
530
579
|
}
|
|
531
580
|
|
|
532
581
|
const response = await sendBackgroundNative(request);
|
|
582
|
+
if (!canPollOtWarmMirror(projectId)) return { ok: false, skipped: true, reason: 'project_changed' };
|
|
533
583
|
if (!response?.ok) {
|
|
534
|
-
|
|
584
|
+
recordOtFailure(response?.error?.code || 'mirror_patch_failed', 'flush');
|
|
535
585
|
updateOtStatusDisplay('inconsistent');
|
|
536
586
|
return response;
|
|
537
587
|
}
|
|
538
588
|
|
|
539
|
-
const
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
? Number(result.skippedCount)
|
|
543
|
-
: skippedFiles.length;
|
|
544
|
-
if (skippedFiles.length || skippedCount > 0) {
|
|
545
|
-
otWarmMirrorState.lastErrorCode = 'mirror_patch_skipped';
|
|
546
|
-
updateOtStatusDisplay('inconsistent');
|
|
547
|
-
return response;
|
|
548
|
-
}
|
|
549
|
-
const appliedFiles = Array.isArray(result?.appliedFiles) ? result.appliedFiles : null;
|
|
550
|
-
const appliedCount = Number.isFinite(Number(result?.appliedCount))
|
|
551
|
-
? Number(result.appliedCount)
|
|
552
|
-
: NaN;
|
|
553
|
-
if (!appliedFiles || !Number.isFinite(appliedCount) || appliedCount <= 0 || appliedFiles.length !== appliedCount) {
|
|
554
|
-
otWarmMirrorState.lastErrorCode = 'mirror_patch_invalid_result';
|
|
589
|
+
const receipt = otWarmMirrorController.validatePatchReceipt(batch, response.result);
|
|
590
|
+
if (!receipt.ok) {
|
|
591
|
+
recordOtFailure(receipt.reason, 'flush');
|
|
555
592
|
updateOtStatusDisplay('inconsistent');
|
|
556
593
|
return response;
|
|
557
594
|
}
|
|
558
595
|
|
|
559
596
|
otWarmMirrorState.lastPatchAt = Date.now();
|
|
560
597
|
otWarmMirrorState.lastErrorCode = '';
|
|
561
|
-
|
|
598
|
+
otWarmMirrorState.failureActive = false;
|
|
599
|
+
if (canPollOtWarmMirror(projectId) && !getCurrentRunView()) {
|
|
562
600
|
updateOtStatusDisplay('observing');
|
|
563
601
|
}
|
|
564
602
|
return response;
|
|
565
603
|
} catch (error) {
|
|
566
|
-
|
|
604
|
+
if (!canPollOtWarmMirror(projectId)) return { ok: false, skipped: true, reason: 'project_changed' };
|
|
605
|
+
recordOtFailure(error?.code || 'mirror_patch_failed', 'flush');
|
|
567
606
|
updateOtStatusDisplay('inconsistent');
|
|
568
607
|
return { ok: false, error };
|
|
569
608
|
} finally {
|
|
570
609
|
otWarmMirrorState.flushing = false;
|
|
571
|
-
|
|
572
|
-
|
|
610
|
+
const nextProjectId = otWarmMirrorState.projectId;
|
|
611
|
+
if (otWarmMirrorState.patchQueue.length && canPollOtWarmMirror(nextProjectId) && !getCurrentRunView()) {
|
|
612
|
+
scheduleOtPatchFlush(nextProjectId, { immediate: true });
|
|
573
613
|
}
|
|
574
614
|
}
|
|
575
615
|
}
|
|
@@ -612,12 +652,13 @@
|
|
|
612
652
|
}
|
|
613
653
|
|
|
614
654
|
function updateOtStatusDisplay(status = currentOtStatus) {
|
|
615
|
-
const normalized = normalizeOtStatus(status);
|
|
655
|
+
const normalized = otWarmMirrorState.failureActive && status === 'observing' ? 'inconsistent' : normalizeOtStatus(status);
|
|
616
656
|
currentOtStatus = normalized;
|
|
617
657
|
otWarmMirrorState.lastStatus = normalized;
|
|
618
658
|
const statusElement = getPanel()?.querySelector('[data-ot-status]');
|
|
619
659
|
if (statusElement) {
|
|
620
|
-
const label = formatOtStatusLabel(normalized)
|
|
660
|
+
const label = formatOtStatusLabel(normalized) + (otWarmMirrorState.failureActive
|
|
661
|
+
? ': ' + tr(otWarmMirrorController.getFailureMessageKey(otWarmMirrorState.lastErrorCode)) : '');
|
|
621
662
|
statusElement.textContent = formatOtToggleStatusText(label);
|
|
622
663
|
statusElement.dataset.otStatus = normalized;
|
|
623
664
|
statusElement.title = formatOtToggleStatusText(label);
|
|
@@ -781,38 +822,24 @@
|
|
|
781
822
|
}
|
|
782
823
|
|
|
783
824
|
async function resolveWarmRunStart(taskContext = {}) {
|
|
825
|
+
const projectId = getCurrentProjectId();
|
|
784
826
|
const focusFiles = taskContext.focusFiles || [];
|
|
785
827
|
const mode = taskContext.mode || getState()?.mode;
|
|
786
828
|
const mirrorStatus = await getMirrorFreshness();
|
|
829
|
+
if (getCurrentProjectId() !== projectId) return { useExistingMirror: false, reason: 'project_changed' };
|
|
787
830
|
const otWarmStart = otWarmMirrorController.canUseOtWarmStart({
|
|
788
|
-
enabled: isExperimentalOtEnabled(),
|
|
831
|
+
enabled: isExperimentalOtEnabled() && !otWarmMirrorState.failureActive,
|
|
789
832
|
focusFiles,
|
|
790
833
|
mirrorStatus
|
|
791
834
|
});
|
|
792
|
-
if (otWarmStart.ok) {
|
|
793
|
-
return {
|
|
794
|
-
useExistingMirror: true,
|
|
795
|
-
warmStart: true,
|
|
796
|
-
otWarmStart: true,
|
|
797
|
-
reason: 'ot_focus_fresh',
|
|
798
|
-
mirrorStatus,
|
|
799
|
-
focusFiles: otWarmStart.focusFiles,
|
|
800
|
-
project: {
|
|
801
|
-
capabilities: {
|
|
802
|
-
fullProjectSnapshot: false,
|
|
803
|
-
method: 'ot-warm-mirror'
|
|
804
|
-
},
|
|
805
|
-
files: []
|
|
806
|
-
}
|
|
807
|
-
};
|
|
808
|
-
}
|
|
809
|
-
if (!mirrorStatus?.exists || !isMirrorReusable(mirrorStatus)) {
|
|
835
|
+
if (!mirrorStatus?.exists || (!otWarmStart.ok && !isMirrorReusable(mirrorStatus))) {
|
|
810
836
|
return { useExistingMirror: false, reason: 'mirror_not_fresh', mirrorStatus };
|
|
811
837
|
}
|
|
812
838
|
|
|
813
839
|
let overlayProject = null;
|
|
814
840
|
try {
|
|
815
841
|
overlayProject = await callPageBridge('getProjectSnapshot', {
|
|
842
|
+
projectId,
|
|
816
843
|
force: true,
|
|
817
844
|
maxAgeMs: 0,
|
|
818
845
|
preferLightweight: true,
|
|
@@ -826,6 +853,7 @@
|
|
|
826
853
|
} catch (error) {
|
|
827
854
|
return { useExistingMirror: false, reason: 'overlay_probe_failed', mirrorStatus };
|
|
828
855
|
}
|
|
856
|
+
if (getCurrentProjectId() !== projectId) return { useExistingMirror: false, reason: 'project_changed' };
|
|
829
857
|
const fileOverlays = buildSnapshotFileOverlays(overlayProject, focusFiles);
|
|
830
858
|
if (!fileOverlays.length) {
|
|
831
859
|
return { useExistingMirror: false, reason: 'missing_current_overlay', mirrorStatus };
|
|
@@ -840,7 +868,9 @@
|
|
|
840
868
|
return {
|
|
841
869
|
useExistingMirror: true,
|
|
842
870
|
warmStart: true,
|
|
843
|
-
|
|
871
|
+
otWarmStart: otWarmStart.ok,
|
|
872
|
+
focusFiles,
|
|
873
|
+
reason: otWarmStart.ok ? 'ot_focus_fresh' : mode === 'ask' ? 'warm_whole_project_ask' : 'warm_whole_project',
|
|
844
874
|
mirrorStatus,
|
|
845
875
|
fileOverlays,
|
|
846
876
|
project: overlayProject
|
|
@@ -928,7 +958,7 @@
|
|
|
928
958
|
return syncOtWarmMirrorController();
|
|
929
959
|
}).catch(error => {
|
|
930
960
|
updateOtStatusDisplay('unavailable');
|
|
931
|
-
appendPlainLog(tx(`
|
|
961
|
+
appendPlainLog(tx(`OT warm mirror unavailable: ${error.message}`, `OT 预热镜像不可用:${error.message}`));
|
|
932
962
|
});
|
|
933
963
|
}
|
|
934
964
|
|