codex-overleaf-link 1.3.0 → 1.3.6
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 +62 -143
- package/extension/src/shared/compatibility.js +1 -1
- package/extension/src/shared/i18n.js +44 -20
- package/extension/src/shared/lineReferences.js +4 -6
- package/extension/src/shared/sessionState.js +31 -5
- package/extension/src/shared/storageDb.js +21 -1
- package/extension/src/shared/undoOperations.js +9 -1
- package/native-host/src/codexHome.js +83 -7
- package/native-host/src/codexSessionRunner.js +3 -0
- package/native-host/src/localSkills.js +46 -7
- package/native-host/src/skills/annotated-rewrite/SKILL.md +71 -0
- package/native-host/src/textPatch.js +669 -16
- package/package.json +1 -1
|
@@ -7,6 +7,10 @@
|
|
|
7
7
|
})(typeof globalThis !== 'undefined' ? globalThis : window, function sessionStateFactory() {
|
|
8
8
|
'use strict';
|
|
9
9
|
|
|
10
|
+
const i18n = (typeof module === 'object' && module.exports)
|
|
11
|
+
? require('./i18n')
|
|
12
|
+
: (typeof globalThis !== 'undefined' ? globalThis : window).CodexOverleafI18n;
|
|
13
|
+
|
|
10
14
|
const DEFAULT_PANEL_STATE = {
|
|
11
15
|
mode: 'confirm',
|
|
12
16
|
model: 'gpt-5.4',
|
|
@@ -17,6 +21,7 @@
|
|
|
17
21
|
autoOpen: true,
|
|
18
22
|
loadCodexLocalSkills: true,
|
|
19
23
|
loadCodexOverleafSkills: true,
|
|
24
|
+
codexOverleafSkillEnabled: {},
|
|
20
25
|
panelWidth: 380,
|
|
21
26
|
task: '',
|
|
22
27
|
focusFiles: [],
|
|
@@ -105,12 +110,14 @@
|
|
|
105
110
|
state.autoOpen = state.autoOpen !== false;
|
|
106
111
|
state.loadCodexLocalSkills = state.loadCodexLocalSkills !== false;
|
|
107
112
|
state.loadCodexOverleafSkills = state.loadCodexOverleafSkills !== false;
|
|
113
|
+
state.codexOverleafSkillEnabled = normalizeCodexOverleafSkillEnabled(state.codexOverleafSkillEnabled);
|
|
108
114
|
state.panelWidth = normalizePanelWidth(state.panelWidth);
|
|
109
115
|
state.task = typeof state.task === 'string' ? state.task : '';
|
|
110
116
|
state.model = typeof state.model === 'string' && state.model ? state.model : DEFAULT_PANEL_STATE.model;
|
|
111
117
|
state.customInstructionsByProject = normalizeCustomInstructionsByProject(state.customInstructionsByProject);
|
|
112
|
-
|
|
113
|
-
state.
|
|
118
|
+
const localizedOptions = { ...options, locale: state.locale };
|
|
119
|
+
state.runs = normalizeRuns(state.runs, localizedOptions);
|
|
120
|
+
state.sessions = normalizeSessions(state, input, localizedOptions);
|
|
114
121
|
state.activeSessionId = resolveActiveSessionId(state.sessions, input.activeSessionId);
|
|
115
122
|
|
|
116
123
|
return mirrorActiveSession(state);
|
|
@@ -497,12 +504,13 @@
|
|
|
497
504
|
|
|
498
505
|
function normalizeRun(run, options = {}) {
|
|
499
506
|
const shouldStopRestoredRun = options.restoreRunningRuns === true && run.status === 'running';
|
|
507
|
+
const locale = options.locale || i18n.DEFAULT_LOCALE;
|
|
500
508
|
const events = normalizeRunEvents(run.events);
|
|
501
509
|
if (shouldStopRestoredRun) {
|
|
502
510
|
events.push({
|
|
503
|
-
title: '
|
|
511
|
+
title: i18n.t(locale, 'restoredRunStoppedTitle'),
|
|
504
512
|
status: 'failed',
|
|
505
|
-
detail: '
|
|
513
|
+
detail: i18n.t(locale, 'restoredRunStoppedDetail'),
|
|
506
514
|
timestamp: new Date().toISOString()
|
|
507
515
|
});
|
|
508
516
|
}
|
|
@@ -515,7 +523,7 @@
|
|
|
515
523
|
reasoningEffort: typeof run.reasoningEffort === 'string' ? run.reasoningEffort : '',
|
|
516
524
|
speedTier: typeof run.speedTier === 'string' ? run.speedTier : '',
|
|
517
525
|
status: shouldStopRestoredRun ? 'failed' : normalizeRunStatus(run.status),
|
|
518
|
-
statusText: shouldStopRestoredRun ? '
|
|
526
|
+
statusText: shouldStopRestoredRun ? i18n.t(locale, 'restoredRunStoppedStatus') : sanitizeAssistantVisibleText(run.statusText),
|
|
519
527
|
startedAt: typeof run.startedAt === 'string' ? run.startedAt : '',
|
|
520
528
|
finishedAt: shouldStopRestoredRun ? new Date().toISOString() : typeof run.finishedAt === 'string' ? run.finishedAt : '',
|
|
521
529
|
events: events.slice(-MAX_RUN_EVENTS),
|
|
@@ -669,6 +677,23 @@
|
|
|
669
677
|
return result;
|
|
670
678
|
}
|
|
671
679
|
|
|
680
|
+
function normalizeCodexOverleafSkillEnabled(value) {
|
|
681
|
+
const result = {};
|
|
682
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
683
|
+
return result;
|
|
684
|
+
}
|
|
685
|
+
for (const key of Object.keys(value)) {
|
|
686
|
+
if (typeof key !== 'string' || !key) {
|
|
687
|
+
continue;
|
|
688
|
+
}
|
|
689
|
+
if (typeof value[key] !== 'boolean') {
|
|
690
|
+
continue;
|
|
691
|
+
}
|
|
692
|
+
result[key] = value[key];
|
|
693
|
+
}
|
|
694
|
+
return result;
|
|
695
|
+
}
|
|
696
|
+
|
|
672
697
|
function normalizeProjectPrefKey(value) {
|
|
673
698
|
const key = typeof value === 'string' ? value.trim() : '';
|
|
674
699
|
if (!key) {
|
|
@@ -714,6 +739,7 @@
|
|
|
714
739
|
autoOpen: source.autoOpen !== false,
|
|
715
740
|
loadCodexLocalSkills: source.loadCodexLocalSkills !== false,
|
|
716
741
|
loadCodexOverleafSkills: source.loadCodexOverleafSkills !== false,
|
|
742
|
+
codexOverleafSkillEnabled: normalizeCodexOverleafSkillEnabled(source.codexOverleafSkillEnabled),
|
|
717
743
|
panelWidth: normalizePanelWidth(source.panelWidth),
|
|
718
744
|
task: summarizeTextForStorage(active?.task || source.task || '', 'task'),
|
|
719
745
|
focusFiles: normalizeFocusFiles(active?.focusFiles || source.focusFiles),
|
|
@@ -342,11 +342,31 @@
|
|
|
342
342
|
experimentalOtByProject: normalizeBooleanMap(state.experimentalOtByProject),
|
|
343
343
|
customInstructionsByProject: normalizeStringMap(state.customInstructionsByProject),
|
|
344
344
|
governanceRulesByProject: normalizeGovernanceRulesMap(state.governanceRulesByProject),
|
|
345
|
-
selectedLocalSkillIdsByProject: normalizeStringListMap(state.selectedLocalSkillIdsByProject)
|
|
345
|
+
selectedLocalSkillIdsByProject: normalizeStringListMap(state.selectedLocalSkillIdsByProject),
|
|
346
|
+
codexOverleafSkillEnabled: normalizeCodexOverleafSkillEnabledMap(state.codexOverleafSkillEnabled)
|
|
346
347
|
};
|
|
347
348
|
return prefs;
|
|
348
349
|
}
|
|
349
350
|
|
|
351
|
+
function normalizeCodexOverleafSkillEnabledMap(value) {
|
|
352
|
+
var result = {};
|
|
353
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
354
|
+
return result;
|
|
355
|
+
}
|
|
356
|
+
var keys = Object.keys(value);
|
|
357
|
+
for (var i = 0; i < keys.length; i++) {
|
|
358
|
+
var key = keys[i];
|
|
359
|
+
if (typeof key !== 'string' || !key) {
|
|
360
|
+
continue;
|
|
361
|
+
}
|
|
362
|
+
if (typeof value[key] !== 'boolean') {
|
|
363
|
+
continue;
|
|
364
|
+
}
|
|
365
|
+
result[key] = value[key];
|
|
366
|
+
}
|
|
367
|
+
return result;
|
|
368
|
+
}
|
|
369
|
+
|
|
350
370
|
function normalizeBooleanMap(value) {
|
|
351
371
|
var result = {};
|
|
352
372
|
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
@@ -223,7 +223,15 @@
|
|
|
223
223
|
if (typeof current !== 'string') {
|
|
224
224
|
return;
|
|
225
225
|
}
|
|
226
|
-
|
|
226
|
+
// The writeback already verified the editor content it produced. When
|
|
227
|
+
// that authoritative post-write content is available, trust it instead
|
|
228
|
+
// of re-deriving the result by re-applying patches: a wide patch's
|
|
229
|
+
// `expected` spans a whole paragraph, so it silently fails to re-apply
|
|
230
|
+
// against any base that drifted even slightly from the patch's base,
|
|
231
|
+
// and the result would otherwise collapse to the un-patched content.
|
|
232
|
+
if (typeof operation.verifiedContent === 'string') {
|
|
233
|
+
filesByPath.set(operation.path, operation.verifiedContent);
|
|
234
|
+
} else if (typeof operation.replaceAll === 'string') {
|
|
227
235
|
filesByPath.set(operation.path, operation.replaceAll);
|
|
228
236
|
} else if (Array.isArray(operation.patches) && operation.patches.length) {
|
|
229
237
|
const patched = applyTextPatches(current, operation.patches);
|
|
@@ -7,25 +7,31 @@ const {
|
|
|
7
7
|
getNativeHostPlatform
|
|
8
8
|
} = require('./nativeHostPlatform');
|
|
9
9
|
const {
|
|
10
|
+
ensureCodexOverleafSkillInstalled,
|
|
10
11
|
getCodexOverleafSkillsRoot,
|
|
11
|
-
materializeProjectSkillsAsCodexSkills
|
|
12
|
+
materializeProjectSkillsAsCodexSkills,
|
|
13
|
+
OFFICIAL_CODEX_OVERLEAF_SKILL_IDS
|
|
12
14
|
} = require('./localSkills');
|
|
13
15
|
|
|
14
16
|
const COPIED_USER_CODEX_FILES = [
|
|
15
17
|
'auth.json',
|
|
16
18
|
'config.toml',
|
|
17
|
-
'AGENTS.md',
|
|
18
19
|
'installation_id',
|
|
19
20
|
'models_cache.json',
|
|
20
21
|
'version.json'
|
|
21
22
|
];
|
|
22
23
|
|
|
23
24
|
const LINKED_USER_CODEX_DIRS = [
|
|
24
|
-
'rules',
|
|
25
|
-
'memories',
|
|
26
25
|
'vendor_imports'
|
|
27
26
|
];
|
|
28
27
|
|
|
28
|
+
// User-global Codex instruction/memory entries that must never enter the plugin
|
|
29
|
+
// Codex home. The extension supplies its own per-project personalization via the
|
|
30
|
+
// prompt; inheriting the user's global Codex guidance here is a leak. The plugin
|
|
31
|
+
// home is reused across runs, so these are removed every prepare to also clear
|
|
32
|
+
// entries left by earlier extension versions.
|
|
33
|
+
const ISOLATED_USER_INSTRUCTION_ENTRIES = ['AGENTS.md', 'rules', 'memories'];
|
|
34
|
+
|
|
29
35
|
const LOCAL_SKILL_USER_CODEX_DIRS = [
|
|
30
36
|
'plugins',
|
|
31
37
|
'superpowers'
|
|
@@ -93,6 +99,18 @@ function preparePluginCodexHome(env = process.env, options = {}) {
|
|
|
93
99
|
copied.push(fileName);
|
|
94
100
|
}
|
|
95
101
|
|
|
102
|
+
// Personalization isolation: the plugin Codex home must never inherit the
|
|
103
|
+
// user's global Codex instructions/memory. Remove them every run — this also
|
|
104
|
+
// clears stale entries left by earlier extension versions. This runs after
|
|
105
|
+
// the samePath early-return above, so it never touches the user's real
|
|
106
|
+
// ~/.codex when the plugin home and user home are the same directory.
|
|
107
|
+
for (const entryName of ISOLATED_USER_INSTRUCTION_ENTRIES) {
|
|
108
|
+
removePluginHomeEntry(pluginHome, entryName, skippedLinks);
|
|
109
|
+
}
|
|
110
|
+
if (!isRegularFile(path.join(userHome, 'config.toml'))) {
|
|
111
|
+
removePluginHomeEntry(pluginHome, 'config.toml', skippedLinks);
|
|
112
|
+
}
|
|
113
|
+
|
|
96
114
|
if (!loadCodexLocalSkills) {
|
|
97
115
|
for (const entryName of LOCAL_SKILL_PLUGIN_HOME_ENTRIES) {
|
|
98
116
|
removePluginHomeEntry(pluginHome, entryName, skippedLinks);
|
|
@@ -119,6 +137,8 @@ function preparePluginCodexHome(env = process.env, options = {}) {
|
|
|
119
137
|
linked.push(dirName);
|
|
120
138
|
}
|
|
121
139
|
|
|
140
|
+
ensureDefaultCodexOverleafSkills({ env });
|
|
141
|
+
|
|
122
142
|
const skillsResult = composePluginSkillsDirectory({
|
|
123
143
|
userHome,
|
|
124
144
|
pluginHome,
|
|
@@ -137,13 +157,68 @@ function preparePluginCodexHome(env = process.env, options = {}) {
|
|
|
137
157
|
return { userHome, pluginHome, copied, linked, skippedLinks };
|
|
138
158
|
}
|
|
139
159
|
|
|
160
|
+
function ensureDefaultCodexOverleafSkills({ env = process.env } = {}) {
|
|
161
|
+
for (const id of OFFICIAL_CODEX_OVERLEAF_SKILL_IDS) {
|
|
162
|
+
const src = path.resolve(__dirname, 'skills', id, 'SKILL.md');
|
|
163
|
+
const content = fs.readFileSync(src, 'utf8');
|
|
164
|
+
ensureCodexOverleafSkillInstalled({ skillId: id, content, env });
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
140
168
|
function copyUserCodexFile(source, target, fileName, options = {}) {
|
|
141
|
-
if (fileName !== 'config.toml'
|
|
169
|
+
if (fileName !== 'config.toml') {
|
|
142
170
|
fs.copyFileSync(source, target);
|
|
143
171
|
return;
|
|
144
172
|
}
|
|
145
|
-
|
|
146
|
-
|
|
173
|
+
let content = stripPersonalizationFromCodexConfig(fs.readFileSync(source, 'utf8'));
|
|
174
|
+
if (options.loadCodexLocalSkills === false) {
|
|
175
|
+
content = sanitizeCodexConfigForLocalSkillIsolation(content);
|
|
176
|
+
}
|
|
177
|
+
fs.writeFileSync(target, content, 'utf8');
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Removes the top-level `personality` key (Codex's built-in "personality"
|
|
181
|
+
// feature) so the plugin Codex home never inherits the user's global
|
|
182
|
+
// personalization. Only the top-level key is removed — a `personality` key
|
|
183
|
+
// inside a [section] is a different key and is preserved. Handles single-line
|
|
184
|
+
// values and both multi-line string forms (""" basic and ''' literal). All
|
|
185
|
+
// other lines pass through unchanged, aside from line endings being normalized to LF.
|
|
186
|
+
function stripPersonalizationFromCodexConfig(content) {
|
|
187
|
+
const lines = String(content || '').split(/\r?\n/);
|
|
188
|
+
const output = [];
|
|
189
|
+
let beforeFirstSection = true;
|
|
190
|
+
let closingDelimiter = '';
|
|
191
|
+
|
|
192
|
+
for (const line of lines) {
|
|
193
|
+
if (closingDelimiter) {
|
|
194
|
+
if (line.includes(closingDelimiter)) {
|
|
195
|
+
closingDelimiter = '';
|
|
196
|
+
}
|
|
197
|
+
// Drop every line of the multi-line value, including the one that closes it.
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
if (parseTomlSectionName(line)) {
|
|
201
|
+
beforeFirstSection = false;
|
|
202
|
+
output.push(line);
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
if (beforeFirstSection) {
|
|
206
|
+
const match = line.match(/^\s*personality\s*=\s*(.*)$/);
|
|
207
|
+
if (match) {
|
|
208
|
+
const value = match[1];
|
|
209
|
+
const opener = value.startsWith('"""') ? '"""'
|
|
210
|
+
: value.startsWith("'''") ? "'''"
|
|
211
|
+
: '';
|
|
212
|
+
if (opener && value.indexOf(opener, opener.length) === -1) {
|
|
213
|
+
closingDelimiter = opener;
|
|
214
|
+
}
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
output.push(line);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return output.join('\n');
|
|
147
222
|
}
|
|
148
223
|
|
|
149
224
|
function sanitizeCodexConfigForLocalSkillIsolation(content) {
|
|
@@ -532,6 +607,7 @@ module.exports = {
|
|
|
532
607
|
buildCodexHomeEnv,
|
|
533
608
|
clearPluginCodexHistory,
|
|
534
609
|
composePluginSkillsDirectory,
|
|
610
|
+
ensureDefaultCodexOverleafSkills,
|
|
535
611
|
getPluginCodexHome,
|
|
536
612
|
getUserCodexHome,
|
|
537
613
|
preparePluginCodexHome
|
|
@@ -68,6 +68,7 @@ async function runCodexSession({ params = {}, env = process.env, emit = () => {}
|
|
|
68
68
|
const codexSkillInvocationContext = loadCodexSkillInvocationContext({
|
|
69
69
|
skillInvocation,
|
|
70
70
|
loadCodexOverleafSkills: skillLoading.loadCodexOverleafSkills,
|
|
71
|
+
enabledCodexOverleafSkillIds: params.enabledCodexOverleafSkillIds,
|
|
71
72
|
env,
|
|
72
73
|
emit
|
|
73
74
|
});
|
|
@@ -385,6 +386,7 @@ function normalizeSkillLoadingSettings(params = {}) {
|
|
|
385
386
|
function loadCodexSkillInvocationContext({
|
|
386
387
|
skillInvocation,
|
|
387
388
|
loadCodexOverleafSkills = true,
|
|
389
|
+
enabledCodexOverleafSkillIds,
|
|
388
390
|
env = process.env,
|
|
389
391
|
emit = () => {}
|
|
390
392
|
} = {}) {
|
|
@@ -399,6 +401,7 @@ function loadCodexSkillInvocationContext({
|
|
|
399
401
|
const result = loadSelectedCodexOverleafSkill({
|
|
400
402
|
skillId: invocation.id,
|
|
401
403
|
loadCodexOverleafSkills,
|
|
404
|
+
enabledCodexOverleafSkillIds,
|
|
402
405
|
env
|
|
403
406
|
});
|
|
404
407
|
if (result.missing.length) {
|
|
@@ -13,6 +13,7 @@ const MAX_SKILL_CONTENT_CHARS = MAX_SKILL_CONTENT_BYTES;
|
|
|
13
13
|
const MAX_SKILL_PREVIEW_CHARS = 240;
|
|
14
14
|
const PROJECT_SKILL_SCOPE = 'project';
|
|
15
15
|
const CODEX_OVERLEAF_SKILL_SCOPE = 'codex-overleaf';
|
|
16
|
+
const OFFICIAL_CODEX_OVERLEAF_SKILL_IDS = ['annotated-rewrite'];
|
|
16
17
|
|
|
17
18
|
function listProjectSkills({ projectId, rootDir } = {}) {
|
|
18
19
|
const skillsDir = getProjectSkillsDir(projectId, { rootDir });
|
|
@@ -110,13 +111,18 @@ function listCodexOverleafSkills({ env = process.env, skillsRoot } = {}) {
|
|
|
110
111
|
continue;
|
|
111
112
|
}
|
|
112
113
|
const content = fs.readFileSync(filePath, 'utf8');
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
114
|
+
const isOfficial = OFFICIAL_CODEX_OVERLEAF_SKILL_IDS.includes(entry.name);
|
|
115
|
+
skills.push({
|
|
116
|
+
...buildSkillMetadata({
|
|
117
|
+
id: entry.name,
|
|
118
|
+
content,
|
|
119
|
+
size: stat.size,
|
|
120
|
+
updatedAt: stat.mtime.toISOString(),
|
|
121
|
+
scope: CODEX_OVERLEAF_SKILL_SCOPE
|
|
122
|
+
}),
|
|
123
|
+
official: isOfficial,
|
|
124
|
+
removable: !isOfficial
|
|
125
|
+
});
|
|
120
126
|
}
|
|
121
127
|
|
|
122
128
|
return { skills: skills.sort((left, right) => left.id.localeCompare(right.id)) };
|
|
@@ -147,6 +153,9 @@ function installCodexOverleafSkill({ skillId, content, env = process.env, skills
|
|
|
147
153
|
|
|
148
154
|
function removeCodexOverleafSkill({ skillId, env = process.env, skillsRoot } = {}) {
|
|
149
155
|
const id = validateSkillId(skillId);
|
|
156
|
+
if (OFFICIAL_CODEX_OVERLEAF_SKILL_IDS.includes(id)) {
|
|
157
|
+
throw new Error(`Cannot remove official skill: ${id}`);
|
|
158
|
+
}
|
|
150
159
|
const root = getCodexOverleafSkillsRoot({ env, skillsRoot });
|
|
151
160
|
const skillDir = resolveInside(root, id, 'Unsafe Codex Overleaf skill path');
|
|
152
161
|
const removed = fs.existsSync(skillDir);
|
|
@@ -156,6 +165,21 @@ function removeCodexOverleafSkill({ skillId, env = process.env, skillsRoot } = {
|
|
|
156
165
|
return { id, scope: CODEX_OVERLEAF_SKILL_SCOPE, removed };
|
|
157
166
|
}
|
|
158
167
|
|
|
168
|
+
function ensureCodexOverleafSkillInstalled({ skillId, content, env = process.env } = {}) {
|
|
169
|
+
const root = getCodexOverleafSkillsRoot({ env });
|
|
170
|
+
const skillPath = resolveCodexOverleafSkillPath(skillId, { env });
|
|
171
|
+
if (fs.existsSync(skillPath)) {
|
|
172
|
+
assertNoSymlinkEscape(skillPath, root, 'Existing official skill path is unsafe');
|
|
173
|
+
const stat = fs.lstatSync(skillPath);
|
|
174
|
+
if (stat.isFile()) {
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
// symlink, directory, or other — remove and reinstall
|
|
178
|
+
fs.rmSync(skillPath, { recursive: true, force: true });
|
|
179
|
+
}
|
|
180
|
+
installCodexOverleafSkill({ skillId, content, env });
|
|
181
|
+
}
|
|
182
|
+
|
|
159
183
|
function loadSelectedProjectSkills({ projectId, selectedSkillIds, rootDir, projectRoot } = {}) {
|
|
160
184
|
const ids = normalizeSelectedSkillIds(selectedSkillIds);
|
|
161
185
|
const skills = [];
|
|
@@ -230,6 +254,7 @@ function materializeProjectSkillsAsCodexSkills({
|
|
|
230
254
|
function loadSelectedCodexOverleafSkill({
|
|
231
255
|
skillId,
|
|
232
256
|
loadCodexOverleafSkills = true,
|
|
257
|
+
enabledCodexOverleafSkillIds,
|
|
233
258
|
env = process.env,
|
|
234
259
|
skillsRoot
|
|
235
260
|
} = {}) {
|
|
@@ -248,6 +273,18 @@ function loadSelectedCodexOverleafSkill({
|
|
|
248
273
|
}]
|
|
249
274
|
};
|
|
250
275
|
}
|
|
276
|
+
// Per-skill enable check: if enabledCodexOverleafSkillIds is provided and does not include
|
|
277
|
+
// this skill, treat it as not available. Absent param = all enabled (backward-safe).
|
|
278
|
+
if (Array.isArray(enabledCodexOverleafSkillIds) && !enabledCodexOverleafSkillIds.includes(id)) {
|
|
279
|
+
return {
|
|
280
|
+
skill: null,
|
|
281
|
+
missing: [],
|
|
282
|
+
ignored: [{
|
|
283
|
+
id,
|
|
284
|
+
reason: 'codex_overleaf_skill_disabled'
|
|
285
|
+
}]
|
|
286
|
+
};
|
|
287
|
+
}
|
|
251
288
|
|
|
252
289
|
const filePath = resolveCodexOverleafSkillPath(id, { env, skillsRoot });
|
|
253
290
|
if (!fs.existsSync(filePath)) {
|
|
@@ -461,7 +498,9 @@ module.exports = {
|
|
|
461
498
|
CODEX_OVERLEAF_SKILL_SCOPE,
|
|
462
499
|
MAX_SKILL_CONTENT_BYTES,
|
|
463
500
|
MAX_SKILL_CONTENT_CHARS,
|
|
501
|
+
OFFICIAL_CODEX_OVERLEAF_SKILL_IDS,
|
|
464
502
|
PROJECT_SKILL_SCOPE,
|
|
503
|
+
ensureCodexOverleafSkillInstalled,
|
|
465
504
|
getDefaultCodexOverleafSkillsRoot,
|
|
466
505
|
getCodexOverleafSkillsRoot,
|
|
467
506
|
installCodexOverleafSkill,
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: annotated-rewrite
|
|
3
|
+
description: >
|
|
4
|
+
When editing `.tex` files, use this skill as often as possible. When
|
|
5
|
+
rewriting `.tex` content spanning more than a few words, comment out the
|
|
6
|
+
original and write the replacement below it so the user can review the
|
|
7
|
+
before/after diff directly in the source.
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Annotated Rewrite
|
|
11
|
+
|
|
12
|
+
When rewriting `.tex` file content (paragraphs, sentences, equations, list
|
|
13
|
+
bodies, or any block larger than a few words), preserve the original by
|
|
14
|
+
commenting it out rather than deleting it, then write the replacement below.
|
|
15
|
+
This lets the user review exactly what changed before accepting the edit.
|
|
16
|
+
|
|
17
|
+
## When to apply
|
|
18
|
+
|
|
19
|
+
Default to this pattern whenever the replaced content is **more than a short inline phrase** (see the word-level exception below), regardless of content type — prose, equations, lists, environment bodies, or any contiguous block. Use it as often as possible — when in doubt, use it.
|
|
20
|
+
|
|
21
|
+
A useful guide: if the replaced content is **3 or more source lines**, always apply it. For shorter replacements (1-2 source lines), lean toward applying it unless the change is purely **word-level**: replacing a single word, fixing a typo, or changing a run of 1-5 words within an unbroken clause.
|
|
22
|
+
|
|
23
|
+
Word-level edits are the only case where skipping the annotated format is clearly appropriate (subject to the structural exceptions in Rule 8).
|
|
24
|
+
|
|
25
|
+
## Edit format
|
|
26
|
+
|
|
27
|
+
```tex
|
|
28
|
+
% [original]
|
|
29
|
+
% The proposed method encodes user history as a fixed-length vector,
|
|
30
|
+
% then applies a linear projection before computing dot-product scores.
|
|
31
|
+
% Training uses binary cross-entropy loss over sampled negatives.
|
|
32
|
+
%
|
|
33
|
+
% \begin{equation}
|
|
34
|
+
% \hat{y}_{ui} = \mathbf{u}_i^\top \mathbf{v}_j
|
|
35
|
+
% \end{equation}
|
|
36
|
+
|
|
37
|
+
% [revised]
|
|
38
|
+
We encode user history with a transformer encoder, producing a
|
|
39
|
+
context-aware representation that is projected into the item space.
|
|
40
|
+
Training minimises a softmax loss over in-batch negatives.
|
|
41
|
+
|
|
42
|
+
\begin{equation}
|
|
43
|
+
\hat{y}_{ui} = \mathrm{softmax}(\mathbf{U}\mathbf{V}^\top)_{ij}
|
|
44
|
+
\end{equation}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Rules
|
|
48
|
+
|
|
49
|
+
1. Copy the original lines **verbatim** into the comment block — do not alter them
|
|
50
|
+
2. Include every line of the replaced content verbatim with `%` prepended;
|
|
51
|
+
blank lines within the block become `%` empty-comment lines (a line
|
|
52
|
+
containing only `%`). Include `\begin{...}` / `\end{...}` markers only when
|
|
53
|
+
they are part of the replaced content — do not comment out enclosing
|
|
54
|
+
environment markers that are not themselves changing
|
|
55
|
+
3. No blank line between `% [original]` and the first commented line
|
|
56
|
+
4. One blank line between the last commented line and `% [revised]`; one blank
|
|
57
|
+
line before `% [original]` and after the last line of the replacement (to
|
|
58
|
+
separate the annotated construct from surrounding document text)
|
|
59
|
+
5. The new content after `% [revised]` is normal LaTeX (not commented)
|
|
60
|
+
6. For multiple non-adjacent replaced blocks, apply the pattern independently
|
|
61
|
+
to each block
|
|
62
|
+
7. **Idempotency**: if the content being edited already contains a
|
|
63
|
+
`% [original]` / `% [revised]` block, edit only the active content after
|
|
64
|
+
`% [revised]` — do not re-wrap the existing annotated block or create a new
|
|
65
|
+
outer annotation around it
|
|
66
|
+
8. **Structure safety**: only apply this pattern at **block level** (paragraph,
|
|
67
|
+
sentence, equation block, list body, or environment body). Do NOT insert
|
|
68
|
+
`% [original]` / `% [revised]` inside
|
|
69
|
+
macro arguments, table rows, math expressions, `\caption{...}`,
|
|
70
|
+
`\item[...]`, or preamble commands — make minimal direct edits there instead,
|
|
71
|
+
or ask for confirmation before changing structurally sensitive content
|