Repository navigation
fix(client): the voice reads a stored reply's words, not its untrusted fence or its delegation trace - #1409
fix(client): the voice reads a stored reply's words, not its untrusted fence or its delegation trace#1409HugoXK wants to merge 1 commit into
Conversation
|
Verdict: FAIL at 8a71763 写这条前现读:head 阻断项:压平后的 trace 仍然被念出来(
// 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)/, ''))
也就是:在本单要修的这条症状上,tip 与 base 一字不差, 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"改法(一行,放 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()
}并在 以下是我核过、确实成立的部分(不是全盘打回)
低、不阻断(同一条下次一起收)
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: 真机麦克风/ 结论: |
|
Self-test: FAIL at 8a71763 Measured on the tip tree, not from the PR body. This tip already carries a Reviewer Verdict — comment Four groups: all pass
Generated artifacts are regenerated, not hand-edited: 3 added / 5 modified, and per-file numstat matches the body table line by line ( Blocking finding (same one the Reviewer names, reproduced independently)
The new test pins only the newline shape, which Non-blocking note: a complete fence embedded in prose ( as of my read: 1 comment, newest |
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.
8a71763 to
150fdbd
Compare
|
Self-test: PASS at 150fdbd 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 The review blocker, re-measured
So the three shapes asked for (flattened, newline, Four groups
Artifacts are regenerated: 3 added / 5 modified, as of my read: 2 comments, newest |
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:
150fdbd90e9f289d34fe0eb1b8100344b3da281fBase:
7a4fa61e(main,#1404)Tree:
08c0586b858ec271ab19e42607bed2136c32d6bdWhat changed
core/src/client/voice.js:15built 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 shapecore/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.jsgains one exportedspeechOf(text)next topreviewOf: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.jsimports it and keepsconst plainSpeech = speechOf; the outline projection that takes the last turn (:65-70) and thespeechSynthesiscall site (:187) are untouched.Review rework, this tip. The first tip read the trace with
stripTracealone, and that rule needs the trace to end at a line break — the outline stores it on the words instead, soChief → Product B踢后测4was still spoken whole whilepreviewOfon the same turn showedB踢后测4.speechOfnow 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 intext.js).previewOfis untouched in this version — byte-identical to the reviewed tip — andvoice.js,palette.jsand the write side are not in the rework either.One trace rule, and who moves it.
TRACE_FLATis defined once intext.jsandspeechOfis its only voice-side reader; this version adds no second chain, andpreviewOf's own rule is left exactly as the reviewer saw it.#1395is taking the name-aware version (readableLine(text, names), longest bot name first) into the shared read chain. Whichever of the two lands second switchesspeechOftoreadableLine(text, names)in that same change — then the voice and the row preview read through one rule andTRACE_FLATis 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 → 已完成andUse Settings → Models firstare 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"> insideis spoken asprose 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.jsHalf (a): the new test against the sources of the reviewed tip
8a71763e(core/src/client/text.jsandcore/src/client/voice.jstaken from that commit, the test kept) — 2 red / 5 green:Half (b): the same command at this tip:
The reviewer's reproduction, run at this tip (
-eneeds--input-type=modulefor the top-levelawait import):Knife
The test file is the binding, not the assertion shape:
sha256sum core/test/voice-speech.test.jsis97abdc5d42540f1c75fd6cd952f817ad3b798ad4368b64a47af53fff9d61e359before and after every knife, andgit diff -- core/test/voice-speech.test.jsis empty.voice.jsproduction lines reverted (the ordered knife): 7/7 green — nothing goes red, because this rework does not touchvoice.jsat all: the reviewed tip already readsconst plainSpeech = speechOf, and the file is byte-identical on both tips (dac1fe8d57ff7ba9658e2c80c9438143e95dd30bed60666a28b705de66d385dc). There is novoice.jsline left to revert here, and I am not dressing a no-op up as a red. Whatvoice.jsdoes bind is the first tip's change: withvoice.jstaken from18d567bb(#1405's base, where it rebuilt its own chain) the import assertion goes red — 1 red / 6 green.core/src/client/text.jsback to8a71763e, 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'. Restoringtext.jsreturns it to03748e7aff905cd4c5771ae65eb220f542fcf21a4258acc1edfc89d63f52567fand the suite to 7/7.Why the product edit reads as a generated diff
core/client.jsand everything inpatches/ds-bot/are generated:npm run buildincore/, thennode scripts/ds-bot-patches.mjs generate.client.jsis carried as one whole-file patch (0008-client-bundle.patch, 276 hunks), so any line added inside the bundle moves everything after it andgeneratere-derives that hunk table: against the reviewed tip the bundle itself differs by three lines (git diff 8a71763e HEAD -- core/client.jsis +2/−1) while the regenerated patch file does not line up with the old copy line by line.node scripts/ds-bot-patches.mjs checkis what proves the patch andcore/still agree.Against this base,
git diff --numstat 7a4fa61 HEAD:core/src/client/text.jsTRACE_FLAT+ thespeechOfchain (+10of it on top of the reviewed tip)core/src/client/voice.jscore/test/voice-speech.test.js+11on top of the reviewed tip)core/client.jsnpm run buildpatches/ds-bot/0001-core-fixes.patchtext.jsandvoice.jshunkspatches/ds-bot/0008-client-bundle.patchpatches/ds-bot/files/test/voice-speech.test.jschangelog.d/1405.fixed.mdgit diff --name-status 7a4fa61 HEADis those 8 paths, threeAand fiveM: no deletion, no rename, and nothing outsidecore/,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 as150fdbd90e9f289d34fe0eb1b8100344b3da281f, both tree08c0586b858ec271ab19e42607bed2136c32d6bd— base7a4fa61e:TZ=UTC npm run verify: exit 0 — core 1830/1830, social 525, tree 125, timeline 125, workflow 28,.github/scripts545, 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)). Insidenpm run verifythe same script reads the clone's localorigin/main, which is older than this base, and prints 65; the run above is the one that matchesmain.node core/scripts/build-client.mjs --check: exit 0.node scripts/ds-bot-patches.mjs check: threeok—ds-bot/=Fei2411/DeepSeek-Bot@2b10c70(tree5834c00, v0.1.5), 10 patches apply cleanly in order,core/equalsds-bot/+patches/ds-bot/. Both mirrors and both patches come fromgenerate, never by hand.What I did not run
core/dev/bench.sh smoke— this changes a pure client-side text helper and touches neitherindex.jsnorsrc/host/; the DSH harness mount is read-only in this sandbox anyway.speechSynthesisitself 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)`."}