@hachej/boring-agent 0.1.95 → 0.1.96
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.
- package/dist/DebugDrawer-2TO5SE2D.js +7 -0
- package/dist/{chunk-NITBKFG6.js → chunk-3OEDBRKY.js} +93 -58
- package/dist/{chunk-BOIV2I3N.js → chunk-46HJK2ES.js} +31 -16
- package/dist/{chunk-AQBXNPMD.js → chunk-4IQ4HHBG.js} +4 -0
- package/dist/{chunk-OFQ7J6SI.js → chunk-LSO2AO6Q.js} +57 -1
- package/dist/{chunk-2EMAKBVR.js → chunk-NSE2RHU3.js} +8967 -7740
- package/dist/{chunk-2GBHYJVC.js → chunk-VXF5DWS6.js} +34 -0
- package/dist/{chunk-2TMS2RU2.js → chunk-WC5KBGP2.js} +518 -165
- package/dist/chunk-YGRQ4Q5P.js +20 -0
- package/dist/core/index.d.ts +5 -5
- package/dist/core/index.js +604 -7
- package/dist/{createHarness-UCD74XXK.js → createHarness-PW3A2MRB.js} +3 -3
- package/dist/{errors-G6T6S7PQ.d.ts → errors-cNC4HMpV.d.ts} +14 -7
- package/dist/eval/index.d.ts +5 -3
- package/dist/eval/index.js +31 -23
- package/dist/file-search-QxnanNjO.d.ts +87 -0
- package/dist/front/index.d.ts +84 -16
- package/dist/front/index.js +832 -339
- package/dist/front/styles.css +1171 -2129
- package/dist/{harness-Cc48o6uS.d.ts → harness-DS8jLKIx.d.ts} +4 -2
- package/dist/{lease-CusB-uLg.d.ts → lease-D8WC6pQl.d.ts} +10 -89
- package/dist/{middleware-CzW8n7B7.d.ts → middleware-Cq_cYHjJ.d.ts} +1 -1
- package/dist/piChatEvent-B3ehh9V0.d.ts +384 -0
- package/dist/{piChatSessionService-C0KbcGw9.d.ts → piChatSessionService-xnv8rcdh.d.ts} +1 -1
- package/dist/{sandbox-DAD8cWQR.d.ts → sandbox-KQiEdz4W.d.ts} +12 -1
- package/dist/server/agent-host/testing/compositionRouteProof.d.ts +8 -0
- package/dist/server/agent-host/testing/compositionRouteProof.js +78 -0
- package/dist/server/agent-host/testing/gatewayConformance.d.ts +6 -6
- package/dist/server/agent-host/testing/gatewayConformance.js +17 -9
- package/dist/server/index.d.ts +569 -370
- package/dist/server/index.js +69 -17
- package/dist/server/worker/index.d.ts +2 -2
- package/dist/server/worker/index.js +9 -9
- package/dist/shared/index.d.ts +212 -56
- package/dist/shared/index.js +43 -7
- package/dist/{types-C-Eroi__.d.ts → types-D_SD-7_l.d.ts} +304 -87
- package/docs/AGENT_GATEWAY_V0.md +6 -3
- package/docs/AGENT_PACKAGES.md +80 -0
- package/docs/ERROR_CODES.md +58 -0
- package/docs/README.md +1 -0
- package/docs/runtime.md +19 -2
- package/package.json +34 -29
- package/dist/DebugDrawer-RYEBQ6CO.js +0 -6
- package/dist/chunk-EOMY2HDT.js +0 -619
- package/dist/piChatEvent-BsMp3MSo.d.ts +0 -368
package/dist/front/index.js
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
2
|
DebugDrawer,
|
|
3
|
+
agentResourceUrl,
|
|
3
4
|
cn,
|
|
4
|
-
copyTextToClipboard
|
|
5
|
-
|
|
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-
|
|
23
|
+
} from "../chunk-LSO2AO6Q.js";
|
|
24
|
+
import "../chunk-YGRQ4Q5P.js";
|
|
19
25
|
import {
|
|
20
26
|
ErrorCode
|
|
21
|
-
} from "../chunk-
|
|
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 =
|
|
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?.
|
|
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
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
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,
|
|
2665
|
-
headers:
|
|
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,
|
|
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
|
|
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
|
|
2784
|
-
const
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
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
|
-
|
|
2791
|
-
for (const name of
|
|
2792
|
-
|
|
2793
|
-
|
|
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 (
|
|
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/
|
|
2803
|
-
const headers =
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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/
|
|
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,
|
|
@@ -4548,6 +4556,7 @@ var RemotePiSession = class {
|
|
|
4548
4556
|
streamRunId = 0;
|
|
4549
4557
|
reconnectAttempt = 0;
|
|
4550
4558
|
started = false;
|
|
4559
|
+
suspended = false;
|
|
4551
4560
|
disposed = false;
|
|
4552
4561
|
streamAbortController;
|
|
4553
4562
|
reconnectTimer;
|
|
@@ -4581,6 +4590,7 @@ var RemotePiSession = class {
|
|
|
4581
4590
|
streamingMessageCount: state.streamingMessage ? 1 : 0
|
|
4582
4591
|
},
|
|
4583
4592
|
disposed: this.disposed,
|
|
4593
|
+
suspended: this.suspended,
|
|
4584
4594
|
generation: this.generation,
|
|
4585
4595
|
streamRunId: this.streamRunId,
|
|
4586
4596
|
reconnectAttempt: this.reconnectAttempt,
|
|
@@ -4592,7 +4602,7 @@ var RemotePiSession = class {
|
|
|
4592
4602
|
return this.store.subscribe(listener);
|
|
4593
4603
|
}
|
|
4594
4604
|
start(cursor) {
|
|
4595
|
-
if (this.disposed || this.started) return Promise.resolve();
|
|
4605
|
+
if (this.disposed || this.suspended || this.started) return Promise.resolve();
|
|
4596
4606
|
this.started = true;
|
|
4597
4607
|
const generation = this.generation;
|
|
4598
4608
|
if (cursor === void 0) {
|
|
@@ -4607,8 +4617,8 @@ var RemotePiSession = class {
|
|
|
4607
4617
|
if (!this.disposed) {
|
|
4608
4618
|
this.store.dispatch({ type: "optimistic-user-message", message: toOptimisticUserMessage(payload) }, { flush: true });
|
|
4609
4619
|
}
|
|
4610
|
-
if (!this.started) await this.start(this.store.getState().lastSeq);
|
|
4611
|
-
else this.ensureReconnectScheduled();
|
|
4620
|
+
if (!this.started && !this.suspended) await this.start(this.store.getState().lastSeq);
|
|
4621
|
+
else if (!this.suspended) this.ensureReconnectScheduled();
|
|
4612
4622
|
try {
|
|
4613
4623
|
const receipt = await this.postCommand("/prompt", payload, PromptReceiptSchema);
|
|
4614
4624
|
return receipt;
|
|
@@ -4621,8 +4631,8 @@ var RemotePiSession = class {
|
|
|
4621
4631
|
if (!this.disposed) {
|
|
4622
4632
|
this.store.dispatch({ type: "optimistic-user-message", message: toOptimisticUserMessage(payload) }, { flush: true });
|
|
4623
4633
|
}
|
|
4624
|
-
if (!this.started) await this.start(this.store.getState().lastSeq);
|
|
4625
|
-
else this.ensureReconnectScheduled();
|
|
4634
|
+
if (!this.started && !this.suspended) await this.start(this.store.getState().lastSeq);
|
|
4635
|
+
else if (!this.suspended) this.ensureReconnectScheduled();
|
|
4626
4636
|
try {
|
|
4627
4637
|
const receipt = await this.postCommand("/followup", payload, FollowUpReceiptSchema);
|
|
4628
4638
|
return receipt;
|
|
@@ -4648,6 +4658,28 @@ var RemotePiSession = class {
|
|
|
4648
4658
|
}
|
|
4649
4659
|
return receipt;
|
|
4650
4660
|
}
|
|
4661
|
+
suspendStream() {
|
|
4662
|
+
if (this.disposed || this.suspended) return;
|
|
4663
|
+
this.suspended = true;
|
|
4664
|
+
this.started = false;
|
|
4665
|
+
this.streamRunId += 1;
|
|
4666
|
+
this.clearReconnectTimer();
|
|
4667
|
+
this.abortEventStream();
|
|
4668
|
+
this.store.dispatch({ type: "connection-state", state: "suspended" }, { flush: true });
|
|
4669
|
+
}
|
|
4670
|
+
resumeStream() {
|
|
4671
|
+
if (this.disposed || !this.suspended && this.started) return;
|
|
4672
|
+
this.suspended = false;
|
|
4673
|
+
this.started = true;
|
|
4674
|
+
const generation = this.generation;
|
|
4675
|
+
const state = this.store.getState();
|
|
4676
|
+
if (!state.hydrated) {
|
|
4677
|
+
void this.hydrateAndConnect(generation);
|
|
4678
|
+
return;
|
|
4679
|
+
}
|
|
4680
|
+
this.store.dispatch({ type: "connection-state", state: "connecting" }, { flush: true });
|
|
4681
|
+
void this.connectEvents(state.lastSeq, generation);
|
|
4682
|
+
}
|
|
4651
4683
|
dispose() {
|
|
4652
4684
|
if (this.disposed) return;
|
|
4653
4685
|
this.disposed = true;
|
|
@@ -4661,33 +4693,34 @@ var RemotePiSession = class {
|
|
|
4661
4693
|
this.store.dispose();
|
|
4662
4694
|
}
|
|
4663
4695
|
async hydrateAndConnect(generation, options = {}) {
|
|
4664
|
-
|
|
4696
|
+
const hydrationRunId = this.streamRunId;
|
|
4697
|
+
if (!this.isHydrationActive(generation, hydrationRunId)) return;
|
|
4665
4698
|
this.clearReconnectTimer();
|
|
4666
4699
|
this.abortEventStream();
|
|
4667
4700
|
this.store.dispatch({ type: "connection-state", state: this.store.getState().hydrated ? "reconnecting" : "connecting" }, { flush: true });
|
|
4668
4701
|
try {
|
|
4669
4702
|
const headers = await this.requestHeaders();
|
|
4670
|
-
if (!this.
|
|
4703
|
+
if (!this.isHydrationActive(generation, hydrationRunId)) return;
|
|
4671
4704
|
const responseBody = await this.fetchJson(this.stateUrl(), { method: "GET", headers });
|
|
4672
|
-
if (!this.
|
|
4673
|
-
const raw =
|
|
4705
|
+
if (!this.isHydrationActive(generation, hydrationRunId)) return;
|
|
4706
|
+
const raw = addressedSnapshot(responseBody);
|
|
4674
4707
|
this.recordLargeStateWarning(raw);
|
|
4675
4708
|
if (readProtocolVersion(raw) !== SUPPORTED_PROTOCOL_VERSION) {
|
|
4676
4709
|
this.dispatchProtocolError(`Unsupported Pi chat protocol version: ${String(readProtocolVersion(raw) ?? "missing")}`);
|
|
4677
4710
|
return;
|
|
4678
4711
|
}
|
|
4679
4712
|
const snapshot = PiChatSnapshotSchema.parse(raw);
|
|
4680
|
-
if (!this.
|
|
4713
|
+
if (!this.isHydrationActive(generation, hydrationRunId)) return;
|
|
4681
4714
|
this.store.dispatch({ type: "hydrate", snapshot, allowSeqRewind: options?.allowSeqRewind }, { flush: true });
|
|
4682
4715
|
this.connectEvents(snapshot.seq, generation);
|
|
4683
4716
|
} catch (error) {
|
|
4684
|
-
if (!this.
|
|
4717
|
+
if (!this.isHydrationActive(generation, hydrationRunId) || isAbortError(error)) return;
|
|
4685
4718
|
this.dispatchProtocolError(errorMessage(error, "Failed to hydrate Pi chat session state."));
|
|
4686
4719
|
this.scheduleReconnect(generation);
|
|
4687
4720
|
}
|
|
4688
4721
|
}
|
|
4689
4722
|
connectEvents(cursor, generation) {
|
|
4690
|
-
if (!this.
|
|
4723
|
+
if (!this.isStreamLifecycleActive(generation)) return Promise.resolve();
|
|
4691
4724
|
this.clearReconnectTimer();
|
|
4692
4725
|
this.abortEventStream();
|
|
4693
4726
|
const runId = ++this.streamRunId;
|
|
@@ -4801,20 +4834,20 @@ var RemotePiSession = class {
|
|
|
4801
4834
|
}
|
|
4802
4835
|
}
|
|
4803
4836
|
rehydrateAfterStreamReset(generation, options = {}) {
|
|
4804
|
-
if (!this.
|
|
4837
|
+
if (!this.isStreamLifecycleActive(generation)) return;
|
|
4805
4838
|
this.streamRunId += 1;
|
|
4806
4839
|
this.abortEventStream();
|
|
4807
4840
|
void this.hydrateAndConnect(generation, options);
|
|
4808
4841
|
}
|
|
4809
4842
|
ensureReconnectScheduled() {
|
|
4810
|
-
if (!this.
|
|
4843
|
+
if (!this.isStreamLifecycleActive(this.generation)) return;
|
|
4811
4844
|
const state = this.store.getState();
|
|
4812
4845
|
if (state.connection.state === "connected" || state.connection.state === "connecting") return;
|
|
4813
4846
|
if (this.reconnectTimer !== void 0) return;
|
|
4814
4847
|
void this.hydrateAndConnect(this.generation);
|
|
4815
4848
|
}
|
|
4816
4849
|
scheduleReconnect(generation) {
|
|
4817
|
-
if (!this.
|
|
4850
|
+
if (!this.isStreamLifecycleActive(generation)) return;
|
|
4818
4851
|
this.clearReconnectTimer();
|
|
4819
4852
|
this.store.dispatch({ type: "connection-state", state: "reconnecting" }, { flush: true });
|
|
4820
4853
|
const attempt = this.reconnectAttempt++;
|
|
@@ -4828,7 +4861,7 @@ var RemotePiSession = class {
|
|
|
4828
4861
|
clearTimeoutFn: this.clearTimeoutFn,
|
|
4829
4862
|
reconnect: () => {
|
|
4830
4863
|
this.reconnectTimer = void 0;
|
|
4831
|
-
if (!this.
|
|
4864
|
+
if (!this.isStreamLifecycleActive(generation)) return;
|
|
4832
4865
|
const state = this.store.getState();
|
|
4833
4866
|
if (state.hydrated) this.connectEvents(state.lastSeq, generation);
|
|
4834
4867
|
else void this.hydrateAndConnect(generation);
|
|
@@ -4851,7 +4884,7 @@ var RemotePiSession = class {
|
|
|
4851
4884
|
return schema.parse(this.addressedCommandReceipt(path, raw));
|
|
4852
4885
|
}
|
|
4853
4886
|
addressedCommandReceipt(path, receipt) {
|
|
4854
|
-
if (
|
|
4887
|
+
if (path !== "/followup" || typeof receipt !== "object" || receipt === null) {
|
|
4855
4888
|
return receipt;
|
|
4856
4889
|
}
|
|
4857
4890
|
const record = receipt;
|
|
@@ -4897,19 +4930,18 @@ var RemotePiSession = class {
|
|
|
4897
4930
|
return this.sessionUrl("/state");
|
|
4898
4931
|
}
|
|
4899
4932
|
eventsUrl(cursor) {
|
|
4900
|
-
|
|
4901
|
-
|
|
4902
|
-
|
|
4903
|
-
|
|
4933
|
+
return buildPiChatEventsUrl({
|
|
4934
|
+
apiBaseUrl: this.apiBaseUrl,
|
|
4935
|
+
agentTypeId: this.options.agentTypeId,
|
|
4936
|
+
sessionId: this.options.sessionId,
|
|
4937
|
+
cursor
|
|
4938
|
+
});
|
|
4904
4939
|
}
|
|
4905
4940
|
sessionUrl(path) {
|
|
4906
|
-
|
|
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}`;
|
|
4941
|
+
return `${this.apiBaseUrl}/api/v1/agents/${encodeURIComponent(this.options.agentTypeId)}/sessions/${encodeURIComponent(this.options.sessionId)}${path}`;
|
|
4910
4942
|
}
|
|
4911
4943
|
addressedCommandPayload(path, payload) {
|
|
4912
|
-
if (
|
|
4944
|
+
if (typeof payload !== "object" || payload === null) return payload;
|
|
4913
4945
|
const record = payload;
|
|
4914
4946
|
if (typeof record.requestId === "string" && record.requestId.length > 0) return payload;
|
|
4915
4947
|
if (path === "/prompt" && typeof record.clientNonce === "string" && typeof record.message === "string") {
|
|
@@ -4972,8 +5004,14 @@ var RemotePiSession = class {
|
|
|
4972
5004
|
isGenerationActive(generation) {
|
|
4973
5005
|
return !this.disposed && generation === this.generation;
|
|
4974
5006
|
}
|
|
5007
|
+
isStreamLifecycleActive(generation) {
|
|
5008
|
+
return !this.suspended && this.isGenerationActive(generation);
|
|
5009
|
+
}
|
|
5010
|
+
isHydrationActive(generation, runId) {
|
|
5011
|
+
return this.isStreamLifecycleActive(generation) && runId === this.streamRunId;
|
|
5012
|
+
}
|
|
4975
5013
|
isStreamActive(generation, runId) {
|
|
4976
|
-
return this.
|
|
5014
|
+
return this.isStreamLifecycleActive(generation) && runId === this.streamRunId;
|
|
4977
5015
|
}
|
|
4978
5016
|
};
|
|
4979
5017
|
function addressedSnapshot(value) {
|
|
@@ -5100,13 +5138,23 @@ function clearPageUnloading() {
|
|
|
5100
5138
|
pageUnloading = false;
|
|
5101
5139
|
}
|
|
5102
5140
|
|
|
5103
|
-
// src/front/chat/session/
|
|
5141
|
+
// src/front/chat/session/sessionSelectionStorage.ts
|
|
5104
5142
|
var ACTIVE_SESSION_KEY_PREFIX = "boring-agent:v2";
|
|
5105
5143
|
var ACTIVE_SESSION_KEY_SUFFIX = "activeSessionId";
|
|
5144
|
+
var BOOT_RESUME_SESSION_KEY_SUFFIX = "bootResumeSessionId";
|
|
5106
5145
|
var DEFAULT_STORAGE_SCOPE2 = "default";
|
|
5107
5146
|
function activeSessionStorageKey(storageScope) {
|
|
5108
|
-
|
|
5109
|
-
|
|
5147
|
+
return scopedSessionStorageKey(storageScope, ACTIVE_SESSION_KEY_SUFFIX);
|
|
5148
|
+
}
|
|
5149
|
+
function bootResumeSessionStorageKey(source) {
|
|
5150
|
+
const scope = typeof source === "object" && source !== null ? encodeURIComponent(JSON.stringify([
|
|
5151
|
+
source.apiBaseUrl?.replace(/\/$/, "") ?? "",
|
|
5152
|
+
source.sessionsApiPath,
|
|
5153
|
+
source.agentTypeId ?? "",
|
|
5154
|
+
source.workspaceId ?? "",
|
|
5155
|
+
source.storageScope ?? DEFAULT_STORAGE_SCOPE2
|
|
5156
|
+
])) : source;
|
|
5157
|
+
return scopedSessionStorageKey(scope, BOOT_RESUME_SESSION_KEY_SUFFIX);
|
|
5110
5158
|
}
|
|
5111
5159
|
function readActiveSessionId(options = {}) {
|
|
5112
5160
|
const storage = resolveStorage2(options.storage);
|
|
@@ -5127,8 +5175,28 @@ function writeActiveSessionId(sessionId, options = {}) {
|
|
|
5127
5175
|
} catch {
|
|
5128
5176
|
}
|
|
5129
5177
|
}
|
|
5130
|
-
function
|
|
5131
|
-
|
|
5178
|
+
function readBootResumeSessionId(options = {}) {
|
|
5179
|
+
const storage = resolveBootStorage(options.storage);
|
|
5180
|
+
if (!storage) return void 0;
|
|
5181
|
+
try {
|
|
5182
|
+
return storage.getItem(bootResumeSessionStorageKey(options.bootResumeSource ?? options.storageScope)) ?? void 0;
|
|
5183
|
+
} catch {
|
|
5184
|
+
return void 0;
|
|
5185
|
+
}
|
|
5186
|
+
}
|
|
5187
|
+
function writeBootResumeSessionId(sessionId, options = {}) {
|
|
5188
|
+
const storage = resolveBootStorage(options.storage);
|
|
5189
|
+
if (!storage) return;
|
|
5190
|
+
try {
|
|
5191
|
+
const key = bootResumeSessionStorageKey(options.bootResumeSource ?? options.storageScope);
|
|
5192
|
+
if (sessionId === void 0 || sessionId.length === 0) storage.removeItem(key);
|
|
5193
|
+
else storage.setItem(key, sessionId);
|
|
5194
|
+
} catch {
|
|
5195
|
+
}
|
|
5196
|
+
}
|
|
5197
|
+
function scopedSessionStorageKey(storageScope, suffix) {
|
|
5198
|
+
const scope = storageScope && storageScope.length > 0 ? storageScope : DEFAULT_STORAGE_SCOPE2;
|
|
5199
|
+
return `${ACTIVE_SESSION_KEY_PREFIX}:${scope}:${suffix}`;
|
|
5132
5200
|
}
|
|
5133
5201
|
function resolveStorage2(storage) {
|
|
5134
5202
|
if (storage) return storage;
|
|
@@ -5138,29 +5206,43 @@ function resolveStorage2(storage) {
|
|
|
5138
5206
|
return void 0;
|
|
5139
5207
|
}
|
|
5140
5208
|
}
|
|
5209
|
+
function resolveBootStorage(storage) {
|
|
5210
|
+
if (storage) return storage;
|
|
5211
|
+
try {
|
|
5212
|
+
return globalThis.sessionStorage;
|
|
5213
|
+
} catch {
|
|
5214
|
+
return void 0;
|
|
5215
|
+
}
|
|
5216
|
+
}
|
|
5141
5217
|
|
|
5142
5218
|
// 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";
|
|
5219
|
+
import { useCallback as useCallback7, useEffect as useEffect8, useLayoutEffect, useMemo as useMemo4, useRef as useRef8, useState as useState10 } from "react";
|
|
5145
5220
|
var SESSION_PAGE_SIZE = 50;
|
|
5146
5221
|
var DEFAULT_MAX_RETRIES = 60;
|
|
5147
5222
|
var DEFAULT_RETRY_BASE_MS = 250;
|
|
5148
5223
|
var DEFAULT_RETRY_MAX_MS = 2e3;
|
|
5224
|
+
var useIsomorphicLayoutEffect = typeof window === "undefined" ? useEffect8 : useLayoutEffect;
|
|
5149
5225
|
var SessionsPreparingError = class extends Error {
|
|
5150
5226
|
constructor() {
|
|
5151
5227
|
super("Agent runtime is still preparing");
|
|
5152
5228
|
this.name = "SessionsPreparingError";
|
|
5153
5229
|
}
|
|
5154
5230
|
};
|
|
5231
|
+
var StaleSessionsSourceError = class extends Error {
|
|
5232
|
+
constructor() {
|
|
5233
|
+
super("Pi sessions source is stale");
|
|
5234
|
+
this.name = "StaleSessionsSourceError";
|
|
5235
|
+
}
|
|
5236
|
+
};
|
|
5155
5237
|
function isNetworkFetchError(error) {
|
|
5156
5238
|
return error instanceof TypeError;
|
|
5157
5239
|
}
|
|
5158
|
-
function usePiSessions(options
|
|
5240
|
+
function usePiSessions(options) {
|
|
5159
5241
|
const enabled = options.enabled ?? true;
|
|
5160
5242
|
const apiBaseUrl = options.apiBaseUrl?.replace(/\/$/, "") ?? "";
|
|
5161
|
-
const
|
|
5162
|
-
const sessionsApiPath = options.sessionsApiPath ?? (addressed ? `/api/v1/agents/${encodeURIComponent(options.agentTypeId)}/sessions` : DEFAULT_SESSIONS_API_PATH);
|
|
5243
|
+
const sessionsApiPath = options.sessionsApiPath ?? `/api/v1/agents/${encodeURIComponent(options.agentTypeId)}/sessions`;
|
|
5163
5244
|
const storageScope = options.storageScope ?? "default";
|
|
5245
|
+
const activeSessionStorageScope = options.activeSessionStorageScope ?? storageScope;
|
|
5164
5246
|
const fetchImpl = useMemo4(() => options.fetch ?? globalThis.fetch.bind(globalThis), [options.fetch]);
|
|
5165
5247
|
const createRemoteSession = options.createRemoteSession ?? createRemotePiSession;
|
|
5166
5248
|
const connectActiveSession = options.connectActiveSession ?? true;
|
|
@@ -5168,12 +5250,30 @@ function usePiSessions(options = {}) {
|
|
|
5168
5250
|
const retryBaseMs = options.retry?.baseMs ?? DEFAULT_RETRY_BASE_MS;
|
|
5169
5251
|
const retryMaxMs = options.retry?.maxMs ?? DEFAULT_RETRY_MAX_MS;
|
|
5170
5252
|
const headersKey = useMemo4(() => headersScopeKey(options.requestHeaders, storageScope), [options.requestHeaders, storageScope]);
|
|
5171
|
-
const normalizedHeaders = useMemo4(() =>
|
|
5172
|
-
const requestScopeKey = useMemo4(
|
|
5173
|
-
|
|
5253
|
+
const normalizedHeaders = useMemo4(() => withStorageScope(options.requestHeaders, storageScope) ?? {}, [headersKey, storageScope]);
|
|
5254
|
+
const requestScopeKey = useMemo4(
|
|
5255
|
+
() => requestScopeIdentity(apiBaseUrl, sessionsApiPath, options.agentTypeId, storageScope, headersKey, options.workspaceId, options.sourceIdentity),
|
|
5256
|
+
[apiBaseUrl, headersKey, options.agentTypeId, options.sourceIdentity, options.workspaceId, sessionsApiPath, storageScope]
|
|
5257
|
+
);
|
|
5258
|
+
const dataSourceKey = useMemo4(
|
|
5259
|
+
() => dataSourceIdentity(apiBaseUrl, sessionsApiPath, options.agentTypeId, storageScope, options.workspaceId, options.sourceIdentity),
|
|
5260
|
+
[apiBaseUrl, options.agentTypeId, options.sourceIdentity, options.workspaceId, sessionsApiPath, storageScope]
|
|
5261
|
+
);
|
|
5262
|
+
const bootResumeSource = useMemo4(() => ({
|
|
5263
|
+
apiBaseUrl,
|
|
5264
|
+
sessionsApiPath,
|
|
5265
|
+
agentTypeId: options.agentTypeId,
|
|
5266
|
+
workspaceId: options.workspaceId,
|
|
5267
|
+
storageScope
|
|
5268
|
+
}), [apiBaseUrl, options.agentTypeId, options.workspaceId, sessionsApiPath, storageScope]);
|
|
5174
5269
|
const [sessions, setSessions] = useState10([]);
|
|
5175
5270
|
const [dataStorageScope, setDataStorageScope] = useState10(storageScope);
|
|
5176
|
-
const [activeSessionId, setActiveSessionId] = useState10(
|
|
5271
|
+
const [activeSessionId, setActiveSessionId] = useState10(void 0);
|
|
5272
|
+
const [resumeSessionState, setResumeSessionState] = useState10(() => ({
|
|
5273
|
+
dataSourceKey,
|
|
5274
|
+
id: readBootResumeSessionId({ bootResumeSource, storage: options.bootResumeStorage })
|
|
5275
|
+
}));
|
|
5276
|
+
const resumeSessionId = resumeSessionState.dataSourceKey === dataSourceKey ? resumeSessionState.id : void 0;
|
|
5177
5277
|
const [activePiSession, setActivePiSession] = useState10(void 0);
|
|
5178
5278
|
const [loading, setLoading] = useState10(enabled);
|
|
5179
5279
|
const [loadingMore, setLoadingMore] = useState10(false);
|
|
@@ -5190,17 +5290,30 @@ function usePiSessions(options = {}) {
|
|
|
5190
5290
|
const loadMoreRequestSeqRef = useRef8(0);
|
|
5191
5291
|
const loadMoreInFlightRef = useRef8(false);
|
|
5192
5292
|
const pendingCreatedRef = useRef8(/* @__PURE__ */ new Map());
|
|
5293
|
+
const pendingDeletedRef = useRef8(/* @__PURE__ */ new Set());
|
|
5193
5294
|
const pendingCreatedScopeRef = useRef8(requestScopeKey);
|
|
5194
5295
|
const dataStorageScopeRef = useRef8(storageScope);
|
|
5195
5296
|
const loadedDataSourceRef = useRef8(dataSourceKey);
|
|
5196
|
-
const
|
|
5197
|
-
|
|
5297
|
+
const committedRequestScopeRef = useRef8(requestScopeKey);
|
|
5298
|
+
useIsomorphicLayoutEffect(() => {
|
|
5299
|
+
committedRequestScopeRef.current = requestScopeKey;
|
|
5300
|
+
}, [requestScopeKey]);
|
|
5301
|
+
const sourceIsCurrent = useCallback7(
|
|
5302
|
+
(scope) => mountedRef.current && committedRequestScopeRef.current === scope,
|
|
5303
|
+
[]
|
|
5304
|
+
);
|
|
5198
5305
|
const remoteSessionOptionsRef = useRef8(options.remoteSessionOptions);
|
|
5199
5306
|
remoteSessionOptionsRef.current = options.remoteSessionOptions;
|
|
5200
5307
|
const remoteSessionOptionsKey = useMemo4(
|
|
5201
5308
|
() => remoteSessionOptionsIdentity(options.remoteSessionOptions),
|
|
5202
5309
|
[options.remoteSessionOptions]
|
|
5203
5310
|
);
|
|
5311
|
+
useIsomorphicLayoutEffect(() => {
|
|
5312
|
+
setResumeSessionState((current) => current.dataSourceKey === dataSourceKey ? current : {
|
|
5313
|
+
dataSourceKey,
|
|
5314
|
+
id: readBootResumeSessionId({ bootResumeSource, storage: options.bootResumeStorage })
|
|
5315
|
+
});
|
|
5316
|
+
}, [bootResumeSource, dataSourceKey, options.bootResumeStorage]);
|
|
5204
5317
|
useEffect8(() => {
|
|
5205
5318
|
sessionsRef.current = sessions;
|
|
5206
5319
|
}, [sessions]);
|
|
@@ -5213,48 +5326,66 @@ function usePiSessions(options = {}) {
|
|
|
5213
5326
|
const activeSessionKnown = Boolean(activeSessionId && sessions.some((session) => session.id === activeSessionId));
|
|
5214
5327
|
const requestHeaders = useCallback7(() => normalizedHeaders, [normalizedHeaders]);
|
|
5215
5328
|
const sessionsUrl = useCallback7((suffix = "") => `${apiBaseUrl}${sessionsApiPath}${suffix}`, [apiBaseUrl, sessionsApiPath]);
|
|
5216
|
-
const sessionsListUrl = useCallback7((
|
|
5217
|
-
const query = new URLSearchParams();
|
|
5218
|
-
if (
|
|
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();
|
|
5329
|
+
const sessionsListUrl = useCallback7((cursor) => {
|
|
5330
|
+
const query = new URLSearchParams({ limit: String(SESSION_PAGE_SIZE) });
|
|
5331
|
+
if (cursor) query.set("cursor", cursor);
|
|
5229
5332
|
return sessionsUrl(`?${query.toString()}`);
|
|
5230
|
-
}, [
|
|
5333
|
+
}, [sessionsUrl]);
|
|
5231
5334
|
const persistActive = useCallback7((id) => {
|
|
5232
|
-
writeActiveSessionId(id, { storageScope, storage: options.storage });
|
|
5233
|
-
}, [options.storage
|
|
5335
|
+
writeActiveSessionId(id, { storageScope: activeSessionStorageScope, storage: options.storage });
|
|
5336
|
+
}, [activeSessionStorageScope, options.storage]);
|
|
5337
|
+
const persistBootResume = useCallback7((id) => {
|
|
5338
|
+
writeBootResumeSessionId(id, { bootResumeSource, storage: options.bootResumeStorage });
|
|
5339
|
+
setResumeSessionState({ dataSourceKey, id });
|
|
5340
|
+
}, [bootResumeSource, dataSourceKey, options.bootResumeStorage]);
|
|
5341
|
+
const rememberConfirmedEmptySession = useCallback7((id) => {
|
|
5342
|
+
writeBootResumeSessionId(id, { bootResumeSource, storage: options.bootResumeStorage });
|
|
5343
|
+
setResumeSessionState({ dataSourceKey, id: void 0 });
|
|
5344
|
+
}, [bootResumeSource, dataSourceKey, options.bootResumeStorage]);
|
|
5234
5345
|
const ensurePendingScope = useCallback7(() => {
|
|
5235
5346
|
if (pendingCreatedScopeRef.current === requestScopeKey) return;
|
|
5236
5347
|
pendingCreatedScopeRef.current = requestScopeKey;
|
|
5237
5348
|
pendingCreatedRef.current.clear();
|
|
5349
|
+
pendingDeletedRef.current.clear();
|
|
5238
5350
|
}, [requestScopeKey]);
|
|
5239
5351
|
const preferredSessionId = useCallback7(() => {
|
|
5240
|
-
const persisted = options.initialActiveSessionId ?? readActiveSessionId({
|
|
5352
|
+
const persisted = options.initialActiveSessionId ?? readActiveSessionId({
|
|
5353
|
+
storageScope: activeSessionStorageScope,
|
|
5354
|
+
storage: options.storage
|
|
5355
|
+
});
|
|
5241
5356
|
if (loadedDataSourceRef.current === dataSourceKey) return activeSessionIdRef.current ?? persisted;
|
|
5242
5357
|
if (dataStorageScopeRef.current !== storageScope) return persisted;
|
|
5243
5358
|
return void 0;
|
|
5244
|
-
}, [dataSourceKey, options.initialActiveSessionId, options.storage, storageScope]);
|
|
5359
|
+
}, [activeSessionStorageScope, dataSourceKey, options.initialActiveSessionId, options.storage, storageScope]);
|
|
5245
5360
|
const applySessions = useCallback7((data, applyOptions = {}) => {
|
|
5246
5361
|
ensurePendingScope();
|
|
5247
5362
|
const replacingScope = loadedDataSourceRef.current !== dataSourceKey;
|
|
5248
5363
|
const requestedActiveId = preferredSessionId();
|
|
5249
5364
|
const replacingScopePreferred = replacingScope ? requestedActiveId : void 0;
|
|
5250
5365
|
const pendingCreated = pendingCreatedRef.current;
|
|
5366
|
+
const pendingDeleted = pendingDeletedRef.current;
|
|
5367
|
+
const deletedIds = new Set(pendingDeleted);
|
|
5251
5368
|
for (const session of data) pendingCreated.delete(session.id);
|
|
5252
|
-
const
|
|
5253
|
-
|
|
5369
|
+
const filteredData = data.filter((session) => !deletedIds.has(session.id));
|
|
5370
|
+
if (applyOptions.nextCursor === void 0) {
|
|
5371
|
+
const returnedIds = new Set(data.map((session) => session.id));
|
|
5372
|
+
for (const deletedId of pendingDeleted) {
|
|
5373
|
+
if (!returnedIds.has(deletedId)) pendingDeleted.delete(deletedId);
|
|
5374
|
+
}
|
|
5375
|
+
}
|
|
5376
|
+
const canonicalCount = canonicalPageCount(filteredData);
|
|
5377
|
+
const pageMayHaveMore = applyOptions.nextCursor !== void 0;
|
|
5254
5378
|
const wasExhaustedBeyondFirstPage = applyOptions.background && !hasMoreRef.current && canonicalLoadedCountRef.current >= canonicalCount;
|
|
5255
|
-
const requestedActiveReturned = Boolean(requestedActiveId &&
|
|
5379
|
+
const requestedActiveReturned = Boolean(requestedActiveId && filteredData.some((session) => session.id === requestedActiveId));
|
|
5256
5380
|
const current = applyOptions.background && pageMayHaveMore ? sessionsRef.current.filter((session) => !requestedActiveId || requestedActiveReturned || session.id !== requestedActiveId) : [];
|
|
5257
|
-
const merged = mergeSessions(Array.from(pendingCreated.values()),
|
|
5381
|
+
const merged = mergeSessions(Array.from(pendingCreated.values()), filteredData, current.filter((session) => !deletedIds.has(session.id)));
|
|
5382
|
+
const rememberedEmptyId = readBootResumeSessionId({
|
|
5383
|
+
bootResumeSource,
|
|
5384
|
+
storage: options.bootResumeStorage
|
|
5385
|
+
});
|
|
5386
|
+
if (rememberedEmptyId && filteredData.some((session) => session.id === rememberedEmptyId)) {
|
|
5387
|
+
persistBootResume(void 0);
|
|
5388
|
+
}
|
|
5258
5389
|
const nextHasMore = pageMayHaveMore && !wasExhaustedBeyondFirstPage;
|
|
5259
5390
|
canonicalLoadedCountRef.current = applyOptions.background ? Math.max(canonicalLoadedCountRef.current, canonicalCount) : canonicalCount;
|
|
5260
5391
|
loadedDataSourceRef.current = dataSourceKey;
|
|
@@ -5269,10 +5400,12 @@ function usePiSessions(options = {}) {
|
|
|
5269
5400
|
persistActive(next);
|
|
5270
5401
|
return next;
|
|
5271
5402
|
});
|
|
5272
|
-
}, [
|
|
5403
|
+
}, [bootResumeSource, dataSourceKey, ensurePendingScope, options.bootResumeStorage, persistActive, persistBootResume, preferredSessionId, storageScope]);
|
|
5273
5404
|
const refresh = useCallback7(async (refreshOptions = {}) => {
|
|
5405
|
+
const scope = requestScopeKey;
|
|
5406
|
+
if (mountedRef.current && !sourceIsCurrent(scope)) throw new StaleSessionsSourceError();
|
|
5274
5407
|
const version = ++refreshVersionRef.current;
|
|
5275
|
-
const isCurrent = () =>
|
|
5408
|
+
const isCurrent = () => sourceIsCurrent(scope) && version === refreshVersionRef.current;
|
|
5276
5409
|
clearRetryTimer(retryTimerRef);
|
|
5277
5410
|
const background = refreshOptions.background === true;
|
|
5278
5411
|
if (!enabled) {
|
|
@@ -5299,7 +5432,7 @@ function usePiSessions(options = {}) {
|
|
|
5299
5432
|
let page;
|
|
5300
5433
|
for (let attempt = 0; ; attempt += 1) {
|
|
5301
5434
|
try {
|
|
5302
|
-
page = await fetchSessionList(fetchImpl, sessionsListUrl(
|
|
5435
|
+
page = await fetchSessionList(fetchImpl, sessionsListUrl(), requestHeaders());
|
|
5303
5436
|
break;
|
|
5304
5437
|
} catch (err) {
|
|
5305
5438
|
const transient = err instanceof SessionsPreparingError || isNetworkFetchError(err);
|
|
@@ -5321,7 +5454,7 @@ function usePiSessions(options = {}) {
|
|
|
5321
5454
|
setLoading(false);
|
|
5322
5455
|
if (refreshOptions.throwOnError) throw error2;
|
|
5323
5456
|
}
|
|
5324
|
-
}, [
|
|
5457
|
+
}, [applySessions, enabled, fetchImpl, persistActive, preferredSessionId, requestHeaders, requestScopeKey, retryBaseMs, retryMaxMs, retryMaxRetries, sessionsListUrl, sourceIsCurrent]);
|
|
5325
5458
|
useEffect8(() => {
|
|
5326
5459
|
mountedRef.current = true;
|
|
5327
5460
|
void refresh();
|
|
@@ -5337,14 +5470,13 @@ function usePiSessions(options = {}) {
|
|
|
5337
5470
|
loadMoreInFlightRef.current = true;
|
|
5338
5471
|
const version = refreshVersionRef.current;
|
|
5339
5472
|
const scope = requestScopeKey;
|
|
5340
|
-
const offset = canonicalLoadedCountRef.current;
|
|
5341
5473
|
setLoadingMore(true);
|
|
5342
5474
|
try {
|
|
5343
|
-
const page = await fetchSessionList(fetchImpl, sessionsListUrl(
|
|
5344
|
-
const data = page.sessions;
|
|
5345
|
-
if (
|
|
5346
|
-
const merged = mergeSessions(sessionsRef.current, data);
|
|
5347
|
-
const nextHasMore =
|
|
5475
|
+
const page = await fetchSessionList(fetchImpl, sessionsListUrl(nextCursorRef.current), requestHeaders());
|
|
5476
|
+
const data = page.sessions.filter((session) => !pendingDeletedRef.current.has(session.id));
|
|
5477
|
+
if (requestSeq !== loadMoreRequestSeqRef.current || version !== refreshVersionRef.current || !sourceIsCurrent(scope)) return;
|
|
5478
|
+
const merged = mergeSessions(sessionsRef.current.filter((session) => !pendingDeletedRef.current.has(session.id)), data);
|
|
5479
|
+
const nextHasMore = page.nextCursor !== void 0;
|
|
5348
5480
|
canonicalLoadedCountRef.current += data.length;
|
|
5349
5481
|
nextCursorRef.current = page.nextCursor;
|
|
5350
5482
|
setSessions(merged);
|
|
@@ -5357,16 +5489,16 @@ function usePiSessions(options = {}) {
|
|
|
5357
5489
|
return next;
|
|
5358
5490
|
});
|
|
5359
5491
|
} catch (err) {
|
|
5360
|
-
if (
|
|
5492
|
+
if (requestSeq === loadMoreRequestSeqRef.current && version === refreshVersionRef.current && sourceIsCurrent(scope)) {
|
|
5361
5493
|
setError(err instanceof Error ? err : new Error(String(err)));
|
|
5362
5494
|
}
|
|
5363
5495
|
} finally {
|
|
5364
5496
|
if (requestSeq === loadMoreRequestSeqRef.current) loadMoreInFlightRef.current = false;
|
|
5365
|
-
if (
|
|
5497
|
+
if (requestSeq === loadMoreRequestSeqRef.current && version === refreshVersionRef.current && sourceIsCurrent(scope)) {
|
|
5366
5498
|
setLoadingMore(false);
|
|
5367
5499
|
}
|
|
5368
5500
|
}
|
|
5369
|
-
}, [
|
|
5501
|
+
}, [enabled, fetchImpl, hasMore, loading, loadingMore, persistActive, requestHeaders, requestScopeKey, sessionsListUrl, sourceIsCurrent]);
|
|
5370
5502
|
useEffect8(() => {
|
|
5371
5503
|
if (!enabled || !connectActiveSession || !activeSessionId || !activeSessionKnown) {
|
|
5372
5504
|
setActivePiSession(void 0);
|
|
@@ -5388,6 +5520,8 @@ function usePiSessions(options = {}) {
|
|
|
5388
5520
|
};
|
|
5389
5521
|
}, [activeSessionId, activeSessionKnown, apiBaseUrl, connectActiveSession, createRemoteSession, enabled, fetchImpl, remoteSessionOptionsKey, options.agentTypeId, options.workspaceId, requestHeaders, storageScope]);
|
|
5390
5522
|
const create = useCallback7(async (init) => {
|
|
5523
|
+
const scope = requestScopeKey;
|
|
5524
|
+
if (!sourceIsCurrent(scope)) throw new StaleSessionsSourceError();
|
|
5391
5525
|
if (!enabled) throw new Error("Pi sessions are disabled");
|
|
5392
5526
|
const response = await fetchImpl(sessionsUrl(), {
|
|
5393
5527
|
method: "POST",
|
|
@@ -5400,8 +5534,10 @@ function usePiSessions(options = {}) {
|
|
|
5400
5534
|
throw err;
|
|
5401
5535
|
}
|
|
5402
5536
|
const body = await response.json();
|
|
5403
|
-
const session =
|
|
5537
|
+
const session = addressedCreatedSession(body, options.agentTypeId, init?.title);
|
|
5538
|
+
if (!sourceIsCurrent(scope)) return session;
|
|
5404
5539
|
ensurePendingScope();
|
|
5540
|
+
rememberConfirmedEmptySession(session.id);
|
|
5405
5541
|
pendingCreatedRef.current.set(session.id, session);
|
|
5406
5542
|
setDataStorageScope(storageScope);
|
|
5407
5543
|
setSessions((previous) => mergeSessions([session], previous));
|
|
@@ -5409,17 +5545,43 @@ function usePiSessions(options = {}) {
|
|
|
5409
5545
|
persistActive(session.id);
|
|
5410
5546
|
void refresh();
|
|
5411
5547
|
return session;
|
|
5412
|
-
}, [
|
|
5548
|
+
}, [enabled, ensurePendingScope, fetchImpl, persistActive, refresh, rememberConfirmedEmptySession, requestHeaders, requestScopeKey, sessionsUrl, sourceIsCurrent, storageScope]);
|
|
5413
5549
|
const switchSession = useCallback7((id) => {
|
|
5550
|
+
if (!sourceIsCurrent(requestScopeKey)) return;
|
|
5414
5551
|
const known = sessionsRef.current.some((session) => session.id === id);
|
|
5415
5552
|
const next = known ? id : sessionsRef.current[0]?.id;
|
|
5416
5553
|
setActiveSessionId(next);
|
|
5417
5554
|
persistActive(next);
|
|
5418
|
-
}, [persistActive]);
|
|
5555
|
+
}, [persistActive, requestScopeKey, sourceIsCurrent]);
|
|
5556
|
+
const rename = useCallback7(async (id, title) => {
|
|
5557
|
+
const scope = requestScopeKey;
|
|
5558
|
+
if (!sourceIsCurrent(scope)) throw new StaleSessionsSourceError();
|
|
5559
|
+
if (!enabled) throw new Error("Pi sessions are disabled");
|
|
5560
|
+
const response = await fetchImpl(sessionsUrl(`/${encodeURIComponent(id)}/rename`), {
|
|
5561
|
+
method: "POST",
|
|
5562
|
+
headers: { ...requestHeaders(), "Content-Type": "application/json" },
|
|
5563
|
+
body: JSON.stringify({ requestId: createRequestId("rename"), title })
|
|
5564
|
+
});
|
|
5565
|
+
if (!response.ok) throw new Error(`Failed to rename session: ${response.status}`);
|
|
5566
|
+
const renamed = toAddressedSessionSummary(await response.json());
|
|
5567
|
+
if (!sourceIsCurrent(scope)) throw new StaleSessionsSourceError();
|
|
5568
|
+
ensurePendingScope();
|
|
5569
|
+
if (pendingCreatedRef.current.has(id)) pendingCreatedRef.current.set(id, renamed);
|
|
5570
|
+
setSessions((current) => current.map((session) => session.id === id ? renamed : session));
|
|
5571
|
+
return renamed;
|
|
5572
|
+
}, [enabled, ensurePendingScope, fetchImpl, requestHeaders, requestScopeKey, sessionsUrl, sourceIsCurrent]);
|
|
5419
5573
|
const deleteSession = useCallback7(async (id) => {
|
|
5574
|
+
const scope = requestScopeKey;
|
|
5575
|
+
if (!sourceIsCurrent(scope)) throw new StaleSessionsSourceError();
|
|
5420
5576
|
if (!enabled) throw new Error("Pi sessions are disabled");
|
|
5421
5577
|
ensurePendingScope();
|
|
5578
|
+
if (readBootResumeSessionId({ bootResumeSource, storage: options.bootResumeStorage }) === id) {
|
|
5579
|
+
persistBootResume(void 0);
|
|
5580
|
+
}
|
|
5581
|
+
const deletedSession = sessionsRef.current.find((session) => session.id === id);
|
|
5582
|
+
const deletedSessionWasActive = activeSessionIdRef.current === id;
|
|
5422
5583
|
pendingCreatedRef.current.delete(id);
|
|
5584
|
+
pendingDeletedRef.current.add(id);
|
|
5423
5585
|
setDataStorageScope(storageScope);
|
|
5424
5586
|
setSessions((previous) => previous.filter((session) => session.id !== id));
|
|
5425
5587
|
setActiveSessionId((previous) => {
|
|
@@ -5436,18 +5598,29 @@ function usePiSessions(options = {}) {
|
|
|
5436
5598
|
if (!response.ok && response.status !== 404) throw new Error(`Failed to delete session: ${response.status}`);
|
|
5437
5599
|
} catch (err) {
|
|
5438
5600
|
const error2 = err instanceof Error ? err : new Error(String(err));
|
|
5439
|
-
|
|
5440
|
-
|
|
5601
|
+
if (sourceIsCurrent(scope)) {
|
|
5602
|
+
pendingDeletedRef.current.delete(id);
|
|
5603
|
+
if (deletedSession) {
|
|
5604
|
+
setSessions((previous) => mergeSessions([deletedSession], previous));
|
|
5605
|
+
if (deletedSessionWasActive) {
|
|
5606
|
+
setActiveSessionId(id);
|
|
5607
|
+
persistActive(id);
|
|
5608
|
+
}
|
|
5609
|
+
}
|
|
5610
|
+
setError(error2);
|
|
5611
|
+
void refresh();
|
|
5612
|
+
}
|
|
5441
5613
|
throw error2;
|
|
5442
5614
|
}
|
|
5443
|
-
void refresh();
|
|
5444
|
-
}, [enabled, ensurePendingScope, fetchImpl, persistActive, refresh, requestHeaders, sessionsUrl, storageScope]);
|
|
5615
|
+
if (sourceIsCurrent(scope)) void refresh();
|
|
5616
|
+
}, [bootResumeSource, enabled, ensurePendingScope, fetchImpl, options.bootResumeStorage, persistActive, persistBootResume, refresh, requestHeaders, requestScopeKey, sessionsUrl, sourceIsCurrent, storageScope]);
|
|
5445
5617
|
const reset = useCallback7(() => {
|
|
5618
|
+
if (!sourceIsCurrent(requestScopeKey)) return;
|
|
5446
5619
|
pendingCreatedRef.current.clear();
|
|
5620
|
+
pendingDeletedRef.current.clear();
|
|
5447
5621
|
loadMoreRequestSeqRef.current += 1;
|
|
5448
5622
|
loadMoreInFlightRef.current = false;
|
|
5449
5623
|
canonicalLoadedCountRef.current = canonicalPageCount(sessionsRef.current);
|
|
5450
|
-
nextCursorRef.current = void 0;
|
|
5451
5624
|
loadedDataSourceRef.current = dataSourceKey;
|
|
5452
5625
|
dataStorageScopeRef.current = storageScope;
|
|
5453
5626
|
setDataStorageScope(storageScope);
|
|
@@ -5455,36 +5628,35 @@ function usePiSessions(options = {}) {
|
|
|
5455
5628
|
setActivePiSession(void 0);
|
|
5456
5629
|
setLoadingMore(false);
|
|
5457
5630
|
persistActive(void 0);
|
|
5458
|
-
}, [dataSourceKey, persistActive, storageScope]);
|
|
5631
|
+
}, [dataSourceKey, persistActive, requestScopeKey, sourceIsCurrent, storageScope]);
|
|
5459
5632
|
const visibleActiveSessionId = enabled ? activeSessionId : void 0;
|
|
5460
5633
|
const activeSession = enabled ? sessions.find((session) => session.id === visibleActiveSessionId) : void 0;
|
|
5461
5634
|
return {
|
|
5462
5635
|
sessions,
|
|
5463
5636
|
activeSession,
|
|
5464
5637
|
activeSessionId: visibleActiveSessionId,
|
|
5638
|
+
resumeSessionId,
|
|
5465
5639
|
activePiSession: visibleActiveSessionId ? activePiSession : void 0,
|
|
5466
5640
|
dataStorageScope,
|
|
5641
|
+
sourceIdentity: enabled && loadedDataSourceRef.current === dataSourceKey ? options.sourceIdentity : void 0,
|
|
5467
5642
|
loading: enabled ? loading : false,
|
|
5468
5643
|
loadingMore,
|
|
5469
5644
|
hasMore: enabled ? hasMore : false,
|
|
5470
5645
|
error,
|
|
5471
5646
|
refresh,
|
|
5472
5647
|
create,
|
|
5648
|
+
rename,
|
|
5473
5649
|
switch: switchSession,
|
|
5474
5650
|
delete: deleteSession,
|
|
5475
5651
|
loadMore,
|
|
5476
5652
|
reset
|
|
5477
5653
|
};
|
|
5478
5654
|
}
|
|
5479
|
-
async function fetchSessionList(fetchImpl, url, headers
|
|
5655
|
+
async function fetchSessionList(fetchImpl, url, headers) {
|
|
5480
5656
|
const response = await fetchImpl(url, Object.keys(headers).length > 0 ? { headers } : void 0);
|
|
5481
5657
|
if (response.status === 503) throw new SessionsPreparingError();
|
|
5482
5658
|
if (!response.ok) throw new Error(`Failed to load sessions: ${response.status}`);
|
|
5483
5659
|
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
5660
|
if (typeof body !== "object" || body === null || !Array.isArray(body.sessions)) {
|
|
5489
5661
|
throw new Error("Failed to load sessions: invalid response");
|
|
5490
5662
|
}
|
|
@@ -5494,19 +5666,6 @@ async function fetchSessionList(fetchImpl, url, headers, addressed) {
|
|
|
5494
5666
|
...typeof page.nextCursor === "string" ? { nextCursor: page.nextCursor } : {}
|
|
5495
5667
|
};
|
|
5496
5668
|
}
|
|
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
5669
|
function toAddressedSessionSummary(value) {
|
|
5511
5670
|
if (typeof value !== "object" || value === null) throw new Error("invalid addressed session summary");
|
|
5512
5671
|
const record = value;
|
|
@@ -5516,21 +5675,29 @@ function toAddressedSessionSummary(value) {
|
|
|
5516
5675
|
}
|
|
5517
5676
|
const createdAt = typeof record.createdAt === "number" ? new Date(record.createdAt).toISOString() : (/* @__PURE__ */ new Date(0)).toISOString();
|
|
5518
5677
|
const updatedAt = typeof record.updatedAt === "number" ? new Date(record.updatedAt).toISOString() : createdAt;
|
|
5678
|
+
const agentTypeId = ref.agentTypeId;
|
|
5679
|
+
const status = record.status;
|
|
5680
|
+
const addressedStatus = status === "idle" || status === "running" || status === "aborting" || status === "error" ? status : void 0;
|
|
5519
5681
|
return {
|
|
5520
5682
|
id: ref.sessionId,
|
|
5521
5683
|
title: typeof record.title === "string" ? record.title : "Untitled",
|
|
5522
5684
|
createdAt,
|
|
5523
5685
|
updatedAt,
|
|
5524
|
-
turnCount: 0
|
|
5686
|
+
turnCount: typeof record.turnCount === "number" ? record.turnCount : 0,
|
|
5687
|
+
...typeof agentTypeId === "string" ? { agentTypeId } : {},
|
|
5688
|
+
...typeof record.nativeSessionId === "string" ? { nativeSessionId: record.nativeSessionId } : {},
|
|
5689
|
+
...typeof record.hasAssistantReply === "boolean" ? { hasAssistantReply: record.hasAssistantReply } : {},
|
|
5690
|
+
...addressedStatus ? { status: addressedStatus } : {}
|
|
5525
5691
|
};
|
|
5526
5692
|
}
|
|
5527
|
-
function addressedCreatedSession(value, title) {
|
|
5528
|
-
if (typeof value !== "object" || value === null || typeof value.sessionId !== "string") {
|
|
5693
|
+
function addressedCreatedSession(value, expectedAgentTypeId, title) {
|
|
5694
|
+
if (typeof value !== "object" || value === null || typeof value.sessionId !== "string" || value.agentTypeId !== expectedAgentTypeId) {
|
|
5529
5695
|
throw new Error("invalid addressed session ref");
|
|
5530
5696
|
}
|
|
5531
5697
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
5532
5698
|
return {
|
|
5533
5699
|
id: value.sessionId,
|
|
5700
|
+
agentTypeId: expectedAgentTypeId,
|
|
5534
5701
|
title: title ?? "Untitled",
|
|
5535
5702
|
createdAt: now,
|
|
5536
5703
|
updatedAt: now,
|
|
@@ -5586,31 +5753,25 @@ function mergeSessions(...lists) {
|
|
|
5586
5753
|
}
|
|
5587
5754
|
return merged;
|
|
5588
5755
|
}
|
|
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
5756
|
function headersScopeKey(headers, storageScope) {
|
|
5598
5757
|
return JSON.stringify({ storageScope, headers: Object.entries(headers ?? {}).sort(([a], [b]) => a.localeCompare(b)) });
|
|
5599
5758
|
}
|
|
5600
|
-
function requestScopeIdentity(apiBaseUrl, sessionsApiPath, storageScope, headersKey) {
|
|
5759
|
+
function requestScopeIdentity(apiBaseUrl, sessionsApiPath, agentTypeId, storageScope, headersKey, workspaceId, sourceIdentity) {
|
|
5601
5760
|
return `${apiBaseUrl}
|
|
5602
5761
|
${sessionsApiPath}
|
|
5762
|
+
${agentTypeId}
|
|
5603
5763
|
${storageScope}
|
|
5604
|
-
${headersKey}
|
|
5764
|
+
${headersKey}
|
|
5765
|
+
${workspaceId ?? ""}
|
|
5766
|
+
${sourceIdentity ?? ""}`;
|
|
5605
5767
|
}
|
|
5606
|
-
function dataSourceIdentity(apiBaseUrl, sessionsApiPath, storageScope) {
|
|
5768
|
+
function dataSourceIdentity(apiBaseUrl, sessionsApiPath, agentTypeId, storageScope, workspaceId, sourceIdentity) {
|
|
5607
5769
|
return `${apiBaseUrl}
|
|
5608
5770
|
${sessionsApiPath}
|
|
5609
|
-
${
|
|
5610
|
-
}
|
|
5611
|
-
|
|
5612
|
-
|
|
5613
|
-
return Object.keys(headers).some((key) => key.toLowerCase() === lower);
|
|
5771
|
+
${agentTypeId}
|
|
5772
|
+
${storageScope}
|
|
5773
|
+
${workspaceId ?? ""}
|
|
5774
|
+
${sourceIdentity ?? ""}`;
|
|
5614
5775
|
}
|
|
5615
5776
|
function retryDelayMs(attempt, retry) {
|
|
5616
5777
|
const base = retry.baseMs ?? DEFAULT_RETRY_BASE_MS;
|
|
@@ -5991,8 +6152,8 @@ function searchPiSessions(sessions, query, options = {}) {
|
|
|
5991
6152
|
}
|
|
5992
6153
|
|
|
5993
6154
|
// src/front/chat/components/PiConversationSurface.tsx
|
|
5994
|
-
import {
|
|
5995
|
-
import { useCallback as useCallback14, useEffect as useEffect14, useLayoutEffect, useRef as useRef11, useState as useState15 } from "react";
|
|
6155
|
+
import { Skeleton } from "@hachej/boring-ui-kit";
|
|
6156
|
+
import { useCallback as useCallback14, useEffect as useEffect14, useLayoutEffect as useLayoutEffect2, useRef as useRef11, useState as useState15 } from "react";
|
|
5996
6157
|
import { useStickToBottomContext as useStickToBottomContext2 } from "use-stick-to-bottom";
|
|
5997
6158
|
|
|
5998
6159
|
// src/front/primitives/conversation.tsx
|
|
@@ -6053,7 +6214,7 @@ var ConversationScrollButton = ({
|
|
|
6053
6214
|
Button7,
|
|
6054
6215
|
{
|
|
6055
6216
|
className: cn(
|
|
6056
|
-
"absolute bottom-4 left-[50%] translate-x-[-50%] rounded-full dark:bg-background dark:hover:bg-muted",
|
|
6217
|
+
"boring-agent-scroll-latest absolute bottom-4 left-[50%] translate-x-[-50%] rounded-full dark:bg-background dark:hover:bg-muted",
|
|
6057
6218
|
className
|
|
6058
6219
|
),
|
|
6059
6220
|
onClick: handleScrollToBottom,
|
|
@@ -6098,8 +6259,27 @@ function noticeTextClass(className) {
|
|
|
6098
6259
|
import { memo as memo2 } from "react";
|
|
6099
6260
|
import { AlertTriangleIcon, InfoIcon, Loader2Icon, PlugZapIcon, RefreshCwIcon, XIcon as XIcon2 } from "lucide-react";
|
|
6100
6261
|
import { Button as Button8 } from "@hachej/boring-ui-kit";
|
|
6101
|
-
|
|
6102
|
-
|
|
6262
|
+
|
|
6263
|
+
// src/front/chat/components/terminalChatErrors.ts
|
|
6264
|
+
var TERMINAL_CHAT_ERROR_IDS = /* @__PURE__ */ new Set(["chat-error", "session-navigation-error"]);
|
|
6265
|
+
function isTerminalChatErrorId(id) {
|
|
6266
|
+
return TERMINAL_CHAT_ERROR_IDS.has(id);
|
|
6267
|
+
}
|
|
6268
|
+
function isTerminalChatErrorNotice(id, historyEmpty) {
|
|
6269
|
+
return historyEmpty && isTerminalChatErrorId(id);
|
|
6270
|
+
}
|
|
6271
|
+
function hasTerminalChatError(notices, historyEmpty) {
|
|
6272
|
+
return historyEmpty && notices.some((notice) => isTerminalChatErrorId(notice.id));
|
|
6273
|
+
}
|
|
6274
|
+
var COMPETING_NOISE_NOTICE_IDS = /* @__PURE__ */ new Set(["connection-reconnecting", "auto-retry"]);
|
|
6275
|
+
function filterCompetingNoiseNotices(notices, historyEmpty) {
|
|
6276
|
+
if (!hasTerminalChatError(notices, historyEmpty)) return notices;
|
|
6277
|
+
return notices.filter((notice) => !COMPETING_NOISE_NOTICE_IDS.has(notice.id));
|
|
6278
|
+
}
|
|
6279
|
+
|
|
6280
|
+
// src/front/chat/components/RuntimeNotices.tsx
|
|
6281
|
+
import { Fragment as Fragment7, jsx as jsx16, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
6282
|
+
var RuntimeNotices = memo2(({ notices, onDismiss, onAction, renderAction, historyEmpty, className, ...props }) => {
|
|
6103
6283
|
if (notices.length === 0) return null;
|
|
6104
6284
|
return /* @__PURE__ */ jsx16(
|
|
6105
6285
|
"div",
|
|
@@ -6107,16 +6287,29 @@ var RuntimeNotices = memo2(({ notices, onDismiss, onAction, renderAction, classN
|
|
|
6107
6287
|
"data-boring-agent-part": "runtime-notices",
|
|
6108
6288
|
className: cn("flex flex-col gap-2 px-4 py-2", className),
|
|
6109
6289
|
...props,
|
|
6110
|
-
children: notices.map((notice) => /* @__PURE__ */ jsx16(
|
|
6290
|
+
children: notices.map((notice) => /* @__PURE__ */ jsx16(
|
|
6291
|
+
RuntimeNoticeRow,
|
|
6292
|
+
{
|
|
6293
|
+
notice,
|
|
6294
|
+
onDismiss,
|
|
6295
|
+
onAction,
|
|
6296
|
+
renderAction,
|
|
6297
|
+
historyEmpty: historyEmpty ?? false
|
|
6298
|
+
},
|
|
6299
|
+
notice.id
|
|
6300
|
+
))
|
|
6111
6301
|
}
|
|
6112
6302
|
);
|
|
6113
6303
|
});
|
|
6114
6304
|
RuntimeNotices.displayName = "RuntimeNotices";
|
|
6115
|
-
function RuntimeNoticeRow({ notice, onDismiss, onAction, renderAction }) {
|
|
6305
|
+
function RuntimeNoticeRow({ notice, onDismiss, onAction, renderAction, historyEmpty }) {
|
|
6116
6306
|
const kind = inferNoticeKind(notice);
|
|
6117
6307
|
const Icon = iconForNotice(kind, notice.level);
|
|
6118
6308
|
const actionLabel = notice.actionLabel ?? defaultActionLabel(kind);
|
|
6119
6309
|
const hostAction = renderAction?.(notice);
|
|
6310
|
+
const isTerminalError = isTerminalChatErrorNotice(notice.id, historyEmpty);
|
|
6311
|
+
const explicitAction = actionLabel && onAction ? /* @__PURE__ */ jsx16(Button8, { type: "button", variant: "ghost", size: "sm", onClick: () => onAction(notice.id), children: actionLabel }) : null;
|
|
6312
|
+
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
6313
|
return /* @__PURE__ */ jsxs14(
|
|
6121
6314
|
"div",
|
|
6122
6315
|
{
|
|
@@ -6132,9 +6325,15 @@ function RuntimeNoticeRow({ notice, onDismiss, onAction, renderAction }) {
|
|
|
6132
6325
|
),
|
|
6133
6326
|
children: [
|
|
6134
6327
|
/* @__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__ */
|
|
6136
|
-
|
|
6137
|
-
|
|
6328
|
+
/* @__PURE__ */ jsx16("div", { className: "min-w-0 flex-1", children: isTerminalError ? /* @__PURE__ */ jsxs14(Fragment7, { children: [
|
|
6329
|
+
/* @__PURE__ */ jsx16("p", { className: "text-sm font-semibold text-foreground", children: "Chat history unavailable" }),
|
|
6330
|
+
/* @__PURE__ */ jsx16("p", { className: noticeTextClass("mt-0.5"), children: "The saved conversation could not be loaded. Reload the workspace to try again." }),
|
|
6331
|
+
/* @__PURE__ */ jsxs14("details", { className: "mt-2 text-xs text-muted-foreground", children: [
|
|
6332
|
+
/* @__PURE__ */ jsx16("summary", { className: "cursor-pointer select-none", children: "Error details" }),
|
|
6333
|
+
/* @__PURE__ */ jsx16("p", { className: "mt-1 break-words", children: notice.text })
|
|
6334
|
+
] })
|
|
6335
|
+
] }) : /* @__PURE__ */ jsx16("p", { className: noticeTextClass(), children: notice.text }) }),
|
|
6336
|
+
primaryAction,
|
|
6138
6337
|
notice.dismissible && onDismiss ? /* @__PURE__ */ jsx16(
|
|
6139
6338
|
Button8,
|
|
6140
6339
|
{
|
|
@@ -6177,7 +6376,8 @@ import { jsx as jsx17, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
|
6177
6376
|
function RuntimeNoticeMessages({
|
|
6178
6377
|
notices,
|
|
6179
6378
|
onDismiss,
|
|
6180
|
-
renderAction
|
|
6379
|
+
renderAction,
|
|
6380
|
+
historyEmpty
|
|
6181
6381
|
}) {
|
|
6182
6382
|
const visible = notices.filter(
|
|
6183
6383
|
(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 +6389,7 @@ function RuntimeNoticeMessages({
|
|
|
6189
6389
|
notices: visible,
|
|
6190
6390
|
onDismiss,
|
|
6191
6391
|
renderAction,
|
|
6392
|
+
historyEmpty,
|
|
6192
6393
|
className: "mx-auto w-full max-w-3xl px-0 py-0"
|
|
6193
6394
|
}
|
|
6194
6395
|
);
|
|
@@ -6650,11 +6851,11 @@ function extractCodeBlockText(children) {
|
|
|
6650
6851
|
};
|
|
6651
6852
|
return walk(children);
|
|
6652
6853
|
}
|
|
6653
|
-
var MarkdownPre = ({ children }) => {
|
|
6854
|
+
var MarkdownPre = ({ children, filename }) => {
|
|
6654
6855
|
const code3 = extractCodeBlockText(children).replace(/\n$/, "");
|
|
6655
6856
|
const language = extractCodeBlockLanguage(children);
|
|
6656
6857
|
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" }) }),
|
|
6858
|
+
/* @__PURE__ */ jsx19(CodeBlockTitle, { children: /* @__PURE__ */ jsx19(CodeBlockFilename, { children: filename ?? language ?? "text" }) }),
|
|
6658
6859
|
/* @__PURE__ */ jsx19(CodeBlockCopyButton, {})
|
|
6659
6860
|
] }) });
|
|
6660
6861
|
};
|
|
@@ -6684,7 +6885,7 @@ var markdownComponents = {
|
|
|
6684
6885
|
code: MarkdownCode
|
|
6685
6886
|
};
|
|
6686
6887
|
var MessageResponse = memo3(
|
|
6687
|
-
({ className, shikiTheme, components, ...props }) => /* @__PURE__ */ jsx19(
|
|
6888
|
+
({ className, shikiTheme, components, codeFilename, ...props }) => /* @__PURE__ */ jsx19(
|
|
6688
6889
|
Streamdown,
|
|
6689
6890
|
{
|
|
6690
6891
|
className: cn(
|
|
@@ -6693,11 +6894,15 @@ var MessageResponse = memo3(
|
|
|
6693
6894
|
),
|
|
6694
6895
|
plugins: streamdownPlugins,
|
|
6695
6896
|
shikiTheme: shikiTheme ?? DEFAULT_SHIKI_THEME,
|
|
6696
|
-
components: {
|
|
6897
|
+
components: {
|
|
6898
|
+
...markdownComponents,
|
|
6899
|
+
...components ?? {},
|
|
6900
|
+
...codeFilename ? { pre: (props2) => /* @__PURE__ */ jsx19(MarkdownPre, { ...props2, filename: codeFilename }) } : {}
|
|
6901
|
+
},
|
|
6697
6902
|
...props
|
|
6698
6903
|
}
|
|
6699
6904
|
),
|
|
6700
|
-
(prevProps, nextProps) => prevProps.children === nextProps.children && nextProps.isAnimating === prevProps.isAnimating && nextProps.components === prevProps.components
|
|
6905
|
+
(prevProps, nextProps) => prevProps.children === nextProps.children && nextProps.isAnimating === prevProps.isAnimating && nextProps.components === prevProps.components && prevProps.codeFilename === nextProps.codeFilename
|
|
6701
6906
|
);
|
|
6702
6907
|
MessageResponse.displayName = "MessageResponse";
|
|
6703
6908
|
|
|
@@ -6778,7 +6983,7 @@ var ShimmerComponent = ({
|
|
|
6778
6983
|
var Shimmer = memo4(ShimmerComponent);
|
|
6779
6984
|
|
|
6780
6985
|
// src/front/primitives/reasoning.tsx
|
|
6781
|
-
import { Fragment as
|
|
6986
|
+
import { Fragment as Fragment8, jsx as jsx21, jsxs as jsxs18 } from "react/jsx-runtime";
|
|
6782
6987
|
var ReasoningContext = createContext6(null);
|
|
6783
6988
|
var useReasoning = () => {
|
|
6784
6989
|
const context = useContext6(ReasoningContext);
|
|
@@ -6891,7 +7096,7 @@ var ReasoningTrigger = memo5(
|
|
|
6891
7096
|
className
|
|
6892
7097
|
),
|
|
6893
7098
|
...props,
|
|
6894
|
-
children: children ?? /* @__PURE__ */ jsxs18(
|
|
7099
|
+
children: children ?? /* @__PURE__ */ jsxs18(Fragment8, { children: [
|
|
6895
7100
|
/* @__PURE__ */ jsx21(BrainIcon, { className: "size-3" }),
|
|
6896
7101
|
getThinkingMessage(isStreaming, duration),
|
|
6897
7102
|
/* @__PURE__ */ jsx21(
|
|
@@ -7053,6 +7258,10 @@ var ToolCallGroup = memo6(({ tools, mergedToolRenderers }) => {
|
|
|
7053
7258
|
return () => window.clearInterval(interval);
|
|
7054
7259
|
}, [isRunning]);
|
|
7055
7260
|
if (toolParts.length === 0) return null;
|
|
7261
|
+
const inlineToolKeys = new Set(toolParts.flatMap(({ part, key }) => {
|
|
7262
|
+
const { renderer } = resolveToolRendererForPart(part, mergedToolRenderers);
|
|
7263
|
+
return renderer.presentation === "inline" ? [key] : [];
|
|
7264
|
+
}));
|
|
7056
7265
|
return /* @__PURE__ */ jsxs19(
|
|
7057
7266
|
Collapsible3,
|
|
7058
7267
|
{
|
|
@@ -7061,6 +7270,11 @@ var ToolCallGroup = memo6(({ tools, mergedToolRenderers }) => {
|
|
|
7061
7270
|
className: "not-prose my-1.5",
|
|
7062
7271
|
"data-boring-agent-tool-state": visualState,
|
|
7063
7272
|
children: [
|
|
7273
|
+
toolParts.map(({ part, key }) => {
|
|
7274
|
+
if (!inlineToolKeys.has(key)) return null;
|
|
7275
|
+
const { renderer, part: resolvedPart, resolution } = resolveToolRendererForPart(part, mergedToolRenderers);
|
|
7276
|
+
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);
|
|
7277
|
+
}),
|
|
7064
7278
|
/* @__PURE__ */ jsxs19(
|
|
7065
7279
|
CollapsibleTrigger4,
|
|
7066
7280
|
{
|
|
@@ -7106,7 +7320,10 @@ var ToolCallGroup = memo6(({ tools, mergedToolRenderers }) => {
|
|
|
7106
7320
|
children: [
|
|
7107
7321
|
/* @__PURE__ */ jsx22("div", { className: "absolute left-[5px] top-0 bottom-2 w-px bg-border/35" }),
|
|
7108
7322
|
/* @__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(
|
|
7323
|
+
const { renderer, part: resolvedPart, resolution } = resolveToolRendererForPart(
|
|
7324
|
+
part,
|
|
7325
|
+
inlineToolKeys.has(key) ? {} : mergedToolRenderers
|
|
7326
|
+
);
|
|
7110
7327
|
return /* @__PURE__ */ jsx22(
|
|
7111
7328
|
"div",
|
|
7112
7329
|
{
|
|
@@ -7158,7 +7375,7 @@ function ChatMessageContributionBoundary({
|
|
|
7158
7375
|
|
|
7159
7376
|
// src/front/chat/components/MessageMentions.tsx
|
|
7160
7377
|
import { Children, cloneElement, isValidElement as isValidElement2 } from "react";
|
|
7161
|
-
import { Fragment as
|
|
7378
|
+
import { Fragment as Fragment9, jsx as jsx24 } from "react/jsx-runtime";
|
|
7162
7379
|
var TERMINAL_BOUNDARY = String.raw`(?=$|\s|[.,;!?)}\]"'”’]+(?=$|\s))`;
|
|
7163
7380
|
var PREFIX_BOUNDARY = String.raw`(^|[\s(\[{"'“‘])`;
|
|
7164
7381
|
function escapeRegExp2(value) {
|
|
@@ -7316,8 +7533,8 @@ function createMessageMentionMarkdownComponents(catalog, onActivate) {
|
|
|
7316
7533
|
const content = Children.toArray(children).filter((child) => child !== null && child !== "");
|
|
7317
7534
|
if (content.length === 1 && isValidElement2(content[0])) {
|
|
7318
7535
|
const child = content[0];
|
|
7319
|
-
if (child.props.node?.tagName === "img") return /* @__PURE__ */ jsx24(
|
|
7320
|
-
if (child.props.node?.tagName === "code" && "data-block" in child.props) return /* @__PURE__ */ jsx24(
|
|
7536
|
+
if (child.props.node?.tagName === "img") return /* @__PURE__ */ jsx24(Fragment9, { children });
|
|
7537
|
+
if (child.props.node?.tagName === "code" && "data-block" in child.props) return /* @__PURE__ */ jsx24(Fragment9, { children });
|
|
7321
7538
|
}
|
|
7322
7539
|
return /* @__PURE__ */ jsx24("p", { ...props, children: decorate(children) });
|
|
7323
7540
|
};
|
|
@@ -7403,12 +7620,13 @@ function DefaultPiTimelineMessage({ message, isLast, isStreaming, showThoughts,
|
|
|
7403
7620
|
mentionActivateRef.current?.(mention);
|
|
7404
7621
|
}, [openArtifact]);
|
|
7405
7622
|
const hasMentions = effectiveMentionCatalog.files || effectiveMentionCatalog.commands.length > 0 || effectiveMentionCatalog.skills.length > 0;
|
|
7406
|
-
const
|
|
7623
|
+
const messageIsStreaming = isAssistant && isLast && (isStreaming || message.status === "streaming");
|
|
7624
|
+
const mentionsEnabled = Boolean(isAssistant && !messageIsStreaming && hasMentions);
|
|
7407
7625
|
const mentionMarkdownComponents = useMemo12(
|
|
7408
7626
|
() => mentionsEnabled ? createMessageMentionMarkdownComponents(effectiveMentionCatalog, activateCurrentMention) : void 0,
|
|
7409
7627
|
[activateCurrentMention, effectiveMentionCatalog, mentionSignature, mentionsEnabled]
|
|
7410
7628
|
);
|
|
7411
|
-
const shouldReserveStreamingActions =
|
|
7629
|
+
const shouldReserveStreamingActions = messageIsStreaming;
|
|
7412
7630
|
return /* @__PURE__ */ jsxs20(
|
|
7413
7631
|
Message,
|
|
7414
7632
|
{
|
|
@@ -7467,7 +7685,7 @@ function DefaultPiTimelineMessage({ message, isLast, isStreaming, showThoughts,
|
|
|
7467
7685
|
})
|
|
7468
7686
|
}
|
|
7469
7687
|
) : null,
|
|
7470
|
-
finalParts.map((item) => {
|
|
7688
|
+
finalParts.map((item, index) => {
|
|
7471
7689
|
if (item.kind === "reasoning") {
|
|
7472
7690
|
return /* @__PURE__ */ jsx25(
|
|
7473
7691
|
TimelineReasoningPart,
|
|
@@ -7491,6 +7709,7 @@ function DefaultPiTimelineMessage({ message, isLast, isStreaming, showThoughts,
|
|
|
7491
7709
|
MessageResponse,
|
|
7492
7710
|
{
|
|
7493
7711
|
components: mentionMarkdownComponents,
|
|
7712
|
+
codeFilename: codeFilenameForPart(finalParts, index),
|
|
7494
7713
|
className: "boring-agent-markdown max-w-none",
|
|
7495
7714
|
children: text
|
|
7496
7715
|
},
|
|
@@ -7507,7 +7726,7 @@ function DefaultPiTimelineMessage({ message, isLast, isStreaming, showThoughts,
|
|
|
7507
7726
|
MessageActionsBar,
|
|
7508
7727
|
{
|
|
7509
7728
|
text: textParts.map((part) => part.text).join("\n\n"),
|
|
7510
|
-
visible: !
|
|
7729
|
+
visible: !messageIsStreaming
|
|
7511
7730
|
}
|
|
7512
7731
|
) : null
|
|
7513
7732
|
]
|
|
@@ -7542,7 +7761,7 @@ function TimelineReasoningPart({ item, showThoughts }) {
|
|
|
7542
7761
|
ReasoningTrigger,
|
|
7543
7762
|
{
|
|
7544
7763
|
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: (
|
|
7764
|
+
getThinkingMessage: () => /* @__PURE__ */ jsx25("span", { children: "Reasoning" }),
|
|
7546
7765
|
onClick: toggleManualOpen
|
|
7547
7766
|
}
|
|
7548
7767
|
),
|
|
@@ -7603,6 +7822,15 @@ ${part.text}`;
|
|
|
7603
7822
|
flushTools();
|
|
7604
7823
|
return grouped;
|
|
7605
7824
|
}
|
|
7825
|
+
function codeFilenameForPart(parts, index) {
|
|
7826
|
+
const precedingPart = parts[index - 1];
|
|
7827
|
+
if (!precedingPart || precedingPart.kind !== "tool-plain") return void 0;
|
|
7828
|
+
if (precedingPart.part.toolName !== "write" && precedingPart.part.toolName !== "edit") return void 0;
|
|
7829
|
+
const input = precedingPart.part.input;
|
|
7830
|
+
if (!input || typeof input !== "object" || Array.isArray(input)) return void 0;
|
|
7831
|
+
const path = input.path;
|
|
7832
|
+
return typeof path === "string" && path.trim() ? path : void 0;
|
|
7833
|
+
}
|
|
7606
7834
|
function NoticeBubble({ level, text }) {
|
|
7607
7835
|
return /* @__PURE__ */ jsx25(
|
|
7608
7836
|
"div",
|
|
@@ -7683,10 +7911,10 @@ function MessageActionsBar({
|
|
|
7683
7911
|
}
|
|
7684
7912
|
};
|
|
7685
7913
|
const iconActionBtnClass = cn(
|
|
7686
|
-
"inline-flex
|
|
7687
|
-
"text-muted-foreground/
|
|
7688
|
-
"hover:bg-foreground/[0.04] hover:text-muted-foreground
|
|
7689
|
-
"focus-visible:outline-none focus-visible:ring-
|
|
7914
|
+
"boring-agent-message-action inline-flex size-8 items-center justify-center rounded-[var(--radius-sm)]",
|
|
7915
|
+
"text-muted-foreground/70 transition-colors duration-150",
|
|
7916
|
+
"hover:bg-foreground/[0.04] hover:text-muted-foreground",
|
|
7917
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[color:var(--accent)]/40"
|
|
7690
7918
|
);
|
|
7691
7919
|
const hiddenActionProps = visible ? {} : { tabIndex: -1 };
|
|
7692
7920
|
return /* @__PURE__ */ jsx25(
|
|
@@ -7694,7 +7922,7 @@ function MessageActionsBar({
|
|
|
7694
7922
|
{
|
|
7695
7923
|
"aria-hidden": !visible,
|
|
7696
7924
|
className: cn(
|
|
7697
|
-
"flex min-h-
|
|
7925
|
+
"flex min-h-8 items-center gap-1 -mt-1 text-xs text-muted-foreground transition-opacity duration-200",
|
|
7698
7926
|
visible ? "opacity-100" : "pointer-events-none opacity-0"
|
|
7699
7927
|
),
|
|
7700
7928
|
children: /* @__PURE__ */ jsx25(
|
|
@@ -7755,6 +7983,8 @@ function PiConversationSurface({
|
|
|
7755
7983
|
}) {
|
|
7756
7984
|
const messageItems = buildMessageRenderItems(messages);
|
|
7757
7985
|
const total = messageItems.length;
|
|
7986
|
+
const historyEmpty = messages.length === 0;
|
|
7987
|
+
const terminalError = hasTerminalChatError(runtimeNotices, historyEmpty);
|
|
7758
7988
|
const [visibleCount, setVisibleCount] = useState15(TRANSCRIPT_WINDOW);
|
|
7759
7989
|
useEffect14(() => {
|
|
7760
7990
|
setVisibleCount(TRANSCRIPT_WINDOW);
|
|
@@ -7778,11 +8008,8 @@ function PiConversationSurface({
|
|
|
7778
8008
|
chrome ? "max-w-3xl px-6 py-8" : "max-w-[680px] px-4 py-4",
|
|
7779
8009
|
emptyHero && "py-4 text-center"
|
|
7780
8010
|
), children: [
|
|
7781
|
-
messages.length === 0 && emptyStateHydrating ? /* @__PURE__ */
|
|
7782
|
-
|
|
7783
|
-
"Loading chat history\u2026"
|
|
7784
|
-
] }) : null,
|
|
7785
|
-
messages.length === 0 && !emptyStateHydrating ? /* @__PURE__ */ jsx26(
|
|
8011
|
+
messages.length === 0 && emptyStateHydrating && !terminalError ? /* @__PURE__ */ jsx26(ConversationHistoryLoadingState, {}) : null,
|
|
8012
|
+
messages.length === 0 && !emptyStateHydrating && !terminalError ? /* @__PURE__ */ jsx26(
|
|
7786
8013
|
ChatEmptyState,
|
|
7787
8014
|
{
|
|
7788
8015
|
eyebrow: emptyState?.eyebrow,
|
|
@@ -7814,13 +8041,45 @@ function PiConversationSurface({
|
|
|
7814
8041
|
},
|
|
7815
8042
|
key
|
|
7816
8043
|
)),
|
|
7817
|
-
/* @__PURE__ */ jsx26(RuntimeNoticeMessages, { notices: runtimeNotices, onDismiss: onDismissNotice, renderAction: renderNoticeAction })
|
|
8044
|
+
/* @__PURE__ */ jsx26(RuntimeNoticeMessages, { notices: runtimeNotices, onDismiss: onDismissNotice, renderAction: renderNoticeAction, historyEmpty })
|
|
7818
8045
|
] }),
|
|
7819
8046
|
/* @__PURE__ */ jsx26(ConversationScrollButton, {})
|
|
7820
8047
|
]
|
|
7821
8048
|
}
|
|
7822
8049
|
);
|
|
7823
8050
|
}
|
|
8051
|
+
function ConversationHistoryLoadingState() {
|
|
8052
|
+
const skeletonClassName = "motion-reduce:animate-none";
|
|
8053
|
+
return /* @__PURE__ */ jsxs21(
|
|
8054
|
+
"div",
|
|
8055
|
+
{
|
|
8056
|
+
role: "status",
|
|
8057
|
+
"aria-label": "Loading chat history",
|
|
8058
|
+
"aria-busy": "true",
|
|
8059
|
+
"data-boring-agent-part": "chat-history-loading",
|
|
8060
|
+
className: "flex flex-col gap-7 py-4",
|
|
8061
|
+
children: [
|
|
8062
|
+
/* @__PURE__ */ jsxs21("div", { "aria-hidden": "true", className: "flex items-start gap-3", children: [
|
|
8063
|
+
/* @__PURE__ */ jsx26(Skeleton, { className: cn("mt-0.5 size-7 shrink-0 rounded-lg", skeletonClassName) }),
|
|
8064
|
+
/* @__PURE__ */ jsxs21("div", { className: "flex min-w-0 flex-1 flex-col gap-2.5", children: [
|
|
8065
|
+
/* @__PURE__ */ jsx26(Skeleton, { className: cn("h-3 w-24 rounded-sm", skeletonClassName) }),
|
|
8066
|
+
/* @__PURE__ */ jsx26(Skeleton, { className: cn("h-3 w-full rounded-sm", skeletonClassName) }),
|
|
8067
|
+
/* @__PURE__ */ jsx26(Skeleton, { className: cn("h-3 w-4/5 rounded-sm", skeletonClassName) })
|
|
8068
|
+
] })
|
|
8069
|
+
] }),
|
|
8070
|
+
/* @__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) }) }),
|
|
8071
|
+
/* @__PURE__ */ jsxs21("div", { "aria-hidden": "true", className: "flex items-start gap-3", children: [
|
|
8072
|
+
/* @__PURE__ */ jsx26(Skeleton, { className: cn("mt-0.5 size-7 shrink-0 rounded-lg", skeletonClassName) }),
|
|
8073
|
+
/* @__PURE__ */ jsxs21("div", { className: "flex min-w-0 flex-1 flex-col gap-3", children: [
|
|
8074
|
+
/* @__PURE__ */ jsx26(Skeleton, { className: cn("h-3 w-20 rounded-sm", skeletonClassName) }),
|
|
8075
|
+
/* @__PURE__ */ jsx26(Skeleton, { className: cn("h-20 w-full rounded-lg", skeletonClassName) }),
|
|
8076
|
+
/* @__PURE__ */ jsx26(Skeleton, { className: cn("h-3 w-3/5 rounded-sm", skeletonClassName) })
|
|
8077
|
+
] })
|
|
8078
|
+
] })
|
|
8079
|
+
]
|
|
8080
|
+
}
|
|
8081
|
+
);
|
|
8082
|
+
}
|
|
7824
8083
|
function TranscriptHistoryLoader({ olderCount, onLoadOlder }) {
|
|
7825
8084
|
const { scrollRef } = useStickToBottomContext2();
|
|
7826
8085
|
const pendingAnchor = useRef11(null);
|
|
@@ -7839,7 +8098,7 @@ function TranscriptHistoryLoader({ olderCount, onLoadOlder }) {
|
|
|
7839
8098
|
el.addEventListener("scroll", onScroll, { passive: true });
|
|
7840
8099
|
return () => el.removeEventListener("scroll", onScroll);
|
|
7841
8100
|
}, [scrollRef, onLoadOlder]);
|
|
7842
|
-
|
|
8101
|
+
useLayoutEffect2(() => {
|
|
7843
8102
|
const el = scrollRef.current;
|
|
7844
8103
|
if (!el) return;
|
|
7845
8104
|
if (pendingAnchor.current != null) {
|
|
@@ -7853,7 +8112,7 @@ function TranscriptHistoryLoader({ olderCount, onLoadOlder }) {
|
|
|
7853
8112
|
{
|
|
7854
8113
|
type: "button",
|
|
7855
8114
|
onClick: onLoadOlder,
|
|
7856
|
-
className: "rounded-full border border-border/60 px-3 py-1 text-
|
|
8115
|
+
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
8116
|
children: [
|
|
7858
8117
|
"Load ",
|
|
7859
8118
|
olderCount,
|
|
@@ -7885,8 +8144,8 @@ function buildMessageRenderItems(messages) {
|
|
|
7885
8144
|
}
|
|
7886
8145
|
|
|
7887
8146
|
// src/front/chat/components/PiChatComposerSurface.tsx
|
|
7888
|
-
import { useCallback as useCallback16, useLayoutEffect as
|
|
7889
|
-
import { motion as motion2 } from "motion/react";
|
|
8147
|
+
import { useCallback as useCallback16, useLayoutEffect as useLayoutEffect3 } from "react";
|
|
8148
|
+
import { motion as motion2, useReducedMotion } from "motion/react";
|
|
7890
8149
|
|
|
7891
8150
|
// src/front/composer/PluginUpdateStatus.tsx
|
|
7892
8151
|
import { useEffect as useEffect15, useRef as useRef12 } from "react";
|
|
@@ -8129,7 +8388,7 @@ function PluginUpdateStatus({
|
|
|
8129
8388
|
|
|
8130
8389
|
// src/front/composer/CommandRunStatus.tsx
|
|
8131
8390
|
import { useEffect as useEffect16, useRef as useRef13 } from "react";
|
|
8132
|
-
import { Fragment as
|
|
8391
|
+
import { Fragment as Fragment10, jsx as jsx29, jsxs as jsxs24 } from "react/jsx-runtime";
|
|
8133
8392
|
function CommandRunStatus({
|
|
8134
8393
|
state,
|
|
8135
8394
|
onDismiss,
|
|
@@ -8154,7 +8413,7 @@ function CommandRunStatus({
|
|
|
8154
8413
|
tone: "running",
|
|
8155
8414
|
dataAttribute: "data-boring-command-run",
|
|
8156
8415
|
maxWidthClassName,
|
|
8157
|
-
runningContent: state.command ? /* @__PURE__ */ jsxs24(
|
|
8416
|
+
runningContent: state.command ? /* @__PURE__ */ jsxs24(Fragment10, { children: [
|
|
8158
8417
|
" Running ",
|
|
8159
8418
|
/* @__PURE__ */ jsxs24("code", { className: "font-mono", children: [
|
|
8160
8419
|
"/",
|
|
@@ -8172,7 +8431,7 @@ function CommandRunStatus({
|
|
|
8172
8431
|
tone: "success",
|
|
8173
8432
|
dataAttribute: "data-boring-command-run",
|
|
8174
8433
|
maxWidthClassName,
|
|
8175
|
-
title: state.command ? /* @__PURE__ */ jsxs24(
|
|
8434
|
+
title: state.command ? /* @__PURE__ */ jsxs24(Fragment10, { children: [
|
|
8176
8435
|
" Ran ",
|
|
8177
8436
|
/* @__PURE__ */ jsxs24("code", { className: "font-mono", children: [
|
|
8178
8437
|
"/",
|
|
@@ -8192,7 +8451,7 @@ function CommandRunStatus({
|
|
|
8192
8451
|
tone: "error",
|
|
8193
8452
|
dataAttribute: "data-boring-command-run",
|
|
8194
8453
|
maxWidthClassName,
|
|
8195
|
-
title: state.command ? /* @__PURE__ */ jsxs24(
|
|
8454
|
+
title: state.command ? /* @__PURE__ */ jsxs24(Fragment10, { children: [
|
|
8196
8455
|
" ",
|
|
8197
8456
|
/* @__PURE__ */ jsxs24("code", { className: "font-mono", children: [
|
|
8198
8457
|
"/",
|
|
@@ -8242,7 +8501,7 @@ function displayProviderLabel(provider) {
|
|
|
8242
8501
|
}
|
|
8243
8502
|
|
|
8244
8503
|
// src/front/chatPanelComposerControls.tsx
|
|
8245
|
-
import { Fragment as
|
|
8504
|
+
import { Fragment as Fragment11, jsx as jsx30, jsxs as jsxs25 } from "react/jsx-runtime";
|
|
8246
8505
|
var composerActionClass = cn(
|
|
8247
8506
|
"inline-flex h-8 items-center justify-center gap-1.5 rounded-[var(--radius-md)] border-0 bg-transparent",
|
|
8248
8507
|
"text-muted-foreground shadow-none transition-colors motion-reduce:transition-none",
|
|
@@ -8384,14 +8643,14 @@ var ModelSelectTrigger = forwardRef(function ModelSelectTrigger2({
|
|
|
8384
8643
|
className
|
|
8385
8644
|
),
|
|
8386
8645
|
...props,
|
|
8387
|
-
children: trigger === "slash" ? /* @__PURE__ */ jsxs25(
|
|
8646
|
+
children: trigger === "slash" ? /* @__PURE__ */ jsxs25(Fragment11, { children: [
|
|
8388
8647
|
/* @__PURE__ */ jsx30("span", { className: "text-muted-foreground", children: "Model \xB7 " }),
|
|
8389
8648
|
/* @__PURE__ */ jsx30("span", { className: "min-w-0 truncate text-foreground", children: triggerLabel }),
|
|
8390
8649
|
value ? /* @__PURE__ */ jsxs25("span", { className: "shrink-0 text-muted-foreground", children: [
|
|
8391
8650
|
" \xB7 ",
|
|
8392
8651
|
displayProviderLabel(value.provider)
|
|
8393
8652
|
] }) : null
|
|
8394
|
-
] }) : /* @__PURE__ */ jsxs25(
|
|
8653
|
+
] }) : /* @__PURE__ */ jsxs25(Fragment11, { children: [
|
|
8395
8654
|
/* @__PURE__ */ jsx30("span", { className: "min-w-0 truncate", children: triggerDisplay }),
|
|
8396
8655
|
/* @__PURE__ */ jsx30(ChevronDownIcon5, { className: "h-3 w-3 shrink-0 text-muted-foreground/50", "aria-hidden": "true" })
|
|
8397
8656
|
] })
|
|
@@ -8680,10 +8939,10 @@ var ThinkingSelectTrigger = forwardRef(function ThinkingSelectTrigger2({
|
|
|
8680
8939
|
...props,
|
|
8681
8940
|
children: [
|
|
8682
8941
|
trigger === "button" ? /* @__PURE__ */ jsx30(ThinkingLevelGlyph, { level: value }) : null,
|
|
8683
|
-
trigger === "slash" ? /* @__PURE__ */ jsxs25(
|
|
8942
|
+
trigger === "slash" ? /* @__PURE__ */ jsxs25(Fragment11, { children: [
|
|
8684
8943
|
/* @__PURE__ */ jsx30("span", { className: "text-muted-foreground", children: "Thinking \xB7 " }),
|
|
8685
8944
|
/* @__PURE__ */ jsx30("span", { className: "text-foreground", children: THINKING_LEVEL_STATUS_LABELS[value] })
|
|
8686
|
-
] }) : /* @__PURE__ */ jsxs25(
|
|
8945
|
+
] }) : /* @__PURE__ */ jsxs25(Fragment11, { children: [
|
|
8687
8946
|
/* @__PURE__ */ jsx30("span", { children: THINKING_LEVEL_LABELS[value] }),
|
|
8688
8947
|
/* @__PURE__ */ jsx30(ChevronDownIcon5, { className: "h-3 w-3 shrink-0 text-muted-foreground/50", "aria-hidden": "true" })
|
|
8689
8948
|
] })
|
|
@@ -8847,7 +9106,7 @@ var PromptInputFooter = ({
|
|
|
8847
9106
|
);
|
|
8848
9107
|
|
|
8849
9108
|
// src/front/primitives/prompt-input.tsx
|
|
8850
|
-
import { Fragment as
|
|
9109
|
+
import { Fragment as Fragment12, jsx as jsx32, jsxs as jsxs27 } from "react/jsx-runtime";
|
|
8851
9110
|
var PromptInput = ({
|
|
8852
9111
|
className,
|
|
8853
9112
|
accept,
|
|
@@ -9190,7 +9449,7 @@ var PromptInput = ({
|
|
|
9190
9449
|
},
|
|
9191
9450
|
[usingProvider, controller, files, onSubmit, clear]
|
|
9192
9451
|
);
|
|
9193
|
-
const inner = /* @__PURE__ */ jsxs27(
|
|
9452
|
+
const inner = /* @__PURE__ */ jsxs27(Fragment12, { children: [
|
|
9194
9453
|
/* @__PURE__ */ jsx32(
|
|
9195
9454
|
Input,
|
|
9196
9455
|
{
|
|
@@ -9359,7 +9618,7 @@ var PromptInputSubmit = ({
|
|
|
9359
9618
|
|
|
9360
9619
|
// src/front/primitives/slash-command-picker.tsx
|
|
9361
9620
|
import { useEffect as useEffect19, useMemo as useMemo14, useRef as useRef16, useState as useState18 } from "react";
|
|
9362
|
-
import { Fragment as
|
|
9621
|
+
import { Fragment as Fragment13, jsx as jsx33, jsxs as jsxs28 } from "react/jsx-runtime";
|
|
9363
9622
|
var ALL_PLUGINS = "__all__";
|
|
9364
9623
|
function commandGroup(cmd) {
|
|
9365
9624
|
if (cmd.source === "skill") return "skills";
|
|
@@ -9471,7 +9730,7 @@ function SlashCommandPicker({ query, commands, onSelect, onDismiss }) {
|
|
|
9471
9730
|
"/",
|
|
9472
9731
|
cmd.name
|
|
9473
9732
|
] }),
|
|
9474
|
-
cmd.source === "skill" ? /* @__PURE__ */ jsxs28(
|
|
9733
|
+
cmd.source === "skill" ? /* @__PURE__ */ jsxs28(Fragment13, { children: [
|
|
9475
9734
|
/* @__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
9735
|
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
9736
|
] }) : cmd.sourcePlugin ? (
|
|
@@ -9488,7 +9747,7 @@ function SlashCommandPicker({ query, commands, onSelect, onDismiss }) {
|
|
|
9488
9747
|
}
|
|
9489
9748
|
|
|
9490
9749
|
// src/front/chat/components/ComposerAttachments.tsx
|
|
9491
|
-
import { AlertCircleIcon as AlertCircleIcon2, Loader2 as
|
|
9750
|
+
import { AlertCircleIcon as AlertCircleIcon2, Loader2 as Loader22, PaperclipIcon as PaperclipIcon2 } from "lucide-react";
|
|
9492
9751
|
import { IconButton as IconButton3 } from "@hachej/boring-ui-kit";
|
|
9493
9752
|
import { jsx as jsx34, jsxs as jsxs29 } from "react/jsx-runtime";
|
|
9494
9753
|
function AttachmentButton({ disabled, className }) {
|
|
@@ -9503,7 +9762,7 @@ function AttachmentButton({ disabled, className }) {
|
|
|
9503
9762
|
onClick: () => {
|
|
9504
9763
|
if (!disabled) attachments.openFileDialog();
|
|
9505
9764
|
},
|
|
9506
|
-
className: cn(composerActionClass, "!h-10 !w-10", className),
|
|
9765
|
+
className: cn(composerActionClass, "composer-attachment-button !h-10 !w-10", className),
|
|
9507
9766
|
"aria-label": "Attach files",
|
|
9508
9767
|
title: disabled ? "Attachments are available when the composer is ready." : "Attach files",
|
|
9509
9768
|
children: /* @__PURE__ */ jsx34(PaperclipIcon2, { className: "h-4 w-4", strokeWidth: 1.75 })
|
|
@@ -9526,18 +9785,18 @@ function AttachmentsList() {
|
|
|
9526
9785
|
data: file,
|
|
9527
9786
|
onRemove: () => attachments.remove(file.id),
|
|
9528
9787
|
className: cn(
|
|
9529
|
-
"!h-9 !gap-2 !rounded-full !border-input/80 !bg-muted/40 !pl-1 !pr-2",
|
|
9788
|
+
"composer-attachment-chip !h-9 !gap-2 !rounded-full !border-input/80 !bg-muted/40 !pl-1 !pr-2",
|
|
9530
9789
|
"transition-colors hover:!bg-muted/70 hover:!text-foreground",
|
|
9531
9790
|
file.status === "error" && "!border-destructive/50 !bg-destructive/10"
|
|
9532
9791
|
),
|
|
9533
9792
|
children: [
|
|
9534
9793
|
/* @__PURE__ */ jsxs29("div", { className: "relative shrink-0", children: [
|
|
9535
9794
|
/* @__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(
|
|
9795
|
+
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
9796
|
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
9797
|
] }),
|
|
9539
9798
|
/* @__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" })
|
|
9799
|
+
/* @__PURE__ */ jsx34(AttachmentRemove, { className: "composer-attachment-remove !size-5 !rounded-full !opacity-100 text-muted-foreground/80 hover:text-foreground" })
|
|
9541
9800
|
]
|
|
9542
9801
|
},
|
|
9543
9802
|
file.id
|
|
@@ -9629,6 +9888,7 @@ function PiChatComposerSurface({
|
|
|
9629
9888
|
onSubmitMessage,
|
|
9630
9889
|
onStop
|
|
9631
9890
|
}) {
|
|
9891
|
+
const prefersReducedMotion = useReducedMotion();
|
|
9632
9892
|
const workspaceRequestId = getHeaderValue(requestHeaders, "x-boring-workspace-id");
|
|
9633
9893
|
const composerContributions = useComposerContributions();
|
|
9634
9894
|
const uploadAttachment = useCallback16((file) => uploadFile(file, {
|
|
@@ -9658,7 +9918,7 @@ function PiChatComposerSurface({
|
|
|
9658
9918
|
rail?.style.setProperty("--composer-input-group-height", `${inputGroupHeight}px`);
|
|
9659
9919
|
rail?.setAttribute("data-composer-multiline", inputGroupHeight > COMPOSER_INPUT_GROUP_MIN_HEIGHT ? "true" : "false");
|
|
9660
9920
|
}, []);
|
|
9661
|
-
|
|
9921
|
+
useLayoutEffect3(() => {
|
|
9662
9922
|
resizeTextarea(textareaRef.current);
|
|
9663
9923
|
}, [draft, resizeTextarea, textareaRef]);
|
|
9664
9924
|
return /* @__PURE__ */ jsxs30("div", { className: cn(
|
|
@@ -9681,15 +9941,15 @@ function PiChatComposerSurface({
|
|
|
9681
9941
|
"data-testid": isStreaming ? "chat-working" : void 0,
|
|
9682
9942
|
role: isStreaming ? "status" : void 0,
|
|
9683
9943
|
"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-
|
|
9944
|
+
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
9945
|
children: [
|
|
9686
9946
|
/* @__PURE__ */ jsx35(
|
|
9687
9947
|
motion2.span,
|
|
9688
9948
|
{
|
|
9689
9949
|
"aria-hidden": "true",
|
|
9690
9950
|
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" }
|
|
9951
|
+
animate: prefersReducedMotion ? { opacity: 1 } : { opacity: [0.35, 1, 0.35] },
|
|
9952
|
+
transition: prefersReducedMotion ? { duration: 0 } : { duration: 1.2, repeat: Infinity, ease: "easeInOut" }
|
|
9693
9953
|
}
|
|
9694
9954
|
),
|
|
9695
9955
|
/* @__PURE__ */ jsx35("span", { children: "Working\u2026" })
|
|
@@ -9881,7 +10141,7 @@ function PiChatComposerSurface({
|
|
|
9881
10141
|
onStop,
|
|
9882
10142
|
disabled: submitDisabled,
|
|
9883
10143
|
className: cn(
|
|
9884
|
-
"h-8 w-8 shrink-0 rounded-full",
|
|
10144
|
+
"composer-submit-control h-8 w-8 shrink-0 rounded-full",
|
|
9885
10145
|
"bg-foreground text-background",
|
|
9886
10146
|
"transition-all duration-150 ease-[cubic-bezier(0.22,1,0.36,1)]",
|
|
9887
10147
|
"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 +10180,7 @@ function PiChatComposerSurface({
|
|
|
9920
10180
|
disabled: isStreaming || modelControlled,
|
|
9921
10181
|
trigger: "slash",
|
|
9922
10182
|
open: modelPickerOpen,
|
|
10183
|
+
className: "composer-settings-trigger",
|
|
9923
10184
|
onClick: () => {
|
|
9924
10185
|
if (modelPickerOpen) {
|
|
9925
10186
|
onSetModelPickerOpen(false);
|
|
@@ -9936,6 +10197,7 @@ function PiChatComposerSurface({
|
|
|
9936
10197
|
disabled: isStreaming || thinkingControlled,
|
|
9937
10198
|
trigger: "slash",
|
|
9938
10199
|
open: thinkingPickerOpen,
|
|
10200
|
+
className: "composer-settings-trigger",
|
|
9939
10201
|
onClick: () => {
|
|
9940
10202
|
if (thinkingPickerOpen) {
|
|
9941
10203
|
onSetThinkingPickerOpen(false);
|
|
@@ -9958,86 +10220,6 @@ function getHeaderValue(headers, name) {
|
|
|
9958
10220
|
|
|
9959
10221
|
// src/front/chat/piChatPanelHooks.ts
|
|
9960
10222
|
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
10223
|
|
|
10042
10224
|
// src/front/chat/piChatPanelUtils.ts
|
|
10043
10225
|
function headersContentKey(headers) {
|
|
@@ -10144,9 +10326,103 @@ function errorMessage2(error, fallback) {
|
|
|
10144
10326
|
return error instanceof Error && error.message ? error.message : fallback;
|
|
10145
10327
|
}
|
|
10146
10328
|
|
|
10329
|
+
// src/front/chat/piChatPanelHooks.ts
|
|
10330
|
+
function useExternalRemotePiSession({
|
|
10331
|
+
sessionId,
|
|
10332
|
+
agentTypeId,
|
|
10333
|
+
workspaceId,
|
|
10334
|
+
storageScope,
|
|
10335
|
+
apiBaseUrl,
|
|
10336
|
+
requestHeaders,
|
|
10337
|
+
fetch: fetch2,
|
|
10338
|
+
createRemoteSession,
|
|
10339
|
+
remoteSessionOptions,
|
|
10340
|
+
enabled = true
|
|
10341
|
+
}) {
|
|
10342
|
+
const [session, setSession] = useState19();
|
|
10343
|
+
const remoteSessionOptionsRef = useRef17(remoteSessionOptions);
|
|
10344
|
+
remoteSessionOptionsRef.current = remoteSessionOptions;
|
|
10345
|
+
const requestHeadersKey = headersContentKey(requestHeaders);
|
|
10346
|
+
const stableRequestHeaders = useMemo15(
|
|
10347
|
+
() => normalizedHeadersFromContentKey(requestHeadersKey),
|
|
10348
|
+
[requestHeadersKey]
|
|
10349
|
+
);
|
|
10350
|
+
const remoteSessionOptionsKey = useMemo15(
|
|
10351
|
+
() => remoteSessionOptionsIdentity2(remoteSessionOptions),
|
|
10352
|
+
[remoteSessionOptions]
|
|
10353
|
+
);
|
|
10354
|
+
useEffect20(() => {
|
|
10355
|
+
if (!sessionId) {
|
|
10356
|
+
setSession(void 0);
|
|
10357
|
+
return;
|
|
10358
|
+
}
|
|
10359
|
+
const next = (createRemoteSession ?? createRemotePiSession)({
|
|
10360
|
+
...remoteSessionOptionsRef.current,
|
|
10361
|
+
autoStart: false,
|
|
10362
|
+
sessionId,
|
|
10363
|
+
agentTypeId,
|
|
10364
|
+
workspaceId,
|
|
10365
|
+
storageScope,
|
|
10366
|
+
apiBaseUrl,
|
|
10367
|
+
headers: stableRequestHeaders,
|
|
10368
|
+
fetch: fetch2
|
|
10369
|
+
});
|
|
10370
|
+
setSession(next);
|
|
10371
|
+
return () => next.dispose();
|
|
10372
|
+
}, [agentTypeId, apiBaseUrl, createRemoteSession, fetch2, remoteSessionOptionsKey, sessionId, stableRequestHeaders, storageScope, workspaceId]);
|
|
10373
|
+
useEffect20(() => {
|
|
10374
|
+
if (!session) return;
|
|
10375
|
+
if (enabled) session.resumeStream?.();
|
|
10376
|
+
else session.suspendStream?.();
|
|
10377
|
+
}, [enabled, session]);
|
|
10378
|
+
return session;
|
|
10379
|
+
}
|
|
10380
|
+
var remoteSessionOptionObjectIds2 = /* @__PURE__ */ new WeakMap();
|
|
10381
|
+
var remoteSessionOptionObjectSeq2 = 0;
|
|
10382
|
+
function remoteSessionOptionObjectIdentity2(value) {
|
|
10383
|
+
if (typeof value !== "object" && typeof value !== "function" || value === null) return void 0;
|
|
10384
|
+
const object = value;
|
|
10385
|
+
let id = remoteSessionOptionObjectIds2.get(object);
|
|
10386
|
+
if (!id) {
|
|
10387
|
+
id = ++remoteSessionOptionObjectSeq2;
|
|
10388
|
+
remoteSessionOptionObjectIds2.set(object, id);
|
|
10389
|
+
}
|
|
10390
|
+
return String(id);
|
|
10391
|
+
}
|
|
10392
|
+
function remoteSessionOptionsIdentity2(options) {
|
|
10393
|
+
if (!options) return "{}";
|
|
10394
|
+
return JSON.stringify({
|
|
10395
|
+
autoStart: options.autoStart,
|
|
10396
|
+
requestTimeoutMs: options.requestTimeoutMs,
|
|
10397
|
+
onEvent: remoteSessionOptionObjectIdentity2(options.onEvent),
|
|
10398
|
+
storeOptions: remoteSessionOptionObjectIdentity2(options.storeOptions),
|
|
10399
|
+
setTimeoutFn: remoteSessionOptionObjectIdentity2(options.setTimeoutFn),
|
|
10400
|
+
clearTimeoutFn: remoteSessionOptionObjectIdentity2(options.clearTimeoutFn),
|
|
10401
|
+
reconnect: options.reconnect ? {
|
|
10402
|
+
baseMs: options.reconnect.baseMs,
|
|
10403
|
+
maxMs: options.reconnect.maxMs,
|
|
10404
|
+
jitterRatio: options.reconnect.jitterRatio,
|
|
10405
|
+
random: remoteSessionOptionObjectIdentity2(options.reconnect.random)
|
|
10406
|
+
} : void 0,
|
|
10407
|
+
debug: options.debug ? {
|
|
10408
|
+
largeStateWarningBytes: options.debug.largeStateWarningBytes,
|
|
10409
|
+
largeStateWarningMessages: options.debug.largeStateWarningMessages,
|
|
10410
|
+
onWarning: remoteSessionOptionObjectIdentity2(options.debug.onWarning)
|
|
10411
|
+
} : void 0
|
|
10412
|
+
});
|
|
10413
|
+
}
|
|
10414
|
+
function useRemotePiSessionState(session) {
|
|
10415
|
+
return useSyncExternalStore(
|
|
10416
|
+
useCallback17((listener) => session?.subscribe(listener) ?? (() => {
|
|
10417
|
+
}), [session]),
|
|
10418
|
+
useCallback17(() => session?.getState(), [session]),
|
|
10419
|
+
useCallback17(() => session?.getState(), [session])
|
|
10420
|
+
);
|
|
10421
|
+
}
|
|
10422
|
+
|
|
10147
10423
|
// src/front/chat/PiChatPanel.tsx
|
|
10148
10424
|
import { jsx as jsx36, jsxs as jsxs31 } from "react/jsx-runtime";
|
|
10149
|
-
var DebugDrawer2 = lazy(() => import("../DebugDrawer-
|
|
10425
|
+
var DebugDrawer2 = lazy(() => import("../DebugDrawer-2TO5SE2D.js").then((m) => ({ default: m.DebugDrawer })));
|
|
10150
10426
|
var EMPTY_COMMANDS = [];
|
|
10151
10427
|
var EMPTY_COMMAND_NAMES = [];
|
|
10152
10428
|
var EMPTY_BLOCKERS = [];
|
|
@@ -10282,6 +10558,7 @@ function PiChatPanel({
|
|
|
10282
10558
|
const selectedSessionPending = Boolean(activeSessionId && !selectedChatState);
|
|
10283
10559
|
const modelDiscoveryEnabled = serverResourcesEnabled && availableModels === void 0;
|
|
10284
10560
|
const modelDiscovery = useChatModelSelection({
|
|
10561
|
+
agentTypeId,
|
|
10285
10562
|
apiBaseUrl,
|
|
10286
10563
|
defaultModel,
|
|
10287
10564
|
fetch: fetch2,
|
|
@@ -10385,6 +10662,7 @@ function PiChatPanel({
|
|
|
10385
10662
|
return next;
|
|
10386
10663
|
}, [apiBaseUrl, commands, contributedCommands, excludeBuiltinCommands, extraCommands, hotReloadEnabled, normalizedRequestHeaders, serverResourcesEnabled, serverSkillsRefreshKey, storageScope]);
|
|
10387
10664
|
const commandsStamp = useServerCommands({
|
|
10665
|
+
agentTypeId,
|
|
10388
10666
|
registry,
|
|
10389
10667
|
requestHeaders: normalizedRequestHeaders,
|
|
10390
10668
|
sessionId: activeSessionId ?? "default",
|
|
@@ -10414,7 +10692,8 @@ function PiChatPanel({
|
|
|
10414
10692
|
const queuePreview = selectedChatState ? selectQueuePreview(selectedChatState) : [];
|
|
10415
10693
|
const messages = canonicalMessages;
|
|
10416
10694
|
const userHistory = useMemo16(() => selectComposerHistoryFromCanonicalUsers(canonicalMessages), [canonicalMessages]);
|
|
10417
|
-
const
|
|
10695
|
+
const awaitingHydration = Boolean(hydrateMessages && selectedChatState && !selectedChatState.hydrated);
|
|
10696
|
+
const emptyStateHydrating = statusForState(selectedChatState, sessionsLoading || chatStatePending || selectedSessionPending) === "hydrating" || awaitingHydration;
|
|
10418
10697
|
const emptyHero = emptyPlacement === "hero" && messages.length === 0 && queuePreview.length === 0 && !emptyStateHydrating;
|
|
10419
10698
|
const debugState = selectedPiSession?.getDebugState();
|
|
10420
10699
|
const composerBlocked = workspaceWarmupBlocked || activeBlockers.length > 0;
|
|
@@ -10430,8 +10709,9 @@ function PiChatPanel({
|
|
|
10430
10709
|
text: `Large chat state: ${debugState.largeStateWarning.messageCount} messages, approximately ${debugState.largeStateWarning.approxBytes} bytes.`,
|
|
10431
10710
|
dismissible: true
|
|
10432
10711
|
}] : [];
|
|
10433
|
-
|
|
10434
|
-
|
|
10712
|
+
const combined = [...fromState, ...sessionNotice, ...largeStateNotice, ...localNotices].filter((notice) => !dismissedNoticeIds.has(notice.id));
|
|
10713
|
+
return filterCompetingNoiseNotices(combined, messages.length === 0);
|
|
10714
|
+
}, [debug, debugState?.largeStateWarning, dismissedNoticeIds, localNotices, messages.length, selectedChatState, sessionsError]);
|
|
10435
10715
|
const addLocalNotice = useCallback18((notice) => {
|
|
10436
10716
|
setLocalNotices((previous) => {
|
|
10437
10717
|
const next = previous.filter((candidate) => candidate.id !== notice.id);
|
|
@@ -10462,11 +10742,11 @@ function PiChatPanel({
|
|
|
10462
10742
|
if (resetInProgressRef.current) return;
|
|
10463
10743
|
if (autoCreateInFlightRef.current) return;
|
|
10464
10744
|
autoCreateInFlightRef.current = true;
|
|
10465
|
-
void sessions.create().catch((error) => {
|
|
10745
|
+
void sessions.create({ resumeSessionId: sessions.resumeSessionId }).catch((error) => {
|
|
10466
10746
|
autoCreateInFlightRef.current = false;
|
|
10467
10747
|
addLocalNotice({ id: "session-auto-create-error", level: "error", text: errorMessage2(error, "Could not create a chat session."), dismissible: true });
|
|
10468
10748
|
});
|
|
10469
|
-
}, [activeSessionId, addLocalNotice, externalSessionId, sessionList.length, sessions.create, sessionsError, sessionsLoading]);
|
|
10749
|
+
}, [activeSessionId, addLocalNotice, externalSessionId, sessionList.length, sessions.create, sessions.resumeSessionId, sessionsError, sessionsLoading]);
|
|
10470
10750
|
useEffect21(() => {
|
|
10471
10751
|
if (externalSessionId || sessionsError || activeSessionId || sessionList.length > 0) {
|
|
10472
10752
|
autoCreateInFlightRef.current = false;
|
|
@@ -10914,14 +11194,18 @@ function PiChatPanel({
|
|
|
10914
11194
|
}, [isStreaming]);
|
|
10915
11195
|
useEffect21(() => {
|
|
10916
11196
|
if (typeof window === "undefined" || !activeChatSessionId) return;
|
|
10917
|
-
window.dispatchEvent(new CustomEvent("boring:chat-session-status", {
|
|
11197
|
+
const emitStatus = () => window.dispatchEvent(new CustomEvent("boring:chat-session-status", {
|
|
10918
11198
|
detail: {
|
|
11199
|
+
...workspaceId ? { workspaceId } : {},
|
|
10919
11200
|
sessionId: activeChatSessionId,
|
|
10920
11201
|
...agentTypeId ? { agentTypeId } : {},
|
|
10921
11202
|
working: isStreaming
|
|
10922
11203
|
}
|
|
10923
11204
|
}));
|
|
10924
|
-
|
|
11205
|
+
emitStatus();
|
|
11206
|
+
window.addEventListener("boring:chat-session-status-request", emitStatus);
|
|
11207
|
+
return () => window.removeEventListener("boring:chat-session-status-request", emitStatus);
|
|
11208
|
+
}, [activeChatSessionId, agentTypeId, isStreaming, workspaceId]);
|
|
10925
11209
|
const onTextareaKeyDown = useCallback18((event) => {
|
|
10926
11210
|
if (event.key === "Escape" && isStreaming) {
|
|
10927
11211
|
if (event.defaultPrevented || mentionState !== null || slashQuery !== null) {
|
|
@@ -11011,6 +11295,7 @@ function PiChatPanel({
|
|
|
11011
11295
|
{
|
|
11012
11296
|
"data-boring-agent": "",
|
|
11013
11297
|
"data-boring-agent-part": "chat",
|
|
11298
|
+
"data-agent-type-id": agentTypeId,
|
|
11014
11299
|
"data-pi-chat-session-id": activeSessionId,
|
|
11015
11300
|
"data-pi-chat-connection": debugState?.connection ?? "disconnected",
|
|
11016
11301
|
"data-pi-chat-last-seq": debugState?.lastSeq ?? 0,
|
|
@@ -11089,6 +11374,7 @@ function PiChatPanel({
|
|
|
11089
11374
|
debug ? /* @__PURE__ */ jsx36(Suspense, { fallback: null, children: /* @__PURE__ */ jsx36("div", { "aria-label": "Chat debug metadata", className: "contents", role: "region", children: /* @__PURE__ */ jsx36(
|
|
11090
11375
|
DebugDrawer2,
|
|
11091
11376
|
{
|
|
11377
|
+
agentTypeId,
|
|
11092
11378
|
apiBaseUrl,
|
|
11093
11379
|
fetch: fetch2,
|
|
11094
11380
|
sessionId: activeSessionId ?? activeChatSessionId ?? "unknown",
|
|
@@ -11105,6 +11391,207 @@ function PiChatPanel({
|
|
|
11105
11391
|
) });
|
|
11106
11392
|
}
|
|
11107
11393
|
|
|
11394
|
+
// src/front/chat/useAddressedAgentSelection.ts
|
|
11395
|
+
import { useCallback as useCallback19, useEffect as useEffect22, useMemo as useMemo17, useState as useState21 } from "react";
|
|
11396
|
+
|
|
11397
|
+
// src/front/chat/gatewayResponseError.ts
|
|
11398
|
+
var RUNTIME_SCOPE_MISMATCH_MESSAGE = "This chat belongs to a previous runtime configuration and can no longer be changed.";
|
|
11399
|
+
var GatewayResponseError = class extends Error {
|
|
11400
|
+
constructor(status, message, code3, details, body, operation) {
|
|
11401
|
+
super(message);
|
|
11402
|
+
this.status = status;
|
|
11403
|
+
this.code = code3;
|
|
11404
|
+
this.details = details;
|
|
11405
|
+
this.body = body;
|
|
11406
|
+
this.operation = operation;
|
|
11407
|
+
this.name = "GatewayResponseError";
|
|
11408
|
+
this.errorCode = code3;
|
|
11409
|
+
}
|
|
11410
|
+
status;
|
|
11411
|
+
code;
|
|
11412
|
+
details;
|
|
11413
|
+
body;
|
|
11414
|
+
operation;
|
|
11415
|
+
errorCode;
|
|
11416
|
+
};
|
|
11417
|
+
async function gatewayResponseError(response, fallbackMessage, operation) {
|
|
11418
|
+
const body = await safeReadJson2(response);
|
|
11419
|
+
return gatewayResponseErrorFromBody(response.status, body, fallbackMessage, operation);
|
|
11420
|
+
}
|
|
11421
|
+
function gatewayResponseErrorFromBody(status, body, fallbackMessage, operation) {
|
|
11422
|
+
const payload = errorPayload(body);
|
|
11423
|
+
const code3 = gatewayResponseErrorCode(payload?.code);
|
|
11424
|
+
const serverMessage = typeof payload?.message === "string" && payload.message.trim().length > 0 ? payload.message : fallbackMessage;
|
|
11425
|
+
const message = code3 === AgentGatewayErrorCode.AGENT_SESSION_RUNTIME_SCOPE_MISMATCH ? RUNTIME_SCOPE_MISMATCH_MESSAGE : serverMessage;
|
|
11426
|
+
const details = isJsonValue(payload?.details) ? payload.details : void 0;
|
|
11427
|
+
return new GatewayResponseError(status, message, code3, details, body, operation);
|
|
11428
|
+
}
|
|
11429
|
+
function gatewayResponseErrorCode(value) {
|
|
11430
|
+
const sharedCode = ErrorCode.safeParse(value);
|
|
11431
|
+
if (sharedCode.success) return sharedCode.data;
|
|
11432
|
+
return typeof value === "string" && AGENT_GATEWAY_ERROR_CODES.includes(value) ? value : void 0;
|
|
11433
|
+
}
|
|
11434
|
+
function errorResponseCode(error) {
|
|
11435
|
+
if (error instanceof GatewayResponseError) return error.code;
|
|
11436
|
+
const record = typeof error === "object" && error !== null ? error : void 0;
|
|
11437
|
+
return gatewayResponseErrorCode(record?.code ?? record?.errorCode);
|
|
11438
|
+
}
|
|
11439
|
+
function isRuntimeScopeMismatchError(error) {
|
|
11440
|
+
return errorResponseCode(error) === AgentGatewayErrorCode.AGENT_SESSION_RUNTIME_SCOPE_MISMATCH;
|
|
11441
|
+
}
|
|
11442
|
+
async function safeReadJson2(response) {
|
|
11443
|
+
const text = await response.text();
|
|
11444
|
+
if (!text) return void 0;
|
|
11445
|
+
try {
|
|
11446
|
+
return JSON.parse(text);
|
|
11447
|
+
} catch {
|
|
11448
|
+
return void 0;
|
|
11449
|
+
}
|
|
11450
|
+
}
|
|
11451
|
+
function errorPayload(body) {
|
|
11452
|
+
if (typeof body !== "object" || body === null) return void 0;
|
|
11453
|
+
const record = body;
|
|
11454
|
+
return typeof record.error === "object" && record.error !== null ? record.error : record;
|
|
11455
|
+
}
|
|
11456
|
+
function isJsonValue(value) {
|
|
11457
|
+
if (value === null || ["string", "number", "boolean"].includes(typeof value)) return true;
|
|
11458
|
+
if (Array.isArray(value)) return value.every(isJsonValue);
|
|
11459
|
+
return typeof value === "object" && value !== null && Object.values(value).every(isJsonValue);
|
|
11460
|
+
}
|
|
11461
|
+
|
|
11462
|
+
// src/front/chat/useAddressedAgentSelection.ts
|
|
11463
|
+
var inFlightDiscoveries = /* @__PURE__ */ new WeakMap();
|
|
11464
|
+
function useAddressedAgentSelection({
|
|
11465
|
+
apiBaseUrl,
|
|
11466
|
+
requestHeaders,
|
|
11467
|
+
storageScope,
|
|
11468
|
+
fetch: fetch2,
|
|
11469
|
+
enabled = false
|
|
11470
|
+
} = {}) {
|
|
11471
|
+
const normalizedBaseUrl = apiBaseUrl?.replace(/\/$/, "") ?? "";
|
|
11472
|
+
const headersKey = useMemo17(
|
|
11473
|
+
() => JSON.stringify({
|
|
11474
|
+
storageScope: storageScope ?? "",
|
|
11475
|
+
headers: Object.entries(requestHeaders ?? {}).sort(([a], [b]) => a.localeCompare(b))
|
|
11476
|
+
}),
|
|
11477
|
+
[requestHeaders, storageScope]
|
|
11478
|
+
);
|
|
11479
|
+
const discoveryKey = `${normalizedBaseUrl}
|
|
11480
|
+
${headersKey}`;
|
|
11481
|
+
const fetchImpl = fetch2 ?? globalThis.fetch;
|
|
11482
|
+
const [state, setState] = useState21(() => ({
|
|
11483
|
+
discoveryKey: enabled ? "" : discoveryKey,
|
|
11484
|
+
agents: [],
|
|
11485
|
+
selectedAgentTypeId: void 0,
|
|
11486
|
+
loading: enabled,
|
|
11487
|
+
error: void 0
|
|
11488
|
+
}));
|
|
11489
|
+
useEffect22(() => {
|
|
11490
|
+
if (!enabled) {
|
|
11491
|
+
setState({
|
|
11492
|
+
discoveryKey,
|
|
11493
|
+
agents: [],
|
|
11494
|
+
selectedAgentTypeId: void 0,
|
|
11495
|
+
loading: false,
|
|
11496
|
+
error: void 0
|
|
11497
|
+
});
|
|
11498
|
+
return;
|
|
11499
|
+
}
|
|
11500
|
+
let cancelled = false;
|
|
11501
|
+
setState((previous) => ({
|
|
11502
|
+
discoveryKey,
|
|
11503
|
+
agents: previous.discoveryKey === discoveryKey ? previous.agents : [],
|
|
11504
|
+
selectedAgentTypeId: previous.discoveryKey === discoveryKey ? previous.selectedAgentTypeId : void 0,
|
|
11505
|
+
loading: true,
|
|
11506
|
+
error: void 0
|
|
11507
|
+
}));
|
|
11508
|
+
void discoverAgents(fetchImpl, discoveryKey, `${normalizedBaseUrl}/api/v1/agents`, {
|
|
11509
|
+
headers: scopedHeaders(requestHeaders, storageScope)
|
|
11510
|
+
}).then((agents2) => {
|
|
11511
|
+
if (cancelled) return;
|
|
11512
|
+
setState((previous) => ({
|
|
11513
|
+
discoveryKey,
|
|
11514
|
+
agents: agents2,
|
|
11515
|
+
selectedAgentTypeId: agents2.some((agent) => agent.agentTypeId === previous.selectedAgentTypeId) ? previous.selectedAgentTypeId : agents2[0]?.agentTypeId,
|
|
11516
|
+
loading: false,
|
|
11517
|
+
error: void 0
|
|
11518
|
+
}));
|
|
11519
|
+
}).catch((error) => {
|
|
11520
|
+
if (cancelled) return;
|
|
11521
|
+
setState({
|
|
11522
|
+
discoveryKey,
|
|
11523
|
+
agents: [],
|
|
11524
|
+
selectedAgentTypeId: void 0,
|
|
11525
|
+
loading: false,
|
|
11526
|
+
error: error instanceof Error ? error : new Error(String(error))
|
|
11527
|
+
});
|
|
11528
|
+
});
|
|
11529
|
+
return () => {
|
|
11530
|
+
cancelled = true;
|
|
11531
|
+
};
|
|
11532
|
+
}, [discoveryKey, enabled, fetchImpl, normalizedBaseUrl]);
|
|
11533
|
+
const current = state.discoveryKey === discoveryKey;
|
|
11534
|
+
const agents = enabled && current ? state.agents : [];
|
|
11535
|
+
const selectedAgentTypeId = enabled && current ? state.selectedAgentTypeId : void 0;
|
|
11536
|
+
const selectAgentTypeId = useCallback19((agentTypeId) => {
|
|
11537
|
+
setState((previous) => {
|
|
11538
|
+
if (previous.discoveryKey !== discoveryKey) return previous;
|
|
11539
|
+
if (!previous.agents.some((agent) => agent.agentTypeId === agentTypeId)) return previous;
|
|
11540
|
+
return { ...previous, selectedAgentTypeId: agentTypeId };
|
|
11541
|
+
});
|
|
11542
|
+
}, [discoveryKey]);
|
|
11543
|
+
return {
|
|
11544
|
+
agents,
|
|
11545
|
+
selectedAgentTypeId,
|
|
11546
|
+
loading: enabled && (!current || state.loading),
|
|
11547
|
+
error: enabled && current ? state.error : void 0,
|
|
11548
|
+
selectAgentTypeId
|
|
11549
|
+
};
|
|
11550
|
+
}
|
|
11551
|
+
function discoverAgents(fetchImpl, discoveryKey, url, init) {
|
|
11552
|
+
let requests = inFlightDiscoveries.get(fetchImpl);
|
|
11553
|
+
if (!requests) {
|
|
11554
|
+
requests = /* @__PURE__ */ new Map();
|
|
11555
|
+
inFlightDiscoveries.set(fetchImpl, requests);
|
|
11556
|
+
}
|
|
11557
|
+
const existing = requests.get(discoveryKey);
|
|
11558
|
+
if (existing) return existing;
|
|
11559
|
+
const request = fetchImpl(url, init).then(async (response) => {
|
|
11560
|
+
if (!response.ok) throw await gatewayResponseError(response, "Failed to load agents.", "load agents");
|
|
11561
|
+
return parseAgentOptions(await response.json());
|
|
11562
|
+
});
|
|
11563
|
+
requests.set(discoveryKey, request);
|
|
11564
|
+
void request.finally(() => {
|
|
11565
|
+
if (requests?.get(discoveryKey) === request) requests.delete(discoveryKey);
|
|
11566
|
+
}).catch(() => void 0);
|
|
11567
|
+
return request;
|
|
11568
|
+
}
|
|
11569
|
+
function parseAgentOptions(value) {
|
|
11570
|
+
if (!Array.isArray(value)) throw new Error("Failed to load agents: invalid response");
|
|
11571
|
+
return value.map((item) => {
|
|
11572
|
+
if (typeof item !== "object" || item === null) throw new Error("Failed to load agents: invalid response");
|
|
11573
|
+
const record = item;
|
|
11574
|
+
if (typeof record.agentTypeId !== "string" || record.agentTypeId.length === 0) {
|
|
11575
|
+
throw new Error("Failed to load agents: invalid response");
|
|
11576
|
+
}
|
|
11577
|
+
return {
|
|
11578
|
+
agentTypeId: record.agentTypeId,
|
|
11579
|
+
label: typeof record.label === "string" && record.label.length > 0 ? record.label : record.agentTypeId,
|
|
11580
|
+
...typeof record.description === "string" ? { description: record.description } : {},
|
|
11581
|
+
...Array.isArray(record.pluginIds) ? { pluginIds: record.pluginIds.filter((pluginId) => typeof pluginId === "string" && pluginId.length > 0) } : {}
|
|
11582
|
+
};
|
|
11583
|
+
});
|
|
11584
|
+
}
|
|
11585
|
+
function scopedHeaders(headers, storageScope) {
|
|
11586
|
+
const result = {};
|
|
11587
|
+
for (const [key, value] of Object.entries(headers ?? {})) {
|
|
11588
|
+
if (typeof value === "string") result[key] = value;
|
|
11589
|
+
}
|
|
11590
|
+
const hasStorageScope = Object.keys(result).some((key) => key.toLowerCase() === "x-boring-storage-scope");
|
|
11591
|
+
if (storageScope && !hasStorageScope) result["x-boring-storage-scope"] = storageScope;
|
|
11592
|
+
return result;
|
|
11593
|
+
}
|
|
11594
|
+
|
|
11108
11595
|
// src/front/commands.ts
|
|
11109
11596
|
var AGENT_COMMAND_SOURCE = "agent";
|
|
11110
11597
|
function getAgentCommands(options = {}) {
|
|
@@ -11150,6 +11637,7 @@ export {
|
|
|
11150
11637
|
ConversationContent,
|
|
11151
11638
|
ConversationScrollButton,
|
|
11152
11639
|
DebugDrawer,
|
|
11640
|
+
GatewayResponseError,
|
|
11153
11641
|
Message,
|
|
11154
11642
|
MessageAction,
|
|
11155
11643
|
MessageActions,
|
|
@@ -11165,6 +11653,7 @@ export {
|
|
|
11165
11653
|
PromptInputFooter,
|
|
11166
11654
|
PromptInputSubmit,
|
|
11167
11655
|
PromptInputTextarea,
|
|
11656
|
+
RUNTIME_SCOPE_MISMATCH_MESSAGE,
|
|
11168
11657
|
Reasoning,
|
|
11169
11658
|
ReasoningContent,
|
|
11170
11659
|
ReasoningTrigger,
|
|
@@ -11175,12 +11664,15 @@ export {
|
|
|
11175
11664
|
ToolHeader,
|
|
11176
11665
|
activeSessionStorageKey,
|
|
11177
11666
|
builtinCommands,
|
|
11178
|
-
clearActiveSessionId,
|
|
11179
11667
|
cn,
|
|
11180
11668
|
createCommandRegistry,
|
|
11181
11669
|
defaultChatSuggestions,
|
|
11182
11670
|
defaultToolRenderers,
|
|
11671
|
+
errorResponseCode,
|
|
11672
|
+
gatewayResponseError,
|
|
11673
|
+
gatewayResponseErrorFromBody,
|
|
11183
11674
|
getAgentCommands,
|
|
11675
|
+
isRuntimeScopeMismatchError,
|
|
11184
11676
|
matchPiSessionSearch,
|
|
11185
11677
|
mergeShadcnToolRenderers,
|
|
11186
11678
|
mergeToolRenderers,
|
|
@@ -11190,6 +11682,7 @@ export {
|
|
|
11190
11682
|
resolveToolRenderer,
|
|
11191
11683
|
searchPiSessions,
|
|
11192
11684
|
uploadFile,
|
|
11685
|
+
useAddressedAgentSelection,
|
|
11193
11686
|
useOpenArtifact,
|
|
11194
11687
|
usePiSessions,
|
|
11195
11688
|
writeActiveSessionId
|