diff --git a/.env.deploy.example b/.env.deploy.example new file mode 100644 index 0000000..0953aeb --- /dev/null +++ b/.env.deploy.example @@ -0,0 +1,11 @@ +# 本文件是部署配置示例;真实配置写入 .env.deploy,且不要提交到 git。 + +export OPENAI_API_KEY="" +export OPENAI_BASE_URL="http://model.mify.ai.srv/v1" +export OPENAI_MODEL="xiaomi/mimo-v2-flash" + +export CLAW_BACKEND_HOST="127.0.0.1" +export CLAW_BACKEND_PORT="8765" +export CLAW_FRONTEND_HOST="0.0.0.0" +export CLAW_FRONTEND_PORT="3000" +export CLAW_API_URL="http://127.0.0.1:8765" diff --git a/README.md b/README.md index a010c0d..0d5e1b6 100644 --- a/README.md +++ b/README.md @@ -1,933 +1,167 @@ -# Claw Code Agent 架构逆向分析报告 +# ZK Data Agent -本文基于只读分析仓库源码撰写,目标不是介绍如何使用,而是回答这个项目作为一个 Agent 系统是如何被构造出来的。 +ZK Data Agent 是面向中控数据开发流程的 Agent 服务。当前仓库包含: ---- +- Python 后端:负责 Agent loop、工具调用、会话持久化和数据开发工具。 +- Next.js 前端:提供 Web UI、会话管理、工具调用展示和数据开发交互入口。 +- Skill / Tools:承载产品定义到数据生成、线上数据挖掘等数据开发链路。 -## 1. 总体定位 +## 快速部署 -### 1.1 这个项目本质上是什么 +### 从 Git 一键部署 -这个项目本质上是一个 **agentic coding runtime**,而不是单纯的 workflow engine,也不只是一个 tool harness。 +适合首次部署,也适合后续更新: -更准确地说,它处在三者交叉区: - -- **最核心身份**:Claude Code / Codex 风格的本地 coding agent runtime -- **承载方式**:以 Python 实现的 agent loop + tool harness -- **扩展方式**:外挂了大量 runtime 子系统,例如 search、MCP、plan、task、team、workflow、worktree、plugin、hook policy - -因此它不是一个“纯工作流系统”。workflow 在这里是一个被 agent 调用的能力域,而不是系统唯一中心。系统中心仍然是 `LocalCodingAgent` 的多轮模型-工具循环,见 `src/agent_runtime.py:103`、`src/agent_runtime.py:358`。 - -### 1.2 核心抽象是什么 - -这个项目有几个关键抽象: - -- **Agent**:`LocalCodingAgent`,真正的 orchestrator,负责 prompt 构建、loop、工具调用、budget、session 持久化,见 `src/agent_runtime.py:103` -- **Session**:`AgentSessionState`,维护系统消息、用户上下文、assistant/tool transcript 以及 mutation lineage,见 `src/agent_session.py:89` -- **Tool**:`AgentTool` + `ToolExecutionContext`,以 OpenAI function calling 风格对工具进行 schema 化和执行,见 `src/agent_tools.py:44`、`src/agent_tools.py:73` -- **Prompt Context**:`PromptContext` / `AgentContextSnapshot`,把工作目录、git 状态、CLAUDE.md、各种 runtime 状态拼进模型上下文,见 `src/agent_prompting.py:15`、`src/agent_context.py:40` -- **StoredAgentSession`**:把完整 transcript、tool_calls、usage、file_history、plugin_state 持久化到 `.port_sessions/agent/*.json`,见 `src/session_store.py:53` - -换句话说,它不是围绕“任务节点图”来设计的,而是围绕: - -1. 当前 session 里有哪些上下文 -2. 当前模型要不要发起 tool call -3. tool result 如何再次喂回模型 -4. 何时 compact / stop / persist - -### 1.3 与 Claude Code / Codex 的相似点与差异 - -#### 相似点 - -- 典型 **LLM-driven tool loop**:模型产出 tool call,runtime 执行工具,再把结果作为 `tool` message 回灌 -- 以 **workspace / repository** 为中心,强依赖文件系统、git 状态、shell、搜索 -- 支持 **系统提示词 + repo memory** 注入,尤其是 `CLAUDE.md` -- 有 **多轮对话 session**、resume、context compaction、权限 gating -- 支持 **Agent 子代理 / delegation** - -#### 差异点 - -- 比 Claude Code / Codex 更“runtime platform 化”:这里塞了大量本地 runtime 子系统,如 `task_runtime`、`plan_runtime`、`team_runtime`、`workflow_runtime`、`remote_runtime`、`mcp_runtime` -- “skills” 实现较轻:当前更像 prompt-template / slash-command bridge,而不是成熟的按需 markdown 技能系统 -- 权限模型相对简化:主要是 CLI 启动时布尔权限 + policy deny,而不是更细粒度的人机交互审批流 -- 可观测性是“有 transcript / event / file_history 基础设施,但 UI 和诊断链条尚未完全产品化” - -结论上,这个项目像是: - -> 一个以 coding agent loop 为中心、向外扩张成通用本地 agent runtime 平台的 Python 实现。 - ---- - -## 2. Agent Loop - -### 2.1 主循环入口 - -真实入口是 `LocalCodingAgent.run()`,它生成 `session_id`、scratchpad 目录,然后进入 `_run_prompt()`,见: - -- `src/agent_runtime.py:358` -- `src/agent_runtime.py:413` - -恢复会话则走 `resume()`,它把持久化 session 反序列化成 `AgentSessionState`,并补充 file history replay 与 compaction replay,见 `src/agent_runtime.py:376`。 - -### 2.2 每轮如何观察当前状态 - -Agent 的“观察”不是通过一个单独的 observe() 函数完成,而是在 loop 外预构建 session,并在每轮前做上下文压力处理: - -1. **启动时构建 prompt context** - - `build_prompt_context()` -> `build_context_snapshot()` - - 汇总 cwd、platform、git status、scratchpad、CLAUDE.md bundle、各 runtime summary - - 见 `src/agent_runtime.py:240` 左右、`src/agent_prompting.py:30`、`src/agent_context.py:71` - -2. **构建初始 session** - - `build_session()` 使用 system prompt parts + user_context/system_context 生成 session - - `AgentSessionState.create()` 会插入: - - `system` message - - `user_context reminder` - - 当前用户 prompt - - 见 `src/agent_runtime.py:255` 左右、`src/agent_session.py:97` - -3. **每轮前的状态整理** - - `_microcompact_session_if_needed()` - - `_snip_session_if_needed()` - - `_compact_session_if_needed()` - - `_preflight_prompt_length()` - - 见 `src/agent_runtime.py:527-547` - -因此,“观察当前状态”既包括文件系统 / git / runtime summary,也包括自身历史 transcript 的压缩后视图。 - -### 2.3 如何构造上下文 - -上下文构造分两层: - -#### 第一层:环境与 memory 采样 - -`src/agent_context.py` 负责采样: - -- cwd、shell、platform、date -- git status snapshot,见 `src/agent_context.py:279` -- scratchpad directory,见 `src/agent_context.py:197` -- `CLAUDE.md` / `.claude/CLAUDE.md` / `CLAUDE.local.md` / `.claude/rules/*.md` bundle,见 `src/agent_context.py:313-372` -- plugin / hook policy / MCP / remote / search / account / ask_user / config / LSP / plan / task / team / workflow / worktree 的 runtime summary,见 `src/agent_context.py:205-275` - -#### 第二层:系统提示词装配 - -`build_system_prompt_parts()` 会拼接: - -- system instructions -- doing tasks guidance -- tool usage guidance -- agent guidance -- plugin / MCP / search / account / ask-user / config / LSP / plan / task / team / hook policy guidance -- session-specific permission guidance -- simple env info - -见 `src/agent_prompting.py:75-119`。 - -也就是说,这个项目的上下文不是“只把聊天记录喂给模型”,而是: - -> transcript + injected local memory + runtime summaries + environment facts + permissions hints - -### 2.4 如何决定下一步行动 - -行动选择主要由模型驱动。 - -每轮中 runtime 做的事情是: - -1. 把 `session.to_openai_messages()` 和 `tool_specs` 发给模型,见 `src/agent_runtime.py:1153-1157` -2. 从返回中解析: - - 普通 assistant content - - tool calls - - finish_reason - - usage -3. 若无 tool call,则视作模型选择直接回答 -4. 若有 tool call,则 runtime 执行这些工具,再把工具结果回灌到 session - -真正的“决策”是模型自己通过 OpenAI tool calling 选择的,而不是 runtime 用规则树规划下一步。 - -因此 loop 属于: - -- **外层固定流程** -- **内层模型驱动的动态行动选择** - -### 2.5 如何调用工具 - -工具定义为 `AgentTool`,带: - -- `name` -- `description` -- JSON Schema 参数定义 `parameters` -- `handler` - -见 `src/agent_tools.py:73-111`。 - -每轮把所有工具变成 OpenAI function spec: - -- `tool_specs = [tool.to_openai_tool() for tool in self.tool_registry.values()]` -- 见 `src/agent_runtime.py:463` - -模型返回 tool call 后,主循环在 `src/agent_runtime.py:808-1119` 中执行: - -- 创建一个 tool message 占位 `session.start_tool(...)` -- 记录 `tool_start` event -- 应用 plugin / hook policy preflight -- 执行 delegate / Skill / 普通 tool -- 以 `serialize_tool_result()` 将结果序列化成 JSON 字符串写回 transcript - -### 2.6 工具结果如何回到模型上下文 - -工具结果通过 `session.finalize_tool(...)` 变成一个 `role='tool'` 的 message,内容是: - -```json -{ - "tool": "...", - "ok": true/false, - "content": "...", - "metadata": {...} -} +```bash +curl -fsSL "https://git.n.xiaomi.com/wuyang6/zk-data-agent/-/raw/main/scripts/install-from-git.sh" | bash ``` -见: +指定部署目录或分支: -- `src/agent_tools.py:1248` -- `src/agent_runtime.py:1037-1047` +```bash +curl -fsSL "https://git.n.xiaomi.com/wuyang6/zk-data-agent/-/raw/main/scripts/install-from-git.sh" | \ + bash -s -- --dir "$HOME/zk-data-agent" --branch main +``` -下一轮模型调用时,`session.to_openai_messages()` 会把这些 tool messages 一并送回模型。因此工具结果不是旁路状态,而是标准 transcript 的一部分。 +这个入口脚本会自动完成: -### 2.7 什么时候停止 +- 目标目录不存在时执行 `git clone` +- 目标目录已存在时执行 `git fetch` / `git pull --ff-only` +- 进入仓库后执行 `bash scripts/deploy-ubuntu.sh` -停止条件有多类: +### 仓库内一键部署/更新 -- **模型正常回答且无 tool_calls**:结束,见 `src/agent_runtime.py:764-806` -- **finish_reason = length/max_tokens**:自动注入 continuation prompt,再继续一轮,见 `src/agent_runtime.py:766-783` -- **budget exceeded**:停止,见 `src/agent_runtime.py:500-525`、`552-581`、`733-762` -- **prompt too long / preflight fail**:停止或先 compact 再试,见 `src/agent_runtime.py:543-605` -- **backend error**:停止,见 `src/agent_runtime.py:703-725` -- **达到 `max_turns`**:停止,见 `src/agent_runtime.py:1121-1146` +如果已经在仓库目录中: -### 2.8 loop 是固定流程还是模型驱动 +```bash +bash scripts/deploy-ubuntu.sh +``` -结论: +部署指定分支: -- **loop 骨架是固定的 runtime state machine** -- **每轮内的动作内容是模型驱动的** +```bash +bash scripts/deploy-ubuntu.sh main +``` -固定部分是: +强制覆盖服务器工作区: -1. 整理上下文 -2. 预算检查 -3. 调模型 -4. 若 tool_calls 则执行工具 -5. 把工具结果写回 transcript -6. 进入下一轮或停止 +```bash +bash scripts/deploy-ubuntu.sh main --force +``` -动态部分是: +## 部署配置 -- 调什么工具 -- 调多少个工具 -- 是继续观察、修改还是直接回答 +首次执行 `scripts/deploy-ubuntu.sh` 时,如果仓库根目录不存在 `.env.deploy`,脚本会交互式提示输入: ---- +- `OPENAI_API_KEY` +- `OPENAI_BASE_URL` +- `OPENAI_MODEL` +- 后端监听地址和端口 +- 前端监听地址和端口 -## 3. Context / Memory - -### 3.1 是否有短期上下文 - -有。短期上下文就是当前 `AgentSessionState.messages`。 - -其中包含: - -- system prompt -- user context reminder -- user / assistant / tool messages -- mutation metadata - -见 `src/agent_session.py:89-148`。 - -### 3.2 是否有长期记忆 - -有,但不是向量数据库式长期记忆,而是 **文件化长期记忆**: - -- `~/.claude/CLAUDE.md` -- 向上目录搜索的 `CLAUDE.md` -- `.claude/CLAUDE.md` -- `CLAUDE.local.md` -- `.claude/rules/*.md` - -这些文件会在 session 创建时被 bundle 进 `user_context['claudeMd']`,见 `src/agent_context.py:205-207`、`313-372`。 - -因此它支持“repo memory / instruction memory”,但不支持自动写回、检索式 memory store 或 embedding memory。 - -### 3.3 是否有 conversation history - -有,而且比较完整: - -- 运行时 transcript:`AgentSessionState.messages` -- 持久化 transcript:`StoredAgentSession.messages` -- resume 后会恢复完整历史,见 `src/session_store.py:53-115` - -工具调用、usage、message metadata 也一起保存。 - -### 3.4 是否有压缩 / summarize / compaction - -有,而且这是一个比较成熟的部分。 - -机制分三层: - -- **microcompact**:时间驱动的小型压缩,清理旧 tool results,见 `src/agent_runtime.py:113-115 import`、`src/agent_runtime.py:527-533` -- **snip**:把早期消息 tombstone 化为短摘要,见 `src/agent_runtime.py:533-537` 与 `_snip_session_pass` -- **compact**:调用 `compact_conversation()` 生成更正式的压缩摘要,见 `src/agent_runtime.py:538-547`、`src/agent_runtime.py:1240` 前后逻辑 - -此外还有: - -- prompt too long 时的 **reactive compaction retry** -- resume 时的 **compaction replay** - -见 `src/agent_runtime.py:3042-3142`。 - -### 3.5 是否有外部文件作为 memory - -有,且非常明显: - -- `CLAUDE.md` 系列 -- `.claude/rules/*.md` -- 各类 workspace state 文件:config、plan、task、team、workflow、worktree 等 runtime manifest/state 文件 - -不过严格说,后者更多是 “runtime state injection”,不是纯 memory。 - -### 3.6 是否支持类似 CLAUDE.md / AGENTS.md / skills 的持久知识 - -#### CLAUDE.md - -支持,且是当前唯一成熟的一类持久知识注入。 - -#### AGENTS.md - -**不原生支持 `AGENTS.md` 作为 memory bundle**。当前 memory 发现逻辑只搜索 `CLAUDE.md`、`CLAUDE.local.md` 和 `.claude/rules/*.md`,见 `src/agent_context.py:355-372`。 - -#### skills - -只支持“bundled skills”: - -- 在 Python 代码里硬编码 skill 元数据和 prompt builder -- 通过 `Skill` 工具或 `/skills` 暴露 - -不支持从工作区扫描 markdown skill 文档并按需加载。 - -### 3.7 当前设计局限 - -1. **长期记忆是纯文件注入,不是检索式 memory** - - 规模大时容易塞满 prompt - - 没有 relevance ranking - -2. **只识别 CLAUDE.md 家族,不识别 AGENTS.md** - - 对多代理协作约束不够统一 - -3. **resume 依赖 transcript replay,不是真正的 semantic memory** - - 历史很多时仍然会发生 compaction 丢失细节 - -4. **runtime summaries 很多,但上下文管理是“全量摘要注入”** - - 不是 query-time selective loading - -5. **task / plan / workflow 已有,但未形成强约束的 workspace memory layout** - - 更像多个离散 runtime,而不是统一的 task-memory 文件系统 - ---- - -## 4. Tools / Actions - -### 4.1 工具能力概览 - -核心工具能力包括: - -- **文件系统** - - `list_dir` - - `read_file` - - `write_file` - - `edit_file` - - `notebook_edit` -- **搜索与代码理解** - - `glob_search` - - `grep_search` - - `LSP` - - `tool_search` -- **Shell** - - `bash` -- **Web / Search** - - `web_fetch` - - `web_search` - - search provider 管理工具 -- **计划 / 任务** - - `update_plan` - - `plan_get` - - task runtime 相关工具 -- **Human gate** - - `ask_user_question` -- **扩展 runtime** - - `mcp_*` - - `remote_*` - - `workflow_*` - - `worktree_*` - - `account_*` - - `config_*` -- **Agent 特有** - - `Agent` / `delegate_agent` - - `Skill` - -tool registry 定义入口在 `src/agent_tools.py:210`。 - -### 4.2 工具 schema 怎么定义 - -每个工具是一个 `AgentTool`: - -- `name` -- `description` -- `parameters`(JSON Schema) -- `handler` - -见 `src/agent_tools.py:73-111`。 - -它通过 `to_openai_tool()` 转成 OpenAI-compatible function calling schema,见 `src/agent_tools.py:80-88`。 - -### 4.3 工具调用怎么执行 - -模型返回 tool call 后,runtime 会: - -1. 解析 tool call,见 `src/openai_compat.py:273-298` -2. 在 loop 中逐个消费,见 `src/agent_runtime.py:808-1119` -3. 普通工具走 `execute_tool_streaming()` 或 `tool.execute()`,见 `src/agent_tools.py:181-207` -4. 特殊工具: - - `Agent` / `delegate_agent` 走子代理执行,见 `src/agent_runtime.py:2214` - - `Skill` 走 `_execute_skill()`,见 `src/agent_runtime.py:2059` - -### 4.4 工具结果如何返回 - -工具结果统一封装为 `ToolExecutionResult`: - -- `name` -- `ok` -- `content` -- `metadata` - -然后 `serialize_tool_result()` 把它转成 JSON 字符串,塞入 tool message,见 `src/agent_tools.py:1248-1255`。 - -这意味着: - -- 对模型来说,工具结果是 transcript 中的标准 `tool` message -- 对 runtime 来说,工具结果又带 `metadata`,可用于 file_history、policy event、plugin hook - -### 4.5 是否有安全边界 - -有,但不是特别细粒度。 - -#### 文件边界 - -`_resolve_path()` 会强制路径位于 workspace root 内,防止越界,见 `src/agent_tools.py:1277-1288`。 - -#### 写权限边界 - -`write_file` / `edit_file` 受 `allow_file_write` 控制,见 `src/agent_tools.py:1291-1295`。 - -#### shell 权限边界 - -`bash` 受两层控制: - -- `allow_shell_commands` -- `allow_destructive_shell_commands` - -并有 destructive regex 阻断,比如 `rm`、`git reset --hard`、`git clean -fd` 等,见 `src/agent_tools.py:1298-1324`。 - -#### policy / hook deny - -workspace 可以通过 `.claw-policy.json` / `.codex-policy.json` / `.claw-hooks.json` 配置 deny_tools、deny_tool_prefixes 以及 hook message,见 `src/hook_policy.py:160-256`。 - -#### CLI 层 deny context - -还有一个更轻的 `ToolPermissionContext`,可按名字或前缀过滤工具,见 `src/permissions.py:7-17`。 - -### 4.6 这一层的架构评价 - -这个项目的 tools 层已经不是“几个本地 IO 工具”,而是: - -> 一个围绕 workspace 资源、外部 runtime 状态和 agent orchestration 展开的统一 action surface。 - -优点是扩展性强,缺点是工具域越来越大后,模型对何时该用哪个工具会变得更依赖 prompt guidance。 - ---- - -## 5. Markdown / Workspace - -### 5.1 是否支持 workspace-first - -是,明显是 workspace-first 设计。 - -证据: - -- `ToolExecutionContext.root` 是工作区根,见 `src/agent_tools.py:45` -- 所有文件工具都围绕 workspace root 做相对路径解析,见 `src/agent_tools.py:1277-1288` -- context 注入围绕 cwd、git repo、worktree、scratchpad、additional_working_directories 展开,见 `src/agent_context.py:71-99` -- 很多 runtime 都是 `from_workspace(cwd)` 初始化的 - -### 5.2 是否适合让 Agent 维护 `.md` 文件作为任务记忆 - -适合,而且比很多 agent 框架更适合。 - -原因: - -- 已经原生支持 markdown 记忆文件注入(CLAUDE.md / rules) -- 文件读写/编辑能力完整 -- 有 plan / task runtime,可作为结构化状态 -- 有 scratchpad directory,可作为会话级临时区 - -不足是它没有约定一个统一的 task-memory 目录布局。 - -### 5.3 是否有任务目录 / workspace 概念 - -有 workspace 概念,但没有强约定的任务目录结构。 - -已有元素: - -- workspace root -- additional working directories -- scratchpad root / scratchpad directory -- `.claude/` 作为本地 agent/runtime 元数据目录 - -但没有默认的: - -- `/tasks/{task_id}/...` -- `/artifacts/...` -- `/memory/...` - -### 5.4 能否改造成 task directory layout - -完全可以,而且改造成本不高。 - -一个可行布局: +配置会写入: ```text -/tasks/{task_id}/goal.md -/tasks/{task_id}/memory/open_questions.md -/tasks/{task_id}/memory/working_notes.md -/tasks/{task_id}/artifacts/report.md -/tasks/{task_id}/artifacts/commands.md -/tasks/{task_id}/artifacts/findings.md +.env.deploy ``` -最小改造方式: +该文件包含敏感信息,只保存在部署机器本地,权限会设置为 `600`,并且已被 `.gitignore` 忽略,不会提交到 git。 -1. 在 `task_runtime` 中给 task 增加 `task_root` -2. 在 `agent_context._load_memory_bundle()` 中把 `/tasks/{active_task}/memory/*.md` 纳入 memory discovery -3. 在 prompt context 中注入 `active task root` -4. 给 `update_plan` / task 工具增加 task-root 绑定 +如需修改模型或 key: -这样不需要重写 loop,只是增强 workspace memory layout。 +```bash +vim .env.deploy +``` ---- +可参考示例: -## 6. Skills +```bash +cp .env.deploy.example .env.deploy +``` -### 6.1 当前是否支持 skill +## 运行方式 -支持,但属于 **弱 skill 系统**。 +部署脚本会安装两个 systemd 服务: -当前 skill 机制分两部分: +```bash +zk-data-agent-backend +zk-data-agent-frontend +``` -1. **Bundled skills** - - `BundledSkill` 定义 name / description / when_to_use / aliases / allowed_tools / get_prompt - - 见 `src/bundled_skills.py:22-32` +查看状态: -2. **Skill tool** - - `_execute_skill()` 优先查 bundled skill - - 查不到再退回 slash command - - 见 `src/agent_runtime.py:2059-2142` +```bash +sudo systemctl status zk-data-agent-backend +sudo systemctl status zk-data-agent-frontend +``` -### 6.2 是否有技能发现机制 +查看日志: -有,但只对 bundled skill 生效: +```bash +sudo journalctl -u zk-data-agent-backend -f +sudo journalctl -u zk-data-agent-frontend -f +``` -- `format_skills_for_system_prompt()` 把 skill 列表渲染进系统提示,帮助模型“知道”有哪些 skill,见 `src/bundled_skills.py:268-289` -- GUI 也有 `/api/skills` 列表,见 `backend/api/server.py` +重启服务: -但它没有: +```bash +sudo systemctl restart zk-data-agent-backend zk-data-agent-frontend +``` -- 从工作区扫描 skill 文件 -- 从 skill 文档按需加载正文 -- skill-level semantic retrieval +停止服务: -### 6.3 是否支持 progressive disclosure +```bash +sudo systemctl stop zk-data-agent-backend zk-data-agent-frontend +``` -**严格说不支持。** +## 本地开发启动 -当前 bundled skill 更像: +本地调试可以使用: -- 用户 / 模型选中 skill -- skill 生成一段 prompt -- prompt 立即进入模型上下文 +```bash +bash scripts/start-webui.sh +``` -并不是: +默认端口: -- 先列 metadata -- 决定是否需要 skill -- 再按需读取 skill markdown 正文 -- 只展开相关片段 +- 前端:`http://127.0.0.1:3000` +- 后端:`http://127.0.0.1:8765` -因此它缺乏真正的 progressive disclosure。 +`scripts/start-webui.sh` 会优先读取本机 `.env.deploy`,也可以直接使用当前 shell 中的环境变量。 -### 6.4 如果没有,应该如何扩展 +## 环境要求 -建议做一个最小可行版 workspace skill system,不破坏现有 loop。 +Ubuntu 部署建议准备: -#### 最小改造方案 +- `git` +- `bash` +- `curl` +- `sudo` +- `systemd` +- `pyenv` +- Python `3.10.14` +- Node.js `20` 或 `22` +- `npm` -##### 目录结构 +Python 后端依赖由部署脚本安装到项目根目录 `.venv`。 + +前端依赖由部署脚本在 `frontend/app` 下执行 `npm ci` 安装,并执行 `npm run build`。 + +## 目录说明 ```text -.claude/skills/ - review/ - skill.json - SKILL.md - data-agent/ - skill.json - SKILL.md - python-debug/ - skill.json - SKILL.md +backend/ FastAPI Web 后端 +frontend/app/ Next.js 前端 +src/ Agent runtime、tools、skills +scripts/ 本地启动、部署、systemd 启动脚本 +deploy/systemd/ systemd service 模板 +.port_sessions/ 本地运行数据,禁止提交 +.env.deploy 本机私有部署配置,禁止提交 ``` -##### metadata +## 注意事项 -`skill.json` 示例: - -```json -{ - "name": "data-agent", - "description": "Analyze datasets and produce structured markdown reports.", - "when_to_use": "When the task involves CSV/JSON/table exploration, metric comparison, or report writing.", - "aliases": ["data", "analysis"], - "allowed_tools": ["read_file", "write_file", "edit_file", "glob_search", "grep_search", "bash"], - "entry_markdown": "SKILL.md" -} -``` - -##### loading 时机 - -建议两阶段: - -1. **session 初始化时只加载 metadata** - - 像 bundled skill 一样列进 system prompt - - 不把 `SKILL.md` 正文直接注入 - -2. **当模型选择 Skill 工具时再加载正文** - - `Skill(skill="data-agent")` - - runtime 读取对应 `SKILL.md` - - 返回 skill prompt 或 `` 包装内容 - -##### Agent 如何决定读取哪个 skill - -沿用现有机制,最小改造即可: - -- 在 system prompt 里暴露 `name + description + when_to_use` -- 模型自己决定是否调用 `Skill` -- `Skill` 工具负责延迟读取 skill 正文 - -##### 代码落点 - -最小改造建议: - -- 扩展 `src/bundled_skills.py` 为“bundled + discovered skills” -- 或新建 `workspace_skills.py` -- 在 `_execute_skill()` 中优先: - 1. bundled skill - 2. workspace discovered skill - 3. slash command fallback - -这个改法不会触碰 loop 主干,只是在 skill lookup 阶段增加一个来源。 - ---- - -## 7. Policy / Human Gate - -### 7.1 如何处理权限 - -当前权限机制主要有三层。 - -#### 第一层:启动时权限布尔开关 - -`AgentPermissions`: - -- `allow_file_write` -- `allow_shell_commands` -- `allow_destructive_shell_commands` - -见 `src/agent_types.py:147-150`。 - -这是最直接的 runtime gate。 - -#### 第二层:workspace hook / policy - -`HookPolicyRuntime` 支持: - -- `trusted` -- `managed_settings` -- `safe_env_names` -- `deny_tools` -- `deny_tool_prefixes` -- `before_prompt` -- `after_turn` -- `before_tool` -- `after_tool` -- `budget_overrides` - -见 `src/hook_policy.py:10-22`。 - -#### 第三层:ask_user runtime - -并不是所有危险动作都会自动弹确认框。当前“人工确认”更像一个工具能力: - -- `ask_user_question` - -也就是说,模型可以选择向用户提问,但 runtime 本身不会像桌面 Codex 那样对每个危险动作自动出现审批 UI。 - -### 7.2 哪些动作会请求用户确认 - -严格来说,**默认没有统一的自动确认拦截器**。 - -当前模式更像: - -- 文件写:如果没开 `allow_write`,直接拒绝 -- shell:如果没开 `allow_shell`,直接拒绝 -- destructive shell:如果没开 `--unsafe`,直接拒绝 -- policy deny:直接拒绝 -- ask-user:模型显式调用 `ask_user_question` 才会走人工澄清 - -因此它偏向: - -> hard gate / deny-first,而不是 ask-on-demand 审批流。 - -### 7.3 哪些动作默认允许 - -默认允许的是安全读类能力,例如: - -- read -- list -- search -- tool discovery -- runtime status inspection - -默认不允许的是: - -- 写文件 -- shell -- destructive shell - -具体提示也被写进 system prompt,见 `src/agent_prompting.py:388-401`。 - -### 7.4 是否有 deny / allow / ask 策略 - -有 `allow` 与 `deny`,但 `ask` 不是系统级统一策略。 - -- **allow**:CLI flag / runtime permissions -- **deny**:hook policy deny_tools / deny_tool_prefixes -- **ask**:只通过 `ask_user_question` 工具实现,非自动策略 - -### 7.5 是否支持项目级规则 - -支持。 - -项目级规则来自: - -- `.claw-policy.json` -- `.codex-policy.json` -- `.claw-hooks.json` - -并且会沿 cwd 向上目录发现,见 `src/hook_policy.py:160-187`。 - -### 7.6 对数据 Agent 的不足 - -如果你们要做数据 Agent,这套 human gate 还有几个明显缺口: - -1. **缺少按数据源级别的权限模型** - - 没有“允许读 CSV 但不允许发网络请求”这种更细粒度策略 - -2. **没有结构化审批流** - - 例如“将执行 SQL / 访问线上表 / 导出结果集”的 ask approval 缺位 - -3. **没有数据脱敏/泄露策略** - - safe_env 只管环境变量,不管输出内容 - -4. **没有审计级 action classification** - - tool 结果有 metadata,但没有“高风险数据出站/高风险变更”的统一标签体系 - -5. **policy 更偏 workspace engineering,而不是 data governance** - -如果面向数据 Agent,建议增加: - -- data source allowlist -- query approval policy -- outbound redaction hook -- result size limits -- table/column sensitivity policy - ---- - -## 8. Trace / Observability - -### 8.1 是否记录每一轮模型输入输出 - -有记录,但“模型输入原文”不单独存为一条日志,而是可由 session transcript 和 system/user context 还原。 - -保存内容包括: - -- `messages` transcript -- `system_prompt_parts` -- `user_context` -- `system_context` -- `usage` -- `turns` -- `tool_calls` - -见 `src/session_store.py:53-115`、`src/agent_runtime.py:3269-3345`。 - -模型输出则直接进入 `assistant` messages。 - -### 8.2 是否记录工具调用 - -有,而且记录得比较细: - -- transcript 中的 assistant tool_calls -- transcript 中的 tool result message -- `events` 中的 `tool_start` / `tool_delta` / `tool_result` -- `file_history` 中的摘要化变更记录 - -见 `src/agent_runtime.py:852-1119`。 - -### 8.3 是否记录文件变更 - -有,`file_history` 是一个重要机制。 - -resume 时会把 file_history replay 成一个 `` 给模型,见: - -- `src/agent_runtime.py:2914-3040` - -持久化时也会一起保存到 session json,见: - -- `src/session_store.py:66` -- `src/agent_runtime.py:3333` - -它不是完整 patch log,但已经记录: - -- action -- path / changed_paths -- snapshot ids -- before/after preview -- result preview - -### 8.4 是否有 replay/debug 能力 - -有基础能力: - -- `agent-resume` -- transcript 持久化 -- file_history replay -- compaction replay -- GUI 可以展示 transcript 和 tool calls - -但没有一个真正成熟的“调试控制台”或统一 trace viewer。 - -### 8.5 现有 observability 的优点 - -优点: - -- 数据结构已经齐全:transcript、events、file_history、budget_state、plugin_state -- session 是 JSON 文件,易于离线分析 -- message metadata 里保留了 mutation lineage / revision 信息 - -这意味着系统已经具备做更强 observability 的底座。 - -### 8.6 还缺什么 - -目前还缺: - -1. **完整 prompt snapshot** - - 现在有 system/user context 和 transcript,但缺“每轮实际发给模型的最终 payload”落盘 - -2. **turn-level structured trace** - - 比如 `turn_03.json` - - 包含 prompt size、tool candidates、selected tool calls、latency、errors - -3. **统一 replay viewer** - - 现在 resume 偏“继续工作” - - 不是“回放调试” - -4. **tool latency / error taxonomy** - - 有 event,但缺统计聚合 - -5. **compaction provenance** - - 已有 replay,但缺图形化 lineage/summary mapping - -### 8.7 如果要补,怎么加 - -最小建议: - -#### 建议新增 trace 目录 - -```text -.port_sessions/traces/{session_id}/ - turn_001.request.json - turn_001.response.json - turn_001.events.json - turn_001.tools.json -``` - -#### 每轮保存这些内容 - -- request - - rendered messages - - tool specs - - token budget snapshot -- response - - raw model response - - parsed tool calls - - finish_reason -- tools - - tool args - - tool result - - latency -- events - - 当前 turn 的 event list - -#### 代码落点 - -最合适挂点在: - -- `_query_model()` 前后 -- tool loop `for tool_call in turn.tool_calls` -- `_persist_session()` - -这样能复用现有 `session_id` 和 `turn_index`。 - ---- - -## 总结判断 - -这个项目已经具备一个完整 coding agent runtime 的主要骨架: - -- 有明确的 session 与 transcript 模型 -- 有模型驱动的动态工具循环 -- 有 context injection 与 memory bundle -- 有 compaction / replay / resume -- 有较丰富的本地工具和 runtime 子系统 - -但从“下一代 workspace-native agent platform”的角度看,它还有几个明显缺口: - -- skills 仍是 prompt-template 级,未形成 markdown-native progressive disclosure -- policy 偏 deny/allow,不是强审批流 -- memory 已经文件化,但没有统一 task directory layout -- observability 有底座,缺一层真正可用的 trace 产品化视图 - -如果目标是把它改造成更强的“任务型数据 Agent / 文档驱动 Agent / workspace-memory-first Agent”,最值得优先补的不是 loop,而是三件事: - -1. **workspace skill discovery + on-demand markdown loading** -2. **任务目录结构与 memory layout 约定** -3. **turn-level trace 持久化与 replay viewer** - -这三项都可以在不重写 `LocalCodingAgent` 主循环的前提下增量演进。 +- 不要把 `OPENAI_API_KEY` 写入可提交文件。 +- `.env.deploy`、`.venv`、`.port_sessions` 都是本机文件,不进入 git。 +- 服务器更新优先使用 `bash scripts/deploy-ubuntu.sh`,不要手工分散执行依赖安装和服务重启。 +- 生产/长期运行使用 systemd 服务;`scripts/start-webui.sh` 只用于本地调试。 diff --git a/deploy/systemd/zk-data-agent-backend.service.template b/deploy/systemd/zk-data-agent-backend.service.template new file mode 100644 index 0000000..e5c4d15 --- /dev/null +++ b/deploy/systemd/zk-data-agent-backend.service.template @@ -0,0 +1,16 @@ +[Unit] +Description=ZK Data Agent backend +After=network-online.target +Wants=network-online.target + +[Service] +Type=simple +User=__USER__ +WorkingDirectory=__PROJECT_ROOT__ +ExecStart=__PROJECT_ROOT__/scripts/start-backend.sh +Restart=always +RestartSec=5 +Environment=PYTHONUNBUFFERED=1 + +[Install] +WantedBy=multi-user.target diff --git a/deploy/systemd/zk-data-agent-frontend.service.template b/deploy/systemd/zk-data-agent-frontend.service.template new file mode 100644 index 0000000..961e8e3 --- /dev/null +++ b/deploy/systemd/zk-data-agent-frontend.service.template @@ -0,0 +1,15 @@ +[Unit] +Description=ZK Data Agent frontend +After=network-online.target zk-data-agent-backend.service +Wants=network-online.target + +[Service] +Type=simple +User=__USER__ +WorkingDirectory=__PROJECT_ROOT__/frontend/app +ExecStart=__PROJECT_ROOT__/scripts/start-frontend.sh +Restart=always +RestartSec=5 + +[Install] +WantedBy=multi-user.target diff --git a/frontend/app/app/claw-llm-settings.tsx b/frontend/app/app/claw-llm-settings.tsx index 7144c83..0de166f 100644 --- a/frontend/app/app/claw-llm-settings.tsx +++ b/frontend/app/app/claw-llm-settings.tsx @@ -25,7 +25,7 @@ type ClawState = { const DEFAULT_STATE: ClawState = { base_url: "http://model.mify.ai.srv/v1", - api_key: "sk-Jxoh5lf1jWg6HQZYYyfyagXudGxUXNAgkZklxMnnXHn7pFmU", + api_key: "", }; export function ClawLlmSettings() { diff --git a/scripts/deploy-ubuntu.sh b/scripts/deploy-ubuntu.sh new file mode 100755 index 0000000..998c29f --- /dev/null +++ b/scripts/deploy-ubuntu.sh @@ -0,0 +1,272 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Ubuntu 一键部署/更新脚本。 +# - 首次执行会交互式生成 .env.deploy,本机保存,不提交 git。 +# - 后续执行会拉取代码、更新依赖、构建前端、安装/重启 systemd 服务。 + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +ENV_FILE="${ROOT_DIR}/.env.deploy" +PYTHON_VERSION="${PYTHON_VERSION:-3.10.14}" +BACKEND_SERVICE="zk-data-agent-backend" +FRONTEND_SERVICE="zk-data-agent-frontend" +BRANCH="" +FORCE=0 +SKIP_GIT=0 + +usage() { + cat < 覆盖本地改动 + --skip-git 跳过 git 拉取,只部署当前工作区 +EOF +} + +while [[ $# -gt 0 ]]; do + case "$1" in + --force) + FORCE=1 + shift + ;; + --skip-git) + SKIP_GIT=1 + shift + ;; + -h|--help) + usage + exit 0 + ;; + *) + if [[ -n "${BRANCH}" ]]; then + echo "Unexpected argument: $1" >&2 + usage >&2 + exit 1 + fi + BRANCH="$1" + shift + ;; + esac +done + +log() { + printf "\n\033[1;34m==>\033[0m %s\n" "$1" +} + +warn() { + printf "\033[1;33mWARN\033[0m %s\n" "$1" >&2 +} + +fail() { + printf "\033[1;31mERROR\033[0m %s\n" "$1" >&2 + exit 1 +} + +require_command() { + local name="$1" + local hint="$2" + if ! command -v "${name}" >/dev/null 2>&1; then + fail "缺少命令:${name}。${hint}" + fi +} + +prompt_value() { + local var_name="$1" + local prompt="$2" + local default_value="${3:-}" + local secret="${4:-0}" + local value + + if [[ "${secret}" == "1" ]]; then + read -r -s -p "${prompt}" value + printf "\n" + else + if [[ -n "${default_value}" ]]; then + read -r -p "${prompt} [${default_value}]: " value + else + read -r -p "${prompt}: " value + fi + fi + value="${value:-${default_value}}" + printf -v "${var_name}" "%s" "${value}" +} + +write_env_file() { + local api_key="$1" + local base_url="$2" + local model="$3" + local backend_host="$4" + local backend_port="$5" + local frontend_host="$6" + local frontend_port="$7" + local claw_api_url="http://${backend_host}:${backend_port}" + + umask 077 + cat >"${ENV_FILE}" </dev/null 2>&1; then + echo "pyenv 未发现 Python ${PYTHON_VERSION},开始安装。" + pyenv install "${PYTHON_VERSION}" + fi + PYENV_VERSION="${PYTHON_VERSION}" pyenv exec python -m venv "${ROOT_DIR}/.venv" + "${ROOT_DIR}/.venv/bin/python" -m pip install --upgrade pip setuptools wheel + "${ROOT_DIR}/.venv/bin/python" -m pip install -e . +} + +build_frontend() { + require_command npm "请先安装 Node.js 和 npm,建议 Node.js 20 或 22。" + log "安装并构建前端" + cd "${ROOT_DIR}/frontend/app" + if [[ -f package-lock.json ]]; then + npm ci + else + npm install + fi + npm run build +} + +install_systemd_service() { + local service_name="$1" + local template_path="$2" + local target_path="/etc/systemd/system/${service_name}.service" + local tmp_path + tmp_path="$(mktemp)" + sed \ + -e "s#__PROJECT_ROOT__#${ROOT_DIR}#g" \ + -e "s#__USER__#$(id -un)#g" \ + "${template_path}" >"${tmp_path}" + sudo install -m 0644 "${tmp_path}" "${target_path}" + rm -f "${tmp_path}" +} + +install_services() { + require_command systemctl "当前系统不支持 systemd,无法安装服务。" + require_command sudo "安装 systemd 服务需要 sudo。" + log "安装/更新 systemd 服务" + install_systemd_service "${BACKEND_SERVICE}" "${ROOT_DIR}/deploy/systemd/${BACKEND_SERVICE}.service.template" + install_systemd_service "${FRONTEND_SERVICE}" "${ROOT_DIR}/deploy/systemd/${FRONTEND_SERVICE}.service.template" + sudo systemctl daemon-reload + sudo systemctl enable "${BACKEND_SERVICE}" "${FRONTEND_SERVICE}" +} + +restart_services() { + log "重启服务" + sudo systemctl restart "${BACKEND_SERVICE}" + sudo systemctl restart "${FRONTEND_SERVICE}" +} + +health_check() { + # shellcheck disable=SC1090 + source "${ENV_FILE}" + local backend_url="http://${CLAW_BACKEND_HOST:-127.0.0.1}:${CLAW_BACKEND_PORT:-8765}/api/state" + local frontend_url="http://127.0.0.1:${CLAW_FRONTEND_PORT:-3000}/" + log "健康检查" + for _ in {1..40}; do + if curl -fsS "${backend_url}" >/dev/null 2>&1 && curl -fsS "${frontend_url}" >/dev/null 2>&1; then + echo "backend: ${backend_url}" + echo "frontend: http://<机器IP>:${CLAW_FRONTEND_PORT:-3000}/" + echo "服务已启动。" + return + fi + sleep 0.5 + done + warn "健康检查失败,输出最近日志。" + sudo journalctl -u "${BACKEND_SERVICE}" -n 80 --no-pager || true + sudo journalctl -u "${FRONTEND_SERVICE}" -n 80 --no-pager || true + exit 1 +} + +main() { + log "ZK Data Agent Ubuntu 部署" + update_git + ensure_env_file + ensure_python + build_frontend + install_services + restart_services + health_check +} + +main "$@" diff --git a/scripts/install-from-git.sh b/scripts/install-from-git.sh new file mode 100755 index 0000000..a518637 --- /dev/null +++ b/scripts/install-from-git.sh @@ -0,0 +1,67 @@ +#!/usr/bin/env bash +set -euo pipefail + +# 适合 README 中 curl | bash 的安装入口。 +# 它只负责 clone/pull 仓库,然后调用仓库内 scripts/deploy-ubuntu.sh。 + +REPO="${REPO:-git@git.n.xiaomi.com:wuyang6/zk-data-agent.git}" +BRANCH="${BRANCH:-main}" +APP_DIR="${APP_DIR:-${HOME}/zk-data-agent}" +FORCE=0 + +usage() { + cat <&2 + usage >&2 + exit 1 + ;; + esac +done + +command -v git >/dev/null 2>&1 || { + echo "Missing git. Please install git first." >&2 + exit 1 +} + +if [[ -d "${APP_DIR}/.git" ]]; then + cd "${APP_DIR}" + git fetch origin + git checkout "${BRANCH}" + if [[ "${FORCE}" == "1" ]]; then + git reset --hard "origin/${BRANCH}" + else + git pull --ff-only origin "${BRANCH}" + fi +else + git clone --branch "${BRANCH}" "${REPO}" "${APP_DIR}" + cd "${APP_DIR}" +fi + +bash scripts/deploy-ubuntu.sh "${BRANCH}" --skip-git diff --git a/scripts/start-backend.sh b/scripts/start-backend.sh new file mode 100755 index 0000000..89d229c --- /dev/null +++ b/scripts/start-backend.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Ubuntu/systemd 后端启动入口。 +# 配置从项目根目录的 .env.deploy 读取;该文件只保存在部署机器本地,不提交到 git。 + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +ENV_FILE="${ROOT_DIR}/.env.deploy" + +if [[ -f "${ENV_FILE}" ]]; then + # shellcheck disable=SC1090 + source "${ENV_FILE}" +else + echo "Missing deploy config: ${ENV_FILE}" >&2 + echo "Run: bash scripts/deploy-ubuntu.sh" >&2 + exit 1 +fi + +export OPENAI_API_KEY +export OPENAI_BASE_URL="${OPENAI_BASE_URL:-http://model.mify.ai.srv/v1}" +export OPENAI_MODEL="${OPENAI_MODEL:-xiaomi/mimo-v2-flash}" + +BACKEND_HOST="${CLAW_BACKEND_HOST:-127.0.0.1}" +BACKEND_PORT="${CLAW_BACKEND_PORT:-8765}" +PYTHON_BIN="${CLAW_PYTHON_BIN:-${ROOT_DIR}/.venv/bin/python}" + +exec "${PYTHON_BIN}" -m src.gui \ + --host "${BACKEND_HOST}" \ + --port "${BACKEND_PORT}" \ + --cwd "${ROOT_DIR}" \ + --session-dir "${ROOT_DIR}/.port_sessions/agent" \ + --allow-write \ + --allow-shell \ + --no-browser diff --git a/scripts/start-frontend.sh b/scripts/start-frontend.sh new file mode 100755 index 0000000..49d4a8e --- /dev/null +++ b/scripts/start-frontend.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Ubuntu/systemd 前端启动入口。 +# 运行生产构建后的 Next.js 服务;开发调试继续使用 scripts/start-webui.sh。 + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +ENV_FILE="${ROOT_DIR}/.env.deploy" + +if [[ -f "${ENV_FILE}" ]]; then + # shellcheck disable=SC1090 + source "${ENV_FILE}" +else + echo "Missing deploy config: ${ENV_FILE}" >&2 + echo "Run: bash scripts/deploy-ubuntu.sh" >&2 + exit 1 +fi + +BACKEND_HOST="${CLAW_BACKEND_HOST:-127.0.0.1}" +BACKEND_PORT="${CLAW_BACKEND_PORT:-8765}" +FRONTEND_HOST="${CLAW_FRONTEND_HOST:-0.0.0.0}" +FRONTEND_PORT="${CLAW_FRONTEND_PORT:-3000}" + +export CLAW_API_URL="${CLAW_API_URL:-http://${BACKEND_HOST}:${BACKEND_PORT}}" + +cd "${ROOT_DIR}/frontend/app" +exec npm run start -- --hostname "${FRONTEND_HOST}" --port "${FRONTEND_PORT}" diff --git a/scripts/start-webui.sh b/scripts/start-webui.sh index 98be7e2..c06bfe5 100755 --- a/scripts/start-webui.sh +++ b/scripts/start-webui.sh @@ -8,6 +8,11 @@ set -euo pipefail ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" RUN_DIR="${ROOT_DIR}/.port_sessions" +ENV_FILE="${ROOT_DIR}/.env.deploy" +if [[ -f "${ENV_FILE}" ]]; then + # shellcheck disable=SC1090 + source "${ENV_FILE}" +fi BACKEND_HOST="${CLAW_BACKEND_HOST:-0.0.0.0}" BACKEND_PORT="${CLAW_BACKEND_PORT:-8765}" FRONTEND_HOST="${CLAW_FRONTEND_HOST:-0.0.0.0}" @@ -129,7 +134,11 @@ stop_pid_file "${RUN_DIR}/webui-backend.pid" "backend" stop_port_listener "${FRONTEND_PORT}" "frontend" stop_port_listener "${BACKEND_PORT}" "backend" -export OPENAI_API_KEY="${OPENAI_API_KEY:-sk-Jxoh5lf1jWg6HQZYYyfyagXudGxUXNAgkZklxMnnXHn7pFmU}" +if [[ -z "${OPENAI_API_KEY:-}" ]]; then + echo "OPENAI_API_KEY is not configured." >&2 + echo "Run scripts/deploy-ubuntu.sh once, or create ${ENV_FILE} from .env.deploy.example." >&2 +fi +export OPENAI_API_KEY="${OPENAI_API_KEY:-}" export OPENAI_BASE_URL="${OPENAI_BASE_URL:-http://model.mify.ai.srv/v1}" export OPENAI_MODEL="${OPENAI_MODEL:-xiaomi/mimo-v2-flash}" diff --git a/src/gui/__main__.py b/src/gui/__main__.py index 3e8229c..4f3fff8 100644 --- a/src/gui/__main__.py +++ b/src/gui/__main__.py @@ -36,10 +36,7 @@ def main() -> None: ) parser.add_argument( '--api-key', - default=os.environ.get( - 'OPENAI_API_KEY', - 'sk-Jxoh5lf1jWg6HQZYYyfyagXudGxUXNAgkZklxMnnXHn7pFmU', - ), + default=os.environ.get('OPENAI_API_KEY', ''), ) parser.add_argument( '--session-dir',