codex-overleaf-link 2.1.5 → 2.2.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 (67) hide show
  1. package/README.md +27 -23
  2. package/extension/bootstrap/background.js +14 -1
  3. package/extension/bootstrap/manifest.template.json +1 -1
  4. package/extension/runtime-manifest.json +10 -0
  5. package/extension/src/background.js +507 -8
  6. package/extension/src/backgroundUpdateCoordinator.js +284 -19
  7. package/extension/src/content/activeTurnControl.js +179 -0
  8. package/extension/src/content/composerPanel.js +14 -5
  9. package/extension/src/content/contentRuntime.js +330 -309
  10. package/extension/src/content/diagnosticsPanel.js +132 -1
  11. package/extension/src/content/markdownDomRenderer.js +451 -0
  12. package/extension/src/content/markdownText.js +86 -7
  13. package/extension/src/content/mathText.js +49 -4
  14. package/extension/src/content/modelPicker.js +14 -5
  15. package/extension/src/content/nativeChannel.js +7 -1
  16. package/extension/src/content/panelRenderer.js +112 -12
  17. package/extension/src/content/pendingInputView.js +106 -0
  18. package/extension/src/content/projectProviderSelection.js +68 -0
  19. package/extension/src/content/projectSessionCleanup.js +4 -0
  20. package/extension/src/content/providerSettingsCoordinator.js +170 -22
  21. package/extension/src/content/providerSettingsDialog.js +2 -2
  22. package/extension/src/content/recentProjects.js +7 -2
  23. package/extension/src/content/runGuidanceController.js +70 -0
  24. package/extension/src/content/runGuidanceView.js +48 -0
  25. package/extension/src/content/runQueueScheduler.js +260 -0
  26. package/extension/src/content/runTimelineView.js +33 -80
  27. package/extension/src/content/sessionManager.js +2 -2
  28. package/extension/src/content/sessionPersistence.js +62 -0
  29. package/extension/src/content/settingsPanel.js +1 -1
  30. package/extension/src/content/updateNotice.js +80 -1
  31. package/extension/src/page/writebackRouter.js +3 -9
  32. package/extension/src/shared/compatibility.js +2 -1
  33. package/extension/src/shared/i18n.js +38 -0
  34. package/extension/src/shared/providerProfiles.js +3 -2
  35. package/extension/src/shared/runInputQueue.js +253 -0
  36. package/extension/src/shared/sessionState.js +45 -22
  37. package/extension/src/shared/storageDb.js +39 -44
  38. package/extension/src/shared/storageMigration.js +123 -3
  39. package/extension/src/shared/storageRunActions.js +34 -1
  40. package/extension/src/shared/updateConsent.js +15 -4
  41. package/extension/styles/panel.css +319 -20
  42. package/extension/vendor/katex/katex.min.css +1 -1
  43. package/extension/vendor/markdown-it/LICENSE +22 -0
  44. package/extension/vendor/markdown-it/SOURCE.json +7 -0
  45. package/extension/vendor/markdown-it/VERSION +1 -0
  46. package/extension/vendor/markdown-it/markdown-it.min.js +3 -0
  47. package/native-host/src/anthropicBridgeRequest.js +3 -3
  48. package/native-host/src/anthropicBridgeResponse.js +58 -17
  49. package/native-host/src/anthropicMessagesBridge.js +156 -23
  50. package/native-host/src/chatBridgeRequest.js +1 -1
  51. package/native-host/src/chatBridgeResponse.js +12 -8
  52. package/native-host/src/chatCompletionsBridge.js +22 -40
  53. package/native-host/src/codexPromptAssembly.js +3 -1
  54. package/native-host/src/codexProviderLaunch.js +6 -3
  55. package/native-host/src/codexSessionRunner.js +65 -65
  56. package/native-host/src/codexSessionTiming.js +45 -0
  57. package/native-host/src/index.js +58 -3
  58. package/native-host/src/managedInstall.js +45 -7
  59. package/native-host/src/mirrorWorkspace.js +15 -8
  60. package/native-host/src/providerProfile.js +2 -2
  61. package/native-host/src/providerStreamLifecycle.js +100 -0
  62. package/native-host/src/responsesPassthroughBridge.js +636 -0
  63. package/native-host/src/taskRunnerRuntime.js +81 -2
  64. package/native-host/src/updateArchive.js +4 -3
  65. package/native-host/src/updateManager.js +306 -19
  66. package/native-host/src/updateTrust.js +1 -1
  67. package/package.json +3 -1
