codex-overleaf-link 2.2.0 → 2.3.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 (72) 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 +53 -69
  5. package/extension/src/backgroundUpdateCoordinator.js +128 -66
  6. package/extension/src/content/applyResultFormatters.js +2 -3
  7. package/extension/src/content/contentRuntime.js +650 -1449
  8. package/extension/src/content/contextTray.js +4 -6
  9. package/extension/src/content/diagnosticsController.js +1 -2
  10. package/extension/src/content/diffReviewPanel.js +14 -13
  11. package/extension/src/content/generated/content.bundle.js +41370 -0
  12. package/extension/src/content/generated/content.bundle.meta.json +102 -0
  13. package/extension/src/content/legacyGlobalRegistry.js +129 -0
  14. package/extension/src/content/markdownText.js +5 -7
  15. package/extension/src/content/modelPicker.js +1 -1
  16. package/extension/src/content/modelPickerSupport.js +10 -5
  17. package/extension/src/content/moduleRegistryKernel.js +125 -0
  18. package/extension/src/content/nativeCompatibilityController.js +313 -0
  19. package/extension/src/content/otWarmMirror.js +7 -5
  20. package/extension/src/content/pageBridgeClient.js +224 -0
  21. package/extension/src/content/panelMaintenance.js +8 -3
  22. package/extension/src/content/postNavigationSettlementPersistence.js +100 -0
  23. package/extension/src/content/projectProviderSelection.js +4 -12
  24. package/extension/src/content/projectSessionCleanup.js +3 -3
  25. package/extension/src/content/providerSettingsCoordinator.js +59 -26
  26. package/extension/src/content/providerSettingsDialog.js +30 -18
  27. package/extension/src/content/recentProjects.js +15 -23
  28. package/extension/src/content/runGuidanceController.js +1 -1
  29. package/extension/src/content/runQueueScheduler.js +129 -29
  30. package/extension/src/content/runSettlementPersistence.js +51 -0
  31. package/extension/src/content/runTimelineView.js +11 -8
  32. package/extension/src/content/scopedPersistenceCoordinator.js +100 -0
  33. package/extension/src/content/scopedPersistenceDetachedCommit.js +47 -0
  34. package/extension/src/content/scopedPersistencePanelState.js +111 -0
  35. package/extension/src/content/sessionManager.js +15 -12
  36. package/extension/src/content/sessionPersistence.js +67 -8
  37. package/extension/src/content/updateNotice.js +10 -4
  38. package/extension/src/content/writebackOrchestrator.js +91 -23
  39. package/extension/src/contentScript.js +5 -1
  40. package/extension/src/page/trackedChangesLifecycle.js +6 -1
  41. package/extension/src/pageBridge.js +47 -98
  42. package/extension/src/shared/agentTranscript.js +8 -5
  43. package/extension/src/shared/compatibility.js +1 -1
  44. package/extension/src/shared/governanceRules.js +1 -1
  45. package/extension/src/shared/managedUpdateProjection.js +337 -0
  46. package/extension/src/shared/nativeRequestIdentity.js +60 -0
  47. package/extension/src/shared/otText.js +0 -19
  48. package/extension/src/shared/pageRpcContract.js +136 -0
  49. package/extension/src/shared/runExecutionSnapshot.js +177 -0
  50. package/extension/src/shared/runExecutionSnapshotCodec.js +168 -0
  51. package/extension/src/shared/runInputQueue.js +81 -34
  52. package/extension/src/shared/scopedPersistenceBrowserAdapter.js +79 -0
  53. package/extension/src/shared/scopedPersistenceQueuePolicy.js +181 -0
  54. package/extension/src/shared/scopedPersistenceTransaction.js +238 -0
  55. package/extension/src/shared/sessionState.js +64 -48
  56. package/extension/src/shared/settlementFacts.js +351 -0
  57. package/extension/src/shared/storageDb.js +19 -5
  58. package/extension/src/shared/storageMigration.js +275 -61
  59. package/extension/src/shared/storageSessionClaims.js +60 -0
  60. package/extension/src/shared/updateConsent.js +4 -0
  61. package/extension/src/shared/updateRevocationIntent.js +109 -0
  62. package/extension/src/shared/writebackEvidenceProjection.js +150 -0
  63. package/extension/src/shared/writebackOperationEvidence.js +143 -0
  64. package/extension/src/shared/writebackSettlement.js +705 -0
  65. package/native-host/src/codexRuntimeIdentity.js +1 -1
  66. package/native-host/src/codexSessionRunner.js +7 -13
  67. package/native-host/src/index.js +3 -69
  68. package/native-host/src/managedInstall.js +50 -8
  69. package/native-host/src/mirrorWorkspace.js +15 -8
  70. package/native-host/src/nativeTransportEnvelope.js +148 -0
  71. package/native-host/src/taskRunnerRuntime.js +3 -3
  72. package/package.json +13 -5
