chore: rebuild CentOS7 release package

This commit is contained in:
shiyue
2026-07-06 13:43:35 +08:00
parent a3d83080cb
commit daaf41daa1
6 changed files with 97 additions and 29 deletions

View File

@@ -66,7 +66,8 @@
codexapp: 'Codex App',
};
const DEFAULT_AGENT = 'claude';
const PRIMARY_UI_AGENT = 'codexapp';
const DEFAULT_AGENT = PRIMARY_UI_AGENT;
const SESSION_CACHE_LIMIT = 4;
const SESSION_CACHE_MAX_WEIGHT = 1_500_000;
const SIDEBAR_SWIPE_TRIGGER = 72;
@@ -178,8 +179,8 @@
let hasGrouped = false; // 本次输出是否已触发过折叠
let cmdMenuIndex = -1;
let currentMode = 'yolo';
let currentModel = 'opus';
let currentAgent = AGENT_LABELS[localStorage.getItem('cc-web-agent')] ? localStorage.getItem('cc-web-agent') : DEFAULT_AGENT;
let currentModel = '';
let currentAgent = DEFAULT_AGENT;
let currentTheme = (document.documentElement.dataset.theme || localStorage.getItem('cc-web-theme') || 'washi');
let showAgentDividerTime = localStorage.getItem(DIVIDER_TIME_STORAGE_KEY) !== '0';
let codexConfigCache = null;
@@ -268,6 +269,7 @@
const chatSessionIdBtn = $('#chat-session-id-btn');
const chatAgentBtn = $('#chat-agent-btn');
const chatAgentMenu = $('#chat-agent-menu');
const chatAgentPicker = chatAgentBtn?.closest('.chat-agent-picker') || chatAgentMenu?.closest('.chat-agent-picker') || null;
const chatRuntimeState = $('#chat-runtime-state');
const chatCwd = $('#chat-cwd');
const userOutlineBtn = $('#user-outline-btn');
@@ -308,6 +310,14 @@
return AGENT_LABELS[agent] ? agent : DEFAULT_AGENT;
}
function normalizeUiAgent() {
return PRIMARY_UI_AGENT;
}
function isPrimaryUiAgent(agent) {
return normalizeAgent(agent) === PRIMARY_UI_AGENT;
}
function isCodexLikeAgent(agent) {
const normalized = normalizeAgent(agent);
return normalized === 'codex' || normalized === 'codexapp';
@@ -3750,7 +3760,7 @@
}
function getVisibleSessions() {
return sessions.filter((s) => normalizeAgent(s.agent) === currentAgent);
return sessions.filter((s) => isPrimaryUiAgent(s.agent));
}
function normalizeSessionSearchQuery(query) {
@@ -4123,9 +4133,11 @@
}
function updateAgentScopedUI() {
if (chatAgentPicker) chatAgentPicker.hidden = true;
if (chatAgentMenu) chatAgentMenu.hidden = true;
if (chatAgentBtn) {
chatAgentBtn.textContent = AGENT_LABELS[currentAgent];
chatAgentBtn.setAttribute('aria-expanded', chatAgentMenu && !chatAgentMenu.hidden ? 'true' : 'false');
chatAgentBtn.setAttribute('aria-expanded', 'false');
}
if (chatAgentMenu) {
chatAgentMenu.querySelectorAll('.chat-agent-option').forEach((btn) => {
@@ -4144,7 +4156,7 @@
}
function setCurrentAgent(agent) {
currentAgent = normalizeAgent(agent);
currentAgent = normalizeUiAgent(agent);
localStorage.setItem('cc-web-agent', currentAgent);
currentMode = localStorage.getItem(getAgentModeStorageKey(currentAgent)) || 'yolo';
modeSelect.value = currentMode;
@@ -4160,6 +4172,7 @@
function toggleAgentMenu() {
if (!chatAgentMenu || !chatAgentBtn) return;
if (chatAgentPicker?.hidden) return;
const willOpen = chatAgentMenu.hidden;
chatAgentMenu.hidden = !willOpen;
chatAgentBtn.setAttribute('aria-expanded', willOpen ? 'true' : 'false');
@@ -4199,8 +4212,13 @@
}
function applySessionSnapshot(snapshot, options = {}) {
if (!snapshot) return;
if (!snapshot) return false;
const snapshotAgent = normalizeAgent(snapshot.agent);
if (!isPrimaryUiAgent(snapshotAgent)) {
if (currentSessionId === snapshot.sessionId) resetChatView(PRIMARY_UI_AGENT);
renderSessionList();
return false;
}
if (fileBrowserState && (fileBrowserState.sessionId !== snapshot.sessionId || (snapshot.cwd && fileBrowserState.rootPath !== snapshot.cwd))) {
closeFileBrowser();
}
@@ -4260,10 +4278,11 @@
if (snapshot.hasUnread && !options.suppressUnreadToast) {
showToast('后台任务已完成', snapshot.sessionId);
}
return true;
}
function syncViewForAgent(agent, options = {}) {
const targetAgent = normalizeAgent(agent);
const targetAgent = normalizeUiAgent(agent);
const { preserveCurrent = true, loadLast = true } = options;
setCurrentAgent(targetAgent);
closeUserOutlinePanel();
@@ -4530,6 +4549,7 @@
function showCachedSession(sessionId) {
const snapshot = buildCachedSessionSnapshot(sessionId);
if (!snapshot) return false;
if (!isPrimaryUiAgent(snapshot.agent)) return false;
if (currentSessionId && currentSessionId !== sessionId) {
send({ type: 'detach_view' });
}
@@ -4537,12 +4557,17 @@
closeCcwebPromptOutlinePanel();
clearSessionLoading();
touchSessionCache(sessionId);
applySessionSnapshot(snapshot, { immediate: true, suppressUnreadToast: true });
return true;
return applySessionSnapshot(snapshot, { immediate: true, suppressUnreadToast: true });
}
function openSession(sessionId, options = {}) {
if (!sessionId) return;
const meta = getSessionMeta(sessionId);
const cachedAgent = sessionCache.get(sessionId)?.snapshot?.agent;
if ((meta && !isPrimaryUiAgent(meta.agent)) || (!meta && cachedAgent && !isPrimaryUiAgent(cachedAgent))) {
renderSessionList();
return;
}
closeUserOutlinePanel();
closeCcwebPromptOutlinePanel();
if (options.forceSync) {
@@ -5230,11 +5255,16 @@
pendingNewSessionRequest = null;
if (!matchesActiveLoad) clearSessionLoading();
}
applySessionSnapshot(snapshot, {
const appliedSnapshot = applySessionSnapshot(snapshot, {
immediate: isBlockingSessionLoad(msg.sessionId),
suppressUnreadToast: false,
preserveStreaming: msg.sessionId === currentSessionId && msg.isRunning,
});
if (!appliedSnapshot) {
if (!msg.historyPending) cacheSessionSnapshot(snapshot);
if (matchesActiveLoad) clearSessionLoading(msg.sessionId);
break;
}
if (msg.sessionId === currentSessionId) {
setCurrentSessionRunningState(!!msg.isRunning);
}
@@ -9348,6 +9378,7 @@
}
function showSettingsPanel() {
setCurrentAgent(PRIMARY_UI_AGENT);
if (isCodexLikeAgent(currentAgent)) {
showCodexSettingsPanel();
return;
@@ -9912,7 +9943,7 @@
function requestNewSession(options = {}) {
const cwd = options.cwd || null;
const rawCwd = options.rawCwd !== undefined ? options.rawCwd : (cwd || '');
const agent = normalizeAgent(options.agent || currentAgent);
const agent = normalizeUiAgent(options.agent || currentAgent);
const mode = ['default', 'plan', 'yolo'].includes(options.mode) ? options.mode : currentMode;
const model = typeof options.model === 'string' ? options.model.trim() : '';
const title = typeof options.title === 'string' ? options.title.trim() : '';
@@ -9945,8 +9976,7 @@
let _onCwdSuggestions = null;
function showNewSessionModal(options = {}) {
const targetAgent = normalizeAgent(options.agent || currentAgent);
const targetLabel = AGENT_LABELS[targetAgent] || AGENT_LABELS.claude;
const targetAgent = normalizeUiAgent(options.agent || currentAgent);
const recentCwds = getRecentCwds();
const requestedMode = ['default', 'plan', 'yolo'].includes(options.mode) ? options.mode : currentMode;
let suggestionsRequested = false;
@@ -9961,11 +9991,10 @@
overlay.innerHTML = `
<div class="modal-panel">
<div class="modal-header">
<span class="modal-title">新建 ${escapeHtml(targetLabel)} 会话</span>
<span class="modal-title">新建会话</span>
<button class="modal-close-btn" id="ns-close-btn">✕</button>
</div>
<div class="modal-body">
${buildAgentContextCard(targetAgent, `当前将在 ${targetLabel} 区创建会话`, `新会话会直接进入 ${targetLabel} 模块,并只出现在 ${targetLabel} 会话列表中。`)}
<div class="modal-stack">
<div>
<label class="modal-field-label" for="ns-cwd-input">工作目录</label>

View File

@@ -73,14 +73,6 @@
<button id="menu-btn" class="menu-btn" title="菜单"></button>
<span id="chat-title" class="chat-title">新会话</span>
<button id="chat-session-id-btn" class="chat-session-id-btn" type="button" title="复制当前会话 ID" hidden>ID</button>
<div class="chat-agent-picker">
<button id="chat-agent-btn" class="chat-agent-btn" type="button" aria-haspopup="menu" aria-expanded="false">Claude</button>
<div id="chat-agent-menu" class="chat-agent-menu" hidden>
<button type="button" class="chat-agent-option active" data-agent="claude">Claude</button>
<button type="button" class="chat-agent-option" data-agent="codex">Codex</button>
<button type="button" class="chat-agent-option" data-agent="codexapp">Codex App</button>
</div>
</div>
<button id="chat-cwd" class="chat-cwd" type="button" hidden></button>
</header>
@@ -173,6 +165,6 @@
<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/12.0.1/marked.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/mermaid/10.9.1/mermaid.min.js"></script>
<script src="app.js?v=20260702-visible-no-rerender"></script>
<script src="app.js?v=20260705-codexapp-ui-agent"></script>
</body>
</html>

View File

@@ -1628,7 +1628,7 @@ body.session-loading-active {
}
.chat-agent-picker {
position: relative;
display: inline-flex;
display: none;
flex-shrink: 0;
}
.chat-agent-btn {

View File

@@ -647,6 +647,50 @@ function assertFrontendMcpReloadContract() {
assert(source.includes('MCP 启动失败'), 'Frontend should expose a failed startup toast');
}
function assertFrontendPrimaryCodexAppUiContract() {
const source = fs.readFileSync(PUBLIC_APP_PATH, 'utf8');
const indexSource = fs.readFileSync(PUBLIC_INDEX_PATH, 'utf8');
const styleSource = fs.readFileSync(PUBLIC_STYLE_PATH, 'utf8');
const serverSource = fs.readFileSync(SERVER_PATH, 'utf8');
assert(!indexSource.includes('id="chat-agent-btn"'), 'Ordinary UI should not render an agent picker button');
assert(!indexSource.includes('id="chat-agent-menu"'), 'Ordinary UI should not render an agent picker menu');
assert(!indexSource.includes('class="chat-agent-picker"'), 'Ordinary UI should not render the agent picker container');
assert(
/\.chat-agent-picker\s*\{[\s\S]*?display:\s*none;/.test(styleSource),
'Stale cached agent picker markup should be hidden by CSS'
);
assert(source.includes("const PRIMARY_UI_AGENT = 'codexapp';"), 'Frontend should define Codex App as the primary UI agent');
assert(source.includes('const DEFAULT_AGENT = PRIMARY_UI_AGENT;'), 'Frontend default agent should point at the primary UI agent');
assert(source.includes('let currentAgent = DEFAULT_AGENT;'), 'Frontend should not initialize currentAgent from stale localStorage');
assert(source.includes("localStorage.setItem('cc-web-agent', currentAgent);"), 'Frontend should overwrite stale cc-web-agent storage with the primary UI agent');
assert(source.includes('currentAgent = normalizeUiAgent(agent);'), 'setCurrentAgent should coerce ordinary UI agent changes back to Codex App');
assert(source.includes('return sessions.filter((s) => isPrimaryUiAgent(s.agent));'), 'Session list should only expose Codex App sessions in ordinary UI');
assert(
/function applySessionSnapshot\(snapshot[\s\S]*?!isPrimaryUiAgent\(snapshotAgent\)[\s\S]*?return false;[\s\S]*?return true;/.test(source),
'Frontend should reject legacy Claude/Codex snapshots from the current view'
);
assert(source.includes('if (!isPrimaryUiAgent(snapshot.agent)) return false;'), 'Cached legacy sessions should not render in ordinary UI');
assert(source.includes('const agent = normalizeUiAgent(options.agent || currentAgent);'), 'Ordinary new-session UI should create Codex App sessions');
assert(source.includes('const targetAgent = normalizeUiAgent(options.agent || currentAgent);'), 'New-session modal should present the Codex App space');
assert(
/function showSettingsPanel\(\)[\s\S]*?setCurrentAgent\(PRIMARY_UI_AGENT\);[\s\S]*?showCodexSettingsPanel\(\);/.test(source),
'Settings button should route to existing Codex settings in ordinary UI'
);
assert(indexSource.includes('id="mode-select"'), 'Permission mode selector should remain visible');
assert(indexSource.includes('<option value="yolo">YOLO</option>'), 'YOLO permission mode should remain available');
assert(indexSource.includes('<option value="default">默认</option>'), 'Default permission mode should remain available');
assert(indexSource.includes('<option value="plan">Plan</option>'), 'Plan permission mode should remain available');
assert(indexSource.includes('Claude / Codex Web Chat'), 'Static login copy should keep existing Claude/Codex wording');
assert(indexSource.includes('开始与 Claude 对话'), 'Static welcome copy should keep existing Claude wording');
assert(
serverSource.includes("const VALID_AGENTS = new Set(['claude', 'codex', 'codexapp']);"),
'Server explicit agent support should remain available for API/MCP paths'
);
}
function assertSetTitleMcpContract() {
const serverSource = fs.readFileSync(SERVER_PATH, 'utf8');
const frontendSource = fs.readFileSync(PUBLIC_APP_PATH, 'utf8');
@@ -766,6 +810,7 @@ async function main() {
assertFrontendMarkdownLinkContract();
assertMockCodexAppPromptUserNotTextTriggered();
assertFrontendMcpReloadContract();
assertFrontendPrimaryCodexAppUiContract();
assertSetTitleMcpContract();
assertSessionSwitchResilienceContract();
@@ -1972,16 +2017,18 @@ async function main() {
msg.type === 'session_message' &&
msg.sessionId === codexAppSession.sessionId &&
msg.message?.role === 'user' &&
/我已回答 ccweb 提示的问题/.test(msg.message.content || '') &&
/表单答案/.test(msg.message.content || '') &&
/btnShortageReleaseConfirm/.test(msg.message.content || '')
));
assert(/使用 FineUI 弹窗,方便固定按钮 ID。/.test(promptAnswerMessage.message.content || ''), 'Prompt submission should become a normal user message with the free-form answer');
assert(!/我已回答 ccweb 提示的问题/.test(promptAnswerMessage.message.content || ''), 'Prompt submission should not use the old first-person hard-coded prefix');
assert(!/ccweb 提示表单答案/.test(promptAnswerMessage.message.content || ''), 'Prompt submission should not expose the internal ccweb product name in the user message prefix');
const promptAnswerDelta = await nextMessage(messages, ws, (msg) => (
msg.type === 'text_delta' &&
msg.sessionId === codexAppSession.sessionId &&
/btnShortageReleaseConfirm/.test(msg.text || '')
));
assert(/我已回答 ccweb 提示的问题/.test(promptAnswerDelta.text || ''), 'Prompt submission should trigger a Codex App turn with the answer text');
assert(/表单答案/.test(promptAnswerDelta.text || ''), 'Prompt submission should trigger a Codex App turn with the answer text');
await nextMessage(messages, ws, (msg) => msg.type === 'done' && msg.sessionId === codexAppSession.sessionId);
storedCodexApp = JSON.parse(fs.readFileSync(path.join(sessionsDir, `${codexAppSession.sessionId}.json`), 'utf8'));
const storedPromptMessage = storedCodexApp.messages.find((message) => message.ccwebPrompt?.id === promptUserResult.body.promptId);

View File

@@ -4708,7 +4708,7 @@ function normalizeCcwebPromptUserAnswers(prompt, rawAnswers = {}) {
}
function buildCcwebPromptUserResponseText(prompt, answerList) {
const lines = ['我已回答 ccweb 提示的问题'];
const lines = ['表单答案'];
if (prompt.title) {
lines.push('', `表单:${prompt.title}`);
}