From 0472a7374be270dc45a34d457911f00527c9c2ad Mon Sep 17 00:00:00 2001 From: Lily Shen <115414357+lilyshen0722@users.noreply.github.com> Date: Tue, 27 Oct 2026 21:42:17 -0700 Subject: [PATCH 01/12] feat(byo): harness picker rows come from the walk table (TASK-361) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The harness picker lists only harnesses with a passing walk on record, one row each with the connect method and OS dots. Rows are data in harnessWalkTable.ts, mirroring ~/agents/shared/d15/harness-walk-table.md: Claude Code, Codex, OpenCode, Qwen Code, DeepSeek Harness (private pods only). Hermes, goose, pi, Gemini and Grok Build stay hidden. Walked dots are filled, unwalked dots dashed, never hidden. Not wired into the BYO form yet; the next PR replaces the adapter select with this picker after ux-lead gates the render. Written by Swift, a Commonly agent — https://commonly.me/v2/pods/6a692a1be833c668acdb84cf Co-Authored-By: Claude Haiku 5.5 Co-authored-by: swift Agent-Model: claude/claude-haiku-5-5/default --- frontend/src/i18n/locales/en.json | 10 +++ frontend/src/i18n/locales/zh-CN.json | 10 +++ .../src/v2/__tests__/V2HarnessPicker.test.tsx | 63 +++++++++++++++++ .../src/v2/components/V2HarnessPicker.tsx | 68 ++++++++++++++++++ frontend/src/v2/utils/harnessWalkTable.ts | 69 +++++++++++++++++++ frontend/src/v2/v2.css | 40 +++++++++++ 6 files changed, 260 insertions(+) create mode 100644 frontend/src/v2/__tests__/V2HarnessPicker.test.tsx create mode 100644 frontend/src/v2/components/V2HarnessPicker.tsx create mode 100644 frontend/src/v2/utils/harnessWalkTable.ts diff --git a/frontend/src/i18n/locales/en.json b/frontend/src/i18n/locales/en.json index cd41b29eed..7048917b9d 100644 --- a/frontend/src/i18n/locales/en.json +++ b/frontend/src/i18n/locales/en.json @@ -1507,6 +1507,16 @@ "unavailable": "Connection status unavailable." }, "agentByo": { + "harnessPicker": { + "label": "Harness", + "connects": "connects", + "mac": "mac", + "linux": "linux", + "windows": "windows", + "connectsCommand": "one command", + "connectsTerminal": "terminal steps", + "privateOnly": "private pods only" + }, "title": "Bring your own agent", "description": "A running agent answers @mentions in its pod.", "form": { diff --git a/frontend/src/i18n/locales/zh-CN.json b/frontend/src/i18n/locales/zh-CN.json index 1667dc2a2c..7444adfc6f 100644 --- a/frontend/src/i18n/locales/zh-CN.json +++ b/frontend/src/i18n/locales/zh-CN.json @@ -1567,6 +1567,16 @@ "unavailable": "暂时无法获取连接状态。" }, "agentByo": { + "harnessPicker": { + "label": "运行环境", + "connects": "接入方式", + "mac": "Mac", + "linux": "Linux", + "windows": "Windows", + "connectsCommand": "一条命令", + "connectsTerminal": "终端步骤", + "privateOnly": "仅限私有 Pod" + }, "title": "接入你自己的智能体", "description": "智能体运行期间会在所在 Pod 回复 @提及。", "form": { diff --git a/frontend/src/v2/__tests__/V2HarnessPicker.test.tsx b/frontend/src/v2/__tests__/V2HarnessPicker.test.tsx new file mode 100644 index 0000000000..b47cbfce50 --- /dev/null +++ b/frontend/src/v2/__tests__/V2HarnessPicker.test.tsx @@ -0,0 +1,63 @@ +// @ts-nocheck +import React from 'react'; +import { fireEvent, render, screen, within } from '@testing-library/react'; +import V2HarnessPicker from '../components/V2HarnessPicker'; +import { HARNESS_WALK_TABLE, HIDDEN_HARNESS_IDS } from '../utils/harnessWalkTable'; + +afterEach(() => jest.clearAllMocks()); + +describe('V2HarnessPicker', () => { + it('renders exactly the walked harnesses from the walk table, in table order', () => { + render(); + const rows = within(screen.getByTestId('harness-picker')).getAllByRole('button'); + expect(rows.map((r) => r.getAttribute('data-testid'))).toEqual( + HARNESS_WALK_TABLE.map((row) => `harness-row-${row.id}`), + ); + expect(rows.map((r) => r.textContent.replace(/\s+/g, ' ').trim()).join('|')).toContain('Qwen Code'); + }); + + it('never renders a harness that has no walk on record', () => { + render(); + HIDDEN_HARNESS_IDS.forEach((id) => { + expect(screen.queryByTestId(`harness-row-${id}`)).toBeNull(); + }); + expect(screen.queryByText('Hermes')).toBeNull(); + expect(screen.queryByText('goose')).toBeNull(); + }); + + it('shows Qwen Code and hides Hermes, per the rev 3 walk table', () => { + render(); + expect(screen.getByTestId('harness-row-qwen')).toBeInTheDocument(); + expect(screen.queryByTestId('harness-row-hermes')).toBeNull(); + }); + + it('draws the OS dots from the table: filled when walked, dashed when not', () => { + render(); + const claude = screen.getByTestId('harness-row-claude'); + expect(within(claude).getByTestId('harness-dot-mac')).toHaveAttribute('data-state', 'walked'); + expect(within(claude).getByTestId('harness-dot-linux')).toHaveAttribute('data-state', 'not-walked'); + expect(within(claude).getByTestId('harness-dot-windows')).toHaveAttribute('data-state', 'not-walked'); + }); + + it('marks only DeepSeek Harness as private-pods-only', () => { + render(); + const privateRows = HARNESS_WALK_TABLE.filter((row) => row.privatePodsOnly).map((row) => row.id); + expect(privateRows).toEqual(['dsh']); + expect(within(screen.getByTestId('harness-row-dsh')).getByText('private pods only')).toBeInTheDocument(); + }); + + it('marks the selected row and reports the id it picks', () => { + const onSelect = jest.fn(); + render(); + expect(screen.getByTestId('harness-row-codex')).toHaveAttribute('aria-pressed', 'true'); + expect(screen.getByTestId('harness-row-claude')).toHaveAttribute('aria-pressed', 'false'); + fireEvent.click(screen.getByTestId('harness-row-opencode')); + expect(onSelect).toHaveBeenCalledWith('opencode'); + }); + + it('uses the connect method from the table for each row', () => { + render(); + expect(screen.getByTestId('harness-connects-claude')).toHaveTextContent('one command'); + expect(screen.getByTestId('harness-connects-opencode')).toHaveTextContent('terminal steps'); + }); +}); diff --git a/frontend/src/v2/components/V2HarnessPicker.tsx b/frontend/src/v2/components/V2HarnessPicker.tsx new file mode 100644 index 0000000000..5c3a6d4721 --- /dev/null +++ b/frontend/src/v2/components/V2HarnessPicker.tsx @@ -0,0 +1,68 @@ +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { HARNESS_WALK_TABLE, HarnessOsState, HarnessWalkRow } from '../utils/harnessWalkTable'; + +interface V2HarnessPickerProps { + selectedId: string; + onSelect: (id: string) => void; +} + +// One dot per OS: filled when walked, dashed ring when not. A row never hides an OS. +const OsDot = ({ state, os }: { state: HarnessOsState; os: string }) => ( +