synabun 2026.4.21 → 2026.4.25-beta.2

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 (61) hide show
  1. package/CHANGELOG.md +175 -881
  2. package/README.md +1 -1
  3. package/hooks/claude-code/prompt-submit.mjs +39 -18
  4. package/hooks/claude-code/session-start.mjs +10 -3
  5. package/hooks/claude-code/shared.mjs +39 -2
  6. package/hooks/claude-code/stop.mjs +33 -6
  7. package/mcp-server/src/index.ts +9 -1
  8. package/mcp-server/src/services/neural-interface.ts +42 -14
  9. package/mcp-server/src/services/profiles.ts +8 -7
  10. package/mcp-server/src/tools/browser-advanced.ts +12 -10
  11. package/mcp-server/src/tools/browser-interact.ts +39 -38
  12. package/mcp-server/src/tools/browser-navigate.ts +12 -4
  13. package/mcp-server/src/tools/loop.ts +7 -3
  14. package/mcp-server/src/tools/profile.ts +19 -1
  15. package/neural-interface/lib/loop-logger.js +103 -0
  16. package/neural-interface/lib/mcp-installer.js +109 -0
  17. package/neural-interface/lib/plugin-installer.js +264 -0
  18. package/neural-interface/package.json +1 -1
  19. package/neural-interface/public/shared/api.js +66 -17
  20. package/neural-interface/public/shared/cdx/cdx-icons.js +5 -4
  21. package/neural-interface/public/shared/cdx/cdx-panel.js +117 -14
  22. package/neural-interface/public/shared/cdx/cdx-render.js +170 -43
  23. package/neural-interface/public/shared/cdx/cdx-styles.js +55 -10
  24. package/neural-interface/public/shared/cdx/cdx-tabs.js +394 -72
  25. package/neural-interface/public/shared/cli-status.js +157 -0
  26. package/neural-interface/public/shared/constants.js +3 -2
  27. package/neural-interface/public/shared/html-shell.js +32 -11
  28. package/neural-interface/public/shared/ocp/ocp-panel.js +127 -28
  29. package/neural-interface/public/shared/ocp/ocp-render.js +52 -44
  30. package/neural-interface/public/shared/ocp/ocp-styles.js +33 -0
  31. package/neural-interface/public/shared/ocp/ocp-tabs.js +693 -82
  32. package/neural-interface/public/shared/styles.css +635 -0
  33. package/neural-interface/public/shared/ui-automation-studio.js +38 -12
  34. package/neural-interface/public/shared/ui-claude-panel.js +322 -15
  35. package/neural-interface/public/shared/ui-file-explorer.js +38 -18
  36. package/neural-interface/public/shared/ui-menubar.js +34 -10
  37. package/neural-interface/public/shared/ui-navbar.js +8 -6
  38. package/neural-interface/public/shared/ui-schedules-studio.js +1464 -0
  39. package/neural-interface/public/shared/ui-settings.js +274 -50
  40. package/neural-interface/public/shared/ui-sidepanel-layout.js +18 -7
  41. package/neural-interface/public/shared/ui-sidepanel-tray.js +287 -0
  42. package/neural-interface/public/shared/ui-sync.js +9 -5
  43. package/neural-interface/public/shared/ui-terminal.js +76 -27
  44. package/neural-interface/public/shared/ui-tool-updates.js +49 -15
  45. package/neural-interface/public/shared/ui-tutorial-steps.js +4 -2
  46. package/neural-interface/public/shared/ui-tutorial.js +24 -18
  47. package/neural-interface/public/shared/ui-whiteboard.js +79 -31
  48. package/neural-interface/public/shared/vterm-buffer.js +9 -8
  49. package/neural-interface/public/variant/2d/main.js +9 -7
  50. package/neural-interface/public/variant/3d/main.js +10 -8
  51. package/neural-interface/server.js +1292 -255
  52. package/package.json +1 -1
  53. package/skills/leonardo/SKILL.md +5 -1
  54. package/skills/leonardo/modules/image-prompter.md +5 -1
  55. package/skills/leonardo/modules/video-prompter.md +5 -1
  56. package/skills/synabun/SKILL.md +44 -26
  57. package/skills/synabun/modules/audit.md +40 -36
  58. package/skills/synabun/modules/changelog.md +22 -18
  59. package/skills/synabun/modules/memorize.md +19 -15
  60. package/skills/synabun/modules/schedule.md +285 -0
  61. package/CLAUDE.md +0 -217
@@ -9,7 +9,7 @@ import { isClaudePanelOpen, toggleClaudePanel } from '../ui-claude-panel.js';
9
9
  import { reserveRightPanelLayout, clearRightPanelLayout } from '../ui-sidepanel-layout.js';
10
10
  import { notify, NOTIF_TYPE } from '../ui-notifications.js';
