Skip to content

[Feature]: CLI - ACP: select the account profile at launch (personal and work token plans in one editor) #391

Open

Description

@minmax

Product or interface

CLI - ACP


中文摘要:mcode acp 启动时只调用一次 prepareTuiDataDir(),整个 stdio server 的所有 session 共享同一个 runtime、同一个账号。目前选择账号的唯一手段是 MINIMAX_DATA_DIR / MAVIS_DATA_DIR,但它是机器级全局变量 —— 给编辑器配了它,交互式 TUI 的账号也会一起被改掉。而 main 上并没有 --profile 标志:内部虽然有 profile 概念(getProfile() → ~/.minimax-<profile>),但 commander 从未注册该选项,因此 mcode acp --profile work 报 unknown option。建议让 ACP 支持按 profile 启动,并让客户端能看出当前 agent 用的是哪个账号。

Use case and problem

I build an ACP client, and I also use mcode interactively on the same machine. The two need different accounts: a personal token plan for the interactive TUI, and a work token plan for the agent inside my editor.

The account is bound once per ACP process, before the client speaks. runTuiAcpCommand resolves the data directory a single time and builds one runtime around it, and that runtime serves every session on that stdio connection:

  • packages/tui/src/cli/run-acp-command.ts:59 — const dataDir = await prepareTuiDataDir()
  • packages/tui/src/cli/run-acp-command.ts:60-66 — one createRuntime({ dataDir, ... surface: 'acp' }) for the whole server

So the account is decided when the editor spawns the process, and no session on that connection can be anything else. An ACP server is long-lived — an editor may keep one alive across many threads, projects, and days — so "one account per server" means "one account per machine, in practice".

The only existing lever is a machine-global environment variable. The TUI resolves its data directory as readDataDirOverride(environment) ?? getDefaultDataDir() (packages/tui/src/runtime/data-dir.ts:39-44), and the override reads MINIMAX_DATA_DIR / MAVIS_DATA_DIR from process.env (packages/config/src/config.ts:1316-1320). Setting it in my editor's agent config to give the ACP server a work account also changes the account that the interactive TUI, mcode login, and every other mcode process on that machine use. There is no per-invocation alternative today.

And --profile does not exist on main. The profile concept is already in the resolution chain — getExplicitProfileEnv() reads --profile from raw argv and resolveDataDir({ profile }) produces ~/.minimax-<profile> — but no CLI surface registers the option. getExplicitProfileEnv is cliArgValue("profile") ?? runtimeEnv("PROFILE") (packages/config/src/config.ts:1333-1336), and cliArgValue is a raw-argv scan (packages/config/src/config.ts:1220) that runs whether or not commander knows the flag. The env fallback is behind isLegacyRuntimeEnvAllowed() (packages/config/src/config.ts:83-85), so it is off unless __MAVIS_ALLOW_LEGACY_RUNTIME_ENV=1.

I verified on 0.5.8 that mcode acp --profile work is rejected as an unknown option, while the internal scanner already understands --profile — the two disagree about what the flag is.

The protocol gives the client no way to see which account it got. initialize advertises exactly one authentication method, with no account dimension:

"authMethods": [
  { "type": "terminal", "id": "minimax-code-login",
    "name": "Sign in to MiniMax Code", "args": ["login"] }
]

That is the same single entry whether the server was started on the default profile or a named one. agent/authenticate rejects any other methodId with Unknown authentication method. (packages/tui/src/acp/agent.ts:45, :461-468, :486-492), and an unauthenticated runtime fails with authRequired and the message "Run mcode login and try again." (packages/tui/src/acp/agent.ts:2110-2115).

So an ACP client can ask the user to sign in, but it cannot express which sign-in, cannot display which account is live, and cannot host a personal thread and a work thread in the same editor.

Current chain

The data-directory resolution is already profile-aware end to end; only the ACP edge and the CLI surface are missing.

  • packages/config/src/config.ts:1333-1336 — getExplicitProfileEnv() = cliArgValue("profile") ?? runtimeEnv("PROFILE")
  • packages/config/src/config.ts:1506 — resolveDataDir({ profile: explicitProfile })
  • packages/config/src/data-dir.ts:36-42 — basenameForProfile → getPrimaryDataDirPath returns ~/.minimax-<profile>
  • packages/oauth-core/src/namespace.ts:21-41 — the OAuth namespace derives both its paths and its credential key from that data directory, so a different data directory is a different token
  • packages/tui/src/cli/run-acp-command.ts:59 — the single point where ACP binds it

Desired behavior

Let the ACP server start on a named profile, and let the client see which one it has.

