codex-overleaf-link 1.8.6 → 1.9.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 (72) hide show
  1. package/README.md +45 -21
  2. package/bin/codex-overleaf-link.mjs +58 -0
  3. package/extension/assets/icons/codex-overleaf-dialog-icon.png +0 -0
  4. package/extension/assets/icons/codex-overleaf-icon.png +0 -0
  5. package/extension/assets/icons/icon128.png +0 -0
  6. package/extension/assets/icons/icon16.png +0 -0
  7. package/extension/assets/icons/icon32.png +0 -0
  8. package/extension/assets/icons/icon48.png +0 -0
  9. package/extension/bootstrap/background.js +404 -0
  10. package/extension/bootstrap/manifest.template.json +51 -0
  11. package/extension/bootstrap/popup.css +26 -0
  12. package/extension/bootstrap/popup.html +38 -0
  13. package/extension/bootstrap/popup.js +44 -0
  14. package/extension/bootstrap/runtimeContext.js +2 -0
  15. package/extension/runtime-manifest.json +60 -0
  16. package/extension/src/background.js +532 -0
  17. package/extension/src/content/applyResultFormatters.js +387 -0
  18. package/extension/src/content/composerAttachments.js +522 -0
  19. package/extension/src/content/composerPanel.js +254 -0
  20. package/extension/src/content/contentRuntime.js +8895 -0
  21. package/extension/src/content/contextTray.js +686 -0
  22. package/extension/src/content/diagnosticsController.js +659 -0
  23. package/extension/src/content/diagnosticsPanel.js +342 -0
  24. package/extension/src/content/diffReviewPanel.js +797 -0
  25. package/extension/src/content/localSkillsPanel.js +280 -0
  26. package/extension/src/content/markdownText.js +606 -0
  27. package/extension/src/content/mirrorHealth.js +92 -0
  28. package/extension/src/content/modelPicker.js +489 -0
  29. package/extension/src/content/nativeChannel.js +62 -0
  30. package/extension/src/content/otWarmMirror.js +964 -0
  31. package/extension/src/content/otWarmMirrorController.js +174 -0
  32. package/extension/src/content/panelMaintenance.js +367 -0
  33. package/extension/src/content/panelRenderer.js +253 -0
  34. package/extension/src/content/recentProjects.js +1063 -0
  35. package/extension/src/content/reviewHunks.js +247 -0
  36. package/extension/src/content/runController.js +242 -0
  37. package/extension/src/content/runTimelineView.js +1126 -0
  38. package/extension/src/content/sessionManager.js +487 -0
  39. package/extension/src/content/sessionPanel.js +263 -0
  40. package/extension/src/content/settingsPanel.js +527 -0
  41. package/extension/src/content/themeController.js +91 -0
  42. package/extension/src/content/updateIdle.js +67 -0
  43. package/extension/src/content/writebackController.js +300 -0
  44. package/extension/src/content/writebackOrchestrator.js +850 -0
  45. package/extension/src/contentScript.js +8 -0
  46. package/extension/src/page/compileBridge.js +460 -0
  47. package/extension/src/page/overleafCapabilities.js +100 -0
  48. package/extension/src/page/overleafEditor.js +282 -0
  49. package/extension/src/page/overleafProjectSnapshot.js +234 -0
  50. package/extension/src/page/overleafRealtimeObserver.js +491 -0
  51. package/extension/src/page/pageBridgeCapability.js +71 -0
  52. package/extension/src/page/snapshotRouter.js +948 -0
  53. package/extension/src/page/trackedChangesLifecycle.js +1506 -0
  54. package/extension/src/page/treeOperations.js +1584 -0
  55. package/extension/src/page/writeGuard.js +142 -0
  56. package/extension/src/page/writebackRouter.js +1934 -0
  57. package/extension/src/pageBridge.js +2224 -0
  58. package/extension/src/popup.js +247 -0
  59. package/extension/src/shared/compatibility.js +1 -1
  60. package/extension/styles/panel.css +4048 -0
  61. package/native-host/src/index.js +9 -2
  62. package/native-host/src/managedInstall.js +393 -0
  63. package/native-host/src/managedLauncherRuntime.js +71 -0
  64. package/native-host/src/nativeHostPlatform.js +16 -0
  65. package/native-host/src/runtimeInstaller.js +6 -1
  66. package/native-host/src/taskRunnerRuntime.js +8 -0
  67. package/native-host/src/updateArchive.js +253 -0
  68. package/native-host/src/updateManager.js +548 -0
  69. package/native-host/src/updateTrust.js +156 -0
  70. package/package.json +11 -2
  71. package/scripts/install-managed.mjs +64 -0
  72. package/scripts/uninstall-managed.mjs +48 -0
