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.
Files changed (189) hide show
  1. package/README.md +155 -392
  2. package/README.zh-CN.md +154 -393
  3. package/extension/bootstrap/background.js +52 -9
  4. package/extension/bootstrap/manifest.template.json +1 -1
  5. package/extension/bootstrap/popup.html +1 -0
  6. package/extension/bootstrap/popupFallback.js +55 -0
  7. package/extension/bootstrap/updateStatus.js +1 -0
  8. package/extension/src/background.js +283 -55
  9. package/extension/src/backgroundBootstrapMigration.js +181 -0
  10. package/extension/src/backgroundSharedSessions.js +129 -16
  11. package/extension/src/backgroundUpdateCoordinator.js +242 -29
  12. package/extension/src/backgroundUpdateSafety.js +173 -0
  13. package/extension/src/content/activeTurnControl.js +171 -8
  14. package/extension/src/content/applyResultFormatters.js +46 -73
  15. package/extension/src/content/assetTransferBroker.js +72 -14
  16. package/extension/src/content/composerAttachments.js +104 -47
  17. package/extension/src/content/composerPanel.js +110 -12
  18. package/extension/src/content/contentRuntime.js +1555 -926
  19. package/extension/src/content/contextTray.js +8 -2
  20. package/extension/src/content/contextUsageView.js +267 -0
  21. package/extension/src/content/diagnosticsController.js +122 -96
  22. package/extension/src/content/diagnosticsExport.js +19 -5
  23. package/extension/src/content/diagnosticsPanel.js +48 -0
  24. package/extension/src/content/directoryCleanupRecovery.js +54 -0
  25. package/extension/src/content/generated/content.bundle.js +11077 -3412
  26. package/extension/src/content/generated/content.bundle.meta.json +14 -3
  27. package/extension/src/content/globalPreferencesController.js +1 -1
  28. package/extension/src/content/legacyGlobalRegistry.js +10 -0
  29. package/extension/src/content/localSkillsPanel.js +104 -14
  30. package/extension/src/content/markdownDomRenderer.js +5 -3
  31. package/extension/src/content/markdownText.js +47 -11
  32. package/extension/src/content/modelPicker.js +8 -4
  33. package/extension/src/content/nativeCompatibilityController.js +74 -30
  34. package/extension/src/content/otWarmMirror.js +5 -6
  35. package/extension/src/content/otWarmMirrorController.js +17 -20
  36. package/extension/src/content/pageBridgeClient.js +17 -4
  37. package/extension/src/content/panelMaintenance.js +46 -14
  38. package/extension/src/content/panelRenderer.js +57 -27
  39. package/extension/src/content/pendingInputView.js +1 -1
  40. package/extension/src/content/projectProviderSelection.js +13 -4
  41. package/extension/src/content/projectSessionCleanup.js +109 -62
  42. package/extension/src/content/projectSettingsCoordinator.js +67 -6
  43. package/extension/src/content/providerSettingsCoordinator.js +46 -8
  44. package/extension/src/content/providerSettingsDialog.js +73 -14
  45. package/extension/src/content/recentProjects.js +15 -8
  46. package/extension/src/content/runActivitySummary.js +160 -18
  47. package/extension/src/content/runCompletionMeta.js +532 -0
  48. package/extension/src/content/runController.js +39 -15
  49. package/extension/src/content/runFailureNotice.js +29 -13
  50. package/extension/src/content/runGuidanceController.js +72 -27
  51. package/extension/src/content/runGuidanceView.js +59 -10
  52. package/extension/src/content/runPresence.js +18 -8
  53. package/extension/src/content/runQueueScheduler.js +157 -47
  54. package/extension/src/content/runRecoveryActions.js +147 -0
  55. package/extension/src/content/runResultActions.js +28 -143
  56. package/extension/src/content/runReviewController.js +633 -0
  57. package/extension/src/content/runScrollLayout.js +21 -2
  58. package/extension/src/content/runTimelineView.js +367 -175
  59. package/extension/src/content/scopedPersistencePanelState.js +134 -20
  60. package/extension/src/content/selectionContextView.js +1 -1
  61. package/extension/src/content/sensitivePreflight.js +153 -0
  62. package/extension/src/content/sessionForkController.js +80 -17
  63. package/extension/src/content/sessionManager.js +50 -4
  64. package/extension/src/content/sessionMenuView.js +54 -18
  65. package/extension/src/content/sessionPersistence.js +25 -5
  66. package/extension/src/content/settingsPanel.js +22 -5
  67. package/extension/src/content/settingsWorkbench.js +132 -89
  68. package/extension/src/content/sharedSessionViewSync.js +49 -11
  69. package/extension/src/content/subagentActivityView.js +23 -7
  70. package/extension/src/content/updateIdle.js +4 -1
  71. package/extension/src/content/updateNotice.js +118 -12
  72. package/extension/src/content/updateSafetyReceiver.js +58 -0
  73. package/extension/src/content/writebackController.js +53 -4
  74. package/extension/src/content/writebackOrchestrator.js +116 -94
  75. package/extension/src/content/writingStyleSettings.js +35 -11
  76. package/extension/src/page/binaryAssetUploader.js +136 -14
  77. package/extension/src/page/compileBridge.js +193 -180
  78. package/extension/src/page/fileMover.js +256 -0
  79. package/extension/src/page/overleafCapabilities.js +4 -1
  80. package/extension/src/page/ownedDirectoryCleanup.js +94 -0
  81. package/extension/src/page/referenceProjects.js +1 -0
  82. package/extension/src/page/reviewContext.js +113 -0
  83. package/extension/src/page/saveState.js +6 -4
  84. package/extension/src/page/snapshotRouter.js +84 -39
  85. package/extension/src/page/textCoordinates.js +17 -1
  86. package/extension/src/page/textFileCreator.js +114 -26
  87. package/extension/src/page/trackedChangeCapture.js +12 -2
  88. package/extension/src/page/trackedChangeOwnership.js +19 -1
  89. package/extension/src/page/trackedChangeReplay.js +41 -21
  90. package/extension/src/page/trackedChangesLifecycle.js +217 -199
  91. package/extension/src/page/treeOperations.js +7 -10
  92. package/extension/src/page/updateSafetyProbe.js +41 -0
  93. package/extension/src/page/writebackReceiptJournal.js +2 -0
  94. package/extension/src/page/writebackRouter.js +103 -155
  95. package/extension/src/pageBridge.js +126 -69
  96. package/extension/src/popup.js +62 -10
  97. package/extension/src/shared/agentTranscript.js +373 -17
  98. package/extension/src/shared/auditRecords.js +4 -0
  99. package/extension/src/shared/compatibility.js +22 -6
  100. package/extension/src/shared/compileAdapter.js +129 -66
  101. package/extension/src/shared/composerAttachmentDraft.js +156 -0
  102. package/extension/src/shared/contextUsage.js +125 -0
  103. package/extension/src/shared/directoryCleanupProof.js +43 -0
  104. package/extension/src/shared/failureReasons.js +349 -4
  105. package/extension/src/shared/globalPreferences.js +30 -13
  106. package/extension/src/shared/governanceRules.js +4 -37
  107. package/extension/src/shared/i18n.js +590 -24
  108. package/extension/src/shared/lineReferences.js +38 -31
  109. package/extension/src/shared/managedUpdateProjection.js +2 -2
  110. package/extension/src/shared/nativeRequestIdentity.js +1 -0
  111. package/extension/src/shared/pageRpcContract.js +5 -4
  112. package/extension/src/shared/panelIcons.js +39 -0
  113. package/extension/src/shared/projectFiles.js +21 -44
  114. package/extension/src/shared/providerProfiles.js +5 -3
  115. package/extension/src/shared/reviewing.js +130 -1
  116. package/extension/src/shared/runActivityModel.js +70 -4
  117. package/extension/src/shared/runExecutionSnapshot.js +13 -0
  118. package/extension/src/shared/runInputQueue.js +48 -5
  119. package/extension/src/shared/selectionContext.js +12 -3
  120. package/extension/src/shared/sessionState.js +132 -28
  121. package/extension/src/shared/sessionTitle.js +14 -1
  122. package/extension/src/shared/settlementFacts.js +10 -2
  123. package/extension/src/shared/sharedSessionBridge.js +118 -27
  124. package/extension/src/shared/sharedSessionTransfer.js +140 -0
  125. package/extension/src/shared/staleGuard.js +166 -52
  126. package/extension/src/shared/storageDb.js +90 -26
  127. package/extension/src/shared/storageMigration.js +78 -112
  128. package/extension/src/shared/storageRunActions.js +267 -30
  129. package/extension/src/shared/subagentActivityModel.js +1 -1
  130. package/extension/src/shared/undoOperations.js +253 -32
  131. package/extension/src/shared/updateConsent.js +6 -4
  132. package/extension/src/shared/updateRuntimeIdentity.js +17 -3
  133. package/extension/src/shared/writebackIntent.js +16 -0
  134. package/extension/src/shared/writebackOperationEvidence.js +4 -15
  135. package/extension/src/shared/writebackSettlement.js +58 -40
  136. package/extension/styles/panel.css +1123 -493
  137. package/native-host/src/anthropicBridgeRequest.js +4 -2
  138. package/native-host/src/anthropicBridgeResponse.js +6 -2
  139. package/native-host/src/anthropicMessagesBridge.js +24 -16
  140. package/native-host/src/bootstrapMigration.js +296 -0
  141. package/native-host/src/bootstrapMigrationContract.js +101 -0
  142. package/native-host/src/bootstrapUpdate.js +269 -0
  143. package/native-host/src/bootstrapUpdateWatchdog.js +44 -0
  144. package/native-host/src/chatBridgeRequest.js +60 -6
  145. package/native-host/src/chatBridgeResponse.js +20 -1
  146. package/native-host/src/chatCompletionsBridge.js +48 -15
  147. package/native-host/src/codexConfigPaths.js +106 -0
  148. package/native-host/src/codexHome.js +66 -73
  149. package/native-host/src/codexHomeScope.js +124 -0
  150. package/native-host/src/codexInheritedAuth.js +90 -0
  151. package/native-host/src/codexLocalSkills.js +301 -0
  152. package/native-host/src/codexPromptAssembly.js +114 -127
  153. package/native-host/src/codexProviderTest.js +24 -26
  154. package/native-host/src/codexSessionRunner.js +234 -198
  155. package/native-host/src/codexSessionTiming.js +18 -8
  156. package/native-host/src/codexSkillIsolation.js +170 -0
  157. package/native-host/src/codexThreadFork.js +19 -3
  158. package/native-host/src/index.js +19 -7
  159. package/native-host/src/managedInstall.js +110 -35
  160. package/native-host/src/managedInstallRecovery.js +140 -0
  161. package/native-host/src/managedRuntimeEvidence.js +57 -0
  162. package/native-host/src/mirrorWorkspace.js +92 -22
  163. package/native-host/src/nativeDoctor.js +15 -0
  164. package/native-host/src/nativeHelperLifecycle.js +21 -0
  165. package/native-host/src/nativeHostRegistration.js +33 -0
  166. package/native-host/src/nativeRunLifecycle.js +71 -0
  167. package/native-host/src/processStopBarrier.js +42 -7
  168. package/native-host/src/projectPathPlatform.js +30 -0
  169. package/native-host/src/providerContextError.js +20 -0
  170. package/native-host/src/providerHttp.js +80 -0
  171. package/native-host/src/providerProfile.js +4 -2
  172. package/native-host/src/providerReasoning.js +2 -7
  173. package/native-host/src/providerRuntime.js +1 -0
  174. package/native-host/src/providerStore.js +72 -14
  175. package/native-host/src/responsesPassthroughBridge.js +266 -52
  176. package/native-host/src/runtimeInstaller.js +21 -1
  177. package/native-host/src/selectionScope.js +36 -8
  178. package/native-host/src/subagentAdmission.js +26 -0
  179. package/native-host/src/subagentBroker.js +79 -66
  180. package/native-host/src/subagentTelemetry.js +19 -10
  181. package/native-host/src/subagentWorkspacePath.js +5 -2
  182. package/native-host/src/taskRunnerRuntime.js +92 -57
  183. package/native-host/src/textPatch.js +473 -377
  184. package/native-host/src/tomlStatements.js +140 -0
  185. package/native-host/src/updateManager.js +409 -46
  186. package/native-host/src/writingStyleRuntime.js +57 -29
  187. package/native-host/src/writingStyleStore.js +73 -0
  188. package/package.json +5 -8
  189. package/scripts/install-managed.mjs +1 -0
