@tangle-network/agent-app 0.43.40 → 0.43.42

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/dist/assistant/index.d.ts +3 -2
  2. package/dist/assistant/index.js +4 -3
  3. package/dist/assistant/index.js.map +1 -1
  4. package/dist/chat-routes/index.d.ts +19 -5
  5. package/dist/chat-routes/index.js +100 -7
  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-2EO7CPL3.js +191 -0
  11. package/dist/chunk-2EO7CPL3.js.map +1 -0
  12. package/dist/{chunk-Y4QHNQ75.js → chunk-6KVH5SP5.js} +110 -6
  13. package/dist/chunk-6KVH5SP5.js.map +1 -0
  14. package/dist/{chunk-XYWCGFII.js → chunk-B5JD3DXD.js} +48 -2
  15. package/dist/chunk-B5JD3DXD.js.map +1 -0
  16. package/dist/{chunk-I2R2XT4M.js → chunk-I2ATYB7R.js} +18 -2
  17. package/dist/chunk-I2ATYB7R.js.map +1 -0
  18. package/dist/chunk-SIXYZ2FB.js +101 -0
  19. package/dist/chunk-SIXYZ2FB.js.map +1 -0
  20. package/dist/{chunk-4TXDD6P2.js → chunk-XAWFPMAR.js} +38 -3
  21. package/dist/chunk-XAWFPMAR.js.map +1 -0
  22. package/dist/{chunk-D4HB72W2.js → chunk-ZLHK25C3.js} +1890 -1213
  23. package/dist/chunk-ZLHK25C3.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/file-index-Bw_IQE_G.d.ts +194 -0
  32. package/dist/index.d.ts +5 -3
  33. package/dist/index.js +123 -45
  34. package/dist/interactions/index.d.ts +58 -3
  35. package/dist/interactions/index.js +7 -3
  36. package/dist/{parts-BcbitSNp.d.ts → parts-DjX0RRTS.d.ts} +9 -3
  37. package/dist/plans/index.d.ts +69 -0
  38. package/dist/plans/index.js +21 -0
  39. package/dist/plans/index.js.map +1 -0
  40. package/dist/stream/index.js +3 -1
  41. package/dist/teams/index.js +9 -9
  42. package/dist/teams/invitations-api.js +3 -3
  43. package/dist/web-react/index.d.ts +345 -94
  44. package/dist/web-react/index.js +50 -5
  45. package/package.json +11 -1
  46. package/dist/chunk-4TXDD6P2.js.map +0 -1
  47. package/dist/chunk-5SV5PSU7.js +0 -80
  48. package/dist/chunk-5SV5PSU7.js.map +0 -1
  49. package/dist/chunk-D4HB72W2.js.map +0 -1
  50. package/dist/chunk-I2R2XT4M.js.map +0 -1
  51. package/dist/chunk-XYWCGFII.js.map +0 -1
  52. package/dist/chunk-Y4QHNQ75.js.map +0 -1
  53. package/dist/wire-BaUF66AS.d.ts +0 -61
@@ -6,16 +6,22 @@ import {
6
6
  fieldAcceptsFreeText,
7
7
  interactionFromWireRequest,
8
8
  isTerminalInteractionStatus,
9
+ parseInteractionCancel,
9
10
  parseInteractionRequest,
11
+ persistedPartToInteraction,
10
12
  questionInteractionContentSignature
11
- } from "./chunk-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 useMemo6, useRef as useRef6, useState as useState9, 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";
1055
+ return cards;
876
1056
  }
877
- function hasSecretField(fields) {
878
- return fields.some((field) => field.type === "secret");
879
- }
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");
1124
+ if (parsed.kind === "tool_result") {
1125
+ cb.onToolResult?.({
1126
+ toolCallId: parsed.toolCallId,
1127
+ toolName: parsed.toolName,
1128
+ label: parsed.label,
1129
+ outcome: parsed.outcome ?? parsed.result
1130
+ });
1131
+ return { receivedContent: true };
1132
+ }
1133
+ const evt = parsed.kind === "event" ? parsed.event : parsed;
1134
+ if (!evt || typeof evt !== "object") return { receivedContent };
1135
+ switch (evt.type) {
1136
+ case "turn":
1137
+ if (typeof evt.turnId === "string") turnId = evt.turnId;
1138
+ break;
1139
+ case "text":
1140
+ if (typeof evt.text === "string") {
1141
+ cb.onText?.(evt.text);
1142
+ receivedContent = true;
1143
+ }
1144
+ break;
1145
+ case "reasoning":
1146
+ if (typeof evt.text === "string") {
1147
+ cb.onReasoning?.(evt.text);
1148
+ receivedContent = true;
1149
+ }
1150
+ break;
1151
+ case "tool_call": {
1152
+ const call = evt.call ?? evt;
1153
+ cb.onToolCall?.({
1154
+ toolCallId: call.toolCallId ?? call.id,
1155
+ toolName: String(call.toolName ?? call.name ?? "unknown"),
1156
+ args: call.args ?? {}
1157
+ });
1158
+ receivedContent = true;
1159
+ break;
1160
+ }
1161
+ case "tool_result":
1162
+ cb.onToolResult?.({
1163
+ toolCallId: evt.toolCallId,
1164
+ toolName: evt.toolName,
1165
+ label: evt.label,
1166
+ outcome: evt.outcome ?? evt.result
1167
+ });
1168
+ receivedContent = true;
1169
+ break;
1170
+ case "usage": {
1171
+ const u = evt.usage;
1172
+ if (u) cb.onUsage?.({ promptTokens: u.promptTokens ?? 0, completionTokens: u.completionTokens ?? 0 });
1173
+ break;
1174
+ }
1175
+ case "metadata":
1176
+ cb.onMetadata?.(evt.data ?? {});
1177
+ break;
1178
+ case "interaction": {
1179
+ const parsed2 = parseInteractionRequest(evt.data);
1180
+ if (parsed2.succeeded) {
1181
+ cb.onInteraction?.(interactionFromWireRequest(parsed2.value));
1182
+ receivedContent = true;
1183
+ } else {
1184
+ console.error("[chat-stream] dropping malformed interaction line:", parsed2.error);
1185
+ }
1186
+ break;
1187
+ }
1188
+ case "interaction.cancel": {
1189
+ const cancelled = parseInteractionCancel(evt.data);
1190
+ if (cancelled.succeeded) {
1191
+ cb.onInteractionCancel?.(cancelled.value);
1192
+ receivedContent = true;
1193
+ } else {
1194
+ console.error("[chat-stream] dropping malformed interaction.cancel line:", cancelled.error);
1195
+ }
1196
+ break;
1197
+ }
1198
+ case "error": {
1199
+ const data = evt.data;
1200
+ const message = String(data?.message ?? evt.details ?? evt.error ?? "Unknown stream error");
1201
+ if (cb.onErrorEvent) {
1202
+ cb.onErrorEvent(message);
1203
+ } else {
1204
+ console.error("[chat-stream] unhandled stream error event:", message);
1205
+ cb.onText?.(`
1206
+
1207
+ The agent hit an error and this turn stopped: ${message}`);
1208
+ receivedContent = true;
1209
+ }
1210
+ break;
1211
+ }
1212
+ default: {
1213
+ if (typeof evt.type === "string" && evt.type.startsWith("plan.")) {
1214
+ const submitted = parsePlanSubmittedEvent(evt);
1215
+ const planRecord = evt.data?.plan ?? evt.properties?.plan;
1216
+ const plan = submitted.succeeded ? submitted.value : planRecord ? persistedPartToPlan({ type: "plan", ...planRecord }) : null;
1217
+ if (plan) {
1218
+ cb.onPlan?.(plan);
1219
+ receivedContent = true;
1220
+ } else {
1221
+ console.error("[chat-stream] dropping malformed durable plan line:", evt.type);
1222
+ }
1223
+ }
1224
+ break;
1225
+ }
1226
+ }
1227
+ return { turnId, receivedContent };
906
1228
  }
907
- 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 {
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",
1557
+ showFooter && /* @__PURE__ */ jsx8("div", { className: "mb-0.5 flex shrink-0 items-center gap-1.5", children: controls }),
1558
+ isStreaming ? /* @__PURE__ */ jsxs6(
1559
+ "button",
1157
1560
  {
1158
- type: "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
1561
+ type: "button",
1562
+ onClick: onCancel,
1563
+ "aria-label": "Stop response",
1564
+ className: "mb-0.5 inline-flex shrink-0 items-center gap-1.5 rounded-full bg-destructive/15 px-3.5 py-2 text-sm font-medium text-destructive transition hover:bg-destructive/25 focus:outline-none focus-visible:ring-2 focus-visible:ring-destructive/50",
1565
+ children: [
1566
+ /* @__PURE__ */ jsx8(StopGlyph, { className: "h-3.5 w-3.5" }),
1567
+ /* @__PURE__ */ jsx8("span", { children: "Stop" })
1568
+ ]
1165
1569
  }
1166
- )
1167
- ] }) : 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",
1570
+ ) : /* @__PURE__ */ jsxs6(
1571
+ "button",
1170
1572
  {
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
- ] });
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)) ?? {};
1646
+ }
1647
+ function createDurablePlanDecisionClient(options) {
1648
+ const fetchImpl = options.fetchImpl ?? fetch;
1649
+ const urlFor = (input) => typeof options.url === "function" ? options.url(input) : options.url;
1650
+ const read = async (response) => {
1651
+ const body = await responseBody(response);
1652
+ const result = parseDecisionResult(body);
1653
+ if (response.ok && result) return result;
1654
+ const currentPlan = readPlan(body.plan) ?? void 0;
1655
+ const message = typeof body.error === "string" ? body.error : typeof body.message === "string" ? body.message : `Plan request failed (${response.status})`;
1656
+ throw new DurablePlanClientError(
1657
+ message,
1658
+ response.status,
1659
+ typeof body.code === "string" ? body.code : void 0,
1660
+ currentPlan
1661
+ );
1662
+ };
1663
+ return {
1664
+ async current(input) {
1665
+ const rawUrl = urlFor(input);
1666
+ const url = new URL(rawUrl, globalThis.location?.origin ?? "http://localhost");
1667
+ url.searchParams.set("planId", input.planId);
1668
+ if (input.revision !== void 0) url.searchParams.set("revision", String(input.revision));
1669
+ const target = /^https?:/.test(rawUrl) ? url.toString() : `${url.pathname}${url.search}`;
1670
+ return read(await fetchImpl(target, { method: "GET" }));
1671
+ },
1672
+ async decide(input) {
1673
+ const extra = typeof options.body === "function" ? options.body(input) : options.body ?? {};
1674
+ return read(await fetchImpl(urlFor(input), {
1675
+ method: "POST",
1676
+ headers: { "Content-Type": "application/json" },
1677
+ body: JSON.stringify({ ...extra, ...input })
1678
+ }));
1679
+ }
1680
+ };
1238
1681
  }
