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
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
(function initCodexOverleafOtWarmMirrorController(root, factory) {
|
|
2
2
|
if (typeof module === 'object' && module.exports) {
|
|
3
|
-
module.exports = factory();
|
|
3
|
+
module.exports = factory(require('../shared/projectFiles'));
|
|
4
4
|
} else {
|
|
5
|
-
root.
|
|
5
|
+
root.CodexOverleafModuleRegistry.define('OtWarmMirrorController', ['ProjectFiles'], factory);
|
|
6
6
|
}
|
|
7
|
-
})(typeof window !== 'undefined' ? window : globalThis, function otWarmMirrorControllerFactory() {
|
|
7
|
+
})(typeof window !== 'undefined' ? window : globalThis, function otWarmMirrorControllerFactory(projectFiles) {
|
|
8
8
|
'use strict';
|
|
9
9
|
|
|
10
10
|
const OT_POLL_INTERVAL_MS = 1000;
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
const OT_MAX_BATCH_BYTES = 2 * 1024 * 1024;
|
|
18
18
|
const encoder = typeof TextEncoder === 'function' ? new TextEncoder() : null;
|
|
19
19
|
const PAUSE_STATES = new Set(['running', 'writing', 'undoing', 'compiling']);
|
|
20
|
+
const normalizeLiteralPath = projectFiles.normalizeSafeProjectPath;
|
|
20
21
|
|
|
21
22
|
function buildPatchFilesRequest({ projectId, events, nativeCompatibility } = {}) {
|
|
22
23
|
const files = [];
|
|
@@ -24,8 +25,8 @@
|
|
|
24
25
|
if (files.length >= OT_MAX_PATCH_BATCH) {
|
|
25
26
|
break;
|
|
26
27
|
}
|
|
27
|
-
const filePath =
|
|
28
|
-
if (!
|
|
28
|
+
const filePath = normalizeLiteralPath(event?.path);
|
|
29
|
+
if (!filePath || typeof event?.baseHash !== 'string') {
|
|
29
30
|
continue;
|
|
30
31
|
}
|
|
31
32
|
const baseHash = event.baseHash.trim();
|
|
@@ -73,7 +74,7 @@
|
|
|
73
74
|
return { pause: false };
|
|
74
75
|
}
|
|
75
76
|
|
|
76
|
-
function canUseOtWarmStart({ enabled, focusFiles, mirrorStatus, now = Date.now() } = {}) {
|
|
77
|
+
function canUseOtWarmStart({ enabled, focusFiles, focusFilesAreLiteral = false, mirrorStatus, now = Date.now() } = {}) {
|
|
77
78
|
if (!enabled) {
|
|
78
79
|
return { ok: false, reason: 'disabled' };
|
|
79
80
|
}
|
|
@@ -82,7 +83,10 @@
|
|
|
82
83
|
}
|
|
83
84
|
if (mirrorStatus.dirty === true) return { ok: false, reason: 'mirror_dirty' };
|
|
84
85
|
|
|
85
|
-
|
|
86
|
+
if (focusFilesAreLiteral === true && Array.isArray(focusFiles) && focusFiles.some(path => !normalizeLiteralPath(path))) {
|
|
87
|
+
return { ok: false, reason: 'invalid_focus_paths' };
|
|
88
|
+
}
|
|
89
|
+
const normalizedFocusFiles = normalizePaths(focusFiles, focusFilesAreLiteral === true);
|
|
86
90
|
if (!normalizedFocusFiles.length) {
|
|
87
91
|
return { ok: false, reason: 'no_focus_files' };
|
|
88
92
|
}
|
|
@@ -94,7 +98,7 @@
|
|
|
94
98
|
}
|
|
95
99
|
const age = now - Date.parse(file.observedAt ?? file.lastPatchAt ?? '');
|
|
96
100
|
if (!Number.isFinite(age) || age < 0 || age > OT_FRESHNESS_MAX_AGE_MS) continue;
|
|
97
|
-
const filePath =
|
|
101
|
+
const filePath = normalizeLiteralPath(file.path);
|
|
98
102
|
if (filePath) {
|
|
99
103
|
freshFiles.add(filePath);
|
|
100
104
|
}
|
|
@@ -124,9 +128,9 @@
|
|
|
124
128
|
let bytes = next.reduce((sum, event) => sum + patchBytes(event), 0);
|
|
125
129
|
if (next.length > OT_MAX_PENDING_EVENTS || bytes > OT_MAX_PENDING_BYTES) return { ok: false, reason: 'ot_queue_limit' };
|
|
126
130
|
for (const input of events) {
|
|
127
|
-
const path =
|
|
131
|
+
const path = normalizeLiteralPath(input?.path);
|
|
128
132
|
const time = typeof input?.observedAt === 'number' ? input.observedAt : Date.parse(input?.observedAt || '');
|
|
129
|
-
if (!
|
|
133
|
+
if (!path || path.length > 1024 || typeof input?.nextContent !== 'string' ||
|
|
130
134
|
typeof input.baseHash !== 'string' || !input.baseHash || input.baseHash.length > 128) {
|
|
131
135
|
return { ok: false, reason: 'ot_event_invalid' };
|
|
132
136
|
}
|
|
@@ -165,7 +169,7 @@
|
|
|
165
169
|
}
|
|
166
170
|
if (!events.length || Number(result.appliedCount) !== events.length || !Array.isArray(result.appliedFiles) ||
|
|
167
171
|
result.appliedFiles.length !== events.length || !result.appliedFiles.every((file, index) =>
|
|
168
|
-
|
|
172
|
+
normalizeLiteralPath(file?.path) === events[index].path && (!events[index].nextHash || file.hash === events[index].nextHash))) {
|
|
169
173
|
return { ok: false, reason: 'mirror_patch_invalid_result' };
|
|
170
174
|
}
|
|
171
175
|
if (events.some(event => { const time = Date.parse(event.observedAt);
|
|
@@ -220,11 +224,11 @@
|
|
|
220
224
|
.replace(/^\/+/, '');
|
|
221
225
|
}
|
|
222
226
|
|
|
223
|
-
function normalizePaths(value) {
|
|
227
|
+
function normalizePaths(value, literal = false) {
|
|
224
228
|
const seen = new Set();
|
|
225
229
|
const paths = [];
|
|
226
230
|
for (const item of Array.isArray(value) ? value : []) {
|
|
227
|
-
const filePath = normalizePath(item);
|
|
231
|
+
const filePath = literal ? normalizeLiteralPath(item) : normalizePath(item);
|
|
228
232
|
if (!filePath || seen.has(filePath)) {
|
|
229
233
|
continue;
|
|
230
234
|
}
|
|
@@ -234,13 +238,6 @@
|
|
|
234
238
|
return paths;
|
|
235
239
|
}
|
|
236
240
|
|
|
237
|
-
function isSafeRelativePath(filePath) {
|
|
238
|
-
if (!filePath) {
|
|
239
|
-
return false;
|
|
240
|
-
}
|
|
241
|
-
return !filePath.split('/').some(part => part === '.' || part === '..');
|
|
242
|
-
}
|
|
243
|
-
|
|
244
241
|
function isPrimitiveMetadata(value) {
|
|
245
242
|
return ['string', 'number', 'boolean'].includes(typeof value);
|
|
246
243
|
}
|
|
@@ -21,14 +21,17 @@
|
|
|
21
21
|
['src/page/overleafProjectSnapshot.js', 'codex-overleaf-project-snapshot-script'],
|
|
22
22
|
['src/page/pageBridgeCapability.js', 'codex-overleaf-page-bridge-capability-script'],
|
|
23
23
|
['src/page/treeOperations.js', 'codex-overleaf-tree-operations-script', true],
|
|
24
|
+
['src/page/ownedDirectoryCleanup.js', 'codex-overleaf-owned-directory-cleanup-script', true],
|
|
24
25
|
['src/page/textFileCreator.js', 'codex-overleaf-text-file-creator-script', true],
|
|
25
26
|
['src/page/binaryAssetUploader.js', 'codex-overleaf-binary-asset-uploader-script', true],
|
|
26
27
|
['src/page/snapshotRouter.js', 'codex-overleaf-snapshot-router-script'],
|
|
27
28
|
['src/page/referenceProjects.js', 'codex-overleaf-reference-projects-script', true],
|
|
29
|
+
['src/page/fileMover.js', 'codex-overleaf-file-mover-script', true],
|
|
28
30
|
['src/page/writeGuard.js', 'codex-overleaf-write-guard-script', true],
|
|
29
31
|
['src/page/trackedChangeOwnership.js', 'codex-overleaf-tracked-change-ownership-script', true],
|
|
30
32
|
['src/page/trackedChangeCapture.js', 'codex-overleaf-tracked-change-capture-script', true],
|
|
31
33
|
['src/page/trackedChangeReplay.js', 'codex-overleaf-tracked-change-replay-script', true],
|
|
34
|
+
['src/page/reviewContext.js', 'codex-overleaf-review-context-script', true],
|
|
32
35
|
['src/page/trackedChangesLifecycle.js', 'codex-overleaf-tracked-changes-lifecycle-script', true],
|
|
33
36
|
['src/page/writebackRouter.js', 'codex-overleaf-writeback-router-script', true],
|
|
34
37
|
['src/page/writebackReceiptJournal.js', 'codex-overleaf-writeback-receipt-script', true],
|
|
@@ -75,6 +78,11 @@
|
|
|
75
78
|
const requestTimeoutMs = Number.isFinite(Number(policy.timeoutMs))
|
|
76
79
|
? Number(policy.timeoutMs)
|
|
77
80
|
: Contract.resolveTimeoutMs(method, timeoutOverrides);
|
|
81
|
+
// Review preparation and its nested writes share the transport budget.
|
|
82
|
+
if (Contract.getMethod(method)?.timeoutClass === 'lifecycle') {
|
|
83
|
+
params = { ...params, deadlineAt: params?.deadlineAt == null ? now() + requestTimeoutMs
|
|
84
|
+
: Number.isFinite(params.deadlineAt) ? Math.min(params.deadlineAt, now() + requestTimeoutMs) : params.deadlineAt };
|
|
85
|
+
}
|
|
78
86
|
const remainingMs = params?.deadlineAt == null ? Infinity
|
|
79
87
|
: Number.isFinite(params.deadlineAt) ? params.deadlineAt - now() : 0;
|
|
80
88
|
if (remainingMs <= 0) {
|
|
@@ -84,7 +92,7 @@
|
|
|
84
92
|
}
|
|
85
93
|
const timeoutMs = Math.min(requestTimeoutMs, remainingMs);
|
|
86
94
|
const cancellable = policy.cancellation === 'content_abort';
|
|
87
|
-
let settled = false;
|
|
95
|
+
let settled = false, dispatched = false;
|
|
88
96
|
let timeout = null;
|
|
89
97
|
function cleanup() {
|
|
90
98
|
if (timeout !== null) windowRef.clearTimeout(timeout);
|
|
@@ -107,8 +115,13 @@
|
|
|
107
115
|
const error = new Error('Codex run was cancelled by the user');
|
|
108
116
|
error.code = 'codex_cancelled';
|
|
109
117
|
error.cancelled = true;
|
|
118
|
+
error.requestDispatched = dispatched;
|
|
119
|
+
error.changedDocument = dispatched && Contract.getMethod(method)?.mutation === 'document';
|
|
120
|
+
if (dispatched && method === 'applyOperations') { error.requestId = id; error.rpcMethod = method; }
|
|
110
121
|
rejectOnce(error);
|
|
111
122
|
}
|
|
123
|
+
// Cancellation stops new requests; receipt settlement has its own non-cancellable policy.
|
|
124
|
+
if (cancellable && options.isCancellationRequested?.()) { cancelRequest(); return; }
|
|
112
125
|
timeout = windowRef.setTimeout(() => {
|
|
113
126
|
resolveOnce({ ok: false, error: 'Page bridge timed out',
|
|
114
127
|
...(method === 'applyOperations' ? { code: 'page_bridge_timeout', rpcMethod: method, requestId: id } : {}) });
|
|
@@ -126,11 +139,11 @@
|
|
|
126
139
|
}
|
|
127
140
|
if (cancellable) {
|
|
128
141
|
activeCancellationHandlers.set(id, cancelRequest);
|
|
129
|
-
if (options.isCancellationRequested?.()) windowRef.queueMicrotask(cancelRequest);
|
|
130
142
|
}
|
|
131
143
|
windowRef.addEventListener('message', onMessage);
|
|
132
144
|
// Every low-level request remains one attempt. Higher layers must opt
|
|
133
145
|
// into replay so a document mutation is never duplicated implicitly.
|
|
146
|
+
dispatched = true;
|
|
134
147
|
windowRef.postMessage({
|
|
135
148
|
source: 'codex-overleaf/content',
|
|
136
149
|
id,
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
openCustomInstructionsSettings,
|
|
25
25
|
onHistoryRowJump,
|
|
26
26
|
resetProjectNameCache,
|
|
27
|
+
clearHistoryComposer,
|
|
27
28
|
getPanel,
|
|
28
29
|
getState,
|
|
29
30
|
setState,
|
|
@@ -110,27 +111,54 @@
|
|
|
110
111
|
if (!confirmed) {
|
|
111
112
|
return;
|
|
112
113
|
}
|
|
114
|
+
if (getCurrentRunView()) {
|
|
115
|
+
showPluginToast(tx('A run started while confirmation was open. Finish or cancel it before clearing history.', '确认期间有任务开始运行,请先完成或取消任务后再清理历史。'));
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
let historyClearedAt;
|
|
113
119
|
try {
|
|
114
|
-
await StorageDb
|
|
120
|
+
await StorageDb.clearAllStores();
|
|
121
|
+
historyClearedAt = Date.now();
|
|
115
122
|
historyView.invalidate();
|
|
116
|
-
// v1.8.1: the dashboard's project-name cache lives in
|
|
117
|
-
// chrome.storage.local, outside the IndexedDB stores — clear it too so
|
|
118
|
-
// no (potentially sensitive) project titles outlive the wipe.
|
|
119
|
-
await resetProjectNameCache?.();
|
|
120
123
|
} catch (error) {
|
|
121
124
|
showPluginToast(tx(`Could not clear history: ${error.message}`, `清空历史失败:${error.message}`));
|
|
122
125
|
return;
|
|
123
126
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
127
|
+
const cleanupErrors = [];
|
|
128
|
+
try {
|
|
129
|
+
// Project names live outside the history store. Failure here must not
|
|
130
|
+
// leave deleted history in memory for a later autosave to replay.
|
|
131
|
+
await resetProjectNameCache?.();
|
|
132
|
+
} catch (error) {
|
|
133
|
+
cleanupErrors.push(error.message);
|
|
134
|
+
}
|
|
135
|
+
// Clear the compatibility mirror as well as the session list; otherwise
|
|
136
|
+
// normalization can reconstruct old summary runs and the Native thread.
|
|
137
|
+
const clearedState = normalizePanelState({
|
|
138
|
+
...getState(), session: null, sessions: [], runs: [], activeSessionId: '',
|
|
139
|
+
task: '', focusFiles: [], selectionContext: null, composerAttachments: []
|
|
140
|
+
});
|
|
141
|
+
const freshSession = clearedState.sessions[0];
|
|
142
|
+
// The shared store rejects createdAt <= its clear marker. A new session
|
|
143
|
+
// in the same millisecond needs the next tick, at most 1 ms ahead of now.
|
|
144
|
+
freshSession.createdAt = freshSession.updatedAt = new Date(Math.max(Date.now(), historyClearedAt + 1)).toISOString();
|
|
145
|
+
setState(clearedState);
|
|
146
|
+
try {
|
|
147
|
+
clearHistoryComposer?.();
|
|
148
|
+
if (await saveState({ requireDurable: true }) !== true) {
|
|
149
|
+
throw new Error(tx('The blank session save was not confirmed.', '空白会话的保存结果尚未确认。'));
|
|
150
|
+
}
|
|
151
|
+
} catch (error) {
|
|
152
|
+
cleanupErrors.push(error.message);
|
|
153
|
+
}
|
|
154
|
+
// The store has already been cleared, including when the fresh-session
|
|
155
|
+
// save fails. Always remove old cards and draft inputs from the panel.
|
|
130
156
|
applyStateToPanel();
|
|
131
157
|
refreshStorageUsageSummary();
|
|
132
158
|
void renderAuditHistoryPanel();
|
|
133
|
-
showPluginToast(
|
|
159
|
+
showPluginToast(cleanupErrors.length
|
|
160
|
+
? tx(`Saved history was removed, but cleanup or blank-session saving needs attention: ${cleanupErrors.join('; ')}`, `已删除保存的历史,但清理或空白会话保存仍有问题:${cleanupErrors.join(';')}`)
|
|
161
|
+
: tx('Local history cleared. Overleaf files are unchanged.', '本地历史已清理,Overleaf 文件保持不变。'));
|
|
134
162
|
getPanel()?.querySelector('[data-task]')?.focus();
|
|
135
163
|
}
|
|
136
164
|
|
|
@@ -117,6 +117,12 @@
|
|
|
117
117
|
sessionSlot: instance.sessionSlot,
|
|
118
118
|
composerSlot: instance.composerSlot,
|
|
119
119
|
setView: view => { panelEl.dataset.view = view; },
|
|
120
|
+
setLocale: labels => {
|
|
121
|
+
launcherEl.title = labels.launcher;
|
|
122
|
+
launcherEl.setAttribute('aria-label', labels.launcher);
|
|
123
|
+
const close = panelEl.querySelector('[data-close-panel]');
|
|
124
|
+
if (close) { close.title = labels.close; close.setAttribute('aria-label', labels.close); }
|
|
125
|
+
},
|
|
120
126
|
destroy: () => destroy(instance),
|
|
121
127
|
_instance: instance
|
|
122
128
|
};
|
|
@@ -2,12 +2,20 @@
|
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
4
|
function create(deps = {}) {
|
|
5
|
-
|
|
5
|
+
function assertSwitchAllowed() {
|
|
6
6
|
if ((deps.getRunningSessionIds?.() || []).length) {
|
|
7
|
-
|
|
7
|
+
throw Object.assign(new Error(deps.tx(
|
|
8
8
|
'Wait for the running task to finish before switching providers.',
|
|
9
9
|
'请等待正在运行的任务结束后再切换模型服务。'
|
|
10
|
-
), {
|
|
10
|
+
)), { code: 'provider_switch_running' });
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async function confirmSwitch({ providerName } = {}) {
|
|
15
|
+
try {
|
|
16
|
+
assertSwitchAllowed();
|
|
17
|
+
} catch (error) {
|
|
18
|
+
deps.showToast?.(error.message, { status: 'warning' });
|
|
11
19
|
return false;
|
|
12
20
|
}
|
|
13
21
|
return deps.showConfirm?.({
|
|
@@ -33,6 +41,7 @@
|
|
|
33
41
|
const providerConfigurationChanged = nextProviderId === (state.providerId || 'builtin')
|
|
34
42
|
&& nextProviderRevision !== Number(state.providerRevision || 0);
|
|
35
43
|
const resetProviderThread = providerChanged || providerConfigurationChanged;
|
|
44
|
+
if (resetProviderThread) assertSwitchAllowed();
|
|
36
45
|
const sessions = (state.sessions || []).map(session => ({
|
|
37
46
|
...session,
|
|
38
47
|
providerId: nextProviderId,
|
|
@@ -53,7 +62,7 @@
|
|
|
53
62
|
deps.applyStateToPanel?.();
|
|
54
63
|
}
|
|
55
64
|
|
|
56
|
-
return { confirmSwitch, commit };
|
|
65
|
+
return { assertSwitchAllowed, confirmSwitch, commit };
|
|
57
66
|
}
|
|
58
67
|
|
|
59
68
|
window.CodexOverleafProjectProviderSelection = { create };
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
function create(deps = {}) {
|
|
7
7
|
const {
|
|
8
8
|
tr,
|
|
9
|
+
tx = en => en,
|
|
9
10
|
getAccountScopeId,
|
|
10
11
|
showPluginConfirm,
|
|
11
12
|
showPluginToast,
|
|
@@ -22,79 +23,125 @@
|
|
|
22
23
|
return !Number.isFinite(touched) || (Date.now() - touched) <= FRESH_RUNNING_WINDOW_MS;
|
|
23
24
|
}
|
|
24
25
|
|
|
25
|
-
async function clearProjectSessions(projectId, projectName) {
|
|
26
|
+
async function clearProjectSessions(projectId, projectName, options = {}) {
|
|
26
27
|
const StorageDb = deps.StorageDb;
|
|
27
28
|
const SessionState = deps.SessionState;
|
|
28
29
|
if (!StorageDb || !SessionState) {
|
|
29
30
|
return;
|
|
30
31
|
}
|
|
31
|
-
const scope = getAccountScopeId();
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
cancelLabel: tr('confirmDefaultCancel'),
|
|
47
|
-
destructive: true
|
|
48
|
-
});
|
|
49
|
-
if (!approved) {
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
const Migration = deps.StorageMigration;
|
|
53
|
-
if (Migration?.addSessionTombstones) {
|
|
54
|
-
await Migration.addSessionTombstones(projectId, records.map(record => record.id));
|
|
55
|
-
}
|
|
32
|
+
const scope = options.accountScopeId ?? getAccountScopeId();
|
|
33
|
+
const scopeChanged = error => error?.code === 'history_cleanup_scope_changed'
|
|
34
|
+
|| error?.code === 'account_scope_unavailable';
|
|
35
|
+
const assertCurrent = () => {
|
|
36
|
+
if (!scope || getAccountScopeId() !== scope || options.isCurrent?.() === false
|
|
37
|
+
|| options.generation !== undefined && deps.getViewGeneration?.() !== options.generation) {
|
|
38
|
+
throw Object.assign(new Error('History cleanup identity changed.'), { code: 'history_cleanup_scope_changed' });
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
const readRecords = async () => {
|
|
42
|
+
assertCurrent();
|
|
43
|
+
const records = await StorageDb.getAllByIndex('sessions', 'projectId', projectId);
|
|
44
|
+
assertCurrent();
|
|
45
|
+
return records.filter(record => record && record.accountScopeId === scope);
|
|
46
|
+
};
|
|
56
47
|
try {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
48
|
+
let records = await readRecords();
|
|
49
|
+
if (!records.length) {
|
|
50
|
+
await renderRecentProjectsVariant();
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if (records.some(record => isFreshRunningRecord(StorageDb, record))) {
|
|
54
|
+
showPluginToast(tr('recentProjects_clearProject_running'), { status: 'warning', sticky: true });
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const approved = await showPluginConfirm({
|
|
58
|
+
title: tr('recentProjects_clearProject_title', { project: projectName }),
|
|
59
|
+
message: tr('recentProjects_clearProject_message', { count: String(records.length), project: projectName }),
|
|
60
|
+
confirmLabel: tr('recentProjects_clearProject_confirm'),
|
|
61
|
+
cancelLabel: tr('confirmDefaultCancel'),
|
|
62
|
+
destructive: true
|
|
63
|
+
});
|
|
64
|
+
if (!approved) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
// Re-read after the destructive dialog: another tab can have started a
|
|
68
|
+
// run or added a conversation while the original count was displayed.
|
|
69
|
+
const approvedIds = new Set(records.map(record => record.id));
|
|
70
|
+
const latest = await readRecords();
|
|
71
|
+
if (latest.some(record => isFreshRunningRecord(StorageDb, record))) {
|
|
72
|
+
showPluginToast(tr('recentProjects_clearProject_running'), { status: 'warning', sticky: true });
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
records = latest.filter(record => approvedIds.has(record.id));
|
|
76
|
+
if (!records.length) { await renderRecentProjectsVariant(); return; }
|
|
77
|
+
const Migration = deps.StorageMigration;
|
|
78
|
+
if (Migration?.addSessionTombstones) {
|
|
79
|
+
assertCurrent();
|
|
80
|
+
await Migration.addSessionTombstones(projectId, records.map(record => record.id));
|
|
81
|
+
assertCurrent();
|
|
82
|
+
}
|
|
70
83
|
try {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
84
|
+
assertCurrent();
|
|
85
|
+
await mutateProjectPanelState(projectId, state => records.reduce(
|
|
86
|
+
(nextState, record) => SessionState.deleteSession(nextState, record.id),
|
|
87
|
+
state
|
|
88
|
+
), { assertCurrent });
|
|
89
|
+
assertCurrent();
|
|
90
|
+
} catch (error) {
|
|
91
|
+
if (scopeChanged(error)) throw error;
|
|
92
|
+
assertCurrent(); // Other legacy-blob failures do not replace shared deletion.
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
let removed = 0;
|
|
96
|
+
let historyFailures = 0;
|
|
97
|
+
for (const record of records) {
|
|
98
|
+
try {
|
|
99
|
+
assertCurrent();
|
|
100
|
+
await StorageDb.deleteRecord('sessions', record.id, { accountScopeId: scope, projectId });
|
|
101
|
+
assertCurrent();
|
|
102
|
+
removed++;
|
|
103
|
+
} catch (error) {
|
|
104
|
+
if (scopeChanged(error)) throw error;
|
|
105
|
+
assertCurrent(); // The remaining record can be retried in the same scope.
|
|
106
|
+
}
|
|
107
|
+
try {
|
|
108
|
+
assertCurrent();
|
|
109
|
+
const response = await sendBackgroundNative({
|
|
110
|
+
method: 'codex.history.clearPlugin',
|
|
111
|
+
params: { sessionId: record.id, threadId: record.codexThreadId || '' }
|
|
112
|
+
});
|
|
113
|
+
assertCurrent();
|
|
114
|
+
if (!response || !response.ok) {
|
|
115
|
+
historyFailures++;
|
|
116
|
+
}
|
|
117
|
+
} catch (error) {
|
|
118
|
+
if (scopeChanged(error)) throw error;
|
|
119
|
+
assertCurrent();
|
|
76
120
|
historyFailures++;
|
|
77
121
|
}
|
|
78
|
-
} catch (_error) {
|
|
79
|
-
historyFailures++;
|
|
80
122
|
}
|
|
123
|
+
if (removed < records.length) {
|
|
124
|
+
showPluginToast(tr('recentProjects_clearProject_partial', {
|
|
125
|
+
removed: String(removed),
|
|
126
|
+
count: String(records.length)
|
|
127
|
+
}), { status: 'warning', sticky: true });
|
|
128
|
+
} else if (historyFailures) {
|
|
129
|
+
showPluginToast(tr('recentProjects_clearProject_historyPartial', {
|
|
130
|
+
count: String(historyFailures)
|
|
131
|
+
}), { status: 'warning', sticky: true });
|
|
132
|
+
} else {
|
|
133
|
+
showPluginToast(tr('recentProjects_clearProject_done', {
|
|
134
|
+
count: String(removed),
|
|
135
|
+
project: projectName
|
|
136
|
+
}), { status: 'completed' });
|
|
137
|
+
}
|
|
138
|
+
await renderRecentProjectsVariant();
|
|
139
|
+
} catch (error) {
|
|
140
|
+
if (!scopeChanged(error)) throw error;
|
|
141
|
+
showPluginToast(tx('History view or account changed. Reopen the history list before deleting sessions.',
|
|
142
|
+
'历史视图或账号已变化,请重新打开历史列表后再清理会话。'), { status: 'warning', sticky: true });
|
|
143
|
+
return false;
|
|
81
144
|
}
|
|
82
|
-
if (removed < records.length) {
|
|
83
|
-
showPluginToast(tr('recentProjects_clearProject_partial', {
|
|
84
|
-
removed: String(removed),
|
|
85
|
-
count: String(records.length)
|
|
86
|
-
}), { status: 'warning', sticky: true });
|
|
87
|
-
} else if (historyFailures) {
|
|
88
|
-
showPluginToast(tr('recentProjects_clearProject_historyPartial', {
|
|
89
|
-
count: String(historyFailures)
|
|
90
|
-
}), { status: 'warning', sticky: true });
|
|
91
|
-
} else {
|
|
92
|
-
showPluginToast(tr('recentProjects_clearProject_done', {
|
|
93
|
-
count: String(removed),
|
|
94
|
-
project: projectName
|
|
95
|
-
}), { status: 'completed' });
|
|
96
|
-
}
|
|
97
|
-
await renderRecentProjectsVariant();
|
|
98
145
|
}
|
|
99
146
|
|
|
100
147
|
return { clearProjectSessions };
|
|
@@ -140,9 +140,13 @@
|
|
|
140
140
|
if (!settingsPanelInstance) {
|
|
141
141
|
return;
|
|
142
142
|
}
|
|
143
|
-
const
|
|
143
|
+
const settings = getSkillLoadingSettings();
|
|
144
|
+
const localCount = settings.loadCodexLocalSkills === false ? 0
|
|
145
|
+
: (deps.getCodexLocalSkills?.() || []).filter(skill => isCodexLocalSkillEnabled(skill.id, skill)).length;
|
|
146
|
+
const overleafCount = settings.loadCodexOverleafSkills === false ? 0 : countEnabledCodexOverleafSkills();
|
|
147
|
+
const summary = settings.loadCodexLocalSkills === false && settings.loadCodexOverleafSkills === false
|
|
144
148
|
? tr('codexOverleafSkillsSummaryOff')
|
|
145
|
-
: tr('codexOverleafSkillsSummaryCount', { count:
|
|
149
|
+
: tr('codexOverleafSkillsSummaryCount', { count: localCount + overleafCount });
|
|
146
150
|
SettingsPanel.setSkillsSummary(settingsPanelInstance, summary);
|
|
147
151
|
}
|
|
148
152
|
|
|
@@ -296,6 +300,36 @@
|
|
|
296
300
|
deps.updateGlobalPreferences?.({ codexOverleafSkillEnabled: { [skillId]: Boolean(enabled) } })?.catch(() => {});
|
|
297
301
|
}
|
|
298
302
|
|
|
303
|
+
function getCodexLocalSkillEnabled() {
|
|
304
|
+
const map = getState()?.codexLocalSkillEnabled;
|
|
305
|
+
return map && typeof map === 'object' && !Array.isArray(map) ? map : {};
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function isCodexLocalSkillEnabled(skillId, skill) {
|
|
309
|
+
const map = getCodexLocalSkillEnabled();
|
|
310
|
+
return Object.prototype.hasOwnProperty.call(map, skillId) ? map[skillId] !== false : skill?.enabled !== false;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function setCodexLocalSkillEnabled(skillId, enabled) {
|
|
314
|
+
if (deps.updateGlobalPreferences) {
|
|
315
|
+
try {
|
|
316
|
+
const pending = deps.updateGlobalPreferences({ codexLocalSkillEnabled: { [skillId]: Boolean(enabled) } });
|
|
317
|
+
Promise.resolve(pending).then(() => saveStateSoon()).catch(() => {
|
|
318
|
+
renderLocalSkillList();
|
|
319
|
+
setProjectSettingsStatus(tx('Could not save the local skill selection. Try again.', '无法保存本地技能选择,请重试。'), 'failed');
|
|
320
|
+
});
|
|
321
|
+
} catch (_error) {
|
|
322
|
+
renderLocalSkillList();
|
|
323
|
+
setProjectSettingsStatus(tx('Could not save the local skill selection. Try again.', '无法保存本地技能选择,请重试。'), 'failed');
|
|
324
|
+
}
|
|
325
|
+
} else {
|
|
326
|
+
setState({ ...getState(), codexLocalSkillEnabled: { ...getCodexLocalSkillEnabled(), [skillId]: Boolean(enabled) } });
|
|
327
|
+
saveStateSoon();
|
|
328
|
+
renderLocalSkillList();
|
|
329
|
+
}
|
|
330
|
+
deps.onSkillsChange?.();
|
|
331
|
+
}
|
|
332
|
+
|
|
299
333
|
function readSkillLoadingSettingsFromSettings() {
|
|
300
334
|
return SettingsPanel.readState(getSettingsPanelInstance()).skillToggles;
|
|
301
335
|
}
|
|
@@ -337,6 +371,9 @@
|
|
|
337
371
|
closeCustomInstructionsSettings,
|
|
338
372
|
closeSkillsView,
|
|
339
373
|
getCodexOverleafSkillEnabled,
|
|
374
|
+
getCodexLocalSkillEnabled,
|
|
375
|
+
isCodexLocalSkillEnabled,
|
|
376
|
+
setCodexLocalSkillEnabled,
|
|
340
377
|
getCustomInstructionsForCurrentProject,
|
|
341
378
|
getGovernanceRulesForCurrentProject,
|
|
342
379
|
getSkillLoadingSettings,
|