codex-overleaf-link 2.5.0 → 2.5.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 +38 -26
- package/README.zh-CN.md +39 -27
- package/extension/bootstrap/background.js +52 -9
- package/extension/bootstrap/manifest.template.json +1 -1
- package/extension/bootstrap/popup.html +1 -0
- package/extension/bootstrap/popupFallback.js +55 -0
- package/extension/bootstrap/updateStatus.js +1 -0
- package/extension/src/background.js +206 -53
- package/extension/src/backgroundBootstrapMigration.js +181 -0
- package/extension/src/backgroundSharedSessions.js +74 -14
- package/extension/src/backgroundUpdateCoordinator.js +118 -21
- package/extension/src/backgroundUpdateSafety.js +173 -0
- package/extension/src/content/activeTurnControl.js +8 -2
- package/extension/src/content/applyResultFormatters.js +46 -73
- package/extension/src/content/assetTransferBroker.js +72 -14
- package/extension/src/content/composerAttachments.js +93 -45
- package/extension/src/content/composerPanel.js +18 -0
- package/extension/src/content/contentRuntime.js +661 -676
- package/extension/src/content/diagnosticsController.js +122 -96
- package/extension/src/content/diagnosticsExport.js +19 -5
- package/extension/src/content/diagnosticsPanel.js +30 -0
- package/extension/src/content/directoryCleanupRecovery.js +54 -0
- package/extension/src/content/generated/content.bundle.js +6879 -2786
- package/extension/src/content/generated/content.bundle.meta.json +11 -3
- package/extension/src/content/globalPreferencesController.js +1 -1
- package/extension/src/content/legacyGlobalRegistry.js +6 -0
- package/extension/src/content/localSkillsPanel.js +102 -12
- package/extension/src/content/markdownText.js +5 -6
- package/extension/src/content/modelPicker.js +7 -4
- package/extension/src/content/nativeCompatibilityController.js +45 -20
- package/extension/src/content/otWarmMirror.js +4 -5
- package/extension/src/content/otWarmMirrorController.js +17 -20
- package/extension/src/content/pageBridgeClient.js +15 -2
- package/extension/src/content/panelMaintenance.js +40 -12
- package/extension/src/content/panelRenderer.js +6 -0
- package/extension/src/content/projectProviderSelection.js +13 -4
- package/extension/src/content/projectSessionCleanup.js +109 -62
- package/extension/src/content/projectSettingsCoordinator.js +39 -2
- package/extension/src/content/providerSettingsCoordinator.js +46 -8
- package/extension/src/content/providerSettingsDialog.js +71 -13
- package/extension/src/content/recentProjects.js +11 -5
- package/extension/src/content/runActivitySummary.js +40 -6
- package/extension/src/content/runCompletionMeta.js +344 -0
- package/extension/src/content/runController.js +37 -14
- package/extension/src/content/runFailureNotice.js +21 -12
- package/extension/src/content/runGuidanceController.js +72 -27
- package/extension/src/content/runGuidanceView.js +59 -10
- package/extension/src/content/runQueueScheduler.js +148 -42
- package/extension/src/content/runRecoveryActions.js +146 -0
- package/extension/src/content/runResultActions.js +8 -125
- package/extension/src/content/runReviewController.js +574 -0
- package/extension/src/content/runScrollLayout.js +21 -2
- package/extension/src/content/runTimelineView.js +146 -153
- package/extension/src/content/scopedPersistencePanelState.js +51 -6
- package/extension/src/content/selectionContextView.js +1 -1
- package/extension/src/content/sensitivePreflight.js +140 -0
- package/extension/src/content/sessionForkController.js +80 -17
- package/extension/src/content/sessionManager.js +1 -1
- package/extension/src/content/settingsPanel.js +7 -3
- package/extension/src/content/settingsWorkbench.js +5 -0
- package/extension/src/content/sharedSessionViewSync.js +45 -8
- package/extension/src/content/updateIdle.js +4 -1
- package/extension/src/content/updateNotice.js +60 -8
- package/extension/src/content/updateSafetyReceiver.js +58 -0
- package/extension/src/content/writebackController.js +53 -4
- package/extension/src/content/writebackOrchestrator.js +82 -73
- package/extension/src/content/writingStyleSettings.js +35 -11
- package/extension/src/page/binaryAssetUploader.js +136 -14
- package/extension/src/page/fileMover.js +256 -0
- package/extension/src/page/ownedDirectoryCleanup.js +94 -0
- package/extension/src/page/referenceProjects.js +1 -0
- package/extension/src/page/reviewContext.js +113 -0
- package/extension/src/page/saveState.js +6 -4
- package/extension/src/page/snapshotRouter.js +84 -39
- package/extension/src/page/textFileCreator.js +105 -25
- package/extension/src/page/trackedChangeCapture.js +12 -2
- package/extension/src/page/trackedChangeOwnership.js +19 -1
- package/extension/src/page/trackedChangeReplay.js +41 -21
- package/extension/src/page/trackedChangesLifecycle.js +217 -199
- package/extension/src/page/treeOperations.js +7 -10
- package/extension/src/page/updateSafetyProbe.js +41 -0
- package/extension/src/page/writebackReceiptJournal.js +2 -0
- package/extension/src/page/writebackRouter.js +80 -79
- package/extension/src/pageBridge.js +19 -3
- package/extension/src/popup.js +62 -10
- package/extension/src/shared/agentTranscript.js +23 -0
- package/extension/src/shared/auditRecords.js +2 -0
- package/extension/src/shared/compatibility.js +22 -6
- package/extension/src/shared/compileAdapter.js +79 -57
- package/extension/src/shared/composerAttachmentDraft.js +151 -0
- package/extension/src/shared/directoryCleanupProof.js +43 -0
- package/extension/src/shared/failureReasons.js +309 -3
- package/extension/src/shared/globalPreferences.js +30 -13
- package/extension/src/shared/governanceRules.js +4 -37
- package/extension/src/shared/i18n.js +298 -2
- package/extension/src/shared/managedUpdateProjection.js +2 -2
- package/extension/src/shared/nativeRequestIdentity.js +1 -0
- package/extension/src/shared/pageRpcContract.js +5 -4
- package/extension/src/shared/projectFiles.js +21 -44
- package/extension/src/shared/providerProfiles.js +5 -3
- package/extension/src/shared/runInputQueue.js +48 -5
- package/extension/src/shared/selectionContext.js +12 -3
- package/extension/src/shared/sessionState.js +72 -27
- package/extension/src/shared/settlementFacts.js +10 -2
- package/extension/src/shared/sharedSessionBridge.js +37 -14
- package/extension/src/shared/sharedSessionTransfer.js +140 -0
- package/extension/src/shared/staleGuard.js +118 -13
- package/extension/src/shared/storageDb.js +58 -26
- package/extension/src/shared/storageMigration.js +71 -111
- package/extension/src/shared/storageRunActions.js +209 -26
- package/extension/src/shared/subagentActivityModel.js +1 -1
- package/extension/src/shared/undoOperations.js +136 -13
- package/extension/src/shared/updateConsent.js +6 -4
- package/extension/src/shared/updateRuntimeIdentity.js +1 -1
- package/extension/src/shared/writebackIntent.js +16 -0
- package/extension/src/shared/writebackOperationEvidence.js +4 -15
- package/extension/src/shared/writebackSettlement.js +38 -38
- package/extension/styles/panel.css +19 -5
- package/native-host/src/anthropicBridgeRequest.js +4 -2
- package/native-host/src/anthropicBridgeResponse.js +6 -2
- package/native-host/src/anthropicMessagesBridge.js +24 -16
- package/native-host/src/bootstrapMigration.js +296 -0
- package/native-host/src/bootstrapMigrationContract.js +101 -0
- package/native-host/src/bootstrapUpdate.js +233 -0
- package/native-host/src/bootstrapUpdateWatchdog.js +44 -0
- package/native-host/src/chatBridgeResponse.js +10 -0
- package/native-host/src/chatCompletionsBridge.js +21 -15
- package/native-host/src/codexConfigPaths.js +106 -0
- package/native-host/src/codexHome.js +66 -73
- package/native-host/src/codexHomeScope.js +124 -0
- package/native-host/src/codexInheritedAuth.js +90 -0
- package/native-host/src/codexLocalSkills.js +301 -0
- package/native-host/src/codexPromptAssembly.js +31 -12
- package/native-host/src/codexProviderTest.js +24 -26
- package/native-host/src/codexSessionRunner.js +110 -183
- package/native-host/src/codexSessionTiming.js +18 -8
- package/native-host/src/codexSkillIsolation.js +170 -0
- package/native-host/src/codexThreadFork.js +19 -3
- package/native-host/src/index.js +19 -7
- package/native-host/src/managedInstall.js +110 -35
- package/native-host/src/managedInstallRecovery.js +140 -0
- package/native-host/src/managedRuntimeEvidence.js +57 -0
- package/native-host/src/mirrorWorkspace.js +92 -22
- package/native-host/src/nativeDoctor.js +15 -0
- package/native-host/src/nativeHelperLifecycle.js +21 -0
- package/native-host/src/nativeHostRegistration.js +33 -0
- package/native-host/src/nativeRunLifecycle.js +71 -0
- package/native-host/src/processStopBarrier.js +42 -7
- package/native-host/src/projectPathPlatform.js +30 -0
- package/native-host/src/providerHttp.js +80 -0
- package/native-host/src/providerProfile.js +4 -2
- package/native-host/src/providerReasoning.js +2 -7
- package/native-host/src/providerRuntime.js +1 -0
- package/native-host/src/providerStore.js +72 -14
- package/native-host/src/responsesPassthroughBridge.js +266 -52
- package/native-host/src/runtimeInstaller.js +21 -1
- package/native-host/src/selectionScope.js +36 -8
- package/native-host/src/subagentAdmission.js +26 -0
- package/native-host/src/subagentBroker.js +79 -66
- package/native-host/src/subagentTelemetry.js +19 -10
- package/native-host/src/subagentWorkspacePath.js +5 -2
- package/native-host/src/taskRunnerRuntime.js +77 -54
- package/native-host/src/tomlStatements.js +140 -0
- package/native-host/src/updateManager.js +297 -39
- package/native-host/src/writingStyleRuntime.js +57 -29
- package/native-host/src/writingStyleStore.js +73 -0
- package/package.json +1 -1
- package/scripts/install-managed.mjs +1 -0
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
'use strict';
|
|
9
9
|
|
|
10
10
|
var PREFS_KEY = 'codexOverleafPrefs';
|
|
11
|
+
var LEGACY_MIGRATION_PREFIX = 'codexOverleafLegacyPanelMigration:';
|
|
11
12
|
var SESSION_TOMBSTONES_KEY = 'codexOverleafSessionTombstones';
|
|
12
13
|
var SESSION_TOMBSTONE_ENTRY_PREFIX = 'codexOverleafSessionTombstone:v2:';
|
|
13
14
|
var MAX_DELETED_SESSION_IDS_PER_PROJECT = 100;
|
|
@@ -139,118 +140,77 @@
|
|
|
139
140
|
|
|
140
141
|
function runMigrationIfNeeded(projectId, legacyStorageKey, accountScopeId) {
|
|
141
142
|
projectId = normalizeProjectPrefKey(projectId);
|
|
142
|
-
var
|
|
143
|
-
if (!
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
);
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
});
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
// Migration v0 → v1
|
|
204
|
-
var legacyBlob = stored[legacyStorageKey] || {};
|
|
205
|
-
var legacySessions = Array.isArray(legacyBlob.sessions) ? legacyBlob.sessions : [];
|
|
206
|
-
var migratedSessions = [];
|
|
207
|
-
|
|
208
|
-
for (var i = 0; i < legacySessions.length; i++) {
|
|
209
|
-
var legacy = legacySessions[i];
|
|
210
|
-
if (!legacy || !legacy.id) { continue; }
|
|
211
|
-
if (deletedSessionIds.indexOf(legacy.id) !== -1) { continue; }
|
|
212
|
-
var record = StorageDb.buildSessionRecord(
|
|
213
|
-
buildLegacySessionRecordInput(projectId, legacyBlob, legacy, normalizedAccountScopeId)
|
|
214
|
-
);
|
|
215
|
-
migratedSessions.push(record);
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
var putPromise = migratedSessions.length
|
|
219
|
-
? StorageDb.putRecords('sessions', migratedSessions)
|
|
220
|
-
: Promise.resolve([]);
|
|
221
|
-
|
|
222
|
-
return putPromise.then(function () {
|
|
223
|
-
var newPrefs = StorageDb.extractLightweightPrefs(legacyBlob, projectId);
|
|
224
|
-
newPrefs.activeSessionByProject = StorageDb.buildActiveSessionByProject(
|
|
225
|
-
{},
|
|
226
|
-
projectId,
|
|
227
|
-
legacyBlob.activeSessionId || (migratedSessions.length ? migratedSessions[migratedSessions.length - 1].id : '')
|
|
228
|
-
);
|
|
229
|
-
var mergedPrefs = mergeScopedProjectPreferences(
|
|
230
|
-
storedPrefs,
|
|
231
|
-
newPrefs,
|
|
232
|
-
normalizedAccountScopeId,
|
|
233
|
-
projectId
|
|
234
|
-
);
|
|
235
|
-
var prefsView = buildProjectPreferenceView(
|
|
236
|
-
mergedPrefs,
|
|
237
|
-
normalizedAccountScopeId,
|
|
238
|
-
projectId
|
|
239
|
-
);
|
|
240
|
-
|
|
241
|
-
return chrome.storage.local.set({ [PREFS_KEY]: mergedPrefs }).then(function () {
|
|
242
|
-
return chrome.storage.local.remove(legacyStorageKey).catch(function () {});
|
|
243
|
-
}).then(function () {
|
|
244
|
-
return {
|
|
245
|
-
prefs: prefsView,
|
|
246
|
-
sessions: migratedSessions,
|
|
247
|
-
activeSessionId: prefsView.activeSessionByProject[projectId] || '',
|
|
248
|
-
migrated: true
|
|
249
|
-
};
|
|
250
|
-
});
|
|
251
|
-
});
|
|
143
|
+
var account = normalizeTextField(accountScopeId, PROJECT_PREF_KEY_MAX_CHARS);
|
|
144
|
+
if (!account || !projectId) return Promise.resolve({ prefs: normalizePrefs({}), sessions: [],
|
|
145
|
+
activeSessionId: '', migrated: false, reason: 'account_scope_unavailable' });
|
|
146
|
+
return withProjectPreferenceLock(projectId, async function () {
|
|
147
|
+
var stored = await chrome.storage.local.get(null);
|
|
148
|
+
var claimed = claimLegacyProjectPreferences(stored[PREFS_KEY] || {}, account, projectId);
|
|
149
|
+
var storedPrefs = claimed.prefs;
|
|
150
|
+
if (claimed.changed) await chrome.storage.local.set({ [PREFS_KEY]: storedPrefs });
|
|
151
|
+
var prefs = buildProjectPreferenceView(storedPrefs, account, projectId);
|
|
152
|
+
var deleted = getDeletedSessionIds(collectSessionTombstones(stored), projectId);
|
|
153
|
+
var markerKey = LEGACY_MIGRATION_PREFIX + encodeURIComponent(String(legacyStorageKey || ''));
|
|
154
|
+
var marker = stored[markerKey];
|
|
155
|
+
var legacyBlob = stored[legacyStorageKey];
|
|
156
|
+
var hasLegacy = Boolean(legacyBlob && typeof legacyBlob === 'object' && !Array.isArray(legacyBlob));
|
|
157
|
+
var schemaCurrent = (prefs.storageSchemaVersion || 0) >= StorageDb.TARGET_SCHEMA_VERSION;
|
|
158
|
+
if (marker && (marker.projectId !== projectId || marker.sourceKey !== legacyStorageKey || !marker.accountScopeId)) {
|
|
159
|
+
throw Object.assign(new Error('The legacy history source has a conflicting migration identity.'), { code: 'legacy_migration_scope_conflict' });
|
|
160
|
+
}
|
|
161
|
+
if (typeof StorageDb.claimSessionsForAccount !== 'function') {
|
|
162
|
+
throw Object.assign(new Error('Atomic legacy-session claiming is unavailable.'), { code: 'scoped_claim_unavailable' });
|
|
163
|
+
}
|
|
164
|
+
var visible = await StorageDb.claimSessionsForAccount(projectId, account, deleted);
|
|
165
|
+
function result(currentPrefs, sessions, migrated) {
|
|
166
|
+
var wanted = currentPrefs.activeSessionByProject?.[projectId] || '';
|
|
167
|
+
return { prefs: currentPrefs, sessions: sessions,
|
|
168
|
+
activeSessionId: sessions.some(session => session.id === wanted) ? wanted : sessions.at(-1)?.id || '',
|
|
169
|
+
migrated: migrated };
|
|
170
|
+
}
|
|
171
|
+
if ((schemaCurrent && !hasLegacy) || marker?.complete || (marker && marker.accountScopeId !== account)) {
|
|
172
|
+
return result(prefs, visible, false);
|
|
173
|
+
}
|
|
174
|
+
legacyBlob = hasLegacy ? legacyBlob : {};
|
|
175
|
+
var legacySessions = Array.isArray(legacyBlob.sessions) ? legacyBlob.sessions : [];
|
|
176
|
+
var owners = new Set([legacyBlob.accountScopeId, legacyBlob.__codexOverleafFallbackAccountScopeId,
|
|
177
|
+
...legacySessions.map(session => session?.accountScopeId)].filter(value => typeof value === 'string' && value));
|
|
178
|
+
if ([...owners].some(owner => owner !== account)) return result(prefs, visible, false);
|
|
179
|
+
// The shared preferences lock serializes this source claim across accounts.
|
|
180
|
+
// Keep it after a write failure; only its owner can finish the import.
|
|
181
|
+
if (hasLegacy && !marker) {
|
|
182
|
+
marker = { sourceKey: legacyStorageKey, projectId: projectId, accountScopeId: account, complete: false };
|
|
183
|
+
await chrome.storage.local.set({ [markerKey]: marker });
|
|
184
|
+
}
|
|
185
|
+
var existingIds = new Set(visible.map(session => session.id));
|
|
186
|
+
var incoming = legacySessions.filter(session => session?.id && !deleted.includes(session.id) && !existingIds.has(session.id))
|
|
187
|
+
.map(session => StorageDb.buildSessionRecord(buildLegacySessionRecordInput(projectId, legacyBlob, session, account)));
|
|
188
|
+
var written = incoming.length ? await StorageDb.putRecords('sessions', incoming) : [];
|
|
189
|
+
if (incoming.length && (!Array.isArray(written) || incoming.some(session => !written.some(record =>
|
|
190
|
+
record.id === session.id && record.accountScopeId === account && record.projectId === projectId)))) {
|
|
191
|
+
throw Object.assign(new Error('Legacy history was not confirmed in the main session store.'), { code: 'legacy_migration_not_persisted' });
|
|
192
|
+
}
|
|
193
|
+
var sessions = [...visible, ...written];
|
|
194
|
+
var newPrefs = StorageDb.extractLightweightPrefs(legacyBlob, projectId);
|
|
195
|
+
if (schemaCurrent) {
|
|
196
|
+
// A later project's import must not reset current global preferences.
|
|
197
|
+
var legacyPrefs = newPrefs;
|
|
198
|
+
newPrefs = Object.assign({}, legacyPrefs, prefs);
|
|
199
|
+
PROJECT_SCOPED_PREF_MAPS.forEach(function (name) {
|
|
200
|
+
newPrefs[name] = Object.assign({}, legacyPrefs[name] || {}, prefs[name] || {});
|
|
252
201
|
});
|
|
253
|
-
}
|
|
202
|
+
}
|
|
203
|
+
var currentActive = prefs.activeSessionByProject?.[projectId];
|
|
204
|
+
var activeId = sessions.some(session => session.id === currentActive) ? currentActive
|
|
205
|
+
: legacyBlob.activeSessionId || sessions.at(-1)?.id || '';
|
|
206
|
+
newPrefs.activeSessionByProject = StorageDb.buildActiveSessionByProject({}, projectId, activeId);
|
|
207
|
+
var mergedPrefs = mergeScopedProjectPreferences(storedPrefs, newPrefs, account, projectId);
|
|
208
|
+
await chrome.storage.local.set({ [PREFS_KEY]: mergedPrefs });
|
|
209
|
+
// Completion is per source and owner, independent of the global schema.
|
|
210
|
+
// A failed cleanup cannot replay old content or transfer it to another account.
|
|
211
|
+
if (hasLegacy) await chrome.storage.local.set({ [markerKey]: { ...marker, complete: true } });
|
|
212
|
+
await chrome.storage.local.remove(legacyStorageKey).catch(function () {});
|
|
213
|
+
return result(buildProjectPreferenceView(mergedPrefs, account, projectId), sessions, true);
|
|
254
214
|
});
|
|
255
215
|
}
|
|
256
216
|
|
|
@@ -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'));
|
|
4
4
|
} else {
|
|
5
|
-
root.CodexOverleafModuleRegistry.define('StorageRunActions', ['SelectionContext', 'WritebackIntent', 'WritingStyle'], factory);
|
|
5
|
+
root.CodexOverleafModuleRegistry.define('StorageRunActions', ['SelectionContext', 'WritebackIntent', 'WritingStyle', 'DirectoryCleanupProof'], 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) {
|
|
8
8
|
'use strict';
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
|
|
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
|
|
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
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
|
|
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
|
|
@@ -56,29 +132,96 @@
|
|
|
56
132
|
return trackedLifecycle || reviewingEditorUndo || legacyUndo;
|
|
57
133
|
}
|
|
58
134
|
|
|
59
|
-
function
|
|
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');
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function compactCoreActionPayload(run, keepActionPayload, options = {}) {
|
|
60
189
|
var empty = emptyActionPayload();
|
|
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
|
|
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
|
-
|
|
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)) <=
|
|
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
|
}
|
|
@@ -169,17 +312,20 @@
|
|
|
169
312
|
function mergeSessionReviewState(incoming, existing, reviewRunIds) {
|
|
170
313
|
if (!existing || incoming.id !== existing.id || incoming.projectId !== existing.projectId
|
|
171
314
|
|| !incoming.accountScopeId || incoming.accountScopeId !== existing.accountScopeId) return incoming;
|
|
172
|
-
var terminal = run => ['accepted', 'rejected'].includes(run?.trackedChangeStatus);
|
|
315
|
+
var terminal = run => ['accepted', 'rejected'].includes(run?.trackedChangeStatus) || run?.undoStatus === 'applied';
|
|
173
316
|
var incomingRuns = new Map((incoming.runs || []).map(run => [run.id, run]));
|
|
174
317
|
var existingRuns = new Map((existing.runs || []).map(run => [run.id, run]));
|
|
175
318
|
var requestedIds = new Set(reviewRunIds || []);
|
|
176
|
-
var explicitReview = Array.from(requestedIds).some(id =>
|
|
177
|
-
|
|
319
|
+
var explicitReview = Array.from(requestedIds).some(id => {
|
|
320
|
+
var run = incomingRuns.get(id);
|
|
321
|
+
return ['accepted', 'rejected', 'needs_review'].includes(run?.trackedChangeStatus)
|
|
322
|
+
|| ['applied', 'partial'].includes(run?.undoStatus);
|
|
323
|
+
});
|
|
178
324
|
var newer = (Date.parse(incoming.updatedAt) || 0) >= (Date.parse(existing.updatedAt) || 0);
|
|
179
325
|
if (!newer && !explicitReview) return incoming;
|
|
180
326
|
var base = newer ? incoming : existing;
|
|
181
|
-
var fields = ['trackedChangeStatus', 'undoStatus', 'undoTrackedChanges', 'undoExpectedFiles',
|
|
182
|
-
'undoOperations', 'undoBaseFiles', 'appliedOperations', 'trackedChangeCaptures', 'settlement', 'settlementFacts'];
|
|
327
|
+
var fields = ['trackedChangeStatus', 'undoStatus', 'undoOwnerProtocol', 'undoTrackedChanges', 'undoExpectedFiles',
|
|
328
|
+
'undoOperations', 'undoBaseFiles', 'appliedOperations', 'trackedChangeCaptures', 'recoveryRetention', 'settlement', 'settlementFacts'];
|
|
183
329
|
var changed = false;
|
|
184
330
|
var runs = (base.runs || []).map(function (run) {
|
|
185
331
|
var previous = existingRuns.get(run.id), candidate = incomingRuns.get(run.id);
|
|
@@ -218,7 +364,44 @@
|
|
|
218
364
|
return (hash >>> 0).toString(16).padStart(8, '0');
|
|
219
365
|
}
|
|
220
366
|
|
|
367
|
+
// A successful durable write can omit an oversized checkpoint while the
|
|
368
|
+
// current tab still has it. Warn only the view and exact action revision
|
|
369
|
+
// that was saved; never mutate or remove its in-memory recovery material.
|
|
370
|
+
function projectRetentionUpdates({ liveState, capturedState, written, capturedScope, currentScope } = {}) {
|
|
371
|
+
if (!capturedScope?.accountScopeId || !capturedScope.projectId
|
|
372
|
+
|| !Number.isSafeInteger(capturedScope.generation)
|
|
373
|
+
|| ['accountScopeId', 'projectId', 'generation'].some(key => capturedScope[key] !== currentScope?.[key])) return [];
|
|
374
|
+
var fields = ['appliedOperations', 'undoOperations', 'undoBaseFiles', 'undoTrackedChanges',
|
|
375
|
+
'undoExpectedFiles', 'undoStatus', 'trackedChangeStatus', 'pendingDirectoryCleanup'];
|
|
376
|
+
var fingerprint = run => JSON.stringify(fields.map(field => run[field]));
|
|
377
|
+
var updates = [];
|
|
378
|
+
for (var record of written || []) {
|
|
379
|
+
if (record.accountScopeId !== capturedScope.accountScopeId || record.projectId !== capturedScope.projectId) continue;
|
|
380
|
+
var live = liveState?.sessions?.find(session => session.id === record.id);
|
|
381
|
+
var captured = capturedState?.sessions?.find(session => session.id === record.id);
|
|
382
|
+
for (var storedRun of record.runs || []) {
|
|
383
|
+
var run = live?.runs?.find(run => run.id === storedRun.id);
|
|
384
|
+
var original = captured?.runs?.find(run => run.id === storedRun.id);
|
|
385
|
+
if (!run || !original || run.runProjectId !== capturedScope.projectId
|
|
386
|
+
|| original.runProjectId !== capturedScope.projectId || !hasReloadableRunActionPayload(run)) continue;
|
|
387
|
+
try {
|
|
388
|
+
if (fingerprint(run) !== fingerprint(original)) continue;
|
|
389
|
+
if (storedRun.recoveryRetention?.state === 'omitted') updates.push({ runId: run.id, retention: { ...storedRun.recoveryRetention } });
|
|
390
|
+
else if (fingerprint(storedRun) === fingerprint(original)) updates.push({ runId: run.id, retention: null });
|
|
391
|
+
} catch (_error) { /* Invalid data cannot establish a revision. */ }
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
return updates;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function projectRetentionWarnings(input) {
|
|
398
|
+
return projectRetentionUpdates(input).filter(update => update.retention).map(update => update.runId);
|
|
399
|
+
}
|
|
400
|
+
|
|
221
401
|
return {
|
|
402
|
+
projectRetentionUpdates: projectRetentionUpdates,
|
|
403
|
+
projectRetentionWarnings: projectRetentionWarnings,
|
|
404
|
+
compactSessionBatchActions: compactSessionBatchActions,
|
|
222
405
|
compactStructuredEventValue: compactStructuredEventValue,
|
|
223
406
|
mergeSessionReviewState: mergeSessionReviewState,
|
|
224
407
|
compactRunsForStorage: compactRunsForStorage,
|
|
@@ -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
|
|
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;
|