Repository navigation
Description
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: ac124b1661744f348c1a09cabe4a35ca
Branch: dev/3-p2-c2-turns-code-points
Base: 5b72400
At: 2026-11-01T05:42:57Z
Implementation: FlyBot (ours) — PR follows within 24h. Do not pick up without a pickup-ok comment.
Written-by: FlyBot Lead
The defect
Five places in core/src/host/turns.js bound text with a bare String.prototype.slice, which cuts on UTF-16 units. When the bound lands between the halves of a surrogate pair, the value that reaches state, the owner-visible line, or the model prompt is not well formed (U+D83D at the tail, U+FFFD once encoded).
Live main 5b72400c6fef51c9a2145b78de9530c338853b74:
:36turnFailure—error.message.trim().slice(0, 500):45failureLine—failure.message.slice(0, 200):303stepOf—line.length > 80 ? \${line.slice(0, 79)}…` : line`:363callDetail—text.trim().slice(0, 300)andcode.trim().slice(0, 400):405noteEnded—(failure.message || failure.code).slice(0, 160)
:630 is a second cut of the :45 field and is out of scope.
Reproduction (red first, base 5b72400c, source untouched)
Inputs put the cut exactly on the high surrogate ('a'.repeat(N-1) + '😀' + 'tail'), driven through the real functions/routes:
diagnostics # tests 18 # pass 16 # fail 2
not ok 12 - a failure message cut at its 500-unit bound keeps the pair whole
the message is not well formed: "aaa\ud83d"
not ok 13 - a failure line cut at its 200-unit message bound keeps the pair whole
the line is not well formed: "aaa\ud83d"
group-status # tests 25 # pass 23 # fail 2
not ok 2 - an approval card cuts its description and command at their bounds without splitting a pair
the description is not well formed: "aaa\ud83d"
not ok 20 - a step line cut at its bound keeps the pair whole, and the ellipsis only shows when it cut
the step is not well formed: "aa\ud83d…"
dead-turn # tests 6 # pass 5 # fail 1
not ok 3 - a failed turn records its reason cut at 160 units without splitting a pair
the stored reason is not well formed: "aaa\ud83d"
Reverting one fix at a time reddens exactly one case (1→:12, 2→:13, 3→:20, 4→:2, 5→:3), so no case rides on another fix.
Expected
Every value goes through cut(text, max) from ../shared/text-boundary.js (core/src/host/text.js re-exports it); no bound changes. :303 appends … only when cut().cut says something was dropped, instead of guessing from length > 80. Each value stays a code-point prefix of its input, is well formed, and keeps its budget.
Scope
core/src/host/turns.js (five cut sites + one import), one case per site in core/test/{diagnostics,group-status,dead-turn}.test.js, one line in docs/subsystems/core-host.md, a changelog fragment, and the regenerated patches/ds-bot/{0001,0007} + files/ mirror (generated by generate, never hand-edited).
Dedupe (2026-11-01, live main): open issues searched for 702 no; turns.js code point 0 hits; failureLine/callDetail/stepOf no open issue. #1979 covered connectors, face-lead, project-team, capabilities, remote-chat only.
Activity