Skip to content

chore: merge upstream main (TUI themes, edit perf, Windows contract — through #328 / v0.5.2) - #19

Merged
weekbin merged 17 commits into
mainfrom
chore/sync-upstream-0.5.2
Sep 23, 2026
Merged

weekbin merged 17 commits into
mainfrom
chore/sync-upstream-0.5.2

Conversation

@weekbin

@weekbin weekbin commented Sep 23, 2026

Copy link
Copy Markdown
Collaborator

Change

Merge MiniMax-AI/minimax-code main (through 944e874, v0.5.2) into this fork as real merge commits, so upstream's commits are reachable in this repository's history instead of being flattened into one squashed commit the way #15 did. GitHub can now attribute the fork against upstream rather than showing the branch as diverged.

Merge commits:

  • f5d701f — parents 6d9915d (this fork's main) and 7f2fe52 (upstream, 12 commits, #303#328)
  • 6dbc3e8 — Release-audit allowlist for a pre-existing synthetic test fixture (see below)
  • 32e57a4 — parents 6dbc3e8 and 944e874 (upstream, 2 more commits, #329#330)

Upstream commits brought in (14, #303#330):

Commit Change
944e874 fix(tui): switch theme appearance with arrow keys (MiniMax-AI#330)
cf66c7f fix(tui): dismiss interrupted duration on submit frame (MiniMax-AI#329)
7f2fe52 fix(tui): clear previous run duration on new turn (MiniMax-AI#327)
bb39720 fix(tui): rebuild chat after closing full viewport interactions (MiniMax-AI#328)
b7456eb docs: define pull request label usage (MiniMax-AI#326)
25518db fix(tui): restore early-aborted prompt to composer (MiniMax-AI#325)
b47eb90 fix(tui): preserve transcript position while reading history (MiniMax-AI#324)
2ede8e4 docs: document BYOK session-affinity compat key (MiniMax-AI#323)
44b13d3 perf(edit): bound the post-edit diff and unified patch (MiniMax-AI#284)
a346908 chore: release MiniMax Code 0.5.2 (MiniMax-AI#313)
00a7e7d feat(tui): add selectable themes and custom theme files (MiniMax-AI#311)
bcb1d96 fix(tui): erase viewport redraws without saving stale history (MiniMax-AI#309)
ed1f927 fix(tui): preserve native scrolling when visible content shrinks (MiniMax-AI#306)
e7d809d fix(windows): clarify source checkout requirements (MiniMax-AI#303)

This fork's ahead work is not overwritten. The merges change only files those 14 upstream commits touch — no file unique to this fork's own line (packages/webui, the mcode-web launcher, the webui docs set) is modified. The seven files both sides touched (ci.yml, package.json, README.md, README_ZH.md, release/public-source.json, scripts/verify.mjs, test/vitest-suites.json) merged additively, keeping the fork's changes and layering upstream's on top. The second merge was conflict-free.

Merge resolution

test/vitest-suites.json conflicted in the first merge: this fork keeps the capability list alphabetically sorted, while upstream appends new files. Resolved by keeping the sorted list and adding upstream's 8 new test files (edit-diff-bounds, tui-tool-preview-diff-omitted, turn-submission-retainer, host-tui-settings, theme-picker, theme/custom-themes, theme/palettes, theme/runtime) plus upstream's new windows group; the fork's own cli-webui-command.test.ts entry is retained. All 175 declared paths verified to exist, with no duplicates. The #329/#330 commits only modify existing test files, so no further registry change was needed.

release/public-source.json was regenerated with node scripts/source-inventory.mjs --write after the merge; the result is byte-identical to the auto-merged file.

Release audit: one gitleaks finding, fixed

The Release audit workflow (security.yml) scans complete history with gitleaks git --log-opts=--all. This fork had never run it before, so it surfaced on this PR for the first time and failed:

RuleID: generic-api-key
File:   packages/webui/test/trajectory/store.test.mjs
Commit: a10f820  (2026-09-21, already in this fork's history)
Match:  api_key=abcdef123456'

That line is a fixture asserting the redaction helper works (assert.match(redactText('api_key=abcdef123456'), /api_key=\[redacted\]/)), not a credential. Rewriting history was not an option, so 6dbc3e8 adds an allowlist entry anchored to the exact match text, mirroring the existing synthetic-marker allowlist for packages/local-runtime/test/unit/error-reporting-privacy.test.ts. Reproduced and re-verified locally with the workflow's exact step: no leaks in history, the HEAD archive, or dist.

Validation

Run on the PR head with a clean tracked working tree, full profile:

  • pnpm verifypassed, 14 gates on linux (check:source, check:tsconfig, export source preview, test:release-tools, typecheck, build, check:standalone, test:artifact, test:capabilities, test:status-contract, test:smoke, test:byok, test:webui, test:policy).
  • pnpm check:source — 4486 files, workspace exports and native helper integrity verified.
  • pnpm check:tsconfig — paths match 126 package exports.
  • Gitleaks step reproduced locally (gitleaks git --log-opts=--all, git archive HEAD + gitleaks dir, gitleaks dir dist) — no leaks found.
  • CI on this PR: Source verification green on ubuntu, windows and macos; Release audit, CLI release and Performance green.

One macOS-only failure appeared on the first CI run and did not reproduce: packages/tui/test/unit/tui/theme/runtime.test.ts > repaints live colors when a custom theme file changes in place (expected '#112233' to be '#AABBCC'). That is upstream's test from MiniMax-AI#311, untouched by this fork, and it relies on a plain fs.watch on a tmpdir path; it passed on ubuntu and windows in the same run and on the macOS re-run. Reported here as an observed flake, not as a fix.

NOT RUN, platform limitations and live-service boundaries:

  • test:windows (requires a Windows host) and test:sandbox (not applicable on linux) were skipped by the local verifier; test:release-package requires an npm release archive. The Windows contract is covered by the verify (windows-latest) CI job, which passes.
  • No live-service or cross-platform acceptance is claimed beyond the CI jobs listed above.

Publication and contribution checks

  • I have permission to contribute these changes under the existing licenses applicable to the changed files/packages; imported material and its provenance are identified and existing notices are preserved.
  • No credentials, account data, real user content, internal source history or private review material is included.
  • Added/removed source files were reviewed before regenerating release/public-source.json; new tests are declared in test/vitest-suites.json where applicable.
  • Shared English/Chinese documentation and capability/verification records are updated where applicable. Mock/offline results are not described as live-service acceptance.

Maintainer handoff

Publication scope or license changes (if any): none — the merges carry upstream files unchanged.

Shared-source port: not needed. release/extraction.json sourceRevision is unchanged (9b9885e) and matches upstream's; this syncs from the public upstream, not from an internal source revision.

hetaoBackend and others added 17 commits September 22, 2026 12:39
* fix(windows): clarify source checkout requirements

Skip only WSL conversion fixtures on native Windows and add an explicit local-NTFS preflight to the source-build instructions. Cover localized fsutil output and fail-closed diagnostics without changing production path validation.

* ci: add focused Windows source contract

Run a Windows Node 24 contract on non-documentation pull requests while keeping the full capability suite on Linux and macOS. Share the gate through the verifier and Vitest suite manifest, keep the compatibility matrix macOS/Linux, and document the Windows-only boundary.
* feat(tui): add selectable themes and custom theme files

MCode TUI colors are now driven by named themes, each shipping a dark
and a light palette. `/theme` opens a panel with live preview so users
can switch themes and lock light/dark on top of terminal detection.

Custom theme files under `tui/themes/*.json` support partial overrides,
color aliases and syntax palettes, and hot reload so editing a theme in
use takes effect without restarting.

The selected theme persists in `tui/tui-settings.json` alongside
`tuiMode`; unrecognized values fall back to the default theme instead of
blocking startup.

Assisted-by: mavis reason:port-theme-system

* test: register theme system tests and source inventory

The public source gate requires every first-party test to be listed in
test/vitest-suites.json and every shipped source path to be recorded in
release/public-source.json. Register the new theme system tests and the
11 files this change adds.

Assisted-by: mavis reason:register-theme-tests
* perf(edit): bound the post-edit diff and unified patch

`edit` computed its display diff and its unified patch with unbounded
Myers, which costs O((N+M)*D) in the edit-script length D. A whole-file
rewrite therefore scaled quadratically in the number of changed lines:
rewriting every line of a 20 000-line file blocked the tool for over two
minutes and produced a multi-megabyte patch that no renderer displays.
The sibling `write` path already caps the same work in
`packages/agent-tools/src/shared/write-capture.ts`; `edit` had no cap.

Pass jsdiff's `maxEditLength` (1000 edits) and `timeout` (5 s) to
`diffLines` and `createTwoFilesPatch`. `maxEditLength` is the primary
bound because it is deterministic and therefore unit-testable; `timeout`
only backstops slow machines. When a bound trips, `details.diff` carries
a one-line notice so every renderer still has something to show,
`details.patch` is omitted, and the new `details.diffOmitted` names the
reason. The unified patch is skipped once the display diff was abandoned
rather than repeating a second Myers run that aborts on the same bound.
The file is written before any diff runs, so a dropped diff never
changes what lands on disk.

Measured end to end through `createEditTool` on a 20 000-line whole-file
rewrite, three runs each on the same machine: 120 s / 138 s / 139 s
before, 60 ms / 60 ms / 58 ms after.

Assisted-by: minimax-code reason:bound-edit-diff-cost

* fix(tui): render an omitted edit diff as a summary block

When the edit tool hits its diff bounds it replaces the diff body with a
one-line notice, so the body carries no `+`/`-` lines. Both preview
consumers misread that notice as a real diff:

- The TUI counted changed lines from the body and rendered
  `subject.ts · +0 -0 · Applied` for a 20k-line rewrite.
- The ACP projector ran the notice through `parseUnifiedDiff`, emitting a
  diff block with `oldText === newText === "(diff omitted: ...)"`, which a
  client renders as "no changes".

Read `details.diffOmitted` alongside `details.diff` and emit the existing
summary block instead, which renders as a header plus the notice and needs
no line counts. `timeout` maps to `unavailable`, bounds hits to `too-large`.

E2E: PASS (tmux TUI against dist/cli.js, 20000-line replace_all edit:
header is `subject.ts · Applied` + `(diff omitted: more than 1000 lines
changed)`; a 1-line edit still renders `+1 -1 · Applied`)

* fix(agent-tools): keep Compatible hooks working when the edit patch is bounded out

Bounding the post-edit diff drops `details.patch` on exactly the edits
the bound targets, and `withCompatibleEditToolResponse` returned the
result untouched in that case. The Compatible PostToolUse payload then
carried no `tool_response`, so `needsPostToolAdapter` found no usable
CLAUDE handler and the runner rejected the hook with
HOOK_INVALID_INPUT — every Compatible PostToolUse hook was silently
skipped on large edits that used to run fine. There is no fallback for
`edit` in `buildCompatiblePostToolResponse`, unlike bash, read and mcp.

Synthesize the structured patch instead of reparsing one that does not
exist: a bounded-out edit is by definition a wholesale rewrite, so emit
a single hunk that removes every old line and adds every new one. This
costs no Myers run, keeps the payload the same order as the
`originalFile` already in it, and stays a correct — if imprecise —
description of what landed on disk. The `\ No newline at end of file`
marker is reproduced on whichever side lacks the trailing newline, in
jsdiff's position.

`EditCapture` now also carries the written content, and BOM stripping is
shared between the two sides so the synthesized hunk matches what
jsdiff would have produced.

Assisted-by: minimax-code reason:bound-edit-diff-cost

* fix(edit): report why the unified patch was omitted

The unified patch is a second, independently timed Myers run over the
same input as the display diff. When only that run ran out of time,
`details` held a diff, no patch, and no `diffOmitted`, so a consumer
could not tell an omitted patch apart from a tool that never produces
one.

Add `details.patchOmitted`: it mirrors `diffOmitted` when the display
diff was abandoned, and reports `timeout` when only the patch was, so
`details.patch` is absent exactly when `patchOmitted` is set. jsdiff
routes `createTwoFilesPatch` through the same bounded `diffLines`, so
`maxEditLength` is deterministic across both runs and can never trip for
the patch alone; only its separately measured wall clock can.
`diffOmitted` keeps its meaning and is unchanged.

The patch-only timeout is not unit-testable — it needs the two runs to
land on opposite sides of a 5 s wall clock, which no deterministic input
can force — so the tests pin the invariant on both reachable paths
instead.

Assisted-by: minimax-code reason:bound-edit-diff-cost

* fix(edit): raise the diff bound so ordinary rewrites keep their diff

`maxEditLength` bounds D, the length of the Myers edit script, not the
number of changed lines: replacing a line costs one deletion plus one
insertion. A bound of 1000 therefore gave up on any full rewrite past
500 lines, and a reported 501-line whole-file replacement lost its diff
even though the whole tool call, diff included, finishes in under 55 ms.
That is well inside what the tool should still render.

Raise the bound to 2000, which admits a full rewrite of any file up to
1000 lines. Measured end to end through `createEditTool`, three runs
each on the same machine: 53 / 49 / 47 ms for a 501-line rewrite,
177 / 173 / 177 ms for 1000 lines, and 200 / 192 / 193 ms for a
20 000-line rewrite that still gives up — against 167 620 / 173 523 /
167 547 ms unbounded, with peak heap dropping from 47-60 MB to
14-15 MB. Doubling the bound roughly doubles the worst case the bound
itself admits, which stays near 200 ms.

Also correct the notice the bound emits. It read "more than N lines
changed", which misreports a rewrite by a factor of two; D is exactly
the number of added plus removed lines, so say that instead.

Assisted-by: minimax-code reason:bound-edit-diff-cost

* fix(agent-tools): send an empty structured patch when the edit patch is bounded out

Synthesizing a whole-file hunk kept Compatible PostToolUse handlers
selectable when the bound dropped `details.patch`, but pushed the hook
payload past the runner's 1 MiB input limit on exactly the large files
the bound targets. A 20 000-line file with 1001 changed lines serialized
to 1 679 234 B and failed `serializeHookInput`, so the handler still
never ran — it now failed one step later than before. Trimming the
common prefix and suffix does not fix that either: the same shape still
reaches 1 154 543 B on a 960 KB file.

`structuredPatch` is a required array in the Compatible file-edit output
schema, and the upstream tool already sends `[]` when its own diff times
out, so the empty array is that schema's own way to say "no diff
information". It keeps the handler selectable, keeps `edit` renamed to
`Edit` for matchers, and passes the write-back `hasCompatibleJsonShape`
check, which compares array-ness only and so still accepts a hook that
returns real hunks. The same payload now serializes to 563 600 B.

`EditCapture.updatedFile` existed only to feed the synthesized hunk and
goes away with it, restoring the capture to its previous shape. A
malformed `details.patch` now also degrades to `[]` instead of dropping
the whole compatible response, which is strictly better: the handler
runs rather than being skipped with HOOK_INVALID_INPUT.

This does not make every hook payload fit. The response separately
carries `originalFile`, `oldString` and `newString` in full, so a
whole-file rewrite of a large file still exceeds the limit on those
fields alone, both before and after this change. That bound predates
this branch and is left alone.

Also correct the stale timings in the test header, which predated the
remeasurement at the current bound.

Assisted-by: minimax-code reason:bound-edit-diff-cost
An Anthropic-compatible relay that routes prompt-cache hits per session had
no documented way to learn the session identity. The request builder forwards
metadata.user_id only when a caller sets metadata explicitly, because it is an
abuse-detection and attribution field rather than a cache key, so nothing in
the public documentation explained how to preserve session identity instead.

Document the supported sendSessionAffinityHeaders compat override for custom
providers, and pin the resulting wire-level request: no session identity
without the override, x-session-affinity once a provider entry opts in, and no
session header when the turn runs without prompt caching.

Closes MiniMax-AI#318
…-AI#324)

* docs(tui): design follow-tail preservation

* docs(tui): add follow-tail implementation plan

* fix(tui): preserve detached transcript position

* fix(tui): re-arm follow-tail on explicit navigation

* test(tui): cover follow-tail intent boundaries

* fix(tui): re-arm follow-tail for queued messages

* fix(tui): ignore stale queue admissions
* fix(tui): restore early-aborted prompt to composer

* fix(tui): preserve restored submission edits and metadata
* fix(tui): clear previous run duration on new turn

* fix(tui): dismiss stale duration when a message appears
Sync MiniMax-AI/minimax-code main through 7f2fe52 (MiniMax-AI#328, v0.5.2):

- feat(tui): selectable themes and custom theme files (MiniMax-AI#311)
- perf(edit): bound the post-edit diff and unified patch (MiniMax-AI#284)
- fix(windows): source-location preflight and focused Windows contract profile (MiniMax-AI#303)
- fix(tui): scrollback/transcript, abort-restore, run duration, full viewport (MiniMax-AI#306, MiniMax-AI#309, MiniMax-AI#324, MiniMax-AI#325, MiniMax-AI#327, MiniMax-AI#328)
- docs: BYOK session-affinity compat key, pull request label usage (MiniMax-AI#323, MiniMax-AI#326)

Merge resolution: test/vitest-suites.json kept the fork's alphabetically
sorted capability list and added upstream's 8 new test files; release
inventory regenerated and unchanged.
Render the projected submission synchronously so the prior interrupted footer is erased before input returns. Queue the normal input render before dispatch so a synchronous render coalesces instead of doing a second pass.
…scan

The Release audit workflow scans complete history with gitleaks and reported
one finding: packages/webui/test/trajectory/store.test.mjs asserts that
redactText() scrubs 'api_key=abcdef123456', so the fake value is a fixture,
not a credential. It originates from commit a10f820 (2026-09-21) and is
therefore already in this fork's history; the workflow had simply never run
here before, so it surfaced for the first time on this PR.

Anchored to the exact match text, mirroring the existing synthetic-marker
allowlist for the local-runtime privacy test. Verified with
`gitleaks git --log-opts=--all` and the archive/dir scan: no leaks found.
Sync MiniMax-AI/minimax-code main through 944e874 (MiniMax-AI#330):

- fix(tui): dismiss interrupted duration on the submit frame (MiniMax-AI#329)
- fix(tui): switch theme appearance with arrow keys (MiniMax-AI#330)

No conflicts; no file unique to this fork is touched.
@weekbin
weekbin merged commit 85f74a6 into main Sep 23, 2026
18 checks passed
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.

3 participants