codex-overleaf-link 2.3.0 → 2.3.2

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.
Files changed (50) hide show
  1. package/README.md +23 -23
  2. package/extension/bootstrap/manifest.template.json +1 -1
  3. package/extension/src/background.js +20 -0
  4. package/extension/src/content/assetTransferBroker.js +169 -0
  5. package/extension/src/content/composerPanel.js +0 -2
  6. package/extension/src/content/contentRuntime.js +135 -111
  7. package/extension/src/content/generated/content.bundle.js +1066 -345
  8. package/extension/src/content/generated/content.bundle.meta.json +7 -3
  9. package/extension/src/content/legacyGlobalRegistry.js +3 -0
  10. package/extension/src/content/markdownText.js +12 -10
  11. package/extension/src/content/mathText.js +155 -9
  12. package/extension/src/content/modelPicker.js +2 -2
  13. package/extension/src/content/modelPickerSupport.js +3 -3
  14. package/extension/src/content/nativeCompatibilityController.js +2 -2
  15. package/extension/src/content/otWarmMirror.js +1 -1
  16. package/extension/src/content/pageBridgeClient.js +1 -0
  17. package/extension/src/content/runResultActions.js +114 -0
  18. package/extension/src/content/runTimelineView.js +23 -0
  19. package/extension/src/content/sessionForkController.js +120 -0
  20. package/extension/src/content/writebackController.js +3 -0
  21. package/extension/src/content/writebackOrchestrator.js +5 -50
  22. package/extension/src/page/binaryAssetUploader.js +343 -0
  23. package/extension/src/page/trackedChangesLifecycle.js +12 -4
  24. package/extension/src/page/writebackRouter.js +6 -2
  25. package/extension/src/pageBridge.js +30 -1
  26. package/extension/src/shared/agentTranscript.js +10 -3
  27. package/extension/src/shared/compatibility.js +4 -2
  28. package/extension/src/shared/i18n.js +18 -4
  29. package/extension/src/shared/models.js +13 -2
  30. package/extension/src/shared/pageRpcContract.js +6 -2
  31. package/extension/src/shared/runExecutionSnapshot.js +14 -4
  32. package/extension/src/shared/runExecutionSnapshotCodec.js +5 -2
  33. package/extension/src/shared/scopedPersistenceQueuePolicy.js +20 -1
  34. package/extension/src/shared/scopedPersistenceReceipt.js +60 -0
  35. package/extension/src/shared/scopedPersistenceTransaction.js +50 -47
  36. package/extension/src/shared/sessionState.js +58 -15
  37. package/extension/src/shared/storageDb.js +6 -5
  38. package/extension/src/shared/storageRunActions.js +11 -1
  39. package/extension/src/shared/writebackOperationEvidence.js +16 -6
  40. package/extension/src/shared/writebackSettlement.js +28 -6
  41. package/extension/styles/panel.css +79 -8
  42. package/native-host/src/codexModels.js +6 -2
  43. package/native-host/src/codexPromptAssembly.js +1 -1
  44. package/native-host/src/codexSessionRunner.js +5 -1
  45. package/native-host/src/codexThreadFork.js +103 -0
  46. package/native-host/src/mirrorWorkspace.js +5 -19
  47. package/native-host/src/nativeAssetTransfer.js +183 -0
  48. package/native-host/src/taskRunnerRuntime.js +33 -81
  49. package/native-host/src/updateManager.js +36 -1
  50. package/package.json +1 -1
@@ -2,13 +2,14 @@
2
2
  "schemaVersion": 1,
3
3
  "entry": "extension/entries/content-entry.mjs",
4
4
  "output": "extension/src/content/generated/content.bundle.js",
