@sentry/junior-dashboard 0.241.0 → 0.243.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.
- package/dist/app.js +50 -12
- package/dist/client.js +48 -48
- package/dist/index.js +50 -12
- package/dist/tailwind.css +1 -1
- package/package.json +3 -3
- package/src/client/conversations/ConversationChildAgents.tsx +107 -0
- package/src/client/conversations/ConversationDetailsDrawer.tsx +8 -1
- package/src/client/conversations/SubagentTranscriptDrawer.tsx +1 -1
- package/src/client/conversations/eventLog.ts +2 -1
- package/src/client/types.ts +1 -1
- package/src/mock-reporting/fixtures.ts +44 -10
- package/src/mock-reporting/routes.ts +12 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { useQuery } from "@tanstack/react-query";
|
|
2
|
+
import { Bot } from "lucide-react";
|
|
3
|
+
import { Link } from "react-router";
|
|
4
|
+
import {
|
|
5
|
+
conversationChildAgentsReportSchema,
|
|
6
|
+
type ConversationChildAgent,
|
|
7
|
+
} from "@sentry/junior/api/schema";
|
|
8
|
+
|
|
9
|
+
import { StatusChip, type StatusChipTone } from "../components/StatusChip";
|
|
10
|
+
import { formatMessageTimestamp } from "../format";
|
|
11
|
+
import { fetchDashboardJson } from "../http";
|
|
12
|
+
import { conversationPath } from "./conversationRoutes";
|
|
13
|
+
|
|
14
|
+
const statusTone: Record<ConversationChildAgent["status"], StatusChipTone> = {
|
|
15
|
+
blocked: "warning",
|
|
16
|
+
completed: "success",
|
|
17
|
+
failed: "danger",
|
|
18
|
+
paused: "info",
|
|
19
|
+
pending: "neutral",
|
|
20
|
+
running: "info",
|
|
21
|
+
stopped: "neutral",
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/** Load and show the child agents of one Conversation with runtime status. */
|
|
25
|
+
export function ConversationChildAgents(props: {
|
|
26
|
+
conversationId: string;
|
|
27
|
+
/** Called before a child link leaves the current Conversation. */
|
|
28
|
+
onNavigate(): void;
|
|
29
|
+
}) {
|
|
30
|
+
const query = useQuery({
|
|
31
|
+
queryFn: ({ signal }) =>
|
|
32
|
+
fetchDashboardJson(
|
|
33
|
+
conversationChildAgentsReportSchema,
|
|
34
|
+
`/api/conversations/${encodeURIComponent(props.conversationId)}/agents`,
|
|
35
|
+
signal,
|
|
36
|
+
),
|
|
37
|
+
queryKey: ["conversation", props.conversationId, "agents"],
|
|
38
|
+
retry: false,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
if (query.isPending) {
|
|
42
|
+
return (
|
|
43
|
+
<p className="m-0 text-sm text-dashboard-text-muted" role="status">
|
|
44
|
+
Loading subagents…
|
|
45
|
+
</p>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
if (query.error) {
|
|
49
|
+
return (
|
|
50
|
+
<p className="m-0 text-sm text-red-300/80">
|
|
51
|
+
Could not load subagents from this Conversation.
|
|
52
|
+
</p>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
if (query.data.agents.length === 0) {
|
|
56
|
+
return (
|
|
57
|
+
<div className="grid justify-items-center gap-2 py-10 text-center text-dashboard-text-muted">
|
|
58
|
+
<Bot aria-hidden="true" className="size-5 opacity-60" />
|
|
59
|
+
<p className="m-0 text-sm">No subagents were started.</p>
|
|
60
|
+
</div>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<ol aria-label="Subagents" className="m-0 grid list-none gap-3 p-0">
|
|
66
|
+
{query.data.agents.map((agent) => (
|
|
67
|
+
<li
|
|
68
|
+
className="grid gap-2 rounded-lg border border-dashboard-border-subtle bg-dashboard-surface px-3 py-3"
|
|
69
|
+
key={agent.conversationId}
|
|
70
|
+
>
|
|
71
|
+
<div className="flex min-w-0 flex-wrap items-center gap-2">
|
|
72
|
+
<Link
|
|
73
|
+
className="min-w-0 truncate font-mono text-xs font-semibold text-dashboard-text no-underline hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-dashboard-focus"
|
|
74
|
+
onClick={props.onNavigate}
|
|
75
|
+
to={conversationPath(agent.conversationId)}
|
|
76
|
+
>
|
|
77
|
+
{agent.redacted ? "Private subagent" : (agent.name ?? "agent")}
|
|
78
|
+
</Link>
|
|
79
|
+
<StatusChip
|
|
80
|
+
className="ml-auto"
|
|
81
|
+
size="compact"
|
|
82
|
+
tone={statusTone[agent.status]}
|
|
83
|
+
>
|
|
84
|
+
{agent.status}
|
|
85
|
+
</StatusChip>
|
|
86
|
+
</div>
|
|
87
|
+
{agent.task ? (
|
|
88
|
+
<p className="m-0 line-clamp-4 whitespace-pre-wrap break-words text-sm leading-relaxed text-dashboard-text">
|
|
89
|
+
{agent.task}
|
|
90
|
+
</p>
|
|
91
|
+
) : null}
|
|
92
|
+
{agent.error ? (
|
|
93
|
+
<p className="m-0 whitespace-pre-wrap break-words text-xs leading-relaxed text-dashboard-text-muted">
|
|
94
|
+
{agent.error}
|
|
95
|
+
</p>
|
|
96
|
+
) : null}
|
|
97
|
+
<time
|
|
98
|
+
className="font-mono text-xs text-dashboard-text-muted"
|
|
99
|
+
dateTime={agent.updatedAt}
|
|
100
|
+
>
|
|
101
|
+
{formatMessageTimestamp(Date.parse(agent.updatedAt), true)}
|
|
102
|
+
</time>
|
|
103
|
+
</li>
|
|
104
|
+
))}
|
|
105
|
+
</ol>
|
|
106
|
+
);
|
|
107
|
+
}
|
|
@@ -5,12 +5,14 @@ import { formatRelativeTime, formatTime } from "../format";
|
|
|
5
5
|
import { ButtonLink } from "../components/Button";
|
|
6
6
|
import { Drawer } from "../components/Drawer";
|
|
7
7
|
import { SegmentedTabs } from "../components/SegmentedTabs";
|
|
8
|
+
import { ConversationChildAgents } from "./ConversationChildAgents";
|
|
8
9
|
import { ConversationMemories } from "./ConversationMemories";
|
|
9
10
|
import { ShareConversationButton } from "./ShareConversationButton";
|
|
10
11
|
|
|
11
12
|
const tabs = [
|
|
12
13
|
{ label: "Details", value: "details" },
|
|
13
14
|
{ label: "Memories", value: "memories" },
|
|
15
|
+
{ label: "Subagents", value: "agents" },
|
|
14
16
|
] as const;
|
|
15
17
|
|
|
16
18
|
/** Put conversation context before usage and diagnostic links. */
|
|
@@ -26,7 +28,7 @@ export function ConversationDetailsDrawer(props: {
|
|
|
26
28
|
stats: ReactNode;
|
|
27
29
|
title: string;
|
|
28
30
|
}) {
|
|
29
|
-
const [tab, setTab] = useState<
|
|
31
|
+
const [tab, setTab] = useState<(typeof tabs)[number]["value"]>("details");
|
|
30
32
|
const titleId = "conversation-details-drawer-title";
|
|
31
33
|
const sections = [
|
|
32
34
|
{ content: props.brief, title: "Summary" },
|
|
@@ -65,6 +67,11 @@ export function ConversationDetailsDrawer(props: {
|
|
|
65
67
|
>
|
|
66
68
|
{tab === "memories" ? (
|
|
67
69
|
<ConversationMemories conversationId={props.conversationId} />
|
|
70
|
+
) : tab === "agents" ? (
|
|
71
|
+
<ConversationChildAgents
|
|
72
|
+
conversationId={props.conversationId}
|
|
73
|
+
onNavigate={props.onClose}
|
|
74
|
+
/>
|
|
68
75
|
) : sections.length > 0 ? (
|
|
69
76
|
<div className="grid min-w-0 gap-5">
|
|
70
77
|
{sections.map((section) => (
|
|
@@ -169,7 +169,7 @@ function statusLabel(
|
|
|
169
169
|
part: TranscriptViewSubagentPart,
|
|
170
170
|
detailStatus?: string,
|
|
171
171
|
): string {
|
|
172
|
-
if (part.status
|
|
172
|
+
if (part.status !== "running" && part.status !== "completed") {
|
|
173
173
|
return part.status;
|
|
174
174
|
}
|
|
175
175
|
return detailStatus ?? part.status;
|
|
@@ -111,7 +111,8 @@ export function eventLogTone(data: ConversationReportEventData): string {
|
|
|
111
111
|
}
|
|
112
112
|
if (
|
|
113
113
|
(data.type === "guardian_action_reviewed" && data.decision === "ask") ||
|
|
114
|
-
(data.type === "subagent" &&
|
|
114
|
+
(data.type === "subagent" &&
|
|
115
|
+
(data.status === "aborted" || data.status === "blocked"))
|
|
115
116
|
) {
|
|
116
117
|
return "text-amber-200";
|
|
117
118
|
}
|
package/src/client/types.ts
CHANGED
|
@@ -40,7 +40,7 @@ export type TranscriptViewToolCallPart = {
|
|
|
40
40
|
export type TranscriptViewSubagentPart = {
|
|
41
41
|
childConversationId: string;
|
|
42
42
|
id: string;
|
|
43
|
-
status: "aborted" | "completed" | "error" | "running";
|
|
43
|
+
status: "aborted" | "blocked" | "completed" | "error" | "running";
|
|
44
44
|
subagentKind: string;
|
|
45
45
|
type: "subagent";
|
|
46
46
|
};
|
|
@@ -8,6 +8,7 @@ import type {
|
|
|
8
8
|
ActorProfileReport,
|
|
9
9
|
ActorSummaryReport,
|
|
10
10
|
ActorWindowMetrics,
|
|
11
|
+
ConversationChildAgentsReport,
|
|
11
12
|
ConversationDetailReport,
|
|
12
13
|
ConversationEventPage,
|
|
13
14
|
ConversationFeed,
|
|
@@ -694,7 +695,7 @@ function dashboardQaConversation(nowMs: number): ConversationDetailReport {
|
|
|
694
695
|
calls: [
|
|
695
696
|
{
|
|
696
697
|
toolCallId: "qa-advisor",
|
|
697
|
-
name: "
|
|
698
|
+
name: "spawnAgent",
|
|
698
699
|
status: "running",
|
|
699
700
|
},
|
|
700
701
|
],
|
|
@@ -704,11 +705,11 @@ function dashboardQaConversation(nowMs: number): ConversationDetailReport {
|
|
|
704
705
|
calls: [
|
|
705
706
|
{
|
|
706
707
|
toolCallId: "qa-advisor",
|
|
707
|
-
name: "
|
|
708
|
+
name: "spawnAgent",
|
|
708
709
|
status: "running",
|
|
709
710
|
startedSeq: 1,
|
|
710
711
|
startedAt: iso(Date.parse(startedAt), 2_000),
|
|
711
|
-
input: {
|
|
712
|
+
input: { task: "Review the dashboard plan before editing." },
|
|
712
713
|
},
|
|
713
714
|
],
|
|
714
715
|
}),
|
|
@@ -717,7 +718,7 @@ function dashboardQaConversation(nowMs: number): ConversationDetailReport {
|
|
|
717
718
|
startedSeq: 3,
|
|
718
719
|
startedAt: iso(Date.parse(startedAt), 4_000),
|
|
719
720
|
childConversationId: DASHBOARD_QA_PLAN_ID,
|
|
720
|
-
subagentKind: "
|
|
721
|
+
subagentKind: "agent",
|
|
721
722
|
parentToolCallId: "qa-advisor",
|
|
722
723
|
status: "running",
|
|
723
724
|
}),
|
|
@@ -726,7 +727,7 @@ function dashboardQaConversation(nowMs: number): ConversationDetailReport {
|
|
|
726
727
|
startedSeq: 3,
|
|
727
728
|
startedAt: iso(Date.parse(startedAt), 4_000),
|
|
728
729
|
childConversationId: DASHBOARD_QA_PLAN_ID,
|
|
729
|
-
subagentKind: "
|
|
730
|
+
subagentKind: "agent",
|
|
730
731
|
parentToolCallId: "qa-advisor",
|
|
731
732
|
status: "completed",
|
|
732
733
|
}),
|
|
@@ -735,11 +736,11 @@ function dashboardQaConversation(nowMs: number): ConversationDetailReport {
|
|
|
735
736
|
calls: [
|
|
736
737
|
{
|
|
737
738
|
toolCallId: "qa-advisor",
|
|
738
|
-
name: "
|
|
739
|
+
name: "spawnAgent",
|
|
739
740
|
status: "completed",
|
|
740
741
|
startedSeq: 1,
|
|
741
742
|
startedAt: iso(Date.parse(startedAt), 2_000),
|
|
742
|
-
output: {
|
|
743
|
+
output: { conversation_id: DASHBOARD_QA_PLAN_ID },
|
|
743
744
|
},
|
|
744
745
|
],
|
|
745
746
|
}),
|
|
@@ -748,7 +749,7 @@ function dashboardQaConversation(nowMs: number): ConversationDetailReport {
|
|
|
748
749
|
startedSeq: 6,
|
|
749
750
|
startedAt: iso(Date.parse(startedAt), 25_000),
|
|
750
751
|
childConversationId: DASHBOARD_QA_REVIEW_ID,
|
|
751
|
-
subagentKind: "
|
|
752
|
+
subagentKind: "agent",
|
|
752
753
|
status: "running",
|
|
753
754
|
}),
|
|
754
755
|
reportEvent(7, iso(Date.parse(startedAt), 44_000), {
|
|
@@ -756,8 +757,8 @@ function dashboardQaConversation(nowMs: number): ConversationDetailReport {
|
|
|
756
757
|
startedSeq: 6,
|
|
757
758
|
startedAt: iso(Date.parse(startedAt), 25_000),
|
|
758
759
|
childConversationId: DASHBOARD_QA_REVIEW_ID,
|
|
759
|
-
subagentKind: "
|
|
760
|
-
status: "
|
|
760
|
+
subagentKind: "agent",
|
|
761
|
+
status: "blocked",
|
|
761
762
|
}),
|
|
762
763
|
reportEvent(8, iso(Date.parse(startedAt), 50_000), {
|
|
763
764
|
type: "tool_calls",
|
|
@@ -1816,6 +1817,39 @@ export function readMockConversationPendingMessages(
|
|
|
1816
1817
|
};
|
|
1817
1818
|
}
|
|
1818
1819
|
|
|
1820
|
+
/** Return the child agents of one mock Conversation, matching its transcript. */
|
|
1821
|
+
export function readMockConversationChildAgents(
|
|
1822
|
+
conversationId: string,
|
|
1823
|
+
): ConversationChildAgentsReport | undefined {
|
|
1824
|
+
const conversation = mockConversations(NOW_MS).find(
|
|
1825
|
+
(candidate) => candidate.conversationId === conversationId,
|
|
1826
|
+
);
|
|
1827
|
+
if (!conversation) return undefined;
|
|
1828
|
+
if (conversationId !== DASHBOARD_QA_CONVERSATION_ID) {
|
|
1829
|
+
return { agents: [], conversationId };
|
|
1830
|
+
}
|
|
1831
|
+
const startedAtMs = NOW_MS - 5 * 60 * 60_000;
|
|
1832
|
+
return {
|
|
1833
|
+
agents: [
|
|
1834
|
+
{
|
|
1835
|
+
conversationId: DASHBOARD_QA_REVIEW_ID,
|
|
1836
|
+
error: "GitHub access is needed to read the pull request.",
|
|
1837
|
+
name: "reviewer",
|
|
1838
|
+
status: "blocked",
|
|
1839
|
+
task: "Review the open dashboard pull requests and list risky changes.",
|
|
1840
|
+
updatedAt: iso(startedAtMs, 44_000),
|
|
1841
|
+
},
|
|
1842
|
+
{
|
|
1843
|
+
conversationId: DASHBOARD_QA_PLAN_ID,
|
|
1844
|
+
status: "completed",
|
|
1845
|
+
task: "Review the dashboard plan before editing.",
|
|
1846
|
+
updatedAt: iso(startedAtMs, 20_000),
|
|
1847
|
+
},
|
|
1848
|
+
],
|
|
1849
|
+
conversationId,
|
|
1850
|
+
};
|
|
1851
|
+
}
|
|
1852
|
+
|
|
1819
1853
|
/** Return one canonical-event visual-QA conversation detail fixture. */
|
|
1820
1854
|
export function readMockConversationDetail(
|
|
1821
1855
|
conversationId: string,
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
actorProfileReportSchema,
|
|
8
8
|
archiveConversationBodySchema,
|
|
9
9
|
archiveConversationResponseSchema,
|
|
10
|
+
conversationChildAgentsReportSchema,
|
|
10
11
|
conversationDetailQuerySchema,
|
|
11
12
|
conversationDetailReportSchema,
|
|
12
13
|
conversationEventPageSchema,
|
|
@@ -38,6 +39,7 @@ import {
|
|
|
38
39
|
readMockCodeRepository,
|
|
39
40
|
} from "./code-repository";
|
|
40
41
|
import {
|
|
42
|
+
readMockConversationChildAgents,
|
|
41
43
|
readMockConversationDetail,
|
|
42
44
|
readMockConversationEvents,
|
|
43
45
|
readMockConversationFeed,
|
|
@@ -193,6 +195,16 @@ export function createMockReportingApi(): Hono<{
|
|
|
193
195
|
? jsonResponse(conversationEventPageSchema, report)
|
|
194
196
|
: errorResponse("Invalid conversation cursor.", 400);
|
|
195
197
|
});
|
|
198
|
+
app.get("/conversations/:conversationId/agents", (c) => {
|
|
199
|
+
const params = conversationParamsSchema.safeParse(c.req.param());
|
|
200
|
+
if (!params.success) {
|
|
201
|
+
return errorResponse("Invalid route parameters.", 400);
|
|
202
|
+
}
|
|
203
|
+
const report = readMockConversationChildAgents(params.data.conversationId);
|
|
204
|
+
return report
|
|
205
|
+
? jsonResponse(conversationChildAgentsReportSchema, report)
|
|
206
|
+
: errorResponse("Conversation not found.", 404);
|
|
207
|
+
});
|
|
196
208
|
app.get("/conversations/:conversationId/pending-messages", (c) => {
|
|
197
209
|
const params = conversationParamsSchema.safeParse(c.req.param());
|
|
198
210
|
if (!params.success) {
|