codex-overleaf-link 2.5.1 → 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/README.md +149 -398
  2. package/README.zh-CN.md +153 -404
  3. package/extension/bootstrap/manifest.template.json +1 -1
  4. package/extension/src/background.js +78 -3
  5. package/extension/src/backgroundSharedSessions.js +55 -2
  6. package/extension/src/backgroundUpdateCoordinator.js +128 -12
  7. package/extension/src/content/activeTurnControl.js +164 -7
  8. package/extension/src/content/composerAttachments.js +11 -2
  9. package/extension/src/content/composerPanel.js +92 -12
  10. package/extension/src/content/contentRuntime.js +920 -276
  11. package/extension/src/content/contextTray.js +8 -2
  12. package/extension/src/content/contextUsageView.js +267 -0
  13. package/extension/src/content/diagnosticsPanel.js +18 -0
  14. package/extension/src/content/generated/content.bundle.js +4496 -924
  15. package/extension/src/content/generated/content.bundle.meta.json +6 -3
  16. package/extension/src/content/legacyGlobalRegistry.js +4 -0
  17. package/extension/src/content/localSkillsPanel.js +2 -2
  18. package/extension/src/content/markdownDomRenderer.js +5 -3
  19. package/extension/src/content/markdownText.js +42 -5
  20. package/extension/src/content/modelPicker.js +1 -0
  21. package/extension/src/content/nativeCompatibilityController.js +29 -10
  22. package/extension/src/content/otWarmMirror.js +1 -1
  23. package/extension/src/content/pageBridgeClient.js +2 -2
  24. package/extension/src/content/panelMaintenance.js +6 -2
  25. package/extension/src/content/panelRenderer.js +51 -27
  26. package/extension/src/content/pendingInputView.js +1 -1
  27. package/extension/src/content/projectSettingsCoordinator.js +30 -6
  28. package/extension/src/content/providerSettingsDialog.js +2 -1
  29. package/extension/src/content/recentProjects.js +4 -3
  30. package/extension/src/content/runActivitySummary.js +122 -14
  31. package/extension/src/content/runCompletionMeta.js +194 -6
  32. package/extension/src/content/runController.js +2 -1
  33. package/extension/src/content/runFailureNotice.js +8 -1
  34. package/extension/src/content/runPresence.js +18 -8
  35. package/extension/src/content/runQueueScheduler.js +9 -5
  36. package/extension/src/content/runRecoveryActions.js +2 -1
  37. package/extension/src/content/runResultActions.js +22 -20
  38. package/extension/src/content/runReviewController.js +94 -35
  39. package/extension/src/content/runTimelineView.js +224 -25
  40. package/extension/src/content/scopedPersistencePanelState.js +92 -23
  41. package/extension/src/content/sensitivePreflight.js +14 -1
  42. package/extension/src/content/sessionManager.js +49 -3
  43. package/extension/src/content/sessionMenuView.js +54 -18
  44. package/extension/src/content/sessionPersistence.js +25 -5
  45. package/extension/src/content/settingsPanel.js +15 -2
  46. package/extension/src/content/settingsWorkbench.js +127 -89
  47. package/extension/src/content/sharedSessionViewSync.js +4 -3
  48. package/extension/src/content/subagentActivityView.js +23 -7
  49. package/extension/src/content/updateNotice.js +60 -6
  50. package/extension/src/content/writebackOrchestrator.js +35 -22
  51. package/extension/src/page/compileBridge.js +193 -180
  52. package/extension/src/page/overleafCapabilities.js +4 -1
  53. package/extension/src/page/textCoordinates.js +17 -1
  54. package/extension/src/page/textFileCreator.js +9 -1
  55. package/extension/src/page/writebackRouter.js +24 -77
  56. package/extension/src/pageBridge.js +107 -66
  57. package/extension/src/popup.js +1 -1
  58. package/extension/src/shared/agentTranscript.js +350 -17
  59. package/extension/src/shared/auditRecords.js +2 -0
  60. package/extension/src/shared/compatibility.js +1 -1
  61. package/extension/src/shared/compileAdapter.js +58 -17
  62. package/extension/src/shared/composerAttachmentDraft.js +6 -1
  63. package/extension/src/shared/contextUsage.js +125 -0
  64. package/extension/src/shared/failureReasons.js +42 -3
  65. package/extension/src/shared/i18n.js +299 -29
  66. package/extension/src/shared/lineReferences.js +38 -31
  67. package/extension/src/shared/pageRpcContract.js +1 -1
  68. package/extension/src/shared/panelIcons.js +39 -0
  69. package/extension/src/shared/reviewing.js +130 -1
  70. package/extension/src/shared/runActivityModel.js +70 -4
  71. package/extension/src/shared/runExecutionSnapshot.js +13 -0
  72. package/extension/src/shared/sessionState.js +63 -4
  73. package/extension/src/shared/sessionTitle.js +14 -1
  74. package/extension/src/shared/sharedSessionBridge.js +81 -13
  75. package/extension/src/shared/staleGuard.js +72 -63
  76. package/extension/src/shared/storageDb.js +35 -3
  77. package/extension/src/shared/storageMigration.js +7 -1
  78. package/extension/src/shared/storageRunActions.js +62 -8
  79. package/extension/src/shared/undoOperations.js +126 -28
  80. package/extension/src/shared/updateRuntimeIdentity.js +16 -2
  81. package/extension/src/shared/writebackSettlement.js +21 -3
  82. package/extension/styles/panel.css +1106 -490
  83. package/native-host/src/bootstrapUpdate.js +52 -16
  84. package/native-host/src/chatBridgeRequest.js +60 -6
  85. package/native-host/src/chatBridgeResponse.js +10 -1
  86. package/native-host/src/chatCompletionsBridge.js +29 -2
  87. package/native-host/src/codexPromptAssembly.js +86 -118
  88. package/native-host/src/codexSessionRunner.js +126 -17
  89. package/native-host/src/providerContextError.js +20 -0
  90. package/native-host/src/taskRunnerRuntime.js +18 -6
  91. package/native-host/src/textPatch.js +473 -377
  92. package/native-host/src/updateManager.js +120 -15
  93. package/package.json +5 -8
