feat: improve goal mode and Codex App resilience
This commit is contained in:
39
server.js
39
server.js
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user