@tangle-network/agent-app 0.43.41 → 0.43.43

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 (52) 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-KM766NN3.js → chunk-PEUBCJXF.js} +1772 -1224
  17. package/dist/chunk-PEUBCJXF.js.map +1 -0
  18. package/dist/{chunk-UMSOSUEU.js → chunk-QYOD3K56.js} +2 -2
  19. package/dist/chunk-QYOD3K56.js.map +1 -0
  20. package/dist/chunk-SIXYZ2FB.js +101 -0
  21. package/dist/chunk-SIXYZ2FB.js.map +1 -0
  22. package/dist/{chunk-4TXDD6P2.js → chunk-XAWFPMAR.js} +38 -3
  23. package/dist/chunk-XAWFPMAR.js.map +1 -0
  24. package/dist/chunk-ZU5GNSOJ.js +920 -0
  25. package/dist/chunk-ZU5GNSOJ.js.map +1 -0
  26. package/dist/{contract-DYbTzEDf.d.ts → contract-KfqJh_au.d.ts} +23 -2
  27. package/dist/design-canvas-react/index.js +4 -4
  28. package/dist/durable-chat/index.d.ts +419 -0
  29. package/dist/durable-chat/index.js +59 -0
  30. package/dist/durable-chat/index.js.map +1 -0
  31. package/dist/index.d.ts +5 -3
  32. package/dist/index.js +124 -46
  33. package/dist/interactions/index.d.ts +58 -3
  34. package/dist/interactions/index.js +7 -3
  35. package/dist/object-store/index.d.ts +6 -4
  36. package/dist/object-store/index.js +1 -1
  37. package/dist/{parts-BcbitSNp.d.ts → parts-DjX0RRTS.d.ts} +9 -3
  38. package/dist/plans/index.d.ts +69 -0
  39. package/dist/plans/index.js +21 -0
  40. package/dist/plans/index.js.map +1 -0
  41. package/dist/stream/index.js +3 -1
  42. package/dist/teams/index.js +9 -9
  43. package/dist/teams/invitations-api.js +3 -3
  44. package/dist/web-react/index.d.ts +252 -93
  45. package/dist/web-react/index.js +32 -3
  46. package/package.json +11 -1
  47. package/dist/chunk-4TXDD6P2.js.map +0 -1
  48. package/dist/chunk-I2R2XT4M.js.map +0 -1
  49. package/dist/chunk-KM766NN3.js.map +0 -1
  50. package/dist/chunk-UMSOSUEU.js.map +0 -1
  51. package/dist/chunk-XYWCGFII.js.map +0 -1
  52. 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,
