Files
PaperClipAI/ui/src/components/RuntimeTestCard.tsx
T
DottaandPaperclip ebaeba40ee feat: simplify agent onboarding and configuration (#13011)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Operators create agents and configure their runtimes in the board
UI.
> - The old creation flow presents several choices and a large form
before an agent can start.
> - The existing onboarding controls already provide clear provider
connection steps.
> - This pull request uses those controls in a new-agent wizard and
organizes the full configuration pages.
> - Operators can connect, test, save, and assign a first task while
keeping the existing configuration tools.

## Linked Issues or Issue Description

Related: #10974. That earlier open PR also reorganizes agent
configuration. This PR follows the reviewed Storybook designs for agent
creation and the current configuration tabs.

**What existing behavior does this improve?**

Agent creation, provider connection, runtime tests, and full agent
configuration.

**Current behavior**

The creation dialog leads to a large manual configuration form. Provider
login controls differ from onboarding. Environment variables and secret
access appear in separate places.

**Proposed behavior**

Choose a name and adapter. Connect Claude or Codex through the existing
onboarding controls. Configure and test the runtime, save the agent, and
open a task dialog with that agent assigned. Use the same design on the
existing configuration tabs.

**Reason and benefit**

The first setup asks for fewer decisions. The full editor keeps
instructions, skills, runtime controls, secret access, permissions,
keys, and revisions available in clear sections.

**Breaking changes**

The board creation and configuration layouts change. The
test-environment API adds an optional, allowlisted `testCredentials`
field for one-shot probes. Database contracts stay the same. Native ACPX
tests now reject unsupported local platforms before a CLI login can mask
the runtime restriction.

## What Changed

- Added a new-agent wizard with numbered steps, adapter branding,
provider connections, editable model choices, runtime tests, and
confirmation.
- Added Codex app-server, Claude ACPX, and OpenCode runner choices.
- Stored API credentials through existing secret APIs and persisted
references in agent configuration. New setup keys are isolated from
credentials used by existing agents.
- Preserved external-agent invitations beside the wizard, including
optional messages, one-time prompts, and clipboard fallback.
- Added OpenRouter provider and secret bindings for Pi and OpenCode.
- Added adapter-specific prerequisite fields for Cursor, Gemini, Kimi,
and Hermes. Cursor Cloud keys are saved as new organization secrets.
- Fixed Cursor Cloud repository field mapping, omitted empty remote
environment values, and added useful model and repository error
messages.
- Preserved complete MCP assignments when multiple valid profiles
contain more than 250 tools in total. Generated profiles retain exact
tool selectors.
- Added service branding and deployment-aware adapter choices. Cloud
setup offers Claude, Codex, and OpenCode; local native runners require
the experimental setting.
- Made the agent list responsive at intermediate widths.
- Applied the reviewed design to the real agent configuration pages.
Kept the instruction editor, skills, and existing mutations.
- Combined secret access and environment variables under one Save and
Discard action.
- Added interactive Storybook screens for setup, configuration,
confirmation, authentication, and test results.
- Fixed Pi provider-error parsing and thinking-effort persistence.
Native ACPX validates Linux x64 on the actual local, SSH, or sandbox
target.
- Redacted the complete transient probe-credential field from HTTP error
logs, including rejected provider names.

## Verification

- Current head `df0292fe6` has a fresh Greptile 5/5 review with no
unresolved findings. All 31 executed CI checks passed, including the
aggregate verification gate and all browser E2E shards. Storybook visual
regression is skipped by its workflow; the local Storybook build passed.

- Browser tests completed real assigned tasks with direct Codex, Claude,
OpenCode, Pi, and native Codex.
- Verified external-agent invitation generation and automatic prompt
copying in the live browser.
- Pi and OpenCode used an existing OpenRouter secret. Browser checks
covered save and reload, instruction edits, skill selection,
environment-variable Save and Discard, and assigned task creation.
- Invalid Claude API credentials remained on the connection step with an
error. A live Pi/OpenRouter invalid-key probe returned a provider
failure and left the user-secret inventory unchanged (zero entries
before and after).
- Full workspace typecheck and build passed after rebasing onto current
master. After review fixes, server and UI typechecks, token gates, and
the full build passed again. Storybook built successfully.
- All 5,542 local UI tests passed. The Cursor Cloud and Pi adapter
regressions passed all 24 tests. Review regressions passed 69 server
tests and all 18 agent-list tests.
- The local full test command ran 6,971 general server tests
successfully. Editing review fixes during that long run caused nine
tests to use stale modules; fresh isolated runs passed. An unrelated
embedded-Postgres fixture hit the host shared-memory limit; its 15
affected tests passed when the fixture groups ran separately.
- Local workspace groups passed after rerunning 18 CLI tests
sequentially to avoid host database limits and parallel-load timeouts.
The local full command stopped at the general server phase, so
serialized server verification comes from the five passing CI shards.
- Browser testing at 390px confirmed that the agent action menu opens
and the page has no horizontal overflow. CI browser E2E shards passed.
- Review the `Onboarding / New agent` and `Agents / Configuration
refresh` Storybook groups. In the real app, create an agent, run its
connection test, save it, assign a task, and reload its configuration.

## Risks

- This changes the main agent setup and configuration UI. Regression
tests cover routing, persistence, secret bindings, and form actions.
- Native Claude ACPX requires Linux x64. Direct Claude works on macOS.
Remote checks execute a bounded platform probe and reject unsupported or
unverified targets.
- A native OpenCode task reached the provider context limit because of
its tool payload. Its provider connection test passed. Direct OpenCode
completed a task. This existing native execution limit is not fixed
here.
- Claude and Codex connection keys use the existing user-secret store.
Other runtime setup keys use distinct organization secrets. Existing
credentials are never rotated. Probes do not store entered keys. Failed
agent creation removes newly staged credentials.
- Cursor Cloud has not completed a live task. Its authenticated account
still needs GitHub repository access. The live run passed MCP
provisioning, remote environment validation, and explicit Auto model
selection before the repository prerequisite blocked execution.
- Generated runtime MCP profiles can exceed the public profile-edit
request limit. They still contain exact catalog selectors and preserve
permission boundaries.
- No database migration, dependency, lockfile, or workflow changes are
included.

## Model Used

OpenAI Codex, based on GPT-6, with reasoning, repository tools, shell
execution, and browser automation. The runtime did not expose the exact
model ID or context window size.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-09-08 14:22:32 -05:00

158 lines
4.8 KiB
TypeScript

import {
CircleCheck,
CircleAlert,
Loader2,
Play,
ChevronRight,
} from "lucide-react";
import type { AdapterEnvironmentTestResult } from "@paperclipai/shared";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
export type TestState = "idle" | "running" | "pass" | "fail";
const copy = {
idle: {
title: "Test your agent",
description: "Check that your runtime and model can respond.",
action: "Run test",
},
running: {
title: "Testing connection",
description: "Checking the runtime and waiting for a model response…",
action: "Testing…",
},
pass: {
title: "Connection successful",
description: "Your runtime checks passed. Review the details below.",
action: "Test again",
},
warn: {
title: "Connection needs attention",
description: "Review the test details before running your agent.",
action: "Test again",
},
fail: {
title: "Couldn't connect",
description: "Check your model and provider connection, then try again.",
action: "Retry test",
},
} as const;
export function RuntimeTestCard({
state,
result,
error,
onTest,
disabled = false,
}: {
state: TestState | "warn";
result: AdapterEnvironmentTestResult | null;
error?: string | null;
onTest: () => void;
disabled?: boolean;
}) {
const content = copy[state];
const Icon =
state === "running"
? Loader2
: state === "pass"
? CircleCheck
: state === "fail" || state === "warn"
? CircleAlert
: Play;
return (
<section
aria-label="Runtime test"
className="rounded-lg border border-border bg-card"
>
<div className="flex items-start gap-3 p-4 sm:items-center">
<span
aria-hidden="true"
className={cn(
"flex size-9 shrink-0 items-center justify-center rounded-full bg-muted",
state === "pass"
? "text-green-600 dark:text-green-400"
: state === "fail"
? "text-destructive"
: "text-muted-foreground",
)}
>
<Icon
className={cn(
"size-4",
state === "running" && "animate-spin motion-reduce:animate-none",
)}
/>
</span>
<div className="flex min-w-0 flex-1 flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
<div
role={state === "fail" ? "alert" : "status"}
aria-atomic="true"
className="min-w-0 space-y-1"
>
<h3 className="text-sm font-medium">{content.title}</h3>
<p className="text-xs leading-relaxed text-muted-foreground">
{state === "fail" && error ? error : content.description}
</p>
</div>
<Button
type="button"
variant="outline"
size="sm"
className="shrink-0 self-start sm:self-center"
disabled={disabled || state === "running"}
onClick={onTest}
>
{content.action}
</Button>
</div>
</div>
{result && result.checks.length > 0 && (
<details
key={`${state}:${result.testedAt}`}
className="group border-t border-border"
>
<summary className="flex cursor-pointer list-none items-center gap-1.5 px-4 py-2.5 text-xs text-muted-foreground outline-none hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring">
<ChevronRight
aria-hidden="true"
className="size-3 transition-transform group-open:rotate-90"
/>
Test details
</summary>
<ul className="space-y-3 px-4 pb-4">
{result.checks.map((check) => (
<li
key={check.code}
className="flex gap-2 text-xs leading-relaxed"
>
{check.level === "error" || check.level === "warn" ? (
<CircleAlert
aria-hidden="true"
className="mt-0.5 size-3.5 shrink-0 text-destructive"
/>
) : (
<CircleCheck
aria-hidden="true"
className="mt-0.5 size-3.5 shrink-0 text-muted-foreground"
/>
)}
<div className="min-w-0 space-y-1">
<p className="break-words text-foreground">{check.message}</p>
{check.detail && (
<p className="break-all text-muted-foreground">
{check.detail}
</p>
)}
{check.hint && (
<p className="text-muted-foreground">{check.hint}</p>
)}
</div>
</li>
))}
</ul>
</details>
)}
</section>
);
}