# tju-code-agent **Repository Path**: e4glet/tju-code-agent ## Basic Information - **Project Name**: tju-code-agent - **Description**: 包含CLI端与GUI端的编程智能体工具 - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-08-09 - **Last Updated**: 2026-10-08 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Tju code 编程智能体工具 由天津大学系统安全与可信计算研究中心内部自研的 coding agent 工具,参考借鉴开源工具 `pi` 的架构与`opencode`的执行质量设计,目标是:省 token、高缓存命中率、可靠的工具调用。 ![alt text](image-1.png) ![alt text](image-2.png) ![alt text](image-3.png) ## 特性 - 双层 agent 循环(外层 turn 循环 + 内层 provider 流式读取),天然适配流式 UI。 - 统一 AI 层:OpenAI 兼容(chat/completions)与 Anthropic Messages,SSE 流式解析。 - 内建工具集:`read` / `bash` / `edit` / `write` / `apply_patch` / `grep` / `glob` / `fetch` / `scan` + 会话级 `todowrite` 计划工具 + `task` 子代理委托,参数用 zod schema 校验。 - 可靠执行:工具参数 JSON 非法或输出被截断的 tool call 一律不执行并要求重发;write 原子写、read 行数截断、edit 失败附带上下文、apply_patch 一次调用跨多文件顺序增改删(失败时列出已应用操作)、Windows bash 进程树清理、bash 报"命令未识别"时附等价替代提示、fetch 限流/截断/超时/中止。 - 联网查询体验:`fetch` 联网时 CLI/GUI 显示【正在联网查询中】与完成状态,不直接回显网页原始 HTML;抓取内容交由 AI 提取关键信息后以摘要/回答形式呈现给用户。 - 内建安全扫描:`scan` 工具做密钥泄露检查(API key / token / 私钥等常见模式)与依赖漏洞检查(`npm audit` 联网查,离线降级内置高危名单);write/edit 后自动追加安全提示,把检查"左移"到编码阶段。 - 高可用:provider 瞬时错误(429/5xx/网络)指数退避自动重试;provider 报 context 溢出时自动强制压缩并重试一次;**SSE 流停滞超时**——provider 中途沉默 120s 即报错、不再无限挂起;任一 hook 抛错不打断整轮。 - 复杂任务韧性:多步任务用 `todowrite` 先列计划再逐步勾选(清单经工具结果留在会话中,GUI 以 `- [~] 内容 (high)` 可视化);**`task` 子代理委托**——把自包含子任务委托给有界嵌套 agent(独立上下文、默认 5 轮、结果摘要回传),主会话保持精简,复杂任务无需拉高 `--max-turns`(默认 50 即可);**自动纠偏**——检测到重复调用同一工具(≥3 次)或整轮空转时,注入一次纠偏提示拉回主线(`afterTurn` hook,可自定义);撞上轮数上限时,最后收尾轮禁用工具并强制模型给出「已完成 / 未完成任务 / 下一步建议」的结构化交接说明。系统提示词已引导:审阅整个目录/模块、全量审计等大块独立工作用 `task` 拆解,避免主对话被细节撑爆。 - 上下文与缓存:`transformContext` 接入 `compactTranscript`,超预算时用 LLM 生成结构化 `` 摘要(Objective / Work State / Next Move 等)保留工作记忆,已有摘要时增量合并,失败回退保守裁剪;预算内不动前缀以保持缓存命中;usage 记录缓存读/写,`/status` 展示命中率。 - 端点穿插 hooks:`transformContext`(上下文压缩/截断/注入的入口)、`beforeToolCall` / `afterToolCall`、`getSteeringMessages` / `getFollowUpMessages`。 - **运行中注入引导(steering)**:agent 正在跑时,直接在输入框敲补充要求按 `Enter` 即可注入(`Shift+Enter` 换行),当前工具调用结束后、下一轮开始时模型就带上了你的最新指示;输入框上方显示 `⟳ 待注入:…` 小胶囊,被接收时消失。停止仍走主按钮(运行中变为停止图标),不会误注入。 - 两种界面: - CLI `chat`:终端交互会话。 - GUI `gui`:本地 HTTP + SSE 服务器 + 浏览器界面(默认 `http://localhost:9399`),内置「对话 / 轨迹」双页签,轨迹页签可视化每次 run 的工具调用与耗时(见下文「GUI 轨迹功能」)。 - **GUI 插件系统**:`plugins/` 目录下的插件会被自动扫描加载,零侵入主项目代码。当前内建 `pet` 桌宠插件(见下文「桌宠插件」)。 ## 快速开始 要求:Node >= 20。 推荐: - Node 20-22 之间。 ### 初始化环境依赖 ```bash npm install ``` ### 界面 - `chat`:终端交互会话(默认命令)。 - `gui`:浏览器界面,默认打开 `http://localhost:9399`(`--no-open` 可禁止自动打开浏览器,`--port` 改端口)。 > 安全说明:GUI 服务绑定本机 127.0.0.1。启动时会生成一个随机会话 token 注入前端页面,所有 `/api/*` 请求需携带该 token(浏览器自动带),并校验同源 Origin,跨站网页无法伪造请求(防 CSRF / 本地 RCE)。token 会打印在启动日志中,供非浏览器客户端调用 API 时使用。 ## 操作说明 ### 方式一:用内置厂商预设(推荐) 预设已配好接口地址与默认模型,只要设置对应的 API key: | 预设 | 接口地址 | 默认模型 | API key 环境变量 | | --- | --- | --- | --- | | `deepseek` | https://api.deepseek.com | `deepseek-v4-flash` | `DEEPSEEK_API_KEY` | | `kimi` | https://api.moonshot.cn/v1 | `moonshot-v1-8k` | `MOONSHOT_API_KEY` | | `qwen` | https://dashscope.aliyuncs.com/compatible-mode/v1 | `qwen-max` | `DASHSCOPE_API_KEY` | 推荐使用`deepseek v4 flash 0731`。 Windows PowerShell: ```powershell $env:DEEPSEEK_API_KEY = "sk-你的key" npm run dev:gui -- --profile deepseek # 浏览器界面 npm run dev -- --profile deepseek # 终端聊天 ``` bash: ```bash export DEEPSEEK_API_KEY=sk-你的key npm run dev:gui -- --profile deepseek ``` 预设的参数都可单独覆盖,例如换更强的模型: ```bash npm run dev -- --profile deepseek --model deepseek-v4-pro ``` ### 方式二:手动指定(任意 OpenAI 兼容接口) `--api-key` / `--base-url` / `--model` 三件套直接给: ```powershell npm run dev:gui -- --api-key sk-你的key --base-url https://api.deepseek.com --model deepseek-v4-flash ``` 注意:`--base-url` 需要与 OpenAI 兼容格式匹配,程序会拼上 `/chat/completions`(如 DeepSeek 传 `https://api.deepseek.com`,Moonshot 传 `https://api.moonshot.cn/v1`)。Anthropic 接口切 `--api anthropic-messages`,走 `/v1/messages`。 #### 预设 + 自定义接口(中转站 / 私有部署) 保留厂商预设的其它默认,只替换接口地址、模型名、key: ```powershell npm run dev -- ` --profile deepseek ` --base-url https://你的网关地址 ` --model 你的模型名 ` --api-key sk-你的key ``` 同名 flag 优先于环境变量。也可以只用环境变量覆盖预设(切换 base-url 之后需重启进程才生效): | 预设 | API key | 可覆盖的 base-url 环境变量 | 模型名覆盖 | | --- | --- | --- | --- | | `deepseek` | `DEEPSEEK_API_KEY` | `DEEPSEEK_BASE_URL` | 仅 `--model` | | `kimi` | `MOONSHOT_API_KEY` | `MOONSHOT_BASE_URL` | 仅 `--model` | | `qwen` | `DASHSCOPE_API_KEY` | `DASHSCOPE_BASE_URL` | 仅 `--model` | 各预设的模型名只能通过 `--model` 修改。 ### 方式三:环境变量 OpenAI 兼容接口用 `OPENAI_API_KEY` / `OPENAI_BASE_URL`,Anthropic 用 `ANTHROPIC_API_KEY` / `ANTHROPIC_BASE_URL`;厂商预设还有各自的 `<厂商>_API_KEY`(见上表)。 ### 方式四:图形界面里管理接口(推荐给非命令行用户) 打开 GUI → 左下角**设置**齿轮 → **接口**页签,可以**新增 / 修改 / 删除**任意接口(名称、协议 OpenAI 兼容 / Anthropic、接口地址、默认模型、API key),点「测试连接」会真实请求 `/models` 校验地址与 key;点「使用」把该接口设为当前接口(输入框右侧的下拉框也能随时切换)。 配置存放在本机两个**纯文本文件**(就是"内置的那份本地 json"): | 文件 | 内容 | | --- | --- | | `~/.tju-code/providers.json` | 接口条目(id / 显示名 / 协议 / 地址 / 默认模型),带 `schemaVersion` | | `~/.tju-code/secrets.json` | 每个接口的 API key(Windows 下按当前用户账户隔离,仅本机可读) | **key 只存在 `secrets.json` 里,不会写进 `providers.json`。** 内置的厂商预设(deepseek / kimi / qwen)是**只读**的;想改内置预增设的默认地址或模型,编辑它会生成一条**同 id 的用户覆盖**,删除该覆盖即恢复内置默认。 #### 双击启动脚本(.bat)的行为 仓库根目录的 `启动.bat` / `启动-gui.bat` 顶部 CONFIG 区可以填 `API_KEY` / `BASE_URL` / `MODEL`: 1. **第一次双击**:脚本里的 `BASE_URL` / `API_KEY` / `MODEL` 会被**自动导入**成本机接口(第一个叫 `启动脚本`,之后的不同地址/模型叫 `启动脚本·xxx`),存进本机 `providers.json` + `secrets.json`,并自动切到它。 2. **之后双击同一个脚本**:以本机保存的配置为准——你在「设置 → 接口」里对它的任何修改都会被保留(脚本不再覆盖),因此不会每次启动都把你的改动冲掉。 3. **多个脚本累加**:按"地址 + 模型"区分,每个不同的组合各存一条、互不覆盖;双击哪个脚本就自动选中它对应的条目。同地址多模型(如同平台的三个模型各一个 bat)同样各存一条。 4. **完全没有配置过的脚本**(CONFIG 区留空):**依然可以正常启动**,GUI 会打开「设置 → 接口」并提示先配置;在界面里填好接口和 key 后就能直接对话,不需要再改脚本。 > 想让脚本参数**始终优先**(不走导入、每次以脚本为准),在脚本里显式加 `--profile <接口id>` 即可。 > 在界面里**删除**了 `启动脚本` 这个接口,但脚本里 `BASE_URL` 还填着:下次双击会**重新导入**("双击带配置的脚本"即视为你要用它)。想永久弃用,把脚本 CONFIG 区的 `BASE_URL` 清空即可。 ### 多工作项 CLI 与图形界面都支持**多个独立会话(工作项)**并存,持久化在 `~/.tju-code/works/`(每个工作项一个子目录,原子写入 `work-item.json`;可用 `--session-dir` 改变存储目录)。 **GUI**:左侧边栏「工作项」列表展示全部工作项的数量与名称,支持: - **新建**(名称可留空;目录留空用工具启动目录,也可点"选择"打开目录浏览器:盘符/上级/手动输入都可,只列子目录,选定后回填) - **点击切换**工作项(工具工作目录跟着切换,切换提示会告诉你切到了哪个目录) - 每项悬停后 **✎ 编辑名称与目录** / **× 删除**(悬停提示里可看到该项目录;最近用过的目录会留一条快捷回填) **CLI**:会话内命令: | 命令 | 作用 | | --- | --- | | `/work` 或 `/work list` | 列出全部工作项(`*` 标当前) | | `/work new` | 新建工作项并切过去 | | `/work open ` | 打开指定工作项 | | `/work rm ` | 删除工作项(活动项不能删) | **每个工作项都是完整会话**(消息 + todo + 模型),并在**每轮结束后自动保存**(GUI 防抖 1.5s,退出时冲刷,运行中断/进程被杀也不丢对话)。启动自动恢复上次使用的工作项;运行中切换工作项会立即切换到该工作项的完整对话。工作项只保存会话,不包含 run 轨迹(轨迹可随时从「轨迹」页签回放)。 每个工作项还绑定一个**工作目录**(默认工具启动目录,即你双击脚本 / 启动命令所在的目录): - 切换工作项时,工具与审批门同时换到该目录(只影响后续调用,历史对话不变);改当前项的目录即时生效,忙时需等本轮结束。 - **改目录要经过一次确认框**:弹窗里会写明"审批边界随之切换",点"确认变更"才真正生效——这是有意设计的安全确认,防止模型在你不知情时把工作区挪到别处(如 `C:\`)。新建时填了与当前不同的目录,同样要走这一次确认。 - 确认凭证只存在你最初打开的那个浏览器标签页里:**书签 / 新标签页直接打开、确认时报"缺少确认凭证",用最初自动打开的那个页面操作即可**(或复制启动日志里的完整 URL 重新打开)。 ### 目录访问权限 工具(read / write / edit / grep / bash / scan)首次访问**工作目录之外**的目录时,会向你确认授权;"总是允许"记住该目录(含子目录),还可以选"总是允许上级 `D:\xxx\*`"一次覆盖整棵树,避免同级目录反复弹窗;拒绝则本次操作被阻止(模型会收到"未授权"的提示)。bash 命令里出现本机回环地址(`127.x` / `localhost`,如 `curl http://127.0.0.1:9399/`)同样会弹窗确认,防止模型在你不知情时访问本机服务;`fetch` 访问回环地址则直接拒绝(模型传参也绕不过)。`curl 公网地址 -o %TEMP%\...` 这类命令如果弹窗,弹的是环境变量展开后的输出文件目录(如 Temp),属于正常行为,不是误报。`fetch` 是纯网络工具,不读本地文件系统,不受目录权限限制。审批只识别**独立盘符路径**(如 `D:\...`),`redis://` / `https://` 等协议串不会被误判成 `s:\` / `p:\` 而误弹授权框。 - CLI:终端里输入 `y` 允许、`n` 拒绝。 - GUI:页面弹出"允许一次 / 总是允许 / 总是允许上级* / 拒绝"四个按钮。 - 工作目录内的读写不受影响,无需确认。 - **符号链接无法绕过授权**:目录边界判断会先用 `realpath` 解析符号链接、再比较,工作目录内一个指向外部的 junction / 符号链接(如指向 `C:\Users\<你>\.ssh`)**不会**被当成“目录内”而静默放行;弹窗里显示的是**真实物理路径**而不是链接名。所以克隆并直接运行不受信任的第三方仓库时,模型无法靠仓库里的链接把敏感文件读走而不弹窗。(同类缺陷在 Claude Code、Roo Code 等产品上对应 CVE-2025-59829 / CVE-2026-25724 / CVE-2025-58373。) ### 联网安全(fetch 的 SSRF 防护) `fetch` 默认**拒绝访问非公网地址**(环回 `127.0.0.1`/`::1`、链路本地与云元数据 `169.254.x.x`、RFC1918 内网 `10/8` `172.16/12` `192.168/16`、CGNAT、多播等),解析域名后按解析出的 IP 逐一校验,防止模型被网页内容诱导去探测本机、内网或云元数据接口(如 `169.254.169.254`)。确需访问本地或内网服务时,在调用参数里显式传 `allowPrivate: true` 放行。 ### 安全扫描 内建 `scan` 工具把安全检查"左移"到编码阶段:写代码时模型可主动调用,发现风险会在提交前被标记出来。 - **密钥扫描**:正则匹配常见敏感信息(OpenAI/Anthropic key、AWS Access Key、GitHub token、Slack token、Google API key、Stripe key、JWT、PEM 私钥、`api_key`/`secret`/`token`/`password` 赋值等),报告 `文件:行号` 与命中的类别。自动跳过 `node_modules` / `.git` / `dist` 等目录。 - **依赖漏洞检查**:调用 `npm audit --json` 联网查询依赖漏洞;离线或 npm 不可用时降级为内置已知高危包名单(lodash / minimist / glob-parent / nth-check / shell-quote)快查。 - **自动提示**:`write` / `edit` 成功后,工具结果末尾自动追加一行安全提醒,引导模型运行 `scan` 复核后再提交。 ```powershell node dist/cli.js --profile deepseek # 会话里对模型说"扫描项目安全"即可 ``` > 说明:`scan` 是内建工具,由模型在对话中触发;密钥扫描走工作目录,依赖扫描基于当前目录的 `package.json` / `package-lock.json`。 ### 工具集安全性(无工具池攻击面) 本工具的工具集是**完全封闭**的,这是有意设计: - 全部工具在 `createAllTools()` 中硬编码,只有 `read` / `bash` / `edit` / `write` / `apply_patch` / `grep` / `fetch` / `scan` 八个,加会话级 `todowrite` 与 `task`; - 工具 `description` 是源码内的静态字符串,不会在运行时从外部或远程拉取; - 参数由固定的 zod schema 在调用前 `safeParse` 严格校验,未知工具名直接返回 `Tool not found`,模型无法“创造”出新工具; - **没有任何从外部加载或注册第三方工具的机制**(`plugins/` 下的插件只做 GUI 侧扩展、通过 SSE 订阅事件,不参与 agent 的工具选择)。 因此 XTHP(Cross-Tool Harvesting and Polluting,UIUC / NDSS 2026)一类“工具池投毒”攻击的前提——agent 批量导入社区第三方工具、工具描述可被攻击者篡改、模型自主挑选——在本工具中**不成立**。 ### 思考模式与推理强度 DeepSeek 等官方推荐开启思考模式,**默认开启**。开启后请求会附带 `thinking: { type: "enabled" }` 与 `reasoning_effort`(默认 `high`),编程能力显著提升。不需要时用 `--no-thinking` 关闭(CLI 默认强度仍为 `high`,不提供强度开关)。 ```powershell npm run dev -- --profile deepseek --no-thinking ``` CLI 在模型思考(未输出正文)时会显示一行灰色 `[思考中...]` 提示,不会展示思考内容;纯文本轮与 `--no-thinking` 下不出现。 **GUI** 在输入框上方提供「推理」分段控件,四档与官方 `reasoning_effort` 取值一致: - `none`:关闭思考模式(请求发 `thinking: { type: "disabled" }`); - `low` / `high` / `max`:开启思考模式并指定强度,默认 `high`。 两个端点都能用:OpenAI 兼容端点直接发送 `reasoning_effort`;Anthropic 格式端点(如 `dashscope.aliyuncs.com/apps/anthropic`)用 `thinking.budget_tokens` 表达强度(`low` 2048 / `high` 16384 / `max` 32768,且始终小于 `max_tokens`),仅当模型或地址属于 DeepSeek 时额外发送 `output_config.effort`(该端点会忽略 `budget_tokens`),以免影响原生 Anthropic。 选择即时生效,并同时写入 `localStorage`(`tju.gui.effort`)与当前工作项:切换/重启后按工作项各自恢复。旧值 `minimal`/`medium`/`xhigh`/`ultra` 会自动归一到 `low`/`high`/`high`/`max`。 ### GUI 发送方式与设置 右上角设置齿轮(⚙)打开设置弹窗,分「通用 / 接口 / 更新」三个页签: - **通用**:**Enter 发送**(默认关闭):勾选后按 `Enter` 直接发送、`Shift+Enter` 换行;不勾选时按 `Ctrl`(macOS 为 `Cmd`)+ `Enter` 发送。选择即时生效并持久化到浏览器本地(`localStorage`),输入框 placeholder 会同步提示当前快捷键。 - 中文输入法组合态(IME composing)下按 Enter 只确认候选词,不会误发送。 - **接口**:新增 / 修改 / 删除任意接口(名称、协议 OpenAI 兼容 / Anthropic、接口地址、默认模型、API key),点「测试连接」会真实请求校验地址与 key;点「使用」把该接口设为当前接口(顶部输入框右侧的下拉框也能随时切换)。详见上文「方式四」。**不用敲命令**:配接口、换模型、填 key 全在图形界面里点完。 - **更新**:显示当前版本号,一键检查 / 安装 / 回滚,详见下文「GUI 自更新」。**不用拷文件**:有新版点几下按钮、重启服务即完成升级。 运行中的「停止」入口在**发送按钮本身**:运行期间发送按钮变为停止图标,点击即中止当前 run(不再单独设顶栏中止按钮)。 ### GUI 自更新 `dist/` 即发行版。配好更新源后,点设置 → 更新页签即可一键升级,无需手动拷文件。页签里会显示**当前版本号**,五个按钮按状态出现: | 按钮 | 何时出现 | 点完发生什么 | | --- | --- | --- | | 检查更新 | 一直 | 去更新源看有没有新版;有则显示"发现新版本 vX",并出现「立即更新」 | | 立即更新 | 有新版时 | 下载并校验替换文件(按钮变"更新中…"防连点),完成后出现「重启服务」 | | 重启服务 | 更新/回滚完成后 | 拉起新版进程,旧窗口提示可直接关闭;随后出现「刷新页面」 | | 刷新页面 | 重启后 | 重新加载页面(重启后会话 token 已轮换,必须刷新,否则接口全 403) | | 回滚上一版 | 有备份时 | 回到上一个版本(同样走"替换→重启→刷新"三步,可反复横跳) | 规则补充: - **前置条件**:用发行版 `dist` 启动(源码 `tsx` 运行会拒绝更新);启动时配好更新源地址(二选一):启动脚本 CONFIG 区填 `UPDATE_URL`,或 `--update-url <地址>` / 环境变量 `TJU_UPDATE_URL`。没配会直接提示你先配置。 - **任务执行中**点更新 / 回滚 / 重启会被拒绝(等本轮结束再点)。 - **发版流程**(维护者):改 `package.json` 版本号 → `npm run build`(版本号此刻 baked 进包,**改号后必须重新 build 再 pack**)→ `npm run pack:update`(生成 `update/`:`latest.json` + 版本全量文件)→ 把 `update/` 传到静态服务器。客户端用"检查更新"验证 manifest 可达后再通知用户升级。 #### 运行中注入引导(steering) agent 正在执行时,你仍可以直接纠正它——**不需要先停止、再重新提问**: | 场景 | 输入框为空 | 输入框有内容 | | --- | --- | --- | | 运行中按 `Enter` | 无操作 | **注入引导**(`Shift+Enter` 仍换行) | | 运行中按 `Ctrl`/`Cmd`+`Enter` | 无操作 | **注入引导** | | 运行中点击主按钮 | **停止**(图标为方块) | **停止**(语义不变) | | 运行中点击副按钮 | 隐藏 | **注入引导** | - 注入的文本会立刻清空输入框,并在输入框上方显示一个 `⟳ 待注入:<内容>` 小胶囊,表示它已在队列里等待。 - 当前这一轮工具调用跑完后、下一轮开始时,胶囊消失,对话流里出现你的这条消息(用户气泡),模型即按新指示继续。 - 若模型本轮已经打算收尾(不再调用工具),同一条引导仍会在它停止前被取走、模型再走一轮,所以“最后关头”的纠正同样生效。 - 若在你敲回车与请求到达之间这一轮恰好结束,服务端会把它当作一条普通新消息处理,胶囊自动移除,文本不会被吞掉。 鼠标路径:运行中输入内容后,主按钮**左侧**会出现一个箭头副按钮(`→|`),点击即注入。这样主按钮始终保持“停止”语义,避免“想停止却注入”的误操作。运行中输入框 placeholder 会变为“补充引导:Enter 注入(AI 将在下一步收到),Shift+Enter 换行...”。 ### GUI 轨迹功能 浏览器界面顶部栏有「对话 / 轨迹」两个页签,轨迹页签把每次运行(run)可视化为一组**横向轨迹条**,dsh 风格的事件溯源视图: - **实时 run 在最上方**,历史 run 按时间倒序往下排;每条轨迹条头部 = caret + 时间徽标 + 迷你时间线 + 首条用户消息摘要 + 工具数,点击头部展开/收起。 - 展开后是**台账式分轮列表**:每轮一个可折叠区块(轮头 sticky),块内每行 = 全局序号 + 类型标签(USER 绿 / ASSISTANT 蓝 / TOOL 琥珀 pill)+ 文本 + 行尾指标列(Input / Output / Think / Time)。TOOL 行内联 `→ 结果`(运行中/成功/失败三色),ASSISTANT 报错显示红色 `→ 错误详情` 整行标红,纯工具调用的 ASSISTANT 行显示 `调用 工具名(参数)`。 - **点击任意轨迹行可展开完整详情**:行下方内联面板显示该行的完整参数与完整结果(TOOL)或完整文本(USER/ASSISTANT),可滚动、可复制;同一轮内同时只展开一行。完整内容取自内存(历史 run 经事件日志按需回放),页面存储仍只保留截断摘要,不放大本地存储。 - 概览条与迷你条为 Chrome 网络面板式时间线,颜色与台账一致,一眼看出每轮/每个工具调用占用多少时间。 - **右键历史 run 的头部**会弹出上下文菜单,可「删除该 run」,确认后连同其事件日志一并删除(实时 run 无菜单)。 - 轨迹数据全部来自 SSE AgentEvent,并持久化到 `localStorage`(`tju.gui.traj`);刷新页面自动重建当前轨迹。历史 run 的事件日志落盘在 `~/.tju-code/logs`(`--log-dir` 可改,`--log-retention` 控制保留天数),刷新/重连时会回放已落盘事件,进行中的轨迹不丢失。事件日志读取走流式(`node:readline` 逐行),大 run 不整文件载入内存。 - **多工作项**:`~/.tju-code/works` 持久化每个工作项的完整会话(消息 + todo + 模型),启动自动恢复上次工作项;工作项在**每轮结束后自动保存**(防抖 1.5s,退出时冲刷),运行中断/进程被杀也不会丢对话;切换工作项时对话区按核心 transcript 重建——用户消息、思考+回答、以及按 `toolCallId` 配对的**工具块(参数+结果)**都会还原,不会丢失工具调用信息;历史 run 的完整轨迹仍可从「轨迹」页签回放。 ### 桌宠插件(pet) GUI 内建一个 Live2D 风格的桌宠,位于浏览器窗口右下角,随对话实时变化: - **状态联动**:订阅 SSE AgentEvent 流,自动匹配当前工作状态——思考、读文件、写代码、敲终端、搜索、完成、出错等,每种状态对应不同表情精灵图。 - **表情动画**:12 秒 phase 轮换,工作/干饭交替,闲置 5 分钟打哈欠、15 分钟睡觉。 - **气泡文字**:头顶气泡显示当前状态标签与详情,流式输出时滚动展示,10 秒无活动自动隐藏。 - **交互**:鼠标拖拽移动位置,滚轮缩放(65%-140%),位置/缩放持久化到 localStorage。 - **可插拔**:放在 `plugins/pet/` 目录下,删除该目录即可完全移除,不影响主项目。 ``` plugins/pet/ ├── pet.js # 插件入口(状态机 + 渲染 + 拖拽 + SSE 订阅) └── assets/ # 30 个 WebP 精灵图(从 deepseek-pet-main 移植) ``` ### CLI 命令 终端会话内可用斜杠命令(`/help` 查看完整说明): | 命令 | 作用 | | --- | --- | | `/exit` | 退出(若正在运行先终止) | | `/clear` | 清空对话记录 | | `/abort` | 取消当前一轮 | | `/model ` | 热切换模型(下一轮生效),如 `/model deepseek-v4-pro` | | `/status` | 显示模型、thinking 开关、消息数、token 与缓存命中 | | `/work` | 工作项管理:`list` / `open ` / `new` / `rm `(详见「多工作项」一节) | | `/help` | 完整帮助 | 运行期间按 Ctrl+C:一次取消当轮,再按一次退出。 ### 上下文与缓存 - 上下文默认预算 128K token(`--max-context-tokens ` 可调)。超出后自动用 LLM 把旧历史压缩为结构化 `` 摘要(Objective / Important Details / Work State·Completed·Active·Blocked / Next Move / Relevant Files),保留最近约 8K token 原文,摘要落回会话以保持前缀缓存稳定;已有摘要时增量合并。摘要生成失败时回退为保守裁剪(超大单条截断 + 按整轮丢弃最旧 turn)。预算内完全不动,保证 DeepSeek 等前缀缓存的命中。 - 若 provider 因上下文超长拒绝请求(`context length` / `prompt is too long` 等),会自动强制压缩并重试一次,不再直接报错中断。 - 每个 assistant 消息的 `usage` 记录 `cacheRead`(命中)/ `cacheWrite`(写入),`/status` 展示累计命中数与命中率。 ### 构建与验证 ```bash npm run build # 产物 dist/,bin 指向 dist/cli.js npm run typecheck npm test # 18 个测试文件 / 176 项(1 项仅 POSIX,Windows 跳过) ``` ### 使用编译产物(dist/) `npm run build` 后,`dist/` 里就是可直接发布的产物,两个入口: - **CLI(dist/cli.js)**:不经过 tsx 直接跑编译后的命令,效果与 `npm run dev` 一致: ```powershell $env:DEEPSEEK_API_KEY = "sk-你的key" node dist/cli.js --profile deepseek # 终端聊天 node dist/cli.js gui --profile deepseek # 浏览器界面 node dist/cli.js --help # 查看全部参数 ``` 想**全局安装**、在任意目录敲 `tju-code`: ```powershell npm link # 把 dist/cli.js 注册为全局命令 tju-code tju-code --profile deepseek # 不再需要时:npm unlink tju-code ``` 换机器部署时,把 `dist\` 整个拷走即可(`start` 已把 `zod`/`zod-to-json-schema` 直接打进单文件,**自包含、无需安装依赖**;`dist\cli.js` 也带 shebang,可执行)。 也可以直接用仓库根目录的**一键启动脚本**(脚本顶部 CONFIG 区直接填 API_KEY / BASE_URL / MODEL 等,无需设环境变量): | 脚本 | 平台 | 用途 | | --- | --- | --- | | `启动-dist-gui.bat` | Windows | 浏览器 GUI(推荐) | | `启动-dist.bat` | Windows | 终端 chat | | `启动-dist-gui.sh` / `启动-dist.sh` | Linux/macOS | 浏览器 GUI / 终端 chat(`chmod +x` 后执行) | | `启动.bat` / `启动-gui.bat` | Windows | 自动检测 dist,缺失时回退 tsx 跑源码 | 每个脚本顶部 CONFIG 区变量作用相同: | 变量 | 默认 | 说明 | | --- | --- | --- | | `API_KEY` | 空 | 你的 API Key(**首次双击**会导入到本机配置,之后可在「设置 → 接口」里改) | | `BASE_URL` | 空 | 接口地址(**留空也能启动**,进界面里配置即可;URL 含 `anthropic` 自动走 Anthropic 协议,否则 OpenAI) | | `MODEL` | 空 | 模型名(留空用默认) | | `WORKDIR` | 空 | 智能体的工作目录(留空 = 脚本所在目录) | | `PORT` | `9399` | GUI 端口(仅 gui 脚本) | > bat 脚本双击运行,结束/报错时会 `pause` 等待按键;路径含中文可正常使用(脚本已 `chcp 65001` 切 UTF-8)。 > 脚本里的 `API_KEY` / `BASE_URL` 是**首次导入**用的引导值:导入后以本机 `~/.tju-code/providers.json` 为准,界面里的修改不会被下次双击覆盖(详见上文「方式四」)。 - **库(dist/index.js + index.d.ts)**:作为编程接口被其他项目引用。 ```js // 本地相对导入: import { createAgent, runAgentLoop, PROVIDER_PROFILES } from "./dist/index.js"; // 安装/ link 后按包名导入: // import { createAgent } from "tju-code"; ``` 导出内容以 `dist/index.d.ts` 为准(`createAgent` / `Agent` / `runAgentLoop` / 工具工厂 / `PROVIDER_PROFILES` 等)。 > 注意:`dist/` 是独立打包的单文件,别把里面单个文件拆走单独使用;整包移动即可,无需依赖 `src/`。 ## 命令与参数 ``` tju-code [command] [flags] Commands: chat 交互终端会话(默认) gui 浏览器 UI,http://localhost:9399(默认自动打开浏览器) version / help Flags: --api --profile --provider --model --api-key --base-url --no-thinking 关闭思考模式(默认开启) --max-tokens 单次输出上限 --max-context-tokens 上下文预算(默认 128000,超出自动压缩) --max-turns 每轮 run 的最大 turn 数(默认 50,超出强制收尾) --port (gui) --no-open --update-url 自更新源地址(GUI 设置→更新页用;也可配 TJU_UPDATE_URL) --log-dir 运行事件日志目录(默认 ~/.tju-code/logs) --log-retention 事件日志保留天数(默认 7) --session-dir 工作项持久化目录(默认 ~/.tju-code/works) ``` 环境变量:`OPENAI_API_KEY` / `OPENAI_BASE_URL` / `ANTHROPIC_API_KEY` / `ANTHROPIC_BASE_URL`,厂商预设各自的 `<厂商>_API_KEY`。 ## 架构 数据流: ``` CLI/GUI -> Agent (stateful) -> runAgentLoop -> ai layer (OpenAI/Anthropic adapter) -> EventStream ``` 三层消息模型: 1. `GroundEvent` - AI 层产物:`start` / `thinking_delta` / `text_delta` / `toolcall_*` / `done | error` 2. `Message` - 核心层持久化消息:`UserMessage` / `AssistantMessage` / `ToolResultMessage` 3. `AgentEvent` - UI 事件:`message_*` / `turn_*` / `tool_*` / `agent_*` 核心类型与契约集中在 `src/core/types.ts`,AI 层契约在 `src/ai/types.ts`。 ## 扩展点 - **添加 provider**:实现 `src/ai/types.ts` 的 `ProviderAdapter`(`api` + `stream`),在 `src/ai/index.ts` 注册。 - **自定义工具**:`src/core/types.ts` 的 `AgentTool`,用 `zod` 给出 `parameters` 即可被 `createAgent` 使用。 - **运行钩子**:见 `src/core/types.ts` 的 `AgentLoopConfig`,特别是 `transformContext`--上下文压缩/截断/注入的唯一入口(现接入 `compactTranscript`,LLM 结构化摘要压缩)。 - **GUI 插件**:在 `plugins/` 下创建子目录,放入 `pet.js` 入口文件,启动时自动扫描加载。插件通过 SSE 订阅 AgentEvent,可自由扩展 GUI 行为。 ## 目录 ``` src/ ai/ 统一 AI 层(openai / anthropic / sse / utils) core/ agent 运行时(agent / agent-loop / event-stream / types / tools) gui/ HTTP + SSE 服务器与内嵌前端 cli/ chat 交互会话 config/ create-agent -> CLI 组装 plugins/ pet/ 桌宠插件(纯前端,零侵入主项目) ``` ## 研究机构 - 天津大学系统安全与可信计算研究中心