Acceptance criteria

  1. mcode acp --profile <name> starts the server bound to that profile's data directory and its stored credential. The flag is accepted on acp and on session-taking subcommands, so a client can set it in its agent args with no new protocol method.
  2. initialize reports the active profile (in _meta or alongside agentInfo), so a client can display which account the agent is signed into. Today the client cannot tell a personal agent from a work agent, which is the actual problem — a wrong-account session is invisible until it bills or writes to the wrong tenant.
  3. Selecting a profile never mutates another profile's directory, and never changes the account the interactive TUI uses. This is the same isolation criterion PR feat: support session-scoped interactive model overrides #227 and issue [Feature]: CLI - ACP: expose the session context-window selection (parity with the /model picker) #384 held model selection to.
  4. A profile name that is not usable is refused with an error, not silently replaced by the default profile. The name becomes a path segment under $HOME, so an unchecked value must not resolve outside the home directory, and a silent fallback would open the wrong account.
  5. Nice to have: a per-session profile. sessionCapabilities already advertises list, fork, resume, and close (packages/tui/src/acp/agent.ts:452-457), so one long-lived server could host a personal thread and a work thread side by side instead of requiring one server per account. Baseline is (1) + (2); this is the larger win.

Interaction example

# editor agent config — baseline
command: mcode
args:    ["acp", "--profile", "work"]

initialize
  → _meta: { "minimax-code/profile": "work" }
  → authMethods: [ { id: "minimax-code-login", ... } ]   # unchanged shape

# two servers, one editor
server A: mcode acp                        → personal, threads for side projects
server B: mcode acp --profile work         → work, threads for client work

Platform

macOS (macOS 15 arm64, mcode 0.5.8, source at 9b9c07b). The gap is in the CLI/protocol surface, so it should apply to every platform.

Alternatives and additional context

  • MINIMAX_DATA_DIR per client instance. Already works, and is what I use today. The problem is that it is read from process.env and is therefore machine-global in practice: the editor's agent config either leaks the work account into the interactive TUI or cannot be set without a wrapper. A first-class flag is the same isolation as mcode exec --config <path> but scoped to the account.
  • A separate binary or a --lane-style internal switch. mcode --lane already exists as an internal separation mechanism, but it is rejected in the public build and is documented as internal; it is not a user-facing account selector.
  • Related: [Bug]: Unable to use team token plan on new app #88 asks for switching between a personal and a team token plan, which is the same underlying need on the Desktop side. [Feature]: CLI - ACP: expose the session context-window selection (parity with the /model picker) #384 and Third-party desktop client integration: is ACP the right surface for embedding mcode? #176 are the same ACP surface this touches.
  • I have a working implementation of criteria 1–4 on a branch (core module, the CLI flag, a mcode profile list|current|remove command, the login-restart fix, and tests) and can share a patch or a branch if that is useful. I am not a collaborator, so per CONTRIBUTING.md I am not opening a PR — happy to provide the diff for someone with push access.

Verification: mcode 0.5.8; the authMethods transcript above is a live initialize over stdio. No private project content included.

