Improve onboarding defaults and issue goal fallback
This commit is contained in:
@@ -17,9 +17,13 @@ import {
|
||||
} from "@/components/ui/popover";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { cn } from "../lib/utils";
|
||||
import { extractModelName, extractProviderIdWithFallback } from "../lib/model-utils";
|
||||
import {
|
||||
extractModelName,
|
||||
extractProviderIdWithFallback
|
||||
} from "../lib/model-utils";
|
||||
import { getUIAdapter } from "../adapters";
|
||||
import { defaultCreateValues } from "./agent-config-defaults";
|
||||
import { parseOnboardingGoalInput } from "../lib/onboarding-goal";
|
||||
import {
|
||||
DEFAULT_CODEX_LOCAL_BYPASS_APPROVALS_AND_SANDBOX,
|
||||
DEFAULT_CODEX_LOCAL_MODEL
|
||||
@@ -61,11 +65,13 @@ type AdapterType =
|
||||
| "http"
|
||||
| "openclaw_gateway";
|
||||
|
||||
const DEFAULT_TASK_DESCRIPTION = `Setup yourself as the CEO. Use the ceo persona found here: [https://github.com/paperclipai/companies/blob/main/default/ceo/AGENTS.md](https://github.com/paperclipai/companies/blob/main/default/ceo/AGENTS.md)
|
||||
const DEFAULT_TASK_DESCRIPTION = `Setup yourself as the CEO. Use the ceo persona found here:
|
||||
|
||||
Ensure you have a folder agents/ceo and then download this AGENTS.md as well as the sibling HEARTBEAT.md, SOUL.md, and TOOLS.md. and set that AGENTS.md as the path to your agents instruction file
|
||||
https://github.com/paperclipai/companies/blob/main/default/ceo/AGENTS.md
|
||||
|
||||
And after you've finished that, hire yourself a Founding Engineer agent`;
|
||||
Ensure you have a folder agents/ceo and then download this AGENTS.md, and sibling HEARTBEAT.md, SOULD.md, and TOOLS.md. and set that AGENTS.md as the path to your agents instruction file
|
||||
|
||||
After that, hire yourself a Founding Engineer agent and then plan the roadmap and tasks for your new company.`;
|
||||
|
||||
export function OnboardingWizard() {
|
||||
const { onboardingOpen, onboardingOptions, closeOnboarding } = useDialog();
|
||||
@@ -159,12 +165,11 @@ export function OnboardingWizard() {
|
||||
data: adapterModels,
|
||||
error: adapterModelsError,
|
||||
isLoading: adapterModelsLoading,
|
||||
isFetching: adapterModelsFetching,
|
||||
isFetching: adapterModelsFetching
|
||||
} = useQuery({
|
||||
queryKey:
|
||||
createdCompanyId
|
||||
? queryKeys.agents.adapterModels(createdCompanyId, adapterType)
|
||||
: ["agents", "none", "adapter-models", adapterType],
|
||||
queryKey: createdCompanyId
|
||||
? queryKeys.agents.adapterModels(createdCompanyId, adapterType)
|
||||
: ["agents", "none", "adapter-models", adapterType],
|
||||
queryFn: () => agentsApi.adapterModels(createdCompanyId!, adapterType),
|
||||
enabled: Boolean(createdCompanyId) && onboardingOpen && step === 2
|
||||
});
|
||||
@@ -181,10 +186,10 @@ export function OnboardingWizard() {
|
||||
: adapterType === "gemini_local"
|
||||
? "gemini"
|
||||
: adapterType === "cursor"
|
||||
? "agent"
|
||||
: adapterType === "opencode_local"
|
||||
? "opencode"
|
||||
: "claude");
|
||||
? "agent"
|
||||
: adapterType === "opencode_local"
|
||||
? "opencode"
|
||||
: "claude");
|
||||
|
||||
useEffect(() => {
|
||||
if (step !== 2) return;
|
||||
@@ -219,8 +224,8 @@ export function OnboardingWizard() {
|
||||
return [
|
||||
{
|
||||
provider: "models",
|
||||
entries: [...filteredModels].sort((a, b) => a.id.localeCompare(b.id)),
|
||||
},
|
||||
entries: [...filteredModels].sort((a, b) => a.id.localeCompare(b.id))
|
||||
}
|
||||
];
|
||||
}
|
||||
const groups = new Map<string, Array<{ id: string; label: string }>>();
|
||||
@@ -234,7 +239,7 @@ export function OnboardingWizard() {
|
||||
.sort(([a], [b]) => a.localeCompare(b))
|
||||
.map(([provider, entries]) => ({
|
||||
provider,
|
||||
entries: [...entries].sort((a, b) => a.id.localeCompare(b.id)),
|
||||
entries: [...entries].sort((a, b) => a.id.localeCompare(b.id))
|
||||
}));
|
||||
}, [filteredModels, adapterType]);
|
||||
|
||||
@@ -281,7 +286,7 @@ export function OnboardingWizard() {
|
||||
: adapterType === "gemini_local"
|
||||
? model || DEFAULT_GEMINI_LOCAL_MODEL
|
||||
: adapterType === "cursor"
|
||||
? model || DEFAULT_CURSOR_LOCAL_MODEL
|
||||
? model || DEFAULT_CURSOR_LOCAL_MODEL
|
||||
: model,
|
||||
command,
|
||||
args,
|
||||
@@ -347,8 +352,12 @@ export function OnboardingWizard() {
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.companies.all });
|
||||
|
||||
if (companyGoal.trim()) {
|
||||
const parsedGoal = parseOnboardingGoalInput(companyGoal);
|
||||
await goalsApi.create(company.id, {
|
||||
title: companyGoal.trim(),
|
||||
title: parsedGoal.title,
|
||||
...(parsedGoal.description
|
||||
? { description: parsedGoal.description }
|
||||
: {}),
|
||||
level: "company",
|
||||
status: "active"
|
||||
});
|
||||
@@ -373,19 +382,23 @@ export function OnboardingWizard() {
|
||||
if (adapterType === "opencode_local") {
|
||||
const selectedModelId = model.trim();
|
||||
if (!selectedModelId) {
|
||||
setError("OpenCode requires an explicit model in provider/model format.");
|
||||
setError(
|
||||
"OpenCode requires an explicit model in provider/model format."
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (adapterModelsError) {
|
||||
setError(
|
||||
adapterModelsError instanceof Error
|
||||
? adapterModelsError.message
|
||||
: "Failed to load OpenCode models.",
|
||||
: "Failed to load OpenCode models."
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (adapterModelsLoading || adapterModelsFetching) {
|
||||
setError("OpenCode models are still loading. Please wait and try again.");
|
||||
setError(
|
||||
"OpenCode models are still loading. Please wait and try again."
|
||||
);
|
||||
return;
|
||||
}
|
||||
const discoveredModels = adapterModels ?? [];
|
||||
@@ -393,7 +406,7 @@ export function OnboardingWizard() {
|
||||
setError(
|
||||
discoveredModels.length === 0
|
||||
? "No OpenCode models discovered. Run `opencode models` and authenticate providers."
|
||||
: `Configured OpenCode model is unavailable: ${selectedModelId}`,
|
||||
: `Configured OpenCode model is unavailable: ${selectedModelId}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -554,19 +567,23 @@ export function OnboardingWizard() {
|
||||
</button>
|
||||
|
||||
{/* Left half — form */}
|
||||
<div className={cn(
|
||||
"w-full flex flex-col overflow-y-auto transition-[width] duration-500 ease-in-out",
|
||||
step === 1 ? "md:w-1/2" : "md:w-full"
|
||||
)}>
|
||||
<div
|
||||
className={cn(
|
||||
"w-full flex flex-col overflow-y-auto transition-[width] duration-500 ease-in-out",
|
||||
step === 1 ? "md:w-1/2" : "md:w-full"
|
||||
)}
|
||||
>
|
||||
<div className="w-full max-w-md mx-auto my-auto px-8 py-12 shrink-0">
|
||||
{/* Progress tabs */}
|
||||
<div className="flex items-center gap-0 mb-8 border-b border-border">
|
||||
{([
|
||||
{ step: 1 as Step, label: "Company", icon: Building2 },
|
||||
{ step: 2 as Step, label: "Agent", icon: Bot },
|
||||
{ step: 3 as Step, label: "Task", icon: ListTodo },
|
||||
{ step: 4 as Step, label: "Launch", icon: Rocket },
|
||||
] as const).map(({ step: s, label, icon: Icon }) => (
|
||||
{(
|
||||
[
|
||||
{ step: 1 as Step, label: "Company", icon: Building2 },
|
||||
{ step: 2 as Step, label: "Agent", icon: Bot },
|
||||
{ step: 3 as Step, label: "Task", icon: ListTodo },
|
||||
{ step: 4 as Step, label: "Launch", icon: Rocket }
|
||||
] as const
|
||||
).map(({ step: s, label, icon: Icon }) => (
|
||||
<button
|
||||
key={s}
|
||||
type="button"
|
||||
@@ -599,7 +616,14 @@ export function OnboardingWizard() {
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-3 group">
|
||||
<label className={cn("text-xs mb-1 block transition-colors", companyName.trim() ? "text-foreground" : "text-muted-foreground group-focus-within:text-foreground")}>
|
||||
<label
|
||||
className={cn(
|
||||
"text-xs mb-1 block transition-colors",
|
||||
companyName.trim()
|
||||
? "text-foreground"
|
||||
: "text-muted-foreground group-focus-within:text-foreground"
|
||||
)}
|
||||
>
|
||||
Company name
|
||||
</label>
|
||||
<input
|
||||
@@ -611,7 +635,14 @@ export function OnboardingWizard() {
|
||||
/>
|
||||
</div>
|
||||
<div className="group">
|
||||
<label className={cn("text-xs mb-1 block transition-colors", companyGoal.trim() ? "text-foreground" : "text-muted-foreground group-focus-within:text-foreground")}>
|
||||
<label
|
||||
className={cn(
|
||||
"text-xs mb-1 block transition-colors",
|
||||
companyGoal.trim()
|
||||
? "text-foreground"
|
||||
: "text-muted-foreground group-focus-within:text-foreground"
|
||||
)}
|
||||
>
|
||||
Mission / goal (optional)
|
||||
</label>
|
||||
<textarea
|
||||
@@ -707,7 +738,12 @@ export function OnboardingWizard() {
|
||||
className="flex items-center gap-1.5 mt-3 text-xs text-muted-foreground hover:text-foreground transition-colors"
|
||||
onClick={() => setShowMoreAdapters((v) => !v)}
|
||||
>
|
||||
<ChevronDown className={cn("h-3 w-3 transition-transform", showMoreAdapters ? "rotate-0" : "-rotate-90")} />
|
||||
<ChevronDown
|
||||
className={cn(
|
||||
"h-3 w-3 transition-transform",
|
||||
showMoreAdapters ? "rotate-0" : "-rotate-90"
|
||||
)}
|
||||
/>
|
||||
More Agent Adapter Types
|
||||
</button>
|
||||
|
||||
@@ -755,8 +791,8 @@ export function OnboardingWizard() {
|
||||
opt.comingSoon
|
||||
? "border-border opacity-40 cursor-not-allowed"
|
||||
: adapterType === opt.value
|
||||
? "border-foreground bg-accent"
|
||||
: "border-border hover:bg-accent/50"
|
||||
? "border-foreground bg-accent"
|
||||
: "border-border hover:bg-accent/50"
|
||||
)}
|
||||
onClick={() => {
|
||||
if (opt.comingSoon) return;
|
||||
@@ -783,8 +819,8 @@ export function OnboardingWizard() {
|
||||
<span className="font-medium">{opt.label}</span>
|
||||
<span className="text-muted-foreground text-[10px]">
|
||||
{opt.comingSoon
|
||||
? (opt as { disabledLabel?: string }).disabledLabel ??
|
||||
"Coming soon"
|
||||
? (opt as { disabledLabel?: string })
|
||||
.disabledLabel ?? "Coming soon"
|
||||
: opt.desc}
|
||||
</span>
|
||||
</button>
|
||||
@@ -869,12 +905,15 @@ export function OnboardingWizard() {
|
||||
setModelOpen(false);
|
||||
}}
|
||||
>
|
||||
Default
|
||||
</button>
|
||||
Default
|
||||
</button>
|
||||
)}
|
||||
<div className="max-h-[240px] overflow-y-auto">
|
||||
{groupedModels.map((group) => (
|
||||
<div key={group.provider} className="mb-1 last:mb-0">
|
||||
<div
|
||||
key={group.provider}
|
||||
className="mb-1 last:mb-0"
|
||||
>
|
||||
{adapterType === "opencode_local" && (
|
||||
<div className="px-2 py-1 text-[10px] uppercase tracking-wide text-muted-foreground">
|
||||
{group.provider} ({group.entries.length})
|
||||
@@ -892,8 +931,13 @@ export function OnboardingWizard() {
|
||||
setModelOpen(false);
|
||||
}}
|
||||
>
|
||||
<span className="block w-full text-left truncate" title={m.id}>
|
||||
{adapterType === "opencode_local" ? extractModelName(m.id) : m.label}
|
||||
<span
|
||||
className="block w-full text-left truncate"
|
||||
title={m.id}
|
||||
>
|
||||
{adapterType === "opencode_local"
|
||||
? extractModelName(m.id)
|
||||
: m.label}
|
||||
</span>
|
||||
</button>
|
||||
))}
|
||||
@@ -940,7 +984,8 @@ export function OnboardingWizard() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{adapterEnvResult && adapterEnvResult.status === "pass" ? (
|
||||
{adapterEnvResult &&
|
||||
adapterEnvResult.status === "pass" ? (
|
||||
<div className="flex items-center gap-2 rounded-md border border-green-300 dark:border-green-500/40 bg-green-50 dark:bg-green-500/10 px-3 py-2 text-xs text-green-700 dark:text-green-300 animate-in fade-in slide-in-from-bottom-1 duration-300">
|
||||
<Check className="h-3.5 w-3.5 shrink-0" />
|
||||
<span className="font-medium">Passed</span>
|
||||
@@ -952,17 +997,23 @@ export function OnboardingWizard() {
|
||||
{shouldSuggestUnsetAnthropicApiKey && (
|
||||
<div className="rounded-md border border-amber-300/60 bg-amber-50/40 px-2.5 py-2 space-y-2">
|
||||
<p className="text-[11px] text-amber-900/90 leading-relaxed">
|
||||
Claude failed while <span className="font-mono">ANTHROPIC_API_KEY</span> is set.
|
||||
You can clear it in this CEO adapter config and retry the probe.
|
||||
Claude failed while{" "}
|
||||
<span className="font-mono">ANTHROPIC_API_KEY</span>{" "}
|
||||
is set. You can clear it in this CEO adapter config
|
||||
and retry the probe.
|
||||
</p>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className="h-7 px-2.5 text-xs"
|
||||
disabled={adapterEnvLoading || unsetAnthropicLoading}
|
||||
disabled={
|
||||
adapterEnvLoading || unsetAnthropicLoading
|
||||
}
|
||||
onClick={() => void handleUnsetAnthropicApiKey()}
|
||||
>
|
||||
{unsetAnthropicLoading ? "Retrying..." : "Unset ANTHROPIC_API_KEY"}
|
||||
{unsetAnthropicLoading
|
||||
? "Retrying..."
|
||||
: "Unset ANTHROPIC_API_KEY"}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
@@ -1001,24 +1052,43 @@ export function OnboardingWizard() {
|
||||
in
|
||||
env or run{" "}
|
||||
<span className="font-mono">
|
||||
{adapterType === "cursor"
|
||||
? "agent login"
|
||||
: adapterType === "codex_local"
|
||||
Respond with hello.
|
||||
</span>
|
||||
</p>
|
||||
{adapterType === "cursor" ||
|
||||
adapterType === "codex_local" ||
|
||||
adapterType === "gemini_local" ||
|
||||
adapterType === "opencode_local" ? (
|
||||
<p className="text-muted-foreground">
|
||||
If auth fails, set{" "}
|
||||
<span className="font-mono">
|
||||
{adapterType === "cursor"
|
||||
? "CURSOR_API_KEY"
|
||||
: adapterType === "gemini_local"
|
||||
? "GEMINI_API_KEY"
|
||||
: "OPENAI_API_KEY"}
|
||||
</span>{" "}
|
||||
in env or run{" "}
|
||||
<span className="font-mono">
|
||||
{adapterType === "cursor"
|
||||
? "agent login"
|
||||
: adapterType === "codex_local"
|
||||
? "codex login"
|
||||
: adapterType === "gemini_local"
|
||||
? "gemini auth"
|
||||
: "opencode auth login"}
|
||||
</span>.
|
||||
</p>
|
||||
) : (
|
||||
<p className="text-muted-foreground">
|
||||
If login is required, run{" "}
|
||||
<span className="font-mono">claude login</span> and
|
||||
retry.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</span>
|
||||
.
|
||||
</p>
|
||||
) : (
|
||||
<p className="text-muted-foreground">
|
||||
If login is required, run{" "}
|
||||
<span className="font-mono">claude login</span>{" "}
|
||||
and retry.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1049,14 +1119,21 @@ export function OnboardingWizard() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{(adapterType === "http" || adapterType === "openclaw_gateway") && (
|
||||
{(adapterType === "http" ||
|
||||
adapterType === "openclaw_gateway") && (
|
||||
<div>
|
||||
<label className="text-xs text-muted-foreground mb-1 block">
|
||||
{adapterType === "openclaw_gateway" ? "Gateway URL" : "Webhook URL"}
|
||||
{adapterType === "openclaw_gateway"
|
||||
? "Gateway URL"
|
||||
: "Webhook URL"}
|
||||
</label>
|
||||
<input
|
||||
className="w-full rounded-md border border-border bg-transparent px-3 py-2 text-sm font-mono outline-none focus:ring-1 focus:ring-ring placeholder:text-muted-foreground/50"
|
||||
placeholder={adapterType === "openclaw_gateway" ? "ws://127.0.0.1:18789" : "https://..."}
|
||||
placeholder={
|
||||
adapterType === "openclaw_gateway"
|
||||
? "ws://127.0.0.1:18789"
|
||||
: "https://..."
|
||||
}
|
||||
value={url}
|
||||
onChange={(e) => setUrl(e.target.value)}
|
||||
/>
|
||||
@@ -1240,10 +1317,12 @@ export function OnboardingWizard() {
|
||||
</div>
|
||||
|
||||
{/* Right half — ASCII art (hidden on mobile) */}
|
||||
<div className={cn(
|
||||
"hidden md:block overflow-hidden bg-[#1d1d1d] transition-[width,opacity] duration-500 ease-in-out",
|
||||
step === 1 ? "w-1/2 opacity-100" : "w-0 opacity-0"
|
||||
)}>
|
||||
<div
|
||||
className={cn(
|
||||
"hidden md:block overflow-hidden bg-[#1d1d1d] transition-[width,opacity] duration-500 ease-in-out",
|
||||
step === 1 ? "w-1/2 opacity-100" : "w-0 opacity-0"
|
||||
)}
|
||||
>
|
||||
<AsciiArtAnimation />
|
||||
</div>
|
||||
</div>
|
||||
@@ -1261,14 +1340,14 @@ function AdapterEnvironmentResult({
|
||||
result.status === "pass"
|
||||
? "Passed"
|
||||
: result.status === "warn"
|
||||
? "Warnings"
|
||||
: "Failed";
|
||||
? "Warnings"
|
||||
: "Failed";
|
||||
const statusClass =
|
||||
result.status === "pass"
|
||||
? "text-green-700 dark:text-green-300 border-green-300 dark:border-green-500/40 bg-green-50 dark:bg-green-500/10"
|
||||
: result.status === "warn"
|
||||
? "text-amber-700 dark:text-amber-300 border-amber-300 dark:border-amber-500/40 bg-amber-50 dark:bg-amber-500/10"
|
||||
: "text-red-700 dark:text-red-300 border-red-300 dark:border-red-500/40 bg-red-50 dark:bg-red-500/10";
|
||||
? "text-amber-700 dark:text-amber-300 border-amber-300 dark:border-amber-500/40 bg-amber-50 dark:bg-amber-500/10"
|
||||
: "text-red-700 dark:text-red-300 border-red-300 dark:border-red-500/40 bg-red-50 dark:bg-red-500/10";
|
||||
|
||||
return (
|
||||
<div className={`rounded-md border px-2.5 py-2 text-[11px] ${statusClass}`}>
|
||||
|
||||
Reference in New Issue
Block a user