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,18 +1,29 @@
|
|
|
1
|
-
(function
|
|
1
|
+
(function (root, factory) {
|
|
2
|
+
if (typeof module === 'object' && module.exports) module.exports = factory(require('./runRecoveryActions'));
|
|
3
|
+
else root.CodexOverleafModuleRegistry.define('RunTimelineView', ['RunRecoveryActions'], factory);
|
|
4
|
+
})(typeof window !== 'undefined' ? window : globalThis, function (RunRecoveryActions) {
|
|
2
5
|
'use strict';
|
|
3
6
|
|
|
7
|
+
|
|
4
8
|
// Owns scroll, cards, reports and mature run actions. The optional activity
|
|
5
9
|
// presenter projects stored events without taking ownership of settlement.
|
|
6
10
|
function create(deps = {}) {
|
|
7
11
|
const {
|
|
8
12
|
RunActivitySummary,
|
|
9
13
|
RunPresence,
|
|
14
|
+
PanelIcons,
|
|
10
15
|
RunActivityModel,
|
|
11
16
|
SubagentActivityView,
|
|
12
17
|
RunGuidanceView,
|
|
18
|
+
getPendingGuidance = () => [],
|
|
13
19
|
RunResultActions,
|
|
14
20
|
RunScrollLayout,
|
|
15
21
|
RunFailureNotice,
|
|
22
|
+
I18n,
|
|
23
|
+
FailureReasons,
|
|
24
|
+
projectContextLimitEvent = value => value,
|
|
25
|
+
localizeVisibleReason = value => value,
|
|
26
|
+
localizeCompileReason = value => value,
|
|
16
27
|
tr,
|
|
17
28
|
tx,
|
|
18
29
|
getLocale,
|
|
@@ -47,6 +58,7 @@
|
|
|
47
58
|
trackedChangeInFlight,
|
|
48
59
|
projectRunSettlement,
|
|
49
60
|
getCurrentProjectId,
|
|
61
|
+
getAccountScopeId,
|
|
50
62
|
getProjectName
|
|
51
63
|
} = deps;
|
|
52
64
|
|
|
@@ -59,6 +71,7 @@
|
|
|
59
71
|
// drives the floating "jump to latest" button's counter.
|
|
60
72
|
let scrollLogRafId = 0;
|
|
61
73
|
let scrollLogPendingForce = false;
|
|
74
|
+
let pendingGuidanceReveal = null;
|
|
62
75
|
let jumpToLatestButton = null;
|
|
63
76
|
let unreadSinceDetach = 0;
|
|
64
77
|
// Elapsed time belongs to the run; tool rows retain their own lifecycle.
|
|
@@ -69,11 +82,25 @@
|
|
|
69
82
|
onLayoutChange: scroller => logAutoFollow ? scrollLogToBottom() : updateJumpToLatestButton(scroller)
|
|
70
83
|
});
|
|
71
84
|
const failureNotice = RunFailureNotice?.create({ tr, projectRunSettlement, sanitizeText: sanitizeAssistantVisibleText });
|
|
85
|
+
const { buildRecoveryButton, appendRecoveryActionForFailure } = RunRecoveryActions.create({
|
|
86
|
+
document: typeof document === 'undefined' ? undefined : document,
|
|
87
|
+
window: typeof window === 'undefined' ? undefined : window,
|
|
88
|
+
tx, failureNotice, refillComposerForRetry, openProjectFileForFailure, showNativeSetupGuidance,
|
|
89
|
+
openStorageSettings, forceCancelStuckTaskForCurrentProject
|
|
90
|
+
});
|
|
72
91
|
const presence = RunPresence?.create({ tx, getLocale, formatElapsed });
|
|
92
|
+
const getNotStartedRun = run => RunResultActions.projectNotStartedRun?.(run, getLocale());
|
|
93
|
+
const getFinishedRun = run => RunResultActions.projectFinishedRun?.(run, getLocale(), { projectRunSettlement });
|
|
73
94
|
const displayRun = run => RunFailureNotice?.projectResolvedUndo?.(run) || run;
|
|
74
|
-
const
|
|
95
|
+
const displayLocalizedRun = run => {
|
|
96
|
+
const displayed = displayRun(run);
|
|
97
|
+
return displayed ? { ...displayed, events: (displayed.events || []).map(sanitizeRunEventForRender) } : displayed;
|
|
98
|
+
};
|
|
99
|
+
const activitySummary = RunActivitySummary?.create({ RunActivityModel, RunGuidanceView, getPendingGuidance, SubagentActivityView, PanelIcons, tx, findRunRecord: (...args) => displayLocalizedRun(findRunRecord(...args)), getPanel, cssEscape,
|
|
100
|
+
projectReviewResultEvent: sanitizeRunEventForRender,
|
|
101
|
+
formatReviewResultFailure,
|
|
75
102
|
formatEventTime, formatElapsed, renderMarkdownBlockText, renderRunEvent, formatEventDetail,
|
|
76
|
-
presence, classifyStatus: classifyRunPresence, getProjectId: () => getCurrentProjectId?.() || '',
|
|
103
|
+
presence, getNotStartedRun, getFinishedRun, classifyStatus: classifyRunPresence, getProjectId: () => getCurrentProjectId?.() || '',
|
|
77
104
|
summarizeCommand: RunPresence?.commandSummary,
|
|
78
105
|
isGuidance: event => Boolean(RunGuidanceView.getGuidanceText(event)), sanitizeAssistantVisibleText });
|
|
79
106
|
|
|
@@ -82,6 +109,7 @@
|
|
|
82
109
|
function resetAutoFollow() {
|
|
83
110
|
logAutoFollow = true;
|
|
84
111
|
userScrollIntentUntil = 0;
|
|
112
|
+
pendingGuidanceReveal = null;
|
|
85
113
|
}
|
|
86
114
|
|
|
87
115
|
function bindLogAutoFollow() {
|
|
@@ -156,7 +184,7 @@
|
|
|
156
184
|
? tx(`↓ Latest · ${unreadSinceDetach}`, `↓ 最新 · ${unreadSinceDetach}`)
|
|
157
185
|
: tx('↓ Latest', '↓ 最新');
|
|
158
186
|
button.setAttribute('aria-label', unreadSinceDetach > 0
|
|
159
|
-
? tx(`Jump to latest, ${unreadSinceDetach} new
|
|
187
|
+
? tx(`Jump to latest, ${unreadSinceDetach} new step${unreadSinceDetach === 1 ? '' : 's'}`, `跳到最新,${unreadSinceDetach} 个新步骤`)
|
|
160
188
|
: tx('Jump to latest', '跳到最新'));
|
|
161
189
|
}
|
|
162
190
|
|
|
@@ -183,6 +211,7 @@
|
|
|
183
211
|
if ((event?.type === 'wheel' && event.deltaY < 0) || ['ArrowUp', 'PageUp', 'Home'].includes(event?.key)) {
|
|
184
212
|
logAutoFollow = false;
|
|
185
213
|
scrollLogPendingForce = false;
|
|
214
|
+
pendingGuidanceReveal = null;
|
|
186
215
|
}
|
|
187
216
|
}
|
|
188
217
|
|
|
@@ -207,6 +236,7 @@
|
|
|
207
236
|
userScrollIntentUntil = 0;
|
|
208
237
|
unreadSinceDetach = 0;
|
|
209
238
|
scrollLogPendingForce = true;
|
|
239
|
+
pendingGuidanceReveal = null;
|
|
210
240
|
} else if (!logAutoFollow) {
|
|
211
241
|
// The user has scrolled up: do not yank them. Just keep the jump button
|
|
212
242
|
// state current.
|
|
@@ -241,10 +271,26 @@
|
|
|
241
271
|
writeNow();
|
|
242
272
|
});
|
|
243
273
|
}
|
|
274
|
+
function revealGuidance(row) {
|
|
275
|
+
if (!logAutoFollow || activitySummary?.isSubagentViewOpen?.() || !getLogScrollContainer()?.contains?.(row)) return false;
|
|
276
|
+
pendingGuidanceReveal = row;
|
|
277
|
+
scrollLogToBottom();
|
|
278
|
+
return true;
|
|
279
|
+
}
|
|
244
280
|
function setLogScrollPosition(scroller) {
|
|
281
|
+
const reveal = pendingGuidanceReveal;
|
|
282
|
+
pendingGuidanceReveal = null;
|
|
283
|
+
const result = reveal && scrollLayout?.revealGuidance(reveal);
|
|
284
|
+
if (result) {
|
|
285
|
+
logAutoFollow = result.following;
|
|
286
|
+
if (!logAutoFollow) scrollLayout.captureAnchor(true);
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
245
289
|
scroller.scrollTop = scroller.scrollHeight;
|
|
246
290
|
}
|
|
247
291
|
function collapseRunProcess(view, statusText) {
|
|
292
|
+
const record = view?.recordId ? displayLocalizedRun(findRunRecord(view.recordId, view.sessionId)) : null;
|
|
293
|
+
if (record && getFinishedRun(record)) refreshCompletionView(view.root, view.report, record);
|
|
248
294
|
if (activitySummary) { activitySummary.settle(view, statusText); return; }
|
|
249
295
|
const runProcess = view?.runProcess || view?.root?.querySelector('[data-run-process]');
|
|
250
296
|
if (runProcess) {
|
|
@@ -257,11 +303,22 @@
|
|
|
257
303
|
const record = view?.recordId ? findRunRecord(view.recordId, view.sessionId) : null;
|
|
258
304
|
const stepCount = (record?.events || []).filter(event => (event.kind || 'activity') === 'activity').length;
|
|
259
305
|
statusEl.textContent = stepCount > 0
|
|
260
|
-
? `${statusText} · ${tx(`${stepCount}
|
|
306
|
+
? `${statusText} · ${tx(`${stepCount} step${stepCount === 1 ? '' : 's'}`, `${stepCount} 步`)}`
|
|
261
307
|
: statusText;
|
|
262
308
|
}
|
|
263
309
|
}
|
|
264
310
|
|
|
311
|
+
function refreshCompletionView(root, report, run) {
|
|
312
|
+
const event = (run.events || []).filter(item => item.kind === 'report').at(-1);
|
|
313
|
+
if (!root || !report || !event) return;
|
|
314
|
+
// Result actions may already live inside the previous report's facts row.
|
|
315
|
+
const actions = root.querySelector('.run-result-actions');
|
|
316
|
+
report.replaceChildren(renderCompletionReport(event, run));
|
|
317
|
+
if (actions) report.append(actions);
|
|
318
|
+
RunResultActions.configureResultActions(root, run);
|
|
319
|
+
configureEditButton(root, run);
|
|
320
|
+
}
|
|
321
|
+
|
|
265
322
|
// Keep elapsed labels live without creating new activity records.
|
|
266
323
|
function startRunElapsedTick() {
|
|
267
324
|
stopRunElapsedTick();
|
|
@@ -357,7 +414,7 @@
|
|
|
357
414
|
}
|
|
358
415
|
|
|
359
416
|
function renderRunCard(run) {
|
|
360
|
-
run =
|
|
417
|
+
run = displayLocalizedRun(run);
|
|
361
418
|
const root = document.createElement('section');
|
|
362
419
|
root.className = 'transcript-turn run-card';
|
|
363
420
|
root.dataset.status = run.status || 'completed';
|
|
@@ -391,7 +448,6 @@
|
|
|
391
448
|
<div class="run-result-actions" hidden>
|
|
392
449
|
<button type="button" class="run-copy-action" data-run-copy hidden></button>
|
|
393
450
|
<button type="button" class="run-fork-action" data-run-fork hidden></button>
|
|
394
|
-
<span class="run-completed-time" data-run-completed-time hidden></span>
|
|
395
451
|
</div>
|
|
396
452
|
<div class="run-report" data-run-report aria-live="polite" hidden></div>
|
|
397
453
|
</div>
|
|
@@ -418,28 +474,15 @@
|
|
|
418
474
|
truncated.textContent = tr('eventsTruncatedNote');
|
|
419
475
|
events.append(truncated);
|
|
420
476
|
}
|
|
421
|
-
|
|
422
|
-
const renderedGuidanceIds = new Set();
|
|
423
|
-
const completedLegacyGuidance = new Set((run.events || [])
|
|
424
|
-
.filter(event => event.kind === 'guidance' && !event.guidanceId && event.status === 'completed')
|
|
425
|
-
.map(event => event.title));
|
|
477
|
+
if (!activitySummary) RunGuidanceView.reconcileGuidance?.(guidance, (run.events || []).concat(getPendingGuidance(run)), tx, { active: run.status === 'running' });
|
|
426
478
|
for (const event of run.events || []) {
|
|
427
479
|
if (event.kind === 'report') {
|
|
428
480
|
report.hidden = false;
|
|
429
481
|
report.replaceChildren(renderCompletionReport(event, run));
|
|
430
|
-
} else if (activitySummary || event.kind === 'technical') {
|
|
482
|
+
} else if (getNotStartedRun(run) || activitySummary || event.kind === 'technical') {
|
|
431
483
|
continue;
|
|
432
484
|
} else if (RunGuidanceView.getGuidanceText(event)) {
|
|
433
|
-
|
|
434
|
-
continue;
|
|
435
|
-
}
|
|
436
|
-
if (!event.guidanceId && event.status === 'queued' && completedLegacyGuidance.has(event.title)) {
|
|
437
|
-
continue;
|
|
438
|
-
}
|
|
439
|
-
if (event.guidanceId) {
|
|
440
|
-
renderedGuidanceIds.add(event.guidanceId);
|
|
441
|
-
}
|
|
442
|
-
guidanceTarget.append(renderRunEvent(event));
|
|
485
|
+
continue;
|
|
443
486
|
} else if (event.kind === 'stream') {
|
|
444
487
|
upsertStreamEvent({ events }, event);
|
|
445
488
|
} else {
|
|
@@ -457,7 +500,19 @@
|
|
|
457
500
|
}
|
|
458
501
|
|
|
459
502
|
function getRunStatusText(run = {}) {
|
|
503
|
+
const notStarted = getNotStartedRun(run);
|
|
504
|
+
if (notStarted) return notStarted.status;
|
|
505
|
+
const outcome = getFinishedRun(run);
|
|
506
|
+
if (outcome?.status) return outcome.status;
|
|
460
507
|
if (run.retryingWriteback) return tx('Checking file sync', '正在检查文件同步');
|
|
508
|
+
if (['Stopped tracking after a page refresh', '页面刷新后已停止跟踪'].includes(run.statusText)) return tr('restoredRunStoppedStatus');
|
|
509
|
+
const knownStatus = /^(?:Done|Failed|Cancelled|Running|已处理|处理失败|已取消|处理中)(?:\s+[\d.]+(?:ms|s|m|h|秒|分钟)(?:\s+[\d.]+(?:ms|s|m|h|秒|分钟))*)?$/.test(run.statusText || '');
|
|
510
|
+
if (knownStatus) {
|
|
511
|
+
const elapsed = String(run.statusText).replace(/^(?:Done|Failed|Cancelled|Running|已处理|处理失败|已取消|处理中)\s*/, '');
|
|
512
|
+
const key = run.status === 'running' ? 'processing' : run.status === 'failed' ? 'processedFailed'
|
|
513
|
+
: ['rejected', 'cancelled'].includes(run.status) ? 'processedCancelled' : 'processed';
|
|
514
|
+
return tr(key, { elapsed }).trim();
|
|
515
|
+
}
|
|
461
516
|
if (run.statusText) {
|
|
462
517
|
return run.statusText;
|
|
463
518
|
}
|
|
@@ -532,20 +587,64 @@
|
|
|
532
587
|
}
|
|
533
588
|
|
|
534
589
|
function sanitizeRunEventForRender(event = {}) {
|
|
590
|
+
event = projectContextLimitEvent(event);
|
|
535
591
|
const streamRole = sanitizeAssistantVisibleText(event.streamRole);
|
|
536
592
|
const title = sanitizeAssistantVisibleText(event.title);
|
|
593
|
+
const activity = !streamRole && (!event.kind || event.kind === 'activity') && !event.guidanceId;
|
|
594
|
+
const localized = activity ? localizeProductActivity(title, sanitizeAssistantVisibleValue(event.detail)) : null;
|
|
537
595
|
return {
|
|
538
596
|
...event,
|
|
597
|
+
...(activity && event.activity?.kind === 'compile' ? { activity: { ...event.activity, output: localizeCompileReason(event.activity.output) } } : {}),
|
|
539
598
|
title: streamRole === 'reasoning'
|
|
540
599
|
? stripEmptyHtmlCommentPlaceholders(title)
|
|
541
|
-
: title,
|
|
600
|
+
: localized?.title || title,
|
|
542
601
|
status: sanitizeAssistantVisibleText(event.status),
|
|
543
|
-
detail: sanitizeAssistantVisibleValue(event.detail),
|
|
602
|
+
detail: localized?.detail ?? sanitizeAssistantVisibleValue(event.detail),
|
|
544
603
|
technicalDetail: sanitizeAssistantVisibleValue(event.technicalDetail),
|
|
545
604
|
streamKey: sanitizeAssistantVisibleText(event.streamKey),
|
|
546
605
|
streamRole
|
|
547
606
|
};
|
|
548
607
|
}
|
|
608
|
+
|
|
609
|
+
function localizeProductActivity(title, detail) {
|
|
610
|
+
let known = false, nextTitle = title;
|
|
611
|
+
const keys = ['undoUnsafeFullFileTitle', 'undoNoTraceTitle', 'undoFileSelectTitle', 'undoNoTraceStarted', 'undoTrackedTitle', 'undoNativeTitle', 'undoTrackedStarted', 'undoNativeStarted', 'undoSwitchedEditing', 'undoReviewingRestoreUnverified', 'runAcceptTrackedStarted', 'runAcceptTrackedFailed', 'undoResult', 'undoTrackedResult', 'undoNativeResult', 'runAcceptTrackedResult'];
|
|
612
|
+
if (I18n?.t) outer: for (const key of keys) for (const locale of ['en', 'zh']) {
|
|
613
|
+
const template = I18n.t(locale, key, { applied: '__APPLIED__', skipped: '__SKIPPED__' });
|
|
614
|
+
const pattern = template.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace('__APPLIED__', '(\\d+)').replace('__SKIPPED__', '(\\d+)');
|
|
615
|
+
const match = new RegExp('^' + pattern + '$').exec(title);
|
|
616
|
+
if (match) { known = true; nextTitle = tr(key, { applied: match[1], skipped: match[2] }); break outer; }
|
|
617
|
+
}
|
|
618
|
+
let countedTitle;
|
|
619
|
+
const writeResult = /^(?:Write result: wrote (\d+) item\(s\), skipped (\d+)|写入结果:已写入 (\d+) 项,跳过 (\d+) 项)$/.exec(title);
|
|
620
|
+
const governance = /^(?:Project governance blocked (\d+) write\(s\) before review\.|项目治理规则在审核前阻止了 (\d+) 项写入。)$/.exec(title);
|
|
621
|
+
if (writeResult) {
|
|
622
|
+
const applied = writeResult[1] ?? writeResult[3], skipped = writeResult[2] ?? writeResult[4];
|
|
623
|
+
countedTitle = tx(`Write result: wrote ${applied} item(s), skipped ${skipped}`, `写入结果:已写入 ${applied} 项,跳过 ${skipped} 项`);
|
|
624
|
+
} else if (governance) {
|
|
625
|
+
const count = governance[1] ?? governance[2];
|
|
626
|
+
countedTitle = tx(`Project governance blocked ${count} write(s) before review.`, `项目治理规则在审核前阻止了 ${count} 项写入。`);
|
|
627
|
+
}
|
|
628
|
+
const labels = ['detailAccepted', 'detailRejected', 'detailUndone', 'detailSkipped', 'detailFile', 'detailRecord', 'detailReason'];
|
|
629
|
+
const project = (value, reason = false) => {
|
|
630
|
+
if (typeof value === 'string') return reason ? localizeVisibleReason(value) : value;
|
|
631
|
+
if (Array.isArray(value)) return value.map(item => project(item, reason));
|
|
632
|
+
if (!value || typeof value !== 'object') return value;
|
|
633
|
+
return Object.fromEntries(Object.entries(value).map(([key, child]) => {
|
|
634
|
+
const label = labels.find(id => ['en', 'zh'].some(locale => I18n?.t?.(locale, id) === key));
|
|
635
|
+
return [label ? tr(label) : key, project(child, label === 'detailReason')];
|
|
636
|
+
}));
|
|
637
|
+
};
|
|
638
|
+
return { title: countedTitle || (known ? nextTitle : localizeCompileReason(localizeVisibleReason(title))),
|
|
639
|
+
detail: typeof detail === 'string' ? localizeVisibleReason(detail) : known ? project(detail) : detail };
|
|
640
|
+
}
|
|
641
|
+
function formatReviewResultFailure(failure) {
|
|
642
|
+
if (failure?.code === 'unknown_legacy_failure' && (!failure.evidence?.originalCode || failure.evidence.originalCode === 'unknown_legacy_failure')
|
|
643
|
+
&& ['Unknown reason', '未知原因'].includes(failure.userMessage)) {
|
|
644
|
+
failure = { ...failure, userMessage: tr('unknownReason') };
|
|
645
|
+
}
|
|
646
|
+
return FailureReasons?.formatFailureReasonForDisplay(failure, getLocale?.(), key => { const value = tr(key, failure?.file ? undefined : { file: tr('unknownFile'), activeFile: tr('unknownFile') }); return value && value !== key ? value : undefined; });
|
|
647
|
+
}
|
|
549
648
|
function renderActivityLine(input) {
|
|
550
649
|
const event = sanitizeRunEventForRender(input);
|
|
551
650
|
const row = document.createElement('div');
|
|
@@ -586,27 +685,48 @@
|
|
|
586
685
|
// The result presenter owns parsing and current lifecycle metadata.
|
|
587
686
|
const splitFlatCompletionReport = text => RunResultActions.splitFlatCompletionReport(text, tx);
|
|
588
687
|
const projectCompletionMeta = (meta, run) => RunResultActions.projectCompletionMeta(meta, run, {
|
|
589
|
-
tx, trackedChangeInFlight, isTrackedChangeLifecycleRun, projectRunSettlement
|
|
688
|
+
tx, getLocale, tr, trackedChangeInFlight, isTrackedChangeLifecycleRun, projectRunSettlement,
|
|
689
|
+
projectId: getCurrentProjectId?.(), accountScopeId: getAccountScopeId?.()
|
|
590
690
|
});
|
|
591
691
|
|
|
592
692
|
// The user's own open/closed choice per run survives card re-renders.
|
|
593
693
|
const metaOpenChoices = new Map();
|
|
594
694
|
|
|
695
|
+
// The completion time lives with the other run facts, not in the action row.
|
|
696
|
+
// `classed` matches lists whose rows carry the meta label/value classes.
|
|
697
|
+
function appendFinishedRow(list, run, { classed = false } = {}) {
|
|
698
|
+
const finishedAt = new Date(String(run?.finishedAt || ''));
|
|
699
|
+
if (!Number.isFinite(finishedAt.getTime())) return;
|
|
700
|
+
const dt = Object.assign(document.createElement('dt'), { textContent: tx('Finished', '完成于') });
|
|
701
|
+
dt.dataset.metaKey = 'finishedAt';
|
|
702
|
+
const dd = Object.assign(document.createElement('dd'), {
|
|
703
|
+
textContent: new Intl.DateTimeFormat(getLocale(), { hour: 'numeric', minute: '2-digit' }).format(finishedAt),
|
|
704
|
+
title: finishedAt.toLocaleString(getLocale()) });
|
|
705
|
+
if (classed) {
|
|
706
|
+
dt.className = 'run-final-answer__meta-label';
|
|
707
|
+
dd.className = 'run-final-answer__meta-value';
|
|
708
|
+
}
|
|
709
|
+
list.append(dt, dd);
|
|
710
|
+
}
|
|
711
|
+
|
|
595
712
|
// Renders the run-metadata rows (Why nothing changed / Write result / Undo /
|
|
596
713
|
// Next) behind a one-line summary beneath the answer. Shared by the
|
|
597
714
|
// structured and flat-fallback render paths so both demote identically.
|
|
598
|
-
function appendCompletionMetaBlock(report, meta, run) {
|
|
715
|
+
function appendCompletionMetaBlock(report, meta, run, { quiet = false } = {}) {
|
|
599
716
|
if (!Array.isArray(meta) || !meta.length) {
|
|
600
717
|
return;
|
|
601
718
|
}
|
|
602
|
-
const
|
|
719
|
+
const noWrites = getFinishedRun(run)?.noWrites;
|
|
720
|
+
const rows = projectCompletionMeta(meta, run).filter(row => row?.label && row.value
|
|
721
|
+
&& !(noWrites && ['writeResult', 'undo'].includes(row.key)))
|
|
603
722
|
.map(row => ({ ...row, shown: failureNotice?.formatMetaValue(report, row) ?? row.value }));
|
|
604
723
|
if (!rows.length) return;
|
|
605
724
|
const summary = RunResultActions.summarizeCompletionMeta(rows, run, {
|
|
606
|
-
tx, failed: report.dataset.status === 'failed', unconfirmed: rows.some(row => row.shown !== row.value)
|
|
725
|
+
tx, failed: report.dataset.status === 'failed' && !noWrites, unconfirmed: rows.some(row => row.shown !== row.value)
|
|
607
726
|
});
|
|
608
727
|
const metaBlock = document.createElement('dl');
|
|
609
728
|
metaBlock.className = 'run-final-answer__meta';
|
|
729
|
+
appendFinishedRow(metaBlock, run, { classed: true });
|
|
610
730
|
metaBlock.id = `codex-run-meta-${String(run?.id || 'report').replace(/[^a-zA-Z0-9_-]/g, '-')}`;
|
|
611
731
|
for (const row of rows) {
|
|
612
732
|
const dt = document.createElement('dt');
|
|
@@ -619,6 +739,12 @@
|
|
|
619
739
|
if (summary.warnKeys.has(row.key)) dt.dataset.tone = dd.dataset.tone = 'warn';
|
|
620
740
|
metaBlock.append(dt, dd);
|
|
621
741
|
}
|
|
742
|
+
if (quiet) {
|
|
743
|
+
const details = createOutcomeDetails(run);
|
|
744
|
+
details.querySelector('.run-outcome-details-content').append(metaBlock);
|
|
745
|
+
report.append(details);
|
|
746
|
+
return;
|
|
747
|
+
}
|
|
622
748
|
const facts = document.createElement('div');
|
|
623
749
|
facts.className = 'run-final-answer__facts';
|
|
624
750
|
const toggle = document.createElement('button');
|
|
@@ -633,12 +759,18 @@
|
|
|
633
759
|
toggle.append(item);
|
|
634
760
|
});
|
|
635
761
|
const more = Object.assign(document.createElement('span'), { className: 'run-final-answer__facts-more' });
|
|
636
|
-
|
|
762
|
+
// The chevron stays with the last fact, so a narrow row never leaves it alone on a line.
|
|
763
|
+
const tail = Object.assign(document.createElement('span'), { className: 'run-final-answer__facts-tail' });
|
|
764
|
+
if (toggle.lastElementChild) tail.append(toggle.lastElementChild);
|
|
765
|
+
tail.append(more);
|
|
766
|
+
toggle.append(tail);
|
|
637
767
|
const setOpen = open => {
|
|
638
768
|
facts.dataset.open = String(open);
|
|
639
769
|
metaBlock.hidden = !open;
|
|
640
770
|
toggle.setAttribute('aria-expanded', String(open));
|
|
641
|
-
|
|
771
|
+
// The facts are the toggle; the chevron and the tooltip carry its state.
|
|
772
|
+
toggle.title = open ? tx('Hide details', '收起详情') : tx('Show details', '展开详情');
|
|
773
|
+
more.innerHTML = `<svg class="codex-glyph" viewBox="0 0 16 16" aria-hidden="true" focusable="false"><path d="m6 4 4 4-4 4"/></svg>`;
|
|
642
774
|
};
|
|
643
775
|
setOpen(metaOpenChoices.get(run?.id) ?? summary.open);
|
|
644
776
|
toggle.addEventListener('click', event => {
|
|
@@ -651,11 +783,130 @@
|
|
|
651
783
|
report.append(facts);
|
|
652
784
|
}
|
|
653
785
|
|
|
786
|
+
function createOutcomeDetails(run) {
|
|
787
|
+
const details = document.createElement('details');
|
|
788
|
+
details.className = 'run-outcome-details';
|
|
789
|
+
details.open = metaOpenChoices.get(run?.id) ?? false;
|
|
790
|
+
details.append(Object.assign(document.createElement('summary'), { textContent: tr('runOutcomeDetails') }));
|
|
791
|
+
details.append(Object.assign(document.createElement('div'), { className: 'run-outcome-details-content' }));
|
|
792
|
+
details.addEventListener('toggle', () => { if (run?.id) metaOpenChoices.set(run.id, details.open); });
|
|
793
|
+
return details;
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
function renderOutcomeCard(report, event, run, outcome) {
|
|
797
|
+
const card = document.createElement('div');
|
|
798
|
+
card.className = 'run-outcome';
|
|
799
|
+
card.dataset.outcome = outcome.kind;
|
|
800
|
+
const main = document.createElement('div');
|
|
801
|
+
main.className = 'run-final-answer';
|
|
802
|
+
const title = document.createElement('div');
|
|
803
|
+
title.className = 'run-final-heading';
|
|
804
|
+
title.textContent = sanitizeAssistantVisibleText(outcome.title);
|
|
805
|
+
main.append(title);
|
|
806
|
+
if (outcome.body) main.append(Object.assign(document.createElement('p'), {
|
|
807
|
+
className: 'run-outcome-body', textContent: sanitizeAssistantVisibleText(outcome.body)
|
|
808
|
+
}));
|
|
809
|
+
if (outcome.files.length) {
|
|
810
|
+
const files = document.createElement('ul');
|
|
811
|
+
files.className = 'run-outcome-files';
|
|
812
|
+
for (const item of outcome.files) {
|
|
813
|
+
const row = document.createElement('li');
|
|
814
|
+
const mark = document.createElement('span');
|
|
815
|
+
mark.className = 'run-outcome-file-icon';
|
|
816
|
+
mark.dataset.written = String(item.written);
|
|
817
|
+
mark.innerHTML = RunPresence.icon(item.written ? 'written' : 'skipped');
|
|
818
|
+
mark.setAttribute('aria-label', tr(item.written ? 'runOutcomeWritten' : 'runOutcomeSkipped'));
|
|
819
|
+
row.append(mark, Object.assign(document.createElement('span'), {
|
|
820
|
+
className: 'run-outcome-file-name', textContent: sanitizeAssistantVisibleText(item.file)
|
|
821
|
+
}));
|
|
822
|
+
if (item.reason) row.append(Object.assign(document.createElement('span'), {
|
|
823
|
+
className: 'run-outcome-file-reason', textContent: sanitizeAssistantVisibleText(item.reason)
|
|
824
|
+
}));
|
|
825
|
+
files.append(row);
|
|
826
|
+
}
|
|
827
|
+
main.append(files);
|
|
828
|
+
}
|
|
829
|
+
const next = document.createElement('dl');
|
|
830
|
+
next.className = 'run-outcome-next';
|
|
831
|
+
next.append(Object.assign(document.createElement('dt'), { textContent: tr('detailNext') }),
|
|
832
|
+
Object.assign(document.createElement('dd'), { textContent: sanitizeAssistantVisibleText(outcome.nextStep) }));
|
|
833
|
+
card.append(main, next);
|
|
834
|
+
report.append(card);
|
|
835
|
+
failureNotice?.append(report, event, run, card);
|
|
836
|
+
appendRecoveryActionForFailure(report, event, run);
|
|
837
|
+
|
|
838
|
+
const details = createOutcomeDetails(run);
|
|
839
|
+
const rows = projectCompletionMeta(event.detailStructured.meta || [], run)
|
|
840
|
+
.filter(row => row?.value && row.key !== 'nextStep'
|
|
841
|
+
&& !(outcome.noWrites && ['writeResult', 'undo'].includes(row.key)));
|
|
842
|
+
if (outcome.failure?.code) rows.unshift({ label: tr('runOutcomeCode'), value: outcome.failure.code });
|
|
843
|
+
if (outcome.failure?.stage) rows.push({ label: tr('runOutcomeStage'), value: outcome.failure.stage });
|
|
844
|
+
if (outcome.noWrites) rows.push({ label: tr('runOutcomeWrites'), value: tr('runOutcomeNoneAttempted') });
|
|
845
|
+
const meta = document.createElement('dl');
|
|
846
|
+
meta.className = 'run-final-answer__meta';
|
|
847
|
+
appendFinishedRow(meta, run);
|
|
848
|
+
for (const row of rows) {
|
|
849
|
+
const label = Object.assign(document.createElement('dt'), { textContent: row.label });
|
|
850
|
+
if (row.key) label.dataset.metaKey = row.key;
|
|
851
|
+
meta.append(label, Object.assign(document.createElement('dd'), {
|
|
852
|
+
textContent: sanitizeAssistantVisibleText(failureNotice?.formatMetaValue(report, row) ?? row.value)
|
|
853
|
+
}));
|
|
854
|
+
}
|
|
855
|
+
const detailContent = details.querySelector('.run-outcome-details-content');
|
|
856
|
+
detailContent.append(meta);
|
|
857
|
+
// Preserve the original report body as optional context without repeating
|
|
858
|
+
// its skipped-item prose in the default outcome view.
|
|
859
|
+
if (event.detailStructured.body) {
|
|
860
|
+
const body = document.createElement('div');
|
|
861
|
+
renderMarkdownBlockText(body, RunResultActions.projectCompletionBody(event.detailStructured.body, tx));
|
|
862
|
+
detailContent.append(body);
|
|
863
|
+
}
|
|
864
|
+
card.append(details);
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
function formatCompileErrorNames(errors, totalCount) {
|
|
868
|
+
const names = [...new Set(errors.map(error => String(error)
|
|
869
|
+
.trim().replace(/^!\s*/, '').replace(/^(?:[A-Za-z]:)?[^:\r\n]+:\d+:\s*/, '').replace(/^!\s*/, '')
|
|
870
|
+
.split(/[ \t]+l\.\d+\b|[\r\n]/, 1)[0]
|
|
871
|
+
.trim().replace(/\.$/, '').replace(/\s+/g, ' ').trim()).filter(Boolean))];
|
|
872
|
+
const shown = names.slice(0, 3);
|
|
873
|
+
// History may retain fewer diagnostics than the original count. Preserve
|
|
874
|
+
// that omitted count without inventing names or changing the repair draft.
|
|
875
|
+
const more = Math.max(0, names.length - shown.length) + Math.max(0, totalCount - errors.length);
|
|
876
|
+
if (more) shown.push(tx(`+${more} more`, `另 ${more} 个`));
|
|
877
|
+
return shown.join(tx('; ', ';'));
|
|
878
|
+
}
|
|
879
|
+
|
|
654
880
|
function renderCompletionReport(input, run) {
|
|
655
881
|
const event = sanitizeRunEventForRender(input);
|
|
656
882
|
const report = document.createElement('section');
|
|
657
883
|
report.className = 'run-completion-report';
|
|
658
884
|
report.dataset.status = event.status || 'completed';
|
|
885
|
+
const notStarted = getNotStartedRun(run);
|
|
886
|
+
if (notStarted) {
|
|
887
|
+
report.dataset.status = 'not-started';
|
|
888
|
+
const card = document.createElement('div');
|
|
889
|
+
card.className = 'run-outcome';
|
|
890
|
+
const main = document.createElement('div');
|
|
891
|
+
main.className = 'run-final-answer';
|
|
892
|
+
const title = document.createElement('div');
|
|
893
|
+
title.className = 'run-final-heading';
|
|
894
|
+
title.textContent = notStarted.title;
|
|
895
|
+
const body = document.createElement('p');
|
|
896
|
+
body.className = 'run-outcome-body';
|
|
897
|
+
body.textContent = notStarted.body;
|
|
898
|
+
main.append(title, body);
|
|
899
|
+
const next = document.createElement('dl');
|
|
900
|
+
next.className = 'run-outcome-next';
|
|
901
|
+
next.append(Object.assign(document.createElement('dt'), {
|
|
902
|
+
textContent: tr('detailNext')
|
|
903
|
+
}), Object.assign(document.createElement('dd'), {
|
|
904
|
+
textContent: notStarted.nextStep
|
|
905
|
+
}));
|
|
906
|
+
card.append(main, next);
|
|
907
|
+
report.append(card);
|
|
908
|
+
return report;
|
|
909
|
+
}
|
|
659
910
|
// Compile errors captured post-write get a one-click fix loop: the
|
|
660
911
|
// structured error list rides on the report event (compileErrors).
|
|
661
912
|
const appendCompileFix = target => {
|
|
@@ -664,7 +915,7 @@
|
|
|
664
915
|
return;
|
|
665
916
|
}
|
|
666
917
|
const fix = buildRecoveryButton('compile_errors',
|
|
667
|
-
tx(`Fix ${compileErrors.length} compile error
|
|
918
|
+
tx(`Fix ${compileErrors.length} compile error${compileErrors.length === 1 ? '' : 's'}`, `修复 ${compileErrors.length} 个编译错误`),
|
|
668
919
|
tx('Prefills a task with @compile-log and the captured errors.', '自动组一条带 @compile-log 和错误列表的任务。'));
|
|
669
920
|
fix.addEventListener('click', clickEvent => {
|
|
670
921
|
clickEvent.stopPropagation();
|
|
@@ -702,6 +953,13 @@
|
|
|
702
953
|
// visually demoted block beneath, with a separator and muted color so
|
|
703
954
|
// they read as run metadata rather than part of the answer.
|
|
704
955
|
const structured = event.detailStructured;
|
|
956
|
+
const outcome = getFinishedRun(run);
|
|
957
|
+
if (outcome && !['cancelled', 'compile-errors'].includes(outcome.kind)) {
|
|
958
|
+
renderOutcomeCard(report, event, run, outcome);
|
|
959
|
+
appendCompileFix(report);
|
|
960
|
+
appendRejectedRedo(report);
|
|
961
|
+
return report;
|
|
962
|
+
}
|
|
705
963
|
const hasStructured = structured
|
|
706
964
|
&& (structured.conclusion || structured.body || (Array.isArray(structured.meta) && structured.meta.length));
|
|
707
965
|
if (hasStructured) {
|
|
@@ -709,10 +967,11 @@
|
|
|
709
967
|
main.className = 'run-final-answer';
|
|
710
968
|
const mainSections = [];
|
|
711
969
|
if (structured.conclusion) {
|
|
712
|
-
mainSections.push(
|
|
970
|
+
mainSections.push(outcome?.kind === 'compile-errors' ? outcome.conclusion
|
|
971
|
+
: RunResultActions.projectCompletionConclusion?.(event, run, tx('en', 'zh'), { localizeCompileReason }) ?? String(structured.conclusion));
|
|
713
972
|
}
|
|
714
973
|
if (structured.body) {
|
|
715
|
-
mainSections.push(structured.body);
|
|
974
|
+
mainSections.push(RunResultActions.projectCompletionBody?.(structured.body, tx) ?? structured.body);
|
|
716
975
|
}
|
|
717
976
|
if (mainSections.length) {
|
|
718
977
|
renderMarkdownBlockText(main, mainSections.join('\n\n'));
|
|
@@ -721,8 +980,20 @@
|
|
|
721
980
|
}
|
|
722
981
|
|
|
723
982
|
failureNotice?.append(report, event, run);
|
|
724
|
-
|
|
725
|
-
|
|
983
|
+
if (outcome?.kind === 'compile-errors') {
|
|
984
|
+
const compile = document.createElement('div');
|
|
985
|
+
compile.className = 'run-outcome run-outcome-compile';
|
|
986
|
+
const capturedErrors = Array.isArray(event.compileErrors) ? event.compileErrors.filter(Boolean) : [];
|
|
987
|
+
compile.append(Object.assign(document.createElement('strong'), { className: 'run-final-heading', textContent: outcome.title }),
|
|
988
|
+
Object.assign(document.createElement('p'), { className: 'run-outcome-body',
|
|
989
|
+
textContent: formatCompileErrorNames(capturedErrors.length ? capturedErrors : outcome.compileErrors, outcome.compileCount) }));
|
|
990
|
+
appendCompileFix(compile);
|
|
991
|
+
report.append(compile);
|
|
992
|
+
appendCompletionMetaBlock(report, structured.meta, run, { quiet: true });
|
|
993
|
+
} else {
|
|
994
|
+
appendCompletionMetaBlock(report, structured.meta, run);
|
|
995
|
+
appendCompileFix(report);
|
|
996
|
+
}
|
|
726
997
|
appendRejectedRedo(report);
|
|
727
998
|
appendRecoveryActionForFailure(report, event, run);
|
|
728
999
|
return report;
|
|
@@ -737,7 +1008,8 @@
|
|
|
737
1008
|
const split = splitFlatCompletionReport(flatText);
|
|
738
1009
|
const body = document.createElement('div');
|
|
739
1010
|
body.className = 'run-final-answer';
|
|
740
|
-
renderMarkdownBlockText(body,
|
|
1011
|
+
renderMarkdownBlockText(body, RunResultActions.projectCompletionConclusion?.(event, run, tx('en', 'zh'),
|
|
1012
|
+
{ flat: true, text: split.body || flatText, localizeCompileReason }) ?? (split.body || flatText));
|
|
741
1013
|
decorateCompletionReport(body, { tx });
|
|
742
1014
|
report.append(body);
|
|
743
1015
|
appendCompileFix(report);
|
|
@@ -755,132 +1027,6 @@
|
|
|
755
1027
|
// the normal cancel button is hidden because currentRunView is null).
|
|
756
1028
|
// Recovery-action registry: failure codes whose "next step" is executable
|
|
757
1029
|
// from the panel get a button, not prose. One primary action per failure.
|
|
758
|
-
const RETRYABLE_FAILURE_CODES = new Set([
|
|
759
|
-
'native_request_failed', 'codex_timeout', 'codex_no_usable_result',
|
|
760
|
-
'codex_output_limit', 'stale_source_changed', 'patch_anchor_not_found',
|
|
761
|
-
'target_editor_not_ready', 'write_timeout', 'partial_write_needs_review',
|
|
762
|
-
'write_operation_failed'
|
|
763
|
-
]);
|
|
764
|
-
const NATIVE_SETUP_FAILURE_CODES = new Set([
|
|
765
|
-
'native_bridge_unavailable', 'native_protocol_incompatible'
|
|
766
|
-
]);
|
|
767
|
-
const OPEN_FILE_FAILURE_CODES = new Set([
|
|
768
|
-
'target_file_not_active', 'target_file_open_failed', 'target_file_not_found'
|
|
769
|
-
]);
|
|
770
|
-
const STORAGE_FAILURE_CODES = new Set([
|
|
771
|
-
'storage_quota_exceeded', 'run_state_persist_failed'
|
|
772
|
-
]);
|
|
773
|
-
// A missing `codex` binary is fixed in the terminal, not the panel — the
|
|
774
|
-
// action opens the README troubleshooting section (PATH / login checks).
|
|
775
|
-
const CODEX_INSTALL_FAILURE_CODES = new Set(['codex_not_found']);
|
|
776
|
-
const CODEX_CLI_TROUBLESHOOTING_URL = 'https://github.com/Ghqqqq/codex-overleaf-link#faq-and-troubleshooting';
|
|
777
|
-
|
|
778
|
-
function buildRecoveryButton(failureCode, label, title) {
|
|
779
|
-
const button = document.createElement('button');
|
|
780
|
-
button.type = 'button';
|
|
781
|
-
button.className = 'run-final-answer__recovery-action';
|
|
782
|
-
button.dataset.recoveryFor = failureCode;
|
|
783
|
-
button.textContent = label;
|
|
784
|
-
if (title) {
|
|
785
|
-
button.title = title;
|
|
786
|
-
}
|
|
787
|
-
return button;
|
|
788
|
-
}
|
|
789
|
-
|
|
790
|
-
function appendEditAndResend(report, run, actionKey) {
|
|
791
|
-
const retry = buildRecoveryButton(actionKey,
|
|
792
|
-
tx('Edit & resend', '编辑后重发'),
|
|
793
|
-
tx('Put this run\u2019s task back into the composer so you can adjust and resend it.', '把本轮任务填回输入框,修改后可直接重发。'));
|
|
794
|
-
retry.addEventListener('click', clickEvent => {
|
|
795
|
-
clickEvent.stopPropagation();
|
|
796
|
-
refillComposerForRetry(run || null);
|
|
797
|
-
});
|
|
798
|
-
report.append(retry);
|
|
799
|
-
}
|
|
800
|
-
|
|
801
|
-
function appendRecoveryActionForFailure(report, event, run) {
|
|
802
|
-
const recovery = failureNotice?.prepareRecovery(report, { openFile: openProjectFileForFailure });
|
|
803
|
-
const failureCode = event?.failure?.code || '';
|
|
804
|
-
if (recovery?.handled) return;
|
|
805
|
-
report = recovery?.target || report;
|
|
806
|
-
if (RETRYABLE_FAILURE_CODES.has(failureCode) && typeof refillComposerForRetry === 'function') {
|
|
807
|
-
appendEditAndResend(report, run, failureCode);
|
|
808
|
-
return;
|
|
809
|
-
}
|
|
810
|
-
if (NATIVE_SETUP_FAILURE_CODES.has(failureCode) && typeof showNativeSetupGuidance === 'function') {
|
|
811
|
-
const fix = buildRecoveryButton(failureCode,
|
|
812
|
-
tx('Fix the native host', '修复 native host'),
|
|
813
|
-
tx('Opens the install/update guidance with the copyable command.', '打开安装/更新指引(含可复制命令)。'));
|
|
814
|
-
fix.addEventListener('click', clickEvent => {
|
|
815
|
-
clickEvent.stopPropagation();
|
|
816
|
-
showNativeSetupGuidance();
|
|
817
|
-
});
|
|
818
|
-
report.append(fix);
|
|
819
|
-
return;
|
|
820
|
-
}
|
|
821
|
-
if (OPEN_FILE_FAILURE_CODES.has(failureCode) && event?.failure?.file && typeof openProjectFileForFailure === 'function') {
|
|
822
|
-
const open = buildRecoveryButton(failureCode,
|
|
823
|
-
tx(`Open ${event.failure.file} in Overleaf`, `在 Overleaf 打开 ${event.failure.file}`), '');
|
|
824
|
-
open.addEventListener('click', clickEvent => {
|
|
825
|
-
clickEvent.stopPropagation();
|
|
826
|
-
openProjectFileForFailure(event.failure.file);
|
|
827
|
-
});
|
|
828
|
-
report.append(open);
|
|
829
|
-
return;
|
|
830
|
-
}
|
|
831
|
-
if (STORAGE_FAILURE_CODES.has(failureCode) && typeof openStorageSettings === 'function') {
|
|
832
|
-
const clean = buildRecoveryButton(failureCode,
|
|
833
|
-
tx('Open history & storage cleanup', '打开历史与存储清理'), '');
|
|
834
|
-
clean.addEventListener('click', clickEvent => {
|
|
835
|
-
clickEvent.stopPropagation();
|
|
836
|
-
openStorageSettings();
|
|
837
|
-
});
|
|
838
|
-
report.append(clean);
|
|
839
|
-
return;
|
|
840
|
-
}
|
|
841
|
-
if (CODEX_INSTALL_FAILURE_CODES.has(failureCode)) {
|
|
842
|
-
const guide = buildRecoveryButton(failureCode,
|
|
843
|
-
tx('Codex CLI troubleshooting guide', '查看 Codex CLI 排查指引'),
|
|
844
|
-
tx('Opens the README section on fixing a missing `codex` command (PATH / login).', '打开 README 中「找不到 codex 命令」的排查说明(PATH/登录)。'));
|
|
845
|
-
guide.addEventListener('click', clickEvent => {
|
|
846
|
-
clickEvent.stopPropagation();
|
|
847
|
-
window.open(CODEX_CLI_TROUBLESHOOTING_URL, '_blank', 'noopener');
|
|
848
|
-
});
|
|
849
|
-
report.append(guide);
|
|
850
|
-
return;
|
|
851
|
-
}
|
|
852
|
-
if (failureCode !== 'codex_project_locked') return;
|
|
853
|
-
const button = document.createElement('button');
|
|
854
|
-
button.type = 'button';
|
|
855
|
-
button.className = 'run-final-answer__recovery-action';
|
|
856
|
-
button.dataset.recoveryFor = failureCode;
|
|
857
|
-
button.textContent = tx('Force-release the stuck task', '强制释放卡住的任务');
|
|
858
|
-
button.title = tx(
|
|
859
|
-
'Sends a force-cancel to the native host that drops the project lock so a new run can start. Use this when refreshing the tab did not free the project.',
|
|
860
|
-
'向 native host 发送强制取消请求,释放当前 Overleaf 项目的占用,让新的任务可以启动。刷新页面没用时使用。'
|
|
861
|
-
);
|
|
862
|
-
button.addEventListener('click', event => {
|
|
863
|
-
event.stopPropagation();
|
|
864
|
-
button.disabled = true;
|
|
865
|
-
button.textContent = tx('Releasing…', '正在释放…');
|
|
866
|
-
forceCancelStuckTaskForCurrentProject().then(result => {
|
|
867
|
-
if (result?.ok) {
|
|
868
|
-
button.textContent = tx('Force-released — you can retry the run.', '已释放,可以重试本轮任务。');
|
|
869
|
-
return;
|
|
870
|
-
}
|
|
871
|
-
// The helper resolves (never throws) with a non-ok result when the
|
|
872
|
-
// request was not delivered. Report the real outcome instead of a
|
|
873
|
-
// blanket success, and re-enable the button so the user can retry.
|
|
874
|
-
button.disabled = false;
|
|
875
|
-
button.textContent = tx('Release failed — refresh the tab, then try again.', '释放失败,请刷新页面后重试。');
|
|
876
|
-
}).catch(() => {
|
|
877
|
-
button.disabled = false;
|
|
878
|
-
button.textContent = tx('Release failed — refresh the tab, then try again.', '释放失败,请刷新页面后重试。');
|
|
879
|
-
});
|
|
880
|
-
});
|
|
881
|
-
report.append(button);
|
|
882
|
-
}
|
|
883
|
-
|
|
884
1030
|
// A run belongs to the tracked-change lifecycle iff trackedChangeStatus is set
|
|
885
1031
|
// or it still has tracked-change refs. Every other run is a legacy-undo run.
|
|
886
1032
|
function isTrackedChangeLifecycleRun(run) {
|
|
@@ -902,7 +1048,8 @@
|
|
|
902
1048
|
&& Boolean(String(run?.task || '').trim())
|
|
903
1049
|
&& typeof canEditRun === 'function' && canEditRun(run)
|
|
904
1050
|
&& typeof refillComposerForRetry === 'function';
|
|
905
|
-
|
|
1051
|
+
const cardHasEdit = Boolean(root.querySelector('.run-outcome [data-recovery-action="edit-resend"]'));
|
|
1052
|
+
button.hidden = !available || cardHasEdit;
|
|
906
1053
|
if (!available) return;
|
|
907
1054
|
button.disabled = typeof options.running === 'boolean'
|
|
908
1055
|
? options.running : Boolean(getCurrentRunView?.());
|
|
@@ -950,19 +1097,24 @@
|
|
|
950
1097
|
}
|
|
951
1098
|
|
|
952
1099
|
const projection = RunResultActions.projectUndoAvailability(run, projectRunSettlement);
|
|
953
|
-
if (!projection.canUndo && run.undoStatus !== 'applied') {
|
|
1100
|
+
if (!projection.canUndo && run.undoStatus !== 'applied' && !projection.undoUnavailable?.length) {
|
|
954
1101
|
button.hidden = true;
|
|
955
1102
|
return;
|
|
956
1103
|
}
|
|
957
1104
|
|
|
958
1105
|
button.hidden = false;
|
|
959
|
-
|
|
960
|
-
|
|
1106
|
+
const inFlight = trackedChangeInFlight?.get(run.id) === 'reject';
|
|
1107
|
+
const applied = run.undoStatus === 'applied' && !inFlight;
|
|
1108
|
+
button.disabled = !projection.canUndo || inFlight || (run.undoStatus === 'running' && !run.undoRecoveryCheckRequired) || run.undoStatus === 'applied';
|
|
1109
|
+
button.textContent = applied
|
|
961
1110
|
? tr('undoApplied')
|
|
962
1111
|
: (run.partialWriteback ? tr('undoPartialRun') : tr('undoRun'));
|
|
963
|
-
button.title =
|
|
1112
|
+
button.title = applied
|
|
964
1113
|
? tr('undoAppliedTitle')
|
|
965
1114
|
: (run.partialWriteback ? tr('undoPartialRunTitle') : tr('undoRunTitle'));
|
|
1115
|
+
if (run.undoRecoveryUnavailable) button.title = tx('Undo recovery checks are unavailable. Reload this page after the connection recovers.', '撤销恢复检查暂不可用,请在连接恢复后重新加载此页面。');
|
|
1116
|
+
if (projection.undoUnavailable?.length) button.title = tr('failureReason_snapshot_undo_unavailable_user')
|
|
1117
|
+
+ ' ' + projection.undoUnavailable.map(item => item.path).join(', ');
|
|
966
1118
|
button.addEventListener('click', event => {
|
|
967
1119
|
event.stopPropagation();
|
|
968
1120
|
undoRun(run.id);
|
|
@@ -979,6 +1131,9 @@
|
|
|
979
1131
|
const status = run.trackedChangeStatus || '';
|
|
980
1132
|
const inFlight = trackedChangeInFlight.get(run.id);
|
|
981
1133
|
const projection = projectRunSettlement(run);
|
|
1134
|
+
const undoTitle = projection.undoUnavailable?.length
|
|
1135
|
+
? tr('failureReason_snapshot_undo_unavailable_user') + ' ' + projection.undoUnavailable.map(item => item.path).join(', ')
|
|
1136
|
+
: tr('undoRunTitle');
|
|
982
1137
|
// §7 settlement matrix: needs_review keeps BOTH controls visible AND
|
|
983
1138
|
// actionable. Branch placed before the terminal branches so a
|
|
984
1139
|
// needs_review run is never treated as terminal.
|
|
@@ -986,7 +1141,7 @@
|
|
|
986
1141
|
button.hidden = false;
|
|
987
1142
|
button.disabled = !projection.canUndo || inFlight === 'reject' || inFlight === 'accept';
|
|
988
1143
|
button.textContent = tr('undoRun');
|
|
989
|
-
button.title =
|
|
1144
|
+
button.title = undoTitle;
|
|
990
1145
|
button.addEventListener('click', event => {
|
|
991
1146
|
event.stopPropagation();
|
|
992
1147
|
undoRun(run.id);
|
|
@@ -1006,7 +1161,7 @@
|
|
|
1006
1161
|
button.hidden = false;
|
|
1007
1162
|
button.disabled = true;
|
|
1008
1163
|
button.textContent = tr('undoRun');
|
|
1009
|
-
button.title = tr('
|
|
1164
|
+
button.title = tr('undoAcceptedTitle');
|
|
1010
1165
|
return;
|
|
1011
1166
|
}
|
|
1012
1167
|
if (status !== 'pending' && !inFlight) {
|
|
@@ -1016,7 +1171,7 @@
|
|
|
1016
1171
|
button.hidden = false;
|
|
1017
1172
|
button.disabled = !projection.canUndo || inFlight === 'reject' || inFlight === 'accept';
|
|
1018
1173
|
button.textContent = tr('undoRun');
|
|
1019
|
-
button.title =
|
|
1174
|
+
button.title = undoTitle;
|
|
1020
1175
|
button.addEventListener('click', event => {
|
|
1021
1176
|
event.stopPropagation();
|
|
1022
1177
|
undoRun(run.id);
|
|
@@ -1085,7 +1240,7 @@
|
|
|
1085
1240
|
button.hidden = false;
|
|
1086
1241
|
button.disabled = true;
|
|
1087
1242
|
button.textContent = tr('runAcceptTracked');
|
|
1088
|
-
button.title = tr('
|
|
1243
|
+
button.title = tr('undoAppliedTitle');
|
|
1089
1244
|
return;
|
|
1090
1245
|
}
|
|
1091
1246
|
if (status !== 'pending' && !inFlight) {
|
|
@@ -1146,7 +1301,7 @@
|
|
|
1146
1301
|
const log = getPanel()?.querySelector('[data-log]');
|
|
1147
1302
|
const existing = log?.querySelector(`[data-run-id="${cssEscape(runId)}"]`);
|
|
1148
1303
|
const run = findRunRecord(runId);
|
|
1149
|
-
if (!log || !existing || !run || displayRun(run) === run) {
|
|
1304
|
+
if (!log || !existing || !run || (displayRun(run) === run && !getFinishedRun(run))) {
|
|
1150
1305
|
return false;
|
|
1151
1306
|
}
|
|
1152
1307
|
const reading = scrollLayout?.snapshot(), open = existing.querySelector('[data-run-process]')?.open;
|
|
@@ -1158,7 +1313,43 @@
|
|
|
1158
1313
|
return true;
|
|
1159
1314
|
}
|
|
1160
1315
|
|
|
1316
|
+
function refreshLocale() {
|
|
1317
|
+
const log = getPanel()?.querySelector('[data-log]');
|
|
1318
|
+
if (!log) return;
|
|
1319
|
+
if (!getState().runs?.length) { renderRunHistory({ preserveScroll: true }); return; }
|
|
1320
|
+
const reading = scrollLayout?.snapshot() || { scrollTop: log.scrollTop };
|
|
1321
|
+
for (const run of getState().runs) {
|
|
1322
|
+
const existing = log.querySelector(`[data-run-id="${cssEscape(run.id)}"]`);
|
|
1323
|
+
if (!existing) continue;
|
|
1324
|
+
const current = getCurrentRunView();
|
|
1325
|
+
if (current?.root === existing) {
|
|
1326
|
+
// Streaming owners retain their DOM references and buffered model text.
|
|
1327
|
+
const status = existing.querySelector('[data-run-status]');
|
|
1328
|
+
if (status) status.textContent = getRunStatusText(run);
|
|
1329
|
+
configureAcceptButton(existing, run); configureUndoButton(existing, run);
|
|
1330
|
+
configureEditButton(existing, run); configureWritebackButton(existing, run);
|
|
1331
|
+
RunResultActions.configureResultActions(existing, run);
|
|
1332
|
+
const reportEvent = (run.events || []).filter(event => event.kind === 'report').at(-1);
|
|
1333
|
+
const report = current.report || existing.querySelector('[data-run-report]');
|
|
1334
|
+
if (reportEvent && report) refreshCompletionView(existing, report, displayLocalizedRun(run));
|
|
1335
|
+
activitySummary?.update(current);
|
|
1336
|
+
if (!activitySummary) RunGuidanceView.reconcileGuidance?.(existing.querySelector('[data-run-guidance]'),
|
|
1337
|
+
(run.events || []).concat(getPendingGuidance(run)), tx, { active: run.status === 'running' });
|
|
1338
|
+
continue;
|
|
1339
|
+
}
|
|
1340
|
+
const open = existing.querySelector('[data-run-process]')?.open;
|
|
1341
|
+
const next = renderRunCard(run);
|
|
1342
|
+
const process = next.querySelector('[data-run-process]');
|
|
1343
|
+
if (process && typeof open === 'boolean') process.open = open;
|
|
1344
|
+
existing.replaceWith(next);
|
|
1345
|
+
}
|
|
1346
|
+
log.scrollTop = reading.scrollTop;
|
|
1347
|
+
scrollLayout?.restore(reading);
|
|
1348
|
+
updateJumpToLatestButton(log);
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1161
1351
|
return {
|
|
1352
|
+
refreshLocale,
|
|
1162
1353
|
refreshResolvedUndo: refreshRunCard,
|
|
1163
1354
|
refreshActivitySummary: activitySummary?.update,
|
|
1164
1355
|
appendActivityDetail,
|
|
@@ -1166,6 +1357,7 @@
|
|
|
1166
1357
|
bindLogAutoFollow,
|
|
1167
1358
|
bumpUnreadIfDetached,
|
|
1168
1359
|
scrollLogToBottom,
|
|
1360
|
+
revealGuidance,
|
|
1169
1361
|
collapseRunProcess,
|
|
1170
1362
|
startRunElapsedTick,
|
|
1171
1363
|
stopRunElapsedTick,
|
|
@@ -1184,5 +1376,5 @@
|
|
|
1184
1376
|
};
|
|
1185
1377
|
}
|
|
1186
1378
|
|
|
1187
|
-
|
|
1188
|
-
})
|
|
1379
|
+
return { create };
|
|
1380
|
+
});
|