codex-overleaf-link 2.5.0 → 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (189) hide show
  1. package/README.md +155 -392
  2. package/README.zh-CN.md +154 -393
  3. package/extension/bootstrap/background.js +52 -9
  4. package/extension/bootstrap/manifest.template.json +1 -1
  5. package/extension/bootstrap/popup.html +1 -0
  6. package/extension/bootstrap/popupFallback.js +55 -0
  7. package/extension/bootstrap/updateStatus.js +1 -0
  8. package/extension/src/background.js +283 -55
  9. package/extension/src/backgroundBootstrapMigration.js +181 -0
  10. package/extension/src/backgroundSharedSessions.js +129 -16
  11. package/extension/src/backgroundUpdateCoordinator.js +242 -29
  12. package/extension/src/backgroundUpdateSafety.js +173 -0
  13. package/extension/src/content/activeTurnControl.js +171 -8
  14. package/extension/src/content/applyResultFormatters.js +46 -73
  15. package/extension/src/content/assetTransferBroker.js +72 -14
  16. package/extension/src/content/composerAttachments.js +104 -47
  17. package/extension/src/content/composerPanel.js +110 -12
  18. package/extension/src/content/contentRuntime.js +1555 -926
  19. package/extension/src/content/contextTray.js +8 -2
  20. package/extension/src/content/contextUsageView.js +267 -0
  21. package/extension/src/content/diagnosticsController.js +122 -96
  22. package/extension/src/content/diagnosticsExport.js +19 -5
  23. package/extension/src/content/diagnosticsPanel.js +48 -0
  24. package/extension/src/content/directoryCleanupRecovery.js +54 -0
  25. package/extension/src/content/generated/content.bundle.js +11077 -3412
  26. package/extension/src/content/generated/content.bundle.meta.json +14 -3
  27. package/extension/src/content/globalPreferencesController.js +1 -1
  28. package/extension/src/content/legacyGlobalRegistry.js +10 -0
  29. package/extension/src/content/localSkillsPanel.js +104 -14
  30. package/extension/src/content/markdownDomRenderer.js +5 -3
  31. package/extension/src/content/markdownText.js +47 -11
  32. package/extension/src/content/modelPicker.js +8 -4
  33. package/extension/src/content/nativeCompatibilityController.js +74 -30
  34. package/extension/src/content/otWarmMirror.js +5 -6
  35. package/extension/src/content/otWarmMirrorController.js +17 -20
  36. package/extension/src/content/pageBridgeClient.js +17 -4
  37. package/extension/src/content/panelMaintenance.js +46 -14
  38. package/extension/src/content/panelRenderer.js +57 -27
  39. package/extension/src/content/pendingInputView.js +1 -1
  40. package/extension/src/content/projectProviderSelection.js +13 -4
  41. package/extension/src/content/projectSessionCleanup.js +109 -62
  42. package/extension/src/content/projectSettingsCoordinator.js +67 -6
  43. package/extension/src/content/providerSettingsCoordinator.js +46 -8
  44. package/extension/src/content/providerSettingsDialog.js +73 -14
  45. package/extension/src/content/recentProjects.js +15 -8
  46. package/extension/src/content/runActivitySummary.js +160 -18
  47. package/extension/src/content/runCompletionMeta.js +532 -0
  48. package/extension/src/content/runController.js +39 -15
  49. package/extension/src/content/runFailureNotice.js +29 -13
  50. package/extension/src/content/runGuidanceController.js +72 -27
  51. package/extension/src/content/runGuidanceView.js +59 -10
  52. package/extension/src/content/runPresence.js +18 -8
  53. package/extension/src/content/runQueueScheduler.js +157 -47
  54. package/extension/src/content/runRecoveryActions.js +147 -0
  55. package/extension/src/content/runResultActions.js +28 -143
  56. package/extension/src/content/runReviewController.js +633 -0
  57. package/extension/src/content/runScrollLayout.js +21 -2
  58. package/extension/src/content/runTimelineView.js +367 -175
  59. package/extension/src/content/scopedPersistencePanelState.js +134 -20
  60. package/extension/src/content/selectionContextView.js +1 -1
  61. package/extension/src/content/sensitivePreflight.js +153 -0
  62. package/extension/src/content/sessionForkController.js +80 -17
  63. package/extension/src/content/sessionManager.js +50 -4
  64. package/extension/src/content/sessionMenuView.js +54 -18
  65. package/extension/src/content/sessionPersistence.js +25 -5
  66. package/extension/src/content/settingsPanel.js +22 -5
  67. package/extension/src/content/settingsWorkbench.js +132 -89
  68. package/extension/src/content/sharedSessionViewSync.js +49 -11
  69. package/extension/src/content/subagentActivityView.js +23 -7
  70. package/extension/src/content/updateIdle.js +4 -1
  71. package/extension/src/content/updateNotice.js +118 -12
  72. package/extension/src/content/updateSafetyReceiver.js +58 -0
  73. package/extension/src/content/writebackController.js +53 -4
  74. package/extension/src/content/writebackOrchestrator.js +116 -94
  75. package/extension/src/content/writingStyleSettings.js +35 -11
  76. package/extension/src/page/binaryAssetUploader.js +136 -14
  77. package/extension/src/page/compileBridge.js +193 -180
  78. package/extension/src/page/fileMover.js +256 -0
  79. package/extension/src/page/overleafCapabilities.js +4 -1
  80. package/extension/src/page/ownedDirectoryCleanup.js +94 -0
  81. package/extension/src/page/referenceProjects.js +1 -0
  82. package/extension/src/page/reviewContext.js +113 -0
  83. package/extension/src/page/saveState.js +6 -4
  84. package/extension/src/page/snapshotRouter.js +84 -39
  85. package/extension/src/page/textCoordinates.js +17 -1
  86. package/extension/src/page/textFileCreator.js +114 -26
  87. package/extension/src/page/trackedChangeCapture.js +12 -2
  88. package/extension/src/page/trackedChangeOwnership.js +19 -1
  89. package/extension/src/page/trackedChangeReplay.js +41 -21
  90. package/extension/src/page/trackedChangesLifecycle.js +217 -199
  91. package/extension/src/page/treeOperations.js +7 -10
  92. package/extension/src/page/updateSafetyProbe.js +41 -0
  93. package/extension/src/page/writebackReceiptJournal.js +2 -0
  94. package/extension/src/page/writebackRouter.js +103 -155
  95. package/extension/src/pageBridge.js +126 -69
  96. package/extension/src/popup.js +62 -10
  97. package/extension/src/shared/agentTranscript.js +373 -17
  98. package/extension/src/shared/auditRecords.js +4 -0
  99. package/extension/src/shared/compatibility.js +22 -6
  100. package/extension/src/shared/compileAdapter.js +129 -66
  101. package/extension/src/shared/composerAttachmentDraft.js +156 -0
  102. package/extension/src/shared/contextUsage.js +125 -0
  103. package/extension/src/shared/directoryCleanupProof.js +43 -0
  104. package/extension/src/shared/failureReasons.js +349 -4
  105. package/extension/src/shared/globalPreferences.js +30 -13
  106. package/extension/src/shared/governanceRules.js +4 -37
  107. package/extension/src/shared/i18n.js +590 -24
  108. package/extension/src/shared/lineReferences.js +38 -31
  109. package/extension/src/shared/managedUpdateProjection.js +2 -2
  110. package/extension/src/shared/nativeRequestIdentity.js +1 -0
  111. package/extension/src/shared/pageRpcContract.js +5 -4
  112. package/extension/src/shared/panelIcons.js +39 -0
  113. package/extension/src/shared/projectFiles.js +21 -44
  114. package/extension/src/shared/providerProfiles.js +5 -3
  115. package/extension/src/shared/reviewing.js +130 -1
  116. package/extension/src/shared/runActivityModel.js +70 -4
  117. package/extension/src/shared/runExecutionSnapshot.js +13 -0
  118. package/extension/src/shared/runInputQueue.js +48 -5
  119. package/extension/src/shared/selectionContext.js +12 -3
  120. package/extension/src/shared/sessionState.js +132 -28
  121. package/extension/src/shared/sessionTitle.js +14 -1
  122. package/extension/src/shared/settlementFacts.js +10 -2
  123. package/extension/src/shared/sharedSessionBridge.js +118 -27
  124. package/extension/src/shared/sharedSessionTransfer.js +140 -0
  125. package/extension/src/shared/staleGuard.js +166 -52
  126. package/extension/src/shared/storageDb.js +90 -26
  127. package/extension/src/shared/storageMigration.js +78 -112
  128. package/extension/src/shared/storageRunActions.js +267 -30
  129. package/extension/src/shared/subagentActivityModel.js +1 -1
  130. package/extension/src/shared/undoOperations.js +253 -32
  131. package/extension/src/shared/updateConsent.js +6 -4
  132. package/extension/src/shared/updateRuntimeIdentity.js +17 -3
  133. package/extension/src/shared/writebackIntent.js +16 -0
  134. package/extension/src/shared/writebackOperationEvidence.js +4 -15
  135. package/extension/src/shared/writebackSettlement.js +58 -40
  136. package/extension/styles/panel.css +1123 -493
  137. package/native-host/src/anthropicBridgeRequest.js +4 -2
  138. package/native-host/src/anthropicBridgeResponse.js +6 -2
  139. package/native-host/src/anthropicMessagesBridge.js +24 -16
  140. package/native-host/src/bootstrapMigration.js +296 -0
  141. package/native-host/src/bootstrapMigrationContract.js +101 -0
  142. package/native-host/src/bootstrapUpdate.js +269 -0
  143. package/native-host/src/bootstrapUpdateWatchdog.js +44 -0
  144. package/native-host/src/chatBridgeRequest.js +60 -6
  145. package/native-host/src/chatBridgeResponse.js +20 -1
  146. package/native-host/src/chatCompletionsBridge.js +48 -15
  147. package/native-host/src/codexConfigPaths.js +106 -0
  148. package/native-host/src/codexHome.js +66 -73
  149. package/native-host/src/codexHomeScope.js +124 -0
  150. package/native-host/src/codexInheritedAuth.js +90 -0
  151. package/native-host/src/codexLocalSkills.js +301 -0
  152. package/native-host/src/codexPromptAssembly.js +114 -127
  153. package/native-host/src/codexProviderTest.js +24 -26
  154. package/native-host/src/codexSessionRunner.js +234 -198
  155. package/native-host/src/codexSessionTiming.js +18 -8
  156. package/native-host/src/codexSkillIsolation.js +170 -0
  157. package/native-host/src/codexThreadFork.js +19 -3
  158. package/native-host/src/index.js +19 -7
  159. package/native-host/src/managedInstall.js +110 -35
  160. package/native-host/src/managedInstallRecovery.js +140 -0
  161. package/native-host/src/managedRuntimeEvidence.js +57 -0
  162. package/native-host/src/mirrorWorkspace.js +92 -22
  163. package/native-host/src/nativeDoctor.js +15 -0
  164. package/native-host/src/nativeHelperLifecycle.js +21 -0
  165. package/native-host/src/nativeHostRegistration.js +33 -0
  166. package/native-host/src/nativeRunLifecycle.js +71 -0
  167. package/native-host/src/processStopBarrier.js +42 -7
  168. package/native-host/src/projectPathPlatform.js +30 -0
  169. package/native-host/src/providerContextError.js +20 -0
  170. package/native-host/src/providerHttp.js +80 -0
  171. package/native-host/src/providerProfile.js +4 -2
  172. package/native-host/src/providerReasoning.js +2 -7
  173. package/native-host/src/providerRuntime.js +1 -0
  174. package/native-host/src/providerStore.js +72 -14
  175. package/native-host/src/responsesPassthroughBridge.js +266 -52
  176. package/native-host/src/runtimeInstaller.js +21 -1
  177. package/native-host/src/selectionScope.js +36 -8
  178. package/native-host/src/subagentAdmission.js +26 -0
  179. package/native-host/src/subagentBroker.js +79 -66
  180. package/native-host/src/subagentTelemetry.js +19 -10
  181. package/native-host/src/subagentWorkspacePath.js +5 -2
  182. package/native-host/src/taskRunnerRuntime.js +92 -57
  183. package/native-host/src/textPatch.js +473 -377
  184. package/native-host/src/tomlStatements.js +140 -0
  185. package/native-host/src/updateManager.js +409 -46
  186. package/native-host/src/writingStyleRuntime.js +57 -29
  187. package/native-host/src/writingStyleStore.js +73 -0
  188. package/package.json +5 -8
  189. package/scripts/install-managed.mjs +1 -0
