codex-overleaf-link 2.5.0 → 2.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +38 -26
- package/README.zh-CN.md +39 -27
- package/extension/bootstrap/background.js +52 -9
- package/extension/bootstrap/manifest.template.json +1 -1
- package/extension/bootstrap/popup.html +1 -0
- package/extension/bootstrap/popupFallback.js +55 -0
- package/extension/bootstrap/updateStatus.js +1 -0
- package/extension/src/background.js +206 -53
- package/extension/src/backgroundBootstrapMigration.js +181 -0
- package/extension/src/backgroundSharedSessions.js +74 -14
- package/extension/src/backgroundUpdateCoordinator.js +118 -21
- package/extension/src/backgroundUpdateSafety.js +173 -0
- package/extension/src/content/activeTurnControl.js +8 -2
- package/extension/src/content/applyResultFormatters.js +46 -73
- package/extension/src/content/assetTransferBroker.js +72 -14
- package/extension/src/content/composerAttachments.js +93 -45
- package/extension/src/content/composerPanel.js +18 -0
- package/extension/src/content/contentRuntime.js +661 -676
- package/extension/src/content/diagnosticsController.js +122 -96
- package/extension/src/content/diagnosticsExport.js +19 -5
- package/extension/src/content/diagnosticsPanel.js +30 -0
- package/extension/src/content/directoryCleanupRecovery.js +54 -0
- package/extension/src/content/generated/content.bundle.js +6879 -2786
- package/extension/src/content/generated/content.bundle.meta.json +11 -3
- package/extension/src/content/globalPreferencesController.js +1 -1
- package/extension/src/content/legacyGlobalRegistry.js +6 -0
- package/extension/src/content/localSkillsPanel.js +102 -12
- package/extension/src/content/markdownText.js +5 -6
- package/extension/src/content/modelPicker.js +7 -4
- package/extension/src/content/nativeCompatibilityController.js +45 -20
- package/extension/src/content/otWarmMirror.js +4 -5
- package/extension/src/content/otWarmMirrorController.js +17 -20
- package/extension/src/content/pageBridgeClient.js +15 -2
- package/extension/src/content/panelMaintenance.js +40 -12
- package/extension/src/content/panelRenderer.js +6 -0
- package/extension/src/content/projectProviderSelection.js +13 -4
- package/extension/src/content/projectSessionCleanup.js +109 -62
- package/extension/src/content/projectSettingsCoordinator.js +39 -2
- package/extension/src/content/providerSettingsCoordinator.js +46 -8
- package/extension/src/content/providerSettingsDialog.js +71 -13
- package/extension/src/content/recentProjects.js +11 -5
- package/extension/src/content/runActivitySummary.js +40 -6
- package/extension/src/content/runCompletionMeta.js +344 -0
- package/extension/src/content/runController.js +37 -14
- package/extension/src/content/runFailureNotice.js +21 -12
- package/extension/src/content/runGuidanceController.js +72 -27
- package/extension/src/content/runGuidanceView.js +59 -10
- package/extension/src/content/runQueueScheduler.js +148 -42
- package/extension/src/content/runRecoveryActions.js +146 -0
- package/extension/src/content/runResultActions.js +8 -125
- package/extension/src/content/runReviewController.js +574 -0
- package/extension/src/content/runScrollLayout.js +21 -2
- package/extension/src/content/runTimelineView.js +146 -153
- package/extension/src/content/scopedPersistencePanelState.js +51 -6
- package/extension/src/content/selectionContextView.js +1 -1
- package/extension/src/content/sensitivePreflight.js +140 -0
- package/extension/src/content/sessionForkController.js +80 -17
- package/extension/src/content/sessionManager.js +1 -1
- package/extension/src/content/settingsPanel.js +7 -3
- package/extension/src/content/settingsWorkbench.js +5 -0
- package/extension/src/content/sharedSessionViewSync.js +45 -8
- package/extension/src/content/updateIdle.js +4 -1
- package/extension/src/content/updateNotice.js +60 -8
- package/extension/src/content/updateSafetyReceiver.js +58 -0
- package/extension/src/content/writebackController.js +53 -4
- package/extension/src/content/writebackOrchestrator.js +82 -73
- package/extension/src/content/writingStyleSettings.js +35 -11
- package/extension/src/page/binaryAssetUploader.js +136 -14
- package/extension/src/page/fileMover.js +256 -0
- package/extension/src/page/ownedDirectoryCleanup.js +94 -0
- package/extension/src/page/referenceProjects.js +1 -0
- package/extension/src/page/reviewContext.js +113 -0
- package/extension/src/page/saveState.js +6 -4
- package/extension/src/page/snapshotRouter.js +84 -39
- package/extension/src/page/textFileCreator.js +105 -25
- package/extension/src/page/trackedChangeCapture.js +12 -2
- package/extension/src/page/trackedChangeOwnership.js +19 -1
- package/extension/src/page/trackedChangeReplay.js +41 -21
- package/extension/src/page/trackedChangesLifecycle.js +217 -199
- package/extension/src/page/treeOperations.js +7 -10
- package/extension/src/page/updateSafetyProbe.js +41 -0
- package/extension/src/page/writebackReceiptJournal.js +2 -0
- package/extension/src/page/writebackRouter.js +80 -79
- package/extension/src/pageBridge.js +19 -3
- package/extension/src/popup.js +62 -10
- package/extension/src/shared/agentTranscript.js +23 -0
- package/extension/src/shared/auditRecords.js +2 -0
- package/extension/src/shared/compatibility.js +22 -6
- package/extension/src/shared/compileAdapter.js +79 -57
- package/extension/src/shared/composerAttachmentDraft.js +151 -0
- package/extension/src/shared/directoryCleanupProof.js +43 -0
- package/extension/src/shared/failureReasons.js +309 -3
- package/extension/src/shared/globalPreferences.js +30 -13
- package/extension/src/shared/governanceRules.js +4 -37
- package/extension/src/shared/i18n.js +298 -2
- package/extension/src/shared/managedUpdateProjection.js +2 -2
- package/extension/src/shared/nativeRequestIdentity.js +1 -0
- package/extension/src/shared/pageRpcContract.js +5 -4
- package/extension/src/shared/projectFiles.js +21 -44
- package/extension/src/shared/providerProfiles.js +5 -3
- package/extension/src/shared/runInputQueue.js +48 -5
- package/extension/src/shared/selectionContext.js +12 -3
- package/extension/src/shared/sessionState.js +72 -27
- package/extension/src/shared/settlementFacts.js +10 -2
- package/extension/src/shared/sharedSessionBridge.js +37 -14
- package/extension/src/shared/sharedSessionTransfer.js +140 -0
- package/extension/src/shared/staleGuard.js +118 -13
- package/extension/src/shared/storageDb.js +58 -26
- package/extension/src/shared/storageMigration.js +71 -111
- package/extension/src/shared/storageRunActions.js +209 -26
- package/extension/src/shared/subagentActivityModel.js +1 -1
- package/extension/src/shared/undoOperations.js +136 -13
- package/extension/src/shared/updateConsent.js +6 -4
- package/extension/src/shared/updateRuntimeIdentity.js +1 -1
- package/extension/src/shared/writebackIntent.js +16 -0
- package/extension/src/shared/writebackOperationEvidence.js +4 -15
- package/extension/src/shared/writebackSettlement.js +38 -38
- package/extension/styles/panel.css +19 -5
- package/native-host/src/anthropicBridgeRequest.js +4 -2
- package/native-host/src/anthropicBridgeResponse.js +6 -2
- package/native-host/src/anthropicMessagesBridge.js +24 -16
- package/native-host/src/bootstrapMigration.js +296 -0
- package/native-host/src/bootstrapMigrationContract.js +101 -0
- package/native-host/src/bootstrapUpdate.js +233 -0
- package/native-host/src/bootstrapUpdateWatchdog.js +44 -0
- package/native-host/src/chatBridgeResponse.js +10 -0
- package/native-host/src/chatCompletionsBridge.js +21 -15
- package/native-host/src/codexConfigPaths.js +106 -0
- package/native-host/src/codexHome.js +66 -73
- package/native-host/src/codexHomeScope.js +124 -0
- package/native-host/src/codexInheritedAuth.js +90 -0
- package/native-host/src/codexLocalSkills.js +301 -0
- package/native-host/src/codexPromptAssembly.js +31 -12
- package/native-host/src/codexProviderTest.js +24 -26
- package/native-host/src/codexSessionRunner.js +110 -183
- package/native-host/src/codexSessionTiming.js +18 -8
- package/native-host/src/codexSkillIsolation.js +170 -0
- package/native-host/src/codexThreadFork.js +19 -3
- package/native-host/src/index.js +19 -7
- package/native-host/src/managedInstall.js +110 -35
- package/native-host/src/managedInstallRecovery.js +140 -0
- package/native-host/src/managedRuntimeEvidence.js +57 -0
- package/native-host/src/mirrorWorkspace.js +92 -22
- package/native-host/src/nativeDoctor.js +15 -0
- package/native-host/src/nativeHelperLifecycle.js +21 -0
- package/native-host/src/nativeHostRegistration.js +33 -0
- package/native-host/src/nativeRunLifecycle.js +71 -0
- package/native-host/src/processStopBarrier.js +42 -7
- package/native-host/src/projectPathPlatform.js +30 -0
- package/native-host/src/providerHttp.js +80 -0
- package/native-host/src/providerProfile.js +4 -2
- package/native-host/src/providerReasoning.js +2 -7
- package/native-host/src/providerRuntime.js +1 -0
- package/native-host/src/providerStore.js +72 -14
- package/native-host/src/responsesPassthroughBridge.js +266 -52
- package/native-host/src/runtimeInstaller.js +21 -1
- package/native-host/src/selectionScope.js +36 -8
- package/native-host/src/subagentAdmission.js +26 -0
- package/native-host/src/subagentBroker.js +79 -66
- package/native-host/src/subagentTelemetry.js +19 -10
- package/native-host/src/subagentWorkspacePath.js +5 -2
- package/native-host/src/taskRunnerRuntime.js +77 -54
- package/native-host/src/tomlStatements.js +140 -0
- package/native-host/src/updateManager.js +297 -39
- package/native-host/src/writingStyleRuntime.js +57 -29
- package/native-host/src/writingStyleStore.js +73 -0
- package/package.json +1 -1
- package/scripts/install-managed.mjs +1 -0
|
@@ -2,40 +2,85 @@
|
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
4
|
function create(deps = {}) {
|
|
5
|
+
// Unacknowledged input is a page-local projection. Only confirmed delivery
|
|
6
|
+
// joins record.events; the durable queue owns refresh/recovery before that.
|
|
7
|
+
const pending = new Map();
|
|
8
|
+
let lastTimestamp = 0;
|
|
9
|
+
const keyOf = target => JSON.stringify([target.sessionId, target.recordId, target.guidanceId]);
|
|
10
|
+
const sameScope = scope => !scope || ['accountScopeId', 'projectId', 'generation'].every(key => scope[key] === deps.getScope?.()?.[key]);
|
|
11
|
+
function ownedRecord(target) {
|
|
12
|
+
if (!target.sessionId || !target.recordId) return null;
|
|
13
|
+
if (target.sessionCreatedAt !== undefined && deps.getSessionCreatedAt
|
|
14
|
+
&& deps.getSessionCreatedAt(target.sessionId) !== target.sessionCreatedAt) return null;
|
|
15
|
+
return deps.findRunRecord?.(target.recordId, target.sessionId) || null;
|
|
16
|
+
}
|
|
17
|
+
function nextTimestamp() {
|
|
18
|
+
lastTimestamp = Math.max(Date.now(), lastTimestamp + 1);
|
|
19
|
+
return new Date(lastTimestamp).toISOString();
|
|
20
|
+
}
|
|
21
|
+
function getPending(run) {
|
|
22
|
+
return [...pending.values()].filter(token => token.target.recordId === run?.id
|
|
23
|
+
&& (!deps.getActiveSessionId || token.target.sessionId === deps.getActiveSessionId())
|
|
24
|
+
&& sameScope(token.scope) && ownedRecord(token.target)).map(token => token.event);
|
|
25
|
+
}
|
|
26
|
+
function refresh(target) {
|
|
27
|
+
const record = ownedRecord(target);
|
|
28
|
+
if (!record) return null;
|
|
29
|
+
const view = deps.getCurrentRunView?.();
|
|
30
|
+
const root = deps.getRunRoot?.(target) || (view?.recordId === target.recordId
|
|
31
|
+
&& view?.sessionId === target.sessionId ? view.root : null);
|
|
32
|
+
const container = root?.querySelector('[data-run-guidance]');
|
|
33
|
+
deps.RunGuidanceView?.reconcileGuidance(container,
|
|
34
|
+
(record.events || []).concat(getPending(record)), deps.tx, { active: record.status === 'running' });
|
|
35
|
+
return container;
|
|
36
|
+
}
|
|
37
|
+
function refreshSettled(target) {
|
|
38
|
+
try { refresh(target); } catch (error) { deps.onPresentationError?.(error); }
|
|
39
|
+
}
|
|
40
|
+
function preview(text, target = {}) {
|
|
41
|
+
const title = String(text || '').trim();
|
|
42
|
+
if (!title || !target.guidanceId || !ownedRecord(target)) return null;
|
|
43
|
+
const token = { target: { ...target }, scope: deps.getScope?.(), event: { title, status: 'steering', kind: 'guidance',
|
|
44
|
+
guidanceId: target.guidanceId, timestamp: nextTimestamp(), guidancePending: true } };
|
|
45
|
+
pending.set(keyOf(target), token);
|
|
46
|
+
try {
|
|
47
|
+
const container = refresh(target);
|
|
48
|
+
const row = container?.querySelector?.(`[data-guidance-id="${deps.cssEscape?.(target.guidanceId) || target.guidanceId}"]`);
|
|
49
|
+
if (row) deps.revealGuidance?.(row);
|
|
50
|
+
deps.bumpUnreadIfDetached?.();
|
|
51
|
+
} catch (error) {
|
|
52
|
+
if (pending.get(keyOf(target)) === token) pending.delete(keyOf(target));
|
|
53
|
+
try { refresh(target); } catch { /* preserve the original presentation error */ }
|
|
54
|
+
throw error;
|
|
55
|
+
}
|
|
56
|
+
return token;
|
|
57
|
+
}
|
|
58
|
+
function clearPreview(token) {
|
|
59
|
+
if (!token || pending.get(keyOf(token.target)) !== token) return;
|
|
60
|
+
pending.delete(keyOf(token.target));
|
|
61
|
+
if (sameScope(token.scope)) refreshSettled(token.target);
|
|
62
|
+
}
|
|
5
63
|
function upsert(text, target = {}) {
|
|
6
|
-
const record =
|
|
64
|
+
const record = ownedRecord(target);
|
|
7
65
|
const guidanceId = String(target.guidanceId || '').trim();
|
|
8
66
|
if (!record || !guidanceId) return null;
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
return event;
|
|
20
|
-
}
|
|
21
|
-
Object.assign(existing, event, { timestamp: existing.timestamp || event.timestamp });
|
|
22
|
-
const view = deps.getCurrentRunView?.();
|
|
23
|
-
if (view?.recordId === target.recordId) {
|
|
24
|
-
view.root?.querySelector(`[data-guidance-id="${deps.cssEscape(guidanceId)}"]`)
|
|
25
|
-
?.replaceWith(deps.renderRunEvent(existing));
|
|
26
|
-
}
|
|
27
|
-
return existing;
|
|
67
|
+
const existing = (record.events || []).find(entry => entry.kind === 'guidance' && entry.guidanceId === guidanceId);
|
|
68
|
+
const event = { title: String(text || '').trim(), status: target.status || 'completed', kind: 'guidance', guidanceId,
|
|
69
|
+
timestamp: existing?.timestamp || target.timestamp || pending.get(keyOf(target))?.event.timestamp || nextTimestamp() };
|
|
70
|
+
if (existing) Object.assign(existing, event);
|
|
71
|
+
else record.events = [...(record.events || []), event].slice(-(deps.maxRunEvents || 300));
|
|
72
|
+
// A late ACK still belongs to the captured run, even if another run is
|
|
73
|
+
// now active. The coordinator saves it together with queue removal.
|
|
74
|
+
deps.onRecordChanged?.(target);
|
|
75
|
+
refreshSettled(target);
|
|
76
|
+
return existing || event;
|
|
28
77
|
}
|
|
29
|
-
|
|
30
78
|
function remove(guidanceId, target = {}) {
|
|
31
|
-
const record =
|
|
79
|
+
const record = ownedRecord(target);
|
|
32
80
|
if (!record || !guidanceId) return false;
|
|
33
81
|
const before = (record.events || []).length;
|
|
34
82
|
record.events = (record.events || []).filter(event => event.guidanceId !== guidanceId);
|
|
35
|
-
|
|
36
|
-
if (view?.recordId === target.recordId) {
|
|
37
|
-
view.root?.querySelector(`[data-guidance-id="${deps.cssEscape(guidanceId)}"]`)?.remove();
|
|
38
|
-
}
|
|
83
|
+
refreshSettled(target);
|
|
39
84
|
return record.events.length !== before;
|
|
40
85
|
}
|
|
41
86
|
|
|
@@ -64,7 +109,7 @@
|
|
|
64
109
|
return true;
|
|
65
110
|
}
|
|
66
111
|
|
|
67
|
-
return { upsert, remove, settleView, appendToView };
|
|
112
|
+
return { preview, clearPreview, getPending, upsert, remove, settleView, appendToView };
|
|
68
113
|
}
|
|
69
114
|
|
|
70
115
|
window.CodexOverleafRunGuidanceController = { create };
|
|
@@ -26,23 +26,72 @@
|
|
|
26
26
|
const row = document.createElement('div');
|
|
27
27
|
row.className = 'run-guidance-message run-user-message';
|
|
28
28
|
row.dataset.kind = 'guidance';
|
|
29
|
-
row.dataset.status = event.status || 'completed';
|
|
30
|
-
if (event.guidanceId) {
|
|
31
|
-
row.dataset.guidanceId = event.guidanceId;
|
|
32
|
-
}
|
|
33
29
|
row.setAttribute('role', 'group');
|
|
34
|
-
row.setAttribute('aria-label', tx('Follow-up guidance', '\u540e\u7eed\u5f15\u5bfc'));
|
|
35
|
-
|
|
36
30
|
const text = document.createElement('div');
|
|
37
31
|
text.className = 'run-guidance-text';
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
row.append(text);
|
|
32
|
+
const label = document.createElement('div');
|
|
33
|
+
label.className = 'run-guidance-label';
|
|
34
|
+
row.append(text, label);
|
|
35
|
+
updateGuidanceMessage(row, event, guidanceText, tx);
|
|
41
36
|
return row;
|
|
42
37
|
}
|
|
43
38
|
|
|
39
|
+
function updateGuidanceMessage(row, event, guidanceText, tx, { active = true } = {}) {
|
|
40
|
+
row.dataset.status = event.status || 'completed';
|
|
41
|
+
if (event.guidanceId) row.dataset.guidanceId = event.guidanceId;
|
|
42
|
+
row.dataset.guidanceTimestamp = event.timestamp || '';
|
|
43
|
+
const label = tx('Follow-up guidance', '补充指令');
|
|
44
|
+
const sending = (active || event.guidancePending === true) && ['queued', 'steering'].includes(event.status);
|
|
45
|
+
const text = row.querySelector('.run-guidance-text');
|
|
46
|
+
if (text.textContent !== guidanceText) text.textContent = guidanceText;
|
|
47
|
+
const status = row.querySelector('.run-guidance-label');
|
|
48
|
+
const statusText = label + (sending ? tx(' · Sending…', ' · 发送中…') : '');
|
|
49
|
+
if (status.textContent !== statusText) status.textContent = statusText;
|
|
50
|
+
row.setAttribute('aria-label', statusText);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Stored events and the controller's in-flight projection share a container.
|
|
54
|
+
// Confirmation updates the same row; removed previews leave no stale bubble.
|
|
55
|
+
function reconcileGuidance(container, events, tx, options = {}) {
|
|
56
|
+
if (!container) return;
|
|
57
|
+
const completedLegacy = new Set(events.filter(event => !event.guidanceId && event.status === 'completed')
|
|
58
|
+
.map(getGuidanceText).filter(Boolean));
|
|
59
|
+
const entries = new Map();
|
|
60
|
+
for (const [index, event] of events.entries()) {
|
|
61
|
+
const text = getGuidanceText(event);
|
|
62
|
+
if (!text || (!event.guidanceId && event.status === 'queued' && completedLegacy.has(text))) continue;
|
|
63
|
+
const key = event.guidanceId ? 'id:' + event.guidanceId : 'legacy:' + index;
|
|
64
|
+
const previous = entries.get(key);
|
|
65
|
+
if (previous && event.guidancePending === true && previous.event.guidancePending !== true) continue;
|
|
66
|
+
entries.set(key, { key, event: previous ? { ...event, timestamp: previous.event.timestamp || event.timestamp } : event, text });
|
|
67
|
+
}
|
|
68
|
+
const existing = new Map();
|
|
69
|
+
for (const row of [...container.children]) {
|
|
70
|
+
const key = row.dataset.guidanceId ? 'id:' + row.dataset.guidanceId : row.dataset.guidanceKey;
|
|
71
|
+
if (!key || existing.has(key)) { row.remove(); continue; }
|
|
72
|
+
existing.set(key, row);
|
|
73
|
+
}
|
|
74
|
+
for (const [key, row] of existing) if (!entries.has(key)) row.remove();
|
|
75
|
+
const known = [...entries.values()].filter(entry => Number.isFinite(Date.parse(entry.event.timestamp)))
|
|
76
|
+
.sort((a, b) => Date.parse(a.event.timestamp) - Date.parse(b.event.timestamp));
|
|
77
|
+
let datedIndex = 0;
|
|
78
|
+
// Keep legacy rows without timestamps in their event-order slots.
|
|
79
|
+
const ordered = [...entries.values()].map(entry => Number.isFinite(Date.parse(entry.event.timestamp)) ? known[datedIndex++] : entry);
|
|
80
|
+
let cursor = container.firstChild;
|
|
81
|
+
for (const entry of ordered) {
|
|
82
|
+
const row = existing.get(entry.key) || renderGuidanceMessage(entry.event, entry.text, tx);
|
|
83
|
+
row.dataset.guidanceKey = entry.key;
|
|
84
|
+
if (entry.event.guidancePending === true) row.dataset.guidancePending = 'true';
|
|
85
|
+
else delete row.dataset.guidancePending;
|
|
86
|
+
updateGuidanceMessage(row, entry.event, entry.text, tx, options);
|
|
87
|
+
if (row !== cursor) container.insertBefore(row, cursor);
|
|
88
|
+
cursor = row.nextSibling;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
44
92
|
window.CodexOverleafRunGuidanceView = {
|
|
45
93
|
getGuidanceText,
|
|
46
|
-
renderGuidanceMessage
|
|
94
|
+
renderGuidanceMessage,
|
|
95
|
+
reconcileGuidance
|
|
47
96
|
};
|
|
48
97
|
})();
|
|
@@ -171,6 +171,25 @@
|
|
|
171
171
|
|
|
172
172
|
function createCoordinator(options = {}) {
|
|
173
173
|
let view = null;
|
|
174
|
+
const guidanceAttempts = new Map();
|
|
175
|
+
const guidanceKey = (sessionId, itemId) => JSON.stringify([sessionId, itemId]);
|
|
176
|
+
const matchesGuidanceItem = (item, identity) => item?.id === identity.itemId
|
|
177
|
+
&& item.createdAt === identity.itemCreatedAt && item.text === identity.text
|
|
178
|
+
&& item.clientUserMessageId === identity.clientUserMessageId;
|
|
179
|
+
const matchesGuidanceScope = expected => {
|
|
180
|
+
const scope = options.getGuideScope?.();
|
|
181
|
+
return !expected || ['projectId', 'accountScopeId', 'generation'].every(key => scope?.[key] === expected[key]);
|
|
182
|
+
};
|
|
183
|
+
const guideErrorKeys = {
|
|
184
|
+
codex_turn_not_ready: 'queuedInputGuideNotReady',
|
|
185
|
+
codex_turn_mismatch: 'queuedInputGuideTurnChanged',
|
|
186
|
+
codex_steer_empty: 'queuedInputGuideEmpty',
|
|
187
|
+
codex_steer_too_large: 'queuedInputGuideTooLarge'
|
|
188
|
+
};
|
|
189
|
+
function formatGuideError(error) {
|
|
190
|
+
const key = guideErrorKeys[error?.code];
|
|
191
|
+
return (key && options.tr?.(key)) || error?.message || options.tr?.('queuedInputGuideFailed');
|
|
192
|
+
}
|
|
174
193
|
const scheduler = create({
|
|
175
194
|
getQueue: sessionId => options.getSession?.(sessionId)?.pendingInputs || [],
|
|
176
195
|
setQueue: (sessionId, pendingInputs) => options.setQueue?.(sessionId, pendingInputs),
|
|
@@ -226,62 +245,143 @@
|
|
|
226
245
|
const item = (session?.pendingInputs || []).find(entry => entry.id === itemId);
|
|
227
246
|
const run = options.getCurrentRun?.();
|
|
228
247
|
const binding = run?.activeTurn;
|
|
229
|
-
|
|
248
|
+
const initialScope = options.getGuideScope?.();
|
|
249
|
+
const guideScope = initialScope ? { ...initialScope } : null;
|
|
250
|
+
if (!session || !item || item.status !== 'queued' || !run || run.sessionId !== session.id || !binding?.threadId || !binding?.turnId) {
|
|
230
251
|
options.toast?.(options.tr?.('queuedInputGuideUnavailable'), 'warning');
|
|
231
252
|
return;
|
|
232
253
|
}
|
|
254
|
+
const identity = { requestId: run.nativeRequestId, threadId: binding.threadId, turnId: binding.turnId,
|
|
255
|
+
projectKey: run.runProjectId, recordId: run.recordId, sessionId: session.id, sessionCreatedAt: session.createdAt,
|
|
256
|
+
itemId, itemCreatedAt: item.createdAt, text: item.text, clientUserMessageId: item.clientUserMessageId };
|
|
257
|
+
const attemptKey = guidanceKey(identity.sessionId, itemId);
|
|
258
|
+
const attempt = { identity, scope: guideScope, previewToken: null };
|
|
259
|
+
const sameScope = () => matchesGuidanceScope(guideScope);
|
|
260
|
+
const ownedSession = () => {
|
|
261
|
+
const latest = options.getSession?.(identity.sessionId);
|
|
262
|
+
return sameScope() && latest?.createdAt === identity.sessionCreatedAt ? latest : null;
|
|
263
|
+
};
|
|
264
|
+
const ownedItemSession = (allowPaused = false) => {
|
|
265
|
+
const latest = ownedSession();
|
|
266
|
+
const current = latest?.pendingInputs?.find(entry => entry.id === itemId);
|
|
267
|
+
return guidanceAttempts.get(attemptKey) === attempt
|
|
268
|
+
&& (current?.status === 'steering' || (allowPaused && current?.status === 'paused'))
|
|
269
|
+
&& matchesGuidanceItem(current, identity) ? latest : null;
|
|
270
|
+
};
|
|
271
|
+
function clearPreview() {
|
|
272
|
+
const token = attempt.previewToken;
|
|
273
|
+
attempt.previewToken = null;
|
|
274
|
+
if (token) options.clearGuidancePreview?.(token);
|
|
275
|
+
}
|
|
276
|
+
const assertCurrent = () => {
|
|
277
|
+
const currentRun = options.getCurrentRun?.();
|
|
278
|
+
if (options.isGuideCancelled?.() || currentRun !== run || options.getActiveSession?.()?.id !== identity.sessionId
|
|
279
|
+
|| currentRun?.nativeRequestId !== identity.requestId
|
|
280
|
+
|| currentRun?.recordId !== identity.recordId || currentRun?.runProjectId !== identity.projectKey
|
|
281
|
+
|| currentRun?.activeTurn?.threadId !== identity.threadId || currentRun?.activeTurn?.turnId !== identity.turnId
|
|
282
|
+
|| !ownedItemSession()) {
|
|
283
|
+
throw Object.assign(new Error(options.tr?.('queuedInputGuideUnavailable') || 'Guidance is no longer available for this turn.'), { code: 'codex_turn_mismatch' });
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
async function restoreUnsent(error) {
|
|
287
|
+
// No steer was dispatched. Restore only this attempt's still-owned item;
|
|
288
|
+
// a concurrent removal, replacement or scope switch must stay untouched.
|
|
289
|
+
clearPreview();
|
|
290
|
+
const latest = ownedItemSession();
|
|
291
|
+
if (latest) {
|
|
292
|
+
options.setQueue?.(identity.sessionId, Queue.returnToQueue(latest.pendingInputs, itemId));
|
|
293
|
+
render();
|
|
294
|
+
try {
|
|
295
|
+
await options.save?.({ queueMutation: { type: 'transition', sessionId: identity.sessionId, itemId } });
|
|
296
|
+
} catch (recoveryError) {
|
|
297
|
+
if (error && typeof error === 'object') error.persistenceRecoveryError = recoveryError?.message || String(recoveryError);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
233
301
|
options.setQueue?.(session.id, Queue.markSteering(session.pendingInputs, itemId));
|
|
234
|
-
|
|
235
|
-
await options.save?.({
|
|
236
|
-
queueMutation: { type: 'transition', sessionId: session.id, itemId }
|
|
237
|
-
});
|
|
302
|
+
guidanceAttempts.set(attemptKey, attempt);
|
|
238
303
|
try {
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
304
|
+
try {
|
|
305
|
+
attempt.previewToken = options.previewGuidance?.(identity.text, {
|
|
306
|
+
guidanceId: itemId, sessionId: identity.sessionId, recordId: identity.recordId,
|
|
307
|
+
sessionCreatedAt: identity.sessionCreatedAt
|
|
308
|
+
});
|
|
309
|
+
render();
|
|
310
|
+
const preflight = await options.preflightGuide?.({ task: identity.text, assertCurrent });
|
|
311
|
+
assertCurrent();
|
|
312
|
+
if (preflight?.blocked) throw Object.assign(new Error('Guidance was blocked before dispatch.'), { guidanceBlocked: true });
|
|
313
|
+
await options.save?.({
|
|
314
|
+
queueMutation: { type: 'transition', sessionId: identity.sessionId, itemId }
|
|
315
|
+
});
|
|
316
|
+
assertCurrent();
|
|
317
|
+
} catch (error) {
|
|
318
|
+
await restoreUnsent(error);
|
|
319
|
+
if (error?.guidanceBlocked) return { ok: false, blocked: true };
|
|
320
|
+
throw error;
|
|
321
|
+
}
|
|
322
|
+
let response;
|
|
323
|
+
try {
|
|
324
|
+
response = await options.sendSteer?.({
|
|
325
|
+
requestId: identity.requestId,
|
|
326
|
+
projectKey: identity.projectKey,
|
|
327
|
+
threadId: identity.threadId,
|
|
328
|
+
expectedTurnId: identity.turnId,
|
|
329
|
+
clientUserMessageId: identity.clientUserMessageId,
|
|
330
|
+
input: [{ type: 'text', text: identity.text }]
|
|
331
|
+
}, { assertCurrent });
|
|
332
|
+
} catch (error) {
|
|
333
|
+
if (error?.requestDispatched === false) { await restoreUnsent(error); throw error; }
|
|
334
|
+
clearPreview();
|
|
335
|
+
const latest = ownedItemSession(true);
|
|
336
|
+
if (latest) {
|
|
337
|
+
const paused = latest.pendingInputs.map(entry => entry.id === itemId
|
|
338
|
+
? { ...entry, status: 'paused', pauseReason: 'steer_delivery_unknown' }
|
|
339
|
+
: entry);
|
|
340
|
+
options.setQueue?.(identity.sessionId, Queue.normalizeQueue(paused));
|
|
341
|
+
}
|
|
342
|
+
options.toast?.(formatGuideError(error), 'warning');
|
|
343
|
+
render();
|
|
344
|
+
if (latest) await options.save?.({
|
|
345
|
+
queueMutation: { type: 'transition', sessionId: identity.sessionId, itemId }
|
|
346
|
+
});
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
// Cancellation can pause the owned row while a dispatched steer is
|
|
350
|
+
// awaiting acknowledgement. A confirmed receipt also consumes that
|
|
351
|
+
// paused copy, so Resume cannot send an already accepted input again.
|
|
352
|
+
const latest = ownedItemSession(Boolean(response?.ok));
|
|
353
|
+
if (latest) options.setQueue?.(
|
|
354
|
+
identity.sessionId,
|
|
355
|
+
response?.ok ? Queue.remove(latest.pendingInputs, itemId)
|
|
356
|
+
: Queue.returnToQueue(latest.pendingInputs, itemId, response?.error?.code || 'steer_failed')
|
|
253
357
|
);
|
|
254
358
|
if (response?.ok) {
|
|
255
|
-
if (typeof options.appendGuidance === 'function') {
|
|
256
|
-
options.appendGuidance(
|
|
257
|
-
guidanceId:
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
status: 'completed'
|
|
359
|
+
if (ownedSession() && typeof options.appendGuidance === 'function') {
|
|
360
|
+
options.appendGuidance(identity.text, {
|
|
361
|
+
guidanceId: itemId, sessionId: identity.sessionId, recordId: identity.recordId,
|
|
362
|
+
sessionCreatedAt: identity.sessionCreatedAt, status: 'completed',
|
|
363
|
+
timestamp: attempt.previewToken?.event?.timestamp
|
|
261
364
|
});
|
|
262
|
-
} else {
|
|
365
|
+
} else if (ownedSession() && options.getCurrentRun?.() === run) {
|
|
263
366
|
options.appendEvent?.(options.tr?.('queuedInputGuided'), 'completed');
|
|
264
367
|
}
|
|
265
368
|
} else {
|
|
266
|
-
options.toast?.(response?.error
|
|
369
|
+
options.toast?.(formatGuideError(response?.error), 'warning');
|
|
267
370
|
}
|
|
371
|
+
clearPreview();
|
|
268
372
|
render();
|
|
269
|
-
|
|
373
|
+
// A Native acknowledgement is final delivery evidence. A later local
|
|
374
|
+
// save failure must not turn it into an unknown delivery or resend it.
|
|
375
|
+
if (latest) await options.save?.({
|
|
270
376
|
queueMutation: response?.ok
|
|
271
|
-
? { type: 'remove', sessionId:
|
|
272
|
-
: { type: 'transition', sessionId:
|
|
377
|
+
? { type: 'remove', sessionId: identity.sessionId, itemId }
|
|
378
|
+
: { type: 'transition', sessionId: identity.sessionId, itemId }
|
|
273
379
|
});
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
: entry);
|
|
279
|
-
options.setQueue?.(session.id, Queue.normalizeQueue(paused));
|
|
280
|
-
options.toast?.(error?.message || options.tr?.('queuedInputGuideFailed'), 'warning');
|
|
380
|
+
else if (response?.ok && ownedSession()) await options.save?.();
|
|
381
|
+
} finally {
|
|
382
|
+
clearPreview();
|
|
383
|
+
if (guidanceAttempts.get(attemptKey) === attempt) guidanceAttempts.delete(attemptKey);
|
|
281
384
|
render();
|
|
282
|
-
await options.save?.({
|
|
283
|
-
queueMutation: { type: 'transition', sessionId: session.id, itemId }
|
|
284
|
-
});
|
|
285
385
|
}
|
|
286
386
|
}
|
|
287
387
|
|
|
@@ -320,7 +420,7 @@
|
|
|
320
420
|
view = options.createView?.({
|
|
321
421
|
container,
|
|
322
422
|
tr: options.tr,
|
|
323
|
-
onGuide: itemId => guide(itemId).catch(error => options.toast?.(error
|
|
423
|
+
onGuide: itemId => guide(itemId).catch(error => options.toast?.(formatGuideError(error), 'warning')),
|
|
324
424
|
onRemove: remove,
|
|
325
425
|
onResume: resume,
|
|
326
426
|
onToast: message => options.toast?.(message, 'warning')
|
|
@@ -331,7 +431,13 @@
|
|
|
331
431
|
}
|
|
332
432
|
const session = options.getActiveSession?.();
|
|
333
433
|
const run = options.getCurrentRun?.();
|
|
334
|
-
|
|
434
|
+
const visibleQueue = (session?.pendingInputs || []).filter(item => {
|
|
435
|
+
const attempt = guidanceAttempts.get(guidanceKey(session.id, item.id));
|
|
436
|
+
return !attempt?.previewToken || !['steering', 'paused'].includes(item.status)
|
|
437
|
+
|| !matchesGuidanceScope(attempt.scope)
|
|
438
|
+
|| session.createdAt !== attempt.identity.sessionCreatedAt || !matchesGuidanceItem(item, attempt.identity);
|
|
439
|
+
});
|
|
440
|
+
view?.render?.(visibleQueue, {
|
|
335
441
|
running: Boolean(run),
|
|
336
442
|
canGuide: Boolean(run?.activeTurn?.turnId)
|
|
337
443
|
});
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
(function (root, factory) {
|
|
2
|
+
if (typeof module === 'object' && module.exports) module.exports = factory();
|
|
3
|
+
else root.CodexOverleafModuleRegistry.define('RunRecoveryActions', [], factory);
|
|
4
|
+
})(typeof window !== 'undefined' ? window : globalThis, function () {
|
|
5
|
+
'use strict';
|
|
6
|
+
|
|
7
|
+
function formatForceCancelOutcome(response, tx) {
|
|
8
|
+
if (response?.ok !== true) return tx('Release failed — refresh the tab, then try again.', '释放失败,请刷新页面后重试。');
|
|
9
|
+
const result = response.result || response;
|
|
10
|
+
if (result.lockReleased === true) return tx('Force-released — you can retry the run.', '已释放,可以重试本轮任务。');
|
|
11
|
+
if (result.cancelled === true) return tx('Cancellation requested. Wait for the task to stop before retrying.', '已请求取消,请等待任务停止后再重试。');
|
|
12
|
+
if (result.cancelled === false) return tx('No active task matched the release request.', '没有发现与释放请求匹配的活动任务。');
|
|
13
|
+
if (result.sent === true) return tx('Cancellation request sent; release is not yet confirmed.', '取消请求已发送,释放状态尚未确认。');
|
|
14
|
+
return tx('Release is not yet confirmed. Check the current task before retrying.', '释放状态尚未确认,请检查当前任务后再重试。');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function create(deps = {}) {
|
|
18
|
+
const { tx, failureNotice, refillComposerForRetry, openProjectFileForFailure, showNativeSetupGuidance,
|
|
19
|
+
openStorageSettings, forceCancelStuckTaskForCurrentProject } = deps;
|
|
20
|
+
const document = deps.document || globalThis.document;
|
|
21
|
+
const window = deps.window || globalThis.window;
|
|
22
|
+
|
|
23
|
+
const RETRYABLE_FAILURE_CODES = new Set([
|
|
24
|
+
'native_request_failed', 'codex_timeout', 'codex_no_usable_result',
|
|
25
|
+
'codex_output_limit', 'stale_source_changed', 'patch_anchor_not_found',
|
|
26
|
+
'target_editor_not_ready', 'write_timeout', 'partial_write_needs_review',
|
|
27
|
+
'write_operation_failed'
|
|
28
|
+
]);
|
|
29
|
+
const NATIVE_SETUP_FAILURE_CODES = new Set([
|
|
30
|
+
'native_bridge_unavailable', 'native_protocol_incompatible'
|
|
31
|
+
]);
|
|
32
|
+
const OPEN_FILE_FAILURE_CODES = new Set([
|
|
33
|
+
'target_file_not_active', 'target_file_open_failed', 'target_file_not_found'
|
|
34
|
+
]);
|
|
35
|
+
const STORAGE_FAILURE_CODES = new Set([
|
|
36
|
+
'storage_quota_exceeded', 'run_state_persist_failed'
|
|
37
|
+
]);
|
|
38
|
+
// A missing `codex` binary is fixed in the terminal, not the panel — the
|
|
39
|
+
// action opens the README troubleshooting section (PATH / login checks).
|
|
40
|
+
const CODEX_INSTALL_FAILURE_CODES = new Set(['codex_not_found']);
|
|
41
|
+
const CODEX_CLI_TROUBLESHOOTING_URL = 'https://github.com/Ghqqqq/codex-overleaf-link#faq-and-troubleshooting';
|
|
42
|
+
|
|
43
|
+
function buildRecoveryButton(failureCode, label, title) {
|
|
44
|
+
const button = document.createElement('button');
|
|
45
|
+
button.type = 'button';
|
|
46
|
+
button.className = 'run-final-answer__recovery-action';
|
|
47
|
+
button.dataset.recoveryFor = failureCode;
|
|
48
|
+
button.textContent = label;
|
|
49
|
+
if (title) {
|
|
50
|
+
button.title = title;
|
|
51
|
+
}
|
|
52
|
+
return button;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function appendEditAndResend(report, run, actionKey) {
|
|
56
|
+
const retry = buildRecoveryButton(actionKey,
|
|
57
|
+
tx('Edit & resend', '编辑后重发'),
|
|
58
|
+
tx('Put this run\u2019s task back into the composer so you can adjust and resend it.', '把本轮任务填回输入框,修改后可直接重发。'));
|
|
59
|
+
retry.addEventListener('click', clickEvent => {
|
|
60
|
+
clickEvent.stopPropagation();
|
|
61
|
+
refillComposerForRetry(run || null);
|
|
62
|
+
});
|
|
63
|
+
report.append(retry);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function appendRecoveryActionForFailure(report, event, run) {
|
|
67
|
+
const recovery = failureNotice?.prepareRecovery(report, { openFile: openProjectFileForFailure });
|
|
68
|
+
const failure = recovery?.failure || event?.failure;
|
|
69
|
+
const failureCode = failure?.code || '';
|
|
70
|
+
if (recovery?.handled) return;
|
|
71
|
+
report = recovery?.target || report;
|
|
72
|
+
if (RETRYABLE_FAILURE_CODES.has(failureCode) && typeof refillComposerForRetry === 'function') {
|
|
73
|
+
appendEditAndResend(report, run, failureCode);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
if (NATIVE_SETUP_FAILURE_CODES.has(failureCode) && typeof showNativeSetupGuidance === 'function') {
|
|
77
|
+
const fix = buildRecoveryButton(failureCode,
|
|
78
|
+
tx('Fix the native host', '修复 native host'),
|
|
79
|
+
tx('Opens the install/update guidance with the copyable command.', '打开安装/更新指引(含可复制命令)。'));
|
|
80
|
+
fix.addEventListener('click', clickEvent => {
|
|
81
|
+
clickEvent.stopPropagation();
|
|
82
|
+
showNativeSetupGuidance();
|
|
83
|
+
});
|
|
84
|
+
report.append(fix);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
if (OPEN_FILE_FAILURE_CODES.has(failureCode) && failure?.file && typeof openProjectFileForFailure === 'function') {
|
|
88
|
+
const open = buildRecoveryButton(failureCode,
|
|
89
|
+
tx(`Open ${failure.file} in Overleaf`, `在 Overleaf 打开 ${failure.file}`), '');
|
|
90
|
+
open.addEventListener('click', clickEvent => {
|
|
91
|
+
clickEvent.stopPropagation();
|
|
92
|
+
openProjectFileForFailure(failure.file);
|
|
93
|
+
});
|
|
94
|
+
report.append(open);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
if (STORAGE_FAILURE_CODES.has(failureCode) && typeof openStorageSettings === 'function') {
|
|
98
|
+
const clean = buildRecoveryButton(failureCode,
|
|
99
|
+
tx('Open history & storage cleanup', '打开历史与存储清理'), '');
|
|
100
|
+
clean.addEventListener('click', clickEvent => {
|
|
101
|
+
clickEvent.stopPropagation();
|
|
102
|
+
openStorageSettings();
|
|
103
|
+
});
|
|
104
|
+
report.append(clean);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (CODEX_INSTALL_FAILURE_CODES.has(failureCode)) {
|
|
108
|
+
const guide = buildRecoveryButton(failureCode,
|
|
109
|
+
tx('Codex CLI troubleshooting guide', '查看 Codex CLI 排查指引'),
|
|
110
|
+
tx('Opens the README section on fixing a missing `codex` command (PATH / login).', '打开 README 中「找不到 codex 命令」的排查说明(PATH/登录)。'));
|
|
111
|
+
guide.addEventListener('click', clickEvent => {
|
|
112
|
+
clickEvent.stopPropagation();
|
|
113
|
+
window.open(CODEX_CLI_TROUBLESHOOTING_URL, '_blank', 'noopener');
|
|
114
|
+
});
|
|
115
|
+
report.append(guide);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
if (failureCode !== 'codex_project_locked') return;
|
|
119
|
+
const button = document.createElement('button');
|
|
120
|
+
button.type = 'button';
|
|
121
|
+
button.className = 'run-final-answer__recovery-action';
|
|
122
|
+
button.dataset.recoveryFor = failureCode;
|
|
123
|
+
button.textContent = tx('Force-release the stuck task', '强制释放卡住的任务');
|
|
124
|
+
button.title = tx(
|
|
125
|
+
'Requests cancellation for this project. A sent request does not confirm that the task has stopped. Use this when refreshing the tab did not free the project.',
|
|
126
|
+
'请求取消当前项目的任务;请求已发送不代表任务已经停止。刷新页面仍未解除占用时使用。'
|
|
127
|
+
);
|
|
128
|
+
button.addEventListener('click', event => {
|
|
129
|
+
event.stopPropagation();
|
|
130
|
+
button.disabled = true;
|
|
131
|
+
button.textContent = tx('Releasing…', '正在释放…');
|
|
132
|
+
forceCancelStuckTaskForCurrentProject().then(result => {
|
|
133
|
+
button.textContent = formatForceCancelOutcome(result, tx);
|
|
134
|
+
button.disabled = result?.ok === true;
|
|
135
|
+
}).catch(() => {
|
|
136
|
+
button.disabled = false;
|
|
137
|
+
button.textContent = tx('Release failed — refresh the tab, then try again.', '释放失败,请刷新页面后重试。');
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
report.append(button);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return { buildRecoveryButton, appendRecoveryActionForFailure };
|
|
144
|
+
}
|
|
145
|
+
return { create, formatForceCancelOutcome };
|
|
146
|
+
});
|