@sentry/junior-dashboard 0.263.0 → 0.265.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/LICENSE +201 -0
- package/README.md +2 -5
- package/dist/app.js +119 -35
- package/dist/client.js +148 -135
- package/dist/index.d.ts +0 -1
- package/dist/index.js +119 -35
- package/dist/tailwind.css +1 -1
- package/package.json +9 -4
- package/src/api/schema.ts +0 -1
- package/src/app.ts +5 -20
- package/src/client/App.tsx +29 -31
- package/src/client/api.ts +7 -0
- package/src/client/components/BottomSheet.tsx +26 -18
- package/src/client/components/CopyButton.tsx +83 -0
- package/src/client/components/DetailsPanel.tsx +133 -0
- package/src/client/components/Drawer.tsx +34 -33
- package/src/client/components/FilterBar.tsx +16 -31
- package/src/client/components/Metric.tsx +33 -23
- package/src/client/components/PageContentSkeleton.tsx +1 -1
- package/src/client/components/Pagination.tsx +1 -1
- package/src/client/components/ParticipantAvatarStack.tsx +40 -36
- package/src/client/components/ProfileMenu.tsx +27 -93
- package/src/client/components/README.md +101 -64
- package/src/client/components/SegmentedNav.tsx +1 -1
- package/src/client/components/ShimmerText.tsx +2 -6
- package/src/client/components/ThemeSwitcher.tsx +19 -15
- package/src/client/components/charts/ActivityChart.tsx +173 -288
- package/src/client/components/charts/CacheWrites.tsx +63 -50
- package/src/client/components/charts/InputCacheChart.tsx +137 -131
- package/src/client/components/charts/README.md +4 -4
- package/src/client/components/charts/SystemMetricCharts.tsx +68 -95
- package/src/client/components/charts/WorkspaceUsageChart.tsx +42 -62
- package/src/client/components/controls/DirectorySortSelect.tsx +3 -3
- package/src/client/components/controls/TimeRangeSelector.tsx +10 -15
- package/src/client/components/layout/DashboardHeader.tsx +44 -90
- package/src/client/components/layout/SecondaryNavigation.tsx +2 -1
- package/src/client/components/layout/VisualViewportShell.tsx +2 -2
- package/src/client/components/ui/accordion.tsx +77 -0
- package/src/client/components/ui/alert-dialog.tsx +89 -0
- package/src/client/components/ui/button.tsx +85 -0
- package/src/client/components/ui/chart.tsx +74 -0
- package/src/client/components/ui/checkbox.tsx +26 -0
- package/src/client/components/ui/collapsible.tsx +22 -0
- package/src/client/components/ui/field.tsx +49 -0
- package/src/client/components/ui/input.tsx +35 -0
- package/src/client/components/ui/native-select.tsx +57 -0
- package/src/client/components/ui/popover.tsx +67 -0
- package/src/client/components/ui/radio-group.tsx +32 -0
- package/src/client/components/ui/sheet.tsx +151 -0
- package/src/client/components/ui/skeleton.tsx +19 -0
- package/src/client/components/ui/table.tsx +80 -0
- package/src/client/components/ui/tabs.tsx +75 -0
- package/src/client/components/ui/textarea.tsx +17 -0
- package/src/client/components/ui/toast.tsx +215 -0
- package/src/client/components/ui/toggle-group.tsx +36 -0
- package/src/client/components/ui/toggle.tsx +37 -0
- package/src/client/components/ui/tooltip.tsx +168 -0
- package/src/client/components/ui/trace.tsx +96 -0
- package/src/client/components/ui/useSheet.ts +74 -0
- package/src/client/conversations/ChatLayout.tsx +7 -3
- package/src/client/conversations/ConversationBrief.tsx +17 -6
- package/src/client/conversations/ConversationChildAgents.tsx +87 -58
- package/src/client/conversations/ConversationComposer.tsx +26 -12
- package/src/client/conversations/ConversationDetails.tsx +198 -95
- package/src/client/conversations/ConversationEventDetails.tsx +141 -0
- package/src/client/conversations/ConversationEventLog.tsx +150 -129
- package/src/client/conversations/ConversationHeader.tsx +8 -3
- package/src/client/conversations/ConversationHeaderActions.tsx +90 -82
- package/src/client/conversations/ConversationHomeList.tsx +1 -1
- package/src/client/conversations/ConversationLinkedWork.tsx +118 -0
- package/src/client/conversations/ConversationMeta.tsx +111 -92
- package/src/client/conversations/ConversationPage.tsx +241 -110
- package/src/client/conversations/ConversationSidebar.tsx +48 -29
- package/src/client/conversations/ConversationUsageBreakdown.tsx +16 -3
- package/src/client/conversations/ConversationWatches.tsx +165 -0
- package/src/client/conversations/ConversationWorkspace.tsx +12 -22
- package/src/client/conversations/ConversationWorkspaceLoading.tsx +1 -1
- package/src/client/conversations/DetailsList.tsx +124 -0
- package/src/client/conversations/EventDetails.tsx +42 -8
- package/src/client/conversations/ForkConversationButton.tsx +27 -16
- package/src/client/conversations/ObjectCard.tsx +20 -8
- package/src/client/conversations/PendingMailboxStack.tsx +52 -33
- package/src/client/conversations/README.md +93 -19
- package/src/client/conversations/ShareConversationButton.tsx +28 -3
- package/src/client/conversations/SubagentTranscriptDrawer.tsx +21 -15
- package/src/client/conversations/ToolFrame.tsx +36 -52
- package/src/client/conversations/TranscriptActivityGroup.tsx +13 -4
- package/src/client/conversations/TranscriptAutomationTriggerView.tsx +5 -4
- package/src/client/conversations/TranscriptContextEventView.tsx +25 -11
- package/src/client/conversations/TranscriptHeadingRow.tsx +7 -7
- package/src/client/conversations/TranscriptLoading.tsx +1 -1
- package/src/client/conversations/TranscriptMarkdown.tsx +25 -20
- package/src/client/conversations/TranscriptMessageContextView.tsx +8 -2
- package/src/client/conversations/TranscriptMessageView.tsx +6 -1
- package/src/client/conversations/TranscriptReasoningView.tsx +54 -68
- package/src/client/conversations/TranscriptSubagentView.tsx +3 -3
- package/src/client/conversations/TranscriptTimestamp.tsx +9 -7
- package/src/client/conversations/TranscriptToolView.tsx +1 -1
- package/src/client/conversations/TranscriptTurnContextView.tsx +99 -27
- package/src/client/conversations/TranscriptView.tsx +63 -27
- package/src/client/conversations/eventLog.ts +57 -0
- package/src/client/conversations/eventTime.ts +60 -0
- package/src/client/conversations/queries.ts +29 -0
- package/src/client/conversations/transcriptBottomPinning.ts +34 -0
- package/src/client/format.ts +5 -0
- package/src/client/lib/utils.ts +1 -0
- package/src/client/pages/ConnectionsPage.tsx +2 -2
- package/src/client/pages/PersonalTokensPage.tsx +4 -4
- package/src/client/pages/SettingsPage.tsx +9 -10
- package/src/client/pages/admin/ServiceAccountsSection.tsx +2 -2
- package/src/client/pages/automations/AutomationCostChart.tsx +50 -71
- package/src/client/pages/automations/AutomationDeleteDialog.tsx +87 -0
- package/src/client/pages/automations/AutomationDetails.tsx +193 -0
- package/src/client/pages/automations/AutomationEditPage.tsx +7 -8
- package/src/client/pages/automations/AutomationEditor.tsx +41 -29
- package/src/client/pages/automations/AutomationEditorLoading.tsx +1 -1
- package/src/client/pages/automations/AutomationEventFields.tsx +202 -119
- package/src/client/pages/automations/AutomationExecutionChart.tsx +51 -64
- package/src/client/pages/automations/AutomationExecutionStatusChart.tsx +47 -70
- package/src/client/pages/automations/AutomationExecutionsPage.tsx +150 -126
- package/src/client/pages/automations/AutomationFilters.tsx +189 -74
- package/src/client/pages/automations/AutomationLifecycle.tsx +109 -78
- package/src/client/pages/automations/AutomationOutcomeFields.tsx +26 -24
- package/src/client/pages/automations/AutomationRecentRuns.tsx +143 -0
- package/src/client/pages/automations/AutomationRow.tsx +12 -138
- package/src/client/pages/automations/AutomationRunDetails.tsx +307 -0
- package/src/client/pages/automations/AutomationRunsPage.tsx +119 -94
- package/src/client/pages/automations/AutomationScheduleFields.tsx +51 -51
- package/src/client/pages/automations/AutomationStatus.tsx +201 -0
- package/src/client/pages/automations/AutomationVersionsPage.tsx +65 -28
- package/src/client/pages/automations/AutomationsPage.tsx +80 -68
- package/src/client/pages/automations/AutomationsPageLayout.tsx +46 -14
- package/src/client/pages/automations/README.md +57 -0
- package/src/client/pages/automations/TriggerFilters.tsx +22 -0
- package/src/client/pages/automations/automationFormat.ts +37 -23
- package/src/client/pages/code/CodeActivityChart.tsx +59 -101
- package/src/client/pages/code/CodePage.tsx +52 -55
- package/src/client/pages/code/RepositoryPage.tsx +6 -8
- package/src/client/pages/locations/LocationActivityChart.tsx +53 -70
- package/src/client/pages/locations/LocationDirectoryActivityChart.tsx +60 -80
- package/src/client/pages/memory/MemoryCostChart.tsx +79 -103
- package/src/client/pages/memory/MemoryDetails.tsx +218 -0
- package/src/client/pages/memory/MemoryPage.tsx +168 -112
- package/src/client/pages/memory/MemoryPageLayout.tsx +33 -9
- package/src/client/pages/memory/MemoryTimeline.tsx +46 -69
- package/src/client/pages/memory/memoryRecord.ts +1 -1
- package/src/client/pages/people/ContributionGrid.tsx +23 -16
- package/src/client/pages/people/PeopleActivityChart.tsx +68 -107
- package/src/client/pages/people/ProfileCodeChanges.tsx +5 -5
- package/src/client/pages/system/BaseSandboxCard.tsx +1 -1
- package/src/client/pages/system/ConversationActivityChart.tsx +49 -67
- package/src/client/pages/system/GuardianActivity.tsx +1 -2
- package/src/client/pages/system/PluginBarChart.tsx +106 -87
- package/src/client/pages/system/PluginReportRecordSet.tsx +25 -24
- package/src/client/pages/system/README.md +23 -0
- package/src/client/pages/system/SystemNavigation.tsx +5 -1
- package/src/client/pages/system/WorkspaceDeleteDialog.tsx +111 -0
- package/src/client/pages/system/WorkspaceDetails.tsx +200 -43
- package/src/client/pages/system/WorkspaceEditor.tsx +59 -61
- package/src/client/pages/system/WorkspaceFormPage.tsx +23 -47
- package/src/client/pages/system/WorkspaceList.tsx +20 -30
- package/src/client/pages/system/WorkspacesPage.tsx +117 -119
- package/src/client/pages/system/sandboxSize.ts +8 -0
- package/src/client/pages/system/workspaceQueries.ts +53 -0
- package/src/client/pages/user/PluginUserPage.tsx +51 -2
- package/src/client/pages/user/pluginUserPageData.ts +1 -6
- package/src/client/styles.ts +4 -0
- package/src/client.tsx +7 -1
- package/src/mock-reporting/automation-collection.ts +4 -2
- package/src/mock-reporting/fixtures.ts +23 -7
- package/src/mock-reporting/routes.ts +12 -0
- package/src/mock-reporting/watches.ts +94 -0
- package/src/shell.ts +4 -9
- package/src/tailwind.css +77 -10
- package/src/client/components/Button.tsx +0 -118
- package/src/client/components/Field.tsx +0 -53
- package/src/client/components/SegmentedTabs.tsx +0 -119
- package/src/client/components/Select.tsx +0 -52
- package/src/client/components/Sheet.LICENSE +0 -21
- package/src/client/components/Sheet.tsx +0 -141
- package/src/client/components/Skeleton.tsx +0 -17
- package/src/client/components/TextInput.tsx +0 -58
- package/src/client/components/Tooltip.tsx +0 -216
- package/src/client/components/charts/useChartLayout.ts +0 -27
- package/src/client/conversations/ConversationDetailsFrame.tsx +0 -91
- package/src/client/conversations/CopyMarkdownButton.tsx +0 -65
- package/src/client/pages/automations/AutomationDetailsDrawer.tsx +0 -219
- package/src/client/pages/dev/ChartFixtures.tsx +0 -325
- package/src/client/pages/dev/ComponentsPage.tsx +0 -899
- package/src/client/pages/dev/ConversationFixture.tsx +0 -231
- package/src/client/pages/dev/ObjectFixtures.tsx +0 -138
- package/src/client/pages/dev/SegmentedFixtures.tsx +0 -59
- package/src/client/pages/dev/SelectFixtures.tsx +0 -62
- package/src/client/pages/dev/TranscriptActivityFixtures.tsx +0 -206
- package/src/client/pages/memory/MemoryDetailsDrawer.tsx +0 -167
- package/src/client/pages/system/SandboxSizeSummary.tsx +0 -47
- package/src/client/pages/system/SnapshotSummary.tsx +0 -78
|
@@ -1,21 +1,19 @@
|
|
|
1
|
-
import { ChevronRight, FolderGit2
|
|
2
|
-
import { useNavigate } from "react-router";
|
|
1
|
+
import { ChevronRight, FolderGit2 } from "lucide-react";
|
|
3
2
|
import type { WorkspaceReport } from "@sentry/junior/api/schema";
|
|
4
3
|
|
|
5
|
-
import { Button } from "../../components/Button";
|
|
6
4
|
import { EmptyTelemetry } from "../../components/EmptyTelemetry";
|
|
7
5
|
import { SelectableRow } from "../../components/SelectableRow";
|
|
8
6
|
import { Card } from "../../components/layout/Card";
|
|
9
|
-
import { formatSandboxSize } from "./
|
|
7
|
+
import { formatSandboxSize } from "./sandboxSize";
|
|
10
8
|
|
|
11
9
|
type WorkspaceListProps = {
|
|
12
|
-
busy: boolean;
|
|
13
10
|
defaultVcpus: number | null;
|
|
14
|
-
|
|
11
|
+
onSelect(workspace: WorkspaceReport): void;
|
|
12
|
+
selectedId: string | undefined;
|
|
15
13
|
workspaces: WorkspaceReport[];
|
|
16
14
|
};
|
|
17
15
|
|
|
18
|
-
/** List Workspace recipes
|
|
16
|
+
/** List Workspace recipes. Selecting a row opens its details. */
|
|
19
17
|
export function WorkspaceList(props: WorkspaceListProps) {
|
|
20
18
|
return (
|
|
21
19
|
<Card padding="none">
|
|
@@ -30,10 +28,10 @@ export function WorkspaceList(props: WorkspaceListProps) {
|
|
|
30
28
|
<ul className="m-0 list-none p-0">
|
|
31
29
|
{props.workspaces.map((workspace) => (
|
|
32
30
|
<WorkspaceListItem
|
|
33
|
-
busy={props.busy}
|
|
34
31
|
defaultVcpus={props.defaultVcpus}
|
|
35
32
|
key={workspace.id}
|
|
36
|
-
|
|
33
|
+
onSelect={() => props.onSelect(workspace)}
|
|
34
|
+
selected={workspace.id === props.selectedId}
|
|
37
35
|
workspace={workspace}
|
|
38
36
|
/>
|
|
39
37
|
))}
|
|
@@ -44,26 +42,27 @@ export function WorkspaceList(props: WorkspaceListProps) {
|
|
|
44
42
|
}
|
|
45
43
|
|
|
46
44
|
function WorkspaceListItem(props: {
|
|
47
|
-
busy: boolean;
|
|
48
45
|
defaultVcpus: number | null;
|
|
49
|
-
|
|
46
|
+
onSelect(): void;
|
|
47
|
+
selected: boolean;
|
|
50
48
|
workspace: WorkspaceReport;
|
|
51
49
|
}) {
|
|
52
50
|
const { workspace } = props;
|
|
53
|
-
const navigate = useNavigate();
|
|
54
|
-
const openWorkspace = () =>
|
|
55
|
-
navigate(`/system/workspaces/${encodeURIComponent(workspace.id)}`);
|
|
56
51
|
return (
|
|
57
|
-
<li
|
|
52
|
+
<li
|
|
53
|
+
className="border-b border-dashboard-border-subtle last:border-b-0"
|
|
54
|
+
data-workspace-id={workspace.id}
|
|
55
|
+
>
|
|
58
56
|
<SelectableRow
|
|
59
|
-
className="
|
|
60
|
-
onSelect={
|
|
61
|
-
selected={
|
|
57
|
+
className="px-4 py-3.5"
|
|
58
|
+
onSelect={props.onSelect}
|
|
59
|
+
selected={props.selected}
|
|
62
60
|
>
|
|
63
61
|
<button
|
|
64
|
-
aria-
|
|
65
|
-
|
|
66
|
-
|
|
62
|
+
aria-expanded={props.selected}
|
|
63
|
+
aria-label={`View Workspace details: ${workspace.name}`}
|
|
64
|
+
className="flex w-full min-w-0 cursor-pointer items-center gap-3 rounded-md border-0 bg-transparent p-0 text-left focus-visible:outline focus-visible:outline-2 focus-visible:outline-dashboard-focus"
|
|
65
|
+
onClick={props.onSelect}
|
|
67
66
|
type="button"
|
|
68
67
|
>
|
|
69
68
|
<span className="grid size-9 shrink-0 place-items-center rounded-md border border-dashboard-border-strong bg-dashboard-fill-soft text-dashboard-accent transition-colors group-hover:border-dashboard-accent/20 group-hover:bg-dashboard-accent/[0.06] group-hover:text-dashboard-accent-text">
|
|
@@ -94,15 +93,6 @@ function WorkspaceListItem(props: {
|
|
|
94
93
|
size={16}
|
|
95
94
|
/>
|
|
96
95
|
</button>
|
|
97
|
-
<Button
|
|
98
|
-
aria-label={`Delete ${workspace.name}`}
|
|
99
|
-
disabled={props.busy}
|
|
100
|
-
onClick={() => props.onDelete(workspace)}
|
|
101
|
-
size="icon"
|
|
102
|
-
title={`Delete ${workspace.name}`}
|
|
103
|
-
>
|
|
104
|
-
<Trash2 aria-hidden="true" size={15} />
|
|
105
|
-
</Button>
|
|
106
96
|
</SelectableRow>
|
|
107
97
|
</li>
|
|
108
98
|
);
|
|
@@ -1,138 +1,136 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useRef, useState } from "react";
|
|
2
|
+
import { Link, useNavigate, useParams } from "react-router";
|
|
2
3
|
import { Plus } from "lucide-react";
|
|
3
|
-
import {
|
|
4
|
-
workspaceListSchema,
|
|
5
|
-
type WorkspaceListReport,
|
|
6
|
-
type WorkspaceReport,
|
|
7
|
-
} from "@sentry/junior/api/schema";
|
|
4
|
+
import type { WorkspaceReport } from "@sentry/junior/api/schema";
|
|
8
5
|
|
|
9
6
|
import { getDashboardAgentName } from "../../agentName";
|
|
10
|
-
import {
|
|
7
|
+
import { buttonVariants } from "../../components/ui/button";
|
|
11
8
|
import { EmptyTelemetry } from "../../components/EmptyTelemetry";
|
|
12
|
-
import {
|
|
9
|
+
import { PageContentSkeleton } from "../../components/PageContentSkeleton";
|
|
13
10
|
import { Card } from "../../components/layout/Card";
|
|
14
11
|
import { PageHeader } from "../../components/layout/PageHeader";
|
|
15
|
-
import {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
} from "../../http";
|
|
12
|
+
import { PageLayout } from "../../components/layout/PageLayout";
|
|
13
|
+
import { useDocumentTitle } from "../../documentTitle";
|
|
14
|
+
import { workspacePath } from "../../format";
|
|
15
|
+
import { cn } from "../../styles";
|
|
20
16
|
import { BaseSandboxCard } from "./BaseSandboxCard";
|
|
21
|
-
import {
|
|
17
|
+
import { SystemNavigation } from "./SystemNavigation";
|
|
18
|
+
import { WorkspaceDeleteDialog } from "./WorkspaceDeleteDialog";
|
|
19
|
+
import { WorkspaceDetails } from "./WorkspaceDetails";
|
|
22
20
|
import { WorkspaceList } from "./WorkspaceList";
|
|
21
|
+
import { useWorkspacesData } from "./workspaceQueries";
|
|
23
22
|
|
|
24
|
-
|
|
23
|
+
const listPath = "/system/workspaces";
|
|
25
24
|
|
|
26
|
-
/**
|
|
27
|
-
export function readWorkspaceApiError(
|
|
28
|
-
error: unknown,
|
|
29
|
-
fallback: string,
|
|
30
|
-
): string {
|
|
31
|
-
if (error instanceof DashboardApiError) {
|
|
32
|
-
return error.apiError ?? fallback;
|
|
33
|
-
}
|
|
34
|
-
if (error instanceof Error && error.message.trim()) {
|
|
35
|
-
return error.message;
|
|
36
|
-
}
|
|
37
|
-
return fallback;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/** Manage install-wide repository Workspace recipes. */
|
|
25
|
+
/** List install-wide Workspace recipes. The URL selects one for the details panel. */
|
|
41
26
|
export function WorkspacesPage() {
|
|
42
|
-
const
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
});
|
|
49
|
-
const deleteMutation = useMutation({
|
|
50
|
-
mutationFn: async (workspace: WorkspaceReport) => {
|
|
51
|
-
await deleteDashboardResource(
|
|
52
|
-
`/api/workspaces/${encodeURIComponent(workspace.id)}`,
|
|
53
|
-
);
|
|
54
|
-
return workspace;
|
|
55
|
-
},
|
|
56
|
-
onSuccess: async (workspace) => {
|
|
57
|
-
await queryClient.cancelQueries({ queryKey: workspacesQueryKey });
|
|
58
|
-
queryClient.setQueryData<WorkspaceListReport>(
|
|
59
|
-
workspacesQueryKey,
|
|
60
|
-
(current) =>
|
|
61
|
-
current && {
|
|
62
|
-
...current,
|
|
63
|
-
workspaces: current.workspaces.filter(
|
|
64
|
-
(item) => item.id !== workspace.id,
|
|
65
|
-
),
|
|
66
|
-
},
|
|
67
|
-
);
|
|
68
|
-
},
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
if (!workspacesQuery.data && !workspacesQuery.error) {
|
|
72
|
-
return (
|
|
73
|
-
<SystemRouteLoading
|
|
74
|
-
description="Repository recipes Junior can switch into."
|
|
75
|
-
label="Loading Workspaces"
|
|
76
|
-
title="Workspaces"
|
|
77
|
-
variant="list"
|
|
78
|
-
/>
|
|
79
|
-
);
|
|
80
|
-
}
|
|
81
|
-
|
|
27
|
+
const { workspaceId } = useParams();
|
|
28
|
+
const navigate = useNavigate();
|
|
29
|
+
const listRef = useRef<HTMLDivElement>(null);
|
|
30
|
+
const deleteSource = useRef<HTMLButtonElement | null>(null);
|
|
31
|
+
const [deleteTarget, setDeleteTarget] = useState<WorkspaceReport>();
|
|
32
|
+
const workspacesQuery = useWorkspacesData();
|
|
82
33
|
const workspaces = workspacesQuery.data?.workspaces ?? [];
|
|
83
|
-
const baselineSnapshot = workspacesQuery.data?.baselineSnapshot ?? null;
|
|
84
34
|
const defaultVcpus = workspacesQuery.data?.defaultVcpus ?? null;
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
actions={
|
|
89
|
-
<ButtonLink to="/system/workspaces/new">
|
|
90
|
-
<Plus aria-hidden="true" size={14} />
|
|
91
|
-
New Workspace
|
|
92
|
-
</ButtonLink>
|
|
93
|
-
}
|
|
94
|
-
description={`Named repository recipes ${getDashboardAgentName()} can switch into without cloning each turn.`}
|
|
95
|
-
title="Workspaces"
|
|
96
|
-
/>
|
|
97
|
-
|
|
98
|
-
{workspacesQuery.error ? (
|
|
99
|
-
<Card padding="sm">
|
|
100
|
-
<EmptyTelemetry>
|
|
101
|
-
Workspaces failed to load. Try refreshing the dashboard.
|
|
102
|
-
</EmptyTelemetry>
|
|
103
|
-
</Card>
|
|
104
|
-
) : null}
|
|
35
|
+
const selected = workspaces.find((item) => item.id === workspaceId);
|
|
36
|
+
// PageHeader keeps its title after close, so this page owns the tab title.
|
|
37
|
+
useDocumentTitle(selected?.name ?? "Workspaces");
|
|
105
38
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
39
|
+
const closeDetails = () => {
|
|
40
|
+
const trigger = workspaceId
|
|
41
|
+
? listRef.current?.querySelector<HTMLButtonElement>(
|
|
42
|
+
`[data-workspace-id="${CSS.escape(workspaceId)}"] button`,
|
|
43
|
+
)
|
|
44
|
+
: undefined;
|
|
45
|
+
navigate(listPath);
|
|
46
|
+
(trigger ?? listRef.current)?.focus();
|
|
47
|
+
};
|
|
114
48
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
49
|
+
return (
|
|
50
|
+
<SystemNavigation className="grid h-full min-h-0 grid-rows-[auto_minmax(0,1fr)] md:grid-rows-[minmax(0,1fr)]">
|
|
51
|
+
<div
|
|
52
|
+
className={cn(
|
|
53
|
+
"grid h-full min-h-0 min-w-0",
|
|
54
|
+
workspaceId && "xl:grid-cols-[minmax(0,1fr)_25rem]",
|
|
55
|
+
)}
|
|
56
|
+
>
|
|
57
|
+
<div
|
|
58
|
+
aria-label="Workspaces list"
|
|
59
|
+
className="min-h-0 overflow-y-auto overscroll-contain outline-none focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-dashboard-focus"
|
|
60
|
+
ref={listRef}
|
|
61
|
+
role="region"
|
|
62
|
+
tabIndex={0}
|
|
63
|
+
>
|
|
64
|
+
<PageLayout>
|
|
65
|
+
<PageHeader
|
|
66
|
+
actions={
|
|
67
|
+
<Link className={buttonVariants()} to={`${listPath}/new`}>
|
|
68
|
+
<Plus aria-hidden="true" size={14} />
|
|
69
|
+
New Workspace
|
|
70
|
+
</Link>
|
|
71
|
+
}
|
|
72
|
+
description={`Named repository recipes ${getDashboardAgentName()} can switch into without cloning each turn.`}
|
|
73
|
+
title="Workspaces"
|
|
74
|
+
/>
|
|
75
|
+
{workspacesQuery.error ? (
|
|
76
|
+
<Card padding="sm">
|
|
77
|
+
<EmptyTelemetry>
|
|
78
|
+
Workspaces failed to load. Try refreshing the dashboard.
|
|
79
|
+
</EmptyTelemetry>
|
|
80
|
+
</Card>
|
|
81
|
+
) : workspacesQuery.data ? (
|
|
82
|
+
<>
|
|
83
|
+
<BaseSandboxCard
|
|
84
|
+
defaultVcpus={defaultVcpus}
|
|
85
|
+
snapshot={workspacesQuery.data.baselineSnapshot}
|
|
86
|
+
/>
|
|
87
|
+
<WorkspaceList
|
|
88
|
+
defaultVcpus={defaultVcpus}
|
|
89
|
+
onSelect={(workspace) =>
|
|
90
|
+
navigate(
|
|
91
|
+
workspace.id === workspaceId
|
|
92
|
+
? listPath
|
|
93
|
+
: workspacePath(workspace.id),
|
|
94
|
+
)
|
|
95
|
+
}
|
|
96
|
+
selectedId={workspaceId}
|
|
97
|
+
workspaces={workspaces}
|
|
98
|
+
/>
|
|
99
|
+
</>
|
|
100
|
+
) : (
|
|
101
|
+
<PageContentSkeleton label="Loading Workspaces" variant="list" />
|
|
102
|
+
)}
|
|
103
|
+
</PageLayout>
|
|
104
|
+
</div>
|
|
105
|
+
{workspaceId ? (
|
|
106
|
+
<WorkspaceDetails
|
|
107
|
+
defaultVcpus={defaultVcpus}
|
|
108
|
+
key={workspaceId}
|
|
109
|
+
listError={Boolean(workspacesQuery.error)}
|
|
110
|
+
onClose={closeDetails}
|
|
111
|
+
onDelete={(workspace, source) => {
|
|
112
|
+
deleteSource.current = source;
|
|
113
|
+
setDeleteTarget(workspace);
|
|
114
|
+
}}
|
|
115
|
+
workspace={selected}
|
|
116
|
+
workspaceId={workspaceId}
|
|
117
|
+
/>
|
|
118
|
+
) : null}
|
|
119
|
+
</div>
|
|
120
|
+
{deleteTarget ? (
|
|
121
|
+
<WorkspaceDeleteDialog
|
|
122
|
+
onClose={() => setDeleteTarget(undefined)}
|
|
123
|
+
onDeleted={() => {
|
|
124
|
+
if (workspaceId === deleteTarget.id) navigate(listPath);
|
|
125
|
+
}}
|
|
126
|
+
returnFocus={() =>
|
|
127
|
+
deleteSource.current?.isConnected
|
|
128
|
+
? deleteSource.current
|
|
129
|
+
: (listRef.current ?? false)
|
|
130
|
+
}
|
|
131
|
+
workspace={deleteTarget}
|
|
119
132
|
/>
|
|
120
133
|
) : null}
|
|
121
|
-
|
|
122
|
-
<WorkspaceList
|
|
123
|
-
busy={deleteMutation.isPending}
|
|
124
|
-
defaultVcpus={defaultVcpus}
|
|
125
|
-
onDelete={(workspace) => {
|
|
126
|
-
if (
|
|
127
|
-
window.confirm(
|
|
128
|
-
`Delete Workspace “${workspace.name}”? Active conversations keep their current Sandbox until the next switch.`,
|
|
129
|
-
)
|
|
130
|
-
) {
|
|
131
|
-
deleteMutation.mutate(workspace);
|
|
132
|
-
}
|
|
133
|
-
}}
|
|
134
|
-
workspaces={workspaces}
|
|
135
|
-
/>
|
|
136
|
-
</SystemPageLayout>
|
|
134
|
+
</SystemNavigation>
|
|
137
135
|
);
|
|
138
136
|
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** Vercel Sandbox gives each vCPU 2 GB of memory. */
|
|
2
|
+
const MEMORY_GB_PER_VCPU = 2;
|
|
3
|
+
|
|
4
|
+
/** Format a Sandbox vCPU count with its memory. Null means the Vercel default. */
|
|
5
|
+
export function formatSandboxSize(vcpus: number | null): string {
|
|
6
|
+
if (vcpus === null) return "Vercel default";
|
|
7
|
+
return `${vcpus} vCPU${vcpus === 1 ? "" : "s"} · ${vcpus * MEMORY_GB_PER_VCPU} GB`;
|
|
8
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { useQuery } from "@tanstack/react-query";
|
|
2
|
+
import {
|
|
3
|
+
workspaceListSchema,
|
|
4
|
+
workspaceSchema,
|
|
5
|
+
} from "@sentry/junior/api/schema";
|
|
6
|
+
|
|
7
|
+
import { DashboardApiError, fetchDashboardJson } from "../../http";
|
|
8
|
+
|
|
9
|
+
export const workspacesQueryKey = ["dashboard", "workspaces"] as const;
|
|
10
|
+
|
|
11
|
+
/** Cache key for one Workspace detail read. */
|
|
12
|
+
export function workspaceQueryKey(workspaceId: string | undefined) {
|
|
13
|
+
return ["dashboard", "workspace", workspaceId] as const;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Return a useful dashboard error message without exposing response details. */
|
|
17
|
+
export function readWorkspaceApiError(
|
|
18
|
+
error: unknown,
|
|
19
|
+
fallback: string,
|
|
20
|
+
): string {
|
|
21
|
+
if (error instanceof DashboardApiError) {
|
|
22
|
+
return error.apiError ?? fallback;
|
|
23
|
+
}
|
|
24
|
+
if (error instanceof Error && error.message.trim()) {
|
|
25
|
+
return error.message;
|
|
26
|
+
}
|
|
27
|
+
return fallback;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Load Workspace recipes, the default Sandbox size, and the baseline snapshot. */
|
|
31
|
+
export function useWorkspacesData() {
|
|
32
|
+
return useQuery({
|
|
33
|
+
queryKey: workspacesQueryKey,
|
|
34
|
+
queryFn: ({ signal }) =>
|
|
35
|
+
fetchDashboardJson(workspaceListSchema, "/api/workspaces", signal),
|
|
36
|
+
retry: false,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Load one Workspace recipe with its current snapshot, which the list omits. */
|
|
41
|
+
export function useWorkspaceData(workspaceId: string | undefined) {
|
|
42
|
+
return useQuery({
|
|
43
|
+
enabled: workspaceId !== undefined,
|
|
44
|
+
queryKey: workspaceQueryKey(workspaceId),
|
|
45
|
+
queryFn: ({ signal }) =>
|
|
46
|
+
fetchDashboardJson(
|
|
47
|
+
workspaceSchema,
|
|
48
|
+
`/api/workspaces/${encodeURIComponent(workspaceId!)}`,
|
|
49
|
+
signal,
|
|
50
|
+
),
|
|
51
|
+
retry: false,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
@@ -1,8 +1,18 @@
|
|
|
1
1
|
import { Boxes, Trash2 } from "lucide-react";
|
|
2
|
+
import { useState } from "react";
|
|
2
3
|
import { Navigate, useLocation, useParams } from "react-router";
|
|
3
4
|
import type { PluginUserPageLink } from "@sentry/junior-plugin-api";
|
|
4
5
|
|
|
5
6
|
import { InlineError } from "../../components/InlineError";
|
|
7
|
+
import {
|
|
8
|
+
AlertDialog,
|
|
9
|
+
AlertDialogAction,
|
|
10
|
+
AlertDialogCancel,
|
|
11
|
+
AlertDialogContent,
|
|
12
|
+
AlertDialogDescription,
|
|
13
|
+
AlertDialogFooter,
|
|
14
|
+
AlertDialogTitle,
|
|
15
|
+
} from "../../components/ui/alert-dialog";
|
|
6
16
|
import { LoadingView } from "../../components/LoadingView";
|
|
7
17
|
import { LoadMorePagination } from "../../components/Pagination";
|
|
8
18
|
import { SearchInput } from "../../components/SearchInput";
|
|
@@ -11,7 +21,11 @@ import { PageHeader } from "../../components/layout/PageHeader";
|
|
|
11
21
|
import { MemoryPage } from "../memory/MemoryPage";
|
|
12
22
|
import { pathWithSearch } from "../../searchParams";
|
|
13
23
|
import { dashboardContainerClass } from "../../styles";
|
|
14
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
usePluginUserPageData,
|
|
26
|
+
type PluginUserPageRecord,
|
|
27
|
+
type PluginUserPageRecordAction,
|
|
28
|
+
} from "./pluginUserPageData";
|
|
15
29
|
|
|
16
30
|
/** Build the canonical dashboard path for a plugin-owned page. */
|
|
17
31
|
export function pluginUserPagePath(pluginName: string, pageId: string): string {
|
|
@@ -68,6 +82,12 @@ export function PluginUserPage(props: { page: PluginUserPageLink }) {
|
|
|
68
82
|
searchText,
|
|
69
83
|
setSearchText,
|
|
70
84
|
} = usePluginUserPageData(props.page);
|
|
85
|
+
// Keep the action after close so the closing dialog keeps its text.
|
|
86
|
+
const [confirming, setConfirming] = useState<{
|
|
87
|
+
action: PluginUserPageRecordAction;
|
|
88
|
+
record: PluginUserPageRecord;
|
|
89
|
+
}>();
|
|
90
|
+
const [confirmOpen, setConfirmOpen] = useState(false);
|
|
71
91
|
|
|
72
92
|
if (!query.data && !query.error) {
|
|
73
93
|
return <LoadingView label={`Loading ${props.page.label}`} />;
|
|
@@ -158,7 +178,14 @@ export function PluginUserPage(props: { page: PluginUserPageLink }) {
|
|
|
158
178
|
}`}
|
|
159
179
|
disabled={action.isPending}
|
|
160
180
|
key={`${recordAction.method}:${recordAction.href}`}
|
|
161
|
-
onClick={() =>
|
|
181
|
+
onClick={() => {
|
|
182
|
+
if (!recordAction.confirmation) {
|
|
183
|
+
runAction(recordAction);
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
setConfirming({ action: recordAction, record });
|
|
187
|
+
setConfirmOpen(true);
|
|
188
|
+
}}
|
|
162
189
|
title={recordAction.label}
|
|
163
190
|
type="button"
|
|
164
191
|
>
|
|
@@ -205,6 +232,28 @@ export function PluginUserPage(props: { page: PluginUserPageLink }) {
|
|
|
205
232
|
</div>
|
|
206
233
|
)}
|
|
207
234
|
</section>
|
|
235
|
+
<AlertDialog onOpenChange={setConfirmOpen} open={confirmOpen}>
|
|
236
|
+
<AlertDialogContent>
|
|
237
|
+
<AlertDialogTitle>{confirming?.action.confirmation}</AlertDialogTitle>
|
|
238
|
+
<AlertDialogDescription>
|
|
239
|
+
{confirming?.record.title}
|
|
240
|
+
</AlertDialogDescription>
|
|
241
|
+
<AlertDialogFooter>
|
|
242
|
+
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
|
243
|
+
<AlertDialogAction
|
|
244
|
+
onClick={() => {
|
|
245
|
+
if (confirming) runAction(confirming.action);
|
|
246
|
+
setConfirmOpen(false);
|
|
247
|
+
}}
|
|
248
|
+
variant={
|
|
249
|
+
confirming?.action.tone === "danger" ? "danger" : "primary"
|
|
250
|
+
}
|
|
251
|
+
>
|
|
252
|
+
{confirming?.action.label}
|
|
253
|
+
</AlertDialogAction>
|
|
254
|
+
</AlertDialogFooter>
|
|
255
|
+
</AlertDialogContent>
|
|
256
|
+
</AlertDialog>
|
|
208
257
|
</div>
|
|
209
258
|
);
|
|
210
259
|
}
|
|
@@ -87,14 +87,9 @@ export function usePluginUserPageData(page: PluginUserPageLink) {
|
|
|
87
87
|
},
|
|
88
88
|
});
|
|
89
89
|
|
|
90
|
+
// Callers ask for the action's confirmation before they run it.
|
|
90
91
|
function runAction(recordAction: PluginUserPageRecordAction) {
|
|
91
92
|
if (actionStarted.current) return;
|
|
92
|
-
if (
|
|
93
|
-
recordAction.confirmation &&
|
|
94
|
-
!window.confirm(recordAction.confirmation)
|
|
95
|
-
) {
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
93
|
actionStarted.current = true;
|
|
99
94
|
action.mutate(recordAction);
|
|
100
95
|
}
|
package/src/client/styles.ts
CHANGED
|
@@ -17,3 +17,7 @@ export const dashboardInteractiveTextClass =
|
|
|
17
17
|
/** Raised input surface so the chat box reads against the near-black shell. */
|
|
18
18
|
export const dashboardComposerSurfaceClass =
|
|
19
19
|
"rounded-xl border border-dashboard-border-emphasis bg-dashboard-surface-raised shadow-[0_8px_32px_rgba(0,0,0,0.35)] transition-colors";
|
|
20
|
+
|
|
21
|
+
/** Turn a disclosure chevron when its `group` trigger sets `aria-expanded`. */
|
|
22
|
+
export const dashboardDisclosureChevronClass =
|
|
23
|
+
"size-3.5 shrink-0 transition-transform group-aria-expanded:rotate-90 motion-reduce:transition-none";
|
package/src/client.tsx
CHANGED
|
@@ -3,6 +3,8 @@ import { Component, type ErrorInfo, type ReactNode } from "react";
|
|
|
3
3
|
import { createRoot } from "react-dom/client";
|
|
4
4
|
import { createBrowserRouter, RouterProvider } from "react-router";
|
|
5
5
|
|
|
6
|
+
import { Toaster } from "./client/components/ui/toast";
|
|
7
|
+
import { TooltipProvider } from "./client/components/ui/tooltip";
|
|
6
8
|
import { DashboardShell } from "./client/App";
|
|
7
9
|
import { getDashboardAgentName } from "./client/agentName";
|
|
8
10
|
|
|
@@ -87,7 +89,11 @@ const router = createBrowserRouter(
|
|
|
87
89
|
createRoot(root).render(
|
|
88
90
|
<DashboardErrorBoundary>
|
|
89
91
|
<QueryClientProvider client={queryClient}>
|
|
90
|
-
<
|
|
92
|
+
<Toaster>
|
|
93
|
+
<TooltipProvider>
|
|
94
|
+
<RouterProvider router={router} />
|
|
95
|
+
</TooltipProvider>
|
|
96
|
+
</Toaster>
|
|
91
97
|
</QueryClientProvider>
|
|
92
98
|
</DashboardErrorBoundary>,
|
|
93
99
|
);
|
|
@@ -33,13 +33,15 @@ export function mockAutomationCollection(
|
|
|
33
33
|
(automation.status !== "paused" &&
|
|
34
34
|
automation.status !== "completed" &&
|
|
35
35
|
(automation.status === "blocked" ||
|
|
36
|
-
(automation.kind === "event" &&
|
|
36
|
+
(automation.kind === "event" &&
|
|
37
|
+
automation.trigger.problems.length > 0) ||
|
|
37
38
|
automation.lastRunStatus === "failed" ||
|
|
38
39
|
automation.lastRunStatus === "blocked"))) &&
|
|
39
40
|
(input.type === "all" || automation.kind === input.type) &&
|
|
40
41
|
(input.state === "all" ||
|
|
41
42
|
(input.state === "unavailable"
|
|
42
|
-
? automation.kind === "event" &&
|
|
43
|
+
? automation.kind === "event" &&
|
|
44
|
+
automation.trigger.problems.length > 0
|
|
43
45
|
: automation.status === input.state)) &&
|
|
44
46
|
(!input.creator || creator(automation) === input.creator) &&
|
|
45
47
|
(!input.destination || destination(automation) === input.destination) &&
|
|
@@ -2972,7 +2972,6 @@ function mockTasks(): AutomationSummary[] {
|
|
|
2972
2972
|
teamId: "T123",
|
|
2973
2973
|
visibility: "public",
|
|
2974
2974
|
},
|
|
2975
|
-
events: ["issue.closed"],
|
|
2976
2975
|
id: "event-1",
|
|
2977
2976
|
instruction: "Summarize the closed issue",
|
|
2978
2977
|
kind: "event",
|
|
@@ -2986,7 +2985,11 @@ function mockTasks(): AutomationSummary[] {
|
|
|
2986
2985
|
outcomes: [],
|
|
2987
2986
|
title: "Closed issue summary",
|
|
2988
2987
|
totalRuns: 7,
|
|
2989
|
-
|
|
2988
|
+
trigger: {
|
|
2989
|
+
events: ["issue closed"],
|
|
2990
|
+
filters: ["labels includes bug", "repository is api or web"],
|
|
2991
|
+
problems: [],
|
|
2992
|
+
},
|
|
2990
2993
|
status: "active",
|
|
2991
2994
|
},
|
|
2992
2995
|
{
|
|
@@ -2999,7 +3002,6 @@ function mockTasks(): AutomationSummary[] {
|
|
|
2999
3002
|
teamId: "T123",
|
|
3000
3003
|
visibility: "public",
|
|
3001
3004
|
},
|
|
3002
|
-
events: ["incident.updated"],
|
|
3003
3005
|
id: "event-2",
|
|
3004
3006
|
instruction: "Notify responders when the incident changes",
|
|
3005
3007
|
kind: "event",
|
|
@@ -3016,7 +3018,11 @@ function mockTasks(): AutomationSummary[] {
|
|
|
3016
3018
|
],
|
|
3017
3019
|
title: "Incident change alerts",
|
|
3018
3020
|
totalRuns: 0,
|
|
3019
|
-
|
|
3021
|
+
trigger: {
|
|
3022
|
+
events: ["incident updated"],
|
|
3023
|
+
filters: [],
|
|
3024
|
+
problems: ["The plugin for this source is not enabled."],
|
|
3025
|
+
},
|
|
3020
3026
|
status: "active",
|
|
3021
3027
|
},
|
|
3022
3028
|
];
|
|
@@ -3115,7 +3121,19 @@ export function readMockAutomationExecutions(
|
|
|
3115
3121
|
const costUsd = costs[index % costs.length];
|
|
3116
3122
|
const durationMs = durations[index % durations.length];
|
|
3117
3123
|
const totalTokens = tokens[index % tokens.length];
|
|
3124
|
+
const executedAtMs = nowMs - index * 86_400_000 - 3_600_000;
|
|
3118
3125
|
return {
|
|
3126
|
+
automationVersion: index < 3 ? 2 : 1,
|
|
3127
|
+
credentialMode: task.credentialMode,
|
|
3128
|
+
...(status === "failed"
|
|
3129
|
+
? { error: "Slack rejected the result message: channel_not_found." }
|
|
3130
|
+
: status === "blocked"
|
|
3131
|
+
? { error: "GitHub needs the creator to reconnect their account." }
|
|
3132
|
+
: undefined),
|
|
3133
|
+
...(task.kind === "scheduled"
|
|
3134
|
+
? { scheduledFor: new Date(executedAtMs - 60_000).toISOString() }
|
|
3135
|
+
: undefined),
|
|
3136
|
+
startedAt: new Date(executedAtMs - (durationMs ?? 5_000)).toISOString(),
|
|
3119
3137
|
...(hasConversation
|
|
3120
3138
|
? {
|
|
3121
3139
|
conversationId:
|
|
@@ -3128,9 +3146,7 @@ export function readMockAutomationExecutions(
|
|
|
3128
3146
|
...(totalTokens !== undefined ? { totalTokens } : undefined),
|
|
3129
3147
|
}
|
|
3130
3148
|
: undefined),
|
|
3131
|
-
executedAt: new Date(
|
|
3132
|
-
nowMs - index * 86_400_000 - 3_600_000,
|
|
3133
|
-
).toISOString(),
|
|
3149
|
+
executedAt: new Date(executedAtMs).toISOString(),
|
|
3134
3150
|
executionId: `${id}-run-${index + 1}`,
|
|
3135
3151
|
status,
|
|
3136
3152
|
};
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
conversationParamsSchema,
|
|
18
18
|
conversationPendingMessagesReportSchema,
|
|
19
19
|
conversationStatsReportSchema,
|
|
20
|
+
conversationWatchesReportSchema,
|
|
20
21
|
codeOverviewReportSchema,
|
|
21
22
|
codePersonReportSchema,
|
|
22
23
|
codeRepositoryParamsSchema,
|
|
@@ -58,6 +59,7 @@ import {
|
|
|
58
59
|
readMockAutomationList,
|
|
59
60
|
setMockConversationArchived,
|
|
60
61
|
} from "./fixtures";
|
|
62
|
+
import { readMockConversationWatches } from "./watches";
|
|
61
63
|
|
|
62
64
|
function errorResponse(error: string, status: 400 | 404): Response {
|
|
63
65
|
return jsonResponse(apiErrorSchema, { error }, { status });
|
|
@@ -205,6 +207,16 @@ export function createMockReportingApi(): Hono<{
|
|
|
205
207
|
? jsonResponse(conversationChildAgentsReportSchema, report)
|
|
206
208
|
: errorResponse("Conversation not found.", 404);
|
|
207
209
|
});
|
|
210
|
+
app.get("/conversations/:conversationId/watches", (c) => {
|
|
211
|
+
const params = conversationParamsSchema.safeParse(c.req.param());
|
|
212
|
+
if (!params.success) {
|
|
213
|
+
return errorResponse("Invalid route parameters.", 400);
|
|
214
|
+
}
|
|
215
|
+
const report = readMockConversationWatches(params.data.conversationId);
|
|
216
|
+
return report
|
|
217
|
+
? jsonResponse(conversationWatchesReportSchema, report)
|
|
218
|
+
: errorResponse("Conversation not found.", 404);
|
|
219
|
+
});
|
|
208
220
|
app.get("/conversations/:conversationId/pending-messages", (c) => {
|
|
209
221
|
const params = conversationParamsSchema.safeParse(c.req.param());
|
|
210
222
|
if (!params.success) {
|