5
- "sourceDigest": "83c5f2bc9eb28735ba091c432476e505b311cfba37d53aa205049ea70ef2d2fc",
6
- "outputDigest": "582a7804b46491dd34cd9f6f24304597f4e946e79870e323b9868a825b4666dd",
7
- "byteLength": 2277820,
5
+ "sourceDigest": "d2167fca49721d0ef5e477dc76b11a0cedb33c9f51b920b55ea666ab4668dc4c",
6
+ "outputDigest": "87cbcf7d9877860b7c1d047cd3fa6e985e28812c8df81c9105ec9312a5e45ea6",
7
+ "byteLength": 2309285,
8
8
  "inputs": [
9
9
  "extension/entries/content-entry.mjs",
10
10
  "extension/src/content/activeTurnControl.js",
11
11
  "extension/src/content/applyResultFormatters.js",
12
+ "extension/src/content/assetTransferBroker.js",
12
13
  "extension/src/content/composerAttachments.js",
13
14
  "extension/src/content/composerPanel.js",
14
15
  "extension/src/content/contentRuntime.js",
@@ -45,11 +46,13 @@
45
46
  "extension/src/content/runGuidanceController.js",
46
47
  "extension/src/content/runGuidanceView.js",
47
48
  "extension/src/content/runQueueScheduler.js",
49
+ "extension/src/content/runResultActions.js",
48
50
  "extension/src/content/runSettlementPersistence.js",
49
51
  "extension/src/content/runTimelineView.js",
50
52
  "extension/src/content/scopedPersistenceCoordinator.js",
51
53
  "extension/src/content/scopedPersistenceDetachedCommit.js",
52
54
  "extension/src/content/scopedPersistencePanelState.js",
55
+ "extension/src/content/sessionForkController.js",
53
56
  "extension/src/content/sessionManager.js",
54
57
  "extension/src/content/sessionMenuView.js",
55
58
  "extension/src/content/sessionPanel.js",
@@ -81,6 +84,7 @@
81
84
  "extension/src/shared/runInputQueue.js",
82
85
  "extension/src/shared/scopedPersistenceBrowserAdapter.js",
83
86
  "extension/src/shared/scopedPersistenceQueuePolicy.js",
87
+ "extension/src/shared/scopedPersistenceReceipt.js",
84
88
  "extension/src/shared/scopedPersistenceTransaction.js",
85
89
  "extension/src/shared/sensitiveScan.js",
86
90
  "extension/src/shared/sessionState.js",
@@ -15,6 +15,7 @@
15
15
  'ActiveTurnControl',
16
16
  'AgentTranscript',
17
17
  'ApplyResultFormatters',
18
+ 'AssetTransferBroker',
18
19
  'AuditRecords',
19
20
  'Compatibility',
20
21
  'ComposerAttachments',
@@ -53,12 +54,14 @@
53
54
  'RunGuidanceController',
54
55
  'RunInputQueue',
55
56
  'RunQueueScheduler',
57
+ 'RunResultActions',
56
58
  'RunSettlementPersistence',
57
59
  'RunTimelineView',
58
60
  'ScopedPersistenceCoordinator',
59
61
  'ScopedPersistenceTransaction',
60
62
  'SensitiveScan',
61
63
  'SessionManager',
64
+ 'SessionForkController',
62
65
  'SessionPanel',
63
66
  'SessionPersistence',
64
67
  'SessionState',
@@ -13,12 +13,14 @@
13
13
  tx,
14
14
  callPageBridge, getCurrentProjectId,
15
15
  getCurrentProjectReferenceFiles,
16
+ getCurrentProjectMathSources,
16
17
  showPluginToast
17
18
  } = deps;
18
19
 
19
20
  function renderMarkdownInlineText(target, value) {
20
21
  target.replaceChildren(...buildMarkdownInlineNodes(value));
21
22
  }
23
+ function getMathMacros() { return MathText?.buildMathMacros?.(getCurrentProjectMathSources?.() || []) || {}; }
22
24
 
