codex-overleaf-link 2.3.1 → 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 (33) hide show
  1. package/README.md +23 -23
  2. package/extension/bootstrap/manifest.template.json +1 -1
  3. package/extension/src/content/assetTransferBroker.js +169 -0
  4. package/extension/src/content/contentRuntime.js +27 -1
  5. package/extension/src/content/generated/content.bundle.js +551 -65
  6. package/extension/src/content/generated/content.bundle.meta.json +6 -3
  7. package/extension/src/content/legacyGlobalRegistry.js +3 -0
  8. package/extension/src/content/modelPicker.js +2 -2
  9. package/extension/src/content/modelPickerSupport.js +3 -3
  10. package/extension/src/content/pageBridgeClient.js +1 -0
  11. package/extension/src/content/runResultActions.js +114 -0
  12. package/extension/src/content/runTimelineView.js +17 -0
  13. package/extension/src/content/sessionForkController.js +120 -0
  14. package/extension/src/content/writebackController.js +3 -0
  15. package/extension/src/content/writebackOrchestrator.js +3 -1
  16. package/extension/src/page/binaryAssetUploader.js +343 -0
  17. package/extension/src/page/trackedChangesLifecycle.js +2 -0
  18. package/extension/src/pageBridge.js +30 -1
  19. package/extension/src/shared/compatibility.js +4 -2
  20. package/extension/src/shared/i18n.js +16 -0
  21. package/extension/src/shared/models.js +13 -2
  22. package/extension/src/shared/pageRpcContract.js +5 -1
  23. package/extension/src/shared/sessionState.js +12 -4
  24. package/extension/src/shared/storageDb.js +1 -1
  25. package/extension/src/shared/storageRunActions.js +11 -1
  26. package/extension/styles/panel.css +61 -0
  27. package/native-host/src/codexModels.js +6 -2
  28. package/native-host/src/codexSessionRunner.js +5 -1
  29. package/native-host/src/codexThreadFork.js +103 -0
  30. package/native-host/src/mirrorWorkspace.js +5 -19
  31. package/native-host/src/nativeAssetTransfer.js +183 -0
  32. package/native-host/src/taskRunnerRuntime.js +22 -0
  33. 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": "ddbc07ac8d34d864aa3648c53211c5858e0312a73df815a0392bc52ec5eac1cc",
6
- "outputDigest": "42051c8624bdfca3094c8bef2430094e55b7e1ec49d475ac27f6df14629bf313",
7
- "byteLength": 2285557,
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",
@@ -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',
@@ -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;
@@ -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,
@@ -346,6 +347,11 @@
346
347
  </summary>
347
348
  </details>
348
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>
349
355
  <div class="run-report" data-run-report aria-live="polite" hidden></div>
350
356
  </div>
