Tree-of-thought canvas chat (Claude / Codex backed, Next.js)
Translation pending
Developer toolsTypeScript · ShellGitHub @SmokingMouseSelf-installOwner unconfirmed
Paste into Claude or ChatGPT for installation instructions suited to your environment.
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Installable projects can be verified without a deployment domain. Use GitHub sign-in for a personal repository, or the repository verification file above for an organization repository.
Ask about other verification options
Loading the full README. The saved excerpt is shown below.
树状的 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 即可在那一句旁边长出分叉子节点,原会话不动。每个分支独立流式生成、独立持久化、独立可回跳,画布上呈现整棵树。Read the full 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 各有自己的服务条款,自行确认。