@@ -2,7 +2,8 @@
2
2
  'use strict';
3
3
 
4
4
  const PAGE_BRIDGE_INSTALL_VERSION = window.CodexOverleafCompatibility?.BUILD_TARGET_VERSION || 'dev';
5
- const PAGE_BRIDGE_INSTALL_REVISION = '2026-05-21-editor-readiness-v11';
5
+ const pageRpcContract = window.CodexOverleafPageRpcContract;
6
+ const PAGE_BRIDGE_INSTALL_REVISION = pageRpcContract.REVISION;
6
7
  if (window.__codexOverleafPageBridgeInstalledVersion === PAGE_BRIDGE_INSTALL_VERSION
7
8
  && window.__codexOverleafPageBridgeInstalledRevision === PAGE_BRIDGE_INSTALL_REVISION) {
8
9
  return;
@@ -152,10 +153,7 @@
152
153
  result = await dispatch(method, params || {});
153
154
  }
154
155
  } catch (error) {
155
- result = {
156
- ok: false,
157
- error: error.message
158
- };
156
+ result = pageRpcContract.normalizeFailure(method, error);
159
157
  }
160
158
 
161
159
  window.postMessage({
@@ -187,111 +185,62 @@
187
185
  return writeCancellationSequence;
188
186
  }
189
187
 
190
- async function dispatch(method, params) {
191
- if (method === 'probe') {
192
- return probe(params);
193
- }
194
- if (method === 'cancelActiveWrite') {
188
+ const pageRpcHandlers = {
189
+ probe,
190
+ cancelActiveWrite() {
195
191
  const sequence = bumpWriteCancellationSequence();
196
192
  return { ok: true, cancelled: true, sequence };
197
- }
198
- if (method === 'getProjectSnapshot') {
199
- return projectSnapshotBridge.getProjectSnapshot(withSnapshotCacheIdentity(params));
200
- }
201
- if (method === 'getProjectFileList') {
202
- return projectSnapshotBridge.getProjectFileList(params);
203
- }
204
- if (method === 'invalidateProjectSnapshot') {
193
+ },
194
+ getProjectSnapshot: params => projectSnapshotBridge.getProjectSnapshot(withSnapshotCacheIdentity(params)),
195
+ getProjectFileList: params => projectSnapshotBridge.getProjectFileList(params),
196
+ invalidateProjectSnapshot(params) {
205
197
  projectSnapshotBridge.invalidateProjectSnapshot(params);
206
198
  return { ok: true };
207
- }
208
- if (method === 'createCheckpoint') {
209
- return createCheckpoint(params.label);
210
- }
211
- if (method === 'ensureReviewing') {
212
- // Gate the Reviewing-mode toggle with the same writeGuard the per-write
213
- // path uses. Without this, a mid-flight SPA navigation could leave the
214
- // pre-flight toggle flipping Track Changes in a different Overleaf
215
- // project than the run was bound to. params.runProjectId is set by
216
- // the content-side caller (preflightWriteSafety / ensureReviewingBeforeWrite);
217
- // when absent (defensive), the guard short-circuits via its own
218
- // missing-runProjectId branch.
219
- if (typeof params?.runProjectId === 'string' && params.runProjectId) {
220
- const blocked = await writeGuard.runWriteGuard(params);
221
- if (blocked) return blocked;
222
- }
223
- return ensureReviewing(params);
224
- }
225
- if (method === 'ensureEditing') {
226
- if (typeof params?.runProjectId === 'string' && params.runProjectId) {
227
- const blocked = await writeGuard.runWriteGuard(params);
228
- if (blocked) return blocked;
229
- }
230
- return ensureEditing(params);
231
- }
232
- if (method === 'applyOperations') {
233
- // Welcome-panel + write-guard:
234
- // First gate: runProjectId vs page-side editorProjectId. Runs before
235
- // any reviewing / save-state / open-file readiness check so a
236
- // mid-run navigation cannot write into a different project. runWriteGuard
237
- // is async because it retries the page-side editorProjectId reader with
238
- // backoff to ride out Overleaf's hydration window.
239
- const blocked = await writeGuard.runWriteGuard(params);
240
- if (blocked) return blocked;
241
- return applyOperations(params.operations || [], {
199
+ },
200
+ createCheckpoint: params => createCheckpoint(params.label),
201
+ ensureReviewing,
202
+ ensureEditing,
203
+ applyOperations: params => applyOperations(params.operations || [], {
242
204
  baseFiles: params.baseFiles || null,
243
205
  reviewingPolicy: params.reviewingPolicy || '',
244
206
  requireReviewing: params.requireReviewing === true,
245
207
  requireEditing: params.requireEditing === true,
246
- // Propagate runProjectId so the writeback router's defense-in-depth
247
- // missing-runProjectId check sees it on this in-bridge dispatch path.
248
208
  runProjectId: typeof params.runProjectId === 'string' ? params.runProjectId : ''
249
- });
250
- }
251
- if (method === 'jumpToPosition') {
252
- return jumpToPosition(params);
253
- }
254
- if (method === 'rejectTrackedChanges') {
255
- const blocked = await writeGuard.runWriteGuard(params);
256
- if (blocked) return blocked;
257
- return rejectTrackedChanges(params);
209
+ }),
210
+ jumpToPosition,
211
+ rejectTrackedChanges,
212
+ acceptTrackedChanges,
213
+ triggerCompile: params => compileBridge.triggerCompile(params),
214
+ getCompileLog: params => compileBridge.getCompileLog(params),
215
+ getCompileState: () => compileBridge.getCompileState(),
216
+ waitForSaveState,
217
+ startOtObserver: params => otObserver.start(params),
218
+ stopOtObserver: () => otObserver.stop(),
219
+ getOtStatus: () => otObserver.getStatus(),
220
+ drainOtEvents() {
221
+ return { ok: true, events: otObserver.drainEvents().map(sanitizeOtEventForContent) };
258
222
  }
259
- if (method === 'acceptTrackedChanges') {
223
+ };
224
+
225
+ async function dispatch(method, params) {
226
+ const contract = pageRpcContract.getMethod(method);
227
+ if (!contract || method === PAGE_BRIDGE_CAPABILITY_METHOD) {
228
+ return { ok: false, error: `Unknown page bridge method: ${method}` };
229
+ }
230
+ const handler = pageRpcHandlers[method];
231
+ if (typeof handler !== 'function') {
232
+ return { ok: false, error: `Page bridge handler unavailable: ${method}` };
233
+ }
234
+ const hasRunProjectId = typeof params?.runProjectId === 'string'
235
+ && params.runProjectId.trim().length > 0;
236
+ if (
237
+ contract.projectIdentity === 'required'
238
+ || (contract.projectIdentity === 'optional' && hasRunProjectId)
239
+ ) {
260
240
  const blocked = await writeGuard.runWriteGuard(params);
261
241
  if (blocked) return blocked;
262
- return acceptTrackedChanges(params);
263
- }
264
- if (method === 'triggerCompile') {
265
- return compileBridge.triggerCompile(params);
266
- }
267
- if (method === 'getCompileLog') {
268
- return compileBridge.getCompileLog(params);
269
- }
270
- if (method === 'getCompileState') {
271
- return compileBridge.getCompileState();
272
- }
273
- if (method === 'waitForSaveState') {
274
- return waitForSaveState(params);
275
242
  }
276
- if (method === 'startOtObserver') {
277
- return otObserver.start(params);
278
- }
279
- if (method === 'stopOtObserver') {
280
- return otObserver.stop();
281
- }
282
- if (method === 'getOtStatus') {
283
- return otObserver.getStatus();
284
- }
285
- if (method === 'drainOtEvents') {
286
- return {
287
- ok: true,
288
- events: otObserver.drainEvents().map(sanitizeOtEventForContent)
289
- };
290
- }
291
- return {
292
- ok: false,
293
- error: `Unknown page bridge method: ${method}`
294
- };
243
+ return handler(params);
295
244
  }
296
245
 
297
246
  function sanitizeOtEventForContent(event = {}) {
@@ -468,7 +417,7 @@
468
417
  reviewing: window.CodexOverleafReviewing.detectReviewingFromSignals(reviewingSignals),
469
418
  reviewingDiagnostics: reviewingSignals.diagnostics,
470
419
  editor,
471
- capabilities: collectPageCapabilities(editor),
420
+ capabilities: pageRpcContract.withCapabilityReport(collectPageCapabilities(editor)),
472
421
  editorDiagnostics: buildEditorDiagnostics(editor),
473
422
  projectDiagnostics: {
474
423
  internalRootKeys: collectInternalRootKeys(),
@@ -5,11 +5,14 @@
5
5
  getI18n: function getI18nCjs() { return require('./i18n.js'); }
6
6
  });
7
7
  } else {
8
- // Browser script-tag load order is determined by manifest.json content_scripts;
9
- // resolve dependencies lazily so this module can be listed before its deps.
10
- root.CodexOverleafAgentTranscript = factory({
11
- getFailureReasons: function getFailureReasonsWindow() { return root.CodexOverleafFailureReasons || null; },
12
- getI18n: function getI18nWindow() { return root.CodexOverleafI18n || null; }
8
+ root.CodexOverleafModuleRegistry.define('AgentTranscript', [
9
+ 'FailureReasons',
10
+ 'I18n'
11
+ ], function createAgentTranscript(FailureReasons, I18n) {
12
+ return factory({
13
+ getFailureReasons: function getFailureReasonsWindow() { return FailureReasons; },
14
+ getI18n: function getI18nWindow() { return I18n; }
15
+ });
13
16
  });
14
17
  }
15
18
  })(typeof globalThis !== 'undefined' ? globalThis : window, function agentTranscriptFactory(deps) {
@@ -12,7 +12,7 @@
12
12
  const MIN_NATIVE_VERSION = '1.0.0';
13
13
  const MIN_COMPATIBLE_NATIVE_VERSION = '1.0.0';
14
14
  const MIN_COMPATIBLE_EXTENSION_VERSION = '1.0.0';
15
- const BUILD_TARGET_VERSION = '2.2.0';
15
+ const BUILD_TARGET_VERSION = '2.3.0';
16
16
  const DEFAULT_CHROME_EXTENSION_ID = 'illdpneeeopfffmiepaejglgmhpmdhdc';
17
17
  const REQUIRED_CAPABILITIES = Object.freeze([
18
18
  'bridgePing',
@@ -8,7 +8,7 @@
8
8
  }
9
9
  module.exports = factory(projectFiles);
10
10
  } else {
11
- root.CodexOverleafGovernanceRules = factory(root.CodexOverleafProjectFiles);
11
+ root.CodexOverleafModuleRegistry.define('GovernanceRules', ['ProjectFiles'], factory);
12
12
  }
13
13
  })(typeof globalThis !== 'undefined' ? globalThis : window, function governanceRulesFactory(projectFiles) {
14
14
  'use strict';
@@ -0,0 +1,337 @@
1
+ (function initCodexOverleafManagedUpdateProjection(root) {
2
+ 'use strict';
3
+
4
+ const PHASE_ALIASES = Object.freeze({
5
+ waiting: 'waiting_for_idle',
6
+ waiting_for_safe_point: 'waiting_for_idle',
7
+ awaiting_health_check: 'awaiting_health',
8
+ health_checking: 'awaiting_health'
9
+ });
10
+
11
+ const PHASES = Object.freeze([
12
+ 'idle',
13
+ 'up_to_date',
14
+ 'checking',
15
+ 'update_available',
16
+ 'downloading',
17
+ 'staged',
18
+ 'waiting_for_idle',
19
+ 'applying',
20
+ 'awaiting_health',
21
+ 'committed',
22
+ 'failed',
23
+ 'rolling_back',
24
+ 'rolled_back',
25
+ 'deferred'
26
+ ]);
27
+
28
+ const PHASE_TIMEOUTS = Object.freeze({
29
+ checking: 30 * 1000,
30
+ downloading: 2 * 60 * 1000,
31
+ applying: 90 * 1000,
32
+ awaiting_health: 2 * 60 * 1000,
33
+ rolling_back: 60 * 1000
34
+ });
35
+
36
+ const PROGRESS = Object.freeze({
37
+ checking: 8,
38
+ update_available: 0,
39
+ downloading: 25,
40
+ staged: 48,
41
+ waiting_for_idle: 55,
42
+ applying: 75,
43
+ awaiting_health: 90,
44
+ committed: 100,
45
+ rolled_back: 100
46
+ });
47
+
48
+ const ACTIVE_STAGE = Object.freeze({
49
+ downloading: 0,
50
+ staged: 1,
51
+ waiting_for_idle: 1,
52
+ applying: 2,
53
+ awaiting_health: 2,
54
+ committed: 3
55
+ });
56
+
57
+ const ACTIVE_PHASES = Object.freeze({
58
+ update_page: Object.freeze([
59
+ 'checking',
60
+ 'downloading',
61
+ 'staged',
62
+ 'waiting_for_idle',
63
+ 'applying',
64
+ 'awaiting_health'
65
+ ]),
66
+ panel: Object.freeze([
67
+ 'downloading',
68
+ 'staged',
69
+ 'waiting_for_idle',
70
+ 'applying',
71
+ 'awaiting_health'
72
+ ])
73
+ });
74
+
75
+ const TRANSITIONS = Object.freeze({
76
+ idle: Object.freeze(['checking']),
77
+ up_to_date: Object.freeze(['checking', 'idle']),
78
+ checking: Object.freeze(['idle', 'up_to_date', 'update_available', 'failed']),
79
+ update_available: Object.freeze(['checking', 'downloading', 'deferred', 'failed']),
80
+ downloading: Object.freeze(['staged', 'failed']),
81
+ staged: Object.freeze(['waiting_for_idle', 'applying', 'update_available', 'deferred', 'failed']),
82
+ waiting_for_idle: Object.freeze(['applying', 'update_available', 'deferred', 'failed']),
83
+ applying: Object.freeze(['staged', 'awaiting_health', 'rolling_back', 'failed']),
84
+ awaiting_health: Object.freeze(['staged', 'committed', 'rolling_back', 'failed']),
85
+ committed: Object.freeze(['checking', 'idle']),
86
+ failed: Object.freeze(['checking', 'rolling_back', 'update_available', 'idle']),
87
+ rolling_back: Object.freeze(['rolled_back', 'failed']),
88
+ rolled_back: Object.freeze(['checking', 'idle']),
89
+ deferred: Object.freeze(['checking', 'update_available', 'idle'])
90
+ });
91
+
92
+ function asObject(value) {
93
+ return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
94
+ }
95
+
96
+ function unwrap(value) {
97
+ const source = asObject(value);
98
+ return source.state && typeof source.state === 'object' && !Array.isArray(source.state)
99
+ ? source.state
100
+ : source;
101
+ }
102
+
103
+ function normalizePhase(value) {
104
+ const normalized = String(value || 'idle').trim().toLowerCase().replace(/[- ]+/g, '_');
105
+ return PHASE_ALIASES[normalized] || normalized || 'idle';
106
+ }
107
+
108
+ function phaseOf(value) {
109
+ if (typeof value === 'string') {
110
+ return normalizePhase(value);
111
+ }
112
+ const source = unwrap(value);
113
+ return normalizePhase(source.phase || source.status || source.state || source.lifecycle || 'idle');
114
+ }
115
+
116
+ function normalizeBlockers(values, normalizeFn) {
117
+ if (typeof normalizeFn === 'function') {
118
+ return normalizeFn(values);
119
+ }
120
+ const result = [];
121
+ const seen = new Set();
122
+ for (const value of Array.isArray(values) ? values : [values]) {
123
+ const code = String(value || '').trim();
124
+ if (!code || seen.has(code)) continue;
125
+ seen.add(code);
126
+ result.push(code);
127
+ }
128
+ return result;
129
+ }
130
+
131
+ function normalize(value, options = {}) {
132
+ const source = unwrap(value);
133
+ const currentVersion = String(
134
+ source.currentVersion || options.currentVersion || source.latestVersion || ''
135
+ ).replace(/^v/i, '');
136
+ const latestVersion = String(
137
+ source.latestVersion || source.targetVersion || currentVersion
138
+ ).replace(/^v/i, '');
139
+ const blockers = normalizeBlockers(
140
+ Array.isArray(source.blockers) ? source.blockers : (source.blocker ? [source.blocker] : []),
141
+ options.normalizeBlockers
142
+ );
143
+ return {
144
+ ...source,
145
+ state: phaseOf(source),
146
+ managed: source.managed !== false,
147
+ currentVersion,
148
+ latestVersion,
149
+ etag: String(source.etag || ''),
150
+ lastCheckedAt: Number(source.lastCheckedAt || 0),
151
+ stagedAt: Number(source.stagedAt || 0),
152
+ postponeUntil: Number(source.postponeUntil || 0),
153
+ transactionId: String(source.transactionId || ''),
154
+ blocker: blockers[0] || '',
155
+ blockers,
156
+ code: String(source.code || ''),
157
+ message: String(source.message || '').slice(0, 300),
158
+ phaseStartedAt: Number(source.phaseStartedAt || 0),
159
+ deadlineAt: Number(source.deadlineAt || 0),
160
+ heartbeatAt: Number(source.heartbeatAt || 0)
161
+ };
162
+ }
163
+
164
+ function getPhaseTimeoutMs(value) {
165
+ return Number(PHASE_TIMEOUTS[phaseOf(value)] || 0);
166
+ }
167
+
168
+ function ensurePhaseMetadata(value, options = {}) {
169
+ const normalized = normalize(value, options);
170
+ const phase = phaseOf(normalized);
171
+ const timeoutMs = getPhaseTimeoutMs(phase);
172
+ if (!timeoutMs) {
173
+ return {
174
+ ...normalized,
175
+ phaseStartedAt: 0,
176
+ deadlineAt: 0,
177
+ heartbeatAt: 0
178
+ };
179
+ }
180
+ const now = Number(options.now || Date.now());
181
+ const phaseStartedAt = Number(normalized.phaseStartedAt || now);
182
+ return {
183
+ ...normalized,
184
+ phaseStartedAt,
185
+ deadlineAt: Number(normalized.deadlineAt || (phaseStartedAt + timeoutMs)),
186
+ heartbeatAt: Number(normalized.heartbeatAt || now)
187
+ };
188
+ }
189
+
190
+ function transition(previous, patch, options = {}) {
191
+ const prior = normalize(previous, options);
192
+ const nextSource = buildTransitionSource(prior, patch, options);
193
+ const nextPhase = phaseOf(nextSource);
194
+ const previousPhase = phaseOf(prior);
195
+ if (nextPhase !== previousPhase) {
196
+ nextSource.phaseStartedAt = 0;
197
+ nextSource.deadlineAt = 0;
198
+ nextSource.heartbeatAt = 0;
199
+ }
200
+ const transitioned = ensurePhaseMetadata(nextSource, options);
201
+ if (getPhaseTimeoutMs(nextPhase)) {
202
+ transitioned.heartbeatAt = Number(options.now || Date.now());
203
+ }
204
+ return transitioned;
205
+ }
206
+
207
+ function transitionCommand(previous, patch, options = {}) {
208
+ const prior = normalize(previous, options);
209
+ const nextSource = buildTransitionSource(prior, patch, options);
210
+ const previousPhase = phaseOf(prior);
211
+ const nextPhase = phaseOf(nextSource);
212
+ if (!isLegalTransition(previousPhase, nextPhase)) {
213
+ const error = new Error(`Illegal managed update transition: ${previousPhase} -> ${nextPhase}`);
214
+ error.code = 'managed_update_transition_invalid';
215
+ error.from = previousPhase;
216
+ error.to = nextPhase;
217
+ throw error;
218
+ }
219
+ return transition(prior, nextSource, { ...options, merge: false });
220
+ }
221
+
222
+ function buildTransitionSource(prior, patch, options = {}) {
223
+ return options.merge === false
224
+ ? asObject(patch)
225
+ : { ...prior, ...asObject(patch) };
226
+ }
227
+
228
+ function isLegalTransition(from, to) {
229
+ const fromPhase = phaseOf(from);
230
+ const toPhase = phaseOf(to);
231
+ if (fromPhase === toPhase) return true;
232
+ return (TRANSITIONS[fromPhase] || []).includes(toPhase);
233
+ }
234
+
235
+ function reconcile(value, transaction, options = {}) {
236
+ const state = normalize(value, options);
237
+ const native = asObject(transaction);
238
+ if (native.state === 'rolled_back') {
239
+ return {
240
+ action: 'reload_tabs',
241
+ state: transition(state, {
242
+ state: 'rolled_back',
243
+ currentVersion: native.sourceVersion || state.currentVersion,
244
+ transactionId: native.id || state.transactionId,
245
+ code: native.reasonCode || state.code || 'update_rolled_back'
246
+ }, options)
247
+ };
248
+ }
249
+ if (native.state === 'committed') {
250
+ const targetVersion = native.targetVersion || state.latestVersion || state.currentVersion;
251
+ return {
252
+ action: 'reload_tabs',
253
+ state: transition(state, {
254
+ state: 'committed',
255
+ currentVersion: targetVersion,
256
+ latestVersion: targetVersion,
257
+ transactionId: '',
258
+ blocker: '',
259
+ blockers: [],
260
+ code: '',
261
+ message: ''
262
+ }, options)
263
+ };
264
+ }
265
+ if (native.state === 'staged' && ['applying', 'awaiting_health'].includes(phaseOf(state))) {
266
+ return {
267
+ action: 'retry_install',
268
+ state: transition(state, {
269
+ state: 'staged',
270
+ transactionId: native.id || state.transactionId,
271
+ blocker: '',
272
+ blockers: [],
273
+ code: '',
274
+ message: ''
275
+ }, options)
276
+ };
277
+ }
278
+ if (!native.state && ['applying', 'awaiting_health', 'rolling_back'].includes(phaseOf(state))) {
279
+ return {
280
+ action: 'none',
281
+ state: transition(state, {
282
+ state: 'failed',
283
+ code: 'update_transaction_lost',
284
+ message: options.transactionLostMessage
285
+ || 'The managed update transaction could not be recovered. Use the manual update command.'
286
+ }, options)
287
+ };
288
+ }
289
+ return { action: 'none', state };
290
+ }
291
+
292
+ function progressFor(value) {
293
+ const source = asObject(value);
294
+ const raw = asObject(source.progress).percent
295
+ ?? asObject(source.progress).value
296
+ ?? source.progressPercent
297
+ ?? (typeof source.progress === 'number' ? source.progress : undefined);
298
+ if (Number.isFinite(Number(raw))) {
299
+ const numeric = Number(raw);
300
+ return Math.max(0, Math.min(100, numeric <= 1 ? numeric * 100 : numeric));
301
+ }
302
+ return Number(PROGRESS[phaseOf(value)] || 0);
303
+ }
304
+
305
+ function activeStageFor(value) {
306
+ return ACTIVE_STAGE[phaseOf(value)] ?? -1;
307
+ }
308
+
309
+ function activePhases(surface = 'update_page') {
310
+ return [...(ACTIVE_PHASES[surface] || ACTIVE_PHASES.update_page)];
311
+ }
312
+
313
+ function isActive(value, surface = 'update_page') {
314
+ return activePhases(surface).includes(phaseOf(value));
315
+ }
316
+
317
+ root.CodexOverleafManagedUpdateProjection = Object.freeze({
318
+ ACTIVE_PHASES,
319
+ PHASE_ALIASES,
320
+ PHASE_TIMEOUTS,
321
+ PHASES,
322
+ PROGRESS,
323
+ TRANSITIONS,
324
+ activePhases,
325
+ activeStageFor,
326
+ ensurePhaseMetadata,
327
+ getPhaseTimeoutMs,
328
+ isActive,
329
+ isLegalTransition,
330
+ normalize,
331
+ phaseOf,
332
+ progressFor,
333
+ reconcile,
334
+ transition,
335
+ transitionCommand
336
+ });
337
+ })(typeof window !== 'undefined' ? window : globalThis);
@@ -0,0 +1,60 @@
1
+ (function initNativeRequestIdentity(root, factory) {
2
+ const api = factory();
3
+ if (typeof module === "object" && module.exports) {
4
+ module.exports = api;
5
+ }
6
+ if (root) {
7
+ root.CodexOverleafNativeRequestIdentity = api;
8
+ }
9
+ })(typeof globalThis !== "undefined" ? globalThis : this, function createNativeRequestIdentity() {
10
+ "use strict";
11
+
12
+ const MAX_REQUEST_ID_CHARS = 160;
13
+
14
+ function invalidIdentity(value) {
15
+ return {
16
+ ok: false,
17
+ error: {
18
+ code: "native_request_id_invalid",
19
+ message:
20
+ `Native request id must be a finite number or a non-empty string no longer than ${MAX_REQUEST_ID_CHARS} characters.`,
21
+ details: {
22
+ actualType: value === null ? "null" : typeof value,
23
+ actualLength: typeof value === "string" ? value.length : null,
24
+ limit: MAX_REQUEST_ID_CHARS
25
+ }
26
+ }
27
+ };
28
+ }
29
+
30
+ function validate(value) {
31
+ if (typeof value === "number") {
32
+ return Number.isFinite(value) ? { ok: true, id: value } : invalidIdentity(value);
33
+ }
34
+ if (typeof value !== "string" || value.length === 0 || value.length > MAX_REQUEST_ID_CHARS) {
35
+ return invalidIdentity(value);
36
+ }
37
+ return { ok: true, id: value };
38
+ }
39
+
40
+ function resolve(value, randomUUID) {
41
+ if (value === undefined || value === null || value === "") {
42
+ const generate =
43
+ typeof randomUUID === "function"
44
+ ? randomUUID
45
+ : typeof crypto !== "undefined" && typeof crypto.randomUUID === "function"
46
+ ? () => crypto.randomUUID()
47
+ : null;
48
+ if (!generate) {
49
+ return invalidIdentity(value);
50
+ }
51
+ return validate(generate());
52
+ }
53
+ return validate(value);
54
+ }
55
+
56
+ return Object.freeze({
57
+ MAX_REQUEST_ID_CHARS,
58
+ resolve
59
+ });
60
+ });
@@ -274,28 +274,9 @@
274
274
 
275
275
  function hashText(text) {
276
276
  const value = String(text ?? '');
277
- const nodeCrypto = getNodeCrypto();
278
- if (nodeCrypto) {
279
- return nodeCrypto.createHash('sha256').update(value, 'utf8').digest('hex');
280
- }
281
277
  return sha256Hex(utf8Bytes(value));
282
278
  }
283
279
 
284
- function getNodeCrypto() {
285
- if (typeof require !== 'function') {
286
- return null;
287
- }
288
- try {
289
- return require('node:crypto');
290
- } catch (error) {
291
- try {
292
- return require('crypto');
293
- } catch (fallbackError) {
294
- return null;
295
- }
296
- }
297
- }
298
-
299
280
  function normalizePath(value) {
300
281
  return String(value || '')
301
282
  .replace(/\s+/g, ' ')