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
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
(function installUpdateSafetyReceiver(root) {
|
|
2
|
+
'use strict';
|
|
3
|
+
if (root.__codexOverleafUpdateSafetyReceiverInstalled) return;
|
|
4
|
+
let binding, idle, inFlight = false;
|
|
5
|
+
const blocked = () => ({ idle: false, saved: false, blockers: ['tab_probe_unavailable'] });
|
|
6
|
+
function install(next, dependencies) {
|
|
7
|
+
if (!next?.transactionId || !next.token || next.url !== location.href) return { ok: false };
|
|
8
|
+
if (binding) {
|
|
9
|
+
if (binding.transactionId !== next.transactionId || binding.targetVersion !== next.targetVersion
|
|
10
|
+
|| binding.documentId !== next.documentId || binding.url !== next.url || inFlight) return { ok: false };
|
|
11
|
+
binding = Object.freeze({ ...next });
|
|
12
|
+
return { ok: true, url: location.href };
|
|
13
|
+
}
|
|
14
|
+
binding = Object.freeze({ ...next });
|
|
15
|
+
const pageWrites = new Set();
|
|
16
|
+
const visible = node => !node.hidden && node.getAttribute('aria-hidden') !== 'true' && node.getClientRects().length > 0;
|
|
17
|
+
const { UpdateIdle, PageRpcContract } = dependencies;
|
|
18
|
+
idle = UpdateIdle.create({ document, includePanelActivity: true,
|
|
19
|
+
getBusyState: () => ({ reviewAction: pageWrites.size > 0
|
|
20
|
+
|| [...document.querySelectorAll('#codex-overleaf-panel [data-run-accept-confirm], #codex-overleaf-panel button:disabled')]
|
|
21
|
+
.some(node => visible(node) && (node.hasAttribute('data-run-accept-confirm') || /Accepting|接受中/.test(node.textContent)
|
|
22
|
+
|| (node.hasAttribute('data-run-undo') && !/Undone|已撤销/.test(node.textContent)
|
|
23
|
+
&& !/Accepted|已接受/.test(node.closest('[data-run-id]')?.querySelector('[data-run-accept]')?.textContent || '')))),
|
|
24
|
+
run: document.querySelector('#codex-overleaf-panel')?.dataset.running === 'true',
|
|
25
|
+
cancelling: document.querySelector('#codex-overleaf-panel')?.dataset.cancelling === 'true',
|
|
26
|
+
dialog: [...document.querySelectorAll('[role="dialog"], dialog[open]')].some(visible) }),
|
|
27
|
+
checkSaved: async () => {
|
|
28
|
+
const response = await chrome.runtime.sendMessage({ type: 'codex-overleaf/migration-save-probe', ...binding,
|
|
29
|
+
nonce: activeNonce });
|
|
30
|
+
return response?.ok && response.nonce === activeNonce ? response.result : { ok: false, state: 'unavailable' };
|
|
31
|
+
} });
|
|
32
|
+
root.addEventListener('message', event => {
|
|
33
|
+
if (event.source !== root || event.origin !== location.origin || typeof event.data?.id !== 'string') return;
|
|
34
|
+
const data = event.data;
|
|
35
|
+
const mutation = PageRpcContract.getMethod(data.method)?.mutation;
|
|
36
|
+
if (data.source === 'codex-overleaf/content' && ['document', 'page_state', 'navigation'].includes(mutation)) {
|
|
37
|
+
pageWrites.add(data.id); idle.noteActivity();
|
|
38
|
+
} else if (data.source === 'codex-overleaf/page' && pageWrites.delete(data.id)) idle.noteActivity();
|
|
39
|
+
});
|
|
40
|
+
chrome.runtime.onMessage.addListener((message, sender, respond) => {
|
|
41
|
+
if (message?.type !== 'codex-overleaf/migration-idle-probe') return undefined;
|
|
42
|
+
if (sender?.id !== chrome.runtime.id || sender.tab || message.token !== binding.token
|
|
43
|
+
|| message.transactionId !== binding.transactionId || typeof message.nonce !== 'string'
|
|
44
|
+
|| !message.nonce || location.href !== binding.url || inFlight) { respond(blocked()); return false; }
|
|
45
|
+
inFlight = true;
|
|
46
|
+
activeNonce = message.nonce;
|
|
47
|
+
idle.buildIdleProbe().then(result => respond(location.href === binding.url ? {
|
|
48
|
+
...result, transactionId: binding.transactionId, token: binding.token, nonce: message.nonce,
|
|
49
|
+
url: location.href, targetVersion: binding.targetVersion, safetyOnly: true
|
|
50
|
+
} : blocked())).catch(() => respond(blocked())).finally(() => { inFlight = false; activeNonce = ''; });
|
|
51
|
+
return true;
|
|
52
|
+
});
|
|
53
|
+
return { ok: true, url: location.href };
|
|
54
|
+
}
|
|
55
|
+
let activeNonce = '';
|
|
56
|
+
root.CodexOverleafUpdateSafetyReceiver = Object.freeze({ install });
|
|
57
|
+
root.__codexOverleafUpdateSafetyReceiverInstalled = true;
|
|
58
|
+
})(globalThis);
|
|
@@ -10,6 +10,13 @@
|
|
|
10
10
|
function buildSyncApplyOperations(syncChanges = [], project = {}) {
|
|
11
11
|
const existingPaths = new Set((project.files || []).map(file => file.path));
|
|
12
12
|
return (syncChanges || []).map(change => {
|
|
13
|
+
// Keep an old Native move distinct so preflight can reject it safely.
|
|
14
|
+
// Falling through would turn a move into an empty create operation.
|
|
15
|
+
if (change.type === 'move') {
|
|
16
|
+
return { type: 'move', path: change.path, to: change.to,
|
|
17
|
+
moveProof: change.moveProof && typeof change.moveProof === 'object' ? { ...change.moveProof } : undefined,
|
|
18
|
+
reason: 'Move the existing Overleaf file while preserving its identity.' };
|
|
19
|
+
}
|
|
13
20
|
if (change.type === 'binary-create' || change.type === 'overwrite-binary') {
|
|
14
21
|
return {
|
|
15
22
|
type: change.type,
|
|
@@ -22,6 +29,7 @@
|
|
|
22
29
|
previousExists: change.previousExists === true,
|
|
23
30
|
previousKind: change.previousKind || '',
|
|
24
31
|
previousSize: Number.isFinite(Number(change.previousSize)) ? Number(change.previousSize) : undefined,
|
|
32
|
+
...(typeof change.previousSha256 === 'string' ? { previousSha256: change.previousSha256 } : {}),
|
|
25
33
|
reasonKey: change.type === 'overwrite-binary' ? 'localWorkspaceBinaryOverwrite' : 'localWorkspaceBinaryCreate',
|
|
26
34
|
reason: change.type === 'overwrite-binary'
|
|
27
35
|
? 'Synced a binary asset replacement from the local Codex workspace.'
|
|
@@ -158,14 +166,18 @@
|
|
|
158
166
|
|
|
159
167
|
if ((operation.type === 'rename' || operation.type === 'move') && operation.to) {
|
|
160
168
|
const previous = filesByPath.get(operation.path) || originalByPath.get(operation.path);
|
|
169
|
+
const destination = filesByPath.get(operation.to);
|
|
170
|
+
const binary = operation.moveProof?.kind === 'binary' || previous?.kind === 'binary';
|
|
161
171
|
filesByPath.delete(operation.path);
|
|
162
172
|
filesByPath.set(operation.to, {
|
|
163
173
|
...(previous || {}),
|
|
174
|
+
...(destination || {}),
|
|
164
175
|
path: operation.to,
|
|
165
|
-
kind: 'text',
|
|
166
|
-
content: result.verifiedContent
|
|
176
|
+
kind: binary ? 'binary' : 'text',
|
|
177
|
+
...(!binary ? { content: result.verifiedContent ?? destination?.content ?? previous?.content ?? '' } : {}),
|
|
167
178
|
source: 'verified-writeback'
|
|
168
179
|
});
|
|
180
|
+
if (binary) delete filesByPath.get(operation.to).content;
|
|
169
181
|
continue;
|
|
170
182
|
}
|
|
171
183
|
|
|
@@ -227,10 +239,15 @@
|
|
|
227
239
|
? changeOrReason
|
|
228
240
|
: { reason: changeOrReason };
|
|
229
241
|
const reason = change.reason;
|
|
242
|
+
if (reason === 'snapshot_content_unavailable') {
|
|
243
|
+
return isEnglish
|
|
244
|
+
? 'Current Overleaf content was not read; local changes were withheld until the file can be refreshed.'
|
|
245
|
+
: '未读取该文件当前的 Overleaf 内容;刷新取得内容前,本地改动不会写回。';
|
|
246
|
+
}
|
|
230
247
|
if (reason === 'selection_scope_violation') {
|
|
231
248
|
return isEnglish
|
|
232
|
-
? '
|
|
233
|
-
: '
|
|
249
|
+
? 'This local change exceeded the captured selection and was withheld. Independently valid selection changes may still be written.'
|
|
250
|
+
: '该项本地改动超出了已附加的选区,已阻止写回;独立且合法的选区内改动仍可写入。';
|
|
234
251
|
}
|
|
235
252
|
if (reason === 'generated_artifact') {
|
|
236
253
|
return isEnglish
|
|
@@ -314,6 +331,13 @@
|
|
|
314
331
|
if (!path || result.ok === false) { unsupported.push(path || 'unknown'); continue; }
|
|
315
332
|
if (operation.type === 'delete') { expected.set(path, { absent: true }); continue; }
|
|
316
333
|
if (operation.type === 'rename' || operation.type === 'move') {
|
|
334
|
+
const moveProof = result.moveProof || operation.moveProof;
|
|
335
|
+
if (moveProof?.v === 1 && moveProof.kind === 'binary' && /^[a-f0-9]{64}$/i.test(moveProof.sha256 || '')) {
|
|
336
|
+
if (!operation.to || (operation.moveProof?.sha256 && operation.moveProof.sha256 !== moveProof.sha256)) { unsupported.push(path); continue; }
|
|
337
|
+
if (operation.to !== path) expected.set(path, { absent: true });
|
|
338
|
+
expected.set(operation.to, { sha256: moveProof.sha256.toLowerCase() });
|
|
339
|
+
continue;
|
|
340
|
+
}
|
|
317
341
|
const content = typeof result.verifiedContent === 'string'
|
|
318
342
|
? result.verifiedContent : original.get(path)?.content;
|
|
319
343
|
if (!operation.to || typeof content !== 'string') { unsupported.push(path); continue; }
|
|
@@ -448,6 +472,30 @@
|
|
|
448
472
|
} };
|
|
449
473
|
}
|
|
450
474
|
|
|
475
|
+
async function buildMirrorWritebackConfirmation(applied = {}) {
|
|
476
|
+
const entries = Array.isArray(applied.applied) ? applied.applied : [];
|
|
477
|
+
if (!entries.length) return null;
|
|
478
|
+
const postImages = new Map();
|
|
479
|
+
for (const entry of entries) {
|
|
480
|
+
const operation = entry?.operation, result = entry?.result;
|
|
481
|
+
if (operation?.type !== 'edit' || typeof operation.path !== 'string' || !operation.path || result?.ok !== true
|
|
482
|
+
|| result.verified !== true || typeof result.verifiedContent !== 'string') return null;
|
|
483
|
+
// Several edits can target one file; the last verified readback is its post-image.
|
|
484
|
+
postImages.set(operation.path, result.verifiedContent);
|
|
485
|
+
}
|
|
486
|
+
try {
|
|
487
|
+
const expectedFiles = [];
|
|
488
|
+
for (const [path, content] of postImages) {
|
|
489
|
+
const sha256 = await textDigest(content);
|
|
490
|
+
if (!/^[a-f0-9]{64}$/.test(sha256)) return null;
|
|
491
|
+
expectedFiles.push({ path, sha256 });
|
|
492
|
+
}
|
|
493
|
+
return { paths: expectedFiles.map(file => file.path), expectedFiles };
|
|
494
|
+
} catch (_error) {
|
|
495
|
+
return null; // No digest evidence: use the authoritative snapshot fallback.
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
451
499
|
async function bytesDigest(bytes) {
|
|
452
500
|
if (!globalThis.crypto?.subtle) return '';
|
|
453
501
|
const digest = await globalThis.crypto.subtle.digest('SHA-256', bytes);
|
|
@@ -543,6 +591,7 @@
|
|
|
543
591
|
|
|
544
592
|
return {
|
|
545
593
|
confirmPostWriteSave,
|
|
594
|
+
buildMirrorWritebackConfirmation,
|
|
546
595
|
buildSaveCheck,
|
|
547
596
|
confirmSaveCheck,
|
|
548
597
|
buildSavedWritebackExpectations,
|
|
@@ -100,6 +100,21 @@
|
|
|
100
100
|
return pending;
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
+
function selectionViolationNextStep() {
|
|
104
|
+
return tx('Select the intended region again and request only changes within it. Valid written parts can be undone.',
|
|
105
|
+
'请重新选择目标范围,仅请求修改该范围。已写入的合法部分可撤销。');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function buildWithheldEntry(change) {
|
|
109
|
+
const reason = writebackController.formatUnsupportedLocalChangeReason(change, getLocale());
|
|
110
|
+
const nextAction = change.reason === 'selection_scope_violation' ? selectionViolationNextStep()
|
|
111
|
+
: tx('The main agent must review the withheld file before syncing it.', '主代理需核对未同步文件后再写回。');
|
|
112
|
+
return { operation: { type: 'create', path: change.path }, result: { ok: false,
|
|
113
|
+
code: change.reason, reason, changedDocument: false, failure: { code: change.reason,
|
|
114
|
+
stage: 'preflight', severity: 'blocked', retryable: false, terminalState: 'blocked', changedDocument: false,
|
|
115
|
+
userMessage: reason, nextAction } } };
|
|
116
|
+
}
|
|
117
|
+
|
|
103
118
|
async function applySyncChangesToOverleaf(syncChanges = [], project = {}, options = {}) {
|
|
104
119
|
const activeRun = getCurrentRunView?.();
|
|
105
120
|
const saveTarget = { runProjectId: activeRun?.runProjectId || '', recordId: activeRun?.recordId || '', sessionId: activeRun?.sessionId || '' };
|
|
@@ -110,20 +125,23 @@
|
|
|
110
125
|
const assistantMessage = cleanFinalAnswer(options.assistantMessage || getAssistantAnswerForCurrentRun());
|
|
111
126
|
const unsupportedChanges = Array.isArray(options.unsupportedChanges) ? options.unsupportedChanges : [];
|
|
112
127
|
appendUnsupportedLocalChanges(unsupportedChanges);
|
|
113
|
-
const withheldOnly = unsupportedChanges.filter(change => ['subagent_unfinished_output', 'subagent_unauthorized_edit'].includes(change.reason));
|
|
128
|
+
const withheldOnly = unsupportedChanges.filter(change => ['subagent_unfinished_output', 'subagent_unauthorized_edit', 'selection_scope_violation'].includes(change.reason));
|
|
114
129
|
if (runMode !== 'ask' && !syncChanges.length && withheldOnly.length) {
|
|
115
|
-
const skipped = withheldOnly.map(
|
|
116
|
-
|
|
117
|
-
reason: writebackController.formatUnsupportedLocalChangeReason(change, getLocale())
|
|
118
|
-
} }));
|
|
130
|
+
const skipped = withheldOnly.map(buildWithheldEntry);
|
|
131
|
+
const hasSelectionViolation = withheldOnly.some(change => change.reason === 'selection_scope_violation');
|
|
119
132
|
const applied = { ok: false, applied: [], skipped };
|
|
120
|
-
appendCompletionReport({ conclusion: assistantMessage || tx('Some generated
|
|
133
|
+
appendCompletionReport({ conclusion: assistantMessage || tx('Some generated changes were blocked.', '部分生成的改动已被阻止。'),
|
|
121
134
|
status: 'failed', operations: [], applyResults: [applied], mode: runMode,
|
|
122
|
-
nextStep:
|
|
123
|
-
|
|
135
|
+
nextStep: hasSelectionViolation
|
|
136
|
+
? selectionViolationNextStep()
|
|
137
|
+
: tx('The main agent must review and adopt the withheld files before syncing them.', '主代理需核对并接管未同步文件后再写回。') });
|
|
138
|
+
return { summaryLine: hasSelectionViolation
|
|
139
|
+
? tx('Changes outside the selection were blocked.', '选区外改动已被阻止。')
|
|
140
|
+
: tx('Generated files are awaiting review.', '生成文件待核对。'), hasSkippedOperations: true,
|
|
124
141
|
applied, settlement: buildSettlement({ operations: [], applyResult: applied }),
|
|
125
142
|
audit: buildAuditSummaryFromApply({ operations: [], applyResults: [applied], resultStatus: 'completed_with_skips' }) };
|
|
126
143
|
}
|
|
144
|
+
|
|
127
145
|
// Guard on the immutable submitted mode, not the mutable panel state.
|
|
128
146
|
if (options.mode === 'ask' && ((syncChanges || []).length || unsupportedChanges.length)) {
|
|
129
147
|
appendRunEvent({
|
|
@@ -166,7 +184,16 @@
|
|
|
166
184
|
}
|
|
167
185
|
let operations = buildSyncApplyOperations(syncChanges, project);
|
|
168
186
|
let visibleSyncChanges = syncChanges || [];
|
|
169
|
-
|
|
187
|
+
// A previous Native build can still report inferred metadata moves.
|
|
188
|
+
// Forward sync never promotes them to a new file or a new capability.
|
|
189
|
+
let additionalSkippedEntries = operations.filter(operation => operation.type === 'move').map(operation => ({
|
|
190
|
+
operation,
|
|
191
|
+
result: { ok: false, code: 'file_tree_controls_unavailable', changedDocument: false,
|
|
192
|
+
reason: tx('Automatic file moves are unavailable. Handle the path change in Overleaf before retrying.',
|
|
193
|
+
'暂不支持自动移动文件,请先在 Overleaf 中处理路径变化后再重试。') }
|
|
194
|
+
}));
|
|
195
|
+
operations = operations.filter(operation => operation.type !== 'move');
|
|
196
|
+
if (additionalSkippedEntries.length) visibleSyncChanges = filterSyncChangesByOperations(syncChanges, operations);
|
|
170
197
|
let skippedFilesForAudit = additionalSkippedEntries;
|
|
171
198
|
let appliedFilesForAudit = [];
|
|
172
199
|
const pathSafety = partitionUnsafeProjectPathOperations(operations);
|
|
@@ -191,13 +218,20 @@
|
|
|
191
218
|
visibleSyncChanges = filterSyncChangesByOperations(syncChanges, operations);
|
|
192
219
|
}
|
|
193
220
|
if (!operations.length) {
|
|
221
|
+
const blockedSettlement = buildSettlement({
|
|
222
|
+
operations: buildSyncApplyOperations(syncChanges, project),
|
|
223
|
+
applyResult: { ok: additionalSkippedEntries.length === 0, applied: [], skipped: additionalSkippedEntries }
|
|
224
|
+
});
|
|
225
|
+
const blockedFailure = blockedSettlement
|
|
226
|
+
? writebackSettlement.projectRunSettlement({ settlement: blockedSettlement.facts }).primaryFailure : undefined;
|
|
194
227
|
appendRunEvent({
|
|
195
228
|
title: tx('Codex did not produce file changes that need to sync back to Overleaf.', 'Codex 没有产生需要同步回 Overleaf 的文件改动。'),
|
|
196
229
|
status: 'completed'
|
|
197
230
|
});
|
|
198
231
|
appendCompletionReport({
|
|
199
232
|
conclusion: assistantMessage || tx('Codex finished locally. There are no changes to sync back to Overleaf.', 'Codex 已完成本地处理,没有需要同步回 Overleaf 的改动。'),
|
|
200
|
-
status: runMode === 'ask' ? tr('modeAsk') : 'completed',
|
|
233
|
+
status: additionalSkippedEntries.length ? 'failed' : runMode === 'ask' ? tr('modeAsk') : 'completed',
|
|
234
|
+
failure: blockedFailure,
|
|
201
235
|
operations: [],
|
|
202
236
|
applyResults: additionalSkippedEntries.length ? [{ ok: false, applied: [], skipped: additionalSkippedEntries }] : [],
|
|
203
237
|
unchangedReason: additionalSkippedEntries.find(item => item?.result?.reason)?.result.reason
|
|
@@ -208,14 +242,7 @@
|
|
|
208
242
|
return {
|
|
209
243
|
summaryLine: assistantMessage || tx('No changes to sync', '没有需要同步的改动'),
|
|
210
244
|
hasSkippedOperations: additionalSkippedEntries.length > 0,
|
|
211
|
-
settlement:
|
|
212
|
-
operations: buildSyncApplyOperations(syncChanges, project),
|
|
213
|
-
applyResult: {
|
|
214
|
-
ok: additionalSkippedEntries.length === 0,
|
|
215
|
-
applied: [],
|
|
216
|
-
skipped: additionalSkippedEntries
|
|
217
|
-
}
|
|
218
|
-
}),
|
|
245
|
+
settlement: blockedSettlement,
|
|
219
246
|
audit: buildAuditSummaryFromApply({
|
|
220
247
|
operations: buildSyncApplyOperations(syncChanges, project),
|
|
221
248
|
applyResults: additionalSkippedEntries.length ? [{ ok: false, applied: [], skipped: additionalSkippedEntries }] : [],
|
|
@@ -258,13 +285,20 @@
|
|
|
258
285
|
additionalSkippedEntries.push(...binaryDecision.skipped);
|
|
259
286
|
|
|
260
287
|
if (!operations.length) {
|
|
288
|
+
const approvalSettlement = buildSettlement({
|
|
289
|
+
operations: buildSyncApplyOperations(syncChanges, project),
|
|
290
|
+
applyResult: { ok: additionalSkippedEntries.length === 0, applied: [], skipped: additionalSkippedEntries }
|
|
291
|
+
});
|
|
292
|
+
const approvalFailure = approvalSettlement
|
|
293
|
+
? writebackSettlement.projectRunSettlement({ settlement: approvalSettlement.facts }).primaryFailure : undefined;
|
|
261
294
|
appendRunEvent({
|
|
262
295
|
title: tx('No approved file changes remain to sync back to Overleaf.', '没有剩余已确认的文件改动需要同步回 Overleaf。'),
|
|
263
296
|
status: 'completed'
|
|
264
297
|
});
|
|
265
298
|
appendCompletionReport({
|
|
266
299
|
conclusion: assistantMessage || tx('No approved changes were written back to Overleaf.', '没有已确认的改动写回 Overleaf。'),
|
|
267
|
-
status: runMode === 'ask' ? tr('modeAsk') : 'completed',
|
|
300
|
+
status: additionalSkippedEntries.length ? 'failed' : runMode === 'ask' ? tr('modeAsk') : 'completed',
|
|
301
|
+
failure: approvalFailure,
|
|
268
302
|
operations: [],
|
|
269
303
|
applyResults: additionalSkippedEntries.length ? [{ ok: false, applied: [], skipped: additionalSkippedEntries }] : [],
|
|
270
304
|
unchangedReason: additionalSkippedEntries.find(item => item?.result?.reason)?.result.reason
|
|
@@ -275,14 +309,7 @@
|
|
|
275
309
|
return {
|
|
276
310
|
summaryLine: tx('No changes applied', '没有应用改动'),
|
|
277
311
|
hasSkippedOperations: additionalSkippedEntries.length > 0,
|
|
278
|
-
settlement:
|
|
279
|
-
operations: buildSyncApplyOperations(syncChanges, project),
|
|
280
|
-
applyResult: {
|
|
281
|
-
ok: additionalSkippedEntries.length === 0,
|
|
282
|
-
applied: [],
|
|
283
|
-
skipped: additionalSkippedEntries
|
|
284
|
-
}
|
|
285
|
-
}),
|
|
312
|
+
settlement: approvalSettlement,
|
|
286
313
|
audit: buildAuditSummaryFromApply({
|
|
287
314
|
operations: buildSyncApplyOperations(syncChanges, project),
|
|
288
315
|
applyResults: additionalSkippedEntries.length ? [{ ok: false, applied: [], skipped: additionalSkippedEntries }] : [],
|
|
@@ -336,7 +363,7 @@
|
|
|
336
363
|
status: 'running',
|
|
337
364
|
activity: { v: 1, kind: 'sync', id: writebackActivityId, state: 'running', paths: operations.map(operation => operation.path).filter(Boolean) }
|
|
338
365
|
});
|
|
339
|
-
await stageWritebackRecovery?.(operations, saveTarget, runRequireReviewing);
|
|
366
|
+
await stageWritebackRecovery?.(operations, saveTarget, runRequireReviewing, project);
|
|
340
367
|
const applied = operations.length
|
|
341
368
|
? mergeApplyResultSkipped(await assetTransferBroker.applyOperations({
|
|
342
369
|
operations,
|
|
@@ -351,12 +378,8 @@
|
|
|
351
378
|
}), additionalSkippedEntries)
|
|
352
379
|
: mergeApplyResultSkipped({ ok: true, applied: [], skipped: [] }, additionalSkippedEntries);
|
|
353
380
|
const hasConfirmedApplyResult = hasApplyResultEntries(applied);
|
|
354
|
-
for (const change of unsupportedChanges.filter(item => ['subagent_unfinished_output', 'subagent_unauthorized_edit'].includes(item.reason))) {
|
|
355
|
-
|
|
356
|
-
applied.skipped.push({ operation: { type: 'create', path: change.path }, result: { ok: false,
|
|
357
|
-
code: change.reason, reason, changedDocument: false, failure: { code: change.reason,
|
|
358
|
-
stage: 'preflight', severity: 'blocked', retryable: false, terminalState: 'blocked', changedDocument: false,
|
|
359
|
-
userMessage: reason, nextAction: 'The main agent must review the withheld file before syncing it.' } } });
|
|
381
|
+
for (const change of unsupportedChanges.filter(item => ['subagent_unfinished_output', 'subagent_unauthorized_edit', 'selection_scope_violation'].includes(item.reason))) {
|
|
382
|
+
applied.skipped.push(buildWithheldEntry(change));
|
|
360
383
|
applied.ok = false;
|
|
361
384
|
}
|
|
362
385
|
// Record the apply result + undo checkpoint IMMEDIATELY, before any
|
|
@@ -367,7 +390,7 @@
|
|
|
367
390
|
// landed in Overleaf. The manual-confirm path already records undo right
|
|
368
391
|
// after its write — match that ordering here (v1.6.2).
|
|
369
392
|
appendApplyResult(applied);
|
|
370
|
-
recordUndoFromApply(project, applied);
|
|
393
|
+
await recordUndoFromApply(project, applied);
|
|
371
394
|
const skippedEntries = getSkippedEntries(applied);
|
|
372
395
|
if (skippedEntries.length) {
|
|
373
396
|
appendPartialWritebackWarning(applied);
|
|
@@ -568,37 +591,29 @@
|
|
|
568
591
|
return 'failed';
|
|
569
592
|
}
|
|
570
593
|
|
|
571
|
-
//
|
|
572
|
-
//
|
|
573
|
-
//
|
|
574
|
-
//
|
|
575
|
-
// written files without re-downloading the project. Anything unusual —
|
|
576
|
-
// tree ops, binary writes, dirty mirror, an older native host without the
|
|
577
|
-
// method — falls back to the full zip resync below.
|
|
578
|
-
const MIRROR_CONFIRMABLE_OPERATION_TYPES = new Set(['edit']);
|
|
594
|
+
// Text-only confirmation requires the page's verified post-image digest.
|
|
595
|
+
// Local files can change after writeback; Native must match those digests
|
|
596
|
+
// before advancing its baseline. Missing evidence and non-text/tree writes
|
|
597
|
+
// retain the authoritative ZIP fallback below.
|
|
579
598
|
|
|
580
599
|
async function tryConfirmMirrorWriteback(applied, runProjectId) {
|
|
581
|
-
const entries = Array.isArray(applied?.applied) ? applied.applied : [];
|
|
582
|
-
if (!entries.length) {
|
|
583
|
-
return false;
|
|
584
|
-
}
|
|
585
|
-
const paths = [];
|
|
586
|
-
for (const entry of entries) {
|
|
587
|
-
const operation = entry?.operation || {};
|
|
588
|
-
if (!MIRROR_CONFIRMABLE_OPERATION_TYPES.has(operation.type) || !operation.path) {
|
|
589
|
-
return false;
|
|
590
|
-
}
|
|
591
|
-
paths.push(operation.path);
|
|
592
|
-
}
|
|
593
600
|
try {
|
|
601
|
+
if (!runProjectId || getCurrentProjectId() !== runProjectId) return false;
|
|
602
|
+
const proof = await writebackController.buildMirrorWritebackConfirmation?.(applied);
|
|
603
|
+
if (!proof || getCurrentProjectId() !== runProjectId) return false;
|
|
594
604
|
const response = await sendBackgroundNative({
|
|
595
605
|
method: 'mirror.confirmWriteback',
|
|
596
606
|
params: {
|
|
597
607
|
projectId: runProjectId,
|
|
598
|
-
|
|
608
|
+
...proof
|
|
599
609
|
}
|
|
600
610
|
});
|
|
601
|
-
|
|
611
|
+
const confirmations = Array.isArray(response?.result?.confirmed) ? response.result.confirmed : [];
|
|
612
|
+
const confirmed = new Map(confirmations.map(file => [file?.path, file?.hash]));
|
|
613
|
+
return getCurrentProjectId() === runProjectId && response?.ok === true && response?.result?.ok === true
|
|
614
|
+
&& confirmations.length === proof.expectedFiles.length
|
|
615
|
+
&& confirmed.size === proof.expectedFiles.length
|
|
616
|
+
&& proof.expectedFiles.every(file => confirmed.get(file.path) === file.sha256);
|
|
602
617
|
} catch (error) {
|
|
603
618
|
return false;
|
|
604
619
|
}
|
|
@@ -751,12 +766,13 @@
|
|
|
751
766
|
}
|
|
752
767
|
return buildPostWriteCompileSummary({ result, logResult });
|
|
753
768
|
} else {
|
|
754
|
-
const reason = result?.reason || tx('unknown reason', '未知原因');
|
|
769
|
+
const reason = CompileAdapter.localizeCompileReason(result?.reason, getLocale()) || tx('unknown reason', '未知原因');
|
|
755
770
|
appendRunEvent({ title: tx(`Post-write compile did not succeed: ${reason}`, `写后编译未成功:${reason}`), status: 'failed', activity: activity('failed', reason) });
|
|
756
771
|
return buildPostWriteCompileSummary({ result });
|
|
757
772
|
}
|
|
758
773
|
} catch (error) {
|
|
759
|
-
|
|
774
|
+
const reason = CompileAdapter.localizeCompileReason(error.message, getLocale());
|
|
775
|
+
appendRunEvent({ title: tx(`Post-write compile failed: ${reason}`, `写后编译出错:${reason}`), status: 'failed', activity: activity('failed', reason) });
|
|
760
776
|
return buildPostWriteCompileSummary({ error });
|
|
761
777
|
}
|
|
762
778
|
}
|
|
@@ -765,19 +781,11 @@
|
|
|
765
781
|
return compileAdapter.buildPostWriteCompileSummary(input);
|
|
766
782
|
}
|
|
767
783
|
|
|
768
|
-
//
|
|
769
|
-
//
|
|
784
|
+
// Use the same normalized failure selection as settlement and its UI, while
|
|
785
|
+
// preserving the selected producer's full evidence on the historical report.
|
|
770
786
|
function promoteWritebackSkippedFailure(applied) {
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
if (failure?.code) {
|
|
774
|
-
if (!failure.file && entry?.path) {
|
|
775
|
-
return { ...failure, file: entry.path };
|
|
776
|
-
}
|
|
777
|
-
return failure;
|
|
778
|
-
}
|
|
779
|
-
}
|
|
780
|
-
return null;
|
|
787
|
+
const failures = writebackSettlement.collectFailuresFromResult(applied);
|
|
788
|
+
return writebackSettlement.selectPrimaryFailure(failures);
|
|
781
789
|
}
|
|
782
790
|
|
|
783
791
|
function appendCompileSummaryToConclusion(conclusion, compileSummary) {
|
|
@@ -807,9 +815,10 @@
|
|
|
807
815
|
)
|
|
808
816
|
: tx('Post-write compile was triggered; Overleaf may still be updating the result.', '已触发写后编译;Overleaf 可能仍在更新结果。');
|
|
809
817
|
} else if (compileSummary.status === 'failed') {
|
|
818
|
+
const reason = compileAdapter.localizeCompileReason(compileSummary.reason, getLocale());
|
|
810
819
|
summary = tx(
|
|
811
|
-
`Post-write compile check failed: ${
|
|
812
|
-
`写后编译检查失败:${
|
|
820
|
+
`Post-write compile check failed: ${reason || 'unknown reason'}`,
|
|
821
|
+
`写后编译检查失败:${reason || '未知原因'}`
|
|
813
822
|
);
|
|
814
823
|
} else {
|
|
815
824
|
summary = tx(
|
|
@@ -16,7 +16,18 @@
|
|
|
16
16
|
let pickerOpen = false, query = '', filter = 'all';
|
|
17
17
|
let activeGeneration = null, cancelling = false;
|
|
18
18
|
let preview = null, previewBusy = false, previewFile = 'references/writing-style.md';
|
|
19
|
-
const
|
|
19
|
+
const ownMessagePairs = new Map();
|
|
20
|
+
const tx = (en, zh) => {
|
|
21
|
+
const pair = [en, zh];
|
|
22
|
+
ownMessagePairs.set(en, pair); ownMessagePairs.set(zh, pair);
|
|
23
|
+
return deps.tx(en, zh);
|
|
24
|
+
};
|
|
25
|
+
// Cached status text keeps its diagnostic value; only known copy is
|
|
26
|
+
// projected again when sync renders under a different language.
|
|
27
|
+
const displayMessage = value => {
|
|
28
|
+
const pair = ownMessagePairs.get(value);
|
|
29
|
+
return pair ? deps.tx(...pair) : value;
|
|
30
|
+
};
|
|
20
31
|
const node = (tag, className, text) => {
|
|
21
32
|
const element = document.createElement(tag);
|
|
22
33
|
if (className) element.className = className;
|
|
@@ -62,6 +73,12 @@
|
|
|
62
73
|
const value = deps.getScope();
|
|
63
74
|
return value?.accountScopeId && value?.projectId ? value : null;
|
|
64
75
|
}
|
|
76
|
+
function errorMessage(message, code) {
|
|
77
|
+
// codex.run currently wraps style-build error codes as codex_run_failed.
|
|
78
|
+
if (code === 'writing_style_busy' || message === 'Writing-style settings are being updated by another Native Host. Retry shortly.') return tx('Writing-style settings are being updated by another Native Host. Retry shortly.', '写作风格设置正在由另一个本地进程更新,请稍后重试。');
|
|
79
|
+
if (code === 'writing_style_changed' || message === 'The saved writing skill changed. Rebuild it in General settings.' || message === 'Writing-style asset integrity changed.') return tx(message || 'The saved writing skill changed. Rebuild it in General settings.', '写作风格资产不可用,请在常规设置中重新生成。');
|
|
80
|
+
return message;
|
|
81
|
+
}
|
|
65
82
|
function alignScope() {
|
|
66
83
|
const next = currentScope(), key = next ? next.accountScopeId + ':' + next.projectId : '';
|
|
67
84
|
if (scopeKey !== key) {
|
|
@@ -83,7 +100,7 @@
|
|
|
83
100
|
async function request(method, params = {}) {
|
|
84
101
|
const response = await deps.request({ method, params });
|
|
85
102
|
if (!response?.ok) {
|
|
86
|
-
const error = new Error(response?.error?.message || tx('The request could not be completed.', '请求未能完成。'));
|
|
103
|
+
const error = new Error(errorMessage(response?.error?.message, response?.error?.code) || tx('The request could not be completed.', '请求未能完成。'));
|
|
87
104
|
error.code = response?.error?.code || 'writing_style_failed'; throw error;
|
|
88
105
|
}
|
|
89
106
|
return response.result;
|
|
@@ -105,7 +122,7 @@
|
|
|
105
122
|
meta = value;
|
|
106
123
|
if (setupEnabled === null) desiredEnabled = value.enabled === true;
|
|
107
124
|
if (!dirty && !activeGeneration) restoreSources(value);
|
|
108
|
-
if (value.error && !message) { message = value.error; messageTone = 'error'; }
|
|
125
|
+
if (value.error && !message) { message = errorMessage(value.error, value.errorCode); messageTone = 'error'; }
|
|
109
126
|
clearTimeout(pollTimer); pollTimer = null;
|
|
110
127
|
if (value.building) pollTimer = setTimeout(() => { void ensureLoaded(true).catch(() => {}); }, 1800);
|
|
111
128
|
else if (!activeGeneration) cancelling = false;
|
|
@@ -198,7 +215,8 @@
|
|
|
198
215
|
activeGeneration = operation; cancelling = false; pickerOpen = false; preview = null;
|
|
199
216
|
message = ''; messageTone = ''; expanded = true; render();
|
|
200
217
|
const stillActive = () => {
|
|
201
|
-
|
|
218
|
+
const current = currentScope();
|
|
219
|
+
if (operation.cancelled || generation !== epoch || current?.accountScopeId !== target.accountScopeId || current?.projectId !== target.projectId) {
|
|
202
220
|
const error = new Error(tx('Style generation cancelled.', '已取消风格生成。'));
|
|
203
221
|
error.code = 'codex_cancelled'; throw error;
|
|
204
222
|
}
|
|
@@ -206,7 +224,7 @@
|
|
|
206
224
|
try {
|
|
207
225
|
const settings = await deps.getModelSettings();
|
|
208
226
|
stillActive();
|
|
209
|
-
const attachments = [], selected = [];
|
|
227
|
+
const attachments = [], selected = [], projectFiles = [];
|
|
210
228
|
for (const source of chosen) {
|
|
211
229
|
stillActive();
|
|
212
230
|
if (source.kind === 'pdf') {
|
|
@@ -218,10 +236,11 @@
|
|
|
218
236
|
const snapshot = await deps.getReferenceSnapshot(source.projectId, operation.readId);
|
|
219
237
|
operation.readId = ''; stillActive();
|
|
220
238
|
if (snapshot?.projectId !== source.projectId || snapshot?.capabilities?.referenceTextComplete !== true) {
|
|
221
|
-
throw new Error(tx('The reference project could not be read completely: ', '参考项目未能完整读取:'
|
|
239
|
+
throw new Error(tx('The reference project could not be read completely: ' + source.name, '参考项目未能完整读取:' + source.name));
|
|
222
240
|
}
|
|
223
241
|
const body = new Blob([JSON.stringify(snapshot)], { type: 'application/json' });
|
|
224
|
-
if (body.size > LIMITS.maxProjectBytes) throw new Error(tx('Reference project is too large: ', '参考项目过大:'
|
|
242
|
+
if (body.size > LIMITS.maxProjectBytes) throw new Error(tx('Reference project is too large: ' + source.name, '参考项目过大:' + source.name));
|
|
243
|
+
projectFiles.push(...(snapshot.files || []).map(file => ({ ...file, path: source.projectId + '/' + file.path })));
|
|
225
244
|
const id = crypto.randomUUID();
|
|
226
245
|
attachments.push({ id, name: source.projectId + '.writing-reference.json', size: body.size,
|
|
227
246
|
mimeType: 'application/json', kind: 'file', contentBase64: await encode(body) });
|
|
@@ -229,6 +248,11 @@
|
|
|
229
248
|
} else selected.push({ kind: 'saved', sourceId: source.sourceId });
|
|
230
249
|
}
|
|
231
250
|
stillActive();
|
|
251
|
+
if (projectFiles.length) {
|
|
252
|
+
const check = await deps.preflightProjectReferences({ project: { files: projectFiles }, assertCurrent: stillActive });
|
|
253
|
+
stillActive();
|
|
254
|
+
if (check.blocked) throw new Error(tx('Blocked: sensitive content review required', '已阻止:需要处理敏感内容'));
|
|
255
|
+
}
|
|
232
256
|
operation.phase = 'learning'; operation.sourceName = ''; render();
|
|
233
257
|
const tracked = await deps.startBuild({ ...settings, ...target, mode: 'ask',
|
|
234
258
|
task: 'Generate the selected project writing skill and its section references.',
|
|
@@ -241,7 +265,7 @@
|
|
|
241
265
|
const response = await tracked.promise;
|
|
242
266
|
if (generation !== epoch) return;
|
|
243
267
|
if (!response?.ok) {
|
|
244
|
-
const error = new Error(response?.error?.message || tx('Style generation failed.', '风格生成失败。'));
|
|
268
|
+
const error = new Error(errorMessage(response?.error?.message, response?.error?.code) || tx('Style generation failed.', '风格生成失败。'));
|
|
245
269
|
error.code = response?.error?.code; throw error;
|
|
246
270
|
}
|
|
247
271
|
const result = response.result?.writingStyle;
|
|
@@ -253,7 +277,7 @@
|
|
|
253
277
|
} catch (error) {
|
|
254
278
|
if (generation === epoch) {
|
|
255
279
|
message = operation.cancelled || error.code === 'codex_cancelled'
|
|
256
|
-
? tx('Style generation cancelled.', '已取消风格生成。') : error.message;
|
|
280
|
+
? tx('Style generation cancelled.', '已取消风格生成。') : errorMessage(error.message, error.code);
|
|
257
281
|
messageTone = operation.cancelled || error.code === 'codex_cancelled' ? '' : 'error';
|
|
258
282
|
}
|
|
259
283
|
} finally {
|
|
@@ -340,7 +364,7 @@
|
|
|
340
364
|
list.append(loading);
|
|
341
365
|
} else if (projectError) {
|
|
342
366
|
const error = node('div', 'ws2-picker-state ws2-picker-error');
|
|
343
|
-
error.append(node('strong', '', tx('Projects could not be loaded', '项目列表未能加载')), node('p', '', projectError),
|
|
367
|
+
error.append(node('strong', '', tx('Projects could not be loaded', '项目列表未能加载')), node('p', '', displayMessage(projectError)),
|
|
344
368
|
button(tx('Retry', '重试'), () => { void loadProjects(true); }, 'ws2-button', 'retry-projects'));
|
|
345
369
|
list.append(error);
|
|
346
370
|
} else {
|
|
@@ -540,7 +564,7 @@
|
|
|
540
564
|
}
|
|
541
565
|
}
|
|
542
566
|
if (message || !meta) {
|
|
543
|
-
const status = node('div', 'ws2-status', message || tx('Loading writing-style settings…', '正在加载写作风格设置…'));
|
|
567
|
+
const status = node('div', 'ws2-status', displayMessage(message) || tx('Loading writing-style settings…', '正在加载写作风格设置…'));
|
|
544
568
|
status.dataset.tone = messageTone;
|
|
545
569
|
status.setAttribute('role', messageTone === 'error' ? 'alert' : 'status');
|
|
546
570
|
if (!meta && message) status.append(button(tx('Retry', '重试'), () => {
|