@@ -0,0 +1,125 @@
1
+ (function initContextUsage(root, factory) {
2
+ if (typeof module === 'object' && module.exports) module.exports = factory();
3
+ else root.CodexOverleafModuleRegistry.define('ContextUsage', [], factory);
4
+ })(typeof globalThis !== 'undefined' ? globalThis : window, function contextUsageFactory() {
5
+ 'use strict';
6
+
7
+ const count = value => Number.isSafeInteger(value) && value >= 0 ? value : null;
8
+ const identity = value => typeof value === 'string' && value.trim() && value.length <= 512
9
+ && !/[\0\r\n]/.test(value) ? value.trim() : '';
10
+ const timestamp = value => typeof value === 'string' && Number.isFinite(Date.parse(value))
11
+ ? new Date(value).toISOString() : '';
12
+
13
+ // Store a bounded fact from one model response, never the cumulative billed
14
+ // token total. The last reported response is an estimate of context occupancy;
15
+ // tool output added since then is not included until the next server update.
16
+ function normalize(value) {
17
+ if (!value || value.v !== 1) return null;
18
+ const threadId = identity(value.threadId), turnId = identity(value.turnId);
19
+ const runId = identity(value.runId), providerId = identity(value.providerId), model = identity(value.model);
20
+ const usedTokens = count(value.usedTokens), providerRevision = count(value.providerRevision);
21
+ const observedAt = timestamp(value.observedAt);
22
+ if (!threadId || !turnId || !runId || !providerId || !model || usedTokens === null
23
+ || providerRevision === null || !observedAt) return null;
24
+ const capacity = count(value.contextWindow);
25
+ const eventSeq = count(value.eventSeq);
26
+ const inputTokens = count(value.inputTokens), outputTokens = count(value.outputTokens);
27
+ const cachedInput = count(value.cachedInputTokens);
28
+ // Cache reads are a subset of input, never an additional category. Older
29
+ // snapshots and providers without these counters retain unknown values.
30
+ const cachedInputTokens = inputTokens !== null && cachedInput !== null && cachedInput <= inputTokens
31
+ ? cachedInput : null;
32
+ return { v: 1, threadId, turnId, runId, providerId, providerRevision, model,
33
+ usedTokens, contextWindow: capacity > 0 ? capacity : null,
34
+ inputTokens, outputTokens, cachedInputTokens,
35
+ eventSeq: eventSeq > 0 ? eventSeq : null, observedAt };
36
+ }
37
+
38
+ function matches(value, scope = {}) {
39
+ const snapshot = normalize(value);
40
+ return Boolean(snapshot && snapshot.threadId === identity(scope.threadId || scope.codexThreadId)
41
+ && snapshot.providerId === identity(scope.providerId)
42
+ && snapshot.providerRevision === count(scope.providerRevision)
43
+ && snapshot.model === identity(scope.model));
44
+ }
45
+
46
+ // The caller supplies the captured run owner, after its Native thread/turn
47
+ // binding is known. Reject worker, previous-turn and unbound notifications.
48
+ function fromEvent(event, owner = {}) {
49
+ if (!event || (event.type && event.type !== 'codex.session.event') || event.subagent === true) return null;
50
+ const notification = event.detail || event;
51
+ if (notification.method !== 'thread/tokenUsage/updated') return null;
52
+ const params = notification.params || {};
53
+ const threadId = identity(params.threadId), turnId = identity(params.turnId);
54
+ if (!threadId || !turnId || threadId !== identity(owner.threadId || owner.codexThreadId)
55
+ || turnId !== identity(owner.turnId || owner.codexTurnId) || owner.forkSnapshot === true) return null;
56
+ const last = params.tokenUsage?.last;
57
+ const usedTokens = count(last?.totalTokens);
58
+ if (usedTokens === null) return null;
59
+ return normalize({ v: 1, threadId, turnId, runId: owner.runId || owner.id,
60
+ providerId: owner.providerId, providerRevision: owner.providerRevision, model: owner.model,
61
+ usedTokens, contextWindow: params.tokenUsage?.modelContextWindow,
62
+ inputTokens: last?.inputTokens, outputTokens: last?.outputTokens, cachedInputTokens: last?.cachedInputTokens,
63
+ eventSeq: event.nativeEventSeq ?? event.eventSeq ?? event.seq ?? owner.eventSeq,
64
+ observedAt: timestamp(event.timestamp || event.at) || timestamp(owner.observedAt) || new Date().toISOString() });
65
+ }
66
+
67
+ function isNewer(next, previous) {
68
+ next = normalize(next); previous = normalize(previous);
69
+ if (!next) return false;
70
+ if (!previous) return true;
71
+ if (next.runId === previous.runId && next.threadId === previous.threadId && next.turnId === previous.turnId
72
+ && next.eventSeq !== null && previous.eventSeq !== null)
73
+ return next.eventSeq > previous.eventSeq;
74
+ return Date.parse(next.observedAt) > Date.parse(previous.observedAt);
75
+ }
76
+
77
+ // `scope.compactedAt` (epoch ms) carries an automatic compaction completed
78
+ // inside a normal turn; manual compact runs are read from the session.
79
+ // `pending` means the old sample is invalid; `compacted` additionally requires
80
+ // successful completion of the most recent compaction that invalidated it.
81
+ function status(session, scope = {}) {
82
+ if (!session || session.forkSnapshot === true) return { usage: null, pending: false, compacted: false };
83
+ const inThread = value => value && value.threadId === identity(scope.threadId || scope.codexThreadId)
84
+ && value.providerId === identity(scope.providerId)
85
+ && value.providerRevision === count(scope.providerRevision);
86
+ // A dispatched compaction can change this thread even when its completion
87
+ // is lost on reload. Do not re-display a pre-compaction sample as current.
88
+ const runs = Array.isArray(session.runs) ? session.runs : [];
89
+ let invalidatedAfter = Number.isFinite(scope.compactedAt) && scope.compactedAt > 0 ? scope.compactedAt : 0;
90
+ let completedAfter = invalidatedAfter;
91
+ for (const run of runs) {
92
+ if (run?.operation !== 'compact' || run.forkSnapshot
93
+ || run.codexThreadId !== identity(scope.threadId || scope.codexThreadId)) continue;
94
+ const startedAt = Date.parse(run.startedAt || '') || 0;
95
+ invalidatedAfter = Math.max(invalidatedAfter, startedAt);
96
+ if (run.status === 'completed') completedAfter = Math.max(completedAfter, startedAt);
97
+ }
98
+ let selected = normalize(session.contextUsage);
99
+ if (!inThread(selected)) selected = null;
100
+ for (const run of runs) {
101
+ if (run?.forkSnapshot === true) continue;
102
+ const candidate = normalize(run.contextUsage);
103
+ if (!inThread(candidate) || candidate.runId !== run.id) continue;
104
+ if (isNewer(candidate, selected)) selected = candidate;
105
+ }
106
+ const beforeCompaction = !selected || Date.parse(selected.observedAt) < invalidatedAfter;
107
+ // A later turn can grow this thread using another model. Selecting an older
108
+ // model again must not resurrect its obsolete, smaller context estimate.
109
+ const usage = selected?.model === identity(scope.model) && !beforeCompaction ? selected : null;
110
+ const pending = !usage && invalidatedAfter > 0 && beforeCompaction;
111
+ return { usage, pending, compacted: pending && completedAfter >= invalidatedAfter };
112
+ }
113
+
114
+ function select(session, scope = {}) {
115
+ return status(session, scope).usage;
116
+ }
117
+
118
+ // Presentation level of a reported share of the window, in percent.
119
+ function level(percent) {
120
+ if (typeof percent !== 'number' || !Number.isFinite(percent)) return '';
121
+ return percent >= 90 ? 'near-limit' : percent >= 70 ? 'high' : 'normal';
122
+ }
123
+
124
+ return { normalize, fromEvent, matches, isNewer, select, status, level };
125
+ });
@@ -106,6 +106,11 @@
106
106
  },
