codex-overleaf-link 2.2.1 → 2.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/README.md +25 -23
  2. package/extension/bootstrap/manifest.template.json +1 -1
  3. package/extension/runtime-manifest.json +5 -71
  4. package/extension/src/background.js +67 -63
  5. package/extension/src/backgroundUpdateCoordinator.js +128 -66
  6. package/extension/src/content/applyResultFormatters.js +2 -3
  7. package/extension/src/content/composerPanel.js +0 -2
  8. package/extension/src/content/contentRuntime.js +757 -1558
  9. package/extension/src/content/contextTray.js +4 -6
  10. package/extension/src/content/diagnosticsController.js +1 -2
  11. package/extension/src/content/diffReviewPanel.js +14 -13
  12. package/extension/src/content/generated/content.bundle.js +41605 -0
  13. package/extension/src/content/generated/content.bundle.meta.json +103 -0
  14. package/extension/src/content/legacyGlobalRegistry.js +129 -0
  15. package/extension/src/content/markdownText.js +17 -17
  16. package/extension/src/content/mathText.js +155 -9
  17. package/extension/src/content/modelPicker.js +1 -1
  18. package/extension/src/content/modelPickerSupport.js +10 -5
  19. package/extension/src/content/moduleRegistryKernel.js +125 -0
  20. package/extension/src/content/nativeCompatibilityController.js +313 -0
  21. package/extension/src/content/otWarmMirror.js +8 -6
  22. package/extension/src/content/pageBridgeClient.js +224 -0
  23. package/extension/src/content/panelMaintenance.js +8 -3
  24. package/extension/src/content/postNavigationSettlementPersistence.js +100 -0
  25. package/extension/src/content/projectProviderSelection.js +4 -12
  26. package/extension/src/content/projectSessionCleanup.js +3 -3
  27. package/extension/src/content/providerSettingsCoordinator.js +59 -26
  28. package/extension/src/content/providerSettingsDialog.js +30 -18
  29. package/extension/src/content/recentProjects.js +15 -23
  30. package/extension/src/content/runGuidanceController.js +1 -1
  31. package/extension/src/content/runQueueScheduler.js +129 -29
  32. package/extension/src/content/runSettlementPersistence.js +51 -0
  33. package/extension/src/content/runTimelineView.js +17 -8
  34. package/extension/src/content/scopedPersistenceCoordinator.js +100 -0
  35. package/extension/src/content/scopedPersistenceDetachedCommit.js +47 -0
  36. package/extension/src/content/scopedPersistencePanelState.js +111 -0
  37. package/extension/src/content/sessionManager.js +15 -12
  38. package/extension/src/content/sessionPersistence.js +67 -8
  39. package/extension/src/content/updateNotice.js +10 -4
  40. package/extension/src/content/writebackOrchestrator.js +89 -68
  41. package/extension/src/contentScript.js +5 -1
  42. package/extension/src/page/trackedChangesLifecycle.js +16 -5
  43. package/extension/src/page/writebackRouter.js +6 -2
  44. package/extension/src/pageBridge.js +47 -98
  45. package/extension/src/shared/agentTranscript.js +18 -8
  46. package/extension/src/shared/compatibility.js +1 -1
  47. package/extension/src/shared/governanceRules.js +1 -1
  48. package/extension/src/shared/i18n.js +2 -4
  49. package/extension/src/shared/managedUpdateProjection.js +337 -0
  50. package/extension/src/shared/nativeRequestIdentity.js +60 -0
  51. package/extension/src/shared/otText.js +0 -19
  52. package/extension/src/shared/pageRpcContract.js +136 -0
  53. package/extension/src/shared/runExecutionSnapshot.js +187 -0
  54. package/extension/src/shared/runExecutionSnapshotCodec.js +171 -0
  55. package/extension/src/shared/runInputQueue.js +81 -34
  56. package/extension/src/shared/scopedPersistenceBrowserAdapter.js +79 -0
  57. package/extension/src/shared/scopedPersistenceQueuePolicy.js +200 -0
  58. package/extension/src/shared/scopedPersistenceReceipt.js +60 -0
  59. package/extension/src/shared/scopedPersistenceTransaction.js +241 -0
  60. package/extension/src/shared/sessionState.js +110 -59
  61. package/extension/src/shared/settlementFacts.js +351 -0
  62. package/extension/src/shared/storageDb.js +25 -10
  63. package/extension/src/shared/storageMigration.js +275 -61
  64. package/extension/src/shared/storageSessionClaims.js +60 -0
  65. package/extension/src/shared/updateConsent.js +4 -0
  66. package/extension/src/shared/updateRevocationIntent.js +109 -0
  67. package/extension/src/shared/writebackEvidenceProjection.js +150 -0
  68. package/extension/src/shared/writebackOperationEvidence.js +153 -0
  69. package/extension/src/shared/writebackSettlement.js +727 -0
  70. package/extension/styles/panel.css +18 -8
  71. package/native-host/src/codexPromptAssembly.js +1 -1
  72. package/native-host/src/codexRuntimeIdentity.js +1 -1
  73. package/native-host/src/codexSessionRunner.js +7 -13
  74. package/native-host/src/index.js +3 -69
  75. package/native-host/src/managedInstall.js +50 -8
  76. package/native-host/src/nativeTransportEnvelope.js +148 -0
  77. package/native-host/src/taskRunnerRuntime.js +14 -84
  78. package/native-host/src/updateManager.js +36 -1
  79. package/package.json +13 -5
