feat: improve goal mode and Codex App resilience

This commit is contained in:
shiyue
2026-08-18 21:27:58 +08:00
parent 389690af64
commit 8fe31e0a5e
27 changed files with 631 additions and 24 deletions

View File

@@ -819,6 +819,8 @@ const VALID_AGENTS = new Set(['claude', 'codex', 'codexapp']);
const VALID_PERMISSION_MODES = new Set(['default', 'plan', 'yolo']);
const VALID_TITLE_SOURCES = new Set(['derived', 'llm', 'manual', 'system']);
const MCP_CONVERSATION_TITLE_MAX_CHARS = 120;
const GOAL_DERIVED_TITLE_MAX_CHARS = 60;
const DEFAULT_CONVERSATION_TITLE_VALUES = new Set(['new chat', 'untitled']);
const MCP_PROMPT_TITLE_MAX_CHARS = 160;
const MCP_PROMPT_DESCRIPTION_MAX_CHARS = 2000;
const MCP_PROMPT_QUESTION_MAX_COUNT = 10;
@@ -8102,9 +8104,10 @@ function persistCodexAppGoalDisplayMessage(session, command, source = {}) {
},
};
session.messages.push(message);
const titleUpdate = applyDerivedGoalConversationTitle(session, command.objective);
session.updated = timestamp;
saveSession(session);
return { message, created: true, duplicate: false };
return { message, created: true, duplicate: false, titleUpdate };
}
async function ensureCodexAppGoalThread(session) {
@@ -8214,6 +8217,15 @@ async function handleCodexAppGoalSlashCommand(ws, text, session, source = {}) {
sessionId: session.id,
message: goalDisplayMessage,
});
if (persisted.titleUpdate?.changed) {
sendSessionEventToViewers(session.id, {
type: 'session_renamed',
sessionId: session.id,
title: session.title,
...publicTitleMetadata(session),
});
broadcastSessionList();
}
} catch (err) {
sendGoalSystemMessage(ws, `Goal failed: ${err?.message || err}`, { sessionId: session.id }, { preserveComposerDraft: true });
return;
@@ -8552,6 +8564,31 @@ function isTitleLockedByUser(session) {
return String(session?.titleSource || '').trim().toLowerCase() === 'manual';
}
function isDefaultConversationTitle(session) {
if (!session || isTitleLockedByUser(session)) return false;
const normalized = String(session.title || '').replace(/\s+/g, ' ').trim().toLowerCase();
return !normalized || DEFAULT_CONVERSATION_TITLE_VALUES.has(normalized);
}
function deriveGoalConversationTitle(objective) {
const normalized = String(objective || '').replace(/\s+/g, ' ').trim();
return [...normalized].slice(0, GOAL_DERIVED_TITLE_MAX_CHARS).join('');
}
function applyDerivedGoalConversationTitle(session, objective) {
if (!isDefaultConversationTitle(session)) {
return { changed: false, title: session?.title || 'Untitled' };
}
const title = deriveGoalConversationTitle(objective);
if (!title) return { changed: false, title: session?.title || 'Untitled' };
const previousTitle = session.title || 'Untitled';
const changed = previousTitle !== title
|| String(session.titleSource || '').trim().toLowerCase() !== 'derived';
session.title = title;
session.titleSource = 'derived';
return { changed, title, previousTitle };
}
function publicTitleMetadata(session) {
return {
titleSource: String(session?.titleSource || '').trim().toLowerCase() || null,