1439
+ }, []);
1440
+ const handleDragOver = useCallback((e) => {
1441
+ e.preventDefault();
1442
+ e.stopPropagation();
1443
+ if (e.dataTransfer) e.dataTransfer.dropEffect = "copy";
1444
+ }, []);
1445
+ const handleDrop = useCallback(
1446
+ (e) => {
1447
+ e.preventDefault();
1448
+ e.stopPropagation();
1449
+ dragDepth.current = 0;
1450
+ setDragOver(false);
1451
+ const files = e.dataTransfer?.files;
1452
+ if (files?.length) onAttach?.(files);
1453
+ },
1454
+ [onAttach]
1455
+ );
1456
+ const folderChips = pendingFiles.filter((f) => f.kind === "folder");
1457
+ const fileChips = pendingFiles.filter((f) => f.kind !== "folder");
1458
+ const showFooter = controls != null && controlsPlacement === "footer";
1459
+ const showAbove = controls != null && controlsPlacement === "above";
1460
+ return /* @__PURE__ */ jsxs6(
1461
+ "div",
1462
+ {
1463
+ className: `relative ${className ?? ""}`,
1464
+ onDragEnter: onAttach ? handleDragEnter : void 0,
1465
+ onDragLeave: onAttach ? handleDragLeave : void 0,
1466
+ onDragOver: onAttach ? handleDragOver : void 0,
1467
+ onDrop: onAttach ? handleDrop : void 0,
1468
+ children: [
1469
+ dragOver && /* @__PURE__ */ jsx8("div", { className: "pointer-events-none absolute inset-0 z-10 flex items-center justify-center rounded-2xl border-2 border-dashed border-primary/50 bg-card/95", children: /* @__PURE__ */ jsxs6("div", { className: "text-center", children: [
1470
+ /* @__PURE__ */ jsx8("span", { className: "mx-auto mb-2 flex h-11 w-11 items-center justify-center rounded-xl bg-primary/10 text-primary", children: /* @__PURE__ */ jsx8(UploadGlyph, { className: "h-5 w-5" }) }),
1471
+ /* @__PURE__ */ jsx8("p", { className: "text-sm font-semibold text-foreground", children: dropTitle }),
1472
+ /* @__PURE__ */ jsx8("p", { className: "mt-0.5 text-xs text-muted-foreground", children: dropDescription })
1473
+ ] }) }),
1474
+ showAbove && /* @__PURE__ */ jsx8("div", { className: "mb-1.5 flex flex-wrap items-center gap-1.5 px-1", children: controls }),
1475
+ pendingFiles.length > 0 && /* @__PURE__ */ jsx8("div", { className: "mb-2 flex flex-wrap gap-1.5", children: [...folderChips, ...fileChips].map((f) => /* @__PURE__ */ jsxs6(
1476
+ "span",
1477
+ {
1478
+ className: `inline-flex items-center gap-1.5 rounded-full border px-2.5 py-1 text-xs ${f.status === "error" ? "border-destructive/40 text-destructive" : "border-border bg-muted/50 text-foreground"}`,
1479
+ children: [
1480
+ f.kind === "folder" ? /* @__PURE__ */ jsx8(FolderGlyph, { className: "h-3 w-3 shrink-0" }) : /* @__PURE__ */ jsx8(PaperclipGlyph, { className: "h-3 w-3 shrink-0" }),
1481
+ /* @__PURE__ */ jsx8("span", { className: "max-w-[150px] truncate", children: f.name }),
1482
+ f.fileCount !== void 0 && /* @__PURE__ */ jsxs6("span", { className: "text-muted-foreground", children: [
1483
+ "(",
1484
+ f.fileCount,
1485
+ ")"
1486
+ ] }),
1487
+ f.status === "uploading" && /* @__PURE__ */ jsx8("span", { className: "h-3 w-3 animate-spin rounded-full border-2 border-primary border-t-transparent" }),
1488
+ onRemoveFile && /* @__PURE__ */ jsx8(
1489
+ "button",
1490
+ {
1491
+ type: "button",
1492
+ "aria-label": `Remove ${f.name}`,
1493
+ onClick: () => onRemoveFile(f.id),
1494
+ className: "rounded p-0.5 text-muted-foreground transition hover:text-foreground focus:outline-none focus-visible:ring-2 focus-visible:ring-ring",
1495
+ children: /* @__PURE__ */ jsx8(CloseGlyph, { className: "h-3 w-3" })
1496
+ }
1497
+ )
1498
+ ]
1499
+ },
1500
+ f.id
1501
+ )) }),
1502
+ /* @__PURE__ */ jsxs6("div", { className: "flex items-end gap-2 rounded-2xl border border-border bg-card px-2.5 py-2 transition focus-within:border-primary/40 focus-within:ring-2 focus-within:ring-primary/15", children: [
1503
+ onAttach && /* @__PURE__ */ jsxs6(Fragment3, { children: [
1504
+ /* @__PURE__ */ jsx8(
1505
+ "button",
1506
+ {
1507
+ type: "button",
1508
+ onClick: () => fileInputRef.current?.click(),
1509
+ disabled,
1510
+ "aria-label": "Attach files",
1511
+ title: "Attach files",
1512
+ className: "mb-0.5 shrink-0 rounded-lg p-2 text-muted-foreground transition hover:bg-accent/40 hover:text-foreground disabled:opacity-50 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring",
1513
+ children: /* @__PURE__ */ jsx8(PaperclipGlyph, { className: "h-4 w-4" })
1514
+ }
1515
+ ),
1516
+ /* @__PURE__ */ jsx8("input", { ref: fileInputRef, type: "file", multiple: true, className: "hidden", accept, onChange: handleFileChange })
1517
+ ] }),
1518
+ onAttachFolder && /* @__PURE__ */ jsxs6(Fragment3, { children: [
1519
+ /* @__PURE__ */ jsx8(
1520
+ "button",
1521
+ {
1522
+ type: "button",
1523
+ onClick: () => folderInputRef.current?.click(),
1524
+ disabled,
1525
+ "aria-label": "Attach folder",
1526
+ title: "Attach folder",
1527
+ className: "mb-0.5 shrink-0 rounded-lg p-2 text-muted-foreground transition hover:bg-accent/40 hover:text-foreground disabled:opacity-50 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring",
1528
+ children: /* @__PURE__ */ jsx8(FolderGlyph, { className: "h-4 w-4" })
1529
+ }
1530
+ ),
1531
+ /* @__PURE__ */ jsx8(
1532
+ "input",
1533
+ {
1534
+ ref: folderInputRef,
1535
+ type: "file",
1536
+ multiple: true,
1537
+ className: "hidden",
1538
+ onChange: handleFolderChange,
1539
+ ...{ webkitdirectory: "" }
1540
+ }
1541
+ )
1542
+ ] }),
1543
+ /* @__PURE__ */ jsx8(
1544
+ "textarea",
1145
1545
  {
1146
- groupName: `${interaction.id}-${field.name}`,
1147
- idPrefix: `${interaction.id}-${field.name}`,
1148
- options: select.options,
1149
- multi: select.multi === true,
1150
- selectedValues: value.selected ?? [],
1546
+ ref: textareaRef,
1547
+ value: text,
1548
+ onChange: (e) => setText(e.target.value),
1549
+ onKeyDown: handleKeyDown,
1550
+ placeholder,
1151
1551
  disabled,
1152
- onToggle: (optionValue) => toggleSelected(select, optionValue)
1552
+ rows: 1,
1553
+ "aria-label": "Message input",
1554
+ className: "max-h-[168px] min-h-[40px] flex-1 resize-none bg-transparent px-1.5 py-2 text-[15px] leading-6 text-foreground outline-none placeholder:text-muted-foreground disabled:opacity-50"
1153
1555
  }
1154
1556
  ),
1155
- 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",
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",
1170
1560
  {
1171
- type: "radio",
1172
- name: `${interaction.id}-${field.name}`,
1173
- value: boolValue,
1174
- checked: (value.selected ?? [])[0] === boolValue,
1175
- disabled,
1176
- onChange: () => setFieldValue(field.name, { selected: [boolValue] }),
1177
- className: "h-4 w-4 accent-primary"
1178
- }
1179
- ),
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
- ] });
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) {
@@ -2242,42 +2761,47 @@ function proposalPreview(call) {
2242
2761
  }
2243
2762
  function truncate(v, max = 240) {
2244
2763
  const s = typeof v === "string" ? v : JSON.stringify(v);
2764
+ if (typeof s !== "string") return "";
2245
2765
  return s.length > max ? `${s.slice(0, max)}\u2026` : s;
2246
2766
  }
2247
2767
  function KvRows({ data }) {
2248
2768
  const entries = Object.entries(data).filter(([, v]) => v !== void 0 && v !== null && v !== "");
2249
2769
  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) })
