codex-overleaf-link 2.6.0 → 2.6.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/README.md +9 -9
  2. package/README.zh-CN.md +9 -9
  3. package/extension/bootstrap/manifest.template.json +1 -1
  4. package/extension/src/background.js +70 -0
  5. package/extension/src/content/composerAttachments.js +35 -8
  6. package/extension/src/content/contentRuntime.js +200 -51
  7. package/extension/src/content/diagnosticsController.js +77 -34
  8. package/extension/src/content/diagnosticsExport.js +1 -1
  9. package/extension/src/content/diagnosticsPanel.js +129 -42
  10. package/extension/src/content/generated/content.bundle.js +1283 -331
  11. package/extension/src/content/generated/content.bundle.meta.json +3 -3
  12. package/extension/src/content/localSkillsPanel.js +4 -2
  13. package/extension/src/content/markdownText.js +11 -9
  14. package/extension/src/content/modelPicker.js +28 -19
  15. package/extension/src/content/nativeCompatibilityController.js +23 -9
  16. package/extension/src/content/pageBridgeClient.js +5 -5
  17. package/extension/src/content/panelMaintenance.js +4 -0
  18. package/extension/src/content/panelRenderer.js +4 -1
  19. package/extension/src/content/providerSettingsDialog.js +26 -18
  20. package/extension/src/content/recentProjects.js +26 -16
  21. package/extension/src/content/runQueueScheduler.js +19 -9
  22. package/extension/src/content/runResultActions.js +51 -4
  23. package/extension/src/content/runReviewController.js +9 -3
  24. package/extension/src/content/scopedPersistencePanelState.js +34 -25
  25. package/extension/src/content/selectionContextView.js +30 -3
  26. package/extension/src/content/sharedSessionViewSync.js +9 -1
  27. package/extension/src/content/writebackOrchestrator.js +266 -50
  28. package/extension/src/content/writingStyleSettings.js +70 -17
  29. package/extension/src/page/overleafEditor.js +12 -3
  30. package/extension/src/page/overleafProjectSnapshot.js +56 -3
  31. package/extension/src/page/snapshotRouter.js +75 -66
  32. package/extension/src/page/treeOperations.js +200 -19
  33. package/extension/src/page/writebackRouter.js +152 -40
  34. package/extension/src/pageBridge.js +17 -7
  35. package/extension/src/shared/agentTranscript.js +23 -2
  36. package/extension/src/shared/auditRecords.js +1 -0
  37. package/extension/src/shared/compatibility.js +1 -1
  38. package/extension/src/shared/composerAttachmentDraft.js +22 -7
  39. package/extension/src/shared/failureReasons.js +15 -0
  40. package/extension/src/shared/i18n.js +16 -0
  41. package/extension/src/shared/pageRpcContract.js +1 -1
  42. package/extension/src/shared/projectFiles.js +2 -1
  43. package/extension/src/shared/scopedPersistenceBrowserAdapter.js +34 -0
  44. package/extension/src/shared/staleGuard.js +58 -0
  45. package/extension/src/shared/storageMigration.js +104 -1
  46. package/extension/styles/panel.css +191 -63
  47. package/native-host/src/codexSessionRunner.js +1 -0
  48. package/native-host/src/debugLog.js +27 -3
  49. package/native-host/src/index.js +42 -11
  50. package/native-host/src/localSkills.js +1 -1
  51. package/native-host/src/mirrorWorkspace.js +47 -3
  52. package/native-host/src/nativeEnvironment.js +27 -5
  53. package/native-host/src/providerRuntime.js +1 -0
  54. package/native-host/src/skills/reviewer-response/SKILL.md +65 -0
  55. package/native-host/src/taskRunnerRuntime.js +11 -3
  56. package/native-host/src/updateManager.js +24 -6
  57. package/native-host/src/writingStyleRuntime.js +1 -1
  58. package/package.json +1 -1
