@hachej/boring-agent 0.1.22 → 0.1.24
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/{agentPluginEvents-zyIvVjsA.d.ts → agentPluginEvents-CF0BGY4D.d.ts} +11 -1
- package/dist/{chunk-6R7O6BHW.js → chunk-FILARC5X.js} +6 -28
- package/dist/chunk-HDOSWEXG.js +29 -0
- package/dist/eval/index.js +33 -1
- package/dist/front/index.d.ts +49 -4
- package/dist/front/index.js +495 -97
- package/dist/front/styles.css +69 -21
- package/dist/{harness-BCit36Ha.d.ts → harness-CQ0uw6xW.d.ts} +5 -0
- package/dist/server/index.d.ts +23 -6
- package/dist/server/index.js +971 -615
- package/dist/shared/index.d.ts +17 -17
- package/dist/shared/index.js +8 -6
- package/docs/ERROR_CODES.md +4 -0
- package/docs/README.md +1 -0
- package/docs/runtime.md +133 -2
- package/package.json +9 -8
package/dist/front/index.js
CHANGED
|
@@ -3,6 +3,9 @@ import {
|
|
|
3
3
|
WORKSPACE_AGENT_PLUGINS_RELOADED_EVENT,
|
|
4
4
|
extractToolUiMetadata
|
|
5
5
|
} from "../chunk-B5JECXMG.js";
|
|
6
|
+
import {
|
|
7
|
+
ErrorCode
|
|
8
|
+
} from "../chunk-FILARC5X.js";
|
|
6
9
|
import {
|
|
7
10
|
DebugDrawer,
|
|
8
11
|
cn
|
|
@@ -256,6 +259,7 @@ import { DefaultChatTransport } from "ai";
|
|
|
256
259
|
import { useEffect as useEffect4, useMemo as useMemo2, useRef as useRef4, useState as useState3 } from "react";
|
|
257
260
|
function useAgentChat(opts) {
|
|
258
261
|
const { sessionId } = opts;
|
|
262
|
+
const hydrateMessages = opts.hydrateMessages ?? true;
|
|
259
263
|
const optsRef = useRef4(opts);
|
|
260
264
|
optsRef.current = opts;
|
|
261
265
|
const transport = useMemo2(
|
|
@@ -273,7 +277,7 @@ function useAgentChat(opts) {
|
|
|
273
277
|
const chat = useChat({
|
|
274
278
|
id: sessionId,
|
|
275
279
|
transport,
|
|
276
|
-
resume:
|
|
280
|
+
resume: hydrateMessages,
|
|
277
281
|
// Match AI SDK's documented React smoothing knob: render at most every
|
|
278
282
|
// ~50ms while chunks stream instead of once per incoming chunk. This only
|
|
279
283
|
// throttles AI SDK's own messages store; pi's custom data-pi projection
|
|
@@ -288,6 +292,10 @@ function useAgentChat(opts) {
|
|
|
288
292
|
const [hydrated, setHydrated] = useState3(false);
|
|
289
293
|
useEffect4(() => {
|
|
290
294
|
if (!sessionId || !cacheKey) return;
|
|
295
|
+
if (!hydrateMessages) {
|
|
296
|
+
setHydrated(true);
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
291
299
|
let aborted = false;
|
|
292
300
|
setHydrated(false);
|
|
293
301
|
const loadFromCache = () => {
|
|
@@ -306,24 +314,30 @@ function useAgentChat(opts) {
|
|
|
306
314
|
const request = fetchOpts ? fetch(messagesUrl, fetchOpts) : fetch(messagesUrl);
|
|
307
315
|
request.then((res) => res.ok ? res.json() : null).then((payload) => {
|
|
308
316
|
if (aborted) return;
|
|
317
|
+
const localTurnStarted = statusRef.current === "submitted" || statusRef.current === "streaming" || messagesRef.current.length > 0;
|
|
309
318
|
const serverMessages = payload?.messages;
|
|
310
319
|
if (Array.isArray(serverMessages) && serverMessages.length > 0) {
|
|
311
|
-
setMessages(serverMessages);
|
|
320
|
+
if (!localTurnStarted) setMessages(serverMessages);
|
|
312
321
|
return;
|
|
313
322
|
}
|
|
314
|
-
loadFromCache();
|
|
323
|
+
if (!localTurnStarted) loadFromCache();
|
|
315
324
|
}).catch(() => {
|
|
316
325
|
if (aborted) return;
|
|
317
|
-
|
|
326
|
+
const localTurnStarted = statusRef.current === "submitted" || statusRef.current === "streaming" || messagesRef.current.length > 0;
|
|
327
|
+
if (!localTurnStarted) loadFromCache();
|
|
318
328
|
}).finally(() => {
|
|
319
329
|
if (!aborted) setHydrated(true);
|
|
320
330
|
});
|
|
321
331
|
return () => {
|
|
322
332
|
aborted = true;
|
|
323
333
|
};
|
|
324
|
-
}, [sessionId, cacheKey, setMessages]);
|
|
334
|
+
}, [hydrateMessages, sessionId, cacheKey, setMessages]);
|
|
325
335
|
const messages = chat.messages;
|
|
326
336
|
const status = chat.status;
|
|
337
|
+
const messagesRef = useRef4(messages);
|
|
338
|
+
const statusRef = useRef4(status);
|
|
339
|
+
messagesRef.current = messages;
|
|
340
|
+
statusRef.current = status;
|
|
327
341
|
useEffect4(() => {
|
|
328
342
|
if (opts.persistMessages === false) return;
|
|
329
343
|
if (!hydrated || !cacheKey) return;
|
|
@@ -444,6 +458,20 @@ function applyBufferedDeltas(items, deltas) {
|
|
|
444
458
|
items
|
|
445
459
|
);
|
|
446
460
|
}
|
|
461
|
+
function messageVisibleText(message) {
|
|
462
|
+
return (message.parts ?? []).filter((part) => part.type === "text").map((part) => "text" in part && typeof part.text === "string" ? part.text : "").join("\n").trim();
|
|
463
|
+
}
|
|
464
|
+
function countExistingSdkUserTexts(messages) {
|
|
465
|
+
const counts = /* @__PURE__ */ new Map();
|
|
466
|
+
for (const message of messages) {
|
|
467
|
+
if (message.role !== "user") continue;
|
|
468
|
+
if ((message.parts ?? []).some((part) => typeof part.type === "string" && part.type.startsWith("data-pi-"))) continue;
|
|
469
|
+
const text = messageVisibleText(message);
|
|
470
|
+
if (!text) continue;
|
|
471
|
+
counts.set(text, (counts.get(text) ?? 0) + 1);
|
|
472
|
+
}
|
|
473
|
+
return counts;
|
|
474
|
+
}
|
|
447
475
|
function rebuildPiMessagesFromDataParts(sourceMessages) {
|
|
448
476
|
const dataParts = sourceMessages.flatMap((message) => message.parts ?? []).map(asPiDataPart).filter(Boolean);
|
|
449
477
|
if (dataParts.length === 0) return [];
|
|
@@ -535,7 +563,16 @@ function mergeRebuiltPiMessages(existing, rebuilt) {
|
|
|
535
563
|
if (rebuiltIds.has(message.id)) return false;
|
|
536
564
|
return !(message.parts ?? []).some((part) => typeof part.type === "string" && part.type.startsWith("data-pi-"));
|
|
537
565
|
});
|
|
538
|
-
|
|
566
|
+
const sdkUserTextCounts = countExistingSdkUserTexts(preserved);
|
|
567
|
+
const rebuiltMissingFromSdk = rebuilt.filter((message) => {
|
|
568
|
+
if (message.role !== "user") return true;
|
|
569
|
+
const text = messageVisibleText(message);
|
|
570
|
+
const alreadyRepresentedCount = text ? sdkUserTextCounts.get(text) ?? 0 : 0;
|
|
571
|
+
if (alreadyRepresentedCount <= 0) return true;
|
|
572
|
+
sdkUserTextCounts.set(text, alreadyRepresentedCount - 1);
|
|
573
|
+
return false;
|
|
574
|
+
});
|
|
575
|
+
return [...preserved, ...rebuiltMissingFromSdk];
|
|
539
576
|
}
|
|
540
577
|
function usePiChatProjection({
|
|
541
578
|
messages,
|
|
@@ -709,8 +746,7 @@ function usePiChatProjection({
|
|
|
709
746
|
if (status !== "ready") return;
|
|
710
747
|
if (prev !== "streaming" && prev !== "submitted") return;
|
|
711
748
|
if (!sessionId || piMessages.length === 0) return;
|
|
712
|
-
const
|
|
713
|
-
const messagesToPersist = canonicalMessages.length > 0 ? mergeRebuiltPiMessages(piMessages, canonicalMessages) : piMessages;
|
|
749
|
+
const messagesToPersist = mergeRebuiltPiMessages(messages, piMessages);
|
|
714
750
|
const stripped = messagesToPersist.map((msg) => ({
|
|
715
751
|
...msg,
|
|
716
752
|
parts: msg.parts?.filter((part) => {
|
|
@@ -1760,6 +1796,38 @@ var ShimmerComponent = ({
|
|
|
1760
1796
|
};
|
|
1761
1797
|
var Shimmer = memo(ShimmerComponent);
|
|
1762
1798
|
|
|
1799
|
+
// src/front/workspaceReadinessStatus.ts
|
|
1800
|
+
var COPY = {
|
|
1801
|
+
"workspace-fs": "Files are still loading.",
|
|
1802
|
+
"sandbox-exec": "Sandbox is still waking.",
|
|
1803
|
+
"ui-bridge": "Workspace UI is still connecting."
|
|
1804
|
+
};
|
|
1805
|
+
var VALID_REQUIREMENTS = /* @__PURE__ */ new Set([
|
|
1806
|
+
"workspace-fs",
|
|
1807
|
+
"sandbox-exec",
|
|
1808
|
+
"ui-bridge"
|
|
1809
|
+
]);
|
|
1810
|
+
function detailsFromOutput(output) {
|
|
1811
|
+
if (!output || typeof output !== "object") return null;
|
|
1812
|
+
const record = output;
|
|
1813
|
+
const details = record.details && typeof record.details === "object" ? record.details : record;
|
|
1814
|
+
return details;
|
|
1815
|
+
}
|
|
1816
|
+
function getWorkspaceNotReadyStatus(output) {
|
|
1817
|
+
const details = detailsFromOutput(output);
|
|
1818
|
+
if (!details) return null;
|
|
1819
|
+
if (details.code !== ErrorCode.enum.WORKSPACE_NOT_READY) return null;
|
|
1820
|
+
if (details.retryable !== true) return null;
|
|
1821
|
+
const requirement = details.requirement;
|
|
1822
|
+
if (typeof requirement !== "string" || !VALID_REQUIREMENTS.has(requirement)) return null;
|
|
1823
|
+
return {
|
|
1824
|
+
code: ErrorCode.enum.WORKSPACE_NOT_READY,
|
|
1825
|
+
retryable: true,
|
|
1826
|
+
requirement,
|
|
1827
|
+
message: COPY[requirement]
|
|
1828
|
+
};
|
|
1829
|
+
}
|
|
1830
|
+
|
|
1763
1831
|
// src/front/primitives/tool-call-group.tsx
|
|
1764
1832
|
import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1765
1833
|
function isSettledState(state) {
|
|
@@ -1795,10 +1863,14 @@ var ToolCallGroup = memo2(({ tools, mergedToolRenderers }) => {
|
|
|
1795
1863
|
if (!isToolUIPart(part)) return true;
|
|
1796
1864
|
return isSettledState(part.state);
|
|
1797
1865
|
});
|
|
1866
|
+
const workspaceNotReady = tools.map(({ part }) => {
|
|
1867
|
+
if (!isToolUIPart(part)) return null;
|
|
1868
|
+
return getWorkspaceNotReadyStatus(part.output);
|
|
1869
|
+
}).find(Boolean);
|
|
1798
1870
|
const hasError = tools.some(({ part }) => {
|
|
1799
1871
|
if (!isToolUIPart(part)) return false;
|
|
1800
1872
|
return part.state === "output-error";
|
|
1801
|
-
});
|
|
1873
|
+
}) && !workspaceNotReady;
|
|
1802
1874
|
const [isOpen, setIsOpen] = useState9(false);
|
|
1803
1875
|
const handleOpenChange = useCallback6((open) => setIsOpen(open), []);
|
|
1804
1876
|
const title = useMemo5(() => buildTitle(tools, isSettled), [tools, isSettled]);
|
|
@@ -1831,7 +1903,7 @@ var ToolCallGroup = memo2(({ tools, mergedToolRenderers }) => {
|
|
|
1831
1903
|
)
|
|
1832
1904
|
}
|
|
1833
1905
|
),
|
|
1834
|
-
!isSettled ? /* @__PURE__ */ jsx10(Shimmer, { as: "span", duration: 1.5, children: title }) : /* @__PURE__ */ jsx10("span", { children: title }),
|
|
1906
|
+
workspaceNotReady ? /* @__PURE__ */ jsx10("span", { children: workspaceNotReady.message }) : !isSettled ? /* @__PURE__ */ jsx10(Shimmer, { as: "span", duration: 1.5, children: title }) : /* @__PURE__ */ jsx10("span", { children: title }),
|
|
1835
1907
|
/* @__PURE__ */ jsx10("span", { className: cn(
|
|
1836
1908
|
"ml-1 shrink-0 rounded-sm border border-border/40 px-1 tabular-nums",
|
|
1837
1909
|
"text-[10px] text-muted-foreground/50"
|
|
@@ -2498,6 +2570,17 @@ function PathLabel({ path }) {
|
|
|
2498
2570
|
}
|
|
2499
2571
|
);
|
|
2500
2572
|
}
|
|
2573
|
+
function renderWorkspaceNotReady(part) {
|
|
2574
|
+
const status = getWorkspaceNotReadyStatus(part.output);
|
|
2575
|
+
if (!status) return null;
|
|
2576
|
+
return /* @__PURE__ */ jsxs13(Tool2, { children: [
|
|
2577
|
+
/* @__PURE__ */ jsx15(ToolHeader, { title: `${part.toolName} \xB7 ${status.message}`, ...toHeaderProps(part) }),
|
|
2578
|
+
/* @__PURE__ */ jsx15(ToolContent, { children: /* @__PURE__ */ jsxs13("div", { className: "rounded-md border border-border/60 bg-muted/30 px-3 py-2 text-sm text-muted-foreground", children: [
|
|
2579
|
+
status.message,
|
|
2580
|
+
" Try again in a moment."
|
|
2581
|
+
] }) })
|
|
2582
|
+
] });
|
|
2583
|
+
}
|
|
2501
2584
|
function pathTitle(prefix, path) {
|
|
2502
2585
|
return /* @__PURE__ */ jsxs13("span", { className: "flex min-w-0 items-center gap-1.5", children: [
|
|
2503
2586
|
/* @__PURE__ */ jsx15("span", { className: "text-muted-foreground", children: prefix }),
|
|
@@ -2506,6 +2589,8 @@ function pathTitle(prefix, path) {
|
|
|
2506
2589
|
] });
|
|
2507
2590
|
}
|
|
2508
2591
|
function renderBash2(part) {
|
|
2592
|
+
const readiness = renderWorkspaceNotReady(part);
|
|
2593
|
+
if (readiness) return readiness;
|
|
2509
2594
|
const input = asRecord2(part.input);
|
|
2510
2595
|
const output = asRecord2(part.output);
|
|
2511
2596
|
const command = typeof input.command === "string" ? input.command : "";
|
|
@@ -2537,6 +2622,8 @@ function renderBash2(part) {
|
|
|
2537
2622
|
] });
|
|
2538
2623
|
}
|
|
2539
2624
|
function renderRead2(part) {
|
|
2625
|
+
const readiness = renderWorkspaceNotReady(part);
|
|
2626
|
+
if (readiness) return readiness;
|
|
2540
2627
|
const input = asRecord2(part.input);
|
|
2541
2628
|
const output = asRecord2(part.output);
|
|
2542
2629
|
const path = typeof input.path === "string" ? input.path : "";
|
|
@@ -2554,6 +2641,8 @@ function renderRead2(part) {
|
|
|
2554
2641
|
] });
|
|
2555
2642
|
}
|
|
2556
2643
|
function renderWrite2(part) {
|
|
2644
|
+
const readiness = renderWorkspaceNotReady(part);
|
|
2645
|
+
if (readiness) return readiness;
|
|
2557
2646
|
const input = asRecord2(part.input);
|
|
2558
2647
|
const path = typeof input.path === "string" ? input.path : "";
|
|
2559
2648
|
const content = typeof input.content === "string" ? input.content : "";
|
|
@@ -2616,6 +2705,8 @@ function renderWrite2(part) {
|
|
|
2616
2705
|
] });
|
|
2617
2706
|
}
|
|
2618
2707
|
function renderEdit2(part) {
|
|
2708
|
+
const readiness = renderWorkspaceNotReady(part);
|
|
2709
|
+
if (readiness) return readiness;
|
|
2619
2710
|
const input = asRecord2(part.input);
|
|
2620
2711
|
const path = typeof input.path === "string" ? input.path : "";
|
|
2621
2712
|
const oldString = typeof input.oldString === "string" ? input.oldString : "";
|
|
@@ -2641,6 +2732,8 @@ function renderEdit2(part) {
|
|
|
2641
2732
|
] });
|
|
2642
2733
|
}
|
|
2643
2734
|
function renderSearchLike2(toolName, part) {
|
|
2735
|
+
const readiness = renderWorkspaceNotReady(part);
|
|
2736
|
+
if (readiness) return readiness;
|
|
2644
2737
|
const input = asRecord2(part.input);
|
|
2645
2738
|
const SearchLikeIcon = toolName === "find" || toolName === "grep" ? SearchIcon : FileTextIcon;
|
|
2646
2739
|
const pattern = typeof input.pattern === "string" ? input.pattern : "";
|
|
@@ -2677,6 +2770,8 @@ function extractParamTokens(value, depth = 0) {
|
|
|
2677
2770
|
return [String(value)];
|
|
2678
2771
|
}
|
|
2679
2772
|
function renderExecUi2(part) {
|
|
2773
|
+
const readiness = renderWorkspaceNotReady(part);
|
|
2774
|
+
if (readiness) return readiness;
|
|
2680
2775
|
const input = asRecord2(part.input);
|
|
2681
2776
|
const kind = typeof input.kind === "string" ? input.kind : "(empty)";
|
|
2682
2777
|
const tokens = extractParamTokens(input.params);
|
|
@@ -2733,6 +2828,8 @@ function renderExecUi2(part) {
|
|
|
2733
2828
|
] });
|
|
2734
2829
|
}
|
|
2735
2830
|
function renderFallback2(part) {
|
|
2831
|
+
const readiness = renderWorkspaceNotReady(part);
|
|
2832
|
+
if (readiness) return readiness;
|
|
2736
2833
|
return /* @__PURE__ */ jsxs13(Tool2, { children: [
|
|
2737
2834
|
/* @__PURE__ */ jsx15(ToolHeader, { title: part.toolName, ...toHeaderProps(part) }),
|
|
2738
2835
|
/* @__PURE__ */ jsxs13(ToolContent, { children: [
|
|
@@ -3678,23 +3775,25 @@ var PromptInput = ({
|
|
|
3678
3775
|
return item;
|
|
3679
3776
|
})
|
|
3680
3777
|
);
|
|
3681
|
-
const result = onSubmit({ files: convertedFiles, text }, event);
|
|
3682
|
-
if (result
|
|
3683
|
-
try {
|
|
3684
|
-
await result;
|
|
3685
|
-
clear();
|
|
3686
|
-
if (usingProvider) {
|
|
3687
|
-
controller.textInput.clear();
|
|
3688
|
-
}
|
|
3689
|
-
} catch {
|
|
3690
|
-
}
|
|
3691
|
-
} else {
|
|
3692
|
-
clear();
|
|
3778
|
+
const result = await onSubmit({ files: convertedFiles, text }, event);
|
|
3779
|
+
if (result === false) {
|
|
3693
3780
|
if (usingProvider) {
|
|
3694
|
-
controller.textInput.
|
|
3781
|
+
controller.textInput.setInput(text);
|
|
3782
|
+
} else {
|
|
3783
|
+
const textInput = form.elements.namedItem("message");
|
|
3784
|
+
if (textInput) textInput.value = text;
|
|
3695
3785
|
}
|
|
3786
|
+
return;
|
|
3787
|
+
}
|
|
3788
|
+
clear();
|
|
3789
|
+
if (usingProvider) {
|
|
3790
|
+
controller.textInput.clear();
|
|
3696
3791
|
}
|
|
3697
3792
|
} catch {
|
|
3793
|
+
if (!usingProvider) {
|
|
3794
|
+
const textInput = form.elements.namedItem("message");
|
|
3795
|
+
if (textInput && textInput.value === "") textInput.value = text;
|
|
3796
|
+
}
|
|
3698
3797
|
}
|
|
3699
3798
|
},
|
|
3700
3799
|
[usingProvider, controller, files, onSubmit, clear]
|
|
@@ -4121,10 +4220,23 @@ function friendlyError(err) {
|
|
|
4121
4220
|
detail: `${label} ${message?.toLowerCase() ?? "failed validation"}.`
|
|
4122
4221
|
};
|
|
4123
4222
|
}
|
|
4223
|
+
if (code3 === ErrorCode.enum.AGENT_RUNTIME_NOT_READY) {
|
|
4224
|
+
return {
|
|
4225
|
+
title: "Preparing workspace\u2026",
|
|
4226
|
+
code: code3
|
|
4227
|
+
};
|
|
4228
|
+
}
|
|
4229
|
+
if (code3 === ErrorCode.enum.RUNTIME_PROVISIONING_FAILED) {
|
|
4230
|
+
return {
|
|
4231
|
+
title: "Workspace setup failed.",
|
|
4232
|
+
detail: message ?? "Reload the workspace and try again.",
|
|
4233
|
+
code: code3
|
|
4234
|
+
};
|
|
4235
|
+
}
|
|
4124
4236
|
if (code3 === "internal" || code3 === "internal_error") {
|
|
4125
|
-
return { title: "The server hit an internal error.", detail: message };
|
|
4237
|
+
return { title: "The server hit an internal error.", detail: message, code: code3 };
|
|
4126
4238
|
}
|
|
4127
|
-
return { title: message ?? "Something went wrong.", detail: code3 };
|
|
4239
|
+
return { title: message ?? "Something went wrong.", detail: code3, code: code3 };
|
|
4128
4240
|
} catch {
|
|
4129
4241
|
return { title: raw };
|
|
4130
4242
|
}
|
|
@@ -4429,7 +4541,8 @@ function encodeModelKey(sel) {
|
|
|
4429
4541
|
// src/front/hooks/useChatModelSelection.ts
|
|
4430
4542
|
function useChatModelSelection({
|
|
4431
4543
|
defaultModel,
|
|
4432
|
-
requestHeaders
|
|
4544
|
+
requestHeaders,
|
|
4545
|
+
enabled = true
|
|
4433
4546
|
}) {
|
|
4434
4547
|
const initialModelState = useMemo11(readStoredModelState, []);
|
|
4435
4548
|
const [model, setModelState] = useState15(
|
|
@@ -4456,6 +4569,7 @@ function useChatModelSelection({
|
|
|
4456
4569
|
setModelState(defaultModel);
|
|
4457
4570
|
}, [defaultModel]);
|
|
4458
4571
|
useEffect14(() => {
|
|
4572
|
+
if (!enabled) return;
|
|
4459
4573
|
let aborted = false;
|
|
4460
4574
|
fetch("/api/v1/agent/models", { headers: requestHeaders }).then((res) => res.ok ? res.json() : null).then((payload) => {
|
|
4461
4575
|
if (aborted || !payload?.models) return;
|
|
@@ -4474,7 +4588,7 @@ function useChatModelSelection({
|
|
|
4474
4588
|
return () => {
|
|
4475
4589
|
aborted = true;
|
|
4476
4590
|
};
|
|
4477
|
-
}, [requestHeaders]);
|
|
4591
|
+
}, [enabled, requestHeaders]);
|
|
4478
4592
|
useEffect14(() => {
|
|
4479
4593
|
const onChange = (event) => {
|
|
4480
4594
|
const next = parseModelSelection(event.detail);
|
|
@@ -4490,10 +4604,12 @@ function useChatModelSelection({
|
|
|
4490
4604
|
import { useEffect as useEffect15, useState as useState16 } from "react";
|
|
4491
4605
|
function useServerSkills({
|
|
4492
4606
|
registry,
|
|
4493
|
-
requestHeaders
|
|
4607
|
+
requestHeaders,
|
|
4608
|
+
enabled = true
|
|
4494
4609
|
}) {
|
|
4495
4610
|
const [skillsStamp, setSkillsStamp] = useState16(0);
|
|
4496
4611
|
useEffect15(() => {
|
|
4612
|
+
if (!enabled) return;
|
|
4497
4613
|
let aborted = false;
|
|
4498
4614
|
fetch("/api/v1/agent/skills", { headers: requestHeaders }).then((res) => res.ok ? res.json() : null).then((payload) => {
|
|
4499
4615
|
if (aborted || !payload?.skills) return;
|
|
@@ -4511,7 +4627,7 @@ function useServerSkills({
|
|
|
4511
4627
|
return () => {
|
|
4512
4628
|
aborted = true;
|
|
4513
4629
|
};
|
|
4514
|
-
}, [requestHeaders, registry]);
|
|
4630
|
+
}, [enabled, requestHeaders, registry]);
|
|
4515
4631
|
return skillsStamp;
|
|
4516
4632
|
}
|
|
4517
4633
|
|
|
@@ -4551,7 +4667,7 @@ function useAttachmentNotice(timeoutMs = 4e3) {
|
|
|
4551
4667
|
|
|
4552
4668
|
// src/front/chatPanelComposerControls.tsx
|
|
4553
4669
|
import { useState as useState19 } from "react";
|
|
4554
|
-
import {
|
|
4670
|
+
import { CheckIcon as CheckIcon2, ChevronDownIcon as ChevronDownIcon5, EyeIcon, EyeOffIcon } from "lucide-react";
|
|
4555
4671
|
import {
|
|
4556
4672
|
Command as Command2,
|
|
4557
4673
|
CommandEmpty as CommandEmpty2,
|
|
@@ -4603,6 +4719,33 @@ var THINKING_LEVEL_LABELS = {
|
|
|
4603
4719
|
medium: "Med",
|
|
4604
4720
|
high: "High"
|
|
4605
4721
|
};
|
|
4722
|
+
function ThinkingLevelGlyph({ level }) {
|
|
4723
|
+
const lit = level === "off" ? 0 : level === "low" ? 1 : level === "medium" ? 2 : 3;
|
|
4724
|
+
return /* @__PURE__ */ jsx23(
|
|
4725
|
+
"svg",
|
|
4726
|
+
{
|
|
4727
|
+
"aria-hidden": "true",
|
|
4728
|
+
width: "14",
|
|
4729
|
+
height: "14",
|
|
4730
|
+
viewBox: "0 0 14 14",
|
|
4731
|
+
fill: "none",
|
|
4732
|
+
className: "shrink-0",
|
|
4733
|
+
children: [0, 1, 2].map((i) => /* @__PURE__ */ jsx23(
|
|
4734
|
+
"rect",
|
|
4735
|
+
{
|
|
4736
|
+
x: 2 + i * 4,
|
|
4737
|
+
y: 10 - i * 2,
|
|
4738
|
+
width: "2",
|
|
4739
|
+
height: 3 + i * 2,
|
|
4740
|
+
rx: "0.5",
|
|
4741
|
+
fill: "currentColor",
|
|
4742
|
+
opacity: i < lit ? 1 : 0.25
|
|
4743
|
+
},
|
|
4744
|
+
i
|
|
4745
|
+
))
|
|
4746
|
+
}
|
|
4747
|
+
);
|
|
4748
|
+
}
|
|
4606
4749
|
function ModelSelect({
|
|
4607
4750
|
value,
|
|
4608
4751
|
onChange,
|
|
@@ -4641,13 +4784,16 @@ function ModelSelect({
|
|
|
4641
4784
|
"aria-label": "Model",
|
|
4642
4785
|
className: cn(
|
|
4643
4786
|
composerActionClass,
|
|
4644
|
-
|
|
4645
|
-
|
|
4787
|
+
// Model is the only piece of state the composer carries between
|
|
4788
|
+
// turns — give it a status-pill shape so it reads as data, not
|
|
4789
|
+
// another tertiary control. The label is the signal; no icon
|
|
4790
|
+
// (the bot/AI glyph was decoration, not information).
|
|
4791
|
+
"w-auto max-w-[min(52vw,260px)] gap-1 rounded-full bg-muted/40 px-2.5 text-[12px] font-medium text-foreground/85 hover:bg-muted/70",
|
|
4792
|
+
open && "bg-muted/70 text-foreground"
|
|
4646
4793
|
),
|
|
4647
4794
|
children: [
|
|
4648
|
-
/* @__PURE__ */ jsx23(BotIcon, { className: "h-4 w-4 shrink-0", "aria-hidden": "true" }),
|
|
4649
4795
|
/* @__PURE__ */ jsx23("span", { className: "min-w-0 truncate", children: triggerLabel }),
|
|
4650
|
-
/* @__PURE__ */ jsx23(ChevronDownIcon5, { className: "h-3 w-3 shrink-0 text-muted-foreground/
|
|
4796
|
+
/* @__PURE__ */ jsx23(ChevronDownIcon5, { className: "h-3 w-3 shrink-0 text-muted-foreground/50", "aria-hidden": "true" })
|
|
4651
4797
|
]
|
|
4652
4798
|
}
|
|
4653
4799
|
) }),
|
|
@@ -4657,7 +4803,7 @@ function ModelSelect({
|
|
|
4657
4803
|
align: "start",
|
|
4658
4804
|
sideOffset: 6,
|
|
4659
4805
|
"data-boring-agent": "",
|
|
4660
|
-
className: "w-[min(
|
|
4806
|
+
className: "w-[min(92vw,340px)] rounded-xl border-border/60 bg-popover p-1 shadow-xl",
|
|
4661
4807
|
children: /* @__PURE__ */ jsxs21(Command2, { children: [
|
|
4662
4808
|
menuOptions.length > 8 && /* @__PURE__ */ jsx23(
|
|
4663
4809
|
CommandInput2,
|
|
@@ -4686,7 +4832,13 @@ function ModelSelect({
|
|
|
4686
4832
|
},
|
|
4687
4833
|
className: cn(
|
|
4688
4834
|
"flex items-center gap-2 rounded-md px-2 py-1.5 text-[13px]",
|
|
4689
|
-
|
|
4835
|
+
// Soften cmdk's full-saturation `data-[selected=true]:bg-accent`
|
|
4836
|
+
// (keyboard-cursor focus) to a subtle warm tint so the
|
|
4837
|
+
// active row doesn't read as a saturated tile in dark mode.
|
|
4838
|
+
"data-[selected=true]:bg-[color:oklch(from_var(--accent)_l_c_h/0.15)] data-[selected=true]:text-foreground",
|
|
4839
|
+
// The user's CURRENTLY-SELECTED model gets a neutral wash +
|
|
4840
|
+
// the CheckIcon (accent is reserved for primary CTA / focus).
|
|
4841
|
+
key === currentKey && "bg-foreground/[0.06] text-foreground"
|
|
4690
4842
|
),
|
|
4691
4843
|
children: [
|
|
4692
4844
|
/* @__PURE__ */ jsx23(
|
|
@@ -4694,12 +4846,12 @@ function ModelSelect({
|
|
|
4694
4846
|
{
|
|
4695
4847
|
className: cn(
|
|
4696
4848
|
"h-3.5 w-3.5 shrink-0",
|
|
4697
|
-
key === currentKey ? "opacity-100" : "opacity-0"
|
|
4849
|
+
key === currentKey ? "text-[color:var(--accent)] opacity-100" : "opacity-0"
|
|
4698
4850
|
)
|
|
4699
4851
|
}
|
|
4700
4852
|
),
|
|
4701
4853
|
/* @__PURE__ */ jsx23("span", { className: "truncate", children: label }),
|
|
4702
|
-
/* @__PURE__ */ jsx23("span", { className: "ml-auto shrink-0 text-[10px] text-muted-foreground/
|
|
4854
|
+
/* @__PURE__ */ jsx23("span", { className: "ml-auto shrink-0 text-[10px] text-muted-foreground/60", children: m.id })
|
|
4703
4855
|
]
|
|
4704
4856
|
},
|
|
4705
4857
|
key
|
|
@@ -4733,12 +4885,17 @@ function ThinkingSelect({
|
|
|
4733
4885
|
{
|
|
4734
4886
|
"data-boring-agent-part": "thinking-select",
|
|
4735
4887
|
"data-boring-state": disabled ? "disabled" : void 0,
|
|
4736
|
-
className: cn(
|
|
4737
|
-
|
|
4888
|
+
className: cn(
|
|
4889
|
+
composerActionClass,
|
|
4890
|
+
"w-8 px-0 [&>span[aria-hidden]]:hidden",
|
|
4891
|
+
value !== "off" && "text-foreground"
|
|
4892
|
+
),
|
|
4893
|
+
"aria-label": `Thinking level: ${THINKING_LEVEL_LABELS[value]}`,
|
|
4894
|
+
title: `Thinking: ${THINKING_LEVEL_LABELS[value]}`,
|
|
4738
4895
|
"data-testid": "thinking-select",
|
|
4739
4896
|
children: [
|
|
4740
4897
|
THINKING_LEVELS.map((level) => /* @__PURE__ */ jsx23("span", { "data-value": level, hidden: true }, level)),
|
|
4741
|
-
/* @__PURE__ */ jsx23(
|
|
4898
|
+
/* @__PURE__ */ jsx23(ThinkingLevelGlyph, { level: value })
|
|
4742
4899
|
]
|
|
4743
4900
|
}
|
|
4744
4901
|
),
|
|
@@ -4772,25 +4929,26 @@ function ThoughtVisibilityButton({
|
|
|
4772
4929
|
variant: "ghost",
|
|
4773
4930
|
size: "icon-sm",
|
|
4774
4931
|
onClick: onToggle,
|
|
4775
|
-
className: cn(composerActionClass, "w-8"),
|
|
4932
|
+
className: cn(composerActionClass, "w-8", visible && "text-foreground"),
|
|
4776
4933
|
"aria-pressed": visible,
|
|
4777
4934
|
"aria-label": visible ? "Hide thoughts" : "Show thoughts",
|
|
4778
4935
|
title: visible ? "Hide thoughts" : "Show thoughts",
|
|
4779
|
-
children: /* @__PURE__ */ jsx23(Icon, { className: "h-3.5 w-3.5" })
|
|
4936
|
+
children: /* @__PURE__ */ jsx23(Icon, { className: "h-3.5 w-3.5", strokeWidth: 1.75 })
|
|
4780
4937
|
}
|
|
4781
4938
|
);
|
|
4782
4939
|
}
|
|
4783
4940
|
|
|
4784
4941
|
// src/front/chatPanelKbdHints.tsx
|
|
4942
|
+
import { Kbd } from "@hachej/boring-ui-kit";
|
|
4785
4943
|
import { jsx as jsx24, jsxs as jsxs22 } from "react/jsx-runtime";
|
|
4786
4944
|
function KbdHints() {
|
|
4787
4945
|
return /* @__PURE__ */ jsxs22(
|
|
4788
|
-
|
|
4946
|
+
Kbd,
|
|
4789
4947
|
{
|
|
4790
4948
|
"aria-hidden": "true",
|
|
4949
|
+
title: "Shift + Enter for newline",
|
|
4791
4950
|
className: cn(
|
|
4792
|
-
"hidden
|
|
4793
|
-
"bg-background/50 px-1.5 font-mono text-[10px] text-muted-foreground/40",
|
|
4951
|
+
"hidden gap-0.5 border-border/60 bg-muted/40 leading-none shadow-none",
|
|
4794
4952
|
"sm:inline-flex"
|
|
4795
4953
|
),
|
|
4796
4954
|
children: [
|
|
@@ -4830,6 +4988,9 @@ function hasVisibleMessageContent(message) {
|
|
|
4830
4988
|
return Boolean(reasoning?.text.trim());
|
|
4831
4989
|
});
|
|
4832
4990
|
}
|
|
4991
|
+
function messageText(message) {
|
|
4992
|
+
return (message.parts ?? []).filter(isTextPart).map((part) => part.text).join("\n").trim();
|
|
4993
|
+
}
|
|
4833
4994
|
function hasPiVisibleDataParts(messages) {
|
|
4834
4995
|
return messages.some(
|
|
4835
4996
|
(message) => (message.parts ?? []).some((part) => {
|
|
@@ -4848,7 +5009,8 @@ function collectSdkRepresentedMessageIds(messages) {
|
|
|
4848
5009
|
if (!hasVisibleMessageContent(message)) continue;
|
|
4849
5010
|
represented.add(message.id);
|
|
4850
5011
|
for (const part of message.parts ?? []) {
|
|
4851
|
-
|
|
5012
|
+
const type = dataPartType(part);
|
|
5013
|
+
if (type !== "data-pi-message-start" && type !== "data-pi-message-end") continue;
|
|
4852
5014
|
const data = part.data;
|
|
4853
5015
|
if (typeof data?.messageId === "string") represented.add(data.messageId);
|
|
4854
5016
|
}
|
|
@@ -4865,6 +5027,19 @@ function uniqueVisiblePiMessages(messages) {
|
|
|
4865
5027
|
}
|
|
4866
5028
|
return out;
|
|
4867
5029
|
}
|
|
5030
|
+
function sdkVisibleMessagesWithoutPiOnlyAssistants(messages) {
|
|
5031
|
+
return messages.filter((message) => {
|
|
5032
|
+
if (!hasVisibleMessageContent(message)) return false;
|
|
5033
|
+
if (message.role !== "assistant") return true;
|
|
5034
|
+
const hasVisibleAssistantPart = (message.parts ?? []).some((part) => {
|
|
5035
|
+
if (isTextPart(part)) return !isBlankTextPart(part);
|
|
5036
|
+
if (isToolUIPart2(part)) return true;
|
|
5037
|
+
const reasoning = getReasoningPart(part);
|
|
5038
|
+
return Boolean(reasoning?.text.trim());
|
|
5039
|
+
});
|
|
5040
|
+
return hasVisibleAssistantPart;
|
|
5041
|
+
});
|
|
5042
|
+
}
|
|
4868
5043
|
function getSettledPiAssistantIds(message) {
|
|
4869
5044
|
const ids = [];
|
|
4870
5045
|
for (const part of message.parts ?? []) {
|
|
@@ -4901,6 +5076,23 @@ function mergeSettledPiFallbacksInPlace(messages, piMessages) {
|
|
|
4901
5076
|
}
|
|
4902
5077
|
return out;
|
|
4903
5078
|
}
|
|
5079
|
+
function composerNoticeForWarmup(status) {
|
|
5080
|
+
if (!status || status.status === "ready") return null;
|
|
5081
|
+
if (status.status === "failed") {
|
|
5082
|
+
return {
|
|
5083
|
+
title: "Workspace setup failed.",
|
|
5084
|
+
detail: status.message ?? "Reload the workspace and try again.",
|
|
5085
|
+
code: ErrorCode.enum.RUNTIME_PROVISIONING_FAILED
|
|
5086
|
+
};
|
|
5087
|
+
}
|
|
5088
|
+
return {
|
|
5089
|
+
title: "Preparing workspace\u2026",
|
|
5090
|
+
code: ErrorCode.enum.AGENT_RUNTIME_NOT_READY
|
|
5091
|
+
};
|
|
5092
|
+
}
|
|
5093
|
+
function isComposerRuntimeNotice(error) {
|
|
5094
|
+
return error?.code === ErrorCode.enum.AGENT_RUNTIME_NOT_READY || error?.code === ErrorCode.enum.RUNTIME_PROVISIONING_FAILED;
|
|
5095
|
+
}
|
|
4904
5096
|
function getQueuedPiTail(messages, piMessages) {
|
|
4905
5097
|
if (piMessages.length === 0) return [];
|
|
4906
5098
|
const queuedIds = /* @__PURE__ */ new Set();
|
|
@@ -4955,8 +5147,19 @@ function ChatPanel(props) {
|
|
|
4955
5147
|
defaultModel,
|
|
4956
5148
|
onData,
|
|
4957
5149
|
requestHeaders,
|
|
5150
|
+
hydrateMessages,
|
|
4958
5151
|
onOpenArtifact,
|
|
4959
5152
|
debug = false,
|
|
5153
|
+
initialDraft,
|
|
5154
|
+
autoSubmitInitialDraft = false,
|
|
5155
|
+
onDraftRestored,
|
|
5156
|
+
onAutoSubmitInitialDraftAccepted,
|
|
5157
|
+
onAutoSubmitInitialDraftSettled,
|
|
5158
|
+
onBeforeSubmit,
|
|
5159
|
+
serverResourcesEnabled = true,
|
|
5160
|
+
emptyPlacement = "default",
|
|
5161
|
+
composerPlaceholder,
|
|
5162
|
+
workspaceWarmupStatus,
|
|
4960
5163
|
composerBlockers = [],
|
|
4961
5164
|
onComposerStop,
|
|
4962
5165
|
onComposerBlockerAction
|
|
@@ -4969,6 +5172,23 @@ function ChatPanel(props) {
|
|
|
4969
5172
|
});
|
|
4970
5173
|
const followUpDataHandlerRef = useRef13(() => {
|
|
4971
5174
|
});
|
|
5175
|
+
const autoSubmittedDraftRef = useRef13(void 0);
|
|
5176
|
+
const autoSubmittingDraftRef = useRef13(void 0);
|
|
5177
|
+
const pendingAutoSubmitUnlockRef = useRef13(void 0);
|
|
5178
|
+
const pendingAutoSubmitSettleRef = useRef13(void 0);
|
|
5179
|
+
const activeAutoSubmitSessionRef = useRef13(sessionId);
|
|
5180
|
+
const liveSessionIdRef = useRef13(sessionId);
|
|
5181
|
+
liveSessionIdRef.current = sessionId;
|
|
5182
|
+
activeAutoSubmitSessionRef.current = sessionId;
|
|
5183
|
+
const [acceptedAutoSubmittedDraft, setAcceptedAutoSubmittedDraft] = useState20(void 0);
|
|
5184
|
+
const [composerRuntimeNotice, setComposerRuntimeNotice] = useState20(null);
|
|
5185
|
+
useEffect18(() => {
|
|
5186
|
+
autoSubmittedDraftRef.current = void 0;
|
|
5187
|
+
autoSubmittingDraftRef.current = void 0;
|
|
5188
|
+
pendingAutoSubmitUnlockRef.current = void 0;
|
|
5189
|
+
pendingAutoSubmitSettleRef.current = void 0;
|
|
5190
|
+
setAcceptedAutoSubmittedDraft(void 0);
|
|
5191
|
+
}, [sessionId]);
|
|
4972
5192
|
const {
|
|
4973
5193
|
messages,
|
|
4974
5194
|
sendMessage,
|
|
@@ -4985,7 +5205,8 @@ function ChatPanel(props) {
|
|
|
4985
5205
|
onData?.(part);
|
|
4986
5206
|
},
|
|
4987
5207
|
requestHeaders,
|
|
4988
|
-
persistMessages: capabilities.aiSdkOwnsHistory
|
|
5208
|
+
persistMessages: capabilities.aiSdkOwnsHistory,
|
|
5209
|
+
hydrateMessages
|
|
4989
5210
|
});
|
|
4990
5211
|
const { piMessages, handleData: handlePiData } = usePiChatProjection({
|
|
4991
5212
|
messages,
|
|
@@ -5014,9 +5235,14 @@ function ChatPanel(props) {
|
|
|
5014
5235
|
followUpDataHandlerRef.current = handleFollowUpData;
|
|
5015
5236
|
}, [handleFollowUpData]);
|
|
5016
5237
|
const mergedToolRenderers = mergeShadcnToolRenderers(toolRenderers);
|
|
5017
|
-
const
|
|
5238
|
+
const friendlyChatError = error ? friendlyError(error) : null;
|
|
5239
|
+
const runtimeErrorNotice = isComposerRuntimeNotice(friendlyChatError) ? friendlyChatError : null;
|
|
5240
|
+
const warmupNotice = composerNoticeForWarmup(workspaceWarmupStatus);
|
|
5241
|
+
const composerStatusNotice = composerRuntimeNotice ?? runtimeErrorNotice ?? warmupNotice;
|
|
5242
|
+
const workspaceWarmupBlocked = Boolean(warmupNotice);
|
|
5243
|
+
const composerBlocked = workspaceWarmupBlocked || composerBlockers.length > 0;
|
|
5018
5244
|
const primaryComposerBlocker = composerBlockers[0];
|
|
5019
|
-
const composerBlockerLabel = primaryComposerBlocker?.label ?? "Complete the pending workspace action to continue.";
|
|
5245
|
+
const composerBlockerLabel = workspaceWarmupBlocked ? warmupNotice?.title ?? "Preparing workspace\u2026" : primaryComposerBlocker?.label ?? "Complete the pending workspace action to continue.";
|
|
5020
5246
|
const registry = useMemo12(
|
|
5021
5247
|
() => {
|
|
5022
5248
|
const effectiveBuiltins = hotReloadEnabled ? builtinCommands : builtinCommands.filter((cmd) => cmd.name !== "reload");
|
|
@@ -5024,11 +5250,12 @@ function ChatPanel(props) {
|
|
|
5024
5250
|
},
|
|
5025
5251
|
[extraCommands, hotReloadEnabled]
|
|
5026
5252
|
);
|
|
5027
|
-
const skillsStamp = useServerSkills({ registry, requestHeaders });
|
|
5253
|
+
const skillsStamp = useServerSkills({ registry, requestHeaders, enabled: serverResourcesEnabled });
|
|
5028
5254
|
const allCommands = useMemo12(() => registry.list(), [registry, skillsStamp]);
|
|
5029
5255
|
const { availableModels, model, setModel } = useChatModelSelection({
|
|
5030
5256
|
defaultModel,
|
|
5031
|
-
requestHeaders
|
|
5257
|
+
requestHeaders,
|
|
5258
|
+
enabled: serverResourcesEnabled
|
|
5032
5259
|
});
|
|
5033
5260
|
const { thinkingLevel, setThinkingLevel, showThoughts, setShowThoughts } = useThinkingSettings(thinkingControl);
|
|
5034
5261
|
const { attachmentNotice, setAttachmentNotice } = useAttachmentNotice();
|
|
@@ -5127,23 +5354,49 @@ ${formatPluginReloadDiagnostics(diagnostics)}` : base;
|
|
|
5127
5354
|
const waitingTail = projectedTailMessages.filter((message) => projectedStatusById.get(message.id) === "queued");
|
|
5128
5355
|
const queuedPiTail = getQueuedPiTail(messages, piMessages);
|
|
5129
5356
|
const sdkHasVisibleAssistant = hasStandardVisibleAssistantParts(messages);
|
|
5357
|
+
let baseMessages;
|
|
5130
5358
|
if (sdkHasVisibleAssistant) {
|
|
5131
5359
|
const mergedMessages = mergeSettledPiFallbacksInPlace(messages, piMessages);
|
|
5132
|
-
|
|
5360
|
+
baseMessages = [
|
|
5133
5361
|
...mergedMessages,
|
|
5134
5362
|
...coalesceAssistantToolFragments(queuedPiTail),
|
|
5135
5363
|
...waitingTail
|
|
5136
5364
|
];
|
|
5365
|
+
} else if (piMessages.length > 0 && queuedPiTail.length > 0) {
|
|
5366
|
+
baseMessages = [...coalesceAssistantToolFragments(piMessages), ...waitingTail];
|
|
5367
|
+
} else if (piMessages.length > 0 && status === "ready" && hasPiVisibleDataParts(messages)) {
|
|
5368
|
+
baseMessages = [
|
|
5369
|
+
...sdkVisibleMessagesWithoutPiOnlyAssistants(messages),
|
|
5370
|
+
...coalesceAssistantToolFragments(piMessages),
|
|
5371
|
+
...waitingTail
|
|
5372
|
+
];
|
|
5373
|
+
} else {
|
|
5374
|
+
baseMessages = [...messages, ...waitingTail];
|
|
5137
5375
|
}
|
|
5138
|
-
|
|
5139
|
-
|
|
5140
|
-
|
|
5141
|
-
if (
|
|
5142
|
-
|
|
5376
|
+
const autoSubmittedDraft = acceptedAutoSubmittedDraft?.trim();
|
|
5377
|
+
if (!autoSubmittedDraft) return baseMessages;
|
|
5378
|
+
const hasUserTurn = baseMessages.some((message) => message.role === "user" && messageText(message).includes(autoSubmittedDraft));
|
|
5379
|
+
if (hasUserTurn) return baseMessages;
|
|
5380
|
+
const syntheticUserMessage = {
|
|
5381
|
+
id: `auto-submitted-user:${sessionId}:${autoSubmittedDraft}`,
|
|
5382
|
+
role: "user",
|
|
5383
|
+
parts: [{ type: "text", text: autoSubmittedDraft }]
|
|
5384
|
+
};
|
|
5385
|
+
let insertAt = baseMessages.length;
|
|
5386
|
+
for (let index = baseMessages.length - 1; index >= 0; index -= 1) {
|
|
5387
|
+
if (baseMessages[index]?.role === "assistant") {
|
|
5388
|
+
insertAt = index;
|
|
5389
|
+
break;
|
|
5390
|
+
}
|
|
5143
5391
|
}
|
|
5144
|
-
return [
|
|
5145
|
-
|
|
5146
|
-
|
|
5392
|
+
return [
|
|
5393
|
+
...baseMessages.slice(0, insertAt),
|
|
5394
|
+
syntheticUserMessage,
|
|
5395
|
+
...baseMessages.slice(insertAt)
|
|
5396
|
+
];
|
|
5397
|
+
}, [acceptedAutoSubmittedDraft, messages, piMessages, projectedTailMessages, projectedStatusById, sessionId, status]);
|
|
5398
|
+
const renderMessages = displayMessages.filter((message) => message.role !== "assistant" || hasVisibleMessageContent(message));
|
|
5399
|
+
const emptyHero = emptyPlacement === "hero" && renderMessages.length === 0;
|
|
5147
5400
|
const handleStop = useCallback16(() => {
|
|
5148
5401
|
onComposerStop?.();
|
|
5149
5402
|
stopAndClearFollowUps();
|
|
@@ -5167,6 +5420,14 @@ ${formatPluginReloadDiagnostics(diagnostics)}` : base;
|
|
|
5167
5420
|
[messages]
|
|
5168
5421
|
);
|
|
5169
5422
|
const textareaRef = useRef13(null);
|
|
5423
|
+
const promptInputController = useOptionalPromptInputController();
|
|
5424
|
+
const setComposerDraft = useCallback16((draft, focus = true) => {
|
|
5425
|
+
promptInputController?.textInput.setInput(draft);
|
|
5426
|
+
if (textareaRef.current) {
|
|
5427
|
+
textareaRef.current.value = draft;
|
|
5428
|
+
if (focus) textareaRef.current.focus();
|
|
5429
|
+
}
|
|
5430
|
+
}, [promptInputController]);
|
|
5170
5431
|
const {
|
|
5171
5432
|
mentionState,
|
|
5172
5433
|
slashQuery,
|
|
@@ -5183,12 +5444,31 @@ ${formatPluginReloadDiagnostics(diagnostics)}` : base;
|
|
|
5183
5444
|
textareaRef,
|
|
5184
5445
|
disabled: mentionState !== null || slashQuery !== null
|
|
5185
5446
|
});
|
|
5186
|
-
|
|
5187
|
-
|
|
5447
|
+
const restoredDraftRef = useRef13(void 0);
|
|
5448
|
+
useEffect18(() => {
|
|
5449
|
+
if (initialDraft === void 0) return;
|
|
5450
|
+
if (restoredDraftRef.current === initialDraft) return;
|
|
5451
|
+
if (!promptInputController && !textareaRef.current) return;
|
|
5452
|
+
setComposerDraft(initialDraft, initialDraft.length > 0);
|
|
5453
|
+
restoredDraftRef.current = initialDraft;
|
|
5454
|
+
onDraftRestored?.();
|
|
5455
|
+
}, [initialDraft, onDraftRestored, promptInputController, setComposerDraft]);
|
|
5456
|
+
async function runBeforeSubmit(draft, files, source) {
|
|
5457
|
+
const result = await onBeforeSubmit?.(draft, { files, sessionId, source });
|
|
5458
|
+
return result !== false;
|
|
5459
|
+
}
|
|
5460
|
+
async function handleSubmit({ text, files }, source = "composer") {
|
|
5461
|
+
if (composerBlocked) {
|
|
5462
|
+
if (warmupNotice) setComposerRuntimeNotice(warmupNotice);
|
|
5463
|
+
return false;
|
|
5464
|
+
}
|
|
5188
5465
|
const trimmed = text.trim();
|
|
5189
5466
|
if (trimmed.length === 0 && (!files || files.length === 0)) {
|
|
5190
5467
|
return;
|
|
5191
5468
|
}
|
|
5469
|
+
setComposerRuntimeNotice(null);
|
|
5470
|
+
if (!await runBeforeSubmit(text, files ?? [], source)) return false;
|
|
5471
|
+
if (liveSessionIdRef.current !== sessionId) return false;
|
|
5192
5472
|
const parsed = parseSlashCommand(text);
|
|
5193
5473
|
if (parsed) {
|
|
5194
5474
|
const cmd = registry.get(parsed.name);
|
|
@@ -5240,6 +5520,7 @@ ${parsed.args}` : `skill: ${parsed.name}`;
|
|
|
5240
5520
|
files: files ?? [],
|
|
5241
5521
|
mentionedFiles
|
|
5242
5522
|
});
|
|
5523
|
+
if (liveSessionIdRef.current !== sessionId) return false;
|
|
5243
5524
|
clearMentionedFiles();
|
|
5244
5525
|
if (isStreaming && files.length > 0) {
|
|
5245
5526
|
setAttachmentNotice("Attachments can be sent after the current response finishes.");
|
|
@@ -5277,6 +5558,48 @@ ${parsed.args}` : `skill: ${parsed.name}`;
|
|
|
5277
5558
|
}
|
|
5278
5559
|
);
|
|
5279
5560
|
}
|
|
5561
|
+
useEffect18(() => {
|
|
5562
|
+
const pendingDraft = pendingAutoSubmitUnlockRef.current;
|
|
5563
|
+
if (!pendingDraft) return;
|
|
5564
|
+
const localTurnStarted = status === "submitted" || status === "streaming" || messages.some(
|
|
5565
|
+
(message) => message.role === "user" && message.parts.some((part) => isTextPart(part) && part.text.includes(pendingDraft))
|
|
5566
|
+
);
|
|
5567
|
+
if (!localTurnStarted) return;
|
|
5568
|
+
pendingAutoSubmitUnlockRef.current = void 0;
|
|
5569
|
+
onAutoSubmitInitialDraftAccepted?.();
|
|
5570
|
+
}, [messages, onAutoSubmitInitialDraftAccepted, status]);
|
|
5571
|
+
const prevAutoSubmitStatusRef = useRef13(status);
|
|
5572
|
+
useEffect18(() => {
|
|
5573
|
+
const prev = prevAutoSubmitStatusRef.current;
|
|
5574
|
+
prevAutoSubmitStatusRef.current = status;
|
|
5575
|
+
if (!pendingAutoSubmitSettleRef.current) return;
|
|
5576
|
+
if (status !== "ready") return;
|
|
5577
|
+
if (prev !== "submitted" && prev !== "streaming") return;
|
|
5578
|
+
pendingAutoSubmitSettleRef.current = void 0;
|
|
5579
|
+
onAutoSubmitInitialDraftSettled?.();
|
|
5580
|
+
}, [onAutoSubmitInitialDraftSettled, status]);
|
|
5581
|
+
useEffect18(() => {
|
|
5582
|
+
if (workspaceWarmupStatus?.status === "ready") setComposerRuntimeNotice(null);
|
|
5583
|
+
}, [workspaceWarmupStatus?.status]);
|
|
5584
|
+
useEffect18(() => {
|
|
5585
|
+
if (!autoSubmitInitialDraft) return;
|
|
5586
|
+
if (!initialDraft?.trim()) return;
|
|
5587
|
+
if (autoSubmittedDraftRef.current === initialDraft) return;
|
|
5588
|
+
if (autoSubmittingDraftRef.current === initialDraft) return;
|
|
5589
|
+
const targetSessionId = sessionId;
|
|
5590
|
+
autoSubmittingDraftRef.current = initialDraft;
|
|
5591
|
+
void (async () => {
|
|
5592
|
+
const result = await handleSubmit({ text: initialDraft, files: [] }, "composer");
|
|
5593
|
+
if (activeAutoSubmitSessionRef.current !== targetSessionId) return;
|
|
5594
|
+
autoSubmittingDraftRef.current = void 0;
|
|
5595
|
+
if (result === false) return;
|
|
5596
|
+
autoSubmittedDraftRef.current = initialDraft;
|
|
5597
|
+
pendingAutoSubmitUnlockRef.current = initialDraft;
|
|
5598
|
+
pendingAutoSubmitSettleRef.current = initialDraft;
|
|
5599
|
+
setAcceptedAutoSubmittedDraft(initialDraft);
|
|
5600
|
+
setComposerDraft("", false);
|
|
5601
|
+
})();
|
|
5602
|
+
}, [autoSubmitInitialDraft, handleSubmit, initialDraft, sessionId, setComposerDraft]);
|
|
5280
5603
|
return /* @__PURE__ */ jsx25(ArtifactOpenProvider, { onOpenArtifact, children: /* @__PURE__ */ jsxs23(
|
|
5281
5604
|
"div",
|
|
5282
5605
|
{
|
|
@@ -5296,6 +5619,7 @@ ${parsed.args}` : `skill: ${parsed.name}`;
|
|
|
5296
5619
|
{
|
|
5297
5620
|
className: cn(
|
|
5298
5621
|
"flex h-full min-h-0 flex-col overflow-hidden",
|
|
5622
|
+
emptyHero && "justify-center",
|
|
5299
5623
|
chrome && "mx-3 my-3 rounded-xl bg-[color:var(--surface-chat)] shadow-[0_1px_0_oklch(0_0_0/0.02),0_1px_2px_-1px_oklch(0_0_0/0.04),inset_0_0_0_1px_oklch(from_var(--border)_l_c_h/0.6)]"
|
|
5300
5624
|
),
|
|
5301
5625
|
children: [
|
|
@@ -5325,7 +5649,7 @@ ${parsed.args}` : `skill: ${parsed.name}`;
|
|
|
5325
5649
|
/* @__PURE__ */ jsxs23(
|
|
5326
5650
|
Conversation,
|
|
5327
5651
|
{
|
|
5328
|
-
className: "flex-1",
|
|
5652
|
+
className: emptyHero ? "max-h-[45vh] flex-none" : "flex-1",
|
|
5329
5653
|
"aria-label": "Agent conversation",
|
|
5330
5654
|
"aria-live": "polite",
|
|
5331
5655
|
onScrollToBottomReady: (scrollToBottom) => {
|
|
@@ -5334,29 +5658,24 @@ ${parsed.args}` : `skill: ${parsed.name}`;
|
|
|
5334
5658
|
children: [
|
|
5335
5659
|
/* @__PURE__ */ jsxs23(ConversationContent, { className: cn(
|
|
5336
5660
|
"mx-auto flex w-full flex-col gap-6",
|
|
5337
|
-
chrome ? "max-w-3xl px-6 py-8" : "max-w-[680px] px-4 py-4"
|
|
5661
|
+
chrome ? "max-w-3xl px-6 py-8" : "max-w-[680px] px-4 py-4",
|
|
5662
|
+
emptyHero && "py-4 text-center"
|
|
5338
5663
|
), children: [
|
|
5339
|
-
|
|
5664
|
+
renderMessages.length === 0 && /* @__PURE__ */ jsx25(
|
|
5340
5665
|
ChatEmptyState,
|
|
5341
5666
|
{
|
|
5342
5667
|
eyebrow: emptyState?.eyebrow,
|
|
5343
5668
|
title: emptyState?.title,
|
|
5344
5669
|
description: emptyState?.description,
|
|
5345
5670
|
suggestions,
|
|
5671
|
+
className: emptyHero ? "items-center text-center [&>p]:mx-auto" : void 0,
|
|
5346
5672
|
onSelect: (s) => {
|
|
5347
5673
|
const text = s.prompt ?? s.label;
|
|
5348
5674
|
if (!text.trim()) return;
|
|
5349
|
-
void
|
|
5350
|
-
{ text, files: [] },
|
|
5351
|
-
|
|
5352
|
-
|
|
5353
|
-
sessionId,
|
|
5354
|
-
message: text,
|
|
5355
|
-
...modelPayload(model),
|
|
5356
|
-
attachments: []
|
|
5357
|
-
}
|
|
5358
|
-
}
|
|
5359
|
-
);
|
|
5675
|
+
void (async () => {
|
|
5676
|
+
const submitted = await handleSubmit({ text, files: [] }, "suggestion");
|
|
5677
|
+
if (submitted === false) setComposerDraft(text);
|
|
5678
|
+
})();
|
|
5360
5679
|
}
|
|
5361
5680
|
}
|
|
5362
5681
|
),
|
|
@@ -5372,7 +5691,11 @@ ${parsed.args}` : `skill: ${parsed.name}`;
|
|
|
5372
5691
|
const key = `${message.id}-${index}`;
|
|
5373
5692
|
if (!reasoningPart) {
|
|
5374
5693
|
if (!isBlankTextPart(part)) {
|
|
5375
|
-
|
|
5694
|
+
const previous2 = items[items.length - 1];
|
|
5695
|
+
const duplicateAdjacentAssistantText = role === "assistant" && isTextPart(part) && previous2?.kind === "part" && isTextPart(previous2.part) && previous2.part.text.trim() === part.text.trim();
|
|
5696
|
+
if (!duplicateAdjacentAssistantText) {
|
|
5697
|
+
items.push({ kind: "part", part, key });
|
|
5698
|
+
}
|
|
5376
5699
|
}
|
|
5377
5700
|
return items;
|
|
5378
5701
|
}
|
|
@@ -5399,6 +5722,12 @@ ${reasoningPart.text}`;
|
|
|
5399
5722
|
acc.push({ kind: "tool-group", tools: [{ part: item.part, key: item.key }], key: item.key });
|
|
5400
5723
|
}
|
|
5401
5724
|
} else if (item.kind === "part" && !isTextPart(item.part)) {
|
|
5725
|
+
} else if (item.kind === "part" && isTextPart(item.part)) {
|
|
5726
|
+
const prev = acc[acc.length - 1];
|
|
5727
|
+
const duplicateAdjacentAssistantText = role === "assistant" && prev?.kind === "part" && isTextPart(prev.part) && prev.part.text.trim() === item.part.text.trim();
|
|
5728
|
+
if (!duplicateAdjacentAssistantText) {
|
|
5729
|
+
acc.push(item);
|
|
5730
|
+
}
|
|
5402
5731
|
} else {
|
|
5403
5732
|
acc.push(item);
|
|
5404
5733
|
}
|
|
@@ -5559,8 +5888,8 @@ ${reasoningPart.text}`;
|
|
|
5559
5888
|
);
|
|
5560
5889
|
}),
|
|
5561
5890
|
(() => {
|
|
5562
|
-
if (!
|
|
5563
|
-
const friendly =
|
|
5891
|
+
if (!friendlyChatError || isComposerRuntimeNotice(friendlyChatError)) return null;
|
|
5892
|
+
const friendly = friendlyChatError;
|
|
5564
5893
|
return /* @__PURE__ */ jsx25(Message, { from: "assistant", className: "!max-w-full", children: /* @__PURE__ */ jsx25(MessageContent, { className: "rounded-xl border border-destructive/40 bg-destructive/10 px-4 py-3", children: /* @__PURE__ */ jsxs23("div", { role: "alert", className: "flex items-start gap-3 text-sm text-destructive", children: [
|
|
5565
5894
|
/* @__PURE__ */ jsx25("div", { className: "mt-0.5 shrink-0", children: /* @__PURE__ */ jsxs23("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
|
|
5566
5895
|
/* @__PURE__ */ jsx25("circle", { cx: "12", cy: "12", r: "10" }),
|
|
@@ -5627,7 +5956,26 @@ ${reasoningPart.text}`;
|
|
|
5627
5956
|
)
|
|
5628
5957
|
}
|
|
5629
5958
|
),
|
|
5630
|
-
|
|
5959
|
+
composerStatusNotice && /* @__PURE__ */ jsx25(
|
|
5960
|
+
"div",
|
|
5961
|
+
{
|
|
5962
|
+
"data-testid": "chat-composer-runtime-notice",
|
|
5963
|
+
role: "status",
|
|
5964
|
+
"aria-live": "polite",
|
|
5965
|
+
className: cn(
|
|
5966
|
+
"mx-auto mb-2 w-full max-w-3xl rounded-[var(--radius-md)] border border-accent/40 bg-[color:var(--accent-soft)]",
|
|
5967
|
+
"px-3 py-2 text-xs text-foreground"
|
|
5968
|
+
),
|
|
5969
|
+
children: /* @__PURE__ */ jsxs23("div", { className: "flex items-start gap-2", children: [
|
|
5970
|
+
composerStatusNotice.code === ErrorCode.enum.AGENT_RUNTIME_NOT_READY ? /* @__PURE__ */ jsx25(Loader2, { "aria-hidden": "true", className: "mt-0.5 size-3.5 shrink-0 animate-spin text-muted-foreground" }) : /* @__PURE__ */ jsx25(AlertCircleIcon, { "aria-hidden": "true", className: "mt-0.5 size-3.5 shrink-0 text-destructive" }),
|
|
5971
|
+
/* @__PURE__ */ jsxs23("div", { className: "min-w-0", children: [
|
|
5972
|
+
/* @__PURE__ */ jsx25("div", { className: "font-medium", children: composerStatusNotice.title }),
|
|
5973
|
+
composerStatusNotice.detail && /* @__PURE__ */ jsx25("div", { className: "mt-0.5 text-muted-foreground", children: composerStatusNotice.detail })
|
|
5974
|
+
] })
|
|
5975
|
+
] })
|
|
5976
|
+
}
|
|
5977
|
+
),
|
|
5978
|
+
composerBlocked && !workspaceWarmupBlocked && /* @__PURE__ */ jsxs23(
|
|
5631
5979
|
"div",
|
|
5632
5980
|
{
|
|
5633
5981
|
role: "status",
|
|
@@ -5722,7 +6070,7 @@ ${reasoningPart.text}`;
|
|
|
5722
6070
|
PromptInput,
|
|
5723
6071
|
{
|
|
5724
6072
|
"data-boring-state": status,
|
|
5725
|
-
onSubmit: handleSubmit,
|
|
6073
|
+
onSubmit: (message) => handleSubmit(message),
|
|
5726
6074
|
multiple: true,
|
|
5727
6075
|
maxFiles: attachmentsDisabled ? 0 : 20,
|
|
5728
6076
|
maxFileSize: 5 * 1024 * 1024,
|
|
@@ -5744,14 +6092,16 @@ ${reasoningPart.text}`;
|
|
|
5744
6092
|
/* @__PURE__ */ jsx25(
|
|
5745
6093
|
PromptInputTextarea,
|
|
5746
6094
|
{
|
|
5747
|
-
|
|
6095
|
+
defaultValue: promptInputController ? void 0 : initialDraft,
|
|
6096
|
+
placeholder: composerBlocked ? composerBlockerLabel : composerPlaceholder ?? "Ask anything\u2026",
|
|
5748
6097
|
disabled: composerBlocked,
|
|
5749
6098
|
readOnly: composerBlocked,
|
|
6099
|
+
ref: textareaRef,
|
|
5750
6100
|
onChange: handleComposerChange,
|
|
5751
6101
|
onKeyDown: handleComposerKeyDown,
|
|
5752
6102
|
className: cn(
|
|
5753
|
-
"min-h-
|
|
5754
|
-
"px-4 py-
|
|
6103
|
+
"min-h-11 resize-none border-0 bg-transparent shadow-none",
|
|
6104
|
+
"px-4 py-2.5 text-[13px] leading-[1.55] placeholder:text-muted-foreground/45",
|
|
5755
6105
|
"focus-visible:ring-0 focus-visible:ring-offset-0"
|
|
5756
6106
|
)
|
|
5757
6107
|
}
|
|
@@ -5809,7 +6159,7 @@ ${reasoningPart.text}`;
|
|
|
5809
6159
|
// to earn the real estate. Becomes a Stop affordance
|
|
5810
6160
|
// (square icon + aria-label="Stop") while the turn
|
|
5811
6161
|
// streams.
|
|
5812
|
-
"h-8 w-8 shrink-0 rounded-
|
|
6162
|
+
"h-8 w-8 shrink-0 rounded-full",
|
|
5813
6163
|
"bg-[color:var(--accent)] text-[color:var(--accent-foreground)]",
|
|
5814
6164
|
"transition-all duration-150 ease-[cubic-bezier(0.22,1,0.36,1)]",
|
|
5815
6165
|
"hover:shadow-[0_0_0_3px_oklch(from_var(--accent)_l_c_h/0.30)] hover:brightness-110 hover:scale-[1.04]",
|
|
@@ -5863,7 +6213,7 @@ function AttachmentButton({ disabled }) {
|
|
|
5863
6213
|
className: cn(composerActionClass, "w-8"),
|
|
5864
6214
|
"aria-label": "Attach files",
|
|
5865
6215
|
title: disabled ? "Attachments are available after the current response finishes." : "Attach files",
|
|
5866
|
-
children: /* @__PURE__ */ jsx25(PaperclipIcon2, { className: "h-
|
|
6216
|
+
children: /* @__PURE__ */ jsx25(PaperclipIcon2, { className: "h-3.5 w-3.5", strokeWidth: 1.75 })
|
|
5867
6217
|
}
|
|
5868
6218
|
);
|
|
5869
6219
|
}
|
|
@@ -6103,7 +6453,7 @@ function getAgentCommands(options = {}) {
|
|
|
6103
6453
|
}
|
|
6104
6454
|
|
|
6105
6455
|
// src/front/hooks/useSessions.ts
|
|
6106
|
-
import { useState as useState21, useEffect as useEffect19, useCallback as useCallback17, useRef as useRef14 } from "react";
|
|
6456
|
+
import { useState as useState21, useEffect as useEffect19, useCallback as useCallback17, useMemo as useMemo13, useRef as useRef14 } from "react";
|
|
6107
6457
|
var API_BASE = "/api/v1/agent/sessions";
|
|
6108
6458
|
var STORAGE_KEY = "boring-agent:activeSessionId";
|
|
6109
6459
|
function readPersistedId(storageKey) {
|
|
@@ -6120,6 +6470,9 @@ function persistId(storageKey, id) {
|
|
|
6120
6470
|
} catch {
|
|
6121
6471
|
}
|
|
6122
6472
|
}
|
|
6473
|
+
function headersScopeKey(headers) {
|
|
6474
|
+
return JSON.stringify(Object.entries(headers ?? {}).sort(([a], [b]) => a.localeCompare(b)));
|
|
6475
|
+
}
|
|
6123
6476
|
function requestInit(headers) {
|
|
6124
6477
|
if (!headers || Object.keys(headers).length === 0) return void 0;
|
|
6125
6478
|
return { headers };
|
|
@@ -6133,21 +6486,46 @@ async function fetchSessions(headers) {
|
|
|
6133
6486
|
function useSessions(opts = {}) {
|
|
6134
6487
|
const storageKey = opts.storageKey ?? STORAGE_KEY;
|
|
6135
6488
|
const requestHeaders = opts.requestHeaders;
|
|
6489
|
+
const enabled = opts.enabled ?? true;
|
|
6490
|
+
const refreshKey = opts.refreshKey;
|
|
6491
|
+
const scopeKey = useMemo13(
|
|
6492
|
+
() => `${storageKey}
|
|
6493
|
+
${headersScopeKey(requestHeaders)}`,
|
|
6494
|
+
[requestHeaders, storageKey]
|
|
6495
|
+
);
|
|
6136
6496
|
const [sessions, setSessions] = useState21([]);
|
|
6137
6497
|
const [activeSessionId, setActiveSessionId] = useState21(
|
|
6138
6498
|
() => readPersistedId(storageKey)
|
|
6139
6499
|
);
|
|
6140
6500
|
const [loading, setLoading] = useState21(true);
|
|
6141
6501
|
const [error, setError] = useState21();
|
|
6502
|
+
const [loaded, setLoaded] = useState21(false);
|
|
6142
6503
|
const versionRef = useRef14(0);
|
|
6504
|
+
const loadedScopeRef = useRef14(scopeKey);
|
|
6143
6505
|
const refresh = useCallback17(async () => {
|
|
6144
6506
|
const v = ++versionRef.current;
|
|
6507
|
+
if (!enabled) {
|
|
6508
|
+
setSessions([]);
|
|
6509
|
+
setActiveSessionId(void 0);
|
|
6510
|
+
setError(void 0);
|
|
6511
|
+
setLoaded(false);
|
|
6512
|
+
setLoading(false);
|
|
6513
|
+
return;
|
|
6514
|
+
}
|
|
6515
|
+
setLoaded(false);
|
|
6516
|
+
setLoading(true);
|
|
6145
6517
|
try {
|
|
6146
6518
|
const data = await fetchSessions(requestHeaders);
|
|
6147
6519
|
if (v === versionRef.current) {
|
|
6520
|
+
const replacingLoadedScope = loadedScopeRef.current !== scopeKey;
|
|
6521
|
+
loadedScopeRef.current = scopeKey;
|
|
6522
|
+
const persisted = readPersistedId(storageKey);
|
|
6523
|
+
setError(void 0);
|
|
6524
|
+
setLoaded(true);
|
|
6148
6525
|
setSessions(data);
|
|
6149
6526
|
setActiveSessionId((prev) => {
|
|
6150
|
-
|
|
6527
|
+
const preferred = replacingLoadedScope ? persisted : prev ?? persisted;
|
|
6528
|
+
if (preferred && data.some((session) => session.id === preferred)) return preferred;
|
|
6151
6529
|
const next = data[0]?.id;
|
|
6152
6530
|
persistId(storageKey, next);
|
|
6153
6531
|
return next;
|
|
@@ -6156,16 +6534,32 @@ function useSessions(opts = {}) {
|
|
|
6156
6534
|
}
|
|
6157
6535
|
} catch (err) {
|
|
6158
6536
|
if (v === versionRef.current) {
|
|
6537
|
+
const replacingLoadedScope = loadedScopeRef.current !== scopeKey;
|
|
6538
|
+
loadedScopeRef.current = scopeKey;
|
|
6539
|
+
if (replacingLoadedScope) {
|
|
6540
|
+
setSessions([]);
|
|
6541
|
+
setActiveSessionId(void 0);
|
|
6542
|
+
}
|
|
6543
|
+
setLoaded(true);
|
|
6159
6544
|
setError(err instanceof Error ? err : new Error(String(err)));
|
|
6160
6545
|
setLoading(false);
|
|
6161
6546
|
}
|
|
6162
6547
|
}
|
|
6163
|
-
}, [requestHeaders, storageKey]);
|
|
6548
|
+
}, [enabled, requestHeaders, scopeKey, storageKey]);
|
|
6164
6549
|
useEffect19(() => {
|
|
6550
|
+
if (!enabled) {
|
|
6551
|
+
setSessions([]);
|
|
6552
|
+
setActiveSessionId(void 0);
|
|
6553
|
+
setError(void 0);
|
|
6554
|
+
setLoaded(false);
|
|
6555
|
+
setLoading(false);
|
|
6556
|
+
return;
|
|
6557
|
+
}
|
|
6165
6558
|
void refresh();
|
|
6166
|
-
}, [refresh]);
|
|
6559
|
+
}, [enabled, refresh, refreshKey, scopeKey]);
|
|
6167
6560
|
const create = useCallback17(
|
|
6168
6561
|
async (init) => {
|
|
6562
|
+
if (!enabled) throw new Error("Sessions are disabled");
|
|
6169
6563
|
const res = await fetch(API_BASE, {
|
|
6170
6564
|
method: "POST",
|
|
6171
6565
|
headers: { ...requestHeaders, "Content-Type": "application/json" },
|
|
@@ -6183,7 +6577,7 @@ function useSessions(opts = {}) {
|
|
|
6183
6577
|
void refresh();
|
|
6184
6578
|
return session;
|
|
6185
6579
|
},
|
|
6186
|
-
[refresh, requestHeaders, storageKey]
|
|
6580
|
+
[enabled, refresh, requestHeaders, storageKey]
|
|
6187
6581
|
);
|
|
6188
6582
|
const switchSession = useCallback17((id) => {
|
|
6189
6583
|
setActiveSessionId(id);
|
|
@@ -6191,6 +6585,7 @@ function useSessions(opts = {}) {
|
|
|
6191
6585
|
}, [storageKey]);
|
|
6192
6586
|
const deleteSession = useCallback17(
|
|
6193
6587
|
async (id) => {
|
|
6588
|
+
if (!enabled) throw new Error("Sessions are disabled");
|
|
6194
6589
|
setSessions((prev) => prev.filter((s) => s.id !== id));
|
|
6195
6590
|
setActiveSessionId((prev) => {
|
|
6196
6591
|
if (prev === id) {
|
|
@@ -6214,13 +6609,16 @@ function useSessions(opts = {}) {
|
|
|
6214
6609
|
}
|
|
6215
6610
|
void refresh();
|
|
6216
6611
|
},
|
|
6217
|
-
[refresh, requestHeaders, storageKey]
|
|
6612
|
+
[enabled, refresh, requestHeaders, storageKey]
|
|
6218
6613
|
);
|
|
6614
|
+
const scopeMatches = loadedScopeRef.current === scopeKey;
|
|
6615
|
+
const visibleSessions = enabled && scopeMatches ? sessions : [];
|
|
6616
|
+
const visibleActiveSessionId = enabled && scopeMatches ? activeSessionId : void 0;
|
|
6219
6617
|
return {
|
|
6220
|
-
sessions,
|
|
6221
|
-
activeSession:
|
|
6222
|
-
activeSessionId,
|
|
6223
|
-
loading,
|
|
6618
|
+
sessions: visibleSessions,
|
|
6619
|
+
activeSession: visibleSessions.find((s) => s.id === visibleActiveSessionId),
|
|
6620
|
+
activeSessionId: visibleActiveSessionId,
|
|
6621
|
+
loading: enabled ? !scopeMatches || loading || !loaded : false,
|
|
6224
6622
|
error,
|
|
6225
6623
|
create,
|
|
6226
6624
|
switch: switchSession,
|