23
25
  function sanitizeAssistantVisibleText(value) {
24
26
  if (typeof value !== 'string' || !value) {
@@ -67,11 +69,8 @@
67
69
  if (!mathText?.buildMathNodes) {
68
70
  return buildMarkdownInlineNodesWithoutMath(source);
69
71
  }
70
- return mathText.buildMathNodes(source, {
71
- document,
72
- katex: window.katex,
73
- renderText: buildMarkdownInlineNodesWithoutMath
74
- });
72
+ return mathText.buildMathNodes(source, { document, katex: window.katex,
73
+ macros: getMathMacros(), renderText: buildMarkdownInlineNodesWithoutMath });
75
74
  }
76
75
 
77
76
  function buildMarkdownInlineNodesWithoutMath(value) {
@@ -109,6 +108,10 @@
109
108
  function buildInlineCodeNodes(value) {
110
109
  const source = String(value || '');
111
110
  const trimmed = source.trim();
111
+ const standaloneMath = MathText?.parseStandaloneMath?.(trimmed);
112
+ if (standaloneMath && MathText?.createMathNode) return [MathText.createMathNode(
113
+ standaloneMath, { document, katex: window.katex, macros: getMathMacros() }
114
+ )];
112
115
  const refs = trimmed ? parseRuntimeLineReferences(trimmed, 'plain-text-token') : [];
113
116
  const ref = refs.length === 1 && refs[0]?.rawText === trimmed ? refs[0] : null;
114
117
  if (ref && !isRuntimeLocalPathLike(ref.rawPath)) {
@@ -492,12 +495,11 @@
492
495
  `[${safeLabel.replace(/]/g, '\\]')}](${target.replace(/\)/g, '\\)')})`
493
496
  );
494
497
  },
495
- renderCodeNodes: text => buildMarkdownInlineNodes(`\`${String(text || '').replace(/`/g, '\\`')}\``),
498
+ renderCodeNodes: text => buildInlineCodeNodes(text),
496
499
  sanitizeText: sanitizeAssistantVisibleText,
497
- createMathNode: segment => runtimeRoot.CodexOverleafMathText.createMathNode(segment, {
498
- document, katex: runtimeRoot.katex,
499
- renderText: text => buildMarkdownInlineNodes(text)
500
- }),
500
+ createMathNode: segment => runtimeRoot.CodexOverleafMathText.createMathNode(segment,
501
+ { document, katex: runtimeRoot.katex, macros: getMathMacros(),
502
+ renderText: text => buildMarkdownInlineNodes(text) }),
501
503
  recordDiagnostic: recordRenderingDiagnostic
502
504
  });
503
505
  return;
@@ -9,6 +9,19 @@
9
9
  ]);
10
10
  const LONG_INLINE_MATH_CHARS = 120;
11
11
  const COMPLEX_INLINE_MATH_CHARS = 80;
12
+ const MAX_PROJECT_MACRO_SOURCES = 12;
13
+ const MAX_PROJECT_MACRO_SOURCE_CHARS = 256 * 1024;
14
+ const MAX_PROJECT_MACROS = 64;
15
+ const MAX_MACRO_EXPANSION_CHARS = 512;
16
+ const DEFAULT_MATH_MACROS = Object.freeze({
17
+ '\\E': '\\mathbb{E}',
18
+ '\\Var': '\\operatorname{Var}',
19
+ '\\Cov': '\\operatorname{Cov}',
20
+ '\\R': '\\mathbb{R}',
21
+ '\\N': '\\mathbb{N}',
22
+ '\\Z': '\\mathbb{Z}'
23
+ });
24
+ const UNSAFE_MACRO_PATTERN = /\\(?:href|url|includegraphics|input|include|write|openout|read|html\w*|class|style|data|def|gdef|edef|xdef|newcommand|renewcommand|providecommand|DeclareMathOperator)\b/i;
12
25
 
13
26
  function parseMathSegments(value) {
14
27
  const source = String(value || '');
@@ -74,9 +87,10 @@
74
87
  searchFrom = start + delimiter.open.length;
75
88
  continue;
76
89
  }
77
- const value = source.slice(start + delimiter.open.length, closeStart);
90
+ const rawValue = source.slice(start + delimiter.open.length, closeStart);
91
+ const value = rawValue.trim();
78
92
  const end = closeStart + delimiter.close.length;
79
- if (!isValidMathValue(value, delimiter) || overlapsCodeRange(start, end, codeRanges)) {
93
+ if (!isValidMathValue(rawValue, delimiter) || overlapsCodeRange(start, end, codeRanges)) {
80
94
  searchFrom = start + delimiter.open.length;
81
95
  continue;
82
96
  }
@@ -94,7 +108,7 @@
94
108
  }
95
109
  const previous = source[index - 1] || '';
96
110
  const next = source[index + 1] || '';
97
- return previous !== '$' && next !== '$' && next !== '' && !/\s/.test(next);
111
+ return previous !== '$' && next !== '$' && next !== '';
98
112
  }
99
113
 
100
114
  function findClosingDelimiter(source, fromIndex, delimiter, codeRanges) {
@@ -107,7 +121,7 @@
107
121
  const previous = source[index - 1] || '';
108
122
  const next = source[index + delimiter.close.length] || '';
109
123
  const invalidSingleDollar = delimiter.singleDollar
110
- && (previous === '$' || next === '$' || /\s/.test(previous));
124
+ && (previous === '$' || next === '$');
111
125
  if (!isEscaped(source, index) && !isInsideRange(index, codeRanges) && !invalidSingleDollar) {
112
126
  return index;
113
127
  }
@@ -118,10 +132,24 @@
118
132
 
119
133
  function isValidMathValue(value, delimiter) {
120
134
  const source = String(value || '');
121
- if (!source.trim()) {
135
+ const trimmed = source.trim();
136
+ if (!trimmed) {
122
137
  return false;
123
138
  }
124
- return delimiter.display || !/[\r\n]/.test(source);
139
+ if (!delimiter.display && /[\r\n]/.test(source)) {
140
+ return false;
141
+ }
142
+ if (!delimiter.singleDollar || (!/^\s/.test(source) && !/\s$/.test(source))) {
143
+ return true;
144
+ }
145
+ return looksLikeWhitespaceWrappedMath(trimmed);
146
+ }
147
+
148
+ function looksLikeWhitespaceWrappedMath(value) {
149
+ const source = String(value || '').trim();
150
+ return /\\[A-Za-z]+/.test(source)
151
+ || /[_^=<>+\-*/()[\]{}|]/.test(source)
152
+ || /^[A-Za-z](?:_[A-Za-z0-9{}]+|\^[A-Za-z0-9{}]+)?$/.test(source);
125
153
  }
126
154
 
127
155
  function collectInlineCodeRanges(source) {
@@ -179,7 +207,8 @@
179
207
  }
180
208
  nodes.push(createMathNode(segment, {
181
209
  document: documentRef,
182
- katex: options.katex || root.katex
210
+ katex: options.katex || root.katex,
211
+ macros: options.macros || buildMathMacros(options.projectSources)
183
212
  }));
184
213
  }
185
214
  return nodes;
@@ -206,6 +235,7 @@
206
235
  strict: 'warn',
207
236
  maxExpand: 200,
208
237
  maxSize: 20,
238
+ macros: options.macros || buildMathMacros(),
209
239
  output: 'htmlAndMathml'
210
240
  });
211
241
  node.dataset.mathRendered = 'true';
@@ -213,7 +243,7 @@
213
243
  } catch (_error) {
214
244
  node.classList.add('run-math--fallback');
215
245
  node.dataset.mathRendered = 'false';
216
- node.textContent = segment.raw;
246
+ node.textContent = source;
217
247
  return node;
218
248
  }
219
249
  }
@@ -235,6 +265,120 @@
235
265
  return /(?:=|\\(?:le|ge|approx|sim|to|Rightarrow|Longrightarrow)\b|\\begin\{(?:aligned|gathered|split|array)\}|\\\\)/.test(source);
236
266
  }
237
267
 
