轮次占用 — 停止、队列与 session 槽位#
一个 session 同时只允许 一个活着的 turn。跑着的时候打字是排队,不是第二轮。停止是中断:占用在取消 意图 上释放,而不是等旧 turn 线程死掉。
相关:interaction-feedback.md(0ms UI)、
send-queue-reliability.html(队列机制)。
代码:use-chat-submit.ts(stopSession)、
server.py(_finish_owned_run / _try_reserve_run)、
run_control.py(cancel_execution、CANCEL_GRACE_S)、
providers/utils/cancelable_stream.py。
排队 vs 中断(Claude Code)#
Claude Code:turn 还在跑时 type+Enter = 排队。Esc/停止先打断当前 turn,然后立刻把队列里的发出去。
Vercel AI SDK:stop 现在就中止请求,并把 abort 转给模型。不要等下一个 token。
OpenProgram 两边都抄。发送队列已经会在 runningTask 存在时把打好的字停住。停止必须:
- 先发
execution.cancel(或stop兜底)。 - 把活着的 assistant 补成
cancelled(保留已流出的文字)。 setRunningTaskFor(sessionId, null, "always"),让队列在 0ms 出队。
把 cancelling: true 留在 running task 上,或 drain: "never",会锁住输入框、干等到 running_task_clear。那是旧 bug。
占用在取消意图上释放#
session 槽位是 _running_tasks 加上 active runtime。只 pop 任务表不够:_is_run_active 还会因 _has_active_runtime 返回 True。
cancel_execution 成功后:
- 把
execution_id映射到session_id+msg_id(聊天执行是{msg_id}_reply)。 - 调用
_finish_owned_run(session_id, msg_id),任务条目和 runtime 一起注销。 - 广播
running_task_clear,让其他客户端对齐。
msg_id 对不上时 _finish_owned_run 是空操作 — 更新的预占不会被抢走。被取消的 turn 的 finally 也会再调一次,第二次是空操作。
同时退役这次 execution 的取消 token。占用是槽位,token 是停止旗标。cancelled 的 token 留在 _current_tokens 里,下一轮 claim_cancel_event 会失败,或者复用的 {msg_id}_reply 一上来就是已取消。旧流仍看着自己的 Event(opts.signal),那个已经被 set。
不要等 process_user_turn 返回才允许下一次 _try_reserve_run。
0ms UI#
0ms 规则已经写了:停止立刻清掉 runningTask 并补丁 assistant。停止也在同一瞬间把发送队列出队。
迟到的、属于已取消 execution_id 的 running_task / stream_event 不得把任务救活,也不得再追加 token。消息状态 cancelled(或 cancelling)就是守卫。
runningTask 一旦是 null,showStop / isCancelling 为假,发送可用。这是预期。不要再为旧的收尾把发送禁用。
HTTP 中止 — 不要等下一个 token#
Anthropic 和 OpenAI Completions 以前是 async for SDK 流,然后才看取消信号。推理中途那就是好几秒。
iter_until_cancelled 每 ≤250ms 看一次取消信号,但 同一条 __anext__ 不能被超时取消。wait_for 一超时就会 cancel 这次读,httpx/OpenAI 的 SSE 迭代器会断。Grok 思考经常超过 250ms 没有分片,于是收成一条空的 completed 回复。取消时再退出,让 async with 关 HTTP 流。
4 秒宽限只给真子进程#
CANCEL_GRACE_S = 4.0 保留。只在 owner 有子进程、或有会杀掉子进程的 terminate 钩子时生效(工具、process runner)。只有 token、没有进程、没有杀子进程钩子的聊天 owner,取消意图之后不得再等 4 秒。协作取消 + HTTP 中止就够了;占用已经释放。