@@ -266,8 +266,6 @@
266
266
  writeMode: 'Write mode',
267
267
  modeAsk: 'Ask',
268
268
  modeAskTitle: 'Read and analyze only. Do not write to Overleaf.',
269
- modeConfirm: 'Suggest',
270
- modeConfirmTitle: 'Show a change plan first, then write after approval.',
271
269
  modeAuto: 'Auto',
272
270
  modeAutoTitle: 'Write directly after authorization. Deletes still require confirmation.',
273
271
  addContext: 'Add @ context',
@@ -403,6 +401,7 @@
403
401
  refreshProbeDone: 'Refreshed: {status}',
404
402
  refreshProbeFailed: 'Refresh failed. Reload Overleaf and try again.',
405
403
  processedFailed: 'Failed {elapsed}',
404
+ processedCancelled: 'Cancelled {elapsed}',
406
405
  processing: 'Running {elapsed}',
407
406
  processed: 'Done {elapsed}',
408
407
  restoredRunStoppedTitle: 'Stopped tracking this run after a page refresh',
@@ -779,8 +778,6 @@
779
778
  writeMode: '写入模式',
780
779
  modeAsk: '只问不改',
781
780
  modeAskTitle: '只读取和分析,不写入 Overleaf',
782
- modeConfirm: '建议修改',
783
- modeConfirmTitle: '先给出修改方案,确认后写入',
784
781
  modeAuto: '自动写入',
785
782
  modeAutoTitle: '授权后直接写入,删除仍需确认',
786
783
  addContext: '添加 @ 上下文',
@@ -916,6 +913,7 @@
916
913
  refreshProbeDone: '已重新检测:{status}',
917
914
  refreshProbeFailed: '检测失败:请刷新 Overleaf 页面后重试',
918
915
  processedFailed: '处理失败 {elapsed}',
916
+ processedCancelled: '已取消 {elapsed}',
919
917
  processing: '处理中 {elapsed}',
920
918
  processed: '已处理 {elapsed}',
921
919
  restoredRunStoppedTitle: '页面刷新后已停止跟踪这轮任务',
