codex-overleaf-link 2.3.2 → 2.3.4

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.
@@ -4,11 +4,14 @@
4
4
  const ROOT_CLASS = 'codex-overleaf-panel-mounted';
5
5
  const RESIZE_CLASS = 'codex-overleaf-panel-resizing';
6
6
  const COMPACT_CLASS = 'codex-overleaf-panel-compact';
7
+ const LAUNCHER_VISIBILITY_STORAGE_KEY = 'codex-overleaf:launcher-visible';
7
8
 
8
9
  function codexIcon(name) {
9
10
  const icons = {
10
11
  refresh: '<path d="M4.2 5.3A5 5 0 1 1 3.6 10"/><path d="M4.2 2.2v3.1H1.1"/>',
11
12
  plus: '<path d="M8 3.2v9.6"/><path d="M3.2 8h9.6"/>',
13
+ panel: '<rect x="2.3" y="2.3" width="11.4" height="11.4" rx="2"/><path d="M9.2 2.5v11"/><path d="M5.1 6.1h1.5M5.1 8h1.5M5.1 9.9h1.5"/>',
14
+ collapse: '<path d="m6.1 3.3 4.7 4.7-4.7 4.7"/>',
12
15
  settings: '<path d="M3.4 5.2h5.9"/><path d="M11.7 5.2h.9"/><path d="M10.1 3.6v3.2"/><path d="M3.4 10.8h1"/><path d="M6.8 10.8h5.8"/><path d="M5.9 9.2v3.2"/>'
13
16
  };
14
17
  const safeName = Object.prototype.hasOwnProperty.call(icons, name) ? name : 'settings';
@@ -19,9 +22,11 @@
19
22
  const doc = options.document || document;
20
23
  const container = options.container || doc.documentElement;
21
24
  const panelEl = doc.createElement('aside');
25
+ const launcherEl = doc.createElement('button');
22
26
  const callbacks = options.callbacks || {};
23
27
  const instance = {
24
28
  panelEl,
29
+ launcherEl,
25
30
  callbacks,
26
31
  defaultWidth: options.defaultWidth || 380,
27
32
  minWidth: options.minWidth || 340,
@@ -30,9 +35,18 @@
30
35
  lastDesktopWidth: options.initialWidth || options.defaultWidth || 380,
31
36
  listeners: [],
32
37
  document: doc,
33
- window: options.window || window
38
+ window: options.window || window,
39
+ chromeApi: options.chromeApi || (typeof chrome !== 'undefined' ? chrome : null),
40
+ storageChangeListener: null
34
41
  };
35
42
 
43
+ launcherEl.id = 'codex-overleaf-launcher';
44
+ launcherEl.type = 'button';
45
+ launcherEl.title = options.launcherLabel || 'Show or hide Codex panel';
46
+ launcherEl.setAttribute('aria-label', launcherEl.title);
47
+ launcherEl.setAttribute('aria-expanded', 'false');
48
+ launcherEl.innerHTML = codexIcon('panel');
49
+
36
50
  panelEl.id = options.panelId || 'codex-overleaf-panel';
37
51
  panelEl.dataset.view = 'session';
38
52
  panelEl.innerHTML = `
@@ -44,6 +58,7 @@
44
58
  <div data-diagnostics-slot></div>
45
59
  <button type="button" data-new-session title="New Session" aria-label="New Session">${codexIcon('plus')}</button>
46
60
  <button type="button" data-custom-instructions-settings title="Project Settings" aria-label="Project Settings" aria-expanded="false">${codexIcon('settings')}</button>
61
+ <button type="button" data-close-panel title="Close Codex panel" aria-label="Close Codex panel">${codexIcon('collapse')}</button>
47
62
  </div>
48
63
  </div>
49
64
  <div data-settings-slot></div>
@@ -59,6 +74,7 @@
59
74
  `;
60
75
 
61
76
  container.append(panelEl);
77
+ container.append(launcherEl);
62
78
  doc.documentElement.classList.add(ROOT_CLASS);
63
79
 
64
80
  instance.headerEl = panelEl.querySelector('[data-panel-header]');
@@ -73,6 +89,8 @@
73
89
  bind(instance, panelEl.querySelector('[data-refresh]'), 'click', () => callbacks.onRefresh?.());
74
90
  bind(instance, panelEl.querySelector('[data-new-session]'), 'click', () => callbacks.onNewSession?.());
75
91
  bind(instance, panelEl.querySelector('[data-custom-instructions-settings]'), 'click', () => callbacks.onSettingsClick?.());
92
+ bind(instance, panelEl.querySelector('[data-close-panel]'), 'click', () => callbacks.onClosePanel?.());
93
+ bind(instance, launcherEl, 'click', () => callbacks.onLauncherToggle?.());
76
94
  bind(instance, panelEl.querySelector('[data-panel-resize-handle]'), 'pointerdown', event => startResize(instance, event));
77
95
  bind(instance, panelEl.querySelector('[data-panel-resize-handle]'), 'dblclick', event => {
78
96
  event?.preventDefault?.();
@@ -87,9 +105,11 @@
87
105
  });
88
106
 
89
107
  setWidth(instance, options.initialWidth || instance.defaultWidth, { notify: false });
108
+ initializeLauncherVisibility(instance);
90
109
 
91
110
  return {
92
111
  panelEl,
112
+ launcherEl,
93
113
  headerEl: instance.headerEl,
94
114
  bodyEl: instance.bodyEl,
95
115
  diagnosticsSlot: instance.diagnosticsSlot,
@@ -198,11 +218,64 @@
198
218
  function setVisible(panelEl, visible) {
199
219
  panelEl?.classList?.toggle('is-open', Boolean(visible));
200
220
  document.documentElement.classList.toggle(ROOT_CLASS, Boolean(visible));
221
+ const launcherEl = panelEl?.ownerDocument?.getElementById?.('codex-overleaf-launcher');
222
+ launcherEl?.classList?.toggle('is-panel-open', Boolean(visible));
223
+ launcherEl?.setAttribute?.('aria-expanded', visible ? 'true' : 'false');
224
+ if (launcherEl) {
225
+ launcherEl.hidden = Boolean(visible) || launcherEl.dataset.enabled === 'false';
226
+ launcherEl.setAttribute('aria-hidden', launcherEl.hidden ? 'true' : 'false');
227
+ }
201
228
  if (!visible) {
202
229
  document.documentElement.classList.remove(RESIZE_CLASS, COMPACT_CLASS);
203
230
  }
204
231
  }
205
232
 
233
+ function initializeLauncherVisibility(instance) {
234
+ const storage = instance.chromeApi?.storage;
235
+ if (!storage?.local?.get) {
236
+ setLauncherVisible(instance, true, { persist: false });
237
+ return;
238
+ }
239
+ Promise.resolve(storage.local.get({ [LAUNCHER_VISIBILITY_STORAGE_KEY]: true }))
240
+ .then(values => applyLauncherVisibility(instance, values?.[LAUNCHER_VISIBILITY_STORAGE_KEY]))
241
+ .catch(() => applyLauncherVisibility(instance, true));
242
+ if (storage.onChanged?.addListener) {
243
+ instance.storageChangeListener = (changes, areaName) => {
244
+ if (areaName === 'local' && changes?.[LAUNCHER_VISIBILITY_STORAGE_KEY]) {
245
+ applyLauncherVisibility(instance, changes[LAUNCHER_VISIBILITY_STORAGE_KEY].newValue);
246
+ }
247
+ };
248
+ storage.onChanged.addListener(instance.storageChangeListener);
249
+ }
250
+ }
251
+
252
+ function applyLauncherVisibility(instance, value) {
253
+ const visible = value !== false;
254
+ setLauncherVisible(instance, visible, { persist: false });
255
+ instance.callbacks.onLauncherVisibilityChange?.(visible);
256
+ }
257
+
258
+ function setLauncherVisible(target, visible, options = {}) {
259
+ const instance = target?._instance || target;
260
+ const launcherEl = instance?.launcherEl;
261
+ if (!launcherEl) return false;
262
+ const nextVisible = Boolean(visible);
263
+ launcherEl.dataset.enabled = nextVisible ? 'true' : 'false';
264
+ launcherEl.hidden = !nextVisible || instance.panelEl?.classList?.contains('is-open') === true;
265
+ launcherEl.setAttribute('aria-hidden', launcherEl.hidden ? 'true' : 'false');
266
+ if (options.persist === true) {
267
+ Promise.resolve(instance.chromeApi?.storage?.local?.set?.({
268
+ [LAUNCHER_VISIBILITY_STORAGE_KEY]: nextVisible
269
+ })).catch(() => {});
270
+ }
271
+ return nextVisible;
272
+ }
273
+
274
+ function isLauncherVisible(target) {
275
+ const instance = target?._instance || target;
276
+ return Boolean(instance?.launcherEl && instance.launcherEl.dataset.enabled !== 'false');
277
+ }
278
+
206
279
  function setBadge(headerEl, badge = {}) {
207
280
  const actions = headerEl?.querySelector?.('.codex-vscode-head-actions');
208
281
  if (!actions) {
@@ -340,6 +413,10 @@
340
413
  target.removeEventListener?.(type, listener, options);
341
414
  }
342
415
  instance.panelEl?.remove?.();
416
+ instance.launcherEl?.remove?.();
417
+ if (instance.storageChangeListener) {
418
+ instance.chromeApi?.storage?.onChanged?.removeListener?.(instance.storageChangeListener);
419
+ }
343
420
  instance.document.documentElement.classList.remove(ROOT_CLASS, RESIZE_CLASS, COMPACT_CLASS);
344
421
  }
345
422
 
@@ -347,6 +424,8 @@
347
424
  create,
348
425
  createConfirmController,
349
426
  setVisible,
427
+ setLauncherVisible,
428
+ isLauncherVisible,
350
429
  setBadge,
351
430
  setWidth
352
431
  };
@@ -16,10 +16,17 @@
16
16
  }
17
17
  container.replaceChildren();
18
18
  container.hidden = !items.length;
19
+ if (!items.length) {
20
+ return;
21
+ }
19
22
  for (const item of items) {
20
23
  const row = document.createElement('article');
21
24
  row.className = 'codex-pending-input';
22
25
  row.dataset.status = item.status || 'queued';
26
+ const marker = document.createElement('span');
27
+ marker.className = 'codex-pending-input-marker';
28
+ marker.setAttribute('aria-hidden', 'true');
29
+ marker.append(createIcon('queue'));
23
30
  const copy = document.createElement('div');
24
31
  copy.className = 'codex-pending-input-copy';
25
32
  const status = document.createElement('span');
@@ -34,7 +41,10 @@
34
41
  const text = document.createElement('span');
35
42
  text.className = 'codex-pending-input-text';
36
43
  text.textContent = item.text;
37
- copy.append(status, text);
44
+ copy.append(text);
45
+ if (item.status && item.status !== 'queued') {
46
+ copy.append(status);
47
+ }
38
48
  const actions = document.createElement('div');
39
49
  actions.className = 'codex-pending-input-actions';
40
50
  if (item.status === 'paused') {
@@ -60,7 +70,8 @@
60
70
  remove.disabled = item.status === 'steering' || item.status === 'executing';
61
71
  text.title = item.text;
62
72
  actions.append(remove);
63
- row.append(copy, actions);
73
+ row.setAttribute('aria-label', `${status.textContent || options.tr?.('queuedInputQueued') || 'Queued'}: ${item.text}`);
74
+ row.append(marker, copy, actions);
64
75
  container.append(row);
65
76
  }
66
77
  }
@@ -93,7 +104,9 @@
93
104
  svg.setAttribute('focusable', 'false');
94
105
  const paths = icon === 'guide'
95
106
  ? ['M4 4.5v4a4 4 0 0 0 4 4h7', 'm12 9 3.5 3.5L12 16']
96
- : ['M6 6h8', 'M8 6V4.5h4V6', 'm7.2 8 0.6 7.5h4.4l.6-7.5'];
107
+ : icon === 'queue'
108
+ ? ['M4 5.5h12', 'M4 10h12', 'M4 14.5h8']
109
+ : ['M6 6h8', 'M8 6V4.5h4V6', 'm7.2 8 0.6 7.5h4.4l.6-7.5'];
97
110
  for (const value of paths) {
98
111
  const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
99
112
  path.setAttribute('d', value);
@@ -985,6 +985,7 @@
985
985
  return;
986
986
  }
987
987
  const style = instance.document.defaultView.getComputedStyle(panel);
988
+ instance.root.dataset.theme = panel.dataset.theme === 'light' ? 'light' : 'dark';
988
989
  instance.root.style.setProperty('--provider-panel-bg', style.backgroundColor || '#151716');
989
990
  instance.root.style.setProperty('--provider-panel-color', style.color || '#eceeea');
990
991
  }
@@ -231,11 +231,46 @@
231
231
  return String(content || '').trim().length > 0;
232
232
  }
233
233
 
234
+ function isRetryReplacementEligible(run = {}, projection = {}) {
235
+ if (!run.id || !['failed', 'interrupted'].includes(run.status)) return false;
236
+ if (run.trackedChangeStatus || run.undoStatus) return false;
237
+ if (['undoOperations', 'undoTrackedChanges', 'undoExpectedFiles', 'undoBaseFiles', 'appliedOperations']
238
+ .some(field => Array.isArray(run[field]) && run[field].length)) return false;
239
+ if (projection.canAccept || projection.canUndo || projection.changedDocument === true) return false;
240
+ if (projection.failureTerminalState === 'needs_review' || projection.terminalState === 'needs_review') return false;
241
+ return projection.changedDocument === false || run.mode === 'ask' || hasZeroWriteReport(run.events);
242
+ }
243
+
244
+ function hasZeroWriteReport(events = []) {
245
+ return (Array.isArray(events) ? events : []).some(event => {
246
+ const rows = Array.isArray(event?.detailStructured?.meta) ? event.detailStructured.meta : [];
247
+ return rows.some(row => row?.key === 'writeResult'
248
+ && /(?:wrote|写入)\s*0\b[\s\S]*(?:skipped|跳过)\s*0\b/i.test(String(row.value || '')));
249
+ });
250
+ }
251
+
252
+ function resolveRetryReplacement({ candidate, activeSessionId, runs, projectRunSettlement } = {}) {
253
+ if (!candidate?.runId || candidate.sessionId !== activeSessionId) return '';
254
+ const source = (Array.isArray(runs) ? runs : []).find(run => run?.id === candidate.runId);
255
+ if (!source) return '';
256
+ const projection = typeof projectRunSettlement === 'function' ? projectRunSettlement(source) : {};
257
+ return isRetryReplacementEligible(source, projection) ? source.id : '';
258
+ }
259
+
260
+ function replaceRunForRetry(runs, sourceRunId, nextRun, maxRuns = 20) {
261
+ const retained = (Array.isArray(runs) ? runs : [])
262
+ .filter(run => !sourceRunId || run?.id !== sourceRunId);
263
+ return [...retained, nextRun].slice(-Math.max(1, maxRuns));
264
+ }
265
+
234
266
  return {
235
267
  buildCodexRunParams,
236
268
  canUseFocusedPartialSnapshot,
237
269
  shouldRestrictWritebackToFocus,
238
270
  buildSessionHistoryResult,
271
+ isRetryReplacementEligible,
272
+ replaceRunForRetry,
273
+ resolveRetryReplacement,
239
274
  truncateSessionHistoryText,
240
275
  normalizeComposerAttachments,
241
276
  normalizeSkillInvocation
@@ -30,6 +30,7 @@
30
30
  cssEscape,
31
31
  renderMarkdownInlineText,
32
32
  renderMarkdownBlockText,
33
+ decorateCompletionReport,
33
34
  sanitizeAssistantVisibleText,
34
35
  sanitizeAssistantVisibleValue,
35
36
  stripEmptyHtmlCommentPlaceholders = value => value,
@@ -709,6 +710,7 @@
709
710
  }
710
711
  if (mainSections.length) {
711
712
  renderMarkdownBlockText(main, mainSections.join('\n\n'));
713
+ decorateCompletionReport(main, { tx });
712
714
  report.append(main);
713
715
  }
714
716
 
@@ -729,6 +731,7 @@
729
731
  const body = document.createElement('div');
730
732
  body.className = 'run-final-answer';
731
733
  renderMarkdownBlockText(body, split.body || flatText);
734
+ decorateCompletionReport(body, { tx });
732
735
  report.append(body);
733
736
  appendCompileFix(report);
734
737
  appendRejectedRedo(report);
@@ -18,7 +18,7 @@
18
18
  let currentInstallCommand = DEFAULT_INSTALL_COMMAND;
19
19
 
20
20
  installCommand.textContent = currentInstallCommand;
21
- button.textContent = 'Open panel in Overleaf';
21
+ button.textContent = 'Show Codex button in Overleaf';
22
22
  setVersionStatus({
23
23
  status: 'native_missing',
24
24
  classification: 'incompatible',
@@ -32,7 +32,7 @@
32
32
  return;
33
33
  }
34
34
 
35
- await chrome.tabs.sendMessage(tab.id, { type: 'codex-overleaf/toggle-panel' });
35
+ await chrome.tabs.sendMessage(tab.id, { type: 'codex-overleaf/toggle-launcher' });
36
36
  window.close();
37
37
  });
38
38
 
@@ -56,20 +56,20 @@
56
56
  const tab = await getActiveOverleafProjectTab();
57
57
  activeOverleafTab = tab;
58
58
  if (!tab?.id) {
59
- button.textContent = 'Open panel in Overleaf';
59
+ button.textContent = 'Show Codex button in Overleaf';
60
60
  return;
61
61
  }
62
62
 
63
63
  try {
64
64
  const response = await chrome.tabs.sendMessage(tab.id, { type: 'codex-overleaf/get-panel-state' });
65
- const open = response?.open === true;
66
- button.textContent = open ? 'Close panel in Overleaf' : 'Open panel in Overleaf';
67
- status.textContent = open
68
- ? 'Panel is open in this Overleaf tab.'
69
- : 'Panel is closed in this Overleaf tab.';
65
+ const visible = response?.launcherVisible !== false;
66
+ button.textContent = visible ? 'Hide Codex edge button' : 'Show Codex edge button';
67
+ status.textContent = visible
68
+ ? 'The edge button appears whenever the Codex panel is closed.'
69
+ : 'The Codex edge button is hidden in Overleaf.';
70
70
  } catch (_error) {
71
- button.textContent = 'Open panel in Overleaf';
72
- status.textContent = 'Refresh the Overleaf tab, then open the panel.';
71
+ button.textContent = 'Show Codex button in Overleaf';
72
+ status.textContent = 'Refresh the Overleaf tab, then show the Codex button.';
73
73
  }
74
74
  }
75
75
 
@@ -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.3.2';
15
+ const BUILD_TARGET_VERSION = '2.3.4';
16
16
  const DEFAULT_CHROME_EXTENSION_ID = 'illdpneeeopfffmiepaejglgmhpmdhdc';
17
17
  const REQUIRED_CAPABILITIES = Object.freeze([
18
18
  'bridgePing',