Repository navigation
Description
Written-by: Delivery-2
Two text bounds in plugins/workflow/src/graph.js cut with a bare String#slice, so a bound that lands between the halves of a surrogate pair stores a lone high surrogate — read as U+FFFD by any encoder and drawn as �.
Current vs expected
| site (this base) | what it bounds | current | expected |
|---|---|---|---|
plugins/workflow/src/graph.js:37 |
a node prompt, saved through workflow/graph / define_workflow and shown in the canvas and the run stage |
return out.replace(/[ \t]+\n/g, '\n').trim().slice(0, max) |
the cut lands on a code point boundary, and max counts code points |
plugins/workflow/src/graph.js:81 |
the step-result body kept from a Bot's reply (readStepResult) |
const body = kept.join('\n').trim().slice(0, 2000) |
same |
cleanLine sits 14 lines above cleanPrompt in the same file and already states the rule: "Cut on code points, never on UTF-16 units: a unit offset can split a surrogate pair and leave a lone surrogate, which renders as a replacement character." The same class was fixed for the host under #1993/#2000 and for the workflow node avatar under #2017; these two bounds were left behind.
The bound also counted UTF-16 units, so an emoji-heavy prompt reached only half the room an ASCII prompt has (a prompt of 4000 emoji was cut at 2000).
Reproduction (this base, 5e71a07e)
cd core && node -e "
import('../plugins/workflow/src/graph.js').then(m => {
const out = m.cleanPrompt('a'.repeat(3999) + '🚀tail')
console.log('prompt', out.length, out.codePointAt(out.length - 1).toString(16), out.isWellFormed())
const r = m.readStepResult('x'.repeat(1999) + '🚀y', { outputs: [] })
console.log('body ', r.text.length, r.text.codePointAt(r.text.length - 1).toString(16), r.text.isWellFormed())
})"prints prompt 4000 d83d false and body 2000 d83d false: both end in the lone high surrogate \ud83d.
Red first
plugins/workflow/test/prompt-bounds.test.js, written before the source change and run on this base with the source untouched — 3 fail / 0 pass:
not ok 1 - a prompt bound lands on a code point boundary, not inside an emoji
expected: true
actual: false
not ok 2 - the prompt bound counts code points, so whole emoji are never cut in half
expected: 4000
actual: 2000
not ok 3 - a step result body bound lands on a code point boundary, not inside an emoji
expected: true
actual: false
Scope
plugins/workflow/src/graph.js only: the shared code-point cut (cleanLine's existing behaviour, now one local helper), its use in cleanPrompt and readStepResult, and the new test. No maximum changes, no payload shape, no endpoint, no contract.
Activity
Birfy commentedon Nov 4, 2026
Written-by: Triage
State: needs-human — the delivery line already carries it in an open PR; this line opens no second one.
#2429 is carried by the open PR #2433 (
HugoXK:delivery2/workflow-prompt-codepoints→delivery/ai-centric-development-service,Fixes #2429). One issue, one PR (AGENTS.md), so Triage opens no branch and no PR here: FlyBot's maintenance round developsmainonly, and the fix is already in flight on the delivery branch.Blocked on: line ownership, not on the report — the site (plugins/workflow/src/graph.js:37,81) and the reproduction in the body are specific, and nothing in them needs a rewrite. Missing: a person's decision that the delivery line keeps it. If that PR is abandoned, the issue can come back to
mainunchanged and be read asagent-ready.Written-by: Triage