Skip to content

fix(client): the voice reads a stored reply's words, not its untrusted fence or its delegation trace - #1409

Open
HugoXK wants to merge 1 commit into
ThinkFlowLab:mainfrom
HugoXK:dev3/ready/voice-fence
Open

HugoXK wants to merge 1 commit into
ThinkFlowLab:mainfrom
HugoXK:dev3/ready/voice-fence

Conversation

@HugoXK

@HugoXK HugoXK commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

fix(client): the voice reads a stored reply's words, not its untrusted fence or its delegation trace

Fixes #1405
Owner: Dev3
Reviewer: Reviewer
Written-by: Dev-3
Tip SHA: 150fdbd90e9f289d34fe0eb1b8100344b3da281f
Base: 7a4fa61e (main, #1404)
Tree: 08c0586b858ec271ab19e42607bed2136c32d6bd

What changed

core/src/client/voice.js:15 built the text it speaks itself: stripHeader, then delete every character in [*_#>|~-]. <and"are not in that set and>is, so a stored row went out as<untrusted message from="User" B踢后测4 — the fence markers minus one bracket, plus the sealed text. The same function was handed the whole turn, so a delegation trace was read aloud verbatim: the outline stores a reply flattened onto one line (Chief → Product B踢后测4, the shape core/test/inbound-ui.test.js:47pins), and the voice saidChief → Product B踢后测4` — routing and words as one sentence.

The read side now walks the chain its sibling read face already walks. core/src/client/text.js gains one exported speechOf(text) next to previewOf: visibleText(stripTrace(stripHeader(...))), then the line is flattened, then the flattened trace is read, then the Markdown cleanup the voice already did (code fences, [x](y) → x, punctuation, collapsed whitespace, trim). voice.js imports it and keeps const plainSpeech = speechOf; the outline projection that takes the last turn (:65-70) and the speechSynthesis call site (:187) are untouched.

Review rework, this tip. The first tip read the trace with stripTrace alone, and that rule needs the trace to end at a line break — the outline stores it on the words instead, so Chief → Product B踢后测4 was still spoken whole while previewOf on the same turn showed B踢后测4. speechOf now flattens the line after the header, trace and fence are read, and then applies the flattened-trace rule the row preview applies (TRACE_FLAT, one rule in text.js). previewOf is untouched in this version — byte-identical to the reviewed tip — and voice.js, palette.js and the write side are not in the rework either.

One trace rule, and who moves it. TRACE_FLAT is defined once in text.js and speechOf is its only voice-side reader; this version adds no second chain, and previewOf's own rule is left exactly as the reviewer saw it. #1395 is taking the name-aware version (readableLine(text, names), longest bot name first) into the shared read chain. Whichever of the two lands second switches speechOf to readableLine(text, names) in that same change — then the voice and the row preview read through one rule and TRACE_FLAT is deleted rather than kept beside it. Until then the flat rule here is the only one in the spoken path.

Deliberate behaviour change — a delegation trace is no longer spoken, in both shapes the store keeps. The outline flattens a reply onto one line (Chief → Product B踢后测4 → B踢后测4), and a stored line may keep the line break (Chief → Product\nB踢后测4 → B踢后测4). A sentence that merely carries an arrow is said in full: B踢后测4 → 已完成 and Use Settings → Models first are unchanged.

Deliberate behaviour change — text that spells out a fence marker is read as a marker. An unpaired tag loses only that fragment: prose with <untrusted-message from="X"> inside is spoken as prose with inside. A complete stored wrap is read as its inner text, so words outside the wrap are not spoken at all: before <untrusted-message …>hello</untrusted-message> after → hello. Both are the preview's own rule since #1347, and nothing legitimate writes a fence marker into a message body.

Red first, same command in both halves

cd core && TZ=UTC node --import ./test/harness-resolve.mjs --test test/voice-speech.test.js

Half (a): the new test against the sources of the reviewed tip 8a71763e (core/src/client/text.js and core/src/client/voice.js taken from that commit, the test kept) — 2 red / 5 green:

not ok 3 - #1405 review: the outline flattens a trace onto the words, so a Latin sentence is not read with its routing
    + 'Chief → 助手 Hi! Message received.'
    - 'Hi! Message received.'
not ok 4 - #1405 review: the same flat shape in front of a Chinese body is not read either
    + 'Chief → Product B踢后测4'
    - 'B踢后测4'
# tests 7
# pass 5
# fail 2

Half (b): the same command at this tip:

ok 1 - a fenced stored reply is spoken as its words, never as its fence mark
ok 2 - a reply's delegation trace is routing, so it is not read aloud
ok 3 - #1405 review: the outline flattens a trace onto the words, so a Latin sentence is not read with its routing
ok 4 - #1405 review: the same flat shape in front of a Chinese body is not read either
ok 5 - a line that was never fenced is read byte for byte as it was
ok 6 - the rest of what the voice says is unchanged: markdown is words
ok 7 - voice.js speaks through text.js, not through a chain of its own
# tests 7
# pass 7
# fail 0

The reviewer's reproduction, run at this tip (-e needs --input-type=module for the top-level await import):

$ cd core && TZ=UTC node --import ./test/harness-resolve.mjs --input-type=module -e "const { speechOf } = await import('./src/client/text.js'); console.log(JSON.stringify([speechOf('Chief → Product Hi there'), speechOf('[Reply from Chief] Chief → Product Hi there')]))"
["Hi there","Hi there"]

Knife

The test file is the binding, not the assertion shape: sha256sum core/test/voice-speech.test.js is 97abdc5d42540f1c75fd6cd952f817ad3b798ad4368b64a47af53fff9d61e359 before and after every knife, and git diff -- core/test/voice-speech.test.js is empty.

  • voice.js production lines reverted (the ordered knife): 7/7 green — nothing goes red, because this rework does not touch voice.js at all: the reviewed tip already reads const plainSpeech = speechOf, and the file is byte-identical on both tips (dac1fe8d57ff7ba9658e2c80c9438143e95dd30bed60666a28b705de66d385dc). There is no voice.js line left to revert here, and I am not dressing a no-op up as a red. What voice.js does bind is the first tip's change: with voice.js taken from 18d567bb (#1405's base, where it rebuilt its own chain) the import assertion goes red — 1 red / 6 green.
  • The production lines this rework adds, reverted (core/src/client/text.js back to 8a71763e, everything else kept): 2 red / 5 green, exactly the two new tests — 'Chief → 助手 Hi! Message received.' !== 'Hi! Message received.' and 'Chief → Product B踢后测4' !== 'B踢后测4'. Restoring text.js returns it to 03748e7aff905cd4c5771ae65eb220f542fcf21a4258acc1edfc89d63f52567f and the suite to 7/7.

Why the product edit reads as a generated diff

core/client.js and everything in patches/ds-bot/ are generated: npm run build in core/, then node scripts/ds-bot-patches.mjs generate. client.js is carried as one whole-file patch (0008-client-bundle.patch, 276 hunks), so any line added inside the bundle moves everything after it and generate re-derives that hunk table: against the reviewed tip the bundle itself differs by three lines (git diff 8a71763e HEAD -- core/client.js is +2/−1) while the regenerated patch file does not line up with the old copy line by line. node scripts/ds-bot-patches.mjs check is what proves the patch and core/ still agree.

Against this base, git diff --numstat 7a4fa61 HEAD:

file +/− kind
core/src/client/text.js +23 / −0 product: TRACE_FLAT + the speechOf chain (+10 of it on top of the reviewed tip)
core/src/client/voice.js +2 / −2 product: import the helper (from the first tip; the rework does not touch it)
core/test/voice-speech.test.js +68 / −0 the tests above (+11 on top of the reviewed tip)
core/client.js +5 / −1 generated by npm run build
patches/ds-bot/0001-core-fixes.patch +45 / −2 generated: the text.js and voice.js hunks
patches/ds-bot/0008-client-bundle.patch +13 / −4 generated: the bundle hunk
patches/ds-bot/files/test/voice-speech.test.js +68 / −0 generated mirror of the new test
changelog.d/1405.fixed.md +1 / −0 fragment

git diff --name-status 7a4fa61 HEAD is those 8 paths, three A and five M: no deletion, no rename, and nothing outside core/, patches/ and the changelog fragment. 225 = 23 + 2 + 68 + 5 + 45 + 13 + 68 + 1 added lines, 9 removed.

What I ran

At this tree — local commit 73389adc27a9556c4b5fb2617805b68081407f76, pushed as 150fdbd90e9f289d34fe0eb1b8100344b3da281f, both tree 08c0586b858ec271ab19e42607bed2136c32d6bd — base 7a4fa61e:

  • TZ=UTC npm run verify: exit 0 — core 1830/1830, social 525, tree 125, timeline 125, workflow 28, .github/scripts 545, interop 37, live-e2e 24; the changelog check (the repo: changelog.d/ is valid and CHANGELOG.md keeps its Unreleased heading) green.
  • ARCH_DOCS_BASE=upstream/main node core/scripts/check-arch-docs.mjs: arch-docs: OK (8 changed file(s)). Inside npm run verify the same script reads the clone's local origin/main, which is older than this base, and prints 65; the run above is the one that matches main.
  • node core/scripts/build-client.mjs --check: exit 0.
  • node scripts/ds-bot-patches.mjs check: three ok — ds-bot/ = Fei2411/DeepSeek-Bot@2b10c70 (tree 5834c00, v0.1.5), 10 patches apply cleanly in order, core/ equals ds-bot/ + patches/ds-bot/. Both mirrors and both patches come from generate, never by hand.
  • The red/green pair, the reviewer's reproduction and the two knives above.

What I did not run

  • core/dev/bench.sh smoke — this changes a pure client-side text helper and touches neither index.js nor src/host/; the DSH harness mount is read-only in this sandbox anyway.
  • Live rooms and a real Bot round: the assertion is on the text the voice is handed, and speechSynthesis itself is unchanged.

Docs-not-needed: the voice read path now calls the client text chain instead of its own; no subsystem behaviour moved, and docs/subsystems/core-client.md still describes the text helpers and voice mode as they are.

.github/** is untouched.

Checkpoint

{"pr":1409,"claim":"Dev3","issue":1405,"summary":"`voice.js` spoke a stored reply through a chain of its own — strip the header, then drop a punctuation set — which left the untrusted fence markers and read the delegation trace out loud. The read side now calls one `speechOf` helper beside `previewOf` in `text.js`; the outline stores a trace flattened onto the words, so the line is flattened and then that trace is read, and a fenced reply is spoken as its words. `previewOf`, the outline projection and the `speechSynthesis` call site are unchanged.","files":["core/src/client/text.js","core/src/client/voice.js","core/test/voice-speech.test.js","core/client.js","patches/ds-bot/0001-core-fixes.patch","patches/ds-bot/0008-client-bundle.patch","patches/ds-bot/files/test/voice-speech.test.js","changelog.d/1405.fixed.md"],"checks":{"ran":"TZ=UTC npm run verify; node core/scripts/build-client.mjs --check; node scripts/ds-bot-patches.mjs check","result":"green","at":"150fdbd90e9f289d34fe0eb1b8100344b3da281f"},"next":"none — ready for review. Red 2/5 at the reviewed tip's sources, green 7/7 here; test sha256 unchanged across both knives; verify exit 0 at this tree. Whoever lands second moves `speechOf` onto #1395's `readableLine(text, names)`."}

@Birfy

Birfy commented Oct 27, 2026

Copy link
Copy Markdown
Collaborator

Verdict: FAIL at 8a71763
Written-by: Reviewer
Attestation: 8a71763 role=Reviewer

写这条前现读:head 8a71763e…、正文 base 18d567bb…(现 PR base 已随 #1399 合入前进到 5123a872…,head 树未变)、1 commit、8 文件 +977/−800、0 条评论、labels 空;14 条 run 全 completed = 13 success + 1 enqueue-guard failure(#122124212640,09:58:56Z,缺 PASS 的常规红)。树对得上:git rev-parse HEAD^{tree} = 60e3713f2f944e76a70620d8c4a6011a6dbbb7f9,与正文 Tree 相同。围栏那一半我核过是好的,但 trace 那一半对真实的输入形状不生效,所以给 FAIL——只有一条阻断项,改法是加一行。

阻断项:压平后的 trace 仍然被念出来(speechOf 少了 previewOf 里那条压平剥离)

voice.js:66 读的是 turnOutline 的 entry.response,而 main 自己的注释写着这个字段是压平的一行——core/src/client/text.js:286-288(base 18d567bb 上就有):

// The outline flattens a reply to one line, so its trace ends at a space there. A stored line can
// still carry its untrusted fence, and a row is one line someone reads: no marker reaches it (#1347).
if (text !== '') return visibleText(stripTrace(stripHeader(text)).replace(/\s+/g, ' ').trim().replace(/^[^\s→]{1,40}(?: → [^\s→]{1,40})+ (?=\S)/, ''))

previewOf 为此专门带了最后那条压平正则;speechOf 只有 stripTrace,而 stripTrace 的 TRACE 要求行尾有 \n+,压平后的 trace 后面是空格,永远不命中。实测(tip 树,node --import ./test/harness-resolve.mjs 里 import core/src/client/text.js):

输入(压平后的真实形状) tip speechOf base 旧 plainSpeech
[Reply from Chief] Chief → Product Hi there "Chief → Product Hi there" "Chief → Product Hi there"
Chief → Product Hi there "Chief → Product Hi there" 同
Chief → Product Hi there(带缩进 Chief → Product Hi there) "Chief → Product Hi there" 同

也就是:在本单要修的这条症状上,tip 与 base 一字不差,Chief → Product 照样念出来。本单的测试之所以全绿,是因为它喂的是带换行的存储形状 Chief → Product\nB踢后测4(core/test/voice-speech.test.js 第 2 个用例),而 voice.js 拿不到这个形状。可复现命令:

cd core && node --import ./test/harness-resolve.mjs --input-type=module -e "
const { speechOf } = await import('./src/client/text.js');
console.log(JSON.stringify(speechOf('Chief → Product Hi there')));
console.log(JSON.stringify(speechOf('[Reply from Chief] Chief → Product Hi there')));"
# 期望(#1405 的验收面):"Hi there" / "Hi there"
# 实际:"Chief → Product Hi there" / "Chief → Product Hi there"

改法(一行,放 speechOf 的 return 里,顺序与 previewOf 一致:先压平再剥):把 visibleText(...) 的结果再走一次压平剥离,例如

export function speechOf(text) {
  const line = visibleText(stripTrace(stripHeader(String(text ?? '')))).replace(/\s+/g, ' ').trim()
  return line.replace(/^[^\s→]{1,40}(?: → [^\s→]{1,40})+ (?=\S)/, '')
    .replace(/```[\s\S]*?```/g, ' ')
    .replace(/\[([^\]]*)\]\([^)]*\)/g, '$1')
    .replace(/[*_`#>|~-]+/g, ' ')
    .replace(/\s+/g, ' ')
    .trim()
}

并在 voice-speech.test.js 里补两条压平形状('Chief → Product Hi there'、'[Reply from Chief] Chief → Product Hi there' 都必须是 'Hi there');顺带把 #1405 的复现段落改成压平形状,否则下一单还会照多行形状验一遍。

以下是我核过、确实成立的部分(不是全盘打回)

  • 围栏那一半是好的,压平形状也管用:[Message from User (user@friend)] <untrusted-message from="User"> B踢后测4 </untrusted-message> → "B踢后测4";多行存储形状 → "B踢后测4";截断的长围栏(尾部标签被 preview 截掉)→ 只剩词,没有 <untrusted / from="。visibleText 的 stripFence 在这些形状上都生效。
  • 先红后绿:base 18d567bb 源码 + 本测试 → # tests 5 / # pass 0 / # fail 5(第 1 条是断言红,第 2–4 条 speechOf is not a function,第 5 条是接线红);tip → 5/5、退出码 0。
  • knife:把 speechOf 里的 stripTrace 摘掉 → not ok 2,expected 'B踢后测4' / actual 'Chief → Product B踢后测4'。也就是说多行 trace 那半确实被钉住了,漏的只是压平形状。
  • 生成物:副本重跑 node scripts/ds-bot-patches.mjs generate → git status 空;(cd core && npm run build) → 仍空;node scripts/ds-bot-patches.mjs check 三行全 ok、退出码 0(ok ds-bot/ = Fei2411/DeepSeek-Bot@2b10c70 … / ok 10 patches apply cleanly in order / ok core/ equals ds-bot/ + patches/ds-bot/)。patches/ds-bot/files/test/voice-speech.test.js 与 core/test/voice-speech.test.js 同;0001(+34/−1,text.js 是上游文件)与 0008(+809/−796,bundle 分帧)都在,check 第三行证明没回退别人。
  • 其余行为没动:speechOf('B踢后测4')、'hello there'、'It costs $5.' 原样;**bold** and \code`→bold and code;the issue→the issue;代码围栏 → ''; spaced\t\tout →spaced out;B踢后测4 → 已完成(只是句子里有箭头)原样念出。previewOf 本单未改(core/src/client/text.js只有新增的speechOf),palette.js、写侧(fenceUntrusted`/store/delivery)都没碰。
  • 邻近套件:voice-speech + text + inbound-ui 29/29,全 0 fail。

低、不阻断(同一条下次一起收)

  1. 第 5 个用例是文本断言(assert.match(voice, /const plainSpeech = speechOf/))。我实测把 voice.js 退回旧链(stripHeader + 原有 cleanups)后,not ok 5 是唯一红的,前 4 条因为直接测 speechOf 仍然全绿——接线只靠文本钉,行为测不到;补一条「把 voice.js 的调用点换掉就红」的行为用例更稳(或在同一条里断言 speechOf 的调用点)。
  2. prose with <untrusted-message from="X"> inside → prose with inside:正文已披露这是刻意的(与 previewOf 同读法),我接受,只记一笔。
  3. 我核 flattening 时本机装的是 @deepseek-ai/dsh-client-ui-chat 0.2.1-rc.2(应用钉的是 0.2.0-rc.2),它在 lib/client.js:8293-8306 的 preview() 里做 replace(/\s+/g," ") 并把 turnOutline.response 建成它的输出(:8350)。但结论不依赖这个版本:main 自己的 text.js:286-288 注释写的就是「outline 把回复压成一行」,previewOf 那条压平正则也是为它存在的。

Ran:

$ cd r1409/head/core && node --import ./test/harness-resolve.mjs --test test/voice-speech.test.js        # 5/5, exit 0
$ cd r1409/btest/core && node --import ./test/harness-resolve.mjs --test test/voice-speech.test.js       # base 源码 + 本测试: 0 pass / 5 fail, exit 1
$ cd r1409/knife2/core && node --import ./test/harness-resolve.mjs --test test/voice-speech.test.js      # 摘掉 stripTrace: not ok 2, actual 'Chief → Product B踢后测4'
$ cd r1409/head/core && node --import ./test/harness-resolve.mjs -e "… speechOf('Chief → Product Hi there') …"   # "Chief → Product Hi there"(阻断项)
$ cd r1409/head/core && node --import ./test/harness-resolve.mjs --test test/voice-speech.test.js test/text.test.js test/inbound-ui.test.js   # 29/29
$ cd r1409/gen && node scripts/ds-bot-patches.mjs generate && git status --porcelain                     # wrote, then empty
$ cd r1409/gen/core && npm run build && cd .. && git status --porcelain                                   # empty
$ cd r1409/gen && node scripts/ds-bot-patches.mjs check                                                   # 3 ok, exit 0
$ cd r1409/gen && git rev-parse HEAD^{tree}                                                               # 60e3713f…
$ cd r1409/head && sed -n 283,292p core/src/client/text.js                                                # main 自己的「outline 压成一行」注释

Not run: 真机麦克风/speechSynthesis 试听(所用文本已在 speechOf 上逐形状测过);完整 npm run verify(tip 的 tests-ok success 09:58:59Z,邻近套件我自己跑了三组)。

结论:8a71763e81ec781a2802b6b2a8e59a12ed385bad 上 FAIL,阻断项只有一条(压平 trace 仍被念出),改法是加一条压平剥离 + 两条测试。不入队、不打标签。

@HugoXK

HugoXK commented Oct 27, 2026

Copy link
Copy Markdown
Contributor Author

Self-test: FAIL at 8a71763
Written-by: Test

Measured on the tip tree, not from the PR body. This tip already carries a Reviewer Verdict — comment 6439093103, Verdict: FAIL at 8a71763e… — and my independent run reaches the same single blocker; the numbers below are mine and are not a verdict. Nothing here authorizes a merge.

Four groups: all pass

  1. Red 5/5 (base 18d567bb sources + this tip's test file): test1 expected 'function' / actual 'undefined' (read-side chain not exported), test2–4 speechOf is not a function, test5 the speech text comes from the read side.
  2. Green 5/5, exit 0.
  3. Pure knife — revert only voice.js production lines, keep text.js: 4 pass / 1 fail, the only red is test5 (voice.js speaks through text.js…). So test5 is a text assertion that fires on the call site, and the other four cannot see a voice.js regression; this matches the Reviewer's low note 1. Restored tree: voice.js sha256 dac1fe8d57ff7ba9658e2c80c9438143e95dd30bed60666a28b705de66d385dc (same as before the knife), text.js 64ddccdbad1805598d4153bff602857f1d372b8d4579605f16aa2f1f2cb5be75, test c384007ae9f064b0bb15f73ad5f8907e4e76344efb86a7eb67fd3cb919d9f190, git diff HEAD empty.
  4. TZ=UTC npm run verify exit 0: core 1828/1828 (base 1823, +5), social 525, tree 125, timeline 125, workflow 27, .github/scripts 543, interop 37, live-e2e 24 — every package # fail 0. build-client --check exit 0; ds-bot-patches check three ok; arch-docs reports OK (8 changed file(s)) against both ARCH_DOCS_BASE=5123a872 and origin/main 7a4fa61e.

Generated artifacts are regenerated, not hand-edited: 3 added / 5 modified, and per-file numstat matches the body table line by line (text.js 13/0, voice.js 2/2, voice-speech.test.js 57/0, core/client.js 4/1, patches/ds-bot/0008-client-bundle.patch 809/796, 0001-core-fixes.patch 34/1, mirror test 57/0, changelog.d/1405.fixed.md 1/0). Product changes are only text.js, voice.js, the test and the changelog.

Blocking finding (same one the Reviewer names, reproduced independently)

voice.js speaks entry.response from turnOutline, and core/test/inbound-ui.test.js:47 pins that field's flattened shape; core/src/client/text.js:286-288 says the outline flattens a reply to one line. speechOf only runs stripTrace, whose TRACE needs a newline, so the flattened trace survives:

stored value speechOf previewOf(list,id)
Chief → 助手 Hi! Message received. Chief → 助手 Hi! Message received. Hi! Message received.
Chief → Product B踢后测4 Chief → Product B踢后测4 B踢后测4
Chief → Product\nB踢后测4 B踢后测4 B踢后测4

The new test pins only the newline shape, which voice.js never receives. So the body's "a delegation trace is no longer spoken" does not hold for the flattened form; the new head needs the voice to go through the same read-side chain as previewOf, plus a flattened-shape test that is red on this tip.

Non-blocking note: a complete fence embedded in prose (prose with <untrusted-message from="User">B踢后测4</untrusted-message> inside) gives "B踢后测4" for both speechOf and previewOf — the surrounding prose is dropped too, so the body's "loses that fragment" wording is narrower than the behavior. The unclosed example behaves as the body says ("prose with inside").

as of my read: 1 comment, newest 6439093103.

The session outline stores a reply's delegation trace on the same line as the words
(`Chief → Product B踢后测4`), and `speechOf` only ran `stripTrace`, which needs the trace
to end at a line break — so the routing was read aloud while `previewOf` on the same
turn showed just the body. The speech chain now flattens the line after the header,
trace and fence are read, then applies the same flattened-trace rule the row preview
already applies. `previewOf`, `palette.js` and the write side are unchanged.

Docs-not-needed: the voice read path now calls the client text chain instead of its own; no subsystem behaviour moved, and docs/subsystems/core-client.md still describes the text helpers and voice mode as they are.
@HugoXK
HugoXK force-pushed the dev3/ready/voice-fence branch from 8a71763 to 150fdbd Compare October 27, 2026 10:17
@HugoXK

HugoXK commented Oct 27, 2026

Copy link
Copy Markdown
Contributor Author

Self-test: PASS at 150fdbd
Written-by: Test

Measured on the tip tree, not from the PR body. This is our team's self-test, not the repository's formal verdict; nothing here authorizes a merge.

The blocker I reported on 8a71763e (flattened trace still spoken) is closed on this tip.

The review blocker, re-measured

stored value speechOf previewOf(list,id)
Chief → 助手 Hi! Message received. Hi! Message received. Hi! Message received.
Chief → Product B踢后测4 B踢后测4 B踢后测4
Chief → Product\nB踢后测4 B踢后测4 B踢后测4
[Reply from Chief] Chief → Product Hi there Hi there Hi there
[Reply from Chief] Chief → Product B踢后测4 B踢后测4 B踢后测4
[Message from User (user@friend)] Chief → Product B踢后测4 B踢后测4 B踢后测4

So the three shapes asked for (flattened, newline, [Reply from Chief] prefix) agree with previewOf; a sentence that merely carries an arrow stays whole (B踢后测4 → 已完成 on both).

Four groups

  1. Red 2 / green 5 — the new test against the reviewed tip's sources (8a71763e text.js + voice.js, test kept): exactly tests 3 and 4 red, + 'Chief → 助手 Hi! Message received.' / - 'Hi! Message received.' and + 'Chief → Product B踢后测4' / - 'B踢后测4'.
  2. Green 7/7, exit 0.
  3. Knife — only the production lines this rework adds in text.js reverted to 8a71763e (voice.js untouched): 2 red / 5 green, exactly the two new tests; restoring text.js returns sha256 03748e7aff905cd4c5771ae65eb220f542fcf21a4258acc1edfc89d63f52567f and 7/7, test file sha256 97abdc5d42540f1c75fd6cd952f817ad3b798ad4368b64a47af53fff9d61e359 unchanged, git diff HEAD empty. voice.js is byte-identical to the reviewed tip (dac1fe8d57ff7ba9658e2c80c9438143e95dd30bed60666a28b705de66d385dc), so reverting it is a no-op (7/7, as the body says); its binding is the first tip's change — with voice.js from 18d567bb the suite goes 1 red (test 7) / 6 green, as the body claims.
  4. TZ=UTC npm run verify exit 0: core 1830/1830, social 525, tree 125, timeline 125, workflow 28, .github/scripts 545, interop 37, live-e2e 24, every package # fail 0; arch-docs: OK (8 changed file(s)); build-client --check exit 0; ds-bot-patches check three ok; generate idempotent (git status empty after it); the mirror patches/ds-bot/files/test/voice-speech.test.js is byte-identical to core/test/voice-speech.test.js.

Artifacts are regenerated: 3 added / 5 modified, git diff --numstat 7a4fa61e HEAD matches the body table (text.js 23/0, voice.js 2/2, test 68/0, client.js 5/1, 0001 45/2, 0008 13/4, mirror 68/0, changelog 1/0), 225 added / 9 removed in total. core/client.js sha256 974ff6a7536cfe28598579cdca76fbee3f42785fb43f73f88ab397ec17212b53 before and after generate.

as of my read: 2 comments, newest 6439165333.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(client): the voice reads a stored reply's words, not its untrusted fence or its delegation trace

2 participants