@@ -10,6 +10,7 @@
10
10
  getSelectedModel: options.getSelectedModel || (() => ''),
11
11
  getSelectedProviderId: options.getSelectedProviderId || (() => 'builtin'),
12
12
  setSelectedProviderId: options.setSelectedProviderId || (() => {}),
13
+ confirmProviderSwitch: options.confirmProviderSwitch || (() => true),
13
14
  clearSelectedModel: options.clearSelectedModel || (() => {}),
14
15
  refreshModelOptions: options.refreshModelOptions || (() => {}),
15
16
  persistInputs: options.persistInputs || (() => {}),
@@ -38,7 +39,7 @@
38
39
  setupCrossTabRefresh(instance, options.window || window);
39
40
  return {
40
41
  open: () => open(instance),
41
- refreshSummary: () => refresh(instance),
42
+ refreshSummary: () => refreshSummary(instance),
42
43
  ensureLoaded: () => ensureLoaded(instance),
43
44
  getRunSelection: providerId => instance.loaded
44
45
  ? Profiles.buildRunSelection(instance.catalog, providerId || instance.getSelectedProviderId())
@@ -93,9 +94,45 @@
93
94
  return instance.catalog;
94
95
  }
95
96
 
97
+ async function ensureLoadedWithRetry(instance) {
98
+ try {
99
+ return await ensureLoaded(instance);
100
+ } catch (_firstError) {
101
+ // Extension reloads can race the first Native Messaging connection.
102
+ // Keep the persisted project selection visible, then retry once after
103
+ // the service worker and Native Host have had a chance to reconnect.
104
+ updateSettingsSummary(instance);
105
+ await new Promise(resolve => setTimeout(resolve, 800));
106
+ try {
107
+ const result = await refresh(instance);
108
+ return result?.catalog || instance.catalog;
109
+ } catch (error) {
110
+ updateSettingsSummary(instance);
111
+ throw error;
112
+ }
113
+ }
114
+ }
115
+
116
+ async function refreshSummary(instance) {
117
+ // Project state hydrates before the Native Host catalog is guaranteed to
118
+ // be ready. Project the persisted selection immediately so Settings never
119
+ // claims Built-in Codex (or stays on a loading placeholder) while the
120
+ // authoritative catalog request is still pending.
121
+ updateSettingsSummary(instance);
122
+ try {
123
+ await ensureLoadedWithRetry(instance);
124
+ } catch (_error) {
125
+ // This card describes the project's saved selection. Native connection
126
+ // failures are surfaced when Configure is opened, without replacing the
127
+ // selection with a misleading Built-in/unavailable state.
128
+ }
129
+ updateSettingsSummary(instance);
130
+ return instance.catalog;
131
+ }
132
+
96
133
  async function syncSessionProvider(instance) {
97
134
  try {
98
- await ensureLoaded(instance);
135
+ await ensureLoadedWithRetry(instance);
99
136
  } catch (_error) {
100
137
  return instance.refreshModelOptions(
101
138
  window.CodexOverleafProviderProfiles.buildRunSelection(instance.catalog, instance.getSelectedProviderId())
@@ -109,22 +146,78 @@
109
146
  const requestedProviderId = typeof change.sessionProviderId === 'string' && change.sessionProviderId
110
147
  ? change.sessionProviderId
111
148
  : previousProviderId;
112
- const sessionProviderChanged = requestedProviderId !== previousProviderId;
113
- if (sessionProviderChanged) {
149
+ const projectProviderChanged = requestedProviderId !== previousProviderId;
150
+ if (projectProviderChanged && change.providerSwitchApproved !== true) {
114
151
  const requestedProvider = window.CodexOverleafProviderProfiles.getProviderById(catalog, requestedProviderId);
115
- const preferredModelId = requestedProvider?.kind === 'custom'
116
- ? requestedProvider.defaultModelId || requestedProvider.models?.[0]?.id || ''
117
- : '';
118
- instance.setSelectedProviderId(requestedProviderId, preferredModelId);
152
+ const approved = await instance.confirmProviderSwitch({
153
+ providerId: requestedProviderId,
154
+ providerName: requestedProvider?.name || requestedProviderId
155
+ });
156
+ if (!approved) {
157
+ return { cancelled: true };
158
+ }
119
159
  }
120
160
  const changedProviderIds = Array.isArray(change.changedProviderIds) ? change.changedProviderIds : [];
121
- if (!change.forceSessionRefresh && !sessionProviderChanged && !changedProviderIds.includes(requestedProviderId)) {
161
+ if (!change.forceSessionRefresh && !projectProviderChanged && !changedProviderIds.includes(requestedProviderId)) {
122
162
  return;
123
163
  }
124
- await instance.refreshModelOptions(
125
- window.CodexOverleafProviderProfiles.buildRunSelection(catalog, requestedProviderId)
126
- );
164
+ const loadResult = change.modelsPreloaded === true
165
+ ? { stale: false, selectedModel: change.preloadedModelId || '' }
166
+ : await instance.refreshModelOptions(
167
+ window.CodexOverleafProviderProfiles.buildRunSelection(catalog, requestedProviderId),
168
+ { persist: false }
169
+ );
170
+ if (loadResult?.stale || loadResult?.error) {
171
+ const error = createClientError(
172
+ loadResult?.error?.code || 'provider_model_catalog_unavailable',
173
+ loadResult?.error?.message || 'The provider model catalog could not be loaded.'
174
+ );
175
+ error.details = loadResult?.error || {};
176
+ throw error;
177
+ }
178
+ if (projectProviderChanged) {
179
+ await instance.setSelectedProviderId(requestedProviderId, loadResult?.selectedModel || '');
180
+ }
127
181
  await instance.persistInputs();
182
+ updateSettingsSummary(instance);
183
+ return { providerId: requestedProviderId, selectedModel: loadResult?.selectedModel || '' };
184
+ }
185
+
186
+ async function prepareProviderActivation(instance, providerId) {
187
+ const previousProviderId = instance.getSelectedProviderId() || 'builtin';
188
+ const provider = window.CodexOverleafProviderProfiles.getProviderById(instance.catalog, providerId);
189
+ if (providerId !== previousProviderId) {
190
+ const approved = await instance.confirmProviderSwitch({
191
+ providerId,
192
+ providerName: provider?.name || providerId
193
+ });
194
+ if (!approved) {
195
+ return { cancelled: true, previousProviderId };
196
+ }
197
+ }
198
+ const result = await instance.refreshModelOptions(
199
+ window.CodexOverleafProviderProfiles.buildRunSelection(instance.catalog, providerId),
200
+ { persist: false }
201
+ );
202
+ if (result?.stale || result?.error) {
203
+ if (providerId !== previousProviderId) {
204
+ await instance.refreshModelOptions(
205
+ window.CodexOverleafProviderProfiles.buildRunSelection(instance.catalog, previousProviderId),
206
+ { persist: false }
207
+ ).catch(() => {});
208
+ }
209
+ const error = createClientError(
210
+ result?.error?.code || 'provider_model_catalog_unavailable',
211
+ result?.error?.message || 'The provider model catalog could not be loaded.'
212
+ );
213
+ error.details = result?.error || {};
214
+ throw error;
215
+ }
216
+ return {
217
+ cancelled: false,
218
+ previousProviderId,
219
+ selectedModel: result?.selectedModel || ''
220
+ };
128
221
  }
129
222
 
130
223
  async function testProvider(instance, context) {
@@ -264,8 +357,13 @@
264
357
  }
265
358
 
266
359
  async function activateProvider(instance, context) {
267
- instance.dialog.setBusy('saving', instance.tx('Activating provider…', '正在启用模型服务…'));
360
+ let preparation;
268
361
  try {
362
+ preparation = await prepareProviderActivation(instance, context.profileId);
363
+ if (preparation.cancelled) {
364
+ return;
365
+ }
366
+ instance.dialog.setBusy('saving', instance.tx('Activating provider…', '正在启用模型服务…'));
269
367
  const result = await request(instance, 'codex.providers.activate', {
270
368
  providerId: context.profileId,
271
369
  expectedRevision: context.expectedRevision,
@@ -276,30 +374,59 @@
276
374
  updateDialog: true,
277
375
  selectedId: context.profileId
278
376
  });
279
- await notifyChanged(instance, applied.change, { sessionProviderId: context.profileId });
377
+ await notifyChanged(instance, applied.change, {
378
+ sessionProviderId: context.profileId,
379
+ providerSwitchApproved: true,
380
+ modelsPreloaded: true,
381
+ preloadedModelId: preparation.selectedModel,
382
+ requireProjection: true
383
+ });
280
384
  instance.dialog.setBusy('', '');
281
385
  instance.dialog.setStatus({
282
386
  tone: 'success',
283
387
  title: instance.tx('Provider activated.', '模型服务已启用。')
284
388
  });
285
389
  } catch (error) {
390
+ if (preparation?.previousProviderId && preparation.previousProviderId !== context.profileId) {
391
+ await instance.refreshModelOptions(
392
+ window.CodexOverleafProviderProfiles.buildRunSelection(instance.catalog, preparation.previousProviderId),
393
+ { persist: false }
394
+ ).catch(() => {});
395
+ }
286
396
  instance.dialog.setBusy('failed', formatError(instance, error));
287
397
  instance.dialog.setStatus({ tone: 'failed', title: formatError(instance, error) });
288
398
  }
289
399
  }
290
400
 
291
401
  async function activateBuiltin(instance) {
292
- instance.dialog.setBusy('saving', instance.tx('Activating built-in Codex…', '正在启用内置 Codex…'));
402
+ let preparation;
293
403
  try {
404
+ preparation = await prepareProviderActivation(instance, 'builtin');
405
+ if (preparation.cancelled) {
406
+ return;
407
+ }
408
+ instance.dialog.setBusy('saving', instance.tx('Activating built-in Codex…', '正在启用内置 Codex…'));
294
409
  const result = await request(instance, 'codex.providers.activate', {
295
410
  providerId: 'builtin',
296
411
  expectedRevision: 0
297
412
  });
298
413
  const applied = applyCatalog(instance, result, { updateDialog: true, selectedId: 'builtin' });
299
- await notifyChanged(instance, applied.change, { sessionProviderId: 'builtin' });
414
+ await notifyChanged(instance, applied.change, {
415
+ sessionProviderId: 'builtin',
416
+ providerSwitchApproved: true,
417
+ modelsPreloaded: true,
418
+ preloadedModelId: preparation.selectedModel,
419
+ requireProjection: true
420
+ });
300
421
  instance.dialog.setBusy('', '');
301
422
  instance.dialog.setStatus({ tone: 'success', title: instance.tx('Built-in Codex is active.', '已启用内置 Codex。') });
302
423
  } catch (error) {
424
+ if (preparation?.previousProviderId && preparation.previousProviderId !== 'builtin') {
425
+ await instance.refreshModelOptions(
426
+ window.CodexOverleafProviderProfiles.buildRunSelection(instance.catalog, preparation.previousProviderId),
427
+ { persist: false }
428
+ ).catch(() => {});
429
+ }
303
430
  instance.dialog.setBusy('failed', formatError(instance, error));
304
431
  }
305
432
  }
@@ -340,13 +467,31 @@
340
467
  }
341
468
 
342
469
  function updateSettingsSummary(instance, override = {}) {
343
- const active = window.CodexOverleafProviderProfiles.getActiveProvider(instance.catalog);
344
- const summary = override.summary || (active.kind === 'builtin'
345
- ? instance.tx('Built-in Codex · default for new sessions', '内置 Codex · 新会话默认')
346
- : `${active.name} · ${active.defaultModelId || instance.tx('No model', '未配置模型')} · ${instance.tx('Default for new sessions', '新会话默认')}`);
470
+ const selectedProviderId = instance.getSelectedProviderId() || 'builtin';
471
+ const catalogProvider = window.CodexOverleafProviderProfiles.getProviderById(
472
+ instance.catalog,
473
+ selectedProviderId
474
+ );
475
+ const selectedProvider = catalogProvider?.id === selectedProviderId
476
+ ? catalogProvider
477
+ : (selectedProviderId === 'builtin'
478
+ ? window.CodexOverleafProviderProfiles.getActiveProvider(instance.catalog)
479
+ : {
480
+ id: selectedProviderId,
481
+ name: selectedProviderId,
482
+ kind: 'custom',
483
+ defaultModelId: instance.getSelectedModel() || '',
484
+ wireApiPreference: '',
485
+ resolvedWireApi: ''
486
+ });
487
+ const summary = override.summary || (selectedProvider.kind === 'builtin'
488
+ ? instance.tx('Built-in Codex · current project', '内置 Codex · 当前项目')
489
+ : `${selectedProvider.name} · ${instance.getSelectedModel() || selectedProvider.defaultModelId || instance.tx('No model', '未配置模型')} · ${instance.tx('Current project', '当前项目')}`);
347
490
  instance.getSettingsPanelInstance()?.setProviderSummary?.({
348
491
  summary,
349
- tone: override.tone || (active.kind === 'custom' && active.wireApiPreference === 'auto' && !active.resolvedWireApi ? 'warning' : 'ok')
492
+ tone: override.tone || (selectedProvider.kind === 'custom'
493
+ && selectedProvider.wireApiPreference === 'auto'
494
+ && !selectedProvider.resolvedWireApi ? 'warning' : 'ok')
350
495
  });
351
496
  }
352
497
 
@@ -358,7 +503,10 @@
358
503
  }
359
504
  try {
360
505
  await instance.onProviderChanged(instance.catalog, { ...change, ...localChange });
361
- } catch (_error) {
506
+ } catch (error) {
507
+ if (localChange.requireProjection === true) {
508
+ throw error;
509
+ }
362
510
  // The authoritative catalog is already saved. A later refresh can retry
363
511
  // the local model projection without rolling back the provider change.
364
512
  }
@@ -381,7 +381,7 @@
381
381
  </label>
382
382
  <label class="codex-provider-field">
383
383
  <span>${escapeHtml(tx('Maximum output tokens', '最大输出 Token'))}</span>
384
- <input type="number" data-provider-field="maxOutputTokens" min="256" max="200000" step="256" value="${Number(draft.maxOutputTokens) || 32768}">
384
+ <input type="number" data-provider-field="maxOutputTokens" min="256" max="65536" step="256" value="${Number(draft.maxOutputTokens) || 65536}">
385
385
  </label>
386
386
  <div class="codex-provider-field codex-provider-field--wide">
387
387
  <span>${escapeHtml(tx('Anthropic compatibility', 'Anthropic 兼容能力'))}</span>
@@ -736,7 +736,7 @@
736
736
  ,anthropicThinkingMode: get('anthropicThinkingMode')?.value || 'budget'
737
737
  ,anthropicPromptCaching: Boolean(get('anthropicPromptCaching')?.checked)
738
738
  ,impersonateClaudeCode: Boolean(get('impersonateClaudeCode')?.checked)
739
- ,maxOutputTokens: Number(get('maxOutputTokens')?.value || 32768)
739
+ ,maxOutputTokens: Number(get('maxOutputTokens')?.value || 65536)
740
740
  };
741
741
  const apiKey = String(get('apiKey')?.value || '');
742
742
  const secretMutation = instance.secretAction === 'replace' && apiKey
@@ -510,9 +510,11 @@
510
510
  return [];
511
511
  }
