feat(cli): gate standalone resource command families - #2396
Conversation
|
Claude Security Review: no high-confidence findings. (run) |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## refactor #2396 +/- ##
=========================================
Coverage 97.25% 97.25%
=========================================
Files 612 612
Lines 41004 41019 +15
=========================================
+ Hits 39878 39893 +15
Misses 1126 1126 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
AgentCore Harness Review
Verdict: Looks good
Nice, tightly scoped gating change. The imperative-commands flag mirrors the existing imperative-mutation-commands pattern, and the test matrix in src/handlers/imperative.test.tsx covers the important combinations (parent undefined/false/true × mutations off/on, CLI rejection without Core calls, root registration without config IO, TUI redirect for hidden menus, CLI-only fallback preservation).
A few things I specifically checked and are fine:
- TUI redirect for hidden commands —
RouterScreennow resolves the command first and issues a<Navigate replace>to the resolved path when the requested path is unreachable. Tests coverharness,runtime/endpoint,harness/version, and the CLI-onlypaymentfallback. - Test seeding consistency — Tests that need imperative commands now pass
IMPERATIVE_GLOBAL_CONFIGto bothcreateRootHandler({ globalConfig })andnew TestGlobalConfigAccessor({ initialConfigData }), so the synchronous snapshot and any downstream accessor reads agree. - Doc generator —
optional: truegroups are dropped only when the command isn't discovered;missing(ungrouped) commands still fail validation, andunknownnow only reports required groups. - Gateway mutation gate stays independent — Enabling the parent doesn't auto-enable Gateway CUD; the existing
imperative-mutation-commandsbehavior is preserved and tested.
No serious issues; nothing to change before merge from my side.
|
Claude Security Review: no high-confidence findings. (run) |
|
Claude Security Review: no high-confidence findings. (run) |
| * Schema for the global config file. All fields should be optional with defaults defined. | ||
| */ | ||
| export const globalConfigFileSchema = z.object({ | ||
| "imperative-commands": z.boolean().optional(), |
There was a problem hiding this comment.
what is the interaction between these two flags? will imperative-mutation-commands only work if both are enabled?
There was a problem hiding this comment.
Yes it would only work if you have the imperative command enabled as well. So both would need to be set to true. In my opinion we could probably remove the other flag in general, but I thought it was OOS from this PR and could be done separately.
| logger: Logger; | ||
| globalConfigAccessor: GlobalConfigAccessor; | ||
| /** Resolved startup settings used when constructing command trees. */ | ||
| globalConfig?: GlobalConfig; |
There was a problem hiding this comment.
whats the difference between globalConfig and globalConfigAccessor? can we remove globalConfig in favor of the accessor?
There was a problem hiding this comment.
globalConfig is the settings object already loaded by await globalConfigAccessor.get() at startup. We use those values when building the command tree, so disabled commands are excluded from help and TUI menus.
globalConfigAccessor is still needed by handlers such as config to read and save settings. Passing both does not load the file twice. The accessor caches the loaded value.
Using only the accessor is possible, but its get() is asynchronous while command-tree construction is currently synchronous, so that would require a separate API change.
There was a problem hiding this comment.
This system came from the first PR where I hid the gateway CUD commands and so this is mainly a trade off coming from specifically hiding commands based on saved configuration. If you think it'd be worth it to make the accesssor's get() synchronous then I could make the change in this PR.
There was a problem hiding this comment.
I looked into this further. The current setup works, and I’d probably lean to keep it for this PR. We could remove the separate globalConfig argument by making the builder read from the accessor, but that would also require changing the shared test setup and its callers to all await renderScreen. I think that cleanup belongs in a separate PR where we also possibly remove the old mutation config flag. The accessor’s get() can stay async.
There was a problem hiding this comment.
I see, thanks for explaining. perhaps moving the compile to be async makes sense as a future clean up to avoid what feels like duplicate parameters.
My concern is that its difficult to know where each is used without looking at the code, which means callers have to know the implementation details of createRootHandler to properly use it.
| }; | ||
| } | ||
|
|
||
| export function renderImperativeScreen( |
There was a problem hiding this comment.
if i called this method with options that don't include the feature flag, wouldn't the imperative screen not be rendered?
If so, that feels misleading.
There was a problem hiding this comment.
This test helper uses an imperative enabled preset when globalConfig is omitted, but honors explicitly supplied config, including a disabled flag. We could rename it to renderScreenWithImperativeDefaults to make clear that it supplies defaults rather than forcing imperative commands on.
There was a problem hiding this comment.
Would it be easier to inline?
renderScreen(path, {
...options,
globalConfig: IMPERATIVE_GLOBAL_CONFIG,
});
i know its more code, but then its explicit about what config its using. not a blocker.
There was a problem hiding this comment.
Yeah I was looking into this for a bit. I think this is probably the cleanest method.
| io: io.io, | ||
| logger: createSilentLogger(), | ||
| globalConfigAccessor: new TestGlobalConfigAccessor(), | ||
| globalConfigAccessor: new TestGlobalConfigAccessor({ initialConfigData: globalConfig }), |
There was a problem hiding this comment.
why do we need to pass global config twice?
There was a problem hiding this comment.
We seed the fake accessor with the same settings used to build the command tree. Otherwise, enabling commands through the globalConfig leaves the fake config service reporting that they’re disabled. These tests don’t test that read, but keeping the fixture consistent will avoid that mismatch.
| // and open their help instead (see CliOnlyScreen). | ||
| export function RouterScreen({ ctx, path, tuiOnlyCommands = [] }: RouterScreenProps) { | ||
| export function RouterScreen(props: RouterScreenProps) { | ||
| const command = resolveCommand(props.ctx.require(CommandKey), props.path); |
There was a problem hiding this comment.
why exactly does the behavior here need to change? How is this change related to the goal of putting the resource commands behind a feature flag?
There was a problem hiding this comment.
The issue is that some existing actions on those screens still return to the Harness menu afterward. Hiding the command won't remove the existing navigation paths. The check essentially says if if this menu isn’t available anymore then go to an available parent menu before showing any choices. When testing this it worked well for me, but there could be a better solution for this.
|
approving and merging to unblock other work. |
This PR hides Harness, Identity, Runtime, Memory, Gateway, and Payment commands by default, keeping the CLI and TUI focused on project workflows and evals. A configuration gate (
imperative-commands) controls which command families are registered, so command execution, help, and menus stay consistent. The existing Gateway mutation gate and project workflows are preserved, and I've updated the documentation to reflect the default command surface.The commands are disabled by default or can be disabled using this command:
To enable the commands: