diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 2eca4a469..bfdc3fae0 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -17,7 +17,7 @@ - Fixed generated profile aliases to pass the profile as `--profile=`, avoiding the separate argv value that could be misread as an initial prompt while still forcing the CLI's explicit profile bootstrap path. - Fixed `--alias` when run from a source checkout (`bun src/cli.ts` / `omp-test`) so the generated profile command targets that same checkout instead of a stale installed `omp` binary, while preserving the directory where the alias is invoked. -- Fixed explicit `omp launch --profile ` / `omp launch --alias ` so `launch` behaves like the default command during profile bootstrap instead of blocking global profile extraction. +- Fixed explicit `omp launch --profile ` / `omp launch --alias ` and `omp acp --profile ` / `omp acp --alias ` so launch-shaped subcommands behave like the default command during profile bootstrap instead of blocking global profile extraction. - Fixed profile bootstrap and alias installation edge cases: `--profile` is now still honored for `launch` argv that merely contain subcommand-shaped words; a trailing global `--profile`/`--alias` after an unknown (extension) flag is still extracted unless that flag would consume it as a value-like successor (mirroring `parseArgs`); extension flags no longer parse literal text after `--`; alias installation preserves non-ENOENT shell config read failures; `/bin/sh` is rejected instead of being treated as bash; aliases cannot shadow `omp` case-insensitively; on Windows the PowerShell edition is inferred from `PSModulePath` (then `POWERSHELL_DISTRIBUTION_CHANNEL`) when `$SHELL` is unset; and the fish alias honors `$XDG_CONFIG_HOME`. - Fixed `/review`'s uncommitted-change mode in Jujutsu repositories to read `jj diff --git` from the current workspace, so non-default JJ workspaces include their working-copy changes instead of falling back to the colocated Git checkout. - Fixed empty assistant stop retry continuations preserving auto-retry state until a non-empty assistant turn completes or recovery reaches its retry cap. diff --git a/packages/coding-agent/src/cli/profile-bootstrap.ts b/packages/coding-agent/src/cli/profile-bootstrap.ts index 991e54407..7f2733118 100644 --- a/packages/coding-agent/src/cli/profile-bootstrap.ts +++ b/packages/coding-agent/src/cli/profile-bootstrap.ts @@ -35,6 +35,10 @@ import { isSubcommand } from "../cli-commands"; import { OPTIONAL_FLAGS, OPTIONAL_VALUE_FLAGS, STRING_VALUE_FLAGS, VALUELESS_FLAGS } from "./flag-tables"; +function isProfileBootstrapSubcommand(arg: string): boolean { + return arg === "launch" || arg === "acp"; +} + export interface ProfileBootstrapResult { argv: string[]; profile?: string; @@ -47,11 +51,12 @@ export interface ProfileBootstrapResult { * and the captured flag values. * * Global flag extraction stops only when the first residual argv token names a - * registered non-launch subcommand (e.g. `grep`): everything from that token - * onward is forwarded verbatim so a subcommand's own flags and positionals are - * never stolen (`omp grep --profile ` greps for `--profile`; it does not - * select a profile). `launch` is the explicit spelling of the default command, - * so `omp launch --profile work` still selects profile `work`. + * registered command that owns its own flags (e.g. `grep`): everything from + * that token onward is forwarded verbatim so a subcommand's own flags and + * positionals are never stolen (`omp grep --profile ` greps for + * `--profile`; it does not select a profile). `launch` and `acp` are explicit + * spellings of launch-shaped commands, so `omp launch --profile work` and + * `omp acp --profile work` still select profile `work`. * * Throws when either flag is supplied without a value. */ @@ -175,9 +180,9 @@ export function extractProfileFlags(argv: readonly string[]): ProfileBootstrapRe // Only the first residual argv token can be the dispatched subcommand. Once // any other token has been forwarded, later subcommand names are launch text. - // `launch` is special: it is an explicit spelling of the default command, - // so global launch flags that follow it must still be extracted. - if (canDispatchSubcommand && isSubcommand(arg) && arg !== "launch") { + // `launch` and `acp` are explicit spellings of launch-shaped commands, so + // global launch flags that follow them must still be extracted. + if (canDispatchSubcommand && isSubcommand(arg) && !isProfileBootstrapSubcommand(arg)) { sawSubcommand = true; } canDispatchSubcommand = false; diff --git a/packages/coding-agent/test/profile-bootstrap.test.ts b/packages/coding-agent/test/profile-bootstrap.test.ts index 465fd9b40..c9a68a19a 100644 --- a/packages/coding-agent/test/profile-bootstrap.test.ts +++ b/packages/coding-agent/test/profile-bootstrap.test.ts @@ -111,6 +111,14 @@ describe("extractProfileFlags", () => { }); }); + it("treats explicit acp as launch-shaped and keeps extracting globals", () => { + expect(extractProfileFlags(["acp", "--profile", "work"])).toEqual({ + argv: ["acp"], + profile: "work", + aliasName: undefined, + }); + }); + it("treats later subcommand-shaped words as launch text after explicit launch", () => { const result = extractProfileFlags(["launch", "grep", "--profile", "work"]); expect(result.profile).toBe("work"); diff --git a/packages/coding-agent/test/profile-cli.test.ts b/packages/coding-agent/test/profile-cli.test.ts index 31cedf43d..00232cad1 100644 --- a/packages/coding-agent/test/profile-cli.test.ts +++ b/packages/coding-agent/test/profile-cli.test.ts @@ -178,6 +178,32 @@ describe("global --profile flag", () => { expect(output).not.toContain(`${APP_NAME}/${VERSION}`); }); + it("installs a shell alias when acp is explicit", async () => { + const installSpy = vi.spyOn(profileAliasCli, "installProfileAlias").mockResolvedValue({ + shell: "bash", + configPath: "/home/me/.bashrc", + aliasName: "omp-work", + profile: "work", + command: "omp --profile=work", + reloadedWith: ". '/home/me/.bashrc'", + }); + const outSpy = vi.spyOn(process.stdout, "write").mockImplementation(() => true); + + await runCli(["acp", "--profile", "work", "--alias", "omp-work", "--version"]); + + expect(process.exitCode).toBe(0); + expect(installSpy).toHaveBeenCalledWith( + expect.objectContaining({ + profile: "work", + aliasName: "omp-work", + }), + ); + expect(getActiveProfile()).toBe("work"); + const output = outSpy.mock.calls.map(call => String(call[0] ?? "")).join("\n"); + expect(output).toContain("Created omp-work"); + expect(output).not.toContain(`${APP_NAME}/${VERSION}`); + }); + it("rejects missing profile values without dispatching", async () => { const errSpy = vi.spyOn(process.stderr, "write").mockImplementation(() => true); const outSpy = vi.spyOn(process.stdout, "write").mockImplementation(() => true);