107
107
 
108
108
  // 9.2 Preflight
109
+ sensitive_content_blocked: {
110
+ stage: 'preflight', severity: 'blocked', defaultRetryable: false,
111
+ fallbackUserMessage: 'Sensitive content was found before sending, so Codex did not receive this request.',
112
+ fallbackNextAction: 'Remove the sensitive content, narrow @context, or enable explicit sensitive confirmation in Project Settings.'
113
+ },
109
114
  governance_blocked: {
110
115
  stage: 'preflight', severity: 'blocked', defaultRetryable: false,
111
116
  fallbackUserMessage: 'Project file rules blocked this write. Codex did not write the affected path.',
@@ -232,6 +237,11 @@
232
237
  },
233
238
 
234
239
  // 9.4 Reviewing
240
+ reviewing_unavailable: {
241
+ stage: 'reviewing', severity: 'blocked', defaultRetryable: false,
242
+ fallbackUserMessage: "Track Changes is unavailable for this project. It depends on the project owner's Overleaf plan.",
243
+ fallbackNextAction: 'Turn off Track in the composer to write without tracked changes. Codex still records each change so you can undo it.'
244
+ },
235
245
  reviewing_state_unknown: {
236
246
  stage: 'reviewing', severity: 'blocked', defaultRetryable: true,
237
247
  fallbackUserMessage: 'Codex could not determine whether Reviewing/Track Changes is enabled.',
@@ -363,6 +373,11 @@
363
373
  fallbackUserMessage: 'Local Codex skills could not be loaded. This run did not start.',
364
374
  fallbackNextAction: 'Open Skills to reload the local catalog, then submit again.'
365
375
  },
