feat: support codex app goal command
This commit is contained in:
103
public/app.js
103
public/app.js
@@ -2,7 +2,7 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
const ASSET_VERSION = '20260616-child-agent-close-state';
|
||||
const ASSET_VERSION = '20260617-codexapp-approval';
|
||||
const WS_URL = `${location.protocol === 'https:' ? 'wss' : 'ws'}://${location.host}/ws`;
|
||||
const RENDER_DEBOUNCE = 100;
|
||||
const COMPOSER_SUGGESTION_DEBOUNCE = 120;
|
||||
@@ -14,6 +14,7 @@
|
||||
{ cmd: '/mode', desc: '查看/切换权限模式' },
|
||||
{ cmd: '/cost', desc: '查看会话费用' },
|
||||
{ cmd: '/compact', desc: '压缩上下文' },
|
||||
{ cmd: '/goal', desc: '设置/查看 Codex App 持久目标' },
|
||||
{ cmd: '/init', desc: '生成/更新 Agent 指南文件' },
|
||||
{ cmd: '/help', desc: '显示帮助' },
|
||||
];
|
||||
@@ -158,6 +159,7 @@
|
||||
let fileBrowserState = null;
|
||||
let directoryPickerState = null;
|
||||
let codexAppUserInputModal = null;
|
||||
let codexAppApprovalModal = null;
|
||||
let pendingNewSessionRequest = null;
|
||||
let skipDeleteConfirm = localStorage.getItem('cc-web-skip-delete-confirm') === '1';
|
||||
let pendingInitialSessionLoad = false;
|
||||
@@ -1165,6 +1167,98 @@
|
||||
}
|
||||
}
|
||||
|
||||
function codexAppApprovalPayloadText(payload) {
|
||||
if (payload === null || payload === undefined) return '';
|
||||
if (typeof payload === 'string') return payload;
|
||||
try {
|
||||
return JSON.stringify(payload, null, 2);
|
||||
} catch {
|
||||
return String(payload);
|
||||
}
|
||||
}
|
||||
|
||||
function closeCodexAppApprovalModal(sendCancel = false) {
|
||||
if (!codexAppApprovalModal) return;
|
||||
const { overlay, escapeHandler, requestId, sessionId } = codexAppApprovalModal;
|
||||
if (escapeHandler) document.removeEventListener('keydown', escapeHandler);
|
||||
if (overlay && overlay.parentNode) overlay.parentNode.removeChild(overlay);
|
||||
codexAppApprovalModal = null;
|
||||
if (sendCancel && requestId) {
|
||||
send({
|
||||
type: 'codex_app_approval_response',
|
||||
action: 'cancel',
|
||||
sessionId,
|
||||
requestId,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function submitCodexAppApproval(action) {
|
||||
if (!codexAppApprovalModal) return;
|
||||
const { requestId, sessionId } = codexAppApprovalModal;
|
||||
send({
|
||||
type: 'codex_app_approval_response',
|
||||
action,
|
||||
sessionId,
|
||||
requestId,
|
||||
});
|
||||
closeCodexAppApprovalModal(false);
|
||||
}
|
||||
|
||||
function showCodexAppApprovalModal(msg) {
|
||||
closeCodexAppApprovalModal(true);
|
||||
const payloadText = codexAppApprovalPayloadText(msg.payload);
|
||||
const overlay = document.createElement('div');
|
||||
overlay.className = 'modal-overlay codex-approval-overlay';
|
||||
overlay.innerHTML = `
|
||||
<div class="modal-panel codex-approval-panel">
|
||||
<div class="modal-header">
|
||||
<span class="modal-title">${escapeHtml(msg.title || 'Codex App 请求审批')}</span>
|
||||
<button class="modal-close-btn" type="button" data-codex-approval-cancel>✕</button>
|
||||
</div>
|
||||
<div class="modal-body codex-approval-body">
|
||||
${msg.summary ? `<div class="codex-approval-summary">${escapeHtml(msg.summary)}</div>` : ''}
|
||||
${msg.reason ? `<div class="codex-approval-reason">${escapeHtml(msg.reason)}</div>` : ''}
|
||||
<div class="codex-approval-meta">
|
||||
<span>${escapeHtml(msg.approvalType || 'request')}</span>
|
||||
${msg.itemId ? `<span>${escapeHtml(msg.itemId)}</span>` : ''}
|
||||
</div>
|
||||
${payloadText ? `<pre class="codex-approval-payload">${escapeHtml(payloadText)}</pre>` : ''}
|
||||
</div>
|
||||
<div class="modal-footer codex-approval-footer">
|
||||
<button class="modal-btn-secondary" type="button" data-codex-approval-cancel>取消</button>
|
||||
<button class="modal-btn-secondary codex-approval-deny-btn" type="button" data-codex-approval-action="deny">拒绝</button>
|
||||
<button class="modal-btn-primary" type="button" data-codex-approval-action="approve">本次批准</button>
|
||||
${msg.allowSessionScope ? '<button class="modal-btn-primary" type="button" data-codex-approval-action="approve_session">本会话批准</button>' : ''}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
document.body.appendChild(overlay);
|
||||
|
||||
const escapeHandler = (e) => {
|
||||
if (e.key === 'Escape') closeCodexAppApprovalModal(true);
|
||||
};
|
||||
document.addEventListener('keydown', escapeHandler);
|
||||
|
||||
codexAppApprovalModal = {
|
||||
overlay,
|
||||
requestId: msg.requestId || '',
|
||||
sessionId: msg.sessionId || '',
|
||||
escapeHandler,
|
||||
};
|
||||
|
||||
overlay.querySelectorAll('[data-codex-approval-cancel]').forEach((button) => {
|
||||
button.addEventListener('click', () => closeCodexAppApprovalModal(true));
|
||||
});
|
||||
overlay.addEventListener('click', (e) => {
|
||||
if (e.target === overlay) closeCodexAppApprovalModal(true);
|
||||
});
|
||||
overlay.querySelectorAll('[data-codex-approval-action]').forEach((button) => {
|
||||
button.addEventListener('click', () => submitCodexAppApproval(button.dataset.codexApprovalAction || 'cancel'));
|
||||
});
|
||||
overlay.querySelector('[data-codex-approval-action="approve"]')?.focus();
|
||||
}
|
||||
|
||||
function cssEscape(value) {
|
||||
if (window.CSS && typeof window.CSS.escape === 'function') return window.CSS.escape(String(value || ''));
|
||||
return String(value || '').replace(/["\\]/g, '\\$&');
|
||||
@@ -3135,6 +3229,13 @@
|
||||
showCodexAppUserInputModal(msg);
|
||||
break;
|
||||
|
||||
case 'codex_app_approval_request':
|
||||
if (msg.sessionId && msg.sessionId !== currentSessionId) {
|
||||
showToast('Codex App 需要审批', msg.sessionId);
|
||||
}
|
||||
showCodexAppApprovalModal(msg);
|
||||
break;
|
||||
|
||||
case 'ccweb_mcp_child_agent_update':
|
||||
applyCcwebMcpChildAgentUpdate(msg);
|
||||
break;
|
||||
|
||||
@@ -4153,6 +4153,61 @@ html[data-theme='coolvibe'] .settings-back:hover {
|
||||
.codex-user-input-text:focus {
|
||||
border-color: var(--accent);
|
||||
}
|
||||
.codex-approval-panel {
|
||||
max-width: 560px;
|
||||
}
|
||||
.codex-approval-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
.codex-approval-summary {
|
||||
color: var(--text-primary);
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
line-height: 1.45;
|
||||
word-break: break-word;
|
||||
}
|
||||
.codex-approval-reason {
|
||||
color: var(--text-secondary);
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
word-break: break-word;
|
||||
}
|
||||
.codex-approval-meta {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
}
|
||||
.codex-approval-meta span {
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 999px;
|
||||
background: var(--bg-primary);
|
||||
color: var(--text-muted);
|
||||
padding: 3px 8px;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
}
|
||||
.codex-approval-payload {
|
||||
max-height: 260px;
|
||||
overflow: auto;
|
||||
margin: 0;
|
||||
padding: 12px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
background: var(--bg-primary);
|
||||
color: var(--text-primary);
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
.codex-approval-footer {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.codex-approval-deny-btn {
|
||||
color: var(--danger);
|
||||
}
|
||||
.modal-quick-picks {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
Reference in New Issue
Block a user