@tangle-network/agent-app 0.43.41 → 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.
Files changed (47) hide show
  1. package/dist/assistant/index.d.ts +2 -1
  2. package/dist/assistant/index.js +3 -2
  3. package/dist/assistant/index.js.map +1 -1
  4. package/dist/chat-routes/index.d.ts +17 -3
  5. package/dist/chat-routes/index.js +91 -6
  6. package/dist/chat-routes/index.js.map +1 -1
  7. package/dist/chat-store/index.d.ts +4 -3
  8. package/dist/chat-store/index.js +5 -1
  9. package/dist/chat-store/index.js.map +1 -1
  10. package/dist/{chunk-Y4QHNQ75.js → chunk-6KVH5SP5.js} +110 -6
  11. package/dist/chunk-6KVH5SP5.js.map +1 -0
  12. package/dist/{chunk-XYWCGFII.js → chunk-B5JD3DXD.js} +48 -2
  13. package/dist/chunk-B5JD3DXD.js.map +1 -0
  14. package/dist/{chunk-I2R2XT4M.js → chunk-I2ATYB7R.js} +18 -2
  15. package/dist/chunk-I2ATYB7R.js.map +1 -0
  16. package/dist/chunk-SIXYZ2FB.js +101 -0
  17. package/dist/chunk-SIXYZ2FB.js.map +1 -0
  18. package/dist/{chunk-4TXDD6P2.js → chunk-XAWFPMAR.js} +38 -3
  19. package/dist/chunk-XAWFPMAR.js.map +1 -0
  20. package/dist/{chunk-KM766NN3.js → chunk-ZLHK25C3.js} +1766 -1223
  21. package/dist/chunk-ZLHK25C3.js.map +1 -0
  22. package/dist/chunk-ZU5GNSOJ.js +920 -0
  23. package/dist/chunk-ZU5GNSOJ.js.map +1 -0
  24. package/dist/{contract-DYbTzEDf.d.ts → contract-KfqJh_au.d.ts} +23 -2
  25. package/dist/design-canvas-react/index.js +4 -4
  26. package/dist/durable-chat/index.d.ts +419 -0
  27. package/dist/durable-chat/index.js +59 -0
  28. package/dist/durable-chat/index.js.map +1 -0
  29. package/dist/index.d.ts +5 -3
  30. package/dist/index.js +123 -45
  31. package/dist/interactions/index.d.ts +58 -3
  32. package/dist/interactions/index.js +7 -3
  33. package/dist/{parts-BcbitSNp.d.ts → parts-DjX0RRTS.d.ts} +9 -3
  34. package/dist/plans/index.d.ts +69 -0
  35. package/dist/plans/index.js +21 -0
  36. package/dist/plans/index.js.map +1 -0
  37. package/dist/stream/index.js +3 -1
  38. package/dist/teams/index.js +9 -9
  39. package/dist/teams/invitations-api.js +3 -3
  40. package/dist/web-react/index.d.ts +252 -93
  41. package/dist/web-react/index.js +32 -3
  42. package/package.json +11 -1
  43. package/dist/chunk-4TXDD6P2.js.map +0 -1
  44. package/dist/chunk-I2R2XT4M.js.map +0 -1
  45. package/dist/chunk-KM766NN3.js.map +0 -1
  46. package/dist/chunk-XYWCGFII.js.map +0 -1
  47. package/dist/chunk-Y4QHNQ75.js.map +0 -1
