@getcatalystiq/agent-plane-ui 0.1.15 → 0.1.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -7,6 +7,7 @@ var swr = require('swr');
7
7
  var ReactMarkdown = require('react-markdown');
8
8
  var cmdk = require('cmdk');
9
9
  var Popover = require('@radix-ui/react-popover');
10
+ var remarkGfm = require('remark-gfm');
10
11
 
11
12
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
12
13
 
@@ -31,6 +32,7 @@ function _interopNamespace(e) {
31
32
  var React3__namespace = /*#__PURE__*/_interopNamespace(React3);
32
33
  var ReactMarkdown__default = /*#__PURE__*/_interopDefault(ReactMarkdown);
33
34
  var Popover__namespace = /*#__PURE__*/_interopNamespace(Popover);
35
+ var remarkGfm__default = /*#__PURE__*/_interopDefault(remarkGfm);
34
36
 
35
37
  function Select({ className = "", ...props }) {
36
38
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative w-full", children: [
@@ -2528,8 +2530,10 @@ function AgentEditForm({ agent, onSaved }) {
2528
2530
  const [maxTurns, setMaxTurns] = React3.useState(agent.max_turns.toString());
2529
2531
  const [maxBudget, setMaxBudget] = React3.useState(agent.max_budget_usd.toString());
2530
2532
  const [maxRuntime, setMaxRuntime] = React3.useState(Math.floor(agent.max_runtime_seconds / 60).toString());
2533
+ const [soulMd, setSoulMd] = React3.useState(agent.soul_md ?? "");
2534
+ const [identityMd, setIdentityMd] = React3.useState(agent.identity_md ?? "");
2531
2535
  const [saving, setSaving] = React3.useState(false);
2532
- const isDirty = name !== agent.name || description !== (agent.description ?? "") || model !== agent.model || runner !== (agent.runner ?? "") || permissionMode !== agent.permission_mode || maxTurns !== agent.max_turns.toString() || maxBudget !== agent.max_budget_usd.toString() || maxRuntime !== Math.floor(agent.max_runtime_seconds / 60).toString();
2536
+ const isDirty = name !== agent.name || description !== (agent.description ?? "") || model !== agent.model || runner !== (agent.runner ?? "") || permissionMode !== agent.permission_mode || maxTurns !== agent.max_turns.toString() || maxBudget !== agent.max_budget_usd.toString() || maxRuntime !== Math.floor(agent.max_runtime_seconds / 60).toString() || soulMd !== (agent.soul_md ?? "") || identityMd !== (agent.identity_md ?? "");
2533
2537
  const [error, setError] = React3.useState("");
2534
2538
  async function handleSave() {
2535
2539
  setSaving(true);
@@ -2543,7 +2547,9 @@ function AgentEditForm({ agent, onSaved }) {
2543
2547
  permission_mode: permissionMode,
2544
2548
  max_turns: parseInt(maxTurns) || agent.max_turns,
2545
2549
  max_budget_usd: parseFloat(maxBudget) || agent.max_budget_usd,
2546
- max_runtime_seconds: (parseInt(maxRuntime) || Math.floor(agent.max_runtime_seconds / 60)) * 60
2550
+ max_runtime_seconds: (parseInt(maxRuntime) || Math.floor(agent.max_runtime_seconds / 60)) * 60,
2551
+ soul_md: soulMd || null,
2552
+ identity_md: identityMd || null
2547
2553
  });
2548
2554
  onSaved?.();
2549
2555
  } catch (err) {
@@ -2605,7 +2611,46 @@ function AgentEditForm({ agent, onSaved }) {
2605
2611
  /* @__PURE__ */ jsxRuntime.jsx("option", { value: "plan", children: "plan" })
2606
2612
  ] }) }) })
2607
2613
  ] })
2608
- ] }) })
2614
+ ] }) }),
2615
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-lg border border-muted-foreground/25 p-5", children: [
2616
+ /* @__PURE__ */ jsxRuntime.jsx(SectionHeader, { title: "Identity" }),
2617
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-4", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
2618
+ /* @__PURE__ */ jsxRuntime.jsx("div", { children: /* @__PURE__ */ jsxRuntime.jsxs(FormField, { label: "SOUL.md", children: [
2619
+ /* @__PURE__ */ jsxRuntime.jsx(
2620
+ Textarea,
2621
+ {
2622
+ value: soulMd,
2623
+ onChange: (e) => setSoulMd(e.target.value),
2624
+ placeholder: "## Voice & Tone\nDirect, concise, technical.\n\n## Values\nClarity over completeness.\n\n## Stance\nProactive problem-solver.\n\n## Boundaries\n- Never modify production data directly\n\n## Essence\nA focused engineering assistant.",
2625
+ rows: 10,
2626
+ className: "font-mono text-sm",
2627
+ disabled: saving
2628
+ }
2629
+ ),
2630
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs text-muted-foreground mt-1 block", children: [
2631
+ soulMd.split(/\s+/).filter(Boolean).length,
2632
+ " words"
2633
+ ] })
2634
+ ] }) }),
2635
+ /* @__PURE__ */ jsxRuntime.jsx("div", { children: /* @__PURE__ */ jsxRuntime.jsxs(FormField, { label: "IDENTITY.md", children: [
2636
+ /* @__PURE__ */ jsxRuntime.jsx(
2637
+ Textarea,
2638
+ {
2639
+ value: identityMd,
2640
+ onChange: (e) => setIdentityMd(e.target.value),
2641
+ placeholder: "- **Communication Verbosity:** concise\n- **Communication Tone:** direct\n- **Decision Autonomy:** high\n- **Risk Tolerance:** moderate\n- **Collaboration Mode:** autonomous\n\n## Escalation Preferences\n- Budget over $50 -> escalate\n- Breaking changes -> escalate",
2642
+ rows: 10,
2643
+ className: "font-mono text-sm",
2644
+ disabled: saving
2645
+ }
2646
+ ),
2647
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs text-muted-foreground mt-1 block", children: [
2648
+ identityMd.split(/\s+/).filter(Boolean).length,
2649
+ " words"
2650
+ ] })
2651
+ ] }) })
2652
+ ] }) })
2653
+ ] })
2609
2654
  ] });
2610
2655
  }
2611
2656
  function ToolkitMultiselect({ value, onChange }) {
@@ -4386,6 +4431,443 @@ function ScheduleCard({
4386
4431
  ] })
4387
4432
  ] });
4388
4433
  }
