mirror of
https://github.com/alkimake/paperclip.git
synced 2026-06-14 18:10:39 +09:00
feat(adapters): external adapter plugin system with dynamic UI parser
- Plugin loader: install/reload/remove/reinstall external adapters from npm packages or local directories - Plugin store persisted at ~/.paperclip/adapter-plugins.json - Self-healing UI parser resolution with version caching - UI: Adapter Manager page, dynamic loader, display registry with humanized names for unknown adapter types - Dev watch: exclude adapter-plugins dir from tsx watcher to prevent mid-request server restarts during reinstall - All consumer fallbacks use getAdapterLabel() for consistent display - AdapterTypeDropdown uses controlled open state for proper close behavior - Remove hermes-local from built-in UI (externalized to plugin) - Add docs for external adapters and UI parser contract
This commit is contained in:
parent
f8452a4520
commit
14d59da316
72 changed files with 4102 additions and 585 deletions
|
|
@ -81,4 +81,33 @@ describe("RunTranscriptView", () => {
|
|||
text: "Working on the task.",
|
||||
});
|
||||
});
|
||||
|
||||
it("renders successful result summaries as markdown in nice mode", () => {
|
||||
const html = renderToStaticMarkup(
|
||||
<ThemeProvider>
|
||||
<RunTranscriptView
|
||||
density="compact"
|
||||
entries={[
|
||||
{
|
||||
kind: "result",
|
||||
ts: "2026-03-12T00:00:02.000Z",
|
||||
text: "## Summary\n\n- fixed deploy config\n- posted issue update",
|
||||
inputTokens: 10,
|
||||
outputTokens: 20,
|
||||
cachedTokens: 0,
|
||||
costUsd: 0,
|
||||
subtype: "success",
|
||||
isError: false,
|
||||
errors: [],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</ThemeProvider>,
|
||||
);
|
||||
|
||||
expect(html).toContain("<h2>Summary</h2>");
|
||||
expect(html).toContain("<li>fixed deploy config</li>");
|
||||
expect(html).toContain("<li>posted issue update</li>");
|
||||
expect(html).not.toContain("result");
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -491,6 +491,10 @@ export function normalizeTranscript(entries: TranscriptEntry[], streaming: boole
|
|||
label: "result",
|
||||
tone: entry.isError ? "error" : "info",
|
||||
text: entry.text.trim() || entry.errors[0] || (entry.isError ? "Run failed" : "Completed"),
|
||||
detail:
|
||||
!entry.isError && entry.text.trim().length > 0
|
||||
? `${formatTokens(entry.inputTokens)} / ${formatTokens(entry.outputTokens)} / $${entry.costUsd.toFixed(6)}`
|
||||
: undefined,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
|
@ -1062,9 +1066,14 @@ function TranscriptEventRow({
|
|||
)}
|
||||
<div className="min-w-0 flex-1">
|
||||
{block.label === "result" && block.tone !== "error" ? (
|
||||
<div className={cn("whitespace-pre-wrap break-words text-sky-700 dark:text-sky-300", compact ? "text-[11px]" : "text-xs")}>
|
||||
<MarkdownBody
|
||||
className={cn(
|
||||
"[&>*:first-child]:mt-0 [&>*:last-child]:mb-0 text-sky-700 dark:text-sky-300",
|
||||
compact ? "text-[11px] leading-5" : "text-xs leading-5",
|
||||
)}
|
||||
>
|
||||
{block.text}
|
||||
</div>
|
||||
</MarkdownBody>
|
||||
) : (
|
||||
<div className={cn("whitespace-pre-wrap break-words", compact ? "text-[11px]" : "text-xs")}>
|
||||
<span className="text-[10px] font-semibold uppercase tracking-[0.1em] text-muted-foreground/70">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue