Skip to content

Release/1.23 - #116

Merged
hotheadhacker merged 63 commits into
mainfrom
release/1.23
Sep 10, 2026
Merged

Release/1.23#116
hotheadhacker merged 63 commits into
mainfrom
release/1.23

Conversation

@hotheadhacker

Copy link
Copy Markdown
Contributor

No description provided.

hotheadhacker and others added 30 commits August 31, 2026 16:11
- Pixel-font MERCURY CODE wordmark: centered, device-precise (single-codepoint
  block glyphs, constant-width left column), vibrant cyan/magenta duotone
  with light/dark background adaptation
- Full-screen /code mode: bordered input box, centered command hints,
  status line with dir/git/mode/provider, transcript scrollback
- Mouse support: SGR/X10 sequence parser, wheel-driven scrollback,
  clean terminal restore on exit
- Single live feedback feed: spinner + current action, done ticks,
  parallel sub-agent swarm panel
- Smarter coding agent protocol: intent-first analysis, read-before-write,
  verify builds/tests, structured atomic progress narration
- New commands: /code diff (syntax-highlighted), /code init (AGENTS.md),
  /code exit (confirmation flow), Ctrl+P/Ctrl+X/Ctrl+G shortcuts
- Dependency-free syntax highlighter wired into chat code blocks and diffs
- Esc-Esc double-press exit arming with 1.5s window
Terminal crash after 1-2 min in /code traced to three stacked issues:

1. The stdin filter dropped an escape prefix whenever a mouse sequence
   was split across two stdin chunks, leaking the tail (e.g. '64;53;5M')
   into the TUI input box as keystrokes — the garbage seen in the shell
   prompt after abort.
2. Leaked fragments accumulated unboundedly in the input state, driving
   render churn until V8 aborted.
3. Crash paths never disabled mouse tracking, leaving the terminal
   spewing SGR reports after death.

Fixes:
- Rewrite the filter as a stateful MouseSequenceFilter: holds back
  partial ESC/CSI/SGR/X10 prefixes across chunk boundaries (bounded at
  64 chars so a corrupt stream cannot grow memory), parses complete
  mouse events, passes all other sequences through whole. X10 in-flight
  check runs before generic CSI pass-through since 'M' is a valid CSI
  final byte.
- restoreTerminal(): DEC reset + cursor show on every exit path (TUI
  exit, channel stop, SIGTERM/SIGINT shutdown).
- 8000-char input flood guard as defense in depth.
- 12 new tests covering split-chunk joins for SGR and X10, wheel
  parsing, motion/release classification, and holdback overflow.
Second abort (uv __run_timers stack) pointed at timer-driven runaway
accumulation during 1-2 min Mercury Code sessions. Also: the user's
crash came from a stale dist that predated the mouse-filter fix — npm
run build now emits the guarded build.

Hardening:
- Cap live toolSteps (60) and TUI transcript messages (250); long
  coding sessions no longer grow render/memory unboundedly.
- statusPollerTick: Mercury Code git header read is now async
  (execSync blocked the event loop mid-task every 2s).
- Wheel events only count presses (release no longer double-scrolls).
- Crash forensics: uncaughtException/unhandledRejection/SIGABRT handlers
  append JS stack + heap stats to ~/.mercury/crash-report.log so any
  future native abort has a readable cause.
- PTY stress run: 180s live TUI at steady-state RSS (~140MB), no crash.
Root causes confirmed via ~/.mercury/crash-report.log forensics:

1. Ink's reconciler freed Yoga nodes (freeRecursive) but left JS
   references dangling — the renderer then read freed WASM memory
   through node.staticNode?.yogaNode after mode switches unmounted
   <Static>. Patched ink (patches/ink+5.2.1.patch, wired via
   patch-package postinstall) to null every reference in removed
   subtrees and clear the root's cached staticNode when the removed
   subtree contains it. Also handles ink's childNodes-less #text nodes.

2. <Static>'s positional index assumes append-only items; the bounded
   slice(-MAX_STATIC_MESSAGES) window shifted at constant length, so
   new messages never rendered and every commit unmounted the whole
   static subtree. Added itemKey identity dedup to the patched Static
   component; App.tsx passes message ids.

Also lands the in-flight crash-hardening work: memory governor,
execute guard, stream completion bounding, live-activity, and the
bounded Mercury Code transcript projection with scroll.

patch-package ships as a runtime dependency and patches/ is included
in published files so consumer installs get the fix.

