mirror of
https://github.com/apache/maka.git
synced 2026-10-28 17:04:39 +08:00
fix(ui): share Agent scope between selection and recovery
Generated-by: Codex
This commit is contained in:
1 parent
f231aeb0fb
commit
68a62a5300
3 files changed
+33
-21
No files matched your search
@@ -58,22 +58,28 @@ function actions(dom: ReturnType<typeof installTranscriptDom>) {
|
||||
};
|
||||
}
|
||||
|
||||
test('unselected Agents retain sign-in and discovery retry without committing a selection', async () => {
|
||||
test('new tasks can recover another Agent without changing their selection', async () => {
|
||||
const dom = installTranscriptDom();
|
||||
const setup: string[] = [];
|
||||
let retries = 0;
|
||||
let selectionWrites = 0;
|
||||
try {
|
||||
for (const readiness of ['authentication_required', 'unavailable'] as const) {
|
||||
await dom.render(<LocaleProvider locale="en"><ComposerModelPicker label="Native" choices={choices}
|
||||
executor={{ catalog: [{ ...catalog[0]!, readiness }],
|
||||
onSelect() { assert.fail('recovery must not select an unavailable Agent'); },
|
||||
onSetup: id => { setup.push(id!); }, onRetry: () => { retries++; }, onNewTask() {} }}
|
||||
/></LocaleProvider>);
|
||||
const { click } = actions(dom);
|
||||
await click([...dom.document.querySelectorAll('button')].find(el => el.textContent === (readiness === 'unavailable' ? 'Retry' : 'Go to sign in')));
|
||||
for (const selected of [false, true]) {
|
||||
await dom.render(<LocaleProvider locale="en"><ComposerModelPicker label="Native" choices={choices}
|
||||
executor={{ catalog: [{ ...catalog[0]!, readiness }, { ...catalog[0]!, id: 'ready', displayName: 'Ready Agent' }],
|
||||
selection: selected ? { executorId: 'ready', configuration: { model: 'opaque-high' } } : undefined,
|
||||
onSelect() { selectionWrites++; },
|
||||
onSetup: id => { setup.push(id!); }, onRetry: () => { retries++; }, onNewTask() {} }}
|
||||
/></LocaleProvider>);
|
||||
const { click, trigger } = actions(dom);
|
||||
await click([...dom.document.querySelectorAll('button')].find(el => el.textContent === (readiness === 'unavailable' ? 'Retry' : 'Go to sign in')));
|
||||
assert.equal(trigger()?.textContent, selected ? 'Ready Agent · Family' : 'Native');
|
||||
}
|
||||
}
|
||||
assert.deepEqual(setup, ['agent']);
|
||||
assert.equal(retries, 1);
|
||||
assert.deepEqual(setup, ['agent', 'agent']);
|
||||
assert.equal(retries, 2);
|
||||
assert.equal(selectionWrites, 0);
|
||||
} finally { await dom.cleanup(); }
|
||||
});
|
||||
|
||||
@@ -266,12 +272,13 @@ test('fixed external sessions show only their executor and failed writes retain
|
||||
const dom = installTranscriptDom();
|
||||
try {
|
||||
await dom.render(<LocaleProvider locale="en"><ComposerModelPicker label="Native" choices={choices}
|
||||
executor={{ catalog, selection: { executorId: 'agent', configuration: { model: 'opaque-high' } }, fixed: true,
|
||||
executor={{ catalog: [...catalog, { ...catalog[0]!, id: 'other', displayName: 'Other Agent', readiness: 'authentication_required' }], selection: { executorId: 'agent', configuration: { model: 'opaque-high' } }, fixed: true,
|
||||
onSelect: async () => { throw Error('offline'); }, onSetup() {}, onRetry() {}, onNewTask() {} }}
|
||||
/></LocaleProvider>);
|
||||
const { click, option, trigger } = actions(dom);
|
||||
await click(trigger());
|
||||
assert.equal(dom.document.querySelector('[role="group"][aria-label="OpenAI"]'), null);
|
||||
assert.deepEqual([...dom.document.querySelectorAll('[role="group"]')].map(element => element.getAttribute('aria-label')), ['Agent']);
|
||||
assert.equal([...dom.document.querySelectorAll('button')].some(element => element.textContent === 'Go to sign in'), false);
|
||||
await click(option('Other'));
|
||||
assert.equal(trigger()?.textContent, 'Family');
|
||||
assert.match(dom.document.querySelector('[role="status"]')?.textContent ?? '', /failed/);
|
||||
|
||||
@@ -220,9 +220,10 @@ export function ComposerModelPicker(props: {
|
||||
const copy = executorCopy(locale);
|
||||
const executor = props.executor;
|
||||
const contribution = props.contribution;
|
||||
const catalog = executor?.catalog.filter(entry => entry.id !== contribution?.id && (!executor.fixed || entry.id === executor.selection?.executorId)) ?? [];
|
||||
const contributed = contribution?.id === props.executorTarget?.executorId ? contribution : undefined;
|
||||
const contributedModel = contributed?.models.find(model => model.id === props.executorTarget?.model);
|
||||
const selected = !contributed ? executor?.catalog.find(entry => entry.id === executor.selection?.executorId) : undefined;
|
||||
const selected = catalog.find(entry => entry.id === executor?.selection?.executorId);
|
||||
const selectedModel = executor?.selection?.configuration.model ?? selected?.currentModel;
|
||||
const group = selected && executorModelGroup(selected, selectedModel);
|
||||
const modelLabel = contributed ? contributedModel?.label ?? props.executorTarget?.model ?? copy.default : executor?.selection
|
||||
@@ -266,9 +267,7 @@ export function ComposerModelPicker(props: {
|
||||
return { value, label: model.label, icon: providerMarkIcon(model.providerType, props.renderProviderMark) };
|
||||
}) });
|
||||
}
|
||||
for (const entry of executor?.catalog ?? []) {
|
||||
if (entry.id === contribution?.id) continue;
|
||||
if (executor?.fixed && entry.id !== executor.selection?.executorId) continue;
|
||||
for (const entry of catalog) {
|
||||
options.push({
|
||||
type: 'section',
|
||||
title: entry.displayName,
|
||||
@@ -308,9 +307,7 @@ export function ComposerModelPicker(props: {
|
||||
executor?.onSelect({ executorId: executor.selection!.executorId, configuration: executor.fixed || (configuration.model !== undefined && configuration.model !== selectedModel)
|
||||
? configuration : { ...executor.selection!.configuration, ...configuration } });
|
||||
const mode = executor?.selection?.configuration.mode ?? selected?.currentMode;
|
||||
const recoveryEntries = executor?.selection
|
||||
? selected && selected.readiness !== 'ready' ? [selected] : []
|
||||
: executor?.catalog.filter(entry => entry.readiness === 'authentication_required' || entry.readiness === 'unavailable') ?? [];
|
||||
const recoveryEntries = catalog.filter(entry => entry.readiness !== 'ready');
|
||||
return <>
|
||||
{/* Keep portaled search clicks from focusing the composer editor. */}
|
||||
<Stack direction="horizontal" className="maka-model-picker-root"
|
||||
|
||||
@@ -173,14 +173,22 @@ export const CodexAndAcp: Story = {
|
||||
},
|
||||
};
|
||||
|
||||
// Real path: a new task discovers an ACP Agent that requires sign-in, before any selection.
|
||||
// Real path: a new task selects a ready ACP Agent while another configured Agent requires sign-in.
|
||||
export const UnselectedUnavailableAgentNarrow: Story = {
|
||||
render: () => <PickerFrame catalog={[{ ...CATALOG[0]!, readiness: 'authentication_required' }]} />,
|
||||
render: () => <PickerFrame catalog={[CATALOG[0]!, {
|
||||
id: 'claude', displayName: 'Claude Agent', readiness: 'authentication_required',
|
||||
models: [], supportsModelChange: false, supportsAttachments: false,
|
||||
}]} />,
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
await expect(canvas.getByRole('button', { name: /Go to sign in|前往登录/ })).toBeVisible();
|
||||
const label = canvasElement.querySelector<HTMLElement>('.maka-composer-model-label')!;
|
||||
await waitFor(() => expect(label.scrollWidth).toBeLessThanOrEqual(label.clientWidth + 1));
|
||||
const page = await openModels(canvasElement);
|
||||
await userEvent.click(page.getByRole('option', { name: /Gemini 3.8 Flash/ }));
|
||||
await waitFor(() => expect(canvasElement.querySelector('.maka-model-switcher-trigger')).toHaveTextContent('Antigravity · Gemini 3.8 Flash'));
|
||||
await expect(canvas.getByRole('button', { name: /Go to sign in|前往登录/ })).toBeVisible();
|
||||
await expect(canvasElement.querySelector('.maka-executor-notice')).toHaveTextContent('Claude Agent');
|
||||
await openModels(canvasElement);
|
||||
},
|
||||
};
|
||||
|
||||
Reference in new issue
Block a user