codex-overleaf-link 1.1.1

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 (52) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +457 -0
  3. package/bin/codex-overleaf-link.mjs +223 -0
  4. package/extension/src/shared/agentTranscript.js +1175 -0
  5. package/extension/src/shared/auditRecords.js +568 -0
  6. package/extension/src/shared/compatibility.js +372 -0
  7. package/extension/src/shared/compileAdapter.js +176 -0
  8. package/extension/src/shared/governanceRules.js +252 -0
  9. package/extension/src/shared/i18n.js +565 -0
  10. package/extension/src/shared/models.js +106 -0
  11. package/extension/src/shared/otText.js +505 -0
  12. package/extension/src/shared/projectFiles.js +180 -0
  13. package/extension/src/shared/reviewing.js +99 -0
  14. package/extension/src/shared/sensitiveScan.js +116 -0
  15. package/extension/src/shared/sessionState.js +1084 -0
  16. package/extension/src/shared/staleGuard.js +150 -0
  17. package/extension/src/shared/storageDb.js +986 -0
  18. package/extension/src/shared/storageKeys.js +29 -0
  19. package/extension/src/shared/storageMigration.js +168 -0
  20. package/extension/src/shared/summary.js +248 -0
  21. package/extension/src/shared/undoOperations.js +369 -0
  22. package/native-host/src/codexArgs.js +43 -0
  23. package/native-host/src/codexHome.js +538 -0
  24. package/native-host/src/codexModels.js +247 -0
  25. package/native-host/src/codexPrompt.js +192 -0
  26. package/native-host/src/codexPromptAssembly.js +411 -0
  27. package/native-host/src/codexSessionRunner.js +1247 -0
  28. package/native-host/src/commandApproval.js +914 -0
  29. package/native-host/src/debugLog.js +78 -0
  30. package/native-host/src/diffEngine.js +247 -0
  31. package/native-host/src/index.js +132 -0
  32. package/native-host/src/launcher.js +81 -0
  33. package/native-host/src/localSkills.js +476 -0
  34. package/native-host/src/manifest.js +226 -0
  35. package/native-host/src/mirrorSensitiveScan.js +119 -0
  36. package/native-host/src/mirrorWorkspace.js +1019 -0
  37. package/native-host/src/nativeDoctor.js +826 -0
  38. package/native-host/src/nativeEnvironment.js +315 -0
  39. package/native-host/src/nativeHostPlatform.js +112 -0
  40. package/native-host/src/nativeMessaging.js +60 -0
  41. package/native-host/src/nativeQuotas.js +294 -0
  42. package/native-host/src/nativeResponseBudget.js +194 -0
  43. package/native-host/src/runtimeInstaller.js +357 -0
  44. package/native-host/src/taskRunner.js +3 -0
  45. package/native-host/src/taskRunnerRuntime.js +1083 -0
  46. package/native-host/src/textPatch.js +287 -0
  47. package/package.json +40 -0
  48. package/scripts/codex-json-agent.mjs +269 -0
  49. package/scripts/install-native-host.mjs +255 -0
  50. package/scripts/npm-package-files-v1.1.1.txt +52 -0
  51. package/scripts/uninstall-native-host.mjs +298 -0
  52. package/scripts/verify-npm-package.mjs +296 -0
@@ -0,0 +1,78 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs');
4
+ const path = require('node:path');
5
+ const {
6
+ getCodexOverleafHome,
7
+ getNativeHostPlatform
8
+ } = require('./nativeHostPlatform');
9
+
10
+ const MAX_FIELD_LENGTH = 8000;
11
+
12
+ function getDebugLogDir(options = {}) {
13
+ return getCodexOverleafHome(options);
14
+ }
15
+
16
+ function getDebugLogPath(options = {}) {
17
+ const platformPath = getNativeHostPlatform(options) === 'win32' ? path.win32 : path.posix;
18
+ return platformPath.join(getDebugLogDir(options), 'native-host.log');
19
+ }
20
+
21
+ function logDebug(event, detail = {}, options = {}) {
22
+ try {
23
+ const logDir = getDebugLogDir(options);
24
+ const logPath = getDebugLogPath(options);
25
+ fs.mkdirSync(logDir, { recursive: true });
26
+ fs.appendFileSync(logPath, `${JSON.stringify({
27
+ time: new Date().toISOString(),
28
+ event,
29
+ detail: sanitize(detail)
30
+ })}\n`, 'utf8');
31
+ } catch {
32
+ // Debug logging must never break the native messaging host.
33
+ }
34
+ }
35
+
36
+ function truncateText(value, limit = MAX_FIELD_LENGTH) {
37
+ const text = String(value || '');
38
+ if (text.length <= limit) {
39
+ return text;
40
+ }
41
+ return `${text.slice(0, limit)}\n...[truncated ${text.length - limit} chars]`;
42
+ }
43
+
44
+ function sanitize(value) {
45
+ if (typeof value === 'string') {
46
+ return truncateText(value);
47
+ }
48
+ if (!value || typeof value !== 'object') {
49
+ return value;
50
+ }
51
+ if (Array.isArray(value)) {
52
+ return value.slice(0, 20).map(sanitize);
53
+ }
54
+ const result = {};
55
+ for (const [key, item] of Object.entries(value)) {
56
+ if (key === 'content') {
57
+ result[key] = `[${String(item || '').length} chars]`;
58
+ } else if (key === 'files' && Array.isArray(item)) {
59
+ result[key] = item.map(file => ({
60
+ path: file?.path,
61
+ contentLength: String(file?.content || '').length
62
+ }));
63
+ } else {
64
+ result[key] = sanitize(item);
65
+ }
66
+ }
67
+ return result;
68
+ }
69
+
70
+ module.exports = {
71
+ getDebugLogDir,
72
+ getDebugLogPath,
73
+ get LOG_PATH() {
74
+ return getDebugLogPath();
75
+ },
76
+ logDebug,
77
+ truncateText
78
+ };
@@ -0,0 +1,247 @@
1
+ 'use strict';
2
+
3
+ function computeLineDiff(oldText, newText, contextLines = 3) {
4
+ const oldLines = splitLines(oldText);
5
+ const newLines = splitLines(newText);
6
+
7
+ if (oldLines.join('\n') === newLines.join('\n')) {
8
+ return [];
9
+ }
10
+
11
+ const MAX_LINES = 5000;
12
+ const MAX_DIFF_PRODUCT = 4000000;
13
+
14
+ if (oldLines.length > MAX_LINES || newLines.length > MAX_LINES || oldLines.length * newLines.length > MAX_DIFF_PRODUCT) {
15
+ return buildTruncatedResult(oldLines.length, newLines.length);
16
+ }
17
+
18
+ const edits = myersDiff(oldLines, newLines);
19
+ if (edits === null) {
20
+ return buildTruncatedResult(oldLines.length, newLines.length);
21
+ }
22
+
23
+ return buildHunks(oldLines, newLines, edits, contextLines);
24
+ }
25
+
26
+ function buildTruncatedResult(oldCount, newCount) {
27
+ return [{
28
+ startA: 1,
29
+ startB: 1,
30
+ truncated: true,
31
+ lines: [{ type: 'context', text: `文件改动较大(${oldCount} → ${newCount} 行),请在 Overleaf 中查看完整差异。` }]
32
+ }];
33
+ }
34
+
35
+ function splitLines(text) {
36
+ const lines = String(text || '').split('\n');
37
+ if (lines.length > 0 && lines[lines.length - 1] === '') {
38
+ lines.pop();
39
+ }
40
+ return lines;
41
+ }
42
+
43
+ function myersDiff(oldLines, newLines) {
44
+ const n = oldLines.length;
45
+ const m = newLines.length;
46
+ const max = n + m;
47
+
48
+ if (max === 0) {
49
+ return [];
50
+ }
51
+
52
+ const vSize = 2 * max + 1;
53
+ let v = new Array(vSize).fill(0);
54
+ const trace = [];
55
+
56
+ const MAX_EDIT_DISTANCE = 1500;
57
+
58
+ for (let d = 0; d <= max; d++) {
59
+ if (d > MAX_EDIT_DISTANCE) {
60
+ return null;
61
+ }
62
+ trace.push([...v]);
63
+ const newV = [...v];
64
+ for (let k = -d; k <= d; k += 2) {
65
+ const kIndex = k + max;
66
+ let x;
67
+ if (k === -d || (k !== d && v[kIndex - 1] < v[kIndex + 1])) {
68
+ x = v[kIndex + 1];
69
+ } else {
70
+ x = v[kIndex - 1] + 1;
71
+ }
72
+ let y = x - k;
73
+
74
+ while (x < n && y < m && oldLines[x] === newLines[y]) {
75
+ x++;
76
+ y++;
77
+ }
78
+
79
+ newV[kIndex] = x;
80
+
81
+ if (x >= n && y >= m) {
82
+ trace[d] = newV;
83
+ return backtrack(trace, oldLines, newLines, d, max);
84
+ }
85
+ }
86
+ v = newV;
87
+ }
88
+
89
+ return buildFallbackEdits(oldLines, newLines);
90
+ }
91
+
92
+ function backtrack(trace, oldLines, newLines, finalD, max) {
93
+ const edits = [];
94
+ let x = oldLines.length;
95
+ let y = newLines.length;
96
+
97
+ for (let d = finalD; d >= 0; d--) {
98
+ const k = x - y;
99
+
100
+ let prevK, prevX, prevY;
101
+ if (d === 0) {
102
+ prevX = 0;
103
+ prevY = 0;
104
+ } else {
105
+ const prevVState = trace[d];
106
+ if (k === -d || (k !== d && prevVState[k - 1 + max] < prevVState[k + 1 + max])) {
107
+ prevK = k + 1;
108
+ } else {
109
+ prevK = k - 1;
110
+ }
111
+ prevX = prevVState[prevK + max];
112
+ prevY = prevX - prevK;
113
+ }
114
+
115
+ // Snake: diagonal moves after the edit
116
+ const midX = d > 0 ? (prevK < k ? prevX + 1 : prevX) : 0;
117
+ const midY = d > 0 ? (prevK > k ? prevY + 1 : prevY) : 0;
118
+
119
+ while (x > midX && y > midY) {
120
+ x--;
121
+ y--;
122
+ edits.unshift({ type: 'equal', oldIndex: x, newIndex: y });
123
+ }
124
+
125
+ // The edit move itself
126
+ if (d > 0) {
127
+ if (prevK < k) {
128
+ // Moved right: deletion
129
+ x--;
130
+ edits.unshift({ type: 'remove', oldIndex: x });
131
+ } else {
132
+ // Moved down: insertion
133
+ y--;
134
+ edits.unshift({ type: 'add', newIndex: y });
135
+ }
136
+ }
137
+ }
138
+
139
+ // Initial diagonal at d=0
140
+ while (x > 0 && y > 0) {
141
+ x--;
142
+ y--;
143
+ edits.unshift({ type: 'equal', oldIndex: x, newIndex: y });
144
+ }
145
+
146
+ return edits;
147
+ }
148
+
149
+ function buildFallbackEdits(oldLines, newLines) {
150
+ const edits = [];
151
+ for (let i = 0; i < oldLines.length; i++) {
152
+ edits.push({ type: 'remove', oldIndex: i });
153
+ }
154
+ for (let i = 0; i < newLines.length; i++) {
155
+ edits.push({ type: 'add', newIndex: i });
156
+ }
157
+ return edits;
158
+ }
159
+
160
+ function buildHunks(oldLines, newLines, edits, contextLines) {
161
+ const changes = edits.filter(e => e.type !== 'equal');
162
+ if (!changes.length) {
163
+ return [];
164
+ }
165
+
166
+ const changeIndices = [];
167
+ for (let i = 0; i < edits.length; i++) {
168
+ if (edits[i].type !== 'equal') {
169
+ changeIndices.push(i);
170
+ }
171
+ }
172
+
173
+ const groups = [];
174
+ let currentGroup = [changeIndices[0]];
175
+
176
+ for (let i = 1; i < changeIndices.length; i++) {
177
+ const gap = changeIndices[i] - changeIndices[i - 1] - 1;
178
+ if (gap <= contextLines * 2) {
179
+ currentGroup.push(changeIndices[i]);
180
+ } else {
181
+ groups.push(currentGroup);
182
+ currentGroup = [changeIndices[i]];
183
+ }
184
+ }
185
+ groups.push(currentGroup);
186
+
187
+ const hunks = [];
188
+ for (const group of groups) {
189
+ const firstIdx = Math.max(0, group[0] - contextLines);
190
+ const lastIdx = Math.min(edits.length - 1, group[group.length - 1] + contextLines);
191
+ const hunkEdits = edits.slice(firstIdx, lastIdx + 1);
192
+
193
+ const lines = [];
194
+ let startA = -1;
195
+ let startB = -1;
196
+
197
+ for (const edit of hunkEdits) {
198
+ if (edit.type === 'equal') {
199
+ if (startA === -1) {
200
+ startA = edit.oldIndex;
201
+ startB = edit.newIndex;
202
+ }
203
+ lines.push({ type: 'context', text: oldLines[edit.oldIndex] });
204
+ } else if (edit.type === 'remove') {
205
+ if (startA === -1) {
206
+ startA = edit.oldIndex;
207
+ startB = findNewIndex(edits, firstIdx, edit.oldIndex);
208
+ }
209
+ lines.push({ type: 'remove', text: oldLines[edit.oldIndex] });
210
+ } else if (edit.type === 'add') {
211
+ if (startA === -1) {
212
+ startA = findOldIndex(edits, firstIdx, edit.newIndex);
213
+ startB = edit.newIndex;
214
+ }
215
+ lines.push({ type: 'add', text: newLines[edit.newIndex] });
216
+ }
217
+ }
218
+
219
+ hunks.push({
220
+ startA: startA >= 0 ? startA + 1 : 1,
221
+ startB: startB >= 0 ? startB + 1 : 1,
222
+ lines
223
+ });
224
+ }
225
+
226
+ return hunks;
227
+ }
228
+
229
+ function findNewIndex(edits, startFrom, oldIndex) {
230
+ for (let i = startFrom; i < edits.length; i++) {
231
+ if (edits[i].newIndex !== undefined) {
232
+ return edits[i].newIndex;
233
+ }
234
+ }
235
+ return 0;
236
+ }
237
+
238
+ function findOldIndex(edits, startFrom, newIndex) {
239
+ for (let i = startFrom; i < edits.length; i++) {
240
+ if (edits[i].oldIndex !== undefined) {
241
+ return edits[i].oldIndex;
242
+ }
243
+ }
244
+ return 0;
245
+ }
246
+
247
+ module.exports = { computeLineDiff };
@@ -0,0 +1,132 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const { decodeFrames, encodeMessage } = require('./nativeMessaging');
5
+ const { logDebug } = require('./debugLog');
6
+ const { handleRequest } = require('./taskRunner');
7
+ const { buildNativeRuntimeEnv, summarizeNativeEnvironment } = require('./nativeEnvironment');
8
+
9
+ let buffered = Buffer.alloc(0);
10
+ const runtimeEnv = buildNativeRuntimeEnv(process.env);
11
+ Object.assign(process.env, runtimeEnv);
12
+ logDebug('environment.ready', summarizeNativeEnvironment(runtimeEnv));
13
+
14
+ process.stdin.on('data', chunk => {
15
+ logDebug('stdin.data', { bytes: chunk.length, bufferedBytes: buffered.length });
16
+ buffered = Buffer.concat([buffered, chunk]);
17
+
18
+ let decoded;
19
+ try {
20
+ decoded = decodeFrames(buffered);
21
+ } catch (error) {
22
+ writeResponse({
23
+ ok: false,
24
+ error: {
25
+ code: 'invalid_native_message',
26
+ message: error.message
27
+ }
28
+ });
29
+ buffered = Buffer.alloc(0);
30
+ return;
31
+ }
32
+
33
+ buffered = decoded.remainder;
34
+ for (const message of decoded.messages) {
35
+ handleDecodedMessage(message);
36
+ }
37
+ });
38
+
39
+ async function handleDecodedMessage(message) {
40
+ try {
41
+ logDebug('request.received', summarizeRequest(message));
42
+ const response = await handleRequest(message, runtimeEnv, event => {
43
+ writeResponse({
44
+ id: message?.id,
45
+ ok: true,
46
+ event
47
+ });
48
+ });
49
+ logDebug('response.ready', summarizeResponse(response));
50
+ writeResponse(response);
51
+ } catch (error) {
52
+ const response = {
53
+ id: message?.id,
54
+ ok: false,
55
+ error: {
56
+ code: 'internal_error',
57
+ message: error.message
58
+ }
59
+ };
60
+ logDebug('response.internal_error', summarizeResponse(response));
61
+ writeResponse(response);
62
+ }
63
+ }
64
+
65
+ process.stdin.on('error', error => {
66
+ logDebug('stdin.error', { message: error.message });
67
+ console.error(`stdin error: ${error.message}`);
68
+ });
69
+
70
+ process.on('uncaughtException', error => {
71
+ logDebug('process.uncaught_exception', {
72
+ message: error.message,
73
+ stack: error.stack
74
+ });
75
+ process.exit(1);
76
+ });
77
+
78
+ process.on('unhandledRejection', reason => {
79
+ logDebug('process.unhandled_rejection', {
80
+ message: reason?.message || String(reason),
81
+ stack: reason?.stack
82
+ });
83
+ process.exit(1);
84
+ });
85
+
86
+ function writeResponse(response) {
87
+ const frame = encodeMessage(response);
88
+ logDebug('stdout.write', { bytes: frame.length, ok: response?.ok, code: response?.error?.code });
89
+ process.stdout.write(frame);
90
+ }
91
+
92
+ function summarizeRequest(message) {
93
+ const params = message?.params || {};
94
+ return {
95
+ id: message?.id,
96
+ method: message?.method,
97
+ mode: params.mode,
98
+ model: params.model,
99
+ reasoningEffort: params.reasoningEffort,
100
+ taskLength: String(params.task || '').length,
101
+ reviewingOk: params.reviewing?.ok,
102
+ checkpointOk: params.checkpoint?.ok,
103
+ activePath: params.project?.activePath,
104
+ fileCount: Array.isArray(params.project?.files) ? params.project.files.length : 0,
105
+ fileSummary: summarizeProjectFiles(params.project?.files)
106
+ };
107
+ }
108
+
109
+ function summarizeProjectFiles(files) {
110
+ return (Array.isArray(files) ? files : []).slice(0, 50).map(file => ({
111
+ path: file?.path,
112
+ kind: file?.kind || (file?.contentBase64 ? 'binary' : 'text'),
113
+ size: Number(file?.size || file?.byteLength || 0) || stringByteLength(file?.content)
114
+ }));
115
+ }
116
+
117
+ function stringByteLength(value) {
118
+ return typeof value === 'string' ? Buffer.byteLength(value, 'utf8') : 0;
119
+ }
120
+
121
+ function summarizeResponse(response) {
122
+ return {
123
+ id: response?.id,
124
+ ok: response?.ok,
125
+ code: response?.error?.code,
126
+ message: response?.error?.message,
127
+ status: response?.result?.status,
128
+ operationCount: Array.isArray(response?.result?.operations) ? response.result.operations.length : 0,
129
+ hasDeletePlan: Boolean(response?.result?.deletePlan),
130
+ hasPlanId: Boolean(response?.result?.planId)
131
+ };
132
+ }
@@ -0,0 +1,81 @@
1
+ 'use strict';
2
+
3
+ const DEFAULT_POSIX_PATH_SEGMENTS = [
4
+ '/Library/TeX/texbin',
5
+ '/opt/homebrew/bin',
6
+ '/usr/local/bin',
7
+ '/usr/bin',
8
+ '/bin',
9
+ '/usr/sbin',
10
+ '/sbin'
11
+ ];
12
+
13
+ function buildLauncher({ platform = process.platform, nodePath, bridgeEntryPath, agentPath, defaultPathSegments }) {
14
+ if (platform === 'win32') {
15
+ return buildWindowsLauncherScript({ nodePath, bridgeEntryPath, agentPath });
16
+ }
17
+ if (platform === 'darwin' || platform === 'linux') {
18
+ return buildLauncherScript({ nodePath, bridgeEntryPath, agentPath, defaultPathSegments });
19
+ }
20
+ throw new Error(`Unsupported launcher platform: ${platform}`);
21
+ }
22
+
23
+ function buildLauncherScript({ nodePath, bridgeEntryPath, agentPath, defaultPathSegments = DEFAULT_POSIX_PATH_SEGMENTS }) {
24
+ const launcherPath = defaultPathSegments.join(':');
25
+ return [
26
+ '#!/bin/sh',
27
+ 'set -eu',
28
+ 'HOME_DIR="${HOME:-/tmp}"',
29
+ 'LOG_DIR="$HOME_DIR/.codex-overleaf"',
30
+ 'mkdir -p "$LOG_DIR" 2>/dev/null || true',
31
+ '{',
32
+ ' printf \'%s launcher.start pid=%s ppid=%s home=%s path=%s\\n\' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$$" "${PPID:-}" "$HOME_DIR" "${PATH:-}"',
33
+ ' printf \'%s launcher.node=%s\\n\' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "' + escapeShellSingleQuoted(nodePath) + '"',
34
+ '} >> "$LOG_DIR/native-host-launcher.log" 2>/dev/null || true',
35
+ `export PATH="${escapeDoubleQuoted(launcherPath)}:\${PATH:-}"`,
36
+ `export CODEX_OVERLEAF_AGENT_FILE=${shellSingleQuoted(nodePath)}`,
37
+ `export CODEX_OVERLEAF_AGENT_ARGS_JSON=${shellSingleQuoted(JSON.stringify([agentPath]))}`,
38
+ `exec "${escapeDoubleQuoted(nodePath)}" "${escapeDoubleQuoted(bridgeEntryPath)}"`,
39
+ ''
40
+ ].join('\n');
41
+ }
42
+
43
+ function buildWindowsLauncherScript({ nodePath, bridgeEntryPath, agentPath }) {
44
+ return [
45
+ '@echo off',
46
+ 'setlocal',
47
+ `set "CODEX_OVERLEAF_AGENT_FILE=${escapeBatchSetValue(nodePath)}"`,
48
+ `set CODEX_OVERLEAF_AGENT_ARGS_JSON=${escapeBatchRawSetValue(JSON.stringify([agentPath]))}`,
49
+ `"${escapeBatchCommandArg(nodePath)}" "${escapeBatchCommandArg(bridgeEntryPath)}"`,
50
+ ''
51
+ ].join('\r\n');
52
+ }
53
+
54
+ function escapeDoubleQuoted(value) {
55
+ return String(value).replace(/(["\\$`])/g, '\\$1');
56
+ }
57
+
58
+ function escapeShellSingleQuoted(value) {
59
+ return String(value).replace(/'/g, "'\\''");
60
+ }
61
+
62
+ function shellSingleQuoted(value) {
63
+ return `'${escapeShellSingleQuoted(value)}'`;
64
+ }
65
+
66
+ function escapeBatchSetValue(value) {
67
+ return String(value).replace(/%/g, '%%').replace(/"/g, '""');
68
+ }
69
+
70
+ function escapeBatchCommandArg(value) {
71
+ return String(value).replace(/%/g, '%%').replace(/"/g, '""');
72
+ }
73
+
74
+ function escapeBatchRawSetValue(value) {
75
+ return String(value).replace(/%/g, '%%').replace(/[\r\n]/g, '');
76
+ }
77
+
78
+ module.exports = {
79
+ buildLauncher,
80
+ buildLauncherScript
81
+ };