Skip to content

feat: 中断 router 分发(✖ on thinking chip)#107

Merged
SymbolStar merged 2 commits into
mainfrom
feature/interrupt-dispatch
Jun 25, 2026
Merged

feat: 中断 router 分发(✖ on thinking chip)#107
SymbolStar merged 2 commits into
mainfrom
feature/interrupt-dispatch

Conversation

@SymbolStar

Copy link
Copy Markdown
Owner

背景

Scott 在 thread feature-中断 router 得分发 提的需求:思考中能 ✖ 取消。@designer 给了 UI spec。

后端

  • 新 phase cancelled,新 endpoint POST /api/threads/<tid>/posts/<chip>/cancel
  • router 单一信任源:取消即把 chip 加进 _cancelled_chips,worker 落回复前 check,late reply 直接丢弃,hung agent 永远污染不了 thread
  • best-effort SIGTERM 子进程组(native openclaw agent + ACP CLI 两条都接)
  • 幂等(重复 cancel → 200;done/failed/skipped → 409 already_completed
  • 审计:每次 cancel 落一条 __router__ post(⛔ 的本轮回复已被中断)
  • ACP runtime 从 subprocess.run 换成 Popen + communicate,这样能 SIGTERM pgid

UI(按 @designer 的 spec)

  • ✖ 常驻半透明(opacity .45);hover 整行升到 opacity 1 + --danger 色 + 圆形 hover 背景
  • 28×28 命中区(视觉 16×16 + 透明 padding),无二次确认
  • 触屏 (hover: none) opacity 1 常驻
  • 点击乐观更新:立即 morph 成 cancelled(带 strike-through 过渡),不等 API
  • cancelled--text-soft ⛔ pill,禁止 hex(参考 PR fix(web/style): activity-chip hover/disabled use theme vars (dark mode) #105
  • 第一期不做「全部中断」,按需观察数据再加

测试

3 个新 endpoint 测试(成功 / 幂等 / 409 terminal)+ ACP runtime 测试 migrate 到 Popen。pytest tests/ 全绿(556 tests)。

cc @designer 完了麻烦盲读验收一次(dark/light × 1280/1024)。

Adds a stable, observable mechanism to cancel an in-flight agent turn:

- New STATUS_PHASES value 'cancelled'; chips can transition from
  thinking|running → cancelled via the new endpoint.
- POST /api/threads/<tid>/posts/<chip_id>/cancel
  * idempotent (already-cancelled → 200; terminal phases → 409 already_completed)
  * patches chip phase to 'cancelled'
  * marks chip in post_router._cancelled_chips so a late agent reply
    is silently dropped at the router boundary (router is single
    source of truth for what lands in the thread)
  * best-effort SIGTERMs the bound subprocess group for both native
    openclaw agent and ACP CLI employees
  * posts a __router__ audit line so the thread explains itself
- agent_runtime.cancel_chip_subprocess + acp_runtime.cancel_acp_chip_subprocess
  expose chip_id → pgid maps for the endpoint to nudge the right group.
- Worker (post_router._route_to_agent) checks is_cancelled() in two
  spots: on AgentError (so a SIGTERM-induced non-zero exit becomes
  'cancelled' instead of 'failed') and after a successful reply (late
  cancel drops the reply).
- ACP runtime switched from subprocess.run → Popen+communicate so the
  cancel path can SIGTERM the pgid; old timeout/failure semantics preserved.

UI (per @designer spec):
- ✖ button on thinking/running chips. Default opacity .45, hover full
  opacity + --danger color, 28×28 hit area (visual 16×16 with transparent
  padding), no confirm dialog. Touch (hover:none) keeps opacity 1.
- 'cancelled' phase renders as a low-key ⛔ pill, --text-soft only.
- Optimistic UI: click flips chip to 'cancelled' instantly with a brief
  strike-through animation; SSE/refresh reconciles. 409 path rolls back.
- All colors via CSS tokens (--danger / --text-soft); no hex (PR #105
  activity-chip lesson).

Tests:
- 3 new tests in tests/test_status_chip.py covering the happy path,
  idempotency, and terminal-phase rejection.
- tests/test_acp_runtime.py updated to fake Popen instead of run.
@github-actions

github-actions Bot commented Jun 25, 2026

Copy link
Copy Markdown

🤖 bot-review (comment-only · phase 1)

Diff: 8 files changed, 484 insertions(+), 46 deletions(-) @ c2dee9c

Red-line checks:

  • ✅ A-7.5: no new 'forbidden' code in xiaof

Needs human review — these paths are not eligible for future auto-approve:

  • server.py (HTTP routes / handler — never auto-approve)

Phase 2: auto-approve + auto-merge fire only when red-lines are clean, author is internal, and no needs-human path is touched. Block with no-auto-merge label or [no-auto-merge] in title.

Two fixes from blind read-through (light OK, dark weak):

1. asc-cancelling strike-through invisible in dark.
   Old: text-decoration: line-through; opacity: .7 on already --text-soft
   text — the strike was lighter than the text. Lost the 'click landed'
   feedback that the optimistic 0ms morph depends on.
   New: animation pulls .asc-phase color up to var(--text) + 2px
   strike-thickness for ~600ms, then eases back to --text-soft so the
   final cancelled pill still rests at low-key gravity.

2. asc-cancel:hover background looked 'swallowed' in dark.
   Old: --danger-soft (#3A1818) is too close to the deep bg; the red
   circle had hue but no punch.
   New: --danger-soft-strong token (rgba(.., .22) in dark, .16 in light)
   used only by the hover circle. Light/dark each get their own value
   so the red 'lifts' off the surface uniformly.

Both fixes go through tokens — zero hex literals in component CSS.
@SymbolStar SymbolStar merged commit 6c2d9f6 into main Jun 25, 2026
5 of 6 checks passed
@SymbolStar SymbolStar deleted the feature/interrupt-dispatch branch June 25, 2026 15:09
SymbolStar added a commit that referenced this pull request Jun 26, 2026
main 上残留的 ruff lint 失败(PR #107 引入),在本 PR 里顺手清掉,让 CI 绿。
github-actions Bot pushed a commit that referenced this pull request Jun 26, 2026
* fix(branding): dock F 再缩一档 (0.82 → 0.78)

Scott 反馈 macOS dock 里 F 仍偏大。把 inner content scale 从 0.82
降到 0.78(约 -5%),translate 同步重算保持视觉居中。

dock 圆角遮罩下留白更舒服,缩到 32/48 也仍有识别度。

- logo-forge-f.svg / -inverse.svg: scale 0.82 → 0.78
- regen 32/48/256 PNGs

* chore(lint): ruff I001 auto-fix on acp_runtime + test_status_chip

main 上残留的 ruff lint 失败(PR #107 引入),在本 PR 里顺手清掉,让 CI 绿。
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant