AI Decision System
Replayable, offline-verifiable records for AI-assisted go/no-go decisions: experiment promotions, typed model answers and human-approved actions.
생각 트리 캔버스 채팅 (Claude / Codex 기반, Next.js)
Tree-of-thought canvas chat (Claude / Codex backed, Next.js)
개발 도구TypeScript · ShellGitHub @SmokingMouse직접 설치운영자 미확인
써 보셨어요? 한 번만 눌러 주세요. 로그인은 필요 없어요.
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
설치형 프로젝트는 배포 도메인 없이도 확인할 수 있습니다. 개인 저장소는 GitHub 로그인으로, 조직 저장소는 위의 저장소 확인 파일로 직접 확인해 주세요.
README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
树状的 AI 对话 — 把"线性聊天"撕开成可分叉、可回跳、可聚焦阅读的思维树, 同时和你本机的 Claude Code / Codex CLI 会话 双向打通 。 1. 安装依赖:make setup 2. 启动服务:make dev 3. 访问地址:http://localhost:3000 前置依赖和进阶说明见下方 Quickstart。 Project 模式默认的 线性 thread 视图 —— 连续阅读 + 行内分叉「↳ N 个分支」+「在 CLI 继续」+ 右下角 树缩略图 导航 行内分叉:线性视图里真分叉折成「↳ N 个分支」,点开就地切到那条 lineage 笔记本:选中 → 📌 进抽屉,点笔记跳回原节点 + 闪烁定位 Wide tree:55+ 节点的真实使用形态,深度学习 / 个股研究都能撑住 1. 想顺手追问一句细节,整个上下文就跑偏了。 你被迫在「问完再回主线」和「假装没看见」之间二选一。 2. 长会话尾段越聊越钝。 Claude/GPT 的实际有效上下文比标称小得多,一窗到底等于反复让模型在长 prompt 里做摘要。 Trellis 的处理方式:每个回答都是一个节点,选中文字 → ⌘K 即可在那一句旁边长出分叉子节点,原会话不动。每个分支独立流式生成、独立持久化、独立可回跳,画布上呈现整棵树。README 전문 보기 ↗
make setupmake devhttp://localhost:3000前置依赖和进阶说明见下方 Quickstart。
| 树状画布 — 思维树概览 | 聚焦阅读 — 锚点回跳 | 参考材料 — 飞书 / YouTube / 网页通吃 |
|---|---|---|
行内分叉:线性视图里真分叉折成「↳ N 个分支」,点开就地切到那条 lineage
选区分叉:在任意回复里选一段 → ⌘K 长出子节点
笔记本:选中 → 📌 进抽屉,点笔记跳回原节点 + 闪烁定位
Wide tree:55+ 节点的真实使用形态,深度学习 / 个股研究都能撑住
线性聊天有两个老问题:
Trellis 的处理方式:每个回答都是一个节点,选中文字 → ⌘K 即可在那一句旁边长出分叉子节点,原会话不动。每个分支独立流式生成、独立持久化、独立可回跳,画布上呈现整棵树。
而且它不是另起炉灶——Trellis 把请求转交给你本机的 claude / codex CLI 子进程,并且能把 CLI 已有的会话 attach 进来双向同步:CLI 里聊的、trellis 里聊的,落的是同一份 transcript,两边都看得到、都能续。
适合:长篇研究式问答(个股深度研究、技术学习、文档导读)、需要保留多条思路的探索、需要随时回到某个引用处对照的阅读,以及——把你在终端里跑的 Claude Code 会话搬进一个能浏览/搜索/分叉的工作台。
▶ N / ▼ N 角标一键收起后代;折叠状态按 session 持久化F)、切 active 平滑 pan<mark>,点击跳到子节点;子节点全屏顶部常驻「从『…』分叉」横幅,B 一键回父节点引用处 + emerald 闪烁定位详见 下方专章。这是 Trellis 区别于一般 AI 客户端的核心。
~/.claude/projects 或 $CODEX_HOME/sessions 里的已有会话,手选 attach 进 Trellis(不按目录批量灌)/branch ↔ trellis 分叉双向对齐(一棵 trellis 树 = 一组 CLI session 按 message uuid 求并集)。trellis 从任意历史节点分叉 → 构造前缀 jsonl,在 CLI 侧也成为独立可 --resume 的会话claude --resume <id> 或 codex resume <id> 命令,回终端无缝接着聊session 创建时锁定 mode + workspace + model(per-session 持久化,切走再回来不会变),顶栏只读 badge 显示。详见 下方专章。
| 模式 | 类比 | cwd | Tools / Skills / MCP | 跨节点记忆 | 适合 |
|---|---|---|---|---|---|
| Chat | GPT 网页客户端 | 无 | Claude:WebSearch + WebFetch;Codex:内置 cached web search + read-only;⚡ 增强模式 = scratch 目录 + 全工具 | 无 | 默认日常问答、补认知、查信息;临时跑个命令 / skill 开增强 |
| Project | Claude Projects / 长期项目 | session 绑定 | 全开 | 有(沿祖先链,分支间隔离) | 在仓库里干活、跨节点延续记忆,cache 命中通常 70%+ |
Provider 可切 Claude(Sonnet / Opus / Haiku)/ Codex(OpenAI)/ Mock(确定性假回复,看 UI 用),两档语义对齐。
tool_use / tool_result,在节点折叠区展示每次 Bash / Read / Write / WebFetch 的入参与输出AskUserQuestion / ExitPlanMode 时渲染成可填表单(单选/多选/批准计划),就地作答回灌/ 前缀触发 —— /new /clear(🧹 开新话题/清空上下文)/archive /switch /model 等 session 元操作,纯 Trellis 命令本地执行不发 LLM/<skill-name>;Codex 输入 $<skill-name>,并按 .agents/skills、~/.agents/skills、$CODEX_HOME/skills 等作用域发现(纯 Chat 点选自动开启增强模式)@slug 单轮调用⌘1-9 快切 + live 状态点(streaming / done / error)~/.trellis/blobs/<sha256>.<ext> 自动去重,不进 SQLiteEsc 或卡片 ⏹ 中止aborted 灰色态),原 prompt 回填便于编辑重试#N + 未读小圆点(停留 1s+ 自动标已读)+ J/K 跳未读 + Done Toast| 键 | 行为 | 作用域 |
|---|---|---|
⌘K | 选区分叉 popover | 文本选中时 |
⌘D | 选区入笔记本 | 文本选中时 |
⌘P | 跨 session 全文搜索 | 全局 |
⌘↩ / Enter | 发送(默认 Enter,可切 ⌘Enter) | textarea |
Esc | 关 popover / 中止流 / 关弹窗 | 全局 |
J / K | 下/上一个未读节点(环绕) | 全局 |
Alt + 方向 | 节点导航:上=父 / 下=首子 / 左右=兄弟 | 全局 |
⌘1-9 | 切到第 N 个 session tab | 全局 |
F | Fit canvas to viewport | 画布 |
B | 回父节点锚点引用处 | 全屏阅读且有父节点时 |
~/.trellis/data.db(WAL,启动自迁移),全部 session / 节点 / 引用 / 笔记 / 节点位置持久化~/.trellis/data.db),单人单机。claude / codex CLI 子进程——订阅 / 余额 / 模型权限完全跟 CLI 走,Trellis 不存任何 API key。proxy.ts)。npm i -g @anthropic-ai/claude-code → claude 可用并已登录codex login 完成登录brew install ttyd tmux;Debian/Ubuntu:apt install ttyd tmuxMock,返回固定假回复,仅用于看 UITrellis 的 LLM/CLI 运行时层是普通 npm 依赖(@smokingmouse/agent + @smokingmouse/llm,源码在 sm-toolkit),bun install 一步装完,无需额外 clone/build 任何仓库。
git clone https://github.com/SmokingMouse/trellis.git
cd trellis
make setup # = bun install + 前置检查
make dev
只想看当前环境缺什么、不装任何东西:make check。
若未安装 ttyd,Trellis 仍可启动,但 Web 终端不可用;启动/部署日志与终端面板会提示 ttyd 缺失和对应平台的安装命令。Trellis 不会自动执行安装命令。
唯一的运行时注意点:必须用 bun --bun run dev(make dev/make build/make start 已经这么做了)——bun run 不会把 Next/Turbopack 内部起的 worker 进程纳入 bun 运行时,导致 bun:sqlite(lib/server/sqlite.ts 用)解析不到。
打开 http://localhost:3000,第一次输入问题即创建 session。想 attach 已有 CLI 会话:左侧 sidebar →「Attach CLI 会话」。
原生 claude / codex 走 CLI 登录态,零配置。想接 Anthropic 兼容的第三方端点(deepseek / kimi / ark …):
mkdir -p ~/.config/sm
cp node_modules/@smokingmouse/llm/endpoints.example.yaml ~/.config/sm/endpoints.yaml
# 编辑它:填你自己的 provider / 模型清单,API key 只写环境变量名,key 本体放 env
搜索顺序 $SM_ENDPOINTS_PATH → ~/.config/sm/endpoints.yaml → ~/.claude/global/endpoints.yaml(legacy)。改完重启即出现在模型 picker。
要改 sm-toolkit 本体时:clone 到 ~/sdk,make link-sdk 把两个包软链过来(bun install 会冲掉软链,重跑即可);make unlink-sdk 回到 registry 版本。
单机随手跑一把:
make build && make start # 固定 -p 3088;等价 bun --bun run build && bun --bun run start -- -p 3088
数据落 ~/.trellis/data.db(SQLite WAL,自动迁移)。卸载只需删掉这个目录。
如果 Trellis 是常驻服务(launchd / systemd),不要在它正在运行的那个目录里 make build 再重启:next build 会就地改掉运行中的 .next,build 失败就把服务打成半死,build 成功却忘了重启则是「内存里旧模块 + 磁盘上新文件」混跑(页面能开但交互全挂)。而且没有回滚路径。
改用 make deploy:
make deploy # 部署当前 HEAD
make deploy REF=v1.2.0 # 部署指定 ref
make rollback # 切回上一个 release
make deploy-status # 看 current / previous 与上次部署状态
它做的事:把目标 commit 导出到 ~/.trellis/releases/<sha>/ 里装依赖并 build(全程不碰正在跑的服务)→ 用真数据库的一致性快照起一个临时实例,验证它真的能启动、能出页面、能读现网数据 → 备份数据库 → 原子换 ~/.trellis/current 软链 + 重启 → 验活;验活不过自动回滚到上一个 release。
任何一步失败都不会动到正在跑的版本。实测切换窗口 ≈ 0.2s,且期间返回的是维护页而不是连接被拒——网关进程先占住端口、再拉起 Next,Next 起不来时它不会跟着自杀,而是退避重启并出 503 维护页(未登录只显示「正在更新」,版本号与日志要登录后才看得到)。GET /__gate/health 给出网关与 Next 的真实状态。
首次启用需要把常驻服务的工作目录指向软链,一次性:
make deploy # 先建出第一个 release
make install-service # 把常驻服务的工作目录改成 ~/.trellis/current(会先备份原定义文件)
之后仓库目录就只是开发用的 checkout 了,在里面 build 不再影响线上。应急回滚不依赖仓库:~/.trellis/bin/rollback.sh。
macOS / Linux 都支持:重启走 launchd(launchctl kickstart -k)还是 systemd user unit(systemctl --user restart)按平台自动判定,unit 名默认取 label 的最后一段(com.smokingmouse.trellis → trellis.service,用 TRELLIS_DEPLOY_UNIT 覆盖)。make deploy-status 会把认到的那套连同它当前的工作目录一起打出来。
两种情况
make deploy会拒绝执行,都可以FORCE=1强行继续:① 有会话正在生成(切换会中断它们,会列出是哪些);② 常驻服务的工作目录不是~/.trellis/current(这种状态下重启只是让服务在原目录里重来一遍,跟部署的那个 sha 没关系)。
部署产物(release / 数据库 / 配置)全部落在 ~/.trellis/,代码里没有写死的机器路径;真正跟机器走的是宿主机状态:bun、已登录的 CLI、常驻服务定义、以及不进 git 的 .env.local。按顺序补齐:
装依赖:bun(必须,运行时用了 bun:sqlite/Bun.serve,Node 跑不了)→ claude CLI 并 claude login → 可选 codex / ttyd / tmux。make check 随时报缺什么。
clone + make setup。
建运行期配置 ~/.trellis/shared/.env.local(shared/ 下的文件会被软链进每个 release,不随 release 清理丢失):
TRELLIS_AUTH_PASS=<登录密码>
TRELLIS_AUTH_TOKEN=<随机长串,cookie 会话令牌>
TRELLIS_REPO_DIR=<trellis 仓库 checkout 的绝对路径>
# 可选:agent-server 主页收编(TRELLIS_AS_ADOPT),配置说明见下
TRELLIS_AS=off
前两个任一缺失 = 认证闸静默关闭。机器只在内网/Tailscale 里可以不配;挂公网隧道必须配——Trellis 能 spawn CLI 在宿主机执行任意代码,这道闸是唯一的门。TRELLIS_REPO_DIR 给应用内「检查更新/更新到最新」用(release 目录不是 git 仓库,部署脚本要回到 checkout 里跑)。
装常驻服务定义(模板见下),WorkingDirectory 先指向仓库 checkout,并在 checkout 里 make build 一次让它有东西可跑,然后加载服务。
首次部署:make deploy FORCE=1——此时服务工作目录还不是 ~/.trellis/current(正是下一步要改的),preflight 会拦,首次明知故犯一次。
make install-service:把服务定义的工作目录改到 ~/.trellis/current(自动备份原文件并重载)。之后 make deploy 不再需要 FORCE,仓库目录退化为纯开发 checkout。
验证:make deploy-status;curl http://127.0.0.1:3088/__gate/health 应给出 next=ready 且 auth=on。
主页收编与 project 切流共用启用判定:TRELLIS_AS=off 优先级最高,即使设置了 socket 也关闭;否则 TRELLIS_AS=on 或设置 TRELLIS_AS_SOCKET 即启用。另设 TRELLIS_AS_PROJECT=on 才给新 project 会话打 thread 标记。只关 PROJECT 会停止新会话打标,已绑定会话继续使用 daemon;关 AS 则 project 新请求带 notice 回退兼容模式,保留输入与祖先历史,外部会话保留历史并拒绝发送。pane 绑定仍由 herdr-bridge 接线。
先独立启动 agent-server daemon,再在开发用 .env.local 或部署用 ~/.trellis/shared/.env.local 配置下表变量并重启 Trellis。可参考仓库的 .env.example。设置 TRELLIS_AS_ADOPT=on 后,在主页查看并继续外部会话,审批与系统日志也在主页呈现。
| 变量 | 含义与默认值 |
|---|---|
TRELLIS_AS | 默认不启用;on 启用,off 是最高优先级硬关闸,即使已配置 socket 也禁用。未设置时,非空 TRELLIS_AS_SOCKET 会自动启用。 |
TRELLIS_AS_PROJECT | on 时为新 project 会话绑定 daemon thread,默认关闭。已绑定会话由 TRELLIS_AS 控制。 |
TRELLIS_AS_ADOPT | 默认 off;设为 on 自动收编 daemon 上至少有一个 turn、尚未绑定且非 closed 的外部线程。TRELLIS_AS=off 覆盖它。 |
TRELLIS_AS_PROJECT_ID | 可选的精确 projects.id,只允许该项目所属工作区的新会话绑定 AS;未知归属保持 legacy。未设置时保留全部新 project 会话可切流的行为;停流请关闭 PROJECT 或 AS,不要只删此筛选值。 |
TRELLIS_AS_SOCKET | daemon Unix socket 的绝对路径。未设置时遵循 agent-server 路径规则:AGENT_SERVER_SOCKET_PATH 优先,其次绝对 XDG_RUNTIME_DIR 下的 sm-toolkit/agent-server.sock,其次绝对 XDG_STATE_HOME 下的同一路径,最后为 $HOME/.sm-toolkit/agent-server.sock。 |
TRELLIS_AS_TOKEN_PATH | 已运行 daemon 的 token 文件绝对路径,不是 token 内容。默认绝对 XDG_STATE_HOME 下的 sm-toolkit/agent-server/token,否则 $HOME/.agent-server/token。自定义 socket 不会自动改变 token 路径,两个配置需指向同一个 daemon。 |
连接失败不会阻断 Trellis 启动;收编扫描器按下述策略退避。主页权限与系统日志通过节点 SSE 订阅 daemon 快照与通知,保留断线续传、15 秒保活与慢消费者保护。主页与接口沿用 Trellis 现有鉴权闸;这些变量仅在服务端读取,不要使用 NEXT_PUBLIC_ 前缀。
实时审批依赖 daemon 的 pendingRequests 能力:首次 attach/断线重连用快照初始化,随后订阅 thread/pendingRequests;project 的可操作表单来自 server request。静默时不重复推送历史;旧 daemon 应升级后使用实时审批。
project 从最新空闲节点续聊复用 thread;早期节点续聊、显式 fork、重试,或原 thread 已被其他客户端推进时,需要新 thread。daemon 声明 midThreadFork 时,以 as_turns.last_item_id 为边界发送 thread/fork {fromItemId},新历史只含所选节点之前的完整前缀。缺少该能力才把所选祖先历史播种到新 thread;有能力但边界无效时直接报错,不静默退回播种。无 daemon 映射的兼容历史仍需播种。
客户端随仓库 vendor,来源固定在 vendor/agent-server/VENDORED_FROM;刷新用 SM_TOOLKIT_DIR=/path/to/sm-toolkit sh scripts/vendor-agent-server.sh 后执行 bun install。复核命令:bunx tsc --noEmit、bun test、scripts/mobile-verify/mobile-as-project.sh、scripts/mobile-verify/mobile-as-adopt.sh;移动验收只用 mock 引擎与脚本锁定的隔离端口。
外部会话收编由 TRELLIS_AS_ADOPT=on 单独开启,不依赖 PROJECT 开关或 PROJECT_ID 灰度范围。启动发现线程并读取首次快照,健康时每 1.5 秒用一次 thread/list 发现新线程(超过协议每页 10000 条才分页);已订阅线程由通知标记变化,只有新增、摘要变化或收到通知的线程按已存 sinceSeq 增量 attach,无变化时零 attach。故障按 2–30 秒指数退避,重连从已投影 cursor 补齐断线变化;关闭/删除线程清理观察缓存。关闭 ADOPT 不启动扫描器,不向 daemon 发出收编请求。零 turn 线程持续观察但不建会话,首个 turn 出现后才收编;扫描快照及 turn 通知都可识别首轮。
每轮 thread/list 底噪随历史累计线程数(含 closed)线性增长,按二审实测约 160 条线程后单轮超过 100 KB,已记 backlog。
非 closed、未绑定的线程按 realpath 后 cwd 的最长包含真实 workspace 根归属;真实根必须有 .git 文件/目录,或是 Herdr 已知的 repo/worktree。非 git 目录项目与系统 home/scratch 根均不参与,cwd 精确等于系统根也不复用其归属。非 git 目录项目如「投研」不参与归属,其下外部线程进入「外部会话」。没有真实匹配时自动创建唯一的系统项目「外部会话」(cluster_key=trellis:external)。若 cwd 已被其他非真实 workspace 占用,则使用外部项目的独立归组 workspace,实际执行目录仍保存在 sessions.workspace_path;不移动已有 workspace 或其中的会话。会话及节点携带 origin=external、backend,标题保留 meta/fjContext 中的标题或契约号。
主页提问复用原 thread,审批竞答、中断和分叉沿用 project 路径;正在运行的轮次不能被静默覆盖。外部线程关闭后,主页保留历史并显示已结束,拒绝新提问。Trellis 不会调用 thread/close 收编线程;删除会话只解绑,as_adoptions 留永久墓碑,即使外部再有新 turn 或 Trellis 重启也不会重新收编。关闭 ADOPT 不删除已收编历史;需要回退时设 TRELLIS_AS_ADOPT=off 并重启。整体关闭可设 TRELLIS_AS=off,收编会话保持历史并拒绝发送,不会另起兼容引擎。
as/1 的 attach 提供 items、不提供历史 Turn 记录;回填文本和工具项保持原投影格式,历史状态从 items 与线程状态恢复,不虚构历史 usage。新 turn 的生命周期通知用于完成状态和 usage。验证脚本 scripts/mobile-verify/mobile-as-adopt.sh 使用隔离 fixture daemon、数据库副本、3479/3480 与共用互斥锁,清理所有自建进程并输出手机截图。
launchd(macOS):存为 ~/Library/LaunchAgents/com.smokingmouse.trellis.plist(label 可用 TRELLIS_DEPLOY_LABEL 换),launchctl bootstrap gui/$(id -u) <plist路径> 加载。把 /Users/YOU 全部换成真实家目录——plist 不展开 ~:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.smokingmouse.trellis</string>
<key>ProgramArguments</key>
<array>
<string>/Users/YOU/.bun/bin/bun</string>
<string>--bun</string><string>run</string><string>start</string>
<string>--</string><string>-p</string><string>3088</string>
</array>
<!-- install-service 只改这一行;首次先指向仓库 checkout -->
<key>WorkingDirectory</key>
<string>/Users/YOU/path/to/trellis</string>
<key>EnvironmentVariables</key>
<dict>
<key>HOME</key>
<string>/Users/YOU</string>
<!-- launchd 不继承 shell 环境。spawn claude/git/ttyd 全靠这条 PATH:
必须含 claude CLI 所在 bin(npm -g 的目录,nvm 下形如
~/.nvm/versions/node/vXX/bin)和 Homebrew bin。 -->
<key>PATH</key>
<string>/Users/YOU/.bun/bin:/Users/YOU/.nvm/versions/node/vXX.X.X/bin:/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin</string>
<key>NODE_ENV</key>
<string>production</string>
</dict>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>/Users/YOU/Library/Logs/trellis.log</string>
<key>StandardErrorPath</key>
<string>/Users/YOU/Library/Logs/trellis.log</string>
</dict>
</plist>
macOS 已知雷:claude CLI 的凭证默认存 keychain,launchd 会话读 keychain 可能读到失效凭证——症状是终端里
claude一切正常、Trellis spawn 出的却报未登录。修法:删掉 keychain 里的 Claude Code 凭证条目让 CLI 回退到~/.claude/.credentials.json文件存储,重新claude login。
systemd user unit(Linux):存为 ~/.config/systemd/user/trellis.service,systemctl --user daemon-reload && systemctl --user enable --now trellis。ssh 登出后要继续跑:loginctl enable-linger $USER(缺它连 systemctl --user 都不可用,部署 preflight 会报 XDG_RUNTIME_DIR 相关错误):
[Unit]
Description=trellis
After=network.target
[Service]
# install-service 只改这一行;首次先指向仓库 checkout
WorkingDirectory=%h/path/to/trellis
ExecStart=%h/.bun/bin/bun --bun run start -- -p 3088
# systemd 不继承 shell 环境。PATH 必须含 claude CLI 所在 bin。
Environment=PATH=%h/.bun/bin:%h/.local/bin:/usr/local/bin:/usr/bin:/bin
Environment=NODE_ENV=production
Restart=always
RestartSec=2
[Install]
WantedBy=default.target
Trellis 把每个 Project 会话当成真实 CLI thread:Claude transcript 位于 ~/.claude/projects/<encoded-cwd>/<session-id>.jsonl,Codex rollout 位于 $CODEX_HOME/sessions/YYYY/MM/DD/*.jsonl。在此之上做了三件事:
cli-import,跑在 Project 模式instrumentation.ts 启动)监听 attached jsonl 所在目录,文件一变(CLI 侧聊了新轮 / rewind / /branch)→ debounce 后增量重导 → SSE 推前端无刷新 reloadCLI 的单条会话是线性的,Trellis 的是树。统一模型:一棵 Trellis 树 = 一组 CLI thread(root + 各 fork)的 lineage 并集。
/branch / --fork-session 或 Codex fork 产生的新 transcript → Trellis 自动按共享前缀归并到同一棵树/clear 与 /compact 怎么对应/clear(CLI 抹掉上下文、开新 session id)→ 那是另一份不共享 uuid 的 jsonl,trellis 不会自动并进当前树(它是「不相关的新对话」),而是作为一条独立可 attach 的会话出现。等价物 = trellis 的「🧹 新话题」(同树里加一个全新上下文的根)/compact(同 session 摘要压缩、不换 jsonl)→ 在原 jsonl 追加一个 type:"system" 边界节点,对话继续。trellis 读全量 jsonl,边界前后所有原始轮都完整显示(compact 只压缩「模型那侧的窗口」,不动存储)——解析器专门桥接这个 system 边界节点,保证父链不在 compact 处断裂在终端 git worktree add 出来的目录不用重启 trellis 就会被服务端认到,但不是实时——扫描是后台节流跑的,不再挂在侧栏角标请求上(挂上去的代价:一个几十条 workspace 路径都指向同一个大仓的机器上,每次角标请求都要为每条路径同步 spawn 一次 git worktree list,主线程被占满,登录页能被拖到秒级)。
下表是服务端延迟上界——即新 worktree 多久进 DB、多久出现在侧栏接口返回的数据里。浏览器那一侧还要等一次刷新:客户端的侧栏 effect 依赖 sessionsRevision / gitNonce,后台重扫只更新服务端内存 generation,不会主动推给已经打开的静止页面。所以停在页面上什么都不做时,不保证在下表时限内自己冒出来;切回窗口(focus)、发生任何一次侧栏刷新或手动刷新即可见。
| 通道 | 服务端可见延迟上界 |
|---|---|
fs.watch 盯住 <repo>/.git/worktrees(默认,最多同时盯 32 个 repo) | 去抖窗口 + 一轮扫描,通常 1–2 秒(实测 ~1.08s 进 DB 并出现在 listProjectTree) |
| 后台定时兜底(watch 不可用 / 超过 32 个 repo / 该 repo 还没有任何 linked worktree) | 一个扫描周期,默认 60 秒 |
| 环境变量 | 默认 | 说明 |
|---|---|---|
TRELLIS_WORKTREE_RESCAN_MS | 60000 | 后台重扫周期。0 = 不起定时器(只剩 fs.watch) |
TRELLIS_WORKTREE_WATCH_MS | 1000 | fs.watch 事件去抖窗口——git worktree add 会连着触发好几次 |
TRELLIS_GIT_STATUS_CONCURRENCY | 6 | 角标接口同时跑几个 git status。大仓上 6 条并发已经能吃满盘 |
扫描本身按 repo 去重(几十条同仓路径只跑一次 git worktree list),且 <repo>/.git/worktrees 的 mtime 没变时直接复用上一轮结果,一个 git 子进程都不起。侧栏角标请求只读扫描结果,rescan.skipped 恒为 true 明示「本次请求没有重扫」。
session 创建时锁定 mode + workspace,整棵树共用。两档差别在 prompt 怎么组装、CLI 走什么权限、是否绑定 cwd、跨节点要不要共享记忆,trade-off 是「成本 / 权限范围 / 上下文连续性」。
历史注:2026-07-16 砍掉了中间档 Workspace(一次性 CLI、每轮无状态)——实际用量为零,它的两个场景已被别人吃掉:临时跑命令/skill → Chat 增强模式;在仓库干活 → Project。存量 workspace 会话(如有)migrate 时自动归入 Project。
WebSearch + WebFetch,cwd ~,不读 CLAUDE.md / 不加载 skills / MCP / Bash--fork-session,历史活在 fork 出的 CLI session 里,不往 prompt 折叠)~/.trellis/chat-scratch scratch 目录 + 权限全开——能跑 skill / Bash / 文件工具,但不绑定你的仓库/clear)Chat → sandbox read-only,隔离 AGENTS.md / 环境 skills / plugins / MCP,保留 Codex 内置 web search(CLI 默认 cached);增强模式 = scratch 目录 + 全工具Project → $CODEX_HOME/config.toml + MCP/tools/web 全开,一条分支一个可独立 resume 的 rollout lineage,cwd = workspace_pathflowchart LR
subgraph 浏览器["浏览器 (Next.js App Router)"]
UI["React + Zustand<br/>线性 thread / ReactFlow 画布"]
DOM["DOM mark injector<br/>(textNode wrap)"]
end
subgraph 服务端["Next.js Route Handlers (Node runtime)"]
Chat["/api/chat · SSE 推流"]
Refs["/api/references"]
Sync["/api/cli-sync<br/>discover / attach / events"]
Sess["/api/sessions · /api/search"]
end
subgraph CLI["CLI 子进程"]
Claude["claude (stream-json)"]
Codex["codex exec (jsonl)"]
Mock["mock (确定性)"]
end
DB[("~/.trellis/data.db<br/>SQLite WAL")]
JSONL[("~/.claude/projects · $CODEX_HOME/sessions<br/>CLI transcripts")]
UI -- "POST /api/chat" --> Chat
Chat -- "SSE delta" --> UI
Chat -- "spawn" --> Claude
Chat -- "spawn" --> Codex
Chat -- "spawn" --> Mock
Claude -- "stdout JSONL" --> Chat
Refs -- "spawn URL fetch" --> Claude
Watcher["cli-sync watcher<br/>(instrumentation boot)"]
JSONL -- "fs.watch 增量" --> Watcher
Watcher -- "import + SSE" --> UI
Claude -- "resume 写回" --> JSONL
Chat <--> DB
Sync <--> DB
Sess <--> DB
Watcher <--> DB
runtime: nodejs),spawn claude -p … --output-format stream-json / codex exec --json,按行解析 JSONL 转 SSEinstrumentation.ts 启动单例 watcher;Claude/Codex provider parser(jsonl → Q/A 树)+ cli-import-db.ts(union upsert)+ cli-fork.ts(前缀 transcript / lineage 解析)~/.trellis/data.db(better-sqlite3 WAL),schema 在 lib/server/sqlite.ts 启动自迁移<mark>(lib/dom-mark-injector.ts),不改 markdown 源更详细的架构决策见 progress/ 下各 spec(CLI 同步、分支对齐 P1/P2、线性视图等)。
app/ Next.js App Router 页面 + API routes(含 api/cli-sync)
components/ React UI(LinearThreadView / Canvas / NodeFullView / Outline / …)
hooks/ useUnreadNavigation / useCliSyncEvents / useReconnectStreams / …
lib/
collapsed.ts 折叠/祖先链纯函数
layout.ts Dagre 布局 + LoD 阈值
llm/ provider 抽象(claude / codex / mock)
server/
cli-import*.ts CLI jsonl 解析 + DB union 导入
cli-fork.ts 前缀 jsonl 构造 / lineage 解析 / 在 CLI 继续
cli-sync-*.ts watcher + SSE 事件
repo.ts / sqlite.ts DB 访问 + schema 迁移
run-bus.ts spawn 所有权 + durable streams
stores/ Zustand session store(视图 / 折叠 / 流式控制)
progress/ 开发 dashboard + session log + 各 feature spec
instrumentation.ts Next 启动钩子:拉起 CLI 同步 watcher
Alpha,自用为主,已在生产环境跑了一段时间。还存在的粗糙边角:
/branch 也这样)欢迎提 Issue / PR,但请理解定位是个人工具,不会为了泛用化把模型 / 路由抽象到框架级别。
MIT(见 LICENSE)。底层依赖的 Claude Code / Codex CLI 各有自己的服务条款,自行确认。