skydive-cli 0.1.0-beta.45 → 0.1.0-beta.50

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/js/bin.mjs CHANGED
@@ -13,7 +13,7 @@ import fs from "node:fs";
13
13
  import zlib from "node:zlib";
14
14
 
15
15
  //#region package.json
16
- var version$1 = "0.1.0-beta.45";
16
+ var version$1 = "0.1.0-beta.50";
17
17
 
18
18
  //#endregion
19
19
  //#region src/types.ts
@@ -2046,7 +2046,7 @@ const chatCommand = {
2046
2046
  printError(`Unknown theme "${themeId}". Valid themes: ${themes.map((t) => t.id).join(", ")}`);
2047
2047
  process.exit(1);
2048
2048
  }
2049
- const { runChat } = await import("./boot-BQ8xC2R8.mjs");
2049
+ const { runChat } = await import("./boot-SDnaYmi-.mjs");
2050
2050
  await runChat({
2051
2051
  appUrl,
2052
2052
  sessionToken: session.value.sessionToken,
@@ -2064,7 +2064,7 @@ async function runPrintMode({ argv, appUrl }) {
2064
2064
  printError(`${session.error.message} For non-interactive use, run \`skydive auth login --web\` first, or set SKYDIVE_SESSION_TOKEN.`);
2065
2065
  process.exit(1);
2066
2066
  }
2067
- const { runPrint, readStdin } = await import("./print-B76KzChc.mjs").then((n) => n.t);
2067
+ const { runPrint, readStdin } = await import("./print-BtMdNPJR.mjs").then((n) => n.t);
2068
2068
  let prompt = (argv.print ?? "").trim();
2069
2069
  if (!prompt) {
2070
2070
  if (process.stdin.isTTY) {
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { i as createRestClient, n as resolveAgent, r as HttpError } from "./print-B76KzChc.mjs";
2
+ import { i as createRestClient, n as resolveAgent, r as HttpError } from "./print-BtMdNPJR.mjs";
3
3
  import { _ as resolveWebUrl, a as noColorRequested, c as themeMode, d as themesForMode, f as getActiveWorkspaceId, g as getSavedTheme, h as DEFAULT_APP_URL, i as monoTheme, l as themeModeFromColorFgBg, m as DEFAULT_API_URL, n as applyTheme, o as theme, p as listWorkspaces, r as findTheme, s as themeForMode, t as DEFAULT_THEME_ID, u as themeVersion, v as saveTheme } from "./bin.mjs";
4
4
  import path, { basename, isAbsolute, join, win32 } from "node:path";
5
5
  import { z } from "zod";
@@ -7,10 +7,11 @@ import open from "open";
7
7
  import { spawn } from "node:child_process";
8
8
  import { MarkdownRenderable, RenderableEvents, SyntaxStyle, createCliRenderer, decodePasteBytes, detectLinks } from "@opentui/core";
9
9
  import { createRoot, extend, useKeyboard, usePaste, useRenderer, useTerminalDimensions } from "@opentui/react";
10
- import { useCallback, useEffect, useMemo, useRef, useState } from "react";
10
+ import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
11
11
  import { create } from "zustand";
12
12
  import { WebSocket } from "ws";
13
13
  import os, { homedir, platform, release, tmpdir } from "node:os";
14
+ import { createConnection } from "node:net";
14
15
  import { appendFile, mkdir, readFile, rm, stat, writeFile } from "node:fs/promises";
15
16
  import { Fragment, jsx, jsxs } from "@opentui/react/jsx-runtime";
16
17
  import { fileURLToPath } from "node:url";
@@ -65,7 +66,6 @@ const useStore = create((set, get) => ({
65
66
  chatTitle: null,
66
67
  portal: initialPortalUi,
67
68
  portalClient: null,
68
- attention: null,
69
69
  toast: null,
70
70
  goTo: (screen) => set({
71
71
  screen,
@@ -122,7 +122,6 @@ const useStore = create((set, get) => ({
122
122
  ...state.portal,
123
123
  prompt
124
124
  } })),
125
- setAttention: (attention) => set({ attention }),
126
125
  showToast: (input) => set({ toast: createToast(input) }),
127
126
  dismissToast: (id) => set((state) => state.toast?.id === id ? { toast: null } : {})
128
127
  }));
@@ -649,19 +648,49 @@ function usePortal({ appUrl, sessionToken, shareMachine }) {
649
648
  }
650
649
 
651
650
  //#endregion
652
- //#region src/chat/tui/attention.ts
653
- /**
654
- * Whether a notification should fire. We only ping when the terminal is
655
- * blurred — the whole point is to catch the user who has tabbed away. While
656
- * focus is still `unknown` (no focus/blur reported yet, or a terminal that
657
- * doesn't report focus at all) we stay silent rather than interrupt someone
658
- * who is very likely watching.
659
- */
651
+ //#region src/chat/agent-host/context.tsx
652
+ const noopAgentHost = {
653
+ transition() {},
654
+ setSession() {},
655
+ dispose() {}
656
+ };
657
+ const AgentHostContext = createContext(noopAgentHost);
658
+ function useAgentHost() {
659
+ return useContext(AgentHostContext);
660
+ }
661
+
662
+ //#endregion
663
+ //#region src/chat/agent-host/compose.ts
664
+ /** Fans semantic events out to every detected adapter. */
665
+ function composeAgentHost(adapters) {
666
+ let disposed = false;
667
+ return {
668
+ transition(transition) {
669
+ if (disposed) return;
670
+ for (const adapter of adapters) adapter.transition?.(transition);
671
+ },
672
+ setSession(sessionId) {
673
+ if (disposed) return;
674
+ for (const adapter of adapters) adapter.setSession?.(sessionId);
675
+ },
676
+ dispose() {
677
+ if (disposed) return;
678
+ disposed = true;
679
+ for (const adapter of adapters) adapter.dispose?.();
680
+ }
681
+ };
682
+ }
683
+
684
+ //#endregion
685
+ //#region src/chat/agent-host/adapters/terminal-notifications.ts
660
686
  function shouldNotify({ enabled, focus }) {
661
687
  return enabled && focus === "blurred";
662
688
  }
663
- /** The title and body for each attention event. */
664
- function attentionMessage(event, agentName) {
689
+ function notificationMessage({ event, agentName, message }) {
690
+ if (message) return {
691
+ title: agentName,
692
+ message
693
+ };
665
694
  switch (event) {
666
695
  case "run-finished": return {
667
696
  title: agentName,
@@ -674,12 +703,10 @@ function attentionMessage(event, agentName) {
674
703
  }
675
704
  }
676
705
  /**
677
- * Tracks terminal focus and fires a desktop notification when the agent wants
678
- * the user's attention while the terminal is in the background. The renderer's
679
- * focus/blur events are the source of truth for whether the terminal is in the
680
- * foreground.
706
+ * Sends focus-gated desktop notifications through OpenTUI. OpenTUI selects
707
+ * OSC 9/99/777 for the detected terminal; this adapter owns that transport.
681
708
  */
682
- function createAttention({ renderer, enabled }) {
709
+ function createTerminalNotificationsAdapter({ renderer, enabled }) {
683
710
  let focus = "unknown";
684
711
  const onFocus = () => {
685
712
  focus = "focused";
@@ -690,12 +717,12 @@ function createAttention({ renderer, enabled }) {
690
717
  renderer.on("focus", onFocus);
691
718
  renderer.on("blur", onBlur);
692
719
  return {
693
- notify(event, agentName) {
694
- if (!shouldNotify({
720
+ transition({ notification }) {
721
+ if (!notification || !shouldNotify({
695
722
  enabled,
696
723
  focus
697
724
  })) return;
698
- const { title, message } = attentionMessage(event, agentName);
725
+ const { title, message } = notificationMessage(notification);
699
726
  try {
700
727
  renderer.triggerNotification(message, title);
701
728
  } catch (_error) {}
@@ -707,6 +734,181 @@ function createAttention({ renderer, enabled }) {
707
734
  };
708
735
  }
709
736
 
737
+ //#endregion
738
+ //#region src/chat/agent-host/adapters/herdr/adapter.ts
739
+ /** Maps one complete semantic transition to one herdr state report. */
740
+ function createHerdrAdapter(herdr) {
741
+ let lastState;
742
+ return {
743
+ transition({ state, notification }) {
744
+ if (state === lastState && !notification) return;
745
+ lastState = state;
746
+ const message = state === "idle" && notification?.event === "run-finished" ? notification.message : void 0;
747
+ herdr.report(state, message);
748
+ },
749
+ setSession: herdr.setConversation,
750
+ dispose: herdr.dispose
751
+ };
752
+ }
753
+
754
+ //#endregion
755
+ //#region src/chat/agent-host/adapters/herdr/client.ts
756
+ /** How we identify ourselves to herdr. The agent label is what herdr shows
757
+ * in its sidebar/agents pane; unknown labels pass through verbatim (herdr
758
+ * normalizes only its built-in agents), so no herdr-side support is needed. */
759
+ const SOURCE = "skydive-cli";
760
+ const AGENT_LABEL = "skydive";
761
+ /** Cap on how long a report may hold a socket open. herdr answers in
762
+ * microseconds when healthy; past this we drop the report on the floor. */
763
+ const REQUEST_TIMEOUT_MS = 500;
764
+ /**
765
+ * The herdr environment, if this process is running inside a herdr pane.
766
+ * All three variables are injected by herdr into every pane it spawns;
767
+ * anything missing means "not in herdr" and reporting stays off.
768
+ */
769
+ function detectHerdrEnv(env = process.env) {
770
+ if (env.HERDR_ENV !== "1") return null;
771
+ const socketPath = env.HERDR_SOCKET_PATH;
772
+ const paneId = env.HERDR_PANE_ID;
773
+ if (!socketPath || !paneId) return null;
774
+ return {
775
+ socketPath,
776
+ paneId
777
+ };
778
+ }
779
+ function buildStateReport({ paneId, state, message, seq, conversationId }) {
780
+ return {
781
+ id: requestId(),
782
+ method: "pane.report_agent",
783
+ params: {
784
+ pane_id: paneId,
785
+ source: SOURCE,
786
+ agent: AGENT_LABEL,
787
+ state,
788
+ ...message ? { message } : {},
789
+ seq,
790
+ ...conversationId ? { agent_session_id: conversationId } : {}
791
+ }
792
+ };
793
+ }
794
+ function buildSessionReport({ paneId, seq, conversationId }) {
795
+ return {
796
+ id: requestId(),
797
+ method: "pane.report_agent_session",
798
+ params: {
799
+ pane_id: paneId,
800
+ source: SOURCE,
801
+ agent: AGENT_LABEL,
802
+ seq,
803
+ agent_session_id: conversationId
804
+ }
805
+ };
806
+ }
807
+ function buildRelease({ paneId, seq }) {
808
+ return {
809
+ id: requestId(),
810
+ method: "pane.release_agent",
811
+ params: {
812
+ pane_id: paneId,
813
+ source: SOURCE,
814
+ agent: AGENT_LABEL,
815
+ seq
816
+ }
817
+ };
818
+ }
819
+ let requestCounter = 0;
820
+ function requestId() {
821
+ requestCounter += 1;
822
+ return `${SOURCE}:${Date.now()}:${requestCounter}`;
823
+ }
824
+ const socketSend = (socketPath, request) => {
825
+ try {
826
+ const socket = createConnection(socketPath);
827
+ socket.unref();
828
+ let done = false;
829
+ const finish = () => {
830
+ if (done) return;
831
+ done = true;
832
+ clearTimeout(timeout);
833
+ socket.destroy();
834
+ };
835
+ socket.on("error", finish);
836
+ socket.on("data", finish);
837
+ socket.on("end", finish);
838
+ socket.on("connect", () => {
839
+ socket.write(`${JSON.stringify(request)}\n`);
840
+ });
841
+ const timeout = setTimeout(finish, REQUEST_TIMEOUT_MS);
842
+ timeout.unref?.();
843
+ } catch (_error) {}
844
+ };
845
+ /**
846
+ * The reporter, or null when not running inside herdr (the common case —
847
+ * callers hold `HerdrReporter | null` and optional-chain every call).
848
+ */
849
+ function createHerdrReporter({ env, send = socketSend } = { env: detectHerdrEnv() }) {
850
+ if (!env) return null;
851
+ const { socketPath, paneId } = env;
852
+ let seq = Date.now() * 1e3;
853
+ const nextSeq = () => {
854
+ seq += 1;
855
+ return seq;
856
+ };
857
+ let lastState;
858
+ let lastMessage;
859
+ let conversationId = null;
860
+ let disposed = false;
861
+ return {
862
+ report(state, message) {
863
+ if (disposed || state === lastState && message === lastMessage) return;
864
+ lastState = state;
865
+ lastMessage = message;
866
+ send(socketPath, buildStateReport({
867
+ paneId,
868
+ state,
869
+ message,
870
+ seq: nextSeq(),
871
+ conversationId
872
+ }));
873
+ },
874
+ setConversation(id) {
875
+ if (disposed || id === conversationId) return;
876
+ conversationId = id;
877
+ if (!id) return;
878
+ send(socketPath, buildSessionReport({
879
+ paneId,
880
+ seq: nextSeq(),
881
+ conversationId: id
882
+ }));
883
+ },
884
+ dispose() {
885
+ if (disposed) return;
886
+ disposed = true;
887
+ send(socketPath, buildRelease({
888
+ paneId,
889
+ seq: nextSeq()
890
+ }));
891
+ }
892
+ };
893
+ }
894
+
895
+ //#endregion
896
+ //#region src/chat/agent-host/create.ts
897
+ /**
898
+ * The sole integration assembly point. Adding a host protocol means adding an
899
+ * adapter and registering it here; chat lifecycle and generic host code stay
900
+ * unchanged.
901
+ */
902
+ function createAgentHost({ renderer, notifications }) {
903
+ const adapters = [createTerminalNotificationsAdapter({
904
+ renderer,
905
+ enabled: notifications
906
+ })];
907
+ const herdr = createHerdrReporter();
908
+ if (herdr) adapters.push(createHerdrAdapter(herdr));
909
+ return composeAgentHost(adapters);
910
+ }
911
+
710
912
  //#endregion
711
913
  //#region src/chat/clipboard.ts
712
914
  /** Spawns a command with no stdin and collects its stdout. Resolves (never
@@ -2797,6 +2999,17 @@ function RenderItem({ item }) {
2797
2999
  barColor: null,
2798
3000
  children: /* @__PURE__ */ jsx(CardItem, { item })
2799
3001
  });
3002
+ case "recap": return /* @__PURE__ */ jsx(Row, {
3003
+ barColor: null,
3004
+ children: /* @__PURE__ */ jsxs("text", {
3005
+ fg: theme.dim,
3006
+ children: [
3007
+ /* @__PURE__ */ jsx("b", { children: "recap" }),
3008
+ " ",
3009
+ /* @__PURE__ */ jsx("i", { children: item.text })
3010
+ ]
3011
+ })
3012
+ });
2800
3013
  }
2801
3014
  }
2802
3015
  function StreamingAssistantText({ text }) {
@@ -3409,6 +3622,36 @@ function reconcileMaskedInput(previousValue, displayed) {
3409
3622
  return previousValue.slice(0, kept) + displayed.slice(kept);
3410
3623
  }
3411
3624
 
3625
+ //#endregion
3626
+ //#region src/chat/notification-preview.ts
3627
+ const MAX_NOTIFICATION_PREVIEW_CHARS = 240;
3628
+ /**
3629
+ * Collects assistant `text-delta` chunks for one run and turns them into a
3630
+ * desktop-notification-sized preview. Reasoning, tool output, cards, errors,
3631
+ * and old transcript items are intentionally excluded.
3632
+ */
3633
+ function createResponsePreview() {
3634
+ let text = "";
3635
+ let lastPartId;
3636
+ return {
3637
+ addChunk(chunk) {
3638
+ if (chunk.type !== "text-delta") return;
3639
+ const delta = typeof chunk.delta === "string" ? chunk.delta : typeof chunk.text === "string" ? chunk.text : "";
3640
+ const partId = typeof chunk.id === "string" ? chunk.id : void 0;
3641
+ if (text && partId && lastPartId && partId !== lastPartId) text += " ";
3642
+ text += delta;
3643
+ lastPartId = partId ?? lastPartId;
3644
+ },
3645
+ get() {
3646
+ const normalized = text.replace(/\s+/g, " ").trim();
3647
+ if (!normalized) return void 0;
3648
+ const characters = Array.from(normalized);
3649
+ if (characters.length <= MAX_NOTIFICATION_PREVIEW_CHARS) return normalized;
3650
+ return `${characters.slice(0, MAX_NOTIFICATION_PREVIEW_CHARS - 1).join("")}…`;
3651
+ }
3652
+ };
3653
+ }
3654
+
3412
3655
  //#endregion
3413
3656
  //#region src/chat/tui/spinner-frames.ts
3414
3657
  /** Braille dot-cycle frames — the classic smooth terminal spinner. */
@@ -4486,6 +4729,7 @@ function ChatScreen({ agent, conversation }) {
4486
4729
  const grantPrompt = portal.prompt && portal.prompt.agentId === agent.id ? portal.prompt : null;
4487
4730
  const isShared = portal.status !== "off";
4488
4731
  const initialConversationId = isNewConversation(conversation) ? null : conversation.id;
4732
+ const agentHost = useAgentHost();
4489
4733
  const [conversationId, setConversationId] = useState(initialConversationId);
4490
4734
  const [items, setItems] = useState([]);
4491
4735
  const [historyLoaded, setHistoryLoaded] = useState(initialConversationId === null);
@@ -4509,6 +4753,17 @@ function ChatScreen({ agent, conversation }) {
4509
4753
  inputRef.current = input;
4510
4754
  const credPromptRef = useRef(credPrompt);
4511
4755
  credPromptRef.current = credPrompt;
4756
+ const hostState = grantPrompt || credPrompt ? "blocked" : run.kind === "idle" ? "idle" : "working";
4757
+ useEffect(() => {
4758
+ agentHost.transition({ state: hostState });
4759
+ }, [agentHost, hostState]);
4760
+ useEffect(() => () => {
4761
+ agentHost.transition({ state: "idle" });
4762
+ agentHost.setSession(null);
4763
+ }, [agentHost]);
4764
+ useEffect(() => {
4765
+ agentHost.setSession(conversationId);
4766
+ }, [agentHost, conversationId]);
4512
4767
  const scrollRef = useRef(null);
4513
4768
  const composerRef = useRef(null);
4514
4769
  const composerBoxHeight = composerRows + 2;
@@ -4542,9 +4797,15 @@ function ChatScreen({ agent, conversation }) {
4542
4797
  let cancelled = false;
4543
4798
  (async () => {
4544
4799
  try {
4545
- const messages = await rest.listMessages({ conversationId: initialConversationId });
4800
+ const [messages, recapText] = await Promise.all([rest.listMessages({ conversationId: initialConversationId }), rest.getRecap({ conversationId: initialConversationId }).catch(() => null)]);
4546
4801
  if (cancelled) return;
4547
- setItems(uiMessagesToItems(messages));
4802
+ const loaded = uiMessagesToItems(messages);
4803
+ if (recapText && loaded.length > 0) loaded.push({
4804
+ kind: "recap",
4805
+ id: `recap-${initialConversationId}`,
4806
+ text: recapText
4807
+ });
4808
+ setItems(loaded);
4548
4809
  } catch (err) {
4549
4810
  if (cancelled) return;
4550
4811
  setItems((prev) => [...prev, {
@@ -4563,6 +4824,7 @@ function ChatScreen({ agent, conversation }) {
4563
4824
  const attachToRun = useCallback((runId) => {
4564
4825
  if (!rest) return;
4565
4826
  const abort = new AbortController();
4827
+ const responsePreview = createResponsePreview();
4566
4828
  setRun({
4567
4829
  kind: "streaming",
4568
4830
  runId,
@@ -4573,6 +4835,7 @@ function ChatScreen({ agent, conversation }) {
4573
4835
  signal: abort.signal,
4574
4836
  onEvent: (event) => {
4575
4837
  if (event.kind === "chunk") {
4838
+ responsePreview.addChunk(event.chunk);
4576
4839
  setItems((prev) => applyChunk(prev, event.chunk));
4577
4840
  return;
4578
4841
  }
@@ -4588,7 +4851,14 @@ function ChatScreen({ agent, conversation }) {
4588
4851
  text: m.text
4589
4852
  } : m));
4590
4853
  setRun({ kind: "idle" });
4591
- useStore.getState().attention?.notify("run-finished", agent.name);
4854
+ agentHost.transition({
4855
+ state: "idle",
4856
+ notification: {
4857
+ event: "run-finished",
4858
+ agentName: agent.name,
4859
+ message: responsePreview.get()
4860
+ }
4861
+ });
4592
4862
  }
4593
4863
  }).catch((err) => {
4594
4864
  if (abort.signal.aborted) return;
@@ -4599,7 +4869,11 @@ function ChatScreen({ agent, conversation }) {
4599
4869
  }]);
4600
4870
  setRun({ kind: "idle" });
4601
4871
  });
4602
- }, [rest, agent.name]);
4872
+ }, [
4873
+ rest,
4874
+ agent.name,
4875
+ agentHost
4876
+ ]);
4603
4877
  useEffect(() => {
4604
4878
  if (!rest || !conversationId) return;
4605
4879
  if (initialConversationId !== null) return;
@@ -5062,7 +5336,13 @@ function ChatScreen({ agent, conversation }) {
5062
5336
  agentId: agent.id,
5063
5337
  agentName: agent.name
5064
5338
  });
5065
- useStore.getState().attention?.notify("needs-input", agent.name);
5339
+ agentHost.transition({
5340
+ state: "blocked",
5341
+ notification: {
5342
+ event: "needs-input",
5343
+ agentName: agent.name
5344
+ }
5345
+ });
5066
5346
  }, [
5067
5347
  portalClient,
5068
5348
  portal.status,
@@ -5070,7 +5350,8 @@ function ChatScreen({ agent, conversation }) {
5070
5350
  portal.prompt,
5071
5351
  agent.id,
5072
5352
  agent.name,
5073
- setPortalPrompt
5353
+ setPortalPrompt,
5354
+ agentHost
5074
5355
  ]);
5075
5356
  useKeyboard((key) => {
5076
5357
  if (modelPickerOpen || themePickerOpen || helpOpen) return;
@@ -5169,6 +5450,7 @@ function ChatScreen({ agent, conversation }) {
5169
5450
  }
5170
5451
  if (ctrlCArmed) {
5171
5452
  portalClient?.dispose();
5453
+ agentHost.dispose();
5172
5454
  renderer.destroy();
5173
5455
  return;
5174
5456
  }
@@ -5515,28 +5797,20 @@ function App({ appUrl, sessionToken, shareMachine, promptHistoryPath, notificati
5515
5797
  const rest = useStore((s) => s.rest);
5516
5798
  const goTo = useStore((s) => s.goTo);
5517
5799
  const setClients = useStore((s) => s.setClients);
5518
- const setAttention = useStore((s) => s.setAttention);
5519
5800
  const showToast = useStore((s) => s.showToast);
5801
+ const [agentHost] = useState(() => createAgentHost({
5802
+ renderer,
5803
+ notifications
5804
+ }));
5520
5805
  usePortal({
5521
5806
  appUrl,
5522
5807
  sessionToken,
5523
5808
  shareMachine
5524
5809
  });
5525
5810
  useEffect(() => {
5526
- const attention = createAttention({
5527
- renderer,
5528
- enabled: notifications
5529
- });
5530
- setAttention(attention);
5531
- return () => {
5532
- attention.dispose();
5533
- setAttention(null);
5534
- };
5535
- }, [
5536
- renderer,
5537
- notifications,
5538
- setAttention
5539
- ]);
5811
+ agentHost.transition({ state: "idle" });
5812
+ return () => agentHost.dispose();
5813
+ }, [agentHost]);
5540
5814
  useEffect(() => {
5541
5815
  const selectionCopy = createSelectionCopy({
5542
5816
  renderer,
@@ -5615,37 +5889,40 @@ function App({ appUrl, sessionToken, shareMachine, promptHistoryPath, notificati
5615
5889
  useKeyboard((key) => {
5616
5890
  if (key.name === "c" && key.ctrl && screen.kind !== "chat") {
5617
5891
  useStore.getState().portalClient?.dispose();
5618
- useStore.getState().attention?.dispose();
5892
+ agentHost.dispose();
5619
5893
  renderer.destroy();
5620
5894
  }
5621
5895
  });
5622
- return /* @__PURE__ */ jsxs("box", {
5623
- style: {
5624
- flexDirection: "column",
5625
- flexGrow: 1,
5626
- backgroundColor: theme.background
5627
- },
5628
- children: [
5629
- /* @__PURE__ */ jsx(StatusBar, {}),
5630
- /* @__PURE__ */ jsx(ToastView, {}),
5631
- /* @__PURE__ */ jsxs("box", {
5632
- style: {
5633
- flexGrow: 1,
5634
- padding: 1
5635
- },
5636
- children: [
5637
- screen.kind === "splash" && /* @__PURE__ */ jsx(Splash, {}),
5638
- screen.kind === "error" && /* @__PURE__ */ jsx(ErrorScreen, { message: screen.message }),
5639
- screen.kind === "agent-picker" && rest && /* @__PURE__ */ jsx(AgentPickerScreen, {}),
5640
- screen.kind === "agent-create" && rest && /* @__PURE__ */ jsx(AgentCreateScreen, {}),
5641
- screen.kind === "conversation-picker" && rest && /* @__PURE__ */ jsx(ConversationPickerScreen, { agent: screen.agent }),
5642
- screen.kind === "chat" && rest && /* @__PURE__ */ jsx(ChatScreen, {
5643
- agent: screen.agent,
5644
- conversation: screen.conversation
5645
- })
5646
- ]
5647
- })
5648
- ]
5896
+ return /* @__PURE__ */ jsx(AgentHostContext.Provider, {
5897
+ value: agentHost,
5898
+ children: /* @__PURE__ */ jsxs("box", {
5899
+ style: {
5900
+ flexDirection: "column",
5901
+ flexGrow: 1,
5902
+ backgroundColor: theme.background
5903
+ },
5904
+ children: [
5905
+ /* @__PURE__ */ jsx(StatusBar, {}),
5906
+ /* @__PURE__ */ jsx(ToastView, {}),
5907
+ /* @__PURE__ */ jsxs("box", {
5908
+ style: {
5909
+ flexGrow: 1,
5910
+ padding: 1
5911
+ },
5912
+ children: [
5913
+ screen.kind === "splash" && /* @__PURE__ */ jsx(Splash, {}),
5914
+ screen.kind === "error" && /* @__PURE__ */ jsx(ErrorScreen, { message: screen.message }),
5915
+ screen.kind === "agent-picker" && rest && /* @__PURE__ */ jsx(AgentPickerScreen, {}),
5916
+ screen.kind === "agent-create" && rest && /* @__PURE__ */ jsx(AgentCreateScreen, {}),
5917
+ screen.kind === "conversation-picker" && rest && /* @__PURE__ */ jsx(ConversationPickerScreen, { agent: screen.agent }),
5918
+ screen.kind === "chat" && rest && /* @__PURE__ */ jsx(ChatScreen, {
5919
+ agent: screen.agent,
5920
+ conversation: screen.conversation
5921
+ })
5922
+ ]
5923
+ })
5924
+ ]
5925
+ })
5649
5926
  });
5650
5927
  }
5651
5928
  function Splash() {
@@ -107,6 +107,10 @@ function createRestClient({ appUrl, sessionToken }) {
107
107
  const { messages } = await get(`/api/v1/conversations/${encodeURIComponent(conversationId)}/messages`, listMessagesResponseSchema);
108
108
  return messages;
109
109
  },
110
+ getRecap: async ({ conversationId }) => {
111
+ const { recap } = await get(`/api/v1/conversations/${encodeURIComponent(conversationId)}/recap`, recapResponseSchema);
112
+ return recap?.text ?? null;
113
+ },
110
114
  uploadAttachment: async ({ agentId, fileName, mediaType, data }) => {
111
115
  const size = data.byteLength;
112
116
  const presign = await post("/api/v1/attachments/presign", {
@@ -311,6 +315,7 @@ const uiMessageSchema = z.object({
311
315
  role: z.string(),
312
316
  parts: z.array(uiMessagePartSchema)
313
317
  });
318
+ const recapResponseSchema = z.object({ recap: z.object({ text: z.string() }).nullable() });
314
319
  const listMessagesResponseSchema = z.object({ messages: z.array(uiMessageSchema) });
315
320
  const sendResultSchema = z.object({
316
321
  runId: z.string(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skydive-cli",
3
- "version": "0.1.0-beta.45",
3
+ "version": "0.1.0-beta.50",
4
4
  "description": "Skydive CLI — manage AI agents from the command line",
5
5
  "homepage": "https://skydive.com",
6
6
  "license": "MIT",