@hachej/boring-agent 0.1.95 → 0.1.97

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.
Files changed (45) hide show
  1. package/dist/DebugDrawer-2TO5SE2D.js +7 -0
  2. package/dist/{chunk-NITBKFG6.js → chunk-3OEDBRKY.js} +93 -58
  3. package/dist/{chunk-BOIV2I3N.js → chunk-46HJK2ES.js} +31 -16
  4. package/dist/{chunk-AQBXNPMD.js → chunk-4IQ4HHBG.js} +4 -0
  5. package/dist/{chunk-2EMAKBVR.js → chunk-CXMM24NW.js} +9039 -7788
  6. package/dist/{chunk-OFQ7J6SI.js → chunk-LSO2AO6Q.js} +57 -1
  7. package/dist/{chunk-2GBHYJVC.js → chunk-VXF5DWS6.js} +34 -0
  8. package/dist/{chunk-2TMS2RU2.js → chunk-WC5KBGP2.js} +518 -165
  9. package/dist/chunk-YGRQ4Q5P.js +20 -0
  10. package/dist/core/index.d.ts +5 -5
  11. package/dist/core/index.js +604 -7
  12. package/dist/{createHarness-UCD74XXK.js → createHarness-PW3A2MRB.js} +3 -3
  13. package/dist/{errors-G6T6S7PQ.d.ts → errors-cNC4HMpV.d.ts} +14 -7
  14. package/dist/eval/index.d.ts +5 -3
  15. package/dist/eval/index.js +31 -23
  16. package/dist/file-search-QxnanNjO.d.ts +87 -0
  17. package/dist/front/index.d.ts +86 -16
  18. package/dist/front/index.js +839 -343
  19. package/dist/front/styles.css +1171 -2129
  20. package/dist/{harness-Cc48o6uS.d.ts → harness-DS8jLKIx.d.ts} +4 -2
  21. package/dist/{lease-CusB-uLg.d.ts → lease-D8WC6pQl.d.ts} +10 -89
  22. package/dist/{middleware-CzW8n7B7.d.ts → middleware-Cq_cYHjJ.d.ts} +1 -1
  23. package/dist/piChatEvent-B3ehh9V0.d.ts +384 -0
  24. package/dist/{piChatSessionService-C0KbcGw9.d.ts → piChatSessionService-xnv8rcdh.d.ts} +1 -1
  25. package/dist/{sandbox-DAD8cWQR.d.ts → sandbox-KQiEdz4W.d.ts} +12 -1
  26. package/dist/server/agent-host/testing/compositionRouteProof.d.ts +8 -0
  27. package/dist/server/agent-host/testing/compositionRouteProof.js +78 -0
  28. package/dist/server/agent-host/testing/gatewayConformance.d.ts +6 -6
  29. package/dist/server/agent-host/testing/gatewayConformance.js +17 -9
  30. package/dist/server/index.d.ts +572 -370
  31. package/dist/server/index.js +69 -17
  32. package/dist/server/worker/index.d.ts +2 -2
  33. package/dist/server/worker/index.js +9 -9
  34. package/dist/shared/index.d.ts +212 -56
  35. package/dist/shared/index.js +43 -7
  36. package/dist/{types-C-Eroi__.d.ts → types-D_SD-7_l.d.ts} +304 -87
  37. package/docs/AGENT_GATEWAY_V0.md +6 -3
  38. package/docs/AGENT_PACKAGES.md +80 -0
  39. package/docs/ERROR_CODES.md +58 -0
  40. package/docs/README.md +1 -0
  41. package/docs/runtime.md +19 -2
  42. package/package.json +34 -29
  43. package/dist/DebugDrawer-RYEBQ6CO.js +0 -6
  44. package/dist/chunk-EOMY2HDT.js +0 -619
  45. package/dist/piChatEvent-BsMp3MSo.d.ts +0 -368
@@ -1,9 +1,14 @@
1
1
  import {
2
2
  DebugDrawer,
3
+ agentResourceUrl,
3
4
  cn,
4
- copyTextToClipboard
5
- } from "../chunk-BOIV2I3N.js";
5
+ copyTextToClipboard,
6
+ createRequestId,
7
+ withStorageScope
8
+ } from "../chunk-46HJK2ES.js";
6
9
  import {
10
+ AGENT_GATEWAY_ERROR_CODES,
11
+ AgentGatewayErrorCode,
7
12
  CommandReceiptSchema,
8
13
  FollowUpReceiptSchema,
9
14
  PiChatSnapshotSchema,
@@ -15,10 +20,11 @@ import {
15
20
  WORKSPACE_COMMAND_NOTIFY_EVENT,
16
21
  extractToolUiMetadata,
17
22
  sanitizeToolUiMetadata
18
- } from "../chunk-OFQ7J6SI.js";
23
+ } from "../chunk-LSO2AO6Q.js";
24
+ import "../chunk-YGRQ4Q5P.js";
19
25
  import {
20
26
  ErrorCode
21
- } from "../chunk-2GBHYJVC.js";
27
+ } from "../chunk-VXF5DWS6.js";
22
28
 
23
29
  // src/front/upload/uploadFile.ts
