Skip to content

workflow: a node prompt and a step reply cut at their bound keep the character there - #2533

Merged
Birfy merged 1 commit into
ThinkFlowLab:mainfrom
HugoXK:dev/5-workflow-prompt-cut
Nov 5, 2026
Merged

Birfy merged 1 commit into
ThinkFlowLab:mainfrom
HugoXK:dev/5-workflow-prompt-cut

Conversation

@HugoXK

@HugoXK HugoXK commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #2429
Owner: Dev-5
Written-by: Dev-5
Reviewer: Birfy
Risk tier: T2 — the source line sits under plugins/**; the rest is its test and one changelog fragment.
Base: 90a5944

What changed

  • plugins/workflow/src/graph.js — the two cuts that still used String#slice now count code points: cleanPrompt (a node prompt, and the task brief workflow/assign normalises with MAX_BRIEF) and the step body kept by readStepResult. cleanLine is not touched; its own cut already did this since fix(workflow): keep Runs and timeline task titles whole instead of cutting UTF-16 units #1588, and those are the two bounds the issue names.
  • Before: cleanPrompt returned out…trim().slice(0, max) and readStepResult used kept.join('\n').trim().slice(0, 2000). A bound landing between the halves of a surrogate pair stored the lone high surrogate — \ud83d — which every encoder reads as U+FFFD and every renderer draws as �. The bound also counted UTF-16 units, so a prompt of 4000 emoji was cut at 2000 characters.
  • After: the cut lands on a code point boundary and the bound counts code points, exactly like cleanLine's own comment above it states — "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."
  • No bound changes, no payload shape, no endpoint, no contract. plugins/workflow/src/service.js, core/client.js and patches/ds-bot/* are untouched.

Why these two sites and not a new rule

cleanLine sits in the same file and already follows the code-point rule; so do the two earlier fixes of this class, #1993/#2000 for the host and #2018 for the workflow node avatar. cleanPrompt and readStepResult were the two bounds left behind, which is why the issue names exactly graph.js:37 and :81. The change is pinned to those two lines — cleanLine's shape (text.length <= max ? text : Array.from(text).slice(0, max).join('')) inlined at each, no new abstraction and no refactor of the callers.

Files (git diff --numstat 90a59447...HEAD)

  • plugins/workflow/src/graph.js +5/−2
  • plugins/workflow/test/graph.test.js +23/−2
  • changelog.d/workflow-prompt-codepoint-cut.fixed.md +1/−0

Docs-not-needed: the bounds and what they mean are unchanged; only the unit they are counted in changed.

Red first

On the clean base 90a594471b5886737b433e116f6397aa8ca2b6f2 with only the new case added — the source untouched — in plugins/workflow:

node --import ./test/resolve.mjs --test test/graph.test.js

# tests 9 / # pass 8 / # fail 1:

not ok 9 - a node prompt and a step reply stop on the bound without splitting the character there
  a character that fits the bound is kept whole
  + actual - expected

  + 'aaaa…aaaa\ud83d'
  - 'aaaa…aaaa🚀'

The case builds a prompt of 3999 a then one 🚀 and asserts the bounded text equals the prompt (the 🚀 fits, counted as one character) and carries no lone surrogate; it then asks cleanPrompt for a text one character over the bound and asserts the 🚀 drops whole, and finally feeds 1999 a then 🚀 through readStepResult. Each assertion is red on the base and green after the change. The same file at the tip is # tests 9 / # pass 9 / # fail 0.

Ran

  • plugins/workflow, at the tip, node --import ./test/resolve.mjs --test test/graph.test.js → # tests 9 / # pass 9 / # fail 0
  • plugins/workflow, at the tip, npm test → # tests 441 / # suites 4 / # pass 441 / # fail 0
  • TZ=UTC npm --prefix core test → exit 0, # tests 2927 / # pass 2927 / # fail 0
  • node scripts/lint-plugins.mjs → exit 0, Found 16 warnings and 0 errors (the warnings are the existing ones in plugins/social, plugins/timeline and plugins/tree), pack-import OK for every plugin
  • Not run: the smoke test — no core/index.js or core/src/host/** change; ds-bot-patches generate/check — no core/ or patches/ change.

Docs

Docs-not-needed: the cut bound and its maximum stay the same number of characters; only the unit it is counted in moves from UTF-16 to code points, which is what cleanLine already documents in this file and what the two earlier fixes of this class established.

Checkpoint

Test advisory: PASS at d9284be993cd79bb14c0461143ca3d0a491b56fb — FlyBot Test, independent of the author: red first reproduced on a clean 90a59447 (9 tests / 8 pass / 1 fail, the failure is not ok 9, '…\ud83d' vs '…🚀'); after the change 9/9, plugins/workflow 441/441, core 2927/2927; cleanLine byte-identical between base and tip, two hunks in graph.js, +5/−2. Advisory: this is a T2 change, so the merge still waits on a Verdict naming this tip.

{
  "pr": 2533,
  "claim": "Dev-5",
  "issue": 2429,
  "summary": "The node prompt and the step body still cut their text with String#slice, so a bound inside a surrogate pair stored half a character, and an emoji-heavy prompt only reached half the room. Both cuts now count code points, the rule cleanLine has followed since #1588; cleanLine itself, every bound and every caller stay as they were. The brief a run receives goes through the same cut, so it also stops being truncated mid-character. Nothing outside plugins/workflow/src/graph.js, its test and one changelog fragment moves.",
  "files": [
    "plugins/workflow/src/graph.js",
    "plugins/workflow/test/graph.test.js",
    "changelog.d/workflow-prompt-codepoint-cut.fixed.md"
  ],
  "checks": {
    "ran": "plugins/workflow npm test; TZ=UTC npm --prefix core test; node scripts/lint-plugins.mjs",
    "result": "green",
    "at": "d9284be993cd79bb14c0461143ca3d0a491b56fb"
  },
  "next": "none \u2014 ready for review"
}

@HugoXK

HugoXK commented Nov 5, 2026

Copy link
Copy Markdown
Contributor Author

@Birfy review request for the current tip.

  • PR tip: d9284be993cd79bb14c0461143ca3d0a491b56fb (T2, plugins/workflow/src/graph.js), head of HugoXK:dev/5-workflow-prompt-cut.
  • Wanted: a Verdict naming this tip (d9284be99). Every other check at this tip is green (14 success, 0 cancelled); enqueue-guard is red for exactly one reason: "no Verifier report names the tip d9284be".
  • What it changes: the node prompt bound and the step-reply bound cut on code points instead of UTF-16 units, the same rule cleanLine already follows since fix(workflow): keep Runs and timeline task titles whole instead of cutting UTF-16 units #1588 — two call sites, no new abstraction, cleanLine untouched. Test and changelog fragment only besides that file.
  • Red first: not ok 9 on a clean 90a59447 ('…\ud83d' vs '…🚀'); after the change 9/9, plugins/workflow 441/441, core 2927/2927.

…r in half

cleanLine has counted its bound in code points since ThinkFlowLab#1588, but cleanPrompt and
readStepResult still used String#slice, so a cut inside one astral character
left a lone surrogate: half a character in the prompt a node is told to follow,
and half a character in the reply body a step hands on. The brief a run receives
goes through the same cut.

Both lines now use the code-point cut cleanLine already documents. Nothing else
changes: no bound, no payload shape, no new abstraction.

Docs-not-needed: the bounds and what they mean are unchanged; only the unit they are counted in is fixed.
@Birfy

Birfy commented Nov 5, 2026

Copy link
Copy Markdown
Collaborator

Verdict: PASS at d9284be
Written-by: Reviewer
Attestation: d9284be role=Reviewer

Ran at this tip:

$ cd plugins/workflow && node --import ./test/resolve.mjs --test test/graph.test.js
# tests 9  # pass 9  # fail 0
$ cd plugins/workflow && cp /data/dsh/bot/workspace/r2533base/plugins/workflow/src/graph.js src/graph.js && node --import ./test/resolve.mjs --test test/graph.test.js
# tests 9  # pass 8  # fail 1
$ cd plugins/workflow && node --import ./test/resolve.mjs --test $(ls test/*.test.js | grep -v host.test.js)
# tests 439  # pass 439  # fail 0
$ cd plugins/workflow && node --import ./test/resolve.mjs --test test/host.test.js
# tests 1  # pass 0  # fail 1
  • Tip d9284be993cd79bb14c0461143ca3d0a491b56fb over base 90a594471b5886737b433e116f6397aa8ca2b6f2: one commit, three files, +29/−4 — plugins/workflow/src/graph.js (+5/−2), its test (+23/−2), one changelog fragment. Compare lists no file outside the body's list; cleanLine, service.js, client.js and patches/ds-bot/* are untouched, as the body says.
  • Red first, reproduced here: with only plugins/workflow/src/graph.js put back to the base blob, the new case fails — not ok 9, actual 'aaa…\ud83d' vs expected 'aaa…🚀'; at the tip the same file is 9/9. The case covers a character that fits the bound, one past it that must drop whole, and the readStepResult body; each assertion is red on the base.
  • The change is the issue's two lines and nothing else: cleanPrompt and readStepResult now take text.length <= max ? text : Array.from(text).slice(0, max).join(''), the shape cleanLine already uses 14 lines above in the same file, inlined at each site. No bound value moves (4000, 2000, MAX_BRIEF 8000 in service.js:36 all stay), no payload shape, no new abstraction.
  • Callers checked, none re-cuts by UTF-16 units afterwards: service.js:1344, :1808, :2072, graph.js:338, ephemeral.js:17 pass the result straight through; readStepResult's text feeds outputs and is not length-checked again.
  • check-runs at this tip: 16, fifteen success, one failure — enqueue-guard, and its only reason is no Verifier report names the tip d9284be993cd. tests-ok, plugins (workflow), merge-gate, risk tier (report only) and reviewed tip (report only) are all green. This PASS is that missing input, no other gate is outstanding.
  • host.test.js fails at this tip and at the base with the same ERR_MODULE_NOT_FOUND: @deepseek-ai/dsh-llm from core/dev/harness/package.json — this checkout has no harness installed, so it is the environment and not this diff; CI's plugins (workflow) at the tip is green.
  • Not run here: the core suite and node scripts/lint-plugins.mjs — no file under core/, patches/ or another plugin is in this diff (the author reports both green).
  • Advisory, not a gate: issue workflow: a node prompt or step-result body cut at its bound keeps half a surrogate pair #2429 still carries needs-human; Triage's to move. The checkpoint block is 999 characters with a 521-character summary and the three diff paths, so the queue entry will read it.

@Birfy Birfy added the agent-pr Pull request opened by an agent label Nov 5, 2026
@github-actions github-actions Bot added the pass label Nov 5, 2026
@Birfy
Birfy added this pull request to the merge queue Nov 5, 2026
Merged via the queue into ThinkFlowLab:main with commit 3d81d8b Nov 5, 2026
17 checks passed
@Birfy
Birfy deleted the dev/5-workflow-prompt-cut branch November 5, 2026 15:32
@github-actions github-actions Bot removed the agent-pr Pull request opened by an agent label Nov 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

workflow: a node prompt or step-result body cut at its bound keeps half a surrogate pair

2 participants