v1.8.0 —— 后台子 agent + 回合中插话
springai-agentdemo v1.8.0
在 v1.7.0 基础上的功能版(minor)。核心交付物仍是终端编码智能体 springai-code-tui。
本版主题是**「不用干等」:两件事让你在 agent 忙的时候仍然能动——后台子 agent(派出去的活不占着回合)与回合中插话**(想说的话不用等它跑完)。第三件是记住上次用的模型。
⚠️ 先读这一条:权限行为又变了
BYPASS(跳过全部权限检查)现在进了Shift+Tab的循环,不再需要--dangerously-skip-permissions启动参数。四档平权:默认→自动接受编辑→计划模式→跳过全部权限检查。那个启动参数此前把两件事合并成了一个布尔:启动时能否起步于 BYPASS、运行期能否切进 BYPASS。前者该受启动参数管,后者不该——为了临时放开一次就得重启进程,代价不合理。参数本身保留,语义收窄为「启动即起步于该档」。
这是安全性的净下降:此前不带那个参数启动的进程,运行期无论如何按不进 BYPASS;本版起按三下
Shift+Tab就能进。而 v1.7.0 已经把这一档改成了真的跳过全部检查(内置底线与 ask 规则都不执行,只剩你自己写的 deny 规则)。顺带修掉一句流传在三份文档里的假话:「BYPASS 也盖不住内置底线」。v1.7.0 起它就盖得住了,文档没跟上。
下载物仍是两个自包含运行包(解压即用,无需构建):
springai-code-tui-1.8.0-dist.tar.gz(macOS / Linux 首选)springai-code-tui-1.8.0-dist.zip(Windows 首选)
两者内容一致:启动脚本(bin/)+ 主 jar + 全部运行期依赖(lib/)+ LICENSE/NOTICE/README。运行时界面版本标识为 v1.8.0。
完整功能全景与
⚠️ 安全声明见 v1.3.0 发布说明 与 v1.0.0 发布说明;本文只列出相对 v1.7.0 的变化。
✨ 新功能:后台子 agent(run_in_background)
Task 与 ParallelTasks 多了一个 run_in_background 参数(默认 false,不传时行为与从前逐字一致)。传 true 时它立刻返回一个 task id,子 agent 在一个常驻线程池里跑:主 agent 可以接着调别的工具、接着说话、接着追派任务,你也能立刻提交下一条消息——后台任务不计入 busy 闸门。
> 让 explore 在后台把整个权限层的判定顺序摸一遍,你先接着改 README
⏱ 后台任务已启动 task_3f9ac21b · explore · 摸清权限层判定顺序
好,我先改 README……
结果怎么回来(两条路,互斥)
靠一个「已消费」标记互斥,同一个结果绝不会送两遍:
- 模型主动取——
TaskOutput(task_id),可选阻塞等待。超时返回的是「任务仍在运行」,不是失败:把「还没跑完」说成失败,模型会去重派一个一模一样的任务。 - 自动送达——空闲且输入框为空时程序自动起一个新回合,把已完成任务的结果交给模型。多个任务合成一条通知。
「输入框为空」是这条判据里最实用的一半:你正在打字时它不会插进来抢走回合。想让它现在就送,把输入框清空即可。
界面
- ⏱ 后台任务面板常驻输入框上方,列出全部后台任务(
▶运行 /✓完成 /✗失败 /⊘已终止 + 耗时 + 当前工具)。零任务时不占行。 /tasks打开管理面板:↑↓ 选择、Enter 展开结果、k终止(先确认)、Esc 关闭。任何时候可开——后台任务的意义就在于「回合还在跑的时候也能看一眼」。- 后台任务只有起止各一行进滚动区,它的工具活动与结果正文都不进——否则会一行行插进你与主 agent 的对话里。
Esc取消当前回合不碰后台任务。它们的生命周期与回合无关,这正是它们的用处。要停它去/tasks按k。/clear与退出(/exit与 Ctrl+C)终止全部后台任务,清理有界 2s。
⚠️ 权限:后台任务的 ASK 一律 DENY
这是这个功能最容易踩的地方。 后台任务走同一个权限引擎(判定顺序、规则、内置底线一字不差),差别只在最后一步:引擎判出 ASK 时,前台弹审批面板,后台直接拒绝。
因为它的 turnId 早已过期,审批请求会被 UI 的迟到过滤当场静默拒绝——模型只看到一个没有理由的失败,然后对同一个操作反复重试直到耗光回合。所以这里主动拒绝,并在工具结果里写明原因、当前档位与正确的下一步。这与 BYPASS 档「永远不停下来等人」是同一条设计推理:一个不会停下来等人的执行路径,必须把「等不到人」变成一个模型看得懂、能据此改道的结果。
让后台任务真干活有三条路:事先写窄 allow 规则(精度最高)、切到「自动接受编辑」档(实用的中间档)、或只派只读任务(explore / plan 天然如此)。
/continue 与 ListTasks:让模型也知道有哪些任务
/continue会把当前进程正在跑、以及已完成但结果还没送出去的后台任务一并告诉模型,目的只有一个:别让同一批活跑两遍。ListTasks(新工具,仅主 agent)让模型随时列出本进程的后台任务及状态。为什么需要它:TaskOutput的 task id 只存在于会话历史里那条Task的返回值中,/compact之后就被压掉了——没有它,模型能派后台任务却看不见自己派了什么。- 后台任务不跨进程:
-c恢复上次会话时,上个进程的后台任务已经全部结束(只活在内存里)。此时/continue会提醒模型重新派发,而不是对着早已作废的 task id 干等。
✨ 新功能:回合中插话
一个回合可能有十几轮工具调用、跑好几分钟。而人在工具循环中途开口,十有八九是看它跑偏了想纠正——等事都做完再听,这句话就从「纠偏」降级成了「善后」。
过去只有两个出口:干等到底,或按 Esc 砍掉整个回合(连已经跑对的部分一起丢)。现在有第三条路:不打断回合,把消息插进下一次模型调用。
| 你想 | 怎么做 | 等待 |
|---|---|---|
| 尽快让模型看到 | 直接 Enter(默认) |
当前这一个工具跑完 |
| 等它忙完再说 | /queue <消息> |
整个回合结束 |
| 别跑了,听我的 | Esc |
立即;未送达的插话放回输入框 |
界面:没走的钉着,走了才滚动
和排队消息同一套:
› 帮我看看这个模块
⏺ Glob {"pattern": "*"}
⎿ Glob ✓
› 换个思路 ← 送达那一刻才打出来,位置正好在工具结果之后
好,那我改成……
⤷ 还没送出去的那句 ← 未送达:钉在输入框上方,随时看得见
› 等下回合再发的那句 ← /queue 排队的,在它下面
两个面板的上下顺序即送达先后,行首符号也不同(⤷ vs ›)——不然分不清自己那句话什么时候会被听见。状态栏另有一个 · 插话 N 条 实时计数作为第二条反馈。
输入那一刻刻意什么都不往对话区打。 那时它还没送达,而对话区里的行事后改不了;打下去就永远停在「输入时」这个位置上,而它的真实位置在后面那条工具结果之后。于是屏幕顺序 = 发给模型的消息表顺序 = -c 回放顺序,三者一致。
消息插在哪里
合法位置只有一个:所有工具结果之后。落在 assistant(tool_calls) 与 tool 之间就是悬空 tool_calls,下一次请求直接被网关 400。而会话存储层看不到这个位置——工具结果落库与「构建下一次 prompt」是同一步。所以注入挂在 ChatModel 装饰器层,那一层拿到的消息表已经配平。
插话送给模型时会被 [interjection] 包裹并附一句行为指引(大意:用户在任务执行中插话,未完成的工作仍在进行;与当前方向冲突就调整,否则先把手头的做完)。不包的话,模型在 tool 结果之后突然看到一条 user 消息,很容易判定「上一轮结束了,这是新任务」,于是丢下没做完的活。回合结束时这条插话会按锚点补进会话历史——否则 -c 恢复和压缩之后,历史上就会出现一次「无来由的转向」。
什么时候会自动回落成排队
- 压缩中、以及回合已被 Esc 取消、只剩子 agent 在收尾——这两种状态不会再有模型调用,插话进去等于石沉大海。
- 本条消息挂了技能(
/skill)——插话是一条纯 user 消息,带不了技能参数。
与后台任务的接缝
TaskOutput(block=true) 是故意等下去的(默认上限 300 秒),而这一等发生在主 agent 的工具线程上——期间主 agent 一次模型调用都不发,而模型调用是插话的唯一送达点。不处理的话,「后台」两个字会被 block=true 抵消得干干净净:界面一切正常(回合在跑、⤷ 面板照常显示),但你那句话要等满 5 分钟。故那个工具的轮询每 200ms 顺路查一次插话队列,有就提前收工。
已知边界:模型对那句行为指引的实际反应未经真实 provider 验证(冒烟用的是桩模型)。指引措辞目前偏保守,倾向让它先做完再理你。
✨ 新功能:记住上次用的模型
/model 选中的模型记在 <项目根>/.codetui/model.json(单键 lastModel,已被 .gitignore),下次启动自动恢复。
- 按项目隔离——不同仓库各记各的。你在这个项目里习惯用推理模型、在那个项目里用快模型,不用每次开机重选。
- 与会话恢复正交:不带
-c的默认启动照样恢复模型。这正是它存在的理由——「换个话题重开」和「换个模型」是两件事,不该被绑在一起。 - 只记 modelId,不记 provider,与
/model面板本身的选择粒度一致(面板也只能按 id 选)。*_MODELS环境变量造成跨家重名时命中列表序靠前的可用家——已知限制,不是疏忽。 - 该模型已不可用时(比如你临时注释掉了那家的 key),回退到首个可用 provider 的默认模型并提示一行,但盘上那条记录不清——于是不再选一次的话每次启动都会重复那句提示。这是有意的:清掉的代价是「你只是临时跑了一次没带 key,回头 key 加回来记忆已经没了」。
- 写盘失败不影响使用,只在对话区提示「仅本次运行生效」。
与权限档位的处理刻意相反:权限档每次启动都回到默认,模型选择则记住。前者记错了会放大权限,后者记错了最多是模型不合手、一个
/model就改回来。
🔧 其它变化
- 启动不再等 MCP:MCP 连接改到后台进行。此前配了较慢的 MCP server 时,界面要等全部连上才出来。现在立刻可用,状态栏用
⟳ MCP 连接中 N显示进度,连上后工具自动进入可用集。 /continue会把后台任务摘要拼进提示词:正在跑的、以及已完成但结果还没送出去的后台任务一并告诉模型,避免同一批活跑两遍。ListTasks(新工具):让模型随时列出本进程的后台任务及状态。没有它的话,/compact压掉历史之后模型就看不见自己派了什么。- 忙时 notice 降级为后缀:此前一条 notice(如「已取消当前回合」)会整条盖掉状态栏,把正在跑的回合指示一起挡住。现在忙时它退成后缀,运行指示始终可见。
- 修文档假话:「BYPASS 也盖不住内置底线」这句在三份文档里流传,而 v1.7.0 起它就盖得住了。三份副本一并改正。
🧪 质量
mvn -pl springai-code-tui test → 1353 个用例,0 失败 0 错误,9 跳过。另有 9 个 pty 实机冒烟全绿(插话、后台任务、/clear、记忆、权限、模型记忆、编辑快捷键、附件、MCP 管理)——内联 TUI 的渲染缺陷单测原理上抓不到,只能开真伪终端读屏。
本版两个新功能的验收都压在冒烟上,这点值得写明:插话的接线(InterjectingChatModel.wrap() 那一行)摘掉后 1322 个单测一个都不红,只有冒烟里断「桩模型实际收到的请求体」那条会红——因为接线断掉时插话面板和状态栏计数照样正常显示,肉眼看界面完全正常,功能却已经死了。
⚠️ 安全声明
本版的权限变化是净下降,请重读文首那一条。 BYPASS 现在无需启动参数即可在运行期按进去,而它自 v1.7.0 起是真的跳过全部检查——内置底线与 ask 规则都不执行,只剩你自己写的 deny 规则。
后台子 agent 带来的新面:
后台任务在没有人看着的时候执行工具。 它走同一个权限引擎,且
ASK一律DENY(见上),所以默认档下它做不了需要审批的事。但切到「自动接受编辑」或写了宽 allow 规则之后,它就是在你不看屏幕的时候改文件。给后台任务写规则时请比给前台写得更窄。
其余(权限层不是沙箱、贴图会原样发给第三方模型 API、引用块防伪造不防提示注入)与 v1.7.0 一致。详见 code-tui README 的安全声明 与 SECURITY.md。
⬆️ 升级须知
- 无破坏性变更:旧会话、旧
permissions.json、旧mcp.json、旧技能目录照常可用。 - 不传
run_in_background时,Task/ParallelTasks的行为与 v1.7.0 逐字一致。 Enter在忙时的行为变了:此前是排到下回合,现在默认插话(尽快送达)。想要旧行为用/queue <消息>。--dangerously-skip-permissions的语义收窄为「启动即起步于 BYPASS 档」;「运行期能否切进 BYPASS」不再受它管——现在任何进程都能切。- 新增两个可选环境变量:
CODETUI_TASK_OUTPUT_TIMEOUT_SECONDS(TaskOutput(block=true)的等待上限,默认 300,钳在[1, 3600])与CODETUI_BACKGROUND_CONCURRENCY(后台子 agent 并发上限,默认 4,钳在[1, 32])。都非法即回落默认,绝不崩启动。 .codetui/model.json是新文件,已被.gitignore。不想要这个记忆的话删掉即可,下次/model会重新写。
🔐 校验(SHA-256)
db06c6dcfbfc3bb2511f29c15bd4cd524e46d6d366a07eb24f6b4c6d94f381d7 springai-code-tui-1.8.0-dist.tar.gz
aef6b927ad4fb45584c39e025139b27dce9b1994d91423710a7b38243100f395 springai-code-tui-1.8.0-dist.zip
shasum -a 256 -c <<'EOF'
db06c6dcfbfc3bb2511f29c15bd4cd524e46d6d366a07eb24f6b4c6d94f381d7 springai-code-tui-1.8.0-dist.tar.gz
aef6b927ad4fb45584c39e025139b27dce9b1994d91423710a7b38243100f395 springai-code-tui-1.8.0-dist.zip
EOF📄 许可
Apache License 2.0。发布包内随附 LICENSE 与 NOTICE(含所分发第三方库:Spring AI / Spring Boot / spring-ai-community 为 Apache 2.0,TamboUI 为 MIT)。本版未引入任何新的第三方依赖。
环境:JDK 17+,macOS / Linux / Windows。