24
30
  function readAsDataUrl(file) {
@@ -2012,14 +2018,16 @@ function MentionPicker({
2012
2018
  setActiveIdx(0);
2013
2019
  const ctrl = new AbortController();
2014
2020
  const runFetch = fetchImpl ?? globalThis.fetch.bind(globalThis);
2015
- const headers = scopedHeaders(requestHeaders, storageScope);
2021
+ const headers = withStorageScope(requestHeaders, storageScope);
2016
2022
  const base = apiBaseUrl.replace(/\/$/, "");
2017
2023
  const t = setTimeout(() => {
2018
2024
  runFetch(`${base}/api/v1/files/search?q=${encodeURIComponent(mentionSearchGlob(mention.query))}&limit=8`, {
2019
2025
  signal: ctrl.signal,
2020
2026
  ...headers ? { headers } : {}
2021
2027
  }).then((r) => r.ok ? r.json() : null).then((data) => {
2022
- if (data?.results) setResults(data.results.slice(0, 8));
2028
+ if (data?.resources) {
2029
+ setResults(data.resources.filter((resource) => resource.filesystem === "user").map((resource) => resource.path).slice(0, 8));
2030
+ }
2023
2031
  }).catch(() => {
2024
2032
  });
2025
2033
  }, 120);
@@ -2070,15 +2078,6 @@ function detectMention(value, cursorPos) {
2070
2078
  const atIdx = before.lastIndexOf("@");
2071
2079
  return { query: match[2], anchorStart: atIdx, anchorEnd: cursorPos };
2072
2080
  }
2073
- function scopedHeaders(requestHeaders, storageScope) {
2074
- const headers = {};
2075
- for (const [key, value] of Object.entries(requestHeaders ?? {})) {
2076
- if (value !== void 0) headers[key] = value;
2077
- }
2078
- const hasStorageScope = Object.keys(headers).some((key) => key.toLowerCase() === "x-boring-storage-scope");
2079
- if (storageScope && !hasStorageScope) headers["x-boring-storage-scope"] = storageScope;
2080
- return Object.keys(headers).length > 0 ? headers : void 0;
2081
- }
2082
2081
 
2083
2082
  // src/front/useComposerPickers.ts
2084
2083
  function setTextareaValue2(textarea, value) {
@@ -2599,6 +2598,7 @@ function resolveStorage(storage) {
2599
2598
 
2600
2599
  // src/front/hooks/useChatModelSelection.ts
2601
2600
  function useChatModelSelection({
2601
+ agentTypeId,
2602
2602
  apiBaseUrl,
2603
2603
  defaultModel,
2604
2604
  fetch: fetchImpl,
@@ -2620,14 +2620,16 @@ function useChatModelSelection({
2620
2620
  userSelectedModelRef.current = userSelectedModel;
2621
2621
  }, [userSelectedModel]);
2622
2622
  const setModel = useCallback6((next) => {
2623
- userSelectedModelRef.current = next !== null;
2624
- setUserSelectedModel(next !== null);
2625
- setModelState(next);
2626
- writePiComposerModelSelection(next, { storageScope, storage });
2623
+ const normalized = next === null ? null : parseModelSelection(next);
2624
+ userSelectedModelRef.current = normalized !== null;
2625
+ setUserSelectedModel(normalized !== null);
2626
+ setModelState(normalized);
2627
+ writePiComposerModelSelection(normalized, { storageScope, storage });
2627
2628
  }, [storage, storageScope]);
2628
2629
  const discoveryKey = useMemo2(
2629
2630
  () => JSON.stringify({
2630
2631
  apiBaseUrl: apiBaseUrl ?? "",
2632
+ agentTypeId,
2631
2633
  headers: Object.entries(requestHeaders ?? {}).sort(([a], [b]) => a.localeCompare(b)),
2632
2634
  storageScope: storageScope ?? ""
2633
2635
  }),
@@ -2661,8 +2663,8 @@ function useChatModelSelection({
2661
2663
  let aborted = false;
2662
2664
  setLoaded(false);
2663
2665
  const nextFetch = fetchImpl ?? globalThis.fetch.bind(globalThis);
2664
- nextFetch(agentResourceUrl(apiBaseUrl, "/api/v1/agent/models"), {
2665
- headers: scopedHeaders2(requestHeaders, storageScope)
2666
+ nextFetch(agentResourceUrl(apiBaseUrl, `/api/v1/agents/${encodeURIComponent(agentTypeId)}/models`), {
2667
+ headers: withStorageScope(requestHeaders, storageScope)
2666
2668
  }).then((res) => res.ok ? res.json() : null).then((payload) => {
2667
2669
  if (aborted) return;
2668
2670
  if (!payload?.models) {
@@ -2702,7 +2704,7 @@ function useChatModelSelection({
2702
2704
  return () => {
2703
2705
  aborted = true;
2704
2706
  };
2705
- }, [apiBaseUrl, discoveryKey, enabled, fetchImpl, requestHeaders, storage, storageScope]);
2707
+ }, [agentTypeId, apiBaseUrl, discoveryKey, enabled, fetchImpl, requestHeaders, storage, storageScope]);
2706
2708
  useEffect5(() => {
2707
2709
  const onChange = (event) => {
2708
2710
  const next = parseModelSelection(event.detail);
@@ -2716,17 +2718,6 @@ function useChatModelSelection({
2716
2718
  const currentModel = currentDiscoveryLoaded ? model : null;
2717
2719
  return { availableModels: currentAvailableModels, loaded: currentDiscoveryLoaded, model: currentModel, setModel };
2718
2720
  }
2719
- function agentResourceUrl(apiBaseUrl, path) {
2720
- const base = apiBaseUrl?.replace(/\/$/, "") ?? "";
2721
- return `${base}${path}`;
2722
- }
2723
- function scopedHeaders2(headers, storageScope) {
2724
- if (!headers && !storageScope) return void 0;
2725
- const result = { ...headers ?? {} };
2726
- const hasStorageScope = Object.keys(result).some((key) => key.toLowerCase() === "x-boring-storage-scope");
2727
- if (storageScope && !hasStorageScope) result["x-boring-storage-scope"] = storageScope;
2728
- return result;
2729
- }
2730
2721
 
2731
2722
  // src/front/hooks/useServerCommands.ts
2732
2723
  import { useEffect as useEffect6, useRef as useRef7, useState as useState8 } from "react";
@@ -2735,21 +2726,21 @@ function serverCommandErrorMessage(body, fallback) {
2735
2726
  if (body.error && typeof body.error === "object" && typeof body.error.message === "string") return body.error.message;
2736
2727
  return fallback;
2737
2728
  }
2738
- function toSlashCommand(command, getSessionId, apiBaseUrl, requestHeaders, fetchImpl) {
2729
+ function toSlashCommand(command, identity, isCurrentIdentity, apiBaseUrl, requestHeaders, fetchImpl) {
2739
2730
  return {
2740
2731
  name: command.name,
2741
2732
  description: command.description ?? "",
2742
2733
  source: command.source,
2743
2734
  ...command.sourcePlugin ? { sourcePlugin: command.sourcePlugin } : {},
2744
2735
  handler: async (args) => {
2736
+ if (!isCurrentIdentity(identity.key)) return;
2745
2737
  const base = apiBaseUrl?.replace(/\/$/, "") ?? "";
2746
- const params = new URLSearchParams({ sessionId: getSessionId() });
2747
- const url = `${base}/api/v1/agent/commands/execute?${params.toString()}`;
2738
+ const url = `${base}/api/v1/agents/${encodeURIComponent(identity.agentTypeId)}/commands/execute`;
2748
2739
  try {
2749
2740
  const res = await fetchImpl(url, {
2750
2741
  method: "POST",
2751
2742
  headers: { ...requestHeaders ?? {}, "content-type": "application/json" },
2752
- body: JSON.stringify({ name: command.name, args })
2743
+ body: JSON.stringify({ requestId: createRequestId("command"), sessionId: identity.sessionId, name: command.name, args })
2753
2744
  });
2754
2745
  if (!res.ok) {
2755
2746
  const body = await res.json().catch(() => ({}));
@@ -2770,6 +2761,7 @@ function toSlashCommand(command, getSessionId, apiBaseUrl, requestHeaders, fetch
2770
2761
  };
2771
2762
  }
2772
2763
  function useServerCommands({
2764
+ agentTypeId,
2773
2765
  registry,
2774
2766
  requestHeaders,
2775
2767
  sessionId,
@@ -2780,41 +2772,62 @@ function useServerCommands({
2780
2772
  refreshKey = 0
2781
2773
  }) {
2782
2774
  const [stamp, setStamp] = useState8(0);
2783
- const registeredNamesRef = useRef7(/* @__PURE__ */ new Set());
2784
- const sessionIdRef = useRef7(sessionId);
2785
- useEffect6(() => {
2786
- sessionIdRef.current = sessionId;
2787
- }, [sessionId]);
2775
+ const registrationsRef = useRef7({ registry, identity: void 0, names: /* @__PURE__ */ new Set() });
2776
+ const canonicalSessionId = sessionId.trim() || void 0;
2777
+ const identityKey = canonicalSessionId ? `${agentTypeId}\0${canonicalSessionId}` : void 0;
2778
+ const identityKeyRef = useRef7(identityKey);
2779
+ identityKeyRef.current = identityKey;
2780
+ useEffect6(() => () => {
2781
+ const registrations = registrationsRef.current;
2782
+ for (const name of registrations.names) registrations.registry.unregister(name);
2783
+ registrationsRef.current = { registry: registrations.registry, identity: void 0, names: /* @__PURE__ */ new Set() };
2784
+ }, []);
2788
2785
  useEffect6(() => {
2789
2786
  const clearRegistered = () => {
2790
- if (registeredNamesRef.current.size === 0) return false;
2791
- for (const name of registeredNamesRef.current) registry.unregister(name);
2792
- registeredNamesRef.current = /* @__PURE__ */ new Set();
2793
- return true;
2787
+ const registrations = registrationsRef.current;
2788
+ for (const name of registrations.names) registrations.registry.unregister(name);
2789
+ const changed = registrations.names.size > 0;
2790
+ registrationsRef.current = { registry, identity: void 0, names: /* @__PURE__ */ new Set() };
2791
+ return changed;
2794
2792
  };
2795
- if (!enabled) {
2793
+ if (registrationsRef.current.registry !== registry) {
2794
+ if (clearRegistered()) setStamp((n) => n + 1);
2795
+ }
2796
+ if (!enabled || !canonicalSessionId || !identityKey) {
2796
2797
  if (clearRegistered()) setStamp((n) => n + 1);
2797
2798
  return;
2798
2799
  }
2800
+ if (registrationsRef.current.identity !== identityKey) {
2801
+ if (clearRegistered()) setStamp((n) => n + 1);
2802
+ }
2799
2803
  let aborted = false;
2800
2804
  const nextFetch = fetchImpl ?? globalThis.fetch.bind(globalThis);
2801
2805
  const base = apiBaseUrl?.replace(/\/$/, "") ?? "";
2802
- const url = `${base}/api/v1/agent/commands?sessionId=${encodeURIComponent(sessionIdRef.current)}`;
2803
- const headers = scopedHeaders3(requestHeaders, storageScope);
2806
+ const url = `${base}/api/v1/agents/${encodeURIComponent(agentTypeId)}/commands?sessionId=${encodeURIComponent(canonicalSessionId)}`;
2807
+ const headers = withStorageScope(requestHeaders, storageScope);
2804
2808
  nextFetch(url, { headers }).then(async (res) => {
2805
2809
  if (!res.ok) throw new Error(`commands request failed (${res.status})`);
2806
2810
  return await res.json();
2807
2811
  }).then((payload) => {
2808
2812
  if (aborted) return;
2809
2813
  const removed = clearRegistered();
2814
+ const registeredNames = /* @__PURE__ */ new Set();
2810
2815
  let added = false;
2811
2816
  for (const serverCommand of payload.commands ?? []) {
2812
- const command = toSlashCommand(serverCommand, () => sessionIdRef.current, apiBaseUrl, headers, nextFetch);
2817
+ const command = toSlashCommand(
2818
+ serverCommand,
2819
+ { key: identityKey, sessionId: canonicalSessionId, agentTypeId },
2820
+ (key) => identityKeyRef.current === key,
2821
+ apiBaseUrl,
2822
+ headers,
2823
+ nextFetch
2824
+ );
2813
2825
  if (registry.get(command.name)) continue;
2814
2826
  registry.register(command);
2815
- registeredNamesRef.current.add(command.name);
2827
+ registeredNames.add(command.name);
2816
2828
  added = true;
2817
2829
  }
2830
+ registrationsRef.current = { registry, identity: identityKey, names: registeredNames };
2818
2831
  if (removed || added) setStamp((n) => n + 1);
2819
2832
  }).catch(() => {
2820
2833
  if (aborted) return;
@@ -2823,16 +2836,9 @@ function useServerCommands({
2823
2836
  return () => {
2824
2837
  aborted = true;
2825
2838
  };
2826
- }, [apiBaseUrl, enabled, fetchImpl, refreshKey, requestHeaders, registry, storageScope]);
2839
+ }, [agentTypeId, apiBaseUrl, canonicalSessionId, enabled, fetchImpl, identityKey, refreshKey, requestHeaders, registry, storageScope]);
2827
2840
  return stamp;
2828
2841
  }
2829
- function scopedHeaders3(headers, storageScope) {
2830
- if (!headers && !storageScope) return void 0;
2831
- const result = { ...headers ?? {} };
2832
- const hasScope = Object.keys(result).some((k) => k.toLowerCase() === "x-boring-storage-scope");
2833
- if (storageScope && !hasScope) result["x-boring-storage-scope"] = storageScope;
2834
- return result;
2835
- }
2836
2842
 
2837
2843
  // src/front/hooks/useAttachmentNotice.ts
2838
2844
  import { useEffect as useEffect7, useState as useState9 } from "react";
@@ -3804,7 +3810,8 @@ function syncCursor(state, cursor) {
3804
3810
  return { ...state, lastSeq: cursor, hydrated: true, needsResync: void 0 };
3805
3811
  }
3806
3812
  function hydrateFromSnapshot(state, snapshot, options = {}) {
3807
- if (snapshot.seq < state.lastSeq && !options.allowSeqRewind) return state;
3813
+ const hasUnhydratedLiveEvents = state.turnId !== void 0 || state.streamingMessage !== void 0 || state.committedMessages.length > 0 || state.status === "streaming" || state.status === "aborting";
3814
+ if (snapshot.seq < state.lastSeq && !options.allowSeqRewind && (state.hydrated || hasUnhydratedLiveEvents)) return state;
3808
3815
  const queue = { followUps: enrichQueueWithKnownMetadata(snapshot.queue.followUps, state.optimisticOutbox, state.queue.followUps) };
3809
3816
  const snapshotMessages = normalizeSnapshotMessages(snapshot.messages);
3810
3817
  const preserveLocalHistory = shouldPreserveLocalCommittedHistory(state, snapshot, snapshotMessages, options);
@@ -4467,11 +4474,12 @@ function parsePiChatReplayRangeError(status, body) {
4467
4474
  }
4468
4475
  function buildPiChatEventsUrl({
4469
4476
  apiBaseUrl = "",
4477
+ agentTypeId,
4470
4478
  sessionId,
4471
4479
  cursor
4472
4480
  }) {
4473
4481
  const base = apiBaseUrl.replace(/\/$/, "");
4474
- return `${base}/api/v1/agent/pi-chat/${encodeURIComponent(sessionId)}/events?cursor=${encodeURIComponent(String(cursor))}`;
4482
+ return `${base}/api/v1/agents/${encodeURIComponent(agentTypeId)}/sessions/${encodeURIComponent(sessionId)}/events?cursor=${encodeURIComponent(String(cursor))}`;
4475
4483
  }
4476
4484
  function calculateJitteredBackoffDelayMs({
4477
4485
  attempt,
@@ -4512,6 +4520,7 @@ var SUPPORTED_PROTOCOL_VERSION = 1;
4512
4520
  var DEFAULT_RECONNECT_BASE_MS = 1e3;
4513
4521
  var DEFAULT_RECONNECT_MAX_MS = 3e4;
4514
4522
  var DEFAULT_REQUEST_TIMEOUT_MS = 15e3;
4523
+ var DEFAULT_COMMAND_TIMEOUT_MS = 6e4;
4515
4524
  var DEFAULT_LARGE_STATE_WARNING_BYTES = 5 * 1024 * 1024;
4516
4525
  var DEFAULT_LARGE_STATE_WARNING_MESSAGES = 300;
4517
4526
  var EVENT_TYPE_RING_LIMIT = 20;
@@ -4528,6 +4537,7 @@ var RemotePiSession = class {
4528
4537
  this.setTimeoutFn = options.setTimeoutFn ?? globalThis.setTimeout;
4529
4538
  this.clearTimeoutFn = options.clearTimeoutFn ?? globalThis.clearTimeout;
4530
4539
  this.requestTimeoutMs = options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
4540
+ this.commandTimeoutMs = options.commandTimeoutMs ?? DEFAULT_COMMAND_TIMEOUT_MS;
4531
4541
  this.store = createPiChatStore(createInitialPiChatState({
4532
4542
  sessionId: options.sessionId,
4533
4543
  workspaceId: options.workspaceId,
@@ -4544,10 +4554,12 @@ var RemotePiSession = class {
4544
4554
  setTimeoutFn;
4545
4555
  clearTimeoutFn;
4546
4556
  requestTimeoutMs;
4557
+ commandTimeoutMs;
4547
4558
  generation = 0;
4548
4559
  streamRunId = 0;
4549
4560
  reconnectAttempt = 0;
4550
4561
  started = false;
4562
+ suspended = false;
4551
4563
  disposed = false;
4552
4564
  streamAbortController;
4553
4565
  reconnectTimer;
@@ -4581,6 +4593,7 @@ var RemotePiSession = class {
4581
4593
  streamingMessageCount: state.streamingMessage ? 1 : 0
4582
4594
  },
4583
4595
  disposed: this.disposed,
4596
+ suspended: this.suspended,
4584
4597
  generation: this.generation,
4585
4598
  streamRunId: this.streamRunId,
4586
4599
  reconnectAttempt: this.reconnectAttempt,
@@ -4592,7 +4605,7 @@ var RemotePiSession = class {
4592
4605
  return this.store.subscribe(listener);
4593
4606
  }
4594
4607
  start(cursor) {
4595
- if (this.disposed || this.started) return Promise.resolve();
4608
+ if (this.disposed || this.suspended || this.started) return Promise.resolve();
4596
4609
  this.started = true;
4597
4610
  const generation = this.generation;
4598
4611
  if (cursor === void 0) {
@@ -4607,8 +4620,8 @@ var RemotePiSession = class {
4607
4620
  if (!this.disposed) {
4608
4621
  this.store.dispatch({ type: "optimistic-user-message", message: toOptimisticUserMessage(payload) }, { flush: true });
4609
4622
  }
4610
- if (!this.started) await this.start(this.store.getState().lastSeq);
4611
- else this.ensureReconnectScheduled();
4623
+ if (!this.started && !this.suspended) await this.start(this.store.getState().lastSeq);
4624
+ else if (!this.suspended) this.ensureReconnectScheduled();
4612
4625
  try {
4613
4626
  const receipt = await this.postCommand("/prompt", payload, PromptReceiptSchema);
4614
4627
  return receipt;
@@ -4621,8 +4634,8 @@ var RemotePiSession = class {
4621
4634
  if (!this.disposed) {
4622
4635
  this.store.dispatch({ type: "optimistic-user-message", message: toOptimisticUserMessage(payload) }, { flush: true });
4623
4636
  }
4624
- if (!this.started) await this.start(this.store.getState().lastSeq);
4625
- else this.ensureReconnectScheduled();
4637
+ if (!this.started && !this.suspended) await this.start(this.store.getState().lastSeq);
4638
+ else if (!this.suspended) this.ensureReconnectScheduled();
4626
4639
  try {
4627
4640
  const receipt = await this.postCommand("/followup", payload, FollowUpReceiptSchema);
4628
4641
  return receipt;
@@ -4648,6 +4661,28 @@ var RemotePiSession = class {
4648
4661
  }
4649
4662
  return receipt;
4650
4663
  }
4664
+ suspendStream() {
4665
+ if (this.disposed || this.suspended) return;
4666
+ this.suspended = true;
4667
+ this.started = false;
4668
+ this.streamRunId += 1;
4669
+ this.clearReconnectTimer();
4670
+ this.abortEventStream();
4671
+ this.store.dispatch({ type: "connection-state", state: "suspended" }, { flush: true });
4672
+ }
4673
+ resumeStream() {
4674
+ if (this.disposed || !this.suspended && this.started) return;
4675
+ this.suspended = false;
4676
+ this.started = true;
4677
+ const generation = this.generation;
4678
+ const state = this.store.getState();
4679
+ if (!state.hydrated) {
4680
+ void this.hydrateAndConnect(generation);
4681
+ return;
4682
+ }
4683
+ this.store.dispatch({ type: "connection-state", state: "connecting" }, { flush: true });
4684
+ void this.connectEvents(state.lastSeq, generation);
4685
+ }
4651
4686
  dispose() {
4652
4687
  if (this.disposed) return;
4653
4688
  this.disposed = true;
@@ -4661,33 +4696,34 @@ var RemotePiSession = class {
4661
4696
  this.store.dispose();
4662
4697
  }
4663
4698
  async hydrateAndConnect(generation, options = {}) {
4664
- if (!this.isGenerationActive(generation)) return;
4699
+ const hydrationRunId = this.streamRunId;
4700
+ if (!this.isHydrationActive(generation, hydrationRunId)) return;
4665
4701
  this.clearReconnectTimer();
4666
4702
  this.abortEventStream();
4667
4703
  this.store.dispatch({ type: "connection-state", state: this.store.getState().hydrated ? "reconnecting" : "connecting" }, { flush: true });
4668
4704
  try {
4669
4705
  const headers = await this.requestHeaders();
4670
- if (!this.isGenerationActive(generation)) return;
4706
+ if (!this.isHydrationActive(generation, hydrationRunId)) return;
4671
4707
  const responseBody = await this.fetchJson(this.stateUrl(), { method: "GET", headers });
4672
- if (!this.isGenerationActive(generation)) return;
4673
- const raw = this.options.agentTypeId ? addressedSnapshot(responseBody) : responseBody;
4708
+ if (!this.isHydrationActive(generation, hydrationRunId)) return;
4709
+ const raw = addressedSnapshot(responseBody);
4674
4710
  this.recordLargeStateWarning(raw);
4675
4711
  if (readProtocolVersion(raw) !== SUPPORTED_PROTOCOL_VERSION) {
4676
4712
  this.dispatchProtocolError(`Unsupported Pi chat protocol version: ${String(readProtocolVersion(raw) ?? "missing")}`);
4677
4713
  return;
4678
4714
  }
4679
4715
  const snapshot = PiChatSnapshotSchema.parse(raw);
4680
- if (!this.isGenerationActive(generation)) return;
4716
+ if (!this.isHydrationActive(generation, hydrationRunId)) return;
4681
4717
  this.store.dispatch({ type: "hydrate", snapshot, allowSeqRewind: options?.allowSeqRewind }, { flush: true });
4682
4718
  this.connectEvents(snapshot.seq, generation);
4683
4719
  } catch (error) {
4684
- if (!this.isGenerationActive(generation) || isAbortError(error)) return;
4720
+ if (!this.isHydrationActive(generation, hydrationRunId) || isAbortError(error)) return;
4685
4721
  this.dispatchProtocolError(errorMessage(error, "Failed to hydrate Pi chat session state."));
4686
4722
  this.scheduleReconnect(generation);
4687
4723
  }
4688
4724
  }
4689
4725
  connectEvents(cursor, generation) {
4690
- if (!this.isGenerationActive(generation)) return Promise.resolve();
4726
+ if (!this.isStreamLifecycleActive(generation)) return Promise.resolve();
4691
4727
  this.clearReconnectTimer();
4692
4728
  this.abortEventStream();
4693
4729
  const runId = ++this.streamRunId;
@@ -4801,20 +4837,20 @@ var RemotePiSession = class {
4801
4837
  }
4802
4838
  }
4803
4839
  rehydrateAfterStreamReset(generation, options = {}) {
4804
- if (!this.isGenerationActive(generation)) return;
4840
+ if (!this.isStreamLifecycleActive(generation)) return;
4805
4841
  this.streamRunId += 1;
4806
4842
  this.abortEventStream();
4807
4843
  void this.hydrateAndConnect(generation, options);
4808
4844
  }
4809
4845
  ensureReconnectScheduled() {
4810
- if (!this.isGenerationActive(this.generation)) return;
4846
+ if (!this.isStreamLifecycleActive(this.generation)) return;
4811
4847
  const state = this.store.getState();
4812
4848
  if (state.connection.state === "connected" || state.connection.state === "connecting") return;
4813
4849
  if (this.reconnectTimer !== void 0) return;
4814
4850
  void this.hydrateAndConnect(this.generation);
4815
4851
  }
4816
4852
  scheduleReconnect(generation) {
4817
- if (!this.isGenerationActive(generation)) return;
4853
+ if (!this.isStreamLifecycleActive(generation)) return;
4818
4854
  this.clearReconnectTimer();
4819
4855
  this.store.dispatch({ type: "connection-state", state: "reconnecting" }, { flush: true });
4820
4856
  const attempt = this.reconnectAttempt++;
@@ -4828,7 +4864,7 @@ var RemotePiSession = class {
4828
4864
  clearTimeoutFn: this.clearTimeoutFn,
4829
4865
  reconnect: () => {
4830
4866
  this.reconnectTimer = void 0;
4831
- if (!this.isGenerationActive(generation)) return;
4867
+ if (!this.isStreamLifecycleActive(generation)) return;
4832
4868
  const state = this.store.getState();
4833
4869
  if (state.hydrated) this.connectEvents(state.lastSeq, generation);
4834
4870
  else void this.hydrateAndConnect(generation);
@@ -4844,14 +4880,14 @@ var RemotePiSession = class {
4844
4880
  method: "POST",
4845
4881
  headers: { ...headers, "Content-Type": "application/json" },
4846
4882
  body: JSON.stringify(this.addressedCommandPayload(path, payload))
4847
- });
4883
+ }, this.commandTimeoutMs);
4848
4884
  if (!this.isGenerationActive(generation)) {
4849
4885
  throw abortError("Remote Pi session disposed before command receipt.");
4850
4886
  }
4851
4887
  return schema.parse(this.addressedCommandReceipt(path, raw));
4852
4888
  }
4853
4889
  addressedCommandReceipt(path, receipt) {
4854
- if (!this.options.agentTypeId || path !== "/followup" || typeof receipt !== "object" || receipt === null) {
4890
+ if (path !== "/followup" || typeof receipt !== "object" || receipt === null) {
4855
4891
  return receipt;
4856
4892
  }
4857
4893
  const record = receipt;
@@ -4861,21 +4897,21 @@ var RemotePiSession = class {
4861
4897
  if (this.disposed) return;
4862
4898
  this.store.dispatch({ type: "remove-optimistic-user-message", clientNonce }, { flush: true });
4863
4899
  }
4864
- async fetchJson(url, init) {
4900
+ async fetchJson(url, init, timeoutMs = this.requestTimeoutMs) {
4865
4901
  const controller = new AbortController();
4866
4902
  this.fetchControllers.add(controller);
4867
4903
  let timedOut = false;
4868
4904
  const timer = globalThis.setTimeout(() => {
4869
4905
  timedOut = true;
4870
4906
  controller.abort();
4871
- }, this.requestTimeoutMs);
4907
+ }, timeoutMs);
4872
4908
  try {
4873
4909
  const response = await this.fetchImpl(url, { ...init, signal: controller.signal });
4874
4910
  const body = await safeReadJson(response);
4875
4911
  if (!response.ok) throw new RemotePiSessionHttpError(response.status, routeErrorMessage(body, `HTTP ${response.status}`), body, routeErrorCode(body));
4876
4912
  return body;
4877
4913
  } catch (error) {
4878
- if (timedOut) throw new Error(`Request to ${url} timed out after ${this.requestTimeoutMs}ms.`);
4914
+ if (timedOut) throw new Error(`Request to ${url} timed out after ${timeoutMs}ms.`);
4879
4915
  throw error;
4880
4916
  } finally {
4881
4917
  globalThis.clearTimeout(timer);
@@ -4897,19 +4933,18 @@ var RemotePiSession = class {
4897
4933
  return this.sessionUrl("/state");
4898
4934
  }
4899
4935
  eventsUrl(cursor) {
4900
- if (!this.options.agentTypeId) {
4901
- return buildPiChatEventsUrl({ apiBaseUrl: this.apiBaseUrl, sessionId: this.options.sessionId, cursor });
4902
- }
4903
- return `${this.sessionUrl("/events")}?cursor=${encodeURIComponent(String(cursor))}`;
4936
+ return buildPiChatEventsUrl({
4937
+ apiBaseUrl: this.apiBaseUrl,
4938
+ agentTypeId: this.options.agentTypeId,
4939
+ sessionId: this.options.sessionId,
4940
+ cursor
4941
+ });
4904
4942
  }
4905
4943
  sessionUrl(path) {
4906
- if (this.options.agentTypeId) {
4907
- return `${this.apiBaseUrl}/api/v1/agents/${encodeURIComponent(this.options.agentTypeId)}/sessions/${encodeURIComponent(this.options.sessionId)}${path}`;
4908
- }
4909
- return `${this.apiBaseUrl}/api/v1/agent/pi-chat/${encodeURIComponent(this.options.sessionId)}${path}`;
4944
+ return `${this.apiBaseUrl}/api/v1/agents/${encodeURIComponent(this.options.agentTypeId)}/sessions/${encodeURIComponent(this.options.sessionId)}${path}`;
4910
4945
  }
4911
4946
  addressedCommandPayload(path, payload) {
4912
- if (!this.options.agentTypeId || typeof payload !== "object" || payload === null) return payload;
4947
+ if (typeof payload !== "object" || payload === null) return payload;
4913
4948
  const record = payload;
4914
4949
  if (typeof record.requestId === "string" && record.requestId.length > 0) return payload;
4915
4950
  if (path === "/prompt" && typeof record.clientNonce === "string" && typeof record.message === "string") {
@@ -4972,8 +5007,14 @@ var RemotePiSession = class {
4972
5007
  isGenerationActive(generation) {
4973
5008
  return !this.disposed && generation === this.generation;
4974
5009
  }
5010
+ isStreamLifecycleActive(generation) {
5011
+ return !this.suspended && this.isGenerationActive(generation);
5012
+ }
5013
+ isHydrationActive(generation, runId) {
5014
+ return this.isStreamLifecycleActive(generation) && runId === this.streamRunId;
5015
+ }
4975
5016
  isStreamActive(generation, runId) {
4976
- return this.isGenerationActive(generation) && runId === this.streamRunId;
5017
+ return this.isStreamLifecycleActive(generation) && runId === this.streamRunId;
4977
5018
  }
4978
5019
  };
4979
5020
  function addressedSnapshot(value) {
@@ -5100,13 +5141,23 @@ function clearPageUnloading() {
5100
5141
  pageUnloading = false;
5101
5142
  }
5102
5143
 
5103
- // src/front/chat/session/activeSessionStorage.ts
5144
+ // src/front/chat/session/sessionSelectionStorage.ts
5104
5145
  var ACTIVE_SESSION_KEY_PREFIX = "boring-agent:v2";
5105
5146
  var ACTIVE_SESSION_KEY_SUFFIX = "activeSessionId";
5147
+ var BOOT_RESUME_SESSION_KEY_SUFFIX = "bootResumeSessionId";
5106
5148
  var DEFAULT_STORAGE_SCOPE2 = "default";
5107
5149
  function activeSessionStorageKey(storageScope) {
5108
- const scope = storageScope && storageScope.length > 0 ? storageScope : DEFAULT_STORAGE_SCOPE2;
5109
- return `${ACTIVE_SESSION_KEY_PREFIX}:${scope}:${ACTIVE_SESSION_KEY_SUFFIX}`;
5150
+ return scopedSessionStorageKey(storageScope, ACTIVE_SESSION_KEY_SUFFIX);
5151
+ }
5152
+ function bootResumeSessionStorageKey(source) {
5153
+ const scope = typeof source === "object" && source !== null ? encodeURIComponent(JSON.stringify([
5154
+ source.apiBaseUrl?.replace(/\/$/, "") ?? "",
5155
+ source.sessionsApiPath,
5156
+ source.agentTypeId ?? "",
5157
+ source.workspaceId ?? "",
5158
+ source.storageScope ?? DEFAULT_STORAGE_SCOPE2
5159
+ ])) : source;
5160
+ return scopedSessionStorageKey(scope, BOOT_RESUME_SESSION_KEY_SUFFIX);
5110
5161
  }
5111
5162
  function readActiveSessionId(options = {}) {
5112
5163
  const storage = resolveStorage2(options.storage);
@@ -5127,8 +5178,28 @@ function writeActiveSessionId(sessionId, options = {}) {
5127
5178
  } catch {
5128
5179
  }
5129
5180
  }
5130
- function clearActiveSessionId(options = {}) {
5131
- writeActiveSessionId(void 0, options);
5181
+ function readBootResumeSessionId(options = {}) {
5182
+ const storage = resolveBootStorage(options.storage);
5183
+ if (!storage) return void 0;
5184
+ try {
5185
+ return storage.getItem(bootResumeSessionStorageKey(options.bootResumeSource ?? options.storageScope)) ?? void 0;
5186
+ } catch {
5187
+ return void 0;
5188
+ }
5189
+ }
5190
+ function writeBootResumeSessionId(sessionId, options = {}) {
5191
+ const storage = resolveBootStorage(options.storage);
5192
+ if (!storage) return;
5193
+ try {
5194
+ const key = bootResumeSessionStorageKey(options.bootResumeSource ?? options.storageScope);
5195
+ if (sessionId === void 0 || sessionId.length === 0) storage.removeItem(key);
5196
+ else storage.setItem(key, sessionId);
5197
+ } catch {
5198
+ }
5199
+ }
5200
+ function scopedSessionStorageKey(storageScope, suffix) {
5201
+ const scope = storageScope && storageScope.length > 0 ? storageScope : DEFAULT_STORAGE_SCOPE2;
5202
+ return `${ACTIVE_SESSION_KEY_PREFIX}:${scope}:${suffix}`;
5132
5203
  }
5133
5204
  function resolveStorage2(storage) {
5134
5205
  if (storage) return storage;
@@ -5138,29 +5209,43 @@ function resolveStorage2(storage) {
5138
5209
  return void 0;
5139
5210
  }
5140
5211
  }
5212
+ function resolveBootStorage(storage) {
5213
+ if (storage) return storage;
5214
+ try {
5215
+ return globalThis.sessionStorage;
5216
+ } catch {
5217
+ return void 0;
5218
+ }
5219
+ }
5141
5220
 
5142
5221
  // src/front/chat/session/usePiSessions.ts
5143
- import { useCallback as useCallback7, useEffect as useEffect8, useMemo as useMemo4, useRef as useRef8, useState as useState10 } from "react";
5144
- var DEFAULT_SESSIONS_API_PATH = "/api/v1/agent/pi-chat/sessions";
5222
+ import { useCallback as useCallback7, useEffect as useEffect8, useLayoutEffect, useMemo as useMemo4, useRef as useRef8, useState as useState10 } from "react";
5145
5223
  var SESSION_PAGE_SIZE = 50;
5146
5224
  var DEFAULT_MAX_RETRIES = 60;
5147
5225
  var DEFAULT_RETRY_BASE_MS = 250;
5148
5226
  var DEFAULT_RETRY_MAX_MS = 2e3;
5227
+ var useIsomorphicLayoutEffect = typeof window === "undefined" ? useEffect8 : useLayoutEffect;
5149
5228
  var SessionsPreparingError = class extends Error {
5150
5229
  constructor() {
5151
5230
  super("Agent runtime is still preparing");
5152
5231
  this.name = "SessionsPreparingError";
5153
5232
  }
5154
5233
  };
5234
+ var StaleSessionsSourceError = class extends Error {
5235
+ constructor() {
5236
+ super("Pi sessions source is stale");
5237
+ this.name = "StaleSessionsSourceError";
5238
+ }
5239
+ };
5155
5240
  function isNetworkFetchError(error) {
5156
5241
  return error instanceof TypeError;
5157
5242
  }
5158
- function usePiSessions(options = {}) {
5243
+ function usePiSessions(options) {
5159
5244
  const enabled = options.enabled ?? true;
5160
5245
  const apiBaseUrl = options.apiBaseUrl?.replace(/\/$/, "") ?? "";
5161
- const addressed = typeof options.agentTypeId === "string" && options.agentTypeId.length > 0;
5162
- const sessionsApiPath = options.sessionsApiPath ?? (addressed ? `/api/v1/agents/${encodeURIComponent(options.agentTypeId)}/sessions` : DEFAULT_SESSIONS_API_PATH);
5246
+ const sessionsApiPath = options.sessionsApiPath ?? `/api/v1/agents/${encodeURIComponent(options.agentTypeId)}/sessions`;
5163
5247
  const storageScope = options.storageScope ?? "default";
5248
+ const activeSessionStorageScope = options.activeSessionStorageScope ?? storageScope;
5164
5249
  const fetchImpl = useMemo4(() => options.fetch ?? globalThis.fetch.bind(globalThis), [options.fetch]);
5165
5250
  const createRemoteSession = options.createRemoteSession ?? createRemotePiSession;
5166
5251
  const connectActiveSession = options.connectActiveSession ?? true;
@@ -5168,12 +5253,30 @@ function usePiSessions(options = {}) {
5168
5253
  const retryBaseMs = options.retry?.baseMs ?? DEFAULT_RETRY_BASE_MS;
5169
5254
  const retryMaxMs = options.retry?.maxMs ?? DEFAULT_RETRY_MAX_MS;
5170
5255
  const headersKey = useMemo4(() => headersScopeKey(options.requestHeaders, storageScope), [options.requestHeaders, storageScope]);
5171
- const normalizedHeaders = useMemo4(() => buildRequestHeaders(options.requestHeaders, storageScope), [headersKey, storageScope]);
5172
- const requestScopeKey = useMemo4(() => requestScopeIdentity(apiBaseUrl, sessionsApiPath, storageScope, headersKey), [apiBaseUrl, headersKey, sessionsApiPath, storageScope]);
5173
- const dataSourceKey = useMemo4(() => dataSourceIdentity(apiBaseUrl, sessionsApiPath, storageScope), [apiBaseUrl, sessionsApiPath, storageScope]);
5256
+ const normalizedHeaders = useMemo4(() => withStorageScope(options.requestHeaders, storageScope) ?? {}, [headersKey, storageScope]);
5257
+ const requestScopeKey = useMemo4(
5258
+ () => requestScopeIdentity(apiBaseUrl, sessionsApiPath, options.agentTypeId, storageScope, headersKey, options.workspaceId, options.sourceIdentity),
5259
+ [apiBaseUrl, headersKey, options.agentTypeId, options.sourceIdentity, options.workspaceId, sessionsApiPath, storageScope]
5260
+ );
5261
+ const dataSourceKey = useMemo4(
5262
+ () => dataSourceIdentity(apiBaseUrl, sessionsApiPath, options.agentTypeId, storageScope, options.workspaceId, options.sourceIdentity),
5263
+ [apiBaseUrl, options.agentTypeId, options.sourceIdentity, options.workspaceId, sessionsApiPath, storageScope]
5264
+ );
5265
+ const bootResumeSource = useMemo4(() => ({
5266
+ apiBaseUrl,
5267
+ sessionsApiPath,
5268
+ agentTypeId: options.agentTypeId,
5269
+ workspaceId: options.workspaceId,
5270
+ storageScope
5271
+ }), [apiBaseUrl, options.agentTypeId, options.workspaceId, sessionsApiPath, storageScope]);
5174
5272
  const [sessions, setSessions] = useState10([]);
5175
5273
  const [dataStorageScope, setDataStorageScope] = useState10(storageScope);
5176
- const [activeSessionId, setActiveSessionId] = useState10(() => options.initialActiveSessionId ?? readActiveSessionId({ storageScope, storage: options.storage }));
5274
+ const [activeSessionId, setActiveSessionId] = useState10(void 0);
5275
+ const [resumeSessionState, setResumeSessionState] = useState10(() => ({
5276
+ dataSourceKey,
5277
+ id: readBootResumeSessionId({ bootResumeSource, storage: options.bootResumeStorage })
5278
+ }));
5279
+ const resumeSessionId = resumeSessionState.dataSourceKey === dataSourceKey ? resumeSessionState.id : void 0;
5177
5280
  const [activePiSession, setActivePiSession] = useState10(void 0);
5178
5281
  const [loading, setLoading] = useState10(enabled);
5179
5282
  const [loadingMore, setLoadingMore] = useState10(false);
@@ -5190,17 +5293,30 @@ function usePiSessions(options = {}) {
5190
5293
  const loadMoreRequestSeqRef = useRef8(0);
5191
5294
  const loadMoreInFlightRef = useRef8(false);
5192
5295
  const pendingCreatedRef = useRef8(/* @__PURE__ */ new Map());
5296
+ const pendingDeletedRef = useRef8(/* @__PURE__ */ new Set());
5193
5297
  const pendingCreatedScopeRef = useRef8(requestScopeKey);
5194
5298
  const dataStorageScopeRef = useRef8(storageScope);
5195
5299
  const loadedDataSourceRef = useRef8(dataSourceKey);
5196
- const requestScopeRef = useRef8(requestScopeKey);
5197
- requestScopeRef.current = requestScopeKey;
5300
+ const committedRequestScopeRef = useRef8(requestScopeKey);
5301
+ useIsomorphicLayoutEffect(() => {
5302
+ committedRequestScopeRef.current = requestScopeKey;
5303
+ }, [requestScopeKey]);
5304
+ const sourceIsCurrent = useCallback7(
5305
+ (scope) => mountedRef.current && committedRequestScopeRef.current === scope,
5306
+ []
5307
+ );
5198
5308
  const remoteSessionOptionsRef = useRef8(options.remoteSessionOptions);
5199
5309
  remoteSessionOptionsRef.current = options.remoteSessionOptions;
5200
5310
  const remoteSessionOptionsKey = useMemo4(
5201
5311
  () => remoteSessionOptionsIdentity(options.remoteSessionOptions),
5202
5312
  [options.remoteSessionOptions]
5203
5313
  );
5314
+ useIsomorphicLayoutEffect(() => {
5315
+ setResumeSessionState((current) => current.dataSourceKey === dataSourceKey ? current : {
5316
+ dataSourceKey,
5317
+ id: readBootResumeSessionId({ bootResumeSource, storage: options.bootResumeStorage })
5318
+ });
5319
+ }, [bootResumeSource, dataSourceKey, options.bootResumeStorage]);
5204
5320
  useEffect8(() => {
5205
5321
  sessionsRef.current = sessions;
5206
5322
  }, [sessions]);
@@ -5213,48 +5329,66 @@ function usePiSessions(options = {}) {
5213
5329
  const activeSessionKnown = Boolean(activeSessionId && sessions.some((session) => session.id === activeSessionId));
5214
5330
  const requestHeaders = useCallback7(() => normalizedHeaders, [normalizedHeaders]);
5215
5331
  const sessionsUrl = useCallback7((suffix = "") => `${apiBaseUrl}${sessionsApiPath}${suffix}`, [apiBaseUrl, sessionsApiPath]);
5216
- const sessionsListUrl = useCallback7((offset = 0, includeId, cursor) => {
5217
- const query = new URLSearchParams();
5218
- if (addressed) {
5219
- query.set("limit", String(SESSION_PAGE_SIZE));
5220
- if (cursor) query.set("cursor", cursor);
5221
- } else {
5222
- if (offset > 0) {
5223
- query.set("limit", String(SESSION_PAGE_SIZE));
5224
- query.set("offset", String(offset));
5225
- }
5226
- if (offset <= 0 && includeId) query.set("activeSessionId", includeId);
5227
- }
5228
- if (query.size === 0) return sessionsUrl();
5332
+ const sessionsListUrl = useCallback7((cursor) => {
5333
+ const query = new URLSearchParams({ limit: String(SESSION_PAGE_SIZE) });
5334
+ if (cursor) query.set("cursor", cursor);
5229
5335
  return sessionsUrl(`?${query.toString()}`);
5230
- }, [addressed, sessionsUrl]);
5336
+ }, [sessionsUrl]);
5231
5337
  const persistActive = useCallback7((id) => {
5232
- writeActiveSessionId(id, { storageScope, storage: options.storage });
5233
- }, [options.storage, storageScope]);
5338
+ writeActiveSessionId(id, { storageScope: activeSessionStorageScope, storage: options.storage });
5339
+ }, [activeSessionStorageScope, options.storage]);
5340
+ const persistBootResume = useCallback7((id) => {
5341
+ writeBootResumeSessionId(id, { bootResumeSource, storage: options.bootResumeStorage });
5342
+ setResumeSessionState({ dataSourceKey, id });
5343
+ }, [bootResumeSource, dataSourceKey, options.bootResumeStorage]);
5344
+ const rememberConfirmedEmptySession = useCallback7((id) => {
5345
+ writeBootResumeSessionId(id, { bootResumeSource, storage: options.bootResumeStorage });
5346
+ setResumeSessionState({ dataSourceKey, id: void 0 });
5347
+ }, [bootResumeSource, dataSourceKey, options.bootResumeStorage]);
5234
5348
  const ensurePendingScope = useCallback7(() => {
5235
5349
  if (pendingCreatedScopeRef.current === requestScopeKey) return;
5236
5350
  pendingCreatedScopeRef.current = requestScopeKey;
5237
5351
  pendingCreatedRef.current.clear();
5352
+ pendingDeletedRef.current.clear();
5238
5353
  }, [requestScopeKey]);
5239
5354
  const preferredSessionId = useCallback7(() => {
5240
- const persisted = options.initialActiveSessionId ?? readActiveSessionId({ storageScope, storage: options.storage });
5355
+ const persisted = options.initialActiveSessionId ?? readActiveSessionId({
5356
+ storageScope: activeSessionStorageScope,
5357
+ storage: options.storage
5358
+ });
5241
5359
  if (loadedDataSourceRef.current === dataSourceKey) return activeSessionIdRef.current ?? persisted;
5242
5360
  if (dataStorageScopeRef.current !== storageScope) return persisted;
5243
5361
  return void 0;
5244
- }, [dataSourceKey, options.initialActiveSessionId, options.storage, storageScope]);
5362
+ }, [activeSessionStorageScope, dataSourceKey, options.initialActiveSessionId, options.storage, storageScope]);
5245
5363
  const applySessions = useCallback7((data, applyOptions = {}) => {
5246
5364
  ensurePendingScope();
5247
5365
  const replacingScope = loadedDataSourceRef.current !== dataSourceKey;
5248
5366
  const requestedActiveId = preferredSessionId();
5249
5367
  const replacingScopePreferred = replacingScope ? requestedActiveId : void 0;
5250
5368
  const pendingCreated = pendingCreatedRef.current;
5369
+ const pendingDeleted = pendingDeletedRef.current;
5370
+ const deletedIds = new Set(pendingDeleted);
5251
5371
  for (const session of data) pendingCreated.delete(session.id);
5252
- const canonicalCount = canonicalPageCount(data);
5253
- const pageMayHaveMore = addressed ? applyOptions.nextCursor !== void 0 : data.length >= SESSION_PAGE_SIZE;
5372
+ const filteredData = data.filter((session) => !deletedIds.has(session.id));
5373
+ if (applyOptions.nextCursor === void 0) {
5374
+ const returnedIds = new Set(data.map((session) => session.id));
5375
+ for (const deletedId of pendingDeleted) {
5376
+ if (!returnedIds.has(deletedId)) pendingDeleted.delete(deletedId);
5377
+ }
5378
+ }
5379
+ const canonicalCount = canonicalPageCount(filteredData);
5380
+ const pageMayHaveMore = applyOptions.nextCursor !== void 0;
5254
5381
  const wasExhaustedBeyondFirstPage = applyOptions.background && !hasMoreRef.current && canonicalLoadedCountRef.current >= canonicalCount;
5255
- const requestedActiveReturned = Boolean(requestedActiveId && data.some((session) => session.id === requestedActiveId));
5382
+ const requestedActiveReturned = Boolean(requestedActiveId && filteredData.some((session) => session.id === requestedActiveId));
5256
5383
  const current = applyOptions.background && pageMayHaveMore ? sessionsRef.current.filter((session) => !requestedActiveId || requestedActiveReturned || session.id !== requestedActiveId) : [];
5257
- const merged = mergeSessions(Array.from(pendingCreated.values()), data, current);
5384
+ const merged = mergeSessions(Array.from(pendingCreated.values()), filteredData, current.filter((session) => !deletedIds.has(session.id)));
5385
+ const rememberedEmptyId = readBootResumeSessionId({
5386
+ bootResumeSource,
5387
+ storage: options.bootResumeStorage
5388
+ });
5389
+ if (rememberedEmptyId && filteredData.some((session) => session.id === rememberedEmptyId)) {
5390
+ persistBootResume(void 0);
5391
+ }
5258
5392
  const nextHasMore = pageMayHaveMore && !wasExhaustedBeyondFirstPage;
5259
5393
  canonicalLoadedCountRef.current = applyOptions.background ? Math.max(canonicalLoadedCountRef.current, canonicalCount) : canonicalCount;
5260
5394
  loadedDataSourceRef.current = dataSourceKey;
@@ -5269,10 +5403,12 @@ function usePiSessions(options = {}) {
5269
5403
  persistActive(next);
5270
5404
  return next;
5271
5405
  });
5272
- }, [addressed, dataSourceKey, ensurePendingScope, persistActive, preferredSessionId, storageScope]);
5406
+ }, [bootResumeSource, dataSourceKey, ensurePendingScope, options.bootResumeStorage, persistActive, persistBootResume, preferredSessionId, storageScope]);
5273
5407
  const refresh = useCallback7(async (refreshOptions = {}) => {
5408
+ const scope = requestScopeKey;
5409
+ if (mountedRef.current && !sourceIsCurrent(scope)) throw new StaleSessionsSourceError();
5274
5410
  const version = ++refreshVersionRef.current;
5275
- const isCurrent = () => mountedRef.current && version === refreshVersionRef.current;
5411
+ const isCurrent = () => sourceIsCurrent(scope) && version === refreshVersionRef.current;
5276
5412
  clearRetryTimer(retryTimerRef);
5277
5413
  const background = refreshOptions.background === true;
5278
5414
  if (!enabled) {
@@ -5299,7 +5435,7 @@ function usePiSessions(options = {}) {
5299
5435
  let page;
5300
5436
  for (let attempt = 0; ; attempt += 1) {
5301
5437
  try {
5302
- page = await fetchSessionList(fetchImpl, sessionsListUrl(0, preferredSessionId()), requestHeaders(), addressed);
5438
+ page = await fetchSessionList(fetchImpl, sessionsListUrl(), requestHeaders());
5303
5439
  break;
5304
5440
  } catch (err) {
5305
5441
  const transient = err instanceof SessionsPreparingError || isNetworkFetchError(err);
@@ -5321,7 +5457,7 @@ function usePiSessions(options = {}) {
5321
5457
  setLoading(false);
5322
5458
  if (refreshOptions.throwOnError) throw error2;
5323
5459
  }
5324
- }, [addressed, applySessions, enabled, fetchImpl, persistActive, preferredSessionId, requestHeaders, retryBaseMs, retryMaxMs, retryMaxRetries, sessionsListUrl]);
5460
+ }, [applySessions, enabled, fetchImpl, persistActive, preferredSessionId, requestHeaders, requestScopeKey, retryBaseMs, retryMaxMs, retryMaxRetries, sessionsListUrl, sourceIsCurrent]);
5325
5461
  useEffect8(() => {
5326
5462
  mountedRef.current = true;
5327
5463
  void refresh();
@@ -5337,14 +5473,13 @@ function usePiSessions(options = {}) {
5337
5473
  loadMoreInFlightRef.current = true;
5338
5474
  const version = refreshVersionRef.current;
5339
5475
  const scope = requestScopeKey;
5340
- const offset = canonicalLoadedCountRef.current;
5341
5476
  setLoadingMore(true);
5342
5477
  try {
5343
- const page = await fetchSessionList(fetchImpl, sessionsListUrl(offset, void 0, nextCursorRef.current), requestHeaders(), addressed);
5344
- const data = page.sessions;
5345
- if (!mountedRef.current || requestSeq !== loadMoreRequestSeqRef.current || version !== refreshVersionRef.current || scope !== requestScopeRef.current) return;
5346
- const merged = mergeSessions(sessionsRef.current, data);
5347
- const nextHasMore = addressed ? page.nextCursor !== void 0 : data.length >= SESSION_PAGE_SIZE;
5478
+ const page = await fetchSessionList(fetchImpl, sessionsListUrl(nextCursorRef.current), requestHeaders());
5479
+ const data = page.sessions.filter((session) => !pendingDeletedRef.current.has(session.id));
5480
+ if (requestSeq !== loadMoreRequestSeqRef.current || version !== refreshVersionRef.current || !sourceIsCurrent(scope)) return;
5481
+ const merged = mergeSessions(sessionsRef.current.filter((session) => !pendingDeletedRef.current.has(session.id)), data);
5482
+ const nextHasMore = page.nextCursor !== void 0;
5348
5483
  canonicalLoadedCountRef.current += data.length;
5349
5484
  nextCursorRef.current = page.nextCursor;
5350
5485
  setSessions(merged);
@@ -5357,16 +5492,16 @@ function usePiSessions(options = {}) {
5357
5492
  return next;
5358
5493
  });
5359
5494
  } catch (err) {
5360
- if (mountedRef.current && requestSeq === loadMoreRequestSeqRef.current && version === refreshVersionRef.current && scope === requestScopeRef.current) {
5495
+ if (requestSeq === loadMoreRequestSeqRef.current && version === refreshVersionRef.current && sourceIsCurrent(scope)) {
5361
5496
  setError(err instanceof Error ? err : new Error(String(err)));
5362
5497
  }
5363
5498
  } finally {
5364
5499
  if (requestSeq === loadMoreRequestSeqRef.current) loadMoreInFlightRef.current = false;
5365
- if (mountedRef.current && requestSeq === loadMoreRequestSeqRef.current && version === refreshVersionRef.current && scope === requestScopeRef.current) {
5500
+ if (requestSeq === loadMoreRequestSeqRef.current && version === refreshVersionRef.current && sourceIsCurrent(scope)) {
5366
5501
  setLoadingMore(false);
5367
5502
  }
5368
5503
  }
5369
- }, [addressed, enabled, fetchImpl, hasMore, loading, loadingMore, persistActive, requestHeaders, requestScopeKey, sessionsListUrl]);
5504
+ }, [enabled, fetchImpl, hasMore, loading, loadingMore, persistActive, requestHeaders, requestScopeKey, sessionsListUrl, sourceIsCurrent]);
5370
5505
  useEffect8(() => {
5371
5506
  if (!enabled || !connectActiveSession || !activeSessionId || !activeSessionKnown) {
5372
5507
  setActivePiSession(void 0);
@@ -5388,6 +5523,8 @@ function usePiSessions(options = {}) {
5388
5523
  };
5389
5524
  }, [activeSessionId, activeSessionKnown, apiBaseUrl, connectActiveSession, createRemoteSession, enabled, fetchImpl, remoteSessionOptionsKey, options.agentTypeId, options.workspaceId, requestHeaders, storageScope]);
5390
5525
  const create = useCallback7(async (init) => {
5526
+ const scope = requestScopeKey;
5527
+ if (!sourceIsCurrent(scope)) throw new StaleSessionsSourceError();
5391
5528
  if (!enabled) throw new Error("Pi sessions are disabled");
5392
5529
  const response = await fetchImpl(sessionsUrl(), {
5393
5530
  method: "POST",
@@ -5400,8 +5537,10 @@ function usePiSessions(options = {}) {
5400
5537
  throw err;
5401
5538
  }
5402
5539
  const body = await response.json();
5403
- const session = addressed ? addressedCreatedSession(body, init?.title) : toSessionSummary(body);
5540
+ const session = addressedCreatedSession(body, options.agentTypeId, init?.title);
5541
+ if (!sourceIsCurrent(scope)) return session;
5404
5542
  ensurePendingScope();
5543
+ rememberConfirmedEmptySession(session.id);
5405
5544
  pendingCreatedRef.current.set(session.id, session);
5406
5545
  setDataStorageScope(storageScope);
5407
5546
  setSessions((previous) => mergeSessions([session], previous));
@@ -5409,17 +5548,43 @@ function usePiSessions(options = {}) {
5409
5548
  persistActive(session.id);
5410
5549
  void refresh();
5411
5550
  return session;
5412
- }, [addressed, enabled, ensurePendingScope, fetchImpl, persistActive, refresh, requestHeaders, sessionsUrl, storageScope]);
5551
+ }, [enabled, ensurePendingScope, fetchImpl, persistActive, refresh, rememberConfirmedEmptySession, requestHeaders, requestScopeKey, sessionsUrl, sourceIsCurrent, storageScope]);
5413
5552
  const switchSession = useCallback7((id) => {
5553
+ if (!sourceIsCurrent(requestScopeKey)) return;
5414
5554
  const known = sessionsRef.current.some((session) => session.id === id);
5415
5555
  const next = known ? id : sessionsRef.current[0]?.id;
5416
5556
  setActiveSessionId(next);
5417
5557
  persistActive(next);
5418
- }, [persistActive]);
5558
+ }, [persistActive, requestScopeKey, sourceIsCurrent]);
5559
+ const rename = useCallback7(async (id, title) => {
5560
+ const scope = requestScopeKey;
5561
+ if (!sourceIsCurrent(scope)) throw new StaleSessionsSourceError();
5562
+ if (!enabled) throw new Error("Pi sessions are disabled");
5563
+ const response = await fetchImpl(sessionsUrl(`/${encodeURIComponent(id)}/rename`), {
5564
+ method: "POST",
5565
+ headers: { ...requestHeaders(), "Content-Type": "application/json" },
5566
+ body: JSON.stringify({ requestId: createRequestId("rename"), title })
5567
+ });
5568
+ if (!response.ok) throw new Error(`Failed to rename session: ${response.status}`);
5569
+ const renamed = toAddressedSessionSummary(await response.json());
5570
+ if (!sourceIsCurrent(scope)) throw new StaleSessionsSourceError();
5571
+ ensurePendingScope();
5572
+ if (pendingCreatedRef.current.has(id)) pendingCreatedRef.current.set(id, renamed);
5573
+ setSessions((current) => current.map((session) => session.id === id ? renamed : session));
5574
+ return renamed;
5575
+ }, [enabled, ensurePendingScope, fetchImpl, requestHeaders, requestScopeKey, sessionsUrl, sourceIsCurrent]);
5419
5576
  const deleteSession = useCallback7(async (id) => {
5577
+ const scope = requestScopeKey;
5578
+ if (!sourceIsCurrent(scope)) throw new StaleSessionsSourceError();
5420
5579
  if (!enabled) throw new Error("Pi sessions are disabled");
5421
5580
  ensurePendingScope();
5581
+ if (readBootResumeSessionId({ bootResumeSource, storage: options.bootResumeStorage }) === id) {
5582
+ persistBootResume(void 0);
5583
+ }
5584
+ const deletedSession = sessionsRef.current.find((session) => session.id === id);
5585
+ const deletedSessionWasActive = activeSessionIdRef.current === id;
5422
5586
  pendingCreatedRef.current.delete(id);
5587
+ pendingDeletedRef.current.add(id);
5423
5588
  setDataStorageScope(storageScope);
5424
5589
  setSessions((previous) => previous.filter((session) => session.id !== id));
5425
5590
  setActiveSessionId((previous) => {
@@ -5436,18 +5601,29 @@ function usePiSessions(options = {}) {
5436
5601
  if (!response.ok && response.status !== 404) throw new Error(`Failed to delete session: ${response.status}`);
5437
5602
  } catch (err) {
5438
5603
  const error2 = err instanceof Error ? err : new Error(String(err));
5439
- setError(error2);
5440
- void refresh();
5604
+ if (sourceIsCurrent(scope)) {
5605
+ pendingDeletedRef.current.delete(id);
5606
+ if (deletedSession) {
5607
+ setSessions((previous) => mergeSessions([deletedSession], previous));
5608
+ if (deletedSessionWasActive) {
5609
+ setActiveSessionId(id);
5610
+ persistActive(id);
5611
+ }
5612
+ }
5613
+ setError(error2);
5614
+ void refresh();
5615
+ }
5441
5616
  throw error2;
5442
5617
  }
5443
- void refresh();
5444
- }, [enabled, ensurePendingScope, fetchImpl, persistActive, refresh, requestHeaders, sessionsUrl, storageScope]);
5618
+ if (sourceIsCurrent(scope)) void refresh();
5619
+ }, [bootResumeSource, enabled, ensurePendingScope, fetchImpl, options.bootResumeStorage, persistActive, persistBootResume, refresh, requestHeaders, requestScopeKey, sessionsUrl, sourceIsCurrent, storageScope]);
5445
5620
  const reset = useCallback7(() => {
5621
+ if (!sourceIsCurrent(requestScopeKey)) return;
5446
5622
  pendingCreatedRef.current.clear();
5623
+ pendingDeletedRef.current.clear();
5447
5624
  loadMoreRequestSeqRef.current += 1;
5448
5625
  loadMoreInFlightRef.current = false;
5449
5626
  canonicalLoadedCountRef.current = canonicalPageCount(sessionsRef.current);
5450
- nextCursorRef.current = void 0;
5451
5627
  loadedDataSourceRef.current = dataSourceKey;
5452
5628
  dataStorageScopeRef.current = storageScope;
5453
5629
  setDataStorageScope(storageScope);
@@ -5455,36 +5631,35 @@ function usePiSessions(options = {}) {
5455
5631
  setActivePiSession(void 0);
5456
5632
  setLoadingMore(false);
5457
5633
  persistActive(void 0);
5458
- }, [dataSourceKey, persistActive, storageScope]);
5634
+ }, [dataSourceKey, persistActive, requestScopeKey, sourceIsCurrent, storageScope]);
5459
5635
  const visibleActiveSessionId = enabled ? activeSessionId : void 0;
5460
5636
  const activeSession = enabled ? sessions.find((session) => session.id === visibleActiveSessionId) : void 0;
5461
5637
  return {
5462
5638
  sessions,
5463
5639
  activeSession,
5464
5640
  activeSessionId: visibleActiveSessionId,
5641
+ resumeSessionId,
5465
5642
  activePiSession: visibleActiveSessionId ? activePiSession : void 0,
5466
5643
  dataStorageScope,
5644
+ sourceIdentity: enabled && loadedDataSourceRef.current === dataSourceKey ? options.sourceIdentity : void 0,
5467
5645
  loading: enabled ? loading : false,
5468
5646
  loadingMore,
5469
5647
  hasMore: enabled ? hasMore : false,
5470
5648
  error,
5471
5649
  refresh,
5472
5650
  create,
5651
+ rename,
5473
5652
  switch: switchSession,
5474
5653
  delete: deleteSession,
5475
5654
  loadMore,
5476
5655
  reset
5477
5656
  };
5478
5657
  }
5479
- async function fetchSessionList(fetchImpl, url, headers, addressed) {
5658
+ async function fetchSessionList(fetchImpl, url, headers) {
5480
5659
  const response = await fetchImpl(url, Object.keys(headers).length > 0 ? { headers } : void 0);
5481
5660
  if (response.status === 503) throw new SessionsPreparingError();
5482
5661
  if (!response.ok) throw new Error(`Failed to load sessions: ${response.status}`);
5483
5662
  const body = await response.json();
5484
- if (!addressed) {
5485
- if (!Array.isArray(body)) throw new Error("Failed to load sessions: invalid response");
5486
- return { sessions: body.map(toSessionSummary) };
5487
- }
5488
5663
  if (typeof body !== "object" || body === null || !Array.isArray(body.sessions)) {
5489
5664
  throw new Error("Failed to load sessions: invalid response");
5490
5665
  }
@@ -5494,19 +5669,6 @@ async function fetchSessionList(fetchImpl, url, headers, addressed) {
5494
5669
  ...typeof page.nextCursor === "string" ? { nextCursor: page.nextCursor } : {}
5495
5670
  };
5496
5671
  }
5497
- function toSessionSummary(value) {
5498
- if (typeof value !== "object" || value === null) throw new Error("invalid session summary");
5499
- const record = value;
5500
- if (typeof record.id !== "string" || !record.id) throw new Error("invalid session id");
5501
- const now = (/* @__PURE__ */ new Date(0)).toISOString();
5502
- return {
5503
- id: record.id,
5504
- title: typeof record.title === "string" ? record.title : "Untitled",
5505
- createdAt: typeof record.createdAt === "string" ? record.createdAt : now,
5506
- updatedAt: typeof record.updatedAt === "string" ? record.updatedAt : now,
5507
- turnCount: typeof record.turnCount === "number" ? record.turnCount : 0
5508
- };
5509
- }
5510
5672
  function toAddressedSessionSummary(value) {
5511
5673
  if (typeof value !== "object" || value === null) throw new Error("invalid addressed session summary");
5512
5674
  const record = value;
@@ -5516,21 +5678,29 @@ function toAddressedSessionSummary(value) {
5516
5678
  }
5517
5679
  const createdAt = typeof record.createdAt === "number" ? new Date(record.createdAt).toISOString() : (/* @__PURE__ */ new Date(0)).toISOString();
5518
5680
  const updatedAt = typeof record.updatedAt === "number" ? new Date(record.updatedAt).toISOString() : createdAt;
5681
+ const agentTypeId = ref.agentTypeId;
5682
+ const status = record.status;
5683
+ const addressedStatus = status === "idle" || status === "running" || status === "aborting" || status === "error" ? status : void 0;
5519
5684
  return {
5520
5685
  id: ref.sessionId,
5521
5686
  title: typeof record.title === "string" ? record.title : "Untitled",
5522
5687
  createdAt,
5523
5688
  updatedAt,
5524
- turnCount: 0
5689
+ turnCount: typeof record.turnCount === "number" ? record.turnCount : 0,
5690
+ ...typeof agentTypeId === "string" ? { agentTypeId } : {},
5691
+ ...typeof record.nativeSessionId === "string" ? { nativeSessionId: record.nativeSessionId } : {},
5692
+ ...typeof record.hasAssistantReply === "boolean" ? { hasAssistantReply: record.hasAssistantReply } : {},
5693
+ ...addressedStatus ? { status: addressedStatus } : {}
5525
5694
  };
5526
5695
  }
5527
- function addressedCreatedSession(value, title) {
5528
- if (typeof value !== "object" || value === null || typeof value.sessionId !== "string") {
5696
+ function addressedCreatedSession(value, expectedAgentTypeId, title) {
5697
+ if (typeof value !== "object" || value === null || typeof value.sessionId !== "string" || value.agentTypeId !== expectedAgentTypeId) {
5529
5698
  throw new Error("invalid addressed session ref");
5530
5699
  }
5531
5700
  const now = (/* @__PURE__ */ new Date()).toISOString();
5532
5701
  return {
5533
5702
  id: value.sessionId,
5703
+ agentTypeId: expectedAgentTypeId,
5534
5704
  title: title ?? "Untitled",
5535
5705
  createdAt: now,
5536
5706
  updatedAt: now,
@@ -5586,31 +5756,25 @@ function mergeSessions(...lists) {
5586
5756
  }
5587
5757
  return merged;
5588
5758
  }
5589
- function buildRequestHeaders(headers, storageScope) {
5590
- const result = {};
5591
- for (const [key, value] of Object.entries(headers ?? {})) {
5592
- if (typeof value === "string") result[key] = value;
5593
- }
5594
- if (storageScope && !hasHeader2(result, "x-boring-storage-scope")) result["x-boring-storage-scope"] = storageScope;
5595
- return result;
5596
- }
5597
5759
  function headersScopeKey(headers, storageScope) {
5598
5760
  return JSON.stringify({ storageScope, headers: Object.entries(headers ?? {}).sort(([a], [b]) => a.localeCompare(b)) });
5599
5761
  }
5600
- function requestScopeIdentity(apiBaseUrl, sessionsApiPath, storageScope, headersKey) {
5762
+ function requestScopeIdentity(apiBaseUrl, sessionsApiPath, agentTypeId, storageScope, headersKey, workspaceId, sourceIdentity) {
5601
5763
  return `${apiBaseUrl}
5602
5764
  ${sessionsApiPath}
5765
+ ${agentTypeId}
5603
5766
  ${storageScope}
5604
- ${headersKey}`;
5767
+ ${headersKey}
5768
+ ${workspaceId ?? ""}
5769
+ ${sourceIdentity ?? ""}`;
5605
5770
  }
5606
- function dataSourceIdentity(apiBaseUrl, sessionsApiPath, storageScope) {
5771
+ function dataSourceIdentity(apiBaseUrl, sessionsApiPath, agentTypeId, storageScope, workspaceId, sourceIdentity) {
5607
5772
  return `${apiBaseUrl}
5608
5773
  ${sessionsApiPath}
5609
- ${storageScope}`;
5610
- }
5611
- function hasHeader2(headers, name) {
5612
- const lower = name.toLowerCase();
5613
- return Object.keys(headers).some((key) => key.toLowerCase() === lower);
5774
+ ${agentTypeId}
5775
+ ${storageScope}
5776
+ ${workspaceId ?? ""}
5777
+ ${sourceIdentity ?? ""}`;
5614
5778
  }
5615
5779
  function retryDelayMs(attempt, retry) {
5616
5780
  const base = retry.baseMs ?? DEFAULT_RETRY_BASE_MS;
@@ -5991,8 +6155,8 @@ function searchPiSessions(sessions, query, options = {}) {
5991
6155
  }
5992
6156
 
5993
6157
  // src/front/chat/components/PiConversationSurface.tsx
5994
- import { Loader2 as Loader22 } from "lucide-react";
5995
- import { useCallback as useCallback14, useEffect as useEffect14, useLayoutEffect, useRef as useRef11, useState as useState15 } from "react";
6158
+ import { Skeleton } from "@hachej/boring-ui-kit";
6159
+ import { useCallback as useCallback14, useEffect as useEffect14, useLayoutEffect as useLayoutEffect2, useRef as useRef11, useState as useState15 } from "react";
5996
6160
  import { useStickToBottomContext as useStickToBottomContext2 } from "use-stick-to-bottom";
5997
6161
 
5998
6162
  // src/front/primitives/conversation.tsx
@@ -6053,7 +6217,7 @@ var ConversationScrollButton = ({
6053
6217
  Button7,
6054
6218
  {
6055
6219
  className: cn(
6056
- "absolute bottom-4 left-[50%] translate-x-[-50%] rounded-full dark:bg-background dark:hover:bg-muted",
6220
+ "boring-agent-scroll-latest absolute bottom-4 left-[50%] translate-x-[-50%] rounded-full dark:bg-background dark:hover:bg-muted",
6057
6221
  className
6058
6222
  ),
6059
6223
  onClick: handleScrollToBottom,
@@ -6098,8 +6262,27 @@ function noticeTextClass(className) {
6098
6262
  import { memo as memo2 } from "react";
6099
6263
  import { AlertTriangleIcon, InfoIcon, Loader2Icon, PlugZapIcon, RefreshCwIcon, XIcon as XIcon2 } from "lucide-react";
6100
6264
  import { Button as Button8 } from "@hachej/boring-ui-kit";
6101
- import { jsx as jsx16, jsxs as jsxs14 } from "react/jsx-runtime";
6102
- var RuntimeNotices = memo2(({ notices, onDismiss, onAction, renderAction, className, ...props }) => {
6265
+
6266
+ // src/front/chat/components/terminalChatErrors.ts
6267
+ var TERMINAL_CHAT_ERROR_IDS = /* @__PURE__ */ new Set(["chat-error", "session-navigation-error"]);
6268
+ function isTerminalChatErrorId(id) {
6269
+ return TERMINAL_CHAT_ERROR_IDS.has(id);
6270
+ }
6271
+ function isTerminalChatErrorNotice(id, historyEmpty) {
6272
+ return historyEmpty && isTerminalChatErrorId(id);
6273
+ }
6274
+ function hasTerminalChatError(notices, historyEmpty) {
6275
+ return historyEmpty && notices.some((notice) => isTerminalChatErrorId(notice.id));
6276
+ }
6277
+ var COMPETING_NOISE_NOTICE_IDS = /* @__PURE__ */ new Set(["connection-reconnecting", "auto-retry"]);
6278
+ function filterCompetingNoiseNotices(notices, historyEmpty) {
6279
+ if (!hasTerminalChatError(notices, historyEmpty)) return notices;
6280
+ return notices.filter((notice) => !COMPETING_NOISE_NOTICE_IDS.has(notice.id));
6281
+ }
6282
+
6283
+ // src/front/chat/components/RuntimeNotices.tsx
6284
+ import { Fragment as Fragment7, jsx as jsx16, jsxs as jsxs14 } from "react/jsx-runtime";
6285
+ var RuntimeNotices = memo2(({ notices, onDismiss, onAction, renderAction, historyEmpty, className, ...props }) => {
6103
6286
  if (notices.length === 0) return null;
6104
6287
  return /* @__PURE__ */ jsx16(
6105
6288
  "div",
@@ -6107,16 +6290,29 @@ var RuntimeNotices = memo2(({ notices, onDismiss, onAction, renderAction, classN
6107
6290
  "data-boring-agent-part": "runtime-notices",
6108
6291
  className: cn("flex flex-col gap-2 px-4 py-2", className),
6109
6292
  ...props,
6110
- children: notices.map((notice) => /* @__PURE__ */ jsx16(RuntimeNoticeRow, { notice, onDismiss, onAction, renderAction }, notice.id))
6293
+ children: notices.map((notice) => /* @__PURE__ */ jsx16(
6294
+ RuntimeNoticeRow,
6295
+ {
6296
+ notice,
6297
+ onDismiss,
6298
+ onAction,
6299
+ renderAction,
6300
+ historyEmpty: historyEmpty ?? false
6301
+ },
6302
+ notice.id
6303
+ ))
6111
6304
  }
6112
6305
  );
6113
6306
  });
6114
6307
  RuntimeNotices.displayName = "RuntimeNotices";
6115
- function RuntimeNoticeRow({ notice, onDismiss, onAction, renderAction }) {
6308
+ function RuntimeNoticeRow({ notice, onDismiss, onAction, renderAction, historyEmpty }) {
6116
6309
  const kind = inferNoticeKind(notice);
6117
6310
  const Icon = iconForNotice(kind, notice.level);
6118
6311
  const actionLabel = notice.actionLabel ?? defaultActionLabel(kind);
6119
6312
  const hostAction = renderAction?.(notice);
6313
+ const isTerminalError = isTerminalChatErrorNotice(notice.id, historyEmpty);
6314
+ const explicitAction = actionLabel && onAction ? /* @__PURE__ */ jsx16(Button8, { type: "button", variant: "ghost", size: "sm", onClick: () => onAction(notice.id), children: actionLabel }) : null;
6315
+ const primaryAction = hostAction ?? explicitAction ?? (isTerminalError ? /* @__PURE__ */ jsx16(Button8, { type: "button", variant: "outline", size: "sm", className: "shrink-0", onClick: () => window.location.reload(), children: "Reload workspace" }) : null);
6120
6316
  return /* @__PURE__ */ jsxs14(
6121
6317
  "div",
6122
6318
  {
@@ -6132,9 +6328,15 @@ function RuntimeNoticeRow({ notice, onDismiss, onAction, renderAction }) {
6132
6328
  ),
6133
6329
  children: [
6134
6330
  /* @__PURE__ */ jsx16(Icon, { className: cn(noticeIconClass(notice.level), kind === "retry" && "animate-spin motion-reduce:animate-none"), "aria-hidden": "true" }),
6135
- /* @__PURE__ */ jsx16("div", { className: "min-w-0 flex-1", children: /* @__PURE__ */ jsx16("p", { className: noticeTextClass(), children: notice.text }) }),
6136
- hostAction ?? null,
6137
- actionLabel && onAction ? /* @__PURE__ */ jsx16(Button8, { type: "button", variant: "ghost", size: "sm", onClick: () => onAction(notice.id), children: actionLabel }) : null,
6331
+ /* @__PURE__ */ jsx16("div", { className: "min-w-0 flex-1", children: isTerminalError ? /* @__PURE__ */ jsxs14(Fragment7, { children: [
6332
+ /* @__PURE__ */ jsx16("p", { className: "text-sm font-semibold text-foreground", children: "Chat history unavailable" }),
6333
+ /* @__PURE__ */ jsx16("p", { className: noticeTextClass("mt-0.5"), children: "The saved conversation could not be loaded. Reload the workspace to try again." }),
6334
+ /* @__PURE__ */ jsxs14("details", { className: "mt-2 text-xs text-muted-foreground", children: [
6335
+ /* @__PURE__ */ jsx16("summary", { className: "cursor-pointer select-none", children: "Error details" }),
6336
+ /* @__PURE__ */ jsx16("p", { className: "mt-1 break-words", children: notice.text })
6337
+ ] })
6338
+ ] }) : /* @__PURE__ */ jsx16("p", { className: noticeTextClass(), children: notice.text }) }),
6339
+ primaryAction,
6138
6340
  notice.dismissible && onDismiss ? /* @__PURE__ */ jsx16(
6139
6341
  Button8,
6140
6342
  {
@@ -6177,7 +6379,8 @@ import { jsx as jsx17, jsxs as jsxs15 } from "react/jsx-runtime";
6177
6379
  function RuntimeNoticeMessages({
6178
6380
  notices,
6179
6381
  onDismiss,
6180
- renderAction
6382
+ renderAction,
6383
+ historyEmpty
6181
6384
  }) {
6182
6385
  const visible = notices.filter(
6183
6386
  (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:")
@@ -6189,6 +6392,7 @@ function RuntimeNoticeMessages({
6189
6392
  notices: visible,
6190
6393
  onDismiss,
6191
6394
  renderAction,
6395
+ historyEmpty,
6192
6396
  className: "mx-auto w-full max-w-3xl px-0 py-0"
6193
6397
  }
6194
6398
  );
@@ -6650,11 +6854,11 @@ function extractCodeBlockText(children) {
6650
6854
  };
6651
6855
  return walk(children);
6652
6856
  }
6653
- var MarkdownPre = ({ children }) => {
6857
+ var MarkdownPre = ({ children, filename }) => {
6654
6858
  const code3 = extractCodeBlockText(children).replace(/\n$/, "");
6655
6859
  const language = extractCodeBlockLanguage(children);
6656
6860
  return /* @__PURE__ */ jsx19(CodeBlock2, { code: code3, language: language ?? "text", className: "my-3", children: /* @__PURE__ */ jsxs17(CodeBlockHeader, { children: [
6657
- /* @__PURE__ */ jsx19(CodeBlockTitle, { children: /* @__PURE__ */ jsx19(CodeBlockFilename, { children: language ?? "text" }) }),
6861
+ /* @__PURE__ */ jsx19(CodeBlockTitle, { children: /* @__PURE__ */ jsx19(CodeBlockFilename, { children: filename ?? language ?? "text" }) }),
6658
6862
  /* @__PURE__ */ jsx19(CodeBlockCopyButton, {})
6659
6863
  ] }) });
6660
6864
  };
@@ -6684,7 +6888,7 @@ var markdownComponents = {
6684
6888
  code: MarkdownCode
6685
6889
  };
6686
6890
  var MessageResponse = memo3(
6687
- ({ className, shikiTheme, components, ...props }) => /* @__PURE__ */ jsx19(
6891
+ ({ className, shikiTheme, components, codeFilename, ...props }) => /* @__PURE__ */ jsx19(
6688
6892
  Streamdown,
6689
6893
  {
6690
6894
  className: cn(
@@ -6693,11 +6897,15 @@ var MessageResponse = memo3(
6693
6897
  ),
6694
6898
  plugins: streamdownPlugins,
6695
6899
  shikiTheme: shikiTheme ?? DEFAULT_SHIKI_THEME,
6696
- components: { ...markdownComponents, ...components ?? {} },
6900
+ components: {
6901
+ ...markdownComponents,
6902
+ ...components ?? {},
6903
+ ...codeFilename ? { pre: (props2) => /* @__PURE__ */ jsx19(MarkdownPre, { ...props2, filename: codeFilename }) } : {}
6904
+ },
6697
6905
  ...props
6698
6906
  }
6699
6907
  ),
6700
- (prevProps, nextProps) => prevProps.children === nextProps.children && nextProps.isAnimating === prevProps.isAnimating && nextProps.components === prevProps.components
6908
+ (prevProps, nextProps) => prevProps.children === nextProps.children && nextProps.isAnimating === prevProps.isAnimating && nextProps.components === prevProps.components && prevProps.codeFilename === nextProps.codeFilename
6701
6909
  );
6702
6910
  MessageResponse.displayName = "MessageResponse";
6703
6911
 
@@ -6778,7 +6986,7 @@ var ShimmerComponent = ({
6778
6986
  var Shimmer = memo4(ShimmerComponent);
6779
6987
 
6780
6988
  // src/front/primitives/reasoning.tsx
6781
- import { Fragment as Fragment7, jsx as jsx21, jsxs as jsxs18 } from "react/jsx-runtime";
6989
+ import { Fragment as Fragment8, jsx as jsx21, jsxs as jsxs18 } from "react/jsx-runtime";
6782
6990
  var ReasoningContext = createContext6(null);
6783
6991
  var useReasoning = () => {
6784
6992
  const context = useContext6(ReasoningContext);
@@ -6891,7 +7099,7 @@ var ReasoningTrigger = memo5(
6891
7099
  className
6892
7100
  ),
6893
7101
  ...props,
6894
- children: children ?? /* @__PURE__ */ jsxs18(Fragment7, { children: [
7102
+ children: children ?? /* @__PURE__ */ jsxs18(Fragment8, { children: [
6895
7103
  /* @__PURE__ */ jsx21(BrainIcon, { className: "size-3" }),
6896
7104
  getThinkingMessage(isStreaming, duration),
6897
7105
  /* @__PURE__ */ jsx21(
@@ -7053,6 +7261,10 @@ var ToolCallGroup = memo6(({ tools, mergedToolRenderers }) => {
7053
7261
  return () => window.clearInterval(interval);
7054
7262
  }, [isRunning]);
7055
7263
  if (toolParts.length === 0) return null;
7264
+ const inlineToolKeys = new Set(toolParts.flatMap(({ part, key }) => {
7265
+ const { renderer } = resolveToolRendererForPart(part, mergedToolRenderers);
7266
+ return renderer.presentation === "inline" ? [key] : [];
7267
+ }));
7056
7268
  return /* @__PURE__ */ jsxs19(
7057
7269
  Collapsible3,
7058
7270
  {
@@ -7061,6 +7273,11 @@ var ToolCallGroup = memo6(({ tools, mergedToolRenderers }) => {
7061
7273
  className: "not-prose my-1.5",
7062
7274
  "data-boring-agent-tool-state": visualState,
7063
7275
  children: [
7276
+ toolParts.map(({ part, key }) => {
7277
+ if (!inlineToolKeys.has(key)) return null;
7278
+ const { renderer, part: resolvedPart, resolution } = resolveToolRendererForPart(part, mergedToolRenderers);
7279
+ return /* @__PURE__ */ jsx22("div", { "data-boring-agent-part": "inline-tool-renderer", "data-tool-call-id": resolvedPart.toolCallId, "data-tool-renderer-key": resolution.key, children: renderer(resolvedPart) }, key);
7280
+ }),
7064
7281
  /* @__PURE__ */ jsxs19(
7065
7282
  CollapsibleTrigger4,
7066
7283
  {
@@ -7106,7 +7323,10 @@ var ToolCallGroup = memo6(({ tools, mergedToolRenderers }) => {
7106
7323
  children: [
7107
7324
  /* @__PURE__ */ jsx22("div", { className: "absolute left-[5px] top-0 bottom-2 w-px bg-border/35" }),
7108
7325
  /* @__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 }) => {
7109
- const { renderer, part: resolvedPart, resolution } = resolveToolRendererForPart(part, mergedToolRenderers);
7326
+ const { renderer, part: resolvedPart, resolution } = resolveToolRendererForPart(
7327
+ part,
7328
+ inlineToolKeys.has(key) ? {} : mergedToolRenderers
7329
+ );
7110
7330
  return /* @__PURE__ */ jsx22(
7111
7331
  "div",
7112
7332
  {
@@ -7158,7 +7378,7 @@ function ChatMessageContributionBoundary({
7158
7378
 
7159
7379
  // src/front/chat/components/MessageMentions.tsx
7160
7380
  import { Children, cloneElement, isValidElement as isValidElement2 } from "react";
7161
- import { Fragment as Fragment8, jsx as jsx24 } from "react/jsx-runtime";
7381
+ import { Fragment as Fragment9, jsx as jsx24 } from "react/jsx-runtime";
7162
7382
  var TERMINAL_BOUNDARY = String.raw`(?=$|\s|[.,;!?)}\]"'”’]+(?=$|\s))`;
7163
7383
  var PREFIX_BOUNDARY = String.raw`(^|[\s(\[{"'“‘])`;
7164
7384
  function escapeRegExp2(value) {
@@ -7316,8 +7536,8 @@ function createMessageMentionMarkdownComponents(catalog, onActivate) {
7316
7536
  const content = Children.toArray(children).filter((child) => child !== null && child !== "");
7317
7537
  if (content.length === 1 && isValidElement2(content[0])) {
7318
7538
  const child = content[0];
7319
- if (child.props.node?.tagName === "img") return /* @__PURE__ */ jsx24(Fragment8, { children });
7320
- if (child.props.node?.tagName === "code" && "data-block" in child.props) return /* @__PURE__ */ jsx24(Fragment8, { children });
7539
+ if (child.props.node?.tagName === "img") return /* @__PURE__ */ jsx24(Fragment9, { children });
7540
+ if (child.props.node?.tagName === "code" && "data-block" in child.props) return /* @__PURE__ */ jsx24(Fragment9, { children });
7321
7541
  }
7322
7542
  return /* @__PURE__ */ jsx24("p", { ...props, children: decorate(children) });
7323
7543
  };
@@ -7403,12 +7623,13 @@ function DefaultPiTimelineMessage({ message, isLast, isStreaming, showThoughts,
7403
7623
  mentionActivateRef.current?.(mention);
7404
7624
  }, [openArtifact]);
7405
7625
  const hasMentions = effectiveMentionCatalog.files || effectiveMentionCatalog.commands.length > 0 || effectiveMentionCatalog.skills.length > 0;
7406
- const mentionsEnabled = Boolean(isAssistant && !(isStreaming && isLast) && hasMentions);
7626
+ const messageIsStreaming = isAssistant && isLast && (isStreaming || message.status === "streaming");
7627
+ const mentionsEnabled = Boolean(isAssistant && !messageIsStreaming && hasMentions);
7407
7628
  const mentionMarkdownComponents = useMemo12(
7408
7629
  () => mentionsEnabled ? createMessageMentionMarkdownComponents(effectiveMentionCatalog, activateCurrentMention) : void 0,
7409
7630
  [activateCurrentMention, effectiveMentionCatalog, mentionSignature, mentionsEnabled]
7410
7631
  );
7411
- const shouldReserveStreamingActions = isStreaming && isAssistant && isLast;
7632
+ const shouldReserveStreamingActions = messageIsStreaming;
7412
7633
  return /* @__PURE__ */ jsxs20(
7413
7634
  Message,
7414
7635
  {
@@ -7467,7 +7688,7 @@ function DefaultPiTimelineMessage({ message, isLast, isStreaming, showThoughts,
7467
7688
  })
7468
7689
  }
7469
7690
  ) : null,
7470
- finalParts.map((item) => {
7691
+ finalParts.map((item, index) => {
7471
7692
  if (item.kind === "reasoning") {
7472
7693
  return /* @__PURE__ */ jsx25(
7473
7694
  TimelineReasoningPart,
@@ -7491,6 +7712,7 @@ function DefaultPiTimelineMessage({ message, isLast, isStreaming, showThoughts,
7491
7712
  MessageResponse,
7492
7713
  {
7493
7714
  components: mentionMarkdownComponents,
7715
+ codeFilename: codeFilenameForPart(finalParts, index),
7494
7716
  className: "boring-agent-markdown max-w-none",
7495
7717
  children: text
7496
7718
  },
@@ -7507,7 +7729,7 @@ function DefaultPiTimelineMessage({ message, isLast, isStreaming, showThoughts,
7507
7729
  MessageActionsBar,
7508
7730
  {
7509
7731
  text: textParts.map((part) => part.text).join("\n\n"),
7510
- visible: !isStreaming
7732
+ visible: !messageIsStreaming
7511
7733
  }
7512
7734
  ) : null
7513
7735
  ]
@@ -7542,7 +7764,7 @@ function TimelineReasoningPart({ item, showThoughts }) {
7542
7764
  ReasoningTrigger,
7543
7765
  {
7544
7766
  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",
7545
- getThinkingMessage: (streaming) => /* @__PURE__ */ jsx25("span", { children: streaming ? "thinking" : "thoughts" }),
7767
+ getThinkingMessage: () => /* @__PURE__ */ jsx25("span", { children: "Reasoning" }),
7546
7768
  onClick: toggleManualOpen
7547
7769
  }
7548
7770
  ),
@@ -7603,6 +7825,15 @@ ${part.text}`;
7603
7825
  flushTools();
7604
7826
  return grouped;
7605
7827
  }
7828
+ function codeFilenameForPart(parts, index) {
7829
+ const precedingPart = parts[index - 1];
7830
+ if (!precedingPart || precedingPart.kind !== "tool-plain") return void 0;
7831
+ if (precedingPart.part.toolName !== "write" && precedingPart.part.toolName !== "edit") return void 0;
7832
+ const input = precedingPart.part.input;
7833
+ if (!input || typeof input !== "object" || Array.isArray(input)) return void 0;
7834
+ const path = input.path;
7835
+ return typeof path === "string" && path.trim() ? path : void 0;
7836
+ }
7606
7837
  function NoticeBubble({ level, text }) {
7607
7838
  return /* @__PURE__ */ jsx25(
7608
7839
  "div",
@@ -7683,10 +7914,10 @@ function MessageActionsBar({
7683
7914
  }
7684
7915
  };
7685
7916
  const iconActionBtnClass = cn(
7686
- "inline-flex h-6 w-6 items-center justify-center rounded-[var(--radius-sm)]",
7687
- "text-muted-foreground/35 transition-colors",
7688
- "hover:bg-foreground/[0.04] hover:text-muted-foreground/80",
7689
- "focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-[color:var(--accent)]/40"
7917
+ "boring-agent-message-action inline-flex size-8 items-center justify-center rounded-[var(--radius-sm)]",
7918
+ "text-muted-foreground/70 transition-colors duration-150",
7919
+ "hover:bg-foreground/[0.04] hover:text-muted-foreground",
7920
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[color:var(--accent)]/40"
7690
7921
  );
7691
7922
  const hiddenActionProps = visible ? {} : { tabIndex: -1 };
7692
7923
  return /* @__PURE__ */ jsx25(
@@ -7694,7 +7925,7 @@ function MessageActionsBar({
7694
7925
  {
7695
7926
  "aria-hidden": !visible,
7696
7927
  className: cn(
7697
- "flex min-h-6 items-center gap-0.5 -mt-1 transition-opacity duration-200",
7928
+ "flex min-h-8 items-center gap-1 -mt-1 text-xs text-muted-foreground transition-opacity duration-200",
7698
7929
  visible ? "opacity-100" : "pointer-events-none opacity-0"
7699
7930
  ),
7700
7931
  children: /* @__PURE__ */ jsx25(
@@ -7755,6 +7986,8 @@ function PiConversationSurface({
7755
7986
  }) {
7756
7987
  const messageItems = buildMessageRenderItems(messages);
7757
7988
  const total = messageItems.length;
7989
+ const historyEmpty = messages.length === 0;
7990
+ const terminalError = hasTerminalChatError(runtimeNotices, historyEmpty);
7758
7991
  const [visibleCount, setVisibleCount] = useState15(TRANSCRIPT_WINDOW);
7759
7992
  useEffect14(() => {
7760
7993
  setVisibleCount(TRANSCRIPT_WINDOW);
@@ -7778,11 +8011,8 @@ function PiConversationSurface({
7778
8011
  chrome ? "max-w-3xl px-6 py-8" : "max-w-[680px] px-4 py-4",
7779
8012
  emptyHero && "py-4 text-center"
7780
8013
  ), children: [
7781
- 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: [
7782
- /* @__PURE__ */ jsx26(Loader22, { className: "h-4 w-4 animate-spin" }),
7783
- "Loading chat history\u2026"
7784
- ] }) : null,
7785
- messages.length === 0 && !emptyStateHydrating ? /* @__PURE__ */ jsx26(
8014
+ messages.length === 0 && emptyStateHydrating && !terminalError ? /* @__PURE__ */ jsx26(ConversationHistoryLoadingState, {}) : null,
8015
+ messages.length === 0 && !emptyStateHydrating && !terminalError ? /* @__PURE__ */ jsx26(
7786
8016
  ChatEmptyState,
7787
8017
  {
7788
8018
  eyebrow: emptyState?.eyebrow,
@@ -7814,13 +8044,45 @@ function PiConversationSurface({
7814
8044
  },
7815
8045
  key
7816
8046
  )),
7817
- /* @__PURE__ */ jsx26(RuntimeNoticeMessages, { notices: runtimeNotices, onDismiss: onDismissNotice, renderAction: renderNoticeAction })
8047
+ /* @__PURE__ */ jsx26(RuntimeNoticeMessages, { notices: runtimeNotices, onDismiss: onDismissNotice, renderAction: renderNoticeAction, historyEmpty })
7818
8048
  ] }),
7819
8049
  /* @__PURE__ */ jsx26(ConversationScrollButton, {})
7820
8050
  ]
7821
8051
  }
7822
8052
  );
7823
8053
  }
8054
+ function ConversationHistoryLoadingState() {
8055
+ const skeletonClassName = "motion-reduce:animate-none";
8056
+ return /* @__PURE__ */ jsxs21(
8057
+ "div",
8058
+ {
8059
+ role: "status",
8060
+ "aria-label": "Loading chat history",
8061
+ "aria-busy": "true",
8062
+ "data-boring-agent-part": "chat-history-loading",
8063
+ className: "flex flex-col gap-7 py-4",
8064
+ children: [
8065
+ /* @__PURE__ */ jsxs21("div", { "aria-hidden": "true", className: "flex items-start gap-3", children: [
8066
+ /* @__PURE__ */ jsx26(Skeleton, { className: cn("mt-0.5 size-7 shrink-0 rounded-lg", skeletonClassName) }),
8067
+ /* @__PURE__ */ jsxs21("div", { className: "flex min-w-0 flex-1 flex-col gap-2.5", children: [
8068
+ /* @__PURE__ */ jsx26(Skeleton, { className: cn("h-3 w-24 rounded-sm", skeletonClassName) }),
8069
+ /* @__PURE__ */ jsx26(Skeleton, { className: cn("h-3 w-full rounded-sm", skeletonClassName) }),
8070
+ /* @__PURE__ */ jsx26(Skeleton, { className: cn("h-3 w-4/5 rounded-sm", skeletonClassName) })
8071
+ ] })
8072
+ ] }),
8073
+ /* @__PURE__ */ jsx26("div", { "aria-hidden": "true", className: "flex justify-end", children: /* @__PURE__ */ jsx26(Skeleton, { className: cn("h-11 w-[min(72%,30rem)] rounded-xl", skeletonClassName) }) }),
8074
+ /* @__PURE__ */ jsxs21("div", { "aria-hidden": "true", className: "flex items-start gap-3", children: [
8075
+ /* @__PURE__ */ jsx26(Skeleton, { className: cn("mt-0.5 size-7 shrink-0 rounded-lg", skeletonClassName) }),
8076
+ /* @__PURE__ */ jsxs21("div", { className: "flex min-w-0 flex-1 flex-col gap-3", children: [
8077
+ /* @__PURE__ */ jsx26(Skeleton, { className: cn("h-3 w-20 rounded-sm", skeletonClassName) }),
8078
+ /* @__PURE__ */ jsx26(Skeleton, { className: cn("h-20 w-full rounded-lg", skeletonClassName) }),
8079
+ /* @__PURE__ */ jsx26(Skeleton, { className: cn("h-3 w-3/5 rounded-sm", skeletonClassName) })
8080
+ ] })
8081
+ ] })
8082
+ ]
8083
+ }
8084
+ );
8085
+ }
7824
8086
  function TranscriptHistoryLoader({ olderCount, onLoadOlder }) {
7825
8087
  const { scrollRef } = useStickToBottomContext2();
7826
8088
  const pendingAnchor = useRef11(null);
@@ -7839,7 +8101,7 @@ function TranscriptHistoryLoader({ olderCount, onLoadOlder }) {
7839
8101
  el.addEventListener("scroll", onScroll, { passive: true });
7840
8102
  return () => el.removeEventListener("scroll", onScroll);
7841
8103
  }, [scrollRef, onLoadOlder]);
7842
- useLayoutEffect(() => {
8104
+ useLayoutEffect2(() => {
7843
8105
  const el = scrollRef.current;
7844
8106
  if (!el) return;
7845
8107
  if (pendingAnchor.current != null) {
@@ -7853,7 +8115,7 @@ function TranscriptHistoryLoader({ olderCount, onLoadOlder }) {
7853
8115
  {
7854
8116
  type: "button",
7855
8117
  onClick: onLoadOlder,
7856
- className: "rounded-full border border-border/60 px-3 py-1 text-[11px] text-muted-foreground transition-colors hover:text-foreground",
8118
+ className: "boring-agent-history-action min-h-8 rounded-full border border-border/60 px-3 py-1.5 text-xs text-muted-foreground transition-colors hover:text-foreground",
7857
8119
  children: [
7858
8120
  "Load ",
7859
8121
  olderCount,
@@ -7885,8 +8147,8 @@ function buildMessageRenderItems(messages) {
7885
8147
  }
7886
8148
 
7887
8149
  // src/front/chat/components/PiChatComposerSurface.tsx
7888
- import { useCallback as useCallback16, useLayoutEffect as useLayoutEffect2 } from "react";
7889
- import { motion as motion2 } from "motion/react";
8150
+ import { useCallback as useCallback16, useLayoutEffect as useLayoutEffect3 } from "react";
8151
+ import { motion as motion2, useReducedMotion } from "motion/react";
7890
8152
 
7891
8153
  // src/front/composer/PluginUpdateStatus.tsx
7892
8154
  import { useEffect as useEffect15, useRef as useRef12 } from "react";
@@ -8129,7 +8391,7 @@ function PluginUpdateStatus({
8129
8391
 
8130
8392
  // src/front/composer/CommandRunStatus.tsx
8131
8393
  import { useEffect as useEffect16, useRef as useRef13 } from "react";
8132
- import { Fragment as Fragment9, jsx as jsx29, jsxs as jsxs24 } from "react/jsx-runtime";
8394
+ import { Fragment as Fragment10, jsx as jsx29, jsxs as jsxs24 } from "react/jsx-runtime";
8133
8395
  function CommandRunStatus({
8134
8396
  state,
8135
8397
  onDismiss,
@@ -8154,7 +8416,7 @@ function CommandRunStatus({
8154
8416
  tone: "running",
8155
8417
  dataAttribute: "data-boring-command-run",
8156
8418
  maxWidthClassName,
8157
- runningContent: state.command ? /* @__PURE__ */ jsxs24(Fragment9, { children: [
8419
+ runningContent: state.command ? /* @__PURE__ */ jsxs24(Fragment10, { children: [
8158
8420
  " Running ",
8159
8421
  /* @__PURE__ */ jsxs24("code", { className: "font-mono", children: [
8160
8422
  "/",
@@ -8172,7 +8434,7 @@ function CommandRunStatus({
8172
8434
  tone: "success",
8173
8435
  dataAttribute: "data-boring-command-run",
8174
8436
  maxWidthClassName,
8175
- title: state.command ? /* @__PURE__ */ jsxs24(Fragment9, { children: [
8437
+ title: state.command ? /* @__PURE__ */ jsxs24(Fragment10, { children: [
8176
8438
  " Ran ",
8177
8439
  /* @__PURE__ */ jsxs24("code", { className: "font-mono", children: [
8178
8440
  "/",
@@ -8192,7 +8454,7 @@ function CommandRunStatus({
8192
8454
  tone: "error",
8193
8455
  dataAttribute: "data-boring-command-run",
8194
8456
  maxWidthClassName,
8195
- title: state.command ? /* @__PURE__ */ jsxs24(Fragment9, { children: [
8457
+ title: state.command ? /* @__PURE__ */ jsxs24(Fragment10, { children: [
8196
8458
  " ",
8197
8459
  /* @__PURE__ */ jsxs24("code", { className: "font-mono", children: [
8198
8460
  "/",
@@ -8242,7 +8504,7 @@ function displayProviderLabel(provider) {
8242
8504
  }
8243
8505
 
8244
8506
  // src/front/chatPanelComposerControls.tsx
8245
- import { Fragment as Fragment10, jsx as jsx30, jsxs as jsxs25 } from "react/jsx-runtime";
8507
+ import { Fragment as Fragment11, jsx as jsx30, jsxs as jsxs25 } from "react/jsx-runtime";
8246
8508
  var composerActionClass = cn(
8247
8509
  "inline-flex h-8 items-center justify-center gap-1.5 rounded-[var(--radius-md)] border-0 bg-transparent",
8248
8510
  "text-muted-foreground shadow-none transition-colors motion-reduce:transition-none",
@@ -8384,14 +8646,14 @@ var ModelSelectTrigger = forwardRef(function ModelSelectTrigger2({
8384
8646
  className
8385
8647
  ),
8386
8648
  ...props,
8387
- children: trigger === "slash" ? /* @__PURE__ */ jsxs25(Fragment10, { children: [
8649
+ children: trigger === "slash" ? /* @__PURE__ */ jsxs25(Fragment11, { children: [
8388
8650
  /* @__PURE__ */ jsx30("span", { className: "text-muted-foreground", children: "Model \xB7 " }),
8389
8651
  /* @__PURE__ */ jsx30("span", { className: "min-w-0 truncate text-foreground", children: triggerLabel }),
8390
8652
  value ? /* @__PURE__ */ jsxs25("span", { className: "shrink-0 text-muted-foreground", children: [
8391
8653
  " \xB7 ",
8392
8654
  displayProviderLabel(value.provider)
8393
8655
  ] }) : null
8394
- ] }) : /* @__PURE__ */ jsxs25(Fragment10, { children: [
8656
+ ] }) : /* @__PURE__ */ jsxs25(Fragment11, { children: [
8395
8657
  /* @__PURE__ */ jsx30("span", { className: "min-w-0 truncate", children: triggerDisplay }),
8396
8658
  /* @__PURE__ */ jsx30(ChevronDownIcon5, { className: "h-3 w-3 shrink-0 text-muted-foreground/50", "aria-hidden": "true" })
8397
8659
  ] })
@@ -8680,10 +8942,10 @@ var ThinkingSelectTrigger = forwardRef(function ThinkingSelectTrigger2({
8680
8942
  ...props,
8681
8943
  children: [
8682
8944
  trigger === "button" ? /* @__PURE__ */ jsx30(ThinkingLevelGlyph, { level: value }) : null,
8683
- trigger === "slash" ? /* @__PURE__ */ jsxs25(Fragment10, { children: [
8945
+ trigger === "slash" ? /* @__PURE__ */ jsxs25(Fragment11, { children: [
8684
8946
  /* @__PURE__ */ jsx30("span", { className: "text-muted-foreground", children: "Thinking \xB7 " }),
8685
8947
  /* @__PURE__ */ jsx30("span", { className: "text-foreground", children: THINKING_LEVEL_STATUS_LABELS[value] })
8686
- ] }) : /* @__PURE__ */ jsxs25(Fragment10, { children: [
8948
+ ] }) : /* @__PURE__ */ jsxs25(Fragment11, { children: [
8687
8949
  /* @__PURE__ */ jsx30("span", { children: THINKING_LEVEL_LABELS[value] }),
8688
8950
  /* @__PURE__ */ jsx30(ChevronDownIcon5, { className: "h-3 w-3 shrink-0 text-muted-foreground/50", "aria-hidden": "true" })
8689
8951
  ] })
@@ -8847,7 +9109,7 @@ var PromptInputFooter = ({
8847
9109
  );
8848
9110
 
8849
9111
  // src/front/primitives/prompt-input.tsx
8850
- import { Fragment as Fragment11, jsx as jsx32, jsxs as jsxs27 } from "react/jsx-runtime";
9112
+ import { Fragment as Fragment12, jsx as jsx32, jsxs as jsxs27 } from "react/jsx-runtime";
8851
9113
  var PromptInput = ({
8852
9114
  className,
8853
9115
  accept,
@@ -9190,7 +9452,7 @@ var PromptInput = ({
9190
9452
  },
9191
9453
  [usingProvider, controller, files, onSubmit, clear]
9192
9454
  );
9193
- const inner = /* @__PURE__ */ jsxs27(Fragment11, { children: [
9455
+ const inner = /* @__PURE__ */ jsxs27(Fragment12, { children: [
9194
9456
  /* @__PURE__ */ jsx32(
9195
9457
  Input,
9196
9458
  {
@@ -9359,7 +9621,7 @@ var PromptInputSubmit = ({
9359
9621
 
9360
9622
  // src/front/primitives/slash-command-picker.tsx
9361
9623
  import { useEffect as useEffect19, useMemo as useMemo14, useRef as useRef16, useState as useState18 } from "react";
9362
- import { Fragment as Fragment12, jsx as jsx33, jsxs as jsxs28 } from "react/jsx-runtime";
9624
+ import { Fragment as Fragment13, jsx as jsx33, jsxs as jsxs28 } from "react/jsx-runtime";
9363
9625
  var ALL_PLUGINS = "__all__";
9364
9626
  function commandGroup(cmd) {
9365
9627
  if (cmd.source === "skill") return "skills";
@@ -9471,7 +9733,7 @@ function SlashCommandPicker({ query, commands, onSelect, onDismiss }) {
9471
9733
  "/",
9472
9734
  cmd.name
9473
9735
  ] }),
9474
- cmd.source === "skill" ? /* @__PURE__ */ jsxs28(Fragment12, { children: [
9736
+ cmd.source === "skill" ? /* @__PURE__ */ jsxs28(Fragment13, { children: [
9475
9737
  /* @__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" }),
9476
9738
  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
9477
9739
  ] }) : cmd.sourcePlugin ? (
@@ -9488,7 +9750,7 @@ function SlashCommandPicker({ query, commands, onSelect, onDismiss }) {
9488
9750
  }
9489
9751
 
9490
9752
  // src/front/chat/components/ComposerAttachments.tsx
9491
- import { AlertCircleIcon as AlertCircleIcon2, Loader2 as Loader23, PaperclipIcon as PaperclipIcon2 } from "lucide-react";
9753
+ import { AlertCircleIcon as AlertCircleIcon2, Loader2 as Loader22, PaperclipIcon as PaperclipIcon2 } from "lucide-react";
9492
9754
  import { IconButton as IconButton3 } from "@hachej/boring-ui-kit";
9493
9755
  import { jsx as jsx34, jsxs as jsxs29 } from "react/jsx-runtime";
9494
9756
  function AttachmentButton({ disabled, className }) {
@@ -9503,7 +9765,7 @@ function AttachmentButton({ disabled, className }) {
9503
9765
  onClick: () => {
9504
9766
  if (!disabled) attachments.openFileDialog();
9505
9767
  },
9506
- className: cn(composerActionClass, "!h-10 !w-10", className),
9768
+ className: cn(composerActionClass, "composer-attachment-button !h-10 !w-10", className),
9507
9769
  "aria-label": "Attach files",
9508
9770
  title: disabled ? "Attachments are available when the composer is ready." : "Attach files",
9509
9771
  children: /* @__PURE__ */ jsx34(PaperclipIcon2, { className: "h-4 w-4", strokeWidth: 1.75 })
@@ -9526,18 +9788,18 @@ function AttachmentsList() {
9526
9788
  data: file,
9527
9789
  onRemove: () => attachments.remove(file.id),
9528
9790
  className: cn(
9529
- "!h-9 !gap-2 !rounded-full !border-input/80 !bg-muted/40 !pl-1 !pr-2",
9791
+ "composer-attachment-chip !h-9 !gap-2 !rounded-full !border-input/80 !bg-muted/40 !pl-1 !pr-2",
9530
9792
  "transition-colors hover:!bg-muted/70 hover:!text-foreground",
9531
9793
  file.status === "error" && "!border-destructive/50 !bg-destructive/10"
9532
9794
  ),
9533
9795
  children: [
9534
9796
  /* @__PURE__ */ jsxs29("div", { className: "relative shrink-0", children: [
9535
9797
  /* @__PURE__ */ jsx34(AttachmentPreview, { className: "!size-7 overflow-hidden !rounded-full bg-background/60" }),
9536
- 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,
9798
+ file.status === "uploading" ? /* @__PURE__ */ jsx34("div", { className: "absolute inset-0 flex items-center justify-center rounded-full bg-background/70", children: /* @__PURE__ */ jsx34(Loader22, { className: "size-3.5 animate-spin text-muted-foreground" }) }) : null,
9537
9799
  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
9538
9800
  ] }),
9539
9801
  /* @__PURE__ */ jsx34(AttachmentInfo, { className: "min-w-0 !max-w-[180px] truncate text-[13px] font-medium" }),
9540
- /* @__PURE__ */ jsx34(AttachmentRemove, { className: "!size-5 !rounded-full !opacity-100 text-muted-foreground/80 hover:text-foreground" })
9802
+ /* @__PURE__ */ jsx34(AttachmentRemove, { className: "composer-attachment-remove !size-5 !rounded-full !opacity-100 text-muted-foreground/80 hover:text-foreground" })
9541
9803
  ]
9542
9804
  },
9543
9805
  file.id
@@ -9629,6 +9891,7 @@ function PiChatComposerSurface({
9629
9891
  onSubmitMessage,
9630
9892
  onStop
9631
9893
  }) {
9894
+ const prefersReducedMotion = useReducedMotion();
9632
9895
  const workspaceRequestId = getHeaderValue(requestHeaders, "x-boring-workspace-id");
9633
9896
  const composerContributions = useComposerContributions();
9634
9897
  const uploadAttachment = useCallback16((file) => uploadFile(file, {
@@ -9658,7 +9921,7 @@ function PiChatComposerSurface({
9658
9921
  rail?.style.setProperty("--composer-input-group-height", `${inputGroupHeight}px`);
9659
9922
  rail?.setAttribute("data-composer-multiline", inputGroupHeight > COMPOSER_INPUT_GROUP_MIN_HEIGHT ? "true" : "false");
9660
9923
  }, []);
9661
- useLayoutEffect2(() => {
9924
+ useLayoutEffect3(() => {
9662
9925
  resizeTextarea(textareaRef.current);
9663
9926
  }, [draft, resizeTextarea, textareaRef]);
9664
9927
  return /* @__PURE__ */ jsxs30("div", { className: cn(
@@ -9681,15 +9944,15 @@ function PiChatComposerSurface({
9681
9944
  "data-testid": isStreaming ? "chat-working" : void 0,
9682
9945
  role: isStreaming ? "status" : void 0,
9683
9946
  "aria-live": isStreaming ? "polite" : void 0,
9684
- 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",
9947
+ className: "inline-flex items-center gap-2 rounded-full border border-border/50 bg-background/85 px-2.5 py-1 text-[12px] text-foreground shadow-sm backdrop-blur",
9685
9948
  children: [
9686
9949
  /* @__PURE__ */ jsx35(
9687
9950
  motion2.span,
9688
9951
  {
9689
9952
  "aria-hidden": "true",
9690
9953
  className: "inline-block size-1.5 rounded-full bg-[color:var(--accent)]",
9691
- animate: { opacity: [0.35, 1, 0.35] },
9692
- transition: { duration: 1.2, repeat: Infinity, ease: "easeInOut" }
9954
+ animate: prefersReducedMotion ? { opacity: 1 } : { opacity: [0.35, 1, 0.35] },
9955
+ transition: prefersReducedMotion ? { duration: 0 } : { duration: 1.2, repeat: Infinity, ease: "easeInOut" }
9693
9956
  }
9694
9957
  ),
9695
9958
  /* @__PURE__ */ jsx35("span", { children: "Working\u2026" })
@@ -9881,7 +10144,7 @@ function PiChatComposerSurface({
9881
10144
  onStop,
9882
10145
  disabled: submitDisabled,
9883
10146
  className: cn(
9884
- "h-8 w-8 shrink-0 rounded-full",
10147
+ "composer-submit-control h-8 w-8 shrink-0 rounded-full",
9885
10148
  "bg-foreground text-background",
9886
10149
  "transition-all duration-150 ease-[cubic-bezier(0.22,1,0.36,1)]",
9887
10150
  "hover:bg-foreground/90 hover:shadow-[0_0_0_3px_oklch(from_var(--foreground)_l_c_h/0.12)] hover:scale-[1.04]",
@@ -9920,6 +10183,7 @@ function PiChatComposerSurface({
9920
10183
  disabled: isStreaming || modelControlled,
9921
10184
  trigger: "slash",
9922
10185
  open: modelPickerOpen,
10186
+ className: "composer-settings-trigger",
9923
10187
  onClick: () => {
9924
10188
  if (modelPickerOpen) {
9925
10189
  onSetModelPickerOpen(false);
@@ -9936,6 +10200,7 @@ function PiChatComposerSurface({
9936
10200
  disabled: isStreaming || thinkingControlled,
9937
10201
  trigger: "slash",
9938
10202
  open: thinkingPickerOpen,
10203
+ className: "composer-settings-trigger",
9939
10204
  onClick: () => {
9940
10205
  if (thinkingPickerOpen) {
9941
10206
  onSetThinkingPickerOpen(false);
@@ -9958,86 +10223,6 @@ function getHeaderValue(headers, name) {
9958
10223
 
9959
10224
  // src/front/chat/piChatPanelHooks.ts
9960
10225
  import { useCallback as useCallback17, useEffect as useEffect20, useMemo as useMemo15, useRef as useRef17, useState as useState19, useSyncExternalStore } from "react";
9961
- function useExternalRemotePiSession({
9962
- sessionId,
9963
- agentTypeId,
9964
- workspaceId,
9965
- storageScope,
9966
- apiBaseUrl,
9967
- requestHeaders,
9968
- fetch: fetch2,
9969
- createRemoteSession,
9970
- remoteSessionOptions
9971
- }) {
9972
- const [session, setSession] = useState19();
9973
- const remoteSessionOptionsRef = useRef17(remoteSessionOptions);
9974
- remoteSessionOptionsRef.current = remoteSessionOptions;
9975
- const remoteSessionOptionsKey = useMemo15(
9976
- () => remoteSessionOptionsIdentity2(remoteSessionOptions),
9977
- [remoteSessionOptions]
9978
- );
9979
- useEffect20(() => {
9980
- if (!sessionId) {
9981
- setSession(void 0);
9982
- return;
9983
- }
9984
- const next = (createRemoteSession ?? createRemotePiSession)({
9985
- ...remoteSessionOptionsRef.current,
9986
- sessionId,
9987
- agentTypeId,
9988
- workspaceId,
9989
- storageScope,
9990
- apiBaseUrl,
9991
- headers: requestHeaders,
9992
- fetch: fetch2
9993
- });
9994
- setSession(next);
9995
- return () => next.dispose();
9996
- }, [agentTypeId, apiBaseUrl, createRemoteSession, fetch2, remoteSessionOptionsKey, requestHeaders, sessionId, storageScope, workspaceId]);
9997
- return session;
9998
- }
9999
- var remoteSessionOptionObjectIds2 = /* @__PURE__ */ new WeakMap();
10000
- var remoteSessionOptionObjectSeq2 = 0;
10001
- function remoteSessionOptionObjectIdentity2(value) {
10002
- if (typeof value !== "object" && typeof value !== "function" || value === null) return void 0;
10003
- const object = value;
10004
- let id = remoteSessionOptionObjectIds2.get(object);
10005
- if (!id) {
10006
- id = ++remoteSessionOptionObjectSeq2;
10007
- remoteSessionOptionObjectIds2.set(object, id);
10008
- }
10009
- return String(id);
10010
- }
10011
- function remoteSessionOptionsIdentity2(options) {
10012
- if (!options) return "{}";
10013
- return JSON.stringify({
10014
- autoStart: options.autoStart,
10015
- requestTimeoutMs: options.requestTimeoutMs,
10016
- onEvent: remoteSessionOptionObjectIdentity2(options.onEvent),
10017
- storeOptions: remoteSessionOptionObjectIdentity2(options.storeOptions),
10018
- setTimeoutFn: remoteSessionOptionObjectIdentity2(options.setTimeoutFn),
10019
- clearTimeoutFn: remoteSessionOptionObjectIdentity2(options.clearTimeoutFn),
10020
- reconnect: options.reconnect ? {
10021
- baseMs: options.reconnect.baseMs,
10022
- maxMs: options.reconnect.maxMs,
10023
- jitterRatio: options.reconnect.jitterRatio,
10024
- random: remoteSessionOptionObjectIdentity2(options.reconnect.random)
10025
- } : void 0,
10026
- debug: options.debug ? {
10027
- largeStateWarningBytes: options.debug.largeStateWarningBytes,
10028
- largeStateWarningMessages: options.debug.largeStateWarningMessages,
10029
- onWarning: remoteSessionOptionObjectIdentity2(options.debug.onWarning)
10030
- } : void 0
10031
- });
10032
- }
10033
- function useRemotePiSessionState(session) {
10034
- return useSyncExternalStore(
10035
- useCallback17((listener) => session?.subscribe(listener) ?? (() => {
10036
- }), [session]),
10037
- useCallback17(() => session?.getState(), [session]),
10038
- useCallback17(() => session?.getState(), [session])
10039
- );
10040
- }
10041
10226
 
10042
10227
  // src/front/chat/piChatPanelUtils.ts
10043
10228
  function headersContentKey(headers) {
@@ -10144,9 +10329,103 @@ function errorMessage2(error, fallback) {
10144
10329
  return error instanceof Error && error.message ? error.message : fallback;
10145
10330
  }
10146
10331
 
10332
+ // src/front/chat/piChatPanelHooks.ts
10333
+ function useExternalRemotePiSession({
10334
+ sessionId,
10335
+ agentTypeId,
10336
+ workspaceId,
10337
+ storageScope,
10338
+ apiBaseUrl,
10339
+ requestHeaders,
10340
+ fetch: fetch2,
10341
+ createRemoteSession,
10342
+ remoteSessionOptions,
10343
+ enabled = true
10344
+ }) {
10345
+ const [session, setSession] = useState19();
10346
+ const remoteSessionOptionsRef = useRef17(remoteSessionOptions);
10347
+ remoteSessionOptionsRef.current = remoteSessionOptions;
10348
+ const requestHeadersKey = headersContentKey(requestHeaders);
10349
+ const stableRequestHeaders = useMemo15(
10350
+ () => normalizedHeadersFromContentKey(requestHeadersKey),
10351
+ [requestHeadersKey]
10352
+ );
10353
+ const remoteSessionOptionsKey = useMemo15(
10354
+ () => remoteSessionOptionsIdentity2(remoteSessionOptions),
10355
+ [remoteSessionOptions]
10356
+ );
10357
+ useEffect20(() => {
10358
+ if (!sessionId) {
10359
+ setSession(void 0);
10360
+ return;
10361
+ }
10362
+ const next = (createRemoteSession ?? createRemotePiSession)({
10363
+ ...remoteSessionOptionsRef.current,
10364
+ autoStart: false,
10365
+ sessionId,
10366
+ agentTypeId,
10367
+ workspaceId,
10368
+ storageScope,
10369
+ apiBaseUrl,
10370
+ headers: stableRequestHeaders,
10371
+ fetch: fetch2
10372
+ });
10373
+ setSession(next);
10374
+ return () => next.dispose();
10375
+ }, [agentTypeId, apiBaseUrl, createRemoteSession, fetch2, remoteSessionOptionsKey, sessionId, stableRequestHeaders, storageScope, workspaceId]);
10376
+ useEffect20(() => {
10377
+ if (!session) return;
10378
+ if (enabled) session.resumeStream?.();
10379
+ else session.suspendStream?.();
10380
+ }, [enabled, session]);
10381
+ return session;
10382
+ }
10383
+ var remoteSessionOptionObjectIds2 = /* @__PURE__ */ new WeakMap();
10384
+ var remoteSessionOptionObjectSeq2 = 0;
10385
+ function remoteSessionOptionObjectIdentity2(value) {
10386
+ if (typeof value !== "object" && typeof value !== "function" || value === null) return void 0;
10387
+ const object = value;
10388
+ let id = remoteSessionOptionObjectIds2.get(object);
10389
+ if (!id) {
10390
+ id = ++remoteSessionOptionObjectSeq2;
10391
+ remoteSessionOptionObjectIds2.set(object, id);
10392
+ }
10393
+ return String(id);
10394
+ }
10395
+ function remoteSessionOptionsIdentity2(options) {
10396
+ if (!options) return "{}";
10397
+ return JSON.stringify({
10398
+ autoStart: options.autoStart,
10399
+ requestTimeoutMs: options.requestTimeoutMs,
10400
+ onEvent: remoteSessionOptionObjectIdentity2(options.onEvent),
10401
+ storeOptions: remoteSessionOptionObjectIdentity2(options.storeOptions),
10402
+ setTimeoutFn: remoteSessionOptionObjectIdentity2(options.setTimeoutFn),
10403
+ clearTimeoutFn: remoteSessionOptionObjectIdentity2(options.clearTimeoutFn),
10404
+ reconnect: options.reconnect ? {
10405
+ baseMs: options.reconnect.baseMs,
10406
+ maxMs: options.reconnect.maxMs,
10407
+ jitterRatio: options.reconnect.jitterRatio,
10408
+ random: remoteSessionOptionObjectIdentity2(options.reconnect.random)
10409
+ } : void 0,
10410
+ debug: options.debug ? {
10411
+ largeStateWarningBytes: options.debug.largeStateWarningBytes,
10412
+ largeStateWarningMessages: options.debug.largeStateWarningMessages,
10413
+ onWarning: remoteSessionOptionObjectIdentity2(options.debug.onWarning)
10414
+ } : void 0
10415
+ });
10416
+ }
10417
+ function useRemotePiSessionState(session) {
10418
+ return useSyncExternalStore(
10419
+ useCallback17((listener) => session?.subscribe(listener) ?? (() => {
10420
+ }), [session]),
10421
+ useCallback17(() => session?.getState(), [session]),
10422
+ useCallback17(() => session?.getState(), [session])
10423
+ );
10424
+ }
10425
+
10147
10426
  // src/front/chat/PiChatPanel.tsx
10148
10427
  import { jsx as jsx36, jsxs as jsxs31 } from "react/jsx-runtime";
10149
- var DebugDrawer2 = lazy(() => import("../DebugDrawer-RYEBQ6CO.js").then((m) => ({ default: m.DebugDrawer })));
10428
+ var DebugDrawer2 = lazy(() => import("../DebugDrawer-2TO5SE2D.js").then((m) => ({ default: m.DebugDrawer })));
10150
10429
  var EMPTY_COMMANDS = [];
10151
10430
  var EMPTY_COMMAND_NAMES = [];
10152
10431
  var EMPTY_BLOCKERS = [];
@@ -10282,6 +10561,7 @@ function PiChatPanel({
10282
10561
  const selectedSessionPending = Boolean(activeSessionId && !selectedChatState);
10283
10562
  const modelDiscoveryEnabled = serverResourcesEnabled && availableModels === void 0;
10284
10563
  const modelDiscovery = useChatModelSelection({
10564
+ agentTypeId,
10285
10565
  apiBaseUrl,
10286
10566
  defaultModel,
10287
10567
  fetch: fetch2,
@@ -10385,6 +10665,7 @@ function PiChatPanel({
10385
10665
  return next;
10386
10666
  }, [apiBaseUrl, commands, contributedCommands, excludeBuiltinCommands, extraCommands, hotReloadEnabled, normalizedRequestHeaders, serverResourcesEnabled, serverSkillsRefreshKey, storageScope]);
10387
10667
  const commandsStamp = useServerCommands({
10668
+ agentTypeId,
10388
10669
  registry,
10389
10670
  requestHeaders: normalizedRequestHeaders,
10390
10671
  sessionId: activeSessionId ?? "default",
@@ -10414,7 +10695,8 @@ function PiChatPanel({
10414
10695
  const queuePreview = selectedChatState ? selectQueuePreview(selectedChatState) : [];
10415
10696
  const messages = canonicalMessages;
10416
10697
  const userHistory = useMemo16(() => selectComposerHistoryFromCanonicalUsers(canonicalMessages), [canonicalMessages]);
10417
- const emptyStateHydrating = statusForState(selectedChatState, sessionsLoading || chatStatePending || selectedSessionPending) === "hydrating";
10698
+ const awaitingHydration = Boolean(hydrateMessages && selectedChatState && !selectedChatState.hydrated);
10699
+ const emptyStateHydrating = statusForState(selectedChatState, sessionsLoading || chatStatePending || selectedSessionPending) === "hydrating" || awaitingHydration;
10418
10700
  const emptyHero = emptyPlacement === "hero" && messages.length === 0 && queuePreview.length === 0 && !emptyStateHydrating;
10419
10701
  const debugState = selectedPiSession?.getDebugState();
10420
10702
  const composerBlocked = workspaceWarmupBlocked || activeBlockers.length > 0;
@@ -10430,8 +10712,9 @@ function PiChatPanel({
10430
10712
  text: `Large chat state: ${debugState.largeStateWarning.messageCount} messages, approximately ${debugState.largeStateWarning.approxBytes} bytes.`,
10431
10713
  dismissible: true
10432
10714
  }] : [];
10433
- return [...fromState, ...sessionNotice, ...largeStateNotice, ...localNotices].filter((notice) => !dismissedNoticeIds.has(notice.id));
10434
- }, [debug, debugState?.largeStateWarning, dismissedNoticeIds, localNotices, selectedChatState, sessionsError]);
10715
+ const combined = [...fromState, ...sessionNotice, ...largeStateNotice, ...localNotices].filter((notice) => !dismissedNoticeIds.has(notice.id));
10716
+ return filterCompetingNoiseNotices(combined, messages.length === 0);
10717
+ }, [debug, debugState?.largeStateWarning, dismissedNoticeIds, localNotices, messages.length, selectedChatState, sessionsError]);
10435
10718
  const addLocalNotice = useCallback18((notice) => {
10436
10719
  setLocalNotices((previous) => {
10437
10720
  const next = previous.filter((candidate) => candidate.id !== notice.id);
@@ -10462,11 +10745,11 @@ function PiChatPanel({
10462
10745
  if (resetInProgressRef.current) return;
10463
10746
  if (autoCreateInFlightRef.current) return;
10464
10747
  autoCreateInFlightRef.current = true;
10465
- void sessions.create().catch((error) => {
10748
+ void sessions.create({ resumeSessionId: sessions.resumeSessionId }).catch((error) => {
10466
10749
  autoCreateInFlightRef.current = false;
10467
10750
  addLocalNotice({ id: "session-auto-create-error", level: "error", text: errorMessage2(error, "Could not create a chat session."), dismissible: true });
10468
10751
  });
10469
- }, [activeSessionId, addLocalNotice, externalSessionId, sessionList.length, sessions.create, sessionsError, sessionsLoading]);
10752
+ }, [activeSessionId, addLocalNotice, externalSessionId, sessionList.length, sessions.create, sessions.resumeSessionId, sessionsError, sessionsLoading]);
10470
10753
  useEffect21(() => {
10471
10754
  if (externalSessionId || sessionsError || activeSessionId || sessionList.length > 0) {
10472
10755
  autoCreateInFlightRef.current = false;
@@ -10914,14 +11197,18 @@ function PiChatPanel({
10914
11197
  }, [isStreaming]);
10915
11198
  useEffect21(() => {
10916
11199
  if (typeof window === "undefined" || !activeChatSessionId) return;
10917
- window.dispatchEvent(new CustomEvent("boring:chat-session-status", {
11200
+ const emitStatus = () => window.dispatchEvent(new CustomEvent("boring:chat-session-status", {
10918
11201
  detail: {
11202
+ ...workspaceId ? { workspaceId } : {},
10919
11203
  sessionId: activeChatSessionId,
10920
11204
  ...agentTypeId ? { agentTypeId } : {},
10921
11205
  working: isStreaming
10922
11206
  }
10923
11207
  }));
10924
- }, [activeChatSessionId, agentTypeId, isStreaming]);
11208
+ emitStatus();
11209
+ window.addEventListener("boring:chat-session-status-request", emitStatus);
11210
+ return () => window.removeEventListener("boring:chat-session-status-request", emitStatus);
11211
+ }, [activeChatSessionId, agentTypeId, isStreaming, workspaceId]);
10925
11212
  const onTextareaKeyDown = useCallback18((event) => {
10926
11213
  if (event.key === "Escape" && isStreaming) {
10927
11214
  if (event.defaultPrevented || mentionState !== null || slashQuery !== null) {
@@ -11011,6 +11298,7 @@ function PiChatPanel({
11011
11298
  {
11012
11299
  "data-boring-agent": "",
11013
11300
  "data-boring-agent-part": "chat",
11301
+ "data-agent-type-id": agentTypeId,
11014
11302
  "data-pi-chat-session-id": activeSessionId,
11015
11303
  "data-pi-chat-connection": debugState?.connection ?? "disconnected",
11016
11304
  "data-pi-chat-last-seq": debugState?.lastSeq ?? 0,
@@ -11089,6 +11377,7 @@ function PiChatPanel({
11089
11377
  debug ? /* @__PURE__ */ jsx36(Suspense, { fallback: null, children: /* @__PURE__ */ jsx36("div", { "aria-label": "Chat debug metadata", className: "contents", role: "region", children: /* @__PURE__ */ jsx36(
11090
11378
  DebugDrawer2,
11091
11379
  {
11380
+ agentTypeId,
11092
11381
  apiBaseUrl,
11093
11382
  fetch: fetch2,
11094
11383
  sessionId: activeSessionId ?? activeChatSessionId ?? "unknown",
@@ -11105,6 +11394,207 @@ function PiChatPanel({
11105
11394
  ) });
11106
11395
  }
11107
11396
 
11397
+ // src/front/chat/useAddressedAgentSelection.ts
11398
+ import { useCallback as useCallback19, useEffect as useEffect22, useMemo as useMemo17, useState as useState21 } from "react";
11399
+
11400
+ // src/front/chat/gatewayResponseError.ts
11401
+ var RUNTIME_SCOPE_MISMATCH_MESSAGE = "This chat belongs to a previous runtime configuration and can no longer be changed.";
11402
+ var GatewayResponseError = class extends Error {
11403
+ constructor(status, message, code3, details, body, operation) {
11404
+ super(message);
11405
+ this.status = status;
11406
+ this.code = code3;
11407
+ this.details = details;
11408
+ this.body = body;
11409
+ this.operation = operation;
11410
+ this.name = "GatewayResponseError";
11411
+ this.errorCode = code3;
11412
+ }
11413
+ status;
11414
+ code;
11415
+ details;
11416
+ body;
11417
+ operation;
11418
+ errorCode;
11419
+ };
11420
+ async function gatewayResponseError(response, fallbackMessage, operation) {
11421
+ const body = await safeReadJson2(response);
11422
+ return gatewayResponseErrorFromBody(response.status, body, fallbackMessage, operation);
11423
+ }
11424
+ function gatewayResponseErrorFromBody(status, body, fallbackMessage, operation) {
11425
+ const payload = errorPayload(body);
11426
+ const code3 = gatewayResponseErrorCode(payload?.code);
11427
+ const serverMessage = typeof payload?.message === "string" && payload.message.trim().length > 0 ? payload.message : fallbackMessage;
11428
+ const message = code3 === AgentGatewayErrorCode.AGENT_SESSION_RUNTIME_SCOPE_MISMATCH ? RUNTIME_SCOPE_MISMATCH_MESSAGE : serverMessage;
11429
+ const details = isJsonValue(payload?.details) ? payload.details : void 0;
11430
+ return new GatewayResponseError(status, message, code3, details, body, operation);
11431
+ }
11432
+ function gatewayResponseErrorCode(value) {
11433
+ const sharedCode = ErrorCode.safeParse(value);
11434
+ if (sharedCode.success) return sharedCode.data;
11435
+ return typeof value === "string" && AGENT_GATEWAY_ERROR_CODES.includes(value) ? value : void 0;
11436
+ }
11437
+ function errorResponseCode(error) {
11438
+ if (error instanceof GatewayResponseError) return error.code;
11439
+ const record = typeof error === "object" && error !== null ? error : void 0;
11440
+ return gatewayResponseErrorCode(record?.code ?? record?.errorCode);
11441
+ }
11442
+ function isRuntimeScopeMismatchError(error) {
11443
+ return errorResponseCode(error) === AgentGatewayErrorCode.AGENT_SESSION_RUNTIME_SCOPE_MISMATCH;
11444
+ }
11445
+ async function safeReadJson2(response) {
11446
+ const text = await response.text();
11447
+ if (!text) return void 0;
11448
+ try {
11449
+ return JSON.parse(text);
11450
+ } catch {
11451
+ return void 0;
11452
+ }
11453
+ }
11454
+ function errorPayload(body) {
11455
+ if (typeof body !== "object" || body === null) return void 0;
11456
+ const record = body;
11457
+ return typeof record.error === "object" && record.error !== null ? record.error : record;
11458
+ }
11459
+ function isJsonValue(value) {
11460
+ if (value === null || ["string", "number", "boolean"].includes(typeof value)) return true;
11461
+ if (Array.isArray(value)) return value.every(isJsonValue);
11462
+ return typeof value === "object" && value !== null && Object.values(value).every(isJsonValue);
11463
+ }
11464
+
11465
+ // src/front/chat/useAddressedAgentSelection.ts
11466
+ var inFlightDiscoveries = /* @__PURE__ */ new WeakMap();
11467
+ function useAddressedAgentSelection({
11468
+ apiBaseUrl,
11469
+ requestHeaders,
11470
+ storageScope,
11471
+ fetch: fetch2,
11472
+ enabled = false
11473
+ } = {}) {
11474
+ const normalizedBaseUrl = apiBaseUrl?.replace(/\/$/, "") ?? "";
11475
+ const headersKey = useMemo17(
11476
+ () => JSON.stringify({
11477
+ storageScope: storageScope ?? "",
11478
+ headers: Object.entries(requestHeaders ?? {}).sort(([a], [b]) => a.localeCompare(b))
11479
+ }),
11480
+ [requestHeaders, storageScope]
11481
+ );
11482
+ const discoveryKey = `${normalizedBaseUrl}
11483
+ ${headersKey}`;
11484
+ const fetchImpl = fetch2 ?? globalThis.fetch;
11485
+ const [state, setState] = useState21(() => ({
11486
+ discoveryKey: enabled ? "" : discoveryKey,
11487
+ agents: [],
11488
+ selectedAgentTypeId: void 0,
11489
+ loading: enabled,
11490
+ error: void 0
11491
+ }));
11492
+ useEffect22(() => {
11493
+ if (!enabled) {
11494
+ setState({
11495
+ discoveryKey,
11496
+ agents: [],
11497
+ selectedAgentTypeId: void 0,
11498
+ loading: false,
11499
+ error: void 0
11500
+ });
11501
+ return;
11502
+ }
11503
+ let cancelled = false;
11504
+ setState((previous) => ({
11505
+ discoveryKey,
11506
+ agents: previous.discoveryKey === discoveryKey ? previous.agents : [],
11507
+ selectedAgentTypeId: previous.discoveryKey === discoveryKey ? previous.selectedAgentTypeId : void 0,
11508
+ loading: true,
11509
+ error: void 0
11510
+ }));
11511
+ void discoverAgents(fetchImpl, discoveryKey, `${normalizedBaseUrl}/api/v1/agents`, {
11512
+ headers: scopedHeaders(requestHeaders, storageScope)
11513
+ }).then((agents2) => {
11514
+ if (cancelled) return;
11515
+ setState((previous) => ({
11516
+ discoveryKey,
11517
+ agents: agents2,
11518
+ selectedAgentTypeId: agents2.some((agent) => agent.agentTypeId === previous.selectedAgentTypeId) ? previous.selectedAgentTypeId : agents2[0]?.agentTypeId,
11519
+ loading: false,
11520
+ error: void 0
11521
+ }));
11522
+ }).catch((error) => {
11523
+ if (cancelled) return;
11524
+ setState({
11525
+ discoveryKey,
11526
+ agents: [],
11527
+ selectedAgentTypeId: void 0,
11528
+ loading: false,
11529
+ error: error instanceof Error ? error : new Error(String(error))
11530
+ });
11531
+ });
11532
+ return () => {
11533
+ cancelled = true;
11534
+ };
11535
+ }, [discoveryKey, enabled, fetchImpl, normalizedBaseUrl]);
11536
+ const current = state.discoveryKey === discoveryKey;
11537
+ const agents = enabled && current ? state.agents : [];
11538
+ const selectedAgentTypeId = enabled && current ? state.selectedAgentTypeId : void 0;
11539
+ const selectAgentTypeId = useCallback19((agentTypeId) => {
11540
+ setState((previous) => {
11541
+ if (previous.discoveryKey !== discoveryKey) return previous;
11542
+ if (!previous.agents.some((agent) => agent.agentTypeId === agentTypeId)) return previous;
11543
+ return { ...previous, selectedAgentTypeId: agentTypeId };
11544
+ });
11545
+ }, [discoveryKey]);
11546
+ return {
11547
+ agents,
11548
+ selectedAgentTypeId,
11549
+ loading: enabled && (!current || state.loading),
11550
+ error: enabled && current ? state.error : void 0,
11551
+ selectAgentTypeId
11552
+ };
11553
+ }
11554
+ function discoverAgents(fetchImpl, discoveryKey, url, init) {
11555
+ let requests = inFlightDiscoveries.get(fetchImpl);
11556
+ if (!requests) {
11557
+ requests = /* @__PURE__ */ new Map();
11558
+ inFlightDiscoveries.set(fetchImpl, requests);
11559
+ }
11560
+ const existing = requests.get(discoveryKey);
11561
+ if (existing) return existing;
11562
+ const request = fetchImpl(url, init).then(async (response) => {
11563
+ if (!response.ok) throw await gatewayResponseError(response, "Failed to load agents.", "load agents");
11564
+ return parseAgentOptions(await response.json());
11565
+ });
11566
+ requests.set(discoveryKey, request);
11567
+ void request.finally(() => {
11568
+ if (requests?.get(discoveryKey) === request) requests.delete(discoveryKey);
11569
+ }).catch(() => void 0);
11570
+ return request;
11571
+ }
11572
+ function parseAgentOptions(value) {
11573
+ if (!Array.isArray(value)) throw new Error("Failed to load agents: invalid response");
11574
+ return value.map((item) => {
11575
+ if (typeof item !== "object" || item === null) throw new Error("Failed to load agents: invalid response");
11576
+ const record = item;
11577
+ if (typeof record.agentTypeId !== "string" || record.agentTypeId.length === 0) {
11578
+ throw new Error("Failed to load agents: invalid response");
11579
+ }
11580
+ return {
11581
+ agentTypeId: record.agentTypeId,
11582
+ label: typeof record.label === "string" && record.label.length > 0 ? record.label : record.agentTypeId,
11583
+ ...typeof record.description === "string" ? { description: record.description } : {},
11584
+ ...Array.isArray(record.pluginIds) ? { pluginIds: record.pluginIds.filter((pluginId) => typeof pluginId === "string" && pluginId.length > 0) } : {}
11585
+ };
11586
+ });
11587
+ }
11588
+ function scopedHeaders(headers, storageScope) {
11589
+ const result = {};
11590
+ for (const [key, value] of Object.entries(headers ?? {})) {
11591
+ if (typeof value === "string") result[key] = value;
11592
+ }
11593
+ const hasStorageScope = Object.keys(result).some((key) => key.toLowerCase() === "x-boring-storage-scope");
11594
+ if (storageScope && !hasStorageScope) result["x-boring-storage-scope"] = storageScope;
11595
+ return result;
11596
+ }
11597
+
11108
11598
  // src/front/commands.ts
11109
11599
  var AGENT_COMMAND_SOURCE = "agent";
11110
11600
  function getAgentCommands(options = {}) {
@@ -11150,6 +11640,7 @@ export {
11150
11640
  ConversationContent,
11151
11641
  ConversationScrollButton,
11152
11642
  DebugDrawer,
11643
+ GatewayResponseError,
11153
11644
  Message,
11154
11645
  MessageAction,
11155
11646
  MessageActions,
@@ -11165,6 +11656,7 @@ export {
11165
11656
  PromptInputFooter,
11166
11657
  PromptInputSubmit,
11167
11658
  PromptInputTextarea,
11659
+ RUNTIME_SCOPE_MISMATCH_MESSAGE,
11168
11660
  Reasoning,
11169
11661
  ReasoningContent,
11170
11662
  ReasoningTrigger,
@@ -11175,12 +11667,15 @@ export {
11175
11667
  ToolHeader,
11176
11668
  activeSessionStorageKey,
11177
11669
  builtinCommands,
11178
- clearActiveSessionId,
11179
11670
  cn,
11180
11671
  createCommandRegistry,
11181
11672
  defaultChatSuggestions,
11182
11673
  defaultToolRenderers,
11674
+ errorResponseCode,
11675
+ gatewayResponseError,
11676
+ gatewayResponseErrorFromBody,
11183
11677
  getAgentCommands,
11678
+ isRuntimeScopeMismatchError,
11184
11679
  matchPiSessionSearch,
11185
11680
  mergeShadcnToolRenderers,
11186
11681
  mergeToolRenderers,
@@ -11190,6 +11685,7 @@ export {
11190
11685
  resolveToolRenderer,
11191
11686
  searchPiSessions,
11192
11687
  uploadFile,
11688
+ useAddressedAgentSelection,
11193
11689
  useOpenArtifact,
11194
11690
  usePiSessions,
11195
11691
  writeActiveSessionId