@@ -0,0 +1,247 @@
1
+ (function initCodexOverleafReviewHunks(root, factory) {
2
+ if (typeof module === 'object' && module.exports) {
3
+ module.exports = factory();
4
+ } else {
5
+ root.CodexOverleafReviewHunks = factory();
6
+ }
7
+ })(typeof window !== 'undefined' ? window : globalThis, function reviewHunksFactory() {
8
+ 'use strict';
9
+
10
+ function buildReviewModel(syncChanges = [], options = {}) {
11
+ const files = [];
12
+ const hunks = [];
13
+
14
+ for (const [changeIndex, change] of (syncChanges || []).entries()) {
15
+ const path = normalizePath(change?.path);
16
+ if (!path) {
17
+ continue;
18
+ }
19
+
20
+ const file = {
21
+ index: changeIndex,
22
+ type: change?.type,
23
+ path,
24
+ decisionKey: normalizeReviewDecisionKey(path),
25
+ reviewable: false,
26
+ hunks: []
27
+ };
28
+
29
+ if (isHunkReviewableChange(change, options)) {
30
+ file.reviewable = true;
31
+ file.hunks = normalizePatches(change.patches).map((patch, patchIndex) => {
32
+ const id = `${path}:hunk:${patchIndex}`;
33
+ return {
34
+ id,
35
+ path,
36
+ patchIndexes: [patchIndex],
37
+ startOffset: patch.from,
38
+ endOffset: patch.to,
39
+ startLine: getPatchStartLine(change, patchIndex),
40
+ lineCount: getPatchLineCount(patch),
41
+ canApplyIndependently: true,
42
+ truncated: false,
43
+ decisionKey: normalizeReviewDecisionKey(path, id)
44
+ };
45
+ });
46
+ hunks.push(...file.hunks);
47
+ }
48
+
49
+ files.push(file);
50
+ }
51
+
52
+ return {
53
+ files,
54
+ hunks
55
+ };
56
+ }
57
+
58
+ function buildAcceptedSyncChanges(syncChanges = [], decisions = {}) {
59
+ const model = buildReviewModel(syncChanges);
60
+ const filesByIndex = new Map(model.files.map(file => [file.index, file]));
61
+ const accepted = [];
62
+
63
+ for (const [changeIndex, change] of (syncChanges || []).entries()) {
64
+ const file = filesByIndex.get(changeIndex);
65
+ if (!file) {
66
+ continue;
67
+ }
68
+
69
+ if (!file.reviewable) {
70
+ if (isAcceptedDecision(decisions[file.decisionKey])) {
71
+ accepted.push(cloneSyncChange(change));
72
+ }
73
+ continue;
74
+ }
75
+
76
+ const acceptedPatchIndexes = [];
77
+ for (const hunk of file.hunks) {
78
+ if (isAcceptedDecision(decisions[hunk.decisionKey])) {
79
+ acceptedPatchIndexes.push(...hunk.patchIndexes);
80
+ }
81
+ }
82
+
83
+ if (acceptedPatchIndexes.length) {
84
+ const patches = normalizePatches(change.patches)
85
+ .filter((patch, patchIndex) => acceptedPatchIndexes.includes(patchIndex));
86
+ const clone = cloneSyncChange(change);
87
+ clone.patches = patches.map(patch => ({ ...patch }));
88
+ delete clone.replaceAll;
89
+ accepted.push(clone);
90
+ }
91
+ }
92
+
93
+ return accepted;
94
+ }
95
+
96
+ function summarizeReviewModel(model = {}) {
97
+ const files = Array.isArray(model.files) ? model.files : [];
98
+ return {
99
+ files: files.length,
100
+ hunks: files.reduce((count, file) => count + (Array.isArray(file.hunks) ? file.hunks.length : 0), 0),
101
+ fallbackFiles: files.filter(file => !file.reviewable).length,
102
+ reviewableFiles: files.filter(file => file.reviewable).length
103
+ };
104
+ }
105
+
106
+ function normalizeReviewDecisionKey(path, hunkId) {
107
+ const normalizedPath = normalizePath(path);
108
+ return `${normalizedPath || 'unknown'}::${hunkId || 'file'}`;
109
+ }
110
+
111
+ function isHunkReviewableChange(change = {}, options = {}) {
112
+ if (change.type !== 'write') {
113
+ return false;
114
+ }
115
+ if (hasTruncatedDisplayDiff(change)) {
116
+ return false;
117
+ }
118
+ const patches = Array.isArray(change.patches) ? change.patches : [];
119
+ if (!patches.length) {
120
+ return false;
121
+ }
122
+ return normalizePatches(patches).length === patches.length;
123
+ }
124
+
125
+ function hasTruncatedDisplayDiff(change = {}) {
126
+ if (!Array.isArray(change.diff)) {
127
+ return false;
128
+ }
129
+ return change.diff.some(hunk => hunk?.truncated === true);
130
+ }
131
+
132
+ function normalizePatches(patches = []) {
133
+ const normalized = [];
134
+ for (const patch of patches || []) {
135
+ const from = Number(patch?.from);
136
+ const to = Number(patch?.to);
137
+ if (!Number.isInteger(from) || !Number.isInteger(to) || from < 0 || to < from) {
138
+ continue;
139
+ }
140
+ normalized.push({
141
+ from,
142
+ to,
143
+ expected: String(patch.expected ?? ''),
144
+ insert: String(patch.insert ?? '')
145
+ });
146
+ }
147
+ return normalized;
148
+ }
149
+
150
+ function getPatchStartLine(change = {}, patchIndex = 0) {
151
+ const diffHunk = Array.isArray(change.diff) ? change.diff[patchIndex] : null;
152
+ const startB = Number(diffHunk?.startB);
153
+ if (Number.isInteger(startB) && startB > 0) {
154
+ return startB;
155
+ }
156
+ return undefined;
157
+ }
158
+
159
+ function getPatchLineCount(patch = {}) {
160
+ const text = String(patch.insert ?? patch.expected ?? '');
161
+ if (!text) {
162
+ return 1;
163
+ }
164
+ return Math.max(1, text.split('\n').length);
165
+ }
166
+
167
+ function normalizePath(path) {
168
+ return String(path || '').replace(/\\/g, '/').trim().replace(/^\/+/, '');
169
+ }
170
+
171
+ function isAcceptedDecision(decision) {
172
+ return decision === true || decision === 'accepted' || decision === 'accept';
173
+ }
174
+
175
+ function isRejectedDecision(decision) {
176
+ return decision === false || decision === 'rejected' || decision === 'reject';
177
+ }
178
+
179
+ // v1.7.5: summaries of what the user rejected during review, so the
180
+ // completion report can offer a one-click "redo the rejected changes"
181
+ // retry that quotes the location and a snippet of each dropped hunk.
182
+ function buildRejectedHunkSummaries(syncChanges = [], decisions = {}) {
183
+ const model = buildReviewModel(syncChanges);
184
+ const filesByIndex = new Map(model.files.map(file => [file.index, file]));
185
+ const rejected = [];
186
+ for (const [changeIndex, change] of (syncChanges || []).entries()) {
187
+ const file = filesByIndex.get(changeIndex);
188
+ if (!file) {
189
+ continue;
190
+ }
191
+ if (!file.reviewable) {
192
+ if (isRejectedDecision(decisions[file.decisionKey])) {
193
+ rejected.push({ path: file.path, summary: '' });
194
+ }
195
+ continue;
196
+ }
197
+ const patches = normalizePatches(change.patches);
198
+ for (const hunk of file.hunks) {
199
+ if (!isRejectedDecision(decisions[hunk.decisionKey])) {
200
+ continue;
201
+ }
202
+ const patch = patches[hunk.patchIndexes[0]] || {};
203
+ const snippet = String(patch.insert || '').trim().replace(/\s+/g, ' ').slice(0, 80);
204
+ rejected.push({
205
+ path: file.path,
206
+ summary: `${hunk.startLine ? `L${hunk.startLine}` : ''}${hunk.startLine && snippet ? ': ' : ''}${snippet}`
207
+ });
208
+ }
209
+ }
210
+ return rejected;
211
+ }
212
+
213
+ function cloneSyncChange(change) {
214
+ if (!change || typeof change !== 'object') {
215
+ return change;
216
+ }
217
+ const clone = { ...change };
218
+ if (Array.isArray(change.diff)) {
219
+ clone.diff = cloneArrayObjects(change.diff);
220
+ }
221
+ if (Array.isArray(change.patches)) {
222
+ clone.patches = cloneArrayObjects(change.patches);
223
+ }
224
+ return clone;
225
+ }
226
+
227
+ function cloneArrayObjects(items) {
228
+ return items.map(item => {
229
+ if (!item || typeof item !== 'object') {
230
+ return item;
231
+ }
232
+ const clone = { ...item };
233
+ if (Array.isArray(item.lines)) {
234
+ clone.lines = cloneArrayObjects(item.lines);
235
+ }
236
+ return clone;
237
+ });
238
+ }
239
+
240
+ return {
241
+ buildAcceptedSyncChanges,
242
+ buildRejectedHunkSummaries,
243
+ buildReviewModel,
244
+ normalizeReviewDecisionKey,
245
+ summarizeReviewModel
246
+ };
247
+ });
@@ -0,0 +1,242 @@
1
+ (function initCodexOverleafRunController(root, factory) {
2
+ if (typeof module === 'object' && module.exports) {
3
+ module.exports = factory();
4
+ } else {
5
+ root.CodexOverleafRunController = factory();
6
+ }
7
+ })(typeof window !== 'undefined' ? window : globalThis, function runControllerFactory() {
8
+ 'use strict';
9
+
10
+ const DEFAULT_EXPECTED_MIRROR_FRESHNESS_MS = 5 * 60 * 1000;
11
+
12
+ function buildCodexRunParams({
13
+ currentProjectId,
14
+ state = {},
15
+ task,
16
+ project,
17
+ useExistingMirror,
18
+ fileOverlays,
19
+ focusFiles,
20
+ otWarmStart,
21
+ codexThreadId,
22
+ customInstructions,
23
+ skillLoadingSettings,
24
+ enabledCodexOverleafSkillIds,
25
+ attachments,
26
+ skillInvocation,
27
+ compileLogContext,
28
+ submittedMode,
29
+ restrictToFocusFiles
30
+ } = {}) {
31
+ const normalizedCustomInstructions = String(customInstructions || '').trim();
32
+ const normalizedAttachments = normalizeComposerAttachments(attachments);
33
+ const normalizedSkillInvocation = normalizeSkillInvocation(skillInvocation);
34
+ const normalizedEnabledSkillIds = Array.isArray(enabledCodexOverleafSkillIds)
35
+ ? enabledCodexOverleafSkillIds.filter(id => typeof id === 'string' && id.trim())
36
+ : undefined;
37
+ const params = {
38
+ projectId: currentProjectId,
39
+ mode: submittedMode || state.mode,
40
+ task,
41
+ project: useExistingMirror ? undefined : project,
42
+ useExistingMirror: useExistingMirror || undefined,
43
+ fileOverlays: useExistingMirror ? fileOverlays : undefined,
44
+ expectedMirrorFreshness: useExistingMirror ? DEFAULT_EXPECTED_MIRROR_FRESHNESS_MS : undefined,
45
+ otWarmStart: useExistingMirror && otWarmStart ? true : undefined,
46
+ warmStartStrategy: useExistingMirror && otWarmStart ? 'ot-warm-mirror' : undefined,
47
+ focusFiles,
48
+ restrictToFocusFiles: restrictToFocusFiles || undefined,
49
+ model: state.model,
50
+ reasoningEffort: state.reasoningEffort,
51
+ speedTier: state.speedTier,
52
+ session: state.session,
53
+ threadId: codexThreadId || undefined,
54
+ customInstructions: normalizedCustomInstructions || undefined,
55
+ loadCodexLocalSkills: skillLoadingSettings?.loadCodexLocalSkills !== false,
56
+ loadCodexOverleafSkills: skillLoadingSettings?.loadCodexOverleafSkills !== false,
57
+ enabledCodexOverleafSkillIds: normalizedEnabledSkillIds,
58
+ skillInvocation: normalizedSkillInvocation || undefined,
59
+ attachments: normalizedAttachments.length ? normalizedAttachments : undefined
60
+ };
61
+
62
+ if (compileLogContext?.available) {
63
+ params.compileLog = compileLogContext.log;
64
+ params.compileErrors = compileLogContext.errors || [];
65
+ params.compileWarnings = compileLogContext.warnings || [];
66
+ params.compileLogFresh = compileLogContext.fresh;
67
+ params.compileLogCompiledAt = compileLogContext.compiledAt;
68
+ }
69
+
70
+ return params;
71
+ }
72
+
73
+ function buildSessionHistoryResult({ assistantMessage = '', syncOutcome = {}, syncChanges = [], locale = 'zh' } = {}) {
74
+ const parts = [];
75
+ const finalAnswer = truncateSessionHistoryText(assistantMessage, 1600);
76
+ if (finalAnswer) {
77
+ parts.push(finalAnswer);
78
+ }
79
+
80
+ const changedFiles = Array.from(new Set((syncChanges || [])
81
+ .map(change => change?.path)
82
+ .filter(Boolean)));
83
+ if (changedFiles.length) {
84
+ const visibleFiles = changedFiles.slice(0, 8).join(', ');
85
+ parts.push(locale === 'en'
86
+ ? `Files changed: ${visibleFiles}${changedFiles.length > 8 ? ' and more' : ''}`
87
+ : `涉及文件:${visibleFiles}${changedFiles.length > 8 ? ' 等' : ''}`);
88
+ }
89
+
90
+ if (!parts.length) {
91
+ return truncateSessionHistoryText(syncOutcome.summaryLine || 'completed', 1600);
92
+ }
93
+ return truncateSessionHistoryText(parts.join('\n'), 2000);
94
+ }
95
+
96
+ function truncateSessionHistoryText(value, maxLength) {
97
+ const text = String(value || '').replace(/\s+/g, ' ').trim();
98
+ if (!text || text.length <= maxLength) {
99
+ return text;
100
+ }
101
+ return `${text.slice(0, Math.max(0, maxLength - 1))}…`;
102
+ }
103
+
104
+ function canUseFocusedPartialSnapshot({
105
+ project = {},
106
+ snapshotWarnings = {},
107
+ focusFiles = [],
108
+ isUsableProjectFileContent = defaultIsUsableProjectFileContent
109
+ } = {}) {
110
+ const blocking = Array.isArray(snapshotWarnings.blocking) ? snapshotWarnings.blocking : [];
111
+ if (!blocking.length || !blocking.some(isFullSnapshotWarning)) {
112
+ return false;
113
+ }
114
+ const normalizedFocusFiles = normalizeFocusFiles(focusFiles);
115
+ if (!normalizedFocusFiles.length) {
116
+ return false;
117
+ }
118
+ if (project?.capabilities?.fullProjectSnapshot !== false) {
119
+ return false;
120
+ }
121
+
122
+ const textFilesByPath = new Map((project.files || [])
123
+ .filter(file => file && typeof file.path === 'string' && typeof file.content === 'string')
124
+ .map(file => [normalizeFocusPath(file.path), file]));
125
+
126
+ return normalizedFocusFiles.every(filePath => {
127
+ const file = textFilesByPath.get(normalizeFocusPath(filePath));
128
+ return file && isUsableProjectFileContent(file.content);
129
+ });
130
+ }
131
+
132
+ function shouldRestrictWritebackToFocus({ focusFiles = [] } = {}) {
133
+ return normalizeFocusFiles(focusFiles).length > 0;
134
+ }
135
+
136
+ function isFullSnapshotWarning(warning) {
137
+ return /Full project snapshot was not captured/i.test(String(warning || ''));
138
+ }
139
+
140
+ function normalizeFocusFiles(value) {
141
+ const seen = new Set();
142
+ const files = [];
143
+ for (const item of Array.isArray(value) ? value : []) {
144
+ const filePath = normalizeFocusPath(item);
145
+ if (!filePath || seen.has(filePath)) {
146
+ continue;
147
+ }
148
+ seen.add(filePath);
149
+ files.push(filePath);
150
+ }
151
+ return files;
152
+ }
153
+
154
+ function normalizeFocusPath(value) {
155
+ return String(value || '')
156
+ .replace(/^@file:/i, '')
157
+ .replace(/\\/g, '/')
158
+ .trim()
159
+ .replace(/^\/+/, '');
160
+ }
161
+
162
+ function normalizeComposerAttachments(value) {
163
+ const result = [];
164
+ for (const item of Array.isArray(value) ? value : []) {
165
+ const name = normalizeAttachmentName(item?.name);
166
+ const contentBase64 = normalizeAttachmentBase64(item?.contentBase64);
167
+ if (!name || !contentBase64) {
168
+ continue;
169
+ }
170
+ const size = Number(item?.size);
171
+ result.push({
172
+ name,
173
+ mimeType: String(item?.mimeType || '').trim().slice(0, 120),
174
+ size: Number.isFinite(size) && size >= 0 ? size : estimateBase64Size(contentBase64),
175
+ contentBase64
176
+ });
177
+ if (result.length >= 8) {
178
+ break;
179
+ }
180
+ }
181
+ return result;
182
+ }
183
+
184
+ function normalizeSkillInvocation(value) {
185
+ const id = String(value?.id || '').trim();
186
+ if (!isSafeSkillId(id)) {
187
+ return null;
188
+ }
189
+ const title = String(value?.title || 'Skill Installer').trim().slice(0, 80) || 'Skill Installer';
190
+ if (id === 'skill-installer') {
191
+ return { id, title };
192
+ }
193
+ if (value?.scope !== 'codex-overleaf') {
194
+ return null;
195
+ }
196
+ return { id, title, scope: 'codex-overleaf' };
197
+ }
198
+
199
+ function isSafeSkillId(id) {
200
+ return /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,79}$/.test(String(id || ''))
201
+ && !String(id || '').includes('..');
202
+ }
203
+
204
+ function normalizeAttachmentName(value) {
205
+ return String(value || '')
206
+ .replace(/\0/g, '')
207
+ .replace(/\\/g, '/')
208
+ .split('/')
209
+ .filter(Boolean)
210
+ .pop()
211
+ ?.trim()
212
+ .slice(0, 180) || '';
213
+ }
214
+
215
+ function normalizeAttachmentBase64(value) {
216
+ const clean = String(value || '').replace(/\s+/g, '');
217
+ if (!clean || !/^[A-Za-z0-9+/]+={0,2}$/.test(clean)) {
218
+ return '';
219
+ }
220
+ return clean;
221
+ }
222
+
223
+ function estimateBase64Size(value) {
224
+ const clean = String(value || '').replace(/\s+/g, '');
225
+ const padding = clean.endsWith('==') ? 2 : clean.endsWith('=') ? 1 : 0;
226
+ return Math.max(0, Math.floor(clean.length * 3 / 4) - padding);
227
+ }
228
+
229
+ function defaultIsUsableProjectFileContent(content) {
230
+ return String(content || '').trim().length > 0;
231
+ }
232
+
233
+ return {
234
+ buildCodexRunParams,
235
+ canUseFocusedPartialSnapshot,
236
+ shouldRestrictWritebackToFocus,
237
+ buildSessionHistoryResult,
238
+ truncateSessionHistoryText,
239
+ normalizeComposerAttachments,
240
+ normalizeSkillInvocation
241
+ };
242
+ });