@tangle-network/agent-app 0.43.40 → 0.43.42
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/assistant/index.d.ts +3 -2
- package/dist/assistant/index.js +4 -3
- package/dist/assistant/index.js.map +1 -1
- package/dist/chat-routes/index.d.ts +19 -5
- package/dist/chat-routes/index.js +100 -7
- package/dist/chat-routes/index.js.map +1 -1
- package/dist/chat-store/index.d.ts +4 -3
- package/dist/chat-store/index.js +5 -1
- package/dist/chat-store/index.js.map +1 -1
- package/dist/chunk-2EO7CPL3.js +191 -0
- package/dist/chunk-2EO7CPL3.js.map +1 -0
- package/dist/{chunk-Y4QHNQ75.js → chunk-6KVH5SP5.js} +110 -6
- package/dist/chunk-6KVH5SP5.js.map +1 -0
- package/dist/{chunk-XYWCGFII.js → chunk-B5JD3DXD.js} +48 -2
- package/dist/chunk-B5JD3DXD.js.map +1 -0
- package/dist/{chunk-I2R2XT4M.js → chunk-I2ATYB7R.js} +18 -2
- package/dist/chunk-I2ATYB7R.js.map +1 -0
- package/dist/chunk-SIXYZ2FB.js +101 -0
- package/dist/chunk-SIXYZ2FB.js.map +1 -0
- package/dist/{chunk-4TXDD6P2.js → chunk-XAWFPMAR.js} +38 -3
- package/dist/chunk-XAWFPMAR.js.map +1 -0
- package/dist/{chunk-D4HB72W2.js → chunk-ZLHK25C3.js} +1890 -1213
- package/dist/chunk-ZLHK25C3.js.map +1 -0
- package/dist/chunk-ZU5GNSOJ.js +920 -0
- package/dist/chunk-ZU5GNSOJ.js.map +1 -0
- package/dist/{contract-DYbTzEDf.d.ts → contract-KfqJh_au.d.ts} +23 -2
- package/dist/design-canvas-react/index.js +4 -4
- package/dist/durable-chat/index.d.ts +419 -0
- package/dist/durable-chat/index.js +59 -0
- package/dist/durable-chat/index.js.map +1 -0
- package/dist/file-index-Bw_IQE_G.d.ts +194 -0
- package/dist/index.d.ts +5 -3
- package/dist/index.js +123 -45
- package/dist/interactions/index.d.ts +58 -3
- package/dist/interactions/index.js +7 -3
- package/dist/{parts-BcbitSNp.d.ts → parts-DjX0RRTS.d.ts} +9 -3
- package/dist/plans/index.d.ts +69 -0
- package/dist/plans/index.js +21 -0
- package/dist/plans/index.js.map +1 -0
- package/dist/stream/index.js +3 -1
- package/dist/teams/index.js +9 -9
- package/dist/teams/invitations-api.js +3 -3
- package/dist/web-react/index.d.ts +345 -94
- package/dist/web-react/index.js +50 -5
- package/package.json +11 -1
- package/dist/chunk-4TXDD6P2.js.map +0 -1
- package/dist/chunk-5SV5PSU7.js +0 -80
- package/dist/chunk-5SV5PSU7.js.map +0 -1
- package/dist/chunk-D4HB72W2.js.map +0 -1
- package/dist/chunk-I2R2XT4M.js.map +0 -1
- package/dist/chunk-XYWCGFII.js.map +0 -1
- package/dist/chunk-Y4QHNQ75.js.map +0 -1
- package/dist/wire-BaUF66AS.d.ts +0 -61
|
@@ -6,16 +6,22 @@ import {
|
|
|
6
6
|
fieldAcceptsFreeText,
|
|
7
7
|
interactionFromWireRequest,
|
|
8
8
|
isTerminalInteractionStatus,
|
|
9
|
+
parseInteractionCancel,
|
|
9
10
|
parseInteractionRequest,
|
|
11
|
+
persistedPartToInteraction,
|
|
10
12
|
questionInteractionContentSignature
|
|
11
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-XAWFPMAR.js";
|
|
14
|
+
import {
|
|
15
|
+
parsePlanSubmittedEvent,
|
|
16
|
+
persistedPartToPlan
|
|
17
|
+
} from "./chunk-SIXYZ2FB.js";
|
|
12
18
|
import {
|
|
13
19
|
snapHarnessToModel,
|
|
14
20
|
snapModelToHarness
|
|
15
21
|
} from "./chunk-E7QYOOON.js";
|
|
16
22
|
|
|
17
23
|
// src/web-react/index.tsx
|
|
18
|
-
import { useEffect as
|
|
24
|
+
import { useEffect as useEffect8, useMemo as useMemo7, useRef as useRef8, useState as useState12, memo } from "react";
|
|
19
25
|
|
|
20
26
|
// src/web-react/smooth-text.ts
|
|
21
27
|
import { useEffect, useRef, useState } from "react";
|
|
@@ -372,1003 +378,1484 @@ function BrandMark({ size = 24, className }) {
|
|
|
372
378
|
return /* @__PURE__ */ jsx3(Suspense, { fallback: /* @__PURE__ */ jsx3(MarkSpacer, { size, className }), children: /* @__PURE__ */ jsx3(LazyKnot, { size, className }) });
|
|
373
379
|
}
|
|
374
380
|
|
|
375
|
-
// src/web-react/
|
|
376
|
-
|
|
377
|
-
let receivedContent = false;
|
|
378
|
-
let turnId;
|
|
379
|
-
if (!line.trim()) return { receivedContent };
|
|
380
|
-
let parsed;
|
|
381
|
-
try {
|
|
382
|
-
parsed = JSON.parse(line);
|
|
383
|
-
} catch {
|
|
384
|
-
return { receivedContent };
|
|
385
|
-
}
|
|
386
|
-
if (parsed.kind === "tool_result") {
|
|
387
|
-
cb.onToolResult?.({
|
|
388
|
-
toolCallId: parsed.toolCallId,
|
|
389
|
-
toolName: parsed.toolName,
|
|
390
|
-
label: parsed.label,
|
|
391
|
-
outcome: parsed.outcome ?? parsed.result
|
|
392
|
-
});
|
|
393
|
-
return { receivedContent: true };
|
|
394
|
-
}
|
|
395
|
-
const evt = parsed.kind === "event" ? parsed.event : parsed;
|
|
396
|
-
if (!evt || typeof evt !== "object") return { receivedContent };
|
|
397
|
-
switch (evt.type) {
|
|
398
|
-
case "turn":
|
|
399
|
-
if (typeof evt.turnId === "string") turnId = evt.turnId;
|
|
400
|
-
break;
|
|
401
|
-
case "text":
|
|
402
|
-
if (typeof evt.text === "string") {
|
|
403
|
-
cb.onText?.(evt.text);
|
|
404
|
-
receivedContent = true;
|
|
405
|
-
}
|
|
406
|
-
break;
|
|
407
|
-
case "reasoning":
|
|
408
|
-
if (typeof evt.text === "string") {
|
|
409
|
-
cb.onReasoning?.(evt.text);
|
|
410
|
-
receivedContent = true;
|
|
411
|
-
}
|
|
412
|
-
break;
|
|
413
|
-
case "tool_call": {
|
|
414
|
-
const call = evt.call ?? evt;
|
|
415
|
-
cb.onToolCall?.({
|
|
416
|
-
toolCallId: call.toolCallId ?? call.id,
|
|
417
|
-
toolName: String(call.toolName ?? call.name ?? "unknown"),
|
|
418
|
-
args: call.args ?? {}
|
|
419
|
-
});
|
|
420
|
-
receivedContent = true;
|
|
421
|
-
break;
|
|
422
|
-
}
|
|
423
|
-
case "tool_result":
|
|
424
|
-
cb.onToolResult?.({
|
|
425
|
-
toolCallId: evt.toolCallId,
|
|
426
|
-
toolName: evt.toolName,
|
|
427
|
-
label: evt.label,
|
|
428
|
-
outcome: evt.outcome ?? evt.result
|
|
429
|
-
});
|
|
430
|
-
receivedContent = true;
|
|
431
|
-
break;
|
|
432
|
-
case "usage": {
|
|
433
|
-
const u = evt.usage;
|
|
434
|
-
if (u) cb.onUsage?.({ promptTokens: u.promptTokens ?? 0, completionTokens: u.completionTokens ?? 0 });
|
|
435
|
-
break;
|
|
436
|
-
}
|
|
437
|
-
case "metadata":
|
|
438
|
-
cb.onMetadata?.(evt.data ?? {});
|
|
439
|
-
break;
|
|
440
|
-
case "interaction": {
|
|
441
|
-
const parsed2 = parseInteractionRequest(evt.data);
|
|
442
|
-
if (parsed2.succeeded) {
|
|
443
|
-
cb.onInteraction?.(interactionFromWireRequest(parsed2.value));
|
|
444
|
-
receivedContent = true;
|
|
445
|
-
} else {
|
|
446
|
-
console.error("[chat-stream] dropping malformed interaction line:", parsed2.error);
|
|
447
|
-
}
|
|
448
|
-
break;
|
|
449
|
-
}
|
|
450
|
-
case "error": {
|
|
451
|
-
const data = evt.data;
|
|
452
|
-
const message = String(data?.message ?? evt.details ?? evt.error ?? "Unknown stream error");
|
|
453
|
-
if (cb.onErrorEvent) {
|
|
454
|
-
cb.onErrorEvent(message);
|
|
455
|
-
} else {
|
|
456
|
-
console.error("[chat-stream] unhandled stream error event:", message);
|
|
457
|
-
cb.onText?.(`
|
|
381
|
+
// src/web-react/durable-plan-card.tsx
|
|
382
|
+
import { useEffect as useEffect4, useState as useState4 } from "react";
|
|
458
383
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
break;
|
|
466
|
-
}
|
|
467
|
-
return { turnId, receivedContent };
|
|
384
|
+
// src/web-react/interaction-question-card.tsx
|
|
385
|
+
import { useEffect as useEffect3, useMemo as useMemo2, useRef as useRef3, useState as useState3 } from "react";
|
|
386
|
+
|
|
387
|
+
// src/web-react/interaction-card-support.ts
|
|
388
|
+
function interactionStatusLabels(labels) {
|
|
389
|
+
return { cancelled: "Withdrawn", expired: "Expired", ...labels };
|
|
468
390
|
}
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
let receivedContent = false;
|
|
475
|
-
const handle = (line) => {
|
|
476
|
-
const r = dispatchChatStreamLine(line, cb);
|
|
477
|
-
if (r.turnId) {
|
|
478
|
-
turnId = r.turnId;
|
|
479
|
-
cb.onTurnId?.(r.turnId);
|
|
480
|
-
}
|
|
481
|
-
if (r.receivedContent) receivedContent = true;
|
|
391
|
+
function interactionTerminalNotes(noun, extra) {
|
|
392
|
+
return {
|
|
393
|
+
expired: `This ${noun} expired \u2014 send a new message to continue.`,
|
|
394
|
+
cancelled: `The agent withdrew this ${noun}.`,
|
|
395
|
+
...extra
|
|
482
396
|
};
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
397
|
+
}
|
|
398
|
+
function fieldValuesFromAnswers(fields, answers) {
|
|
399
|
+
if (!answers) return {};
|
|
400
|
+
const values = {};
|
|
401
|
+
for (const field of fields) {
|
|
402
|
+
const answer = answers[field.name];
|
|
403
|
+
if (answer === void 0) continue;
|
|
404
|
+
if (field.type === "select") {
|
|
405
|
+
values[field.name] = { selected: Array.isArray(answer) ? [...answer] : [String(answer)] };
|
|
406
|
+
} else if (field.type === "boolean") {
|
|
407
|
+
values[field.name] = { selected: [String(answer)] };
|
|
408
|
+
} else {
|
|
409
|
+
values[field.name] = { text: String(answer) };
|
|
488
410
|
}
|
|
489
|
-
buffer += decoder.decode(value, { stream: true });
|
|
490
|
-
const lines = buffer.split("\n");
|
|
491
|
-
buffer = lines.pop() ?? "";
|
|
492
|
-
for (const line of lines) handle(line);
|
|
493
411
|
}
|
|
494
|
-
return
|
|
412
|
+
return values;
|
|
495
413
|
}
|
|
496
|
-
|
|
497
|
-
const
|
|
498
|
-
if (
|
|
499
|
-
const
|
|
500
|
-
|
|
414
|
+
function fieldAnswer(field, values) {
|
|
415
|
+
const value = values[field.name] ?? {};
|
|
416
|
+
if (field.type === "select") {
|
|
417
|
+
const custom = field.allowCustom === true ? value.custom?.trim() : void 0;
|
|
418
|
+
const chosen = [...value.selected ?? [], ...custom ? [custom] : []];
|
|
419
|
+
if (field.multi !== true && custom) return [custom];
|
|
420
|
+
return chosen.length > 0 ? chosen : null;
|
|
501
421
|
}
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
422
|
+
if (field.type === "number") {
|
|
423
|
+
const parsed = Number(value.text);
|
|
424
|
+
return value.text?.trim() && Number.isFinite(parsed) ? parsed : null;
|
|
425
|
+
}
|
|
426
|
+
if (field.type === "boolean") return value.selected ? value.selected[0] === "true" : null;
|
|
427
|
+
const text = value.text?.trim();
|
|
428
|
+
return text ? text : null;
|
|
429
|
+
}
|
|
430
|
+
function buildAnswerData(fields, values) {
|
|
431
|
+
const data = {};
|
|
432
|
+
for (const field of fields) {
|
|
433
|
+
const answer = fieldAnswer(field, values);
|
|
434
|
+
if (answer === null) {
|
|
435
|
+
if (field.required === false) continue;
|
|
436
|
+
return null;
|
|
508
437
|
}
|
|
509
|
-
|
|
510
|
-
try {
|
|
511
|
-
return await consumeChatStream(res.body, cb);
|
|
512
|
-
} catch (transportErr) {
|
|
513
|
-
if (!turnId || !opts.resume) throw transportErr;
|
|
514
|
-
opts.onResetForResume?.();
|
|
515
|
-
const resumed = await opts.resume(turnId, 0);
|
|
516
|
-
if (!resumed.ok || !resumed.body) throw transportErr;
|
|
517
|
-
return await consumeChatStream(resumed.body, cb);
|
|
438
|
+
data[field.name] = answer;
|
|
518
439
|
}
|
|
440
|
+
return data;
|
|
519
441
|
}
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
import {
|
|
523
|
-
useCallback,
|
|
524
|
-
useEffect as useEffect3,
|
|
525
|
-
useRef as useRef3,
|
|
526
|
-
useState as useState3
|
|
527
|
-
} from "react";
|
|
528
|
-
import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
529
|
-
function SendGlyph({ className }) {
|
|
530
|
-
return /* @__PURE__ */ jsx4("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: /* @__PURE__ */ jsx4("path", { d: "M22 2 11 13M22 2l-7 20-4-9-9-4 20-7z" }) });
|
|
442
|
+
function isLateAnswerableStatus(status) {
|
|
443
|
+
return status === "expired" || status === "cancelled";
|
|
531
444
|
}
|
|
532
|
-
function
|
|
533
|
-
return
|
|
445
|
+
function hasSecretField(fields) {
|
|
446
|
+
return fields.some((field) => field.type === "secret");
|
|
534
447
|
}
|
|
535
|
-
function
|
|
536
|
-
return
|
|
448
|
+
function optionLabel(field, value) {
|
|
449
|
+
return field.options.find((option) => option.value === value)?.label ?? value;
|
|
537
450
|
}
|
|
538
|
-
function
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
451
|
+
function answerText(field, answer) {
|
|
452
|
+
if (field.type === "select" && Array.isArray(answer)) {
|
|
453
|
+
return answer.map((value) => optionLabel(field, value)).join(", ");
|
|
454
|
+
}
|
|
455
|
+
if (field.type === "boolean") return answer === true ? "Yes" : "No";
|
|
456
|
+
if (field.type === "secret") return "[secret omitted]";
|
|
457
|
+
return String(answer);
|
|
543
458
|
}
|
|
544
|
-
function
|
|
545
|
-
|
|
459
|
+
function lateAnswerMessage(interaction, data) {
|
|
460
|
+
const title = interaction.title.trim() || "the earlier question";
|
|
461
|
+
const body = interaction.body?.trim();
|
|
462
|
+
const answers = interaction.fields.map((field) => {
|
|
463
|
+
const answer = data[field.name];
|
|
464
|
+
if (answer === void 0) return null;
|
|
465
|
+
return { label: field.label.trim(), text: answerText(field, answer).trim() };
|
|
466
|
+
}).filter((item) => !!item && item.text.length > 0);
|
|
467
|
+
const only = answers.length === 1 ? answers[0] : void 0;
|
|
468
|
+
const answerSummary = only ? only.text : answers.map((item) => `${item.label || "Answer"}: ${item.text}`).join("\n");
|
|
469
|
+
return [
|
|
470
|
+
`Regarding your earlier question: "${title}"`,
|
|
471
|
+
body ? `Context: ${body}` : null,
|
|
472
|
+
`My answer: ${answerSummary}`
|
|
473
|
+
].filter((line) => !!line).join("\n");
|
|
546
474
|
}
|
|
547
|
-
|
|
548
|
-
|
|
475
|
+
var INTERACTION_SUBMIT_TIMEOUT_MS = 3e4;
|
|
476
|
+
var INTERACTION_SUBMIT_TIMEOUT_MESSAGE = "Could not reach the agent. Try again.";
|
|
477
|
+
async function responseErrorMessage(res) {
|
|
478
|
+
const text = await res.text().catch(() => "");
|
|
479
|
+
if (text) {
|
|
480
|
+
try {
|
|
481
|
+
const parsed = JSON.parse(text);
|
|
482
|
+
const message = typeof parsed.error === "string" && parsed.error.trim() ? parsed.error : typeof parsed.message === "string" && parsed.message.trim() ? parsed.message : null;
|
|
483
|
+
if (message) return { ...typeof parsed.code === "string" ? { code: parsed.code } : {}, message };
|
|
484
|
+
} catch {
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
return { message: `Answer failed (${res.status})` };
|
|
549
488
|
}
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
const isControlled = value !== void 0;
|
|
577
|
-
const [internal, setInternal] = useState3(initialValue ?? "");
|
|
578
|
-
const text = isControlled ? value : internal;
|
|
579
|
-
const textareaRef = useRef3(null);
|
|
580
|
-
const fileInputRef = useRef3(null);
|
|
581
|
-
const folderInputRef = useRef3(null);
|
|
582
|
-
const [dragOver, setDragOver] = useState3(false);
|
|
583
|
-
const dragDepth = useRef3(0);
|
|
584
|
-
const setText = useCallback(
|
|
585
|
-
(next) => {
|
|
586
|
-
if (!isControlled) setInternal(next);
|
|
587
|
-
onValueChange?.(next);
|
|
588
|
-
},
|
|
589
|
-
[isControlled, onValueChange]
|
|
590
|
-
);
|
|
591
|
-
useEffect3(() => {
|
|
592
|
-
const el = textareaRef.current;
|
|
593
|
-
if (!el) return;
|
|
594
|
-
el.style.height = "auto";
|
|
595
|
-
el.style.height = `${Math.min(el.scrollHeight, MAX_HEIGHT)}px`;
|
|
596
|
-
}, [text]);
|
|
597
|
-
const prevSeedRef = useRef3(null);
|
|
598
|
-
const pendingCaretRef = useRef3(null);
|
|
599
|
-
useEffect3(() => {
|
|
600
|
-
const prev = prevSeedRef.current;
|
|
601
|
-
prevSeedRef.current = seed ?? null;
|
|
602
|
-
if (seed == null || seed === prev || isControlled) return;
|
|
603
|
-
setText(seed);
|
|
604
|
-
onSeedApplied?.();
|
|
605
|
-
const el = textareaRef.current;
|
|
606
|
-
if (el && el.value === seed) {
|
|
607
|
-
el.focus();
|
|
608
|
-
el.setSelectionRange(seed.length, seed.length);
|
|
609
|
-
} else {
|
|
610
|
-
pendingCaretRef.current = seed;
|
|
611
|
-
}
|
|
612
|
-
}, [seed, setText, onSeedApplied, isControlled]);
|
|
613
|
-
useEffect3(() => {
|
|
614
|
-
if (pendingCaretRef.current == null || pendingCaretRef.current !== text)
|
|
615
|
-
return;
|
|
616
|
-
pendingCaretRef.current = null;
|
|
617
|
-
const el = textareaRef.current;
|
|
618
|
-
if (!el) return;
|
|
619
|
-
el.focus();
|
|
620
|
-
el.setSelectionRange(text.length, text.length);
|
|
621
|
-
}, [text]);
|
|
622
|
-
useEffect3(() => {
|
|
623
|
-
if (!focusShortcut || disabled) return;
|
|
624
|
-
function onKeyDown(e) {
|
|
625
|
-
if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "l") {
|
|
626
|
-
e.preventDefault();
|
|
627
|
-
textareaRef.current?.focus();
|
|
489
|
+
function createInteractionAnswerSubmitter(options) {
|
|
490
|
+
const timeoutMs = options.timeoutMs ?? INTERACTION_SUBMIT_TIMEOUT_MS;
|
|
491
|
+
return async (submission) => {
|
|
492
|
+
const doFetch = options.fetchImpl ?? fetch;
|
|
493
|
+
const url = typeof options.url === "function" ? options.url(submission) : options.url;
|
|
494
|
+
const extra = typeof options.body === "function" ? options.body(submission) : options.body ?? {};
|
|
495
|
+
const controller = new AbortController();
|
|
496
|
+
const timer = setTimeout(() => controller.abort(INTERACTION_SUBMIT_TIMEOUT_MESSAGE), timeoutMs);
|
|
497
|
+
try {
|
|
498
|
+
const res = await doFetch(url, {
|
|
499
|
+
method: "POST",
|
|
500
|
+
headers: { "Content-Type": "application/json" },
|
|
501
|
+
signal: controller.signal,
|
|
502
|
+
body: JSON.stringify({
|
|
503
|
+
...extra,
|
|
504
|
+
id: submission.id,
|
|
505
|
+
outcome: submission.outcome,
|
|
506
|
+
...submission.data ? { data: submission.data } : {}
|
|
507
|
+
})
|
|
508
|
+
});
|
|
509
|
+
if (res.ok) return { ok: true };
|
|
510
|
+
const failure = await responseErrorMessage(res);
|
|
511
|
+
return { ok: false, expired: res.status === 410, message: failure.message };
|
|
512
|
+
} catch (err) {
|
|
513
|
+
if (controller.signal.aborted) {
|
|
514
|
+
return { ok: false, expired: false, message: INTERACTION_SUBMIT_TIMEOUT_MESSAGE };
|
|
628
515
|
}
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
}, [focusShortcut, disabled]);
|
|
633
|
-
const readyParts = pendingFiles.filter((f) => f.status === "ready" && f.part).map((f) => f.part);
|
|
634
|
-
const hasSendable = onSendParts ? text.trim().length > 0 || readyParts.length > 0 : text.trim().length > 0;
|
|
635
|
-
const canSend = hasSendable && !isStreaming && !disabled;
|
|
636
|
-
const send = useCallback(() => {
|
|
637
|
-
const trimmed = text.trim();
|
|
638
|
-
if (isStreaming || disabled) return;
|
|
639
|
-
if (onSendParts) {
|
|
640
|
-
const parts = pendingFiles.filter((f) => f.status === "ready" && f.part).map((f) => f.part);
|
|
641
|
-
if (!trimmed && parts.length === 0) return;
|
|
642
|
-
onSendParts(trimmed, parts);
|
|
643
|
-
setText("");
|
|
644
|
-
return;
|
|
645
|
-
}
|
|
646
|
-
if (!trimmed) return;
|
|
647
|
-
onSend?.(trimmed);
|
|
648
|
-
setText("");
|
|
649
|
-
}, [text, isStreaming, disabled, onSend, onSendParts, pendingFiles, setText]);
|
|
650
|
-
const handleKeyDown = (e) => {
|
|
651
|
-
if (e.nativeEvent.isComposing) return;
|
|
652
|
-
if (e.key === "Enter" && !e.shiftKey) {
|
|
653
|
-
e.preventDefault();
|
|
654
|
-
send();
|
|
516
|
+
return { ok: false, expired: false, message: err instanceof Error ? err.message : "Failed to submit the answer" };
|
|
517
|
+
} finally {
|
|
518
|
+
clearTimeout(timer);
|
|
655
519
|
}
|
|
656
520
|
};
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// src/web-react/interaction-question-card.tsx
|
|
524
|
+
import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
525
|
+
function CheckGlyph({ className }) {
|
|
526
|
+
return /* @__PURE__ */ jsx4("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: /* @__PURE__ */ jsx4("path", { d: "M20 6 9 17l-5-5" }) });
|
|
527
|
+
}
|
|
528
|
+
var BADGE_VARIANT_CLASSES = {
|
|
529
|
+
outline: "border-border text-foreground",
|
|
530
|
+
default: "border-transparent bg-primary text-primary-foreground",
|
|
531
|
+
destructive: "border-transparent bg-destructive/15 text-destructive"
|
|
532
|
+
};
|
|
533
|
+
function InteractionBadge({ variant, children }) {
|
|
534
|
+
return /* @__PURE__ */ jsx4("span", { className: `inline-flex items-center rounded-md border px-2 py-0.5 text-xs font-medium ${BADGE_VARIANT_CLASSES[variant]}`, children });
|
|
535
|
+
}
|
|
536
|
+
function InteractionActionButton({
|
|
537
|
+
variant = "primary",
|
|
538
|
+
onClick,
|
|
539
|
+
disabled,
|
|
540
|
+
children
|
|
541
|
+
}) {
|
|
542
|
+
const variantClasses = variant === "primary" ? "bg-primary text-primary-foreground shadow-sm hover:bg-primary/90" : "border border-border bg-transparent text-foreground hover:bg-accent/40";
|
|
543
|
+
return /* @__PURE__ */ jsx4(
|
|
544
|
+
"button",
|
|
545
|
+
{
|
|
546
|
+
type: "button",
|
|
547
|
+
onClick,
|
|
548
|
+
disabled,
|
|
549
|
+
className: `inline-flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-sm font-medium transition disabled:cursor-not-allowed disabled:opacity-40 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring ${variantClasses}`,
|
|
550
|
+
children
|
|
678
551
|
}
|
|
679
|
-
}, []);
|
|
680
|
-
const handleDragOver = useCallback((e) => {
|
|
681
|
-
e.preventDefault();
|
|
682
|
-
e.stopPropagation();
|
|
683
|
-
if (e.dataTransfer) e.dataTransfer.dropEffect = "copy";
|
|
684
|
-
}, []);
|
|
685
|
-
const handleDrop = useCallback(
|
|
686
|
-
(e) => {
|
|
687
|
-
e.preventDefault();
|
|
688
|
-
e.stopPropagation();
|
|
689
|
-
dragDepth.current = 0;
|
|
690
|
-
setDragOver(false);
|
|
691
|
-
const files = e.dataTransfer?.files;
|
|
692
|
-
if (files?.length) onAttach?.(files);
|
|
693
|
-
},
|
|
694
|
-
[onAttach]
|
|
695
552
|
);
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
553
|
+
}
|
|
554
|
+
var FIELD_INPUT_CLASSES = "w-full rounded-lg border border-border bg-background px-3 py-2 text-sm text-foreground outline-none focus:border-primary disabled:opacity-50";
|
|
555
|
+
function QuestionOptionList({
|
|
556
|
+
groupName,
|
|
557
|
+
idPrefix,
|
|
558
|
+
options,
|
|
559
|
+
multi,
|
|
560
|
+
selectedValues,
|
|
561
|
+
disabled,
|
|
562
|
+
onToggle
|
|
563
|
+
}) {
|
|
564
|
+
return /* @__PURE__ */ jsx4(Fragment2, { children: options.map((option, optionIndex) => {
|
|
565
|
+
const inputId = `${idPrefix}-${optionIndex}`;
|
|
566
|
+
const checked = selectedValues.includes(option.value);
|
|
567
|
+
return /* @__PURE__ */ jsxs3("label", { htmlFor: inputId, className: "flex cursor-pointer gap-2 rounded-lg border border-border/70 p-3 transition-colors hover:bg-muted/50", children: [
|
|
568
|
+
/* @__PURE__ */ jsx4(
|
|
569
|
+
"input",
|
|
570
|
+
{
|
|
571
|
+
id: inputId,
|
|
572
|
+
type: multi ? "checkbox" : "radio",
|
|
573
|
+
name: groupName,
|
|
574
|
+
value: option.value,
|
|
575
|
+
checked,
|
|
576
|
+
disabled,
|
|
577
|
+
onChange: () => onToggle(option.value),
|
|
578
|
+
"aria-labelledby": `${inputId}-label`,
|
|
579
|
+
"aria-describedby": option.description ? `${inputId}-description` : void 0,
|
|
580
|
+
className: "mt-0.5 h-4 w-4 shrink-0 accent-primary"
|
|
581
|
+
}
|
|
582
|
+
),
|
|
583
|
+
/* @__PURE__ */ jsxs3("span", { className: "min-w-0", children: [
|
|
584
|
+
/* @__PURE__ */ jsx4("span", { id: `${inputId}-label`, className: "block text-sm font-medium leading-5 text-foreground", children: option.label }),
|
|
585
|
+
option.description && /* @__PURE__ */ jsx4("span", { id: `${inputId}-description`, className: "mt-0.5 block text-xs leading-5 text-muted-foreground", children: option.description })
|
|
586
|
+
] })
|
|
587
|
+
] }, `${option.value}-${optionIndex}`);
|
|
588
|
+
}) });
|
|
589
|
+
}
|
|
590
|
+
function selectField(field) {
|
|
591
|
+
return field.type === "select" ? field : null;
|
|
592
|
+
}
|
|
593
|
+
function valuesWithSelected(values, field, optionValue) {
|
|
594
|
+
const current = values[field.name]?.selected ?? [];
|
|
595
|
+
let selected = [optionValue];
|
|
596
|
+
if (field.multi === true) {
|
|
597
|
+
selected = current.includes(optionValue) ? current.filter((item) => item !== optionValue) : [...current, optionValue];
|
|
598
|
+
}
|
|
599
|
+
return { ...values, [field.name]: { ...values[field.name], selected } };
|
|
600
|
+
}
|
|
601
|
+
var STATUS_LABELS = interactionStatusLabels({
|
|
602
|
+
pending: "Waiting for your answer",
|
|
603
|
+
answered: "Answered",
|
|
604
|
+
declined: "Declined"
|
|
605
|
+
});
|
|
606
|
+
var TERMINAL_NOTES = interactionTerminalNotes("question", {
|
|
607
|
+
expired: "The original run ended. Answer now to send a new message with this context.",
|
|
608
|
+
cancelled: "The agent withdrew this question. Answer now to send a new message with this context."
|
|
609
|
+
});
|
|
610
|
+
function InteractionQuestionCard({
|
|
611
|
+
interaction,
|
|
612
|
+
canWrite,
|
|
613
|
+
submitAnswer,
|
|
614
|
+
onResolved,
|
|
615
|
+
onLateAnswer,
|
|
616
|
+
className
|
|
617
|
+
}) {
|
|
618
|
+
const [values, setValues] = useState3(() => fieldValuesFromAnswers(interaction.fields, interaction.answers));
|
|
619
|
+
const [submitting, setSubmitting] = useState3(false);
|
|
620
|
+
const [localStatus, setLocalStatus] = useState3(null);
|
|
621
|
+
const [lateAnswerSent, setLateAnswerSent] = useState3(false);
|
|
622
|
+
const [error, setError] = useState3(null);
|
|
623
|
+
const submitInFlightRef = useRef3(false);
|
|
624
|
+
useEffect3(() => {
|
|
625
|
+
if (!interaction.answers) return;
|
|
626
|
+
setValues(fieldValuesFromAnswers(interaction.fields, interaction.answers));
|
|
627
|
+
}, [interaction.answers, interaction.fields]);
|
|
628
|
+
const status = isTerminalInteractionStatus(interaction.status) ? interaction.status : localStatus ?? interaction.status;
|
|
629
|
+
const answered = status === "answered";
|
|
630
|
+
const lateAnswerable = isLateAnswerableStatus(status) && onLateAnswer !== void 0;
|
|
631
|
+
const secretLateAnswerBlocked = lateAnswerable && hasSecretField(interaction.fields);
|
|
632
|
+
const canLateAnswer = canWrite && lateAnswerable && !lateAnswerSent && !secretLateAnswerBlocked;
|
|
633
|
+
const disabled = !canWrite || status !== "pending" && !canLateAnswer || submitting;
|
|
634
|
+
const answerData = useMemo2(() => buildAnswerData(interaction.fields, values), [interaction.fields, values]);
|
|
635
|
+
const setFieldValue = (name, patch) => {
|
|
636
|
+
setValues((prev) => ({ ...prev, [name]: { ...prev[name], ...patch } }));
|
|
637
|
+
};
|
|
638
|
+
const toggleSelected = (field, optionValue) => {
|
|
639
|
+
setValues((prev) => valuesWithSelected(prev, field, optionValue));
|
|
640
|
+
};
|
|
641
|
+
async function submitLateAnswer() {
|
|
642
|
+
if (submitInFlightRef.current || !canLateAnswer || !onLateAnswer) return;
|
|
643
|
+
const data = buildAnswerData(interaction.fields, values);
|
|
644
|
+
if (!data) return;
|
|
645
|
+
submitInFlightRef.current = true;
|
|
646
|
+
setSubmitting(true);
|
|
647
|
+
setError(null);
|
|
648
|
+
let accepted;
|
|
649
|
+
try {
|
|
650
|
+
accepted = await onLateAnswer(lateAnswerMessage(interaction, data));
|
|
651
|
+
} catch {
|
|
652
|
+
accepted = false;
|
|
653
|
+
} finally {
|
|
654
|
+
submitInFlightRef.current = false;
|
|
655
|
+
setSubmitting(false);
|
|
656
|
+
}
|
|
657
|
+
if (accepted === false) {
|
|
658
|
+
setError("The new message was not sent. Try again from this card.");
|
|
659
|
+
return;
|
|
660
|
+
}
|
|
661
|
+
setLateAnswerSent(true);
|
|
662
|
+
}
|
|
663
|
+
async function submit() {
|
|
664
|
+
if (lateAnswerable) {
|
|
665
|
+
await submitLateAnswer();
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
668
|
+
if (submitInFlightRef.current || disabled || !answerData) return;
|
|
669
|
+
submitInFlightRef.current = true;
|
|
670
|
+
setSubmitting(true);
|
|
671
|
+
setError(null);
|
|
672
|
+
try {
|
|
673
|
+
const result = await submitAnswer({ id: interaction.id, outcome: "accepted", data: answerData });
|
|
674
|
+
if (result.ok) {
|
|
675
|
+
setLocalStatus("answered");
|
|
676
|
+
onResolved?.(interaction.id, "answered", answerData);
|
|
677
|
+
return;
|
|
678
|
+
}
|
|
679
|
+
if (result.expired) {
|
|
680
|
+
setLocalStatus("expired");
|
|
681
|
+
onResolved?.(interaction.id, "expired");
|
|
682
|
+
return;
|
|
683
|
+
}
|
|
684
|
+
setError(result.message);
|
|
685
|
+
} finally {
|
|
686
|
+
submitInFlightRef.current = false;
|
|
687
|
+
setSubmitting(false);
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
const terminalNote = secretLateAnswerBlocked ? "This question asked for a secret, so it cannot be sent as a new chat message. Ask the agent to request it again." : TERMINAL_NOTES[status];
|
|
691
|
+
const showSubmitButton = status === "pending" || canWrite && lateAnswerable && !lateAnswerSent;
|
|
692
|
+
let submitLabel = "Submit answer";
|
|
693
|
+
if (lateAnswerable) {
|
|
694
|
+
submitLabel = submitting ? "Sending\u2026" : "Send as new message";
|
|
695
|
+
} else if (submitting) {
|
|
696
|
+
submitLabel = "Submitting\u2026";
|
|
697
|
+
}
|
|
698
|
+
return /* @__PURE__ */ jsxs3("div", { className: `rounded-xl border border-border bg-card p-4 shadow-sm ${className ?? ""}`, children: [
|
|
699
|
+
/* @__PURE__ */ jsxs3("div", { className: "mb-3 flex flex-wrap items-center justify-between gap-2", children: [
|
|
700
|
+
/* @__PURE__ */ jsxs3("div", { className: "flex flex-wrap items-center gap-2", children: [
|
|
701
|
+
/* @__PURE__ */ jsx4(InteractionBadge, { variant: "outline", children: "Question" }),
|
|
702
|
+
/* @__PURE__ */ jsx4(InteractionBadge, { variant: answered ? "default" : status === "expired" || status === "declined" ? "destructive" : "outline", children: STATUS_LABELS[status] })
|
|
703
|
+
] }),
|
|
704
|
+
/* @__PURE__ */ jsx4("span", { className: "text-xs text-muted-foreground", children: "The agent asked for input" })
|
|
705
|
+
] }),
|
|
706
|
+
interaction.title.trim() && interaction.fields.every((field) => field.label !== interaction.title) && /* @__PURE__ */ jsx4("p", { className: "mb-3 text-sm font-medium leading-5 text-foreground", children: interaction.title }),
|
|
707
|
+
interaction.body && /* @__PURE__ */ jsx4("p", { className: "mb-3 text-sm leading-5 text-muted-foreground", children: interaction.body }),
|
|
708
|
+
/* @__PURE__ */ jsx4("div", { className: "space-y-4", children: interaction.fields.map((field) => {
|
|
709
|
+
const value = values[field.name] ?? {};
|
|
710
|
+
const select = selectField(field);
|
|
711
|
+
return /* @__PURE__ */ jsxs3("fieldset", { className: "space-y-2", children: [
|
|
712
|
+
/* @__PURE__ */ jsx4("p", { className: "text-sm font-medium leading-5 text-foreground", children: field.label }),
|
|
713
|
+
select ? /* @__PURE__ */ jsxs3("div", { className: "space-y-2", children: [
|
|
783
714
|
/* @__PURE__ */ jsx4(
|
|
784
|
-
|
|
715
|
+
QuestionOptionList,
|
|
785
716
|
{
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
717
|
+
groupName: `${interaction.id}-${field.name}`,
|
|
718
|
+
idPrefix: `${interaction.id}-${field.name}`,
|
|
719
|
+
options: select.options,
|
|
720
|
+
multi: select.multi === true,
|
|
721
|
+
selectedValues: value.selected ?? [],
|
|
791
722
|
disabled,
|
|
792
|
-
|
|
793
|
-
"aria-label": "Message input",
|
|
794
|
-
className: "max-h-[168px] min-h-[40px] flex-1 resize-none bg-transparent px-1.5 py-2 text-[15px] leading-6 text-foreground outline-none placeholder:text-muted-foreground disabled:opacity-50"
|
|
723
|
+
onToggle: (optionValue) => toggleSelected(select, optionValue)
|
|
795
724
|
}
|
|
796
725
|
),
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
"button",
|
|
726
|
+
select.allowCustom === true && /* @__PURE__ */ jsx4(
|
|
727
|
+
"input",
|
|
800
728
|
{
|
|
801
|
-
type: "
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
]
|
|
729
|
+
type: "text",
|
|
730
|
+
value: value.custom ?? "",
|
|
731
|
+
disabled,
|
|
732
|
+
onChange: (event) => setFieldValue(field.name, { custom: event.target.value }),
|
|
733
|
+
placeholder: "Other \u2014 type your own answer",
|
|
734
|
+
"aria-label": `Custom answer for ${field.label}`,
|
|
735
|
+
className: FIELD_INPUT_CLASSES
|
|
809
736
|
}
|
|
810
|
-
)
|
|
811
|
-
|
|
737
|
+
)
|
|
738
|
+
] }) : field.type === "boolean" ? /* @__PURE__ */ jsx4("div", { className: "flex gap-4", children: ["true", "false"].map((boolValue) => /* @__PURE__ */ jsxs3("label", { className: "flex cursor-pointer items-center gap-2 text-sm text-foreground", children: [
|
|
739
|
+
/* @__PURE__ */ jsx4(
|
|
740
|
+
"input",
|
|
812
741
|
{
|
|
813
|
-
type: "
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
/* @__PURE__ */ jsx4("span", { children: sendLabel })
|
|
821
|
-
]
|
|
742
|
+
type: "radio",
|
|
743
|
+
name: `${interaction.id}-${field.name}`,
|
|
744
|
+
value: boolValue,
|
|
745
|
+
checked: (value.selected ?? [])[0] === boolValue,
|
|
746
|
+
disabled,
|
|
747
|
+
onChange: () => setFieldValue(field.name, { selected: [boolValue] }),
|
|
748
|
+
className: "h-4 w-4 accent-primary"
|
|
822
749
|
}
|
|
823
|
-
)
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
750
|
+
),
|
|
751
|
+
boolValue === "true" ? "Yes" : "No"
|
|
752
|
+
] }, boolValue)) }) : field.type === "number" ? /* @__PURE__ */ jsx4(
|
|
753
|
+
"input",
|
|
754
|
+
{
|
|
755
|
+
type: "number",
|
|
756
|
+
value: value.text ?? "",
|
|
757
|
+
disabled,
|
|
758
|
+
"aria-label": field.label,
|
|
759
|
+
onChange: (event) => setFieldValue(field.name, { text: event.target.value }),
|
|
760
|
+
className: FIELD_INPUT_CLASSES
|
|
761
|
+
}
|
|
762
|
+
) : field.type === "secret" ? /* @__PURE__ */ jsx4(
|
|
763
|
+
"input",
|
|
764
|
+
{
|
|
765
|
+
type: "password",
|
|
766
|
+
value: value.text ?? "",
|
|
767
|
+
disabled,
|
|
768
|
+
"aria-label": field.label,
|
|
769
|
+
onChange: (event) => setFieldValue(field.name, { text: event.target.value }),
|
|
770
|
+
placeholder: field.placeholder,
|
|
771
|
+
className: FIELD_INPUT_CLASSES
|
|
772
|
+
}
|
|
773
|
+
) : /* @__PURE__ */ jsx4(
|
|
774
|
+
"textarea",
|
|
775
|
+
{
|
|
776
|
+
value: value.text ?? "",
|
|
777
|
+
disabled,
|
|
778
|
+
"aria-label": field.label,
|
|
779
|
+
onChange: (event) => setFieldValue(field.name, { text: event.target.value }),
|
|
780
|
+
rows: 3,
|
|
781
|
+
placeholder: field.type === "text" ? field.placeholder : void 0,
|
|
782
|
+
className: FIELD_INPUT_CLASSES
|
|
783
|
+
}
|
|
784
|
+
)
|
|
785
|
+
] }, field.name);
|
|
786
|
+
}) }),
|
|
787
|
+
error && /* @__PURE__ */ jsx4("p", { className: "mt-3 text-xs text-destructive", children: error }),
|
|
788
|
+
terminalNote && /* @__PURE__ */ jsx4("p", { className: "mt-3 text-xs text-muted-foreground", children: terminalNote }),
|
|
789
|
+
showSubmitButton && /* @__PURE__ */ jsx4("div", { className: "mt-4 flex items-center justify-end gap-2", children: /* @__PURE__ */ jsx4(InteractionActionButton, { onClick: () => void submit(), disabled: disabled || !answerData, children: submitLabel }) }),
|
|
790
|
+
answered && /* @__PURE__ */ jsx4("div", { className: "mt-4 flex items-center justify-end", children: /* @__PURE__ */ jsxs3("span", { className: "inline-flex items-center gap-1 text-xs text-muted-foreground", children: [
|
|
791
|
+
/* @__PURE__ */ jsx4(CheckGlyph, { className: "h-3 w-3" }),
|
|
792
|
+
"Answered"
|
|
793
|
+
] }) }),
|
|
794
|
+
lateAnswerSent && /* @__PURE__ */ jsx4("div", { className: "mt-4 flex items-center justify-end", children: /* @__PURE__ */ jsxs3("span", { className: "inline-flex items-center gap-1 text-xs text-muted-foreground", children: [
|
|
795
|
+
/* @__PURE__ */ jsx4(CheckGlyph, { className: "h-3 w-3" }),
|
|
796
|
+
"Sent as new message"
|
|
797
|
+
] }) })
|
|
798
|
+
] });
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
// src/web-react/durable-plan-card.tsx
|
|
802
|
+
import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
803
|
+
function statusLabel(plan) {
|
|
804
|
+
switch (plan.status) {
|
|
805
|
+
case "pending":
|
|
806
|
+
return "Waiting for your decision";
|
|
807
|
+
case "approved":
|
|
808
|
+
return "Approved";
|
|
809
|
+
case "rejected":
|
|
810
|
+
return "Changes requested";
|
|
811
|
+
case "superseded":
|
|
812
|
+
return "Superseded";
|
|
813
|
+
case "withdrawn":
|
|
814
|
+
return "Withdrawn";
|
|
815
|
+
default:
|
|
816
|
+
return "Preparing";
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
function DurablePlanCard({
|
|
820
|
+
plan,
|
|
821
|
+
canWrite,
|
|
822
|
+
decide,
|
|
823
|
+
deciding = null,
|
|
824
|
+
error,
|
|
825
|
+
renderMarkdown,
|
|
826
|
+
className
|
|
827
|
+
}) {
|
|
828
|
+
const [feedback, setFeedback] = useState4("");
|
|
829
|
+
const [expanded, setExpanded] = useState4(false);
|
|
830
|
+
const [localError, setLocalError] = useState4(null);
|
|
831
|
+
useEffect4(() => setLocalError(null), [plan.planId, plan.revision, plan.status]);
|
|
832
|
+
const actionable = plan.status === "pending";
|
|
833
|
+
const disabled = !canWrite || !actionable || deciding !== null;
|
|
834
|
+
async function submit(decision) {
|
|
835
|
+
const trimmed = feedback.trim();
|
|
836
|
+
if (decision === "rejected" && !trimmed) {
|
|
837
|
+
setLocalError("Describe what you want changed before requesting a revision.");
|
|
838
|
+
return;
|
|
831
839
|
}
|
|
832
|
-
|
|
840
|
+
setLocalError(null);
|
|
841
|
+
await decide(decision, decision === "rejected" ? trimmed : void 0);
|
|
842
|
+
}
|
|
843
|
+
return /* @__PURE__ */ jsxs4("div", { className: `rounded-xl border border-primary/40 bg-card p-4 shadow-sm ${className ?? ""}`, children: [
|
|
844
|
+
/* @__PURE__ */ jsxs4("div", { className: "mb-3 flex flex-wrap items-center justify-between gap-2", children: [
|
|
845
|
+
/* @__PURE__ */ jsxs4("div", { className: "flex flex-wrap items-center gap-2", children: [
|
|
846
|
+
/* @__PURE__ */ jsx5(InteractionBadge, { variant: "outline", children: "Plan decision" }),
|
|
847
|
+
/* @__PURE__ */ jsx5(InteractionBadge, { variant: plan.status === "approved" ? "default" : plan.status === "rejected" || plan.status === "withdrawn" ? "destructive" : "outline", children: statusLabel(plan) })
|
|
848
|
+
] }),
|
|
849
|
+
/* @__PURE__ */ jsxs4("span", { className: "text-xs text-muted-foreground", children: [
|
|
850
|
+
"Revision ",
|
|
851
|
+
plan.revision
|
|
852
|
+
] })
|
|
853
|
+
] }),
|
|
854
|
+
plan.title && /* @__PURE__ */ jsx5("p", { className: "mb-3 text-sm font-medium leading-5 text-foreground", children: plan.title }),
|
|
855
|
+
/* @__PURE__ */ jsxs4("div", { className: "relative", children: [
|
|
856
|
+
/* @__PURE__ */ jsx5("div", { className: "overflow-hidden text-sm", style: expanded ? void 0 : { maxHeight: 320 }, children: renderMarkdown ? renderMarkdown(plan.body) : /* @__PURE__ */ jsx5("p", { className: "whitespace-pre-wrap leading-5", children: plan.body }) }),
|
|
857
|
+
!expanded && /* @__PURE__ */ jsx5("div", { className: "pointer-events-none absolute inset-x-0 bottom-0 h-12 bg-gradient-to-t from-card to-transparent" }),
|
|
858
|
+
/* @__PURE__ */ jsx5(
|
|
859
|
+
"button",
|
|
860
|
+
{
|
|
861
|
+
type: "button",
|
|
862
|
+
onClick: () => setExpanded((value) => !value),
|
|
863
|
+
className: "mt-1 text-xs text-muted-foreground hover:text-foreground focus:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
864
|
+
children: expanded ? "Collapse plan" : "Show full plan"
|
|
865
|
+
}
|
|
866
|
+
)
|
|
867
|
+
] }),
|
|
868
|
+
actionable && /* @__PURE__ */ jsxs4("div", { className: "mt-3 space-y-2", children: [
|
|
869
|
+
/* @__PURE__ */ jsx5("label", { className: "block text-sm font-medium leading-5 text-foreground", htmlFor: `durable-plan-feedback-${plan.planId}-${plan.revision}`, children: "Feedback for requested changes" }),
|
|
870
|
+
/* @__PURE__ */ jsx5(
|
|
871
|
+
"textarea",
|
|
872
|
+
{
|
|
873
|
+
id: `durable-plan-feedback-${plan.planId}-${plan.revision}`,
|
|
874
|
+
value: feedback,
|
|
875
|
+
disabled,
|
|
876
|
+
onChange: (event) => setFeedback(event.target.value),
|
|
877
|
+
rows: 2,
|
|
878
|
+
placeholder: "Describe what you want changed in the plan",
|
|
879
|
+
className: "w-full rounded-lg border border-border bg-background px-3 py-2 text-sm outline-none focus:border-primary disabled:opacity-50"
|
|
880
|
+
}
|
|
881
|
+
)
|
|
882
|
+
] }),
|
|
883
|
+
(localError ?? error) && /* @__PURE__ */ jsx5("p", { className: "mt-3 text-xs text-destructive", children: localError ?? error }),
|
|
884
|
+
actionable && /* @__PURE__ */ jsxs4("div", { className: "mt-4 flex items-center justify-end gap-2", children: [
|
|
885
|
+
/* @__PURE__ */ jsx5(InteractionActionButton, { variant: "outline", onClick: () => void submit("rejected"), disabled, children: deciding === "rejected" ? "Sending\u2026" : "Request changes" }),
|
|
886
|
+
/* @__PURE__ */ jsx5(InteractionActionButton, { onClick: () => void submit("approved"), disabled, children: deciding === "approved" ? "Approving\u2026" : "Approve plan" })
|
|
887
|
+
] })
|
|
888
|
+
] });
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
// src/web-react/interaction-plan-card.tsx
|
|
892
|
+
import { useMemo as useMemo3, useRef as useRef4, useState as useState5 } from "react";
|
|
893
|
+
import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
894
|
+
function CheckGlyph2({ className }) {
|
|
895
|
+
return /* @__PURE__ */ jsx6("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: /* @__PURE__ */ jsx6("path", { d: "M20 6 9 17l-5-5" }) });
|
|
896
|
+
}
|
|
897
|
+
function ChevronDownGlyph({ className }) {
|
|
898
|
+
return /* @__PURE__ */ jsx6("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: /* @__PURE__ */ jsx6("path", { d: "m6 9 6 6 6-6" }) });
|
|
899
|
+
}
|
|
900
|
+
var STATUS_LABELS2 = interactionStatusLabels({
|
|
901
|
+
pending: "Waiting for your approval",
|
|
902
|
+
answered: "Approved",
|
|
903
|
+
declined: "Rejected"
|
|
904
|
+
});
|
|
905
|
+
var TERMINAL_NOTES2 = interactionTerminalNotes("plan", {
|
|
906
|
+
declined: "The agent was asked to revise the plan."
|
|
907
|
+
});
|
|
908
|
+
var COLLAPSED_MAX_HEIGHT = 320;
|
|
909
|
+
function InteractionPlanCard({
|
|
910
|
+
interaction,
|
|
911
|
+
canWrite,
|
|
912
|
+
submitAnswer,
|
|
913
|
+
onResolved,
|
|
914
|
+
renderMarkdown,
|
|
915
|
+
className
|
|
916
|
+
}) {
|
|
917
|
+
const [values, setValues] = useState5({});
|
|
918
|
+
const [expanded, setExpanded] = useState5(false);
|
|
919
|
+
const [submitting, setSubmitting] = useState5(null);
|
|
920
|
+
const [localStatus, setLocalStatus] = useState5(null);
|
|
921
|
+
const [error, setError] = useState5(null);
|
|
922
|
+
const submitInFlightRef = useRef4(false);
|
|
923
|
+
const status = isTerminalInteractionStatus(interaction.status) ? interaction.status : localStatus ?? interaction.status;
|
|
924
|
+
const disabled = !canWrite || status !== "pending" || submitting !== null;
|
|
925
|
+
const approveData = useMemo3(() => buildAnswerData(interaction.fields, values), [interaction.fields, values]);
|
|
926
|
+
const rejectData = useMemo3(() => {
|
|
927
|
+
const data = {};
|
|
928
|
+
for (const field of interaction.fields) {
|
|
929
|
+
const answer = fieldAnswer(field, values);
|
|
930
|
+
if (answer !== null) data[field.name] = answer;
|
|
931
|
+
}
|
|
932
|
+
return data;
|
|
933
|
+
}, [interaction.fields, values]);
|
|
934
|
+
async function submit(outcome) {
|
|
935
|
+
const data = outcome === "accepted" ? approveData : rejectData;
|
|
936
|
+
if (submitInFlightRef.current || disabled || data === null) return;
|
|
937
|
+
submitInFlightRef.current = true;
|
|
938
|
+
setSubmitting(outcome === "accepted" ? "approve" : "reject");
|
|
939
|
+
setError(null);
|
|
940
|
+
try {
|
|
941
|
+
const result = await submitAnswer({ id: interaction.id, outcome, data });
|
|
942
|
+
if (result.ok) {
|
|
943
|
+
const resolved = outcome === "accepted" ? "answered" : "declined";
|
|
944
|
+
setLocalStatus(resolved);
|
|
945
|
+
onResolved?.(interaction.id, resolved);
|
|
946
|
+
return;
|
|
947
|
+
}
|
|
948
|
+
if (result.expired) {
|
|
949
|
+
setLocalStatus("expired");
|
|
950
|
+
onResolved?.(interaction.id, "expired");
|
|
951
|
+
return;
|
|
952
|
+
}
|
|
953
|
+
setError(result.message);
|
|
954
|
+
} finally {
|
|
955
|
+
submitInFlightRef.current = false;
|
|
956
|
+
setSubmitting(null);
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
const terminalNote = TERMINAL_NOTES2[status];
|
|
960
|
+
const approved = status === "answered";
|
|
961
|
+
return /* @__PURE__ */ jsxs5("div", { className: `rounded-xl border border-border bg-card p-4 shadow-sm ${className ?? ""}`, children: [
|
|
962
|
+
/* @__PURE__ */ jsxs5("div", { className: "mb-3 flex flex-wrap items-center justify-between gap-2", children: [
|
|
963
|
+
/* @__PURE__ */ jsxs5("div", { className: "flex flex-wrap items-center gap-2", children: [
|
|
964
|
+
/* @__PURE__ */ jsx6(InteractionBadge, { variant: "outline", children: "Plan" }),
|
|
965
|
+
/* @__PURE__ */ jsx6(InteractionBadge, { variant: approved ? "default" : status === "expired" || status === "declined" ? "destructive" : "outline", children: STATUS_LABELS2[status] })
|
|
966
|
+
] }),
|
|
967
|
+
/* @__PURE__ */ jsx6("span", { className: "text-xs text-muted-foreground", children: "The agent proposed a plan" })
|
|
968
|
+
] }),
|
|
969
|
+
interaction.title.trim() && /* @__PURE__ */ jsx6("p", { className: "mb-3 text-sm font-medium leading-5 text-foreground", children: interaction.title }),
|
|
970
|
+
interaction.body && /* @__PURE__ */ jsxs5("div", { className: "relative", children: [
|
|
971
|
+
/* @__PURE__ */ jsx6(
|
|
972
|
+
"div",
|
|
973
|
+
{
|
|
974
|
+
className: "overflow-hidden text-sm text-foreground",
|
|
975
|
+
style: expanded ? void 0 : { maxHeight: COLLAPSED_MAX_HEIGHT },
|
|
976
|
+
children: renderMarkdown ? renderMarkdown(interaction.body) : /* @__PURE__ */ jsx6("p", { className: "whitespace-pre-wrap leading-5", children: interaction.body })
|
|
977
|
+
}
|
|
978
|
+
),
|
|
979
|
+
!expanded && /* @__PURE__ */ jsx6("div", { className: "pointer-events-none absolute inset-x-0 bottom-0 h-12 bg-gradient-to-t from-card to-transparent" }),
|
|
980
|
+
/* @__PURE__ */ jsxs5(
|
|
981
|
+
"button",
|
|
982
|
+
{
|
|
983
|
+
type: "button",
|
|
984
|
+
onClick: () => setExpanded((prev) => !prev),
|
|
985
|
+
className: "mt-1 inline-flex items-center gap-1 text-xs text-muted-foreground transition hover:text-foreground focus:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
986
|
+
children: [
|
|
987
|
+
/* @__PURE__ */ jsx6(ChevronDownGlyph, { className: `h-3 w-3 transition-transform ${expanded ? "rotate-180" : ""}` }),
|
|
988
|
+
expanded ? "Collapse plan" : "Show full plan"
|
|
989
|
+
]
|
|
990
|
+
}
|
|
991
|
+
)
|
|
992
|
+
] }),
|
|
993
|
+
interaction.fields.length > 0 && /* @__PURE__ */ jsx6("div", { className: "mt-3 space-y-4", children: interaction.fields.map((field) => /* @__PURE__ */ jsxs5("fieldset", { className: "space-y-2", children: [
|
|
994
|
+
/* @__PURE__ */ jsx6("p", { className: "text-sm font-medium leading-5 text-foreground", children: field.label }),
|
|
995
|
+
fieldAcceptsFreeText(field) ? /* @__PURE__ */ jsx6(
|
|
996
|
+
"textarea",
|
|
997
|
+
{
|
|
998
|
+
value: values[field.name]?.text ?? "",
|
|
999
|
+
disabled,
|
|
1000
|
+
"aria-label": field.label,
|
|
1001
|
+
onChange: (event) => setValues((prev) => ({ ...prev, [field.name]: { ...prev[field.name], text: event.target.value } })),
|
|
1002
|
+
rows: 2,
|
|
1003
|
+
placeholder: field.type === "text" ? field.placeholder ?? "Optional feedback for the agent" : void 0,
|
|
1004
|
+
className: "w-full rounded-lg border border-border bg-background px-3 py-2 text-sm text-foreground outline-none focus:border-primary disabled:opacity-50"
|
|
1005
|
+
}
|
|
1006
|
+
) : /* @__PURE__ */ jsx6(
|
|
1007
|
+
"input",
|
|
1008
|
+
{
|
|
1009
|
+
type: "text",
|
|
1010
|
+
value: values[field.name]?.text ?? "",
|
|
1011
|
+
disabled,
|
|
1012
|
+
"aria-label": field.label,
|
|
1013
|
+
onChange: (event) => setValues((prev) => ({ ...prev, [field.name]: { ...prev[field.name], text: event.target.value } })),
|
|
1014
|
+
className: "w-full rounded-lg border border-border bg-background px-3 py-2 text-sm text-foreground outline-none focus:border-primary disabled:opacity-50"
|
|
1015
|
+
}
|
|
1016
|
+
)
|
|
1017
|
+
] }, field.name)) }),
|
|
1018
|
+
error && /* @__PURE__ */ jsx6("p", { className: "mt-3 text-xs text-destructive", children: error }),
|
|
1019
|
+
terminalNote && /* @__PURE__ */ jsx6("p", { className: "mt-3 text-xs text-muted-foreground", children: terminalNote }),
|
|
1020
|
+
status === "pending" && /* @__PURE__ */ jsxs5("div", { className: "mt-4 flex items-center justify-end gap-2", children: [
|
|
1021
|
+
/* @__PURE__ */ jsx6(InteractionActionButton, { variant: "outline", onClick: () => void submit("declined"), disabled, children: submitting === "reject" ? "Sending\u2026" : "Request changes" }),
|
|
1022
|
+
/* @__PURE__ */ jsx6(InteractionActionButton, { onClick: () => void submit("accepted"), disabled: disabled || approveData === null, children: submitting === "approve" ? "Approving\u2026" : "Approve plan" })
|
|
1023
|
+
] }),
|
|
1024
|
+
approved && /* @__PURE__ */ jsx6("div", { className: "mt-4 flex items-center justify-end", children: /* @__PURE__ */ jsxs5("span", { className: "inline-flex items-center gap-1 text-xs text-muted-foreground", children: [
|
|
1025
|
+
/* @__PURE__ */ jsx6(CheckGlyph2, { className: "h-3 w-3" }),
|
|
1026
|
+
"Approved"
|
|
1027
|
+
] }) })
|
|
1028
|
+
] });
|
|
833
1029
|
}
|
|
834
1030
|
|
|
835
|
-
// src/web-react/
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
}
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
}
|
|
846
|
-
function fieldAnswer(field, values) {
|
|
847
|
-
const value = values[field.name] ?? {};
|
|
848
|
-
if (field.type === "select") {
|
|
849
|
-
const custom = field.allowCustom === true ? value.custom?.trim() : void 0;
|
|
850
|
-
const chosen = [...value.selected ?? [], ...custom ? [custom] : []];
|
|
851
|
-
if (field.multi !== true && custom) return [custom];
|
|
852
|
-
return chosen.length > 0 ? chosen : null;
|
|
853
|
-
}
|
|
854
|
-
if (field.type === "number") {
|
|
855
|
-
const parsed = Number(value.text);
|
|
856
|
-
return value.text?.trim() && Number.isFinite(parsed) ? parsed : null;
|
|
1031
|
+
// src/web-react/durable-chat-cards.tsx
|
|
1032
|
+
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
1033
|
+
function planIdentity(planId, revision) {
|
|
1034
|
+
return `${planId}:${revision}`;
|
|
1035
|
+
}
|
|
1036
|
+
function durableChatCardsFromParts(parts) {
|
|
1037
|
+
const durablePlans = /* @__PURE__ */ new Set();
|
|
1038
|
+
for (const part of parts) {
|
|
1039
|
+
const plan = persistedPartToPlan(part);
|
|
1040
|
+
if (plan) durablePlans.add(planIdentity(plan.planId, plan.revision));
|
|
857
1041
|
}
|
|
858
|
-
|
|
859
|
-
const
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
for (const field of fields) {
|
|
865
|
-
const answer = fieldAnswer(field, values);
|
|
866
|
-
if (answer === null) {
|
|
867
|
-
if (field.required === false) continue;
|
|
868
|
-
return null;
|
|
1042
|
+
const cards = [];
|
|
1043
|
+
for (const part of parts) {
|
|
1044
|
+
const plan = persistedPartToPlan(part);
|
|
1045
|
+
if (plan) {
|
|
1046
|
+
cards.push({ kind: "plan", key: `plan:${planIdentity(plan.planId, plan.revision)}`, plan });
|
|
1047
|
+
continue;
|
|
869
1048
|
}
|
|
870
|
-
|
|
1049
|
+
const interaction = persistedPartToInteraction(part);
|
|
1050
|
+
if (!interaction) continue;
|
|
1051
|
+
const correlatedPlan = interaction.kind === "plan" && typeof part.planId === "string" && typeof part.revision === "number" && durablePlans.has(planIdentity(part.planId, part.revision));
|
|
1052
|
+
if (correlatedPlan) continue;
|
|
1053
|
+
cards.push({ kind: "interaction", key: `interaction:${interaction.id}`, interaction });
|
|
871
1054
|
}
|
|
872
|
-
return
|
|
873
|
-
}
|
|
874
|
-
function isLateAnswerableStatus(status) {
|
|
875
|
-
return status === "expired" || status === "cancelled";
|
|
1055
|
+
return cards;
|
|
876
1056
|
}
|
|
877
|
-
function
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
1057
|
+
function DurableChatCards({
|
|
1058
|
+
parts,
|
|
1059
|
+
canWrite,
|
|
1060
|
+
submitInteraction,
|
|
1061
|
+
decidePlan,
|
|
1062
|
+
decidingPlan,
|
|
1063
|
+
planError,
|
|
1064
|
+
onInteractionResolved,
|
|
1065
|
+
onLateAnswer,
|
|
1066
|
+
renderMarkdown,
|
|
1067
|
+
className
|
|
1068
|
+
}) {
|
|
1069
|
+
const cards = durableChatCardsFromParts(parts);
|
|
1070
|
+
if (cards.length === 0) return null;
|
|
1071
|
+
return /* @__PURE__ */ jsx7("div", { className: `space-y-3 ${className ?? ""}`, children: cards.map((card) => {
|
|
1072
|
+
if (card.kind === "plan") {
|
|
1073
|
+
return /* @__PURE__ */ jsx7(
|
|
1074
|
+
DurablePlanCard,
|
|
1075
|
+
{
|
|
1076
|
+
plan: card.plan,
|
|
1077
|
+
canWrite,
|
|
1078
|
+
decide: (decision, feedback) => decidePlan(card.plan, decision, feedback),
|
|
1079
|
+
deciding: decidingPlan?.(card.plan),
|
|
1080
|
+
error: planError?.(card.plan),
|
|
1081
|
+
renderMarkdown
|
|
1082
|
+
},
|
|
1083
|
+
card.key
|
|
1084
|
+
);
|
|
1085
|
+
}
|
|
1086
|
+
if (card.interaction.kind === "plan") {
|
|
1087
|
+
return /* @__PURE__ */ jsx7(
|
|
1088
|
+
InteractionPlanCard,
|
|
1089
|
+
{
|
|
1090
|
+
interaction: card.interaction,
|
|
1091
|
+
canWrite,
|
|
1092
|
+
submitAnswer: submitInteraction,
|
|
1093
|
+
onResolved: onInteractionResolved,
|
|
1094
|
+
renderMarkdown
|
|
1095
|
+
},
|
|
1096
|
+
card.key
|
|
1097
|
+
);
|
|
1098
|
+
}
|
|
1099
|
+
return /* @__PURE__ */ jsx7(
|
|
1100
|
+
InteractionQuestionCard,
|
|
1101
|
+
{
|
|
1102
|
+
interaction: card.interaction,
|
|
1103
|
+
canWrite,
|
|
1104
|
+
submitAnswer: submitInteraction,
|
|
1105
|
+
onResolved: onInteractionResolved,
|
|
1106
|
+
onLateAnswer
|
|
1107
|
+
},
|
|
1108
|
+
card.key
|
|
1109
|
+
);
|
|
1110
|
+
}) });
|
|
882
1111
|
}
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
1112
|
+
|
|
1113
|
+
// src/web-react/chat-stream.ts
|
|
1114
|
+
function dispatchChatStreamLine(line, cb) {
|
|
1115
|
+
let receivedContent = false;
|
|
1116
|
+
let turnId;
|
|
1117
|
+
if (!line.trim()) return { receivedContent };
|
|
1118
|
+
let parsed;
|
|
1119
|
+
try {
|
|
1120
|
+
parsed = JSON.parse(line);
|
|
1121
|
+
} catch {
|
|
1122
|
+
return { receivedContent };
|
|
886
1123
|
}
|
|
887
|
-
if (
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
1124
|
+
if (parsed.kind === "tool_result") {
|
|
1125
|
+
cb.onToolResult?.({
|
|
1126
|
+
toolCallId: parsed.toolCallId,
|
|
1127
|
+
toolName: parsed.toolName,
|
|
1128
|
+
label: parsed.label,
|
|
1129
|
+
outcome: parsed.outcome ?? parsed.result
|
|
1130
|
+
});
|
|
1131
|
+
return { receivedContent: true };
|
|
1132
|
+
}
|
|
1133
|
+
const evt = parsed.kind === "event" ? parsed.event : parsed;
|
|
1134
|
+
if (!evt || typeof evt !== "object") return { receivedContent };
|
|
1135
|
+
switch (evt.type) {
|
|
1136
|
+
case "turn":
|
|
1137
|
+
if (typeof evt.turnId === "string") turnId = evt.turnId;
|
|
1138
|
+
break;
|
|
1139
|
+
case "text":
|
|
1140
|
+
if (typeof evt.text === "string") {
|
|
1141
|
+
cb.onText?.(evt.text);
|
|
1142
|
+
receivedContent = true;
|
|
1143
|
+
}
|
|
1144
|
+
break;
|
|
1145
|
+
case "reasoning":
|
|
1146
|
+
if (typeof evt.text === "string") {
|
|
1147
|
+
cb.onReasoning?.(evt.text);
|
|
1148
|
+
receivedContent = true;
|
|
1149
|
+
}
|
|
1150
|
+
break;
|
|
1151
|
+
case "tool_call": {
|
|
1152
|
+
const call = evt.call ?? evt;
|
|
1153
|
+
cb.onToolCall?.({
|
|
1154
|
+
toolCallId: call.toolCallId ?? call.id,
|
|
1155
|
+
toolName: String(call.toolName ?? call.name ?? "unknown"),
|
|
1156
|
+
args: call.args ?? {}
|
|
1157
|
+
});
|
|
1158
|
+
receivedContent = true;
|
|
1159
|
+
break;
|
|
1160
|
+
}
|
|
1161
|
+
case "tool_result":
|
|
1162
|
+
cb.onToolResult?.({
|
|
1163
|
+
toolCallId: evt.toolCallId,
|
|
1164
|
+
toolName: evt.toolName,
|
|
1165
|
+
label: evt.label,
|
|
1166
|
+
outcome: evt.outcome ?? evt.result
|
|
1167
|
+
});
|
|
1168
|
+
receivedContent = true;
|
|
1169
|
+
break;
|
|
1170
|
+
case "usage": {
|
|
1171
|
+
const u = evt.usage;
|
|
1172
|
+
if (u) cb.onUsage?.({ promptTokens: u.promptTokens ?? 0, completionTokens: u.completionTokens ?? 0 });
|
|
1173
|
+
break;
|
|
1174
|
+
}
|
|
1175
|
+
case "metadata":
|
|
1176
|
+
cb.onMetadata?.(evt.data ?? {});
|
|
1177
|
+
break;
|
|
1178
|
+
case "interaction": {
|
|
1179
|
+
const parsed2 = parseInteractionRequest(evt.data);
|
|
1180
|
+
if (parsed2.succeeded) {
|
|
1181
|
+
cb.onInteraction?.(interactionFromWireRequest(parsed2.value));
|
|
1182
|
+
receivedContent = true;
|
|
1183
|
+
} else {
|
|
1184
|
+
console.error("[chat-stream] dropping malformed interaction line:", parsed2.error);
|
|
1185
|
+
}
|
|
1186
|
+
break;
|
|
1187
|
+
}
|
|
1188
|
+
case "interaction.cancel": {
|
|
1189
|
+
const cancelled = parseInteractionCancel(evt.data);
|
|
1190
|
+
if (cancelled.succeeded) {
|
|
1191
|
+
cb.onInteractionCancel?.(cancelled.value);
|
|
1192
|
+
receivedContent = true;
|
|
1193
|
+
} else {
|
|
1194
|
+
console.error("[chat-stream] dropping malformed interaction.cancel line:", cancelled.error);
|
|
1195
|
+
}
|
|
1196
|
+
break;
|
|
1197
|
+
}
|
|
1198
|
+
case "error": {
|
|
1199
|
+
const data = evt.data;
|
|
1200
|
+
const message = String(data?.message ?? evt.details ?? evt.error ?? "Unknown stream error");
|
|
1201
|
+
if (cb.onErrorEvent) {
|
|
1202
|
+
cb.onErrorEvent(message);
|
|
1203
|
+
} else {
|
|
1204
|
+
console.error("[chat-stream] unhandled stream error event:", message);
|
|
1205
|
+
cb.onText?.(`
|
|
1206
|
+
|
|
1207
|
+
The agent hit an error and this turn stopped: ${message}`);
|
|
1208
|
+
receivedContent = true;
|
|
1209
|
+
}
|
|
1210
|
+
break;
|
|
1211
|
+
}
|
|
1212
|
+
default: {
|
|
1213
|
+
if (typeof evt.type === "string" && evt.type.startsWith("plan.")) {
|
|
1214
|
+
const submitted = parsePlanSubmittedEvent(evt);
|
|
1215
|
+
const planRecord = evt.data?.plan ?? evt.properties?.plan;
|
|
1216
|
+
const plan = submitted.succeeded ? submitted.value : planRecord ? persistedPartToPlan({ type: "plan", ...planRecord }) : null;
|
|
1217
|
+
if (plan) {
|
|
1218
|
+
cb.onPlan?.(plan);
|
|
1219
|
+
receivedContent = true;
|
|
1220
|
+
} else {
|
|
1221
|
+
console.error("[chat-stream] dropping malformed durable plan line:", evt.type);
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
break;
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
return { turnId, receivedContent };
|
|
906
1228
|
}
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
1229
|
+
async function consumeChatStream(body, cb) {
|
|
1230
|
+
const reader = body.getReader();
|
|
1231
|
+
const decoder = new TextDecoder();
|
|
1232
|
+
let buffer = "";
|
|
1233
|
+
let turnId = null;
|
|
1234
|
+
let receivedContent = false;
|
|
1235
|
+
const handle = (line) => {
|
|
1236
|
+
const r = dispatchChatStreamLine(line, cb);
|
|
1237
|
+
if (r.turnId) {
|
|
1238
|
+
turnId = r.turnId;
|
|
1239
|
+
cb.onTurnId?.(r.turnId);
|
|
1240
|
+
}
|
|
1241
|
+
if (r.receivedContent) receivedContent = true;
|
|
1242
|
+
};
|
|
1243
|
+
for (; ; ) {
|
|
1244
|
+
const { done, value } = await reader.read();
|
|
1245
|
+
if (done) {
|
|
1246
|
+
if (buffer.trim()) handle(buffer);
|
|
1247
|
+
break;
|
|
917
1248
|
}
|
|
1249
|
+
buffer += decoder.decode(value, { stream: true });
|
|
1250
|
+
const lines = buffer.split("\n");
|
|
1251
|
+
buffer = lines.pop() ?? "";
|
|
1252
|
+
for (const line of lines) handle(line);
|
|
918
1253
|
}
|
|
919
|
-
return {
|
|
1254
|
+
return { turnId, receivedContent };
|
|
920
1255
|
}
|
|
921
|
-
function
|
|
922
|
-
const
|
|
923
|
-
|
|
924
|
-
const
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
signal: controller.signal,
|
|
934
|
-
body: JSON.stringify({
|
|
935
|
-
...extra,
|
|
936
|
-
id: submission.id,
|
|
937
|
-
outcome: submission.outcome,
|
|
938
|
-
...submission.data ? { data: submission.data } : {}
|
|
939
|
-
})
|
|
940
|
-
});
|
|
941
|
-
if (res.ok) return { ok: true };
|
|
942
|
-
const failure = await responseErrorMessage(res);
|
|
943
|
-
return { ok: false, expired: res.status === 410, message: failure.message };
|
|
944
|
-
} catch (err) {
|
|
945
|
-
if (controller.signal.aborted) {
|
|
946
|
-
return { ok: false, expired: false, message: INTERACTION_SUBMIT_TIMEOUT_MESSAGE };
|
|
947
|
-
}
|
|
948
|
-
return { ok: false, expired: false, message: err instanceof Error ? err.message : "Failed to submit the answer" };
|
|
949
|
-
} finally {
|
|
950
|
-
clearTimeout(timer);
|
|
1256
|
+
async function streamChatTurn(opts) {
|
|
1257
|
+
const res = await opts.start();
|
|
1258
|
+
if (!res.ok || !res.body) {
|
|
1259
|
+
const err = await res.json().catch(() => ({ error: `HTTP ${res.status}` }));
|
|
1260
|
+
throw new Error(err.error ?? `HTTP ${res.status}`);
|
|
1261
|
+
}
|
|
1262
|
+
let turnId = null;
|
|
1263
|
+
const cb = {
|
|
1264
|
+
...opts.callbacks,
|
|
1265
|
+
onTurnId: (id) => {
|
|
1266
|
+
turnId = id;
|
|
1267
|
+
opts.callbacks.onTurnId?.(id);
|
|
951
1268
|
}
|
|
952
1269
|
};
|
|
1270
|
+
try {
|
|
1271
|
+
return await consumeChatStream(res.body, cb);
|
|
1272
|
+
} catch (transportErr) {
|
|
1273
|
+
if (!turnId || !opts.resume) throw transportErr;
|
|
1274
|
+
opts.onResetForResume?.();
|
|
1275
|
+
const resumed = await opts.resume(turnId, 0);
|
|
1276
|
+
if (!resumed.ok || !resumed.body) throw transportErr;
|
|
1277
|
+
return await consumeChatStream(resumed.body, cb);
|
|
1278
|
+
}
|
|
953
1279
|
}
|
|
954
1280
|
|
|
955
|
-
// src/web-react/
|
|
956
|
-
import {
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
1281
|
+
// src/web-react/chat-composer.tsx
|
|
1282
|
+
import {
|
|
1283
|
+
useCallback,
|
|
1284
|
+
useEffect as useEffect5,
|
|
1285
|
+
useRef as useRef5,
|
|
1286
|
+
useState as useState6
|
|
1287
|
+
} from "react";
|
|
1288
|
+
import { Fragment as Fragment3, jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1289
|
+
function SendGlyph({ className }) {
|
|
1290
|
+
return /* @__PURE__ */ jsx8("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: /* @__PURE__ */ jsx8("path", { d: "M22 2 11 13M22 2l-7 20-4-9-9-4 20-7z" }) });
|
|
960
1291
|
}
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
default: "border-transparent bg-primary text-primary-foreground",
|
|
964
|
-
destructive: "border-transparent bg-destructive/15 text-destructive"
|
|
965
|
-
};
|
|
966
|
-
function InteractionBadge({ variant, children }) {
|
|
967
|
-
return /* @__PURE__ */ jsx5("span", { className: `inline-flex items-center rounded-md border px-2 py-0.5 text-xs font-medium ${BADGE_VARIANT_CLASSES[variant]}`, children });
|
|
1292
|
+
function StopGlyph({ className }) {
|
|
1293
|
+
return /* @__PURE__ */ jsx8("svg", { className, viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": true, children: /* @__PURE__ */ jsx8("rect", { x: "6", y: "6", width: "12", height: "12", rx: "2" }) });
|
|
968
1294
|
}
|
|
969
|
-
function
|
|
970
|
-
|
|
971
|
-
onClick,
|
|
972
|
-
disabled,
|
|
973
|
-
children
|
|
974
|
-
}) {
|
|
975
|
-
const variantClasses = variant === "primary" ? "bg-primary text-primary-foreground shadow-sm hover:bg-primary/90" : "border border-border bg-transparent text-foreground hover:bg-accent/40";
|
|
976
|
-
return /* @__PURE__ */ jsx5(
|
|
977
|
-
"button",
|
|
978
|
-
{
|
|
979
|
-
type: "button",
|
|
980
|
-
onClick,
|
|
981
|
-
disabled,
|
|
982
|
-
className: `inline-flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-sm font-medium transition disabled:cursor-not-allowed disabled:opacity-40 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring ${variantClasses}`,
|
|
983
|
-
children
|
|
984
|
-
}
|
|
985
|
-
);
|
|
1295
|
+
function PaperclipGlyph({ className }) {
|
|
1296
|
+
return /* @__PURE__ */ jsx8("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: /* @__PURE__ */ jsx8("path", { d: "m21.44 11.05-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48" }) });
|
|
986
1297
|
}
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
multi,
|
|
993
|
-
selectedValues,
|
|
994
|
-
disabled,
|
|
995
|
-
onToggle
|
|
996
|
-
}) {
|
|
997
|
-
return /* @__PURE__ */ jsx5(Fragment3, { children: options.map((option, optionIndex) => {
|
|
998
|
-
const inputId = `${idPrefix}-${optionIndex}`;
|
|
999
|
-
const checked = selectedValues.includes(option.value);
|
|
1000
|
-
return /* @__PURE__ */ jsxs4("label", { htmlFor: inputId, className: "flex cursor-pointer gap-2 rounded-lg border border-border/70 p-3 transition-colors hover:bg-muted/50", children: [
|
|
1001
|
-
/* @__PURE__ */ jsx5(
|
|
1002
|
-
"input",
|
|
1003
|
-
{
|
|
1004
|
-
id: inputId,
|
|
1005
|
-
type: multi ? "checkbox" : "radio",
|
|
1006
|
-
name: groupName,
|
|
1007
|
-
value: option.value,
|
|
1008
|
-
checked,
|
|
1009
|
-
disabled,
|
|
1010
|
-
onChange: () => onToggle(option.value),
|
|
1011
|
-
"aria-labelledby": `${inputId}-label`,
|
|
1012
|
-
"aria-describedby": option.description ? `${inputId}-description` : void 0,
|
|
1013
|
-
className: "mt-0.5 h-4 w-4 shrink-0 accent-primary"
|
|
1014
|
-
}
|
|
1015
|
-
),
|
|
1016
|
-
/* @__PURE__ */ jsxs4("span", { className: "min-w-0", children: [
|
|
1017
|
-
/* @__PURE__ */ jsx5("span", { id: `${inputId}-label`, className: "block text-sm font-medium leading-5 text-foreground", children: option.label }),
|
|
1018
|
-
option.description && /* @__PURE__ */ jsx5("span", { id: `${inputId}-description`, className: "mt-0.5 block text-xs leading-5 text-muted-foreground", children: option.description })
|
|
1019
|
-
] })
|
|
1020
|
-
] }, `${option.value}-${optionIndex}`);
|
|
1021
|
-
}) });
|
|
1298
|
+
function FolderGlyph({ className }) {
|
|
1299
|
+
return /* @__PURE__ */ jsxs6("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: [
|
|
1300
|
+
/* @__PURE__ */ jsx8("path", { d: "M4 20h16a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.93a2 2 0 0 1-1.66-.9l-.82-1.2A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13c0 1.1.9 2 2 2Z" }),
|
|
1301
|
+
/* @__PURE__ */ jsx8("path", { d: "M12 10v6m-3-3h6" })
|
|
1302
|
+
] });
|
|
1022
1303
|
}
|
|
1023
|
-
function
|
|
1024
|
-
return
|
|
1304
|
+
function CloseGlyph({ className }) {
|
|
1305
|
+
return /* @__PURE__ */ jsx8("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", "aria-hidden": true, children: /* @__PURE__ */ jsx8("path", { d: "M18 6 6 18M6 6l12 12" }) });
|
|
1025
1306
|
}
|
|
1026
|
-
function
|
|
1027
|
-
|
|
1028
|
-
let selected = [optionValue];
|
|
1029
|
-
if (field.multi === true) {
|
|
1030
|
-
selected = current.includes(optionValue) ? current.filter((item) => item !== optionValue) : [...current, optionValue];
|
|
1031
|
-
}
|
|
1032
|
-
return { ...values, [field.name]: { ...values[field.name], selected } };
|
|
1307
|
+
function UploadGlyph({ className }) {
|
|
1308
|
+
return /* @__PURE__ */ jsx8("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: /* @__PURE__ */ jsx8("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M17 8l-5-5-5 5M12 3v12" }) });
|
|
1033
1309
|
}
|
|
1034
|
-
var
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1310
|
+
var MAX_HEIGHT = 168;
|
|
1311
|
+
function ChatComposer({
|
|
1312
|
+
onSend,
|
|
1313
|
+
onSendParts,
|
|
1314
|
+
onCancel,
|
|
1315
|
+
isStreaming = false,
|
|
1316
|
+
disabled = false,
|
|
1317
|
+
placeholder = "Message the agent\u2026",
|
|
1318
|
+
value,
|
|
1319
|
+
onValueChange,
|
|
1320
|
+
initialValue,
|
|
1321
|
+
seed,
|
|
1322
|
+
onSeedApplied,
|
|
1323
|
+
controls,
|
|
1324
|
+
controlsPlacement = "above",
|
|
1325
|
+
onAttach,
|
|
1326
|
+
onAttachFolder,
|
|
1327
|
+
pendingFiles = [],
|
|
1328
|
+
onRemoveFile,
|
|
1329
|
+
accept,
|
|
1330
|
+
dropTitle = "Drop files to add context",
|
|
1331
|
+
dropDescription = "They attach to your next message.",
|
|
1332
|
+
focusShortcut = true,
|
|
1333
|
+
sendLabel = "Send",
|
|
1049
1334
|
className
|
|
1050
1335
|
}) {
|
|
1051
|
-
const
|
|
1052
|
-
const [
|
|
1053
|
-
const
|
|
1054
|
-
const
|
|
1055
|
-
const
|
|
1056
|
-
const
|
|
1057
|
-
const
|
|
1058
|
-
const
|
|
1059
|
-
const
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1336
|
+
const isControlled = value !== void 0;
|
|
1337
|
+
const [internal, setInternal] = useState6(initialValue ?? "");
|
|
1338
|
+
const text = isControlled ? value : internal;
|
|
1339
|
+
const textareaRef = useRef5(null);
|
|
1340
|
+
const fileInputRef = useRef5(null);
|
|
1341
|
+
const folderInputRef = useRef5(null);
|
|
1342
|
+
const [dragOver, setDragOver] = useState6(false);
|
|
1343
|
+
const dragDepth = useRef5(0);
|
|
1344
|
+
const setText = useCallback(
|
|
1345
|
+
(next) => {
|
|
1346
|
+
if (!isControlled) setInternal(next);
|
|
1347
|
+
onValueChange?.(next);
|
|
1348
|
+
},
|
|
1349
|
+
[isControlled, onValueChange]
|
|
1350
|
+
);
|
|
1351
|
+
useEffect5(() => {
|
|
1352
|
+
const el = textareaRef.current;
|
|
1353
|
+
if (!el) return;
|
|
1354
|
+
el.style.height = "auto";
|
|
1355
|
+
el.style.height = `${Math.min(el.scrollHeight, MAX_HEIGHT)}px`;
|
|
1356
|
+
}, [text]);
|
|
1357
|
+
const prevSeedRef = useRef5(null);
|
|
1358
|
+
const pendingCaretRef = useRef5(null);
|
|
1359
|
+
useEffect5(() => {
|
|
1360
|
+
const prev = prevSeedRef.current;
|
|
1361
|
+
prevSeedRef.current = seed ?? null;
|
|
1362
|
+
if (seed == null || seed === prev || isControlled) return;
|
|
1363
|
+
setText(seed);
|
|
1364
|
+
onSeedApplied?.();
|
|
1365
|
+
const el = textareaRef.current;
|
|
1366
|
+
if (el && el.value === seed) {
|
|
1367
|
+
el.focus();
|
|
1368
|
+
el.setSelectionRange(seed.length, seed.length);
|
|
1369
|
+
} else {
|
|
1370
|
+
pendingCaretRef.current = seed;
|
|
1371
|
+
}
|
|
1372
|
+
}, [seed, setText, onSeedApplied, isControlled]);
|
|
1373
|
+
useEffect5(() => {
|
|
1374
|
+
if (pendingCaretRef.current == null || pendingCaretRef.current !== text)
|
|
1375
|
+
return;
|
|
1376
|
+
pendingCaretRef.current = null;
|
|
1377
|
+
const el = textareaRef.current;
|
|
1378
|
+
if (!el) return;
|
|
1379
|
+
el.focus();
|
|
1380
|
+
el.setSelectionRange(text.length, text.length);
|
|
1381
|
+
}, [text]);
|
|
1382
|
+
useEffect5(() => {
|
|
1383
|
+
if (!focusShortcut || disabled) return;
|
|
1384
|
+
function onKeyDown(e) {
|
|
1385
|
+
if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "l") {
|
|
1386
|
+
e.preventDefault();
|
|
1387
|
+
textareaRef.current?.focus();
|
|
1388
|
+
}
|
|
1085
1389
|
}
|
|
1086
|
-
|
|
1087
|
-
|
|
1390
|
+
document.addEventListener("keydown", onKeyDown);
|
|
1391
|
+
return () => document.removeEventListener("keydown", onKeyDown);
|
|
1392
|
+
}, [focusShortcut, disabled]);
|
|
1393
|
+
const readyParts = pendingFiles.filter((f) => f.status === "ready" && f.part).map((f) => f.part);
|
|
1394
|
+
const hasSendable = onSendParts ? text.trim().length > 0 || readyParts.length > 0 : text.trim().length > 0;
|
|
1395
|
+
const canSend = hasSendable && !isStreaming && !disabled;
|
|
1396
|
+
const send = useCallback(() => {
|
|
1397
|
+
const trimmed = text.trim();
|
|
1398
|
+
if (isStreaming || disabled) return;
|
|
1399
|
+
if (onSendParts) {
|
|
1400
|
+
const parts = pendingFiles.filter((f) => f.status === "ready" && f.part).map((f) => f.part);
|
|
1401
|
+
if (!trimmed && parts.length === 0) return;
|
|
1402
|
+
onSendParts(trimmed, parts);
|
|
1403
|
+
setText("");
|
|
1088
1404
|
return;
|
|
1089
1405
|
}
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1406
|
+
if (!trimmed) return;
|
|
1407
|
+
onSend?.(trimmed);
|
|
1408
|
+
setText("");
|
|
1409
|
+
}, [text, isStreaming, disabled, onSend, onSendParts, pendingFiles, setText]);
|
|
1410
|
+
const handleKeyDown = (e) => {
|
|
1411
|
+
if (e.nativeEvent.isComposing) return;
|
|
1412
|
+
if (e.key === "Enter" && !e.shiftKey) {
|
|
1413
|
+
e.preventDefault();
|
|
1414
|
+
send();
|
|
1096
1415
|
}
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1416
|
+
};
|
|
1417
|
+
const handleFileChange = (e) => {
|
|
1418
|
+
if (e.target.files?.length) onAttach?.(e.target.files);
|
|
1419
|
+
e.target.value = "";
|
|
1420
|
+
};
|
|
1421
|
+
const handleFolderChange = (e) => {
|
|
1422
|
+
if (e.target.files?.length) (onAttachFolder ?? onAttach)?.(e.target.files);
|
|
1423
|
+
e.target.value = "";
|
|
1424
|
+
};
|
|
1425
|
+
const handleDragEnter = useCallback((e) => {
|
|
1426
|
+
e.preventDefault();
|
|
1427
|
+
e.stopPropagation();
|
|
1428
|
+
dragDepth.current++;
|
|
1429
|
+
if (e.dataTransfer?.types.includes("Files")) setDragOver(true);
|
|
1430
|
+
}, []);
|
|
1431
|
+
const handleDragLeave = useCallback((e) => {
|
|
1432
|
+
e.preventDefault();
|
|
1433
|
+
e.stopPropagation();
|
|
1434
|
+
dragDepth.current--;
|
|
1435
|
+
if (dragDepth.current <= 0) {
|
|
1436
|
+
dragDepth.current = 0;
|
|
1437
|
+
setDragOver(false);
|
|
1117
1438
|
}
|
|
1118
|
-
}
|
|
1119
|
-
const
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1439
|
+
}, []);
|
|
1440
|
+
const handleDragOver = useCallback((e) => {
|
|
1441
|
+
e.preventDefault();
|
|
1442
|
+
e.stopPropagation();
|
|
1443
|
+
if (e.dataTransfer) e.dataTransfer.dropEffect = "copy";
|
|
1444
|
+
}, []);
|
|
1445
|
+
const handleDrop = useCallback(
|
|
1446
|
+
(e) => {
|
|
1447
|
+
e.preventDefault();
|
|
1448
|
+
e.stopPropagation();
|
|
1449
|
+
dragDepth.current = 0;
|
|
1450
|
+
setDragOver(false);
|
|
1451
|
+
const files = e.dataTransfer?.files;
|
|
1452
|
+
if (files?.length) onAttach?.(files);
|
|
1453
|
+
},
|
|
1454
|
+
[onAttach]
|
|
1455
|
+
);
|
|
1456
|
+
const folderChips = pendingFiles.filter((f) => f.kind === "folder");
|
|
1457
|
+
const fileChips = pendingFiles.filter((f) => f.kind !== "folder");
|
|
1458
|
+
const showFooter = controls != null && controlsPlacement === "footer";
|
|
1459
|
+
const showAbove = controls != null && controlsPlacement === "above";
|
|
1460
|
+
return /* @__PURE__ */ jsxs6(
|
|
1461
|
+
"div",
|
|
1462
|
+
{
|
|
1463
|
+
className: `relative ${className ?? ""}`,
|
|
1464
|
+
onDragEnter: onAttach ? handleDragEnter : void 0,
|
|
1465
|
+
onDragLeave: onAttach ? handleDragLeave : void 0,
|
|
1466
|
+
onDragOver: onAttach ? handleDragOver : void 0,
|
|
1467
|
+
onDrop: onAttach ? handleDrop : void 0,
|
|
1468
|
+
children: [
|
|
1469
|
+
dragOver && /* @__PURE__ */ jsx8("div", { className: "pointer-events-none absolute inset-0 z-10 flex items-center justify-center rounded-2xl border-2 border-dashed border-primary/50 bg-card/95", children: /* @__PURE__ */ jsxs6("div", { className: "text-center", children: [
|
|
1470
|
+
/* @__PURE__ */ jsx8("span", { className: "mx-auto mb-2 flex h-11 w-11 items-center justify-center rounded-xl bg-primary/10 text-primary", children: /* @__PURE__ */ jsx8(UploadGlyph, { className: "h-5 w-5" }) }),
|
|
1471
|
+
/* @__PURE__ */ jsx8("p", { className: "text-sm font-semibold text-foreground", children: dropTitle }),
|
|
1472
|
+
/* @__PURE__ */ jsx8("p", { className: "mt-0.5 text-xs text-muted-foreground", children: dropDescription })
|
|
1473
|
+
] }) }),
|
|
1474
|
+
showAbove && /* @__PURE__ */ jsx8("div", { className: "mb-1.5 flex flex-wrap items-center gap-1.5 px-1", children: controls }),
|
|
1475
|
+
pendingFiles.length > 0 && /* @__PURE__ */ jsx8("div", { className: "mb-2 flex flex-wrap gap-1.5", children: [...folderChips, ...fileChips].map((f) => /* @__PURE__ */ jsxs6(
|
|
1476
|
+
"span",
|
|
1477
|
+
{
|
|
1478
|
+
className: `inline-flex items-center gap-1.5 rounded-full border px-2.5 py-1 text-xs ${f.status === "error" ? "border-destructive/40 text-destructive" : "border-border bg-muted/50 text-foreground"}`,
|
|
1479
|
+
children: [
|
|
1480
|
+
f.kind === "folder" ? /* @__PURE__ */ jsx8(FolderGlyph, { className: "h-3 w-3 shrink-0" }) : /* @__PURE__ */ jsx8(PaperclipGlyph, { className: "h-3 w-3 shrink-0" }),
|
|
1481
|
+
/* @__PURE__ */ jsx8("span", { className: "max-w-[150px] truncate", children: f.name }),
|
|
1482
|
+
f.fileCount !== void 0 && /* @__PURE__ */ jsxs6("span", { className: "text-muted-foreground", children: [
|
|
1483
|
+
"(",
|
|
1484
|
+
f.fileCount,
|
|
1485
|
+
")"
|
|
1486
|
+
] }),
|
|
1487
|
+
f.status === "uploading" && /* @__PURE__ */ jsx8("span", { className: "h-3 w-3 animate-spin rounded-full border-2 border-primary border-t-transparent" }),
|
|
1488
|
+
onRemoveFile && /* @__PURE__ */ jsx8(
|
|
1489
|
+
"button",
|
|
1490
|
+
{
|
|
1491
|
+
type: "button",
|
|
1492
|
+
"aria-label": `Remove ${f.name}`,
|
|
1493
|
+
onClick: () => onRemoveFile(f.id),
|
|
1494
|
+
className: "rounded p-0.5 text-muted-foreground transition hover:text-foreground focus:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
1495
|
+
children: /* @__PURE__ */ jsx8(CloseGlyph, { className: "h-3 w-3" })
|
|
1496
|
+
}
|
|
1497
|
+
)
|
|
1498
|
+
]
|
|
1499
|
+
},
|
|
1500
|
+
f.id
|
|
1501
|
+
)) }),
|
|
1502
|
+
/* @__PURE__ */ jsxs6("div", { className: "flex items-end gap-2 rounded-2xl border border-border bg-card px-2.5 py-2 transition focus-within:border-primary/40 focus-within:ring-2 focus-within:ring-primary/15", children: [
|
|
1503
|
+
onAttach && /* @__PURE__ */ jsxs6(Fragment3, { children: [
|
|
1504
|
+
/* @__PURE__ */ jsx8(
|
|
1505
|
+
"button",
|
|
1506
|
+
{
|
|
1507
|
+
type: "button",
|
|
1508
|
+
onClick: () => fileInputRef.current?.click(),
|
|
1509
|
+
disabled,
|
|
1510
|
+
"aria-label": "Attach files",
|
|
1511
|
+
title: "Attach files",
|
|
1512
|
+
className: "mb-0.5 shrink-0 rounded-lg p-2 text-muted-foreground transition hover:bg-accent/40 hover:text-foreground disabled:opacity-50 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
1513
|
+
children: /* @__PURE__ */ jsx8(PaperclipGlyph, { className: "h-4 w-4" })
|
|
1514
|
+
}
|
|
1515
|
+
),
|
|
1516
|
+
/* @__PURE__ */ jsx8("input", { ref: fileInputRef, type: "file", multiple: true, className: "hidden", accept, onChange: handleFileChange })
|
|
1517
|
+
] }),
|
|
1518
|
+
onAttachFolder && /* @__PURE__ */ jsxs6(Fragment3, { children: [
|
|
1519
|
+
/* @__PURE__ */ jsx8(
|
|
1520
|
+
"button",
|
|
1521
|
+
{
|
|
1522
|
+
type: "button",
|
|
1523
|
+
onClick: () => folderInputRef.current?.click(),
|
|
1524
|
+
disabled,
|
|
1525
|
+
"aria-label": "Attach folder",
|
|
1526
|
+
title: "Attach folder",
|
|
1527
|
+
className: "mb-0.5 shrink-0 rounded-lg p-2 text-muted-foreground transition hover:bg-accent/40 hover:text-foreground disabled:opacity-50 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
1528
|
+
children: /* @__PURE__ */ jsx8(FolderGlyph, { className: "h-4 w-4" })
|
|
1529
|
+
}
|
|
1530
|
+
),
|
|
1531
|
+
/* @__PURE__ */ jsx8(
|
|
1532
|
+
"input",
|
|
1533
|
+
{
|
|
1534
|
+
ref: folderInputRef,
|
|
1535
|
+
type: "file",
|
|
1536
|
+
multiple: true,
|
|
1537
|
+
className: "hidden",
|
|
1538
|
+
onChange: handleFolderChange,
|
|
1539
|
+
...{ webkitdirectory: "" }
|
|
1540
|
+
}
|
|
1541
|
+
)
|
|
1542
|
+
] }),
|
|
1543
|
+
/* @__PURE__ */ jsx8(
|
|
1544
|
+
"textarea",
|
|
1145
1545
|
{
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1546
|
+
ref: textareaRef,
|
|
1547
|
+
value: text,
|
|
1548
|
+
onChange: (e) => setText(e.target.value),
|
|
1549
|
+
onKeyDown: handleKeyDown,
|
|
1550
|
+
placeholder,
|
|
1151
1551
|
disabled,
|
|
1152
|
-
|
|
1552
|
+
rows: 1,
|
|
1553
|
+
"aria-label": "Message input",
|
|
1554
|
+
className: "max-h-[168px] min-h-[40px] flex-1 resize-none bg-transparent px-1.5 py-2 text-[15px] leading-6 text-foreground outline-none placeholder:text-muted-foreground disabled:opacity-50"
|
|
1153
1555
|
}
|
|
1154
1556
|
),
|
|
1155
|
-
|
|
1156
|
-
|
|
1557
|
+
showFooter && /* @__PURE__ */ jsx8("div", { className: "mb-0.5 flex shrink-0 items-center gap-1.5", children: controls }),
|
|
1558
|
+
isStreaming ? /* @__PURE__ */ jsxs6(
|
|
1559
|
+
"button",
|
|
1157
1560
|
{
|
|
1158
|
-
type: "
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1561
|
+
type: "button",
|
|
1562
|
+
onClick: onCancel,
|
|
1563
|
+
"aria-label": "Stop response",
|
|
1564
|
+
className: "mb-0.5 inline-flex shrink-0 items-center gap-1.5 rounded-full bg-destructive/15 px-3.5 py-2 text-sm font-medium text-destructive transition hover:bg-destructive/25 focus:outline-none focus-visible:ring-2 focus-visible:ring-destructive/50",
|
|
1565
|
+
children: [
|
|
1566
|
+
/* @__PURE__ */ jsx8(StopGlyph, { className: "h-3.5 w-3.5" }),
|
|
1567
|
+
/* @__PURE__ */ jsx8("span", { children: "Stop" })
|
|
1568
|
+
]
|
|
1165
1569
|
}
|
|
1166
|
-
)
|
|
1167
|
-
|
|
1168
|
-
/* @__PURE__ */ jsx5(
|
|
1169
|
-
"input",
|
|
1570
|
+
) : /* @__PURE__ */ jsxs6(
|
|
1571
|
+
"button",
|
|
1170
1572
|
{
|
|
1171
|
-
type: "
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
disabled,
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
) : field.type === "secret" ? /* @__PURE__ */ jsx5(
|
|
1192
|
-
"input",
|
|
1193
|
-
{
|
|
1194
|
-
type: "password",
|
|
1195
|
-
value: value.text ?? "",
|
|
1196
|
-
disabled,
|
|
1197
|
-
"aria-label": field.label,
|
|
1198
|
-
onChange: (event) => setFieldValue(field.name, { text: event.target.value }),
|
|
1199
|
-
placeholder: field.placeholder,
|
|
1200
|
-
className: FIELD_INPUT_CLASSES
|
|
1201
|
-
}
|
|
1202
|
-
) : /* @__PURE__ */ jsx5(
|
|
1203
|
-
"textarea",
|
|
1204
|
-
{
|
|
1205
|
-
value: value.text ?? "",
|
|
1206
|
-
disabled,
|
|
1207
|
-
"aria-label": field.label,
|
|
1208
|
-
onChange: (event) => setFieldValue(field.name, { text: event.target.value }),
|
|
1209
|
-
rows: 3,
|
|
1210
|
-
placeholder: field.type === "text" ? field.placeholder : void 0,
|
|
1211
|
-
className: FIELD_INPUT_CLASSES
|
|
1212
|
-
}
|
|
1213
|
-
)
|
|
1214
|
-
] }, field.name);
|
|
1215
|
-
}) }),
|
|
1216
|
-
error && /* @__PURE__ */ jsx5("p", { className: "mt-3 text-xs text-destructive", children: error }),
|
|
1217
|
-
terminalNote && /* @__PURE__ */ jsx5("p", { className: "mt-3 text-xs text-muted-foreground", children: terminalNote }),
|
|
1218
|
-
showSubmitButton && /* @__PURE__ */ jsx5("div", { className: "mt-4 flex items-center justify-end gap-2", children: /* @__PURE__ */ jsx5(InteractionActionButton, { onClick: () => void submit(), disabled: disabled || !answerData, children: submitLabel }) }),
|
|
1219
|
-
answered && /* @__PURE__ */ jsx5("div", { className: "mt-4 flex items-center justify-end", children: /* @__PURE__ */ jsxs4("span", { className: "inline-flex items-center gap-1 text-xs text-muted-foreground", children: [
|
|
1220
|
-
/* @__PURE__ */ jsx5(CheckGlyph, { className: "h-3 w-3" }),
|
|
1221
|
-
"Answered"
|
|
1222
|
-
] }) }),
|
|
1223
|
-
lateAnswerSent && /* @__PURE__ */ jsx5("div", { className: "mt-4 flex items-center justify-end", children: /* @__PURE__ */ jsxs4("span", { className: "inline-flex items-center gap-1 text-xs text-muted-foreground", children: [
|
|
1224
|
-
/* @__PURE__ */ jsx5(CheckGlyph, { className: "h-3 w-3" }),
|
|
1225
|
-
"Sent as new message"
|
|
1226
|
-
] }) })
|
|
1227
|
-
] });
|
|
1573
|
+
type: "button",
|
|
1574
|
+
onClick: send,
|
|
1575
|
+
disabled: !canSend,
|
|
1576
|
+
"aria-label": sendLabel,
|
|
1577
|
+
className: "mb-0.5 inline-flex shrink-0 items-center gap-1.5 rounded-full bg-primary px-3.5 py-2 text-sm font-medium text-primary-foreground shadow-sm transition hover:bg-primary/90 disabled:cursor-not-allowed disabled:opacity-40 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-card",
|
|
1578
|
+
children: [
|
|
1579
|
+
/* @__PURE__ */ jsx8(SendGlyph, { className: "h-3.5 w-3.5" }),
|
|
1580
|
+
/* @__PURE__ */ jsx8("span", { children: sendLabel })
|
|
1581
|
+
]
|
|
1582
|
+
}
|
|
1583
|
+
)
|
|
1584
|
+
] }),
|
|
1585
|
+
focusShortcut && /* @__PURE__ */ jsx8("div", { className: "mt-1.5 flex justify-end px-1", children: /* @__PURE__ */ jsxs6("span", { className: "text-xs text-muted-foreground", children: [
|
|
1586
|
+
/* @__PURE__ */ jsx8("kbd", { className: "rounded border border-border bg-background px-1 py-0.5 text-[10px]", children: "Cmd" }),
|
|
1587
|
+
/* @__PURE__ */ jsx8("kbd", { className: "ml-0.5 rounded border border-border bg-background px-1 py-0.5 text-[10px]", children: "L" }),
|
|
1588
|
+
/* @__PURE__ */ jsx8("span", { className: "ml-1", children: "to focus" })
|
|
1589
|
+
] }) })
|
|
1590
|
+
]
|
|
1591
|
+
}
|
|
1592
|
+
);
|
|
1228
1593
|
}
|
|
1229
1594
|
|
|
1230
|
-
// src/web-react/
|
|
1231
|
-
import {
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1595
|
+
// src/web-react/durable-plan-flow.ts
|
|
1596
|
+
import { useCallback as useCallback2, useEffect as useEffect6, useRef as useRef6, useState as useState7 } from "react";
|
|
1597
|
+
var DurablePlanClientError = class extends Error {
|
|
1598
|
+
constructor(message, status, code, currentPlan) {
|
|
1599
|
+
super(message);
|
|
1600
|
+
this.status = status;
|
|
1601
|
+
this.code = code;
|
|
1602
|
+
this.currentPlan = currentPlan;
|
|
1603
|
+
this.name = "DurablePlanClientError";
|
|
1604
|
+
}
|
|
1605
|
+
status;
|
|
1606
|
+
code;
|
|
1607
|
+
currentPlan;
|
|
1608
|
+
};
|
|
1609
|
+
function recordOf(value) {
|
|
1610
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
1611
|
+
}
|
|
1612
|
+
function readPlan(value) {
|
|
1613
|
+
const plan = recordOf(value);
|
|
1614
|
+
if (!plan) return null;
|
|
1615
|
+
const planId = typeof plan.planId === "string" ? plan.planId : typeof plan.id === "string" ? plan.id : null;
|
|
1616
|
+
if (!planId || typeof plan.revision !== "number" || typeof plan.body !== "string" || typeof plan.submittedAt !== "string" || typeof plan.status !== "string") return null;
|
|
1617
|
+
return { ...plan, planId };
|
|
1618
|
+
}
|
|
1619
|
+
function receiptIdentity(plan, followUp) {
|
|
1620
|
+
if (typeof followUp.receiptId === "string" && followUp.receiptId) return followUp.receiptId;
|
|
1621
|
+
const turnId = typeof followUp.turnId === "string" ? followUp.turnId : "";
|
|
1622
|
+
return `${plan.planId}:${plan.revision}:${turnId}`;
|
|
1623
|
+
}
|
|
1624
|
+
function parseDecisionResult(value) {
|
|
1625
|
+
const body = recordOf(value);
|
|
1626
|
+
const plan = readPlan(body?.plan);
|
|
1627
|
+
if (!body || !plan) return null;
|
|
1628
|
+
const rawFollowUp = recordOf(body.followUp) ?? recordOf(body.receipt);
|
|
1629
|
+
const followUp = rawFollowUp && typeof rawFollowUp.turnId === "string" ? {
|
|
1630
|
+
receiptId: receiptIdentity(plan, rawFollowUp),
|
|
1631
|
+
planId: plan.planId,
|
|
1632
|
+
revision: plan.revision,
|
|
1633
|
+
turnId: rawFollowUp.turnId,
|
|
1634
|
+
state: typeof rawFollowUp.state === "string" ? rawFollowUp.state : "unknown"
|
|
1635
|
+
} : void 0;
|
|
1636
|
+
return {
|
|
1637
|
+
plan,
|
|
1638
|
+
...followUp ? { followUp } : {},
|
|
1639
|
+
idempotent: body.idempotent === true || body.replayed === true,
|
|
1640
|
+
...body.projectionPending === true ? { projectionPending: true } : {},
|
|
1641
|
+
...body.effectPending === true ? { effectPending: true } : {}
|
|
1642
|
+
};
|
|
1235
1643
|
}
|
|
1236
|
-
function
|
|
1237
|
-
return
|
|
1644
|
+
async function responseBody(response) {
|
|
1645
|
+
return recordOf(await response.json().catch(() => null)) ?? {};
|
|
1646
|
+
}
|
|
1647
|
+
function createDurablePlanDecisionClient(options) {
|
|
1648
|
+
const fetchImpl = options.fetchImpl ?? fetch;
|
|
1649
|
+
const urlFor = (input) => typeof options.url === "function" ? options.url(input) : options.url;
|
|
1650
|
+
const read = async (response) => {
|
|
1651
|
+
const body = await responseBody(response);
|
|
1652
|
+
const result = parseDecisionResult(body);
|
|
1653
|
+
if (response.ok && result) return result;
|
|
1654
|
+
const currentPlan = readPlan(body.plan) ?? void 0;
|
|
1655
|
+
const message = typeof body.error === "string" ? body.error : typeof body.message === "string" ? body.message : `Plan request failed (${response.status})`;
|
|
1656
|
+
throw new DurablePlanClientError(
|
|
1657
|
+
message,
|
|
1658
|
+
response.status,
|
|
1659
|
+
typeof body.code === "string" ? body.code : void 0,
|
|
1660
|
+
currentPlan
|
|
1661
|
+
);
|
|
1662
|
+
};
|
|
1663
|
+
return {
|
|
1664
|
+
async current(input) {
|
|
1665
|
+
const rawUrl = urlFor(input);
|
|
1666
|
+
const url = new URL(rawUrl, globalThis.location?.origin ?? "http://localhost");
|
|
1667
|
+
url.searchParams.set("planId", input.planId);
|
|
1668
|
+
if (input.revision !== void 0) url.searchParams.set("revision", String(input.revision));
|
|
1669
|
+
const target = /^https?:/.test(rawUrl) ? url.toString() : `${url.pathname}${url.search}`;
|
|
1670
|
+
return read(await fetchImpl(target, { method: "GET" }));
|
|
1671
|
+
},
|
|
1672
|
+
async decide(input) {
|
|
1673
|
+
const extra = typeof options.body === "function" ? options.body(input) : options.body ?? {};
|
|
1674
|
+
return read(await fetchImpl(urlFor(input), {
|
|
1675
|
+
method: "POST",
|
|
1676
|
+
headers: { "Content-Type": "application/json" },
|
|
1677
|
+
body: JSON.stringify({ ...extra, ...input })
|
|
1678
|
+
}));
|
|
1679
|
+
}
|
|
1680
|
+
};
|
|
1238
1681
|
}
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
const [expanded, setExpanded] = useState5(false);
|
|
1258
|
-
const [submitting, setSubmitting] = useState5(null);
|
|
1259
|
-
const [localStatus, setLocalStatus] = useState5(null);
|
|
1260
|
-
const [error, setError] = useState5(null);
|
|
1261
|
-
const submitInFlightRef = useRef5(false);
|
|
1262
|
-
const status = isTerminalInteractionStatus(interaction.status) ? interaction.status : localStatus ?? interaction.status;
|
|
1263
|
-
const disabled = !canWrite || status !== "pending" || submitting !== null;
|
|
1264
|
-
const approveData = useMemo3(() => buildAnswerData(interaction.fields, values), [interaction.fields, values]);
|
|
1265
|
-
const rejectData = useMemo3(() => {
|
|
1266
|
-
const data = {};
|
|
1267
|
-
for (const field of interaction.fields) {
|
|
1268
|
-
const answer = fieldAnswer(field, values);
|
|
1269
|
-
if (answer !== null) data[field.name] = answer;
|
|
1682
|
+
function useDurablePlanFlow(options) {
|
|
1683
|
+
const [plan, setPlan] = useState7(options.plan);
|
|
1684
|
+
const [deciding, setDeciding] = useState7(null);
|
|
1685
|
+
const [restoring, setRestoring] = useState7(false);
|
|
1686
|
+
const [error, setError] = useState7(null);
|
|
1687
|
+
const attachments = useRef6(/* @__PURE__ */ new Map());
|
|
1688
|
+
const decisionInFlight = useRef6(false);
|
|
1689
|
+
useEffect6(() => setPlan(options.plan), [options.plan]);
|
|
1690
|
+
const apply = useCallback2(async (result) => {
|
|
1691
|
+
setPlan(result.plan);
|
|
1692
|
+
options.onUpdated?.(result.plan);
|
|
1693
|
+
const receipt = result.followUp;
|
|
1694
|
+
if (!receipt || !options.attachFollowUp) return;
|
|
1695
|
+
let pending = attachments.current.get(receipt.receiptId);
|
|
1696
|
+
if (!pending) {
|
|
1697
|
+
pending = Promise.resolve(options.attachFollowUp(receipt));
|
|
1698
|
+
attachments.current.set(receipt.receiptId, pending);
|
|
1699
|
+
void pending.finally(() => attachments.current.delete(receipt.receiptId));
|
|
1270
1700
|
}
|
|
1271
|
-
|
|
1272
|
-
}, [
|
|
1273
|
-
async
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
setSubmitting(outcome === "accepted" ? "approve" : "reject");
|
|
1701
|
+
await pending;
|
|
1702
|
+
}, [options.attachFollowUp, options.onUpdated]);
|
|
1703
|
+
const decide = useCallback2(async (decision, feedback) => {
|
|
1704
|
+
if (decisionInFlight.current) return null;
|
|
1705
|
+
decisionInFlight.current = true;
|
|
1706
|
+
setDeciding(decision);
|
|
1278
1707
|
setError(null);
|
|
1279
1708
|
try {
|
|
1280
|
-
const result = await
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1709
|
+
const result = await options.client.decide({
|
|
1710
|
+
planId: plan.planId,
|
|
1711
|
+
revision: plan.revision,
|
|
1712
|
+
decision,
|
|
1713
|
+
...feedback?.trim() ? { feedback: feedback.trim() } : {}
|
|
1714
|
+
});
|
|
1715
|
+
await apply(result);
|
|
1716
|
+
return result;
|
|
1717
|
+
} catch (cause) {
|
|
1718
|
+
if (cause instanceof DurablePlanClientError && cause.currentPlan) {
|
|
1719
|
+
setPlan(cause.currentPlan);
|
|
1720
|
+
options.onUpdated?.(cause.currentPlan);
|
|
1291
1721
|
}
|
|
1292
|
-
setError(
|
|
1722
|
+
setError(cause instanceof Error ? cause.message : "Could not decide the plan.");
|
|
1723
|
+
return null;
|
|
1293
1724
|
} finally {
|
|
1294
|
-
|
|
1295
|
-
|
|
1725
|
+
decisionInFlight.current = false;
|
|
1726
|
+
setDeciding(null);
|
|
1727
|
+
}
|
|
1728
|
+
}, [apply, options.client, options.onUpdated, plan.planId, plan.revision]);
|
|
1729
|
+
const restore = useCallback2(async () => {
|
|
1730
|
+
setRestoring(true);
|
|
1731
|
+
setError(null);
|
|
1732
|
+
try {
|
|
1733
|
+
const result = await options.client.current({ planId: plan.planId, revision: plan.revision });
|
|
1734
|
+
await apply(result);
|
|
1735
|
+
return result;
|
|
1736
|
+
} catch (cause) {
|
|
1737
|
+
setError(cause instanceof Error ? cause.message : "Could not restore the plan.");
|
|
1738
|
+
return null;
|
|
1739
|
+
} finally {
|
|
1740
|
+
setRestoring(false);
|
|
1296
1741
|
}
|
|
1742
|
+
}, [apply, options.client, plan.planId, plan.revision]);
|
|
1743
|
+
return { plan, deciding, restoring, error, decide, restore, clearError: () => setError(null) };
|
|
1744
|
+
}
|
|
1745
|
+
|
|
1746
|
+
// src/web-react/durable-interaction-submit.ts
|
|
1747
|
+
function attemptStorageKey(namespace, interactionId) {
|
|
1748
|
+
return `${namespace}:${encodeURIComponent(interactionId)}`;
|
|
1749
|
+
}
|
|
1750
|
+
function storedAttempts(storage, key) {
|
|
1751
|
+
try {
|
|
1752
|
+
const value = JSON.parse(storage.getItem(key) ?? "{}");
|
|
1753
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
1754
|
+
} catch {
|
|
1755
|
+
return {};
|
|
1297
1756
|
}
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
return
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1757
|
+
}
|
|
1758
|
+
function createSessionInteractionAttemptStore(storage, namespace = "agent-app:interaction-attempt") {
|
|
1759
|
+
return {
|
|
1760
|
+
get(id, signature) {
|
|
1761
|
+
return storedAttempts(storage, attemptStorageKey(namespace, id))[signature] ?? null;
|
|
1762
|
+
},
|
|
1763
|
+
set(id, signature, attemptKey) {
|
|
1764
|
+
const key = attemptStorageKey(namespace, id);
|
|
1765
|
+
storage.setItem(key, JSON.stringify({ ...storedAttempts(storage, key), [signature]: attemptKey }));
|
|
1766
|
+
},
|
|
1767
|
+
delete(id, signature) {
|
|
1768
|
+
const key = attemptStorageKey(namespace, id);
|
|
1769
|
+
const attempts = storedAttempts(storage, key);
|
|
1770
|
+
delete attempts[signature];
|
|
1771
|
+
if (Object.keys(attempts).length === 0) storage.removeItem(key);
|
|
1772
|
+
else storage.setItem(key, JSON.stringify(attempts));
|
|
1773
|
+
}
|
|
1774
|
+
};
|
|
1775
|
+
}
|
|
1776
|
+
function createMemoryInteractionAttemptStore() {
|
|
1777
|
+
const attempts = /* @__PURE__ */ new Map();
|
|
1778
|
+
const key = (id, signature) => `${id}\0${signature}`;
|
|
1779
|
+
return {
|
|
1780
|
+
get: (id, signature) => attempts.get(key(id, signature)) ?? null,
|
|
1781
|
+
set: (id, signature, attemptKey) => attempts.set(key(id, signature), attemptKey),
|
|
1782
|
+
delete: (id, signature) => {
|
|
1783
|
+
attempts.delete(key(id, signature));
|
|
1784
|
+
}
|
|
1785
|
+
};
|
|
1786
|
+
}
|
|
1787
|
+
function stableValue(value) {
|
|
1788
|
+
if (Array.isArray(value)) return value.map(stableValue);
|
|
1789
|
+
if (!value || typeof value !== "object") return value;
|
|
1790
|
+
return Object.fromEntries(Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, nested]) => [key, stableValue(nested)]));
|
|
1791
|
+
}
|
|
1792
|
+
function interactionSubmissionSignature(submission) {
|
|
1793
|
+
return JSON.stringify(stableValue(submission));
|
|
1794
|
+
}
|
|
1795
|
+
function defaultAttemptKey() {
|
|
1796
|
+
if (globalThis.crypto?.randomUUID) return globalThis.crypto.randomUUID();
|
|
1797
|
+
return `attempt-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
1798
|
+
}
|
|
1799
|
+
function createDurableInteractionAnswerSubmitter(options) {
|
|
1800
|
+
const timeoutMs = options.timeoutMs ?? INTERACTION_SUBMIT_TIMEOUT_MS;
|
|
1801
|
+
const fetchImpl = options.fetchImpl ?? fetch;
|
|
1802
|
+
return async (submission) => {
|
|
1803
|
+
const signature = interactionSubmissionSignature(submission);
|
|
1804
|
+
let attemptKey;
|
|
1805
|
+
try {
|
|
1806
|
+
attemptKey = options.attempts.get(submission.id, signature) ?? "";
|
|
1807
|
+
if (!attemptKey) {
|
|
1808
|
+
attemptKey = (options.createAttemptKey ?? defaultAttemptKey)();
|
|
1809
|
+
options.attempts.set(submission.id, signature, attemptKey);
|
|
1810
|
+
}
|
|
1811
|
+
} catch (cause) {
|
|
1812
|
+
return {
|
|
1813
|
+
ok: false,
|
|
1814
|
+
expired: false,
|
|
1815
|
+
message: cause instanceof Error ? cause.message : "Failed to submit the answer"
|
|
1816
|
+
};
|
|
1817
|
+
}
|
|
1818
|
+
const url = typeof options.url === "function" ? options.url(submission) : options.url;
|
|
1819
|
+
const extra = typeof options.body === "function" ? options.body(submission) : options.body ?? {};
|
|
1820
|
+
const controller = new AbortController();
|
|
1821
|
+
const timer = setTimeout(() => controller.abort(INTERACTION_SUBMIT_TIMEOUT_MESSAGE), timeoutMs);
|
|
1822
|
+
try {
|
|
1823
|
+
const response = await fetchImpl(url, {
|
|
1824
|
+
method: "POST",
|
|
1825
|
+
headers: { "Content-Type": "application/json" },
|
|
1826
|
+
signal: controller.signal,
|
|
1827
|
+
body: JSON.stringify({
|
|
1828
|
+
...extra,
|
|
1829
|
+
id: submission.id,
|
|
1830
|
+
outcome: submission.outcome,
|
|
1831
|
+
attemptKey,
|
|
1832
|
+
...submission.data ? { data: submission.data } : {}
|
|
1833
|
+
})
|
|
1834
|
+
});
|
|
1835
|
+
if (response.ok) {
|
|
1836
|
+
options.attempts.delete(submission.id, signature);
|
|
1837
|
+
return { ok: true };
|
|
1838
|
+
}
|
|
1839
|
+
const failure = await responseErrorMessage(response);
|
|
1840
|
+
if (response.status < 500) options.attempts.delete(submission.id, signature);
|
|
1841
|
+
return { ok: false, expired: response.status === 410, message: failure.message };
|
|
1842
|
+
} catch (cause) {
|
|
1843
|
+
if (controller.signal.aborted) {
|
|
1844
|
+
return { ok: false, expired: false, message: INTERACTION_SUBMIT_TIMEOUT_MESSAGE };
|
|
1845
|
+
}
|
|
1846
|
+
return {
|
|
1847
|
+
ok: false,
|
|
1848
|
+
expired: false,
|
|
1849
|
+
message: cause instanceof Error ? cause.message : "Failed to submit the answer"
|
|
1850
|
+
};
|
|
1851
|
+
} finally {
|
|
1852
|
+
clearTimeout(timer);
|
|
1853
|
+
}
|
|
1854
|
+
};
|
|
1368
1855
|
}
|
|
1369
1856
|
|
|
1370
1857
|
// src/web-react/use-chat-interactions.ts
|
|
1371
|
-
import { useCallback as
|
|
1858
|
+
import { useCallback as useCallback3, useMemo as useMemo4, useState as useState8 } from "react";
|
|
1372
1859
|
function hasPendingContentDuplicate(list, interaction) {
|
|
1373
1860
|
if (interaction.status !== "pending") return false;
|
|
1374
1861
|
const signature = questionInteractionContentSignature(interaction);
|
|
@@ -1382,7 +1869,15 @@ function upsertChatInteraction(list, interaction) {
|
|
|
1382
1869
|
return [...list, interaction];
|
|
1383
1870
|
}
|
|
1384
1871
|
const existing = list[index];
|
|
1385
|
-
if (!existing
|
|
1872
|
+
if (!existing) return list;
|
|
1873
|
+
if (isTerminalInteractionStatus(existing.status)) {
|
|
1874
|
+
if (existing.status === interaction.status && (!existing.answers && interaction.answers || !existing.cancelReason && interaction.cancelReason)) {
|
|
1875
|
+
const next2 = [...list];
|
|
1876
|
+
next2[index] = { ...existing, ...interaction };
|
|
1877
|
+
return next2;
|
|
1878
|
+
}
|
|
1879
|
+
return list;
|
|
1880
|
+
}
|
|
1386
1881
|
const next = [...list];
|
|
1387
1882
|
next[index] = interaction;
|
|
1388
1883
|
return next;
|
|
@@ -1399,51 +1894,204 @@ function cancelChatInteraction(list, cancel) {
|
|
|
1399
1894
|
};
|
|
1400
1895
|
return next;
|
|
1401
1896
|
}
|
|
1402
|
-
function resolveChatInteraction(list, id, status) {
|
|
1897
|
+
function resolveChatInteraction(list, id, status, answers) {
|
|
1403
1898
|
const index = list.findIndex((item) => item.id === id);
|
|
1404
1899
|
const existing = list[index];
|
|
1405
1900
|
if (!existing || existing.status !== "pending") return list;
|
|
1406
1901
|
const next = [...list];
|
|
1407
|
-
next[index] = { ...existing, status };
|
|
1902
|
+
next[index] = { ...existing, status, ...answers ? { answers } : {} };
|
|
1408
1903
|
return next;
|
|
1409
1904
|
}
|
|
1410
1905
|
function terminalizePendingChatInteractions(list, status) {
|
|
1411
1906
|
if (!list.some((item) => item.status === "pending")) return list;
|
|
1412
1907
|
return list.map((item) => item.status === "pending" ? { ...item, status } : item);
|
|
1413
1908
|
}
|
|
1414
|
-
function restoreChatInteractions(list, outstanding) {
|
|
1415
|
-
|
|
1416
|
-
let next = list.map((item) => item.status === "pending" && !outstandingIds.has(item.id) ? { ...item, status: "answered" } : item);
|
|
1909
|
+
function restoreChatInteractions(list, outstanding, options = {}) {
|
|
1910
|
+
let next = list;
|
|
1417
1911
|
for (const request of outstanding) {
|
|
1418
|
-
|
|
1912
|
+
const interaction = interactionFromWireRequest(request);
|
|
1913
|
+
const exact = next.findIndex((item) => item.id === interaction.id);
|
|
1914
|
+
if (exact !== -1) {
|
|
1915
|
+
next = upsertChatInteraction(next, interaction);
|
|
1916
|
+
continue;
|
|
1917
|
+
}
|
|
1918
|
+
const signature = questionInteractionContentSignature(interaction);
|
|
1919
|
+
const obsolete = signature ? next.findIndex((item) => item.status === "pending" && questionInteractionContentSignature(item) === signature) : -1;
|
|
1920
|
+
if (obsolete === -1) {
|
|
1921
|
+
next = [...next, interaction];
|
|
1922
|
+
continue;
|
|
1923
|
+
}
|
|
1924
|
+
next = [...next];
|
|
1925
|
+
next[obsolete] = interaction;
|
|
1926
|
+
}
|
|
1927
|
+
if (options.mode !== "durable") {
|
|
1928
|
+
const outstandingIds = new Set(outstanding.map((request) => request.id));
|
|
1929
|
+
next = next.map((item) => item.status === "pending" && !outstandingIds.has(item.id) ? { ...item, status: "answered" } : item);
|
|
1419
1930
|
}
|
|
1420
1931
|
return next;
|
|
1421
1932
|
}
|
|
1422
|
-
function
|
|
1423
|
-
|
|
1424
|
-
|
|
1933
|
+
function hydrateChatInteractions(list, persisted) {
|
|
1934
|
+
return persisted.reduce(upsertChatInteraction, list);
|
|
1935
|
+
}
|
|
1936
|
+
function useChatInteractions(options = {}) {
|
|
1937
|
+
const [interactions, setInteractions] = useState8([]);
|
|
1938
|
+
const upsert = useCallback3((interaction) => {
|
|
1425
1939
|
setInteractions((prev) => upsertChatInteraction(prev, interaction));
|
|
1426
1940
|
}, []);
|
|
1427
|
-
const applyCancel =
|
|
1941
|
+
const applyCancel = useCallback3((cancel) => {
|
|
1428
1942
|
setInteractions((prev) => cancelChatInteraction(prev, cancel));
|
|
1429
1943
|
}, []);
|
|
1430
|
-
const markResolved =
|
|
1431
|
-
setInteractions((prev) => resolveChatInteraction(prev, id, status));
|
|
1944
|
+
const markResolved = useCallback3((id, status, answers) => {
|
|
1945
|
+
setInteractions((prev) => resolveChatInteraction(prev, id, status, answers));
|
|
1432
1946
|
}, []);
|
|
1433
|
-
const restore =
|
|
1434
|
-
setInteractions((prev) => restoreChatInteractions(prev, outstanding
|
|
1947
|
+
const restore = useCallback3((outstanding, restoreOptions) => {
|
|
1948
|
+
setInteractions((prev) => restoreChatInteractions(prev, outstanding, {
|
|
1949
|
+
mode: restoreOptions?.mode ?? options.mode
|
|
1950
|
+
}));
|
|
1951
|
+
}, [options.mode]);
|
|
1952
|
+
const hydrate = useCallback3((persisted) => {
|
|
1953
|
+
setInteractions((prev) => hydrateChatInteractions(prev, persisted));
|
|
1435
1954
|
}, []);
|
|
1436
|
-
const terminalizePending =
|
|
1955
|
+
const terminalizePending = useCallback3((status) => {
|
|
1437
1956
|
setInteractions((prev) => terminalizePendingChatInteractions(prev, status));
|
|
1438
1957
|
}, []);
|
|
1439
|
-
const reset =
|
|
1958
|
+
const reset = useCallback3(() => setInteractions([]), []);
|
|
1440
1959
|
const pending = useMemo4(() => interactions.filter((item) => item.status === "pending"), [interactions]);
|
|
1441
|
-
return { interactions, pending, upsert, applyCancel, markResolved, restore, terminalizePending, reset };
|
|
1960
|
+
return { interactions, pending, upsert, applyCancel, markResolved, restore, hydrate, terminalizePending, reset };
|
|
1961
|
+
}
|
|
1962
|
+
|
|
1963
|
+
// src/web-react/use-file-mentions.ts
|
|
1964
|
+
import { useCallback as useCallback4, useMemo as useMemo5, useRef as useRef7, useState as useState9 } from "react";
|
|
1965
|
+
var FILE_MENTION_KIND = "file";
|
|
1966
|
+
function toMentionItem(file) {
|
|
1967
|
+
return { id: file.path, label: file.name, detail: file.path, kind: FILE_MENTION_KIND };
|
|
1968
|
+
}
|
|
1969
|
+
function toFileMention(item) {
|
|
1970
|
+
return { path: item.id, name: item.label };
|
|
1971
|
+
}
|
|
1972
|
+
function rankFileMentions(files, query, limit) {
|
|
1973
|
+
const q = query.trim().toLowerCase();
|
|
1974
|
+
if (!q) return files.slice(0, limit);
|
|
1975
|
+
const scored = [];
|
|
1976
|
+
for (const file of files) {
|
|
1977
|
+
const name = file.name.toLowerCase();
|
|
1978
|
+
if (name.startsWith(q)) {
|
|
1979
|
+
scored.push({ file, tier: 0 });
|
|
1980
|
+
continue;
|
|
1981
|
+
}
|
|
1982
|
+
if (name.includes(q)) {
|
|
1983
|
+
scored.push({ file, tier: 1 });
|
|
1984
|
+
continue;
|
|
1985
|
+
}
|
|
1986
|
+
if (file.path.toLowerCase().includes(q)) {
|
|
1987
|
+
scored.push({ file, tier: 2 });
|
|
1988
|
+
}
|
|
1989
|
+
}
|
|
1990
|
+
scored.sort((a, b) => {
|
|
1991
|
+
if (a.tier !== b.tier) return a.tier - b.tier;
|
|
1992
|
+
if (a.file.name.length !== b.file.name.length) return a.file.name.length - b.file.name.length;
|
|
1993
|
+
return a.file.path.localeCompare(b.file.path);
|
|
1994
|
+
});
|
|
1995
|
+
return scored.slice(0, limit).map((s) => s.file);
|
|
1996
|
+
}
|
|
1997
|
+
var RETRY_AFTER_MS = 3e3;
|
|
1998
|
+
var DEFAULT_MENTION_LIMIT = 20;
|
|
1999
|
+
var INDEX_REFRESH_AFTER_MS = 5 * 60 * 1e3;
|
|
2000
|
+
var DEFAULT_MENTION_EMPTY_TEXT = "No matching files";
|
|
2001
|
+
function emptyTextFor(state, fallback) {
|
|
2002
|
+
switch (state.kind) {
|
|
2003
|
+
case "idle":
|
|
2004
|
+
case "loading":
|
|
2005
|
+
return "Loading files\u2026";
|
|
2006
|
+
case "warming":
|
|
2007
|
+
return "Sandbox is starting \u2014 try again in a moment";
|
|
2008
|
+
case "error":
|
|
2009
|
+
return `Couldn't load files: ${state.message}`;
|
|
2010
|
+
case "ready":
|
|
2011
|
+
return fallback;
|
|
2012
|
+
}
|
|
2013
|
+
}
|
|
2014
|
+
function useFileMentions(options) {
|
|
2015
|
+
const {
|
|
2016
|
+
indexUrl,
|
|
2017
|
+
limit = DEFAULT_MENTION_LIMIT,
|
|
2018
|
+
refreshAfterMs = INDEX_REFRESH_AFTER_MS,
|
|
2019
|
+
emptyText = DEFAULT_MENTION_EMPTY_TEXT
|
|
2020
|
+
} = options;
|
|
2021
|
+
const fetchImpl = options.fetchImpl ?? fetch;
|
|
2022
|
+
const [state, setState] = useState9({ kind: "idle" });
|
|
2023
|
+
const stateRef = useRef7(state);
|
|
2024
|
+
stateRef.current = state;
|
|
2025
|
+
const inFlightRef = useRef7(null);
|
|
2026
|
+
const [mentions, setMentions] = useState9([]);
|
|
2027
|
+
const load = useCallback4(() => {
|
|
2028
|
+
if (inFlightRef.current) return inFlightRef.current;
|
|
2029
|
+
if (stateRef.current.kind === "idle") {
|
|
2030
|
+
stateRef.current = { kind: "loading" };
|
|
2031
|
+
setState(stateRef.current);
|
|
2032
|
+
}
|
|
2033
|
+
const attempt = (async () => {
|
|
2034
|
+
let next;
|
|
2035
|
+
try {
|
|
2036
|
+
const res = await fetchImpl(indexUrl);
|
|
2037
|
+
if (!res.ok) {
|
|
2038
|
+
next = { kind: "error", message: `HTTP ${res.status}`, attemptedAt: Date.now() };
|
|
2039
|
+
} else {
|
|
2040
|
+
const body = await res.json();
|
|
2041
|
+
next = body.status === "warming" ? { kind: "warming", attemptedAt: Date.now() } : {
|
|
2042
|
+
kind: "ready",
|
|
2043
|
+
files: body.files,
|
|
2044
|
+
truncated: body.truncated,
|
|
2045
|
+
fetchedAt: Date.now()
|
|
2046
|
+
};
|
|
2047
|
+
}
|
|
2048
|
+
} catch (err) {
|
|
2049
|
+
next = { kind: "error", message: err instanceof Error ? err.message : String(err), attemptedAt: Date.now() };
|
|
2050
|
+
}
|
|
2051
|
+
stateRef.current = next;
|
|
2052
|
+
setState(next);
|
|
2053
|
+
inFlightRef.current = null;
|
|
2054
|
+
return next;
|
|
2055
|
+
})();
|
|
2056
|
+
inFlightRef.current = attempt;
|
|
2057
|
+
return attempt;
|
|
2058
|
+
}, [fetchImpl, indexUrl]);
|
|
2059
|
+
const refresh = useCallback4(async () => {
|
|
2060
|
+
await load();
|
|
2061
|
+
}, [load]);
|
|
2062
|
+
const fetchItems = useCallback4(
|
|
2063
|
+
async (query) => {
|
|
2064
|
+
let current = stateRef.current;
|
|
2065
|
+
if (current.kind === "idle" || current.kind === "loading") {
|
|
2066
|
+
current = await load();
|
|
2067
|
+
} else if (current.kind === "ready") {
|
|
2068
|
+
if (Date.now() - current.fetchedAt > refreshAfterMs) void load();
|
|
2069
|
+
} else if (Date.now() - current.attemptedAt > RETRY_AFTER_MS) {
|
|
2070
|
+
void load();
|
|
2071
|
+
}
|
|
2072
|
+
if (current.kind !== "ready") return [];
|
|
2073
|
+
return rankFileMentions(current.files, query, limit).map(toMentionItem);
|
|
2074
|
+
},
|
|
2075
|
+
[load, limit, refreshAfterMs]
|
|
2076
|
+
);
|
|
2077
|
+
const onMentionsChange = useCallback4((items) => {
|
|
2078
|
+
setMentions(items.filter((item) => item.kind === void 0 || item.kind === FILE_MENTION_KIND).map(toFileMention));
|
|
2079
|
+
}, []);
|
|
2080
|
+
const clearMentions = useCallback4(() => setMentions([]), []);
|
|
2081
|
+
const mention = useMemo5(
|
|
2082
|
+
() => ({
|
|
2083
|
+
fetchItems,
|
|
2084
|
+
onMentionsChange,
|
|
2085
|
+
emptyText: emptyTextFor(state, emptyText)
|
|
2086
|
+
}),
|
|
2087
|
+
[fetchItems, onMentionsChange, state, emptyText]
|
|
2088
|
+
);
|
|
2089
|
+
return { mention, mentions, clearMentions, refresh };
|
|
1442
2090
|
}
|
|
1443
2091
|
|
|
1444
2092
|
// src/web-react/mission-activity.tsx
|
|
1445
|
-
import { useCallback as
|
|
1446
|
-
import { Fragment as Fragment4, jsx as
|
|
2093
|
+
import { useCallback as useCallback5, useEffect as useEffect7, useState as useState10 } from "react";
|
|
2094
|
+
import { Fragment as Fragment4, jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1447
2095
|
var LIVE_STATUSES = /* @__PURE__ */ new Set(["pending", "running"]);
|
|
1448
2096
|
var OK_STATUSES = /* @__PURE__ */ new Set(["completed", "done", "succeeded"]);
|
|
1449
2097
|
var ERROR_STATUSES = /* @__PURE__ */ new Set(["failed", "error", "cancelled", "aborted"]);
|
|
@@ -1490,20 +2138,20 @@ function waterfallLayout(trace) {
|
|
|
1490
2138
|
});
|
|
1491
2139
|
}
|
|
1492
2140
|
function ChevronGlyph({ className }) {
|
|
1493
|
-
return /* @__PURE__ */
|
|
2141
|
+
return /* @__PURE__ */ jsx9("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: /* @__PURE__ */ jsx9("path", { d: "m6 9 6 6 6-6" }) });
|
|
1494
2142
|
}
|
|
1495
2143
|
function RefreshGlyph({ className }) {
|
|
1496
|
-
return /* @__PURE__ */
|
|
2144
|
+
return /* @__PURE__ */ jsx9("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: /* @__PURE__ */ jsx9("path", { d: "M21 12a9 9 0 1 1-2.64-6.36M21 3v6h-6" }) });
|
|
1497
2145
|
}
|
|
1498
2146
|
function CopyGlyph({ className }) {
|
|
1499
|
-
return /* @__PURE__ */
|
|
1500
|
-
/* @__PURE__ */
|
|
1501
|
-
/* @__PURE__ */
|
|
2147
|
+
return /* @__PURE__ */ jsxs7("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: [
|
|
2148
|
+
/* @__PURE__ */ jsx9("rect", { x: "9", y: "9", width: "13", height: "13", rx: "2" }),
|
|
2149
|
+
/* @__PURE__ */ jsx9("path", { d: "M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" })
|
|
1502
2150
|
] });
|
|
1503
2151
|
}
|
|
1504
2152
|
function TraceIdCopy({ traceId }) {
|
|
1505
|
-
const [copied, setCopied] =
|
|
1506
|
-
const copy =
|
|
2153
|
+
const [copied, setCopied] = useState10(false);
|
|
2154
|
+
const copy = useCallback5(() => {
|
|
1507
2155
|
void navigator.clipboard?.writeText(traceId).then(
|
|
1508
2156
|
() => {
|
|
1509
2157
|
setCopied(true);
|
|
@@ -1513,7 +2161,7 @@ function TraceIdCopy({ traceId }) {
|
|
|
1513
2161
|
}
|
|
1514
2162
|
);
|
|
1515
2163
|
}, [traceId]);
|
|
1516
|
-
return /* @__PURE__ */
|
|
2164
|
+
return /* @__PURE__ */ jsxs7(
|
|
1517
2165
|
"button",
|
|
1518
2166
|
{
|
|
1519
2167
|
type: "button",
|
|
@@ -1522,23 +2170,23 @@ function TraceIdCopy({ traceId }) {
|
|
|
1522
2170
|
"aria-label": "Copy trace id",
|
|
1523
2171
|
className: "inline-flex min-w-0 items-center gap-1.5 rounded text-left font-mono text-muted-foreground transition hover:text-foreground focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-card",
|
|
1524
2172
|
children: [
|
|
1525
|
-
/* @__PURE__ */
|
|
1526
|
-
/* @__PURE__ */
|
|
1527
|
-
copied && /* @__PURE__ */
|
|
2173
|
+
/* @__PURE__ */ jsx9("span", { className: "truncate", children: traceId }),
|
|
2174
|
+
/* @__PURE__ */ jsx9(CopyGlyph, { className: "h-3 w-3 shrink-0" }),
|
|
2175
|
+
copied && /* @__PURE__ */ jsx9("span", { className: "shrink-0 not-italic text-success", children: "copied" })
|
|
1528
2176
|
]
|
|
1529
2177
|
}
|
|
1530
2178
|
);
|
|
1531
2179
|
}
|
|
1532
2180
|
function StatusDot({ tone }) {
|
|
1533
|
-
return /* @__PURE__ */
|
|
1534
|
-
/* @__PURE__ */
|
|
2181
|
+
return /* @__PURE__ */ jsxs7("span", { className: "inline-flex items-center", children: [
|
|
2182
|
+
/* @__PURE__ */ jsx9(
|
|
1535
2183
|
"span",
|
|
1536
2184
|
{
|
|
1537
2185
|
"aria-hidden": true,
|
|
1538
2186
|
className: `h-2 w-2 shrink-0 rounded-full ${tone === "live" ? "animate-pulse bg-warning" : tone === "ok" ? "bg-success" : tone === "error" ? "bg-destructive" : "bg-muted-foreground/40"}`
|
|
1539
2187
|
}
|
|
1540
2188
|
),
|
|
1541
|
-
/* @__PURE__ */
|
|
2189
|
+
/* @__PURE__ */ jsx9("span", { className: "sr-only", children: tone })
|
|
1542
2190
|
] });
|
|
1543
2191
|
}
|
|
1544
2192
|
var BAR_CLASS = {
|
|
@@ -1550,19 +2198,19 @@ function FlowWaterfall({ trace }) {
|
|
|
1550
2198
|
const rows = waterfallLayout(trace);
|
|
1551
2199
|
if (rows.length === 0) return null;
|
|
1552
2200
|
const cost = formatActivityCost(trace.costUsd);
|
|
1553
|
-
return /* @__PURE__ */
|
|
1554
|
-
rows.map((row, i) => /* @__PURE__ */
|
|
1555
|
-
/* @__PURE__ */
|
|
1556
|
-
/* @__PURE__ */
|
|
2201
|
+
return /* @__PURE__ */ jsxs7("div", { className: "space-y-1", children: [
|
|
2202
|
+
rows.map((row, i) => /* @__PURE__ */ jsxs7("div", { className: "grid grid-cols-[minmax(0,2fr)_minmax(0,3fr)_auto] items-center gap-2", children: [
|
|
2203
|
+
/* @__PURE__ */ jsx9("span", { className: "truncate font-mono text-[11px] text-muted-foreground", title: row.name, children: row.name }),
|
|
2204
|
+
/* @__PURE__ */ jsx9("div", { className: "relative h-2 rounded-sm bg-muted/40", children: /* @__PURE__ */ jsx9(
|
|
1557
2205
|
"div",
|
|
1558
2206
|
{
|
|
1559
2207
|
className: `absolute inset-y-0 rounded-sm ${row.ok ? BAR_CLASS[row.kind] : "bg-destructive/80"} ${row.approx ? "opacity-70" : ""}`,
|
|
1560
2208
|
style: { left: `${row.offsetPct}%`, width: `${row.widthPct}%` }
|
|
1561
2209
|
}
|
|
1562
2210
|
) }),
|
|
1563
|
-
/* @__PURE__ */
|
|
2211
|
+
/* @__PURE__ */ jsx9("span", { className: "shrink-0 font-mono text-[10px] tabular-nums text-muted-foreground/70", children: row.durationLabel })
|
|
1564
2212
|
] }, i)),
|
|
1565
|
-
/* @__PURE__ */
|
|
2213
|
+
/* @__PURE__ */ jsxs7("p", { className: "pt-0.5 text-right font-mono text-[10px] tabular-nums text-muted-foreground/60", children: [
|
|
1566
2214
|
(trace.totalMs / 1e3).toFixed(1),
|
|
1567
2215
|
"s",
|
|
1568
2216
|
cost ? ` \xB7 ${cost}` : ""
|
|
@@ -1570,43 +2218,43 @@ function FlowWaterfall({ trace }) {
|
|
|
1570
2218
|
] });
|
|
1571
2219
|
}
|
|
1572
2220
|
function MissionActivityLane({ activity, startedAt, nowMs }) {
|
|
1573
|
-
const [expanded, setExpanded] =
|
|
2221
|
+
const [expanded, setExpanded] = useState10(false);
|
|
1574
2222
|
if (activity.length === 0) return null;
|
|
1575
|
-
return /* @__PURE__ */
|
|
2223
|
+
return /* @__PURE__ */ jsxs7("div", { className: "mt-1 border-l border-border/50 pl-3", children: [
|
|
1576
2224
|
activity.map((run) => {
|
|
1577
2225
|
const tone = activityTone(run.status);
|
|
1578
2226
|
const cost = formatActivityCost(run.costUsd);
|
|
1579
2227
|
const duration = formatActivityDuration(run.durationMs);
|
|
1580
|
-
return /* @__PURE__ */
|
|
1581
|
-
/* @__PURE__ */
|
|
1582
|
-
/* @__PURE__ */
|
|
1583
|
-
/* @__PURE__ */
|
|
1584
|
-
/* @__PURE__ */
|
|
2228
|
+
return /* @__PURE__ */ jsxs7("div", { className: "flex items-center gap-2 py-1 text-xs", children: [
|
|
2229
|
+
/* @__PURE__ */ jsx9(StatusDot, { tone }),
|
|
2230
|
+
/* @__PURE__ */ jsxs7("span", { className: "min-w-0 flex-1 truncate", children: [
|
|
2231
|
+
/* @__PURE__ */ jsx9("span", { className: "font-medium", children: run.tool }),
|
|
2232
|
+
/* @__PURE__ */ jsxs7("span", { className: "text-muted-foreground", children: [
|
|
1585
2233
|
" \u2014 ",
|
|
1586
2234
|
run.detail
|
|
1587
2235
|
] })
|
|
1588
2236
|
] }),
|
|
1589
|
-
tone === "live" && (run.iteration !== void 0 || run.phase !== void 0) && /* @__PURE__ */
|
|
1590
|
-
/* @__PURE__ */
|
|
1591
|
-
tone !== "live" && tone !== "ok" && /* @__PURE__ */
|
|
1592
|
-
cost && /* @__PURE__ */
|
|
1593
|
-
duration && /* @__PURE__ */
|
|
2237
|
+
tone === "live" && (run.iteration !== void 0 || run.phase !== void 0) && /* @__PURE__ */ jsx9("span", { className: "shrink-0 rounded-full bg-warning/10 px-1.5 py-0.5 font-mono text-[10px] text-warning", children: [run.iteration !== void 0 ? `iter ${run.iteration}` : null, run.phase ?? null].filter(Boolean).join(" \xB7 ") }),
|
|
2238
|
+
/* @__PURE__ */ jsxs7("span", { className: "flex shrink-0 items-center gap-1.5 font-mono text-[10px] tabular-nums text-muted-foreground/70", children: [
|
|
2239
|
+
tone !== "live" && tone !== "ok" && /* @__PURE__ */ jsx9("span", { children: run.status }),
|
|
2240
|
+
cost && /* @__PURE__ */ jsx9("span", { children: cost }),
|
|
2241
|
+
duration && /* @__PURE__ */ jsx9("span", { children: duration })
|
|
1594
2242
|
] })
|
|
1595
2243
|
] }, run.taskId);
|
|
1596
2244
|
}),
|
|
1597
|
-
/* @__PURE__ */
|
|
2245
|
+
/* @__PURE__ */ jsxs7(
|
|
1598
2246
|
"button",
|
|
1599
2247
|
{
|
|
1600
2248
|
type: "button",
|
|
1601
2249
|
onClick: () => setExpanded((v) => !v),
|
|
1602
2250
|
className: "flex items-center gap-1 py-0.5 text-[10px] font-medium text-muted-foreground/70 transition hover:text-foreground",
|
|
1603
2251
|
children: [
|
|
1604
|
-
/* @__PURE__ */
|
|
2252
|
+
/* @__PURE__ */ jsx9(ChevronGlyph, { className: `h-3 w-3 transition-transform ${expanded ? "rotate-180" : ""}` }),
|
|
1605
2253
|
"timeline"
|
|
1606
2254
|
]
|
|
1607
2255
|
}
|
|
1608
2256
|
),
|
|
1609
|
-
expanded && /* @__PURE__ */
|
|
2257
|
+
expanded && /* @__PURE__ */ jsx9("div", { className: "rounded-md border border-border/50 bg-muted/10 p-2", children: /* @__PURE__ */ jsx9(
|
|
1610
2258
|
FlowWaterfall,
|
|
1611
2259
|
{
|
|
1612
2260
|
trace: stepActivityFlowTrace(activity, {
|
|
@@ -1621,45 +2269,45 @@ function ActivityRow({
|
|
|
1621
2269
|
record,
|
|
1622
2270
|
renderMissionRef
|
|
1623
2271
|
}) {
|
|
1624
|
-
const [open, setOpen] =
|
|
2272
|
+
const [open, setOpen] = useState10(false);
|
|
1625
2273
|
const tone = activityTone(record.status);
|
|
1626
2274
|
const cost = formatActivityCost(record.costUsd);
|
|
1627
2275
|
const duration = formatActivityDuration(record.durationMs);
|
|
1628
|
-
return /* @__PURE__ */
|
|
1629
|
-
/* @__PURE__ */
|
|
1630
|
-
/* @__PURE__ */
|
|
1631
|
-
/* @__PURE__ */
|
|
1632
|
-
/* @__PURE__ */
|
|
1633
|
-
/* @__PURE__ */
|
|
2276
|
+
return /* @__PURE__ */ jsxs7("div", { className: "rounded-lg border border-border/60 bg-card", children: [
|
|
2277
|
+
/* @__PURE__ */ jsxs7("button", { type: "button", onClick: () => setOpen((v) => !v), className: "flex w-full items-center gap-2.5 px-3 py-2 text-left text-sm", children: [
|
|
2278
|
+
/* @__PURE__ */ jsx9(StatusDot, { tone }),
|
|
2279
|
+
/* @__PURE__ */ jsxs7("span", { className: "min-w-0 flex-1 truncate", children: [
|
|
2280
|
+
/* @__PURE__ */ jsx9("span", { className: "font-medium", children: record.tool }),
|
|
2281
|
+
/* @__PURE__ */ jsxs7("span", { className: "text-muted-foreground", children: [
|
|
1634
2282
|
" \u2014 ",
|
|
1635
2283
|
record.detail
|
|
1636
2284
|
] })
|
|
1637
2285
|
] }),
|
|
1638
|
-
tone === "live" && (record.iteration !== void 0 || record.phase !== void 0) && /* @__PURE__ */
|
|
1639
|
-
/* @__PURE__ */
|
|
2286
|
+
tone === "live" && (record.iteration !== void 0 || record.phase !== void 0) && /* @__PURE__ */ jsx9("span", { className: "shrink-0 rounded-full bg-warning/10 px-2 py-0.5 font-mono text-[10px] text-warning", children: [record.iteration !== void 0 ? `iter ${record.iteration}` : null, record.phase ?? null].filter(Boolean).join(" \xB7 ") }),
|
|
2287
|
+
/* @__PURE__ */ jsx9(
|
|
1640
2288
|
"span",
|
|
1641
2289
|
{
|
|
1642
2290
|
className: `shrink-0 rounded-full px-2 py-0.5 text-[10px] font-medium ${tone === "ok" ? "bg-success/10 text-success" : tone === "error" ? "bg-destructive/10 text-destructive" : tone === "live" ? "bg-warning/10 text-warning" : "bg-muted/60 text-muted-foreground"}`,
|
|
1643
2291
|
children: record.status
|
|
1644
2292
|
}
|
|
1645
2293
|
),
|
|
1646
|
-
cost && /* @__PURE__ */
|
|
1647
|
-
/* @__PURE__ */
|
|
2294
|
+
cost && /* @__PURE__ */ jsx9("span", { className: "shrink-0 font-mono text-[11px] tabular-nums text-muted-foreground", children: cost }),
|
|
2295
|
+
/* @__PURE__ */ jsx9(ChevronGlyph, { className: `h-3 w-3 shrink-0 text-muted-foreground transition-transform ${open ? "rotate-180" : ""}` })
|
|
1648
2296
|
] }),
|
|
1649
|
-
open && /* @__PURE__ */
|
|
1650
|
-
record.durationMs !== void 0 && /* @__PURE__ */
|
|
1651
|
-
/* @__PURE__ */
|
|
1652
|
-
/* @__PURE__ */
|
|
1653
|
-
/* @__PURE__ */
|
|
1654
|
-
/* @__PURE__ */
|
|
1655
|
-
/* @__PURE__ */
|
|
1656
|
-
duration && /* @__PURE__ */
|
|
1657
|
-
/* @__PURE__ */
|
|
1658
|
-
/* @__PURE__ */
|
|
2297
|
+
open && /* @__PURE__ */ jsxs7("div", { className: "space-y-2.5 border-t border-border/40 px-3 py-2.5", children: [
|
|
2298
|
+
record.durationMs !== void 0 && /* @__PURE__ */ jsx9("div", { className: "rounded-md border border-border/50 bg-muted/10 p-2", children: /* @__PURE__ */ jsx9(FlowWaterfall, { trace: stepActivityFlowTrace([record]) }) }),
|
|
2299
|
+
/* @__PURE__ */ jsxs7("dl", { className: "grid grid-cols-[auto_1fr] gap-x-3 gap-y-1 font-mono text-[11px]", children: [
|
|
2300
|
+
/* @__PURE__ */ jsx9("dt", { className: "text-muted-foreground/60", children: "task" }),
|
|
2301
|
+
/* @__PURE__ */ jsx9("dd", { className: "truncate text-muted-foreground", children: record.taskId }),
|
|
2302
|
+
/* @__PURE__ */ jsx9("dt", { className: "text-muted-foreground/60", children: "started" }),
|
|
2303
|
+
/* @__PURE__ */ jsx9("dd", { className: "text-muted-foreground", children: new Date(record.startedAt).toLocaleString() }),
|
|
2304
|
+
duration && /* @__PURE__ */ jsxs7(Fragment4, { children: [
|
|
2305
|
+
/* @__PURE__ */ jsx9("dt", { className: "text-muted-foreground/60", children: "duration" }),
|
|
2306
|
+
/* @__PURE__ */ jsx9("dd", { className: "text-muted-foreground", children: duration })
|
|
1659
2307
|
] }),
|
|
1660
|
-
record.traceId && /* @__PURE__ */
|
|
1661
|
-
/* @__PURE__ */
|
|
1662
|
-
/* @__PURE__ */
|
|
2308
|
+
record.traceId && /* @__PURE__ */ jsxs7(Fragment4, { children: [
|
|
2309
|
+
/* @__PURE__ */ jsx9("dt", { className: "text-muted-foreground/60", children: "trace" }),
|
|
2310
|
+
/* @__PURE__ */ jsx9("dd", { className: "min-w-0", children: /* @__PURE__ */ jsx9(TraceIdCopy, { traceId: record.traceId }) })
|
|
1663
2311
|
] })
|
|
1664
2312
|
] }),
|
|
1665
2313
|
record.missionRef && renderMissionRef?.(record.missionRef, record)
|
|
@@ -1667,11 +2315,11 @@ function ActivityRow({
|
|
|
1667
2315
|
] });
|
|
1668
2316
|
}
|
|
1669
2317
|
function AgentActivityPanel({ fetchActivity, renderMissionRef, title = "Agent activity", emptyLabel = "No agent runs yet." }) {
|
|
1670
|
-
const [rows, setRows] =
|
|
1671
|
-
const [cursor, setCursor] =
|
|
1672
|
-
const [loading, setLoading] =
|
|
1673
|
-
const [error, setError] =
|
|
1674
|
-
const load =
|
|
2318
|
+
const [rows, setRows] = useState10([]);
|
|
2319
|
+
const [cursor, setCursor] = useState10(void 0);
|
|
2320
|
+
const [loading, setLoading] = useState10(false);
|
|
2321
|
+
const [error, setError] = useState10(null);
|
|
2322
|
+
const load = useCallback5(
|
|
1675
2323
|
async (from) => {
|
|
1676
2324
|
setLoading(true);
|
|
1677
2325
|
setError(null);
|
|
@@ -1687,13 +2335,13 @@ function AgentActivityPanel({ fetchActivity, renderMissionRef, title = "Agent ac
|
|
|
1687
2335
|
},
|
|
1688
2336
|
[fetchActivity]
|
|
1689
2337
|
);
|
|
1690
|
-
|
|
2338
|
+
useEffect7(() => {
|
|
1691
2339
|
void load();
|
|
1692
2340
|
}, [load]);
|
|
1693
|
-
return /* @__PURE__ */
|
|
1694
|
-
/* @__PURE__ */
|
|
1695
|
-
/* @__PURE__ */
|
|
1696
|
-
/* @__PURE__ */
|
|
2341
|
+
return /* @__PURE__ */ jsxs7("div", { className: "space-y-2", children: [
|
|
2342
|
+
/* @__PURE__ */ jsxs7("div", { className: "flex items-center gap-2", children: [
|
|
2343
|
+
/* @__PURE__ */ jsx9("h2", { className: "flex-1 text-sm font-semibold", children: title }),
|
|
2344
|
+
/* @__PURE__ */ jsx9(
|
|
1697
2345
|
"button",
|
|
1698
2346
|
{
|
|
1699
2347
|
type: "button",
|
|
@@ -1701,14 +2349,14 @@ function AgentActivityPanel({ fetchActivity, renderMissionRef, title = "Agent ac
|
|
|
1701
2349
|
disabled: loading,
|
|
1702
2350
|
"aria-label": "Refresh",
|
|
1703
2351
|
className: "rounded-md p-1.5 text-muted-foreground transition hover:bg-accent/30 hover:text-foreground disabled:opacity-50",
|
|
1704
|
-
children: /* @__PURE__ */
|
|
2352
|
+
children: /* @__PURE__ */ jsx9(RefreshGlyph, { className: `h-3.5 w-3.5 ${loading ? "animate-spin" : ""}` })
|
|
1705
2353
|
}
|
|
1706
2354
|
)
|
|
1707
2355
|
] }),
|
|
1708
|
-
error && /* @__PURE__ */
|
|
1709
|
-
!error && rows.length === 0 && !loading && /* @__PURE__ */
|
|
1710
|
-
/* @__PURE__ */
|
|
1711
|
-
cursor && /* @__PURE__ */
|
|
2356
|
+
error && /* @__PURE__ */ jsx9("p", { role: "alert", className: "rounded-md border border-destructive/40 bg-destructive/5 px-3 py-2 text-xs text-destructive", children: error }),
|
|
2357
|
+
!error && rows.length === 0 && !loading && /* @__PURE__ */ jsx9("p", { className: "px-1 text-sm text-muted-foreground", children: emptyLabel }),
|
|
2358
|
+
/* @__PURE__ */ jsx9("div", { className: "space-y-1.5", children: rows.map((record) => /* @__PURE__ */ jsx9(ActivityRow, { record, renderMissionRef }, record.taskId)) }),
|
|
2359
|
+
cursor && /* @__PURE__ */ jsx9(
|
|
1712
2360
|
"button",
|
|
1713
2361
|
{
|
|
1714
2362
|
type: "button",
|
|
@@ -1722,9 +2370,9 @@ function AgentActivityPanel({ fetchActivity, renderMissionRef, title = "Agent ac
|
|
|
1722
2370
|
}
|
|
1723
2371
|
|
|
1724
2372
|
// src/web-react/seat-paywall.tsx
|
|
1725
|
-
import { jsx as
|
|
2373
|
+
import { jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1726
2374
|
function CheckGlyph3() {
|
|
1727
|
-
return /* @__PURE__ */
|
|
2375
|
+
return /* @__PURE__ */ jsx10(
|
|
1728
2376
|
"svg",
|
|
1729
2377
|
{
|
|
1730
2378
|
className: "h-4 w-4 shrink-0 text-primary",
|
|
@@ -1735,14 +2383,14 @@ function CheckGlyph3() {
|
|
|
1735
2383
|
strokeLinecap: "round",
|
|
1736
2384
|
strokeLinejoin: "round",
|
|
1737
2385
|
"aria-hidden": true,
|
|
1738
|
-
children: /* @__PURE__ */
|
|
2386
|
+
children: /* @__PURE__ */ jsx10("path", { d: "M20 6 9 17l-5-5" })
|
|
1739
2387
|
}
|
|
1740
2388
|
);
|
|
1741
2389
|
}
|
|
1742
2390
|
function Benefit({ children }) {
|
|
1743
|
-
return /* @__PURE__ */
|
|
1744
|
-
/* @__PURE__ */
|
|
1745
|
-
/* @__PURE__ */
|
|
2391
|
+
return /* @__PURE__ */ jsxs8("li", { className: "flex items-start gap-2.5 text-sm text-foreground", children: [
|
|
2392
|
+
/* @__PURE__ */ jsx10("span", { className: "mt-0.5", children: /* @__PURE__ */ jsx10(CheckGlyph3, {}) }),
|
|
2393
|
+
/* @__PURE__ */ jsx10("span", { children })
|
|
1746
2394
|
] });
|
|
1747
2395
|
}
|
|
1748
2396
|
function SeatPaywall({
|
|
@@ -1756,30 +2404,30 @@ function SeatPaywall({
|
|
|
1756
2404
|
footnote
|
|
1757
2405
|
}) {
|
|
1758
2406
|
const { pending, run } = usePending();
|
|
1759
|
-
return /* @__PURE__ */
|
|
1760
|
-
/* @__PURE__ */
|
|
1761
|
-
/* @__PURE__ */
|
|
2407
|
+
return /* @__PURE__ */ jsx10("div", { className: "flex min-h-[60vh] w-full items-center justify-center p-6", children: /* @__PURE__ */ jsxs8("div", { className: "w-full max-w-md rounded-2xl border border-border bg-card p-8 shadow-sm", children: [
|
|
2408
|
+
/* @__PURE__ */ jsx10("p", { className: "text-xs font-medium uppercase tracking-wide text-muted-foreground", children: product }),
|
|
2409
|
+
/* @__PURE__ */ jsxs8("h1", { className: "mt-2 text-2xl font-semibold tracking-tight text-foreground", children: [
|
|
1762
2410
|
"Unlock ",
|
|
1763
2411
|
product
|
|
1764
2412
|
] }),
|
|
1765
|
-
tagline && /* @__PURE__ */
|
|
1766
|
-
/* @__PURE__ */
|
|
1767
|
-
/* @__PURE__ */
|
|
2413
|
+
tagline && /* @__PURE__ */ jsx10("p", { className: "mt-2 text-sm text-muted-foreground", children: tagline }),
|
|
2414
|
+
/* @__PURE__ */ jsxs8("div", { className: "mt-6 flex items-baseline gap-1.5", children: [
|
|
2415
|
+
/* @__PURE__ */ jsxs8("span", { className: "text-3xl font-semibold text-foreground", children: [
|
|
1768
2416
|
"$",
|
|
1769
2417
|
priceUsd
|
|
1770
2418
|
] }),
|
|
1771
|
-
/* @__PURE__ */
|
|
2419
|
+
/* @__PURE__ */ jsx10("span", { className: "text-sm text-muted-foreground", children: "/mo" })
|
|
1772
2420
|
] }),
|
|
1773
|
-
/* @__PURE__ */
|
|
2421
|
+
/* @__PURE__ */ jsxs8("p", { className: "mt-1 text-sm text-muted-foreground", children: [
|
|
1774
2422
|
"Includes $",
|
|
1775
2423
|
includedUsageUsd,
|
|
1776
2424
|
"/mo of AI usage"
|
|
1777
2425
|
] }),
|
|
1778
|
-
/* @__PURE__ */
|
|
2426
|
+
/* @__PURE__ */ jsx10("ul", { className: "mt-6 space-y-2.5", children: (benefits ?? [
|
|
1779
2427
|
`Full access to ${product}`,
|
|
1780
2428
|
`$${includedUsageUsd}/mo of AI usage included, every month`
|
|
1781
|
-
]).map((benefit, i) => /* @__PURE__ */
|
|
1782
|
-
/* @__PURE__ */
|
|
2429
|
+
]).map((benefit, i) => /* @__PURE__ */ jsx10(Benefit, { children: benefit }, i)) }),
|
|
2430
|
+
/* @__PURE__ */ jsx10(
|
|
1783
2431
|
"button",
|
|
1784
2432
|
{
|
|
1785
2433
|
type: "button",
|
|
@@ -1789,13 +2437,13 @@ function SeatPaywall({
|
|
|
1789
2437
|
children: pending ? "Opening checkout\u2026" : ctaLabel ?? `Unlock ${product}`
|
|
1790
2438
|
}
|
|
1791
2439
|
),
|
|
1792
|
-
footnote && /* @__PURE__ */
|
|
2440
|
+
footnote && /* @__PURE__ */ jsx10("p", { className: "mt-3 text-center text-xs text-muted-foreground/70", children: footnote })
|
|
1793
2441
|
] }) });
|
|
1794
2442
|
}
|
|
1795
2443
|
|
|
1796
2444
|
// src/web-react/agent-session-controls.tsx
|
|
1797
|
-
import { useMemo as
|
|
1798
|
-
import { jsx as
|
|
2445
|
+
import { useMemo as useMemo6, useState as useState11 } from "react";
|
|
2446
|
+
import { jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1799
2447
|
var HARNESS_LABELS = {
|
|
1800
2448
|
opencode: "OpenCode (any model)",
|
|
1801
2449
|
"claude-code": "Claude Code (Anthropic)",
|
|
@@ -1815,12 +2463,12 @@ function harnessLabel(h) {
|
|
|
1815
2463
|
return HARNESS_LABELS[h] ?? h;
|
|
1816
2464
|
}
|
|
1817
2465
|
function ChevronDown2({ className }) {
|
|
1818
|
-
return /* @__PURE__ */
|
|
2466
|
+
return /* @__PURE__ */ jsx11("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: /* @__PURE__ */ jsx11("path", { d: "m6 9 6 6 6-6" }) });
|
|
1819
2467
|
}
|
|
1820
2468
|
function GearGlyph({ className }) {
|
|
1821
|
-
return /* @__PURE__ */
|
|
1822
|
-
/* @__PURE__ */
|
|
1823
|
-
/* @__PURE__ */
|
|
2469
|
+
return /* @__PURE__ */ jsxs9("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: [
|
|
2470
|
+
/* @__PURE__ */ jsx11("circle", { cx: "12", cy: "12", r: "3" }),
|
|
2471
|
+
/* @__PURE__ */ jsx11("path", { d: "M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1Z" })
|
|
1824
2472
|
] });
|
|
1825
2473
|
}
|
|
1826
2474
|
var FOCUS_RING = "focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background";
|
|
@@ -1829,11 +2477,11 @@ function HarnessPicker({
|
|
|
1829
2477
|
onChange,
|
|
1830
2478
|
available
|
|
1831
2479
|
}) {
|
|
1832
|
-
const [open, setOpen] =
|
|
2480
|
+
const [open, setOpen] = useState11(false);
|
|
1833
2481
|
const { containerRef, triggerProps } = usePopover(open, setOpen);
|
|
1834
2482
|
const options = available ?? Object.keys(HARNESS_LABELS);
|
|
1835
|
-
return /* @__PURE__ */
|
|
1836
|
-
/* @__PURE__ */
|
|
2483
|
+
return /* @__PURE__ */ jsxs9("div", { ref: containerRef, className: "relative inline-flex", children: [
|
|
2484
|
+
/* @__PURE__ */ jsxs9(
|
|
1837
2485
|
"button",
|
|
1838
2486
|
{
|
|
1839
2487
|
type: "button",
|
|
@@ -1842,12 +2490,12 @@ function HarnessPicker({
|
|
|
1842
2490
|
title: "Agent backend",
|
|
1843
2491
|
className: `inline-flex w-full items-center justify-between gap-1.5 rounded-lg border border-border bg-card px-3 py-1.5 text-sm font-medium text-foreground transition hover:bg-accent/30 ${FOCUS_RING}`,
|
|
1844
2492
|
children: [
|
|
1845
|
-
/* @__PURE__ */
|
|
1846
|
-
/* @__PURE__ */
|
|
2493
|
+
/* @__PURE__ */ jsx11("span", { className: "truncate", children: harnessLabel(value) }),
|
|
2494
|
+
/* @__PURE__ */ jsx11(ChevronDown2, { className: "h-3.5 w-3.5 text-muted-foreground" })
|
|
1847
2495
|
]
|
|
1848
2496
|
}
|
|
1849
2497
|
),
|
|
1850
|
-
open && /* @__PURE__ */
|
|
2498
|
+
open && /* @__PURE__ */ jsx11("div", { role: "menu", className: "absolute bottom-full left-0 z-50 mb-2 max-h-64 w-full min-w-[220px] overflow-y-auto rounded-xl border border-border bg-card p-1 shadow-lg", children: options.map((h) => /* @__PURE__ */ jsx11(
|
|
1851
2499
|
"button",
|
|
1852
2500
|
{
|
|
1853
2501
|
type: "button",
|
|
@@ -1866,7 +2514,7 @@ function HarnessPicker({
|
|
|
1866
2514
|
}
|
|
1867
2515
|
function useCoherentHandlers(props) {
|
|
1868
2516
|
const { model, models, harness, onModelChange, onHarnessChange } = props;
|
|
1869
|
-
const canonicalIds =
|
|
2517
|
+
const canonicalIds = useMemo6(() => models.map((m) => m.id), [models]);
|
|
1870
2518
|
const onModel = (next) => {
|
|
1871
2519
|
onModelChange(next);
|
|
1872
2520
|
const nextHarness = snapHarnessToModel(harness, next);
|
|
@@ -1894,11 +2542,11 @@ function AgentSessionControls(props) {
|
|
|
1894
2542
|
className
|
|
1895
2543
|
} = props;
|
|
1896
2544
|
const { onModel, onHarness } = useCoherentHandlers(props);
|
|
1897
|
-
const [open, setOpen] =
|
|
2545
|
+
const [open, setOpen] = useState11(false);
|
|
1898
2546
|
const { containerRef: popoverRef, triggerProps } = usePopover(open, setOpen);
|
|
1899
2547
|
const selectedModel = models.find((m) => m.id === model);
|
|
1900
2548
|
const showEffort = selectedModel?.supportsReasoning ?? true;
|
|
1901
|
-
const modelPicker = /* @__PURE__ */
|
|
2549
|
+
const modelPicker = /* @__PURE__ */ jsx11(
|
|
1902
2550
|
ModelPicker,
|
|
1903
2551
|
{
|
|
1904
2552
|
value: model,
|
|
@@ -1909,17 +2557,17 @@ function AgentSessionControls(props) {
|
|
|
1909
2557
|
}
|
|
1910
2558
|
);
|
|
1911
2559
|
if (layout === "inline") {
|
|
1912
|
-
return /* @__PURE__ */
|
|
2560
|
+
return /* @__PURE__ */ jsxs9("div", { className: `flex items-center gap-1.5 ${className ?? ""}`, children: [
|
|
1913
2561
|
modelPicker,
|
|
1914
|
-
showHarness && /* @__PURE__ */
|
|
1915
|
-
showEffort && /* @__PURE__ */
|
|
2562
|
+
showHarness && /* @__PURE__ */ jsx11(HarnessPicker, { value: harness, onChange: onHarness, available: availableHarnesses }),
|
|
2563
|
+
showEffort && /* @__PURE__ */ jsx11(EffortPicker, { value: effort, onChange: onEffortChange })
|
|
1916
2564
|
] });
|
|
1917
2565
|
}
|
|
1918
2566
|
const hasAdvanced = showHarness || showEffort;
|
|
1919
|
-
return /* @__PURE__ */
|
|
2567
|
+
return /* @__PURE__ */ jsxs9("div", { className: `flex items-center gap-1.5 ${className ?? ""}`, children: [
|
|
1920
2568
|
modelPicker,
|
|
1921
|
-
hasAdvanced && /* @__PURE__ */
|
|
1922
|
-
/* @__PURE__ */
|
|
2569
|
+
hasAdvanced && /* @__PURE__ */ jsxs9("div", { ref: popoverRef, className: "relative inline-flex", children: [
|
|
2570
|
+
/* @__PURE__ */ jsx11(
|
|
1923
2571
|
"button",
|
|
1924
2572
|
{
|
|
1925
2573
|
type: "button",
|
|
@@ -1928,19 +2576,19 @@ function AgentSessionControls(props) {
|
|
|
1928
2576
|
title: "Model settings \u2014 pick the agent backend and how hard it thinks",
|
|
1929
2577
|
className: `flex h-8 w-8 items-center justify-center rounded-full text-muted-foreground transition-colors hover:bg-muted hover:text-foreground data-[state=open]:bg-muted ${FOCUS_RING}`,
|
|
1930
2578
|
"data-state": open ? "open" : "closed",
|
|
1931
|
-
children: /* @__PURE__ */
|
|
2579
|
+
children: /* @__PURE__ */ jsx11(GearGlyph, { className: "h-4 w-4" })
|
|
1932
2580
|
}
|
|
1933
2581
|
),
|
|
1934
|
-
open && /* @__PURE__ */
|
|
1935
|
-
showHarness && /* @__PURE__ */
|
|
1936
|
-
/* @__PURE__ */
|
|
1937
|
-
/* @__PURE__ */
|
|
1938
|
-
/* @__PURE__ */
|
|
2582
|
+
open && /* @__PURE__ */ jsxs9("div", { className: "absolute bottom-full left-0 z-50 mb-2 w-72 space-y-3 rounded-xl border border-border bg-card p-3 shadow-lg", children: [
|
|
2583
|
+
showHarness && /* @__PURE__ */ jsxs9("div", { className: "space-y-1.5", children: [
|
|
2584
|
+
/* @__PURE__ */ jsx11("p", { className: "text-xs font-medium text-foreground", children: "Agent backend" }),
|
|
2585
|
+
/* @__PURE__ */ jsx11(HarnessPicker, { value: harness, onChange: onHarness, available: availableHarnesses }),
|
|
2586
|
+
/* @__PURE__ */ jsx11("p", { className: "text-[11px] leading-snug text-muted-foreground", children: "The engine that runs the agent. Switching it keeps your model choice compatible." })
|
|
1939
2587
|
] }),
|
|
1940
|
-
showEffort && /* @__PURE__ */
|
|
1941
|
-
/* @__PURE__ */
|
|
1942
|
-
/* @__PURE__ */
|
|
1943
|
-
/* @__PURE__ */
|
|
2588
|
+
showEffort && /* @__PURE__ */ jsxs9("div", { className: "space-y-1.5", children: [
|
|
2589
|
+
/* @__PURE__ */ jsx11("p", { className: "text-xs font-medium text-foreground", children: "Thinking" }),
|
|
2590
|
+
/* @__PURE__ */ jsx11(EffortPicker, { value: effort, onChange: onEffortChange, label: "" }),
|
|
2591
|
+
/* @__PURE__ */ jsx11("p", { className: "text-[11px] leading-snug text-muted-foreground", children: "How hard the agent thinks before answering. Higher is slower but more thorough." })
|
|
1944
2592
|
] })
|
|
1945
2593
|
] })
|
|
1946
2594
|
] })
|
|
@@ -1948,7 +2596,7 @@ function AgentSessionControls(props) {
|
|
|
1948
2596
|
}
|
|
1949
2597
|
|
|
1950
2598
|
// src/web-react/index.tsx
|
|
1951
|
-
import { Fragment as Fragment5, jsx as
|
|
2599
|
+
import { Fragment as Fragment5, jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
1952
2600
|
function formatModelCost(msg, models) {
|
|
1953
2601
|
if (msg.promptTokens == null && msg.completionTokens == null) return null;
|
|
1954
2602
|
const pricing = models.find((m) => m.id === msg.modelUsed)?.pricing;
|
|
@@ -1962,41 +2610,41 @@ function formatTokensPerSecond(msg) {
|
|
|
1962
2610
|
return `${Math.round(msg.completionTokens / (msg.durationMs / 1e3))} tok/s`;
|
|
1963
2611
|
}
|
|
1964
2612
|
function RunDrillIn({ run, onClose }) {
|
|
1965
|
-
return /* @__PURE__ */
|
|
1966
|
-
/* @__PURE__ */
|
|
1967
|
-
/* @__PURE__ */
|
|
2613
|
+
return /* @__PURE__ */ jsxs10("div", { className: "fixed inset-y-0 right-0 z-50 flex w-[480px] max-w-full flex-col border-l border-border bg-card shadow-xl", children: [
|
|
2614
|
+
/* @__PURE__ */ jsxs10("div", { className: "flex items-center gap-2 border-b border-border px-4 py-3", children: [
|
|
2615
|
+
/* @__PURE__ */ jsx12(
|
|
1968
2616
|
"span",
|
|
1969
2617
|
{
|
|
1970
2618
|
className: `h-2 w-2 shrink-0 rounded-full ${run.status === "running" ? "bg-warning" : run.status === "error" ? "bg-destructive" : "bg-success"}`
|
|
1971
2619
|
}
|
|
1972
2620
|
),
|
|
1973
|
-
/* @__PURE__ */
|
|
1974
|
-
/* @__PURE__ */
|
|
1975
|
-
/* @__PURE__ */
|
|
2621
|
+
/* @__PURE__ */ jsxs10("div", { className: "min-w-0 flex-1", children: [
|
|
2622
|
+
/* @__PURE__ */ jsx12("p", { className: "truncate text-sm font-semibold", children: run.title }),
|
|
2623
|
+
/* @__PURE__ */ jsx12("p", { className: "truncate font-mono text-[11px] text-muted-foreground", children: run.toolName })
|
|
1976
2624
|
] }),
|
|
1977
|
-
/* @__PURE__ */
|
|
2625
|
+
/* @__PURE__ */ jsx12(
|
|
1978
2626
|
"button",
|
|
1979
2627
|
{
|
|
1980
2628
|
type: "button",
|
|
1981
2629
|
onClick: onClose,
|
|
1982
2630
|
"aria-label": "Close",
|
|
1983
2631
|
className: "rounded-md p-1.5 text-muted-foreground transition hover:bg-accent/30 hover:text-foreground",
|
|
1984
|
-
children: /* @__PURE__ */
|
|
2632
|
+
children: /* @__PURE__ */ jsx12("svg", { className: "h-4 w-4", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", "aria-hidden": true, children: /* @__PURE__ */ jsx12("path", { d: "M18 6 6 18M6 6l12 12" }) })
|
|
1985
2633
|
}
|
|
1986
2634
|
)
|
|
1987
2635
|
] }),
|
|
1988
|
-
/* @__PURE__ */
|
|
1989
|
-
run.steps.length === 0 && /* @__PURE__ */
|
|
1990
|
-
run.steps.map((step, i) => /* @__PURE__ */
|
|
1991
|
-
/* @__PURE__ */
|
|
1992
|
-
/* @__PURE__ */
|
|
1993
|
-
/* @__PURE__ */
|
|
1994
|
-
/* @__PURE__ */
|
|
2636
|
+
/* @__PURE__ */ jsxs10("div", { className: "flex-1 space-y-3 overflow-y-auto p-4", children: [
|
|
2637
|
+
run.steps.length === 0 && /* @__PURE__ */ jsx12("p", { className: "text-sm text-muted-foreground", children: "No steps recorded yet." }),
|
|
2638
|
+
run.steps.map((step, i) => /* @__PURE__ */ jsxs10("div", { className: "rounded-lg border border-border/60 bg-background", children: [
|
|
2639
|
+
/* @__PURE__ */ jsxs10("div", { className: "flex items-baseline gap-2 border-b border-border/40 px-3 py-1.5", children: [
|
|
2640
|
+
/* @__PURE__ */ jsx12("span", { className: `font-mono text-[11px] ${step.status === "error" ? "text-destructive" : "text-muted-foreground"}`, children: step.status === "error" ? "\u2717" : "$" }),
|
|
2641
|
+
/* @__PURE__ */ jsx12("code", { className: "min-w-0 flex-1 truncate font-mono text-xs", children: step.label }),
|
|
2642
|
+
/* @__PURE__ */ jsx12("span", { className: "shrink-0 text-[10px] text-muted-foreground", children: new Date(step.at).toLocaleTimeString() })
|
|
1995
2643
|
] }),
|
|
1996
|
-
step.detail && /* @__PURE__ */
|
|
2644
|
+
step.detail && /* @__PURE__ */ jsx12("pre", { className: "max-h-48 overflow-auto whitespace-pre-wrap px-3 py-2 font-mono text-[11px] leading-relaxed text-muted-foreground", children: step.detail })
|
|
1997
2645
|
] }, i))
|
|
1998
2646
|
] }),
|
|
1999
|
-
/* @__PURE__ */
|
|
2647
|
+
/* @__PURE__ */ jsx12("p", { className: "border-t border-border px-4 py-2 text-[11px] text-muted-foreground", children: "Readonly drill-in. Follow up in the main chat." })
|
|
2000
2648
|
] });
|
|
2001
2649
|
}
|
|
2002
2650
|
function pendingApprovalOf(call) {
|
|
@@ -2010,20 +2658,20 @@ function ChatEmptyState({
|
|
|
2010
2658
|
subline = "Describe the outcome you want. The agent works through it step by step, and pauses for your approval before anything irreversible.",
|
|
2011
2659
|
doors
|
|
2012
2660
|
}) {
|
|
2013
|
-
return /* @__PURE__ */
|
|
2014
|
-
/* @__PURE__ */
|
|
2015
|
-
/* @__PURE__ */
|
|
2016
|
-
/* @__PURE__ */
|
|
2017
|
-
subline && /* @__PURE__ */
|
|
2018
|
-
doors && doors.length > 0 && /* @__PURE__ */
|
|
2661
|
+
return /* @__PURE__ */ jsxs10("div", { className: "mx-auto flex w-full max-w-2xl flex-col items-center px-6 py-12 text-center sm:py-20", children: [
|
|
2662
|
+
/* @__PURE__ */ jsx12("span", { className: "mb-5 inline-flex h-14 w-14 items-center justify-center rounded-2xl bg-primary/10 ring-1 ring-primary/15", children: /* @__PURE__ */ jsx12(BrandMark, { size: 32, className: "shrink-0" }) }),
|
|
2663
|
+
/* @__PURE__ */ jsx12("p", { className: "text-[11px] font-semibold uppercase tracking-[0.18em] text-muted-foreground", children: productName }),
|
|
2664
|
+
/* @__PURE__ */ jsx12("h2", { className: "mt-1.5 text-balance text-2xl font-semibold leading-tight text-foreground sm:text-[28px]", children: headline }),
|
|
2665
|
+
subline && /* @__PURE__ */ jsx12("p", { className: "mt-3 max-w-md text-[15px] leading-relaxed text-muted-foreground", children: subline }),
|
|
2666
|
+
doors && doors.length > 0 && /* @__PURE__ */ jsx12("div", { className: "mt-7 grid w-full gap-2.5 sm:grid-cols-3", children: doors.slice(0, 3).map((door, i) => /* @__PURE__ */ jsxs10(
|
|
2019
2667
|
"button",
|
|
2020
2668
|
{
|
|
2021
2669
|
type: "button",
|
|
2022
2670
|
onClick: door.onSelect,
|
|
2023
2671
|
className: "group flex min-h-[44px] flex-col items-start rounded-xl border border-border bg-card px-4 py-3 text-left transition hover:border-primary/40 hover:bg-accent/30 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
|
|
2024
2672
|
children: [
|
|
2025
|
-
/* @__PURE__ */
|
|
2026
|
-
door.description && /* @__PURE__ */
|
|
2673
|
+
/* @__PURE__ */ jsx12("span", { className: "text-sm font-semibold text-foreground", children: door.label }),
|
|
2674
|
+
door.description && /* @__PURE__ */ jsx12("span", { className: "mt-0.5 text-[12px] leading-snug text-muted-foreground", children: door.description })
|
|
2027
2675
|
]
|
|
2028
2676
|
},
|
|
2029
2677
|
i
|
|
@@ -2032,26 +2680,26 @@ function ChatEmptyState({
|
|
|
2032
2680
|
}
|
|
2033
2681
|
function ToolGlyph({ name, className }) {
|
|
2034
2682
|
if (name.startsWith("sandbox_")) {
|
|
2035
|
-
return /* @__PURE__ */
|
|
2036
|
-
/* @__PURE__ */
|
|
2037
|
-
/* @__PURE__ */
|
|
2683
|
+
return /* @__PURE__ */ jsxs10("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: [
|
|
2684
|
+
/* @__PURE__ */ jsx12("polyline", { points: "4 17 10 11 4 5" }),
|
|
2685
|
+
/* @__PURE__ */ jsx12("line", { x1: "12", y1: "19", x2: "20", y2: "19" })
|
|
2038
2686
|
] });
|
|
2039
2687
|
}
|
|
2040
2688
|
if (name === "submit_proposal") {
|
|
2041
|
-
return /* @__PURE__ */
|
|
2042
|
-
/* @__PURE__ */
|
|
2043
|
-
/* @__PURE__ */
|
|
2689
|
+
return /* @__PURE__ */ jsxs10("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: [
|
|
2690
|
+
/* @__PURE__ */ jsx12("path", { d: "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" }),
|
|
2691
|
+
/* @__PURE__ */ jsx12("path", { d: "M14 2v6h6M9 15l2 2 4-4" })
|
|
2044
2692
|
] });
|
|
2045
2693
|
}
|
|
2046
2694
|
if (name === "schedule_followup") {
|
|
2047
|
-
return /* @__PURE__ */
|
|
2048
|
-
/* @__PURE__ */
|
|
2049
|
-
/* @__PURE__ */
|
|
2695
|
+
return /* @__PURE__ */ jsxs10("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", "aria-hidden": true, children: [
|
|
2696
|
+
/* @__PURE__ */ jsx12("circle", { cx: "12", cy: "12", r: "9" }),
|
|
2697
|
+
/* @__PURE__ */ jsx12("path", { d: "M12 7v5l3 3" })
|
|
2050
2698
|
] });
|
|
2051
2699
|
}
|
|
2052
|
-
return /* @__PURE__ */
|
|
2053
|
-
/* @__PURE__ */
|
|
2054
|
-
/* @__PURE__ */
|
|
2700
|
+
return /* @__PURE__ */ jsxs10("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: [
|
|
2701
|
+
/* @__PURE__ */ jsx12("path", { d: "M12 3v3m0 12v3M3 12h3m12 0h3" }),
|
|
2702
|
+
/* @__PURE__ */ jsx12("circle", { cx: "12", cy: "12", r: "4" })
|
|
2055
2703
|
] });
|
|
2056
2704
|
}
|
|
2057
2705
|
function toolOutcomeOf(call) {
|
|
@@ -2118,36 +2766,36 @@ function truncate(v, max = 240) {
|
|
|
2118
2766
|
function KvRows({ data }) {
|
|
2119
2767
|
const entries = Object.entries(data).filter(([, v]) => v !== void 0 && v !== null && v !== "");
|
|
2120
2768
|
if (!entries.length) return null;
|
|
2121
|
-
return /* @__PURE__ */
|
|
2122
|
-
/* @__PURE__ */
|
|
2123
|
-
/* @__PURE__ */
|
|
2769
|
+
return /* @__PURE__ */ jsx12("dl", { className: "grid grid-cols-[auto_1fr] gap-x-3 gap-y-1", children: entries.map(([k, v]) => /* @__PURE__ */ jsxs10("div", { className: "contents", children: [
|
|
2770
|
+
/* @__PURE__ */ jsx12("dt", { className: "font-mono text-[11px] text-muted-foreground", children: k }),
|
|
2771
|
+
/* @__PURE__ */ jsx12("dd", { className: "min-w-0 whitespace-pre-wrap break-words font-mono text-[11px] text-muted-foreground", children: truncate(v) })
|
|
2124
2772
|
] }, k)) });
|
|
2125
2773
|
}
|
|
2126
2774
|
function ShellDetail({ call }) {
|
|
2127
2775
|
const outcome = toolOutcomeOf(call);
|
|
2128
2776
|
const r = outcome?.result ?? {};
|
|
2129
|
-
return /* @__PURE__ */
|
|
2130
|
-
/* @__PURE__ */
|
|
2131
|
-
/* @__PURE__ */
|
|
2132
|
-
/* @__PURE__ */
|
|
2133
|
-
r.exitCode != null && /* @__PURE__ */
|
|
2777
|
+
return /* @__PURE__ */ jsxs10("div", { className: "overflow-hidden rounded-md bg-zinc-900 font-mono text-[11px] leading-relaxed", children: [
|
|
2778
|
+
/* @__PURE__ */ jsxs10("div", { className: "flex items-center gap-2 px-3 pt-2 text-zinc-400", children: [
|
|
2779
|
+
/* @__PURE__ */ jsx12("span", { className: "select-none text-zinc-500", children: "$" }),
|
|
2780
|
+
/* @__PURE__ */ jsx12("span", { className: "min-w-0 flex-1 truncate text-zinc-200", children: String(call.args?.command ?? "") }),
|
|
2781
|
+
r.exitCode != null && /* @__PURE__ */ jsxs10("span", { className: r.exitCode === 0 ? "text-success" : "text-destructive", children: [
|
|
2134
2782
|
"exit ",
|
|
2135
2783
|
r.exitCode
|
|
2136
2784
|
] })
|
|
2137
2785
|
] }),
|
|
2138
|
-
/* @__PURE__ */
|
|
2786
|
+
/* @__PURE__ */ jsx12("pre", { className: "max-h-56 overflow-auto whitespace-pre-wrap px-3 pb-2.5 pt-1.5 text-zinc-300", children: outcome?.ok === false ? outcome.message ?? "failed" : [r.stdout, r.stderr].filter(Boolean).join("\n") || "(no output)" })
|
|
2139
2787
|
] });
|
|
2140
2788
|
}
|
|
2141
2789
|
function DefaultToolDetail({ call }) {
|
|
2142
2790
|
const outcome = toolOutcomeOf(call);
|
|
2143
|
-
return /* @__PURE__ */
|
|
2144
|
-
call.args && Object.keys(call.args).length > 0 && /* @__PURE__ */
|
|
2145
|
-
/* @__PURE__ */
|
|
2146
|
-
/* @__PURE__ */
|
|
2791
|
+
return /* @__PURE__ */ jsxs10("div", { className: "space-y-2", children: [
|
|
2792
|
+
call.args && Object.keys(call.args).length > 0 && /* @__PURE__ */ jsxs10("div", { children: [
|
|
2793
|
+
/* @__PURE__ */ jsx12("p", { className: "mb-1 text-[10px] font-semibold uppercase tracking-wide text-muted-foreground", children: "Called with" }),
|
|
2794
|
+
/* @__PURE__ */ jsx12(KvRows, { data: call.args })
|
|
2147
2795
|
] }),
|
|
2148
|
-
outcome && /* @__PURE__ */
|
|
2149
|
-
/* @__PURE__ */
|
|
2150
|
-
outcome.ok === false ? /* @__PURE__ */
|
|
2796
|
+
outcome && /* @__PURE__ */ jsxs10("div", { children: [
|
|
2797
|
+
/* @__PURE__ */ jsx12("p", { className: "mb-1 text-[10px] font-semibold uppercase tracking-wide text-muted-foreground", children: outcome.ok === false ? "Failed" : "Result" }),
|
|
2798
|
+
outcome.ok === false ? /* @__PURE__ */ jsx12("p", { className: "text-xs text-destructive", children: outcome.message ?? "Tool failed" }) : outcome.result && typeof outcome.result === "object" ? /* @__PURE__ */ jsx12(KvRows, { data: outcome.result }) : /* @__PURE__ */ jsx12("p", { className: "font-mono text-[11px] text-muted-foreground", children: truncate(outcome.result) })
|
|
2151
2799
|
] })
|
|
2152
2800
|
] });
|
|
2153
2801
|
}
|
|
@@ -2158,23 +2806,23 @@ function ProposalCard({
|
|
|
2158
2806
|
approval,
|
|
2159
2807
|
renderers
|
|
2160
2808
|
}) {
|
|
2161
|
-
const [expanded, setExpanded] =
|
|
2809
|
+
const [expanded, setExpanded] = useState12(false);
|
|
2162
2810
|
const { summary, meta } = proposalPreview(call);
|
|
2163
2811
|
const custom = renderers?.[call.name]?.(call, message);
|
|
2164
2812
|
const { pending: deciding, run: decide } = usePending();
|
|
2165
|
-
return /* @__PURE__ */
|
|
2166
|
-
/* @__PURE__ */
|
|
2167
|
-
/* @__PURE__ */
|
|
2168
|
-
/* @__PURE__ */
|
|
2169
|
-
/* @__PURE__ */
|
|
2170
|
-
/* @__PURE__ */
|
|
2171
|
-
summary && /* @__PURE__ */
|
|
2172
|
-
meta.length > 0 && /* @__PURE__ */
|
|
2813
|
+
return /* @__PURE__ */ jsxs10("div", { className: "w-full max-w-full rounded-xl border border-warning/50 bg-warning/[0.06] text-sm shadow-sm ring-1 ring-warning/10", children: [
|
|
2814
|
+
/* @__PURE__ */ jsxs10("div", { className: "flex items-start gap-2.5 px-4 pt-3.5", children: [
|
|
2815
|
+
/* @__PURE__ */ jsx12("span", { className: "mt-0.5 inline-flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-warning/15 text-warning", children: /* @__PURE__ */ jsx12(ToolGlyph, { name: call.name, className: "h-3.5 w-3.5" }) }),
|
|
2816
|
+
/* @__PURE__ */ jsxs10("div", { className: "min-w-0 flex-1", children: [
|
|
2817
|
+
/* @__PURE__ */ jsx12("p", { className: "text-[10px] font-semibold uppercase tracking-wider text-warning-foreground", children: "Needs your approval" }),
|
|
2818
|
+
/* @__PURE__ */ jsx12("p", { className: "mt-0.5 text-[15px] font-semibold leading-snug text-foreground", children: friendlyToolTitle(call) }),
|
|
2819
|
+
summary && /* @__PURE__ */ jsx12("p", { className: "mt-1 text-[13px] leading-relaxed text-muted-foreground", children: summary }),
|
|
2820
|
+
meta.length > 0 && /* @__PURE__ */ jsx12("div", { className: "mt-1.5 flex flex-wrap items-center gap-1.5", children: meta.map((m, i) => /* @__PURE__ */ jsx12("span", { className: "rounded-full bg-muted/60 px-2 py-0.5 text-[11px] font-medium text-muted-foreground", children: m }, i)) })
|
|
2173
2821
|
] })
|
|
2174
2822
|
] }),
|
|
2175
|
-
/* @__PURE__ */
|
|
2176
|
-
approval ? /* @__PURE__ */
|
|
2177
|
-
/* @__PURE__ */
|
|
2823
|
+
/* @__PURE__ */ jsxs10("div", { className: "flex flex-wrap items-center gap-2 px-4 pb-3.5 pt-3", children: [
|
|
2824
|
+
approval ? /* @__PURE__ */ jsxs10(Fragment5, { children: [
|
|
2825
|
+
/* @__PURE__ */ jsx12(
|
|
2178
2826
|
"button",
|
|
2179
2827
|
{
|
|
2180
2828
|
type: "button",
|
|
@@ -2184,7 +2832,7 @@ function ProposalCard({
|
|
|
2184
2832
|
children: "Approve & run"
|
|
2185
2833
|
}
|
|
2186
2834
|
),
|
|
2187
|
-
/* @__PURE__ */
|
|
2835
|
+
/* @__PURE__ */ jsx12(
|
|
2188
2836
|
"button",
|
|
2189
2837
|
{
|
|
2190
2838
|
type: "button",
|
|
@@ -2194,8 +2842,8 @@ function ProposalCard({
|
|
|
2194
2842
|
children: "Reject"
|
|
2195
2843
|
}
|
|
2196
2844
|
)
|
|
2197
|
-
] }) : /* @__PURE__ */
|
|
2198
|
-
/* @__PURE__ */
|
|
2845
|
+
] }) : /* @__PURE__ */ jsx12("span", { className: "text-[12px] font-medium text-muted-foreground", children: "Awaiting approval\u2026" }),
|
|
2846
|
+
/* @__PURE__ */ jsxs10(
|
|
2199
2847
|
"button",
|
|
2200
2848
|
{
|
|
2201
2849
|
type: "button",
|
|
@@ -2204,23 +2852,23 @@ function ProposalCard({
|
|
|
2204
2852
|
className: "ml-auto inline-flex items-center gap-1 rounded text-[12px] font-medium text-muted-foreground transition hover:text-foreground focus:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
2205
2853
|
children: [
|
|
2206
2854
|
expanded ? "Hide details" : "View details",
|
|
2207
|
-
/* @__PURE__ */
|
|
2855
|
+
/* @__PURE__ */ jsx12(ChevronDown, { className: `h-3 w-3 transition-transform ${expanded ? "rotate-180" : ""}` })
|
|
2208
2856
|
]
|
|
2209
2857
|
}
|
|
2210
2858
|
)
|
|
2211
2859
|
] }),
|
|
2212
|
-
expanded && /* @__PURE__ */
|
|
2860
|
+
expanded && /* @__PURE__ */ jsx12("div", { className: "border-t border-warning/20 px-4 py-3 text-xs", children: custom ?? /* @__PURE__ */ jsx12(DefaultToolDetail, { call }) })
|
|
2213
2861
|
] });
|
|
2214
2862
|
}
|
|
2215
2863
|
function FollowupCard({ call }) {
|
|
2216
2864
|
const a = call.args ?? {};
|
|
2217
2865
|
const when = typeof a.when === "string" ? a.when : typeof a.at === "string" ? a.at : typeof a.schedule === "string" ? a.schedule : null;
|
|
2218
|
-
return /* @__PURE__ */
|
|
2219
|
-
/* @__PURE__ */
|
|
2220
|
-
/* @__PURE__ */
|
|
2221
|
-
/* @__PURE__ */
|
|
2866
|
+
return /* @__PURE__ */ jsxs10("div", { className: "w-fit min-w-[260px] max-w-full rounded-lg border border-border/60 border-l-2 border-l-primary/60 bg-muted/20 px-3 py-2 text-sm", children: [
|
|
2867
|
+
/* @__PURE__ */ jsxs10("div", { className: "flex items-center gap-2", children: [
|
|
2868
|
+
/* @__PURE__ */ jsx12(ToolGlyph, { name: call.name, className: "h-3.5 w-3.5 shrink-0 text-primary/80" }),
|
|
2869
|
+
/* @__PURE__ */ jsx12("span", { className: "min-w-0 flex-1 truncate font-medium text-foreground", children: friendlyToolTitle(call) })
|
|
2222
2870
|
] }),
|
|
2223
|
-
when && /* @__PURE__ */
|
|
2871
|
+
when && /* @__PURE__ */ jsx12("p", { className: "mt-0.5 pl-[22px] text-[12px] text-muted-foreground", children: when })
|
|
2224
2872
|
] });
|
|
2225
2873
|
}
|
|
2226
2874
|
function ToolCallCard({
|
|
@@ -2230,13 +2878,13 @@ function ToolCallCard({
|
|
|
2230
2878
|
onOpenRun,
|
|
2231
2879
|
renderers
|
|
2232
2880
|
}) {
|
|
2233
|
-
const [expanded, setExpanded] =
|
|
2881
|
+
const [expanded, setExpanded] = useState12(false);
|
|
2234
2882
|
const pending = call.status === "done" ? pendingApprovalOf(call) : null;
|
|
2235
2883
|
const kind = blockKindOf(call);
|
|
2236
2884
|
const failed = call.status === "error" || toolOutcomeOf(call)?.ok === false;
|
|
2237
2885
|
const custom = renderers?.[call.name]?.(call, message);
|
|
2238
2886
|
if (pending) {
|
|
2239
|
-
return /* @__PURE__ */
|
|
2887
|
+
return /* @__PURE__ */ jsx12(
|
|
2240
2888
|
ProposalCard,
|
|
2241
2889
|
{
|
|
2242
2890
|
call,
|
|
@@ -2248,16 +2896,16 @@ function ToolCallCard({
|
|
|
2248
2896
|
);
|
|
2249
2897
|
}
|
|
2250
2898
|
if (kind === "followup" && !failed) {
|
|
2251
|
-
return /* @__PURE__ */
|
|
2899
|
+
return /* @__PURE__ */ jsx12(FollowupCard, { call });
|
|
2252
2900
|
}
|
|
2253
2901
|
const isCommand = kind === "command";
|
|
2254
|
-
return /* @__PURE__ */
|
|
2902
|
+
return /* @__PURE__ */ jsxs10(
|
|
2255
2903
|
"div",
|
|
2256
2904
|
{
|
|
2257
2905
|
className: `w-fit min-w-[280px] max-w-full rounded-lg border text-xs transition ${failed ? "border-destructive/40 bg-destructive/5" : "border-border/60 bg-muted/20"}`,
|
|
2258
2906
|
children: [
|
|
2259
|
-
/* @__PURE__ */
|
|
2260
|
-
/* @__PURE__ */
|
|
2907
|
+
/* @__PURE__ */ jsxs10("div", { className: "flex w-full items-center gap-2 px-3 py-2", children: [
|
|
2908
|
+
/* @__PURE__ */ jsxs10(
|
|
2261
2909
|
"button",
|
|
2262
2910
|
{
|
|
2263
2911
|
type: "button",
|
|
@@ -2265,14 +2913,14 @@ function ToolCallCard({
|
|
|
2265
2913
|
"aria-expanded": expanded,
|
|
2266
2914
|
className: "flex min-w-0 flex-1 items-center gap-2 rounded text-left focus:outline-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
2267
2915
|
children: [
|
|
2268
|
-
/* @__PURE__ */
|
|
2916
|
+
/* @__PURE__ */ jsx12(
|
|
2269
2917
|
"span",
|
|
2270
2918
|
{
|
|
2271
2919
|
className: `h-2 w-2 shrink-0 rounded-full ${call.status === "running" ? "animate-pulse bg-warning" : failed ? "bg-destructive" : "bg-success"}`
|
|
2272
2920
|
}
|
|
2273
2921
|
),
|
|
2274
|
-
/* @__PURE__ */
|
|
2275
|
-
/* @__PURE__ */
|
|
2922
|
+
/* @__PURE__ */ jsx12(ToolGlyph, { name: call.name, className: "h-3.5 w-3.5 shrink-0 text-muted-foreground" }),
|
|
2923
|
+
/* @__PURE__ */ jsx12(
|
|
2276
2924
|
"span",
|
|
2277
2925
|
{
|
|
2278
2926
|
className: `min-w-0 flex-1 truncate ${isCommand ? "font-mono text-[12px] tracking-tight text-foreground/90" : "font-medium"}`,
|
|
@@ -2282,8 +2930,8 @@ function ToolCallCard({
|
|
|
2282
2930
|
]
|
|
2283
2931
|
}
|
|
2284
2932
|
),
|
|
2285
|
-
/* @__PURE__ */
|
|
2286
|
-
/* @__PURE__ */
|
|
2933
|
+
/* @__PURE__ */ jsx12("span", { className: "shrink-0 text-[11px] text-muted-foreground", children: call.status === "running" ? "running\u2026" : failed ? "failed" : "done" }),
|
|
2934
|
+
/* @__PURE__ */ jsx12(
|
|
2287
2935
|
"button",
|
|
2288
2936
|
{
|
|
2289
2937
|
type: "button",
|
|
@@ -2291,13 +2939,13 @@ function ToolCallCard({
|
|
|
2291
2939
|
"aria-label": expanded ? "Collapse details" : "Expand details",
|
|
2292
2940
|
"aria-expanded": expanded,
|
|
2293
2941
|
className: "shrink-0 rounded p-0.5 focus:outline-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
2294
|
-
children: /* @__PURE__ */
|
|
2942
|
+
children: /* @__PURE__ */ jsx12(ChevronDown, { className: `h-3 w-3 text-muted-foreground transition-transform ${expanded ? "rotate-180" : ""}` })
|
|
2295
2943
|
}
|
|
2296
2944
|
)
|
|
2297
2945
|
] }),
|
|
2298
|
-
expanded && /* @__PURE__ */
|
|
2299
|
-
custom ?? (call.name === "sandbox_run_command" ? /* @__PURE__ */
|
|
2300
|
-
onOpenRun && call.name.startsWith("sandbox_") && /* @__PURE__ */
|
|
2946
|
+
expanded && /* @__PURE__ */ jsxs10("div", { className: "border-t border-border/40 px-3 py-2.5", children: [
|
|
2947
|
+
custom ?? (call.name === "sandbox_run_command" ? /* @__PURE__ */ jsx12(ShellDetail, { call }) : /* @__PURE__ */ jsx12(DefaultToolDetail, { call })),
|
|
2948
|
+
onOpenRun && call.name.startsWith("sandbox_") && /* @__PURE__ */ jsx12(
|
|
2301
2949
|
"button",
|
|
2302
2950
|
{
|
|
2303
2951
|
type: "button",
|
|
@@ -2312,7 +2960,7 @@ function ToolCallCard({
|
|
|
2312
2960
|
);
|
|
2313
2961
|
}
|
|
2314
2962
|
function StreamingCaret() {
|
|
2315
|
-
return /* @__PURE__ */
|
|
2963
|
+
return /* @__PURE__ */ jsx12(
|
|
2316
2964
|
"span",
|
|
2317
2965
|
{
|
|
2318
2966
|
className: "ml-0.5 inline-block h-[1.1em] w-[3px] translate-y-[2px] animate-pulse rounded-sm bg-foreground/70",
|
|
@@ -2327,11 +2975,11 @@ function SegmentText({
|
|
|
2327
2975
|
renderBody
|
|
2328
2976
|
}) {
|
|
2329
2977
|
const text = useSmoothText(content, streaming);
|
|
2330
|
-
const body =
|
|
2978
|
+
const body = useMemo7(() => renderBody(text), [renderBody, text]);
|
|
2331
2979
|
if (!content.trim() && !showCaret) return null;
|
|
2332
|
-
return /* @__PURE__ */
|
|
2980
|
+
return /* @__PURE__ */ jsxs10("div", { className: "text-base leading-[1.75]", children: [
|
|
2333
2981
|
body,
|
|
2334
|
-
showCaret && /* @__PURE__ */
|
|
2982
|
+
showCaret && /* @__PURE__ */ jsx12(StreamingCaret, {})
|
|
2335
2983
|
] });
|
|
2336
2984
|
}
|
|
2337
2985
|
var COLLAPSE_TOOL_RUN_AT = 3;
|
|
@@ -2354,7 +3002,7 @@ function SegmentedBody({
|
|
|
2354
3002
|
const leftoverToolCalls = (msg.toolCalls ?? []).filter(
|
|
2355
3003
|
(tc) => !segmentToolIds.has(tc.id)
|
|
2356
3004
|
);
|
|
2357
|
-
const renderToolCard = (call) => /* @__PURE__ */
|
|
3005
|
+
const renderToolCard = (call) => /* @__PURE__ */ jsx12(
|
|
2358
3006
|
ToolCallCard,
|
|
2359
3007
|
{
|
|
2360
3008
|
call,
|
|
@@ -2377,9 +3025,9 @@ function SegmentedBody({
|
|
|
2377
3025
|
else groups.push({ kind: "tools", index: i, calls: [seg.call] });
|
|
2378
3026
|
}
|
|
2379
3027
|
}
|
|
2380
|
-
return /* @__PURE__ */
|
|
3028
|
+
return /* @__PURE__ */ jsxs10("div", { className: "flex flex-col gap-2", children: [
|
|
2381
3029
|
groups.map(
|
|
2382
|
-
(g) => g.kind === "text" ? /* @__PURE__ */
|
|
3030
|
+
(g) => g.kind === "text" ? /* @__PURE__ */ jsx12(
|
|
2383
3031
|
SegmentText,
|
|
2384
3032
|
{
|
|
2385
3033
|
content: g.content,
|
|
@@ -2388,24 +3036,24 @@ function SegmentedBody({
|
|
|
2388
3036
|
renderBody
|
|
2389
3037
|
},
|
|
2390
3038
|
`text-${g.index}`
|
|
2391
|
-
) : !streaming && g.calls.length >= COLLAPSE_TOOL_RUN_AT && !g.calls.some(isImportantTool) ? /* @__PURE__ */
|
|
3039
|
+
) : !streaming && g.calls.length >= COLLAPSE_TOOL_RUN_AT && !g.calls.some(isImportantTool) ? /* @__PURE__ */ jsxs10(
|
|
2392
3040
|
"details",
|
|
2393
3041
|
{
|
|
2394
3042
|
className: "rounded-lg border-l-2 border-border/70 bg-muted/20 px-3 py-2",
|
|
2395
3043
|
children: [
|
|
2396
|
-
/* @__PURE__ */
|
|
3044
|
+
/* @__PURE__ */ jsxs10("summary", { className: "cursor-pointer select-none text-xs font-medium text-muted-foreground", children: [
|
|
2397
3045
|
"Worked through ",
|
|
2398
3046
|
g.calls.length,
|
|
2399
3047
|
" steps"
|
|
2400
3048
|
] }),
|
|
2401
|
-
/* @__PURE__ */
|
|
3049
|
+
/* @__PURE__ */ jsx12("div", { className: "mt-2 flex flex-col gap-2", children: g.calls.map(renderToolCard) })
|
|
2402
3050
|
]
|
|
2403
3051
|
},
|
|
2404
3052
|
`tools-${g.index}`
|
|
2405
|
-
) : /* @__PURE__ */
|
|
3053
|
+
) : /* @__PURE__ */ jsx12("div", { className: "flex flex-col gap-2", children: g.calls.map(renderToolCard) }, `tools-${g.index}`)
|
|
2406
3054
|
),
|
|
2407
3055
|
leftoverToolCalls.map(renderToolCard),
|
|
2408
|
-
streaming && segments[lastIndex]?.kind === "tool" && /* @__PURE__ */
|
|
3056
|
+
streaming && segments[lastIndex]?.kind === "tool" && /* @__PURE__ */ jsx12(StreamingCaret, {})
|
|
2409
3057
|
] });
|
|
2410
3058
|
}
|
|
2411
3059
|
function AssistantMessageImpl({
|
|
@@ -2417,44 +3065,45 @@ function AssistantMessageImpl({
|
|
|
2417
3065
|
approval,
|
|
2418
3066
|
onToolCallClick,
|
|
2419
3067
|
toolRenderers,
|
|
2420
|
-
renderExtras
|
|
3068
|
+
renderExtras,
|
|
3069
|
+
durableCards
|
|
2421
3070
|
}) {
|
|
2422
3071
|
const content = useSmoothText(msg.content, streaming);
|
|
2423
3072
|
const reasoning = useSmoothText(msg.reasoning ?? "", streaming);
|
|
2424
|
-
const body =
|
|
3073
|
+
const body = useMemo7(() => renderBody(content), [renderBody, content]);
|
|
2425
3074
|
const segments = msg.segments;
|
|
2426
3075
|
const hasAnswerText = content !== "" || (segments?.some((s) => s.kind === "text" && s.content.trim() !== "") ?? false);
|
|
2427
|
-
const reasoningScrollRef =
|
|
2428
|
-
const thinkStartRef =
|
|
2429
|
-
const thinkMsRef =
|
|
3076
|
+
const reasoningScrollRef = useRef8(null);
|
|
3077
|
+
const thinkStartRef = useRef8(null);
|
|
3078
|
+
const thinkMsRef = useRef8(null);
|
|
2430
3079
|
if (streaming && reasoning && !hasAnswerText && thinkStartRef.current === null) {
|
|
2431
3080
|
thinkStartRef.current = performance.now();
|
|
2432
3081
|
}
|
|
2433
3082
|
if (hasAnswerText && thinkStartRef.current !== null && thinkMsRef.current === null) {
|
|
2434
3083
|
thinkMsRef.current = performance.now() - thinkStartRef.current;
|
|
2435
3084
|
}
|
|
2436
|
-
|
|
3085
|
+
useEffect8(() => {
|
|
2437
3086
|
const el = reasoningScrollRef.current;
|
|
2438
3087
|
if (el && streaming && !hasAnswerText) el.scrollTop = el.scrollHeight;
|
|
2439
3088
|
}, [reasoning, streaming, hasAnswerText]);
|
|
2440
3089
|
const thinkingSeconds = useThinkingSeconds(
|
|
2441
3090
|
streaming && !!reasoning && !hasAnswerText
|
|
2442
3091
|
);
|
|
2443
|
-
return /* @__PURE__ */
|
|
2444
|
-
/* @__PURE__ */
|
|
2445
|
-
/* @__PURE__ */
|
|
2446
|
-
msg.modelUsed && /* @__PURE__ */
|
|
2447
|
-
formatTokensPerSecond(msg) && /* @__PURE__ */
|
|
2448
|
-
formatModelCost(msg, models) && /* @__PURE__ */
|
|
3092
|
+
return /* @__PURE__ */ jsxs10("div", { className: "mx-auto w-full max-w-3xl px-6 py-3", children: [
|
|
3093
|
+
/* @__PURE__ */ jsxs10("div", { className: "mb-1 flex items-baseline gap-2 text-[11px] tracking-wide text-muted-foreground", children: [
|
|
3094
|
+
/* @__PURE__ */ jsx12("span", { className: "font-semibold uppercase", children: agentLabel }),
|
|
3095
|
+
msg.modelUsed && /* @__PURE__ */ jsx12("span", { className: "font-mono normal-case", children: msg.modelUsed }),
|
|
3096
|
+
formatTokensPerSecond(msg) && /* @__PURE__ */ jsx12("span", { children: formatTokensPerSecond(msg) }),
|
|
3097
|
+
formatModelCost(msg, models) && /* @__PURE__ */ jsx12("span", { children: formatModelCost(msg, models) })
|
|
2449
3098
|
] }),
|
|
2450
|
-
reasoning && /* @__PURE__ */
|
|
2451
|
-
/* @__PURE__ */
|
|
3099
|
+
reasoning && /* @__PURE__ */ jsxs10("details", { className: "mb-2 rounded-lg border-l-2 border-border/70 bg-muted/20 px-3 py-2", open: !hasAnswerText, children: [
|
|
3100
|
+
/* @__PURE__ */ jsx12("summary", { className: "cursor-pointer select-none text-xs font-medium text-muted-foreground", children: !hasAnswerText ? /* @__PURE__ */ jsxs10("span", { className: "animate-pulse", children: [
|
|
2452
3101
|
"Thinking",
|
|
2453
3102
|
thinkingSeconds >= 3 ? ` \xB7 ${thinkingSeconds}s` : "\u2026"
|
|
2454
3103
|
] }) : thinkMsRef.current != null ? `Thought for ${Math.max(1, Math.round(thinkMsRef.current / 1e3))}s` : "Thought process" }),
|
|
2455
|
-
/* @__PURE__ */
|
|
3104
|
+
/* @__PURE__ */ jsx12("div", { ref: reasoningScrollRef, className: "mt-2 max-h-48 overflow-y-auto whitespace-pre-wrap text-[13px] leading-relaxed text-muted-foreground", children: reasoning })
|
|
2456
3105
|
] }),
|
|
2457
|
-
segments && segments.length > 0 ? /* @__PURE__ */
|
|
3106
|
+
segments && segments.length > 0 ? /* @__PURE__ */ jsx12(
|
|
2458
3107
|
SegmentedBody,
|
|
2459
3108
|
{
|
|
2460
3109
|
segments,
|
|
@@ -2465,12 +3114,12 @@ function AssistantMessageImpl({
|
|
|
2465
3114
|
onToolCallClick,
|
|
2466
3115
|
toolRenderers
|
|
2467
3116
|
}
|
|
2468
|
-
) : /* @__PURE__ */
|
|
2469
|
-
/* @__PURE__ */
|
|
3117
|
+
) : /* @__PURE__ */ jsxs10(Fragment5, { children: [
|
|
3118
|
+
/* @__PURE__ */ jsxs10("div", { className: "text-base leading-[1.75]", children: [
|
|
2470
3119
|
body,
|
|
2471
|
-
streaming && content && !msg.toolCalls?.length && /* @__PURE__ */
|
|
3120
|
+
streaming && content && !msg.toolCalls?.length && /* @__PURE__ */ jsx12(StreamingCaret, {})
|
|
2472
3121
|
] }),
|
|
2473
|
-
msg.toolCalls && msg.toolCalls.length > 0 && /* @__PURE__ */
|
|
3122
|
+
msg.toolCalls && msg.toolCalls.length > 0 && /* @__PURE__ */ jsx12("div", { className: "mt-2 flex flex-col gap-1.5", children: msg.toolCalls.map((tc) => /* @__PURE__ */ jsx12(
|
|
2474
3123
|
ToolCallCard,
|
|
2475
3124
|
{
|
|
2476
3125
|
call: tc,
|
|
@@ -2482,13 +3131,22 @@ function AssistantMessageImpl({
|
|
|
2482
3131
|
tc.id
|
|
2483
3132
|
)) })
|
|
2484
3133
|
] }),
|
|
3134
|
+
durableCards && msg.parts && /* @__PURE__ */ jsx12(
|
|
3135
|
+
DurableChatCards,
|
|
3136
|
+
{
|
|
3137
|
+
...durableCards,
|
|
3138
|
+
parts: msg.parts,
|
|
3139
|
+
renderMarkdown: renderBody,
|
|
3140
|
+
className: "mt-3"
|
|
3141
|
+
}
|
|
3142
|
+
),
|
|
2485
3143
|
renderExtras?.(msg)
|
|
2486
3144
|
] });
|
|
2487
3145
|
}
|
|
2488
3146
|
var AssistantMessage = memo(AssistantMessageImpl);
|
|
2489
3147
|
function useThinkingSeconds(active) {
|
|
2490
|
-
const [seconds, setSeconds] =
|
|
2491
|
-
|
|
3148
|
+
const [seconds, setSeconds] = useState12(0);
|
|
3149
|
+
useEffect8(() => {
|
|
2492
3150
|
if (!active) return;
|
|
2493
3151
|
setSeconds(0);
|
|
2494
3152
|
const id = setInterval(() => setSeconds((s) => s + 1), 1e3);
|
|
@@ -2498,23 +3156,23 @@ function useThinkingSeconds(active) {
|
|
|
2498
3156
|
}
|
|
2499
3157
|
function ThinkingRow({ agentLabel }) {
|
|
2500
3158
|
const seconds = useThinkingSeconds(true);
|
|
2501
|
-
return /* @__PURE__ */
|
|
2502
|
-
/* @__PURE__ */
|
|
2503
|
-
/* @__PURE__ */
|
|
2504
|
-
/* @__PURE__ */
|
|
3159
|
+
return /* @__PURE__ */ jsxs10("div", { className: "mx-auto w-full max-w-3xl px-6 py-3", children: [
|
|
3160
|
+
/* @__PURE__ */ jsx12("p", { className: "mb-1 text-[11px] font-semibold uppercase tracking-wide text-muted-foreground", children: agentLabel }),
|
|
3161
|
+
/* @__PURE__ */ jsxs10("div", { className: "flex items-center gap-2 text-base text-muted-foreground", children: [
|
|
3162
|
+
/* @__PURE__ */ jsx12("svg", { className: "h-4 w-4 animate-spin", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", "aria-hidden": true, children: /* @__PURE__ */ jsx12("path", { d: "M21 12a9 9 0 1 1-6.219-8.56", strokeLinecap: "round" }) }),
|
|
2505
3163
|
"Thinking",
|
|
2506
3164
|
seconds >= 3 ? ` \xB7 ${seconds}s` : "..."
|
|
2507
3165
|
] })
|
|
2508
3166
|
] });
|
|
2509
3167
|
}
|
|
2510
3168
|
function StreamErrorRow({ message, onRetry }) {
|
|
2511
|
-
return /* @__PURE__ */
|
|
2512
|
-
/* @__PURE__ */
|
|
2513
|
-
/* @__PURE__ */
|
|
2514
|
-
/* @__PURE__ */
|
|
3169
|
+
return /* @__PURE__ */ jsx12("div", { className: "mx-auto w-full max-w-3xl px-6 py-3", children: /* @__PURE__ */ jsxs10("div", { role: "alert", className: "flex items-start gap-2.5 rounded-lg border border-destructive/40 bg-destructive/5 px-3 py-2.5 text-sm text-destructive", children: [
|
|
3170
|
+
/* @__PURE__ */ jsxs10("svg", { className: "mt-0.5 h-4 w-4 shrink-0", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: [
|
|
3171
|
+
/* @__PURE__ */ jsx12("circle", { cx: "12", cy: "12", r: "9" }),
|
|
3172
|
+
/* @__PURE__ */ jsx12("path", { d: "M12 8v4m0 4h.01" })
|
|
2515
3173
|
] }),
|
|
2516
|
-
/* @__PURE__ */
|
|
2517
|
-
onRetry && /* @__PURE__ */
|
|
3174
|
+
/* @__PURE__ */ jsx12("span", { className: "min-w-0 flex-1 break-words", children: message }),
|
|
3175
|
+
onRetry && /* @__PURE__ */ jsx12(
|
|
2518
3176
|
"button",
|
|
2519
3177
|
{
|
|
2520
3178
|
type: "button",
|
|
@@ -2530,6 +3188,7 @@ function ChatMessages({
|
|
|
2530
3188
|
models = [],
|
|
2531
3189
|
renderMarkdown,
|
|
2532
3190
|
renderExtras,
|
|
3191
|
+
durableCards,
|
|
2533
3192
|
userLabel = "User",
|
|
2534
3193
|
agentLabel = "Agent",
|
|
2535
3194
|
loading,
|
|
@@ -2542,25 +3201,25 @@ function ChatMessages({
|
|
|
2542
3201
|
emptyState,
|
|
2543
3202
|
header
|
|
2544
3203
|
}) {
|
|
2545
|
-
const renderBody =
|
|
2546
|
-
() => renderMarkdown ?? ((content) => /* @__PURE__ */
|
|
3204
|
+
const renderBody = useMemo7(
|
|
3205
|
+
() => renderMarkdown ?? ((content) => /* @__PURE__ */ jsx12("p", { className: "whitespace-pre-wrap", children: content })),
|
|
2547
3206
|
[renderMarkdown]
|
|
2548
3207
|
);
|
|
2549
3208
|
const lastIsUser = messages[messages.length - 1]?.role === "user";
|
|
2550
3209
|
if (messages.length === 0 && !loading && !error) {
|
|
2551
|
-
const empty = renderEmpty ? renderEmpty() : /* @__PURE__ */
|
|
2552
|
-
return /* @__PURE__ */
|
|
3210
|
+
const empty = renderEmpty ? renderEmpty() : /* @__PURE__ */ jsx12(ChatEmptyState, { ...emptyState });
|
|
3211
|
+
return /* @__PURE__ */ jsxs10(Fragment5, { children: [
|
|
2553
3212
|
header,
|
|
2554
3213
|
empty
|
|
2555
3214
|
] });
|
|
2556
3215
|
}
|
|
2557
|
-
return /* @__PURE__ */
|
|
3216
|
+
return /* @__PURE__ */ jsxs10(Fragment5, { children: [
|
|
2558
3217
|
header,
|
|
2559
3218
|
messages.map(
|
|
2560
|
-
(msg) => msg.role === "user" ? /* @__PURE__ */
|
|
2561
|
-
/* @__PURE__ */
|
|
2562
|
-
/* @__PURE__ */
|
|
2563
|
-
] }) }, msg.id) : /* @__PURE__ */
|
|
3219
|
+
(msg) => msg.role === "user" ? /* @__PURE__ */ jsx12("div", { className: "mx-auto w-full max-w-3xl px-6 py-3", children: /* @__PURE__ */ jsxs10("div", { className: "ml-auto w-fit max-w-[85%]", children: [
|
|
3220
|
+
/* @__PURE__ */ jsx12("p", { className: "mb-1 text-right text-[11px] font-semibold uppercase tracking-wide text-muted-foreground", children: userLabel }),
|
|
3221
|
+
/* @__PURE__ */ jsx12("div", { className: "rounded-2xl rounded-tr-md bg-primary/10 px-4 py-2.5 text-base leading-relaxed", children: /* @__PURE__ */ jsx12("p", { className: "whitespace-pre-wrap", children: msg.content }) })
|
|
3222
|
+
] }) }, msg.id) : /* @__PURE__ */ jsx12(
|
|
2564
3223
|
AssistantMessage,
|
|
2565
3224
|
{
|
|
2566
3225
|
msg,
|
|
@@ -2571,13 +3230,14 @@ function ChatMessages({
|
|
|
2571
3230
|
approval,
|
|
2572
3231
|
onToolCallClick,
|
|
2573
3232
|
toolRenderers,
|
|
2574
|
-
renderExtras
|
|
3233
|
+
renderExtras,
|
|
3234
|
+
durableCards
|
|
2575
3235
|
},
|
|
2576
3236
|
msg.id
|
|
2577
3237
|
)
|
|
2578
3238
|
),
|
|
2579
|
-
loading && lastIsUser && /* @__PURE__ */
|
|
2580
|
-
error && !loading && /* @__PURE__ */
|
|
3239
|
+
loading && lastIsUser && /* @__PURE__ */ jsx12(ThinkingRow, { agentLabel }),
|
|
3240
|
+
error && !loading && /* @__PURE__ */ jsx12(StreamErrorRow, { message: error, onRetry })
|
|
2581
3241
|
] });
|
|
2582
3242
|
}
|
|
2583
3243
|
|
|
@@ -2590,12 +3250,9 @@ export {
|
|
|
2590
3250
|
ModelPicker,
|
|
2591
3251
|
DEFAULT_EFFORT_LEVELS,
|
|
2592
3252
|
EffortPicker,
|
|
2593
|
-
dispatchChatStreamLine,
|
|
2594
|
-
consumeChatStream,
|
|
2595
|
-
streamChatTurn,
|
|
2596
|
-
ChatComposer,
|
|
2597
3253
|
interactionStatusLabels,
|
|
2598
3254
|
interactionTerminalNotes,
|
|
3255
|
+
fieldValuesFromAnswers,
|
|
2599
3256
|
fieldAnswer,
|
|
2600
3257
|
buildAnswerData,
|
|
2601
3258
|
isLateAnswerableStatus,
|
|
@@ -2609,13 +3266,33 @@ export {
|
|
|
2609
3266
|
InteractionActionButton,
|
|
2610
3267
|
QuestionOptionList,
|
|
2611
3268
|
InteractionQuestionCard,
|
|
3269
|
+
DurablePlanCard,
|
|
2612
3270
|
InteractionPlanCard,
|
|
3271
|
+
durableChatCardsFromParts,
|
|
3272
|
+
DurableChatCards,
|
|
3273
|
+
dispatchChatStreamLine,
|
|
3274
|
+
consumeChatStream,
|
|
3275
|
+
streamChatTurn,
|
|
3276
|
+
ChatComposer,
|
|
3277
|
+
DurablePlanClientError,
|
|
3278
|
+
createDurablePlanDecisionClient,
|
|
3279
|
+
useDurablePlanFlow,
|
|
3280
|
+
createSessionInteractionAttemptStore,
|
|
3281
|
+
createMemoryInteractionAttemptStore,
|
|
3282
|
+
interactionSubmissionSignature,
|
|
3283
|
+
createDurableInteractionAnswerSubmitter,
|
|
2613
3284
|
upsertChatInteraction,
|
|
2614
3285
|
cancelChatInteraction,
|
|
2615
3286
|
resolveChatInteraction,
|
|
2616
3287
|
terminalizePendingChatInteractions,
|
|
2617
3288
|
restoreChatInteractions,
|
|
3289
|
+
hydrateChatInteractions,
|
|
2618
3290
|
useChatInteractions,
|
|
3291
|
+
rankFileMentions,
|
|
3292
|
+
DEFAULT_MENTION_LIMIT,
|
|
3293
|
+
INDEX_REFRESH_AFTER_MS,
|
|
3294
|
+
DEFAULT_MENTION_EMPTY_TEXT,
|
|
3295
|
+
useFileMentions,
|
|
2619
3296
|
activityTone,
|
|
2620
3297
|
formatActivityCost,
|
|
2621
3298
|
formatActivityDuration,
|
|
@@ -2634,4 +3311,4 @@ export {
|
|
|
2634
3311
|
useThinkingSeconds,
|
|
2635
3312
|
ChatMessages
|
|
2636
3313
|
};
|
|
2637
|
-
//# sourceMappingURL=chunk-
|
|
3314
|
+
//# sourceMappingURL=chunk-ZLHK25C3.js.map
|