From fe1ba781d1055f60d1a6b3e5ce952093eee98ea4 Mon Sep 17 00:00:00 2001 From: HouYunFei <1844025705@qq.com> Date: Wed, 29 Jul 2026 16:50:51 +0800 Subject: [PATCH] feat(agent): standardize approval policy and sandbox naming conventions in Codex settings --- CHANGELOG.md | 1 + canvas-agent/src/agent/codex-client.ts | 4 +- canvas-agent/src/agent/codex-protocol.ts | 6 +- docs/content/docs/progress/pending-test.mdx | 3 +- web/src/styles/globals.css | 261 +++++++++++--------- 5 files changed, 149 insertions(+), 126 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 787718f..2f00813 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unreleased ++ [新增] 画布 Agent 支持请求批准、自动审查和完全访问三档 Codex 权限,并可在对话中审批文件、命令与网络访问。 + [优化] Agent Markdown 代码块与外链确认弹窗改为紧凑中文样式,改善右侧面板阅读体验。 + [优化] Agent 思考摘要支持 Markdown 渲染,改善强调、列表和代码内容的阅读效果。 + [修复] Agent 对话启用 Codex 可读思考摘要,并修复一轮结束或刷新恢复后思考摘要、命令和工具过程记录缺失的问题。 diff --git a/canvas-agent/src/agent/codex-client.ts b/canvas-agent/src/agent/codex-client.ts index 1f12356..e8382ec 100644 --- a/canvas-agent/src/agent/codex-client.ts +++ b/canvas-agent/src/agent/codex-client.ts @@ -346,12 +346,12 @@ function codexConfig(permissionMode: AgentPermissionMode) { } function threadSettings(permissionMode: AgentPermissionMode) { - return { approvalPolicy: permissionMode === "full" ? "never" as const : "onRequest" as const, sandbox: permissionMode === "full" ? "dangerFullAccess" as const : "workspaceWrite" as const, config: codexConfig(permissionMode) }; + return { approvalPolicy: permissionMode === "full" ? "never" as const : "on-request" as const, sandbox: permissionMode === "full" ? "danger-full-access" as const : "workspace-write" as const, config: codexConfig(permissionMode) }; } function turnSettings(permissionMode: AgentPermissionMode) { return { - approvalPolicy: permissionMode === "full" ? "never" as const : "onRequest" as const, + approvalPolicy: permissionMode === "full" ? "never" as const : "on-request" as const, sandboxPolicy: permissionMode === "full" ? { type: "dangerFullAccess" as const } : { type: "workspaceWrite" as const, networkAccess: false }, }; } diff --git a/canvas-agent/src/agent/codex-protocol.ts b/canvas-agent/src/agent/codex-protocol.ts index 1c235ae..b40e61b 100644 --- a/canvas-agent/src/agent/codex-protocol.ts +++ b/canvas-agent/src/agent/codex-protocol.ts @@ -12,8 +12,8 @@ export type CodexTurnInput = | { type: "localImage"; path: string }; type ThreadOptions = { - approvalPolicy: "never" | "onRequest"; - sandbox: "workspaceWrite" | "dangerFullAccess"; + approvalPolicy: "never" | "on-request"; + sandbox: "workspace-write" | "danger-full-access"; config: JsonRecord; cwd?: string; }; @@ -54,7 +54,7 @@ type CodexRequestSpec = { result: Record; }; "turn/start": { - params: { threadId: string; input: CodexTurnInput[]; approvalPolicy: "never" | "onRequest"; sandboxPolicy: { type: "workspaceWrite"; networkAccess: boolean } | { type: "dangerFullAccess" } }; + params: { threadId: string; input: CodexTurnInput[]; approvalPolicy: "never" | "on-request"; sandboxPolicy: { type: "workspaceWrite"; networkAccess: boolean } | { type: "dangerFullAccess" } }; result: { turn: CodexTurn }; }; "turn/interrupt": { diff --git a/docs/content/docs/progress/pending-test.mdx b/docs/content/docs/progress/pending-test.mdx index 123b9e8..12a6fc4 100644 --- a/docs/content/docs/progress/pending-test.mdx +++ b/docs/content/docs/progress/pending-test.mdx @@ -5,7 +5,7 @@ description: 当前版本已实现但仍需人工验证的变更项 # 待测试 -- Agent Markdown 样式:右侧 Agent 回复中的代码块应使用紧凑细边框样式,不再出现大面积空白,只保留复制操作;行内代码、链接在浅色和深色主题下应清晰;点击外部链接后应显示中文紧凑确认弹窗,长路径能够正常换行且复制、继续打开和关闭操作可用。 +- Agent Markdown 样式:右侧 Agent 回复中的代码块应为横向占满消息区域、无语言标题和无双层边框的紧凑代码条,单行内容不应保留纵向大面积空白,复制操作仅在悬停时弱化显示;行内代码、链接在浅色和深色主题下应清晰;点击外部链接后应显示中文紧凑确认弹窗,长路径能够正常换行且复制、继续打开和关闭操作可用。 - Agent 工具确认模式:右侧面板标题栏不应再显示「工具确认」开关;对话输入框左下方应显示确认模式选择,默认选中「自动确认」,画布写入工具应直接执行;切换为「手动确认」后,画布写入工具应在对话中展示等待确认卡片,并支持批准或拒绝。 - Canvas Agent Codex 升级:启动 Canvas Agent 后应能正常连接 Codex、创建或恢复会话、发送消息并调用画布工具,实际运行的 Codex CLI 版本应为 0.145.0;运行中停止任务应只中断当前 turn,随后无需重启 Agent 即可继续发送新任务。 - Canvas Agent Debug:使用 `npx -y @basketikun/canvas-agent --debug` 启动后,终端应按“时间 级别 消息 详情”的纯文本单行格式输出日志并显示日志文件路径,`~/.infinite-canvas/logs/` 下应按启动日期生成相同格式的 `canvas-agent-YYYY-MM-DD.log`,同一天多次启动应追加到同一文件;普通启动保持原有简洁输出,日志中不应出现连接 token 或图片 Data URL 原文。 @@ -14,6 +14,7 @@ description: 当前版本已实现但仍需人工验证的变更项 - Agent 回复实时显示:在右侧 Agent 发送消息后,用户消息下方应立即出现“正在思考...”,任务运行期间不应闪退;工具完成后应显示 Codex 正在继续处理及已等待时长,等待超过 30 秒时提示可继续等待或停止本轮;Codex 的回复应在当前对话中持续显示,实时事件缺失时也应在任务完成后自动同步完整内容,无需切换到历史或日志再返回对话。模型繁忙等任务失败时应立即结束等待状态,在对话中显示中文错误原因和重试建议,诊断日志不应再把失败轮次记为“处理完成”,刷新历史后错误仍应保留。新建尚未发送首条消息的空会话不应反复出现历史读取失败。 - Agent 流式交互性能:发送长回复时文字应连续平滑出现,输入框、滚动和画布操作不应随回复变长而明显卡顿;长历史会话中只有当前流式消息持续更新,屏幕外消息不应造成明显布局压力;任务完成后应通过 SSE 自动同步完整历史,不再持续请求 `/health` 轮询状态。 - Agent 过程时间线:新建 Agent 对话后 Codex 应生成可读思考摘要,并在运行时依次显示中文的思考摘要、执行计划、命令执行、网页搜索、文件修改和画布工具活动;思考摘要的图标、标题和箭头应稳定保持在同一行,默认收起且无边框,点击箭头后才展开 Codex 实际返回的具体摘要,其中 Markdown 强调、列表和代码应正确渲染而非显示原始符号,完成事件不应再用「已完成分析」覆盖已经收到的摘要;命令执行也应使用无边框紧凑行,显示「已执行 1 条命令」和单行命令预览,点击后再展开工作目录、耗时、退出状态和运行输出;其他工具调用继续使用紧凑卡片排版,状态图标、标题和状态文字保持在同一行;结构化任务进度不应混在对话时间线中,而应独立放在对话区下方、Token 统计上方,支持展开和折叠,并逐项实时更新「待处理」「进行中」「已完成」状态;新任务生成时默认展开,任务结束后保留最新结果,同一计划更新不应生成重复内容;文件详情展示文件路径与新增/修改/删除动作,工具详情不应出现请求 ID、英文工具名或原始 JSON;一轮对话结束自动同步历史以及刷新页面后从历史中 resume 对话时,思考摘要、命令和工具过程记录仍应完整保留并保持相同展示。 +- Agent 权限控制:输入框可选择「请求批准」「自动审查」「完全访问」;请求批准模式下,Codex 编辑工作区外文件、执行受限命令或访问网络时应在对话中显示审批卡片,支持拒绝、允许一次和本会话允许,并能依次处理多个并发请求;自动审查模式应仅将需要用户决定的风险操作送入审批;完全访问必须先显示风险确认,启用后可访问网络和本机文件且不再请求审批;选择应在刷新后保留。 - Agent 历史记录:点击记录卡片应直接进入对应对话,不再显示「进入」按钮;可勾选单条或全选多条记录并批量删除,删除当前对话后聊天内容应清空。 - Agent 默认新对话:每次进入任一画布并连接 Agent 后,对话区应保持空白且不自动恢复上一次会话;第一次发送消息时才创建新线程,不应产生未发送消息的空历史记录;需要继续旧对话时可在「历史」中主动选择恢复。 - Agent 当前画布优先:在已打开某个画布时要求 Agent 创建、修改、整理或生成内容,Agent 应直接读取并操作当前画布,不应先调用 `canvas_list_projects` 或使用 `site_navigate` 重复进入画布;只有明确要求查看或切换其他画布时才允许查询画布列表并导航。 diff --git a/web/src/styles/globals.css b/web/src/styles/globals.css index 18c0b3b..e414bac 100644 --- a/web/src/styles/globals.css +++ b/web/src/styles/globals.css @@ -472,124 +472,145 @@ mix-blend-mode: normal; } -@layer components { - .agent-streamdown { - overflow-wrap: anywhere; - } - - .agent-streamdown > :first-child { - margin-top: 0; - } - - .agent-streamdown > :last-child { - margin-bottom: 0; - } - - .agent-streamdown [data-streamdown="code-block"] { - margin-block: 0.75rem; - overflow: hidden; - border-color: color-mix(in srgb, currentColor 13%, transparent); - border-radius: 0.75rem; - background: color-mix(in srgb, currentColor 3%, transparent); - } - - .agent-streamdown [data-streamdown="code-block-header"] { - min-height: 0; - padding: 0.45rem 0.75rem; - border-color: color-mix(in srgb, currentColor 9%, transparent); - background: transparent; - font-size: 0.625rem; - line-height: 1rem; - letter-spacing: 0.08em; - text-transform: uppercase; - opacity: 0.52; - } - - .agent-streamdown [data-streamdown="code-block-body"] { - padding: 0.8rem 0.9rem; - background: transparent !important; - font-size: 0.75rem; - line-height: 1.25rem; - } - - .agent-streamdown [data-streamdown="code-block-actions"] { - gap: 0; - margin: 0.35rem; - padding: 0; - border: 0; - border-radius: 0.375rem; - background: color-mix(in srgb, var(--background) 88%, transparent); - box-shadow: none; - } - - .agent-streamdown [data-streamdown="code-block-actions"] button { - padding: 0.35rem; - border-radius: 0.375rem; - } - - .agent-streamdown [data-streamdown="inline-code"] { - padding: 0.12rem 0.35rem; - border: 0; - border-radius: 0.3rem; - background: color-mix(in srgb, currentColor 7%, transparent); - font-size: 0.88em; - } - - .agent-streamdown [data-streamdown="link"] { - color: inherit; - text-decoration-color: color-mix(in srgb, currentColor 35%, transparent); - text-underline-offset: 0.2em; - } - - [data-streamdown="link-safety-modal"] { - padding: 1rem; - background: rgb(28 25 23 / 24%); - backdrop-filter: blur(8px); - } - - [data-streamdown="link-safety-modal"] > div { - max-width: 24rem; - gap: 1rem; - padding: 1.25rem; - border-color: color-mix(in srgb, currentColor 12%, transparent); - border-radius: 1rem; - box-shadow: 0 20px 60px rgb(28 25 23 / 18%); - } - - [data-streamdown="link-safety-modal"] > div > button { - top: 0.9rem; - right: 0.9rem; - } - - [data-streamdown="link-safety-modal"] > div > div:first-of-type { - gap: 0.25rem; - padding-right: 2rem; - } - - [data-streamdown="link-safety-modal"] > div > div:first-of-type > div { - gap: 0.5rem; - font-size: 1rem; - } - - [data-streamdown="link-safety-modal"] > div > div:nth-of-type(2) { - max-height: 7rem; - padding: 0.75rem; - border: 1px solid color-mix(in srgb, currentColor 9%, transparent); - background: color-mix(in srgb, currentColor 4%, transparent); - font-size: 0.7rem; - line-height: 1.15rem; - overflow-wrap: anywhere; - } - - [data-streamdown="link-safety-modal"] > div > div:last-child { - justify-content: flex-end; - } - - [data-streamdown="link-safety-modal"] > div > div:last-child button { - flex: none; - min-width: 7rem; - padding: 0.5rem 0.8rem; - border-radius: 0.6rem; - font-size: 0.75rem; - } +.agent-streamdown { + overflow-wrap: anywhere; +} + +.agent-streamdown > :first-child { + margin-top: 0; +} + +.agent-streamdown > :last-child { + margin-bottom: 0; +} + +.agent-streamdown [data-streamdown="code-block"] { + position: relative; + display: block; + contain: none; + contain-intrinsic-size: none !important; + width: 100%; + margin-block: 0.35rem; + padding: 0; + gap: 0; + overflow: hidden; + border: 0; + border-radius: 0.4rem; + background: color-mix(in srgb, currentColor 5%, transparent); +} + +.agent-streamdown [data-streamdown="code-block-header"] { + display: none; +} + +.agent-streamdown [data-streamdown="code-block"] > div:has(> [data-streamdown="code-block-actions"]) { + position: absolute; + top: 0.2rem; + right: 0.2rem; + z-index: 1; + height: auto; + margin: 0; +} + +.agent-streamdown [data-streamdown="code-block-body"] { + min-height: 0; + padding: 0.4rem 2rem 0.4rem 0.6rem; + overflow-x: auto; + border: 0; + border-radius: 0; + background: transparent !important; + font-size: 0.75rem; + line-height: 1.15rem; +} + +.agent-streamdown [data-streamdown="code-block-actions"] { + gap: 0; + margin: 0; + padding: 0; + border: 0; + border-radius: 0.3rem; + background: transparent; + box-shadow: none; + opacity: 0; + transition: opacity 150ms ease; +} + +.agent-streamdown [data-streamdown="code-block"]:hover [data-streamdown="code-block-actions"], +.agent-streamdown [data-streamdown="code-block-actions"]:focus-within { + opacity: 0.58; +} + +.agent-streamdown [data-streamdown="code-block-actions"] button { + padding: 0.25rem; + border-radius: 0.3rem; +} + +.agent-streamdown [data-streamdown="code-block-actions"] button:hover { + background: color-mix(in srgb, currentColor 8%, transparent); +} + +.agent-streamdown [data-streamdown="inline-code"] { + padding: 0.12rem 0.35rem; + border: 0; + border-radius: 0.3rem; + background: color-mix(in srgb, currentColor 7%, transparent); + font-size: 0.88em; +} + +.agent-streamdown [data-streamdown="link"] { + color: inherit; + text-decoration-color: color-mix(in srgb, currentColor 35%, transparent); + text-underline-offset: 0.2em; +} + +[data-streamdown="link-safety-modal"] { + padding: 1rem; + background: rgb(28 25 23 / 24%); + backdrop-filter: blur(8px); +} + +[data-streamdown="link-safety-modal"] > div { + max-width: 24rem; + gap: 1rem; + padding: 1.25rem; + border-color: color-mix(in srgb, currentColor 12%, transparent); + border-radius: 1rem; + box-shadow: 0 20px 60px rgb(28 25 23 / 18%); +} + +[data-streamdown="link-safety-modal"] > div > button { + top: 0.9rem; + right: 0.9rem; +} + +[data-streamdown="link-safety-modal"] > div > div:first-of-type { + gap: 0.25rem; + padding-right: 2rem; +} + +[data-streamdown="link-safety-modal"] > div > div:first-of-type > div { + gap: 0.5rem; + font-size: 1rem; +} + +[data-streamdown="link-safety-modal"] > div > div:nth-of-type(2) { + max-height: 7rem; + padding: 0.75rem; + border: 1px solid color-mix(in srgb, currentColor 9%, transparent); + background: color-mix(in srgb, currentColor 4%, transparent); + font-size: 0.7rem; + line-height: 1.15rem; + overflow-wrap: anywhere; +} + +[data-streamdown="link-safety-modal"] > div > div:last-child { + justify-content: flex-end; +} + +[data-streamdown="link-safety-modal"] > div > div:last-child button { + flex: none; + min-width: 7rem; + padding: 0.5rem 0.8rem; + border-radius: 0.6rem; + font-size: 0.75rem; }