@deepagents/devtool-history 7.0.0

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.
@@ -0,0 +1,67 @@
1
+ import { type ReactNode } from 'react';
2
+ import type { ChildProgress, ConversationStatus } from '@deepagents/experimental/zukhruf';
3
+ import type { OwnerEvent } from '@deepagents/experimental/zukhruf/http';
4
+ export type { ConversationStatus } from '@deepagents/experimental/zukhruf';
5
+ export type HistoryRecord = {
6
+ chatId: string;
7
+ userId: string;
8
+ title?: string;
9
+ createdAt: number;
10
+ updatedAt: number;
11
+ messageCount: number;
12
+ status: ConversationStatus;
13
+ children?: readonly ChildProgress[];
14
+ };
15
+ export type ConversationEvent = {
16
+ type: 'change';
17
+ resource: 'conversation';
18
+ id: string;
19
+ status: ConversationStatus;
20
+ child?: ChildProgress;
21
+ };
22
+ export type RuntimeEvent = OwnerEvent;
23
+ export declare function conversationStatusLabel(status: ConversationStatus): "idle" | "active" | "error" | "approval" | "input";
24
+ export declare function RuntimeEventsProvider({ href, onEvent, history, children, }: {
25
+ href: string | undefined;
26
+ onEvent: (event: RuntimeEvent) => void;
27
+ history?: readonly HistoryRecord[];
28
+ children: ReactNode;
29
+ }): import("react").JSX.Element;
30
+ export declare function useConversationStatus(chatId: string, snapshot: ConversationStatus): ConversationStatus;
31
+ /** One row per child in the selected project, refreshed by the owner stream. */
32
+ export declare function ChildProgressList({ treeId, snapshot, }: {
33
+ treeId: string;
34
+ snapshot?: readonly ChildProgress[];
35
+ }): import("react").JSX.Element | null;
36
+ export declare function isConversationEvent(event: RuntimeEvent): event is ConversationEvent;
37
+ declare function HistoryRoot({ activeChatId, onSelect, className, children, }: {
38
+ activeChatId: string | undefined;
39
+ onSelect: (history: HistoryRecord) => void;
40
+ className?: string;
41
+ children: ReactNode;
42
+ }): import("react").JSX.Element;
43
+ declare function HistoryItem({ className, children, }: {
44
+ className?: string;
45
+ children: ReactNode;
46
+ }): import("react").JSX.Element;
47
+ declare function HistoryItemTrigger({ history, className, children, }: {
48
+ history: HistoryRecord;
49
+ className?: string;
50
+ children: ReactNode;
51
+ }): import("react").JSX.Element;
52
+ declare function HistoryEmpty({ className, children, }: {
53
+ className?: string;
54
+ children: ReactNode;
55
+ }): import("react").JSX.Element;
56
+ export declare function ChatStatusIcon({ chatId, status, }: Pick<HistoryRecord, 'chatId' | 'status'>): import("react").JSX.Element;
57
+ export declare function StatusBadge({ status }: {
58
+ status: string;
59
+ }): import("react").JSX.Element;
60
+ export declare function formatTimestamp(value: number | string): string;
61
+ export declare const History: {
62
+ readonly Root: typeof HistoryRoot;
63
+ readonly Item: typeof HistoryItem;
64
+ readonly ItemTrigger: typeof HistoryItemTrigger;
65
+ readonly Empty: typeof HistoryEmpty;
66
+ };
67
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":"AAOA,OAAO,EACL,KAAK,SAAS,EAOf,MAAM,OAAO,CAAC;AAEf,OAAO,KAAK,EAEV,aAAa,EACb,kBAAkB,EACnB,MAAM,kCAAkC,CAAC;AAC1C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uCAAuC,CAAC;AAGxE,YAAY,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAC;AAE3E,MAAM,MAAM,aAAa,GAAG;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,kBAAkB,CAAC;IAC3B,QAAQ,CAAC,EAAE,SAAS,aAAa,EAAE,CAAC;CACrC,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,EAAE,QAAQ,CAAC;IACf,QAAQ,EAAE,cAAc,CAAC;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,kBAAkB,CAAC;IAC3B,KAAK,CAAC,EAAE,aAAa,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG,UAAU,CAAC;AAEtC,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,kBAAkB,sDAWjE;AAOD,wBAAgB,qBAAqB,CAAC,EACpC,IAAI,EACJ,OAAO,EACP,OAAY,EACZ,QAAQ,GACT,EAAE;IACD,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,OAAO,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAC;IACvC,OAAO,CAAC,EAAE,SAAS,aAAa,EAAE,CAAC;IACnC,QAAQ,EAAE,SAAS,CAAC;CACrB,+BA2BA;AAED,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,kBAAkB,GAC3B,kBAAkB,CAQpB;AAqBD,gFAAgF;AAChF,wBAAgB,iBAAiB,CAAC,EAChC,MAAM,EACN,QAAa,GACd,EAAE;IACD,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,SAAS,aAAa,EAAE,CAAC;CACrC,sCA6CA;AAED,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,YAAY,GAClB,KAAK,IAAI,iBAAiB,CAQ5B;AA0FD,iBAAS,WAAW,CAAC,EACnB,YAAY,EACZ,QAAQ,EACR,SAAS,EACT,QAAQ,GACT,EAAE;IACD,YAAY,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,QAAQ,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,IAAI,CAAC;IAC3C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,SAAS,CAAC;CACrB,+BAYA;AAED,iBAAS,WAAW,CAAC,EACnB,SAAS,EACT,QAAQ,GACT,EAAE;IACD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,SAAS,CAAC;CACrB,+BAMA;AAED,iBAAS,kBAAkB,CAAC,EAC1B,OAAO,EACP,SAAS,EACT,QAAQ,GACT,EAAE;IACD,OAAO,EAAE,aAAa,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,SAAS,CAAC;CACrB,+BAmBA;AAED,iBAAS,YAAY,CAAC,EACpB,SAAS,EACT,QAAQ,GACT,EAAE;IACD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,SAAS,CAAC;CACrB,+BAMA;AAED,wBAAgB,cAAc,CAAC,EAC7B,MAAM,EACN,MAAM,GACP,EAAE,IAAI,CAAC,aAAa,EAAE,QAAQ,GAAG,QAAQ,CAAC,+BA8B1C;AAED,wBAAgB,WAAW,CAAC,EAAE,MAAM,EAAE,EAAE;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,+BAYzD;AAOD,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,UAErD;AAED,eAAO,MAAM,OAAO;;;;;CAKV,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,302 @@
1
+ // packages/devtool/history/src/index.tsx
2
+ import {
3
+ CircleAlertIcon,
4
+ CircleDotIcon,
5
+ CirclePauseIcon,
6
+ MessageCircleQuestionIcon,
7
+ MessageSquareIcon
8
+ } from "lucide-react";
9
+ import {
10
+ createContext,
11
+ use,
12
+ useEffect,
13
+ useEffectEvent,
14
+ useMemo,
15
+ useState
16
+ } from "react";
17
+ import { cn } from "@deepagents/react-shadcn";
18
+ import { jsx, jsxs } from "react/jsx-runtime";
19
+ function conversationStatusLabel(status) {
20
+ switch (status.type) {
21
+ case "idle":
22
+ return "idle";
23
+ case "systemError":
24
+ return "error";
25
+ case "active":
26
+ if (status.activeFlags.includes("waitingOnApproval")) return "approval";
27
+ if (status.activeFlags.includes("waitingOnUserInput")) return "input";
28
+ return "active";
29
+ }
30
+ }
31
+ var ConversationStatusContext = createContext(null);
32
+ var RuntimeHistoryContext = createContext([]);
33
+ function RuntimeEventsProvider({
34
+ href,
35
+ onEvent,
36
+ history = [],
37
+ children
38
+ }) {
39
+ const [statuses, setStatuses] = useState(/* @__PURE__ */ new Map());
40
+ const notify = useEffectEvent(onEvent);
41
+ useEffect(() => {
42
+ if (!href) return;
43
+ const source = new EventSource(href);
44
+ const handleEvent = (message) => {
45
+ const event = parseRuntimeEvent(message.data);
46
+ if (!event) return;
47
+ if (event.type === "ready") {
48
+ setStatuses(/* @__PURE__ */ new Map());
49
+ } else if (isConversationEvent(event)) {
50
+ setStatuses((previous) => new Map(previous).set(event.id, event));
51
+ }
52
+ notify(event);
53
+ };
54
+ source.addEventListener("message", handleEvent);
55
+ return () => {
56
+ source.removeEventListener("message", handleEvent);
57
+ source.close();
58
+ };
59
+ }, [href]);
60
+ return /* @__PURE__ */ jsx(ConversationStatusContext, { value: statuses, children: /* @__PURE__ */ jsx(RuntimeHistoryContext, { value: history, children }) });
61
+ }
62
+ function useConversationStatus(chatId, snapshot) {
63
+ const statuses = use(ConversationStatusContext);
64
+ if (!statuses) {
65
+ throw new Error(
66
+ "useConversationStatus must be used inside <RuntimeEventsProvider>."
67
+ );
68
+ }
69
+ return statuses.get(chatId)?.status ?? snapshot;
70
+ }
71
+ var childStateLabels = {
72
+ pending: "Pending",
73
+ queued: "Queued",
74
+ running: "Running",
75
+ waitingOnApproval: "Waiting on approval",
76
+ waitingOnUserInput: "Waiting on user input",
77
+ completed: "Completed",
78
+ failed: "Failed",
79
+ interrupted: "Interrupted"
80
+ };
81
+ var activityLabels = {
82
+ spawn: "Spawned",
83
+ message: "Message sent",
84
+ followup: "Follow-up sent",
85
+ interrupt: "Interrupt accepted",
86
+ completion: "Turn finished"
87
+ };
88
+ function ChildProgressList({
89
+ treeId,
90
+ snapshot = []
91
+ }) {
92
+ const events = use(ConversationStatusContext);
93
+ const history = use(RuntimeHistoryContext);
94
+ const saved = history.find((record) => record.chatId === treeId)?.children ?? snapshot;
95
+ const children = new Map(
96
+ saved.filter((child) => child.treeId === treeId).map((child) => [child.chatId, child])
97
+ );
98
+ for (const event of events?.values() ?? []) {
99
+ if (event.child?.treeId === treeId) children.set(event.id, event.child);
100
+ }
101
+ if (children.size === 0) return null;
102
+ return /* @__PURE__ */ jsxs("section", { "aria-label": "Child agents", className: "border-t px-6 py-3", children: [
103
+ /* @__PURE__ */ jsx("h2", { className: "mb-2 text-sm font-medium", children: "Child agents" }),
104
+ /* @__PURE__ */ jsx("ul", { className: "space-y-2", children: [...children.values()].sort((a, b) => a.path.localeCompare(b.path)).map((child) => {
105
+ const latest = Object.values(child.activities).sort(
106
+ (a, b) => b.at - a.at
107
+ )[0];
108
+ return /* @__PURE__ */ jsxs(
109
+ "li",
110
+ {
111
+ className: "flex items-center justify-between gap-4 text-sm",
112
+ children: [
113
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
114
+ /* @__PURE__ */ jsx("p", { className: "truncate font-mono text-xs", children: child.path }),
115
+ latest && /* @__PURE__ */ jsxs("p", { className: "text-muted-foreground text-xs", children: [
116
+ activityLabels[latest.type],
117
+ " \xB7 ",
118
+ latest.actorPath,
119
+ " \u2192",
120
+ " ",
121
+ latest.targetPath
122
+ ] })
123
+ ] }),
124
+ /* @__PURE__ */ jsx(StatusBadge, { status: childStateLabels[child.state] })
125
+ ]
126
+ },
127
+ child.chatId
128
+ );
129
+ }) })
130
+ ] });
131
+ }
132
+ function isConversationEvent(event) {
133
+ return event.type === "change" && event.resource === "conversation" && isConversationStatus(event.status) && (event.child === void 0 || isChildProgress(event.child) && event.child.chatId === event.id);
134
+ }
135
+ function parseRuntimeEvent(data) {
136
+ let event;
137
+ try {
138
+ event = JSON.parse(data);
139
+ } catch {
140
+ return void 0;
141
+ }
142
+ if (!isRecord(event) || typeof event.type !== "string") return void 0;
143
+ if (event.type === "ready") return { type: "ready" };
144
+ if (event.type !== "change" || typeof event.resource !== "string" || !event.resource || typeof event.id !== "string" || !event.id) {
145
+ return void 0;
146
+ }
147
+ if (event.resource === "conversation" && (!isConversationStatus(event.status) || event.child !== void 0 && (!isChildProgress(event.child) || event.child.chatId !== event.id))) {
148
+ return void 0;
149
+ }
150
+ return event;
151
+ }
152
+ function isConversationStatus(value) {
153
+ if (!isRecord(value) || typeof value.type !== "string") return false;
154
+ if (value.type === "idle" || value.type === "systemError") return true;
155
+ return value.type === "active" && Array.isArray(value.activeFlags) && value.activeFlags.every(
156
+ (flag) => flag === "waitingOnApproval" || flag === "waitingOnUserInput"
157
+ );
158
+ }
159
+ function isRecord(value) {
160
+ return typeof value === "object" && value !== null && !Array.isArray(value);
161
+ }
162
+ function isChildProgress(value) {
163
+ if (!isRecord(value) || !isRecord(value.activities) || !["chatId", "treeId", "path", "parentChatId", "declarationName"].every(
164
+ (key) => typeof value[key] === "string"
165
+ ) || typeof value.state !== "string" || !Object.hasOwn(childStateLabels, value.state))
166
+ return false;
167
+ return Object.entries(value.activities).every(
168
+ ([kind, activity]) => Object.hasOwn(activityLabels, kind) && isRecord(activity) && activity.type === kind && typeof activity.at === "number" && Number.isFinite(activity.at) && (activity.outcome === void 0 || ["completed", "failed", "cancelled"].includes(
169
+ activity.outcome
170
+ )) && ["id", "actorPath", "targetPath", "streamId"].every(
171
+ (key) => typeof activity[key] === "string"
172
+ )
173
+ );
174
+ }
175
+ var HistoryContext = createContext(null);
176
+ function useHistoryContext() {
177
+ const context = use(HistoryContext);
178
+ if (!context) {
179
+ throw new Error("History.* parts must be rendered inside <History.Root>.");
180
+ }
181
+ return context;
182
+ }
183
+ function HistoryRoot({
184
+ activeChatId,
185
+ onSelect,
186
+ className,
187
+ children
188
+ }) {
189
+ const value = useMemo(
190
+ () => ({ activeChatId, onSelect }),
191
+ [activeChatId, onSelect]
192
+ );
193
+ return /* @__PURE__ */ jsx(HistoryContext.Provider, { value, children: /* @__PURE__ */ jsx("div", { className: cn("flex min-h-0 flex-col gap-1", className), children }) });
194
+ }
195
+ function HistoryItem({
196
+ className,
197
+ children
198
+ }) {
199
+ return /* @__PURE__ */ jsx("div", { className: cn("group/history-item relative", className), children });
200
+ }
201
+ function HistoryItemTrigger({
202
+ history,
203
+ className,
204
+ children
205
+ }) {
206
+ const { activeChatId, onSelect } = useHistoryContext();
207
+ const isActive = activeChatId === history.chatId;
208
+ return /* @__PURE__ */ jsx(
209
+ "button",
210
+ {
211
+ type: "button",
212
+ "aria-current": isActive ? "true" : void 0,
213
+ onClick: () => onSelect(history),
214
+ className: cn(
215
+ "flex w-full items-center gap-2 rounded-lg px-2 py-1.5 text-left text-sm transition-colors",
216
+ isActive ? "bg-accent text-foreground" : "text-muted-foreground hover:bg-accent hover:text-foreground",
217
+ className
218
+ ),
219
+ children
220
+ }
221
+ );
222
+ }
223
+ function HistoryEmpty({
224
+ className,
225
+ children
226
+ }) {
227
+ return /* @__PURE__ */ jsx("div", { className: cn("text-muted-foreground px-2 py-4 text-sm", className), children });
228
+ }
229
+ function ChatStatusIcon({
230
+ chatId,
231
+ status
232
+ }) {
233
+ const className = "size-3.5 shrink-0";
234
+ const label = conversationStatusLabel(useConversationStatus(chatId, status));
235
+ switch (label) {
236
+ case "active":
237
+ return /* @__PURE__ */ jsx(CircleDotIcon, { "aria-label": "Active", className });
238
+ case "error":
239
+ return /* @__PURE__ */ jsx(
240
+ CircleAlertIcon,
241
+ {
242
+ "aria-label": "Error",
243
+ className: cn(className, "text-destructive")
244
+ }
245
+ );
246
+ case "idle":
247
+ return /* @__PURE__ */ jsx(MessageSquareIcon, { "aria-label": "idle", className });
248
+ case "approval":
249
+ return /* @__PURE__ */ jsx(
250
+ CirclePauseIcon,
251
+ {
252
+ "aria-label": "Waiting on approval",
253
+ className
254
+ }
255
+ );
256
+ case "input":
257
+ return /* @__PURE__ */ jsx(
258
+ MessageCircleQuestionIcon,
259
+ {
260
+ "aria-label": "Waiting on user input",
261
+ className
262
+ }
263
+ );
264
+ }
265
+ }
266
+ function StatusBadge({ status }) {
267
+ return /* @__PURE__ */ jsx(
268
+ "span",
269
+ {
270
+ className: cn(
271
+ "text-muted-foreground rounded-md border px-2 py-1 font-mono text-[0.6875rem] capitalize",
272
+ (status === "failed" || status === "error") && "text-destructive border-destructive/30"
273
+ ),
274
+ children: status
275
+ }
276
+ );
277
+ }
278
+ var dateTime = new Intl.DateTimeFormat(void 0, {
279
+ dateStyle: "medium",
280
+ timeStyle: "short"
281
+ });
282
+ function formatTimestamp(value) {
283
+ return dateTime.format(new Date(value));
284
+ }
285
+ var History = {
286
+ Root: HistoryRoot,
287
+ Item: HistoryItem,
288
+ ItemTrigger: HistoryItemTrigger,
289
+ Empty: HistoryEmpty
290
+ };
291
+ export {
292
+ ChatStatusIcon,
293
+ ChildProgressList,
294
+ History,
295
+ RuntimeEventsProvider,
296
+ StatusBadge,
297
+ conversationStatusLabel,
298
+ formatTimestamp,
299
+ isConversationEvent,
300
+ useConversationStatus
301
+ };
302
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/index.tsx"],
4
+ "sourcesContent": ["import {\n CircleAlertIcon,\n CircleDotIcon,\n CirclePauseIcon,\n MessageCircleQuestionIcon,\n MessageSquareIcon,\n} from 'lucide-react';\nimport {\n type ReactNode,\n createContext,\n use,\n useEffect,\n useEffectEvent,\n useMemo,\n useState,\n} from 'react';\n\nimport type {\n ChildActivity,\n ChildProgress,\n ConversationStatus,\n} from '@deepagents/experimental/zukhruf';\nimport type { OwnerEvent } from '@deepagents/experimental/zukhruf/http';\nimport { cn } from '@deepagents/react-shadcn';\n\nexport type { ConversationStatus } from '@deepagents/experimental/zukhruf';\n\nexport type HistoryRecord = {\n chatId: string;\n userId: string;\n title?: string;\n createdAt: number;\n updatedAt: number;\n messageCount: number;\n status: ConversationStatus;\n children?: readonly ChildProgress[];\n};\n\nexport type ConversationEvent = {\n type: 'change';\n resource: 'conversation';\n id: string;\n status: ConversationStatus;\n child?: ChildProgress;\n};\n\nexport type RuntimeEvent = OwnerEvent;\n\nexport function conversationStatusLabel(status: ConversationStatus) {\n switch (status.type) {\n case 'idle':\n return 'idle';\n case 'systemError':\n return 'error';\n case 'active':\n if (status.activeFlags.includes('waitingOnApproval')) return 'approval';\n if (status.activeFlags.includes('waitingOnUserInput')) return 'input';\n return 'active';\n }\n}\n\ntype LiveStatuses = ReadonlyMap<string, ConversationEvent>;\n\nconst ConversationStatusContext = createContext<LiveStatuses | null>(null);\nconst RuntimeHistoryContext = createContext<readonly HistoryRecord[]>([]);\n\nexport function RuntimeEventsProvider({\n href,\n onEvent,\n history = [],\n children,\n}: {\n href: string | undefined;\n onEvent: (event: RuntimeEvent) => void;\n history?: readonly HistoryRecord[];\n children: ReactNode;\n}) {\n const [statuses, setStatuses] = useState<LiveStatuses>(new Map());\n const notify = useEffectEvent(onEvent);\n useEffect(() => {\n if (!href) return;\n const source = new EventSource(href);\n const handleEvent = (message: MessageEvent<string>) => {\n const event = parseRuntimeEvent(message.data);\n if (!event) return;\n if (event.type === 'ready') {\n setStatuses(new Map());\n } else if (isConversationEvent(event)) {\n setStatuses((previous) => new Map(previous).set(event.id, event));\n }\n notify(event);\n };\n source.addEventListener('message', handleEvent);\n return () => {\n source.removeEventListener('message', handleEvent);\n source.close();\n };\n }, [href]);\n return (\n <ConversationStatusContext value={statuses}>\n <RuntimeHistoryContext value={history}>{children}</RuntimeHistoryContext>\n </ConversationStatusContext>\n );\n}\n\nexport function useConversationStatus(\n chatId: string,\n snapshot: ConversationStatus,\n): ConversationStatus {\n const statuses = use(ConversationStatusContext);\n if (!statuses) {\n throw new Error(\n 'useConversationStatus must be used inside <RuntimeEventsProvider>.',\n );\n }\n return statuses.get(chatId)?.status ?? snapshot;\n}\n\nconst childStateLabels: Record<ChildProgress['state'], string> = {\n pending: 'Pending',\n queued: 'Queued',\n running: 'Running',\n waitingOnApproval: 'Waiting on approval',\n waitingOnUserInput: 'Waiting on user input',\n completed: 'Completed',\n failed: 'Failed',\n interrupted: 'Interrupted',\n};\n\nconst activityLabels: Record<ChildActivity['type'], string> = {\n spawn: 'Spawned',\n message: 'Message sent',\n followup: 'Follow-up sent',\n interrupt: 'Interrupt accepted',\n completion: 'Turn finished',\n};\n\n/** One row per child in the selected project, refreshed by the owner stream. */\nexport function ChildProgressList({\n treeId,\n snapshot = [],\n}: {\n treeId: string;\n snapshot?: readonly ChildProgress[];\n}) {\n const events = use(ConversationStatusContext);\n const history = use(RuntimeHistoryContext);\n const saved =\n history.find((record) => record.chatId === treeId)?.children ?? snapshot;\n const children = new Map(\n saved\n .filter((child) => child.treeId === treeId)\n .map((child) => [child.chatId, child]),\n );\n for (const event of events?.values() ?? []) {\n if (event.child?.treeId === treeId) children.set(event.id, event.child);\n }\n if (children.size === 0) return null;\n return (\n <section aria-label=\"Child agents\" className=\"border-t px-6 py-3\">\n <h2 className=\"mb-2 text-sm font-medium\">Child agents</h2>\n <ul className=\"space-y-2\">\n {[...children.values()]\n .sort((a, b) => a.path.localeCompare(b.path))\n .map((child) => {\n const latest = Object.values(child.activities).sort(\n (a, b) => b.at - a.at,\n )[0];\n return (\n <li\n key={child.chatId}\n className=\"flex items-center justify-between gap-4 text-sm\"\n >\n <div className=\"min-w-0\">\n <p className=\"truncate font-mono text-xs\">{child.path}</p>\n {latest && (\n <p className=\"text-muted-foreground text-xs\">\n {activityLabels[latest.type]} \u00B7 {latest.actorPath} \u2192{' '}\n {latest.targetPath}\n </p>\n )}\n </div>\n <StatusBadge status={childStateLabels[child.state]} />\n </li>\n );\n })}\n </ul>\n </section>\n );\n}\n\nexport function isConversationEvent(\n event: RuntimeEvent,\n): event is ConversationEvent {\n return (\n event.type === 'change' &&\n event.resource === 'conversation' &&\n isConversationStatus(event.status) &&\n (event.child === undefined ||\n (isChildProgress(event.child) && event.child.chatId === event.id))\n );\n}\n\nfunction parseRuntimeEvent(data: string): RuntimeEvent | undefined {\n let event: unknown;\n try {\n event = JSON.parse(data);\n } catch {\n return undefined;\n }\n if (!isRecord(event) || typeof event.type !== 'string') return undefined;\n if (event.type === 'ready') return { type: 'ready' };\n if (\n event.type !== 'change' ||\n typeof event.resource !== 'string' ||\n !event.resource ||\n typeof event.id !== 'string' ||\n !event.id\n ) {\n return undefined;\n }\n if (\n event.resource === 'conversation' &&\n (!isConversationStatus(event.status) ||\n (event.child !== undefined &&\n (!isChildProgress(event.child) || event.child.chatId !== event.id)))\n ) {\n return undefined;\n }\n return event as RuntimeEvent;\n}\n\nfunction isConversationStatus(value: unknown): value is ConversationStatus {\n if (!isRecord(value) || typeof value.type !== 'string') return false;\n if (value.type === 'idle' || value.type === 'systemError') return true;\n return (\n value.type === 'active' &&\n Array.isArray(value.activeFlags) &&\n value.activeFlags.every(\n (flag) => flag === 'waitingOnApproval' || flag === 'waitingOnUserInput',\n )\n );\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction isChildProgress(value: unknown): value is ChildProgress {\n if (\n !isRecord(value) ||\n !isRecord(value.activities) ||\n !['chatId', 'treeId', 'path', 'parentChatId', 'declarationName'].every(\n (key) => typeof value[key] === 'string',\n ) ||\n typeof value.state !== 'string' ||\n !Object.hasOwn(childStateLabels, value.state)\n )\n return false;\n return Object.entries(value.activities).every(\n ([kind, activity]) =>\n Object.hasOwn(activityLabels, kind) &&\n isRecord(activity) &&\n activity.type === kind &&\n typeof activity.at === 'number' &&\n Number.isFinite(activity.at) &&\n (activity.outcome === undefined ||\n ['completed', 'failed', 'cancelled'].includes(\n activity.outcome as string,\n )) &&\n ['id', 'actorPath', 'targetPath', 'streamId'].every(\n (key) => typeof activity[key] === 'string',\n ),\n );\n}\n\ntype HistoryContextValue = {\n activeChatId: string | undefined;\n onSelect: (history: HistoryRecord) => void;\n};\n\nconst HistoryContext = createContext<HistoryContextValue | null>(null);\n\nfunction useHistoryContext() {\n const context = use(HistoryContext);\n if (!context) {\n throw new Error('History.* parts must be rendered inside <History.Root>.');\n }\n return context;\n}\n\nfunction HistoryRoot({\n activeChatId,\n onSelect,\n className,\n children,\n}: {\n activeChatId: string | undefined;\n onSelect: (history: HistoryRecord) => void;\n className?: string;\n children: ReactNode;\n}) {\n const value = useMemo(\n () => ({ activeChatId, onSelect }),\n [activeChatId, onSelect],\n );\n return (\n <HistoryContext.Provider value={value}>\n <div className={cn('flex min-h-0 flex-col gap-1', className)}>\n {children}\n </div>\n </HistoryContext.Provider>\n );\n}\n\nfunction HistoryItem({\n className,\n children,\n}: {\n className?: string;\n children: ReactNode;\n}) {\n return (\n <div className={cn('group/history-item relative', className)}>\n {children}\n </div>\n );\n}\n\nfunction HistoryItemTrigger({\n history,\n className,\n children,\n}: {\n history: HistoryRecord;\n className?: string;\n children: ReactNode;\n}) {\n const { activeChatId, onSelect } = useHistoryContext();\n const isActive = activeChatId === history.chatId;\n return (\n <button\n type=\"button\"\n aria-current={isActive ? 'true' : undefined}\n onClick={() => onSelect(history)}\n className={cn(\n 'flex w-full items-center gap-2 rounded-lg px-2 py-1.5 text-left text-sm transition-colors',\n isActive\n ? 'bg-accent text-foreground'\n : 'text-muted-foreground hover:bg-accent hover:text-foreground',\n className,\n )}\n >\n {children}\n </button>\n );\n}\n\nfunction HistoryEmpty({\n className,\n children,\n}: {\n className?: string;\n children: ReactNode;\n}) {\n return (\n <div className={cn('text-muted-foreground px-2 py-4 text-sm', className)}>\n {children}\n </div>\n );\n}\n\nexport function ChatStatusIcon({\n chatId,\n status,\n}: Pick<HistoryRecord, 'chatId' | 'status'>) {\n const className = 'size-3.5 shrink-0';\n const label = conversationStatusLabel(useConversationStatus(chatId, status));\n switch (label) {\n case 'active':\n return <CircleDotIcon aria-label=\"Active\" className={className} />;\n case 'error':\n return (\n <CircleAlertIcon\n aria-label=\"Error\"\n className={cn(className, 'text-destructive')}\n />\n );\n case 'idle':\n return <MessageSquareIcon aria-label=\"idle\" className={className} />;\n case 'approval':\n return (\n <CirclePauseIcon\n aria-label=\"Waiting on approval\"\n className={className}\n />\n );\n case 'input':\n return (\n <MessageCircleQuestionIcon\n aria-label=\"Waiting on user input\"\n className={className}\n />\n );\n }\n}\n\nexport function StatusBadge({ status }: { status: string }) {\n return (\n <span\n className={cn(\n 'text-muted-foreground rounded-md border px-2 py-1 font-mono text-[0.6875rem] capitalize',\n (status === 'failed' || status === 'error') &&\n 'text-destructive border-destructive/30',\n )}\n >\n {status}\n </span>\n );\n}\n\nconst dateTime = new Intl.DateTimeFormat(undefined, {\n dateStyle: 'medium',\n timeStyle: 'short',\n});\n\nexport function formatTimestamp(value: number | string) {\n return dateTime.format(new Date(value));\n}\n\nexport const History = {\n Root: HistoryRoot,\n Item: HistoryItem,\n ItemTrigger: HistoryItemTrigger,\n Empty: HistoryEmpty,\n} as const;\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@deepagents/devtool-history",
3
+ "version": "7.0.0",
4
+ "license": "MIT",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/JanuaryLabs/deepagents",
8
+ "directory": "packages/devtool/history"
9
+ },
10
+ "type": "module",
11
+ "main": "./dist/index.js",
12
+ "module": "./dist/index.js",
13
+ "types": "./dist/index.d.ts",
14
+ "files": [
15
+ "dist",
16
+ "!dist/spec/**",
17
+ "!**/*.test.*",
18
+ "!**/*.tsbuildinfo"
19
+ ],
20
+ "publishConfig": {
21
+ "access": "public"
22
+ },
23
+ "exports": {
24
+ "./package.json": "./package.json",
25
+ ".": {
26
+ "types": "./dist/index.d.ts",
27
+ "import": "./dist/index.js",
28
+ "default": "./dist/index.js"
29
+ }
30
+ },
31
+ "dependencies": {
32
+ "@deepagents/experimental": "7.0.0",
33
+ "@deepagents/react-shadcn": "7.0.0",
34
+ "lucide-react": "^1.33.0"
35
+ },
36
+ "peerDependencies": {
37
+ "react": "19.2.8"
38
+ },
39
+ "devDependencies": {
40
+ "@types/react": "19.2.18",
41
+ "esbuild": "^0.28.0",
42
+ "typescript": "^6.0.3"
43
+ }
44
+ }