@agent-native/dispatch 0.3.0 → 0.5.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/actions/archive-workspace-app.d.ts +3 -0
- package/dist/actions/archive-workspace-app.d.ts.map +1 -0
- package/dist/actions/archive-workspace-app.js +15 -0
- package/dist/actions/archive-workspace-app.js.map +1 -0
- package/dist/actions/index.d.ts.map +1 -1
- package/dist/actions/index.js +10 -0
- package/dist/actions/index.js.map +1 -1
- package/dist/actions/list-available-workspace-templates.d.ts +3 -0
- package/dist/actions/list-available-workspace-templates.d.ts.map +1 -0
- package/dist/actions/list-available-workspace-templates.js +10 -0
- package/dist/actions/list-available-workspace-templates.js.map +1 -0
- package/dist/actions/remove-pending-workspace-app.d.ts +3 -0
- package/dist/actions/remove-pending-workspace-app.d.ts.map +1 -0
- package/dist/actions/remove-pending-workspace-app.js +15 -0
- package/dist/actions/remove-pending-workspace-app.js.map +1 -0
- package/dist/actions/scaffold-workspace-app.d.ts +3 -0
- package/dist/actions/scaffold-workspace-app.d.ts.map +1 -0
- package/dist/actions/scaffold-workspace-app.js +27 -0
- package/dist/actions/scaffold-workspace-app.js.map +1 -0
- package/dist/actions/unarchive-workspace-app.d.ts +3 -0
- package/dist/actions/unarchive-workspace-app.d.ts.map +1 -0
- package/dist/actions/unarchive-workspace-app.js +15 -0
- package/dist/actions/unarchive-workspace-app.js.map +1 -0
- package/dist/components/workspace-app-card.d.ts.map +1 -1
- package/dist/components/workspace-app-card.js +33 -2
- package/dist/components/workspace-app-card.js.map +1 -1
- package/dist/lib/workspace-apps.d.ts +1 -0
- package/dist/lib/workspace-apps.d.ts.map +1 -1
- package/dist/lib/workspace-apps.js.map +1 -1
- package/dist/routes/pages/apps.d.ts.map +1 -1
- package/dist/routes/pages/apps.js +41 -8
- package/dist/routes/pages/apps.js.map +1 -1
- package/dist/server/lib/app-creation-store.d.ts +40 -0
- package/dist/server/lib/app-creation-store.d.ts.map +1 -1
- package/dist/server/lib/app-creation-store.js +245 -6
- package/dist/server/lib/app-creation-store.js.map +1 -1
- package/dist/server/lib/thread-debug-store.d.ts +2 -2
- package/package.json +2 -2
- package/src/actions/archive-workspace-app.ts +16 -0
- package/src/actions/index.ts +10 -0
- package/src/actions/list-available-workspace-templates.ts +11 -0
- package/src/actions/remove-pending-workspace-app.ts +16 -0
- package/src/actions/scaffold-workspace-app.ts +34 -0
- package/src/actions/unarchive-workspace-app.ts +16 -0
- package/src/components/workspace-app-card.tsx +95 -5
- package/src/lib/workspace-apps.ts +1 -0
- package/src/routes/pages/apps.tsx +159 -6
- package/src/server/lib/app-creation-store.ts +312 -15
|
@@ -1,6 +1,21 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useActionMutation } from "@agent-native/core/client";
|
|
2
|
+
import {
|
|
3
|
+
IconArrowUpRight,
|
|
4
|
+
IconClockHour4,
|
|
5
|
+
IconDots,
|
|
6
|
+
IconEye,
|
|
7
|
+
IconEyeOff,
|
|
8
|
+
IconTrash,
|
|
9
|
+
} from "@tabler/icons-react";
|
|
10
|
+
import { toast } from "sonner";
|
|
2
11
|
import { AppKeysPopover } from "@/components/app-keys-popover";
|
|
3
12
|
import { Badge } from "@/components/ui/badge";
|
|
13
|
+
import {
|
|
14
|
+
DropdownMenu,
|
|
15
|
+
DropdownMenuContent,
|
|
16
|
+
DropdownMenuItem,
|
|
17
|
+
DropdownMenuTrigger,
|
|
18
|
+
} from "@/components/ui/dropdown-menu";
|
|
4
19
|
import { cn } from "@/lib/utils";
|
|
5
20
|
import {
|
|
6
21
|
isPendingBuilderHref,
|
|
@@ -17,12 +32,41 @@ export function WorkspaceAppCard({
|
|
|
17
32
|
}) {
|
|
18
33
|
const href = workspaceAppHref(app);
|
|
19
34
|
const openInNewTab = isPendingBuilderHref(app);
|
|
35
|
+
const isPending = app.status === "pending";
|
|
36
|
+
const isArchived = !!app.archived;
|
|
37
|
+
|
|
38
|
+
const archive = useActionMutation("archive-workspace-app", {
|
|
39
|
+
onError: (err) =>
|
|
40
|
+
toast.error(`Could not hide ${app.name}: ${stringifyError(err)}`),
|
|
41
|
+
});
|
|
42
|
+
const unarchive = useActionMutation("unarchive-workspace-app", {
|
|
43
|
+
onError: (err) =>
|
|
44
|
+
toast.error(`Could not restore ${app.name}: ${stringifyError(err)}`),
|
|
45
|
+
});
|
|
46
|
+
const removePending = useActionMutation("remove-pending-workspace-app", {
|
|
47
|
+
onError: (err) =>
|
|
48
|
+
toast.error(`Could not remove ${app.name}: ${stringifyError(err)}`),
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const handleArchive = () => {
|
|
52
|
+
archive.mutate({ appId: app.id });
|
|
53
|
+
toast.success(`Hid ${app.name} from the Apps list`);
|
|
54
|
+
};
|
|
55
|
+
const handleUnarchive = () => {
|
|
56
|
+
unarchive.mutate({ appId: app.id });
|
|
57
|
+
toast.success(`Restored ${app.name} to the Apps list`);
|
|
58
|
+
};
|
|
59
|
+
const handleRemovePending = () => {
|
|
60
|
+
removePending.mutate({ appId: app.id });
|
|
61
|
+
toast.success(`Removed pending ${app.name}`);
|
|
62
|
+
};
|
|
20
63
|
|
|
21
64
|
return (
|
|
22
65
|
<div
|
|
23
66
|
aria-disabled={!href}
|
|
24
67
|
className={cn(
|
|
25
68
|
"group relative rounded-lg border bg-card p-4 transition hover:border-foreground/30 aria-disabled:opacity-60",
|
|
69
|
+
isArchived && "opacity-70",
|
|
26
70
|
className,
|
|
27
71
|
)}
|
|
28
72
|
>
|
|
@@ -42,7 +86,7 @@ export function WorkspaceAppCard({
|
|
|
42
86
|
<h3 className="truncate text-sm font-semibold text-foreground">
|
|
43
87
|
{app.name}
|
|
44
88
|
</h3>
|
|
45
|
-
{
|
|
89
|
+
{isPending ? (
|
|
46
90
|
<Badge
|
|
47
91
|
variant="outline"
|
|
48
92
|
className="shrink-0 gap-1 border-amber-500/30 bg-amber-500/10 text-amber-700 dark:text-amber-300"
|
|
@@ -51,11 +95,17 @@ export function WorkspaceAppCard({
|
|
|
51
95
|
Building
|
|
52
96
|
</Badge>
|
|
53
97
|
) : null}
|
|
98
|
+
{isArchived ? (
|
|
99
|
+
<Badge variant="outline" className="shrink-0 gap-1">
|
|
100
|
+
<IconEyeOff size={12} />
|
|
101
|
+
Hidden
|
|
102
|
+
</Badge>
|
|
103
|
+
) : null}
|
|
54
104
|
</div>
|
|
55
105
|
<p className="mt-1 truncate font-mono text-xs text-muted-foreground">
|
|
56
106
|
{app.path}
|
|
57
107
|
</p>
|
|
58
|
-
{
|
|
108
|
+
{isPending && app.branchName ? (
|
|
59
109
|
<p className="mt-1 truncate text-xs text-muted-foreground">
|
|
60
110
|
Branch: {app.branchName}
|
|
61
111
|
</p>
|
|
@@ -67,12 +117,47 @@ export function WorkspaceAppCard({
|
|
|
67
117
|
) : null}
|
|
68
118
|
</div>
|
|
69
119
|
<div className="flex shrink-0 items-center gap-1">
|
|
70
|
-
{
|
|
120
|
+
{!isPending && !isArchived ? (
|
|
71
121
|
<div className="pointer-events-auto">
|
|
72
122
|
<AppKeysPopover appId={app.id} appName={app.name} />
|
|
73
123
|
</div>
|
|
74
124
|
) : null}
|
|
75
|
-
|
|
125
|
+
<div className="pointer-events-auto">
|
|
126
|
+
<DropdownMenu>
|
|
127
|
+
<DropdownMenuTrigger asChild>
|
|
128
|
+
<button
|
|
129
|
+
type="button"
|
|
130
|
+
aria-label={`More actions for ${app.name}`}
|
|
131
|
+
className="inline-flex h-7 w-7 cursor-pointer items-center justify-center rounded-md text-muted-foreground opacity-0 transition hover:bg-accent hover:text-foreground focus-visible:opacity-100 group-hover:opacity-100 data-[state=open]:opacity-100"
|
|
132
|
+
onClick={(e) => e.stopPropagation()}
|
|
133
|
+
>
|
|
134
|
+
<IconDots size={15} />
|
|
135
|
+
</button>
|
|
136
|
+
</DropdownMenuTrigger>
|
|
137
|
+
<DropdownMenuContent align="end" className="w-44">
|
|
138
|
+
{isPending ? (
|
|
139
|
+
<DropdownMenuItem
|
|
140
|
+
onSelect={handleRemovePending}
|
|
141
|
+
className="text-red-600 focus:text-red-600 dark:text-red-400 dark:focus:text-red-400"
|
|
142
|
+
>
|
|
143
|
+
<IconTrash size={14} className="mr-2" />
|
|
144
|
+
Remove from list
|
|
145
|
+
</DropdownMenuItem>
|
|
146
|
+
) : isArchived ? (
|
|
147
|
+
<DropdownMenuItem onSelect={handleUnarchive}>
|
|
148
|
+
<IconEye size={14} className="mr-2" />
|
|
149
|
+
Restore to list
|
|
150
|
+
</DropdownMenuItem>
|
|
151
|
+
) : (
|
|
152
|
+
<DropdownMenuItem onSelect={handleArchive}>
|
|
153
|
+
<IconEyeOff size={14} className="mr-2" />
|
|
154
|
+
Hide from list
|
|
155
|
+
</DropdownMenuItem>
|
|
156
|
+
)}
|
|
157
|
+
</DropdownMenuContent>
|
|
158
|
+
</DropdownMenu>
|
|
159
|
+
</div>
|
|
160
|
+
{href && !isArchived ? (
|
|
76
161
|
<IconArrowUpRight
|
|
77
162
|
size={16}
|
|
78
163
|
className="text-muted-foreground transition group-hover:text-foreground"
|
|
@@ -83,3 +168,8 @@ export function WorkspaceAppCard({
|
|
|
83
168
|
</div>
|
|
84
169
|
);
|
|
85
170
|
}
|
|
171
|
+
|
|
172
|
+
function stringifyError(err: unknown): string {
|
|
173
|
+
if (err instanceof Error) return err.message;
|
|
174
|
+
return String(err);
|
|
175
|
+
}
|
|
@@ -1,5 +1,23 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import { useActionMutation, useActionQuery } from "@agent-native/core/client";
|
|
3
|
+
import {
|
|
4
|
+
IconApps,
|
|
5
|
+
IconBrush,
|
|
6
|
+
IconCalendarMonth,
|
|
7
|
+
IconChartBar,
|
|
8
|
+
IconClipboardList,
|
|
9
|
+
IconEyeOff,
|
|
10
|
+
IconFileText,
|
|
11
|
+
IconLoader2,
|
|
12
|
+
IconMail,
|
|
13
|
+
IconPlus,
|
|
14
|
+
IconPresentation,
|
|
15
|
+
IconScreenShare,
|
|
16
|
+
IconSparkles,
|
|
17
|
+
IconStack3,
|
|
18
|
+
IconVideo,
|
|
19
|
+
} from "@tabler/icons-react";
|
|
20
|
+
import { toast } from "sonner";
|
|
3
21
|
import { CreateAppPopover } from "@/components/create-app-popover";
|
|
4
22
|
import { DispatchShell } from "@/components/dispatch-shell";
|
|
5
23
|
import { WorkspaceAppCard } from "@/components/workspace-app-card";
|
|
@@ -16,10 +34,33 @@ interface WorkspaceInfo {
|
|
|
16
34
|
appCount: number;
|
|
17
35
|
}
|
|
18
36
|
|
|
37
|
+
interface AvailableTemplate {
|
|
38
|
+
name: string;
|
|
39
|
+
label: string;
|
|
40
|
+
hint: string;
|
|
41
|
+
icon: string;
|
|
42
|
+
color: string;
|
|
43
|
+
colorRgb: string;
|
|
44
|
+
core: boolean;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const TEMPLATE_ICONS: Record<string, typeof IconMail> = {
|
|
48
|
+
Mail: IconMail,
|
|
49
|
+
CalendarMonth: IconCalendarMonth,
|
|
50
|
+
FileText: IconFileText,
|
|
51
|
+
Presentation: IconPresentation,
|
|
52
|
+
ScreenShare: IconScreenShare,
|
|
53
|
+
ChartBar: IconChartBar,
|
|
54
|
+
ClipboardList: IconClipboardList,
|
|
55
|
+
Brush: IconBrush,
|
|
56
|
+
Video: IconVideo,
|
|
57
|
+
};
|
|
58
|
+
|
|
19
59
|
export default function AppsRoute() {
|
|
60
|
+
const [showHidden, setShowHidden] = useState(false);
|
|
20
61
|
const { data: apps = [] } = useActionQuery(
|
|
21
62
|
"list-workspace-apps",
|
|
22
|
-
{ includeAgentCards: false },
|
|
63
|
+
{ includeAgentCards: false, includeArchived: true },
|
|
23
64
|
{
|
|
24
65
|
refetchInterval: 2_000,
|
|
25
66
|
},
|
|
@@ -29,11 +70,20 @@ export default function AppsRoute() {
|
|
|
29
70
|
{},
|
|
30
71
|
{ staleTime: 60_000 },
|
|
31
72
|
);
|
|
73
|
+
const { data: templates = [] } = useActionQuery(
|
|
74
|
+
"list-available-workspace-templates",
|
|
75
|
+
{},
|
|
76
|
+
{ refetchInterval: 5_000 },
|
|
77
|
+
);
|
|
78
|
+
|
|
32
79
|
const ws = workspace as WorkspaceInfo | undefined;
|
|
33
80
|
const workspaceLabel = ws?.displayName ?? ws?.name ?? null;
|
|
34
|
-
const
|
|
81
|
+
const allApps = (apps as WorkspaceAppSummary[]).filter(
|
|
35
82
|
(app) => !app.isDispatch,
|
|
36
83
|
);
|
|
84
|
+
const visibleApps = allApps.filter((app) => !app.archived);
|
|
85
|
+
const archivedApps = allApps.filter((app) => app.archived);
|
|
86
|
+
const typedTemplates = templates as AvailableTemplate[];
|
|
37
87
|
|
|
38
88
|
return (
|
|
39
89
|
<DispatchShell
|
|
@@ -44,7 +94,7 @@ export default function AppsRoute() {
|
|
|
44
94
|
: "Open workspace apps and start new app creation from Dispatch."
|
|
45
95
|
}
|
|
46
96
|
>
|
|
47
|
-
<div className="space-y-
|
|
97
|
+
<div className="space-y-6">
|
|
48
98
|
<section className="space-y-3">
|
|
49
99
|
<div className="flex items-center justify-between gap-3">
|
|
50
100
|
<div className="flex items-center gap-2">
|
|
@@ -67,14 +117,117 @@ export default function AppsRoute() {
|
|
|
67
117
|
</div>
|
|
68
118
|
|
|
69
119
|
<div className="grid gap-3 md:grid-cols-2 xl:grid-cols-3">
|
|
70
|
-
{
|
|
120
|
+
{visibleApps.map((app) => (
|
|
71
121
|
<WorkspaceAppCard key={app.id} app={app} />
|
|
72
122
|
))}
|
|
73
123
|
|
|
74
124
|
<CreateAppPopover />
|
|
75
125
|
</div>
|
|
76
126
|
</section>
|
|
127
|
+
|
|
128
|
+
{typedTemplates.length > 0 ? (
|
|
129
|
+
<section className="space-y-3">
|
|
130
|
+
<div className="flex items-center gap-2">
|
|
131
|
+
<IconStack3 size={16} className="text-muted-foreground" />
|
|
132
|
+
<h2 className="text-sm font-semibold text-foreground">
|
|
133
|
+
Add a template
|
|
134
|
+
</h2>
|
|
135
|
+
<span className="text-xs text-muted-foreground">
|
|
136
|
+
Scaffold a first-party app into{" "}
|
|
137
|
+
<code className="font-mono text-[11px]">apps/</code>.
|
|
138
|
+
</span>
|
|
139
|
+
</div>
|
|
140
|
+
<div className="grid gap-3 md:grid-cols-2 xl:grid-cols-3">
|
|
141
|
+
{typedTemplates.map((template) => (
|
|
142
|
+
<AddTemplateCard key={template.name} template={template} />
|
|
143
|
+
))}
|
|
144
|
+
</div>
|
|
145
|
+
</section>
|
|
146
|
+
) : null}
|
|
147
|
+
|
|
148
|
+
{archivedApps.length > 0 ? (
|
|
149
|
+
<section className="space-y-3">
|
|
150
|
+
<button
|
|
151
|
+
type="button"
|
|
152
|
+
onClick={() => setShowHidden((cur) => !cur)}
|
|
153
|
+
className="inline-flex cursor-pointer items-center gap-2 text-xs text-muted-foreground hover:text-foreground"
|
|
154
|
+
>
|
|
155
|
+
<IconEyeOff size={14} />
|
|
156
|
+
{showHidden ? "Hide" : "Show"} {archivedApps.length} hidden{" "}
|
|
157
|
+
{archivedApps.length === 1 ? "app" : "apps"}
|
|
158
|
+
</button>
|
|
159
|
+
{showHidden ? (
|
|
160
|
+
<div className="grid gap-3 md:grid-cols-2 xl:grid-cols-3">
|
|
161
|
+
{archivedApps.map((app) => (
|
|
162
|
+
<WorkspaceAppCard key={app.id} app={app} />
|
|
163
|
+
))}
|
|
164
|
+
</div>
|
|
165
|
+
) : null}
|
|
166
|
+
</section>
|
|
167
|
+
) : null}
|
|
77
168
|
</div>
|
|
78
169
|
</DispatchShell>
|
|
79
170
|
);
|
|
80
171
|
}
|
|
172
|
+
|
|
173
|
+
function AddTemplateCard({ template }: { template: AvailableTemplate }) {
|
|
174
|
+
const Icon = TEMPLATE_ICONS[template.icon] ?? IconSparkles;
|
|
175
|
+
const scaffold = useActionMutation("scaffold-workspace-app", {
|
|
176
|
+
onSuccess: (result: any) => {
|
|
177
|
+
toast.success(
|
|
178
|
+
`Scaffolded apps/${result?.appId || template.name}. The gateway will pick it up shortly.`,
|
|
179
|
+
);
|
|
180
|
+
},
|
|
181
|
+
onError: (err) => {
|
|
182
|
+
toast.error(
|
|
183
|
+
`Could not scaffold ${template.label}: ${
|
|
184
|
+
err instanceof Error ? err.message : String(err)
|
|
185
|
+
}`,
|
|
186
|
+
);
|
|
187
|
+
},
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
return (
|
|
191
|
+
<div className="group relative flex items-start gap-3 rounded-lg border bg-card p-4 transition hover:border-foreground/30">
|
|
192
|
+
<div
|
|
193
|
+
className="flex h-9 w-9 shrink-0 items-center justify-center rounded-md"
|
|
194
|
+
style={{
|
|
195
|
+
backgroundColor: `rgb(${template.colorRgb} / 0.12)`,
|
|
196
|
+
color: template.color,
|
|
197
|
+
}}
|
|
198
|
+
>
|
|
199
|
+
<Icon size={18} />
|
|
200
|
+
</div>
|
|
201
|
+
<div className="min-w-0 flex-1">
|
|
202
|
+
<div className="flex min-w-0 items-center gap-2">
|
|
203
|
+
<h3 className="truncate text-sm font-semibold text-foreground">
|
|
204
|
+
{template.label}
|
|
205
|
+
</h3>
|
|
206
|
+
</div>
|
|
207
|
+
<p className="mt-1 line-clamp-2 text-xs leading-relaxed text-muted-foreground">
|
|
208
|
+
{template.hint}
|
|
209
|
+
</p>
|
|
210
|
+
<div className="mt-3">
|
|
211
|
+
<Button
|
|
212
|
+
size="sm"
|
|
213
|
+
variant="outline"
|
|
214
|
+
disabled={scaffold.isPending}
|
|
215
|
+
onClick={() => scaffold.mutate({ template: template.name })}
|
|
216
|
+
>
|
|
217
|
+
{scaffold.isPending ? (
|
|
218
|
+
<>
|
|
219
|
+
<IconLoader2 size={14} className="mr-1.5 animate-spin" />
|
|
220
|
+
Adding…
|
|
221
|
+
</>
|
|
222
|
+
) : (
|
|
223
|
+
<>
|
|
224
|
+
<IconPlus size={14} className="mr-1.5" />
|
|
225
|
+
Add to workspace
|
|
226
|
+
</>
|
|
227
|
+
)}
|
|
228
|
+
</Button>
|
|
229
|
+
</div>
|
|
230
|
+
</div>
|
|
231
|
+
</div>
|
|
232
|
+
);
|
|
233
|
+
}
|