376
+ codex_context_limit: {
377
+ stage: 'codex', severity: 'error', defaultRetryable: true,
378
+ fallbackUserMessage: 'This run stopped because the model context limit was exceeded.',
379
+ fallbackNextAction: 'Retry the task. If the context limit is reached again, use /compact or start a new conversation with less context.'
380
+ },
366
381
  codex_no_usable_result: {
367
382
  stage: 'codex', severity: 'error', defaultRetryable: true,
368
383
  fallbackUserMessage: 'Local Codex returned no usable final report or operations.',
@@ -616,6 +631,7 @@
616
631
  nextAction: catalog.fallbackNextAction,
617
632
  evidence: {}
618
633
  };
634
+ if (canonicalCode === 'reviewing_unavailable') failure.changedDocument = false;
619
635
  if (legacyReason) {
620
636
  failure.technicalMessage = legacyReason;
621
637
  }
@@ -791,8 +807,20 @@
791
807
  return !path.startsWith('/') && !/^[A-Za-z]:/.test(path) && !path.split('/').includes('..') ? path : '';
792
808
  }
793
809
 
810
+ function isContextLimitError(message, code) {
811
+ const text = String(message || '');
812
+ return ['codex_context_limit', 'context_length_exceeded', 'context_window_exceeded'].includes(code)
813
+ || /\b(?:codex_context_limit|context_length_exceeded|context_window_exceeded)\b/i.test(text)
814
+ || /Codex ran out of room in the model['’]s context window/i.test(text)
815
+ || /maximum context length is \d+ tokens[\s\S]*?(?:you requested|resulted in) \d+ tokens/i.test(text);
816
+ }
817
+
794
818
  function recoverDisplayFailure(failure, allowFilePrefix = true) {
795
819
  if (!failure || typeof failure !== 'object') return failure;
820
+ if (failure.code === 'codex_no_usable_result' && failure.stage === 'codex'
821
+ && isContextLimitError(failure.technicalMessage)) return { ...failure, code: 'codex_context_limit',
822
+ userMessage: FAILURE_CODE_CATALOG.codex_context_limit.fallbackUserMessage,
823
+ nextAction: FAILURE_CODE_CATALOG.codex_context_limit.fallbackNextAction };
796
824
  const originalCode = failure.evidence?.originalCode;
797
825
  const displayCodeAllowed = code => REVIEW_DISPLAY_CODES.has(code)
798
826
  && (code !== 'native_untracked_undo_pending_changes'
@@ -829,7 +857,8 @@
829
857
  function formatFailureReasonForDisplay(failure, locale, i18nLookup) {
830
858
  const display = recoverDisplayFailure(failure);
831
859
  const lookup = typeof i18nLookup === 'function' ? i18nLookup : () => undefined;
832
- const key = display?.code ? 'failureReason_' + display.code + '_user' : '';
860
+ const messageCode = getFailureMessageCode(display);
861
+ const key = messageCode ? 'failureReason_' + messageCode + '_user' : '';
833
862
  const template = key ? lookup(key) : '';
834
863
  const file = safeDisplayProjectPath(display?.file);
835
864
  const localized = localizeFailureReason(display ? { ...display, file } : display, locale, lookup);
@@ -838,6 +867,15 @@
838
867
  return { ...localized, code: display?.code, message, file, localized: Boolean(template && template !== key) };
839
868
  }
840
869
 
870
+ function getFailureMessageCode(failure) {
871
+ // Reload recovery retains its historical retry code. Only its presentation
872
+ // identifies the page interruption instead of a failed bridge request.
873
+ if (failure?.code === 'native_request_failed' && failure.source === 'panel_reload') {
874
+ return 'panel_reload_interrupted';
875
+ }
876
+ return typeof failure?.code === 'string' ? failure.code : '';
877
+ }
878
+
841
879
  function localizeFailureMessage(message, locale, i18nLookup) {
842
880
  return formatFailureReasonForDisplay({ code: 'unknown_legacy_failure', userMessage: message }, locale, i18nLookup).message;
843
881
  }
@@ -949,7 +987,7 @@
949
987
  const value = typeof i18nLookup === 'function' ? i18nLookup(key) : undefined;
950
988
  return value && value !== key ? value : undefined;
951
989
  };
952
- const code = failure && typeof failure.code === 'string' ? failure.code : '';
990
+ const code = getFailureMessageCode(failure);
953
991
  const userTemplate = code ? lookup('failureReason_' + code + '_user') : undefined;
954
992
  const nextTemplate = (code ? lookup('failureReason_' + code + '_next') : undefined)
955
993
  || (REVIEW_DISPLAY_CODES.has(code) ? lookup('failureReason_review_detail_next') : undefined);
@@ -989,6 +1027,7 @@
989
1027
  selectPrimaryFailure,
990
1028
  localizeFailureReason,
991
1029
  formatFailureReasonForDisplay,
992
- localizeFailureMessage
1030
+ localizeFailureMessage,
1031
+ isContextLimitError
993
1032
  };
994
1033
  });