codex-overleaf-link 2.5.1 → 2.6.5

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 (111) hide show
  1. package/README.md +149 -398
  2. package/README.zh-CN.md +153 -404
  3. package/extension/bootstrap/manifest.template.json +1 -1
  4. package/extension/src/background.js +148 -3
  5. package/extension/src/backgroundSharedSessions.js +55 -2
  6. package/extension/src/backgroundUpdateCoordinator.js +128 -12
  7. package/extension/src/content/activeTurnControl.js +164 -7
  8. package/extension/src/content/composerAttachments.js +46 -10
  9. package/extension/src/content/composerPanel.js +92 -12
  10. package/extension/src/content/contentRuntime.js +1114 -321
  11. package/extension/src/content/contextTray.js +8 -2
  12. package/extension/src/content/contextUsageView.js +267 -0
  13. package/extension/src/content/diagnosticsController.js +77 -34
  14. package/extension/src/content/diagnosticsExport.js +1 -1
  15. package/extension/src/content/diagnosticsPanel.js +147 -42
  16. package/extension/src/content/generated/content.bundle.js +5761 -1237
  17. package/extension/src/content/generated/content.bundle.meta.json +6 -3
  18. package/extension/src/content/legacyGlobalRegistry.js +4 -0
  19. package/extension/src/content/localSkillsPanel.js +6 -4
  20. package/extension/src/content/markdownDomRenderer.js +5 -3
  21. package/extension/src/content/markdownText.js +53 -14
  22. package/extension/src/content/modelPicker.js +29 -19
  23. package/extension/src/content/nativeCompatibilityController.js +52 -19
  24. package/extension/src/content/otWarmMirror.js +1 -1
  25. package/extension/src/content/pageBridgeClient.js +7 -7
  26. package/extension/src/content/panelMaintenance.js +10 -2
  27. package/extension/src/content/panelRenderer.js +55 -28
  28. package/extension/src/content/pendingInputView.js +1 -1
  29. package/extension/src/content/projectSettingsCoordinator.js +30 -6
  30. package/extension/src/content/providerSettingsDialog.js +28 -19
  31. package/extension/src/content/recentProjects.js +30 -19
  32. package/extension/src/content/runActivitySummary.js +122 -14
  33. package/extension/src/content/runCompletionMeta.js +194 -6
  34. package/extension/src/content/runController.js +2 -1
  35. package/extension/src/content/runFailureNotice.js +8 -1
  36. package/extension/src/content/runPresence.js +18 -8
  37. package/extension/src/content/runQueueScheduler.js +27 -13
  38. package/extension/src/content/runRecoveryActions.js +2 -1
  39. package/extension/src/content/runResultActions.js +73 -24
  40. package/extension/src/content/runReviewController.js +102 -37
  41. package/extension/src/content/runTimelineView.js +224 -25
  42. package/extension/src/content/scopedPersistencePanelState.js +124 -46
  43. package/extension/src/content/selectionContextView.js +30 -3
  44. package/extension/src/content/sensitivePreflight.js +14 -1
  45. package/extension/src/content/sessionManager.js +49 -3
  46. package/extension/src/content/sessionMenuView.js +54 -18
  47. package/extension/src/content/sessionPersistence.js +25 -5
  48. package/extension/src/content/settingsPanel.js +15 -2
  49. package/extension/src/content/settingsWorkbench.js +127 -89
  50. package/extension/src/content/sharedSessionViewSync.js +13 -4
  51. package/extension/src/content/subagentActivityView.js +23 -7
  52. package/extension/src/content/updateNotice.js +60 -6
  53. package/extension/src/content/writebackOrchestrator.js +301 -72
  54. package/extension/src/content/writingStyleSettings.js +70 -17
  55. package/extension/src/page/compileBridge.js +193 -180
  56. package/extension/src/page/overleafCapabilities.js +4 -1
  57. package/extension/src/page/overleafEditor.js +12 -3
  58. package/extension/src/page/overleafProjectSnapshot.js +56 -3
  59. package/extension/src/page/snapshotRouter.js +75 -66
  60. package/extension/src/page/textCoordinates.js +17 -1
  61. package/extension/src/page/textFileCreator.js +9 -1
  62. package/extension/src/page/treeOperations.js +200 -19
  63. package/extension/src/page/writebackRouter.js +172 -113
  64. package/extension/src/pageBridge.js +124 -73
  65. package/extension/src/popup.js +1 -1
  66. package/extension/src/shared/agentTranscript.js +372 -18
  67. package/extension/src/shared/auditRecords.js +3 -0
  68. package/extension/src/shared/compatibility.js +1 -1
  69. package/extension/src/shared/compileAdapter.js +58 -17
  70. package/extension/src/shared/composerAttachmentDraft.js +27 -7
  71. package/extension/src/shared/contextUsage.js +125 -0
  72. package/extension/src/shared/failureReasons.js +57 -3
  73. package/extension/src/shared/i18n.js +315 -29
  74. package/extension/src/shared/lineReferences.js +38 -31
  75. package/extension/src/shared/pageRpcContract.js +1 -1
  76. package/extension/src/shared/panelIcons.js +39 -0
  77. package/extension/src/shared/projectFiles.js +2 -1
  78. package/extension/src/shared/reviewing.js +130 -1
  79. package/extension/src/shared/runActivityModel.js +70 -4
  80. package/extension/src/shared/runExecutionSnapshot.js +13 -0
  81. package/extension/src/shared/scopedPersistenceBrowserAdapter.js +34 -0
  82. package/extension/src/shared/sessionState.js +63 -4
  83. package/extension/src/shared/sessionTitle.js +14 -1
  84. package/extension/src/shared/sharedSessionBridge.js +81 -13
  85. package/extension/src/shared/staleGuard.js +130 -63
  86. package/extension/src/shared/storageDb.js +35 -3
  87. package/extension/src/shared/storageMigration.js +111 -2
  88. package/extension/src/shared/storageRunActions.js +62 -8
  89. package/extension/src/shared/undoOperations.js +126 -28
  90. package/extension/src/shared/updateRuntimeIdentity.js +16 -2
  91. package/extension/src/shared/writebackSettlement.js +21 -3
  92. package/extension/styles/panel.css +1293 -549
  93. package/native-host/src/bootstrapUpdate.js +52 -16
  94. package/native-host/src/chatBridgeRequest.js +60 -6
  95. package/native-host/src/chatBridgeResponse.js +10 -1
  96. package/native-host/src/chatCompletionsBridge.js +29 -2
  97. package/native-host/src/codexPromptAssembly.js +86 -118
  98. package/native-host/src/codexSessionRunner.js +127 -17
  99. package/native-host/src/debugLog.js +27 -3
  100. package/native-host/src/index.js +42 -11
  101. package/native-host/src/localSkills.js +1 -1
  102. package/native-host/src/mirrorWorkspace.js +47 -3
  103. package/native-host/src/nativeEnvironment.js +27 -5
  104. package/native-host/src/providerContextError.js +20 -0
  105. package/native-host/src/providerRuntime.js +1 -0
  106. package/native-host/src/skills/reviewer-response/SKILL.md +65 -0
  107. package/native-host/src/taskRunnerRuntime.js +28 -8
  108. package/native-host/src/textPatch.js +473 -377
  109. package/native-host/src/updateManager.js +144 -21
  110. package/native-host/src/writingStyleRuntime.js +1 -1
  111. package/package.json +5 -8
