@mlpal/yodex-ui 0.1.0 → 0.2.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.js CHANGED
@@ -9,6 +9,7 @@ class YodexClient {
9
9
  seq = 0;
10
10
  state = "closed";
11
11
  constructor(options) {
12
+ const given = Object.fromEntries(Object.entries(options).filter(([, v]) => v !== undefined));
12
13
  this.opts = {
13
14
  backoffMs: { min: 500, max: 1e4 },
14
15
  requestTimeoutMs: 15000,
@@ -16,7 +17,7 @@ class YodexClient {
16
17
  setTimer: (fn, ms) => setTimeout(fn, ms),
17
18
  clearTimer: (h) => clearTimeout(h),
18
19
  random: Math.random,
19
- ...options
20
+ ...given
20
21
  };
21
22
  }
22
23
  get url() {
@@ -165,6 +166,7 @@ import { addUsage, emptyUsage, HUMAN } from "@mlpal/harness-protocol";
165
166
  function initialState() {
166
167
  return {
167
168
  connection: "connecting",
169
+ capabilities: [],
168
170
  items: [],
169
171
  running: false,
170
172
  usageTotal: emptyUsage(),
@@ -294,10 +296,21 @@ function applyEvent(state, ev, live) {
294
296
  }
295
297
  function reduce(state, action) {
296
298
  switch (action.type) {
297
- case "connection":
298
- return { ...state, connection: action.state };
299
+ case "connection": {
300
+ if (action.state === "connected" || !state.running)
301
+ return { ...state, connection: action.state };
302
+ return {
303
+ ...state,
304
+ connection: action.state,
305
+ running: false,
306
+ activity: undefined,
307
+ pendingPermissions: [],
308
+ pendingQuestion: undefined,
309
+ items: [...dropDraft(state.items), { kind: "error", id: nextId("e"), message: "Connection lost while the agent was working; what streamed so far was kept.", ts: new Date().toISOString() }]
310
+ };
311
+ }
299
312
  case "clear":
300
- return { ...initialState(), connection: state.connection, protocol: state.protocol, sessions: state.sessions, models: state.models, skills: state.skills };
313
+ return { ...initialState(), connection: state.connection, protocol: state.protocol, capabilities: state.capabilities, hop: state.hop, rootCwd: state.rootCwd, sessions: state.sessions, models: state.models, skills: state.skills };
301
314
  case "error":
302
315
  return { ...state, error: action.message };
303
316
  case "local_user":
@@ -306,7 +319,7 @@ function reduce(state, action) {
306
319
  items: [...state.items, { kind: "user", id: nextId("u"), text: action.text, author: HUMAN, ts: action.ts, ...action.blocks?.length ? { blocks: action.blocks } : {} }]
307
320
  };
308
321
  case "run_started":
309
- return { ...state, running: true, runStartedAt: action.at, activity: "Thinking", error: undefined, lastResult: undefined, ...action.sessionId ? { sessionId: action.sessionId } : {} };
322
+ return { ...state, running: true, runStartedAt: action.at, activity: "Thinking", error: undefined, lastResult: undefined, pendingApproval: undefined, ...action.sessionId ? { sessionId: action.sessionId } : {} };
310
323
  case "permission_answered":
311
324
  return { ...state, pendingPermissions: state.pendingPermissions.filter((p) => p.id !== action.id) };
312
325
  case "question_answered":
@@ -321,25 +334,46 @@ function reduce(state, action) {
321
334
  const m = action.message;
322
335
  switch (m.type) {
323
336
  case "ready":
324
- return { ...state, connection: "connected", protocol: m.protocol };
337
+ return { ...state, connection: "connected", protocol: m.protocol, capabilities: m.capabilities ?? [], hop: m.hop, rootCwd: m.cwd };
325
338
  case "event":
339
+ if (state.sessionId && m.sessionId !== state.sessionId)
340
+ return state;
326
341
  return applyEvent(state.sessionId ? state : { ...state, sessionId: m.sessionId }, m.event, true);
327
342
  case "permission_request":
328
- return { ...state, pendingPermissions: [...state.pendingPermissions, { id: m.id, sessionId: m.sessionId, toolName: m.toolName, input: m.input }] };
343
+ if (state.sessionId && m.sessionId !== state.sessionId)
344
+ return state;
345
+ return { ...state, pendingPermissions: [...state.pendingPermissions, { id: m.id, sessionId: m.sessionId, toolName: m.toolName, input: m.input, ...m.reason ? { reason: m.reason } : {}, ...m.safetyReason ? { safetyReason: m.safetyReason } : {} }] };
329
346
  case "question_request":
347
+ if (state.sessionId && m.sessionId !== state.sessionId)
348
+ return state;
330
349
  return { ...state, pendingQuestion: { id: m.id, sessionId: m.sessionId, questions: m.questions } };
331
- case "question_cleared":
332
- return state.pendingQuestion?.id === m.id ? { ...state, pendingQuestion: undefined } : state;
333
350
  case "sessions":
334
351
  return { ...state, sessions: m.sessions };
335
352
  case "models":
336
353
  return { ...state, models: m.models };
337
354
  case "skills":
338
355
  return { ...state, skills: m.skills };
339
- case "session_history":
340
- return reduce(state, { type: "history", sessionId: m.sessionId, messages: m.messages });
356
+ case "session_history": {
357
+ const s = reduce(state, { type: "history", sessionId: m.sessionId, messages: m.messages });
358
+ return {
359
+ ...s,
360
+ pendingQuestion: m.pendingQuestion ? { id: m.pendingQuestion.id, sessionId: m.sessionId, questions: m.pendingQuestion.questions } : undefined,
361
+ pendingApproval: m.pendingApproval,
362
+ liveElsewhere: m.live ?? false
363
+ };
364
+ }
341
365
  case "done":
342
- return { ...state, running: false, activity: undefined, pendingPermissions: [], sessionId: m.sessionId };
366
+ if (state.sessionId && m.sessionId !== state.sessionId)
367
+ return state;
368
+ return {
369
+ ...state,
370
+ running: false,
371
+ activity: undefined,
372
+ pendingPermissions: [],
373
+ sessionId: m.sessionId,
374
+ ...m.pendingApproval ? { pendingApproval: m.pendingApproval } : {},
375
+ ...m.usage && !state.lastResult ? { usageTotal: addUsage(state.usageTotal, m.usage) } : {}
376
+ };
343
377
  case "error":
344
378
  return { ...state, error: m.message, items: [...dropDraft(state.items), { kind: "error", id: nextId("e"), message: m.message, ts: new Date().toISOString() }] };
345
379
  default:
@@ -377,12 +411,18 @@ import { useCallback, useEffect, useMemo, useReducer, useRef } from "react";
377
411
  function useYodexSession(options) {
378
412
  const [state, dispatch] = useReducer(reduce, undefined, initialState);
379
413
  const sessionIdRef = useRef(options.sessionId);
414
+ const allowed = useRef(new Set);
415
+ const ruleFor = (p) => `${p.toolName}\x00${typeof p.input.command === "string" ? p.input.command : ""}`;
380
416
  const client = useMemo(() => new YodexClient({
381
417
  url: options.url,
382
418
  token: options.token,
383
419
  socketFactory: options.socketFactory,
384
420
  onState: (s) => dispatch({ type: "connection", state: s }),
385
421
  onMessage: (m) => {
422
+ if (m.type === "permission_request" && allowed.current.has(ruleFor(m))) {
423
+ client.respondPermission(m.id, "allow");
424
+ return;
425
+ }
386
426
  dispatch({ type: "server", message: m });
387
427
  if (m.type === "ready" && sessionIdRef.current) {
388
428
  client.loadSession(sessionIdRef.current).catch((e) => dispatch({ type: "error", message: e.message }));
@@ -398,6 +438,16 @@ function useYodexSession(options) {
398
438
  useEffect(() => {
399
439
  sessionIdRef.current = state.sessionId ?? options.sessionId;
400
440
  }, [state.sessionId, options.sessionId]);
441
+ const lastRequested = useRef(options.sessionId);
442
+ useEffect(() => {
443
+ if (options.sessionId === lastRequested.current)
444
+ return;
445
+ lastRequested.current = options.sessionId;
446
+ sessionIdRef.current = options.sessionId;
447
+ dispatch({ type: "clear" });
448
+ if (options.sessionId && client.connected)
449
+ client.loadSession(options.sessionId).catch((e) => dispatch({ type: "error", message: e.message }));
450
+ }, [options.sessionId, client]);
401
451
  const guard = useCallback((fn) => {
402
452
  try {
403
453
  fn();
@@ -407,17 +457,17 @@ function useYodexSession(options) {
407
457
  }, []);
408
458
  const run = useCallback((text, attachments = [], runOptions = {}) => {
409
459
  const blocks = attachments.map((a) => ({ type: "image", source: { type: "base64", media_type: a.mediaType, data: a.data } }));
410
- if (blocks.length && state.protocol === "yodex/1") {
411
- dispatch({ type: "error", message: "This server (yodex/1) cannot receive image attachments yet; the text was not sent." });
460
+ if (blocks.length && !state.capabilities.includes("attachments")) {
461
+ dispatch({ type: "error", message: "This server does not accept image attachments; the message was not sent." });
412
462
  return;
413
463
  }
414
464
  guard(() => {
415
465
  const sessionId = sessionIdRef.current;
416
466
  dispatch({ type: "local_user", text, ts: new Date().toISOString(), blocks });
417
467
  dispatch({ type: "run_started", sessionId, at: Date.now() });
418
- client.run(text, { ...sessionId ? { sessionId } : {}, ...options.run, ...runOptions });
468
+ client.run(text, { ...sessionId ? { sessionId } : {}, ...options.run, ...runOptions, ...blocks.length ? { blocks } : {} });
419
469
  });
420
- }, [client, guard, options.run, state.protocol]);
470
+ }, [client, guard, options.run, state.capabilities]);
421
471
  const stop = useCallback(() => guard(() => client.interrupt()), [client, guard]);
422
472
  const allow = useCallback((id) => guard(() => {
423
473
  client.respondPermission(id, "allow");
@@ -438,6 +488,23 @@ function useYodexSession(options) {
438
488
  dispatch({ type: "run_started", sessionId, at: Date.now() });
439
489
  client.respondApproval(sessionId, decision, reason);
440
490
  }), [client, guard]);
491
+ const allowForSession = useCallback((id) => {
492
+ const p = state.pendingPermissions.find((x) => x.id === id);
493
+ if (p)
494
+ allowed.current.add(ruleFor(p));
495
+ guard(() => {
496
+ client.respondPermission(id, "allow");
497
+ dispatch({ type: "permission_answered", id });
498
+ });
499
+ }, [client, guard, state.pendingPermissions]);
500
+ const steer = useCallback((text) => guard(() => {
501
+ const sessionId = sessionIdRef.current;
502
+ if (!sessionId)
503
+ throw new Error("no session to steer");
504
+ client.sendPeer(sessionId, text);
505
+ dispatch({ type: "local_user", text, ts: new Date().toISOString() });
506
+ }), [client, guard]);
507
+ const waitingOn = state.pendingQuestion ? "question" : state.pendingPermissions.length ? "permission" : undefined;
441
508
  const loadSession = useCallback(async (sessionId) => {
442
509
  sessionIdRef.current = sessionId;
443
510
  dispatch({ type: "clear" });
@@ -465,7 +532,7 @@ function useYodexSession(options) {
465
532
  sessionIdRef.current = undefined;
466
533
  dispatch({ type: "clear" });
467
534
  }, []);
468
- return { state, client, run, stop, allow, deny, answer, approve, loadSession, refreshSessions, refreshModels, newSession };
535
+ return { state, client, run, stop, allow, deny, answer, approve, allowForSession, steer, waitingOn, loadSession, refreshSessions, refreshModels, newSession };
469
536
  }
470
537
  // src/components/Transcript.tsx
471
538
  import { useEffect as useEffect2, useRef as useRef2 } from "react";
@@ -482,73 +549,88 @@ function cn(...inputs) {
482
549
  }
483
550
 
484
551
  // src/components/AssistantMessage.tsx
485
- import { jsx } from "react/jsx-runtime";
552
+ import { jsx, jsxs } from "react/jsx-runtime";
486
553
  var plugins = [remarkGfm];
487
554
  function AssistantMessage({ text, live, className }) {
488
- return /* @__PURE__ */ jsx("div", {
489
- className: cn("yx-assistant prose prose-sm max-w-none break-words text-foreground", live && "yx-live", className),
555
+ return /* @__PURE__ */ jsxs("div", {
556
+ className: cn("yx-assistant max-w-none break-words text-[15px] leading-6 text-foreground", className),
490
557
  "data-live": live ? "true" : undefined,
491
- children: /* @__PURE__ */ jsx(Markdown, {
492
- remarkPlugins: plugins,
493
- skipHtml: true,
494
- components: {
495
- a: ({ node: _n, ...props }) => /* @__PURE__ */ jsx("a", {
496
- ...props,
497
- target: "_blank",
498
- rel: "noopener noreferrer",
499
- className: "text-info underline underline-offset-2"
500
- }),
501
- pre: ({ node: _n, ...props }) => /* @__PURE__ */ jsx("pre", {
502
- ...props,
503
- className: "overflow-x-auto rounded-md border border-border bg-card p-3 font-mono text-[12.5px] leading-5"
504
- }),
505
- code: ({ node: _n, className: cls, ...props }) => cls ? /* @__PURE__ */ jsx("code", {
506
- ...props,
507
- className: cls
508
- }) : /* @__PURE__ */ jsx("code", {
509
- ...props,
510
- className: "rounded bg-muted px-1 py-0.5 font-mono text-[0.9em]"
511
- }),
512
- table: ({ node: _n, ...props }) => /* @__PURE__ */ jsx("div", {
513
- className: "overflow-x-auto",
514
- children: /* @__PURE__ */ jsx("table", {
558
+ children: [
559
+ /* @__PURE__ */ jsx(Markdown, {
560
+ remarkPlugins: plugins,
561
+ skipHtml: true,
562
+ components: {
563
+ a: ({ node: _n, ...props }) => /* @__PURE__ */ jsx("a", {
564
+ ...props,
565
+ target: "_blank",
566
+ rel: "noopener noreferrer",
567
+ className: "text-info underline underline-offset-2"
568
+ }),
569
+ pre: ({ node: _n, ...props }) => /* @__PURE__ */ jsx("pre", {
570
+ ...props,
571
+ className: "overflow-x-auto rounded-md border border-border bg-card p-3 font-mono text-[12.5px] leading-5"
572
+ }),
573
+ code: ({ node: _n, className: cls, ...props }) => cls ? /* @__PURE__ */ jsx("code", {
574
+ ...props,
575
+ className: cls
576
+ }) : /* @__PURE__ */ jsx("code", {
577
+ ...props,
578
+ className: "rounded bg-muted px-1 py-0.5 font-mono text-[0.9em]"
579
+ }),
580
+ table: ({ node: _n, ...props }) => /* @__PURE__ */ jsx("div", {
581
+ className: "overflow-x-auto",
582
+ children: /* @__PURE__ */ jsx("table", {
583
+ ...props,
584
+ className: "my-2 w-full border-collapse text-sm"
585
+ })
586
+ }),
587
+ th: ({ node: _n, ...props }) => /* @__PURE__ */ jsx("th", {
515
588
  ...props,
516
- className: "my-2 w-full border-collapse text-sm"
589
+ className: "border border-border bg-muted px-2 py-1 text-left font-medium text-foreground"
590
+ }),
591
+ strong: ({ node: _n, ...props }) => /* @__PURE__ */ jsx("strong", {
592
+ ...props,
593
+ className: "font-semibold text-foreground"
594
+ }),
595
+ blockquote: ({ node: _n, ...props }) => /* @__PURE__ */ jsx("blockquote", {
596
+ ...props,
597
+ className: "border-l-2 border-border pl-3 text-muted-foreground"
598
+ }),
599
+ td: ({ node: _n, ...props }) => /* @__PURE__ */ jsx("td", {
600
+ ...props,
601
+ className: "border border-border px-2 py-1 align-top"
517
602
  })
518
- }),
519
- th: ({ node: _n, ...props }) => /* @__PURE__ */ jsx("th", {
520
- ...props,
521
- className: "border border-border bg-muted px-2 py-1 text-left font-medium"
522
- }),
523
- td: ({ node: _n, ...props }) => /* @__PURE__ */ jsx("td", {
524
- ...props,
525
- className: "border border-border px-2 py-1 align-top"
526
- })
527
- },
528
- children: text
529
- })
603
+ },
604
+ children: text
605
+ }),
606
+ live && /* @__PURE__ */ jsx("span", {
607
+ className: "yx-caret",
608
+ "aria-hidden": true,
609
+ children: "▍"
610
+ })
611
+ ]
530
612
  });
531
613
  }
532
614
 
533
615
  // src/components/PlanChecklist.tsx
534
616
  import { Check, Circle, Loader2 } from "lucide-react";
535
- import { jsx as jsx2, jsxs } from "react/jsx-runtime";
617
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
536
618
  function PlanChecklist({ items, className }) {
537
619
  if (!items.length)
538
620
  return null;
539
621
  const done = items.filter((t) => t.status === "completed").length;
540
- return /* @__PURE__ */ jsxs("div", {
622
+ return /* @__PURE__ */ jsxs2("div", {
541
623
  className: cn("yx-plan rounded-md border border-border bg-card p-3 text-sm", className),
542
624
  "aria-label": "plan",
543
625
  children: [
544
- /* @__PURE__ */ jsxs("div", {
626
+ /* @__PURE__ */ jsxs2("div", {
545
627
  className: "mb-2 flex items-center justify-between",
546
628
  children: [
547
629
  /* @__PURE__ */ jsx2("span", {
548
630
  className: "font-medium",
549
631
  children: "Plan"
550
632
  }),
551
- /* @__PURE__ */ jsxs("span", {
633
+ /* @__PURE__ */ jsxs2("span", {
552
634
  className: "font-mono text-[12px] tabular-nums text-muted-foreground",
553
635
  children: [
554
636
  done,
@@ -560,7 +642,7 @@ function PlanChecklist({ items, className }) {
560
642
  }),
561
643
  /* @__PURE__ */ jsx2("ol", {
562
644
  className: "space-y-1",
563
- children: items.map((t) => /* @__PURE__ */ jsxs("li", {
645
+ children: items.map((t) => /* @__PURE__ */ jsxs2("li", {
564
646
  className: cn("flex items-start gap-2", t.status === "completed" && "text-muted-foreground line-through"),
565
647
  "data-status": t.status,
566
648
  children: [
@@ -621,7 +703,7 @@ function DiffView({ diff, className }) {
621
703
  }
622
704
 
623
705
  // src/components/ToolCall.tsx
624
- import { jsx as jsx4, jsxs as jsxs2, Fragment } from "react/jsx-runtime";
706
+ import { jsx as jsx4, jsxs as jsxs3, Fragment } from "react/jsx-runtime";
625
707
  function toolSummary(name, input) {
626
708
  const pick = (...keys) => {
627
709
  for (const k of keys) {
@@ -667,12 +749,12 @@ function ToolCall({ item, defaultOpen = false, onOpenFile }) {
667
749
  const counts = r?.diff ? diffCounts(r.diff) : null;
668
750
  const path = typeof item.input.file_path === "string" ? item.input.file_path : typeof item.input.path === "string" ? item.input.path : undefined;
669
751
  const Icon = r?.diff ? FileDiff : item.name === "Bash" ? Terminal : Wrench;
670
- return /* @__PURE__ */ jsxs2("div", {
752
+ return /* @__PURE__ */ jsxs3("div", {
671
753
  className: cn("yx-tool rounded-md border border-border bg-card text-sm", status === "error" && "border-destructive/40"),
672
754
  "data-tool": item.name,
673
755
  "data-status": status,
674
756
  children: [
675
- /* @__PURE__ */ jsxs2("button", {
757
+ /* @__PURE__ */ jsxs3("button", {
676
758
  type: "button",
677
759
  onClick: () => setOpen((o) => !o),
678
760
  "aria-expanded": open,
@@ -694,10 +776,10 @@ function ToolCall({ item, defaultOpen = false, onOpenFile }) {
694
776
  className: "min-w-0 flex-1 truncate font-mono text-[12.5px] text-muted-foreground",
695
777
  children: clip(summary)
696
778
  }),
697
- counts && /* @__PURE__ */ jsxs2("span", {
779
+ counts && /* @__PURE__ */ jsxs3("span", {
698
780
  className: "shrink-0 font-mono text-[12px] tabular-nums",
699
781
  children: [
700
- /* @__PURE__ */ jsxs2("span", {
782
+ /* @__PURE__ */ jsxs3("span", {
701
783
  className: "text-success",
702
784
  children: [
703
785
  "+",
@@ -705,7 +787,7 @@ function ToolCall({ item, defaultOpen = false, onOpenFile }) {
705
787
  ]
706
788
  }),
707
789
  " ",
708
- /* @__PURE__ */ jsxs2("span", {
790
+ /* @__PURE__ */ jsxs3("span", {
709
791
  className: "text-destructive",
710
792
  children: [
711
793
  "−",
@@ -728,7 +810,7 @@ function ToolCall({ item, defaultOpen = false, onOpenFile }) {
728
810
  })
729
811
  ]
730
812
  }),
731
- open && /* @__PURE__ */ jsxs2("div", {
813
+ open && /* @__PURE__ */ jsxs3("div", {
732
814
  className: "border-t border-border px-3 py-2",
733
815
  children: [
734
816
  /* @__PURE__ */ jsx4("div", {
@@ -739,9 +821,9 @@ function ToolCall({ item, defaultOpen = false, onOpenFile }) {
739
821
  className: "overflow-x-auto whitespace-pre-wrap break-words font-mono text-[12px] leading-5",
740
822
  children: summary && Object.keys(item.input).length === 1 ? summary : JSON.stringify(item.input, null, 2)
741
823
  }),
742
- r && /* @__PURE__ */ jsxs2(Fragment, {
824
+ r && /* @__PURE__ */ jsxs3(Fragment, {
743
825
  children: [
744
- /* @__PURE__ */ jsxs2("div", {
826
+ /* @__PURE__ */ jsxs3("div", {
745
827
  className: "mb-1 mt-2 flex items-center gap-2 text-[11px] uppercase tracking-wide text-muted-foreground",
746
828
  children: [
747
829
  /* @__PURE__ */ jsx4("span", {
@@ -771,7 +853,85 @@ function ToolCall({ item, defaultOpen = false, onOpenFile }) {
771
853
  }
772
854
 
773
855
  // src/components/Transcript.tsx
774
- import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
856
+ import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
857
+ function TranscriptRow({ item, onOpenFile }) {
858
+ const it = item;
859
+ switch (it.kind) {
860
+ case "user":
861
+ return /* @__PURE__ */ jsx5("div", {
862
+ className: cn("flex", it.author.type === "human" ? "justify-end" : "justify-start"),
863
+ "data-author": it.author.type,
864
+ children: /* @__PURE__ */ jsxs4("div", {
865
+ className: cn("max-w-[85%] whitespace-pre-wrap break-words rounded-2xl px-4 py-2 text-[15px] leading-6", it.author.type === "human" ? "rounded-br-md bg-primary text-primary-foreground" : "rounded-bl-md border border-border bg-card"),
866
+ children: [
867
+ it.author.type !== "human" && /* @__PURE__ */ jsx5("div", {
868
+ className: "mb-1 text-[11px] text-muted-foreground",
869
+ children: it.author.displayName ?? it.author.type
870
+ }),
871
+ it.blocks?.some((b) => b.type === "image") && /* @__PURE__ */ jsx5("div", {
872
+ className: "mb-1 text-[11px] opacity-80",
873
+ children: "[image]"
874
+ }),
875
+ it.text
876
+ ]
877
+ })
878
+ });
879
+ case "assistant":
880
+ return /* @__PURE__ */ jsx5(AssistantMessage, {
881
+ text: it.text,
882
+ live: it.live
883
+ });
884
+ case "tool":
885
+ return /* @__PURE__ */ jsx5(ToolCall, {
886
+ item: it,
887
+ onOpenFile
888
+ });
889
+ case "plan":
890
+ return /* @__PURE__ */ jsx5(PlanChecklist, {
891
+ items: it.items
892
+ });
893
+ case "result": {
894
+ const { label, tone } = resultLabel(it.result);
895
+ return /* @__PURE__ */ jsxs4("div", {
896
+ className: cn("flex items-center gap-2 text-[12px] text-muted-foreground", tone === "bad" && "text-destructive"),
897
+ "data-subtype": it.result.subtype,
898
+ children: [
899
+ /* @__PURE__ */ jsx5("span", {
900
+ children: label
901
+ }),
902
+ /* @__PURE__ */ jsx5("span", {
903
+ children: "·"
904
+ }),
905
+ /* @__PURE__ */ jsxs4("span", {
906
+ className: "font-mono tabular-nums",
907
+ children: [
908
+ it.result.numTurns,
909
+ " step",
910
+ it.result.numTurns === 1 ? "" : "s",
911
+ " · ",
912
+ formatTokens(it.result.usage)
913
+ ]
914
+ })
915
+ ]
916
+ });
917
+ }
918
+ case "error":
919
+ return /* @__PURE__ */ jsx5("div", {
920
+ role: "alert",
921
+ className: "rounded-md border border-destructive/40 bg-destructive/5 px-3 py-2 text-[13px] text-destructive",
922
+ children: it.message
923
+ });
924
+ case "compact":
925
+ return /* @__PURE__ */ jsxs4("div", {
926
+ className: "text-center text-[11px] text-muted-foreground",
927
+ children: [
928
+ "— context compacted (",
929
+ it.replacedMessages,
930
+ " earlier messages summarised) —"
931
+ ]
932
+ });
933
+ }
934
+ }
775
935
  function Transcript({ items, autoScroll = true, onOpenFile, footer, emptyState, className }) {
776
936
  const ref = useRef2(null);
777
937
  const pinned = useRef2(true);
@@ -794,97 +954,23 @@ function Transcript({ items, autoScroll = true, onOpenFile, footer, emptyState,
794
954
  ref,
795
955
  className: cn("yx-transcript flex-1 overflow-y-auto px-4 py-3", className),
796
956
  role: "log",
797
- "aria-live": "polite",
798
- children: /* @__PURE__ */ jsxs3("div", {
957
+ children: /* @__PURE__ */ jsxs4("div", {
799
958
  className: "mx-auto flex w-full max-w-3xl flex-col gap-3",
800
959
  children: [
801
960
  items.length === 0 && emptyState,
802
- items.map((it) => {
803
- switch (it.kind) {
804
- case "user":
805
- return /* @__PURE__ */ jsx5("div", {
806
- className: cn("flex", it.author.type === "human" ? "justify-end" : "justify-start"),
807
- "data-author": it.author.type,
808
- children: /* @__PURE__ */ jsxs3("div", {
809
- className: cn("max-w-[85%] whitespace-pre-wrap break-words rounded-2xl px-4 py-2 text-[15px] leading-6", it.author.type === "human" ? "rounded-br-md bg-primary text-primary-foreground" : "rounded-bl-md border border-border bg-card"),
810
- children: [
811
- it.author.type !== "human" && /* @__PURE__ */ jsx5("div", {
812
- className: "mb-1 text-[11px] text-muted-foreground",
813
- children: it.author.displayName ?? it.author.type
814
- }),
815
- it.blocks?.some((b) => b.type === "image") && /* @__PURE__ */ jsx5("div", {
816
- className: "mb-1 text-[11px] opacity-80",
817
- children: "[image]"
818
- }),
819
- it.text
820
- ]
821
- })
822
- }, it.id);
823
- case "assistant":
824
- return /* @__PURE__ */ jsx5(AssistantMessage, {
825
- text: it.text,
826
- live: it.live
827
- }, it.id);
828
- case "tool":
829
- return /* @__PURE__ */ jsx5(ToolCall, {
830
- item: it,
831
- onOpenFile
832
- }, it.id);
833
- case "plan":
834
- return /* @__PURE__ */ jsx5(PlanChecklist, {
835
- items: it.items
836
- }, "plan");
837
- case "result": {
838
- const { label, tone } = resultLabel(it.result);
839
- return /* @__PURE__ */ jsxs3("div", {
840
- className: cn("flex items-center gap-2 text-[12px] text-muted-foreground", tone === "bad" && "text-destructive"),
841
- "data-subtype": it.result.subtype,
842
- children: [
843
- /* @__PURE__ */ jsx5("span", {
844
- children: label
845
- }),
846
- /* @__PURE__ */ jsx5("span", {
847
- children: "·"
848
- }),
849
- /* @__PURE__ */ jsxs3("span", {
850
- className: "font-mono tabular-nums",
851
- children: [
852
- it.result.numTurns,
853
- " step",
854
- it.result.numTurns === 1 ? "" : "s",
855
- " · ",
856
- formatTokens(it.result.usage)
857
- ]
858
- })
859
- ]
860
- }, it.id);
861
- }
862
- case "error":
863
- return /* @__PURE__ */ jsx5("div", {
864
- role: "alert",
865
- className: "rounded-md border border-destructive/40 bg-destructive/5 px-3 py-2 text-[13px] text-destructive",
866
- children: it.message
867
- }, it.id);
868
- case "compact":
869
- return /* @__PURE__ */ jsxs3("div", {
870
- className: "text-center text-[11px] text-muted-foreground",
871
- children: [
872
- "— context compacted (",
873
- it.replacedMessages,
874
- " earlier messages summarised) —"
875
- ]
876
- }, it.id);
877
- }
878
- }),
961
+ items.map((it) => /* @__PURE__ */ jsx5(TranscriptRow, {
962
+ item: it,
963
+ onOpenFile
964
+ }, it.id)),
879
965
  footer
880
966
  ]
881
967
  })
882
968
  });
883
969
  }
884
970
  // src/components/PermissionPrompt.tsx
885
- import { useEffect as useEffect3 } from "react";
971
+ import { useEffect as useEffect3, useState as useState2 } from "react";
886
972
  import { ShieldAlert } from "lucide-react";
887
- import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
973
+ import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
888
974
  function permissionSummary(p) {
889
975
  const v = p.input.command ?? p.input.file_path ?? p.input.path ?? p.input.url;
890
976
  if (typeof v === "string")
@@ -892,8 +978,12 @@ function permissionSummary(p) {
892
978
  const s = JSON.stringify(p.input);
893
979
  return s.length > 240 ? `${s.slice(0, 239)}…` : s;
894
980
  }
895
- function PermissionPrompt({ request, onAllow, onDeny, className }) {
981
+ function PermissionPrompt({ request, onAllow, onDeny, onAllowSession, hotkeys = false, className }) {
982
+ const [reasonOpen, setReasonOpen] = useState2(false);
983
+ const [reason, setReason] = useState2("");
896
984
  useEffect3(() => {
985
+ if (!hotkeys)
986
+ return;
897
987
  const key = (e) => {
898
988
  const t = e.target;
899
989
  if (t && (t.tagName === "TEXTAREA" || t.tagName === "INPUT" || t.isContentEditable))
@@ -905,15 +995,19 @@ function PermissionPrompt({ request, onAllow, onDeny, className }) {
905
995
  };
906
996
  document.addEventListener("keydown", key);
907
997
  return () => document.removeEventListener("keydown", key);
908
- }, [request.id, onAllow, onDeny]);
909
- return /* @__PURE__ */ jsxs4("div", {
998
+ }, [hotkeys, request.id, onAllow, onDeny]);
999
+ const kbd = (k) => hotkeys ? /* @__PURE__ */ jsx6("kbd", {
1000
+ className: "ml-1 rounded bg-black/10 px-1 text-[11px]",
1001
+ children: k
1002
+ }) : null;
1003
+ return /* @__PURE__ */ jsxs5("div", {
910
1004
  role: "alertdialog",
911
1005
  "aria-labelledby": `perm-${request.id}`,
912
1006
  className: cn("yx-permission rounded-md border border-warning/50 bg-card p-3 text-sm", className),
913
1007
  children: [
914
- /* @__PURE__ */ jsxs4("div", {
1008
+ /* @__PURE__ */ jsxs5("div", {
915
1009
  id: `perm-${request.id}`,
916
- className: "mb-2 flex items-center gap-2 font-medium",
1010
+ className: "mb-1 flex items-center gap-2 font-medium",
917
1011
  children: [
918
1012
  /* @__PURE__ */ jsx6(ShieldAlert, {
919
1013
  className: "h-4 w-4 text-warning",
@@ -927,46 +1021,116 @@ function PermissionPrompt({ request, onAllow, onDeny, className }) {
927
1021
  "?"
928
1022
  ]
929
1023
  }),
1024
+ (request.safetyReason || request.reason) && /* @__PURE__ */ jsx6("div", {
1025
+ className: "mb-2 text-[12px] text-muted-foreground",
1026
+ children: request.safetyReason ?? request.reason
1027
+ }),
930
1028
  /* @__PURE__ */ jsx6("pre", {
931
1029
  className: "mb-3 overflow-x-auto whitespace-pre-wrap break-words rounded bg-muted p-2 font-mono text-[12.5px] leading-5",
932
1030
  children: permissionSummary(request)
933
1031
  }),
934
- /* @__PURE__ */ jsxs4("div", {
935
- className: "flex gap-2",
1032
+ /* @__PURE__ */ jsxs5("div", {
1033
+ className: "flex flex-wrap gap-2",
936
1034
  children: [
937
- /* @__PURE__ */ jsxs4("button", {
1035
+ /* @__PURE__ */ jsxs5("button", {
938
1036
  type: "button",
939
1037
  onClick: () => onAllow(request.id),
940
1038
  className: "min-h-11 flex-1 rounded-md bg-primary px-4 font-medium text-primary-foreground hover:opacity-90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
941
1039
  children: [
942
1040
  "Allow ",
943
- /* @__PURE__ */ jsx6("kbd", {
944
- className: "ml-1 rounded bg-primary-foreground/20 px-1 text-[11px]",
945
- children: "a"
946
- })
1041
+ kbd("a")
947
1042
  ]
948
1043
  }),
949
- /* @__PURE__ */ jsxs4("button", {
1044
+ onAllowSession && /* @__PURE__ */ jsx6("button", {
1045
+ type: "button",
1046
+ onClick: () => onAllowSession(request.id),
1047
+ className: "min-h-11 flex-1 rounded-md border border-border bg-background px-4 font-medium hover:bg-muted",
1048
+ children: "Allow for this session"
1049
+ }),
1050
+ /* @__PURE__ */ jsxs5("button", {
950
1051
  type: "button",
951
- onClick: () => onDeny(request.id),
1052
+ onClick: () => reasonOpen ? onDeny(request.id, reason.trim() || undefined) : onDeny(request.id),
952
1053
  className: "min-h-11 flex-1 rounded-md border border-border bg-background px-4 font-medium hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
953
1054
  children: [
954
1055
  "Deny ",
955
- /* @__PURE__ */ jsx6("kbd", {
956
- className: "ml-1 rounded bg-muted px-1 text-[11px]",
957
- children: "d"
958
- })
1056
+ kbd("d")
959
1057
  ]
960
1058
  })
961
1059
  ]
1060
+ }),
1061
+ reasonOpen ? /* @__PURE__ */ jsxs5("label", {
1062
+ className: "mt-2 block",
1063
+ children: [
1064
+ /* @__PURE__ */ jsx6("span", {
1065
+ className: "mb-1 block text-[12px] text-muted-foreground",
1066
+ children: "Tell the agent why (sent with the denial)"
1067
+ }),
1068
+ /* @__PURE__ */ jsx6("input", {
1069
+ value: reason,
1070
+ onChange: (e) => setReason(e.target.value),
1071
+ className: "min-h-11 w-full rounded-md border border-border bg-input-background px-3 text-[16px]"
1072
+ })
1073
+ ]
1074
+ }) : /* @__PURE__ */ jsx6("button", {
1075
+ type: "button",
1076
+ onClick: () => setReasonOpen(true),
1077
+ className: "mt-2 min-h-9 text-[12px] text-info underline-offset-2 hover:underline",
1078
+ children: "Deny with a reason…"
1079
+ })
1080
+ ]
1081
+ });
1082
+ }
1083
+ // src/components/ApprovalPrompt.tsx
1084
+ import { ShieldAlert as ShieldAlert2 } from "lucide-react";
1085
+ import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
1086
+ function ApprovalPrompt({ approval, onDecide, className }) {
1087
+ return /* @__PURE__ */ jsxs6("div", {
1088
+ role: "alertdialog",
1089
+ "aria-label": "Approval needed",
1090
+ className: cn("yx-approval rounded-md border border-warning/50 bg-card p-3 text-sm", className),
1091
+ children: [
1092
+ /* @__PURE__ */ jsxs6("div", {
1093
+ className: "mb-1 flex items-center gap-2 font-medium",
1094
+ children: [
1095
+ /* @__PURE__ */ jsx7(ShieldAlert2, {
1096
+ className: "h-4 w-4 text-warning",
1097
+ "aria-hidden": true
1098
+ }),
1099
+ "The agent stopped before this and needs your approval"
1100
+ ]
1101
+ }),
1102
+ /* @__PURE__ */ jsx7("div", {
1103
+ className: "mb-2 text-[12px] text-muted-foreground",
1104
+ children: approval.reason
1105
+ }),
1106
+ /* @__PURE__ */ jsx7("pre", {
1107
+ className: "mb-3 overflow-x-auto whitespace-pre-wrap break-words rounded bg-muted p-2 font-mono text-[12.5px] leading-5",
1108
+ children: approval.command
1109
+ }),
1110
+ /* @__PURE__ */ jsxs6("div", {
1111
+ className: "flex gap-2",
1112
+ children: [
1113
+ /* @__PURE__ */ jsx7("button", {
1114
+ type: "button",
1115
+ onClick: () => onDecide("allow"),
1116
+ className: "min-h-11 flex-1 rounded-md bg-primary px-4 font-medium text-primary-foreground hover:opacity-90",
1117
+ children: "Approve and continue"
1118
+ }),
1119
+ /* @__PURE__ */ jsx7("button", {
1120
+ type: "button",
1121
+ onClick: () => onDecide("deny"),
1122
+ className: "min-h-11 flex-1 rounded-md border border-border px-4 font-medium hover:bg-muted",
1123
+ children: "Don't run it"
1124
+ })
1125
+ ]
962
1126
  })
963
1127
  ]
964
1128
  });
965
1129
  }
966
1130
  // src/components/QuestionForm.tsx
967
- import { useState as useState2 } from "react";
1131
+ import { useState as useState3 } from "react";
968
1132
  import { HelpCircle, X } from "lucide-react";
969
- import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
1133
+ import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
970
1134
  function emptyDraft(questions) {
971
1135
  return { selected: questions.map(() => []), notes: questions.map(() => "") };
972
1136
  }
@@ -989,8 +1153,8 @@ function chip(q, i) {
989
1153
  }
990
1154
  function QuestionForm({ request, onSubmit, onDecline, className }) {
991
1155
  const qs = request.questions;
992
- const [tab, setTab] = useState2(0);
993
- const [draft, setDraft] = useState2(() => emptyDraft(qs));
1156
+ const [tab, setTab] = useState3(0);
1157
+ const [draft, setDraft] = useState3(() => emptyDraft(qs));
994
1158
  const q = qs[tab];
995
1159
  if (!q)
996
1160
  return null;
@@ -1010,38 +1174,38 @@ function QuestionForm({ request, onSubmit, onDecline, className }) {
1010
1174
  });
1011
1175
  const answered = qs.map((_, i) => (draft.selected[i]?.length ?? 0) > 0 || (draft.notes[i] ?? "").trim().length > 0);
1012
1176
  const previewOf = (q.options ?? []).find((o) => picked[0] === o.label)?.preview;
1013
- return /* @__PURE__ */ jsxs5("div", {
1177
+ return /* @__PURE__ */ jsxs7("div", {
1014
1178
  role: "dialog",
1015
1179
  "aria-label": "The agent has a question",
1016
1180
  className: cn("yx-question rounded-md border border-info/50 bg-card text-sm", className),
1017
1181
  children: [
1018
- /* @__PURE__ */ jsxs5("div", {
1182
+ /* @__PURE__ */ jsxs7("div", {
1019
1183
  className: "flex items-center gap-2 border-b border-border px-3 py-2",
1020
1184
  children: [
1021
- /* @__PURE__ */ jsx7(HelpCircle, {
1185
+ /* @__PURE__ */ jsx8(HelpCircle, {
1022
1186
  className: "h-4 w-4 text-info",
1023
1187
  "aria-hidden": true
1024
1188
  }),
1025
- /* @__PURE__ */ jsx7("span", {
1189
+ /* @__PURE__ */ jsx8("span", {
1026
1190
  className: "font-medium",
1027
1191
  children: "The agent needs your input"
1028
1192
  }),
1029
- /* @__PURE__ */ jsx7("button", {
1193
+ /* @__PURE__ */ jsx8("button", {
1030
1194
  type: "button",
1031
1195
  onClick: () => onDecline(request.id),
1032
1196
  "aria-label": "Close and keep talking",
1033
1197
  className: "ml-auto flex h-11 w-11 items-center justify-center rounded-md text-muted-foreground hover:bg-muted",
1034
- children: /* @__PURE__ */ jsx7(X, {
1198
+ children: /* @__PURE__ */ jsx8(X, {
1035
1199
  className: "h-4 w-4",
1036
1200
  "aria-hidden": true
1037
1201
  })
1038
1202
  })
1039
1203
  ]
1040
1204
  }),
1041
- qs.length > 1 && /* @__PURE__ */ jsx7("div", {
1205
+ qs.length > 1 && /* @__PURE__ */ jsx8("div", {
1042
1206
  role: "tablist",
1043
1207
  className: "flex gap-1 overflow-x-auto border-b border-border px-2 py-1",
1044
- children: qs.map((qq, i) => /* @__PURE__ */ jsx7("button", {
1208
+ children: qs.map((qq, i) => /* @__PURE__ */ jsx8("button", {
1045
1209
  role: "tab",
1046
1210
  type: "button",
1047
1211
  "aria-selected": i === tab,
@@ -1050,37 +1214,37 @@ function QuestionForm({ request, onSubmit, onDecline, className }) {
1050
1214
  children: chip(qq, i)
1051
1215
  }, i))
1052
1216
  }),
1053
- /* @__PURE__ */ jsxs5("div", {
1217
+ /* @__PURE__ */ jsxs7("div", {
1054
1218
  className: "space-y-3 p-3",
1055
1219
  children: [
1056
- /* @__PURE__ */ jsx7("p", {
1220
+ /* @__PURE__ */ jsx8("p", {
1057
1221
  className: "font-medium",
1058
1222
  children: q.question
1059
1223
  }),
1060
- (q.options ?? []).length > 0 && /* @__PURE__ */ jsx7("div", {
1224
+ (q.options ?? []).length > 0 && /* @__PURE__ */ jsx8("div", {
1061
1225
  role: q.multiSelect ? "group" : "radiogroup",
1062
1226
  className: "space-y-2",
1063
1227
  children: (q.options ?? []).map((o) => {
1064
1228
  const on = picked.includes(o.label);
1065
- return /* @__PURE__ */ jsxs5("button", {
1229
+ return /* @__PURE__ */ jsxs7("button", {
1066
1230
  type: "button",
1067
1231
  role: q.multiSelect ? "checkbox" : "radio",
1068
1232
  "aria-checked": on,
1069
1233
  onClick: () => toggle(o.label),
1070
1234
  className: cn("flex min-h-11 w-full items-start gap-3 rounded-md border px-3 py-2 text-left hover:bg-muted/60", on ? "border-primary bg-muted" : "border-border"),
1071
1235
  children: [
1072
- /* @__PURE__ */ jsx7("span", {
1236
+ /* @__PURE__ */ jsx8("span", {
1073
1237
  className: cn("mt-1 h-4 w-4 shrink-0 border", q.multiSelect ? "rounded" : "rounded-full", on ? "border-primary bg-primary" : "border-muted-foreground"),
1074
1238
  "aria-hidden": true
1075
1239
  }),
1076
- /* @__PURE__ */ jsxs5("span", {
1240
+ /* @__PURE__ */ jsxs7("span", {
1077
1241
  className: "min-w-0",
1078
1242
  children: [
1079
- /* @__PURE__ */ jsx7("span", {
1243
+ /* @__PURE__ */ jsx8("span", {
1080
1244
  className: "block font-medium",
1081
1245
  children: o.label
1082
1246
  }),
1083
- o.description && /* @__PURE__ */ jsx7("span", {
1247
+ o.description && /* @__PURE__ */ jsx8("span", {
1084
1248
  className: "block text-muted-foreground",
1085
1249
  children: o.description
1086
1250
  })
@@ -1090,18 +1254,18 @@ function QuestionForm({ request, onSubmit, onDecline, className }) {
1090
1254
  }, o.label);
1091
1255
  })
1092
1256
  }),
1093
- previewOf && /* @__PURE__ */ jsx7("pre", {
1257
+ previewOf && /* @__PURE__ */ jsx8("pre", {
1094
1258
  className: "overflow-x-auto rounded-md border border-border bg-muted p-2 font-mono text-[12px] leading-5",
1095
1259
  children: previewOf
1096
1260
  }),
1097
- /* @__PURE__ */ jsxs5("label", {
1261
+ /* @__PURE__ */ jsxs7("label", {
1098
1262
  className: "block",
1099
1263
  children: [
1100
- /* @__PURE__ */ jsx7("span", {
1264
+ /* @__PURE__ */ jsx8("span", {
1101
1265
  className: "mb-1 block text-[12px] text-muted-foreground",
1102
1266
  children: (q.options ?? []).length ? "Add a note (optional)" : "Your answer"
1103
1267
  }),
1104
- /* @__PURE__ */ jsx7("textarea", {
1268
+ /* @__PURE__ */ jsx8("textarea", {
1105
1269
  value: draft.notes[tab] ?? "",
1106
1270
  onChange: (e) => setNote(e.target.value),
1107
1271
  rows: 2,
@@ -1109,16 +1273,16 @@ function QuestionForm({ request, onSubmit, onDecline, className }) {
1109
1273
  })
1110
1274
  ]
1111
1275
  }),
1112
- /* @__PURE__ */ jsxs5("div", {
1276
+ /* @__PURE__ */ jsxs7("div", {
1113
1277
  className: "flex gap-2",
1114
1278
  children: [
1115
- tab < qs.length - 1 ? /* @__PURE__ */ jsx7("button", {
1279
+ tab < qs.length - 1 ? /* @__PURE__ */ jsx8("button", {
1116
1280
  type: "button",
1117
1281
  onClick: () => setTab(tab + 1),
1118
1282
  className: "min-h-11 flex-1 rounded-md border border-border px-4 font-medium hover:bg-muted",
1119
1283
  children: "Next"
1120
1284
  }) : null,
1121
- /* @__PURE__ */ jsx7("button", {
1285
+ /* @__PURE__ */ jsx8("button", {
1122
1286
  type: "button",
1123
1287
  onClick: () => onSubmit(request.id, buildOutcome(qs, draft)),
1124
1288
  disabled: !answered.some(Boolean),
@@ -1133,11 +1297,11 @@ function QuestionForm({ request, onSubmit, onDecline, className }) {
1133
1297
  });
1134
1298
  }
1135
1299
  // src/components/RunStatus.tsx
1136
- import { useEffect as useEffect4, useState as useState3 } from "react";
1300
+ import { useEffect as useEffect4, useState as useState4 } from "react";
1137
1301
  import { AlertTriangle, Check as Check3, Loader2 as Loader23, Square } from "lucide-react";
1138
- import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
1139
- function RunStatus({ running, startedAt, activity, lastResult, usageTotal, connection, onStop, onApprove, className, now = Date.now }) {
1140
- const [, tick] = useState3(0);
1302
+ import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
1303
+ function RunStatus({ running, startedAt, activity, waitingOn, lastResult, usageTotal, connection, onStop, onApprove, className, now = Date.now }) {
1304
+ const [, tick] = useState4(0);
1141
1305
  useEffect4(() => {
1142
1306
  if (!running)
1143
1307
  return;
@@ -1145,11 +1309,11 @@ function RunStatus({ running, startedAt, activity, lastResult, usageTotal, conne
1145
1309
  return () => clearInterval(h);
1146
1310
  }, [running]);
1147
1311
  if (connection && connection !== "connected") {
1148
- return /* @__PURE__ */ jsxs6("div", {
1312
+ return /* @__PURE__ */ jsxs8("div", {
1149
1313
  className: cn("yx-status flex min-h-9 items-center gap-2 text-[12.5px] text-muted-foreground", className),
1150
1314
  role: "status",
1151
1315
  children: [
1152
- /* @__PURE__ */ jsx8(Loader23, {
1316
+ /* @__PURE__ */ jsx9(Loader23, {
1153
1317
  className: "h-3.5 w-3.5 animate-spin",
1154
1318
  "aria-hidden": true
1155
1319
  }),
@@ -1158,32 +1322,34 @@ function RunStatus({ running, startedAt, activity, lastResult, usageTotal, conne
1158
1322
  });
1159
1323
  }
1160
1324
  if (running) {
1161
- return /* @__PURE__ */ jsxs6("div", {
1162
- className: cn("yx-status flex min-h-9 items-center gap-2 text-[12.5px] text-muted-foreground", className),
1163
- role: "status",
1164
- "aria-live": "polite",
1325
+ const label2 = waitingOn === "permission" ? "Waiting for your approval" : waitingOn === "question" ? "Waiting for your answer" : activity ?? "Working";
1326
+ return /* @__PURE__ */ jsxs8("div", {
1327
+ className: cn("yx-status flex min-h-11 items-center gap-2 text-[12.5px] text-muted-foreground", className),
1165
1328
  children: [
1166
- /* @__PURE__ */ jsx8(Loader23, {
1167
- className: "h-3.5 w-3.5 animate-spin text-accent",
1329
+ /* @__PURE__ */ jsx9(Loader23, {
1330
+ className: cn("h-3.5 w-3.5 animate-spin", waitingOn ? "text-warning" : "text-accent"),
1168
1331
  "aria-hidden": true
1169
1332
  }),
1170
- /* @__PURE__ */ jsx8("span", {
1171
- children: activity ?? "Working"
1333
+ /* @__PURE__ */ jsx9("span", {
1334
+ role: "status",
1335
+ "aria-live": "polite",
1336
+ children: label2
1172
1337
  }),
1173
- startedAt !== undefined && /* @__PURE__ */ jsxs6("span", {
1338
+ startedAt !== undefined && /* @__PURE__ */ jsxs8("span", {
1174
1339
  className: "font-mono tabular-nums",
1340
+ "aria-hidden": true,
1175
1341
  children: [
1176
1342
  "· ",
1177
1343
  elapsedLabel(now() - startedAt)
1178
1344
  ]
1179
1345
  }),
1180
- onStop && /* @__PURE__ */ jsxs6("button", {
1346
+ onStop && /* @__PURE__ */ jsxs8("button", {
1181
1347
  type: "button",
1182
1348
  onClick: onStop,
1183
- className: "ml-auto flex min-h-9 items-center gap-1 rounded-md px-2 hover:bg-muted",
1349
+ className: "ml-auto flex min-h-11 items-center gap-1 rounded-md px-3 hover:bg-muted",
1184
1350
  "aria-label": "Stop",
1185
1351
  children: [
1186
- /* @__PURE__ */ jsx8(Square, {
1352
+ /* @__PURE__ */ jsx9(Square, {
1187
1353
  className: "h-3.5 w-3.5",
1188
1354
  "aria-hidden": true
1189
1355
  }),
@@ -1197,30 +1363,30 @@ function RunStatus({ running, startedAt, activity, lastResult, usageTotal, conne
1197
1363
  return null;
1198
1364
  const { label, tone } = resultLabel(lastResult);
1199
1365
  const needsApproval = lastResult.subtype === "needs_approval";
1200
- return /* @__PURE__ */ jsxs6("div", {
1366
+ return /* @__PURE__ */ jsxs8("div", {
1201
1367
  className: cn("yx-status flex min-h-9 flex-wrap items-center gap-2 text-[12.5px]", className),
1202
1368
  role: "status",
1203
1369
  "data-subtype": lastResult.subtype,
1204
1370
  children: [
1205
- tone === "ok" ? /* @__PURE__ */ jsx8(Check3, {
1371
+ tone === "ok" ? /* @__PURE__ */ jsx9(Check3, {
1206
1372
  className: "h-3.5 w-3.5 text-success",
1207
1373
  "aria-hidden": true
1208
- }) : tone === "bad" ? /* @__PURE__ */ jsx8(AlertTriangle, {
1374
+ }) : tone === "bad" ? /* @__PURE__ */ jsx9(AlertTriangle, {
1209
1375
  className: "h-3.5 w-3.5 text-destructive",
1210
1376
  "aria-hidden": true
1211
- }) : /* @__PURE__ */ jsx8(AlertTriangle, {
1377
+ }) : /* @__PURE__ */ jsx9(AlertTriangle, {
1212
1378
  className: cn("h-3.5 w-3.5", tone === "warn" ? "text-warning" : "text-muted-foreground"),
1213
1379
  "aria-hidden": true
1214
1380
  }),
1215
- /* @__PURE__ */ jsx8("span", {
1381
+ /* @__PURE__ */ jsx9("span", {
1216
1382
  className: cn(tone === "ok" && "text-success", tone === "bad" && "text-destructive", tone === "warn" && "text-warning", tone === "muted" && "text-muted-foreground"),
1217
1383
  children: label
1218
1384
  }),
1219
- /* @__PURE__ */ jsx8("span", {
1385
+ /* @__PURE__ */ jsx9("span", {
1220
1386
  className: "text-muted-foreground",
1221
1387
  children: "·"
1222
1388
  }),
1223
- /* @__PURE__ */ jsxs6("span", {
1389
+ /* @__PURE__ */ jsxs8("span", {
1224
1390
  className: "font-mono tabular-nums text-muted-foreground",
1225
1391
  children: [
1226
1392
  lastResult.numTurns,
@@ -1230,42 +1396,19 @@ function RunStatus({ running, startedAt, activity, lastResult, usageTotal, conne
1230
1396
  formatTokens(usageTotal ?? lastResult.usage)
1231
1397
  ]
1232
1398
  }),
1233
- needsApproval && lastResult.pendingApproval && /* @__PURE__ */ jsxs6("div", {
1234
- className: "basis-full rounded-md border border-warning/50 bg-card p-2",
1235
- children: [
1236
- /* @__PURE__ */ jsx8("div", {
1237
- className: "mb-1 text-[12px] text-muted-foreground",
1238
- children: lastResult.pendingApproval.reason
1239
- }),
1240
- /* @__PURE__ */ jsx8("pre", {
1241
- className: "mb-2 overflow-x-auto whitespace-pre-wrap break-words font-mono text-[12px]",
1242
- children: lastResult.pendingApproval.command
1243
- }),
1244
- onApprove && /* @__PURE__ */ jsxs6("div", {
1245
- className: "flex gap-2",
1246
- children: [
1247
- /* @__PURE__ */ jsx8("button", {
1248
- type: "button",
1249
- onClick: () => onApprove("allow"),
1250
- className: "min-h-11 flex-1 rounded-md bg-primary px-3 font-medium text-primary-foreground",
1251
- children: "Approve and continue"
1252
- }),
1253
- /* @__PURE__ */ jsx8("button", {
1254
- type: "button",
1255
- onClick: () => onApprove("deny"),
1256
- className: "min-h-11 flex-1 rounded-md border border-border px-3 font-medium hover:bg-muted",
1257
- children: "Don't run it"
1258
- })
1259
- ]
1260
- })
1261
- ]
1399
+ needsApproval && lastResult.pendingApproval && onApprove && /* @__PURE__ */ jsx9("div", {
1400
+ className: "basis-full",
1401
+ children: /* @__PURE__ */ jsx9(ApprovalPrompt, {
1402
+ approval: lastResult.pendingApproval,
1403
+ onDecide: onApprove
1404
+ })
1262
1405
  })
1263
1406
  ]
1264
1407
  });
1265
1408
  }
1266
1409
  // src/components/SessionList.tsx
1267
1410
  import { Plus } from "lucide-react";
1268
- import { jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
1411
+ import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
1269
1412
  function groupSessions(rows) {
1270
1413
  const groups = new Map;
1271
1414
  for (const r of rows) {
@@ -1285,74 +1428,75 @@ function when(iso) {
1285
1428
  }
1286
1429
  function SessionList({ sessions, selectedId, onSelect, onNew, className }) {
1287
1430
  const groups = groupSessions(sessions);
1288
- return /* @__PURE__ */ jsxs7("nav", {
1431
+ return /* @__PURE__ */ jsxs9("nav", {
1289
1432
  className: cn("yx-sessions text-sm", className),
1290
1433
  "aria-label": "Sessions",
1291
1434
  children: [
1292
- onNew && /* @__PURE__ */ jsxs7("button", {
1435
+ onNew && /* @__PURE__ */ jsxs9("button", {
1293
1436
  type: "button",
1294
1437
  onClick: onNew,
1295
1438
  className: "mb-2 flex min-h-11 w-full items-center gap-2 rounded-md border border-dashed border-border px-3 text-left hover:bg-muted",
1296
1439
  children: [
1297
- /* @__PURE__ */ jsx9(Plus, {
1440
+ /* @__PURE__ */ jsx10(Plus, {
1298
1441
  className: "h-4 w-4",
1299
1442
  "aria-hidden": true
1300
1443
  }),
1301
1444
  " New session"
1302
1445
  ]
1303
1446
  }),
1304
- sessions.length === 0 && /* @__PURE__ */ jsx9("p", {
1447
+ sessions.length === 0 && /* @__PURE__ */ jsx10("p", {
1305
1448
  className: "px-3 py-2 text-muted-foreground",
1306
1449
  children: "No sessions yet."
1307
1450
  }),
1308
- groups.map((g) => /* @__PURE__ */ jsxs7("div", {
1451
+ groups.map((g) => /* @__PURE__ */ jsxs9("div", {
1309
1452
  className: "mb-2",
1310
1453
  children: [
1311
- g.project && /* @__PURE__ */ jsx9("div", {
1454
+ g.project && /* @__PURE__ */ jsx10("div", {
1312
1455
  className: "px-3 py-1 text-[11px] uppercase tracking-wide text-muted-foreground",
1313
1456
  children: g.project
1314
1457
  }),
1315
- /* @__PURE__ */ jsx9("ul", {
1458
+ /* @__PURE__ */ jsx10("ul", {
1316
1459
  children: g.rows.map((s) => {
1317
1460
  const on = s.sessionId === selectedId;
1318
1461
  const title = s.name || s.title || s.lastMessagePreview || s.sessionId.slice(0, 8);
1319
- return /* @__PURE__ */ jsx9("li", {
1320
- children: /* @__PURE__ */ jsxs7("button", {
1462
+ return /* @__PURE__ */ jsx10("li", {
1463
+ children: /* @__PURE__ */ jsxs9("button", {
1321
1464
  type: "button",
1322
1465
  onClick: () => onSelect(s.sessionId),
1323
1466
  "aria-current": on ? "true" : undefined,
1324
1467
  className: cn("flex min-h-11 w-full flex-col gap-0.5 rounded-md px-3 py-2 text-left hover:bg-muted", on && "bg-muted"),
1325
1468
  "data-status": s.status,
1326
1469
  children: [
1327
- /* @__PURE__ */ jsxs7("span", {
1470
+ /* @__PURE__ */ jsxs9("span", {
1328
1471
  className: "flex items-center gap-2",
1329
1472
  children: [
1330
- /* @__PURE__ */ jsx9("span", {
1331
- className: cn("h-2 w-2 shrink-0 rounded-full", s.status === "active" ? "bg-success" : s.status === "listening" ? "bg-info" : s.status === "error" ? "bg-destructive" : "bg-muted-foreground/40"),
1332
- "aria-label": s.status
1473
+ /* @__PURE__ */ jsx10("span", {
1474
+ className: cn("h-2 w-2 shrink-0 rounded-full", s.live ? "animate-pulse bg-accent" : s.status === "active" ? "bg-success" : s.status === "listening" ? "bg-info" : s.status === "error" ? "bg-destructive" : "bg-muted-foreground/40"),
1475
+ "aria-label": s.live ? "working" : s.status,
1476
+ "data-live": s.live ? "true" : undefined
1333
1477
  }),
1334
- /* @__PURE__ */ jsx9("span", {
1478
+ /* @__PURE__ */ jsx10("span", {
1335
1479
  className: "min-w-0 flex-1 truncate font-medium",
1336
1480
  children: title
1337
1481
  }),
1338
- s.pendingQuestion && /* @__PURE__ */ jsx9("span", {
1482
+ s.pendingQuestion && /* @__PURE__ */ jsx10("span", {
1339
1483
  className: "shrink-0 rounded-full bg-warning px-1.5 text-[10px] font-medium text-warning-foreground",
1340
1484
  children: "question"
1341
1485
  })
1342
1486
  ]
1343
1487
  }),
1344
- /* @__PURE__ */ jsxs7("span", {
1488
+ /* @__PURE__ */ jsxs9("span", {
1345
1489
  className: "flex gap-2 truncate text-[11.5px] text-muted-foreground",
1346
1490
  children: [
1347
- s.hop && /* @__PURE__ */ jsx9("span", {
1491
+ s.hop && /* @__PURE__ */ jsx10("span", {
1348
1492
  className: "truncate",
1349
1493
  children: s.hop
1350
1494
  }),
1351
- s.relCwd && /* @__PURE__ */ jsx9("span", {
1495
+ s.relCwd && /* @__PURE__ */ jsx10("span", {
1352
1496
  className: "truncate",
1353
1497
  children: s.relCwd
1354
1498
  }),
1355
- /* @__PURE__ */ jsx9("span", {
1499
+ /* @__PURE__ */ jsx10("span", {
1356
1500
  className: "ml-auto shrink-0 tabular-nums",
1357
1501
  children: when(s.updatedAt)
1358
1502
  })
@@ -1369,9 +1513,9 @@ function SessionList({ sessions, selectedId, onSelect, onNew, className }) {
1369
1513
  });
1370
1514
  }
1371
1515
  // src/components/Composer.tsx
1372
- import { useCallback as useCallback2, useEffect as useEffect5, useRef as useRef3, useState as useState4 } from "react";
1516
+ import { useCallback as useCallback2, useEffect as useEffect5, useRef as useRef3, useState as useState5 } from "react";
1373
1517
  import { ArrowUp, ImagePlus, Square as Square2, X as X2 } from "lucide-react";
1374
- import { jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
1518
+ import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
1375
1519
  var ACCEPT = ["image/png", "image/jpeg", "image/gif", "image/webp"];
1376
1520
  async function fileToAttachment(f) {
1377
1521
  const buf = new Uint8Array(await f.arrayBuffer());
@@ -1380,17 +1524,34 @@ async function fileToAttachment(f) {
1380
1524
  bin += String.fromCharCode(...buf.subarray(i, i + 32768));
1381
1525
  return { name: f.name || "image", mediaType: f.type, data: btoa(bin), previewUrl: URL.createObjectURL(f) };
1382
1526
  }
1383
- function Composer({ onSubmit, running, onStop, disabled, disabledReason, placeholder = "Ask yodex to do something…", hopOptions, hop, onHopChange, leading, maxAttachmentBytes = 8 * 1024 * 1024, className, autoFocus }) {
1384
- const [text, setText] = useState4("");
1385
- const [attachments, setAttachments] = useState4([]);
1386
- const [note, setNote] = useState4(null);
1527
+ function coarsePointer() {
1528
+ return typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia("(pointer: coarse)").matches;
1529
+ }
1530
+ function Composer({ onSubmit, running, onStop, onSteer, value, onChange, initialValue, submitOnEnter, hideAttach, disabled, disabledReason, placeholder = "Ask yodex to do something…", hopOptions, hop, onHopChange, leading, maxAttachmentBytes = 8 * 1024 * 1024, className, autoFocus }) {
1531
+ const [own, setOwn] = useState5(initialValue ?? "");
1532
+ const text = value ?? own;
1533
+ const setText = (t) => {
1534
+ if (value === undefined)
1535
+ setOwn(t);
1536
+ onChange?.(t);
1537
+ };
1538
+ const [attachments, setAttachments] = useState5([]);
1539
+ const [note, setNote] = useState5(null);
1387
1540
  const ref = useRef3(null);
1388
1541
  useEffect5(() => {
1389
1542
  const el = ref.current;
1390
1543
  if (!el)
1391
1544
  return;
1392
- el.style.height = "0px";
1393
- el.style.height = `${Math.min(el.scrollHeight, 240)}px`;
1545
+ const fit = () => {
1546
+ el.style.height = "0px";
1547
+ el.style.height = `${Math.min(el.scrollHeight, 240)}px`;
1548
+ };
1549
+ fit();
1550
+ if (typeof ResizeObserver === "undefined")
1551
+ return;
1552
+ const ro = new ResizeObserver(fit);
1553
+ ro.observe(el);
1554
+ return () => ro.disconnect();
1394
1555
  }, [text]);
1395
1556
  const addFiles = useCallback2(async (files) => {
1396
1557
  const list = [...files].filter((f) => ACCEPT.includes(f.type));
@@ -1411,10 +1572,16 @@ function Composer({ onSubmit, running, onStop, disabled, disabledReason, placeho
1411
1572
  URL.revokeObjectURL(gone.previewUrl);
1412
1573
  return a.filter((_, j) => j !== i);
1413
1574
  });
1414
- const canSend = !disabled && !running && (text.trim().length > 0 || attachments.length > 0);
1575
+ const steering = Boolean(running && onSteer);
1576
+ const canSend = !disabled && (steering ? text.trim().length > 0 : !running && (text.trim().length > 0 || attachments.length > 0));
1415
1577
  const submit = () => {
1416
1578
  if (!canSend)
1417
1579
  return;
1580
+ if (steering) {
1581
+ onSteer(text.trim());
1582
+ setText("");
1583
+ return;
1584
+ }
1418
1585
  onSubmit(text.trim(), attachments);
1419
1586
  setText("");
1420
1587
  for (const a of attachments)
@@ -1423,10 +1590,13 @@ function Composer({ onSubmit, running, onStop, disabled, disabledReason, placeho
1423
1590
  setAttachments([]);
1424
1591
  };
1425
1592
  const onKey = (e) => {
1426
- if (e.key === "Enter" && !e.shiftKey && !e.nativeEvent.isComposing) {
1427
- e.preventDefault();
1428
- submit();
1429
- }
1593
+ if (e.key !== "Enter" || e.shiftKey || e.nativeEvent.isComposing)
1594
+ return;
1595
+ const enterSends = submitOnEnter ?? !coarsePointer();
1596
+ if (!enterSends)
1597
+ return;
1598
+ e.preventDefault();
1599
+ submit();
1430
1600
  };
1431
1601
  const onPaste = (e) => {
1432
1602
  const files = [...e.clipboardData.files];
@@ -1441,39 +1611,42 @@ function Composer({ onSubmit, running, onStop, disabled, disabledReason, placeho
1441
1611
  addFiles(e.dataTransfer.files);
1442
1612
  }
1443
1613
  };
1444
- return /* @__PURE__ */ jsxs8("div", {
1614
+ return /* @__PURE__ */ jsxs10("div", {
1445
1615
  className: cn("yx-composer rounded-lg border border-border bg-card shadow-sm", disabled && "opacity-70", className),
1446
1616
  onDrop,
1447
1617
  onDragOver: (e) => e.preventDefault(),
1448
1618
  "data-disabled": disabled ? "true" : undefined,
1449
1619
  children: [
1450
- attachments.length > 0 && /* @__PURE__ */ jsx10("ul", {
1620
+ attachments.length > 0 && /* @__PURE__ */ jsx11("ul", {
1451
1621
  className: "flex flex-wrap gap-2 px-3 pt-3",
1452
1622
  "aria-label": "attachments",
1453
- children: attachments.map((a, i) => /* @__PURE__ */ jsxs8("li", {
1623
+ children: attachments.map((a, i) => /* @__PURE__ */ jsxs10("li", {
1454
1624
  className: "relative",
1455
1625
  children: [
1456
- a.previewUrl ? /* @__PURE__ */ jsx10("img", {
1626
+ a.previewUrl ? /* @__PURE__ */ jsx11("img", {
1457
1627
  src: a.previewUrl,
1458
1628
  alt: a.name,
1459
1629
  className: "h-14 w-14 rounded-md border border-border object-cover"
1460
- }) : /* @__PURE__ */ jsx10("span", {
1630
+ }) : /* @__PURE__ */ jsx11("span", {
1461
1631
  className: "block h-14 w-14 rounded-md border border-border bg-muted"
1462
1632
  }),
1463
- /* @__PURE__ */ jsx10("button", {
1633
+ /* @__PURE__ */ jsx11("button", {
1464
1634
  type: "button",
1465
1635
  onClick: () => remove(i),
1466
1636
  "aria-label": `Remove ${a.name}`,
1467
- className: "absolute -right-2 -top-2 flex h-6 w-6 items-center justify-center rounded-full border border-border bg-background",
1468
- children: /* @__PURE__ */ jsx10(X2, {
1469
- className: "h-3 w-3",
1470
- "aria-hidden": true
1637
+ className: "absolute -right-4 -top-4 flex h-11 w-11 items-center justify-center rounded-full",
1638
+ children: /* @__PURE__ */ jsx11("span", {
1639
+ className: "flex h-6 w-6 items-center justify-center rounded-full border border-border bg-background",
1640
+ children: /* @__PURE__ */ jsx11(X2, {
1641
+ className: "h-3 w-3",
1642
+ "aria-hidden": true
1643
+ })
1471
1644
  })
1472
1645
  })
1473
1646
  ]
1474
1647
  }, `${a.name}-${i}`))
1475
1648
  }),
1476
- /* @__PURE__ */ jsx10("textarea", {
1649
+ /* @__PURE__ */ jsx11("textarea", {
1477
1650
  ref,
1478
1651
  value: text,
1479
1652
  onChange: (e) => setText(e.target.value),
@@ -1482,35 +1655,35 @@ function Composer({ onSubmit, running, onStop, disabled, disabledReason, placeho
1482
1655
  disabled,
1483
1656
  autoFocus,
1484
1657
  rows: 1,
1485
- placeholder: disabled && disabledReason ? disabledReason : placeholder,
1486
- "aria-label": "Message",
1487
- className: "block w-full resize-none bg-transparent px-3 py-3 text-[15px] leading-6 placeholder:text-muted-foreground focus-visible:outline-none"
1658
+ placeholder: disabled && disabledReason ? disabledReason : steering ? "Steer the agent (it reads this at its next step)…" : placeholder,
1659
+ "aria-label": steering ? "Steer" : "Message",
1660
+ className: "block w-full resize-none bg-transparent px-3 py-3 text-[16px] leading-6 placeholder:text-muted-foreground focus-visible:outline-none"
1488
1661
  }),
1489
- /* @__PURE__ */ jsxs8("div", {
1662
+ /* @__PURE__ */ jsxs10("div", {
1490
1663
  className: "flex items-center gap-1 px-2 pb-2",
1491
1664
  children: [
1492
1665
  leading,
1493
- hopOptions && hopOptions.length > 0 && /* @__PURE__ */ jsx10("select", {
1666
+ hopOptions && hopOptions.length > 0 && /* @__PURE__ */ jsx11("select", {
1494
1667
  value: hop ?? "",
1495
1668
  onChange: (e) => onHopChange?.(e.target.value),
1496
1669
  disabled,
1497
1670
  "aria-label": "HOP",
1498
1671
  className: "min-h-9 max-w-[45%] rounded-md border border-border bg-background px-2 text-[12.5px]",
1499
- children: hopOptions.map((o) => /* @__PURE__ */ jsx10("option", {
1672
+ children: hopOptions.map((o) => /* @__PURE__ */ jsx11("option", {
1500
1673
  value: o.value,
1501
1674
  title: o.description,
1502
1675
  children: o.label
1503
1676
  }, o.value))
1504
1677
  }),
1505
- /* @__PURE__ */ jsxs8("label", {
1678
+ !hideAttach && /* @__PURE__ */ jsxs10("label", {
1506
1679
  className: cn("flex h-11 w-11 cursor-pointer items-center justify-center rounded-md text-muted-foreground hover:bg-muted", disabled && "pointer-events-none"),
1507
1680
  "aria-label": "Attach image",
1508
1681
  children: [
1509
- /* @__PURE__ */ jsx10(ImagePlus, {
1682
+ /* @__PURE__ */ jsx11(ImagePlus, {
1510
1683
  className: "h-4 w-4",
1511
1684
  "aria-hidden": true
1512
1685
  }),
1513
- /* @__PURE__ */ jsx10("input", {
1686
+ /* @__PURE__ */ jsx11("input", {
1514
1687
  type: "file",
1515
1688
  accept: ACCEPT.join(","),
1516
1689
  multiple: true,
@@ -1520,29 +1693,29 @@ function Composer({ onSubmit, running, onStop, disabled, disabledReason, placeho
1520
1693
  })
1521
1694
  ]
1522
1695
  }),
1523
- note && /* @__PURE__ */ jsx10("span", {
1696
+ note && /* @__PURE__ */ jsx11("span", {
1524
1697
  className: "truncate text-[12px] text-destructive",
1525
1698
  children: note
1526
1699
  }),
1527
- /* @__PURE__ */ jsx10("span", {
1700
+ /* @__PURE__ */ jsx11("span", {
1528
1701
  className: "flex-1"
1529
1702
  }),
1530
- running ? /* @__PURE__ */ jsx10("button", {
1703
+ running && onStop && !(steering && text.trim()) ? /* @__PURE__ */ jsx11("button", {
1531
1704
  type: "button",
1532
1705
  onClick: onStop,
1533
1706
  "aria-label": "Stop",
1534
1707
  className: "flex h-11 w-11 items-center justify-center rounded-md bg-destructive text-destructive-foreground",
1535
- children: /* @__PURE__ */ jsx10(Square2, {
1708
+ children: /* @__PURE__ */ jsx11(Square2, {
1536
1709
  className: "h-4 w-4",
1537
1710
  "aria-hidden": true
1538
1711
  })
1539
- }) : /* @__PURE__ */ jsx10("button", {
1712
+ }) : /* @__PURE__ */ jsx11("button", {
1540
1713
  type: "button",
1541
1714
  onClick: submit,
1542
1715
  disabled: !canSend,
1543
1716
  "aria-label": "Send",
1544
1717
  className: "flex h-11 w-11 items-center justify-center rounded-md bg-primary text-primary-foreground disabled:opacity-40",
1545
- children: /* @__PURE__ */ jsx10(ArrowUp, {
1718
+ children: /* @__PURE__ */ jsx11(ArrowUp, {
1546
1719
  className: "h-4 w-4",
1547
1720
  "aria-hidden": true
1548
1721
  })
@@ -1567,6 +1740,7 @@ export {
1567
1740
  buildOutcome,
1568
1741
  applyEvent,
1569
1742
  YodexClient,
1743
+ TranscriptRow,
1570
1744
  Transcript,
1571
1745
  ToolCall,
1572
1746
  SessionList,
@@ -1576,8 +1750,9 @@ export {
1576
1750
  PermissionPrompt,
1577
1751
  DiffView,
1578
1752
  Composer,
1579
- AssistantMessage
1753
+ AssistantMessage,
1754
+ ApprovalPrompt
1580
1755
  };
1581
1756
 
1582
- //# debugId=145F4AED7D23344664756E2164756E21
1757
+ //# debugId=C0D62ACAF9016C3D64756E2164756E21
1583
1758
  //# sourceMappingURL=index.js.map