@agent-native/dispatch 0.30.5 → 0.31.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/provider-api-register.d.ts +11 -11
- package/dist/components/automation-details-panel.d.ts +8 -0
- package/dist/components/automation-details-panel.d.ts.map +1 -0
- package/dist/components/automation-details-panel.js +80 -0
- package/dist/components/automation-details-panel.js.map +1 -0
- package/dist/lib/automations.d.ts +12 -0
- package/dist/lib/automations.d.ts.map +1 -1
- package/dist/lib/automations.js +9 -0
- package/dist/lib/automations.js.map +1 -1
- package/dist/routes/pages/admin.d.ts.map +1 -1
- package/dist/routes/pages/admin.js +6 -1
- package/dist/routes/pages/admin.js.map +1 -1
- package/dist/routes/pages/automations.d.ts.map +1 -1
- package/dist/routes/pages/automations.js +59 -21
- package/dist/routes/pages/automations.js.map +1 -1
- package/package.json +3 -3
- package/src/components/automation-details-panel.tsx +420 -0
- package/src/lib/automations.spec.ts +19 -1
- package/src/lib/automations.ts +23 -0
- package/src/routes/pages/admin.tsx +8 -1
- package/src/routes/pages/automations.tsx +177 -97
- package/dist/components/automation-run-history-dialog.d.ts +0 -9
- package/dist/components/automation-run-history-dialog.d.ts.map +0 -1
- package/dist/components/automation-run-history-dialog.js +0 -63
- package/dist/components/automation-run-history-dialog.js.map +0 -1
- package/src/components/automation-run-history-dialog.tsx +0 -247
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-native/dispatch",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.31.0",
|
|
4
4
|
"description": "Dispatch — workspace control plane for agent-native apps. Vault, integrations, destinations, scheduled jobs, and cross-app delegation, shipped as a single drop-in package.",
|
|
5
5
|
"homepage": "https://github.com/BuilderIO/agent-native#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -101,10 +101,10 @@
|
|
|
101
101
|
"typescript-7": "npm:typescript@^7.0.2",
|
|
102
102
|
"vite": "8.1.0",
|
|
103
103
|
"vitest": "^4.1.5",
|
|
104
|
-
"@agent-native/core": "0.0.0-beta-
|
|
104
|
+
"@agent-native/core": "0.0.0-beta-20260819190057"
|
|
105
105
|
},
|
|
106
106
|
"peerDependencies": {
|
|
107
|
-
"@agent-native/core": "0.0.0-beta-
|
|
107
|
+
"@agent-native/core": "0.0.0-beta-20260819190057",
|
|
108
108
|
"react": ">=19.2.7",
|
|
109
109
|
"react-dom": ">=19.2.7",
|
|
110
110
|
"react-router": ">=8"
|
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
import { useActionQuery } from "@agent-native/core/client/hooks";
|
|
2
|
+
import {
|
|
3
|
+
IconAlertTriangle,
|
|
4
|
+
IconClock,
|
|
5
|
+
IconExternalLink,
|
|
6
|
+
IconPlayerPause,
|
|
7
|
+
IconPlayerPlay,
|
|
8
|
+
} from "@tabler/icons-react";
|
|
9
|
+
import { Link } from "react-router";
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
automationScopeLabel,
|
|
13
|
+
automationStatus,
|
|
14
|
+
automationTarget,
|
|
15
|
+
} from "../lib/automation-display";
|
|
16
|
+
import {
|
|
17
|
+
automationRunScope,
|
|
18
|
+
type DispatchAutomationItem,
|
|
19
|
+
} from "../lib/automations";
|
|
20
|
+
import { ActionQueryError } from "./action-query-error";
|
|
21
|
+
import { Badge } from "./ui/badge";
|
|
22
|
+
import { Button } from "./ui/button";
|
|
23
|
+
import { Skeleton } from "./ui/skeleton";
|
|
24
|
+
|
|
25
|
+
interface AutomationRun {
|
|
26
|
+
id: string;
|
|
27
|
+
runId: string | null;
|
|
28
|
+
threadId: string | null;
|
|
29
|
+
status: "running" | "success" | "error" | "interrupted";
|
|
30
|
+
startedAt: number;
|
|
31
|
+
finishedAt: number | null;
|
|
32
|
+
error: string | null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface AutomationDetailsPanelProps {
|
|
36
|
+
automation: DispatchAutomationItem;
|
|
37
|
+
isToggling?: boolean;
|
|
38
|
+
onToggle: () => void;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function formatTimestamp(value: number | string | null | undefined): string {
|
|
42
|
+
if (value == null) return "Not recorded";
|
|
43
|
+
const date = new Date(value);
|
|
44
|
+
return Number.isNaN(date.getTime()) ? "Unknown time" : date.toLocaleString();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function formatDuration(run: AutomationRun): string | null {
|
|
48
|
+
if (!run.finishedAt) return null;
|
|
49
|
+
const seconds = Math.max(
|
|
50
|
+
0,
|
|
51
|
+
Math.round((run.finishedAt - run.startedAt) / 1000),
|
|
52
|
+
);
|
|
53
|
+
return `${seconds}s`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function runDebugPath(run: AutomationRun): string | null {
|
|
57
|
+
const params = new URLSearchParams();
|
|
58
|
+
if (run.runId) params.set("runId", run.runId);
|
|
59
|
+
else if (run.threadId) params.set("threadId", run.threadId);
|
|
60
|
+
else return null;
|
|
61
|
+
return `/admin/thread-debug?${params.toString()}`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function runStatusVariant(
|
|
65
|
+
status: AutomationRun["status"],
|
|
66
|
+
): "default" | "destructive" | "outline" {
|
|
67
|
+
if (status === "error") return "destructive";
|
|
68
|
+
if (status === "success") return "default";
|
|
69
|
+
return "outline";
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function DetailField({
|
|
73
|
+
label,
|
|
74
|
+
value,
|
|
75
|
+
mono = false,
|
|
76
|
+
}: {
|
|
77
|
+
label: string;
|
|
78
|
+
value: string;
|
|
79
|
+
mono?: boolean;
|
|
80
|
+
}) {
|
|
81
|
+
return (
|
|
82
|
+
<div className="min-w-0">
|
|
83
|
+
<dt className="text-[11px] uppercase tracking-wide text-muted-foreground">
|
|
84
|
+
{label}
|
|
85
|
+
</dt>
|
|
86
|
+
<dd
|
|
87
|
+
className={
|
|
88
|
+
mono
|
|
89
|
+
? "mt-1 break-words font-mono text-xs text-foreground"
|
|
90
|
+
: "mt-1 break-words text-sm text-foreground"
|
|
91
|
+
}
|
|
92
|
+
>
|
|
93
|
+
{value}
|
|
94
|
+
</dd>
|
|
95
|
+
</div>
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function runAsLabel(value: DispatchAutomationItem["runAs"]): string {
|
|
100
|
+
if (value === "creator") return "Creator identity";
|
|
101
|
+
if (value === "shared") return "Shared identity";
|
|
102
|
+
return "Default identity";
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function AutomationDetailsPanel({
|
|
106
|
+
automation,
|
|
107
|
+
isToggling = false,
|
|
108
|
+
onToggle,
|
|
109
|
+
}: AutomationDetailsPanelProps) {
|
|
110
|
+
const runsQuery = useActionQuery<AutomationRun[]>(
|
|
111
|
+
"list-automation-runs",
|
|
112
|
+
{
|
|
113
|
+
name: automation.name,
|
|
114
|
+
scope: automationRunScope(automation),
|
|
115
|
+
appId: automation.appId ?? "dispatch",
|
|
116
|
+
},
|
|
117
|
+
{ staleTime: 5_000 },
|
|
118
|
+
);
|
|
119
|
+
const runs = runsQuery.data ?? [];
|
|
120
|
+
const status = automationStatus(automation);
|
|
121
|
+
const isScheduled = automation.triggerType !== "event";
|
|
122
|
+
const prompt = automation.body?.trim();
|
|
123
|
+
const hasExecutionTarget = Boolean(
|
|
124
|
+
automation.executionHostId ||
|
|
125
|
+
automation.executionEngine ||
|
|
126
|
+
automation.executionCwd,
|
|
127
|
+
);
|
|
128
|
+
const hasDelivery = Boolean(
|
|
129
|
+
automation.deliveryPlatform ||
|
|
130
|
+
automation.deliveryDestination ||
|
|
131
|
+
automation.deliveryThreadRef,
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
return (
|
|
135
|
+
<aside className="flex min-h-[34rem] min-w-0 flex-col overflow-hidden rounded-lg border bg-card">
|
|
136
|
+
<header className="flex items-start justify-between gap-3 border-b p-5">
|
|
137
|
+
<div className="min-w-0">
|
|
138
|
+
<div className="flex items-center gap-2">
|
|
139
|
+
<span
|
|
140
|
+
className={
|
|
141
|
+
status.tone === "danger"
|
|
142
|
+
? "size-2 shrink-0 rounded-full bg-destructive"
|
|
143
|
+
: status.tone === "default"
|
|
144
|
+
? "size-2 shrink-0 rounded-full bg-primary"
|
|
145
|
+
: "size-2 shrink-0 rounded-full bg-muted-foreground/40"
|
|
146
|
+
}
|
|
147
|
+
/>
|
|
148
|
+
<Badge
|
|
149
|
+
variant={status.tone === "danger" ? "destructive" : "outline"}
|
|
150
|
+
className="h-5"
|
|
151
|
+
>
|
|
152
|
+
{status.label}
|
|
153
|
+
</Badge>
|
|
154
|
+
</div>
|
|
155
|
+
<h2 className="mt-2 break-words text-base font-semibold text-foreground">
|
|
156
|
+
{automation.name}
|
|
157
|
+
</h2>
|
|
158
|
+
<p className="mt-1 text-xs text-muted-foreground">
|
|
159
|
+
{isScheduled ? "Schedule" : "Event"} ·{" "}
|
|
160
|
+
{automationScopeLabel(automation)}
|
|
161
|
+
</p>
|
|
162
|
+
</div>
|
|
163
|
+
</header>
|
|
164
|
+
|
|
165
|
+
<div className="min-h-0 flex-1 space-y-5 overflow-y-auto p-5">
|
|
166
|
+
<section>
|
|
167
|
+
<h3 className="text-sm font-semibold text-foreground">Prompt</h3>
|
|
168
|
+
{prompt ? (
|
|
169
|
+
<pre className="mt-2 max-h-64 overflow-auto whitespace-pre-wrap break-words rounded-md bg-muted/40 p-3 font-sans text-sm leading-6 text-foreground">
|
|
170
|
+
{prompt}
|
|
171
|
+
</pre>
|
|
172
|
+
) : (
|
|
173
|
+
<p className="mt-2 rounded-md border border-dashed p-3 text-sm text-muted-foreground">
|
|
174
|
+
No prompt recorded.
|
|
175
|
+
</p>
|
|
176
|
+
)}
|
|
177
|
+
</section>
|
|
178
|
+
|
|
179
|
+
<section className="border-t pt-5">
|
|
180
|
+
<h3 className="text-sm font-semibold text-foreground">
|
|
181
|
+
Configuration
|
|
182
|
+
</h3>
|
|
183
|
+
<dl className="mt-3 grid grid-cols-2 gap-x-4 gap-y-4">
|
|
184
|
+
<DetailField
|
|
185
|
+
label="Trigger"
|
|
186
|
+
value={isScheduled ? "Scheduled" : "Event-triggered"}
|
|
187
|
+
/>
|
|
188
|
+
<DetailField
|
|
189
|
+
label={isScheduled ? "Schedule" : "Event"}
|
|
190
|
+
value={automationTarget(automation)}
|
|
191
|
+
/>
|
|
192
|
+
{isScheduled ? (
|
|
193
|
+
<>
|
|
194
|
+
{automation.schedule ? (
|
|
195
|
+
<DetailField
|
|
196
|
+
label="Cron expression"
|
|
197
|
+
value={automation.schedule}
|
|
198
|
+
mono
|
|
199
|
+
/>
|
|
200
|
+
) : null}
|
|
201
|
+
<DetailField
|
|
202
|
+
label="Timezone"
|
|
203
|
+
value={automation.timezone || "Account default"}
|
|
204
|
+
/>
|
|
205
|
+
</>
|
|
206
|
+
) : null}
|
|
207
|
+
<DetailField
|
|
208
|
+
label="Condition"
|
|
209
|
+
value={automation.condition || "Always"}
|
|
210
|
+
/>
|
|
211
|
+
<DetailField
|
|
212
|
+
label="Domain"
|
|
213
|
+
value={automation.domain || "Default"}
|
|
214
|
+
/>
|
|
215
|
+
<DetailField
|
|
216
|
+
label="Model"
|
|
217
|
+
value={automation.model || "Default model"}
|
|
218
|
+
/>
|
|
219
|
+
<DetailField label="Mode" value={automation.mode || "Agentic"} />
|
|
220
|
+
<DetailField label="Runs as" value={runAsLabel(automation.runAs)} />
|
|
221
|
+
<DetailField
|
|
222
|
+
label="Next run"
|
|
223
|
+
value={
|
|
224
|
+
automation.enabled
|
|
225
|
+
? formatTimestamp(automation.nextRun)
|
|
226
|
+
: "Paused"
|
|
227
|
+
}
|
|
228
|
+
/>
|
|
229
|
+
<DetailField
|
|
230
|
+
label="Last run"
|
|
231
|
+
value={formatTimestamp(automation.lastRun)}
|
|
232
|
+
/>
|
|
233
|
+
<DetailField
|
|
234
|
+
label="Last checked"
|
|
235
|
+
value={formatTimestamp(automation.lastCheck)}
|
|
236
|
+
/>
|
|
237
|
+
<DetailField
|
|
238
|
+
label="Created by"
|
|
239
|
+
value={automation.createdBy || "Unknown"}
|
|
240
|
+
/>
|
|
241
|
+
</dl>
|
|
242
|
+
</section>
|
|
243
|
+
|
|
244
|
+
<section className="border-t pt-5">
|
|
245
|
+
<h3 className="text-sm font-semibold text-foreground">
|
|
246
|
+
Capabilities
|
|
247
|
+
</h3>
|
|
248
|
+
{automation.mcpTools && automation.mcpTools.length > 0 ? (
|
|
249
|
+
<div className="mt-2 flex flex-wrap gap-1.5">
|
|
250
|
+
{automation.mcpTools.map((toolName) => (
|
|
251
|
+
<code
|
|
252
|
+
key={toolName}
|
|
253
|
+
className="max-w-full break-all rounded bg-muted px-1.5 py-1 text-[11px] text-muted-foreground"
|
|
254
|
+
>
|
|
255
|
+
{toolName}
|
|
256
|
+
</code>
|
|
257
|
+
))}
|
|
258
|
+
</div>
|
|
259
|
+
) : (
|
|
260
|
+
<p className="mt-2 text-sm text-muted-foreground">
|
|
261
|
+
No explicit tool allowlist.
|
|
262
|
+
</p>
|
|
263
|
+
)}
|
|
264
|
+
</section>
|
|
265
|
+
|
|
266
|
+
{hasExecutionTarget ? (
|
|
267
|
+
<section className="border-t pt-5">
|
|
268
|
+
<h3 className="text-sm font-semibold text-foreground">
|
|
269
|
+
Execution target
|
|
270
|
+
</h3>
|
|
271
|
+
<dl className="mt-3 grid grid-cols-2 gap-x-4 gap-y-4">
|
|
272
|
+
<DetailField
|
|
273
|
+
label="Host"
|
|
274
|
+
value={automation.executionHostId || "Server default"}
|
|
275
|
+
mono={Boolean(automation.executionHostId)}
|
|
276
|
+
/>
|
|
277
|
+
<DetailField
|
|
278
|
+
label="Engine"
|
|
279
|
+
value={automation.executionEngine || "Default"}
|
|
280
|
+
/>
|
|
281
|
+
{automation.executionCwd ? (
|
|
282
|
+
<DetailField
|
|
283
|
+
label="Working directory"
|
|
284
|
+
value={automation.executionCwd}
|
|
285
|
+
mono
|
|
286
|
+
/>
|
|
287
|
+
) : null}
|
|
288
|
+
</dl>
|
|
289
|
+
</section>
|
|
290
|
+
) : null}
|
|
291
|
+
|
|
292
|
+
{hasDelivery ? (
|
|
293
|
+
<section className="border-t pt-5">
|
|
294
|
+
<h3 className="text-sm font-semibold text-foreground">Delivery</h3>
|
|
295
|
+
<dl className="mt-3 grid grid-cols-2 gap-x-4 gap-y-4">
|
|
296
|
+
{automation.deliveryPlatform ? (
|
|
297
|
+
<DetailField
|
|
298
|
+
label="Platform"
|
|
299
|
+
value={automation.deliveryPlatform}
|
|
300
|
+
/>
|
|
301
|
+
) : null}
|
|
302
|
+
{automation.deliveryDestination ? (
|
|
303
|
+
<DetailField
|
|
304
|
+
label="Destination"
|
|
305
|
+
value={automation.deliveryDestination}
|
|
306
|
+
/>
|
|
307
|
+
) : null}
|
|
308
|
+
{automation.deliveryThreadRef ? (
|
|
309
|
+
<DetailField
|
|
310
|
+
label="Thread reference"
|
|
311
|
+
value={automation.deliveryThreadRef}
|
|
312
|
+
mono
|
|
313
|
+
/>
|
|
314
|
+
) : null}
|
|
315
|
+
</dl>
|
|
316
|
+
</section>
|
|
317
|
+
) : null}
|
|
318
|
+
|
|
319
|
+
{automation.lastError ? (
|
|
320
|
+
<section className="flex items-start gap-2 border-t border-destructive/30 pt-5">
|
|
321
|
+
<IconAlertTriangle className="mt-0.5 size-4 shrink-0 text-destructive" />
|
|
322
|
+
<div className="min-w-0">
|
|
323
|
+
<h3 className="text-sm font-semibold text-destructive">
|
|
324
|
+
Latest scheduler result
|
|
325
|
+
</h3>
|
|
326
|
+
<p className="mt-1 break-words text-sm text-muted-foreground">
|
|
327
|
+
{automation.lastError}
|
|
328
|
+
</p>
|
|
329
|
+
</div>
|
|
330
|
+
</section>
|
|
331
|
+
) : null}
|
|
332
|
+
|
|
333
|
+
<section className="border-t pt-5">
|
|
334
|
+
<div className="flex items-center justify-between gap-3">
|
|
335
|
+
<h3 className="text-sm font-semibold text-foreground">Past runs</h3>
|
|
336
|
+
{runs.length > 0 ? (
|
|
337
|
+
<Badge variant="outline">{runs.length} shown</Badge>
|
|
338
|
+
) : null}
|
|
339
|
+
</div>
|
|
340
|
+
<div className="mt-3">
|
|
341
|
+
{runsQuery.isLoading ? (
|
|
342
|
+
<div className="space-y-3 rounded-md border p-3">
|
|
343
|
+
{Array.from({ length: 3 }).map((_, index) => (
|
|
344
|
+
<div key={index} className="space-y-2">
|
|
345
|
+
<Skeleton className="h-4 w-2/3" />
|
|
346
|
+
<Skeleton className="h-3 w-1/2" />
|
|
347
|
+
</div>
|
|
348
|
+
))}
|
|
349
|
+
</div>
|
|
350
|
+
) : runsQuery.isError ? (
|
|
351
|
+
<ActionQueryError
|
|
352
|
+
error={runsQuery.error}
|
|
353
|
+
onRetry={() => void runsQuery.refetch()}
|
|
354
|
+
/>
|
|
355
|
+
) : runs.length === 0 ? (
|
|
356
|
+
<p className="rounded-md border border-dashed p-3 text-sm text-muted-foreground">
|
|
357
|
+
No execution recorded yet.
|
|
358
|
+
</p>
|
|
359
|
+
) : (
|
|
360
|
+
<ul className="divide-y rounded-md border">
|
|
361
|
+
{runs.map((run) => {
|
|
362
|
+
const debugPath = runDebugPath(run);
|
|
363
|
+
const duration = formatDuration(run);
|
|
364
|
+
return (
|
|
365
|
+
<li key={run.id} className="space-y-2 p-3">
|
|
366
|
+
<div className="flex flex-wrap items-center gap-2 text-sm">
|
|
367
|
+
<Badge variant={runStatusVariant(run.status)}>
|
|
368
|
+
{run.status}
|
|
369
|
+
</Badge>
|
|
370
|
+
<span className="text-muted-foreground">
|
|
371
|
+
{formatTimestamp(run.startedAt)}
|
|
372
|
+
</span>
|
|
373
|
+
{duration ? (
|
|
374
|
+
<span className="inline-flex items-center gap-1 text-xs text-muted-foreground">
|
|
375
|
+
<IconClock className="size-3.5" />
|
|
376
|
+
{duration}
|
|
377
|
+
</span>
|
|
378
|
+
) : null}
|
|
379
|
+
{debugPath ? (
|
|
380
|
+
<Link
|
|
381
|
+
to={debugPath}
|
|
382
|
+
className="ml-auto inline-flex items-center gap-1 text-xs font-medium text-foreground underline-offset-4 hover:underline"
|
|
383
|
+
>
|
|
384
|
+
<IconExternalLink className="size-3.5" />
|
|
385
|
+
Open debug
|
|
386
|
+
</Link>
|
|
387
|
+
) : null}
|
|
388
|
+
</div>
|
|
389
|
+
{run.error ? (
|
|
390
|
+
<p className="break-words text-sm text-destructive">
|
|
391
|
+
{run.error}
|
|
392
|
+
</p>
|
|
393
|
+
) : null}
|
|
394
|
+
</li>
|
|
395
|
+
);
|
|
396
|
+
})}
|
|
397
|
+
</ul>
|
|
398
|
+
)}
|
|
399
|
+
</div>
|
|
400
|
+
</section>
|
|
401
|
+
</div>
|
|
402
|
+
|
|
403
|
+
<footer className="flex flex-wrap items-center gap-2 border-t p-4">
|
|
404
|
+
<Button
|
|
405
|
+
type="button"
|
|
406
|
+
variant="outline"
|
|
407
|
+
size="sm"
|
|
408
|
+
onClick={onToggle}
|
|
409
|
+
disabled={automation.canUpdate === false || isToggling}
|
|
410
|
+
>
|
|
411
|
+
{automation.enabled ? <IconPlayerPause /> : <IconPlayerPlay />}
|
|
412
|
+
{automation.enabled ? "Pause" : "Resume"}
|
|
413
|
+
</Button>
|
|
414
|
+
{automation.canUpdate === false ? (
|
|
415
|
+
<span className="text-xs text-muted-foreground">Read-only</span>
|
|
416
|
+
) : null}
|
|
417
|
+
</footer>
|
|
418
|
+
</aside>
|
|
419
|
+
);
|
|
420
|
+
}
|
|
@@ -1,6 +1,24 @@
|
|
|
1
1
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
2
2
|
|
|
3
|
-
import { listDispatchAutomations } from "./automations.js";
|
|
3
|
+
import { automationRunScope, listDispatchAutomations } from "./automations.js";
|
|
4
|
+
|
|
5
|
+
describe("automationRunScope", () => {
|
|
6
|
+
it("uses the requesting user for legacy shared history", () => {
|
|
7
|
+
expect(
|
|
8
|
+
automationRunScope({ owner: "__shared__", scope: "organization" }),
|
|
9
|
+
).toBe("personal");
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it("uses organization history when a shared automation has an org id", () => {
|
|
13
|
+
expect(
|
|
14
|
+
automationRunScope({
|
|
15
|
+
owner: "__shared__",
|
|
16
|
+
orgId: "org-1",
|
|
17
|
+
scope: "organization",
|
|
18
|
+
}),
|
|
19
|
+
).toBe("organization");
|
|
20
|
+
});
|
|
21
|
+
});
|
|
4
22
|
|
|
5
23
|
describe("listDispatchAutomations", () => {
|
|
6
24
|
afterEach(() => {
|
package/src/lib/automations.ts
CHANGED
|
@@ -6,6 +6,7 @@ export interface DispatchAutomationItem {
|
|
|
6
6
|
path: string;
|
|
7
7
|
owner: string;
|
|
8
8
|
appId?: string;
|
|
9
|
+
orgId?: string;
|
|
9
10
|
scope?: "personal" | "organization";
|
|
10
11
|
canUpdate?: boolean;
|
|
11
12
|
triggerType?: "schedule" | "event" | string;
|
|
@@ -23,6 +24,28 @@ export interface DispatchAutomationItem {
|
|
|
23
24
|
lastError?: string;
|
|
24
25
|
nextRun?: string;
|
|
25
26
|
createdBy?: string;
|
|
27
|
+
body?: string;
|
|
28
|
+
model?: string;
|
|
29
|
+
mcpTools?: string[];
|
|
30
|
+
runAs?: "creator" | "shared";
|
|
31
|
+
executionHostId?: string;
|
|
32
|
+
executionEngine?: string;
|
|
33
|
+
executionCwd?: string;
|
|
34
|
+
deliveryPlatform?: string;
|
|
35
|
+
deliveryDestination?: string;
|
|
36
|
+
deliveryThreadRef?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function automationRunScope(
|
|
40
|
+
automation: Pick<DispatchAutomationItem, "owner" | "orgId" | "scope">,
|
|
41
|
+
): "personal" | "organization" {
|
|
42
|
+
if (automation.owner === "__shared__" && !automation.orgId) {
|
|
43
|
+
return "personal";
|
|
44
|
+
}
|
|
45
|
+
return automation.scope === "organization" ||
|
|
46
|
+
automation.owner.startsWith("__organization__:")
|
|
47
|
+
? "organization"
|
|
48
|
+
: "personal";
|
|
26
49
|
}
|
|
27
50
|
|
|
28
51
|
export interface SetDispatchAutomationEnabledInput {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Outlet } from "react-router";
|
|
1
|
+
import { Outlet, useLocation } from "react-router";
|
|
2
2
|
|
|
3
3
|
import { AdminShell } from "../../components/admin-navigation";
|
|
4
4
|
|
|
@@ -7,6 +7,13 @@ export function meta() {
|
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
export default function AdminRoute() {
|
|
10
|
+
const location = useLocation();
|
|
11
|
+
const isAutomationsRoute =
|
|
12
|
+
location.pathname === "/admin/automations" ||
|
|
13
|
+
location.pathname.startsWith("/admin/automations/");
|
|
14
|
+
|
|
15
|
+
if (isAutomationsRoute) return <Outlet />;
|
|
16
|
+
|
|
10
17
|
return (
|
|
11
18
|
<AdminShell>
|
|
12
19
|
<Outlet />
|