351
357
  `;
@@ -399,6 +405,7 @@
399
405
  }
400
406
  }
401
407
 
408
+ RunResultActions.configureResultActions(root, run);
402
409
  configureAcceptButton(root, run);
403
410
  configureUndoButton(root, run);
404
411
  return root;
@@ -886,6 +893,11 @@
886
893
  const button = existing.cloneNode(true);
887
894
  existing.replaceWith(button);
888
895
 
896
+ if (run.forkSnapshot === true) {
897
+ button.hidden = true;
898
+ return;
899
+ }
900
+
889
901
  if (isTrackedChangeLifecycleRun(run)) {
890
902
  configureLifecycleUndoButton(button, run);
891
903
  return;
@@ -982,6 +994,11 @@
982
994
  const button = existing.cloneNode(true);
983
995
  existing.replaceWith(button);
984
996
 
997
+ if (run.forkSnapshot === true) {
998
+ button.hidden = true;
999
+ return;
1000
+ }
1001
+
985
1002
  if (!isTrackedChangeLifecycleRun(run)) {
986
1003
  button.hidden = true;
987
1004
  return;
@@ -0,0 +1,120 @@
1
+ (function initCodexOverleafSessionForkController(root, factory) {
2
+ const api = factory();
3
+ if (typeof module === 'object' && module.exports) module.exports = api;
4
+ if (root) root.CodexOverleafSessionForkController = api;
5
+ })(typeof window !== 'undefined' ? window : globalThis, function sessionForkControllerFactory() {
6
+ 'use strict';
7
+
8
+ function create(deps = {}) {
9
+ const {
10
+ SessionState, getState, setState, sendBackgroundNative, saveState,
11
+ applyStateToPanel, showPluginToast, tr
12
+ } = deps;
13
+
14
+ async function forkRunFromNode(runId) {
15
+ const previousState = getState();
16
+ const source = findSource(previousState, runId);
17
+ if (!source) return failFork('session_fork_source_missing', tr('forkRunUnavailable'));
18
+ const { session, run, runIndex } = source;
19
+ if (run.status === 'running' || !session.codexThreadId || !run.codexTurnId) {
20
+ return failFork('session_fork_unavailable', tr('forkRunUnavailable'));
21
+ }
22
+ const response = await sendBackgroundNative({
23
+ method: 'codex.thread.fork',
24
+ params: {
25
+ threadId: session.codexThreadId,
26
+ lastTurnId: run.codexTurnId,
27
+ speedTier: session.speedTier,
28
+ loadCodexLocalSkills: previousState.loadCodexLocalSkills !== false,
29
+ loadCodexOverleafSkills: previousState.loadCodexOverleafSkills !== false
30
+ }
31
+ });
32
+ if (!response?.ok || !response.result?.threadId) {
33
+ return failFork(response?.error?.code || 'session_fork_failed', response?.error?.message || tr('forkRunFailed'));
34
+ }
35
+ const copiedRuns = session.runs.slice(0, runIndex + 1).map(copyRunSnapshot);
36
+ const titleBase = String(session.title || copiedRuns[0]?.task || tr('newSessionFallback')).trim();
37
+ const forkTitle = buildUniqueForkTitle(previousState.sessions, titleBase);
38
+ const forked = SessionState.createSession({
39
+ title: forkTitle,
40
+ titleSource: 'manual',
41
+ runs: copiedRuns,
42
+ task: '',
43
+ mode: session.mode,
44
+ providerId: session.providerId,
45
+ model: session.model,
46
+ reasoningEffort: session.reasoningEffort,
47
+ speedTier: session.speedTier,
48
+ requireReviewing: session.requireReviewing,
49
+ focusFiles: session.focusFiles,
50
+ projectReferenceFiles: session.projectReferenceFiles,
51
+ codexThreadId: response.result.threadId,
52
+ pendingInputs: [],
53
+ forkedFromSessionId: session.id,
54
+ forkedFromRunId: run.id
55
+ });
56
+ setState(SessionState.normalizePanelState({
57
+ ...previousState,
58
+ sessions: [...(previousState.sessions || []), forked],
59
+ activeSessionId: forked.id
60
+ }));
61
+ try {
62
+ await saveState();
63
+ } catch (error) {
64
+ setState(previousState);
65
+ await sendBackgroundNative({
66
+ method: 'codex.history.clearPlugin',
67
+ params: { sessionId: forked.id, threadId: response.result.threadId }
68
+ }).catch(() => null);
69
+ showPluginToast(error.message || tr('forkRunFailed'), { status: 'failed', sticky: true });
70
+ throw error;
71
+ }
72
+ applyStateToPanel();
73
+ showPluginToast(tr('forkRunDone'), { status: 'completed' });
74
+ return forked;
75
+ }
76
+
77
+ function failFork(code, message) {
78
+ showPluginToast(message, { status: 'failed', sticky: true });
79
+ throw codedError(code, message);
80
+ }
81
+
82
+ return { forkRunFromNode };
83
+ }
84
+
85
+ function buildUniqueForkTitle(sessions = [], sourceTitle = '') {
86
+ const fallback = String(sourceTitle || 'New Session').trim() || 'New Session';
87
+ const baseTitle = fallback
88
+ .replace(/\s+\(fork\)$/i, '')
89
+ .replace(/\s+\(\d+\)$/, '')
90
+ .trim() || fallback;
91
+ const usedTitles = new Set((sessions || []).map(session => String(session?.title || '').trim()));
92
+ let index = 1;
93
+ while (usedTitles.has(`${baseTitle} (${index})`)) index += 1;
94
+ return `${baseTitle} (${index})`;
95
+ }
96
+
97
+ function findSource(state, runId) {
98
+ for (const session of state?.sessions || []) {
99
+ const runIndex = (session.runs || []).findIndex(run => run.id === runId);
100
+ if (runIndex >= 0) return { session, run: session.runs[runIndex], runIndex };
101
+ }
102
+ return null;
103
+ }
104
+
105
+ function copyRunSnapshot(run) {
106
+ const copy = JSON.parse(JSON.stringify(run || {}));
107
+ return {
108
+ ...copy,
109
+ id: `fork_${String(copy.id || 'run')}_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 7)}`,
110
+ forkSnapshot: true,
111
+ forkSourceRunId: String(copy.id || ''),
112
+ attachments: [], appliedOperations: [], undoOperations: [], undoBaseFiles: [],
113
+ undoTrackedChanges: [], undoExpectedFiles: [], undoStatus: '',
114
+ trackedChangeStatus: '', nativeRequestId: '', queueItemId: '', interruptedDraft: undefined
115
+ };
116
+ }
117
+
118
+ function codedError(code, message) { const error = new Error(message); error.code = code; return error; }
119
+ return { copyRunSnapshot, create };
120
+ });
@@ -15,6 +15,9 @@
15
15
  type: change.type,
16
16
  path: change.path,
17
17
  contentBase64: change.contentBase64 || '',
18
+ assetRef: change.assetRef && typeof change.assetRef === 'object' ? { ...change.assetRef } : undefined,
19
+ sha256: change.sha256 || change.assetRef?.sha256 || '',
20
+ mimeType: change.mimeType || change.assetRef?.mimeType || '',
18
21
  size: Number.isFinite(Number(change.size)) ? Number(change.size) : undefined,
19
22
  previousExists: change.previousExists === true,
20
23
  previousKind: change.previousKind || '',
@@ -61,6 +61,7 @@
61
61
  let onMirrorRefreshSettled;
62
62
  let writebackSettlement;
63
63
  let injectedWritebackSettlement;
64
+ let assetTransferBroker;
64
65
 
65
66
  function buildSettlement(input = {}) {
66
67
  return writebackSettlement?.settle instanceof Function
@@ -283,7 +284,7 @@
283
284
  };
284
285
  }
285
286
  const applied = operations.length
286
- ? mergeApplyResultSkipped(await callPageBridge('applyOperations', {
287
+ ? mergeApplyResultSkipped(await assetTransferBroker.applyOperations({
287
288
  operations,
288
289
  baseFiles: project?.files || [],
289
290
  requireReviewing: runRequireReviewing,
@@ -859,6 +860,7 @@
859
860
  getState,
860
861
  getCurrentRunView,
861
862
  } = deps);
863
+ assetTransferBroker = deps.assetTransferBroker;
862
864
  onMirrorRefreshSettled = deps.onMirrorRefreshSettled;
863
865
  writebackSettlement = deps.writebackSettlement;
864
866
  compileAdapter = deps.compileAdapter;