@workerdeck/ui 0.22.0 → 0.23.0

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.
@@ -2,6 +2,7 @@ import * as _$react from "react";
2
2
  import { ReactNode } from "react";
3
3
  import { LucideIcon } from "lucide-react";
4
4
  import { ModelOption, PermissionMode } from "@workerdeck/protocol";
5
+ import * as _$_workerdeck_react0 from "@workerdeck/react";
5
6
  import { ConnectionState, TranscriptState, UseToolCallHostOptions } from "@workerdeck/react";
6
7
  import { WorkerDeckClient } from "@workerdeck/client";
7
8
 
@@ -511,6 +512,62 @@ interface SessionPanelProps {
511
512
  * "`>_` Tell the agent what to do." placeholder.
512
513
  */
513
514
  emptyState?: ReactNode;
515
+ /**
516
+ * Called when a link in the transcript is clicked. The embedder decides what
517
+ * happens: navigate in-app, open a browser tab, show a confirmation, or
518
+ * suppress.
519
+ *
520
+ * Return `true` (or a truthy value) to indicate the click was handled — the
521
+ * default action (`window.open(href, '_blank')`) is suppressed. Return
522
+ * `false` / `undefined` / nothing to let the browser open the link normally.
523
+ *
524
+ * Absent means "browser default" — links open in a new tab as Streamdown's
525
+ * `target="_blank"` intends. VS Code's webview overrides this through its own
526
+ * native handler (the "allow once / add to allowlist" dialog) and does not
527
+ * need this prop.
528
+ *
529
+ * **Typical embedder patterns:**
530
+ * - Relative URLs → in-app navigation, no confirmation
531
+ * - External URLs → confirmation dialog, or open unconditionally
532
+ * - Suppress all links → `() => true`
533
+ */
534
+ onLinkClick?: (href: string) => boolean | void;
535
+ /**
536
+ * Client-side tool handlers. Each key is a tool name the model can call; the
537
+ * handler receives the model's input and returns a result. The tool's
538
+ * **schema** must be registered server-side (via `tools` on
539
+ * `ProviderRunnerOptions`), but the handler runs here — right where the data
540
+ * the tool needs lives.
541
+ *
542
+ * Shorthand for `toolHost.clientTools`; when both are set, this wins for
543
+ * overlapping names.
544
+ *
545
+ * ```tsx
546
+ * <SessionPanel
547
+ * clientTools={{
548
+ * app_navigate: async (input) => {
549
+ * router.push((input as { path: string }).path)
550
+ * return { value: 'navigated' }
551
+ * },
552
+ * }}
553
+ * />
554
+ * ```
555
+ */
556
+ clientTools?: Record<string, _$_workerdeck_react0.ClientToolHandler>;
557
+ /**
558
+ * Base font size in **whole pixels**. Drives the overall scale of everything
559
+ * the panel draws — prompt, output, markdown, status bar — in both variants.
560
+ *
561
+ * Under the terminal theme it sets `--term-font-size` and derives
562
+ * `--term-line` at the CLI's own 13 : 18 ratio (unless {@link terminalMetrics}
563
+ * overrides those individually). Under cards it sets the panel root's
564
+ * `font-size`, which scales every `rem`/`em`-based token the type scale uses.
565
+ *
566
+ * Absent means "platform default": 13 px for the terminal theme, the
567
+ * inherited body size for cards. That is the right choice for a host that has
568
+ * no preference — the panel reads at the size the rest of the app does.
569
+ */
570
+ fontSize?: number;
514
571
  className?: string;
515
572
  }
516
573
  /** What an embedder needs to *change* a session it doesn't own the attach for. */
@@ -600,10 +657,13 @@ declare function SessionPanel({
600
657
  unseen,
601
658
  readOnly,
602
659
  toolHost,
660
+ clientTools,
603
661
  cacheTranscript,
604
662
  emptyState,
663
+ onLinkClick,
664
+ fontSize,
605
665
  className
606
666
  }: SessionPanelProps): _$react.JSX.Element;
607
667
  //#endregion
608
668
  export { permissionModeChoices as C, PermissionModeSelectProps as S, useAffordances as _, SessionVitals as a, PermissionModeMeta as b, TranscriptDensityProvider as c, TranscriptVariantProvider as d, useTranscriptDensity as f, WithActions as g, TerminalAffordances as h, SessionSurfacePanel as i, TranscriptFont as l, CopyAction as m, SessionPanel as n, TerminalMetrics as o, useTranscriptVariant as p, SessionPanelProps as r, TranscriptDensity as s, SessionControls as t, TranscriptVariant as u, PERMISSION_MODES as v, permissionModeMeta as w, PermissionModeSelect as x, PermissionModeChoice as y };
609
- //# sourceMappingURL=SessionPanel-13l25ubU.d.mts.map
669
+ //# sourceMappingURL=SessionPanel-BobynUo4.d.mts.map
@@ -8652,13 +8652,19 @@ function StatusBar({ state, rateLimits, connected, connection, onOpenStatus, onO
8652
8652
  }) : null]
8653
8653
  })
8654
8654
  }) : null,
8655
- controls,
8655
+ controls ? /* @__PURE__ */ jsx("span", {
8656
+ className: "self-center",
8657
+ children: controls
8658
+ }) : null,
8656
8659
  /* @__PURE__ */ jsx("span", { className: "flex-1" }),
8657
8660
  /* @__PURE__ */ jsx("span", {
8658
8661
  className: "font-mono text-label text-fg-3",
8659
8662
  children: formatCost(state.totalCostUsd)
8660
8663
  }),
8661
- actions
8664
+ actions ? /* @__PURE__ */ jsx("span", {
8665
+ className: "self-center",
8666
+ children: actions
8667
+ }) : null
8662
8668
  ]
8663
8669
  });
8664
8670
  }
@@ -8839,6 +8845,7 @@ const Response = memo(function Response({ children, streaming, className }) {
8839
8845
  mode: streaming ? "streaming" : "static",
8840
8846
  parseIncompleteMarkdown: streaming,
8841
8847
  shikiTheme: ["github-light", "github-dark"],
8848
+ linkSafety: { enabled: false },
8842
8849
  className: cn("size-full [&>*:first-child]:mt-0 [&>*:last-child]:mb-0", className),
8843
8850
  children
8844
8851
  });
