Skip to content

Busy ack rewrites {d}/{t} typed in the user's own message #1634

Closed

Description

@HugoXK

Implementation: FlyBot (ours) — PR follows within 24h. Do not pick up without a pickup-ok comment on this issue.

Written-by: FlyBot Lead
Claimed-by: FlyBot Lead
Claim-Id: 1bab102216224d0f902e6261bda01285
Branch: dev/1634-busy-ack-rewrites-d-t-typed-in-the-user
Base: 331dd62
At: 2026-10-29T04:37:30Z

Implementation: FlyBot (ours) — PR follows within 24h. Do not pick up without a pickup-ok comment.

Written-by: FlyBot Lead

What happens

The busy ack (#1584, landed in #1630) quotes the human's message back inside 「…」 / "…". If that message itself contains the letters {d} or {t}, the quoted part is rewritten: {d} becomes the running tool's description and {t} gets dropped (empty when there is no tool words), so the ack no longer echoes what the person wrote.

Reproduced through the real host path (install(), live={"s1":{turn:{},tool:"bash"}}, chatOwners containing s1, uiLanguage zh-CN):

  • message 把 echo ${d} 改成 echo ${t} → ack Saw "把 echo $running a command 改成 echo ${t}" — I’m running a command right now, back on it in a moment.
  • message python 的 f"{d}" 也要改 with tool edit → ack 「python 的 f"改文件"…」收到,手上在改文件,这步完了就来。

The second one is the worst shape: {d} silently becomes 「改文件」 inside the user's own code, so the ack attributes words to the person that they never wrote.

Why

core/src/host/busy-ack.js:117:

const fill = index => pool[index % pool.length].replaceAll('{t}', topic).replaceAll('{d}', doing ?? '')

Two passes over the same string. The first pass inserts topic, which is text taken from the user's message (topicOf(), :27-49); the second pass then scans the freshly inserted user text as if it were template markup. Any {d} / {t} the person typed is therefore treated as a placeholder.

Repro (unit level, no host needed)

composeBusyAck({ message: '改 ${d} 这行', tool: 'bash', uiLanguage: 'zh-CN' })

Current output contains 「改 $跑命令 这行」 instead of 「改 ${d} 这行」. Keep the message short on purpose: topicOf() clips the topic at TOPIC_MAX.zh = 16 chars, so a long sentence hides the effect behind the ellipsis.

Expected

Text quoted from the message is echoed verbatim: {d} and {t} typed by the person stay {d} and {t} in the ack. Only the template's own placeholders are substituted.

Scope

S, one line plus tests:

  • busy-ack.js:117 — substitute in a single pass so inserted text is never rescanned, e.g. replace(/\{(t|d)\}/g, (_, key) => key === 't' ? topic : (doing ?? '')). Keep the previous-avoidance loop and everything else as it is.
  • Tests in core/test/busy-ack.test.js (and the client mirror if it renders the same text): a message containing {d} and one containing {t}, with and without a running tool — the ack quotes them unchanged; plus the existing 10 green cases untouched.

Not this issue (observed, out of scope)

  • ackLanguage() (:17-25) decides zh when cjk * 2 >= latin, so a Chinese sentence carrying many Latin characters (a code snippet, a path) is answered in English: 把 echo ${d} 改成 echo ${t} → 4 CJK vs ~10 Latin → en. That is a judgement call about density, not a correctness bug; tracked separately if it is worth changing.
  • The ack only fires in a Bot's own chat, never in rooms (shouldNoteBusyAck() needs ownChat). That is the gap fix(chat): busy / background send stays silent — need a short spoken acknowledgment #1584 left on the room side; separate work.

Activity

  1. HugoXK commented on Oct 29, 2026

    @HugoXK
    ContributorAuthor

    Release

    Written-by: FlyBot Lead
    Released: 1bab102216224d0f902e6261bda01285
    Reason: stood down
    At: 2026-10-29T05:25:19Z

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions