codex-overleaf-link 1.8.6 → 1.9.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 (73) 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 +8897 -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 +1131 -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/agentTranscript.js +18 -4
  60. package/extension/src/shared/compatibility.js +1 -1
  61. package/extension/styles/panel.css +4048 -0
  62. package/native-host/src/index.js +9 -2
  63. package/native-host/src/managedInstall.js +393 -0
  64. package/native-host/src/managedLauncherRuntime.js +71 -0
  65. package/native-host/src/nativeHostPlatform.js +16 -0
  66. package/native-host/src/runtimeInstaller.js +6 -1
  67. package/native-host/src/taskRunnerRuntime.js +8 -0
  68. package/native-host/src/updateArchive.js +253 -0
  69. package/native-host/src/updateManager.js +548 -0
  70. package/native-host/src/updateTrust.js +156 -0
  71. package/package.json +11 -2
  72. package/scripts/install-managed.mjs +64 -0
  73. package/scripts/uninstall-managed.mjs +48 -0
@@ -4,6 +4,8 @@
4
4
  const { decodeFrames, encodeMessage } = require('./nativeMessaging');
5
5
  const { logDebug } = require('./debugLog');
6
6
  const { handleRequest } = require('./taskRunner');
7
+ const { getActiveNativeWorkState } = require('./taskRunnerRuntime');
8
+ const { handleUpdateRequest, isUpdateMethod } = require('./updateManager');
7
9
  const { buildNativeRuntimeEnv, summarizeNativeEnvironment } = require('./nativeEnvironment');
8
10
 
9
11
  let buffered = Buffer.alloc(0);
