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.
|
|
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-
|
|
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-
|
|
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-
|
|
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/
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
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
|
-
|
|
664
|
-
|
|
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
|
-
*
|
|
678
|
-
*
|
|
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
|
|
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
|
-
|
|
694
|
-
if (!shouldNotify({
|
|
720
|
+
transition({ notification }) {
|
|
721
|
+
if (!notification || !shouldNotify({
|
|
695
722
|
enabled,
|
|
696
723
|
focus
|
|
697
724
|
})) return;
|
|
698
|
-
const { title, message } =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
}, [
|
|
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
|
-
|
|
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
|
-
|
|
5527
|
-
|
|
5528
|
-
|
|
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
|
-
|
|
5892
|
+
agentHost.dispose();
|
|
5619
5893
|
renderer.destroy();
|
|
5620
5894
|
}
|
|
5621
5895
|
});
|
|
5622
|
-
return /* @__PURE__ */
|
|
5623
|
-
|
|
5624
|
-
|
|
5625
|
-
|
|
5626
|
-
|
|
5627
|
-
|
|
5628
|
-
|
|
5629
|
-
|
|
5630
|
-
|
|
5631
|
-
|
|
5632
|
-
|
|
5633
|
-
|
|
5634
|
-
|
|
5635
|
-
|
|
5636
|
-
|
|
5637
|
-
|
|
5638
|
-
|
|
5639
|
-
|
|
5640
|
-
|
|
5641
|
-
|
|
5642
|
-
|
|
5643
|
-
agent: screen.agent,
|
|
5644
|
-
|
|
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(),
|