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
|
@@ -9,47 +9,48 @@
|
|
|
9
9
|
|
|
10
10
|
const MAX_LOG_BYTES = 32 * 1024;
|
|
11
11
|
const COMPILABLE_EXTENSIONS = new Set(['.tex', '.bib', '.sty', '.cls', '.bst', '.bbx', '.cbx']);
|
|
12
|
-
|
|
13
12
|
function parseCompileResponse(response) {
|
|
14
|
-
if (!response || typeof response !== 'object') {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
if (!Array.isArray(response.outputFiles)) {
|
|
19
|
-
return { ok: false, reason: 'Invalid compile response: outputFiles is not an array' };
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const logFile = response.outputFiles.find(function (file) {
|
|
13
|
+
if (!response || typeof response !== 'object') return { ok: false, reason: 'Invalid compile response: not an object' };
|
|
14
|
+
if (!Array.isArray(response.outputFiles)) return { ok: false, status: response.status, reason: 'Invalid compile response: outputFiles is not an array' };
|
|
15
|
+
const logFile = response.outputFiles.find(file => /(^|\/)output\.log$/.test(file?.path || '')) || response.outputFiles.find(function (file) {
|
|
23
16
|
if (!file) return false;
|
|
24
17
|
if (file.type === 'log') return true;
|
|
25
18
|
if (typeof file.path === 'string' && file.path.endsWith('.log')) return true;
|
|
26
19
|
return false;
|
|
27
20
|
});
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
var logUrl = logFile.url || null;
|
|
34
|
-
if (!logUrl) {
|
|
35
|
-
return { ok: false, reason: 'Log file has no URL' };
|
|
36
|
-
}
|
|
37
|
-
|
|
21
|
+
if (!logFile) return { ok: false, status: response.status, reason: 'No log file found in compile output' };
|
|
22
|
+
var logUrl = typeof logFile.url === 'string' ? logFile.url : null;
|
|
23
|
+
if (!logUrl) return { ok: false, status: response.status, reason: 'Log file has no URL' };
|
|
38
24
|
return {
|
|
39
25
|
ok: true,
|
|
40
26
|
status: response.status,
|
|
27
|
+
fromCache: response.fromCache === true,
|
|
41
28
|
logUrl: logUrl,
|
|
42
|
-
logPath: logFile.path || null
|
|
29
|
+
logPath: logFile.path || null,
|
|
30
|
+
build: logFile.build || null,
|
|
31
|
+
pdfDownloadDomain: normalizePdfDownloadDomain(response.pdfDownloadDomain),
|
|
32
|
+
clsiServerId: typeof response.clsiServerId === 'string' && /^[A-Za-z0-9_-]{1,128}$/.test(response.clsiServerId)
|
|
33
|
+
? response.clsiServerId : null
|
|
43
34
|
};
|
|
44
35
|
}
|
|
45
36
|
|
|
37
|
+
function normalizePdfDownloadDomain(value) {
|
|
38
|
+
if (typeof value !== 'string' || !/^https:\/\/[^\s\\]+$/i.test(value)) return null;
|
|
39
|
+
try {
|
|
40
|
+
const url = new URL(value);
|
|
41
|
+
return url.protocol === 'https:' && !url.username && !url.password && !url.search && !url.hash ? value : null;
|
|
42
|
+
} catch (_error) {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
46
47
|
function extractErrorBlocks(lines) {
|
|
47
48
|
var blocks = [];
|
|
48
49
|
var i = 0;
|
|
49
50
|
|
|
50
51
|
while (i < lines.length && blocks.length < 20) {
|
|
51
52
|
var line = lines[i];
|
|
52
|
-
if (line
|
|
53
|
+
if (isLogErrorLine(line) || /^l\.\d+/.test(line)) {
|
|
53
54
|
var block = [];
|
|
54
55
|
// Collect surrounding context: go back up to 2 lines for pre-context
|
|
55
56
|
var start = Math.max(0, i - 2);
|
|
@@ -82,13 +83,9 @@
|
|
|
82
83
|
}
|
|
83
84
|
|
|
84
85
|
function truncateLogForContext(log) {
|
|
85
|
-
if (!log || typeof log !== 'string')
|
|
86
|
-
return '';
|
|
87
|
-
}
|
|
86
|
+
if (!log || typeof log !== 'string') return '';
|
|
88
87
|
|
|
89
|
-
if (new Blob([log]).size <= MAX_LOG_BYTES)
|
|
90
|
-
return log;
|
|
91
|
-
}
|
|
88
|
+
if (new Blob([log]).size <= MAX_LOG_BYTES) return log;
|
|
92
89
|
|
|
93
90
|
var lines = log.split('\n');
|
|
94
91
|
var errorBlocks = extractErrorBlocks(lines);
|
|
@@ -108,31 +105,28 @@
|
|
|
108
105
|
}
|
|
109
106
|
|
|
110
107
|
function isCompileLogFresh(log, lastKnownSourceEditTimestamp) {
|
|
111
|
-
if (!log || typeof log !== 'object')
|
|
112
|
-
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
if (log.sourceChangeTimestamp == null) {
|
|
116
|
-
return false;
|
|
117
|
-
}
|
|
108
|
+
if (!log || typeof log !== 'object') return false;
|
|
109
|
+
if (log.sourceChangeTimestamp == null) return false;
|
|
118
110
|
|
|
119
111
|
return log.sourceChangeTimestamp >= lastKnownSourceEditTimestamp;
|
|
120
112
|
}
|
|
121
113
|
|
|
122
114
|
function isCompilableFile(filePath) {
|
|
123
|
-
if (!filePath || typeof filePath !== 'string')
|
|
124
|
-
return false;
|
|
125
|
-
}
|
|
115
|
+
if (!filePath || typeof filePath !== 'string') return false;
|
|
126
116
|
|
|
127
117
|
var lastDot = filePath.lastIndexOf('.');
|
|
128
|
-
if (lastDot === -1)
|
|
129
|
-
return false;
|
|
130
|
-
}
|
|
118
|
+
if (lastDot === -1) return false;
|
|
131
119
|
|
|
132
120
|
var ext = filePath.slice(lastDot).toLowerCase();
|
|
133
121
|
return COMPILABLE_EXTENSIONS.has(ext);
|
|
134
122
|
}
|
|
135
123
|
|
|
124
|
+
function isLogErrorLine(line) {
|
|
125
|
+
// TeX's -file-line-error output (used by Overleaf) has no leading '!'.
|
|
126
|
+
return line.startsWith('!') || /^Runaway argument/.test(line)
|
|
127
|
+
|| (/^(?:\.{0,2}\/|[A-Za-z]:[\\/])?.+?:\d+:\s+\S/.test(line) && !/\bWarning\b/i.test(line));
|
|
128
|
+
}
|
|
129
|
+
|
|
136
130
|
function parseLogErrors(logContent) {
|
|
137
131
|
var result = { errors: [], warnings: [] };
|
|
138
132
|
|
|
@@ -142,20 +136,23 @@
|
|
|
142
136
|
|
|
143
137
|
var lines = logContent.split('\n');
|
|
144
138
|
|
|
145
|
-
//
|
|
139
|
+
// Retain the bounded error list; consumers limit displayed snippets separately.
|
|
146
140
|
for (var i = 0; i < lines.length && result.errors.length < 50; i++) {
|
|
147
|
-
if (lines[i]
|
|
141
|
+
if (isLogErrorLine(lines[i])) {
|
|
148
142
|
var errorLines = [lines[i]];
|
|
149
143
|
for (var j = 1; j <= 3 && (i + j) < lines.length; j++) {
|
|
144
|
+
if (!lines[i + j].trim() || isLogErrorLine(lines[i + j])) break;
|
|
150
145
|
errorLines.push(lines[i + j]);
|
|
151
146
|
}
|
|
152
147
|
result.errors.push(errorLines.join('\n'));
|
|
153
148
|
}
|
|
154
149
|
}
|
|
155
150
|
|
|
156
|
-
//
|
|
151
|
+
// Count only the warning forms Overleaf's own log parser reports, so
|
|
152
|
+
// informational lines such as "Package: infwarerr … info/warning/error
|
|
153
|
+
// messages" are not shown as warnings.
|
|
157
154
|
for (var k = 0; k < lines.length && result.warnings.length < 50; k++) {
|
|
158
|
-
if (
|
|
155
|
+
if (/^LaTeX(?:3| Font)? Warning: /.test(lines[k]) || /^(?:Package|Class|Module) \b.+\b Warning:/.test(lines[k])) {
|
|
159
156
|
result.warnings.push(lines[k]);
|
|
160
157
|
}
|
|
161
158
|
}
|
|
@@ -164,34 +161,35 @@
|
|
|
164
161
|
}
|
|
165
162
|
|
|
166
163
|
function buildPostWriteCompileSummary({ result = null, logResult = null, error = null } = {}) {
|
|
167
|
-
const
|
|
164
|
+
const captured = result?.ok === true && result.resultCaptured === true
|
|
165
|
+
&& Number.isSafeInteger(result.compile?.captureId) && result.compile.captureId > 0;
|
|
166
|
+
const logAvailable = captured && logResult?.ok === true && logResult.fresh === true
|
|
167
|
+
&& result.compile?.captureId === logResult.captureId;
|
|
168
168
|
const errors = logAvailable && Array.isArray(logResult.errors)
|
|
169
|
-
? logResult.errors.
|
|
169
|
+
? logResult.errors.map(formatCompileDiagnosticForSummary)
|
|
170
170
|
: [];
|
|
171
171
|
const warnings = logAvailable && Array.isArray(logResult.warnings)
|
|
172
172
|
? logResult.warnings.slice(0, 5).map(formatCompileDiagnosticForSummary)
|
|
173
173
|
: [];
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
if (
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
logAvailable
|
|
190
|
-
};
|
|
174
|
+
const compilerFailed = captured && logResult?.fresh !== false
|
|
175
|
+
&& ['failed', 'failure', 'error', 'timedout', 'timeout'].includes(result.compile?.status);
|
|
176
|
+
if (compilerFailed && !errors.length) errors.push('Post-write compile failed.');
|
|
177
|
+
const failed = compilerFailed || errors.length > 0;
|
|
178
|
+
const success = captured && result.compile?.ok === true && result.compile.status === 'success'
|
|
179
|
+
&& result.compile.fromCache !== true && logAvailable;
|
|
180
|
+
const status = failed ? 'failed' : success ? 'success' : result?.ok && !captured ? 'triggered' : 'unconfirmed';
|
|
181
|
+
let reason = error ? error?.message || 'Post-write compile failed.'
|
|
182
|
+
: !result?.ok ? result?.reason || 'Post-write compile did not succeed.' : result.reason || '';
|
|
183
|
+
if (status === 'unconfirmed' || status === 'triggered') {
|
|
184
|
+
// Technical evidence belongs in the existing activity output, not the outcome label.
|
|
185
|
+
reason ||= logResult?.reason || (!captured ? 'compile_capture_missing'
|
|
186
|
+
: result.compile?.reason || (result.compile.fromCache ? 'compile_response_from_cache'
|
|
187
|
+
: logResult?.captureId !== result.compile.captureId ? 'compile_log_capture_mismatch'
|
|
188
|
+
: logResult?.fresh === false ? 'compile_log_stale' : 'compile_result_unconfirmed'));
|
|
191
189
|
}
|
|
192
190
|
return {
|
|
193
|
-
status
|
|
194
|
-
reason
|
|
191
|
+
status,
|
|
192
|
+
reason,
|
|
195
193
|
errors,
|
|
196
194
|
warnings,
|
|
197
195
|
logAvailable
|
|
@@ -203,9 +201,74 @@
|
|
|
203
201
|
return text.length > 180 ? `${text.slice(0, 179)}...` : text;
|
|
204
202
|
}
|
|
205
203
|
|
|
204
|
+
// Page/adapter-owned explanations and activity labels are display copy. TeX logs and unknown
|
|
205
|
+
// upstream diagnostics stay literal; callers retain the original evidence.
|
|
206
|
+
function localizeCompileReason(value, locale = 'en', depth = 0) {
|
|
207
|
+
const text = String(value || '');
|
|
208
|
+
const zh = /^zh(?:\b|[-_])/i.test(String(locale));
|
|
209
|
+
const messages = [
|
|
210
|
+
['Compile succeeded.', '编译成功。'],
|
|
211
|
+
['Overleaf compile was triggered. The page will continue showing progress.', '已触发 Overleaf 编译;页面会继续显示编译进度。'],
|
|
212
|
+
['Fetching compile log (@compile-log).', '正在获取编译日志 (@compile-log)。'],
|
|
213
|
+
['Overleaf save state was not verified.', '尚未确认 Overleaf 已保存。'],
|
|
214
|
+
['No Overleaf Recompile button or compile request template is available.', '没有可用的 Overleaf 重新编译按钮或编译请求模板。'],
|
|
215
|
+
['Stored compile request template expired. Please click Overleaf Recompile once, then retry.', '已保存的编译请求模板已过期。请先点击一次 Overleaf 重新编译,再重试。'],
|
|
216
|
+
['CompileAdapter not available', '编译适配器不可用'],
|
|
217
|
+
['CompileAdapter not loaded', '编译适配器未加载'],
|
|
218
|
+
['Overleaf save-state checker is unavailable.', 'Overleaf 保存状态检查器不可用。'],
|
|
219
|
+
['No compile log URL available', '没有可用的编译日志地址'],
|
|
220
|
+
['Clicked Overleaf Recompile, but no compile result was captured.', '已点击 Overleaf 重新编译,但未获取到编译结果。'],
|
|
221
|
+
['Invalid compile response: not an object', '编译响应无效:结果不是对象'],
|
|
222
|
+
['Invalid compile response: outputFiles is not an array', '编译响应无效:outputFiles 不是数组'],
|
|
223
|
+
['No log file found in compile output', '编译输出中未找到日志文件'],
|
|
224
|
+
['Log file has no URL', '日志文件没有可用地址'],
|
|
225
|
+
['Post-write compile failed.', '写后编译失败。'],
|
|
226
|
+
['Post-write compile did not succeed.', '写后编译未成功。'],
|
|
227
|
+
['Overleaf save state has not been checked yet.', '尚未检查 Overleaf 保存状态。'],
|
|
228
|
+
['Overleaf save state is unavailable.', 'Overleaf 保存状态不可用。'],
|
|
229
|
+
['Overleaf is offline; saving cannot be confirmed.', 'Overleaf 当前离线,无法确认保存。'],
|
|
230
|
+
['Overleaf document is unavailable for save-state detection.', 'Overleaf 文档不可用,无法检测保存状态。'],
|
|
231
|
+
['Overleaf save indicator reports changes are not saved.', 'Overleaf 保存指示器显示修改尚未保存。'],
|
|
232
|
+
['Overleaf is still saving or syncing changes.', 'Overleaf 仍在保存或同步修改。'],
|
|
233
|
+
['Overleaf save indicator was present, but did not verify that all changes are saved.', '已找到 Overleaf 保存指示器,但未能确认全部修改已保存。'],
|
|
234
|
+
['Overleaf save indicator was not found.', '未找到 Overleaf 保存指示器。']
|
|
235
|
+
];
|
|
236
|
+
for (const [en, chinese] of messages) {
|
|
237
|
+
if (text === en || text === chinese) return zh ? chinese : en;
|
|
238
|
+
}
|
|
239
|
+
for (const [en, chinese] of [
|
|
240
|
+
['Compile request failed with status ', '编译请求失败,状态码为 '],
|
|
241
|
+
['Failed to fetch compile log: ', '获取编译日志失败,状态码为 ']
|
|
242
|
+
]) {
|
|
243
|
+
const prefix = text.startsWith(en) ? en : text.startsWith(chinese) ? chinese : '';
|
|
244
|
+
if (prefix && /^\d{3}$/.test(text.slice(prefix.length))) return (zh ? chinese : en) + text.slice(prefix.length);
|
|
245
|
+
}
|
|
246
|
+
if (depth < 4) for (const [en, chinese] of [
|
|
247
|
+
['Post-write compile did not succeed: ', '写后编译未成功:'],
|
|
248
|
+
['Post-write compile failed: ', '写后编译出错:'],
|
|
249
|
+
['Compile log unavailable: ', '编译日志不可用:'],
|
|
250
|
+
['Compile request error: ', '编译请求出错:'],
|
|
251
|
+
['Overleaf save-state checker failed: ', 'Overleaf 保存状态检查失败:'],
|
|
252
|
+
['Could not check Overleaf save state: ', '无法检查 Overleaf 保存状态:'],
|
|
253
|
+
['Could not get fresh compile: ', '无法获取最新编译结果:'],
|
|
254
|
+
['Log fetch error: ', '获取日志出错:'],
|
|
255
|
+
['Timed out waiting for Overleaf to finish saving changes.', '等待 Overleaf 完成保存超时。'],
|
|
256
|
+
['Timed out waiting for a verified Overleaf save state.', '等待确认 Overleaf 保存状态超时。']
|
|
257
|
+
]) {
|
|
258
|
+
const prefix = text.startsWith(en) ? en : text.startsWith(chinese) ? chinese : '';
|
|
259
|
+
if (prefix) {
|
|
260
|
+
const rest = text.slice(prefix.length);
|
|
261
|
+
if (prefix === en && en.endsWith('.') && rest && !rest.startsWith(' ')) continue;
|
|
262
|
+
const suffix = localizeCompileReason(prefix === en && en.endsWith('.') ? rest.slice(1) : rest, locale, depth + 1);
|
|
263
|
+
return (zh ? chinese : en) + (!zh && suffix && en.endsWith('.') ? ' ' : '') + suffix;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return text;
|
|
267
|
+
}
|
|
206
268
|
return {
|
|
207
269
|
buildPostWriteCompileSummary,
|
|
208
270
|
formatCompileDiagnosticForSummary,
|
|
271
|
+
localizeCompileReason,
|
|
209
272
|
COMPILABLE_EXTENSIONS,
|
|
210
273
|
MAX_LOG_BYTES,
|
|
211
274
|
extractErrorBlocks,
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
(function (root, factory) {
|
|
2
|
+
if (typeof module === 'object' && module.exports) module.exports = factory(require('./sessionState'));
|
|
3
|
+
else root.CodexOverleafModuleRegistry.define('ComposerAttachmentDraft', ['SessionState'], factory);
|
|
4
|
+
})(typeof globalThis !== 'undefined' ? globalThis : this, function (SessionState) {
|
|
5
|
+
'use strict';
|
|
6
|
+
const EPOCH_KEY = 'composer-attachment-draft:clear-epoch';
|
|
7
|
+
const normalize = value => SessionState.normalizeComposerAttachmentsForPersistence(value);
|
|
8
|
+
const scopeKey = scope => JSON.stringify([scope?.accountScopeId || '', scope?.projectId || '']);
|
|
9
|
+
function checkedScope(scope) {
|
|
10
|
+
const accountScopeId = String(scope?.accountScopeId || '').trim();
|
|
11
|
+
const projectId = String(scope?.projectId || '').trim();
|
|
12
|
+
if (!accountScopeId || !projectId) throw failure('account_scope_unavailable');
|
|
13
|
+
return { accountScopeId, projectId };
|
|
14
|
+
}
|
|
15
|
+
function failure(code) {
|
|
16
|
+
return Object.assign(new Error(code === 'composer_draft_cleared'
|
|
17
|
+
? 'Saved attachments were cleared in another tab. Reload, then reattach files before retrying.'
|
|
18
|
+
: code === 'composer_draft_conflict'
|
|
19
|
+
? 'The saved attachment draft changed in another tab. Reload before changing it.'
|
|
20
|
+
: 'Attachment draft scope is unavailable or changed.'), { code });
|
|
21
|
+
}
|
|
22
|
+
function createStore(openDb) {
|
|
23
|
+
async function access(input, value) {
|
|
24
|
+
const scope = checkedScope(input);
|
|
25
|
+
const id = 'composer-attachment-draft:' + scopeKey(scope);
|
|
26
|
+
const db = await openDb();
|
|
27
|
+
return new Promise((resolve, reject) => {
|
|
28
|
+
const tx = db.transaction('artifacts', value ? 'readwrite' : 'readonly');
|
|
29
|
+
const store = tx.objectStore('artifacts');
|
|
30
|
+
let result, error;
|
|
31
|
+
tx.oncomplete = () => resolve(result);
|
|
32
|
+
tx.onerror = tx.onabort = () => reject(error || tx.error || new Error('Attachment draft transaction failed.'));
|
|
33
|
+
const epochRequest = store.get(EPOCH_KEY);
|
|
34
|
+
epochRequest.onsuccess = () => {
|
|
35
|
+
const epoch = Number(epochRequest.result?.epoch) || 0;
|
|
36
|
+
const clearedAt = Number(epochRequest.result?.clearedAt) || 0;
|
|
37
|
+
const request = store.get(id);
|
|
38
|
+
request.onsuccess = () => {
|
|
39
|
+
try {
|
|
40
|
+
const previous = request.result;
|
|
41
|
+
if (previous && (previous.accountScopeId !== scope.accountScopeId || previous.projectId !== scope.projectId)) {
|
|
42
|
+
throw failure('account_scope_unavailable');
|
|
43
|
+
}
|
|
44
|
+
const revision = Number(previous?.revision) || 0;
|
|
45
|
+
result = { epoch, clearedAt, revision, attachments: normalize(previous?.attachments) };
|
|
46
|
+
if (!value) return;
|
|
47
|
+
const attachments = normalize(value.attachments);
|
|
48
|
+
if (value.epoch !== epoch) {
|
|
49
|
+
// A clear performed by this page also invalidates its old base.
|
|
50
|
+
// Its empty draft needs no write; an old nonempty draft cannot return.
|
|
51
|
+
if (!attachments.length) return;
|
|
52
|
+
throw failure('composer_draft_cleared');
|
|
53
|
+
}
|
|
54
|
+
if (value.revision !== revision) {
|
|
55
|
+
// Writing what another tab already stored changes nothing (for example
|
|
56
|
+
// both tabs clearing an empty draft on send); only a real edit conflicts.
|
|
57
|
+
if (JSON.stringify(attachments) === JSON.stringify(result.attachments)) return;
|
|
58
|
+
throw failure('composer_draft_conflict');
|
|
59
|
+
}
|
|
60
|
+
if (attachments.length && value.unbased && clearedAt && value.changedAt <= clearedAt) {
|
|
61
|
+
throw failure('composer_draft_cleared');
|
|
62
|
+
}
|
|
63
|
+
result = { epoch, clearedAt, revision: revision + 1, attachments };
|
|
64
|
+
store.put({ id, type: 'composer-attachment-draft', ...scope, ...result });
|
|
65
|
+
} catch (caught) { error = caught; tx.abort(); }
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
async function clear(storeNames) {
|
|
71
|
+
const db = await openDb();
|
|
72
|
+
return new Promise((resolve, reject) => {
|
|
73
|
+
const tx = db.transaction(storeNames, 'readwrite');
|
|
74
|
+
const artifacts = tx.objectStore('artifacts');
|
|
75
|
+
const request = artifacts.get(EPOCH_KEY);
|
|
76
|
+
let epoch;
|
|
77
|
+
request.onsuccess = () => {
|
|
78
|
+
epoch = (Number(request.result?.epoch) || 0) + 1;
|
|
79
|
+
for (const name of storeNames) tx.objectStore(name).clear();
|
|
80
|
+
artifacts.put({ id: EPOCH_KEY, type: 'composer-attachment-draft-epoch', epoch, clearedAt: Date.now() });
|
|
81
|
+
};
|
|
82
|
+
tx.oncomplete = () => resolve({ composerDraftEpoch: epoch });
|
|
83
|
+
tx.onerror = tx.onabort = () => reject(tx.error || new Error('History clear failed.'));
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
return { read: scope => access(scope), write: (scope, value) => access(scope, value), clear };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function createController(options) {
|
|
90
|
+
let scope = options.getScope(), key = scopeKey(scope), generation = 0;
|
|
91
|
+
let base = null, hydrated = false, changedVersion = 0, savedVersion = 0;
|
|
92
|
+
let dirtySince = 0, dirtyUnbased = false, loadSequence = 0;
|
|
93
|
+
let saving = Promise.resolve();
|
|
94
|
+
const publish = () => options.setAttachments(normalize(options.getAttachments()));
|
|
95
|
+
function syncScope() {
|
|
96
|
+
const next = options.getScope(), nextKey = scopeKey(next);
|
|
97
|
+
if (nextKey === key) return;
|
|
98
|
+
scope = next; key = nextKey; generation++;
|
|
99
|
+
base = null; hydrated = false; changedVersion = savedVersion = 0; dirtySince = 0; dirtyUnbased = false;
|
|
100
|
+
options.clear(); publish();
|
|
101
|
+
}
|
|
102
|
+
async function load(requestedScope = options.getScope()) {
|
|
103
|
+
syncScope();
|
|
104
|
+
if (scopeKey(requestedScope) !== key) throw failure('stale_hydration');
|
|
105
|
+
const captured = generation, sequence = ++loadSequence;
|
|
106
|
+
const snapshot = await options.read(checkedScope(scope));
|
|
107
|
+
if (captured !== generation || sequence !== loadSequence || scopeKey(options.getScope()) !== key) throw failure('stale_hydration');
|
|
108
|
+
if (base && snapshot.epoch !== base.epoch) {
|
|
109
|
+
// Adopt an observed history clear together with its empty local view;
|
|
110
|
+
// otherwise the next edit could attach an old view to the new epoch.
|
|
111
|
+
options.clear(); changedVersion = savedVersion = 0; dirtySince = 0; dirtyUnbased = false;
|
|
112
|
+
hydrated = false; base = snapshot; publish();
|
|
113
|
+
}
|
|
114
|
+
const keepLocal = changedVersion || hydrated;
|
|
115
|
+
// A read alone does not give an unchanged visible draft a newer CAS base.
|
|
116
|
+
if (!base || !keepLocal) base = snapshot;
|
|
117
|
+
return keepLocal ? normalize(options.getAttachments()) : snapshot.attachments;
|
|
118
|
+
}
|
|
119
|
+
function hydrate(attachments) {
|
|
120
|
+
syncScope();
|
|
121
|
+
if (hydrated) return;
|
|
122
|
+
// Loading is allowed to finish after a local add/remove. Local edits win.
|
|
123
|
+
if (!changedVersion && !options.getAttachments().length) options.restore(normalize(attachments));
|
|
124
|
+
hydrated = true; publish();
|
|
125
|
+
}
|
|
126
|
+
function changed() {
|
|
127
|
+
const before = key;
|
|
128
|
+
syncScope();
|
|
129
|
+
if (before !== key) return;
|
|
130
|
+
if (changedVersion === savedVersion) { dirtySince = Date.now(); dirtyUnbased = !base; }
|
|
131
|
+
changedVersion++; hydrated = true; publish(); options.scheduleSave();
|
|
132
|
+
}
|
|
133
|
+
function save(requestedScope = options.getScope()) {
|
|
134
|
+
syncScope();
|
|
135
|
+
if (scopeKey(requestedScope) !== key) return Promise.resolve();
|
|
136
|
+
const captured = generation;
|
|
137
|
+
const work = async () => {
|
|
138
|
+
if (captured !== generation || scopeKey(options.getScope()) !== key || changedVersion === savedVersion) return;
|
|
139
|
+
const version = changedVersion;
|
|
140
|
+
const attachments = normalize(options.getAttachments());
|
|
141
|
+
const unbased = dirtyUnbased, changedAt = dirtySince;
|
|
142
|
+
if (!base) await load(scope);
|
|
143
|
+
if (captured !== generation || scopeKey(options.getScope()) !== key) return;
|
|
144
|
+
const written = await options.write(checkedScope(scope), { ...base, attachments, unbased, changedAt });
|
|
145
|
+
if (captured !== generation) return;
|
|
146
|
+
base = written; savedVersion = version;
|
|
147
|
+
if (changedVersion === version) { dirtySince = 0; dirtyUnbased = false; }
|
|
148
|
+
};
|
|
149
|
+
const next = saving.then(work, work);
|
|
150
|
+
saving = next.catch(() => {});
|
|
151
|
+
return next;
|
|
152
|
+
}
|
|
153
|
+
return { syncScope, load, hydrate, changed, save };
|
|
154
|
+
}
|
|
155
|
+
return { createStore, createController, normalize, EPOCH_KEY };
|
|
156
|
+
});
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
(function initContextUsage(root, factory) {
|
|
2
|
+
if (typeof module === 'object' && module.exports) module.exports = factory();
|
|
3
|
+
else root.CodexOverleafModuleRegistry.define('ContextUsage', [], factory);
|
|
4
|
+
})(typeof globalThis !== 'undefined' ? globalThis : window, function contextUsageFactory() {
|
|
5
|
+
'use strict';
|
|
6
|
+
|
|
7
|
+
const count = value => Number.isSafeInteger(value) && value >= 0 ? value : null;
|
|
8
|
+
const identity = value => typeof value === 'string' && value.trim() && value.length <= 512
|
|
9
|
+
&& !/[\0\r\n]/.test(value) ? value.trim() : '';
|
|
10
|
+
const timestamp = value => typeof value === 'string' && Number.isFinite(Date.parse(value))
|
|
11
|
+
? new Date(value).toISOString() : '';
|
|
12
|
+
|
|
13
|
+
// Store a bounded fact from one model response, never the cumulative billed
|
|
14
|
+
// token total. The last reported response is an estimate of context occupancy;
|
|
15
|
+
// tool output added since then is not included until the next server update.
|
|
16
|
+
function normalize(value) {
|
|
17
|
+
if (!value || value.v !== 1) return null;
|
|
18
|
+
const threadId = identity(value.threadId), turnId = identity(value.turnId);
|
|
19
|
+
const runId = identity(value.runId), providerId = identity(value.providerId), model = identity(value.model);
|
|
20
|
+
const usedTokens = count(value.usedTokens), providerRevision = count(value.providerRevision);
|
|
21
|
+
const observedAt = timestamp(value.observedAt);
|
|
22
|
+
if (!threadId || !turnId || !runId || !providerId || !model || usedTokens === null
|
|
23
|
+
|| providerRevision === null || !observedAt) return null;
|
|
24
|
+
const capacity = count(value.contextWindow);
|
|
25
|
+
const eventSeq = count(value.eventSeq);
|
|
26
|
+
const inputTokens = count(value.inputTokens), outputTokens = count(value.outputTokens);
|
|
27
|
+
const cachedInput = count(value.cachedInputTokens);
|
|
28
|
+
// Cache reads are a subset of input, never an additional category. Older
|
|
29
|
+
// snapshots and providers without these counters retain unknown values.
|
|
30
|
+
const cachedInputTokens = inputTokens !== null && cachedInput !== null && cachedInput <= inputTokens
|
|
31
|
+
? cachedInput : null;
|
|
32
|
+
return { v: 1, threadId, turnId, runId, providerId, providerRevision, model,
|
|
33
|
+
usedTokens, contextWindow: capacity > 0 ? capacity : null,
|
|
34
|
+
inputTokens, outputTokens, cachedInputTokens,
|
|
35
|
+
eventSeq: eventSeq > 0 ? eventSeq : null, observedAt };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function matches(value, scope = {}) {
|
|
39
|
+
const snapshot = normalize(value);
|
|
40
|
+
return Boolean(snapshot && snapshot.threadId === identity(scope.threadId || scope.codexThreadId)
|
|
41
|
+
&& snapshot.providerId === identity(scope.providerId)
|
|
42
|
+
&& snapshot.providerRevision === count(scope.providerRevision)
|
|
43
|
+
&& snapshot.model === identity(scope.model));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// The caller supplies the captured run owner, after its Native thread/turn
|
|
47
|
+
// binding is known. Reject worker, previous-turn and unbound notifications.
|
|
48
|
+
function fromEvent(event, owner = {}) {
|
|
49
|
+
if (!event || (event.type && event.type !== 'codex.session.event') || event.subagent === true) return null;
|
|
50
|
+
const notification = event.detail || event;
|
|
51
|
+
if (notification.method !== 'thread/tokenUsage/updated') return null;
|
|
52
|
+
const params = notification.params || {};
|
|
53
|
+
const threadId = identity(params.threadId), turnId = identity(params.turnId);
|
|
54
|
+
if (!threadId || !turnId || threadId !== identity(owner.threadId || owner.codexThreadId)
|
|
55
|
+
|| turnId !== identity(owner.turnId || owner.codexTurnId) || owner.forkSnapshot === true) return null;
|
|
56
|
+
const last = params.tokenUsage?.last;
|
|
57
|
+
const usedTokens = count(last?.totalTokens);
|
|
58
|
+
if (usedTokens === null) return null;
|
|
59
|
+
return normalize({ v: 1, threadId, turnId, runId: owner.runId || owner.id,
|
|
60
|
+
providerId: owner.providerId, providerRevision: owner.providerRevision, model: owner.model,
|
|
61
|
+
usedTokens, contextWindow: params.tokenUsage?.modelContextWindow,
|
|
62
|
+
inputTokens: last?.inputTokens, outputTokens: last?.outputTokens, cachedInputTokens: last?.cachedInputTokens,
|
|
63
|
+
eventSeq: event.nativeEventSeq ?? event.eventSeq ?? event.seq ?? owner.eventSeq,
|
|
64
|
+
observedAt: timestamp(event.timestamp || event.at) || timestamp(owner.observedAt) || new Date().toISOString() });
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function isNewer(next, previous) {
|
|
68
|
+
next = normalize(next); previous = normalize(previous);
|
|
69
|
+
if (!next) return false;
|
|
70
|
+
if (!previous) return true;
|
|
71
|
+
if (next.runId === previous.runId && next.threadId === previous.threadId && next.turnId === previous.turnId
|
|
72
|
+
&& next.eventSeq !== null && previous.eventSeq !== null)
|
|
73
|
+
return next.eventSeq > previous.eventSeq;
|
|
74
|
+
return Date.parse(next.observedAt) > Date.parse(previous.observedAt);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// `scope.compactedAt` (epoch ms) carries an automatic compaction completed
|
|
78
|
+
// inside a normal turn; manual compact runs are read from the session.
|
|
79
|
+
// `pending` means the old sample is invalid; `compacted` additionally requires
|
|
80
|
+
// successful completion of the most recent compaction that invalidated it.
|
|
81
|
+
function status(session, scope = {}) {
|
|
82
|
+
if (!session || session.forkSnapshot === true) return { usage: null, pending: false, compacted: false };
|
|
83
|
+
const inThread = value => value && value.threadId === identity(scope.threadId || scope.codexThreadId)
|
|
84
|
+
&& value.providerId === identity(scope.providerId)
|
|
85
|
+
&& value.providerRevision === count(scope.providerRevision);
|
|
86
|
+
// A dispatched compaction can change this thread even when its completion
|
|
87
|
+
// is lost on reload. Do not re-display a pre-compaction sample as current.
|
|
88
|
+
const runs = Array.isArray(session.runs) ? session.runs : [];
|
|
89
|
+
let invalidatedAfter = Number.isFinite(scope.compactedAt) && scope.compactedAt > 0 ? scope.compactedAt : 0;
|
|
90
|
+
let completedAfter = invalidatedAfter;
|
|
91
|
+
for (const run of runs) {
|
|
92
|
+
if (run?.operation !== 'compact' || run.forkSnapshot
|
|
93
|
+
|| run.codexThreadId !== identity(scope.threadId || scope.codexThreadId)) continue;
|
|
94
|
+
const startedAt = Date.parse(run.startedAt || '') || 0;
|
|
95
|
+
invalidatedAfter = Math.max(invalidatedAfter, startedAt);
|
|
96
|
+
if (run.status === 'completed') completedAfter = Math.max(completedAfter, startedAt);
|
|
97
|
+
}
|
|
98
|
+
let selected = normalize(session.contextUsage);
|
|
99
|
+
if (!inThread(selected)) selected = null;
|
|
100
|
+
for (const run of runs) {
|
|
101
|
+
if (run?.forkSnapshot === true) continue;
|
|
102
|
+
const candidate = normalize(run.contextUsage);
|
|
103
|
+
if (!inThread(candidate) || candidate.runId !== run.id) continue;
|
|
104
|
+
if (isNewer(candidate, selected)) selected = candidate;
|
|
105
|
+
}
|
|
106
|
+
const beforeCompaction = !selected || Date.parse(selected.observedAt) < invalidatedAfter;
|
|
107
|
+
// A later turn can grow this thread using another model. Selecting an older
|
|
108
|
+
// model again must not resurrect its obsolete, smaller context estimate.
|
|
109
|
+
const usage = selected?.model === identity(scope.model) && !beforeCompaction ? selected : null;
|
|
110
|
+
const pending = !usage && invalidatedAfter > 0 && beforeCompaction;
|
|
111
|
+
return { usage, pending, compacted: pending && completedAfter >= invalidatedAfter };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function select(session, scope = {}) {
|
|
115
|
+
return status(session, scope).usage;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Presentation level of a reported share of the window, in percent.
|
|
119
|
+
function level(percent) {
|
|
120
|
+
if (typeof percent !== 'number' || !Number.isFinite(percent)) return '';
|
|
121
|
+
return percent >= 90 ? 'near-limit' : percent >= 70 ? 'high' : 'normal';
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return { normalize, fromEvent, matches, isNewer, select, status, level };
|
|
125
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
(function (root, factory) {
|
|
2
|
+
if (typeof module === 'object' && module.exports) module.exports = factory();
|
|
3
|
+
else if (root.CodexOverleafModuleRegistry) root.CodexOverleafModuleRegistry.define('DirectoryCleanupProof', [], factory);
|
|
4
|
+
else root.CodexOverleafDirectoryCleanupProof = factory();
|
|
5
|
+
})(typeof globalThis !== 'undefined' ? globalThis : window, function () {
|
|
6
|
+
'use strict';
|
|
7
|
+
const LIMITS = Object.freeze({ maxDirectories: 100, maxBytes: 64 * 1024 });
|
|
8
|
+
const id = (value, max = 160) => typeof value === 'string' && value.length > 0 && value.length <= max
|
|
9
|
+
&& value.trim() === value && !/[\u0000-\u001f\u007f]/.test(value);
|
|
10
|
+
const path = value => id(value, 2048) && !/[\\]/.test(value) && !value.startsWith('/') && !/^[A-Za-z]:/.test(value)
|
|
11
|
+
&& !value.split('/').some(part => !part || part === '.' || part === '..');
|
|
12
|
+
const ownerKeys = ['accountScopeId', 'projectId', 'sessionId', 'runId'];
|
|
13
|
+
function normalize(value) {
|
|
14
|
+
if (!value || value.v !== 1 || !ownerKeys.every(key => id(value[key])) || !Array.isArray(value.directories)
|
|
15
|
+
|| !value.directories.length || value.directories.length > LIMITS.maxDirectories) return null;
|
|
16
|
+
const directories = [], seen = new Set();
|
|
17
|
+
for (const raw of value.directories) {
|
|
18
|
+
if (raw?.v !== 1 || raw.projectId !== value.projectId || !path(raw.path)
|
|
19
|
+
|| typeof raw.nativeFolderId !== 'string' || (raw.nativeFolderId && !id(raw.nativeFolderId, 256))) return null;
|
|
20
|
+
const key = JSON.stringify([raw.path, raw.nativeFolderId]);
|
|
21
|
+
if (seen.has(key)) continue;
|
|
22
|
+
seen.add(key);
|
|
23
|
+
directories.push({ v: 1, projectId: raw.projectId, path: raw.path, nativeFolderId: raw.nativeFolderId,
|
|
24
|
+
// No current producer has an acknowledged server-side creator ID.
|
|
25
|
+
ownershipConfirmed: false, observedAfterCreate: raw.observedAfterCreate === true });
|
|
26
|
+
}
|
|
27
|
+
const result = { v: 1, ...Object.fromEntries(ownerKeys.map(key => [key, value[key]])), directories };
|
|
28
|
+
const bytes = typeof TextEncoder === 'function' ? new TextEncoder().encode(JSON.stringify(result)).length : JSON.stringify(result).length * 2;
|
|
29
|
+
return bytes <= LIMITS.maxBytes ? result : null;
|
|
30
|
+
}
|
|
31
|
+
function merge(owner, previous, directories) {
|
|
32
|
+
const prior = previous == null ? null : normalize(previous);
|
|
33
|
+
if (previous != null && (!prior || ownerKeys.some(key => prior[key] !== owner[key]))) return null;
|
|
34
|
+
const combined = [...(prior?.directories || [])];
|
|
35
|
+
for (const next of directories || []) {
|
|
36
|
+
const index = combined.findIndex(item => item.path === next?.path && item.projectId === next?.projectId
|
|
37
|
+
&& (!item.nativeFolderId || item.nativeFolderId === next?.nativeFolderId));
|
|
38
|
+
if (index < 0) combined.push(next); else combined[index] = next;
|
|
39
|
+
}
|
|
40
|
+
return normalize({ v: 1, ...owner, directories: combined });
|
|
41
|
+
}
|
|
42
|
+
return { LIMITS, normalize, merge };
|
|
43
|
+
});
|