2770
+ 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: [
2771
+ /* @__PURE__ */ jsx12("dt", { className: "font-mono text-[11px] text-muted-foreground", children: k }),
2772
+ /* @__PURE__ */ jsx12("dd", { className: "min-w-0 whitespace-pre-wrap break-words font-mono text-[11px] text-muted-foreground", children: truncate(v) })
2253
2773
  ] }, k)) });
2254
2774
  }
2255
2775
  function ShellDetail({ call }) {
2256
2776
  const outcome = toolOutcomeOf(call);
2257
2777
  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: [
2778
+ return /* @__PURE__ */ jsxs10("div", { className: "overflow-hidden rounded-md bg-zinc-900 font-mono text-[11px] leading-relaxed", children: [
2779
+ /* @__PURE__ */ jsxs10("div", { className: "flex items-center gap-2 px-3 pt-2 text-zinc-400", children: [
2780
+ /* @__PURE__ */ jsx12("span", { className: "select-none text-zinc-500", children: "$" }),
2781
+ /* @__PURE__ */ jsx12("span", { className: "min-w-0 flex-1 truncate text-zinc-200", children: String(call.args?.command ?? "") }),
2782
+ r.exitCode != null && /* @__PURE__ */ jsxs10("span", { className: r.exitCode === 0 ? "text-success" : "text-destructive", children: [
2263
2783
  "exit ",
2264
2784
  r.exitCode
2265
2785
  ] })
2266
2786
  ] }),
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)" })
2787
+ /* @__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
2788
  ] });
2269
2789
  }
2270
2790
  function DefaultToolDetail({ call }) {
2271
- 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
+ const result = call.result;
2792
+ const envelope = typeof result === "object" && result !== null ? result : null;
2793
+ return /* @__PURE__ */ jsxs10("div", { className: "space-y-2", children: [
2794
+ call.args && Object.keys(call.args).length > 0 && /* @__PURE__ */ jsxs10("div", { children: [
2795
+ /* @__PURE__ */ jsx12("p", { className: "mb-1 text-[10px] font-semibold uppercase tracking-wide text-muted-foreground", children: "Called with" }),
2796
+ /* @__PURE__ */ jsx12(KvRows, { data: call.args })
2276
2797
  ] }),
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) })
2280
- ] })
2798
+ envelope ? /* @__PURE__ */ jsxs10("div", { children: [
2799
+ /* @__PURE__ */ jsx12("p", { className: "mb-1 text-[10px] font-semibold uppercase tracking-wide text-muted-foreground", children: envelope.ok === false ? "Failed" : "Result" }),
2800
+ envelope.ok === false ? /* @__PURE__ */ jsx12("p", { className: "text-xs text-destructive", children: envelope.message ?? "Tool failed" }) : envelope.result && typeof envelope.result === "object" ? /* @__PURE__ */ jsx12(KvRows, { data: envelope.result }) : envelope.result != null ? /* @__PURE__ */ jsx12("p", { className: "font-mono text-[11px] text-muted-foreground", children: truncate(envelope.result) }) : null
2801
+ ] }) : result != null ? /* @__PURE__ */ jsxs10("div", { children: [
2802
+ /* @__PURE__ */ jsx12("p", { className: "mb-1 text-[10px] font-semibold uppercase tracking-wide text-muted-foreground", children: "Result" }),
2803
+ /* @__PURE__ */ jsx12("p", { className: "font-mono text-[11px] text-muted-foreground", children: truncate(result) })
2804
+ ] }) : null
2281
2805
  ] });
2282
2806
  }
2283
2807
  function ProposalCard({
@@ -2287,23 +2811,23 @@ function ProposalCard({
2287
2811
  approval,
2288
2812
  renderers
2289
2813
  }) {
2290
- const [expanded, setExpanded] = useState10(false);
2814
+ const [expanded, setExpanded] = useState12(false);
2291
2815
  const { summary, meta } = proposalPreview(call);
2292
2816
  const custom = renderers?.[call.name]?.(call, message);
2293
2817
  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)) })
2818
+ 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: [
2819
+ /* @__PURE__ */ jsxs10("div", { className: "flex items-start gap-2.5 px-4 pt-3.5", children: [
2820
+ /* @__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" }) }),
2821
+ /* @__PURE__ */ jsxs10("div", { className: "min-w-0 flex-1", children: [
2822
+ /* @__PURE__ */ jsx12("p", { className: "text-[10px] font-semibold uppercase tracking-wider text-warning-foreground", children: "Needs your approval" }),
2823
+ /* @__PURE__ */ jsx12("p", { className: "mt-0.5 text-[15px] font-semibold leading-snug text-foreground", children: friendlyToolTitle(call) }),
2824
+ summary && /* @__PURE__ */ jsx12("p", { className: "mt-1 text-[13px] leading-relaxed text-muted-foreground", children: summary }),
2825
+ 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
2826
  ] })
2303
2827
  ] }),
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(
2828
+ /* @__PURE__ */ jsxs10("div", { className: "flex flex-wrap items-center gap-2 px-4 pb-3.5 pt-3", children: [
2829
+ approval ? /* @__PURE__ */ jsxs10(Fragment5, { children: [
2830
+ /* @__PURE__ */ jsx12(
2307
2831
  "button",
2308
2832
  {
2309
2833
  type: "button",
@@ -2313,7 +2837,7 @@ function ProposalCard({
2313
2837
  children: "Approve & run"
2314
2838
  }
2315
2839
  ),
2316
- /* @__PURE__ */ jsx10(
2840
+ /* @__PURE__ */ jsx12(
2317
2841
  "button",
2318
2842
  {
2319
2843
  type: "button",
@@ -2323,8 +2847,8 @@ function ProposalCard({
2323
2847
  children: "Reject"
2324
2848
  }
2325
2849
  )
2326
- ] }) : /* @__PURE__ */ jsx10("span", { className: "text-[12px] font-medium text-muted-foreground", children: "Awaiting approval\u2026" }),
2327
- /* @__PURE__ */ jsxs9(
2850
+ ] }) : /* @__PURE__ */ jsx12("span", { className: "text-[12px] font-medium text-muted-foreground", children: "Awaiting approval\u2026" }),
2851
+ /* @__PURE__ */ jsxs10(
2328
2852
  "button",
2329
2853
  {
2330
2854
  type: "button",
@@ -2333,23 +2857,23 @@ function ProposalCard({
2333
2857
  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
2858
  children: [
2335
2859
  expanded ? "Hide details" : "View details",
2336
- /* @__PURE__ */ jsx10(ChevronDown, { className: `h-3 w-3 transition-transform ${expanded ? "rotate-180" : ""}` })
2860
+ /* @__PURE__ */ jsx12(ChevronDown, { className: `h-3 w-3 transition-transform ${expanded ? "rotate-180" : ""}` })
2337
2861
  ]
2338
2862
  }
2339
2863
  )
2340
2864
  ] }),
2341
- expanded && /* @__PURE__ */ jsx10("div", { className: "border-t border-warning/20 px-4 py-3 text-xs", children: custom ?? /* @__PURE__ */ jsx10(DefaultToolDetail, { call }) })
2865
+ expanded && /* @__PURE__ */ jsx12("div", { className: "border-t border-warning/20 px-4 py-3 text-xs", children: custom ?? /* @__PURE__ */ jsx12(DefaultToolDetail, { call }) })
2342
2866
  ] });
2343
2867
  }
2344
2868
  function FollowupCard({ call }) {
2345
2869
  const a = call.args ?? {};
2346
2870
  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) })
2871
+ 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: [
2872
+ /* @__PURE__ */ jsxs10("div", { className: "flex items-center gap-2", children: [
2873
+ /* @__PURE__ */ jsx12(ToolGlyph, { name: call.name, className: "h-3.5 w-3.5 shrink-0 text-primary/80" }),
2874
+ /* @__PURE__ */ jsx12("span", { className: "min-w-0 flex-1 truncate font-medium text-foreground", children: friendlyToolTitle(call) })
2351
2875
  ] }),
2352
- when && /* @__PURE__ */ jsx10("p", { className: "mt-0.5 pl-[22px] text-[12px] text-muted-foreground", children: when })
2876
+ when && /* @__PURE__ */ jsx12("p", { className: "mt-0.5 pl-[22px] text-[12px] text-muted-foreground", children: when })
2353
2877
  ] });
2354
2878
  }
2355
2879
  function ToolCallCard({
@@ -2359,13 +2883,13 @@ function ToolCallCard({
2359
2883
  onOpenRun,
2360
2884
  renderers
2361
2885
  }) {
2362
- const [expanded, setExpanded] = useState10(false);
2886
+ const [expanded, setExpanded] = useState12(false);
2363
2887
  const pending = call.status === "done" ? pendingApprovalOf(call) : null;
2364
2888
  const kind = blockKindOf(call);
2365
2889
  const failed = call.status === "error" || toolOutcomeOf(call)?.ok === false;
2366
2890
  const custom = renderers?.[call.name]?.(call, message);
2367
2891
  if (pending) {
2368
- return /* @__PURE__ */ jsx10(
2892
+ return /* @__PURE__ */ jsx12(
2369
2893
  ProposalCard,
2370
2894
  {
2371
2895
  call,
@@ -2377,16 +2901,16 @@ function ToolCallCard({
2377
2901
  );
2378
2902
  }
2379
2903
  if (kind === "followup" && !failed) {
2380
- return /* @__PURE__ */ jsx10(FollowupCard, { call });
2904
+ return /* @__PURE__ */ jsx12(FollowupCard, { call });
2381
2905
  }
2382
2906
  const isCommand = kind === "command";
2383
- return /* @__PURE__ */ jsxs9(
2907
+ return /* @__PURE__ */ jsxs10(
2384
2908
  "div",
2385
2909
  {
2386
2910
  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
2911
  children: [
2388
- /* @__PURE__ */ jsxs9("div", { className: "flex w-full items-center gap-2 px-3 py-2", children: [
2389
- /* @__PURE__ */ jsxs9(
2912
+ /* @__PURE__ */ jsxs10("div", { className: "flex w-full items-center gap-2 px-3 py-2", children: [
2913
+ /* @__PURE__ */ jsxs10(
2390
2914
  "button",
2391
2915
  {
2392
2916
  type: "button",
@@ -2394,14 +2918,14 @@ function ToolCallCard({
2394
2918
  "aria-expanded": expanded,
2395
2919
  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
2920
  children: [
2397
- /* @__PURE__ */ jsx10(
2921
+ /* @__PURE__ */ jsx12(
2398
2922
  "span",
2399
2923
  {
2400
2924
  className: `h-2 w-2 shrink-0 rounded-full ${call.status === "running" ? "animate-pulse bg-warning" : failed ? "bg-destructive" : "bg-success"}`
2401
2925
  }
2402
2926
  ),
2403
- /* @__PURE__ */ jsx10(ToolGlyph, { name: call.name, className: "h-3.5 w-3.5 shrink-0 text-muted-foreground" }),
2404
- /* @__PURE__ */ jsx10(
2927
+ /* @__PURE__ */ jsx12(ToolGlyph, { name: call.name, className: "h-3.5 w-3.5 shrink-0 text-muted-foreground" }),
2928
+ /* @__PURE__ */ jsx12(
2405
2929
  "span",
2406
2930
  {
2407
2931
  className: `min-w-0 flex-1 truncate ${isCommand ? "font-mono text-[12px] tracking-tight text-foreground/90" : "font-medium"}`,
@@ -2411,8 +2935,8 @@ function ToolCallCard({
2411
2935
  ]
2412
2936
  }
2413
2937
  ),
2414
- /* @__PURE__ */ jsx10("span", { className: "shrink-0 text-[11px] text-muted-foreground", children: call.status === "running" ? "running\u2026" : failed ? "failed" : "done" }),
2415
- /* @__PURE__ */ jsx10(
2938
+ /* @__PURE__ */ jsx12("span", { className: "shrink-0 text-[11px] text-muted-foreground", children: call.status === "running" ? "running\u2026" : failed ? "failed" : "done" }),
2939
+ /* @__PURE__ */ jsx12(
2416
2940
  "button",
2417
2941
  {
2418
2942
  type: "button",
@@ -2420,13 +2944,13 @@ function ToolCallCard({
2420
2944
  "aria-label": expanded ? "Collapse details" : "Expand details",
2421
2945
  "aria-expanded": expanded,
2422
2946
  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" : ""}` })
2947
+ children: /* @__PURE__ */ jsx12(ChevronDown, { className: `h-3 w-3 text-muted-foreground transition-transform ${expanded ? "rotate-180" : ""}` })
2424
2948
  }
2425
2949
  )
2426
2950
  ] }),
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(
2951
+ expanded && /* @__PURE__ */ jsxs10("div", { className: "border-t border-border/40 px-3 py-2.5", children: [
2952
+ custom ?? (call.name === "sandbox_run_command" ? /* @__PURE__ */ jsx12(ShellDetail, { call }) : /* @__PURE__ */ jsx12(DefaultToolDetail, { call })),
2953
+ onOpenRun && call.name.startsWith("sandbox_") && /* @__PURE__ */ jsx12(
2430
2954
  "button",
2431
2955
  {
2432
2956
  type: "button",
@@ -2441,7 +2965,7 @@ function ToolCallCard({
2441
2965
  );
2442
2966
  }
2443
2967
  function StreamingCaret() {
2444
- return /* @__PURE__ */ jsx10(
2968
+ return /* @__PURE__ */ jsx12(
2445
2969
  "span",
2446
2970
  {
2447
2971
  className: "ml-0.5 inline-block h-[1.1em] w-[3px] translate-y-[2px] animate-pulse rounded-sm bg-foreground/70",
@@ -2458,9 +2982,9 @@ function SegmentText({
2458
2982
  const text = useSmoothText(content, streaming);
2459
2983
  const body = useMemo7(() => renderBody(text), [renderBody, text]);
2460
2984
  if (!content.trim() && !showCaret) return null;
2461
- return /* @__PURE__ */ jsxs9("div", { className: "text-base leading-[1.75]", children: [
2985
+ return /* @__PURE__ */ jsxs10("div", { className: "text-base leading-[1.75]", children: [
2462
2986
  body,
2463
- showCaret && /* @__PURE__ */ jsx10(StreamingCaret, {})
2987
+ showCaret && /* @__PURE__ */ jsx12(StreamingCaret, {})
2464
2988
  ] });
2465
2989
  }
2466
2990
  var COLLAPSE_TOOL_RUN_AT = 3;
@@ -2483,7 +3007,7 @@ function SegmentedBody({
2483
3007
  const leftoverToolCalls = (msg.toolCalls ?? []).filter(
2484
3008
  (tc) => !segmentToolIds.has(tc.id)
2485
3009
  );
2486
- const renderToolCard = (call) => /* @__PURE__ */ jsx10(
3010
+ const renderToolCard = (call) => /* @__PURE__ */ jsx12(
2487
3011
  ToolCallCard,
2488
3012
  {
2489
3013
  call,
@@ -2506,9 +3030,9 @@ function SegmentedBody({
2506
3030
  else groups.push({ kind: "tools", index: i, calls: [seg.call] });
2507
3031
  }
2508
3032
  }
2509
- return /* @__PURE__ */ jsxs9("div", { className: "flex flex-col gap-2", children: [
3033
+ return /* @__PURE__ */ jsxs10("div", { className: "flex flex-col gap-2", children: [
2510
3034
  groups.map(
2511
- (g) => g.kind === "text" ? /* @__PURE__ */ jsx10(
3035
+ (g) => g.kind === "text" ? /* @__PURE__ */ jsx12(
2512
3036
  SegmentText,
2513
3037
  {
2514
3038
  content: g.content,
@@ -2517,24 +3041,24 @@ function SegmentedBody({
2517
3041
  renderBody
2518
3042
  },
2519
3043
  `text-${g.index}`
2520
- ) : !streaming && g.calls.length >= COLLAPSE_TOOL_RUN_AT && !g.calls.some(isImportantTool) ? /* @__PURE__ */ jsxs9(
3044
+ ) : !streaming && g.calls.length >= COLLAPSE_TOOL_RUN_AT && !g.calls.some(isImportantTool) ? /* @__PURE__ */ jsxs10(
2521
3045
  "details",
2522
3046
  {
2523
3047
  className: "rounded-lg border-l-2 border-border/70 bg-muted/20 px-3 py-2",
2524
3048
  children: [
2525
- /* @__PURE__ */ jsxs9("summary", { className: "cursor-pointer select-none text-xs font-medium text-muted-foreground", children: [
3049
+ /* @__PURE__ */ jsxs10("summary", { className: "cursor-pointer select-none text-xs font-medium text-muted-foreground", children: [
2526
3050
  "Worked through ",
2527
3051
  g.calls.length,
2528
3052
  " steps"
2529
3053
  ] }),
2530
- /* @__PURE__ */ jsx10("div", { className: "mt-2 flex flex-col gap-2", children: g.calls.map(renderToolCard) })
3054
+ /* @__PURE__ */ jsx12("div", { className: "mt-2 flex flex-col gap-2", children: g.calls.map(renderToolCard) })
2531
3055
  ]
2532
3056
  },
2533
3057
  `tools-${g.index}`
2534
- ) : /* @__PURE__ */ jsx10("div", { className: "flex flex-col gap-2", children: g.calls.map(renderToolCard) }, `tools-${g.index}`)
3058
+ ) : /* @__PURE__ */ jsx12("div", { className: "flex flex-col gap-2", children: g.calls.map(renderToolCard) }, `tools-${g.index}`)
2535
3059
  ),
2536
3060
  leftoverToolCalls.map(renderToolCard),
2537
- streaming && segments[lastIndex]?.kind === "tool" && /* @__PURE__ */ jsx10(StreamingCaret, {})
3061
+ streaming && segments[lastIndex]?.kind === "tool" && /* @__PURE__ */ jsx12(StreamingCaret, {})
2538
3062
  ] });
2539
3063
  }
2540
3064
  function AssistantMessageImpl({
@@ -2546,44 +3070,45 @@ function AssistantMessageImpl({
2546
3070
  approval,
2547
3071
  onToolCallClick,
2548
3072
  toolRenderers,
2549
- renderExtras
3073
+ renderExtras,
3074
+ durableCards
2550
3075
  }) {
2551
3076
  const content = useSmoothText(msg.content, streaming);
2552
3077
  const reasoning = useSmoothText(msg.reasoning ?? "", streaming);
2553
3078
  const body = useMemo7(() => renderBody(content), [renderBody, content]);
2554
3079
  const segments = msg.segments;
2555
3080
  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);
3081
+ const reasoningScrollRef = useRef8(null);
3082
+ const thinkStartRef = useRef8(null);
3083
+ const thinkMsRef = useRef8(null);
2559
3084
  if (streaming && reasoning && !hasAnswerText && thinkStartRef.current === null) {
2560
3085
  thinkStartRef.current = performance.now();
2561
3086
  }
2562
3087
  if (hasAnswerText && thinkStartRef.current !== null && thinkMsRef.current === null) {
2563
3088
  thinkMsRef.current = performance.now() - thinkStartRef.current;
2564
3089
  }
2565
- useEffect5(() => {
3090
+ useEffect8(() => {
2566
3091
  const el = reasoningScrollRef.current;
2567
3092
  if (el && streaming && !hasAnswerText) el.scrollTop = el.scrollHeight;
2568
3093
  }, [reasoning, streaming, hasAnswerText]);
2569
3094
  const thinkingSeconds = useThinkingSeconds(
2570
3095
  streaming && !!reasoning && !hasAnswerText
2571
3096
  );
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) })
3097
+ return /* @__PURE__ */ jsxs10("div", { className: "mx-auto w-full max-w-3xl px-6 py-3", children: [
3098
+ /* @__PURE__ */ jsxs10("div", { className: "mb-1 flex items-baseline gap-2 text-[11px] tracking-wide text-muted-foreground", children: [
3099
+ /* @__PURE__ */ jsx12("span", { className: "font-semibold uppercase", children: agentLabel }),
3100
+ msg.modelUsed && /* @__PURE__ */ jsx12("span", { className: "font-mono normal-case", children: msg.modelUsed }),
3101
+ formatTokensPerSecond(msg) && /* @__PURE__ */ jsx12("span", { children: formatTokensPerSecond(msg) }),
3102
+ formatModelCost(msg, models) && /* @__PURE__ */ jsx12("span", { children: formatModelCost(msg, models) })
2578
3103
  ] }),
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: [
3104
+ 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: [
3105
+ /* @__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
3106
  "Thinking",
2582
3107
  thinkingSeconds >= 3 ? ` \xB7 ${thinkingSeconds}s` : "\u2026"
2583
3108
  ] }) : 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 })
3109
+ /* @__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
3110
  ] }),
2586
- segments && segments.length > 0 ? /* @__PURE__ */ jsx10(
3111
+ segments && segments.length > 0 ? /* @__PURE__ */ jsx12(
2587
3112
  SegmentedBody,
2588
3113
  {
2589
3114
  segments,
@@ -2594,12 +3119,12 @@ function AssistantMessageImpl({
2594
3119
  onToolCallClick,
2595
3120
  toolRenderers
2596
3121
  }
2597
- ) : /* @__PURE__ */ jsxs9(Fragment5, { children: [
2598
- /* @__PURE__ */ jsxs9("div", { className: "text-base leading-[1.75]", children: [
3122
+ ) : /* @__PURE__ */ jsxs10(Fragment5, { children: [
3123
+ /* @__PURE__ */ jsxs10("div", { className: "text-base leading-[1.75]", children: [
2599
3124
  body,
2600
- streaming && content && !msg.toolCalls?.length && /* @__PURE__ */ jsx10(StreamingCaret, {})
3125
+ streaming && content && !msg.toolCalls?.length && /* @__PURE__ */ jsx12(StreamingCaret, {})
2601
3126
  ] }),
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(
3127
+ 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
3128
  ToolCallCard,
2604
3129
  {
2605
3130
  call: tc,
@@ -2611,13 +3136,22 @@ function AssistantMessageImpl({
2611
3136
  tc.id
2612
3137
  )) })
2613
3138
  ] }),
3139
+ durableCards && msg.parts && /* @__PURE__ */ jsx12(
3140
+ DurableChatCards,
3141
+ {
3142
+ ...durableCards,
3143
+ parts: msg.parts,
3144
+ renderMarkdown: renderBody,
3145
+ className: "mt-3"
3146
+ }
3147
+ ),
2614
3148
  renderExtras?.(msg)
2615
3149
  ] });
2616
3150
  }
2617
3151
  var AssistantMessage = memo(AssistantMessageImpl);
2618
3152
  function useThinkingSeconds(active) {
2619
- const [seconds, setSeconds] = useState10(0);
2620
- useEffect5(() => {
3153
+ const [seconds, setSeconds] = useState12(0);
3154
+ useEffect8(() => {
2621
3155
  if (!active) return;
2622
3156
  setSeconds(0);
2623
3157
  const id = setInterval(() => setSeconds((s) => s + 1), 1e3);
@@ -2627,23 +3161,23 @@ function useThinkingSeconds(active) {
2627
3161
  }
2628
3162
  function ThinkingRow({ agentLabel }) {
2629
3163
  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" }) }),
3164
+ return /* @__PURE__ */ jsxs10("div", { className: "mx-auto w-full max-w-3xl px-6 py-3", children: [
3165
+ /* @__PURE__ */ jsx12("p", { className: "mb-1 text-[11px] font-semibold uppercase tracking-wide text-muted-foreground", children: agentLabel }),
3166
+ /* @__PURE__ */ jsxs10("div", { className: "flex items-center gap-2 text-base text-muted-foreground", children: [
3167
+ /* @__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
3168
  "Thinking",
2635
3169
  seconds >= 3 ? ` \xB7 ${seconds}s` : "..."
2636
3170
  ] })
2637
3171
  ] });
2638
3172
  }
2639
3173
  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" })
3174
+ 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: [
3175
+ /* @__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: [
3176
+ /* @__PURE__ */ jsx12("circle", { cx: "12", cy: "12", r: "9" }),
3177
+ /* @__PURE__ */ jsx12("path", { d: "M12 8v4m0 4h.01" })
2644
3178
  ] }),
2645
- /* @__PURE__ */ jsx10("span", { className: "min-w-0 flex-1 break-words", children: message }),
2646
- onRetry && /* @__PURE__ */ jsx10(
3179
+ /* @__PURE__ */ jsx12("span", { className: "min-w-0 flex-1 break-words", children: message }),
3180
+ onRetry && /* @__PURE__ */ jsx12(
2647
3181
  "button",
2648
3182
  {
2649
3183
  type: "button",
@@ -2659,6 +3193,7 @@ function ChatMessages({
2659
3193
  models = [],
2660
3194
  renderMarkdown,
2661
3195
  renderExtras,
3196
+ durableCards,
2662
3197
  userLabel = "User",
2663
3198
  agentLabel = "Agent",
2664
3199
  loading,
@@ -2672,24 +3207,24 @@ function ChatMessages({
2672
3207
  header
2673
3208
  }) {
2674
3209
  const renderBody = useMemo7(
2675
- () => renderMarkdown ?? ((content) => /* @__PURE__ */ jsx10("p", { className: "whitespace-pre-wrap", children: content })),
3210
+ () => renderMarkdown ?? ((content) => /* @__PURE__ */ jsx12("p", { className: "whitespace-pre-wrap", children: content })),
2676
3211
  [renderMarkdown]
2677
3212
  );
2678
3213
  const lastIsUser = messages[messages.length - 1]?.role === "user";
2679
3214
  if (messages.length === 0 && !loading && !error) {
2680
- const empty = renderEmpty ? renderEmpty() : /* @__PURE__ */ jsx10(ChatEmptyState, { ...emptyState });
2681
- return /* @__PURE__ */ jsxs9(Fragment5, { children: [
3215
+ const empty = renderEmpty ? renderEmpty() : /* @__PURE__ */ jsx12(ChatEmptyState, { ...emptyState });
3216
+ return /* @__PURE__ */ jsxs10(Fragment5, { children: [
2682
3217
  header,
2683
3218
  empty
2684
3219
  ] });
2685
3220
  }
2686
- return /* @__PURE__ */ jsxs9(Fragment5, { children: [
3221
+ return /* @__PURE__ */ jsxs10(Fragment5, { children: [
2687
3222
  header,
2688
3223
  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(
3224
+ (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: [
3225
+ /* @__PURE__ */ jsx12("p", { className: "mb-1 text-right text-[11px] font-semibold uppercase tracking-wide text-muted-foreground", children: userLabel }),
3226
+ /* @__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 }) })
3227
+ ] }) }, msg.id) : /* @__PURE__ */ jsx12(
2693
3228
  AssistantMessage,
2694
3229
  {
2695
3230
  msg,
@@ -2700,13 +3235,14 @@ function ChatMessages({
2700
3235
  approval,
2701
3236
  onToolCallClick,
2702
3237
  toolRenderers,
2703
- renderExtras
3238
+ renderExtras,
3239
+ durableCards
2704
3240
  },
2705
3241
  msg.id
2706
3242
  )
2707
3243
  ),
2708
- loading && lastIsUser && /* @__PURE__ */ jsx10(ThinkingRow, { agentLabel }),
2709
- error && !loading && /* @__PURE__ */ jsx10(StreamErrorRow, { message: error, onRetry })
3244
+ loading && lastIsUser && /* @__PURE__ */ jsx12(ThinkingRow, { agentLabel }),
3245
+ error && !loading && /* @__PURE__ */ jsx12(StreamErrorRow, { message: error, onRetry })
2710
3246
  ] });
2711
3247
  }
2712
3248
 
@@ -2719,12 +3255,9 @@ export {
2719
3255
  ModelPicker,
2720
3256
  DEFAULT_EFFORT_LEVELS,
2721
3257
  EffortPicker,
2722
- dispatchChatStreamLine,
2723
- consumeChatStream,
2724
- streamChatTurn,
2725
- ChatComposer,
2726
3258
  interactionStatusLabels,
2727
3259
  interactionTerminalNotes,
3260
+ fieldValuesFromAnswers,
2728
3261
  fieldAnswer,
2729
3262
  buildAnswerData,
2730
3263
  isLateAnswerableStatus,
@@ -2738,12 +3271,27 @@ export {
2738
3271
  InteractionActionButton,
2739
3272
  QuestionOptionList,
2740
3273
  InteractionQuestionCard,
3274
+ DurablePlanCard,
2741
3275
  InteractionPlanCard,
3276
+ durableChatCardsFromParts,
3277
+ DurableChatCards,
3278
+ dispatchChatStreamLine,
3279
+ consumeChatStream,
3280
+ streamChatTurn,
3281
+ ChatComposer,
3282
+ DurablePlanClientError,
3283
+ createDurablePlanDecisionClient,
3284
+ useDurablePlanFlow,
3285
+ createSessionInteractionAttemptStore,
3286
+ createMemoryInteractionAttemptStore,
3287
+ interactionSubmissionSignature,
3288
+ createDurableInteractionAnswerSubmitter,
2742
3289
  upsertChatInteraction,
2743
3290
  cancelChatInteraction,
2744
3291
  resolveChatInteraction,
2745
3292
  terminalizePendingChatInteractions,
2746
3293
  restoreChatInteractions,
3294
+ hydrateChatInteractions,
2747
3295
  useChatInteractions,
2748
3296
  rankFileMentions,
2749
3297
  DEFAULT_MENTION_LIMIT,
@@ -2768,4 +3316,4 @@ export {
2768
3316
  useThinkingSeconds,
2769
3317
  ChatMessages
2770
3318
  };
2771
- //# sourceMappingURL=chunk-KM766NN3.js.map
3319
+ //# sourceMappingURL=chunk-PEUBCJXF.js.map