codex-overleaf-link 2.5.0 → 2.5.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.
- package/README.md +38 -26
- package/README.zh-CN.md +39 -27
- package/extension/bootstrap/background.js +52 -9
- package/extension/bootstrap/manifest.template.json +1 -1
- package/extension/bootstrap/popup.html +1 -0
- package/extension/bootstrap/popupFallback.js +55 -0
- package/extension/bootstrap/updateStatus.js +1 -0
- package/extension/src/background.js +206 -53
- package/extension/src/backgroundBootstrapMigration.js +181 -0
- package/extension/src/backgroundSharedSessions.js +74 -14
- package/extension/src/backgroundUpdateCoordinator.js +118 -21
- package/extension/src/backgroundUpdateSafety.js +173 -0
- package/extension/src/content/activeTurnControl.js +8 -2
- package/extension/src/content/applyResultFormatters.js +46 -73
- package/extension/src/content/assetTransferBroker.js +72 -14
- package/extension/src/content/composerAttachments.js +93 -45
- package/extension/src/content/composerPanel.js +18 -0
- package/extension/src/content/contentRuntime.js +661 -676
- package/extension/src/content/diagnosticsController.js +122 -96
- package/extension/src/content/diagnosticsExport.js +19 -5
- package/extension/src/content/diagnosticsPanel.js +30 -0
- package/extension/src/content/directoryCleanupRecovery.js +54 -0
- package/extension/src/content/generated/content.bundle.js +6879 -2786
- package/extension/src/content/generated/content.bundle.meta.json +11 -3
- package/extension/src/content/globalPreferencesController.js +1 -1
- package/extension/src/content/legacyGlobalRegistry.js +6 -0
- package/extension/src/content/localSkillsPanel.js +102 -12
- package/extension/src/content/markdownText.js +5 -6
- package/extension/src/content/modelPicker.js +7 -4
- package/extension/src/content/nativeCompatibilityController.js +45 -20
- package/extension/src/content/otWarmMirror.js +4 -5
- package/extension/src/content/otWarmMirrorController.js +17 -20
- package/extension/src/content/pageBridgeClient.js +15 -2
- package/extension/src/content/panelMaintenance.js +40 -12
- package/extension/src/content/panelRenderer.js +6 -0
- package/extension/src/content/projectProviderSelection.js +13 -4
- package/extension/src/content/projectSessionCleanup.js +109 -62
- package/extension/src/content/projectSettingsCoordinator.js +39 -2
- package/extension/src/content/providerSettingsCoordinator.js +46 -8
- package/extension/src/content/providerSettingsDialog.js +71 -13
- package/extension/src/content/recentProjects.js +11 -5
- package/extension/src/content/runActivitySummary.js +40 -6
- package/extension/src/content/runCompletionMeta.js +344 -0
- package/extension/src/content/runController.js +37 -14
- package/extension/src/content/runFailureNotice.js +21 -12
- package/extension/src/content/runGuidanceController.js +72 -27
- package/extension/src/content/runGuidanceView.js +59 -10
- package/extension/src/content/runQueueScheduler.js +148 -42
- package/extension/src/content/runRecoveryActions.js +146 -0
- package/extension/src/content/runResultActions.js +8 -125
- package/extension/src/content/runReviewController.js +574 -0
- package/extension/src/content/runScrollLayout.js +21 -2
- package/extension/src/content/runTimelineView.js +146 -153
- package/extension/src/content/scopedPersistencePanelState.js +51 -6
- package/extension/src/content/selectionContextView.js +1 -1
- package/extension/src/content/sensitivePreflight.js +140 -0
- package/extension/src/content/sessionForkController.js +80 -17
- package/extension/src/content/sessionManager.js +1 -1
- package/extension/src/content/settingsPanel.js +7 -3
- package/extension/src/content/settingsWorkbench.js +5 -0
- package/extension/src/content/sharedSessionViewSync.js +45 -8
- package/extension/src/content/updateIdle.js +4 -1
- package/extension/src/content/updateNotice.js +60 -8
- package/extension/src/content/updateSafetyReceiver.js +58 -0
- package/extension/src/content/writebackController.js +53 -4
- package/extension/src/content/writebackOrchestrator.js +82 -73
- package/extension/src/content/writingStyleSettings.js +35 -11
- package/extension/src/page/binaryAssetUploader.js +136 -14
- package/extension/src/page/fileMover.js +256 -0
- package/extension/src/page/ownedDirectoryCleanup.js +94 -0
- package/extension/src/page/referenceProjects.js +1 -0
- package/extension/src/page/reviewContext.js +113 -0
- package/extension/src/page/saveState.js +6 -4
- package/extension/src/page/snapshotRouter.js +84 -39
- package/extension/src/page/textFileCreator.js +105 -25
- package/extension/src/page/trackedChangeCapture.js +12 -2
- package/extension/src/page/trackedChangeOwnership.js +19 -1
- package/extension/src/page/trackedChangeReplay.js +41 -21
- package/extension/src/page/trackedChangesLifecycle.js +217 -199
- package/extension/src/page/treeOperations.js +7 -10
- package/extension/src/page/updateSafetyProbe.js +41 -0
- package/extension/src/page/writebackReceiptJournal.js +2 -0
- package/extension/src/page/writebackRouter.js +80 -79
- package/extension/src/pageBridge.js +19 -3
- package/extension/src/popup.js +62 -10
- package/extension/src/shared/agentTranscript.js +23 -0
- package/extension/src/shared/auditRecords.js +2 -0
- package/extension/src/shared/compatibility.js +22 -6
- package/extension/src/shared/compileAdapter.js +79 -57
- package/extension/src/shared/composerAttachmentDraft.js +151 -0
- package/extension/src/shared/directoryCleanupProof.js +43 -0
- package/extension/src/shared/failureReasons.js +309 -3
- package/extension/src/shared/globalPreferences.js +30 -13
- package/extension/src/shared/governanceRules.js +4 -37
- package/extension/src/shared/i18n.js +298 -2
- package/extension/src/shared/managedUpdateProjection.js +2 -2
- package/extension/src/shared/nativeRequestIdentity.js +1 -0
- package/extension/src/shared/pageRpcContract.js +5 -4
- package/extension/src/shared/projectFiles.js +21 -44
- package/extension/src/shared/providerProfiles.js +5 -3
- package/extension/src/shared/runInputQueue.js +48 -5
- package/extension/src/shared/selectionContext.js +12 -3
- package/extension/src/shared/sessionState.js +72 -27
- package/extension/src/shared/settlementFacts.js +10 -2
- package/extension/src/shared/sharedSessionBridge.js +37 -14
- package/extension/src/shared/sharedSessionTransfer.js +140 -0
- package/extension/src/shared/staleGuard.js +118 -13
- package/extension/src/shared/storageDb.js +58 -26
- package/extension/src/shared/storageMigration.js +71 -111
- package/extension/src/shared/storageRunActions.js +209 -26
- package/extension/src/shared/subagentActivityModel.js +1 -1
- package/extension/src/shared/undoOperations.js +136 -13
- package/extension/src/shared/updateConsent.js +6 -4
- package/extension/src/shared/updateRuntimeIdentity.js +1 -1
- package/extension/src/shared/writebackIntent.js +16 -0
- package/extension/src/shared/writebackOperationEvidence.js +4 -15
- package/extension/src/shared/writebackSettlement.js +38 -38
- package/extension/styles/panel.css +19 -5
- package/native-host/src/anthropicBridgeRequest.js +4 -2
- package/native-host/src/anthropicBridgeResponse.js +6 -2
- package/native-host/src/anthropicMessagesBridge.js +24 -16
- package/native-host/src/bootstrapMigration.js +296 -0
- package/native-host/src/bootstrapMigrationContract.js +101 -0
- package/native-host/src/bootstrapUpdate.js +233 -0
- package/native-host/src/bootstrapUpdateWatchdog.js +44 -0
- package/native-host/src/chatBridgeResponse.js +10 -0
- package/native-host/src/chatCompletionsBridge.js +21 -15
- package/native-host/src/codexConfigPaths.js +106 -0
- package/native-host/src/codexHome.js +66 -73
- package/native-host/src/codexHomeScope.js +124 -0
- package/native-host/src/codexInheritedAuth.js +90 -0
- package/native-host/src/codexLocalSkills.js +301 -0
- package/native-host/src/codexPromptAssembly.js +31 -12
- package/native-host/src/codexProviderTest.js +24 -26
- package/native-host/src/codexSessionRunner.js +110 -183
- package/native-host/src/codexSessionTiming.js +18 -8
- package/native-host/src/codexSkillIsolation.js +170 -0
- package/native-host/src/codexThreadFork.js +19 -3
- package/native-host/src/index.js +19 -7
- package/native-host/src/managedInstall.js +110 -35
- package/native-host/src/managedInstallRecovery.js +140 -0
- package/native-host/src/managedRuntimeEvidence.js +57 -0
- package/native-host/src/mirrorWorkspace.js +92 -22
- package/native-host/src/nativeDoctor.js +15 -0
- package/native-host/src/nativeHelperLifecycle.js +21 -0
- package/native-host/src/nativeHostRegistration.js +33 -0
- package/native-host/src/nativeRunLifecycle.js +71 -0
- package/native-host/src/processStopBarrier.js +42 -7
- package/native-host/src/projectPathPlatform.js +30 -0
- package/native-host/src/providerHttp.js +80 -0
- package/native-host/src/providerProfile.js +4 -2
- package/native-host/src/providerReasoning.js +2 -7
- package/native-host/src/providerRuntime.js +1 -0
- package/native-host/src/providerStore.js +72 -14
- package/native-host/src/responsesPassthroughBridge.js +266 -52
- package/native-host/src/runtimeInstaller.js +21 -1
- package/native-host/src/selectionScope.js +36 -8
- package/native-host/src/subagentAdmission.js +26 -0
- package/native-host/src/subagentBroker.js +79 -66
- package/native-host/src/subagentTelemetry.js +19 -10
- package/native-host/src/subagentWorkspacePath.js +5 -2
- package/native-host/src/taskRunnerRuntime.js +77 -54
- package/native-host/src/tomlStatements.js +140 -0
- package/native-host/src/updateManager.js +297 -39
- package/native-host/src/writingStyleRuntime.js +57 -29
- package/native-host/src/writingStyleStore.js +73 -0
- package/package.json +1 -1
- package/scripts/install-managed.mjs +1 -0
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const { getCodexOverleafSkillsRoot } = require('./localSkills');
|
|
6
|
+
const { createCodexLocalSkillIdentity } = require('./codexLocalSkills');
|
|
7
|
+
|
|
8
|
+
async function applyCodexSkillIsolation({ input = {}, childEnv = process.env, request, emit = () => {} } = {}) {
|
|
9
|
+
if (input.loadCodexLocalSkills !== false && input.loadCodexOverleafSkills !== false
|
|
10
|
+
&& !Array.isArray(input.enabledCodexOverleafSkillIds) && !Array.isArray(input.disableCodexOverleafSkillIds)
|
|
11
|
+
&& !Array.isArray(input.enabledCodexLocalSkillIds)) {
|
|
12
|
+
return { disabled: [] };
|
|
13
|
+
}
|
|
14
|
+
if (typeof request !== 'function') {
|
|
15
|
+
throw new Error('Codex skill isolation requires an app-server request function');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const listResult = await request('skills/list', {
|
|
19
|
+
cwd: input.workspacePath,
|
|
20
|
+
includeDisabled: true,
|
|
21
|
+
...(input.workspacePath ? { cwds: [input.workspacePath] } : {}),
|
|
22
|
+
forceReload: true
|
|
23
|
+
});
|
|
24
|
+
if (Array.isArray(input.enabledCodexLocalSkillIds) && (!Array.isArray(listResult?.data)
|
|
25
|
+
|| listResult.data.some(group => !Array.isArray(group?.skills)
|
|
26
|
+
|| (group.errors !== undefined && !Array.isArray(group.errors))))) {
|
|
27
|
+
throw Object.assign(new Error('Local Codex skills could not be prepared. Refresh the skill list and try again.'),
|
|
28
|
+
{ code: 'codex_local_skill_unavailable' });
|
|
29
|
+
}
|
|
30
|
+
const skills = flattenCodexSkillsList(listResult);
|
|
31
|
+
const disabled = [];
|
|
32
|
+
const enabled = [];
|
|
33
|
+
for (const skill of skills) {
|
|
34
|
+
const localSelection = getLocalSkillSelection(skill, input, childEnv);
|
|
35
|
+
const shouldDisable = shouldDisableCodexSkillForIsolation(skill, input, childEnv);
|
|
36
|
+
if (localSelection === true && skill?.enabled === false) {
|
|
37
|
+
const params = buildSkillDisableParams(skill, { preferPath: true });
|
|
38
|
+
if (params) {
|
|
39
|
+
await request('skills/config/write', { ...params, enabled: true });
|
|
40
|
+
enabled.push(String(skill.name || skill.path || '').trim());
|
|
41
|
+
}
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
if (skill?.enabled === false || !shouldDisable) {
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
const params = buildSkillDisableParams(skill, { preferPath: localSelection !== null });
|
|
48
|
+
if (!params) {
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
await request('skills/config/write', params);
|
|
52
|
+
disabled.push(String(skill.name || skill.path || '').trim());
|
|
53
|
+
}
|
|
54
|
+
if (disabled.length) {
|
|
55
|
+
emitCodexEvent(emit, 'codex.skill_isolation.applied', 'Applied Codex skill loading settings for this turn', {
|
|
56
|
+
disabledSkillNames: disabled.filter(Boolean)
|
|
57
|
+
}, 'completed');
|
|
58
|
+
}
|
|
59
|
+
return { disabled, enabled };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function getLocalSkillSelection(skill, input, childEnv) {
|
|
63
|
+
if (!Array.isArray(input.enabledCodexLocalSkillIds)
|
|
64
|
+
|| getOverleafSkillId(skill.path, input, childEnv)) return null;
|
|
65
|
+
if (isCodexSystemSkill(skill) && isAllowedSystemSkillForIsolation(skill, input)) return true;
|
|
66
|
+
if (input.loadCodexLocalSkills === false) return false;
|
|
67
|
+
const id = createCodexLocalSkillIdentity(skill, childEnv);
|
|
68
|
+
return Boolean(id) && input.enabledCodexLocalSkillIds.includes(id);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function flattenCodexSkillsList(listResult = {}) {
|
|
72
|
+
const data = Array.isArray(listResult?.data) ? listResult.data : [];
|
|
73
|
+
return data.flatMap(entry => Array.isArray(entry?.skills) ? entry.skills : []);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function shouldDisableCodexSkillForIsolation(skill = {}, input = {}, childEnv = process.env) {
|
|
77
|
+
const localSelection = getLocalSkillSelection(skill, input, childEnv);
|
|
78
|
+
if (localSelection !== null) return !localSelection;
|
|
79
|
+
if (isCodexSystemSkill(skill)) {
|
|
80
|
+
return input.loadCodexLocalSkills === false && !isAllowedSystemSkillForIsolation(skill, input);
|
|
81
|
+
}
|
|
82
|
+
const id = getOverleafSkillId(skill.path, input, childEnv);
|
|
83
|
+
if (id && input.installCodexOverleafSkillsTarget !== true) {
|
|
84
|
+
if (input.loadCodexOverleafSkills === false) return true;
|
|
85
|
+
if (Array.isArray(input.enabledCodexOverleafSkillIds) && !input.enabledCodexOverleafSkillIds.includes(id)) return true;
|
|
86
|
+
if (Array.isArray(input.disableCodexOverleafSkillIds) && input.disableCodexOverleafSkillIds.includes(id)) return true;
|
|
87
|
+
}
|
|
88
|
+
return input.loadCodexLocalSkills === false && !isAllowedCodexOverleafSkillPath(skill.path, input, childEnv);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function getOverleafSkillId(skillPath, input, childEnv) {
|
|
92
|
+
if (!skillPath || !path.isAbsolute(String(skillPath))) return '';
|
|
93
|
+
const roots = [getCodexOverleafSkillsRoot({ env: childEnv })];
|
|
94
|
+
// A composed home with local inheritance disabled contains only plugin or
|
|
95
|
+
// explicitly materialized project skills. Real paths identify plugin aliases.
|
|
96
|
+
if (input.loadCodexLocalSkills === false) roots.push(path.join(String(childEnv.CODEX_HOME || ''), 'skills'));
|
|
97
|
+
for (const root of roots) for (const base of comparablePaths(root)) for (const candidate of comparablePaths(skillPath)) {
|
|
98
|
+
const relative = path.relative(base, candidate);
|
|
99
|
+
if (relative && !relative.startsWith('..') && !path.isAbsolute(relative)) return relative.split(path.sep)[0];
|
|
100
|
+
}
|
|
101
|
+
return '';
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function isCodexSystemSkill(skill = {}) {
|
|
105
|
+
return String(skill.scope || '') === 'system' || isSystemSkillPath(skill.path);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function isAllowedSystemSkillForIsolation(skill = {}, input = {}) {
|
|
109
|
+
return input.installCodexOverleafSkillsTarget === true && String(skill.name || '') === 'skill-installer';
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function isAllowedCodexOverleafSkillPath(skillPath, input = {}, childEnv = process.env) {
|
|
113
|
+
if (input.loadCodexOverleafSkills === false && input.installCodexOverleafSkillsTarget !== true) {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
const pathText = String(skillPath || '');
|
|
117
|
+
if (!pathText || !path.isAbsolute(pathText) || isSystemSkillPath(pathText)) {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
const roots = [
|
|
121
|
+
path.join(String(childEnv.CODEX_HOME || ''), 'skills'),
|
|
122
|
+
getCodexOverleafSkillsRoot({ env: childEnv })
|
|
123
|
+
].filter(Boolean);
|
|
124
|
+
return roots.some(root => isInsideOrSamePath(pathText, root));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function isSystemSkillPath(skillPath) {
|
|
128
|
+
return String(skillPath || '').split(path.sep).includes('.system');
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function buildSkillDisableParams(skill = {}, { preferPath = false } = {}) {
|
|
132
|
+
const name = String(skill.name || '').trim();
|
|
133
|
+
const skillPath = String(skill.path || '').trim();
|
|
134
|
+
if (preferPath && path.isAbsolute(skillPath)) return { path: skillPath, enabled: false };
|
|
135
|
+
if (isCodexSystemSkill(skill) && name) {
|
|
136
|
+
return { name, enabled: false };
|
|
137
|
+
}
|
|
138
|
+
if (path.isAbsolute(skillPath)) {
|
|
139
|
+
return { path: skillPath, enabled: false };
|
|
140
|
+
}
|
|
141
|
+
if (name) {
|
|
142
|
+
return { name, enabled: false };
|
|
143
|
+
}
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function isInsideOrSamePath(target, root) {
|
|
148
|
+
const targetPaths = comparablePaths(target);
|
|
149
|
+
const rootPaths = comparablePaths(root);
|
|
150
|
+
return targetPaths.some(targetPath => rootPaths.some(rootPath => (
|
|
151
|
+
targetPath === rootPath || targetPath.startsWith(rootPath + path.sep)
|
|
152
|
+
)));
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function comparablePaths(value) {
|
|
156
|
+
const resolved = path.resolve(String(value || ''));
|
|
157
|
+
const candidates = [resolved];
|
|
158
|
+
try {
|
|
159
|
+
candidates.push(fs.realpathSync.native(resolved));
|
|
160
|
+
} catch (_) {
|
|
161
|
+
// Fall back to the lexical path when the file is not present yet.
|
|
162
|
+
}
|
|
163
|
+
return Array.from(new Set(candidates));
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function emitCodexEvent(emit, type, title, detail = {}, status = 'running') {
|
|
167
|
+
emit({ type, title, status, detail, timestamp: new Date().toISOString() });
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
module.exports = { applyCodexSkillIsolation };
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const { runOwnedNativeHelper } = require('./nativeHelperLifecycle');
|
|
4
|
+
const { stopCodexAppServer, throwIfAborted } = require('./codexSessionTiming');
|
|
3
5
|
const { spawn } = require('node:child_process');
|
|
4
6
|
const { version: PACKAGE_VERSION } = require('../../package.json');
|
|
5
7
|
const { buildCodexHomeEnv } = require('./codexHome');
|
|
@@ -7,9 +9,17 @@ const { resolveCodexCommand, shouldUseShellForCommand } = require('./codexComman
|
|
|
7
9
|
const { buildCodexAppServerArgs } = require('./codexSessionRunner');
|
|
8
10
|
|
|
9
11
|
function forkCodexThread(params = {}, env = process.env) {
|
|
12
|
+
return runOwnedNativeHelper(signal => runForkProcess(params, env, signal));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function runForkProcess(params, env, signal) {
|
|
16
|
+
throwIfAborted(signal);
|
|
10
17
|
const threadId = cleanId(params.threadId, 'threadId');
|
|
11
18
|
const lastTurnId = cleanId(params.lastTurnId, 'lastTurnId');
|
|
12
19
|
const childEnv = buildCodexHomeEnv(env, {
|
|
20
|
+
isolateRuntimeHome: true,
|
|
21
|
+
enabledCodexOverleafSkillIds: params.enabledCodexOverleafSkillIds,
|
|
22
|
+
enabledCodexLocalSkillIds: params.enabledCodexLocalSkillIds,
|
|
13
23
|
loadCodexLocalSkills: params.loadCodexLocalSkills !== false,
|
|
14
24
|
loadCodexOverleafSkills: params.loadCodexOverleafSkills !== false
|
|
15
25
|
});
|
|
@@ -23,6 +33,7 @@ function forkCodexThread(params = {}, env = process.env) {
|
|
|
23
33
|
}), {
|
|
24
34
|
env: childEnv,
|
|
25
35
|
shell: shouldUseShellForCommand(command, childEnv),
|
|
36
|
+
detached: process.platform !== 'win32',
|
|
26
37
|
stdio: ['pipe', 'pipe', 'pipe']
|
|
27
38
|
});
|
|
28
39
|
const pending = new Map();
|
|
@@ -30,6 +41,8 @@ function forkCodexThread(params = {}, env = process.env) {
|
|
|
30
41
|
let buffer = '';
|
|
31
42
|
let stderr = '';
|
|
32
43
|
let settled = false;
|
|
44
|
+
const onAbort = () => finish(signal.reason);
|
|
45
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
33
46
|
const timeout = setTimeout(() => finish(codedError('codex_thread_fork_timeout', 'Codex thread fork timed out.')), 30000);
|
|
34
47
|
|
|
35
48
|
child.stdout.setEncoding('utf8');
|
|
@@ -41,12 +54,14 @@ function forkCodexThread(params = {}, env = process.env) {
|
|
|
41
54
|
for (const line of lines) if (line.trim()) handleMessage(line);
|
|
42
55
|
});
|
|
43
56
|
child.stderr.on('data', chunk => { stderr += chunk; });
|
|
57
|
+
child.stdin.on('error', finish);
|
|
44
58
|
child.on('error', finish);
|
|
45
59
|
child.on('close', code => {
|
|
46
60
|
if (!settled) finish(codedError('codex_thread_fork_failed', stderr || `Codex app-server exited with code ${code}.`));
|
|
47
61
|
});
|
|
48
62
|
|
|
49
63
|
(async () => {
|
|
64
|
+
throwIfAborted(signal);
|
|
50
65
|
await request('initialize', {
|
|
51
66
|
clientInfo: { name: 'codex-overleaf-link', version: PACKAGE_VERSION },
|
|
52
67
|
capabilities: null
|
|
@@ -85,11 +100,12 @@ function forkCodexThread(params = {}, env = process.env) {
|
|
|
85
100
|
if (settled) return;
|
|
86
101
|
settled = true;
|
|
87
102
|
clearTimeout(timeout);
|
|
103
|
+
signal.removeEventListener('abort', onAbort);
|
|
88
104
|
for (const waiter of pending.values()) waiter.reject(error || codedError('codex_thread_fork_closed', 'Fork process closed.'));
|
|
89
105
|
pending.clear();
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
106
|
+
stopCodexAppServer(child, { processGroup: process.platform !== 'win32', reason: error }).then(() => {
|
|
107
|
+
if (error) reject(error); else resolve(result);
|
|
108
|
+
}, reject);
|
|
93
109
|
}
|
|
94
110
|
});
|
|
95
111
|
}
|
package/native-host/src/index.js
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
const { decodeInputFrames, encodeOutputFrame } = require('./nativeTransportEnvelope');
|
|
5
5
|
const { logDebug } = require('./debugLog');
|
|
6
6
|
const { handleRequest } = require('./taskRunner');
|
|
7
|
-
const { abortAllActiveOperations, getActiveNativeWorkState } = require('./taskRunnerRuntime');
|
|
8
|
-
const { handleUpdateRequest, isUpdateMethod } = require('./updateManager');
|
|
7
|
+
const { abortAllActiveOperations, quarantineActiveOperations, getActiveNativeWorkState } = require('./taskRunnerRuntime');
|
|
8
|
+
const { handleUpdateRequest, isUpdateMethod, assertBootstrapBusinessAvailable } = require('./updateManager');
|
|
9
9
|
const { buildNativeRuntimeEnv, summarizeNativeEnvironment } = require('./nativeEnvironment');
|
|
10
10
|
|
|
11
11
|
let buffered = Buffer.alloc(0);
|
|
@@ -41,12 +41,14 @@ process.stdin.on('data', chunk => {
|
|
|
41
41
|
});
|
|
42
42
|
|
|
43
43
|
async function handleDecodedMessage(message) {
|
|
44
|
+
if (nativeDisconnectHandled) return;
|
|
44
45
|
try {
|
|
45
46
|
logDebug('request.received', summarizeRequest(message));
|
|
47
|
+
assertBootstrapBusinessAvailable(message, { env: runtimeEnv });
|
|
46
48
|
const response = isUpdateMethod(message?.method)
|
|
47
49
|
? await handleUpdateRequest(message, {
|
|
48
50
|
env: runtimeEnv,
|
|
49
|
-
getWorkState: getActiveNativeWorkState
|
|
51
|
+
getWorkState: () => getActiveNativeWorkState(runtimeEnv)
|
|
50
52
|
})
|
|
51
53
|
: await handleRequest(message, runtimeEnv, event => {
|
|
52
54
|
writeResponse({
|
|
@@ -62,7 +64,7 @@ async function handleDecodedMessage(message) {
|
|
|
62
64
|
id: message?.id,
|
|
63
65
|
ok: false,
|
|
64
66
|
error: {
|
|
65
|
-
code: 'internal_error',
|
|
67
|
+
code: /^[a-z0-9_]{1,80}$/.test(String(error?.code || '')) ? error.code : 'internal_error',
|
|
66
68
|
message: error.message
|
|
67
69
|
}
|
|
68
70
|
};
|
|
@@ -145,10 +147,20 @@ function handleNativeDisconnect(source, error) {
|
|
|
145
147
|
code: error?.code,
|
|
146
148
|
message: error?.message
|
|
147
149
|
});
|
|
148
|
-
abortAllActiveOperations('Native messaging transport disconnected.');
|
|
149
150
|
process.exitCode = 0;
|
|
150
|
-
|
|
151
|
-
|
|
151
|
+
// Publish protection before stopping: another Host may reconnect while the
|
|
152
|
+
// old owned process is still inside its interrupt/termination budget.
|
|
153
|
+
quarantineActiveOperations();
|
|
154
|
+
const exitTimer = setTimeout(() => {
|
|
155
|
+
const failures = quarantineActiveOperations();
|
|
156
|
+
logDebug('native_transport.shutdown_deadline', { persistenceFailures: failures });
|
|
157
|
+
process.exit(failures.length ? 1 : 0);
|
|
158
|
+
}, 5000);
|
|
159
|
+
// Keep the prior grace for other entry points while owned Codex runs drain.
|
|
160
|
+
Promise.all([
|
|
161
|
+
abortAllActiveOperations('Native messaging transport disconnected.'),
|
|
162
|
+
new Promise(resolve => setTimeout(resolve, 1500))
|
|
163
|
+
]).then(() => { clearTimeout(exitTimer); process.exit(0); });
|
|
152
164
|
}
|
|
153
165
|
|
|
154
166
|
function summarizeRequest(message) {
|
|
@@ -18,8 +18,11 @@ const {
|
|
|
18
18
|
getDefaultManagedRoot,
|
|
19
19
|
getNativeHostRegistrationTarget
|
|
20
20
|
} = require('./nativeHostPlatform');
|
|
21
|
+
const { queryWindowsNativeHostRegistration } = require('./nativeHostRegistration');
|
|
21
22
|
const { buildRuntimeFileManifest } = require('./runtimeInstaller');
|
|
22
23
|
const { BOOTSTRAP_PROTOCOL, parseSemver, updateError } = require('./updateTrust');
|
|
24
|
+
const { createInstallRestorePoint } = require('./managedInstallRecovery');
|
|
25
|
+
const { settleReinstalledUpdate } = require('./updateManager');
|
|
23
26
|
|
|
24
27
|
const MANAGED_BY = 'codex-overleaf-link';
|
|
25
28
|
const EXTENSION_MARKER = '.codex-overleaf-managed-extension.json';
|
|
@@ -61,51 +64,91 @@ function installManagedDistribution(options = {}) {
|
|
|
61
64
|
version,
|
|
62
65
|
releaseRef,
|
|
63
66
|
releaseChannel,
|
|
64
|
-
allowedFiles: options.allowedFiles
|
|
67
|
+
allowedFiles: options.allowedFiles,
|
|
68
|
+
installIdentity: { managedRoot, browser: browser === 'auto' ? 'chrome' : browser, extensionId, platform }
|
|
65
69
|
});
|
|
66
70
|
} catch (error) {
|
|
67
71
|
safeRemove(extensionStage);
|
|
68
72
|
throw error;
|
|
69
73
|
}
|
|
70
74
|
|
|
71
|
-
|
|
75
|
+
let previousExtensionMarker = readManagedMarker(extensionRoot, EXTENSION_MARKER);
|
|
72
76
|
if (fs.existsSync(extensionRoot) && !isManagedMarker(previousExtensionMarker, 'extension')) {
|
|
73
77
|
safeRemove(extensionStage);
|
|
74
78
|
throw new Error('Refusing to replace an unmarked managed extension root: ' + extensionRoot);
|
|
75
79
|
}
|
|
76
80
|
let extensionRollbackCreated = false;
|
|
81
|
+
let restorePoint;
|
|
82
|
+
let previousTransaction;
|
|
83
|
+
const registration = getNativeHostRegistrationTarget({ platform, browser, env });
|
|
84
|
+
const bridgePath = getDefaultBridgePath({ platform, browser, env });
|
|
85
|
+
let previousRegistration = null;
|
|
86
|
+
let registrationAttempted = false;
|
|
77
87
|
try {
|
|
88
|
+
const nativeMarker = readManagedMarker(nativeRoot, NATIVE_MARKER);
|
|
89
|
+
if (fs.existsSync(nativeRoot) && !isManagedMarker(nativeMarker, 'native')) {
|
|
90
|
+
throw new Error('Refusing to replace an unmarked managed native root: ' + nativeRoot);
|
|
91
|
+
}
|
|
92
|
+
restorePoint = createInstallRestorePoint({ nativeRoot, version, files: [
|
|
93
|
+
path.join(nativeRoot, 'active-version'), path.join(nativeRoot, 'previous-version'),
|
|
94
|
+
path.join(nativeRoot, NATIVE_MARKER), path.join(nativeRoot, 'bootstrap', 'launcher.js'),
|
|
95
|
+
path.join(nativeRoot, 'updates', 'transaction.json'), path.join(nativeRoot, 'updates', 'authorization.json'),
|
|
96
|
+
path.join(managedRoot, '.codex-overleaf-managed.json'), bridgePath, registration.manifestPath
|
|
97
|
+
] });
|
|
98
|
+
previousExtensionMarker = readManagedMarker(extensionRoot, EXTENSION_MARKER);
|
|
99
|
+
if (fs.existsSync(extensionRoot) && !isManagedMarker(previousExtensionMarker, 'extension')) {
|
|
100
|
+
throw new Error('Refusing to replace an unmarked managed extension root: ' + extensionRoot);
|
|
101
|
+
}
|
|
102
|
+
previousRegistration = registration.kind === 'registry'
|
|
103
|
+
? queryWindowsNativeHostRegistration(registration, { env }) : null;
|
|
104
|
+
previousTransaction = readManagedMarker(path.join(nativeRoot, 'updates'), 'transaction.json');
|
|
78
105
|
if (fs.existsSync(extensionRoot)) {
|
|
79
106
|
fs.renameSync(extensionRoot, extensionRollback);
|
|
80
107
|
extensionRollbackCreated = true;
|
|
81
108
|
}
|
|
82
109
|
fs.renameSync(extensionStage, extensionRoot);
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
if (extensionRollbackCreated && !fs.existsSync(extensionRoot)) {
|
|
86
|
-
fs.renameSync(extensionRollback, extensionRoot);
|
|
87
|
-
}
|
|
88
|
-
throw error;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
try {
|
|
92
|
-
installManagedNativeVersion({ packageRoot, nativeRoot, version, releaseRef, releaseChannel });
|
|
110
|
+
installManagedNativeVersion({ packageRoot, nativeRoot, version, releaseRef, releaseChannel,
|
|
111
|
+
allowUnmarkedRoot: !restorePoint.nativeExisted });
|
|
93
112
|
installManagedNativeRegistration({
|
|
94
113
|
browser,
|
|
95
114
|
env,
|
|
96
115
|
extensionId,
|
|
97
116
|
managedRoot,
|
|
98
117
|
nativeRoot,
|
|
99
|
-
platform
|
|
118
|
+
platform,
|
|
119
|
+
onRegistryMutation: () => { registrationAttempted = true; }
|
|
100
120
|
});
|
|
101
|
-
|
|
121
|
+
settleReinstalledUpdate({ managed: true, nativeRoot, extensionRoot,
|
|
122
|
+
updatesRoot: path.join(nativeRoot, 'updates'), env }, version, previousTransaction);
|
|
102
123
|
} catch (error) {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
124
|
+
const failures = [];
|
|
125
|
+
try {
|
|
126
|
+
safeRemove(extensionStage);
|
|
127
|
+
if (extensionRollbackCreated && fs.existsSync(extensionRollback)) {
|
|
128
|
+
safeRemove(extensionRoot);
|
|
129
|
+
fs.renameSync(extensionRollback, extensionRoot);
|
|
130
|
+
} else if (!previousExtensionMarker && restorePoint) safeRemove(extensionRoot);
|
|
131
|
+
} catch (failure) { failures.push(failure); }
|
|
132
|
+
if (restorePoint) {
|
|
133
|
+
try { restorePoint.restore(); } catch (failure) { failures.push(failure); }
|
|
134
|
+
if (registrationAttempted && registration.kind === 'registry') {
|
|
135
|
+
try {
|
|
136
|
+
if (!previousRegistration?.registeredManifestPath) throw new Error('Previous browser registration could not be verified.');
|
|
137
|
+
runRegistry(['add', registration.registryKey, '/ve', '/t', 'REG_SZ', '/d', previousRegistration.registeredManifestPath, '/f']);
|
|
138
|
+
} catch (failure) { failures.push(failure); }
|
|
139
|
+
}
|
|
140
|
+
try { failures.length ? restorePoint.release() : restorePoint.finish(); }
|
|
141
|
+
catch (failure) { failures.push(failure); }
|
|
106
142
|
}
|
|
143
|
+
if (failures.length) throw updateError('managed_install_restore_failed',
|
|
144
|
+
'Managed installation failed and the previous installation could not be fully restored.', { cause: error });
|
|
107
145
|
throw error;
|
|
108
146
|
}
|
|
147
|
+
// The installed pair is committed. Cleanup errors must not trigger a second
|
|
148
|
+
// rollback after some of its retained files have already been removed.
|
|
149
|
+
const warnings = [];
|
|
150
|
+
try { safeRemove(extensionRollback); } catch (_) { warnings.push('Previous extension cleanup is incomplete.'); }
|
|
151
|
+
try { restorePoint.finish(); } catch (_) { warnings.push('Previous Native installation cleanup is incomplete.'); }
|
|
109
152
|
|
|
110
153
|
return {
|
|
111
154
|
ok: true,
|
|
@@ -117,7 +160,8 @@ function installManagedDistribution(options = {}) {
|
|
|
117
160
|
extensionRoot,
|
|
118
161
|
nativeRoot,
|
|
119
162
|
loadUnpackedPath: extensionRoot,
|
|
120
|
-
allowedOrigin: buildAllowedOrigin(extensionId)
|
|
163
|
+
allowedOrigin: buildAllowedOrigin(extensionId),
|
|
164
|
+
...(warnings.length ? { warning: warnings.join(' ') } : {})
|
|
121
165
|
};
|
|
122
166
|
}
|
|
123
167
|
|
|
@@ -154,6 +198,7 @@ function buildManagedExtensionTree(options = {}) {
|
|
|
154
198
|
fs.writeFileSync(path.join(targetRoot, EXTENSION_MARKER), JSON.stringify({
|
|
155
199
|
managedBy: MANAGED_BY,
|
|
156
200
|
kind: 'extension',
|
|
201
|
+
...(options.installIdentity ? { installIdentity: options.installIdentity } : {}),
|
|
157
202
|
bootstrapProtocol: BOOTSTRAP_PROTOCOL,
|
|
158
203
|
version,
|
|
159
204
|
releaseRef,
|
|
@@ -163,9 +208,9 @@ function buildManagedExtensionTree(options = {}) {
|
|
|
163
208
|
return { targetRoot, version };
|
|
164
209
|
}
|
|
165
210
|
|
|
166
|
-
function installManagedNativeVersion({ packageRoot, nativeRoot, version, releaseRef = `v${version}`, releaseChannel = 'stable' }) {
|
|
211
|
+
function installManagedNativeVersion({ packageRoot, nativeRoot, version, releaseRef = `v${version}`, releaseChannel = 'stable', allowUnmarkedRoot = false }) {
|
|
167
212
|
const marker = readManagedMarker(nativeRoot, NATIVE_MARKER);
|
|
168
|
-
if (fs.existsSync(nativeRoot) && !isManagedMarker(marker, 'native')) {
|
|
213
|
+
if (fs.existsSync(nativeRoot) && !isManagedMarker(marker, 'native') && !allowUnmarkedRoot) {
|
|
169
214
|
throw new Error('Refusing to replace an unmarked managed native root: ' + nativeRoot);
|
|
170
215
|
}
|
|
171
216
|
fs.mkdirSync(path.join(nativeRoot, 'versions'), { recursive: true });
|
|
@@ -174,18 +219,21 @@ function installManagedNativeVersion({ packageRoot, nativeRoot, version, release
|
|
|
174
219
|
|
|
175
220
|
const stage = path.join(nativeRoot, 'versions', '.' + version + '.staging-' + process.pid + '-' + Date.now());
|
|
176
221
|
const target = path.join(nativeRoot, 'versions', version);
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
version,
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
safeRemove(target);
|
|
222
|
+
try {
|
|
223
|
+
for (const entry of buildRuntimeFileManifest({ packageRoot })) {
|
|
224
|
+
copyFile(entry.sourcePath, path.join(stage, entry.relativePath));
|
|
225
|
+
}
|
|
226
|
+
fs.writeFileSync(path.join(stage, '.codex-overleaf-version.json'), JSON.stringify({
|
|
227
|
+
version,
|
|
228
|
+
releaseRef,
|
|
229
|
+
releaseChannel
|
|
230
|
+
}, null, 2) + '\n');
|
|
231
|
+
if (fs.existsSync(target)) safeRemove(target);
|
|
232
|
+
fs.renameSync(stage, target);
|
|
233
|
+
} catch (error) {
|
|
234
|
+
safeRemove(stage);
|
|
235
|
+
throw error;
|
|
187
236
|
}
|
|
188
|
-
fs.renameSync(stage, target);
|
|
189
237
|
|
|
190
238
|
copyFile(
|
|
191
239
|
path.join(packageRoot, 'native-host/src/managedLauncherRuntime.js'),
|
|
@@ -205,7 +253,7 @@ function installManagedNativeVersion({ packageRoot, nativeRoot, version, release
|
|
|
205
253
|
}, null, 2) + '\n', 'utf8');
|
|
206
254
|
}
|
|
207
255
|
|
|
208
|
-
function installManagedNativeRegistration({ browser, env, extensionId, managedRoot, nativeRoot, platform }) {
|
|
256
|
+
function installManagedNativeRegistration({ browser, env, extensionId, managedRoot, nativeRoot, platform, onRegistryMutation }) {
|
|
209
257
|
const bridgePath = getDefaultBridgePath({ platform, browser, env });
|
|
210
258
|
const platformPath = platform === 'win32' ? path.win32 : path.posix;
|
|
211
259
|
const bootstrapEntry = platformPath.join(nativeRoot, 'bootstrap', 'launcher.js');
|
|
@@ -226,6 +274,7 @@ function installManagedNativeRegistration({ browser, env, extensionId, managedRo
|
|
|
226
274
|
fs.mkdirSync(platformPath.dirname(target.manifestPath), { recursive: true });
|
|
227
275
|
fs.writeFileSync(target.manifestPath, JSON.stringify(manifest, null, 2) + '\n', 'utf8');
|
|
228
276
|
if (target.kind === 'registry') {
|
|
277
|
+
onRegistryMutation?.();
|
|
229
278
|
runRegistry(['add', target.registryKey, '/ve', '/t', 'REG_SZ', '/d', target.manifestPath, '/f']);
|
|
230
279
|
}
|
|
231
280
|
fs.writeFileSync(path.join(managedRoot, '.codex-overleaf-managed.json'), JSON.stringify({
|
|
@@ -255,11 +304,19 @@ function uninstallManagedDistribution(options = {}) {
|
|
|
255
304
|
}
|
|
256
305
|
const registration = getNativeHostRegistrationTarget({ platform, browser, env });
|
|
257
306
|
const bridgePath = getDefaultBridgePath({ platform, browser, env });
|
|
258
|
-
|
|
307
|
+
const bridgeOwned = () => isBridgeForManagedRoot(bridgePath, nativeRoot, platform);
|
|
308
|
+
const manifestOwned = () => {
|
|
309
|
+
try {
|
|
310
|
+
const manifest = readJson(registration.manifestPath);
|
|
311
|
+
return bridgeOwned() && manifest.name === 'com.codex.overleaf' && manifest.path === bridgePath;
|
|
312
|
+
} catch (_) { return false; }
|
|
313
|
+
};
|
|
314
|
+
if (registration.kind === 'registry' && manifestOwned()
|
|
315
|
+
&& queryWindowsNativeHostRegistration(registration, { env }).verified) {
|
|
259
316
|
runRegistry(['delete', registration.registryKey, '/f'], { allowMissing: true });
|
|
260
317
|
}
|
|
261
|
-
fs.rmSync(registration.manifestPath, { force: true });
|
|
262
|
-
fs.rmSync(bridgePath, { force: true });
|
|
318
|
+
if (manifestOwned()) fs.rmSync(registration.manifestPath, { force: true });
|
|
319
|
+
if (bridgeOwned()) fs.rmSync(bridgePath, { force: true });
|
|
263
320
|
safeRemove(extensionRoot);
|
|
264
321
|
safeRemove(nativeRoot);
|
|
265
322
|
try {
|
|
@@ -272,6 +329,24 @@ function uninstallManagedDistribution(options = {}) {
|
|
|
272
329
|
return { ok: true, managedRoot, extensionRoot, nativeRoot, removed: true };
|
|
273
330
|
}
|
|
274
331
|
|
|
332
|
+
// Older installs lack ownership metadata. Compare the existing launcher's
|
|
333
|
+
// exact target argument and agent assignment; the Node executable may change.
|
|
334
|
+
function isBridgeForManagedRoot(bridgePath, nativeRoot, platform) {
|
|
335
|
+
try {
|
|
336
|
+
const platformPath = platform === 'win32' ? path.win32 : path.posix;
|
|
337
|
+
const entry = platformPath.join(nativeRoot, 'bootstrap', 'launcher.js');
|
|
338
|
+
const expected = buildLauncher({ platform, nodePath: '__node__', bridgeEntryPath: entry, agentPath: entry }).split(/\r?\n/);
|
|
339
|
+
const actual = fs.readFileSync(bridgePath, 'utf8').split(/\r?\n/);
|
|
340
|
+
const assignment = expected.find(line => line.includes('CODEX_OVERLEAF_AGENT_ARGS_JSON='));
|
|
341
|
+
const launch = expected.find(line => platform === 'win32' ? line.startsWith('"__node__" ') : line.startsWith('exec "__node__" '));
|
|
342
|
+
const target = launch.slice(launch.indexOf('"__node__"') + '"__node__"'.length);
|
|
343
|
+
const invocation = platform === 'win32' ? /^"(?:[^"]|"")*"/ : /^exec "(?:[^"\\]|\\.)+"/;
|
|
344
|
+
return actual.includes(assignment) && actual.some(line => {
|
|
345
|
+
const first = line.match(invocation); return first && line.slice(first[0].length) === target;
|
|
346
|
+
});
|
|
347
|
+
} catch (_) { return false; }
|
|
348
|
+
}
|
|
349
|
+
|
|
275
350
|
function assertSafeManagedRoot(root, options = {}) {
|
|
276
351
|
const platformPath = options.platformPath || path;
|
|
277
352
|
const resolved = platformPath.resolve(root);
|