Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions frontend/src/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1507,6 +1507,22 @@
"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",
"osState": {
"walked": "Walked on {{os}}",
"mcp-only": "{{os}}: connects over MCP only, no @mention listener",
"not-walked": "Not walked on {{os}} yet",
"not-offered": "Not offered on {{os}}"
}
},
"title": "Bring your own agent",
"description": "A running agent answers @mentions in its pod.",
"form": {
Expand Down
16 changes: 16 additions & 0 deletions frontend/src/i18n/locales/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -1567,6 +1567,22 @@
"unavailable": "暂时无法获取连接状态。"
},
"agentByo": {
"harnessPicker": {
"label": "运行器",
"connects": "接入方式",
"mac": "Mac",
"linux": "Linux",
"windows": "Windows",
"connectsCommand": "一条命令",
"connectsTerminal": "终端步骤",
"privateOnly": "仅限私密 Pod",
"osState": {
"walked": "{{os}} 已验证",
"mcp-only": "{{os}}:仅通过 MCP 接入,不会监听 @提及",
"not-walked": "{{os}} 尚未验证",
"not-offered": "{{os}} 上不提供"
}
},
"title": "接入你自己的智能体",
"description": "智能体运行期间会在所在 Pod 回复 @提及。",
"form": {
Expand Down
101 changes: 101 additions & 0 deletions frontend/src/v2/__tests__/V2HarnessPicker.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// @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';
import fs from 'fs';
import path from 'path';

const v2Css = fs.readFileSync(path.join(__dirname, '../v2.css'), 'utf8').replace(/\/\*[\s\S]*?\*\//g, '');
// Body of the first rule whose selector list includes `selector` as a complete selector
// (not a prefix of a longer class name), so grouped selectors match too.
const ruleBody = (selector) => {
// Escape the selector, then require it to start a selector-list entry and end at a comma or
// an opening brace. A compound selector such as `.dot--walked.is-active` does not match.
const escaped = selector.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const match = v2Css.match(new RegExp(`(?:^|[,\\n])\\s*${escaped}\\s*(?=,|\\{)[^{}]*\\{([^}]*)\\}`));
return match ? match[1] : '';
};

afterEach(() => jest.clearAllMocks());

describe('V2HarnessPicker', () => {
it('renders exactly the walked harnesses from the walk table, in table order', () => {
render(<V2HarnessPicker selectedId="claude" onSelect={jest.fn()} />);
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(<V2HarnessPicker selectedId="claude" onSelect={jest.fn()} />);
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(<V2HarnessPicker selectedId="claude" onSelect={jest.fn()} />);
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(<V2HarnessPicker selectedId="claude" onSelect={jest.fn()} />);
const claude = screen.getByTestId('harness-row-claude');
expect(within(claude).getByTestId('harness-dot-claude-mac')).toHaveAttribute('data-state', 'walked');
expect(within(claude).getByTestId('harness-dot-claude-linux')).toHaveAttribute('data-state', 'mcp-only');
expect(within(claude).getByTestId('harness-dot-claude-windows')).toHaveAttribute('data-state', 'mcp-only');
expect(within(screen.getByTestId('harness-row-codex')).getByTestId('harness-dot-codex-linux')).toHaveAttribute('data-state', 'not-walked');
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
});

it('gives each dot the state class the stylesheet keys on, for every state', () => {
render(<V2HarnessPicker selectedId="claude" onSelect={jest.fn()} />);
expect(screen.getByTestId('harness-dot-claude-mac')).toHaveClass('v2-harness-picker__dot--walked');
expect(screen.getByTestId('harness-dot-claude-linux')).toHaveClass('v2-harness-picker__dot--mcp-only');
expect(screen.getByTestId('harness-dot-codex-linux')).toHaveClass('v2-harness-picker__dot--not-walked');
expect(screen.getByTestId('harness-dot-dsh-windows')).toHaveClass('v2-harness-picker__dot--not-offered');
});

it('titles each OS dot with why it is filled or dashed', () => {
render(<V2HarnessPicker selectedId="claude" onSelect={jest.fn()} />);
expect(screen.getByTestId('harness-dot-claude-mac')).toHaveAttribute('title', 'Walked on Mac');
expect(screen.getByTestId('harness-dot-claude-linux')).toHaveAttribute('title', 'Linux: connects over MCP only, no @mention listener');
expect(screen.getByTestId('harness-dot-dsh-windows')).toHaveAttribute('title', 'Not offered on Windows');
});

it('keeps the filled and dashed dot rules in v2.css', () => {
expect(ruleBody('.v2-harness-picker__dot--walked')).toContain('background: var(--v2-ink)');
expect(ruleBody('.v2-harness-picker__dot--not-walked')).toContain('border: 1px dashed var(--v2-text-tertiary)');
});

it('marks only DeepSeek Harness as private-pods-only', () => {
render(<V2HarnessPicker selectedId="claude" onSelect={jest.fn()} />);
const privateRows = HARNESS_WALK_TABLE.filter((row) => row.privatePodsOnly).map((row) => row.id);
expect(privateRows).toEqual(['dsh']);
expect(within(screen.getByTestId('harness-row-dsh')).getByRole('img', { name: 'private pods only' })).toBeInTheDocument();
});

it('marks the selected row and reports the id it picks', () => {
const onSelect = jest.fn();
render(<V2HarnessPicker selectedId="codex" onSelect={onSelect} />);
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('pins the one-command rows to Claude Code and Codex', () => {
expect(HARNESS_WALK_TABLE.filter((row) => row.connects === 'command').map((row) => row.id)).toEqual(['claude', 'codex']);
});

it('uses the connect method from the table for each row', () => {
render(<V2HarnessPicker selectedId="claude" onSelect={jest.fn()} />);
expect(screen.getByTestId('harness-connects-claude')).toHaveAttribute('aria-label', 'one command');
expect(screen.getByTestId('harness-connects-opencode')).toHaveAttribute('aria-label', 'terminal steps');
});
});
14 changes: 14 additions & 0 deletions frontend/src/v2/__tests__/v2-layout-invariants.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5150,3 +5150,17 @@ describe('v2 board on a phone (TASK-576)', () => {
expect(page).toContain('<V2MobileTabs needsYouCount={attention.count ?? 0} />');
});
});

describe('harness picker tracks (TASK-361)', () => {
const v2 = read('../v2.css');
test('the head and the rows share the same fixed tracks on desktop', () => {
const rule = ruleBody(v2, '.v2-harness-picker__head,\n.v2-harness-picker__row');
expect(rule).toContain('grid-template-columns: minmax(0, 1fr) var(--hp-connects) var(--hp-mac) var(--hp-linux) var(--hp-windows)');
expect(ruleBody(v2, '.v2-harness-picker')).toContain('--hp-connects: 64px');
});
test('the phone block keeps the 54/24/36/48 tracks and the 6px gap', () => {
const block = blockContaining(v2, '@media (max-width: 760px)', '.v2-harness-picker__head');
expect(block).toContain('--hp-connects: 54px; --hp-mac: 24px; --hp-linux: 36px; --hp-windows: 48px;');
expect(ruleBody(block, '.v2-harness-picker__head,\n .v2-harness-picker__row')).toContain('gap: 6px');
});
});
87 changes: 87 additions & 0 deletions frontend/src/v2/components/V2HarnessPicker.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
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;
}

const GLYPH_PROPS = { width: 16, height: 16, viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: 1.8, 'aria-hidden': true };

const ClipboardGlyph = () => (
<svg {...GLYPH_PROPS}><rect x="6" y="4" width="12" height="17" rx="2" /><path d="M9 4V3h6v1" /></svg>
);
const TerminalGlyph = () => (
<svg {...GLYPH_PROPS}><rect x="3" y="4" width="18" height="16" rx="2" /><path d="M7 9l3 3-3 3M13 15h4" /></svg>
);
const LockGlyph = () => (
<svg {...GLYPH_PROPS}><rect x="5" y="11" width="14" height="10" rx="2" /><path d="M8 11V7a4 4 0 0 1 8 0v4" /></svg>
);

// One dot per OS. Filled when walked, dashed ring otherwise. The title says why.
const OsDot = ({ state, os, row }: { state: HarnessOsState; os: 'mac' | 'linux' | 'windows'; row: HarnessWalkRow }) => {
const { t } = useTranslation();
const title = t(`agentByo.harnessPicker.osState.${state}`, { os: t(`agentByo.harnessPicker.${os}`) });
return (
<span
className={`v2-harness-picker__dot v2-harness-picker__dot--${state}`}
data-testid={`harness-dot-${row.id}-${os}`}
data-state={state}
title={title}
aria-label={title}
role="img"
/>
);
};

const HarnessRow = ({ row, selected, onSelect }: { row: HarnessWalkRow; selected: boolean; onSelect: (id: string) => void }) => {
const { t } = useTranslation();
const connectsLabel = t(row.connects === 'command' ? 'agentByo.harnessPicker.connectsCommand' : 'agentByo.harnessPicker.connectsTerminal');
return (
<button
type="button"
className={`v2-harness-picker__row${selected ? ' v2-harness-picker__row--selected' : ''}`}
aria-pressed={selected}
data-testid={`harness-row-${row.id}`}
onClick={() => onSelect(row.id)}
>
<span className="v2-harness-picker__name">
<span className="v2-harness-picker__mark" aria-hidden="true">{row.monogram}</span>
<span>{row.label}</span>
{row.privatePodsOnly && (
<span className="v2-harness-picker__lock" role="img" title={t('agentByo.harnessPicker.privateOnly')} aria-label={t('agentByo.harnessPicker.privateOnly')}>
<LockGlyph />
</span>
)}
</span>
<span className="v2-harness-picker__connects" role="img" title={connectsLabel} aria-label={connectsLabel} data-testid={`harness-connects-${row.id}`}>
{row.connects === 'command' ? <ClipboardGlyph /> : <TerminalGlyph />}
</span>
<OsDot state={row.os.mac} os="mac" row={row} />
<OsDot state={row.os.linux} os="linux" row={row} />
<OsDot state={row.os.windows} os="windows" row={row} />
</button>
);
};

// Renders only the rows in HARNESS_WALK_TABLE. Hidden harnesses have no entry to render.
const V2HarnessPicker = ({ selectedId, onSelect }: V2HarnessPickerProps) => {
const { t } = useTranslation();
return (
<div className="v2-harness-picker" role="group" aria-label={t('agentByo.harnessPicker.label')} data-testid="harness-picker">
<div className="v2-harness-picker__head" aria-hidden="true">
<span />
<span>{t('agentByo.harnessPicker.connects')}</span>
<span>{t('agentByo.harnessPicker.mac')}</span>
<span>{t('agentByo.harnessPicker.linux')}</span>
<span>{t('agentByo.harnessPicker.windows')}</span>
</div>
{HARNESS_WALK_TABLE.map((row) => (
<HarnessRow key={row.id} row={row} selected={row.id === selectedId} onSelect={onSelect} />
))}
</div>
);
};

export default V2HarnessPicker;
73 changes: 73 additions & 0 deletions frontend/src/v2/utils/harnessWalkTable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// The harness walk table, as data. Source of truth: ~/agents/shared/d15/harness-walk-table.md
// (ux-lead, 2026-10-27). A harness is listed in the picker only when it has a passing walk on
// record. Change a row here only with a new walk on record, and update the table to match.

/**
* walked: passing walk on record. mcp-only: connects, but no @mention listener on this OS.
* not-walked: no walk yet. not-offered: the harness does not ship for this OS.
*/
export type HarnessOsState = 'walked' | 'mcp-only' | 'not-walked' | 'not-offered';

export interface HarnessWalkRow {
id: string;
label: string;
/** Two-letter stand-in until the vendor's own mark is wired in. */
monogram: string;
/** `command` = one clipboard command; `terminal` = steps in your terminal. */
connects: 'command' | 'terminal';
os: { mac: HarnessOsState; linux: HarnessOsState; windows: HarnessOsState };
/** Private pods only (lock glyph). */
privatePodsOnly: boolean;
walkedOn: string;
}

export const HARNESS_WALK_TABLE: readonly HarnessWalkRow[] = [
{
id: 'claude',
label: 'Claude Code',
monogram: 'CC',
connects: 'command',
os: { mac: 'walked', linux: 'mcp-only', windows: 'mcp-only' },
privatePodsOnly: false,
walkedOn: '2026-10-26',
},
{
id: 'codex',
label: 'Codex',
monogram: 'CX',
connects: 'command',
os: { mac: 'walked', linux: 'not-walked', windows: 'not-walked' },
privatePodsOnly: false,
walkedOn: '2026-10-27',
},
{
id: 'opencode',
label: 'OpenCode',
monogram: 'OC',
connects: 'terminal',
os: { mac: 'walked', linux: 'not-walked', windows: 'not-walked' },
privatePodsOnly: false,
walkedOn: '2026-10-27',
},
{
id: 'qwen',
label: 'Qwen Code',
monogram: 'QW',
connects: 'terminal',
os: { mac: 'walked', linux: 'not-walked', windows: 'not-walked' },
privatePodsOnly: false,
walkedOn: '2026-10-27',
},
{
id: 'dsh',
label: 'DeepSeek Harness',
monogram: 'DS',
connects: 'terminal',
os: { mac: 'walked', linux: 'not-offered', windows: 'not-offered' },
privatePodsOnly: true,
walkedOn: '2026-10-23',
},
];

/** Harnesses with no passing walk on record. They never render in the picker. */
export const HIDDEN_HARNESS_IDS: readonly string[] = ['hermes', 'goose', 'pi', 'gemini', 'grok-build'];
53 changes: 53 additions & 0 deletions frontend/src/v2/v2.css
Original file line number Diff line number Diff line change
Expand Up @@ -13346,3 +13346,56 @@ body.modern-ui.v2-canvas {
.v2-legal__section { margin-bottom: 28px; }
.v2-legal__section h2 { font-size: 18px; font-weight: 600; margin: 0 0 8px; }
.v2-legal__section p { font-size: 15px; line-height: 1.6; color: var(--v2-text-secondary); margin: 0 0 8px; }

/* TASK-361 harness picker: one row per walked harness, OS dots from the walk table. */
/* The head and every row share the same fixed tracks, so each head sits over its dot. */
.v2-harness-picker { --hp-connects: 64px; --hp-mac: 60px; --hp-linux: 60px; --hp-windows: 60px; display: flex; flex-direction: column; gap: 8px; }
.v2-harness-picker__head,
.v2-harness-picker__row {
display: grid;
grid-template-columns: minmax(0, 1fr) var(--hp-connects) var(--hp-mac) var(--hp-linux) var(--hp-windows);
align-items: center;
gap: 16px;
}
.v2-harness-picker__head { padding: 0 16px; font-family: var(--v2-font-mono); font-size: 12px; color: var(--v2-text-tertiary); }
.v2-harness-picker__head > span { white-space: nowrap; text-align: center; }
.v2-harness-picker__row {
width: 100%;
padding: 14px 16px;
background: var(--v2-surface);
border: 1px solid var(--v2-border);
border-radius: var(--v2-radius-sm);
color: var(--v2-text-primary);
font: inherit;
text-align: left;
cursor: pointer;
}
.v2-harness-picker__row:hover { background: var(--v2-surface-hover); }
.v2-harness-picker__row--selected { border-color: var(--v2-accent); }
.v2-harness-picker__name { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; min-width: 0; font-size: 15px; font-weight: 500; }
.v2-harness-picker__mark {
display: inline-grid;
place-items: center;
flex: none;
width: 28px;
height: 28px;
border: 1px solid var(--v2-border);
border-radius: var(--v2-radius-sm);
font-family: var(--v2-font-mono);
font-size: 11px;
}
.v2-harness-picker__lock { display: inline-flex; color: var(--v2-text-tertiary); }
.v2-harness-picker__connects { display: inline-flex; justify-self: center; color: var(--v2-text-secondary); }
.v2-harness-picker__dot { justify-self: center; width: 8px; height: 8px; border-radius: 50%; }
.v2-harness-picker__dot--walked { background: var(--v2-ink); }
.v2-harness-picker__dot--mcp-only,
.v2-harness-picker__dot--not-walked,
.v2-harness-picker__dot--not-offered { border: 1px dashed var(--v2-text-tertiary); }

@media (max-width: 760px) {
.v2-harness-picker { --hp-connects: 54px; --hp-mac: 24px; --hp-linux: 36px; --hp-windows: 48px; }
.v2-harness-picker__head,
.v2-harness-picker__row { gap: 6px; padding-left: 12px; padding-right: 12px; }
.v2-harness-picker__head { font-family: var(--v2-font-mono); font-size: 11px; }
.v2-harness-picker__name { font-size: 14px; }
}
Loading