Files
multica/server
Jiang Bohan b26d9a666e fix(execenv): preserve non-ASCII via Codex-scoped stdin / file mandate
Re-applies the work from PR #2247 (merged then reverted in #2252) on top
of current main. Same end-state as the final commit on the previous
branch, restructured as a single coherent change set so the review can
focus on the final design, not the iteration history.

## Why

Two reports on Win11 + codex CLI showed agent comments and quick-create
descriptions arriving with every Chinese character replaced by `?`
(#2198, #2236). Root cause is *not* in the multica CLI's stdin parsing —
it's the Windows shell layer between the agent and `multica.exe`:

- Agents on Windows pipe HEREDOC content via the agent's tool layer →
  PowerShell 5.1 / cmd.exe → multica.exe stdin.
- PowerShell 5.1's `$OutputEncoding` defaults to ASCII when piping to a
  native command. Characters the active codepage cannot represent are
  silently replaced with `?` before the bytes reach multica.exe.
- `daemon.log` records the agent's correct UTF-8 because slog writes
  the file directly, so the regression hides until the user opens the
  issue page.

The "MUST pipe via stdin" canonical pattern was originally added in
#1795 / #1851 to fix a Codex-specific habit (emitting literal `\n`
inside `--content "..."`, MUL-1467) but landed in the all-provider
Available Commands section AND in the provider-agnostic
`BuildCommentReplyInstructions` helper, which steered every provider
into the broken Windows path.

## What

Three concrete changes, all gated to keep non-Codex providers and
non-Windows hosts on their existing canonical paths:

1. **CLI: add `--content-file` / `--description-file`** to
   `multica issue comment add` and `multica issue {create,update}`
   (`server/cmd/multica/cmd_issue.go`). Reads bytes off disk verbatim,
   skipping the shell entirely. All three input modes (`--content`,
   `--content-stdin`, `--content-file`) are now mutually exclusive.

2. **Available Commands becomes neutral** (`runtime_config.go`).
   Replace the unconditional "MUST pipe via stdin" /
   `--description-stdin` block with a three-line factual menu of the
   three input modes. Same text for every provider, every OS.

3. **Codex-Specific section + `BuildCommentReplyInstructions` become
   provider- / platform-aware**. Codex on Linux/macOS keeps the
   canonical stdin/HEREDOC mandate (preserves the MUL-1467 fix); Codex
   on Windows gets a `--content-file` template (preserves non-ASCII
   bytes); every other provider gets the lightweight pre-#1795 inline
   `--content "..."` template, which works on every platform because
   argv goes through CreateProcessW UTF-16 on Windows and the CLI
   server-side decodes `\n` etc.

`BuildPrompt`, `buildCommentPrompt`, and
`execenv.BuildCommentReplyInstructions` all gain a `provider` arg;
`daemon.runTask` already has it in scope.

A package-level `runtimeGOOS` var (default `runtime.GOOS`, overridable
in tests) lets the platform-aware tests exercise both branches
deterministically without per-OS CI runners.

## Tests

- `TestResolveTextFlag` covers file-source preservation with non-ASCII
  (`标题 / 中文段落`), missing-file error, empty-file rejection, and
  three-way mutual exclusion.
- `TestInjectRuntimeConfigAvailableCommandsIsNeutral` sweeps every
  non-Codex provider × {linux, darwin, windows} and pins (a) all three
  flag modes are listed and (b) the over-spread substrings
  (`MUST pipe via stdin`, `Agent-authored comments should always pipe
  content via stdin`, `use --description-stdin and pipe a HEREDOC`) are
  GONE.
- `TestInjectRuntimeConfigCodexWindowsRecommendsContentFile` pins the
  Codex Windows file-first carve-out + the codex/linux stdin retention.
- `TestBuildCommentReplyInstructionsCodexLinux` /
  `TestBuildCommentReplyInstructionsCodexWindowsUsesContentFile` /
  `TestInjectRuntimeConfigCodexWindowsCommentTriggerHasNoStdin` keep
  the Windows file-first end-to-end pin for codex.
- `TestBuildCommentReplyInstructionsNonCodexUsesInline` sweeps every
  non-Codex provider × {linux, darwin, windows}, pins the inline
  template, bans the codex-specific stdin/file substrings.

`go test ./...` and `go vet ./...` clean.

Closes #2198, #2236.

Co-authored-by: multica-agent <github@multica.ai>
2026-05-08 16:24:59 +08:00
..