@hachej/boring-agent 0.1.63 → 0.1.65

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.
@@ -4,7 +4,6 @@ import {
4
4
  } from "../chunk-DGRKMMXO.js";
5
5
  import {
6
6
  CommandReceiptSchema,
7
- ErrorCode,
8
7
  FollowUpReceiptSchema,
9
8
  PiChatSnapshotSchema,
10
9
  PiChatStreamFrameSchema,
@@ -13,7 +12,10 @@ import {
13
12
  StopReceiptSchema,
14
13
  extractToolUiMetadata,
15
14
  sanitizeToolUiMetadata
16
- } from "../chunk-G6YPXDHR.js";
15
+ } from "../chunk-4LXA7OOV.js";
16
+ import {
17
+ ErrorCode
18
+ } from "../chunk-XZKU7FBV.js";
17
19
  import {
18
20
  DebugDrawer,
19
21
  cn,
@@ -1534,7 +1536,7 @@ function toHeaderProps(part) {
1534
1536
  toolName: part.toolName
1535
1537
  };
1536
1538
  }
1537
- function PathLabel({ path }) {
1539
+ function PathLabel({ path, filesystem }) {
1538
1540
  const onOpen = useOpenArtifact();
1539
1541
  if (!onOpen) {
1540
1542
  return /* @__PURE__ */ jsx11("span", { className: "font-mono text-[12.5px] text-foreground/85", children: path });
@@ -1542,7 +1544,8 @@ function PathLabel({ path }) {
1542
1544
  const open = (e) => {
1543
1545
  e.preventDefault();
1544
1546
  e.stopPropagation();
1545
- onOpen(path);
1547
+ if (filesystem) onOpen(path, { filesystem });
1548
+ else onOpen(path);
1546
1549
  };
1547
1550
  const handleKeyDown = (e) => {
1548
1551
  if (e.key === "Enter" || e.key === " ") open(e);
@@ -1603,11 +1606,11 @@ function renderRuntimeNotReady(part) {
1603
1606
  function renderReadinessBlock(part) {
1604
1607
  return renderWorkspaceNotReady(part) ?? renderRuntimeNotReady(part);
1605
1608
  }
1606
- function pathTitle(prefix, path) {
1609
+ function pathTitle(prefix, path, filesystem) {
1607
1610
  return /* @__PURE__ */ jsxs10("span", { className: "flex min-w-0 items-center gap-1.5", children: [
1608
1611
  /* @__PURE__ */ jsx11("span", { className: "text-muted-foreground", children: prefix }),
1609
1612
  /* @__PURE__ */ jsx11("span", { className: "text-muted-foreground/40", "aria-hidden": "true", children: "\xB7" }),
1610
- /* @__PURE__ */ jsx11(PathLabel, { path })
1613
+ /* @__PURE__ */ jsx11(PathLabel, { path, filesystem })
1611
1614
  ] });
1612
1615
  }
1613
1616
  function renderBash2(part) {
@@ -1649,10 +1652,11 @@ function renderRead2(part) {
1649
1652
  const input = asRecord2(part.input);
1650
1653
  const output = asRecord2(part.output);
1651
1654
  const path = typeof input.path === "string" ? input.path : "";
1655
+ const filesystem = typeof input.filesystem === "string" ? input.filesystem : void 0;
1652
1656
  const content = extractTextContent(output);
1653
1657
  const lang = langFromPath(path);
1654
1658
  return /* @__PURE__ */ jsxs10(Tool2, { children: [
1655
- /* @__PURE__ */ jsx11(ToolHeader, { icon: /* @__PURE__ */ jsx11(FileTextIcon, { className: "size-4 text-muted-foreground" }), title: pathTitle("read", path), ...toHeaderProps(part) }),
1659
+ /* @__PURE__ */ jsx11(ToolHeader, { icon: /* @__PURE__ */ jsx11(FileTextIcon, { className: "size-4 text-muted-foreground" }), title: pathTitle("read", path, filesystem), ...toHeaderProps(part) }),
1656
1660
  /* @__PURE__ */ jsxs10(ToolContent, { children: [
1657
1661
  /* @__PURE__ */ jsx11(ToolInput, { input }),
1658
1662
  content && /* @__PURE__ */ jsxs10("section", { className: "space-y-2", children: [
@@ -1667,11 +1671,12 @@ function renderWrite2(part) {
1667
1671
  if (readiness) return readiness;
1668
1672
  const input = asRecord2(part.input);
1669
1673
  const path = typeof input.path === "string" ? input.path : "";
1674
+ const filesystem = typeof input.filesystem === "string" ? input.filesystem : void 0;
1670
1675
  const content = typeof input.content === "string" ? input.content : "";
1671
1676
  const bytes = content.length;
1672
1677
  const lang = langFromPath(path);
1673
1678
  return /* @__PURE__ */ jsxs10(Tool2, { children: [
1674
- /* @__PURE__ */ jsx11(ToolHeader, { icon: /* @__PURE__ */ jsx11(FilePlus2Icon, { className: "size-4 text-muted-foreground" }), title: pathTitle("write", path), ...toHeaderProps(part) }),
1679
+ /* @__PURE__ */ jsx11(ToolHeader, { icon: /* @__PURE__ */ jsx11(FilePlus2Icon, { className: "size-4 text-muted-foreground" }), title: pathTitle("write", path, filesystem), ...toHeaderProps(part) }),
1675
1680
  /* @__PURE__ */ jsx11(ToolContent, { children: /* @__PURE__ */ jsxs10(Artifact, { className: "rounded-none border-0 bg-transparent shadow-none", children: [
1676
1681
  /* @__PURE__ */ jsxs10(ArtifactHeader, { className: "border-0 px-0 pt-0 pb-2", children: [
1677
1682
  /* @__PURE__ */ jsxs10("div", { children: [
@@ -1728,10 +1733,11 @@ function renderEdit2(part) {
1728
1733
  if (readiness) return readiness;
1729
1734
  const input = asRecord2(part.input);
1730
1735
  const path = typeof input.path === "string" ? input.path : "";
1736
+ const filesystem = typeof input.filesystem === "string" ? input.filesystem : void 0;
1731
1737
  const oldString = typeof input.oldString === "string" ? input.oldString : "";
1732
1738
  const newString = typeof input.newString === "string" ? input.newString : "";
1733
1739
  return /* @__PURE__ */ jsxs10(Tool2, { children: [
1734
- /* @__PURE__ */ jsx11(ToolHeader, { icon: /* @__PURE__ */ jsx11(FileDiffIcon, { className: "size-4 text-muted-foreground" }), title: pathTitle("edit", path), ...toHeaderProps(part) }),
1740
+ /* @__PURE__ */ jsx11(ToolHeader, { icon: /* @__PURE__ */ jsx11(FileDiffIcon, { className: "size-4 text-muted-foreground" }), title: pathTitle("edit", path, filesystem), ...toHeaderProps(part) }),
1735
1741
  /* @__PURE__ */ jsxs10(ToolContent, { children: [
1736
1742
  /* @__PURE__ */ jsxs10("section", { className: "space-y-2", children: [
1737
1743
  /* @__PURE__ */ jsx11("h4", { className: "font-medium text-muted-foreground text-xs uppercase tracking-wide", children: "Diff" }),
@@ -2658,7 +2664,17 @@ function useChatModelSelection({
2658
2664
  setModelState(next);
2659
2665
  writePiComposerModelSelection(next, { storageScope, storage });
2660
2666
  }, [storage, storageScope]);
2667
+ const discoveryKey = useMemo2(
2668
+ () => JSON.stringify({
2669
+ apiBaseUrl: apiBaseUrl ?? "",
2670
+ headers: Object.entries(requestHeaders ?? {}).sort(([a], [b]) => a.localeCompare(b)),
2671
+ storageScope: storageScope ?? ""
2672
+ }),
2673
+ [apiBaseUrl, requestHeaders, storageScope]
2674
+ );
2661
2675
  const [availableModels, setAvailableModels] = useState7([]);
2676
+ const [loaded, setLoaded] = useState7(!enabled);
2677
+ const [loadedDiscoveryKey, setLoadedDiscoveryKey] = useState7(enabled ? null : discoveryKey);
2662
2678
  useEffect5(() => {
2663
2679
  if (loadedSettingsSourceRef.current.storage !== storage || loadedSettingsSourceRef.current.storageScope !== storageScope) return;
2664
2680
  if (!userSelectedModel || !model) return;
@@ -2676,14 +2692,31 @@ function useChatModelSelection({
2676
2692
  setModelState(defaultModel);
2677
2693
  }, [defaultModel]);
2678
2694
  useEffect5(() => {
2679
- if (!enabled) return;
2695
+ if (!enabled) {
2696
+ setLoaded(true);
2697
+ setLoadedDiscoveryKey(discoveryKey);
2698
+ return;
2699
+ }
2680
2700
  let aborted = false;
2701
+ setLoaded(false);
2681
2702
  const nextFetch = fetchImpl ?? globalThis.fetch.bind(globalThis);
2682
2703
  nextFetch(agentResourceUrl(apiBaseUrl, "/api/v1/agent/models"), {
2683
2704
  headers: scopedHeaders2(requestHeaders, storageScope)
2684
2705
  }).then((res) => res.ok ? res.json() : null).then((payload) => {
2685
- if (aborted || !payload?.models) return;
2706
+ if (aborted) return;
2707
+ if (!payload?.models) {
2708
+ userSelectedModelRef.current = false;
2709
+ setUserSelectedModel(false);
2710
+ setAvailableModels([]);
2711
+ setModelState(null);
2712
+ writePiComposerModelSelection(null, { storageScope, storage });
2713
+ setLoadedDiscoveryKey(discoveryKey);
2714
+ setLoaded(true);
2715
+ return;
2716
+ }
2686
2717
  setAvailableModels(payload.models);
2718
+ setLoadedDiscoveryKey(discoveryKey);
2719
+ setLoaded(true);
2687
2720
  const available = payload.models.filter((m) => m.available);
2688
2721
  setModelState((current) => {
2689
2722
  const currentAvailable = current ? available.some((m) => m.provider === current.provider && m.id === current.id) : false;
@@ -2691,14 +2724,24 @@ function useChatModelSelection({
2691
2724
  userSelectedModelRef.current = false;
2692
2725
  setUserSelectedModel(false);
2693
2726
  writePiComposerModelSelection(null, { storageScope, storage });
2694
- return payload.defaultModel ? { provider: payload.defaultModel.provider, id: payload.defaultModel.id } : null;
2727
+ if (payload.defaultModel) return { provider: payload.defaultModel.provider, id: payload.defaultModel.id };
2728
+ const firstAvailable = available[0];
2729
+ return firstAvailable ? { provider: firstAvailable.provider, id: firstAvailable.id } : null;
2695
2730
  });
2696
2731
  }).catch(() => {
2732
+ if (aborted) return;
2733
+ userSelectedModelRef.current = false;
2734
+ setUserSelectedModel(false);
2735
+ setAvailableModels([]);
2736
+ setModelState(null);
2737
+ writePiComposerModelSelection(null, { storageScope, storage });
2738
+ setLoadedDiscoveryKey(discoveryKey);
2739
+ setLoaded(true);
2697
2740
  });
2698
2741
  return () => {
2699
2742
  aborted = true;
2700
2743
  };
2701
- }, [apiBaseUrl, enabled, fetchImpl, requestHeaders, storage, storageScope]);
2744
+ }, [apiBaseUrl, discoveryKey, enabled, fetchImpl, requestHeaders, storage, storageScope]);
2702
2745
  useEffect5(() => {
2703
2746
  const onChange = (event) => {
2704
2747
  const next = parseModelSelection(event.detail);
@@ -2707,7 +2750,10 @@ function useChatModelSelection({
2707
2750
  globalThis.addEventListener?.("boring:model-change", onChange);
2708
2751
  return () => globalThis.removeEventListener?.("boring:model-change", onChange);
2709
2752
  }, [setModel]);
2710
- return { availableModels, model, setModel };
2753
+ const currentDiscoveryLoaded = !enabled || loaded && loadedDiscoveryKey === discoveryKey;
2754
+ const currentAvailableModels = currentDiscoveryLoaded ? availableModels : [];
2755
+ const currentModel = currentDiscoveryLoaded ? model : null;
2756
+ return { availableModels: currentAvailableModels, loaded: currentDiscoveryLoaded, model: currentModel, setModel };
2711
2757
  }
2712
2758
  function agentResourceUrl(apiBaseUrl, path) {
2713
2759
  const base = apiBaseUrl?.replace(/\/$/, "") ?? "";
@@ -2723,6 +2769,11 @@ function scopedHeaders2(headers, storageScope) {
2723
2769
 
2724
2770
  // src/front/hooks/useServerCommands.ts
2725
2771
  import { useEffect as useEffect6, useRef as useRef7, useState as useState8 } from "react";
2772
+ function serverCommandErrorMessage(body, fallback) {
2773
+ if (typeof body.error === "string") return body.error;
2774
+ if (body.error && typeof body.error === "object" && typeof body.error.message === "string") return body.error.message;
2775
+ return fallback;
2776
+ }
2726
2777
  function toSlashCommand(command, getSessionId, apiBaseUrl, requestHeaders, fetchImpl) {
2727
2778
  return {
2728
2779
  name: command.name,
@@ -2743,7 +2794,7 @@ function toSlashCommand(command, getSessionId, apiBaseUrl, requestHeaders, fetch
2743
2794
  const body = await res.json().catch(() => ({}));
2744
2795
  if (typeof globalThis.dispatchEvent === "function") {
2745
2796
  globalThis.dispatchEvent(new CustomEvent(WORKSPACE_COMMAND_NOTIFY_EVENT, {
2746
- detail: { message: body.error ?? `/${command.name} failed`, tone: "error", command: command.name }
2797
+ detail: { message: serverCommandErrorMessage(body, `/${command.name} failed`), tone: "error", command: command.name }
2747
2798
  }));
2748
2799
  }
2749
2800
  }
@@ -5663,7 +5714,7 @@ function sortByUpdatedDesc(a, b) {
5663
5714
  return a.id.localeCompare(b.id);
5664
5715
  }
5665
5716
 
5666
- // ../../node_modules/.pnpm/@earendil-works+pi-tui@0.75.5/node_modules/@earendil-works/pi-tui/dist/fuzzy.js
5717
+ // ../../node_modules/.pnpm/@earendil-works+pi-tui@0.80.3/node_modules/@earendil-works/pi-tui/dist/fuzzy.js
5667
5718
  function fuzzyMatch(query, text) {
5668
5719
  const queryLower = query.toLowerCase();
5669
5720
  const textLower = text.toLowerCase();
@@ -6082,7 +6133,7 @@ function ComposerBlockerNotice({
6082
6133
  {
6083
6134
  role: "status",
6084
6135
  "aria-live": "polite",
6085
- className: noticeSurfaceClass("info", "mx-auto mb-2 w-full max-w-3xl text-xs"),
6136
+ className: noticeSurfaceClass("info", "relative z-20 mx-auto mb-2 w-full max-w-3xl text-xs"),
6086
6137
  children: [
6087
6138
  /* @__PURE__ */ jsx16("span", { children: label }),
6088
6139
  blocker?.actions?.map((action) => {
@@ -7046,7 +7097,8 @@ function PiTimelineMessage({ message, isLast, isStreaming, showThoughts, toolRen
7046
7097
  const canOpen = Boolean(openArtifact && openPath);
7047
7098
  const openAttachment = () => {
7048
7099
  if (!openPath) return;
7049
- openArtifact?.(openPath);
7100
+ if (file.filesystem) openArtifact?.(openPath, { filesystem: file.filesystem });
7101
+ else openArtifact?.(openPath);
7050
7102
  };
7051
7103
  const openAttachmentFromKeyboard = (event) => {
7052
7104
  if (event.key !== "Enter" && event.key !== " ") return;
@@ -9149,7 +9201,7 @@ function PiChatComposerSurface({
9149
9201
  useLayoutEffect2(() => {
9150
9202
  resizeTextarea(textareaRef.current);
9151
9203
  }, [draft, resizeTextarea, textareaRef]);
9152
- return /* @__PURE__ */ jsxs30("div", { className: cn(chrome ? "px-4 pb-4 pt-2 sm:px-6 sm:pb-5" : "px-3 pb-2 pt-1"), children: [
9204
+ 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: [
9153
9205
  /* @__PURE__ */ jsx32(
9154
9206
  "div",
9155
9207
  {
@@ -9733,6 +9785,7 @@ function PiChatPanel({
9733
9785
  const selectedPiSession = selectedChatState ? activePiSession : void 0;
9734
9786
  const chatStatePending = Boolean(activeSessionId && chatState && chatState.sessionId !== activeSessionId);
9735
9787
  const selectedSessionPending = Boolean(activeSessionId && !selectedChatState);
9788
+ const modelDiscoveryEnabled = serverResourcesEnabled && availableModels === void 0;
9736
9789
  const modelDiscovery = useChatModelSelection({
9737
9790
  apiBaseUrl,
9738
9791
  defaultModel,
@@ -9740,13 +9793,19 @@ function PiChatPanel({
9740
9793
  requestHeaders: normalizedRequestHeaders,
9741
9794
  storage,
9742
9795
  storageScope,
9743
- enabled: serverResourcesEnabled && availableModels === void 0
9796
+ enabled: modelDiscoveryEnabled
9744
9797
  });
9745
9798
  const selectedModel = model === void 0 ? modelDiscovery.model : model;
9746
9799
  const modelOptions = useMemo13(
9747
9800
  () => modelOptionsForSelection(availableModels ?? modelDiscovery.availableModels, selectedModel),
9748
9801
  [availableModels, modelDiscovery.availableModels, selectedModel]
9749
9802
  );
9803
+ const selectedModelAuthorizedByDiscovery = !modelDiscoveryEnabled || Boolean(selectedModel && modelDiscovery.availableModels.some(
9804
+ (modelOption) => modelOption.available && modelOption.provider === selectedModel.provider && modelOption.id === selectedModel.id
9805
+ ));
9806
+ const serverModelSelectionPending = modelDiscoveryEnabled && !modelDiscovery.loaded;
9807
+ const serverModelSelectionUnavailable = modelDiscoveryEnabled && modelDiscovery.loaded && !selectedModelAuthorizedByDiscovery;
9808
+ const serverModelSelectionReady = !serverModelSelectionPending && !serverModelSelectionUnavailable;
9750
9809
  const [storedThinkingLevel, setStoredThinkingLevel] = useState20(
9751
9810
  () => thinkingControl ? readPiComposerSettings({ storageScope, storage }).thinkingLevel : DEFAULT_THINKING
9752
9811
  );
@@ -10077,7 +10136,7 @@ function PiChatPanel({
10077
10136
  insertSlashCommand(name);
10078
10137
  }, [dismissSlash, insertSlashCommand, openModelPicker, openThinkingPicker, setComposerDraft]);
10079
10138
  const policy = useMemo13(() => {
10080
- if (!selectedPiSession || !activeChatSessionId) return void 0;
10139
+ if (!selectedPiSession || !activeChatSessionId || !serverModelSelectionReady) return void 0;
10081
10140
  const policySession = {
10082
10141
  getState: () => {
10083
10142
  const state = selectedPiSession.getState();
@@ -10142,7 +10201,7 @@ function PiChatPanel({
10142
10201
  onMentionedFilesConsumed?.();
10143
10202
  }
10144
10203
  });
10145
- }, [activeChatSessionId, addLocalNotice, allowPromptDuringInitialHydration, clearMentionedFiles, composerBlocked, composerBlockerLabel, effectiveMentionedFiles, markLocalSubmitted, onBeforeSubmit, onCommandResult, onComposerWarning, onMentionedFilesConsumed, onPromptSubmitStarted, openModelPicker, openThinkingPicker, registry, reloadAgentPlugins, resetSession, runPluginUpdate, selectComposerModel, selectComposerThinking, selectedModel, selectedPiSession, selectedThinking, setComposerDraft, submitThinkingControl, suppressPreSubmitCancelledWarning]);
10204
+ }, [activeChatSessionId, addLocalNotice, allowPromptDuringInitialHydration, clearMentionedFiles, composerBlocked, composerBlockerLabel, effectiveMentionedFiles, markLocalSubmitted, onBeforeSubmit, onCommandResult, onComposerWarning, onMentionedFilesConsumed, onPromptSubmitStarted, openModelPicker, openThinkingPicker, registry, reloadAgentPlugins, resetSession, runPluginUpdate, selectComposerModel, selectComposerThinking, selectedModel, selectedPiSession, selectedThinking, serverModelSelectionReady, setComposerDraft, submitThinkingControl, suppressPreSubmitCancelledWarning]);
10146
10205
  const surfaceRunRejected = useCallback18((error) => {
10147
10206
  const errorCode = piChatErrorCode(error);
10148
10207
  dropLocalNotice(RUN_REJECTED_NOTICE_ID);
@@ -10287,10 +10346,12 @@ function PiChatPanel({
10287
10346
  const initialHydrationPromptAllowed = Boolean(
10288
10347
  allowPromptDuringInitialHydration && selectedChatState && selectedChatState.status === "hydrating" && !selectedChatState.hydrated && selectedChatState.history.messageCount === 0 && selectedChatState.committedMessages.length === 0 && selectedChatState.queue.followUps.length === 0 && Object.keys(selectedChatState.optimisticOutbox).length === 0 && !selectedChatState.streamingMessage
10289
10348
  );
10349
+ const noDiscoveredModelAvailable = modelDiscoveryEnabled && modelDiscovery.loaded && modelDiscovery.availableModels.every((modelOption) => !modelOption.available);
10350
+ const modelSelectionBlocked = serverModelSelectionPending || serverModelSelectionUnavailable || noDiscoveredModelAvailable;
10290
10351
  const disabled = !policy || sessionsLoading || composerBlocked;
10291
10352
  const isStreaming = isPiBusyStatus(status);
10292
10353
  const submitStatus = initialHydrationPromptAllowed ? "ready" : toPromptSubmitStatus(status);
10293
- const submitDisabled = !policy || sessionsLoading || composerBlocked && !isStreaming;
10354
+ const submitDisabled = !policy || sessionsLoading || modelSelectionBlocked || composerBlocked && !isStreaming;
10294
10355
  const mergedToolRenderers = useMemo13(() => mergeShadcnToolRenderers(toolRenderers), [toolRenderers]);
10295
10356
  const debugMessages = useMemo13(() => messages.map(toDebugUiMessage), [messages]);
10296
10357
  const onTextareaChange = useCallback18((event) => {
@@ -10309,13 +10370,6 @@ function PiChatPanel({
10309
10370
  window.dispatchEvent(new CustomEvent("boring:chat-session-status", {
10310
10371
  detail: { sessionId: activeChatSessionId, working: isStreaming }
10311
10372
  }));
10312
- if (!isStreaming) return;
10313
- const sessionId2 = activeChatSessionId;
10314
- return () => {
10315
- window.dispatchEvent(new CustomEvent("boring:chat-session-status", {
10316
- detail: { sessionId: sessionId2, working: false }
10317
- }));
10318
- };
10319
10373
  }, [activeChatSessionId, isStreaming]);
10320
10374
  const onTextareaKeyDown = useCallback18((event) => {
10321
10375
  if (event.key === "Escape" && isStreaming) {