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
|
@@ -227,20 +227,20 @@
|
|
|
227
227
|
|
|
228
228
|
function collectMarkdownTargetReferences(text, mode, refs) {
|
|
229
229
|
const normalizedText = stripReferenceQueryAndHash(String(text || '').trim());
|
|
230
|
-
const match = normalizedText.match(new RegExp(`^(.+?\\.${TEXT_EXTENSION_PATTERN}):(\\d+)(
|
|
230
|
+
const match = normalizedText.match(new RegExp(`^(.+?\\.${TEXT_EXTENSION_PATTERN}):(\\d+)(?:[-–](\\d+)|:(\\d+))?$`, 'i'));
|
|
231
231
|
if (!match) {
|
|
232
232
|
return;
|
|
233
233
|
}
|
|
234
|
-
const
|
|
235
|
-
const column = match[
|
|
236
|
-
if (!
|
|
234
|
+
const span = parseLineSpan(match[2], match[3]);
|
|
235
|
+
const column = match[4] ? parsePositiveInteger(match[4]) : null;
|
|
236
|
+
if (!span || (match[4] && !column)) {
|
|
237
237
|
return;
|
|
238
238
|
}
|
|
239
239
|
refs.push({
|
|
240
240
|
rawText: text,
|
|
241
241
|
displayText: text,
|
|
242
242
|
rawPath: match[1],
|
|
243
|
-
|
|
243
|
+
...span,
|
|
244
244
|
column,
|
|
245
245
|
source: mode,
|
|
246
246
|
index: 0
|
|
@@ -248,12 +248,14 @@
|
|
|
248
248
|
}
|
|
249
249
|
|
|
250
250
|
function collectColonReferences(text, mode, refs) {
|
|
251
|
-
|
|
251
|
+
// Range/column suffixes are mutually exclusive; malformed spans must not
|
|
252
|
+
// backtrack into a shorter reference, including decimal/exponent endpoints.
|
|
253
|
+
const pattern = new RegExp(`${REFERENCE_PREFIX_PATTERN}${PATH_PATTERN}:(\\d+)(?:[-–](\\d+)(?!\\.\\d|e[+-]?\\d)|:(\\d+))?(?![-–:\\d]|(?:\\.\\d+)?(?:e[+-]?\\d+)?[-–])`, 'gi');
|
|
252
254
|
let match;
|
|
253
255
|
while ((match = pattern.exec(text)) !== null) {
|
|
254
|
-
const
|
|
255
|
-
const column = match[
|
|
256
|
-
if (!
|
|
256
|
+
const span = parseLineSpan(match[3], match[4]);
|
|
257
|
+
const column = match[5] ? parsePositiveInteger(match[5]) : null;
|
|
258
|
+
if (!span || (match[5] && !column)) {
|
|
257
259
|
continue;
|
|
258
260
|
}
|
|
259
261
|
const prefixLength = match[1].length;
|
|
@@ -262,7 +264,7 @@
|
|
|
262
264
|
rawText,
|
|
263
265
|
displayText: rawText,
|
|
264
266
|
rawPath: match[2],
|
|
265
|
-
|
|
267
|
+
...span,
|
|
266
268
|
column,
|
|
267
269
|
source: mode,
|
|
268
270
|
index: match.index + prefixLength
|
|
@@ -271,20 +273,20 @@
|
|
|
271
273
|
}
|
|
272
274
|
|
|
273
275
|
function collectLineWordReferences(text, mode, refs) {
|
|
274
|
-
const pattern = new RegExp(`${REFERENCE_PREFIX_PATTERN}${PATH_PATTERN}\\s+line
|
|
276
|
+
const pattern = new RegExp(`${REFERENCE_PREFIX_PATTERN}${PATH_PATTERN}\\s+line(?:s(?=\\s+\\d+[-–]))?\\s+(\\d+)(?:[-–](\\d+)(?!\\.\\d|e[+-]?\\d))?(?![-–:\\d]|(?:\\.\\d+)?(?:e[+-]?\\d+)?[-–])`, 'gi');
|
|
275
277
|
let match;
|
|
276
278
|
while ((match = pattern.exec(text)) !== null) {
|
|
277
|
-
const
|
|
278
|
-
if (!
|
|
279
|
+
const span = parseLineSpan(match[3], match[4]);
|
|
280
|
+
if (!span) {
|
|
279
281
|
continue;
|
|
280
282
|
}
|
|
281
283
|
const prefixLength = match[1].length;
|
|
282
284
|
const rawText = match[0].slice(prefixLength);
|
|
283
285
|
refs.push({
|
|
284
286
|
rawText,
|
|
285
|
-
displayText: `${match[2]}:${line}`,
|
|
287
|
+
displayText: `${match[2]}:${span.line}${span.endLine ? `–${span.endLine}` : ''}`,
|
|
286
288
|
rawPath: match[2],
|
|
287
|
-
|
|
289
|
+
...span,
|
|
288
290
|
column: null,
|
|
289
291
|
source: mode,
|
|
290
292
|
index: match.index + prefixLength
|
|
@@ -293,20 +295,20 @@
|
|
|
293
295
|
}
|
|
294
296
|
|
|
295
297
|
function collectChineseLineReferences(text, mode, refs) {
|
|
296
|
-
const pattern = new RegExp(`${REFERENCE_PREFIX_PATTERN}${PATH_PATTERN}\\s+第\\s*(\\d+)\\s*行`, 'gi');
|
|
298
|
+
const pattern = new RegExp(`${REFERENCE_PREFIX_PATTERN}${PATH_PATTERN}\\s+第\\s*(\\d+)(?:\\s*[-–至]\\s*(\\d+))?\\s*行`, 'gi');
|
|
297
299
|
let match;
|
|
298
300
|
while ((match = pattern.exec(text)) !== null) {
|
|
299
|
-
const
|
|
300
|
-
if (!
|
|
301
|
+
const span = parseLineSpan(match[3], match[4]);
|
|
302
|
+
if (!span) {
|
|
301
303
|
continue;
|
|
302
304
|
}
|
|
303
305
|
const prefixLength = match[1].length;
|
|
304
306
|
const rawText = match[0].slice(prefixLength);
|
|
305
307
|
refs.push({
|
|
306
308
|
rawText,
|
|
307
|
-
displayText: `${match[2]}:${line}`,
|
|
309
|
+
displayText: `${match[2]}:${span.line}${span.endLine ? `–${span.endLine}` : ''}`,
|
|
308
310
|
rawPath: match[2],
|
|
309
|
-
|
|
311
|
+
...span,
|
|
310
312
|
column: null,
|
|
311
313
|
source: mode,
|
|
312
314
|
index: match.index + prefixLength
|
|
@@ -361,13 +363,13 @@
|
|
|
361
363
|
projectFiles
|
|
362
364
|
});
|
|
363
365
|
if (resolved) {
|
|
364
|
-
return `${resolved.path}:${ref.line}${ref.column ? `:${ref.column}` : ''}`;
|
|
366
|
+
return `${resolved.path}:${ref.line}${ref.endLine ? `-${ref.endLine}` : ref.column ? `:${ref.column}` : ''}`;
|
|
365
367
|
}
|
|
366
|
-
return formatLocalPathPlaceholder(ref.line, placeholderBrackets);
|
|
368
|
+
return formatLocalPathPlaceholder(ref.line, placeholderBrackets, ref.endLine);
|
|
367
369
|
}
|
|
368
370
|
|
|
369
|
-
function formatLocalPathPlaceholder(line, includeBrackets) {
|
|
370
|
-
const value = line ? `local path:${line}` : 'local path';
|
|
371
|
+
function formatLocalPathPlaceholder(line, includeBrackets, endLine) {
|
|
372
|
+
const value = line ? `local path:${line}${endLine ? `-${endLine}` : ''}` : 'local path';
|
|
371
373
|
return includeBrackets ? `[${value}]` : value;
|
|
372
374
|
}
|
|
373
375
|
|
|
@@ -383,7 +385,7 @@
|
|
|
383
385
|
}
|
|
384
386
|
|
|
385
387
|
function stripReferenceQueryAndHash(value) {
|
|
386
|
-
return String(value || '').replace(/([:.]\d+(
|
|
388
|
+
return String(value || '').replace(/([:.]\d+(?:[-–]\d+|:\d+)?)(?:[?#].*)$/, '$1');
|
|
387
389
|
}
|
|
388
390
|
|
|
389
391
|
function collectTextProjectFiles(projectFiles) {
|
|
@@ -423,18 +425,13 @@
|
|
|
423
425
|
function shouldSkipReference(ref) {
|
|
424
426
|
const rawPath = String(ref.rawPath || '');
|
|
425
427
|
return /^https?:\/\//i.test(rawPath)
|
|
426
|
-
|| isEmailLikePath(rawPath)
|
|
427
|
-
|| hasUnsafeLineRange(ref.rawText);
|
|
428
|
+
|| isEmailLikePath(rawPath);
|
|
428
429
|
}
|
|
429
430
|
|
|
430
431
|
function isEmailLikePath(rawPath) {
|
|
431
432
|
return /^[^@\s/\\]+@[^@\s/\\]+$/.test(rawPath);
|
|
432
433
|
}
|
|
433
434
|
|
|
434
|
-
function hasUnsafeLineRange(rawText) {
|
|
435
|
-
return /:\d+-\d+(?=$|[^\d])/.test(rawText);
|
|
436
|
-
}
|
|
437
|
-
|
|
438
435
|
function hasUnsafePathSegments(path) {
|
|
439
436
|
return normalizeReferencePath(path)
|
|
440
437
|
.split('/')
|
|
@@ -455,6 +452,15 @@
|
|
|
455
452
|
return parsed;
|
|
456
453
|
}
|
|
457
454
|
|
|
455
|
+
function parseLineSpan(rawLine, rawEndLine) {
|
|
456
|
+
const line = parsePositiveInteger(rawLine);
|
|
457
|
+
const endLine = rawEndLine === undefined ? null : parsePositiveInteger(rawEndLine);
|
|
458
|
+
if (!line || (rawEndLine !== undefined && (!endLine || endLine < line))) {
|
|
459
|
+
return null;
|
|
460
|
+
}
|
|
461
|
+
return { line, ...(endLine > line ? { endLine } : {}) };
|
|
462
|
+
}
|
|
463
|
+
|
|
458
464
|
function getBasename(path) {
|
|
459
465
|
const parts = String(path || '').split('/');
|
|
460
466
|
return parts[parts.length - 1] || '';
|
|
@@ -477,6 +483,7 @@
|
|
|
477
483
|
displayText: ref.displayText,
|
|
478
484
|
rawPath: ref.rawPath,
|
|
479
485
|
line: ref.line,
|
|
486
|
+
...(ref.endLine ? { endLine: ref.endLine } : {}),
|
|
480
487
|
column: ref.column,
|
|
481
488
|
source: ref.source
|
|
482
489
|
};
|
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
waiting: 'waiting_for_idle',
|
|
6
6
|
waiting_for_safe_point: 'waiting_for_idle',
|
|
7
7
|
awaiting_health_check: 'awaiting_health',
|
|
8
|
-
health_checking: 'awaiting_health'
|
|
8
|
+
health_checking: 'awaiting_health',
|
|
9
|
+
bootstrap_pending: 'awaiting_health'
|
|
9
10
|
});
|
|
10
|
-
|
|
11
11
|
const PHASES = Object.freeze([
|
|
12
12
|
'idle',
|
|
13
13
|
'up_to_date',
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
})(typeof window !== 'undefined' ? window : (typeof globalThis !== 'undefined' ? globalThis : this), function () {
|
|
6
6
|
'use strict';
|
|
7
7
|
|
|
8
|
-
var REVISION = '2026-
|
|
8
|
+
var REVISION = '2026-10-19-mode-menu-switch-v52';
|
|
9
9
|
var METHODS = freezeCatalog({
|
|
10
10
|
initializeCapability: entry('bridge.initialize', 'control', 'none', 'default', false, 'none'),
|
|
11
11
|
probe: entry('bridge.probe', 'read', 'none', 'default', false, 'safe'),
|
|
@@ -20,12 +20,13 @@
|
|
|
20
20
|
ensureReviewing: entry('reviewing.enable', 'page_state', 'optional', 'default', true, 'idempotent'),
|
|
21
21
|
ensureEditing: entry('reviewing.disable', 'page_state', 'optional', 'default', true, 'idempotent'),
|
|
22
22
|
applyOperations: entry('document.write', 'document', 'required', 'writeback', true, 'no_retry'),
|
|
23
|
-
|
|
23
|
+
// Receipts settle already-dispatched writes after cancellation; their RPC deadlines still apply.
|
|
24
|
+
getWritebackReceipt: entry('writeback.receipt', 'read', 'required', 'default', false, 'safe'),
|
|
24
25
|
getSelectionContext: entry('editor.selection', 'read', 'required', 'default', true, 'safe'),
|
|
25
|
-
binaryUploadBegin: entry('asset.stage', 'cache', 'required', 'default',
|
|
26
|
+
binaryUploadBegin: entry('asset.stage', 'cache', 'required', 'default', true, 'no_retry'),
|
|
26
27
|
binaryUploadAppend: entry('asset.stage', 'cache', 'none', 'writeback', true, 'no_retry'),
|
|
27
28
|
binaryUploadCommit: entry('asset.write', 'document', 'required', 'writeback', true, 'no_retry'),
|
|
28
|
-
binaryUploadStatus: entry('asset.receipt', 'read', 'required', 'default',
|
|
29
|
+
binaryUploadStatus: entry('asset.receipt', 'read', 'required', 'default', false, 'safe'),
|
|
29
30
|
binaryUploadAbort: entry('asset.stage', 'cache', 'none', 'default', false, 'idempotent'),
|
|
30
31
|
jumpToPosition: entry('editor.navigate', 'navigation', 'required', 'default', false, 'idempotent'),
|
|
31
32
|
reconcileTrackedChangeCapture: entry('tracked_changes.read', 'read', 'required', 'default', false, 'safe'),
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
(function initPanelIcons(root, factory) {
|
|
2
|
+
if (typeof module === 'object' && module.exports) module.exports = factory();
|
|
3
|
+
else root.CodexOverleafModuleRegistry.define('PanelIcons', [], factory);
|
|
4
|
+
})(typeof globalThis !== 'undefined' ? globalThis : window, function panelIconsFactory() {
|
|
5
|
+
'use strict';
|
|
6
|
+
|
|
7
|
+
// 16-unit line icons drawn in the panel header's codexIcon style. Size and
|
|
8
|
+
// color come from the caller; `.codex-glyph` supplies stroke and caps.
|
|
9
|
+
const PATHS = Object.freeze({
|
|
10
|
+
plus: '<path d="M8 3.2v9.6M3.2 8h9.6"/>',
|
|
11
|
+
send: '<path d="M8 12.8V3.6"/><path d="m4.2 7.4 3.8-3.8 3.8 3.8"/>',
|
|
12
|
+
stop: '<rect x="4.4" y="4.4" width="7.2" height="7.2" rx="1.6" fill="currentColor" stroke="none"/>',
|
|
13
|
+
attach: '<path d="m12.6 7.4-4.8 4.8a3 3 0 0 1-4.2-4.2l5-5a2 2 0 0 1 2.8 2.8l-4.9 4.9a1 1 0 0 1-1.4-1.4l4.4-4.4"/>',
|
|
14
|
+
refresh: '<path d="M4.2 5.3A5 5 0 1 1 3.6 10"/><path d="M4.2 2.2v3.1H1.1"/>',
|
|
15
|
+
sparkle: '<path d="M7.4 2.6c.5 2.6 1.8 3.9 4.4 4.4-2.6.5-3.9 1.8-4.4 4.4C6.9 8.8 5.6 7.5 3 7c2.6-.5 3.9-1.8 4.4-4.4Z"/><path d="M12.3 10.5c.2 1 .7 1.5 1.7 1.7-1 .2-1.5.7-1.7 1.7-.2-1-.7-1.5-1.7-1.7 1-.2 1.5-.7 1.7-1.7Z"/>',
|
|
16
|
+
close: '<path d="m4.6 4.6 6.8 6.8M11.4 4.6l-6.8 6.8"/>',
|
|
17
|
+
pencil: '<path d="M10.4 3.1 12.9 5.6 6 12.5l-3.1.6.6-3.1Z"/><path d="m9.2 4.3 2.5 2.5"/>',
|
|
18
|
+
chevronDown: '<path d="m4.4 6.4 3.6 3.6 3.6-3.6"/>',
|
|
19
|
+
chevronLeft: '<path d="m9.8 3.6-4.4 4.4 4.4 4.4"/>',
|
|
20
|
+
lock: '<rect x="3.6" y="7.1" width="8.8" height="6.3" rx="1.5"/><path d="M5.6 7.1V5.3a2.4 2.4 0 0 1 4.8 0v1.8"/>',
|
|
21
|
+
compact: '<path d="M4.6 2.4 8 5.6l3.4-3.2"/><path d="M4.6 13.6 8 10.4l3.4 3.2"/><path d="M2.6 8h10.8"/>',
|
|
22
|
+
install: '<path d="M8 2.4v7.4"/><path d="m4.8 6.8 3.2 3.2 3.2-3.2"/><path d="M2.6 11v1.5c0 .6.4 1 1 1h8.8c.6 0 1-.4 1-1V11"/>',
|
|
23
|
+
book: '<path d="M8 4.3c-1.3-1.1-3-1.5-5.2-1.4v9.3c2.2-.1 3.9.3 5.2 1.4 1.3-1.1 3-1.5 5.2-1.4V2.9C11 2.8 9.3 3.2 8 4.3Z"/><path d="M8 4.3v9.3"/>',
|
|
24
|
+
terminal: '<rect x="2.1" y="2.9" width="11.8" height="10.2" rx="2"/><path d="m5 6.4 2 1.7-2 1.7"/><path d="M8.7 10h2.5"/>',
|
|
25
|
+
file: '<path d="M9.2 2.4H4.6a1 1 0 0 0-1 1v9.2a1 1 0 0 0 1 1h6.8a1 1 0 0 0 1-1V5.6Z"/><path d="M9.2 2.4v3.2h3.2"/>',
|
|
26
|
+
folder: '<path d="M2.2 4.4a1 1 0 0 1 1-1h3l1.4 1.6h5.2a1 1 0 0 1 1 1v6.4a1 1 0 0 1-1 1H3.2a1 1 0 0 1-1-1Z"/>',
|
|
27
|
+
alert: '<circle cx="8" cy="8" r="5.9"/><path d="M8 5v3.5"/><path d="M8 11h.01"/>',
|
|
28
|
+
spin: '<path d="M13.6 8A5.6 5.6 0 1 1 8 2.4"/>'
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
function svg(name, className = '') {
|
|
32
|
+
const paths = PATHS[name];
|
|
33
|
+
if (!paths) return '';
|
|
34
|
+
return '<svg class="codex-glyph' + (className ? ' ' + className : '') + '" viewBox="0 0 16 16" aria-hidden="true" focusable="false">'
|
|
35
|
+
+ paths + '</svg>';
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return { svg };
|
|
39
|
+
});
|
|
@@ -83,55 +83,31 @@
|
|
|
83
83
|
.replace(/^\/+/, '');
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
+
// Project metadata, ZIP names and operation paths are already literal names.
|
|
87
|
+
// Decoding here would alias e.g. "a%20b.tex" with "a b.tex".
|
|
86
88
|
function normalizeSafeProjectPath(value) {
|
|
87
|
-
if (typeof value !== 'string')
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
}
|
|
93
|
-
const trimmed = value.trim();
|
|
94
|
-
if (!trimmed || trimmed.length > MAX_SAFE_PROJECT_PATH_LENGTH) {
|
|
95
|
-
return '';
|
|
96
|
-
}
|
|
97
|
-
if (trimmed.startsWith('/') || trimmed.includes('\\') || hasWindowsDrivePrefix(trimmed)) {
|
|
98
|
-
return '';
|
|
99
|
-
}
|
|
89
|
+
if (typeof value !== 'string' || !value || value.length > MAX_SAFE_PROJECT_PATH_LENGTH || value.trim() !== value
|
|
90
|
+
|| hasControlCharacter(value) || value.startsWith('/') || value.includes('\\')
|
|
91
|
+
|| hasWindowsDrivePrefix(value)) return '';
|
|
92
|
+
return value.split('/').some(segment => !segment || segment === '.' || segment === '..') ? '' : value;
|
|
93
|
+
}
|
|
100
94
|
|
|
101
|
-
|
|
95
|
+
// Use only at an explicitly URL-encoded input boundary. Return one decoding,
|
|
96
|
+
// while conservatively checking nested encodings for traversal/separators.
|
|
97
|
+
function normalizeUrlEncodedProjectPath(value) {
|
|
98
|
+
if (typeof value !== 'string' || hasEncodedSeparator(value)) return '';
|
|
99
|
+
let decoded;
|
|
100
|
+
try { decoded = decodeURIComponent(value); } catch (_error) { return ''; }
|
|
101
|
+
const result = normalizeSafeProjectPath(decoded);
|
|
102
|
+
if (!result) return '';
|
|
102
103
|
for (let index = 0; index < 3; index += 1) {
|
|
103
|
-
if (hasEncodedSeparator(decoded))
|
|
104
|
-
return '';
|
|
105
|
-
}
|
|
104
|
+
if (hasEncodedSeparator(decoded) || !normalizeSafeProjectPath(decoded)) return '';
|
|
106
105
|
let next;
|
|
107
|
-
try {
|
|
108
|
-
|
|
109
|
-
} catch (_error) {
|
|
110
|
-
return '';
|
|
111
|
-
}
|
|
112
|
-
if (next === decoded) {
|
|
113
|
-
break;
|
|
114
|
-
}
|
|
106
|
+
try { next = decodeURIComponent(decoded); } catch (_error) { break; }
|
|
107
|
+
if (next === decoded) break;
|
|
115
108
|
decoded = next;
|
|
116
109
|
}
|
|
117
|
-
|
|
118
|
-
if (hasControlCharacter(decoded)
|
|
119
|
-
|| decoded.startsWith('/')
|
|
120
|
-
|| decoded.includes('\\')
|
|
121
|
-
|| hasWindowsDrivePrefix(decoded)
|
|
122
|
-
|| decoded.length > MAX_SAFE_PROJECT_PATH_LENGTH) {
|
|
123
|
-
return '';
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
const normalized = decoded.replace(/\/+/g, '/').trim();
|
|
127
|
-
if (!normalized || normalized.length > MAX_SAFE_PROJECT_PATH_LENGTH) {
|
|
128
|
-
return '';
|
|
129
|
-
}
|
|
130
|
-
const segments = normalized.split('/');
|
|
131
|
-
if (segments.some(segment => !segment || segment === '.' || segment === '..')) {
|
|
132
|
-
return '';
|
|
133
|
-
}
|
|
134
|
-
return normalized;
|
|
110
|
+
return hasEncodedSeparator(decoded) || !normalizeSafeProjectPath(decoded) ? '' : result;
|
|
135
111
|
}
|
|
136
112
|
|
|
137
113
|
function assertSafeProjectPath(value, label = 'path') {
|
|
@@ -175,6 +151,7 @@
|
|
|
175
151
|
isUsableProjectFileContent,
|
|
176
152
|
isTextProjectPath,
|
|
177
153
|
normalizePath,
|
|
178
|
-
normalizeSafeProjectPath
|
|
154
|
+
normalizeSafeProjectPath,
|
|
155
|
+
normalizeUrlEncodedProjectPath
|
|
179
156
|
};
|
|
180
157
|
});
|
|
@@ -167,7 +167,8 @@
|
|
|
167
167
|
anthropicThinkingMode: normalizeAnthropicThinkingMode(value.anthropicThinkingMode),
|
|
168
168
|
anthropicPromptCaching: value.anthropicPromptCaching === true,
|
|
169
169
|
impersonateClaudeCode: value.impersonateClaudeCode === true,
|
|
170
|
-
maxOutputTokens: normalizeMaxOutputTokens(value.maxOutputTokens),
|
|
170
|
+
maxOutputTokens: normalizeMaxOutputTokens(value.maxOutputTokens, value.outputBudgetVersion !== 1),
|
|
171
|
+
outputBudgetVersion: 1,
|
|
171
172
|
hasSecret: value.hasSecret === true,
|
|
172
173
|
secretUpdatedAt: normalizeInteger(value.secretUpdatedAt),
|
|
173
174
|
endpointDisclosureHost: normalizeText(value.endpointDisclosureHost),
|
|
@@ -262,6 +263,7 @@
|
|
|
262
263
|
anthropicPromptCaching: false,
|
|
263
264
|
impersonateClaudeCode: false,
|
|
264
265
|
maxOutputTokens: 65536,
|
|
266
|
+
outputBudgetVersion: 1,
|
|
265
267
|
hasSecret: false,
|
|
266
268
|
secretUpdatedAt: 0,
|
|
267
269
|
endpointDisclosureHost: '',
|
|
@@ -380,9 +382,9 @@
|
|
|
380
382
|
return Number.isFinite(number) && number >= 0 ? Math.floor(number) : 0;
|
|
381
383
|
}
|
|
382
384
|
|
|
383
|
-
function normalizeMaxOutputTokens(value) {
|
|
385
|
+
function normalizeMaxOutputTokens(value, legacy = false) {
|
|
384
386
|
const number = normalizeInteger(value);
|
|
385
|
-
if (!number || number === 8192 || number === 32768) return 65536;
|
|
387
|
+
if (!number || (legacy && (number === 8192 || number === 32768))) return 65536;
|
|
386
388
|
return Math.min(65536, Math.max(256, number));
|
|
387
389
|
}
|
|
388
390
|
|
|
@@ -77,6 +77,135 @@
|
|
|
77
77
|
};
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
const UNAVAILABLE_TTL_MS = 7 * 24 * 60 * 60 * 1000;
|
|
81
|
+
// Keep all upgrade-dialog selectors here: Overleaf DOM acceptance may recalibrate them.
|
|
82
|
+
const UPGRADE_MODAL_SELECTORS = Object.freeze({
|
|
83
|
+
body: '.upgrade-track-changes-modal',
|
|
84
|
+
modal: '.modal[role="dialog"], [role="dialog"][aria-modal="true"]',
|
|
85
|
+
close: '.modal-header button.btn-close, .modal-header button.close'
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
function visible(node) {
|
|
89
|
+
if (!node || !node.isConnected || node.hidden || node.getAttribute?.('aria-hidden') === 'true') return false;
|
|
90
|
+
const style = node.ownerDocument?.defaultView?.getComputedStyle(node);
|
|
91
|
+
return style?.display !== 'none' && style?.visibility !== 'hidden' && node.getClientRects().length > 0;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function findUpgradeModal(doc) {
|
|
95
|
+
for (const body of doc?.querySelectorAll(UPGRADE_MODAL_SELECTORS.body) || []) {
|
|
96
|
+
const modal = body.closest(UPGRADE_MODAL_SELECTORS.modal);
|
|
97
|
+
if (visible(body) && visible(modal)) return modal;
|
|
98
|
+
}
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async function dismissUpgradeModal(modal, { delay, assertCurrent }) {
|
|
103
|
+
const isOpen = () => visible(modal);
|
|
104
|
+
const waitClosed = async () => {
|
|
105
|
+
for (let i = 0; i < 6 && isOpen(); i++) { await delay(80); assertCurrent(); }
|
|
106
|
+
};
|
|
107
|
+
assertCurrent();
|
|
108
|
+
const close = modal.querySelector(UPGRADE_MODAL_SELECTORS.close);
|
|
109
|
+
if (visible(close) && !close.disabled) close.click();
|
|
110
|
+
await waitClosed();
|
|
111
|
+
if (isOpen()) {
|
|
112
|
+
assertCurrent();
|
|
113
|
+
const KeyboardEvent = modal.ownerDocument.defaultView.KeyboardEvent;
|
|
114
|
+
modal.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', code: 'Escape', keyCode: 27, bubbles: true }));
|
|
115
|
+
await waitClosed();
|
|
116
|
+
}
|
|
117
|
+
return !isOpen();
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Page owns state/identity; this controller owns the click/poll/upgrade stop sequence.
|
|
121
|
+
async function toggleMode(enabled, params, deps) {
|
|
122
|
+
const { assertCurrent, delay, getState, isConfirmed, click, findControls, findOption } = deps;
|
|
123
|
+
let attempted = false;
|
|
124
|
+
const inspect = async () => {
|
|
125
|
+
assertCurrent();
|
|
126
|
+
const modal = findUpgradeModal(deps.document);
|
|
127
|
+
if (enabled && modal) {
|
|
128
|
+
const evidence = { upgradeModal: true, dismissed: await dismissUpgradeModal(modal, deps) };
|
|
129
|
+
return { ok: false, code: 'reviewing_unavailable', attempted, evidence,
|
|
130
|
+
failure: deps.buildFailure('reviewing_unavailable', {
|
|
131
|
+
operationType: 'ensureReviewing', changedDocument: false, evidence
|
|
132
|
+
}) };
|
|
133
|
+
}
|
|
134
|
+
const state = getState();
|
|
135
|
+
return { ok: isConfirmed(enabled, state), attempted, reviewing: state.reviewing };
|
|
136
|
+
};
|
|
137
|
+
const poll = async waitMs => {
|
|
138
|
+
let result = await inspect();
|
|
139
|
+
for (let elapsed = 0; !result.ok && !result.code && elapsed < waitMs; elapsed += 120) {
|
|
140
|
+
await delay(120);
|
|
141
|
+
result = await inspect();
|
|
142
|
+
}
|
|
143
|
+
return result;
|
|
144
|
+
};
|
|
145
|
+
const waitMs = Number.isFinite(Number(params.waitMs)) ? Math.max(0, Number(params.waitMs)) : 1600;
|
|
146
|
+
const menuWaitMs = Math.min(waitMs, 1200);
|
|
147
|
+
const selectVisibleOption = async () => {
|
|
148
|
+
const option = findOption(enabled);
|
|
149
|
+
if (!option) return null;
|
|
150
|
+
assertCurrent(); click(option); attempted = true;
|
|
151
|
+
return poll(waitMs);
|
|
152
|
+
};
|
|
153
|
+
let result = await inspect();
|
|
154
|
+
if (result.ok || result.code) return result;
|
|
155
|
+
// An open menu must be consumed before a toggle can close it.
|
|
156
|
+
let selected = await selectVisibleOption();
|
|
157
|
+
if (selected) return selected;
|
|
158
|
+
for (const control of [...new Set(findControls().filter(Boolean))]) {
|
|
159
|
+
result = await inspect();
|
|
160
|
+
if (result.ok || result.code) return result;
|
|
161
|
+
selected = await selectVisibleOption();
|
|
162
|
+
if (selected) return selected;
|
|
163
|
+
if (control.getAttribute?.('aria-expanded') !== 'true') {
|
|
164
|
+
assertCurrent(); click(control); attempted = true;
|
|
165
|
+
}
|
|
166
|
+
// Menu mounting and target-mode confirmation have separate bounded waits.
|
|
167
|
+
for (let elapsed = 0; ; ) {
|
|
168
|
+
result = await inspect();
|
|
169
|
+
if (result.ok || result.code) return result;
|
|
170
|
+
selected = await selectVisibleOption();
|
|
171
|
+
if (selected) return selected;
|
|
172
|
+
if (elapsed >= menuWaitMs) break;
|
|
173
|
+
const step = Math.min(100, menuWaitMs - elapsed);
|
|
174
|
+
await delay(step);
|
|
175
|
+
elapsed += step;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return inspect();
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function knownUnavailable(record, now = Date.now()) {
|
|
182
|
+
return record?.status === 'unavailable' && Number.isFinite(record.observedAt)
|
|
183
|
+
&& now >= record.observedAt && now - record.observedAt < UNAVAILABLE_TTL_MS;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Content preflight invokes this before dispatch. Only explicit choices can lower the policy.
|
|
187
|
+
async function resolveWritePolicy({ requireReviewing, knownUnavailable: unavailable }, deps) {
|
|
188
|
+
deps.assertCurrent();
|
|
189
|
+
let result = unavailable && requireReviewing
|
|
190
|
+
? { ok: false, code: 'reviewing_unavailable', evidence: { upgradeModal: true, remembered: true } }
|
|
191
|
+
: await deps.ensureMode(requireReviewing);
|
|
192
|
+
deps.assertCurrent();
|
|
193
|
+
if (requireReviewing && result.ok) await deps.observe('available', 'reviewing_confirmed');
|
|
194
|
+
if (requireReviewing && result.code === 'reviewing_unavailable') {
|
|
195
|
+
if (!unavailable) await deps.observe('unavailable', 'upgrade_modal');
|
|
196
|
+
deps.assertCurrent();
|
|
197
|
+
const choice = await deps.choose(result);
|
|
198
|
+
deps.assertCurrent();
|
|
199
|
+
if (choice !== 'continue' && choice !== 'disable') return { ...result, ok: false };
|
|
200
|
+
await deps.applyUntracked(choice);
|
|
201
|
+
deps.assertCurrent();
|
|
202
|
+
result = await deps.ensureMode(false);
|
|
203
|
+
deps.assertCurrent();
|
|
204
|
+
return { ...result, requireReviewing: false, trackingDecision: choice };
|
|
205
|
+
}
|
|
206
|
+
return { ...result, requireReviewing };
|
|
207
|
+
}
|
|
208
|
+
|
|
80
209
|
function matchesControlReview(text) {
|
|
81
210
|
return CONTROL_REVIEW_PATTERNS.some(pattern => pattern.test(text));
|
|
82
211
|
}
|
|
@@ -94,6 +223,6 @@
|
|
|
94
223
|
}
|
|
95
224
|
|
|
96
225
|
return {
|
|
97
|
-
detectReviewingFromSignals
|
|
226
|
+
detectReviewingFromSignals, findUpgradeModal, toggleMode, knownUnavailable, resolveWritePolicy, UNAVAILABLE_TTL_MS
|
|
98
227
|
};
|
|
99
228
|
});
|
|
@@ -86,6 +86,27 @@
|
|
|
86
86
|
written: Number.isSafeInteger(fileCount) ? fileCount : undefined, output: version }, sanitize);
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
+
function contextCompactionState(event = {}) {
|
|
90
|
+
if (event.activity?.kind === 'lifecycle' && event.activity.target === 'context_compaction') {
|
|
91
|
+
return event.activity.state === 'running' ? 'running' : 'completed';
|
|
92
|
+
}
|
|
93
|
+
const raw = event.technicalDetail || (event.kind === 'technical' ? event.detail : null) || event;
|
|
94
|
+
const type = text(raw.type);
|
|
95
|
+
const method = text(raw.detail?.method || (type === 'codex.session.event' ? raw.title : ''));
|
|
96
|
+
if (raw.detail?.params?.item?.type === 'contextCompaction') {
|
|
97
|
+
if (method === 'item/started') return 'running';
|
|
98
|
+
if (method === 'item/completed') return 'completed';
|
|
99
|
+
}
|
|
100
|
+
// The legacy completion notification can carry an incorrectly inferred
|
|
101
|
+
// `running` status. The protocol method is authoritative in both versions.
|
|
102
|
+
if (method === 'thread/compacted') return 'completed';
|
|
103
|
+
const label = type + ' ' + method;
|
|
104
|
+
if (!/(compact|compaction|compacted)/i.test(label)
|
|
105
|
+
|| !/(context|thread|turn|conversation|codex)/i.test(label)) return '';
|
|
106
|
+
if (/(completed|compacted|finished)/i.test(label)) return 'completed';
|
|
107
|
+
return raw.status === 'running' || /(started|starting|begin|prepar)/i.test(label) ? 'running' : 'completed';
|
|
108
|
+
}
|
|
109
|
+
|
|
89
110
|
function capture(event = {}, sanitize = text) {
|
|
90
111
|
if (event.activity?.v === 1) return normalize(event.activity, sanitize);
|
|
91
112
|
if (event.kind === 'report' || event.kind === 'guidance' || event.kind === 'stream') return undefined;
|
|
@@ -103,6 +124,10 @@
|
|
|
103
124
|
const scope = JSON.stringify([thread, turn]);
|
|
104
125
|
const id = itemId ? JSON.stringify([thread, turn, itemId]) : '';
|
|
105
126
|
const base = { id, scope, state: event.status };
|
|
127
|
+
const compactState = contextCompactionState(event);
|
|
128
|
+
if (compactState) {
|
|
129
|
+
return normalize({ ...base, kind: 'lifecycle', target: 'context_compaction', state: compactState }, sanitize);
|
|
130
|
+
}
|
|
106
131
|
if (NOTICE_SOURCES.has(method)) {
|
|
107
132
|
return normalize({ ...base, kind: 'notice', noticeSource: method,
|
|
108
133
|
output: text(params.details), state: event.status === 'failed' ? 'failed' : 'warning' }, sanitize);
|
|
@@ -219,13 +244,31 @@
|
|
|
219
244
|
&& /(?:^|[\/\s"';&|])(?:latexmk|pdflatex|xelatex|lualatex|bibtex|biber)(?=\s|["']|$)/i.test(meta.command);
|
|
220
245
|
}
|
|
221
246
|
|
|
247
|
+
// Read the completion watermark without building a full timeline for every
|
|
248
|
+
// older run when the composer refreshes its context indicator.
|
|
249
|
+
function getCompactedAt(run = {}) {
|
|
250
|
+
return (Array.isArray(run.events) ? run.events : []).reduce((latest, event) => {
|
|
251
|
+
if (!event || event.kind === 'report') return latest;
|
|
252
|
+
const meta = event.activity?.v === 1 ? event.activity : capture(event);
|
|
253
|
+
return meta?.kind === 'lifecycle' && meta.target === 'context_compaction' && meta.state === 'completed'
|
|
254
|
+
? Math.max(latest, Date.parse(event.timestamp || '') || 0) : latest;
|
|
255
|
+
}, 0);
|
|
256
|
+
}
|
|
257
|
+
|
|
222
258
|
function project(run = {}, incoming, options = {}) {
|
|
223
259
|
const saved = Array.isArray(run.events) ? run.events : [];
|
|
224
|
-
|
|
260
|
+
// Stream updates are saved before rendering; buffering and localization copy
|
|
261
|
+
// their objects. Keep the current saved event instead of its incoming copy.
|
|
262
|
+
const incomingStreamKey = incoming?.kind === 'stream' ? text(incoming.streamKey) : '';
|
|
263
|
+
const alreadySaved = incoming && saved.some(event => event === incoming
|
|
264
|
+
|| (incomingStreamKey && event?.kind === 'stream' && event.streamKey === incomingStreamKey));
|
|
265
|
+
const events = incoming && !alreadySaved ? saved.concat(incoming) : saved;
|
|
225
266
|
const ordered = [];
|
|
226
267
|
const diagnostics = [];
|
|
227
268
|
const diagnosticNotices = new Set();
|
|
228
269
|
const byId = new Map();
|
|
270
|
+
const compactions = new Map();
|
|
271
|
+
let compactedAt = 0;
|
|
229
272
|
const report = events.filter(event => event?.kind === 'report').at(-1);
|
|
230
273
|
const finalText = text(report?.detailStructured?.conclusion
|
|
231
274
|
|| report?.detail?.Conclusion || report?.detail?.['结论']).trim();
|
|
@@ -296,6 +339,23 @@
|
|
|
296
339
|
}
|
|
297
340
|
const meta = event.activity?.v === 1 ? event.activity : capture(event);
|
|
298
341
|
clipped ||= meta?.clipped === true;
|
|
342
|
+
if (meta?.kind === 'lifecycle' && meta.target === 'context_compaction') {
|
|
343
|
+
startWork(event);
|
|
344
|
+
const identity = meta.id || meta.scope;
|
|
345
|
+
if (meta.state !== 'running') {
|
|
346
|
+
if (meta.state === 'completed') compactedAt = Math.max(compactedAt, Date.parse(event.timestamp || '') || 0);
|
|
347
|
+
compactions.set(identity, { scope: meta.scope, running: false });
|
|
348
|
+
// Legacy completions have no item id and close the scope's active item.
|
|
349
|
+
if (!meta.id) for (const entry of compactions.values()) {
|
|
350
|
+
if (entry.scope === meta.scope) entry.running = false;
|
|
351
|
+
}
|
|
352
|
+
} else if (!meta.id || !compactions.has(identity)) {
|
|
353
|
+
compactions.set(identity, { scope: meta.scope, running: true });
|
|
354
|
+
}
|
|
355
|
+
diagnostics.push({ key, event });
|
|
356
|
+
if (stage === 'preparing') stage = 'working';
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
299
359
|
if (isDiagnosticNotice(event, meta)) {
|
|
300
360
|
const noticeKey = JSON.stringify([meta?.noticeSource || '', event.title]);
|
|
301
361
|
if (!diagnosticNotices.has(noticeKey)) diagnostics.push({ key, event });
|
|
@@ -312,7 +372,10 @@
|
|
|
312
372
|
} else if (!meta && /^(?:Note: |提示:)/.test(text(event.title))) addSetupStep('note', event);
|
|
313
373
|
if (!meta || meta.kind === 'lifecycle' || event.kind === 'technical') {
|
|
314
374
|
diagnostics.push({ key, event });
|
|
315
|
-
if (meta?.kind === 'lifecycle')
|
|
375
|
+
if (meta?.kind === 'lifecycle') {
|
|
376
|
+
stage = meta.target;
|
|
377
|
+
if (stage === 'local_completed') compactions.clear();
|
|
378
|
+
}
|
|
316
379
|
return;
|
|
317
380
|
}
|
|
318
381
|
const identity = meta.id ? 'item:' + meta.scope + ':' + meta.id : 'event:' + key;
|
|
@@ -354,8 +417,11 @@
|
|
|
354
417
|
}
|
|
355
418
|
const legacy = diagnostics.some(({ event }) => event.kind !== 'technical' && !event.technicalDetail && !event.activity);
|
|
356
419
|
if (stage === 'local_completed') startWork({});
|
|
357
|
-
|
|
420
|
+
const compacting = run.status === 'running' && stage !== 'local_completed'
|
|
421
|
+
&& [...compactions.values()].some(entry => entry.running);
|
|
422
|
+
// Latest completed compaction in this run, in epoch milliseconds; 0 when none completed.
|
|
423
|
+
return { blocks, diagnostics, notices, stage, compacting, compactedAt, setup, fileCount: files.size, clipped, hasReport: Boolean(report), legacy };
|
|
358
424
|
}
|
|
359
425
|
|
|
360
|
-
return { normalize, capture, project, normalizePhase: phase, isRoutineCancellationEvent };
|
|
426
|
+
return { normalize, capture, project, getCompactedAt, contextCompactionState, normalizePhase: phase, isRoutineCancellationEvent };
|
|
361
427
|
});
|