@@ -39,13 +41,18 @@ process.stdin.on('data', chunk => {
39
41
  async function handleDecodedMessage(message) {
40
42
  try {
41
43
  logDebug('request.received', summarizeRequest(message));
42
- const response = await handleRequest(message, runtimeEnv, event => {
44
+ const response = isUpdateMethod(message?.method)
45
+ ? await handleUpdateRequest(message, {
46
+ env: runtimeEnv,
47
+ getWorkState: getActiveNativeWorkState
48
+ })
49
+ : await handleRequest(message, runtimeEnv, event => {
43
50
  writeResponse({
44
51
  id: message?.id,
45
52
  ok: true,
46
53
  event
47
54
  });
48
- });
55
+ });
49
56
  logDebug('response.ready', summarizeResponse(response));
50
57
  writeResponse(response);
51
58
  } catch (error) {
@@ -0,0 +1,393 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs');
4
+ const os = require('node:os');
5
+ const path = require('node:path');
6
+ const { spawnSync } = require('node:child_process');
7
+ const { buildLauncher } = require('./launcher');
8
+ const {
9
+ DEFAULT_CHROME_EXTENSION_ID,
10
+ buildAllowedOrigin,
11
+ buildHostManifest,
12
+ validateChromeExtensionId
13
+ } = require('./manifest');
14
+ const {
15
+ getDefaultBridgePath,
16
+ getDefaultManagedExtensionRoot,
17
+ getDefaultManagedNativeRoot,
18
+ getDefaultManagedRoot,
19
+ getNativeHostRegistrationTarget
20
+ } = require('./nativeHostPlatform');
21
+ const { buildRuntimeFileManifest } = require('./runtimeInstaller');
22
+ const { parseSemver, updateError } = require('./updateTrust');
23
+
24
+ const MANAGED_BY = 'codex-overleaf-link';
25
+ const EXTENSION_MARKER = '.codex-overleaf-managed-extension.json';
26
+ const NATIVE_MARKER = '.codex-overleaf-managed-native.json';
27
+
28
+ function installManagedDistribution(options = {}) {
29
+ const packageRoot = path.resolve(options.packageRoot || path.resolve(__dirname, '../..'));
30
+ const pkg = readJson(path.join(packageRoot, 'package.json'));
31
+ const version = String(options.version || pkg.version || '');
32
+ if (!parseSemver(version)) {
33
+ throw updateError('managed_version_invalid', 'Managed installation requires a stable semantic version.');
34
+ }
35
+ const platform = options.platform || process.platform;
36
+ const browser = options.browser || 'chrome';
37
+ const env = options.env || process.env;
38
+ const platformPath = platform === 'win32' ? path.win32 : path.posix;
39
+ const managedRoot = resolveForPlatform(options.managedRoot || getDefaultManagedRoot({ platform, env }), platformPath);
40
+ const extensionRoot = resolveForPlatform(options.extensionRoot || platformPath.join(managedRoot, 'extension'), platformPath);
41
+ const nativeRoot = resolveForPlatform(options.nativeRoot || platformPath.join(managedRoot, 'native'), platformPath);
42
+ const extensionId = options.extensionId || DEFAULT_CHROME_EXTENSION_ID;
43
+ if (!validateChromeExtensionId(extensionId)) {
44
+ throw new Error('Invalid Chrome extension id: ' + extensionId);
45
+ }
46
+ assertSafeManagedRoot(managedRoot, { platformPath, packageRoot, env, platform });
47
+ assertManagedChildRoot(extensionRoot, managedRoot, platformPath, 'extension');
48
+ assertManagedChildRoot(nativeRoot, managedRoot, platformPath, 'native');
49
+
50
+ const parent = path.dirname(path.resolve(extensionRoot));
51
+ fs.mkdirSync(parent, { recursive: true });
52
+ const unique = process.pid + '-' + Date.now() + '-' + Math.random().toString(16).slice(2);
53
+ const extensionStage = path.join(parent, '.extension.staging-' + unique);
54
+ const extensionRollback = path.join(parent, '.extension.rollback-' + unique);
55
+ buildManagedExtensionTree({ packageRoot, targetRoot: extensionStage, version, allowedFiles: options.allowedFiles });
56
+
57
+ const previousExtensionMarker = readManagedMarker(extensionRoot, EXTENSION_MARKER);
58
+ if (fs.existsSync(extensionRoot) && !isManagedMarker(previousExtensionMarker, 'extension')) {
59
+ safeRemove(extensionStage);
60
+ throw new Error('Refusing to replace an unmarked managed extension root: ' + extensionRoot);
61
+ }
62
+ let extensionRollbackCreated = false;
63
+ try {
64
+ if (fs.existsSync(extensionRoot)) {
65
+ fs.renameSync(extensionRoot, extensionRollback);
66
+ extensionRollbackCreated = true;
67
+ }
68
+ fs.renameSync(extensionStage, extensionRoot);
69
+ } catch (error) {
70
+ safeRemove(extensionStage);
71
+ if (extensionRollbackCreated && !fs.existsSync(extensionRoot)) {
72
+ fs.renameSync(extensionRollback, extensionRoot);
73
+ }
74
+ throw error;
75
+ }
76
+
77
+ try {
78
+ installManagedNativeVersion({ packageRoot, nativeRoot, version });
79
+ installManagedNativeRegistration({
80
+ browser,
81
+ env,
82
+ extensionId,
83
+ managedRoot,
84
+ nativeRoot,
85
+ platform
86
+ });
87
+ safeRemove(extensionRollback);
88
+ } catch (error) {
89
+ safeRemove(extensionRoot);
90
+ if (extensionRollbackCreated && fs.existsSync(extensionRollback)) {
91
+ fs.renameSync(extensionRollback, extensionRoot);
92
+ }
93
+ throw error;
94
+ }
95
+
96
+ return {
97
+ ok: true,
98
+ action: previousExtensionMarker ? 'updated' : 'installed',
99
+ browser: browser === 'auto' ? 'chrome' : browser,
100
+ extensionId,
101
+ version,
102
+ managedRoot,
103
+ extensionRoot,
104
+ nativeRoot,
105
+ loadUnpackedPath: extensionRoot,
106
+ allowedOrigin: buildAllowedOrigin(extensionId)
107
+ };
108
+ }
109
+
110
+ function buildManagedExtensionTree(options = {}) {
111
+ const packageRoot = path.resolve(options.packageRoot || path.resolve(__dirname, '../..'));
112
+ const targetRoot = path.resolve(String(options.targetRoot || ''));
113
+ const version = String(options.version || readJson(path.join(packageRoot, 'package.json')).version || '');
114
+ if (!parseSemver(version)) {
115
+ throw new Error('Managed extension version is invalid.');
116
+ }
117
+ if (fs.existsSync(targetRoot) && fs.readdirSync(targetRoot).length) {
118
+ throw new Error('Managed extension target must be empty: ' + targetRoot);
119
+ }
120
+ fs.mkdirSync(targetRoot, { recursive: true, mode: 0o700 });
121
+ const allowedFiles = normalizeAllowedFiles(options.allowedFiles);
122
+ const templatePath = path.join(packageRoot, 'extension/bootstrap/manifest.template.json');
123
+ const manifest = readJson(templatePath);
124
+ manifest.version = version;
125
+ fs.writeFileSync(path.join(targetRoot, 'manifest.json'), JSON.stringify(manifest, null, 2) + '\n', 'utf8');
126
+
127
+ copyPackageTree(packageRoot, 'extension/bootstrap', targetRoot, 'bootstrap', {
128
+ allowedFiles,
129
+ exclude: new Set(['extension/bootstrap/manifest.template.json'])
130
+ });
131
+ copyPackageTree(packageRoot, 'extension/assets', targetRoot, 'assets', { allowedFiles });
132
+ copyPackageTree(packageRoot, 'extension/src', targetRoot, 'runtime/src', { allowedFiles });
133
+ copyPackageTree(packageRoot, 'extension/styles', targetRoot, 'runtime/styles', { allowedFiles });
134
+ copyPackageFile(packageRoot, 'extension/runtime-manifest.json', targetRoot, 'runtime/runtime-manifest.json', allowedFiles);
135
+ fs.writeFileSync(path.join(targetRoot, EXTENSION_MARKER), JSON.stringify({
136
+ managedBy: MANAGED_BY,
137
+ kind: 'extension',
138
+ bootstrapProtocol: 1,
139
+ version,
140
+ installedAt: new Date().toISOString()
141
+ }, null, 2) + '\n', 'utf8');
142
+ return { targetRoot, version };
143
+ }
144
+
145
+ function installManagedNativeVersion({ packageRoot, nativeRoot, version }) {
146
+ const marker = readManagedMarker(nativeRoot, NATIVE_MARKER);
147
+ if (fs.existsSync(nativeRoot) && !isManagedMarker(marker, 'native')) {
148
+ throw new Error('Refusing to replace an unmarked managed native root: ' + nativeRoot);
149
+ }
150
+ fs.mkdirSync(path.join(nativeRoot, 'versions'), { recursive: true });
151
+ fs.mkdirSync(path.join(nativeRoot, 'updates'), { recursive: true });
152
+ fs.mkdirSync(path.join(nativeRoot, 'bootstrap'), { recursive: true });
153
+
154
+ const stage = path.join(nativeRoot, 'versions', '.' + version + '.staging-' + process.pid + '-' + Date.now());
155
+ const target = path.join(nativeRoot, 'versions', version);
156
+ for (const entry of buildRuntimeFileManifest({ packageRoot })) {
157
+ copyFile(entry.sourcePath, path.join(stage, entry.relativePath));
158
+ }
159
+ fs.writeFileSync(path.join(stage, '.codex-overleaf-version.json'), JSON.stringify({ version }, null, 2) + '\n');
160
+ if (fs.existsSync(target)) {
161
+ safeRemove(target);
162
+ }
163
+ fs.renameSync(stage, target);
164
+
165
+ copyFile(
166
+ path.join(packageRoot, 'native-host/src/managedLauncherRuntime.js'),
167
+ path.join(nativeRoot, 'bootstrap/launcher.js')
168
+ );
169
+ const previous = readVersionPointer(nativeRoot, 'active-version');
170
+ if (previous && previous !== version) {
171
+ atomicWrite(path.join(nativeRoot, 'previous-version'), previous + '\n');
172
+ }
173
+ atomicWrite(path.join(nativeRoot, 'active-version'), version + '\n');
174
+ fs.writeFileSync(path.join(nativeRoot, NATIVE_MARKER), JSON.stringify({
175
+ managedBy: MANAGED_BY,
176
+ kind: 'native',
177
+ bootstrapProtocol: 1,
178
+ version,
179
+ installedAt: new Date().toISOString()
180
+ }, null, 2) + '\n', 'utf8');
181
+ }
182
+
183
+ function installManagedNativeRegistration({ browser, env, extensionId, managedRoot, nativeRoot, platform }) {
184
+ const bridgePath = getDefaultBridgePath({ platform, browser, env });
185
+ const platformPath = platform === 'win32' ? path.win32 : path.posix;
186
+ const bootstrapEntry = platformPath.join(nativeRoot, 'bootstrap', 'launcher.js');
187
+ const bridge = buildLauncher({
188
+ platform,
189
+ nodePath: process.execPath,
190
+ bridgeEntryPath: bootstrapEntry,
191
+ agentPath: bootstrapEntry
192
+ });
193
+ fs.mkdirSync(platformPath.dirname(bridgePath), { recursive: true });
194
+ fs.writeFileSync(bridgePath, bridge, 'utf8');
195
+ if (platform !== 'win32') {
196
+ fs.chmodSync(bridgePath, 0o755);
197
+ }
198
+
199
+ const target = getNativeHostRegistrationTarget({ platform, browser, env });
200
+ const manifest = buildHostManifest({ extensionId, extensionIds: [extensionId], bridgePath, platform });
201
+ fs.mkdirSync(platformPath.dirname(target.manifestPath), { recursive: true });
202
+ fs.writeFileSync(target.manifestPath, JSON.stringify(manifest, null, 2) + '\n', 'utf8');
203
+ if (target.kind === 'registry') {
204
+ runRegistry(['add', target.registryKey, '/ve', '/t', 'REG_SZ', '/d', target.manifestPath, '/f']);
205
+ }
206
+ fs.writeFileSync(path.join(managedRoot, '.codex-overleaf-managed.json'), JSON.stringify({
207
+ managedBy: MANAGED_BY,
208
+ extensionId,
209
+ browser: target.browser,
210
+ bridgePath,
211
+ manifestPath: target.manifestPath
212
+ }, null, 2) + '\n');
213
+ }
214
+
215
+ function uninstallManagedDistribution(options = {}) {
216
+ const platform = options.platform || process.platform;
217
+ const browser = options.browser || 'chrome';
218
+ const env = options.env || process.env;
219
+ const managedRoot = path.resolve(options.managedRoot || getDefaultManagedRoot({ platform, env }));
220
+ const extensionRoot = path.resolve(options.extensionRoot || path.join(managedRoot, 'extension'));
221
+ const nativeRoot = path.resolve(options.nativeRoot || path.join(managedRoot, 'native'));
222
+ assertSafeManagedRoot(managedRoot, { packageRoot: path.resolve(__dirname, '../..'), env, platform });
223
+ assertManagedChildRoot(extensionRoot, managedRoot, path, 'extension');
224
+ assertManagedChildRoot(nativeRoot, managedRoot, path, 'native');
225
+ if (fs.existsSync(extensionRoot) && !isManagedMarker(readManagedMarker(extensionRoot, EXTENSION_MARKER), 'extension')) {
226
+ throw new Error('Refusing to remove an unmarked managed extension root.');
227
+ }
228
+ if (fs.existsSync(nativeRoot) && !isManagedMarker(readManagedMarker(nativeRoot, NATIVE_MARKER), 'native')) {
229
+ throw new Error('Refusing to remove an unmarked managed native root.');
230
+ }
231
+ const registration = getNativeHostRegistrationTarget({ platform, browser, env });
232
+ const bridgePath = getDefaultBridgePath({ platform, browser, env });
233
+ if (registration.kind === 'registry') {
234
+ runRegistry(['delete', registration.registryKey, '/f'], { allowMissing: true });
235
+ }
236
+ fs.rmSync(registration.manifestPath, { force: true });
237
+ fs.rmSync(bridgePath, { force: true });
238
+ safeRemove(extensionRoot);
239
+ safeRemove(nativeRoot);
240
+ try {
241
+ if (fs.existsSync(managedRoot) && fs.readdirSync(managedRoot).length === 1 && fs.existsSync(path.join(managedRoot, '.codex-overleaf-managed.json'))) {
242
+ safeRemove(managedRoot);
243
+ }
244
+ } catch (_error) {
245
+ // Managed roots are already removed; leave harmless metadata when another process owns it.
246
+ }
247
+ return { ok: true, managedRoot, extensionRoot, nativeRoot, removed: true };
248
+ }
249
+
250
+ function assertSafeManagedRoot(root, options = {}) {
251
+ const platformPath = options.platformPath || path;
252
+ const resolved = platformPath.resolve(root);
253
+ const home = platformPath.resolve(options.platform === 'win32'
254
+ ? (options.env?.USERPROFILE || os.homedir())
255
+ : (options.env?.HOME || os.homedir()));
256
+ const repo = platformPath.resolve(options.packageRoot || path.resolve(__dirname, '../..'));
257
+ if (resolved === platformPath.parse(resolved).root || resolved === home || resolved === repo) {
258
+ throw new Error('Refusing unsafe managed installation root: ' + root);
259
+ }
260
+ const temp = platformPath.resolve(os.tmpdir());
261
+ const underHome = isSameOrDescendant(resolved, home, platformPath);
262
+ const underTemp = isSameOrDescendant(resolved, temp, platformPath);
263
+ if (!underHome && !underTemp) {
264
+ throw new Error('Managed installation root must stay under the user home or temporary directory: ' + root);
265
+ }
266
+ const trustedBase = underHome ? home : temp;
267
+ if (options.platform !== 'win32' || process.platform === 'win32') {
268
+ assertNoSymlinkAncestors(resolved, trustedBase, platformPath);
269
+ }
270
+ return resolved;
271
+ }
272
+
273
+ function assertManagedChildRoot(target, managedRoot, platformPath, label) {
274
+ const resolvedTarget = platformPath.resolve(target);
275
+ const resolvedRoot = platformPath.resolve(managedRoot);
276
+ if (resolvedTarget === resolvedRoot || !isSameOrDescendant(resolvedTarget, resolvedRoot, platformPath)) {
277
+ throw new Error('Managed ' + label + ' root must stay inside the managed installation root.');
278
+ }
279
+ if (process.platform !== 'win32' || platformPath === path.win32) {
280
+ assertNoSymlinkAncestors(resolvedTarget, resolvedRoot, platformPath);
281
+ }
282
+ }
283
+
284
+ function assertNoSymlinkAncestors(target, stopAt, platformPath) {
285
+ let current = platformPath.resolve(target);
286
+ const boundary = platformPath.resolve(stopAt);
287
+ while (current !== boundary) {
288
+ if (fs.existsSync(current) && fs.lstatSync(current).isSymbolicLink()) {
289
+ throw new Error('Managed installation path contains a symlink: ' + current);
290
+ }
291
+ const parent = platformPath.dirname(current);
292
+ if (parent === current || !isSameOrDescendant(parent, boundary, platformPath)) break;
293
+ current = parent;
294
+ }
295
+ }
296
+
297
+ function isSameOrDescendant(target, parent, platformPath) {
298
+ if (target === parent) return true;
299
+ const relative = platformPath.relative(parent, target);
300
+ return Boolean(relative) && !relative.startsWith('..') && !platformPath.isAbsolute(relative);
301
+ }
302
+
303
+ function copyPackageTree(packageRoot, sourceRelative, targetRoot, targetRelative, options = {}) {
304
+ const source = path.join(packageRoot, sourceRelative);
305
+ for (const dirent of fs.readdirSync(source, { withFileTypes: true })) {
306
+ const childSourceRelative = path.posix.join(sourceRelative.replace(/\\/g, '/'), dirent.name);
307
+ const childTargetRelative = path.posix.join(targetRelative.replace(/\\/g, '/'), dirent.name);
308
+ if (dirent.isSymbolicLink()) {
309
+ throw new Error('Managed package source cannot contain symlinks: ' + childSourceRelative);
310
+ }
311
+ if (dirent.isDirectory()) {
312
+ copyPackageTree(packageRoot, childSourceRelative, targetRoot, childTargetRelative, options);
313
+ } else if (dirent.isFile() && !options.exclude?.has(childSourceRelative)) {
314
+ copyPackageFile(packageRoot, childSourceRelative, targetRoot, childTargetRelative, options.allowedFiles);
315
+ }
316
+ }
317
+ }
318
+
319
+ function copyPackageFile(packageRoot, sourceRelative, targetRoot, targetRelative, allowedFiles) {
320
+ if (allowedFiles && !allowedFiles.has(sourceRelative)) {
321
+ return;
322
+ }
323
+ copyFile(path.join(packageRoot, sourceRelative), path.join(targetRoot, targetRelative));
324
+ }
325
+
326
+ function copyFile(source, target) {
327
+ fs.mkdirSync(path.dirname(target), { recursive: true });
328
+ fs.copyFileSync(source, target);
329
+ fs.chmodSync(target, fs.statSync(source).mode & 0o777);
330
+ }
331
+
332
+ function normalizeAllowedFiles(value) {
333
+ if (!value) return null;
334
+ return value instanceof Set ? value : new Set(value);
335
+ }
336
+
337
+ function readManagedMarker(root, markerName) {
338
+ try {
339
+ return readJson(path.join(root, markerName));
340
+ } catch (_error) {
341
+ return null;
342
+ }
343
+ }
344
+
345
+ function isManagedMarker(marker, kind) {
346
+ return marker?.managedBy === MANAGED_BY && marker?.kind === kind && marker?.bootstrapProtocol === 1;
347
+ }
348
+
349
+ function readVersionPointer(nativeRoot, name) {
350
+ try {
351
+ const value = fs.readFileSync(path.join(nativeRoot, name), 'utf8').trim();
352
+ return parseSemver(value) ? value : '';
353
+ } catch (_error) {
354
+ return '';
355
+ }
356
+ }
357
+
358
+ function atomicWrite(target, content) {
359
+ const temp = target + '.tmp-' + process.pid + '-' + Date.now();
360
+ fs.mkdirSync(path.dirname(target), { recursive: true });
361
+ fs.writeFileSync(temp, content, { mode: 0o600 });
362
+ fs.renameSync(temp, target);
363
+ }
364
+
365
+ function runRegistry(args, options = {}) {
366
+ const result = spawnSync(process.env.CODEX_OVERLEAF_REG_EXE || 'reg.exe', args, { encoding: 'utf8' });
367
+ if (result.error) throw result.error;
368
+ if (result.status !== 0 && !options.allowMissing) {
369
+ throw new Error(result.stderr || result.stdout || 'Windows registry command failed.');
370
+ }
371
+ }
372
+
373
+ function resolveForPlatform(value, platformPath) {
374
+ return platformPath.isAbsolute(value) ? value : platformPath.resolve(value);
375
+ }
376
+
377
+ function safeRemove(target) {
378
+ fs.rmSync(target, { recursive: true, force: true });
379
+ }
380
+
381
+ function readJson(filePath) {
382
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'));
383
+ }
384
+
385
+ module.exports = {
386
+ EXTENSION_MARKER,
387
+ MANAGED_BY,
388
+ NATIVE_MARKER,
389
+ assertSafeManagedRoot,
390
+ buildManagedExtensionTree,
391
+ installManagedDistribution,
392
+ uninstallManagedDistribution
393
+ };
@@ -0,0 +1,71 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const fs = require('node:fs');
5
+ const path = require('node:path');
6
+ const { spawn } = require('node:child_process');
7
+
8
+ const nativeRoot = path.resolve(__dirname, '..');
9
+ const managedRoot = path.dirname(nativeRoot);
10
+ const extensionRoot = path.join(managedRoot, 'extension');
11
+ const activeVersion = readVersionPointer('active-version');
12
+ const previousVersion = readVersionPointer('previous-version');
13
+ const selectedVersion = selectRunnableVersion(activeVersion, previousVersion);
14
+
15
+ if (!selectedVersion) {
16
+ process.stderr.write('Codex Overleaf managed native runtime is unavailable. Run install-managed again.\n');
17
+ process.exit(1);
18
+ }
19
+
20
+ const versionRoot = path.join(nativeRoot, 'versions', selectedVersion);
21
+ const entryPath = path.join(versionRoot, 'native-host', 'src', 'index.js');
22
+ const agentPath = path.join(versionRoot, 'scripts', 'codex-json-agent.mjs');
23
+ const env = {
24
+ ...process.env,
25
+ CODEX_OVERLEAF_MANAGED: '1',
26
+ CODEX_OVERLEAF_MANAGED_ROOT: managedRoot,
27
+ CODEX_OVERLEAF_MANAGED_NATIVE_ROOT: nativeRoot,
28
+ CODEX_OVERLEAF_MANAGED_EXTENSION_ROOT: extensionRoot,
29
+ CODEX_OVERLEAF_ACTIVE_VERSION: selectedVersion,
30
+ CODEX_OVERLEAF_AGENT_FILE: process.execPath,
31
+ CODEX_OVERLEAF_AGENT_ARGS_JSON: JSON.stringify([agentPath])
32
+ };
33
+
34
+ const child = spawn(process.execPath, [entryPath], {
35
+ env,
36
+ stdio: 'inherit',
37
+ windowsHide: true
38
+ });
39
+ child.on('error', error => {
40
+ process.stderr.write('Failed to start Codex Overleaf managed runtime: ' + error.message + '\n');
41
+ process.exit(1);
42
+ });
43
+ child.on('exit', (code, signal) => {
44
+ if (signal) {
45
+ process.kill(process.pid, signal);
46
+ return;
47
+ }
48
+ process.exit(Number.isInteger(code) ? code : 1);
49
+ });
50
+
51
+ function readVersionPointer(name) {
52
+ try {
53
+ const value = fs.readFileSync(path.join(nativeRoot, name), 'utf8').trim();
54
+ return /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/.test(value) ? value : '';
55
+ } catch (_error) {
56
+ return '';
57
+ }
58
+ }
59
+
60
+ function selectRunnableVersion(active, previous) {
61
+ for (const version of [active, previous]) {
62
+ if (!version) {
63
+ continue;
64
+ }
65
+ const entry = path.join(nativeRoot, 'versions', version, 'native-host', 'src', 'index.js');
66
+ if (fs.existsSync(entry) && fs.statSync(entry).isFile()) {
67
+ return version;
68
+ }
69
+ }
70
+ return '';
71
+ }
@@ -27,6 +27,19 @@ function getDefaultRuntimeRoot(options = {}) {
27
27
  return platformPath.join(getCodexOverleafHome(options), 'native-host-runtime');
28
28
  }
29
29
 
30
+ function getDefaultManagedRoot(options = {}) {
31
+ const platformPath = getPathModule(options);
32
+ return platformPath.join(getCodexOverleafHome(options), 'managed');
33
+ }
34
+
35
+ function getDefaultManagedExtensionRoot(options = {}) {
36
+ return getPathModule(options).join(getDefaultManagedRoot(options), 'extension');
37
+ }
38
+
39
+ function getDefaultManagedNativeRoot(options = {}) {
40
+ return getPathModule(options).join(getDefaultManagedRoot(options), 'native');
41
+ }
42
+
30
43
  function getDefaultBridgePath(options = {}) {
31
44
  const platformPath = getPathModule(options);
32
45
  const bridgeName = getNativeHostPlatform(options) === 'win32'
@@ -104,6 +117,9 @@ function throwUnsupportedBrowser(platform, browser) {
104
117
  module.exports = {
105
118
  getCodexOverleafHome,
106
119
  getDefaultBridgePath,
120
+ getDefaultManagedExtensionRoot,
121
+ getDefaultManagedNativeRoot,
122
+ getDefaultManagedRoot,
107
123
  getDefaultRuntimeRoot,
108
124
  getHomeDir,
109
125
  getNativeHostPlatform,
@@ -11,6 +11,9 @@ const DEFAULT_RUNTIME_ROOT = getDefaultRuntimeRoot();
11
11
 
12
12
  function buildRuntimeFileManifest(options = {}) {
13
13
  const packageRoot = path.resolve(options.packageRoot || getDefaultPackageRoot());
14
+ const allowedFiles = options.allowedFiles
15
+ ? new Set([...options.allowedFiles].map((relativePath) => String(relativePath).replace(/\\/g, '/')))
16
+ : null;
14
17
  const files = [
15
18
  fileEntry(packageRoot, 'package.json'),
16
19
  ...directoryEntries(packageRoot, 'native-host/src'),
@@ -20,7 +23,9 @@ function buildRuntimeFileManifest(options = {}) {
20
23
  fileEntry(packageRoot, 'scripts/uninstall-native-host.mjs')
21
24
  ];
22
25
 
23
- return files.sort((left, right) => left.relativePath.localeCompare(right.relativePath));
26
+ return files
27
+ .filter((entry) => !allowedFiles || allowedFiles.has(entry.relativePath))
28
+ .sort((left, right) => left.relativePath.localeCompare(right.relativePath));
24
29
  }
25
30
 
26
31
  function assertSafeManagedRuntimeRoot(runtimeRoot, options = {}) {
@@ -1155,9 +1155,17 @@ function errorResponse(id, code, message, details = {}) {
1155
1155
  };
1156
1156
  }
1157
1157
 
1158
+ function getActiveNativeWorkState() {
1159
+ return {
1160
+ projectLocks: activeProjectLocks.size,
1161
+ runControllers: activeRunControllers.size
1162
+ };
1163
+ }
1164
+
1158
1165
  module.exports = {
1159
1166
  NATIVE_REQUEST_QUOTAS,
1160
1167
  buildDefaultTaskResult,
1168
+ getActiveNativeWorkState,
1161
1169
  handleRequest,
1162
1170
  parseAgentEventLines,
1163
1171
  purgeExpiredPendingPlans