chore: rebuild CentOS7 release package
This commit is contained in:
Binary file not shown.
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -1628,7 +1628,7 @@ body.session-loading-active {
|
||||
}
|
||||
.chat-agent-picker {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
display: none;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.chat-agent-btn {
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user