Co-Authored-By: Claude Code <noreply@anthropic.com>
/code chat (alias /code back) returns to regular chat immediately,
skipping the exit-confirm dance — the confirm guards the Esc-Esc path
against accidental exits; an explicit command is deliberate.

Also fixes /chat and /c doing a half-exit from Mercury Code: they
flipped the view but left mercuryCode state, scroll offset, and
programming mode dangling. Both now tear down via exitMercuryCode().

Hints block and /help manual updated; regression test guards the
teardown (mode chat, mercuryCode null, programming mode off).

Co-Authored-By: Claude Code <noreply@anthropic.com>
Tasks that did not finish were reported as finished. Root paths confirmed
in the code: step-budget exhaustion produced a "Task complete" banner
(main loop) and a 'completed' status (sub-agents); implementation turns
needed only one successful edit to count as done; silent stalls had no
watchdog; completion was declared by loop termination, not task outcome.

- completion-verdict.ts: classify why the turn ended (text-stop /
  steps-exhausted / interrupted / truncated / aborted). Budget exhaustion
  with tool work pending is a pause, never a completion.
- Main loop: bounded auto-continuation on step-budget exhaustion (fresh
  budget + resume nudge, mirroring the execute-guard round); past the
  bound, ask the user — declining records work-ledger 'paused' with the
  "send continue" hint. MERCURY_MAX_STEPS env override for cheap soak
  testing of the exhaustion path.
- execute-guard.ts: evidence-based verification gate — execute-mode work
  with no build/test/typecheck command forces one bounded verification
  round before completion.
- stall-watchdog.ts: 3 min silence → visible still-working pulse; 8 min →
  abort the attempt so inspect-and-resume machinery engages (never a
  silent hang). Env-tunable thresholds.
- Sub-agents: budget exhaustion now yields 'paused' + supervisor
  auto-resume (once) with a fresh budget; background tasks no longer
  mark paused agents failed.
- Work ledger: new 'paused' status — resumable, recovered on restart,
  never pruned as terminal.
- CLI banners: paused tasks wear "Task paused · send continue"; execute-
  mode turns with zero file changes wear "Response delivered · no file
  changes" instead of a false "Task complete".

Co-Authored-By: Claude Code <noreply@anthropic.com>
Regression from the itemKey patch: committing item keys did not unmount
the rendered children. Ink's renderer recomputes static output from
still-mounted <Static> children on every render and writes them to the
terminal again — so in idle chat the last transcript message duplicated
on every render cycle (~30s heartbeat/token churn), piling up copies of
the same response. Original ink avoids this by unmounting children in
its layout effect via setIndex; the itemKey path now does the same via
a commitTick re-render after keys are committed.

Functional reproduction added (src/ui/static-rerender.test.tsx): a real
ink render with a fake terminal asserts each item is written exactly
once across heartbeat-like rerenders. Verified by temporarily reverting
the fix (6 writes vs 1). The test's itemKey is deliberately module-level
— an inline arrow re-runs the dedup memo every render and masks the bug.

Co-Authored-By: Claude Code <noreply@anthropic.com>
AUTO is now the default when entering Mercury Code (previously PLAN,
which required a manual /code execute to build anything):

- Reads, plans silently, implements immediately in one uninterrupted
  flow. Small/medium changes proceed without asking; large or
  consequential changes present a concise plan with a single ask_user
  confirmation (recommended option default-selected), then build
  without re-asking.
- AUTO shares execute-class semantics: full tool set and the entire
  completion contract (verification gate, narration guard, step-budget
  continuation, honest pauses).
- System prompt adds an explicit anti-narration mandate: every sentence
  about intended work must be followed by the tool call doing it in the
  same turn ("Act, don't announce"). Execute and AUTO share one
  behavioral contract constant.
- /code auto added (TUI fast path, chat handler, web API); toggle
  cycles off → auto → plan → execute → off. UI labels, hints card, and
  /help manual updated.

Co-Authored-By: Claude Code <noreply@anthropic.com>
When a file tool succeeds in Mercury Code / coding mode, the transcript
now shows a selective excerpt of the change instead of a bare tool step:

- create/write: header (path + line count) + code fence in the file's
  own language — up to 48 lines, larger files show a head excerpt plus
  "… +N more lines (full content on disk)".
