codex-overleaf-link 2.5.1 → 2.6.5
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 +149 -398
- package/README.zh-CN.md +153 -404
- package/extension/bootstrap/manifest.template.json +1 -1
- package/extension/src/background.js +148 -3
- package/extension/src/backgroundSharedSessions.js +55 -2
- package/extension/src/backgroundUpdateCoordinator.js +128 -12
- package/extension/src/content/activeTurnControl.js +164 -7
- package/extension/src/content/composerAttachments.js +46 -10
- package/extension/src/content/composerPanel.js +92 -12
- package/extension/src/content/contentRuntime.js +1114 -321
- package/extension/src/content/contextTray.js +8 -2
- package/extension/src/content/contextUsageView.js +267 -0
- package/extension/src/content/diagnosticsController.js +77 -34
- package/extension/src/content/diagnosticsExport.js +1 -1
- package/extension/src/content/diagnosticsPanel.js +147 -42
- package/extension/src/content/generated/content.bundle.js +5761 -1237
- package/extension/src/content/generated/content.bundle.meta.json +6 -3
- package/extension/src/content/legacyGlobalRegistry.js +4 -0
- package/extension/src/content/localSkillsPanel.js +6 -4
- package/extension/src/content/markdownDomRenderer.js +5 -3
- package/extension/src/content/markdownText.js +53 -14
- package/extension/src/content/modelPicker.js +29 -19
- package/extension/src/content/nativeCompatibilityController.js +52 -19
- package/extension/src/content/otWarmMirror.js +1 -1
- package/extension/src/content/pageBridgeClient.js +7 -7
- package/extension/src/content/panelMaintenance.js +10 -2
- package/extension/src/content/panelRenderer.js +55 -28
- package/extension/src/content/pendingInputView.js +1 -1
- package/extension/src/content/projectSettingsCoordinator.js +30 -6
- package/extension/src/content/providerSettingsDialog.js +28 -19
- package/extension/src/content/recentProjects.js +30 -19
- package/extension/src/content/runActivitySummary.js +122 -14
- package/extension/src/content/runCompletionMeta.js +194 -6
- package/extension/src/content/runController.js +2 -1
- package/extension/src/content/runFailureNotice.js +8 -1
- package/extension/src/content/runPresence.js +18 -8
- package/extension/src/content/runQueueScheduler.js +27 -13
- package/extension/src/content/runRecoveryActions.js +2 -1
- package/extension/src/content/runResultActions.js +73 -24
- package/extension/src/content/runReviewController.js +102 -37
- package/extension/src/content/runTimelineView.js +224 -25
- package/extension/src/content/scopedPersistencePanelState.js +124 -46
- package/extension/src/content/selectionContextView.js +30 -3
- package/extension/src/content/sensitivePreflight.js +14 -1
- package/extension/src/content/sessionManager.js +49 -3
- package/extension/src/content/sessionMenuView.js +54 -18
- package/extension/src/content/sessionPersistence.js +25 -5
- package/extension/src/content/settingsPanel.js +15 -2
- package/extension/src/content/settingsWorkbench.js +127 -89
- package/extension/src/content/sharedSessionViewSync.js +13 -4
- package/extension/src/content/subagentActivityView.js +23 -7
- package/extension/src/content/updateNotice.js +60 -6
- package/extension/src/content/writebackOrchestrator.js +301 -72
- package/extension/src/content/writingStyleSettings.js +70 -17
- package/extension/src/page/compileBridge.js +193 -180
- package/extension/src/page/overleafCapabilities.js +4 -1
- package/extension/src/page/overleafEditor.js +12 -3
- package/extension/src/page/overleafProjectSnapshot.js +56 -3
- package/extension/src/page/snapshotRouter.js +75 -66
- package/extension/src/page/textCoordinates.js +17 -1
- package/extension/src/page/textFileCreator.js +9 -1
- package/extension/src/page/treeOperations.js +200 -19
- package/extension/src/page/writebackRouter.js +172 -113
- package/extension/src/pageBridge.js +124 -73
- package/extension/src/popup.js +1 -1
- package/extension/src/shared/agentTranscript.js +372 -18
- package/extension/src/shared/auditRecords.js +3 -0
- package/extension/src/shared/compatibility.js +1 -1
- package/extension/src/shared/compileAdapter.js +58 -17
- package/extension/src/shared/composerAttachmentDraft.js +27 -7
- package/extension/src/shared/contextUsage.js +125 -0
- package/extension/src/shared/failureReasons.js +57 -3
- package/extension/src/shared/i18n.js +315 -29
- package/extension/src/shared/lineReferences.js +38 -31
- package/extension/src/shared/pageRpcContract.js +1 -1
- package/extension/src/shared/panelIcons.js +39 -0
- package/extension/src/shared/projectFiles.js +2 -1
- 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/scopedPersistenceBrowserAdapter.js +34 -0
- package/extension/src/shared/sessionState.js +63 -4
- package/extension/src/shared/sessionTitle.js +14 -1
- package/extension/src/shared/sharedSessionBridge.js +81 -13
- package/extension/src/shared/staleGuard.js +130 -63
- package/extension/src/shared/storageDb.js +35 -3
- package/extension/src/shared/storageMigration.js +111 -2
- package/extension/src/shared/storageRunActions.js +62 -8
- package/extension/src/shared/undoOperations.js +126 -28
- package/extension/src/shared/updateRuntimeIdentity.js +16 -2
- package/extension/src/shared/writebackSettlement.js +21 -3
- package/extension/styles/panel.css +1293 -549
- package/native-host/src/bootstrapUpdate.js +52 -16
- package/native-host/src/chatBridgeRequest.js +60 -6
- package/native-host/src/chatBridgeResponse.js +10 -1
- package/native-host/src/chatCompletionsBridge.js +29 -2
- package/native-host/src/codexPromptAssembly.js +86 -118
- package/native-host/src/codexSessionRunner.js +127 -17
- package/native-host/src/debugLog.js +27 -3
- package/native-host/src/index.js +42 -11
- package/native-host/src/localSkills.js +1 -1
- package/native-host/src/mirrorWorkspace.js +47 -3
- package/native-host/src/nativeEnvironment.js +27 -5
- package/native-host/src/providerContextError.js +20 -0
- package/native-host/src/providerRuntime.js +1 -0
- package/native-host/src/skills/reviewer-response/SKILL.md +65 -0
- package/native-host/src/taskRunnerRuntime.js +28 -8
- package/native-host/src/textPatch.js +473 -377
- package/native-host/src/updateManager.js +144 -21
- package/native-host/src/writingStyleRuntime.js +1 -1
- package/package.json +5 -8
|
@@ -11,31 +11,46 @@
|
|
|
11
11
|
const COMPILABLE_EXTENSIONS = new Set(['.tex', '.bib', '.sty', '.cls', '.bst', '.bbx', '.cbx']);
|
|
12
12
|
function parseCompileResponse(response) {
|
|
13
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, reason: 'Invalid compile response: outputFiles is not an array' };
|
|
15
|
-
const logFile = response.outputFiles.find(function (file) {
|
|
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) {
|
|
16
16
|
if (!file) return false;
|
|
17
17
|
if (file.type === 'log') return true;
|
|
18
18
|
if (typeof file.path === 'string' && file.path.endsWith('.log')) return true;
|
|
19
19
|
return false;
|
|
20
20
|
});
|
|
21
|
-
if (!logFile) return { ok: false, reason: 'No log file found in compile output' };
|
|
22
|
-
var logUrl = logFile.url
|
|
23
|
-
if (!logUrl) return { ok: false, reason: 'Log file has no URL' };
|
|
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' };
|
|
24
24
|
return {
|
|
25
25
|
ok: true,
|
|
26
26
|
status: response.status,
|
|
27
|
+
fromCache: response.fromCache === true,
|
|
27
28
|
logUrl: logUrl,
|
|
28
|
-
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
|
|
29
34
|
};
|
|
30
35
|
}
|
|
31
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
|
+
|
|
32
47
|
function extractErrorBlocks(lines) {
|
|
33
48
|
var blocks = [];
|
|
34
49
|
var i = 0;
|
|
35
50
|
|
|
36
51
|
while (i < lines.length && blocks.length < 20) {
|
|
37
52
|
var line = lines[i];
|
|
38
|
-
if (line
|
|
53
|
+
if (isLogErrorLine(line) || /^l\.\d+/.test(line)) {
|
|
39
54
|
var block = [];
|
|
40
55
|
// Collect surrounding context: go back up to 2 lines for pre-context
|
|
41
56
|
var start = Math.max(0, i - 2);
|
|
@@ -106,6 +121,12 @@
|
|
|
106
121
|
return COMPILABLE_EXTENSIONS.has(ext);
|
|
107
122
|
}
|
|
108
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
|
+
|
|
109
130
|
function parseLogErrors(logContent) {
|
|
110
131
|
var result = { errors: [], warnings: [] };
|
|
111
132
|
|
|
@@ -115,20 +136,23 @@
|
|
|
115
136
|
|
|
116
137
|
var lines = logContent.split('\n');
|
|
117
138
|
|
|
118
|
-
//
|
|
139
|
+
// Retain the bounded error list; consumers limit displayed snippets separately.
|
|
119
140
|
for (var i = 0; i < lines.length && result.errors.length < 50; i++) {
|
|
120
|
-
if (lines[i]
|
|
141
|
+
if (isLogErrorLine(lines[i])) {
|
|
121
142
|
var errorLines = [lines[i]];
|
|
122
143
|
for (var j = 1; j <= 3 && (i + j) < lines.length; j++) {
|
|
144
|
+
if (!lines[i + j].trim() || isLogErrorLine(lines[i + j])) break;
|
|
123
145
|
errorLines.push(lines[i + j]);
|
|
124
146
|
}
|
|
125
147
|
result.errors.push(errorLines.join('\n'));
|
|
126
148
|
}
|
|
127
149
|
}
|
|
128
150
|
|
|
129
|
-
//
|
|
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.
|
|
130
154
|
for (var k = 0; k < lines.length && result.warnings.length < 50; k++) {
|
|
131
|
-
if (
|
|
155
|
+
if (/^LaTeX(?:3| Font)? Warning: /.test(lines[k]) || /^(?:Package|Class|Module) \b.+\b Warning:/.test(lines[k])) {
|
|
132
156
|
result.warnings.push(lines[k]);
|
|
133
157
|
}
|
|
134
158
|
}
|
|
@@ -137,18 +161,35 @@
|
|
|
137
161
|
}
|
|
138
162
|
|
|
139
163
|
function buildPostWriteCompileSummary({ result = null, logResult = null, error = null } = {}) {
|
|
140
|
-
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;
|
|
141
168
|
const errors = logAvailable && Array.isArray(logResult.errors)
|
|
142
|
-
? logResult.errors.
|
|
169
|
+
? logResult.errors.map(formatCompileDiagnosticForSummary)
|
|
143
170
|
: [];
|
|
144
171
|
const warnings = logAvailable && Array.isArray(logResult.warnings)
|
|
145
172
|
? logResult.warnings.slice(0, 5).map(formatCompileDiagnosticForSummary)
|
|
146
173
|
: [];
|
|
147
|
-
const
|
|
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'));
|
|
189
|
+
}
|
|
148
190
|
return {
|
|
149
|
-
status
|
|
150
|
-
reason
|
|
151
|
-
: !result?.ok ? result?.reason || 'Post-write compile did not succeed.' : result.reason || '',
|
|
191
|
+
status,
|
|
192
|
+
reason,
|
|
152
193
|
errors,
|
|
153
194
|
warnings,
|
|
154
195
|
logAvailable
|
|
@@ -5,6 +5,13 @@
|
|
|
5
5
|
'use strict';
|
|
6
6
|
const EPOCH_KEY = 'composer-attachment-draft:clear-epoch';
|
|
7
7
|
const normalize = value => SessionState.normalizeComposerAttachmentsForPersistence(value);
|
|
8
|
+
function normalizeOmitted(value) {
|
|
9
|
+
const names = (Array.isArray(value?.names) ? value.names : [])
|
|
10
|
+
.filter(name => typeof name === 'string')
|
|
11
|
+
.map(name => name.replace(/\0/g, '').trim().slice(0, 180))
|
|
12
|
+
.filter(Boolean).slice(0, 8);
|
|
13
|
+
return { count: names.length, names };
|
|
14
|
+
}
|
|
8
15
|
const scopeKey = scope => JSON.stringify([scope?.accountScopeId || '', scope?.projectId || '']);
|
|
9
16
|
function checkedScope(scope) {
|
|
10
17
|
const accountScopeId = String(scope?.accountScopeId || '').trim();
|
|
@@ -42,20 +49,28 @@
|
|
|
42
49
|
throw failure('account_scope_unavailable');
|
|
43
50
|
}
|
|
44
51
|
const revision = Number(previous?.revision) || 0;
|
|
45
|
-
result = { epoch, clearedAt, revision, attachments: normalize(previous?.attachments) };
|
|
52
|
+
result = { epoch, clearedAt, revision, attachments: normalize(previous?.attachments), omittedAttachments: normalizeOmitted(previous?.omittedAttachments) };
|
|
46
53
|
if (!value) return;
|
|
47
54
|
const attachments = normalize(value.attachments);
|
|
55
|
+
const omittedAttachments = normalizeOmitted(value.omittedAttachments);
|
|
56
|
+
const hasDraft = attachments.length || omittedAttachments.count;
|
|
48
57
|
if (value.epoch !== epoch) {
|
|
49
58
|
// A clear performed by this page also invalidates its old base.
|
|
50
59
|
// Its empty draft needs no write; an old nonempty draft cannot return.
|
|
51
|
-
if (!
|
|
60
|
+
if (!hasDraft) return;
|
|
52
61
|
throw failure('composer_draft_cleared');
|
|
53
62
|
}
|
|
54
|
-
if (value.revision !== revision)
|
|
55
|
-
|
|
63
|
+
if (value.revision !== revision) {
|
|
64
|
+
// Writing what another tab already stored changes nothing (for example
|
|
65
|
+
// both tabs clearing an empty draft on send); only a real edit conflicts.
|
|
66
|
+
if (JSON.stringify(attachments) === JSON.stringify(result.attachments)
|
|
67
|
+
&& JSON.stringify(omittedAttachments) === JSON.stringify(result.omittedAttachments)) return;
|
|
68
|
+
throw failure('composer_draft_conflict');
|
|
69
|
+
}
|
|
70
|
+
if (hasDraft && value.unbased && clearedAt && value.changedAt <= clearedAt) {
|
|
56
71
|
throw failure('composer_draft_cleared');
|
|
57
72
|
}
|
|
58
|
-
result = { epoch, clearedAt, revision: revision + 1, attachments };
|
|
73
|
+
result = { epoch, clearedAt, revision: revision + 1, attachments, omittedAttachments };
|
|
59
74
|
store.put({ id, type: 'composer-attachment-draft', ...scope, ...result });
|
|
60
75
|
} catch (caught) { error = caught; tx.abort(); }
|
|
61
76
|
};
|
|
@@ -115,7 +130,11 @@
|
|
|
115
130
|
syncScope();
|
|
116
131
|
if (hydrated) return;
|
|
117
132
|
// Loading is allowed to finish after a local add/remove. Local edits win.
|
|
118
|
-
if (!changedVersion && !options.getAttachments().length)
|
|
133
|
+
if (!changedVersion && !options.getAttachments().length) {
|
|
134
|
+
options.restore(normalize(attachments));
|
|
135
|
+
const omitted = normalizeOmitted(base?.omittedAttachments);
|
|
136
|
+
if (omitted.count) options.onRestoreOmittedAttachments?.(omitted);
|
|
137
|
+
}
|
|
119
138
|
hydrated = true; publish();
|
|
120
139
|
}
|
|
121
140
|
function changed() {
|
|
@@ -133,10 +152,11 @@
|
|
|
133
152
|
if (captured !== generation || scopeKey(options.getScope()) !== key || changedVersion === savedVersion) return;
|
|
134
153
|
const version = changedVersion;
|
|
135
154
|
const attachments = normalize(options.getAttachments());
|
|
155
|
+
const omittedAttachments = normalizeOmitted({ names: options.getOmittedAttachmentNames?.() });
|
|
136
156
|
const unbased = dirtyUnbased, changedAt = dirtySince;
|
|
137
157
|
if (!base) await load(scope);
|
|
138
158
|
if (captured !== generation || scopeKey(options.getScope()) !== key) return;
|
|
139
|
-
const written = await options.write(checkedScope(scope), { ...base, attachments, unbased, changedAt });
|
|
159
|
+
const written = await options.write(checkedScope(scope), { ...base, attachments, omittedAttachments, unbased, changedAt });
|
|
140
160
|
if (captured !== generation) return;
|
|
141
161
|
base = written; savedVersion = version;
|
|
142
162
|
if (changedVersion === version) { dirtySince = 0; dirtyUnbased = false; }
|
|
@@ -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
|
+
});
|
|
@@ -106,6 +106,11 @@
|
|
|
106
106
|
},
|
|
107
107
|
|
|
108
108
|
// 9.2 Preflight
|
|
109
|
+
sensitive_content_blocked: {
|
|
110
|
+
stage: 'preflight', severity: 'blocked', defaultRetryable: false,
|
|
111
|
+
fallbackUserMessage: 'Sensitive content was found before sending, so Codex did not receive this request.',
|
|
112
|
+
fallbackNextAction: 'Remove the sensitive content, narrow @context, or enable explicit sensitive confirmation in Project Settings.'
|
|
113
|
+
},
|
|
109
114
|
governance_blocked: {
|
|
110
115
|
stage: 'preflight', severity: 'blocked', defaultRetryable: false,
|
|
111
116
|
fallbackUserMessage: 'Project file rules blocked this write. Codex did not write the affected path.',
|
|
@@ -131,6 +136,11 @@
|
|
|
131
136
|
fallbackUserMessage: 'The file changed while Codex was working.',
|
|
132
137
|
fallbackNextAction: 'Review the current file, then rerun the task.'
|
|
133
138
|
},
|
|
139
|
+
document_identity_mismatch: {
|
|
140
|
+
stage: 'preflight', severity: 'blocked', defaultRetryable: true,
|
|
141
|
+
fallbackUserMessage: 'Codex could not confirm that the editor content belongs to the target file, so it was not used.',
|
|
142
|
+
fallbackNextAction: 'Wait for the target file to open and settle, then retry. Reload Overleaf if this continues.'
|
|
143
|
+
},
|
|
134
144
|
patch_anchor_not_found: {
|
|
135
145
|
stage: 'preflight', severity: 'blocked', defaultRetryable: true,
|
|
136
146
|
fallbackUserMessage: 'The edit anchor no longer matches current Overleaf content.',
|
|
@@ -232,6 +242,11 @@
|
|
|
232
242
|
},
|
|
233
243
|
|
|
234
244
|
// 9.4 Reviewing
|
|
245
|
+
reviewing_unavailable: {
|
|
246
|
+
stage: 'reviewing', severity: 'blocked', defaultRetryable: false,
|
|
247
|
+
fallbackUserMessage: "Track Changes is unavailable for this project. It depends on the project owner's Overleaf plan.",
|
|
248
|
+
fallbackNextAction: 'Turn off Track in the composer to write without tracked changes. Codex still records each change so you can undo it.'
|
|
249
|
+
},
|
|
235
250
|
reviewing_state_unknown: {
|
|
236
251
|
stage: 'reviewing', severity: 'blocked', defaultRetryable: true,
|
|
237
252
|
fallbackUserMessage: 'Codex could not determine whether Reviewing/Track Changes is enabled.',
|
|
@@ -348,6 +363,11 @@
|
|
|
348
363
|
fallbackUserMessage: 'Native host request failed.',
|
|
349
364
|
fallbackNextAction: 'Open diagnostics and retry.'
|
|
350
365
|
},
|
|
366
|
+
native_request_timeout: {
|
|
367
|
+
stage: 'native', severity: 'error', defaultRetryable: true,
|
|
368
|
+
fallbackUserMessage: 'The local service did not respond.',
|
|
369
|
+
fallbackNextAction: 'Retry, or open Diagnostics to check the local connection.'
|
|
370
|
+
},
|
|
351
371
|
native_protocol_incompatible: {
|
|
352
372
|
stage: 'native', severity: 'blocked', defaultRetryable: true,
|
|
353
373
|
fallbackUserMessage: 'Native host version or capabilities do not match the extension.',
|
|
@@ -363,6 +383,11 @@
|
|
|
363
383
|
fallbackUserMessage: 'Local Codex skills could not be loaded. This run did not start.',
|
|
364
384
|
fallbackNextAction: 'Open Skills to reload the local catalog, then submit again.'
|
|
365
385
|
},
|
|
386
|
+
codex_context_limit: {
|
|
387
|
+
stage: 'codex', severity: 'error', defaultRetryable: true,
|
|
388
|
+
fallbackUserMessage: 'This run stopped because the model context limit was exceeded.',
|
|
389
|
+
fallbackNextAction: 'Retry the task. If the context limit is reached again, use /compact or start a new conversation with less context.'
|
|
390
|
+
},
|
|
366
391
|
codex_no_usable_result: {
|
|
367
392
|
stage: 'codex', severity: 'error', defaultRetryable: true,
|
|
368
393
|
fallbackUserMessage: 'Local Codex returned no usable final report or operations.',
|
|
@@ -413,6 +438,11 @@
|
|
|
413
438
|
fallbackUserMessage: 'The provider rate limit was reached.',
|
|
414
439
|
fallbackNextAction: 'Wait for the rate-limit window to recover and retry.'
|
|
415
440
|
},
|
|
441
|
+
provider_quota_exceeded: {
|
|
442
|
+
stage: 'codex', severity: 'error', defaultRetryable: true,
|
|
443
|
+
fallbackUserMessage: 'The model provider quota or billing limit was reached.',
|
|
444
|
+
fallbackNextAction: 'Check the provider account and plan, or switch model or provider before retrying.'
|
|
445
|
+
},
|
|
416
446
|
provider_connection_timeout: {
|
|
417
447
|
stage: 'codex', severity: 'error', defaultRetryable: true,
|
|
418
448
|
fallbackUserMessage: 'The provider connection timed out.',
|
|
@@ -616,6 +646,7 @@
|
|
|
616
646
|
nextAction: catalog.fallbackNextAction,
|
|
617
647
|
evidence: {}
|
|
618
648
|
};
|
|
649
|
+
if (canonicalCode === 'reviewing_unavailable') failure.changedDocument = false;
|
|
619
650
|
if (legacyReason) {
|
|
620
651
|
failure.technicalMessage = legacyReason;
|
|
621
652
|
}
|
|
@@ -791,8 +822,20 @@
|
|
|
791
822
|
return !path.startsWith('/') && !/^[A-Za-z]:/.test(path) && !path.split('/').includes('..') ? path : '';
|
|
792
823
|
}
|
|
793
824
|
|
|
825
|
+
function isContextLimitError(message, code) {
|
|
826
|
+
const text = String(message || '');
|
|
827
|
+
return ['codex_context_limit', 'context_length_exceeded', 'context_window_exceeded'].includes(code)
|
|
828
|
+
|| /\b(?:codex_context_limit|context_length_exceeded|context_window_exceeded)\b/i.test(text)
|
|
829
|
+
|| /Codex ran out of room in the model['’]s context window/i.test(text)
|
|
830
|
+
|| /maximum context length is \d+ tokens[\s\S]*?(?:you requested|resulted in) \d+ tokens/i.test(text);
|
|
831
|
+
}
|
|
832
|
+
|
|
794
833
|
function recoverDisplayFailure(failure, allowFilePrefix = true) {
|
|
795
834
|
if (!failure || typeof failure !== 'object') return failure;
|
|
835
|
+
if (failure.code === 'codex_no_usable_result' && failure.stage === 'codex'
|
|
836
|
+
&& isContextLimitError(failure.technicalMessage)) return { ...failure, code: 'codex_context_limit',
|
|
837
|
+
userMessage: FAILURE_CODE_CATALOG.codex_context_limit.fallbackUserMessage,
|
|
838
|
+
nextAction: FAILURE_CODE_CATALOG.codex_context_limit.fallbackNextAction };
|
|
796
839
|
const originalCode = failure.evidence?.originalCode;
|
|
797
840
|
const displayCodeAllowed = code => REVIEW_DISPLAY_CODES.has(code)
|
|
798
841
|
&& (code !== 'native_untracked_undo_pending_changes'
|
|
@@ -829,7 +872,8 @@
|
|
|
829
872
|
function formatFailureReasonForDisplay(failure, locale, i18nLookup) {
|
|
830
873
|
const display = recoverDisplayFailure(failure);
|
|
831
874
|
const lookup = typeof i18nLookup === 'function' ? i18nLookup : () => undefined;
|
|
832
|
-
const
|
|
875
|
+
const messageCode = getFailureMessageCode(display);
|
|
876
|
+
const key = messageCode ? 'failureReason_' + messageCode + '_user' : '';
|
|
833
877
|
const template = key ? lookup(key) : '';
|
|
834
878
|
const file = safeDisplayProjectPath(display?.file);
|
|
835
879
|
const localized = localizeFailureReason(display ? { ...display, file } : display, locale, lookup);
|
|
@@ -838,6 +882,15 @@
|
|
|
838
882
|
return { ...localized, code: display?.code, message, file, localized: Boolean(template && template !== key) };
|
|
839
883
|
}
|
|
840
884
|
|
|
885
|
+
function getFailureMessageCode(failure) {
|
|
886
|
+
// Reload recovery retains its historical retry code. Only its presentation
|
|
887
|
+
// identifies the page interruption instead of a failed bridge request.
|
|
888
|
+
if (failure?.code === 'native_request_failed' && failure.source === 'panel_reload') {
|
|
889
|
+
return 'panel_reload_interrupted';
|
|
890
|
+
}
|
|
891
|
+
return typeof failure?.code === 'string' ? failure.code : '';
|
|
892
|
+
}
|
|
893
|
+
|
|
841
894
|
function localizeFailureMessage(message, locale, i18nLookup) {
|
|
842
895
|
return formatFailureReasonForDisplay({ code: 'unknown_legacy_failure', userMessage: message }, locale, i18nLookup).message;
|
|
843
896
|
}
|
|
@@ -949,7 +1002,7 @@
|
|
|
949
1002
|
const value = typeof i18nLookup === 'function' ? i18nLookup(key) : undefined;
|
|
950
1003
|
return value && value !== key ? value : undefined;
|
|
951
1004
|
};
|
|
952
|
-
const code = failure
|
|
1005
|
+
const code = getFailureMessageCode(failure);
|
|
953
1006
|
const userTemplate = code ? lookup('failureReason_' + code + '_user') : undefined;
|
|
954
1007
|
const nextTemplate = (code ? lookup('failureReason_' + code + '_next') : undefined)
|
|
955
1008
|
|| (REVIEW_DISPLAY_CODES.has(code) ? lookup('failureReason_review_detail_next') : undefined);
|
|
@@ -989,6 +1042,7 @@
|
|
|
989
1042
|
selectPrimaryFailure,
|
|
990
1043
|
localizeFailureReason,
|
|
991
1044
|
formatFailureReasonForDisplay,
|
|
992
|
-
localizeFailureMessage
|
|
1045
|
+
localizeFailureMessage,
|
|
1046
|
+
isContextLimitError
|
|
993
1047
|
};
|
|
994
1048
|
});
|