codex-overleaf-link 1.8.6 → 1.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +45 -21
- package/bin/codex-overleaf-link.mjs +58 -0
- package/extension/assets/icons/codex-overleaf-dialog-icon.png +0 -0
- package/extension/assets/icons/codex-overleaf-icon.png +0 -0
- package/extension/assets/icons/icon128.png +0 -0
- package/extension/assets/icons/icon16.png +0 -0
- package/extension/assets/icons/icon32.png +0 -0
- package/extension/assets/icons/icon48.png +0 -0
- package/extension/bootstrap/background.js +404 -0
- package/extension/bootstrap/manifest.template.json +51 -0
- package/extension/bootstrap/popup.css +26 -0
- package/extension/bootstrap/popup.html +38 -0
- package/extension/bootstrap/popup.js +44 -0
- package/extension/bootstrap/runtimeContext.js +2 -0
- package/extension/runtime-manifest.json +60 -0
- package/extension/src/background.js +532 -0
- package/extension/src/content/applyResultFormatters.js +387 -0
- package/extension/src/content/composerAttachments.js +522 -0
- package/extension/src/content/composerPanel.js +254 -0
- package/extension/src/content/contentRuntime.js +8897 -0
- package/extension/src/content/contextTray.js +686 -0
- package/extension/src/content/diagnosticsController.js +659 -0
- package/extension/src/content/diagnosticsPanel.js +342 -0
- package/extension/src/content/diffReviewPanel.js +797 -0
- package/extension/src/content/localSkillsPanel.js +280 -0
- package/extension/src/content/markdownText.js +606 -0
- package/extension/src/content/mirrorHealth.js +92 -0
- package/extension/src/content/modelPicker.js +489 -0
- package/extension/src/content/nativeChannel.js +62 -0
- package/extension/src/content/otWarmMirror.js +964 -0
- package/extension/src/content/otWarmMirrorController.js +174 -0
- package/extension/src/content/panelMaintenance.js +367 -0
- package/extension/src/content/panelRenderer.js +253 -0
- package/extension/src/content/recentProjects.js +1063 -0
- package/extension/src/content/reviewHunks.js +247 -0
- package/extension/src/content/runController.js +242 -0
- package/extension/src/content/runTimelineView.js +1131 -0
- package/extension/src/content/sessionManager.js +487 -0
- package/extension/src/content/sessionPanel.js +263 -0
- package/extension/src/content/settingsPanel.js +527 -0
- package/extension/src/content/themeController.js +91 -0
- package/extension/src/content/updateIdle.js +67 -0
- package/extension/src/content/writebackController.js +300 -0
- package/extension/src/content/writebackOrchestrator.js +850 -0
- package/extension/src/contentScript.js +8 -0
- package/extension/src/page/compileBridge.js +460 -0
- package/extension/src/page/overleafCapabilities.js +100 -0
- package/extension/src/page/overleafEditor.js +282 -0
- package/extension/src/page/overleafProjectSnapshot.js +234 -0
- package/extension/src/page/overleafRealtimeObserver.js +491 -0
- package/extension/src/page/pageBridgeCapability.js +71 -0
- package/extension/src/page/snapshotRouter.js +948 -0
- package/extension/src/page/trackedChangesLifecycle.js +1506 -0
- package/extension/src/page/treeOperations.js +1584 -0
- package/extension/src/page/writeGuard.js +142 -0
- package/extension/src/page/writebackRouter.js +1934 -0
- package/extension/src/pageBridge.js +2224 -0
- package/extension/src/popup.js +247 -0
- package/extension/src/shared/agentTranscript.js +18 -4
- package/extension/src/shared/compatibility.js +1 -1
- package/extension/styles/panel.css +4048 -0
- package/native-host/src/index.js +9 -2
- package/native-host/src/managedInstall.js +393 -0
- package/native-host/src/managedLauncherRuntime.js +71 -0
- package/native-host/src/nativeHostPlatform.js +16 -0
- package/native-host/src/runtimeInstaller.js +6 -1
- package/native-host/src/taskRunnerRuntime.js +8 -0
- package/native-host/src/updateArchive.js +253 -0
- package/native-host/src/updateManager.js +548 -0
- package/native-host/src/updateTrust.js +156 -0
- package/package.json +11 -2
- package/scripts/install-managed.mjs +64 -0
- package/scripts/uninstall-managed.mjs +48 -0
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
(function initCodexOverleafReviewHunks(root, factory) {
|
|
2
|
+
if (typeof module === 'object' && module.exports) {
|
|
3
|
+
module.exports = factory();
|
|
4
|
+
} else {
|
|
5
|
+
root.CodexOverleafReviewHunks = factory();
|
|
6
|
+
}
|
|
7
|
+
})(typeof window !== 'undefined' ? window : globalThis, function reviewHunksFactory() {
|
|
8
|
+
'use strict';
|
|
9
|
+
|
|
10
|
+
function buildReviewModel(syncChanges = [], options = {}) {
|
|
11
|
+
const files = [];
|
|
12
|
+
const hunks = [];
|
|
13
|
+
|
|
14
|
+
for (const [changeIndex, change] of (syncChanges || []).entries()) {
|
|
15
|
+
const path = normalizePath(change?.path);
|
|
16
|
+
if (!path) {
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const file = {
|
|
21
|
+
index: changeIndex,
|
|
22
|
+
type: change?.type,
|
|
23
|
+
path,
|
|
24
|
+
decisionKey: normalizeReviewDecisionKey(path),
|
|
25
|
+
reviewable: false,
|
|
26
|
+
hunks: []
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
if (isHunkReviewableChange(change, options)) {
|
|
30
|
+
file.reviewable = true;
|
|
31
|
+
file.hunks = normalizePatches(change.patches).map((patch, patchIndex) => {
|
|
32
|
+
const id = `${path}:hunk:${patchIndex}`;
|
|
33
|
+
return {
|
|
34
|
+
id,
|
|
35
|
+
path,
|
|
36
|
+
patchIndexes: [patchIndex],
|
|
37
|
+
startOffset: patch.from,
|
|
38
|
+
endOffset: patch.to,
|
|
39
|
+
startLine: getPatchStartLine(change, patchIndex),
|
|
40
|
+
lineCount: getPatchLineCount(patch),
|
|
41
|
+
canApplyIndependently: true,
|
|
42
|
+
truncated: false,
|
|
43
|
+
decisionKey: normalizeReviewDecisionKey(path, id)
|
|
44
|
+
};
|
|
45
|
+
});
|
|
46
|
+
hunks.push(...file.hunks);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
files.push(file);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
files,
|
|
54
|
+
hunks
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function buildAcceptedSyncChanges(syncChanges = [], decisions = {}) {
|
|
59
|
+
const model = buildReviewModel(syncChanges);
|
|
60
|
+
const filesByIndex = new Map(model.files.map(file => [file.index, file]));
|
|
61
|
+
const accepted = [];
|
|
62
|
+
|
|
63
|
+
for (const [changeIndex, change] of (syncChanges || []).entries()) {
|
|
64
|
+
const file = filesByIndex.get(changeIndex);
|
|
65
|
+
if (!file) {
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (!file.reviewable) {
|
|
70
|
+
if (isAcceptedDecision(decisions[file.decisionKey])) {
|
|
71
|
+
accepted.push(cloneSyncChange(change));
|
|
72
|
+
}
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const acceptedPatchIndexes = [];
|
|
77
|
+
for (const hunk of file.hunks) {
|
|
78
|
+
if (isAcceptedDecision(decisions[hunk.decisionKey])) {
|
|
79
|
+
acceptedPatchIndexes.push(...hunk.patchIndexes);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (acceptedPatchIndexes.length) {
|
|
84
|
+
const patches = normalizePatches(change.patches)
|
|
85
|
+
.filter((patch, patchIndex) => acceptedPatchIndexes.includes(patchIndex));
|
|
86
|
+
const clone = cloneSyncChange(change);
|
|
87
|
+
clone.patches = patches.map(patch => ({ ...patch }));
|
|
88
|
+
delete clone.replaceAll;
|
|
89
|
+
accepted.push(clone);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return accepted;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function summarizeReviewModel(model = {}) {
|
|
97
|
+
const files = Array.isArray(model.files) ? model.files : [];
|
|
98
|
+
return {
|
|
99
|
+
files: files.length,
|
|
100
|
+
hunks: files.reduce((count, file) => count + (Array.isArray(file.hunks) ? file.hunks.length : 0), 0),
|
|
101
|
+
fallbackFiles: files.filter(file => !file.reviewable).length,
|
|
102
|
+
reviewableFiles: files.filter(file => file.reviewable).length
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function normalizeReviewDecisionKey(path, hunkId) {
|
|
107
|
+
const normalizedPath = normalizePath(path);
|
|
108
|
+
return `${normalizedPath || 'unknown'}::${hunkId || 'file'}`;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function isHunkReviewableChange(change = {}, options = {}) {
|
|
112
|
+
if (change.type !== 'write') {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
if (hasTruncatedDisplayDiff(change)) {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
const patches = Array.isArray(change.patches) ? change.patches : [];
|
|
119
|
+
if (!patches.length) {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
return normalizePatches(patches).length === patches.length;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function hasTruncatedDisplayDiff(change = {}) {
|
|
126
|
+
if (!Array.isArray(change.diff)) {
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
return change.diff.some(hunk => hunk?.truncated === true);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function normalizePatches(patches = []) {
|
|
133
|
+
const normalized = [];
|
|
134
|
+
for (const patch of patches || []) {
|
|
135
|
+
const from = Number(patch?.from);
|
|
136
|
+
const to = Number(patch?.to);
|
|
137
|
+
if (!Number.isInteger(from) || !Number.isInteger(to) || from < 0 || to < from) {
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
normalized.push({
|
|
141
|
+
from,
|
|
142
|
+
to,
|
|
143
|
+
expected: String(patch.expected ?? ''),
|
|
144
|
+
insert: String(patch.insert ?? '')
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
return normalized;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function getPatchStartLine(change = {}, patchIndex = 0) {
|
|
151
|
+
const diffHunk = Array.isArray(change.diff) ? change.diff[patchIndex] : null;
|
|
152
|
+
const startB = Number(diffHunk?.startB);
|
|
153
|
+
if (Number.isInteger(startB) && startB > 0) {
|
|
154
|
+
return startB;
|
|
155
|
+
}
|
|
156
|
+
return undefined;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function getPatchLineCount(patch = {}) {
|
|
160
|
+
const text = String(patch.insert ?? patch.expected ?? '');
|
|
161
|
+
if (!text) {
|
|
162
|
+
return 1;
|
|
163
|
+
}
|
|
164
|
+
return Math.max(1, text.split('\n').length);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function normalizePath(path) {
|
|
168
|
+
return String(path || '').replace(/\\/g, '/').trim().replace(/^\/+/, '');
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function isAcceptedDecision(decision) {
|
|
172
|
+
return decision === true || decision === 'accepted' || decision === 'accept';
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function isRejectedDecision(decision) {
|
|
176
|
+
return decision === false || decision === 'rejected' || decision === 'reject';
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// v1.7.5: summaries of what the user rejected during review, so the
|
|
180
|
+
// completion report can offer a one-click "redo the rejected changes"
|
|
181
|
+
// retry that quotes the location and a snippet of each dropped hunk.
|
|
182
|
+
function buildRejectedHunkSummaries(syncChanges = [], decisions = {}) {
|
|
183
|
+
const model = buildReviewModel(syncChanges);
|
|
184
|
+
const filesByIndex = new Map(model.files.map(file => [file.index, file]));
|
|
185
|
+
const rejected = [];
|
|
186
|
+
for (const [changeIndex, change] of (syncChanges || []).entries()) {
|
|
187
|
+
const file = filesByIndex.get(changeIndex);
|
|
188
|
+
if (!file) {
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
if (!file.reviewable) {
|
|
192
|
+
if (isRejectedDecision(decisions[file.decisionKey])) {
|
|
193
|
+
rejected.push({ path: file.path, summary: '' });
|
|
194
|
+
}
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
const patches = normalizePatches(change.patches);
|
|
198
|
+
for (const hunk of file.hunks) {
|
|
199
|
+
if (!isRejectedDecision(decisions[hunk.decisionKey])) {
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
const patch = patches[hunk.patchIndexes[0]] || {};
|
|
203
|
+
const snippet = String(patch.insert || '').trim().replace(/\s+/g, ' ').slice(0, 80);
|
|
204
|
+
rejected.push({
|
|
205
|
+
path: file.path,
|
|
206
|
+
summary: `${hunk.startLine ? `L${hunk.startLine}` : ''}${hunk.startLine && snippet ? ': ' : ''}${snippet}`
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return rejected;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function cloneSyncChange(change) {
|
|
214
|
+
if (!change || typeof change !== 'object') {
|
|
215
|
+
return change;
|
|
216
|
+
}
|
|
217
|
+
const clone = { ...change };
|
|
218
|
+
if (Array.isArray(change.diff)) {
|
|
219
|
+
clone.diff = cloneArrayObjects(change.diff);
|
|
220
|
+
}
|
|
221
|
+
if (Array.isArray(change.patches)) {
|
|
222
|
+
clone.patches = cloneArrayObjects(change.patches);
|
|
223
|
+
}
|
|
224
|
+
return clone;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function cloneArrayObjects(items) {
|
|
228
|
+
return items.map(item => {
|
|
229
|
+
if (!item || typeof item !== 'object') {
|
|
230
|
+
return item;
|
|
231
|
+
}
|
|
232
|
+
const clone = { ...item };
|
|
233
|
+
if (Array.isArray(item.lines)) {
|
|
234
|
+
clone.lines = cloneArrayObjects(item.lines);
|
|
235
|
+
}
|
|
236
|
+
return clone;
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return {
|
|
241
|
+
buildAcceptedSyncChanges,
|
|
242
|
+
buildRejectedHunkSummaries,
|
|
243
|
+
buildReviewModel,
|
|
244
|
+
normalizeReviewDecisionKey,
|
|
245
|
+
summarizeReviewModel
|
|
246
|
+
};
|
|
247
|
+
});
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
(function initCodexOverleafRunController(root, factory) {
|
|
2
|
+
if (typeof module === 'object' && module.exports) {
|
|
3
|
+
module.exports = factory();
|
|
4
|
+
} else {
|
|
5
|
+
root.CodexOverleafRunController = factory();
|
|
6
|
+
}
|
|
7
|
+
})(typeof window !== 'undefined' ? window : globalThis, function runControllerFactory() {
|
|
8
|
+
'use strict';
|
|
9
|
+
|
|
10
|
+
const DEFAULT_EXPECTED_MIRROR_FRESHNESS_MS = 5 * 60 * 1000;
|
|
11
|
+
|
|
12
|
+
function buildCodexRunParams({
|
|
13
|
+
currentProjectId,
|
|
14
|
+
state = {},
|
|
15
|
+
task,
|
|
16
|
+
project,
|
|
17
|
+
useExistingMirror,
|
|
18
|
+
fileOverlays,
|
|
19
|
+
focusFiles,
|
|
20
|
+
otWarmStart,
|
|
21
|
+
codexThreadId,
|
|
22
|
+
customInstructions,
|
|
23
|
+
skillLoadingSettings,
|
|
24
|
+
enabledCodexOverleafSkillIds,
|
|
25
|
+
attachments,
|
|
26
|
+
skillInvocation,
|
|
27
|
+
compileLogContext,
|
|
28
|
+
submittedMode,
|
|
29
|
+
restrictToFocusFiles
|
|
30
|
+
} = {}) {
|
|
31
|
+
const normalizedCustomInstructions = String(customInstructions || '').trim();
|
|
32
|
+
const normalizedAttachments = normalizeComposerAttachments(attachments);
|
|
33
|
+
const normalizedSkillInvocation = normalizeSkillInvocation(skillInvocation);
|
|
34
|
+
const normalizedEnabledSkillIds = Array.isArray(enabledCodexOverleafSkillIds)
|
|
35
|
+
? enabledCodexOverleafSkillIds.filter(id => typeof id === 'string' && id.trim())
|
|
36
|
+
: undefined;
|
|
37
|
+
const params = {
|
|
38
|
+
projectId: currentProjectId,
|
|
39
|
+
mode: submittedMode || state.mode,
|
|
40
|
+
task,
|
|
41
|
+
project: useExistingMirror ? undefined : project,
|
|
42
|
+
useExistingMirror: useExistingMirror || undefined,
|
|
43
|
+
fileOverlays: useExistingMirror ? fileOverlays : undefined,
|
|
44
|
+
expectedMirrorFreshness: useExistingMirror ? DEFAULT_EXPECTED_MIRROR_FRESHNESS_MS : undefined,
|
|
45
|
+
otWarmStart: useExistingMirror && otWarmStart ? true : undefined,
|
|
46
|
+
warmStartStrategy: useExistingMirror && otWarmStart ? 'ot-warm-mirror' : undefined,
|
|
47
|
+
focusFiles,
|
|
48
|
+
restrictToFocusFiles: restrictToFocusFiles || undefined,
|
|
49
|
+
model: state.model,
|
|
50
|
+
reasoningEffort: state.reasoningEffort,
|
|
51
|
+
speedTier: state.speedTier,
|
|
52
|
+
session: state.session,
|
|
53
|
+
threadId: codexThreadId || undefined,
|
|
54
|
+
customInstructions: normalizedCustomInstructions || undefined,
|
|
55
|
+
loadCodexLocalSkills: skillLoadingSettings?.loadCodexLocalSkills !== false,
|
|
56
|
+
loadCodexOverleafSkills: skillLoadingSettings?.loadCodexOverleafSkills !== false,
|
|
57
|
+
enabledCodexOverleafSkillIds: normalizedEnabledSkillIds,
|
|
58
|
+
skillInvocation: normalizedSkillInvocation || undefined,
|
|
59
|
+
attachments: normalizedAttachments.length ? normalizedAttachments : undefined
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
if (compileLogContext?.available) {
|
|
63
|
+
params.compileLog = compileLogContext.log;
|
|
64
|
+
params.compileErrors = compileLogContext.errors || [];
|
|
65
|
+
params.compileWarnings = compileLogContext.warnings || [];
|
|
66
|
+
params.compileLogFresh = compileLogContext.fresh;
|
|
67
|
+
params.compileLogCompiledAt = compileLogContext.compiledAt;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return params;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function buildSessionHistoryResult({ assistantMessage = '', syncOutcome = {}, syncChanges = [], locale = 'zh' } = {}) {
|
|
74
|
+
const parts = [];
|
|
75
|
+
const finalAnswer = truncateSessionHistoryText(assistantMessage, 1600);
|
|
76
|
+
if (finalAnswer) {
|
|
77
|
+
parts.push(finalAnswer);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const changedFiles = Array.from(new Set((syncChanges || [])
|
|
81
|
+
.map(change => change?.path)
|
|
82
|
+
.filter(Boolean)));
|
|
83
|
+
if (changedFiles.length) {
|
|
84
|
+
const visibleFiles = changedFiles.slice(0, 8).join(', ');
|
|
85
|
+
parts.push(locale === 'en'
|
|
86
|
+
? `Files changed: ${visibleFiles}${changedFiles.length > 8 ? ' and more' : ''}`
|
|
87
|
+
: `涉及文件:${visibleFiles}${changedFiles.length > 8 ? ' 等' : ''}`);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (!parts.length) {
|
|
91
|
+
return truncateSessionHistoryText(syncOutcome.summaryLine || 'completed', 1600);
|
|
92
|
+
}
|
|
93
|
+
return truncateSessionHistoryText(parts.join('\n'), 2000);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function truncateSessionHistoryText(value, maxLength) {
|
|
97
|
+
const text = String(value || '').replace(/\s+/g, ' ').trim();
|
|
98
|
+
if (!text || text.length <= maxLength) {
|
|
99
|
+
return text;
|
|
100
|
+
}
|
|
101
|
+
return `${text.slice(0, Math.max(0, maxLength - 1))}…`;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function canUseFocusedPartialSnapshot({
|
|
105
|
+
project = {},
|
|
106
|
+
snapshotWarnings = {},
|
|
107
|
+
focusFiles = [],
|
|
108
|
+
isUsableProjectFileContent = defaultIsUsableProjectFileContent
|
|
109
|
+
} = {}) {
|
|
110
|
+
const blocking = Array.isArray(snapshotWarnings.blocking) ? snapshotWarnings.blocking : [];
|
|
111
|
+
if (!blocking.length || !blocking.some(isFullSnapshotWarning)) {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
const normalizedFocusFiles = normalizeFocusFiles(focusFiles);
|
|
115
|
+
if (!normalizedFocusFiles.length) {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
if (project?.capabilities?.fullProjectSnapshot !== false) {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const textFilesByPath = new Map((project.files || [])
|
|
123
|
+
.filter(file => file && typeof file.path === 'string' && typeof file.content === 'string')
|
|
124
|
+
.map(file => [normalizeFocusPath(file.path), file]));
|
|
125
|
+
|
|
126
|
+
return normalizedFocusFiles.every(filePath => {
|
|
127
|
+
const file = textFilesByPath.get(normalizeFocusPath(filePath));
|
|
128
|
+
return file && isUsableProjectFileContent(file.content);
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function shouldRestrictWritebackToFocus({ focusFiles = [] } = {}) {
|
|
133
|
+
return normalizeFocusFiles(focusFiles).length > 0;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function isFullSnapshotWarning(warning) {
|
|
137
|
+
return /Full project snapshot was not captured/i.test(String(warning || ''));
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function normalizeFocusFiles(value) {
|
|
141
|
+
const seen = new Set();
|
|
142
|
+
const files = [];
|
|
143
|
+
for (const item of Array.isArray(value) ? value : []) {
|
|
144
|
+
const filePath = normalizeFocusPath(item);
|
|
145
|
+
if (!filePath || seen.has(filePath)) {
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
seen.add(filePath);
|
|
149
|
+
files.push(filePath);
|
|
150
|
+
}
|
|
151
|
+
return files;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function normalizeFocusPath(value) {
|
|
155
|
+
return String(value || '')
|
|
156
|
+
.replace(/^@file:/i, '')
|
|
157
|
+
.replace(/\\/g, '/')
|
|
158
|
+
.trim()
|
|
159
|
+
.replace(/^\/+/, '');
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function normalizeComposerAttachments(value) {
|
|
163
|
+
const result = [];
|
|
164
|
+
for (const item of Array.isArray(value) ? value : []) {
|
|
165
|
+
const name = normalizeAttachmentName(item?.name);
|
|
166
|
+
const contentBase64 = normalizeAttachmentBase64(item?.contentBase64);
|
|
167
|
+
if (!name || !contentBase64) {
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
const size = Number(item?.size);
|
|
171
|
+
result.push({
|
|
172
|
+
name,
|
|
173
|
+
mimeType: String(item?.mimeType || '').trim().slice(0, 120),
|
|
174
|
+
size: Number.isFinite(size) && size >= 0 ? size : estimateBase64Size(contentBase64),
|
|
175
|
+
contentBase64
|
|
176
|
+
});
|
|
177
|
+
if (result.length >= 8) {
|
|
178
|
+
break;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return result;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function normalizeSkillInvocation(value) {
|
|
185
|
+
const id = String(value?.id || '').trim();
|
|
186
|
+
if (!isSafeSkillId(id)) {
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
const title = String(value?.title || 'Skill Installer').trim().slice(0, 80) || 'Skill Installer';
|
|
190
|
+
if (id === 'skill-installer') {
|
|
191
|
+
return { id, title };
|
|
192
|
+
}
|
|
193
|
+
if (value?.scope !== 'codex-overleaf') {
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
return { id, title, scope: 'codex-overleaf' };
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function isSafeSkillId(id) {
|
|
200
|
+
return /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,79}$/.test(String(id || ''))
|
|
201
|
+
&& !String(id || '').includes('..');
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function normalizeAttachmentName(value) {
|
|
205
|
+
return String(value || '')
|
|
206
|
+
.replace(/\0/g, '')
|
|
207
|
+
.replace(/\\/g, '/')
|
|
208
|
+
.split('/')
|
|
209
|
+
.filter(Boolean)
|
|
210
|
+
.pop()
|
|
211
|
+
?.trim()
|
|
212
|
+
.slice(0, 180) || '';
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function normalizeAttachmentBase64(value) {
|
|
216
|
+
const clean = String(value || '').replace(/\s+/g, '');
|
|
217
|
+
if (!clean || !/^[A-Za-z0-9+/]+={0,2}$/.test(clean)) {
|
|
218
|
+
return '';
|
|
219
|
+
}
|
|
220
|
+
return clean;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function estimateBase64Size(value) {
|
|
224
|
+
const clean = String(value || '').replace(/\s+/g, '');
|
|
225
|
+
const padding = clean.endsWith('==') ? 2 : clean.endsWith('=') ? 1 : 0;
|
|
226
|
+
return Math.max(0, Math.floor(clean.length * 3 / 4) - padding);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function defaultIsUsableProjectFileContent(content) {
|
|
230
|
+
return String(content || '').trim().length > 0;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return {
|
|
234
|
+
buildCodexRunParams,
|
|
235
|
+
canUseFocusedPartialSnapshot,
|
|
236
|
+
shouldRestrictWritebackToFocus,
|
|
237
|
+
buildSessionHistoryResult,
|
|
238
|
+
truncateSessionHistoryText,
|
|
239
|
+
normalizeComposerAttachments,
|
|
240
|
+
normalizeSkillInvocation
|
|
241
|
+
};
|
|
242
|
+
});
|