This is an automated email from the ASF dual-hosted git repository.
lizhimins pushed a commit to branch rocketmq-studio
in repository https://gitbox.apache.org/repos/asf/rocketmq-dashboard.git
The following commit(s) were added to refs/heads/rocketmq-studio by this push:
new 03cc74a5c fix(ai): reset stale capability probe state (#5591)
03cc74a5c is described below
commit 03cc74a5c518acc1e151c55a87499ade7fc31cd3
Author: aias00 <[email protected]>
AuthorDate: Thu Oct 8 20:26:47 2026 -0700
fix(ai): reset stale capability probe state (#5591)
`useState(true)` only takes effect on mount, so when `enabled` re-ran the
probe after a mock-mode
switch the previous explicit `false` survived - permanently when the probe
failed - contradicting
the unknown-state fallback this file documents at the top. Reset to that
fallback at the start of
each probe.
---
.../pages/ai/hooks/useAgentCapabilities.test.ts | 87 ++++++++++++++++++++++
web/src/pages/ai/hooks/useAgentCapabilities.ts | 4 +
2 files changed, 91 insertions(+)
diff --git a/web/src/pages/ai/hooks/useAgentCapabilities.test.ts
b/web/src/pages/ai/hooks/useAgentCapabilities.test.ts
new file mode 100644
index 000000000..fc22a2a13
--- /dev/null
+++ b/web/src/pages/ai/hooks/useAgentCapabilities.test.ts
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { act, renderHook, waitFor } from '@testing-library/react';
+import { beforeEach, describe, expect, it, vi } from 'vitest';
+import type { AiAgentCapabilitiesVO } from '../../../api/aiEvents';
+import { useAgentCapabilities } from './useAgentCapabilities';
+
+vi.mock('../../../api/aiConversations', () => ({
+ getAgentCapabilities: vi.fn(),
+}));
+
+import { getAgentCapabilities } from '../../../api/aiConversations';
+
+const capabilitiesMock = vi.mocked(getAgentCapabilities);
+
+function deferred<T>() {
+ let resolve: (value: T) => void = () => undefined;
+ let reject: (reason?: unknown) => void = () => undefined;
+ const promise = new Promise<T>((release, fail) => {
+ resolve = release;
+ reject = fail;
+ });
+ return { promise, resolve, reject };
+}
+
+function capabilities(rmqctlAvailable: boolean): AiAgentCapabilitiesVO {
+ return {
+ rmqctlAvailable,
+ claudeAvailable: true,
+ qoderAvailable: true,
+ mcpEnabled: true,
+ l3ToolsAllowed: false,
+ };
+}
+
+describe('useAgentCapabilities', () => {
+ beforeEach(() => {
+ capabilitiesMock.mockReset();
+ });
+
+ it('failedReprobeDoesNotRetainAnOlderUnavailableResultTest', async () => {
+ const firstProbe = deferred<AiAgentCapabilitiesVO>();
+ const secondProbe = deferred<AiAgentCapabilitiesVO>();
+ capabilitiesMock
+ .mockReturnValueOnce(firstProbe.promise)
+ .mockReturnValueOnce(secondProbe.promise);
+
+ const { result, rerender } = renderHook(
+ ({ enabled }: { enabled: boolean }) => useAgentCapabilities(enabled),
+ { initialProps: { enabled: true } },
+ );
+
+ await act(async () => {
+ firstProbe.resolve(capabilities(false));
+ await firstProbe.promise;
+ });
+ expect(result.current).toBe(false);
+
+ rerender({ enabled: false });
+ expect(capabilitiesMock).toHaveBeenCalledTimes(1);
+
+ rerender({ enabled: true });
+ await waitFor(() => expect(capabilitiesMock).toHaveBeenCalledTimes(2));
+ expect(result.current).toBe(true);
+
+ await act(async () => {
+ secondProbe.reject(new Error('temporary network failure'));
+ await secondProbe.promise.catch(() => undefined);
+ });
+ expect(result.current).toBe(true);
+ });
+});
diff --git a/web/src/pages/ai/hooks/useAgentCapabilities.ts
b/web/src/pages/ai/hooks/useAgentCapabilities.ts
index ba0781cd1..27f9e75ee 100644
--- a/web/src/pages/ai/hooks/useAgentCapabilities.ts
+++ b/web/src/pages/ai/hooks/useAgentCapabilities.ts
@@ -35,6 +35,10 @@ export function useAgentCapabilities(enabled: boolean):
boolean {
useEffect(() => {
if (!enabled) return;
let cancelled = false;
+ // A new probe makes the previous explicit answer stale. Until this
request answers, follow the
+ // documented unknown-state fallback instead of continuing to claim the
tool is unavailable.
+ // eslint-disable-next-line react-hooks/set-state-in-effect
+ setRmqctlAvailable(true);
getAgentCapabilities()
.then((capabilities) => {
if (!cancelled) setRmqctlAvailable(capabilities.rmqctlAvailable);