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.
- package/CHANGELOG.md +175 -881
- package/README.md +1 -1
- package/hooks/claude-code/prompt-submit.mjs +39 -18
- package/hooks/claude-code/session-start.mjs +10 -3
- package/hooks/claude-code/shared.mjs +39 -2
- package/hooks/claude-code/stop.mjs +33 -6
- package/mcp-server/src/index.ts +9 -1
- package/mcp-server/src/services/neural-interface.ts +42 -14
- package/mcp-server/src/services/profiles.ts +8 -7
- package/mcp-server/src/tools/browser-advanced.ts +12 -10
- package/mcp-server/src/tools/browser-interact.ts +39 -38
- package/mcp-server/src/tools/browser-navigate.ts +12 -4
- package/mcp-server/src/tools/loop.ts +7 -3
- package/mcp-server/src/tools/profile.ts +19 -1
- package/neural-interface/lib/loop-logger.js +103 -0
- package/neural-interface/lib/mcp-installer.js +109 -0
- package/neural-interface/lib/plugin-installer.js +264 -0
- package/neural-interface/package.json +1 -1
- package/neural-interface/public/shared/api.js +66 -17
- package/neural-interface/public/shared/cdx/cdx-icons.js +5 -4
- package/neural-interface/public/shared/cdx/cdx-panel.js +117 -14
- package/neural-interface/public/shared/cdx/cdx-render.js +170 -43
- package/neural-interface/public/shared/cdx/cdx-styles.js +55 -10
- package/neural-interface/public/shared/cdx/cdx-tabs.js +394 -72
- package/neural-interface/public/shared/cli-status.js +157 -0
- package/neural-interface/public/shared/constants.js +3 -2
- package/neural-interface/public/shared/html-shell.js +32 -11
- package/neural-interface/public/shared/ocp/ocp-panel.js +127 -28
- package/neural-interface/public/shared/ocp/ocp-render.js +52 -44
- package/neural-interface/public/shared/ocp/ocp-styles.js +33 -0
- package/neural-interface/public/shared/ocp/ocp-tabs.js +693 -82
- package/neural-interface/public/shared/styles.css +635 -0
- package/neural-interface/public/shared/ui-automation-studio.js +38 -12
- package/neural-interface/public/shared/ui-claude-panel.js +322 -15
- package/neural-interface/public/shared/ui-file-explorer.js +38 -18
- package/neural-interface/public/shared/ui-menubar.js +34 -10
- package/neural-interface/public/shared/ui-navbar.js +8 -6
- package/neural-interface/public/shared/ui-schedules-studio.js +1464 -0
- package/neural-interface/public/shared/ui-settings.js +274 -50
- package/neural-interface/public/shared/ui-sidepanel-layout.js +18 -7
- package/neural-interface/public/shared/ui-sidepanel-tray.js +287 -0
- package/neural-interface/public/shared/ui-sync.js +9 -5
- package/neural-interface/public/shared/ui-terminal.js +76 -27
- package/neural-interface/public/shared/ui-tool-updates.js +49 -15
- package/neural-interface/public/shared/ui-tutorial-steps.js +4 -2
- package/neural-interface/public/shared/ui-tutorial.js +24 -18
- package/neural-interface/public/shared/ui-whiteboard.js +79 -31
- package/neural-interface/public/shared/vterm-buffer.js +9 -8
- package/neural-interface/public/variant/2d/main.js +9 -7
- package/neural-interface/public/variant/3d/main.js +10 -8
- package/neural-interface/server.js +1292 -255
- package/package.json +1 -1
- package/skills/leonardo/SKILL.md +5 -1
- package/skills/leonardo/modules/image-prompter.md +5 -1
- package/skills/leonardo/modules/video-prompter.md +5 -1
- package/skills/synabun/SKILL.md +44 -26
- package/skills/synabun/modules/audit.md +40 -36
- package/skills/synabun/modules/changelog.md +22 -18
- package/skills/synabun/modules/memorize.md +19 -15
- package/skills/synabun/modules/schedule.md +285 -0
- 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,
|
|
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) {
|
|
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) {
|
|
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) {
|
|
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
|
|
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
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
_boundTab.
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
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
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
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 (
|
|
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
|
-
|
|
3526
|
+
completedItem.content = [...imgItems, ...(completedItem.content || [])];
|
|
3278
3527
|
_boundTab._pendingSendImages = null;
|
|
3279
3528
|
}
|
|
3280
|
-
updateItemFromData(
|
|
3281
|
-
trackActiveItemCompletion(
|
|
3282
|
-
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
3580
|
-
if (!
|
|
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
|
-
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
|
|
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
|
-
|
|
3588
|
-
|
|
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
|
|
3915
|
-
|
|
3916
|
-
|
|
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
|
|
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; },
|