Follow-up to 6505971: the pre-commit prettier hook rewrapped the new
import and an Array.from() call after the commit landed, leaving the
working tree dirty. Commit the reflow so the tree is clean.
Long bot replies that exceeded Discord's 2000-char limit were split by a
naive byte-slice loop. When the split fell inside a ```fenced code block
the first chunk lost its closing fence and the next chunk lost its
opener, so Discord rendered raw backticks and the remainder as headings
or bold instead of code.
Replace the slice loop with chunkForDiscord(), a fence-aware splitter
that prefers newline boundaries, re-closes any open fence at the end of
a chunk, and reopens it (with the same language tag) at the start of the
next. Single oversize lines still fall back to a surrogate-pair-safe
byte split. Adds 10 unit tests covering the seam, multi-fence
documents, surrogate pairs, and single-line overflow.
The reviewer (codex) sometimes emits a final result with no visible text.
The runtime marks the run failed → handleFailedReviewerExecution
preserves status as review_ready → the follow-up scheduler re-queues
another reviewer-turn → loop. The existing round_trip / arbiter caps
never fire because round_trip_count only advances on owner-side
submissions; bot-side reviewer flakes never increment it.
Mirror the owner_failure_count pattern: add reviewer_failure_count to
paired_tasks, increment on each silent failure, and escalate via
requestArbiterOrEscalate once it reaches 2. Reset to 0 on every
successful reviewer completion path (PROCEED/REVISE/arbiter/wait-for-user)
and when the owner re-submits a fresh review cycle.
Repro chat: 1507762222724546560 (stock-adiviser). Task
33968d31-0da2-480c-85d3-7a3999822ab4 logged 11 consecutive
reviewer-turn entries with no work_items and round_trip_count stuck
at 1.
Previously formatOutbound stripped `*`, `_`, `~`, `|`, `` ` ``, `#`, `>`
from prose so they wouldn't trigger Discord formatting. That worked but
silently lost information — `**DONE**`, `STEP_DONE`, ~~strike~~ all
arrived in Discord with the source characters missing.
Switch to backslash-escape so the literal source text shows up while
still suppressing the formatting interpretation. Triple-backtick fenced
code blocks are still preserved verbatim.
Escape is non-idempotent (running it twice double-escapes backslashes),
so split the pipeline:
- sanitizeForOutbound: strip internal tags + tool-call leaks + redact
secrets. Use this for intermediate text that will pass through
another formatOutbound call downstream (work-item storage, channel
wrappers, session-command output).
- formatOutbound: sanitize + neutralizeStrayMarkdown. Reserved for the
single Discord-send boundary in channels/discord.ts.
Internal callers (message-turn-controller, session-commands, the
sendFormattedChannelMessage / sendFormattedTrackedChannelMessage /
editFormattedTrackedChannelMessage wrappers in index.ts, and the cron
reviewer-bot path) now use sanitizeForOutbound so the markdown escape
runs exactly once at the channel boundary.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* feat(dashboard): improve mobile control plane UX
* fix(dashboard): improve mobile nav accessibility
* feat(dashboard): add mobile drawer and usage glance cards
* feat(dashboard): add compact usage matrix and i18n
* fix(dashboard): make usage-first console layout
* fix(dashboard): remove chrome and group usage rows
Adds a disabled-by-default loopback web dashboard MVP with read-only control-plane views, prompt preview redaction, Vite React UI, and validation coverage.
- Upgrade EJClaw bundled Codex CLI to 0.124.0 for gpt-5.5 access
- Resolve Claude Agent SDK native binary packages via package.json
- Prefer glibc Claude binary before musl on Linux and align platform binary names
- Add regression coverage for optional package resolution