@@ -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, ' ')
@@ -0,0 +1,136 @@
1
+ (function (root, factory) {
2
+ var api = factory();
3
+ if (typeof module === 'object' && module.exports) module.exports = api;
4
+ if (root) root.CodexOverleafPageRpcContract = api;
5
+ })(typeof window !== 'undefined' ? window : (typeof globalThis !== 'undefined' ? globalThis : this), function () {
6
+ 'use strict';
7
+
8
+ var REVISION = '2026-07-31-page-rpc-contract-v14';
9
+ var METHODS = freezeCatalog({
10
+ initializeCapability: entry('bridge.initialize', 'control', 'none', 'default', false, 'none'),
11
+ probe: entry('bridge.probe', 'read', 'none', 'default', false, 'safe'),
12
+ cancelActiveWrite: entry('write.cancel', 'control', 'none', 'default', false, 'idempotent'),
13
+ getProjectSnapshot: entry('snapshot.read', 'read', 'none', 'snapshot', true, 'safe'),
14
+ getProjectFileList: entry('snapshot.list', 'read', 'none', 'file_list', true, 'safe'),
15
+ invalidateProjectSnapshot: entry('snapshot.invalidate', 'cache', 'none', 'default', false, 'idempotent'),
16
+ createCheckpoint: entry('checkpoint.create', 'page_state', 'none', 'default', false, 'no_retry'),
17
+ ensureReviewing: entry('reviewing.enable', 'page_state', 'optional', 'default', true, 'idempotent'),
18
+ ensureEditing: entry('reviewing.disable', 'page_state', 'optional', 'default', true, 'idempotent'),
19
+ applyOperations: entry('document.write', 'document', 'required', 'writeback', true, 'no_retry'),
20
+ jumpToPosition: entry('editor.navigate', 'navigation', 'required', 'default', false, 'idempotent'),
21
+ rejectTrackedChanges: entry('tracked_changes.reject', 'document', 'required', 'lifecycle', true, 'no_retry'),
22
+ acceptTrackedChanges: entry('tracked_changes.accept', 'document', 'required', 'lifecycle', true, 'no_retry'),
23
+ triggerCompile: entry('compile.trigger', 'page_state', 'required', 'compile', false, 'no_retry'),
24
+ getCompileLog: entry('compile.log', 'read', 'required', 'compile', true, 'safe'),
25
+ getCompileState: entry('compile.state', 'read', 'none', 'default', false, 'safe'),
26
+ waitForSaveState: entry('save.wait', 'read', 'none', 'default', true, 'safe'),
27
+ startOtObserver: entry('ot.start', 'observer', 'none', 'default', false, 'idempotent'),
28
+ stopOtObserver: entry('ot.stop', 'observer', 'none', 'default', false, 'idempotent'),
29
+ getOtStatus: entry('ot.status', 'read', 'none', 'default', false, 'safe'),
30
+ drainOtEvents: entry('ot.drain', 'observer', 'none', 'default', false, 'no_retry')
31
+ });
32
+
33
+ function entry(capability, mutation, projectIdentity, timeoutClass, cancellable, retryClass) {
34
+ return {
35
+ capability: capability,
36
+ mutation: mutation,
37
+ projectIdentity: projectIdentity,
38
+ timeoutClass: timeoutClass,
39
+ cancellation: cancellable ? 'content_abort' : 'none',
40
+ retryClass: retryClass,
41
+ failureClass: mutation === 'document' ? 'write' : mutation
42
+ };
43
+ }
44
+
45
+ function getMethod(name) {
46
+ return typeof name === 'string' && METHODS[name] ? METHODS[name] : null;
47
+ }
48
+
49
+ function listMethods() {
50
+ return Object.keys(METHODS);
51
+ }
52
+
53
+ function isCancellable(name) {
54
+ return getMethod(name)?.cancellation === 'content_abort';
55
+ }
56
+
57
+ function requiresProjectIdentity(name) {
58
+ return getMethod(name)?.projectIdentity === 'required';
59
+ }
60
+
61
+ function resolveTimeoutMs(name, overrides) {
62
+ overrides = overrides || {};
63
+ var timeoutClass = getMethod(name)?.timeoutClass || 'default';
64
+ var defaults = {
65
+ default: 8000,
66
+ snapshot: 70000,
67
+ file_list: 35000,
68
+ compile: 75000,
69
+ lifecycle: 120000,
70
+ writeback: 45000
71
+ };
72
+ var candidate = Number(overrides[timeoutClass]);
73
+ return Number.isFinite(candidate) && candidate > 0 ? candidate : defaults[timeoutClass];
74
+ }
75
+
76
+ function resolveDispatchPolicy(name, overrides) {
77
+ var method = getMethod(name);
78
+ return Object.freeze({
79
+ timeoutMs: resolveTimeoutMs(name, overrides),
80
+ cancellation: method ? method.cancellation : 'none',
81
+ retryClass: method ? method.retryClass : 'none',
82
+ automaticAttempts: 1
83
+ });
84
+ }
85
+
86
+ function capabilityReport() {
87
+ var report = {};
88
+ listMethods().forEach(function (name) {
89
+ var method = METHODS[name];
90
+ report[name] = {
91
+ capability: method.capability,
92
+ mutation: method.mutation,
93
+ projectIdentity: method.projectIdentity,
94
+ retryClass: method.retryClass,
95
+ timeoutClass: method.timeoutClass,
96
+ cancellation: method.cancellation
97
+ };
98
+ });
99
+ return report;
100
+ }
101
+
102
+ function withCapabilityReport(capabilities) {
103
+ return Object.assign({}, capabilities || {}, { pageRpc: capabilityReport() });
104
+ }
105
+
106
+ function normalizeFailure(method, error) {
107
+ var contract = getMethod(method);
108
+ var message = error && error.message ? error.message : String(error || 'Page bridge request failed');
109
+ return {
110
+ ok: false,
111
+ code: 'page_bridge_dispatch_failed',
112
+ error: message,
113
+ rpcMethod: typeof method === 'string' ? method : '',
114
+ failureClass: contract ? contract.failureClass : 'unknown'
115
+ };
116
+ }
117
+
118
+ function freezeCatalog(catalog) {
119
+ Object.keys(catalog).forEach(function (name) { Object.freeze(catalog[name]); });
120
+ return Object.freeze(catalog);
121
+ }
122
+
123
+ return Object.freeze({
124
+ METHODS: METHODS,
125
+ REVISION: REVISION,
126
+ capabilityReport: capabilityReport,
127
+ getMethod: getMethod,
128
+ isCancellable: isCancellable,
129
+ listMethods: listMethods,
130
+ normalizeFailure: normalizeFailure,
131
+ requiresProjectIdentity: requiresProjectIdentity,
132
+ resolveDispatchPolicy: resolveDispatchPolicy,
133
+ resolveTimeoutMs: resolveTimeoutMs,
134
+ withCapabilityReport: withCapabilityReport
135
+ });
136
+ });