@hachej/boring-agent 0.1.92 → 0.1.94

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.
@@ -68,7 +68,7 @@ function rawWorkspaceFileUrl(path, opts) {
68
68
  }
69
69
 
70
70
  // src/front/chat/PiChatPanel.tsx
71
- import { lazy, Suspense, useCallback as useCallback18, useEffect as useEffect21, useMemo as useMemo13, useRef as useRef17, useState as useState20 } from "react";
71
+ import { lazy, Suspense, useCallback as useCallback18, useEffect as useEffect21, useMemo as useMemo16, useRef as useRef18, useState as useState20 } from "react";
72
72
 
73
73
  // src/front/ArtifactOpenContext.tsx
74
74
  import { createContext, useContext } from "react";
@@ -279,6 +279,7 @@ var builtinCommands = [
279
279
  {
280
280
  name: "reload",
281
281
  description: "Reload agent plugins",
282
+ clickBehavior: "execute",
282
283
  handler(_, ctx) {
283
284
  if (ctx.pluginUpdate) return ctx.pluginUpdate.run();
284
285
  return ctx.reloadAgentPlugins();
@@ -345,7 +346,7 @@ var lineClass = {
345
346
  " ": ""
346
347
  };
347
348
  var prefixMap = { "+": "+", "-": "-", " ": " " };
348
- function DiffView({ oldString, newString, path, replaceAll }) {
349
+ function DiffView({ oldString, newString, path, replaceAll, actions }) {
349
350
  const [expanded, setExpanded] = useState(false);
350
351
  if (oldString === newString) {
351
352
  return /* @__PURE__ */ jsx3("div", { "data-testid": "diff-no-change", className: "py-2 italic opacity-60", children: "No changes" });
@@ -355,9 +356,12 @@ function DiffView({ oldString, newString, path, replaceAll }) {
355
356
  const collapsed = !expanded && lines.length > COLLAPSE_THRESHOLD;
356
357
  const visible = collapsed ? lines.slice(0, COLLAPSE_THRESHOLD) : lines;
357
358
  return /* @__PURE__ */ jsxs2("div", { "data-testid": "diff-view", children: [
358
- /* @__PURE__ */ jsxs2("div", { className: "px-3 py-1.5 font-[family-name:var(--boring-agent-font-mono,monospace)] text-xs opacity-70", children: [
359
- path,
360
- replaceAll ? " (replace all)" : ""
359
+ /* @__PURE__ */ jsxs2("div", { className: "flex min-h-7 items-center justify-between gap-2 px-2 py-0.5 font-[family-name:var(--boring-agent-font-mono,monospace)] text-xs opacity-70", children: [
360
+ /* @__PURE__ */ jsxs2("span", { className: "min-w-0 truncate", children: [
361
+ path,
362
+ replaceAll ? " (replace all)" : ""
363
+ ] }),
364
+ actions ? /* @__PURE__ */ jsx3("span", { className: "flex shrink-0 items-center gap-0.5", children: actions }) : null
361
365
  ] }),
362
366
  /* @__PURE__ */ jsx3("pre", { className: "m-0 overflow-auto py-2 font-[family-name:var(--boring-agent-font-mono,monospace)] text-[0.8125rem] leading-relaxed", children: visible.map((line, i) => /* @__PURE__ */ jsxs2("div", { className: cn("px-3", lineClass[line.type]), children: [
363
367
  /* @__PURE__ */ jsx3("span", { className: "inline-block w-[1.5ch] select-none opacity-50", children: prefixMap[line.type] }),
@@ -1263,9 +1267,9 @@ var statusIcons = {
1263
1267
  "output-error": /* @__PURE__ */ jsx9(XCircleIcon, { className: "size-4 text-destructive" }),
1264
1268
  aborted: /* @__PURE__ */ jsx9(XCircleIcon, { className: "size-4 text-muted-foreground" })
1265
1269
  };
1266
- var getStatusBadge = (status) => /* @__PURE__ */ jsxs8(Badge, { className: "shrink-0 gap-1.5 rounded-full text-xs", variant: "secondary", children: [
1270
+ var getStatusBadge = (status, label = statusLabels[status]) => /* @__PURE__ */ jsxs8(Badge, { className: "shrink-0 gap-1.5 rounded-full text-xs", variant: "secondary", children: [
1267
1271
  statusIcons[status],
1268
- statusLabels[status]
1272
+ label
1269
1273
  ] });
1270
1274
  var ToolHeader = ({
1271
1275
  className,
@@ -1274,6 +1278,7 @@ var ToolHeader = ({
1274
1278
  state,
1275
1279
  icon,
1276
1280
  toolName,
1281
+ statusLabel,
1277
1282
  ...props
1278
1283
  }) => {
1279
1284
  const derivedName = type === "dynamic-tool" || type === "tool-call" ? toolName : type.split("-").slice(1).join("-");
@@ -1290,7 +1295,7 @@ var ToolHeader = ({
1290
1295
  /* @__PURE__ */ jsxs8("div", { className: "flex min-w-0 flex-1 items-center gap-2", children: [
1291
1296
  icon ?? /* @__PURE__ */ jsx9(WrenchIcon, { className: "size-4 shrink-0 text-muted-foreground" }),
1292
1297
  /* @__PURE__ */ jsx9("span", { "data-boring-agent-part": "tool-title", className: "min-w-0 flex-1 truncate text-sm font-medium", children: title ?? derivedName }),
1293
- getStatusBadge(state)
1298
+ getStatusBadge(state, statusLabel)
1294
1299
  ] }),
1295
1300
  /* @__PURE__ */ jsx9(
1296
1301
  ChevronDownIcon,
@@ -1356,7 +1361,7 @@ var ToolOutput = ({
1356
1361
 
1357
1362
  // src/front/toolRenderers.tsx
1358
1363
  import { CollapsibleTrigger as CollapsibleTrigger2 } from "@hachej/boring-ui-kit";
1359
- import { ChevronDownIcon as ChevronDownIcon2, ExternalLinkIcon, FileDiffIcon, FilePlus2Icon, FileTextIcon, SearchIcon, SquareTerminalIcon, ZapIcon } from "lucide-react";
1364
+ import { ChevronDownIcon as ChevronDownIcon2, CopyIcon as CopyIcon2, DownloadIcon, ExternalLinkIcon, FileDiffIcon, FilePlus2Icon, FileTextIcon, SearchIcon, SquareTerminalIcon, ZapIcon } from "lucide-react";
1360
1365
 
1361
1366
  // src/front/primitives/artifact.tsx
1362
1367
  import { Button as Button6 } from "@hachej/boring-ui-kit";
@@ -1368,44 +1373,6 @@ import {
1368
1373
  } from "@hachej/boring-ui-kit";
1369
1374
  import { XIcon } from "lucide-react";
1370
1375
  import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
1371
- var Artifact = ({ className, ...props }) => /* @__PURE__ */ jsx10(
1372
- "div",
1373
- {
1374
- className: cn(
1375
- "flex flex-col overflow-hidden rounded-lg border bg-background shadow-sm",
1376
- className
1377
- ),
1378
- ...props
1379
- }
1380
- );
1381
- var ArtifactHeader = ({
1382
- className,
1383
- ...props
1384
- }) => /* @__PURE__ */ jsx10(
1385
- "div",
1386
- {
1387
- className: cn(
1388
- "flex items-center justify-between border-b bg-muted/50 px-4 py-3",
1389
- className
1390
- ),
1391
- ...props
1392
- }
1393
- );
1394
- var ArtifactTitle = ({ className, ...props }) => /* @__PURE__ */ jsx10(
1395
- "p",
1396
- {
1397
- className: cn("font-medium text-foreground text-sm", className),
1398
- ...props
1399
- }
1400
- );
1401
- var ArtifactDescription = ({
1402
- className,
1403
- ...props
1404
- }) => /* @__PURE__ */ jsx10("p", { className: cn("text-muted-foreground text-sm", className), ...props });
1405
- var ArtifactActions = ({
1406
- className,
1407
- ...props
1408
- }) => /* @__PURE__ */ jsx10("div", { className: cn("flex items-center gap-1", className), ...props });
1409
1376
  var ArtifactAction = ({
1410
1377
  tooltip,
1411
1378
  label,
@@ -1441,13 +1408,6 @@ var ArtifactAction = ({
1441
1408
  }
1442
1409
  return button;
1443
1410
  };
1444
- var ArtifactContent = ({
1445
- className,
1446
- ...props
1447
- }) => /* @__PURE__ */ jsx10("div", { className: cn("flex-1 overflow-auto p-4", className), ...props });
1448
-
1449
- // src/front/toolRenderers.tsx
1450
- import { CopyIcon as CopyIcon2, DownloadIcon } from "lucide-react";
1451
1411
 
1452
1412
  // src/front/runtimeReadinessStatus.ts
1453
1413
  var COPY = {
@@ -1513,7 +1473,7 @@ function getWorkspaceNotReadyStatus(output) {
1513
1473
  }
1514
1474
 
1515
1475
  // src/front/toolRenderers.tsx
1516
- import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
1476
+ import { Fragment as Fragment3, jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
1517
1477
  function asRecord2(value) {
1518
1478
  return typeof value === "object" && value !== null ? value : {};
1519
1479
  }
@@ -1671,59 +1631,44 @@ function renderWrite2(part) {
1671
1631
  const path = typeof input.path === "string" ? input.path : "";
1672
1632
  const filesystem = typeof input.filesystem === "string" ? input.filesystem : void 0;
1673
1633
  const content = typeof input.content === "string" ? input.content : "";
1674
- const bytes = content.length;
1675
- const lang = langFromPath(path);
1676
- return /* @__PURE__ */ jsxs10(Tool2, { children: [
1677
- /* @__PURE__ */ jsx11(ToolHeader, { icon: /* @__PURE__ */ jsx11(FilePlus2Icon, { className: "size-4 text-muted-foreground" }), title: pathTitle("write", path, filesystem), ...toHeaderProps(part) }),
1678
- /* @__PURE__ */ jsx11(ToolContent, { children: /* @__PURE__ */ jsxs10(Artifact, { className: "rounded-none border-0 bg-transparent shadow-none", children: [
1679
- /* @__PURE__ */ jsxs10(ArtifactHeader, { className: "border-0 px-0 pt-0 pb-2", children: [
1680
- /* @__PURE__ */ jsxs10("div", { children: [
1681
- /* @__PURE__ */ jsx11(ArtifactTitle, { children: path || "untitled" }),
1682
- /* @__PURE__ */ jsxs10(ArtifactDescription, { children: [
1683
- bytes.toLocaleString(),
1684
- " ",
1685
- bytes === 1 ? "byte" : "bytes",
1686
- lang ? ` \xB7 ${lang}` : ""
1687
- ] })
1688
- ] }),
1689
- /* @__PURE__ */ jsxs10(ArtifactActions, { children: [
1690
- /* @__PURE__ */ jsx11(
1691
- ArtifactAction,
1692
- {
1693
- icon: CopyIcon2,
1694
- tooltip: "Copy contents",
1695
- label: "Copy",
1696
- onClick: () => {
1697
- void copyTextToClipboard(content);
1698
- }
1699
- }
1700
- ),
1701
- /* @__PURE__ */ jsx11(
1702
- ArtifactAction,
1703
- {
1704
- icon: DownloadIcon,
1705
- tooltip: "Download file",
1706
- label: "Download",
1707
- onClick: () => {
1708
- try {
1634
+ return /* @__PURE__ */ jsxs10(Tool2, { defaultOpen: part.state === "output-available", children: [
1635
+ /* @__PURE__ */ jsx11(ToolHeader, { className: "p-2", icon: /* @__PURE__ */ jsx11(FilePlus2Icon, { className: "size-4 shrink-0 text-muted-foreground" }), title: pathTitle("write", path, filesystem), ...toHeaderProps(part) }),
1636
+ /* @__PURE__ */ jsxs10(ToolContent, { className: "p-1.5", children: [
1637
+ /* @__PURE__ */ jsx11("div", { className: "max-h-80 overflow-auto overscroll-contain rounded-sm", children: content ? /* @__PURE__ */ jsx11(
1638
+ DiffView,
1639
+ {
1640
+ oldString: "",
1641
+ newString: content,
1642
+ path,
1643
+ actions: /* @__PURE__ */ jsxs10(Fragment3, { children: [
1644
+ /* @__PURE__ */ jsx11(ArtifactAction, { className: "size-6", icon: CopyIcon2, tooltip: "Copy contents", label: "Copy", onClick: () => {
1645
+ void copyTextToClipboard(content);
1646
+ } }),
1647
+ /* @__PURE__ */ jsx11(
1648
+ ArtifactAction,
1649
+ {
1650
+ className: "size-6",
1651
+ icon: DownloadIcon,
1652
+ tooltip: "Download file",
1653
+ label: "Download",
1654
+ onClick: () => {
1709
1655
  const blob = new Blob([content], { type: "text/plain" });
1710
1656
  const url = URL.createObjectURL(blob);
1711
- const a = document.createElement("a");
1712
- a.href = url;
1713
- a.download = path.split("/").pop() || "artifact.txt";
1714
- document.body.appendChild(a);
1715
- a.click();
1716
- a.remove();
1657
+ const anchor = document.createElement("a");
1658
+ anchor.href = url;
1659
+ anchor.download = path.split("/").pop() || "artifact.txt";
1660
+ document.body.appendChild(anchor);
1661
+ anchor.click();
1662
+ anchor.remove();
1717
1663
  URL.revokeObjectURL(url);
1718
- } catch {
1719
1664
  }
1720
1665
  }
1721
- }
1722
- )
1723
- ] })
1724
- ] }),
1725
- content && /* @__PURE__ */ jsx11(ArtifactContent, { className: "p-0", children: /* @__PURE__ */ jsx11(CodeBlock2, { code: content, language: lang ?? "text", showLineNumbers: true }) })
1726
- ] }) })
1666
+ )
1667
+ ] })
1668
+ }
1669
+ ) : /* @__PURE__ */ jsx11("p", { className: "px-2 py-1 text-[12px] text-muted-foreground", children: "No file content was included with this tool call." }) }),
1670
+ part.errorText || part.state === "output-error" || part.state === "output-denied" ? /* @__PURE__ */ jsx11(ToolOutput, { output: part.output, errorText: part.errorText }) : null
1671
+ ] })
1727
1672
  ] });
1728
1673
  }
1729
1674
  function renderEdit2(part) {
@@ -1732,25 +1677,26 @@ function renderEdit2(part) {
1732
1677
  const input = asRecord2(part.input);
1733
1678
  const path = typeof input.path === "string" ? input.path : "";
1734
1679
  const filesystem = typeof input.filesystem === "string" ? input.filesystem : void 0;
1735
- const oldString = typeof input.oldString === "string" ? input.oldString : "";
1736
- const newString = typeof input.newString === "string" ? input.newString : "";
1737
- return /* @__PURE__ */ jsxs10(Tool2, { children: [
1738
- /* @__PURE__ */ jsx11(ToolHeader, { icon: /* @__PURE__ */ jsx11(FileDiffIcon, { className: "size-4 text-muted-foreground" }), title: pathTitle("edit", path, filesystem), ...toHeaderProps(part) }),
1739
- /* @__PURE__ */ jsxs10(ToolContent, { children: [
1740
- /* @__PURE__ */ jsxs10("section", { className: "space-y-2", children: [
1741
- /* @__PURE__ */ jsx11("h4", { className: "font-medium text-muted-foreground text-xs uppercase tracking-wide", children: "Diff" }),
1742
- /* @__PURE__ */ jsxs10("div", { className: "overflow-hidden rounded-sm border border-input/60 bg-muted/30 font-mono text-[13px] leading-relaxed", children: [
1743
- oldString && /* @__PURE__ */ jsxs10("div", { className: "grid grid-cols-[auto_1fr] gap-2 border-b border-input/40 bg-destructive/5 px-3 py-2", children: [
1744
- /* @__PURE__ */ jsx11("span", { className: "select-none text-destructive/70", children: "-" }),
1745
- /* @__PURE__ */ jsx11("span", { className: cn("whitespace-pre-wrap break-all text-destructive/90"), children: oldString })
1746
- ] }),
1747
- newString && /* @__PURE__ */ jsxs10("div", { className: "grid grid-cols-[auto_1fr] gap-2 bg-accent/5 px-3 py-2", children: [
1748
- /* @__PURE__ */ jsx11("span", { className: "select-none text-accent/80", children: "+" }),
1749
- /* @__PURE__ */ jsx11("span", { className: "whitespace-pre-wrap break-all text-accent", children: newString })
1750
- ] })
1751
- ] })
1752
- ] }),
1753
- /* @__PURE__ */ jsx11(ToolOutput, { output: part.output, errorText: part.errorText })
1680
+ const edits = Array.isArray(input.edits) ? input.edits.flatMap((value) => {
1681
+ const edit = asRecord2(value);
1682
+ const oldText = typeof edit.oldText === "string" ? edit.oldText : "";
1683
+ const newText = typeof edit.newText === "string" ? edit.newText : "";
1684
+ return oldText || newText ? [{ oldText, newText }] : [];
1685
+ }) : [];
1686
+ const legacyOldText = typeof input.oldString === "string" ? input.oldString : typeof input.oldText === "string" ? input.oldText : "";
1687
+ const legacyNewText = typeof input.newString === "string" ? input.newString : typeof input.newText === "string" ? input.newText : "";
1688
+ const visibleEdits = edits.length > 0 ? edits : legacyOldText || legacyNewText ? [{ oldText: legacyOldText, newText: legacyNewText }] : [];
1689
+ return /* @__PURE__ */ jsxs10(Tool2, { defaultOpen: part.state === "output-available", children: [
1690
+ /* @__PURE__ */ jsx11(ToolHeader, { className: "p-2", icon: /* @__PURE__ */ jsx11(FileDiffIcon, { className: "size-4 shrink-0 text-muted-foreground" }), title: pathTitle("edit", path, filesystem), ...toHeaderProps(part) }),
1691
+ /* @__PURE__ */ jsxs10(ToolContent, { className: "space-y-2 p-2", children: [
1692
+ /* @__PURE__ */ jsx11("div", { className: "max-h-80 space-y-2 overflow-auto overscroll-contain rounded-sm", children: visibleEdits.length > 0 ? visibleEdits.map((edit, index) => /* @__PURE__ */ jsxs10("div", { children: [
1693
+ visibleEdits.length > 1 ? /* @__PURE__ */ jsxs10("div", { className: "mb-1 px-1 text-[11px] text-muted-foreground", children: [
1694
+ "Change ",
1695
+ index + 1
1696
+ ] }) : null,
1697
+ /* @__PURE__ */ jsx11(DiffView, { oldString: edit.oldText, newString: edit.newText, path })
1698
+ ] }, `${index}:${edit.oldText.length}:${edit.newText.length}`)) : /* @__PURE__ */ jsx11("p", { className: "px-2 py-1 text-[12px] text-muted-foreground", children: "Diff details were not included with this tool call." }) }),
1699
+ part.errorText || part.state === "output-error" || part.state === "output-denied" ? /* @__PURE__ */ jsx11(ToolOutput, { output: part.output, errorText: part.errorText }) : null
1754
1700
  ] })
1755
1701
  ] });
1756
1702
  }
@@ -2026,7 +1972,7 @@ function usePickerKeyboard({
2026
1972
  }
2027
1973
 
2028
1974
  // src/front/primitives/mention-picker.tsx
2029
- import { Fragment as Fragment3, jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
1975
+ import { Fragment as Fragment4, jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
2030
1976
  var mentionSearchGlob = toFileSearchGlob;
2031
1977
  function highlight(text, query) {
2032
1978
  if (!query) return null;
@@ -2089,7 +2035,7 @@ function MentionPicker({
2089
2035
  onSelect(path);
2090
2036
  },
2091
2037
  children: [
2092
- /* @__PURE__ */ jsx12("span", { className: "font-medium", children: hl ? /* @__PURE__ */ jsxs11(Fragment3, { children: [
2038
+ /* @__PURE__ */ jsx12("span", { className: "font-medium", children: hl ? /* @__PURE__ */ jsxs11(Fragment4, { children: [
2093
2039
  hl.before,
2094
2040
  /* @__PURE__ */ jsx12("mark", { className: "bg-transparent text-[color:var(--accent)]", children: hl.match }),
2095
2041
  hl.after
@@ -2495,10 +2441,11 @@ var PiComposerPolicyController = class {
2495
2441
  }));
2496
2442
  }
2497
2443
  async runLocalCommand(commandName, args) {
2498
- if (isPiChatBusy(this.options.session.getState().status)) {
2444
+ const command = this.options.registry.get(commandName);
2445
+ if (isPiChatBusy(this.options.session.getState().status) && command?.allowWhileBusy?.(args) !== true) {
2499
2446
  return this.block("busy-slash-command", "Slash commands are not queued while the agent is responding.");
2500
2447
  }
2501
- const result = await Promise.resolve(this.options.registry.get(commandName)?.handler(args, this.options.slashContext));
2448
+ const result = await Promise.resolve(command?.handler(args, this.options.slashContext));
2502
2449
  const message = typeof result === "string" ? result : result && typeof result === "object" ? result.message : void 0;
2503
2450
  const preserveDraft = Boolean(result && typeof result === "object" && result.preserveDraft === true);
2504
2451
  if (message) this.options.onCommandResult?.(message);
@@ -2911,6 +2858,25 @@ function composerNoticeForRuntimeDependencies(status) {
2911
2858
  return null;
2912
2859
  }
2913
2860
 
2861
+ // src/front/chat/composerContributions.tsx
2862
+ import { createContext as createContext3, useContext as useContext3, useMemo as useMemo3 } from "react";
2863
+ import { jsx as jsx13 } from "react/jsx-runtime";
2864
+ var ComposerContributionContext = createContext3([]);
2865
+ function ComposerContributionProvider({
2866
+ contribution,
2867
+ children
2868
+ }) {
2869
+ const parent = useContext3(ComposerContributionContext);
2870
+ const contributions = useMemo3(
2871
+ () => [contribution, ...parent.filter((entry) => entry.id !== contribution.id)],
2872
+ [contribution, parent]
2873
+ );
2874
+ return /* @__PURE__ */ jsx13(ComposerContributionContext.Provider, { value: contributions, children });
2875
+ }
2876
+ function useComposerContributions() {
2877
+ return useContext3(ComposerContributionContext);
2878
+ }
2879
+
2914
2880
  // src/front/chat/pi/piChatMessageMetadata.ts
2915
2881
  function earliestCreatedAt(left, right) {
2916
2882
  if (!left) return right;
@@ -5158,7 +5124,7 @@ function resolveStorage2(storage) {
5158
5124
  }
5159
5125
 
5160
5126
  // src/front/chat/session/usePiSessions.ts
5161
- import { useCallback as useCallback7, useEffect as useEffect8, useMemo as useMemo3, useRef as useRef8, useState as useState10 } from "react";
5127
+ import { useCallback as useCallback7, useEffect as useEffect8, useMemo as useMemo4, useRef as useRef8, useState as useState10 } from "react";
5162
5128
  var DEFAULT_SESSIONS_API_PATH = "/api/v1/agent/pi-chat/sessions";
5163
5129
  var SESSION_PAGE_SIZE = 50;
5164
5130
  var DEFAULT_MAX_RETRIES = 60;
@@ -5179,16 +5145,16 @@ function usePiSessions(options = {}) {
5179
5145
  const addressed = typeof options.agentTypeId === "string" && options.agentTypeId.length > 0;
5180
5146
  const sessionsApiPath = options.sessionsApiPath ?? (addressed ? `/api/v1/agents/${encodeURIComponent(options.agentTypeId)}/sessions` : DEFAULT_SESSIONS_API_PATH);
5181
5147
  const storageScope = options.storageScope ?? "default";
5182
- const fetchImpl = useMemo3(() => options.fetch ?? globalThis.fetch.bind(globalThis), [options.fetch]);
5148
+ const fetchImpl = useMemo4(() => options.fetch ?? globalThis.fetch.bind(globalThis), [options.fetch]);
5183
5149
  const createRemoteSession = options.createRemoteSession ?? createRemotePiSession;
5184
5150
  const connectActiveSession = options.connectActiveSession ?? true;
5185
5151
  const retryMaxRetries = options.retry?.maxRetries ?? DEFAULT_MAX_RETRIES;
5186
5152
  const retryBaseMs = options.retry?.baseMs ?? DEFAULT_RETRY_BASE_MS;
5187
5153
  const retryMaxMs = options.retry?.maxMs ?? DEFAULT_RETRY_MAX_MS;
5188
- const headersKey = useMemo3(() => headersScopeKey(options.requestHeaders, storageScope), [options.requestHeaders, storageScope]);
5189
- const normalizedHeaders = useMemo3(() => buildRequestHeaders(options.requestHeaders, storageScope), [headersKey, storageScope]);
5190
- const requestScopeKey = useMemo3(() => requestScopeIdentity(apiBaseUrl, sessionsApiPath, storageScope, headersKey), [apiBaseUrl, headersKey, sessionsApiPath, storageScope]);
5191
- const dataSourceKey = useMemo3(() => dataSourceIdentity(apiBaseUrl, sessionsApiPath, storageScope), [apiBaseUrl, sessionsApiPath, storageScope]);
5154
+ const headersKey = useMemo4(() => headersScopeKey(options.requestHeaders, storageScope), [options.requestHeaders, storageScope]);
5155
+ const normalizedHeaders = useMemo4(() => buildRequestHeaders(options.requestHeaders, storageScope), [headersKey, storageScope]);
5156
+ const requestScopeKey = useMemo4(() => requestScopeIdentity(apiBaseUrl, sessionsApiPath, storageScope, headersKey), [apiBaseUrl, headersKey, sessionsApiPath, storageScope]);
5157
+ const dataSourceKey = useMemo4(() => dataSourceIdentity(apiBaseUrl, sessionsApiPath, storageScope), [apiBaseUrl, sessionsApiPath, storageScope]);
5192
5158
  const [sessions, setSessions] = useState10([]);
5193
5159
  const [dataStorageScope, setDataStorageScope] = useState10(storageScope);
5194
5160
  const [activeSessionId, setActiveSessionId] = useState10(() => options.initialActiveSessionId ?? readActiveSessionId({ storageScope, storage: options.storage }));
@@ -5215,7 +5181,7 @@ function usePiSessions(options = {}) {
5215
5181
  requestScopeRef.current = requestScopeKey;
5216
5182
  const remoteSessionOptionsRef = useRef8(options.remoteSessionOptions);
5217
5183
  remoteSessionOptionsRef.current = options.remoteSessionOptions;
5218
- const remoteSessionOptionsKey = useMemo3(
5184
+ const remoteSessionOptionsKey = useMemo4(
5219
5185
  () => remoteSessionOptionsIdentity(options.remoteSessionOptions),
5220
5186
  [options.remoteSessionOptions]
5221
5187
  );
@@ -5657,10 +5623,10 @@ function clearRetryTimer(ref) {
5657
5623
  }
5658
5624
 
5659
5625
  // src/front/chat/session/SessionList.tsx
5660
- import { useMemo as useMemo4 } from "react";
5626
+ import { useMemo as useMemo5 } from "react";
5661
5627
  import { ChevronLeftIcon, PlusIcon, Trash2Icon } from "lucide-react";
5662
5628
  import { IconButton } from "@hachej/boring-ui-kit";
5663
- import { Fragment as Fragment4, jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
5629
+ import { Fragment as Fragment5, jsx as jsx14, jsxs as jsxs12 } from "react/jsx-runtime";
5664
5630
  var DAY_MS = 24 * 60 * 60 * 1e3;
5665
5631
  function SessionList({
5666
5632
  sessions,
@@ -5675,7 +5641,7 @@ function SessionList({
5675
5641
  onClose,
5676
5642
  className
5677
5643
  }) {
5678
- const groups = useMemo4(() => groupSessions(sessions), [sessions]);
5644
+ const groups = useMemo5(() => groupSessions(sessions), [sessions]);
5679
5645
  return /* @__PURE__ */ jsxs12(
5680
5646
  "div",
5681
5647
  {
@@ -5686,26 +5652,26 @@ function SessionList({
5686
5652
  "aria-busy": loading ? "true" : void 0,
5687
5653
  children: [
5688
5654
  /* @__PURE__ */ jsxs12("div", { className: "flex h-11 items-center justify-between border-b border-border/60 px-3.5", children: [
5689
- /* @__PURE__ */ jsx13("span", { className: "text-[12px] font-medium tracking-tight text-foreground/70", children: "Sessions" }),
5655
+ /* @__PURE__ */ jsx14("span", { className: "text-[12px] font-medium tracking-tight text-foreground/70", children: "Sessions" }),
5690
5656
  /* @__PURE__ */ jsxs12("div", { className: "flex items-center gap-0.5", children: [
5691
- onCreate ? /* @__PURE__ */ jsx13(IconButton, { type: "button", variant: "ghost", size: "icon-xs", onClick: onCreate, "aria-label": "New session", title: "New chat", children: /* @__PURE__ */ jsx13(PlusIcon, { className: "h-3.5 w-3.5", strokeWidth: 1.75 }) }) : null,
5692
- onClose ? /* @__PURE__ */ jsx13(IconButton, { type: "button", variant: "ghost", size: "icon-xs", onClick: onClose, "aria-label": "Close sessions", title: "Close sessions", children: /* @__PURE__ */ jsx13(ChevronLeftIcon, { className: "h-4 w-4", strokeWidth: 1.75 }) }) : null
5657
+ onCreate ? /* @__PURE__ */ jsx14(IconButton, { type: "button", variant: "ghost", size: "icon-xs", onClick: onCreate, "aria-label": "New session", title: "New chat", children: /* @__PURE__ */ jsx14(PlusIcon, { className: "h-3.5 w-3.5", strokeWidth: 1.75 }) }) : null,
5658
+ onClose ? /* @__PURE__ */ jsx14(IconButton, { type: "button", variant: "ghost", size: "icon-xs", onClick: onClose, "aria-label": "Close sessions", title: "Close sessions", children: /* @__PURE__ */ jsx14(ChevronLeftIcon, { className: "h-4 w-4", strokeWidth: 1.75 }) }) : null
5693
5659
  ] })
5694
5660
  ] }),
5695
5661
  /* @__PURE__ */ jsxs12("div", { className: "flex-1 overflow-y-auto py-2.5", children: [
5696
5662
  sessions.length === 0 ? /* @__PURE__ */ jsxs12("div", { "data-boring-agent-part": "session-list-empty", className: "px-3 py-8 text-center text-[13px] text-muted-foreground", children: [
5697
5663
  loading ? "Loading sessions\u2026" : "No sessions yet.",
5698
- !loading ? /* @__PURE__ */ jsxs12(Fragment4, { children: [
5699
- /* @__PURE__ */ jsx13("br", {}),
5664
+ !loading ? /* @__PURE__ */ jsxs12(Fragment5, { children: [
5665
+ /* @__PURE__ */ jsx14("br", {}),
5700
5666
  "Start a new chat to begin."
5701
5667
  ] }) : null
5702
5668
  ] }) : null,
5703
5669
  groups.map((group, index) => /* @__PURE__ */ jsxs12("section", { className: cn(index > 0 && "mt-4"), children: [
5704
5670
  /* @__PURE__ */ jsxs12("div", { className: "flex items-baseline justify-between gap-2 px-3.5 pb-2 pt-2 text-[11px] font-medium tracking-tight text-muted-foreground/75", children: [
5705
- /* @__PURE__ */ jsx13("span", { children: group.label }),
5706
- /* @__PURE__ */ jsx13("span", { "aria-hidden": "true", className: "text-[10.5px] tabular-nums text-muted-foreground/40", children: group.items.length })
5671
+ /* @__PURE__ */ jsx14("span", { children: group.label }),
5672
+ /* @__PURE__ */ jsx14("span", { "aria-hidden": "true", className: "text-[10.5px] tabular-nums text-muted-foreground/40", children: group.items.length })
5707
5673
  ] }),
5708
- /* @__PURE__ */ jsx13("ul", { role: "list", className: "flex flex-col", children: group.items.map((session) => /* @__PURE__ */ jsx13(
5674
+ /* @__PURE__ */ jsx14("ul", { role: "list", className: "flex flex-col", children: group.items.map((session) => /* @__PURE__ */ jsx14(
5709
5675
  SessionRow,
5710
5676
  {
5711
5677
  session,
@@ -5716,7 +5682,7 @@ function SessionList({
5716
5682
  session.id
5717
5683
  )) })
5718
5684
  ] }, group.key)),
5719
- hasMore && onLoadMore ? /* @__PURE__ */ jsx13("div", { className: "px-3 py-3", children: /* @__PURE__ */ jsx13(
5685
+ hasMore && onLoadMore ? /* @__PURE__ */ jsx14("div", { className: "px-3 py-3", children: /* @__PURE__ */ jsx14(
5720
5686
  "button",
5721
5687
  {
5722
5688
  type: "button",
@@ -5748,10 +5714,10 @@ function SessionRow({ session, active, onSwitch, onDelete }) {
5748
5714
  onClick: () => onSwitch?.(session.id),
5749
5715
  children: [
5750
5716
  /* @__PURE__ */ jsxs12("span", { className: "min-w-0 flex-1 truncate leading-5", title: session.title, children: [
5751
- /* @__PURE__ */ jsx13("span", { className: cn(active ? "font-medium text-foreground" : "text-foreground/90"), children: session.title || "Untitled" }),
5752
- time ? /* @__PURE__ */ jsx13("span", { className: cn("ml-1.5 tabular-nums text-[11px]", active ? "text-[color:var(--accent)]" : "text-muted-foreground/60"), children: time }) : null
5717
+ /* @__PURE__ */ jsx14("span", { className: cn(active ? "font-medium text-foreground" : "text-foreground/90"), children: session.title || "Untitled" }),
5718
+ time ? /* @__PURE__ */ jsx14("span", { className: cn("ml-1.5 tabular-nums text-[11px]", active ? "text-[color:var(--accent)]" : "text-muted-foreground/60"), children: time }) : null
5753
5719
  ] }),
5754
- onDelete ? /* @__PURE__ */ jsx13(
5720
+ onDelete ? /* @__PURE__ */ jsx14(
5755
5721
  IconButton,
5756
5722
  {
5757
5723
  type: "button",
@@ -5763,7 +5729,7 @@ function SessionRow({ session, active, onSwitch, onDelete }) {
5763
5729
  onDelete(session.id);
5764
5730
  },
5765
5731
  "aria-label": `Delete ${session.title || "session"}`,
5766
- children: /* @__PURE__ */ jsx13(Trash2Icon, { className: "h-3 w-3" })
5732
+ children: /* @__PURE__ */ jsx14(Trash2Icon, { className: "h-3 w-3" })
5767
5733
  }
5768
5734
  ) : null
5769
5735
  ]
@@ -6010,7 +5976,7 @@ function searchPiSessions(sessions, query, options = {}) {
6010
5976
 
6011
5977
  // src/front/chat/components/PiConversationSurface.tsx
6012
5978
  import { Loader2 as Loader22 } from "lucide-react";
6013
- import { useCallback as useCallback14, useEffect as useEffect14, useLayoutEffect, useRef as useRef10, useState as useState15 } from "react";
5979
+ import { useCallback as useCallback14, useEffect as useEffect14, useLayoutEffect, useRef as useRef11, useState as useState15 } from "react";
6014
5980
  import { useStickToBottomContext as useStickToBottomContext2 } from "use-stick-to-bottom";
6015
5981
 
6016
5982
  // src/front/primitives/conversation.tsx
@@ -6018,7 +5984,7 @@ import { Button as Button7 } from "@hachej/boring-ui-kit";
6018
5984
  import { ArrowDownIcon, DownloadIcon as DownloadIcon2 } from "lucide-react";
6019
5985
  import { useCallback as useCallback8, useEffect as useEffect9 } from "react";
6020
5986
  import { StickToBottom, useStickToBottomContext } from "use-stick-to-bottom";
6021
- import { Fragment as Fragment5, jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime";
5987
+ import { Fragment as Fragment6, jsx as jsx15, jsxs as jsxs13 } from "react/jsx-runtime";
6022
5988
  var ConversationScrollController = ({
6023
5989
  onScrollToBottomReady
6024
5990
  }) => {
@@ -6044,7 +6010,7 @@ var Conversation = ({
6044
6010
  role: "log",
6045
6011
  ...props,
6046
6012
  children: [
6047
- /* @__PURE__ */ jsx14(ConversationScrollController, { onScrollToBottomReady }),
6013
+ /* @__PURE__ */ jsx15(ConversationScrollController, { onScrollToBottomReady }),
6048
6014
  children
6049
6015
  ]
6050
6016
  }
@@ -6052,7 +6018,7 @@ var Conversation = ({
6052
6018
  var ConversationContent = ({
6053
6019
  className,
6054
6020
  ...props
6055
- }) => /* @__PURE__ */ jsx14(
6021
+ }) => /* @__PURE__ */ jsx15(
6056
6022
  StickToBottom.Content,
6057
6023
  {
6058
6024
  className: cn("flex flex-col gap-8 p-4", className),
@@ -6067,7 +6033,7 @@ var ConversationScrollButton = ({
6067
6033
  const handleScrollToBottom = useCallback8(() => {
6068
6034
  scrollToBottom();
6069
6035
  }, [scrollToBottom]);
6070
- return !isAtBottom && /* @__PURE__ */ jsx14(
6036
+ return !isAtBottom && /* @__PURE__ */ jsx15(
6071
6037
  Button7,
6072
6038
  {
6073
6039
  className: cn(
@@ -6080,7 +6046,7 @@ var ConversationScrollButton = ({
6080
6046
  variant: "outline",
6081
6047
  "aria-label": "Scroll to latest message",
6082
6048
  ...props,
6083
- children: /* @__PURE__ */ jsx14(ArrowDownIcon, { className: "size-4" })
6049
+ children: /* @__PURE__ */ jsx15(ArrowDownIcon, { className: "size-4" })
6084
6050
  }
6085
6051
  );
6086
6052
  };
@@ -6116,16 +6082,16 @@ function noticeTextClass(className) {
6116
6082
  import { memo as memo2 } from "react";
6117
6083
  import { AlertTriangleIcon, InfoIcon, Loader2Icon, PlugZapIcon, RefreshCwIcon, XIcon as XIcon2 } from "lucide-react";
6118
6084
  import { Button as Button8 } from "@hachej/boring-ui-kit";
6119
- import { jsx as jsx15, jsxs as jsxs14 } from "react/jsx-runtime";
6085
+ import { jsx as jsx16, jsxs as jsxs14 } from "react/jsx-runtime";
6120
6086
  var RuntimeNotices = memo2(({ notices, onDismiss, onAction, renderAction, className, ...props }) => {
6121
6087
  if (notices.length === 0) return null;
6122
- return /* @__PURE__ */ jsx15(
6088
+ return /* @__PURE__ */ jsx16(
6123
6089
  "div",
6124
6090
  {
6125
6091
  "data-boring-agent-part": "runtime-notices",
6126
6092
  className: cn("flex flex-col gap-2 px-4 py-2", className),
6127
6093
  ...props,
6128
- children: notices.map((notice) => /* @__PURE__ */ jsx15(RuntimeNoticeRow, { notice, onDismiss, onAction, renderAction }, notice.id))
6094
+ children: notices.map((notice) => /* @__PURE__ */ jsx16(RuntimeNoticeRow, { notice, onDismiss, onAction, renderAction }, notice.id))
6129
6095
  }
6130
6096
  );
6131
6097
  });
@@ -6149,11 +6115,11 @@ function RuntimeNoticeRow({ notice, onDismiss, onAction, renderAction }) {
6149
6115
  "flex items-start gap-2.5 motion-reduce:transition-none"
6150
6116
  ),
6151
6117
  children: [
6152
- /* @__PURE__ */ jsx15(Icon, { className: cn(noticeIconClass(notice.level), kind === "retry" && "animate-spin motion-reduce:animate-none"), "aria-hidden": "true" }),
6153
- /* @__PURE__ */ jsx15("div", { className: "min-w-0 flex-1", children: /* @__PURE__ */ jsx15("p", { className: noticeTextClass(), children: notice.text }) }),
6118
+ /* @__PURE__ */ jsx16(Icon, { className: cn(noticeIconClass(notice.level), kind === "retry" && "animate-spin motion-reduce:animate-none"), "aria-hidden": "true" }),
6119
+ /* @__PURE__ */ jsx16("div", { className: "min-w-0 flex-1", children: /* @__PURE__ */ jsx16("p", { className: noticeTextClass(), children: notice.text }) }),
6154
6120
  hostAction ?? null,
6155
- actionLabel && onAction ? /* @__PURE__ */ jsx15(Button8, { type: "button", variant: "ghost", size: "sm", onClick: () => onAction(notice.id), children: actionLabel }) : null,
6156
- notice.dismissible && onDismiss ? /* @__PURE__ */ jsx15(
6121
+ actionLabel && onAction ? /* @__PURE__ */ jsx16(Button8, { type: "button", variant: "ghost", size: "sm", onClick: () => onAction(notice.id), children: actionLabel }) : null,
6122
+ notice.dismissible && onDismiss ? /* @__PURE__ */ jsx16(
6157
6123
  Button8,
6158
6124
  {
6159
6125
  type: "button",
@@ -6162,7 +6128,7 @@ function RuntimeNoticeRow({ notice, onDismiss, onAction, renderAction }) {
6162
6128
  "aria-label": "Dismiss notice",
6163
6129
  onClick: () => onDismiss(notice.id),
6164
6130
  className: "-mr-1 -mt-1 shrink-0 text-muted-foreground/70 hover:bg-muted hover:text-foreground",
6165
- children: /* @__PURE__ */ jsx15(XIcon2, { className: "size-3" })
6131
+ children: /* @__PURE__ */ jsx16(XIcon2, { className: "size-3" })
6166
6132
  }
6167
6133
  ) : null
6168
6134
  ]
@@ -6191,7 +6157,7 @@ function defaultActionLabel(kind) {
6191
6157
  }
6192
6158
 
6193
6159
  // src/front/chat/components/ChatNotices.tsx
6194
- import { jsx as jsx16, jsxs as jsxs15 } from "react/jsx-runtime";
6160
+ import { jsx as jsx17, jsxs as jsxs15 } from "react/jsx-runtime";
6195
6161
  function RuntimeNoticeMessages({
6196
6162
  notices,
6197
6163
  onDismiss,
@@ -6201,7 +6167,7 @@ function RuntimeNoticeMessages({
6201
6167
  (notice) => notice.level === "error" || notice.level === "warning" || notice.id === "connection-reconnecting" || notice.id === "auto-retry" || notice.id === "large-state-warning" || notice.id.startsWith("command:") || notice.id.startsWith("composer-warning:")
6202
6168
  );
6203
6169
  if (visible.length === 0) return null;
6204
- return /* @__PURE__ */ jsx16(
6170
+ return /* @__PURE__ */ jsx17(
6205
6171
  RuntimeNotices,
6206
6172
  {
6207
6173
  notices: visible,
@@ -6213,7 +6179,7 @@ function RuntimeNoticeMessages({
6213
6179
  }
6214
6180
  function ComposerRuntimeNotice({ notice }) {
6215
6181
  const level = notice.code === ErrorCode.enum.AGENT_RUNTIME_NOT_READY ? "info" : "error";
6216
- return /* @__PURE__ */ jsx16(
6182
+ return /* @__PURE__ */ jsx17(
6217
6183
  "div",
6218
6184
  {
6219
6185
  "data-testid": "chat-composer-runtime-notice",
@@ -6221,17 +6187,17 @@ function ComposerRuntimeNotice({ notice }) {
6221
6187
  "aria-live": "polite",
6222
6188
  className: noticeSurfaceClass(level, "mx-auto mb-2 w-full max-w-3xl text-xs"),
6223
6189
  children: /* @__PURE__ */ jsxs15("div", { className: "flex items-start gap-2", children: [
6224
- notice.code === ErrorCode.enum.AGENT_RUNTIME_NOT_READY ? /* @__PURE__ */ jsx16(Loader2, { "aria-hidden": "true", className: "mt-0.5 size-3.5 shrink-0 animate-spin text-muted-foreground" }) : /* @__PURE__ */ jsx16(AlertCircleIcon, { "aria-hidden": "true", className: "mt-0.5 size-3.5 shrink-0 text-destructive" }),
6190
+ notice.code === ErrorCode.enum.AGENT_RUNTIME_NOT_READY ? /* @__PURE__ */ jsx17(Loader2, { "aria-hidden": "true", className: "mt-0.5 size-3.5 shrink-0 animate-spin text-muted-foreground" }) : /* @__PURE__ */ jsx17(AlertCircleIcon, { "aria-hidden": "true", className: "mt-0.5 size-3.5 shrink-0 text-destructive" }),
6225
6191
  /* @__PURE__ */ jsxs15("div", { className: "min-w-0", children: [
6226
- /* @__PURE__ */ jsx16("div", { className: "font-medium", children: notice.title }),
6227
- notice.detail ? /* @__PURE__ */ jsx16(NoticeText, { className: "mt-0.5 text-muted-foreground", children: notice.detail }) : null
6192
+ /* @__PURE__ */ jsx17("div", { className: "font-medium", children: notice.title }),
6193
+ notice.detail ? /* @__PURE__ */ jsx17(NoticeText, { className: "mt-0.5 text-muted-foreground", children: notice.detail }) : null
6228
6194
  ] })
6229
6195
  ] })
6230
6196
  }
6231
6197
  );
6232
6198
  }
6233
6199
  function NoticeText({ className, children }) {
6234
- return /* @__PURE__ */ jsx16("div", { className: noticeTextClass(className), children });
6200
+ return /* @__PURE__ */ jsx17("div", { className: noticeTextClass(className), children });
6235
6201
  }
6236
6202
  function ComposerBlockerNotice({
6237
6203
  blocker,
@@ -6245,10 +6211,10 @@ function ComposerBlockerNotice({
6245
6211
  "aria-live": "polite",
6246
6212
  className: noticeSurfaceClass("info", "relative z-20 mx-auto mb-2 w-full max-w-3xl text-xs"),
6247
6213
  children: [
6248
- /* @__PURE__ */ jsx16("span", { children: label }),
6214
+ /* @__PURE__ */ jsx17("span", { children: label }),
6249
6215
  blocker?.actions?.map((action) => {
6250
6216
  const icon = composerBlockerActionIcon(action.id);
6251
- return icon ? /* @__PURE__ */ jsx16(
6217
+ return icon ? /* @__PURE__ */ jsx17(
6252
6218
  IconButton2,
6253
6219
  {
6254
6220
  type: "button",
@@ -6261,7 +6227,7 @@ function ComposerBlockerNotice({
6261
6227
  children: icon
6262
6228
  },
6263
6229
  action.id
6264
- ) : /* @__PURE__ */ jsx16(
6230
+ ) : /* @__PURE__ */ jsx17(
6265
6231
  "button",
6266
6232
  {
6267
6233
  type: "button",
@@ -6277,8 +6243,8 @@ function ComposerBlockerNotice({
6277
6243
  );
6278
6244
  }
6279
6245
  function composerBlockerActionIcon(actionId) {
6280
- if (actionId === "open") return /* @__PURE__ */ jsx16(ExternalLinkIcon2, { className: "size-3.5", "aria-hidden": "true" });
6281
- if (actionId === "cancel") return /* @__PURE__ */ jsx16(XIcon3, { className: "size-3.5", "aria-hidden": "true" });
6246
+ if (actionId === "open") return /* @__PURE__ */ jsx17(ExternalLinkIcon2, { className: "size-3.5", "aria-hidden": "true" });
6247
+ if (actionId === "cancel") return /* @__PURE__ */ jsx17(XIcon3, { className: "size-3.5", "aria-hidden": "true" });
6282
6248
  return null;
6283
6249
  }
6284
6250
  function QueuedComposerNotice({ followUps, onEdit }) {
@@ -6297,9 +6263,9 @@ function QueuedComposerNotice({ followUps, onEdit }) {
6297
6263
  " queued follow-up",
6298
6264
  followUps.length === 1 ? "" : "s"
6299
6265
  ] }),
6300
- /* @__PURE__ */ jsx16("div", { className: "truncate text-[color:var(--muted-foreground)]", "data-boring-agent-part": "composer-queue-preview-text", children: followUps.map((followUp) => followUp.displayText).join(" - ") })
6266
+ /* @__PURE__ */ jsx17("div", { className: "truncate text-[color:var(--muted-foreground)]", "data-boring-agent-part": "composer-queue-preview-text", children: followUps.map((followUp) => followUp.displayText).join(" - ") })
6301
6267
  ] }),
6302
- /* @__PURE__ */ jsx16(
6268
+ /* @__PURE__ */ jsx17(
6303
6269
  IconButton2,
6304
6270
  {
6305
6271
  type: "button",
@@ -6309,7 +6275,7 @@ function QueuedComposerNotice({ followUps, onEdit }) {
6309
6275
  className: "shrink-0 text-[color:var(--muted-foreground)] hover:bg-[color:var(--muted)] hover:text-[color:var(--foreground)]",
6310
6276
  "aria-label": "Edit queued follow-ups",
6311
6277
  title: "Edit queued follow-ups",
6312
- children: /* @__PURE__ */ jsx16(ListRestartIcon, { className: "size-3.5", "aria-hidden": "true" })
6278
+ children: /* @__PURE__ */ jsx17(ListRestartIcon, { className: "size-3.5", "aria-hidden": "true" })
6313
6279
  }
6314
6280
  )
6315
6281
  ]
@@ -6318,7 +6284,7 @@ function QueuedComposerNotice({ followUps, onEdit }) {
6318
6284
  }
6319
6285
 
6320
6286
  // src/front/chat/components/PiTimelineMessage.tsx
6321
- import { useCallback as useCallback13, useEffect as useEffect13, useState as useState14 } from "react";
6287
+ import { useCallback as useCallback13, useEffect as useEffect13, useMemo as useMemo12, useRef as useRef10, useState as useState14 } from "react";
6322
6288
  import { CheckIcon as CheckIcon2, CopyIcon as CopyIcon3 } from "lucide-react";
6323
6289
  import { Button as Button11 } from "@hachej/boring-ui-kit";
6324
6290
 
@@ -6333,8 +6299,8 @@ import {
6333
6299
  VideoIcon,
6334
6300
  XIcon as XIcon4
6335
6301
  } from "lucide-react";
6336
- import { createContext as createContext3, useCallback as useCallback9, useContext as useContext3, useMemo as useMemo5 } from "react";
6337
- import { jsx as jsx17, jsxs as jsxs16 } from "react/jsx-runtime";
6302
+ import { createContext as createContext4, useCallback as useCallback9, useContext as useContext4, useMemo as useMemo6 } from "react";
6303
+ import { jsx as jsx18, jsxs as jsxs16 } from "react/jsx-runtime";
6338
6304
  var mediaCategoryIcons = {
6339
6305
  audio: Music2Icon,
6340
6306
  document: FileTextIcon2,
@@ -6369,7 +6335,7 @@ var getAttachmentLabel = (data) => {
6369
6335
  const category = getMediaCategory(data);
6370
6336
  return data.filename || (category === "image" ? "Image" : "Attachment");
6371
6337
  };
6372
- var renderAttachmentImage = (url, filename, isGrid) => isGrid ? /* @__PURE__ */ jsx17(
6338
+ var renderAttachmentImage = (url, filename, isGrid) => isGrid ? /* @__PURE__ */ jsx18(
6373
6339
  "img",
6374
6340
  {
6375
6341
  alt: filename || "Image",
@@ -6378,7 +6344,7 @@ var renderAttachmentImage = (url, filename, isGrid) => isGrid ? /* @__PURE__ */
6378
6344
  src: url,
6379
6345
  width: 96
6380
6346
  }
6381
- ) : /* @__PURE__ */ jsx17(
6347
+ ) : /* @__PURE__ */ jsx18(
6382
6348
  "img",
6383
6349
  {
6384
6350
  alt: filename || "Image",
@@ -6388,11 +6354,11 @@ var renderAttachmentImage = (url, filename, isGrid) => isGrid ? /* @__PURE__ */
6388
6354
  width: 20
6389
6355
  }
6390
6356
  );
6391
- var AttachmentsContext = createContext3(null);
6392
- var AttachmentContext = createContext3(null);
6393
- var useAttachmentsContext = () => useContext3(AttachmentsContext) ?? { variant: "grid" };
6357
+ var AttachmentsContext = createContext4(null);
6358
+ var AttachmentContext = createContext4(null);
6359
+ var useAttachmentsContext = () => useContext4(AttachmentsContext) ?? { variant: "grid" };
6394
6360
  var useAttachmentContext = () => {
6395
- const ctx = useContext3(AttachmentContext);
6361
+ const ctx = useContext4(AttachmentContext);
6396
6362
  if (!ctx) {
6397
6363
  throw new Error("Attachment components must be used within <Attachment>");
6398
6364
  }
@@ -6404,8 +6370,8 @@ var Attachments = ({
6404
6370
  children,
6405
6371
  ...props
6406
6372
  }) => {
6407
- const contextValue = useMemo5(() => ({ variant }), [variant]);
6408
- return /* @__PURE__ */ jsx17(AttachmentsContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx17(
6373
+ const contextValue = useMemo6(() => ({ variant }), [variant]);
6374
+ return /* @__PURE__ */ jsx18(AttachmentsContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx18(
6409
6375
  "div",
6410
6376
  {
6411
6377
  className: cn(
@@ -6428,11 +6394,11 @@ var Attachment = ({
6428
6394
  }) => {
6429
6395
  const { variant } = useAttachmentsContext();
6430
6396
  const mediaCategory = getMediaCategory(data);
6431
- const contextValue = useMemo5(
6397
+ const contextValue = useMemo6(
6432
6398
  () => ({ data, mediaCategory, onRemove, variant }),
6433
6399
  [data, mediaCategory, onRemove, variant]
6434
6400
  );
6435
- return /* @__PURE__ */ jsx17(AttachmentContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx17(
6401
+ return /* @__PURE__ */ jsx18(AttachmentContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx18(
6436
6402
  "div",
6437
6403
  {
6438
6404
  className: cn(
@@ -6462,18 +6428,18 @@ var AttachmentPreview = ({
6462
6428
  }) => {
6463
6429
  const { data, mediaCategory, variant } = useAttachmentContext();
6464
6430
  const iconSize = variant === "inline" ? "size-3" : "size-4";
6465
- const renderIcon = (Icon) => /* @__PURE__ */ jsx17(Icon, { className: cn(iconSize, "text-muted-foreground") });
6431
+ const renderIcon = (Icon) => /* @__PURE__ */ jsx18(Icon, { className: cn(iconSize, "text-muted-foreground") });
6466
6432
  const renderContent = () => {
6467
6433
  if (mediaCategory === "image" && data.type === "file" && data.url) {
6468
6434
  return renderAttachmentImage(data.url, data.filename, variant === "grid");
6469
6435
  }
6470
6436
  if (mediaCategory === "video" && data.type === "file" && data.url) {
6471
- return /* @__PURE__ */ jsx17("video", { className: "size-full object-cover", muted: true, src: data.url });
6437
+ return /* @__PURE__ */ jsx18("video", { className: "size-full object-cover", muted: true, src: data.url });
6472
6438
  }
6473
6439
  const Icon = mediaCategoryIcons[mediaCategory];
6474
6440
  return fallbackIcon ?? renderIcon(Icon);
6475
6441
  };
6476
- return /* @__PURE__ */ jsx17(
6442
+ return /* @__PURE__ */ jsx18(
6477
6443
  "div",
6478
6444
  {
6479
6445
  className: cn(
@@ -6499,8 +6465,8 @@ var AttachmentInfo = ({
6499
6465
  return null;
6500
6466
  }
6501
6467
  return /* @__PURE__ */ jsxs16("div", { className: cn("min-w-0 flex-1", className), ...props, children: [
6502
- /* @__PURE__ */ jsx17("span", { className: "block truncate", children: label }),
6503
- showMediaType && data.mediaType && /* @__PURE__ */ jsx17("span", { className: "block truncate text-muted-foreground text-xs", children: data.mediaType })
6468
+ /* @__PURE__ */ jsx18("span", { className: "block truncate", children: label }),
6469
+ showMediaType && data.mediaType && /* @__PURE__ */ jsx18("span", { className: "block truncate text-muted-foreground text-xs", children: data.mediaType })
6504
6470
  ] });
6505
6471
  };
6506
6472
  var AttachmentRemove = ({
@@ -6545,8 +6511,8 @@ var AttachmentRemove = ({
6545
6511
  variant: "ghost",
6546
6512
  ...props,
6547
6513
  children: [
6548
- children ?? /* @__PURE__ */ jsx17(XIcon4, {}),
6549
- /* @__PURE__ */ jsx17("span", { className: "sr-only", children: label })
6514
+ children ?? /* @__PURE__ */ jsx18(XIcon4, {}),
6515
+ /* @__PURE__ */ jsx18("span", { className: "sr-only", children: label })
6550
6516
  ]
6551
6517
  }
6552
6518
  );
@@ -6560,28 +6526,33 @@ import {
6560
6526
  TooltipProvider as TooltipProvider2,
6561
6527
  TooltipTrigger as TooltipTrigger2
6562
6528
  } from "@hachej/boring-ui-kit";
6529
+
6530
+ // src/front/primitives/markdownStyles.ts
6531
+ var INLINE_CODE_CLASS_NAME = "rounded-[0.3em] bg-muted/55 px-[0.32em] py-[0.08em] font-mono text-[0.9em] font-medium text-foreground/90";
6532
+
6533
+ // src/front/primitives/message.tsx
6563
6534
  import { cjk } from "@streamdown/cjk";
6564
6535
  import { code } from "@streamdown/code";
6565
6536
  import { math } from "@streamdown/math";
6566
6537
  import { mermaid } from "@streamdown/mermaid";
6567
6538
  import { ChevronLeftIcon as ChevronLeftIcon2, ChevronRightIcon } from "lucide-react";
6568
6539
  import {
6569
- createContext as createContext4,
6540
+ createContext as createContext5,
6570
6541
  memo as memo3,
6571
6542
  useCallback as useCallback10,
6572
- useContext as useContext4,
6543
+ useContext as useContext5,
6573
6544
  useEffect as useEffect10,
6574
- useMemo as useMemo6,
6545
+ useMemo as useMemo7,
6575
6546
  useState as useState11
6576
6547
  } from "react";
6577
6548
  import { Streamdown } from "streamdown";
6578
- import { jsx as jsx18, jsxs as jsxs17 } from "react/jsx-runtime";
6579
- var Message = ({ className, from, ...props }) => /* @__PURE__ */ jsx18(
6549
+ import { jsx as jsx19, jsxs as jsxs17 } from "react/jsx-runtime";
6550
+ var Message = ({ className, from, ...props }) => /* @__PURE__ */ jsx19(
6580
6551
  "div",
6581
6552
  {
6582
6553
  "data-boring-agent-message-role": from,
6583
6554
  className: cn(
6584
- "group flex w-full max-w-[95%] flex-col gap-2",
6555
+ "group flex w-full max-w-full flex-col gap-1.5",
6585
6556
  from === "user" ? "is-user ml-auto justify-end" : "is-assistant",
6586
6557
  className
6587
6558
  ),
@@ -6592,13 +6563,13 @@ var MessageContent = ({
6592
6563
  children,
6593
6564
  className,
6594
6565
  ...props
6595
- }) => /* @__PURE__ */ jsx18(
6566
+ }) => /* @__PURE__ */ jsx19(
6596
6567
  "div",
6597
6568
  {
6598
6569
  className: cn(
6599
- "is-user:dark flex w-fit min-w-0 max-w-full flex-col gap-2 overflow-hidden text-sm",
6600
- "group-[.is-user]:ml-auto group-[.is-user]:rounded-lg group-[.is-user]:bg-secondary group-[.is-user]:px-4 group-[.is-user]:py-3 group-[.is-user]:text-foreground",
6601
- "group-[.is-assistant]:text-foreground",
6570
+ "is-user:dark flex w-fit min-w-0 max-w-full flex-col gap-2 overflow-visible text-[13px] leading-5",
6571
+ "group-[.is-user]:ml-auto group-[.is-user]:max-w-[80%] group-[.is-user]:rounded-[var(--radius-lg)] group-[.is-user]:bg-secondary group-[.is-user]:px-4 group-[.is-user]:py-2.5 group-[.is-user]:text-foreground",
6572
+ "group-[.is-assistant]:w-full group-[.is-assistant]:max-w-[40.5rem] group-[.is-assistant]:bg-transparent group-[.is-assistant]:p-0 group-[.is-assistant]:text-foreground",
6602
6573
  className
6603
6574
  ),
6604
6575
  ...props,
@@ -6609,7 +6580,7 @@ var MessageActions = ({
6609
6580
  className,
6610
6581
  children,
6611
6582
  ...props
6612
- }) => /* @__PURE__ */ jsx18("div", { className: cn("flex items-center gap-1", className), ...props, children });
6583
+ }) => /* @__PURE__ */ jsx19("div", { className: cn("flex items-center gap-1", className), ...props, children });
6613
6584
  var MessageAction = ({
6614
6585
  tooltip,
6615
6586
  children,
@@ -6620,17 +6591,17 @@ var MessageAction = ({
6620
6591
  }) => {
6621
6592
  const button = /* @__PURE__ */ jsxs17(Button10, { size, type: "button", variant, ...props, children: [
6622
6593
  children,
6623
- /* @__PURE__ */ jsx18("span", { className: "sr-only", children: label || tooltip })
6594
+ /* @__PURE__ */ jsx19("span", { className: "sr-only", children: label || tooltip })
6624
6595
  ] });
6625
6596
  if (tooltip) {
6626
- return /* @__PURE__ */ jsx18(TooltipProvider2, { children: /* @__PURE__ */ jsxs17(Tooltip2, { children: [
6627
- /* @__PURE__ */ jsx18(TooltipTrigger2, { asChild: true, children: button }),
6628
- /* @__PURE__ */ jsx18(TooltipContent2, { children: /* @__PURE__ */ jsx18("p", { children: tooltip }) })
6597
+ return /* @__PURE__ */ jsx19(TooltipProvider2, { children: /* @__PURE__ */ jsxs17(Tooltip2, { children: [
6598
+ /* @__PURE__ */ jsx19(TooltipTrigger2, { asChild: true, children: button }),
6599
+ /* @__PURE__ */ jsx19(TooltipContent2, { children: /* @__PURE__ */ jsx19("p", { children: tooltip }) })
6629
6600
  ] }) });
6630
6601
  }
6631
6602
  return button;
6632
6603
  };
6633
- var MessageBranchContext = createContext4(
6604
+ var MessageBranchContext = createContext5(
6634
6605
  null
6635
6606
  );
6636
6607
  var streamdownPlugins = { cjk, code, math, mermaid };
@@ -6666,9 +6637,9 @@ function extractCodeBlockText(children) {
6666
6637
  var MarkdownPre = ({ children }) => {
6667
6638
  const code3 = extractCodeBlockText(children).replace(/\n$/, "");
6668
6639
  const language = extractCodeBlockLanguage(children);
6669
- return /* @__PURE__ */ jsx18(CodeBlock2, { code: code3, language: language ?? "text", className: "my-3", children: /* @__PURE__ */ jsxs17(CodeBlockHeader, { children: [
6670
- /* @__PURE__ */ jsx18(CodeBlockTitle, { children: /* @__PURE__ */ jsx18(CodeBlockFilename, { children: language ?? "text" }) }),
6671
- /* @__PURE__ */ jsx18(CodeBlockCopyButton, {})
6640
+ return /* @__PURE__ */ jsx19(CodeBlock2, { code: code3, language: language ?? "text", className: "my-3", children: /* @__PURE__ */ jsxs17(CodeBlockHeader, { children: [
6641
+ /* @__PURE__ */ jsx19(CodeBlockTitle, { children: /* @__PURE__ */ jsx19(CodeBlockFilename, { children: language ?? "text" }) }),
6642
+ /* @__PURE__ */ jsx19(CodeBlockCopyButton, {})
6672
6643
  ] }) });
6673
6644
  };
6674
6645
  var MarkdownCode = ({
@@ -6678,14 +6649,13 @@ var MarkdownCode = ({
6678
6649
  ...props
6679
6650
  }) => {
6680
6651
  if (inline === false) {
6681
- return /* @__PURE__ */ jsx18("code", { className, ...props, children });
6652
+ return /* @__PURE__ */ jsx19("code", { className, ...props, children });
6682
6653
  }
6683
- return /* @__PURE__ */ jsx18(
6654
+ return /* @__PURE__ */ jsx19(
6684
6655
  "code",
6685
6656
  {
6686
6657
  className: cn(
6687
- "rounded-[0.3em] bg-muted/55 px-[0.32em] py-[0.08em]",
6688
- "font-mono text-[0.9em] font-medium text-foreground/90",
6658
+ INLINE_CODE_CLASS_NAME,
6689
6659
  className
6690
6660
  ),
6691
6661
  ...props,
@@ -6698,7 +6668,7 @@ var markdownComponents = {
6698
6668
  code: MarkdownCode
6699
6669
  };
6700
6670
  var MessageResponse = memo3(
6701
- ({ className, shikiTheme, components, ...props }) => /* @__PURE__ */ jsx18(
6671
+ ({ className, shikiTheme, components, ...props }) => /* @__PURE__ */ jsx19(
6702
6672
  Streamdown,
6703
6673
  {
6704
6674
  className: cn(
@@ -6711,7 +6681,7 @@ var MessageResponse = memo3(
6711
6681
  ...props
6712
6682
  }
6713
6683
  ),
6714
- (prevProps, nextProps) => prevProps.children === nextProps.children && nextProps.isAnimating === prevProps.isAnimating
6684
+ (prevProps, nextProps) => prevProps.children === nextProps.children && nextProps.isAnimating === prevProps.isAnimating && nextProps.components === prevProps.components
6715
6685
  );
6716
6686
  MessageResponse.displayName = "MessageResponse";
6717
6687
 
@@ -6728,12 +6698,12 @@ import { math as math2 } from "@streamdown/math";
6728
6698
  import { mermaid as mermaid2 } from "@streamdown/mermaid";
6729
6699
  import { BrainIcon, ChevronDownIcon as ChevronDownIcon3 } from "lucide-react";
6730
6700
  import {
6731
- createContext as createContext5,
6701
+ createContext as createContext6,
6732
6702
  memo as memo5,
6733
6703
  useCallback as useCallback11,
6734
- useContext as useContext5,
6704
+ useContext as useContext6,
6735
6705
  useEffect as useEffect11,
6736
- useMemo as useMemo8,
6706
+ useMemo as useMemo9,
6737
6707
  useRef as useRef9,
6738
6708
  useState as useState12
6739
6709
  } from "react";
@@ -6741,8 +6711,8 @@ import { Streamdown as Streamdown2 } from "streamdown";
6741
6711
 
6742
6712
  // src/front/primitives/shimmer.tsx
6743
6713
  import { motion } from "motion/react";
6744
- import { memo as memo4, useMemo as useMemo7 } from "react";
6745
- import { jsx as jsx19 } from "react/jsx-runtime";
6714
+ import { memo as memo4, useMemo as useMemo8 } from "react";
6715
+ import { jsx as jsx20 } from "react/jsx-runtime";
6746
6716
  var motionComponentCache = /* @__PURE__ */ new Map();
6747
6717
  var getMotionComponent = (element) => {
6748
6718
  let component = motionComponentCache.get(element);
@@ -6762,11 +6732,11 @@ var ShimmerComponent = ({
6762
6732
  const MotionComponent = getMotionComponent(
6763
6733
  Component
6764
6734
  );
6765
- const dynamicSpread = useMemo7(
6735
+ const dynamicSpread = useMemo8(
6766
6736
  () => (children?.length ?? 0) * spread,
6767
6737
  [children, spread]
6768
6738
  );
6769
- return /* @__PURE__ */ jsx19(
6739
+ return /* @__PURE__ */ jsx20(
6770
6740
  MotionComponent,
6771
6741
  {
6772
6742
  animate: { backgroundPosition: "0% center" },
@@ -6792,10 +6762,10 @@ var ShimmerComponent = ({
6792
6762
  var Shimmer = memo4(ShimmerComponent);
6793
6763
 
6794
6764
  // src/front/primitives/reasoning.tsx
6795
- import { Fragment as Fragment6, jsx as jsx20, jsxs as jsxs18 } from "react/jsx-runtime";
6796
- var ReasoningContext = createContext5(null);
6765
+ import { Fragment as Fragment7, jsx as jsx21, jsxs as jsxs18 } from "react/jsx-runtime";
6766
+ var ReasoningContext = createContext6(null);
6797
6767
  var useReasoning = () => {
6798
- const context = useContext5(ReasoningContext);
6768
+ const context = useContext6(ReasoningContext);
6799
6769
  if (!context) {
6800
6770
  throw new Error("Reasoning components must be used within Reasoning");
6801
6771
  }
@@ -6860,11 +6830,11 @@ var Reasoning = memo5(
6860
6830
  },
6861
6831
  [setIsOpen]
6862
6832
  );
6863
- const contextValue = useMemo8(
6833
+ const contextValue = useMemo9(
6864
6834
  () => ({ duration, isOpen, isStreaming, setIsOpen }),
6865
6835
  [duration, isOpen, isStreaming, setIsOpen]
6866
6836
  );
6867
- return /* @__PURE__ */ jsx20(ReasoningContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx20(
6837
+ return /* @__PURE__ */ jsx21(ReasoningContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx21(
6868
6838
  Collapsible2,
6869
6839
  {
6870
6840
  className: cn("not-prose mb-2", className),
@@ -6878,10 +6848,10 @@ var Reasoning = memo5(
6878
6848
  );
6879
6849
  var defaultGetThinkingMessage = (isStreaming, duration) => {
6880
6850
  if (isStreaming || duration === 0) {
6881
- return /* @__PURE__ */ jsx20(Shimmer, { duration: 1, children: "thinking" });
6851
+ return /* @__PURE__ */ jsx21(Shimmer, { duration: 1, children: "thinking" });
6882
6852
  }
6883
6853
  if (duration === void 0) {
6884
- return /* @__PURE__ */ jsx20("span", { children: "thoughts" });
6854
+ return /* @__PURE__ */ jsx21("span", { children: "thoughts" });
6885
6855
  }
6886
6856
  return /* @__PURE__ */ jsxs18("span", { children: [
6887
6857
  "thoughts \xB7 ",
@@ -6897,7 +6867,7 @@ var ReasoningTrigger = memo5(
6897
6867
  ...props
6898
6868
  }) => {
6899
6869
  const { isStreaming, isOpen, duration } = useReasoning();
6900
- return /* @__PURE__ */ jsx20(
6870
+ return /* @__PURE__ */ jsx21(
6901
6871
  CollapsibleTrigger3,
6902
6872
  {
6903
6873
  className: cn(
@@ -6905,10 +6875,10 @@ var ReasoningTrigger = memo5(
6905
6875
  className
6906
6876
  ),
6907
6877
  ...props,
6908
- children: children ?? /* @__PURE__ */ jsxs18(Fragment6, { children: [
6909
- /* @__PURE__ */ jsx20(BrainIcon, { className: "size-3" }),
6878
+ children: children ?? /* @__PURE__ */ jsxs18(Fragment7, { children: [
6879
+ /* @__PURE__ */ jsx21(BrainIcon, { className: "size-3" }),
6910
6880
  getThinkingMessage(isStreaming, duration),
6911
- /* @__PURE__ */ jsx20(
6881
+ /* @__PURE__ */ jsx21(
6912
6882
  ChevronDownIcon3,
6913
6883
  {
6914
6884
  className: cn(
@@ -6924,7 +6894,7 @@ var ReasoningTrigger = memo5(
6924
6894
  );
6925
6895
  var streamdownPlugins2 = { cjk: cjk2, code: code2, math: math2, mermaid: mermaid2 };
6926
6896
  var ReasoningContent = memo5(
6927
- ({ className, children, ...props }) => /* @__PURE__ */ jsx20(
6897
+ ({ className, children, ...props }) => /* @__PURE__ */ jsx21(
6928
6898
  CollapsibleContent2,
6929
6899
  {
6930
6900
  className: cn(
@@ -6933,7 +6903,7 @@ var ReasoningContent = memo5(
6933
6903
  className
6934
6904
  ),
6935
6905
  ...props,
6936
- children: /* @__PURE__ */ jsx20(Streamdown2, { plugins: streamdownPlugins2, children })
6906
+ children: /* @__PURE__ */ jsx21(Streamdown2, { plugins: streamdownPlugins2, children })
6937
6907
  }
6938
6908
  )
6939
6909
  );
@@ -6943,7 +6913,7 @@ ReasoningContent.displayName = "ReasoningContent";
6943
6913
 
6944
6914
  // src/front/primitives/tool-call-group.tsx
6945
6915
  import { ChevronDownIcon as ChevronDownIcon4 } from "lucide-react";
6946
- import { memo as memo6, useCallback as useCallback12, useEffect as useEffect12, useMemo as useMemo9, useState as useState13 } from "react";
6916
+ import { memo as memo6, useCallback as useCallback12, useEffect as useEffect12, useMemo as useMemo10, useState as useState13 } from "react";
6947
6917
  import { Collapsible as Collapsible3, CollapsibleContent as CollapsibleContent3, CollapsibleTrigger as CollapsibleTrigger4 } from "@hachej/boring-ui-kit";
6948
6918
 
6949
6919
  // src/front/primitives/tool-call-group-state.ts
@@ -6982,10 +6952,7 @@ var TOOL_GROUP_VISUAL_PRESENTATION = {
6982
6952
  };
6983
6953
 
6984
6954
  // src/front/primitives/tool-call-group.tsx
6985
- import { jsx as jsx21, jsxs as jsxs19 } from "react/jsx-runtime";
6986
- function isSettledState(state) {
6987
- return state === "output-available" || state === "output-error" || state === "output-denied" || state === "approval-responded" || state === "aborted";
6988
- }
6955
+ import { jsx as jsx22, jsxs as jsxs19 } from "react/jsx-runtime";
6989
6956
  function isInputRunningState(state) {
6990
6957
  return state === "input-streaming" || state === "input-available";
6991
6958
  }
@@ -7035,11 +7002,10 @@ function resolveVisualState({
7035
7002
  return "settled";
7036
7003
  }
7037
7004
  var ToolCallGroup = memo6(({ tools, mergedToolRenderers }) => {
7038
- const toolParts = useMemo9(() => tools.map(({ part, key }) => {
7005
+ const toolParts = useMemo10(() => tools.map(({ part, key }) => {
7039
7006
  const toolPart = toToolPart(part);
7040
7007
  return toolPart ? { part: toolPart, key } : null;
7041
7008
  }).filter((entry) => Boolean(entry)), [tools]);
7042
- const isSettled = toolParts.every(({ part }) => isSettledState(part.state));
7043
7009
  const workspaceNotReady = toolParts.map(({ part }) => getWorkspaceNotReadyStatus(part.output)).find(Boolean);
7044
7010
  const hasError = toolParts.some(({ part }) => part.state === "output-error") && !workspaceNotReady;
7045
7011
  const hasAborted = toolParts.some(({ part }) => part.state === "aborted") && !workspaceNotReady;
@@ -7056,7 +7022,7 @@ var ToolCallGroup = memo6(({ tools, mergedToolRenderers }) => {
7056
7022
  });
7057
7023
  const isRunning = visualState === RUNNING_TOOL_GROUP_VISUAL_STATE;
7058
7024
  const [elapsedSeconds, setElapsedSeconds] = useState13(0);
7059
- const title = useMemo9(() => buildTitle(toolParts.map(({ part }) => part), visualState), [toolParts, visualState]);
7025
+ const title = useMemo10(() => buildTitle(toolParts.map(({ part }) => part), visualState), [toolParts, visualState]);
7060
7026
  const elapsedLabel = isRunning ? `Running ${formatElapsed(elapsedSeconds)}` : null;
7061
7027
  useEffect12(() => {
7062
7028
  if (!isRunning) {
@@ -7084,50 +7050,48 @@ var ToolCallGroup = memo6(({ tools, mergedToolRenderers }) => {
7084
7050
  {
7085
7051
  "aria-label": `Tool calls: ${title}`,
7086
7052
  className: cn(
7087
- "group/trigger flex w-fit items-center gap-1.5 rounded-md px-2.5 py-1 text-[12px] font-medium transition-colors motion-reduce:transition-none",
7088
- "border border-border/40 bg-card/40 text-muted-foreground/70",
7089
- "hover:border-border/70 hover:bg-card/70 hover:text-muted-foreground",
7053
+ "group/trigger flex min-h-10 w-full items-center gap-2 rounded-[var(--radius-md)] px-2 py-1 text-[13px] font-normal",
7054
+ "border border-border/60 bg-muted/45 text-muted-foreground",
7055
+ "transition-colors hover:bg-muted/70 hover:text-foreground motion-reduce:transition-none",
7056
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
7090
7057
  TOOL_GROUP_VISUAL_PRESENTATION[visualState].triggerClassName
7091
7058
  ),
7092
7059
  children: [
7093
- /* @__PURE__ */ jsx21(
7060
+ /* @__PURE__ */ jsx22("span", { className: "flex size-4 shrink-0 items-center justify-center", "aria-hidden": "true", children: /* @__PURE__ */ jsx22(
7094
7061
  "span",
7095
7062
  {
7096
7063
  "data-boring-agent-part": "tool-group-state-dot",
7097
7064
  className: cn(
7098
- "size-1.5 shrink-0 rounded-full",
7065
+ "size-1.5 rounded-full",
7099
7066
  TOOL_GROUP_VISUAL_PRESENTATION[visualState].dotClassName
7100
7067
  )
7101
7068
  }
7102
- ),
7103
- /* @__PURE__ */ jsx21(
7069
+ ) }),
7070
+ /* @__PURE__ */ jsx22("span", { "data-boring-agent-part": "tool-group-title", children: workspaceNotReady ? workspaceNotReady.message : title }),
7071
+ elapsedLabel ? /* @__PURE__ */ jsx22("span", { className: "ml-1 shrink-0 tabular-nums text-[12px] text-muted-foreground/70", children: elapsedLabel }) : null,
7072
+ /* @__PURE__ */ jsx22("span", { className: "ml-1 shrink-0 tabular-nums text-[12px] text-muted-foreground/70", children: toolParts.length }),
7073
+ /* @__PURE__ */ jsx22(
7104
7074
  ChevronDownIcon4,
7105
7075
  {
7106
7076
  className: cn(
7107
- "size-3 shrink-0 transition-transform duration-150",
7077
+ "ml-auto size-4 shrink-0 transition-transform duration-150 motion-reduce:transition-none",
7108
7078
  isOpen && "rotate-180"
7109
7079
  )
7110
7080
  }
7111
- ),
7112
- workspaceNotReady ? /* @__PURE__ */ jsx21("span", { "data-boring-agent-part": "tool-group-title", children: workspaceNotReady.message }) : !isSettled ? /* @__PURE__ */ jsx21("span", { "data-boring-agent-part": "tool-group-title", children: /* @__PURE__ */ jsx21(Shimmer, { as: "span", duration: 1.5, children: title }) }) : /* @__PURE__ */ jsx21("span", { "data-boring-agent-part": "tool-group-title", children: title }),
7113
- elapsedLabel ? /* @__PURE__ */ jsx21("span", { className: "ml-1 shrink-0 tabular-nums text-[10px] text-muted-foreground/50", children: elapsedLabel }) : null,
7114
- /* @__PURE__ */ jsx21("span", { className: cn(
7115
- "ml-1 shrink-0 rounded-sm border border-border/40 px-1 tabular-nums",
7116
- "text-[10px] text-muted-foreground/50"
7117
- ), children: toolParts.length })
7081
+ )
7118
7082
  ]
7119
7083
  }
7120
7084
  ),
7121
- /* @__PURE__ */ jsx21(CollapsibleContent3, { className: "overflow-hidden data-[state=closed]:animate-[boring-collapse-close_200ms_ease] data-[state=open]:animate-[boring-collapse-open_200ms_ease]", children: /* @__PURE__ */ jsxs19(
7085
+ /* @__PURE__ */ jsx22(CollapsibleContent3, { className: "overflow-hidden data-[state=closed]:animate-[boring-collapse-close_200ms_ease] data-[state=open]:animate-[boring-collapse-open_200ms_ease]", children: /* @__PURE__ */ jsxs19(
7122
7086
  "div",
7123
7087
  {
7124
7088
  "data-boring-agent-part": "tool-group-details",
7125
7089
  className: "relative mt-1.5 w-full max-w-2xl pl-[18px]",
7126
7090
  children: [
7127
- /* @__PURE__ */ jsx21("div", { className: "absolute left-[5px] top-0 bottom-2 w-px bg-border/35" }),
7128
- /* @__PURE__ */ jsx21("div", { className: "flex min-w-0 flex-col gap-1.5 [&_[data-boring-agent-part=tool-card]]:!my-0", children: toolParts.map(({ part, key }) => {
7091
+ /* @__PURE__ */ jsx22("div", { className: "absolute left-[5px] top-0 bottom-2 w-px bg-border/35" }),
7092
+ /* @__PURE__ */ jsx22("div", { className: "flex min-w-0 flex-col gap-1.5 [&_[data-boring-agent-part=tool-card]]:!my-0", children: toolParts.map(({ part, key }) => {
7129
7093
  const { renderer, part: resolvedPart, resolution } = resolveToolRendererForPart(part, mergedToolRenderers);
7130
- return /* @__PURE__ */ jsx21(
7094
+ return /* @__PURE__ */ jsx22(
7131
7095
  "div",
7132
7096
  {
7133
7097
  className: "min-w-0",
@@ -7148,8 +7112,237 @@ var ToolCallGroup = memo6(({ tools, mergedToolRenderers }) => {
7148
7112
  });
7149
7113
  ToolCallGroup.displayName = "ToolCallGroup";
7150
7114
 
7115
+ // src/front/chat/messageContributions.tsx
7116
+ import { createContext as createContext7, createElement, useContext as useContext7, useMemo as useMemo11 } from "react";
7117
+ import { jsx as jsx23 } from "react/jsx-runtime";
7118
+ var ChatMessageContributionContext = createContext7([]);
7119
+ function ChatMessageContributionProvider({
7120
+ contribution,
7121
+ children
7122
+ }) {
7123
+ const parent = useContext7(ChatMessageContributionContext);
7124
+ const contributions = useMemo11(
7125
+ () => [contribution, ...parent.filter((entry) => entry.id !== contribution.id)],
7126
+ [contribution, parent]
7127
+ );
7128
+ return /* @__PURE__ */ jsx23(ChatMessageContributionContext.Provider, { value: contributions, children });
7129
+ }
7130
+ function useCustomChatMessage(message) {
7131
+ const contributions = useContext7(ChatMessageContributionContext);
7132
+ const contribution = contributions.find((entry) => entry.matches(message));
7133
+ return contribution ? createElement(contribution.Component, { message }) : void 0;
7134
+ }
7135
+ function ChatMessageContributionBoundary({
7136
+ message,
7137
+ children
7138
+ }) {
7139
+ const customMessage = useCustomChatMessage(message);
7140
+ return customMessage !== void 0 ? customMessage : children;
7141
+ }
7142
+
7143
+ // src/front/chat/components/MessageMentions.tsx
7144
+ import { Children, cloneElement, isValidElement as isValidElement2 } from "react";
7145
+ import { Fragment as Fragment8, jsx as jsx24 } from "react/jsx-runtime";
7146
+ var TERMINAL_BOUNDARY = String.raw`(?=$|\s|[.,;!?)}\]"'”’]+(?=$|\s))`;
7147
+ var PREFIX_BOUNDARY = String.raw`(^|[\s(\[{"'“‘])`;
7148
+ function escapeRegExp2(value) {
7149
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
7150
+ }
7151
+ function registeredNamePattern(names) {
7152
+ const safeNames = [...new Set(names)].filter((name) => /^\w[\w-]*$/.test(name)).sort((a, b) => b.length - a.length);
7153
+ return safeNames.length > 0 ? safeNames.map(escapeRegExp2).join("|") : void 0;
7154
+ }
7155
+ function addRegisteredCandidates(text, prefix, names, createMention, candidates) {
7156
+ const namesPattern = registeredNamePattern(names);
7157
+ if (!namesPattern || !text.includes(prefix)) return;
7158
+ const pattern = new RegExp(`${PREFIX_BOUNDARY}\\${prefix}(${namesPattern})${TERMINAL_BOUNDARY}`, "g");
7159
+ for (const match of text.matchAll(pattern)) {
7160
+ if (match.index === void 0) continue;
7161
+ const boundary = match[1] ?? "";
7162
+ const name = match[2];
7163
+ const mention = createMention(name);
7164
+ if (!mention) continue;
7165
+ const start = match.index + boundary.length;
7166
+ candidates.push({ start, end: start + name.length + 1, mention });
7167
+ }
7168
+ }
7169
+ function isSafeWorkspaceMentionPath(path) {
7170
+ if (!path || path.startsWith("/") || path.includes("\\") || path.includes("://")) return false;
7171
+ const segments = path.split("/");
7172
+ if (!segments.every((segment) => segment.length > 0 && segment !== "." && segment !== "..")) return false;
7173
+ const basename = segments.at(-1) ?? "";
7174
+ return basename.startsWith(".") || /\.[A-Za-z0-9]{1,12}$/.test(basename);
7175
+ }
7176
+ function addFileCandidates(text, candidates) {
7177
+ if (!text.includes("@")) return;
7178
+ const pattern = new RegExp(`${PREFIX_BOUNDARY}@([A-Za-z0-9_.-]+(?:/[A-Za-z0-9_.-]+)*)${TERMINAL_BOUNDARY}`, "g");
7179
+ for (const match of text.matchAll(pattern)) {
7180
+ if (match.index === void 0) continue;
7181
+ const boundary = match[1] ?? "";
7182
+ const path = match[2].replace(/[.,;!?]+$/, "");
7183
+ if (!isSafeWorkspaceMentionPath(path)) continue;
7184
+ const start = match.index + boundary.length;
7185
+ const label = `@${path}`;
7186
+ candidates.push({ start, end: start + label.length, mention: { kind: "file", path, label } });
7187
+ }
7188
+ }
7189
+ function collectMessageMentionCandidates(text, catalog) {
7190
+ const candidates = [];
7191
+ const commandsByName = new Map(catalog.commands.map((command) => [command.name, command]));
7192
+ addRegisteredCandidates(text, "/", [...commandsByName.keys()], (name) => {
7193
+ const command = commandsByName.get(name);
7194
+ return command ? { kind: "command", name, label: `/${name}`, behavior: command.clickBehavior } : void 0;
7195
+ }, candidates);
7196
+ const skillsByName = new Map(catalog.skills.map((skill) => [skill.name, skill]));
7197
+ addRegisteredCandidates(text, "!", [...skillsByName.keys()], (name) => {
7198
+ const skill = skillsByName.get(name);
7199
+ return skill ? { kind: "skill", name, commandName: skill.commandName, label: `!${name}` } : void 0;
7200
+ }, candidates);
7201
+ if (catalog.files) addFileCandidates(text, candidates);
7202
+ return candidates;
7203
+ }
7204
+ function splitMessageMentions(text, catalog, context = {}) {
7205
+ const before = context.before ?? "";
7206
+ const after = context.after ?? "";
7207
+ const source = `${before}${text}${after}`;
7208
+ const contentStart = before.length;
7209
+ const contentEnd = contentStart + text.length;
7210
+ const candidates = collectMessageMentionCandidates(source, catalog).filter((candidate) => candidate.start >= contentStart && candidate.end <= contentEnd).map((candidate) => ({ ...candidate, start: candidate.start - contentStart, end: candidate.end - contentStart })).sort((a, b) => a.start - b.start || b.end - a.end);
7211
+ const segments = [];
7212
+ let cursor = 0;
7213
+ for (const candidate of candidates) {
7214
+ if (candidate.start < cursor) continue;
7215
+ if (candidate.start > cursor) segments.push({ text: text.slice(cursor, candidate.start) });
7216
+ segments.push({ text: candidate.mention.label, mention: candidate.mention });
7217
+ cursor = candidate.end;
7218
+ }
7219
+ if (cursor < text.length) segments.push({ text: text.slice(cursor) });
7220
+ return segments.length > 0 ? segments : [{ text }];
7221
+ }
7222
+ function mentionActionLabel(mention) {
7223
+ if (mention.kind === "command") return mention.behavior === "execute" ? `Run ${mention.label} command` : `Insert ${mention.label} command`;
7224
+ if (mention.kind === "skill") return `Insert ${mention.label} skill`;
7225
+ return `Open ${mention.path}`;
7226
+ }
7227
+ function visibleText(node) {
7228
+ if (typeof node === "string" || typeof node === "number") return String(node);
7229
+ if (!isValidElement2(node)) return "";
7230
+ const element = node;
7231
+ return Children.toArray(element.props.children).map(visibleText).join("");
7232
+ }
7233
+ function MessageMentions({
7234
+ children,
7235
+ catalog,
7236
+ onActivate,
7237
+ before = "",
7238
+ after = ""
7239
+ }) {
7240
+ const nodes = Children.toArray(children);
7241
+ const boundaryText = (index, direction) => {
7242
+ const sibling = nodes[index + direction];
7243
+ if (sibling !== void 0) return visibleText(sibling);
7244
+ return direction === -1 ? before : after;
7245
+ };
7246
+ const decorate = (child, index) => {
7247
+ if (typeof child === "string") {
7248
+ return splitMessageMentions(child, catalog, {
7249
+ before: boundaryText(index, -1),
7250
+ after: boundaryText(index, 1)
7251
+ }).map((segment, segmentIndex) => {
7252
+ if (!segment.mention) return segment.text;
7253
+ const actionLabel = mentionActionLabel(segment.mention);
7254
+ return /* @__PURE__ */ jsx24(
7255
+ "button",
7256
+ {
7257
+ type: "button",
7258
+ "aria-label": actionLabel,
7259
+ title: actionLabel,
7260
+ "data-message-mention-kind": segment.mention.kind,
7261
+ className: cn(
7262
+ "mx-0.5 inline-flex items-center rounded-[var(--radius-sm)] border border-border/70 bg-muted/55 px-1.5 py-0.5",
7263
+ "font-mono text-[0.9em] font-medium leading-none text-foreground underline-offset-2",
7264
+ "hover:bg-muted hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1"
7265
+ ),
7266
+ onClick: () => onActivate(segment.mention),
7267
+ children: segment.text
7268
+ },
7269
+ `${index}:${segmentIndex}`
7270
+ );
7271
+ });
7272
+ }
7273
+ if (isValidElement2(child)) {
7274
+ const element = child;
7275
+ const tagName = element.props.node?.tagName;
7276
+ if (tagName === "strong" || tagName === "em" || tagName === "del" || element.type === "em" || element.type === "del") {
7277
+ return cloneElement(
7278
+ element,
7279
+ void 0,
7280
+ /* @__PURE__ */ jsx24(
7281
+ MessageMentions,
7282
+ {
7283
+ catalog,
7284
+ onActivate,
7285
+ before: boundaryText(index, -1),
7286
+ after: boundaryText(index, 1),
7287
+ children: element.props.children
7288
+ }
7289
+ )
7290
+ );
7291
+ }
7292
+ }
7293
+ return child;
7294
+ };
7295
+ return nodes.map(decorate);
7296
+ }
7297
+ function createMessageMentionMarkdownComponents(catalog, onActivate) {
7298
+ const decorate = (children) => /* @__PURE__ */ jsx24(MessageMentions, { catalog, onActivate, children });
7299
+ const paragraph = ({ node: _node, children, ...props }) => {
7300
+ const content = Children.toArray(children).filter((child) => child !== null && child !== "");
7301
+ if (content.length === 1 && isValidElement2(content[0])) {
7302
+ const child = content[0];
7303
+ if (child.props.node?.tagName === "img") return /* @__PURE__ */ jsx24(Fragment8, { children });
7304
+ if (child.props.node?.tagName === "code" && "data-block" in child.props) return /* @__PURE__ */ jsx24(Fragment8, { children });
7305
+ }
7306
+ return /* @__PURE__ */ jsx24("p", { ...props, children: decorate(children) });
7307
+ };
7308
+ const tableHeader = ({ node: _node, children, className, ...props }) => /* @__PURE__ */ jsx24("th", { ...props, "data-streamdown": "table-header-cell", className: cn("whitespace-nowrap px-4 py-2 text-left font-semibold text-sm", className), children: decorate(children) });
7309
+ const tableCell = ({ node: _node, children, className, ...props }) => /* @__PURE__ */ jsx24("td", { ...props, "data-streamdown": "table-cell", className: cn("px-4 py-2 text-sm", className), children: decorate(children) });
7310
+ const listItem = ({ node: _node, children, className, ...props }) => /* @__PURE__ */ jsx24("li", { ...props, "data-streamdown": "list-item", className: cn("py-1 [&>p]:inline", className), children: decorate(children) });
7311
+ const headingClasses = {
7312
+ h1: "text-3xl",
7313
+ h2: "text-2xl",
7314
+ h3: "text-xl",
7315
+ h4: "text-lg",
7316
+ h5: "text-base",
7317
+ h6: "text-sm"
7318
+ };
7319
+ const heading = (Tag) => ({ node: _node, children, className, ...props }) => /* @__PURE__ */ jsx24(Tag, { ...props, "data-streamdown": `heading-${Tag.slice(1)}`, className: cn("mt-6 mb-2 font-semibold", headingClasses[Tag], className), children: decorate(children) });
7320
+ const inlineCode = ({ node: _node, children, className, ...props }) => /* @__PURE__ */ jsx24(
7321
+ "code",
7322
+ {
7323
+ ...props,
7324
+ className: cn(INLINE_CODE_CLASS_NAME, className),
7325
+ children
7326
+ }
7327
+ );
7328
+ return {
7329
+ p: paragraph,
7330
+ th: tableHeader,
7331
+ td: tableCell,
7332
+ li: listItem,
7333
+ h1: heading("h1"),
7334
+ h2: heading("h2"),
7335
+ h3: heading("h3"),
7336
+ h4: heading("h4"),
7337
+ h5: heading("h5"),
7338
+ h6: heading("h6"),
7339
+ inlineCode
7340
+ };
7341
+ }
7342
+
7151
7343
  // src/front/chat/components/PiTimelineMessage.tsx
7152
- import { jsx as jsx22, jsxs as jsxs20 } from "react/jsx-runtime";
7344
+ import { jsx as jsx25, jsxs as jsxs20 } from "react/jsx-runtime";
7345
+ var EMPTY_MENTION_CATALOG = { commands: [], skills: [], files: false };
7153
7346
  var COLLAPSIBLE_TOOL_NAMES = /* @__PURE__ */ new Set([
7154
7347
  "read",
7155
7348
  "ls",
@@ -7163,7 +7356,10 @@ var COLLAPSIBLE_TOOL_NAMES = /* @__PURE__ */ new Set([
7163
7356
  function isCollapsibleTool(part) {
7164
7357
  return part.type === "tool-call" && COLLAPSIBLE_TOOL_NAMES.has(part.toolName);
7165
7358
  }
7166
- function PiTimelineMessage({ message, isLast, isStreaming, showThoughts, toolRenderers }) {
7359
+ function PiTimelineMessage(props) {
7360
+ return /* @__PURE__ */ jsx25(ChatMessageContributionBoundary, { message: props.message, children: /* @__PURE__ */ jsx25(DefaultPiTimelineMessage, { ...props }) });
7361
+ }
7362
+ function DefaultPiTimelineMessage({ message, isLast, isStreaming, showThoughts, toolRenderers, mentionCatalog = EMPTY_MENTION_CATALOG, onMentionActivate }) {
7167
7363
  const role = message.role;
7168
7364
  const isAssistant = role === "assistant";
7169
7365
  const textParts = message.parts.filter((part) => part.type === "text");
@@ -7171,6 +7367,31 @@ function PiTimelineMessage({ message, isLast, isStreaming, showThoughts, toolRen
7171
7367
  const finalParts = groupRenderableParts(message);
7172
7368
  const attachmentSummaryPaths = role === "user" ? attachmentPathsFromTextParts(textParts) : [];
7173
7369
  const openArtifact = useOpenArtifact();
7370
+ const effectiveMentionCatalog = useMemo12(() => ({
7371
+ commands: mentionCatalog.commands,
7372
+ skills: mentionCatalog.skills,
7373
+ files: mentionCatalog.files && Boolean(openArtifact)
7374
+ }), [mentionCatalog, openArtifact]);
7375
+ const mentionSignature = [
7376
+ ...effectiveMentionCatalog.commands.map((command) => `command:${command.name}:${command.clickBehavior}`),
7377
+ ...effectiveMentionCatalog.skills.map((skill) => `skill:${skill.name}:${skill.commandName}`),
7378
+ effectiveMentionCatalog.files ? "files:enabled" : "files:disabled"
7379
+ ].join("|");
7380
+ const mentionActivateRef = useRef10(onMentionActivate);
7381
+ mentionActivateRef.current = onMentionActivate;
7382
+ const activateCurrentMention = useCallback13((mention) => {
7383
+ if (mention.kind === "file") {
7384
+ openArtifact?.(mention.path);
7385
+ return;
7386
+ }
7387
+ mentionActivateRef.current?.(mention);
7388
+ }, [openArtifact]);
7389
+ const hasMentions = effectiveMentionCatalog.files || effectiveMentionCatalog.commands.length > 0 || effectiveMentionCatalog.skills.length > 0;
7390
+ const mentionsEnabled = Boolean(isAssistant && !(isStreaming && isLast) && hasMentions);
7391
+ const mentionMarkdownComponents = useMemo12(
7392
+ () => mentionsEnabled ? createMessageMentionMarkdownComponents(effectiveMentionCatalog, activateCurrentMention) : void 0,
7393
+ [activateCurrentMention, effectiveMentionCatalog, mentionSignature, mentionsEnabled]
7394
+ );
7174
7395
  const shouldReserveStreamingActions = isStreaming && isAssistant && isLast;
7175
7396
  return /* @__PURE__ */ jsxs20(
7176
7397
  Message,
@@ -7180,118 +7401,93 @@ function PiTimelineMessage({ message, isLast, isStreaming, showThoughts, toolRen
7180
7401
  "data-boring-agent-message-id": message.id,
7181
7402
  "data-boring-agent-message-role": role,
7182
7403
  "data-boring-agent-message-status": message.status,
7183
- className: "!max-w-full !gap-1.5",
7184
7404
  children: [
7185
- /* @__PURE__ */ jsxs20(
7186
- MessageContent,
7187
- {
7188
- className: cn(
7189
- "!overflow-visible text-[13px] leading-relaxed text-foreground",
7190
- role === "user" ? cn(
7191
- "!ml-auto !max-w-[80%] !rounded-[var(--radius-lg)]",
7192
- "!px-4 !py-2.5",
7193
- "!bg-secondary !text-secondary-foreground"
7194
- ) : "!w-full !bg-transparent !p-0"
7195
- ),
7196
- children: [
7197
- fileParts.length > 0 ? /* @__PURE__ */ jsx22(
7198
- Attachments,
7199
- {
7200
- variant: role === "user" ? "inline" : "list",
7201
- className: cn(
7202
- role === "user" ? "mb-2 w-full gap-2" : "gap-1.5",
7203
- role === "user" ? "[&>div]:max-w-full [&>div]:border-input/60 [&>div]:bg-background/35 [&>div]:px-1.5 [&>div]:text-secondary-foreground [&>div:hover]:bg-background/45" : void 0
7204
- ),
7205
- children: fileParts.map((file, index) => {
7206
- const openPath = file.path ?? attachmentSummaryPaths[index];
7207
- const openUrl = file.url && !openPath ? file.url : void 0;
7208
- const canOpen = Boolean(openArtifact && openPath || openUrl);
7209
- const openAttachment = () => {
7210
- if (openPath) {
7211
- if (file.filesystem) openArtifact?.(openPath, { filesystem: file.filesystem });
7212
- else openArtifact?.(openPath);
7213
- return;
7214
- }
7215
- if (openUrl) window.open(openUrl, "_blank", "noopener,noreferrer");
7216
- };
7217
- const openAttachmentFromKeyboard = (event) => {
7218
- if (event.key !== "Enter" && event.key !== " ") return;
7219
- event.preventDefault();
7220
- openAttachment();
7221
- };
7222
- return /* @__PURE__ */ jsxs20(
7223
- Attachment,
7224
- {
7225
- data: toAttachmentData(file, `file-${message.id}-${index}`),
7226
- ...canOpen ? {
7227
- role: "button",
7228
- tabIndex: 0,
7229
- title: openPath ? `Open ${openPath} in workspace` : `Open ${file.filename ?? "attachment"}`,
7230
- "aria-label": openPath ? `Open ${file.filename ?? openPath} in workspace` : `Open ${file.filename ?? "attachment"}`,
7231
- ...openPath ? { "data-workspace-path": openPath } : {},
7232
- onClick: openAttachment,
7233
- onKeyDown: openAttachmentFromKeyboard
7234
- } : void 0,
7235
- children: [
7236
- /* @__PURE__ */ jsx22(AttachmentPreview, { className: role === "user" ? "!size-5 shrink-0 rounded-[var(--radius-sm)]" : "size-10 shrink-0 rounded-[var(--radius-md)]" }),
7237
- /* @__PURE__ */ jsx22(AttachmentInfo, { className: role === "user" ? "min-w-0 max-w-[220px] flex-1 text-[12px]" : "min-w-0 flex-1" })
7238
- ]
7239
- },
7240
- `file-${message.id}-${index}`
7241
- );
7242
- })
7243
- }
7244
- ) : null,
7245
- finalParts.map((item) => {
7246
- if (item.kind === "reasoning") {
7247
- return /* @__PURE__ */ jsx22(
7248
- TimelineReasoningPart,
7249
- {
7250
- item,
7251
- showThoughts
7252
- },
7253
- item.key
7254
- );
7255
- }
7256
- if (item.kind === "tool-group") {
7257
- return /* @__PURE__ */ jsx22("div", { "data-boring-agent-part": "message-tools", children: /* @__PURE__ */ jsx22(ToolCallGroup, { tools: item.tools, mergedToolRenderers: toolRenderers }) }, item.key);
7258
- }
7259
- if (item.kind === "tool-plain") {
7260
- return /* @__PURE__ */ jsx22("div", { "data-boring-agent-part": "message-tools", children: /* @__PURE__ */ jsx22(PlainToolCard, { part: item.part, renderers: toolRenderers }) }, item.key);
7261
- }
7262
- if (item.part.type === "text") {
7263
- const text = textForMessageDisplay(item.part.text, role);
7264
- if (!text) return null;
7265
- return /* @__PURE__ */ jsx22("div", { "data-boring-agent-part": "message-text", children: /* @__PURE__ */ jsx22(
7266
- MessageResponse,
7267
- {
7268
- className: cn(
7269
- "max-w-none",
7270
- "prose prose-invert prose-neutral",
7271
- "prose-p:my-3 prose-p:leading-[1.7] prose-p:text-[13px]",
7272
- "prose-headings:mt-5 prose-headings:mb-2 prose-headings:font-semibold prose-headings:tracking-[-0.01em]",
7273
- "prose-ul:my-3 prose-ul:pl-6 prose-ol:my-3 prose-ol:pl-6",
7274
- "prose-li:my-1.5 prose-li:leading-[1.7] prose-li:pl-1 prose-li:marker:text-muted-foreground/70",
7275
- "prose-strong:font-semibold prose-strong:text-foreground",
7276
- "prose-em:text-foreground/90",
7277
- "prose-a:text-[color:var(--accent)] prose-a:underline-offset-4 hover:prose-a:underline",
7278
- "prose-code:before:content-none prose-code:after:content-none",
7279
- "prose-pre:my-0 prose-pre:rounded-none prose-pre:border-0",
7280
- "prose-pre:bg-transparent prose-pre:p-0"
7281
- ),
7282
- children: text
7283
- }
7284
- ) }, item.key);
7285
- }
7286
- if (item.part.type === "notice") {
7287
- return /* @__PURE__ */ jsx22(NoticeBubble, { level: item.part.level, text: item.part.text }, item.key);
7288
- }
7289
- return null;
7405
+ /* @__PURE__ */ jsxs20(MessageContent, { children: [
7406
+ fileParts.length > 0 ? /* @__PURE__ */ jsx25(
7407
+ Attachments,
7408
+ {
7409
+ variant: role === "user" ? "inline" : "list",
7410
+ className: cn(
7411
+ role === "user" ? "mb-2 w-full gap-2" : "gap-1.5",
7412
+ role === "user" ? "[&>div]:max-w-full [&>div]:border-input/60 [&>div]:bg-background/35 [&>div]:px-1.5 [&>div]:text-secondary-foreground [&>div:hover]:bg-background/45" : void 0
7413
+ ),
7414
+ children: fileParts.map((file, index) => {
7415
+ const openPath = file.path ?? attachmentSummaryPaths[index];
7416
+ const openUrl = file.url && !openPath ? file.url : void 0;
7417
+ const canOpen = Boolean(openArtifact && openPath || openUrl);
7418
+ const openAttachment = () => {
7419
+ if (openPath) {
7420
+ if (file.filesystem) openArtifact?.(openPath, { filesystem: file.filesystem });
7421
+ else openArtifact?.(openPath);
7422
+ return;
7423
+ }
7424
+ if (openUrl) window.open(openUrl, "_blank", "noopener,noreferrer");
7425
+ };
7426
+ const openAttachmentFromKeyboard = (event) => {
7427
+ if (event.key !== "Enter" && event.key !== " ") return;
7428
+ event.preventDefault();
7429
+ openAttachment();
7430
+ };
7431
+ return /* @__PURE__ */ jsxs20(
7432
+ Attachment,
7433
+ {
7434
+ data: toAttachmentData(file, `file-${message.id}-${index}`),
7435
+ ...canOpen ? {
7436
+ role: "button",
7437
+ tabIndex: 0,
7438
+ title: openPath ? `Open ${openPath} in workspace` : `Open ${file.filename ?? "attachment"}`,
7439
+ "aria-label": openPath ? `Open ${file.filename ?? openPath} in workspace` : `Open ${file.filename ?? "attachment"}`,
7440
+ ...openPath ? { "data-workspace-path": openPath } : {},
7441
+ onClick: openAttachment,
7442
+ onKeyDown: openAttachmentFromKeyboard
7443
+ } : void 0,
7444
+ children: [
7445
+ /* @__PURE__ */ jsx25(AttachmentPreview, { className: role === "user" ? "!size-5 shrink-0 rounded-[var(--radius-sm)]" : "size-10 shrink-0 rounded-[var(--radius-md)]" }),
7446
+ /* @__PURE__ */ jsx25(AttachmentInfo, { className: role === "user" ? "min-w-0 max-w-[220px] flex-1 text-[12px]" : "min-w-0 flex-1" })
7447
+ ]
7448
+ },
7449
+ `file-${message.id}-${index}`
7450
+ );
7290
7451
  })
7291
- ]
7292
- }
7293
- ),
7294
- isAssistant && (textParts.length > 0 || shouldReserveStreamingActions) ? /* @__PURE__ */ jsx22(
7452
+ }
7453
+ ) : null,
7454
+ finalParts.map((item) => {
7455
+ if (item.kind === "reasoning") {
7456
+ return /* @__PURE__ */ jsx25(
7457
+ TimelineReasoningPart,
7458
+ {
7459
+ item,
7460
+ showThoughts
7461
+ },
7462
+ item.key
7463
+ );
7464
+ }
7465
+ if (item.kind === "tool-group") {
7466
+ return /* @__PURE__ */ jsx25("div", { "data-boring-agent-part": "message-tools", children: /* @__PURE__ */ jsx25(ToolCallGroup, { tools: item.tools, mergedToolRenderers: toolRenderers }) }, item.key);
7467
+ }
7468
+ if (item.kind === "tool-plain") {
7469
+ return /* @__PURE__ */ jsx25("div", { "data-boring-agent-part": "message-tools", children: /* @__PURE__ */ jsx25(PlainToolCard, { part: item.part, renderers: toolRenderers }) }, item.key);
7470
+ }
7471
+ if (item.part.type === "text") {
7472
+ const text = textForMessageDisplay(item.part.text, role);
7473
+ if (!text) return null;
7474
+ return /* @__PURE__ */ jsx25("div", { "data-boring-agent-part": "message-text", children: /* @__PURE__ */ jsx25(
7475
+ MessageResponse,
7476
+ {
7477
+ components: mentionMarkdownComponents,
7478
+ className: "boring-agent-markdown max-w-none",
7479
+ children: text
7480
+ },
7481
+ `${mentionSignature || "no-message-mentions"}:${mentionsEnabled ? "enabled" : "static"}`
7482
+ ) }, item.key);
7483
+ }
7484
+ if (item.part.type === "notice") {
7485
+ return /* @__PURE__ */ jsx25(NoticeBubble, { level: item.part.level, text: item.part.text }, item.key);
7486
+ }
7487
+ return null;
7488
+ })
7489
+ ] }),
7490
+ isAssistant && (textParts.length > 0 || shouldReserveStreamingActions) ? /* @__PURE__ */ jsx25(
7295
7491
  MessageActionsBar,
7296
7492
  {
7297
7493
  text: textParts.map((part) => part.text).join("\n\n"),
@@ -7326,15 +7522,15 @@ function TimelineReasoningPart({ item, showThoughts }) {
7326
7522
  onOpenChange: handleOpenChange,
7327
7523
  autoClose: false,
7328
7524
  children: [
7329
- /* @__PURE__ */ jsx22(
7525
+ /* @__PURE__ */ jsx25(
7330
7526
  ReasoningTrigger,
7331
7527
  {
7332
7528
  className: "mb-1 w-fit rounded-[var(--radius-sm)] px-0 py-0 !text-xs !font-normal !text-muted-foreground/75 hover:bg-transparent hover:!text-muted-foreground/75 [&_svg]:!text-muted-foreground/75",
7333
- getThinkingMessage: (streaming) => /* @__PURE__ */ jsx22("span", { children: streaming ? "thinking" : "thoughts" }),
7529
+ getThinkingMessage: (streaming) => /* @__PURE__ */ jsx25("span", { children: streaming ? "thinking" : "thoughts" }),
7334
7530
  onClick: toggleManualOpen
7335
7531
  }
7336
7532
  ),
7337
- /* @__PURE__ */ jsx22(ReasoningContent, { children: item.text })
7533
+ /* @__PURE__ */ jsx25(ReasoningContent, { children: item.text })
7338
7534
  ]
7339
7535
  }
7340
7536
  );
@@ -7343,7 +7539,7 @@ function PlainToolCard({ part, renderers }) {
7343
7539
  const toolPart = toToolPart(part);
7344
7540
  if (!toolPart) return null;
7345
7541
  const { renderer, part: resolved, resolution } = resolveToolRendererForPart(toolPart, renderers);
7346
- return /* @__PURE__ */ jsx22(
7542
+ return /* @__PURE__ */ jsx25(
7347
7543
  "div",
7348
7544
  {
7349
7545
  "data-tool-call-id": resolved.toolCallId,
@@ -7392,13 +7588,13 @@ ${part.text}`;
7392
7588
  return grouped;
7393
7589
  }
7394
7590
  function NoticeBubble({ level, text }) {
7395
- return /* @__PURE__ */ jsx22(
7591
+ return /* @__PURE__ */ jsx25(
7396
7592
  "div",
7397
7593
  {
7398
7594
  "data-boring-agent-part": "message-notice",
7399
7595
  "data-notice-level": level,
7400
7596
  className: noticeSurfaceClass(level, "text-xs"),
7401
- children: /* @__PURE__ */ jsx22("div", { className: noticeTextClass(), children: text })
7597
+ children: /* @__PURE__ */ jsx25("div", { className: noticeTextClass(), children: text })
7402
7598
  }
7403
7599
  );
7404
7600
  }
@@ -7477,7 +7673,7 @@ function MessageActionsBar({
7477
7673
  "focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-[color:var(--accent)]/40"
7478
7674
  );
7479
7675
  const hiddenActionProps = visible ? {} : { tabIndex: -1 };
7480
- return /* @__PURE__ */ jsx22(
7676
+ return /* @__PURE__ */ jsx25(
7481
7677
  "div",
7482
7678
  {
7483
7679
  "aria-hidden": !visible,
@@ -7485,7 +7681,7 @@ function MessageActionsBar({
7485
7681
  "flex min-h-6 items-center gap-0.5 -mt-1 transition-opacity duration-200",
7486
7682
  visible ? "opacity-100" : "pointer-events-none opacity-0"
7487
7683
  ),
7488
- children: /* @__PURE__ */ jsx22(
7684
+ children: /* @__PURE__ */ jsx25(
7489
7685
  Button11,
7490
7686
  {
7491
7687
  type: "button",
@@ -7496,7 +7692,7 @@ function MessageActionsBar({
7496
7692
  "aria-label": copied ? "Copied" : "Copy message",
7497
7693
  title: copied ? "Copied" : "Copy",
7498
7694
  ...hiddenActionProps,
7499
- children: copied ? /* @__PURE__ */ jsx22(CheckIcon2, { className: "h-3.5 w-3.5 text-[color:var(--accent)]" }) : /* @__PURE__ */ jsx22(CopyIcon3, { className: "h-3.5 w-3.5" })
7695
+ children: copied ? /* @__PURE__ */ jsx25(CheckIcon2, { className: "h-3.5 w-3.5 text-[color:var(--accent)]" }) : /* @__PURE__ */ jsx25(CopyIcon3, { className: "h-3.5 w-3.5" })
7500
7696
  }
7501
7697
  )
7502
7698
  }
@@ -7517,7 +7713,7 @@ function partKey(messageId, part, index) {
7517
7713
  }
7518
7714
 
7519
7715
  // src/front/chat/components/PiConversationSurface.tsx
7520
- import { jsx as jsx23, jsxs as jsxs21 } from "react/jsx-runtime";
7716
+ import { jsx as jsx26, jsxs as jsxs21 } from "react/jsx-runtime";
7521
7717
  var TRANSCRIPT_WINDOW = 60;
7522
7718
  var TRANSCRIPT_WINDOW_STEP = 40;
7523
7719
  var LOAD_OLDER_THRESHOLD_PX = 320;
@@ -7531,6 +7727,8 @@ function PiConversationSurface({
7531
7727
  isStreaming,
7532
7728
  showThoughts,
7533
7729
  toolRenderers,
7730
+ mentionCatalog,
7731
+ onMentionActivate,
7534
7732
  runtimeNotices,
7535
7733
  onDismissNotice,
7536
7734
  renderNoticeAction,
@@ -7565,10 +7763,10 @@ function PiConversationSurface({
7565
7763
  emptyHero && "py-4 text-center"
7566
7764
  ), children: [
7567
7765
  messages.length === 0 && emptyStateHydrating ? /* @__PURE__ */ jsxs21("div", { className: "flex items-center gap-2 rounded-xl border border-border bg-card/70 px-4 py-3 text-sm text-muted-foreground", children: [
7568
- /* @__PURE__ */ jsx23(Loader22, { className: "h-4 w-4 animate-spin" }),
7766
+ /* @__PURE__ */ jsx26(Loader22, { className: "h-4 w-4 animate-spin" }),
7569
7767
  "Loading chat history\u2026"
7570
7768
  ] }) : null,
7571
- messages.length === 0 && !emptyStateHydrating ? /* @__PURE__ */ jsx23(
7769
+ messages.length === 0 && !emptyStateHydrating ? /* @__PURE__ */ jsx26(
7572
7770
  ChatEmptyState,
7573
7771
  {
7574
7772
  eyebrow: emptyState?.eyebrow,
@@ -7586,29 +7784,31 @@ function PiConversationSurface({
7586
7784
  }
7587
7785
  }
7588
7786
  ) : null,
7589
- hasOlder ? /* @__PURE__ */ jsx23(TranscriptHistoryLoader, { olderCount, onLoadOlder: loadOlder }) : null,
7590
- visibleItems.map(({ message, key }, index) => /* @__PURE__ */ jsx23(
7787
+ hasOlder ? /* @__PURE__ */ jsx26(TranscriptHistoryLoader, { olderCount, onLoadOlder: loadOlder }) : null,
7788
+ visibleItems.map(({ message, key }, index) => /* @__PURE__ */ jsx26(
7591
7789
  PiTimelineMessage,
7592
7790
  {
7593
7791
  message,
7594
7792
  isLast: index === visibleItems.length - 1,
7595
7793
  isStreaming,
7596
7794
  showThoughts,
7597
- toolRenderers
7795
+ toolRenderers,
7796
+ mentionCatalog,
7797
+ onMentionActivate
7598
7798
  },
7599
7799
  key
7600
7800
  )),
7601
- /* @__PURE__ */ jsx23(RuntimeNoticeMessages, { notices: runtimeNotices, onDismiss: onDismissNotice, renderAction: renderNoticeAction })
7801
+ /* @__PURE__ */ jsx26(RuntimeNoticeMessages, { notices: runtimeNotices, onDismiss: onDismissNotice, renderAction: renderNoticeAction })
7602
7802
  ] }),
7603
- /* @__PURE__ */ jsx23(ConversationScrollButton, {})
7803
+ /* @__PURE__ */ jsx26(ConversationScrollButton, {})
7604
7804
  ]
7605
7805
  }
7606
7806
  );
7607
7807
  }
7608
7808
  function TranscriptHistoryLoader({ olderCount, onLoadOlder }) {
7609
7809
  const { scrollRef } = useStickToBottomContext2();
7610
- const pendingAnchor = useRef10(null);
7611
- const armed = useRef10(true);
7810
+ const pendingAnchor = useRef11(null);
7811
+ const armed = useRef11(true);
7612
7812
  useEffect14(() => {
7613
7813
  const el = scrollRef.current;
7614
7814
  if (!el) return;
@@ -7632,7 +7832,7 @@ function TranscriptHistoryLoader({ olderCount, onLoadOlder }) {
7632
7832
  }
7633
7833
  if (el.scrollTop > LOAD_OLDER_THRESHOLD_PX) armed.current = true;
7634
7834
  });
7635
- return /* @__PURE__ */ jsx23("div", { className: "flex justify-center", "data-boring-agent-part": "transcript-load-older", children: /* @__PURE__ */ jsxs21(
7835
+ return /* @__PURE__ */ jsx26("div", { className: "flex justify-center", "data-boring-agent-part": "transcript-load-older", children: /* @__PURE__ */ jsxs21(
7636
7836
  "button",
7637
7837
  {
7638
7838
  type: "button",
@@ -7673,10 +7873,10 @@ import { useCallback as useCallback16, useLayoutEffect as useLayoutEffect2 } fro
7673
7873
  import { motion as motion2 } from "motion/react";
7674
7874
 
7675
7875
  // src/front/composer/PluginUpdateStatus.tsx
7676
- import { useEffect as useEffect15, useRef as useRef11 } from "react";
7876
+ import { useEffect as useEffect15, useRef as useRef12 } from "react";
7677
7877
 
7678
7878
  // src/front/composer/ComposerStatusBanner.tsx
7679
- import { jsx as jsx24, jsxs as jsxs22 } from "react/jsx-runtime";
7879
+ import { jsx as jsx27, jsxs as jsxs22 } from "react/jsx-runtime";
7680
7880
  function ComposerStatusBanner({
7681
7881
  tone,
7682
7882
  dataAttribute,
@@ -7705,8 +7905,8 @@ function ComposerStatusBanner({
7705
7905
  maxWidthClassName
7706
7906
  ),
7707
7907
  children: [
7708
- /* @__PURE__ */ jsx24("span", { className: "inline-block h-2 w-2 animate-pulse rounded-full bg-accent", "aria-hidden": "true" }),
7709
- /* @__PURE__ */ jsx24("span", { children: runningContent })
7908
+ /* @__PURE__ */ jsx27("span", { className: "inline-block h-2 w-2 animate-pulse rounded-full bg-accent", "aria-hidden": "true" }),
7909
+ /* @__PURE__ */ jsx27("span", { children: runningContent })
7710
7910
  ]
7711
7911
  }
7712
7912
  );
@@ -7725,12 +7925,12 @@ function ComposerStatusBanner({
7725
7925
  ),
7726
7926
  children: [
7727
7927
  /* @__PURE__ */ jsxs22("div", { className: "flex items-start gap-2", children: [
7728
- /* @__PURE__ */ jsx24("span", { className: "mt-0.5 text-[oklch(0.45_0.13_148)]", "aria-hidden": "true", children: "\u2713" }),
7928
+ /* @__PURE__ */ jsx27("span", { className: "mt-0.5 text-[oklch(0.45_0.13_148)]", "aria-hidden": "true", children: "\u2713" }),
7729
7929
  /* @__PURE__ */ jsxs22("span", { className: "min-w-0 flex-1", children: [
7730
- /* @__PURE__ */ jsx24("span", { className: "block font-medium leading-5", children: title }),
7731
- detail ? /* @__PURE__ */ jsx24("span", { className: "block leading-4 text-muted-foreground", children: detail }) : null
7930
+ /* @__PURE__ */ jsx27("span", { className: "block font-medium leading-5", children: title }),
7931
+ detail ? /* @__PURE__ */ jsx27("span", { className: "block leading-4 text-muted-foreground", children: detail }) : null
7732
7932
  ] }),
7733
- onDismiss ? /* @__PURE__ */ jsx24(
7933
+ onDismiss ? /* @__PURE__ */ jsx27(
7734
7934
  "button",
7735
7935
  {
7736
7936
  type: "button",
@@ -7759,9 +7959,9 @@ function ComposerStatusBanner({
7759
7959
  ),
7760
7960
  children: [
7761
7961
  /* @__PURE__ */ jsxs22("div", { className: "flex items-center gap-2", children: [
7762
- /* @__PURE__ */ jsx24("span", { className: "text-destructive", "aria-hidden": "true", children: "\u26A0" }),
7763
- /* @__PURE__ */ jsx24("span", { className: "flex-1 font-medium", children: title }),
7764
- onRetry ? /* @__PURE__ */ jsx24(
7962
+ /* @__PURE__ */ jsx27("span", { className: "text-destructive", "aria-hidden": "true", children: "\u26A0" }),
7963
+ /* @__PURE__ */ jsx27("span", { className: "flex-1 font-medium", children: title }),
7964
+ onRetry ? /* @__PURE__ */ jsx27(
7765
7965
  "button",
7766
7966
  {
7767
7967
  type: "button",
@@ -7770,7 +7970,7 @@ function ComposerStatusBanner({
7770
7970
  children: retryLabel
7771
7971
  }
7772
7972
  ) : null,
7773
- onDismiss ? /* @__PURE__ */ jsx24(
7973
+ onDismiss ? /* @__PURE__ */ jsx27(
7774
7974
  "button",
7775
7975
  {
7776
7976
  type: "button",
@@ -7781,14 +7981,14 @@ function ComposerStatusBanner({
7781
7981
  }
7782
7982
  ) : null
7783
7983
  ] }),
7784
- message ? /* @__PURE__ */ jsx24("pre", { className: "mt-2 whitespace-pre-wrap break-words text-[11px] text-destructive/90", children: message }) : null
7984
+ message ? /* @__PURE__ */ jsx27("pre", { className: "mt-2 whitespace-pre-wrap break-words text-[11px] text-destructive/90", children: message }) : null
7785
7985
  ]
7786
7986
  }
7787
7987
  );
7788
7988
  }
7789
7989
 
7790
7990
  // src/front/composer/PluginUpdateStatus.tsx
7791
- import { jsx as jsx25, jsxs as jsxs23 } from "react/jsx-runtime";
7991
+ import { jsx as jsx28, jsxs as jsxs23 } from "react/jsx-runtime";
7792
7992
  function PluginUpdateStatus({
7793
7993
  state,
7794
7994
  onDismiss,
@@ -7796,7 +7996,7 @@ function PluginUpdateStatus({
7796
7996
  successAutoDismissMs = 1400,
7797
7997
  maxWidthClassName = "max-w-3xl"
7798
7998
  }) {
7799
- const onDismissRef = useRef11(onDismiss);
7999
+ const onDismissRef = useRef12(onDismiss);
7800
8000
  useEffect15(() => {
7801
8001
  onDismissRef.current = onDismiss;
7802
8002
  }, [onDismiss]);
@@ -7810,7 +8010,7 @@ function PluginUpdateStatus({
7810
8010
  }, [state?.kind, successDismissKey, successAutoDismissMs]);
7811
8011
  if (!state) return null;
7812
8012
  if (state.kind === "running") {
7813
- return /* @__PURE__ */ jsx25(
8013
+ return /* @__PURE__ */ jsx28(
7814
8014
  ComposerStatusBanner,
7815
8015
  {
7816
8016
  tone: "running",
@@ -7848,7 +8048,7 @@ function PluginUpdateStatus({
7848
8048
  ),
7849
8049
  children: [
7850
8050
  /* @__PURE__ */ jsxs23("div", { className: "flex items-center gap-1.5 font-medium text-[oklch(0.48_0.15_60)]", children: [
7851
- /* @__PURE__ */ jsx25("span", { "aria-hidden": "true", children: "\u26A0" }),
8051
+ /* @__PURE__ */ jsx28("span", { "aria-hidden": "true", children: "\u26A0" }),
7852
8052
  /* @__PURE__ */ jsxs23("span", { children: [
7853
8053
  "Reload diagnostics for ",
7854
8054
  diagnostics.length,
@@ -7856,8 +8056,8 @@ function PluginUpdateStatus({
7856
8056
  diagnostics.length === 1 ? "" : "s"
7857
8057
  ] })
7858
8058
  ] }),
7859
- /* @__PURE__ */ jsx25("ul", { className: "mt-1 ml-4 list-disc text-foreground/85", children: diagnostics.map((diagnostic, index) => /* @__PURE__ */ jsxs23("li", { children: [
7860
- /* @__PURE__ */ jsx25("code", { className: "font-mono text-[10.5px]", children: diagnostic.pluginId ?? diagnostic.source ?? "plugin" }),
8059
+ /* @__PURE__ */ jsx28("ul", { className: "mt-1 ml-4 list-disc text-foreground/85", children: diagnostics.map((diagnostic, index) => /* @__PURE__ */ jsxs23("li", { children: [
8060
+ /* @__PURE__ */ jsx28("code", { className: "font-mono text-[10.5px]", children: diagnostic.pluginId ?? diagnostic.source ?? "plugin" }),
7861
8061
  " \u2014 ",
7862
8062
  diagnostic.message ?? "reload diagnostic"
7863
8063
  ] }, `${diagnostic.pluginId ?? diagnostic.source ?? "plugin"}-${index}`)) })
@@ -7874,7 +8074,7 @@ function PluginUpdateStatus({
7874
8074
  ),
7875
8075
  children: [
7876
8076
  /* @__PURE__ */ jsxs23("div", { className: "flex items-center gap-1.5 font-medium text-[oklch(0.48_0.15_60)]", children: [
7877
- /* @__PURE__ */ jsx25("span", { "aria-hidden": "true", children: "\u26A0" }),
8077
+ /* @__PURE__ */ jsx28("span", { "aria-hidden": "true", children: "\u26A0" }),
7878
8078
  /* @__PURE__ */ jsxs23("span", { children: [
7879
8079
  "Restart needed for ",
7880
8080
  warnings.length,
@@ -7882,12 +8082,12 @@ function PluginUpdateStatus({
7882
8082
  warnings.length === 1 ? "" : "s"
7883
8083
  ] })
7884
8084
  ] }),
7885
- /* @__PURE__ */ jsx25("ul", { className: "mt-1 ml-4 list-disc text-foreground/85", children: warnings.map((w) => /* @__PURE__ */ jsxs23("li", { children: [
7886
- /* @__PURE__ */ jsx25("code", { className: "font-mono text-[10.5px]", children: w.id }),
8085
+ /* @__PURE__ */ jsx28("ul", { className: "mt-1 ml-4 list-disc text-foreground/85", children: warnings.map((w) => /* @__PURE__ */ jsxs23("li", { children: [
8086
+ /* @__PURE__ */ jsx28("code", { className: "font-mono text-[10.5px]", children: w.id }),
7887
8087
  " \u2014 ",
7888
8088
  w.surfaces.join(" + ")
7889
8089
  ] }, w.id)) }),
7890
- /* @__PURE__ */ jsx25("p", { className: "mt-1 text-foreground/70", children: "The interface reloaded successfully, but routes and tools were wired at boot. Restart the workspace process to pick up the new code." })
8090
+ /* @__PURE__ */ jsx28("p", { className: "mt-1 text-foreground/70", children: "The interface reloaded successfully, but routes and tools were wired at boot. Restart the workspace process to pick up the new code." })
7891
8091
  ]
7892
8092
  }
7893
8093
  ) : null
@@ -7895,7 +8095,7 @@ function PluginUpdateStatus({
7895
8095
  }
7896
8096
  );
7897
8097
  }
7898
- return /* @__PURE__ */ jsx25(
8098
+ return /* @__PURE__ */ jsx28(
7899
8099
  ComposerStatusBanner,
7900
8100
  {
7901
8101
  tone: "error",
@@ -7912,15 +8112,15 @@ function PluginUpdateStatus({
7912
8112
  }
7913
8113
 
7914
8114
  // src/front/composer/CommandRunStatus.tsx
7915
- import { useEffect as useEffect16, useRef as useRef12 } from "react";
7916
- import { Fragment as Fragment7, jsx as jsx26, jsxs as jsxs24 } from "react/jsx-runtime";
8115
+ import { useEffect as useEffect16, useRef as useRef13 } from "react";
8116
+ import { Fragment as Fragment9, jsx as jsx29, jsxs as jsxs24 } from "react/jsx-runtime";
7917
8117
  function CommandRunStatus({
7918
8118
  state,
7919
8119
  onDismiss,
7920
8120
  successAutoDismissMs = 1400,
7921
8121
  maxWidthClassName = "max-w-3xl"
7922
8122
  }) {
7923
- const onDismissRef = useRef12(onDismiss);
8123
+ const onDismissRef = useRef13(onDismiss);
7924
8124
  useEffect16(() => {
7925
8125
  onDismissRef.current = onDismiss;
7926
8126
  }, [onDismiss]);
@@ -7932,13 +8132,13 @@ function CommandRunStatus({
7932
8132
  }, [state?.kind, successKey, successAutoDismissMs]);
7933
8133
  if (!state) return null;
7934
8134
  if (state.kind === "running") {
7935
- return /* @__PURE__ */ jsx26(
8135
+ return /* @__PURE__ */ jsx29(
7936
8136
  ComposerStatusBanner,
7937
8137
  {
7938
8138
  tone: "running",
7939
8139
  dataAttribute: "data-boring-command-run",
7940
8140
  maxWidthClassName,
7941
- runningContent: state.command ? /* @__PURE__ */ jsxs24(Fragment7, { children: [
8141
+ runningContent: state.command ? /* @__PURE__ */ jsxs24(Fragment9, { children: [
7942
8142
  " Running ",
7943
8143
  /* @__PURE__ */ jsxs24("code", { className: "font-mono", children: [
7944
8144
  "/",
@@ -7950,13 +8150,13 @@ function CommandRunStatus({
7950
8150
  );
7951
8151
  }
7952
8152
  if (state.kind === "success") {
7953
- return /* @__PURE__ */ jsx26(
8153
+ return /* @__PURE__ */ jsx29(
7954
8154
  ComposerStatusBanner,
7955
8155
  {
7956
8156
  tone: "success",
7957
8157
  dataAttribute: "data-boring-command-run",
7958
8158
  maxWidthClassName,
7959
- title: state.command ? /* @__PURE__ */ jsxs24(Fragment7, { children: [
8159
+ title: state.command ? /* @__PURE__ */ jsxs24(Fragment9, { children: [
7960
8160
  " Ran ",
7961
8161
  /* @__PURE__ */ jsxs24("code", { className: "font-mono", children: [
7962
8162
  "/",
@@ -7970,13 +8170,13 @@ function CommandRunStatus({
7970
8170
  }
7971
8171
  );
7972
8172
  }
7973
- return /* @__PURE__ */ jsx26(
8173
+ return /* @__PURE__ */ jsx29(
7974
8174
  ComposerStatusBanner,
7975
8175
  {
7976
8176
  tone: "error",
7977
8177
  dataAttribute: "data-boring-command-run",
7978
8178
  maxWidthClassName,
7979
- title: state.command ? /* @__PURE__ */ jsxs24(Fragment7, { children: [
8179
+ title: state.command ? /* @__PURE__ */ jsxs24(Fragment9, { children: [
7980
8180
  " ",
7981
8181
  /* @__PURE__ */ jsxs24("code", { className: "font-mono", children: [
7982
8182
  "/",
@@ -7992,7 +8192,7 @@ function CommandRunStatus({
7992
8192
  }
7993
8193
 
7994
8194
  // src/front/chatPanelComposerControls.tsx
7995
- import { forwardRef, useEffect as useEffect17, useRef as useRef13, useState as useState16 } from "react";
8195
+ import { forwardRef, useEffect as useEffect17, useRef as useRef14, useState as useState16 } from "react";
7996
8196
  import { CheckIcon as CheckIcon3, ChevronDownIcon as ChevronDownIcon5 } from "lucide-react";
7997
8197
  import {
7998
8198
  Command,
@@ -8026,7 +8226,7 @@ function displayProviderLabel(provider) {
8026
8226
  }
8027
8227
 
8028
8228
  // src/front/chatPanelComposerControls.tsx
8029
- import { Fragment as Fragment8, jsx as jsx27, jsxs as jsxs25 } from "react/jsx-runtime";
8229
+ import { Fragment as Fragment10, jsx as jsx30, jsxs as jsxs25 } from "react/jsx-runtime";
8030
8230
  var composerActionClass = cn(
8031
8231
  "inline-flex h-8 items-center justify-center gap-1.5 rounded-[var(--radius-md)] border-0 bg-transparent",
8032
8232
  "text-muted-foreground shadow-none transition",
@@ -8094,7 +8294,7 @@ function useDismissOnOutsidePointer(ref, onClose) {
8094
8294
  }
8095
8295
  function ThinkingLevelGlyph({ level }) {
8096
8296
  const lit = level === "off" ? 0 : level === "low" ? 1 : level === "medium" ? 2 : 3;
8097
- return /* @__PURE__ */ jsx27(
8297
+ return /* @__PURE__ */ jsx30(
8098
8298
  "svg",
8099
8299
  {
8100
8300
  "aria-hidden": "true",
@@ -8103,7 +8303,7 @@ function ThinkingLevelGlyph({ level }) {
8103
8303
  viewBox: "0 0 14 14",
8104
8304
  fill: "none",
8105
8305
  className: "shrink-0",
8106
- children: [0, 1, 2].map((i) => /* @__PURE__ */ jsx27(
8306
+ children: [0, 1, 2].map((i) => /* @__PURE__ */ jsx30(
8107
8307
  "rect",
8108
8308
  {
8109
8309
  x: 2 + i * 4,
@@ -8149,7 +8349,7 @@ var ModelSelectTrigger = forwardRef(function ModelSelectTrigger2({
8149
8349
  }, ref) {
8150
8350
  const triggerLabel = modelTriggerLabel(value, options, emptyLabel);
8151
8351
  const triggerDisplay = value ? `${triggerLabel} (${displayProviderLabel(value.provider)})` : triggerLabel;
8152
- return /* @__PURE__ */ jsx27(
8352
+ return /* @__PURE__ */ jsx30(
8153
8353
  "button",
8154
8354
  {
8155
8355
  ref,
@@ -8167,12 +8367,12 @@ var ModelSelectTrigger = forwardRef(function ModelSelectTrigger2({
8167
8367
  className
8168
8368
  ),
8169
8369
  ...props,
8170
- children: trigger === "slash" ? /* @__PURE__ */ jsxs25(Fragment8, { children: [
8171
- /* @__PURE__ */ jsx27("span", { className: "text-muted-foreground", children: "/model: " }),
8172
- /* @__PURE__ */ jsx27("span", { className: "text-foreground", children: triggerDisplay })
8173
- ] }) : /* @__PURE__ */ jsxs25(Fragment8, { children: [
8174
- /* @__PURE__ */ jsx27("span", { className: "min-w-0 truncate", children: triggerDisplay }),
8175
- /* @__PURE__ */ jsx27(ChevronDownIcon5, { className: "h-3 w-3 shrink-0 text-muted-foreground/50", "aria-hidden": "true" })
8370
+ children: trigger === "slash" ? /* @__PURE__ */ jsxs25(Fragment10, { children: [
8371
+ /* @__PURE__ */ jsx30("span", { className: "text-muted-foreground", children: "/model: " }),
8372
+ /* @__PURE__ */ jsx30("span", { className: "text-foreground", children: triggerDisplay })
8373
+ ] }) : /* @__PURE__ */ jsxs25(Fragment10, { children: [
8374
+ /* @__PURE__ */ jsx30("span", { className: "min-w-0 truncate", children: triggerDisplay }),
8375
+ /* @__PURE__ */ jsx30(ChevronDownIcon5, { className: "h-3 w-3 shrink-0 text-muted-foreground/50", "aria-hidden": "true" })
8176
8376
  ] })
8177
8377
  }
8178
8378
  );
@@ -8193,8 +8393,8 @@ function ModelPickerMenu({
8193
8393
  const keyboardOptions = hideDefaultOption ? groupedOptions : [null, ...groupedOptions];
8194
8394
  const selectedIndex = currentKey ? Math.max(0, keyboardOptions.findIndex((option) => option && encodeModelKey(option) === currentKey)) : 0;
8195
8395
  const [activeIndex, setActiveIndex] = useState16(selectedIndex);
8196
- const activeIndexRef = useRef13(selectedIndex);
8197
- const menuRef = useRef13(null);
8396
+ const activeIndexRef = useRef14(selectedIndex);
8397
+ const menuRef = useRef14(null);
8198
8398
  useDismissOnOutsidePointer(menuRef, onClose);
8199
8399
  const setKeyboardActiveIndex = (next) => {
8200
8400
  const resolved = typeof next === "function" ? next(activeIndexRef.current) : next;
@@ -8229,8 +8429,8 @@ function ModelPickerMenu({
8229
8429
  }, [activeIndex, disabled, keyboardOptions, onChange, onClose]);
8230
8430
  const optionIndexOffset = hideDefaultOption ? 0 : 1;
8231
8431
  const optionIndexes = new Map(groupedOptions.map((option, index) => [encodeModelKey(option), index + optionIndexOffset]));
8232
- return /* @__PURE__ */ jsx27("div", { ref: menuRef, "data-boring-agent": "", "data-boring-agent-part": "model-picker-menu", className: cn(composerPickerMenuClass, className), children: /* @__PURE__ */ jsxs25(Command, { className: "bg-transparent text-[color:var(--popover-foreground)]", children: [
8233
- menuOptions.length > 8 && /* @__PURE__ */ jsx27("div", { className: "border-b border-border/60 px-2", children: /* @__PURE__ */ jsx27(
8432
+ return /* @__PURE__ */ jsx30("div", { ref: menuRef, "data-boring-agent": "", "data-boring-agent-part": "model-picker-menu", className: cn(composerPickerMenuClass, className), children: /* @__PURE__ */ jsxs25(Command, { className: "bg-transparent text-[color:var(--popover-foreground)]", children: [
8433
+ menuOptions.length > 8 && /* @__PURE__ */ jsx30("div", { className: "border-b border-border/60 px-2", children: /* @__PURE__ */ jsx30(
8234
8434
  CommandInput,
8235
8435
  {
8236
8436
  placeholder: "Search models\u2026",
@@ -8239,8 +8439,8 @@ function ModelPickerMenu({
8239
8439
  }
8240
8440
  ) }),
8241
8441
  /* @__PURE__ */ jsxs25(CommandList, { className: "max-h-[300px] p-0.5", children: [
8242
- /* @__PURE__ */ jsx27(CommandEmpty, { className: "py-4 text-center text-[13px] text-muted-foreground", children: "No models found" }),
8243
- !hideDefaultOption ? /* @__PURE__ */ jsx27(CommandGroup, { children: /* @__PURE__ */ jsxs25(
8442
+ /* @__PURE__ */ jsx30(CommandEmpty, { className: "py-4 text-center text-[13px] text-muted-foreground", children: "No models found" }),
8443
+ !hideDefaultOption ? /* @__PURE__ */ jsx30(CommandGroup, { children: /* @__PURE__ */ jsxs25(
8244
8444
  CommandItem,
8245
8445
  {
8246
8446
  value: "Default model automatic auto",
@@ -8251,7 +8451,7 @@ function ModelPickerMenu({
8251
8451
  },
8252
8452
  className: selectorItemClass(!value || activeIndex === 0),
8253
8453
  children: [
8254
- /* @__PURE__ */ jsx27(
8454
+ /* @__PURE__ */ jsx30(
8255
8455
  CheckIcon3,
8256
8456
  {
8257
8457
  className: cn(
@@ -8260,12 +8460,12 @@ function ModelPickerMenu({
8260
8460
  )
8261
8461
  }
8262
8462
  ),
8263
- /* @__PURE__ */ jsx27("span", { className: "truncate", children: "Default model" }),
8264
- /* @__PURE__ */ jsx27("span", { className: "ml-auto shrink-0 text-[10px] text-muted-foreground/60", children: "auto" })
8463
+ /* @__PURE__ */ jsx30("span", { className: "truncate", children: "Default model" }),
8464
+ /* @__PURE__ */ jsx30("span", { className: "ml-auto shrink-0 text-[10px] text-muted-foreground/60", children: "auto" })
8265
8465
  ]
8266
8466
  }
8267
8467
  ) }) : null,
8268
- [...groups.entries()].map(([provider, list]) => /* @__PURE__ */ jsx27(
8468
+ [...groups.entries()].map(([provider, list]) => /* @__PURE__ */ jsx30(
8269
8469
  CommandGroup,
8270
8470
  {
8271
8471
  heading: displayProviderLabel(provider),
@@ -8285,7 +8485,7 @@ function ModelPickerMenu({
8285
8485
  },
8286
8486
  className: cn(selectorItemClass(key === currentKey || activeIndex === itemIndex)),
8287
8487
  children: [
8288
- /* @__PURE__ */ jsx27(
8488
+ /* @__PURE__ */ jsx30(
8289
8489
  CheckIcon3,
8290
8490
  {
8291
8491
  className: cn(
@@ -8294,8 +8494,8 @@ function ModelPickerMenu({
8294
8494
  )
8295
8495
  }
8296
8496
  ),
8297
- /* @__PURE__ */ jsx27("span", { className: "min-w-0 flex-1 truncate whitespace-nowrap", children: label }),
8298
- /* @__PURE__ */ jsx27("span", { className: "ml-auto max-w-[45%] shrink-0 truncate whitespace-nowrap text-right text-[10px] text-muted-foreground/60", children: m.id })
8497
+ /* @__PURE__ */ jsx30("span", { className: "min-w-0 flex-1 truncate whitespace-nowrap", children: label }),
8498
+ /* @__PURE__ */ jsx30("span", { className: "ml-auto max-w-[45%] shrink-0 truncate whitespace-nowrap text-right text-[10px] text-muted-foreground/60", children: m.id })
8299
8499
  ]
8300
8500
  },
8301
8501
  key
@@ -8321,7 +8521,7 @@ function ModelSelect({
8321
8521
  hideDefaultOption = false
8322
8522
  }) {
8323
8523
  const [open, setOpen] = useState16(false);
8324
- const previousOpenSignalRef = useRef13(openSignal);
8524
+ const previousOpenSignalRef = useRef14(openSignal);
8325
8525
  useEffect17(() => {
8326
8526
  if (disabled) setOpen(false);
8327
8527
  }, [disabled]);
@@ -8334,7 +8534,7 @@ function ModelSelect({
8334
8534
  previousOpenSignalRef.current = openSignal;
8335
8535
  }, [disabled, openSignal]);
8336
8536
  return /* @__PURE__ */ jsxs25(Popover, { open, onOpenChange: disabled ? void 0 : setOpen, children: [
8337
- /* @__PURE__ */ jsx27(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx27(
8537
+ /* @__PURE__ */ jsx30(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx30(
8338
8538
  ModelSelectTrigger,
8339
8539
  {
8340
8540
  value,
@@ -8348,7 +8548,7 @@ function ModelSelect({
8348
8548
  "aria-describedby": ariaDescribedBy
8349
8549
  }
8350
8550
  ) }),
8351
- /* @__PURE__ */ jsx27(
8551
+ /* @__PURE__ */ jsx30(
8352
8552
  PopoverContent,
8353
8553
  {
8354
8554
  align: trigger === "slash" ? "center" : "start",
@@ -8357,7 +8557,7 @@ function ModelSelect({
8357
8557
  collisionPadding: 12,
8358
8558
  "data-boring-agent": "",
8359
8559
  className: cn("w-[min(92vw,340px)]", selectorContentClass),
8360
- children: /* @__PURE__ */ jsx27(
8560
+ children: /* @__PURE__ */ jsx30(
8361
8561
  ModelPickerMenu,
8362
8562
  {
8363
8563
  value,
@@ -8382,7 +8582,7 @@ function ThinkingSelect({
8382
8582
  className
8383
8583
  }) {
8384
8584
  const [open, setOpen] = useState16(false);
8385
- const previousOpenSignalRef = useRef13(openSignal);
8585
+ const previousOpenSignalRef = useRef14(openSignal);
8386
8586
  useEffect17(() => {
8387
8587
  if (disabled) setOpen(false);
8388
8588
  }, [disabled]);
@@ -8395,7 +8595,7 @@ function ThinkingSelect({
8395
8595
  previousOpenSignalRef.current = openSignal;
8396
8596
  }, [disabled, openSignal]);
8397
8597
  return /* @__PURE__ */ jsxs25(Popover, { open, onOpenChange: disabled ? void 0 : setOpen, children: [
8398
- /* @__PURE__ */ jsx27(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx27(
8598
+ /* @__PURE__ */ jsx30(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx30(
8399
8599
  ThinkingSelectTrigger,
8400
8600
  {
8401
8601
  value,
@@ -8405,7 +8605,7 @@ function ThinkingSelect({
8405
8605
  className
8406
8606
  }
8407
8607
  ) }),
8408
- /* @__PURE__ */ jsx27(
8608
+ /* @__PURE__ */ jsx30(
8409
8609
  PopoverContent,
8410
8610
  {
8411
8611
  align: trigger === "slash" ? "center" : "end",
@@ -8414,7 +8614,7 @@ function ThinkingSelect({
8414
8614
  collisionPadding: 12,
8415
8615
  "data-boring-agent": "",
8416
8616
  className: cn("w-[min(92vw,240px)]", selectorContentClass),
8417
- children: /* @__PURE__ */ jsx27(
8617
+ children: /* @__PURE__ */ jsx30(
8418
8618
  ThinkingPickerMenu,
8419
8619
  {
8420
8620
  value,
@@ -8458,13 +8658,13 @@ var ThinkingSelectTrigger = forwardRef(function ThinkingSelectTrigger2({
8458
8658
  onClick,
8459
8659
  ...props,
8460
8660
  children: [
8461
- trigger === "button" ? /* @__PURE__ */ jsx27(ThinkingLevelGlyph, { level: value }) : null,
8462
- trigger === "slash" ? /* @__PURE__ */ jsxs25(Fragment8, { children: [
8463
- /* @__PURE__ */ jsx27("span", { className: "text-muted-foreground", children: "/thinking: " }),
8464
- /* @__PURE__ */ jsx27("span", { className: "text-foreground", children: THINKING_LEVEL_STATUS_LABELS[value] })
8465
- ] }) : /* @__PURE__ */ jsxs25(Fragment8, { children: [
8466
- /* @__PURE__ */ jsx27("span", { children: THINKING_LEVEL_LABELS[value] }),
8467
- /* @__PURE__ */ jsx27(ChevronDownIcon5, { className: "h-3 w-3 shrink-0 text-muted-foreground/50", "aria-hidden": "true" })
8661
+ trigger === "button" ? /* @__PURE__ */ jsx30(ThinkingLevelGlyph, { level: value }) : null,
8662
+ trigger === "slash" ? /* @__PURE__ */ jsxs25(Fragment10, { children: [
8663
+ /* @__PURE__ */ jsx30("span", { className: "text-muted-foreground", children: "/thinking: " }),
8664
+ /* @__PURE__ */ jsx30("span", { className: "text-foreground", children: THINKING_LEVEL_STATUS_LABELS[value] })
8665
+ ] }) : /* @__PURE__ */ jsxs25(Fragment10, { children: [
8666
+ /* @__PURE__ */ jsx30("span", { children: THINKING_LEVEL_LABELS[value] }),
8667
+ /* @__PURE__ */ jsx30(ChevronDownIcon5, { className: "h-3 w-3 shrink-0 text-muted-foreground/50", "aria-hidden": "true" })
8468
8668
  ] })
8469
8669
  ]
8470
8670
  }
@@ -8479,8 +8679,8 @@ function ThinkingPickerMenu({
8479
8679
  }) {
8480
8680
  const selectedIndex = Math.max(0, THINKING_LEVELS.indexOf(value));
8481
8681
  const [activeIndex, setActiveIndex] = useState16(selectedIndex);
8482
- const activeIndexRef = useRef13(selectedIndex);
8483
- const menuRef = useRef13(null);
8682
+ const activeIndexRef = useRef14(selectedIndex);
8683
+ const menuRef = useRef14(null);
8484
8684
  useDismissOnOutsidePointer(menuRef, onClose);
8485
8685
  const setKeyboardActiveIndex = (next) => {
8486
8686
  const resolved = typeof next === "function" ? next(activeIndexRef.current) : next;
@@ -8513,7 +8713,7 @@ function ThinkingPickerMenu({
8513
8713
  window.addEventListener("keydown", handler, { capture: true });
8514
8714
  return () => window.removeEventListener("keydown", handler, { capture: true });
8515
8715
  }, [activeIndex, disabled, onChange, onClose]);
8516
- return /* @__PURE__ */ jsx27("div", { ref: menuRef, "data-boring-agent": "", "data-boring-agent-part": "thinking-picker-menu", className: cn(composerPickerMenuClass, className), children: /* @__PURE__ */ jsx27(Command, { className: "bg-transparent text-[color:var(--popover-foreground)]", children: /* @__PURE__ */ jsx27(CommandList, { className: "max-h-[300px] p-0.5", children: THINKING_LEVELS.map((level, index) => /* @__PURE__ */ jsxs25(
8716
+ return /* @__PURE__ */ jsx30("div", { ref: menuRef, "data-boring-agent": "", "data-boring-agent-part": "thinking-picker-menu", className: cn(composerPickerMenuClass, className), children: /* @__PURE__ */ jsx30(Command, { className: "bg-transparent text-[color:var(--popover-foreground)]", children: /* @__PURE__ */ jsx30(CommandList, { className: "max-h-[300px] p-0.5", children: THINKING_LEVELS.map((level, index) => /* @__PURE__ */ jsxs25(
8517
8717
  CommandItem,
8518
8718
  {
8519
8719
  value: `Thinking ${THINKING_LEVEL_LABELS[level]} ${THINKING_LEVEL_DETAILS[level]}`,
@@ -8524,7 +8724,7 @@ function ThinkingPickerMenu({
8524
8724
  },
8525
8725
  className: selectorItemClass(level === value || index === activeIndex),
8526
8726
  children: [
8527
- /* @__PURE__ */ jsx27(
8727
+ /* @__PURE__ */ jsx30(
8528
8728
  CheckIcon3,
8529
8729
  {
8530
8730
  className: cn(
@@ -8533,9 +8733,9 @@ function ThinkingPickerMenu({
8533
8733
  )
8534
8734
  }
8535
8735
  ),
8536
- /* @__PURE__ */ jsx27(ThinkingLevelGlyph, { level }),
8537
- /* @__PURE__ */ jsx27("span", { className: "font-medium", children: THINKING_LEVEL_LABELS[level] }),
8538
- /* @__PURE__ */ jsx27("span", { className: "ml-auto text-[11px] text-muted-foreground/65", children: THINKING_LEVEL_DETAILS[level] })
8736
+ /* @__PURE__ */ jsx30(ThinkingLevelGlyph, { level }),
8737
+ /* @__PURE__ */ jsx30("span", { className: "font-medium", children: THINKING_LEVEL_LABELS[level] }),
8738
+ /* @__PURE__ */ jsx30("span", { className: "ml-auto text-[11px] text-muted-foreground/65", children: THINKING_LEVEL_DETAILS[level] })
8539
8739
  ]
8540
8740
  },
8541
8741
  level
@@ -8576,22 +8776,22 @@ import {
8576
8776
  XIcon as XIcon5
8577
8777
  } from "lucide-react";
8578
8778
  import { nanoid } from "nanoid";
8579
- import { useCallback as useCallback15, useEffect as useEffect18, useMemo as useMemo10, useRef as useRef14, useState as useState17 } from "react";
8779
+ import { useCallback as useCallback15, useEffect as useEffect18, useMemo as useMemo13, useRef as useRef15, useState as useState17 } from "react";
8580
8780
 
8581
8781
  // src/front/primitives/prompt-input-context.ts
8582
- import { createContext as createContext6, useContext as useContext6 } from "react";
8583
- var PromptInputController = createContext6(
8782
+ import { createContext as createContext8, useContext as useContext8 } from "react";
8783
+ var PromptInputController = createContext8(
8584
8784
  null
8585
8785
  );
8586
- var ProviderAttachmentsContext = createContext6(
8786
+ var ProviderAttachmentsContext = createContext8(
8587
8787
  null
8588
8788
  );
8589
- var useOptionalPromptInputController = () => useContext6(PromptInputController);
8590
- var useOptionalProviderAttachments = () => useContext6(ProviderAttachmentsContext);
8591
- var LocalAttachmentsContext = createContext6(null);
8789
+ var useOptionalPromptInputController = () => useContext8(PromptInputController);
8790
+ var useOptionalProviderAttachments = () => useContext8(ProviderAttachmentsContext);
8791
+ var LocalAttachmentsContext = createContext8(null);
8592
8792
  var usePromptInputAttachments = () => {
8593
8793
  const provider = useOptionalProviderAttachments();
8594
- const local = useContext6(LocalAttachmentsContext);
8794
+ const local = useContext8(LocalAttachmentsContext);
8595
8795
  const context = local ?? provider;
8596
8796
  if (!context) {
8597
8797
  throw new Error(
@@ -8600,7 +8800,7 @@ var usePromptInputAttachments = () => {
8600
8800
  }
8601
8801
  return context;
8602
8802
  };
8603
- var LocalReferencedSourcesContext = createContext6(null);
8803
+ var LocalReferencedSourcesContext = createContext8(null);
8604
8804
 
8605
8805
  // src/front/primitives/prompt-input-wrappers.tsx
8606
8806
  import {
@@ -8610,13 +8810,13 @@ import {
8610
8810
  TooltipContent as TooltipContent3,
8611
8811
  TooltipTrigger as TooltipTrigger3
8612
8812
  } from "@hachej/boring-ui-kit";
8613
- import { Children } from "react";
8614
- import { jsx as jsx28, jsxs as jsxs26 } from "react/jsx-runtime";
8813
+ import { Children as Children2 } from "react";
8814
+ import { jsx as jsx31, jsxs as jsxs26 } from "react/jsx-runtime";
8615
8815
  var PromptInputFooter = ({
8616
8816
  align = "block-end",
8617
8817
  className,
8618
8818
  ...props
8619
- }) => /* @__PURE__ */ jsx28(
8819
+ }) => /* @__PURE__ */ jsx31(
8620
8820
  InputGroupAddon,
8621
8821
  {
8622
8822
  align,
@@ -8626,7 +8826,7 @@ var PromptInputFooter = ({
8626
8826
  );
8627
8827
 
8628
8828
  // src/front/primitives/prompt-input.tsx
8629
- import { Fragment as Fragment9, jsx as jsx29, jsxs as jsxs27 } from "react/jsx-runtime";
8829
+ import { Fragment as Fragment11, jsx as jsx32, jsxs as jsxs27 } from "react/jsx-runtime";
8630
8830
  var PromptInput = ({
8631
8831
  className,
8632
8832
  accept,
@@ -8643,12 +8843,12 @@ var PromptInput = ({
8643
8843
  }) => {
8644
8844
  const controller = useOptionalPromptInputController();
8645
8845
  const usingProvider = !!controller;
8646
- const providerTextValueRef = useRef14("");
8846
+ const providerTextValueRef = useRef15("");
8647
8847
  if (usingProvider) {
8648
8848
  providerTextValueRef.current = controller.textInput.value;
8649
8849
  }
8650
- const inputRef = useRef14(null);
8651
- const formRef = useRef14(null);
8850
+ const inputRef = useRef15(null);
8851
+ const formRef = useRef15(null);
8652
8852
  const [items, setItems] = useState17([]);
8653
8853
  const setFileUrlLocal = useCallback15((id, url, status, path) => {
8654
8854
  setItems((prev) => prev.map((f) => {
@@ -8659,7 +8859,7 @@ var PromptInput = ({
8659
8859
  }, []);
8660
8860
  const files = usingProvider ? controller.attachments.files : items;
8661
8861
  const [referencedSources, setReferencedSources] = useState17([]);
8662
- const filesRef = useRef14(files);
8862
+ const filesRef = useRef15(files);
8663
8863
  useEffect18(() => {
8664
8864
  filesRef.current = files;
8665
8865
  }, [files]);
@@ -8881,7 +9081,7 @@ var PromptInput = ({
8881
9081
  [add]
8882
9082
  );
8883
9083
  const setFileUrl = usingProvider ? controller.attachments.setFileUrl : setFileUrlLocal;
8884
- const attachmentsCtx = useMemo10(
9084
+ const attachmentsCtx = useMemo13(
8885
9085
  () => ({
8886
9086
  add,
8887
9087
  clear: clearAttachments,
@@ -8893,7 +9093,7 @@ var PromptInput = ({
8893
9093
  }),
8894
9094
  [files, add, remove, clearAttachments, openFileDialog, setFileUrl]
8895
9095
  );
8896
- const refsCtx = useMemo10(
9096
+ const refsCtx = useMemo13(
8897
9097
  () => ({
8898
9098
  add: (incoming) => {
8899
9099
  const array = Array.isArray(incoming) ? incoming : [incoming];
@@ -8969,8 +9169,8 @@ var PromptInput = ({
8969
9169
  },
8970
9170
  [usingProvider, controller, files, onSubmit, clear]
8971
9171
  );
8972
- const inner = /* @__PURE__ */ jsxs27(Fragment9, { children: [
8973
- /* @__PURE__ */ jsx29(
9172
+ const inner = /* @__PURE__ */ jsxs27(Fragment11, { children: [
9173
+ /* @__PURE__ */ jsx32(
8974
9174
  Input,
8975
9175
  {
8976
9176
  accept,
@@ -8983,7 +9183,7 @@ var PromptInput = ({
8983
9183
  type: "file"
8984
9184
  }
8985
9185
  ),
8986
- /* @__PURE__ */ jsx29(
9186
+ /* @__PURE__ */ jsx32(
8987
9187
  "form",
8988
9188
  {
8989
9189
  "data-boring-agent-part": "composer",
@@ -8991,12 +9191,12 @@ var PromptInput = ({
8991
9191
  onSubmit: handleSubmit,
8992
9192
  ref: formRef,
8993
9193
  ...props,
8994
- children: /* @__PURE__ */ jsx29(InputGroup, { className: "overflow-hidden", children })
9194
+ children: /* @__PURE__ */ jsx32(InputGroup, { className: "overflow-hidden", children })
8995
9195
  }
8996
9196
  )
8997
9197
  ] });
8998
- const withReferencedSources = /* @__PURE__ */ jsx29(LocalReferencedSourcesContext.Provider, { value: refsCtx, children: inner });
8999
- return /* @__PURE__ */ jsx29(LocalAttachmentsContext.Provider, { value: attachmentsCtx, children: withReferencedSources });
9198
+ const withReferencedSources = /* @__PURE__ */ jsx32(LocalReferencedSourcesContext.Provider, { value: refsCtx, children: inner });
9199
+ return /* @__PURE__ */ jsx32(LocalAttachmentsContext.Provider, { value: attachmentsCtx, children: withReferencedSources });
9000
9200
  };
9001
9201
  var PromptInputTextarea = ({
9002
9202
  onChange,
@@ -9074,7 +9274,7 @@ var PromptInputTextarea = ({
9074
9274
  } : {
9075
9275
  onChange
9076
9276
  };
9077
- return /* @__PURE__ */ jsx29(
9277
+ return /* @__PURE__ */ jsx32(
9078
9278
  InputGroupTextarea,
9079
9279
  {
9080
9280
  "data-boring-agent-part": "composer-input",
@@ -9101,13 +9301,13 @@ var PromptInputSubmit = ({
9101
9301
  ...props
9102
9302
  }) => {
9103
9303
  const isGenerating = status === "submitted" || status === "streaming";
9104
- let Icon = /* @__PURE__ */ jsx29(CornerDownLeftIcon, { className: "size-4" });
9304
+ let Icon = /* @__PURE__ */ jsx32(CornerDownLeftIcon, { className: "size-4" });
9105
9305
  if (status === "submitted") {
9106
- Icon = /* @__PURE__ */ jsx29(Spinner, {});
9306
+ Icon = /* @__PURE__ */ jsx32(Spinner, {});
9107
9307
  } else if (status === "streaming") {
9108
- Icon = /* @__PURE__ */ jsx29(SquareIcon, { className: "size-4" });
9308
+ Icon = /* @__PURE__ */ jsx32(SquareIcon, { className: "size-4" });
9109
9309
  } else if (status === "error") {
9110
- Icon = /* @__PURE__ */ jsx29(XIcon5, { className: "size-4" });
9310
+ Icon = /* @__PURE__ */ jsx32(XIcon5, { className: "size-4" });
9111
9311
  }
9112
9312
  const handleClick = useCallback15(
9113
9313
  (e) => {
@@ -9120,7 +9320,7 @@ var PromptInputSubmit = ({
9120
9320
  },
9121
9321
  [isGenerating, onStop, onClick]
9122
9322
  );
9123
- return /* @__PURE__ */ jsx29(
9323
+ return /* @__PURE__ */ jsx32(
9124
9324
  InputGroupButton2,
9125
9325
  {
9126
9326
  "aria-label": isGenerating ? "Stop" : "Submit",
@@ -9137,8 +9337,8 @@ var PromptInputSubmit = ({
9137
9337
  };
9138
9338
 
9139
9339
  // src/front/primitives/slash-command-picker.tsx
9140
- import { useEffect as useEffect19, useMemo as useMemo11, useRef as useRef15, useState as useState18 } from "react";
9141
- import { Fragment as Fragment10, jsx as jsx30, jsxs as jsxs28 } from "react/jsx-runtime";
9340
+ import { useEffect as useEffect19, useMemo as useMemo14, useRef as useRef16, useState as useState18 } from "react";
9341
+ import { Fragment as Fragment12, jsx as jsx33, jsxs as jsxs28 } from "react/jsx-runtime";
9142
9342
  var ALL_PLUGINS = "__all__";
9143
9343
  function commandGroup(cmd) {
9144
9344
  if (cmd.source === "skill") return "skills";
@@ -9149,17 +9349,17 @@ function SlashCommandPicker({ query, commands, onSelect, onDismiss }) {
9149
9349
  const [plugin, setPlugin] = useState18(ALL_PLUGINS);
9150
9350
  const [searchQuery, setSearchQuery] = useState18(query);
9151
9351
  const [activeIdx, setActiveIdx] = useState18(0);
9152
- const containerRef = useRef15(null);
9153
- const listRef = useRef15(null);
9154
- const inputRef = useRef15(null);
9352
+ const containerRef = useRef16(null);
9353
+ const listRef = useRef16(null);
9354
+ const inputRef = useRef16(null);
9155
9355
  useEffect19(() => {
9156
9356
  setSearchQuery(query);
9157
9357
  }, [query]);
9158
- const groups = useMemo11(() => {
9358
+ const groups = useMemo14(() => {
9159
9359
  const set = new Set(commands.map(commandGroup));
9160
9360
  return Array.from(set).sort((a, b) => a.localeCompare(b));
9161
9361
  }, [commands]);
9162
- const filtered = useMemo11(() => {
9362
+ const filtered = useMemo14(() => {
9163
9363
  const q = searchQuery.trim().toLowerCase();
9164
9364
  return commands.filter((c) => {
9165
9365
  if (plugin !== ALL_PLUGINS && commandGroup(c) !== plugin) return false;
@@ -9190,7 +9390,7 @@ function SlashCommandPicker({ query, commands, onSelect, onDismiss }) {
9190
9390
  onDismiss
9191
9391
  });
9192
9392
  return /* @__PURE__ */ jsxs28("div", { ref: containerRef, className: "mb-1 w-full overflow-hidden rounded-lg border border-border/60 bg-popover shadow-lg", children: [
9193
- /* @__PURE__ */ jsx30(
9393
+ /* @__PURE__ */ jsx33(
9194
9394
  "input",
9195
9395
  {
9196
9396
  ref: inputRef,
@@ -9205,9 +9405,9 @@ function SlashCommandPicker({ query, commands, onSelect, onDismiss }) {
9205
9405
  autoComplete: "off"
9206
9406
  }
9207
9407
  ),
9208
- /* @__PURE__ */ jsx30("div", { className: "flex flex-wrap gap-1 border-b border-border/50 px-2 py-1.5", role: "tablist", "aria-label": "Filter by plugin", children: [ALL_PLUGINS, ...groups].map((g) => {
9408
+ /* @__PURE__ */ jsx33("div", { className: "flex flex-wrap gap-1 border-b border-border/50 px-2 py-1.5", role: "tablist", "aria-label": "Filter by plugin", children: [ALL_PLUGINS, ...groups].map((g) => {
9209
9409
  const selected = plugin === g;
9210
- return /* @__PURE__ */ jsx30(
9410
+ return /* @__PURE__ */ jsx33(
9211
9411
  "button",
9212
9412
  {
9213
9413
  type: "button",
@@ -9227,7 +9427,7 @@ function SlashCommandPicker({ query, commands, onSelect, onDismiss }) {
9227
9427
  g
9228
9428
  );
9229
9429
  }) }),
9230
- filtered.length === 0 ? /* @__PURE__ */ jsx30("div", { className: "px-3 py-2 text-[11px] text-muted-foreground", children: "No matching commands." }) : /* @__PURE__ */ jsx30("ul", { ref: listRef, className: "max-h-80 overflow-y-auto py-1", role: "listbox", "aria-label": "Commands", children: filtered.map((cmd, i) => /* @__PURE__ */ jsxs28(
9430
+ filtered.length === 0 ? /* @__PURE__ */ jsx33("div", { className: "px-3 py-2 text-[11px] text-muted-foreground", children: "No matching commands." }) : /* @__PURE__ */ jsx33("ul", { ref: listRef, className: "max-h-80 overflow-y-auto py-1", role: "listbox", "aria-label": "Commands", children: filtered.map((cmd, i) => /* @__PURE__ */ jsxs28(
9231
9431
  "li",
9232
9432
  {
9233
9433
  role: "option",
@@ -9250,15 +9450,15 @@ function SlashCommandPicker({ query, commands, onSelect, onDismiss }) {
9250
9450
  "/",
9251
9451
  cmd.name
9252
9452
  ] }),
9253
- cmd.source === "skill" ? /* @__PURE__ */ jsxs28(Fragment10, { children: [
9254
- /* @__PURE__ */ jsx30("span", { className: "rounded-sm bg-accent/15 px-1 py-px text-[9px] font-medium uppercase tracking-wide text-accent-foreground", children: "skill" }),
9255
- cmd.sourcePlugin ? /* @__PURE__ */ jsx30("span", { className: "rounded-sm bg-muted px-1 py-px text-[9px] font-medium text-muted-foreground", children: cmd.sourcePlugin }) : null
9453
+ cmd.source === "skill" ? /* @__PURE__ */ jsxs28(Fragment12, { children: [
9454
+ /* @__PURE__ */ jsx33("span", { className: "rounded-sm bg-accent/15 px-1 py-px text-[9px] font-medium uppercase tracking-wide text-accent-foreground", children: "skill" }),
9455
+ cmd.sourcePlugin ? /* @__PURE__ */ jsx33("span", { className: "rounded-sm bg-muted px-1 py-px text-[9px] font-medium text-muted-foreground", children: cmd.sourcePlugin }) : null
9256
9456
  ] }) : cmd.sourcePlugin ? (
9257
9457
  // Originating plugin/package for non-skill commands.
9258
- /* @__PURE__ */ jsx30("span", { className: "rounded-sm bg-muted px-1 py-px text-[9px] font-medium text-muted-foreground", children: cmd.sourcePlugin })
9458
+ /* @__PURE__ */ jsx33("span", { className: "rounded-sm bg-muted px-1 py-px text-[9px] font-medium text-muted-foreground", children: cmd.sourcePlugin })
9259
9459
  ) : null
9260
9460
  ] }),
9261
- /* @__PURE__ */ jsx30("span", { className: "truncate text-[11px] opacity-50", children: cmd.description })
9461
+ /* @__PURE__ */ jsx33("span", { className: "truncate text-[11px] opacity-50", children: cmd.description })
9262
9462
  ]
9263
9463
  },
9264
9464
  cmd.name
@@ -9269,10 +9469,10 @@ function SlashCommandPicker({ query, commands, onSelect, onDismiss }) {
9269
9469
  // src/front/chat/components/ComposerAttachments.tsx
9270
9470
  import { AlertCircleIcon as AlertCircleIcon2, Loader2 as Loader23, PaperclipIcon as PaperclipIcon2 } from "lucide-react";
9271
9471
  import { IconButton as IconButton3 } from "@hachej/boring-ui-kit";
9272
- import { jsx as jsx31, jsxs as jsxs29 } from "react/jsx-runtime";
9472
+ import { jsx as jsx34, jsxs as jsxs29 } from "react/jsx-runtime";
9273
9473
  function AttachmentButton({ disabled, className }) {
9274
9474
  const attachments = usePromptInputAttachments();
9275
- return /* @__PURE__ */ jsx31(
9475
+ return /* @__PURE__ */ jsx34(
9276
9476
  IconButton3,
9277
9477
  {
9278
9478
  type: "button",
@@ -9285,14 +9485,14 @@ function AttachmentButton({ disabled, className }) {
9285
9485
  className: cn(composerActionClass, "w-8", className),
9286
9486
  "aria-label": "Attach files",
9287
9487
  title: disabled ? "Attachments are available when the composer is ready." : "Attach files",
9288
- children: /* @__PURE__ */ jsx31(PaperclipIcon2, { className: "h-3.5 w-3.5", strokeWidth: 1.75 })
9488
+ children: /* @__PURE__ */ jsx34(PaperclipIcon2, { className: "h-3.5 w-3.5", strokeWidth: 1.75 })
9289
9489
  }
9290
9490
  );
9291
9491
  }
9292
9492
  function AttachmentsList() {
9293
9493
  const attachments = usePromptInputAttachments();
9294
9494
  if (attachments.files.length === 0) return null;
9295
- return /* @__PURE__ */ jsx31(
9495
+ return /* @__PURE__ */ jsx34(
9296
9496
  Attachments,
9297
9497
  {
9298
9498
  "data-boring-agent-part": "composer-attachment-row",
@@ -9311,12 +9511,12 @@ function AttachmentsList() {
9311
9511
  ),
9312
9512
  children: [
9313
9513
  /* @__PURE__ */ jsxs29("div", { className: "relative shrink-0", children: [
9314
- /* @__PURE__ */ jsx31(AttachmentPreview, { className: "!size-7 overflow-hidden !rounded-full bg-background/60" }),
9315
- file.status === "uploading" ? /* @__PURE__ */ jsx31("div", { className: "absolute inset-0 flex items-center justify-center rounded-full bg-background/70", children: /* @__PURE__ */ jsx31(Loader23, { className: "size-3.5 animate-spin text-muted-foreground" }) }) : null,
9316
- file.status === "error" ? /* @__PURE__ */ jsx31("div", { className: "absolute inset-0 flex items-center justify-center rounded-full bg-destructive/20", children: /* @__PURE__ */ jsx31(AlertCircleIcon2, { className: "size-3.5 text-destructive" }) }) : null
9514
+ /* @__PURE__ */ jsx34(AttachmentPreview, { className: "!size-7 overflow-hidden !rounded-full bg-background/60" }),
9515
+ file.status === "uploading" ? /* @__PURE__ */ jsx34("div", { className: "absolute inset-0 flex items-center justify-center rounded-full bg-background/70", children: /* @__PURE__ */ jsx34(Loader23, { className: "size-3.5 animate-spin text-muted-foreground" }) }) : null,
9516
+ file.status === "error" ? /* @__PURE__ */ jsx34("div", { className: "absolute inset-0 flex items-center justify-center rounded-full bg-destructive/20", children: /* @__PURE__ */ jsx34(AlertCircleIcon2, { className: "size-3.5 text-destructive" }) }) : null
9317
9517
  ] }),
9318
- /* @__PURE__ */ jsx31(AttachmentInfo, { className: "min-w-0 !max-w-[180px] truncate text-[13px] font-medium" }),
9319
- /* @__PURE__ */ jsx31(AttachmentRemove, { className: "!size-5 !rounded-full !opacity-100 text-muted-foreground/80 hover:text-foreground" })
9518
+ /* @__PURE__ */ jsx34(AttachmentInfo, { className: "min-w-0 !max-w-[180px] truncate text-[13px] font-medium" }),
9519
+ /* @__PURE__ */ jsx34(AttachmentRemove, { className: "!size-5 !rounded-full !opacity-100 text-muted-foreground/80 hover:text-foreground" })
9320
9520
  ]
9321
9521
  },
9322
9522
  file.id
@@ -9326,7 +9526,7 @@ function AttachmentsList() {
9326
9526
  }
9327
9527
 
9328
9528
  // src/front/chat/components/PiChatComposerSurface.tsx
9329
- import { jsx as jsx32, jsxs as jsxs30 } from "react/jsx-runtime";
9529
+ import { jsx as jsx35, jsxs as jsxs30 } from "react/jsx-runtime";
9330
9530
  var MAX_PROMPT_ATTACHMENTS = 2;
9331
9531
  var MAX_PROMPT_ATTACHMENT_BYTES = 4 * 1024 * 1024;
9332
9532
  var COMPOSER_INPUT_GROUP_MIN_HEIGHT = 56;
@@ -9403,10 +9603,12 @@ function PiChatComposerSurface({
9403
9603
  textareaRef,
9404
9604
  onTextareaChange,
9405
9605
  onTextareaKeyDown,
9606
+ updateDraft,
9406
9607
  onSubmitMessage,
9407
9608
  onStop
9408
9609
  }) {
9409
9610
  const workspaceRequestId = getHeaderValue(requestHeaders, "x-boring-workspace-id");
9611
+ const composerContributions = useComposerContributions();
9410
9612
  const uploadAttachment = useCallback16((file) => uploadFile(file, {
9411
9613
  apiBaseUrl,
9412
9614
  workspaceRequestId,
@@ -9438,7 +9640,7 @@ function PiChatComposerSurface({
9438
9640
  resizeTextarea(textareaRef.current);
9439
9641
  }, [draft, resizeTextarea, textareaRef]);
9440
9642
  return /* @__PURE__ */ jsxs30("div", { className: cn("relative z-20", chrome ? "px-4 pb-4 pt-2 sm:px-6 sm:pb-5" : "px-3 pb-2 pt-1"), children: [
9441
- /* @__PURE__ */ jsx32(
9643
+ /* @__PURE__ */ jsx35(
9442
9644
  "div",
9443
9645
  {
9444
9646
  "data-boring-agent-part": "chat-working-slot",
@@ -9456,7 +9658,7 @@ function PiChatComposerSurface({
9456
9658
  "aria-live": isStreaming ? "polite" : void 0,
9457
9659
  className: "inline-flex items-center gap-2 rounded-full border border-border/50 bg-background/85 px-2.5 py-1 text-[12px] text-muted-foreground/75 shadow-sm backdrop-blur",
9458
9660
  children: [
9459
- /* @__PURE__ */ jsx32(
9661
+ /* @__PURE__ */ jsx35(
9460
9662
  motion2.span,
9461
9663
  {
9462
9664
  "aria-hidden": "true",
@@ -9465,14 +9667,14 @@ function PiChatComposerSurface({
9465
9667
  transition: { duration: 1.2, repeat: Infinity, ease: "easeInOut" }
9466
9668
  }
9467
9669
  ),
9468
- /* @__PURE__ */ jsx32("span", { children: "Working\u2026" })
9670
+ /* @__PURE__ */ jsx35("span", { children: "Working\u2026" })
9469
9671
  ]
9470
9672
  }
9471
9673
  )
9472
9674
  }
9473
9675
  ),
9474
- composerStatusNotice ? /* @__PURE__ */ jsx32(ComposerRuntimeNotice, { notice: composerStatusNotice }) : null,
9475
- composerBlocked && !workspaceWarmupBlocked ? /* @__PURE__ */ jsx32(
9676
+ composerStatusNotice ? /* @__PURE__ */ jsx35(ComposerRuntimeNotice, { notice: composerStatusNotice }) : null,
9677
+ composerBlocked && !workspaceWarmupBlocked ? /* @__PURE__ */ jsx35(
9476
9678
  ComposerBlockerNotice,
9477
9679
  {
9478
9680
  blocker: primaryComposerBlocker,
@@ -9480,8 +9682,8 @@ function PiChatComposerSurface({
9480
9682
  onAction: onComposerBlockerAction
9481
9683
  }
9482
9684
  ) : null,
9483
- queuePreview.length > 0 ? /* @__PURE__ */ jsx32(QueuedComposerNotice, { followUps: queuePreview, onEdit: onEditQueued }) : null,
9484
- hotReloadEnabled ? /* @__PURE__ */ jsx32(
9685
+ queuePreview.length > 0 ? /* @__PURE__ */ jsx35(QueuedComposerNotice, { followUps: queuePreview, onEdit: onEditQueued }) : null,
9686
+ hotReloadEnabled ? /* @__PURE__ */ jsx35(
9485
9687
  PluginUpdateStatus,
9486
9688
  {
9487
9689
  state: pluginUpdateState,
@@ -9489,14 +9691,14 @@ function PiChatComposerSurface({
9489
9691
  onRetry: onRunPluginUpdate
9490
9692
  }
9491
9693
  ) : null,
9492
- /* @__PURE__ */ jsx32(
9694
+ /* @__PURE__ */ jsx35(
9493
9695
  CommandRunStatus,
9494
9696
  {
9495
9697
  state: commandNotifyState,
9496
9698
  onDismiss: onDismissCommandNotify
9497
9699
  }
9498
9700
  ),
9499
- attachmentNotice ? /* @__PURE__ */ jsx32(
9701
+ attachmentNotice ? /* @__PURE__ */ jsx35(
9500
9702
  "div",
9501
9703
  {
9502
9704
  role: "status",
@@ -9506,7 +9708,7 @@ function PiChatComposerSurface({
9506
9708
  }
9507
9709
  ) : null,
9508
9710
  /* @__PURE__ */ jsxs30("div", { className: cn("mx-auto w-full", chrome ? "max-w-3xl" : "max-w-[680px]"), children: [
9509
- mentionState ? /* @__PURE__ */ jsx32(
9711
+ mentionState ? /* @__PURE__ */ jsx35(
9510
9712
  MentionPicker,
9511
9713
  {
9512
9714
  mention: mentionState,
@@ -9518,7 +9720,7 @@ function PiChatComposerSurface({
9518
9720
  onDismiss: onDismissMention
9519
9721
  }
9520
9722
  ) : null,
9521
- slashQuery !== null ? /* @__PURE__ */ jsx32(
9723
+ slashQuery !== null ? /* @__PURE__ */ jsx35(
9522
9724
  SlashCommandPicker,
9523
9725
  {
9524
9726
  query: slashQuery,
@@ -9527,7 +9729,7 @@ function PiChatComposerSurface({
9527
9729
  onDismiss: onDismissSlash
9528
9730
  }
9529
9731
  ) : null,
9530
- mentionState === null && slashQuery === null && modelPickerOpen ? /* @__PURE__ */ jsx32(
9732
+ mentionState === null && slashQuery === null && modelPickerOpen ? /* @__PURE__ */ jsx35(
9531
9733
  ModelPickerMenu,
9532
9734
  {
9533
9735
  value: selectedModel,
@@ -9538,7 +9740,7 @@ function PiChatComposerSurface({
9538
9740
  onClose: () => onSetModelPickerOpen(false)
9539
9741
  }
9540
9742
  ) : null,
9541
- mentionState === null && slashQuery === null && thinkingPickerOpen ? /* @__PURE__ */ jsx32(
9743
+ mentionState === null && slashQuery === null && thinkingPickerOpen ? /* @__PURE__ */ jsx35(
9542
9744
  ThinkingPickerMenu,
9543
9745
  {
9544
9746
  value: selectedThinking,
@@ -9548,7 +9750,17 @@ function PiChatComposerSurface({
9548
9750
  }
9549
9751
  ) : null
9550
9752
  ] }),
9551
- /* @__PURE__ */ jsx32(
9753
+ composerContributions.map(({ id, Top }) => Top ? /* @__PURE__ */ jsx35(
9754
+ "div",
9755
+ {
9756
+ "data-boring-agent-part": "composer-contribution-top",
9757
+ "data-boring-agent-contribution-id": id,
9758
+ className: cn("mx-auto mb-2 w-full empty:hidden", chrome ? "max-w-3xl" : "max-w-[680px]"),
9759
+ children: /* @__PURE__ */ jsx35(Top, {})
9760
+ },
9761
+ id
9762
+ ) : null),
9763
+ /* @__PURE__ */ jsx35(
9552
9764
  "div",
9553
9765
  {
9554
9766
  "data-boring-agent-part": "composer-rail",
@@ -9583,15 +9795,15 @@ function PiChatComposerSurface({
9583
9795
  else onAttachmentNotice(err.message || "Attachment rejected.");
9584
9796
  },
9585
9797
  children: [
9586
- /* @__PURE__ */ jsx32(AttachmentsList, {}),
9798
+ /* @__PURE__ */ jsx35(AttachmentsList, {}),
9587
9799
  /* @__PURE__ */ jsxs30(
9588
9800
  "div",
9589
9801
  {
9590
9802
  "data-boring-agent-part": "composer-input-row",
9591
9803
  className: "flex min-h-[var(--composer-input-group-height)] w-full items-center",
9592
9804
  children: [
9593
- /* @__PURE__ */ jsx32("div", { className: "flex h-14 shrink-0 items-center pl-2", children: /* @__PURE__ */ jsx32(AttachmentButton, { disabled: disabled || isStreaming }) }),
9594
- /* @__PURE__ */ jsx32(
9805
+ /* @__PURE__ */ jsx35("div", { className: "flex h-14 shrink-0 items-center pl-2", children: /* @__PURE__ */ jsx35(AttachmentButton, { disabled: disabled || isStreaming }) }),
9806
+ /* @__PURE__ */ jsx35(
9595
9807
  PromptInputTextarea,
9596
9808
  {
9597
9809
  value: draft,
@@ -9615,32 +9827,44 @@ function PiChatComposerSurface({
9615
9827
  )
9616
9828
  }
9617
9829
  ),
9618
- /* @__PURE__ */ jsx32(
9830
+ /* @__PURE__ */ jsx35(
9619
9831
  PromptInputFooter,
9620
9832
  {
9621
9833
  align: "inline-end",
9622
9834
  "data-boring-agent-part": "composer-submit-addon",
9623
9835
  className: "!order-none !w-auto shrink-0 self-center justify-between border-0 bg-transparent !px-2 !py-0",
9624
- children: /* @__PURE__ */ jsx32("div", { className: "ml-auto flex items-center gap-1.5", children: /* @__PURE__ */ jsx32(
9625
- PromptInputSubmit,
9626
- {
9627
- "data-boring-agent-part": "composer-submit",
9628
- status: submitStatus,
9629
- onStop,
9630
- disabled: submitDisabled,
9631
- className: cn(
9632
- "h-8 w-8 shrink-0 rounded-full",
9633
- "bg-foreground",
9634
- "text-background",
9635
- "transition-all duration-150 ease-[cubic-bezier(0.22,1,0.36,1)]",
9636
- "hover:bg-foreground/90 hover:shadow-[0_0_0_3px_oklch(from_var(--foreground)_l_c_h/0.12)] hover:scale-[1.04]",
9637
- "active:scale-[0.93] active:brightness-95",
9638
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-foreground/20",
9639
- "disabled:pointer-events-none disabled:opacity-40",
9640
- "[&>svg]:size-3.5"
9641
- )
9642
- }
9643
- ) })
9836
+ children: /* @__PURE__ */ jsxs30("div", { className: "ml-auto flex items-center gap-1.5", children: [
9837
+ composerContributions.map(({ id, Action }) => Action ? /* @__PURE__ */ jsx35(
9838
+ "span",
9839
+ {
9840
+ "data-boring-agent-part": "composer-contribution-action",
9841
+ "data-boring-agent-contribution-id": id,
9842
+ className: "contents",
9843
+ children: /* @__PURE__ */ jsx35(Action, { updateDraft })
9844
+ },
9845
+ id
9846
+ ) : null),
9847
+ /* @__PURE__ */ jsx35(
9848
+ PromptInputSubmit,
9849
+ {
9850
+ "data-boring-agent-part": "composer-submit",
9851
+ status: submitStatus,
9852
+ onStop,
9853
+ disabled: submitDisabled,
9854
+ className: cn(
9855
+ "h-8 w-8 shrink-0 rounded-full",
9856
+ "bg-foreground",
9857
+ "text-background",
9858
+ "transition-all duration-150 ease-[cubic-bezier(0.22,1,0.36,1)]",
9859
+ "hover:bg-foreground/90 hover:shadow-[0_0_0_3px_oklch(from_var(--foreground)_l_c_h/0.12)] hover:scale-[1.04]",
9860
+ "active:scale-[0.93] active:brightness-95",
9861
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-foreground/20",
9862
+ "disabled:pointer-events-none disabled:opacity-40",
9863
+ "[&>svg]:size-3.5"
9864
+ )
9865
+ }
9866
+ )
9867
+ ] })
9644
9868
  }
9645
9869
  )
9646
9870
  ]
@@ -9660,7 +9884,7 @@ function PiChatComposerSurface({
9660
9884
  chrome ? "max-w-3xl" : "max-w-[680px]"
9661
9885
  ),
9662
9886
  children: [
9663
- /* @__PURE__ */ jsx32(
9887
+ /* @__PURE__ */ jsx35(
9664
9888
  ModelSelectTrigger,
9665
9889
  {
9666
9890
  value: selectedModel,
@@ -9677,7 +9901,7 @@ function PiChatComposerSurface({
9677
9901
  }
9678
9902
  }
9679
9903
  ),
9680
- thinkingControl ? /* @__PURE__ */ jsx32(
9904
+ thinkingControl ? /* @__PURE__ */ jsx35(
9681
9905
  ThinkingSelectTrigger,
9682
9906
  {
9683
9907
  value: selectedThinking,
@@ -9704,7 +9928,7 @@ function getHeaderValue(headers, name) {
9704
9928
  }
9705
9929
 
9706
9930
  // src/front/chat/piChatPanelHooks.ts
9707
- import { useCallback as useCallback17, useEffect as useEffect20, useMemo as useMemo12, useRef as useRef16, useState as useState19, useSyncExternalStore } from "react";
9931
+ import { useCallback as useCallback17, useEffect as useEffect20, useMemo as useMemo15, useRef as useRef17, useState as useState19, useSyncExternalStore } from "react";
9708
9932
  function useExternalRemotePiSession({
9709
9933
  sessionId,
9710
9934
  agentTypeId,
@@ -9717,9 +9941,9 @@ function useExternalRemotePiSession({
9717
9941
  remoteSessionOptions
9718
9942
  }) {
9719
9943
  const [session, setSession] = useState19();
9720
- const remoteSessionOptionsRef = useRef16(remoteSessionOptions);
9944
+ const remoteSessionOptionsRef = useRef17(remoteSessionOptions);
9721
9945
  remoteSessionOptionsRef.current = remoteSessionOptions;
9722
- const remoteSessionOptionsKey = useMemo12(
9946
+ const remoteSessionOptionsKey = useMemo15(
9723
9947
  () => remoteSessionOptionsIdentity2(remoteSessionOptions),
9724
9948
  [remoteSessionOptions]
9725
9949
  );
@@ -9892,7 +10116,7 @@ function errorMessage2(error, fallback) {
9892
10116
  }
9893
10117
 
9894
10118
  // src/front/chat/PiChatPanel.tsx
9895
- import { jsx as jsx33, jsxs as jsxs31 } from "react/jsx-runtime";
10119
+ import { jsx as jsx36, jsxs as jsxs31 } from "react/jsx-runtime";
9896
10120
  var DebugDrawer2 = lazy(() => import("../DebugDrawer-RYEBQ6CO.js").then((m) => ({ default: m.DebugDrawer })));
9897
10121
  var EMPTY_COMMANDS = [];
9898
10122
  var EMPTY_COMMAND_NAMES = [];
@@ -9964,14 +10188,14 @@ function PiChatPanel({
9964
10188
  }) {
9965
10189
  const externalSessionId = sessionId?.trim() || void 0;
9966
10190
  const showSessionSidebar = showSessions ?? externalSessionId === void 0;
9967
- const onDataRef = useRef17(onData);
10191
+ const onDataRef = useRef18(onData);
9968
10192
  onDataRef.current = onData;
9969
- const onTurnCompleteRef = useRef17(onTurnComplete);
10193
+ const onTurnCompleteRef = useRef18(onTurnComplete);
9970
10194
  onTurnCompleteRef.current = onTurnComplete;
9971
- const sessionListRefreshRef = useRef17(void 0);
9972
- const requestHeadersKey = useMemo13(() => headersContentKey(requestHeaders), [requestHeaders]);
9973
- const normalizedRequestHeaders = useMemo13(() => normalizedHeadersFromContentKey(requestHeadersKey), [requestHeadersKey]);
9974
- const remoteSessionOptionsWithEvents = useMemo13(() => ({
10195
+ const sessionListRefreshRef = useRef18(void 0);
10196
+ const requestHeadersKey = useMemo16(() => headersContentKey(requestHeaders), [requestHeaders]);
10197
+ const normalizedRequestHeaders = useMemo16(() => normalizedHeadersFromContentKey(requestHeadersKey), [requestHeadersKey]);
10198
+ const remoteSessionOptionsWithEvents = useMemo16(() => ({
9975
10199
  ...remoteSessionOptions,
9976
10200
  ...hydrateMessages ? {} : { autoStart: false },
9977
10201
  onEvent: (event) => {
@@ -10038,7 +10262,7 @@ function PiChatPanel({
10038
10262
  enabled: modelDiscoveryEnabled
10039
10263
  });
10040
10264
  const selectedModel = model === void 0 ? modelDiscovery.model : model;
10041
- const modelOptions = useMemo13(
10265
+ const modelOptions = useMemo16(
10042
10266
  () => modelOptionsForSelection(availableModels ?? modelDiscovery.availableModels, selectedModel),
10043
10267
  [availableModels, modelDiscovery.availableModels, selectedModel]
10044
10268
  );
@@ -10076,13 +10300,13 @@ function PiChatPanel({
10076
10300
  const [modelPickerOpen, setModelPickerOpen] = useState20(false);
10077
10301
  const [thinkingPickerOpen, setThinkingPickerOpen] = useState20(false);
10078
10302
  const [draft, setDraft] = useState20(() => initialDraft ?? "");
10079
- const draftRef = useRef17(draft);
10303
+ const draftRef = useRef18(draft);
10080
10304
  draftRef.current = draft;
10081
- const initialDraftGuard = useRef17(new InitialDraftAutoSubmitGuard());
10082
- const pendingAutoSubmitSettleRef = useRef17(void 0);
10083
- const acceptedAutoSubmitSettleRef = useRef17(void 0);
10084
- const resetInProgressRef = useRef17(false);
10085
- const autoCreateInFlightRef = useRef17(false);
10305
+ const initialDraftGuard = useRef18(new InitialDraftAutoSubmitGuard());
10306
+ const pendingAutoSubmitSettleRef = useRef18(void 0);
10307
+ const acceptedAutoSubmitSettleRef = useRef18(void 0);
10308
+ const resetInProgressRef = useRef18(false);
10309
+ const autoCreateInFlightRef = useRef18(false);
10086
10310
  const settlePendingAutoSubmit = useCallback18((sessionId2) => {
10087
10311
  const pendingSessionId = pendingAutoSubmitSettleRef.current;
10088
10312
  if (!pendingSessionId || sessionId2 && pendingSessionId !== sessionId2) return false;
@@ -10091,20 +10315,25 @@ function PiChatPanel({
10091
10315
  onAutoSubmitInitialDraftSettled?.();
10092
10316
  return true;
10093
10317
  }, [onAutoSubmitInitialDraftSettled]);
10094
- const prevStatusRef = useRef17("idle");
10095
- const statusRef = useRef17("idle");
10096
- const scrollToBottomRef = useRef17(() => {
10318
+ const prevStatusRef = useRef18("idle");
10319
+ const statusRef = useRef18("idle");
10320
+ const scrollToBottomRef = useRef18(() => {
10097
10321
  });
10098
- const textareaRef = useRef17(null);
10322
+ const textareaRef = useRef18(null);
10099
10323
  const [localNotices, setLocalNotices] = useState20([]);
10100
10324
  const [dismissedNoticeIds, setDismissedNoticeIds] = useState20(() => /* @__PURE__ */ new Set());
10101
10325
  const [pluginUpdateState, setPluginUpdateState] = useState20(null);
10102
10326
  const [commandNotifyState, setCommandNotifyState] = useState20(null);
10103
- const commandRunIdRef = useRef17(0);
10327
+ const commandRunIdRef = useRef18(0);
10104
10328
  const [serverSkillsRefreshKey, setServerSkillsRefreshKey] = useState20(0);
10105
10329
  const [localSubmittedSessionId, setLocalSubmittedSessionId] = useState20();
10106
- const localSubmittedSessionRef = useRef17(void 0);
10330
+ const localSubmittedSessionRef = useRef18(void 0);
10107
10331
  const { attachmentNotice, setAttachmentNotice } = useAttachmentNotice();
10332
+ const composerContributions = useComposerContributions();
10333
+ const contributedCommands = useMemo16(
10334
+ () => composerContributions.flatMap((contribution) => contribution.commands ?? []),
10335
+ [composerContributions]
10336
+ );
10108
10337
  const markLocalSubmitted = useCallback18((sessionId2) => {
10109
10338
  localSubmittedSessionRef.current = sessionId2;
10110
10339
  setLocalSubmittedSessionId(sessionId2);
@@ -10114,7 +10343,7 @@ function PiChatPanel({
10114
10343
  localSubmittedSessionRef.current = void 0;
10115
10344
  setLocalSubmittedSessionId(void 0);
10116
10345
  }, []);
10117
- const registry = useMemo13(() => {
10346
+ const registry = useMemo16(() => {
10118
10347
  const excludedBuiltins = new Set(excludeBuiltinCommands);
10119
10348
  const effectiveBuiltins = builtinCommands.filter((command) => {
10120
10349
  if (!hotReloadEnabled && command.name === "reload") return false;
@@ -10122,9 +10351,10 @@ function PiChatPanel({
10122
10351
  });
10123
10352
  const next = createCommandRegistry(effectiveBuiltins);
10124
10353
  for (const command of extraCommands ?? []) next.register(command);
10354
+ for (const command of contributedCommands) next.register(command);
10125
10355
  for (const command of commands) next.register(command);
10126
10356
  return next;
10127
- }, [apiBaseUrl, commands, excludeBuiltinCommands, extraCommands, hotReloadEnabled, normalizedRequestHeaders, serverResourcesEnabled, serverSkillsRefreshKey, storageScope]);
10357
+ }, [apiBaseUrl, commands, contributedCommands, excludeBuiltinCommands, extraCommands, hotReloadEnabled, normalizedRequestHeaders, serverResourcesEnabled, serverSkillsRefreshKey, storageScope]);
10128
10358
  const commandsStamp = useServerCommands({
10129
10359
  registry,
10130
10360
  requestHeaders: normalizedRequestHeaders,
@@ -10135,12 +10365,16 @@ function PiChatPanel({
10135
10365
  refreshKey: serverSkillsRefreshKey,
10136
10366
  enabled: serverResourcesEnabled
10137
10367
  });
10138
- const allCommands = useMemo13(() => registry.list(), [registry, commandsStamp]);
10368
+ const allCommands = useMemo16(() => registry.list(), [registry, commandsStamp]);
10369
+ const actionableSlashCommands = useMemo16(
10370
+ () => allCommands.flatMap((command) => command.clickBehavior === "execute" || command.clickBehavior === "insert" ? [{ name: command.name, clickBehavior: command.clickBehavior }] : []),
10371
+ [allCommands]
10372
+ );
10139
10373
  const activeChatSessionId = selectedChatState?.sessionId;
10140
10374
  const warmupNotice = composerNoticeForWarmup(workspaceWarmupStatus);
10141
10375
  const runtimeDependenciesNotice = composerNoticeForRuntimeDependencies(workspaceWarmupStatus);
10142
10376
  const workspaceWarmupBlocked = Boolean(warmupNotice);
10143
- const activeBlockers = useMemo13(
10377
+ const activeBlockers = useMemo16(
10144
10378
  // A missing active session id means a single/sessionless chat host. In that
10145
10379
  // mode, keep scoped blockers visible instead of hiding the only attention UI.
10146
10380
  // Multi-session hosts should pass a session id so unrelated blockers filter out.
@@ -10150,7 +10384,7 @@ function PiChatPanel({
10150
10384
  const canonicalMessages = selectedChatState ? selectMessagesForRender(selectedChatState) : [];
10151
10385
  const queuePreview = selectedChatState ? selectQueuePreview(selectedChatState) : [];
10152
10386
  const messages = canonicalMessages;
10153
- const userHistory = useMemo13(() => selectComposerHistoryFromCanonicalUsers(canonicalMessages), [canonicalMessages]);
10387
+ const userHistory = useMemo16(() => selectComposerHistoryFromCanonicalUsers(canonicalMessages), [canonicalMessages]);
10154
10388
  const emptyStateHydrating = statusForState(selectedChatState, sessionsLoading || chatStatePending || selectedSessionPending) === "hydrating";
10155
10389
  const emptyHero = emptyPlacement === "hero" && messages.length === 0 && queuePreview.length === 0 && !emptyStateHydrating;
10156
10390
  const debugState = selectedPiSession?.getDebugState();
@@ -10158,7 +10392,7 @@ function PiChatPanel({
10158
10392
  const primaryComposerBlocker = activeBlockers[0];
10159
10393
  const composerBlockerLabel = workspaceWarmupBlocked ? warmupNotice?.title ?? "Preparing workspace..." : primaryComposerBlocker?.label ?? primaryComposerBlocker?.reason ?? "Complete the pending workspace action to continue.";
10160
10394
  const composerStatusNotice = warmupNotice ?? runtimeDependenciesNotice;
10161
- const runtimeNotices = useMemo13(() => {
10395
+ const runtimeNotices = useMemo16(() => {
10162
10396
  const fromState = selectedChatState ? selectRuntimeNotices(selectedChatState) : [];
10163
10397
  const sessionNotice = sessionsError ? [{ id: "session-navigation-error", level: "error", text: sessionsError.message, dismissible: true }] : [];
10164
10398
  const largeStateNotice = debug && debugState?.largeStateWarning ? [{
@@ -10372,7 +10606,7 @@ function PiChatPanel({
10372
10606
  }
10373
10607
  insertSlashCommand(name);
10374
10608
  }, [dismissSlash, insertSlashCommand, openModelPicker, openThinkingPicker, setComposerDraft]);
10375
- const policy = useMemo13(() => {
10609
+ const policy = useMemo16(() => {
10376
10610
  if (!selectedPiSession || !activeChatSessionId || !serverModelSelectionReady) return void 0;
10377
10611
  const policySession = {
10378
10612
  getState: () => {
@@ -10450,16 +10684,16 @@ function PiChatPanel({
10450
10684
  ...errorCode ? { errorCode } : {}
10451
10685
  });
10452
10686
  }, [addLocalNotice, dropLocalNotice]);
10453
- const sendComposerMessage = useCallback18(async ({ text, files, source = "composer" }) => {
10687
+ const sendComposerMessage = useCallback18(async ({ text, files, source = "composer", preserveExistingDraft = false }) => {
10454
10688
  if (!policy) {
10455
10689
  addLocalNotice({ id: "composer-no-session", level: "warning", text: "Create or select a chat session before sending.", dismissible: true });
10456
10690
  return false;
10457
10691
  }
10458
10692
  const submittedDraft = text;
10459
10693
  const restoreSubmittedDraft = () => {
10460
- if (draftRef.current === "") setComposerDraft(submittedDraft);
10694
+ if (!preserveExistingDraft && draftRef.current === "") setComposerDraft(submittedDraft);
10461
10695
  };
10462
- setComposerDraft("", false);
10696
+ if (!preserveExistingDraft) setComposerDraft("", false);
10463
10697
  scrollToBottomRef.current();
10464
10698
  try {
10465
10699
  const result = await policy.submit({ text, files, source });
@@ -10483,6 +10717,53 @@ function PiChatPanel({
10483
10717
  return false;
10484
10718
  }
10485
10719
  }, [activeChatSessionId, clearLocalSubmitted, dropLocalNotice, markLocalSubmitted, onPromptSubmitStarted, policy, selectedPiSession, setComposerDraft, surfaceRunRejected]);
10720
+ const availableAssistantSlashCommands = useMemo16(
10721
+ () => policy ? actionableSlashCommands : actionableSlashCommands.filter((command) => command.clickBehavior === "insert"),
10722
+ [actionableSlashCommands, policy]
10723
+ );
10724
+ const assistantMentionCatalog = useMemo16(() => {
10725
+ const skills = /* @__PURE__ */ new Map();
10726
+ for (const command of allCommands) {
10727
+ if (command.source !== "skill" && command.kind !== "skill") continue;
10728
+ const name = command.name.replace(/^skill:/, "");
10729
+ if (!name) continue;
10730
+ const priority = command.kind === "skill" ? 2 : 1;
10731
+ const existing = skills.get(name);
10732
+ if (!existing || priority > existing.priority) skills.set(name, { name, commandName: command.name, priority });
10733
+ }
10734
+ return {
10735
+ commands: availableAssistantSlashCommands,
10736
+ skills: [...skills.values()].map(({ name, commandName }) => ({ name, commandName })),
10737
+ files: true
10738
+ };
10739
+ }, [allCommands, availableAssistantSlashCommands]);
10740
+ const insertMentionDraft = useCallback18((invocation) => {
10741
+ const currentDraft = draftRef.current;
10742
+ const separator = currentDraft && !/\s$/.test(invocation) ? "\n" : "";
10743
+ setComposerDraft(`${invocation}${separator}${currentDraft}`, true);
10744
+ }, [setComposerDraft]);
10745
+ const activateAssistantMention = useCallback18((mention) => {
10746
+ const command = registry.get(mention.kind === "skill" ? mention.commandName : mention.name);
10747
+ if (mention.kind === "skill") {
10748
+ if (command?.source !== "skill" && command?.kind !== "skill") return;
10749
+ const invocation = command.kind === "skill" && !mention.commandName.includes(":") ? `/${mention.commandName} ` : `skill: ${mention.name}
10750
+
10751
+ `;
10752
+ insertMentionDraft(invocation);
10753
+ return;
10754
+ }
10755
+ if (command?.clickBehavior === "insert") {
10756
+ insertMentionDraft(`/${mention.name} `);
10757
+ return;
10758
+ }
10759
+ if (command?.clickBehavior !== "execute" || !policy) return;
10760
+ void sendComposerMessage({
10761
+ text: `/${mention.name}`,
10762
+ files: [],
10763
+ source: "composer",
10764
+ preserveExistingDraft: true
10765
+ });
10766
+ }, [insertMentionDraft, policy, registry, sendComposerMessage]);
10486
10767
  const editQueued = useCallback18(() => {
10487
10768
  if (!policy) return;
10488
10769
  void policy.editQueued().then((result) => {
@@ -10589,8 +10870,8 @@ function PiChatPanel({
10589
10870
  const isStreaming = isPiBusyStatus(status);
10590
10871
  const submitStatus = initialHydrationPromptAllowed ? "ready" : toPromptSubmitStatus(status);
10591
10872
  const submitDisabled = !policy || sessionsLoading || modelSelectionBlocked || composerBlocked && !isStreaming;
10592
- const mergedToolRenderers = useMemo13(() => mergeShadcnToolRenderers(toolRenderers), [toolRenderers]);
10593
- const debugMessages = useMemo13(() => messages.map(toDebugUiMessage), [messages]);
10873
+ const mergedToolRenderers = useMemo16(() => mergeShadcnToolRenderers(toolRenderers), [toolRenderers]);
10874
+ const debugMessages = useMemo16(() => messages.map(toDebugUiMessage), [messages]);
10594
10875
  const onTextareaChange = useCallback18((event) => {
10595
10876
  setModelPickerOpen(false);
10596
10877
  setThinkingPickerOpen(false);
@@ -10624,7 +10905,7 @@ function PiChatPanel({
10624
10905
  }
10625
10906
  handleComposerKeyDown(event);
10626
10907
  }, [handleComposerKeyDown, interrupt, isStreaming, mentionState, slashQuery]);
10627
- return /* @__PURE__ */ jsx33(ArtifactOpenProvider, { onOpenArtifact, children: /* @__PURE__ */ jsxs31(
10908
+ return /* @__PURE__ */ jsx36(ArtifactOpenProvider, { onOpenArtifact, children: /* @__PURE__ */ jsxs31(
10628
10909
  "div",
10629
10910
  {
10630
10911
  "data-boring-agent": "",
@@ -10641,7 +10922,7 @@ function PiChatPanel({
10641
10922
  role: "region",
10642
10923
  "aria-label": "Agent assistant",
10643
10924
  children: [
10644
- showSessionSidebar ? /* @__PURE__ */ jsx33("aside", { "data-boring-agent-part": "pi-chat-session-sidebar", className: "min-h-0 w-64 shrink-0 border-r border-border/60", children: /* @__PURE__ */ jsx33(
10925
+ showSessionSidebar ? /* @__PURE__ */ jsx36("aside", { "data-boring-agent-part": "pi-chat-session-sidebar", className: "min-h-0 w-64 shrink-0 border-r border-border/60", children: /* @__PURE__ */ jsx36(
10645
10926
  SessionList,
10646
10927
  {
10647
10928
  sessions: sessionList,
@@ -10655,7 +10936,7 @@ function PiChatPanel({
10655
10936
  loadingMore: sessions.loadingMore
10656
10937
  }
10657
10938
  ) }) : null,
10658
- /* @__PURE__ */ jsx33("div", { className: "flex min-h-0 min-w-0 flex-1 flex-col", children: /* @__PURE__ */ jsxs31(
10939
+ /* @__PURE__ */ jsx36("div", { className: "flex min-h-0 min-w-0 flex-1 flex-col", children: /* @__PURE__ */ jsxs31(
10659
10940
  "div",
10660
10941
  {
10661
10942
  className: cn(
@@ -10664,7 +10945,7 @@ function PiChatPanel({
10664
10945
  chrome && "mx-3 my-3 rounded-xl bg-[color:var(--surface-chat)] shadow-[0_1px_0_oklch(0_0_0/0.02),0_1px_2px_-1px_oklch(0_0_0/0.04),inset_0_0_0_1px_oklch(from_var(--border)_l_c_h/0.6)]"
10665
10946
  ),
10666
10947
  children: [
10667
- /* @__PURE__ */ jsx33(
10948
+ /* @__PURE__ */ jsx36(
10668
10949
  PiConversationSurface,
10669
10950
  {
10670
10951
  chrome,
@@ -10676,6 +10957,8 @@ function PiChatPanel({
10676
10957
  isStreaming,
10677
10958
  showThoughts,
10678
10959
  toolRenderers: mergedToolRenderers,
10960
+ mentionCatalog: assistantMentionCatalog,
10961
+ onMentionActivate: activateAssistantMention,
10679
10962
  runtimeNotices,
10680
10963
  onDismissNotice: clearLocalNotice,
10681
10964
  renderNoticeAction,
@@ -10687,7 +10970,7 @@ function PiChatPanel({
10687
10970
  windowResetKey: activeSessionId
10688
10971
  }
10689
10972
  ),
10690
- /* @__PURE__ */ jsx33(
10973
+ /* @__PURE__ */ jsx36(
10691
10974
  PiChatComposerSurface,
10692
10975
  {
10693
10976
  chrome,
@@ -10744,6 +11027,9 @@ function PiChatPanel({
10744
11027
  textareaRef,
10745
11028
  onTextareaChange,
10746
11029
  onTextareaKeyDown,
11030
+ updateDraft: (update, options) => {
11031
+ setComposerDraft(update(draftRef.current), options?.focus ?? true);
11032
+ },
10747
11033
  onSubmitMessage: ({ text, files }) => sendComposerMessage({ text, files }),
10748
11034
  onStop: stop
10749
11035
  }
@@ -10751,7 +11037,7 @@ function PiChatPanel({
10751
11037
  ]
10752
11038
  }
10753
11039
  ) }),
10754
- debug ? /* @__PURE__ */ jsx33(Suspense, { fallback: null, children: /* @__PURE__ */ jsx33("div", { "aria-label": "Chat debug metadata", className: "contents", role: "region", children: /* @__PURE__ */ jsx33(
11040
+ debug ? /* @__PURE__ */ jsx36(Suspense, { fallback: null, children: /* @__PURE__ */ jsx36("div", { "aria-label": "Chat debug metadata", className: "contents", role: "region", children: /* @__PURE__ */ jsx36(
10755
11041
  DebugDrawer2,
10756
11042
  {
10757
11043
  apiBaseUrl,
@@ -10807,8 +11093,10 @@ function getAgentCommands(options = {}) {
10807
11093
  export {
10808
11094
  ArtifactOpenProvider,
10809
11095
  ChatEmptyState,
11096
+ ChatMessageContributionProvider,
10810
11097
  PiChatPanel as ChatPanel,
10811
11098
  CodeBlock2 as CodeBlock,
11099
+ ComposerContributionProvider,
10812
11100
  Conversation,
10813
11101
  ConversationContent,
10814
11102
  ConversationScrollButton,
@@ -10832,7 +11120,10 @@ export {
10832
11120
  ReasoningContent,
10833
11121
  ReasoningTrigger,
10834
11122
  ThinkingSelect,
11123
+ Tool2 as Tool,
10835
11124
  ToolCallGroup,
11125
+ ToolContent,
11126
+ ToolHeader,
10836
11127
  activeSessionStorageKey,
10837
11128
  builtinCommands,
10838
11129
  clearActiveSessionId,