@@ -227,20 +227,20 @@
227
227
 
228
228
  function collectMarkdownTargetReferences(text, mode, refs) {
229
229
  const normalizedText = stripReferenceQueryAndHash(String(text || '').trim());
230
- const match = normalizedText.match(new RegExp(`^(.+?\\.${TEXT_EXTENSION_PATTERN}):(\\d+)(?::(\\d+))?$`, 'i'));
230
+ const match = normalizedText.match(new RegExp(`^(.+?\\.${TEXT_EXTENSION_PATTERN}):(\\d+)(?:[-–](\\d+)|:(\\d+))?$`, 'i'));
231
231
  if (!match) {
232
232
  return;
233
233
  }
234
- const line = parsePositiveInteger(match[2]);
235
- const column = match[3] ? parsePositiveInteger(match[3]) : null;
236
- if (!line || (match[3] && !column)) {
234
+ const span = parseLineSpan(match[2], match[3]);
235
+ const column = match[4] ? parsePositiveInteger(match[4]) : null;
236
+ if (!span || (match[4] && !column)) {
237
237
  return;
238
238
  }
239
239
  refs.push({
240
240
  rawText: text,
241
241
  displayText: text,
242
242
  rawPath: match[1],
243
- line,
243
+ ...span,
244
244
  column,
245
245
  source: mode,
246
246
  index: 0
@@ -248,12 +248,14 @@
248
248
  }
249
249
 
250
250
  function collectColonReferences(text, mode, refs) {
251
- const pattern = new RegExp(`${REFERENCE_PREFIX_PATTERN}${PATH_PATTERN}:(\\d+)(?::(\\d+))?(?![-:\\d])`, 'gi');
251
+ // Range/column suffixes are mutually exclusive; malformed spans must not
252
+ // backtrack into a shorter reference, including decimal/exponent endpoints.
253
+ const pattern = new RegExp(`${REFERENCE_PREFIX_PATTERN}${PATH_PATTERN}:(\\d+)(?:[-–](\\d+)(?!\\.\\d|e[+-]?\\d)|:(\\d+))?(?![-–:\\d]|(?:\\.\\d+)?(?:e[+-]?\\d+)?[-–])`, 'gi');
252
254
  let match;
253
255
  while ((match = pattern.exec(text)) !== null) {
254
- const line = parsePositiveInteger(match[3]);
255
- const column = match[4] ? parsePositiveInteger(match[4]) : null;
256
- if (!line || (match[4] && !column)) {
256
+ const span = parseLineSpan(match[3], match[4]);
257
+ const column = match[5] ? parsePositiveInteger(match[5]) : null;
258
+ if (!span || (match[5] && !column)) {
257
259
  continue;
258
260
  }
259
261
  const prefixLength = match[1].length;
@@ -262,7 +264,7 @@
262
264
  rawText,
263
265
  displayText: rawText,
264
266
  rawPath: match[2],
265
- line,
267
+ ...span,
266
268
  column,
267
269
  source: mode,
268
270
  index: match.index + prefixLength
@@ -271,20 +273,20 @@
271
273
  }
272
274
 
273
275
  function collectLineWordReferences(text, mode, refs) {
274
- const pattern = new RegExp(`${REFERENCE_PREFIX_PATTERN}${PATH_PATTERN}\\s+line\\s+(\\d+)(?![-:\\d])`, 'gi');
276
+ const pattern = new RegExp(`${REFERENCE_PREFIX_PATTERN}${PATH_PATTERN}\\s+line(?:s(?=\\s+\\d+[-–]))?\\s+(\\d+)(?:[-–](\\d+)(?!\\.\\d|e[+-]?\\d))?(?![-–:\\d]|(?:\\.\\d+)?(?:e[+-]?\\d+)?[-–])`, 'gi');
275
277
  let match;
276
278
  while ((match = pattern.exec(text)) !== null) {
277
- const line = parsePositiveInteger(match[3]);
278
- if (!line) {
279
+ const span = parseLineSpan(match[3], match[4]);
280
+ if (!span) {
279
281
  continue;
280
282
  }
281
283
  const prefixLength = match[1].length;
282
284
  const rawText = match[0].slice(prefixLength);
283
285
  refs.push({
284
286
  rawText,
285
- displayText: `${match[2]}:${line}`,
287
+ displayText: `${match[2]}:${span.line}${span.endLine ? `–${span.endLine}` : ''}`,
286
288
  rawPath: match[2],
287
- line,
289
+ ...span,
288
290
  column: null,
289
291
  source: mode,
290
292
  index: match.index + prefixLength
@@ -293,20 +295,20 @@
293
295
  }
294
296
 
295
297
  function collectChineseLineReferences(text, mode, refs) {
296
- const pattern = new RegExp(`${REFERENCE_PREFIX_PATTERN}${PATH_PATTERN}\\s+第\\s*(\\d+)\\s*行`, 'gi');
298
+ const pattern = new RegExp(`${REFERENCE_PREFIX_PATTERN}${PATH_PATTERN}\\s+第\\s*(\\d+)(?:\\s*[-–至]\\s*(\\d+))?\\s*行`, 'gi');
297
299
  let match;
298
300
  while ((match = pattern.exec(text)) !== null) {
299
- const line = parsePositiveInteger(match[3]);
300
- if (!line) {
301
+ const span = parseLineSpan(match[3], match[4]);
302
+ if (!span) {
301
303
  continue;
302
304
  }
303
305
  const prefixLength = match[1].length;
304
306
  const rawText = match[0].slice(prefixLength);
305
307
  refs.push({
306
308
  rawText,
307
- displayText: `${match[2]}:${line}`,
309
+ displayText: `${match[2]}:${span.line}${span.endLine ? `–${span.endLine}` : ''}`,
308
310
  rawPath: match[2],
309
- line,
311
+ ...span,
310
312
  column: null,
311
313
  source: mode,
312
314
  index: match.index + prefixLength
@@ -361,13 +363,13 @@
361
363
  projectFiles
362
364
  });
363
365
  if (resolved) {
364
- return `${resolved.path}:${ref.line}${ref.column ? `:${ref.column}` : ''}`;
366
+ return `${resolved.path}:${ref.line}${ref.endLine ? `-${ref.endLine}` : ref.column ? `:${ref.column}` : ''}`;
365
367
  }
366
- return formatLocalPathPlaceholder(ref.line, placeholderBrackets);
368
+ return formatLocalPathPlaceholder(ref.line, placeholderBrackets, ref.endLine);
367
369
  }
368
370
 
369
- function formatLocalPathPlaceholder(line, includeBrackets) {
370
- const value = line ? `local path:${line}` : 'local path';
371
+ function formatLocalPathPlaceholder(line, includeBrackets, endLine) {
372
+ const value = line ? `local path:${line}${endLine ? `-${endLine}` : ''}` : 'local path';
371
373
  return includeBrackets ? `[${value}]` : value;
372
374
  }
373
375
 
@@ -383,7 +385,7 @@
383
385
  }
384
386
 
385
387
  function stripReferenceQueryAndHash(value) {
386
- return String(value || '').replace(/([:.]\d+(?::\d+)?)(?:[?#].*)$/, '$1');
388
+ return String(value || '').replace(/([:.]\d+(?:[-–]\d+|:\d+)?)(?:[?#].*)$/, '$1');
387
389
  }
388
390
 
389
391
  function collectTextProjectFiles(projectFiles) {
@@ -423,18 +425,13 @@
423
425
  function shouldSkipReference(ref) {
424
426
  const rawPath = String(ref.rawPath || '');
425
427
  return /^https?:\/\//i.test(rawPath)
426
- || isEmailLikePath(rawPath)
427
- || hasUnsafeLineRange(ref.rawText);
428
+ || isEmailLikePath(rawPath);
428
429
  }
429
430
 
430
431
  function isEmailLikePath(rawPath) {
431
432
  return /^[^@\s/\\]+@[^@\s/\\]+$/.test(rawPath);
432
433
  }
433
434
 
434
- function hasUnsafeLineRange(rawText) {
435
- return /:\d+-\d+(?=$|[^\d])/.test(rawText);
436
- }
437
-
438
435
  function hasUnsafePathSegments(path) {
439
436
  return normalizeReferencePath(path)
440
437
  .split('/')
@@ -455,6 +452,15 @@
455
452
  return parsed;
456
453
  }
457
454
 
455
+ function parseLineSpan(rawLine, rawEndLine) {
456
+ const line = parsePositiveInteger(rawLine);
457
+ const endLine = rawEndLine === undefined ? null : parsePositiveInteger(rawEndLine);
458
+ if (!line || (rawEndLine !== undefined && (!endLine || endLine < line))) {
459
+ return null;
460
+ }
461
+ return { line, ...(endLine > line ? { endLine } : {}) };
462
+ }
463
+
458
464
  function getBasename(path) {
459
465
  const parts = String(path || '').split('/');
460
466
  return parts[parts.length - 1] || '';
@@ -477,6 +483,7 @@
477
483
  displayText: ref.displayText,
478
484
  rawPath: ref.rawPath,
479
485
  line: ref.line,
486
+ ...(ref.endLine ? { endLine: ref.endLine } : {}),
480
487
  column: ref.column,
481
488
  source: ref.source
482
489
  };
@@ -5,9 +5,9 @@
5
5
  waiting: 'waiting_for_idle',
6
6
  waiting_for_safe_point: 'waiting_for_idle',
7
7
  awaiting_health_check: 'awaiting_health',
8
- health_checking: 'awaiting_health'
8
+ health_checking: 'awaiting_health',
9
+ bootstrap_pending: 'awaiting_health'
9
10
  });
10
-
11
11
  const PHASES = Object.freeze([
12
12
  'idle',
13
13
  'up_to_date',
@@ -55,6 +55,7 @@
55
55
 
56
56
  return Object.freeze({
57
57
  MAX_REQUEST_ID_CHARS,
58
+ validate,
58
59
  resolve
59
60
  });
60
61
  });
@@ -5,7 +5,7 @@
5
5
  })(typeof window !== 'undefined' ? window : (typeof globalThis !== 'undefined' ? globalThis : this), function () {
6
6
  'use strict';
7
7
 
8
- var REVISION = '2026-09-27-writing-style-references-v33';
8
+ var REVISION = '2026-10-19-mode-menu-switch-v52';
9
9
  var METHODS = freezeCatalog({
10
10
  initializeCapability: entry('bridge.initialize', 'control', 'none', 'default', false, 'none'),
11
11
  probe: entry('bridge.probe', 'read', 'none', 'default', false, 'safe'),
@@ -20,12 +20,13 @@
20
20
  ensureReviewing: entry('reviewing.enable', 'page_state', 'optional', 'default', true, 'idempotent'),
21
21
  ensureEditing: entry('reviewing.disable', 'page_state', 'optional', 'default', true, 'idempotent'),
22
22
  applyOperations: entry('document.write', 'document', 'required', 'writeback', true, 'no_retry'),
23
- getWritebackReceipt: entry('writeback.receipt', 'read', 'required', 'default', true, 'safe'),
23
+ // Receipts settle already-dispatched writes after cancellation; their RPC deadlines still apply.
24
+ getWritebackReceipt: entry('writeback.receipt', 'read', 'required', 'default', false, 'safe'),
24
25
  getSelectionContext: entry('editor.selection', 'read', 'required', 'default', true, 'safe'),
25
- binaryUploadBegin: entry('asset.stage', 'cache', 'required', 'default', false, 'no_retry'),
26
+ binaryUploadBegin: entry('asset.stage', 'cache', 'required', 'default', true, 'no_retry'),
26
27
  binaryUploadAppend: entry('asset.stage', 'cache', 'none', 'writeback', true, 'no_retry'),
27
28
  binaryUploadCommit: entry('asset.write', 'document', 'required', 'writeback', true, 'no_retry'),
28
- binaryUploadStatus: entry('asset.receipt', 'read', 'required', 'default', true, 'safe'),
29
+ binaryUploadStatus: entry('asset.receipt', 'read', 'required', 'default', false, 'safe'),
29
30
  binaryUploadAbort: entry('asset.stage', 'cache', 'none', 'default', false, 'idempotent'),
30
31
  jumpToPosition: entry('editor.navigate', 'navigation', 'required', 'default', false, 'idempotent'),
31
32
  reconcileTrackedChangeCapture: entry('tracked_changes.read', 'read', 'required', 'default', false, 'safe'),
@@ -0,0 +1,39 @@
1
+ (function initPanelIcons(root, factory) {
2
+ if (typeof module === 'object' && module.exports) module.exports = factory();
3
+ else root.CodexOverleafModuleRegistry.define('PanelIcons', [], factory);
4
+ })(typeof globalThis !== 'undefined' ? globalThis : window, function panelIconsFactory() {
5
+ 'use strict';
6
+
7
+ // 16-unit line icons drawn in the panel header's codexIcon style. Size and
8
+ // color come from the caller; `.codex-glyph` supplies stroke and caps.
9
+ const PATHS = Object.freeze({
10
+ plus: '<path d="M8 3.2v9.6M3.2 8h9.6"/>',
11
+ send: '<path d="M8 12.8V3.6"/><path d="m4.2 7.4 3.8-3.8 3.8 3.8"/>',
12
+ stop: '<rect x="4.4" y="4.4" width="7.2" height="7.2" rx="1.6" fill="currentColor" stroke="none"/>',
13
+ attach: '<path d="m12.6 7.4-4.8 4.8a3 3 0 0 1-4.2-4.2l5-5a2 2 0 0 1 2.8 2.8l-4.9 4.9a1 1 0 0 1-1.4-1.4l4.4-4.4"/>',
14
+ refresh: '<path d="M4.2 5.3A5 5 0 1 1 3.6 10"/><path d="M4.2 2.2v3.1H1.1"/>',
15
+ sparkle: '<path d="M7.4 2.6c.5 2.6 1.8 3.9 4.4 4.4-2.6.5-3.9 1.8-4.4 4.4C6.9 8.8 5.6 7.5 3 7c2.6-.5 3.9-1.8 4.4-4.4Z"/><path d="M12.3 10.5c.2 1 .7 1.5 1.7 1.7-1 .2-1.5.7-1.7 1.7-.2-1-.7-1.5-1.7-1.7 1-.2 1.5-.7 1.7-1.7Z"/>',
16
+ close: '<path d="m4.6 4.6 6.8 6.8M11.4 4.6l-6.8 6.8"/>',
17
+ pencil: '<path d="M10.4 3.1 12.9 5.6 6 12.5l-3.1.6.6-3.1Z"/><path d="m9.2 4.3 2.5 2.5"/>',
18
+ chevronDown: '<path d="m4.4 6.4 3.6 3.6 3.6-3.6"/>',
19
+ chevronLeft: '<path d="m9.8 3.6-4.4 4.4 4.4 4.4"/>',
20
+ lock: '<rect x="3.6" y="7.1" width="8.8" height="6.3" rx="1.5"/><path d="M5.6 7.1V5.3a2.4 2.4 0 0 1 4.8 0v1.8"/>',
21
+ compact: '<path d="M4.6 2.4 8 5.6l3.4-3.2"/><path d="M4.6 13.6 8 10.4l3.4 3.2"/><path d="M2.6 8h10.8"/>',
22
+ install: '<path d="M8 2.4v7.4"/><path d="m4.8 6.8 3.2 3.2 3.2-3.2"/><path d="M2.6 11v1.5c0 .6.4 1 1 1h8.8c.6 0 1-.4 1-1V11"/>',
23
+ book: '<path d="M8 4.3c-1.3-1.1-3-1.5-5.2-1.4v9.3c2.2-.1 3.9.3 5.2 1.4 1.3-1.1 3-1.5 5.2-1.4V2.9C11 2.8 9.3 3.2 8 4.3Z"/><path d="M8 4.3v9.3"/>',
24
+ terminal: '<rect x="2.1" y="2.9" width="11.8" height="10.2" rx="2"/><path d="m5 6.4 2 1.7-2 1.7"/><path d="M8.7 10h2.5"/>',
25
+ file: '<path d="M9.2 2.4H4.6a1 1 0 0 0-1 1v9.2a1 1 0 0 0 1 1h6.8a1 1 0 0 0 1-1V5.6Z"/><path d="M9.2 2.4v3.2h3.2"/>',
26
+ folder: '<path d="M2.2 4.4a1 1 0 0 1 1-1h3l1.4 1.6h5.2a1 1 0 0 1 1 1v6.4a1 1 0 0 1-1 1H3.2a1 1 0 0 1-1-1Z"/>',
27
+ alert: '<circle cx="8" cy="8" r="5.9"/><path d="M8 5v3.5"/><path d="M8 11h.01"/>',
28
+ spin: '<path d="M13.6 8A5.6 5.6 0 1 1 8 2.4"/>'
29
+ });
30
+
31
+ function svg(name, className = '') {
32
+ const paths = PATHS[name];
33
+ if (!paths) return '';
34
+ return '<svg class="codex-glyph' + (className ? ' ' + className : '') + '" viewBox="0 0 16 16" aria-hidden="true" focusable="false">'
35
+ + paths + '</svg>';
36
+ }
37
+
38
+ return { svg };
39
+ });
@@ -83,55 +83,31 @@
83
83
  .replace(/^\/+/, '');
84
84
  }
85
85
 
86
+ // Project metadata, ZIP names and operation paths are already literal names.
87
+ // Decoding here would alias e.g. "a%20b.tex" with "a b.tex".
86
88
  function normalizeSafeProjectPath(value) {
87
- if (typeof value !== 'string') {
88
- return '';
89
- }
90
- if (hasControlCharacter(value)) {
91
- return '';
92
- }
93
- const trimmed = value.trim();
94
- if (!trimmed || trimmed.length > MAX_SAFE_PROJECT_PATH_LENGTH) {
95
- return '';
96
- }
97
- if (trimmed.startsWith('/') || trimmed.includes('\\') || hasWindowsDrivePrefix(trimmed)) {
98
- return '';
99
- }
89
+ if (typeof value !== 'string' || !value || value.length > MAX_SAFE_PROJECT_PATH_LENGTH || value.trim() !== value
90
+ || hasControlCharacter(value) || value.startsWith('/') || value.includes('\\')
91
+ || hasWindowsDrivePrefix(value)) return '';
92
+ return value.split('/').some(segment => !segment || segment === '.' || segment === '..') ? '' : value;
93
+ }
100
94
 
101
- let decoded = trimmed;
95
+ // Use only at an explicitly URL-encoded input boundary. Return one decoding,
96
+ // while conservatively checking nested encodings for traversal/separators.
97
+ function normalizeUrlEncodedProjectPath(value) {
98
+ if (typeof value !== 'string' || hasEncodedSeparator(value)) return '';
99
+ let decoded;
100
+ try { decoded = decodeURIComponent(value); } catch (_error) { return ''; }
101
+ const result = normalizeSafeProjectPath(decoded);
102
+ if (!result) return '';
102
103
  for (let index = 0; index < 3; index += 1) {
103
- if (hasEncodedSeparator(decoded)) {
104
- return '';
105
- }
104
+ if (hasEncodedSeparator(decoded) || !normalizeSafeProjectPath(decoded)) return '';
106
105
  let next;
107
- try {
108
- next = decodeURIComponent(decoded);
109
- } catch (_error) {
110
- return '';
111
- }
112
- if (next === decoded) {
113
- break;
114
- }
106
+ try { next = decodeURIComponent(decoded); } catch (_error) { break; }
107
+ if (next === decoded) break;
115
108
  decoded = next;
116
109
  }
117
-
118
- if (hasControlCharacter(decoded)
119
- || decoded.startsWith('/')
120
- || decoded.includes('\\')
121
- || hasWindowsDrivePrefix(decoded)
122
- || decoded.length > MAX_SAFE_PROJECT_PATH_LENGTH) {
123
- return '';
124
- }
125
-
126
- const normalized = decoded.replace(/\/+/g, '/').trim();
127
- if (!normalized || normalized.length > MAX_SAFE_PROJECT_PATH_LENGTH) {
128
- return '';
129
- }
130
- const segments = normalized.split('/');
131
- if (segments.some(segment => !segment || segment === '.' || segment === '..')) {
132
- return '';
133
- }
134
- return normalized;
110
+ return hasEncodedSeparator(decoded) || !normalizeSafeProjectPath(decoded) ? '' : result;
135
111
  }
136
112
 
137
113
  function assertSafeProjectPath(value, label = 'path') {
@@ -175,6 +151,7 @@
175
151
  isUsableProjectFileContent,
176
152
  isTextProjectPath,
177
153
  normalizePath,
178
- normalizeSafeProjectPath
154
+ normalizeSafeProjectPath,
155
+ normalizeUrlEncodedProjectPath
179
156
  };
180
157
  });
@@ -167,7 +167,8 @@
167
167
  anthropicThinkingMode: normalizeAnthropicThinkingMode(value.anthropicThinkingMode),
168
168
  anthropicPromptCaching: value.anthropicPromptCaching === true,
169
169
  impersonateClaudeCode: value.impersonateClaudeCode === true,
170
- maxOutputTokens: normalizeMaxOutputTokens(value.maxOutputTokens),
170
+ maxOutputTokens: normalizeMaxOutputTokens(value.maxOutputTokens, value.outputBudgetVersion !== 1),
171
+ outputBudgetVersion: 1,
171
172
  hasSecret: value.hasSecret === true,
172
173
  secretUpdatedAt: normalizeInteger(value.secretUpdatedAt),
173
174
  endpointDisclosureHost: normalizeText(value.endpointDisclosureHost),
@@ -262,6 +263,7 @@
262
263
  anthropicPromptCaching: false,
263
264
  impersonateClaudeCode: false,
264
265
  maxOutputTokens: 65536,
266
+ outputBudgetVersion: 1,
265
267
  hasSecret: false,
266
268
  secretUpdatedAt: 0,
267
269
  endpointDisclosureHost: '',
@@ -380,9 +382,9 @@
380
382
  return Number.isFinite(number) && number >= 0 ? Math.floor(number) : 0;
381
383
  }
382
384
 
383
- function normalizeMaxOutputTokens(value) {
385
+ function normalizeMaxOutputTokens(value, legacy = false) {
384
386
  const number = normalizeInteger(value);
385
- if (!number || number === 8192 || number === 32768) return 65536;
387
+ if (!number || (legacy && (number === 8192 || number === 32768))) return 65536;
386
388
  return Math.min(65536, Math.max(256, number));
387
389
  }
388
390
 
@@ -77,6 +77,135 @@
77
77
  };
78
78
  }
79
79
 
80
+ const UNAVAILABLE_TTL_MS = 7 * 24 * 60 * 60 * 1000;
81
+ // Keep all upgrade-dialog selectors here: Overleaf DOM acceptance may recalibrate them.
82
+ const UPGRADE_MODAL_SELECTORS = Object.freeze({
83
+ body: '.upgrade-track-changes-modal',
84
+ modal: '.modal[role="dialog"], [role="dialog"][aria-modal="true"]',
85
+ close: '.modal-header button.btn-close, .modal-header button.close'
86
+ });
87
+
88
+ function visible(node) {
89
+ if (!node || !node.isConnected || node.hidden || node.getAttribute?.('aria-hidden') === 'true') return false;
90
+ const style = node.ownerDocument?.defaultView?.getComputedStyle(node);
91
+ return style?.display !== 'none' && style?.visibility !== 'hidden' && node.getClientRects().length > 0;
92
+ }
93
+
94
+ function findUpgradeModal(doc) {
95
+ for (const body of doc?.querySelectorAll(UPGRADE_MODAL_SELECTORS.body) || []) {
96
+ const modal = body.closest(UPGRADE_MODAL_SELECTORS.modal);
97
+ if (visible(body) && visible(modal)) return modal;
98
+ }
99
+ return null;
100
+ }
101
+
102
+ async function dismissUpgradeModal(modal, { delay, assertCurrent }) {
103
+ const isOpen = () => visible(modal);
104
+ const waitClosed = async () => {
105
+ for (let i = 0; i < 6 && isOpen(); i++) { await delay(80); assertCurrent(); }
106
+ };
107
+ assertCurrent();
108
+ const close = modal.querySelector(UPGRADE_MODAL_SELECTORS.close);
109
+ if (visible(close) && !close.disabled) close.click();
110
+ await waitClosed();
111
+ if (isOpen()) {
112
+ assertCurrent();
113
+ const KeyboardEvent = modal.ownerDocument.defaultView.KeyboardEvent;
114
+ modal.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', code: 'Escape', keyCode: 27, bubbles: true }));
115
+ await waitClosed();
116
+ }
117
+ return !isOpen();
118
+ }
119
+
120
+ // Page owns state/identity; this controller owns the click/poll/upgrade stop sequence.
121
+ async function toggleMode(enabled, params, deps) {
122
+ const { assertCurrent, delay, getState, isConfirmed, click, findControls, findOption } = deps;
123
+ let attempted = false;
124
+ const inspect = async () => {
125
+ assertCurrent();
126
+ const modal = findUpgradeModal(deps.document);
127
+ if (enabled && modal) {
128
+ const evidence = { upgradeModal: true, dismissed: await dismissUpgradeModal(modal, deps) };
129
+ return { ok: false, code: 'reviewing_unavailable', attempted, evidence,
130
+ failure: deps.buildFailure('reviewing_unavailable', {
131
+ operationType: 'ensureReviewing', changedDocument: false, evidence
132
+ }) };
133
+ }
134
+ const state = getState();
135
+ return { ok: isConfirmed(enabled, state), attempted, reviewing: state.reviewing };
136
+ };
137
+ const poll = async waitMs => {
138
+ let result = await inspect();
139
+ for (let elapsed = 0; !result.ok && !result.code && elapsed < waitMs; elapsed += 120) {
140
+ await delay(120);
141
+ result = await inspect();
142
+ }
143
+ return result;
144
+ };
145
+ const waitMs = Number.isFinite(Number(params.waitMs)) ? Math.max(0, Number(params.waitMs)) : 1600;
146
+ const menuWaitMs = Math.min(waitMs, 1200);
147
+ const selectVisibleOption = async () => {
148
+ const option = findOption(enabled);
149
+ if (!option) return null;
150
+ assertCurrent(); click(option); attempted = true;
151
+ return poll(waitMs);
152
+ };
153
+ let result = await inspect();
154
+ if (result.ok || result.code) return result;
155
+ // An open menu must be consumed before a toggle can close it.
156
+ let selected = await selectVisibleOption();
157
+ if (selected) return selected;
158
+ for (const control of [...new Set(findControls().filter(Boolean))]) {
159
+ result = await inspect();
160
+ if (result.ok || result.code) return result;
161
+ selected = await selectVisibleOption();
162
+ if (selected) return selected;
163
+ if (control.getAttribute?.('aria-expanded') !== 'true') {
164
+ assertCurrent(); click(control); attempted = true;
165
+ }
166
+ // Menu mounting and target-mode confirmation have separate bounded waits.
167
+ for (let elapsed = 0; ; ) {
168
+ result = await inspect();
169
+ if (result.ok || result.code) return result;
170
+ selected = await selectVisibleOption();
171
+ if (selected) return selected;
172
+ if (elapsed >= menuWaitMs) break;
173
+ const step = Math.min(100, menuWaitMs - elapsed);
174
+ await delay(step);
175
+ elapsed += step;
176
+ }
177
+ }
178
+ return inspect();
179
+ }
180
+
181
+ function knownUnavailable(record, now = Date.now()) {
182
+ return record?.status === 'unavailable' && Number.isFinite(record.observedAt)
183
+ && now >= record.observedAt && now - record.observedAt < UNAVAILABLE_TTL_MS;
184
+ }
185
+
186
+ // Content preflight invokes this before dispatch. Only explicit choices can lower the policy.
187
+ async function resolveWritePolicy({ requireReviewing, knownUnavailable: unavailable }, deps) {
188
+ deps.assertCurrent();
189
+ let result = unavailable && requireReviewing
190
+ ? { ok: false, code: 'reviewing_unavailable', evidence: { upgradeModal: true, remembered: true } }
191
+ : await deps.ensureMode(requireReviewing);
192
+ deps.assertCurrent();
193
+ if (requireReviewing && result.ok) await deps.observe('available', 'reviewing_confirmed');
194
+ if (requireReviewing && result.code === 'reviewing_unavailable') {
195
+ if (!unavailable) await deps.observe('unavailable', 'upgrade_modal');
196
+ deps.assertCurrent();
197
+ const choice = await deps.choose(result);
198
+ deps.assertCurrent();
199
+ if (choice !== 'continue' && choice !== 'disable') return { ...result, ok: false };
200
+ await deps.applyUntracked(choice);
201
+ deps.assertCurrent();
202
+ result = await deps.ensureMode(false);
203
+ deps.assertCurrent();
204
+ return { ...result, requireReviewing: false, trackingDecision: choice };
205
+ }
206
+ return { ...result, requireReviewing };
207
+ }
208
+
80
209
  function matchesControlReview(text) {
81
210
  return CONTROL_REVIEW_PATTERNS.some(pattern => pattern.test(text));
82
211
  }
@@ -94,6 +223,6 @@
94
223
  }
95
224
 
96
225
  return {
97
- detectReviewingFromSignals
226
+ detectReviewingFromSignals, findUpgradeModal, toggleMode, knownUnavailable, resolveWritePolicy, UNAVAILABLE_TTL_MS
98
227
  };
99
228
  });
@@ -86,6 +86,27 @@
86
86
  written: Number.isSafeInteger(fileCount) ? fileCount : undefined, output: version }, sanitize);
87
87
  }
88
88
 
89
+ function contextCompactionState(event = {}) {
90
+ if (event.activity?.kind === 'lifecycle' && event.activity.target === 'context_compaction') {
91
+ return event.activity.state === 'running' ? 'running' : 'completed';
92
+ }
93
+ const raw = event.technicalDetail || (event.kind === 'technical' ? event.detail : null) || event;
94
+ const type = text(raw.type);
95
+ const method = text(raw.detail?.method || (type === 'codex.session.event' ? raw.title : ''));
96
+ if (raw.detail?.params?.item?.type === 'contextCompaction') {
97
+ if (method === 'item/started') return 'running';
98
+ if (method === 'item/completed') return 'completed';
99
+ }
100
+ // The legacy completion notification can carry an incorrectly inferred
101
+ // `running` status. The protocol method is authoritative in both versions.
102
+ if (method === 'thread/compacted') return 'completed';
103
+ const label = type + ' ' + method;
104
+ if (!/(compact|compaction|compacted)/i.test(label)
105
+ || !/(context|thread|turn|conversation|codex)/i.test(label)) return '';
106
+ if (/(completed|compacted|finished)/i.test(label)) return 'completed';
107
+ return raw.status === 'running' || /(started|starting|begin|prepar)/i.test(label) ? 'running' : 'completed';
108
+ }
109
+
89
110
  function capture(event = {}, sanitize = text) {
90
111
  if (event.activity?.v === 1) return normalize(event.activity, sanitize);
91
112
  if (event.kind === 'report' || event.kind === 'guidance' || event.kind === 'stream') return undefined;
@@ -103,6 +124,10 @@
103
124
  const scope = JSON.stringify([thread, turn]);
104
125
  const id = itemId ? JSON.stringify([thread, turn, itemId]) : '';
105
126
  const base = { id, scope, state: event.status };
127
+ const compactState = contextCompactionState(event);
128
+ if (compactState) {
129
+ return normalize({ ...base, kind: 'lifecycle', target: 'context_compaction', state: compactState }, sanitize);
130
+ }
106
131
  if (NOTICE_SOURCES.has(method)) {
107
132
  return normalize({ ...base, kind: 'notice', noticeSource: method,
108
133
  output: text(params.details), state: event.status === 'failed' ? 'failed' : 'warning' }, sanitize);
@@ -219,13 +244,31 @@
219
244
  && /(?:^|[\/\s"';&|])(?:latexmk|pdflatex|xelatex|lualatex|bibtex|biber)(?=\s|["']|$)/i.test(meta.command);
220
245
  }
221
246
 
247
+ // Read the completion watermark without building a full timeline for every
248
+ // older run when the composer refreshes its context indicator.
249
+ function getCompactedAt(run = {}) {
250
+ return (Array.isArray(run.events) ? run.events : []).reduce((latest, event) => {
251
+ if (!event || event.kind === 'report') return latest;
252
+ const meta = event.activity?.v === 1 ? event.activity : capture(event);
253
+ return meta?.kind === 'lifecycle' && meta.target === 'context_compaction' && meta.state === 'completed'
254
+ ? Math.max(latest, Date.parse(event.timestamp || '') || 0) : latest;
255
+ }, 0);
256
+ }
257
+
222
258
  function project(run = {}, incoming, options = {}) {
223
259
  const saved = Array.isArray(run.events) ? run.events : [];
224
- const events = incoming && !saved.includes(incoming) ? saved.concat(incoming) : saved;
260
+ // Stream updates are saved before rendering; buffering and localization copy
261
+ // their objects. Keep the current saved event instead of its incoming copy.
262
+ const incomingStreamKey = incoming?.kind === 'stream' ? text(incoming.streamKey) : '';
263
+ const alreadySaved = incoming && saved.some(event => event === incoming
264
+ || (incomingStreamKey && event?.kind === 'stream' && event.streamKey === incomingStreamKey));
265
+ const events = incoming && !alreadySaved ? saved.concat(incoming) : saved;
225
266
  const ordered = [];
226
267
  const diagnostics = [];
227
268
  const diagnosticNotices = new Set();
228
269
  const byId = new Map();
270
+ const compactions = new Map();
271
+ let compactedAt = 0;
229
272
  const report = events.filter(event => event?.kind === 'report').at(-1);
230
273
  const finalText = text(report?.detailStructured?.conclusion
231
274
  || report?.detail?.Conclusion || report?.detail?.['结论']).trim();
@@ -296,6 +339,23 @@
296
339
  }
297
340
  const meta = event.activity?.v === 1 ? event.activity : capture(event);
298
341
  clipped ||= meta?.clipped === true;
342
+ if (meta?.kind === 'lifecycle' && meta.target === 'context_compaction') {
343
+ startWork(event);
344
+ const identity = meta.id || meta.scope;
345
+ if (meta.state !== 'running') {
346
+ if (meta.state === 'completed') compactedAt = Math.max(compactedAt, Date.parse(event.timestamp || '') || 0);
347
+ compactions.set(identity, { scope: meta.scope, running: false });
348
+ // Legacy completions have no item id and close the scope's active item.
349
+ if (!meta.id) for (const entry of compactions.values()) {
350
+ if (entry.scope === meta.scope) entry.running = false;
351
+ }
352
+ } else if (!meta.id || !compactions.has(identity)) {
353
+ compactions.set(identity, { scope: meta.scope, running: true });
354
+ }
355
+ diagnostics.push({ key, event });
356
+ if (stage === 'preparing') stage = 'working';
357
+ return;
358
+ }
299
359
  if (isDiagnosticNotice(event, meta)) {
300
360
  const noticeKey = JSON.stringify([meta?.noticeSource || '', event.title]);
301
361
  if (!diagnosticNotices.has(noticeKey)) diagnostics.push({ key, event });
@@ -312,7 +372,10 @@
312
372
  } else if (!meta && /^(?:Note: |提示:)/.test(text(event.title))) addSetupStep('note', event);
313
373
  if (!meta || meta.kind === 'lifecycle' || event.kind === 'technical') {
314
374
  diagnostics.push({ key, event });
315
- if (meta?.kind === 'lifecycle') stage = meta.target;
375
+ if (meta?.kind === 'lifecycle') {
376
+ stage = meta.target;
377
+ if (stage === 'local_completed') compactions.clear();
378
+ }
316
379
  return;
317
380
  }
318
381
  const identity = meta.id ? 'item:' + meta.scope + ':' + meta.id : 'event:' + key;
@@ -354,8 +417,11 @@
354
417
  }
355
418
  const legacy = diagnostics.some(({ event }) => event.kind !== 'technical' && !event.technicalDetail && !event.activity);
356
419
  if (stage === 'local_completed') startWork({});
357
- return { blocks, diagnostics, notices, stage, setup, fileCount: files.size, clipped, hasReport: Boolean(report), legacy };
420
+ const compacting = run.status === 'running' && stage !== 'local_completed'
421
+ && [...compactions.values()].some(entry => entry.running);
422
+ // Latest completed compaction in this run, in epoch milliseconds; 0 when none completed.
423
+ return { blocks, diagnostics, notices, stage, compacting, compactedAt, setup, fileCount: files.size, clipped, hasReport: Boolean(report), legacy };
358
424
  }
359
425
 
360
- return { normalize, capture, project, normalizePhase: phase, isRoutineCancellationEvent };
426
+ return { normalize, capture, project, getCompactedAt, contextCompactionState, normalizePhase: phase, isRoutineCancellationEvent };
361
427
  });