4434
+ function MarkdownContent({ children }) {
4435
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-sm text-foreground [&_p]:my-1 [&_h1]:text-lg [&_h1]:font-bold [&_h1]:my-2 [&_h2]:text-base [&_h2]:font-semibold [&_h2]:my-2 [&_h3]:text-sm [&_h3]:font-semibold [&_h3]:my-1 [&_ul]:list-disc [&_ul]:pl-5 [&_ul]:my-1 [&_ol]:list-decimal [&_ol]:pl-5 [&_ol]:my-1 [&_li]:my-0.5 [&_pre]:bg-muted [&_pre]:rounded-md [&_pre]:p-3 [&_pre]:overflow-x-auto [&_pre]:text-xs [&_pre]:my-2 [&_code:not(pre_code)]:bg-muted [&_code:not(pre_code)]:px-1 [&_code:not(pre_code)]:py-0.5 [&_code:not(pre_code)]:rounded [&_code:not(pre_code)]:text-xs [&_code:not(pre_code)]:font-mono [&_a]:text-blue-400 [&_a]:underline [&_blockquote]:border-l-2 [&_blockquote]:border-border [&_blockquote]:pl-3 [&_blockquote]:text-muted-foreground [&_blockquote]:my-2 [&_hr]:border-border [&_hr]:my-3 [&_table]:border-collapse [&_table]:text-xs [&_table]:w-full [&_th]:border [&_th]:border-border [&_th]:px-2 [&_th]:py-1 [&_th]:text-left [&_th]:font-semibold [&_th]:bg-muted [&_td]:border [&_td]:border-border [&_td]:px-2 [&_td]:py-1 [&_strong]:font-semibold [&_em]:italic", children: /* @__PURE__ */ jsxRuntime.jsx(
4436
+ ReactMarkdown__default.default,
4437
+ {
4438
+ remarkPlugins: [remarkGfm__default.default],
4439
+ components: {
4440
+ a: ({ href, children: linkChildren }) => /* @__PURE__ */ jsxRuntime.jsx("a", { href, target: "_blank", rel: "noopener noreferrer", children: linkChildren })
4441
+ },
4442
+ children
4443
+ }
4444
+ ) });
4445
+ }
4446
+ function CollapsibleJson({ data, maxHeight = "12rem" }) {
4447
+ const [expanded, setExpanded] = React3.useState(false);
4448
+ const json = typeof data === "string" ? data : JSON.stringify(data, null, 2);
4449
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
4450
+ /* @__PURE__ */ jsxRuntime.jsx(
4451
+ "pre",
4452
+ {
4453
+ className: `text-xs font-mono text-muted-foreground bg-muted/50 rounded-md p-2 overflow-x-auto whitespace-pre-wrap break-all ${expanded ? "" : "overflow-hidden"}`,
4454
+ style: expanded ? void 0 : { maxHeight },
4455
+ children: json
4456
+ }
4457
+ ),
4458
+ json.length > 200 && /* @__PURE__ */ jsxRuntime.jsx(
4459
+ "button",
4460
+ {
4461
+ onClick: () => setExpanded(!expanded),
4462
+ className: "text-xs text-blue-400 hover:text-blue-300 mt-1",
4463
+ children: expanded ? "Collapse" : "Expand"
4464
+ }
4465
+ )
4466
+ ] });
4467
+ }
4468
+ function renderEvent(event, idx) {
4469
+ if (event.type === "heartbeat") return null;
4470
+ if (event.type === "text_delta") return null;
4471
+ if (event.type === "session_created") return null;
4472
+ if (event.type === "session_info") return null;
4473
+ if (event.type === "mcp_status") return null;
4474
+ if (event.type === "rate_limit_event") return null;
4475
+ if (event.type === "user_message") {
4476
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "border-t border-border pt-3 mt-1", children: [
4477
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-semibold text-emerald-400 uppercase", children: "You" }),
4478
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-foreground mt-1 whitespace-pre-wrap", children: String(event.text) })
4479
+ ] }, idx);
4480
+ }
4481
+ if (event.type === "assistant") {
4482
+ const content = event.message;
4483
+ const blocks = content?.content ?? [];
4484
+ const textBlocks = blocks.filter((c) => c.type === "text").map((c) => c.text).join("");
4485
+ const toolUseBlocks = blocks.filter((c) => c.type === "tool_use");
4486
+ if (!textBlocks && toolUseBlocks.length === 0) return null;
4487
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
4488
+ textBlocks && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1", children: [
4489
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-semibold text-blue-400 uppercase", children: "Assistant" }),
4490
+ /* @__PURE__ */ jsxRuntime.jsx(MarkdownContent, { children: textBlocks })
4491
+ ] }),
4492
+ toolUseBlocks.map((tool, ti) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1 ml-3 pl-3 border-l-2 border-yellow-800/50", children: [
4493
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs font-semibold text-yellow-400 uppercase", children: [
4494
+ "Tool Call: ",
4495
+ tool.name ?? "unknown"
4496
+ ] }),
4497
+ tool.id && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground ml-2 font-mono", children: String(tool.id) }),
4498
+ tool.input != null && /* @__PURE__ */ jsxRuntime.jsx(CollapsibleJson, { data: tool.input })
4499
+ ] }, ti))
4500
+ ] }, idx);
4501
+ }
4502
+ if (event.type === "tool_use") {
4503
+ const toolName = String(event.tool_name ?? event.name ?? "unknown");
4504
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1 ml-3 pl-3 border-l-2 border-yellow-800/50", children: [
4505
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
4506
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-semibold text-yellow-400 uppercase", children: "Tool Call" }),
4507
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-mono text-yellow-400/80", children: toolName }),
4508
+ event.tool_use_id ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground font-mono", children: String(event.tool_use_id) }) : null
4509
+ ] }),
4510
+ event.input != null ? /* @__PURE__ */ jsxRuntime.jsx(CollapsibleJson, { data: event.input }) : null
4511
+ ] }, idx);
4512
+ }
4513
+ if (event.type === "tool_result") {
4514
+ const isError = event.is_error === true || event.error === true;
4515
+ const content = event.output ?? event.content ?? "";
4516
+ const contentStr = typeof content === "string" ? content : JSON.stringify(content, null, 2);
4517
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `space-y-1 ml-3 pl-3 border-l-2 ${isError ? "border-red-800/50" : "border-green-800/50"}`, children: [
4518
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
4519
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: `text-xs font-semibold uppercase ${isError ? "text-red-400" : "text-green-400"}`, children: isError ? "Tool Error" : "Tool Result" }),
4520
+ event.tool_name ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-mono text-muted-foreground", children: String(event.tool_name) }) : null,
4521
+ event.tool_use_id ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground font-mono", children: String(event.tool_use_id) }) : null
4522
+ ] }),
4523
+ contentStr ? /* @__PURE__ */ jsxRuntime.jsx(CollapsibleJson, { data: contentStr }) : null
4524
+ ] }, idx);
4525
+ }
4526
+ if (event.type === "result") {
4527
+ const success = event.subtype === "success";
4528
+ const costUsd = event.cost_usd ?? event.total_cost_usd;
4529
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `rounded-md px-3 py-2 flex items-center gap-3 ${success ? "bg-green-950 border border-green-900" : "bg-red-950 border border-red-900"}`, children: [
4530
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: `text-xs font-semibold ${success ? "text-green-400" : "text-red-400"}`, children: success ? "Completed" : "Failed" }),
4531
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap gap-3 text-xs text-zinc-400", children: [
4532
+ event.num_turns != null && /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
4533
+ String(event.num_turns),
4534
+ " turns"
4535
+ ] }),
4536
+ costUsd != null && Number(costUsd) > 0 && /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
4537
+ "$",
4538
+ Number(costUsd).toFixed(4)
4539
+ ] }),
4540
+ event.duration_ms != null && /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
4541
+ (Number(event.duration_ms) / 1e3).toFixed(1),
4542
+ "s"
4543
+ ] }),
4544
+ event.duration_api_ms != null && /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
4545
+ "API: ",
4546
+ (Number(event.duration_api_ms) / 1e3).toFixed(1),
4547
+ "s"
4548
+ ] })
4549
+ ] })
4550
+ ] }, idx);
4551
+ }
4552
+ if (event.type === "error") {
4553
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-md p-3 bg-red-950 border border-red-800", children: [
4554
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
4555
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-semibold text-red-400", children: "Error" }),
4556
+ event.code ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-mono text-red-400/70", children: String(event.code) }) : null
4557
+ ] }),
4558
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-foreground mt-1", children: String(event.error ?? "Unknown error") })
4559
+ ] }, idx);
4560
+ }
4561
+ if (event.type === "stream_detached") {
4562
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-xs text-muted-foreground italic border-t border-border pt-2", children: [
4563
+ "Stream detached at ",
4564
+ event.timestamp ? new Date(String(event.timestamp)).toLocaleTimeString() : "unknown",
4565
+ " \u2014 run continues in background"
4566
+ ] }, idx);
4567
+ }
4568
+ if (event.type === "queued") {
4569
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-xs text-muted-foreground", children: "Queued\u2026" }, idx);
4570
+ }
4571
+ if (event.type === "sandbox_starting") {
4572
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-xs text-muted-foreground", children: "Starting sandbox\u2026" }, idx);
4573
+ }
4574
+ if (event.type === "run_started") {
4575
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-xs text-muted-foreground", children: [
4576
+ "Agent started",
4577
+ event.model ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ml-2 font-mono text-foreground/60", children: String(event.model) }) : null,
4578
+ event.mcp_server_count != null && Number(event.mcp_server_count) > 0 && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "ml-2", children: [
4579
+ String(event.mcp_server_count),
4580
+ " MCP server",
4581
+ Number(event.mcp_server_count) !== 1 ? "s" : ""
4582
+ ] })
4583
+ ] }, idx);
4584
+ }
4585
+ if (event.type === "system") {
4586
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-xs text-muted-foreground italic", children: String(event.message ?? JSON.stringify(event)) }, idx);
4587
+ }
4588
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1", children: [
4589
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-semibold text-purple-400 uppercase", children: event.type }),
4590
+ /* @__PURE__ */ jsxRuntime.jsx(CollapsibleJson, { data: event, maxHeight: "8rem" })
4591
+ ] }, idx);
4592
+ }
4593
+ var TERMINAL_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "cancelled", "timed_out"]);
4594
+ function PlaygroundPage({ agentId }) {
4595
+ const client = chunk4XBBDUSZ_cjs.useAgentPlaneClient();
4596
+ const { LinkComponent, basePath } = chunk4XBBDUSZ_cjs.useNavigation();
4597
+ const { data: agent, error: agentError, isLoading } = chunk4XBBDUSZ_cjs.useApi(
4598
+ `agent-${agentId}`,
4599
+ (c) => c.agents.get(agentId)
4600
+ );
4601
+ const [prompt, setPrompt] = React3.useState("");
4602
+ const [events, setEvents] = React3.useState([]);
4603
+ const [streamingText, setStreamingText] = React3.useState("");
4604
+ const [running, setRunning] = React3.useState(false);
4605
+ const [polling, setPolling] = React3.useState(false);
4606
+ const [error, setError] = React3.useState(null);
4607
+ const [sessionId, setSessionId] = React3.useState(null);
4608
+ const sessionIdRef = React3.useRef(null);
4609
+ const abortRef = React3.useRef(null);
4610
+ const runIdRef = React3.useRef(null);
4611
+ const streamRef = React3.useRef(null);
4612
+ const scrollRef = React3.useRef(null);
4613
+ const textareaRef = React3.useRef(null);
4614
+ React3.useEffect(() => {
4615
+ const el = scrollRef.current;
4616
+ if (el) el.scrollTop = el.scrollHeight;
4617
+ }, [events, streamingText]);
4618
+ React3.useEffect(() => {
4619
+ return () => {
4620
+ abortRef.current?.abort();
4621
+ streamRef.current?.abort();
4622
+ };
4623
+ }, []);
4624
+ const pollForFinalResult = React3.useCallback(async (runId) => {
4625
+ setPolling(true);
4626
+ let delay = 3e3;
4627
+ const maxDelay = 1e4;
4628
+ try {
4629
+ while (true) {
4630
+ if (abortRef.current?.signal.aborted) break;
4631
+ await new Promise((r) => setTimeout(r, delay));
4632
+ if (abortRef.current?.signal.aborted) break;
4633
+ try {
4634
+ const run = await client.runs.get(runId);
4635
+ if (TERMINAL_STATUSES.has(run.status)) {
4636
+ try {
4637
+ const transcriptEvents = await client.runs.transcriptArray(runId);
4638
+ if (transcriptEvents.length > 0) {
4639
+ const detachIdx = transcriptEvents.findIndex((ev) => ev.type === "stream_detached");
4640
+ const eventsAfterDetach = detachIdx >= 0 ? transcriptEvents.slice(detachIdx + 1) : [];
4641
+ const newEvents = eventsAfterDetach.filter(
4642
+ (ev) => ev.type !== "heartbeat" && ev.type !== "text_delta" && ev.type !== "run_started" && ev.type !== "queued" && ev.type !== "sandbox_starting"
4643
+ );
4644
+ if (newEvents.length > 0) {
4645
+ setEvents((prev) => [...prev, ...newEvents]);
4646
+ }
4647
+ }
4648
+ } catch {
4649
+ }
4650
+ setEvents((prev) => {
4651
+ if (prev.some((ev) => ev.type === "result")) return prev;
4652
+ const syntheticResult = {
4653
+ type: "result",
4654
+ subtype: run.status === "completed" ? "success" : "failed",
4655
+ cost_usd: run.cost_usd,
4656
+ num_turns: run.num_turns,
4657
+ duration_ms: run.duration_ms
4658
+ };
4659
+ if (run.error_type) {
4660
+ syntheticResult.result = run.error_type;
4661
+ }
4662
+ return [...prev, syntheticResult];
4663
+ });
4664
+ break;
4665
+ }
4666
+ delay = Math.min(delay * 2, maxDelay);
4667
+ } catch (err) {
4668
+ if (err?.name === "AbortError") break;
4669
+ delay = Math.min(delay * 2, maxDelay);
4670
+ }
4671
+ }
4672
+ } finally {
4673
+ setPolling(false);
4674
+ setRunning(false);
4675
+ abortRef.current = null;
4676
+ runIdRef.current = null;
4677
+ streamRef.current = null;
4678
+ }
4679
+ }, [client]);
4680
+ const consumeStream = React3.useCallback(async (stream) => {
4681
+ streamRef.current = stream;
4682
+ let handedOffToPoll = false;
4683
+ try {
4684
+ for await (const event of stream) {
4685
+ const ev = event;
4686
+ if (ev.type === "session_created" && ev.session_id) {
4687
+ const sid = ev.session_id;
4688
+ sessionIdRef.current = sid;
4689
+ setSessionId(sid);
4690
+ }
4691
+ if (ev.type === "run_started" && ev.run_id) {
4692
+ runIdRef.current = ev.run_id;
4693
+ }
4694
+ if (ev.type === "text_delta") {
4695
+ setStreamingText((prev) => prev + (ev.text ?? ""));
4696
+ } else if (ev.type === "stream_detached") {
4697
+ setStreamingText("");
4698
+ setEvents((prev) => [...prev, ev]);
4699
+ if (runIdRef.current) {
4700
+ handedOffToPoll = true;
4701
+ pollForFinalResult(runIdRef.current);
4702
+ return;
4703
+ }
4704
+ } else {
4705
+ if (ev.type === "assistant") setStreamingText("");
4706
+ setEvents((prev) => [...prev, ev]);
4707
+ }
4708
+ }
4709
+ } finally {
4710
+ if (!handedOffToPoll) {
4711
+ setRunning(false);
4712
+ abortRef.current = null;
4713
+ runIdRef.current = null;
4714
+ streamRef.current = null;
4715
+ }
4716
+ }
4717
+ }, [pollForFinalResult]);
4718
+ const handleSend = React3.useCallback(async () => {
4719
+ if (!prompt.trim() || running) return;
4720
+ const messageText = prompt.trim();
4721
+ setPrompt("");
4722
+ setRunning(true);
4723
+ setStreamingText("");
4724
+ setError(null);
4725
+ setPolling(false);
4726
+ setEvents((prev) => [...prev, { type: "user_message", text: messageText }]);
4727
+ const abort = new AbortController();
4728
+ abortRef.current = abort;
4729
+ try {
4730
+ let stream;
4731
+ const currentSessionId = sessionIdRef.current;
4732
+ if (currentSessionId) {
4733
+ stream = await client.sessions.sendMessage(
4734
+ currentSessionId,
4735
+ { prompt: messageText },
4736
+ { signal: abort.signal }
4737
+ );
4738
+ } else {
4739
+ stream = await client.sessions.create(
4740
+ { agent_id: agentId, prompt: messageText },
4741
+ { signal: abort.signal }
4742
+ );
4743
+ }
4744
+ await consumeStream(stream);
4745
+ } catch (err) {
4746
+ if (err?.name !== "AbortError") {
4747
+ const msg = err instanceof Error ? err.message : "Unknown error";
4748
+ setError(msg);
4749
+ }
4750
+ setRunning(false);
4751
+ abortRef.current = null;
4752
+ runIdRef.current = null;
4753
+ streamRef.current = null;
4754
+ }
4755
+ }, [prompt, running, agentId, client, consumeStream]);
4756
+ function handleNewChat() {
4757
+ abortRef.current?.abort();
4758
+ streamRef.current?.abort();
4759
+ if (sessionId) {
4760
+ client.sessions.stop(sessionId).catch(() => {
4761
+ });
4762
+ }
4763
+ sessionIdRef.current = null;
4764
+ setSessionId(null);
4765
+ setEvents([]);
4766
+ setStreamingText("");
4767
+ setRunning(false);
4768
+ setPolling(false);
4769
+ setError(null);
4770
+ setPrompt("");
4771
+ runIdRef.current = null;
4772
+ abortRef.current = null;
4773
+ streamRef.current = null;
4774
+ textareaRef.current?.focus();
4775
+ }
4776
+ function handleStop() {
4777
+ abortRef.current?.abort();
4778
+ streamRef.current?.abort();
4779
+ const id = runIdRef.current;
4780
+ if (id) {
4781
+ runIdRef.current = null;
4782
+ client.runs.cancel(id).catch(() => {
4783
+ });
4784
+ }
4785
+ }
4786
+ if (isLoading) {
4787
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col h-[calc(100vh-6rem)]", children: [
4788
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3 mb-4", children: [
4789
+ /* @__PURE__ */ jsxRuntime.jsx(chunk4XBBDUSZ_cjs.Skeleton, { className: "h-8 w-24" }),
4790
+ /* @__PURE__ */ jsxRuntime.jsx(chunk4XBBDUSZ_cjs.Skeleton, { className: "h-4 w-40" })
4791
+ ] }),
4792
+ /* @__PURE__ */ jsxRuntime.jsx(chunk4XBBDUSZ_cjs.Skeleton, { className: "flex-1 rounded-lg" }),
4793
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-4 space-y-2", children: [
4794
+ /* @__PURE__ */ jsxRuntime.jsx(chunk4XBBDUSZ_cjs.Skeleton, { className: "h-24 w-full rounded-lg" }),
4795
+ /* @__PURE__ */ jsxRuntime.jsx(chunk4XBBDUSZ_cjs.Skeleton, { className: "h-8 w-20" })
4796
+ ] })
4797
+ ] });
4798
+ }
4799
+ if (agentError || !agent) {
4800
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center justify-center h-[calc(100vh-6rem)] gap-3", children: [
4801
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-destructive text-sm", children: agentError?.status === 404 ? "Agent not found." : `Failed to load agent: ${agentError?.message ?? "Unknown error"}` }),
4802
+ /* @__PURE__ */ jsxRuntime.jsx(
4803
+ LinkComponent,
4804
+ {
4805
+ href: `${basePath}/agents`,
4806
+ className: "text-sm text-muted-foreground hover:text-foreground",
4807
+ children: "\u2190 Back to agents"
4808
+ }
4809
+ )
4810
+ ] });
4811
+ }
4812
+ const hasContent = events.length > 0 || running;
4813
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col h-[calc(100vh-6rem)]", children: [
4814
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3 mb-4", children: [
4815
+ /* @__PURE__ */ jsxRuntime.jsxs(
4816
+ LinkComponent,
4817
+ {
4818
+ href: `${basePath}/agents/${agentId}`,
4819
+ className: "text-sm text-muted-foreground hover:text-foreground",
4820
+ children: [
4821
+ "\u2190 ",
4822
+ agent.name
4823
+ ]
4824
+ }
4825
+ ),
4826
+ (sessionId || events.length > 0) && /* @__PURE__ */ jsxRuntime.jsx(chunk4XBBDUSZ_cjs.Button, { onClick: handleNewChat, variant: "outline", size: "sm", disabled: running, children: "New Chat" }),
4827
+ sessionId && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs text-muted-foreground font-mono", children: [
4828
+ "Session: ",
4829
+ sessionId.slice(0, 12),
4830
+ "\u2026"
4831
+ ] })
4832
+ ] }),
4833
+ hasContent && /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: scrollRef, className: "flex-1 min-h-0 overflow-y-auto rounded-lg border border-border bg-muted/20 p-4 space-y-4 mb-4", children: [
4834
+ events.map((ev, i) => renderEvent(ev, i)),
4835
+ streamingText && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1", children: [
4836
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-semibold text-blue-400 uppercase", children: "Assistant" }),
4837
+ /* @__PURE__ */ jsxRuntime.jsx(MarkdownContent, { children: streamingText }),
4838
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "inline-block w-0.5 h-4 bg-foreground animate-pulse align-text-bottom" })
4839
+ ] }),
4840
+ running && !streamingText && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 text-xs text-muted-foreground", children: [
4841
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "animate-pulse", children: "\u25CF" }),
4842
+ " ",
4843
+ polling ? "Reconnected, streaming updates\u2026" : "Running\u2026"
4844
+ ] })
4845
+ ] }),
4846
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2 shrink-0", children: [
4847
+ error && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-destructive", children: error }),
4848
+ /* @__PURE__ */ jsxRuntime.jsx(
4849
+ Textarea,
4850
+ {
4851
+ ref: textareaRef,
4852
+ placeholder: sessionId ? "Send a follow-up message\u2026" : "Enter your prompt\u2026",
4853
+ value: prompt,
4854
+ onChange: (e) => setPrompt(e.target.value),
4855
+ rows: hasContent ? 3 : 12,
4856
+ disabled: running,
4857
+ className: "font-mono text-sm resize-none",
4858
+ onKeyDown: (e) => {
4859
+ if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) handleSend();
4860
+ }
4861
+ }
4862
+ ),
4863
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
4864
+ /* @__PURE__ */ jsxRuntime.jsx(chunk4XBBDUSZ_cjs.Button, { onClick: handleSend, disabled: running || !prompt.trim(), size: "sm", children: running ? "Running\u2026" : sessionId ? "Send" : "Run" }),
4865
+ running && /* @__PURE__ */ jsxRuntime.jsx(chunk4XBBDUSZ_cjs.Button, { onClick: handleStop, variant: "outline", size: "sm", children: "Stop" }),
4866
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground ml-1", children: "\u2318+Enter to send" })
4867
+ ] })
4868
+ ] })
4869
+ ] });
4870
+ }
4389
4871
 
