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);

Reply via email to