fix(synology-chat): strip internal tool-trace banners from outbound text#102925
Conversation
|
Codex review: needs maintainer review before merge. Reviewed July 9, 2026, 11:20 AM ET / 15:20 UTC. Summary PR surface: Source +2, Tests +26. Total +28 across 2 files. Reproducibility: yes. Current main source shows Synology Chat lacks the Review metrics: none identified. Root-cause cluster Members:
Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything. Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Rank-up moves:
Risk before merge
Maintainer options:
Next step before merge
Security Review detailsBest possible solution: Land the narrow Synology Chat sanitizer hook with regression coverage, while keeping the umbrella issue open for the remaining channel adapters and any shared-boundary decision. Do we have a high-confidence way to reproduce the issue? Yes. Current main source shows Synology Chat lacks the Is this the best way to solve the issue? Yes. This is the best narrow slice for Synology Chat: it reuses the existing SDK sanitizer hook already used by sibling channels, adds no config or new API surface, and tests the exported plugin hook directly. AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against 12ceefcb0393. Label changesLabel changes:
Label justifications:
Evidence reviewedPR surface: Source +2, Tests +26. Total +28 across 2 files. View PR surface stats
What I checked:
Likely related people:
What the crustacean ranks mean
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics. How this review workflow works
|
26ba784 to
a416d86
Compare
|
Land-ready maintainer proof for exact head
No remaining code or proof blocker found. |
|
Merged via squash.
|
Align with the pattern steipete landed in openclaw#102925: type sanitizeText as NonNullable<ChannelOutboundAdapter["sanitizeText"]> instead of the inherited optional Pick shape, and drop the now-redundant optional chaining on sanitizeText in the tests.
…ext (openclaw#102925) * fix(synology-chat): strip internal tool-trace banners from outbound text * fix(synology-chat): satisfy eslint curly rule * fix(synology-chat): add sanitizeText to outbound type contract * fix(synology-chat): use canonical outbound sanitizer type --------- Co-authored-by: Pick-cat <[email protected]> Co-authored-by: Peter Steinberger <[email protected]>
sanitizeText inherited the optional modifier from Pick<ChannelOutboundAdapter, "sanitizeText">, so even with the NonNullable intersection TS still inferred it as possibly undefined (TS2722 in the tests). Drop it from the Pick union so it is declared only via NonNullable<...> in the intersection, matching the steipete openclaw#102925 pattern.
Align with the pattern steipete landed in openclaw#102925: type sanitizeText as NonNullable<ChannelOutboundAdapter["sanitizeText"]> instead of the inherited optional Pick shape, and drop the now-redundant optional chaining on sanitizeText in the tests.
sanitizeText inherited the optional modifier from Pick<ChannelOutboundAdapter, "sanitizeText">, so even with the NonNullable intersection TS still inferred it as possibly undefined (TS2722 in the tests). Drop it from the Pick union so it is declared only via NonNullable<...> in the intersection, matching the steipete openclaw#102925 pattern.
Align with the pattern steipete landed in openclaw#102925: type sanitizeText as NonNullable<ChannelOutboundAdapter["sanitizeText"]> instead of the inherited optional Pick shape, and drop the now-redundant optional chaining on sanitizeText in the tests.
sanitizeText inherited the optional modifier from Pick<ChannelOutboundAdapter, "sanitizeText">, so even with the NonNullable intersection TS still inferred it as possibly undefined (TS2722 in the tests). Drop it from the Pick union so it is declared only via NonNullable<...> in the intersection, matching the steipete openclaw#102925 pattern.
Align with the pattern steipete landed in openclaw#102925: type sanitizeText as NonNullable<ChannelOutboundAdapter["sanitizeText"]> instead of the inherited optional Pick shape, and drop the now-redundant optional chaining on sanitizeText in the tests.
sanitizeText inherited the optional modifier from Pick<ChannelOutboundAdapter, "sanitizeText">, so even with the NonNullable intersection TS still inferred it as possibly undefined (TS2722 in the tests). Drop it from the Pick union so it is declared only via NonNullable<...> in the intersection, matching the steipete openclaw#102925 pattern.
Part of #90684
What Problem This Solves
Synology Chat outbound delivery omitted the channel sanitizer hook, so internal assistant tool-trace banners such as “⚠️ 🛠️ search repos (agent) failed” could reach end users. A trace-only reply was also sent as a standalone webhook message.
Why This Change Was Made
The Synology Chat outbound adapter now uses the canonical
sanitizeAssistantVisibleTexthook before transport, matching the existing channel-owned delivery contract. The adapter type reusesChannelOutboundAdapter["sanitizeText"]instead of maintaining a narrower local callback shape that could drift fromReplyPayload.This is the narrowest correct owner boundary: the shared durable-delivery pipeline invokes the explicit channel hook, then drops payloads that become empty before platform I/O. A global default would change channels that intentionally preserve different text, while sanitizing inside the HTTP client would be too late and would duplicate delivery policy.
User Impact
Evidence
a416d86e0d6caafbec575c6f838b6462ae424bd3.tbx_01kx56yss8pk3ze695em4bz009(harbor-barnacle), Linux Node 24.openclaw message send --channel synology-chat+ real HTTP webhook capture:mainposted all three inputs verbatim: mixed prose/banner, trace-only banner, and normal prose;Done.and the unchanged normal prose;pnpm test extensions/synology-chat/src/channel.test.ts extensions/synology-chat/src/client.test.ts— 62/62 passed.check:changed— passed, including TypeScript, extension/core lint, import cycles, package guards, and repository policy checks.The environment has no Synology NAS webhook credentials or hardware, so the final dependency hop into a physical NAS was unavailable. The real Gateway, plugin loader, durable-delivery sanitizer/drop path, CLI client, Synology HTTP transport, recipient encoding, and webhook request were exercised end to end against a local endpoint. No screenshot is useful for this non-visual transport fix; the captured request bodies are the behavioral proof.