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.
Files changed (79) hide show
  1. package/README.md +25 -23
  2. package/extension/bootstrap/manifest.template.json +1 -1
  3. package/extension/runtime-manifest.json +5 -71
  4. package/extension/src/background.js +67 -63
  5. package/extension/src/backgroundUpdateCoordinator.js +128 -66
  6. package/extension/src/content/applyResultFormatters.js +2 -3
  7. package/extension/src/content/composerPanel.js +0 -2
  8. package/extension/src/content/contentRuntime.js +757 -1558
  9. package/extension/src/content/contextTray.js +4 -6
  10. package/extension/src/content/diagnosticsController.js +1 -2
  11. package/extension/src/content/diffReviewPanel.js +14 -13
  12. package/extension/src/content/generated/content.bundle.js +41605 -0
  13. package/extension/src/content/generated/content.bundle.meta.json +103 -0
  14. package/extension/src/content/legacyGlobalRegistry.js +129 -0
  15. package/extension/src/content/markdownText.js +17 -17
  16. package/extension/src/content/mathText.js +155 -9
  17. package/extension/src/content/modelPicker.js +1 -1
  18. package/extension/src/content/modelPickerSupport.js +10 -5
  19. package/extension/src/content/moduleRegistryKernel.js +125 -0
  20. package/extension/src/content/nativeCompatibilityController.js +313 -0
  21. package/extension/src/content/otWarmMirror.js +8 -6
  22. package/extension/src/content/pageBridgeClient.js +224 -0
  23. package/extension/src/content/panelMaintenance.js +8 -3
  24. package/extension/src/content/postNavigationSettlementPersistence.js +100 -0
  25. package/extension/src/content/projectProviderSelection.js +4 -12
  26. package/extension/src/content/projectSessionCleanup.js +3 -3
  27. package/extension/src/content/providerSettingsCoordinator.js +59 -26
  28. package/extension/src/content/providerSettingsDialog.js +30 -18
  29. package/extension/src/content/recentProjects.js +15 -23
  30. package/extension/src/content/runGuidanceController.js +1 -1
  31. package/extension/src/content/runQueueScheduler.js +129 -29
  32. package/extension/src/content/runSettlementPersistence.js +51 -0
  33. package/extension/src/content/runTimelineView.js +17 -8
  34. package/extension/src/content/scopedPersistenceCoordinator.js +100 -0
  35. package/extension/src/content/scopedPersistenceDetachedCommit.js +47 -0
  36. package/extension/src/content/scopedPersistencePanelState.js +111 -0
  37. package/extension/src/content/sessionManager.js +15 -12
  38. package/extension/src/content/sessionPersistence.js +67 -8
  39. package/extension/src/content/updateNotice.js +10 -4
  40. package/extension/src/content/writebackOrchestrator.js +89 -68
  41. package/extension/src/contentScript.js +5 -1
  42. package/extension/src/page/trackedChangesLifecycle.js +16 -5
  43. package/extension/src/page/writebackRouter.js +6 -2
  44. package/extension/src/pageBridge.js +47 -98
  45. package/extension/src/shared/agentTranscript.js +18 -8
  46. package/extension/src/shared/compatibility.js +1 -1
  47. package/extension/src/shared/governanceRules.js +1 -1
  48. package/extension/src/shared/i18n.js +2 -4
  49. package/extension/src/shared/managedUpdateProjection.js +337 -0
  50. package/extension/src/shared/nativeRequestIdentity.js +60 -0
  51. package/extension/src/shared/otText.js +0 -19
  52. package/extension/src/shared/pageRpcContract.js +136 -0
  53. package/extension/src/shared/runExecutionSnapshot.js +187 -0
  54. package/extension/src/shared/runExecutionSnapshotCodec.js +171 -0
  55. package/extension/src/shared/runInputQueue.js +81 -34
  56. package/extension/src/shared/scopedPersistenceBrowserAdapter.js +79 -0
  57. package/extension/src/shared/scopedPersistenceQueuePolicy.js +200 -0
  58. package/extension/src/shared/scopedPersistenceReceipt.js +60 -0
  59. package/extension/src/shared/scopedPersistenceTransaction.js +241 -0
  60. package/extension/src/shared/sessionState.js +110 -59
  61. package/extension/src/shared/settlementFacts.js +351 -0
  62. package/extension/src/shared/storageDb.js +25 -10
  63. package/extension/src/shared/storageMigration.js +275 -61
  64. package/extension/src/shared/storageSessionClaims.js +60 -0
  65. package/extension/src/shared/updateConsent.js +4 -0
  66. package/extension/src/shared/updateRevocationIntent.js +109 -0
  67. package/extension/src/shared/writebackEvidenceProjection.js +150 -0
  68. package/extension/src/shared/writebackOperationEvidence.js +153 -0
  69. package/extension/src/shared/writebackSettlement.js +727 -0
  70. package/extension/styles/panel.css +18 -8
  71. package/native-host/src/codexPromptAssembly.js +1 -1
  72. package/native-host/src/codexRuntimeIdentity.js +1 -1
  73. package/native-host/src/codexSessionRunner.js +7 -13
  74. package/native-host/src/index.js +3 -69
  75. package/native-host/src/managedInstall.js +50 -8
  76. package/native-host/src/nativeTransportEnvelope.js +148 -0
  77. package/native-host/src/taskRunnerRuntime.js +14 -84
  78. package/native-host/src/updateManager.js +36 -1
  79. 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.CodexOverleafSessionState = factory();
6
- }
7
- })(typeof globalThis !== 'undefined' ? globalThis : window, function sessionStateFactory() {
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: 'confirm',
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', 'confirm', 'auto']);
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
- if (!VALID_MODES.has(state.mode)) {
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: VALID_MODES.has(session.mode) ? session.mode : fallbackState.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(session.pendingInputs, options.restoreRunningRuns === true)
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 = VALID_MODES.has(active.mode) ? active.mode : DEFAULT_PANEL_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: VALID_MODES.has(overrides.mode) ? overrides.mode : DEFAULT_PANEL_STATE.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 !== undefined && !TERMINAL_TRACKED_CHANGE_STATUS.has(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: VALID_MODES.has(active?.mode) ? active.mode : normalizeMode(source.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
- return selectedRuns.map(run => compactRunForStorage(run, limits, undoRunIds.has(run.id)));
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
- return VALID_MODES.has(mode) ? mode : DEFAULT_PANEL_STATE.mode;
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
- const Queue = (typeof globalThis !== 'undefined' ? globalThis.CodexOverleafRunInputQueue : null)
1389
- || (typeof module === 'object' && module.exports ? require('./runInputQueue') : null);
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
+ });