mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-12 18:06:11 +02:00
## 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>
158 lines
4.8 KiB
TypeScript
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>
|
|
);
|
|
}
|