@@ -40,6 +40,11 @@ function buildNativeRuntimeEnv(baseEnv = process.env, options = {}) {
40
40
  for (const tool of TOOL_NAMES) {
41
41
  env[getToolEnvName(tool)] = resolveExecutable(tool, env.PATH, { ...runtimeOptions, env }) || '';
42
42
  }
43
+ const startupContext = getCodexRuntimeRefreshContext({
44
+ ...env,
45
+ CODEX_OVERLEAF_CODEX_PATH: explicitCodexPath || env.CODEX_OVERLEAF_CODEX_PATH,
46
+ CODEX_OVERLEAF_CODEX_RUNTIME_JSON: ''
47
+ }, runtimeOptions);
43
48
  const codexRuntime = buildCodexRuntimeIdentity({
44
49
  selectedPath: explicitCodexPath || env.CODEX_OVERLEAF_CODEX_PATH,
45
50
  selectionPolicy: explicitCodexPath ? 'explicit-path' : 'newest-version',
@@ -53,12 +58,20 @@ function buildNativeRuntimeEnv(baseEnv = process.env, options = {}) {
53
58
  env.CODEX_OVERLEAF_CODEX_VERSION = codexRuntime.selected?.version || '';
54
59
  env.CODEX_OVERLEAF_CODEX_SOURCE = codexRuntime.selected?.source || '';
55
60
 
61
+ const { key, pathValue, signatures } = getCodexRuntimeRefreshContext(env, runtimeOptions);
62
+ // Selection can reorder candidates, but discovery must not cache changed files.
63
+ if (pathValue === startupContext.pathValue
64
+ && signatures.length === startupContext.signatures.length
65
+ && signatures.every(signature => startupContext.signatures.some(before => JSON.stringify(before) === JSON.stringify(signature)))) {
66
+ cacheCodexRuntimeIdentity(key, codexRuntime);
67
+ }
68
+
56
69
  return env;
57
70
  }
58
71
 
59
72
  const codexRuntimeRefreshCache = new Map();
60
73
 
61
- function refreshCodexRuntimeEnv(baseEnv = process.env, options = {}) {
74
+ function getCodexRuntimeRefreshContext(baseEnv, options) {
62
75
  const runtimeOptions = { ...options, platform: getNativeRuntimePlatform({ ...options, env: baseEnv }) };
63
76
  const previous = getCodexRuntimeIdentityFromEnv(baseEnv);
64
77
  const selectedPath = previous.selected?.path || String(baseEnv.CODEX_OVERLEAF_CODEX_PATH || '');
@@ -78,6 +91,18 @@ function refreshCodexRuntimeEnv(baseEnv = process.env, options = {}) {
78
91
  } catch (_) { return [candidate, 'missing']; }
79
92
  });
80
93
  const key = JSON.stringify([runtimeOptions.platform, pinned ? selectedPath : '', pathValue, signatures]);
94
+ return { runtimeOptions, previous, selectedPath, pinned, pathValue, signatures, key };
95
+ }
96
+
97
+ function cacheCodexRuntimeIdentity(key, identity) {
98
+ codexRuntimeRefreshCache.set(key, { identity, createdAt: Date.now() });
99
+ while (codexRuntimeRefreshCache.size > 4) {
100
+ codexRuntimeRefreshCache.delete(codexRuntimeRefreshCache.keys().next().value);
101
+ }
102
+ }
103
+
104
+ function refreshCodexRuntimeEnv(baseEnv = process.env, options = {}) {
105
+ const { runtimeOptions, previous, selectedPath, pinned, pathValue, key } = getCodexRuntimeRefreshContext(baseEnv, options);
81
106
  const cached = codexRuntimeRefreshCache.get(key);
82
107
  let identity = cached?.identity;
83
108
  if (options.force || !cached || Date.now() - cached.createdAt > 30000) {
@@ -89,10 +114,7 @@ function refreshCodexRuntimeEnv(baseEnv = process.env, options = {}) {
89
114
  env: { ...baseEnv, PATH: pathValue },
90
115
  platform: runtimeOptions.platform
91
116
  });
92
- codexRuntimeRefreshCache.set(key, { identity, createdAt: Date.now() });
93
- while (codexRuntimeRefreshCache.size > 4) {
94
- codexRuntimeRefreshCache.delete(codexRuntimeRefreshCache.keys().next().value);
95
- }
117
+ cacheCodexRuntimeIdentity(key, identity);
96
118
  if (identity.selected?.path !== previous.selected?.path
97
119
  || identity.selected?.version !== previous.selected?.version) {
98
120
  logDebug('codex.runtime.refreshed', {
@@ -237,6 +237,7 @@ function resolveProviderModels(params = {}, env = process.env, resolveBuiltInMod
237
237
  providerId: profile.id,
238
238
  providerRevision: profile.revision,
239
239
  providerName: profile.name,
240
+ defaultModelId: profile.defaultModelId,
240
241
  models: profile.models.map(model => {
241
242
  const reasoning = getReasoningControl(profile, model);
242
243
  return {
@@ -0,0 +1,65 @@
1
+ ---
2
+ name: reviewer-response
3
+ description: >
4
+ Use only when the user asks to respond to peer-review or reviewer comments,
5
+ write a rebuttal or response letter, or plan a revision from reviews.
6
+ ---
7
+
8
+ # Reviewer Response
9
+
10
+ Map reviewer comments to evidence in the paper, proposed edits, and reply
11
+ drafts. Keep missing evidence visible instead of filling it with assumptions.
12
+
13
+ ## Workflow
14
+
15
+ 1. Split the reviews into atomic comments with stable IDs R<reviewer>.<n>,
16
+ such as R1.2. Keep the reviewer's own numbering when present; retain the
17
+ original label when splitting a numbered comment into smaller items.
18
+ 2. For each comment, look for evidence in the project files. Cite actual
19
+ source lines as plain-text path:line or path:start-end, for example
20
+ main.tex:212-230. Do not wrap references in backticks: the panel turns
21
+ plain-text references into clickable links. If evidence is absent, write
22
+ "Not found"; distinguish files not read from files searched without a match.
23
+ 3. Output a compact ledger table with these columns:
24
+
25
+ ID | Comment | Evidence in the paper | Proposed change | Status
26
+
27
+ Keep cells short for the narrow panel, with each Comment at most about
28
+ 12 words. Put necessary explanations below the table. Use only these
29
+ statuses, adding a short reason:
30
+ - Ready to edit: the evidence supports a specific local edit.
31
+ - Needs an experiment: new experimental evidence is required.
32
+ - Needs author input: facts or a decision must come from the author.
33
+ - Unresolved: the comment remains open for another stated reason.
34
+ - No change: explain why the current paper already addresses the comment
35
+ or why a change is unwarranted.
36
+
37
+ Write the table and explanations in the user's language, translating
38
+ headings, statuses, and "Not found" accordingly. Keep the IDs unchanged.
39
+ 4. After the ledger, draft replies only for the comments the user asked
40
+ about, or for every comment when they requested a full response letter.
41
+ Write reply drafts in English unless the user asks otherwise. Label each
42
+ draft with its comment ID and distinguish proposed work from completed work.
43
+ 5. Finish with one "Next:" line, localized to the user's language, explaining
44
+ how to apply approved items. For example: Next: after approving R1.2,
45
+ switch to Auto and send "Apply R1.2".
46
+
47
+ ## Integrity rules
48
+
49
+ - Never invent results, numbers, experiments, citations, or completed changes.
50
+ - Anything needed for a response that is not present in the paper or provided
51
+ by the user must be marked Needs an experiment or Needs author input.
52
+ - Reply drafts must not promise results. State missing evidence or pending
53
+ author input explicitly; do not describe planned edits as already made.
54
+ - Cite only files and lines actually inspected. User-provided facts are not
55
+ evidence already in the paper; identify their source separately.
56
+
57
+ ## Editing rules
58
+
59
+ - In Ask mode, never edit files.
60
+ - In Auto mode, edit only the comment IDs the user explicitly approves.
61
+ Keep edits minimal and local, leaving unrelated text untouched. An approved
62
+ ID does not authorize inventing missing evidence or results.
63
+ - After editing, report which IDs changed with plain-text path:line or
64
+ path:start-end references. Keep pending items separate and do not claim
65
+ an edit, save, or experiment completed without evidence.
@@ -50,7 +50,7 @@ const activeRunEntries = new Map();
50
50
  // Overleaf tab was reloaded — the requestId lived in content-side JS state
51
51
  // and is gone, but the native-host-side controller is still running).
52
52
  const activeRunByProject = new Map();
53
- const CODEX_RUN_PASSTHROUGH_ERROR_CODES = new Set(['thread_resume_failed', 'codex_no_usable_result', 'codex_context_limit', 'codex_process_stop_unconfirmed', 'selected_context_unresolved', 'codex_local_skill_unavailable']);
53
+ const CODEX_RUN_PASSTHROUGH_ERROR_CODES = new Set(['thread_resume_failed', 'codex_no_usable_result', 'codex_context_limit', 'codex_process_stop_unconfirmed', 'selected_context_unresolved', 'codex_local_skill_unavailable', 'document_identity_mismatch']);
54
54
 
55
55
  async function handleRequest(request, env = process.env, emit = () => {}) {
56
56
  if (!request || typeof request !== 'object') {
@@ -308,7 +308,7 @@ async function handleCodexRun(request, env, emit) {
308
308
  // OT overlays prove that the current page agrees with the trusted OT
309
309
  // baseline; they must not rewrite it or supply a replacement hash.
310
310
  if (!isOtWarmMirrorReuseRequest(params) && Array.isArray(params.fileOverlays) && params.fileOverlays.length) {
311
- await applyFileOverlays({ projectId: projectKey, overlays: params.fileOverlays, rootDir });
311
+ await applyFileOverlays({ projectId: projectKey, evidenceProjectId: params.projectId, overlays: params.fileOverlays, rootDir });
312
312
  }
313
313
  } else if (params.operation !== 'compact' && !params.writingStyleBuild && !isSnapshotlessSkillInstallerRun(params) && !hasRunnableProjectSnapshotEvidence(params)) {
314
314
  return errorResponse(
@@ -456,6 +456,13 @@ function handleCodexCancel(request, env = process.env) {
456
456
  });
457
457
  }
458
458
 
459
+ if (targetId != null && !force) {
460
+ return okResponse(request.id, {
461
+ cancelled: false,
462
+ reason: 'request_not_active'
463
+ });
464
+ }
465
+
459
466
  if (projectKey) {
460
467
  const entry = activeRunByProject.get(projectKey);
461
468
  if (entry?.controller) {
@@ -733,6 +740,7 @@ async function handleMirrorSync(request, env) {
733
740
  try {
734
741
  const result = await syncOverleafToMirror({
735
742
  projectId,
743
+ evidenceProjectId: params.projectId,
736
744
  project: params.project || { files: [] },
737
745
  rootDir
738
746
  });
@@ -769,7 +777,7 @@ async function handleMirrorPatchFiles(request, env) {
769
777
  });
770
778
  return okResponse(request.id, result);
771
779
  } catch (error) {
772
- return errorResponse(request.id, 'mirror_patch_files_failed', error.message);
780
+ return errorResponse(request.id, error.code === 'document_identity_mismatch' ? error.code : 'mirror_patch_files_failed', error.message);
773
781
  } finally {
774
782
  releaseProjectLock(projectKey, lockToken);
775
783
  }
@@ -999,12 +999,19 @@ function rollbackFiles(context, journal, options = {}) {
999
999
  const runtime = path.join(context.extensionRoot, 'runtime');
1000
1000
  const previousRuntime = path.join(context.extensionRoot, 'slots', 'previous', 'runtime');
1001
1001
  // An early interrupted apply may still have the source in the current slot.
1002
- // A missing previous slot is recoverable only when these actual bytes match
1003
- // the source pair; the source manifest and markers alone are insufficient.
1004
- const source = readManagedRuntimeEvidence(context, journal.sourceVersion,
1005
- fs.existsSync(previousRuntime) ? previousRuntime : runtime);
1002
+ // A missing or stale previous slot is recoverable only when these actual
1003
+ // bytes match the source pair; manifest and markers alone are insufficient.
1004
+ let sourceRuntime = fs.existsSync(previousRuntime) ? previousRuntime : runtime;
1005
+ let source;
1006
+ try {
1007
+ source = readManagedRuntimeEvidence(context, journal.sourceVersion, sourceRuntime);
1008
+ } catch (error) {
1009
+ if (journal.state !== 'applying' || sourceRuntime !== previousRuntime) throw error;
1010
+ source = readManagedRuntimeEvidence(context, journal.sourceVersion, runtime);
1011
+ sourceRuntime = runtime;
1012
+ }
1006
1013
  if (journal.bootstrapMigration) BootstrapUpdate.restoreBootstrap(context, journal);
1007
- if (fs.existsSync(previousRuntime)) {
1014
+ if (sourceRuntime === previousRuntime) {
1008
1015
  fs.rmSync(runtime, { recursive: true, force: true });
1009
1016
  fs.renameSync(previousRuntime, runtime);
1010
1017
  fs.rmSync(path.dirname(previousRuntime), { recursive: true, force: true });
@@ -1122,7 +1129,18 @@ function recoverAndReadStatus(context, params = {}) {
1122
1129
  cleanupStage(journal.stageRoot);
1123
1130
  }
1124
1131
  }
1125
- return currentStatus(context, journal);
1132
+ const status = currentStatus(context, journal);
1133
+ if (journal?.state === 'rolled_back' && !journal.bootstrapMigration && status.restoredPair) {
1134
+ const authorization = readAuthorization(context);
1135
+ if (['bound', 'consumed'].includes(authorization?.state) && authorization.id === journal.authorizationId
1136
+ && authorization.transactionId === journal.id && authorization.sourceVersion === journal.sourceVersion
1137
+ && authorization.targetVersion === journal.targetVersion) {
1138
+ settleAuthorization(context, journal.authorizationId, 'revoked');
1139
+ status.authorization = publicAuthorization(readAuthorization(context));
1140
+ }
1141
+ cleanupStage(journal.stageRoot);
1142
+ }
1143
+ return status;
1126
1144
  }
1127
1145
 
1128
1146
  function finishSourceActivation(context, journal) {
@@ -374,7 +374,7 @@ async function build({ params, env, signal, execute, providerLaunch, onControlRe
374
374
  reasoningEffort: params.reasoningEffort || '', speedTier: params.speedTier || 'standard',
375
375
  sandboxMode: 'read-only', approvalPolicy: 'never',
376
376
  loadCodexLocalSkills: false, loadCodexOverleafSkills: false,
377
- disableCodexOverleafSkillIds: ['annotated-rewrite', 'parallel-subagents'],
377
+ disableCodexOverleafSkillIds: ['annotated-rewrite', 'parallel-subagents', 'reviewer-response'],
378
378
  skillInvocation: null, projectLocalSkills: null, providerLaunch, env, signal,
379
379
  onControlReady, emit: () => {}
380
380
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codex-overleaf-link",
3
- "version": "2.6.0",
3
+ "version": "2.6.5",
4
4
  "description": "Cross-platform Chrome bridge that connects Codex to the active Overleaf project.",
5
5
  "license": "MIT",
6
6
  "type": "commonjs",