@nuxtagentic/automations 0.0.0-stage → 0.1.1
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.md +9 -0
- package/README.md +173 -2
- package/client.ts +24 -0
- package/module.ts +55 -0
- package/package.json +59 -3
- package/runtime/components/AutomationCanvas.vue +488 -0
- package/runtime/components/AutomationInspector.vue +436 -0
- package/runtime/components/AutomationNodeIcon.vue +27 -0
- package/runtime/components/AutomationRunsPanel.vue +475 -0
- package/runtime/components/AutomationSidebar.vue +253 -0
- package/runtime/components/AutomationStatusIcon.vue +44 -0
- package/runtime/pages/automations.vue +1181 -0
- package/runtime/server/api/automations/[id]/endpoint.get.ts +12 -0
- package/runtime/server/api/automations/[id]/run.post.ts +30 -0
- package/runtime/server/api/automations/events.post.ts +37 -0
- package/runtime/server/api/automations/runs/[id]/resume.post.ts +23 -0
- package/runtime/server/handlers/automation-hook.post.ts +51 -0
- package/runtime/server/handlers/connect-hook.post.ts +54 -0
- package/runtime/server/handlers/integration-hook.post.ts +86 -0
- package/runtime/server/plugins/events.ts +79 -0
- package/runtime/server/plugins/maintenance.ts +20 -0
- package/runtime/server/utils/agent-runner.ts +15 -0
- package/runtime/server/utils/automation-context.ts +77 -0
- package/runtime/server/utils/automation-engine.ts +697 -0
- package/runtime/server/utils/automation-launch.ts +205 -0
- package/runtime/server/utils/automation-schedule.ts +31 -0
- package/runtime/server/utils/connection-events.ts +114 -0
- package/runtime/server/utils/maintenance.ts +255 -0
- package/runtime/server/utils/schedule-sync.ts +32 -0
- package/runtime/server/utils/transient-retry.ts +34 -0
- package/runtime/server/workflows/runner.ts +39 -0
- package/runtime/shared/agent.ts +5 -0
- package/runtime/shared/automation-run.ts +61 -0
- package/runtime/shared/automation.ts +202 -0
- package/runtime/workspace.ts +56 -0
- package/schema.ts +222 -0
- package/server.ts +2 -0
- package/workflow.ts +2 -0
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, ref } from 'vue'
|
|
3
|
+
import type { AutomationDraft } from '../shared/automation'
|
|
4
|
+
|
|
5
|
+
const open = defineModel<boolean>('open', { required: true })
|
|
6
|
+
const {
|
|
7
|
+
automations: automationRecords,
|
|
8
|
+
folders,
|
|
9
|
+
currentId,
|
|
10
|
+
currentFolder,
|
|
11
|
+
busy,
|
|
12
|
+
createFolder,
|
|
13
|
+
} = defineProps<{
|
|
14
|
+
automations: (Pick<AutomationDraft, 'name' | 'folder' | 'active'> & { id: number })[]
|
|
15
|
+
folders: string[]
|
|
16
|
+
currentId: number
|
|
17
|
+
currentFolder: string
|
|
18
|
+
busy: boolean
|
|
19
|
+
createFolder: (name: string) => Promise<boolean>
|
|
20
|
+
}>()
|
|
21
|
+
const emit = defineEmits<{
|
|
22
|
+
select: [id: number]
|
|
23
|
+
folder: [name: string]
|
|
24
|
+
create: []
|
|
25
|
+
removeFolder: [name: string]
|
|
26
|
+
}>()
|
|
27
|
+
const expanded = ref<Record<string, boolean>>({})
|
|
28
|
+
const isCreating = ref(false)
|
|
29
|
+
const folderName = ref('')
|
|
30
|
+
const sortedAutomations = computed(() =>
|
|
31
|
+
automationRecords.toSorted((a, b) => a.name.localeCompare(b.name)),
|
|
32
|
+
)
|
|
33
|
+
const groups = computed(() =>
|
|
34
|
+
folders
|
|
35
|
+
.filter((name) => name !== 'Unsorted')
|
|
36
|
+
.map((name) => ({
|
|
37
|
+
name,
|
|
38
|
+
automations: sortedAutomations.value.filter((row) => row.folder === name),
|
|
39
|
+
})),
|
|
40
|
+
)
|
|
41
|
+
const loose = computed(() => sortedAutomations.value.filter((row) => row.folder === 'Unsorted'))
|
|
42
|
+
function isExpanded(name: string) {
|
|
43
|
+
return (
|
|
44
|
+
expanded.value[name] ??
|
|
45
|
+
(name === currentFolder ||
|
|
46
|
+
automationRecords.some((row) => row.id === currentId && row.folder === name))
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
function toggleFolder(name: string) {
|
|
50
|
+
const wasExpanded = isExpanded(name)
|
|
51
|
+
expanded.value = { ...expanded.value, [name]: !wasExpanded }
|
|
52
|
+
emit('folder', name)
|
|
53
|
+
}
|
|
54
|
+
async function saveFolder() {
|
|
55
|
+
if (!folderName.value.trim() || busy) {
|
|
56
|
+
return
|
|
57
|
+
}
|
|
58
|
+
if (await createFolder(folderName.value.trim())) {
|
|
59
|
+
expanded.value = { ...expanded.value, [folderName.value.trim()]: true }
|
|
60
|
+
isCreating.value = false
|
|
61
|
+
folderName.value = ''
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
</script>
|
|
65
|
+
|
|
66
|
+
<template>
|
|
67
|
+
<aside
|
|
68
|
+
id="automations-folder-sidebar"
|
|
69
|
+
class="absolute inset-y-0 left-0 z-20 h-full shrink-0 overflow-hidden bg-(--gray-200) transition-[width,border-color] duration-260 ease-[cubic-bezier(.2,0,0,1)] motion-reduce:transition-none @min-[700px]/automation:relative"
|
|
70
|
+
:class="
|
|
71
|
+
open
|
|
72
|
+
? 'w-62 border-e border-default'
|
|
73
|
+
: 'pointer-events-none w-0 border-e-0 border-transparent'
|
|
74
|
+
"
|
|
75
|
+
:inert="!open"
|
|
76
|
+
:aria-hidden="!open"
|
|
77
|
+
aria-label="Automation folders"
|
|
78
|
+
>
|
|
79
|
+
<div
|
|
80
|
+
class="flex h-full w-62 flex-col transition-opacity duration-180 motion-reduce:transition-none"
|
|
81
|
+
:class="open ? 'opacity-100' : 'opacity-0'"
|
|
82
|
+
>
|
|
83
|
+
<div class="flex h-14 shrink-0 items-center gap-0.5 pr-2 pl-4">
|
|
84
|
+
<h2 class="min-w-0 flex-1 truncate text-sm font-medium text-highlighted">Automations</h2>
|
|
85
|
+
<UButton
|
|
86
|
+
icon="i-lucide-panel-left-close"
|
|
87
|
+
color="neutral"
|
|
88
|
+
variant="ghost"
|
|
89
|
+
class="size-8 justify-center rounded-md p-0 text-muted hover:bg-(--gray-400) hover:text-highlighted"
|
|
90
|
+
:ui="{ leadingIcon: 'size-4' }"
|
|
91
|
+
aria-label="Close automation folders"
|
|
92
|
+
@click="open = false"
|
|
93
|
+
/>
|
|
94
|
+
<UDropdownMenu
|
|
95
|
+
:items="[
|
|
96
|
+
{ label: 'New automation', icon: 'i-lucide-workflow', onSelect: () => emit('create') },
|
|
97
|
+
{
|
|
98
|
+
label: 'New folder',
|
|
99
|
+
icon: 'i-lucide-folder-plus',
|
|
100
|
+
onSelect: () => (isCreating = true),
|
|
101
|
+
},
|
|
102
|
+
]"
|
|
103
|
+
:content="{ align: 'start' }"
|
|
104
|
+
>
|
|
105
|
+
<UButton
|
|
106
|
+
icon="i-lucide-plus"
|
|
107
|
+
color="neutral"
|
|
108
|
+
variant="ghost"
|
|
109
|
+
:disabled="busy"
|
|
110
|
+
class="size-8 justify-center rounded-md p-0 text-muted hover:bg-(--gray-400) hover:text-highlighted"
|
|
111
|
+
:ui="{ leadingIcon: 'size-4' }"
|
|
112
|
+
aria-label="Create automation or folder"
|
|
113
|
+
/>
|
|
114
|
+
</UDropdownMenu>
|
|
115
|
+
</div>
|
|
116
|
+
<nav
|
|
117
|
+
class="flex min-h-0 flex-1 flex-col gap-0.5 overflow-y-auto px-2 pt-1 pb-4"
|
|
118
|
+
aria-label="Automation folder contents"
|
|
119
|
+
>
|
|
120
|
+
<form
|
|
121
|
+
v-if="isCreating"
|
|
122
|
+
class="mb-1 flex items-center gap-1"
|
|
123
|
+
@submit.prevent="saveFolder"
|
|
124
|
+
>
|
|
125
|
+
<UInput
|
|
126
|
+
v-model="folderName"
|
|
127
|
+
autofocus
|
|
128
|
+
icon="i-lucide-folder"
|
|
129
|
+
placeholder="Folder name"
|
|
130
|
+
aria-label="New folder name"
|
|
131
|
+
:maxlength="80"
|
|
132
|
+
:disabled="busy"
|
|
133
|
+
class="min-w-0 flex-1"
|
|
134
|
+
:ui="{ base: 'h-8 text-[13px]' }"
|
|
135
|
+
@keydown.esc.stop="isCreating = false"
|
|
136
|
+
/>
|
|
137
|
+
<UButton
|
|
138
|
+
icon="i-lucide-check"
|
|
139
|
+
type="submit"
|
|
140
|
+
aria-label="Create folder"
|
|
141
|
+
color="neutral"
|
|
142
|
+
variant="ghost"
|
|
143
|
+
class="size-7"
|
|
144
|
+
:disabled="busy || !folderName.trim()"
|
|
145
|
+
/>
|
|
146
|
+
<UButton
|
|
147
|
+
icon="i-lucide-x"
|
|
148
|
+
aria-label="Cancel new folder"
|
|
149
|
+
color="neutral"
|
|
150
|
+
variant="ghost"
|
|
151
|
+
class="size-7"
|
|
152
|
+
:disabled="busy"
|
|
153
|
+
@click="isCreating = false"
|
|
154
|
+
/>
|
|
155
|
+
</form>
|
|
156
|
+
<div
|
|
157
|
+
v-for="group in groups"
|
|
158
|
+
:key="group.name"
|
|
159
|
+
class="shrink-0"
|
|
160
|
+
>
|
|
161
|
+
<UContextMenu
|
|
162
|
+
:items="[
|
|
163
|
+
{
|
|
164
|
+
label: 'Delete folder',
|
|
165
|
+
icon: 'i-lucide-trash-2',
|
|
166
|
+
disabled: busy,
|
|
167
|
+
onSelect: () => emit('removeFolder', group.name),
|
|
168
|
+
},
|
|
169
|
+
]"
|
|
170
|
+
>
|
|
171
|
+
<UButton
|
|
172
|
+
color="neutral"
|
|
173
|
+
variant="ghost"
|
|
174
|
+
:disabled="busy"
|
|
175
|
+
class="h-8.5 w-full justify-start gap-2 rounded-lg py-0 pr-2 pl-1.5 text-[13px] font-normal text-highlighted hover:bg-(--gray-400)"
|
|
176
|
+
:aria-expanded="isExpanded(group.name)"
|
|
177
|
+
@click="toggleFolder(group.name)"
|
|
178
|
+
>
|
|
179
|
+
<UIcon
|
|
180
|
+
name="i-lucide-chevron-right"
|
|
181
|
+
class="size-3 shrink-0 text-dimmed transition-transform duration-220 motion-reduce:transition-none"
|
|
182
|
+
:class="isExpanded(group.name) && 'rotate-90'"
|
|
183
|
+
/>
|
|
184
|
+
<UIcon
|
|
185
|
+
name="i-lucide-folder"
|
|
186
|
+
class="size-4 shrink-0 text-muted"
|
|
187
|
+
/>
|
|
188
|
+
<span class="min-w-0 flex-1 truncate text-left">{{ group.name }}</span>
|
|
189
|
+
<span class="text-xs text-dimmed tabular-nums">{{ group.automations.length }}</span>
|
|
190
|
+
</UButton>
|
|
191
|
+
</UContextMenu>
|
|
192
|
+
<div
|
|
193
|
+
class="grid transition-[grid-template-rows] duration-220 ease-[cubic-bezier(.2,0,0,1)] motion-reduce:transition-none"
|
|
194
|
+
:class="isExpanded(group.name) ? 'grid-rows-[1fr]' : 'grid-rows-[0fr]'"
|
|
195
|
+
:inert="!isExpanded(group.name)"
|
|
196
|
+
:aria-hidden="!isExpanded(group.name)"
|
|
197
|
+
>
|
|
198
|
+
<div class="flex min-h-0 flex-col gap-0.5 overflow-hidden">
|
|
199
|
+
<UButton
|
|
200
|
+
v-for="automation in group.automations"
|
|
201
|
+
:key="automation.id"
|
|
202
|
+
color="neutral"
|
|
203
|
+
variant="ghost"
|
|
204
|
+
:disabled="busy"
|
|
205
|
+
class="h-8 w-full shrink-0 justify-start gap-2 rounded-lg py-0 px-2 text-[13px] font-normal hover:bg-(--gray-400) hover:text-highlighted"
|
|
206
|
+
:class="
|
|
207
|
+
automation.id === currentId
|
|
208
|
+
? 'bg-(--gray-400) text-highlighted'
|
|
209
|
+
: 'text-(--gray-1000)'
|
|
210
|
+
"
|
|
211
|
+
:aria-current="automation.id === currentId ? 'page' : undefined"
|
|
212
|
+
:title="automation.name"
|
|
213
|
+
@click="emit('select', automation.id)"
|
|
214
|
+
>
|
|
215
|
+
<UIcon
|
|
216
|
+
:name="automation.active ? 'i-lucide-zap' : 'i-lucide-workflow'"
|
|
217
|
+
class="size-3.5 shrink-0 text-muted"
|
|
218
|
+
/>
|
|
219
|
+
<span class="min-w-0 truncate">{{ automation.name }}</span>
|
|
220
|
+
</UButton>
|
|
221
|
+
<p
|
|
222
|
+
v-if="!group.automations.length"
|
|
223
|
+
class="flex h-8 items-center pr-2 pl-7.5 text-[13px] text-dimmed"
|
|
224
|
+
>
|
|
225
|
+
No automations yet
|
|
226
|
+
</p>
|
|
227
|
+
</div>
|
|
228
|
+
</div>
|
|
229
|
+
</div>
|
|
230
|
+
<UButton
|
|
231
|
+
v-for="automation in loose"
|
|
232
|
+
:key="automation.id"
|
|
233
|
+
color="neutral"
|
|
234
|
+
variant="ghost"
|
|
235
|
+
:disabled="busy"
|
|
236
|
+
class="h-8 w-full shrink-0 justify-start gap-2 rounded-lg py-0 pr-2 pl-6.5 text-[13px] font-normal hover:bg-(--gray-400) hover:text-highlighted"
|
|
237
|
+
:class="
|
|
238
|
+
automation.id === currentId ? 'bg-(--gray-400) text-highlighted' : 'text-(--gray-1000)'
|
|
239
|
+
"
|
|
240
|
+
:aria-current="automation.id === currentId ? 'page' : undefined"
|
|
241
|
+
:title="automation.name"
|
|
242
|
+
@click="emit('select', automation.id)"
|
|
243
|
+
>
|
|
244
|
+
<UIcon
|
|
245
|
+
:name="automation.active ? 'i-lucide-zap' : 'i-lucide-workflow'"
|
|
246
|
+
class="size-3.5 shrink-0 text-muted"
|
|
247
|
+
/>
|
|
248
|
+
<span class="min-w-0 truncate">{{ automation.name }}</span>
|
|
249
|
+
</UButton>
|
|
250
|
+
</nav>
|
|
251
|
+
</div>
|
|
252
|
+
</aside>
|
|
253
|
+
</template>
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from 'vue'
|
|
3
|
+
import { getStatusColor, type DisplayStatus } from '../shared/automation-run'
|
|
4
|
+
|
|
5
|
+
const { status } = defineProps<{ status?: DisplayStatus }>()
|
|
6
|
+
const path = computed(() => {
|
|
7
|
+
switch (status) {
|
|
8
|
+
case 'running':
|
|
9
|
+
return 'M21 12a9 9 0 1 1-6.2-8.6'
|
|
10
|
+
case 'succeeded':
|
|
11
|
+
return 'M22 11.1V12a10 10 0 1 1-5.9-9.1M22 4 12 14l-3-3'
|
|
12
|
+
case 'failed':
|
|
13
|
+
return 'M12 22a10 10 0 1 0 0-20 10 10 0 0 0 0 20ZM15 9l-6 6M9 9l6 6'
|
|
14
|
+
case 'retry':
|
|
15
|
+
return 'M3 12a9 9 0 1 0 3-6.7L3 8M3 3v5h5'
|
|
16
|
+
case 'skipped':
|
|
17
|
+
return 'M5 12h14'
|
|
18
|
+
default:
|
|
19
|
+
return 'M12 21a9 9 0 1 0 0-18 9 9 0 0 0 0 18Z'
|
|
20
|
+
}
|
|
21
|
+
})
|
|
22
|
+
</script>
|
|
23
|
+
|
|
24
|
+
<template>
|
|
25
|
+
<svg
|
|
26
|
+
viewBox="0 0 24 24"
|
|
27
|
+
fill="none"
|
|
28
|
+
stroke="currentColor"
|
|
29
|
+
stroke-width="2"
|
|
30
|
+
stroke-linecap="round"
|
|
31
|
+
stroke-linejoin="round"
|
|
32
|
+
aria-hidden="true"
|
|
33
|
+
class="shrink-0 transition-colors duration-200 motion-reduce:transition-none"
|
|
34
|
+
:class="[
|
|
35
|
+
status === 'skipped' || status === 'queued' || !status
|
|
36
|
+
? 'text-(--gray-700)'
|
|
37
|
+
: getStatusColor(status),
|
|
38
|
+
(status === 'running' || status === 'retry') &&
|
|
39
|
+
'animate-[spin_0.9s_linear_infinite] motion-reduce:animate-none',
|
|
40
|
+
]"
|
|
41
|
+
>
|
|
42
|
+
<path :d="path" />
|
|
43
|
+
</svg>
|
|
44
|
+
</template>
|