codex-overleaf-link 2.5.0 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +155 -392
- package/README.zh-CN.md +154 -393
- 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 +283 -55
- package/extension/src/backgroundBootstrapMigration.js +181 -0
- package/extension/src/backgroundSharedSessions.js +129 -16
- package/extension/src/backgroundUpdateCoordinator.js +242 -29
- package/extension/src/backgroundUpdateSafety.js +173 -0
- package/extension/src/content/activeTurnControl.js +171 -8
- package/extension/src/content/applyResultFormatters.js +46 -73
- package/extension/src/content/assetTransferBroker.js +72 -14
- package/extension/src/content/composerAttachments.js +104 -47
- package/extension/src/content/composerPanel.js +110 -12
- package/extension/src/content/contentRuntime.js +1555 -926
- package/extension/src/content/contextTray.js +8 -2
- package/extension/src/content/contextUsageView.js +267 -0
- package/extension/src/content/diagnosticsController.js +122 -96
- package/extension/src/content/diagnosticsExport.js +19 -5
- package/extension/src/content/diagnosticsPanel.js +48 -0
- package/extension/src/content/directoryCleanupRecovery.js +54 -0
- package/extension/src/content/generated/content.bundle.js +11077 -3412
- package/extension/src/content/generated/content.bundle.meta.json +14 -3
- package/extension/src/content/globalPreferencesController.js +1 -1
- package/extension/src/content/legacyGlobalRegistry.js +10 -0
- package/extension/src/content/localSkillsPanel.js +104 -14
- package/extension/src/content/markdownDomRenderer.js +5 -3
- package/extension/src/content/markdownText.js +47 -11
- package/extension/src/content/modelPicker.js +8 -4
- package/extension/src/content/nativeCompatibilityController.js +74 -30
- package/extension/src/content/otWarmMirror.js +5 -6
- package/extension/src/content/otWarmMirrorController.js +17 -20
- package/extension/src/content/pageBridgeClient.js +17 -4
- package/extension/src/content/panelMaintenance.js +46 -14
- package/extension/src/content/panelRenderer.js +57 -27
- package/extension/src/content/pendingInputView.js +1 -1
- package/extension/src/content/projectProviderSelection.js +13 -4
- package/extension/src/content/projectSessionCleanup.js +109 -62
- package/extension/src/content/projectSettingsCoordinator.js +67 -6
- package/extension/src/content/providerSettingsCoordinator.js +46 -8
- package/extension/src/content/providerSettingsDialog.js +73 -14
- package/extension/src/content/recentProjects.js +15 -8
- package/extension/src/content/runActivitySummary.js +160 -18
- package/extension/src/content/runCompletionMeta.js +532 -0
- package/extension/src/content/runController.js +39 -15
- package/extension/src/content/runFailureNotice.js +29 -13
- package/extension/src/content/runGuidanceController.js +72 -27
- package/extension/src/content/runGuidanceView.js +59 -10
- package/extension/src/content/runPresence.js +18 -8
- package/extension/src/content/runQueueScheduler.js +157 -47
- package/extension/src/content/runRecoveryActions.js +147 -0
- package/extension/src/content/runResultActions.js +28 -143
- package/extension/src/content/runReviewController.js +633 -0
- package/extension/src/content/runScrollLayout.js +21 -2
- package/extension/src/content/runTimelineView.js +367 -175
- package/extension/src/content/scopedPersistencePanelState.js +134 -20
- package/extension/src/content/selectionContextView.js +1 -1
- package/extension/src/content/sensitivePreflight.js +153 -0
- package/extension/src/content/sessionForkController.js +80 -17
- package/extension/src/content/sessionManager.js +50 -4
- package/extension/src/content/sessionMenuView.js +54 -18
- package/extension/src/content/sessionPersistence.js +25 -5
- package/extension/src/content/settingsPanel.js +22 -5
- package/extension/src/content/settingsWorkbench.js +132 -89
- package/extension/src/content/sharedSessionViewSync.js +49 -11
- package/extension/src/content/subagentActivityView.js +23 -7
- package/extension/src/content/updateIdle.js +4 -1
- package/extension/src/content/updateNotice.js +118 -12
- package/extension/src/content/updateSafetyReceiver.js +58 -0
- package/extension/src/content/writebackController.js +53 -4
- package/extension/src/content/writebackOrchestrator.js +116 -94
- package/extension/src/content/writingStyleSettings.js +35 -11
- package/extension/src/page/binaryAssetUploader.js +136 -14
- package/extension/src/page/compileBridge.js +193 -180
- package/extension/src/page/fileMover.js +256 -0
- package/extension/src/page/overleafCapabilities.js +4 -1
- 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/textCoordinates.js +17 -1
- package/extension/src/page/textFileCreator.js +114 -26
- 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 +103 -155
- package/extension/src/pageBridge.js +126 -69
- package/extension/src/popup.js +62 -10
- package/extension/src/shared/agentTranscript.js +373 -17
- package/extension/src/shared/auditRecords.js +4 -0
- package/extension/src/shared/compatibility.js +22 -6
- package/extension/src/shared/compileAdapter.js +129 -66
- package/extension/src/shared/composerAttachmentDraft.js +156 -0
- package/extension/src/shared/contextUsage.js +125 -0
- package/extension/src/shared/directoryCleanupProof.js +43 -0
- package/extension/src/shared/failureReasons.js +349 -4
- package/extension/src/shared/globalPreferences.js +30 -13
- package/extension/src/shared/governanceRules.js +4 -37
- package/extension/src/shared/i18n.js +590 -24
- package/extension/src/shared/lineReferences.js +38 -31
- 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/panelIcons.js +39 -0
- package/extension/src/shared/projectFiles.js +21 -44
- package/extension/src/shared/providerProfiles.js +5 -3
- package/extension/src/shared/reviewing.js +130 -1
- package/extension/src/shared/runActivityModel.js +70 -4
- package/extension/src/shared/runExecutionSnapshot.js +13 -0
- package/extension/src/shared/runInputQueue.js +48 -5
- package/extension/src/shared/selectionContext.js +12 -3
- package/extension/src/shared/sessionState.js +132 -28
- package/extension/src/shared/sessionTitle.js +14 -1
- package/extension/src/shared/settlementFacts.js +10 -2
- package/extension/src/shared/sharedSessionBridge.js +118 -27
- package/extension/src/shared/sharedSessionTransfer.js +140 -0
- package/extension/src/shared/staleGuard.js +166 -52
- package/extension/src/shared/storageDb.js +90 -26
- package/extension/src/shared/storageMigration.js +78 -112
- package/extension/src/shared/storageRunActions.js +267 -30
- package/extension/src/shared/subagentActivityModel.js +1 -1
- package/extension/src/shared/undoOperations.js +253 -32
- package/extension/src/shared/updateConsent.js +6 -4
- package/extension/src/shared/updateRuntimeIdentity.js +17 -3
- package/extension/src/shared/writebackIntent.js +16 -0
- package/extension/src/shared/writebackOperationEvidence.js +4 -15
- package/extension/src/shared/writebackSettlement.js +58 -40
- package/extension/styles/panel.css +1123 -493
- 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 +269 -0
- package/native-host/src/bootstrapUpdateWatchdog.js +44 -0
- package/native-host/src/chatBridgeRequest.js +60 -6
- package/native-host/src/chatBridgeResponse.js +20 -1
- package/native-host/src/chatCompletionsBridge.js +48 -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 +114 -127
- package/native-host/src/codexProviderTest.js +24 -26
- package/native-host/src/codexSessionRunner.js +234 -198
- 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/providerContextError.js +20 -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 +92 -57
- package/native-host/src/textPatch.js +473 -377
- package/native-host/src/tomlStatements.js +140 -0
- package/native-host/src/updateManager.js +409 -46
- package/native-host/src/writingStyleRuntime.js +57 -29
- package/native-host/src/writingStyleStore.js +73 -0
- package/package.json +5 -8
- package/scripts/install-managed.mjs +1 -0
|
@@ -1,17 +1,29 @@
|
|
|
1
1
|
(function initCodexOverleafRunController(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.CodexOverleafRunController = factory();
|
|
5
|
+
root.CodexOverleafRunController = factory(root.CodexOverleafProjectFiles);
|
|
6
6
|
}
|
|
7
|
-
})(typeof window !== 'undefined' ? window : globalThis, function runControllerFactory() {
|
|
7
|
+
})(typeof window !== 'undefined' ? window : globalThis, function runControllerFactory(ProjectFiles) {
|
|
8
8
|
'use strict';
|
|
9
9
|
|
|
10
10
|
const DEFAULT_EXPECTED_MIRROR_FRESHNESS_MS = 5 * 60 * 1000;
|
|
11
11
|
|
|
12
|
+
function captureSessionContext(state = {}) {
|
|
13
|
+
// This is the prompt projection, not the mutable visible-session alias.
|
|
14
|
+
const session = state.session;
|
|
15
|
+
return session ? JSON.parse(JSON.stringify(session)) : null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function isSubmittedComposerUnchanged(submitted, current) {
|
|
19
|
+
return ['task', 'attachments', 'skillInvocation', 'selectionContext'].every(key =>
|
|
20
|
+
JSON.stringify(submitted[key] ?? null) === JSON.stringify(current[key] ?? null));
|
|
21
|
+
}
|
|
22
|
+
|
|
12
23
|
function buildCodexRunParams({
|
|
13
24
|
currentProjectId,
|
|
14
25
|
state = {},
|
|
26
|
+
locale = state.locale,
|
|
15
27
|
task,
|
|
16
28
|
project,
|
|
17
29
|
useExistingMirror,
|
|
@@ -22,6 +34,7 @@
|
|
|
22
34
|
customInstructions,
|
|
23
35
|
skillLoadingSettings,
|
|
24
36
|
enabledCodexOverleafSkillIds,
|
|
37
|
+
enabledCodexLocalSkillIds,
|
|
25
38
|
attachments,
|
|
26
39
|
skillInvocation,
|
|
27
40
|
compileLogContext,
|
|
@@ -45,17 +58,21 @@
|
|
|
45
58
|
otWarmStart: useExistingMirror && otWarmStart ? true : undefined,
|
|
46
59
|
warmStartStrategy: useExistingMirror && otWarmStart ? 'ot-warm-mirror' : undefined,
|
|
47
60
|
focusFiles,
|
|
61
|
+
focusFilesAreLiteral: true,
|
|
48
62
|
restrictToFocusFiles: restrictToFocusFiles || undefined,
|
|
49
63
|
model: state.model,
|
|
50
64
|
reasoningEffort: state.reasoningEffort,
|
|
51
65
|
speedTier: state.speedTier,
|
|
52
|
-
locale:
|
|
66
|
+
locale: locale === 'zh' ? 'zh' : 'en',
|
|
53
67
|
session: state.session,
|
|
54
68
|
threadId: codexThreadId || undefined,
|
|
55
69
|
customInstructions: normalizedCustomInstructions || undefined,
|
|
56
70
|
loadCodexLocalSkills: skillLoadingSettings?.loadCodexLocalSkills !== false,
|
|
57
71
|
loadCodexOverleafSkills: skillLoadingSettings?.loadCodexOverleafSkills !== false,
|
|
58
72
|
enabledCodexOverleafSkillIds: normalizedEnabledSkillIds,
|
|
73
|
+
enabledCodexLocalSkillIds: Array.isArray(enabledCodexLocalSkillIds)
|
|
74
|
+
? [...new Set(enabledCodexLocalSkillIds.filter(id => typeof id === 'string' && isSafeSkillId(id)))]
|
|
75
|
+
: undefined,
|
|
59
76
|
skillInvocation: normalizedSkillInvocation || undefined,
|
|
60
77
|
attachments: normalizedAttachments.length ? normalizedAttachments : undefined
|
|
61
78
|
};
|
|
@@ -106,13 +123,14 @@
|
|
|
106
123
|
project = {},
|
|
107
124
|
snapshotWarnings = {},
|
|
108
125
|
focusFiles = [],
|
|
126
|
+
focusFilesAreLiteral = false,
|
|
109
127
|
isUsableProjectFileContent = defaultIsUsableProjectFileContent
|
|
110
128
|
} = {}) {
|
|
111
129
|
const blocking = Array.isArray(snapshotWarnings.blocking) ? snapshotWarnings.blocking : [];
|
|
112
130
|
if (!blocking.length || !blocking.some(isFullSnapshotWarning)) {
|
|
113
131
|
return false;
|
|
114
132
|
}
|
|
115
|
-
const normalizedFocusFiles = normalizeFocusFiles(focusFiles);
|
|
133
|
+
const normalizedFocusFiles = normalizeFocusFiles(focusFiles, focusFilesAreLiteral === true);
|
|
116
134
|
if (!normalizedFocusFiles.length) {
|
|
117
135
|
return false;
|
|
118
136
|
}
|
|
@@ -122,10 +140,10 @@
|
|
|
122
140
|
|
|
123
141
|
const textFilesByPath = new Map((project.files || [])
|
|
124
142
|
.filter(file => file && typeof file.path === 'string' && typeof file.content === 'string')
|
|
125
|
-
.map(file => [normalizeFocusPath(file.path), file]));
|
|
143
|
+
.map(file => [normalizeFocusPath(file.path, true), file]));
|
|
126
144
|
|
|
127
145
|
return normalizedFocusFiles.every(filePath => {
|
|
128
|
-
const file = textFilesByPath.get(
|
|
146
|
+
const file = textFilesByPath.get(filePath);
|
|
129
147
|
return file && isUsableProjectFileContent(file.content);
|
|
130
148
|
});
|
|
131
149
|
}
|
|
@@ -138,11 +156,11 @@
|
|
|
138
156
|
return /Full project snapshot was not captured/i.test(String(warning || ''));
|
|
139
157
|
}
|
|
140
158
|
|
|
141
|
-
function normalizeFocusFiles(value) {
|
|
159
|
+
function normalizeFocusFiles(value, literal = false) {
|
|
142
160
|
const seen = new Set();
|
|
143
161
|
const files = [];
|
|
144
162
|
for (const item of Array.isArray(value) ? value : []) {
|
|
145
|
-
const filePath = normalizeFocusPath(item);
|
|
163
|
+
const filePath = normalizeFocusPath(item, literal);
|
|
146
164
|
if (!filePath || seen.has(filePath)) {
|
|
147
165
|
continue;
|
|
148
166
|
}
|
|
@@ -152,7 +170,8 @@
|
|
|
152
170
|
return files;
|
|
153
171
|
}
|
|
154
172
|
|
|
155
|
-
function normalizeFocusPath(value) {
|
|
173
|
+
function normalizeFocusPath(value, literal = false) {
|
|
174
|
+
if (literal) return ProjectFiles.normalizeSafeProjectPath(value);
|
|
156
175
|
return String(value || '')
|
|
157
176
|
.replace(/^@file:/i, '')
|
|
158
177
|
.replace(/\\/g, '/')
|
|
@@ -187,19 +206,22 @@
|
|
|
187
206
|
if (!isSafeSkillId(id)) {
|
|
188
207
|
return null;
|
|
189
208
|
}
|
|
190
|
-
const
|
|
191
|
-
|
|
209
|
+
const installer = id === 'skill-installer' && (value?.scope == null || value.scope === 'builtin');
|
|
210
|
+
const fallbackTitle = installer ? 'Skill Installer' : id;
|
|
211
|
+
const title = String(value?.title || fallbackTitle).trim().slice(0, 80) || fallbackTitle;
|
|
212
|
+
if (installer) {
|
|
192
213
|
return { id, title };
|
|
193
214
|
}
|
|
194
|
-
if (
|
|
215
|
+
if (!['codex-overleaf', 'codex-local'].includes(value?.scope)) {
|
|
195
216
|
return null;
|
|
196
217
|
}
|
|
197
|
-
return { id, title, scope:
|
|
218
|
+
return { id, title, scope: value.scope };
|
|
198
219
|
}
|
|
199
220
|
|
|
200
221
|
function isSafeSkillId(id) {
|
|
201
222
|
return /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,79}$/.test(String(id || ''))
|
|
202
|
-
&& !String(id || '').includes('..')
|
|
223
|
+
&& !String(id || '').includes('..')
|
|
224
|
+
&& !['__proto__', 'prototype', 'constructor'].includes(id);
|
|
203
225
|
}
|
|
204
226
|
|
|
205
227
|
function normalizeAttachmentName(value) {
|
|
@@ -266,6 +288,8 @@
|
|
|
266
288
|
}
|
|
267
289
|
|
|
268
290
|
return {
|
|
291
|
+
captureSessionContext,
|
|
292
|
+
isSubmittedComposerUnchanged,
|
|
269
293
|
buildCodexRunParams,
|
|
270
294
|
canUseFocusedPartialSnapshot,
|
|
271
295
|
shouldRestrictWritebackToFocus,
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
(function initRunFailureNotice(root, factory) {
|
|
2
2
|
if (typeof module === 'object' && module.exports) {
|
|
3
|
-
module.exports = factory();
|
|
3
|
+
module.exports = factory(require('../shared/failureReasons'));
|
|
4
4
|
} else {
|
|
5
|
-
root.CodexOverleafModuleRegistry.define('RunFailureNotice', [], factory);
|
|
5
|
+
root.CodexOverleafModuleRegistry.define('RunFailureNotice', ['FailureReasons'], factory);
|
|
6
6
|
}
|
|
7
|
-
})(typeof window !== 'undefined' ? window : globalThis, function runFailureNoticeFactory() {
|
|
7
|
+
})(typeof window !== 'undefined' ? window : globalThis, function runFailureNoticeFactory(FailureReasons) {
|
|
8
8
|
'use strict';
|
|
9
9
|
|
|
10
10
|
const REVIEW_CODES = new Set(['write_timeout', 'partial_write_needs_review']);
|
|
11
11
|
const CANCEL_CODES = new Set(['cancelled', 'user_cancelled', 'codex_cancelled']);
|
|
12
|
-
|
|
13
12
|
// Old attempts remain in the stored audit trail; a resolved undo must not
|
|
14
13
|
// advertise them as a current failure in the transcript or its summary.
|
|
15
14
|
function projectResolvedUndo(run) {
|
|
@@ -27,18 +26,25 @@
|
|
|
27
26
|
document: documentRef = globalThis.document } = {}) {
|
|
28
27
|
const notices = new WeakMap();
|
|
29
28
|
|
|
30
|
-
function append(report, event = {}, run = {}) {
|
|
29
|
+
function append(report, event = {}, run = {}, outcomeTarget = null) {
|
|
31
30
|
const projection = typeof projectRunSettlement === 'function' && run
|
|
32
31
|
? projectRunSettlement(run) : null;
|
|
33
32
|
const failure = projection?.primaryFailure || event.failure;
|
|
34
33
|
if (report.dataset.status === 'cancelled' || CANCEL_CODES.has(failure?.code)) return;
|
|
35
34
|
const facts = run?.settlement || run?.settlementFacts || {};
|
|
36
|
-
const unconfirmed = REVIEW_CODES.has(failure?.code)
|
|
35
|
+
const unconfirmed = projection?.requiresWriteReview === true || REVIEW_CODES.has(failure?.code)
|
|
37
36
|
|| facts.documentEffect === 'possibly-changed'
|
|
38
37
|
|| (facts.evidence?.applied === 'unknown' && facts.evidence?.settled === 'needs-review');
|
|
39
38
|
const partial = facts.evidence?.applied === 'partial' || run?.partialWriteback === true;
|
|
40
39
|
if (!failure && !unconfirmed && !partial && report.dataset.status !== 'failed') return;
|
|
41
40
|
|
|
41
|
+
// A quiet outcome replaces the notice's presentation, not its recovery
|
|
42
|
+
// selection or precedence. Keep the same context for prepareRecovery.
|
|
43
|
+
if (outcomeTarget) {
|
|
44
|
+
notices.set(report, { notice: outcomeTarget, unconfirmed, failure, file: safeProjectPath(failure?.file) });
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
42
48
|
const notice = documentRef.createElement('aside');
|
|
43
49
|
notice.className = 'run-feedback';
|
|
44
50
|
notice.dataset.tone = unconfirmed || partial || failure?.terminalState === 'degraded'
|
|
@@ -49,9 +55,14 @@
|
|
|
49
55
|
heading.textContent = tr(unconfirmed ? 'runFeedbackUnconfirmedTitle'
|
|
50
56
|
: partial ? 'runFeedbackPartialTitle' : 'runFeedbackFailedTitle');
|
|
51
57
|
const message = documentRef.createElement('p');
|
|
52
|
-
const detail = typeof failure?.userMessage === 'string' ? failure.userMessage : '';
|
|
53
58
|
const file = safeProjectPath(failure?.file);
|
|
54
|
-
|
|
59
|
+
// Localization is a display projection. Recovery keeps the original
|
|
60
|
+
// failure and its trusted file, even when historical text yields a path.
|
|
61
|
+
const display = FailureReasons?.formatFailureReasonForDisplay?.(failure, undefined, key => {
|
|
62
|
+
const value = tr(key);
|
|
63
|
+
return value && value !== key ? value : undefined;
|
|
64
|
+
});
|
|
65
|
+
message.textContent = sanitizeText((display?.message || [file, failure?.userMessage].filter(Boolean).join(': ')).slice(0, 1000))
|
|
55
66
|
|| tr('runFeedbackGeneric');
|
|
56
67
|
notice.append(heading, message);
|
|
57
68
|
if (unconfirmed) {
|
|
@@ -74,10 +85,15 @@
|
|
|
74
85
|
return row.value;
|
|
75
86
|
}
|
|
76
87
|
|
|
77
|
-
function
|
|
88
|
+
function getRecoveryContext(report) {
|
|
78
89
|
const info = notices.get(report);
|
|
79
|
-
|
|
80
|
-
|
|
90
|
+
return { target: info?.notice || report, failure: info?.failure, unconfirmed: info?.unconfirmed === true, file: info?.file || '' };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function prepareRecovery(report, { openFile } = {}) {
|
|
94
|
+
const info = getRecoveryContext(report);
|
|
95
|
+
const target = info.target;
|
|
96
|
+
if (!info.unconfirmed) return { target, handled: false, failure: info.failure };
|
|
81
97
|
const canOpen = Boolean(info.file && typeof openFile === 'function');
|
|
82
98
|
const button = documentRef.createElement('button');
|
|
83
99
|
button.type = 'button';
|
|
@@ -100,10 +116,10 @@
|
|
|
100
116
|
}
|
|
101
117
|
});
|
|
102
118
|
target.append(button);
|
|
103
|
-
return { target, handled: true };
|
|
119
|
+
return { target, handled: true, failure: info.failure };
|
|
104
120
|
}
|
|
105
121
|
|
|
106
|
-
return Object.freeze({ append, formatMetaValue, prepareRecovery });
|
|
122
|
+
return Object.freeze({ append, formatMetaValue, getRecoveryContext, prepareRecovery });
|
|
107
123
|
}
|
|
108
124
|
|
|
109
125
|
function safeProjectPath(value) {
|
|
@@ -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
|
})();
|
|
@@ -39,6 +39,10 @@
|
|
|
39
39
|
const MARK_STYLE = '--mark-leaf-origin:70.6% 41.6%;--mark-cloud-origin:44.3% 55%';
|
|
40
40
|
const ICONS = {
|
|
41
41
|
alert: '<circle cx="8" cy="8" r="6"/><path d="M8 4.5v4 M8 11h.01"/>',
|
|
42
|
+
minus: '<circle cx="8" cy="8" r="6"/><path d="M5 8h6"/>',
|
|
43
|
+
half: '<circle cx="6" cy="6" r="5"/><path d="M6 1a5 5 0 0 0 0 10z" fill="currentColor" stroke="none"/>',
|
|
44
|
+
written: '<path d="m2.4 6.3 2.4 2.4 4.8-5.2" stroke-width="1.5"/>',
|
|
45
|
+
skipped: '<path d="m3 3 6 6M9 3 3 9" stroke-width="1.5"/>',
|
|
42
46
|
check: '<path d="m3.5 8 3 3 6-6"/>',
|
|
43
47
|
file: '<path d="M9 2H4v12h8V5L9 2Z M9 2v4h3 M6 9h4 M6 11h3"/>',
|
|
44
48
|
refresh: '<path d="M4.2 5.3A5 5 0 1 1 3.6 10"/><path d="M4.2 2.2v3.1H1.1"/>'
|
|
@@ -49,7 +53,8 @@
|
|
|
49
53
|
})[char]);
|
|
50
54
|
|
|
51
55
|
function icon(name, className = 'codex-glyph') {
|
|
52
|
-
|
|
56
|
+
const size = ['half', 'written', 'skipped'].includes(name) ? 12 : 16;
|
|
57
|
+
return '<svg class="' + className + '" viewBox="0 0 ' + size + ' ' + size + '" aria-hidden="true" focusable="false">'
|
|
53
58
|
+ (ICONS[name] || ICONS.check) + '</svg>';
|
|
54
59
|
}
|
|
55
60
|
|
|
@@ -103,14 +108,16 @@
|
|
|
103
108
|
const files = tx(facts.files + (facts.files === 1 ? ' file' : ' files'), facts.files + ' 个文件');
|
|
104
109
|
// A clipped history only proves the files it recorded.
|
|
105
110
|
parts.push('<span>' + escapeHtml(facts.filesClipped ? tx(files + ' recorded', '已记录 ' + files) : files) + '</span>');
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
111
|
+
// Show only nonzero sides; a red "−0" reads as a problem that isn't there.
|
|
112
|
+
const counts = [];
|
|
113
|
+
if (Number(facts.added) > 0) counts.push('<span class="run-summary-add">+' + Number(facts.added) + '</span>');
|
|
114
|
+
if (Number(facts.removed) > 0) counts.push('<span class="run-summary-del">−' + Number(facts.removed) + '</span>');
|
|
115
|
+
if (counts.length) parts.push('<span>' + counts.join(' ') + '</span>');
|
|
110
116
|
} else if (facts.steps > 0) {
|
|
111
|
-
parts.push('<span>' + escapeHtml(tx(facts.steps + ' steps', facts.steps + ' 步')) + '</span>');
|
|
117
|
+
parts.push('<span>' + escapeHtml(tx(facts.steps + (facts.steps === 1 ? ' step' : ' steps'), facts.steps + ' 步')) + '</span>');
|
|
112
118
|
}
|
|
113
119
|
if (facts.compiled) parts.push('<span>' + escapeHtml(tx('compiled', '已编译')) + '</span>');
|
|
120
|
+
if (facts.compileChip) parts.push('<span class="run-summary-compile-chip">' + escapeHtml(facts.compileChip) + '</span>');
|
|
114
121
|
if (facts.firstCompile) {
|
|
115
122
|
parts.push('<span class="run-summary-qed">' + escapeHtml(tx('Q.E.D. — first clean compile', 'Q.E.D. · 首次编译通过')) + '</span>');
|
|
116
123
|
}
|
|
@@ -253,8 +260,11 @@
|
|
|
253
260
|
el.dataset.presence = facts.kind || 'plain';
|
|
254
261
|
if (facts.kind === 'done' || facts.kind === 'stopped') {
|
|
255
262
|
el.innerHTML = settledHtml({ ...facts, firstCompile }, tx, formatElapsed);
|
|
256
|
-
} else if (
|
|
257
|
-
el.innerHTML = icon('alert') + '<span>' + escapeHtml(facts.text) + '</span>';
|
|
263
|
+
} else if (['failed', 'not-started', 'not-started-cancelled'].includes(facts.kind)) {
|
|
264
|
+
el.innerHTML = icon(facts.kind === 'not-started-cancelled' ? 'minus' : 'alert') + '<span>' + escapeHtml(facts.text) + '</span>';
|
|
265
|
+
} else if (['didnt-finish', 'partly-written', 'not-written', 'check-overleaf'].includes(facts.kind)) {
|
|
266
|
+
el.innerHTML = icon(facts.kind === 'check-overleaf' ? 'alert' : facts.kind === 'partly-written' ? 'half' : 'minus') + '<span>' + escapeHtml(facts.text) + '</span>'
|
|
267
|
+
+ (Number.isFinite(facts.elapsedMs) ? '<span class="run-summary-sep">·</span><span>' + escapeHtml(formatElapsed(facts.elapsedMs)) + '</span>' : '');
|
|
258
268
|
} else {
|
|
259
269
|
el.textContent = facts.text || '';
|
|
260
270
|
}
|