Activity

  1. minmax commented on Sep 29, 2026

    @minmax
    Author

    Diff is up now, so here is the concrete branch for criteria 1–4:

    Compare against main: main...minmax:feat/auth-profiles

    Branch: https://github.com/minmax/minimax-code/tree/feat/auth-profiles
    Commit: 3da282c7 — 1 commit, 22 files, based on main at 1e136bf, no merge commits.

    It implements the baseline only. It does not attempt criterion 5 (per-session profile): runTuiAcpCommand resolves one dataDir per process and every session on that connection shares the runtime, so serving two accounts from one server needs a runtime change rather than another flag. That is the piece I would want direction on before building.

    An adversarial review of the first draft found four problems that are worth calling out, because two of them were the same wrong-account failure this issue is about:

    • --profile default resolved to ~/.minimax-default — a second account with its own token, never listed by mcode profile list and refused by mcode profile remove default. default is now reserved and means the implicit profile.
    • mcode profile current read the environment directly, so with both selectors set it reported the environment's account while the process had already opened the flag's directory.
    • The raw-argv scanner ran past --, so a prompt containing --profile work redirected the account; it also returned the first occurrence while commander returns the last.
    • The generated mcode-tools launchers unset MINIMAX_DATA_DIR but not MINIMAX_PROFILE, so a child could inherit a profile that disagreed with the data directory it was handed.

    Validation (macOS, main at 1e136bf):

    Check Result
    pnpm verify --profile platform (13 of 14 gates) Passed
    test:capabilities 203 files, 5241 tests passed
    Profile-specific tests 127 passed (validation, argv resolution, CLI, restart, namespace isolation, smoke)
    mcode acp --profile work handshake over stdio Starts, one authMethods entry, as on main

    The only failing gate is test:sandbox, which fails identically on a clean main on this machine (srt-macos probe vs. the mavis-trash shim on PATH) — unrelated to this change.

    I checked the observations in the report against main before filing and corrected three line references that had drifted. Happy to answer anything about the diff, or to drop criterion 5 if you would rather keep the runtime untouched.

  2. hetaoBackend commented on Sep 29, 2026

    @hetaoBackend
    Collaborator

    This was generated by AI during triage.

    @minmax — I checked your chain against main and it holds up, and the branch is a strong contribution. Answering your open question below.

    Verified on main

    • The profile-aware resolution chain is exactly as you describe: getExplicitProfileEnv() = cliArgValue("profile") ?? runtimeEnv("PROFILE") (packages/config/src/config.ts:1333-1334), feeding resolveDataDir({ profile }) (:1506) and basenameForProfile → ~/.minimax-<profile> (packages/config/src/data-dir.ts:36-42).
    • No CLI surface registers --profile — commander has no such option, so mcode acp --profile work is rejected while the internal raw-argv scanner still understands the flag. The two disagreeing about what the flag is is a real, user-visible inconsistency.
    • The OAuth namespace derives its credential key from the data directory (packages/oauth-core/src/namespace.ts), so a distinct data dir really is a distinct account — which is what makes the isolation criterion meaningful rather than cosmetic.
    • The single binding point is runTuiAcpCommand (packages/tui/src/cli/run-acp-command.ts:59), so this is genuinely an edge-surface change.

    Your diagnosis is right that the only current lever is a machine-global MINIMAX_DATA_DIR / MAVIS_DATA_DIR, and that it silently changes the account for the interactive TUI and every other mcode process on the machine. That's a genuinely bad property for a long-lived ACP server.

    Your adversarial review is the most valuable part

    The four issues you caught are exactly the class of bug this feature is dangerous if it gets wrong:

    • --profile default → ~/.minimax-default creating a phantom second account was a real correctness/security problem (wrong tenant, invisible to profile list, unremovable). Reserving default as the implicit profile is the right call.
    • profile current reading the environment while the process had already opened the flag's directory is a genuine "reports the wrong thing" inconsistency.
    • The raw-argv scanner running past -- and returning the first occurrence — both are correct catches; commander takes the last occurrence, so a prompt containing --profile work could silently redirect the account. That's the highest-severity item on your list.
    • The generated launchers unsetting MINIMAX_DATA_DIR but not MINIMAX_PROFILE is a subtle inherited-state leak that would have been very hard to find later.

    Fixing these before asking for review, and documenting them, is the right instinct. The validation numbers (13/14 gates, 5,241 capability tests, 127 profile-specific tests) make this easy to adopt.

    Direction on criterion 5 (per-session profile) — my recommendation

    Keep it out of this change, exactly as you've scoped it. Reasons:

    1. It's a runtime change, not a flag. runTuiAcpCommand builds one runtime per process and every session on that connection shares it. Making the account per-session means either multiple runtimes (significant lifecycle/credential implications) or making the data dir mutable mid-connection — both meaningfully larger than criteria 1–4, and neither is a prerequisite for the stated problem.
    2. Criteria 1–4 already solve the reported pain (personal TUI + work ACP on the same machine) with zero runtime change. Per-session profiles solve a different, larger problem (personal and work threads inside one editor at once) and should be evaluated on their own merits — possibly alongside the scheduler/parallel-agent work rather than smuggled in here.
    3. Keeping the runtime untouched is what makes this reviewable and mergeable now.

    So: land 1–4 as-is, and please don't drop it silently — file criterion 5 as a separate follow-up issue so it's tracked rather than lost. That way the runtime design gets its own discussion instead of being decided by whatever's convenient in this diff.

    Suggested follow-ups worth attaching to criterion 5 (when you file it)

    • Whether the ACP client can even express a per-session account (this may need a protocol affordance, not just a runtime one).
    • What happens to an already-running session if the data dir changes (should be refused, not migrated).
    • How profile list / profile current report multiple live profiles in one process.

    Process

    Same path as #149, #364, and your #384 branch: the branch is ready for a maintainer to bring in with attribution whenever they pick it up. To make that easy, please keep it a single commit rebased on current main (you cited 1e136bf) and re-run pnpm verify at that point, since #394/#395 have landed since — in particular test:sandbox was failing for exactly the mavis-trash-on-PATH reason you correctly identified, and is now fixed by pinning the probe PATH, so that gate should pass for you too.

    Keeping this in needs-triage for a maintainer to review and merge. Good contribution.

    No credentials or private data echoed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestneeds-triageAwaiting maintainer assessment

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions