@hachej/boring-agent 0.1.30 → 0.1.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{DebugDrawer-MCYZ4AWZ.js → DebugDrawer-PX2PHXJH.js} +1 -1
- package/dist/chunk-IDRC4SFU.js +85 -0
- package/dist/{chunk-MMJA3QON.js → chunk-UTUXVT2K.js} +33 -3
- package/dist/front/index.d.ts +47 -3
- package/dist/front/index.js +736 -304
- package/dist/front/styles.css +40 -23
- package/dist/{harness-CQ0uw6xW.d.ts → harness-CtTHLQyy.d.ts} +1 -1
- package/dist/server/index.d.ts +18 -12
- package/dist/server/index.js +1108 -406
- package/dist/shared/index.d.ts +2 -2
- package/docs/ERROR_CODES.md +38 -2
- package/package.json +5 -3
package/dist/front/index.js
CHANGED
|
@@ -3,13 +3,19 @@ import {
|
|
|
3
3
|
WORKSPACE_AGENT_PLUGINS_RELOADED_EVENT,
|
|
4
4
|
extractToolUiMetadata
|
|
5
5
|
} from "../chunk-B5JECXMG.js";
|
|
6
|
+
import {
|
|
7
|
+
sanitizeUiMessage,
|
|
8
|
+
sanitizeUiMessages,
|
|
9
|
+
uiMessageContentKey
|
|
10
|
+
} from "../chunk-IDRC4SFU.js";
|
|
6
11
|
import {
|
|
7
12
|
ErrorCode
|
|
8
13
|
} from "../chunk-UBWQ5LT4.js";
|
|
9
14
|
import {
|
|
10
15
|
DebugDrawer,
|
|
11
|
-
cn
|
|
12
|
-
|
|
16
|
+
cn,
|
|
17
|
+
copyTextToClipboard
|
|
18
|
+
} from "../chunk-UTUXVT2K.js";
|
|
13
19
|
|
|
14
20
|
// src/front/upload/uploadFile.ts
|
|
15
21
|
function readAsDataUrl(file) {
|
|
@@ -50,7 +56,7 @@ async function uploadFile(file, opts = {}) {
|
|
|
50
56
|
// src/front/ChatPanel.tsx
|
|
51
57
|
import { isToolUIPart as isToolUIPart2 } from "ai";
|
|
52
58
|
import { motion as motion2 } from "motion/react";
|
|
53
|
-
import { lazy, Suspense, useCallback as
|
|
59
|
+
import { lazy, Suspense, useCallback as useCallback17, useEffect as useEffect18, useMemo as useMemo12, useRef as useRef14, useState as useState20 } from "react";
|
|
54
60
|
|
|
55
61
|
// src/front/primitives/mention-picker.tsx
|
|
56
62
|
import { useEffect as useEffect2, useRef as useRef2, useState, useCallback } from "react";
|
|
@@ -256,12 +262,164 @@ function SlashCommandPicker({ query, commands, onSelect, onDismiss }) {
|
|
|
256
262
|
// src/front/hooks/useAgentChat.ts
|
|
257
263
|
import { useChat } from "@ai-sdk/react";
|
|
258
264
|
import { DefaultChatTransport } from "ai";
|
|
259
|
-
import { useEffect as useEffect4, useMemo as useMemo2, useRef as useRef4, useState as useState3 } from "react";
|
|
265
|
+
import { useCallback as useCallback3, useEffect as useEffect4, useMemo as useMemo2, useRef as useRef4, useState as useState3 } from "react";
|
|
266
|
+
function mergeMessages(base, tail, opts) {
|
|
267
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
268
|
+
const seenContent = /* @__PURE__ */ new Set();
|
|
269
|
+
const stableContent = /* @__PURE__ */ new Set();
|
|
270
|
+
const pendingUserContent = /* @__PURE__ */ new Map();
|
|
271
|
+
const merged = [];
|
|
272
|
+
for (const rawMessage of [...base, ...tail]) {
|
|
273
|
+
const message = sanitizeUiMessage(rawMessage);
|
|
274
|
+
const id = typeof message.id === "string" ? message.id : void 0;
|
|
275
|
+
const contentKey = uiMessageContentKey(message);
|
|
276
|
+
const pendingUser = id?.startsWith("pending-user:") === true;
|
|
277
|
+
if (id) {
|
|
278
|
+
if (seenIds.has(id)) continue;
|
|
279
|
+
if (pendingUser && (seenContent.has(contentKey) || opts?.dedupePendingAgainstStable !== false && stableContent.has(contentKey))) continue;
|
|
280
|
+
if (!pendingUser) {
|
|
281
|
+
const pendingIndex = pendingUserContent.get(contentKey);
|
|
282
|
+
if (pendingIndex !== void 0) {
|
|
283
|
+
merged[pendingIndex] = message;
|
|
284
|
+
seenIds.add(id);
|
|
285
|
+
seenContent.add(contentKey);
|
|
286
|
+
pendingUserContent.delete(contentKey);
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
seenIds.add(id);
|
|
291
|
+
if (pendingUser) {
|
|
292
|
+
pendingUserContent.set(contentKey, merged.length);
|
|
293
|
+
} else {
|
|
294
|
+
stableContent.add(contentKey);
|
|
295
|
+
}
|
|
296
|
+
} else {
|
|
297
|
+
if (seenContent.has(contentKey)) continue;
|
|
298
|
+
seenContent.add(contentKey);
|
|
299
|
+
}
|
|
300
|
+
merged.push(message);
|
|
301
|
+
}
|
|
302
|
+
return sanitizeUiMessages(merged);
|
|
303
|
+
}
|
|
304
|
+
function sameMessageOrder(a, b) {
|
|
305
|
+
if (a.length !== b.length) return false;
|
|
306
|
+
return a.every((message, index) => {
|
|
307
|
+
const other = b[index];
|
|
308
|
+
return message.id === other?.id && JSON.stringify(message.parts ?? []) === JSON.stringify(other?.parts ?? []);
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
function sameMessageIdentityOrContent(a, b) {
|
|
312
|
+
if (!a || !b) return false;
|
|
313
|
+
if (typeof a.id === "string" && typeof b.id === "string" && a.id === b.id) return true;
|
|
314
|
+
return uiMessageContentKey(a) === uiMessageContentKey(b);
|
|
315
|
+
}
|
|
316
|
+
function mergeHydratedMessages(serverMessages, cachedMessages) {
|
|
317
|
+
if (serverMessages.length === 0) return mergeMessages([], cachedMessages);
|
|
318
|
+
if (cachedMessages.length === 0) return mergeMessages([], serverMessages);
|
|
319
|
+
let commonPrefix = 0;
|
|
320
|
+
while (commonPrefix < serverMessages.length && commonPrefix < cachedMessages.length && sameMessageIdentityOrContent(serverMessages[commonPrefix], cachedMessages[commonPrefix])) {
|
|
321
|
+
commonPrefix += 1;
|
|
322
|
+
}
|
|
323
|
+
if (commonPrefix > 0 && commonPrefix < cachedMessages.length && commonPrefix < serverMessages.length) {
|
|
324
|
+
return mergeMessages(serverMessages.slice(0, commonPrefix), [
|
|
325
|
+
...cachedMessages.slice(commonPrefix),
|
|
326
|
+
...serverMessages.slice(commonPrefix)
|
|
327
|
+
]);
|
|
328
|
+
}
|
|
329
|
+
const firstCached = cachedMessages[0];
|
|
330
|
+
const serverHasUser = serverMessages.some((message) => message.role === "user");
|
|
331
|
+
if (commonPrefix === 0 && !serverHasUser && firstCached?.role === "user" && typeof firstCached.id === "string" && firstCached.id.startsWith("pending-user:") && serverMessages[0]?.role === "assistant") {
|
|
332
|
+
return mergeMessages([], [...cachedMessages, ...serverMessages]);
|
|
333
|
+
}
|
|
334
|
+
return mergeMessages(serverMessages, cachedMessages);
|
|
335
|
+
}
|
|
336
|
+
function messagesLookSettled(messages) {
|
|
337
|
+
const last = messages[messages.length - 1];
|
|
338
|
+
if (last?.role !== "assistant") return false;
|
|
339
|
+
return !last.parts?.some((part) => {
|
|
340
|
+
const candidate = part;
|
|
341
|
+
return typeof candidate.type === "string" && candidate.type.startsWith("tool-") && candidate.state !== "output-available" && candidate.state !== "output-error" && candidate.state !== "output-denied" && candidate.state !== "approval-responded";
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
function readCachedMessages(cacheKey) {
|
|
345
|
+
try {
|
|
346
|
+
const cached = globalThis.localStorage?.getItem(cacheKey);
|
|
347
|
+
if (!cached) return [];
|
|
348
|
+
const parsed = JSON.parse(cached);
|
|
349
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
350
|
+
} catch {
|
|
351
|
+
return [];
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
function messagesNeedResume(cachedMessages) {
|
|
355
|
+
const last = cachedMessages[cachedMessages.length - 1];
|
|
356
|
+
if (!last) return false;
|
|
357
|
+
if (last.role === "user") return true;
|
|
358
|
+
return Boolean(last.parts?.some((part) => {
|
|
359
|
+
const candidate = part;
|
|
360
|
+
if (typeof candidate.type === "string" && candidate.type.startsWith("tool-")) {
|
|
361
|
+
return candidate.state !== "output-available" && candidate.state !== "output-error" && candidate.state !== "output-denied" && candidate.state !== "approval-responded";
|
|
362
|
+
}
|
|
363
|
+
return false;
|
|
364
|
+
}));
|
|
365
|
+
}
|
|
366
|
+
function cachedMessagesNeedResume(cacheKey) {
|
|
367
|
+
return messagesNeedResume(readCachedMessages(cacheKey));
|
|
368
|
+
}
|
|
369
|
+
function cachedStatusNeedsResume(statusKey) {
|
|
370
|
+
try {
|
|
371
|
+
return globalThis.localStorage?.getItem(statusKey) === "active";
|
|
372
|
+
} catch {
|
|
373
|
+
return false;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
function createClientTurnId() {
|
|
377
|
+
return `turn:${Date.now()}:${Math.random().toString(36).slice(2)}`;
|
|
378
|
+
}
|
|
379
|
+
function turnIdFromDataPart(part) {
|
|
380
|
+
const candidate = part;
|
|
381
|
+
if (candidate?.type !== "data-turn-start") return null;
|
|
382
|
+
return typeof candidate.data?.turnId === "string" && candidate.data.turnId.length > 0 ? candidate.data.turnId : null;
|
|
383
|
+
}
|
|
384
|
+
function optimisticUserMessageFromSendArgs(args) {
|
|
385
|
+
const input = args[0];
|
|
386
|
+
const text = typeof input?.text === "string" ? input.text : "";
|
|
387
|
+
if (!text.trim()) return null;
|
|
388
|
+
return {
|
|
389
|
+
id: `pending-user:${Date.now()}:${Math.random().toString(36).slice(2)}`,
|
|
390
|
+
role: "user",
|
|
391
|
+
parts: [{ type: "text", text }]
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
function writeCachedMessages(cacheKey, messages) {
|
|
395
|
+
try {
|
|
396
|
+
globalThis.localStorage?.setItem(cacheKey, JSON.stringify(messages));
|
|
397
|
+
} catch {
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
function clearCachedMessages(cacheKey) {
|
|
401
|
+
try {
|
|
402
|
+
globalThis.localStorage?.removeItem?.(cacheKey);
|
|
403
|
+
} catch {
|
|
404
|
+
}
|
|
405
|
+
}
|
|
260
406
|
function useAgentChat(opts) {
|
|
261
407
|
const { sessionId } = opts;
|
|
262
408
|
const hydrateMessages = opts.hydrateMessages ?? true;
|
|
263
409
|
const optsRef = useRef4(opts);
|
|
264
410
|
optsRef.current = opts;
|
|
411
|
+
const cacheKey = sessionId ? `boring-agent:messages:${sessionId}` : null;
|
|
412
|
+
const statusKey = sessionId ? `boring-agent:status:${sessionId}` : null;
|
|
413
|
+
const [settledResumeKey, setSettledResumeKey] = useState3(null);
|
|
414
|
+
const [localTurnActive, setLocalTurnActive] = useState3(false);
|
|
415
|
+
const localTurnVersionRef = useRef4(0);
|
|
416
|
+
const shouldResume = useMemo2(
|
|
417
|
+
() => hydrateMessages && Boolean(
|
|
418
|
+
cacheKey && !localTurnActive && settledResumeKey !== cacheKey && (cachedMessagesNeedResume(cacheKey) || (statusKey ? cachedStatusNeedsResume(statusKey) : false))
|
|
419
|
+
),
|
|
420
|
+
[cacheKey, hydrateMessages, localTurnActive, settledResumeKey, statusKey]
|
|
421
|
+
);
|
|
422
|
+
const activeTurnIdRef = useRef4(null);
|
|
265
423
|
const transport = useMemo2(
|
|
266
424
|
() => new DefaultChatTransport({
|
|
267
425
|
api: "/api/v1/agent/chat",
|
|
@@ -269,7 +427,8 @@ function useAgentChat(opts) {
|
|
|
269
427
|
body: () => ({
|
|
270
428
|
sessionId: optsRef.current.sessionId,
|
|
271
429
|
model: optsRef.current.model,
|
|
272
|
-
thinkingLevel: optsRef.current.thinkingLevel
|
|
430
|
+
thinkingLevel: optsRef.current.thinkingLevel,
|
|
431
|
+
...activeTurnIdRef.current ? { clientTurnId: activeTurnIdRef.current } : {}
|
|
273
432
|
})
|
|
274
433
|
}),
|
|
275
434
|
[sessionId]
|
|
@@ -277,75 +436,166 @@ function useAgentChat(opts) {
|
|
|
277
436
|
const chat = useChat({
|
|
278
437
|
id: sessionId,
|
|
279
438
|
transport,
|
|
280
|
-
resume:
|
|
439
|
+
resume: shouldResume,
|
|
281
440
|
// Match AI SDK's documented React smoothing knob: render at most every
|
|
282
441
|
// ~50ms while chunks stream instead of once per incoming chunk. This only
|
|
283
442
|
// throttles AI SDK's own messages store; pi's custom data-pi projection
|
|
284
443
|
// does its own matching delta batching in usePiChatProjection.
|
|
285
444
|
experimental_throttle: 50,
|
|
286
445
|
onData: (part) => {
|
|
446
|
+
const turnId = turnIdFromDataPart(part);
|
|
447
|
+
if (turnId) activeTurnIdRef.current = turnId;
|
|
287
448
|
optsRef.current.onData?.(part);
|
|
288
449
|
}
|
|
289
450
|
});
|
|
451
|
+
const rawMessages = chat.messages;
|
|
452
|
+
const messages = useMemo2(() => mergeMessages([], rawMessages), [rawMessages]);
|
|
453
|
+
const messagesRef = useRef4(messages);
|
|
454
|
+
messagesRef.current = messages;
|
|
455
|
+
const rawStop = chat.stop;
|
|
456
|
+
const stop = useCallback3(() => {
|
|
457
|
+
rawStop();
|
|
458
|
+
if (!sessionId) return;
|
|
459
|
+
const turnId = activeTurnIdRef.current;
|
|
460
|
+
const suffix = turnId ? `?turnId=${encodeURIComponent(turnId)}` : "";
|
|
461
|
+
fetch(`/api/v1/agent/chat/${encodeURIComponent(sessionId)}/turn${suffix}`, {
|
|
462
|
+
method: "DELETE",
|
|
463
|
+
headers: optsRef.current.requestHeaders
|
|
464
|
+
}).catch(() => {
|
|
465
|
+
});
|
|
466
|
+
}, [rawStop, sessionId]);
|
|
467
|
+
useEffect4(() => {
|
|
468
|
+
setLocalTurnActive(false);
|
|
469
|
+
}, [sessionId]);
|
|
470
|
+
const sendMessage = useCallback3((...args) => {
|
|
471
|
+
activeTurnIdRef.current = createClientTurnId();
|
|
472
|
+
localTurnVersionRef.current += 1;
|
|
473
|
+
setLocalTurnActive(true);
|
|
474
|
+
setSettledResumeKey((current) => current === cacheKey ? null : current);
|
|
475
|
+
if (cacheKey) {
|
|
476
|
+
const optimisticUser = optimisticUserMessageFromSendArgs(args);
|
|
477
|
+
if (optimisticUser) {
|
|
478
|
+
const cachedMessages = readCachedMessages(cacheKey);
|
|
479
|
+
const base = messagesRef.current.length > 0 ? messagesRef.current : cachedMessages;
|
|
480
|
+
const next = mergeMessages(base, [optimisticUser], { dedupePendingAgainstStable: false });
|
|
481
|
+
writeCachedMessages(cacheKey, next);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
if (statusKey) {
|
|
485
|
+
try {
|
|
486
|
+
globalThis.localStorage?.setItem(statusKey, "active");
|
|
487
|
+
} catch {
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
return chat.sendMessage(...args);
|
|
491
|
+
}, [cacheKey, chat.sendMessage, statusKey]);
|
|
290
492
|
const setMessages = chat.setMessages;
|
|
291
|
-
const
|
|
292
|
-
const
|
|
493
|
+
const [hydratedKey, setHydratedKey] = useState3(null);
|
|
494
|
+
const hydrated = !hydrateMessages || !sessionId || !cacheKey || hydratedKey === cacheKey;
|
|
293
495
|
useEffect4(() => {
|
|
294
496
|
if (!sessionId || !cacheKey) return;
|
|
295
497
|
if (!hydrateMessages) {
|
|
296
|
-
|
|
498
|
+
setHydratedKey(cacheKey);
|
|
297
499
|
return;
|
|
298
500
|
}
|
|
299
501
|
let aborted = false;
|
|
300
|
-
|
|
301
|
-
const
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
502
|
+
setHydratedKey(null);
|
|
503
|
+
const hydrateLocalTurnVersion = localTurnVersionRef.current;
|
|
504
|
+
const hydrateMerged = (serverMessages, cachedMessages) => {
|
|
505
|
+
const current = messagesRef.current;
|
|
506
|
+
const fromServerAndCache = mergeHydratedMessages(serverMessages, cachedMessages);
|
|
507
|
+
const next = current.length > 0 ? mergeMessages(fromServerAndCache, current) : fromServerAndCache;
|
|
508
|
+
if (next.length > 0) {
|
|
509
|
+
setMessages(next);
|
|
510
|
+
const serverLatest = serverMessages[serverMessages.length - 1];
|
|
511
|
+
const cachedLatest = cachedMessages[cachedMessages.length - 1];
|
|
512
|
+
const serverCoversCachedTail = !messagesNeedResume(cachedMessages) || serverLatest && cachedLatest && sameMessageIdentityOrContent(serverLatest, cachedLatest) || serverMessages.length >= cachedMessages.length;
|
|
513
|
+
const authoritativeSettledServerTail = Boolean(
|
|
514
|
+
serverLatest && serverCoversCachedTail && messagesLookSettled(serverMessages)
|
|
515
|
+
);
|
|
516
|
+
if (authoritativeSettledServerTail) {
|
|
517
|
+
setSettledResumeKey(cacheKey);
|
|
518
|
+
if (localTurnVersionRef.current === hydrateLocalTurnVersion) rawStop();
|
|
519
|
+
clearCachedMessages(cacheKey);
|
|
520
|
+
if (statusKey) {
|
|
521
|
+
try {
|
|
522
|
+
globalThis.localStorage?.setItem(statusKey, "ready");
|
|
523
|
+
} catch {
|
|
524
|
+
}
|
|
525
|
+
}
|
|
308
526
|
}
|
|
309
|
-
} catch {
|
|
310
527
|
}
|
|
311
528
|
};
|
|
529
|
+
const loadFromCache = () => {
|
|
530
|
+
const cachedMessages = readCachedMessages(cacheKey);
|
|
531
|
+
if (cachedMessages.length > 0) hydrateMerged([], cachedMessages);
|
|
532
|
+
};
|
|
312
533
|
const fetchOpts = optsRef.current.requestHeaders ? { headers: optsRef.current.requestHeaders } : void 0;
|
|
313
534
|
const messagesUrl = `/api/v1/agent/chat/${encodeURIComponent(sessionId)}/messages`;
|
|
314
535
|
const request = fetchOpts ? fetch(messagesUrl, fetchOpts) : fetch(messagesUrl);
|
|
315
536
|
request.then((res) => res.ok ? res.json() : null).then((payload) => {
|
|
316
537
|
if (aborted) return;
|
|
317
|
-
const localTurnStarted = statusRef.current === "submitted" || statusRef.current === "streaming" || messagesRef.current.length > 0;
|
|
318
538
|
const serverMessages = payload?.messages;
|
|
539
|
+
const cachedMessages = readCachedMessages(cacheKey);
|
|
319
540
|
if (Array.isArray(serverMessages) && serverMessages.length > 0) {
|
|
320
|
-
|
|
541
|
+
hydrateMerged(serverMessages, cachedMessages);
|
|
321
542
|
return;
|
|
322
543
|
}
|
|
323
|
-
if (
|
|
544
|
+
if (cachedMessages.length > 0) hydrateMerged([], cachedMessages);
|
|
324
545
|
}).catch(() => {
|
|
325
546
|
if (aborted) return;
|
|
326
|
-
|
|
327
|
-
if (!localTurnStarted) loadFromCache();
|
|
547
|
+
loadFromCache();
|
|
328
548
|
}).finally(() => {
|
|
329
|
-
if (!aborted)
|
|
549
|
+
if (!aborted) setHydratedKey(cacheKey);
|
|
330
550
|
});
|
|
331
551
|
return () => {
|
|
332
552
|
aborted = true;
|
|
333
553
|
};
|
|
334
|
-
}, [hydrateMessages, sessionId, cacheKey, setMessages]);
|
|
335
|
-
const
|
|
336
|
-
const
|
|
337
|
-
const
|
|
338
|
-
const
|
|
339
|
-
|
|
340
|
-
|
|
554
|
+
}, [hydrateMessages, sessionId, cacheKey, setMessages, statusKey, rawStop]);
|
|
555
|
+
const rawStatus = chat.status;
|
|
556
|
+
const rawStatusActive = rawStatus === "submitted" || rawStatus === "streaming";
|
|
557
|
+
const knownActiveTurn = localTurnActive || shouldResume;
|
|
558
|
+
const hydratingWithoutMessages = hydrateMessages && Boolean(sessionId) && !hydrated && messages.length === 0;
|
|
559
|
+
const status = hydratingWithoutMessages ? "ready" : rawStatusActive && !knownActiveTurn ? "ready" : knownActiveTurn && rawStatus === "ready" ? "submitted" : rawStatus;
|
|
560
|
+
const prevRawStatusRef = useRef4(rawStatus);
|
|
341
561
|
useEffect4(() => {
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
562
|
+
const prev = prevRawStatusRef.current;
|
|
563
|
+
prevRawStatusRef.current = rawStatus;
|
|
564
|
+
const prevActive = prev === "submitted" || prev === "streaming";
|
|
565
|
+
if (prevActive && (rawStatus === "ready" || rawStatus === "error")) {
|
|
566
|
+
setLocalTurnActive(false);
|
|
567
|
+
activeTurnIdRef.current = null;
|
|
568
|
+
if (cacheKey) setSettledResumeKey(cacheKey);
|
|
569
|
+
if (statusKey) {
|
|
570
|
+
try {
|
|
571
|
+
globalThis.localStorage?.setItem(statusKey, "ready");
|
|
572
|
+
} catch {
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
if (cacheKey && shouldResume) clearCachedMessages(cacheKey);
|
|
576
|
+
}
|
|
577
|
+
}, [rawStatus, sessionId, cacheKey, statusKey, shouldResume]);
|
|
578
|
+
useEffect4(() => {
|
|
579
|
+
if (!statusKey) return;
|
|
345
580
|
try {
|
|
346
|
-
|
|
581
|
+
if (knownActiveTurn) {
|
|
582
|
+
globalThis.localStorage?.setItem(statusKey, "active");
|
|
583
|
+
} else if (rawStatus === "ready" || rawStatus === "error" || rawStatusActive) {
|
|
584
|
+
globalThis.localStorage?.setItem(statusKey, "ready");
|
|
585
|
+
}
|
|
347
586
|
} catch {
|
|
348
587
|
}
|
|
588
|
+
}, [knownActiveTurn, rawStatus, rawStatusActive, statusKey]);
|
|
589
|
+
useEffect4(() => {
|
|
590
|
+
if (messages.length === 0) return;
|
|
591
|
+
const deduped = mergeMessages([], rawMessages);
|
|
592
|
+
if (!sameMessageOrder(rawMessages, deduped)) setMessages(deduped);
|
|
593
|
+
}, [rawMessages, messages.length, setMessages]);
|
|
594
|
+
useEffect4(() => {
|
|
595
|
+
if (opts.persistMessages === false) return;
|
|
596
|
+
if (!hydrated || !cacheKey) return;
|
|
597
|
+
if (messages.length === 0) return;
|
|
598
|
+
writeCachedMessages(cacheKey, mergeMessages([], messages));
|
|
349
599
|
}, [opts.persistMessages, hydrated, cacheKey, messages]);
|
|
350
600
|
const SETTLED_STATES = /* @__PURE__ */ new Set([
|
|
351
601
|
"output-available",
|
|
@@ -388,7 +638,8 @@ function useAgentChat(opts) {
|
|
|
388
638
|
if (prev !== "streaming" && prev !== "submitted") return;
|
|
389
639
|
if (!sessionId || messages.length === 0) return;
|
|
390
640
|
const url = `/api/v1/agent/chat/${encodeURIComponent(sessionId)}/messages`;
|
|
391
|
-
const
|
|
641
|
+
const persistedMessages = mergeMessages([], messages);
|
|
642
|
+
const stripped = persistedMessages.map((msg) => ({
|
|
392
643
|
...msg,
|
|
393
644
|
parts: msg.parts?.map((part) => {
|
|
394
645
|
const p = part;
|
|
@@ -408,11 +659,11 @@ function useAgentChat(opts) {
|
|
|
408
659
|
}).catch(() => {
|
|
409
660
|
});
|
|
410
661
|
}, [opts.persistMessages, sessionId, status, messages]);
|
|
411
|
-
return chat;
|
|
662
|
+
return { ...chat, messages, sendMessage, stop, status, hydrated, hydratingMessages: hydrateMessages && Boolean(sessionId) && !hydrated };
|
|
412
663
|
}
|
|
413
664
|
|
|
414
665
|
// src/front/pi/piChatProjection.ts
|
|
415
|
-
import { useCallback as
|
|
666
|
+
import { useCallback as useCallback4, useEffect as useEffect5, useRef as useRef5, useState as useState4 } from "react";
|
|
416
667
|
function asPiDataPart(part) {
|
|
417
668
|
const typed = part;
|
|
418
669
|
if (typeof typed.type !== "string" || !typed.type.startsWith("data-pi-")) return null;
|
|
@@ -584,12 +835,12 @@ function usePiChatProjection({
|
|
|
584
835
|
const piMessagesRef = useRef5([]);
|
|
585
836
|
const bufferedDeltaRef = useRef5(/* @__PURE__ */ new Map());
|
|
586
837
|
const flushTimerRef = useRef5(null);
|
|
587
|
-
const updatePiMessages =
|
|
838
|
+
const updatePiMessages = useCallback4((updater) => {
|
|
588
839
|
const next = updater(piMessagesRef.current);
|
|
589
840
|
piMessagesRef.current = next;
|
|
590
841
|
setPiMessages(next);
|
|
591
842
|
}, []);
|
|
592
|
-
const flushBufferedDeltas =
|
|
843
|
+
const flushBufferedDeltas = useCallback4(() => {
|
|
593
844
|
if (flushTimerRef.current) {
|
|
594
845
|
clearTimeout(flushTimerRef.current);
|
|
595
846
|
flushTimerRef.current = null;
|
|
@@ -599,7 +850,7 @@ function usePiChatProjection({
|
|
|
599
850
|
bufferedDeltaRef.current.clear();
|
|
600
851
|
updatePiMessages((items) => applyBufferedDeltas(items, deltas));
|
|
601
852
|
}, [updatePiMessages]);
|
|
602
|
-
const queueBufferedDelta =
|
|
853
|
+
const queueBufferedDelta = useCallback4((entry) => {
|
|
603
854
|
const key = `${entry.kind}:${entry.messageId}:${entry.partId}`;
|
|
604
855
|
const existing = bufferedDeltaRef.current.get(key);
|
|
605
856
|
bufferedDeltaRef.current.set(key, existing ? { ...existing, delta: `${existing.delta}${entry.delta}` } : entry);
|
|
@@ -624,7 +875,7 @@ function usePiChatProjection({
|
|
|
624
875
|
piMessagesRef.current = [];
|
|
625
876
|
setPiMessages([]);
|
|
626
877
|
}, [sessionId]);
|
|
627
|
-
const handleData =
|
|
878
|
+
const handleData = useCallback4((part) => {
|
|
628
879
|
const typed = part;
|
|
629
880
|
const data = typed.data ?? {};
|
|
630
881
|
const piMessageId = typeof data.messageId === "string" ? data.messageId : void 0;
|
|
@@ -774,11 +1025,18 @@ function usePiChatProjection({
|
|
|
774
1025
|
}
|
|
775
1026
|
|
|
776
1027
|
// src/front/pi/piNativeFollowUpQueue.ts
|
|
777
|
-
import { useCallback as
|
|
1028
|
+
import { useCallback as useCallback5, useEffect as useEffect6, useMemo as useMemo3, useRef as useRef6, useState as useState5 } from "react";
|
|
778
1029
|
var STORAGE_FOLLOWUP_SEQ_PREFIX = "boring-agent:followup-seq:";
|
|
1030
|
+
var STORAGE_FOLLOWUP_QUEUE_PREFIX = "boring-agent:followup-queue:";
|
|
779
1031
|
var MAX_FOLLOWUP_POST_ATTEMPTS = 5;
|
|
780
|
-
function
|
|
781
|
-
|
|
1032
|
+
function workspaceScopeFromHeaders(headers) {
|
|
1033
|
+
return headers?.["x-boring-workspace-id"] ?? headers?.["X-Boring-Workspace-Id"] ?? "global";
|
|
1034
|
+
}
|
|
1035
|
+
function scopedStorageKey(prefix, storageScope, sessionId) {
|
|
1036
|
+
return `${prefix}${encodeURIComponent(storageScope)}:${encodeURIComponent(sessionId)}`;
|
|
1037
|
+
}
|
|
1038
|
+
function nextStoredFollowUpSeq(storageScope, sessionId) {
|
|
1039
|
+
const key = scopedStorageKey(STORAGE_FOLLOWUP_SEQ_PREFIX, storageScope, sessionId);
|
|
782
1040
|
try {
|
|
783
1041
|
const current = Number(globalThis.localStorage?.getItem(key) ?? "0");
|
|
784
1042
|
const next = Number.isFinite(current) ? current + 1 : 1;
|
|
@@ -788,16 +1046,57 @@ function nextStoredFollowUpSeq(sessionId) {
|
|
|
788
1046
|
return Date.now();
|
|
789
1047
|
}
|
|
790
1048
|
}
|
|
1049
|
+
function followUpQueueStorageKey(storageScope, sessionId) {
|
|
1050
|
+
return scopedStorageKey(STORAGE_FOLLOWUP_QUEUE_PREFIX, storageScope, sessionId);
|
|
1051
|
+
}
|
|
1052
|
+
function isPendingFollowUp(value, sessionId) {
|
|
1053
|
+
const item = value;
|
|
1054
|
+
return Boolean(
|
|
1055
|
+
item && item.sessionId === sessionId && typeof item.id === "string" && typeof item.text === "string" && typeof item.serverMessage === "string" && typeof item.clientNonce === "string" && typeof item.clientSeq === "number" && typeof item.postAttempts === "number" && Array.isArray(item.files) && Array.isArray(item.attachments) && typeof item.posted === "boolean" && typeof item.consumed === "boolean"
|
|
1056
|
+
);
|
|
1057
|
+
}
|
|
1058
|
+
function pendingToProjected(item) {
|
|
1059
|
+
return {
|
|
1060
|
+
id: item.id,
|
|
1061
|
+
role: "user",
|
|
1062
|
+
text: item.text,
|
|
1063
|
+
files: item.files,
|
|
1064
|
+
status: item.consumed ? "done" : "queued"
|
|
1065
|
+
};
|
|
1066
|
+
}
|
|
1067
|
+
function readStoredFollowUps(storageScope, sessionId) {
|
|
1068
|
+
try {
|
|
1069
|
+
const raw = globalThis.localStorage?.getItem(followUpQueueStorageKey(storageScope, sessionId));
|
|
1070
|
+
if (!raw) return [];
|
|
1071
|
+
const parsed = JSON.parse(raw);
|
|
1072
|
+
if (!Array.isArray(parsed)) return [];
|
|
1073
|
+
return parsed.filter((item) => isPendingFollowUp(item, sessionId));
|
|
1074
|
+
} catch {
|
|
1075
|
+
return [];
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
function writeStoredFollowUps(storageScope, sessionId, items) {
|
|
1079
|
+
try {
|
|
1080
|
+
const scoped = items.filter((item) => item.sessionId === sessionId && !item.consumed);
|
|
1081
|
+
const storage = globalThis.localStorage;
|
|
1082
|
+
if (!storage) return;
|
|
1083
|
+
const key = followUpQueueStorageKey(storageScope, sessionId);
|
|
1084
|
+
if (scoped.length === 0) storage.removeItem(key);
|
|
1085
|
+
else storage.setItem(key, JSON.stringify(scoped));
|
|
1086
|
+
} catch {
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
791
1089
|
function usePiNativeFollowUpQueue({
|
|
792
1090
|
sessionId,
|
|
793
1091
|
status,
|
|
794
1092
|
requestHeaders,
|
|
795
1093
|
stop
|
|
796
1094
|
}) {
|
|
797
|
-
const
|
|
798
|
-
const
|
|
799
|
-
const
|
|
800
|
-
const
|
|
1095
|
+
const storageScope = workspaceScopeFromHeaders(requestHeaders);
|
|
1096
|
+
const [pendingMessages, setPendingMessages] = useState5(() => readStoredFollowUps(storageScope, sessionId));
|
|
1097
|
+
const pendingMessagesRef = useRef6(pendingMessages);
|
|
1098
|
+
const [projectedFollowUps, setProjectedFollowUps] = useState5(() => pendingMessages.map(pendingToProjected));
|
|
1099
|
+
const projectedFollowUpsRef = useRef6(projectedFollowUps);
|
|
801
1100
|
const activeProjectedAssistantRef = useRef6(null);
|
|
802
1101
|
const lastConsumedProjectedUserRef = useRef6(null);
|
|
803
1102
|
const followUpPostTimerRef = useRef6(null);
|
|
@@ -805,34 +1104,50 @@ function usePiNativeFollowUpQueue({
|
|
|
805
1104
|
const postPendingFollowUpsRef = useRef6(() => {
|
|
806
1105
|
});
|
|
807
1106
|
const consumedFollowUpRef = useRef6(null);
|
|
808
|
-
const
|
|
1107
|
+
const activeStorageRef = useRef6({ storageScope, sessionId });
|
|
1108
|
+
activeStorageRef.current = { storageScope, sessionId };
|
|
1109
|
+
const updatePendingMessages = useCallback5((updater) => {
|
|
809
1110
|
const next = updater(pendingMessagesRef.current);
|
|
810
1111
|
pendingMessagesRef.current = next;
|
|
1112
|
+
writeStoredFollowUps(storageScope, sessionId, next);
|
|
811
1113
|
setPendingMessages(next);
|
|
812
|
-
}, []);
|
|
813
|
-
const updateProjectedFollowUps =
|
|
1114
|
+
}, [storageScope, sessionId]);
|
|
1115
|
+
const updateProjectedFollowUps = useCallback5((updater) => {
|
|
814
1116
|
const next = updater(projectedFollowUpsRef.current);
|
|
815
1117
|
projectedFollowUpsRef.current = next;
|
|
816
1118
|
setProjectedFollowUps(next);
|
|
817
1119
|
}, []);
|
|
818
|
-
const
|
|
1120
|
+
const clearRuntimeState = useCallback5(() => {
|
|
819
1121
|
consumedFollowUpRef.current = null;
|
|
820
|
-
pendingMessagesRef.current = [];
|
|
821
|
-
projectedFollowUpsRef.current = [];
|
|
822
1122
|
activeProjectedAssistantRef.current = null;
|
|
823
1123
|
lastConsumedProjectedUserRef.current = null;
|
|
824
1124
|
if (followUpPostTimerRef.current) clearTimeout(followUpPostTimerRef.current);
|
|
825
1125
|
followUpPostTimerRef.current = null;
|
|
826
1126
|
followUpPostInFlightRef.current = false;
|
|
1127
|
+
}, []);
|
|
1128
|
+
const loadLocalForScope = useCallback5((nextStorageScope, nextSessionId) => {
|
|
1129
|
+
clearRuntimeState();
|
|
1130
|
+
const restored = readStoredFollowUps(nextStorageScope, nextSessionId);
|
|
1131
|
+
const projected = restored.map(pendingToProjected);
|
|
1132
|
+
pendingMessagesRef.current = restored;
|
|
1133
|
+
projectedFollowUpsRef.current = projected;
|
|
1134
|
+
setPendingMessages(restored);
|
|
1135
|
+
setProjectedFollowUps(projected);
|
|
1136
|
+
}, [clearRuntimeState]);
|
|
1137
|
+
const clearLocal = useCallback5(() => {
|
|
1138
|
+
clearRuntimeState();
|
|
1139
|
+
pendingMessagesRef.current = [];
|
|
1140
|
+
projectedFollowUpsRef.current = [];
|
|
1141
|
+
writeStoredFollowUps(storageScope, sessionId, []);
|
|
827
1142
|
setPendingMessages([]);
|
|
828
1143
|
setProjectedFollowUps([]);
|
|
829
|
-
}, []);
|
|
830
|
-
const
|
|
1144
|
+
}, [clearRuntimeState, storageScope, sessionId]);
|
|
1145
|
+
const previousStorageRef = useRef6({ storageScope, sessionId });
|
|
831
1146
|
useEffect6(() => {
|
|
832
|
-
if (
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
}, [sessionId,
|
|
1147
|
+
if (previousStorageRef.current.storageScope === storageScope && previousStorageRef.current.sessionId === sessionId) return;
|
|
1148
|
+
previousStorageRef.current = { storageScope, sessionId };
|
|
1149
|
+
loadLocalForScope(storageScope, sessionId);
|
|
1150
|
+
}, [storageScope, sessionId, loadLocalForScope]);
|
|
836
1151
|
useEffect6(() => {
|
|
837
1152
|
return () => {
|
|
838
1153
|
if (followUpPostTimerRef.current) {
|
|
@@ -841,18 +1156,22 @@ function usePiNativeFollowUpQueue({
|
|
|
841
1156
|
}
|
|
842
1157
|
};
|
|
843
1158
|
}, []);
|
|
844
|
-
const postPendingFollowUps =
|
|
1159
|
+
const postPendingFollowUps = useCallback5(() => {
|
|
845
1160
|
if (followUpPostInFlightRef.current) return;
|
|
1161
|
+
const postStorageScope = storageScope;
|
|
1162
|
+
const postSessionId = sessionId;
|
|
1163
|
+
const isActiveScope = () => activeStorageRef.current.storageScope === postStorageScope && activeStorageRef.current.sessionId === postSessionId;
|
|
1164
|
+
if (!isActiveScope()) return;
|
|
846
1165
|
followUpPostInFlightRef.current = true;
|
|
847
1166
|
void (async () => {
|
|
848
1167
|
let shouldContinue = true;
|
|
849
1168
|
try {
|
|
850
1169
|
while (true) {
|
|
851
|
-
|
|
1170
|
+
if (!isActiveScope()) return;
|
|
1171
|
+
const pending = pendingMessagesRef.current.find((item) => item.sessionId === postSessionId && !item.posted);
|
|
852
1172
|
if (!pending) return;
|
|
853
|
-
updatePendingMessages((items) => items.map((item) => item.id === pending.id ? { ...item, posted: true } : item));
|
|
854
1173
|
try {
|
|
855
|
-
const res = await fetch(`/api/v1/agent/chat/${encodeURIComponent(
|
|
1174
|
+
const res = await fetch(`/api/v1/agent/chat/${encodeURIComponent(postSessionId)}/followup`, {
|
|
856
1175
|
method: "POST",
|
|
857
1176
|
headers: {
|
|
858
1177
|
"Content-Type": "application/json",
|
|
@@ -867,8 +1186,11 @@ function usePiNativeFollowUpQueue({
|
|
|
867
1186
|
})
|
|
868
1187
|
});
|
|
869
1188
|
if (!res.ok) throw new Error(`follow-up rejected: ${res.status}`);
|
|
1189
|
+
if (!isActiveScope()) return;
|
|
1190
|
+
updatePendingMessages((items) => items.map((item) => item.id === pending.id ? { ...item, posted: true } : item));
|
|
870
1191
|
} catch {
|
|
871
1192
|
shouldContinue = false;
|
|
1193
|
+
if (!isActiveScope()) return;
|
|
872
1194
|
const nextAttempts = pending.postAttempts + 1;
|
|
873
1195
|
updatePendingMessages((items) => items.map((item) => item.id === pending.id ? { ...item, posted: false, postAttempts: nextAttempts } : item));
|
|
874
1196
|
if (nextAttempts < MAX_FOLLOWUP_POST_ATTEMPTS) {
|
|
@@ -882,15 +1204,17 @@ function usePiNativeFollowUpQueue({
|
|
|
882
1204
|
}
|
|
883
1205
|
}
|
|
884
1206
|
} finally {
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
1207
|
+
if (isActiveScope()) {
|
|
1208
|
+
followUpPostInFlightRef.current = false;
|
|
1209
|
+
if (shouldContinue && pendingMessagesRef.current.some((item) => item.sessionId === postSessionId && !item.posted)) {
|
|
1210
|
+
postPendingFollowUps();
|
|
1211
|
+
}
|
|
888
1212
|
}
|
|
889
1213
|
}
|
|
890
1214
|
})();
|
|
891
|
-
}, [sessionId, requestHeaders, updatePendingMessages]);
|
|
1215
|
+
}, [storageScope, sessionId, requestHeaders, updatePendingMessages]);
|
|
892
1216
|
postPendingFollowUpsRef.current = postPendingFollowUps;
|
|
893
|
-
const queueFollowUp =
|
|
1217
|
+
const queueFollowUp = useCallback5((input) => {
|
|
894
1218
|
const nextPending = {
|
|
895
1219
|
id: globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random()}`,
|
|
896
1220
|
sessionId,
|
|
@@ -901,7 +1225,7 @@ function usePiNativeFollowUpQueue({
|
|
|
901
1225
|
posted: false,
|
|
902
1226
|
consumed: false,
|
|
903
1227
|
clientNonce: globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random()}-nonce`,
|
|
904
|
-
clientSeq: nextStoredFollowUpSeq(sessionId),
|
|
1228
|
+
clientSeq: nextStoredFollowUpSeq(storageScope, sessionId),
|
|
905
1229
|
postAttempts: 0
|
|
906
1230
|
};
|
|
907
1231
|
updatePendingMessages((items) => [...items, nextPending]);
|
|
@@ -920,8 +1244,8 @@ function usePiNativeFollowUpQueue({
|
|
|
920
1244
|
postPendingFollowUps();
|
|
921
1245
|
}, 1e3);
|
|
922
1246
|
}
|
|
923
|
-
}, [sessionId, status, postPendingFollowUps, updatePendingMessages, updateProjectedFollowUps]);
|
|
924
|
-
const handleData =
|
|
1247
|
+
}, [storageScope, sessionId, status, postPendingFollowUps, updatePendingMessages, updateProjectedFollowUps]);
|
|
1248
|
+
const handleData = useCallback5((part) => {
|
|
925
1249
|
const typed = part;
|
|
926
1250
|
if (typed.type === "data-followup-consumed") {
|
|
927
1251
|
const serverText = typeof typed.data?.text === "string" ? typed.data.text : "";
|
|
@@ -984,7 +1308,7 @@ function usePiNativeFollowUpQueue({
|
|
|
984
1308
|
lastConsumedProjectedUserRef.current = null;
|
|
985
1309
|
setProjectedFollowUps([]);
|
|
986
1310
|
}, [status, updatePendingMessages]);
|
|
987
|
-
const deleteFollowUp =
|
|
1311
|
+
const deleteFollowUp = useCallback5((id) => {
|
|
988
1312
|
const pending = pendingMessagesRef.current.find((item) => item.id === id);
|
|
989
1313
|
updatePendingMessages((items) => items.filter((item) => item.id !== id));
|
|
990
1314
|
updateProjectedFollowUps((items) => items.filter((item) => item.id !== id));
|
|
@@ -998,7 +1322,7 @@ function usePiNativeFollowUpQueue({
|
|
|
998
1322
|
}).catch(() => {
|
|
999
1323
|
});
|
|
1000
1324
|
}, [sessionId, requestHeaders, updatePendingMessages, updateProjectedFollowUps]);
|
|
1001
|
-
const clearFollowUps =
|
|
1325
|
+
const clearFollowUps = useCallback5(() => {
|
|
1002
1326
|
clearLocal();
|
|
1003
1327
|
fetch(`/api/v1/agent/chat/${encodeURIComponent(sessionId)}/followup`, {
|
|
1004
1328
|
method: "DELETE",
|
|
@@ -1006,7 +1330,7 @@ function usePiNativeFollowUpQueue({
|
|
|
1006
1330
|
}).catch(() => {
|
|
1007
1331
|
});
|
|
1008
1332
|
}, [clearLocal, sessionId, requestHeaders]);
|
|
1009
|
-
const stopAndClearFollowUps =
|
|
1333
|
+
const stopAndClearFollowUps = useCallback5(() => {
|
|
1010
1334
|
stop();
|
|
1011
1335
|
clearFollowUps();
|
|
1012
1336
|
}, [stop, clearFollowUps]);
|
|
@@ -1103,21 +1427,27 @@ function createCommandRegistry(initial) {
|
|
|
1103
1427
|
}
|
|
1104
1428
|
|
|
1105
1429
|
// src/front/composer/PluginUpdateStatus.tsx
|
|
1106
|
-
import { useEffect as useEffect7 } from "react";
|
|
1430
|
+
import { useEffect as useEffect7, useRef as useRef7 } from "react";
|
|
1107
1431
|
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
1108
1432
|
function PluginUpdateStatus({
|
|
1109
1433
|
state,
|
|
1110
1434
|
onDismiss,
|
|
1111
1435
|
onRetry,
|
|
1112
|
-
successAutoDismissMs =
|
|
1436
|
+
successAutoDismissMs = 1400,
|
|
1437
|
+
maxWidthClassName = "max-w-3xl"
|
|
1113
1438
|
}) {
|
|
1439
|
+
const onDismissRef = useRef7(onDismiss);
|
|
1440
|
+
useEffect7(() => {
|
|
1441
|
+
onDismissRef.current = onDismiss;
|
|
1442
|
+
}, [onDismiss]);
|
|
1443
|
+
const successDismissKey = state?.kind === "success" ? `${state.reloaded}:${(state.restartWarnings?.length ?? 0) > 0 || (state.diagnostics?.length ?? 0) > 0}` : null;
|
|
1114
1444
|
useEffect7(() => {
|
|
1115
1445
|
if (!state || state.kind !== "success" || successAutoDismissMs <= 0) return;
|
|
1116
|
-
const
|
|
1117
|
-
if (
|
|
1118
|
-
const timeout = window.setTimeout(
|
|
1446
|
+
const hasWarningsOrDiagnostics = (state.restartWarnings?.length ?? 0) > 0 || (state.diagnostics?.length ?? 0) > 0;
|
|
1447
|
+
if (hasWarningsOrDiagnostics) return;
|
|
1448
|
+
const timeout = window.setTimeout(() => onDismissRef.current(), successAutoDismissMs);
|
|
1119
1449
|
return () => window.clearTimeout(timeout);
|
|
1120
|
-
}, [state,
|
|
1450
|
+
}, [state?.kind, successDismissKey, successAutoDismissMs]);
|
|
1121
1451
|
if (!state) return null;
|
|
1122
1452
|
if (state.kind === "running") {
|
|
1123
1453
|
return /* @__PURE__ */ jsxs3(
|
|
@@ -1127,8 +1457,9 @@ function PluginUpdateStatus({
|
|
|
1127
1457
|
role: "status",
|
|
1128
1458
|
"aria-live": "polite",
|
|
1129
1459
|
className: cn(
|
|
1130
|
-
"mx-auto mb-2 w-full
|
|
1131
|
-
"px-3 py-2 text-xs text-foreground flex items-center gap-2"
|
|
1460
|
+
"mx-auto mb-2 w-full rounded-[var(--radius-md)] border border-accent/30 bg-[color:var(--accent-soft)]",
|
|
1461
|
+
"px-3 py-2 text-xs text-foreground flex items-center gap-2",
|
|
1462
|
+
maxWidthClassName
|
|
1132
1463
|
),
|
|
1133
1464
|
children: [
|
|
1134
1465
|
/* @__PURE__ */ jsx3("span", { className: "inline-block h-2 w-2 animate-pulse rounded-full bg-accent", "aria-hidden": "true" }),
|
|
@@ -1141,6 +1472,9 @@ function PluginUpdateStatus({
|
|
|
1141
1472
|
const warnings = state.restartWarnings ?? [];
|
|
1142
1473
|
const diagnostics = state.diagnostics ?? [];
|
|
1143
1474
|
const frontEvents = state.frontEvents ?? [];
|
|
1475
|
+
const hasWarningsOrDiagnostics = warnings.length > 0 || diagnostics.length > 0;
|
|
1476
|
+
const title = state.reloaded ? hasWarningsOrDiagnostics ? "Reload finished with warnings" : "Reload complete" : "Reload queued";
|
|
1477
|
+
const detail = state.reloaded ? hasWarningsOrDiagnostics ? "Some plugin changes need attention." : frontEvents.length > 0 ? `${frontEvents.length} plugin module${frontEvents.length === 1 ? "" : "s"} refreshed. Changes are live.` : "Changes are live." : void 0;
|
|
1144
1478
|
return /* @__PURE__ */ jsxs3(
|
|
1145
1479
|
"div",
|
|
1146
1480
|
{
|
|
@@ -1148,21 +1482,25 @@ function PluginUpdateStatus({
|
|
|
1148
1482
|
role: "status",
|
|
1149
1483
|
"aria-live": "polite",
|
|
1150
1484
|
className: cn(
|
|
1151
|
-
"mx-auto mb-2 w-full
|
|
1152
|
-
"px-3 py-2 text-xs text-foreground"
|
|
1485
|
+
"mx-auto mb-2 w-full rounded-[var(--radius-md)] border border-[oklch(0.78_0.13_148)]/35 bg-[oklch(0.95_0.05_148/0.28)]",
|
|
1486
|
+
"px-3 py-2 text-xs text-foreground shadow-sm",
|
|
1487
|
+
maxWidthClassName
|
|
1153
1488
|
),
|
|
1154
1489
|
children: [
|
|
1155
|
-
/* @__PURE__ */ jsxs3("div", { className: "flex items-
|
|
1156
|
-
/* @__PURE__ */ jsx3("span", { className: "text-[oklch(0.45_0.13_148)]", "aria-hidden": "true", children: "\u2713" }),
|
|
1157
|
-
/* @__PURE__ */
|
|
1490
|
+
/* @__PURE__ */ jsxs3("div", { className: "flex items-start gap-2", children: [
|
|
1491
|
+
/* @__PURE__ */ jsx3("span", { className: "mt-0.5 text-[oklch(0.45_0.13_148)]", "aria-hidden": "true", children: "\u2713" }),
|
|
1492
|
+
/* @__PURE__ */ jsxs3("span", { className: "min-w-0 flex-1", children: [
|
|
1493
|
+
/* @__PURE__ */ jsx3("span", { className: "block font-medium leading-5", children: title }),
|
|
1494
|
+
detail ? /* @__PURE__ */ jsx3("span", { className: "block leading-4 text-muted-foreground", children: detail }) : null
|
|
1495
|
+
] }),
|
|
1158
1496
|
/* @__PURE__ */ jsx3(
|
|
1159
1497
|
"button",
|
|
1160
1498
|
{
|
|
1161
1499
|
type: "button",
|
|
1162
1500
|
onClick: onDismiss,
|
|
1163
|
-
className: "rounded border border-transparent px-
|
|
1501
|
+
className: "-mr-1 rounded border border-transparent px-1.5 py-0.5 text-[13px] leading-none text-muted-foreground hover:bg-muted hover:text-foreground",
|
|
1164
1502
|
"aria-label": "Dismiss plugin update status",
|
|
1165
|
-
children: "
|
|
1503
|
+
children: "\xD7"
|
|
1166
1504
|
}
|
|
1167
1505
|
)
|
|
1168
1506
|
] }),
|
|
@@ -1192,31 +1530,6 @@ function PluginUpdateStatus({
|
|
|
1192
1530
|
]
|
|
1193
1531
|
}
|
|
1194
1532
|
) : null,
|
|
1195
|
-
frontEvents.length > 0 ? /* @__PURE__ */ jsxs3(
|
|
1196
|
-
"div",
|
|
1197
|
-
{
|
|
1198
|
-
"data-boring-plugin-update-front-events": "",
|
|
1199
|
-
className: cn(
|
|
1200
|
-
"mt-2 rounded border border-[oklch(0.78_0.13_148)]/35 bg-[oklch(0.95_0.05_148/0.25)]",
|
|
1201
|
-
"px-2 py-1.5 text-[11px] text-foreground"
|
|
1202
|
-
),
|
|
1203
|
-
children: [
|
|
1204
|
-
/* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-1.5 font-medium text-[oklch(0.45_0.13_148)]", children: [
|
|
1205
|
-
/* @__PURE__ */ jsx3("span", { "aria-hidden": "true", children: "\u2713" }),
|
|
1206
|
-
/* @__PURE__ */ jsxs3("span", { children: [
|
|
1207
|
-
"Browser plugin modules updated (",
|
|
1208
|
-
frontEvents.length,
|
|
1209
|
-
")"
|
|
1210
|
-
] })
|
|
1211
|
-
] }),
|
|
1212
|
-
/* @__PURE__ */ jsx3("ul", { className: "mt-1 ml-4 list-disc text-foreground/85", children: frontEvents.map((event, index) => /* @__PURE__ */ jsxs3("li", { children: [
|
|
1213
|
-
/* @__PURE__ */ jsx3("code", { className: "font-mono text-[10.5px]", children: event.pluginId ?? event.source ?? "plugin" }),
|
|
1214
|
-
" \u2014 ",
|
|
1215
|
-
event.message ?? "front module updated"
|
|
1216
|
-
] }, `${event.pluginId ?? event.source ?? "plugin"}-${index}`)) })
|
|
1217
|
-
]
|
|
1218
|
-
}
|
|
1219
|
-
) : null,
|
|
1220
1533
|
warnings.length > 0 ? /* @__PURE__ */ jsxs3(
|
|
1221
1534
|
"div",
|
|
1222
1535
|
{
|
|
@@ -1255,8 +1568,9 @@ function PluginUpdateStatus({
|
|
|
1255
1568
|
role: "status",
|
|
1256
1569
|
"aria-live": "polite",
|
|
1257
1570
|
className: cn(
|
|
1258
|
-
"mx-auto mb-2 w-full
|
|
1259
|
-
"px-3 py-2 text-xs text-foreground"
|
|
1571
|
+
"mx-auto mb-2 w-full rounded-[var(--radius-md)] border border-destructive/40 bg-destructive/10",
|
|
1572
|
+
"px-3 py-2 text-xs text-foreground",
|
|
1573
|
+
maxWidthClassName
|
|
1260
1574
|
),
|
|
1261
1575
|
children: [
|
|
1262
1576
|
/* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-2", children: [
|
|
@@ -1291,7 +1605,7 @@ function PluginUpdateStatus({
|
|
|
1291
1605
|
// src/front/primitives/tool-call-group.tsx
|
|
1292
1606
|
import { getToolName, isToolUIPart } from "ai";
|
|
1293
1607
|
import { ChevronDownIcon } from "lucide-react";
|
|
1294
|
-
import { memo as memo2, useCallback as
|
|
1608
|
+
import { memo as memo2, useCallback as useCallback7, useMemo as useMemo5, useState as useState9 } from "react";
|
|
1295
1609
|
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@hachej/boring-ui-kit";
|
|
1296
1610
|
|
|
1297
1611
|
// src/front/bareToolRenderers/DiffView.tsx
|
|
@@ -1347,7 +1661,7 @@ function DiffView({ oldString, newString, path, replaceAll }) {
|
|
|
1347
1661
|
}
|
|
1348
1662
|
|
|
1349
1663
|
// src/front/barePrimitives/Tool.tsx
|
|
1350
|
-
import { useEffect as useEffect8, useRef as
|
|
1664
|
+
import { useEffect as useEffect8, useRef as useRef8, useState as useState7 } from "react";
|
|
1351
1665
|
import { Button as Button2 } from "@hachej/boring-ui-kit";
|
|
1352
1666
|
import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1353
1667
|
function stateLabel(state) {
|
|
@@ -1386,7 +1700,7 @@ function Tool({
|
|
|
1386
1700
|
const isRunning = state === "input-streaming" || state === "input-available";
|
|
1387
1701
|
const isComplete = state === "output-available" || state === "output-error" || state === "output-denied";
|
|
1388
1702
|
const [elapsedSec, setElapsedSec] = useState7(0);
|
|
1389
|
-
const startRef =
|
|
1703
|
+
const startRef = useRef8(0);
|
|
1390
1704
|
useEffect8(() => {
|
|
1391
1705
|
if (!isRunning) {
|
|
1392
1706
|
setElapsedSec(0);
|
|
@@ -1486,7 +1800,7 @@ function Terminal({
|
|
|
1486
1800
|
}
|
|
1487
1801
|
|
|
1488
1802
|
// src/front/barePrimitives/CodeBlock.tsx
|
|
1489
|
-
import { useCallback as
|
|
1803
|
+
import { useCallback as useCallback6, useState as useState8 } from "react";
|
|
1490
1804
|
import { Button as Button3 } from "@hachej/boring-ui-kit";
|
|
1491
1805
|
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1492
1806
|
function CodeBlock({
|
|
@@ -1498,8 +1812,9 @@ function CodeBlock({
|
|
|
1498
1812
|
copyable = true
|
|
1499
1813
|
}) {
|
|
1500
1814
|
const [copied, setCopied] = useState8(false);
|
|
1501
|
-
const handleCopy =
|
|
1502
|
-
|
|
1815
|
+
const handleCopy = useCallback6(() => {
|
|
1816
|
+
void copyTextToClipboard(code3).then((ok) => {
|
|
1817
|
+
if (!ok) return;
|
|
1503
1818
|
setCopied(true);
|
|
1504
1819
|
setTimeout(() => setCopied(false), 2e3);
|
|
1505
1820
|
});
|
|
@@ -1824,7 +2139,7 @@ function getWorkspaceNotReadyStatus(output) {
|
|
|
1824
2139
|
code: ErrorCode.enum.WORKSPACE_NOT_READY,
|
|
1825
2140
|
retryable: true,
|
|
1826
2141
|
requirement,
|
|
1827
|
-
message: COPY[requirement]
|
|
2142
|
+
message: COPY[requirement] ?? "Workspace is still preparing."
|
|
1828
2143
|
};
|
|
1829
2144
|
}
|
|
1830
2145
|
|
|
@@ -1872,7 +2187,7 @@ var ToolCallGroup = memo2(({ tools, mergedToolRenderers }) => {
|
|
|
1872
2187
|
return part.state === "output-error";
|
|
1873
2188
|
}) && !workspaceNotReady;
|
|
1874
2189
|
const [isOpen, setIsOpen] = useState9(false);
|
|
1875
|
-
const handleOpenChange =
|
|
2190
|
+
const handleOpenChange = useCallback7((open) => setIsOpen(open), []);
|
|
1876
2191
|
const title = useMemo5(() => buildTitle(tools, isSettled), [tools, isSettled]);
|
|
1877
2192
|
return /* @__PURE__ */ jsxs9(Collapsible, { open: isOpen, onOpenChange: handleOpenChange, className: "not-prose my-1.5", children: [
|
|
1878
2193
|
/* @__PURE__ */ jsxs9(
|
|
@@ -1951,11 +2266,11 @@ import { CheckIcon, CopyIcon } from "lucide-react";
|
|
|
1951
2266
|
import {
|
|
1952
2267
|
createContext,
|
|
1953
2268
|
memo as memo3,
|
|
1954
|
-
useCallback as
|
|
2269
|
+
useCallback as useCallback8,
|
|
1955
2270
|
useContext,
|
|
1956
2271
|
useEffect as useEffect9,
|
|
1957
2272
|
useMemo as useMemo6,
|
|
1958
|
-
useRef as
|
|
2273
|
+
useRef as useRef9,
|
|
1959
2274
|
useState as useState10
|
|
1960
2275
|
} from "react";
|
|
1961
2276
|
import { createHighlighter } from "shiki";
|
|
@@ -2195,7 +2510,7 @@ var CodeBlockContent = ({
|
|
|
2195
2510
|
[code3, lang, rawTokens]
|
|
2196
2511
|
);
|
|
2197
2512
|
const [asyncTokens, setAsyncTokens] = useState10(null);
|
|
2198
|
-
const asyncKeyRef =
|
|
2513
|
+
const asyncKeyRef = useRef9({ code: code3, language });
|
|
2199
2514
|
if (asyncKeyRef.current.code !== code3 || asyncKeyRef.current.language !== language) {
|
|
2200
2515
|
asyncKeyRef.current = { code: code3, language };
|
|
2201
2516
|
setAsyncTokens(null);
|
|
@@ -2244,26 +2559,27 @@ var CodeBlockCopyButton = ({
|
|
|
2244
2559
|
...props
|
|
2245
2560
|
}) => {
|
|
2246
2561
|
const [isCopied, setIsCopied] = useState10(false);
|
|
2247
|
-
const timeoutRef =
|
|
2562
|
+
const timeoutRef = useRef9(0);
|
|
2248
2563
|
const { code: code3 } = useContext(CodeBlockContext);
|
|
2249
|
-
const copyToClipboard =
|
|
2250
|
-
if (typeof window === "undefined"
|
|
2564
|
+
const copyToClipboard = useCallback8(async () => {
|
|
2565
|
+
if (typeof window === "undefined") {
|
|
2251
2566
|
onError?.(new Error("Clipboard API not available"));
|
|
2252
2567
|
return;
|
|
2253
2568
|
}
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
await navigator.clipboard.writeText(code3);
|
|
2257
|
-
setIsCopied(true);
|
|
2258
|
-
onCopy?.();
|
|
2259
|
-
timeoutRef.current = window.setTimeout(
|
|
2260
|
-
() => setIsCopied(false),
|
|
2261
|
-
timeout
|
|
2262
|
-
);
|
|
2263
|
-
}
|
|
2264
|
-
} catch (error) {
|
|
2265
|
-
onError?.(error);
|
|
2569
|
+
if (isCopied) {
|
|
2570
|
+
return;
|
|
2266
2571
|
}
|
|
2572
|
+
const copied = await copyTextToClipboard(code3);
|
|
2573
|
+
if (!copied) {
|
|
2574
|
+
onError?.(new Error("Clipboard API not available"));
|
|
2575
|
+
return;
|
|
2576
|
+
}
|
|
2577
|
+
setIsCopied(true);
|
|
2578
|
+
onCopy?.();
|
|
2579
|
+
timeoutRef.current = window.setTimeout(
|
|
2580
|
+
() => setIsCopied(false),
|
|
2581
|
+
timeout
|
|
2582
|
+
);
|
|
2267
2583
|
}, [code3, onCopy, onError, timeout, isCopied]);
|
|
2268
2584
|
useEffect9(
|
|
2269
2585
|
() => () => {
|
|
@@ -2507,6 +2823,39 @@ var ArtifactContent = ({
|
|
|
2507
2823
|
|
|
2508
2824
|
// src/front/toolRenderers.tsx
|
|
2509
2825
|
import { CopyIcon as CopyIcon2, DownloadIcon } from "lucide-react";
|
|
2826
|
+
|
|
2827
|
+
// src/front/runtimeReadinessStatus.ts
|
|
2828
|
+
var COPY2 = {
|
|
2829
|
+
"runtime-dependencies": "Runtime dependencies are still installing.",
|
|
2830
|
+
"runtime:python": "Python runtime dependencies are still installing.",
|
|
2831
|
+
"runtime:node": "Node runtime dependencies are still installing."
|
|
2832
|
+
};
|
|
2833
|
+
function isRuntimeRequirement(requirement) {
|
|
2834
|
+
return requirement === "runtime-dependencies" || requirement.startsWith("runtime:");
|
|
2835
|
+
}
|
|
2836
|
+
function detailsFromOutput2(output) {
|
|
2837
|
+
if (!output || typeof output !== "object") return null;
|
|
2838
|
+
const record = output;
|
|
2839
|
+
const details = record.details && typeof record.details === "object" ? record.details : record;
|
|
2840
|
+
return details;
|
|
2841
|
+
}
|
|
2842
|
+
function getRuntimeReadinessStatus(output) {
|
|
2843
|
+
const details = detailsFromOutput2(output);
|
|
2844
|
+
if (!details) return null;
|
|
2845
|
+
if (details.code !== ErrorCode.enum.AGENT_RUNTIME_NOT_READY && details.code !== ErrorCode.enum.RUNTIME_PROVISIONING_FAILED) return null;
|
|
2846
|
+
const requirement = details.requirement;
|
|
2847
|
+
if (typeof requirement !== "string" || !isRuntimeRequirement(requirement)) return null;
|
|
2848
|
+
const failed = details.code === ErrorCode.enum.RUNTIME_PROVISIONING_FAILED || details.state === "failed";
|
|
2849
|
+
return {
|
|
2850
|
+
code: details.code,
|
|
2851
|
+
retryable: details.retryable === true,
|
|
2852
|
+
requirement,
|
|
2853
|
+
state: typeof details.state === "string" ? details.state : void 0,
|
|
2854
|
+
message: failed ? "Runtime setup failed. Retry or reload the workspace." : COPY2[requirement] ?? "Runtime dependencies are still installing."
|
|
2855
|
+
};
|
|
2856
|
+
}
|
|
2857
|
+
|
|
2858
|
+
// src/front/toolRenderers.tsx
|
|
2510
2859
|
import { jsx as jsx15, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
2511
2860
|
function asRecord2(value) {
|
|
2512
2861
|
return typeof value === "object" && value !== null ? value : {};
|
|
@@ -2581,6 +2930,22 @@ function renderWorkspaceNotReady(part) {
|
|
|
2581
2930
|
] }) })
|
|
2582
2931
|
] });
|
|
2583
2932
|
}
|
|
2933
|
+
function renderRuntimeNotReady(part) {
|
|
2934
|
+
const status = getRuntimeReadinessStatus(part.output);
|
|
2935
|
+
if (!status) return null;
|
|
2936
|
+
const suffix = status.code === ErrorCode.enum.RUNTIME_PROVISIONING_FAILED ? "Retry or reload the workspace." : "This is retryable; try again shortly.";
|
|
2937
|
+
return /* @__PURE__ */ jsxs13(Tool2, { children: [
|
|
2938
|
+
/* @__PURE__ */ jsx15(ToolHeader, { title: `${part.toolName} \xB7 ${status.message} ${suffix}`, ...toHeaderProps(part) }),
|
|
2939
|
+
/* @__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: [
|
|
2940
|
+
status.message,
|
|
2941
|
+
" ",
|
|
2942
|
+
suffix
|
|
2943
|
+
] }) })
|
|
2944
|
+
] });
|
|
2945
|
+
}
|
|
2946
|
+
function renderReadinessBlock(part) {
|
|
2947
|
+
return renderWorkspaceNotReady(part) ?? renderRuntimeNotReady(part);
|
|
2948
|
+
}
|
|
2584
2949
|
function pathTitle(prefix, path) {
|
|
2585
2950
|
return /* @__PURE__ */ jsxs13("span", { className: "flex min-w-0 items-center gap-1.5", children: [
|
|
2586
2951
|
/* @__PURE__ */ jsx15("span", { className: "text-muted-foreground", children: prefix }),
|
|
@@ -2589,7 +2954,7 @@ function pathTitle(prefix, path) {
|
|
|
2589
2954
|
] });
|
|
2590
2955
|
}
|
|
2591
2956
|
function renderBash2(part) {
|
|
2592
|
-
const readiness =
|
|
2957
|
+
const readiness = renderReadinessBlock(part);
|
|
2593
2958
|
if (readiness) return readiness;
|
|
2594
2959
|
const input = asRecord2(part.input);
|
|
2595
2960
|
const output = asRecord2(part.output);
|
|
@@ -2622,7 +2987,7 @@ function renderBash2(part) {
|
|
|
2622
2987
|
] });
|
|
2623
2988
|
}
|
|
2624
2989
|
function renderRead2(part) {
|
|
2625
|
-
const readiness =
|
|
2990
|
+
const readiness = renderReadinessBlock(part);
|
|
2626
2991
|
if (readiness) return readiness;
|
|
2627
2992
|
const input = asRecord2(part.input);
|
|
2628
2993
|
const output = asRecord2(part.output);
|
|
@@ -2641,7 +3006,7 @@ function renderRead2(part) {
|
|
|
2641
3006
|
] });
|
|
2642
3007
|
}
|
|
2643
3008
|
function renderWrite2(part) {
|
|
2644
|
-
const readiness =
|
|
3009
|
+
const readiness = renderReadinessBlock(part);
|
|
2645
3010
|
if (readiness) return readiness;
|
|
2646
3011
|
const input = asRecord2(part.input);
|
|
2647
3012
|
const path = typeof input.path === "string" ? input.path : "";
|
|
@@ -2669,10 +3034,7 @@ function renderWrite2(part) {
|
|
|
2669
3034
|
tooltip: "Copy contents",
|
|
2670
3035
|
label: "Copy",
|
|
2671
3036
|
onClick: () => {
|
|
2672
|
-
|
|
2673
|
-
navigator.clipboard.writeText(content).catch(() => {
|
|
2674
|
-
});
|
|
2675
|
-
}
|
|
3037
|
+
void copyTextToClipboard(content);
|
|
2676
3038
|
}
|
|
2677
3039
|
}
|
|
2678
3040
|
),
|
|
@@ -2705,7 +3067,7 @@ function renderWrite2(part) {
|
|
|
2705
3067
|
] });
|
|
2706
3068
|
}
|
|
2707
3069
|
function renderEdit2(part) {
|
|
2708
|
-
const readiness =
|
|
3070
|
+
const readiness = renderReadinessBlock(part);
|
|
2709
3071
|
if (readiness) return readiness;
|
|
2710
3072
|
const input = asRecord2(part.input);
|
|
2711
3073
|
const path = typeof input.path === "string" ? input.path : "";
|
|
@@ -2732,7 +3094,7 @@ function renderEdit2(part) {
|
|
|
2732
3094
|
] });
|
|
2733
3095
|
}
|
|
2734
3096
|
function renderSearchLike2(toolName, part) {
|
|
2735
|
-
const readiness =
|
|
3097
|
+
const readiness = renderReadinessBlock(part);
|
|
2736
3098
|
if (readiness) return readiness;
|
|
2737
3099
|
const input = asRecord2(part.input);
|
|
2738
3100
|
const SearchLikeIcon = toolName === "find" || toolName === "grep" ? SearchIcon : FileTextIcon;
|
|
@@ -2770,7 +3132,7 @@ function extractParamTokens(value, depth = 0) {
|
|
|
2770
3132
|
return [String(value)];
|
|
2771
3133
|
}
|
|
2772
3134
|
function renderExecUi2(part) {
|
|
2773
|
-
const readiness =
|
|
3135
|
+
const readiness = renderReadinessBlock(part);
|
|
2774
3136
|
if (readiness) return readiness;
|
|
2775
3137
|
const input = asRecord2(part.input);
|
|
2776
3138
|
const kind = typeof input.kind === "string" ? input.kind : "(empty)";
|
|
@@ -2828,7 +3190,7 @@ function renderExecUi2(part) {
|
|
|
2828
3190
|
] });
|
|
2829
3191
|
}
|
|
2830
3192
|
function renderFallback2(part) {
|
|
2831
|
-
const readiness =
|
|
3193
|
+
const readiness = renderReadinessBlock(part);
|
|
2832
3194
|
if (readiness) return readiness;
|
|
2833
3195
|
return /* @__PURE__ */ jsxs13(Tool2, { children: [
|
|
2834
3196
|
/* @__PURE__ */ jsx15(ToolHeader, { title: part.toolName, ...toHeaderProps(part) }),
|
|
@@ -2861,7 +3223,7 @@ function mergeShadcnToolRenderers(overrides) {
|
|
|
2861
3223
|
// src/front/primitives/conversation.tsx
|
|
2862
3224
|
import { Button as Button6 } from "@hachej/boring-ui-kit";
|
|
2863
3225
|
import { ArrowDownIcon, DownloadIcon as DownloadIcon2 } from "lucide-react";
|
|
2864
|
-
import { useCallback as
|
|
3226
|
+
import { useCallback as useCallback9, useEffect as useEffect10 } from "react";
|
|
2865
3227
|
import { StickToBottom, useStickToBottomContext } from "use-stick-to-bottom";
|
|
2866
3228
|
import { Fragment as Fragment4, jsx as jsx16, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
2867
3229
|
var ConversationScrollController = ({
|
|
@@ -2909,7 +3271,7 @@ var ConversationScrollButton = ({
|
|
|
2909
3271
|
...props
|
|
2910
3272
|
}) => {
|
|
2911
3273
|
const { isAtBottom, scrollToBottom } = useStickToBottomContext();
|
|
2912
|
-
const handleScrollToBottom =
|
|
3274
|
+
const handleScrollToBottom = useCallback9(() => {
|
|
2913
3275
|
scrollToBottom();
|
|
2914
3276
|
}, [scrollToBottom]);
|
|
2915
3277
|
return !isAtBottom && /* @__PURE__ */ jsx16(
|
|
@@ -2973,17 +3335,24 @@ function ChatEmptyState({
|
|
|
2973
3335
|
"div",
|
|
2974
3336
|
{
|
|
2975
3337
|
"data-boring-agent-part": "empty-state",
|
|
3338
|
+
style: {
|
|
3339
|
+
["--chat-empty-eyebrow-color"]: "var(--muted-foreground)",
|
|
3340
|
+
["--chat-empty-title-color"]: "var(--foreground)",
|
|
3341
|
+
["--chat-empty-description-color"]: "var(--muted-foreground)",
|
|
3342
|
+
["--chat-empty-card-hover"]: "var(--accent-soft)",
|
|
3343
|
+
["--chat-empty-accent"]: "var(--accent)"
|
|
3344
|
+
},
|
|
2976
3345
|
className: cn(
|
|
2977
3346
|
"mx-auto flex w-full max-w-[640px] flex-col px-2 pt-12 pb-4",
|
|
2978
3347
|
className
|
|
2979
3348
|
),
|
|
2980
3349
|
children: [
|
|
2981
|
-
eyebrow && /* @__PURE__ */ jsxs15("div", { className: "flex items-center gap-2 text-[10.5px] font-medium uppercase tracking-[0.16em] text-
|
|
2982
|
-
/* @__PURE__ */ jsx17("span", { className: "inline-block h-px w-4 bg-[color:var(--accent)]", "aria-hidden": "true" }),
|
|
3350
|
+
eyebrow && /* @__PURE__ */ jsxs15("div", { className: "flex items-center gap-2 text-[10.5px] font-medium uppercase tracking-[0.16em] text-[color:var(--chat-empty-eyebrow-color)]", children: [
|
|
3351
|
+
/* @__PURE__ */ jsx17("span", { className: "inline-block h-px w-4 bg-[color:var(--chat-empty-accent)]", "aria-hidden": "true" }),
|
|
2983
3352
|
eyebrow
|
|
2984
3353
|
] }),
|
|
2985
|
-
title && /* @__PURE__ */ jsx17("h3", { className: "mt-3 text-[34px] font-medium leading-[1.05] tracking-[-0.02em] text-
|
|
2986
|
-
description && /* @__PURE__ */ jsx17("p", { className: "mt-3 max-w-[440px] text-[14px] leading-relaxed text-
|
|
3354
|
+
title && /* @__PURE__ */ jsx17("h3", { className: "mt-3 text-[34px] font-medium leading-[1.05] tracking-[-0.02em] text-[color:var(--chat-empty-title-color)]", children: title }),
|
|
3355
|
+
description && /* @__PURE__ */ jsx17("p", { className: "mt-3 max-w-[440px] text-[14px] leading-relaxed text-[color:var(--chat-empty-description-color)]", children: description }),
|
|
2987
3356
|
suggestions.length > 0 && /* @__PURE__ */ jsx17(
|
|
2988
3357
|
"div",
|
|
2989
3358
|
{
|
|
@@ -3004,12 +3373,12 @@ function ChatEmptyState({
|
|
|
3004
3373
|
}
|
|
3005
3374
|
onSelect?.(suggestion);
|
|
3006
3375
|
},
|
|
3007
|
-
className: "group h-auto justify-start gap-3 rounded-none bg-background px-4 py-3.5 text-left hover:bg-[color:var(--
|
|
3376
|
+
className: "group h-auto justify-start gap-3 rounded-none bg-background px-4 py-3.5 text-left hover:bg-[color:var(--chat-empty-card-hover)] focus-visible:bg-[color:var(--chat-empty-card-hover)]",
|
|
3008
3377
|
children: [
|
|
3009
3378
|
Icon && /* @__PURE__ */ jsx17(
|
|
3010
3379
|
Icon,
|
|
3011
3380
|
{
|
|
3012
|
-
className: "mt-0.5 h-4 w-4 shrink-0 text-muted-foreground transition-colors group-hover:text-[color:var(--accent)]",
|
|
3381
|
+
className: "mt-0.5 h-4 w-4 shrink-0 text-muted-foreground transition-colors group-hover:text-[color:var(--chat-empty-accent)]",
|
|
3013
3382
|
strokeWidth: 1.75
|
|
3014
3383
|
}
|
|
3015
3384
|
),
|
|
@@ -3022,7 +3391,7 @@ function ChatEmptyState({
|
|
|
3022
3391
|
{
|
|
3023
3392
|
className: cn(
|
|
3024
3393
|
"mt-0.5 h-3.5 w-3.5 shrink-0 text-muted-foreground opacity-0 transition-all duration-200 ease-[cubic-bezier(0.22,1,0.36,1)]",
|
|
3025
|
-
"group-hover:translate-x-0.5 group-hover:-translate-y-0.5 group-hover:opacity-100 group-hover:text-[color:var(--accent)]"
|
|
3394
|
+
"group-hover:translate-x-0.5 group-hover:-translate-y-0.5 group-hover:opacity-100 group-hover:text-[color:var(--chat-empty-accent)]"
|
|
3026
3395
|
),
|
|
3027
3396
|
strokeWidth: 2
|
|
3028
3397
|
}
|
|
@@ -3056,7 +3425,7 @@ import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react";
|
|
|
3056
3425
|
import {
|
|
3057
3426
|
createContext as createContext3,
|
|
3058
3427
|
memo as memo4,
|
|
3059
|
-
useCallback as
|
|
3428
|
+
useCallback as useCallback10,
|
|
3060
3429
|
useContext as useContext3,
|
|
3061
3430
|
useEffect as useEffect11,
|
|
3062
3431
|
useMemo as useMemo7,
|
|
@@ -3218,11 +3587,11 @@ import { BrainIcon, ChevronDownIcon as ChevronDownIcon4 } from "lucide-react";
|
|
|
3218
3587
|
import {
|
|
3219
3588
|
createContext as createContext4,
|
|
3220
3589
|
memo as memo5,
|
|
3221
|
-
useCallback as
|
|
3590
|
+
useCallback as useCallback11,
|
|
3222
3591
|
useContext as useContext4,
|
|
3223
3592
|
useEffect as useEffect12,
|
|
3224
3593
|
useMemo as useMemo8,
|
|
3225
|
-
useRef as
|
|
3594
|
+
useRef as useRef10,
|
|
3226
3595
|
useState as useState12
|
|
3227
3596
|
} from "react";
|
|
3228
3597
|
import { Streamdown as Streamdown2 } from "streamdown";
|
|
@@ -3259,9 +3628,9 @@ var Reasoning = memo5(
|
|
|
3259
3628
|
defaultProp: void 0,
|
|
3260
3629
|
prop: durationProp
|
|
3261
3630
|
});
|
|
3262
|
-
const hasEverStreamedRef =
|
|
3631
|
+
const hasEverStreamedRef = useRef10(isStreaming);
|
|
3263
3632
|
const [hasAutoClosed, setHasAutoClosed] = useState12(false);
|
|
3264
|
-
const startTimeRef =
|
|
3633
|
+
const startTimeRef = useRef10(null);
|
|
3265
3634
|
useEffect12(() => {
|
|
3266
3635
|
if (isStreaming) {
|
|
3267
3636
|
hasEverStreamedRef.current = true;
|
|
@@ -3287,7 +3656,7 @@ var Reasoning = memo5(
|
|
|
3287
3656
|
return () => clearTimeout(timer);
|
|
3288
3657
|
}
|
|
3289
3658
|
}, [isStreaming, isOpen, setIsOpen, hasAutoClosed]);
|
|
3290
|
-
const handleOpenChange =
|
|
3659
|
+
const handleOpenChange = useCallback11(
|
|
3291
3660
|
(newOpen) => {
|
|
3292
3661
|
setIsOpen(newOpen);
|
|
3293
3662
|
},
|
|
@@ -3408,7 +3777,7 @@ import {
|
|
|
3408
3777
|
XIcon as XIcon2
|
|
3409
3778
|
} from "lucide-react";
|
|
3410
3779
|
import { nanoid } from "nanoid";
|
|
3411
|
-
import { useCallback as
|
|
3780
|
+
import { useCallback as useCallback12, useEffect as useEffect13, useMemo as useMemo9, useRef as useRef11, useState as useState13 } from "react";
|
|
3412
3781
|
|
|
3413
3782
|
// src/front/browserFiles.ts
|
|
3414
3783
|
var convertBlobUrlToDataUrl = async (url) => {
|
|
@@ -3490,10 +3859,10 @@ var PromptInput = ({
|
|
|
3490
3859
|
}) => {
|
|
3491
3860
|
const controller = useOptionalPromptInputController();
|
|
3492
3861
|
const usingProvider = !!controller;
|
|
3493
|
-
const inputRef =
|
|
3494
|
-
const formRef =
|
|
3862
|
+
const inputRef = useRef11(null);
|
|
3863
|
+
const formRef = useRef11(null);
|
|
3495
3864
|
const [items, setItems] = useState13([]);
|
|
3496
|
-
const setFileUrlLocal =
|
|
3865
|
+
const setFileUrlLocal = useCallback12((id, url, status) => {
|
|
3497
3866
|
setItems((prev) => prev.map((f) => {
|
|
3498
3867
|
if (f.id !== id) return f;
|
|
3499
3868
|
if (f.url !== url && f.url.startsWith("blob:")) URL.revokeObjectURL(f.url);
|
|
@@ -3502,14 +3871,14 @@ var PromptInput = ({
|
|
|
3502
3871
|
}, []);
|
|
3503
3872
|
const files = usingProvider ? controller.attachments.files : items;
|
|
3504
3873
|
const [referencedSources, setReferencedSources] = useState13([]);
|
|
3505
|
-
const filesRef =
|
|
3874
|
+
const filesRef = useRef11(files);
|
|
3506
3875
|
useEffect13(() => {
|
|
3507
3876
|
filesRef.current = files;
|
|
3508
3877
|
}, [files]);
|
|
3509
|
-
const openFileDialogLocal =
|
|
3878
|
+
const openFileDialogLocal = useCallback12(() => {
|
|
3510
3879
|
inputRef.current?.click();
|
|
3511
3880
|
}, []);
|
|
3512
|
-
const matchesAccept =
|
|
3881
|
+
const matchesAccept = useCallback12(
|
|
3513
3882
|
(f) => {
|
|
3514
3883
|
if (!accept || accept.trim() === "") {
|
|
3515
3884
|
return true;
|
|
@@ -3525,7 +3894,7 @@ var PromptInput = ({
|
|
|
3525
3894
|
},
|
|
3526
3895
|
[accept]
|
|
3527
3896
|
);
|
|
3528
|
-
const addLocal =
|
|
3897
|
+
const addLocal = useCallback12(
|
|
3529
3898
|
(fileList) => {
|
|
3530
3899
|
const incoming = [...fileList];
|
|
3531
3900
|
const accepted = incoming.filter((f) => matchesAccept(f));
|
|
@@ -3572,7 +3941,7 @@ var PromptInput = ({
|
|
|
3572
3941
|
},
|
|
3573
3942
|
[matchesAccept, maxFiles, maxFileSize, onError, items.length, onUploadFile, setFileUrlLocal]
|
|
3574
3943
|
);
|
|
3575
|
-
const removeLocal =
|
|
3944
|
+
const removeLocal = useCallback12(
|
|
3576
3945
|
(id) => setItems((prev) => {
|
|
3577
3946
|
const found = prev.find((file) => file.id === id);
|
|
3578
3947
|
if (found?.url.startsWith("blob:")) {
|
|
@@ -3582,7 +3951,7 @@ var PromptInput = ({
|
|
|
3582
3951
|
}),
|
|
3583
3952
|
[]
|
|
3584
3953
|
);
|
|
3585
|
-
const addWithProviderValidation =
|
|
3954
|
+
const addWithProviderValidation = useCallback12(
|
|
3586
3955
|
(fileList) => {
|
|
3587
3956
|
const incoming = [...fileList];
|
|
3588
3957
|
const accepted = incoming.filter((f) => matchesAccept(f));
|
|
@@ -3617,7 +3986,7 @@ var PromptInput = ({
|
|
|
3617
3986
|
},
|
|
3618
3987
|
[matchesAccept, maxFileSize, maxFiles, onError, files.length, controller]
|
|
3619
3988
|
);
|
|
3620
|
-
const clearAttachments =
|
|
3989
|
+
const clearAttachments = useCallback12(
|
|
3621
3990
|
() => usingProvider ? controller?.attachments.clear() : setItems((prev) => {
|
|
3622
3991
|
for (const file of prev) {
|
|
3623
3992
|
if (file.url.startsWith("blob:")) {
|
|
@@ -3628,14 +3997,14 @@ var PromptInput = ({
|
|
|
3628
3997
|
}),
|
|
3629
3998
|
[usingProvider, controller]
|
|
3630
3999
|
);
|
|
3631
|
-
const clearReferencedSources =
|
|
4000
|
+
const clearReferencedSources = useCallback12(
|
|
3632
4001
|
() => setReferencedSources([]),
|
|
3633
4002
|
[]
|
|
3634
4003
|
);
|
|
3635
4004
|
const add = usingProvider ? addWithProviderValidation : addLocal;
|
|
3636
4005
|
const remove = usingProvider ? controller.attachments.remove : removeLocal;
|
|
3637
4006
|
const openFileDialog = usingProvider ? controller.attachments.openFileDialog : openFileDialogLocal;
|
|
3638
|
-
const clear =
|
|
4007
|
+
const clear = useCallback12(() => {
|
|
3639
4008
|
clearAttachments();
|
|
3640
4009
|
clearReferencedSources();
|
|
3641
4010
|
}, [clearAttachments, clearReferencedSources]);
|
|
@@ -3714,7 +4083,7 @@ var PromptInput = ({
|
|
|
3714
4083
|
},
|
|
3715
4084
|
[usingProvider]
|
|
3716
4085
|
);
|
|
3717
|
-
const handleChange =
|
|
4086
|
+
const handleChange = useCallback12(
|
|
3718
4087
|
(event) => {
|
|
3719
4088
|
if (event.currentTarget.files) {
|
|
3720
4089
|
add(event.currentTarget.files);
|
|
@@ -3753,7 +4122,7 @@ var PromptInput = ({
|
|
|
3753
4122
|
}),
|
|
3754
4123
|
[referencedSources, clearReferencedSources]
|
|
3755
4124
|
);
|
|
3756
|
-
const handleSubmit =
|
|
4125
|
+
const handleSubmit = useCallback12(
|
|
3757
4126
|
async (event) => {
|
|
3758
4127
|
event.preventDefault();
|
|
3759
4128
|
const form = event.currentTarget;
|
|
@@ -3837,7 +4206,7 @@ var PromptInputTextarea = ({
|
|
|
3837
4206
|
const controller = useOptionalPromptInputController();
|
|
3838
4207
|
const attachments = usePromptInputAttachments();
|
|
3839
4208
|
const [isComposing, setIsComposing] = useState13(false);
|
|
3840
|
-
const handleKeyDown =
|
|
4209
|
+
const handleKeyDown = useCallback12(
|
|
3841
4210
|
(e) => {
|
|
3842
4211
|
onKeyDown?.(e);
|
|
3843
4212
|
if (e.defaultPrevented) {
|
|
@@ -3870,7 +4239,7 @@ var PromptInputTextarea = ({
|
|
|
3870
4239
|
},
|
|
3871
4240
|
[onKeyDown, isComposing, attachments]
|
|
3872
4241
|
);
|
|
3873
|
-
const handlePaste =
|
|
4242
|
+
const handlePaste = useCallback12(
|
|
3874
4243
|
(event) => {
|
|
3875
4244
|
const items = event.clipboardData?.items;
|
|
3876
4245
|
if (!items) {
|
|
@@ -3892,8 +4261,8 @@ var PromptInputTextarea = ({
|
|
|
3892
4261
|
},
|
|
3893
4262
|
[attachments]
|
|
3894
4263
|
);
|
|
3895
|
-
const handleCompositionEnd =
|
|
3896
|
-
const handleCompositionStart =
|
|
4264
|
+
const handleCompositionEnd = useCallback12(() => setIsComposing(false), []);
|
|
4265
|
+
const handleCompositionStart = useCallback12(() => setIsComposing(true), []);
|
|
3897
4266
|
const controlledProps = controller ? {
|
|
3898
4267
|
onChange: (e) => {
|
|
3899
4268
|
controller.textInput.setInput(e.currentTarget.value);
|
|
@@ -3938,7 +4307,7 @@ var PromptInputSubmit = ({
|
|
|
3938
4307
|
} else if (status === "error") {
|
|
3939
4308
|
Icon = /* @__PURE__ */ jsx21(XIcon2, { className: "size-4" });
|
|
3940
4309
|
}
|
|
3941
|
-
const handleClick =
|
|
4310
|
+
const handleClick = useCallback12(
|
|
3942
4311
|
(e) => {
|
|
3943
4312
|
if (isGenerating && onStop) {
|
|
3944
4313
|
e.preventDefault();
|
|
@@ -3975,7 +4344,7 @@ import {
|
|
|
3975
4344
|
VideoIcon,
|
|
3976
4345
|
XIcon as XIcon3
|
|
3977
4346
|
} from "lucide-react";
|
|
3978
|
-
import { createContext as createContext6, useCallback as
|
|
4347
|
+
import { createContext as createContext6, useCallback as useCallback13, useContext as useContext6, useMemo as useMemo10 } from "react";
|
|
3979
4348
|
import { jsx as jsx22, jsxs as jsxs20 } from "react/jsx-runtime";
|
|
3980
4349
|
var mediaCategoryIcons = {
|
|
3981
4350
|
audio: Music2Icon,
|
|
@@ -4152,7 +4521,7 @@ var AttachmentRemove = ({
|
|
|
4152
4521
|
...props
|
|
4153
4522
|
}) => {
|
|
4154
4523
|
const { onRemove, variant } = useAttachmentContext();
|
|
4155
|
-
const handleClick =
|
|
4524
|
+
const handleClick = useCallback13(
|
|
4156
4525
|
(e) => {
|
|
4157
4526
|
e.stopPropagation();
|
|
4158
4527
|
onRemove?.();
|
|
@@ -4195,7 +4564,7 @@ var AttachmentRemove = ({
|
|
|
4195
4564
|
};
|
|
4196
4565
|
|
|
4197
4566
|
// src/front/ChatPanel.tsx
|
|
4198
|
-
import { PaperclipIcon as PaperclipIcon2, CopyIcon as CopyIcon3, CheckIcon as CheckIcon3, RefreshCwIcon, Loader2, AlertCircleIcon, XIcon as XIcon4 } from "lucide-react";
|
|
4567
|
+
import { PaperclipIcon as PaperclipIcon2, CopyIcon as CopyIcon3, CheckIcon as CheckIcon3, RefreshCwIcon, Loader2, AlertCircleIcon, XIcon as XIcon4, SquareArrowOutUpRight } from "lucide-react";
|
|
4199
4568
|
import {
|
|
4200
4569
|
Button as Button10,
|
|
4201
4570
|
IconButton as IconButton2
|
|
@@ -4269,7 +4638,7 @@ function getReasoningPart(part) {
|
|
|
4269
4638
|
}
|
|
4270
4639
|
|
|
4271
4640
|
// src/front/useComposerHistory.ts
|
|
4272
|
-
import { useCallback as
|
|
4641
|
+
import { useCallback as useCallback14, useRef as useRef12 } from "react";
|
|
4273
4642
|
var HISTORY_RESET_IGNORED_KEYS = ["Shift", "Meta", "Control", "Alt", "CapsLock"];
|
|
4274
4643
|
function setTextareaValue(textarea, value) {
|
|
4275
4644
|
const setter = Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, "value")?.set;
|
|
@@ -4281,9 +4650,9 @@ function useComposerHistory({
|
|
|
4281
4650
|
textareaRef,
|
|
4282
4651
|
disabled
|
|
4283
4652
|
}) {
|
|
4284
|
-
const historyIdxRef =
|
|
4285
|
-
const draftRef =
|
|
4286
|
-
return
|
|
4653
|
+
const historyIdxRef = useRef12(-1);
|
|
4654
|
+
const draftRef = useRef12("");
|
|
4655
|
+
return useCallback14((e) => {
|
|
4287
4656
|
const ta = e.currentTarget;
|
|
4288
4657
|
textareaRef.current = ta;
|
|
4289
4658
|
if (disabled) return;
|
|
@@ -4312,7 +4681,7 @@ function useComposerHistory({
|
|
|
4312
4681
|
}
|
|
4313
4682
|
|
|
4314
4683
|
// src/front/useComposerPickers.ts
|
|
4315
|
-
import { useCallback as
|
|
4684
|
+
import { useCallback as useCallback15, useState as useState14 } from "react";
|
|
4316
4685
|
function setTextareaValue2(textarea, value) {
|
|
4317
4686
|
const setter = Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, "value")?.set;
|
|
4318
4687
|
setter?.call(textarea, value);
|
|
@@ -4324,7 +4693,7 @@ function useComposerPickers({
|
|
|
4324
4693
|
const [mentionState, setMentionState] = useState14(null);
|
|
4325
4694
|
const [slashQuery, setSlashQuery] = useState14(null);
|
|
4326
4695
|
const [mentionedFiles, setMentionedFiles] = useState14([]);
|
|
4327
|
-
const handleComposerChange =
|
|
4696
|
+
const handleComposerChange = useCallback15((e) => {
|
|
4328
4697
|
const ta = e.currentTarget;
|
|
4329
4698
|
textareaRef.current = ta;
|
|
4330
4699
|
const cursor = ta.selectionStart ?? ta.value.length;
|
|
@@ -4338,7 +4707,7 @@ function useComposerPickers({
|
|
|
4338
4707
|
setMentionState(detectMention(ta.value, cursor));
|
|
4339
4708
|
}
|
|
4340
4709
|
}, [textareaRef]);
|
|
4341
|
-
const selectMention =
|
|
4710
|
+
const selectMention = useCallback15((path) => {
|
|
4342
4711
|
const ta = textareaRef.current;
|
|
4343
4712
|
if (!ta || !mentionState) return;
|
|
4344
4713
|
const { anchorStart, anchorEnd } = mentionState;
|
|
@@ -4351,7 +4720,7 @@ function useComposerPickers({
|
|
|
4351
4720
|
setMentionState(null);
|
|
4352
4721
|
setMentionedFiles((prev) => prev.includes(path) ? prev : [...prev, path]);
|
|
4353
4722
|
}, [mentionState, textareaRef]);
|
|
4354
|
-
const selectSlashCommand =
|
|
4723
|
+
const selectSlashCommand = useCallback15((name) => {
|
|
4355
4724
|
const ta = textareaRef.current;
|
|
4356
4725
|
if (!ta) return;
|
|
4357
4726
|
const newValue = `/${name} `;
|
|
@@ -4360,13 +4729,13 @@ function useComposerPickers({
|
|
|
4360
4729
|
ta.focus();
|
|
4361
4730
|
setSlashQuery(null);
|
|
4362
4731
|
}, [textareaRef]);
|
|
4363
|
-
const clearMentionedFiles =
|
|
4732
|
+
const clearMentionedFiles = useCallback15(() => {
|
|
4364
4733
|
setMentionedFiles([]);
|
|
4365
4734
|
}, []);
|
|
4366
|
-
const dismissMention =
|
|
4735
|
+
const dismissMention = useCallback15(() => {
|
|
4367
4736
|
setMentionState(null);
|
|
4368
4737
|
}, []);
|
|
4369
|
-
const dismissSlash =
|
|
4738
|
+
const dismissSlash = useCallback15(() => {
|
|
4370
4739
|
setSlashQuery(null);
|
|
4371
4740
|
}, []);
|
|
4372
4741
|
return {
|
|
@@ -4437,7 +4806,7 @@ ${content}
|
|
|
4437
4806
|
}
|
|
4438
4807
|
|
|
4439
4808
|
// src/front/hooks/useChatModelSelection.ts
|
|
4440
|
-
import { useCallback as
|
|
4809
|
+
import { useCallback as useCallback16, useEffect as useEffect14, useMemo as useMemo11, useRef as useRef13, useState as useState15 } from "react";
|
|
4441
4810
|
|
|
4442
4811
|
// src/front/chatPanelSettings.ts
|
|
4443
4812
|
var STORAGE_MODEL_KEY = "boring-agent:composer:model";
|
|
@@ -4551,11 +4920,11 @@ function useChatModelSelection({
|
|
|
4551
4920
|
const [userSelectedModel, setUserSelectedModel] = useState15(
|
|
4552
4921
|
() => initialModelState.userSelected
|
|
4553
4922
|
);
|
|
4554
|
-
const userSelectedModelRef =
|
|
4923
|
+
const userSelectedModelRef = useRef13(userSelectedModel);
|
|
4555
4924
|
useEffect14(() => {
|
|
4556
4925
|
userSelectedModelRef.current = userSelectedModel;
|
|
4557
4926
|
}, [userSelectedModel]);
|
|
4558
|
-
const setModel =
|
|
4927
|
+
const setModel = useCallback16((next) => {
|
|
4559
4928
|
setUserSelectedModel(true);
|
|
4560
4929
|
setModelState(next);
|
|
4561
4930
|
}, []);
|
|
@@ -4961,7 +5330,7 @@ function KbdHints() {
|
|
|
4961
5330
|
|
|
4962
5331
|
// src/front/ChatPanel.tsx
|
|
4963
5332
|
import { Fragment as Fragment7, jsx as jsx25, jsxs as jsxs23 } from "react/jsx-runtime";
|
|
4964
|
-
var DebugDrawer2 = lazy(() => import("../DebugDrawer-
|
|
5333
|
+
var DebugDrawer2 = lazy(() => import("../DebugDrawer-PX2PHXJH.js").then((m) => ({ default: m.DebugDrawer })));
|
|
4965
5334
|
function hasToolPart(message) {
|
|
4966
5335
|
return (message.parts ?? []).some((part) => isToolUIPart2(part));
|
|
4967
5336
|
}
|
|
@@ -5090,6 +5459,18 @@ function composerNoticeForWarmup(status) {
|
|
|
5090
5459
|
code: ErrorCode.enum.AGENT_RUNTIME_NOT_READY
|
|
5091
5460
|
};
|
|
5092
5461
|
}
|
|
5462
|
+
function composerNoticeForRuntimeDependencies(status) {
|
|
5463
|
+
const runtime = status?.runtimeDependencies;
|
|
5464
|
+
if (!runtime || runtime.state === "ready") return null;
|
|
5465
|
+
if (runtime.state === "failed") {
|
|
5466
|
+
return {
|
|
5467
|
+
title: "Runtime tools failed to prepare.",
|
|
5468
|
+
detail: runtime.message ?? "Chat still works, but dependency-backed tools may be unavailable.",
|
|
5469
|
+
code: ErrorCode.enum.RUNTIME_PROVISIONING_FAILED
|
|
5470
|
+
};
|
|
5471
|
+
}
|
|
5472
|
+
return null;
|
|
5473
|
+
}
|
|
5093
5474
|
function isComposerRuntimeNotice(error) {
|
|
5094
5475
|
return error?.code === ErrorCode.enum.AGENT_RUNTIME_NOT_READY || error?.code === ErrorCode.enum.RUNTIME_PROVISIONING_FAILED;
|
|
5095
5476
|
}
|
|
@@ -5166,18 +5547,18 @@ function ChatPanel(props) {
|
|
|
5166
5547
|
} = props;
|
|
5167
5548
|
const [debugWidth, setDebugWidth] = useState20(440);
|
|
5168
5549
|
const capabilities = PI_AGENT_RUNTIME_CAPABILITIES;
|
|
5169
|
-
const scrollToBottomRef =
|
|
5550
|
+
const scrollToBottomRef = useRef14(() => {
|
|
5170
5551
|
});
|
|
5171
|
-
const piDataHandlerRef =
|
|
5552
|
+
const piDataHandlerRef = useRef14(() => {
|
|
5172
5553
|
});
|
|
5173
|
-
const followUpDataHandlerRef =
|
|
5554
|
+
const followUpDataHandlerRef = useRef14(() => {
|
|
5174
5555
|
});
|
|
5175
|
-
const autoSubmittedDraftRef =
|
|
5176
|
-
const autoSubmittingDraftRef =
|
|
5177
|
-
const pendingAutoSubmitUnlockRef =
|
|
5178
|
-
const pendingAutoSubmitSettleRef =
|
|
5179
|
-
const activeAutoSubmitSessionRef =
|
|
5180
|
-
const liveSessionIdRef =
|
|
5556
|
+
const autoSubmittedDraftRef = useRef14(void 0);
|
|
5557
|
+
const autoSubmittingDraftRef = useRef14(void 0);
|
|
5558
|
+
const pendingAutoSubmitUnlockRef = useRef14(void 0);
|
|
5559
|
+
const pendingAutoSubmitSettleRef = useRef14(void 0);
|
|
5560
|
+
const activeAutoSubmitSessionRef = useRef14(sessionId);
|
|
5561
|
+
const liveSessionIdRef = useRef14(sessionId);
|
|
5181
5562
|
liveSessionIdRef.current = sessionId;
|
|
5182
5563
|
activeAutoSubmitSessionRef.current = sessionId;
|
|
5183
5564
|
const [acceptedAutoSubmittedDraft, setAcceptedAutoSubmittedDraft] = useState20(void 0);
|
|
@@ -5196,7 +5577,8 @@ function ChatPanel(props) {
|
|
|
5196
5577
|
status,
|
|
5197
5578
|
error,
|
|
5198
5579
|
stop,
|
|
5199
|
-
clearError
|
|
5580
|
+
clearError,
|
|
5581
|
+
hydratingMessages
|
|
5200
5582
|
} = useAgentChat({
|
|
5201
5583
|
sessionId,
|
|
5202
5584
|
onData: (part) => {
|
|
@@ -5238,11 +5620,15 @@ function ChatPanel(props) {
|
|
|
5238
5620
|
const friendlyChatError = error ? friendlyError(error) : null;
|
|
5239
5621
|
const runtimeErrorNotice = isComposerRuntimeNotice(friendlyChatError) ? friendlyChatError : null;
|
|
5240
5622
|
const warmupNotice = composerNoticeForWarmup(workspaceWarmupStatus);
|
|
5241
|
-
const
|
|
5623
|
+
const runtimeDependenciesNotice = composerNoticeForRuntimeDependencies(workspaceWarmupStatus);
|
|
5624
|
+
const composerStatusNotice = composerRuntimeNotice ?? runtimeErrorNotice ?? warmupNotice ?? runtimeDependenciesNotice;
|
|
5242
5625
|
const workspaceWarmupBlocked = Boolean(warmupNotice);
|
|
5243
|
-
const
|
|
5626
|
+
const hostComposerBlocked = composerBlockers.length > 0;
|
|
5627
|
+
const composerBlocked = workspaceWarmupBlocked || hostComposerBlocked;
|
|
5244
5628
|
const primaryComposerBlocker = composerBlockers[0];
|
|
5245
5629
|
const composerBlockerLabel = workspaceWarmupBlocked ? warmupNotice?.title ?? "Preparing workspace\u2026" : primaryComposerBlocker?.label ?? "Complete the pending workspace action to continue.";
|
|
5630
|
+
const composerSubmitStatus = hostComposerBlocked && !workspaceWarmupBlocked ? "streaming" : status;
|
|
5631
|
+
const activityBadgeLabel = hostComposerBlocked && !workspaceWarmupBlocked ? "Waiting for your answer\u2026" : "Working\u2026";
|
|
5246
5632
|
const registry = useMemo12(
|
|
5247
5633
|
() => {
|
|
5248
5634
|
const effectiveBuiltins = hotReloadEnabled ? builtinCommands : builtinCommands.filter((cmd) => cmd.name !== "reload");
|
|
@@ -5259,7 +5645,7 @@ function ChatPanel(props) {
|
|
|
5259
5645
|
});
|
|
5260
5646
|
const { thinkingLevel, setThinkingLevel, showThoughts, setShowThoughts } = useThinkingSettings(thinkingControl);
|
|
5261
5647
|
const { attachmentNotice, setAttachmentNotice } = useAttachmentNotice();
|
|
5262
|
-
const callPluginReload =
|
|
5648
|
+
const callPluginReload = useCallback17(async () => {
|
|
5263
5649
|
const res = await fetch("/api/v1/agent/reload", {
|
|
5264
5650
|
method: "POST",
|
|
5265
5651
|
headers: { ...requestHeaders ?? {}, "content-type": "application/json" },
|
|
@@ -5279,7 +5665,7 @@ function ChatPanel(props) {
|
|
|
5279
5665
|
...payload.diagnostics && payload.diagnostics.length > 0 ? { diagnostics: payload.diagnostics } : {}
|
|
5280
5666
|
};
|
|
5281
5667
|
}, [requestHeaders, sessionId]);
|
|
5282
|
-
const reloadAgentPlugins =
|
|
5668
|
+
const reloadAgentPlugins = useCallback17(async () => {
|
|
5283
5669
|
try {
|
|
5284
5670
|
const { reloaded, diagnostics } = await callPluginReload();
|
|
5285
5671
|
const base = reloaded ? "Agent plugins reloaded." : "Agent plugins will reload on the next message.";
|
|
@@ -5292,7 +5678,7 @@ ${formatPluginReloadDiagnostics(diagnostics)}` : base;
|
|
|
5292
5678
|
}
|
|
5293
5679
|
}, [callPluginReload]);
|
|
5294
5680
|
const [pluginUpdateState, setPluginUpdateState] = useState20(null);
|
|
5295
|
-
const activeSessionRef =
|
|
5681
|
+
const activeSessionRef = useRef14(sessionId);
|
|
5296
5682
|
useEffect18(() => {
|
|
5297
5683
|
activeSessionRef.current = sessionId;
|
|
5298
5684
|
setPluginUpdateState(null);
|
|
@@ -5323,7 +5709,7 @@ ${formatPluginReloadDiagnostics(diagnostics)}` : base;
|
|
|
5323
5709
|
window.addEventListener(WORKSPACE_AGENT_PLUGINS_RELOADED_EVENT, onBrowserPluginReload);
|
|
5324
5710
|
return () => window.removeEventListener(WORKSPACE_AGENT_PLUGINS_RELOADED_EVENT, onBrowserPluginReload);
|
|
5325
5711
|
}, []);
|
|
5326
|
-
const runPluginUpdate =
|
|
5712
|
+
const runPluginUpdate = useCallback17(async () => {
|
|
5327
5713
|
const capturedSession = activeSessionRef.current;
|
|
5328
5714
|
setPluginUpdateState({ kind: "running" });
|
|
5329
5715
|
try {
|
|
@@ -5347,7 +5733,7 @@ ${formatPluginReloadDiagnostics(diagnostics)}` : base;
|
|
|
5347
5733
|
return `Plugin update failed: ${message}`;
|
|
5348
5734
|
}
|
|
5349
5735
|
}, [callPluginReload]);
|
|
5350
|
-
const dismissPluginUpdate =
|
|
5736
|
+
const dismissPluginUpdate = useCallback17(() => setPluginUpdateState(null), []);
|
|
5351
5737
|
const isStreaming = status === "submitted" || status === "streaming";
|
|
5352
5738
|
const attachmentsDisabled = isStreaming || pendingMessages.length > 0;
|
|
5353
5739
|
const displayMessages = useMemo12(() => {
|
|
@@ -5396,32 +5782,36 @@ ${formatPluginReloadDiagnostics(diagnostics)}` : base;
|
|
|
5396
5782
|
];
|
|
5397
5783
|
}, [acceptedAutoSubmittedDraft, messages, piMessages, projectedTailMessages, projectedStatusById, sessionId, status]);
|
|
5398
5784
|
const renderMessages = displayMessages.filter((message) => message.role !== "assistant" || hasVisibleMessageContent(message));
|
|
5399
|
-
const emptyHero = emptyPlacement === "hero" && renderMessages.length === 0;
|
|
5400
|
-
const handleStop =
|
|
5785
|
+
const emptyHero = emptyPlacement === "hero" && renderMessages.length === 0 && !hydratingMessages;
|
|
5786
|
+
const handleStop = useCallback17(() => {
|
|
5401
5787
|
onComposerStop?.();
|
|
5788
|
+
if (hostComposerBlocked && !workspaceWarmupBlocked) return;
|
|
5402
5789
|
stopAndClearFollowUps();
|
|
5403
|
-
}, [onComposerStop, stopAndClearFollowUps]);
|
|
5404
|
-
const handleInterrupt =
|
|
5790
|
+
}, [hostComposerBlocked, onComposerStop, stopAndClearFollowUps, workspaceWarmupBlocked]);
|
|
5791
|
+
const handleInterrupt = useCallback17(() => {
|
|
5405
5792
|
stop();
|
|
5406
5793
|
}, [stop]);
|
|
5407
5794
|
useEffect18(() => {
|
|
5408
5795
|
const onKeyDown = (e) => {
|
|
5409
5796
|
if (e.key !== "Escape" || !isStreaming) return;
|
|
5410
|
-
const
|
|
5797
|
+
const target = e.target instanceof HTMLElement ? e.target : null;
|
|
5798
|
+
const tag = target?.tagName;
|
|
5411
5799
|
if (tag === "INPUT" || tag === "TEXTAREA") return;
|
|
5800
|
+
if (target?.closest('[role="dialog"], [role="menu"], [role="listbox"]')) return;
|
|
5412
5801
|
e.preventDefault();
|
|
5802
|
+
e.stopPropagation();
|
|
5413
5803
|
handleInterrupt();
|
|
5414
5804
|
};
|
|
5415
|
-
window.addEventListener("keydown", onKeyDown);
|
|
5416
|
-
return () => window.removeEventListener("keydown", onKeyDown);
|
|
5805
|
+
window.addEventListener("keydown", onKeyDown, { capture: true });
|
|
5806
|
+
return () => window.removeEventListener("keydown", onKeyDown, { capture: true });
|
|
5417
5807
|
}, [isStreaming, handleInterrupt]);
|
|
5418
5808
|
const userHistory = useMemo12(
|
|
5419
5809
|
() => messages.filter((m) => m.role === "user").map((m) => m.parts.filter((p) => p.type === "text").map((p) => p.text).join("\n").trim()).filter(Boolean),
|
|
5420
5810
|
[messages]
|
|
5421
5811
|
);
|
|
5422
|
-
const textareaRef =
|
|
5812
|
+
const textareaRef = useRef14(null);
|
|
5423
5813
|
const promptInputController = useOptionalPromptInputController();
|
|
5424
|
-
const setComposerDraft =
|
|
5814
|
+
const setComposerDraft = useCallback17((draft, focus = true) => {
|
|
5425
5815
|
promptInputController?.textInput.setInput(draft);
|
|
5426
5816
|
if (textareaRef.current) {
|
|
5427
5817
|
textareaRef.current.value = draft;
|
|
@@ -5444,7 +5834,7 @@ ${formatPluginReloadDiagnostics(diagnostics)}` : base;
|
|
|
5444
5834
|
textareaRef,
|
|
5445
5835
|
disabled: mentionState !== null || slashQuery !== null
|
|
5446
5836
|
});
|
|
5447
|
-
const restoredDraftRef =
|
|
5837
|
+
const restoredDraftRef = useRef14(void 0);
|
|
5448
5838
|
useEffect18(() => {
|
|
5449
5839
|
if (initialDraft === void 0) return;
|
|
5450
5840
|
if (restoredDraftRef.current === initialDraft) return;
|
|
@@ -5473,6 +5863,7 @@ ${formatPluginReloadDiagnostics(diagnostics)}` : base;
|
|
|
5473
5863
|
if (parsed) {
|
|
5474
5864
|
const cmd = registry.get(parsed.name);
|
|
5475
5865
|
if (cmd?.kind === "skill") {
|
|
5866
|
+
dismissSlash();
|
|
5476
5867
|
const skillMessage = parsed.args ? `skill: ${parsed.name}
|
|
5477
5868
|
|
|
5478
5869
|
${parsed.args}` : `skill: ${parsed.name}`;
|
|
@@ -5484,6 +5875,7 @@ ${parsed.args}` : `skill: ${parsed.name}`;
|
|
|
5484
5875
|
return;
|
|
5485
5876
|
}
|
|
5486
5877
|
if (cmd) {
|
|
5878
|
+
dismissSlash();
|
|
5487
5879
|
const ctx = {
|
|
5488
5880
|
sessionId,
|
|
5489
5881
|
clearMessages: () => setMessages([]),
|
|
@@ -5568,7 +5960,7 @@ ${parsed.args}` : `skill: ${parsed.name}`;
|
|
|
5568
5960
|
pendingAutoSubmitUnlockRef.current = void 0;
|
|
5569
5961
|
onAutoSubmitInitialDraftAccepted?.();
|
|
5570
5962
|
}, [messages, onAutoSubmitInitialDraftAccepted, status]);
|
|
5571
|
-
const prevAutoSubmitStatusRef =
|
|
5963
|
+
const prevAutoSubmitStatusRef = useRef14(status);
|
|
5572
5964
|
useEffect18(() => {
|
|
5573
5965
|
const prev = prevAutoSubmitStatusRef.current;
|
|
5574
5966
|
prevAutoSubmitStatusRef.current = status;
|
|
@@ -5661,7 +6053,11 @@ ${parsed.args}` : `skill: ${parsed.name}`;
|
|
|
5661
6053
|
chrome ? "max-w-3xl px-6 py-8" : "max-w-[680px] px-4 py-4",
|
|
5662
6054
|
emptyHero && "py-4 text-center"
|
|
5663
6055
|
), children: [
|
|
5664
|
-
renderMessages.length === 0 && /* @__PURE__ */
|
|
6056
|
+
renderMessages.length === 0 && hydratingMessages && /* @__PURE__ */ jsxs23("div", { className: "flex items-center gap-2 rounded-xl border border-border bg-card/70 px-4 py-3 text-sm text-muted-foreground", children: [
|
|
6057
|
+
/* @__PURE__ */ jsx25(Loader2, { className: "h-4 w-4 animate-spin" }),
|
|
6058
|
+
"Loading chat history\u2026"
|
|
6059
|
+
] }),
|
|
6060
|
+
renderMessages.length === 0 && !hydratingMessages && /* @__PURE__ */ jsx25(
|
|
5665
6061
|
ChatEmptyState,
|
|
5666
6062
|
{
|
|
5667
6063
|
eyebrow: emptyState?.eyebrow,
|
|
@@ -5923,13 +6319,12 @@ ${reasoningPart.text}`;
|
|
|
5923
6319
|
}
|
|
5924
6320
|
),
|
|
5925
6321
|
/* @__PURE__ */ jsxs23("div", { className: cn(chrome ? "px-4 pb-4 pt-2 sm:px-6 sm:pb-5" : "px-3 pb-3 pt-1"), children: [
|
|
5926
|
-
/* @__PURE__ */ jsx25(
|
|
6322
|
+
(isStreaming || hostComposerBlocked && !workspaceWarmupBlocked) && /* @__PURE__ */ jsx25(
|
|
5927
6323
|
"div",
|
|
5928
6324
|
{
|
|
5929
6325
|
className: cn(
|
|
5930
|
-
"mx-auto w-full overflow-hidden transition-all duration-300",
|
|
5931
|
-
chrome ? "max-w-3xl" : "max-w-[680px]"
|
|
5932
|
-
isStreaming ? "mb-2 max-h-8" : "max-h-0"
|
|
6326
|
+
"mx-auto mb-2 w-full overflow-hidden transition-all duration-300 max-h-8",
|
|
6327
|
+
chrome ? "max-w-3xl" : "max-w-[680px]"
|
|
5933
6328
|
),
|
|
5934
6329
|
children: /* @__PURE__ */ jsxs23(
|
|
5935
6330
|
"div",
|
|
@@ -5950,7 +6345,7 @@ ${reasoningPart.text}`;
|
|
|
5950
6345
|
transition: { duration: 1.2, repeat: Infinity, ease: "easeInOut" }
|
|
5951
6346
|
}
|
|
5952
6347
|
),
|
|
5953
|
-
/* @__PURE__ */ jsx25("span", { children:
|
|
6348
|
+
/* @__PURE__ */ jsx25("span", { children: activityBadgeLabel })
|
|
5954
6349
|
]
|
|
5955
6350
|
}
|
|
5956
6351
|
)
|
|
@@ -5975,28 +6370,34 @@ ${reasoningPart.text}`;
|
|
|
5975
6370
|
] })
|
|
5976
6371
|
}
|
|
5977
6372
|
),
|
|
5978
|
-
composerBlocked && !workspaceWarmupBlocked && /* @__PURE__ */
|
|
6373
|
+
composerBlocked && !workspaceWarmupBlocked && /* @__PURE__ */ jsx25(
|
|
5979
6374
|
"div",
|
|
5980
6375
|
{
|
|
5981
6376
|
role: "status",
|
|
5982
6377
|
"aria-live": "polite",
|
|
5983
6378
|
className: cn(
|
|
5984
|
-
"mx-auto mb-2 w-full
|
|
5985
|
-
"px-3 py-2 text-xs text-foreground"
|
|
6379
|
+
"mx-auto mb-2 w-full rounded-[var(--radius-md)] border border-primary/30 bg-primary/10",
|
|
6380
|
+
"px-3 py-2 text-xs text-foreground",
|
|
6381
|
+
chrome ? "max-w-3xl" : "max-w-[680px]"
|
|
5986
6382
|
),
|
|
5987
|
-
children: [
|
|
5988
|
-
/* @__PURE__ */ jsx25("span", { children: composerBlockerLabel }),
|
|
5989
|
-
primaryComposerBlocker?.actions?.map((action) =>
|
|
5990
|
-
"
|
|
5991
|
-
|
|
5992
|
-
|
|
5993
|
-
|
|
5994
|
-
|
|
5995
|
-
|
|
5996
|
-
|
|
5997
|
-
|
|
5998
|
-
|
|
5999
|
-
|
|
6383
|
+
children: /* @__PURE__ */ jsxs23("div", { className: "flex items-center gap-2", children: [
|
|
6384
|
+
/* @__PURE__ */ jsx25("span", { className: "min-w-0 flex-1 truncate", children: composerBlockerLabel }),
|
|
6385
|
+
primaryComposerBlocker?.actions?.map((action) => {
|
|
6386
|
+
const ActionIcon = action.id === "open" ? SquareArrowOutUpRight : action.id === "cancel" ? XIcon4 : null;
|
|
6387
|
+
return /* @__PURE__ */ jsx25(
|
|
6388
|
+
"button",
|
|
6389
|
+
{
|
|
6390
|
+
type: "button",
|
|
6391
|
+
"aria-label": action.label,
|
|
6392
|
+
title: action.label,
|
|
6393
|
+
className: "inline-flex size-6 shrink-0 items-center justify-center rounded border border-primary/30 text-primary hover:bg-primary/10",
|
|
6394
|
+
onClick: () => onComposerBlockerAction?.(primaryComposerBlocker, action.id),
|
|
6395
|
+
children: ActionIcon ? /* @__PURE__ */ jsx25(ActionIcon, { "aria-hidden": "true", className: "size-3.5" }) : /* @__PURE__ */ jsx25("span", { className: "px-1 text-[11px] font-medium", children: action.label })
|
|
6396
|
+
},
|
|
6397
|
+
action.id
|
|
6398
|
+
);
|
|
6399
|
+
})
|
|
6400
|
+
] })
|
|
6000
6401
|
}
|
|
6001
6402
|
),
|
|
6002
6403
|
hotReloadEnabled && /* @__PURE__ */ jsx25(
|
|
@@ -6004,7 +6405,8 @@ ${reasoningPart.text}`;
|
|
|
6004
6405
|
{
|
|
6005
6406
|
state: pluginUpdateState,
|
|
6006
6407
|
onDismiss: dismissPluginUpdate,
|
|
6007
|
-
onRetry: runPluginUpdate
|
|
6408
|
+
onRetry: runPluginUpdate,
|
|
6409
|
+
maxWidthClassName: chrome ? "max-w-3xl" : "max-w-[680px]"
|
|
6008
6410
|
}
|
|
6009
6411
|
),
|
|
6010
6412
|
attachmentNotice && /* @__PURE__ */ jsx25(
|
|
@@ -6149,9 +6551,9 @@ ${reasoningPart.text}`;
|
|
|
6149
6551
|
/* @__PURE__ */ jsx25(
|
|
6150
6552
|
PromptInputSubmit,
|
|
6151
6553
|
{
|
|
6152
|
-
status,
|
|
6554
|
+
status: composerSubmitStatus,
|
|
6153
6555
|
onStop: handleStop,
|
|
6154
|
-
disabled:
|
|
6556
|
+
disabled: workspaceWarmupBlocked,
|
|
6155
6557
|
className: cn(
|
|
6156
6558
|
// Primary action. Uses the warm accent (not `primary`,
|
|
6157
6559
|
// which is a neutral foreground tone) — this is the one
|
|
@@ -6366,30 +6768,8 @@ function MessageActionsBar({
|
|
|
6366
6768
|
};
|
|
6367
6769
|
const handleCopy = async () => {
|
|
6368
6770
|
if (!visible) return;
|
|
6369
|
-
|
|
6370
|
-
|
|
6371
|
-
await navigator.clipboard.writeText(text);
|
|
6372
|
-
markCopied();
|
|
6373
|
-
return;
|
|
6374
|
-
} catch {
|
|
6375
|
-
}
|
|
6376
|
-
}
|
|
6377
|
-
if (typeof document === "undefined") return;
|
|
6378
|
-
const ta = document.createElement("textarea");
|
|
6379
|
-
ta.value = text;
|
|
6380
|
-
ta.setAttribute("readonly", "");
|
|
6381
|
-
ta.style.position = "fixed";
|
|
6382
|
-
ta.style.opacity = "0";
|
|
6383
|
-
ta.style.pointerEvents = "none";
|
|
6384
|
-
document.body.appendChild(ta);
|
|
6385
|
-
ta.select();
|
|
6386
|
-
try {
|
|
6387
|
-
const ok = document.execCommand("copy");
|
|
6388
|
-
if (ok) markCopied();
|
|
6389
|
-
} catch {
|
|
6390
|
-
} finally {
|
|
6391
|
-
document.body.removeChild(ta);
|
|
6392
|
-
}
|
|
6771
|
+
const ok = await copyTextToClipboard(text);
|
|
6772
|
+
if (ok) markCopied();
|
|
6393
6773
|
};
|
|
6394
6774
|
const iconActionBtnClass = cn(
|
|
6395
6775
|
"inline-flex h-6 w-6 items-center justify-center rounded-[var(--radius-sm)]",
|
|
@@ -6453,7 +6833,7 @@ function getAgentCommands(options = {}) {
|
|
|
6453
6833
|
}
|
|
6454
6834
|
|
|
6455
6835
|
// src/front/hooks/useSessions.ts
|
|
6456
|
-
import { useState as useState21, useEffect as useEffect19, useCallback as
|
|
6836
|
+
import { useState as useState21, useEffect as useEffect19, useCallback as useCallback18, useMemo as useMemo13, useRef as useRef15 } from "react";
|
|
6457
6837
|
var API_BASE = "/api/v1/agent/sessions";
|
|
6458
6838
|
var STORAGE_KEY = "boring-agent:activeSessionId";
|
|
6459
6839
|
function readPersistedId(storageKey) {
|
|
@@ -6477,9 +6857,23 @@ function requestInit(headers) {
|
|
|
6477
6857
|
if (!headers || Object.keys(headers).length === 0) return void 0;
|
|
6478
6858
|
return { headers };
|
|
6479
6859
|
}
|
|
6860
|
+
var SessionsPreparingError = class extends Error {
|
|
6861
|
+
constructor() {
|
|
6862
|
+
super("Agent runtime is still preparing");
|
|
6863
|
+
this.name = "SessionsPreparingError";
|
|
6864
|
+
}
|
|
6865
|
+
};
|
|
6866
|
+
var MAX_SESSIONS_RETRIES = 8;
|
|
6867
|
+
function retryDelayMs(attempt) {
|
|
6868
|
+
return Math.min(250 * 2 ** attempt, 2e3);
|
|
6869
|
+
}
|
|
6870
|
+
function delay(ms) {
|
|
6871
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
6872
|
+
}
|
|
6480
6873
|
async function fetchSessions(headers) {
|
|
6481
6874
|
const init = requestInit(headers);
|
|
6482
6875
|
const res = init ? await fetch(API_BASE, init) : await fetch(API_BASE);
|
|
6876
|
+
if (res.status === 503) throw new SessionsPreparingError();
|
|
6483
6877
|
if (!res.ok) throw new Error(`Failed to load sessions: ${res.status}`);
|
|
6484
6878
|
return res.json();
|
|
6485
6879
|
}
|
|
@@ -6488,6 +6882,7 @@ function useSessions(opts = {}) {
|
|
|
6488
6882
|
const requestHeaders = opts.requestHeaders;
|
|
6489
6883
|
const enabled = opts.enabled ?? true;
|
|
6490
6884
|
const refreshKey = opts.refreshKey;
|
|
6885
|
+
const initialActiveSessionId = opts.initialActiveSessionId;
|
|
6491
6886
|
const scopeKey = useMemo13(
|
|
6492
6887
|
() => `${storageKey}
|
|
6493
6888
|
${headersScopeKey(requestHeaders)}`,
|
|
@@ -6495,15 +6890,24 @@ ${headersScopeKey(requestHeaders)}`,
|
|
|
6495
6890
|
);
|
|
6496
6891
|
const [sessions, setSessions] = useState21([]);
|
|
6497
6892
|
const [activeSessionId, setActiveSessionId] = useState21(
|
|
6498
|
-
() => readPersistedId(storageKey)
|
|
6893
|
+
() => initialActiveSessionId ?? readPersistedId(storageKey)
|
|
6499
6894
|
);
|
|
6500
6895
|
const [loading, setLoading] = useState21(true);
|
|
6501
6896
|
const [error, setError] = useState21();
|
|
6502
6897
|
const [loaded, setLoaded] = useState21(false);
|
|
6503
|
-
const versionRef =
|
|
6504
|
-
const loadedScopeRef =
|
|
6505
|
-
const
|
|
6898
|
+
const versionRef = useRef15(0);
|
|
6899
|
+
const loadedScopeRef = useRef15(scopeKey);
|
|
6900
|
+
const pendingCreatedSessionsRef = useRef15(/* @__PURE__ */ new Map());
|
|
6901
|
+
const pendingCreatedScopeRef = useRef15(scopeKey);
|
|
6902
|
+
const mountedRef = useRef15(true);
|
|
6903
|
+
function ensurePendingCreatedScope() {
|
|
6904
|
+
if (pendingCreatedScopeRef.current === scopeKey) return;
|
|
6905
|
+
pendingCreatedScopeRef.current = scopeKey;
|
|
6906
|
+
pendingCreatedSessionsRef.current.clear();
|
|
6907
|
+
}
|
|
6908
|
+
const refresh = useCallback18(async () => {
|
|
6506
6909
|
const v = ++versionRef.current;
|
|
6910
|
+
const isCurrent = () => v === versionRef.current && mountedRef.current;
|
|
6507
6911
|
if (!enabled) {
|
|
6508
6912
|
setSessions([]);
|
|
6509
6913
|
setActiveSessionId(void 0);
|
|
@@ -6515,25 +6919,45 @@ ${headersScopeKey(requestHeaders)}`,
|
|
|
6515
6919
|
setLoaded(false);
|
|
6516
6920
|
setLoading(true);
|
|
6517
6921
|
try {
|
|
6518
|
-
|
|
6519
|
-
|
|
6922
|
+
let data;
|
|
6923
|
+
for (let attempt = 0; ; attempt++) {
|
|
6924
|
+
try {
|
|
6925
|
+
data = await fetchSessions(requestHeaders);
|
|
6926
|
+
break;
|
|
6927
|
+
} catch (err) {
|
|
6928
|
+
const retryable = err instanceof SessionsPreparingError && attempt < MAX_SESSIONS_RETRIES;
|
|
6929
|
+
if (!retryable) throw err;
|
|
6930
|
+
if (!isCurrent()) return;
|
|
6931
|
+
await delay(retryDelayMs(attempt));
|
|
6932
|
+
if (!isCurrent()) return;
|
|
6933
|
+
}
|
|
6934
|
+
}
|
|
6935
|
+
if (isCurrent() && data) {
|
|
6936
|
+
ensurePendingCreatedScope();
|
|
6937
|
+
const pendingCreatedSessions = pendingCreatedSessionsRef.current;
|
|
6938
|
+
for (const session of data) pendingCreatedSessions.delete(session.id);
|
|
6939
|
+
const serverIds = new Set(data.map((session) => session.id));
|
|
6940
|
+
const mergedData = pendingCreatedSessions.size > 0 ? [
|
|
6941
|
+
...Array.from(pendingCreatedSessions.values()).filter((session) => !serverIds.has(session.id)),
|
|
6942
|
+
...data
|
|
6943
|
+
] : data;
|
|
6520
6944
|
const replacingLoadedScope = loadedScopeRef.current !== scopeKey;
|
|
6521
6945
|
loadedScopeRef.current = scopeKey;
|
|
6522
|
-
const persisted = readPersistedId(storageKey);
|
|
6946
|
+
const persisted = initialActiveSessionId ?? readPersistedId(storageKey);
|
|
6523
6947
|
setError(void 0);
|
|
6524
6948
|
setLoaded(true);
|
|
6525
|
-
setSessions(
|
|
6949
|
+
setSessions(mergedData);
|
|
6526
6950
|
setActiveSessionId((prev) => {
|
|
6527
6951
|
const preferred = replacingLoadedScope ? persisted : prev ?? persisted;
|
|
6528
|
-
if (preferred &&
|
|
6529
|
-
const next =
|
|
6952
|
+
if (preferred && mergedData.some((session) => session.id === preferred)) return preferred;
|
|
6953
|
+
const next = mergedData[0]?.id;
|
|
6530
6954
|
persistId(storageKey, next);
|
|
6531
6955
|
return next;
|
|
6532
6956
|
});
|
|
6533
6957
|
setLoading(false);
|
|
6534
6958
|
}
|
|
6535
6959
|
} catch (err) {
|
|
6536
|
-
if (
|
|
6960
|
+
if (isCurrent()) {
|
|
6537
6961
|
const replacingLoadedScope = loadedScopeRef.current !== scopeKey;
|
|
6538
6962
|
loadedScopeRef.current = scopeKey;
|
|
6539
6963
|
if (replacingLoadedScope) {
|
|
@@ -6545,8 +6969,9 @@ ${headersScopeKey(requestHeaders)}`,
|
|
|
6545
6969
|
setLoading(false);
|
|
6546
6970
|
}
|
|
6547
6971
|
}
|
|
6548
|
-
}, [enabled, requestHeaders, scopeKey, storageKey]);
|
|
6972
|
+
}, [enabled, initialActiveSessionId, requestHeaders, scopeKey, storageKey]);
|
|
6549
6973
|
useEffect19(() => {
|
|
6974
|
+
mountedRef.current = true;
|
|
6550
6975
|
if (!enabled) {
|
|
6551
6976
|
setSessions([]);
|
|
6552
6977
|
setActiveSessionId(void 0);
|
|
@@ -6556,8 +6981,11 @@ ${headersScopeKey(requestHeaders)}`,
|
|
|
6556
6981
|
return;
|
|
6557
6982
|
}
|
|
6558
6983
|
void refresh();
|
|
6984
|
+
return () => {
|
|
6985
|
+
mountedRef.current = false;
|
|
6986
|
+
};
|
|
6559
6987
|
}, [enabled, refresh, refreshKey, scopeKey]);
|
|
6560
|
-
const create =
|
|
6988
|
+
const create = useCallback18(
|
|
6561
6989
|
async (init) => {
|
|
6562
6990
|
if (!enabled) throw new Error("Sessions are disabled");
|
|
6563
6991
|
const res = await fetch(API_BASE, {
|
|
@@ -6571,21 +6999,25 @@ ${headersScopeKey(requestHeaders)}`,
|
|
|
6571
6999
|
throw err;
|
|
6572
7000
|
}
|
|
6573
7001
|
const session = await res.json();
|
|
6574
|
-
|
|
7002
|
+
ensurePendingCreatedScope();
|
|
7003
|
+
pendingCreatedSessionsRef.current.set(session.id, session);
|
|
7004
|
+
setSessions((prev) => [session, ...prev.filter((existing) => existing.id !== session.id)]);
|
|
6575
7005
|
setActiveSessionId(session.id);
|
|
6576
7006
|
persistId(storageKey, session.id);
|
|
6577
7007
|
void refresh();
|
|
6578
7008
|
return session;
|
|
6579
7009
|
},
|
|
6580
|
-
[enabled, refresh, requestHeaders, storageKey]
|
|
7010
|
+
[enabled, refresh, requestHeaders, scopeKey, storageKey]
|
|
6581
7011
|
);
|
|
6582
|
-
const switchSession =
|
|
7012
|
+
const switchSession = useCallback18((id) => {
|
|
6583
7013
|
setActiveSessionId(id);
|
|
6584
7014
|
persistId(storageKey, id);
|
|
6585
7015
|
}, [storageKey]);
|
|
6586
|
-
const deleteSession =
|
|
7016
|
+
const deleteSession = useCallback18(
|
|
6587
7017
|
async (id) => {
|
|
6588
7018
|
if (!enabled) throw new Error("Sessions are disabled");
|
|
7019
|
+
ensurePendingCreatedScope();
|
|
7020
|
+
pendingCreatedSessionsRef.current.delete(id);
|
|
6589
7021
|
setSessions((prev) => prev.filter((s) => s.id !== id));
|
|
6590
7022
|
setActiveSessionId((prev) => {
|
|
6591
7023
|
if (prev === id) {
|
|
@@ -6609,7 +7041,7 @@ ${headersScopeKey(requestHeaders)}`,
|
|
|
6609
7041
|
}
|
|
6610
7042
|
void refresh();
|
|
6611
7043
|
},
|
|
6612
|
-
[enabled, refresh, requestHeaders, storageKey]
|
|
7044
|
+
[enabled, refresh, requestHeaders, scopeKey, storageKey]
|
|
6613
7045
|
);
|
|
6614
7046
|
const scopeMatches = loadedScopeRef.current === scopeKey;
|
|
6615
7047
|
const visibleSessions = enabled && scopeMatches ? sessions : [];
|