@@ -1,13 +1,14 @@
1
1
  (function initCodexOverleafTreeOperations(root, factory) {
2
2
  if (typeof module === 'object' && module.exports) {
3
- module.exports = factory();
3
+ module.exports = factory(require('../shared/staleGuard'), require('../shared/otText'));
4
4
  } else {
5
- root.CodexOverleafTreeOperations = factory();
5
+ root.CodexOverleafTreeOperations = factory(root.CodexOverleafStaleGuard, root.CodexOverleafOtText);
6
6
  }
7
- })(typeof window !== 'undefined' ? window : globalThis, function treeOperationsFactory() {
7
+ })(typeof window !== 'undefined' ? window : globalThis, function treeOperationsFactory(StaleGuard, OtText) {
8
8
  'use strict';
9
9
 
10
10
  const TEXT_PATH_EXTENSION_PATTERN = '(?:tex|bib|sty|cls|bst|bbx|cbx|lbx|cfg|def|clo|ist|txt|md|latex)';
11
+ const ACTIVE_EDITOR_STABILITY_MS = 300;
11
12
 
12
13
  function create({
13
14
  window: windowDep,
@@ -15,6 +16,8 @@
15
16
  normalizePath = fallbackNormalizeSafeProjectPath,
16
17
  readActiveEditorText = () => '',
17
18
  getActiveEditorIdentity = () => null,
19
+ getCodeMirrorEditorView = () => getActiveEditorIdentity()?.view,
20
+ getCodeMirrorEditorViews = () => [getCodeMirrorEditorView()].filter(Boolean),
18
21
  activeEditorIdentityChanged = () => false,
19
22
  getActiveFilePathFromEditorStore: readActiveFilePathFromEditorStore = () => ''
20
23
  } = {}) {
@@ -93,10 +96,144 @@
93
96
  }
94
97
 
95
98
  function getProjectId() {
96
- const match = window.location.pathname.match(/\/project\/([^/]+)/);
99
+ const match = (window.location?.pathname || '').match(/\/project\/([^/]+)/);
97
100
  return match ? decodeURIComponent(match[1]) : null;
98
101
  }
99
102
 
103
+ function normalizeDocumentId(value) {
104
+ return typeof value === 'string' && /^[a-f0-9]{24}$/i.test(value) ? value.toLowerCase() : '';
105
+ }
106
+
107
+ // Navigation's broad scanners and cached basename maps are deliberately not
108
+ // evidence. Only the scoped project tree and literal native doc rows qualify.
109
+ function resolveTargetDocument(path, { projectId = getProjectId() } = {}) {
110
+ const failure = (status, reason) => ({ status, projectId, path, reason });
111
+ if (!path || normalizeSafeProjectPath(path) !== path || !projectId || getProjectId() !== projectId)
112
+ return failure('ambiguous', 'project_changed');
113
+ const records = [];
114
+ let truncated = false;
115
+ const seen = new Set();
116
+ function walk(folder, prefix, depth) {
117
+ if (!folder || typeof folder !== 'object') return;
118
+ if (depth > 7 || seen.has(folder) || records.length >= 120) { truncated = true; return; }
119
+ seen.add(folder);
120
+ const docs = folder.docs || [], folders = folder.folders || [];
121
+ if (!Array.isArray(docs) || !Array.isArray(folders)) { truncated = true; return; }
122
+ if (docs.length > 250 || folders.length > 250) truncated = true;
123
+ for (const doc of docs.slice(0, 250)) {
124
+ if (records.length >= 120) { truncated = true; break; }
125
+ const name = doc?.name, id = normalizeDocumentId(doc?._id);
126
+ const filePath = typeof name === 'string' && !/[/\\]/.test(name) ? normalizeSafeProjectPath(prefix + name) : '';
127
+ if (!filePath || !id) { truncated = true; continue; }
128
+ records.push({ path: filePath, id, source: 'project-tree' });
129
+ }
130
+ for (const child of folders.slice(0, 250)) {
131
+ const name = child?.name;
132
+ if (typeof name !== 'string' || /[/\\]/.test(name) || !normalizeSafeProjectPath(prefix + name)) {
133
+ truncated = true;
134
+ continue;
135
+ }
136
+ walk(child, prefix + name + '/', depth + 1);
137
+ }
138
+ }
139
+ const project = window._ide?.project;
140
+ if (project?._id && project._id !== projectId) return failure('ambiguous', 'project_changed');
141
+ const editorProjectId = document.querySelector?.('[data-project-id]')?.getAttribute?.('data-project-id');
142
+ if (editorProjectId && editorProjectId !== projectId) return failure('ambiguous', 'project_changed');
143
+ if (project?._id === projectId && project.rootFolder) {
144
+ const roots = Array.isArray(project.rootFolder) ? project.rootFolder : [project.rootFolder];
145
+ if (roots.length !== 1) truncated = true;
146
+ for (const root of roots.slice(0, 1)) walk(root, '', 0);
147
+ }
148
+ const root = document.querySelector?.('[data-testid="file-tree-list-root"]');
149
+ const nodes = root?.querySelectorAll?.('[data-file-type="doc"][data-file-id]') || [];
150
+ if (nodes.length >= 120) truncated = true;
151
+ for (let index = 0; index < Math.min(nodes.length, 120); index += 1) {
152
+ const node = nodes[index];
153
+ const row = node.closest?.('[role="treeitem"]');
154
+ if (!row || row.querySelector?.('[data-file-type="doc"][data-file-id]') !== node) continue;
155
+ const filePath = readNativeTreePath(node), id = normalizeDocumentId(node.getAttribute?.('data-file-id'));
156
+ if (!filePath || !id) { truncated = true; continue; }
157
+ records.push({ path: filePath, id, source: 'native-tree' });
158
+ }
159
+ const matches = records.filter(record => record.path === path);
160
+ const ids = new Set(matches.map(record => record.id));
161
+ if (ids.size > 1 || matches.some(match => records.some(record => record.id === match.id && record.path !== path)))
162
+ return failure('ambiguous', 'identity_conflict');
163
+ if (truncated || ids.size !== 1) return failure('unavailable', 'identity_unavailable');
164
+ return { status: 'known', projectId, path, documentId: matches[0].id, source: matches[0].source };
165
+ }
166
+
167
+ function readNavigationDocumentIds() {
168
+ const store = window.overleaf?.unstable?.store || window.Overleaf?.unstable?.store;
169
+ const values = [window._ide?.editor?.open_doc_id, window._ide?.editor?.openDocId];
170
+ for (const key of ['editor.open_doc_id', 'editor.openDocId']) {
171
+ try { values.push(store?.get?.(key)); } catch (_error) { /* No positive evidence from a store hint. */ }
172
+ }
173
+ return values.filter(value => typeof value === 'string' && value.length).map(value => value.toLowerCase());
174
+ }
175
+
176
+ function captureEditorDocument({ path, projectId = getProjectId() } = {}) {
177
+ const fail = (identityState, reason) => ({ ok: false, code: 'document_identity_mismatch', identityState, reason });
178
+ try {
179
+ const target = resolveTargetDocument(path, { projectId });
180
+ if (target.status === 'ambiguous') return fail('mismatch', target.reason);
181
+ const hints = readNavigationDocumentIds();
182
+ if (new Set(hints).size > 1 || (target.status === 'known' && hints.some(id => id !== target.documentId)))
183
+ return fail('unavailable', 'navigation_conflict');
184
+ const views = [...new Set(getCodeMirrorEditorViews())];
185
+ if (views.length > 1) return fail('mismatch', 'identity_ambiguous');
186
+ const view = views[0], state = view?.state, doc = state?.doc;
187
+ if (!view || view.dom?.isConnected === false || typeof view.dispatch !== 'function' || !doc)
188
+ return fail('unavailable', 'identity_unavailable');
189
+ if (!Array.isArray(state.values)) return fail('unavailable', 'identity_unavailable');
190
+ if (state.values.length > 2048) return fail('mismatch', 'identity_ambiguous');
191
+ const entries = state.values.filter(value => value && typeof value === 'object'
192
+ && Object.prototype.hasOwnProperty.call(value, 'ranges') && value.ranges != null
193
+ && Object.prototype.hasOwnProperty.call(value, 'threads'));
194
+ if (!entries.length) return fail('unavailable', 'identity_unavailable');
195
+ if (entries.length !== 1) return fail('mismatch', 'identity_ambiguous');
196
+ const entry = entries[0], documentId = normalizeDocumentId(entry.ranges?.docId);
197
+ if (!Object.prototype.hasOwnProperty.call(entry, 'threads') || !Array.isArray(entry.ranges?.changes)
198
+ || !Array.isArray(entry.ranges?.comments) || !documentId) return fail('mismatch', 'identity_conflict');
199
+ // Overleaf installs ranges asynchronously. A stale switch timeout may
200
+ // attach a previous document's ranges to a newly mounted state's text.
201
+ if (readNavigationDocumentIds().some(id => id !== documentId)) return fail('unavailable', 'navigation_conflict');
202
+ if (target.status !== 'known') return fail('unavailable', target.reason);
203
+ const documentIdentity = { version: 1, source: 'cm-ranges-v1', projectId, path,
204
+ targetDocumentId: target.documentId, documentId };
205
+ const decision = StaleGuard.assessDocumentProvenance({ path, projectId, documentIdentity });
206
+ if (!decision.ok) return { ...decision, reason: 'identity_conflict' };
207
+ const text = doc.toString();
208
+ if (typeof text !== 'string' || !Number.isSafeInteger(doc.length) || doc.length !== text.length)
209
+ return fail('unavailable', 'unread_document');
210
+ // The target resolution and text read are synchronous. Emission and
211
+ // dispatch resolve again; retain the cheap local capture checks here.
212
+ const currentViews = [...new Set(getCodeMirrorEditorViews())];
213
+ if (currentViews.length !== 1 || currentViews[0] !== view || view.state !== state || state.doc !== doc
214
+ || getProjectId() !== projectId
215
+ || readNavigationDocumentIds().some(id => id !== documentId)) return fail('unavailable', 'capture_changed');
216
+ return { ok: true, identityState: 'match', text, documentIdentity, view, state, doc };
217
+ } catch (_error) {
218
+ return fail('unavailable', 'unread_document');
219
+ }
220
+ }
221
+
222
+ function isCaptureCurrent(capture) {
223
+ if (!capture?.ok || !capture.documentIdentity) return false;
224
+ const current = captureEditorDocument(capture.documentIdentity);
225
+ return current.ok && current.view === capture.view && current.state === capture.state && current.doc === capture.doc
226
+ && current.text === capture.text && current.documentIdentity.documentId === capture.documentIdentity.documentId;
227
+ }
228
+
229
+ function serializeEditorCapture(capture) {
230
+ if (!capture?.ok || typeof capture.text !== 'string')
231
+ return { ok: false, code: 'document_identity_mismatch', identityState: 'unavailable' };
232
+ const contentSha256 = OtText.hashText(capture.text);
233
+ if (!isCaptureCurrent(capture)) return { ok: false, code: 'document_identity_mismatch', identityState: 'unavailable' };
234
+ return { ok: true, documentIdentity: { ...capture.documentIdentity, contentSha256 } };
235
+ }
236
+
100
237
  function readNativeTreePath(node) {
101
238
  const row = node?.closest?.('[role="treeitem"]');
102
239
  const root = document.querySelector?.('[data-testid="file-tree-list-root"]');
@@ -761,13 +898,18 @@
761
898
  }
762
899
 
763
900
  function collectProjectTextPaths(activePath) {
901
+ const docRecords = collectDocRecords();
764
902
  const rawPaths = [
765
903
  activePath,
766
- ...collectDocRecords().map(record => record.path),
904
+ ...docRecords.map(record => record.path),
767
905
  ...collectInternalProjectPaths(),
768
906
  ...collectDomProjectPaths()
769
907
  ];
770
- return window.CodexOverleafProjectFiles.collectUniqueTextPaths(rawPaths, 80);
908
+ const paths = window.CodexOverleafProjectFiles.collectUniqueTextPaths(rawPaths, 80);
909
+ if (docRecords.truncated) {
910
+ paths.truncated = true;
911
+ }
912
+ return paths;
771
913
  }
772
914
 
773
915
  function collectDocRecords(options = {}) {
@@ -784,6 +926,8 @@
784
926
  seen.add(key);
785
927
  records.push(record);
786
928
  if (records.length >= 120) {
929
+ // The internal collector also stops here, so exhaustion is unproven.
930
+ records.truncated = true;
787
931
  break;
788
932
  }
789
933
  }
@@ -1381,28 +1525,61 @@
1381
1525
 
1382
1526
  async function waitForActiveEditorText(filePath, timeoutMs, options = {}) {
1383
1527
  const deadline = Date.now() + timeoutMs;
1528
+ const projectId = options.projectId || getProjectId();
1529
+ const identityDeadline = Math.min(deadline, Date.now() + ACTIVE_EDITOR_STABILITY_MS);
1384
1530
  let lastText = '';
1531
+ let stableView = null, stablePath = '', stableDocumentId = '', stableSince = 0, mismatch = null;
1385
1532
  while (Date.now() < deadline) {
1386
- const activeFileMatches = !filePath || getActiveFilePath() === filePath;
1387
- const text = readActiveEditorText();
1533
+ if (getProjectId() !== projectId) return { ok: false, code: 'document_identity_mismatch', reason: 'project_changed' };
1534
+ const activePath = options.passive ? getEditorStoreFilePath({ passive: true }) : getActiveFilePath();
1535
+ const activeFileMatches = Boolean(activePath) && (!filePath || activePath === filePath);
1536
+ const capture = captureEditorDocument({ path: filePath || activePath, projectId });
1537
+ mismatch = capture.identityState === 'mismatch' || capture.reason === 'navigation_conflict'
1538
+ ? { ...capture, identityState: 'mismatch' } : null;
1539
+ let text, identity, observed;
1540
+ try {
1541
+ identity = getActiveEditorIdentity();
1542
+ text = capture.ok ? capture.text : readActiveEditorText();
1543
+ if (identity?.type === 'codemirror-view') observed = identity.doc?.toString?.();
1544
+ else if (identity?.type === 'textarea') observed = identity.node?.value;
1545
+ else if (identity?.type === 'contenteditable') observed = identity.node?.innerText ?? identity.node?.textContent;
1546
+ } catch (_error) { text = undefined; }
1547
+ const view = capture.ok ? capture.view : identity?.view || identity?.node;
1388
1548
  lastText = text;
1389
- const knownEmptyEditor = options.allowEmpty === true && typeof text === 'string' && /^(?:\r?\n)*$/.test(text)
1390
- && Boolean(getActiveEditorIdentity());
1549
+ const knownEmptyEditor = options.allowEmpty === true && typeof text === 'string' && !text.trim()
1550
+ && typeof observed === 'string' && observed === text;
1391
1551
  const textReady = options.requireEmpty === true ? knownEmptyEditor
1392
1552
  : knownEmptyEditor || window.CodexOverleafProjectFiles.isUsableProjectFileContent(text);
1393
- const signatureChanged = !options.notSignature || contentSignature(text) !== options.notSignature;
1394
- if (activeFileMatches && textReady && signatureChanged) {
1395
- return {
1396
- ok: true,
1397
- text
1398
- };
1553
+ const signatureChanged = capture.ok || !options.notSignature || contentSignature(text) !== options.notSignature;
1554
+ const identityReady = capture.ok || (capture.identityState === 'unavailable' && capture.reason !== 'navigation_conflict'
1555
+ && Date.now() >= identityDeadline);
1556
+ if (activeFileMatches && view && textReady && signatureChanged && identityReady) {
1557
+ const documentId = capture.documentIdentity?.documentId || '';
1558
+ if (view !== stableView || activePath !== stablePath || documentId !== stableDocumentId) {
1559
+ stableView = view; stablePath = activePath; stableDocumentId = documentId; stableSince = Date.now();
1560
+ } else if (Date.now() - stableSince >= ACTIVE_EDITOR_STABILITY_MS) {
1561
+ const provenance = StaleGuard.assessDocumentProvenance({ path: activePath, text, references: options.references,
1562
+ projectId, documentIdentity: capture.documentIdentity });
1563
+ if (provenance.ok) {
1564
+ if (!capture.ok) return { ok: true, text, identityState: 'fallback' };
1565
+ const serialized = serializeEditorCapture(capture);
1566
+ if (serialized.ok) return { ok: true, text, capture, documentIdentity: serialized.documentIdentity, identityState: 'match' };
1567
+ mismatch = serialized;
1568
+ stableView = null;
1569
+ } else {
1570
+ mismatch = provenance;
1571
+ }
1572
+ }
1573
+ } else {
1574
+ stableView = null;
1399
1575
  }
1400
- await delay(150);
1576
+ await delay(Math.max(0, Math.min(150, deadline - Date.now())));
1401
1577
  }
1402
1578
  return {
1403
1579
  ok: false,
1404
1580
  text: lastText,
1405
- reason: `Editor content was not ready for ${filePath || 'active file'}; last length ${String(lastText || '').length}`
1581
+ ...(mismatch || {}),
1582
+ reason: mismatch?.code || `Editor content was not ready for ${filePath || 'active file'}; last length ${String(lastText || '').length}`
1406
1583
  };
1407
1584
  }
1408
1585
 
@@ -1588,6 +1765,10 @@
1588
1765
  getEditorStoreFilePath,
1589
1766
  getRecentFileTreeSelectionPath,
1590
1767
  getProjectId,
1768
+ resolveTargetDocument,
1769
+ captureEditorDocument,
1770
+ isCaptureCurrent,
1771
+ serializeEditorCapture,
1591
1772
  navigateToFile,
1592
1773
  openFileByPath,
1593
1774
  projectPathExists,
@@ -1612,5 +1793,5 @@
1612
1793
  move: ['moveEntity', 'moveFile', 'moveDoc'], delete: ['deleteEntity', 'deleteFile', 'deleteDoc', 'removeEntity']
1613
1794
  }[type] || [];
1614
1795
  }
1615
- return { create, fileTreeMethodNames };
1796
+ return { create, fileTreeMethodNames, ACTIVE_EDITOR_STABILITY_MS };
1616
1797
  });