4390
4872
  Object.defineProperty(exports, "AgentPlaneProvider", {
4391
4873
  enumerable: true,
@@ -4486,6 +4968,7 @@ exports.McpServerListPage = McpServerListPage;
4486
4968
  exports.MetricCard = MetricCard;
4487
4969
  exports.ModelSelector = ModelSelector;
4488
4970
  exports.PaginationBar = PaginationBar;
4971
+ exports.PlaygroundPage = PlaygroundPage;
4489
4972
  exports.PluginDetailPage = PluginDetailPage;
4490
4973
  exports.PluginMarketplaceDetailPage = PluginMarketplaceDetailPage;
4491
4974
  exports.PluginMarketplaceListPage = PluginMarketplaceListPage;
package/dist/index.d.cts CHANGED
@@ -7,6 +7,52 @@ import * as class_variance_authority_types from 'class-variance-authority/types'
7
7
  import { VariantProps } from 'class-variance-authority';
8
8
  import { DailyAgentStat } from './charts.cjs';
9
9
 
10
+ /** Minimal stream event types used by the playground UI. */
11
+ interface PlaygroundTextDeltaEvent {
12
+ type: "text_delta";
13
+ text: string;
14
+ }
15
+ interface PlaygroundRunStartedEvent {
16
+ type: "run_started";
17
+ run_id: string;
18
+ agent_id: string;
19
+ model: string;
20
+ timestamp: string;
21
+ }
22
+ interface PlaygroundToolUseEvent {
23
+ type: "tool_use";
24
+ name?: string;
25
+ [key: string]: unknown;
26
+ }
27
+ interface PlaygroundToolResultEvent {
28
+ type: "tool_result";
29
+ [key: string]: unknown;
30
+ }
31
+ interface PlaygroundResultEvent {
32
+ type: "result";
33
+ subtype: string;
34
+ total_cost_usd?: number;
35
+ num_turns?: number;
36
+ duration_ms?: number;
37
+ }
38
+ interface PlaygroundErrorEvent {
39
+ type: "error";
40
+ error: string;
41
+ code?: string;
42
+ }
43
+ interface PlaygroundSessionCreatedEvent {
44
+ type: "session_created";
45
+ session_id: string;
46
+ }
47
+ type PlaygroundStreamEvent = PlaygroundTextDeltaEvent | PlaygroundRunStartedEvent | PlaygroundToolUseEvent | PlaygroundToolResultEvent | PlaygroundResultEvent | PlaygroundErrorEvent | PlaygroundSessionCreatedEvent | {
48
+ type: string;
49
+ [key: string]: unknown;
50
+ };
51
+ /** Async iterable stream of events (compatible with SDK RunStream). */
52
+ interface PlaygroundStream extends AsyncIterable<PlaygroundStreamEvent> {
53
+ run_id: string | null;
54
+ abort(reason?: unknown): void;
55
+ }
10
56
  /**
11
57
  * Structural interface for the AgentPlane SDK client.
12
58
  * Declares all methods the UI actually uses, avoiding a hard compile-time
@@ -47,6 +93,17 @@ interface AgentPlaneClient {
47
93
  list(params?: Record<string, unknown>): Promise<unknown>;
48
94
  get(sessionId: string): Promise<unknown>;
49
95
  stop(sessionId: string): Promise<unknown>;
96
+ create(params: {
97
+ agent_id: string;
98
+ prompt?: string;
99
+ }, options?: {
100
+ signal?: AbortSignal;
101
+ }): Promise<unknown | PlaygroundStream>;
102
+ sendMessage(sessionId: string, params: {
103
+ prompt: string;
104
+ }, options?: {
105
+ signal?: AbortSignal;
106
+ }): Promise<PlaygroundStream>;
50
107
  };
51
108
  connectors: {
52
109
  list(agentId: string): Promise<unknown[]>;
@@ -555,6 +612,8 @@ interface Agent {
555
612
  max_turns: number;
556
613
  max_budget_usd: number;
557
614
  max_runtime_seconds: number;
615
+ soul_md?: string | null;
616
+ identity_md?: string | null;
558
617
  }
559
618
  interface AgentEditFormProps {
560
619
  agent: Agent;
@@ -632,6 +691,11 @@ interface Props$1 {
632
691
  }
633
692
  declare function AgentA2aInfo({ agentId, tenantSlug, agentSlug, baseUrl, initialEnabled, initialTags, onChanged, }: Props$1): react_jsx_runtime.JSX.Element;
634
693
 
694
+ interface PlaygroundPageProps {
695
+ agentId: string;
696
+ }
697
+ declare function PlaygroundPage({ agentId }: PlaygroundPageProps): react_jsx_runtime.JSX.Element;
698
+
635
699
  interface ModelSelectorProps {
636
700
  value: string;
637
701
  onChange: (modelId: string) => void;
@@ -645,4 +709,4 @@ interface Props {
645
709
  }
646
710
  declare function ToolkitMultiselect({ value, onChange }: Props): react_jsx_runtime.JSX.Element;
647
711
 
648
- export { AdminTable, AdminTableHead, AdminTableRow, AgentA2aInfo, AgentConnectorsManager, AgentDetailPage, AgentEditForm, AgentListPage, type AgentPlaneClient, AgentPlaneProvider, type AgentPlaneProviderProps, AgentPluginManager, AgentRuns, AgentScheduleForm, AgentSkillManager, Badge, type BadgeProps, Button, type ButtonProps, Card, CardContent, CardDescription, CardHeader, CardTitle, ConfirmDialog, CopyButton, DashboardPage, type DashboardPageProps, DetailPageHeader, Dialog, DialogBody, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, EmptyRow, FormError, FormField, Input, type LinkComponentProps, LocalDate, McpServerListPage, type McpServerListPageProps, MetricCard, ModelSelector, type NavigationProps, PaginationBar, PluginDetailPage, type PluginDetailPageProps, PluginMarketplaceDetailPage, type PluginMarketplaceDetailPageProps, PluginMarketplaceListPage, type PluginMarketplaceListPageProps, RunDetailPage, type RunDetailPageProps, RunListPage, type RunListPageProps, RunSourceBadge, RunStatusBadge, SectionHeader, Select, SettingsPage, type SettingsPageProps, Skeleton, Tabs, Textarea, type TextareaProps, Th, ToolkitMultiselect, TranscriptViewer, badgeVariants, buttonVariants, cn, parsePaginationParams, useAgentPlaneClient, useApi, useAuthError, useNavigation };
712
+ export { AdminTable, AdminTableHead, AdminTableRow, AgentA2aInfo, AgentConnectorsManager, AgentDetailPage, AgentEditForm, AgentListPage, type AgentPlaneClient, AgentPlaneProvider, type AgentPlaneProviderProps, AgentPluginManager, AgentRuns, AgentScheduleForm, AgentSkillManager, Badge, type BadgeProps, Button, type ButtonProps, Card, CardContent, CardDescription, CardHeader, CardTitle, ConfirmDialog, CopyButton, DashboardPage, type DashboardPageProps, DetailPageHeader, Dialog, DialogBody, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, EmptyRow, FormError, FormField, Input, type LinkComponentProps, LocalDate, McpServerListPage, type McpServerListPageProps, MetricCard, ModelSelector, type NavigationProps, PaginationBar, PlaygroundPage, type PlaygroundPageProps, type PlaygroundStream, type PlaygroundStreamEvent, PluginDetailPage, type PluginDetailPageProps, PluginMarketplaceDetailPage, type PluginMarketplaceDetailPageProps, PluginMarketplaceListPage, type PluginMarketplaceListPageProps, RunDetailPage, type RunDetailPageProps, RunListPage, type RunListPageProps, RunSourceBadge, RunStatusBadge, SectionHeader, Select, SettingsPage, type SettingsPageProps, Skeleton, Tabs, Textarea, type TextareaProps, Th, ToolkitMultiselect, TranscriptViewer, badgeVariants, buttonVariants, cn, parsePaginationParams, useAgentPlaneClient, useApi, useAuthError, useNavigation };
package/dist/index.d.ts CHANGED
@@ -7,6 +7,52 @@ import * as class_variance_authority_types from 'class-variance-authority/types'
7
7
  import { VariantProps } from 'class-variance-authority';
8
8
  import { DailyAgentStat } from './charts.js';
9
9
 
10
+ /** Minimal stream event types used by the playground UI. */
11
+ interface PlaygroundTextDeltaEvent {
12
+ type: "text_delta";
13
+ text: string;
14
+ }
15
+ interface PlaygroundRunStartedEvent {
16
+ type: "run_started";
17
+ run_id: string;
18
+ agent_id: string;
19
+ model: string;
20
+ timestamp: string;
21
+ }
22
+ interface PlaygroundToolUseEvent {
23
+ type: "tool_use";
24
+ name?: string;
25
+ [key: string]: unknown;
26
+ }
27
+ interface PlaygroundToolResultEvent {
28
+ type: "tool_result";
29
+ [key: string]: unknown;
30
+ }
31
+ interface PlaygroundResultEvent {
32
+ type: "result";
33
+ subtype: string;
34
+ total_cost_usd?: number;
35
+ num_turns?: number;
36
+ duration_ms?: number;
37
+ }
38
+ interface PlaygroundErrorEvent {
39
+ type: "error";
40
+ error: string;
41
+ code?: string;
42
+ }
43
+ interface PlaygroundSessionCreatedEvent {
44
+ type: "session_created";
45
+ session_id: string;
46
+ }
47
+ type PlaygroundStreamEvent = PlaygroundTextDeltaEvent | PlaygroundRunStartedEvent | PlaygroundToolUseEvent | PlaygroundToolResultEvent | PlaygroundResultEvent | PlaygroundErrorEvent | PlaygroundSessionCreatedEvent | {
48
+ type: string;
49
+ [key: string]: unknown;
50
+ };
51
+ /** Async iterable stream of events (compatible with SDK RunStream). */
52
+ interface PlaygroundStream extends AsyncIterable<PlaygroundStreamEvent> {
53
+ run_id: string | null;
54
+ abort(reason?: unknown): void;
55
+ }
10
56
  /**
11
57
  * Structural interface for the AgentPlane SDK client.
12
58
  * Declares all methods the UI actually uses, avoiding a hard compile-time
@@ -47,6 +93,17 @@ interface AgentPlaneClient {
47
93
  list(params?: Record<string, unknown>): Promise<unknown>;
48
94
  get(sessionId: string): Promise<unknown>;
49
95
  stop(sessionId: string): Promise<unknown>;
96
+ create(params: {
97
+ agent_id: string;
98
+ prompt?: string;
99
+ }, options?: {
100
+ signal?: AbortSignal;
101
+ }): Promise<unknown | PlaygroundStream>;
102
+ sendMessage(sessionId: string, params: {
103
+ prompt: string;
104
+ }, options?: {
105
+ signal?: AbortSignal;
106
+ }): Promise<PlaygroundStream>;
50
107
  };
51
108
  connectors: {
52
109
  list(agentId: string): Promise<unknown[]>;
@@ -555,6 +612,8 @@ interface Agent {
555
612
  max_turns: number;
556
613
  max_budget_usd: number;
557
614
  max_runtime_seconds: number;
615
+ soul_md?: string | null;
616
+ identity_md?: string | null;
558
617
  }
559
618
  interface AgentEditFormProps {
560
619
  agent: Agent;
@@ -632,6 +691,11 @@ interface Props$1 {
632
691
  }
633
692
  declare function AgentA2aInfo({ agentId, tenantSlug, agentSlug, baseUrl, initialEnabled, initialTags, onChanged, }: Props$1): react_jsx_runtime.JSX.Element;
634
693
 
694
+ interface PlaygroundPageProps {
695
+ agentId: string;
696
+ }
697
+ declare function PlaygroundPage({ agentId }: PlaygroundPageProps): react_jsx_runtime.JSX.Element;
698
+
635
699
  interface ModelSelectorProps {
636
700
  value: string;
637
701
  onChange: (modelId: string) => void;
@@ -645,4 +709,4 @@ interface Props {
645
709
  }
646
710
  declare function ToolkitMultiselect({ value, onChange }: Props): react_jsx_runtime.JSX.Element;
647
711
 
648
- export { AdminTable, AdminTableHead, AdminTableRow, AgentA2aInfo, AgentConnectorsManager, AgentDetailPage, AgentEditForm, AgentListPage, type AgentPlaneClient, AgentPlaneProvider, type AgentPlaneProviderProps, AgentPluginManager, AgentRuns, AgentScheduleForm, AgentSkillManager, Badge, type BadgeProps, Button, type ButtonProps, Card, CardContent, CardDescription, CardHeader, CardTitle, ConfirmDialog, CopyButton, DashboardPage, type DashboardPageProps, DetailPageHeader, Dialog, DialogBody, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, EmptyRow, FormError, FormField, Input, type LinkComponentProps, LocalDate, McpServerListPage, type McpServerListPageProps, MetricCard, ModelSelector, type NavigationProps, PaginationBar, PluginDetailPage, type PluginDetailPageProps, PluginMarketplaceDetailPage, type PluginMarketplaceDetailPageProps, PluginMarketplaceListPage, type PluginMarketplaceListPageProps, RunDetailPage, type RunDetailPageProps, RunListPage, type RunListPageProps, RunSourceBadge, RunStatusBadge, SectionHeader, Select, SettingsPage, type SettingsPageProps, Skeleton, Tabs, Textarea, type TextareaProps, Th, ToolkitMultiselect, TranscriptViewer, badgeVariants, buttonVariants, cn, parsePaginationParams, useAgentPlaneClient, useApi, useAuthError, useNavigation };
712
+ export { AdminTable, AdminTableHead, AdminTableRow, AgentA2aInfo, AgentConnectorsManager, AgentDetailPage, AgentEditForm, AgentListPage, type AgentPlaneClient, AgentPlaneProvider, type AgentPlaneProviderProps, AgentPluginManager, AgentRuns, AgentScheduleForm, AgentSkillManager, Badge, type BadgeProps, Button, type ButtonProps, Card, CardContent, CardDescription, CardHeader, CardTitle, ConfirmDialog, CopyButton, DashboardPage, type DashboardPageProps, DetailPageHeader, Dialog, DialogBody, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, EmptyRow, FormError, FormField, Input, type LinkComponentProps, LocalDate, McpServerListPage, type McpServerListPageProps, MetricCard, ModelSelector, type NavigationProps, PaginationBar, PlaygroundPage, type PlaygroundPageProps, type PlaygroundStream, type PlaygroundStreamEvent, PluginDetailPage, type PluginDetailPageProps, PluginMarketplaceDetailPage, type PluginMarketplaceDetailPageProps, PluginMarketplaceListPage, type PluginMarketplaceListPageProps, RunDetailPage, type RunDetailPageProps, RunListPage, type RunListPageProps, RunSourceBadge, RunStatusBadge, SectionHeader, Select, SettingsPage, type SettingsPageProps, Skeleton, Tabs, Textarea, type TextareaProps, Th, ToolkitMultiselect, TranscriptViewer, badgeVariants, buttonVariants, cn, parsePaginationParams, useAgentPlaneClient, useApi, useAuthError, useNavigation };
package/dist/index.js CHANGED
@@ -7,6 +7,7 @@ import { useSWRConfig } from 'swr';
7
7
  import ReactMarkdown from 'react-markdown';
8
8
  import { Command } from 'cmdk';
9
9
  import * as Popover from '@radix-ui/react-popover';
10
+ import remarkGfm from 'remark-gfm';
10
11
 
11
12
  function Select({ className = "", ...props }) {
12
13
  return /* @__PURE__ */ jsxs("div", { className: "relative w-full", children: [
@@ -2504,8 +2505,10 @@ function AgentEditForm({ agent, onSaved }) {
2504
2505
  const [maxTurns, setMaxTurns] = useState(agent.max_turns.toString());
2505
2506
  const [maxBudget, setMaxBudget] = useState(agent.max_budget_usd.toString());
2506
2507
  const [maxRuntime, setMaxRuntime] = useState(Math.floor(agent.max_runtime_seconds / 60).toString());
2508
+ const [soulMd, setSoulMd] = useState(agent.soul_md ?? "");
2509
+ const [identityMd, setIdentityMd] = useState(agent.identity_md ?? "");
2507
2510
  const [saving, setSaving] = useState(false);
2508
- const isDirty = name !== agent.name || description !== (agent.description ?? "") || model !== agent.model || runner !== (agent.runner ?? "") || permissionMode !== agent.permission_mode || maxTurns !== agent.max_turns.toString() || maxBudget !== agent.max_budget_usd.toString() || maxRuntime !== Math.floor(agent.max_runtime_seconds / 60).toString();
2511
+ const isDirty = name !== agent.name || description !== (agent.description ?? "") || model !== agent.model || runner !== (agent.runner ?? "") || permissionMode !== agent.permission_mode || maxTurns !== agent.max_turns.toString() || maxBudget !== agent.max_budget_usd.toString() || maxRuntime !== Math.floor(agent.max_runtime_seconds / 60).toString() || soulMd !== (agent.soul_md ?? "") || identityMd !== (agent.identity_md ?? "");
2509
2512
  const [error, setError] = useState("");
2510
2513
  async function handleSave() {
2511
2514
  setSaving(true);
@@ -2519,7 +2522,9 @@ function AgentEditForm({ agent, onSaved }) {
2519
2522
  permission_mode: permissionMode,
2520
2523
  max_turns: parseInt(maxTurns) || agent.max_turns,
2521
2524
  max_budget_usd: parseFloat(maxBudget) || agent.max_budget_usd,
2522
- max_runtime_seconds: (parseInt(maxRuntime) || Math.floor(agent.max_runtime_seconds / 60)) * 60
2525
+ max_runtime_seconds: (parseInt(maxRuntime) || Math.floor(agent.max_runtime_seconds / 60)) * 60,
2526
+ soul_md: soulMd || null,
2527
+ identity_md: identityMd || null
2523
2528
  });
2524
2529
  onSaved?.();
2525
2530
  } catch (err) {
@@ -2581,7 +2586,46 @@ function AgentEditForm({ agent, onSaved }) {
2581
2586
  /* @__PURE__ */ jsx("option", { value: "plan", children: "plan" })
2582
2587
  ] }) }) })
2583
2588
  ] })
2584
- ] }) })
2589
+ ] }) }),
2590
+ /* @__PURE__ */ jsxs("div", { className: "rounded-lg border border-muted-foreground/25 p-5", children: [
2591
+ /* @__PURE__ */ jsx(SectionHeader, { title: "Identity" }),
2592
+ /* @__PURE__ */ jsx("div", { className: "space-y-4", children: /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
2593
+ /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsxs(FormField, { label: "SOUL.md", children: [
2594
+ /* @__PURE__ */ jsx(
2595
+ Textarea,
2596
+ {
2597
+ value: soulMd,
2598
+ onChange: (e) => setSoulMd(e.target.value),
2599
+ placeholder: "## Voice & Tone\nDirect, concise, technical.\n\n## Values\nClarity over completeness.\n\n## Stance\nProactive problem-solver.\n\n## Boundaries\n- Never modify production data directly\n\n## Essence\nA focused engineering assistant.",
2600
+ rows: 10,
2601
+ className: "font-mono text-sm",
2602
+ disabled: saving
2603
+ }
2604
+ ),
2605
+ /* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground mt-1 block", children: [
2606
+ soulMd.split(/\s+/).filter(Boolean).length,
2607
+ " words"
2608
+ ] })
2609
+ ] }) }),
2610
+ /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsxs(FormField, { label: "IDENTITY.md", children: [
2611
+ /* @__PURE__ */ jsx(
2612
+ Textarea,
2613
+ {
2614
+ value: identityMd,
2615
+ onChange: (e) => setIdentityMd(e.target.value),
2616
+ placeholder: "- **Communication Verbosity:** concise\n- **Communication Tone:** direct\n- **Decision Autonomy:** high\n- **Risk Tolerance:** moderate\n- **Collaboration Mode:** autonomous\n\n## Escalation Preferences\n- Budget over $50 -> escalate\n- Breaking changes -> escalate",
2617
+ rows: 10,
2618
+ className: "font-mono text-sm",
2619
+ disabled: saving
2620
+ }
2621
+ ),
2622
+ /* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground mt-1 block", children: [
2623
+ identityMd.split(/\s+/).filter(Boolean).length,
2624
+ " words"
2625
+ ] })
2626
+ ] }) })
2627
+ ] }) })
2628
+ ] })
2585
2629
  ] });
2586
2630
  }
2587
2631
  function ToolkitMultiselect({ value, onChange }) {
@@ -4362,5 +4406,442 @@ function ScheduleCard({
4362
4406
  ] })
4363
4407
  ] });
4364
4408
  }
4409
+ function MarkdownContent({ children }) {
4410
+ return /* @__PURE__ */ jsx("div", { className: "text-sm text-foreground [&_p]:my-1 [&_h1]:text-lg [&_h1]:font-bold [&_h1]:my-2 [&_h2]:text-base [&_h2]:font-semibold [&_h2]:my-2 [&_h3]:text-sm [&_h3]:font-semibold [&_h3]:my-1 [&_ul]:list-disc [&_ul]:pl-5 [&_ul]:my-1 [&_ol]:list-decimal [&_ol]:pl-5 [&_ol]:my-1 [&_li]:my-0.5 [&_pre]:bg-muted [&_pre]:rounded-md [&_pre]:p-3 [&_pre]:overflow-x-auto [&_pre]:text-xs [&_pre]:my-2 [&_code:not(pre_code)]:bg-muted [&_code:not(pre_code)]:px-1 [&_code:not(pre_code)]:py-0.5 [&_code:not(pre_code)]:rounded [&_code:not(pre_code)]:text-xs [&_code:not(pre_code)]:font-mono [&_a]:text-blue-400 [&_a]:underline [&_blockquote]:border-l-2 [&_blockquote]:border-border [&_blockquote]:pl-3 [&_blockquote]:text-muted-foreground [&_blockquote]:my-2 [&_hr]:border-border [&_hr]:my-3 [&_table]:border-collapse [&_table]:text-xs [&_table]:w-full [&_th]:border [&_th]:border-border [&_th]:px-2 [&_th]:py-1 [&_th]:text-left [&_th]:font-semibold [&_th]:bg-muted [&_td]:border [&_td]:border-border [&_td]:px-2 [&_td]:py-1 [&_strong]:font-semibold [&_em]:italic", children: /* @__PURE__ */ jsx(
4411
+ ReactMarkdown,
4412
+ {
4413
+ remarkPlugins: [remarkGfm],
4414
+ components: {
4415
+ a: ({ href, children: linkChildren }) => /* @__PURE__ */ jsx("a", { href, target: "_blank", rel: "noopener noreferrer", children: linkChildren })
4416
+ },
4417
+ children
4418
+ }
4419
+ ) });
4420
+ }
4421
+ function CollapsibleJson({ data, maxHeight = "12rem" }) {
4422
+ const [expanded, setExpanded] = useState(false);
4423
+ const json = typeof data === "string" ? data : JSON.stringify(data, null, 2);
4424
+ return /* @__PURE__ */ jsxs("div", { className: "relative", children: [
4425
+ /* @__PURE__ */ jsx(
4426
+ "pre",
4427
+ {
4428
+ className: `text-xs font-mono text-muted-foreground bg-muted/50 rounded-md p-2 overflow-x-auto whitespace-pre-wrap break-all ${expanded ? "" : "overflow-hidden"}`,
4429
+ style: expanded ? void 0 : { maxHeight },
4430
+ children: json
4431
+ }
4432
+ ),
4433
+ json.length > 200 && /* @__PURE__ */ jsx(
4434
+ "button",
4435
+ {
4436
+ onClick: () => setExpanded(!expanded),
4437
+ className: "text-xs text-blue-400 hover:text-blue-300 mt-1",
4438
+ children: expanded ? "Collapse" : "Expand"
4439
+ }
4440
+ )
4441
+ ] });
4442
+ }
4443
+ function renderEvent(event, idx) {
4444
+ if (event.type === "heartbeat") return null;
4445
+ if (event.type === "text_delta") return null;
4446
+ if (event.type === "session_created") return null;
4447
+ if (event.type === "session_info") return null;
4448
+ if (event.type === "mcp_status") return null;
4449
+ if (event.type === "rate_limit_event") return null;
4450
+ if (event.type === "user_message") {
4451
+ return /* @__PURE__ */ jsxs("div", { className: "border-t border-border pt-3 mt-1", children: [
4452
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-semibold text-emerald-400 uppercase", children: "You" }),
4453
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-foreground mt-1 whitespace-pre-wrap", children: String(event.text) })
4454
+ ] }, idx);
4455
+ }
4456
+ if (event.type === "assistant") {
4457
+ const content = event.message;
4458
+ const blocks = content?.content ?? [];
4459
+ const textBlocks = blocks.filter((c) => c.type === "text").map((c) => c.text).join("");
4460
+ const toolUseBlocks = blocks.filter((c) => c.type === "tool_use");
4461
+ if (!textBlocks && toolUseBlocks.length === 0) return null;
4462
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
4463
+ textBlocks && /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
4464
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-semibold text-blue-400 uppercase", children: "Assistant" }),
4465
+ /* @__PURE__ */ jsx(MarkdownContent, { children: textBlocks })
4466
+ ] }),
4467
+ toolUseBlocks.map((tool, ti) => /* @__PURE__ */ jsxs("div", { className: "space-y-1 ml-3 pl-3 border-l-2 border-yellow-800/50", children: [
4468
+ /* @__PURE__ */ jsxs("span", { className: "text-xs font-semibold text-yellow-400 uppercase", children: [
4469
+ "Tool Call: ",
4470
+ tool.name ?? "unknown"
4471
+ ] }),
4472
+ tool.id && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground ml-2 font-mono", children: String(tool.id) }),
4473
+ tool.input != null && /* @__PURE__ */ jsx(CollapsibleJson, { data: tool.input })
4474
+ ] }, ti))
4475
+ ] }, idx);
4476
+ }
4477
+ if (event.type === "tool_use") {
4478
+ const toolName = String(event.tool_name ?? event.name ?? "unknown");
4479
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-1 ml-3 pl-3 border-l-2 border-yellow-800/50", children: [
4480
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
4481
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-semibold text-yellow-400 uppercase", children: "Tool Call" }),
4482
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-mono text-yellow-400/80", children: toolName }),
4483
+ event.tool_use_id ? /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground font-mono", children: String(event.tool_use_id) }) : null
4484
+ ] }),
4485
+ event.input != null ? /* @__PURE__ */ jsx(CollapsibleJson, { data: event.input }) : null
4486
+ ] }, idx);
4487
+ }
4488
+ if (event.type === "tool_result") {
4489
+ const isError = event.is_error === true || event.error === true;
4490
+ const content = event.output ?? event.content ?? "";
4491
+ const contentStr = typeof content === "string" ? content : JSON.stringify(content, null, 2);
4492
+ return /* @__PURE__ */ jsxs("div", { className: `space-y-1 ml-3 pl-3 border-l-2 ${isError ? "border-red-800/50" : "border-green-800/50"}`, children: [
4493
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
4494
+ /* @__PURE__ */ jsx("span", { className: `text-xs font-semibold uppercase ${isError ? "text-red-400" : "text-green-400"}`, children: isError ? "Tool Error" : "Tool Result" }),
4495
+ event.tool_name ? /* @__PURE__ */ jsx("span", { className: "text-xs font-mono text-muted-foreground", children: String(event.tool_name) }) : null,
4496
+ event.tool_use_id ? /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground font-mono", children: String(event.tool_use_id) }) : null
4497
+ ] }),
4498
+ contentStr ? /* @__PURE__ */ jsx(CollapsibleJson, { data: contentStr }) : null
4499
+ ] }, idx);
4500
+ }
4501
+ if (event.type === "result") {
4502
+ const success = event.subtype === "success";
4503
+ const costUsd = event.cost_usd ?? event.total_cost_usd;
4504
+ return /* @__PURE__ */ jsxs("div", { className: `rounded-md px-3 py-2 flex items-center gap-3 ${success ? "bg-green-950 border border-green-900" : "bg-red-950 border border-red-900"}`, children: [
4505
+ /* @__PURE__ */ jsx("span", { className: `text-xs font-semibold ${success ? "text-green-400" : "text-red-400"}`, children: success ? "Completed" : "Failed" }),
4506
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-3 text-xs text-zinc-400", children: [
4507
+ event.num_turns != null && /* @__PURE__ */ jsxs("span", { children: [
4508
+ String(event.num_turns),
4509
+ " turns"
4510
+ ] }),
4511
+ costUsd != null && Number(costUsd) > 0 && /* @__PURE__ */ jsxs("span", { children: [
4512
+ "$",
4513
+ Number(costUsd).toFixed(4)
4514
+ ] }),
4515
+ event.duration_ms != null && /* @__PURE__ */ jsxs("span", { children: [
4516
+ (Number(event.duration_ms) / 1e3).toFixed(1),
4517
+ "s"
4518
+ ] }),
4519
+ event.duration_api_ms != null && /* @__PURE__ */ jsxs("span", { children: [
4520
+ "API: ",
4521
+ (Number(event.duration_api_ms) / 1e3).toFixed(1),
4522
+ "s"
4523
+ ] })
4524
+ ] })
4525
+ ] }, idx);
4526
+ }
4527
+ if (event.type === "error") {
4528
+ return /* @__PURE__ */ jsxs("div", { className: "rounded-md p-3 bg-red-950 border border-red-800", children: [
4529
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
4530
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-semibold text-red-400", children: "Error" }),
4531
+ event.code ? /* @__PURE__ */ jsx("span", { className: "text-xs font-mono text-red-400/70", children: String(event.code) }) : null
4532
+ ] }),
4533
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-foreground mt-1", children: String(event.error ?? "Unknown error") })
4534
+ ] }, idx);
4535
+ }
4536
+ if (event.type === "stream_detached") {
4537
+ return /* @__PURE__ */ jsxs("div", { className: "text-xs text-muted-foreground italic border-t border-border pt-2", children: [
4538
+ "Stream detached at ",
4539
+ event.timestamp ? new Date(String(event.timestamp)).toLocaleTimeString() : "unknown",
4540
+ " \u2014 run continues in background"
4541
+ ] }, idx);
4542
+ }
4543
+ if (event.type === "queued") {
4544
+ return /* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground", children: "Queued\u2026" }, idx);
4545
+ }
4546
+ if (event.type === "sandbox_starting") {
4547
+ return /* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground", children: "Starting sandbox\u2026" }, idx);
4548
+ }
4549
+ if (event.type === "run_started") {
4550
+ return /* @__PURE__ */ jsxs("div", { className: "text-xs text-muted-foreground", children: [
4551
+ "Agent started",
4552
+ event.model ? /* @__PURE__ */ jsx("span", { className: "ml-2 font-mono text-foreground/60", children: String(event.model) }) : null,
4553
+ event.mcp_server_count != null && Number(event.mcp_server_count) > 0 && /* @__PURE__ */ jsxs("span", { className: "ml-2", children: [
4554
+ String(event.mcp_server_count),
4555
+ " MCP server",
4556
+ Number(event.mcp_server_count) !== 1 ? "s" : ""
4557
+ ] })
4558
+ ] }, idx);
4559
+ }
4560
+ if (event.type === "system") {
4561
+ return /* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground italic", children: String(event.message ?? JSON.stringify(event)) }, idx);
4562
+ }
4563
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
4564
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-semibold text-purple-400 uppercase", children: event.type }),
4565
+ /* @__PURE__ */ jsx(CollapsibleJson, { data: event, maxHeight: "8rem" })
4566
+ ] }, idx);
4567
+ }
4568
+ var TERMINAL_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "cancelled", "timed_out"]);
4569
+ function PlaygroundPage({ agentId }) {
4570
+ const client = useAgentPlaneClient();
4571
+ const { LinkComponent, basePath } = useNavigation();
4572
+ const { data: agent, error: agentError, isLoading } = useApi(
4573
+ `agent-${agentId}`,
4574
+ (c) => c.agents.get(agentId)
4575
+ );
4576
+ const [prompt, setPrompt] = useState("");
4577
+ const [events, setEvents] = useState([]);
4578
+ const [streamingText, setStreamingText] = useState("");
4579
+ const [running, setRunning] = useState(false);
4580
+ const [polling, setPolling] = useState(false);
4581
+ const [error, setError] = useState(null);
4582
+ const [sessionId, setSessionId] = useState(null);
4583
+ const sessionIdRef = useRef(null);
4584
+ const abortRef = useRef(null);
4585
+ const runIdRef = useRef(null);
4586
+ const streamRef = useRef(null);
4587
+ const scrollRef = useRef(null);
4588
+ const textareaRef = useRef(null);
4589
+ useEffect(() => {
4590
+ const el = scrollRef.current;
4591
+ if (el) el.scrollTop = el.scrollHeight;
4592
+ }, [events, streamingText]);
4593
+ useEffect(() => {
4594
+ return () => {
4595
+ abortRef.current?.abort();
4596
+ streamRef.current?.abort();
4597
+ };
4598
+ }, []);
4599
+ const pollForFinalResult = useCallback(async (runId) => {
4600
+ setPolling(true);
4601
+ let delay = 3e3;
4602
+ const maxDelay = 1e4;
4603
+ try {
4604
+ while (true) {
4605
+ if (abortRef.current?.signal.aborted) break;
4606
+ await new Promise((r) => setTimeout(r, delay));
4607
+ if (abortRef.current?.signal.aborted) break;
4608
+ try {
4609
+ const run = await client.runs.get(runId);
4610
+ if (TERMINAL_STATUSES.has(run.status)) {
4611
+ try {
4612
+ const transcriptEvents = await client.runs.transcriptArray(runId);
4613
+ if (transcriptEvents.length > 0) {
4614
+ const detachIdx = transcriptEvents.findIndex((ev) => ev.type === "stream_detached");
4615
+ const eventsAfterDetach = detachIdx >= 0 ? transcriptEvents.slice(detachIdx + 1) : [];
4616
+ const newEvents = eventsAfterDetach.filter(
4617
+ (ev) => ev.type !== "heartbeat" && ev.type !== "text_delta" && ev.type !== "run_started" && ev.type !== "queued" && ev.type !== "sandbox_starting"
4618
+ );
4619
+ if (newEvents.length > 0) {
4620
+ setEvents((prev) => [...prev, ...newEvents]);
4621
+ }
4622
+ }
4623
+ } catch {
4624
+ }
4625
+ setEvents((prev) => {
4626
+ if (prev.some((ev) => ev.type === "result")) return prev;
4627
+ const syntheticResult = {
4628
+ type: "result",
4629
+ subtype: run.status === "completed" ? "success" : "failed",
4630
+ cost_usd: run.cost_usd,
4631
+ num_turns: run.num_turns,
4632
+ duration_ms: run.duration_ms
4633
+ };
4634
+ if (run.error_type) {
4635
+ syntheticResult.result = run.error_type;
4636
+ }
4637
+ return [...prev, syntheticResult];
4638
+ });
4639
+ break;
4640
+ }
4641
+ delay = Math.min(delay * 2, maxDelay);
4642
+ } catch (err) {
4643
+ if (err?.name === "AbortError") break;
4644
+ delay = Math.min(delay * 2, maxDelay);
4645
+ }
4646
+ }
4647
+ } finally {
4648
+ setPolling(false);
4649
+ setRunning(false);
4650
+ abortRef.current = null;
4651
+ runIdRef.current = null;
4652
+ streamRef.current = null;
4653
+ }
4654
+ }, [client]);
4655
+ const consumeStream = useCallback(async (stream) => {
4656
+ streamRef.current = stream;
4657
+ let handedOffToPoll = false;
4658
+ try {
4659
+ for await (const event of stream) {
4660
+ const ev = event;
4661
+ if (ev.type === "session_created" && ev.session_id) {
4662
+ const sid = ev.session_id;
4663
+ sessionIdRef.current = sid;
4664
+ setSessionId(sid);
4665
+ }
4666
+ if (ev.type === "run_started" && ev.run_id) {
4667
+ runIdRef.current = ev.run_id;
4668
+ }
4669
+ if (ev.type === "text_delta") {
4670
+ setStreamingText((prev) => prev + (ev.text ?? ""));
4671
+ } else if (ev.type === "stream_detached") {
4672
+ setStreamingText("");
4673
+ setEvents((prev) => [...prev, ev]);
4674
+ if (runIdRef.current) {
4675
+ handedOffToPoll = true;
4676
+ pollForFinalResult(runIdRef.current);
4677
+ return;
4678
+ }
4679
+ } else {
4680
+ if (ev.type === "assistant") setStreamingText("");
4681
+ setEvents((prev) => [...prev, ev]);
4682
+ }
4683
+ }
4684
+ } finally {
4685
+ if (!handedOffToPoll) {
4686
+ setRunning(false);
4687
+ abortRef.current = null;
4688
+ runIdRef.current = null;
4689
+ streamRef.current = null;
4690
+ }
4691
+ }
4692
+ }, [pollForFinalResult]);
4693
+ const handleSend = useCallback(async () => {
4694
+ if (!prompt.trim() || running) return;
4695
+ const messageText = prompt.trim();
4696
+ setPrompt("");
4697
+ setRunning(true);
4698
+ setStreamingText("");
4699
+ setError(null);
4700
+ setPolling(false);
4701
+ setEvents((prev) => [...prev, { type: "user_message", text: messageText }]);
4702
+ const abort = new AbortController();
4703
+ abortRef.current = abort;
4704
+ try {
4705
+ let stream;
4706
+ const currentSessionId = sessionIdRef.current;
4707
+ if (currentSessionId) {
4708
+ stream = await client.sessions.sendMessage(
4709
+ currentSessionId,
4710
+ { prompt: messageText },
4711
+ { signal: abort.signal }
4712
+ );
4713
+ } else {
4714
+ stream = await client.sessions.create(
4715
+ { agent_id: agentId, prompt: messageText },
4716
+ { signal: abort.signal }
4717
+ );
4718
+ }
4719
+ await consumeStream(stream);
4720
+ } catch (err) {
4721
+ if (err?.name !== "AbortError") {
4722
+ const msg = err instanceof Error ? err.message : "Unknown error";
4723
+ setError(msg);
4724
+ }
4725
+ setRunning(false);
4726
+ abortRef.current = null;
4727
+ runIdRef.current = null;
4728
+ streamRef.current = null;
4729
+ }
4730
+ }, [prompt, running, agentId, client, consumeStream]);
4731
+ function handleNewChat() {
4732
+ abortRef.current?.abort();
4733
+ streamRef.current?.abort();
4734
+ if (sessionId) {
4735
+ client.sessions.stop(sessionId).catch(() => {
4736
+ });
4737
+ }
4738
+ sessionIdRef.current = null;
4739
+ setSessionId(null);
4740
+ setEvents([]);
4741
+ setStreamingText("");
4742
+ setRunning(false);
4743
+ setPolling(false);
4744
+ setError(null);
4745
+ setPrompt("");
4746
+ runIdRef.current = null;
4747
+ abortRef.current = null;
4748
+ streamRef.current = null;
4749
+ textareaRef.current?.focus();
4750
+ }
4751
+ function handleStop() {
4752
+ abortRef.current?.abort();
4753
+ streamRef.current?.abort();
4754
+ const id = runIdRef.current;
4755
+ if (id) {
4756
+ runIdRef.current = null;
4757
+ client.runs.cancel(id).catch(() => {
4758
+ });
4759
+ }
4760
+ }
4761
+ if (isLoading) {
4762
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col h-[calc(100vh-6rem)]", children: [
4763
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 mb-4", children: [
4764
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-8 w-24" }),
4765
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-40" })
4766
+ ] }),
4767
+ /* @__PURE__ */ jsx(Skeleton, { className: "flex-1 rounded-lg" }),
4768
+ /* @__PURE__ */ jsxs("div", { className: "mt-4 space-y-2", children: [
4769
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-24 w-full rounded-lg" }),
4770
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-8 w-20" })
4771
+ ] })
4772
+ ] });
4773
+ }
4774
+ if (agentError || !agent) {
4775
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center h-[calc(100vh-6rem)] gap-3", children: [
4776
+ /* @__PURE__ */ jsx("p", { className: "text-destructive text-sm", children: agentError?.status === 404 ? "Agent not found." : `Failed to load agent: ${agentError?.message ?? "Unknown error"}` }),
4777
+ /* @__PURE__ */ jsx(
4778
+ LinkComponent,
4779
+ {
4780
+ href: `${basePath}/agents`,
4781
+ className: "text-sm text-muted-foreground hover:text-foreground",
4782
+ children: "\u2190 Back to agents"
4783
+ }
4784
+ )
4785
+ ] });
4786
+ }
4787
+ const hasContent = events.length > 0 || running;
4788
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col h-[calc(100vh-6rem)]", children: [
4789
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 mb-4", children: [
4790
+ /* @__PURE__ */ jsxs(
4791
+ LinkComponent,
4792
+ {
4793
+ href: `${basePath}/agents/${agentId}`,
4794
+ className: "text-sm text-muted-foreground hover:text-foreground",
4795
+ children: [
4796
+ "\u2190 ",
4797
+ agent.name
4798
+ ]
4799
+ }
4800
+ ),
4801
+ (sessionId || events.length > 0) && /* @__PURE__ */ jsx(Button, { onClick: handleNewChat, variant: "outline", size: "sm", disabled: running, children: "New Chat" }),
4802
+ sessionId && /* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground font-mono", children: [
4803
+ "Session: ",
4804
+ sessionId.slice(0, 12),
4805
+ "\u2026"
4806
+ ] })
4807
+ ] }),
4808
+ hasContent && /* @__PURE__ */ jsxs("div", { ref: scrollRef, className: "flex-1 min-h-0 overflow-y-auto rounded-lg border border-border bg-muted/20 p-4 space-y-4 mb-4", children: [
4809
+ events.map((ev, i) => renderEvent(ev, i)),
4810
+ streamingText && /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
4811
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-semibold text-blue-400 uppercase", children: "Assistant" }),
4812
+ /* @__PURE__ */ jsx(MarkdownContent, { children: streamingText }),
4813
+ /* @__PURE__ */ jsx("span", { className: "inline-block w-0.5 h-4 bg-foreground animate-pulse align-text-bottom" })
4814
+ ] }),
4815
+ running && !streamingText && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-xs text-muted-foreground", children: [
4816
+ /* @__PURE__ */ jsx("span", { className: "animate-pulse", children: "\u25CF" }),
4817
+ " ",
4818
+ polling ? "Reconnected, streaming updates\u2026" : "Running\u2026"
4819
+ ] })
4820
+ ] }),
4821
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2 shrink-0", children: [
4822
+ error && /* @__PURE__ */ jsx("p", { className: "text-sm text-destructive", children: error }),
4823
+ /* @__PURE__ */ jsx(
4824
+ Textarea,
4825
+ {
4826
+ ref: textareaRef,
4827
+ placeholder: sessionId ? "Send a follow-up message\u2026" : "Enter your prompt\u2026",
4828
+ value: prompt,
4829
+ onChange: (e) => setPrompt(e.target.value),
4830
+ rows: hasContent ? 3 : 12,
4831
+ disabled: running,
4832
+ className: "font-mono text-sm resize-none",
4833
+ onKeyDown: (e) => {
4834
+ if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) handleSend();
4835
+ }
4836
+ }
4837
+ ),
4838
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
4839
+ /* @__PURE__ */ jsx(Button, { onClick: handleSend, disabled: running || !prompt.trim(), size: "sm", children: running ? "Running\u2026" : sessionId ? "Send" : "Run" }),
4840
+ running && /* @__PURE__ */ jsx(Button, { onClick: handleStop, variant: "outline", size: "sm", children: "Stop" }),
4841
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground ml-1", children: "\u2318+Enter to send" })
4842
+ ] })
4843
+ ] })
4844
+ ] });
4845
+ }
4365
4846
 