11
11
  import {
12
- STOR, EFFORT_LEVELS, MAX_TABS, MAX_THREAD_SNAPSHOTS, MAX_THREAD_SNAPSHOT_CHARS,
12
+ STOR, EFFORT_LEVELS, MAX_THREAD_SNAPSHOTS, MAX_THREAD_SNAPSHOT_CHARS,
13
13
  CODEX_GREETING_MARKER_START, CODEX_GREETING_MARKER_END, CODEX_PLAN_MODE_PREFIXES,
14
14
  STATUS_TONE, PANEL_OWNER, windowId, STALL_WARN_MS, STALL_KILL_MS,
15
15
  OPENAI_ICON, ICON_SPARK, ICON_TERMINAL, ICON_TOOL, ICON_FILES, ICON_PLUS,
@@ -73,7 +73,12 @@ function scrollEnd() { _onScrollEnd?.(); }
73
73
  function autosizeInput() { _onAutosizeInput?.(); }
74
74
  function emit(...args) { _onEmit?.(...args); }
75
75
 
76
- function sendSocket(msg) { return wsSendSocket(msg, _ws); }
76
+ function sendSocket(msg) {
77
+ const packet = msg && _boundTab?.id && !msg.sessionId
78
+ ? { sessionId: _boundTab.id, ...msg }
79
+ : msg;
80
+ return wsSendSocket(packet, _ws);
81
+ }
77
82
 
78
83
  // ── connectTab / disconnectTab wrappers (build callbacks for cdx-ws) ──
79
84
 
@@ -111,8 +116,14 @@ function buildWsCallbacks(tab) {
111
116
  onClose(tab, ws, { intentional }) {
112
117
  _connected = false;
113
118
  _bootstrapped = false;
119
+ _pendingReattach = !intentional;
114
120
  _ws = null;
115
- if (_boundTab) { _boundTab.connected = false; _boundTab.bootstrapped = false; _boundTab.ws = null; }
121
+ if (_boundTab) {
122
+ _boundTab.connected = false;
123
+ _boundTab.bootstrapped = false;
124
+ _boundTab.pendingReattach = _pendingReattach;
125
+ _boundTab.ws = null;
126
+ }
116
127
  syncInputEnabled();
117
128
  syncSessionControls();
118
129
  if (!intentional && !tab.closed) {
@@ -124,6 +135,8 @@ function buildWsCallbacks(tab) {
124
135
  },
125
136
 
126
137
  onReattachResult(msg) {
138
+ _pendingReattach = false;
139
+ if (_boundTab) _boundTab.pendingReattach = false;
127
140
  if (msg.ok) {
128
141
  _threadId = msg.threadId || _threadId;
129
142
  if (_boundTab) _boundTab.threadId = _threadId;
@@ -244,7 +257,14 @@ function buildWsCallbacks(tab) {
244
257
  onInterruptAck(msg) {
245
258
  _running = false;
246
259
  _activeTurnId = null;
247
- if (_boundTab) { _boundTab.running = false; _boundTab.activeTurnId = null; }
260
+ if (_boundTab) {
261
+ _boundTab.running = false;
262
+ _boundTab.activeTurnId = null;
263
+ if (_boundTab._abortRetry) {
264
+ clearTimeout(_boundTab._abortRetry);
265
+ _boundTab._abortRetry = null;
266
+ }
267
+ }
248
268
  clearInterruptTimer();
249
269
  stopStallTimer();
250
270
  setStatus('Interrupted', 'muted');
@@ -265,7 +285,7 @@ function buildWsCallbacks(tab) {
265
285
  if (window.__cxpModelListResolve) {
266
286
  window.__cxpModelListResolve(models);
267
287
  } else {
268
- populateModelDropdown(models.length ? models : CODEX_DEFAULT_MODELS);
288
+ populateModelDropdown(mergeCodexModelList(models));
269
289
  }
270
290
  },
271
291
 
@@ -355,6 +375,9 @@ let _planContent = '';
355
375
  let _editedPlanContent = '';
356
376
  let _showPostPlanActions = false;
357
377
  let _postPlanHeader = 'PLAN COMPLETE';
378
+ let _planTurnActive = false;
379
+ let _planApprovalPending = false;
380
+ let _lastPlanTurnId = '';
358
381
  let _showPostCompactionPrompt = false;
359
382
  let _postCompactionPending = false;
360
383
  let _stallTimer = null;
@@ -746,6 +769,12 @@ export function flushThreadSnapshotSave(tab = _boundTab) {
746
769
  writeThreadSnapshot(tab);
747
770
  }
748
771
 
772
+ export function flushAllThreadSnapshots() {
773
+ for (const tab of _tabs) {
774
+ try { flushThreadSnapshotSave(tab); } catch {}
775
+ }
776
+ }
777
+
749
778
  function normalizeTokenUsageBreakdown(value) {
750
779
  if (!value || typeof value !== 'object') return null;
751
780
  return {
@@ -822,18 +851,65 @@ let _hljs = null;
822
851
 
823
852
 
824
853
  // ── Plan state functions (lines 886-938) ──
854
+ function isPlanModePromptText(text) {
855
+ const value = String(text || '').trim();
856
+ if (!value) return false;
857
+ return /^\[PLAN MODE\b/i.test(value)
858
+ || CODEX_PLAN_MODE_PREFIXES.some((prefix) => value.startsWith(prefix));
859
+ }
860
+
861
+ function syncPlanHandoffFlags(tab = _boundTab) {
862
+ if (!tab || !isActiveTab(tab)) return;
863
+ _planTurnActive = !!tab.planTurnActive;
864
+ _planApprovalPending = !!tab.planApprovalPending;
865
+ _lastPlanTurnId = tab.lastPlanTurnId || '';
866
+ }
867
+
868
+ function setPlanTurnActive(tab = _boundTab, value, turnId = '') {
869
+ if (!tab) return;
870
+ tab.planTurnActive = !!value;
871
+ if (turnId !== undefined) tab.lastPlanTurnId = turnId || '';
872
+ syncPlanHandoffFlags(tab);
873
+ }
874
+
875
+ function setPlanApprovalPending(tab = _boundTab, value) {
876
+ if (!tab) return;
877
+ tab.planApprovalPending = !!value;
878
+ syncPlanHandoffFlags(tab);
879
+ }
880
+
881
+ function shouldHoldForPlanApproval(tab = activeTab()) {
882
+ return !!(tab?.showPostPlanActions || tab?.planApprovalPending);
883
+ }
884
+
885
+ function captureCompletedPlanItem(tab = _boundTab, item = null) {
886
+ if (!tab || !tab.planTurnActive || !item) return '';
887
+ const itemType = item.type || '';
888
+ if (itemType !== 'plan' && itemType !== 'agentMessage') return '';
889
+ const state = item.id ? tab.items?.get?.(item.id) : null;
890
+ const text = String(state?.buffer || item.text || '').trim();
891
+ if (text.length <= 80) return '';
892
+ return capturePlanContent(tab, text);
893
+ }
894
+
825
895
  export function clearPlanHandoffState(tab = _boundTab, { keepFilePath = false } = {}) {
826
896
  if (!tab) return;
827
897
  tab.planContent = '';
828
898
  tab.editedPlanContent = '';
829
899
  tab.showPostPlanActions = false;
830
900
  tab.postPlanHeader = 'PLAN COMPLETE';
901
+ tab.planTurnActive = false;
902
+ tab.planApprovalPending = false;
903
+ tab.lastPlanTurnId = '';
831
904
  if (!keepFilePath) tab.planFilePath = '';
832
905
  if (isActiveTab(tab)) {
833
906
  _planContent = '';
834
907
  _editedPlanContent = '';
835
908
  _showPostPlanActions = false;
836
909
  _postPlanHeader = 'PLAN COMPLETE';
910
+ _planTurnActive = false;
911
+ _planApprovalPending = false;
912
+ _lastPlanTurnId = '';
837
913
  if (!keepFilePath) _planFilePath = '';
838
914
  }
839
915
  removePostPlanCards(tab.messagesEl);
@@ -914,6 +990,39 @@ export function capturePlanContent(tab = _boundTab, explicitText = '') {
914
990
  return next;
915
991
  }
916
992
 
993
+ export async function ensurePlanFile(tab = _boundTab) {
994
+ if (!tab) return '';
995
+ if (tab.planFilePath) return tab.planFilePath;
996
+ if (tab._planFilePromise) return tab._planFilePromise;
997
+ const planText = capturePlanContent(tab);
998
+ if (!planText) return '';
999
+
1000
+ tab._planFilePromise = fetch('/api/create-plan', {
1001
+ method: 'POST',
1002
+ headers: { 'Content-Type': 'application/json' },
1003
+ body: JSON.stringify({ content: planText, cwd: tab.project || _project || '' }),
1004
+ })
1005
+ .then(async (res) => {
1006
+ const result = await res.json().catch(() => ({}));
1007
+ if (!res.ok || !result?.ok || !result.path) {
1008
+ throw new Error(result?.error || 'create-plan failed');
1009
+ }
1010
+ tab.planFilePath = result.path;
1011
+ if (isActiveTab(tab)) _planFilePath = result.path;
1012
+ saveTabs();
1013
+ return result.path;
1014
+ })
1015
+ .catch((err) => {
1016
+ console.warn('[codex-panel] create-plan failed:', err);
1017
+ return '';
1018
+ })
1019
+ .finally(() => {
1020
+ tab._planFilePromise = null;
1021
+ });
1022
+
1023
+ return tab._planFilePromise;
1024
+ }
1025
+
917
1026
  // ── Formatting & helpers (lines 1148-1421) ──
918
1027
  export function formatStatus(status) {
919
1028
  if (!status) return 'pending';
@@ -966,6 +1075,7 @@ function formatContextTrackerState(meta) {
966
1075
  }
967
1076
 
968
1077
  const CODEX_MODEL_PRICING = {
1078
+ 'gpt-5.5': { input: 5.00, cachedInput: 5.00, output: 30.00 },
969
1079
  'gpt-5.4': { input: 2.50, cachedInput: 0.25, output: 15.00 },
970
1080
  'gpt-5.4-mini': { input: 0.75, cachedInput: 0.075, output: 4.50 },
971
1081
  };
@@ -1000,10 +1110,15 @@ function itemHeadline(item) {
1000
1110
  : 'Preparing file changes',
1001
1111
  };
1002
1112
  case 'reasoning':
1003
- return {
1004
- title: 'Reasoning',
1005
- detail: cleanPreview(Array.isArray(item.summary) ? item.summary.join(' ') : 'Working through the next step'),
1006
- };
1113
+ {
1114
+ const summaryText = Array.isArray(item.summary)
1115
+ ? item.summary.map((part) => typeof part === 'string' ? part : (part?.text || part?.summaryText || '')).filter(Boolean).join(' ')
1116
+ : (item.summaryText || item.summary_text || item.text || '');
1117
+ return {
1118
+ title: 'Reasoning summary',
1119
+ detail: cleanPreview(summaryText || 'Working through the next step'),
1120
+ };
1121
+ }
1007
1122
  case 'plan':
1008
1123
  return {
1009
1124
  title: 'Updating plan',
@@ -1269,6 +1384,9 @@ export function snapshotBoundState() {
1269
1384
  editedPlanContent: _editedPlanContent,
1270
1385
  showPostPlanActions: _showPostPlanActions,
1271
1386
  postPlanHeader: _postPlanHeader,
1387
+ planTurnActive: _planTurnActive,
1388
+ planApprovalPending: _planApprovalPending,
1389
+ lastPlanTurnId: _lastPlanTurnId,
1272
1390
  showPostCompactionPrompt: _showPostCompactionPrompt,
1273
1391
  postCompactionPending: _postCompactionPending,
1274
1392
  blockingServerRequests: _blockingServerRequests,
@@ -1319,6 +1437,9 @@ export function restoreBoundState(state) {
1319
1437
  _editedPlanContent = state.editedPlanContent || '';
1320
1438
  _showPostPlanActions = !!state.showPostPlanActions;
1321
1439
  _postPlanHeader = state.postPlanHeader || 'PLAN COMPLETE';
1440
+ _planTurnActive = !!state.planTurnActive;
1441
+ _planApprovalPending = !!state.planApprovalPending;
1442
+ _lastPlanTurnId = state.lastPlanTurnId || '';
1322
1443
  _showPostCompactionPrompt = !!state.showPostCompactionPrompt;
1323
1444
  _postCompactionPending = !!state.postCompactionPending;
1324
1445
  _blockingServerRequests = state.blockingServerRequests || new Map();
@@ -1368,6 +1489,9 @@ export function commitBoundState() {
1368
1489
  _boundTab.editedPlanContent = _editedPlanContent;
1369
1490
  _boundTab.showPostPlanActions = _showPostPlanActions;
1370
1491
  _boundTab.postPlanHeader = _postPlanHeader;
1492
+ _boundTab.planTurnActive = _planTurnActive;
1493
+ _boundTab.planApprovalPending = _planApprovalPending;
1494
+ _boundTab.lastPlanTurnId = _lastPlanTurnId;
1371
1495
  _boundTab.showPostCompactionPrompt = _showPostCompactionPrompt;
1372
1496
  _boundTab.postCompactionPending = _postCompactionPending;
1373
1497
  _boundTab.blockingServerRequests = _blockingServerRequests;
@@ -1417,6 +1541,9 @@ export function bindTabState(tab) {
1417
1541
  _editedPlanContent = tab?.editedPlanContent || '';
1418
1542
  _showPostPlanActions = !!tab?.showPostPlanActions;
1419
1543
  _postPlanHeader = tab?.postPlanHeader || 'PLAN COMPLETE';
1544
+ _planTurnActive = !!tab?.planTurnActive;
1545
+ _planApprovalPending = !!tab?.planApprovalPending;
1546
+ _lastPlanTurnId = tab?.lastPlanTurnId || '';
1420
1547
  _showPostCompactionPrompt = !!tab?.showPostCompactionPrompt;
1421
1548
  _postCompactionPending = !!tab?.postCompactionPending;
1422
1549
  _blockingServerRequests = tab?.blockingServerRequests || new Map();
@@ -1484,6 +1611,7 @@ function hasMeaningfulTrayState(tab) {
1484
1611
  const draft = normalizeSessionLabel(tab.draft || '');
1485
1612
  return !!(
1486
1613
  tab.threadId
1614
+ || hasCustomSessionLabel(tab)
1487
1615
  || tab.running
1488
1616
  || tab.startingThread
1489
1617
  || draft
@@ -1493,6 +1621,76 @@ function hasMeaningfulTrayState(tab) {
1493
1621
  );
1494
1622
  }
1495
1623
 
1624
+ function hasCustomSessionLabel(tab) {
1625
+ const label = normalizeSessionLabel(tab?.pendingSessionLabel || tab?.sessionLabel || '');
1626
+ if (!label) return false;
1627
+ return !['new session', 'saved session', 'untitled session', 'codex'].includes(label.toLowerCase());
1628
+ }
1629
+
1630
+ function hasMeaningfulTabState(tab) {
1631
+ if (!tab) return false;
1632
+ return !!(
1633
+ hasMeaningfulTrayState(tab)
1634
+ || tab.planContent
1635
+ || tab.editedPlanContent
1636
+ || tab.showPostPlanActions
1637
+ || tab.planApprovalPending
1638
+ || tab.postCompactionPending
1639
+ || tab.items?.size
1640
+ || tab.activeItems?.size
1641
+ || tab.blockingServerRequests?.size
1642
+ );
1643
+ }
1644
+
1645
+ function isDiscardableBlankTab(tab) {
1646
+ return !!tab && !hasMeaningfulTabState(tab);
1647
+ }
1648
+
1649
+ function disposeDiscardedTab(tab) {
1650
+ if (!tab) return;
1651
+ flushThreadSnapshotSave(tab);
1652
+ disconnectTab(tab);
1653
+ tab.closed = true;
1654
+ if (tab.snapshotTimer) {
1655
+ clearTimeout(tab.snapshotTimer);
1656
+ tab.snapshotTimer = null;
1657
+ }
1658
+ tab.messagesEl?.remove();
1659
+ tab.pillEl?.remove();
1660
+ }
1661
+
1662
+ function pruneDiscardableTabs({ keepTab = activeTab(), persist = false } = {}) {
1663
+ // Blank tabs are provisional composer state; inactive blanks have no tray affordance.
1664
+ let pruned = false;
1665
+ for (let idx = _tabs.length - 1; idx >= 0; idx -= 1) {
1666
+ const tab = _tabs[idx];
1667
+ if (tab === keepTab || !isDiscardableBlankTab(tab)) continue;
1668
+ disposeDiscardedTab(tab);
1669
+ _tabs.splice(idx, 1);
1670
+ if (_activeTabIdx > idx) _activeTabIdx -= 1;
1671
+ else if (_activeTabIdx >= _tabs.length) _activeTabIdx = _tabs.length - 1;
1672
+ pruned = true;
1673
+ }
1674
+ if (_activeTabIdx < 0 && _tabs.length) _activeTabIdx = 0;
1675
+ if (pruned) {
1676
+ renderPills();
1677
+ if (persist) saveTabs();
1678
+ }
1679
+ return pruned;
1680
+ }
1681
+
1682
+ function isBlankTabRequest(saved = {}) {
1683
+ return !saved.threadId
1684
+ && !normalizeSessionLabel(saved.title || '')
1685
+ && !normalizeSessionLabel(saved.draft || '')
1686
+ && !saved.planContent
1687
+ && !saved.editedPlanContent
1688
+ && !saved.showPostPlanActions
1689
+ && !saved.planApprovalPending
1690
+ && !saved.showPostCompactionPrompt
1691
+ && !saved.postCompactionPending;
1692
+ }
1693
+
1496
1694
  function createTrayPill(tab) {
1497
1695
  const tray = document.getElementById('term-minimized-tray');
1498
1696
  if (!tray) return null;
@@ -1588,7 +1786,10 @@ export function switchTab(idx) {
1588
1786
  }
1589
1787
 
1590
1788
  export function createTab(saved = {}, { autoSwitch = true } = {}) {
1591
- if (_tabs.length >= MAX_TABS) return null;
1789
+ if (autoSwitch && isBlankTabRequest(saved) && isDiscardableBlankTab(activeTab())) {
1790
+ return activeTab();
1791
+ }
1792
+ pruneDiscardableTabs({ keepTab: activeTab(), persist: false });
1592
1793
  const inheritedProject = saved.project ?? activeTab()?.project ?? (storage.getItem(STOR.project) || '');
1593
1794
  const threadId = saved.threadId || null;
1594
1795
  const title = normalizeSessionLabel(saved.title || '');
@@ -1653,6 +1854,9 @@ export function createTab(saved = {}, { autoSwitch = true } = {}) {
1653
1854
  editedPlanContent: saved.editedPlanContent || '',
1654
1855
  showPostPlanActions: !!saved.showPostPlanActions,
1655
1856
  postPlanHeader: saved.postPlanHeader || 'PLAN COMPLETE',
1857
+ planTurnActive: !!saved.planTurnActive,
1858
+ planApprovalPending: !!saved.planApprovalPending,
1859
+ lastPlanTurnId: saved.lastPlanTurnId || '',
1656
1860
  pendingSessionLabel: saved.pendingSessionLabel || null, // user-named before thread exists; sent via thread_rename once ready
1657
1861
  showPostCompactionPrompt: !!saved.showPostCompactionPrompt,
1658
1862
  postCompactionPending: !!saved.postCompactionPending,
@@ -1736,6 +1940,7 @@ export function closeTab(idx) {
1736
1940
  export function saveTabs() {
1737
1941
  try {
1738
1942
  commitBoundState();
1943
+ pruneDiscardableTabs({ keepTab: activeTab(), persist: false });
1739
1944
  const payload = JSON.stringify({
1740
1945
  tabs: _tabs.map((tab) => {
1741
1946
  const persistedTokenUsage = normalizeThreadTokenUsage(tab.threadTokenUsage);
@@ -1760,6 +1965,9 @@ export function saveTabs() {
1760
1965
  editedPlanContent: tab.editedPlanContent || '',
1761
1966
  showPostPlanActions: !!tab.showPostPlanActions,
1762
1967
  postPlanHeader: tab.postPlanHeader || 'PLAN COMPLETE',
1968
+ planTurnActive: !!tab.planTurnActive,
1969
+ planApprovalPending: !!tab.planApprovalPending,
1970
+ lastPlanTurnId: tab.lastPlanTurnId || '',
1763
1971
  showPostCompactionPrompt: !!tab.showPostCompactionPrompt,
1764
1972
  postCompactionPending: !!tab.postCompactionPending,
1765
1973
  };
@@ -1779,6 +1987,8 @@ export function restoreTabs() {
1779
1987
  if (Array.isArray(data?.tabs) && data.tabs.length) {
1780
1988
  for (const saved of data.tabs) createTab(saved, { autoSwitch: false });
1781
1989
  _activeTabIdx = Math.min(Math.max(Number(data.activeIdx) || 0, 0), _tabs.length - 1);
1990
+ pruneDiscardableTabs({ keepTab: activeTab(), persist: false });
1991
+ if (!_tabs.length) throw new Error('No restorable Codex tabs');
1782
1992
  updateActiveTabView();
1783
1993
  saveTabs();
1784
1994
  return;
@@ -1831,6 +2041,7 @@ function startupEmptyText() {
1831
2041
  export function clearTranscript(emptyText = freshThreadText()) {
1832
2042
  if (!_messagesEl) return;
1833
2043
  _messagesEl.innerHTML = `<div class="cxp-empty"><div class="cxp-empty-logo">${OPENAI_ICON}</div><span class="cxp-empty-name">Codex</span><span class="cxp-empty-status"></span></div>`;
2044
+ document.dispatchEvent(new CustomEvent('cdx-empty-rebuilt'));
1834
2045
  _items.clear();
1835
2046
  _requestCards.clear();
1836
2047
  _blockingServerRequests = new Map();
@@ -3061,15 +3272,18 @@ export function syncInputEnabled() {
3061
3272
  const hasPaths = !!(tab?.pendingPaths?.length);
3062
3273
  const blockedByRequest = hasBlockingServerRequests();
3063
3274
  if (input) input.disabled = !_connected || !_bootstrapped || _startingThread || blockedByRequest;
3275
+ const cliBlocked = document.getElementById('codex-panel')?.classList.contains('cxp-cli-blocked');
3064
3276
  if (send) {
3065
- // Stop button shows when any tab in the panel is running, so the user can
3066
- // abort the whole panel regardless of which tab is focused.
3067
- const isStopMode = _running || _tabs.some((t) => t?.running);
3277
+ const isStopMode = !!(tab?.running || _running);
3068
3278
  send.classList.toggle('cxp-send-stopping', !!isStopMode);
3069
- send.title = isStopMode ? 'Stop' : 'Send';
3070
- send.disabled = isStopMode ? false : (!_connected || !_bootstrapped || _running || _startingThread || blockedByRequest || (!hasText && !hasImages && !hasPaths));
3279
+ send.title = cliBlocked ? 'Codex CLI not installed' : (isStopMode ? 'Stop' : 'Send');
3280
+ send.disabled = cliBlocked || (isStopMode ? false : (!_connected || !_bootstrapped || _running || _startingThread || blockedByRequest || (!hasText && !hasImages && !hasPaths)));
3281
+ }
3282
+ if (fresh) {
3283
+ pruneDiscardableTabs({ keepTab: tab, persist: true });
3284
+ fresh.disabled = false;
3285
+ fresh.title = 'New Codex tab';
3071
3286
  }
3072
- if (fresh) fresh.disabled = _tabs.length >= MAX_TABS;
3073
3287
  if (close) close.disabled = false;
3074
3288
  if (compact) {
3075
3289
  compact.disabled = !_connected || !_bootstrapped || !_threadId || _running || _startingThread || _compacting;
@@ -3108,7 +3322,9 @@ export function dispatchPrompt(prompt, { tab = activeTab(), images = null, paths
3108
3322
  finalPrompt += `${finalPrompt ? '\n\n' : ''}Referenced files:\n${pathList.map((path) => `- ${path}`).join('\n')}`;
3109
3323
  }
3110
3324
 
3325
+ const explicitPlanPrompt = isPlanModePromptText(finalPrompt);
3111
3326
  const usePlanModePrefix = forcePlanModePrefix == null ? !!tab?.planMode : !!forcePlanModePrefix;
3327
+ const sentAsPlanMode = !!usePlanModePrefix || explicitPlanPrompt;
3112
3328
  if (usePlanModePrefix) {
3113
3329
  finalPrompt = `${CODEX_PLAN_MODE_PREFIXES[0]}\n\n${finalPrompt}`;
3114
3330
  }
@@ -3117,6 +3333,8 @@ export function dispatchPrompt(prompt, { tab = activeTab(), images = null, paths
3117
3333
  type: 'query',
3118
3334
  requestId: crypto.randomUUID(),
3119
3335
  prompt: finalPrompt,
3336
+ planMode: sentAsPlanMode,
3337
+ autoAccept: !!tab?.autoAccept,
3120
3338
  threadId: _threadId || null,
3121
3339
  fresh: _freshThread || !_threadId,
3122
3340
  cwd: _project || null,
@@ -3133,6 +3351,16 @@ export function dispatchPrompt(prompt, { tab = activeTab(), images = null, paths
3133
3351
  setStatus('Codex is not connected', 'error');
3134
3352
  return false;
3135
3353
  }
3354
+ if (tab) {
3355
+ tab.planTurnActive = sentAsPlanMode;
3356
+ tab.planApprovalPending = false;
3357
+ tab.lastPlanTurnId = msg.requestId;
3358
+ if (isActiveTab(tab)) {
3359
+ _planTurnActive = sentAsPlanMode;
3360
+ _planApprovalPending = false;
3361
+ _lastPlanTurnId = msg.requestId;
3362
+ }
3363
+ }
3136
3364
  clearPostCompactionState(tab, { updateTranscript: !_freshThread });
3137
3365
  if (_freshThread) clearTranscript('Starting fresh Codex thread…');
3138
3366
  _freshThread = false;
@@ -3194,29 +3422,47 @@ export function handleNotify(method, params) {
3194
3422
  break;
3195
3423
  case 'turn/completed':
3196
3424
  if (_compacting) setCompactingUI(false);
3197
- setRunning(false);
3198
- if (params.turn?.error?.message) {
3199
- appendSystem(params.turn.error.message, 'error');
3200
- setStatus(params.turn.error.message, 'error');
3201
- notifyCodexTurnOutcome(NOTIF_TYPE.ERROR, _boundTab, params.turn?.id || _activeTurnId);
3202
- } else if (_boundTab?.planMode) {
3203
- const capturedPlan = capturePlanContent(_boundTab);
3204
- if (capturedPlan) {
3205
- _boundTab.showPostPlanActions = true;
3206
- _boundTab.postPlanHeader = 'PLAN COMPLETE';
3207
- _showPostPlanActions = true;
3208
- _postPlanHeader = 'PLAN COMPLETE';
3209
- renderPostPlanActions(_boundTab);
3210
- saveTabs();
3425
+ {
3426
+ const tab = _boundTab;
3427
+ const wasPlanTurn = !!(tab?.planTurnActive || _planTurnActive || tab?.planMode);
3428
+ setRunning(false);
3429
+ if (params.turn?.error?.message) {
3430
+ setPlanTurnActive(_boundTab, false, '');
3431
+ setPlanApprovalPending(_boundTab, false);
3432
+ appendSystem(params.turn.error.message, 'error');
3433
+ setStatus(params.turn.error.message, 'error');
3434
+ notifyCodexTurnOutcome(NOTIF_TYPE.ERROR, _boundTab, params.turn?.id || _activeTurnId);
3435
+ } else if (wasPlanTurn && tab) {
3436
+ const capturedPlan = capturePlanContent(tab);
3437
+ if (capturedPlan) {
3438
+ tab.planMode = false;
3439
+ tab.showPostPlanActions = true;
3440
+ tab.postPlanHeader = 'PLAN COMPLETE';
3441
+ tab.planApprovalPending = true;
3442
+ tab.planTurnActive = false;
3443
+ _showPostPlanActions = true;
3444
+ _postPlanHeader = 'PLAN COMPLETE';
3445
+ _planApprovalPending = true;
3446
+ _planTurnActive = false;
3447
+ _lastPlanTurnId = '';
3448
+ tab.lastPlanTurnId = '';
3449
+ ensurePlanFile(tab);
3450
+ renderPostPlanActions(tab);
3451
+ syncToolbarState();
3452
+ saveTabs();
3453
+ } else {
3454
+ setPlanTurnActive(tab, false, '');
3455
+ setPlanApprovalPending(tab, false);
3456
+ }
3457
+ notifyCodexTurnOutcome(NOTIF_TYPE.DONE, tab, params.turn?.id || _activeTurnId);
3458
+ } else {
3459
+ notifyCodexTurnOutcome(NOTIF_TYPE.DONE, _boundTab, params.turn?.id || _activeTurnId);
3211
3460
  }
3212
- notifyCodexTurnOutcome(NOTIF_TYPE.DONE, _boundTab, params.turn?.id || _activeTurnId);
3213
- } else {
3214
- notifyCodexTurnOutcome(NOTIF_TYPE.DONE, _boundTab, params.turn?.id || _activeTurnId);
3215
- }
3216
- if (_boundTab?.postCompactionPending && !_boundTab?.showPostPlanActions) {
3217
- flushPostCompactionPrompt(_boundTab);
3461
+ if (_boundTab?.postCompactionPending && !_boundTab?.showPostPlanActions) {
3462
+ flushPostCompactionPrompt(_boundTab);
3463
+ }
3464
+ if (!shouldHoldForPlanApproval(_boundTab)) advanceQueue();
3218
3465
  }
3219
- advanceQueue();
3220
3466
  break;
3221
3467
  case 'thread/compacted':
3222
3468
  setCompactingUI(false);
@@ -3266,25 +3512,30 @@ export function handleNotify(method, params) {
3266
3512
  case 'item/autoApprovalReview/completed':
3267
3513
  setGuardianReviewState(params.targetItemId, params.review);
3268
3514
  break;
3269
- case 'item/completed':
3515
+ case 'item/completed': {
3516
+ const completedItem = params.item?.type === 'reasoning' && params.item.status == null
3517
+ ? { ...params.item, status: 'complete' }
3518
+ : params.item;
3270
3519
  // Inject pending images into userMessage if not consumed at item/started
3271
- if (params.item?.type === 'userMessage' && _boundTab?._pendingSendImages?.length) {
3520
+ if (completedItem?.type === 'userMessage' && _boundTab?._pendingSendImages?.length) {
3272
3521
  const imgItems = _boundTab._pendingSendImages.map((img) => ({
3273
3522
  type: 'localImage',
3274
3523
  name: img.name || 'image',
3275
3524
  dataUrl: img.dataUrl,
3276
3525
  }));
3277
- params.item.content = [...imgItems, ...(params.item.content || [])];
3526
+ completedItem.content = [...imgItems, ...(completedItem.content || [])];
3278
3527
  _boundTab._pendingSendImages = null;
3279
3528
  }
3280
- updateItemFromData(params.item);
3281
- trackActiveItemCompletion(params.item);
3282
- if (params.item?.type === 'contextCompaction') {
3529
+ updateItemFromData(completedItem);
3530
+ trackActiveItemCompletion(completedItem);
3531
+ captureCompletedPlanItem(_boundTab, completedItem);
3532
+ if (completedItem?.type === 'contextCompaction') {
3283
3533
  markPostCompactionPrompt(_boundTab, { defer: _running });
3284
3534
  }
3285
3535
  // Re-show thinking — Codex is processing between items
3286
3536
  if (_running) showThinking();
3287
3537
  break;
3538
+ }
3288
3539
  case 'item/agentMessage/delta':
3289
3540
  appendAgentDelta(params.itemId, params.delta);
3290
3541
  repositionThinking();
@@ -3294,14 +3545,19 @@ export function handleNotify(method, params) {
3294
3545
  repositionThinking();
3295
3546
  break;
3296
3547
  case 'item/reasoning/textDelta':
3548
+ hideThinking();
3297
3549
  appendReasoningDelta(params.itemId, params.delta, false);
3298
- repositionThinking();
3550
+ syncWorkStatus();
3299
3551
  break;
3300
3552
  case 'item/reasoning/summaryTextDelta':
3553
+ hideThinking();
3301
3554
  appendReasoningDelta(params.itemId, params.delta, true, params.summaryIndex);
3555
+ syncWorkStatus();
3302
3556
  break;
3303
3557
  case 'item/reasoning/summaryPartAdded':
3558
+ hideThinking();
3304
3559
  appendReasoningSummaryPart(params.itemId, params.summaryIndex);
3560
+ syncWorkStatus();
3305
3561
  break;
3306
3562
  case 'item/commandExecution/outputDelta':
3307
3563
  appendOutputDelta(params.itemId, params.delta, 'commandExecution');
@@ -3451,7 +3707,7 @@ export function syncToolbarState() {
3451
3707
  effortBtn.title = `Effort level: ${effort}`;
3452
3708
  effortBtn.classList.toggle('active', effort !== 'off');
3453
3709
  const dots = effortBtn.querySelectorAll('.cxp-effort-dots i');
3454
- const litCount = effort === 'off' ? 0 : effort === 'low' ? 1 : effort === 'medium' ? 2 : 3;
3710
+ const litCount = Math.max(0, EFFORT_LEVELS.indexOf(effort));
3455
3711
  dots.forEach((d, i) => d.classList.toggle('lit', i < litCount));
3456
3712
  }
3457
3713
  if (planBtn) {
@@ -3472,24 +3728,42 @@ export function syncToolbarState() {
3472
3728
  }
3473
3729
  }
3474
3730
 
3475
- const CODEX_DEFAULT_MODELS = ['gpt-5.4', 'gpt-5.4-mini', 'gpt-5.3-codex'];
3731
+ const CODEX_DEFAULT_MODELS = ['gpt-5.5', 'gpt-5.4', 'gpt-5.4-mini', 'gpt-5.3-codex'];
3476
3732
  let _modelListCache = null;
3477
3733
  let _modelListRequest = null;
3478
3734
 
3735
+ function getCodexModelName(model) {
3736
+ const name = typeof model === 'string' ? model : (model?.id || model?.model || model?.name || '');
3737
+ return String(name || '').trim();
3738
+ }
3739
+
3740
+ function mergeCodexModelList(models) {
3741
+ const merged = [];
3742
+ const seen = new Set();
3743
+ for (const model of [...CODEX_DEFAULT_MODELS, ...(Array.isArray(models) ? models : [])]) {
3744
+ const name = getCodexModelName(model);
3745
+ const key = name.toLowerCase();
3746
+ if (!name || seen.has(key)) continue;
3747
+ seen.add(key);
3748
+ merged.push(name);
3749
+ }
3750
+ return merged;
3751
+ }
3752
+
3479
3753
  export function requestModelList() {
3480
3754
  if (_modelListCache) return Promise.resolve(_modelListCache);
3481
3755
  if (_modelListRequest) return _modelListRequest;
3482
3756
  _modelListRequest = new Promise((resolve) => {
3483
3757
  const requestId = crypto.randomUUID();
3484
3758
  if (!sendSocket({ type: 'model_list', requestId })) {
3485
- _modelListCache = CODEX_DEFAULT_MODELS;
3759
+ _modelListCache = mergeCodexModelList([]);
3486
3760
  populateModelDropdown(_modelListCache);
3487
3761
  resolve(_modelListCache);
3488
3762
  _modelListRequest = null;
3489
3763
  return;
3490
3764
  }
3491
3765
  window.__cxpModelListResolve = (models) => {
3492
- _modelListCache = models?.length ? models : CODEX_DEFAULT_MODELS;
3766
+ _modelListCache = mergeCodexModelList(models);
3493
3767
  populateModelDropdown(_modelListCache);
3494
3768
  resolve(_modelListCache);
3495
3769
  _modelListRequest = null;
@@ -3511,7 +3785,7 @@ export function populateModelDropdown(models) {
3511
3785
  if (!menu) return;
3512
3786
  menu.innerHTML = '';
3513
3787
  for (const m of models) {
3514
- const name = typeof m === 'string' ? m : (m?.id || m?.model || m?.name || '');
3788
+ const name = getCodexModelName(m);
3515
3789
  if (!name) continue;
3516
3790
  const item = document.createElement('div');
3517
3791
  item.className = 'cxp-dd-item';
@@ -3576,22 +3850,35 @@ export function stopStallTimer() {
3576
3850
  _stallTimer = null;
3577
3851
  }
3578
3852
 
3579
- export function interruptTurn() {
3580
- if (!_running) return;
3853
+ export function interruptTab(tab = activeTab()) {
3854
+ if (!tab?.running) return false;
3855
+
3856
+ return withTab(tab, () => {
3857
+ const threadId = _threadId || tab.threadId || null;
3858
+
3859
+ // Reset UI immediately; the server interrupt/kill is best-effort cleanup.
3860
+ setRunning(false);
3861
+ setStatus('Stopped', 'info');
3862
+ appendSystem('Stopped.', 'muted');
3581
3863
 
3582
- // Reset UI immediately — don't wait for server round-trip
3583
- setRunning(false);
3584
- setStatus('Stopped', 'info');
3585
- appendSystem('Stopped.', 'muted');
3864
+ clearInterruptTimer();
3865
+ if (tab._abortRetry) {
3866
+ clearTimeout(tab._abortRetry);
3867
+ tab._abortRetry = null;
3868
+ }
3869
+ sendSocket({ type: 'interrupt', sessionId: tab.id, threadId });
3870
+ tab._abortRetry = setTimeout(() => {
3871
+ tab._abortRetry = null;
3872
+ const ws = tab.ws;
3873
+ if (ws?.readyState !== 1) return;
3874
+ try { ws.send(JSON.stringify({ type: 'force_kill', sessionId: tab.id, threadId })); } catch {}
3875
+ }, 2500);
3876
+ return true;
3877
+ });
3878
+ }
3586
3879
 
3587
- clearInterruptTimer();
3588
- sendSocket({ type: 'interrupt', threadId: _threadId || null });
3589
- _interruptTimer = setTimeout(() => {
3590
- _interruptTimer = null;
3591
- if (_interruptRetried) return;
3592
- _interruptRetried = true;
3593
- sendSocket({ type: 'force_kill', threadId: _threadId || null });
3594
- }, 2500);
3880
+ export function interruptTurn() {
3881
+ return interruptTab(activeTab());
3595
3882
  }
3596
3883
 
3597
3884
  export function interruptAllTabs() {
@@ -3602,12 +3889,12 @@ export function interruptAllTabs() {
3602
3889
  const ws = tab.ws;
3603
3890
  const threadId = tab.threadId || null;
3604
3891
  if (ws && ws.readyState === 1) {
3605
- try { ws.send(JSON.stringify({ type: 'interrupt', threadId })); } catch {}
3892
+ try { ws.send(JSON.stringify({ type: 'interrupt', sessionId: tab.id, threadId })); } catch {}
3606
3893
  if (tab._abortRetry) clearTimeout(tab._abortRetry);
3607
3894
  tab._abortRetry = setTimeout(() => {
3608
3895
  tab._abortRetry = null;
3609
3896
  try {
3610
- if (ws.readyState === 1) ws.send(JSON.stringify({ type: 'force_kill', threadId }));
3897
+ if (ws.readyState === 1) ws.send(JSON.stringify({ type: 'force_kill', sessionId: tab.id, threadId }));
3611
3898
  } catch {}
3612
3899
  }, 2000);
3613
3900
  }
@@ -3778,6 +4065,8 @@ function formatStatusSnapshotMarkdown(snapshot, tab = activeTab()) {
3778
4065
  `- Thread: \`${runtime.activeThreadId || tab?.threadId || 'none'}\`${runtime.activeTurnId ? ` (active turn \`${runtime.activeTurnId}\`)` : ''}`,
3779
4066
  `- Model: \`${tab?.model || effective.model || 'default'}\``,
3780
4067
  `- Reasoning effort: \`${tab?.effort && tab.effort !== 'off' ? tab.effort : (effective.model_reasoning_effort || 'default')}\``,
4068
+ `- Reasoning summary: \`${effective.model_reasoning_summary || 'default'}\``,
4069
+ `- Response verbosity: \`${effective.model_verbosity || 'default'}\``,
3781
4070
  `- Personality: \`${effective.personality || 'pragmatic'}\``,
3782
4071
  `- Fast mode: \`${effective.service_tier || 'default'}\``,
3783
4072
  `- Approval policy: \`${effective.approval_policy || 'never'}\``,
@@ -3911,11 +4200,18 @@ function setModelForTab(tab, model) {
3911
4200
  }
3912
4201
 
3913
4202
  function setEffortForTab(tab, effort) {
3914
- if (!tab || !effort) return;
3915
- tab.effort = effort;
3916
- storage.setItem(STOR.effort, effort);
4203
+ if (!tab) return false;
4204
+ const normalized = String(effort || '').trim().toLowerCase();
4205
+ const nextEffort = normalized === 'default' || normalized === 'none' ? 'off' : normalized;
4206
+ if (!EFFORT_LEVELS.includes(nextEffort)) {
4207
+ appendSystem(`Unknown reasoning effort: ${effort}. Use off, minimal, low, medium, high, or xhigh.`, 'error');
4208
+ return false;
4209
+ }
4210
+ tab.effort = nextEffort;
4211
+ storage.setItem(STOR.effort, nextEffort);
3917
4212
  syncToolbarState();
3918
4213
  saveTabs();
4214
+ return true;
3919
4215
  }
3920
4216
 
3921
4217
  async function handleLocalSlashCommand(payload = currentComposerPayload()) {
@@ -4093,8 +4389,7 @@ async function handleLocalSlashCommand(payload = currentComposerPayload()) {
4093
4389
  cycleEffort();
4094
4390
  break;
4095
4391
  }
4096
- setEffortForTab(tab, args);
4097
- appendMarkdown(`Reasoning effort set to \`${args}\`.`);
4392
+ if (setEffortForTab(tab, args)) appendMarkdown(`Reasoning effort set to \`${tab.effort || 'off'}\`.`);
4098
4393
  break;
4099
4394
  case 'mcp':
4100
4395
  consumeComposer(input, tab, { clearAttachments: false });
@@ -4235,6 +4530,12 @@ export function sendPrompt() {
4235
4530
  const { input, tab, prompt, images, paths } = payload;
4236
4531
  if (!input || !_connected || !_bootstrapped) return;
4237
4532
  if (!prompt && !images.length && !paths.length) return;
4533
+ // Block when CLI binary missing — banner already informs user.
4534
+ const panel = document.getElementById('codex-panel');
4535
+ if (panel?.classList.contains('cxp-cli-blocked')) {
4536
+ appendSystem('Codex CLI not installed. Install it first, then click Re-check.', 'error');
4537
+ return;
4538
+ }
4238
4539
  handleLocalSlashCommand(payload).then((handled) => {
4239
4540
  if (handled) return;
4240
4541
  if (_running && tab) {
@@ -4242,6 +4543,10 @@ export function sendPrompt() {
4242
4543
  consumeComposer(input, tab);
4243
4544
  return;
4244
4545
  }
4546
+ if (shouldHoldForPlanApproval(tab)) {
4547
+ appendSystem('Choose Continue with implementation, Compact context, or Edit plan before sending another message.', 'working');
4548
+ return;
4549
+ }
4245
4550
  if (tab?.planMode) {
4246
4551
  preparePlanModeTurn(tab);
4247
4552
  }
@@ -4382,7 +4687,7 @@ export function clearQueue() {
4382
4687
 
4383
4688
  export function advanceQueue() {
4384
4689
  const tab = activeTab();
4385
- if (!tab || _running || tab.queuePaused || !tab.queue.length || hasBlockingServerRequests()) return;
4690
+ if (!tab || _running || tab.queuePaused || !tab.queue.length || hasBlockingServerRequests() || shouldHoldForPlanApproval(tab)) return;
4386
4691
  const next = tab.queue.shift();
4387
4692
  if (!next) return;
4388
4693
  if (tab.planMode) {
@@ -4903,7 +5208,7 @@ export async function openSettingsPanel() {
4903
5208
  // Get dynamic model list (fall back to hardcoded defaults)
4904
5209
  let models;
4905
5210
  try { models = await requestModelList(); } catch { models = null; }
4906
- const modelList = models?.length ? models : CODEX_DEFAULT_MODELS;
5211
+ const modelList = mergeCodexModelList(models);
4907
5212
 
4908
5213
  configEl.innerHTML = '';
4909
5214
  const fieldDefs = [
@@ -4938,6 +5243,19 @@ export async function openSettingsPanel() {
4938
5243
  { value: 'high', label: 'High' },
4939
5244
  { value: 'xhigh', label: 'Extra High' },
4940
5245
  ]},
5246
+ { key: 'model_reasoning_summary', label: 'Reasoning Summary', type: 'select', options: [
5247
+ { value: '', label: '(default)' },
5248
+ { value: 'auto', label: 'Auto' },
5249
+ { value: 'concise', label: 'Concise' },
5250
+ { value: 'detailed', label: 'Detailed' },
5251
+ { value: 'none', label: 'None' },
5252
+ ]},
5253
+ { key: 'model_verbosity', label: 'Response Verbosity', type: 'select', options: [
5254
+ { value: '', label: '(default)' },
5255
+ { value: 'low', label: 'Low' },
5256
+ { value: 'medium', label: 'Medium' },
5257
+ { value: 'high', label: 'High' },
5258
+ ]},
4941
5259
  { key: 'model_context_window', label: 'Context Window', type: 'number', placeholder: 'tokens' },
4942
5260
  { key: 'model_auto_compact_token_limit', label: 'Auto-Compact', type: 'number', placeholder: 'token limit' },
4943
5261
  { key: 'web_search', label: 'Web Search', type: 'checkbox' },
@@ -5063,6 +5381,7 @@ export function initContextBridge() {
5063
5381
  isActiveTab,
5064
5382
  dispatchPrompt,
5065
5383
  capturePlanContent,
5384
+ ensurePlanFile,
5066
5385
  saveTabs,
5067
5386
  hideEmpty,
5068
5387
  showEmpty,
@@ -5101,6 +5420,9 @@ export function initContextBridge() {
5101
5420
  onBlockingServerRequestAnswered: (requestId) => clearBlockingServerRequest(requestId),
5102
5421
  setPostPlanHeader: (v) => { _postPlanHeader = v; },
5103
5422
  setShowPostPlanActions: (v) => { _showPostPlanActions = v; },
5423
+ setPlanTurnActive: (v) => { _planTurnActive = !!v; },
5424
+ setPlanApprovalPending: (v) => { _planApprovalPending = !!v; },
5425
+ setLastPlanTurnId: (v) => { _lastPlanTurnId = v || ''; },
5104
5426
  setShowPostCompactionPrompt: (v) => { _showPostCompactionPrompt = !!v; },
5105
5427
  setPostCompactionPending: (v) => { _postCompactionPending = !!v; },
5106
5428
  setPlanFilePath: (v) => { _planFilePath = v; },