268
+ function parseStandaloneMath(value) {
269
+ const source = String(value || '').trim();
270
+ if (!source) {
271
+ return null;
272
+ }
273
+ const segments = parseMathSegments(source);
274
+ if (segments.length !== 1 || segments[0].type !== 'math' || segments[0].raw !== source) {
275
+ return null;
276
+ }
277
+ return segments[0];
278
+ }
279
+
280
+ function buildMathMacros(projectSources = []) {
281
+ const macros = { ...DEFAULT_MATH_MACROS };
282
+ let remainingChars = MAX_PROJECT_MACRO_SOURCE_CHARS;
283
+ let projectMacroCount = 0;
284
+ for (const sourceRecord of Array.isArray(projectSources)
285
+ ? projectSources.slice(0, MAX_PROJECT_MACRO_SOURCES)
286
+ : []) {
287
+ if (projectMacroCount >= MAX_PROJECT_MACROS || remainingChars <= 0) {
288
+ break;
289
+ }
290
+ const path = String(sourceRecord?.path || '');
291
+ if (!/\.(?:tex|sty|cls|ltx)$/i.test(path)) {
292
+ continue;
293
+ }
294
+ const source = stripLatexComments(
295
+ String(sourceRecord?.content || '').slice(0, remainingChars)
296
+ );
297
+ remainingChars -= source.length;
298
+ const pattern = /\\(newcommand|renewcommand|providecommand|DeclareMathOperator)(\*)?/g;
299
+ let match;
300
+ while ((match = pattern.exec(source)) !== null && projectMacroCount < MAX_PROJECT_MACROS) {
301
+ let cursor = skipWhitespace(source, pattern.lastIndex);
302
+ const nameGroup = readBracedGroup(source, cursor);
303
+ if (!nameGroup) {
304
+ continue;
305
+ }
306
+ const macroName = nameGroup.value.trim();
307
+ cursor = skipWhitespace(source, nameGroup.end);
308
+ if (source[cursor] === '[') {
309
+ continue;
310
+ }
311
+ const expansionGroup = readBracedGroup(source, cursor);
312
+ if (!expansionGroup) {
313
+ continue;
314
+ }
315
+ let expansion = expansionGroup.value.trim();
316
+ if (match[1] === 'DeclareMathOperator') {
317
+ expansion = `\\operatorname${match[2] ? '*' : ''}{${expansion}}`;
318
+ }
319
+ if (!isSafeMacroDefinition(macroName, expansion)) {
320
+ continue;
321
+ }
322
+ macros[macroName] = expansion;
323
+ projectMacroCount++;
324
+ pattern.lastIndex = expansionGroup.end;
325
+ }
326
+ }
327
+ return macros;
328
+ }
329
+
330
+ function readBracedGroup(source, start) {
331
+ if (source[start] !== '{') {
332
+ return null;
333
+ }
334
+ let depth = 0;
335
+ for (let index = start; index < source.length; index++) {
336
+ if (source[index] === '{' && !isEscaped(source, index)) {
337
+ depth++;
338
+ } else if (source[index] === '}' && !isEscaped(source, index)) {
339
+ depth--;
340
+ if (depth === 0) {
341
+ return {
342
+ value: source.slice(start + 1, index),
343
+ end: index + 1
344
+ };
345
+ }
346
+ }
347
+ }
348
+ return null;
349
+ }
350
+
351
+ function skipWhitespace(source, start) {
352
+ let index = start;
353
+ while (index < source.length && /\s/.test(source[index])) {
354
+ index++;
355
+ }
356
+ return index;
357
+ }
358
+
359
+ function stripLatexComments(source) {
360
+ return String(source || '').split(/\r?\n/).map(line => {
361
+ for (let index = 0; index < line.length; index++) {
362
+ if (line[index] === '%' && !isEscaped(line, index)) {
363
+ return line.slice(0, index);
364
+ }
365
+ }
366
+ return line;
367
+ }).join('\n');
368
+ }
369
+
370
+ function isSafeMacroDefinition(name, expansion) {
371
+ const macroName = String(name || '');
372
+ const body = String(expansion || '');
373
+ return /^\\[A-Za-z@]+$/.test(macroName)
374
+ && body.length > 0
375
+ && body.length <= MAX_MACRO_EXPANSION_CHARS
376
+ && !body.includes('#')
377
+ && !body.includes(macroName)
378
+ && !UNSAFE_MACRO_PATTERN.test(macroName)
379
+ && !UNSAFE_MACRO_PATTERN.test(body);
380
+ }
381
+
238
382
  function toNodeArray(value) {
239
383
  if (Array.isArray(value)) {
240
384
  return value;
@@ -265,11 +409,13 @@
265
409
  }
266
410
 
267
411
  const api = {
412
+ buildMathMacros,
268
413
  buildMathNodes,
269
414
  createMathNode,
270
415
  matchMathAt,
271
416
  normalizeMathForRendering,
272
- parseMathSegments
417
+ parseMathSegments,
418
+ parseStandaloneMath
273
419
  };
274
420
  root.CodexOverleafMathText = api;
275
421
  if (typeof module !== 'undefined' && module.exports) {
@@ -452,8 +452,8 @@
452
452
  if (effort === 'xhigh') return 'Max';
453
453
  }
454
454
  const labels = getLocale() === 'zh'
455
- ? { none: '关闭', minimal: '最小', low: '低', medium: '中', high: '高', xhigh: '超高' }
456
- : { none: 'Off', minimal: 'Minimal', low: 'Low', medium: 'Medium', high: 'High', xhigh: 'XHigh' };
455
+ ? { none: '关闭', minimal: '最小', low: '低', medium: '中', high: '高', xhigh: '超高', max: '最大', ultra: '极限' }
456
+ : { none: 'Off', minimal: 'Minimal', low: 'Low', medium: 'Medium', high: 'High', xhigh: 'XHigh', max: 'Max', ultra: 'Ultra' };
457
457
  return labels[effort] || effort || '';
458
458
  }
