@hachej/boring-agent 0.1.103 → 0.1.104

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.
@@ -31,7 +31,7 @@ import {
31
31
  WORKSPACE_COMMAND_NOTIFY_EVENT,
32
32
  extractToolUiMetadata,
33
33
  sanitizeToolUiMetadata
34
- } from "../chunk-7QM22DU7.js";
34
+ } from "../chunk-6DRHERVK.js";
35
35
  import "../chunk-YGRQ4Q5P.js";
36
36
  import {
37
37
  ErrorCode
@@ -85,7 +85,7 @@ function rawWorkspaceFileUrl(path, opts) {
85
85
  }
86
86
 
87
87
  // src/front/chat/PiChatPanel.tsx
88
- import { lazy, Suspense, useCallback as useCallback18, useEffect as useEffect21, useMemo as useMemo16, useRef as useRef18, useState as useState20 } from "react";
88
+ import { lazy, Suspense, useCallback as useCallback18, useEffect as useEffect21, useMemo as useMemo16, useRef as useRef17, useState as useState20 } from "react";
89
89
 
90
90
  // src/front/chatPanelSettings.ts
91
91
  var DEFAULT_THINKING = "medium";
@@ -2126,7 +2126,7 @@ function useComposerPickers({
2126
2126
  }
2127
2127
 
2128
2128
  // src/front/hooks/useChatModelSelection.ts
2129
- import { useCallback as useCallback6, useEffect as useEffect5, useMemo as useMemo2, useRef as useRef6, useState as useState7 } from "react";
2129
+ import { useCallback as useCallback6, useEffect as useEffect5, useMemo as useMemo2, useState as useState7 } from "react";
2130
2130
 
2131
2131
  // src/front/browserFiles.ts
2132
2132
  var convertBlobUrlToDataUrl = async (url) => {
@@ -2550,35 +2550,59 @@ function resolveStorage(storage) {
2550
2550
  }
2551
2551
 
2552
2552
  // src/front/hooks/useChatModelSelection.ts
2553
+ function sameModel(left, right) {
2554
+ return left?.provider === right?.provider && left?.id === right?.id;
2555
+ }
2556
+ function availableModel(models, model) {
2557
+ return Boolean(model && models.some((candidate) => candidate.available && candidate.provider === model.provider && candidate.id === model.id));
2558
+ }
2553
2559
  function useChatModelSelection({
2554
2560
  agentTypeId,
2555
2561
  apiBaseUrl,
2556
2562
  defaultModel,
2563
+ sessionId,
2564
+ sessionHydrated = sessionId === void 0,
2565
+ sessionIsNew = sessionId === void 0,
2566
+ sessionModel,
2557
2567
  fetch: fetchImpl,
2558
2568
  requestHeaders,
2559
2569
  storage,
2560
2570
  storageScope,
2561
2571
  enabled = true
2562
2572
  }) {
2563
- const initialModelState = useMemo2(() => readPiComposerSettings({ storageScope, storage }), []);
2564
- const [model, setModelState] = useState7(
2565
- () => initialModelState.model ?? defaultModel ?? null
2566
- );
2567
- const [userSelectedModel, setUserSelectedModel] = useState7(
2568
- () => initialModelState.userSelectedModel
2569
- );
2570
- const userSelectedModelRef = useRef6(userSelectedModel);
2571
- const loadedSettingsSourceRef = useRef6({ storage, storageScope });
2573
+ const initialSettings = useMemo2(() => readPiComposerSettings({ storageScope, storage }), []);
2574
+ const [selection, setSelection] = useState7(() => ({
2575
+ sessionId,
2576
+ storage,
2577
+ storageScope,
2578
+ localDefault: initialSettings.model ?? defaultModel ?? null
2579
+ }));
2572
2580
  useEffect5(() => {
2573
- userSelectedModelRef.current = userSelectedModel;
2574
- }, [userSelectedModel]);
2581
+ const settings = readPiComposerSettings({ storageScope, storage });
2582
+ setSelection((current) => {
2583
+ const sameSession = current.sessionId === sessionId;
2584
+ const pendingOverride2 = sameSession && current.pendingOverride && sessionModel && !sameModel(current.pendingOverride, sessionModel) ? current.pendingOverride : sessionIsNew && sessionModel && settings.model && !sameModel(settings.model, sessionModel) ? settings.model : void 0;
2585
+ return {
2586
+ sessionId,
2587
+ storage,
2588
+ storageScope,
2589
+ localDefault: settings.model ?? defaultModel ?? null,
2590
+ ...pendingOverride2 ? { pendingOverride: pendingOverride2 } : {}
2591
+ };
2592
+ });
2593
+ }, [defaultModel, sessionId, sessionIsNew, sessionModel?.id, sessionModel?.provider, storage, storageScope]);
2575
2594
  const setModel = useCallback6((next) => {
2576
2595
  const normalized = next === null ? null : parseModelSelection(next);
2577
- userSelectedModelRef.current = normalized !== null;
2578
- setUserSelectedModel(normalized !== null);
2579
- setModelState(normalized);
2596
+ setSelection((current) => {
2597
+ if (sessionModel) {
2598
+ const pendingOverride2 = normalized && !sameModel(normalized, sessionModel) ? normalized : void 0;
2599
+ return { ...current, sessionId, ...pendingOverride2 ? { pendingOverride: pendingOverride2 } : { pendingOverride: void 0 } };
2600
+ }
2601
+ if (sessionId && !sessionIsNew) return current;
2602
+ return { ...current, sessionId, localDefault: normalized, pendingOverride: void 0 };
2603
+ });
2580
2604
  writePiComposerModelSelection(normalized, { storageScope, storage });
2581
- }, [storage, storageScope]);
2605
+ }, [sessionId, sessionIsNew, sessionModel, storage, storageScope]);
2582
2606
  const discoveryKey = useMemo2(
2583
2607
  () => JSON.stringify({
2584
2608
  apiBaseUrl: apiBaseUrl ?? "",
@@ -2591,22 +2615,6 @@ function useChatModelSelection({
2591
2615
  const [availableModels, setAvailableModels] = useState7([]);
2592
2616
  const [loaded, setLoaded] = useState7(!enabled);
2593
2617
  const [loadedDiscoveryKey, setLoadedDiscoveryKey] = useState7(enabled ? null : discoveryKey);
2594
- useEffect5(() => {
2595
- if (loadedSettingsSourceRef.current.storage !== storage || loadedSettingsSourceRef.current.storageScope !== storageScope) return;
2596
- if (!userSelectedModel || !model) return;
2597
- writePiComposerModelSelection(model, { storageScope, storage });
2598
- }, [model, storage, storageScope, userSelectedModel]);
2599
- useEffect5(() => {
2600
- const settings = readPiComposerSettings({ storageScope, storage });
2601
- loadedSettingsSourceRef.current = { storage, storageScope };
2602
- userSelectedModelRef.current = settings.userSelectedModel;
2603
- setUserSelectedModel(settings.userSelectedModel);
2604
- setModelState(settings.model ?? defaultModel ?? null);
2605
- }, [storage, storageScope]);
2606
- useEffect5(() => {
2607
- if (userSelectedModelRef.current || !defaultModel) return;
2608
- setModelState(defaultModel);
2609
- }, [defaultModel]);
2610
2618
  useEffect5(() => {
2611
2619
  if (!enabled) {
2612
2620
  setLoaded(true);
@@ -2618,46 +2626,44 @@ function useChatModelSelection({
2618
2626
  const nextFetch = fetchImpl ?? globalThis.fetch.bind(globalThis);
2619
2627
  nextFetch(agentResourceUrl(apiBaseUrl, `/api/v1/agents/${encodeURIComponent(agentTypeId)}/models`), {
2620
2628
  headers: withStorageScope(requestHeaders, storageScope)
2621
- }).then((res) => res.ok ? res.json() : null).then((payload) => {
2629
+ }).then((response) => response.ok ? response.json() : null).then((payload) => {
2622
2630
  if (aborted) return;
2623
- if (!payload?.models) {
2624
- userSelectedModelRef.current = false;
2625
- setUserSelectedModel(false);
2631
+ const models = payload?.models;
2632
+ if (!models) {
2626
2633
  setAvailableModels([]);
2627
- setModelState(null);
2628
- writePiComposerModelSelection(null, { storageScope, storage });
2634
+ setSelection((current) => ({ ...current, pendingOverride: void 0, localDefault: sessionModel ?? null }));
2635
+ if (!sessionModel) writePiComposerModelSelection(null, { storageScope, storage });
2629
2636
  setLoadedDiscoveryKey(discoveryKey);
2630
2637
  setLoaded(true);
2631
2638
  return;
2632
2639
  }
2633
- setAvailableModels(payload.models);
2640
+ setAvailableModels(models);
2641
+ setSelection((current) => {
2642
+ const selected = current.pendingOverride ?? sessionModel ?? current.localDefault;
2643
+ if (availableModel(models, selected)) return current;
2644
+ if (sessionModel) return { ...current, pendingOverride: void 0 };
2645
+ if (sessionId && !sessionIsNew) return { ...current, pendingOverride: void 0, localDefault: null };
2646
+ const firstAvailable = models.find((candidate) => candidate.available);
2647
+ return {
2648
+ ...current,
2649
+ pendingOverride: void 0,
2650
+ localDefault: payload.defaultModel ?? (firstAvailable ? { provider: firstAvailable.provider, id: firstAvailable.id } : null)
2651
+ };
2652
+ });
2634
2653
  setLoadedDiscoveryKey(discoveryKey);
2635
2654
  setLoaded(true);
2636
- const available = payload.models.filter((m) => m.available);
2637
- setModelState((current) => {
2638
- const currentAvailable = current ? available.some((m) => m.provider === current.provider && m.id === current.id) : false;
2639
- if (currentAvailable) return current;
2640
- userSelectedModelRef.current = false;
2641
- setUserSelectedModel(false);
2642
- writePiComposerModelSelection(null, { storageScope, storage });
2643
- if (payload.defaultModel) return { provider: payload.defaultModel.provider, id: payload.defaultModel.id };
2644
- const firstAvailable = available[0];
2645
- return firstAvailable ? { provider: firstAvailable.provider, id: firstAvailable.id } : null;
2646
- });
2647
2655
  }).catch(() => {
2648
2656
  if (aborted) return;
2649
- userSelectedModelRef.current = false;
2650
- setUserSelectedModel(false);
2651
2657
  setAvailableModels([]);
2652
- setModelState(null);
2653
- writePiComposerModelSelection(null, { storageScope, storage });
2658
+ setSelection((current) => ({ ...current, pendingOverride: void 0, localDefault: sessionModel ?? null }));
2659
+ if (!sessionModel) writePiComposerModelSelection(null, { storageScope, storage });
2654
2660
  setLoadedDiscoveryKey(discoveryKey);
2655
2661
  setLoaded(true);
2656
2662
  });
2657
2663
  return () => {
2658
2664
  aborted = true;
2659
2665
  };
2660
- }, [agentTypeId, apiBaseUrl, discoveryKey, enabled, fetchImpl, requestHeaders, storage, storageScope]);
2666
+ }, [agentTypeId, apiBaseUrl, discoveryKey, enabled, fetchImpl, requestHeaders, sessionId, sessionIsNew, sessionModel, storage, storageScope]);
2661
2667
  useEffect5(() => {
2662
2668
  const onChange = (event) => {
2663
2669
  const next = parseModelSelection(event.detail);
@@ -2667,13 +2673,23 @@ function useChatModelSelection({
2667
2673
  return () => globalThis.removeEventListener?.("boring:model-change", onChange);
2668
2674
  }, [setModel]);
2669
2675
  const currentDiscoveryLoaded = !enabled || loaded && loadedDiscoveryKey === discoveryKey;
2670
- const currentAvailableModels = currentDiscoveryLoaded ? availableModels : [];
2671
- const currentModel = currentDiscoveryLoaded ? model : null;
2672
- return { availableModels: currentAvailableModels, loaded: currentDiscoveryLoaded, model: currentModel, setModel };
2676
+ const selectionBelongsToSession = selection.sessionId === sessionId && selection.storage === storage && selection.storageScope === storageScope;
2677
+ const pendingOverride = selectionBelongsToSession ? selection.pendingOverride : void 0;
2678
+ const isOverride = Boolean(pendingOverride && sessionModel && !sameModel(pendingOverride, sessionModel));
2679
+ const sessionAuthorityReady = !sessionId || sessionHydrated && (sessionModel !== void 0 || sessionIsNew);
2680
+ const model = currentDiscoveryLoaded && sessionAuthorityReady ? pendingOverride ?? sessionModel ?? selection.localDefault : null;
2681
+ return {
2682
+ availableModels: currentDiscoveryLoaded ? availableModels : [],
2683
+ loaded: currentDiscoveryLoaded,
2684
+ model,
2685
+ sessionModel,
2686
+ isOverride,
2687
+ setModel
2688
+ };
2673
2689
  }
2674
2690
 
2675
2691
  // src/front/hooks/useServerCommands.ts
2676
- import { useEffect as useEffect6, useRef as useRef7, useState as useState8 } from "react";
2692
+ import { useEffect as useEffect6, useRef as useRef6, useState as useState8 } from "react";
2677
2693
  function serverCommandErrorMessage(body, fallback) {
2678
2694
  if (typeof body.error === "string") return body.error;
2679
2695
  if (body.error && typeof body.error === "object" && typeof body.error.message === "string") return body.error.message;
@@ -2725,10 +2741,10 @@ function useServerCommands({
2725
2741
  refreshKey = 0
2726
2742
  }) {
2727
2743
  const [stamp, setStamp] = useState8(0);
2728
- const registrationsRef = useRef7({ registry, identity: void 0, names: /* @__PURE__ */ new Set() });
2744
+ const registrationsRef = useRef6({ registry, identity: void 0, names: /* @__PURE__ */ new Set() });
2729
2745
  const canonicalSessionId = sessionId.trim() || void 0;
2730
2746
  const identityKey = canonicalSessionId ? `${agentTypeId}\0${canonicalSessionId}` : void 0;
2731
- const identityKeyRef = useRef7(identityKey);
2747
+ const identityKeyRef = useRef6(identityKey);
2732
2748
  identityKeyRef.current = identityKey;
2733
2749
  useEffect6(() => () => {
2734
2750
  const registrations = registrationsRef.current;
@@ -3760,6 +3776,8 @@ function piChatReducer(state, action) {
3760
3776
  return hydrateFromSnapshot(state, action.snapshot, { allowSeqRewind: action.allowSeqRewind });
3761
3777
  case "cursor-sync":
3762
3778
  return syncCursor(state, action.cursor);
3779
+ case "model-confirmed":
3780
+ return { ...state, currentModel: action.model };
3763
3781
  case "event":
3764
3782
  return applySequencedEvent(state, action.event);
3765
3783
  case "optimistic-user-message":
@@ -3856,6 +3874,7 @@ function hydrateFromSnapshot(state, snapshot, options = {}) {
3856
3874
  ...state,
3857
3875
  sessionId: snapshot.sessionId,
3858
3876
  status: snapshot.status,
3877
+ currentModel: snapshot.currentModel,
3859
3878
  turnId: snapshot.activeTurnId,
3860
3879
  lastSeq: snapshot.seq,
3861
3880
  committedMessages,
@@ -3906,6 +3925,8 @@ function applySequencedEvent(state, event) {
3906
3925
  }
3907
3926
  function reduceEvent(state, event) {
3908
3927
  switch (event.type) {
3928
+ case "model-changed":
3929
+ return { ...state, currentModel: event.currentModel };
3909
3930
  case "agent-start":
3910
3931
  return { ...state, status: "streaming", turnId: event.turnId, error: void 0, streamingPreservedTextPartKeys: void 0 };
3911
3932
  case "agent-end":
@@ -4638,6 +4659,9 @@ var RemotePiSession = class {
4638
4659
  else if (!this.suspended) this.ensureReconnectScheduled();
4639
4660
  try {
4640
4661
  const receipt = await this.postCommand("/prompt", payload, PromptReceiptSchema);
4662
+ if (!this.disposed && payload.model) {
4663
+ this.store.dispatch({ type: "model-confirmed", model: payload.model }, { flush: true });
4664
+ }
4641
4665
  return receipt;
4642
4666
  } catch (error) {
4643
4667
  this.rollbackOptimisticMessage(payload.clientNonce);
@@ -5212,7 +5236,7 @@ function resolveBootStorage(storage) {
5212
5236
  }
5213
5237
 
5214
5238
  // src/front/chat/session/usePiSessions.ts
5215
- import { useCallback as useCallback7, useEffect as useEffect8, useLayoutEffect, useMemo as useMemo4, useRef as useRef8, useState as useState10 } from "react";
5239
+ import { useCallback as useCallback7, useEffect as useEffect8, useLayoutEffect, useMemo as useMemo4, useRef as useRef7, useState as useState10 } from "react";
5216
5240
  var SESSION_PAGE_SIZE = 50;
5217
5241
  var DEFAULT_MAX_RETRIES = 60;
5218
5242
  var DEFAULT_RETRY_BASE_MS = 250;
@@ -5275,22 +5299,22 @@ function usePiSessions(options) {
5275
5299
  const [loadingMore, setLoadingMore] = useState10(false);
5276
5300
  const [hasMore, setHasMore] = useState10(false);
5277
5301
  const [error, setError] = useState10(void 0);
5278
- const mountedRef = useRef8(false);
5279
- const refreshVersionRef = useRef8(0);
5280
- const retryTimerRef = useRef8(void 0);
5281
- const sessionsRef = useRef8([]);
5282
- const activeSessionIdRef = useRef8(activeSessionId);
5283
- const hasMoreRef = useRef8(hasMore);
5284
- const canonicalLoadedCountRef = useRef8(0);
5285
- const nextCursorRef = useRef8(void 0);
5286
- const loadMoreRequestSeqRef = useRef8(0);
5287
- const loadMoreInFlightRef = useRef8(false);
5288
- const pendingCreatedRef = useRef8(/* @__PURE__ */ new Map());
5289
- const pendingDeletedRef = useRef8(/* @__PURE__ */ new Set());
5290
- const pendingCreatedScopeRef = useRef8(requestScopeKey);
5291
- const dataStorageScopeRef = useRef8(storageScope);
5292
- const loadedDataSourceRef = useRef8(dataSourceKey);
5293
- const committedRequestScopeRef = useRef8(requestScopeKey);
5302
+ const mountedRef = useRef7(false);
5303
+ const refreshVersionRef = useRef7(0);
5304
+ const retryTimerRef = useRef7(void 0);
5305
+ const sessionsRef = useRef7([]);
5306
+ const activeSessionIdRef = useRef7(activeSessionId);
5307
+ const hasMoreRef = useRef7(hasMore);
5308
+ const canonicalLoadedCountRef = useRef7(0);
5309
+ const nextCursorRef = useRef7(void 0);
5310
+ const loadMoreRequestSeqRef = useRef7(0);
5311
+ const loadMoreInFlightRef = useRef7(false);
5312
+ const pendingCreatedRef = useRef7(/* @__PURE__ */ new Map());
5313
+ const pendingDeletedRef = useRef7(/* @__PURE__ */ new Set());
5314
+ const pendingCreatedScopeRef = useRef7(requestScopeKey);
5315
+ const dataStorageScopeRef = useRef7(storageScope);
5316
+ const loadedDataSourceRef = useRef7(dataSourceKey);
5317
+ const committedRequestScopeRef = useRef7(requestScopeKey);
5294
5318
  useIsomorphicLayoutEffect(() => {
5295
5319
  committedRequestScopeRef.current = requestScopeKey;
5296
5320
  }, [requestScopeKey]);
@@ -5298,7 +5322,7 @@ function usePiSessions(options) {
5298
5322
  (scope) => mountedRef.current && committedRequestScopeRef.current === scope,
5299
5323
  []
5300
5324
  );
5301
- const remoteSessionOptionsRef = useRef8(options.remoteSessionOptions);
5325
+ const remoteSessionOptionsRef = useRef7(options.remoteSessionOptions);
5302
5326
  remoteSessionOptionsRef.current = options.remoteSessionOptions;
5303
5327
  const remoteSessionOptionsKey = useMemo4(
5304
5328
  () => remoteSessionOptionsIdentity(options.remoteSessionOptions),
@@ -6149,7 +6173,7 @@ function searchPiSessions(sessions, query, options = {}) {
6149
6173
 
6150
6174
  // src/front/chat/components/PiConversationSurface.tsx
6151
6175
  import { Skeleton } from "@hachej/boring-ui-kit";
6152
- import { useCallback as useCallback14, useEffect as useEffect14, useLayoutEffect as useLayoutEffect2, useRef as useRef11, useState as useState15 } from "react";
6176
+ import { useCallback as useCallback14, useEffect as useEffect14, useLayoutEffect as useLayoutEffect2, useRef as useRef10, useState as useState15 } from "react";
6153
6177
  import { useStickToBottomContext as useStickToBottomContext2 } from "use-stick-to-bottom";
6154
6178
 
6155
6179
  // src/front/primitives/conversation.tsx
@@ -6497,7 +6521,7 @@ function QueuedComposerNotice({ followUps, onEdit }) {
6497
6521
  }
6498
6522
 
6499
6523
  // src/front/chat/components/PiTimelineMessage.tsx
6500
- import { useCallback as useCallback13, useEffect as useEffect13, useMemo as useMemo12, useRef as useRef10, useState as useState14 } from "react";
6524
+ import { useCallback as useCallback13, useEffect as useEffect13, useMemo as useMemo12, useRef as useRef9, useState as useState14 } from "react";
6501
6525
  import { CheckIcon as CheckIcon2, CopyIcon as CopyIcon3 } from "lucide-react";
6502
6526
  import { Button as Button10 } from "@hachej/boring-ui-kit";
6503
6527
 
@@ -6921,7 +6945,7 @@ import {
6921
6945
  useContext as useContext5,
6922
6946
  useEffect as useEffect11,
6923
6947
  useMemo as useMemo9,
6924
- useRef as useRef9,
6948
+ useRef as useRef8,
6925
6949
  useState as useState12
6926
6950
  } from "react";
6927
6951
  import { Streamdown as Streamdown2 } from "streamdown";
@@ -7013,9 +7037,9 @@ var Reasoning = memo5(
7013
7037
  defaultProp: void 0,
7014
7038
  prop: durationProp
7015
7039
  });
7016
- const hasEverStreamedRef = useRef9(isStreaming);
7040
+ const hasEverStreamedRef = useRef8(isStreaming);
7017
7041
  const [hasAutoClosed, setHasAutoClosed] = useState12(false);
7018
- const startTimeRef = useRef9(null);
7042
+ const startTimeRef = useRef8(null);
7019
7043
  useEffect11(() => {
7020
7044
  if (isStreaming) {
7021
7045
  hasEverStreamedRef.current = true;
@@ -7606,7 +7630,7 @@ function DefaultPiTimelineMessage({ message, isLast, isStreaming, showThoughts,
7606
7630
  ...effectiveMentionCatalog.skills.map((skill) => `skill:${skill.name}:${skill.commandName}`),
7607
7631
  effectiveMentionCatalog.files ? "files:enabled" : "files:disabled"
7608
7632
  ].join("|");
7609
- const mentionActivateRef = useRef10(onMentionActivate);
7633
+ const mentionActivateRef = useRef9(onMentionActivate);
7610
7634
  mentionActivateRef.current = onMentionActivate;
7611
7635
  const activateCurrentMention = useCallback13((mention) => {
7612
7636
  if (mention.kind === "file") {
@@ -8078,8 +8102,8 @@ function ConversationHistoryLoadingState() {
8078
8102
  }
8079
8103
  function TranscriptHistoryLoader({ olderCount, onLoadOlder }) {
8080
8104
  const { scrollRef } = useStickToBottomContext2();
8081
- const pendingAnchor = useRef11(null);
8082
- const armed = useRef11(true);
8105
+ const pendingAnchor = useRef10(null);
8106
+ const armed = useRef10(true);
8083
8107
  useEffect14(() => {
8084
8108
  const el = scrollRef.current;
8085
8109
  if (!el) return;
@@ -8144,7 +8168,7 @@ import { useCallback as useCallback16, useLayoutEffect as useLayoutEffect3 } fro
8144
8168
  import { motion as motion2, useReducedMotion } from "motion/react";
8145
8169
 
8146
8170
  // src/front/composer/PluginUpdateStatus.tsx
8147
- import { useEffect as useEffect15, useRef as useRef12 } from "react";
8171
+ import { useEffect as useEffect15, useRef as useRef11 } from "react";
8148
8172
 
8149
8173
  // src/front/composer/ComposerStatusBanner.tsx
8150
8174
  import { jsx as jsx25, jsxs as jsxs21 } from "react/jsx-runtime";
@@ -8267,7 +8291,7 @@ function PluginUpdateStatus({
8267
8291
  successAutoDismissMs = 1400,
8268
8292
  maxWidthClassName = "max-w-3xl"
8269
8293
  }) {
8270
- const onDismissRef = useRef12(onDismiss);
8294
+ const onDismissRef = useRef11(onDismiss);
8271
8295
  useEffect15(() => {
8272
8296
  onDismissRef.current = onDismiss;
8273
8297
  }, [onDismiss]);
@@ -8383,7 +8407,7 @@ function PluginUpdateStatus({
8383
8407
  }
8384
8408
 
8385
8409
  // src/front/composer/CommandRunStatus.tsx
8386
- import { useEffect as useEffect16, useRef as useRef13 } from "react";
8410
+ import { useEffect as useEffect16, useRef as useRef12 } from "react";
8387
8411
  import { Fragment as Fragment10, jsx as jsx27, jsxs as jsxs23 } from "react/jsx-runtime";
8388
8412
  function CommandRunStatus({
8389
8413
  state,
@@ -8391,7 +8415,7 @@ function CommandRunStatus({
8391
8415
  successAutoDismissMs = 1400,
8392
8416
  maxWidthClassName = "max-w-3xl"
8393
8417
  }) {
8394
- const onDismissRef = useRef13(onDismiss);
8418
+ const onDismissRef = useRef12(onDismiss);
8395
8419
  useEffect16(() => {
8396
8420
  onDismissRef.current = onDismiss;
8397
8421
  }, [onDismiss]);
@@ -8463,7 +8487,7 @@ function CommandRunStatus({
8463
8487
  }
8464
8488
 
8465
8489
  // src/front/chatPanelComposerControls.tsx
8466
- import { forwardRef, useEffect as useEffect17, useRef as useRef14, useState as useState16 } from "react";
8490
+ import { forwardRef, useEffect as useEffect17, useRef as useRef13, useState as useState16 } from "react";
8467
8491
  import { CheckIcon as CheckIcon3, ChevronDownIcon as ChevronDownIcon5 } from "lucide-react";
8468
8492
  import {
8469
8493
  Command,
@@ -8611,6 +8635,8 @@ function groupModelOptions(options) {
8611
8635
  var ModelSelectTrigger = forwardRef(function ModelSelectTrigger2({
8612
8636
  value,
8613
8637
  options,
8638
+ sessionModel,
8639
+ isOverride = false,
8614
8640
  disabled,
8615
8641
  trigger = "button",
8616
8642
  open = false,
@@ -8620,7 +8646,10 @@ var ModelSelectTrigger = forwardRef(function ModelSelectTrigger2({
8620
8646
  ...props
8621
8647
  }, ref) {
8622
8648
  const triggerLabel = modelTriggerLabel(value, options, emptyLabel);
8649
+ const sessionTriggerLabel = sessionModel ? modelTriggerLabel(sessionModel, options) : void 0;
8623
8650
  const triggerDisplay = value ? `${triggerLabel} (${displayProviderLabel(value.provider)})` : triggerLabel;
8651
+ const sessionDisplay = sessionModel && sessionTriggerLabel ? `${sessionTriggerLabel} (${displayProviderLabel(sessionModel.provider)})` : triggerDisplay;
8652
+ const accessibleDisplay = isOverride && value ? `Current model: ${sessionDisplay}. Next-message override: ${triggerDisplay}` : `Current model: ${sessionDisplay}`;
8624
8653
  return /* @__PURE__ */ jsx28(
8625
8654
  "button",
8626
8655
  {
@@ -8628,9 +8657,10 @@ var ModelSelectTrigger = forwardRef(function ModelSelectTrigger2({
8628
8657
  type: "button",
8629
8658
  "data-boring-agent-part": "model-select",
8630
8659
  "data-boring-state": disabled ? "disabled" : void 0,
8660
+ "data-boring-model-override": isOverride ? "true" : void 0,
8631
8661
  disabled,
8632
- "aria-label": trigger === "slash" ? `Open model picker. Current model: ${triggerDisplay}` : "Model",
8633
- title: trigger === "slash" ? `Open model picker. Current model: ${triggerDisplay}` : void 0,
8662
+ "aria-label": trigger === "slash" ? `Open model picker. ${accessibleDisplay}` : "Model",
8663
+ title: trigger === "slash" ? accessibleDisplay : void 0,
8634
8664
  onClick,
8635
8665
  className: cn(
8636
8666
  trigger === "slash" ? "h-8 max-w-[min(68vw,420px)] cursor-pointer overflow-hidden text-ellipsis whitespace-nowrap rounded-[var(--radius-md)] px-2 text-xs transition-colors hover:bg-muted/60 hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-popover disabled:pointer-events-none disabled:opacity-50 motion-reduce:transition-none" : selectorTriggerClass,
@@ -8641,8 +8671,17 @@ var ModelSelectTrigger = forwardRef(function ModelSelectTrigger2({
8641
8671
  ...props,
8642
8672
  children: trigger === "slash" ? /* @__PURE__ */ jsxs24(Fragment11, { children: [
8643
8673
  /* @__PURE__ */ jsx28("span", { className: "text-muted-foreground", children: "Model \xB7 " }),
8644
- /* @__PURE__ */ jsx28("span", { className: "min-w-0 truncate text-foreground", children: triggerLabel }),
8645
- value ? /* @__PURE__ */ jsxs24("span", { className: "shrink-0 text-muted-foreground", children: [
8674
+ /* @__PURE__ */ jsx28("span", { className: "min-w-0 truncate text-foreground", children: sessionTriggerLabel ?? triggerLabel }),
8675
+ sessionModel ? /* @__PURE__ */ jsxs24("span", { className: "shrink-0 text-muted-foreground", children: [
8676
+ " \xB7 ",
8677
+ displayProviderLabel(sessionModel.provider)
8678
+ ] }) : value ? /* @__PURE__ */ jsxs24("span", { className: "shrink-0 text-muted-foreground", children: [
8679
+ " \xB7 ",
8680
+ displayProviderLabel(value.provider)
8681
+ ] }) : null,
8682
+ isOverride && value ? /* @__PURE__ */ jsxs24("span", { className: "shrink-0 text-amber-600 dark:text-amber-400", children: [
8683
+ " \xB7 Next override: ",
8684
+ triggerLabel,
8646
8685
  " \xB7 ",
8647
8686
  displayProviderLabel(value.provider)
8648
8687
  ] }) : null
@@ -8669,8 +8708,8 @@ function ModelPickerMenu({
8669
8708
  const keyboardOptions = hideDefaultOption ? groupedOptions : [null, ...groupedOptions];
8670
8709
  const selectedIndex = currentKey ? Math.max(0, keyboardOptions.findIndex((option) => option && encodeModelKey(option) === currentKey)) : 0;
8671
8710
  const [activeIndex, setActiveIndex] = useState16(selectedIndex);
8672
- const activeIndexRef = useRef14(selectedIndex);
8673
- const menuRef = useRef14(null);
8711
+ const activeIndexRef = useRef13(selectedIndex);
8712
+ const menuRef = useRef13(null);
8674
8713
  useDismissOnOutsidePointer(menuRef, onClose);
8675
8714
  const setKeyboardActiveIndex = (next) => {
8676
8715
  const resolved = typeof next === "function" ? next(activeIndexRef.current) : next;
@@ -8797,7 +8836,7 @@ function ModelSelect({
8797
8836
  hideDefaultOption = false
8798
8837
  }) {
8799
8838
  const [open, setOpen] = useState16(false);
8800
- const previousOpenSignalRef = useRef14(openSignal);
8839
+ const previousOpenSignalRef = useRef13(openSignal);
8801
8840
  useEffect17(() => {
8802
8841
  if (disabled) setOpen(false);
8803
8842
  }, [disabled]);
@@ -8858,7 +8897,7 @@ function ThinkingSelect({
8858
8897
  className
8859
8898
  }) {
8860
8899
  const [open, setOpen] = useState16(false);
8861
- const previousOpenSignalRef = useRef14(openSignal);
8900
+ const previousOpenSignalRef = useRef13(openSignal);
8862
8901
  useEffect17(() => {
8863
8902
  if (disabled) setOpen(false);
8864
8903
  }, [disabled]);
@@ -8955,8 +8994,8 @@ function ThinkingPickerMenu({
8955
8994
  }) {
8956
8995
  const selectedIndex = Math.max(0, THINKING_LEVELS.indexOf(value));
8957
8996
  const [activeIndex, setActiveIndex] = useState16(selectedIndex);
8958
- const activeIndexRef = useRef14(selectedIndex);
8959
- const menuRef = useRef14(null);
8997
+ const activeIndexRef = useRef13(selectedIndex);
8998
+ const menuRef = useRef13(null);
8960
8999
  useDismissOnOutsidePointer(menuRef, onClose);
8961
9000
  const setKeyboardActiveIndex = (next) => {
8962
9001
  const resolved = typeof next === "function" ? next(activeIndexRef.current) : next;
@@ -9052,7 +9091,7 @@ import {
9052
9091
  XIcon as XIcon4
9053
9092
  } from "lucide-react";
9054
9093
  import { nanoid } from "nanoid";
9055
- import { useCallback as useCallback15, useEffect as useEffect18, useMemo as useMemo13, useRef as useRef15, useState as useState17 } from "react";
9094
+ import { useCallback as useCallback15, useEffect as useEffect18, useMemo as useMemo13, useRef as useRef14, useState as useState17 } from "react";
9056
9095
 
9057
9096
  // src/front/primitives/prompt-input-context.ts
9058
9097
  import { createContext as createContext7, useContext as useContext7 } from "react";
@@ -9119,12 +9158,12 @@ var PromptInput = ({
9119
9158
  }) => {
9120
9159
  const controller = useOptionalPromptInputController();
9121
9160
  const usingProvider = !!controller;
9122
- const providerTextValueRef = useRef15("");
9161
+ const providerTextValueRef = useRef14("");
9123
9162
  if (usingProvider) {
9124
9163
  providerTextValueRef.current = controller.textInput.value;
9125
9164
  }
9126
- const inputRef = useRef15(null);
9127
- const formRef = useRef15(null);
9165
+ const inputRef = useRef14(null);
9166
+ const formRef = useRef14(null);
9128
9167
  const [items, setItems] = useState17([]);
9129
9168
  const setFileUrlLocal = useCallback15((id, url, status, path) => {
9130
9169
  setItems((prev) => prev.map((f) => {
@@ -9135,7 +9174,7 @@ var PromptInput = ({
9135
9174
  }, []);
9136
9175
  const files = usingProvider ? controller.attachments.files : items;
9137
9176
  const [referencedSources, setReferencedSources] = useState17([]);
9138
- const filesRef = useRef15(files);
9177
+ const filesRef = useRef14(files);
9139
9178
  useEffect18(() => {
9140
9179
  filesRef.current = files;
9141
9180
  }, [files]);
@@ -9613,7 +9652,7 @@ var PromptInputSubmit = ({
9613
9652
  };
9614
9653
 
9615
9654
  // src/front/primitives/slash-command-picker.tsx
9616
- import { useEffect as useEffect19, useMemo as useMemo14, useRef as useRef16, useState as useState18 } from "react";
9655
+ import { useEffect as useEffect19, useMemo as useMemo14, useRef as useRef15, useState as useState18 } from "react";
9617
9656
  import { Fragment as Fragment13, jsx as jsx31, jsxs as jsxs27 } from "react/jsx-runtime";
9618
9657
  var ALL_PLUGINS = "__all__";
9619
9658
  function commandGroup(cmd) {
@@ -9625,9 +9664,9 @@ function SlashCommandPicker({ query, commands, onSelect, onDismiss }) {
9625
9664
  const [plugin, setPlugin] = useState18(ALL_PLUGINS);
9626
9665
  const [searchQuery, setSearchQuery] = useState18(query);
9627
9666
  const [activeIdx, setActiveIdx] = useState18(0);
9628
- const containerRef = useRef16(null);
9629
- const listRef = useRef16(null);
9630
- const inputRef = useRef16(null);
9667
+ const containerRef = useRef15(null);
9668
+ const listRef = useRef15(null);
9669
+ const inputRef = useRef15(null);
9631
9670
  useEffect19(() => {
9632
9671
  setSearchQuery(query);
9633
9672
  }, [query]);
@@ -9862,6 +9901,8 @@ function PiChatComposerSurface({
9862
9901
  onDismissSlash,
9863
9902
  modelPickerOpen,
9864
9903
  selectedModel,
9904
+ sessionModel,
9905
+ modelOverride,
9865
9906
  modelOptions,
9866
9907
  modelControlled,
9867
9908
  hideDefaultModelOption = false,
@@ -10172,6 +10213,8 @@ function PiChatComposerSurface({
10172
10213
  ModelSelectTrigger,
10173
10214
  {
10174
10215
  value: selectedModel,
10216
+ sessionModel,
10217
+ isOverride: modelOverride,
10175
10218
  options: modelOptions,
10176
10219
  disabled: isStreaming || modelControlled,
10177
10220
  trigger: "slash",
@@ -10215,7 +10258,7 @@ function getHeaderValue(headers, name) {
10215
10258
  }
10216
10259
 
10217
10260
  // src/front/chat/piChatPanelHooks.ts
10218
- import { useCallback as useCallback17, useEffect as useEffect20, useMemo as useMemo15, useRef as useRef17, useState as useState19, useSyncExternalStore } from "react";
10261
+ import { useCallback as useCallback17, useEffect as useEffect20, useMemo as useMemo15, useRef as useRef16, useState as useState19, useSyncExternalStore } from "react";
10219
10262
 
10220
10263
  // src/front/chat/piChatPanelUtils.ts
10221
10264
  function headersContentKey(headers) {
@@ -10336,7 +10379,7 @@ function useExternalRemotePiSession({
10336
10379
  enabled = true
10337
10380
  }) {
10338
10381
  const [session, setSession] = useState19();
10339
- const remoteSessionOptionsRef = useRef17(remoteSessionOptions);
10382
+ const remoteSessionOptionsRef = useRef16(remoteSessionOptions);
10340
10383
  remoteSessionOptionsRef.current = remoteSessionOptions;
10341
10384
  const requestHeadersKey = headersContentKey(requestHeaders);
10342
10385
  const stableRequestHeaders = useMemo15(
@@ -10489,11 +10532,11 @@ function PiChatPanel({
10489
10532
  }) {
10490
10533
  const externalSessionId = sessionId?.trim() || void 0;
10491
10534
  const showSessionSidebar = showSessions ?? externalSessionId === void 0;
10492
- const onDataRef = useRef18(onData);
10535
+ const onDataRef = useRef17(onData);
10493
10536
  onDataRef.current = onData;
10494
- const onTurnCompleteRef = useRef18(onTurnComplete);
10537
+ const onTurnCompleteRef = useRef17(onTurnComplete);
10495
10538
  onTurnCompleteRef.current = onTurnComplete;
10496
- const sessionListRefreshRef = useRef18(void 0);
10539
+ const sessionListRefreshRef = useRef17(void 0);
10497
10540
  const requestHeadersKey = useMemo16(() => headersContentKey(requestHeaders), [requestHeaders]);
10498
10541
  const normalizedRequestHeaders = useMemo16(() => normalizedHeadersFromContentKey(requestHeadersKey), [requestHeadersKey]);
10499
10542
  const remoteSessionOptionsWithEvents = useMemo16(() => ({
@@ -10557,13 +10600,21 @@ function PiChatPanel({
10557
10600
  agentTypeId,
10558
10601
  apiBaseUrl,
10559
10602
  defaultModel,
10603
+ sessionId: activeSessionId,
10604
+ sessionHydrated: selectedChatState?.hydrated ?? false,
10605
+ sessionIsNew: Boolean(
10606
+ selectedChatState?.hydrated && selectedChatState.history.messageCount === 0 && selectedChatState.committedMessages.length === 0 && selectedChatState.queue.followUps.length === 0 && Object.keys(selectedChatState.optimisticOutbox).length === 0 && !selectedChatState.streamingMessage
10607
+ ),
10608
+ sessionModel: selectedChatState?.currentModel,
10560
10609
  fetch: fetch2,
10561
10610
  requestHeaders: normalizedRequestHeaders,
10562
10611
  storage,
10563
10612
  storageScope,
10564
10613
  enabled: modelDiscoveryEnabled
10565
10614
  });
10615
+ const sessionModel = selectedChatState?.currentModel;
10566
10616
  const selectedModel = model === void 0 ? modelDiscovery.model : model;
10617
+ const modelOverride = model === void 0 ? modelDiscovery.isOverride : Boolean(model && sessionModel && (model.provider !== sessionModel.provider || model.id !== sessionModel.id));
10567
10618
  const modelOptions = useMemo16(
10568
10619
  () => modelOptionsForSelection(availableModels ?? modelDiscovery.availableModels, selectedModel),
10569
10620
  [availableModels, modelDiscovery.availableModels, selectedModel]
@@ -10602,13 +10653,13 @@ function PiChatPanel({
10602
10653
  const [modelPickerOpen, setModelPickerOpen] = useState20(false);
10603
10654
  const [thinkingPickerOpen, setThinkingPickerOpen] = useState20(false);
10604
10655
  const [draft, setDraft] = useState20(() => initialDraft ?? "");
10605
- const draftRef = useRef18(draft);
10656
+ const draftRef = useRef17(draft);
10606
10657
  draftRef.current = draft;
10607
- const initialDraftGuard = useRef18(new InitialDraftAutoSubmitGuard());
10608
- const pendingAutoSubmitSettleRef = useRef18(void 0);
10609
- const acceptedAutoSubmitSettleRef = useRef18(void 0);
10610
- const resetInProgressRef = useRef18(false);
10611
- const autoCreateInFlightRef = useRef18(false);
10658
+ const initialDraftGuard = useRef17(new InitialDraftAutoSubmitGuard());
10659
+ const pendingAutoSubmitSettleRef = useRef17(void 0);
10660
+ const acceptedAutoSubmitSettleRef = useRef17(void 0);
10661
+ const resetInProgressRef = useRef17(false);
10662
+ const autoCreateInFlightRef = useRef17(false);
10612
10663
  const settlePendingAutoSubmit = useCallback18((sessionId2) => {
10613
10664
  const pendingSessionId = pendingAutoSubmitSettleRef.current;
10614
10665
  if (!pendingSessionId || sessionId2 && pendingSessionId !== sessionId2) return false;
@@ -10617,19 +10668,19 @@ function PiChatPanel({
10617
10668
  onAutoSubmitInitialDraftSettled?.();
10618
10669
  return true;
10619
10670
  }, [onAutoSubmitInitialDraftSettled]);
10620
- const prevStatusRef = useRef18("idle");
10621
- const statusRef = useRef18("idle");
10622
- const scrollToBottomRef = useRef18(() => {
10671
+ const prevStatusRef = useRef17("idle");
10672
+ const statusRef = useRef17("idle");
10673
+ const scrollToBottomRef = useRef17(() => {
10623
10674
  });
10624
- const textareaRef = useRef18(null);
10675
+ const textareaRef = useRef17(null);
10625
10676
  const [localNotices, setLocalNotices] = useState20([]);
10626
10677
  const [dismissedNoticeIds, setDismissedNoticeIds] = useState20(() => /* @__PURE__ */ new Set());
10627
10678
  const [pluginUpdateState, setPluginUpdateState] = useState20(null);
10628
10679
  const [commandNotifyState, setCommandNotifyState] = useState20(null);
10629
- const commandRunIdRef = useRef18(0);
10680
+ const commandRunIdRef = useRef17(0);
10630
10681
  const [serverSkillsRefreshKey, setServerSkillsRefreshKey] = useState20(0);
10631
10682
  const [localSubmittedSessionId, setLocalSubmittedSessionId] = useState20();
10632
- const localSubmittedSessionRef = useRef18(void 0);
10683
+ const localSubmittedSessionRef = useRef17(void 0);
10633
10684
  const { attachmentNotice, setAttachmentNotice } = useAttachmentNotice();
10634
10685
  const composerContributions = useComposerContributions();
10635
10686
  const contributedCommands = useMemo16(
@@ -11261,6 +11312,8 @@ function PiChatPanel({
11261
11312
  onDismissSlash: dismissSlash,
11262
11313
  modelPickerOpen,
11263
11314
  selectedModel,
11315
+ sessionModel,
11316
+ modelOverride,
11264
11317
  modelOptions,
11265
11318
  modelControlled: model !== void 0,
11266
11319
  hideDefaultModelOption,