@@ -12020,7 +12027,9 @@ const INTERACTIVE = [
12020
12027
  * the engine name — an absent capability hides the control instead of offering
12021
12028
  * one that can only fail.
12022
12029
  */
12023
- function SessionPanel({ client, sessionId, header, panelSurface = "internal", statusSurface = "internal", statusPlacement = "top", onOpenPanel, onVitals, transcriptVariant = "cards", transcriptDensity = "comfortable", transcriptFont = "sans", affordances, terminalMetrics, scrubber = false, scrubberMarks, reveal, openSubagent, onSubagentChange, stickyPrompt = false, controlsSurface = "internal", onControls, focusComposerOnClick = false, unseen, readOnly = false, toolHost, cacheTranscript, emptyState, className }) {
12030
+ function SessionPanel({ client, sessionId, header, panelSurface = "internal", statusSurface = "internal", statusPlacement = "top", onOpenPanel, onVitals, transcriptVariant = "cards", transcriptDensity = "comfortable", transcriptFont = "sans", affordances, terminalMetrics, scrubber = false, scrubberMarks, reveal, openSubagent, onSubagentChange, stickyPrompt = false, controlsSurface = "internal", onControls, focusComposerOnClick = false, unseen, readOnly = false, toolHost, clientTools, cacheTranscript, emptyState, onLinkClick, fontSize, className }) {
12031
+ const effectiveTermFontSize = terminalMetrics?.fontSize ?? fontSize;
12032
+ const effectiveTermLineHeight = terminalMetrics?.lineHeight ?? (fontSize !== void 0 ? Math.round(fontSize * (18 / 13)) : void 0);
12024
12033
  const external = panelSurface === "external";
12025
12034
  const statusExternal = statusSurface === "external";
12026
12035
  const controlsInStatus = controlsSurface === "status" && !statusExternal;
@@ -12111,7 +12120,13 @@ function SessionPanel({ client, sessionId, header, panelSurface = "internal", st
12111
12120
  document.addEventListener("visibilitychange", onVisible);
12112
12121
  return () => document.removeEventListener("visibilitychange", onVisible);
12113
12122
  }, [reconnectNow]);
12114
- useToolCallHost(handle, toolHost === false ? { enabled: false } : toolHost);
12123
+ useToolCallHost(handle, toolHost === false ? { enabled: false } : clientTools ? {
12124
+ ...toolHost,
12125
+ clientTools: {
12126
+ ...toolHost?.clientTools,
12127
+ ...clientTools
12128
+ }
12129
+ } : toolHost);
12115
12130
  const terminal = transcriptVariant === "terminal";
12116
12131
  const subagentFrameItems = useMemo(() => subagentId === void 0 ? [] : subagentItems(state.items, subagentId), [state.items, subagentId]);
12117
12132
  const subagentTask = useMemo(() => subagentId === void 0 ? void 0 : state.items.find((item) => item.kind === "tool_call" && item.id === subagentId), [state.items, subagentId]);
@@ -12288,6 +12303,24 @@ function SessionPanel({ client, sessionId, header, panelSurface = "internal", st
12288
12303
  onOpenUsage: external && !onOpenPanel ? void 0 : () => openPanel("usage"),
12289
12304
  actions: headerTakesActions ? void 0 : menu
12290
12305
  });
12306
+ const panelRef = useRef(null);
12307
+ useEffect(() => {
12308
+ if (!onLinkClick) return;
12309
+ const el = panelRef.current;
12310
+ if (!el) return;
12311
+ const handler = (e) => {
12312
+ const anchor = e.target?.closest?.("a[href]");
12313
+ if (!anchor) return;
12314
+ const href = anchor.getAttribute("href");
12315
+ if (!href) return;
12316
+ if (onLinkClick(href)) {
12317
+ e.preventDefault();
12318
+ e.stopPropagation();
12319
+ }
12320
+ };
12321
+ el.addEventListener("click", handler, true);
12322
+ return () => el.removeEventListener("click", handler, true);
12323
+ }, [onLinkClick]);
12291
12324
  const handleClick = (event) => {
12292
12325
  if (!focusComposerOnClick || readOnly) return;
12293
12326
  if (event.target?.closest(INTERACTIVE)) return;
@@ -12303,10 +12336,12 @@ function SessionPanel({ client, sessionId, header, panelSurface = "internal", st
12303
12336
  children: /* @__PURE__ */ jsx(ToolResultImageProvider, {
12304
12337
  value: resultImages,
12305
12338
  children: /* @__PURE__ */ jsxs("div", {
12339
+ ref: panelRef,
12306
12340
  "data-slot": "session-panel",
12307
12341
  "data-agent-font": transcriptFont,
12308
12342
  onClick: handleClick,
12309
12343
  className: cn("flex h-full min-h-0 flex-col overflow-hidden bg-bg", className),
12344
+ style: fontSize !== void 0 ? { "--wd-font-size": `${Math.round(fontSize)}px` } : void 0,
12310
12345
  children: [
12311
12346
  headerTakesActions ? header({ actions: menu }) : header,
12312
12347
  statusPlacement === "top" ? statusBar : null,
@@ -12331,8 +12366,8 @@ function SessionPanel({ client, sessionId, header, panelSurface = "internal", st
12331
12366
  label: subagentFallbackLabel,
12332
12367
  onBack: leaveSubagent,
12333
12368
  terminal,
12334
- fontSize: terminalMetrics?.fontSize,
12335
- lineHeight: terminalMetrics?.lineHeight
12369
+ fontSize: effectiveTermFontSize,
12370
+ lineHeight: effectiveTermLineHeight
12336
12371
  }) : null,
12337
12372
  /* @__PURE__ */ jsx(Transcript, {
12338
12373
  state,
@@ -12342,8 +12377,8 @@ function SessionPanel({ client, sessionId, header, panelSurface = "internal", st
12342
12377
  hostImage,
12343
12378
  variant: transcriptVariant,
12344
12379
  density: transcriptDensity,
12345
- fontSize: terminalMetrics?.fontSize,
12346
- lineHeight: terminalMetrics?.lineHeight,
12380
+ fontSize: effectiveTermFontSize,
12381
+ lineHeight: effectiveTermLineHeight,
12347
12382
  affordances,
12348
12383
  stickyPrompt,
12349
12384
  scrubber,
@@ -12399,7 +12434,10 @@ function SessionPanel({ client, sessionId, header, panelSurface = "internal", st
12399
12434
  className: cn(terminal ? "pb-2" : "px-3 pb-2"),
12400
12435
  children: /* @__PURE__ */ jsx(PromptSurface, {
12401
12436
  terminal,
12402
- metrics: terminalMetrics,
12437
+ metrics: {
12438
+ fontSize: effectiveTermFontSize,
12439
+ lineHeight: effectiveTermLineHeight
12440
+ },
12403
12441
  affordances,
12404
12442
  children: state.pendingApprovals.map((request) => {
12405
12443
  const isQuestion = request.toolName === "AskUserQuestion" && parseUserQuestions(request.input).length > 0;
@@ -12439,8 +12477,8 @@ function SessionPanel({ client, sessionId, header, panelSurface = "internal", st
12439
12477
  }) : void 0,
12440
12478
  layout: controlsExternal ? "inline" : "stacked",
12441
12479
  toolbar: controlsExternal ? void 0 : sessionControls,
12442
- fontSize: terminalMetrics?.fontSize,
12443
- lineHeight: terminalMetrics?.lineHeight,
12480
+ fontSize: effectiveTermFontSize,
12481
+ lineHeight: effectiveTermLineHeight,
12444
12482
  affordances
12445
12483
  }),
12446
12484
  statusPlacement === "bottom" ? statusBar : null,
@@ -12583,4 +12621,4 @@ function Notice({ level, onDismiss, children }) {
12583
12621
  //#endregion
12584
12622
  export { toolIcon as $, PERMISSION_MODES as A, MenuSeparator as At, skillPrompt as B, Badge as Bt, TerminalQuestionPrompt as C, DialogContent as Ct, parseUserQuestions as D, Menu$1 as Dt, QuestionPrompt as E, DialogTrigger as Et, SkillsDialog as F, SelectItemText as Ft, Band as G, TerminalMarkdown as H, Button as Ht, McpDialog as I, SelectTrigger as It, Row as J, Blank as K, HostFilesDialog as L, SelectValue as Lt, permissionModeChoices as M, Select$1 as Mt, permissionModeMeta as N, SelectContent as Nt, SubagentStrip as O, MenuContent as Ot, ModelSelect as P, SelectItem as Pt, isMutatingTool as Q, ContextDialog as R, PortalScope as Rt, SessionInfoDialog as S, DialogClose as St, QUESTION_BEHAVIORS as T, DialogRow as Tt, TerminalDiff as U, buttonVariants as Ut, TerminalSurface as V, badgeVariants as Vt, previewPatch as W, cn as Wt, WithActions as X, CopyAction as Y, useAffordances as Z, Conversation as _, Tip as _t, Transcript as a, hashtagTrigger as at, StatusBar as b, Dialog$1 as bt, ToolCallCard as c, PromptArea as ct, Response as d, ProgressRing as dt, TranscriptDensityProvider as et, PromptTokenText as f, Splitter as ft, FileCard as g, copyText as gt, Loader as h, CopyButton as ht, useMinuteClock as i, commandTrigger as it, PermissionModeSelect as j, MenuTrigger as jt, PermissionPrompt as k, MenuItem as kt, SessionEmptyState as l, plainTextToSegments as lt, MessageContent as m, Spinner as mt, UsageDialog as n, useTranscriptDensity as nt, TerminalItemView as o, mentionTrigger as ot, Message as p, CodeBlock as pt, Ink as q, UsageMeters as r, useTranscriptVariant as rt, TerminalTranscript as s, usePromptAreaState as st, SessionPanel as t, TranscriptVariantProvider as tt, Reasoning as u, segmentsToPlainText as ut, ConversationContent as v, TooltipContent as vt, TerminalPermissionPrompt as w, DialogHeader as wt, STATUS_META as x, DialogBody as xt, ConversationScrollButton as y, TooltipProvider as yt, Composer as z, Input as zt };
12585
12623
 
12586
- //# sourceMappingURL=SessionPanel-DgwjH4Ve.mjs.map
12624
+ //# sourceMappingURL=SessionPanel-DUt2VzXG.mjs.map