1239
- 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;
1682
+ function useDurablePlanFlow(options) {
1683
+ const [plan, setPlan] = useState7(options.plan);
1684
+ const [deciding, setDeciding] = useState7(null);
1685
+ const [restoring, setRestoring] = useState7(false);
1686
+ const [error, setError] = useState7(null);
1687
+ const attachments = useRef6(/* @__PURE__ */ new Map());
1688
+ const decisionInFlight = useRef6(false);
1689
+ useEffect6(() => setPlan(options.plan), [options.plan]);
1690
+ const apply = useCallback2(async (result) => {
1691
+ setPlan(result.plan);
1692
+ options.onUpdated?.(result.plan);
1693
+ const receipt = result.followUp;
1694
+ if (!receipt || !options.attachFollowUp) return;
1695
+ let pending = attachments.current.get(receipt.receiptId);
1696
+ if (!pending) {
1697
+ pending = Promise.resolve(options.attachFollowUp(receipt));
1698
+ attachments.current.set(receipt.receiptId, pending);
1699
+ void pending.finally(() => attachments.current.delete(receipt.receiptId));
1270
1700
  }
1271
- 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");
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,51 +1894,204 @@ function cancelChatInteraction(list, cancel) {
1399
1894
  };
1400
1895
  return next;
1401
1896
  }
1402
- function resolveChatInteraction(list, id, status) {
1897
+ function resolveChatInteraction(list, id, status, answers) {
1403
1898
  const index = list.findIndex((item) => item.id === id);
1404
1899
  const existing = list[index];
1405
1900
  if (!existing || existing.status !== "pending") return list;
1406
1901
  const next = [...list];
1407
- next[index] = { ...existing, status };
1902
+ next[index] = { ...existing, status, ...answers ? { answers } : {} };
1408
1903
  return next;
1409
1904
  }
1410
1905
  function terminalizePendingChatInteractions(list, status) {
1411
1906
  if (!list.some((item) => item.status === "pending")) return list;
1412
1907
  return list.map((item) => item.status === "pending" ? { ...item, status } : item);
1413
1908
  }
1414
- function restoreChatInteractions(list, outstanding) {
1415
- 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 };
1961
+ }
1962
+
1963
+ // src/web-react/use-file-mentions.ts
1964
+ import { useCallback as useCallback4, useMemo as useMemo5, useRef as useRef7, useState as useState9 } from "react";
1965
+ var FILE_MENTION_KIND = "file";
1966
+ function toMentionItem(file) {
1967
+ return { id: file.path, label: file.name, detail: file.path, kind: FILE_MENTION_KIND };
1968
+ }
1969
+ function toFileMention(item) {
1970
+ return { path: item.id, name: item.label };
1971
+ }
1972
+ function rankFileMentions(files, query, limit) {
1973
+ const q = query.trim().toLowerCase();
1974
+ if (!q) return files.slice(0, limit);
1975
+ const scored = [];
1976
+ for (const file of files) {
1977
+ const name = file.name.toLowerCase();
1978
+ if (name.startsWith(q)) {
1979
+ scored.push({ file, tier: 0 });
1980
+ continue;
1981
+ }
1982
+ if (name.includes(q)) {
1983
+ scored.push({ file, tier: 1 });
1984
+ continue;
1985
+ }
1986
+ if (file.path.toLowerCase().includes(q)) {
1987
+ scored.push({ file, tier: 2 });
1988
+ }
1989
+ }
1990
+ scored.sort((a, b) => {
1991
+ if (a.tier !== b.tier) return a.tier - b.tier;
1992
+ if (a.file.name.length !== b.file.name.length) return a.file.name.length - b.file.name.length;
1993
+ return a.file.path.localeCompare(b.file.path);
1994
+ });
1995
+ return scored.slice(0, limit).map((s) => s.file);
1996
+ }
1997
+ var RETRY_AFTER_MS = 3e3;
1998
+ var DEFAULT_MENTION_LIMIT = 20;
1999
+ var INDEX_REFRESH_AFTER_MS = 5 * 60 * 1e3;
2000
+ var DEFAULT_MENTION_EMPTY_TEXT = "No matching files";
2001
+ function emptyTextFor(state, fallback) {
2002
+ switch (state.kind) {
2003
+ case "idle":
2004
+ case "loading":
2005
+ return "Loading files\u2026";
2006
+ case "warming":
2007
+ return "Sandbox is starting \u2014 try again in a moment";
2008
+ case "error":
2009
+ return `Couldn't load files: ${state.message}`;
2010
+ case "ready":
2011
+ return fallback;
2012
+ }
2013
+ }
2014
+ function useFileMentions(options) {
2015
+ const {
2016
+ indexUrl,
2017
+ limit = DEFAULT_MENTION_LIMIT,
2018
+ refreshAfterMs = INDEX_REFRESH_AFTER_MS,
2019
+ emptyText = DEFAULT_MENTION_EMPTY_TEXT
2020
+ } = options;
2021
+ const fetchImpl = options.fetchImpl ?? fetch;
2022
+ const [state, setState] = useState9({ kind: "idle" });
2023
+ const stateRef = useRef7(state);
2024
+ stateRef.current = state;
2025
+ const inFlightRef = useRef7(null);
2026
+ const [mentions, setMentions] = useState9([]);
2027
+ const load = useCallback4(() => {
2028
+ if (inFlightRef.current) return inFlightRef.current;
2029
+ if (stateRef.current.kind === "idle") {
2030
+ stateRef.current = { kind: "loading" };
2031
+ setState(stateRef.current);
2032
+ }
2033
+ const attempt = (async () => {
2034
+ let next;
2035
+ try {
2036
+ const res = await fetchImpl(indexUrl);
2037
+ if (!res.ok) {
2038
+ next = { kind: "error", message: `HTTP ${res.status}`, attemptedAt: Date.now() };
2039
+ } else {
2040
+ const body = await res.json();
2041
+ next = body.status === "warming" ? { kind: "warming", attemptedAt: Date.now() } : {
2042
+ kind: "ready",
2043
+ files: body.files,
2044
+ truncated: body.truncated,
2045
+ fetchedAt: Date.now()
2046
+ };
2047
+ }
2048
+ } catch (err) {
2049
+ next = { kind: "error", message: err instanceof Error ? err.message : String(err), attemptedAt: Date.now() };
2050
+ }
2051
+ stateRef.current = next;
2052
+ setState(next);
2053
+ inFlightRef.current = null;
2054
+ return next;
2055
+ })();
2056
+ inFlightRef.current = attempt;
2057
+ return attempt;
2058
+ }, [fetchImpl, indexUrl]);
2059
+ const refresh = useCallback4(async () => {
2060
+ await load();
2061
+ }, [load]);
2062
+ const fetchItems = useCallback4(
2063
+ async (query) => {
2064
+ let current = stateRef.current;
2065
+ if (current.kind === "idle" || current.kind === "loading") {
2066
+ current = await load();
2067
+ } else if (current.kind === "ready") {
2068
+ if (Date.now() - current.fetchedAt > refreshAfterMs) void load();
2069
+ } else if (Date.now() - current.attemptedAt > RETRY_AFTER_MS) {
2070
+ void load();
2071
+ }
2072
+ if (current.kind !== "ready") return [];
2073
+ return rankFileMentions(current.files, query, limit).map(toMentionItem);
2074
+ },
2075
+ [load, limit, refreshAfterMs]
2076
+ );
2077
+ const onMentionsChange = useCallback4((items) => {
2078
+ setMentions(items.filter((item) => item.kind === void 0 || item.kind === FILE_MENTION_KIND).map(toFileMention));
2079
+ }, []);
2080
+ const clearMentions = useCallback4(() => setMentions([]), []);
2081
+ const mention = useMemo5(
2082
+ () => ({
2083
+ fetchItems,
2084
+ onMentionsChange,
2085
+ emptyText: emptyTextFor(state, emptyText)
2086
+ }),
2087
+ [fetchItems, onMentionsChange, state, emptyText]
2088
+ );
2089
+ return { mention, mentions, clearMentions, refresh };
1442
2090
  }
1443
2091
 
1444
2092
  // src/web-react/mission-activity.tsx
1445
- import { useCallback as useCallback3, useEffect as useEffect4, useState as useState7 } from "react";
1446
- 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";
1447
2095
  var LIVE_STATUSES = /* @__PURE__ */ new Set(["pending", "running"]);
1448
2096
  var OK_STATUSES = /* @__PURE__ */ new Set(["completed", "done", "succeeded"]);
1449
2097
  var ERROR_STATUSES = /* @__PURE__ */ new Set(["failed", "error", "cancelled", "aborted"]);
@@ -1490,20 +2138,20 @@ function waterfallLayout(trace) {
1490
2138
  });
1491
2139
  }
1492
2140
  function ChevronGlyph({ className }) {
1493
- return /* @__PURE__ */ 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" }) });
1494
2142
  }
1495
2143
  function RefreshGlyph({ className }) {
1496
- 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" }) });
1497
2145
  }
