feat(core): interrupt 投递、可打断工具与 World 的排队事件操作 - #180
Merged
Merged
Conversation
新增: - TriggerMode 加 interrupt。模型调用无论有无外部输出都取消,已外化的输出保留; 执行中的 interruptible 工具收到 ctx.signal,回执末尾加 [interrupted by a new event while running];本轮尚未开始的调用得到 [not executed: interrupted by a new event]。新事件接在本轮回执之后,同一批内继续。 - ToolDef.interruptible。缺省时工具执行到结束;工具的 signal 不再合并模型请求的取消 信号,只合并关机与(interruptible 时)本轮的 interrupt。 - WorldHost.withdrawPending / promotePending:撤回或提级本 World 一条未投递的事件。 撤回在事件库追加 core.withdrawal 记录,重启补投跳过该事件,水位可越过它。 - World.onEventsSettled:本 World 的事件写入主 session 或被操作者清空队列时通知。 - WakeBus.promote,preempt 处理器带上触发方式。 - preempt 或 interrupt 到达时没有可取消的模型轮,事件在下一次模型请求前送入, 不等下一轮工具返回。 修复: - preempt 取消模型轮后调用 finishTurn 并结束本批,各 World 的 onTurnEnded 被触发: QQ 作废未确认的草稿,cua 清空本轮授权。现在被抢占的轮计入轮数,在同一批内带着新事件 重新请求,不调用 onTurnEnded。tests/core/loop.test.ts「被抢占的轮不结束本次唤醒」在旧代码上 onTurnEnded 计数为 2。 - Cormini 合并多个 outputTap 时,未声明 externalizes 的接收器一律按 false 判定;只有一个 接收器时 loop 按默认规则判定。同一个 World 与别的 World 一起挂载后 preempt 窗口改变。 默认规则移到 types.ts 的 defaultExternalizes,合并时逐个回落到它。 tests/cormini/output-tap.test.ts 新增一条,旧代码返回 false。 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
工具收到 signal 时可能已经做完(截图请求一旦发出就会拍完),Core 不知道工具做到哪一步。 标记从 [interrupted by a new event while running] 改为 [an interrupt signal was sent while this tool ran],做到哪一步由工具回执正文说明。 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- recordWithdrawn 原先在循环停止后直接返回,而事件已被 withdrawPending 移出队列并回报 撤回成功,重启补投会把它送回来。现在照常写 core.withdrawal 记录,水位由重启时按记录结清 (noteHandled 在循环停止后本就不推进)。 - docs/sessions.md 的 owner 含 loop.ts:补上一批之内新事件进入上下文的轮次边界,以及 preempt、interrupt 取消后在同一批内继续、不调用 onTurnEnded。 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
WakeBus 的排队项只记 piggyback,不记 trigger。暂停或投递闸门未放行时,promote 只取消 piggyback;放行时 setPaused(false) 与 clearDeliveryGate 照常整批投递,不再通知主循环。 契约原先写「改为 flush / preempt / interrupt」,在这条路径上不成立,现在照实写。 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 6, 2026
Merged
Phantivia
added a commit
to Pal-AI-Lab/Coopanion
that referenced
this pull request
Oct 6, 2026
* feat(cua): 工具可被 interrupt 打断,停在不留半截输入的地方并报告已做的部分
- 十个工具都声明 interruptible,handler 收到 ctx.signal。等使用者回答授权时
直接返回不执行(问题仍开着,回答对本轮之后的调用有效);等使用者停手时
不发输入;cua_type 停在两段之间并报告打出几个字符;cua_wait 报告等了几秒;
已发出的输入跳过 settle 和截图。
- 引擎协议加 { cancel: id }:引擎只在等使用者停手和打字段间响应,点击、
拖动、滚动、按键序列、切窗口一旦开始就发完,拖动不会留着左键。
- 打字改按 16 个字形一段发送,停下时不拆开多码位的字。
- 需要 Cortico 带 ToolDef.interruptible 的版本才会收到 interrupt;旧版本上
signal 只在关机时触发。
- 测试:cua_wait 被打断时提前返回且不截图;cua_type 打断时向引擎发 cancel,
回执按引擎报告的字数写。
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(desktop-pet): 对话页通道:以使用者身份收发,记下桌宠说过的话与做过的事
- 新增 chat 面板的推送连接(src/chat.ts):对话页发来的文字与图片作为 [打字] 事件以
preempt 投递;送达前在页面上排队,可以 interrupt 立即送达或撤回(WorldHost
promotePending / withdrawPending);onEventsSettled 告诉页面何时送达。
- pet_say 的每一拍、pet_ask 的问题、两句话之间调用过的工具名记为 desktop-pet.self 事件,
只落库不投递,页面据此还原历史;排过队的消息记下送达位置,历史按 bot 实际看到的顺序排。
- pet_ask 在对话页也能回答,回答后桌宠上的气泡关掉;桌宠气泡的回答也同步到页面。
- pet_walk_to 可被 interrupt:页面经 figure-frame 调身体的 stopWalk(id)(形象契约里的可选方法,
kit 已实现),停在原地,回执写停在哪里。
- 气泡里打字的消息也改为 preempt;outputTap 声明 externalizes 恒为 false(桌宠不显示
模型输出流本身,工具执行时才有外部效果)。
- 桌宠打字气泡加展开钮(应用提供 openChat 时显示),带着草稿打开对话页。
- 环境提示词按应用是否提供对话页加一段 {{pet.chat}};pet_say、pet_ask 的回执在对话页
开着时说明内容也显示在那里。
- README 加「对话页」一节,写推送连接的帧、desktop-pet.self 记录与 openChat。
- 测试:对话页消息以 preempt 发出、送达前排队、撤回后不进历史;interrupt 让走路停下并
回报位置。FakeHost 的 deliver:false 事件另记 stored,不计入 events。
需要 Cortico 带 interrupt、RunPhase 与 onEventsSettled 的版本(Pal-AI-Lab/Cortico#180、#181)。
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(core): 桌宠打字气泡的展开钮打开设置窗口的对话页
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(console): 普通模式的「对话」换成对话页,终端页改名「运行轨迹」放进高级组
- 对话页(features/chat)接桌宠 World 的 chat 面板:以使用者身份打字、发图;桌宠的话画成
与桌宠相同的描边气泡,一拍一个,尖角朝左;提问显示为桌宠的提问气泡,可以在这里回答;
两句话之间做过的事折叠成一行小字,只列认得出的工具。
- 送达前的消息排在输入框上方,每条可以「立即发送」(打断当前这一步)或「撤回」(退回输入框)。
- 顶栏按 Core 的运行阶段显示空闲、在想、正在做什么、重试、已暂停;「运行轨迹」进高级模式的
原始时间线。
- 上游的终端页(以「控制台」身份发内部消息、显示原始 session)改名「运行轨迹」,放进「高级」组。
需要 Cortico 的 promptInput.restore 与 message 图标(Pal-AI-Lab/Cortico#182)。
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* chore: vendor/cortico 升到 Cortico main(#180–#183)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(console): 对话页重绘时保留提问的自写回答草稿、焦点与步骤展开状态;走路结束后移除 abort 监听
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


Closes #179
改动
新增
interrupt触发。 事件到达时停止当前轮:recordAbortedStream保存,提前执行过的调用用真实回执,其余调用得到[not executed: interrupted by a new event]。interruptible的工具收到ctx.signal,Core 等它返回,在回执末尾注明执行期间发出过打断信号,做到哪一步由工具回执说明;未声明的工具执行到结束;本轮尚未开始的调用不执行。ToolDef.interruptible。 缺省为不可打断。工具的signal不再合并模型请求的取消信号;此前它只会在关机时与模型请求一起被取消,preempt发生时工具还没开始。preempt取消后不再结束本次唤醒。 被取消的轮计入轮数,在同一批内带着新事件重新请求,不调用onTurnEnded。此前 QQ 的未确认草稿、cua 的本轮授权会因为一次抢占被清掉。一直有 preempt 事件到达时,本批在rounds().hard处收束。到达时没有可取消的轮。
preempt或interrupt在工具执行中、onDelivery等待中或两轮之间到达时,事件在下一次模型请求前送入,不等再一轮工具返回。外化判定统一。 默认规则导出为
defaultExternalizes。Cormini 合并多个接收器时,未声明externalizes的接收器逐个回落到默认规则;此前合并后一律为 false。撤回在循环停止后也写记录。 事件已离开队列,重启补投按
core.withdrawal记录跳过它。World 的排队事件操作。
withdrawPending(cursor):撤回本 World 一条未投递的事件。事件库追加core.withdrawal记录,重启补投跳过该事件,水位可越过它。promotePending(cursor, trigger):让排队中的事件按flush/preempt/interrupt立即触发。暂停或投递闸门挡着时只取消它的 piggyback,放行后随整批投递,不打断。onEventsSettled(events, outcome):本 World 的事件写入主 session(delivered)或被操作者清空队列(discarded)时调用。两个新宿主方法是可选成员,跨进程宿主(minecraft engine-child)未实现。
docs/sessions.md补上一批之内新事件进入上下文的轮次边界,以及两种取消之后的行为。测试
tests/core/loop.test.ts:被抢占的轮不调用onTurnEnded(旧代码计数为 2);interrupt 停下 interruptible 工具、跳过后续调用、新事件接在回执之后并触发onEventsSettled;interrupt 取消已外化的模型轮并保留已输出正文;撤回记录让重启补投跳过该事件。tests/core/bus.test.ts:promote把 piggyback 项改为 interrupt 并通知主循环。tests/cormini/output-tap.test.ts:两个未声明externalizes的接收器合并后,正文增量判为已外化(旧代码为 false)。pnpm test4823 通过,pnpm run typecheck、pnpm typecheck:web无报错。English
This adds an
interrupttrigger. It cancels the in-flight model call whether or not it has produced output, keeping what was already externalized. It signals running tools that declareinterruptible, and skips calls in the round that have not started. The new event is then delivered after the round's receipts, in the same batch.preemptkeeps its meaning (cancel only a round with no external output). It now continues in the same batch and no longer firesonTurnEnded, which used to discard QQ drafts and cua permissions. When either trigger finds nothing to cancel, the event goes into the next model request instead of waiting for another round of tools.The default externalization rule is exported as
defaultExternalizes, and Cormini's merged tap falls back to it per tap. Previously a merged tap treated a tap withoutexternalizesas false, while a single tap used the default rule.Worlds gain
withdrawPending,promotePendingandonEventsSettled. A withdrawal is recorded in the event store ascore.withdrawal, so a restart does not requeue the event.🤖 Generated with Claude Code