codex-overleaf-link 2.1.5 → 2.2.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 +27 -23
- package/extension/bootstrap/background.js +14 -1
- package/extension/bootstrap/manifest.template.json +1 -1
- package/extension/runtime-manifest.json +10 -0
- package/extension/src/background.js +507 -8
- package/extension/src/backgroundUpdateCoordinator.js +284 -19
- package/extension/src/content/activeTurnControl.js +179 -0
- package/extension/src/content/composerPanel.js +14 -5
- package/extension/src/content/contentRuntime.js +330 -309
- package/extension/src/content/diagnosticsPanel.js +132 -1
- package/extension/src/content/markdownDomRenderer.js +451 -0
- package/extension/src/content/markdownText.js +86 -7
- package/extension/src/content/mathText.js +49 -4
- package/extension/src/content/modelPicker.js +14 -5
- package/extension/src/content/nativeChannel.js +7 -1
- package/extension/src/content/panelRenderer.js +112 -12
- package/extension/src/content/pendingInputView.js +106 -0
- package/extension/src/content/projectProviderSelection.js +68 -0
- package/extension/src/content/projectSessionCleanup.js +4 -0
- package/extension/src/content/providerSettingsCoordinator.js +170 -22
- package/extension/src/content/providerSettingsDialog.js +2 -2
- package/extension/src/content/recentProjects.js +7 -2
- package/extension/src/content/runGuidanceController.js +70 -0
- package/extension/src/content/runGuidanceView.js +48 -0
- package/extension/src/content/runQueueScheduler.js +260 -0
- package/extension/src/content/runTimelineView.js +33 -80
- package/extension/src/content/sessionManager.js +2 -2
- package/extension/src/content/sessionPersistence.js +62 -0
- package/extension/src/content/settingsPanel.js +1 -1
- package/extension/src/content/updateNotice.js +80 -1
- package/extension/src/page/writebackRouter.js +3 -9
- package/extension/src/shared/compatibility.js +2 -1
- package/extension/src/shared/i18n.js +38 -0
- package/extension/src/shared/providerProfiles.js +3 -2
- package/extension/src/shared/runInputQueue.js +253 -0
- package/extension/src/shared/sessionState.js +45 -22
- package/extension/src/shared/storageDb.js +39 -44
- package/extension/src/shared/storageMigration.js +123 -3
- package/extension/src/shared/storageRunActions.js +34 -1
- package/extension/src/shared/updateConsent.js +15 -4
- package/extension/styles/panel.css +319 -20
- package/extension/vendor/katex/katex.min.css +1 -1
- package/extension/vendor/markdown-it/LICENSE +22 -0
- package/extension/vendor/markdown-it/SOURCE.json +7 -0
- package/extension/vendor/markdown-it/VERSION +1 -0
- package/extension/vendor/markdown-it/markdown-it.min.js +3 -0
- package/native-host/src/anthropicBridgeRequest.js +3 -3
- package/native-host/src/anthropicBridgeResponse.js +58 -17
- package/native-host/src/anthropicMessagesBridge.js +156 -23
- package/native-host/src/chatBridgeRequest.js +1 -1
- package/native-host/src/chatBridgeResponse.js +12 -8
- package/native-host/src/chatCompletionsBridge.js +22 -40
- package/native-host/src/codexPromptAssembly.js +3 -1
- package/native-host/src/codexProviderLaunch.js +6 -3
- package/native-host/src/codexSessionRunner.js +65 -65
- package/native-host/src/codexSessionTiming.js +45 -0
- package/native-host/src/index.js +58 -3
- package/native-host/src/managedInstall.js +45 -7
- package/native-host/src/providerProfile.js +2 -2
- package/native-host/src/providerStreamLifecycle.js +100 -0
- package/native-host/src/responsesPassthroughBridge.js +636 -0
- package/native-host/src/taskRunnerRuntime.js +81 -2
- package/native-host/src/updateArchive.js +4 -3
- package/native-host/src/updateManager.js +306 -19
- package/native-host/src/updateTrust.js +1 -1
- package/package.json +3 -1
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
'mirror.status',
|
|
37
37
|
'codex.models',
|
|
38
38
|
'codex.run',
|
|
39
|
+
'codex.steer',
|
|
39
40
|
'codex.providers.list',
|
|
40
41
|
'codex.providers.test',
|
|
41
42
|
'codex.providers.test.cancel',
|
|
@@ -113,6 +114,10 @@
|
|
|
113
114
|
const ContextTray = window.CodexOverleafContextTray;
|
|
114
115
|
const LocalSkillsPanel = window.CodexOverleafLocalSkillsPanel;
|
|
115
116
|
const runController = window.CodexOverleafRunController;
|
|
117
|
+
const RunInputQueue = window.CodexOverleafRunInputQueue;
|
|
118
|
+
const RunQueueScheduler = window.CodexOverleafRunQueueScheduler;
|
|
119
|
+
const ActiveTurnControl = window.CodexOverleafActiveTurnControl;
|
|
120
|
+
const PendingInputView = window.CodexOverleafPendingInputView;
|
|
116
121
|
const mirrorHealth = window.CodexOverleafMirrorHealth;
|
|
117
122
|
const otWarmMirrorController = window.CodexOverleafOtWarmMirrorController;
|
|
118
123
|
const PanelRenderer = window.CodexOverleafPanelRenderer;
|
|
@@ -172,7 +177,8 @@
|
|
|
172
177
|
tx,
|
|
173
178
|
callPageBridge,
|
|
174
179
|
getCurrentProjectReferenceFiles,
|
|
175
|
-
showPluginToast
|
|
180
|
+
showPluginToast,
|
|
181
|
+
recordRenderingDiagnostic: detail => appendTechnicalEvent({ type: 'rendering.diagnostic', detail })
|
|
176
182
|
});
|
|
177
183
|
const {
|
|
178
184
|
renderMarkdownInlineText,
|
|
@@ -597,6 +603,14 @@
|
|
|
597
603
|
updateModelDisplay
|
|
598
604
|
} = modelPicker;
|
|
599
605
|
|
|
606
|
+
const projectProviderSelection = window.CodexOverleafProjectProviderSelection.create({
|
|
607
|
+
tx, tr, getRunningSessionIds, showToast: showPluginToast, showConfirm: showPluginConfirm,
|
|
608
|
+
getState: () => state, setState: next => { state = next; }, getPanel: () => panel,
|
|
609
|
+
readSelectedModel: readSelectedModelInput, readSelectedSpeed: readSelectedSpeedInput,
|
|
610
|
+
getRunSelection: providerId => providerSettingsCoordinator.getRunSelection(providerId),
|
|
611
|
+
normalizeState: normalizePanelState, applyStateToPanel
|
|
612
|
+
});
|
|
613
|
+
|
|
600
614
|
const providerSettingsCoordinator = window.CodexOverleafProviderSettingsCoordinator.create({
|
|
601
615
|
tx,
|
|
602
616
|
sendBackgroundNative,
|
|
@@ -604,7 +618,8 @@
|
|
|
604
618
|
window,
|
|
605
619
|
getSettingsPanelInstance: () => settingsPanelInstance,
|
|
606
620
|
getSelectedModel: () => state?.model || '', getSelectedProviderId: () => state?.providerId || 'builtin',
|
|
607
|
-
setSelectedProviderId:
|
|
621
|
+
setSelectedProviderId: projectProviderSelection.commit,
|
|
622
|
+
confirmProviderSwitch: projectProviderSelection.confirmSwitch,
|
|
608
623
|
refreshModelOptions: loadModelOptions, persistInputs: persistPanelInputs
|
|
609
624
|
});
|
|
610
625
|
// v1.8.1 D3: the dashboard was a one-shot render — another tab finishing a
|
|
@@ -767,6 +782,9 @@
|
|
|
767
782
|
let state = null;
|
|
768
783
|
let storageKey = LEGACY_STORAGE_KEY;
|
|
769
784
|
let currentRunView = null;
|
|
785
|
+
let runQueueScheduler = null;
|
|
786
|
+
let runInputCoordinator = null;
|
|
787
|
+
const activeTurnControl = ActiveTurnControl.create({ chrome });
|
|
770
788
|
let saveStateTimer = null;
|
|
771
789
|
// Two-phase saveState scheduling. `saveStateInFlight` flips to true while
|
|
772
790
|
// an async saveState() is actually writing; `saveStateRunAfterFlight` is
|
|
@@ -787,7 +805,7 @@
|
|
|
787
805
|
let renderedSlashCommands = new Map();
|
|
788
806
|
let runCancellationRequested = false;
|
|
789
807
|
let runCancellationController = null;
|
|
790
|
-
let
|
|
808
|
+
let pluginConfirmController = null;
|
|
791
809
|
const updateIdleClient = root.CodexOverleafUpdateIdle?.create({
|
|
792
810
|
document,
|
|
793
811
|
getBusyState: () => ({
|
|
@@ -795,7 +813,7 @@
|
|
|
795
813
|
cancelling: runCancellationRequested,
|
|
796
814
|
storage: saveStateInFlight || Boolean(saveStateTimer),
|
|
797
815
|
reviewAction: trackedChangeInFlight.size > 0,
|
|
798
|
-
dialog: Boolean(
|
|
816
|
+
dialog: Boolean(pluginConfirmController?.isOpen())
|
|
799
817
|
}),
|
|
800
818
|
checkSaved: () => callPageBridge('waitForSaveState', { deadlineMs: 1500, pollIntervalMs: 100, requirePositiveSignal: true, allowQuietEditorFallback: true, quietFallbackMs: 1000 }),
|
|
801
819
|
onApplyingChange: applying => { if (panel) panel.dataset.updating = applying ? 'true' : 'false'; },
|
|
@@ -843,7 +861,7 @@
|
|
|
843
861
|
return;
|
|
844
862
|
}
|
|
845
863
|
if (nativeChannel.shouldHandleNativeEvent(message)) {
|
|
846
|
-
appendNativeEvent(message.event);
|
|
864
|
+
appendNativeEvent(message.event, message.journalSeq);
|
|
847
865
|
}
|
|
848
866
|
});
|
|
849
867
|
|
|
@@ -862,6 +880,15 @@
|
|
|
862
880
|
async function init() {
|
|
863
881
|
storageKey = getProjectStorageKey(LEGACY_STORAGE_KEY, window.location.href);
|
|
864
882
|
state = normalizePanelState(await loadStoredState(), { restoreRunningRuns: true });
|
|
883
|
+
initializeRunQueueScheduler();
|
|
884
|
+
recoverInterruptedRunJournals()
|
|
885
|
+
.then(() => applyStateToPanel())
|
|
886
|
+
.catch(() => {});
|
|
887
|
+
setTimeout(() => {
|
|
888
|
+
recoverInterruptedRunJournals()
|
|
889
|
+
.then(() => applyStateToPanel())
|
|
890
|
+
.catch(() => {});
|
|
891
|
+
}, 900);
|
|
865
892
|
ensurePanelOpen();
|
|
866
893
|
root.CodexOverleafUpdateNotice?.mount?.(panel?.panelEl || panel, {
|
|
867
894
|
getLocale: () => state?.locale || 'en'
|
|
@@ -1338,7 +1365,8 @@
|
|
|
1338
1365
|
setElementTitleAndAria('[data-context-refresh]', tr('refreshFileList'), tr('refreshFileList'));
|
|
1339
1366
|
setElementTitleAndAria('[data-reasoning]', tr('reasoningLabel'), tr('reasoningLabel'));
|
|
1340
1367
|
setElementTitleAndAria('[data-speed]', tr('speedLabel'), tr('speedLabel'));
|
|
1341
|
-
setElementTitleAndAria('[data-run]', currentRunView ? tr('
|
|
1368
|
+
setElementTitleAndAria('[data-run]', currentRunView ? tr('queueNextInput') : tr('send'), currentRunView ? tr('queueNextInput') : tr('send'));
|
|
1369
|
+
setElementTitleAndAria('[data-stop-run]', tr('cancelRun'), tr('cancelRun'));
|
|
1342
1370
|
|
|
1343
1371
|
const actions = panel.querySelector('.codex-vscode-head-actions');
|
|
1344
1372
|
if (actions) {
|
|
@@ -1422,27 +1450,15 @@
|
|
|
1422
1450
|
return '';
|
|
1423
1451
|
}
|
|
1424
1452
|
|
|
1425
|
-
function installDiagnosticsDismiss() {
|
|
1426
|
-
// DiagnosticsPanel installs its outside-click dismiss listener during create().
|
|
1427
|
-
}
|
|
1453
|
+
function installDiagnosticsDismiss() {}
|
|
1428
1454
|
|
|
1429
|
-
function closeDiagnosticsResult() {
|
|
1430
|
-
DiagnosticsPanel.closeResult(diagnosticsPanelInstance);
|
|
1431
|
-
}
|
|
1455
|
+
function closeDiagnosticsResult() { DiagnosticsPanel.closeResult(diagnosticsPanelInstance); }
|
|
1432
1456
|
|
|
1433
|
-
function showDiagnosticsLoading(title, subtitle = tr('diagnosticsLoading')) {
|
|
1434
|
-
DiagnosticsPanel.showLoading(diagnosticsPanelInstance, title, subtitle);
|
|
1435
|
-
}
|
|
1457
|
+
function showDiagnosticsLoading(title, subtitle = tr('diagnosticsLoading')) { DiagnosticsPanel.showLoading(diagnosticsPanelInstance, title, subtitle); }
|
|
1436
1458
|
|
|
1437
|
-
function showDiagnosticsResult(result = {}) {
|
|
1438
|
-
DiagnosticsPanel.showResult(diagnosticsPanelInstance, result);
|
|
1439
|
-
}
|
|
1459
|
+
function showDiagnosticsResult(result = {}) { DiagnosticsPanel.showResult(diagnosticsPanelInstance, result); }
|
|
1440
1460
|
|
|
1441
|
-
|
|
1442
|
-
// diagnostics trigger's status dot.
|
|
1443
|
-
function setDiagnosticsHealth(health) {
|
|
1444
|
-
DiagnosticsPanel.updateStatus(diagnosticsPanelInstance, { health });
|
|
1445
|
-
}
|
|
1461
|
+
function setDiagnosticsHealth(health) { DiagnosticsPanel.updateStatus(diagnosticsPanelInstance, { health }); }
|
|
1446
1462
|
|
|
1447
1463
|
function installContextDismiss() {
|
|
1448
1464
|
return contextTrayController.installContextDismiss();
|
|
@@ -1646,7 +1662,7 @@
|
|
|
1646
1662
|
return contextTrayController.resetContextProject();
|
|
1647
1663
|
}
|
|
1648
1664
|
|
|
1649
|
-
async function runTask() {
|
|
1665
|
+
async function runTask(options = {}) {
|
|
1650
1666
|
// Barrier on a still-flying background mirror refresh from the previous
|
|
1651
1667
|
// turn (v1.7.5) BEFORE startRunView creates the new run view: the mirror's
|
|
1652
1668
|
// wrap-up events must land in the run that produced them, not the new
|
|
@@ -1665,7 +1681,8 @@
|
|
|
1665
1681
|
const submittedAttachments = getComposerAttachmentsForRun();
|
|
1666
1682
|
const submittedSkillInvocation = getComposerSkillInvocationForRun();
|
|
1667
1683
|
|
|
1668
|
-
const
|
|
1684
|
+
const queuedInput = options.queuedInput || null;
|
|
1685
|
+
const task = String(queuedInput?.text || state.task || '').trim();
|
|
1669
1686
|
if (!task) {
|
|
1670
1687
|
appendLog(tx('Enter a task first.', '请先输入任务。'));
|
|
1671
1688
|
return;
|
|
@@ -1681,9 +1698,13 @@
|
|
|
1681
1698
|
reasoningEffort: state.reasoningEffort,
|
|
1682
1699
|
speedTier: state.speedTier,
|
|
1683
1700
|
attachments: submittedAttachments,
|
|
1684
|
-
skillInvocation: submittedSkillInvocation
|
|
1701
|
+
skillInvocation: submittedSkillInvocation,
|
|
1702
|
+
queueItemId: queuedInput?.id || ''
|
|
1685
1703
|
});
|
|
1686
1704
|
const runSessionId = currentRunView.sessionId;
|
|
1705
|
+
if (queuedInput?.id) {
|
|
1706
|
+
await runQueueScheduler?.markExecuting(runSessionId, queuedInput.id, currentRunView.recordId);
|
|
1707
|
+
}
|
|
1687
1708
|
let runAuditDraft = null;
|
|
1688
1709
|
try {
|
|
1689
1710
|
runAuditDraft = await awaitRunStep(createAuditDraftForRun({
|
|
@@ -1693,10 +1714,11 @@
|
|
|
1693
1714
|
focusFiles: getActiveFocusFiles()
|
|
1694
1715
|
}));
|
|
1695
1716
|
announceCrossTabRunStart();
|
|
1696
|
-
//
|
|
1697
|
-
//
|
|
1698
|
-
//
|
|
1699
|
-
clearTaskComposer({ keepAttachments:
|
|
1717
|
+
// Binary attachments belong to this submitted turn. The run record has
|
|
1718
|
+
// already captured their snapshots, so clear them from the composer
|
|
1719
|
+
// together with the submitted text. Persistent @context remains intact.
|
|
1720
|
+
clearTaskComposer({ keepAttachments: false });
|
|
1721
|
+
syncComposerSendAvailability(true);
|
|
1700
1722
|
appendRunEvent({
|
|
1701
1723
|
title: submittedSkillInvocation?.id === 'skill-installer'
|
|
1702
1724
|
? tx('I will use the Codex skill installer for this request.', '我会用 Codex skill installer 处理这个请求。')
|
|
@@ -1909,9 +1931,7 @@
|
|
|
1909
1931
|
await awaitRunStep(settleMirrorPrefetchBeforeRun());
|
|
1910
1932
|
await awaitRunStep(providerSettingsCoordinator.ensureLoaded());
|
|
1911
1933
|
appendRunEvent({ title: tx('Local Codex session is starting.', '本地 Codex session 开始运行。'), status: 'running' });
|
|
1912
|
-
let response = await awaitRunStep(
|
|
1913
|
-
method: 'codex.run',
|
|
1914
|
-
params: buildCodexRunParams({
|
|
1934
|
+
let response = await awaitRunStep(sendTrackedCodexRun(buildCodexRunParams({
|
|
1915
1935
|
task,
|
|
1916
1936
|
project,
|
|
1917
1937
|
useExistingMirror,
|
|
@@ -1926,8 +1946,7 @@
|
|
|
1926
1946
|
attachments: submittedAttachments,
|
|
1927
1947
|
skillInvocation: submittedSkillInvocation,
|
|
1928
1948
|
submittedMode
|
|
1929
|
-
})
|
|
1930
|
-
}));
|
|
1949
|
+
})));
|
|
1931
1950
|
|
|
1932
1951
|
// Handle mirror_stale error by retrying with full sync
|
|
1933
1952
|
if (!response.ok && response.error?.code === 'mirror_stale' && useExistingMirror) {
|
|
@@ -1945,9 +1964,7 @@
|
|
|
1945
1964
|
fileOverlays = null;
|
|
1946
1965
|
otWarmStart = false;
|
|
1947
1966
|
restrictToFocusFiles = staleRetry.restrictToFocusFiles;
|
|
1948
|
-
response = await awaitRunStep(
|
|
1949
|
-
method: 'codex.run',
|
|
1950
|
-
params: buildCodexRunParams({
|
|
1967
|
+
response = await awaitRunStep(sendTrackedCodexRun(buildCodexRunParams({
|
|
1951
1968
|
task,
|
|
1952
1969
|
project,
|
|
1953
1970
|
useExistingMirror: false,
|
|
@@ -1962,8 +1979,7 @@
|
|
|
1962
1979
|
attachments: submittedAttachments,
|
|
1963
1980
|
skillInvocation: submittedSkillInvocation,
|
|
1964
1981
|
submittedMode
|
|
1965
|
-
})
|
|
1966
|
-
}));
|
|
1982
|
+
})));
|
|
1967
1983
|
}
|
|
1968
1984
|
|
|
1969
1985
|
if (!response.ok && response.error?.code === 'thread_resume_failed') {
|
|
@@ -1979,9 +1995,7 @@
|
|
|
1979
1995
|
}
|
|
1980
1996
|
}
|
|
1981
1997
|
appendRunEvent({ title: tx('Creating a new Codex conversation thread.', '正在新建 Codex 会话线程。'), status: 'running' });
|
|
1982
|
-
response = await
|
|
1983
|
-
method: 'codex.run',
|
|
1984
|
-
params: buildCodexRunParams({
|
|
1998
|
+
response = await sendTrackedCodexRun(buildCodexRunParams({
|
|
1985
1999
|
task,
|
|
1986
2000
|
project,
|
|
1987
2001
|
useExistingMirror,
|
|
@@ -1996,8 +2010,7 @@
|
|
|
1996
2010
|
attachments: submittedAttachments,
|
|
1997
2011
|
skillInvocation: submittedSkillInvocation,
|
|
1998
2012
|
submittedMode
|
|
1999
|
-
})
|
|
2000
|
-
});
|
|
2013
|
+
}));
|
|
2001
2014
|
} else {
|
|
2002
2015
|
appendRunEvent({ title: tx('Cancelled: user chose not to create a new thread.', '已取消:用户选择不新建线程。'), status: 'rejected' });
|
|
2003
2016
|
await finalizeAuditRecord(runAuditDraft, { resultStatus: 'rejected' });
|
|
@@ -2158,6 +2171,9 @@
|
|
|
2158
2171
|
mode: submittedMode,
|
|
2159
2172
|
requireReviewing: submittedRequireReviewing
|
|
2160
2173
|
});
|
|
2174
|
+
if (runCancellationRequested) {
|
|
2175
|
+
throw createRunCancellationError();
|
|
2176
|
+
}
|
|
2161
2177
|
await finalizeAuditRecord(runAuditDraft, {
|
|
2162
2178
|
...(syncOutcome.audit || {}),
|
|
2163
2179
|
sensitiveFindings: sensitiveFindings.findings,
|
|
@@ -2306,6 +2322,15 @@
|
|
|
2306
2322
|
});
|
|
2307
2323
|
finishRunView(tx('Task failed', '任务失败'), 'failed');
|
|
2308
2324
|
} finally {
|
|
2325
|
+
const settledView = currentRunView;
|
|
2326
|
+
const settledRecord = settledView ? findRunRecord(settledView.recordId, settledView.sessionId) : null;
|
|
2327
|
+
const settlement = {
|
|
2328
|
+
sessionId: settledView?.sessionId || '',
|
|
2329
|
+
status: settledView?.terminalStatus || settledRecord?.status || 'failed',
|
|
2330
|
+
queueItemId: settledView?.queueItemId || '',
|
|
2331
|
+
requestId: settledView?.nativeRequestId || '',
|
|
2332
|
+
continueQueue: runCancellationRequested
|
|
2333
|
+
};
|
|
2309
2334
|
setRunning(false);
|
|
2310
2335
|
nativeChannel.clearActiveRequest();
|
|
2311
2336
|
stopRunElapsedTick();
|
|
@@ -2315,7 +2340,12 @@
|
|
|
2315
2340
|
if (isExperimentalOtEnabled()) {
|
|
2316
2341
|
await resumeOtWarmMirror('run-settled');
|
|
2317
2342
|
}
|
|
2318
|
-
|
|
2343
|
+
await saveState().catch(() => {});
|
|
2344
|
+
activeTurnControl.release(settlement.requestId);
|
|
2345
|
+
if (settlement.requestId) {
|
|
2346
|
+
activeTurnControl.acknowledge(settlement.requestId).catch(() => {});
|
|
2347
|
+
}
|
|
2348
|
+
runQueueScheduler?.afterSettlement(settlement).catch(() => {});
|
|
2319
2349
|
}
|
|
2320
2350
|
}
|
|
2321
2351
|
|
|
@@ -2554,7 +2584,7 @@
|
|
|
2554
2584
|
return;
|
|
2555
2585
|
}
|
|
2556
2586
|
if (currentRunView) {
|
|
2557
|
-
return;
|
|
2587
|
+
return queueComposerInput();
|
|
2558
2588
|
}
|
|
2559
2589
|
runTask().catch(error => {
|
|
2560
2590
|
setRunning(false);
|
|
@@ -2567,13 +2597,73 @@
|
|
|
2567
2597
|
});
|
|
2568
2598
|
}
|
|
2569
2599
|
|
|
2600
|
+
const runGuidanceController = window.CodexOverleafRunGuidanceController.create({
|
|
2601
|
+
findRunRecord, appendRunEvent, getCurrentRunView: () => currentRunView,
|
|
2602
|
+
renderRunEvent, cssEscape, bumpUnreadIfDetached
|
|
2603
|
+
});
|
|
2604
|
+
|
|
2605
|
+
function initializeRunQueueScheduler() {
|
|
2606
|
+
runInputCoordinator = RunQueueScheduler.createCoordinator({
|
|
2607
|
+
getSession: sessionId => findSessionById(sessionId),
|
|
2608
|
+
getActiveSession: () => getActiveSession(state),
|
|
2609
|
+
setQueue: (sessionId, pendingInputs) => updateSessionById(sessionId, { pendingInputs }),
|
|
2610
|
+
getCurrentRun: () => currentRunView,
|
|
2611
|
+
canStart: sessionId => state?.activeSessionId === sessionId && isProjectEditorRoute(window.location),
|
|
2612
|
+
readInputs: readPanelInputs,
|
|
2613
|
+
getTask: () => panel?.querySelector('[data-task]')?.value || '',
|
|
2614
|
+
hasAttachments: () => getComposerAttachmentsForRun().length > 0,
|
|
2615
|
+
capturePayload: () => ({
|
|
2616
|
+
mode: state.mode,
|
|
2617
|
+
autoRecompile: state.autoRecompile !== false,
|
|
2618
|
+
requireReviewing: state.requireReviewing,
|
|
2619
|
+
focusFiles: getActiveFocusFiles()
|
|
2620
|
+
}),
|
|
2621
|
+
clearTask: () => {
|
|
2622
|
+
panel.querySelector('[data-task]').value = '';
|
|
2623
|
+
updateSessionById(state.activeSessionId, { task: '' });
|
|
2624
|
+
autosizeTaskTextarea();
|
|
2625
|
+
syncComposerSendAvailability();
|
|
2626
|
+
},
|
|
2627
|
+
applyQueuedInput: item => {
|
|
2628
|
+
state.autoRecompile = item.payload?.autoRecompile !== false;
|
|
2629
|
+
state = updateActiveSession(state, {
|
|
2630
|
+
task: item.text,
|
|
2631
|
+
mode: item.payload?.mode || state.mode,
|
|
2632
|
+
requireReviewing: item.payload?.requireReviewing !== false,
|
|
2633
|
+
focusFiles: item.payload?.focusFiles || []
|
|
2634
|
+
});
|
|
2635
|
+
applyStateToPanel();
|
|
2636
|
+
},
|
|
2637
|
+
run: item => runTask({ queuedInput: item }),
|
|
2638
|
+
sendSteer: params => sendBackgroundNative({ method: 'codex.steer', params }),
|
|
2639
|
+
getContainer: () => panel?.querySelector('[data-pending-inputs]'),
|
|
2640
|
+
createView: options => PendingInputView.create(options),
|
|
2641
|
+
appendEvent: (title, status) => appendRunEvent({ title, status }),
|
|
2642
|
+
appendGuidance: runGuidanceController.upsert,
|
|
2643
|
+
removeGuidance: runGuidanceController.remove,
|
|
2644
|
+
toast: (message, status) => showPluginToast(message, { status }),
|
|
2645
|
+
tr,
|
|
2646
|
+
save: () => saveState(),
|
|
2647
|
+
saveSoon: () => saveStateSoon(),
|
|
2648
|
+
randomUUID: () => crypto.randomUUID()
|
|
2649
|
+
});
|
|
2650
|
+
runQueueScheduler = runInputCoordinator.scheduler;
|
|
2651
|
+
}
|
|
2652
|
+
|
|
2653
|
+
function queueComposerInput() {
|
|
2654
|
+
return runInputCoordinator?.queueComposerInput();
|
|
2655
|
+
}
|
|
2656
|
+
|
|
2657
|
+
function renderPendingInputs() {
|
|
2658
|
+
return runInputCoordinator?.render();
|
|
2659
|
+
}
|
|
2660
|
+
|
|
2570
2661
|
async function cancelActiveRun() {
|
|
2571
2662
|
if (!currentRunView || runCancellationRequested) {
|
|
2572
2663
|
return;
|
|
2573
2664
|
}
|
|
2574
2665
|
runCancellationRequested = true;
|
|
2575
2666
|
runCancellationController?.abort();
|
|
2576
|
-
cancelActivePageBridgeRequests();
|
|
2577
2667
|
panel.dataset.cancelling = 'true';
|
|
2578
2668
|
setRunning(true);
|
|
2579
2669
|
appendRunEvent({
|
|
@@ -2758,125 +2848,16 @@
|
|
|
2758
2848
|
return confirmed ? 'new' : 'cancel';
|
|
2759
2849
|
}
|
|
2760
2850
|
|
|
2761
|
-
async function showPluginConfirm({
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
if (!panel) {
|
|
2769
|
-
ensurePanelOpen();
|
|
2770
|
-
}
|
|
2771
|
-
|
|
2772
|
-
if (activePluginConfirmResolve) {
|
|
2773
|
-
activePluginConfirmResolve(false);
|
|
2774
|
-
activePluginConfirmResolve = null;
|
|
2775
|
-
}
|
|
2776
|
-
|
|
2777
|
-
return new Promise(resolve => {
|
|
2778
|
-
let settled = false;
|
|
2779
|
-
const overlay = document.createElement('div');
|
|
2780
|
-
overlay.className = 'codex-plugin-confirm';
|
|
2781
|
-
overlay.setAttribute('data-plugin-confirm', 'true');
|
|
2782
|
-
overlay.setAttribute('role', 'dialog');
|
|
2783
|
-
overlay.setAttribute('aria-modal', 'true');
|
|
2784
|
-
overlay.setAttribute('aria-label', title);
|
|
2785
|
-
|
|
2786
|
-
const card = document.createElement('section');
|
|
2787
|
-
card.className = 'codex-plugin-confirm-card';
|
|
2788
|
-
|
|
2789
|
-
const head = document.createElement('div');
|
|
2790
|
-
head.className = 'codex-plugin-confirm-head';
|
|
2791
|
-
const icon = document.createElement('img');
|
|
2792
|
-
icon.className = 'codex-plugin-confirm-icon';
|
|
2793
|
-
icon.alt = '';
|
|
2794
|
-
icon.setAttribute('aria-hidden', 'true');
|
|
2795
|
-
icon.src = chrome.runtime.getURL('assets/icons/codex-overleaf-dialog-icon.png');
|
|
2796
|
-
const titleWrap = document.createElement('div');
|
|
2797
|
-
const brand = document.createElement('div');
|
|
2798
|
-
brand.className = 'codex-plugin-confirm-brand';
|
|
2799
|
-
brand.textContent = tr('confirmBrand');
|
|
2800
|
-
const titleEl = document.createElement('div');
|
|
2801
|
-
titleEl.className = 'codex-plugin-confirm-title';
|
|
2802
|
-
titleEl.textContent = title;
|
|
2803
|
-
titleWrap.append(brand, titleEl);
|
|
2804
|
-
head.append(icon, titleWrap);
|
|
2805
|
-
|
|
2806
|
-
const body = document.createElement('div');
|
|
2807
|
-
body.className = 'codex-plugin-confirm-body';
|
|
2808
|
-
body.textContent = String(message || '');
|
|
2809
|
-
|
|
2810
|
-
const actions = document.createElement('div');
|
|
2811
|
-
actions.className = 'codex-plugin-confirm-actions';
|
|
2812
|
-
const cancel = document.createElement('button');
|
|
2813
|
-
cancel.type = 'button';
|
|
2814
|
-
cancel.className = 'codex-plugin-confirm-cancel';
|
|
2815
|
-
cancel.textContent = cancelLabel;
|
|
2816
|
-
const confirm = document.createElement('button');
|
|
2817
|
-
confirm.type = 'button';
|
|
2818
|
-
confirm.className = 'codex-plugin-confirm-confirm';
|
|
2819
|
-
if (destructive) {
|
|
2820
|
-
confirm.dataset.destructive = 'true';
|
|
2821
|
-
}
|
|
2822
|
-
confirm.textContent = confirmLabel;
|
|
2823
|
-
actions.append(cancel, confirm);
|
|
2824
|
-
card.append(head, body, actions);
|
|
2825
|
-
overlay.append(card);
|
|
2826
|
-
panel.append(overlay);
|
|
2827
|
-
|
|
2828
|
-
const cleanup = value => {
|
|
2829
|
-
if (settled) {
|
|
2830
|
-
return;
|
|
2831
|
-
}
|
|
2832
|
-
settled = true;
|
|
2833
|
-
activePluginConfirmResolve = null;
|
|
2834
|
-
document.removeEventListener('keydown', onKeydown, true);
|
|
2835
|
-
overlay.remove();
|
|
2836
|
-
resolve(value);
|
|
2837
|
-
};
|
|
2838
|
-
activePluginConfirmResolve = cleanup;
|
|
2839
|
-
const onKeydown = event => {
|
|
2840
|
-
if (event.key === 'Escape') {
|
|
2841
|
-
event.preventDefault();
|
|
2842
|
-
cleanup(false);
|
|
2843
|
-
return;
|
|
2844
|
-
}
|
|
2845
|
-
if (event.key === 'Enter') {
|
|
2846
|
-
event.preventDefault();
|
|
2847
|
-
// Destructive dialogs never treat a bare Enter as consent — the
|
|
2848
|
-
// user must deliberately reach the (red) confirm button. Enter on
|
|
2849
|
-
// a focused button still activates it via the click handler.
|
|
2850
|
-
if (!destructive && event.target !== cancel) {
|
|
2851
|
-
cleanup(true);
|
|
2852
|
-
} else if (event.target === cancel) {
|
|
2853
|
-
cleanup(false);
|
|
2854
|
-
} else if (event.target === confirm) {
|
|
2855
|
-
cleanup(true);
|
|
2856
|
-
}
|
|
2857
|
-
return;
|
|
2858
|
-
}
|
|
2859
|
-
if (event.key === 'Tab') {
|
|
2860
|
-
// aria-modal contract: keep Tab cycling inside the two-button card
|
|
2861
|
-
// instead of drifting onto the Overleaf page behind the overlay.
|
|
2862
|
-
event.preventDefault();
|
|
2863
|
-
const next = event.target === cancel ? confirm : cancel;
|
|
2864
|
-
next.focus();
|
|
2865
|
-
}
|
|
2866
|
-
};
|
|
2867
|
-
|
|
2868
|
-
overlay.addEventListener('click', event => {
|
|
2869
|
-
if (event.target === overlay) {
|
|
2870
|
-
cleanup(false);
|
|
2871
|
-
}
|
|
2851
|
+
async function showPluginConfirm(options = {}) {
|
|
2852
|
+
if (!pluginConfirmController) {
|
|
2853
|
+
pluginConfirmController = PanelRenderer.createConfirmController({
|
|
2854
|
+
getPanel: () => panel,
|
|
2855
|
+
ensurePanelOpen,
|
|
2856
|
+
getIconUrl: () => chrome.runtime.getURL('assets/icons/codex-overleaf-dialog-icon.png'),
|
|
2857
|
+
tr
|
|
2872
2858
|
});
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
document.addEventListener('keydown', onKeydown, true);
|
|
2876
|
-
// Destructive dialogs open on the SAFE button so a stray keystroke
|
|
2877
|
-
// cannot delete anything; benign confirms keep the fast path.
|
|
2878
|
-
(destructive ? cancel : confirm).focus();
|
|
2879
|
-
});
|
|
2859
|
+
}
|
|
2860
|
+
return pluginConfirmController.show(options);
|
|
2880
2861
|
}
|
|
2881
2862
|
|
|
2882
2863
|
function getEnabledCodexOverleafSkillIds() {
|
|
@@ -2930,10 +2911,97 @@
|
|
|
2930
2911
|
});
|
|
2931
2912
|
return {
|
|
2932
2913
|
...params,
|
|
2914
|
+
clientRunId: currentRunView?.recordId || '',
|
|
2915
|
+
clientSessionId: currentRunView?.sessionId || '',
|
|
2933
2916
|
providerSelection: providerSettingsCoordinator.getRunSelection(state?.providerId)
|
|
2934
2917
|
};
|
|
2935
2918
|
}
|
|
2936
2919
|
|
|
2920
|
+
async function sendTrackedCodexRun(params) {
|
|
2921
|
+
const compatibilityGate = await ensureNativeCompatibilityForMethod('codex.run');
|
|
2922
|
+
if (!compatibilityGate.ok) {
|
|
2923
|
+
return compatibilityGate.response;
|
|
2924
|
+
}
|
|
2925
|
+
throwIfCancelledBeforeNativeDispatch('codex.run');
|
|
2926
|
+
const tracked = nativeChannel.sendNativeTracked(attachNativeCompatibilityEvidence({
|
|
2927
|
+
method: 'codex.run',
|
|
2928
|
+
params
|
|
2929
|
+
}, compatibilityGate.compatibility));
|
|
2930
|
+
if (currentRunView) {
|
|
2931
|
+
currentRunView.nativeRequestId = tracked.id;
|
|
2932
|
+
const record = findRunRecord(currentRunView.recordId, currentRunView.sessionId);
|
|
2933
|
+
if (record) {
|
|
2934
|
+
record.nativeRequestId = tracked.id;
|
|
2935
|
+
}
|
|
2936
|
+
activeTurnControl.bind({
|
|
2937
|
+
requestId: tracked.id,
|
|
2938
|
+
projectKey: currentRunView.runProjectId,
|
|
2939
|
+
clientRunId: currentRunView.recordId,
|
|
2940
|
+
sessionId: currentRunView.sessionId
|
|
2941
|
+
});
|
|
2942
|
+
saveStateSoon();
|
|
2943
|
+
}
|
|
2944
|
+
return tracked.promise.finally(() => {
|
|
2945
|
+
if (currentRunView?.nativeRequestId === tracked.id) {
|
|
2946
|
+
currentRunView.activeTurn = null;
|
|
2947
|
+
renderPendingInputs();
|
|
2948
|
+
}
|
|
2949
|
+
});
|
|
2950
|
+
}
|
|
2951
|
+
|
|
2952
|
+
async function recoverInterruptedRunJournals() {
|
|
2953
|
+
const projectKey = getCurrentProjectId();
|
|
2954
|
+
const recovery = await activeTurnControl.recoverJournals({
|
|
2955
|
+
projectKey,
|
|
2956
|
+
findSession: findSessionById,
|
|
2957
|
+
getActiveSession: () => getActiveSession(state),
|
|
2958
|
+
createInterruptedRun: journal => ({
|
|
2959
|
+
id: journal.clientRunId,
|
|
2960
|
+
task: journal.task || 'interrupted task',
|
|
2961
|
+
mode: journal.mode || 'ask',
|
|
2962
|
+
model: journal.model || '',
|
|
2963
|
+
status: 'interrupted',
|
|
2964
|
+
statusText: tr('restoredRunStoppedStatus'),
|
|
2965
|
+
runProjectId: projectKey,
|
|
2966
|
+
startedAt: journal.createdAt || new Date().toISOString(),
|
|
2967
|
+
finishedAt: journal.updatedAt || new Date().toISOString(),
|
|
2968
|
+
events: [],
|
|
2969
|
+
attachments: [],
|
|
2970
|
+
undoOperations: [],
|
|
2971
|
+
undoTrackedChanges: [],
|
|
2972
|
+
undoExpectedFiles: []
|
|
2973
|
+
}),
|
|
2974
|
+
mapEvent: (raw, journal) => {
|
|
2975
|
+
const activity = mapAgentEventToActivity(raw, { locale: getLocale() });
|
|
2976
|
+
if (!activity?.visible || activity.kind === 'technical') {
|
|
2977
|
+
return null;
|
|
2978
|
+
}
|
|
2979
|
+
return {
|
|
2980
|
+
kind: activity.kind || 'activity',
|
|
2981
|
+
title: sanitizeAssistantVisibleText(activity.title) || 'Event',
|
|
2982
|
+
status: activity.status || 'running',
|
|
2983
|
+
detail: sanitizeAssistantVisibleValue(activity.detail),
|
|
2984
|
+
timestamp: raw.timestamp || journal.updatedAt || new Date().toISOString(),
|
|
2985
|
+
streamKey: sanitizeAssistantVisibleText(activity.streamKey),
|
|
2986
|
+
streamRole: sanitizeAssistantVisibleText(activity.streamRole),
|
|
2987
|
+
appendText: sanitizeAssistantVisibleText(activity.appendText),
|
|
2988
|
+
replaceText: activity.replaceText
|
|
2989
|
+
};
|
|
2990
|
+
},
|
|
2991
|
+
upsertStream: upsertRunStreamRecordEvent,
|
|
2992
|
+
pauseSessionQueue: (session, reason) => {
|
|
2993
|
+
session.pendingInputs = RunInputQueue.pauseAll(session.pendingInputs || [], reason);
|
|
2994
|
+
},
|
|
2995
|
+
maxEvents: MAX_RUN_EVENTS
|
|
2996
|
+
});
|
|
2997
|
+
if (recovery.changed) {
|
|
2998
|
+
await saveState();
|
|
2999
|
+
for (const requestId of recovery.acknowledgeIds) {
|
|
3000
|
+
await activeTurnControl.acknowledge(requestId).catch(() => false);
|
|
3001
|
+
}
|
|
3002
|
+
}
|
|
3003
|
+
}
|
|
3004
|
+
|
|
2937
3005
|
async function createAuditDraftForRun(input = {}) {
|
|
2938
3006
|
const StorageDb = window.CodexOverleafStorageDb;
|
|
2939
3007
|
if (!StorageDb || !AuditRecords) {
|
|
@@ -3123,6 +3191,8 @@
|
|
|
3123
3191
|
}
|
|
3124
3192
|
|
|
3125
3193
|
function appendRunCancelledReport() {
|
|
3194
|
+
const hasQueuedFollowUp = (getActiveSession(state)?.pendingInputs || [])
|
|
3195
|
+
.some(item => item?.status === 'queued');
|
|
3126
3196
|
appendRunEvent({
|
|
3127
3197
|
title: tx('Current Codex task was cancelled.', '已中断当前 Codex 任务。'),
|
|
3128
3198
|
status: 'failed'
|
|
@@ -3132,7 +3202,9 @@
|
|
|
3132
3202
|
status: 'rejected',
|
|
3133
3203
|
operations: [],
|
|
3134
3204
|
applyResults: [],
|
|
3135
|
-
nextStep:
|
|
3205
|
+
nextStep: hasQueuedFollowUp
|
|
3206
|
+
? tx('The first queued follow-up will start next.', '接下来会自动启动队首的后续任务。')
|
|
3207
|
+
: tx('Edit the task and run again.', '可以修改任务后重新运行。')
|
|
3136
3208
|
});
|
|
3137
3209
|
}
|
|
3138
3210
|
|
|
@@ -3145,17 +3217,27 @@
|
|
|
3145
3217
|
task.style.height = `${Math.min(task.scrollHeight, 160)}px`;
|
|
3146
3218
|
}
|
|
3147
3219
|
|
|
3148
|
-
function syncComposerSendAvailability() {
|
|
3220
|
+
function syncComposerSendAvailability(runningOverride) {
|
|
3149
3221
|
const runButton = panel?.querySelector('[data-run]');
|
|
3150
3222
|
if (!runButton) {
|
|
3151
3223
|
return;
|
|
3152
3224
|
}
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3225
|
+
const hasInput = Boolean(String(panel?.querySelector('[data-task]')?.value || '').trim());
|
|
3226
|
+
const running = typeof runningOverride === 'boolean'
|
|
3227
|
+
? runningOverride
|
|
3228
|
+
: Boolean(currentRunView);
|
|
3229
|
+
const action = running && !hasInput ? 'cancel' : 'send';
|
|
3230
|
+
const label = action === 'cancel'
|
|
3231
|
+
? tr('cancelRun')
|
|
3232
|
+
: running
|
|
3233
|
+
? tr('queueNextInput')
|
|
3234
|
+
: tr('send');
|
|
3235
|
+
runButton.dataset.action = action;
|
|
3236
|
+
runButton.disabled = action === 'cancel'
|
|
3237
|
+
? runCancellationRequested
|
|
3238
|
+
: !hasInput;
|
|
3239
|
+
runButton.title = label;
|
|
3240
|
+
runButton.setAttribute('aria-label', label);
|
|
3159
3241
|
}
|
|
3160
3242
|
|
|
3161
3243
|
function handleTaskInputKeydown(event) {
|
|
@@ -3172,6 +3254,7 @@
|
|
|
3172
3254
|
function handleTaskInput() {
|
|
3173
3255
|
autosizeTaskTextarea();
|
|
3174
3256
|
syncComposerSendAvailability();
|
|
3257
|
+
renderPendingInputs();
|
|
3175
3258
|
scheduleMirrorPrefetch({
|
|
3176
3259
|
reason: 'composer-input',
|
|
3177
3260
|
delayMs: mirrorHealth?.PREFETCH_DEBOUNCE_MS || 1200
|
|
@@ -5240,102 +5323,23 @@
|
|
|
5240
5323
|
}
|
|
5241
5324
|
|
|
5242
5325
|
function formatProjectSnapshotUserLog(project) {
|
|
5243
|
-
|
|
5244
|
-
if (!files.length) {
|
|
5245
|
-
return tx(
|
|
5246
|
-
'No Overleaf project files were read yet. Make sure the project has loaded, or open a .tex file in Overleaf and retry.',
|
|
5247
|
-
'还没有读到 Overleaf 项目文件。请确认项目已加载完成,或在 Overleaf 点开一个 .tex 文件后重试。'
|
|
5248
|
-
);
|
|
5249
|
-
}
|
|
5250
|
-
|
|
5251
|
-
const textCount = files.filter(isTextSnapshotFile).length;
|
|
5252
|
-
const binaryCount = files.length - textCount;
|
|
5253
|
-
const resourceText = binaryCount ? tx(`, ${binaryCount} asset file(s)`, `,${binaryCount} 个资源文件`) : '';
|
|
5254
|
-
const activePath = project.activePath ? tx(`, current file: ${project.activePath}`, `,当前文件:${project.activePath}`) : '';
|
|
5255
|
-
const focusFiles = getActiveFocusFiles();
|
|
5256
|
-
const focusText = focusFiles.length ? tx(`, focus: ${focusFiles.join(', ')}`, `,优先处理:${focusFiles.join(', ')}`) : '';
|
|
5257
|
-
return tx(
|
|
5258
|
-
`Read Overleaf project: ${textCount} text file(s)${resourceText}${activePath}${focusText}.`,
|
|
5259
|
-
`已读取 Overleaf 项目:${textCount} 个文本文件${resourceText}${activePath}${focusText}。`
|
|
5260
|
-
);
|
|
5326
|
+
return DiagnosticsPanel.formatProjectSnapshotUserLog(project, { tx, getFocusFiles: getActiveFocusFiles });
|
|
5261
5327
|
}
|
|
5262
5328
|
|
|
5263
5329
|
function formatProjectSnapshotFileSize(file) {
|
|
5264
|
-
|
|
5265
|
-
return `${String(file.content || '').length} chars`;
|
|
5266
|
-
}
|
|
5267
|
-
if (Number.isFinite(Number(file?.size))) {
|
|
5268
|
-
return `${Number(file.size)} bytes`;
|
|
5269
|
-
}
|
|
5270
|
-
return file?.contentBase64 ? `${String(file.contentBase64).length} base64` : 'binary';
|
|
5330
|
+
return DiagnosticsPanel.formatProjectSnapshotFileSize(file);
|
|
5271
5331
|
}
|
|
5272
5332
|
|
|
5273
5333
|
function formatProjectSnapshotWarning(warning) {
|
|
5274
|
-
|
|
5275
|
-
return tx('No project source files were read. Make sure Overleaf has loaded, or open a .tex file and retry.', '没有读取到项目源文件。请确认 Overleaf 页面加载完成,或点开一个 .tex 文件后重试。');
|
|
5276
|
-
}
|
|
5277
|
-
if (/Full project snapshot was not captured/i.test(warning)) {
|
|
5278
|
-
return tx('The full Overleaf project was not read. To avoid refreshing the local workspace from an incomplete snapshot, reload Overleaf or retry later.', '没有读到完整的 Overleaf 项目。为了避免本地 workspace 用残缺快照覆盖项目,请刷新 Overleaf 或稍后重试。');
|
|
5279
|
-
}
|
|
5280
|
-
if (/empty or still loading/i.test(warning)) {
|
|
5281
|
-
return tx('Some file content is still loading. Wait for Overleaf to finish loading, then retry.', '读取到的文件内容还在加载中。请等 Overleaf 加载完成后重试。');
|
|
5282
|
-
}
|
|
5283
|
-
if (/suspiciously short|shorter than 80 characters/i.test(warning)) {
|
|
5284
|
-
return tx('Some file content is very short and may not have fully loaded. Results may be incomplete.', '部分文件内容很短,可能还没完全加载。结果可能不完整。');
|
|
5285
|
-
}
|
|
5286
|
-
if (/identical captured content/i.test(warning)) {
|
|
5287
|
-
return tx('Several files appear to have identical captured content, so Overleaf file switching may have failed. Reload and retry.', '多个文件内容看起来相同,Overleaf 文件切换可能没有成功。请刷新页面后重试。');
|
|
5288
|
-
}
|
|
5289
|
-
if (/were skipped/i.test(warning)) {
|
|
5290
|
-
return tx('Some project files were skipped, usually images, PDFs, or unreadable files.', '有些项目文件被跳过,通常是图片、PDF 或无法读取的文件。');
|
|
5291
|
-
}
|
|
5292
|
-
return warning;
|
|
5334
|
+
return DiagnosticsPanel.formatProjectSnapshotWarning(warning, tx);
|
|
5293
5335
|
}
|
|
5294
5336
|
|
|
5295
5337
|
function appendEditorDiagnostics(editorDiagnostics, projectDiagnostics) {
|
|
5296
|
-
|
|
5297
|
-
const active = editorDiagnostics.active;
|
|
5298
|
-
appendLog(`Editor probe: active ${active?.tag || 'none'} ${active?.ariaLabel || active?.className || ''} len=${active?.valueLength || 0}; textareas=${editorDiagnostics.textareaCount || 0}; editables=${editorDiagnostics.editableCount || 0}; iframes=${editorDiagnostics.iframeCount || 0}.`);
|
|
5299
|
-
if (editorDiagnostics.documentStats) {
|
|
5300
|
-
const stats = editorDiagnostics.documentStats;
|
|
5301
|
-
appendLog(`DOM stats: elements=${stats.elementCount || 0}; textareas=${stats.textareaCount || 0}; cm=${stats.cmCount || 0}; role textbox=${stats.roleTextboxCount || 0}.`);
|
|
5302
|
-
}
|
|
5303
|
-
if (editorDiagnostics.unstableStore) {
|
|
5304
|
-
const store = editorDiagnostics.unstableStore;
|
|
5305
|
-
const readable = (store.readable || [])
|
|
5306
|
-
.map(item => `${item.path}:${item.present ? item.type : 'missing'}`)
|
|
5307
|
-
.join(', ');
|
|
5308
|
-
appendLog(`Overleaf store: ${store.present ? 'present' : 'missing'}${readable ? `; ${readable}` : ''}.`);
|
|
5309
|
-
}
|
|
5310
|
-
if (editorDiagnostics.codeMirrorView) {
|
|
5311
|
-
const view = editorDiagnostics.codeMirrorView;
|
|
5312
|
-
appendLog(`CodeMirror view: docLength=${view.docLength || 0}; dispatch=${Boolean(view.hasDispatch)}; source=${view.source || 'unknown'}.`);
|
|
5313
|
-
}
|
|
5314
|
-
if (editorDiagnostics.globals) {
|
|
5315
|
-
const globals = editorDiagnostics.globals;
|
|
5316
|
-
appendLog(`Overleaf globals: overleaf=${globals.overleaf || 'missing'}; Overleaf=${globals.Overleaf || 'missing'}; _ide=${globals._ide || 'missing'}.`);
|
|
5317
|
-
}
|
|
5318
|
-
for (const item of [...(editorDiagnostics.textareas || []), ...(editorDiagnostics.editables || [])].slice(0, 3)) {
|
|
5319
|
-
appendLog(`Editor candidate: ${item.tag || 'node'} ${item.ariaLabel || item.className || item.id || ''} len=${item.valueLength || 0}.`);
|
|
5320
|
-
}
|
|
5321
|
-
}
|
|
5322
|
-
if (projectDiagnostics) {
|
|
5323
|
-
const records = projectDiagnostics.docRecords || [];
|
|
5324
|
-
appendLog(`Project probe: doc records=${projectDiagnostics.docRecordCount || 0}; roots=${(projectDiagnostics.internalRootKeys || []).slice(0, 6).join(', ') || 'none'}.`);
|
|
5325
|
-
for (const record of records.slice(0, 4)) {
|
|
5326
|
-
appendLog(`Doc record: ${record.path} id=${record.id} source=${record.source || 'internal'}.`);
|
|
5327
|
-
}
|
|
5328
|
-
}
|
|
5338
|
+
DiagnosticsPanel.appendEditorDiagnostics(editorDiagnostics, projectDiagnostics, appendLog);
|
|
5329
5339
|
}
|
|
5330
5340
|
|
|
5331
5341
|
function appendProjectWarnings(project) {
|
|
5332
|
-
|
|
5333
|
-
for (const warning of warnings.blocking) {
|
|
5334
|
-
appendLog(`Snapshot blocked: ${warning}`);
|
|
5335
|
-
}
|
|
5336
|
-
for (const warning of warnings.nonBlocking) {
|
|
5337
|
-
appendLog(`Snapshot warning: ${warning}`);
|
|
5338
|
-
}
|
|
5342
|
+
DiagnosticsPanel.appendProjectWarnings(project, { getWarnings: getProjectSnapshotWarnings, appendLog });
|
|
5339
5343
|
}
|
|
5340
5344
|
|
|
5341
5345
|
function getProjectSnapshotWarnings(project) {
|
|
@@ -5385,40 +5389,15 @@
|
|
|
5385
5389
|
}
|
|
5386
5390
|
|
|
5387
5391
|
function isTextSnapshotFile(file) {
|
|
5388
|
-
return
|
|
5392
|
+
return DiagnosticsPanel.isTextSnapshotFile(file);
|
|
5389
5393
|
}
|
|
5390
5394
|
|
|
5391
5395
|
function uniqueContentSignatures(files) {
|
|
5392
|
-
|
|
5393
|
-
const content = String(file.content || '');
|
|
5394
|
-
return `${content.length}:${content.slice(0, 120)}:${content.slice(-120)}`;
|
|
5395
|
-
});
|
|
5396
|
-
return Array.from(new Set(signatures));
|
|
5396
|
+
return DiagnosticsPanel.uniqueContentSignatures(files);
|
|
5397
5397
|
}
|
|
5398
5398
|
|
|
5399
5399
|
function appendReviewingDiagnostics(diagnostics) {
|
|
5400
|
-
|
|
5401
|
-
appendLog(tx('Probe diagnostics unavailable.', '诊断探针不可用。'));
|
|
5402
|
-
return;
|
|
5403
|
-
}
|
|
5404
|
-
|
|
5405
|
-
appendLog(`Probe saw ${diagnostics.controlCount || 0} controls; body Reviewing=${Boolean(diagnostics.bodyTextHasReviewing)}, text Reviewing=${Boolean(diagnostics.textContentHasReviewing)}.`);
|
|
5406
|
-
const controls = diagnostics.reviewLikeControls || [];
|
|
5407
|
-
if (!controls.length) {
|
|
5408
|
-
appendLog('Probe did not see review/track/suggest controls in the page DOM.');
|
|
5409
|
-
return;
|
|
5410
|
-
}
|
|
5411
|
-
|
|
5412
|
-
for (const control of controls.slice(0, 4)) {
|
|
5413
|
-
const label = [
|
|
5414
|
-
control.text,
|
|
5415
|
-
control.ariaLabel && `aria:${control.ariaLabel}`,
|
|
5416
|
-
control.title && `title:${control.title}`,
|
|
5417
|
-
control.dataTestId && `test:${control.dataTestId}`,
|
|
5418
|
-
control.id && `id:${control.id}`
|
|
5419
|
-
].filter(Boolean).join(' | ');
|
|
5420
|
-
appendLog(`Review-like ${control.tag || 'node'}: ${label || control.htmlSnippet || '(no label)'}`);
|
|
5421
|
-
}
|
|
5400
|
+
DiagnosticsPanel.appendReviewingDiagnostics(diagnostics, { appendLog, tx });
|
|
5422
5401
|
}
|
|
5423
5402
|
|
|
5424
5403
|
function sendNative(payload) {
|
|
@@ -5988,6 +5967,7 @@
|
|
|
5988
5967
|
runs: Array.isArray(session.runs) ? session.runs : [],
|
|
5989
5968
|
history: Array.isArray(session.history) ? session.history : [],
|
|
5990
5969
|
task: typeof session.task === 'string' ? session.task : '',
|
|
5970
|
+
pendingInputs: Array.isArray(session.pendingInputs) ? session.pendingInputs : [],
|
|
5991
5971
|
mode: session.mode || prefs.mode || 'confirm', providerId: session.providerId || 'builtin',
|
|
5992
5972
|
model: session.model || prefs.model || 'gpt-5.4',
|
|
5993
5973
|
reasoningEffort: session.reasoningEffort || prefs.reasoningEffort || 'high',
|
|
@@ -6149,14 +6129,11 @@
|
|
|
6149
6129
|
updatedAt: session.updatedAt
|
|
6150
6130
|
}, { preserveRunActionPayload: true })
|
|
6151
6131
|
));
|
|
6152
|
-
|
|
6153
|
-
|
|
6154
|
-
|
|
6155
|
-
|
|
6156
|
-
|
|
6157
|
-
await Promise.all(existingSessions
|
|
6158
|
-
.filter(session => session?.id && !keepSessionIds.has(session.id))
|
|
6159
|
-
.map(session => StorageDb.deleteRecord('sessions', session.id)));
|
|
6132
|
+
await window.CodexOverleafSessionPersistence.writeSessions({
|
|
6133
|
+
Migration, StorageDb, projectId,
|
|
6134
|
+
deletedSessionIds: options?.deletedSessionIds,
|
|
6135
|
+
sessionRecords
|
|
6136
|
+
});
|
|
6160
6137
|
} catch (error) {
|
|
6161
6138
|
// Fallback: try legacy save
|
|
6162
6139
|
try {
|
|
@@ -6310,7 +6287,7 @@
|
|
|
6310
6287
|
saveStateSoon();
|
|
6311
6288
|
return;
|
|
6312
6289
|
}
|
|
6313
|
-
if (event?.target?.matches?.('[data-speed]'))
|
|
6290
|
+
if (event?.target?.matches?.('[data-speed]')) renderReasoningOptions(getRenderedModelEntries());
|
|
6314
6291
|
renderSpeedOptions(getRenderedModelEntries());
|
|
6315
6292
|
readPanelInputs();
|
|
6316
6293
|
renderModelConfigChoices();
|
|
@@ -6364,14 +6341,21 @@
|
|
|
6364
6341
|
syncExperimentalOtToggleForProject(projectId);
|
|
6365
6342
|
}
|
|
6366
6343
|
persistComposerAttachmentsToState();
|
|
6344
|
+
const model = readSelectedModelInput();
|
|
6345
|
+
const reasoningEffort = panel.querySelector('[data-reasoning]').value;
|
|
6346
|
+
const speedTier = readSelectedSpeedInput();
|
|
6367
6347
|
state = updateActiveSession(state, {
|
|
6368
|
-
model: readSelectedModelInput(),
|
|
6369
|
-
reasoningEffort: panel.querySelector('[data-reasoning]').value,
|
|
6370
|
-
speedTier: readSelectedSpeedInput(),
|
|
6371
6348
|
mode: panel.querySelector('[data-mode]').value,
|
|
6372
6349
|
task: panel.querySelector('[data-task]').value,
|
|
6373
6350
|
requireReviewing: panel.querySelector('[data-require-reviewing]').checked
|
|
6374
6351
|
});
|
|
6352
|
+
state.providerId = state.providerId || 'builtin';
|
|
6353
|
+
state.model = model;
|
|
6354
|
+
state.reasoningEffort = reasoningEffort;
|
|
6355
|
+
state.speedTier = speedTier;
|
|
6356
|
+
if (state.session) {
|
|
6357
|
+
state.session.providerId = state.providerId;
|
|
6358
|
+
}
|
|
6375
6359
|
state.autoRecompile = panel.querySelector('[data-auto-recompile]')?.checked !== false;
|
|
6376
6360
|
const experimentalOtCheckbox = panel.querySelector('[data-experimental-ot]');
|
|
6377
6361
|
if (experimentalOtCheckbox) {
|
|
@@ -6516,12 +6500,7 @@
|
|
|
6516
6500
|
|
|
6517
6501
|
|
|
6518
6502
|
function setRunning(running) {
|
|
6519
|
-
|
|
6520
|
-
// Running -> the button is Cancel and must never be disabled; idle -> it
|
|
6521
|
-
// is Send and disables on an empty composer (feedback instead of no-op).
|
|
6522
|
-
runButton.disabled = running ? false : !String(panel.querySelector('[data-task]')?.value || '').trim();
|
|
6523
|
-
runButton.title = running ? tr('cancelRun') : tr('send');
|
|
6524
|
-
runButton.setAttribute('aria-label', running ? tr('cancelRun') : tr('send'));
|
|
6503
|
+
syncComposerSendAvailability(running);
|
|
6525
6504
|
panel.querySelector('[data-new-session]').disabled = false;
|
|
6526
6505
|
panel.querySelector('[data-diagnostics-snapshot]').disabled = running;
|
|
6527
6506
|
if (running) {
|
|
@@ -6529,9 +6508,10 @@
|
|
|
6529
6508
|
}
|
|
6530
6509
|
panel.dataset.running = running ? 'true' : 'false';
|
|
6531
6510
|
panel.dataset.cancelling = running && runCancellationRequested ? 'true' : 'false';
|
|
6511
|
+
renderPendingInputs();
|
|
6532
6512
|
}
|
|
6533
6513
|
|
|
6534
|
-
function startRunView({ task, mode, model, reasoningEffort, speedTier }) {
|
|
6514
|
+
function startRunView({ task, mode, model, reasoningEffort, speedTier, queueItemId = '' }) {
|
|
6535
6515
|
let attachments = [];
|
|
6536
6516
|
if (Array.isArray(arguments[0]?.attachments)) {
|
|
6537
6517
|
attachments = arguments[0].attachments;
|
|
@@ -6555,6 +6535,10 @@
|
|
|
6555
6535
|
undoTrackedChanges: [],
|
|
6556
6536
|
undoExpectedFiles: [],
|
|
6557
6537
|
undoStatus: '',
|
|
6538
|
+
queueItemId,
|
|
6539
|
+
nativeRequestId: '',
|
|
6540
|
+
codexTurnId: '',
|
|
6541
|
+
nativeEventSeq: 0,
|
|
6558
6542
|
// Welcome-panel + write-guard:
|
|
6559
6543
|
// Immutable per-run capture of the project this run was submitted
|
|
6560
6544
|
// against. Every writeback / accept / undo dispatch attaches this id
|
|
@@ -6599,7 +6583,10 @@
|
|
|
6599
6583
|
report: root.querySelector('[data-run-report]'),
|
|
6600
6584
|
status: root.querySelector('[data-run-status]'),
|
|
6601
6585
|
projectFiles: captureProjectReferenceFiles(arguments[0]?.project),
|
|
6602
|
-
startedAt: Date.now()
|
|
6586
|
+
startedAt: Date.now(),
|
|
6587
|
+
queueItemId,
|
|
6588
|
+
nativeRequestId: '',
|
|
6589
|
+
activeTurn: null
|
|
6603
6590
|
};
|
|
6604
6591
|
}
|
|
6605
6592
|
|
|
@@ -6616,6 +6603,8 @@
|
|
|
6616
6603
|
record.status = status;
|
|
6617
6604
|
record.statusText = statusText;
|
|
6618
6605
|
record.finishedAt = new Date().toISOString();
|
|
6606
|
+
currentRunView.terminalStatus = status;
|
|
6607
|
+
runGuidanceController.settleView(currentRunView);
|
|
6619
6608
|
// Welcome-panel + write-guard:
|
|
6620
6609
|
// when the user navigated away mid-run, the URL-derived projectId now
|
|
6621
6610
|
// belongs to a different project (or /project). Routing this finish
|
|
@@ -6646,11 +6635,16 @@
|
|
|
6646
6635
|
}
|
|
6647
6636
|
|
|
6648
6637
|
|
|
6649
|
-
function appendNativeEvent(event) {
|
|
6638
|
+
function appendNativeEvent(event, journalSeq = 0) {
|
|
6650
6639
|
if (!event) {
|
|
6651
6640
|
return;
|
|
6652
6641
|
}
|
|
6653
6642
|
|
|
6643
|
+
if (event.type === 'codex.turn.bound') {
|
|
6644
|
+
bindActiveTurn(event.detail || {}, journalSeq);
|
|
6645
|
+
return;
|
|
6646
|
+
}
|
|
6647
|
+
|
|
6654
6648
|
const activity = mapAgentEventToActivity(event, { locale: getLocale() });
|
|
6655
6649
|
if (!activity?.visible || activity.kind === 'technical') {
|
|
6656
6650
|
return;
|
|
@@ -6666,10 +6660,30 @@
|
|
|
6666
6660
|
streamKey: activity.streamKey,
|
|
6667
6661
|
streamRole: activity.streamRole,
|
|
6668
6662
|
appendText: activity.appendText,
|
|
6669
|
-
replaceText: activity.replaceText
|
|
6663
|
+
replaceText: activity.replaceText,
|
|
6664
|
+
nativeEventSeq: journalSeq
|
|
6670
6665
|
});
|
|
6671
6666
|
}
|
|
6672
6667
|
|
|
6668
|
+
function bindActiveTurn(detail = {}, journalSeq = 0) {
|
|
6669
|
+
if (!currentRunView) {
|
|
6670
|
+
return;
|
|
6671
|
+
}
|
|
6672
|
+
const threadId = String(detail.threadId || '');
|
|
6673
|
+
const turnId = String(detail.turnId || '');
|
|
6674
|
+
currentRunView.activeTurn = { threadId, turnId };
|
|
6675
|
+
const record = findRunRecord(currentRunView.recordId, currentRunView.sessionId);
|
|
6676
|
+
if (record) {
|
|
6677
|
+
record.codexTurnId = turnId;
|
|
6678
|
+
record.nativeEventSeq = Math.max(Number(record.nativeEventSeq || 0), Number(journalSeq || 0));
|
|
6679
|
+
}
|
|
6680
|
+
if (threadId) {
|
|
6681
|
+
updateSessionById(currentRunView.sessionId, { codexThreadId: threadId });
|
|
6682
|
+
}
|
|
6683
|
+
renderPendingInputs();
|
|
6684
|
+
saveState().catch(() => {});
|
|
6685
|
+
}
|
|
6686
|
+
|
|
6673
6687
|
function appendTechnicalEvent(event) {
|
|
6674
6688
|
void event;
|
|
6675
6689
|
}
|
|
@@ -6700,6 +6714,7 @@
|
|
|
6700
6714
|
kind: input.kind || 'activity',
|
|
6701
6715
|
subagent: input.subagent === true ? true : undefined,
|
|
6702
6716
|
technicalDetail: sanitizeAssistantVisibleValue(input.technicalDetail),
|
|
6717
|
+
guidanceId: sanitizeAssistantVisibleText(input.guidanceId),
|
|
6703
6718
|
streamKey: sanitizeAssistantVisibleText(input.streamKey),
|
|
6704
6719
|
streamRole: sanitizeAssistantVisibleText(input.streamRole),
|
|
6705
6720
|
appendText: typeof input.appendText === 'string' ? sanitizeAssistantVisibleText(input.appendText) : input.appendText,
|
|
@@ -6731,6 +6746,9 @@
|
|
|
6731
6746
|
const record = findRunRecord(currentRunView.recordId, currentRunView.sessionId);
|
|
6732
6747
|
let renderedEvent = event;
|
|
6733
6748
|
if (record) {
|
|
6749
|
+
if (Number.isFinite(Number(input.nativeEventSeq))) {
|
|
6750
|
+
record.nativeEventSeq = Math.max(Number(record.nativeEventSeq || 0), Number(input.nativeEventSeq || 0));
|
|
6751
|
+
}
|
|
6734
6752
|
// v1.8.1: keep the session's activity timestamp honest DURING a run —
|
|
6735
6753
|
// it was pinned to run start, so the dashboard's 30-minute zombie
|
|
6736
6754
|
// heuristic mislabeled any long live run as interrupted (fleet P1).
|
|
@@ -6804,6 +6822,7 @@
|
|
|
6804
6822
|
currentRunView.root = root;
|
|
6805
6823
|
currentRunView.runProcess = root.querySelector('[data-run-process]');
|
|
6806
6824
|
currentRunView.processLabel = root.querySelector('[data-run-process-summary]');
|
|
6825
|
+
currentRunView.guidance = root.querySelector('[data-run-guidance]');
|
|
6807
6826
|
currentRunView.events = root.querySelector('[data-run-events]');
|
|
6808
6827
|
currentRunView.report = root.querySelector('[data-run-report]');
|
|
6809
6828
|
currentRunView.status = root.querySelector('[data-run-status]');
|
|
@@ -6821,6 +6840,7 @@
|
|
|
6821
6840
|
if (event.kind === 'technical') {
|
|
6822
6841
|
return;
|
|
6823
6842
|
}
|
|
6843
|
+
if (runGuidanceController.appendToView(event, view)) return;
|
|
6824
6844
|
if (event.kind === 'stream') {
|
|
6825
6845
|
// Stream deltas update an existing line in place — they must not inflate
|
|
6826
6846
|
// the unread-step counter.
|
|
@@ -8089,7 +8109,8 @@
|
|
|
8089
8109
|
detail: event.detail,
|
|
8090
8110
|
timestamp: event.timestamp || new Date().toISOString(),
|
|
8091
8111
|
kind: event.kind || 'activity',
|
|
8092
|
-
technicalDetail: event.technicalDetail
|
|
8112
|
+
technicalDetail: event.technicalDetail,
|
|
8113
|
+
guidanceId: sanitizeAssistantVisibleText(event.guidanceId)
|
|
8093
8114
|
};
|
|
8094
8115
|
record.events = [...(record.events || []), normalized].slice(-MAX_RUN_EVENTS);
|
|
8095
8116
|
saveStateSoon();
|