feat(core,server,web): images reach every input — steering, goal objectives, one fold (#71)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
rank-Yu
2026-07-30 16:50:35 +08:00
committed by GitHub
parent de6380f278
commit 97852f43b8
40 changed files with 1474 additions and 254 deletions
+13 -1
View File
@@ -84,7 +84,19 @@ Task 由若干连续的 Request(轮)组成,每轮:
## 运行中插话(Steering)
Task 运行期间,宿主可通过 `session.steer(text)` 排队一条用户消息而不打断循环:引擎在下一次输入组装时把它作为**独立的用户文本消息**送出,内容包裹在 `[user_steering]…[/user_steering]` 中,与该轮工具输出一起进入下一次请求(该轮没有工具调用时则单独作为继续输入,Task 不会就此结束)。插话是真实的用户输入:像 Prompt 一样写入 Trace、推送到输出流,恢复重放时按普通轮次输入处理;工具输出本身从不被改写。队列在**每次**输入组装时排空——包括运行中压缩完成后的那次,压缩请求期间到达的插话不会被吞掉。没有 Task 运行时 `steer` 返回 `false`(宿主转为发起普通 Task);仅在运行退出(含中断)时丢弃队列。
Task 运行期间,宿主可通过 `session.steer(input)` 排队一条用户消息而不打断循环(`input` 是 OmniMessage 列表,与 `run` 接收 Prompt 的形状一致):引擎在下一次输入组装时把它作为**独立的用户文本消息**送出,内容包裹在 `[user_steering]…[/user_steering]` 中,与该轮工具输出一起进入下一次请求(该轮没有工具调用时则单独作为继续输入,Task 不会就此结束)。输入中的用户文本成为标记块的正文,图片紧跟其后,作为普通用户图片消息送出,因此一张没有配文的图片本身就是一条完整的插话;模型不支持视觉时,图片改为折叠成 `[attached image: <path>]` 路径行写在标记块**内部**,与 Prompt 的图片走同一条路(标记块必须仍是整条文本,否则这条消息会丢掉插话身份、被当成新 Task)。插话是真实的用户输入:像 Prompt 一样写入 Trace、推送到输出流,恢复重放时按普通轮次输入处理;工具输出本身从不被改写。队列在**每次**输入组装时排空——包括运行中压缩完成后的那次,压缩请求期间到达的插话不会被吞掉。没有 Task 运行时 `steer` 返回 `false`(宿主转为发起普通 Task);仅在运行退出(含中断)时丢弃队列。
## 输入图片
一张输入图片要么以图片消息的形态跟着请求走,要么变成一行 `[attached image: <路径>]`,指向会话 scratchpad 里的文件——模型再用 `read_image` / `describe_image` 去看,Web 则从路径还原出缩略图。这个转换是每个 Session 绑定一次的同一个函数(Session 是唯一同时知道 scratchpad 目录和模型能力的层),而**是否折叠由各输入路径自己决定**:
| 输入 | 何时折叠 | 折叠时机 |
| --- | --- | --- |
| Prompt(`run`) | 模型不支持图片 | `run` 入口,早于写 Trace 和取标题素材 |
| 插话(`steer`) | 模型不支持图片 | 投递时,即 turn 边界——入队必须保持同步,而中断时被丢弃的队列若已折叠会留下没人读的孤儿文件 |
| 目标(goal) | **总是** | 抽取 objective 之前,这样路径行才能活过每一轮的重新注入 |
目标模式是唯一的例外,因为它的目标每轮都作为文本重新注入:见[目标模式](/docs/goal-mode)。
## 自动重连