Skip to content
Merged
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
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
41 changes: 38 additions & 3 deletions src/renderer/src/components/OnboardingWizard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import { PixelButton } from './PixelButton';
import { Icon, type IconName } from './Icon';
import { SpritePortrait } from './SpritePortrait';
import { ProviderLogo } from './ProviderLogo';
import { AGENT_PROVIDER_PRESETS, modelsForProvider, type AgentProvider, type HarnessConfig } from '@/store/config';
import { canReceiveInbox, providerPreset } from '@shared/agentProvider';
import { modelsForProvider, onboardingEngineChoices, type AgentProvider, type HarnessConfig } from '@/store/config';
import { providerPreset } from '@shared/agentProvider';
import {
classifyEngineAvailability, engineAvailabilityBadge, engineAvailabilityMessage, engineBlocksOnboarding
} from '@shared/engineAvailability';
Expand Down Expand Up @@ -413,7 +413,7 @@ export function OnboardingWizard({ onComplete }: OnboardingWizardProps) {
</div>

<div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
{AGENT_PROVIDER_PRESETS.filter((p) => canReceiveInbox(p.id)).map((p) => {
{onboardingEngineChoices().eligible.map((p) => {
const sel = godProvider === p.id;
return (
<label key={p.id} style={{
Expand Down Expand Up @@ -478,6 +478,41 @@ export function OnboardingWizard({ onComplete }: OnboardingWizardProps) {
</label>
);
})}
{/* Engines a WORKER can run but Michael cannot (issue #355): shown
disabled instead of hidden, so "Copilot is missing" reads as the
real constraint — no inbox drain path — not as "unsupported". */}
{onboardingEngineChoices().workersOnly.map((p) => (
<label key={p.id} aria-disabled title={t('onboarding.orchestrator.workersOnlyHint')} style={{
display: 'flex', alignItems: 'center', gap: 10,
padding: '8px 10px',
background: 'var(--cth-paper-100)',
boxShadow: 'inset 0 0 0 1px var(--cth-ink-300)',
cursor: 'not-allowed', opacity: 0.75
}}>
<input type="radio" name="godProvider" value={p.id} checked={false} disabled
style={{ width: 16, height: 16, flexShrink: 0 }} />
<span style={{
width: 22, height: 22, flexShrink: 0, display: 'flex',
alignItems: 'center', justifyContent: 'center', color: 'var(--cth-ink-500)'
}}>
<ProviderLogo provider={p.id} size={18} />
</span>
<span style={{ flex: 1, minWidth: 0 }}>
<span style={{ display: 'block', fontFamily: 'var(--cth-font-display)', fontSize: 11, color: 'var(--cth-ink-500)' }}>
{p.label.toUpperCase()}
</span>
<span style={{ display: 'block', fontSize: 11, color: 'var(--cth-ink-500)' }}>
{t('onboarding.orchestrator.workersOnlyHint')}
</span>
</span>
<span style={{
fontSize: 10, padding: '1px 5px', lineHeight: '16px',
background: 'var(--cth-paper-100)', color: 'var(--cth-ink-500)',
boxShadow: 'inset 0 0 0 1px var(--cth-ink-300)',
fontFamily: 'var(--cth-font-display)', flexShrink: 0
}}>{t('onboarding.orchestrator.workersOnly')}</span>
</label>
))}
</div>
{engineBlocked && (
<div style={{
Expand Down
2 changes: 2 additions & 0 deletions src/renderer/src/i18n/locales/ar.json
Original file line number Diff line number Diff line change
Expand Up @@ -659,6 +659,8 @@
"cliAgentPlain": "<strong>وكيل الـCLI</strong> مساعد برمجة ذكي يعمل على حاسوبك — وأشهرها Claude Code (من Anthropic) وCodex (من OpenAI) وAntigravity (من Google Gemini). و<strong>نسختك</strong> هي الوكيل الدائم الذي يُدير مكتبك كله. ننصح بـClaude Code على Opus 4.8 (1M). ويمكنك إضافة الباقي أو التبديل إليها لاحقًا.",
"cliAgent": "كل خيار هو <strong>محرّك CLI</strong> (Claude Code أو Codex أو Antigravity/Gemini أو وسيط محلي مثل Qwen). المحرّكات المعلَّمة بـINSTALLED موجودة على هذا الجهاز؛ وINSTALLS ON FIRST RUN يعني أن التطبيق يُعِدّها عند أول تشغيل لـ{{godName}}. و<strong>نسختك</strong> ({{godName}}) هي المحرّك الذي ينسّق الخلية كلها. الموصى به: Claude Code · Opus 4.8 · 1M. ويمكن ربط مزوّدين آخرين لكل وكيل لاحقًا.",
"recommended": "موصى به",
"workersOnly": "للعمّال فقط",
"workersOnlyHint": "لا يستطيع تصريف بريد الخليّة بعد، لذا لا يمكنه تشغيل نسختك — لكنه يبقى متاحًا عند تعيين العمّال.",
"model": "النموذج",
"modelNote": "هذا يضبط محرّك {{godName}} فقط. ويمكنك تشغيل مزوّدين آخرين لكل وكيل لاحقًا."
},
Expand Down
2 changes: 2 additions & 0 deletions src/renderer/src/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -659,6 +659,8 @@
"cliAgentPlain": "A <strong>CLI agent</strong> is an AI coding assistant that runs on your computer — popular ones are Claude Code (Anthropic), Codex (OpenAI) and Antigravity (Google Gemini). <strong>Your clone</strong> is the always-on one that runs your whole office. We recommend Claude Code on Opus 4.8 (1M). You can add or switch the others later.",
"cliAgent": "Each option is a <strong>CLI engine</strong> (Claude Code, Codex, Antigravity/Gemini, or a local proxy like Qwen). Engines marked INSTALLED are already on this machine; INSTALLS ON FIRST RUN means the app sets it up when {{godName}} first starts. <strong>Your clone</strong> ({{godName}}) is the engine that orchestrates the whole hive. Recommended: Claude Code · Opus 4.8 · 1M. Other providers can be wired per agent later.",
"recommended": "RECOMMENDED",
"workersOnly": "WORKERS ONLY",
"workersOnlyHint": "Can't drain hive mail yet, so it can't run your clone — still available when hiring workers.",
"model": "Model",
"modelNote": "This only sets {{godName}}'s engine. You can run other providers per agent later."
},
Expand Down
2 changes: 2 additions & 0 deletions src/renderer/src/i18n/locales/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -659,6 +659,8 @@
"cliAgentPlain": "一个<strong>CLI 智能体</strong>是运行在你电脑上的 AI 编程助手 —— 常见的有 Claude Code(Anthropic)、Codex(OpenAI)和 Antigravity(Google Gemini)。<strong>你的克隆体</strong>是常驻运行、打理整个办公室的那一个。我们推荐使用 Opus 4.8(1M)的 Claude Code。其他引擎之后可以随时添加或切换。",
"cliAgent": "每个选项都是一个<strong>CLI 引擎</strong>(Claude Code、Codex、Antigravity/Gemini,或本地代理如 Qwen)。标记为 INSTALLED 的引擎已在本机就绪;INSTALLS ON FIRST RUN 表示应用会在 {{godName}} 首次启动时进行安装。<strong>你的克隆体</strong>({{godName}})是统筹整个 hive 的引擎。推荐:Claude Code · Opus 4.8 · 1M。其他提供方之后可按智能体逐一接入。",
"recommended": "推荐",
"workersOnly": "仅限雇员",
"workersOnlyHint": "暂时无法接收蜂巢邮件,因此不能运行你的分身——但雇佣工作智能体时仍然可用。",
"model": "模型",
"modelNote": "这只设置 {{godName}} 的引擎。之后你可以在每个智能体上运行其他提供商。"
},
Expand Down
22 changes: 21 additions & 1 deletion src/renderer/src/store/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import {
providerPreset,
inferAgentProvider,
isClaudeProvider,
type AgentProvider
type AgentProvider,
type AgentProviderPreset
} from '@shared/agentProvider';
import type {
ContextTriggerConfig,
Expand Down Expand Up @@ -305,6 +306,25 @@ export function modelProvidersForAgent(isGod = false) {
);
}

/** The onboarding engine step's two groups (issue #355). Hiding inbox-less
* engines there read as "Copilot isn't supported at all", when the truth is
* narrower: a print-mode / bridge-less CLI can be HIRED as a worker but cannot
* run Michael, because the orchestrator must drain hive mail. So the step now
* shows those engines too, as disabled workers-only rows — same god-eligible
* set as `modelProvidersForAgent(true)` for the selectable group, and every
* other preset except `custom` (bring-your-own command, not an engine) in the
* disabled group. */
export function onboardingEngineChoices(): {
eligible: AgentProviderPreset[];
workersOnly: AgentProviderPreset[];
} {
const eligible = modelProvidersForAgent(true);
const workersOnly = AGENT_PROVIDER_PRESETS.filter(
(preset) => preset.id !== 'custom' && !eligible.includes(preset)
);
return { eligible, workersOnly };
}

/** Native <select> values must carry both provider and model because each
* provider has its own "default" option and model namespace. */
export function encodeProviderModel(provider: AgentProvider, model?: string): string {
Expand Down
19 changes: 18 additions & 1 deletion test/provider-config.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ const {
decodeProviderModel,
encodeProviderModel,
modelProvidersForAgent,
modelsForProvider
modelsForProvider,
onboardingEngineChoices
} = loadTs('src/renderer/src/store/config.ts');

const autoConfig = { defaultCommand: 'claude', autoMode: true };
Expand Down Expand Up @@ -122,6 +123,22 @@ test('Command Center model choices round-trip provider and model', () => {
assert.equal(decodeProviderModel('unknown:model'), null);
});

test('onboarding lists every engine — orchestrator-capable first, workers-only after, custom never', () => {
// Issue #355: hiding Copilot from the onboarding engine step read as "not
// supported at all". The step now SHOWS inbox-less engines as disabled
// workers-only rows instead of omitting them, so the split must be exact:
// selectable = the god-eligible set, workersOnly = everything a worker can
// run but Michael cannot (custom stays hidden — it is not a preset engine).
const { eligible, workersOnly } = onboardingEngineChoices();
assert.deepEqual(
eligible.map((preset) => preset.id),
modelProvidersForAgent(true).map((preset) => preset.id),
'selectable rows are exactly the god-eligible engines, same order'
);
assert.deepEqual(workersOnly.map((preset) => preset.id), ['kimi', 'copilot']);
assert.ok(!eligible.concat(workersOnly).some((preset) => preset.id === 'custom'));
});

test('God only sees providers that can drain hive inbox messages', () => {
// God-eligible = supportsModel && canReceiveInbox: kimi and copilot are
// excluded (no inbox drain path), custom is excluded (no model picker).
Expand Down
Loading