512
512
  var records = await StorageDb.getAllByIndex('sessions', 'projectId', projectId);
513
+ var Persistence = window.CodexOverleafSessionPersistence;
514
+ var deletedIds = await Persistence.getDeletedSessionIds(projectId);
513
515
  var scope = getCachedAccountScopeId();
514
516
  return (records || [])
515
- .filter(function (record) { return record && (!scope || record.accountScopeId === scope); })
517
+ .filter(function (record) { return Persistence.isVisibleRecord(record, deletedIds, scope); })
516
518
  .sort(function (a, b) {
517
519
  return String(b.updatedAt || b.createdAt || '').localeCompare(String(a.updatedAt || a.createdAt || ''));
518
520
  });
@@ -729,6 +731,7 @@
729
731
  if (!approved) {
730
732
  return;
731
733
  }
734
+ await window.CodexOverleafSessionPersistence.addTombstones(projectId, [record.id]);
732
735
  try {
733
736
  await mutateProjectPanelState(projectId, function (state) {
734
737
  return SessionState.deleteSession(state, record.id);
@@ -962,9 +965,11 @@
962
965
  try {
963
966
  var StorageDb = window.CodexOverleafStorageDb;
964
967
  if (StorageDb) {
968
+ var deletedSessionIdsByProject = await window.CodexOverleafSessionPersistence.loadTombstones();
965
969
  rows = await StorageDb.listRecentProjectsAcrossAccount({
966
970
  accountScopeId: accountScopeId,
967
- limit: showAll ? 500 : pageLimit + 1
971
+ limit: showAll ? 500 : pageLimit + 1,
972
+ deletedSessionIdsByProject: deletedSessionIdsByProject
968
973
  });
969
974
  }
970
975
  } catch (_error) {
@@ -0,0 +1,70 @@
1
+ (function initCodexOverleafRunGuidanceController() {
2
+ 'use strict';
3
+
4
+ function create(deps = {}) {
5
+ function upsert(text, target = {}) {
6
+ const record = deps.findRunRecord?.(target.recordId, target.sessionId);
7
+ const guidanceId = String(target.guidanceId || '').trim();
8
+ if (!record || !guidanceId) return null;
9
+ const event = {
10
+ title: String(text || '').trim(),
11
+ status: target.status || 'queued',
12
+ kind: 'guidance',
13
+ guidanceId,
14
+ timestamp: new Date().toISOString()
15
+ };
16
+ const existing = (record.events || []).find(entry => entry.guidanceId === guidanceId);
17
+ if (!existing) {
18
+ deps.appendRunEvent?.(event, record);
19
+ return event;
20
+ }
21
+ Object.assign(existing, event, { timestamp: existing.timestamp || event.timestamp });
22
+ const view = deps.getCurrentRunView?.();
23
+ if (view?.recordId === target.recordId) {
24
+ view.root?.querySelector(`[data-guidance-id="${deps.cssEscape(guidanceId)}"]`)
25
+ ?.replaceWith(deps.renderRunEvent(existing));
26
+ }
27
+ return existing;
28
+ }
29
+
30
+ function remove(guidanceId, target = {}) {
31
+ const record = deps.findRunRecord?.(target.recordId, target.sessionId);
32
+ if (!record || !guidanceId) return false;
33
+ const before = (record.events || []).length;
34
+ record.events = (record.events || []).filter(event => event.guidanceId !== guidanceId);
35
+ const view = deps.getCurrentRunView?.();
36
+ if (view?.recordId === target.recordId) {
37
+ view.root?.querySelector(`[data-guidance-id="${deps.cssEscape(guidanceId)}"]`)?.remove();
38
+ }
39
+ return record.events.length !== before;
40
+ }
41
+
42
+ function settleView(view) {
43
+ const guidance = view?.guidance || view?.root?.querySelector('[data-run-guidance]');
44
+ if (!guidance || !view?.events) return;
45
+ view.guidance = guidance;
46
+ for (const row of view.events.querySelectorAll('[data-kind="guidance"]')) guidance.append(row);
47
+ }
48
+
49
+ function appendToView(event, view) {
50
+ const text = window.CodexOverleafRunGuidanceView?.getGuidanceText(event);
51
+ if (!text) return false;
52
+ const guidance = view.guidance || view.root?.querySelector('[data-run-guidance]');
53
+ const target = view.terminalStatus ? guidance : view.events;
54
+ if (!target) return false;
55
+ view.guidance = guidance;
56
+ const rendered = deps.renderRunEvent(event);
57
+ const existing = event.guidanceId
58
+ ? view.root?.querySelector(`[data-guidance-id="${deps.cssEscape(event.guidanceId)}"]`)
59
+ : null;
60
+ if (existing) existing.replaceWith(rendered);
61
+ else target.append(rendered);
62
+ deps.bumpUnreadIfDetached?.();
63
+ return true;
64
+ }
65
+
66
+ return { upsert, remove, settleView, appendToView };
67
+ }
68
+
69
+ window.CodexOverleafRunGuidanceController = { create };
70
+ })();
@@ -0,0 +1,48 @@
1
+ (function initCodexOverleafRunGuidanceView() {
2
+ 'use strict';
3
+
4
+ const LEGACY_GUIDANCE_TITLES = new Set([
5
+ 'Follow-up guidance was delivered to the active Codex turn.',
6
+ '\u540e\u7eed\u5f15\u5bfc\u5df2\u53d1\u9001\u5230\u5f53\u524d Codex turn\u3002'
7
+ ]);
8
+
9
+ const GUIDANCE_DELIVERY_PREFIX = /^(?:Guidance delivered|Follow-up guidance delivered|引导已发送|后续引导已发送)\s*[::]\s*/i;
10
+
11
+ function normalizeGuidanceText(value) {
12
+ return String(value || '').trim().replace(GUIDANCE_DELIVERY_PREFIX, '').trim();
13
+ }
14
+
15
+ function getGuidanceText(event = {}) {
16
+ const detailText = typeof event.detail === 'string' ? event.detail.trim() : '';
17
+ if (event.kind === 'guidance') {
18
+ return normalizeGuidanceText(detailText || event.title);
19
+ }
20
+ return detailText && LEGACY_GUIDANCE_TITLES.has(String(event.title || ''))
21
+ ? normalizeGuidanceText(detailText)
22
+ : '';
23
+ }
24
+
25
+ function renderGuidanceMessage(event, guidanceText, tx) {
26
+ const row = document.createElement('div');
27
+ row.className = 'run-guidance-message run-user-message';
28
+ row.dataset.kind = 'guidance';
29
+ row.dataset.status = event.status || 'completed';
30
+ if (event.guidanceId) {
31
+ row.dataset.guidanceId = event.guidanceId;
32
+ }
33
+ row.setAttribute('role', 'group');
34
+ row.setAttribute('aria-label', tx('Follow-up guidance', '\u540e\u7eed\u5f15\u5bfc'));
35
+
36
+ const text = document.createElement('div');
37
+ text.className = 'run-guidance-text';
38
+ text.textContent = guidanceText;
39
+
40
+ row.append(text);
41
+ return row;
42
+ }
43
+
44
+ window.CodexOverleafRunGuidanceView = {
45
+ getGuidanceText,
46
+ renderGuidanceMessage
47
+ };
48
+ })();