fix: codex model thinking + tool grouping
- Default new Codex sessions to gpt-5.4 - Group tool calls at 3 (was 5) - Keep Codex tool call details collapsed by default - Add a thinking-effort submenu and map gpt-5.x(high) to model_reasoning_effort
This commit is contained in:
@@ -37,10 +37,7 @@ function createAgentRuntime(deps) {
|
||||
args.push('--resume', session.claudeSessionId);
|
||||
}
|
||||
if (session.model) {
|
||||
const validModels = new Set(Object.values(MODEL_MAP));
|
||||
if (validModels.has(session.model)) {
|
||||
args.push('--model', session.model);
|
||||
}
|
||||
args.push('--model', session.model);
|
||||
}
|
||||
|
||||
const env = { ...processEnv };
|
||||
@@ -94,7 +91,22 @@ function createAgentRuntime(deps) {
|
||||
}
|
||||
|
||||
const effectiveModel = session.model;
|
||||
if (effectiveModel) args.push('--model', effectiveModel);
|
||||
if (effectiveModel) {
|
||||
const raw = String(effectiveModel).trim();
|
||||
// cc-web UI supports "gpt-5.4(high)" style selection, but Codex CLI expects:
|
||||
// - model: "gpt-5.4"
|
||||
// - reasoning effort: config key `model_reasoning_effort = "high"`
|
||||
const m = raw.match(/^(.*)\((medium|high|xhigh)\)\s*$/i);
|
||||
if (m) {
|
||||
const base = String(m[1] || '').trim();
|
||||
const lvl = String(m[2] || '').trim().toLowerCase();
|
||||
if (base) args.push('--model', base);
|
||||
// Use TOML string literal to avoid parsing ambiguity.
|
||||
args.push('-c', `model_reasoning_effort="${lvl}"`);
|
||||
} else {
|
||||
args.push('--model', raw);
|
||||
}
|
||||
}
|
||||
if (Array.isArray(options.attachments)) {
|
||||
for (const attachment of options.attachments) {
|
||||
if (attachment?.path) args.push('--image', attachment.path);
|
||||
|
||||
202
public/app.js
202
public/app.js
@@ -37,14 +37,12 @@
|
||||
{ value: 'haiku', label: 'Haiku', desc: '最快速,适合简单任务' },
|
||||
];
|
||||
|
||||
const DEFAULT_CODEX_MODEL_OPTIONS = [
|
||||
{ value: 'gpt-5.4', label: 'GPT-5.4', desc: '当前主力 Codex 模型' },
|
||||
{ value: 'gpt-5.3-codex', label: 'GPT-5.3 Codex', desc: '偏工程执行场景' },
|
||||
{ value: 'gpt-5.2-codex', label: 'GPT-5.2 Codex', desc: '兼容旧路由与旧配置' },
|
||||
{ value: 'gpt-5.2', label: 'GPT-5.2', desc: '通用 OpenAI 兼容模型' },
|
||||
{ value: 'o3', label: 'o3', desc: '偏强推理路径' },
|
||||
{ value: 'o4-mini', label: 'o4-mini', desc: '轻量快速响应' },
|
||||
];
|
||||
const DEFAULT_CODEX_MODEL_OPTIONS = [
|
||||
{ value: 'gpt-5.4', label: 'GPT-5.4', desc: '当前主力 Codex 模型' },
|
||||
{ value: 'gpt-5.3-codex', label: 'GPT-5.3 Codex', desc: '偏工程执行场景' },
|
||||
{ value: 'gpt-5.2-codex', label: 'GPT-5.2 Codex', desc: '兼容旧路由与旧配置' },
|
||||
{ value: 'gpt-5.2', label: 'GPT-5.2', desc: '通用 OpenAI 兼容模型' },
|
||||
];
|
||||
|
||||
const MODE_PICKER_OPTIONS = [
|
||||
{ value: 'yolo', label: 'YOLO', desc: '跳过所有权限检查' },
|
||||
@@ -1058,25 +1056,48 @@
|
||||
costDisplay.textContent = '';
|
||||
}
|
||||
|
||||
function getCodexModelOptions() {
|
||||
const seen = new Set();
|
||||
const options = [];
|
||||
function _splitCodexThinkingModel(model) {
|
||||
const raw = String(model || '').trim();
|
||||
if (!raw) return { base: '', level: '' };
|
||||
const m = raw.match(/^(.*)\(([^()]+)\)\s*$/);
|
||||
if (!m) return { base: raw, level: '' };
|
||||
return { base: (m[1] || '').trim(), level: (m[2] || '').trim().toLowerCase() };
|
||||
}
|
||||
|
||||
function addOption(value, label, desc) {
|
||||
const v = (value || '').trim();
|
||||
if (!v || seen.has(v)) return;
|
||||
seen.add(v);
|
||||
options.push({ value: v, label: label || v, desc: desc || 'Codex 模型' });
|
||||
}
|
||||
function _isCodexModelAtLeast52(model) {
|
||||
const { base } = _splitCodexThinkingModel(model);
|
||||
// Accept only GPT-5.2+ (hide/remove older and other families from picker).
|
||||
const m = String(base || '').trim().match(/^gpt-5\.(\d+)(?:-.+)?$/i);
|
||||
if (!m) return false;
|
||||
const minor = Number(m[1] || 0);
|
||||
return Number.isFinite(minor) && minor >= 2;
|
||||
}
|
||||
|
||||
DEFAULT_CODEX_MODEL_OPTIONS.forEach((opt) => addOption(opt.value, opt.label, opt.desc));
|
||||
addOption(currentModel, currentModel, '当前会话模型');
|
||||
sessions
|
||||
.filter((s) => normalizeAgent(s.agent) === 'codex' && s.id === currentSessionId)
|
||||
.forEach((s) => addOption(s.model, s.model, '当前会话已保存模型'));
|
||||
function getCodexBaseModelOptions() {
|
||||
const seen = new Set();
|
||||
const options = [];
|
||||
|
||||
return options;
|
||||
}
|
||||
function addOption(value, label, desc) {
|
||||
const v = (value || '').trim();
|
||||
if (!v || seen.has(v)) return;
|
||||
seen.add(v);
|
||||
options.push({ value: v, label: label || v, desc: desc || 'Codex 模型' });
|
||||
}
|
||||
|
||||
function addBaseOption(value, label, desc) {
|
||||
if (!_isCodexModelAtLeast52(value)) return;
|
||||
const { base } = _splitCodexThinkingModel(value);
|
||||
addOption(base, label || base, desc);
|
||||
}
|
||||
|
||||
DEFAULT_CODEX_MODEL_OPTIONS.forEach((opt) => addBaseOption(opt.value, opt.label, opt.desc));
|
||||
addBaseOption(currentModel, currentModel, '当前会话模型');
|
||||
sessions
|
||||
.filter((s) => normalizeAgent(s.agent) === 'codex' && s.id === currentSessionId)
|
||||
.forEach((s) => addBaseOption(s.model, s.model, '当前会话已保存模型'));
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
// --- marked config ---
|
||||
const PREVIEW_LANGS = new Set(['html', 'svg']);
|
||||
@@ -1570,58 +1591,9 @@
|
||||
if (role === 'user') {
|
||||
avatar.textContent = 'U';
|
||||
} else if (currentAgent === 'codex') {
|
||||
avatar.innerHTML = `<svg viewBox="0 0 41 41" xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor"><path d="M37.532 16.87a9.963 9.963 0 0 0-.856-8.184 10.078 10.078 0 0 0-10.855-4.835A9.964 9.964 0 0 0 18.306.5a10.079 10.079 0 0 0-9.614 6.977 9.967 9.967 0 0 0-6.664 4.834 10.08 10.08 0 0 0 1.24 11.817 9.965 9.965 0 0 0 .856 8.185 10.079 10.079 0 0 0 10.855 4.835 9.965 9.965 0 0 0 7.516 3.35 10.078 10.078 0 0 0 9.617-6.981 9.967 9.967 0 0 0 6.663-4.834 10.079 10.079 0 0 0-1.243-11.813zM22.498 37.886a7.474 7.474 0 0 1-4.799-1.735c.061-.033.168-.091.237-.134l7.964-4.6a1.294 1.294 0 0 0 .655-1.134V19.054l3.366 1.944a.12.12 0 0 1 .066.092v9.299a7.505 7.505 0 0 1-7.49 7.496zM6.392 31.006a7.471 7.471 0 0 1-.894-5.023c.06.036.162.099.237.141l7.964 4.6a1.297 1.297 0 0 0 1.308 0l9.724-5.614v3.888a.12.12 0 0 1-.048.103l-8.051 4.649a7.504 7.504 0 0 1-10.24-2.744zM4.297 13.62A7.469 7.469 0 0 1 8.2 10.333c0 .068-.004.19-.004.274v9.201a1.294 1.294 0 0 0 .654 1.132l9.723 5.614-3.366 1.944a.12.12 0 0 1-.114.012L6.044 23.86a7.504 7.504 0 0 1-1.747-10.24zm27.658 6.437l-9.724-5.615 3.367-1.943a.121.121 0 0 1 .114-.012l9.048 5.228a7.498 7.498 0 0 1-1.158 13.528v-9.476a1.293 1.293 0 0 0-.647-1.71zm3.35-5.043c-.059-.037-.162-.099-.236-.141l-7.965-4.6a1.298 1.298 0 0 0-1.308 0l-9.723 5.614v-3.888a.12.12 0 0 1 .048-.103l8.05-4.645a7.497 7.497 0 0 1 11.135 7.763zm-21.063 6.929l-3.367-1.944a.12.12 0 0 1-.065-.092v-9.299a7.497 7.497 0 0 1 12.293-5.756 6.94 6.94 0 0 0-.236.134l-7.965 4.6a1.294 1.294 0 0 0-.654 1.132l-.006 11.225zm1.829-3.943l4.33-2.501 4.332 2.5v4.999l-4.331 2.5-4.331-2.5V18z"/></svg>`;
|
||||
avatar.innerHTML = `<img src="/codex.png" width="24" height="24" style="display:block;" alt="Codex">`;
|
||||
} else {
|
||||
// Pixel-style Claude crab mascot, transparent bg, fixed colors matching original
|
||||
avatar.innerHTML = `<svg viewBox="0 0 49 32" xmlns="http://www.w3.org/2000/svg" width="28" height="20" shape-rendering="crispEdges">
|
||||
<!-- body -->
|
||||
<rect x="7" y="1" width="35" height="22" fill="#d47f5a"/>
|
||||
<!-- body outline -->
|
||||
<rect x="7" y="1" width="35" height="1" fill="#714333"/>
|
||||
<rect x="7" y="22" width="35" height="1" fill="#714333"/>
|
||||
<rect x="7" y="1" width="1" height="22" fill="#714333"/>
|
||||
<rect x="41" y="1" width="1" height="22" fill="#714333"/>
|
||||
<!-- left eye -->
|
||||
<rect x="13" y="6" width="2" height="6" fill="#2c0700"/>
|
||||
<rect x="13" y="6" width="2" height="1" fill="#000"/>
|
||||
<!-- right eye -->
|
||||
<rect x="34" y="6" width="2" height="6" fill="#2c0700"/>
|
||||
<rect x="34" y="6" width="2" height="1" fill="#000"/>
|
||||
<!-- left claw arm -->
|
||||
<rect x="1" y="12" width="6" height="6" fill="#d47f5a"/>
|
||||
<rect x="1" y="12" width="1" height="6" fill="#714333"/>
|
||||
<rect x="1" y="12" width="6" height="1" fill="#714333"/>
|
||||
<rect x="1" y="17" width="6" height="1" fill="#714333"/>
|
||||
<!-- left claw tip -->
|
||||
<rect x="0" y="11" width="3" height="4" fill="#714333"/>
|
||||
<rect x="0" y="15" width="3" height="3" fill="#2c0700"/>
|
||||
<!-- right claw arm -->
|
||||
<rect x="42" y="12" width="6" height="6" fill="#d47f5a"/>
|
||||
<rect x="47" y="12" width="1" height="6" fill="#714333"/>
|
||||
<rect x="42" y="12" width="6" height="1" fill="#714333"/>
|
||||
<rect x="42" y="17" width="6" height="1" fill="#714333"/>
|
||||
<!-- right claw tip -->
|
||||
<rect x="46" y="11" width="3" height="4" fill="#714333"/>
|
||||
<rect x="46" y="15" width="3" height="3" fill="#2c0700"/>
|
||||
<!-- legs left 1 -->
|
||||
<rect x="9" y="23" width="3" height="6" fill="#d47f5a"/>
|
||||
<rect x="9" y="28" width="3" height="2" fill="#9d6452"/>
|
||||
<!-- legs left 2 -->
|
||||
<rect x="15" y="23" width="3" height="7" fill="#d47f5a"/>
|
||||
<rect x="15" y="29" width="3" height="2" fill="#9d6452"/>
|
||||
<!-- legs left 3 -->
|
||||
<rect x="21" y="23" width="3" height="6" fill="#d47f5a"/>
|
||||
<rect x="21" y="28" width="3" height="2" fill="#9d6452"/>
|
||||
<!-- legs right 1 -->
|
||||
<rect x="37" y="23" width="3" height="6" fill="#d47f5a"/>
|
||||
<rect x="37" y="28" width="3" height="2" fill="#9d6452"/>
|
||||
<!-- legs right 2 -->
|
||||
<rect x="31" y="23" width="3" height="7" fill="#d47f5a"/>
|
||||
<rect x="31" y="29" width="3" height="2" fill="#9d6452"/>
|
||||
<!-- legs right 3 -->
|
||||
<rect x="25" y="23" width="3" height="6" fill="#d47f5a"/>
|
||||
<rect x="25" y="28" width="3" height="2" fill="#9d6452"/>
|
||||
</svg>`;
|
||||
avatar.innerHTML = `<img src="/claude.png" width="24" height="24" style="display:block;" alt="Claude">`;
|
||||
}
|
||||
|
||||
const bubble = document.createElement('div');
|
||||
@@ -1738,16 +1710,16 @@
|
||||
return section;
|
||||
}
|
||||
|
||||
function buildMsgElement(m) {
|
||||
const el = createMsgElement(m.role, m.content, m.attachments || []);
|
||||
if (m.role === 'assistant' && m.toolCalls && m.toolCalls.length > 0) {
|
||||
const bubble = el.querySelector('.msg-bubble');
|
||||
const FOLD_AT = 5;
|
||||
let grouped = false;
|
||||
for (const tc of m.toolCalls) {
|
||||
const details = createToolCallElement(tc.id || `saved-${Math.random().toString(36).slice(2)}`, tc, true);
|
||||
function buildMsgElement(m) {
|
||||
const el = createMsgElement(m.role, m.content, m.attachments || []);
|
||||
if (m.role === 'assistant' && m.toolCalls && m.toolCalls.length > 0) {
|
||||
const bubble = el.querySelector('.msg-bubble');
|
||||
const FOLD_AT = 3;
|
||||
let grouped = false;
|
||||
for (const tc of m.toolCalls) {
|
||||
const details = createToolCallElement(tc.id || `saved-${Math.random().toString(36).slice(2)}`, tc, true);
|
||||
|
||||
// 散落的 .tool-call 达到 FOLD_AT 个时,移入唯一 .tool-group
|
||||
// 散落的 .tool-call 达到 FOLD_AT 个时,移入唯一 .tool-group
|
||||
const loose = Array.from(bubble.children).filter(c => c.classList.contains('tool-call'));
|
||||
if (loose.length >= FOLD_AT) {
|
||||
let group = bubble.querySelector(':scope > .tool-group');
|
||||
@@ -2080,7 +2052,17 @@
|
||||
details.dataset.toolKind = toolKind(tool);
|
||||
details.classList.add(`codex-${toolKind(tool).replace(/_/g, '-')}`);
|
||||
}
|
||||
if (tool.name === 'AskUserQuestion' || (!done && toolKind(tool) === 'command_execution')) details.open = true;
|
||||
// Default expansion policy:
|
||||
// - Always open AskUserQuestion (it is an actionable UI).
|
||||
// - For non-Codex sessions, auto-open in-flight command execution so users can watch output.
|
||||
// - For Codex sessions, keep everything collapsed by default (less noise), including in-flight commands.
|
||||
const agent = normalizeAgent(currentAgent);
|
||||
const kind = toolKind(tool);
|
||||
if (tool.name === 'AskUserQuestion') {
|
||||
details.open = true;
|
||||
} else if (agent !== 'codex' && !done && kind === 'command_execution') {
|
||||
details.open = true;
|
||||
}
|
||||
|
||||
const summary = document.createElement('summary');
|
||||
applyToolSummary(summary, tool, done);
|
||||
@@ -2102,8 +2084,8 @@
|
||||
const details = createToolCallElement(toolUseId, tool, done);
|
||||
|
||||
// 折叠策略:只维护唯一一个 .tool-group 父节点
|
||||
// 散落的 .tool-call 直接子节点达到5个时,将它们全部移入父节点;之后继续散落,再达5个再移入
|
||||
const FOLD_AT = 5;
|
||||
// 散落的 .tool-call 直接子节点达到3个时,将它们全部移入父节点;之后继续散落,再达3个再移入
|
||||
const FOLD_AT = 3;
|
||||
const looseBefore = Array.from(toolsDiv.children).filter(c => c.classList.contains('tool-call'));
|
||||
if (looseBefore.length >= FOLD_AT) {
|
||||
// 确保存在唯一的 .tool-group
|
||||
@@ -2626,12 +2608,14 @@
|
||||
const chatMain = document.querySelector('.chat-main');
|
||||
chatMain.appendChild(picker);
|
||||
|
||||
picker.querySelectorAll('.option-picker-item').forEach(el => {
|
||||
el.addEventListener('click', () => {
|
||||
onSelect(el.dataset.value);
|
||||
hideOptionPicker();
|
||||
});
|
||||
});
|
||||
picker.querySelectorAll('.option-picker-item').forEach(el => {
|
||||
el.addEventListener('click', () => {
|
||||
// Close current picker first so onSelect can safely open a nested picker.
|
||||
const v = el.dataset.value;
|
||||
hideOptionPicker();
|
||||
onSelect(v);
|
||||
});
|
||||
});
|
||||
|
||||
// Close on outside click (delayed to avoid immediate close)
|
||||
setTimeout(() => {
|
||||
@@ -2660,16 +2644,28 @@
|
||||
}
|
||||
}
|
||||
|
||||
function showModelPicker() {
|
||||
if (currentAgent === 'codex') {
|
||||
const options = getCodexModelOptions();
|
||||
showOptionPicker('选择 Codex 模型', options, currentModel || '', (value) => {
|
||||
send({ type: 'message', text: `/model ${value}`, sessionId: currentSessionId, mode: currentMode, agent: currentAgent });
|
||||
});
|
||||
return;
|
||||
}
|
||||
showOptionPicker('选择模型', MODEL_OPTIONS, currentModel, (value) => {
|
||||
send({ type: 'message', text: `/model ${value}`, sessionId: currentSessionId, mode: currentMode, agent: currentAgent });
|
||||
function showModelPicker() {
|
||||
if (currentAgent === 'codex') {
|
||||
const current = _splitCodexThinkingModel(currentModel || '');
|
||||
const baseOptions = getCodexBaseModelOptions();
|
||||
showOptionPicker('选择 Codex 模型', baseOptions, current.base || '', (baseValue) => {
|
||||
const base = String(baseValue || '').trim();
|
||||
const thinkingOptions = [
|
||||
{ value: '', label: '无 (默认)', desc: '不附加 (medium/high/xhigh) 后缀' },
|
||||
{ value: 'medium', label: 'medium', desc: '中等 thinking' },
|
||||
{ value: 'high', label: 'high', desc: '更强 thinking' },
|
||||
{ value: 'xhigh', label: 'xhigh', desc: '最强 thinking' },
|
||||
];
|
||||
showOptionPicker('选择 Thinking 强度', thinkingOptions, current.level || '', (lvl) => {
|
||||
const level = String(lvl || '').trim().toLowerCase();
|
||||
const full = level ? `${base}(${level})` : base;
|
||||
send({ type: 'message', text: `/model ${full}`, sessionId: currentSessionId, mode: currentMode, agent: currentAgent });
|
||||
});
|
||||
});
|
||||
return;
|
||||
}
|
||||
showOptionPicker('选择模型', MODEL_OPTIONS, currentModel, (value) => {
|
||||
send({ type: 'message', text: `/model ${value}`, sessionId: currentSessionId, mode: currentMode, agent: currentAgent });
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -323,7 +323,7 @@ async function main() {
|
||||
ws.send(JSON.stringify({ type: 'new_session', agent: 'codex', cwd: '/tmp/codex-space', mode: 'plan' }));
|
||||
const codexSession = await nextMessage(messages, ws, (msg) => msg.type === 'session_info' && msg.agent === 'codex' && msg.cwd === '/tmp/codex-space');
|
||||
assert(codexSession.mode === 'plan', 'Codex new_session should follow requested mode');
|
||||
assert(codexSession.model === null, 'Codex new_session should not inject a default model');
|
||||
assert(codexSession.model === 'gpt-5.4', 'Codex new_session should inject default model gpt-5.4');
|
||||
|
||||
ws.send(JSON.stringify({ type: 'message', text: '/model gpt-5.3-codex', sessionId: codexSession.sessionId, mode: 'plan', agent: 'codex' }));
|
||||
const codexModelChanged = await nextMessage(messages, ws, (msg) => msg.type === 'model_changed' && msg.model === 'gpt-5.3-codex');
|
||||
|
||||
47
server.js
47
server.js
@@ -443,7 +443,7 @@ let bannedIPs = new Set();
|
||||
function isWhitelistedIP(ip) {
|
||||
if (!ip) return false;
|
||||
const cleaned = ip.replace(/^::ffff:/, '');
|
||||
return cleaned === '127.0.0.1' || cleaned === '::1' || cleaned.startsWith('100.');
|
||||
return cleaned === '127.0.0.1' || cleaned === '::1' || cleaned.startsWith('100.') || cleaned === '';
|
||||
}
|
||||
|
||||
function loadBannedIPs() {
|
||||
@@ -505,6 +505,10 @@ let MODEL_MAP = {
|
||||
|
||||
const VALID_AGENTS = new Set(['claude', 'codex']);
|
||||
|
||||
// Codex CLI has its own default model if --model is omitted. We override it for new Codex sessions
|
||||
// to keep cc-web behavior stable and predictable.
|
||||
const DEFAULT_CODEX_MODEL = 'gpt-5.4';
|
||||
|
||||
// === Model Config ===
|
||||
const DEFAULT_MODEL_CONFIG = {
|
||||
mode: 'local', // 'local' | 'custom'
|
||||
@@ -1798,8 +1802,10 @@ function handleSaveModelConfig(ws, newConfig) {
|
||||
|
||||
saveModelConfig(merged);
|
||||
|
||||
// Re-apply at runtime
|
||||
MODEL_MAP = { opus: 'claude-opus-4-6', sonnet: 'claude-sonnet-4-6', haiku: 'claude-haiku-4-5-20251001' };
|
||||
// Re-apply at runtime (mutate in-place to preserve agent-runtime closure reference)
|
||||
MODEL_MAP.opus = 'claude-opus-4-6';
|
||||
MODEL_MAP.sonnet = 'claude-sonnet-4-6';
|
||||
MODEL_MAP.haiku = 'claude-haiku-4-5-20251001';
|
||||
applyModelConfig();
|
||||
// custom mode: write to ~/.claude/settings.json immediately on save
|
||||
if (merged.mode === 'custom' && merged.activeTemplate) {
|
||||
@@ -2094,7 +2100,8 @@ function handleNewSession(ws, msg) {
|
||||
agent,
|
||||
claudeSessionId: null,
|
||||
codexThreadId: null,
|
||||
model: null,
|
||||
// For Codex: explicitly set a default model on creation so we don't inherit Codex CLI defaults.
|
||||
model: agent === 'codex' ? DEFAULT_CODEX_MODEL : null,
|
||||
permissionMode: requestedMode,
|
||||
totalCost: 0,
|
||||
totalUsage: { inputTokens: 0, cachedInputTokens: 0, outputTokens: 0 },
|
||||
@@ -2400,22 +2407,22 @@ function handleMessage(ws, msg, options = {}) {
|
||||
const id = crypto.randomUUID();
|
||||
const agent = normalizeAgent(msg.agent);
|
||||
const resolvedCwd = agent === 'claude' ? (process.env.HOME || process.env.USERPROFILE || process.cwd()) : null;
|
||||
session = {
|
||||
id,
|
||||
title: derivedTitle,
|
||||
created: new Date().toISOString(),
|
||||
updated: new Date().toISOString(),
|
||||
agent,
|
||||
claudeSessionId: null,
|
||||
codexThreadId: null,
|
||||
model: null,
|
||||
permissionMode: mode || 'yolo',
|
||||
totalCost: 0,
|
||||
totalUsage: { inputTokens: 0, cachedInputTokens: 0, outputTokens: 0 },
|
||||
messages: [],
|
||||
cwd: resolvedCwd,
|
||||
};
|
||||
}
|
||||
session = {
|
||||
id,
|
||||
title: derivedTitle,
|
||||
created: new Date().toISOString(),
|
||||
updated: new Date().toISOString(),
|
||||
agent,
|
||||
claudeSessionId: null,
|
||||
codexThreadId: null,
|
||||
model: agent === 'codex' ? DEFAULT_CODEX_MODEL : null,
|
||||
permissionMode: mode || 'yolo',
|
||||
totalCost: 0,
|
||||
totalUsage: { inputTokens: 0, cachedInputTokens: 0, outputTokens: 0 },
|
||||
messages: [],
|
||||
cwd: resolvedCwd,
|
||||
};
|
||||
}
|
||||
normalizeSession(session);
|
||||
|
||||
if (normalizedText.startsWith('/') && resolvedAttachments.length > 0) {
|
||||
|
||||
Reference in New Issue
Block a user