@invergent/agent-chat-react 2.12.0 → 2.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -2504,7 +2504,7 @@ function TabsTrigger({
2504
2504
  {
2505
2505
  "data-slot": "tabs-trigger",
2506
2506
  className: cn(
2507
- "relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-2 border border-transparent px-4 py-1.5 text-xs font-semibold tracking-wider whitespace-nowrap text-foreground/60 uppercase transition-all group-data-vertical/tabs:w-full group-data-vertical/tabs:justify-start group-data-vertical/tabs:px-4 group-data-vertical/tabs:py-2 hover:text-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1 focus-visible:outline-ring disabled:pointer-events-none disabled:opacity-50 has-data-[icon=inline-end]:pr-2.5 has-data-[icon=inline-start]:pl-2.5 dark:text-muted-foreground dark:hover:text-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5",
2507
+ "relative inline-flex min-h-11 h-[calc(100%-1px)] flex-1 items-center justify-center gap-2 border border-transparent px-4 py-1.5 pointer-fine:min-h-0 text-xs font-semibold tracking-wider whitespace-nowrap text-foreground/60 uppercase transition-all group-data-vertical/tabs:w-full group-data-vertical/tabs:justify-start group-data-vertical/tabs:px-4 group-data-vertical/tabs:py-2 hover:text-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1 focus-visible:outline-ring disabled:pointer-events-none disabled:opacity-50 has-data-[icon=inline-end]:pr-2.5 has-data-[icon=inline-start]:pl-2.5 dark:text-muted-foreground dark:hover:text-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5",
2508
2508
  "group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-active:bg-transparent dark:group-data-[variant=line]/tabs-list:data-active:border-transparent dark:group-data-[variant=line]/tabs-list:data-active:bg-transparent",
2509
2509
  "data-active:bg-background data-active:text-foreground dark:data-active:border-input dark:data-active:bg-input/30 dark:data-active:text-foreground",
2510
2510
  "after:absolute after:bg-foreground after:opacity-0 after:transition-opacity group-data-horizontal/tabs:after:inset-x-0 group-data-horizontal/tabs:after:bottom-[-5px] group-data-horizontal/tabs:after:h-0.5 group-data-vertical/tabs:after:inset-y-0 group-data-vertical/tabs:after:-right-1 group-data-vertical/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-active:after:opacity-100",
@@ -4291,21 +4291,162 @@ var OTHER_INDEX_OFFSET = 1e6;
4291
4291
  function emptySelection() {
4292
4292
  return { index: -1, other: "" };
4293
4293
  }
4294
+ function hasChoices(question) {
4295
+ return (question.choices?.length ?? 0) > 0;
4296
+ }
4294
4297
  function buildAnswer(q, sel) {
4295
4298
  if (sel.index < 0) return null;
4296
4299
  if (sel.index >= OTHER_INDEX_OFFSET) {
4297
4300
  const text = sel.other.trim();
4298
4301
  if (!text) return null;
4299
- return { question: q.prompt, answer: text, is_other: true };
4302
+ return { question: q.prompt, answer: text, is_other: hasChoices(q) };
4300
4303
  }
4301
4304
  const choice = q.choices?.[sel.index];
4302
4305
  if (!choice) return null;
4303
4306
  return { question: q.prompt, answer: choice.label, is_other: false };
4304
4307
  }
4305
- function AskUserQuestionToolBlock({ tc }) {
4308
+ function askQuestionsOf(tc) {
4309
+ return parseArgs(tc.args)?.questions ?? [];
4310
+ }
4311
+ function isConversationalAsk(questions) {
4312
+ return questions.length === 1;
4313
+ }
4314
+ function conversationalAskAcceptsFreeText(question) {
4315
+ return !hasChoices(question) || question.allow_other !== false;
4316
+ }
4317
+ function promptEchoedInContent(content, prompt) {
4318
+ const normalize = (s) => s.replace(/[*_`]/g, "").replace(/\s+/g, " ").trim().toLowerCase();
4319
+ const body = normalize(content ?? "");
4320
+ const question = normalize(prompt);
4321
+ return !!body && !!question && body.endsWith(question);
4322
+ }
4323
+ function AskUserQuestionToolBlock({
4324
+ tc,
4325
+ assistantContent,
4326
+ viewMode = "simple"
4327
+ }) {
4328
+ const questions = react.useMemo(() => askQuestionsOf(tc), [tc.args]);
4329
+ if (questions.length === 0) {
4330
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "rounded-md border border-destructive/50 bg-destructive/5 px-3 py-2 text-xs text-destructive", children: "ask_user_question: no questions provided" });
4331
+ }
4332
+ if (isConversationalAsk(questions)) {
4333
+ return /* @__PURE__ */ jsxRuntime.jsx(
4334
+ ConversationalAsk,
4335
+ {
4336
+ tc,
4337
+ question: questions[0],
4338
+ assistantContent
4339
+ }
4340
+ );
4341
+ }
4342
+ return /* @__PURE__ */ jsxRuntime.jsx(BatchAsk, { tc, questions, viewMode });
4343
+ }
4344
+ function ConversationalAsk({
4345
+ tc,
4346
+ question,
4347
+ assistantContent
4348
+ }) {
4349
+ const { adapter, sessionId } = useAgentChatAdapterContext();
4350
+ const [submitting, setSubmitting] = react.useState(false);
4351
+ const [error, setError] = react.useState(null);
4352
+ const answered = tc.askUserQuestionAnswers !== void 0;
4353
+ const pending = !answered && tc.status === "running";
4354
+ const choices = question.choices ?? [];
4355
+ const showPrompt = react.useMemo(
4356
+ () => !promptEchoedInContent(assistantContent, question.prompt),
4357
+ [assistantContent, question.prompt]
4358
+ );
4359
+ react.useEffect(() => {
4360
+ if (!pending || !sessionId) return;
4361
+ const handler = (e) => {
4362
+ if (e.key !== "Escape") return;
4363
+ e.preventDefault();
4364
+ void adapter.pauseSession({ sessionId }).catch(() => {
4365
+ });
4366
+ };
4367
+ window.addEventListener("keydown", handler);
4368
+ return () => window.removeEventListener("keydown", handler);
4369
+ }, [adapter, sessionId, pending]);
4370
+ const pick = react.useCallback(
4371
+ async (label) => {
4372
+ if (!sessionId || submitting) return;
4373
+ setError(null);
4374
+ setSubmitting(true);
4375
+ try {
4376
+ await adapter.submitAskUserQuestionResponse({
4377
+ sessionId,
4378
+ toolCallId: tc.id,
4379
+ responses: [
4380
+ { question: question.prompt, answer: label, is_other: false }
4381
+ ]
4382
+ });
4383
+ } catch (e) {
4384
+ setError(e instanceof Error ? e.message : "Submit failed.");
4385
+ setSubmitting(false);
4386
+ }
4387
+ },
4388
+ [adapter, sessionId, tc.id, question.prompt, submitting]
4389
+ );
4390
+ const stacked = choices.some((c) => !!c.description);
4391
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
4392
+ showPrompt && /* @__PURE__ */ jsxRuntime.jsx(MessageResponse, { children: question.prompt }),
4393
+ pending && choices.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
4394
+ "div",
4395
+ {
4396
+ className: cn(
4397
+ stacked ? "flex flex-col gap-1.5" : "flex flex-wrap gap-2",
4398
+ "max-w-2xl"
4399
+ ),
4400
+ role: "group",
4401
+ "aria-label": "Suggested answers",
4402
+ children: choices.map((choice, i) => /* @__PURE__ */ jsxRuntime.jsx(
4403
+ QuickReply,
4404
+ {
4405
+ choice,
4406
+ stacked,
4407
+ disabled: submitting,
4408
+ onSelect: () => void pick(choice.label)
4409
+ },
4410
+ i
4411
+ ))
4412
+ }
4413
+ ),
4414
+ error && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-destructive", children: error }),
4415
+ !answered && !pending && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs italic text-muted-foreground/70", children: "No answer recorded." })
4416
+ ] });
4417
+ }
4418
+ function QuickReply({
4419
+ choice,
4420
+ stacked,
4421
+ disabled,
4422
+ onSelect
4423
+ }) {
4424
+ return /* @__PURE__ */ jsxRuntime.jsxs(
4425
+ "button",
4426
+ {
4427
+ type: "button",
4428
+ disabled,
4429
+ onClick: onSelect,
4430
+ className: cn(
4431
+ "border border-border bg-background text-left text-foreground transition-colors",
4432
+ "hover:border-foreground/30 hover:bg-muted",
4433
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
4434
+ "disabled:cursor-not-allowed disabled:opacity-50",
4435
+ stacked ? "rounded-md px-3 py-2" : "rounded-full px-3.5 py-1.5 text-sm"
4436
+ ),
4437
+ children: [
4438
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("block", stacked && "text-sm"), children: choice.label }),
4439
+ stacked && choice.description && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "mt-0.5 block text-xs text-muted-foreground", children: choice.description })
4440
+ ]
4441
+ }
4442
+ );
4443
+ }
4444
+ function BatchAsk({
4445
+ tc,
4446
+ questions,
4447
+ viewMode
4448
+ }) {
4306
4449
  const { adapter, sessionId } = useAgentChatAdapterContext();
4307
- const args = react.useMemo(() => parseArgs(tc.args), [tc.args]);
4308
- const questions = args?.questions ?? [];
4309
4450
  const [active, setActive] = react.useState(0);
4310
4451
  const [selections, setSelections] = react.useState(
4311
4452
  () => questions.map(emptySelection)
@@ -4399,11 +4540,8 @@ function AskUserQuestionToolBlock({ tc }) {
4399
4540
  window.addEventListener("keydown", handler);
4400
4541
  return () => window.removeEventListener("keydown", handler);
4401
4542
  }, [handleCancel, handleSubmit, locked]);
4402
- if (questions.length === 0) {
4403
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "rounded-md border border-destructive/50 bg-destructive/5 px-3 py-2 text-xs text-destructive", children: "ask_user_question: no questions provided" });
4404
- }
4405
4543
  if (locked) {
4406
- return /* @__PURE__ */ jsxRuntime.jsx(AskUserQuestionLocked, { tc, questions });
4544
+ return /* @__PURE__ */ jsxRuntime.jsx(BatchAskLocked, { tc, questions, viewMode });
4407
4545
  }
4408
4546
  const current = questions[active];
4409
4547
  const currentSel = selections[active] ?? emptySelection();
@@ -4481,7 +4619,7 @@ function AskUserQuestionToolBlock({ tc }) {
4481
4619
  ] }),
4482
4620
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-3 border-t border-border px-3 py-2", children: [
4483
4621
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 text-xs text-muted-foreground", children: [
4484
- questions.length > 1 && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "mr-2 tabular-nums", children: [
4622
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "mr-2 tabular-nums", children: [
4485
4623
  answeredCount,
4486
4624
  " of ",
4487
4625
  questions.length,
@@ -4490,11 +4628,11 @@ function AskUserQuestionToolBlock({ tc }) {
4490
4628
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground/70", children: "Esc to cancel" }),
4491
4629
  error && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ml-2 text-destructive", children: error })
4492
4630
  ] }),
4493
- allAnswered || questions.length === 1 ? /* @__PURE__ */ jsxRuntime.jsx(
4631
+ allAnswered ? /* @__PURE__ */ jsxRuntime.jsx(
4494
4632
  "button",
4495
4633
  {
4496
4634
  type: "button",
4497
- disabled: !allAnswered || submitting,
4635
+ disabled: submitting,
4498
4636
  onClick: () => void handleSubmit(),
4499
4637
  className: cn(
4500
4638
  "shrink-0 rounded-md bg-primary px-4 py-1.5 text-sm font-medium text-primary-foreground transition-colors",
@@ -4604,9 +4742,10 @@ function Radio({ selected }) {
4604
4742
  }
4605
4743
  );
4606
4744
  }
4607
- function AskUserQuestionLocked({
4745
+ function BatchAskLocked({
4608
4746
  tc,
4609
- questions
4747
+ questions,
4748
+ viewMode
4610
4749
  }) {
4611
4750
  const answers = tc.askUserQuestionAnswers;
4612
4751
  const byPrompt = new Map(
@@ -4631,7 +4770,7 @@ function AskUserQuestionLocked({
4631
4770
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-emerald-500", children: "\u2192" }),
4632
4771
  " ",
4633
4772
  a.answer,
4634
- a.is_other && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ml-1 text-[10px] text-muted-foreground/70", children: "(other)" })
4773
+ a.is_other && viewMode === "expert" && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ml-1 text-[10px] text-muted-foreground/70", children: "(other)" })
4635
4774
  ] }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ml-5 text-muted-foreground/60 italic", children: "\u2014 no answer \u2014" })
4636
4775
  ] }, i);
4637
4776
  }) })
@@ -5036,6 +5175,8 @@ function CodeRunToolBlock({
5036
5175
  function ToolCallBlock({
5037
5176
  tc,
5038
5177
  resolvedArtifactName,
5178
+ assistantContent,
5179
+ viewMode,
5039
5180
  onFileSelect
5040
5181
  }) {
5041
5182
  if (tc.toolName.startsWith("mcp__")) {
@@ -5078,7 +5219,14 @@ function ToolCallBlock({
5078
5219
  case "skill_manage":
5079
5220
  return /* @__PURE__ */ jsxRuntime.jsx(SkillManageToolBlock, { tc });
5080
5221
  case "ask_user_question":
5081
- return /* @__PURE__ */ jsxRuntime.jsx(AskUserQuestionToolBlock, { tc });
5222
+ return /* @__PURE__ */ jsxRuntime.jsx(
5223
+ AskUserQuestionToolBlock,
5224
+ {
5225
+ tc,
5226
+ assistantContent,
5227
+ viewMode
5228
+ }
5229
+ );
5082
5230
  case "create_artifact":
5083
5231
  return /* @__PURE__ */ jsxRuntime.jsx(ArtifactToolBlock, { tc, resolvedName: resolvedArtifactName });
5084
5232
  case "delegate_task":
@@ -5490,6 +5638,22 @@ function CommandEmpty({
5490
5638
  }
5491
5639
  );
5492
5640
  }
5641
+ function CommandGroup({
5642
+ className,
5643
+ ...props
5644
+ }) {
5645
+ return /* @__PURE__ */ jsxRuntime.jsx(
5646
+ cmdk.Command.Group,
5647
+ {
5648
+ "data-slot": "command-group",
5649
+ className: cn(
5650
+ "overflow-hidden p-1.5 text-foreground **:[[cmdk-group-heading]]:mb-1 **:[[cmdk-group-heading]]:rounded-sm **:[[cmdk-group-heading]]:bg-orange-100 **:[[cmdk-group-heading]]:px-3 **:[[cmdk-group-heading]]:py-1.5 **:[[cmdk-group-heading]]:text-sm **:[[cmdk-group-heading]]:font-semibold **:[[cmdk-group-heading]]:tracking-wider **:[[cmdk-group-heading]]:text-foreground dark:**:[[cmdk-group-heading]]:bg-orange-400/40 dark:**:[[cmdk-group-heading]]:text-foreground/90",
5651
+ className
5652
+ ),
5653
+ ...props
5654
+ }
5655
+ );
5656
+ }
5493
5657
  function CommandItem({
5494
5658
  className,
5495
5659
  children,
@@ -8391,13 +8555,20 @@ var PromptInputTextarea = ({
8391
8555
  var PromptInputFooter = ({
8392
8556
  className,
8393
8557
  ...props
8394
- }) => /* @__PURE__ */ jsxRuntime.jsx(
8395
- InputGroupAddon,
8396
- {
8397
- align: "block-end",
8398
- className: cn("justify-between gap-1", className),
8399
- ...props
8400
- }
8558
+ }) => (
8559
+ // flex-wrap: the footer holds a left tools group (add, browser profile,
8560
+ // workspace) and a right group (view mode, send). On a phone those stop
8561
+ // fitting one line as soon as a profile or workspace chip appears, and a
8562
+ // non-wrapping row crushes the icons instead. Wrapping only engages when
8563
+ // the space is genuinely gone, so wider screens are unchanged.
8564
+ /* @__PURE__ */ jsxRuntime.jsx(
8565
+ InputGroupAddon,
8566
+ {
8567
+ align: "block-end",
8568
+ className: cn("flex-wrap justify-between gap-1", className),
8569
+ ...props
8570
+ }
8571
+ )
8401
8572
  );
8402
8573
  var PromptInputTools = ({
8403
8574
  className,
@@ -8511,44 +8682,6 @@ function splitComposerFiles(files) {
8511
8682
  return { images, pending };
8512
8683
  }
8513
8684
 
8514
- // src/components/ui/popover.tsx
8515
- init_utils();
8516
- function Popover({
8517
- ...props
8518
- }) {
8519
- return /* @__PURE__ */ jsxRuntime.jsx(radixUi.Popover.Root, { "data-slot": "popover", ...props });
8520
- }
8521
- function PopoverTrigger({
8522
- ...props
8523
- }) {
8524
- return /* @__PURE__ */ jsxRuntime.jsx(radixUi.Popover.Trigger, { "data-slot": "popover-trigger", ...props });
8525
- }
8526
- function PopoverContent({
8527
- className,
8528
- align = "center",
8529
- sideOffset = 4,
8530
- ...props
8531
- }) {
8532
- return /* @__PURE__ */ jsxRuntime.jsx(radixUi.Popover.Portal, { children: /* @__PURE__ */ jsxRuntime.jsx(
8533
- radixUi.Popover.Content,
8534
- {
8535
- "data-slot": "popover-content",
8536
- align,
8537
- sideOffset,
8538
- className: cn(
8539
- "z-50 flex w-72 origin-(--radix-popover-content-transform-origin) flex-col gap-4 rounded-none bg-popover p-4 text-sm text-popover-foreground shadow-md ring-1 ring-foreground/10 outline-hidden duration-150 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95",
8540
- className
8541
- ),
8542
- ...props
8543
- }
8544
- ) });
8545
- }
8546
- function PopoverAnchor({
8547
- ...props
8548
- }) {
8549
- return /* @__PURE__ */ jsxRuntime.jsx(radixUi.Popover.Anchor, { "data-slot": "popover-anchor", ...props });
8550
- }
8551
-
8552
8685
  // src/components/ui/progress.tsx
8553
8686
  init_utils();
8554
8687
  function Progress({
@@ -8588,24 +8721,26 @@ var ContextContext = react.createContext(null);
8588
8721
  var useContextValue = () => {
8589
8722
  const context = react.useContext(ContextContext);
8590
8723
  if (!context) {
8591
- throw new Error("Context components must be used within Context");
8724
+ throw new Error(
8725
+ "Context components must be used within ContextValueProvider"
8726
+ );
8592
8727
  }
8593
8728
  return context;
8594
8729
  };
8595
- var Context = ({
8730
+ var ContextValueProvider = ({
8596
8731
  usedTokens,
8597
8732
  maxTokens,
8598
8733
  usage,
8599
8734
  modelId,
8600
- ...props
8735
+ children
8601
8736
  }) => {
8602
8737
  const contextValue = react.useMemo(
8603
8738
  () => ({ maxTokens, modelId, usage, usedTokens }),
8604
8739
  [maxTokens, modelId, usage, usedTokens]
8605
8740
  );
8606
- return /* @__PURE__ */ jsxRuntime.jsx(ContextContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsxRuntime.jsx(Popover, { ...props }) });
8741
+ return /* @__PURE__ */ jsxRuntime.jsx(ContextContext.Provider, { value: contextValue, children });
8607
8742
  };
8608
- var ContextIcon = () => {
8743
+ var ContextIcon = ({ className }) => {
8609
8744
  const { usedTokens, maxTokens } = useContextValue();
8610
8745
  const circumference = 2 * Math.PI * ICON_RADIUS;
8611
8746
  const usedPercent = usedTokens / maxTokens;
@@ -8614,6 +8749,7 @@ var ContextIcon = () => {
8614
8749
  "svg",
8615
8750
  {
8616
8751
  "aria-label": "Model context usage",
8752
+ className,
8617
8753
  height: "20",
8618
8754
  role: "img",
8619
8755
  style: { color: "currentcolor" },
@@ -8652,19 +8788,6 @@ var ContextIcon = () => {
8652
8788
  }
8653
8789
  );
8654
8790
  };
8655
- var ContextTrigger = ({ children, ...props }) => {
8656
- return /* @__PURE__ */ jsxRuntime.jsx(PopoverTrigger, { asChild: true, children: children ?? /* @__PURE__ */ jsxRuntime.jsx(Button, { type: "button", variant: "ghost", ...props, children: /* @__PURE__ */ jsxRuntime.jsx(ContextIcon, {}) }) });
8657
- };
8658
- var ContextContent = ({
8659
- className,
8660
- ...props
8661
- }) => /* @__PURE__ */ jsxRuntime.jsx(
8662
- PopoverContent,
8663
- {
8664
- className: cn("min-w-60 divide-y overflow-hidden p-0", className),
8665
- ...props
8666
- }
8667
- );
8668
8791
  var ContextContentHeader = ({
8669
8792
  children,
8670
8793
  className,
@@ -8844,6 +8967,9 @@ var ContextCacheUsage = ({
8844
8967
  );
8845
8968
  };
8846
8969
 
8970
+ // src/components/chat/chat-composer.tsx
8971
+ init_utils();
8972
+
8847
8973
  // src/components/ui/item.tsx
8848
8974
  init_utils();
8849
8975
  var itemVariants = classVarianceAuthority.cva(
@@ -8966,6 +9092,120 @@ function ItemActions({ className, ...props }) {
8966
9092
  );
8967
9093
  }
8968
9094
 
9095
+ // src/components/ui/popover.tsx
9096
+ init_utils();
9097
+ function Popover({
9098
+ ...props
9099
+ }) {
9100
+ return /* @__PURE__ */ jsxRuntime.jsx(radixUi.Popover.Root, { "data-slot": "popover", ...props });
9101
+ }
9102
+ function PopoverTrigger({
9103
+ ...props
9104
+ }) {
9105
+ return /* @__PURE__ */ jsxRuntime.jsx(radixUi.Popover.Trigger, { "data-slot": "popover-trigger", ...props });
9106
+ }
9107
+ function PopoverContent({
9108
+ className,
9109
+ align = "center",
9110
+ sideOffset = 4,
9111
+ ...props
9112
+ }) {
9113
+ return /* @__PURE__ */ jsxRuntime.jsx(radixUi.Popover.Portal, { children: /* @__PURE__ */ jsxRuntime.jsx(
9114
+ radixUi.Popover.Content,
9115
+ {
9116
+ "data-slot": "popover-content",
9117
+ align,
9118
+ sideOffset,
9119
+ className: cn(
9120
+ "z-50 flex w-72 origin-(--radix-popover-content-transform-origin) flex-col gap-4 rounded-none bg-popover p-4 text-sm text-popover-foreground shadow-md ring-1 ring-foreground/10 outline-hidden duration-150 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95",
9121
+ className
9122
+ ),
9123
+ ...props
9124
+ }
9125
+ ) });
9126
+ }
9127
+ function PopoverAnchor({
9128
+ ...props
9129
+ }) {
9130
+ return /* @__PURE__ */ jsxRuntime.jsx(radixUi.Popover.Anchor, { "data-slot": "popover-anchor", ...props });
9131
+ }
9132
+ var MOBILE_QUERY = "(max-width: 767px)";
9133
+ function subscribe(onChange) {
9134
+ const mql = window.matchMedia(MOBILE_QUERY);
9135
+ mql.addEventListener("change", onChange);
9136
+ return () => mql.removeEventListener("change", onChange);
9137
+ }
9138
+ function useIsMobile() {
9139
+ return react.useSyncExternalStore(
9140
+ subscribe,
9141
+ () => window.matchMedia(MOBILE_QUERY).matches,
9142
+ // No viewport when server-rendered; assume desktop, as the SDK does.
9143
+ () => false
9144
+ );
9145
+ }
9146
+
9147
+ // src/components/ui/responsive-panel.tsx
9148
+ init_utils();
9149
+ function ResponsivePanel({
9150
+ open,
9151
+ onOpenChange,
9152
+ trigger,
9153
+ title,
9154
+ children,
9155
+ align = "start",
9156
+ popoverClassName
9157
+ }) {
9158
+ const isMobile = useIsMobile();
9159
+ if (!isMobile) {
9160
+ return /* @__PURE__ */ jsxRuntime.jsxs(Popover, { open, onOpenChange, children: [
9161
+ /* @__PURE__ */ jsxRuntime.jsx(PopoverTrigger, { asChild: true, children: trigger }),
9162
+ /* @__PURE__ */ jsxRuntime.jsx(
9163
+ PopoverContent,
9164
+ {
9165
+ side: "top",
9166
+ align,
9167
+ className: cn("w-64 overflow-hidden rounded-xl p-1", popoverClassName),
9168
+ children
9169
+ }
9170
+ )
9171
+ ] });
9172
+ }
9173
+ return /* @__PURE__ */ jsxRuntime.jsxs(radixUi.Dialog.Root, { open, onOpenChange, children: [
9174
+ /* @__PURE__ */ jsxRuntime.jsx(radixUi.Dialog.Trigger, { asChild: true, children: trigger }),
9175
+ /* @__PURE__ */ jsxRuntime.jsxs(radixUi.Dialog.Portal, { children: [
9176
+ /* @__PURE__ */ jsxRuntime.jsx(
9177
+ radixUi.Dialog.Overlay,
9178
+ {
9179
+ "data-slot": "responsive-panel-overlay",
9180
+ className: "fixed inset-0 z-50 bg-black/40 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0"
9181
+ }
9182
+ ),
9183
+ /* @__PURE__ */ jsxRuntime.jsxs(
9184
+ radixUi.Dialog.Content,
9185
+ {
9186
+ "data-slot": "responsive-panel-sheet",
9187
+ "aria-describedby": void 0,
9188
+ className: cn(
9189
+ "fixed inset-x-0 bottom-0 z-50 flex max-h-[80svh] flex-col overflow-hidden rounded-t-2xl border-t border-border bg-popover pb-[env(safe-area-inset-bottom)] text-popover-foreground shadow-lg",
9190
+ "data-[state=closed]:animate-out data-[state=closed]:slide-out-to-bottom data-[state=open]:animate-in data-[state=open]:slide-in-from-bottom"
9191
+ ),
9192
+ children: [
9193
+ /* @__PURE__ */ jsxRuntime.jsx(radixUi.Dialog.Title, { className: "sr-only", children: title }),
9194
+ /* @__PURE__ */ jsxRuntime.jsx(
9195
+ "div",
9196
+ {
9197
+ "aria-hidden": "true",
9198
+ className: "mx-auto mt-2.5 mb-1 h-1 w-9 shrink-0 rounded-full bg-muted-foreground/30"
9199
+ }
9200
+ ),
9201
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-h-0 flex-1 overflow-y-auto overscroll-contain p-1", children })
9202
+ ]
9203
+ }
9204
+ )
9205
+ ] })
9206
+ ] });
9207
+ }
9208
+
8969
9209
  // src/components/ui/skeleton.tsx
8970
9210
  init_utils();
8971
9211
  function Skeleton({ className, ...props }) {
@@ -9139,6 +9379,7 @@ function SkillsEmptyState() {
9139
9379
  ] })
9140
9380
  ] });
9141
9381
  }
9382
+ var TOOLS_ITEM_CLASS = "gap-3 rounded-md px-3 py-2 pointer-coarse:min-h-11 pointer-coarse:text-[15px]";
9142
9383
  function ChatComposer(props) {
9143
9384
  return /* @__PURE__ */ jsxRuntime.jsx(PromptInputProvider, { children: /* @__PURE__ */ jsxRuntime.jsx(ChatComposerInner, { ...props }) });
9144
9385
  }
@@ -9236,6 +9477,8 @@ function ChatComposerInner({
9236
9477
  isRunning,
9237
9478
  disabled = false,
9238
9479
  disabledReason,
9480
+ awaitingAnswer = false,
9481
+ placeholder,
9239
9482
  tokenUsage,
9240
9483
  onComposerError,
9241
9484
  showBrowser = false,
@@ -9259,7 +9502,6 @@ function ChatComposerInner({
9259
9502
  onViewModeChange
9260
9503
  }) {
9261
9504
  const { adapter } = useAgentChatAdapterContext();
9262
- const [profileMenuOpen, setProfileMenuOpen] = react.useState(false);
9263
9505
  const [browserProfiles, setBrowserProfiles] = react.useState(null);
9264
9506
  const loadBrowserProfiles = react.useCallback(() => {
9265
9507
  if (!adapter.listBrowserProfiles) return;
@@ -9272,16 +9514,15 @@ function ChatComposerInner({
9272
9514
  loadBrowserProfiles();
9273
9515
  }, [browserProfilesEnabled, browserProfileId, browserProfiles, loadBrowserProfiles]);
9274
9516
  const activeBrowserProfile = (browserProfiles ?? []).find((p) => p.id === browserProfileId) ?? null;
9275
- const profileTriggerLabel = activeBrowserProfile && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "max-w-24 truncate text-xs", children: activeBrowserProfile.name });
9276
9517
  const { textInput, attachments } = usePromptInputController();
9277
- const status = isRunning ? "streaming" : disabled ? "error" : "ready";
9518
+ const status = isRunning && !awaitingAnswer ? "streaming" : disabled ? "error" : "ready";
9278
9519
  const [adapterCommands, setAdapterCommands] = react.useState([]);
9279
9520
  const [skillsLoading, setSkillsLoading] = react.useState(false);
9280
9521
  const [buttonMenuOpen, setButtonMenuOpen] = react.useState(false);
9281
9522
  const [menuMode, setMenuMode] = react.useState("all");
9282
9523
  const [addMenuOpen, setAddMenuOpen] = react.useState(false);
9283
9524
  const [menuDismissed, setMenuDismissed] = react.useState(false);
9284
- const showSlashMenu = !menuDismissed && (textInput.value.startsWith("/") || buttonMenuOpen);
9525
+ const showSlashMenu = !menuDismissed && !awaitingAnswer && (textInput.value.startsWith("/") || buttonMenuOpen);
9285
9526
  react.useEffect(() => {
9286
9527
  if (menuDismissed && !textInput.value.startsWith("/") && !buttonMenuOpen) {
9287
9528
  setMenuDismissed(false);
@@ -9477,16 +9718,13 @@ function ChatComposerInner({
9477
9718
  });
9478
9719
  return;
9479
9720
  }
9480
- if (isRunning) {
9481
- await onStop();
9482
- }
9483
9721
  await onSend(
9484
9722
  text,
9485
9723
  images.length > 0 ? images : void 0,
9486
9724
  pending.length > 0 ? pending : void 0
9487
9725
  );
9488
9726
  },
9489
- [onSend, onStop, onComposerError, isRunning, disabled]
9727
+ [onSend, onComposerError, disabled]
9490
9728
  );
9491
9729
  const handlePromptInputError = react.useCallback(
9492
9730
  (err) => {
@@ -9494,6 +9732,59 @@ function ChatComposerInner({
9494
9732
  },
9495
9733
  [onComposerError]
9496
9734
  );
9735
+ const [contextOpen, setContextOpen] = react.useState(false);
9736
+ const contextPercentLabel = tokenUsage?.contextWindow ? `${Math.min(100, Math.round(tokenUsage.totalTokens / tokenUsage.contextWindow * 100))}%` : "0%";
9737
+ const canAttach = !disabled && !awaitingAnswer;
9738
+ const showPaneToggles = Boolean(
9739
+ canShowBrowser && onToggleBrowser || canShowWorkspace && onToggleWorkspace
9740
+ );
9741
+ const canPickProfile = Boolean(
9742
+ browserProfilesEnabled && onSelectBrowserProfile && adapter.listBrowserProfiles
9743
+ );
9744
+ const showToolsPanel = canAttach || showPaneToggles || canPickProfile;
9745
+ const viewModeToggle = onViewModeChange ? /* @__PURE__ */ jsxRuntime.jsxs(
9746
+ ButtonGroup,
9747
+ {
9748
+ "aria-label": "Chat view mode",
9749
+ className: "overflow-hidden rounded-full border border-border bg-muted/40 p-0.5",
9750
+ children: [
9751
+ /* @__PURE__ */ jsxRuntime.jsx(
9752
+ PromptInputButton,
9753
+ {
9754
+ "aria-label": "Simple view",
9755
+ "aria-pressed": viewMode === "simple",
9756
+ tooltip: "Simple \u2014 just the conversation",
9757
+ variant: viewMode === "simple" ? "secondary" : "ghost",
9758
+ onClick: () => onViewModeChange("simple"),
9759
+ className: cn(
9760
+ // 32px is a comfortable segment for a cursor and a miss for a
9761
+ // thumb, so touch takes both halves to the 44px floor.
9762
+ "size-8 rounded-full pointer-coarse:size-11",
9763
+ viewMode === "simple" && "shadow-sm"
9764
+ ),
9765
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MessageSquareIcon, { className: "size-[18px] pointer-coarse:size-5" })
9766
+ }
9767
+ ),
9768
+ /* @__PURE__ */ jsxRuntime.jsx(
9769
+ PromptInputButton,
9770
+ {
9771
+ "aria-label": "Advanced view",
9772
+ "aria-pressed": viewMode === "expert",
9773
+ tooltip: "Advanced \u2014 every step the agent took",
9774
+ variant: viewMode === "expert" ? "secondary" : "ghost",
9775
+ onClick: () => onViewModeChange("expert"),
9776
+ className: cn(
9777
+ // 32px is a comfortable segment for a cursor and a miss for a
9778
+ // thumb, so touch takes both halves to the 44px floor.
9779
+ "size-8 rounded-full pointer-coarse:size-11",
9780
+ viewMode === "expert" && "shadow-sm"
9781
+ ),
9782
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ListTreeIcon, { className: "size-[18px] pointer-coarse:size-5" })
9783
+ }
9784
+ )
9785
+ ]
9786
+ }
9787
+ ) : null;
9497
9788
  return /* @__PURE__ */ jsxRuntime.jsxs(
9498
9789
  Popover,
9499
9790
  {
@@ -9546,154 +9837,132 @@ function ChatComposerInner({
9546
9837
  PromptInputTextarea,
9547
9838
  {
9548
9839
  ref: textareaRef,
9549
- placeholder: disabled ? disabledReason ?? "Session disabled" : "Send a message...",
9840
+ placeholder: disabled ? disabledReason ?? "Session disabled" : placeholder ?? "Send a message...",
9550
9841
  disabled
9551
9842
  }
9552
9843
  ) }),
9553
9844
  /* @__PURE__ */ jsxRuntime.jsxs(PromptInputFooter, { children: [
9554
9845
  /* @__PURE__ */ jsxRuntime.jsxs(PromptInputTools, { children: [
9555
- !disabled && /* @__PURE__ */ jsxRuntime.jsxs(Popover, { open: addMenuOpen, onOpenChange: setAddMenuOpen, children: [
9556
- /* @__PURE__ */ jsxRuntime.jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(PromptInputButton, { "aria-label": "Add", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.PlusIcon, { className: "size-4" }) }) }),
9557
- /* @__PURE__ */ jsxRuntime.jsx(
9558
- PopoverContent,
9559
- {
9560
- side: "top",
9561
- align: "start",
9562
- className: "w-64 overflow-hidden rounded-xl p-1",
9563
- children: /* @__PURE__ */ jsxRuntime.jsx(Command, { children: /* @__PURE__ */ jsxRuntime.jsxs(CommandList, { children: [
9846
+ showToolsPanel && /* @__PURE__ */ jsxRuntime.jsx(
9847
+ ResponsivePanel,
9848
+ {
9849
+ open: addMenuOpen,
9850
+ onOpenChange: (open) => {
9851
+ setAddMenuOpen(open);
9852
+ if (open && canPickProfile && !browserProfileLocked && browserProfiles === null) {
9853
+ loadBrowserProfiles();
9854
+ }
9855
+ },
9856
+ title: "Composer tools",
9857
+ trigger: /* @__PURE__ */ jsxRuntime.jsx(
9858
+ PromptInputButton,
9859
+ {
9860
+ "aria-label": "Composer tools",
9861
+ tooltip: "Attach files, panes and browser profile",
9862
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.PlusIcon, { className: "size-4" })
9863
+ }
9864
+ ),
9865
+ children: /* @__PURE__ */ jsxRuntime.jsx(Command, { children: /* @__PURE__ */ jsxRuntime.jsxs(CommandList, { className: "max-md:max-h-none", children: [
9866
+ canAttach && /* @__PURE__ */ jsxRuntime.jsxs(CommandGroup, { heading: "Attach", children: [
9564
9867
  /* @__PURE__ */ jsxRuntime.jsxs(
9565
9868
  CommandItem,
9566
9869
  {
9567
- disabled: true,
9568
- className: "gap-3 rounded-md px-3 py-2 [&>svg:last-child]:hidden",
9870
+ onSelect: () => {
9871
+ setAddMenuOpen(false);
9872
+ attachments.openFileDialog();
9873
+ },
9874
+ className: TOOLS_ITEM_CLASS,
9569
9875
  children: [
9570
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.CloudIcon, { className: "size-4 shrink-0 text-sky-500" }),
9571
- "Add from OneDrive",
9572
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronRightIcon, { className: "ml-auto size-4 shrink-0 text-muted-foreground" })
9876
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.PaperclipIcon, { className: "size-4 shrink-0 text-muted-foreground" }),
9877
+ "Add local files"
9573
9878
  ]
9574
9879
  }
9575
9880
  ),
9576
- /* @__PURE__ */ jsxRuntime.jsxs(
9881
+ /* @__PURE__ */ jsxRuntime.jsxs(CommandItem, { disabled: true, className: TOOLS_ITEM_CLASS, children: [
9882
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.CloudIcon, { className: "size-4 shrink-0 text-sky-500" }),
9883
+ "Add from OneDrive"
9884
+ ] }),
9885
+ /* @__PURE__ */ jsxRuntime.jsxs(CommandItem, { disabled: true, className: TOOLS_ITEM_CLASS, children: [
9886
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.HardDriveIcon, { className: "size-4 shrink-0 text-emerald-500" }),
9887
+ "Add from Google Drive"
9888
+ ] })
9889
+ ] }),
9890
+ showPaneToggles && // Panes stay available while a question is pending —
9891
+ // only attachments are suppressed there.
9892
+ /* @__PURE__ */ jsxRuntime.jsxs(CommandGroup, { heading: "Panes", children: [
9893
+ canShowBrowser && onToggleBrowser && /* @__PURE__ */ jsxRuntime.jsxs(
9577
9894
  CommandItem,
9578
9895
  {
9579
- disabled: true,
9580
- className: "gap-3 rounded-md px-3 py-2 [&>svg:last-child]:hidden",
9896
+ onSelect: onToggleBrowser,
9897
+ "data-checked": showBrowser || void 0,
9898
+ className: TOOLS_ITEM_CLASS,
9581
9899
  children: [
9582
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.HardDriveIcon, { className: "size-4 shrink-0 text-emerald-500" }),
9583
- "Add from Google Drive"
9900
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.GlobeIcon, { className: "size-4 shrink-0 text-muted-foreground" }),
9901
+ "Browser"
9584
9902
  ]
9585
9903
  }
9586
9904
  ),
9587
- /* @__PURE__ */ jsxRuntime.jsxs(
9905
+ canShowWorkspace && onToggleWorkspace && /* @__PURE__ */ jsxRuntime.jsxs(
9588
9906
  CommandItem,
9589
9907
  {
9590
- onSelect: () => {
9591
- setAddMenuOpen(false);
9592
- attachments.openFileDialog();
9593
- },
9594
- className: "gap-3 rounded-md px-3 py-2 [&>svg:last-child]:hidden",
9908
+ onSelect: onToggleWorkspace,
9909
+ "data-checked": showWorkspace || void 0,
9910
+ className: TOOLS_ITEM_CLASS,
9595
9911
  children: [
9596
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.PaperclipIcon, { className: "size-4 shrink-0 text-muted-foreground" }),
9597
- "Add local files"
9912
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.FolderIcon, { className: "size-4 shrink-0 text-muted-foreground" }),
9913
+ "Workspace"
9598
9914
  ]
9599
9915
  }
9600
9916
  )
9601
- ] }) })
9602
- }
9603
- )
9604
- ] }),
9605
- canShowBrowser && onToggleBrowser && /* @__PURE__ */ jsxRuntime.jsx(
9606
- PromptInputButton,
9607
- {
9608
- "aria-label": showBrowser ? "Hide browser pane" : "Show browser pane",
9609
- "aria-pressed": showBrowser,
9610
- onClick: onToggleBrowser,
9611
- tooltip: showBrowser ? "Hide browser pane" : "Show browser pane",
9612
- className: showBrowser ? "bg-accent text-foreground" : void 0,
9613
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.GlobeIcon, { className: "size-4" })
9614
- }
9615
- ),
9616
- browserProfilesEnabled && onSelectBrowserProfile && adapter.listBrowserProfiles && (browserProfileLocked ? (
9617
- // A profile is bound at session creation; once a session is
9618
- // active the choice can't change. Render disabled (via
9619
- // aria-disabled, not the `disabled` attribute, so the
9620
- // tooltip still shows on hover) with an explanation.
9621
- /* @__PURE__ */ jsxRuntime.jsxs(
9622
- PromptInputButton,
9623
- {
9624
- "aria-label": "Select browser profile",
9625
- "aria-disabled": true,
9626
- "aria-pressed": !!browserProfileId,
9627
- tooltip: activeBrowserProfile ? `Browser profile: ${activeBrowserProfile.name} (locked for this session)` : "A browser profile must be chosen before starting the session",
9628
- className: `cursor-not-allowed opacity-50 ${browserProfileId ? "bg-accent text-foreground" : ""}`,
9629
- children: [
9630
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.IdCardIcon, { className: "size-4" }),
9631
- profileTriggerLabel
9632
- ]
9633
- }
9634
- )
9635
- ) : /* @__PURE__ */ jsxRuntime.jsxs(
9636
- Popover,
9637
- {
9638
- open: profileMenuOpen,
9639
- onOpenChange: (open) => {
9640
- setProfileMenuOpen(open);
9641
- if (open) loadBrowserProfiles();
9642
- },
9643
- children: [
9644
- /* @__PURE__ */ jsxRuntime.jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs(
9645
- PromptInputButton,
9646
- {
9647
- "aria-label": "Select browser profile",
9648
- "aria-pressed": !!browserProfileId,
9649
- tooltip: activeBrowserProfile ? `Browser profile: ${activeBrowserProfile.name}` : "Select browser profile",
9650
- className: browserProfileId ? "bg-accent text-foreground" : void 0,
9651
- children: [
9652
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.IdCardIcon, { className: "size-4" }),
9653
- profileTriggerLabel
9654
- ]
9655
- }
9656
- ) }),
9657
- /* @__PURE__ */ jsxRuntime.jsx(
9658
- PopoverContent,
9659
- {
9660
- side: "top",
9661
- align: "start",
9662
- className: "w-64 overflow-hidden rounded-xl p-1",
9663
- children: /* @__PURE__ */ jsxRuntime.jsx(Command, { children: /* @__PURE__ */ jsxRuntime.jsx(CommandList, { children: [
9664
- { id: null, name: "No profile" },
9665
- ...browserProfiles ?? []
9666
- ].map((p) => /* @__PURE__ */ jsxRuntime.jsx(
9917
+ ] }),
9918
+ canPickProfile && /* @__PURE__ */ jsxRuntime.jsx(CommandGroup, { heading: "Browser profile", children: browserProfileLocked ? (
9919
+ // A profile is bound at session creation and cannot
9920
+ // change mid-session. In a menu there is nothing to
9921
+ // hover, so the reason is stated outright rather
9922
+ // than hidden in a tooltip as it was on the button.
9923
+ /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
9924
+ /* @__PURE__ */ jsxRuntime.jsxs(
9667
9925
  CommandItem,
9668
9926
  {
9669
- onSelect: () => {
9670
- setProfileMenuOpen(false);
9671
- onSelectBrowserProfile(p.id);
9672
- },
9673
- "data-checked": (browserProfileId ?? null) === p.id || void 0,
9674
- className: "gap-3 rounded-md px-3 py-2",
9675
- children: p.name
9927
+ disabled: true,
9928
+ "data-checked": !!browserProfileId || void 0,
9929
+ className: TOOLS_ITEM_CLASS,
9930
+ children: [
9931
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.IdCardIcon, { className: "size-4 shrink-0 text-muted-foreground" }),
9932
+ activeBrowserProfile?.name ?? "No profile"
9933
+ ]
9934
+ }
9935
+ ),
9936
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "px-3 pt-0.5 pb-1.5 text-xs text-muted-foreground", children: activeBrowserProfile ? "Locked for this session." : "A profile can only be chosen before the session starts." })
9937
+ ] })
9938
+ ) : (
9939
+ // data-checked lights CommandItem's built-in
9940
+ // trailing check on the selected entry.
9941
+ [
9942
+ { id: null, name: "No profile" },
9943
+ ...browserProfiles ?? []
9944
+ ].map((p) => /* @__PURE__ */ jsxRuntime.jsxs(
9945
+ CommandItem,
9946
+ {
9947
+ onSelect: () => {
9948
+ setAddMenuOpen(false);
9949
+ onSelectBrowserProfile?.(p.id);
9676
9950
  },
9677
- p.id ?? "none"
9678
- )) }) })
9679
- }
9680
- )
9681
- ]
9682
- }
9683
- )),
9684
- canShowWorkspace && onToggleWorkspace && /* @__PURE__ */ jsxRuntime.jsx(
9685
- PromptInputButton,
9686
- {
9687
- "aria-label": showWorkspace ? "Hide workspace pane" : "Show workspace pane",
9688
- "aria-pressed": showWorkspace,
9689
- onClick: onToggleWorkspace,
9690
- tooltip: showWorkspace ? "Hide workspace pane" : "Show workspace pane",
9691
- className: showWorkspace ? "bg-accent text-foreground" : void 0,
9692
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.FolderIcon, { className: "size-4" })
9951
+ "data-checked": (browserProfileId ?? null) === p.id || void 0,
9952
+ className: TOOLS_ITEM_CLASS,
9953
+ children: [
9954
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.IdCardIcon, { className: "size-4 shrink-0 text-muted-foreground" }),
9955
+ p.name
9956
+ ]
9957
+ },
9958
+ p.id ?? "none"
9959
+ ))
9960
+ ) })
9961
+ ] }) })
9693
9962
  }
9694
9963
  ),
9695
- !disabled && tokenUsage && tokenUsage.contextWindow > 0 && /* @__PURE__ */ jsxRuntime.jsxs(
9696
- Context,
9964
+ !disabled && tokenUsage && tokenUsage.contextWindow > 0 && /* @__PURE__ */ jsxRuntime.jsx(
9965
+ ContextValueProvider,
9697
9966
  {
9698
9967
  usedTokens: tokenUsage.totalTokens,
9699
9968
  maxTokens: tokenUsage.contextWindow,
@@ -9707,82 +9976,68 @@ function ChatComposerInner({
9707
9976
  inputTokenDetails: void 0,
9708
9977
  outputTokenDetails: void 0
9709
9978
  },
9710
- children: [
9711
- /* @__PURE__ */ jsxRuntime.jsx(
9712
- ContextTrigger,
9713
- {
9714
- size: "icon-sm",
9715
- className: "rounded-sm border border-input/70 text-foreground hover:bg-accent"
9716
- }
9717
- ),
9718
- /* @__PURE__ */ jsxRuntime.jsxs(ContextContent, { children: [
9719
- /* @__PURE__ */ jsxRuntime.jsx(ContextContentHeader, {}),
9720
- /* @__PURE__ */ jsxRuntime.jsx(ContextContentBody, { children: tokenUsage.totalTokens > 0 ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
9721
- /* @__PURE__ */ jsxRuntime.jsx(ContextInputUsage, { children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between text-xs", children: [
9722
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: "Input" }),
9723
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: tokenUsage.inputTokens.toLocaleString() })
9724
- ] }) }),
9725
- /* @__PURE__ */ jsxRuntime.jsx(ContextOutputUsage, { children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between text-xs", children: [
9726
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: "Output" }),
9727
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: tokenUsage.outputTokens.toLocaleString() })
9728
- ] }) }),
9729
- tokenUsage.reasoningTokens > 0 && /* @__PURE__ */ jsxRuntime.jsx(ContextReasoningUsage, { children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between text-xs", children: [
9730
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: "Reasoning" }),
9731
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: tokenUsage.reasoningTokens.toLocaleString() })
9732
- ] }) }),
9733
- tokenUsage.cachedInputTokens > 0 && /* @__PURE__ */ jsxRuntime.jsx(ContextCacheUsage, { children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between text-xs", children: [
9734
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: "Cache" }),
9735
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: tokenUsage.cachedInputTokens.toLocaleString() })
9736
- ] }) })
9737
- ] }) : /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground text-center py-1", children: "Empty" }) }),
9738
- tokenUsage.totalTokens > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex w-full items-center justify-end gap-3 bg-secondary p-2", children: /* @__PURE__ */ jsxRuntime.jsx(
9739
- Button,
9979
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
9980
+ ResponsivePanel,
9981
+ {
9982
+ open: contextOpen,
9983
+ onOpenChange: setContextOpen,
9984
+ title: "Session context",
9985
+ align: "end",
9986
+ popoverClassName: "min-w-60 divide-y p-0",
9987
+ trigger: /* @__PURE__ */ jsxRuntime.jsxs(
9988
+ PromptInputButton,
9740
9989
  {
9741
- type: "button",
9742
- size: "xs",
9743
- onClick: () => onSend("/compress"),
9744
- children: "Compress"
9990
+ "aria-label": `Session context: ${contextPercentLabel} of the window used`,
9991
+ tooltip: "Session context",
9992
+ variant: "ghost",
9993
+ className: "h-9 gap-1 rounded-md border-0 bg-transparent px-1.5 text-muted-foreground hover:bg-accent/60 hover:text-foreground pointer-coarse:h-11",
9994
+ children: [
9995
+ /* @__PURE__ */ jsxRuntime.jsx(ContextIcon, { className: "size-4" }),
9996
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[11px] tabular-nums", children: contextPercentLabel })
9997
+ ]
9745
9998
  }
9746
- ) })
9747
- ] })
9748
- ]
9999
+ ),
10000
+ children: [
10001
+ /* @__PURE__ */ jsxRuntime.jsx(ContextContentHeader, {}),
10002
+ /* @__PURE__ */ jsxRuntime.jsx(ContextContentBody, { children: tokenUsage.totalTokens > 0 ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
10003
+ /* @__PURE__ */ jsxRuntime.jsx(ContextInputUsage, { children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between text-xs", children: [
10004
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: "Input" }),
10005
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: tokenUsage.inputTokens.toLocaleString() })
10006
+ ] }) }),
10007
+ /* @__PURE__ */ jsxRuntime.jsx(ContextOutputUsage, { children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between text-xs", children: [
10008
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: "Output" }),
10009
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: tokenUsage.outputTokens.toLocaleString() })
10010
+ ] }) }),
10011
+ tokenUsage.reasoningTokens > 0 && /* @__PURE__ */ jsxRuntime.jsx(ContextReasoningUsage, { children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between text-xs", children: [
10012
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: "Reasoning" }),
10013
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: tokenUsage.reasoningTokens.toLocaleString() })
10014
+ ] }) }),
10015
+ tokenUsage.cachedInputTokens > 0 && /* @__PURE__ */ jsxRuntime.jsx(ContextCacheUsage, { children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between text-xs", children: [
10016
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: "Cache" }),
10017
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: tokenUsage.cachedInputTokens.toLocaleString() })
10018
+ ] }) })
10019
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground text-center py-1", children: "Empty" }) }),
10020
+ tokenUsage.totalTokens > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex w-full items-center justify-end gap-3 bg-secondary p-2", children: /* @__PURE__ */ jsxRuntime.jsx(
10021
+ Button,
10022
+ {
10023
+ type: "button",
10024
+ size: "xs",
10025
+ className: "pointer-coarse:h-11 pointer-coarse:px-4 pointer-coarse:text-sm",
10026
+ onClick: () => {
10027
+ setContextOpen(false);
10028
+ onSend("/compress");
10029
+ },
10030
+ children: "Compress"
10031
+ }
10032
+ ) })
10033
+ ]
10034
+ }
10035
+ )
9749
10036
  }
9750
10037
  )
9751
10038
  ] }),
9752
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
9753
- onViewModeChange && /* @__PURE__ */ jsxRuntime.jsxs(
9754
- ButtonGroup,
9755
- {
9756
- "aria-label": "Chat view mode",
9757
- className: "overflow-hidden rounded-md border border-border",
9758
- children: [
9759
- /* @__PURE__ */ jsxRuntime.jsx(
9760
- Button,
9761
- {
9762
- type: "button",
9763
- size: "xs",
9764
- className: "capitalize font-normal",
9765
- variant: viewMode === "simple" ? "secondary" : "ghost",
9766
- "aria-pressed": viewMode === "simple",
9767
- onClick: () => onViewModeChange("simple"),
9768
- children: "Simple"
9769
- }
9770
- ),
9771
- /* @__PURE__ */ jsxRuntime.jsx(
9772
- Button,
9773
- {
9774
- type: "button",
9775
- size: "xs",
9776
- className: "capitalize font-normal",
9777
- variant: viewMode === "expert" ? "secondary" : "ghost",
9778
- "aria-pressed": viewMode === "expert",
9779
- onClick: () => onViewModeChange("expert"),
9780
- children: "Advanced"
9781
- }
9782
- )
9783
- ]
9784
- }
9785
- ),
10039
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ml-auto flex shrink-0 items-center gap-2", children: [
10040
+ viewModeToggle,
9786
10041
  !disabled && /* @__PURE__ */ jsxRuntime.jsx(
9787
10042
  PromptInputSubmit,
9788
10043
  {
@@ -11723,16 +11978,34 @@ var WORKING_ON_IT_DELAY_MS = 250;
11723
11978
  function hasUserFacingAskContent(msg) {
11724
11979
  return !!msg.toolCalls?.some((tc) => tc.toolName === "ask_user_question");
11725
11980
  }
11726
- function isAwaitingUserInput(messages) {
11981
+ function pendingAskCall(messages) {
11727
11982
  for (let i = messages.length - 1; i >= 0; i--) {
11728
11983
  const m = messages[i];
11729
- if (m.role === "user") return false;
11984
+ if (m.role === "user") return null;
11730
11985
  if (m.role !== "assistant") continue;
11731
- return !!m.toolCalls?.some(
11986
+ return m.toolCalls?.find(
11732
11987
  (tc) => tc.toolName === "ask_user_question" && tc.status === "running"
11733
- );
11988
+ ) ?? null;
11734
11989
  }
11735
- return false;
11990
+ return null;
11991
+ }
11992
+ function askComposerPolicy(pending) {
11993
+ if (!pending) return null;
11994
+ const questions = askQuestionsOf(pending);
11995
+ if (!isConversationalAsk(questions)) {
11996
+ return { locked: true, reason: "Answer the questions above to continue." };
11997
+ }
11998
+ const question = questions[0];
11999
+ if (!conversationalAskAcceptsFreeText(question)) {
12000
+ return {
12001
+ locked: true,
12002
+ reason: "Pick one of the options above to continue."
12003
+ };
12004
+ }
12005
+ return {
12006
+ locked: false,
12007
+ placeholder: hasChoices(question) ? "Pick one above, or type your answer\u2026" : "Type your answer\u2026"
12008
+ };
11736
12009
  }
11737
12010
  function messageToEntries(msg, isLast, artifactFallbacks) {
11738
12011
  if (msg.role === "system") {
@@ -11815,7 +12088,14 @@ function messageToEntries(msg, isLast, artifactFallbacks) {
11815
12088
  const parsed = parseTerminalResult(tc.result, tc.args);
11816
12089
  if (parsed && (parsed.exit_code !== 0 || parsed.error)) continue;
11817
12090
  }
11818
- entries.push({ kind: "tool", key: tc.id, tc });
12091
+ entries.push({
12092
+ kind: "tool",
12093
+ key: tc.id,
12094
+ tc,
12095
+ // Only ask_user_question reads this, to suppress a prompt the
12096
+ // agent already spelled out in its message body.
12097
+ assistantContent: tc.toolName === "ask_user_question" ? msg.content : void 0
12098
+ });
11819
12099
  }
11820
12100
  }
11821
12101
  if (effectiveHasContent) {
@@ -12060,6 +12340,8 @@ function TimelineEntryItem({
12060
12340
  {
12061
12341
  tc: entry.tc,
12062
12342
  resolvedArtifactName: entry.resolvedArtifactName,
12343
+ assistantContent: entry.assistantContent,
12344
+ viewMode: "expert",
12063
12345
  onFileSelect
12064
12346
  }
12065
12347
  ),
@@ -12583,7 +12865,14 @@ function IterationGroup({
12583
12865
  (tc) => tc.toolName === "ask_user_question"
12584
12866
  );
12585
12867
  if (askCall) {
12586
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-1 py-0.5", children: /* @__PURE__ */ jsxRuntime.jsx(AskUserQuestionToolBlock, { tc: askCall }) });
12868
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-1 py-0.5", children: /* @__PURE__ */ jsxRuntime.jsx(
12869
+ AskUserQuestionToolBlock,
12870
+ {
12871
+ tc: askCall,
12872
+ assistantContent: message.content,
12873
+ viewMode: "simple"
12874
+ }
12875
+ ) });
12587
12876
  }
12588
12877
  if (isIterationLive(message)) {
12589
12878
  return /* @__PURE__ */ jsxRuntime.jsx(
@@ -12926,7 +13215,12 @@ function ChatThread({
12926
13215
  onOpenIntegrations
12927
13216
  }) {
12928
13217
  const groups = react.useMemo(() => groupMessages(messages), [messages]);
12929
- const awaitingInput = react.useMemo(() => isAwaitingUserInput(messages), [messages]);
13218
+ const pendingAsk = react.useMemo(() => pendingAskCall(messages), [messages]);
13219
+ const askPolicy = react.useMemo(
13220
+ () => askComposerPolicy(pendingAsk),
13221
+ [pendingAsk?.id, pendingAsk?.args]
13222
+ );
13223
+ const awaitingInput = askPolicy !== null;
12930
13224
  const orbActivity = react.useMemo(
12931
13225
  () => deriveOrbActivity(
12932
13226
  messages,
@@ -12935,8 +13229,9 @@ function ChatThread({
12935
13229
  [messages, viewMode]
12936
13230
  );
12937
13231
  const showWorkingOnIt = useDelayedRunningIndicator(isRunning) && !awaitingInput;
12938
- const composerDisabled = disabled || awaitingInput;
12939
- const composerDisabledReason = awaitingInput ? "Answer the question above to continue." : disabledReason;
13232
+ const composerLocked = askPolicy?.locked ?? false;
13233
+ const composerDisabled = disabled || composerLocked;
13234
+ const composerDisabledReason = composerLocked ? askPolicy?.reason : disabledReason;
12940
13235
  const artifactFallbacks = react.useMemo(() => {
12941
13236
  const fallbacks = {};
12942
13237
  const createArtifactToolCallIds = [];
@@ -12981,6 +13276,8 @@ function ChatThread({
12981
13276
  isRunning,
12982
13277
  disabled: composerDisabled,
12983
13278
  disabledReason: composerDisabledReason,
13279
+ awaitingAnswer: askPolicy !== null && !askPolicy.locked,
13280
+ placeholder: askPolicy?.placeholder,
12984
13281
  tokenUsage,
12985
13282
  onComposerError,
12986
13283
  showBrowser,
@@ -13013,10 +13310,16 @@ function ChatThread({
13013
13310
  )
13014
13311
  ] });
13015
13312
  if (messages.length === 0 && !isLoadingHistory && !disabled) {
13016
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-1 flex-col items-center justify-center overflow-hidden bg-background px-3 text-base", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "w-full", children: [
13017
- /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "mb-8 text-center text-2xl font-bold text-foreground", children: "What should we do ?" }),
13018
- composerArea
13019
- ] }) });
13313
+ return (
13314
+ // A centred hero is a deliberate desktop look. On a phone it leaves the
13315
+ // input floating mid-screen with dead space under it, and the keyboard
13316
+ // then opens across that gap. There the composer keeps its usual place
13317
+ // at the bottom edge and the greeting takes the space above it.
13318
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-1 flex-col items-center justify-end overflow-hidden bg-background px-3 text-base md:justify-center", children: [
13319
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex w-full flex-1 flex-col justify-center md:flex-none", children: /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "mb-6 text-center text-2xl font-bold text-foreground md:mb-8", children: "What should we do ?" }) }),
13320
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full", children: composerArea })
13321
+ ] })
13322
+ );
13020
13323
  }
13021
13324
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-1 flex-col overflow-hidden bg-background text-base", children: [
13022
13325
  /* @__PURE__ */ jsxRuntime.jsxs(Conversation, { className: "relative flex-1 min-h-0", children: [
@@ -13096,6 +13399,7 @@ function IntegrationsBandSlot({
13096
13399
  }
13097
13400
  );
13098
13401
  }
13402
+ init_utils();
13099
13403
 
13100
13404
  // src/components/ai-elements/file-tree.tsx
13101
13405
  init_utils();
@@ -13265,9 +13569,6 @@ function FileTreeFile({
13265
13569
  }
13266
13570
  );
13267
13571
  }
13268
-
13269
- // src/components/workspace/workspace-panel.tsx
13270
- init_utils();
13271
13572
  var SKELETON_WIDTHS2 = [75, 60, 90, 65, 80, 70, 85, 55];
13272
13573
  var DEFAULT_WIDTH = 400;
13273
13574
  var MIN_WIDTH = 300;
@@ -13322,14 +13623,22 @@ function RenderEntries({
13322
13623
  }) {
13323
13624
  return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: entries.map((entry) => {
13324
13625
  if (entry.kind === "dir") {
13325
- return /* @__PURE__ */ jsxRuntime.jsx(FileTreeFolder, { name: entry.name, path: entry.path, children: entry.children && entry.children.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
13326
- RenderEntries,
13626
+ return /* @__PURE__ */ jsxRuntime.jsx(
13627
+ FileTreeFolder,
13327
13628
  {
13328
- entries: entry.children,
13329
- onDelete,
13330
- downloadUrlFor
13331
- }
13332
- ) }, entry.path);
13629
+ name: entry.name,
13630
+ path: entry.path,
13631
+ children: entry.children && entry.children.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
13632
+ RenderEntries,
13633
+ {
13634
+ entries: entry.children,
13635
+ onDelete,
13636
+ downloadUrlFor
13637
+ }
13638
+ )
13639
+ },
13640
+ entry.path
13641
+ );
13333
13642
  }
13334
13643
  const fileName2 = entry.name;
13335
13644
  return /* @__PURE__ */ jsxRuntime.jsxs(FileTreeFile, { name: entry.name, path: entry.path, children: [
@@ -13388,6 +13697,7 @@ function WorkspacePanel({
13388
13697
  const [deleteTarget, setDeleteTarget] = react.useState(null);
13389
13698
  const [expandedPaths, setExpandedPaths] = react.useState(/* @__PURE__ */ new Set());
13390
13699
  const [width, setWidth] = react.useState(DEFAULT_WIDTH);
13700
+ const narrow = useIsMobile();
13391
13701
  const sessionIdRef = react.useRef(sessionId);
13392
13702
  const isResizing = react.useRef(false);
13393
13703
  const startX = react.useRef(0);
@@ -13537,7 +13847,14 @@ function WorkspacePanel({
13537
13847
  setDeleteTarget(null);
13538
13848
  }
13539
13849
  },
13540
- [adapter, disabled, fetchTree, onSelectedPathChange, selectedPath, sessionId]
13850
+ [
13851
+ adapter,
13852
+ disabled,
13853
+ fetchTree,
13854
+ onSelectedPathChange,
13855
+ selectedPath,
13856
+ sessionId
13857
+ ]
13541
13858
  );
13542
13859
  const onResizeStart = react.useCallback(
13543
13860
  (event) => {
@@ -13577,11 +13894,11 @@ function WorkspacePanel({
13577
13894
  "data-testid": "workspace-panel",
13578
13895
  className: cn(
13579
13896
  "relative z-10 flex h-full min-h-0 flex-col overflow-hidden border-l border-muted-foreground/20 bg-card",
13580
- fillParent && "w-full"
13897
+ (fillParent || narrow) && "w-full"
13581
13898
  ),
13582
- style: fillParent ? void 0 : { width, minWidth: MIN_WIDTH, maxWidth: MAX_WIDTH },
13899
+ style: fillParent || narrow ? void 0 : { width, minWidth: MIN_WIDTH, maxWidth: MAX_WIDTH },
13583
13900
  children: [
13584
- !fillParent && /* @__PURE__ */ jsxRuntime.jsx(
13901
+ !(fillParent || narrow) && /* @__PURE__ */ jsxRuntime.jsx(
13585
13902
  "div",
13586
13903
  {
13587
13904
  className: "absolute inset-y-0 left-0 z-20 w-1.5 cursor-col-resize transition-colors hover:bg-primary/20 active:bg-primary/30",
@@ -13754,10 +14071,18 @@ function ConfirmDialog2({
13754
14071
  ] }),
13755
14072
  /* @__PURE__ */ jsxRuntime.jsxs(DialogFooter, { children: [
13756
14073
  /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "outline", disabled: loading, onClick: onCancel, children: "Cancel" }),
13757
- /* @__PURE__ */ jsxRuntime.jsxs(Button, { variant: "destructive", disabled: loading, onClick: handleConfirm, children: [
13758
- loading && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2Icon, { className: "mr-1.5 size-3.5 animate-spin" }),
13759
- confirmLabel
13760
- ] })
14074
+ /* @__PURE__ */ jsxRuntime.jsxs(
14075
+ Button,
14076
+ {
14077
+ variant: "destructive",
14078
+ disabled: loading,
14079
+ onClick: handleConfirm,
14080
+ children: [
14081
+ loading && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2Icon, { className: "mr-1.5 size-3.5 animate-spin" }),
14082
+ confirmLabel
14083
+ ]
14084
+ }
14085
+ )
13761
14086
  ] })
13762
14087
  ] })
13763
14088
  }
@@ -14056,7 +14381,11 @@ function applyAgentChatEvent(state, event) {
14056
14381
  case "ask_user_question.response":
14057
14382
  return withMessages(
14058
14383
  nextState,
14059
- applyAskUserQuestionResponse(nextState.messages, event.data)
14384
+ applyAskUserQuestionResponse(
14385
+ nextState.messages,
14386
+ event.eventId,
14387
+ event.data
14388
+ )
14060
14389
  );
14061
14390
  case "policy.denied":
14062
14391
  return applyPolicyDenied(nextState, event.data);
@@ -14807,34 +15136,59 @@ function applyUserFeedback(messages, data) {
14807
15136
  }
14808
15137
  return messages;
14809
15138
  }
14810
- function applyAskUserQuestionResponse(messages, data) {
15139
+ function applyAskUserQuestionResponse(messages, eventId, data) {
14811
15140
  const targetToolId = stringValue2(data.tool_call_id);
14812
15141
  const responses = Array.isArray(data.responses) ? data.responses : void 0;
14813
15142
  if (!targetToolId || !responses) return messages;
15143
+ const answers = responses.map((response) => {
15144
+ const row = objectValue(response) ?? {};
15145
+ return {
15146
+ question: stringValue2(row.question),
15147
+ answer: stringValue2(row.answer),
15148
+ is_other: Boolean(row.is_other)
15149
+ };
15150
+ });
14814
15151
  const next = [...messages];
14815
15152
  for (let i = next.length - 1; i >= 0; i--) {
14816
15153
  const msg = next[i];
14817
15154
  if (!msg?.toolCalls?.some((tc) => tc.id === targetToolId)) continue;
15155
+ const alreadyAnswered = msg.toolCalls.some(
15156
+ (tc) => tc.id === targetToolId && tc.askUserQuestionAnswers !== void 0
15157
+ );
15158
+ if (alreadyAnswered) return messages;
14818
15159
  next[i] = {
14819
15160
  ...msg,
14820
15161
  toolCalls: msg.toolCalls.map(
14821
- (tc) => tc.id === targetToolId ? {
14822
- ...tc,
14823
- askUserQuestionAnswers: responses.map((response) => {
14824
- const row = objectValue(response) ?? {};
14825
- return {
14826
- question: stringValue2(row.question),
14827
- answer: stringValue2(row.answer),
14828
- is_other: Boolean(row.is_other)
14829
- };
14830
- })
14831
- } : tc
15162
+ (tc) => tc.id === targetToolId ? { ...tc, askUserQuestionAnswers: answers } : tc
14832
15163
  )
14833
15164
  };
14834
- return next;
15165
+ return appendConversationalAnswer(next, i, eventId, answers);
14835
15166
  }
14836
15167
  return messages;
14837
15168
  }
15169
+ function appendConversationalAnswer(messages, askIndex, eventId, answers) {
15170
+ if (answers.length !== 1) return messages;
15171
+ const answer = answers[0].answer.trim();
15172
+ if (!answer) return messages;
15173
+ const id = `evt-${eventId}`;
15174
+ if (messages.some((m) => m.id === id)) return messages;
15175
+ const next = [...messages];
15176
+ for (let i = askIndex + 1; i < next.length; i++) {
15177
+ const msg = next[i];
15178
+ if (msg.role !== "user" || !msg.id.startsWith("local-")) continue;
15179
+ if (msg.status === "error") continue;
15180
+ next[i] = { ...msg, id, content: answer, status: "complete" };
15181
+ return next;
15182
+ }
15183
+ next.push({
15184
+ id,
15185
+ role: "user",
15186
+ content: answer,
15187
+ createdAt: /* @__PURE__ */ new Date(),
15188
+ status: "complete"
15189
+ });
15190
+ return next;
15191
+ }
14838
15192
  function applyPolicyDenied(state, data) {
14839
15193
  const messages = [...state.messages];
14840
15194
  const policyIdx = findLastAssistantIndex(messages);
@@ -15526,6 +15880,11 @@ function parseGoalDefinition(content) {
15526
15880
  var RIGHT_STACK_STYLE = {
15527
15881
  ["--right-stack-w"]: "440px"
15528
15882
  };
15883
+ var MOBILE_PANE_LABELS = {
15884
+ chat: "Chat",
15885
+ browser: "Browser",
15886
+ workspace: "Workspace"
15887
+ };
15529
15888
  function AgentChat({
15530
15889
  adapter,
15531
15890
  agentId,
@@ -15598,7 +15957,14 @@ function AgentChat({
15598
15957
  const handleToggleWorkspace = react.useCallback(() => {
15599
15958
  setShowWorkspace((prev) => !prev);
15600
15959
  }, []);
15601
- const showMobileToggle = rightStackVisible;
15960
+ const mobilePanes = react.useMemo(() => {
15961
+ const panes = ["chat"];
15962
+ if (browserVisible) panes.push("browser");
15963
+ if (workspaceVisible) panes.push("workspace");
15964
+ return panes;
15965
+ }, [browserVisible, workspaceVisible]);
15966
+ const activeMobilePane = mobilePanes.includes(mobileView) ? mobileView : "chat";
15967
+ const showMobileToggle = mobilePanes.length > 1;
15602
15968
  return /* @__PURE__ */ jsxRuntime.jsx(
15603
15969
  AgentChatAdapterProvider,
15604
15970
  {
@@ -15625,43 +15991,36 @@ function AgentChat({
15625
15991
  ),
15626
15992
  style: { direction: "ltr", ...RIGHT_STACK_STYLE },
15627
15993
  children: [
15628
- showMobileToggle && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "md:hidden flex shrink-0 border-b border-line bg-card", children: [
15629
- /* @__PURE__ */ jsxRuntime.jsx(
15630
- "button",
15631
- {
15632
- type: "button",
15633
- onClick: () => setMobileView("chat"),
15634
- "aria-pressed": mobileView === "chat",
15635
- className: cn(
15636
- "flex-1 px-4 py-3 text-sm font-medium border-b-2 -mb-px transition-colors",
15637
- mobileView === "chat" ? "border-primary text-foreground" : "border-transparent text-subtle hover:text-foreground"
15638
- ),
15639
- children: "Chat"
15640
- }
15641
- ),
15642
- /* @__PURE__ */ jsxRuntime.jsx(
15643
- "button",
15644
- {
15645
- type: "button",
15646
- onClick: () => setMobileView("workspace"),
15647
- "aria-pressed": mobileView === "workspace",
15648
- className: cn(
15649
- "flex-1 px-4 py-3 text-sm font-medium border-b-2 -mb-px transition-colors",
15650
- mobileView === "workspace" ? "border-primary text-foreground" : "border-transparent text-subtle hover:text-foreground"
15651
- ),
15652
- children: "Workspace"
15653
- }
15654
- )
15655
- ] }),
15994
+ showMobileToggle && /* @__PURE__ */ jsxRuntime.jsx(
15995
+ "div",
15996
+ {
15997
+ "data-testid": "mobile-pane-toggle",
15998
+ className: "md:hidden flex shrink-0 border-b border-line bg-card",
15999
+ children: mobilePanes.map((pane) => /* @__PURE__ */ jsxRuntime.jsx(
16000
+ "button",
16001
+ {
16002
+ type: "button",
16003
+ onClick: () => setMobileView(pane),
16004
+ "aria-pressed": activeMobilePane === pane,
16005
+ className: cn(
16006
+ "flex-1 min-h-11 px-4 py-3 text-sm font-medium border-b-2 -mb-px transition-colors",
16007
+ activeMobilePane === pane ? "border-primary text-foreground" : "border-transparent text-subtle hover:text-foreground"
16008
+ ),
16009
+ children: MOBILE_PANE_LABELS[pane]
16010
+ },
16011
+ pane
16012
+ ))
16013
+ }
16014
+ ),
15656
16015
  /* @__PURE__ */ jsxRuntime.jsx(
15657
16016
  "div",
15658
16017
  {
15659
16018
  "data-testid": "chat-panel",
15660
- "data-mobile-view": mobileView,
16019
+ "data-mobile-view": activeMobilePane,
15661
16020
  className: cn(
15662
- // Phone: full width column, hidden when workspace tab active.
16021
+ // Phone: full width column, hidden while another pane is picked.
15663
16022
  "flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden",
15664
- showMobileToggle && "data-[mobile-view=workspace]:hidden md:flex!",
16023
+ showMobileToggle && "data-[mobile-view=browser]:hidden data-[mobile-view=workspace]:hidden md:flex!",
15665
16024
  // md+: positioning depends on whether the browser/workspace
15666
16025
  // right stack is laid out. When the browser pane is shown
15667
16026
  // we pin a fixed-width column on the right and absolutely
@@ -15718,7 +16077,7 @@ function AgentChat({
15718
16077
  "div",
15719
16078
  {
15720
16079
  "data-testid": "right-stack",
15721
- "data-mobile-view": mobileView,
16080
+ "data-mobile-view": activeMobilePane,
15722
16081
  className: cn(
15723
16082
  // Phone: full width column, hidden when chat tab active.
15724
16083
  "flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden",
@@ -15736,9 +16095,13 @@ function AgentChat({
15736
16095
  "div",
15737
16096
  {
15738
16097
  "data-testid": "browser-panel",
16098
+ "data-mobile-view": activeMobilePane,
15739
16099
  className: cn(
15740
16100
  "min-h-0 w-full overflow-hidden",
15741
- bothPanesVisible ? "h-1/2 border-b border-line" : "h-full"
16101
+ // With both panes the desktop splits the column in half.
16102
+ // A phone gives whichever pane is selected the whole
16103
+ // height instead — see the mobile toggle above.
16104
+ bothPanesVisible ? "h-full data-[mobile-view=workspace]:hidden md:block! md:h-1/2 md:border-b md:border-line" : "h-full"
15742
16105
  ),
15743
16106
  children: /* @__PURE__ */ jsxRuntime.jsx(
15744
16107
  BrowserPane,
@@ -15755,7 +16118,8 @@ function AgentChat({
15755
16118
  "div",
15756
16119
  {
15757
16120
  "data-testid": "workspace-panel-frame",
15758
- className: bothPanesVisible ? "h-1/2 min-h-0 w-full overflow-hidden" : "min-h-0 h-full",
16121
+ "data-mobile-view": activeMobilePane,
16122
+ className: bothPanesVisible ? "h-full min-h-0 w-full overflow-hidden data-[mobile-view=browser]:hidden md:block! md:h-1/2" : "min-h-0 h-full",
15759
16123
  children: /* @__PURE__ */ jsxRuntime.jsx(
15760
16124
  WorkspacePanel,
15761
16125
  {
@@ -16055,7 +16419,7 @@ function IntegrationsPage({ agentId, adapter, onBack }) {
16055
16419
  {
16056
16420
  type: "button",
16057
16421
  onClick: onBack,
16058
- className: "flex w-fit items-center gap-1.5 text-sm text-muted-foreground hover:text-foreground",
16422
+ className: "-mx-2 inline-flex min-h-11 w-fit items-center gap-1.5 rounded-lg px-2 text-sm text-muted-foreground pointer-fine:mx-0 pointer-fine:min-h-0 pointer-fine:px-0 pointer-fine:hover:text-foreground",
16059
16423
  children: [
16060
16424
  /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ArrowLeftIcon, { className: "h-4 w-4" }),
16061
16425
  "Back"
@@ -16199,7 +16563,7 @@ function CodingAgentsPanel({ agentId, adapter, onBack }) {
16199
16563
  {
16200
16564
  type: "button",
16201
16565
  onClick: onBack,
16202
- className: "flex w-fit items-center gap-1.5 text-sm text-muted-foreground hover:text-foreground",
16566
+ className: "-mx-2 inline-flex min-h-11 w-fit items-center gap-1.5 rounded-lg px-2 text-sm text-muted-foreground pointer-fine:mx-0 pointer-fine:min-h-0 pointer-fine:px-0 pointer-fine:hover:text-foreground",
16203
16567
  children: [
16204
16568
  /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ArrowLeftIcon, { className: "h-4 w-4" }),
16205
16569
  "Back"
@@ -16340,7 +16704,7 @@ function ProviderCard({
16340
16704
  type: "button",
16341
16705
  disabled: busy,
16342
16706
  onClick: submit,
16343
- className: "inline-flex items-center justify-center gap-1.5 rounded-md border border-border px-3 py-1.5 text-xs text-foreground hover:bg-accent disabled:opacity-60",
16707
+ className: "inline-flex min-h-11 items-center justify-center gap-1.5 rounded-md border border-border px-3 text-xs text-foreground disabled:opacity-60 pointer-fine:min-h-0 pointer-fine:py-1.5 pointer-fine:hover:bg-accent",
16344
16708
  children: [
16345
16709
  busy && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2Icon, { className: "h-3.5 w-3.5 animate-spin" }),
16346
16710
  connected ? "Update" : "Connect"
@@ -16360,6 +16724,65 @@ function ProviderCard({
16360
16724
  ] })
16361
16725
  ] });
16362
16726
  }
16727
+
16728
+ // src/components/inbox/inbox-answers.ts
16729
+ function hasChoices2(question) {
16730
+ return (question.choices?.length ?? 0) > 0;
16731
+ }
16732
+ function parseInboxQuestions(item) {
16733
+ const raw = item.payload.questions;
16734
+ if (!Array.isArray(raw)) return [];
16735
+ return raw.map((entry) => {
16736
+ const question = entry;
16737
+ const choices = Array.isArray(question.choices) ? question.choices.map((choice) => ({
16738
+ label: typeof choice.label === "string" ? choice.label : "",
16739
+ description: typeof choice.description === "string" ? choice.description : void 0
16740
+ })).filter((choice) => choice.label) : void 0;
16741
+ return {
16742
+ prompt: typeof question.prompt === "string" ? question.prompt : "",
16743
+ choices,
16744
+ allowOther: question.allow_other !== false
16745
+ };
16746
+ }).filter((question) => question.prompt);
16747
+ }
16748
+ function isOtherAnswer(question, draft) {
16749
+ return hasChoices2(question) && !!draft?.useOther;
16750
+ }
16751
+ function answerText(question, draft) {
16752
+ const fromMenu = hasChoices2(question) && !draft?.useOther;
16753
+ return (fromMenu ? draft?.picked ?? "" : draft?.typed ?? "").trim();
16754
+ }
16755
+ function buildInboxResponse(question, draft) {
16756
+ return {
16757
+ question: question.prompt,
16758
+ answer: answerText(question, draft),
16759
+ is_other: isOtherAnswer(question, draft)
16760
+ };
16761
+ }
16762
+ var TICK_MS = 3e4;
16763
+ function formatExpiresIn(expiresAtIso, nowMs) {
16764
+ const remainingMs = Date.parse(expiresAtIso) - nowMs;
16765
+ if (remainingMs <= 0) return "Expired";
16766
+ if (remainingMs < 6e4) return "Expires in under a minute";
16767
+ const minutes = Math.round(remainingMs / 6e4);
16768
+ return `Expires in ~${minutes} minute${minutes === 1 ? "" : "s"}`;
16769
+ }
16770
+ function useAnswerWindow(expiresAtIso) {
16771
+ const [now, setNow] = react.useState(() => Date.now());
16772
+ react.useEffect(() => {
16773
+ const id = setInterval(() => setNow(Date.now()), TICK_MS);
16774
+ return () => clearInterval(id);
16775
+ }, []);
16776
+ if (!expiresAtIso) {
16777
+ return { expired: false, label: "" };
16778
+ }
16779
+ return {
16780
+ expired: Date.parse(expiresAtIso) <= now,
16781
+ label: formatExpiresIn(expiresAtIso, now)
16782
+ };
16783
+ }
16784
+
16785
+ // src/components/inbox/inbox-panel.tsx
16363
16786
  init_utils();
16364
16787
  var DEFAULT_LIMIT = 50;
16365
16788
  function requireInboxAdapter(adapter) {
@@ -16394,8 +16817,33 @@ function kindIcon(kind) {
16394
16817
  if (kind === "progress_checkin") return lucideReact.TimerIcon;
16395
16818
  return lucideReact.CircleDotIcon;
16396
16819
  }
16820
+ var VIEWS = {
16821
+ active: {
16822
+ statuses: ["pending"],
16823
+ kinds: ["input_required", "action_required", "governance_gate"],
16824
+ empty: "Nothing needs you right now"
16825
+ },
16826
+ updates: {
16827
+ statuses: ["pending"],
16828
+ kinds: ["task_complete", "progress_checkin"],
16829
+ empty: "No news from your agent"
16830
+ },
16831
+ history: {
16832
+ statuses: ["acknowledged", "responded", "expired"],
16833
+ empty: "No history yet"
16834
+ }
16835
+ };
16836
+ var VIEW_NAMES = Object.keys(VIEWS);
16837
+ function belongsToView(item, view) {
16838
+ const kinds = VIEWS[view].kinds;
16839
+ return VIEWS[view].statuses.includes(item.status) && (kinds === void 0 || kinds.includes(item.kind));
16840
+ }
16397
16841
  function sortItems(items) {
16398
- return [...items].sort((a, b) => {
16842
+ const byId = /* @__PURE__ */ new Map();
16843
+ for (const item of items) {
16844
+ if (!byId.has(item.id)) byId.set(item.id, item);
16845
+ }
16846
+ return [...byId.values()].sort((a, b) => {
16399
16847
  if (a.createdAt !== b.createdAt) return a.createdAt > b.createdAt ? -1 : 1;
16400
16848
  return b.id - a.id;
16401
16849
  });
@@ -16409,37 +16857,106 @@ function InboxBody({ body, muted }) {
16409
16857
  }
16410
16858
  );
16411
16859
  }
16860
+ var FIELD_CLASS = "h-9 w-full border border-line bg-background px-2 text-sm text-foreground outline-none focus:border-primary disabled:opacity-50";
16861
+ function errorMessage(error, fallback) {
16862
+ return error instanceof Error && error.message ? error.message : fallback;
16863
+ }
16864
+ function useAction() {
16865
+ const [error, setError] = react.useState(null);
16866
+ const [busy, setBusy] = react.useState(false);
16867
+ const run = react.useCallback(
16868
+ async (fallback, action) => {
16869
+ setBusy(true);
16870
+ setError(null);
16871
+ try {
16872
+ await action();
16873
+ } catch (err) {
16874
+ setError(errorMessage(err, fallback));
16875
+ } finally {
16876
+ setBusy(false);
16877
+ }
16878
+ },
16879
+ []
16880
+ );
16881
+ return { error, busy, run };
16882
+ }
16883
+ function ActionError({ message }) {
16884
+ if (!message) return null;
16885
+ return /* @__PURE__ */ jsxRuntime.jsx("p", { role: "alert", className: "text-sm text-destructive", children: message });
16886
+ }
16887
+ function ExpiryNote({ label, expired }) {
16888
+ return /* @__PURE__ */ jsxRuntime.jsx(
16889
+ "p",
16890
+ {
16891
+ className: cn(
16892
+ "text-xs",
16893
+ expired ? "text-muted-foreground" : "text-foreground"
16894
+ ),
16895
+ children: expired ? "The agent stopped waiting for this answer and moved on, so it can no longer be submitted." : `${label}. Deleting it will not answer the agent; it only clears it from your inbox.`
16896
+ }
16897
+ );
16898
+ }
16899
+ var OTHER_OPTION = "other";
16412
16900
  function QuestionInput({
16413
- prompt,
16414
- choices,
16901
+ question,
16902
+ draft,
16415
16903
  disabled,
16416
- value,
16417
- onChange
16904
+ onPickChoice,
16905
+ onChooseOther,
16906
+ onType
16418
16907
  }) {
16908
+ const { prompt, choices, allowOther } = question;
16909
+ const typed = draft?.typed ?? "";
16910
+ const useOther = !!draft?.useOther;
16419
16911
  if (choices && choices.length > 0) {
16420
- return /* @__PURE__ */ jsxRuntime.jsxs(
16421
- "select",
16422
- {
16423
- "aria-label": prompt,
16424
- className: "h-9 w-full border border-line bg-background px-2 text-sm text-foreground outline-none focus:border-primary disabled:opacity-50",
16425
- disabled,
16426
- value,
16427
- onChange: (event) => onChange(event.target.value),
16428
- children: [
16429
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: "Select" }),
16430
- choices.map((choice) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: choice.label, children: choice.label }, choice.label))
16431
- ]
16432
- }
16912
+ const pickedIndex = choices.findIndex(
16913
+ (choice) => choice.label === draft?.picked
16433
16914
  );
16915
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1.5", children: [
16916
+ /* @__PURE__ */ jsxRuntime.jsxs(
16917
+ "select",
16918
+ {
16919
+ "aria-label": prompt,
16920
+ className: FIELD_CLASS,
16921
+ disabled,
16922
+ value: useOther ? OTHER_OPTION : pickedIndex >= 0 ? String(pickedIndex) : "",
16923
+ onChange: (event) => {
16924
+ const value = event.target.value;
16925
+ if (value === OTHER_OPTION) {
16926
+ onChooseOther();
16927
+ return;
16928
+ }
16929
+ const choice = choices[Number(value)];
16930
+ if (choice) onPickChoice(choice.label);
16931
+ },
16932
+ children: [
16933
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", disabled: true, children: "Select" }),
16934
+ choices.map((choice, index) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: String(index), children: choice.label }, choice.label)),
16935
+ allowOther && /* @__PURE__ */ jsxRuntime.jsx("option", { value: OTHER_OPTION, children: "Other" })
16936
+ ]
16937
+ }
16938
+ ),
16939
+ useOther && /* @__PURE__ */ jsxRuntime.jsx(
16940
+ "input",
16941
+ {
16942
+ "aria-label": `${prompt} (other)`,
16943
+ className: FIELD_CLASS,
16944
+ placeholder: "Type your answer\u2026",
16945
+ disabled,
16946
+ value: typed,
16947
+ onChange: (event) => onType(event.target.value)
16948
+ }
16949
+ )
16950
+ ] });
16434
16951
  }
16435
16952
  return /* @__PURE__ */ jsxRuntime.jsx(
16436
16953
  "input",
16437
16954
  {
16438
16955
  "aria-label": prompt,
16439
- className: "h-9 w-full border border-line bg-background px-2 text-sm text-foreground outline-none focus:border-primary disabled:opacity-50",
16956
+ className: FIELD_CLASS,
16440
16957
  disabled,
16441
- value,
16442
- onChange: (event) => onChange(event.target.value)
16958
+ value: typed,
16959
+ onChange: (event) => onType(event.target.value)
16443
16960
  }
16444
16961
  );
16445
16962
  }
@@ -16450,47 +16967,45 @@ function InboxDetailActions({
16450
16967
  onSessionSelect,
16451
16968
  children
16452
16969
  }) {
16453
- const [deleting, setDeleting] = react.useState(false);
16970
+ const { error, busy: deleting, run } = useAction();
16454
16971
  async function deleteItem() {
16455
16972
  if (!adapter.deleteInboxItem) return;
16456
- setDeleting(true);
16457
- try {
16458
- await onDeleted(item.id);
16459
- } finally {
16460
- setDeleting(false);
16461
- }
16973
+ await run("Failed to delete", () => onDeleted(item.id));
16462
16974
  }
16463
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap gap-2", children: [
16464
- /* @__PURE__ */ jsxRuntime.jsxs(
16465
- Button,
16466
- {
16467
- type: "button",
16468
- size: "sm",
16469
- onClick: () => onSessionSelect?.(item.sessionId, item),
16470
- "aria-label": "Open session",
16471
- children: [
16472
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ExternalLinkIcon, { className: "size-3.5" }),
16473
- "Open session"
16474
- ]
16475
- }
16476
- ),
16477
- children,
16478
- adapter.deleteInboxItem && /* @__PURE__ */ jsxRuntime.jsxs(
16479
- Button,
16480
- {
16481
- type: "button",
16482
- size: "sm",
16483
- variant: "ghost",
16484
- onClick: () => void deleteItem(),
16485
- disabled: deleting,
16486
- "aria-label": "Delete inbox item",
16487
- title: "Delete inbox item",
16488
- children: [
16489
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2Icon, { className: "size-3.5" }),
16490
- deleting ? "Deleting" : "Delete"
16491
- ]
16492
- }
16493
- )
16975
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
16976
+ /* @__PURE__ */ jsxRuntime.jsx(ActionError, { message: error }),
16977
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap gap-2", children: [
16978
+ /* @__PURE__ */ jsxRuntime.jsxs(
16979
+ Button,
16980
+ {
16981
+ type: "button",
16982
+ size: "sm",
16983
+ onClick: () => onSessionSelect?.(item.sessionId, item),
16984
+ "aria-label": "Open session",
16985
+ children: [
16986
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ExternalLinkIcon, { className: "size-3.5" }),
16987
+ "Open session"
16988
+ ]
16989
+ }
16990
+ ),
16991
+ children,
16992
+ adapter.deleteInboxItem && /* @__PURE__ */ jsxRuntime.jsxs(
16993
+ Button,
16994
+ {
16995
+ type: "button",
16996
+ size: "sm",
16997
+ variant: "ghost",
16998
+ onClick: () => void deleteItem(),
16999
+ disabled: deleting,
17000
+ "aria-label": "Delete inbox item",
17001
+ title: "Delete inbox item",
17002
+ children: [
17003
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2Icon, { className: "size-3.5" }),
17004
+ deleting ? "Deleting" : "Delete"
17005
+ ]
17006
+ }
17007
+ )
17008
+ ] })
16494
17009
  ] });
16495
17010
  }
16496
17011
  function InputRequiredDetail({
@@ -16500,36 +17015,31 @@ function InputRequiredDetail({
16500
17015
  onDeleted,
16501
17016
  onSessionSelect
16502
17017
  }) {
16503
- const questions = Array.isArray(item.payload.questions) ? item.payload.questions.map((question) => ({
16504
- prompt: typeof question.prompt === "string" ? question.prompt : "",
16505
- choices: Array.isArray(question.choices) ? question.choices.map((choice) => ({
16506
- label: typeof choice.label === "string" ? choice.label : "",
16507
- description: typeof choice.description === "string" ? choice.description : void 0
16508
- })).filter((choice) => choice.label) : void 0
16509
- })).filter((question) => question.prompt) : [];
16510
- const [answers, setAnswers] = react.useState({});
16511
- const [submitting, setSubmitting] = react.useState(false);
16512
- const disabled = item.status !== "pending" || submitting;
16513
- const canSubmit = questions.every((question) => answers[question.prompt]?.trim());
17018
+ const questions = react.useMemo(() => parseInboxQuestions(item), [item]);
17019
+ const [drafts, setDrafts] = react.useState({});
17020
+ const { error, busy: submitting, run } = useAction();
17021
+ const { expired, label: expiryLabel } = useAnswerWindow(item.expiresAt);
17022
+ const pending = item.status === "pending" && !expired;
17023
+ const disabled = !pending || submitting;
17024
+ const editDraft = (prompt, patch) => setDrafts((current) => ({
17025
+ ...current,
17026
+ [prompt]: { ...current[prompt], ...patch }
17027
+ }));
17028
+ const canSubmit = questions.length > 0 && questions.every((question) => answerText(question, drafts[question.prompt]));
16514
17029
  async function submit() {
16515
17030
  const toolCallId = typeof item.payload.tool_call_id === "string" ? item.payload.tool_call_id : "";
16516
17031
  if (!toolCallId || !canSubmit) return;
16517
- setSubmitting(true);
16518
- try {
16519
- const responses = questions.map((question) => ({
16520
- question: question.prompt,
16521
- answer: answers[question.prompt]?.trim() ?? "",
16522
- is_other: false
16523
- }));
17032
+ await run("Failed to submit your answer", async () => {
17033
+ const responses = questions.map(
17034
+ (question) => buildInboxResponse(question, drafts[question.prompt])
17035
+ );
16524
17036
  await adapter.submitAskUserQuestionResponse({
16525
17037
  sessionId: item.sessionId,
16526
17038
  toolCallId,
16527
17039
  responses
16528
17040
  });
16529
17041
  onUpdated(await adapter.getInboxItem({ itemId: item.id }));
16530
- } finally {
16531
- setSubmitting(false);
16532
- }
17042
+ });
16533
17043
  }
16534
17044
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
16535
17045
  item.body && /* @__PURE__ */ jsxRuntime.jsx(InboxBody, { body: item.body, muted: true }),
@@ -16538,17 +17048,25 @@ function InputRequiredDetail({
16538
17048
  /* @__PURE__ */ jsxRuntime.jsx(
16539
17049
  QuestionInput,
16540
17050
  {
16541
- prompt: question.prompt,
16542
- choices: question.choices,
17051
+ question,
17052
+ draft: drafts[question.prompt],
16543
17053
  disabled,
16544
- value: answers[question.prompt] ?? "",
16545
- onChange: (value) => setAnswers((current) => ({
16546
- ...current,
16547
- [question.prompt]: value
16548
- }))
17054
+ onPickChoice: (label) => (
17055
+ // Dropping the typed value keeps an abandoned draft from
17056
+ // resurfacing if the user switches back to Other.
17057
+ editDraft(question.prompt, {
17058
+ picked: label,
17059
+ typed: "",
17060
+ useOther: false
17061
+ })
17062
+ ),
17063
+ onChooseOther: () => editDraft(question.prompt, { useOther: true }),
17064
+ onType: (value) => editDraft(question.prompt, { typed: value })
16549
17065
  }
16550
17066
  )
16551
17067
  ] }, question.prompt)),
17068
+ item.status === "pending" && /* @__PURE__ */ jsxRuntime.jsx(ExpiryNote, { label: expiryLabel, expired }),
17069
+ /* @__PURE__ */ jsxRuntime.jsx(ActionError, { message: error }),
16552
17070
  /* @__PURE__ */ jsxRuntime.jsx(
16553
17071
  InboxDetailActions,
16554
17072
  {
@@ -16578,8 +17096,11 @@ function AckDetail({
16578
17096
  onDeleted,
16579
17097
  onSessionSelect
16580
17098
  }) {
17099
+ const { error, busy, run } = useAction();
16581
17100
  async function acknowledge() {
16582
- onUpdated(await adapter.acknowledgeInboxItem({ itemId: item.id }));
17101
+ await run("Failed to acknowledge", async () => {
17102
+ onUpdated(await adapter.acknowledgeInboxItem({ itemId: item.id }));
17103
+ });
16583
17104
  }
16584
17105
  const outcome = typeof item.payload.outcome === "string" ? item.payload.outcome : "";
16585
17106
  const duration = typeof item.payload.duration_seconds === "number" ? item.payload.duration_seconds : null;
@@ -16594,6 +17115,7 @@ function AckDetail({
16594
17115
  duration,
16595
17116
  " s)"
16596
17117
  ] }),
17118
+ /* @__PURE__ */ jsxRuntime.jsx(ActionError, { message: error }),
16597
17119
  /* @__PURE__ */ jsxRuntime.jsx(
16598
17120
  InboxDetailActions,
16599
17121
  {
@@ -16607,10 +17129,11 @@ function AckDetail({
16607
17129
  type: "button",
16608
17130
  size: "sm",
16609
17131
  onClick: () => void acknowledge(),
17132
+ disabled: busy,
16610
17133
  "aria-label": "Acknowledge inbox item",
16611
17134
  children: [
16612
17135
  /* @__PURE__ */ jsxRuntime.jsx(lucideReact.CheckIcon, { className: "size-3.5" }),
16613
- "Acknowledge"
17136
+ busy ? "Saving" : "Acknowledge"
16614
17137
  ]
16615
17138
  }
16616
17139
  )
@@ -16625,15 +17148,18 @@ function GovernanceDetail({
16625
17148
  onDeleted,
16626
17149
  onSessionSelect
16627
17150
  }) {
17151
+ const { error, busy, run } = useAction();
16628
17152
  async function decide(decision) {
16629
- onUpdated(
16630
- await adapter.respondGovernanceInboxItem({ itemId: item.id, decision })
16631
- );
17153
+ await run(`Failed to ${decision}`, async () => {
17154
+ onUpdated(
17155
+ await adapter.respondGovernanceInboxItem({ itemId: item.id, decision })
17156
+ );
17157
+ });
16632
17158
  }
16633
17159
  const toolName = typeof item.payload.tool_name === "string" ? item.payload.tool_name : "tool";
16634
17160
  const args = typeof item.payload.arguments_excerpt === "string" ? item.payload.arguments_excerpt : "";
16635
17161
  const reason = typeof item.payload.deny_reason === "string" ? item.payload.deny_reason : "";
16636
- const disabled = item.status !== "pending";
17162
+ const disabled = item.status !== "pending" || busy;
16637
17163
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
16638
17164
  /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
16639
17165
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-base font-semibold text-foreground", children: [
@@ -16644,6 +17170,7 @@ function GovernanceDetail({
16644
17170
  reason && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: reason })
16645
17171
  ] }),
16646
17172
  args && /* @__PURE__ */ jsxRuntime.jsx("pre", { className: "overflow-x-auto border border-line bg-muted p-3 text-xs", children: args }),
17173
+ /* @__PURE__ */ jsxRuntime.jsx(ActionError, { message: error }),
16647
17174
  /* @__PURE__ */ jsxRuntime.jsxs(
16648
17175
  InboxDetailActions,
16649
17176
  {
@@ -16685,23 +17212,22 @@ function ActionRequiredDetail({
16685
17212
  onDeleted,
16686
17213
  onSessionSelect
16687
17214
  }) {
16688
- const [submitting, setSubmitting] = react.useState(false);
17215
+ const { error, busy: submitting, run } = useAction();
16689
17216
  const actionType = typeof item.payload.action_type === "string" ? item.payload.action_type : "";
16690
17217
  const context = typeof item.payload.context === "string" ? item.payload.context : "";
16691
17218
  const disabled = item.status !== "pending" || submitting || !adapter.respondActionRequiredInboxItem;
16692
17219
  async function complete() {
16693
- if (!adapter.respondActionRequiredInboxItem) return;
16694
- setSubmitting(true);
16695
- try {
16696
- onUpdated(await adapter.respondActionRequiredInboxItem({ itemId: item.id }));
16697
- } finally {
16698
- setSubmitting(false);
16699
- }
17220
+ const respond = adapter.respondActionRequiredInboxItem;
17221
+ if (!respond) return;
17222
+ await run("Failed to mark the action complete", async () => {
17223
+ onUpdated(await respond({ itemId: item.id }));
17224
+ });
16700
17225
  }
16701
17226
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
16702
17227
  item.body && /* @__PURE__ */ jsxRuntime.jsx(InboxBody, { body: item.body }),
16703
17228
  context && context !== item.body && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "whitespace-pre-wrap text-sm text-muted-foreground", children: context }),
16704
17229
  actionType && /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant: "secondary", children: actionType }),
17230
+ /* @__PURE__ */ jsxRuntime.jsx(ActionError, { message: error }),
16705
17231
  /* @__PURE__ */ jsxRuntime.jsx(
16706
17232
  InboxDetailActions,
16707
17233
  {
@@ -16734,8 +17260,11 @@ function ProgressDetail({
16734
17260
  onDeleted,
16735
17261
  onSessionSelect
16736
17262
  }) {
17263
+ const { error, busy, run } = useAction();
16737
17264
  async function acknowledge() {
16738
- onUpdated(await adapter.acknowledgeInboxItem({ itemId: item.id }));
17265
+ await run("Failed to acknowledge", async () => {
17266
+ onUpdated(await adapter.acknowledgeInboxItem({ itemId: item.id }));
17267
+ });
16739
17268
  }
16740
17269
  const rows = [];
16741
17270
  for (const row of [
@@ -16753,6 +17282,7 @@ function ProgressDetail({
16753
17282
  /* @__PURE__ */ jsxRuntime.jsx("dt", { className: "w-20 shrink-0", children: String(label) }),
16754
17283
  /* @__PURE__ */ jsxRuntime.jsx("dd", { className: "min-w-0", children: String(value) })
16755
17284
  ] }, String(label))) }),
17285
+ /* @__PURE__ */ jsxRuntime.jsx(ActionError, { message: error }),
16756
17286
  /* @__PURE__ */ jsxRuntime.jsx(
16757
17287
  InboxDetailActions,
16758
17288
  {
@@ -16766,10 +17296,11 @@ function ProgressDetail({
16766
17296
  type: "button",
16767
17297
  size: "sm",
16768
17298
  onClick: () => void acknowledge(),
17299
+ disabled: busy,
16769
17300
  "aria-label": "Acknowledge inbox item",
16770
17301
  children: [
16771
17302
  /* @__PURE__ */ jsxRuntime.jsx(lucideReact.CheckIcon, { className: "size-3.5" }),
16772
- "Acknowledge"
17303
+ busy ? "Saving" : "Acknowledge"
16773
17304
  ]
16774
17305
  }
16775
17306
  )
@@ -16782,10 +17313,23 @@ function InboxDetail({
16782
17313
  adapter,
16783
17314
  onUpdated,
16784
17315
  onDeleted,
16785
- onSessionSelect
17316
+ onSessionSelect,
17317
+ onBack
16786
17318
  }) {
16787
17319
  const Icon = kindIcon(item.kind);
16788
- return /* @__PURE__ */ jsxRuntime.jsxs("section", { className: "min-w-0 flex-1 overflow-y-auto p-6", children: [
17320
+ return /* @__PURE__ */ jsxRuntime.jsxs("section", { className: "min-w-0 flex-1 overflow-y-auto p-4 md:p-6", children: [
17321
+ onBack && /* @__PURE__ */ jsxRuntime.jsxs(
17322
+ "button",
17323
+ {
17324
+ type: "button",
17325
+ onClick: onBack,
17326
+ className: "-ml-2 mb-2 inline-flex min-h-11 items-center gap-1.5 px-2 text-sm font-medium text-muted-foreground md:hidden",
17327
+ children: [
17328
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronLeftIcon, { className: "size-4" }),
17329
+ "Inbox"
17330
+ ]
17331
+ }
17332
+ ),
16789
17333
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-5 flex min-w-0 items-start gap-3", children: [
16790
17334
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-0.5 flex size-8 shrink-0 items-center justify-center border border-line text-muted-foreground", children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { className: "size-4" }) }),
16791
17335
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1", children: [
@@ -16857,6 +17401,7 @@ function InboxPanel({
16857
17401
  const [items, setItems] = react.useState([]);
16858
17402
  const [nextCursor, setNextCursor] = react.useState(null);
16859
17403
  const [internalSelectedId, setInternalSelectedId] = react.useState(null);
17404
+ const [view, setView] = react.useState("active");
16860
17405
  const [loading, setLoading] = react.useState(false);
16861
17406
  const [error, setError] = react.useState(null);
16862
17407
  const requestId = react.useRef(0);
@@ -16865,13 +17410,20 @@ function InboxPanel({
16865
17410
  () => items.find((item) => item.id === selectedItemId) ?? null,
16866
17411
  [items, selectedItemId]
16867
17412
  );
17413
+ const viewRef = react.useRef(view);
17414
+ viewRef.current = view;
16868
17415
  const applyItem = react.useCallback((nextItem) => {
16869
- setItems(
16870
- (current) => sortItems(
16871
- current.map((item) => item.id === nextItem.id ? nextItem : item)
16872
- )
16873
- );
17416
+ setItems((current) => {
17417
+ if (current.some((item) => item.id === nextItem.id)) {
17418
+ return sortItems([nextItem, ...current]);
17419
+ }
17420
+ return belongsToView(nextItem, viewRef.current) ? sortItems([nextItem, ...current]) : current;
17421
+ });
16874
17422
  }, []);
17423
+ const clearSelection = react.useCallback(() => {
17424
+ if (selectedId === void 0) setInternalSelectedId(null);
17425
+ onSelectedIdChange?.(null);
17426
+ }, [onSelectedIdChange, selectedId]);
16875
17427
  const selectItem = react.useCallback(
16876
17428
  async (itemId) => {
16877
17429
  if (selectedId === void 0) setInternalSelectedId(itemId);
@@ -16888,26 +17440,26 @@ function InboxPanel({
16888
17440
  async (cursor) => {
16889
17441
  const id = ++requestId.current;
16890
17442
  setLoading(true);
16891
- setError(null);
16892
17443
  try {
16893
17444
  const response = await inboxAdapter.listInbox({
17445
+ status: VIEWS[view].statuses,
17446
+ kind: VIEWS[view].kinds,
16894
17447
  cursor: cursor ?? void 0,
16895
17448
  limit
16896
17449
  });
16897
17450
  if (id !== requestId.current) return;
16898
- setItems(
16899
- (current) => sortItems(cursor ? [...current, ...response.items] : response.items)
16900
- );
17451
+ setItems((current) => sortItems([...response.items, ...current]));
16901
17452
  setNextCursor(response.nextCursor);
17453
+ setError(null);
16902
17454
  } catch (err) {
16903
17455
  if (id === requestId.current) {
16904
- setError(err instanceof Error ? err.message : "Failed to load inbox");
17456
+ setError(errorMessage(err, "Failed to load inbox"));
16905
17457
  }
16906
17458
  } finally {
16907
17459
  if (id === requestId.current) setLoading(false);
16908
17460
  }
16909
17461
  },
16910
- [inboxAdapter, limit]
17462
+ [inboxAdapter, limit, view]
16911
17463
  );
16912
17464
  react.useEffect(() => {
16913
17465
  void load(null);
@@ -16915,15 +17467,33 @@ function InboxPanel({
16915
17467
  react.useEffect(() => {
16916
17468
  const stream = inboxAdapter.openInboxStream();
16917
17469
  stream.addEventListener("item", (event) => {
16918
- const payload = JSON.parse(event.data);
16919
- if (typeof payload.item_id !== "number") return;
16920
- void inboxAdapter.getInboxItem({ itemId: payload.item_id }).then(applyItem);
17470
+ let itemId;
17471
+ try {
17472
+ itemId = JSON.parse(event.data).item_id;
17473
+ } catch {
17474
+ return;
17475
+ }
17476
+ if (typeof itemId !== "number") return;
17477
+ if (viewRef.current === "history") return;
17478
+ void inboxAdapter.getInboxItem({ itemId }).then(applyItem, () => void 0);
16921
17479
  });
16922
17480
  return () => stream.close();
16923
17481
  }, [applyItem, inboxAdapter]);
16924
17482
  function updateSelectedItem(item) {
16925
17483
  applyItem(item);
16926
17484
  }
17485
+ const selectView = react.useCallback(
17486
+ (next) => {
17487
+ if (next === view) return;
17488
+ setView(next);
17489
+ setItems([]);
17490
+ setNextCursor(null);
17491
+ setError(null);
17492
+ if (selectedId === void 0) setInternalSelectedId(null);
17493
+ onSelectedIdChange?.(null);
17494
+ },
17495
+ [onSelectedIdChange, selectedId, view]
17496
+ );
16927
17497
  const deleteItem = react.useCallback(
16928
17498
  async (itemId) => {
16929
17499
  if (!inboxAdapter.deleteInboxItem) return;
@@ -16937,55 +17507,80 @@ function InboxPanel({
16937
17507
  [inboxAdapter, onSelectedIdChange, selectedId, selectedItemId]
16938
17508
  );
16939
17509
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex h-full min-h-0 min-w-0 bg-background text-foreground", children: [
16940
- /* @__PURE__ */ jsxRuntime.jsxs("aside", { className: "flex w-80 min-w-72 max-w-sm shrink-0 flex-col border-r border-line", children: [
16941
- !hideHeader && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-h-14 items-center gap-2 border-b border-line px-4", children: [
16942
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.InboxIcon, { className: "size-4 text-muted-foreground" }),
16943
- /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "font-semibold", children: title })
16944
- ] }),
16945
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-h-0 flex-1 overflow-y-auto", children: [
16946
- error && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-4 py-3 text-sm text-destructive", children: error }),
16947
- !error && items.length === 0 && !loading && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-4 py-8 text-sm text-muted-foreground", children: "No inbox items" }),
16948
- items.map((item) => {
16949
- const Icon = kindIcon(item.kind);
16950
- const selected = item.id === selectedItemId;
16951
- return /* @__PURE__ */ jsxRuntime.jsxs(
17510
+ /* @__PURE__ */ jsxRuntime.jsxs(
17511
+ "aside",
17512
+ {
17513
+ className: cn(
17514
+ "flex min-w-0 flex-col border-line md:w-80 md:min-w-72 md:max-w-sm md:shrink-0 md:border-r",
17515
+ selectedItemId != null ? "hidden md:flex" : "w-full"
17516
+ ),
17517
+ children: [
17518
+ !hideHeader && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-h-14 items-center gap-2 border-b border-line px-4", children: [
17519
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.InboxIcon, { className: "size-4 text-muted-foreground" }),
17520
+ /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "font-semibold", children: title })
17521
+ ] }),
17522
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex gap-1 border-b border-line px-2 py-2", children: VIEW_NAMES.map((value) => /* @__PURE__ */ jsxRuntime.jsx(
16952
17523
  "button",
16953
17524
  {
16954
17525
  type: "button",
16955
- "aria-label": `Open inbox item ${item.title}`,
17526
+ onClick: () => selectView(value),
16956
17527
  className: cn(
16957
- "flex w-full min-w-0 gap-3 border-l-2 px-3 py-3 text-left transition-colors hover:bg-input",
16958
- selected ? "border-l-primary bg-line text-foreground" : "border-l-transparent text-subtle",
16959
- !item.readAt && "font-medium text-foreground"
17528
+ "px-2 py-1 text-xs capitalize transition-colors",
17529
+ view === value ? "bg-line font-medium text-foreground" : "text-muted-foreground hover:text-foreground"
16960
17530
  ),
16961
- onClick: () => void selectItem(item.id),
16962
- children: [
16963
- /* @__PURE__ */ jsxRuntime.jsx(Icon, { className: "mt-0.5 size-4 shrink-0 text-muted-foreground" }),
16964
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "min-w-0 flex-1", children: [
16965
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex min-w-0 items-center justify-between gap-2", children: [
16966
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate text-xs uppercase tracking-wide text-muted-foreground", children: kindLabel(item.kind) }),
16967
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "shrink-0 text-xs text-muted-foreground", children: formatRelative(item.createdAt) })
16968
- ] }),
16969
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "mt-1 block truncate text-sm", children: item.title }),
16970
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "mt-1 block text-xs text-muted-foreground", children: statusLabel(item.status) })
16971
- ] })
16972
- ]
17531
+ children: value
16973
17532
  },
16974
- item.id
16975
- );
16976
- }),
16977
- nextCursor && /* @__PURE__ */ jsxRuntime.jsx(
16978
- "button",
16979
- {
16980
- type: "button",
16981
- className: "w-full px-3 py-3 text-sm text-muted-foreground hover:bg-input",
16982
- onClick: () => void load(nextCursor),
16983
- disabled: loading,
16984
- children: loading ? "Loading" : "Load more"
16985
- }
16986
- )
16987
- ] })
16988
- ] }),
17533
+ value
17534
+ )) }),
17535
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-h-0 flex-1 overflow-y-auto", children: [
17536
+ error && items.length === 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2 px-4 py-3", children: [
17537
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-destructive", children: error }),
17538
+ /* @__PURE__ */ jsxRuntime.jsx(Button, { type: "button", size: "sm", onClick: () => void load(null), children: "Try again" })
17539
+ ] }),
17540
+ items.length === 0 && !error && !loading && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-4 py-8 text-sm text-muted-foreground", children: VIEWS[view].empty }),
17541
+ items.map((item) => {
17542
+ const Icon = kindIcon(item.kind);
17543
+ const selected = item.id === selectedItemId;
17544
+ return /* @__PURE__ */ jsxRuntime.jsxs(
17545
+ "button",
17546
+ {
17547
+ type: "button",
17548
+ "aria-label": `Open inbox item ${item.title}`,
17549
+ className: cn(
17550
+ "flex w-full min-w-0 gap-3 border-l-2 px-3 py-3 text-left transition-colors hover:bg-input",
17551
+ selected ? "border-l-primary bg-line text-foreground" : "border-l-transparent text-subtle",
17552
+ !item.readAt && "font-medium text-foreground"
17553
+ ),
17554
+ onClick: () => void selectItem(item.id),
17555
+ children: [
17556
+ /* @__PURE__ */ jsxRuntime.jsx(Icon, { className: "mt-0.5 size-4 shrink-0 text-muted-foreground" }),
17557
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "min-w-0 flex-1", children: [
17558
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex min-w-0 items-center justify-between gap-2", children: [
17559
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate text-xs uppercase tracking-wide text-muted-foreground", children: kindLabel(item.kind) }),
17560
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "shrink-0 text-xs text-muted-foreground", children: formatRelative(item.createdAt) })
17561
+ ] }),
17562
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "mt-1 block truncate text-sm", children: item.title }),
17563
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "mt-1 block text-xs text-muted-foreground", children: statusLabel(item.status) })
17564
+ ] })
17565
+ ]
17566
+ },
17567
+ item.id
17568
+ );
17569
+ }),
17570
+ nextCursor && /* @__PURE__ */ jsxRuntime.jsx(
17571
+ "button",
17572
+ {
17573
+ type: "button",
17574
+ className: "w-full px-3 py-3 text-sm text-muted-foreground hover:bg-input",
17575
+ onClick: () => void load(nextCursor),
17576
+ disabled: loading,
17577
+ children: loading ? "Loading" : "Load more"
17578
+ }
17579
+ )
17580
+ ] })
17581
+ ]
17582
+ }
17583
+ ),
16989
17584
  selectedItem ? /* @__PURE__ */ jsxRuntime.jsx(
16990
17585
  InboxDetail,
16991
17586
  {
@@ -16993,59 +17588,59 @@ function InboxPanel({
16993
17588
  adapter: inboxAdapter,
16994
17589
  onUpdated: updateSelectedItem,
16995
17590
  onDeleted: deleteItem,
16996
- onSessionSelect
16997
- }
16998
- ) : /* @__PURE__ */ jsxRuntime.jsx("main", { className: "flex min-w-0 flex-1 items-center justify-center px-4 text-sm text-muted-foreground", children: "Select an item" })
17591
+ onSessionSelect,
17592
+ onBack: clearSelection
17593
+ },
17594
+ selectedItem.id
17595
+ ) : /* @__PURE__ */ jsxRuntime.jsx("main", { className: "hidden min-w-0 flex-1 items-center justify-center px-4 text-sm text-muted-foreground md:flex", children: "Select an item" })
16999
17596
  ] });
17000
17597
  }
17001
17598
  function useInboxUnreadCount(adapter) {
17002
17599
  const [unreadCount, setUnreadCount] = react.useState(0);
17003
17600
  const [hasLoaded, setHasLoaded] = react.useState(false);
17004
17601
  const [error, setError] = react.useState(null);
17602
+ const requestId = react.useRef(0);
17603
+ const listInbox = adapter.listInbox;
17604
+ const openInboxStream = adapter.openInboxStream;
17605
+ const refetch = react.useCallback(async () => {
17606
+ if (!listInbox) return;
17607
+ const id = ++requestId.current;
17608
+ try {
17609
+ const response = await listInbox({ status: "pending", limit: 200 });
17610
+ if (id !== requestId.current) return;
17611
+ setUnreadCount(response.items.filter((item) => !item.readAt).length);
17612
+ setError(null);
17613
+ } catch (err) {
17614
+ if (id !== requestId.current) return;
17615
+ setError(err instanceof Error ? err.message : "Failed to load inbox");
17616
+ } finally {
17617
+ if (id === requestId.current) setHasLoaded(true);
17618
+ }
17619
+ }, [listInbox]);
17005
17620
  react.useEffect(() => {
17006
- let cancelled = false;
17007
- let stream = null;
17008
- if (!adapter.listInbox || !adapter.openInboxStream) {
17621
+ if (!listInbox || !openInboxStream) {
17009
17622
  setError("Inbox is not supported by this adapter.");
17010
17623
  setHasLoaded(true);
17011
- return () => {
17012
- cancelled = true;
17013
- };
17624
+ return;
17014
17625
  }
17015
- adapter.listInbox({ status: "pending", limit: 200 }).then((response) => {
17016
- if (cancelled) return;
17017
- setUnreadCount(response.items.filter((item) => !item.readAt).length);
17018
- setHasLoaded(true);
17019
- }).catch((err) => {
17020
- if (cancelled) return;
17021
- setError(err instanceof Error ? err.message : "Failed to load inbox");
17022
- setHasLoaded(true);
17023
- });
17626
+ void refetch();
17627
+ let stream = null;
17024
17628
  try {
17025
- stream = adapter.openInboxStream();
17026
- stream.addEventListener("snapshot", (event) => {
17027
- if (cancelled) return;
17028
- const payload = JSON.parse(event.data);
17029
- const ids = Array.isArray(payload.unread_ids) ? payload.unread_ids : [];
17030
- setUnreadCount(ids.length);
17031
- setHasLoaded(true);
17032
- });
17033
- stream.addEventListener("item", () => {
17034
- if (cancelled) return;
17035
- setUnreadCount((count) => count + 1);
17036
- setHasLoaded(true);
17037
- });
17038
- stream.onerror = () => {
17039
- if (!cancelled) setError("Inbox stream disconnected.");
17040
- };
17629
+ stream = openInboxStream();
17630
+ const onChange = () => void refetch();
17631
+ stream.addEventListener("snapshot", onChange);
17632
+ stream.addEventListener("item", onChange);
17633
+ stream.onerror = () => setError("Inbox stream disconnected.");
17041
17634
  } catch (err) {
17042
- setError(err instanceof Error ? err.message : "Failed to open inbox stream");
17635
+ setError(
17636
+ err instanceof Error ? err.message : "Failed to open inbox stream"
17637
+ );
17043
17638
  }
17044
17639
  return () => {
17045
- cancelled = true;
17640
+ requestId.current += 1;
17046
17641
  stream?.close();
17047
17642
  };
17048
- }, [adapter]);
17643
+ }, [listInbox, openInboxStream, refetch]);
17049
17644
  return { unreadCount, hasLoaded, error };
17050
17645
  }
17051
17646