diff --git a/frontend/src/i18n/locales/en.json b/frontend/src/i18n/locales/en.json
index 6aa13d30..1f136cec 100644
--- a/frontend/src/i18n/locales/en.json
+++ b/frontend/src/i18n/locales/en.json
@@ -228,8 +228,9 @@
"unavailable": "Expiry unavailable",
"expiresOn": "Expires {{date}}"
},
- "agentHint": "Pick an agent from the catalog to install into this pod.",
- "browseAgents": "Browse agents →",
+ "agentHint": "Connect an agent you already run — Claude Code, Codex, Cursor — and it joins this pod. Takes about two minutes.",
+ "connectOwnAgent": "Connect your own agent →",
+ "browseAgents": "Or browse the catalog",
"errors": {
"loadFailed": "Could not load invite links.",
"generateFailed": "Could not generate invite.",
diff --git a/frontend/src/i18n/locales/zh-CN.json b/frontend/src/i18n/locales/zh-CN.json
index c1f539e4..87828b35 100644
--- a/frontend/src/i18n/locales/zh-CN.json
+++ b/frontend/src/i18n/locales/zh-CN.json
@@ -228,8 +228,9 @@
"unavailable": "无法查看有效期",
"expiresOn": "{{date}} 到期"
},
- "agentHint": "从目录中选择要安装到这个 Pod 的智能体。",
- "browseAgents": "浏览智能体 →",
+ "agentHint": "连接你已经在运行的智能体 —— Claude Code、Codex、Cursor —— 它就会加入这个 Pod。大约需要两分钟。",
+ "connectOwnAgent": "连接你自己的智能体 →",
+ "browseAgents": "或浏览目录",
"errors": {
"loadFailed": "邀请链接加载失败。",
"generateFailed": "邀请链接生成失败。",
diff --git a/frontend/src/v2/__tests__/V2InviteModal.test.tsx b/frontend/src/v2/__tests__/V2InviteModal.test.tsx
index 96a0b232..0b5a2ab5 100644
--- a/frontend/src/v2/__tests__/V2InviteModal.test.tsx
+++ b/frontend/src/v2/__tests__/V2InviteModal.test.tsx
@@ -84,7 +84,12 @@ describe('V2InviteModal', () => {
renderModal('agent');
expect(screen.getByRole('tab', { name: 'Add agent' })).toHaveAttribute('aria-selected', 'true');
- expect(screen.getByRole('button', { name: 'Browse agents →' })).toBeInTheDocument();
+ // The primary action is connect-your-own, not the v1 catalog. The starter
+ // panel deep-links here, so it lands on the flow that works rather than on
+ // the template list that produced the 2026-08-14 dead seat.
+ expect(screen.getByRole('button', { name: 'Connect your own agent →' })).toBeInTheDocument();
+ // The catalog is demoted, not removed.
+ expect(screen.getByRole('button', { name: 'Or browse the catalog' })).toBeInTheDocument();
expect(screen.queryByRole('button', { name: 'Generate invite link' })).not.toBeInTheDocument();
});
});
diff --git a/frontend/src/v2/__tests__/V2InviteModalAgentPath.test.tsx b/frontend/src/v2/__tests__/V2InviteModalAgentPath.test.tsx
new file mode 100644
index 00000000..d612aae7
--- /dev/null
+++ b/frontend/src/v2/__tests__/V2InviteModalAgentPath.test.tsx
@@ -0,0 +1,83 @@
+import React from 'react';
+import { render, screen, fireEvent } from '@testing-library/react';
+import { MemoryRouter } from 'react-router-dom';
+import V2InviteModal from '../components/V2InviteModal';
+import en from '../../i18n/locales/en.json';
+import zhCN from '../../i18n/locales/zh-CN.json';
+
+/**
+ * The in-pod "add an agent" path must lead to the flow that works.
+ *
+ * It used to offer exactly one button, into `/v2/agents/browse` — the v1
+ * AgentsHub, whose catalog still serves 21 internal and smoke-test rows
+ * (`smoke-claude`, `demo-target`, `pod-architect`, `cl-critic`, …) beside real
+ * entries. On 2026-08-14 a real user took that door, installed the
+ * `claude-code` template, never started a local session, asked it the same
+ * question three times and got silence. They had a working Scout in the next
+ * room the entire time.
+ *
+ * So: connect-your-own leads, the catalog stays reachable underneath.
+ */
+
+const mockNavigate = jest.fn();
+jest.mock('react-router-dom', () => {
+ const actual = jest.requireActual('react-router-dom');
+ return { ...actual, useNavigate: () => mockNavigate };
+});
+
+jest.mock('../hooks/useV2Api', () => ({
+ useV2Api: () => ({
+ get: jest.fn().mockResolvedValue({ invites: [] }),
+ post: jest.fn(), patch: jest.fn(), del: jest.fn(),
+ }),
+}));
+
+jest.mock('../../context/AuthContext', () => ({
+ useAuth: () => ({ currentUser: { _id: 'u1', username: 'sam' } }),
+}));
+
+const POD = '6a7d154b0ec237d4b15dd28b';
+
+const open = () => render(
+
+
+ ,
+);
+
+describe('the in-pod add-agent path', () => {
+ beforeEach(() => jest.clearAllMocks());
+
+ test('the primary action goes to BYO connect, with the pod prefilled', () => {
+ open();
+
+ const cta = screen.queryByText(en.inviteModal.connectOwnAgent);
+ expect(cta).toBeTruthy();
+ fireEvent.click(cta as HTMLElement);
+
+ // `?pod=` — V2AgentBYO reads `pod`, NOT `podId`. Getting this wrong sends
+ // the user to an unprefilled form, which is how a two-minute flow becomes
+ // a dead end.
+ expect(mockNavigate).toHaveBeenCalledWith(`/v2/agents/byo?pod=${POD}`);
+ });
+
+ test('the v1 catalog is demoted, not the only door', () => {
+ open();
+
+ const browse = screen.queryByText(en.inviteModal.browseAgents);
+ expect(browse).toBeTruthy();
+ fireEvent.click(browse as HTMLElement);
+
+ expect(mockNavigate).toHaveBeenCalledWith(`/v2/agents/browse?podId=${POD}`);
+ });
+});
+
+describe('both locales carry the new primary action', () => {
+ test.each([['en', en as any], ['zh-CN', zhCN as any]])('%s', (_l, bundle) => {
+ // Two of the users this change is for wrote Chinese.
+ expect(bundle.inviteModal.connectOwnAgent).toBeTruthy();
+ expect(bundle.inviteModal.browseAgents).toBeTruthy();
+ // The hint must describe connecting your own agent, not picking from a
+ // catalog — the sentence is what sets the expectation before the click.
+ expect(bundle.inviteModal.agentHint).not.toMatch(/catalog|目录/);
+ });
+});
diff --git a/frontend/src/v2/components/V2InviteModal.tsx b/frontend/src/v2/components/V2InviteModal.tsx
index 1696c6f1..02257958 100644
--- a/frontend/src/v2/components/V2InviteModal.tsx
+++ b/frontend/src/v2/components/V2InviteModal.tsx
@@ -337,12 +337,34 @@ const V2InviteModal: React.FC = ({
)}
{tab === 'agent' && (
<>
+ {/* The primary action is CONNECT YOUR OWN, not the catalog.
+ This tab used to offer exactly one button, into
+ /v2/agents/browse — the v1 AgentsHub, whose catalog still
+ serves 21 internal and smoke-test rows alongside real
+ entries. A user on 2026-08-14 took that door, installed the
+ `claude-code` template, never started a local session, asked
+ it a question three times and got silence. Connecting your
+ own agent is the path that works and the one the honesty
+ surface can report on, so it leads. The catalog stays
+ reachable underneath rather than being the only way in.
+
+ Note `?pod=` — V2AgentBYO reads `pod`, not `podId`. */}