@springbrand/message-panel 0.1.3-alpha.34 → 0.1.3-alpha.36

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.
@@ -1,9 +1,8 @@
1
1
  import {
2
- ArrowLeft,
3
- ArrowRight,
4
2
  Bell,
5
3
  CaretRight,
6
4
  Check,
5
+ CheckCircle,
7
6
  CircleNotch,
8
7
  Clock,
9
8
  Key,
@@ -11,11 +10,21 @@ import {
11
10
  UsersThree,
12
11
  WarningCircle,
13
12
  } from "@phosphor-icons/react";
14
- import { useRef, useState } from "react";
13
+ import { useState } from "react";
14
+ import {
15
+ Collapsible,
16
+ CollapsibleContent,
17
+ CollapsibleTrigger,
18
+ } from "../primitives/collapsible";
15
19
  import { Tooltip } from "../primitives/tooltip";
16
20
  import { WorkshopButton } from "../primitives/workshop-controls";
17
21
  import { MarkdownMessage } from "./markdown-message";
18
- import { WorkIcon } from "./tool-rows";
22
+ import { collapsePanel } from "./surfaces";
23
+ import {
24
+ DISCLOSURE_ICON,
25
+ DISCLOSURE_TITLE_WITH_ICON,
26
+ WorkIcon,
27
+ } from "./tool-rows";
19
28
  import type {
20
29
  ParallelTool,
21
30
  PlanStep,
@@ -31,7 +40,7 @@ import {
31
40
  /**
32
41
  * UIMessage 协议里存在、gadgets 那套没有的几类 part —— 计划快照、原位提问、
33
42
  * 后续建议、定时任务、并行/子 Agent、原位授权。视觉语言全部沿用 cloud-os:
34
- * 折叠工作行用 `px-1.5 py-1` 的行式布局,卡片用
43
+ * 折叠工作行用 `py-1` 的行式布局,卡片用
35
44
  * `themed-surface-inset rounded-2xl border-kumo-line/70 bg-kumo-elevated/45 p-3`,
36
45
  * 授权卡沿用 renderActionCard 的 blocking callout(brand/40 边 + brand/10 底)。
37
46
  */
@@ -41,73 +50,78 @@ import {
41
50
  export function PlanBlock({
42
51
  steps,
43
52
  running,
53
+ defaultOpen = true,
44
54
  }: {
45
55
  steps: PlanStep[];
46
56
  running: boolean;
57
+ defaultOpen?: boolean;
47
58
  }) {
48
59
  const done = steps.filter((step) => step.status === "done").length;
49
60
  const current = steps.find((step) => step.status === "in_progress");
50
- const [open, setOpen] = useState(true);
61
+ const [open, setOpen] = useState(defaultOpen);
51
62
 
52
63
  return (
53
- <div className="group -ml-0.5">
54
- <button
55
- type="button"
56
- onClick={() => setOpen((value) => !value)}
57
- className="flex w-full cursor-pointer items-center gap-3 rounded-xl px-1.5 py-1 text-left text-kumo-subtle transition-colors duration-150 ease-out hover:text-kumo-default focus-visible:text-kumo-default focus-visible:outline-none active:scale-[0.995]"
58
- aria-expanded={open}
64
+ <Collapsible open={open} onOpenChange={setOpen} className="group">
65
+ <CollapsibleTrigger
66
+ className={`${DISCLOSURE_TITLE_WITH_ICON} w-full`}
59
67
  >
60
- <span className="flex h-5 w-5 flex-shrink-0 items-center justify-center">
61
- <WorkIcon Icon={ListChecks} />
68
+ <span
69
+ className={`${DISCLOSURE_ICON} rounded-full`}
70
+ role="progressbar"
71
+ aria-label="Plan progress"
72
+ aria-valuemin={0}
73
+ aria-valuemax={steps.length}
74
+ aria-valuenow={done}
75
+ style={{
76
+ background: `conic-gradient(var(--color-kumo-brand) ${(done / steps.length) * 100}%, var(--color-kumo-fill) 0)`,
77
+ }}
78
+ >
79
+ <span className="size-2.5 rounded-full bg-kumo-base" />
62
80
  </span>
63
- <span className="min-w-0 flex-1">
64
- <span className="flex min-w-0 items-center gap-2 text-[14px] leading-5 tracking-[-0.25px]">
65
- <span className={`min-w-0 truncate ${running ? "cos-thinking-shimmer" : ""}`}>
66
- Plan · {done}/{steps.length}
67
- </span>
68
- <CaretRight
69
- size={13}
70
- weight="bold"
71
- className={`flex-shrink-0 text-kumo-inactive transition-transform duration-150 ease-out ${open ? "rotate-90" : ""}`}
72
- />
81
+ <span className="flex min-w-0 flex-1 items-center gap-2">
82
+ <span className={`shrink-0 ${running ? "cos-thinking-shimmer" : ""}`}>
83
+ Plan · {done}/{steps.length}
73
84
  </span>
74
85
  {!open && current && (
75
- <span className="mt-1 block truncate font-mono text-[12px] leading-4 text-kumo-inactive">
86
+ <span className="min-w-0 flex-1 truncate text-[12px] leading-4 text-[rgba(0,0,0,0.3)]">
76
87
  {current.text}
77
88
  </span>
78
89
  )}
90
+ <CaretRight
91
+ size={16}
92
+ className={`ml-auto flex-shrink-0 text-kumo-inactive transition-transform duration-150 ease-out ${open ? "rotate-90" : ""}`}
93
+ />
79
94
  </span>
80
- </button>
81
- {open && (
82
- <div className="themed-surface-inset ml-8 mt-1 space-y-1.5 rounded-2xl border border-kumo-line/70 bg-kumo-elevated/45 p-3">
95
+ </CollapsibleTrigger>
96
+ <CollapsibleContent className={collapsePanel}>
97
+ <div className="-mx-2.5 mt-1 flex flex-col gap-2 rounded-xl border border-[rgba(0,0,0,0.07)] px-4 py-3">
83
98
  {steps.map((step, index) => (
84
99
  <div
85
100
  key={`${step.text}-${index}`}
86
- className="flex items-start gap-2.5 text-[13px] leading-[19px] tracking-[-0.25px]"
101
+ className="flex items-center gap-2 text-[14px] leading-[1.4] font-normal tracking-normal"
87
102
  >
88
103
  <span
89
- className="mt-0.5 flex h-4 w-4 flex-shrink-0 items-center justify-center"
104
+ className="flex size-4 shrink-0 items-center justify-center"
90
105
  aria-hidden="true"
91
106
  >
92
107
  {step.status === "done" ? (
93
- <Check size={12} weight="bold" className="text-kumo-success" />
108
+ <CheckCircle size={16} weight="fill" className="text-[#00bf75]" />
94
109
  ) : step.status === "in_progress" ? (
95
110
  <CircleNotch
96
- size={12}
97
- weight="bold"
98
- className="animate-spin text-kumo-brand motion-reduce:animate-none"
111
+ size={16}
112
+ className="animate-spin text-[#ff9938] motion-reduce:animate-none"
99
113
  />
100
114
  ) : (
101
- <span className="h-1.5 w-1.5 rounded-full bg-kumo-fill" />
115
+ <span className="h-1.5 w-1.5 rounded-full bg-[color:var(--text-color-kumo-default)] opacity-20" />
102
116
  )}
103
117
  </span>
104
118
  <span
105
119
  className={
106
120
  step.status === "done"
107
- ? "min-w-0 flex-1 text-kumo-inactive line-through"
121
+ ? "min-w-0 flex-1 text-kumo-default/30 line-through"
108
122
  : step.status === "in_progress"
109
- ? "min-w-0 flex-1 font-medium text-kumo-default"
110
- : "min-w-0 flex-1 text-kumo-subtle"
123
+ ? "min-w-0 flex-1 text-kumo-default"
124
+ : "min-w-0 flex-1 text-kumo-default/70"
111
125
  }
112
126
  >
113
127
  {step.text}
@@ -115,8 +129,8 @@ export function PlanBlock({
115
129
  </div>
116
130
  ))}
117
131
  </div>
118
- )}
119
- </div>
132
+ </CollapsibleContent>
133
+ </Collapsible>
120
134
  );
121
135
  }
122
136
 
@@ -167,8 +181,6 @@ export function AskUserBlock({
167
181
  // 首帧 questions 往往还是空数组,那之后每次 setAnswers 都在空数组上 map,
168
182
  // 选择永远存不进去 → Submit 永久禁用。改成稀疏存 + 按当前 questions 补齐。
169
183
  const [answers, setAnswers] = useState<DraftAnswer[]>([]);
170
- const [current, setCurrent] = useState(0);
171
- const direction = useRef<"back" | "forward">("forward");
172
184
  const answerAt = (index: number): DraftAnswer =>
173
185
  answers[index] ?? { selections: [], text: "" };
174
186
  const toggle = (questionIndex: number, option: string, multi: boolean) =>
@@ -198,26 +210,21 @@ export function AskUserBlock({
198
210
  // 在途只用于按钮转圈;权威状态永远是 part 的 state。
199
211
  const [inFlight, setInFlight] = useState(false);
200
212
  const outcome = askUserOutcome(call);
201
- const settledAnswers = outcome.kind === "answered" ? outcome.answers : [];
213
+ const displayAnswerAt = (index: number): DraftAnswer => {
214
+ if (outcome.kind !== "answered") return answerAt(index);
215
+ const answer = outcome.answers[index];
216
+ return {
217
+ selections: answer?.selections ?? [],
218
+ text: answer?.text ?? "",
219
+ };
220
+ };
202
221
  // 服务端已经结算 = 这张卡永久不可操作,不管结算成什么。
203
222
  const resolved = outcome.kind !== "pending" || inFlight;
204
223
  const valid = questions.length > 0 && questions.every(
205
224
  (_question, index) =>
206
225
  answerAt(index).selections.length > 0 || answerAt(index).text.trim(),
207
226
  );
208
- const currentIndex = Math.min(current, Math.max(questions.length - 1, 0));
209
- const currentQuestion = questions[currentIndex];
210
- const currentAnswer = answerAt(currentIndex);
211
- const currentValid = currentAnswer.selections.length > 0 ||
212
- Boolean(currentAnswer.text.trim());
213
- const isLast = currentIndex === questions.length - 1;
214
- const buttonLabel = outcome.kind === "answered"
215
- ? "Submitted"
216
- : outcome.kind === "closed"
217
- ? "Closed"
218
- : inFlight
219
- ? "Submitting…"
220
- : "Submit";
227
+ const buttonLabel = inFlight ? "Submitting…" : "Submit";
221
228
  const footnote = outcome.kind === "closed"
222
229
  ? outcome.reason === "user_replied_freeform"
223
230
  ? "Answered in the chat instead."
@@ -238,204 +245,102 @@ export function AskUserBlock({
238
245
  }, () => setInFlight(false));
239
246
  };
240
247
 
241
- const settledButton = (
242
- <WorkshopButton
243
- tone="primary"
244
- disabled
245
- className="ml-auto !h-7 flex-shrink-0 gap-1 !rounded-lg px-2.5 text-[12px]"
246
- >
247
- {buttonLabel}
248
- </WorkshopButton>
249
- );
250
-
251
248
  return (
252
- // 收成 w-fit:短问题不该撑成一条横贯全栏的长条,长问题到 560px 再换行。
253
249
  <div
254
- className="w-fit min-w-[15rem] max-w-[560px]"
250
+ className="w-full"
255
251
  data-part-type="ask_user"
256
252
  data-state={outcome.kind === "answered" ? "submitted" : outcome.kind}
257
253
  >
258
- <div className="flex flex-col gap-3 rounded-xl border border-kumo-line bg-kumo-base px-3 py-2.5">
259
- {outcome.kind === "pending" ? (
260
- <>
261
- {questions.length > 1 && (
262
- <div className="flex flex-col gap-2">
263
- <div className="flex items-center justify-between text-[11px] font-medium leading-4 text-kumo-inactive">
264
- <span>Clarifying questions</span>
265
- <span className="tabular-nums">
266
- {currentIndex + 1}/{questions.length}
267
- </span>
268
- </div>
269
- <div className="flex gap-1" aria-hidden="true">
270
- {questions.map((question, index) => (
271
- <span
272
- key={`${question.prompt}:${index}`}
273
- className={`h-1 flex-1 rounded-full ${
274
- index < currentIndex
275
- ? "bg-kumo-brand"
276
- : index === currentIndex
277
- ? "bg-kumo-brand/60"
278
- : "bg-kumo-fill"
279
- }`}
280
- />
281
- ))}
282
- </div>
283
- </div>
284
- )}
285
- {currentQuestion && (
286
- <div className="overflow-hidden">
287
- <div
288
- key={currentIndex}
289
- className="cos-ask-user-question flex flex-col gap-2.5"
290
- data-direction={direction.current}
291
- >
292
- <p className="m-0 text-[13px] leading-[18px] font-medium tracking-[-0.25px] text-kumo-default">
293
- {currentQuestion.prompt}
294
- </p>
295
- {currentQuestion.options.length > 0 && (
296
- <ul className="m-0 flex list-none flex-col gap-1.5 p-0">
297
- {currentQuestion.options.map((option) => {
298
- const selected = currentAnswer.selections.includes(
299
- option,
300
- );
301
- return (
302
- <li key={option} className="m-0 p-0">
303
- <label
304
- className={`flex min-h-8 items-center gap-2 rounded-lg border px-2.5 py-1.5 text-[13px] leading-[18px] tracking-[-0.25px] transition-colors duration-150 ease-out ${
305
- resolved || !onRespond
306
- ? "cursor-default text-kumo-inactive"
307
- : "cursor-pointer text-kumo-subtle hover:bg-kumo-tint hover:text-kumo-default"
308
- } ${
309
- selected
310
- ? "border-kumo-brand/45 bg-kumo-brand/10 text-kumo-default"
311
- : "border-kumo-line bg-kumo-base"
312
- }`}
313
- >
314
- <input
315
- type={currentQuestion.multi
316
- ? "checkbox"
317
- : "radio"}
318
- name={`${call.toolCallId}-${currentIndex}`}
319
- checked={selected}
320
- disabled={resolved || !onRespond}
321
- onChange={() =>
322
- toggle(
323
- currentIndex,
324
- option,
325
- currentQuestion.multi,
326
- )}
327
- className="h-4 w-4 shrink-0 accent-[var(--color-kumo-brand)]"
328
- />
329
- <span>{option}</span>
330
- </label>
331
- </li>
332
- );
333
- })}
334
- </ul>
335
- )}
336
- {currentQuestion.allowCustom && (
337
- <label className="flex flex-col gap-1 text-[12px] leading-4 text-kumo-subtle">
338
- <span>
339
- {currentQuestion.options.length > 0 ? "Other" : "Answer"}
340
- </span>
341
- <input
342
- type="text"
343
- value={currentAnswer.text}
344
- maxLength={2_000}
254
+ <div className="flex flex-col gap-4 py-2.5">
255
+ {outcome.kind !== "closed" && questions.map((question, questionIndex) => {
256
+ const answer = displayAnswerAt(questionIndex);
257
+ const options = outcome.kind === "answered"
258
+ ? [...new Set([...question.options, ...answer.selections])]
259
+ : question.options;
260
+ return (
261
+ <div
262
+ key={`${question.prompt}:${questionIndex}`}
263
+ className="flex flex-col items-start gap-2"
264
+ >
265
+ <p className="m-0 text-[14px] leading-[1.4] font-normal text-kumo-default">
266
+ {question.prompt}
267
+ </p>
268
+ {options.length > 0 && (
269
+ <div className="flex flex-wrap items-center gap-2">
270
+ {options.map((option) => {
271
+ const selected = answer.selections.includes(option);
272
+ return (
273
+ <button
274
+ key={option}
275
+ type="button"
276
+ aria-pressed={selected}
345
277
  disabled={resolved || !onRespond}
346
- aria-label={`自定义回答:${currentQuestion.prompt}`}
347
- placeholder={currentQuestion.options.length > 0
348
- ? "Add a custom answer"
349
- : "Type your answer"}
350
- onChange={(event) =>
351
- setText(currentIndex, event.currentTarget.value)}
352
- className="h-8 rounded-lg border border-kumo-line bg-kumo-base px-2.5 text-[13px] text-kumo-default outline-none placeholder:text-kumo-inactive focus:border-kumo-brand focus:ring-2 focus:ring-kumo-brand/20 disabled:cursor-default"
353
- />
354
- </label>
355
- )}
356
- </div>
357
- </div>
358
- )}
359
- <div className="flex items-center gap-2">
360
- {questions.length > 1 && (
361
- <WorkshopButton
362
- disabled={currentIndex === 0 || resolved}
363
- onClick={() => {
364
- direction.current = "back";
365
- setCurrent((index) => Math.max(0, index - 1));
366
- }}
367
- className="!h-7 gap-1 !rounded-lg px-2.5 text-[12px]"
368
- >
369
- <ArrowLeft size={13} weight="bold" />
370
- Back
371
- </WorkshopButton>
372
- )}
373
- <WorkshopButton
374
- tone="primary"
375
- disabled={
376
- resolved || !onRespond ||
377
- (questions.length > 1 && !isLast ? !currentValid : !valid)
378
- }
379
- onClick={() => {
380
- if (questions.length > 1 && !isLast) {
381
- direction.current = "forward";
382
- setCurrent((index) =>
383
- Math.min(questions.length - 1, index + 1)
278
+ onClick={() =>
279
+ toggle(questionIndex, option, question.multi)}
280
+ style={selected
281
+ ? {
282
+ background:
283
+ "linear-gradient(#fff,#fff) padding-box, linear-gradient(90deg,#ff9938,#ffe46c) border-box",
284
+ }
285
+ : undefined}
286
+ className={`inline-flex w-fit items-center justify-center rounded-lg border px-4 py-2 text-left text-[14px] leading-[1.4] font-normal text-[rgba(0,0,0,0.7)] transition-[background,border-color] duration-150 ease-out focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#ff9938]/30 ${
287
+ selected
288
+ ? "border-solid border-transparent"
289
+ : resolved || !onRespond
290
+ ? "border-[rgba(0,0,0,0.07)] bg-kumo-base opacity-60"
291
+ : "cursor-pointer border-[rgba(0,0,0,0.07)] bg-kumo-base hover:border-[#ff9938]"
292
+ }`}
293
+ >
294
+ {option}
295
+ </button>
384
296
  );
385
- return;
386
- }
387
- submit();
388
- }}
389
- className="ml-auto !h-7 gap-1 !rounded-lg px-2.5 text-[12px]"
390
- >
391
- {questions.length > 1 && !isLast ? (
392
- <>
393
- Next
394
- <ArrowRight size={13} weight="bold" />
395
- </>
396
- ) : buttonLabel}
397
- </WorkshopButton>
398
- </div>
399
- </>
400
- ) : (
401
- <>
402
- {outcome.kind === "answered" &&
403
- questions.map((question, questionIndex) => (
404
- <div
405
- key={`${question.prompt}:${questionIndex}`}
406
- className="flex flex-col gap-1.5"
407
- >
408
- <p className="m-0 text-[13px] leading-[18px] font-medium tracking-[-0.25px] text-kumo-default">
409
- {question.prompt}
410
- </p>
411
- {settledAnswers[questionIndex] && (
412
- <div className="flex flex-col gap-1 text-[12px] leading-4 text-kumo-inactive">
413
- {settledAnswers[questionIndex]!.selections.length > 0 && (
414
- <p className="m-0">
415
- Answered: {settledAnswers[questionIndex]!.selections
416
- .join(" / ")}
417
- </p>
418
- )}
419
- {settledAnswers[questionIndex]!.text.trim() && (
420
- <p className="m-0">
421
- {settledAnswers[questionIndex]!.selections.length > 0
422
- ? `补充:${settledAnswers[questionIndex]!.text.trim()}`
423
- : `Answered: ${settledAnswers[questionIndex]!.text.trim()}`}
424
- </p>
425
- )}
426
- </div>
427
- )}
297
+ })}
428
298
  </div>
429
- ))}
430
- <div className="flex items-center gap-3">
431
- {footnote && (
432
- <p className="m-0 min-w-0 text-[12px] leading-4 text-kumo-inactive">
433
- {footnote}
299
+ )}
300
+ {question.allowCustom && (
301
+ <label className="flex w-full flex-col gap-2 text-[14px] leading-[1.4] text-kumo-default">
302
+ <span>{question.options.length > 0 ? "Other" : "Answer"}</span>
303
+ <input
304
+ type="text"
305
+ value={answer.text}
306
+ maxLength={2_000}
307
+ disabled={resolved || !onRespond}
308
+ aria-label={`自定义回答:${question.prompt}`}
309
+ placeholder={question.options.length > 0
310
+ ? "Add a custom answer"
311
+ : "Type your answer"}
312
+ onChange={(event) =>
313
+ setText(questionIndex, event.currentTarget.value)}
314
+ className="rounded-lg border border-[rgba(0,0,0,0.07)] bg-kumo-base px-4 py-2 text-[14px] leading-[1.4] text-kumo-subtle outline-none placeholder:text-kumo-inactive focus:border-[#ff9938] focus:ring-2 focus:ring-[#ff9938]/20 disabled:cursor-default disabled:opacity-60"
315
+ />
316
+ </label>
317
+ )}
318
+ {outcome.kind === "answered" &&
319
+ !question.allowCustom && answer.text.trim() && (
320
+ <p className="m-0 text-[14px] leading-[1.4] text-kumo-subtle">
321
+ {answer.text.trim()}
434
322
  </p>
435
323
  )}
436
- {settledButton}
437
324
  </div>
438
- </>
325
+ );
326
+ })}
327
+ {outcome.kind === "pending" && (
328
+ <button
329
+ type="button"
330
+ disabled={resolved || !onRespond || !valid}
331
+ onClick={submit}
332
+ className="inline-flex h-10 min-w-[120px] self-start items-center justify-center rounded-lg bg-gradient-to-r from-[#ff9938] to-[#ffe46c] px-5 py-2.5 text-[14px] leading-5 font-medium text-black transition-opacity duration-150 ease-out hover:opacity-90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#ff9938]/30 disabled:cursor-default disabled:opacity-50"
333
+ >
334
+ {buttonLabel}
335
+ </button>
336
+ )}
337
+ {outcome.kind === "answered" && (
338
+ <p className="m-0 text-[12px] leading-4 text-kumo-inactive">Submitted</p>
339
+ )}
340
+ {outcome.kind === "closed" && (
341
+ <p className="m-0 text-[12px] leading-4 text-kumo-inactive">
342
+ Closed{footnote ? ` · ${footnote}` : ""}
343
+ </p>
439
344
  )}
440
345
  </div>
441
346
  </div>
@@ -816,15 +721,15 @@ export function ParallelBlock({
816
721
  const done = tools.filter((tool) => tool.status === "done").length;
817
722
  return (
818
723
  <div className="max-w-[860px]">
819
- <div className="flex items-center gap-3 px-1.5 py-1 text-[14px] leading-5 tracking-[-0.25px] text-kumo-subtle">
820
- <span className="flex h-5 w-5 flex-shrink-0 items-center justify-center">
724
+ <div className="flex items-start gap-2 px-1.5 py-1 text-[14px] leading-[1.4] text-kumo-inactive">
725
+ <span className={DISCLOSURE_ICON}>
821
726
  <WorkIcon Icon={ListChecks} />
822
727
  </span>
823
728
  <span className="min-w-0 truncate">
824
729
  {label} · {done}/{tools.length}
825
730
  </span>
826
731
  </div>
827
- <div className="ml-8 mt-1 space-y-1">
732
+ <div className="mt-1 space-y-1">
828
733
  {tools.map((tool, index) => (
829
734
  <div
830
735
  key={`${tool.label}-${index}`}
@@ -847,15 +752,15 @@ export function ParallelBlock({
847
752
  export function SubAgentsBlock({ agents }: { agents: SubAgentView[] }) {
848
753
  return (
849
754
  <div className="max-w-[860px]">
850
- <div className="flex items-center gap-3 px-1.5 py-1 text-[14px] leading-5 tracking-[-0.25px] text-kumo-subtle">
851
- <span className="flex h-5 w-5 flex-shrink-0 items-center justify-center">
755
+ <div className="flex items-start gap-2 px-1.5 py-1 text-[14px] leading-[1.4] text-kumo-inactive">
756
+ <span className={DISCLOSURE_ICON}>
852
757
  <WorkIcon Icon={UsersThree} />
853
758
  </span>
854
759
  <span className="min-w-0 truncate">
855
760
  {agents.length === 1 ? "Sub-agent" : `${agents.length} sub-agents`}
856
761
  </span>
857
762
  </div>
858
- <div className="ml-8 mt-1 space-y-1">
763
+ <div className="mt-1 space-y-1">
859
764
  {agents.map((agent, index) => (
860
765
  <div
861
766
  key={`${agent.name}-${index}`}
@@ -893,28 +798,24 @@ export function ErrorBlock({
893
798
  const [expanded, setExpanded] = useState(false);
894
799
  return (
895
800
  <div className="group/work max-w-[860px] text-[14px] leading-5 tracking-[-0.25px] text-kumo-subtle">
896
- <div className="flex w-full items-center gap-2 px-1.5 py-1">
801
+ <div className="flex w-full items-start gap-2">
897
802
  <button
898
803
  type="button"
899
804
  onClick={() => setExpanded((value) => !value)}
900
- className="flex min-w-0 flex-1 cursor-pointer items-center gap-3 rounded-md text-left transition-colors duration-150 ease-out hover:text-kumo-default focus-visible:text-kumo-default focus-visible:outline-none active:scale-[0.995]"
805
+ className={`${DISCLOSURE_TITLE_WITH_ICON} flex-1`}
901
806
  aria-expanded={expanded}
902
807
  >
903
808
  <span
904
- className="flex h-5 w-5 flex-shrink-0 items-center justify-center text-kumo-danger"
809
+ className={`${DISCLOSURE_ICON} text-kumo-danger`}
905
810
  aria-hidden="true"
906
811
  >
907
- <WarningCircle size={16} weight="fill" />
812
+ <WarningCircle size={15} weight="fill" />
908
813
  </span>
909
814
  <span className="flex min-w-0 flex-1 items-center gap-1">
910
- <span className="min-w-0 truncate">
911
- <span className="font-medium text-kumo-danger">{title}: </span>
912
- <span className="text-kumo-subtle">{body}</span>
913
- </span>
815
+ <span className="min-w-0 truncate text-kumo-danger">{title}</span>
914
816
  {body && (
915
817
  <CaretRight
916
- size={13}
917
- weight="bold"
818
+ size={16}
918
819
  className={`flex-shrink-0 text-kumo-inactive transition-transform duration-150 ease-out ${expanded ? "rotate-90" : ""}`}
919
820
  />
920
821
  )}
@@ -933,7 +834,7 @@ export function ErrorBlock({
933
834
  )}
934
835
  </div>
935
836
  {expanded && body && (
936
- <div className="ml-8 mt-1">
837
+ <div className="mt-1">
937
838
  <pre className="max-h-48 overflow-auto rounded-xl border border-kumo-line/70 bg-kumo-base p-3 font-mono text-[12px] leading-[18px] text-kumo-subtle whitespace-pre-wrap">
938
839
  {body}
939
840
  </pre>