codex-overleaf-link 2.5.1 → 2.6.5
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 +149 -398
- package/README.zh-CN.md +153 -404
- package/extension/bootstrap/manifest.template.json +1 -1
- package/extension/src/background.js +148 -3
- package/extension/src/backgroundSharedSessions.js +55 -2
- package/extension/src/backgroundUpdateCoordinator.js +128 -12
- package/extension/src/content/activeTurnControl.js +164 -7
- package/extension/src/content/composerAttachments.js +46 -10
- package/extension/src/content/composerPanel.js +92 -12
- package/extension/src/content/contentRuntime.js +1114 -321
- package/extension/src/content/contextTray.js +8 -2
- package/extension/src/content/contextUsageView.js +267 -0
- package/extension/src/content/diagnosticsController.js +77 -34
- package/extension/src/content/diagnosticsExport.js +1 -1
- package/extension/src/content/diagnosticsPanel.js +147 -42
- package/extension/src/content/generated/content.bundle.js +5761 -1237
- package/extension/src/content/generated/content.bundle.meta.json +6 -3
- package/extension/src/content/legacyGlobalRegistry.js +4 -0
- package/extension/src/content/localSkillsPanel.js +6 -4
- package/extension/src/content/markdownDomRenderer.js +5 -3
- package/extension/src/content/markdownText.js +53 -14
- package/extension/src/content/modelPicker.js +29 -19
- package/extension/src/content/nativeCompatibilityController.js +52 -19
- package/extension/src/content/otWarmMirror.js +1 -1
- package/extension/src/content/pageBridgeClient.js +7 -7
- package/extension/src/content/panelMaintenance.js +10 -2
- package/extension/src/content/panelRenderer.js +55 -28
- package/extension/src/content/pendingInputView.js +1 -1
- package/extension/src/content/projectSettingsCoordinator.js +30 -6
- package/extension/src/content/providerSettingsDialog.js +28 -19
- package/extension/src/content/recentProjects.js +30 -19
- package/extension/src/content/runActivitySummary.js +122 -14
- package/extension/src/content/runCompletionMeta.js +194 -6
- package/extension/src/content/runController.js +2 -1
- package/extension/src/content/runFailureNotice.js +8 -1
- package/extension/src/content/runPresence.js +18 -8
- package/extension/src/content/runQueueScheduler.js +27 -13
- package/extension/src/content/runRecoveryActions.js +2 -1
- package/extension/src/content/runResultActions.js +73 -24
- package/extension/src/content/runReviewController.js +102 -37
- package/extension/src/content/runTimelineView.js +224 -25
- package/extension/src/content/scopedPersistencePanelState.js +124 -46
- package/extension/src/content/selectionContextView.js +30 -3
- package/extension/src/content/sensitivePreflight.js +14 -1
- package/extension/src/content/sessionManager.js +49 -3
- package/extension/src/content/sessionMenuView.js +54 -18
- package/extension/src/content/sessionPersistence.js +25 -5
- package/extension/src/content/settingsPanel.js +15 -2
- package/extension/src/content/settingsWorkbench.js +127 -89
- package/extension/src/content/sharedSessionViewSync.js +13 -4
- package/extension/src/content/subagentActivityView.js +23 -7
- package/extension/src/content/updateNotice.js +60 -6
- package/extension/src/content/writebackOrchestrator.js +301 -72
- package/extension/src/content/writingStyleSettings.js +70 -17
- package/extension/src/page/compileBridge.js +193 -180
- package/extension/src/page/overleafCapabilities.js +4 -1
- package/extension/src/page/overleafEditor.js +12 -3
- package/extension/src/page/overleafProjectSnapshot.js +56 -3
- package/extension/src/page/snapshotRouter.js +75 -66
- package/extension/src/page/textCoordinates.js +17 -1
- package/extension/src/page/textFileCreator.js +9 -1
- package/extension/src/page/treeOperations.js +200 -19
- package/extension/src/page/writebackRouter.js +172 -113
- package/extension/src/pageBridge.js +124 -73
- package/extension/src/popup.js +1 -1
- package/extension/src/shared/agentTranscript.js +372 -18
- package/extension/src/shared/auditRecords.js +3 -0
- package/extension/src/shared/compatibility.js +1 -1
- package/extension/src/shared/compileAdapter.js +58 -17
- package/extension/src/shared/composerAttachmentDraft.js +27 -7
- package/extension/src/shared/contextUsage.js +125 -0
- package/extension/src/shared/failureReasons.js +57 -3
- package/extension/src/shared/i18n.js +315 -29
- package/extension/src/shared/lineReferences.js +38 -31
- package/extension/src/shared/pageRpcContract.js +1 -1
- package/extension/src/shared/panelIcons.js +39 -0
- package/extension/src/shared/projectFiles.js +2 -1
- 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/scopedPersistenceBrowserAdapter.js +34 -0
- package/extension/src/shared/sessionState.js +63 -4
- package/extension/src/shared/sessionTitle.js +14 -1
- package/extension/src/shared/sharedSessionBridge.js +81 -13
- package/extension/src/shared/staleGuard.js +130 -63
- package/extension/src/shared/storageDb.js +35 -3
- package/extension/src/shared/storageMigration.js +111 -2
- package/extension/src/shared/storageRunActions.js +62 -8
- package/extension/src/shared/undoOperations.js +126 -28
- package/extension/src/shared/updateRuntimeIdentity.js +16 -2
- package/extension/src/shared/writebackSettlement.js +21 -3
- package/extension/styles/panel.css +1293 -549
- package/native-host/src/bootstrapUpdate.js +52 -16
- package/native-host/src/chatBridgeRequest.js +60 -6
- package/native-host/src/chatBridgeResponse.js +10 -1
- package/native-host/src/chatCompletionsBridge.js +29 -2
- package/native-host/src/codexPromptAssembly.js +86 -118
- package/native-host/src/codexSessionRunner.js +127 -17
- package/native-host/src/debugLog.js +27 -3
- package/native-host/src/index.js +42 -11
- package/native-host/src/localSkills.js +1 -1
- package/native-host/src/mirrorWorkspace.js +47 -3
- package/native-host/src/nativeEnvironment.js +27 -5
- package/native-host/src/providerContextError.js +20 -0
- package/native-host/src/providerRuntime.js +1 -0
- package/native-host/src/skills/reviewer-response/SKILL.md +65 -0
- package/native-host/src/taskRunnerRuntime.js +28 -8
- package/native-host/src/textPatch.js +473 -377
- package/native-host/src/updateManager.js +144 -21
- package/native-host/src/writingStyleRuntime.js +1 -1
- package/package.json +5 -8
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
|
|
4
4
|
function create(deps = {}) {
|
|
5
5
|
if (!deps.RunActivityModel) return null;
|
|
6
|
-
const { RunActivityModel: Model, RunGuidanceView, getPendingGuidance = () => [], tx, findRunRecord, getPanel, cssEscape, isGuidance,
|
|
7
|
-
formatElapsed, renderMarkdownBlockText, renderRunEvent, presence, classifyStatus, getProjectId, summarizeCommand, projectReviewResultEvent, formatReviewResultFailure,
|
|
6
|
+
const { RunActivityModel: Model, RunGuidanceView, getPendingGuidance = () => [], PanelIcons, tx, findRunRecord, getPanel, cssEscape, isGuidance,
|
|
7
|
+
formatElapsed, renderMarkdownBlockText, renderRunEvent, presence, getNotStartedRun, getFinishedRun, classifyStatus, getProjectId, summarizeCommand, projectReviewResultEvent, formatReviewResultFailure,
|
|
8
8
|
sanitizeAssistantVisibleText: sanitize = value => String(value || '') } = deps;
|
|
9
9
|
const cards = new WeakMap();
|
|
10
10
|
const choices = new Map();
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
return node;
|
|
25
25
|
};
|
|
26
26
|
const subagentView = deps.SubagentActivityView?.create({
|
|
27
|
-
tx, getPanel, plain, make, icon, formatElapsed,
|
|
27
|
+
tx, getPanel, plain, plainThought, make, icon, formatElapsed, glyph: name => deps.PanelIcons?.svg?.(name) || '',
|
|
28
28
|
renderBlocks: (parent, blocks, rows, card, run) => reconcile(parent, blocks, rows,
|
|
29
29
|
block => buildBlock(block, card), (row, block) => updateBlock(row, block, card, run))
|
|
30
30
|
});
|
|
@@ -229,6 +229,16 @@
|
|
|
229
229
|
el.addEventListener('toggle', row.showFull);
|
|
230
230
|
return row;
|
|
231
231
|
}
|
|
232
|
+
// One-line thought previews are plain text: drop the Markdown emphasis that
|
|
233
|
+
// reasoning summaries use for their headings ("**Reading the file**").
|
|
234
|
+
function plainThought(value) {
|
|
235
|
+
return String(value || '').replace(/(\*\*|__)(?=\S)([\s\S]*?\S)\1/g, '$2').replace(/\s+/g, ' ').trim();
|
|
236
|
+
}
|
|
237
|
+
// A finished thought that opens with a bold heading is summarised by that heading.
|
|
238
|
+
function thoughtLead(value) {
|
|
239
|
+
const heading = /^\s*(\*\*|__)(?=\S)(.+?\S)\1\s*(?:\n|$)/.exec(String(value || ''));
|
|
240
|
+
return heading ? heading[2].trim() : firstSentence(plainThought(value));
|
|
241
|
+
}
|
|
232
242
|
function updateThought(row, block, run) {
|
|
233
243
|
const value = String(sanitize(block.event.title) || '').trim();
|
|
234
244
|
const live = run.status === 'running' && block.event.status === 'running';
|
|
@@ -243,8 +253,7 @@
|
|
|
243
253
|
const started = Date.parse(block.startedAt || ''), ended = Date.parse(block.event.timestamp || '');
|
|
244
254
|
const seconds = Number.isFinite(started) && Number.isFinite(ended) && ended > started ? formatElapsed(ended - started) : '';
|
|
245
255
|
text(row.label, seconds ? tx('Thought ' + seconds, '思考 ' + seconds) : tx('Thought', '思考'));
|
|
246
|
-
|
|
247
|
-
text(row.lead, (flat.match(/^.{12,160}?[.!?。!?](?=\s|$)/) || [flat.slice(0, 160)])[0]);
|
|
256
|
+
text(row.lead, thoughtLead(value));
|
|
248
257
|
row.value = value;
|
|
249
258
|
row.showFull();
|
|
250
259
|
}
|
|
@@ -378,10 +387,10 @@
|
|
|
378
387
|
const live = run.status === 'running' && block.event.status === 'running';
|
|
379
388
|
const started = Date.parse(block.startedAt || ''), ended = Date.parse(block.event.timestamp || '');
|
|
380
389
|
const seconds = Number.isFinite(started) && Number.isFinite(ended) && ended > started ? ' ' + formatElapsed(ended - started) : '';
|
|
381
|
-
const flat = value
|
|
390
|
+
const flat = plainThought(value);
|
|
382
391
|
entries.push({ key: block.key, kind: 'thought', state: live ? 'running' : 'completed',
|
|
383
392
|
label: live ? tx('Thinking', '思考中') : tx('Thought', '思考') + seconds,
|
|
384
|
-
lead: live ? (flat.length > 90 ? '…' + flat.slice(-90) : flat) :
|
|
393
|
+
lead: live ? (flat.length > 90 ? '…' + flat.slice(-90) : flat) : thoughtLead(value) });
|
|
385
394
|
} else if (block.kind !== 'message' && block.kind !== 'guidance') {
|
|
386
395
|
for (const item of block.kind === 'exploreGroup' ? block.items : [block]) {
|
|
387
396
|
if (item.kind === 'notice') continue;
|
|
@@ -492,6 +501,74 @@
|
|
|
492
501
|
if (!choices.has(row.key)) row.el.open = items.some(item => card.projection.notices.some(notice => notice.key === item.key));
|
|
493
502
|
}
|
|
494
503
|
|
|
504
|
+
// Compaction marks sit outside the activity rows: a manual /compact run is
|
|
505
|
+
// shown as a divider, and a completed automatic compaction leaves one above
|
|
506
|
+
// that turn's answer.
|
|
507
|
+
function mountCompaction(root, run) {
|
|
508
|
+
const node = make('div', 'run-compaction');
|
|
509
|
+
node.hidden = true;
|
|
510
|
+
const line = make('div', 'run-compaction-line');
|
|
511
|
+
const label = make('span', 'run-compaction-label');
|
|
512
|
+
const glyph = make('span', 'run-compaction-glyph');
|
|
513
|
+
glyph.setAttribute('aria-hidden', 'true');
|
|
514
|
+
const title = make('span', 'run-compaction-title');
|
|
515
|
+
const time = make('span', 'run-compaction-time');
|
|
516
|
+
label.append(glyph, title, time);
|
|
517
|
+
line.append(label);
|
|
518
|
+
const sub = make('div', 'run-compaction-sub');
|
|
519
|
+
node.append(line, sub);
|
|
520
|
+
if (run.operation === 'compact') {
|
|
521
|
+
root.dataset.operation = 'compact';
|
|
522
|
+
node.dataset.kind = 'manual';
|
|
523
|
+
root.querySelector('[data-run-task]')?.after(node);
|
|
524
|
+
} else {
|
|
525
|
+
node.dataset.kind = 'auto';
|
|
526
|
+
root.querySelector('[data-run-report]')?.before(node);
|
|
527
|
+
}
|
|
528
|
+
return { node, glyph, title, time, sub, icon: '' };
|
|
529
|
+
}
|
|
530
|
+
function renderCompaction(card, run, result) {
|
|
531
|
+
const view = card.compaction;
|
|
532
|
+
if (!view) return;
|
|
533
|
+
const clock = value => {
|
|
534
|
+
const date = new Date(value || '');
|
|
535
|
+
return Number.isFinite(date.getTime())
|
|
536
|
+
? new Intl.DateTimeFormat(tx('en-US', 'zh-CN'), { hour: '2-digit', minute: '2-digit' }).format(date) : '';
|
|
537
|
+
};
|
|
538
|
+
const setIcon = name => {
|
|
539
|
+
if (view.icon === name) return;
|
|
540
|
+
view.icon = name;
|
|
541
|
+
view.glyph.innerHTML = name ? PanelIcons?.svg(name) || '' : '';
|
|
542
|
+
};
|
|
543
|
+
if (view.node.dataset.kind === 'manual') {
|
|
544
|
+
const state = run.status === 'running' ? 'running' : run.status === 'completed' ? 'completed'
|
|
545
|
+
: ['cancelled', 'rejected'].includes(run.status) ? 'cancelled' : run.status === 'interrupted' ? 'interrupted' : 'failed';
|
|
546
|
+
view.node.dataset.state = state;
|
|
547
|
+
view.node.hidden = false;
|
|
548
|
+
setIcon(state === 'running' ? '' : state === 'completed' ? 'compact' : 'alert');
|
|
549
|
+
text(view.title, ({
|
|
550
|
+
running: tx('Compacting context', '正在压缩上下文'),
|
|
551
|
+
completed: tx('Context compacted', '上下文已压缩'),
|
|
552
|
+
cancelled: tx('Compaction stopped', '已停止压缩'),
|
|
553
|
+
interrupted: tx('Compaction interrupted', '压缩已中断'),
|
|
554
|
+
failed: tx('Compaction did not finish', '压缩未完成')
|
|
555
|
+
})[state]);
|
|
556
|
+
text(view.time, state === 'running' ? '' : clock(run.finishedAt));
|
|
557
|
+
text(view.sub, state === 'running' ? tx('You can keep typing; messages send after compaction.', '可以继续输入,消息会在压缩完成后发送。')
|
|
558
|
+
: state === 'completed' ? tx('Earlier turns were summarized.', '此前的对话已整理为摘要。') : '');
|
|
559
|
+
view.sub.hidden = !view.sub.textContent;
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
view.node.hidden = !result.compactedAt;
|
|
563
|
+
if (!result.compactedAt) return;
|
|
564
|
+
view.node.dataset.state = 'completed';
|
|
565
|
+
setIcon('compact');
|
|
566
|
+
text(view.title, tx('Context compacted automatically', '上下文已自动压缩'));
|
|
567
|
+
text(view.time, clock(result.compactedAt));
|
|
568
|
+
text(view.sub, tx('Codex summarized earlier turns partway through this turn, then continued.',
|
|
569
|
+
'Codex 在这一轮中途整理了此前的对话,然后继续处理。'));
|
|
570
|
+
view.sub.hidden = false;
|
|
571
|
+
}
|
|
495
572
|
function mount(root, run) {
|
|
496
573
|
if (!Model || cards.has(root)) return;
|
|
497
574
|
const process = root.querySelector('[data-run-process]');
|
|
@@ -532,7 +609,7 @@
|
|
|
532
609
|
const card = { id: String(run.id), root, process, summary, events, metrics, attention,
|
|
533
610
|
guidance: root.querySelector('[data-run-guidance]'),
|
|
534
611
|
busy, rows: new Map(), run, setup: setupEl, setupSummary, setupFold, setupSteps,
|
|
535
|
-
peek, peekTrack, peekMore, peekRows: new Map() };
|
|
612
|
+
peek, peekTrack, peekMore, peekRows: new Map(), compaction: mountCompaction(root, run) };
|
|
536
613
|
const expand = () => { remember(card.id + ':run', true); process.open = true; update({ root, recordId: run.id }); };
|
|
537
614
|
peek.addEventListener('click', expand);
|
|
538
615
|
peekMore.addEventListener('click', expand);
|
|
@@ -543,9 +620,10 @@
|
|
|
543
620
|
events.dataset.expanded = String(process.open);
|
|
544
621
|
card.attention.hidden = process.open || !card.noticeCount;
|
|
545
622
|
if (card.projection) {
|
|
546
|
-
|
|
623
|
+
const active = card.run.status === 'running' && !card.notStarted;
|
|
624
|
+
renderSetup(card, card.projection.setup, active);
|
|
547
625
|
if (!process.open) card.setup.hidden = true;
|
|
548
|
-
renderPeek(card, card.projection, card.run,
|
|
626
|
+
renderPeek(card, card.projection, card.run, active);
|
|
549
627
|
}
|
|
550
628
|
});
|
|
551
629
|
render(card, run);
|
|
@@ -553,6 +631,21 @@
|
|
|
553
631
|
function render(card, run, incoming) {
|
|
554
632
|
card.run = run;
|
|
555
633
|
const result = Model.project(run, incoming, { isGuidance });
|
|
634
|
+
const notStarted = getNotStartedRun?.(run);
|
|
635
|
+
const outcome = getFinishedRun?.(run);
|
|
636
|
+
card.root.dataset.outcomeTone = outcome?.tone || '';
|
|
637
|
+
card.root.dataset.outcome = outcome?.kind || '';
|
|
638
|
+
card.notStarted = Boolean(notStarted);
|
|
639
|
+
if (notStarted) {
|
|
640
|
+
// The single launch card replaces preflight workflow/Notice projections.
|
|
641
|
+
// Stored events remain available to history, audit and recovery.
|
|
642
|
+
result.blocks = [];
|
|
643
|
+
result.notices = [];
|
|
644
|
+
result.setup = null;
|
|
645
|
+
}
|
|
646
|
+
// The outcome card owns the visible cause. Keep workflow evidence in the
|
|
647
|
+
// existing process disclosure, without another attention banner.
|
|
648
|
+
if (outcome) result.notices = [];
|
|
556
649
|
const guidanceEvents = (run.events || []).concat(incoming && !(run.events || []).includes(incoming) ? [incoming] : []);
|
|
557
650
|
RunGuidanceView?.reconcileGuidance(card.guidance,
|
|
558
651
|
guidanceEvents.concat(getPendingGuidance(run)),
|
|
@@ -560,11 +653,12 @@
|
|
|
560
653
|
result.blocks = result.blocks.filter(block => block.kind !== 'guidance');
|
|
561
654
|
card.projection = result;
|
|
562
655
|
subagentView?.update(card);
|
|
563
|
-
const active = run.status === 'running';
|
|
656
|
+
const active = run.status === 'running' && !notStarted;
|
|
564
657
|
const abnormal = ['failed', 'interrupted', 'needs_review_after_navigation', 'abandoned_after_navigation'].includes(run.status);
|
|
565
|
-
if (
|
|
658
|
+
if (notStarted) card.process.open = false;
|
|
659
|
+
else if (choices.has(card.id + ':run')) card.process.open = choices.get(card.id + ':run');
|
|
566
660
|
// A live run starts collapsed onto its peek window; problems open it.
|
|
567
|
-
else card.process.open = abnormal;
|
|
661
|
+
else card.process.open = abnormal && !outcome;
|
|
568
662
|
card.events.dataset.expanded = String(card.process.open);
|
|
569
663
|
card.summary.setAttribute('aria-expanded', String(card.process.open));
|
|
570
664
|
reconcile(card.events, result.blocks, card.rows, block => buildBlock(block, card),
|
|
@@ -575,6 +669,7 @@
|
|
|
575
669
|
else if (!choices.has(card.id + ':setup')) card.setup.open = false;
|
|
576
670
|
if (!card.process.open) card.setup.hidden = true;
|
|
577
671
|
renderPeek(card, result, run, active);
|
|
672
|
+
renderCompaction(card, run, result);
|
|
578
673
|
card.noticeCount = result.notices.length;
|
|
579
674
|
const latestNotice = result.notices.at(-1);
|
|
580
675
|
card.attention.hidden = card.process.open || !latestNotice;
|
|
@@ -589,12 +684,13 @@
|
|
|
589
684
|
|| block.meta?.state === 'running');
|
|
590
685
|
// The presence header already narrates the live state.
|
|
591
686
|
card.busy.hidden = !active || hasActive || Boolean(presence);
|
|
592
|
-
text(card.busy, result.stage === 'local_completed'
|
|
687
|
+
text(card.busy, result.compacting ? tx('Compacting context…', '正在压缩上下文…') : result.stage === 'local_completed'
|
|
593
688
|
? (run.mode === 'ask' ? tx('Preparing the answer…', '正在整理回答…') : tx('Preparing writeback and verification…', '正在准备写回与验证…'))
|
|
594
689
|
: result.stage === 'preparing' ? tx('Preparing project context…', '正在准备项目上下文…') : tx('Working…', '正在处理…'));
|
|
595
690
|
}
|
|
596
691
|
// Live header: long phases are named; otherwise a rarely-changing word. Tool steps stay in the round list.
|
|
597
692
|
function runningLabel(result, run) {
|
|
693
|
+
if (result.compacting) return { kind: 'stage', text: tx('Compacting context', '正在压缩上下文') };
|
|
598
694
|
if (result.stage === 'sync') return { kind: 'stage', text: tx('Syncing changes to Overleaf', '正在同步改动到 Overleaf') };
|
|
599
695
|
// Before the model's first response the header names the real start-up step, not a playful word.
|
|
600
696
|
if (result.setup && !result.setup.done) return { kind: 'stage', text: setupLiveLabel(result.setup) };
|
|
@@ -608,6 +704,11 @@
|
|
|
608
704
|
function paintPresence(card, run, result, active) {
|
|
609
705
|
const status = card.root.querySelector('[data-run-status]');
|
|
610
706
|
if (!presence || !status) return;
|
|
707
|
+
const notStarted = getNotStartedRun?.(run);
|
|
708
|
+
if (notStarted) {
|
|
709
|
+
presence.paintSettled(status, { kind: notStarted.cancelled ? 'not-started-cancelled' : 'not-started', text: notStarted.status });
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
611
712
|
// History cards arrive with plain status text; keep it for failure and fallback labels.
|
|
612
713
|
if (!status.dataset.presence && !status.dataset.presenceText) status.dataset.presenceText = status.textContent;
|
|
613
714
|
const started = Date.parse(run.startedAt || '');
|
|
@@ -621,6 +722,12 @@
|
|
|
621
722
|
elapsedMs: Number.isFinite(started) && Number.isFinite(finished) ? Math.max(0, finished - started) : null,
|
|
622
723
|
files: result.fileCount, filesClipped: result.clipped === true, added: 0, removed: 0, steps: 0, compiled: false,
|
|
623
724
|
projectId: getProjectId?.() || '', animate: card.animateNext === true };
|
|
725
|
+
const outcome = getFinishedRun?.(run);
|
|
726
|
+
if (outcome?.status) {
|
|
727
|
+
facts.kind = outcome.kind;
|
|
728
|
+
facts.text = outcome.status;
|
|
729
|
+
}
|
|
730
|
+
if (outcome?.kind === 'compile-errors') facts.compileChip = outcome.compileChip;
|
|
624
731
|
card.animateNext = false;
|
|
625
732
|
for (const block of result.blocks) {
|
|
626
733
|
if (['message', 'guidance', 'thought'].includes(block.kind)) continue;
|
|
@@ -631,6 +738,7 @@
|
|
|
631
738
|
}
|
|
632
739
|
if (block.kind === 'compile' && block.meta?.state === 'completed') facts.compiled = true;
|
|
633
740
|
}
|
|
741
|
+
if (facts.compileChip) facts.compiled = false;
|
|
634
742
|
presence.paintSettled(status, facts);
|
|
635
743
|
}
|
|
636
744
|
function update(view, incoming) {
|
|
@@ -4,6 +4,180 @@
|
|
|
4
4
|
})(typeof window !== 'undefined' ? window : globalThis, function (FailureReasons, I18n, AgentTranscript) {
|
|
5
5
|
'use strict';
|
|
6
6
|
|
|
7
|
+
// Read only persisted product evidence; never infer a launch outcome from
|
|
8
|
+
// arbitrary answer text or change the run's archived status.
|
|
9
|
+
function projectNotStartedRun(run, locale = 'en') {
|
|
10
|
+
const event = (Array.isArray(run?.events) ? run.events : []).filter(item => item?.kind === 'report').at(-1);
|
|
11
|
+
const source = event?.detailStructured?.conclusionSource;
|
|
12
|
+
if (source?.origin !== 'product' || source.key !== 'launch_not_started'
|
|
13
|
+
|| !['en', 'zh'].some(language => AgentTranscript.projectProductConclusion(source, language)
|
|
14
|
+
=== event.detailStructured.conclusion)) return null;
|
|
15
|
+
const meta = Array.isArray(event.detailStructured.meta) ? event.detailStructured.meta : [];
|
|
16
|
+
const savedNext = meta.find(row => row?.key === 'nextStep')?.value || '';
|
|
17
|
+
const declinedChoice = event.failure?.evidence?.declinedChoice;
|
|
18
|
+
const cancelled = declinedChoice === true || run.status === 'cancelled'
|
|
19
|
+
// Legacy reports predate the structured marker; only those need the copy fallback.
|
|
20
|
+
|| (declinedChoice === undefined && ['en', 'zh'].some(language =>
|
|
21
|
+
savedNext === I18n.t(language, 'failureReason_reviewing_unavailable_cancelled_next')));
|
|
22
|
+
const unavailable = event.failure?.code === 'reviewing_unavailable' || cancelled;
|
|
23
|
+
const tx = (en, zh) => locale === 'zh' ? zh : en;
|
|
24
|
+
const next = localizeMetaRow({ key: 'nextStep', value: savedNext }, {
|
|
25
|
+
tx, getLocale: () => locale, tr: key => I18n.t(locale, key)
|
|
26
|
+
});
|
|
27
|
+
return {
|
|
28
|
+
cancelled,
|
|
29
|
+
status: I18n.t(locale, cancelled ? 'runNotStartedCancelled' : 'runNotStarted'),
|
|
30
|
+
title: I18n.t(locale, unavailable ? 'runNotStartedTrackTitle' : 'runNotStartedModeTitle'),
|
|
31
|
+
body: I18n.t(locale, unavailable ? 'runNotStartedTrackBody' : 'runNotStartedModeBody'),
|
|
32
|
+
nextStep: cancelled ? I18n.t(locale, 'runNotStartedCancelledNext')
|
|
33
|
+
: next.value || I18n.t(locale, 'runNotStartedNext')
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function projectCompletionConclusion(event, run, locale = 'en', options = {}) {
|
|
38
|
+
const structured = event?.detailStructured;
|
|
39
|
+
const raw = options.flat ? options.text : String(structured?.conclusion || '');
|
|
40
|
+
if (structured?.conclusionSource) {
|
|
41
|
+
if (structured.conclusionSource.key !== 'writeback'
|
|
42
|
+
&& !['en', 'zh'].some(language => AgentTranscript.projectProductConclusion(structured.conclusionSource, language) === raw)) return raw;
|
|
43
|
+
return AgentTranscript.projectProductConclusion(structured.conclusionSource, locale, options.localizeCompileReason, raw) ?? raw;
|
|
44
|
+
}
|
|
45
|
+
// Reload recovery predates structured conclusions but retains an explicit
|
|
46
|
+
// product source. Translate only its intact flat report, never lookalikes.
|
|
47
|
+
if (options.flat && !structured && event?.kind === 'report' && event.status === 'failed'
|
|
48
|
+
&& !event.streamRole && !event.guidanceId && !event.subagent
|
|
49
|
+
&& event.failure?.code === 'native_request_failed' && event.failure.source === 'panel_reload'
|
|
50
|
+
&& raw === event.detail && ['en', 'zh'].some(language =>
|
|
51
|
+
event.title === I18n.t(language, 'restoredRunStoppedTitle')
|
|
52
|
+
&& raw === I18n.t(language, 'restoredRunStoppedDetail'))) {
|
|
53
|
+
return I18n.t(locale, 'restoredRunStoppedDetail');
|
|
54
|
+
}
|
|
55
|
+
// Legacy cancellation has a unique adjacent producer-owned lifecycle event.
|
|
56
|
+
// Text alone, status alone, and a user's/model's matching answer confer no identity.
|
|
57
|
+
const events = Array.isArray(run?.events) ? run.events : [];
|
|
58
|
+
const matches = events.map((item, index) => ({ item, index })).filter(({ item }) => item.kind === 'report'
|
|
59
|
+
&& (structured ? item.detailStructured === structured : item.detail === event.detail));
|
|
60
|
+
const previous = matches.length === 1 ? events[matches[0].index - 1] : null;
|
|
61
|
+
if (run?.status !== 'cancelled' || event.kind !== 'report' || event.status !== 'completed'
|
|
62
|
+
|| !['Task report', '本轮完成报告'].includes(event.title)
|
|
63
|
+
|| previous?.activity?.kind !== 'lifecycle' || previous.activity.state !== 'cancelled'
|
|
64
|
+
|| previous.activity.target !== 'cancelled' || previous.streamRole || previous.guidanceId
|
|
65
|
+
|| !['Current Codex task was cancelled.', '已中断当前 Codex 任务。'].includes(previous.title)) return raw;
|
|
66
|
+
const source = { origin: 'product', key: 'cancelled' };
|
|
67
|
+
const en = AgentTranscript.projectProductConclusion(source, 'en'), zh = AgentTranscript.projectProductConclusion(source, 'zh');
|
|
68
|
+
const known = options.flat ? [`Conclusion: ${en}`, `结论:${zh}`] : [en, zh];
|
|
69
|
+
if (!known.includes(raw)) return raw;
|
|
70
|
+
const projected = AgentTranscript.projectProductConclusion(source, locale);
|
|
71
|
+
return options.flat ? (locale === 'zh' ? `结论:${projected}` : `Conclusion: ${projected}`) : projected;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Like projectNotStartedRun, this is a view of intact product reports, never
|
|
75
|
+
// a new terminal state. Counts remain in storage (retry replacement reads them).
|
|
76
|
+
function projectFinishedRun(run, locale = 'en', { projectRunSettlement } = {}) {
|
|
77
|
+
if (!run || run.status === 'running' || run.retryingWriteback || projectNotStartedRun(run, locale)) return null;
|
|
78
|
+
const event = (Array.isArray(run.events) ? run.events : []).filter(item => item?.kind === 'report').at(-1);
|
|
79
|
+
const structured = event?.detailStructured, source = structured?.conclusionSource;
|
|
80
|
+
if (typeof structured?.conclusion !== 'string' || source?.origin !== 'product' || !['en', 'zh'].some(language =>
|
|
81
|
+
AgentTranscript.projectProductConclusion(source, language, undefined, structured.conclusion) === structured.conclusion)) return null;
|
|
82
|
+
const t = (key, values) => I18n.t(locale, key, values);
|
|
83
|
+
const tx = (en, zh) => locale === 'zh' ? zh : en;
|
|
84
|
+
const meta = Array.isArray(structured.meta) ? structured.meta : [];
|
|
85
|
+
const value = key => meta.find(row => row?.key === key)?.value || '';
|
|
86
|
+
const counts = /^(?:wrote (\d+) items?, skipped (\d+) items?|已写入 (\d+) 项,跳过 (\d+) 项)$/.exec(value('writeResult'));
|
|
87
|
+
const wrote = counts ? Number(counts[1] ?? counts[3]) : null;
|
|
88
|
+
const skipped = counts ? Number(counts[2] ?? counts[4]) : null;
|
|
89
|
+
const projection = projectRunSettlement?.(run) || {};
|
|
90
|
+
const facts = run.settlement || run.settlementFacts || {};
|
|
91
|
+
const failure = projection.primaryFailure || event.failure;
|
|
92
|
+
const files = Array.isArray(facts.fileSettlements) ? facts.fileSettlements.filter(item => item && typeof item.path === 'string') : [];
|
|
93
|
+
const reloadInterrupted = source.key === 'reload_interrupted';
|
|
94
|
+
const payload = run.recoveryPayload || run;
|
|
95
|
+
const hasWriteEvidence = wrote > 0 || files.length > 0 || Boolean(run.saveCheck)
|
|
96
|
+
|| ['appliedOperations', 'undoOperations', 'undoTrackedChanges'].some(key => payload[key]?.length)
|
|
97
|
+
|| (reloadInterrupted && run.recoveryRetention?.state === 'omitted')
|
|
98
|
+
|| ![undefined, 'none'].includes(facts.evidence?.applied)
|
|
99
|
+
|| ![undefined, 'not-attempted'].includes(facts.evidence?.readBack)
|
|
100
|
+
|| projection.changedDocument === true || ['changed', 'possibly-changed'].includes(facts.documentEffect);
|
|
101
|
+
const unconfirmed = projection.requiresWriteReview === true
|
|
102
|
+
|| FailureReasons.isUnconfirmedWriteFailure(failure)
|
|
103
|
+
|| facts.documentEffect === 'possibly-changed' || facts.evidence?.readBack === 'mismatch'
|
|
104
|
+
|| (reloadInterrupted && hasWriteEvidence);
|
|
105
|
+
const noWrites = wrote === 0 && skipped === 0 && !unconfirmed && !hasWriteEvidence;
|
|
106
|
+
const display = FailureReasons.formatFailureReasonForDisplay(failure, locale, key => {
|
|
107
|
+
const translated = t(key);
|
|
108
|
+
return translated !== key ? translated : undefined;
|
|
109
|
+
});
|
|
110
|
+
const file = typeof failure?.file === 'string' ? failure.file : '';
|
|
111
|
+
const shortKey = `runOutcomeCause_${failure?.source === 'panel_reload' ? 'panel_reload_interrupted' : failure?.code}`;
|
|
112
|
+
const detectorIds = Array.isArray(failure?.evidence?.detectorIds) ? [...new Set(failure.evidence.detectorIds)] : [];
|
|
113
|
+
const detectorId = detectorIds.length === 1 ? detectorIds[0] : '';
|
|
114
|
+
const findingKey = typeof detectorId === 'string' && /^[a-z]+(?:-[a-z]+)+$/.test(detectorId)
|
|
115
|
+
? `sensitiveFinding_${detectorId.replace(/-/g, '_')}` : '';
|
|
116
|
+
const label = t(I18n.MESSAGES.en[findingKey] ? findingKey : 'sensitiveFinding_mixed');
|
|
117
|
+
// Chinese copy separates a Latin term from the preceding Han text with a space.
|
|
118
|
+
const finding = locale === 'zh' && /^[A-Za-z]/.test(label) ? ` ${label}` : label;
|
|
119
|
+
const shortTitle = I18n.MESSAGES[locale]?.[shortKey] ? t(shortKey, { file, finding }) : '';
|
|
120
|
+
const savedNext = localizeMetaRow({ key: 'nextStep', value: value('nextStep') }, {
|
|
121
|
+
tx, getLocale: () => locale, tr: t
|
|
122
|
+
}).value;
|
|
123
|
+
const base = { failure, file, wrote, skipped, noWrites, unconfirmed,
|
|
124
|
+
nextStep: display?.nextAction || savedNext || t('runOutcomeNext'), files: [] };
|
|
125
|
+
if (unconfirmed) return { ...base, kind: 'check-overleaf', tone: 'error',
|
|
126
|
+
status: t('runOutcomeCheck'), title: ['undo', 'accept'].includes(failure?.stage)
|
|
127
|
+
? shortTitle || t('runOutcomeCheck') : t(file ? 'runOutcomeUnconfirmedFile' : 'runOutcomeUnconfirmed', { file }),
|
|
128
|
+
body: ['undo', 'accept'].includes(failure?.stage) ? display?.userMessage || t('runOutcomeUnconfirmedBody') : t('runOutcomeUnconfirmedBody'),
|
|
129
|
+
nextStep: file && !['undo', 'accept'].includes(failure?.stage) ? t('runOutcomeCheckNext', { file }) : base.nextStep };
|
|
130
|
+
if (reloadInterrupted) return { ...base, kind: 'didnt-finish', tone: 'warning',
|
|
131
|
+
status: t('runOutcomeIncomplete'), title: t('runOutcomeCause_panel_reload_interrupted'),
|
|
132
|
+
body: t(run.mode === 'ask' ? 'runOutcomeNoWrites' : 'runOutcomeReloadUnknownBody') };
|
|
133
|
+
if (wrote > 0 && skipped > 0) {
|
|
134
|
+
const failures = Array.isArray(facts.failures) ? facts.failures : [];
|
|
135
|
+
const entries = files.flatMap(item => {
|
|
136
|
+
const result = [];
|
|
137
|
+
if (['complete', 'partial'].includes(item.applied)) result.push({ file: item.path, written: true });
|
|
138
|
+
if (['failed', 'partial'].includes(item.applied)) {
|
|
139
|
+
const cause = failures.find(candidate => candidate?.file === item.path)
|
|
140
|
+
|| { code: item.failureCodes?.[0], file: item.path };
|
|
141
|
+
const reason = cause.code === 'stale_source_changed' ? t('runOutcomeStaleReason')
|
|
142
|
+
: FailureReasons.formatFailureReasonForDisplay(cause, locale, t)?.userMessage || t('runOutcomeSkipped');
|
|
143
|
+
result.push({ file: item.path, written: false, reason });
|
|
144
|
+
}
|
|
145
|
+
return result;
|
|
146
|
+
});
|
|
147
|
+
return { ...base, kind: 'partly-written', tone: 'warning', status: t('runOutcomePartial'),
|
|
148
|
+
title: t('runOutcomePartialTitle', { wrote, total: wrote + skipped }), body: '',
|
|
149
|
+
files: entries.sort((a, b) => Number(b.written) - Number(a.written)),
|
|
150
|
+
nextStep: t('runOutcomePartialNext') };
|
|
151
|
+
}
|
|
152
|
+
if (wrote === 0 && skipped > 0 && facts.documentEffect === 'unchanged' && failure) {
|
|
153
|
+
return { ...base, kind: 'not-written', tone: 'warning', status: t('runOutcomeNotWritten'),
|
|
154
|
+
title: shortTitle || t('runOutcomeNotWritten'),
|
|
155
|
+
body: shortTitle ? t('runOutcomeNotWrittenBody') : display?.userMessage || t('runOutcomeNotWrittenBody'),
|
|
156
|
+
nextStep: failure.code === 'stale_source_changed' ? t('runOutcomeStaleNext') : base.nextStep };
|
|
157
|
+
}
|
|
158
|
+
if (noWrites && ['cancelled', 'rejected'].includes(run.status)) return { ...base, kind: 'cancelled', tone: 'neutral' };
|
|
159
|
+
if (noWrites && (event.status === 'failed' || run.status === 'failed') && failure) {
|
|
160
|
+
return { ...base, kind: 'didnt-finish', tone: 'warning', status: t('runOutcomeIncomplete'),
|
|
161
|
+
title: shortTitle || t('runOutcomeIncomplete'), body: shortTitle ? t('runOutcomeNoWrites') : display?.userMessage || t('runOutcomeNoWrites'),
|
|
162
|
+
nextStep: failure.code === 'codex_context_limit' ? t('runOutcomeContextNext')
|
|
163
|
+
: failure.code === 'native_request_failed' && failure.source !== 'panel_reload' ? t('runOutcomeNativeNext') : base.nextStep };
|
|
164
|
+
}
|
|
165
|
+
if (source.key === 'writeback' && source.outcome === 'synced' && source.hasCompile && source.errorCount > 0
|
|
166
|
+
&& !['failed', 'cancelled', 'rejected'].includes(run.status)) {
|
|
167
|
+
// Verify the whole original suffix above before projecting just the prefix
|
|
168
|
+
// and sync result. Never strip a model sentence by matching its wording.
|
|
169
|
+
const prefix = structured.conclusion.slice(0, source.prefixLength);
|
|
170
|
+
const raw = prefix || AgentTranscript.projectProductConclusion({ origin: 'product', key: 'synced' }, locale);
|
|
171
|
+
const conclusion = prefix ? (source.prefixKey
|
|
172
|
+
? AgentTranscript.projectProductConclusion({ origin: 'product', key: source.prefixKey }, locale) : prefix) : raw;
|
|
173
|
+
return { ...base, kind: 'compile-errors', tone: 'warning', conclusion,
|
|
174
|
+
compileCount: source.errorCount, compileErrors: (Array.isArray(source.errors) ? source.errors : []).slice(0, 3),
|
|
175
|
+
compileChip: t('runOutcomeCompileChip', { count: source.errorCount, plural: source.errorCount === 1 ? '' : 's' }),
|
|
176
|
+
title: t('runOutcomeCompileTitle', { count: source.errorCount, plural: source.errorCount === 1 ? '' : 's' }) };
|
|
177
|
+
}
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
|
|
7
181
|
const FLAT_REPORT_STATUS_SECTIONS = [
|
|
8
182
|
{ key: 'saveState', prefixes: ['Save:', '保存:'], en: 'Save', zh: '保存' },
|
|
9
183
|
{ key: 'unchangedReason', prefixes: ['Why nothing changed:', '未修改原因:'], en: 'Why nothing changed', zh: '未修改原因' },
|
|
@@ -19,7 +193,8 @@
|
|
|
19
193
|
['This run was Ask mode.', '这轮是只问不改。'],
|
|
20
194
|
['You cancelled this change.', '你取消了这轮修改。'],
|
|
21
195
|
['No file changes need to sync back to Overleaf.', '没有产生需要同步回 Overleaf 的文件改动。'],
|
|
22
|
-
['No approved file changes remain to sync back to Overleaf.', '没有剩余已确认的文件改动需要同步回 Overleaf。']
|
|
196
|
+
['No approved file changes remain to sync back to Overleaf.', '没有剩余已确认的文件改动需要同步回 Overleaf。'],
|
|
197
|
+
['Sensitive content was found before sending.', '发送前发现了敏感内容。']
|
|
23
198
|
],
|
|
24
199
|
undo: [['this run has no reversible writes', '本轮没有可撤销的写入']],
|
|
25
200
|
saveState: [['Saved', '已确认保存'],
|
|
@@ -230,6 +405,8 @@
|
|
|
230
405
|
'本轮结束时记录了以下文件夹,当前状态尚未重新核验:') + paths.join(', ') });
|
|
231
406
|
}
|
|
232
407
|
const retentionOmitted = hasOmittedRecoveryRetention(run);
|
|
408
|
+
const undoUnavailable = !undone && run.trackedChangeStatus !== 'accepted'
|
|
409
|
+
? projectRunSettlement?.(run).undoUnavailable || [] : [];
|
|
233
410
|
const inFlight = trackedChangeInFlight?.get(run.id);
|
|
234
411
|
const tracked = isTrackedChangeLifecycleRun(run);
|
|
235
412
|
const pendingFileUndo = tracked && run.trackedChangeStatus === 'needs_review' && run.undoOperations?.some(operation => operation?.undoCreatedFile?.v === 1 || operation?.type === 'create');
|
|
@@ -249,7 +426,7 @@
|
|
|
249
426
|
? tx(pendingFileUndo ? 'Undo is incomplete; retry the remaining files.' : 'Undo is incomplete; remaining changes can still be undone.', pendingFileUndo ? '撤销未完成,可重试剩余文件。' : '撤销未完成,剩余修改仍可撤销。')
|
|
250
427
|
: tx('Undo is incomplete.', '撤销未完成。');
|
|
251
428
|
}
|
|
252
|
-
if (!undo && !run.saveCheck && !run.saveConfirmedAt && !retentionOmitted) return rows;
|
|
429
|
+
if (!undo && !run.saveCheck && !run.saveConfirmedAt && !retentionOmitted && !undoUnavailable.length) return rows;
|
|
253
430
|
const projected = rows.map(row => row?.key === 'undo' && undo
|
|
254
431
|
? { ...row, label: tx('Undo', '撤销'), value: undo }
|
|
255
432
|
: row?.key === 'saveState' && (run.saveCheck || run.saveConfirmedAt)
|
|
@@ -270,6 +447,13 @@
|
|
|
270
447
|
value: directory && (run.recoveryRetention.coreRetained === true || undone || run.trackedChangeStatus === 'accepted')
|
|
271
448
|
? directory : [recovery, directory].filter(Boolean).join(' ') });
|
|
272
449
|
}
|
|
450
|
+
if (undoUnavailable.length) {
|
|
451
|
+
const warning = options.tr('failureReason_snapshot_undo_unavailable_user')
|
|
452
|
+
+ ' ' + undoUnavailable.map(item => item.path).join(', ');
|
|
453
|
+
const retained = projected.find(row => row.key === 'recoveryRetention');
|
|
454
|
+
if (retained) { retained.value += ' ' + warning; retained.undoUnavailable = true; }
|
|
455
|
+
else projected.push({ key: 'recoveryRetention', label: tx('Recovery data', '恢复材料'), undoUnavailable: true, value: warning });
|
|
456
|
+
}
|
|
273
457
|
return projected;
|
|
274
458
|
}
|
|
275
459
|
|
|
@@ -311,6 +495,7 @@
|
|
|
311
495
|
const reason = find('unchangedReason');
|
|
312
496
|
const undone = run?.undoStatus === 'applied' || run?.trackedChangeStatus === 'rejected';
|
|
313
497
|
const retentionOmitted = hasOmittedRecoveryRetention(run);
|
|
498
|
+
const undoUnavailable = rows.some(row => row?.undoUnavailable === true);
|
|
314
499
|
const savePending = !undone && (Boolean(run?.saveCheck) || (!run?.saveConfirmedAt && /^(?:Pending|待确认)/.test(find('saveState'))));
|
|
315
500
|
const pendingFileUndo = run?.trackedChangeStatus === 'needs_review'
|
|
316
501
|
&& run.undoOperations?.some(operation => operation?.undoCreatedFile?.v === 1 || operation?.type === 'create');
|
|
@@ -327,18 +512,21 @@
|
|
|
327
512
|
: pendingFileUndo || run?.undoStatus === 'partial'
|
|
328
513
|
? (hasUndoProgress(run) ? tx('Partly undone', '已撤销部分修改') : tx('Undo incomplete', '撤销未完成'))
|
|
329
514
|
: undoable ? tx(`${undoable} undoable`, `可撤销 ${undoable} 项`)
|
|
330
|
-
|
|
515
|
+
// With nothing written, "Nothing to undo" repeats the write fact; the
|
|
516
|
+
// expanded details still carry the full Undo row.
|
|
517
|
+
: undoUnavailable ? tx('Undo incomplete', '撤销未完成')
|
|
518
|
+
: failed || retentionOmitted || !wrote ? '' : tx('Nothing to undo', '无可撤销写入');
|
|
331
519
|
if (undo) facts.push({ key: 'undo', text: undo });
|
|
332
520
|
if (retentionOmitted) facts.push({ key: 'recoveryRetention', tone: 'warn',
|
|
333
521
|
text: run.recoveryRetention.coreRetained === true
|
|
334
522
|
? tx('Folder observations not retained after reload', '刷新后文件夹观测记录未保留')
|
|
335
523
|
: tx('Recovery data not retained after reload', '刷新后恢复材料未保留') });
|
|
336
524
|
if (savePending) facts.push({ key: 'save', tone: 'warn', text: tx('Save pending', '保存待确认') });
|
|
337
|
-
const attention = failed || skipped > 0 || retentionOmitted;
|
|
525
|
+
const attention = failed || skipped > 0 || retentionOmitted || undoUnavailable;
|
|
338
526
|
const warnKeys = new Set([...(failed || skipped > 0 ? ['writeResult', 'nextStep'] : []),
|
|
339
|
-
...(retentionOmitted ? ['recoveryRetention'] : []), ...(savePending ? ['saveState'] : [])]);
|
|
527
|
+
...(retentionOmitted || undoUnavailable ? ['recoveryRetention'] : []), ...(savePending ? ['saveState'] : [])]);
|
|
340
528
|
return { facts, open: attention, warnKeys };
|
|
341
529
|
}
|
|
342
530
|
|
|
343
|
-
return { splitFlatCompletionReport, projectCompletionBody, projectCompletionMeta, summarizeCompletionMeta };
|
|
531
|
+
return { splitFlatCompletionReport, projectNotStartedRun, projectFinishedRun, projectCompletionConclusion, projectCompletionBody, projectCompletionMeta, summarizeCompletionMeta };
|
|
344
532
|
});
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
function buildCodexRunParams({
|
|
24
24
|
currentProjectId,
|
|
25
25
|
state = {},
|
|
26
|
+
locale = state.locale,
|
|
26
27
|
task,
|
|
27
28
|
project,
|
|
28
29
|
useExistingMirror,
|
|
@@ -62,7 +63,7 @@
|
|
|
62
63
|
model: state.model,
|
|
63
64
|
reasoningEffort: state.reasoningEffort,
|
|
64
65
|
speedTier: state.speedTier,
|
|
65
|
-
locale:
|
|
66
|
+
locale: locale === 'zh' ? 'zh' : 'en',
|
|
66
67
|
session: state.session,
|
|
67
68
|
threadId: codexThreadId || undefined,
|
|
68
69
|
customInstructions: normalizedCustomInstructions || undefined,
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
document: documentRef = globalThis.document } = {}) {
|
|
27
27
|
const notices = new WeakMap();
|
|
28
28
|
|
|
29
|
-
function append(report, event = {}, run = {}) {
|
|
29
|
+
function append(report, event = {}, run = {}, outcomeTarget = null) {
|
|
30
30
|
const projection = typeof projectRunSettlement === 'function' && run
|
|
31
31
|
? projectRunSettlement(run) : null;
|
|
32
32
|
const failure = projection?.primaryFailure || event.failure;
|
|
@@ -38,6 +38,13 @@
|
|
|
38
38
|
const partial = facts.evidence?.applied === 'partial' || run?.partialWriteback === true;
|
|
39
39
|
if (!failure && !unconfirmed && !partial && report.dataset.status !== 'failed') return;
|
|
40
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
|
+
|
|
41
48
|
const notice = documentRef.createElement('aside');
|
|
42
49
|
notice.className = 'run-feedback';
|
|
43
50
|
notice.dataset.tone = unconfirmed || partial || failure?.terminalState === 'degraded'
|
|
@@ -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
|
}
|