@hachej/boring-agent 0.1.23 → 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 +432 -76
- package/dist/front/styles.css +8 -0
- 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
|
|
|
@@ -4872,6 +4988,9 @@ function hasVisibleMessageContent(message) {
|
|
|
4872
4988
|
return Boolean(reasoning?.text.trim());
|
|
4873
4989
|
});
|
|
4874
4990
|
}
|
|
4991
|
+
function messageText(message) {
|
|
4992
|
+
return (message.parts ?? []).filter(isTextPart).map((part) => part.text).join("\n").trim();
|
|
4993
|
+
}
|
|
4875
4994
|
function hasPiVisibleDataParts(messages) {
|
|
4876
4995
|
return messages.some(
|
|
4877
4996
|
(message) => (message.parts ?? []).some((part) => {
|
|
@@ -4890,7 +5009,8 @@ function collectSdkRepresentedMessageIds(messages) {
|
|
|
4890
5009
|
if (!hasVisibleMessageContent(message)) continue;
|
|
4891
5010
|
represented.add(message.id);
|
|
4892
5011
|
for (const part of message.parts ?? []) {
|
|
4893
|
-
|
|
5012
|
+
const type = dataPartType(part);
|
|
5013
|
+
if (type !== "data-pi-message-start" && type !== "data-pi-message-end") continue;
|
|
4894
5014
|
const data = part.data;
|
|
4895
5015
|
if (typeof data?.messageId === "string") represented.add(data.messageId);
|
|
4896
5016
|
}
|
|
@@ -4907,6 +5027,19 @@ function uniqueVisiblePiMessages(messages) {
|
|
|
4907
5027
|
}
|
|
4908
5028
|
return out;
|
|
4909
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
|
+
}
|
|
4910
5043
|
function getSettledPiAssistantIds(message) {
|
|
4911
5044
|
const ids = [];
|
|
4912
5045
|
for (const part of message.parts ?? []) {
|
|
@@ -4943,6 +5076,23 @@ function mergeSettledPiFallbacksInPlace(messages, piMessages) {
|
|
|
4943
5076
|
}
|
|
4944
5077
|
return out;
|
|
4945
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
|
+
}
|
|
4946
5096
|
function getQueuedPiTail(messages, piMessages) {
|
|
4947
5097
|
if (piMessages.length === 0) return [];
|
|
4948
5098
|
const queuedIds = /* @__PURE__ */ new Set();
|
|
@@ -4997,8 +5147,19 @@ function ChatPanel(props) {
|
|
|
4997
5147
|
defaultModel,
|
|
4998
5148
|
onData,
|
|
4999
5149
|
requestHeaders,
|
|
5150
|
+
hydrateMessages,
|
|
5000
5151
|
onOpenArtifact,
|
|
5001
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,
|
|
5002
5163
|
composerBlockers = [],
|
|
5003
5164
|
onComposerStop,
|
|
5004
5165
|
onComposerBlockerAction
|
|
@@ -5011,6 +5172,23 @@ function ChatPanel(props) {
|
|
|
5011
5172
|
});
|
|
5012
5173
|
const followUpDataHandlerRef = useRef13(() => {
|
|
5013
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]);
|
|
5014
5192
|
const {
|
|
5015
5193
|
messages,
|
|
5016
5194
|
sendMessage,
|
|
@@ -5027,7 +5205,8 @@ function ChatPanel(props) {
|
|
|
5027
5205
|
onData?.(part);
|
|
5028
5206
|
},
|
|
5029
5207
|
requestHeaders,
|
|
5030
|
-
persistMessages: capabilities.aiSdkOwnsHistory
|
|
5208
|
+
persistMessages: capabilities.aiSdkOwnsHistory,
|
|
5209
|
+
hydrateMessages
|
|
5031
5210
|
});
|
|
5032
5211
|
const { piMessages, handleData: handlePiData } = usePiChatProjection({
|
|
5033
5212
|
messages,
|
|
@@ -5056,9 +5235,14 @@ function ChatPanel(props) {
|
|
|
5056
5235
|
followUpDataHandlerRef.current = handleFollowUpData;
|
|
5057
5236
|
}, [handleFollowUpData]);
|
|
5058
5237
|
const mergedToolRenderers = mergeShadcnToolRenderers(toolRenderers);
|
|
5059
|
-
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;
|
|
5060
5244
|
const primaryComposerBlocker = composerBlockers[0];
|
|
5061
|
-
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.";
|
|
5062
5246
|
const registry = useMemo12(
|
|
5063
5247
|
() => {
|
|
5064
5248
|
const effectiveBuiltins = hotReloadEnabled ? builtinCommands : builtinCommands.filter((cmd) => cmd.name !== "reload");
|
|
@@ -5066,11 +5250,12 @@ function ChatPanel(props) {
|
|
|
5066
5250
|
},
|
|
5067
5251
|
[extraCommands, hotReloadEnabled]
|
|
5068
5252
|
);
|
|
5069
|
-
const skillsStamp = useServerSkills({ registry, requestHeaders });
|
|
5253
|
+
const skillsStamp = useServerSkills({ registry, requestHeaders, enabled: serverResourcesEnabled });
|
|
5070
5254
|
const allCommands = useMemo12(() => registry.list(), [registry, skillsStamp]);
|
|
5071
5255
|
const { availableModels, model, setModel } = useChatModelSelection({
|
|
5072
5256
|
defaultModel,
|
|
5073
|
-
requestHeaders
|
|
5257
|
+
requestHeaders,
|
|
5258
|
+
enabled: serverResourcesEnabled
|
|
5074
5259
|
});
|
|
5075
5260
|
const { thinkingLevel, setThinkingLevel, showThoughts, setShowThoughts } = useThinkingSettings(thinkingControl);
|
|
5076
5261
|
const { attachmentNotice, setAttachmentNotice } = useAttachmentNotice();
|
|
@@ -5169,23 +5354,49 @@ ${formatPluginReloadDiagnostics(diagnostics)}` : base;
|
|
|
5169
5354
|
const waitingTail = projectedTailMessages.filter((message) => projectedStatusById.get(message.id) === "queued");
|
|
5170
5355
|
const queuedPiTail = getQueuedPiTail(messages, piMessages);
|
|
5171
5356
|
const sdkHasVisibleAssistant = hasStandardVisibleAssistantParts(messages);
|
|
5357
|
+
let baseMessages;
|
|
5172
5358
|
if (sdkHasVisibleAssistant) {
|
|
5173
5359
|
const mergedMessages = mergeSettledPiFallbacksInPlace(messages, piMessages);
|
|
5174
|
-
|
|
5360
|
+
baseMessages = [
|
|
5175
5361
|
...mergedMessages,
|
|
5176
5362
|
...coalesceAssistantToolFragments(queuedPiTail),
|
|
5177
5363
|
...waitingTail
|
|
5178
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];
|
|
5179
5375
|
}
|
|
5180
|
-
|
|
5181
|
-
|
|
5182
|
-
|
|
5183
|
-
if (
|
|
5184
|
-
|
|
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
|
+
}
|
|
5185
5391
|
}
|
|
5186
|
-
return [
|
|
5187
|
-
|
|
5188
|
-
|
|
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;
|
|
5189
5400
|
const handleStop = useCallback16(() => {
|
|
5190
5401
|
onComposerStop?.();
|
|
5191
5402
|
stopAndClearFollowUps();
|
|
@@ -5209,6 +5420,14 @@ ${formatPluginReloadDiagnostics(diagnostics)}` : base;
|
|
|
5209
5420
|
[messages]
|
|
5210
5421
|
);
|
|
5211
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]);
|
|
5212
5431
|
const {
|
|
5213
5432
|
mentionState,
|
|
5214
5433
|
slashQuery,
|
|
@@ -5225,12 +5444,31 @@ ${formatPluginReloadDiagnostics(diagnostics)}` : base;
|
|
|
5225
5444
|
textareaRef,
|
|
5226
5445
|
disabled: mentionState !== null || slashQuery !== null
|
|
5227
5446
|
});
|
|
5228
|
-
|
|
5229
|
-
|
|
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
|
+
}
|
|
5230
5465
|
const trimmed = text.trim();
|
|
5231
5466
|
if (trimmed.length === 0 && (!files || files.length === 0)) {
|
|
5232
5467
|
return;
|
|
5233
5468
|
}
|
|
5469
|
+
setComposerRuntimeNotice(null);
|
|
5470
|
+
if (!await runBeforeSubmit(text, files ?? [], source)) return false;
|
|
5471
|
+
if (liveSessionIdRef.current !== sessionId) return false;
|
|
5234
5472
|
const parsed = parseSlashCommand(text);
|
|
5235
5473
|
if (parsed) {
|
|
5236
5474
|
const cmd = registry.get(parsed.name);
|
|
@@ -5282,6 +5520,7 @@ ${parsed.args}` : `skill: ${parsed.name}`;
|
|
|
5282
5520
|
files: files ?? [],
|
|
5283
5521
|
mentionedFiles
|
|
5284
5522
|
});
|
|
5523
|
+
if (liveSessionIdRef.current !== sessionId) return false;
|
|
5285
5524
|
clearMentionedFiles();
|
|
5286
5525
|
if (isStreaming && files.length > 0) {
|
|
5287
5526
|
setAttachmentNotice("Attachments can be sent after the current response finishes.");
|
|
@@ -5319,6 +5558,48 @@ ${parsed.args}` : `skill: ${parsed.name}`;
|
|
|
5319
5558
|
}
|
|
5320
5559
|
);
|
|
5321
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]);
|
|
5322
5603
|
return /* @__PURE__ */ jsx25(ArtifactOpenProvider, { onOpenArtifact, children: /* @__PURE__ */ jsxs23(
|
|
5323
5604
|
"div",
|
|
5324
5605
|
{
|
|
@@ -5338,6 +5619,7 @@ ${parsed.args}` : `skill: ${parsed.name}`;
|
|
|
5338
5619
|
{
|
|
5339
5620
|
className: cn(
|
|
5340
5621
|
"flex h-full min-h-0 flex-col overflow-hidden",
|
|
5622
|
+
emptyHero && "justify-center",
|
|
5341
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)]"
|
|
5342
5624
|
),
|
|
5343
5625
|
children: [
|
|
@@ -5367,7 +5649,7 @@ ${parsed.args}` : `skill: ${parsed.name}`;
|
|
|
5367
5649
|
/* @__PURE__ */ jsxs23(
|
|
5368
5650
|
Conversation,
|
|
5369
5651
|
{
|
|
5370
|
-
className: "flex-1",
|
|
5652
|
+
className: emptyHero ? "max-h-[45vh] flex-none" : "flex-1",
|
|
5371
5653
|
"aria-label": "Agent conversation",
|
|
5372
5654
|
"aria-live": "polite",
|
|
5373
5655
|
onScrollToBottomReady: (scrollToBottom) => {
|
|
@@ -5376,29 +5658,24 @@ ${parsed.args}` : `skill: ${parsed.name}`;
|
|
|
5376
5658
|
children: [
|
|
5377
5659
|
/* @__PURE__ */ jsxs23(ConversationContent, { className: cn(
|
|
5378
5660
|
"mx-auto flex w-full flex-col gap-6",
|
|
5379
|
-
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"
|
|
5380
5663
|
), children: [
|
|
5381
|
-
|
|
5664
|
+
renderMessages.length === 0 && /* @__PURE__ */ jsx25(
|
|
5382
5665
|
ChatEmptyState,
|
|
5383
5666
|
{
|
|
5384
5667
|
eyebrow: emptyState?.eyebrow,
|
|
5385
5668
|
title: emptyState?.title,
|
|
5386
5669
|
description: emptyState?.description,
|
|
5387
5670
|
suggestions,
|
|
5671
|
+
className: emptyHero ? "items-center text-center [&>p]:mx-auto" : void 0,
|
|
5388
5672
|
onSelect: (s) => {
|
|
5389
5673
|
const text = s.prompt ?? s.label;
|
|
5390
5674
|
if (!text.trim()) return;
|
|
5391
|
-
void
|
|
5392
|
-
{ text, files: [] },
|
|
5393
|
-
|
|
5394
|
-
|
|
5395
|
-
sessionId,
|
|
5396
|
-
message: text,
|
|
5397
|
-
...modelPayload(model),
|
|
5398
|
-
attachments: []
|
|
5399
|
-
}
|
|
5400
|
-
}
|
|
5401
|
-
);
|
|
5675
|
+
void (async () => {
|
|
5676
|
+
const submitted = await handleSubmit({ text, files: [] }, "suggestion");
|
|
5677
|
+
if (submitted === false) setComposerDraft(text);
|
|
5678
|
+
})();
|
|
5402
5679
|
}
|
|
5403
5680
|
}
|
|
5404
5681
|
),
|
|
@@ -5414,7 +5691,11 @@ ${parsed.args}` : `skill: ${parsed.name}`;
|
|
|
5414
5691
|
const key = `${message.id}-${index}`;
|
|
5415
5692
|
if (!reasoningPart) {
|
|
5416
5693
|
if (!isBlankTextPart(part)) {
|
|
5417
|
-
|
|
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
|
+
}
|
|
5418
5699
|
}
|
|
5419
5700
|
return items;
|
|
5420
5701
|
}
|
|
@@ -5441,6 +5722,12 @@ ${reasoningPart.text}`;
|
|
|
5441
5722
|
acc.push({ kind: "tool-group", tools: [{ part: item.part, key: item.key }], key: item.key });
|
|
5442
5723
|
}
|
|
5443
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
|
+
}
|
|
5444
5731
|
} else {
|
|
5445
5732
|
acc.push(item);
|
|
5446
5733
|
}
|
|
@@ -5601,8 +5888,8 @@ ${reasoningPart.text}`;
|
|
|
5601
5888
|
);
|
|
5602
5889
|
}),
|
|
5603
5890
|
(() => {
|
|
5604
|
-
if (!
|
|
5605
|
-
const friendly =
|
|
5891
|
+
if (!friendlyChatError || isComposerRuntimeNotice(friendlyChatError)) return null;
|
|
5892
|
+
const friendly = friendlyChatError;
|
|
5606
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: [
|
|
5607
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: [
|
|
5608
5895
|
/* @__PURE__ */ jsx25("circle", { cx: "12", cy: "12", r: "10" }),
|
|
@@ -5669,7 +5956,26 @@ ${reasoningPart.text}`;
|
|
|
5669
5956
|
)
|
|
5670
5957
|
}
|
|
5671
5958
|
),
|
|
5672
|
-
|
|
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(
|
|
5673
5979
|
"div",
|
|
5674
5980
|
{
|
|
5675
5981
|
role: "status",
|
|
@@ -5764,7 +6070,7 @@ ${reasoningPart.text}`;
|
|
|
5764
6070
|
PromptInput,
|
|
5765
6071
|
{
|
|
5766
6072
|
"data-boring-state": status,
|
|
5767
|
-
onSubmit: handleSubmit,
|
|
6073
|
+
onSubmit: (message) => handleSubmit(message),
|
|
5768
6074
|
multiple: true,
|
|
5769
6075
|
maxFiles: attachmentsDisabled ? 0 : 20,
|
|
5770
6076
|
maxFileSize: 5 * 1024 * 1024,
|
|
@@ -5786,9 +6092,11 @@ ${reasoningPart.text}`;
|
|
|
5786
6092
|
/* @__PURE__ */ jsx25(
|
|
5787
6093
|
PromptInputTextarea,
|
|
5788
6094
|
{
|
|
5789
|
-
|
|
6095
|
+
defaultValue: promptInputController ? void 0 : initialDraft,
|
|
6096
|
+
placeholder: composerBlocked ? composerBlockerLabel : composerPlaceholder ?? "Ask anything\u2026",
|
|
5790
6097
|
disabled: composerBlocked,
|
|
5791
6098
|
readOnly: composerBlocked,
|
|
6099
|
+
ref: textareaRef,
|
|
5792
6100
|
onChange: handleComposerChange,
|
|
5793
6101
|
onKeyDown: handleComposerKeyDown,
|
|
5794
6102
|
className: cn(
|
|
@@ -6145,7 +6453,7 @@ function getAgentCommands(options = {}) {
|
|
|
6145
6453
|
}
|
|
6146
6454
|
|
|
6147
6455
|
// src/front/hooks/useSessions.ts
|
|
6148
|
-
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";
|
|
6149
6457
|
var API_BASE = "/api/v1/agent/sessions";
|
|
6150
6458
|
var STORAGE_KEY = "boring-agent:activeSessionId";
|
|
6151
6459
|
function readPersistedId(storageKey) {
|
|
@@ -6162,6 +6470,9 @@ function persistId(storageKey, id) {
|
|
|
6162
6470
|
} catch {
|
|
6163
6471
|
}
|
|
6164
6472
|
}
|
|
6473
|
+
function headersScopeKey(headers) {
|
|
6474
|
+
return JSON.stringify(Object.entries(headers ?? {}).sort(([a], [b]) => a.localeCompare(b)));
|
|
6475
|
+
}
|
|
6165
6476
|
function requestInit(headers) {
|
|
6166
6477
|
if (!headers || Object.keys(headers).length === 0) return void 0;
|
|
6167
6478
|
return { headers };
|
|
@@ -6175,21 +6486,46 @@ async function fetchSessions(headers) {
|
|
|
6175
6486
|
function useSessions(opts = {}) {
|
|
6176
6487
|
const storageKey = opts.storageKey ?? STORAGE_KEY;
|
|
6177
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
|
+
);
|
|
6178
6496
|
const [sessions, setSessions] = useState21([]);
|
|
6179
6497
|
const [activeSessionId, setActiveSessionId] = useState21(
|
|
6180
6498
|
() => readPersistedId(storageKey)
|
|
6181
6499
|
);
|
|
6182
6500
|
const [loading, setLoading] = useState21(true);
|
|
6183
6501
|
const [error, setError] = useState21();
|
|
6502
|
+
const [loaded, setLoaded] = useState21(false);
|
|
6184
6503
|
const versionRef = useRef14(0);
|
|
6504
|
+
const loadedScopeRef = useRef14(scopeKey);
|
|
6185
6505
|
const refresh = useCallback17(async () => {
|
|
6186
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);
|
|
6187
6517
|
try {
|
|
6188
6518
|
const data = await fetchSessions(requestHeaders);
|
|
6189
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);
|
|
6190
6525
|
setSessions(data);
|
|
6191
6526
|
setActiveSessionId((prev) => {
|
|
6192
|
-
|
|
6527
|
+
const preferred = replacingLoadedScope ? persisted : prev ?? persisted;
|
|
6528
|
+
if (preferred && data.some((session) => session.id === preferred)) return preferred;
|
|
6193
6529
|
const next = data[0]?.id;
|
|
6194
6530
|
persistId(storageKey, next);
|
|
6195
6531
|
return next;
|
|
@@ -6198,16 +6534,32 @@ function useSessions(opts = {}) {
|
|
|
6198
6534
|
}
|
|
6199
6535
|
} catch (err) {
|
|
6200
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);
|
|
6201
6544
|
setError(err instanceof Error ? err : new Error(String(err)));
|
|
6202
6545
|
setLoading(false);
|
|
6203
6546
|
}
|
|
6204
6547
|
}
|
|
6205
|
-
}, [requestHeaders, storageKey]);
|
|
6548
|
+
}, [enabled, requestHeaders, scopeKey, storageKey]);
|
|
6206
6549
|
useEffect19(() => {
|
|
6550
|
+
if (!enabled) {
|
|
6551
|
+
setSessions([]);
|
|
6552
|
+
setActiveSessionId(void 0);
|
|
6553
|
+
setError(void 0);
|
|
6554
|
+
setLoaded(false);
|
|
6555
|
+
setLoading(false);
|
|
6556
|
+
return;
|
|
6557
|
+
}
|
|
6207
6558
|
void refresh();
|
|
6208
|
-
}, [refresh]);
|
|
6559
|
+
}, [enabled, refresh, refreshKey, scopeKey]);
|
|
6209
6560
|
const create = useCallback17(
|
|
6210
6561
|
async (init) => {
|
|
6562
|
+
if (!enabled) throw new Error("Sessions are disabled");
|
|
6211
6563
|
const res = await fetch(API_BASE, {
|
|
6212
6564
|
method: "POST",
|
|
6213
6565
|
headers: { ...requestHeaders, "Content-Type": "application/json" },
|
|
@@ -6225,7 +6577,7 @@ function useSessions(opts = {}) {
|
|
|
6225
6577
|
void refresh();
|
|
6226
6578
|
return session;
|
|
6227
6579
|
},
|
|
6228
|
-
[refresh, requestHeaders, storageKey]
|
|
6580
|
+
[enabled, refresh, requestHeaders, storageKey]
|
|
6229
6581
|
);
|
|
6230
6582
|
const switchSession = useCallback17((id) => {
|
|
6231
6583
|
setActiveSessionId(id);
|
|
@@ -6233,6 +6585,7 @@ function useSessions(opts = {}) {
|
|
|
6233
6585
|
}, [storageKey]);
|
|
6234
6586
|
const deleteSession = useCallback17(
|
|
6235
6587
|
async (id) => {
|
|
6588
|
+
if (!enabled) throw new Error("Sessions are disabled");
|
|
6236
6589
|
setSessions((prev) => prev.filter((s) => s.id !== id));
|
|
6237
6590
|
setActiveSessionId((prev) => {
|
|
6238
6591
|
if (prev === id) {
|
|
@@ -6256,13 +6609,16 @@ function useSessions(opts = {}) {
|
|
|
6256
6609
|
}
|
|
6257
6610
|
void refresh();
|
|
6258
6611
|
},
|
|
6259
|
-
[refresh, requestHeaders, storageKey]
|
|
6612
|
+
[enabled, refresh, requestHeaders, storageKey]
|
|
6260
6613
|
);
|
|
6614
|
+
const scopeMatches = loadedScopeRef.current === scopeKey;
|
|
6615
|
+
const visibleSessions = enabled && scopeMatches ? sessions : [];
|
|
6616
|
+
const visibleActiveSessionId = enabled && scopeMatches ? activeSessionId : void 0;
|
|
6261
6617
|
return {
|
|
6262
|
-
sessions,
|
|
6263
|
-
activeSession:
|
|
6264
|
-
activeSessionId,
|
|
6265
|
-
loading,
|
|
6618
|
+
sessions: visibleSessions,
|
|
6619
|
+
activeSession: visibleSessions.find((s) => s.id === visibleActiveSessionId),
|
|
6620
|
+
activeSessionId: visibleActiveSessionId,
|
|
6621
|
+
loading: enabled ? !scopeMatches || loading || !loaded : false,
|
|
6266
6622
|
error,
|
|
6267
6623
|
create,
|
|
6268
6624
|
switch: switchSession,
|