4366
- export { AdminTable, AdminTableHead, AdminTableRow, AgentA2aInfo, AgentConnectorsManager, AgentDetailPage, AgentEditForm, AgentListPage, AgentPluginManager, AgentRuns, AgentScheduleForm, AgentSkillManager, ConfirmDialog, CopyButton, DashboardPage, DetailPageHeader, Dialog, DialogBody, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, EmptyRow, FormError, FormField, LocalDate, McpServerListPage, MetricCard, ModelSelector, PaginationBar, PluginDetailPage, PluginMarketplaceDetailPage, PluginMarketplaceListPage, RunDetailPage, RunListPage, RunSourceBadge, RunStatusBadge, SectionHeader, Select, SettingsPage, Tabs, Textarea, Th, ToolkitMultiselect, TranscriptViewer, parsePaginationParams };
4847
+ export { AdminTable, AdminTableHead, AdminTableRow, AgentA2aInfo, AgentConnectorsManager, AgentDetailPage, AgentEditForm, AgentListPage, AgentPluginManager, AgentRuns, AgentScheduleForm, AgentSkillManager, ConfirmDialog, CopyButton, DashboardPage, DetailPageHeader, Dialog, DialogBody, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, EmptyRow, FormError, FormField, LocalDate, McpServerListPage, MetricCard, ModelSelector, PaginationBar, PlaygroundPage, PluginDetailPage, PluginMarketplaceDetailPage, PluginMarketplaceListPage, RunDetailPage, RunListPage, RunSourceBadge, RunStatusBadge, SectionHeader, Select, SettingsPage, Tabs, Textarea, Th, ToolkitMultiselect, TranscriptViewer, parsePaginationParams };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getcatalystiq/agent-plane-ui",
3
- "version": "0.1.15",
3
+ "version": "0.1.17",
4
4
  "description": "Embeddable React component library for AgentPlane",
5
5
  "type": "module",
6
6
  "exports": {
@@ -61,12 +61,16 @@
61
61
  "react-dom": "^18.0.0 || ^19.0.0",
62
62
  "react-markdown": "^9.0.0",
63
63
  "recharts": "^2.0.0",
64
+ "remark-gfm": "^4.0.0",
64
65
  "swr": "^2.0.0"
65
66
  },
66
67
  "peerDependenciesMeta": {
67
68
  "react-markdown": {
68
69
  "optional": true
69
70
  },
71
+ "remark-gfm": {
72
+ "optional": true
73
+ },
70
74
  "recharts": {
71
75
  "optional": true
72
76
  },
@@ -96,6 +100,7 @@
96
100
  "devDependencies": {
97
101
  "@types/react": "^19",
98
102
  "@types/react-dom": "^19",
103
+ "remark-gfm": "^4.0.1",
99
104
  "swr": "^2.0.0",
100
105
  "tsup": "^8",
101
106
  "typescript": "^5"