From 5c970d97450f5dc58334030dbc5d0dbe07845cb1 Mon Sep 17 00:00:00 2001 From: KoNit-K <124019182+KoNit-K@users.noreply.github.com> Date: Mon, 14 Sep 2026 17:57:45 -0700 Subject: [PATCH] fix(kanban): neutral block-loop wording on the CLI, Desktop toast, wake text and docs The sibling surfaces of the gateway ping rendered the same false claim: `hermes kanban block` said "needs a human decision", the Desktop toast title said "needs a decision", the wake status line (locales/*.yaml gateway.kanban.wake.block_loop_detected) said "needs a decision" and the docs described the triage route as "for a human decision". A repeated-block circuit breaker only establishes that orchestration attention is needed. Surface sweep from PR #111131 (notifier/test hunks dropped in favour of the typed-kind formatter from PR #111132). --- apps/desktop/src/plugins/kanban/completion-notify.test.ts | 8 ++++++-- apps/desktop/src/plugins/kanban/i18n.ts | 8 ++++---- hermes_cli/kanban.py | 2 +- locales/af.yaml | 2 +- locales/ar.yaml | 2 +- locales/de.yaml | 2 +- locales/en.yaml | 2 +- locales/es.yaml | 2 +- locales/fr.yaml | 2 +- locales/ga.yaml | 2 +- locales/hu.yaml | 2 +- locales/it.yaml | 2 +- locales/ja.yaml | 2 +- locales/ko.yaml | 2 +- locales/pt.yaml | 2 +- locales/ru.yaml | 2 +- locales/tr.yaml | 2 +- locales/uk.yaml | 2 +- locales/zh-hant.yaml | 2 +- locales/zh.yaml | 2 +- website/docs/user-guide/features/kanban.md | 8 ++++---- 21 files changed, 32 insertions(+), 28 deletions(-) diff --git a/apps/desktop/src/plugins/kanban/completion-notify.test.ts b/apps/desktop/src/plugins/kanban/completion-notify.test.ts index 9eb0ab36bf..dc628f4eca 100644 --- a/apps/desktop/src/plugins/kanban/completion-notify.test.ts +++ b/apps/desktop/src/plugins/kanban/completion-notify.test.ts @@ -428,14 +428,18 @@ describe('terminal kinds beyond completed', () => { }) }) - it('block_loop_detected notifies (routed-to-triage handoff)', async () => { + it('block_loop_detected notifies that orchestration attention is needed', async () => { const m = await loadModule() m.bindCompletionNotify(makeRest(() => 100) as never) const fired = await m.onKanbanEventsFrame('smoke', [ev(101, 'block_loop_detected', { reason: 'same cause 3x' })]) expect(fired).toBe(true) - expect(lastNotify()).toMatchObject({ kind: 'warning', message: 'same cause 3x' }) + expect(lastNotify()).toMatchObject({ + kind: 'warning', + title: 'Task routed to triage — orchestration attention needed', + message: 'same cause 3x' + }) }) it('gave_up carries the payload error; crashed and timed_out fall back to the task id', async () => { diff --git a/apps/desktop/src/plugins/kanban/i18n.ts b/apps/desktop/src/plugins/kanban/i18n.ts index 8f36823bab..2dda287fdc 100644 --- a/apps/desktop/src/plugins/kanban/i18n.ts +++ b/apps/desktop/src/plugins/kanban/i18n.ts @@ -414,7 +414,7 @@ export const en: KanbanMessages = { notify: { completedTitle: 'Task completed', blockedTitle: 'Task blocked — needs your input', - blockLoopTitle: 'Task routed to triage — needs a decision', + blockLoopTitle: 'Task routed to triage — orchestration attention needed', gaveUpTitle: 'Task gave up', crashedTitle: 'Worker crashed — will retry', timedOutTitle: 'Task timed out — will retry', @@ -625,7 +625,7 @@ const ja: KanbanMessages = { notify: { completedTitle: 'タスク完了', blockedTitle: 'タスクがブロック中 — 入力が必要です', - blockLoopTitle: 'タスクをトリアージへ移動 — 判断が必要です', + blockLoopTitle: 'タスクをトリアージへ移動 — オーケストレーションの確認が必要です', gaveUpTitle: 'タスクを断念しました', crashedTitle: 'ワーカーがクラッシュ — 再試行します', timedOutTitle: 'タスクがタイムアウト — 再試行します', @@ -833,7 +833,7 @@ const zh: KanbanMessages = { notify: { completedTitle: '任务已完成', blockedTitle: '任务受阻 — 需要你的输入', - blockLoopTitle: '任务已转入分类 — 需要人工决定', + blockLoopTitle: '任务已转入分类 — 需要编排关注', gaveUpTitle: '任务已放弃', crashedTitle: '工作单元崩溃 — 将重试', timedOutTitle: '任务超时 — 将重试', @@ -1041,7 +1041,7 @@ const zhHant: KanbanMessages = { notify: { completedTitle: '任務已完成', blockedTitle: '任務受阻 — 需要你的輸入', - blockLoopTitle: '任務已轉入分類 — 需要人工決定', + blockLoopTitle: '任務已轉入分類 — 需要編排關注', gaveUpTitle: '任務已放棄', crashedTitle: '工作單元當機 — 將重試', timedOutTitle: '任務逾時 — 將重試', diff --git a/hermes_cli/kanban.py b/hermes_cli/kanban.py index 22cd5a5150..0f90462d4b 100644 --- a/hermes_cli/kanban.py +++ b/hermes_cli/kanban.py @@ -919,7 +919,7 @@ def _cmd_block(args: argparse.Namespace) -> int: if where == "todo": return f"{tid} → todo (dependency wait){suffix}" if where == "triage": - return f"{tid} → triage (unblock loop detected — needs a human decision){suffix}" + return f"{tid} → triage (unblock loop detected — orchestration attention needed){suffix}" return f"Blocked {tid}{suffix}" op = _commented(conn, reason, author, "BLOCKED", lambda tid: kb.block_task( diff --git a/locales/af.yaml b/locales/af.yaml index 61e30a9600..a9f6d4ec5e 100644 --- a/locales/af.yaml +++ b/locales/af.yaml @@ -179,7 +179,7 @@ gateway: blocked: "blocked; needs attention" review_requested: "handed off for review; the implementation is done" changes_requested: "review requested changes (BLOCK); implementation is not approved" - block_loop_detected: "routed to triage after repeated blocks; needs a decision" + block_loop_detected: "routed to triage after repeated blocks; orchestration attention needed" status_default: "status changed" status_joiner: ", " message: "[kanban] Task {task_id} {status}.\nTitle: {title}\nAssignee: @{assignee}\nBoard: {board}\n\nCheck the result or decide the next step." diff --git a/locales/ar.yaml b/locales/ar.yaml index ea1eaee8fc..1fb91719b4 100644 --- a/locales/ar.yaml +++ b/locales/ar.yaml @@ -202,7 +202,7 @@ gateway: blocked: "محجوبة؛ تحتاج إلى انتباه" review_requested: "أُحيلت للمراجعة؛ اكتمل التنفيذ" changes_requested: "طلبت المراجعة تغييرات (BLOCK)؛ التنفيذ غير معتمد" - block_loop_detected: "أُحيلت إلى الفرز بعد تكرار الحجب؛ تحتاج إلى قرار" + block_loop_detected: "أُحيلت إلى الفرز بعد تكرار الحجب؛ تحتاج إلى اهتمام بالتنسيق" status_default: "تغيّرت الحالة" status_joiner: "، " message: "[kanban] المهمة {task_id} {status}.\nالعنوان: {title}\nالمُكلَّف: @{assignee}\nاللوحة: {board}\n\nراجع النتيجة أو قرّر الخطوة التالية." diff --git a/locales/de.yaml b/locales/de.yaml index 93e1cd0311..953f841ec3 100644 --- a/locales/de.yaml +++ b/locales/de.yaml @@ -179,7 +179,7 @@ gateway: blocked: "blocked; needs attention" review_requested: "handed off for review; the implementation is done" changes_requested: "review requested changes (BLOCK); implementation is not approved" - block_loop_detected: "routed to triage after repeated blocks; needs a decision" + block_loop_detected: "routed to triage after repeated blocks; orchestration attention needed" status_default: "status changed" status_joiner: ", " message: "[kanban] Task {task_id} {status}.\nTitle: {title}\nAssignee: @{assignee}\nBoard: {board}\n\nCheck the result or decide the next step." diff --git a/locales/en.yaml b/locales/en.yaml index f8548e9dd2..6c4c84b739 100644 --- a/locales/en.yaml +++ b/locales/en.yaml @@ -194,7 +194,7 @@ gateway: blocked: "blocked; needs attention" review_requested: "handed off for review; the implementation is done" changes_requested: "review requested changes (BLOCK); implementation is not approved" - block_loop_detected: "routed to triage after repeated blocks; needs a decision" + block_loop_detected: "routed to triage after repeated blocks; orchestration attention needed" status_default: "status changed" status_joiner: ", " message: "[kanban] Task {task_id} {status}.\nTitle: {title}\nAssignee: @{assignee}\nBoard: {board}\n\nCheck the result or decide the next step." diff --git a/locales/es.yaml b/locales/es.yaml index 9d99e3b530..926019b20b 100644 --- a/locales/es.yaml +++ b/locales/es.yaml @@ -179,7 +179,7 @@ gateway: blocked: "blocked; needs attention" review_requested: "handed off for review; the implementation is done" changes_requested: "review requested changes (BLOCK); implementation is not approved" - block_loop_detected: "routed to triage after repeated blocks; needs a decision" + block_loop_detected: "routed to triage after repeated blocks; orchestration attention needed" status_default: "status changed" status_joiner: ", " message: "[kanban] Task {task_id} {status}.\nTitle: {title}\nAssignee: @{assignee}\nBoard: {board}\n\nCheck the result or decide the next step." diff --git a/locales/fr.yaml b/locales/fr.yaml index 43b2656395..94b2633567 100644 --- a/locales/fr.yaml +++ b/locales/fr.yaml @@ -179,7 +179,7 @@ gateway: blocked: "blocked; needs attention" review_requested: "handed off for review; the implementation is done" changes_requested: "review requested changes (BLOCK); implementation is not approved" - block_loop_detected: "routed to triage after repeated blocks; needs a decision" + block_loop_detected: "routed to triage after repeated blocks; orchestration attention needed" status_default: "status changed" status_joiner: ", " message: "[kanban] Task {task_id} {status}.\nTitle: {title}\nAssignee: @{assignee}\nBoard: {board}\n\nCheck the result or decide the next step." diff --git a/locales/ga.yaml b/locales/ga.yaml index 75a9430ede..e2315e945f 100644 --- a/locales/ga.yaml +++ b/locales/ga.yaml @@ -183,7 +183,7 @@ gateway: blocked: "blocked; needs attention" review_requested: "handed off for review; the implementation is done" changes_requested: "review requested changes (BLOCK); implementation is not approved" - block_loop_detected: "routed to triage after repeated blocks; needs a decision" + block_loop_detected: "routed to triage after repeated blocks; orchestration attention needed" status_default: "status changed" status_joiner: ", " message: "[kanban] Task {task_id} {status}.\nTitle: {title}\nAssignee: @{assignee}\nBoard: {board}\n\nCheck the result or decide the next step." diff --git a/locales/hu.yaml b/locales/hu.yaml index 4913985b0e..ac56e9e767 100644 --- a/locales/hu.yaml +++ b/locales/hu.yaml @@ -179,7 +179,7 @@ gateway: blocked: "blocked; needs attention" review_requested: "handed off for review; the implementation is done" changes_requested: "review requested changes (BLOCK); implementation is not approved" - block_loop_detected: "routed to triage after repeated blocks; needs a decision" + block_loop_detected: "routed to triage after repeated blocks; orchestration attention needed" status_default: "status changed" status_joiner: ", " message: "[kanban] Task {task_id} {status}.\nTitle: {title}\nAssignee: @{assignee}\nBoard: {board}\n\nCheck the result or decide the next step." diff --git a/locales/it.yaml b/locales/it.yaml index 24a6c2c7ae..fee3d15782 100644 --- a/locales/it.yaml +++ b/locales/it.yaml @@ -179,7 +179,7 @@ gateway: blocked: "blocked; needs attention" review_requested: "handed off for review; the implementation is done" changes_requested: "review requested changes (BLOCK); implementation is not approved" - block_loop_detected: "routed to triage after repeated blocks; needs a decision" + block_loop_detected: "routed to triage after repeated blocks; orchestration attention needed" status_default: "status changed" status_joiner: ", " message: "[kanban] Task {task_id} {status}.\nTitle: {title}\nAssignee: @{assignee}\nBoard: {board}\n\nCheck the result or decide the next step." diff --git a/locales/ja.yaml b/locales/ja.yaml index ac80eef3d4..916dbe803c 100644 --- a/locales/ja.yaml +++ b/locales/ja.yaml @@ -179,7 +179,7 @@ gateway: blocked: "blocked; needs attention" review_requested: "handed off for review; the implementation is done" changes_requested: "review requested changes (BLOCK); implementation is not approved" - block_loop_detected: "routed to triage after repeated blocks; needs a decision" + block_loop_detected: "routed to triage after repeated blocks; orchestration attention needed" status_default: "status changed" status_joiner: ", " message: "[kanban] Task {task_id} {status}.\nTitle: {title}\nAssignee: @{assignee}\nBoard: {board}\n\nCheck the result or decide the next step." diff --git a/locales/ko.yaml b/locales/ko.yaml index b18a9a52e2..c1aacbfbac 100644 --- a/locales/ko.yaml +++ b/locales/ko.yaml @@ -179,7 +179,7 @@ gateway: blocked: "blocked; needs attention" review_requested: "handed off for review; the implementation is done" changes_requested: "review requested changes (BLOCK); implementation is not approved" - block_loop_detected: "routed to triage after repeated blocks; needs a decision" + block_loop_detected: "routed to triage after repeated blocks; orchestration attention needed" status_default: "status changed" status_joiner: ", " message: "[kanban] Task {task_id} {status}.\nTitle: {title}\nAssignee: @{assignee}\nBoard: {board}\n\nCheck the result or decide the next step." diff --git a/locales/pt.yaml b/locales/pt.yaml index 25575ad6f1..f326cf36a3 100644 --- a/locales/pt.yaml +++ b/locales/pt.yaml @@ -179,7 +179,7 @@ gateway: blocked: "blocked; needs attention" review_requested: "handed off for review; the implementation is done" changes_requested: "review requested changes (BLOCK); implementation is not approved" - block_loop_detected: "routed to triage after repeated blocks; needs a decision" + block_loop_detected: "routed to triage after repeated blocks; orchestration attention needed" status_default: "status changed" status_joiner: ", " message: "[kanban] Task {task_id} {status}.\nTitle: {title}\nAssignee: @{assignee}\nBoard: {board}\n\nCheck the result or decide the next step." diff --git a/locales/ru.yaml b/locales/ru.yaml index 48636fca0a..e24ec94063 100644 --- a/locales/ru.yaml +++ b/locales/ru.yaml @@ -179,7 +179,7 @@ gateway: blocked: "blocked; needs attention" review_requested: "handed off for review; the implementation is done" changes_requested: "review requested changes (BLOCK); implementation is not approved" - block_loop_detected: "routed to triage after repeated blocks; needs a decision" + block_loop_detected: "routed to triage after repeated blocks; orchestration attention needed" status_default: "status changed" status_joiner: ", " message: "[kanban] Task {task_id} {status}.\nTitle: {title}\nAssignee: @{assignee}\nBoard: {board}\n\nCheck the result or decide the next step." diff --git a/locales/tr.yaml b/locales/tr.yaml index bda87570ba..6809686bf0 100644 --- a/locales/tr.yaml +++ b/locales/tr.yaml @@ -179,7 +179,7 @@ gateway: blocked: "blocked; needs attention" review_requested: "handed off for review; the implementation is done" changes_requested: "review requested changes (BLOCK); implementation is not approved" - block_loop_detected: "routed to triage after repeated blocks; needs a decision" + block_loop_detected: "routed to triage after repeated blocks; orchestration attention needed" status_default: "status changed" status_joiner: ", " message: "[kanban] Task {task_id} {status}.\nTitle: {title}\nAssignee: @{assignee}\nBoard: {board}\n\nCheck the result or decide the next step." diff --git a/locales/uk.yaml b/locales/uk.yaml index 619307c431..f905eaa949 100644 --- a/locales/uk.yaml +++ b/locales/uk.yaml @@ -179,7 +179,7 @@ gateway: blocked: "blocked; needs attention" review_requested: "handed off for review; the implementation is done" changes_requested: "review requested changes (BLOCK); implementation is not approved" - block_loop_detected: "routed to triage after repeated blocks; needs a decision" + block_loop_detected: "routed to triage after repeated blocks; orchestration attention needed" status_default: "status changed" status_joiner: ", " message: "[kanban] Task {task_id} {status}.\nTitle: {title}\nAssignee: @{assignee}\nBoard: {board}\n\nCheck the result or decide the next step." diff --git a/locales/zh-hant.yaml b/locales/zh-hant.yaml index ff57107f80..cd7b3b1648 100644 --- a/locales/zh-hant.yaml +++ b/locales/zh-hant.yaml @@ -179,7 +179,7 @@ gateway: blocked: "blocked; needs attention" review_requested: "handed off for review; the implementation is done" changes_requested: "review requested changes (BLOCK); implementation is not approved" - block_loop_detected: "routed to triage after repeated blocks; needs a decision" + block_loop_detected: "routed to triage after repeated blocks; orchestration attention needed" status_default: "status changed" status_joiner: ", " message: "[kanban] Task {task_id} {status}.\nTitle: {title}\nAssignee: @{assignee}\nBoard: {board}\n\nCheck the result or decide the next step." diff --git a/locales/zh.yaml b/locales/zh.yaml index 6364ae8fa8..545d052827 100644 --- a/locales/zh.yaml +++ b/locales/zh.yaml @@ -179,7 +179,7 @@ gateway: blocked: "被阻塞,需要处理" review_requested: "已交付评审;实现已完成" changes_requested: "评审要求修改(BLOCK),实现未获批准" - block_loop_detected: "反复阻塞后被转入待分诊,需要做出决定" + block_loop_detected: "反复阻塞后被转入待分诊,需要编排关注" status_default: "状态变化" status_joiner: "," message: "[kanban] 任务 {task_id} {status}。\n标题: {title}\n执行者: @{assignee}\n看板: {board}\n\n请检查结果或决定下一步动作。" diff --git a/website/docs/user-guide/features/kanban.md b/website/docs/user-guide/features/kanban.md index 50c7325640..94c4152312 100644 --- a/website/docs/user-guide/features/kanban.md +++ b/website/docs/user-guide/features/kanban.md @@ -351,8 +351,8 @@ If you unblock a task and it later shows up in **`triage`**, the unblock is not what put it there. A subsequent *re-block for the same reason* did: after a task is blocked → unblocked → re-blocked for the same cause `BLOCK_RECURRENCE_LIMIT` times (default `2`), the unblock-loop breaker stops sending it back to `blocked` -— where a cron would just keep unblocking it — and routes it to `triage` for a -human decision. This is a deterministic DB guard, not an LLM judgment call, and +— where a cron would just keep unblocking it — and routes it to `triage` for +orchestration attention. This is a deterministic DB guard, not an LLM judgment call, and a task's body text cannot opt out of it: the recurrence counter deliberately survives each unblock (it resets only on a successful `complete`). To keep an unblocked task in the work pool, resolve *why it keeps re-blocking* (unfinished @@ -1170,7 +1170,7 @@ For `notify+wake`, delivery completes only once the wake is admitted to the adap A "wake" forges a synthetic inbound message to the destination gateway agent so it takes a normal turn (reads the comment + result, reasons, replies) instead of getting a one-line passive notification. It only fires when the notifier runs inside a live gateway process; otherwise a `notify+wake` subscription still delivers its passive message, while a `wake`-only subscription does nothing in that process. -**Which events wake.** The ones that hand a decision back to the origin: `completed`, `blocked`, `gave_up`, `crashed`, `timed_out`, `review_requested` (a worker finished the implementation and handed off via `kanban_request_review`) and `block_loop_detected` (the task was routed to `triage` after repeated blocks). `status`, `archived` and `unblocked` are delivered but never wake — they are bookkeeping transitions, not decisions. When a `completed` or `review_requested` event carries a summary, that handoff rides the wake turn, so the woken agent sees what the worker actually did. +**Which events wake.** The ones that return a task outcome or require orchestration attention: `completed`, `blocked`, `gave_up`, `crashed`, `timed_out`, `review_requested` (a worker finished the implementation and handed off via `kanban_request_review`) and `block_loop_detected` (the task was routed to `triage` after repeated blocks). `status`, `archived` and `unblocked` are delivered but never wake — they are bookkeeping transitions, not attention signals. When a `completed` or `review_requested` event carries a summary, that handoff rides the wake turn, so the woken agent sees what the worker actually did. `--chat-type` (`dm` | `group` | `channel` | `thread`) records the originating chat's type so a woken turn resolves the operator's **real** session: `build_session_key` keys groups, channels, and threads differently from DMs, so an inaccurate `chat_type` would route the wake into a separate, context-less session. The `/kanban` auto-subscribe and slash-command paths capture this automatically — you only set it by hand when subscribing a chat from a script or cron. Omit it to leave an existing subscription unchanged (new subscriptions default to `dm`). @@ -1274,7 +1274,7 @@ Every transition appends a row to `task_events`. Each row carries an optional `r | `completed` | `{result_len, summary?}` | Worker wrote `--result` / `--summary` and task hit `done`. `summary` is the first-line handoff (400-char cap); full version lives on the run row. If `complete_task` is called on a never-claimed task with handoff fields, a zero-duration run is synthesized so `run_id` still points at something. | | `blocked` | `{reason, kind, recurrences}` | Worker or human flipped the task to `blocked`. `kind` is the typed block reason (`needs_input`, `capability`, `transient`, or `null` for a generic block); `recurrences` is the unblock-loop counter. Synthesizes a zero-duration run when called on a never-claimed task with `--reason`. | | `dependency_wait` | `{reason, kind}` | Worker blocked with `kind=dependency` — the task is only waiting on another task, so it routes to `todo` (parent-gated, auto-promoted) instead of `blocked`. No human needed. | -| `block_loop_detected` | `{reason, kind, recurrences, limit}` | A task was unblocked and re-blocked for the same reason `BLOCK_RECURRENCE_LIMIT` times (default 2). Instead of landing in `blocked` again — where a cron would keep unblocking it — it routes to `triage` for a human decision, breaking the unblock↔re-block loop. | +| `block_loop_detected` | `{reason, kind, recurrences, limit}` | A task was unblocked and re-blocked for the same reason `BLOCK_RECURRENCE_LIMIT` times (default 2). Instead of landing in `blocked` again — where a cron would keep unblocking it — it routes to `triage` for orchestration attention, breaking the unblock↔re-block loop. | | `unblocked` | — | `blocked → ready` (or `todo` if parents are still open), either manually or via `/unblock`. Resets the dispatcher's `consecutive_failures` but deliberately preserves `block_recurrences` so the loop breaker keeps its memory. `run_id` is `NULL`. | | `archived` | — | Hidden from the default board. If the task was still running, carries the `run_id` of the run that was reclaimed as a side effect. |