- edit_file: header with +/− stats + a ```diff fence (red/green via the
  existing diff highlighter), bounded to 16 lines per side.
- Failures, deletes, and non-file tools produce no preview; the
  size heuristics keep the transcript selective, never flooded.

Plumbing: utils/file-preview.ts builds previews from tool arguments;
CLIChannel.showFileChange() appends them as system messages (with
duplicate-retry guard); the agent's tool bookkeeping hooks call it on
every loop (stream, continuation, guard, verification); Mercury Code's
transcript projection now parses fenced blocks inside system messages so
previews flow through the same highlight pipeline as agent code.

Co-Authored-By: Claude Code <noreply@anthropic.com>
Regression from the AUTO-mode feature: enterMercuryCode set the TUI to
AUTO, but the agent-side handler immediately called setPlan() and pushed
that stale state back via setProgrammingStatus — the bottom status bar
showed PLAN on every /code entry. The agent-side ProgrammingMode now
syncs to AUTO, and the welcome message matches the automatic flow.
Regression guard added to the Mercury Code exit test.

Co-Authored-By: Claude Code <noreply@anthropic.com>
"I don't know what it's waiting for": during a silent provider hang the
chat-mode ThinkingIndicator showed a generic "Composing response", while
the actual provider/phase state ("Calling <provider>") was pushed as
live activity that only rendered in Mercury Code mode. The indicator now
displays the live activity phase (provider + model/detail) in all
surfaces, so a stalled attempt is attributable at a glance.

Co-Authored-By: Claude Code <noreply@anthropic.com>
The splash mark's shade texture cycled per glyph COLUMN, landing shade
cells at different positions on every row — visible holes inside letters
(the M rendered as "█ ▓ █"). Shading now applies per row, top→bottom:
every filled cell in a row shares one fill character, and the mark is
solid with a shaded bottom band — a deliberate vertical gradient shadow
instead of ragged gaps. The X glyph is fixed to a symmetric 5-wide form
and the default fill is solid. Regression tests guard row-uniform fills,
part alignment, and the absence of shade above solid rows.

Co-Authored-By: Claude Code <noreply@anthropic.com>
The bottom hints row taught input-navigation trivia (↑/PgUp/Ctrl+U
history, Ctrl+A oldest) that developers never look for in a status bar.
Left side now shows only what gets pressed: "↵ send · esc esc exit ·
ctrl+c quit" at live, and the scroll keys only while scrolled back.
Right side gains the token budget (⚡ N%) — it was invisible in Mercury
Code entirely, since the chat-mode token bar doesn't render there.

Co-Authored-By: Claude Code <noreply@anthropic.com>
User decision from live sessions: mid-task pauses that demand a manual
"continue" break the flow of long Mercury Code sessions. Continuation is
now the default, with the pause demoted to a runaway backstop:

- Narration guard: 5 forced rounds (was 2) — models routinely need a few
  nudges to switch from narration to tool use.
- Step-budget / provider-failure continuations: 6 automatic (was 2); a
  provider hard deadline no longer pauses for approval — the failed
  attempt counts toward the bound and the loop keeps going.
- Sub-agent step-budget auto-resume: 3 (was 1).
- The ask/reason strings updated to the new policy.

Co-Authored-By: Claude Code <noreply@anthropic.com>
Regression from AUTO mode: a turn ending in a plain-text question to the
user ("Could you remind me what the bot was supposed to do?") was not
recognized as a deliberate pause, so the narration guard forced more
rounds — the model re-searched and re-asked, looping visibly "forever".

- responseAsksUser(): a response whose last line ends with '?' is a
  legitimate stop; the guard neither forces rounds past it nor pauses
  it as unfinished work.
- The guard nudge now points questions at ask_user with concrete
  options.
- Honest banner/file-change gating now includes AUTO mode (was
  execute-only): the completion banner, no-changes honesty, and file
  summaries all apply to AUTO turns.

Co-Authored-By: Claude Code <noreply@anthropic.com>
Plan checklist: the model maintains its implementation plan with the new
update_plan tool (full-list replacement, one step "active" while being
implemented). The agent records it into the TUI at each tool step, and
Mercury Code renders a compact panel above the live feedback:

  ☑ 2 earlier steps completed
  ☑ Create color.ts
  ▶ Build storage.ts   ← implementing
  ☐ Wire the UI

Normalization is defensive: malformed entries dropped, labels deduped,
multiple "active" steps collapsed, rows budgeted into the fixed chrome
(transcriptHeight accounts for plan + prompt rows). The AUTO and
EXECUTE prompts mandate keeping the checklist current.

Choice picker: PermPromptView (ask_user / permission / continue prompts)
was gated to non-mercury-code modes — in Mercury Code the ask_user tool
blocked on a prompt that never rendered (an invisible hang). The prompt
now renders inside Mercury Code above the input box.

Co-Authored-By: Claude Code <noreply@anthropic.com>
Even with PermPromptView now rendered in Mercury Code, two bugs would
have made it unusable: the mercury-code key branch returned for every
keypress (arrow keys/Enter never reached the prompt navigation
handler), and Esc on an ask_user choice prompt did nothing — leaving
the tool blocked forever. A pending prompt now takes keyboard priority,
and Esc on a choice prompt cancels it (the tool proceeds with best
judgment instead of hanging).

Co-Authored-By: Claude Code <noreply@anthropic.com>
Permission-domain audit findings, fixed:

1. fetch_url had no scheme or host validation and followed redirects
   blindly — the model (steerable by the web content it reads) could be
   pointed at internal services: localhost admin panels, LAN hosts, or
   the cloud metadata endpoint, with their contents flowing into the
   conversation. Now: http/https only, private/loopback/link-local
   ranges blocked for both literal and DNS-resolved hosts, redirects
   validated per hop (redirect: manual), downloads capped at 512KB,
   MERCURY_ALLOW_PRIVATE_FETCH=1 as an explicit opt-out.

2. web-config.json (bcrypt hash) and web-sessions.json (live session
   tokens) were world-readable in a readable ~/.mercury — any local
   process could steal a session and drive Mercury's shell. Credential
   writes now use 0o600 and existing files are repaired on load.

3. Initial web password was a hardcoded constant ('Mercury@123') public
   in the MIT repo. It is now a random per-install password returned to
   the setup flow for one-time display.

4. Shell blocklist: added swapped-flag rm variants (rm -fr /, ~, /*) and
   rm -rf . / .. to the never-execute tier.

Co-Authored-By: Claude Code <noreply@anthropic.com>
…install

Two remaining permission-domain findings, fixed:

1. Secrets in logs: provider errors embed response bodies containing
   API-key fragments, and command output can contain environment
   secrets — all of it persisted into daemon-error.log and session
   transcripts. utils/redact.ts masks known key shapes (sk-/ghp_/AKIA/
   xoxb/Bearer/key=value) with a recognizable prefix/suffix; the pino
   error serializer redacts deeply (nested responseBody included), and
   run_command output is redacted before echoing into the conversation.

2. install_skill fetched skill content with a raw fetch — same SSRF
   hole as fetch_url, no redirect validation, no size cap. It now uses
   the shared SSRF guard (utils/ssrf.ts, extracted from fetch-url):
   scheme + private-range validation on every redirect hop, 512KB
   content cap on both URL and inline installs.

Co-Authored-By: Claude Code <noreply@anthropic.com>
The security pass replaced utils/redact.ts wholesale, dropping the
existing redactPhone/redactUuid/redactIdentity (Signal/Web identity
masking) and userFacingAiError (chat error translation) exports. All are
restored alongside the new secret-redaction functions; the earlier
commit was amended-forward by this one.

Co-Authored-By: Claude Code <noreply@anthropic.com>
'scroll-set N' matched the generic scroll- prefix branch first, parsed
as delta 'set N' (NaN) and returned: the scroll-clamp handler below was
unreachable. After a long-session history trim (4MB transcript budget)
the stored scroll offset exceeded the shrunken transcript, the viewport
clamped to the bottom rows, and the clamp loop that should have repaired
the offset silently did nothing — the user could not scroll at all and
saw only the tail of the last (code) response. scroll-set is now parsed
before the generic branch; regression guard asserts the parse order.

Co-Authored-By: Claude Code <noreply@anthropic.com>
Coding slowness diagnosis and fixes:

- No prompt caching: every agentic step re-processed the full system
  prompt (soul + skills + tool guidelines) and the whole growing
  conversation at full cost — TTFT climbed with session size. The
  Anthropic-family providers now get a cache_control breakpoint on the
  system prompt (OpenAI-compatible providers cache server-side and
  ignore the option harmlessly).
- MAX_RESPONSE_TOKENS 4096 → 8192: large code files truncated mid-write
  and triggered a length-truncation continuation that re-sent the whole
  conversation — a full extra round trip per large file.
- Per-attempt latency logging (durationMs on success and failure) so
  "why is coding slow" becomes measurable instead of guessed.

Co-Authored-By: Claude Code <noreply@anthropic.com>
On longer /code tasks the transcript showed narration from successive
agentic steps concatenated with no separator ("Building it now.Starting
by scaffolding…Clean slate. Now scaffolding…Next.js project
scaffolded…"): the SDK's textStream emits every step's text back-to-back
within one multi-step generation. The TUI-facing streams now wrap
fullStream and insert paragraph breaks at step-start boundaries, so each
step's narration reads as its own block (main stream, continuation
rounds, guard round, and the length-truncation continuation).

Co-Authored-By: Claude Code <noreply@anthropic.com>
The final error showed only the LAST provider's failure ("No output
generated") — hiding that the cloud token expired, the OpenAI key was
malformed, and the DeepSeek key was rejected. The fallback loop now
collects each provider's first failure reason (deduped, bounded) and
appends a "Per-provider:" block to the all-failed message, so the fix
(auth/keys) is visible immediately instead of requiring log digging.

Co-Authored-By: Claude Code <noreply@anthropic.com>
Mercury Cloud connectivity self-recovery (refresh-token rotation → agent
API key redemption) was wired to config.cloud.agentApiKey only — configs
where the agent's key lives as the LLM gateway access key
(providers.mercuryCloud.apiKey, sk-mc-) had NO recovery path: when the
single-use refresh token died, every rotation 401'd forever until a
manual browser re-pair. The store now falls back to the sk-mc- gateway
key for redemption; redeem validates agent identity server-side, so a
mismatch fails safely into an explicit re-pair hint ("mercury cloud
connect") instead of an opaque 401 loop.

Co-Authored-By: Claude Code <noreply@anthropic.com>
…ecovery key"

This reverts commit 86e24f9ac87ee275e245a2653ae62322148d46cb.
…y redemption

The revert removed the sk-mc- redemption fallback (it rotated cloud
credentials server-side and broke the chat connection); this restores
only the actionable error text: when no recovery key is configured, the
rotation failure now says to run "mercury cloud connect" instead of an
opaque 401.

Co-Authored-By: Claude Code <noreply@anthropic.com>
…ut error

When a provider request fails (401, invalid key), the AI SDK's stream
records zero steps and its flush rejects usage/finishReason with a
generic "No output generated. Check the stream for errors." — which was
awaited BEFORE the captured streamError, masking the actual auth failure
behind a useless message on every provider in the fallback chain. The
real streamError (and abort state) is now checked first, so the
per-provider aggregate shows the true reason (bad key, dead token) and
the fix is obvious from the chat.

Co-Authored-By: Claude Code <noreply@anthropic.com>
…calls

Regression report: with the same shared config, the dev build (repo)
could not complete Mercury Cloud LLM calls while the npm-installed build
worked — isolating the difference to yesterday's perf commit. The
prompt-cache breakpoint changed the system prompt from a plain string to
an array of text parts with providerOptions; at least one gateway in
the provider chain rejected that request shape, surfacing as a masked
"no output" failure. Reverted to the plain-string system prompt and the
4096 output cap (matching the working npm build); per-attempt latency
logging stays.

Co-Authored-By: Claude Code <noreply@anthropic.com>
hotheadhacker and others added 28 commits September 8, 2026 15:00
Live finding: a narration-prone model satisfied the FORCED tool call
with list_dir/read_file every round (inspection, not action) and burned
all five guard rounds into an honest pause. Three closing moves:

1. Forced steps are now MUTATING-tools-only — inspection is impossible
   on a forced step (grounding already provided the directory listing).
2. WAKE-UP CALL: when the first full guard cycle fails to start the
   work, the agent does not pause — it doubles the bound and issues a
   blunt directive ("your next response MUST begin with a mutating tool
   call, ZERO prose") plus fresh grounding. Ten mechanical rounds total
   across the provider chain before any pause.
3. The pause, when it finally happens, says WHAT blocked it: the last
   failed mutating-tool result (e.g. "write_file: permission denied")
   is appended to the message and the ledger reason.

Co-Authored-By: Claude Code <noreply@anthropic.com>
… loop

The 'task drops in the middle' failure on medium tasks had two
mechanical causes, both now fixed:

1. Output cap 4096: a single-file app write is emitted AS tool-call
   arguments — at 4096 the call is severed mid-argument, never executes,
   and the model retries into the same wall forever. Small tasks fit
   under 4096 (why they worked); medium ones never could. Restored
   8192 — the earlier cloud failure was the system-prompt request
   SHAPE, not the cap; the cap is a plain number.

2. The truncation continuation round ran with stopWhen: stepCountIs(1)
   — a ONE-step round. The model spent its single step reading files
   and could never reach the write: 'reading all files, then writing
   everything' repeated forever. Continuation rounds now run with the
   full step budget.

3. When the truncation severed a tool call (lastStepHadToolCalls), the
   nudge is write-specific: do not retry the giant write — write in
   sections (create_file first 80 lines, edit_file appends).

Co-Authored-By: Claude Code <noreply@anthropic.com>
"Continuing truncated response · auto-resume after output limit" was
machinery-speak in the user's face. It now reads "Writing the next
section... / Finishing the write — continuing past the size limit" —
consistent with the escalation voice: chat and status speak like a
person, detail lives in the logs.

Co-Authored-By: Claude Code <noreply@anthropic.com>
Adopted the core resilience practice from OpenCode's session pipeline
(sst/opencode — SessionPrompt.loop + SessionCompaction): on context or
memory pressure, COMPACT the conversation and CONTINUE instead of
killing the task. Mercury's step governor aborted on the first 'abort'
verdict — long coding builds died at memory pressure even though old
tool bulk (gigabytes of file reads across a long session) could be
summarized away.

Now: the first governor 'abort' verdict triggers aggressive in-place
conversation compaction (messages beyond the newest 8 get oversized
tool results and long text replaced with head+tail summaries, via
summarizeToolResult) and the loop CONTINUES; a second consecutive
abort verdict aborts as before. The live activity shows "Freeing
memory — compacting the conversation".

Also already aligned with OpenCode: doom-loop detection on repeated
tool calls, provider fallback chain, bounded retries, per-step
governor checkpoints.

Co-Authored-By: Claude Code <noreply@anthropic.com>
"Why do we have a size limit? We should not have any size limits."
Correct: a single-file app write is emitted AS tool-call arguments, so
any Mercury-imposed cap eventually severs a legitimate write mid-flight.

The fixed cap (last 8192) is replaced by MODEL_OUTPUT_TOKEN_LIMIT =
32768 — deliberately far above every mainstream model's NATIVE output
limit, so the model's own limit governs, not ours. If a provider still
rejects the value (some do when max_tokens exceeds their model limit),
the attempt loop halves the cap adaptively and the chain continues —
the task never dies on a configuration argument. The sectioned-write
guidance remains as the fallback for models with genuinely small
native limits.

Co-Authored-By: Claude Code <noreply@anthropic.com>
Two regressions from the recent rework:

1. The live step list at the bottom ("✏️ Writing file.ts") was driven
   only by the MAIN generation loop — forced guard rounds, verification
   rounds, and continuation rounds (where most file writes now happen)
   emitted no tool events, so the file activity went invisible during
   exactly the moments files land. Those rounds now carry the same
   onToolCallStart/Finish wiring as the main loop.

2. Enabling mouse reporting captures click-drag, so text selection
   needs Shift+drag — the status line now says so instead of leaving
   users guessing how to copy.

Co-Authored-By: Claude Code <noreply@anthropic.com>
A model reasoning before speaking (common with GLM/Claude) produced up
to a minute of dead air: the stream only surfaced text, so reasoning
deltas were discarded and the UI showed a bare spinner.

The step-aware stream now also surfaces reasoning deltas as a live
"thinking" preview (the model's own reasoning tail, quoted, dim) shown
in the chat ThinkingIndicator AND the Mercury Code live feedback block.
It clears the moment text starts streaming or the stream ends. The
phase label reads "Thinking..." while only reasoning has arrived.

Co-Authored-By: Claude Code <noreply@anthropic.com>
Two UX completions requested after live runs:

1. The completion banner now carries a change summary: each touched
   file with +/− stats (git-verified) and the verification evidence
   ("✓ Verified: npm test ✓") captured from the run. The agent passes
   the note from its command tracking to sendCompletion.

2. Long code blocks in MODEL responses collapse after 40 visible rows
   ("… code continues — full content on disk"), so a big code response
   no longer pushes the conversation out of the transcript. File-change
   previews were already pre-bounded; this covers the model's own code
   quoting.

Co-Authored-By: Claude Code <noreply@anthropic.com>
User report: the mark still showed gaps between blocks. The per-row
shaded band (████▓) read as faded/broken blocks in real terminal fonts.
The splash is now 100% solid bright blocks — the shading machinery
remains available for callers that want it, but the brand mark itself
is uniformly filled.

Co-Authored-By: Claude Code <noreply@anthropic.com>
Brand option D: the Mercury Code splash is now a framed panel — the
solid wordmark inside a rounded border, with the tagline caption
("⌁ interactive coding agent · vN") centered beneath. Two-tone
coloring preserved exactly (MERCURY cyan / CODE accent). Degrades to
the bare mark on narrow terminals (< mark width + padding).

Co-Authored-By: Claude Code <noreply@anthropic.com>
The Mercury Code splash now renders at double pixel resolution: 10-row
glyphs projected into the same 5 terminal rows via half-blocks
(▀ top-only, ▄ bottom-only, █ both) — real letterforms with smooth
tops, rounded C/O, a true R bowl, and a tapering Y, at 76 cells wide
(fits 80-col terminals). Terminals narrower than 80 columns fall back
to the 5-row solid block mark automatically. Two-tone coloring
(MERCURY cyan / CODE accent) unchanged.

Co-Authored-By: Claude Code <noreply@anthropic.com>
User verdict: the double-resolution mark read as childish. Reverted to
the classic fully-solid 5-row block mark — the retro pixel identity —
with a clean band of air padding the top of the splash.

Co-Authored-By: Claude Code <noreply@anthropic.com>
Version bump, changelog, release page, completion-architecture
reference doc, landing-page announcement, and the rebuilt docs site.

- package.json → 1.2.3; CHANGELOG.md gains the full 1.2.3 section
- website/docs/releases/1.2.3.mdx — detailed release page
- website/docs/reference/completion-architecture.md — the architecture
  documentation page (problem → solution, verdict system, escalation
  harness, compact-on-pressure, watchdog); wired into the sidebar
- releases index lists 1.2.3
- landing page: hero badge + CTA now announce v1.2.3 · Unstoppable
  Mercury, plus a dedicated release banner section with a terminal
  preview of the new completion UX
- docs/ rebuilt via the official build:prod script
- tagged v1.2.3

Co-Authored-By: Claude Code <noreply@anthropic.com>
…no longer skip it

CI regression on Termux (Android x86_64): newer npm policies block
lifecycle scripts (`npm warn install-scripts`), so postinstall never ran
and the bundled ink patch was silently skipped — the freed-Yoga-node
crash class returned and static-transcript.test.ts caught it.

- scripts/apply-ink-patch.cjs: idempotent ensure-script — checks the fix
  markers, applies patches/ink+5.2.1.patch via patch-package, and is
  LOUD on failure (a silent skip is never acceptable for a crash fix).
- scripts/post-build.cjs now enforces it on every build, independent of
  install-script policy.
- postinstall calls the script directly (never throws; loud on failure)
  instead of the silent `|| echo` fallback.

Co-Authored-By: Claude Code <noreply@anthropic.com>
Second Termux failure revealed the remaining dependency: patch-package
itself does not run on Termux/npm-11 containers (`sh: 1: patch-package:
not found`, both via postinstall and npx --yes). The ensure-script now
applies the ink fixes DIRECTLY — string edits with idempotent markers,
no external tool:

- reconciler.js: freed-Yoga-subtree reference hygiene (clearYogaRefs),
  #text-node guard, and staticNode cache clearing on both removal paths
- Static.js: full patched rewrite — itemKey identity dedup + commitTick
  re-render that unmounts written children
- Static.d.ts: itemKey type

Verified end to end against a pristine registry tarball: the applier
transforms true-unpatched ink to fully-marked, and all ink regression
tests pass. Loud on failure; never a silent skip.

Co-Authored-By: Claude Code <noreply@anthropic.com>
toLocaleString() without a locale renders Indian grouping (2,00,000) on
in-IN hosts, making agent output machine-dependent. Add formatNumber()
helper pinned to en-US and use it in TokenBudget.getStatusText().

Co-authored-by: Mercury <mercury@cosmicstack.org>
Mercury Code reworked to the native scrollback model: finalized messages
print once via <Static> into terminal scrollback (patched identity dedup),
only a small live region repaints. No sticky header, no in-app viewport;
wheel scrolling and drag-selection are terminal-native (mouse reporting
stays off). Streaming tails render through the full markdown pipeline on a
fence-aligned 8KB slice; chat/coding live regions capped to 12 rows.

Ink 5's log-update had no line diffing (every render erased + rewrote the
whole frame) — patched to diff-render: only rows from the first changed
line down are repainted (prompt navigation, spinner ticks, streaming).

Bottom status bar rebuilt: single row, state-only segments (dir · git ·
mode · provider+model · tokens); key-hint wall dropped, non-git
workspaces show no git segment, truncation instead of wrapping.

New `mercury attach` mode: a second terminal joins the running runtime —
machine-local 0600 attach token (loopback-only Bearer auth), fetch-based
SSE client with reconnect, live session chat TUI with permission
resolution; plain `mercury` auto-attaches instead of failing.

Co-Authored-By: Claude Code <noreply@anthropic.com>
The Mercury Code completion contract now holds for plain chat on every
channel: an implementation-style request must actually run its tools —
narration alone no longer ends a task without having done anything, and a
change that landed but was never verified is forced through one bounded
evidence round (build/test/typecheck), same as execute mode.

Gating: applies when programming mode is execute (Mercury Code, unchanged)
or off (chat surfaces, non-internal messages). Text deliverables — poems,
emails, essays: replies where the response itself is the work — are
exempt from the narration guard (isTextDeliverableRequest), so chat is not
forced through file tools to write a poem. Questions and chit-chat were
already excluded by the guard's own patterns.

Co-Authored-By: Claude Code <noreply@anthropic.com>
…, release sources kept

- Built GitHub Pages output (docs/) and website landing + release mdx
  resolved in favor of main (38f79df): richer OG cards, emoji-to-SVG
  landing, rebuilt docs with v1.2.3 release page.
- src/, patches/ink+5.2.1.patch resolved in favor of release/1.23
  (6bd9d20): completion contract, TUI scrollback, token formatting.

Co-authored-by: Mercury <mercury@cosmicstack.org>
- New Cloud Instance platform page (hosted Mercury Code via Atomic Bot)
- Hosted-alternative section in Daemon Mode docs
- Sidebar + installation page entries; links use utm_source=agent
MercuryCodeView renders its transcript inline (not via <Static>), so
Ink re-renders the full frame on every state change. The test was
asserting that the transcript is absent from the re-rendered frame,
which is valid for chat mode (<Static>) but not for mercury-code mode.
Updated the test to verify the correct invariant: the ↓ keypress is
processed and the prompt selection remains visible and interactive.

Also adds CI-resilient ready-wait (poll for 'Option' up to 8s) and
retry loop for the keypress (20 attempts × 100ms) to handle slow
runners where Ink's stdin listener attaches after mount.

Co-authored-by: Mercury <mercury@cosmicstack.org>
The full-TUI integration test (TuiApp + useInput + PassThrough stdin)
was inherently fragile on CI: Ink's stdin listener attaches
asynchronously, and on a loaded runner the initial render hadn't
written 'Option' before the ready-signal timed out — stdout.output was
just the cursor-hide escape ('\x1b[?25l') and every retry attempt was
dropped.

Replaced with a direct PermPromptView render test:
- Verifies the ● marker appears at the active index
- Verifies the marker moves when activeIdx changes (rerender)
- Verifies the prompt message and all option labels are present
- No stream timing, no retry loops, no CI flakiness

Also exports PermPromptView from App.tsx so the test can import it.

Co-authored-by: Mercury <mercury@cosmicstack.org>
Ink writes the cursor-hide escape (\x1b[?25l) synchronously but the
component tree flushes on the next tick via React's reconciler.  On a
loaded CI runner the synchronous assertion ran before the flush, so
stdout contained only the escape code and every assertion failed.

Add a flush() helper that waits 20ms (one event-loop turn) after each
render()/rerender() call.  Also use EventEmitter-based FakeStdin
(matching static-rerender.test.tsx) and pass patchConsole: false.
… is 32ms-throttled; fixed sleeps race on CI)
…or writable files

Windows doesn't enforce POSIX permission modes; writeFileSync with 0o600
yields a 0o666 stat. Same gate as work-ledger.test.ts.

Co-Authored-By: Claude <noreply@anthropic.com>
@hotheadhacker
hotheadhacker merged commit 551908c into main Sep 10, 2026
12 checks passed
hotheadhacker added a commit that referenced this pull request Sep 10, 2026
The Release/1.23 (#116) squash merge committed a docs/ directory that
contained 53 stale HTML/JSON files with literal <<<<<<< HEAD conflict
markers — Docusaurus's incremental build cache had skipped regenerating
pages that only differed in the conflict resolution, so the markers
survived into the deployed site and broke every served page.

Rebuilt with npm run clear + npm run build:prod (full cache clear) and
re-synced to docs/. Zero conflict-marker files remain; index.html starts
with <!doctype.

Co-Authored-By: Claude <noreply@anthropic.com>
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.

1 participant