@@ -1,18 +1,16 @@
1
1
  (function initStorageRunActions(root, factory) {
2
2
  if (typeof module === 'object' && module.exports) {
3
- module.exports = factory(require('./selectionContext'), require('./writebackIntent'), require('./writingStyle'));
3
+ module.exports = factory(require('./selectionContext'), require('./writebackIntent'), require('./writingStyle'), require('./directoryCleanupProof'), require('./undoOperations'));
4
4
  } else {
5
- root.CodexOverleafModuleRegistry.define('StorageRunActions', ['SelectionContext', 'WritebackIntent', 'WritingStyle'], factory);
5
+ root.CodexOverleafModuleRegistry.define('StorageRunActions', ['SelectionContext', 'WritebackIntent', 'WritingStyle', 'DirectoryCleanupProof', 'UndoOperations'], factory);
6
6
  }
7
- })(typeof globalThis !== 'undefined' ? globalThis : window, function storageRunActionsFactory(SelectionContext, WritebackIntent, WritingStyle) {
7
+ })(typeof globalThis !== 'undefined' ? globalThis : window, function storageRunActionsFactory(SelectionContext, WritebackIntent, WritingStyle, DirectoryCleanupProof, UndoOperations) {
8
8
  'use strict';
9
9
 
10
- var MAX_PERSISTED_ACTION_RUNS_PER_SESSION = 2;
11
- // Undo/Accept need whole pre- and post-images of every written file, so one
12
- // run that edits a 120 KB roadmap and creates a 100 KB chapter already needs
13
- // ~560 KB. The old 320 KB cap silently dropped the whole payload and the
14
- // buttons vanished after a reload. Two runs per session keep this bounded.
15
- var MAX_PERSISTED_ACTION_BYTES_PER_RUN = 2 * 1024 * 1024;
10
+ // Share a bounded budget across the retained history, rather than expiring
11
+ // otherwise valid recovery data after two edits. The session builder also
12
+ // reserves room for display metadata within the shared transport limit.
13
+ var MAX_PERSISTED_ACTION_BYTES_PER_SESSION = 16 * 1024 * 1024;
16
14
 
17
15
  function compactRunsForStorage(runs, options, maxRuns, compactRun) {
18
16
  if (typeof compactRun !== 'function') {
@@ -21,22 +19,100 @@
21
19
  var selectedRuns = (Array.isArray(runs) ? runs : [])
22
20
  .filter(function (run) { return run && typeof run.id === 'string'; })
23
21
  .slice(-Math.max(0, Number(maxRuns) || 0));
24
- var actionRunIds = new Set();
22
+ var payloads = new Map();
23
+ var remaining = Math.min(MAX_PERSISTED_ACTION_BYTES_PER_SESSION,
24
+ Number.isFinite(options?.maxActionPayloadBytes) ? Math.max(0, options.maxActionPayloadBytes) : Infinity);
25
+ var usedBytes = 0, usedChars = 0;
26
+ var fits = (bytes, chars) => bytes <= remaining && (typeof options?.canRetainActionPayload !== 'function'
27
+ || options.canRetainActionPayload(usedBytes + bytes, usedChars + chars));
25
28
  if (options && options.preserveRunActionPayload === true) {
26
- for (var index = selectedRuns.length - 1;
27
- index >= 0 && actionRunIds.size < MAX_PERSISTED_ACTION_RUNS_PER_SESSION;
28
- index -= 1) {
29
- if (hasReloadableRunActionPayload(selectedRuns[index])) {
30
- actionRunIds.add(selectedRuns[index].id);
29
+ for (var index = selectedRuns.length - 1; index >= 0; index -= 1) {
30
+ var run = selectedRuns[index];
31
+ if (!hasReloadableRunActionPayload(withoutDirectoryFacts(run))) continue;
32
+ var payload = compactRunActionPayload(run, true, { includeCaptures: false, includeDirectoryCleanup: false });
33
+ if (payload.recoveryRetention?.state === 'omitted') { payloads.set(run.id, payload); continue; }
34
+ var serialized = JSON.stringify(payload);
35
+ var bytes = getUtf8ByteLength(serialized);
36
+ if (!fits(bytes, serialized.length)) {
37
+ payloads.set(run.id, { ...emptyActionPayload(), recoveryRetention: omitted('session-budget', bytes) });
38
+ continue;
39
+ }
40
+ remaining -= bytes;
41
+ usedBytes += bytes; usedChars += serialized.length;
42
+ payloads.set(run.id, payload);
43
+ }
44
+ // Captures are optional. They must not crowd out any retained core data.
45
+ for (var run of selectedRuns.slice().reverse()) {
46
+ var payload = payloads.get(run.id);
47
+ if (!payload || payload.recoveryRetention?.state === 'omitted'
48
+ || run.undoStatus === 'applied' || ['accepted', 'rejected'].includes(run.trackedChangeStatus)) continue;
49
+ var captures = cloneSerializableArray(run.trackedChangeCaptures);
50
+ var extraText = captures.length ? JSON.stringify({ trackedChangeCaptures: captures }) : '';
51
+ var extraBytes = getUtf8ByteLength(extraText);
52
+ if (extraBytes && fits(extraBytes, extraText.length)) {
53
+ payload.trackedChangeCaptures = captures;
54
+ remaining -= extraBytes;
55
+ usedBytes += extraBytes; usedChars += extraText.length;
31
56
  }
32
57
  }
33
58
  }
59
+ // Passive directory history uses only the space left after all original
60
+ // recovery payloads and optional captures have been selected.
61
+ var directoryRemaining = Number.isFinite(options?.maxOptionalDirectoryBytes)
62
+ ? Math.max(0, options.maxOptionalDirectoryBytes) : Infinity;
34
63
  return selectedRuns.map(function (run) {
35
- return compactRun(run, actionRunIds.has(run.id));
64
+ var payload = payloads.get(run.id) || compactRunActionPayload(run, false, { includeDirectoryCleanup: false });
65
+ if (options?.deferDirectoryCleanup !== true) payload = appendDirectoryFacts(payload, run,
66
+ options?.preserveRunActionPayload === true, function (bytes, chars) {
67
+ if (bytes > directoryRemaining || !fits(bytes, chars)) return false;
68
+ directoryRemaining -= bytes; remaining -= bytes;
69
+ usedBytes += bytes; usedChars += chars;
70
+ return true;
71
+ }, 'session-budget');
72
+ return compactRun(run, payloads.has(run.id) && !coreWasOmitted(payload), payload);
36
73
  });
37
74
  }
38
75
 
76
+ // Keep the complete display history while allocating finite recovery space
77
+ // across the current project. A record plus its previous merge baseline is
78
+ // transported together; a readable batch must also fit the response bound.
79
+ function compactSessionBatchActions(records, options = {}, buildSessionRecord, SharedSessionTransfer) {
80
+ var budget = SharedSessionTransfer.MAX_RESPONSE_BYTES - 64 * 1024;
81
+ if (SharedSessionTransfer.byteLength(JSON.stringify(records)) <= budget) return records;
82
+ var summaries = records.map(record => buildSessionRecord(record, { preserveRunActionPayload: false, deferDirectoryCleanup: true }));
83
+ var remaining = Math.max(0, budget - SharedSessionTransfer.byteLength(JSON.stringify(summaries)));
84
+ var allocated = new Map();
85
+ var ordered = records.slice().sort((a, b) => Number(b.id === options.activeSessionId) - Number(a.id === options.activeSessionId)
86
+ || (Date.parse(b.updatedAt) || 0) - (Date.parse(a.updatedAt) || 0));
87
+ for (var record of ordered) {
88
+ var summary = summaries.find(item => item.id === record.id);
89
+ var next = buildSessionRecord(record, { preserveRunActionPayload: true, maxActionPayloadBytes: remaining, deferDirectoryCleanup: true });
90
+ remaining -= Math.max(0, SharedSessionTransfer.byteLength(JSON.stringify(next)) - SharedSessionTransfer.byteLength(JSON.stringify(summary)));
91
+ allocated.set(record.id, next);
92
+ }
93
+ // Do not let an active session's optional directory facts evict another
94
+ // session's existing Undo core under the shared response bound.
95
+ for (var record of ordered) {
96
+ var core = allocated.get(record.id);
97
+ var sourceRuns = new Map((record.runs || []).map(run => [run.id, run]));
98
+ var withFacts = { ...core, runs: core.runs.map(run => {
99
+ var source = sourceRuns.get(run.id), next = { ...run };
100
+ if (source?.pendingDirectoryCleanup) next.pendingDirectoryCleanup = source.pendingDirectoryCleanup;
101
+ if (source?.recoveryRetention?.directoryCleanup === true) next.recoveryRetention = {
102
+ ...(next.recoveryRetention || { ...source.recoveryRetention, coreRetained: true }), directoryCleanup: true };
103
+ return next;
104
+ }) };
105
+ var enriched = buildSessionRecord(withFacts, { preserveRunActionPayload: true, maxOptionalDirectoryBytes: remaining });
106
+ remaining -= Math.max(0, SharedSessionTransfer.byteLength(JSON.stringify(enriched)) - SharedSessionTransfer.byteLength(JSON.stringify(core)));
107
+ allocated.set(record.id, enriched);
108
+ }
109
+ return records.map(record => allocated.get(record.id));
110
+ }
111
+
39
112
  function hasReloadableRunActionPayload(run) {
113
+ if (!run || run.forkSnapshot === true) return false;
114
+ if (runDirectoryCleanup(run)) return true;
115
+ if (run.undoStatus === 'applied' || ['accepted', 'rejected'].includes(run.trackedChangeStatus)) return false;
40
116
  var trackedStatus = run && run.trackedChangeStatus;
41
117
  var trackedLifecycle = (trackedStatus === 'pending' || trackedStatus === 'needs_review')
42
118
  && Array.isArray(run.undoTrackedChanges) && run.undoTrackedChanges.length > 0
@@ -53,32 +129,99 @@
53
129
  && Array.isArray(run.undoExpectedFiles) && run.undoExpectedFiles.length > 0
54
130
  && Array.isArray(run.appliedOperations) && run.appliedOperations.length > 0;
55
131
  var legacyUndo = Array.isArray(run && run.undoOperations) && run.undoOperations.length > 0;
56
- return trackedLifecycle || reviewingEditorUndo || legacyUndo;
132
+ return trackedLifecycle || reviewingEditorUndo || legacyUndo || UndoOperations.getUndoUnavailableFiles(run).length > 0;
133
+ }
134
+
135
+ function runDirectoryCleanup(run) {
136
+ var proof = DirectoryCleanupProof.normalize(run?.pendingDirectoryCleanup);
137
+ return proof && proof.runId === run.id && proof.projectId === run.runProjectId ? proof : null;
138
+ }
139
+
140
+ function omitted(reason, bytes = 0) {
141
+ return { state: 'omitted', reason, bytes };
142
+ }
143
+
144
+ function withoutDirectoryFacts(run) {
145
+ var core = { ...run };
146
+ delete core.pendingDirectoryCleanup;
147
+ if (core.recoveryRetention?.directoryCleanup === true) {
148
+ if (core.recoveryRetention.coreRetained === true) delete core.recoveryRetention;
149
+ else {
150
+ core.recoveryRetention = { ...core.recoveryRetention };
151
+ delete core.recoveryRetention.directoryCleanup;
152
+ delete core.recoveryRetention.coreRetained;
153
+ }
154
+ }
155
+ return core;
156
+ }
157
+
158
+ function coreWasOmitted(payload) {
159
+ return payload.recoveryRetention?.state === 'omitted' && payload.recoveryRetention.coreRetained !== true;
160
+ }
161
+
162
+ function appendDirectoryFacts(payload, run, keep, fits, reason) {
163
+ if (run?.forkSnapshot === true) return payload;
164
+ var directory = runDirectoryCleanup(run), previous = run?.recoveryRetention;
165
+ if (!directory && previous?.directoryCleanup !== true) return payload;
166
+ if (directory && keep) {
167
+ var extended = { ...payload, pendingDirectoryCleanup: directory };
168
+ var before = JSON.stringify(payload), after = JSON.stringify(extended);
169
+ if (fits(getUtf8ByteLength(after) - getUtf8ByteLength(before), after.length - before.length)) return extended;
170
+ }
171
+ // The existing omission marker distinguishes optional history from lost
172
+ // recovery authority. Never imply that a retained Undo core was discarded.
173
+ var retention = coreWasOmitted(payload) ? payload.recoveryRetention
174
+ : omitted(!keep ? 'fallback' : directory ? reason : previous.reason,
175
+ directory ? getUtf8ByteLength(JSON.stringify(directory)) : previous.bytes);
176
+ return { ...payload, recoveryRetention: { ...retention, directoryCleanup: true,
177
+ ...(!coreWasOmitted(payload) ? { coreRetained: true } : {}) } };
178
+ }
179
+
180
+ function compactRunActionPayload(run, keepActionPayload, options = {}) {
181
+ var payload = compactCoreActionPayload(withoutDirectoryFacts(run), keepActionPayload, options);
182
+ if (options.includeDirectoryCleanup === false) return payload;
183
+ var serialized = JSON.stringify(payload);
184
+ return appendDirectoryFacts(payload, run, keepActionPayload,
185
+ bytes => getUtf8ByteLength(serialized) + bytes <= MAX_PERSISTED_ACTION_BYTES_PER_SESSION, 'run-budget');
57
186
  }
58
187
 
59
- function compactRunActionPayload(run, keepActionPayload) {
60
- var empty = emptyActionPayload();
188
+ function compactCoreActionPayload(run, keepActionPayload, options = {}) {
189
+ var empty = { ...emptyActionPayload(), appliedOperations: UndoOperations.compactUnavailableUndoOperations(run) };
61
190
  if (!keepActionPayload || !hasReloadableRunActionPayload(run)) {
191
+ if (hasReloadableRunActionPayload(run)) empty.recoveryRetention = omitted('fallback');
192
+ else if (run?.recoveryRetention?.state === 'omitted' && !run.forkSnapshot
193
+ && run.undoStatus !== 'applied' && !['accepted', 'rejected'].includes(run.trackedChangeStatus)) {
194
+ empty.recoveryRetention = omitted(
195
+ ['run-budget', 'session-budget', 'fallback', 'invalid'].includes(run.recoveryRetention.reason)
196
+ ? run.recoveryRetention.reason : 'invalid', Math.max(0, Number(run.recoveryRetention.bytes) || 0));
197
+ }
62
198
  return empty;
63
199
  }
64
- var payload = {
200
+ var terminal = run.undoStatus === 'applied' || ['accepted', 'rejected'].includes(run.trackedChangeStatus);
201
+ var payload = terminal ? emptyActionPayload() : {
65
202
  appliedOperations: cloneSerializableArray(run.appliedOperations),
66
203
  undoOperations: cloneSerializableArray(run.undoOperations),
67
204
  undoBaseFiles: cloneSerializableArray(run.undoBaseFiles),
68
205
  undoTrackedChanges: cloneSerializableArray(run.undoTrackedChanges),
69
206
  undoExpectedFiles: cloneSerializableArray(run.undoExpectedFiles)
70
207
  };
208
+ if (!terminal && Object.keys(payload).some(key => run[key]?.length && !payload[key].length)) {
209
+ return { ...empty, recoveryRetention: omitted('invalid') };
210
+ }
71
211
  var serialized;
72
212
  try {
73
213
  serialized = JSON.stringify(payload);
74
214
  } catch (_error) {
75
- return empty;
215
+ return { ...empty, recoveryRetention: omitted('invalid') };
76
216
  }
77
- if (getUtf8ByteLength(serialized) > MAX_PERSISTED_ACTION_BYTES_PER_RUN) return empty;
217
+ var bytes = getUtf8ByteLength(serialized);
218
+ if (bytes > MAX_PERSISTED_ACTION_BYTES_PER_SESSION) return { ...empty,
219
+ recoveryRetention: omitted('run-budget', bytes) };
220
+ if (options.includeCaptures === false || terminal) return payload;
78
221
  const captures = cloneSerializableArray(run.trackedChangeCaptures);
79
222
  if (captures.length) {
80
223
  const extended = { ...payload, trackedChangeCaptures: captures };
81
- if (getUtf8ByteLength(JSON.stringify(extended)) <= MAX_PERSISTED_ACTION_BYTES_PER_RUN) return extended;
224
+ if (getUtf8ByteLength(JSON.stringify(extended)) <= MAX_PERSISTED_ACTION_BYTES_PER_SESSION) return extended;
82
225
  }
83
226
  return payload; // Optional capture evidence must never evict a mature Undo payload.
84
227
  }
@@ -165,21 +308,76 @@
165
308
  return out;
166
309
  }
167
310
 
168
- // Review terminal states are monotonic even when a later session snapshot is stale.
169
- function mergeSessionReviewState(incoming, existing, reviewRunIds) {
311
+ const REVIEW_FIELDS = ['trackedChangeStatus', 'undoStatus', 'undoOwnerProtocol', 'undoTrackedChanges', 'undoExpectedFiles',
312
+ 'undoOperations', 'undoBaseFiles', 'appliedOperations', 'trackedChangeCaptures', 'recoveryRetention',
313
+ 'interruptedDraft', 'retryWriteback', 'saveCheck'];
314
+ const REVIEW_FACT_FIELDS = ['documentEffect', 'fileSettlements', 'failures', 'recoveryAvailability', 'recoveryDisposition'];
315
+ const terminalReview = run => ['accepted', 'rejected'].includes(run?.trackedChangeStatus) || run?.undoStatus === 'applied';
316
+ const factsOf = run => run?.settlement || run?.settlementFacts || {};
317
+
318
+ function copyRunReviewState(target, source) {
319
+ const merged = { ...target };
320
+ const copyFields = (into, from, fields) => {
321
+ for (const field of fields) {
322
+ if (Object.hasOwn(from, field)) into[field] = JSON.parse(JSON.stringify(from[field]));
323
+ else delete into[field];
324
+ }
325
+ };
326
+ copyFields(merged, source, REVIEW_FIELDS);
327
+ const sourceFacts = factsOf(source), targetFacts = factsOf(target);
328
+ const facts = { ...targetFacts };
329
+ copyFields(facts, sourceFacts, REVIEW_FACT_FIELDS);
330
+ const sourceEvidence = sourceFacts.evidence || {};
331
+ const evidence = { ...(targetFacts.evidence || {}) };
332
+ copyFields(evidence, sourceEvidence, [...new Set([...Object.keys(evidence), ...Object.keys(sourceEvidence)])]
333
+ .filter(key => !['mirrored', 'compiled'].includes(key)));
334
+ if (Object.keys(evidence).length) facts.evidence = evidence; else delete facts.evidence;
335
+ delete merged.settlementFacts;
336
+ if (Object.keys(facts).length) merged.settlement = facts; else delete merged.settlement;
337
+ return merged;
338
+ }
339
+
340
+ function reviewStateSnapshot(run) {
341
+ const facts = factsOf(run);
342
+ return JSON.stringify([REVIEW_FIELDS.map(key => run?.[key]), REVIEW_FACT_FIELDS.map(key => facts[key]),
343
+ Object.entries(facts.evidence || {}).filter(([key]) => !['mirrored', 'compiled'].includes(key))
344
+ .sort(([a], [b]) => a.localeCompare(b))]);
345
+ }
346
+
347
+ // Shared submissions retain the caller's envelope and private baseline. The
348
+ // legacy site-local path explicitly keeps its historical timestamp merge.
349
+ function mergeSessionReviewState(incoming, existing, reviewRunIds, options = {}) {
170
350
  if (!existing || incoming.id !== existing.id || incoming.projectId !== existing.projectId
171
351
  || !incoming.accountScopeId || incoming.accountScopeId !== existing.accountScopeId) return incoming;
172
- var terminal = run => ['accepted', 'rejected'].includes(run?.trackedChangeStatus);
352
+ var terminal = terminalReview;
173
353
  var incomingRuns = new Map((incoming.runs || []).map(run => [run.id, run]));
174
354
  var existingRuns = new Map((existing.runs || []).map(run => [run.id, run]));
175
355
  var requestedIds = new Set(reviewRunIds || []);
176
- var explicitReview = Array.from(requestedIds).some(id =>
177
- ['accepted', 'rejected', 'needs_review'].includes(incomingRuns.get(id)?.trackedChangeStatus));
356
+ var explicitReview = Array.from(requestedIds).some(id => {
357
+ var run = incomingRuns.get(id);
358
+ return ['accepted', 'rejected', 'needs_review'].includes(run?.trackedChangeStatus)
359
+ || ['applied', 'partial'].includes(run?.undoStatus);
360
+ });
361
+ if (options.shared !== false) {
362
+ const runs = (incoming.runs || []).map(run => {
363
+ const previous = existingRuns.get(run.id);
364
+ const source = terminal(previous) ? previous : null;
365
+ if (!source || (source.runProjectId && source.runProjectId !== incoming.projectId)) return run;
366
+ // The same decision can still gain save/recovery evidence. Let the
367
+ // three-way merge retain those edits and reject genuine collisions.
368
+ if (run.trackedChangeStatus === source.trackedChangeStatus && run.undoStatus === source.undoStatus) return run;
369
+ // An absent local settlement has no edits to its metadata or auxiliary
370
+ // evidence. Retain the stored container as well as the terminal fields.
371
+ const target = !run.settlement && !run.settlementFacts ? { ...run, settlement: factsOf(source) } : run;
372
+ return copyRunReviewState(target, source);
373
+ });
374
+ return { ...incoming, runs };
375
+ }
178
376
  var newer = (Date.parse(incoming.updatedAt) || 0) >= (Date.parse(existing.updatedAt) || 0);
179
377
  if (!newer && !explicitReview) return incoming;
180
378
  var base = newer ? incoming : existing;
181
- var fields = ['trackedChangeStatus', 'undoStatus', 'undoTrackedChanges', 'undoExpectedFiles',
182
- 'undoOperations', 'undoBaseFiles', 'appliedOperations', 'trackedChangeCaptures', 'settlement', 'settlementFacts'];
379
+ var fields = ['trackedChangeStatus', 'undoStatus', 'undoOwnerProtocol', 'undoTrackedChanges', 'undoExpectedFiles',
380
+ 'undoOperations', 'undoBaseFiles', 'appliedOperations', 'trackedChangeCaptures', 'recoveryRetention', 'settlement', 'settlementFacts'];
183
381
  var changed = false;
184
382
  var runs = (base.runs || []).map(function (run) {
185
383
  var previous = existingRuns.get(run.id), candidate = incomingRuns.get(run.id);
@@ -218,9 +416,48 @@
218
416
  return (hash >>> 0).toString(16).padStart(8, '0');
219
417
  }
220
418
 
419
+ // A successful durable write can omit an oversized checkpoint while the
420
+ // current tab still has it. Warn only the view and exact action revision
421
+ // that was saved; never mutate or remove its in-memory recovery material.
422
+ function projectRetentionUpdates({ liveState, capturedState, written, capturedScope, currentScope } = {}) {
423
+ if (!capturedScope?.accountScopeId || !capturedScope.projectId
424
+ || !Number.isSafeInteger(capturedScope.generation)
425
+ || ['accountScopeId', 'projectId', 'generation'].some(key => capturedScope[key] !== currentScope?.[key])) return [];
426
+ var fields = ['appliedOperations', 'undoOperations', 'undoBaseFiles', 'undoTrackedChanges',
427
+ 'undoExpectedFiles', 'undoStatus', 'trackedChangeStatus', 'pendingDirectoryCleanup'];
428
+ var fingerprint = run => JSON.stringify(fields.map(field => run[field]));
429
+ var updates = [];
430
+ for (var record of written || []) {
431
+ if (record.accountScopeId !== capturedScope.accountScopeId || record.projectId !== capturedScope.projectId) continue;
432
+ var live = liveState?.sessions?.find(session => session.id === record.id);
433
+ var captured = capturedState?.sessions?.find(session => session.id === record.id);
434
+ for (var storedRun of record.runs || []) {
435
+ var run = live?.runs?.find(run => run.id === storedRun.id);
436
+ var original = captured?.runs?.find(run => run.id === storedRun.id);
437
+ if (!run || !original || run.runProjectId !== capturedScope.projectId
438
+ || original.runProjectId !== capturedScope.projectId || !hasReloadableRunActionPayload(run)) continue;
439
+ try {
440
+ if (fingerprint(run) !== fingerprint(original)) continue;
441
+ if (storedRun.recoveryRetention?.state === 'omitted') updates.push({ runId: run.id, retention: { ...storedRun.recoveryRetention } });
442
+ else if (fingerprint(storedRun) === fingerprint(original)) updates.push({ runId: run.id, retention: null });
443
+ } catch (_error) { /* Invalid data cannot establish a revision. */ }
444
+ }
445
+ }
446
+ return updates;
447
+ }
448
+
449
+ function projectRetentionWarnings(input) {
450
+ return projectRetentionUpdates(input).filter(update => update.retention).map(update => update.runId);
451
+ }
452
+
221
453
  return {
454
+ projectRetentionUpdates: projectRetentionUpdates,
455
+ projectRetentionWarnings: projectRetentionWarnings,
456
+ compactSessionBatchActions: compactSessionBatchActions,
222
457
  compactStructuredEventValue: compactStructuredEventValue,
223
458
  mergeSessionReviewState: mergeSessionReviewState,
459
+ copyRunReviewState: copyRunReviewState,
460
+ reviewStateSnapshot: reviewStateSnapshot,
224
461
  compactRunsForStorage: compactRunsForStorage,
225
462
  compactRunActionPayload: compactRunActionPayload,
226
463
  compactProviderSnapshot: compactProviderSnapshot,
@@ -132,7 +132,7 @@
132
132
  if (typeof value === 'string' && value) agent[field] = clean(value, sanitize, field === 'task' ? 4096 : 160);
133
133
  }
134
134
  agent.clipped ||= detail.historyClipped === true;
135
- agent.historyUnavailable ||= detail.historyUnavailable === true;
135
+ if (typeof detail.historyUnavailable === 'boolean') agent.historyUnavailable = detail.historyUnavailable;
136
136
  let next = type.endsWith('.queued') ? 'pending' : type.endsWith('.started') ? 'running'
137
137
  : type.endsWith('.completed') ? 'completed' : type.endsWith('.rejected') ? 'failed'
138
138
  : type.endsWith('.failed') ? (detail.status === 'cancelled' ? 'cancelled' : 'failed') : detail.status;