@@ -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-4TXDD6P2.js";
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 useEffect5, useMemo as useMemo7, useRef as useRef7, useState as useState10, memo } from "react";
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/chat-stream.ts
376
- function dispatchChatStreamLine(line, cb) {
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
- The agent hit an error and this turn stopped: ${message}`);
460
- receivedContent = true;
461
- }
462
- break;
463
- }
464
- default:
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
- async function consumeChatStream(body, cb) {
470
- const reader = body.getReader();
471
- const decoder = new TextDecoder();
472
- let buffer = "";
473
- let turnId = null;
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
- for (; ; ) {
484
- const { done, value } = await reader.read();
485
- if (done) {
486
- if (buffer.trim()) handle(buffer);
487
- break;
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 { turnId, receivedContent };
412
+ return values;
495
413
  }
496
- async function streamChatTurn(opts) {
497
- const res = await opts.start();
498
- if (!res.ok || !res.body) {
499
- const err = await res.json().catch(() => ({ error: `HTTP ${res.status}` }));
500
- throw new Error(err.error ?? `HTTP ${res.status}`);
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
- let turnId = null;
503
- const cb = {
504
- ...opts.callbacks,
505
- onTurnId: (id) => {
506
- turnId = id;
507
- opts.callbacks.onTurnId?.(id);
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
- // src/web-react/chat-composer.tsx
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 StopGlyph({ className }) {
533
- return /* @__PURE__ */ jsx4("svg", { className, viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": true, children: /* @__PURE__ */ jsx4("rect", { x: "6", y: "6", width: "12", height: "12", rx: "2" }) });
445
+ function hasSecretField(fields) {
446
+ return fields.some((field) => field.type === "secret");
534
447
  }
535
- function PaperclipGlyph({ className }) {
536
- 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: "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" }) });
448
+ function optionLabel(field, value) {
449
+ return field.options.find((option) => option.value === value)?.label ?? value;
537
450
  }
538
- function FolderGlyph({ className }) {
539
- return /* @__PURE__ */ jsxs3("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: [
540
- /* @__PURE__ */ jsx4("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" }),
541
- /* @__PURE__ */ jsx4("path", { d: "M12 10v6m-3-3h6" })
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 CloseGlyph({ className }) {
545
- return /* @__PURE__ */ jsx4("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", "aria-hidden": true, children: /* @__PURE__ */ jsx4("path", { d: "M18 6 6 18M6 6l12 12" }) });
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
- function UploadGlyph({ className }) {
548
- 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: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M17 8l-5-5-5 5M12 3v12" }) });
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
- var MAX_HEIGHT = 168;
551
- function ChatComposer({
552
- onSend,
553
- onSendParts,
554
- onCancel,
555
- isStreaming = false,
556
- disabled = false,
557
- placeholder = "Message the agent\u2026",
558
- value,
559
- onValueChange,
560
- initialValue,
561
- seed,
562
- onSeedApplied,
563
- controls,
564
- controlsPlacement = "above",
565
- onAttach,
566
- onAttachFolder,
567
- pendingFiles = [],
568
- onRemoveFile,
569
- accept,
570
- dropTitle = "Drop files to add context",
571
- dropDescription = "They attach to your next message.",
572
- focusShortcut = true,
573
- sendLabel = "Send",
574
- className
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
- document.addEventListener("keydown", onKeyDown);
631
- return () => document.removeEventListener("keydown", onKeyDown);
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
- const handleFileChange = (e) => {
658
- if (e.target.files?.length) onAttach?.(e.target.files);
659
- e.target.value = "";
660
- };
661
- const handleFolderChange = (e) => {
662
- if (e.target.files?.length) (onAttachFolder ?? onAttach)?.(e.target.files);
663
- e.target.value = "";
664
- };
665
- const handleDragEnter = useCallback((e) => {
666
- e.preventDefault();
667
- e.stopPropagation();
668
- dragDepth.current++;
669
- if (e.dataTransfer?.types.includes("Files")) setDragOver(true);
670
- }, []);
671
- const handleDragLeave = useCallback((e) => {
672
- e.preventDefault();
673
- e.stopPropagation();
674
- dragDepth.current--;
675
- if (dragDepth.current <= 0) {
676
- dragDepth.current = 0;
677
- setDragOver(false);
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
- const folderChips = pendingFiles.filter((f) => f.kind === "folder");
697
- const fileChips = pendingFiles.filter((f) => f.kind !== "folder");
698
- const showFooter = controls != null && controlsPlacement === "footer";
699
- const showAbove = controls != null && controlsPlacement === "above";
700
- return /* @__PURE__ */ jsxs3(
701
- "div",
702
- {
703
- className: `relative ${className ?? ""}`,
704
- onDragEnter: onAttach ? handleDragEnter : void 0,
705
- onDragLeave: onAttach ? handleDragLeave : void 0,
706
- onDragOver: onAttach ? handleDragOver : void 0,
707
- onDrop: onAttach ? handleDrop : void 0,
708
- children: [
709
- dragOver && /* @__PURE__ */ jsx4("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__ */ jsxs3("div", { className: "text-center", children: [
710
- /* @__PURE__ */ jsx4("span", { className: "mx-auto mb-2 flex h-11 w-11 items-center justify-center rounded-xl bg-primary/10 text-primary", children: /* @__PURE__ */ jsx4(UploadGlyph, { className: "h-5 w-5" }) }),
711
- /* @__PURE__ */ jsx4("p", { className: "text-sm font-semibold text-foreground", children: dropTitle }),
712
- /* @__PURE__ */ jsx4("p", { className: "mt-0.5 text-xs text-muted-foreground", children: dropDescription })
713
- ] }) }),
714
- showAbove && /* @__PURE__ */ jsx4("div", { className: "mb-1.5 flex flex-wrap items-center gap-1.5 px-1", children: controls }),
715
- pendingFiles.length > 0 && /* @__PURE__ */ jsx4("div", { className: "mb-2 flex flex-wrap gap-1.5", children: [...folderChips, ...fileChips].map((f) => /* @__PURE__ */ jsxs3(
716
- "span",
717
- {
718
- 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"}`,
719
- children: [
720
- f.kind === "folder" ? /* @__PURE__ */ jsx4(FolderGlyph, { className: "h-3 w-3 shrink-0" }) : /* @__PURE__ */ jsx4(PaperclipGlyph, { className: "h-3 w-3 shrink-0" }),
721
- /* @__PURE__ */ jsx4("span", { className: "max-w-[150px] truncate", children: f.name }),
722
- f.fileCount !== void 0 && /* @__PURE__ */ jsxs3("span", { className: "text-muted-foreground", children: [
723
- "(",
724
- f.fileCount,
725
- ")"
726
- ] }),
727
- f.status === "uploading" && /* @__PURE__ */ jsx4("span", { className: "h-3 w-3 animate-spin rounded-full border-2 border-primary border-t-transparent" }),
728
- onRemoveFile && /* @__PURE__ */ jsx4(
729
- "button",
730
- {
731
- type: "button",
732
- "aria-label": `Remove ${f.name}`,
733
- onClick: () => onRemoveFile(f.id),
734
- className: "rounded p-0.5 text-muted-foreground transition hover:text-foreground focus:outline-none focus-visible:ring-2 focus-visible:ring-ring",
735
- children: /* @__PURE__ */ jsx4(CloseGlyph, { className: "h-3 w-3" })
736
- }
737
- )
738
- ]
739
- },
740
- f.id
741
- )) }),
742
- /* @__PURE__ */ jsxs3("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: [
743
- onAttach && /* @__PURE__ */ jsxs3(Fragment2, { children: [
744
- /* @__PURE__ */ jsx4(
745
- "button",
746
- {
747
- type: "button",
748
- onClick: () => fileInputRef.current?.click(),
749
- disabled,
750
- "aria-label": "Attach files",
751
- title: "Attach files",
752
- 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",
753
- children: /* @__PURE__ */ jsx4(PaperclipGlyph, { className: "h-4 w-4" })
754
- }
755
- ),
756
- /* @__PURE__ */ jsx4("input", { ref: fileInputRef, type: "file", multiple: true, className: "hidden", accept, onChange: handleFileChange })
757
- ] }),
758
- onAttachFolder && /* @__PURE__ */ jsxs3(Fragment2, { children: [
759
- /* @__PURE__ */ jsx4(
760
- "button",
761
- {
762
- type: "button",
763
- onClick: () => folderInputRef.current?.click(),
764
- disabled,
765
- "aria-label": "Attach folder",
766
- title: "Attach folder",
767
- 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",
768
- children: /* @__PURE__ */ jsx4(FolderGlyph, { className: "h-4 w-4" })
769
- }
770
- ),
771
- /* @__PURE__ */ jsx4(
772
- "input",
773
- {
774
- ref: folderInputRef,
775
- type: "file",
776
- multiple: true,
777
- className: "hidden",
778
- onChange: handleFolderChange,
779
- ...{ webkitdirectory: "" }
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
- "textarea",
715
+ QuestionOptionList,
785
716
  {
786
- ref: textareaRef,
787
- value: text,
788
- onChange: (e) => setText(e.target.value),
789
- onKeyDown: handleKeyDown,
790
- placeholder,
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
- rows: 1,
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
- showFooter && /* @__PURE__ */ jsx4("div", { className: "mb-0.5 flex shrink-0 items-center gap-1.5", children: controls }),
798
- isStreaming ? /* @__PURE__ */ jsxs3(
799
- "button",
726
+ select.allowCustom === true && /* @__PURE__ */ jsx4(
727
+ "input",
800
728
  {
801
- type: "button",
802
- onClick: onCancel,
803
- "aria-label": "Stop response",
804
- 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",
805
- children: [
806
- /* @__PURE__ */ jsx4(StopGlyph, { className: "h-3.5 w-3.5" }),
807
- /* @__PURE__ */ jsx4("span", { children: "Stop" })
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
- ) : /* @__PURE__ */ jsxs3(
811
- "button",
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: "button",
814
- onClick: send,
815
- disabled: !canSend,
816
- "aria-label": sendLabel,
817
- 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",
818
- children: [
819
- /* @__PURE__ */ jsx4(SendGlyph, { className: "h-3.5 w-3.5" }),
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
- focusShortcut && /* @__PURE__ */ jsx4("div", { className: "mt-1.5 flex justify-end px-1", children: /* @__PURE__ */ jsxs3("span", { className: "text-xs text-muted-foreground", children: [
826
- /* @__PURE__ */ jsx4("kbd", { className: "rounded border border-border bg-background px-1 py-0.5 text-[10px]", children: "Cmd" }),
827
- /* @__PURE__ */ jsx4("kbd", { className: "ml-0.5 rounded border border-border bg-background px-1 py-0.5 text-[10px]", children: "L" }),
828
- /* @__PURE__ */ jsx4("span", { className: "ml-1", children: "to focus" })
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/interaction-card-support.ts
836
- function interactionStatusLabels(labels) {
837
- return { cancelled: "Withdrawn", expired: "Expired", ...labels };
838
- }
839
- function interactionTerminalNotes(noun, extra) {
840
- return {
841
- expired: `This ${noun} expired \u2014 send a new message to continue.`,
842
- cancelled: `The agent withdrew this ${noun}.`,
843
- ...extra
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
- if (field.type === "boolean") return value.selected ? value.selected[0] === "true" : null;
859
- const text = value.text?.trim();
860
- return text ? text : null;
861
- }
862
- function buildAnswerData(fields, values) {
863
- const data = {};
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
- data[field.name] = answer;
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 data;
873
- }
874
- function isLateAnswerableStatus(status) {
875
- return status === "expired" || status === "cancelled";
876
- }
877
- function hasSecretField(fields) {
878
- return fields.some((field) => field.type === "secret");
1055
+ return cards;
879
1056
  }
880
- function optionLabel(field, value) {
881
- return field.options.find((option) => option.value === value)?.label ?? value;
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
- function answerText(field, answer) {
884
- if (field.type === "select" && Array.isArray(answer)) {
885
- return answer.map((value) => optionLabel(field, value)).join(", ");
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 (field.type === "boolean") return answer === true ? "Yes" : "No";
888
- if (field.type === "secret") return "[secret omitted]";
889
- return String(answer);
890
- }
891
- function lateAnswerMessage(interaction, data) {
892
- const title = interaction.title.trim() || "the earlier question";
893
- const body = interaction.body?.trim();
894
- const answers = interaction.fields.map((field) => {
895
- const answer = data[field.name];
896
- if (answer === void 0) return null;
897
- return { label: field.label.trim(), text: answerText(field, answer).trim() };
898
- }).filter((item) => !!item && item.text.length > 0);
899
- const only = answers.length === 1 ? answers[0] : void 0;
900
- const answerSummary = only ? only.text : answers.map((item) => `${item.label || "Answer"}: ${item.text}`).join("\n");
901
- return [
902
- `Regarding your earlier question: "${title}"`,
903
- body ? `Context: ${body}` : null,
904
- `My answer: ${answerSummary}`
905
- ].filter((line) => !!line).join("\n");
906
- }
907
- var INTERACTION_SUBMIT_TIMEOUT_MS = 3e4;
908
- var INTERACTION_SUBMIT_TIMEOUT_MESSAGE = "Could not reach the agent. Try again.";
909
- async function responseErrorMessage(res) {
910
- const text = await res.text().catch(() => "");
911
- if (text) {
912
- try {
913
- const parsed = JSON.parse(text);
914
- const message = typeof parsed.error === "string" && parsed.error.trim() ? parsed.error : typeof parsed.message === "string" && parsed.message.trim() ? parsed.message : null;
915
- if (message) return { ...typeof parsed.code === "string" ? { code: parsed.code } : {}, message };
916
- } catch {
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 };
1228
+ }
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 { message: `Answer failed (${res.status})` };
1254
+ return { turnId, receivedContent };
920
1255
  }
921
- function createInteractionAnswerSubmitter(options) {
922
- const timeoutMs = options.timeoutMs ?? INTERACTION_SUBMIT_TIMEOUT_MS;
923
- return async (submission) => {
924
- const doFetch = options.fetchImpl ?? fetch;
925
- const url = typeof options.url === "function" ? options.url(submission) : options.url;
926
- const extra = typeof options.body === "function" ? options.body(submission) : options.body ?? {};
927
- const controller = new AbortController();
928
- const timer = setTimeout(() => controller.abort(INTERACTION_SUBMIT_TIMEOUT_MESSAGE), timeoutMs);
929
- try {
930
- const res = await doFetch(url, {
931
- method: "POST",
932
- headers: { "Content-Type": "application/json" },
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/interaction-question-card.tsx
956
- import { useMemo as useMemo2, useRef as useRef4, useState as useState4 } from "react";
957
- import { Fragment as Fragment3, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
958
- function CheckGlyph({ className }) {
959
- return /* @__PURE__ */ jsx5("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: /* @__PURE__ */ jsx5("path", { d: "M20 6 9 17l-5-5" }) });
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
- var BADGE_VARIANT_CLASSES = {
962
- outline: "border-border text-foreground",
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 InteractionActionButton({
970
- variant = "primary",
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
- 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";
988
- function QuestionOptionList({
989
- groupName,
990
- idPrefix,
991
- options,
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 selectField(field) {
1024
- return field.type === "select" ? field : null;
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 valuesWithSelected(values, field, optionValue) {
1027
- const current = values[field.name]?.selected ?? [];
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 STATUS_LABELS = interactionStatusLabels({
1035
- pending: "Waiting for your answer",
1036
- answered: "Answered",
1037
- declined: "Declined"
1038
- });
1039
- var TERMINAL_NOTES = interactionTerminalNotes("question", {
1040
- expired: "The original run ended. Answer now to send a new message with this context.",
1041
- cancelled: "The agent withdrew this question. Answer now to send a new message with this context."
1042
- });
1043
- function InteractionQuestionCard({
1044
- interaction,
1045
- canWrite,
1046
- submitAnswer,
1047
- onResolved,
1048
- onLateAnswer,
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 [values, setValues] = useState4({});
1052
- const [submitting, setSubmitting] = useState4(false);
1053
- const [localStatus, setLocalStatus] = useState4(null);
1054
- const [lateAnswerSent, setLateAnswerSent] = useState4(false);
1055
- const [error, setError] = useState4(null);
1056
- const submitInFlightRef = useRef4(false);
1057
- const status = isTerminalInteractionStatus(interaction.status) ? interaction.status : localStatus ?? interaction.status;
1058
- const answered = status === "answered";
1059
- const lateAnswerable = isLateAnswerableStatus(status) && onLateAnswer !== void 0;
1060
- const secretLateAnswerBlocked = lateAnswerable && hasSecretField(interaction.fields);
1061
- const canLateAnswer = canWrite && lateAnswerable && !lateAnswerSent && !secretLateAnswerBlocked;
1062
- const disabled = !canWrite || status !== "pending" && !canLateAnswer || submitting;
1063
- const answerData = useMemo2(() => buildAnswerData(interaction.fields, values), [interaction.fields, values]);
1064
- const setFieldValue = (name, patch) => {
1065
- setValues((prev) => ({ ...prev, [name]: { ...prev[name], ...patch } }));
1066
- };
1067
- const toggleSelected = (field, optionValue) => {
1068
- setValues((prev) => valuesWithSelected(prev, field, optionValue));
1069
- };
1070
- async function submitLateAnswer() {
1071
- if (submitInFlightRef.current || !canLateAnswer || !onLateAnswer) return;
1072
- const data = buildAnswerData(interaction.fields, values);
1073
- if (!data) return;
1074
- submitInFlightRef.current = true;
1075
- setSubmitting(true);
1076
- setError(null);
1077
- let accepted;
1078
- try {
1079
- accepted = await onLateAnswer(lateAnswerMessage(interaction, data));
1080
- } catch {
1081
- accepted = false;
1082
- } finally {
1083
- submitInFlightRef.current = false;
1084
- setSubmitting(false);
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
- if (accepted === false) {
1087
- setError("The new message was not sent. Try again from this card.");
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
- setLateAnswerSent(true);
1091
- }
1092
- async function submit() {
1093
- if (lateAnswerable) {
1094
- await submitLateAnswer();
1095
- return;
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
- if (submitInFlightRef.current || disabled || !answerData) return;
1098
- submitInFlightRef.current = true;
1099
- setSubmitting(true);
1100
- setError(null);
1101
- try {
1102
- const result = await submitAnswer({ id: interaction.id, outcome: "accepted", data: answerData });
1103
- if (result.ok) {
1104
- setLocalStatus("answered");
1105
- onResolved?.(interaction.id, "answered");
1106
- return;
1107
- }
1108
- if (result.expired) {
1109
- setLocalStatus("expired");
1110
- onResolved?.(interaction.id, "expired");
1111
- return;
1112
- }
1113
- setError(result.message);
1114
- } finally {
1115
- submitInFlightRef.current = false;
1116
- setSubmitting(false);
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 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];
1120
- const showSubmitButton = status === "pending" || canWrite && lateAnswerable && !lateAnswerSent;
1121
- let submitLabel = "Submit answer";
1122
- if (lateAnswerable) {
1123
- submitLabel = submitting ? "Sending\u2026" : "Send as new message";
1124
- } else if (submitting) {
1125
- submitLabel = "Submitting\u2026";
1126
- }
1127
- return /* @__PURE__ */ jsxs4("div", { className: `rounded-xl border border-border bg-card p-4 shadow-sm ${className ?? ""}`, children: [
1128
- /* @__PURE__ */ jsxs4("div", { className: "mb-3 flex flex-wrap items-center justify-between gap-2", children: [
1129
- /* @__PURE__ */ jsxs4("div", { className: "flex flex-wrap items-center gap-2", children: [
1130
- /* @__PURE__ */ jsx5(InteractionBadge, { variant: "outline", children: "Question" }),
1131
- /* @__PURE__ */ jsx5(InteractionBadge, { variant: answered ? "default" : status === "expired" || status === "declined" ? "destructive" : "outline", children: STATUS_LABELS[status] })
1132
- ] }),
1133
- /* @__PURE__ */ jsx5("span", { className: "text-xs text-muted-foreground", children: "The agent asked for input" })
1134
- ] }),
1135
- interaction.title.trim() && interaction.fields.every((field) => field.label !== interaction.title) && /* @__PURE__ */ jsx5("p", { className: "mb-3 text-sm font-medium leading-5 text-foreground", children: interaction.title }),
1136
- interaction.body && /* @__PURE__ */ jsx5("p", { className: "mb-3 text-sm leading-5 text-muted-foreground", children: interaction.body }),
1137
- /* @__PURE__ */ jsx5("div", { className: "space-y-4", children: interaction.fields.map((field) => {
1138
- const value = values[field.name] ?? {};
1139
- const select = selectField(field);
1140
- return /* @__PURE__ */ jsxs4("fieldset", { className: "space-y-2", children: [
1141
- /* @__PURE__ */ jsx5("p", { className: "text-sm font-medium leading-5 text-foreground", children: field.label }),
1142
- select ? /* @__PURE__ */ jsxs4("div", { className: "space-y-2", children: [
1143
- /* @__PURE__ */ jsx5(
1144
- QuestionOptionList,
1145
- {
1146
- groupName: `${interaction.id}-${field.name}`,
1147
- idPrefix: `${interaction.id}-${field.name}`,
1148
- options: select.options,
1149
- multi: select.multi === true,
1150
- selectedValues: value.selected ?? [],
1151
- disabled,
1152
- onToggle: (optionValue) => toggleSelected(select, optionValue)
1153
- }
1154
- ),
1155
- select.allowCustom === true && /* @__PURE__ */ jsx5(
1156
- "input",
1157
- {
1158
- type: "text",
1159
- value: value.custom ?? "",
1160
- disabled,
1161
- onChange: (event) => setFieldValue(field.name, { custom: event.target.value }),
1162
- placeholder: "Other \u2014 type your own answer",
1163
- "aria-label": `Custom answer for ${field.label}`,
1164
- className: FIELD_INPUT_CLASSES
1165
- }
1166
- )
1167
- ] }) : field.type === "boolean" ? /* @__PURE__ */ jsx5("div", { className: "flex gap-4", children: ["true", "false"].map((boolValue) => /* @__PURE__ */ jsxs4("label", { className: "flex cursor-pointer items-center gap-2 text-sm text-foreground", children: [
1168
- /* @__PURE__ */ jsx5(
1169
- "input",
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",
1170
1545
  {
1171
- type: "radio",
1172
- name: `${interaction.id}-${field.name}`,
1173
- value: boolValue,
1174
- checked: (value.selected ?? [])[0] === boolValue,
1546
+ ref: textareaRef,
1547
+ value: text,
1548
+ onChange: (e) => setText(e.target.value),
1549
+ onKeyDown: handleKeyDown,
1550
+ placeholder,
1175
1551
  disabled,
1176
- onChange: () => setFieldValue(field.name, { selected: [boolValue] }),
1177
- className: "h-4 w-4 accent-primary"
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"
1178
1555
  }
1179
1556
  ),
1180
- boolValue === "true" ? "Yes" : "No"
1181
- ] }, boolValue)) }) : field.type === "number" ? /* @__PURE__ */ jsx5(
1182
- "input",
1183
- {
1184
- type: "number",
1185
- value: value.text ?? "",
1186
- disabled,
1187
- "aria-label": field.label,
1188
- onChange: (event) => setFieldValue(field.name, { text: event.target.value }),
1189
- className: FIELD_INPUT_CLASSES
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
- ] });
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",
1560
+ {
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
+ ]
1569
+ }
1570
+ ) : /* @__PURE__ */ jsxs6(
1571
+ "button",
1572
+ {
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/interaction-plan-card.tsx
1231
- import { useMemo as useMemo3, useRef as useRef5, useState as useState5 } from "react";
1232
- import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
1233
- function CheckGlyph2({ className }) {
1234
- 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" }) });
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 ChevronDownGlyph({ className }) {
1237
- 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" }) });
1644
+ async function responseBody(response) {
1645
+ return recordOf(await response.json().catch(() => null)) ?? {};
1238
1646
  }
1239
- var STATUS_LABELS2 = interactionStatusLabels({
1240
- pending: "Waiting for your approval",
1241
- answered: "Approved",
1242
- declined: "Rejected"
1243
- });
1244
- var TERMINAL_NOTES2 = interactionTerminalNotes("plan", {
1245
- declined: "The agent was asked to revise the plan."
1246
- });
1247
- var COLLAPSED_MAX_HEIGHT = 320;
1248
- function InteractionPlanCard({
1249
- interaction,
1250
- canWrite,
1251
- submitAnswer,
1252
- onResolved,
1253
- renderMarkdown,
1254
- className
1255
- }) {
1256
- const [values, setValues] = useState5({});
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;
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
+ }));
1270
1679
  }
1271
- return data;
1272
- }, [interaction.fields, values]);
1273
- async function submit(outcome) {
1274
- const data = outcome === "accepted" ? approveData : rejectData;
1275
- if (submitInFlightRef.current || disabled || data === null) return;
1276
- submitInFlightRef.current = true;
1277
- setSubmitting(outcome === "accepted" ? "approve" : "reject");
1680
+ };
1681
+ }
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));
1700
+ }
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 submitAnswer({ id: interaction.id, outcome, data });
1281
- if (result.ok) {
1282
- const resolved = outcome === "accepted" ? "answered" : "declined";
1283
- setLocalStatus(resolved);
1284
- onResolved?.(interaction.id, resolved);
1285
- return;
1286
- }
1287
- if (result.expired) {
1288
- setLocalStatus("expired");
1289
- onResolved?.(interaction.id, "expired");
1290
- return;
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(result.message);
1722
+ setError(cause instanceof Error ? cause.message : "Could not decide the plan.");
1723
+ return null;
1293
1724
  } finally {
1294
- submitInFlightRef.current = false;
1295
- setSubmitting(null);
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
- const terminalNote = TERMINAL_NOTES2[status];
1299
- const approved = status === "answered";
1300
- return /* @__PURE__ */ jsxs5("div", { className: `rounded-xl border border-border bg-card p-4 shadow-sm ${className ?? ""}`, children: [
1301
- /* @__PURE__ */ jsxs5("div", { className: "mb-3 flex flex-wrap items-center justify-between gap-2", children: [
1302
- /* @__PURE__ */ jsxs5("div", { className: "flex flex-wrap items-center gap-2", children: [
1303
- /* @__PURE__ */ jsx6(InteractionBadge, { variant: "outline", children: "Plan" }),
1304
- /* @__PURE__ */ jsx6(InteractionBadge, { variant: approved ? "default" : status === "expired" || status === "declined" ? "destructive" : "outline", children: STATUS_LABELS2[status] })
1305
- ] }),
1306
- /* @__PURE__ */ jsx6("span", { className: "text-xs text-muted-foreground", children: "The agent proposed a plan" })
1307
- ] }),
1308
- interaction.title.trim() && /* @__PURE__ */ jsx6("p", { className: "mb-3 text-sm font-medium leading-5 text-foreground", children: interaction.title }),
1309
- interaction.body && /* @__PURE__ */ jsxs5("div", { className: "relative", children: [
1310
- /* @__PURE__ */ jsx6(
1311
- "div",
1312
- {
1313
- className: "overflow-hidden text-sm text-foreground",
1314
- style: expanded ? void 0 : { maxHeight: COLLAPSED_MAX_HEIGHT },
1315
- children: renderMarkdown ? renderMarkdown(interaction.body) : /* @__PURE__ */ jsx6("p", { className: "whitespace-pre-wrap leading-5", children: interaction.body })
1316
- }
1317
- ),
1318
- !expanded && /* @__PURE__ */ jsx6("div", { className: "pointer-events-none absolute inset-x-0 bottom-0 h-12 bg-gradient-to-t from-card to-transparent" }),
1319
- /* @__PURE__ */ jsxs5(
1320
- "button",
1321
- {
1322
- type: "button",
1323
- onClick: () => setExpanded((prev) => !prev),
1324
- 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",
1325
- children: [
1326
- /* @__PURE__ */ jsx6(ChevronDownGlyph, { className: `h-3 w-3 transition-transform ${expanded ? "rotate-180" : ""}` }),
1327
- expanded ? "Collapse plan" : "Show full plan"
1328
- ]
1329
- }
1330
- )
1331
- ] }),
1332
- 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: [
1333
- /* @__PURE__ */ jsx6("p", { className: "text-sm font-medium leading-5 text-foreground", children: field.label }),
1334
- fieldAcceptsFreeText(field) ? /* @__PURE__ */ jsx6(
1335
- "textarea",
1336
- {
1337
- value: values[field.name]?.text ?? "",
1338
- disabled,
1339
- "aria-label": field.label,
1340
- onChange: (event) => setValues((prev) => ({ ...prev, [field.name]: { ...prev[field.name], text: event.target.value } })),
1341
- rows: 2,
1342
- placeholder: field.type === "text" ? field.placeholder ?? "Optional feedback for the agent" : void 0,
1343
- 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"
1344
- }
1345
- ) : /* @__PURE__ */ jsx6(
1346
- "input",
1347
- {
1348
- type: "text",
1349
- value: values[field.name]?.text ?? "",
1350
- disabled,
1351
- "aria-label": field.label,
1352
- onChange: (event) => setValues((prev) => ({ ...prev, [field.name]: { ...prev[field.name], text: event.target.value } })),
1353
- 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"
1354
- }
1355
- )
1356
- ] }, field.name)) }),
1357
- error && /* @__PURE__ */ jsx6("p", { className: "mt-3 text-xs text-destructive", children: error }),
1358
- terminalNote && /* @__PURE__ */ jsx6("p", { className: "mt-3 text-xs text-muted-foreground", children: terminalNote }),
1359
- status === "pending" && /* @__PURE__ */ jsxs5("div", { className: "mt-4 flex items-center justify-end gap-2", children: [
1360
- /* @__PURE__ */ jsx6(InteractionActionButton, { variant: "outline", onClick: () => void submit("declined"), disabled, children: submitting === "reject" ? "Sending\u2026" : "Request changes" }),
1361
- /* @__PURE__ */ jsx6(InteractionActionButton, { onClick: () => void submit("accepted"), disabled: disabled || approveData === null, children: submitting === "approve" ? "Approving\u2026" : "Approve plan" })
1362
- ] }),
1363
- 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: [
1364
- /* @__PURE__ */ jsx6(CheckGlyph2, { className: "h-3 w-3" }),
1365
- "Approved"
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 useCallback2, useMemo as useMemo4, useState as useState6 } from "react";
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 || isTerminalInteractionStatus(existing.status)) return list;
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,50 +1894,74 @@ 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
- const outstandingIds = new Set(outstanding.map((request) => request.id));
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
- next = upsertChatInteraction(next, interactionFromWireRequest(request));
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 useChatInteractions() {
1423
- const [interactions, setInteractions] = useState6([]);
1424
- const upsert = useCallback2((interaction) => {
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 = useCallback2((cancel) => {
1941
+ const applyCancel = useCallback3((cancel) => {
1428
1942
  setInteractions((prev) => cancelChatInteraction(prev, cancel));
1429
1943
  }, []);
1430
- const markResolved = useCallback2((id, status) => {
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 = useCallback2((outstanding) => {
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 = useCallback2((status) => {
1955
+ const terminalizePending = useCallback3((status) => {
1437
1956
  setInteractions((prev) => terminalizePendingChatInteractions(prev, status));
1438
1957
  }, []);
1439
- const reset = useCallback2(() => setInteractions([]), []);
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 };
1442
1961
  }
1443
1962
 
1444
1963
  // src/web-react/use-file-mentions.ts
1445
- import { useCallback as useCallback3, useMemo as useMemo5, useRef as useRef6, useState as useState7 } from "react";
1964
+ import { useCallback as useCallback4, useMemo as useMemo5, useRef as useRef7, useState as useState9 } from "react";
1446
1965
  var FILE_MENTION_KIND = "file";
1447
1966
  function toMentionItem(file) {
1448
1967
  return { id: file.path, label: file.name, detail: file.path, kind: FILE_MENTION_KIND };
@@ -1500,12 +2019,12 @@ function useFileMentions(options) {
1500
2019
  emptyText = DEFAULT_MENTION_EMPTY_TEXT
1501
2020
  } = options;
1502
2021
  const fetchImpl = options.fetchImpl ?? fetch;
1503
- const [state, setState] = useState7({ kind: "idle" });
1504
- const stateRef = useRef6(state);
2022
+ const [state, setState] = useState9({ kind: "idle" });
2023
+ const stateRef = useRef7(state);
1505
2024
  stateRef.current = state;
1506
- const inFlightRef = useRef6(null);
1507
- const [mentions, setMentions] = useState7([]);
1508
- const load = useCallback3(() => {
2025
+ const inFlightRef = useRef7(null);
2026
+ const [mentions, setMentions] = useState9([]);
2027
+ const load = useCallback4(() => {
1509
2028
  if (inFlightRef.current) return inFlightRef.current;
1510
2029
  if (stateRef.current.kind === "idle") {
1511
2030
  stateRef.current = { kind: "loading" };
@@ -1537,10 +2056,10 @@ function useFileMentions(options) {
1537
2056
  inFlightRef.current = attempt;
1538
2057
  return attempt;
1539
2058
  }, [fetchImpl, indexUrl]);
1540
- const refresh = useCallback3(async () => {
2059
+ const refresh = useCallback4(async () => {
1541
2060
  await load();
1542
2061
  }, [load]);
1543
- const fetchItems = useCallback3(
2062
+ const fetchItems = useCallback4(
1544
2063
  async (query) => {
1545
2064
  let current = stateRef.current;
1546
2065
  if (current.kind === "idle" || current.kind === "loading") {
@@ -1555,10 +2074,10 @@ function useFileMentions(options) {
1555
2074
  },
1556
2075
  [load, limit, refreshAfterMs]
1557
2076
  );
1558
- const onMentionsChange = useCallback3((items) => {
2077
+ const onMentionsChange = useCallback4((items) => {
1559
2078
  setMentions(items.filter((item) => item.kind === void 0 || item.kind === FILE_MENTION_KIND).map(toFileMention));
1560
2079
  }, []);
1561
- const clearMentions = useCallback3(() => setMentions([]), []);
2080
+ const clearMentions = useCallback4(() => setMentions([]), []);
1562
2081
  const mention = useMemo5(
1563
2082
  () => ({
1564
2083
  fetchItems,
@@ -1571,8 +2090,8 @@ function useFileMentions(options) {
1571
2090
  }
1572
2091
 
1573
2092
  // src/web-react/mission-activity.tsx
1574
- import { useCallback as useCallback4, useEffect as useEffect4, useState as useState8 } from "react";
1575
- import { Fragment as Fragment4, jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
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";
1576
2095
  var LIVE_STATUSES = /* @__PURE__ */ new Set(["pending", "running"]);
1577
2096
  var OK_STATUSES = /* @__PURE__ */ new Set(["completed", "done", "succeeded"]);
1578
2097
  var ERROR_STATUSES = /* @__PURE__ */ new Set(["failed", "error", "cancelled", "aborted"]);
@@ -1619,20 +2138,20 @@ function waterfallLayout(trace) {
1619
2138
  });
1620
2139
  }
1621
2140
  function ChevronGlyph({ className }) {
1622
- return /* @__PURE__ */ jsx7("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: /* @__PURE__ */ jsx7("path", { d: "m6 9 6 6 6-6" }) });
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" }) });
1623
2142
  }
1624
2143
  function RefreshGlyph({ className }) {
1625
- return /* @__PURE__ */ jsx7("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: /* @__PURE__ */ jsx7("path", { d: "M21 12a9 9 0 1 1-2.64-6.36M21 3v6h-6" }) });
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" }) });
1626
2145
  }
1627
2146
  function CopyGlyph({ className }) {
1628
- return /* @__PURE__ */ jsxs6("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: [
1629
- /* @__PURE__ */ jsx7("rect", { x: "9", y: "9", width: "13", height: "13", rx: "2" }),
1630
- /* @__PURE__ */ jsx7("path", { d: "M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" })
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" })
1631
2150
  ] });
1632
2151
  }
1633
2152
  function TraceIdCopy({ traceId }) {
1634
- const [copied, setCopied] = useState8(false);
1635
- const copy = useCallback4(() => {
2153
+ const [copied, setCopied] = useState10(false);
2154
+ const copy = useCallback5(() => {
1636
2155
  void navigator.clipboard?.writeText(traceId).then(
1637
2156
  () => {
1638
2157
  setCopied(true);
@@ -1642,7 +2161,7 @@ function TraceIdCopy({ traceId }) {
1642
2161
  }
1643
2162
  );
1644
2163
  }, [traceId]);
1645
- return /* @__PURE__ */ jsxs6(
2164
+ return /* @__PURE__ */ jsxs7(
1646
2165
  "button",
1647
2166
  {
1648
2167
  type: "button",
@@ -1651,23 +2170,23 @@ function TraceIdCopy({ traceId }) {
1651
2170
  "aria-label": "Copy trace id",
1652
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",
1653
2172
  children: [
1654
- /* @__PURE__ */ jsx7("span", { className: "truncate", children: traceId }),
1655
- /* @__PURE__ */ jsx7(CopyGlyph, { className: "h-3 w-3 shrink-0" }),
1656
- copied && /* @__PURE__ */ jsx7("span", { className: "shrink-0 not-italic text-success", children: "copied" })
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" })
1657
2176
  ]
1658
2177
  }
1659
2178
  );
1660
2179
  }
1661
2180
  function StatusDot({ tone }) {
1662
- return /* @__PURE__ */ jsxs6("span", { className: "inline-flex items-center", children: [
1663
- /* @__PURE__ */ jsx7(
2181
+ return /* @__PURE__ */ jsxs7("span", { className: "inline-flex items-center", children: [
2182
+ /* @__PURE__ */ jsx9(
1664
2183
  "span",
1665
2184
  {
1666
2185
  "aria-hidden": true,
1667
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"}`
1668
2187
  }
1669
2188
  ),
1670
- /* @__PURE__ */ jsx7("span", { className: "sr-only", children: tone })
2189
+ /* @__PURE__ */ jsx9("span", { className: "sr-only", children: tone })
1671
2190
  ] });
1672
2191
  }
1673
2192
  var BAR_CLASS = {
@@ -1679,19 +2198,19 @@ function FlowWaterfall({ trace }) {
1679
2198
  const rows = waterfallLayout(trace);
1680
2199
  if (rows.length === 0) return null;
1681
2200
  const cost = formatActivityCost(trace.costUsd);
1682
- return /* @__PURE__ */ jsxs6("div", { className: "space-y-1", children: [
1683
- rows.map((row, i) => /* @__PURE__ */ jsxs6("div", { className: "grid grid-cols-[minmax(0,2fr)_minmax(0,3fr)_auto] items-center gap-2", children: [
1684
- /* @__PURE__ */ jsx7("span", { className: "truncate font-mono text-[11px] text-muted-foreground", title: row.name, children: row.name }),
1685
- /* @__PURE__ */ jsx7("div", { className: "relative h-2 rounded-sm bg-muted/40", children: /* @__PURE__ */ jsx7(
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(
1686
2205
  "div",
1687
2206
  {
1688
2207
  className: `absolute inset-y-0 rounded-sm ${row.ok ? BAR_CLASS[row.kind] : "bg-destructive/80"} ${row.approx ? "opacity-70" : ""}`,
1689
2208
  style: { left: `${row.offsetPct}%`, width: `${row.widthPct}%` }
1690
2209
  }
1691
2210
  ) }),
1692
- /* @__PURE__ */ jsx7("span", { className: "shrink-0 font-mono text-[10px] tabular-nums text-muted-foreground/70", children: row.durationLabel })
2211
+ /* @__PURE__ */ jsx9("span", { className: "shrink-0 font-mono text-[10px] tabular-nums text-muted-foreground/70", children: row.durationLabel })
1693
2212
  ] }, i)),
1694
- /* @__PURE__ */ jsxs6("p", { className: "pt-0.5 text-right font-mono text-[10px] tabular-nums text-muted-foreground/60", children: [
2213
+ /* @__PURE__ */ jsxs7("p", { className: "pt-0.5 text-right font-mono text-[10px] tabular-nums text-muted-foreground/60", children: [
1695
2214
  (trace.totalMs / 1e3).toFixed(1),
1696
2215
  "s",
1697
2216
  cost ? ` \xB7 ${cost}` : ""
@@ -1699,43 +2218,43 @@ function FlowWaterfall({ trace }) {
1699
2218
  ] });
1700
2219
  }
1701
2220
  function MissionActivityLane({ activity, startedAt, nowMs }) {
1702
- const [expanded, setExpanded] = useState8(false);
2221
+ const [expanded, setExpanded] = useState10(false);
1703
2222
  if (activity.length === 0) return null;
1704
- return /* @__PURE__ */ jsxs6("div", { className: "mt-1 border-l border-border/50 pl-3", children: [
2223
+ return /* @__PURE__ */ jsxs7("div", { className: "mt-1 border-l border-border/50 pl-3", children: [
1705
2224
  activity.map((run) => {
1706
2225
  const tone = activityTone(run.status);
1707
2226
  const cost = formatActivityCost(run.costUsd);
1708
2227
  const duration = formatActivityDuration(run.durationMs);
1709
- return /* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-2 py-1 text-xs", children: [
1710
- /* @__PURE__ */ jsx7(StatusDot, { tone }),
1711
- /* @__PURE__ */ jsxs6("span", { className: "min-w-0 flex-1 truncate", children: [
1712
- /* @__PURE__ */ jsx7("span", { className: "font-medium", children: run.tool }),
1713
- /* @__PURE__ */ jsxs6("span", { className: "text-muted-foreground", children: [
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: [
1714
2233
  " \u2014 ",
1715
2234
  run.detail
1716
2235
  ] })
1717
2236
  ] }),
1718
- tone === "live" && (run.iteration !== void 0 || run.phase !== void 0) && /* @__PURE__ */ jsx7("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 ") }),
1719
- /* @__PURE__ */ jsxs6("span", { className: "flex shrink-0 items-center gap-1.5 font-mono text-[10px] tabular-nums text-muted-foreground/70", children: [
1720
- tone !== "live" && tone !== "ok" && /* @__PURE__ */ jsx7("span", { children: run.status }),
1721
- cost && /* @__PURE__ */ jsx7("span", { children: cost }),
1722
- duration && /* @__PURE__ */ jsx7("span", { children: duration })
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 })
1723
2242
  ] })
1724
2243
  ] }, run.taskId);
1725
2244
  }),
1726
- /* @__PURE__ */ jsxs6(
2245
+ /* @__PURE__ */ jsxs7(
1727
2246
  "button",
1728
2247
  {
1729
2248
  type: "button",
1730
2249
  onClick: () => setExpanded((v) => !v),
1731
2250
  className: "flex items-center gap-1 py-0.5 text-[10px] font-medium text-muted-foreground/70 transition hover:text-foreground",
1732
2251
  children: [
1733
- /* @__PURE__ */ jsx7(ChevronGlyph, { className: `h-3 w-3 transition-transform ${expanded ? "rotate-180" : ""}` }),
2252
+ /* @__PURE__ */ jsx9(ChevronGlyph, { className: `h-3 w-3 transition-transform ${expanded ? "rotate-180" : ""}` }),
1734
2253
  "timeline"
1735
2254
  ]
1736
2255
  }
1737
2256
  ),
1738
- expanded && /* @__PURE__ */ jsx7("div", { className: "rounded-md border border-border/50 bg-muted/10 p-2", children: /* @__PURE__ */ jsx7(
2257
+ expanded && /* @__PURE__ */ jsx9("div", { className: "rounded-md border border-border/50 bg-muted/10 p-2", children: /* @__PURE__ */ jsx9(
1739
2258
  FlowWaterfall,
1740
2259
  {
1741
2260
  trace: stepActivityFlowTrace(activity, {
@@ -1750,45 +2269,45 @@ function ActivityRow({
1750
2269
  record,
1751
2270
  renderMissionRef
1752
2271
  }) {
1753
- const [open, setOpen] = useState8(false);
2272
+ const [open, setOpen] = useState10(false);
1754
2273
  const tone = activityTone(record.status);
1755
2274
  const cost = formatActivityCost(record.costUsd);
1756
2275
  const duration = formatActivityDuration(record.durationMs);
1757
- return /* @__PURE__ */ jsxs6("div", { className: "rounded-lg border border-border/60 bg-card", children: [
1758
- /* @__PURE__ */ jsxs6("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: [
1759
- /* @__PURE__ */ jsx7(StatusDot, { tone }),
1760
- /* @__PURE__ */ jsxs6("span", { className: "min-w-0 flex-1 truncate", children: [
1761
- /* @__PURE__ */ jsx7("span", { className: "font-medium", children: record.tool }),
1762
- /* @__PURE__ */ jsxs6("span", { className: "text-muted-foreground", children: [
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: [
1763
2282
  " \u2014 ",
1764
2283
  record.detail
1765
2284
  ] })
1766
2285
  ] }),
1767
- tone === "live" && (record.iteration !== void 0 || record.phase !== void 0) && /* @__PURE__ */ jsx7("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 ") }),
1768
- /* @__PURE__ */ jsx7(
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(
1769
2288
  "span",
1770
2289
  {
1771
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"}`,
1772
2291
  children: record.status
1773
2292
  }
1774
2293
  ),
1775
- cost && /* @__PURE__ */ jsx7("span", { className: "shrink-0 font-mono text-[11px] tabular-nums text-muted-foreground", children: cost }),
1776
- /* @__PURE__ */ jsx7(ChevronGlyph, { className: `h-3 w-3 shrink-0 text-muted-foreground transition-transform ${open ? "rotate-180" : ""}` })
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" : ""}` })
1777
2296
  ] }),
1778
- open && /* @__PURE__ */ jsxs6("div", { className: "space-y-2.5 border-t border-border/40 px-3 py-2.5", children: [
1779
- record.durationMs !== void 0 && /* @__PURE__ */ jsx7("div", { className: "rounded-md border border-border/50 bg-muted/10 p-2", children: /* @__PURE__ */ jsx7(FlowWaterfall, { trace: stepActivityFlowTrace([record]) }) }),
1780
- /* @__PURE__ */ jsxs6("dl", { className: "grid grid-cols-[auto_1fr] gap-x-3 gap-y-1 font-mono text-[11px]", children: [
1781
- /* @__PURE__ */ jsx7("dt", { className: "text-muted-foreground/60", children: "task" }),
1782
- /* @__PURE__ */ jsx7("dd", { className: "truncate text-muted-foreground", children: record.taskId }),
1783
- /* @__PURE__ */ jsx7("dt", { className: "text-muted-foreground/60", children: "started" }),
1784
- /* @__PURE__ */ jsx7("dd", { className: "text-muted-foreground", children: new Date(record.startedAt).toLocaleString() }),
1785
- duration && /* @__PURE__ */ jsxs6(Fragment4, { children: [
1786
- /* @__PURE__ */ jsx7("dt", { className: "text-muted-foreground/60", children: "duration" }),
1787
- /* @__PURE__ */ jsx7("dd", { className: "text-muted-foreground", children: duration })
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 })
1788
2307
  ] }),
1789
- record.traceId && /* @__PURE__ */ jsxs6(Fragment4, { children: [
1790
- /* @__PURE__ */ jsx7("dt", { className: "text-muted-foreground/60", children: "trace" }),
1791
- /* @__PURE__ */ jsx7("dd", { className: "min-w-0", children: /* @__PURE__ */ jsx7(TraceIdCopy, { traceId: record.traceId }) })
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 }) })
1792
2311
  ] })
1793
2312
  ] }),
1794
2313
  record.missionRef && renderMissionRef?.(record.missionRef, record)
@@ -1796,11 +2315,11 @@ function ActivityRow({
1796
2315
  ] });
1797
2316
  }
1798
2317
  function AgentActivityPanel({ fetchActivity, renderMissionRef, title = "Agent activity", emptyLabel = "No agent runs yet." }) {
1799
- const [rows, setRows] = useState8([]);
1800
- const [cursor, setCursor] = useState8(void 0);
1801
- const [loading, setLoading] = useState8(false);
1802
- const [error, setError] = useState8(null);
1803
- const load = useCallback4(
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(
1804
2323
  async (from) => {
1805
2324
  setLoading(true);
1806
2325
  setError(null);
@@ -1816,13 +2335,13 @@ function AgentActivityPanel({ fetchActivity, renderMissionRef, title = "Agent ac
1816
2335
  },
1817
2336
  [fetchActivity]
1818
2337
  );
1819
- useEffect4(() => {
2338
+ useEffect7(() => {
1820
2339
  void load();
1821
2340
  }, [load]);
1822
- return /* @__PURE__ */ jsxs6("div", { className: "space-y-2", children: [
1823
- /* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-2", children: [
1824
- /* @__PURE__ */ jsx7("h2", { className: "flex-1 text-sm font-semibold", children: title }),
1825
- /* @__PURE__ */ jsx7(
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(
1826
2345
  "button",
1827
2346
  {
1828
2347
  type: "button",
@@ -1830,14 +2349,14 @@ function AgentActivityPanel({ fetchActivity, renderMissionRef, title = "Agent ac
1830
2349
  disabled: loading,
1831
2350
  "aria-label": "Refresh",
1832
2351
  className: "rounded-md p-1.5 text-muted-foreground transition hover:bg-accent/30 hover:text-foreground disabled:opacity-50",
1833
- children: /* @__PURE__ */ jsx7(RefreshGlyph, { className: `h-3.5 w-3.5 ${loading ? "animate-spin" : ""}` })
2352
+ children: /* @__PURE__ */ jsx9(RefreshGlyph, { className: `h-3.5 w-3.5 ${loading ? "animate-spin" : ""}` })
1834
2353
  }
1835
2354
  )
1836
2355
  ] }),
1837
- error && /* @__PURE__ */ jsx7("p", { role: "alert", className: "rounded-md border border-destructive/40 bg-destructive/5 px-3 py-2 text-xs text-destructive", children: error }),
1838
- !error && rows.length === 0 && !loading && /* @__PURE__ */ jsx7("p", { className: "px-1 text-sm text-muted-foreground", children: emptyLabel }),
1839
- /* @__PURE__ */ jsx7("div", { className: "space-y-1.5", children: rows.map((record) => /* @__PURE__ */ jsx7(ActivityRow, { record, renderMissionRef }, record.taskId)) }),
1840
- cursor && /* @__PURE__ */ jsx7(
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(
1841
2360
  "button",
1842
2361
  {
1843
2362
  type: "button",
@@ -1851,9 +2370,9 @@ function AgentActivityPanel({ fetchActivity, renderMissionRef, title = "Agent ac
1851
2370
  }
1852
2371
 
1853
2372
  // src/web-react/seat-paywall.tsx
1854
- import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
2373
+ import { jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
1855
2374
  function CheckGlyph3() {
1856
- return /* @__PURE__ */ jsx8(
2375
+ return /* @__PURE__ */ jsx10(
1857
2376
  "svg",
1858
2377
  {
1859
2378
  className: "h-4 w-4 shrink-0 text-primary",
@@ -1864,14 +2383,14 @@ function CheckGlyph3() {
1864
2383
  strokeLinecap: "round",
1865
2384
  strokeLinejoin: "round",
1866
2385
  "aria-hidden": true,
1867
- children: /* @__PURE__ */ jsx8("path", { d: "M20 6 9 17l-5-5" })
2386
+ children: /* @__PURE__ */ jsx10("path", { d: "M20 6 9 17l-5-5" })
1868
2387
  }
1869
2388
  );
1870
2389
  }
1871
2390
  function Benefit({ children }) {
1872
- return /* @__PURE__ */ jsxs7("li", { className: "flex items-start gap-2.5 text-sm text-foreground", children: [
1873
- /* @__PURE__ */ jsx8("span", { className: "mt-0.5", children: /* @__PURE__ */ jsx8(CheckGlyph3, {}) }),
1874
- /* @__PURE__ */ jsx8("span", { children })
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 })
1875
2394
  ] });
1876
2395
  }
1877
2396
  function SeatPaywall({
@@ -1885,30 +2404,30 @@ function SeatPaywall({
1885
2404
  footnote
1886
2405
  }) {
1887
2406
  const { pending, run } = usePending();
1888
- return /* @__PURE__ */ jsx8("div", { className: "flex min-h-[60vh] w-full items-center justify-center p-6", children: /* @__PURE__ */ jsxs7("div", { className: "w-full max-w-md rounded-2xl border border-border bg-card p-8 shadow-sm", children: [
1889
- /* @__PURE__ */ jsx8("p", { className: "text-xs font-medium uppercase tracking-wide text-muted-foreground", children: product }),
1890
- /* @__PURE__ */ jsxs7("h1", { className: "mt-2 text-2xl font-semibold tracking-tight text-foreground", children: [
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: [
1891
2410
  "Unlock ",
1892
2411
  product
1893
2412
  ] }),
1894
- tagline && /* @__PURE__ */ jsx8("p", { className: "mt-2 text-sm text-muted-foreground", children: tagline }),
1895
- /* @__PURE__ */ jsxs7("div", { className: "mt-6 flex items-baseline gap-1.5", children: [
1896
- /* @__PURE__ */ jsxs7("span", { className: "text-3xl font-semibold text-foreground", children: [
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: [
1897
2416
  "$",
1898
2417
  priceUsd
1899
2418
  ] }),
1900
- /* @__PURE__ */ jsx8("span", { className: "text-sm text-muted-foreground", children: "/mo" })
2419
+ /* @__PURE__ */ jsx10("span", { className: "text-sm text-muted-foreground", children: "/mo" })
1901
2420
  ] }),
1902
- /* @__PURE__ */ jsxs7("p", { className: "mt-1 text-sm text-muted-foreground", children: [
2421
+ /* @__PURE__ */ jsxs8("p", { className: "mt-1 text-sm text-muted-foreground", children: [
1903
2422
  "Includes $",
1904
2423
  includedUsageUsd,
1905
2424
  "/mo of AI usage"
1906
2425
  ] }),
1907
- /* @__PURE__ */ jsx8("ul", { className: "mt-6 space-y-2.5", children: (benefits ?? [
2426
+ /* @__PURE__ */ jsx10("ul", { className: "mt-6 space-y-2.5", children: (benefits ?? [
1908
2427
  `Full access to ${product}`,
1909
2428
  `$${includedUsageUsd}/mo of AI usage included, every month`
1910
- ]).map((benefit, i) => /* @__PURE__ */ jsx8(Benefit, { children: benefit }, i)) }),
1911
- /* @__PURE__ */ jsx8(
2429
+ ]).map((benefit, i) => /* @__PURE__ */ jsx10(Benefit, { children: benefit }, i)) }),
2430
+ /* @__PURE__ */ jsx10(
1912
2431
  "button",
1913
2432
  {
1914
2433
  type: "button",
@@ -1918,13 +2437,13 @@ function SeatPaywall({
1918
2437
  children: pending ? "Opening checkout\u2026" : ctaLabel ?? `Unlock ${product}`
1919
2438
  }
1920
2439
  ),
1921
- footnote && /* @__PURE__ */ jsx8("p", { className: "mt-3 text-center text-xs text-muted-foreground/70", children: footnote })
2440
+ footnote && /* @__PURE__ */ jsx10("p", { className: "mt-3 text-center text-xs text-muted-foreground/70", children: footnote })
1922
2441
  ] }) });
1923
2442
  }
1924
2443
 
1925
2444
  // src/web-react/agent-session-controls.tsx
1926
- import { useMemo as useMemo6, useState as useState9 } from "react";
1927
- import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
2445
+ import { useMemo as useMemo6, useState as useState11 } from "react";
2446
+ import { jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
1928
2447
  var HARNESS_LABELS = {
1929
2448
  opencode: "OpenCode (any model)",
1930
2449
  "claude-code": "Claude Code (Anthropic)",
@@ -1944,12 +2463,12 @@ function harnessLabel(h) {
1944
2463
  return HARNESS_LABELS[h] ?? h;
1945
2464
  }
1946
2465
  function ChevronDown2({ className }) {
1947
- 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" }) });
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" }) });
1948
2467
  }
1949
2468
  function GearGlyph({ className }) {
1950
- return /* @__PURE__ */ jsxs8("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: [
1951
- /* @__PURE__ */ jsx9("circle", { cx: "12", cy: "12", r: "3" }),
1952
- /* @__PURE__ */ jsx9("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" })
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" })
1953
2472
  ] });
1954
2473
  }
1955
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";
@@ -1958,11 +2477,11 @@ function HarnessPicker({
1958
2477
  onChange,
1959
2478
  available
1960
2479
  }) {
1961
- const [open, setOpen] = useState9(false);
2480
+ const [open, setOpen] = useState11(false);
1962
2481
  const { containerRef, triggerProps } = usePopover(open, setOpen);
1963
2482
  const options = available ?? Object.keys(HARNESS_LABELS);
1964
- return /* @__PURE__ */ jsxs8("div", { ref: containerRef, className: "relative inline-flex", children: [
1965
- /* @__PURE__ */ jsxs8(
2483
+ return /* @__PURE__ */ jsxs9("div", { ref: containerRef, className: "relative inline-flex", children: [
2484
+ /* @__PURE__ */ jsxs9(
1966
2485
  "button",
1967
2486
  {
1968
2487
  type: "button",
@@ -1971,12 +2490,12 @@ function HarnessPicker({
1971
2490
  title: "Agent backend",
1972
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}`,
1973
2492
  children: [
1974
- /* @__PURE__ */ jsx9("span", { className: "truncate", children: harnessLabel(value) }),
1975
- /* @__PURE__ */ jsx9(ChevronDown2, { className: "h-3.5 w-3.5 text-muted-foreground" })
2493
+ /* @__PURE__ */ jsx11("span", { className: "truncate", children: harnessLabel(value) }),
2494
+ /* @__PURE__ */ jsx11(ChevronDown2, { className: "h-3.5 w-3.5 text-muted-foreground" })
1976
2495
  ]
1977
2496
  }
1978
2497
  ),
1979
- open && /* @__PURE__ */ jsx9("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__ */ jsx9(
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(
1980
2499
  "button",
1981
2500
  {
1982
2501
  type: "button",
@@ -2023,11 +2542,11 @@ function AgentSessionControls(props) {
2023
2542
  className
2024
2543
  } = props;
2025
2544
  const { onModel, onHarness } = useCoherentHandlers(props);
2026
- const [open, setOpen] = useState9(false);
2545
+ const [open, setOpen] = useState11(false);
2027
2546
  const { containerRef: popoverRef, triggerProps } = usePopover(open, setOpen);
2028
2547
  const selectedModel = models.find((m) => m.id === model);
2029
2548
  const showEffort = selectedModel?.supportsReasoning ?? true;
2030
- const modelPicker = /* @__PURE__ */ jsx9(
2549
+ const modelPicker = /* @__PURE__ */ jsx11(
2031
2550
  ModelPicker,
2032
2551
  {
2033
2552
  value: model,
@@ -2038,17 +2557,17 @@ function AgentSessionControls(props) {
2038
2557
  }
2039
2558
  );
2040
2559
  if (layout === "inline") {
2041
- return /* @__PURE__ */ jsxs8("div", { className: `flex items-center gap-1.5 ${className ?? ""}`, children: [
2560
+ return /* @__PURE__ */ jsxs9("div", { className: `flex items-center gap-1.5 ${className ?? ""}`, children: [
2042
2561
  modelPicker,
2043
- showHarness && /* @__PURE__ */ jsx9(HarnessPicker, { value: harness, onChange: onHarness, available: availableHarnesses }),
2044
- showEffort && /* @__PURE__ */ jsx9(EffortPicker, { value: effort, onChange: onEffortChange })
2562
+ showHarness && /* @__PURE__ */ jsx11(HarnessPicker, { value: harness, onChange: onHarness, available: availableHarnesses }),
2563
+ showEffort && /* @__PURE__ */ jsx11(EffortPicker, { value: effort, onChange: onEffortChange })
2045
2564
  ] });
2046
2565
  }
2047
2566
  const hasAdvanced = showHarness || showEffort;
2048
- return /* @__PURE__ */ jsxs8("div", { className: `flex items-center gap-1.5 ${className ?? ""}`, children: [
2567
+ return /* @__PURE__ */ jsxs9("div", { className: `flex items-center gap-1.5 ${className ?? ""}`, children: [
2049
2568
  modelPicker,
2050
- hasAdvanced && /* @__PURE__ */ jsxs8("div", { ref: popoverRef, className: "relative inline-flex", children: [
2051
- /* @__PURE__ */ jsx9(
2569
+ hasAdvanced && /* @__PURE__ */ jsxs9("div", { ref: popoverRef, className: "relative inline-flex", children: [
2570
+ /* @__PURE__ */ jsx11(
2052
2571
  "button",
2053
2572
  {
2054
2573
  type: "button",
@@ -2057,19 +2576,19 @@ function AgentSessionControls(props) {
2057
2576
  title: "Model settings \u2014 pick the agent backend and how hard it thinks",
2058
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}`,
2059
2578
  "data-state": open ? "open" : "closed",
2060
- children: /* @__PURE__ */ jsx9(GearGlyph, { className: "h-4 w-4" })
2579
+ children: /* @__PURE__ */ jsx11(GearGlyph, { className: "h-4 w-4" })
2061
2580
  }
2062
2581
  ),
2063
- open && /* @__PURE__ */ jsxs8("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: [
2064
- showHarness && /* @__PURE__ */ jsxs8("div", { className: "space-y-1.5", children: [
2065
- /* @__PURE__ */ jsx9("p", { className: "text-xs font-medium text-foreground", children: "Agent backend" }),
2066
- /* @__PURE__ */ jsx9(HarnessPicker, { value: harness, onChange: onHarness, available: availableHarnesses }),
2067
- /* @__PURE__ */ jsx9("p", { className: "text-[11px] leading-snug text-muted-foreground", children: "The engine that runs the agent. Switching it keeps your model choice compatible." })
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." })
2068
2587
  ] }),
2069
- showEffort && /* @__PURE__ */ jsxs8("div", { className: "space-y-1.5", children: [
2070
- /* @__PURE__ */ jsx9("p", { className: "text-xs font-medium text-foreground", children: "Thinking" }),
2071
- /* @__PURE__ */ jsx9(EffortPicker, { value: effort, onChange: onEffortChange, label: "" }),
2072
- /* @__PURE__ */ jsx9("p", { className: "text-[11px] leading-snug text-muted-foreground", children: "How hard the agent thinks before answering. Higher is slower but more thorough." })
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." })
2073
2592
  ] })
2074
2593
  ] })
2075
2594
  ] })
@@ -2077,7 +2596,7 @@ function AgentSessionControls(props) {
2077
2596
  }
2078
2597
 
2079
2598
  // src/web-react/index.tsx
2080
- import { Fragment as Fragment5, jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
2599
+ import { Fragment as Fragment5, jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
2081
2600
  function formatModelCost(msg, models) {
2082
2601
  if (msg.promptTokens == null && msg.completionTokens == null) return null;
2083
2602
  const pricing = models.find((m) => m.id === msg.modelUsed)?.pricing;
@@ -2091,41 +2610,41 @@ function formatTokensPerSecond(msg) {
2091
2610
  return `${Math.round(msg.completionTokens / (msg.durationMs / 1e3))} tok/s`;
2092
2611
  }
2093
2612
  function RunDrillIn({ run, onClose }) {
2094
- return /* @__PURE__ */ jsxs9("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: [
2095
- /* @__PURE__ */ jsxs9("div", { className: "flex items-center gap-2 border-b border-border px-4 py-3", children: [
2096
- /* @__PURE__ */ jsx10(
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(
2097
2616
  "span",
2098
2617
  {
2099
2618
  className: `h-2 w-2 shrink-0 rounded-full ${run.status === "running" ? "bg-warning" : run.status === "error" ? "bg-destructive" : "bg-success"}`
2100
2619
  }
2101
2620
  ),
2102
- /* @__PURE__ */ jsxs9("div", { className: "min-w-0 flex-1", children: [
2103
- /* @__PURE__ */ jsx10("p", { className: "truncate text-sm font-semibold", children: run.title }),
2104
- /* @__PURE__ */ jsx10("p", { className: "truncate font-mono text-[11px] text-muted-foreground", children: run.toolName })
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 })
2105
2624
  ] }),
2106
- /* @__PURE__ */ jsx10(
2625
+ /* @__PURE__ */ jsx12(
2107
2626
  "button",
2108
2627
  {
2109
2628
  type: "button",
2110
2629
  onClick: onClose,
2111
2630
  "aria-label": "Close",
2112
2631
  className: "rounded-md p-1.5 text-muted-foreground transition hover:bg-accent/30 hover:text-foreground",
2113
- children: /* @__PURE__ */ jsx10("svg", { className: "h-4 w-4", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", "aria-hidden": true, children: /* @__PURE__ */ jsx10("path", { d: "M18 6 6 18M6 6l12 12" }) })
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" }) })
2114
2633
  }
2115
2634
  )
2116
2635
  ] }),
2117
- /* @__PURE__ */ jsxs9("div", { className: "flex-1 space-y-3 overflow-y-auto p-4", children: [
2118
- run.steps.length === 0 && /* @__PURE__ */ jsx10("p", { className: "text-sm text-muted-foreground", children: "No steps recorded yet." }),
2119
- run.steps.map((step, i) => /* @__PURE__ */ jsxs9("div", { className: "rounded-lg border border-border/60 bg-background", children: [
2120
- /* @__PURE__ */ jsxs9("div", { className: "flex items-baseline gap-2 border-b border-border/40 px-3 py-1.5", children: [
2121
- /* @__PURE__ */ jsx10("span", { className: `font-mono text-[11px] ${step.status === "error" ? "text-destructive" : "text-muted-foreground"}`, children: step.status === "error" ? "\u2717" : "$" }),
2122
- /* @__PURE__ */ jsx10("code", { className: "min-w-0 flex-1 truncate font-mono text-xs", children: step.label }),
2123
- /* @__PURE__ */ jsx10("span", { className: "shrink-0 text-[10px] text-muted-foreground", children: new Date(step.at).toLocaleTimeString() })
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() })
2124
2643
  ] }),
2125
- step.detail && /* @__PURE__ */ jsx10("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 })
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 })
2126
2645
  ] }, i))
2127
2646
  ] }),
2128
- /* @__PURE__ */ jsx10("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." })
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." })
2129
2648
  ] });
2130
2649
  }
2131
2650
  function pendingApprovalOf(call) {
@@ -2139,20 +2658,20 @@ function ChatEmptyState({
2139
2658
  subline = "Describe the outcome you want. The agent works through it step by step, and pauses for your approval before anything irreversible.",
2140
2659
  doors
2141
2660
  }) {
2142
- return /* @__PURE__ */ jsxs9("div", { className: "mx-auto flex w-full max-w-2xl flex-col items-center px-6 py-12 text-center sm:py-20", children: [
2143
- /* @__PURE__ */ jsx10("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__ */ jsx10(BrandMark, { size: 32, className: "shrink-0" }) }),
2144
- /* @__PURE__ */ jsx10("p", { className: "text-[11px] font-semibold uppercase tracking-[0.18em] text-muted-foreground", children: productName }),
2145
- /* @__PURE__ */ jsx10("h2", { className: "mt-1.5 text-balance text-2xl font-semibold leading-tight text-foreground sm:text-[28px]", children: headline }),
2146
- subline && /* @__PURE__ */ jsx10("p", { className: "mt-3 max-w-md text-[15px] leading-relaxed text-muted-foreground", children: subline }),
2147
- doors && doors.length > 0 && /* @__PURE__ */ jsx10("div", { className: "mt-7 grid w-full gap-2.5 sm:grid-cols-3", children: doors.slice(0, 3).map((door, i) => /* @__PURE__ */ jsxs9(
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(
2148
2667
  "button",
2149
2668
  {
2150
2669
  type: "button",
2151
2670
  onClick: door.onSelect,
2152
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",
2153
2672
  children: [
2154
- /* @__PURE__ */ jsx10("span", { className: "text-sm font-semibold text-foreground", children: door.label }),
2155
- door.description && /* @__PURE__ */ jsx10("span", { className: "mt-0.5 text-[12px] leading-snug text-muted-foreground", children: door.description })
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 })
2156
2675
  ]
2157
2676
  },
2158
2677
  i
@@ -2161,26 +2680,26 @@ function ChatEmptyState({
2161
2680
  }
2162
2681
  function ToolGlyph({ name, className }) {
2163
2682
  if (name.startsWith("sandbox_")) {
2164
- return /* @__PURE__ */ jsxs9("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: [
2165
- /* @__PURE__ */ jsx10("polyline", { points: "4 17 10 11 4 5" }),
2166
- /* @__PURE__ */ jsx10("line", { x1: "12", y1: "19", x2: "20", y2: "19" })
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" })
2167
2686
  ] });
2168
2687
  }
2169
2688
  if (name === "submit_proposal") {
2170
- return /* @__PURE__ */ jsxs9("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: [
2171
- /* @__PURE__ */ jsx10("path", { d: "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" }),
2172
- /* @__PURE__ */ jsx10("path", { d: "M14 2v6h6M9 15l2 2 4-4" })
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" })
2173
2692
  ] });
2174
2693
  }
2175
2694
  if (name === "schedule_followup") {
2176
- return /* @__PURE__ */ jsxs9("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", "aria-hidden": true, children: [
2177
- /* @__PURE__ */ jsx10("circle", { cx: "12", cy: "12", r: "9" }),
2178
- /* @__PURE__ */ jsx10("path", { d: "M12 7v5l3 3" })
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" })
2179
2698
  ] });
2180
2699
  }
2181
- return /* @__PURE__ */ jsxs9("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: [
2182
- /* @__PURE__ */ jsx10("path", { d: "M12 3v3m0 12v3M3 12h3m12 0h3" }),
2183
- /* @__PURE__ */ jsx10("circle", { cx: "12", cy: "12", r: "4" })
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" })
2184
2703
  ] });
2185
2704
  }
2186
2705
  function toolOutcomeOf(call) {
@@ -2247,36 +2766,36 @@ function truncate(v, max = 240) {
2247
2766
  function KvRows({ data }) {
2248
2767
  const entries = Object.entries(data).filter(([, v]) => v !== void 0 && v !== null && v !== "");
2249
2768
  if (!entries.length) return null;
2250
- return /* @__PURE__ */ jsx10("dl", { className: "grid grid-cols-[auto_1fr] gap-x-3 gap-y-1", children: entries.map(([k, v]) => /* @__PURE__ */ jsxs9("div", { className: "contents", children: [
2251
- /* @__PURE__ */ jsx10("dt", { className: "font-mono text-[11px] text-muted-foreground", children: k }),
2252
- /* @__PURE__ */ jsx10("dd", { className: "min-w-0 whitespace-pre-wrap break-words font-mono text-[11px] text-muted-foreground", children: truncate(v) })
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) })
2253
2772
  ] }, k)) });
2254
2773
  }
2255
2774
  function ShellDetail({ call }) {
2256
2775
  const outcome = toolOutcomeOf(call);
2257
2776
  const r = outcome?.result ?? {};
2258
- return /* @__PURE__ */ jsxs9("div", { className: "overflow-hidden rounded-md bg-zinc-900 font-mono text-[11px] leading-relaxed", children: [
2259
- /* @__PURE__ */ jsxs9("div", { className: "flex items-center gap-2 px-3 pt-2 text-zinc-400", children: [
2260
- /* @__PURE__ */ jsx10("span", { className: "select-none text-zinc-500", children: "$" }),
2261
- /* @__PURE__ */ jsx10("span", { className: "min-w-0 flex-1 truncate text-zinc-200", children: String(call.args?.command ?? "") }),
2262
- r.exitCode != null && /* @__PURE__ */ jsxs9("span", { className: r.exitCode === 0 ? "text-success" : "text-destructive", children: [
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: [
2263
2782
  "exit ",
2264
2783
  r.exitCode
2265
2784
  ] })
2266
2785
  ] }),
2267
- /* @__PURE__ */ jsx10("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)" })
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)" })
2268
2787
  ] });
2269
2788
  }
2270
2789
  function DefaultToolDetail({ call }) {
2271
2790
  const outcome = toolOutcomeOf(call);
2272
- return /* @__PURE__ */ jsxs9("div", { className: "space-y-2", children: [
2273
- call.args && Object.keys(call.args).length > 0 && /* @__PURE__ */ jsxs9("div", { children: [
2274
- /* @__PURE__ */ jsx10("p", { className: "mb-1 text-[10px] font-semibold uppercase tracking-wide text-muted-foreground", children: "Called with" }),
2275
- /* @__PURE__ */ jsx10(KvRows, { data: call.args })
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 })
2276
2795
  ] }),
2277
- outcome && /* @__PURE__ */ jsxs9("div", { children: [
2278
- /* @__PURE__ */ jsx10("p", { className: "mb-1 text-[10px] font-semibold uppercase tracking-wide text-muted-foreground", children: outcome.ok === false ? "Failed" : "Result" }),
2279
- outcome.ok === false ? /* @__PURE__ */ jsx10("p", { className: "text-xs text-destructive", children: outcome.message ?? "Tool failed" }) : outcome.result && typeof outcome.result === "object" ? /* @__PURE__ */ jsx10(KvRows, { data: outcome.result }) : /* @__PURE__ */ jsx10("p", { className: "font-mono text-[11px] text-muted-foreground", children: truncate(outcome.result) })
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) })
2280
2799
  ] })
2281
2800
  ] });
2282
2801
  }
@@ -2287,23 +2806,23 @@ function ProposalCard({
2287
2806
  approval,
2288
2807
  renderers
2289
2808
  }) {
2290
- const [expanded, setExpanded] = useState10(false);
2809
+ const [expanded, setExpanded] = useState12(false);
2291
2810
  const { summary, meta } = proposalPreview(call);
2292
2811
  const custom = renderers?.[call.name]?.(call, message);
2293
2812
  const { pending: deciding, run: decide } = usePending();
2294
- return /* @__PURE__ */ jsxs9("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: [
2295
- /* @__PURE__ */ jsxs9("div", { className: "flex items-start gap-2.5 px-4 pt-3.5", children: [
2296
- /* @__PURE__ */ jsx10("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__ */ jsx10(ToolGlyph, { name: call.name, className: "h-3.5 w-3.5" }) }),
2297
- /* @__PURE__ */ jsxs9("div", { className: "min-w-0 flex-1", children: [
2298
- /* @__PURE__ */ jsx10("p", { className: "text-[10px] font-semibold uppercase tracking-wider text-warning-foreground", children: "Needs your approval" }),
2299
- /* @__PURE__ */ jsx10("p", { className: "mt-0.5 text-[15px] font-semibold leading-snug text-foreground", children: friendlyToolTitle(call) }),
2300
- summary && /* @__PURE__ */ jsx10("p", { className: "mt-1 text-[13px] leading-relaxed text-muted-foreground", children: summary }),
2301
- meta.length > 0 && /* @__PURE__ */ jsx10("div", { className: "mt-1.5 flex flex-wrap items-center gap-1.5", children: meta.map((m, i) => /* @__PURE__ */ jsx10("span", { className: "rounded-full bg-muted/60 px-2 py-0.5 text-[11px] font-medium text-muted-foreground", children: m }, i)) })
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)) })
2302
2821
  ] })
2303
2822
  ] }),
2304
- /* @__PURE__ */ jsxs9("div", { className: "flex flex-wrap items-center gap-2 px-4 pb-3.5 pt-3", children: [
2305
- approval ? /* @__PURE__ */ jsxs9(Fragment5, { children: [
2306
- /* @__PURE__ */ jsx10(
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(
2307
2826
  "button",
2308
2827
  {
2309
2828
  type: "button",
@@ -2313,7 +2832,7 @@ function ProposalCard({
2313
2832
  children: "Approve & run"
2314
2833
  }
2315
2834
  ),
2316
- /* @__PURE__ */ jsx10(
2835
+ /* @__PURE__ */ jsx12(
2317
2836
  "button",
2318
2837
  {
2319
2838
  type: "button",
@@ -2323,8 +2842,8 @@ function ProposalCard({
2323
2842
  children: "Reject"
2324
2843
  }
2325
2844
  )
2326
- ] }) : /* @__PURE__ */ jsx10("span", { className: "text-[12px] font-medium text-muted-foreground", children: "Awaiting approval\u2026" }),
2327
- /* @__PURE__ */ jsxs9(
2845
+ ] }) : /* @__PURE__ */ jsx12("span", { className: "text-[12px] font-medium text-muted-foreground", children: "Awaiting approval\u2026" }),
2846
+ /* @__PURE__ */ jsxs10(
2328
2847
  "button",
2329
2848
  {
2330
2849
  type: "button",
@@ -2333,23 +2852,23 @@ function ProposalCard({
2333
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",
2334
2853
  children: [
2335
2854
  expanded ? "Hide details" : "View details",
2336
- /* @__PURE__ */ jsx10(ChevronDown, { className: `h-3 w-3 transition-transform ${expanded ? "rotate-180" : ""}` })
2855
+ /* @__PURE__ */ jsx12(ChevronDown, { className: `h-3 w-3 transition-transform ${expanded ? "rotate-180" : ""}` })
2337
2856
  ]
2338
2857
  }
2339
2858
  )
2340
2859
  ] }),
2341
- expanded && /* @__PURE__ */ jsx10("div", { className: "border-t border-warning/20 px-4 py-3 text-xs", children: custom ?? /* @__PURE__ */ jsx10(DefaultToolDetail, { call }) })
2860
+ expanded && /* @__PURE__ */ jsx12("div", { className: "border-t border-warning/20 px-4 py-3 text-xs", children: custom ?? /* @__PURE__ */ jsx12(DefaultToolDetail, { call }) })
2342
2861
  ] });
2343
2862
  }
2344
2863
  function FollowupCard({ call }) {
2345
2864
  const a = call.args ?? {};
2346
2865
  const when = typeof a.when === "string" ? a.when : typeof a.at === "string" ? a.at : typeof a.schedule === "string" ? a.schedule : null;
2347
- return /* @__PURE__ */ jsxs9("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: [
2348
- /* @__PURE__ */ jsxs9("div", { className: "flex items-center gap-2", children: [
2349
- /* @__PURE__ */ jsx10(ToolGlyph, { name: call.name, className: "h-3.5 w-3.5 shrink-0 text-primary/80" }),
2350
- /* @__PURE__ */ jsx10("span", { className: "min-w-0 flex-1 truncate font-medium text-foreground", children: friendlyToolTitle(call) })
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) })
2351
2870
  ] }),
2352
- when && /* @__PURE__ */ jsx10("p", { className: "mt-0.5 pl-[22px] text-[12px] text-muted-foreground", children: when })
2871
+ when && /* @__PURE__ */ jsx12("p", { className: "mt-0.5 pl-[22px] text-[12px] text-muted-foreground", children: when })
2353
2872
  ] });
2354
2873
  }
2355
2874
  function ToolCallCard({
@@ -2359,13 +2878,13 @@ function ToolCallCard({
2359
2878
  onOpenRun,
2360
2879
  renderers
2361
2880
  }) {
2362
- const [expanded, setExpanded] = useState10(false);
2881
+ const [expanded, setExpanded] = useState12(false);
2363
2882
  const pending = call.status === "done" ? pendingApprovalOf(call) : null;
2364
2883
  const kind = blockKindOf(call);
2365
2884
  const failed = call.status === "error" || toolOutcomeOf(call)?.ok === false;
2366
2885
  const custom = renderers?.[call.name]?.(call, message);
2367
2886
  if (pending) {
2368
- return /* @__PURE__ */ jsx10(
2887
+ return /* @__PURE__ */ jsx12(
2369
2888
  ProposalCard,
2370
2889
  {
2371
2890
  call,
@@ -2377,16 +2896,16 @@ function ToolCallCard({
2377
2896
  );
2378
2897
  }
2379
2898
  if (kind === "followup" && !failed) {
2380
- return /* @__PURE__ */ jsx10(FollowupCard, { call });
2899
+ return /* @__PURE__ */ jsx12(FollowupCard, { call });
2381
2900
  }
2382
2901
  const isCommand = kind === "command";
2383
- return /* @__PURE__ */ jsxs9(
2902
+ return /* @__PURE__ */ jsxs10(
2384
2903
  "div",
2385
2904
  {
2386
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"}`,
2387
2906
  children: [
2388
- /* @__PURE__ */ jsxs9("div", { className: "flex w-full items-center gap-2 px-3 py-2", children: [
2389
- /* @__PURE__ */ jsxs9(
2907
+ /* @__PURE__ */ jsxs10("div", { className: "flex w-full items-center gap-2 px-3 py-2", children: [
2908
+ /* @__PURE__ */ jsxs10(
2390
2909
  "button",
2391
2910
  {
2392
2911
  type: "button",
@@ -2394,14 +2913,14 @@ function ToolCallCard({
2394
2913
  "aria-expanded": expanded,
2395
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",
2396
2915
  children: [
2397
- /* @__PURE__ */ jsx10(
2916
+ /* @__PURE__ */ jsx12(
2398
2917
  "span",
2399
2918
  {
2400
2919
  className: `h-2 w-2 shrink-0 rounded-full ${call.status === "running" ? "animate-pulse bg-warning" : failed ? "bg-destructive" : "bg-success"}`
2401
2920
  }
2402
2921
  ),
2403
- /* @__PURE__ */ jsx10(ToolGlyph, { name: call.name, className: "h-3.5 w-3.5 shrink-0 text-muted-foreground" }),
2404
- /* @__PURE__ */ jsx10(
2922
+ /* @__PURE__ */ jsx12(ToolGlyph, { name: call.name, className: "h-3.5 w-3.5 shrink-0 text-muted-foreground" }),
2923
+ /* @__PURE__ */ jsx12(
2405
2924
  "span",
2406
2925
  {
2407
2926
  className: `min-w-0 flex-1 truncate ${isCommand ? "font-mono text-[12px] tracking-tight text-foreground/90" : "font-medium"}`,
@@ -2411,8 +2930,8 @@ function ToolCallCard({
2411
2930
  ]
2412
2931
  }
2413
2932
  ),
2414
- /* @__PURE__ */ jsx10("span", { className: "shrink-0 text-[11px] text-muted-foreground", children: call.status === "running" ? "running\u2026" : failed ? "failed" : "done" }),
2415
- /* @__PURE__ */ jsx10(
2933
+ /* @__PURE__ */ jsx12("span", { className: "shrink-0 text-[11px] text-muted-foreground", children: call.status === "running" ? "running\u2026" : failed ? "failed" : "done" }),
2934
+ /* @__PURE__ */ jsx12(
2416
2935
  "button",
2417
2936
  {
2418
2937
  type: "button",
@@ -2420,13 +2939,13 @@ function ToolCallCard({
2420
2939
  "aria-label": expanded ? "Collapse details" : "Expand details",
2421
2940
  "aria-expanded": expanded,
2422
2941
  className: "shrink-0 rounded p-0.5 focus:outline-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
2423
- children: /* @__PURE__ */ jsx10(ChevronDown, { className: `h-3 w-3 text-muted-foreground transition-transform ${expanded ? "rotate-180" : ""}` })
2942
+ children: /* @__PURE__ */ jsx12(ChevronDown, { className: `h-3 w-3 text-muted-foreground transition-transform ${expanded ? "rotate-180" : ""}` })
2424
2943
  }
2425
2944
  )
2426
2945
  ] }),
2427
- expanded && /* @__PURE__ */ jsxs9("div", { className: "border-t border-border/40 px-3 py-2.5", children: [
2428
- custom ?? (call.name === "sandbox_run_command" ? /* @__PURE__ */ jsx10(ShellDetail, { call }) : /* @__PURE__ */ jsx10(DefaultToolDetail, { call })),
2429
- onOpenRun && call.name.startsWith("sandbox_") && /* @__PURE__ */ jsx10(
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(
2430
2949
  "button",
2431
2950
  {
2432
2951
  type: "button",
@@ -2441,7 +2960,7 @@ function ToolCallCard({
2441
2960
  );
2442
2961
  }
2443
2962
  function StreamingCaret() {
2444
- return /* @__PURE__ */ jsx10(
2963
+ return /* @__PURE__ */ jsx12(
2445
2964
  "span",
2446
2965
  {
2447
2966
  className: "ml-0.5 inline-block h-[1.1em] w-[3px] translate-y-[2px] animate-pulse rounded-sm bg-foreground/70",
@@ -2458,9 +2977,9 @@ function SegmentText({
2458
2977
  const text = useSmoothText(content, streaming);
2459
2978
  const body = useMemo7(() => renderBody(text), [renderBody, text]);
2460
2979
  if (!content.trim() && !showCaret) return null;
2461
- return /* @__PURE__ */ jsxs9("div", { className: "text-base leading-[1.75]", children: [
2980
+ return /* @__PURE__ */ jsxs10("div", { className: "text-base leading-[1.75]", children: [
2462
2981
  body,
2463
- showCaret && /* @__PURE__ */ jsx10(StreamingCaret, {})
2982
+ showCaret && /* @__PURE__ */ jsx12(StreamingCaret, {})
2464
2983
  ] });
2465
2984
  }
2466
2985
  var COLLAPSE_TOOL_RUN_AT = 3;
@@ -2483,7 +3002,7 @@ function SegmentedBody({
2483
3002
  const leftoverToolCalls = (msg.toolCalls ?? []).filter(
2484
3003
  (tc) => !segmentToolIds.has(tc.id)
2485
3004
  );
2486
- const renderToolCard = (call) => /* @__PURE__ */ jsx10(
3005
+ const renderToolCard = (call) => /* @__PURE__ */ jsx12(
2487
3006
  ToolCallCard,
2488
3007
  {
2489
3008
  call,
@@ -2506,9 +3025,9 @@ function SegmentedBody({
2506
3025
  else groups.push({ kind: "tools", index: i, calls: [seg.call] });
2507
3026
  }
2508
3027
  }
2509
- return /* @__PURE__ */ jsxs9("div", { className: "flex flex-col gap-2", children: [
3028
+ return /* @__PURE__ */ jsxs10("div", { className: "flex flex-col gap-2", children: [
2510
3029
  groups.map(
2511
- (g) => g.kind === "text" ? /* @__PURE__ */ jsx10(
3030
+ (g) => g.kind === "text" ? /* @__PURE__ */ jsx12(
2512
3031
  SegmentText,
2513
3032
  {
2514
3033
  content: g.content,
@@ -2517,24 +3036,24 @@ function SegmentedBody({
2517
3036
  renderBody
2518
3037
  },
2519
3038
  `text-${g.index}`
2520
- ) : !streaming && g.calls.length >= COLLAPSE_TOOL_RUN_AT && !g.calls.some(isImportantTool) ? /* @__PURE__ */ jsxs9(
3039
+ ) : !streaming && g.calls.length >= COLLAPSE_TOOL_RUN_AT && !g.calls.some(isImportantTool) ? /* @__PURE__ */ jsxs10(
2521
3040
  "details",
2522
3041
  {
2523
3042
  className: "rounded-lg border-l-2 border-border/70 bg-muted/20 px-3 py-2",
2524
3043
  children: [
2525
- /* @__PURE__ */ jsxs9("summary", { className: "cursor-pointer select-none text-xs font-medium text-muted-foreground", children: [
3044
+ /* @__PURE__ */ jsxs10("summary", { className: "cursor-pointer select-none text-xs font-medium text-muted-foreground", children: [
2526
3045
  "Worked through ",
2527
3046
  g.calls.length,
2528
3047
  " steps"
2529
3048
  ] }),
2530
- /* @__PURE__ */ jsx10("div", { className: "mt-2 flex flex-col gap-2", children: g.calls.map(renderToolCard) })
3049
+ /* @__PURE__ */ jsx12("div", { className: "mt-2 flex flex-col gap-2", children: g.calls.map(renderToolCard) })
2531
3050
  ]
2532
3051
  },
2533
3052
  `tools-${g.index}`
2534
- ) : /* @__PURE__ */ jsx10("div", { className: "flex flex-col gap-2", children: g.calls.map(renderToolCard) }, `tools-${g.index}`)
3053
+ ) : /* @__PURE__ */ jsx12("div", { className: "flex flex-col gap-2", children: g.calls.map(renderToolCard) }, `tools-${g.index}`)
2535
3054
  ),
2536
3055
  leftoverToolCalls.map(renderToolCard),
2537
- streaming && segments[lastIndex]?.kind === "tool" && /* @__PURE__ */ jsx10(StreamingCaret, {})
3056
+ streaming && segments[lastIndex]?.kind === "tool" && /* @__PURE__ */ jsx12(StreamingCaret, {})
2538
3057
  ] });
2539
3058
  }
2540
3059
  function AssistantMessageImpl({
@@ -2546,44 +3065,45 @@ function AssistantMessageImpl({
2546
3065
  approval,
2547
3066
  onToolCallClick,
2548
3067
  toolRenderers,
2549
- renderExtras
3068
+ renderExtras,
3069
+ durableCards
2550
3070
  }) {
2551
3071
  const content = useSmoothText(msg.content, streaming);
2552
3072
  const reasoning = useSmoothText(msg.reasoning ?? "", streaming);
2553
3073
  const body = useMemo7(() => renderBody(content), [renderBody, content]);
2554
3074
  const segments = msg.segments;
2555
3075
  const hasAnswerText = content !== "" || (segments?.some((s) => s.kind === "text" && s.content.trim() !== "") ?? false);
2556
- const reasoningScrollRef = useRef7(null);
2557
- const thinkStartRef = useRef7(null);
2558
- const thinkMsRef = useRef7(null);
3076
+ const reasoningScrollRef = useRef8(null);
3077
+ const thinkStartRef = useRef8(null);
3078
+ const thinkMsRef = useRef8(null);
2559
3079
  if (streaming && reasoning && !hasAnswerText && thinkStartRef.current === null) {
2560
3080
  thinkStartRef.current = performance.now();
2561
3081
  }
2562
3082
  if (hasAnswerText && thinkStartRef.current !== null && thinkMsRef.current === null) {
2563
3083
  thinkMsRef.current = performance.now() - thinkStartRef.current;
2564
3084
  }
2565
- useEffect5(() => {
3085
+ useEffect8(() => {
2566
3086
  const el = reasoningScrollRef.current;
2567
3087
  if (el && streaming && !hasAnswerText) el.scrollTop = el.scrollHeight;
2568
3088
  }, [reasoning, streaming, hasAnswerText]);
2569
3089
  const thinkingSeconds = useThinkingSeconds(
2570
3090
  streaming && !!reasoning && !hasAnswerText
2571
3091
  );
2572
- return /* @__PURE__ */ jsxs9("div", { className: "mx-auto w-full max-w-3xl px-6 py-3", children: [
2573
- /* @__PURE__ */ jsxs9("div", { className: "mb-1 flex items-baseline gap-2 text-[11px] tracking-wide text-muted-foreground", children: [
2574
- /* @__PURE__ */ jsx10("span", { className: "font-semibold uppercase", children: agentLabel }),
2575
- msg.modelUsed && /* @__PURE__ */ jsx10("span", { className: "font-mono normal-case", children: msg.modelUsed }),
2576
- formatTokensPerSecond(msg) && /* @__PURE__ */ jsx10("span", { children: formatTokensPerSecond(msg) }),
2577
- formatModelCost(msg, models) && /* @__PURE__ */ jsx10("span", { children: formatModelCost(msg, models) })
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) })
2578
3098
  ] }),
2579
- reasoning && /* @__PURE__ */ jsxs9("details", { className: "mb-2 rounded-lg border-l-2 border-border/70 bg-muted/20 px-3 py-2", open: !hasAnswerText, children: [
2580
- /* @__PURE__ */ jsx10("summary", { className: "cursor-pointer select-none text-xs font-medium text-muted-foreground", children: !hasAnswerText ? /* @__PURE__ */ jsxs9("span", { className: "animate-pulse", children: [
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: [
2581
3101
  "Thinking",
2582
3102
  thinkingSeconds >= 3 ? ` \xB7 ${thinkingSeconds}s` : "\u2026"
2583
3103
  ] }) : thinkMsRef.current != null ? `Thought for ${Math.max(1, Math.round(thinkMsRef.current / 1e3))}s` : "Thought process" }),
2584
- /* @__PURE__ */ jsx10("div", { ref: reasoningScrollRef, className: "mt-2 max-h-48 overflow-y-auto whitespace-pre-wrap text-[13px] leading-relaxed text-muted-foreground", children: reasoning })
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 })
2585
3105
  ] }),
2586
- segments && segments.length > 0 ? /* @__PURE__ */ jsx10(
3106
+ segments && segments.length > 0 ? /* @__PURE__ */ jsx12(
2587
3107
  SegmentedBody,
2588
3108
  {
2589
3109
  segments,
@@ -2594,12 +3114,12 @@ function AssistantMessageImpl({
2594
3114
  onToolCallClick,
2595
3115
  toolRenderers
2596
3116
  }
2597
- ) : /* @__PURE__ */ jsxs9(Fragment5, { children: [
2598
- /* @__PURE__ */ jsxs9("div", { className: "text-base leading-[1.75]", children: [
3117
+ ) : /* @__PURE__ */ jsxs10(Fragment5, { children: [
3118
+ /* @__PURE__ */ jsxs10("div", { className: "text-base leading-[1.75]", children: [
2599
3119
  body,
2600
- streaming && content && !msg.toolCalls?.length && /* @__PURE__ */ jsx10(StreamingCaret, {})
3120
+ streaming && content && !msg.toolCalls?.length && /* @__PURE__ */ jsx12(StreamingCaret, {})
2601
3121
  ] }),
2602
- msg.toolCalls && msg.toolCalls.length > 0 && /* @__PURE__ */ jsx10("div", { className: "mt-2 flex flex-col gap-1.5", children: msg.toolCalls.map((tc) => /* @__PURE__ */ jsx10(
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(
2603
3123
  ToolCallCard,
2604
3124
  {
2605
3125
  call: tc,
@@ -2611,13 +3131,22 @@ function AssistantMessageImpl({
2611
3131
  tc.id
2612
3132
  )) })
2613
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
+ ),
2614
3143
  renderExtras?.(msg)
2615
3144
  ] });
2616
3145
  }
2617
3146
  var AssistantMessage = memo(AssistantMessageImpl);
2618
3147
  function useThinkingSeconds(active) {
2619
- const [seconds, setSeconds] = useState10(0);
2620
- useEffect5(() => {
3148
+ const [seconds, setSeconds] = useState12(0);
3149
+ useEffect8(() => {
2621
3150
  if (!active) return;
2622
3151
  setSeconds(0);
2623
3152
  const id = setInterval(() => setSeconds((s) => s + 1), 1e3);
@@ -2627,23 +3156,23 @@ function useThinkingSeconds(active) {
2627
3156
  }
2628
3157
  function ThinkingRow({ agentLabel }) {
2629
3158
  const seconds = useThinkingSeconds(true);
2630
- return /* @__PURE__ */ jsxs9("div", { className: "mx-auto w-full max-w-3xl px-6 py-3", children: [
2631
- /* @__PURE__ */ jsx10("p", { className: "mb-1 text-[11px] font-semibold uppercase tracking-wide text-muted-foreground", children: agentLabel }),
2632
- /* @__PURE__ */ jsxs9("div", { className: "flex items-center gap-2 text-base text-muted-foreground", children: [
2633
- /* @__PURE__ */ jsx10("svg", { className: "h-4 w-4 animate-spin", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", "aria-hidden": true, children: /* @__PURE__ */ jsx10("path", { d: "M21 12a9 9 0 1 1-6.219-8.56", strokeLinecap: "round" }) }),
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" }) }),
2634
3163
  "Thinking",
2635
3164
  seconds >= 3 ? ` \xB7 ${seconds}s` : "..."
2636
3165
  ] })
2637
3166
  ] });
2638
3167
  }
2639
3168
  function StreamErrorRow({ message, onRetry }) {
2640
- return /* @__PURE__ */ jsx10("div", { className: "mx-auto w-full max-w-3xl px-6 py-3", children: /* @__PURE__ */ jsxs9("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: [
2641
- /* @__PURE__ */ jsxs9("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: [
2642
- /* @__PURE__ */ jsx10("circle", { cx: "12", cy: "12", r: "9" }),
2643
- /* @__PURE__ */ jsx10("path", { d: "M12 8v4m0 4h.01" })
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" })
2644
3173
  ] }),
2645
- /* @__PURE__ */ jsx10("span", { className: "min-w-0 flex-1 break-words", children: message }),
2646
- onRetry && /* @__PURE__ */ jsx10(
3174
+ /* @__PURE__ */ jsx12("span", { className: "min-w-0 flex-1 break-words", children: message }),
3175
+ onRetry && /* @__PURE__ */ jsx12(
2647
3176
  "button",
2648
3177
  {
2649
3178
  type: "button",
@@ -2659,6 +3188,7 @@ function ChatMessages({
2659
3188
  models = [],
2660
3189
  renderMarkdown,
2661
3190
  renderExtras,
3191
+ durableCards,
2662
3192
  userLabel = "User",
2663
3193
  agentLabel = "Agent",
2664
3194
  loading,
@@ -2672,24 +3202,24 @@ function ChatMessages({
2672
3202
  header
2673
3203
  }) {
2674
3204
  const renderBody = useMemo7(
2675
- () => renderMarkdown ?? ((content) => /* @__PURE__ */ jsx10("p", { className: "whitespace-pre-wrap", children: content })),
3205
+ () => renderMarkdown ?? ((content) => /* @__PURE__ */ jsx12("p", { className: "whitespace-pre-wrap", children: content })),
2676
3206
  [renderMarkdown]
2677
3207
  );
2678
3208
  const lastIsUser = messages[messages.length - 1]?.role === "user";
2679
3209
  if (messages.length === 0 && !loading && !error) {
2680
- const empty = renderEmpty ? renderEmpty() : /* @__PURE__ */ jsx10(ChatEmptyState, { ...emptyState });
2681
- return /* @__PURE__ */ jsxs9(Fragment5, { children: [
3210
+ const empty = renderEmpty ? renderEmpty() : /* @__PURE__ */ jsx12(ChatEmptyState, { ...emptyState });
3211
+ return /* @__PURE__ */ jsxs10(Fragment5, { children: [
2682
3212
  header,
2683
3213
  empty
2684
3214
  ] });
2685
3215
  }
2686
- return /* @__PURE__ */ jsxs9(Fragment5, { children: [
3216
+ return /* @__PURE__ */ jsxs10(Fragment5, { children: [
2687
3217
  header,
2688
3218
  messages.map(
2689
- (msg) => msg.role === "user" ? /* @__PURE__ */ jsx10("div", { className: "mx-auto w-full max-w-3xl px-6 py-3", children: /* @__PURE__ */ jsxs9("div", { className: "ml-auto w-fit max-w-[85%]", children: [
2690
- /* @__PURE__ */ jsx10("p", { className: "mb-1 text-right text-[11px] font-semibold uppercase tracking-wide text-muted-foreground", children: userLabel }),
2691
- /* @__PURE__ */ jsx10("div", { className: "rounded-2xl rounded-tr-md bg-primary/10 px-4 py-2.5 text-base leading-relaxed", children: /* @__PURE__ */ jsx10("p", { className: "whitespace-pre-wrap", children: msg.content }) })
2692
- ] }) }, msg.id) : /* @__PURE__ */ jsx10(
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(
2693
3223
  AssistantMessage,
2694
3224
  {
2695
3225
  msg,
@@ -2700,13 +3230,14 @@ function ChatMessages({
2700
3230
  approval,
2701
3231
  onToolCallClick,
2702
3232
  toolRenderers,
2703
- renderExtras
3233
+ renderExtras,
3234
+ durableCards
2704
3235
  },
2705
3236
  msg.id
2706
3237
  )
2707
3238
  ),
2708
- loading && lastIsUser && /* @__PURE__ */ jsx10(ThinkingRow, { agentLabel }),
2709
- error && !loading && /* @__PURE__ */ jsx10(StreamErrorRow, { message: error, onRetry })
3239
+ loading && lastIsUser && /* @__PURE__ */ jsx12(ThinkingRow, { agentLabel }),
3240
+ error && !loading && /* @__PURE__ */ jsx12(StreamErrorRow, { message: error, onRetry })
2710
3241
  ] });
2711
3242
  }
2712
3243
 
@@ -2719,12 +3250,9 @@ export {
2719
3250
  ModelPicker,
2720
3251
  DEFAULT_EFFORT_LEVELS,
2721
3252
  EffortPicker,
2722
- dispatchChatStreamLine,
2723
- consumeChatStream,
2724
- streamChatTurn,
2725
- ChatComposer,
2726
3253
  interactionStatusLabels,
2727
3254
  interactionTerminalNotes,
3255
+ fieldValuesFromAnswers,
2728
3256
  fieldAnswer,
2729
3257
  buildAnswerData,
2730
3258
  isLateAnswerableStatus,
@@ -2738,12 +3266,27 @@ export {
2738
3266
  InteractionActionButton,
2739
3267
  QuestionOptionList,
2740
3268
  InteractionQuestionCard,
3269
+ DurablePlanCard,
2741
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,
2742
3284
  upsertChatInteraction,
2743
3285
  cancelChatInteraction,
2744
3286
  resolveChatInteraction,
2745
3287
  terminalizePendingChatInteractions,
2746
3288
  restoreChatInteractions,
3289
+ hydrateChatInteractions,
2747
3290
  useChatInteractions,
2748
3291
  rankFileMentions,
2749
3292
  DEFAULT_MENTION_LIMIT,
@@ -2768,4 +3311,4 @@ export {
2768
3311
  useThinkingSeconds,
2769
3312
  ChatMessages
2770
3313
  };
2771
- //# sourceMappingURL=chunk-KM766NN3.js.map
3314
+ //# sourceMappingURL=chunk-ZLHK25C3.js.map