mirror of
https://github.com/alkimake/paperclip.git
synced 2026-06-14 01:50:39 +09:00
[codex] Split reusable agent hiring templates (#4124)
## Thinking Path > - Paperclip orchestrates AI agents for zero-human companies > - Hiring new agents depends on clear, reusable operating instructions > - The create-agent skill had one large template reference that mixed multiple roles together > - That made it harder to reuse, review, and adapt role-specific instructions during governed hires > - This pull request splits the reusable agent instruction templates into focused role files and polishes the agent instructions pane layout > - The benefit is faster, clearer agent hiring without bloating the main skill document ## What Changed - Split coder, QA, and UX designer reusable instructions into dedicated reference files. - Kept the index reference concise and pointed it at the role-specific files. - Updated the create-agent skill to describe the separated template structure. - Polished the agent detail instructions/package file tree layout so the longer template references remain readable. ## Verification - `pnpm install --frozen-lockfile --ignore-scripts` - `pnpm --filter @paperclipai/ui typecheck` - UI screenshot rationale: no screenshots attached because the visible change is limited to the Agent detail instructions file-tree layout (`wrapLabels` plus the side-by-side breakpoint). There is no new user flow or state transition to demonstrate; reviewers can verify visually by opening an agent's Instructions tab and resizing across the single-column and side-by-side breakpoints to confirm long file names wrap instead of truncating or overflowing. ## Risks - Low risk: this is documentation and UI layout only. - Main risk is stale links in the skill references; the new files are committed in the referenced paths. > For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and discuss it in `#dev` before opening the PR. Feature PRs that overlap with planned core work may need to be redirected — check the roadmap first. See `CONTRIBUTING.md`. ## Model Used - OpenAI Codex coding agent based on GPT-5, tool-enabled local shell and GitHub workflow, exact runtime context window not exposed in this session. ## 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 run tests locally and they pass - [x] I have added or updated tests where applicable - [x] If this change affects the UI, I have included before/after screenshots, or documented why targeted component/type verification is sufficient here - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Paperclip <noreply@paperclip.ing>
This commit is contained in:
parent
73eb23734f
commit
0f4e4b4c10
7 changed files with 284 additions and 148 deletions
|
|
@ -177,6 +177,7 @@ export function PackageFileTree({
|
|||
renderFileExtra,
|
||||
fileRowClassName,
|
||||
showCheckboxes = true,
|
||||
wrapLabels = false,
|
||||
depth = 0,
|
||||
}: {
|
||||
nodes: FileTreeNode[];
|
||||
|
|
@ -191,6 +192,8 @@ export function PackageFileTree({
|
|||
/** Optional additional className for file rows */
|
||||
fileRowClassName?: (node: FileTreeNode, checked: boolean) => string | undefined;
|
||||
showCheckboxes?: boolean;
|
||||
/** Allow long file and directory names to wrap instead of forcing horizontal overflow. */
|
||||
wrapLabels?: boolean;
|
||||
depth?: number;
|
||||
}) {
|
||||
const effectiveCheckedFiles = checkedFiles ?? new Set<string>();
|
||||
|
|
@ -239,7 +242,9 @@ export function PackageFileTree({
|
|||
<Folder className="h-3.5 w-3.5" />
|
||||
)}
|
||||
</span>
|
||||
<span className="truncate">{node.name}</span>
|
||||
<span className={cn("min-w-0", wrapLabels ? "break-all leading-4" : "truncate")}>
|
||||
{node.name}
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
|
|
@ -265,6 +270,7 @@ export function PackageFileTree({
|
|||
renderFileExtra={renderFileExtra}
|
||||
fileRowClassName={fileRowClassName}
|
||||
showCheckboxes={showCheckboxes}
|
||||
wrapLabels={wrapLabels}
|
||||
depth={depth + 1}
|
||||
/>
|
||||
)}
|
||||
|
|
@ -307,7 +313,9 @@ export function PackageFileTree({
|
|||
<span className="flex h-4 w-4 shrink-0 items-center justify-center">
|
||||
<FileIcon className="h-3.5 w-3.5" />
|
||||
</span>
|
||||
<span className="truncate">{node.name}</span>
|
||||
<span className={cn("min-w-0", wrapLabels ? "break-all leading-4" : "truncate")}>
|
||||
{node.name}
|
||||
</span>
|
||||
</button>
|
||||
{renderFileExtra?.(node, checked)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1037,15 +1037,8 @@ export function AgentDetail() {
|
|||
)}
|
||||
|
||||
{/* Floating Save/Cancel (desktop) */}
|
||||
{!isMobile && (
|
||||
<div
|
||||
className={cn(
|
||||
"sticky top-6 z-10 float-right transition-opacity duration-150",
|
||||
showConfigActionBar
|
||||
? "opacity-100"
|
||||
: "opacity-0 pointer-events-none"
|
||||
)}
|
||||
>
|
||||
{!isMobile && showConfigActionBar && (
|
||||
<div className="fixed bottom-6 right-6 z-30">
|
||||
<div className="flex items-center gap-2 bg-background/90 backdrop-blur-sm border border-border rounded-lg px-3 py-1.5 shadow-lg">
|
||||
<Button
|
||||
variant="ghost"
|
||||
|
|
@ -1707,6 +1700,7 @@ function PromptsTab({
|
|||
const [pendingFiles, setPendingFiles] = useState<string[]>([]);
|
||||
const [expandedDirs, setExpandedDirs] = useState<Set<string>>(new Set());
|
||||
const [filePanelWidth, setFilePanelWidth] = useState(260);
|
||||
const [instructionPaneWidth, setInstructionPaneWidth] = useState<number | null>(null);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const [awaitingRefresh, setAwaitingRefresh] = useState(false);
|
||||
const lastFileVersionRef = useRef<string | null>(null);
|
||||
|
|
@ -1854,6 +1848,26 @@ function PromptsTab({
|
|||
setExpandedDirs((current) => (setsEqual(current, nextExpanded) ? current : nextExpanded));
|
||||
}, [visibleFilePaths]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isMobile) {
|
||||
setInstructionPaneWidth(null);
|
||||
return;
|
||||
}
|
||||
const element = containerRef.current;
|
||||
if (!element) return;
|
||||
|
||||
const updateWidth = () => setInstructionPaneWidth(element.getBoundingClientRect().width);
|
||||
updateWidth();
|
||||
|
||||
if (typeof ResizeObserver === "undefined") return;
|
||||
const observer = new ResizeObserver((entries) => {
|
||||
const entry = entries[0];
|
||||
if (entry) setInstructionPaneWidth(entry.contentRect.width);
|
||||
});
|
||||
observer.observe(element);
|
||||
return () => observer.disconnect();
|
||||
}, [bundleLoading, isMobile, visibleFilePaths.length]);
|
||||
|
||||
useEffect(() => {
|
||||
const versionKey = selectedFileExists && selectedFileDetail
|
||||
? `${selectedFileDetail.path}:${selectedFileDetail.content}`
|
||||
|
|
@ -1978,6 +1992,9 @@ function PromptsTab({
|
|||
document.body.style.userSelect = "none";
|
||||
}, [filePanelWidth]);
|
||||
|
||||
const instructionsSideBySide =
|
||||
!isMobile && instructionPaneWidth !== null && instructionPaneWidth >= filePanelWidth + 520;
|
||||
|
||||
if (!isLocal) {
|
||||
return (
|
||||
<div className="max-w-3xl">
|
||||
|
|
@ -2011,8 +2028,8 @@ function PromptsTab({
|
|||
</CollapsibleTrigger>
|
||||
<CollapsibleContent className="pt-4 pb-6">
|
||||
<TooltipProvider>
|
||||
<div className="grid gap-x-6 gap-y-4 sm:grid-cols-[auto_1fr_1fr]">
|
||||
<label className="space-y-1.5">
|
||||
<div className="grid gap-x-6 gap-y-4 md:grid-cols-[auto_minmax(0,1fr)_minmax(12rem,0.65fr)]">
|
||||
<label className="space-y-1.5 min-w-0">
|
||||
<span className="text-xs font-medium text-muted-foreground flex items-center gap-1">
|
||||
Mode
|
||||
<Tooltip>
|
||||
|
|
@ -2157,12 +2174,20 @@ function PromptsTab({
|
|||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
|
||||
<div ref={containerRef} className={cn("flex gap-0", isMobile && "flex-col gap-3")}>
|
||||
<div
|
||||
ref={containerRef}
|
||||
className="grid min-w-0 gap-3"
|
||||
style={
|
||||
instructionsSideBySide
|
||||
? { gridTemplateColumns: `${filePanelWidth}px 0.5rem minmax(0, 1fr)` }
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
<div className={cn(
|
||||
"border border-border rounded-lg p-3 space-y-3 shrink-0",
|
||||
"min-w-0 w-full border border-border rounded-lg p-3 space-y-3",
|
||||
isMobile && showFilePanel && "block",
|
||||
isMobile && !showFilePanel && "hidden",
|
||||
)} style={isMobile ? undefined : { width: filePanelWidth }}>
|
||||
)}>
|
||||
<div className="flex items-center justify-between">
|
||||
<h4 className="text-sm font-medium">Files</h4>
|
||||
<div className="flex items-center gap-1">
|
||||
|
|
@ -2257,6 +2282,7 @@ function PromptsTab({
|
|||
}}
|
||||
onToggleCheck={() => {}}
|
||||
showCheckboxes={false}
|
||||
wrapLabels
|
||||
renderFileExtra={(node) => {
|
||||
const file = bundle?.files.find((entry) => entry.path === node.path);
|
||||
if (!file) return null;
|
||||
|
|
@ -2284,14 +2310,14 @@ function PromptsTab({
|
|||
</div>
|
||||
|
||||
{/* Draggable separator */}
|
||||
{!isMobile && (
|
||||
{instructionsSideBySide && (
|
||||
<div
|
||||
className="w-1 shrink-0 cursor-col-resize hover:bg-border active:bg-primary/50 rounded transition-colors mx-1"
|
||||
className="w-1 cursor-col-resize rounded transition-colors hover:bg-border active:bg-primary/50"
|
||||
onMouseDown={handleSeparatorDrag}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className={cn("border border-border rounded-lg p-4 space-y-3 min-w-0 flex-1", isMobile && showFilePanel && "hidden")}>
|
||||
<div className={cn("min-w-0 w-full overflow-hidden border border-border rounded-lg p-4 space-y-3", isMobile && showFilePanel && "hidden")}>
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<div className="flex items-center gap-2 min-w-0">
|
||||
{isMobile && (
|
||||
|
|
@ -2346,7 +2372,8 @@ function PromptsTab({
|
|||
value={displayValue}
|
||||
onChange={(value) => setDraft(value ?? "")}
|
||||
placeholder="# Agent instructions"
|
||||
contentClassName="min-h-[420px] text-sm font-mono"
|
||||
className="min-w-0 overflow-hidden"
|
||||
contentClassName="min-h-[420px] max-w-full break-words text-sm font-mono"
|
||||
imageUploadHandler={async (file) => {
|
||||
const namespace = `agents/${agent.id}/instructions/${selectedOrEntryFile.replaceAll("/", "-")}`;
|
||||
const asset = await uploadMarkdownImage.mutateAsync({ file, namespace });
|
||||
|
|
@ -2357,7 +2384,7 @@ function PromptsTab({
|
|||
<textarea
|
||||
value={displayValue}
|
||||
onChange={(event) => setDraft(event.target.value)}
|
||||
className="min-h-[420px] w-full rounded-md border border-border bg-transparent px-3 py-2 font-mono text-sm outline-none"
|
||||
className="min-h-[420px] w-full min-w-0 rounded-md border border-border bg-transparent px-3 py-2 font-mono text-sm outline-none"
|
||||
placeholder="File contents"
|
||||
/>
|
||||
)}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue