codex-overleaf-link 2.5.0 → 2.6.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.
- package/README.md +155 -392
- package/README.zh-CN.md +154 -393
- 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 +283 -55
- package/extension/src/backgroundBootstrapMigration.js +181 -0
- package/extension/src/backgroundSharedSessions.js +129 -16
- package/extension/src/backgroundUpdateCoordinator.js +242 -29
- package/extension/src/backgroundUpdateSafety.js +173 -0
- package/extension/src/content/activeTurnControl.js +171 -8
- package/extension/src/content/applyResultFormatters.js +46 -73
- package/extension/src/content/assetTransferBroker.js +72 -14
- package/extension/src/content/composerAttachments.js +104 -47
- package/extension/src/content/composerPanel.js +110 -12
- package/extension/src/content/contentRuntime.js +1555 -926
- package/extension/src/content/contextTray.js +8 -2
- package/extension/src/content/contextUsageView.js +267 -0
- package/extension/src/content/diagnosticsController.js +122 -96
- package/extension/src/content/diagnosticsExport.js +19 -5
- package/extension/src/content/diagnosticsPanel.js +48 -0
- package/extension/src/content/directoryCleanupRecovery.js +54 -0
- package/extension/src/content/generated/content.bundle.js +11077 -3412
- package/extension/src/content/generated/content.bundle.meta.json +14 -3
- package/extension/src/content/globalPreferencesController.js +1 -1
- package/extension/src/content/legacyGlobalRegistry.js +10 -0
- package/extension/src/content/localSkillsPanel.js +104 -14
- package/extension/src/content/markdownDomRenderer.js +5 -3
- package/extension/src/content/markdownText.js +47 -11
- package/extension/src/content/modelPicker.js +8 -4
- package/extension/src/content/nativeCompatibilityController.js +74 -30
- package/extension/src/content/otWarmMirror.js +5 -6
- package/extension/src/content/otWarmMirrorController.js +17 -20
- package/extension/src/content/pageBridgeClient.js +17 -4
- package/extension/src/content/panelMaintenance.js +46 -14
- package/extension/src/content/panelRenderer.js +57 -27
- package/extension/src/content/pendingInputView.js +1 -1
- package/extension/src/content/projectProviderSelection.js +13 -4
- package/extension/src/content/projectSessionCleanup.js +109 -62
- package/extension/src/content/projectSettingsCoordinator.js +67 -6
- package/extension/src/content/providerSettingsCoordinator.js +46 -8
- package/extension/src/content/providerSettingsDialog.js +73 -14
- package/extension/src/content/recentProjects.js +15 -8
- package/extension/src/content/runActivitySummary.js +160 -18
- package/extension/src/content/runCompletionMeta.js +532 -0
- package/extension/src/content/runController.js +39 -15
- package/extension/src/content/runFailureNotice.js +29 -13
- package/extension/src/content/runGuidanceController.js +72 -27
- package/extension/src/content/runGuidanceView.js +59 -10
- package/extension/src/content/runPresence.js +18 -8
- package/extension/src/content/runQueueScheduler.js +157 -47
- package/extension/src/content/runRecoveryActions.js +147 -0
- package/extension/src/content/runResultActions.js +28 -143
- package/extension/src/content/runReviewController.js +633 -0
- package/extension/src/content/runScrollLayout.js +21 -2
- package/extension/src/content/runTimelineView.js +367 -175
- package/extension/src/content/scopedPersistencePanelState.js +134 -20
- package/extension/src/content/selectionContextView.js +1 -1
- package/extension/src/content/sensitivePreflight.js +153 -0
- package/extension/src/content/sessionForkController.js +80 -17
- package/extension/src/content/sessionManager.js +50 -4
- package/extension/src/content/sessionMenuView.js +54 -18
- package/extension/src/content/sessionPersistence.js +25 -5
- package/extension/src/content/settingsPanel.js +22 -5
- package/extension/src/content/settingsWorkbench.js +132 -89
- package/extension/src/content/sharedSessionViewSync.js +49 -11
- package/extension/src/content/subagentActivityView.js +23 -7
- package/extension/src/content/updateIdle.js +4 -1
- package/extension/src/content/updateNotice.js +118 -12
- package/extension/src/content/updateSafetyReceiver.js +58 -0
- package/extension/src/content/writebackController.js +53 -4
- package/extension/src/content/writebackOrchestrator.js +116 -94
- package/extension/src/content/writingStyleSettings.js +35 -11
- package/extension/src/page/binaryAssetUploader.js +136 -14
- package/extension/src/page/compileBridge.js +193 -180
- package/extension/src/page/fileMover.js +256 -0
- package/extension/src/page/overleafCapabilities.js +4 -1
- 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/textCoordinates.js +17 -1
- package/extension/src/page/textFileCreator.js +114 -26
- 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 +103 -155
- package/extension/src/pageBridge.js +126 -69
- package/extension/src/popup.js +62 -10
- package/extension/src/shared/agentTranscript.js +373 -17
- package/extension/src/shared/auditRecords.js +4 -0
- package/extension/src/shared/compatibility.js +22 -6
- package/extension/src/shared/compileAdapter.js +129 -66
- package/extension/src/shared/composerAttachmentDraft.js +156 -0
- package/extension/src/shared/contextUsage.js +125 -0
- package/extension/src/shared/directoryCleanupProof.js +43 -0
- package/extension/src/shared/failureReasons.js +349 -4
- package/extension/src/shared/globalPreferences.js +30 -13
- package/extension/src/shared/governanceRules.js +4 -37
- package/extension/src/shared/i18n.js +590 -24
- package/extension/src/shared/lineReferences.js +38 -31
- 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/panelIcons.js +39 -0
- package/extension/src/shared/projectFiles.js +21 -44
- package/extension/src/shared/providerProfiles.js +5 -3
- package/extension/src/shared/reviewing.js +130 -1
- package/extension/src/shared/runActivityModel.js +70 -4
- package/extension/src/shared/runExecutionSnapshot.js +13 -0
- package/extension/src/shared/runInputQueue.js +48 -5
- package/extension/src/shared/selectionContext.js +12 -3
- package/extension/src/shared/sessionState.js +132 -28
- package/extension/src/shared/sessionTitle.js +14 -1
- package/extension/src/shared/settlementFacts.js +10 -2
- package/extension/src/shared/sharedSessionBridge.js +118 -27
- package/extension/src/shared/sharedSessionTransfer.js +140 -0
- package/extension/src/shared/staleGuard.js +166 -52
- package/extension/src/shared/storageDb.js +90 -26
- package/extension/src/shared/storageMigration.js +78 -112
- package/extension/src/shared/storageRunActions.js +267 -30
- package/extension/src/shared/subagentActivityModel.js +1 -1
- package/extension/src/shared/undoOperations.js +253 -32
- package/extension/src/shared/updateConsent.js +6 -4
- package/extension/src/shared/updateRuntimeIdentity.js +17 -3
- package/extension/src/shared/writebackIntent.js +16 -0
- package/extension/src/shared/writebackOperationEvidence.js +4 -15
- package/extension/src/shared/writebackSettlement.js +58 -40
- package/extension/styles/panel.css +1123 -493
- 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 +269 -0
- package/native-host/src/bootstrapUpdateWatchdog.js +44 -0
- package/native-host/src/chatBridgeRequest.js +60 -6
- package/native-host/src/chatBridgeResponse.js +20 -1
- package/native-host/src/chatCompletionsBridge.js +48 -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 +114 -127
- package/native-host/src/codexProviderTest.js +24 -26
- package/native-host/src/codexSessionRunner.js +234 -198
- 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/providerContextError.js +20 -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 +92 -57
- package/native-host/src/textPatch.js +473 -377
- package/native-host/src/tomlStatements.js +140 -0
- package/native-host/src/updateManager.js +409 -46
- package/native-host/src/writingStyleRuntime.js +57 -29
- package/native-host/src/writingStyleStore.js +73 -0
- package/package.json +5 -8
- package/scripts/install-managed.mjs +1 -0
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const crypto = require('node:crypto');
|
|
4
|
+
const fs = require('node:fs');
|
|
5
|
+
const path = require('node:path');
|
|
6
|
+
const { spawn } = require('node:child_process');
|
|
7
|
+
const { version: PACKAGE_VERSION } = require('../../package.json');
|
|
8
|
+
const { getCodexOverleafSkillsRoot } = require('./localSkills');
|
|
9
|
+
const { resolveCodexCommand, shouldUseShellForCommand } = require('./codexCommand');
|
|
10
|
+
const { runOwnedNativeHelper } = require('./nativeHelperLifecycle');
|
|
11
|
+
const { stopCodexAppServer, throwIfAborted, getAbortReason } = require('./codexSessionTiming');
|
|
12
|
+
|
|
13
|
+
const CODEX_LOCAL_SKILL_SCOPE = 'codex-local';
|
|
14
|
+
const MAX_SKILL_BYTES = 64 * 1024;
|
|
15
|
+
const MAX_DISCOVERY_BYTES = 4 * 1024 * 1024;
|
|
16
|
+
const DISCOVERY_TIMEOUT_MS = 30000;
|
|
17
|
+
|
|
18
|
+
// System skills are regenerated inside each scoped home. Ordinary skills keep
|
|
19
|
+
// their physical source identity, including aliases and same-name skills.
|
|
20
|
+
function createCodexLocalSkillIdentity(skill = {}, env = process.env) {
|
|
21
|
+
const name = String(skill.name || '').trim();
|
|
22
|
+
let source;
|
|
23
|
+
if (skill.scope === 'system' && name) source = `system:${name}`;
|
|
24
|
+
else {
|
|
25
|
+
const location = canonicalSkillPath(skill.path);
|
|
26
|
+
if (!location) return '';
|
|
27
|
+
source = `path:${location}`;
|
|
28
|
+
}
|
|
29
|
+
return `local-${crypto.createHash('sha256').update(source).digest('hex')}`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function isCodexOverleafSkillSource(skill = {}, env = process.env) {
|
|
33
|
+
if (!path.isAbsolute(String(skill.path || ''))) return false;
|
|
34
|
+
const root = getCodexOverleafSkillsRoot({ env });
|
|
35
|
+
const roots = comparablePaths(root);
|
|
36
|
+
return comparablePaths(skill.path).some(candidate => roots.some(base => isInside(candidate, base)));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function listCodexLocalSkills(options = {}) {
|
|
40
|
+
const { entries, discoveryWarnings } = await discoverLocalSkills(options);
|
|
41
|
+
return { skills: entries.map(entry => entry.metadata), discoveryWarnings };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function loadSelectedCodexLocalSkill({ skillId, loadCodexLocalSkills = true,
|
|
45
|
+
enabledCodexLocalSkillIds, env = process.env, signal, timeoutMs } = {}) {
|
|
46
|
+
const id = String(skillId || '').trim();
|
|
47
|
+
const empty = { skill: null, missing: [], ignored: [] };
|
|
48
|
+
if (!id) return empty;
|
|
49
|
+
if (loadCodexLocalSkills === false) return { ...empty, ignored: [{ id, reason: 'codex_local_skills_disabled' }] };
|
|
50
|
+
if (Array.isArray(enabledCodexLocalSkillIds) && !enabledCodexLocalSkillIds.includes(id)) {
|
|
51
|
+
return { ...empty, ignored: [{ id, reason: 'codex_local_skill_disabled' }] };
|
|
52
|
+
}
|
|
53
|
+
if (!/^local-[a-f0-9]{64}$/.test(id)) return { ...empty, missing: [id] };
|
|
54
|
+
const { entries } = await discoverLocalSkills({ env, signal, timeoutMs });
|
|
55
|
+
throwIfAborted(signal);
|
|
56
|
+
const entry = entries.find(item => item.metadata.id === id);
|
|
57
|
+
if (!entry) return { ...empty, missing: [id] };
|
|
58
|
+
if (!Array.isArray(enabledCodexLocalSkillIds) && entry.metadata.enabled === false) {
|
|
59
|
+
return { ...empty, ignored: [{ id, reason: 'codex_local_skill_disabled' }] };
|
|
60
|
+
}
|
|
61
|
+
const content = readSelectedContent(entry);
|
|
62
|
+
if (content === null) return { ...empty, missing: [id] };
|
|
63
|
+
return { ...empty, skill: { ...entry.metadata, path: entry.canonicalPath, content } };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function discoverLocalSkills({ env = process.env, signal, timeoutMs = DISCOVERY_TIMEOUT_MS } = {}) {
|
|
67
|
+
return runOwnedNativeHelper(async ownedSignal => {
|
|
68
|
+
throwIfAborted(ownedSignal);
|
|
69
|
+
// Lazy import avoids the home -> isolation -> identity helper cycle.
|
|
70
|
+
const { buildCodexHomeEnv } = require('./codexHome');
|
|
71
|
+
const childEnv = buildCodexHomeEnv(env, { isolateRuntimeHome: true, forceIsolatedSkillSettings: true,
|
|
72
|
+
loadCodexLocalSkills: true, loadCodexOverleafSkills: false });
|
|
73
|
+
throwIfAborted(ownedSignal);
|
|
74
|
+
const result = await runSkillsMetadataProcess(childEnv, ownedSignal, timeoutMs);
|
|
75
|
+
throwIfAborted(ownedSignal);
|
|
76
|
+
return normalizeCatalog(result, childEnv);
|
|
77
|
+
}, signal);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function normalizeCatalog(result, env) {
|
|
81
|
+
if (!Array.isArray(result?.data)) throw codedError('codex_local_skills_invalid_response', 'Codex returned an invalid local skill catalog.');
|
|
82
|
+
const entries = new Map();
|
|
83
|
+
let skippedCount = 0;
|
|
84
|
+
for (const group of result.data) {
|
|
85
|
+
if (!Array.isArray(group?.skills)) throw codedError('codex_local_skills_invalid_response', 'Codex returned an invalid local skill catalog.');
|
|
86
|
+
if (group.errors !== undefined && !Array.isArray(group.errors)) {
|
|
87
|
+
throw codedError('codex_local_skills_invalid_response', 'Codex returned an invalid local skill catalog.');
|
|
88
|
+
}
|
|
89
|
+
// Invalid source files are omitted by Codex. Keep valid skills available
|
|
90
|
+
// while exposing only a count, never filesystem paths or error contents.
|
|
91
|
+
skippedCount += group.errors?.length || 0;
|
|
92
|
+
for (const skill of group.skills) {
|
|
93
|
+
if (!skill || typeof skill !== 'object' || isCodexOverleafSkillSource(skill, env)) continue;
|
|
94
|
+
const name = cleanText(skill.name, 160);
|
|
95
|
+
const canonicalPath = canonicalSkillPath(skill.path);
|
|
96
|
+
const id = createCodexLocalSkillIdentity(skill, env);
|
|
97
|
+
if (!id || !name || !canonicalPath) continue;
|
|
98
|
+
const sourceKind = getSkillSourceKind(skill, canonicalPath, env);
|
|
99
|
+
const description = cleanText(skill.description, 2048);
|
|
100
|
+
const metadata = { id, name, title: cleanText(skill.interface?.displayName, 160) || name,
|
|
101
|
+
description, preview: cleanText(skill.interface?.shortDescription || skill.shortDescription || description, 240),
|
|
102
|
+
scope: CODEX_LOCAL_SKILL_SCOPE, sourceKind,
|
|
103
|
+
sourceLabel: sourceKind === 'plugin' ? cleanText(skill.pluginId, 160) || pluginNamePrefix(name) : '',
|
|
104
|
+
enabled: skill.enabled !== false, removable: false };
|
|
105
|
+
const existing = entries.get(id);
|
|
106
|
+
if (existing) {
|
|
107
|
+
// An enabled alias makes this same physical skill discoverable.
|
|
108
|
+
existing.metadata.enabled ||= metadata.enabled;
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
entries.set(id, { metadata, canonicalPath, discoveredPath: String(skill.path) });
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
const resolvedEntries = Array.from(entries.values());
|
|
115
|
+
const names = new Map(), titles = new Map();
|
|
116
|
+
for (const { metadata } of resolvedEntries) {
|
|
117
|
+
names.set(metadata.name, (names.get(metadata.name) || 0) + 1);
|
|
118
|
+
titles.set(metadata.title, (titles.get(metadata.title) || 0) + 1);
|
|
119
|
+
}
|
|
120
|
+
for (const entry of resolvedEntries) {
|
|
121
|
+
if (names.get(entry.metadata.name) > 1 || titles.get(entry.metadata.title) > 1) {
|
|
122
|
+
entry.metadata.sourceHint = buildSourceHint(entry, env, resolvedEntries);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return {
|
|
126
|
+
entries: resolvedEntries.sort((a, b) => a.metadata.name.localeCompare(b.metadata.name) || a.metadata.id.localeCompare(b.metadata.id)),
|
|
127
|
+
discoveryWarnings: skippedCount ? [{ code: 'codex_local_skills_skipped', count: skippedCount }] : []
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function buildSourceHint(entry, env, entries) {
|
|
132
|
+
if (entry.metadata.sourceKind === 'system') return `.system/${entry.metadata.name}`;
|
|
133
|
+
const { getUserCodexHome } = require('./codexHome');
|
|
134
|
+
const userHome = getUserCodexHome(env), directory = path.dirname(entry.canonicalPath);
|
|
135
|
+
const relativeDirectory = root => {
|
|
136
|
+
for (const base of comparablePaths(root)) {
|
|
137
|
+
if (directory === base || isInside(directory, base)) return path.relative(base, directory).split(path.sep).join('/');
|
|
138
|
+
}
|
|
139
|
+
return null;
|
|
140
|
+
};
|
|
141
|
+
if (entry.metadata.sourceKind === 'plugin') {
|
|
142
|
+
const relative = relativeDirectory(path.join(userHome, 'plugins'));
|
|
143
|
+
if (relative !== null) return `plugins/${relative.replace(/^cache\//, '')}`;
|
|
144
|
+
}
|
|
145
|
+
const relative = relativeDirectory(userHome);
|
|
146
|
+
if (relative !== null) return relative || path.basename(directory);
|
|
147
|
+
// A user skill symlink may point outside the home. Recover its original
|
|
148
|
+
// relative entry from the composed discovery path without exposing its target.
|
|
149
|
+
const discovered = path.resolve(entry.discoveredPath), runtimeSkills = path.join(String(env.CODEX_HOME || ''), 'skills');
|
|
150
|
+
if (isInside(discovered, runtimeSkills)) {
|
|
151
|
+
const inSkills = path.relative(runtimeSkills, discovered);
|
|
152
|
+
if (canonicalSkillPath(path.join(userHome, 'skills', inSkills)) === entry.canonicalPath) {
|
|
153
|
+
return `skills/${path.dirname(inSkills).split(path.sep).join('/')}`;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
const parts = directory.split(path.sep).filter(Boolean);
|
|
157
|
+
const peers = entries.filter(other => other !== entry && (other.metadata.name === entry.metadata.name
|
|
158
|
+
|| other.metadata.title === entry.metadata.title)).map(other => path.dirname(other.canonicalPath).split(path.sep).filter(Boolean));
|
|
159
|
+
let count = Math.min(3, parts.length);
|
|
160
|
+
while (count < parts.length && peers.some(other => other.slice(-count).join('/') === parts.slice(-count).join('/'))) count++;
|
|
161
|
+
return `…/${parts.slice(-count).join('/')}`;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function getSkillSourceKind(skill, canonicalPath, env) {
|
|
165
|
+
if (skill.scope === 'system') return 'system';
|
|
166
|
+
if (skill.pluginId) return 'plugin';
|
|
167
|
+
// Current app-server metadata reports plugin skills as scope "user" and
|
|
168
|
+
// does not carry a pluginId. Classify only returned files under the active
|
|
169
|
+
// user plugin root; discovering old cache directories is never necessary.
|
|
170
|
+
const { getUserCodexHome } = require('./codexHome');
|
|
171
|
+
const pluginRoots = comparablePaths(path.join(getUserCodexHome(env), 'plugins'));
|
|
172
|
+
if (pluginRoots.some(root => isInside(canonicalPath, root))) return 'plugin';
|
|
173
|
+
return skill.scope === 'user' ? 'user' : 'local';
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function pluginNamePrefix(name) {
|
|
177
|
+
const prefix = name.includes(':') ? name.slice(0, name.indexOf(':')).trim() : '';
|
|
178
|
+
return /[/\\]/.test(prefix) ? '' : prefix;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function runSkillsMetadataProcess(env, signal, timeoutMs) {
|
|
182
|
+
const command = resolveCodexCommand(env);
|
|
183
|
+
if (!command) throw codedError('codex_not_found', 'Codex CLI was not found.');
|
|
184
|
+
return new Promise((resolve, reject) => {
|
|
185
|
+
throwIfAborted(signal);
|
|
186
|
+
const child = spawn(command, ['app-server', '--listen', 'stdio://'], {
|
|
187
|
+
cwd: env.CODEX_HOME, env, shell: shouldUseShellForCommand(command, env),
|
|
188
|
+
detached: process.platform !== 'win32', stdio: ['pipe', 'pipe', 'pipe']
|
|
189
|
+
});
|
|
190
|
+
const pending = new Map();
|
|
191
|
+
let sequence = 0, buffer = '', outputBytes = 0, settled = false;
|
|
192
|
+
const onAbort = () => finish(getAbortReason(signal));
|
|
193
|
+
const budget = Math.min(DISCOVERY_TIMEOUT_MS, Math.max(1, Number(timeoutMs) || DISCOVERY_TIMEOUT_MS));
|
|
194
|
+
const timer = setTimeout(() => finish(codedError('codex_local_skills_timeout', 'Local Codex skill discovery timed out.')), budget);
|
|
195
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
196
|
+
child.stdout.setEncoding('utf8');
|
|
197
|
+
child.stderr.setEncoding('utf8');
|
|
198
|
+
child.stdout.on('data', chunk => {
|
|
199
|
+
if (settled || !countOutput(chunk)) return;
|
|
200
|
+
buffer += chunk;
|
|
201
|
+
const lines = buffer.split(/\r?\n/);
|
|
202
|
+
buffer = lines.pop() || '';
|
|
203
|
+
for (const line of lines) {
|
|
204
|
+
if (settled) break;
|
|
205
|
+
if (line.trim()) handleMessage(line);
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
// Stderr may contain user configuration paths or server diagnostics. Count
|
|
209
|
+
// it for bounded lifetime, but never return it as catalog/UI content.
|
|
210
|
+
child.stderr.on('data', chunk => { if (!settled) countOutput(chunk); });
|
|
211
|
+
child.stdin.on('error', () => finish(codedError('codex_local_skills_failed', 'Local Codex skill discovery failed.')));
|
|
212
|
+
child.on('error', error => finish(codedError(error.code === 'ENOENT' ? 'codex_not_found' : 'codex_local_skills_failed', 'Codex could not start local skill discovery.')));
|
|
213
|
+
child.on('close', () => { if (!settled) finish(codedError('codex_local_skills_failed', 'Codex exited before local skill discovery completed.')); });
|
|
214
|
+
(async () => {
|
|
215
|
+
throwIfAborted(signal);
|
|
216
|
+
await request('initialize', { clientInfo: { name: 'codex-overleaf-link', version: PACKAGE_VERSION }, capabilities: null });
|
|
217
|
+
child.stdin.write(`${JSON.stringify({ method: 'initialized' })}\n`);
|
|
218
|
+
const result = await request('skills/list', { cwd: env.CODEX_HOME, cwds: [env.CODEX_HOME], includeDisabled: true, forceReload: true });
|
|
219
|
+
finish(null, result);
|
|
220
|
+
})().catch(finish);
|
|
221
|
+
|
|
222
|
+
function countOutput(chunk) {
|
|
223
|
+
outputBytes += Buffer.byteLength(chunk);
|
|
224
|
+
if (outputBytes <= MAX_DISCOVERY_BYTES) return true;
|
|
225
|
+
finish(codedError('codex_local_skills_output_limit', 'Local Codex skill discovery exceeded its output limit.'));
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
function request(method, params) {
|
|
229
|
+
return new Promise((resolveRequest, rejectRequest) => {
|
|
230
|
+
if (settled) return rejectRequest(codedError('codex_local_skills_closed', 'Local Codex skill discovery has closed.'));
|
|
231
|
+
const id = ++sequence;
|
|
232
|
+
pending.set(id, { resolve: resolveRequest, reject: rejectRequest });
|
|
233
|
+
child.stdin.write(`${JSON.stringify({ id, method, params })}\n`);
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
function handleMessage(line) {
|
|
237
|
+
let message;
|
|
238
|
+
try { message = JSON.parse(line); } catch { return; }
|
|
239
|
+
const waiter = pending.get(message?.id);
|
|
240
|
+
if (waiter && (Object.hasOwn(message, 'result') || message.error)) {
|
|
241
|
+
pending.delete(message.id);
|
|
242
|
+
if (message.error) waiter.reject(codedError('codex_local_skills_failed', 'Codex could not list local skills.'));
|
|
243
|
+
else waiter.resolve(message.result);
|
|
244
|
+
} else if (message && Object.hasOwn(message, 'id') && message.method) {
|
|
245
|
+
child.stdin.write(`${JSON.stringify({ id: message.id, error: { code: -32601, message: 'Unsupported during skill discovery' } })}\n`);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
function finish(error, result) {
|
|
249
|
+
if (settled) return;
|
|
250
|
+
settled = true;
|
|
251
|
+
clearTimeout(timer);
|
|
252
|
+
signal.removeEventListener('abort', onAbort);
|
|
253
|
+
for (const waiter of pending.values()) waiter.reject(error || codedError('codex_local_skills_closed', 'Local Codex skill discovery has closed.'));
|
|
254
|
+
pending.clear();
|
|
255
|
+
stopCodexAppServer(child, { processGroup: process.platform !== 'win32', reason: error }).then(() => {
|
|
256
|
+
if (error) reject(error); else resolve(result);
|
|
257
|
+
}, reject);
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function readSelectedContent(entry) {
|
|
263
|
+
let fd;
|
|
264
|
+
try {
|
|
265
|
+
if (canonicalSkillPath(entry.discoveredPath) !== entry.canonicalPath) return null;
|
|
266
|
+
fd = fs.openSync(entry.canonicalPath, fs.constants.O_RDONLY | (fs.constants.O_NOFOLLOW || 0));
|
|
267
|
+
const stat = fs.fstatSync(fd);
|
|
268
|
+
if (!stat.isFile() || stat.size > MAX_SKILL_BYTES) return null;
|
|
269
|
+
const bytes = Buffer.alloc(MAX_SKILL_BYTES + 1);
|
|
270
|
+
let length = 0, count;
|
|
271
|
+
while ((count = fs.readSync(fd, bytes, length, bytes.length - length, length)) > 0) {
|
|
272
|
+
length += count;
|
|
273
|
+
if (length > MAX_SKILL_BYTES) return null;
|
|
274
|
+
}
|
|
275
|
+
if (canonicalSkillPath(entry.discoveredPath) !== entry.canonicalPath || bytes.subarray(0, length).includes(0)) return null;
|
|
276
|
+
const current = fs.statSync(entry.canonicalPath);
|
|
277
|
+
if (current.dev !== stat.dev || current.ino !== stat.ino) return null;
|
|
278
|
+
return new TextDecoder('utf-8', { fatal: true }).decode(bytes.subarray(0, length));
|
|
279
|
+
} catch (error) {
|
|
280
|
+
if (['ENOENT', 'ENOTDIR', 'EACCES', 'ELOOP'].includes(error.code) || error instanceof TypeError) return null;
|
|
281
|
+
throw error;
|
|
282
|
+
} finally { if (fd !== undefined) fs.closeSync(fd); }
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function canonicalSkillPath(value) {
|
|
286
|
+
if (!path.isAbsolute(String(value || '')) || path.basename(String(value)) !== 'SKILL.md') return '';
|
|
287
|
+
try {
|
|
288
|
+
const resolved = fs.realpathSync(String(value));
|
|
289
|
+
return fs.statSync(resolved).isFile() ? resolved : '';
|
|
290
|
+
} catch { return ''; }
|
|
291
|
+
}
|
|
292
|
+
function comparablePaths(value) {
|
|
293
|
+
const resolved = path.resolve(value);
|
|
294
|
+
try { return [resolved, fs.realpathSync(resolved)]; } catch { return [resolved]; }
|
|
295
|
+
}
|
|
296
|
+
function isInside(candidate, root) { const relative = path.relative(root, candidate); return relative !== '' && relative !== '..' && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative); }
|
|
297
|
+
function cleanText(value, limit) { return typeof value === 'string' ? value.replace(/\s+/g, ' ').trim().slice(0, limit) : ''; }
|
|
298
|
+
function codedError(code, message) { return Object.assign(new Error(message), { code }); }
|
|
299
|
+
|
|
300
|
+
module.exports = { CODEX_LOCAL_SKILL_SCOPE, createCodexLocalSkillIdentity, isCodexOverleafSkillSource,
|
|
301
|
+
listCodexLocalSkills, loadSelectedCodexLocalSkill };
|