1498
2146
  function CopyGlyph({ className }) {
1499
- return /* @__PURE__ */ jsxs6("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: [
1500
- /* @__PURE__ */ jsx7("rect", { x: "9", y: "9", width: "13", height: "13", rx: "2" }),
1501
- /* @__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" })
1502
2150
  ] });
1503
2151
  }
1504
2152
  function TraceIdCopy({ traceId }) {
1505
- const [copied, setCopied] = useState7(false);
1506
- const copy = useCallback3(() => {
2153
+ const [copied, setCopied] = useState10(false);
2154
+ const copy = useCallback5(() => {
1507
2155
  void navigator.clipboard?.writeText(traceId).then(
1508
2156
  () => {
1509
2157
  setCopied(true);
@@ -1513,7 +2161,7 @@ function TraceIdCopy({ traceId }) {
1513
2161
  }
1514
2162
  );
1515
2163
  }, [traceId]);
1516
- return /* @__PURE__ */ jsxs6(
2164
+ return /* @__PURE__ */ jsxs7(
1517
2165
  "button",
1518
2166
  {
1519
2167
  type: "button",
@@ -1522,23 +2170,23 @@ function TraceIdCopy({ traceId }) {
1522
2170
  "aria-label": "Copy trace id",
1523
2171
  className: "inline-flex min-w-0 items-center gap-1.5 rounded text-left font-mono text-muted-foreground transition hover:text-foreground focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-card",
1524
2172
  children: [
1525
- /* @__PURE__ */ jsx7("span", { className: "truncate", children: traceId }),
1526
- /* @__PURE__ */ jsx7(CopyGlyph, { className: "h-3 w-3 shrink-0" }),
1527
- 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" })
1528
2176
  ]
1529
2177
  }
1530
2178
  );
1531
2179
  }
1532
2180
  function StatusDot({ tone }) {
1533
- return /* @__PURE__ */ jsxs6("span", { className: "inline-flex items-center", children: [
1534
- /* @__PURE__ */ jsx7(
2181
+ return /* @__PURE__ */ jsxs7("span", { className: "inline-flex items-center", children: [
2182
+ /* @__PURE__ */ jsx9(
1535
2183
  "span",
1536
2184
  {
1537
2185
  "aria-hidden": true,
1538
2186
  className: `h-2 w-2 shrink-0 rounded-full ${tone === "live" ? "animate-pulse bg-warning" : tone === "ok" ? "bg-success" : tone === "error" ? "bg-destructive" : "bg-muted-foreground/40"}`
1539
2187
  }
1540
2188
  ),
1541
- /* @__PURE__ */ jsx7("span", { className: "sr-only", children: tone })
2189
+ /* @__PURE__ */ jsx9("span", { className: "sr-only", children: tone })
1542
2190
  ] });
1543
2191
  }
1544
2192
  var BAR_CLASS = {
@@ -1550,19 +2198,19 @@ function FlowWaterfall({ trace }) {
1550
2198
  const rows = waterfallLayout(trace);
1551
2199
  if (rows.length === 0) return null;
1552
2200
  const cost = formatActivityCost(trace.costUsd);
1553
- return /* @__PURE__ */ jsxs6("div", { className: "space-y-1", children: [
1554
- rows.map((row, i) => /* @__PURE__ */ jsxs6("div", { className: "grid grid-cols-[minmax(0,2fr)_minmax(0,3fr)_auto] items-center gap-2", children: [
1555
- /* @__PURE__ */ jsx7("span", { className: "truncate font-mono text-[11px] text-muted-foreground", title: row.name, children: row.name }),
1556
- /* @__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(
1557
2205
  "div",
1558
2206
  {
1559
2207
  className: `absolute inset-y-0 rounded-sm ${row.ok ? BAR_CLASS[row.kind] : "bg-destructive/80"} ${row.approx ? "opacity-70" : ""}`,
1560
2208
  style: { left: `${row.offsetPct}%`, width: `${row.widthPct}%` }
1561
2209
  }
1562
2210
  ) }),
1563
- /* @__PURE__ */ 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 })
1564
2212
  ] }, i)),
1565
- /* @__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: [
1566
2214
  (trace.totalMs / 1e3).toFixed(1),
1567
2215
  "s",
1568
2216
  cost ? ` \xB7 ${cost}` : ""
@@ -1570,43 +2218,43 @@ function FlowWaterfall({ trace }) {
1570
2218
  ] });
1571
2219
  }
1572
2220
  function MissionActivityLane({ activity, startedAt, nowMs }) {
1573
- const [expanded, setExpanded] = useState7(false);
2221
+ const [expanded, setExpanded] = useState10(false);
1574
2222
  if (activity.length === 0) return null;
1575
- 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: [
1576
2224
  activity.map((run) => {
1577
2225
  const tone = activityTone(run.status);
1578
2226
  const cost = formatActivityCost(run.costUsd);
1579
2227
  const duration = formatActivityDuration(run.durationMs);
1580
- return /* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-2 py-1 text-xs", children: [
1581
- /* @__PURE__ */ jsx7(StatusDot, { tone }),
1582
- /* @__PURE__ */ jsxs6("span", { className: "min-w-0 flex-1 truncate", children: [
1583
- /* @__PURE__ */ jsx7("span", { className: "font-medium", children: run.tool }),
1584
- /* @__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: [
1585
2233
  " \u2014 ",
1586
2234
  run.detail
1587
2235
  ] })
1588
2236
  ] }),
1589
- 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 ") }),
1590
- /* @__PURE__ */ jsxs6("span", { className: "flex shrink-0 items-center gap-1.5 font-mono text-[10px] tabular-nums text-muted-foreground/70", children: [
1591
- tone !== "live" && tone !== "ok" && /* @__PURE__ */ jsx7("span", { children: run.status }),
1592
- cost && /* @__PURE__ */ jsx7("span", { children: cost }),
1593
- 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 })
1594
2242
  ] })
1595
2243
  ] }, run.taskId);
1596
2244
  }),
1597
- /* @__PURE__ */ jsxs6(
2245
+ /* @__PURE__ */ jsxs7(
1598
2246
  "button",
1599
2247
  {
1600
2248
  type: "button",
1601
2249
  onClick: () => setExpanded((v) => !v),
1602
2250
  className: "flex items-center gap-1 py-0.5 text-[10px] font-medium text-muted-foreground/70 transition hover:text-foreground",
1603
2251
  children: [
1604
- /* @__PURE__ */ 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" : ""}` }),
1605
2253
  "timeline"
1606
2254
  ]
1607
2255
  }
1608
2256
  ),
1609
- 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(
1610
2258
  FlowWaterfall,
1611
2259
  {
1612
2260
  trace: stepActivityFlowTrace(activity, {
@@ -1621,45 +2269,45 @@ function ActivityRow({
1621
2269
  record,
1622
2270
  renderMissionRef
1623
2271
  }) {
1624
- const [open, setOpen] = useState7(false);
2272
+ const [open, setOpen] = useState10(false);
1625
2273
  const tone = activityTone(record.status);
1626
2274
  const cost = formatActivityCost(record.costUsd);
1627
2275
  const duration = formatActivityDuration(record.durationMs);
1628
- return /* @__PURE__ */ jsxs6("div", { className: "rounded-lg border border-border/60 bg-card", children: [
1629
- /* @__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: [
1630
- /* @__PURE__ */ jsx7(StatusDot, { tone }),
1631
- /* @__PURE__ */ jsxs6("span", { className: "min-w-0 flex-1 truncate", children: [
1632
- /* @__PURE__ */ jsx7("span", { className: "font-medium", children: record.tool }),
1633
- /* @__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: [
1634
2282
  " \u2014 ",
1635
2283
  record.detail
1636
2284
  ] })
1637
2285
  ] }),
1638
- 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 ") }),
1639
- /* @__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(
1640
2288
  "span",
1641
2289
  {
1642
2290
  className: `shrink-0 rounded-full px-2 py-0.5 text-[10px] font-medium ${tone === "ok" ? "bg-success/10 text-success" : tone === "error" ? "bg-destructive/10 text-destructive" : tone === "live" ? "bg-warning/10 text-warning" : "bg-muted/60 text-muted-foreground"}`,
1643
2291
  children: record.status
1644
2292
  }
1645
2293
  ),
1646
- cost && /* @__PURE__ */ jsx7("span", { className: "shrink-0 font-mono text-[11px] tabular-nums text-muted-foreground", children: cost }),
1647
- /* @__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" : ""}` })
1648
2296
  ] }),
1649
- open && /* @__PURE__ */ jsxs6("div", { className: "space-y-2.5 border-t border-border/40 px-3 py-2.5", children: [
1650
- 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]) }) }),
1651
- /* @__PURE__ */ jsxs6("dl", { className: "grid grid-cols-[auto_1fr] gap-x-3 gap-y-1 font-mono text-[11px]", children: [
1652
- /* @__PURE__ */ jsx7("dt", { className: "text-muted-foreground/60", children: "task" }),
1653
- /* @__PURE__ */ jsx7("dd", { className: "truncate text-muted-foreground", children: record.taskId }),
1654
- /* @__PURE__ */ jsx7("dt", { className: "text-muted-foreground/60", children: "started" }),
1655
- /* @__PURE__ */ jsx7("dd", { className: "text-muted-foreground", children: new Date(record.startedAt).toLocaleString() }),
1656
- duration && /* @__PURE__ */ jsxs6(Fragment4, { children: [
1657
- /* @__PURE__ */ jsx7("dt", { className: "text-muted-foreground/60", children: "duration" }),
1658
- /* @__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 })
1659
2307
  ] }),
1660
- record.traceId && /* @__PURE__ */ jsxs6(Fragment4, { children: [
1661
- /* @__PURE__ */ jsx7("dt", { className: "text-muted-foreground/60", children: "trace" }),
1662
- /* @__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 }) })
1663
2311
  ] })
1664
2312
  ] }),
1665
2313
  record.missionRef && renderMissionRef?.(record.missionRef, record)
@@ -1667,11 +2315,11 @@ function ActivityRow({
1667
2315
  ] });
1668
2316
  }
1669
2317
  function AgentActivityPanel({ fetchActivity, renderMissionRef, title = "Agent activity", emptyLabel = "No agent runs yet." }) {
1670
- const [rows, setRows] = useState7([]);
1671
- const [cursor, setCursor] = useState7(void 0);
1672
- const [loading, setLoading] = useState7(false);
1673
- const [error, setError] = useState7(null);
1674
- const load = useCallback3(
2318
+ const [rows, setRows] = useState10([]);
2319
+ const [cursor, setCursor] = useState10(void 0);
2320
+ const [loading, setLoading] = useState10(false);
2321
+ const [error, setError] = useState10(null);
2322
+ const load = useCallback5(
1675
2323
  async (from) => {
1676
2324
  setLoading(true);
1677
2325
  setError(null);
@@ -1687,13 +2335,13 @@ function AgentActivityPanel({ fetchActivity, renderMissionRef, title = "Agent ac
1687
2335
  },
1688
2336
  [fetchActivity]
1689
2337
  );
1690
- useEffect4(() => {
2338
+ useEffect7(() => {
1691
2339
  void load();
1692
2340
  }, [load]);
1693
- return /* @__PURE__ */ jsxs6("div", { className: "space-y-2", children: [
1694
- /* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-2", children: [
1695
- /* @__PURE__ */ jsx7("h2", { className: "flex-1 text-sm font-semibold", children: title }),
1696
- /* @__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(
1697
2345
  "button",
1698
2346
  {
1699
2347
  type: "button",
@@ -1701,14 +2349,14 @@ function AgentActivityPanel({ fetchActivity, renderMissionRef, title = "Agent ac
1701
2349
  disabled: loading,
1702
2350
  "aria-label": "Refresh",
1703
2351
  className: "rounded-md p-1.5 text-muted-foreground transition hover:bg-accent/30 hover:text-foreground disabled:opacity-50",
1704
- children: /* @__PURE__ */ 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" : ""}` })
1705
2353
  }
1706
2354
  )
1707
2355
  ] }),
1708
- 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 }),
1709
- !error && rows.length === 0 && !loading && /* @__PURE__ */ jsx7("p", { className: "px-1 text-sm text-muted-foreground", children: emptyLabel }),
1710
- /* @__PURE__ */ jsx7("div", { className: "space-y-1.5", children: rows.map((record) => /* @__PURE__ */ jsx7(ActivityRow, { record, renderMissionRef }, record.taskId)) }),
1711
- 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(
1712
2360
  "button",
1713
2361
  {
1714
2362
  type: "button",
@@ -1722,9 +2370,9 @@ function AgentActivityPanel({ fetchActivity, renderMissionRef, title = "Agent ac
1722
2370
  }
1723
2371
 
1724
2372
  // src/web-react/seat-paywall.tsx
1725
- import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
2373
+ import { jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
1726
2374
  function CheckGlyph3() {
1727
- return /* @__PURE__ */ jsx8(
2375
+ return /* @__PURE__ */ jsx10(
1728
2376
  "svg",
1729
2377
  {
1730
2378
  className: "h-4 w-4 shrink-0 text-primary",
@@ -1735,14 +2383,14 @@ function CheckGlyph3() {
1735
2383
  strokeLinecap: "round",
1736
2384
  strokeLinejoin: "round",
1737
2385
  "aria-hidden": true,
1738
- children: /* @__PURE__ */ jsx8("path", { d: "M20 6 9 17l-5-5" })
2386
+ children: /* @__PURE__ */ jsx10("path", { d: "M20 6 9 17l-5-5" })
1739
2387
  }
1740
2388
  );
1741
2389
  }
1742
2390
  function Benefit({ children }) {
1743
- return /* @__PURE__ */ jsxs7("li", { className: "flex items-start gap-2.5 text-sm text-foreground", children: [
1744
- /* @__PURE__ */ jsx8("span", { className: "mt-0.5", children: /* @__PURE__ */ jsx8(CheckGlyph3, {}) }),
1745
- /* @__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 })
1746
2394
  ] });
1747
2395
  }
1748
2396
  function SeatPaywall({
@@ -1756,30 +2404,30 @@ function SeatPaywall({
1756
2404
  footnote
1757
2405
  }) {
1758
2406
  const { pending, run } = usePending();
1759
- return /* @__PURE__ */ 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: [
1760
- /* @__PURE__ */ jsx8("p", { className: "text-xs font-medium uppercase tracking-wide text-muted-foreground", children: product }),
1761
- /* @__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: [
1762
2410
  "Unlock ",
1763
2411
  product
1764
2412
  ] }),
1765
- tagline && /* @__PURE__ */ jsx8("p", { className: "mt-2 text-sm text-muted-foreground", children: tagline }),
1766
- /* @__PURE__ */ jsxs7("div", { className: "mt-6 flex items-baseline gap-1.5", children: [
1767
- /* @__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: [
1768
2416
  "$",
1769
2417
  priceUsd
1770
2418
  ] }),
1771
- /* @__PURE__ */ jsx8("span", { className: "text-sm text-muted-foreground", children: "/mo" })
2419
+ /* @__PURE__ */ jsx10("span", { className: "text-sm text-muted-foreground", children: "/mo" })
1772
2420
  ] }),
1773
- /* @__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: [
1774
2422
  "Includes $",
1775
2423
  includedUsageUsd,
1776
2424
  "/mo of AI usage"
1777
2425
  ] }),
1778
- /* @__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 ?? [
1779
2427
  `Full access to ${product}`,
1780
2428
  `$${includedUsageUsd}/mo of AI usage included, every month`
1781
- ]).map((benefit, i) => /* @__PURE__ */ jsx8(Benefit, { children: benefit }, i)) }),
1782
- /* @__PURE__ */ jsx8(
2429
+ ]).map((benefit, i) => /* @__PURE__ */ jsx10(Benefit, { children: benefit }, i)) }),
2430
+ /* @__PURE__ */ jsx10(
1783
2431
  "button",
1784
2432
  {
1785
2433
  type: "button",
@@ -1789,13 +2437,13 @@ function SeatPaywall({
1789
2437
  children: pending ? "Opening checkout\u2026" : ctaLabel ?? `Unlock ${product}`
1790
2438
  }
1791
2439
  ),
1792
- footnote && /* @__PURE__ */ 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 })
1793
2441
  ] }) });
1794
2442
  }
1795
2443
 
1796
2444
  // src/web-react/agent-session-controls.tsx
1797
- import { useMemo as useMemo5, useState as useState8 } from "react";
1798
- 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";
1799
2447
  var HARNESS_LABELS = {
1800
2448
  opencode: "OpenCode (any model)",
1801
2449
  "claude-code": "Claude Code (Anthropic)",
@@ -1815,12 +2463,12 @@ function harnessLabel(h) {
1815
2463
  return HARNESS_LABELS[h] ?? h;
1816
2464
  }
1817
2465
  function ChevronDown2({ className }) {
1818
- return /* @__PURE__ */ 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" }) });
1819
2467
  }
1820
2468
  function GearGlyph({ className }) {
1821
- return /* @__PURE__ */ jsxs8("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: [
1822
- /* @__PURE__ */ jsx9("circle", { cx: "12", cy: "12", r: "3" }),
1823
- /* @__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" })
1824
2472
  ] });
1825
2473
  }
1826
2474
  var FOCUS_RING = "focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background";
@@ -1829,11 +2477,11 @@ function HarnessPicker({
1829
2477
  onChange,
1830
2478
  available
1831
2479
  }) {
1832
- const [open, setOpen] = useState8(false);
2480
+ const [open, setOpen] = useState11(false);
1833
2481
  const { containerRef, triggerProps } = usePopover(open, setOpen);
1834
2482
  const options = available ?? Object.keys(HARNESS_LABELS);
1835
- return /* @__PURE__ */ jsxs8("div", { ref: containerRef, className: "relative inline-flex", children: [
1836
- /* @__PURE__ */ jsxs8(
2483
+ return /* @__PURE__ */ jsxs9("div", { ref: containerRef, className: "relative inline-flex", children: [
2484
+ /* @__PURE__ */ jsxs9(
1837
2485
  "button",
1838
2486
  {
1839
2487
  type: "button",
@@ -1842,12 +2490,12 @@ function HarnessPicker({
1842
2490
  title: "Agent backend",
1843
2491
  className: `inline-flex w-full items-center justify-between gap-1.5 rounded-lg border border-border bg-card px-3 py-1.5 text-sm font-medium text-foreground transition hover:bg-accent/30 ${FOCUS_RING}`,
1844
2492
  children: [
1845
- /* @__PURE__ */ jsx9("span", { className: "truncate", children: harnessLabel(value) }),
1846
- /* @__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" })
1847
2495
  ]
1848
2496
  }
1849
2497
  ),
1850
- 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(
1851
2499
  "button",
1852
2500
  {
1853
2501
  type: "button",
@@ -1866,7 +2514,7 @@ function HarnessPicker({
1866
2514
  }
1867
2515
  function useCoherentHandlers(props) {
1868
2516
  const { model, models, harness, onModelChange, onHarnessChange } = props;
1869
- const canonicalIds = useMemo5(() => models.map((m) => m.id), [models]);
2517
+ const canonicalIds = useMemo6(() => models.map((m) => m.id), [models]);
1870
2518
  const onModel = (next) => {
1871
2519
  onModelChange(next);
1872
2520
  const nextHarness = snapHarnessToModel(harness, next);
@@ -1894,11 +2542,11 @@ function AgentSessionControls(props) {
1894
2542
  className
1895
2543
  } = props;
1896
2544
  const { onModel, onHarness } = useCoherentHandlers(props);
1897
- const [open, setOpen] = useState8(false);
2545
+ const [open, setOpen] = useState11(false);
1898
2546
  const { containerRef: popoverRef, triggerProps } = usePopover(open, setOpen);
1899
2547
  const selectedModel = models.find((m) => m.id === model);
1900
2548
  const showEffort = selectedModel?.supportsReasoning ?? true;
1901
- const modelPicker = /* @__PURE__ */ jsx9(
2549
+ const modelPicker = /* @__PURE__ */ jsx11(
1902
2550
  ModelPicker,
1903
2551
  {
1904
2552
  value: model,
@@ -1909,17 +2557,17 @@ function AgentSessionControls(props) {
1909
2557
  }
1910
2558
  );
1911
2559
  if (layout === "inline") {
1912
- return /* @__PURE__ */ 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: [
1913
2561
  modelPicker,
1914
- showHarness && /* @__PURE__ */ jsx9(HarnessPicker, { value: harness, onChange: onHarness, available: availableHarnesses }),
1915
- 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 })
1916
2564
  ] });
1917
2565
  }
1918
2566
  const hasAdvanced = showHarness || showEffort;
1919
- 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: [
1920
2568
  modelPicker,
1921
- hasAdvanced && /* @__PURE__ */ jsxs8("div", { ref: popoverRef, className: "relative inline-flex", children: [
1922
- /* @__PURE__ */ jsx9(
2569
+ hasAdvanced && /* @__PURE__ */ jsxs9("div", { ref: popoverRef, className: "relative inline-flex", children: [
2570
+ /* @__PURE__ */ jsx11(
1923
2571
  "button",
1924
2572
  {
1925
2573
  type: "button",
@@ -1928,19 +2576,19 @@ function AgentSessionControls(props) {
1928
2576
  title: "Model settings \u2014 pick the agent backend and how hard it thinks",
1929
2577
  className: `flex h-8 w-8 items-center justify-center rounded-full text-muted-foreground transition-colors hover:bg-muted hover:text-foreground data-[state=open]:bg-muted ${FOCUS_RING}`,
1930
2578
  "data-state": open ? "open" : "closed",
1931
- children: /* @__PURE__ */ jsx9(GearGlyph, { className: "h-4 w-4" })
2579
+ children: /* @__PURE__ */ jsx11(GearGlyph, { className: "h-4 w-4" })
1932
2580
  }
1933
2581
  ),
1934
- 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: [
1935
- showHarness && /* @__PURE__ */ jsxs8("div", { className: "space-y-1.5", children: [
1936
- /* @__PURE__ */ jsx9("p", { className: "text-xs font-medium text-foreground", children: "Agent backend" }),
1937
- /* @__PURE__ */ jsx9(HarnessPicker, { value: harness, onChange: onHarness, available: availableHarnesses }),
1938
- /* @__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." })
1939
2587
  ] }),
1940
- showEffort && /* @__PURE__ */ jsxs8("div", { className: "space-y-1.5", children: [
1941
- /* @__PURE__ */ jsx9("p", { className: "text-xs font-medium text-foreground", children: "Thinking" }),
1942
- /* @__PURE__ */ jsx9(EffortPicker, { value: effort, onChange: onEffortChange, label: "" }),
1943
- /* @__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." })
1944
2592
  ] })
1945
2593
  ] })
1946
2594
  ] })
@@ -1948,7 +2596,7 @@ function AgentSessionControls(props) {
1948
2596
  }
1949
2597
 
1950
2598
  // src/web-react/index.tsx
1951
- import { Fragment as Fragment5, jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
2599
+ import { Fragment as Fragment5, jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
1952
2600
  function formatModelCost(msg, models) {
1953
2601
  if (msg.promptTokens == null && msg.completionTokens == null) return null;
1954
2602
  const pricing = models.find((m) => m.id === msg.modelUsed)?.pricing;
@@ -1962,41 +2610,41 @@ function formatTokensPerSecond(msg) {
1962
2610
  return `${Math.round(msg.completionTokens / (msg.durationMs / 1e3))} tok/s`;
1963
2611
  }
1964
2612
  function RunDrillIn({ run, onClose }) {
1965
- return /* @__PURE__ */ 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: [
1966
- /* @__PURE__ */ jsxs9("div", { className: "flex items-center gap-2 border-b border-border px-4 py-3", children: [
1967
- /* @__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(
1968
2616
  "span",
1969
2617
  {
1970
2618
  className: `h-2 w-2 shrink-0 rounded-full ${run.status === "running" ? "bg-warning" : run.status === "error" ? "bg-destructive" : "bg-success"}`
1971
2619
  }
1972
2620
  ),
1973
- /* @__PURE__ */ jsxs9("div", { className: "min-w-0 flex-1", children: [
1974
- /* @__PURE__ */ jsx10("p", { className: "truncate text-sm font-semibold", children: run.title }),
1975
- /* @__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 })
1976
2624
  ] }),
1977
- /* @__PURE__ */ jsx10(
2625
+ /* @__PURE__ */ jsx12(
1978
2626
  "button",
1979
2627
  {
1980
2628
  type: "button",
1981
2629
  onClick: onClose,
1982
2630
  "aria-label": "Close",
1983
2631
  className: "rounded-md p-1.5 text-muted-foreground transition hover:bg-accent/30 hover:text-foreground",
1984
- children: /* @__PURE__ */ 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" }) })
1985
2633
  }
1986
2634
  )
1987
2635
  ] }),
1988
- /* @__PURE__ */ jsxs9("div", { className: "flex-1 space-y-3 overflow-y-auto p-4", children: [
1989
- run.steps.length === 0 && /* @__PURE__ */ jsx10("p", { className: "text-sm text-muted-foreground", children: "No steps recorded yet." }),
1990
- run.steps.map((step, i) => /* @__PURE__ */ jsxs9("div", { className: "rounded-lg border border-border/60 bg-background", children: [
1991
- /* @__PURE__ */ jsxs9("div", { className: "flex items-baseline gap-2 border-b border-border/40 px-3 py-1.5", children: [
1992
- /* @__PURE__ */ jsx10("span", { className: `font-mono text-[11px] ${step.status === "error" ? "text-destructive" : "text-muted-foreground"}`, children: step.status === "error" ? "\u2717" : "$" }),
1993
- /* @__PURE__ */ jsx10("code", { className: "min-w-0 flex-1 truncate font-mono text-xs", children: step.label }),
1994
- /* @__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() })
1995
2643
  ] }),
1996
- 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 })
1997
2645
  ] }, i))
1998
2646
  ] }),
1999
- /* @__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." })
2000
2648
  ] });
2001
2649
  }
2002
2650
  function pendingApprovalOf(call) {
@@ -2010,20 +2658,20 @@ function ChatEmptyState({
2010
2658
  subline = "Describe the outcome you want. The agent works through it step by step, and pauses for your approval before anything irreversible.",
2011
2659
  doors
2012
2660
  }) {
2013
- return /* @__PURE__ */ 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: [
2014
- /* @__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" }) }),
2015
- /* @__PURE__ */ jsx10("p", { className: "text-[11px] font-semibold uppercase tracking-[0.18em] text-muted-foreground", children: productName }),
2016
- /* @__PURE__ */ jsx10("h2", { className: "mt-1.5 text-balance text-2xl font-semibold leading-tight text-foreground sm:text-[28px]", children: headline }),
2017
- subline && /* @__PURE__ */ jsx10("p", { className: "mt-3 max-w-md text-[15px] leading-relaxed text-muted-foreground", children: subline }),
2018
- 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(
2019
2667
  "button",
2020
2668
  {
2021
2669
  type: "button",
2022
2670
  onClick: door.onSelect,
2023
2671
  className: "group flex min-h-[44px] flex-col items-start rounded-xl border border-border bg-card px-4 py-3 text-left transition hover:border-primary/40 hover:bg-accent/30 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
2024
2672
  children: [
2025
- /* @__PURE__ */ jsx10("span", { className: "text-sm font-semibold text-foreground", children: door.label }),
2026
- 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 })
2027
2675
  ]
2028
2676
  },
2029
2677
  i
@@ -2032,26 +2680,26 @@ function ChatEmptyState({
2032
2680
  }
2033
2681
  function ToolGlyph({ name, className }) {
2034
2682
  if (name.startsWith("sandbox_")) {
2035
- return /* @__PURE__ */ jsxs9("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: [
2036
- /* @__PURE__ */ jsx10("polyline", { points: "4 17 10 11 4 5" }),
2037
- /* @__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" })
2038
2686
  ] });
2039
2687
  }
2040
2688
  if (name === "submit_proposal") {
2041
- return /* @__PURE__ */ jsxs9("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: [
2042
- /* @__PURE__ */ jsx10("path", { d: "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" }),
2043
- /* @__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" })
2044
2692
  ] });
2045
2693
  }
2046
2694
  if (name === "schedule_followup") {
2047
- return /* @__PURE__ */ jsxs9("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", "aria-hidden": true, children: [
2048
- /* @__PURE__ */ jsx10("circle", { cx: "12", cy: "12", r: "9" }),
2049
- /* @__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" })
2050
2698
  ] });
2051
2699
  }
2052
- return /* @__PURE__ */ jsxs9("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: [
2053
- /* @__PURE__ */ jsx10("path", { d: "M12 3v3m0 12v3M3 12h3m12 0h3" }),
2054
- /* @__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" })
2055
2703
  ] });
2056
2704
  }
2057
2705
  function toolOutcomeOf(call) {
@@ -2118,36 +2766,36 @@ function truncate(v, max = 240) {
2118
2766
  function KvRows({ data }) {
2119
2767
  const entries = Object.entries(data).filter(([, v]) => v !== void 0 && v !== null && v !== "");
2120
2768
  if (!entries.length) return null;
2121
- return /* @__PURE__ */ 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: [
2122
- /* @__PURE__ */ jsx10("dt", { className: "font-mono text-[11px] text-muted-foreground", children: k }),
2123
- /* @__PURE__ */ jsx10("dd", { className: "min-w-0 whitespace-pre-wrap break-words font-mono text-[11px] text-muted-foreground", children: truncate(v) })
2769
+ return /* @__PURE__ */ jsx12("dl", { className: "grid grid-cols-[auto_1fr] gap-x-3 gap-y-1", children: entries.map(([k, v]) => /* @__PURE__ */ jsxs10("div", { className: "contents", children: [
2770
+ /* @__PURE__ */ jsx12("dt", { className: "font-mono text-[11px] text-muted-foreground", children: k }),
2771
+ /* @__PURE__ */ jsx12("dd", { className: "min-w-0 whitespace-pre-wrap break-words font-mono text-[11px] text-muted-foreground", children: truncate(v) })
2124
2772
  ] }, k)) });
2125
2773
  }
2126
2774
  function ShellDetail({ call }) {
2127
2775
  const outcome = toolOutcomeOf(call);
2128
2776
  const r = outcome?.result ?? {};
2129
- return /* @__PURE__ */ jsxs9("div", { className: "overflow-hidden rounded-md bg-zinc-900 font-mono text-[11px] leading-relaxed", children: [
2130
- /* @__PURE__ */ jsxs9("div", { className: "flex items-center gap-2 px-3 pt-2 text-zinc-400", children: [
2131
- /* @__PURE__ */ jsx10("span", { className: "select-none text-zinc-500", children: "$" }),
2132
- /* @__PURE__ */ jsx10("span", { className: "min-w-0 flex-1 truncate text-zinc-200", children: String(call.args?.command ?? "") }),
2133
- r.exitCode != null && /* @__PURE__ */ jsxs9("span", { className: r.exitCode === 0 ? "text-success" : "text-destructive", children: [
2777
+ return /* @__PURE__ */ jsxs10("div", { className: "overflow-hidden rounded-md bg-zinc-900 font-mono text-[11px] leading-relaxed", children: [
2778
+ /* @__PURE__ */ jsxs10("div", { className: "flex items-center gap-2 px-3 pt-2 text-zinc-400", children: [
2779
+ /* @__PURE__ */ jsx12("span", { className: "select-none text-zinc-500", children: "$" }),
2780
+ /* @__PURE__ */ jsx12("span", { className: "min-w-0 flex-1 truncate text-zinc-200", children: String(call.args?.command ?? "") }),
2781
+ r.exitCode != null && /* @__PURE__ */ jsxs10("span", { className: r.exitCode === 0 ? "text-success" : "text-destructive", children: [
2134
2782
  "exit ",
2135
2783
  r.exitCode
2136
2784
  ] })
2137
2785
  ] }),
2138
- /* @__PURE__ */ jsx10("pre", { className: "max-h-56 overflow-auto whitespace-pre-wrap px-3 pb-2.5 pt-1.5 text-zinc-300", children: outcome?.ok === false ? outcome.message ?? "failed" : [r.stdout, r.stderr].filter(Boolean).join("\n") || "(no output)" })
2786
+ /* @__PURE__ */ jsx12("pre", { className: "max-h-56 overflow-auto whitespace-pre-wrap px-3 pb-2.5 pt-1.5 text-zinc-300", children: outcome?.ok === false ? outcome.message ?? "failed" : [r.stdout, r.stderr].filter(Boolean).join("\n") || "(no output)" })
2139
2787
  ] });
2140
2788
  }
2141
2789
  function DefaultToolDetail({ call }) {
2142
2790
  const outcome = toolOutcomeOf(call);
2143
- return /* @__PURE__ */ jsxs9("div", { className: "space-y-2", children: [
2144
- call.args && Object.keys(call.args).length > 0 && /* @__PURE__ */ jsxs9("div", { children: [
2145
- /* @__PURE__ */ jsx10("p", { className: "mb-1 text-[10px] font-semibold uppercase tracking-wide text-muted-foreground", children: "Called with" }),
2146
- /* @__PURE__ */ jsx10(KvRows, { data: call.args })
2791
+ return /* @__PURE__ */ jsxs10("div", { className: "space-y-2", children: [
2792
+ call.args && Object.keys(call.args).length > 0 && /* @__PURE__ */ jsxs10("div", { children: [
2793
+ /* @__PURE__ */ jsx12("p", { className: "mb-1 text-[10px] font-semibold uppercase tracking-wide text-muted-foreground", children: "Called with" }),
2794
+ /* @__PURE__ */ jsx12(KvRows, { data: call.args })
2147
2795
  ] }),
2148
- outcome && /* @__PURE__ */ jsxs9("div", { children: [
2149
- /* @__PURE__ */ jsx10("p", { className: "mb-1 text-[10px] font-semibold uppercase tracking-wide text-muted-foreground", children: outcome.ok === false ? "Failed" : "Result" }),
2150
- outcome.ok === false ? /* @__PURE__ */ jsx10("p", { className: "text-xs text-destructive", children: outcome.message ?? "Tool failed" }) : outcome.result && typeof outcome.result === "object" ? /* @__PURE__ */ jsx10(KvRows, { data: outcome.result }) : /* @__PURE__ */ jsx10("p", { className: "font-mono text-[11px] text-muted-foreground", children: truncate(outcome.result) })
2796
+ outcome && /* @__PURE__ */ jsxs10("div", { children: [
2797
+ /* @__PURE__ */ jsx12("p", { className: "mb-1 text-[10px] font-semibold uppercase tracking-wide text-muted-foreground", children: outcome.ok === false ? "Failed" : "Result" }),
2798
+ outcome.ok === false ? /* @__PURE__ */ jsx12("p", { className: "text-xs text-destructive", children: outcome.message ?? "Tool failed" }) : outcome.result && typeof outcome.result === "object" ? /* @__PURE__ */ jsx12(KvRows, { data: outcome.result }) : /* @__PURE__ */ jsx12("p", { className: "font-mono text-[11px] text-muted-foreground", children: truncate(outcome.result) })
2151
2799
  ] })
2152
2800
  ] });
2153
2801
  }
@@ -2158,23 +2806,23 @@ function ProposalCard({
2158
2806
  approval,
2159
2807
  renderers
2160
2808
  }) {
2161
- const [expanded, setExpanded] = useState9(false);
2809
+ const [expanded, setExpanded] = useState12(false);
2162
2810
  const { summary, meta } = proposalPreview(call);
2163
2811
  const custom = renderers?.[call.name]?.(call, message);
2164
2812
  const { pending: deciding, run: decide } = usePending();
2165
- return /* @__PURE__ */ 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: [
2166
- /* @__PURE__ */ jsxs9("div", { className: "flex items-start gap-2.5 px-4 pt-3.5", children: [
2167
- /* @__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" }) }),
2168
- /* @__PURE__ */ jsxs9("div", { className: "min-w-0 flex-1", children: [
2169
- /* @__PURE__ */ jsx10("p", { className: "text-[10px] font-semibold uppercase tracking-wider text-warning-foreground", children: "Needs your approval" }),
2170
- /* @__PURE__ */ jsx10("p", { className: "mt-0.5 text-[15px] font-semibold leading-snug text-foreground", children: friendlyToolTitle(call) }),
2171
- summary && /* @__PURE__ */ jsx10("p", { className: "mt-1 text-[13px] leading-relaxed text-muted-foreground", children: summary }),
2172
- meta.length > 0 && /* @__PURE__ */ jsx10("div", { className: "mt-1.5 flex flex-wrap items-center gap-1.5", children: meta.map((m, i) => /* @__PURE__ */ jsx10("span", { className: "rounded-full bg-muted/60 px-2 py-0.5 text-[11px] font-medium text-muted-foreground", children: m }, i)) })
2813
+ return /* @__PURE__ */ jsxs10("div", { className: "w-full max-w-full rounded-xl border border-warning/50 bg-warning/[0.06] text-sm shadow-sm ring-1 ring-warning/10", children: [
2814
+ /* @__PURE__ */ jsxs10("div", { className: "flex items-start gap-2.5 px-4 pt-3.5", children: [
2815
+ /* @__PURE__ */ jsx12("span", { className: "mt-0.5 inline-flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-warning/15 text-warning", children: /* @__PURE__ */ jsx12(ToolGlyph, { name: call.name, className: "h-3.5 w-3.5" }) }),
2816
+ /* @__PURE__ */ jsxs10("div", { className: "min-w-0 flex-1", children: [
2817
+ /* @__PURE__ */ jsx12("p", { className: "text-[10px] font-semibold uppercase tracking-wider text-warning-foreground", children: "Needs your approval" }),
2818
+ /* @__PURE__ */ jsx12("p", { className: "mt-0.5 text-[15px] font-semibold leading-snug text-foreground", children: friendlyToolTitle(call) }),
2819
+ summary && /* @__PURE__ */ jsx12("p", { className: "mt-1 text-[13px] leading-relaxed text-muted-foreground", children: summary }),
2820
+ meta.length > 0 && /* @__PURE__ */ jsx12("div", { className: "mt-1.5 flex flex-wrap items-center gap-1.5", children: meta.map((m, i) => /* @__PURE__ */ jsx12("span", { className: "rounded-full bg-muted/60 px-2 py-0.5 text-[11px] font-medium text-muted-foreground", children: m }, i)) })
2173
2821
  ] })
2174
2822
  ] }),
2175
- /* @__PURE__ */ jsxs9("div", { className: "flex flex-wrap items-center gap-2 px-4 pb-3.5 pt-3", children: [
2176
- approval ? /* @__PURE__ */ jsxs9(Fragment5, { children: [
2177
- /* @__PURE__ */ jsx10(
2823
+ /* @__PURE__ */ jsxs10("div", { className: "flex flex-wrap items-center gap-2 px-4 pb-3.5 pt-3", children: [
2824
+ approval ? /* @__PURE__ */ jsxs10(Fragment5, { children: [
2825
+ /* @__PURE__ */ jsx12(
2178
2826
  "button",
2179
2827
  {
2180
2828
  type: "button",
@@ -2184,7 +2832,7 @@ function ProposalCard({
2184
2832
  children: "Approve & run"
2185
2833
  }
2186
2834
  ),
2187
- /* @__PURE__ */ jsx10(
2835
+ /* @__PURE__ */ jsx12(
2188
2836
  "button",
2189
2837
  {
2190
2838
  type: "button",
@@ -2194,8 +2842,8 @@ function ProposalCard({
2194
2842
  children: "Reject"
2195
2843
  }
2196
2844
  )
2197
- ] }) : /* @__PURE__ */ jsx10("span", { className: "text-[12px] font-medium text-muted-foreground", children: "Awaiting approval\u2026" }),
2198
- /* @__PURE__ */ jsxs9(
2845
+ ] }) : /* @__PURE__ */ jsx12("span", { className: "text-[12px] font-medium text-muted-foreground", children: "Awaiting approval\u2026" }),
2846
+ /* @__PURE__ */ jsxs10(
2199
2847
  "button",
2200
2848
  {
2201
2849
  type: "button",
@@ -2204,23 +2852,23 @@ function ProposalCard({
2204
2852
  className: "ml-auto inline-flex items-center gap-1 rounded text-[12px] font-medium text-muted-foreground transition hover:text-foreground focus:outline-none focus-visible:ring-2 focus-visible:ring-ring",
2205
2853
  children: [
2206
2854
  expanded ? "Hide details" : "View details",
2207
- /* @__PURE__ */ jsx10(ChevronDown, { className: `h-3 w-3 transition-transform ${expanded ? "rotate-180" : ""}` })
2855
+ /* @__PURE__ */ jsx12(ChevronDown, { className: `h-3 w-3 transition-transform ${expanded ? "rotate-180" : ""}` })
2208
2856
  ]
2209
2857
  }
2210
2858
  )
2211
2859
  ] }),
2212
- expanded && /* @__PURE__ */ jsx10("div", { className: "border-t border-warning/20 px-4 py-3 text-xs", children: custom ?? /* @__PURE__ */ jsx10(DefaultToolDetail, { call }) })
2860
+ expanded && /* @__PURE__ */ jsx12("div", { className: "border-t border-warning/20 px-4 py-3 text-xs", children: custom ?? /* @__PURE__ */ jsx12(DefaultToolDetail, { call }) })
2213
2861
  ] });
2214
2862
  }
2215
2863
  function FollowupCard({ call }) {
2216
2864
  const a = call.args ?? {};
2217
2865
  const when = typeof a.when === "string" ? a.when : typeof a.at === "string" ? a.at : typeof a.schedule === "string" ? a.schedule : null;
2218
- return /* @__PURE__ */ 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: [
2219
- /* @__PURE__ */ jsxs9("div", { className: "flex items-center gap-2", children: [
2220
- /* @__PURE__ */ jsx10(ToolGlyph, { name: call.name, className: "h-3.5 w-3.5 shrink-0 text-primary/80" }),
2221
- /* @__PURE__ */ jsx10("span", { className: "min-w-0 flex-1 truncate font-medium text-foreground", children: friendlyToolTitle(call) })
2866
+ return /* @__PURE__ */ jsxs10("div", { className: "w-fit min-w-[260px] max-w-full rounded-lg border border-border/60 border-l-2 border-l-primary/60 bg-muted/20 px-3 py-2 text-sm", children: [
2867
+ /* @__PURE__ */ jsxs10("div", { className: "flex items-center gap-2", children: [
2868
+ /* @__PURE__ */ jsx12(ToolGlyph, { name: call.name, className: "h-3.5 w-3.5 shrink-0 text-primary/80" }),
2869
+ /* @__PURE__ */ jsx12("span", { className: "min-w-0 flex-1 truncate font-medium text-foreground", children: friendlyToolTitle(call) })
2222
2870
  ] }),
2223
- when && /* @__PURE__ */ jsx10("p", { className: "mt-0.5 pl-[22px] text-[12px] text-muted-foreground", children: when })
2871
+ when && /* @__PURE__ */ jsx12("p", { className: "mt-0.5 pl-[22px] text-[12px] text-muted-foreground", children: when })
2224
2872
  ] });
2225
2873
  }
2226
2874
  function ToolCallCard({
@@ -2230,13 +2878,13 @@ function ToolCallCard({
2230
2878
  onOpenRun,
2231
2879
  renderers
2232
2880
  }) {
2233
- const [expanded, setExpanded] = useState9(false);
2881
+ const [expanded, setExpanded] = useState12(false);
2234
2882
  const pending = call.status === "done" ? pendingApprovalOf(call) : null;
2235
2883
  const kind = blockKindOf(call);
2236
2884
  const failed = call.status === "error" || toolOutcomeOf(call)?.ok === false;
2237
2885
  const custom = renderers?.[call.name]?.(call, message);
2238
2886
  if (pending) {
2239
- return /* @__PURE__ */ jsx10(
2887
+ return /* @__PURE__ */ jsx12(
2240
2888
  ProposalCard,
2241
2889
  {
2242
2890
  call,
@@ -2248,16 +2896,16 @@ function ToolCallCard({
2248
2896
  );
2249
2897
  }
2250
2898
  if (kind === "followup" && !failed) {
2251
- return /* @__PURE__ */ jsx10(FollowupCard, { call });
2899
+ return /* @__PURE__ */ jsx12(FollowupCard, { call });
2252
2900
  }
2253
2901
  const isCommand = kind === "command";
2254
- return /* @__PURE__ */ jsxs9(
2902
+ return /* @__PURE__ */ jsxs10(
2255
2903
  "div",
2256
2904
  {
2257
2905
  className: `w-fit min-w-[280px] max-w-full rounded-lg border text-xs transition ${failed ? "border-destructive/40 bg-destructive/5" : "border-border/60 bg-muted/20"}`,
2258
2906
  children: [
2259
- /* @__PURE__ */ jsxs9("div", { className: "flex w-full items-center gap-2 px-3 py-2", children: [
2260
- /* @__PURE__ */ jsxs9(
2907
+ /* @__PURE__ */ jsxs10("div", { className: "flex w-full items-center gap-2 px-3 py-2", children: [
2908
+ /* @__PURE__ */ jsxs10(
2261
2909
  "button",
2262
2910
  {
2263
2911
  type: "button",
@@ -2265,14 +2913,14 @@ function ToolCallCard({
2265
2913
  "aria-expanded": expanded,
2266
2914
  className: "flex min-w-0 flex-1 items-center gap-2 rounded text-left focus:outline-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
2267
2915
  children: [
2268
- /* @__PURE__ */ jsx10(
2916
+ /* @__PURE__ */ jsx12(
2269
2917
  "span",
2270
2918
  {
2271
2919
  className: `h-2 w-2 shrink-0 rounded-full ${call.status === "running" ? "animate-pulse bg-warning" : failed ? "bg-destructive" : "bg-success"}`
2272
2920
  }
2273
2921
  ),
2274
- /* @__PURE__ */ jsx10(ToolGlyph, { name: call.name, className: "h-3.5 w-3.5 shrink-0 text-muted-foreground" }),
2275
- /* @__PURE__ */ jsx10(
2922
+ /* @__PURE__ */ jsx12(ToolGlyph, { name: call.name, className: "h-3.5 w-3.5 shrink-0 text-muted-foreground" }),
2923
+ /* @__PURE__ */ jsx12(
2276
2924
  "span",
2277
2925
  {
2278
2926
  className: `min-w-0 flex-1 truncate ${isCommand ? "font-mono text-[12px] tracking-tight text-foreground/90" : "font-medium"}`,
@@ -2282,8 +2930,8 @@ function ToolCallCard({
2282
2930
  ]
2283
2931
  }
2284
2932
  ),
2285
- /* @__PURE__ */ jsx10("span", { className: "shrink-0 text-[11px] text-muted-foreground", children: call.status === "running" ? "running\u2026" : failed ? "failed" : "done" }),
2286
- /* @__PURE__ */ jsx10(
2933
+ /* @__PURE__ */ jsx12("span", { className: "shrink-0 text-[11px] text-muted-foreground", children: call.status === "running" ? "running\u2026" : failed ? "failed" : "done" }),
2934
+ /* @__PURE__ */ jsx12(
2287
2935
  "button",
2288
2936
  {
2289
2937
  type: "button",
@@ -2291,13 +2939,13 @@ function ToolCallCard({
2291
2939
  "aria-label": expanded ? "Collapse details" : "Expand details",
2292
2940
  "aria-expanded": expanded,
2293
2941
  className: "shrink-0 rounded p-0.5 focus:outline-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
2294
- children: /* @__PURE__ */ jsx10(ChevronDown, { className: `h-3 w-3 text-muted-foreground transition-transform ${expanded ? "rotate-180" : ""}` })
2942
+ children: /* @__PURE__ */ jsx12(ChevronDown, { className: `h-3 w-3 text-muted-foreground transition-transform ${expanded ? "rotate-180" : ""}` })
2295
2943
  }
2296
2944
  )
2297
2945
  ] }),
2298
- expanded && /* @__PURE__ */ jsxs9("div", { className: "border-t border-border/40 px-3 py-2.5", children: [
2299
- custom ?? (call.name === "sandbox_run_command" ? /* @__PURE__ */ jsx10(ShellDetail, { call }) : /* @__PURE__ */ jsx10(DefaultToolDetail, { call })),
2300
- onOpenRun && call.name.startsWith("sandbox_") && /* @__PURE__ */ jsx10(
2946
+ expanded && /* @__PURE__ */ jsxs10("div", { className: "border-t border-border/40 px-3 py-2.5", children: [
2947
+ custom ?? (call.name === "sandbox_run_command" ? /* @__PURE__ */ jsx12(ShellDetail, { call }) : /* @__PURE__ */ jsx12(DefaultToolDetail, { call })),
2948
+ onOpenRun && call.name.startsWith("sandbox_") && /* @__PURE__ */ jsx12(
2301
2949
  "button",
2302
2950
  {
2303
2951
  type: "button",
@@ -2312,7 +2960,7 @@ function ToolCallCard({
2312
2960
  );
2313
2961
  }
2314
2962
  function StreamingCaret() {
2315
- return /* @__PURE__ */ jsx10(
2963
+ return /* @__PURE__ */ jsx12(
2316
2964
  "span",
2317
2965
  {
2318
2966
  className: "ml-0.5 inline-block h-[1.1em] w-[3px] translate-y-[2px] animate-pulse rounded-sm bg-foreground/70",
@@ -2327,11 +2975,11 @@ function SegmentText({
2327
2975
  renderBody
2328
2976
  }) {
2329
2977
  const text = useSmoothText(content, streaming);
2330
- const body = useMemo6(() => renderBody(text), [renderBody, text]);
2978
+ const body = useMemo7(() => renderBody(text), [renderBody, text]);
2331
2979
  if (!content.trim() && !showCaret) return null;
2332
- return /* @__PURE__ */ jsxs9("div", { className: "text-base leading-[1.75]", children: [
2980
+ return /* @__PURE__ */ jsxs10("div", { className: "text-base leading-[1.75]", children: [
2333
2981
  body,
2334
- showCaret && /* @__PURE__ */ jsx10(StreamingCaret, {})
2982
+ showCaret && /* @__PURE__ */ jsx12(StreamingCaret, {})
2335
2983
  ] });
2336
2984
  }
2337
2985
  var COLLAPSE_TOOL_RUN_AT = 3;
@@ -2354,7 +3002,7 @@ function SegmentedBody({
2354
3002
  const leftoverToolCalls = (msg.toolCalls ?? []).filter(
2355
3003
  (tc) => !segmentToolIds.has(tc.id)
2356
3004
  );
2357
- const renderToolCard = (call) => /* @__PURE__ */ jsx10(
3005
+ const renderToolCard = (call) => /* @__PURE__ */ jsx12(
2358
3006
  ToolCallCard,
2359
3007
  {
2360
3008
  call,
@@ -2377,9 +3025,9 @@ function SegmentedBody({
2377
3025
  else groups.push({ kind: "tools", index: i, calls: [seg.call] });
2378
3026
  }
2379
3027
  }
2380
- return /* @__PURE__ */ jsxs9("div", { className: "flex flex-col gap-2", children: [
3028
+ return /* @__PURE__ */ jsxs10("div", { className: "flex flex-col gap-2", children: [
2381
3029
  groups.map(
2382
- (g) => g.kind === "text" ? /* @__PURE__ */ jsx10(
3030
+ (g) => g.kind === "text" ? /* @__PURE__ */ jsx12(
2383
3031
  SegmentText,
2384
3032
  {
2385
3033
  content: g.content,
@@ -2388,24 +3036,24 @@ function SegmentedBody({
2388
3036
  renderBody
2389
3037
  },
2390
3038
  `text-${g.index}`
2391
- ) : !streaming && g.calls.length >= COLLAPSE_TOOL_RUN_AT && !g.calls.some(isImportantTool) ? /* @__PURE__ */ jsxs9(
3039
+ ) : !streaming && g.calls.length >= COLLAPSE_TOOL_RUN_AT && !g.calls.some(isImportantTool) ? /* @__PURE__ */ jsxs10(
2392
3040
  "details",
2393
3041
  {
2394
3042
  className: "rounded-lg border-l-2 border-border/70 bg-muted/20 px-3 py-2",
2395
3043
  children: [
2396
- /* @__PURE__ */ jsxs9("summary", { className: "cursor-pointer select-none text-xs font-medium text-muted-foreground", children: [
3044
+ /* @__PURE__ */ jsxs10("summary", { className: "cursor-pointer select-none text-xs font-medium text-muted-foreground", children: [
2397
3045
  "Worked through ",
2398
3046
  g.calls.length,
2399
3047
  " steps"
2400
3048
  ] }),
2401
- /* @__PURE__ */ jsx10("div", { className: "mt-2 flex flex-col gap-2", children: g.calls.map(renderToolCard) })
3049
+ /* @__PURE__ */ jsx12("div", { className: "mt-2 flex flex-col gap-2", children: g.calls.map(renderToolCard) })
2402
3050
  ]
2403
3051
  },
2404
3052
  `tools-${g.index}`
2405
- ) : /* @__PURE__ */ jsx10("div", { className: "flex flex-col gap-2", children: g.calls.map(renderToolCard) }, `tools-${g.index}`)
3053
+ ) : /* @__PURE__ */ jsx12("div", { className: "flex flex-col gap-2", children: g.calls.map(renderToolCard) }, `tools-${g.index}`)
2406
3054
  ),
2407
3055
  leftoverToolCalls.map(renderToolCard),
2408
- streaming && segments[lastIndex]?.kind === "tool" && /* @__PURE__ */ jsx10(StreamingCaret, {})
3056
+ streaming && segments[lastIndex]?.kind === "tool" && /* @__PURE__ */ jsx12(StreamingCaret, {})
2409
3057
  ] });
2410
3058
  }
2411
3059
  function AssistantMessageImpl({
@@ -2417,44 +3065,45 @@ function AssistantMessageImpl({
2417
3065
  approval,
2418
3066
  onToolCallClick,
2419
3067
  toolRenderers,
2420
- renderExtras
3068
+ renderExtras,
3069
+ durableCards
2421
3070
  }) {
2422
3071
  const content = useSmoothText(msg.content, streaming);
2423
3072
  const reasoning = useSmoothText(msg.reasoning ?? "", streaming);
2424
- const body = useMemo6(() => renderBody(content), [renderBody, content]);
3073
+ const body = useMemo7(() => renderBody(content), [renderBody, content]);
2425
3074
  const segments = msg.segments;
2426
3075
  const hasAnswerText = content !== "" || (segments?.some((s) => s.kind === "text" && s.content.trim() !== "") ?? false);
2427
- const reasoningScrollRef = useRef6(null);
2428
- const thinkStartRef = useRef6(null);
2429
- const thinkMsRef = useRef6(null);
3076
+ const reasoningScrollRef = useRef8(null);
3077
+ const thinkStartRef = useRef8(null);
3078
+ const thinkMsRef = useRef8(null);
2430
3079
  if (streaming && reasoning && !hasAnswerText && thinkStartRef.current === null) {
2431
3080
  thinkStartRef.current = performance.now();
2432
3081
  }
2433
3082
  if (hasAnswerText && thinkStartRef.current !== null && thinkMsRef.current === null) {
2434
3083
  thinkMsRef.current = performance.now() - thinkStartRef.current;
2435
3084
  }
2436
- useEffect5(() => {
3085
+ useEffect8(() => {
2437
3086
  const el = reasoningScrollRef.current;
2438
3087
  if (el && streaming && !hasAnswerText) el.scrollTop = el.scrollHeight;
2439
3088
  }, [reasoning, streaming, hasAnswerText]);
2440
3089
  const thinkingSeconds = useThinkingSeconds(
2441
3090
  streaming && !!reasoning && !hasAnswerText
2442
3091
  );
2443
- return /* @__PURE__ */ jsxs9("div", { className: "mx-auto w-full max-w-3xl px-6 py-3", children: [
2444
- /* @__PURE__ */ jsxs9("div", { className: "mb-1 flex items-baseline gap-2 text-[11px] tracking-wide text-muted-foreground", children: [
2445
- /* @__PURE__ */ jsx10("span", { className: "font-semibold uppercase", children: agentLabel }),
2446
- msg.modelUsed && /* @__PURE__ */ jsx10("span", { className: "font-mono normal-case", children: msg.modelUsed }),
2447
- formatTokensPerSecond(msg) && /* @__PURE__ */ jsx10("span", { children: formatTokensPerSecond(msg) }),
2448
- formatModelCost(msg, models) && /* @__PURE__ */ jsx10("span", { children: formatModelCost(msg, models) })
3092
+ return /* @__PURE__ */ jsxs10("div", { className: "mx-auto w-full max-w-3xl px-6 py-3", children: [
3093
+ /* @__PURE__ */ jsxs10("div", { className: "mb-1 flex items-baseline gap-2 text-[11px] tracking-wide text-muted-foreground", children: [
3094
+ /* @__PURE__ */ jsx12("span", { className: "font-semibold uppercase", children: agentLabel }),
3095
+ msg.modelUsed && /* @__PURE__ */ jsx12("span", { className: "font-mono normal-case", children: msg.modelUsed }),
3096
+ formatTokensPerSecond(msg) && /* @__PURE__ */ jsx12("span", { children: formatTokensPerSecond(msg) }),
3097
+ formatModelCost(msg, models) && /* @__PURE__ */ jsx12("span", { children: formatModelCost(msg, models) })
2449
3098
  ] }),
2450
- 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: [
2451
- /* @__PURE__ */ jsx10("summary", { className: "cursor-pointer select-none text-xs font-medium text-muted-foreground", children: !hasAnswerText ? /* @__PURE__ */ jsxs9("span", { className: "animate-pulse", children: [
3099
+ reasoning && /* @__PURE__ */ jsxs10("details", { className: "mb-2 rounded-lg border-l-2 border-border/70 bg-muted/20 px-3 py-2", open: !hasAnswerText, children: [
3100
+ /* @__PURE__ */ jsx12("summary", { className: "cursor-pointer select-none text-xs font-medium text-muted-foreground", children: !hasAnswerText ? /* @__PURE__ */ jsxs10("span", { className: "animate-pulse", children: [
2452
3101
  "Thinking",
2453
3102
  thinkingSeconds >= 3 ? ` \xB7 ${thinkingSeconds}s` : "\u2026"
2454
3103
  ] }) : thinkMsRef.current != null ? `Thought for ${Math.max(1, Math.round(thinkMsRef.current / 1e3))}s` : "Thought process" }),
2455
- /* @__PURE__ */ jsx10("div", { ref: reasoningScrollRef, className: "mt-2 max-h-48 overflow-y-auto whitespace-pre-wrap text-[13px] leading-relaxed text-muted-foreground", children: reasoning })
3104
+ /* @__PURE__ */ jsx12("div", { ref: reasoningScrollRef, className: "mt-2 max-h-48 overflow-y-auto whitespace-pre-wrap text-[13px] leading-relaxed text-muted-foreground", children: reasoning })
2456
3105
  ] }),
2457
- segments && segments.length > 0 ? /* @__PURE__ */ jsx10(
3106
+ segments && segments.length > 0 ? /* @__PURE__ */ jsx12(
2458
3107
  SegmentedBody,
2459
3108
  {
2460
3109
  segments,
@@ -2465,12 +3114,12 @@ function AssistantMessageImpl({
2465
3114
  onToolCallClick,
2466
3115
  toolRenderers
2467
3116
  }
2468
- ) : /* @__PURE__ */ jsxs9(Fragment5, { children: [
2469
- /* @__PURE__ */ jsxs9("div", { className: "text-base leading-[1.75]", children: [
3117
+ ) : /* @__PURE__ */ jsxs10(Fragment5, { children: [
3118
+ /* @__PURE__ */ jsxs10("div", { className: "text-base leading-[1.75]", children: [
2470
3119
  body,
2471
- streaming && content && !msg.toolCalls?.length && /* @__PURE__ */ jsx10(StreamingCaret, {})
3120
+ streaming && content && !msg.toolCalls?.length && /* @__PURE__ */ jsx12(StreamingCaret, {})
2472
3121
  ] }),
2473
- msg.toolCalls && msg.toolCalls.length > 0 && /* @__PURE__ */ jsx10("div", { className: "mt-2 flex flex-col gap-1.5", children: msg.toolCalls.map((tc) => /* @__PURE__ */ jsx10(
3122
+ msg.toolCalls && msg.toolCalls.length > 0 && /* @__PURE__ */ jsx12("div", { className: "mt-2 flex flex-col gap-1.5", children: msg.toolCalls.map((tc) => /* @__PURE__ */ jsx12(
2474
3123
  ToolCallCard,
2475
3124
  {
2476
3125
  call: tc,
@@ -2482,13 +3131,22 @@ function AssistantMessageImpl({
2482
3131
  tc.id
2483
3132
  )) })
2484
3133
  ] }),
3134
+ durableCards && msg.parts && /* @__PURE__ */ jsx12(
3135
+ DurableChatCards,
3136
+ {
3137
+ ...durableCards,
3138
+ parts: msg.parts,
3139
+ renderMarkdown: renderBody,
3140
+ className: "mt-3"
3141
+ }
3142
+ ),
2485
3143
  renderExtras?.(msg)
2486
3144
  ] });
2487
3145
  }
2488
3146
  var AssistantMessage = memo(AssistantMessageImpl);
2489
3147
  function useThinkingSeconds(active) {
2490
- const [seconds, setSeconds] = useState9(0);
2491
- useEffect5(() => {
3148
+ const [seconds, setSeconds] = useState12(0);
3149
+ useEffect8(() => {
2492
3150
  if (!active) return;
2493
3151
  setSeconds(0);
2494
3152
  const id = setInterval(() => setSeconds((s) => s + 1), 1e3);
@@ -2498,23 +3156,23 @@ function useThinkingSeconds(active) {
2498
3156
  }
2499
3157
  function ThinkingRow({ agentLabel }) {
2500
3158
  const seconds = useThinkingSeconds(true);
2501
- return /* @__PURE__ */ jsxs9("div", { className: "mx-auto w-full max-w-3xl px-6 py-3", children: [
2502
- /* @__PURE__ */ jsx10("p", { className: "mb-1 text-[11px] font-semibold uppercase tracking-wide text-muted-foreground", children: agentLabel }),
2503
- /* @__PURE__ */ jsxs9("div", { className: "flex items-center gap-2 text-base text-muted-foreground", children: [
2504
- /* @__PURE__ */ jsx10("svg", { className: "h-4 w-4 animate-spin", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", "aria-hidden": true, children: /* @__PURE__ */ jsx10("path", { d: "M21 12a9 9 0 1 1-6.219-8.56", strokeLinecap: "round" }) }),
3159
+ return /* @__PURE__ */ jsxs10("div", { className: "mx-auto w-full max-w-3xl px-6 py-3", children: [
3160
+ /* @__PURE__ */ jsx12("p", { className: "mb-1 text-[11px] font-semibold uppercase tracking-wide text-muted-foreground", children: agentLabel }),
3161
+ /* @__PURE__ */ jsxs10("div", { className: "flex items-center gap-2 text-base text-muted-foreground", children: [
3162
+ /* @__PURE__ */ jsx12("svg", { className: "h-4 w-4 animate-spin", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", "aria-hidden": true, children: /* @__PURE__ */ jsx12("path", { d: "M21 12a9 9 0 1 1-6.219-8.56", strokeLinecap: "round" }) }),
2505
3163
  "Thinking",
2506
3164
  seconds >= 3 ? ` \xB7 ${seconds}s` : "..."
2507
3165
  ] })
2508
3166
  ] });
2509
3167
  }
2510
3168
  function StreamErrorRow({ message, onRetry }) {
2511
- return /* @__PURE__ */ 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: [
2512
- /* @__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: [
2513
- /* @__PURE__ */ jsx10("circle", { cx: "12", cy: "12", r: "9" }),
2514
- /* @__PURE__ */ jsx10("path", { d: "M12 8v4m0 4h.01" })
3169
+ return /* @__PURE__ */ jsx12("div", { className: "mx-auto w-full max-w-3xl px-6 py-3", children: /* @__PURE__ */ jsxs10("div", { role: "alert", className: "flex items-start gap-2.5 rounded-lg border border-destructive/40 bg-destructive/5 px-3 py-2.5 text-sm text-destructive", children: [
3170
+ /* @__PURE__ */ jsxs10("svg", { className: "mt-0.5 h-4 w-4 shrink-0", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: [
3171
+ /* @__PURE__ */ jsx12("circle", { cx: "12", cy: "12", r: "9" }),
3172
+ /* @__PURE__ */ jsx12("path", { d: "M12 8v4m0 4h.01" })
2515
3173
  ] }),
2516
- /* @__PURE__ */ jsx10("span", { className: "min-w-0 flex-1 break-words", children: message }),
2517
- onRetry && /* @__PURE__ */ jsx10(
3174
+ /* @__PURE__ */ jsx12("span", { className: "min-w-0 flex-1 break-words", children: message }),
3175
+ onRetry && /* @__PURE__ */ jsx12(
2518
3176
  "button",
2519
3177
  {
2520
3178
  type: "button",
@@ -2530,6 +3188,7 @@ function ChatMessages({
2530
3188
  models = [],
2531
3189
  renderMarkdown,
2532
3190
  renderExtras,
3191
+ durableCards,
2533
3192
  userLabel = "User",
2534
3193
  agentLabel = "Agent",
2535
3194
  loading,
@@ -2542,25 +3201,25 @@ function ChatMessages({
2542
3201
  emptyState,
2543
3202
  header
2544
3203
  }) {
2545
- const renderBody = useMemo6(
2546
- () => renderMarkdown ?? ((content) => /* @__PURE__ */ jsx10("p", { className: "whitespace-pre-wrap", children: content })),
3204
+ const renderBody = useMemo7(
3205
+ () => renderMarkdown ?? ((content) => /* @__PURE__ */ jsx12("p", { className: "whitespace-pre-wrap", children: content })),
2547
3206
  [renderMarkdown]
2548
3207
  );
2549
3208
  const lastIsUser = messages[messages.length - 1]?.role === "user";
2550
3209
  if (messages.length === 0 && !loading && !error) {
2551
- const empty = renderEmpty ? renderEmpty() : /* @__PURE__ */ jsx10(ChatEmptyState, { ...emptyState });
2552
- return /* @__PURE__ */ jsxs9(Fragment5, { children: [
3210
+ const empty = renderEmpty ? renderEmpty() : /* @__PURE__ */ jsx12(ChatEmptyState, { ...emptyState });
3211
+ return /* @__PURE__ */ jsxs10(Fragment5, { children: [
2553
3212
  header,
2554
3213
  empty
2555
3214
  ] });
2556
3215
  }
2557
- return /* @__PURE__ */ jsxs9(Fragment5, { children: [
3216
+ return /* @__PURE__ */ jsxs10(Fragment5, { children: [
2558
3217
  header,
2559
3218
  messages.map(
2560
- (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: [
2561
- /* @__PURE__ */ jsx10("p", { className: "mb-1 text-right text-[11px] font-semibold uppercase tracking-wide text-muted-foreground", children: userLabel }),
2562
- /* @__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 }) })
2563
- ] }) }, msg.id) : /* @__PURE__ */ jsx10(
3219
+ (msg) => msg.role === "user" ? /* @__PURE__ */ jsx12("div", { className: "mx-auto w-full max-w-3xl px-6 py-3", children: /* @__PURE__ */ jsxs10("div", { className: "ml-auto w-fit max-w-[85%]", children: [
3220
+ /* @__PURE__ */ jsx12("p", { className: "mb-1 text-right text-[11px] font-semibold uppercase tracking-wide text-muted-foreground", children: userLabel }),
3221
+ /* @__PURE__ */ jsx12("div", { className: "rounded-2xl rounded-tr-md bg-primary/10 px-4 py-2.5 text-base leading-relaxed", children: /* @__PURE__ */ jsx12("p", { className: "whitespace-pre-wrap", children: msg.content }) })
3222
+ ] }) }, msg.id) : /* @__PURE__ */ jsx12(
2564
3223
  AssistantMessage,
2565
3224
  {
2566
3225
  msg,
@@ -2571,13 +3230,14 @@ function ChatMessages({
2571
3230
  approval,
2572
3231
  onToolCallClick,
2573
3232
  toolRenderers,
2574
- renderExtras
3233
+ renderExtras,
3234
+ durableCards
2575
3235
  },
2576
3236
  msg.id
2577
3237
  )
2578
3238
  ),
2579
- loading && lastIsUser && /* @__PURE__ */ jsx10(ThinkingRow, { agentLabel }),
2580
- error && !loading && /* @__PURE__ */ jsx10(StreamErrorRow, { message: error, onRetry })
3239
+ loading && lastIsUser && /* @__PURE__ */ jsx12(ThinkingRow, { agentLabel }),
3240
+ error && !loading && /* @__PURE__ */ jsx12(StreamErrorRow, { message: error, onRetry })
2581
3241
  ] });
2582
3242
  }
2583
3243
 
@@ -2590,12 +3250,9 @@ export {
2590
3250
  ModelPicker,
2591
3251
  DEFAULT_EFFORT_LEVELS,
2592
3252
  EffortPicker,
2593
- dispatchChatStreamLine,
2594
- consumeChatStream,
2595
- streamChatTurn,
2596
- ChatComposer,
2597
3253
  interactionStatusLabels,
2598
3254
  interactionTerminalNotes,
3255
+ fieldValuesFromAnswers,
2599
3256
  fieldAnswer,
2600
3257
  buildAnswerData,
2601
3258
  isLateAnswerableStatus,
@@ -2609,13 +3266,33 @@ export {
2609
3266
  InteractionActionButton,
2610
3267
  QuestionOptionList,
2611
3268
  InteractionQuestionCard,
3269
+ DurablePlanCard,
2612
3270
  InteractionPlanCard,
3271
+ durableChatCardsFromParts,
3272
+ DurableChatCards,
3273
+ dispatchChatStreamLine,
3274
+ consumeChatStream,
3275
+ streamChatTurn,
3276
+ ChatComposer,
3277
+ DurablePlanClientError,
3278
+ createDurablePlanDecisionClient,
3279
+ useDurablePlanFlow,
3280
+ createSessionInteractionAttemptStore,
3281
+ createMemoryInteractionAttemptStore,
3282
+ interactionSubmissionSignature,
3283
+ createDurableInteractionAnswerSubmitter,
2613
3284
  upsertChatInteraction,
2614
3285
  cancelChatInteraction,
2615
3286
  resolveChatInteraction,
2616
3287
  terminalizePendingChatInteractions,
2617
3288
  restoreChatInteractions,
3289
+ hydrateChatInteractions,
2618
3290
  useChatInteractions,
3291
+ rankFileMentions,
3292
+ DEFAULT_MENTION_LIMIT,
3293
+ INDEX_REFRESH_AFTER_MS,
3294
+ DEFAULT_MENTION_EMPTY_TEXT,
3295
+ useFileMentions,
2619
3296
  activityTone,
2620
3297
  formatActivityCost,
2621
3298
  formatActivityDuration,
@@ -2634,4 +3311,4 @@ export {
2634
3311
  useThinkingSeconds,
2635
3312
  ChatMessages
2636
3313
  };
2637
- //# sourceMappingURL=chunk-D4HB72W2.js.map
3314
+ //# sourceMappingURL=chunk-ZLHK25C3.js.map