@nuxtagentic/automations 0.0.0-stage → 0.1.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.
Files changed (38) hide show
  1. package/LICENSE.md +9 -0
  2. package/README.md +173 -2
  3. package/client.ts +24 -0
  4. package/module.ts +55 -0
  5. package/package.json +59 -3
  6. package/runtime/components/AutomationCanvas.vue +488 -0
  7. package/runtime/components/AutomationInspector.vue +436 -0
  8. package/runtime/components/AutomationNodeIcon.vue +27 -0
  9. package/runtime/components/AutomationRunsPanel.vue +475 -0
  10. package/runtime/components/AutomationSidebar.vue +253 -0
  11. package/runtime/components/AutomationStatusIcon.vue +44 -0
  12. package/runtime/pages/automations.vue +1181 -0
  13. package/runtime/server/api/automations/[id]/endpoint.get.ts +12 -0
  14. package/runtime/server/api/automations/[id]/run.post.ts +30 -0
  15. package/runtime/server/api/automations/events.post.ts +37 -0
  16. package/runtime/server/api/automations/runs/[id]/resume.post.ts +23 -0
  17. package/runtime/server/handlers/automation-hook.post.ts +51 -0
  18. package/runtime/server/handlers/connect-hook.post.ts +54 -0
  19. package/runtime/server/handlers/integration-hook.post.ts +86 -0
  20. package/runtime/server/plugins/events.ts +79 -0
  21. package/runtime/server/plugins/maintenance.ts +20 -0
  22. package/runtime/server/utils/agent-runner.ts +15 -0
  23. package/runtime/server/utils/automation-context.ts +77 -0
  24. package/runtime/server/utils/automation-engine.ts +697 -0
  25. package/runtime/server/utils/automation-launch.ts +205 -0
  26. package/runtime/server/utils/automation-schedule.ts +31 -0
  27. package/runtime/server/utils/connection-events.ts +114 -0
  28. package/runtime/server/utils/maintenance.ts +255 -0
  29. package/runtime/server/utils/schedule-sync.ts +32 -0
  30. package/runtime/server/utils/transient-retry.ts +34 -0
  31. package/runtime/server/workflows/runner.ts +39 -0
  32. package/runtime/shared/agent.ts +5 -0
  33. package/runtime/shared/automation-run.ts +61 -0
  34. package/runtime/shared/automation.ts +202 -0
  35. package/runtime/workspace.ts +56 -0
  36. package/schema.ts +222 -0
  37. package/server.ts +2 -0
  38. 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>