459
459
 
@@ -19,7 +19,7 @@
19
19
  }
20
20
 
21
21
  function normalizeReasoningEffortsForSelect(efforts) {
22
- const allowed = new Set(['none', 'minimal', 'low', 'medium', 'high', 'xhigh']);
22
+ const allowed = new Set(['none', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max', 'ultra']);
23
23
  return Array.from(new Set((Array.isArray(efforts) ? efforts : [])
24
24
  .map(effort => normalizeModelOptionId(effort))
25
25
  .filter(effort => allowed.has(effort))));
@@ -92,12 +92,12 @@
92
92
  const normalized = {
93
93
  id,
94
94
  label: typeof model?.label === 'string' && model.label.length > 0 ? model.label : id,
95
- reasoningEfforts: Array.isArray(model?.reasoningEfforts) ? model.reasoningEfforts.slice() : [],
95
+ reasoningEfforts: normalizeReasoningEffortsForSelect(model?.reasoningEfforts),
96
96
  reasoningPresentation: typeof model?.reasoningPresentation === 'string' ? model.reasoningPresentation : '',
97
97
  speedTiers: normalizeSpeedTiersForSelect(model?.speedTiers)
98
98
  };
99
99
  if (Object.prototype.hasOwnProperty.call(Object(model), 'defaultReasoningEffort')) {
100
- normalized.defaultReasoningEffort = model.defaultReasoningEffort;
100
+ normalized.defaultReasoningEffort = normalizeModelOptionId(model.defaultReasoningEffort);
101
101
  }
102
102
  if (Object.prototype.hasOwnProperty.call(Object(model), 'defaultSpeedTier')) {
103
103
  normalized.defaultSpeedTier = model.defaultSpeedTier;
@@ -256,8 +256,8 @@
256
256
  tip.dataset.onboardingTip = 'true';
257
257
  tip.setAttribute('role', 'status');
258
258
  tip.textContent = tx(
259
- 'Tip: Start with Ask mode to explore safely. Switch to Suggest to review edits before applying.',
260
- '提示:先用 Ask 模式安全探索;需要修改时切换到 Suggest,应用前可先审阅。'
259
+ 'Tip: Start with Ask mode to explore safely. Switch to Auto only when you want Codex to edit files.',
260
+ '提示:先用 Ask 模式安全探索;只有需要 Codex 修改文件时才切换到 Auto。'
261
261
  );
262
262
  Object.assign(tip.style, {
263
263
  position: 'absolute',
@@ -868,7 +868,7 @@
868
868
  applyResults: [],
869
869
  nextStep: tr('warmMirrorStaleBlockedNextStep')
870
870
  });
871
- finishRunView(tr('warmMirrorStaleBlockedTitle'), 'failed');
871
+ await finishRunView(tr('warmMirrorStaleBlockedTitle'), 'failed');
872
872
  return { ok: false };
873
873
  }
874
874
  if (focusedPartialSnapshot) {
@@ -21,6 +21,7 @@
21
21
  ['src/page/overleafProjectSnapshot.js', 'codex-overleaf-project-snapshot-script'],
22
22
  ['src/page/pageBridgeCapability.js', 'codex-overleaf-page-bridge-capability-script'],
23
23
  ['src/page/treeOperations.js', 'codex-overleaf-tree-operations-script', true],
24
+ ['src/page/binaryAssetUploader.js', 'codex-overleaf-binary-asset-uploader-script', true],
24
25
  ['src/page/snapshotRouter.js', 'codex-overleaf-snapshot-router-script'],
25
26
  ['src/page/writeGuard.js', 'codex-overleaf-write-guard-script', true],
26
27
  ['src/page/trackedChangesLifecycle.js', 'codex-overleaf-tracked-changes-lifecycle-script', true],
@@ -0,0 +1,114 @@
1
+ (function initCodexOverleafRunResultActions(root, factory) {
2
+ const api = factory();
3
+ if (typeof module === 'object' && module.exports) module.exports = api;
4
+ if (root) root.CodexOverleafRunResultActions = api;
5
+ })(typeof window !== 'undefined' ? window : globalThis, function runResultActionsFactory() {
6
+ 'use strict';
7
+
8
+ function create(deps = {}) {
9
+ const {
10
+ tr,
11
+ getLocale,
12
+ forkRunFromNode,
13
+ document: documentRef = globalThis.document,
14
+ navigator: navigatorRef = globalThis.navigator
15
+ } = deps;
16
+
17
+ function configureResultActions(root, run) {
18
+ const actions = root.querySelector('.run-result-actions');
19
+ const reports = root.querySelectorAll('.run-completion-report');
20
+ const report = reports[reports.length - 1];
21
+ if (!actions || !report) return;
22
+ report.append(actions);
23
+ actions.hidden = false;
24
+ configureCopyButton(root);
25
+ configureForkButton(root, run);
26
+ configureCompletedTime(actions, run);
27
+ }
28
+
29
+ function configureForkButton(root, run) {
30
+ const existing = root.querySelector('[data-run-fork]');
31
+ const button = existing.cloneNode(true);
32
+ existing.replaceWith(button);
33
+ const available = run.forkSnapshot !== true
34
+ && run.status !== 'running'
35
+ && Boolean(run.codexTurnId)
36
+ && typeof forkRunFromNode === 'function';
37
+ button.hidden = false;
38
+ button.disabled = !available;
39
+ button.title = tr(available ? 'forkRunTitle' : 'forkRunUnavailable');
40
+ button.setAttribute('aria-label', button.title);
41
+ button.innerHTML = '<svg viewBox="0 0 20 20" aria-hidden="true"><path d="M3 10h3.5c3.5 0 4.2-4 7.5-4h2m-2.5-2.5L16 6l-2.5 2.5M6.5 10c3.5 0 4.2 4 7.5 4h2m-2.5-2.5L16 14l-2.5 2.5"/></svg>';
42
+ if (!available) return;
43
+ button.addEventListener('click', async event => {
44
+ event.stopPropagation();
45
+ button.disabled = true;
46
+ button.classList.add('is-running');
47
+ try {
48
+ await forkRunFromNode(run.id);
49
+ } catch (_error) {
50
+ button.disabled = false;
51
+ button.classList.remove('is-running');
52
+ }
53
+ });
54
+ }
55
+
56
+ function configureCompletedTime(actions, run) {
57
+ const target = actions.querySelector('[data-run-completed-time]');
58
+ const finishedAt = String(run.finishedAt || '').trim();
59
+ const date = finishedAt ? new Date(finishedAt) : null;
60
+ if (!target || !date || !Number.isFinite(date.getTime())) return;
61
+ target.hidden = false;
62
+ target.textContent = new Intl.DateTimeFormat(getLocale(), {
63
+ hour: '2-digit',
64
+ minute: '2-digit'
65
+ }).format(date);
66
+ target.title = date.toLocaleString(getLocale());
67
+ }
68
+
69
+ function configureCopyButton(root) {
70
+ const existing = root.querySelector('[data-run-copy]');
71
+ const button = existing.cloneNode(true);
72
+ existing.replaceWith(button);
73
+ const finalAnswer = root.querySelector('.run-completion-report .run-final-answer');
74
+ const copyText = String(finalAnswer?.innerText || finalAnswer?.textContent || '').trim();
75
+ button.hidden = false;
76
+ button.disabled = !copyText;
77
+ button.title = tr('copyConclusionTitle');
78
+ button.setAttribute('aria-label', button.title);
79
+ button.innerHTML = '<svg viewBox="0 0 20 20" aria-hidden="true"><rect x="6.5" y="6.5" width="9" height="9" rx="2"/><path d="M13.5 6.5V5a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v6.5a2 2 0 0 0 2 2h1.5"/></svg>';
80
+ if (!copyText) return;
81
+ button.addEventListener('click', async event => {
82
+ event.stopPropagation();
83
+ try {
84
+ await writeClipboardText(copyText);
85
+ button.title = tr('copyConclusionDone');
86
+ } catch (_error) {
87
+ button.title = tr('copyConclusionFailed');
88
+ }
89
+ button.setAttribute('aria-label', button.title);
90
+ });
91
+ }
92
+
93
+ async function writeClipboardText(text) {
94
+ if (navigatorRef?.clipboard?.writeText) {
95
+ await navigatorRef.clipboard.writeText(text);
96
+ return;
97
+ }
98
+ const textarea = documentRef.createElement('textarea');
99
+ textarea.value = text;
100
+ textarea.setAttribute('readonly', '');
101
+ textarea.style.position = 'fixed';
102
+ textarea.style.opacity = '0';
103
+ documentRef.body.append(textarea);
104
+ textarea.select();
105
+ const copied = documentRef.execCommand('copy');
106
+ textarea.remove();
107
+ if (!copied) throw new Error('clipboard_copy_failed');
108
+ }
109
+
110
+ return Object.freeze({ configureResultActions });
111
+ }
112
+
113
+ return Object.freeze({ create });
114
+ });
@@ -13,6 +13,7 @@
13
13
  function create(deps = {}) {
14
14
  const {
15
15
  RunGuidanceView,
16
+ RunResultActions,
16
17
  tr,
17
18
  tx,
18
19
  getLocale,
@@ -274,6 +275,9 @@
274
275
  if (status === 'failed') {
275
276
  return tr('processedFailed', { elapsed });
276
277
  }
278
+ if (status === 'rejected' || status === 'cancelled') {
279
+ return tr('processedCancelled', { elapsed });
280
+ }
277
281
  if (status === 'running') {
278
282
  return tr('processing', { elapsed });
279
283
  }
@@ -343,6 +347,11 @@
343
347
  </summary>
344
348
  </details>
345
349
  <div class="run-activity-list" data-run-events></div>
350
+ <div class="run-result-actions" hidden>
351
+ <button type="button" class="run-copy-action" data-run-copy hidden></button>
352
+ <button type="button" class="run-fork-action" data-run-fork hidden></button>
353
+ <span class="run-completed-time" data-run-completed-time hidden></span>
354
+ </div>
346
355
  <div class="run-report" data-run-report aria-live="polite" hidden></div>
347
356
  </div>
348
357
  `;
@@ -396,6 +405,7 @@
396
405
  }
397
406
  }
398
407
 
408
+ RunResultActions.configureResultActions(root, run);
399
409
  configureAcceptButton(root, run);
400
410
  configureUndoButton(root, run);
401
411
  return root;
@@ -411,6 +421,9 @@
411
421
  if (run.startedAt && run.finishedAt) {
412
422
  return formatProcessedSummary(run.status || 'completed', new Date(run.finishedAt).getTime() - new Date(run.startedAt).getTime());
413
423
  }
424
+ if (run.status === 'rejected' || run.status === 'cancelled') {
425
+ return tx('Cancelled', '已取消');
426
+ }
414
427
  return run.status === 'failed' ? tx('Failed', '处理失败') : tx('Done', '已处理');
415
428
  }
416
429
 
@@ -880,6 +893,11 @@
880
893
  const button = existing.cloneNode(true);
881
894
  existing.replaceWith(button);
882
895
 
896
+ if (run.forkSnapshot === true) {
897
+ button.hidden = true;
898
+ return;
899
+ }
900
+
883
901
  if (isTrackedChangeLifecycleRun(run)) {
884
902
  configureLifecycleUndoButton(button, run);
885
903
  return;
@@ -976,6 +994,11 @@
976
994
  const button = existing.cloneNode(true);
977
995
  existing.replaceWith(button);
978
996
 
997
+ if (run.forkSnapshot === true) {
998
+ button.hidden = true;
999
+ return;
1000
+ }
1001
+
979
1002
  if (!isTrackedChangeLifecycleRun(run)) {
980
1003
  button.hidden = true;
981
1004
  return;