codex-overleaf-link 2.2.1 → 2.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -23
- package/extension/bootstrap/manifest.template.json +1 -1
- package/extension/runtime-manifest.json +5 -71
- package/extension/src/background.js +67 -63
- package/extension/src/backgroundUpdateCoordinator.js +128 -66
- package/extension/src/content/applyResultFormatters.js +2 -3
- package/extension/src/content/composerPanel.js +0 -2
- package/extension/src/content/contentRuntime.js +757 -1558
- package/extension/src/content/contextTray.js +4 -6
- package/extension/src/content/diagnosticsController.js +1 -2
- package/extension/src/content/diffReviewPanel.js +14 -13
- package/extension/src/content/generated/content.bundle.js +41605 -0
- package/extension/src/content/generated/content.bundle.meta.json +103 -0
- package/extension/src/content/legacyGlobalRegistry.js +129 -0
- package/extension/src/content/markdownText.js +17 -17
- package/extension/src/content/mathText.js +155 -9
- package/extension/src/content/modelPicker.js +1 -1
- package/extension/src/content/modelPickerSupport.js +10 -5
- package/extension/src/content/moduleRegistryKernel.js +125 -0
- package/extension/src/content/nativeCompatibilityController.js +313 -0
- package/extension/src/content/otWarmMirror.js +8 -6
- package/extension/src/content/pageBridgeClient.js +224 -0
- package/extension/src/content/panelMaintenance.js +8 -3
- package/extension/src/content/postNavigationSettlementPersistence.js +100 -0
- package/extension/src/content/projectProviderSelection.js +4 -12
- package/extension/src/content/projectSessionCleanup.js +3 -3
- package/extension/src/content/providerSettingsCoordinator.js +59 -26
- package/extension/src/content/providerSettingsDialog.js +30 -18
- package/extension/src/content/recentProjects.js +15 -23
- package/extension/src/content/runGuidanceController.js +1 -1
- package/extension/src/content/runQueueScheduler.js +129 -29
- package/extension/src/content/runSettlementPersistence.js +51 -0
- package/extension/src/content/runTimelineView.js +17 -8
- package/extension/src/content/scopedPersistenceCoordinator.js +100 -0
- package/extension/src/content/scopedPersistenceDetachedCommit.js +47 -0
- package/extension/src/content/scopedPersistencePanelState.js +111 -0
- package/extension/src/content/sessionManager.js +15 -12
- package/extension/src/content/sessionPersistence.js +67 -8
- package/extension/src/content/updateNotice.js +10 -4
- package/extension/src/content/writebackOrchestrator.js +89 -68
- package/extension/src/contentScript.js +5 -1
- package/extension/src/page/trackedChangesLifecycle.js +16 -5
- package/extension/src/page/writebackRouter.js +6 -2
- package/extension/src/pageBridge.js +47 -98
- package/extension/src/shared/agentTranscript.js +18 -8
- package/extension/src/shared/compatibility.js +1 -1
- package/extension/src/shared/governanceRules.js +1 -1
- package/extension/src/shared/i18n.js +2 -4
- package/extension/src/shared/managedUpdateProjection.js +337 -0
- package/extension/src/shared/nativeRequestIdentity.js +60 -0
- package/extension/src/shared/otText.js +0 -19
- package/extension/src/shared/pageRpcContract.js +136 -0
- package/extension/src/shared/runExecutionSnapshot.js +187 -0
- package/extension/src/shared/runExecutionSnapshotCodec.js +171 -0
- package/extension/src/shared/runInputQueue.js +81 -34
- package/extension/src/shared/scopedPersistenceBrowserAdapter.js +79 -0
- package/extension/src/shared/scopedPersistenceQueuePolicy.js +200 -0
- package/extension/src/shared/scopedPersistenceReceipt.js +60 -0
- package/extension/src/shared/scopedPersistenceTransaction.js +241 -0
- package/extension/src/shared/sessionState.js +110 -59
- package/extension/src/shared/settlementFacts.js +351 -0
- package/extension/src/shared/storageDb.js +25 -10
- package/extension/src/shared/storageMigration.js +275 -61
- package/extension/src/shared/storageSessionClaims.js +60 -0
- package/extension/src/shared/updateConsent.js +4 -0
- package/extension/src/shared/updateRevocationIntent.js +109 -0
- package/extension/src/shared/writebackEvidenceProjection.js +150 -0
- package/extension/src/shared/writebackOperationEvidence.js +153 -0
- package/extension/src/shared/writebackSettlement.js +727 -0
- package/extension/styles/panel.css +18 -8
- package/native-host/src/codexPromptAssembly.js +1 -1
- package/native-host/src/codexRuntimeIdentity.js +1 -1
- package/native-host/src/codexSessionRunner.js +7 -13
- package/native-host/src/index.js +3 -69
- package/native-host/src/managedInstall.js +50 -8
- package/native-host/src/nativeTransportEnvelope.js +148 -0
- package/native-host/src/taskRunnerRuntime.js +14 -84
- package/native-host/src/updateManager.js +36 -1
- package/package.json +13 -5
|
@@ -1,18 +1,30 @@
|
|
|
1
1
|
(function initSessionState(root, factory) {
|
|
2
2
|
if (typeof module === 'object' && module.exports) {
|
|
3
|
-
module.exports = factory(
|
|
3
|
+
module.exports = factory(
|
|
4
|
+
require('./i18n'),
|
|
5
|
+
require('./settlementFacts'),
|
|
6
|
+
require('./runInputQueue'),
|
|
7
|
+
require('./lineReferences'),
|
|
8
|
+
require('./pathRedaction')
|
|
9
|
+
);
|
|
4
10
|
} else {
|
|
5
|
-
root.
|
|
6
|
-
|
|
7
|
-
|
|
11
|
+
root.CodexOverleafModuleRegistry.define(
|
|
12
|
+
'SessionState',
|
|
13
|
+
['I18n', 'SettlementFacts', 'RunInputQueue', 'LineReferences', 'PathRedaction'],
|
|
14
|
+
factory
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
})(typeof globalThis !== 'undefined' ? globalThis : window, function sessionStateFactory(
|
|
18
|
+
i18n,
|
|
19
|
+
SettlementFacts,
|
|
20
|
+
RunInputQueue,
|
|
21
|
+
LineReferences,
|
|
22
|
+
PathRedaction
|
|
23
|
+
) {
|
|
8
24
|
'use strict';
|
|
9
25
|
|
|
10
|
-
const i18n = (typeof module === 'object' && module.exports)
|
|
11
|
-
? require('./i18n')
|
|
12
|
-
: (typeof globalThis !== 'undefined' ? globalThis : window).CodexOverleafI18n;
|
|
13
|
-
|
|
14
26
|
const DEFAULT_PANEL_STATE = {
|
|
15
|
-
mode: '
|
|
27
|
+
mode: 'ask',
|
|
16
28
|
providerId: 'builtin',
|
|
17
29
|
model: 'gpt-5.4',
|
|
18
30
|
reasoningEffort: 'high',
|
|
@@ -35,7 +47,7 @@
|
|
|
35
47
|
customInstructionsByProject: {}
|
|
36
48
|
};
|
|
37
49
|
|
|
38
|
-
const VALID_MODES = new Set(['ask', '
|
|
50
|
+
const VALID_MODES = new Set(['ask', 'auto']);
|
|
39
51
|
// Keep the persisted/session-level contract aligned with the model picker
|
|
40
52
|
// and native provider bridge. Custom providers can explicitly disable
|
|
41
53
|
// reasoning (`none`) or expose the lightest Codex tier (`minimal`). Dropping
|
|
@@ -109,18 +121,13 @@
|
|
|
109
121
|
/\b(?:sk|pk)-[A-Za-z0-9][A-Za-z0-9_-]{7,}\b/g,
|
|
110
122
|
/\b(?:api[_-]?key|token|password|passwd|secret)\b\s*[:=]\s*["']?[^"'\s,;]+["']?/gi
|
|
111
123
|
];
|
|
112
|
-
const LineReferences = loadLineReferences();
|
|
113
|
-
const PathRedaction = loadPathRedaction();
|
|
114
|
-
|
|
115
124
|
function normalizePanelState(input = {}, options = {}) {
|
|
116
125
|
const state = {
|
|
117
126
|
...DEFAULT_PANEL_STATE,
|
|
118
127
|
...input
|
|
119
128
|
};
|
|
120
129
|
|
|
121
|
-
|
|
122
|
-
state.mode = DEFAULT_PANEL_STATE.mode;
|
|
123
|
-
}
|
|
130
|
+
state.mode = normalizeMode(state.mode);
|
|
124
131
|
if (!VALID_REASONING.has(state.reasoningEffort)) {
|
|
125
132
|
state.reasoningEffort = DEFAULT_PANEL_STATE.reasoningEffort;
|
|
126
133
|
}
|
|
@@ -212,7 +219,7 @@
|
|
|
212
219
|
history,
|
|
213
220
|
runs,
|
|
214
221
|
task: sanitizeAssistantVisibleText(session.task),
|
|
215
|
-
mode:
|
|
222
|
+
mode: normalizeMode(session.mode, fallbackState.mode),
|
|
216
223
|
providerId: typeof session.providerId === 'string' && session.providerId.trim()
|
|
217
224
|
? session.providerId.trim()
|
|
218
225
|
: (fallbackState.providerId || 'builtin'),
|
|
@@ -225,8 +232,17 @@
|
|
|
225
232
|
: fallbackState.speedTier,
|
|
226
233
|
requireReviewing: session.requireReviewing !== false,
|
|
227
234
|
focusFiles: normalizeFocusFiles(session.focusFiles),
|
|
235
|
+
projectReferenceFiles: normalizeProjectReferenceFiles(session.projectReferenceFiles),
|
|
228
236
|
codexThreadId: typeof session.codexThreadId === 'string' ? session.codexThreadId : '',
|
|
229
|
-
pendingInputs: normalizePendingInputs(
|
|
237
|
+
pendingInputs: normalizePendingInputs(
|
|
238
|
+
session.pendingInputs,
|
|
239
|
+
options.restoreRunningRuns === true,
|
|
240
|
+
{
|
|
241
|
+
...fallbackState,
|
|
242
|
+
...session,
|
|
243
|
+
focusFiles: normalizeFocusFiles(session.focusFiles)
|
|
244
|
+
}
|
|
245
|
+
)
|
|
230
246
|
};
|
|
231
247
|
}
|
|
232
248
|
|
|
@@ -294,13 +310,14 @@
|
|
|
294
310
|
id: active.id,
|
|
295
311
|
history: Array.isArray(active.history) ? active.history.slice(-10) : [],
|
|
296
312
|
focusFiles: normalizeFocusFiles(active.focusFiles),
|
|
313
|
+
projectReferenceFiles: normalizeProjectReferenceFiles(active.projectReferenceFiles),
|
|
297
314
|
codexThreadId: active.codexThreadId || '',
|
|
298
315
|
providerId: state.providerId || 'builtin'
|
|
299
316
|
};
|
|
300
317
|
state.runs = Array.isArray(active.runs) ? active.runs : [];
|
|
301
318
|
state.task = typeof active.task === 'string' ? active.task : '';
|
|
302
319
|
state.focusFiles = normalizeFocusFiles(active.focusFiles);
|
|
303
|
-
state.mode =
|
|
320
|
+
state.mode = normalizeMode(active.mode);
|
|
304
321
|
state.requireReviewing = active.requireReviewing !== false;
|
|
305
322
|
|
|
306
323
|
return state;
|
|
@@ -322,7 +339,7 @@
|
|
|
322
339
|
history: normalizeHistoryEntries(overrides.history),
|
|
323
340
|
runs,
|
|
324
341
|
task: sanitizeAssistantVisibleText(overrides.task),
|
|
325
|
-
mode:
|
|
342
|
+
mode: normalizeMode(overrides.mode),
|
|
326
343
|
providerId: typeof overrides.providerId === 'string' && overrides.providerId.trim()
|
|
327
344
|
? overrides.providerId.trim()
|
|
328
345
|
: 'builtin',
|
|
@@ -335,6 +352,7 @@
|
|
|
335
352
|
: DEFAULT_PANEL_STATE.speedTier,
|
|
336
353
|
requireReviewing: overrides.requireReviewing !== false,
|
|
337
354
|
focusFiles: normalizeFocusFiles(overrides.focusFiles),
|
|
355
|
+
projectReferenceFiles: normalizeProjectReferenceFiles(overrides.projectReferenceFiles),
|
|
338
356
|
codexThreadId: typeof overrides.codexThreadId === 'string' ? overrides.codexThreadId : '',
|
|
339
357
|
pendingInputs: normalizePendingInputs(overrides.pendingInputs)
|
|
340
358
|
};
|
|
@@ -626,6 +644,18 @@
|
|
|
626
644
|
interruptedDraft: run.interruptedDraft ? sanitizeAssistantVisibleValue(run.interruptedDraft) : undefined
|
|
627
645
|
};
|
|
628
646
|
|
|
647
|
+
if (run.executionSnapshot && typeof run.executionSnapshot === 'object') {
|
|
648
|
+
normalized.executionSnapshot = sanitizeAssistantVisibleValue(run.executionSnapshot);
|
|
649
|
+
}
|
|
650
|
+
const storedSettlement = run.settlement && typeof run.settlement === 'object'
|
|
651
|
+
? run.settlement
|
|
652
|
+
: run.settlementFacts;
|
|
653
|
+
if (storedSettlement && typeof storedSettlement === 'object') {
|
|
654
|
+
normalized.settlement = sanitizeAssistantVisibleValue(storedSettlement);
|
|
655
|
+
}
|
|
656
|
+
if (run.changedDocument === true) {
|
|
657
|
+
normalized.changedDocument = true;
|
|
658
|
+
}
|
|
629
659
|
applyTrackedChangeStatus(normalized, run.trackedChangeStatus);
|
|
630
660
|
|
|
631
661
|
return normalized;
|
|
@@ -658,7 +688,7 @@
|
|
|
658
688
|
// the run returns to the legacy-undo world. A terminal status with no refs
|
|
659
689
|
// is kept — step 3 already empties terminal payloads and the label stays
|
|
660
690
|
// meaningful.
|
|
661
|
-
if (!hasRefs && status
|
|
691
|
+
if (!hasRefs && status === 'pending') {
|
|
662
692
|
status = undefined;
|
|
663
693
|
}
|
|
664
694
|
|
|
@@ -875,6 +905,33 @@
|
|
|
875
905
|
return files.slice(0, 5);
|
|
876
906
|
}
|
|
877
907
|
|
|
908
|
+
function normalizeProjectReferenceFiles(value) {
|
|
909
|
+
const seen = new Set();
|
|
910
|
+
const files = [];
|
|
911
|
+
for (const item of Array.isArray(value) ? value : []) {
|
|
912
|
+
const rawPath = typeof item === 'string' ? item : item?.path;
|
|
913
|
+
if (typeof rawPath !== 'string' || !rawPath.trim() || rawPath.length > 512) {
|
|
914
|
+
continue;
|
|
915
|
+
}
|
|
916
|
+
if (/^(?:[A-Za-z]:|[\\/])/.test(rawPath) || rawPath.includes('\0')) {
|
|
917
|
+
continue;
|
|
918
|
+
}
|
|
919
|
+
const path = rawPath.replace(/\\/g, '/').replace(/\/+/g, '/').replace(/^\.\//, '').trim();
|
|
920
|
+
if (!path || path.split('/').some(segment => !segment || segment === '.' || segment === '..') || seen.has(path)) {
|
|
921
|
+
continue;
|
|
922
|
+
}
|
|
923
|
+
seen.add(path);
|
|
924
|
+
files.push({
|
|
925
|
+
path,
|
|
926
|
+
kind: item?.kind === 'binary' ? 'binary' : 'text'
|
|
927
|
+
});
|
|
928
|
+
if (files.length >= 5000) {
|
|
929
|
+
break;
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
return files;
|
|
933
|
+
}
|
|
934
|
+
|
|
878
935
|
function normalizeCustomInstructionsByProject(value) {
|
|
879
936
|
const result = {};
|
|
880
937
|
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
@@ -965,7 +1022,7 @@
|
|
|
965
1022
|
// Skills page was reopened.
|
|
966
1023
|
codexOverleafSkills: compactCodexOverleafSkillsForStorage(source.codexOverleafSkills),
|
|
967
1024
|
composerAttachments: normalizeComposerAttachmentsForPersistence(source.composerAttachments),
|
|
968
|
-
mode:
|
|
1025
|
+
mode: normalizeMode(active?.mode, source.mode),
|
|
969
1026
|
providerId: typeof source.providerId === 'string' && source.providerId.trim()
|
|
970
1027
|
? source.providerId.trim()
|
|
971
1028
|
: 'builtin',
|
|
@@ -1063,6 +1120,7 @@
|
|
|
1063
1120
|
speedTier: normalizeSpeedTier(session.speedTier || fallbackState.speedTier),
|
|
1064
1121
|
requireReviewing: session.requireReviewing !== false,
|
|
1065
1122
|
focusFiles: normalizeFocusFiles(session.focusFiles),
|
|
1123
|
+
projectReferenceFiles: normalizeProjectReferenceFiles(session.projectReferenceFiles),
|
|
1066
1124
|
codexThreadId: typeof session.codexThreadId === 'string' ? session.codexThreadId : '',
|
|
1067
1125
|
pendingInputs: normalizePendingInputs(session.pendingInputs)
|
|
1068
1126
|
};
|
|
@@ -1121,10 +1179,18 @@
|
|
|
1121
1179
|
.filter(run => Array.isArray(run.undoOperations) && run.undoOperations.length)
|
|
1122
1180
|
.slice(0, limits.maxUndoRunsPerSession)
|
|
1123
1181
|
.map(run => run.id));
|
|
1124
|
-
|
|
1182
|
+
const settlementFactsByRunId = SettlementFacts.compactSessionSettlementFacts(selectedRuns, {
|
|
1183
|
+
sanitize: sanitizeAssistantVisibleValue
|
|
1184
|
+
});
|
|
1185
|
+
return selectedRuns.map(run => compactRunForStorage(
|
|
1186
|
+
run,
|
|
1187
|
+
limits,
|
|
1188
|
+
undoRunIds.has(run.id),
|
|
1189
|
+
settlementFactsByRunId.get(run.id)
|
|
1190
|
+
));
|
|
1125
1191
|
}
|
|
1126
1192
|
|
|
1127
|
-
function compactRunForStorage(run, limits, keepUndoPayload) {
|
|
1193
|
+
function compactRunForStorage(run, limits, keepUndoPayload, settlementFacts = null) {
|
|
1128
1194
|
const undoPayload = compactUndoPayload(run, limits, keepUndoPayload);
|
|
1129
1195
|
const compact = {
|
|
1130
1196
|
id: run.id,
|
|
@@ -1151,6 +1217,15 @@
|
|
|
1151
1217
|
nativeEventSeq: Number.isFinite(Number(run.nativeEventSeq)) ? Number(run.nativeEventSeq) : 0,
|
|
1152
1218
|
queueItemId: normalizeTextField(run.queueItemId, 160)
|
|
1153
1219
|
};
|
|
1220
|
+
if (run.executionSnapshot && typeof run.executionSnapshot === 'object') {
|
|
1221
|
+
compact.executionSnapshot = sanitizeAssistantVisibleValue(run.executionSnapshot);
|
|
1222
|
+
}
|
|
1223
|
+
if (settlementFacts && typeof settlementFacts === 'object') {
|
|
1224
|
+
compact.settlement = settlementFacts;
|
|
1225
|
+
}
|
|
1226
|
+
if (run.changedDocument === true) {
|
|
1227
|
+
compact.changedDocument = true;
|
|
1228
|
+
}
|
|
1154
1229
|
if (run.interruptedDraft) {
|
|
1155
1230
|
compact.interruptedDraft = sanitizeAssistantVisibleValue(run.interruptedDraft);
|
|
1156
1231
|
}
|
|
@@ -1293,8 +1368,14 @@
|
|
|
1293
1368
|
};
|
|
1294
1369
|
}
|
|
1295
1370
|
|
|
1296
|
-
function normalizeMode(mode) {
|
|
1297
|
-
|
|
1371
|
+
function normalizeMode(mode, fallback = DEFAULT_PANEL_STATE.mode) {
|
|
1372
|
+
if (mode === 'confirm') {
|
|
1373
|
+
return 'ask';
|
|
1374
|
+
}
|
|
1375
|
+
if (VALID_MODES.has(mode)) {
|
|
1376
|
+
return mode;
|
|
1377
|
+
}
|
|
1378
|
+
return VALID_MODES.has(fallback) ? fallback : DEFAULT_PANEL_STATE.mode;
|
|
1298
1379
|
}
|
|
1299
1380
|
|
|
1300
1381
|
function normalizeReasoning(reasoningEffort) {
|
|
@@ -1384,11 +1465,9 @@
|
|
|
1384
1465
|
}));
|
|
1385
1466
|
}
|
|
1386
1467
|
|
|
1387
|
-
function normalizePendingInputs(value, recoverActive = false) {
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
if (Queue?.normalizeQueue) {
|
|
1391
|
-
return Queue.normalizeQueue(value, { recoverActive });
|
|
1468
|
+
function normalizePendingInputs(value, recoverActive = false, fallbackInput = {}) {
|
|
1469
|
+
if (RunInputQueue?.normalizeQueue) {
|
|
1470
|
+
return RunInputQueue.normalizeQueue(value, { recoverActive, fallbackInput });
|
|
1392
1471
|
}
|
|
1393
1472
|
return [];
|
|
1394
1473
|
}
|
|
@@ -1442,34 +1521,6 @@
|
|
|
1442
1521
|
return /(?:file:\/\/\/?|[A-Za-z]:[\\/]|\/(?:Users|home|private|var|tmp)\/|[\\/]\.codex-overleaf[\\/]projects[\\/]|\.codex-overleaf[\\/]projects[\\/])/i.test(String(value || ''));
|
|
1443
1522
|
}
|
|
1444
1523
|
|
|
1445
|
-
function loadLineReferences() {
|
|
1446
|
-
if (typeof globalThis !== 'undefined' && globalThis.CodexOverleafLineReferences) {
|
|
1447
|
-
return globalThis.CodexOverleafLineReferences;
|
|
1448
|
-
}
|
|
1449
|
-
if (typeof require === 'function') {
|
|
1450
|
-
try {
|
|
1451
|
-
return require('./lineReferences');
|
|
1452
|
-
} catch (_error) {
|
|
1453
|
-
return null;
|
|
1454
|
-
}
|
|
1455
|
-
}
|
|
1456
|
-
return null;
|
|
1457
|
-
}
|
|
1458
|
-
|
|
1459
|
-
function loadPathRedaction() {
|
|
1460
|
-
if (typeof globalThis !== 'undefined' && globalThis.CodexOverleafPathRedaction) {
|
|
1461
|
-
return globalThis.CodexOverleafPathRedaction;
|
|
1462
|
-
}
|
|
1463
|
-
if (typeof require === 'function') {
|
|
1464
|
-
try {
|
|
1465
|
-
return require('./pathRedaction');
|
|
1466
|
-
} catch (_error) {
|
|
1467
|
-
return null;
|
|
1468
|
-
}
|
|
1469
|
-
}
|
|
1470
|
-
return null;
|
|
1471
|
-
}
|
|
1472
|
-
|
|
1473
1524
|
function fallbackSanitizeLocalReferences(value) {
|
|
1474
1525
|
return String(value || '')
|
|
1475
1526
|
.replace(/\[([^\]]*)\]\(([^)]*)\)/g, (_raw, label, target) => {
|
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
(function initCodexOverleafSettlementFacts(root, factory) {
|
|
2
|
+
if (typeof module === 'object' && module.exports) {
|
|
3
|
+
module.exports = factory(require('./failureReasons'));
|
|
4
|
+
} else {
|
|
5
|
+
root.CodexOverleafModuleRegistry.define('SettlementFacts', ['FailureReasons'], factory);
|
|
6
|
+
}
|
|
7
|
+
})(typeof window !== 'undefined' ? window : globalThis, function settlementFactsFactory(DefaultFailureReasons) {
|
|
8
|
+
'use strict';
|
|
9
|
+
|
|
10
|
+
const SETTLEMENT_FACTS_MAX_BYTES = 32 * 1024;
|
|
11
|
+
const SESSION_SETTLEMENT_DETAIL_MAX_BYTES = 512 * 1024;
|
|
12
|
+
const RECOVERY_FIELDS = Object.freeze([
|
|
13
|
+
'appliedOperations', 'undoOperations', 'undoBaseFiles', 'undoTrackedChanges',
|
|
14
|
+
'undoExpectedFiles', 'undoStatus', 'interruptedDraft'
|
|
15
|
+
]);
|
|
16
|
+
|
|
17
|
+
function compactSettlementFacts(value = {}, maxBytes = SETTLEMENT_FACTS_MAX_BYTES) {
|
|
18
|
+
const legacyFailures = value.primaryFailure && typeof value.primaryFailure === 'object'
|
|
19
|
+
? [value.primaryFailure]
|
|
20
|
+
: [];
|
|
21
|
+
const facts = {
|
|
22
|
+
schemaVersion: 1,
|
|
23
|
+
documentEffect: normalizeDocumentEffect(
|
|
24
|
+
value.documentEffect || (value.changedDocument === true ? 'changed' : '')
|
|
25
|
+
),
|
|
26
|
+
evidence: normalizeEvidence(value),
|
|
27
|
+
fileSettlements: normalizeFiles(value.fileSettlements),
|
|
28
|
+
failures: normalizeFailures(Array.isArray(value.failures) ? value.failures : legacyFailures),
|
|
29
|
+
recoveryAvailability: normalizeAvailability(value.recoveryAvailability),
|
|
30
|
+
recoveryDisposition: normalizeDisposition(value.recoveryDisposition),
|
|
31
|
+
capturedAt: typeof value.capturedAt === 'string' ? value.capturedAt : ''
|
|
32
|
+
};
|
|
33
|
+
if (value.compaction && typeof value.compaction === 'object') {
|
|
34
|
+
facts.compaction = normalizeCompaction(value.compaction);
|
|
35
|
+
}
|
|
36
|
+
if (estimateBytes(facts) <= maxBytes) return facts;
|
|
37
|
+
|
|
38
|
+
const files = facts.fileSettlements;
|
|
39
|
+
files.sort((left, right) => importance(left) - importance(right)
|
|
40
|
+
|| left.path.localeCompare(right.path));
|
|
41
|
+
const omitted = {
|
|
42
|
+
failureOrRecovery: Number(facts.compaction?.omittedFileSettlements?.failureOrRecovery || 0),
|
|
43
|
+
changed: Number(facts.compaction?.omittedFileSettlements?.changed || 0),
|
|
44
|
+
unchangedOrUnknown: Number(facts.compaction?.omittedFileSettlements?.unchangedOrUnknown || 0)
|
|
45
|
+
};
|
|
46
|
+
facts.compaction = {
|
|
47
|
+
...(facts.compaction || {}),
|
|
48
|
+
omittedFileSettlements: omitted,
|
|
49
|
+
omittedFailures: Number(facts.compaction?.omittedFailures || 0)
|
|
50
|
+
};
|
|
51
|
+
while (files.length && estimateBytes(facts) > maxBytes) {
|
|
52
|
+
omitted[category(files.shift())]++;
|
|
53
|
+
}
|
|
54
|
+
facts.fileSettlements = files.sort((left, right) => left.path.localeCompare(right.path));
|
|
55
|
+
if (estimateBytes(facts) > maxBytes) {
|
|
56
|
+
const primaryFailure = selectPrimaryFailure(facts.failures);
|
|
57
|
+
facts.failures = facts.failures.map(compactFailure);
|
|
58
|
+
if (primaryFailure) {
|
|
59
|
+
const primaryIndex = facts.failures.findIndex(item =>
|
|
60
|
+
item.code === primaryFailure.code
|
|
61
|
+
&& item.stage === primaryFailure.stage
|
|
62
|
+
&& item.severity === primaryFailure.severity
|
|
63
|
+
&& item.file === primaryFailure.file
|
|
64
|
+
);
|
|
65
|
+
if (primaryIndex > 0) {
|
|
66
|
+
facts.failures.unshift(facts.failures.splice(primaryIndex, 1)[0]);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
while (facts.failures.length > 1 && estimateBytes(facts) > maxBytes) {
|
|
70
|
+
facts.failures.pop();
|
|
71
|
+
facts.compaction.omittedFailures++;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return facts;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function compactSessionSettlementFacts(runs = [], options = {}) {
|
|
78
|
+
const sanitize = options.sanitize instanceof Function ? options.sanitize : cloneValue;
|
|
79
|
+
const measure = options.estimateBytes instanceof Function ? options.estimateBytes : estimateBytes;
|
|
80
|
+
const maxDetailBytes = Number.isFinite(options.maxDetailBytes)
|
|
81
|
+
? Math.max(0, options.maxDetailBytes)
|
|
82
|
+
: SESSION_SETTLEMENT_DETAIL_MAX_BYTES;
|
|
83
|
+
const entries = (Array.isArray(runs) ? runs : [])
|
|
84
|
+
.filter(run => run?.id && (
|
|
85
|
+
(run.settlement && typeof run.settlement === 'object')
|
|
86
|
+
|| (run.settlementFacts && typeof run.settlementFacts === 'object')
|
|
87
|
+
))
|
|
88
|
+
.map((run, index) => ({
|
|
89
|
+
id: run.id,
|
|
90
|
+
protected: run.status === 'running'
|
|
91
|
+
|| run.trackedChangeStatus === 'pending'
|
|
92
|
+
|| run.trackedChangeStatus === 'needs_review'
|
|
93
|
+
|| index === runs.length - 1,
|
|
94
|
+
facts: sanitize(run.settlement && typeof run.settlement === 'object'
|
|
95
|
+
? run.settlement
|
|
96
|
+
: run.settlementFacts)
|
|
97
|
+
}));
|
|
98
|
+
const detailBytes = () => entries.reduce((total, entry) =>
|
|
99
|
+
total + measure(entry.facts.fileSettlements || []), 0);
|
|
100
|
+
const candidates = [
|
|
101
|
+
...entries.filter(entry => !entry.protected),
|
|
102
|
+
...entries.filter(entry => entry.protected)
|
|
103
|
+
];
|
|
104
|
+
for (const entry of candidates) {
|
|
105
|
+
if (detailBytes() <= maxDetailBytes) break;
|
|
106
|
+
compactSettlementFileDetails(entry.facts, () => detailBytes() <= maxDetailBytes);
|
|
107
|
+
}
|
|
108
|
+
return new Map(entries.map(entry => [entry.id, entry.facts]));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function compactSettlementFileDetails(facts, budgetSatisfied) {
|
|
112
|
+
const files = Array.isArray(facts?.fileSettlements) ? [...facts.fileSettlements] : [];
|
|
113
|
+
files.sort((left, right) => importance(left) - importance(right)
|
|
114
|
+
|| String(left?.path || '').localeCompare(String(right?.path || '')));
|
|
115
|
+
const omitted = {
|
|
116
|
+
failureOrRecovery: positive(facts?.compaction?.omittedFileSettlements?.failureOrRecovery),
|
|
117
|
+
changed: positive(facts?.compaction?.omittedFileSettlements?.changed),
|
|
118
|
+
unchangedOrUnknown: positive(facts?.compaction?.omittedFileSettlements?.unchangedOrUnknown)
|
|
119
|
+
};
|
|
120
|
+
while (files.length && !budgetSatisfied()) {
|
|
121
|
+
omitted[category(files.shift())]++;
|
|
122
|
+
facts.fileSettlements = [...files].sort((left, right) =>
|
|
123
|
+
String(left?.path || '').localeCompare(String(right?.path || '')));
|
|
124
|
+
facts.compaction = { ...(facts.compaction || {}), omittedFileSettlements: omitted };
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function projectRunSettlement(run = {}) {
|
|
129
|
+
const storedFacts = run.settlement && typeof run.settlement === 'object'
|
|
130
|
+
? run.settlement
|
|
131
|
+
: run.settlementFacts;
|
|
132
|
+
const facts = storedFacts && typeof storedFacts === 'object'
|
|
133
|
+
? compactSettlementFacts(storedFacts)
|
|
134
|
+
: null;
|
|
135
|
+
const primaryFailure = selectPrimaryFailure(facts?.failures || []);
|
|
136
|
+
const status = text(run.trackedChangeStatus);
|
|
137
|
+
const actionable = status === 'pending' || status === 'needs_review';
|
|
138
|
+
const terminal = status === 'accepted' || status === 'rejected';
|
|
139
|
+
const payload = run.recoveryPayload && typeof run.recoveryPayload === 'object'
|
|
140
|
+
? run.recoveryPayload
|
|
141
|
+
: run;
|
|
142
|
+
const tracked = hasItems(payload.undoTrackedChanges);
|
|
143
|
+
const anyRecovery = tracked
|
|
144
|
+
|| hasItems(payload.undoOperations)
|
|
145
|
+
|| hasItems(payload.undoBaseFiles)
|
|
146
|
+
|| hasItems(payload.undoExpectedFiles)
|
|
147
|
+
|| hasItems(payload.appliedOperations);
|
|
148
|
+
const canAccept = actionable && tracked;
|
|
149
|
+
const canUndo = !terminal
|
|
150
|
+
&& run.undoStatus !== 'applied'
|
|
151
|
+
&& (actionable ? anyRecovery : (!status && anyRecovery));
|
|
152
|
+
const failureTerminalState = normalizeTerminalState(primaryFailure?.terminalState);
|
|
153
|
+
return {
|
|
154
|
+
changedDocument: projectDocumentEffect(facts?.documentEffect, run.changedDocument),
|
|
155
|
+
primaryFailure,
|
|
156
|
+
failureTerminalState,
|
|
157
|
+
canAccept,
|
|
158
|
+
canUndo,
|
|
159
|
+
canRetry: Boolean(primaryFailure?.retryable),
|
|
160
|
+
terminalState: failureTerminalState || '',
|
|
161
|
+
actions: { accept: canAccept, undo: canUndo }
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function deriveLegacyProjection(run = {}) {
|
|
166
|
+
return projectRunSettlement({ ...run, settlement: null, settlementFacts: null });
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function normalizeEvidence(value = {}) {
|
|
170
|
+
const evidence = value.evidence && typeof value.evidence === 'object' ? value.evidence : {};
|
|
171
|
+
const legacySave = value.save && typeof value.save === 'object' ? value.save : {};
|
|
172
|
+
const savedCandidate = evidence.saved
|
|
173
|
+
|| (legacySave.confidence === 'verified' ? 'verified'
|
|
174
|
+
: legacySave.confidence === 'quiet-observed' ? 'quiet-observed' : '');
|
|
175
|
+
return {
|
|
176
|
+
applied: allowed(evidence.applied, ['none', 'partial', 'complete', 'unknown'], 'unknown'),
|
|
177
|
+
readBack: allowed(evidence.readBack, ['not-attempted', 'exact', 'partial', 'mismatch', 'unavailable'], 'not-attempted'),
|
|
178
|
+
saved: allowed(savedCandidate, ['not-attempted', 'not-required', 'quiet-observed', 'verified', 'failed', 'unavailable'], 'not-attempted'),
|
|
179
|
+
mirrored: normalizeMirror(evidence.mirrored || value.mirror?.state),
|
|
180
|
+
compiled: normalizeCompile(evidence.compiled || value.compile?.state),
|
|
181
|
+
settled: allowed(evidence.settled, ['unsettled', 'partial', 'complete', 'needs-review'], 'unsettled')
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function normalizeFiles(value) {
|
|
186
|
+
return (Array.isArray(value) ? value : []).map(file => ({
|
|
187
|
+
path: safePath(file?.path),
|
|
188
|
+
operationIds: strings(file?.operationIds, 100),
|
|
189
|
+
relatedPaths: strings(file?.relatedPaths, 20).map(safePath).filter(Boolean),
|
|
190
|
+
applied: allowed(file?.applied, ['none', 'partial', 'complete', 'failed', 'unknown'],
|
|
191
|
+
file?.applied === true ? 'complete' : 'unknown'),
|
|
192
|
+
documentEffect: normalizeDocumentEffect(file?.documentEffect),
|
|
193
|
+
readBack: normalizeReadback(file?.readBack),
|
|
194
|
+
recoveryKind: allowed(file?.recoveryKind,
|
|
195
|
+
['tracked-changes', 'editor-history', 'legacy-baseline', 'mixed', 'none'], 'none'),
|
|
196
|
+
failureCodes: strings(file?.failureCodes, 40)
|
|
197
|
+
})).filter(file => file.path);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function normalizeFailures(value) {
|
|
201
|
+
return (Array.isArray(value) ? value : []).filter(item => item && typeof item === 'object')
|
|
202
|
+
.map(item => ({
|
|
203
|
+
code: text(item.code).slice(0, 120),
|
|
204
|
+
stage: text(item.stage).slice(0, 40),
|
|
205
|
+
severity: text(item.severity).slice(0, 40),
|
|
206
|
+
userMessage: text(item.userMessage).slice(0, 1000),
|
|
207
|
+
retryable: item.retryable === true,
|
|
208
|
+
nextAction: text(item.nextAction).slice(0, 1000),
|
|
209
|
+
file: safePath(item.file),
|
|
210
|
+
operationType: text(item.operationType).slice(0, 80),
|
|
211
|
+
changedDocument: item.changedDocument === true,
|
|
212
|
+
terminalState: normalizeTerminalState(item.terminalState)
|
|
213
|
+
}));
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function normalizeDocumentEffect(value) {
|
|
217
|
+
return allowed(value === 'possible' ? 'possibly-changed' : text(value),
|
|
218
|
+
['unchanged', 'changed', 'possibly-changed', 'unknown'], 'unknown');
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function normalizeReadback(value) {
|
|
222
|
+
if (value === 'match') return 'exact';
|
|
223
|
+
if (value === 'missing' || value === 'unknown') return 'unavailable';
|
|
224
|
+
return allowed(value, ['not-attempted', 'exact', 'mismatch', 'unavailable'], 'not-attempted');
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function normalizeMirror(value) {
|
|
228
|
+
if (value === 'not_started' || !value) return 'not-attempted';
|
|
229
|
+
if (value === 'pending') return 'not-attempted';
|
|
230
|
+
return allowed(value, ['not-attempted', 'complete', 'partial', 'failed', 'unknown'], 'unknown');
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function normalizeCompile(value) {
|
|
234
|
+
if (value === 'success' || value === 'completed') return 'succeeded';
|
|
235
|
+
if (value === 'not_started' || !value) return 'not-attempted';
|
|
236
|
+
return allowed(value, ['not-attempted', 'succeeded', 'failed', 'unknown'], 'unknown');
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function normalizeAvailability(value = {}) {
|
|
240
|
+
return {
|
|
241
|
+
trackedChanges: value?.trackedChanges === true,
|
|
242
|
+
editorHistory: value?.editorHistory === true,
|
|
243
|
+
legacyBaseline: value?.legacyBaseline === true
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function normalizeDisposition(value = {}) {
|
|
248
|
+
return Object.fromEntries(RECOVERY_FIELDS.map(field => [
|
|
249
|
+
field, value?.[field] === 'clear' ? 'clear' : 'preserve'
|
|
250
|
+
]));
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function normalizeCompaction(value = {}) {
|
|
254
|
+
const omitted = value.omittedFileSettlements || {};
|
|
255
|
+
return {
|
|
256
|
+
omittedFileSettlements: {
|
|
257
|
+
failureOrRecovery: positive(omitted.failureOrRecovery),
|
|
258
|
+
changed: positive(omitted.changed),
|
|
259
|
+
unchangedOrUnknown: positive(omitted.unchangedOrUnknown)
|
|
260
|
+
},
|
|
261
|
+
omittedFailures: positive(value.omittedFailures)
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function projectDocumentEffect(effect, legacy) {
|
|
266
|
+
if (effect === 'changed') return true;
|
|
267
|
+
if (effect === 'unchanged') return false;
|
|
268
|
+
if (effect === 'possibly-changed' || effect === 'unknown') return null;
|
|
269
|
+
return legacy === true ? true : legacy === false ? false : null;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function category(file = {}) {
|
|
273
|
+
if (file.failureCodes?.length || (file.recoveryKind && file.recoveryKind !== 'none')) return 'failureOrRecovery';
|
|
274
|
+
return file.documentEffect === 'changed' || file.documentEffect === 'possibly-changed'
|
|
275
|
+
? 'changed'
|
|
276
|
+
: 'unchangedOrUnknown';
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function importance(file) {
|
|
280
|
+
const value = category(file);
|
|
281
|
+
return value === 'unchangedOrUnknown' ? 1 : value === 'changed' ? 2 : 3;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function compactFailure(item = {}) {
|
|
285
|
+
const { userMessage, nextAction, ...compact } = item;
|
|
286
|
+
return compact;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function selectPrimaryFailure(failures) {
|
|
290
|
+
return DefaultFailureReasons?.selectPrimaryFailure instanceof Function
|
|
291
|
+
? DefaultFailureReasons.selectPrimaryFailure(failures)
|
|
292
|
+
: failures[0] || null;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function normalizeTerminalState(value) {
|
|
296
|
+
return allowed(text(value), ['failed', 'blocked', 'degraded', 'needs_review'], null);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function safePath(value) {
|
|
300
|
+
const path = text(value).replace(/\\/g, '/');
|
|
301
|
+
if (!path || path.startsWith('/') || /^[A-Za-z]:\//.test(path) || path.split('/').includes('..')) return '';
|
|
302
|
+
return path.slice(0, 300);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function strings(value, limit) {
|
|
306
|
+
return (Array.isArray(value) ? value : []).map(text).filter(Boolean).slice(0, limit);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function hasItems(value) {
|
|
310
|
+
return Array.isArray(value)
|
|
311
|
+
? value.length > 0
|
|
312
|
+
: Boolean(value && typeof value === 'object' && Object.keys(value).length);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function estimateBytes(value) {
|
|
316
|
+
const serialized = JSON.stringify(value);
|
|
317
|
+
return typeof TextEncoder !== 'undefined'
|
|
318
|
+
? new TextEncoder().encode(serialized).length
|
|
319
|
+
: serialized.length * 2;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function cloneValue(value) {
|
|
323
|
+
try {
|
|
324
|
+
return JSON.parse(JSON.stringify(value));
|
|
325
|
+
} catch (_error) {
|
|
326
|
+
return {};
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function allowed(value, values, fallback) {
|
|
331
|
+
return values.includes(value) ? value : fallback;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function positive(value) {
|
|
335
|
+
return Math.max(0, Number(value) || 0);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function text(value) {
|
|
339
|
+
return typeof value === 'string' ? value.trim() : '';
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
return {
|
|
343
|
+
RECOVERY_FIELDS,
|
|
344
|
+
SESSION_SETTLEMENT_DETAIL_MAX_BYTES,
|
|
345
|
+
SETTLEMENT_FACTS_MAX_BYTES,
|
|
346
|
+
compactSessionSettlementFacts,
|
|
347
|
+
compactSettlementFacts,
|
|
348
|
+
deriveLegacyProjection,
|
|
349
|
+
projectRunSettlement
|
|
350
|
+
};
|
|
351
|
+
});
|