@cat-factory/app 0.6.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 +21 -0
- package/README.md +88 -0
- package/app/app.config.ts +8 -0
- package/app/app.vue +11 -0
- package/app/assets/css/main.css +100 -0
- package/app/components/auth/AuthGate.vue +24 -0
- package/app/components/auth/LoginScreen.vue +143 -0
- package/app/components/auth/UserMenu.vue +39 -0
- package/app/components/board/AddTaskModal.vue +444 -0
- package/app/components/board/AgentFailureCard.vue +97 -0
- package/app/components/board/AgentStopButton.vue +61 -0
- package/app/components/board/BoardCanvas.vue +183 -0
- package/app/components/board/ContextPicker.vue +367 -0
- package/app/components/board/RecurringPipelineModal.vue +219 -0
- package/app/components/board/TaskDependencyEdges.vue +132 -0
- package/app/components/board/nodes/AgentChip.vue +59 -0
- package/app/components/board/nodes/BlockNode.vue +433 -0
- package/app/components/board/nodes/DecisionBadge.vue +27 -0
- package/app/components/board/nodes/DraggableTask.vue +48 -0
- package/app/components/board/nodes/ModuleFrame.vue +97 -0
- package/app/components/board/nodes/TaskCard.vue +359 -0
- package/app/components/board/nodes/TaskPipelineMini.vue +159 -0
- package/app/components/bootstrap/BootstrapModal.vue +665 -0
- package/app/components/clarity/ClarityReviewWindow.vue +611 -0
- package/app/components/consensus/ConsensusSessionWindow.vue +210 -0
- package/app/components/documents/DocumentImportModal.vue +161 -0
- package/app/components/documents/DocumentSourceConnectModal.vue +127 -0
- package/app/components/documents/SpawnPreviewModal.vue +161 -0
- package/app/components/documents/TaskContextDocs.vue +83 -0
- package/app/components/focus/BlockFocusView.vue +171 -0
- package/app/components/fragments/FragmentLibraryPanel.vue +340 -0
- package/app/components/gates/GateResultView.vue +282 -0
- package/app/components/github/AddServiceFromRepoModal.vue +354 -0
- package/app/components/github/GitHubConnect.vue +183 -0
- package/app/components/github/GitHubOnboarding.vue +45 -0
- package/app/components/github/GitHubPanel.vue +584 -0
- package/app/components/github/RepoTreeBrowser.vue +171 -0
- package/app/components/layout/AccountTeamSettings.vue +237 -0
- package/app/components/layout/BoardSwitcher.vue +280 -0
- package/app/components/layout/BoardToolbar.vue +156 -0
- package/app/components/layout/CommandBar.vue +336 -0
- package/app/components/layout/GitHubPatBanner.vue +73 -0
- package/app/components/layout/NotificationsInbox.vue +175 -0
- package/app/components/layout/SideBar.vue +314 -0
- package/app/components/layout/SpendWarningBanner.vue +107 -0
- package/app/components/observability/StepMetricsBar.vue +102 -0
- package/app/components/palettes/AgentPalette.vue +86 -0
- package/app/components/panels/AgentStepDetail.vue +737 -0
- package/app/components/panels/DecisionModal.vue +71 -0
- package/app/components/panels/InspectorPanel.vue +465 -0
- package/app/components/panels/ObservabilityPanel.vue +351 -0
- package/app/components/panels/StepMetadataCard.vue +253 -0
- package/app/components/panels/StepRestartControl.vue +90 -0
- package/app/components/panels/StepResultViewHost.vue +40 -0
- package/app/components/panels/StepTestReport.vue +84 -0
- package/app/components/panels/inspector/ContainerSummary.vue +74 -0
- package/app/components/panels/inspector/RecurringScheduleSettings.vue +178 -0
- package/app/components/panels/inspector/ServiceFragments.vue +82 -0
- package/app/components/panels/inspector/ServiceTestConfig.vue +198 -0
- package/app/components/panels/inspector/TaskAgentConfig.vue +81 -0
- package/app/components/panels/inspector/TaskDependencies.vue +70 -0
- package/app/components/panels/inspector/TaskEstimateBadge.vue +56 -0
- package/app/components/panels/inspector/TaskExecution.vue +364 -0
- package/app/components/panels/inspector/TaskRunSettings.vue +187 -0
- package/app/components/panels/inspector/TaskStructure.vue +96 -0
- package/app/components/pipeline/AgentKindIcon.vue +30 -0
- package/app/components/pipeline/IterationCapPrompt.vue +70 -0
- package/app/components/pipeline/PipelineBuilder.vue +817 -0
- package/app/components/pipeline/PipelineProgress.vue +484 -0
- package/app/components/providers/ApiKeysSection.vue +273 -0
- package/app/components/providers/PersonalCredentialModal.vue +128 -0
- package/app/components/providers/PersonalSubscriptionSection.vue +225 -0
- package/app/components/providers/VendorCredentialsModal.vue +197 -0
- package/app/components/recurring/RecurrenceEditor.vue +124 -0
- package/app/components/requirements/RequirementsReviewWindow.vue +620 -0
- package/app/components/settings/DatadogPanel.vue +213 -0
- package/app/components/settings/LocalModelEndpointsPanel.vue +286 -0
- package/app/components/settings/MergeThresholdsPanel.vue +378 -0
- package/app/components/settings/ModelDefaultsPanel.vue +250 -0
- package/app/components/settings/ServiceFragmentDefaultsPanel.vue +124 -0
- package/app/components/settings/WorkspaceSettingsPanel.vue +142 -0
- package/app/components/slack/SlackPanel.vue +299 -0
- package/app/components/tasks/TaskContextIssues.vue +88 -0
- package/app/components/tasks/TaskImportModal.vue +207 -0
- package/app/components/tasks/TaskSourceConnectModal.vue +133 -0
- package/app/components/testing/TestReportWindow.vue +404 -0
- package/app/composables/api/accounts.ts +81 -0
- package/app/composables/api/auth.ts +45 -0
- package/app/composables/api/board.ts +101 -0
- package/app/composables/api/bootstrap.ts +62 -0
- package/app/composables/api/context.ts +25 -0
- package/app/composables/api/documents.ts +74 -0
- package/app/composables/api/execution.ts +127 -0
- package/app/composables/api/fragments.ts +71 -0
- package/app/composables/api/github.ts +131 -0
- package/app/composables/api/models.ts +127 -0
- package/app/composables/api/notifications.ts +23 -0
- package/app/composables/api/presets.ts +29 -0
- package/app/composables/api/recurring.ts +68 -0
- package/app/composables/api/releaseHealth.ts +43 -0
- package/app/composables/api/reviews.ts +146 -0
- package/app/composables/api/slack.ts +54 -0
- package/app/composables/api/tasks.ts +72 -0
- package/app/composables/api/workspaces.ts +36 -0
- package/app/composables/useApi.ts +89 -0
- package/app/composables/useBlockDrag.ts +90 -0
- package/app/composables/useBlockQueries.ts +154 -0
- package/app/composables/useBoardFlow.ts +11 -0
- package/app/composables/useContextLinking.ts +65 -0
- package/app/composables/useDepLabels.ts +26 -0
- package/app/composables/useFrameResize.ts +54 -0
- package/app/composables/useResultView.ts +48 -0
- package/app/composables/useReviewStage.ts +40 -0
- package/app/composables/useSemanticZoom.ts +31 -0
- package/app/composables/useStepApproval.ts +233 -0
- package/app/composables/useStepProse.ts +78 -0
- package/app/composables/useStepTimer.ts +63 -0
- package/app/composables/useTaskExpansion.ts +92 -0
- package/app/composables/useWorkspaceStream.ts +155 -0
- package/app/docs/architecture.md +31 -0
- package/app/pages/index.vue +141 -0
- package/app/stores/accounts.ts +152 -0
- package/app/stores/agentConfig.ts +35 -0
- package/app/stores/agentRuns.ts +122 -0
- package/app/stores/agents.ts +40 -0
- package/app/stores/apiKeys.ts +108 -0
- package/app/stores/auth.ts +166 -0
- package/app/stores/board.spec.ts +205 -0
- package/app/stores/board.ts +286 -0
- package/app/stores/bootstrap.ts +97 -0
- package/app/stores/clarity.ts +196 -0
- package/app/stores/consensus.ts +60 -0
- package/app/stores/documents.ts +176 -0
- package/app/stores/execution.ts +273 -0
- package/app/stores/fragmentLibrary.ts +147 -0
- package/app/stores/fragments.ts +40 -0
- package/app/stores/github.ts +305 -0
- package/app/stores/localModels.ts +51 -0
- package/app/stores/mergePresets.ts +58 -0
- package/app/stores/modelDefaults.ts +76 -0
- package/app/stores/models.ts +134 -0
- package/app/stores/notifications.ts +70 -0
- package/app/stores/observability.ts +144 -0
- package/app/stores/personalSubscriptions.ts +215 -0
- package/app/stores/pipelines.ts +327 -0
- package/app/stores/recurringPipelines.ts +112 -0
- package/app/stores/releaseHealth.ts +75 -0
- package/app/stores/requirements.spec.ts +94 -0
- package/app/stores/requirements.ts +208 -0
- package/app/stores/serviceFragmentDefaults.ts +29 -0
- package/app/stores/services.ts +87 -0
- package/app/stores/slack.ts +142 -0
- package/app/stores/taskExpansion.ts +36 -0
- package/app/stores/tasks.spec.ts +71 -0
- package/app/stores/tasks.ts +176 -0
- package/app/stores/tracker.ts +27 -0
- package/app/stores/ui.ts +434 -0
- package/app/stores/vendorCredentials.ts +54 -0
- package/app/stores/workspace.ts +215 -0
- package/app/stores/workspaceSettings.ts +36 -0
- package/app/types/accounts.ts +77 -0
- package/app/types/bootstrap.ts +83 -0
- package/app/types/clarity.ts +59 -0
- package/app/types/consensus.ts +91 -0
- package/app/types/documents.ts +104 -0
- package/app/types/domain.ts +495 -0
- package/app/types/execution.ts +383 -0
- package/app/types/fragments.ts +72 -0
- package/app/types/github.ts +173 -0
- package/app/types/localModels.ts +73 -0
- package/app/types/merge.ts +71 -0
- package/app/types/models.ts +157 -0
- package/app/types/notifications.ts +74 -0
- package/app/types/recurring.ts +69 -0
- package/app/types/releaseHealth.ts +31 -0
- package/app/types/requirements.ts +61 -0
- package/app/types/services.ts +27 -0
- package/app/types/slack.ts +57 -0
- package/app/types/tasks.ts +82 -0
- package/app/types/tracker.ts +18 -0
- package/app/utils/agentOutput.spec.ts +128 -0
- package/app/utils/agentOutput.ts +173 -0
- package/app/utils/catalog.spec.ts +112 -0
- package/app/utils/catalog.ts +455 -0
- package/app/utils/dnd.ts +29 -0
- package/app/utils/observability.ts +52 -0
- package/app/utils/pipelineRender.ts +151 -0
- package/nuxt.config.ts +55 -0
- package/package.json +45 -0
|
@@ -0,0 +1,817 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, ref, watch } from 'vue'
|
|
3
|
+
import type { AgentKind, Pipeline } from '~/types/domain'
|
|
4
|
+
import AgentPalette from '~/components/palettes/AgentPalette.vue'
|
|
5
|
+
import AgentKindIcon from '~/components/pipeline/AgentKindIcon.vue'
|
|
6
|
+
import { agentKindMeta, companionForProducer, isConsensusEligibleKind } from '~/utils/catalog'
|
|
7
|
+
import type { ConsensusStrategy } from '~/types/consensus'
|
|
8
|
+
|
|
9
|
+
type DraftUnit = { index: number; kind: AgentKind; companionIndex: number | null }
|
|
10
|
+
|
|
11
|
+
const pipelines = usePipelinesStore()
|
|
12
|
+
|
|
13
|
+
const CONSENSUS_STRATEGIES: { value: ConsensusStrategy; label: string }[] = [
|
|
14
|
+
{ value: 'specialist-panel', label: 'Specialist panel' },
|
|
15
|
+
{ value: 'debate', label: 'Debate' },
|
|
16
|
+
{ value: 'ranked-voting', label: 'Ranked voting' },
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
/** Add a blank participant to the draft step's consensus config. */
|
|
20
|
+
function addParticipant(i: number) {
|
|
21
|
+
const cfg = pipelines.draftConsensus[i]
|
|
22
|
+
if (!cfg) return
|
|
23
|
+
cfg.participants.push({ id: `cp_${Math.random().toString(36).slice(2, 9)}`, role: 'Reviewer' })
|
|
24
|
+
}
|
|
25
|
+
function removeParticipant(i: number, pIdx: number) {
|
|
26
|
+
pipelines.draftConsensus[i]?.participants.splice(pIdx, 1)
|
|
27
|
+
}
|
|
28
|
+
/** Toggle gating on/off for a draft step's consensus config. */
|
|
29
|
+
function toggleGating(i: number) {
|
|
30
|
+
const cfg = pipelines.draftConsensus[i]
|
|
31
|
+
if (!cfg) return
|
|
32
|
+
cfg.gating = cfg.gating?.enabled
|
|
33
|
+
? { ...cfg.gating, enabled: false }
|
|
34
|
+
: { enabled: true, minRisk: 0.6, minImpact: 0.6 }
|
|
35
|
+
}
|
|
36
|
+
const agents = useAgentsStore()
|
|
37
|
+
const ui = useUiStore()
|
|
38
|
+
const releaseHealth = useReleaseHealthStore()
|
|
39
|
+
|
|
40
|
+
const open = computed({
|
|
41
|
+
get: () => ui.builderOpen,
|
|
42
|
+
set: (v: boolean) => (ui.builderOpen = v),
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
// Refresh the observability-integration state whenever the builder opens so the palette
|
|
46
|
+
// knows whether to offer the post-release-health gate (it's loaded on demand, not from
|
|
47
|
+
// the snapshot). Best-effort: a failure just leaves the gate hidden.
|
|
48
|
+
watch(open, (isOpen) => {
|
|
49
|
+
if (isOpen) releaseHealth.load().catch(() => {})
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
function add(kind: AgentKind) {
|
|
53
|
+
pipelines.addToDraft(kind)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Saved pipelines render collapsed (name + step count); a click expands the full
|
|
57
|
+
// ordered step list. Tracking expansion as a Set keyed by pipeline id keeps the
|
|
58
|
+
// icon row from overflowing the narrow panel the way an always-on inline list did.
|
|
59
|
+
const expandedSaved = ref<Set<string>>(new Set())
|
|
60
|
+
function toggleSaved(id: string) {
|
|
61
|
+
const next = new Set(expandedSaved.value)
|
|
62
|
+
if (next.has(id)) next.delete(id)
|
|
63
|
+
else next.add(id)
|
|
64
|
+
expandedSaved.value = next
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const toast = useToast()
|
|
68
|
+
|
|
69
|
+
// ---- "Add agent" mini-form -------------------------------------------------
|
|
70
|
+
const addAgentOpen = ref(false)
|
|
71
|
+
const newAgentName = ref('')
|
|
72
|
+
const newAgentDesc = ref('')
|
|
73
|
+
|
|
74
|
+
function openAddAgent() {
|
|
75
|
+
newAgentName.value = ''
|
|
76
|
+
newAgentDesc.value = ''
|
|
77
|
+
addAgentOpen.value = true
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function createAgent() {
|
|
81
|
+
const agent = agents.addAgent({
|
|
82
|
+
label: newAgentName.value,
|
|
83
|
+
description: newAgentDesc.value,
|
|
84
|
+
})
|
|
85
|
+
toast.add({ title: `Added agent “${agent.label}”`, color: 'success', icon: 'i-lucide-check' })
|
|
86
|
+
addAgentOpen.value = false
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function placeholder(what: string) {
|
|
90
|
+
toast.add({ title: 'Placeholder', description: what, icon: 'i-lucide-construction' })
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async function save() {
|
|
94
|
+
const wasEditing = pipelines.editingId !== null
|
|
95
|
+
try {
|
|
96
|
+
const saved = await pipelines.saveDraft()
|
|
97
|
+
if (saved) {
|
|
98
|
+
toast.add({
|
|
99
|
+
title: wasEditing ? `Updated “${saved.name}”` : `Saved “${saved.name}”`,
|
|
100
|
+
color: 'success',
|
|
101
|
+
icon: 'i-lucide-check',
|
|
102
|
+
})
|
|
103
|
+
ui.builderOpen = false
|
|
104
|
+
} else {
|
|
105
|
+
toast.add({ title: 'Add at least one agent first', color: 'warning' })
|
|
106
|
+
}
|
|
107
|
+
} catch (e) {
|
|
108
|
+
// Surface the backend reason (e.g. post-release-health rejected without an
|
|
109
|
+
// observability integration) rather than a generic failure.
|
|
110
|
+
toast.add({
|
|
111
|
+
title: 'Could not save pipeline',
|
|
112
|
+
description: e instanceof Error ? e.message : undefined,
|
|
113
|
+
color: 'error',
|
|
114
|
+
})
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** Remove a producer unit, taking its attached companion with it (companion index first). */
|
|
119
|
+
function removeUnit(unit: DraftUnit) {
|
|
120
|
+
if (unit.companionIndex !== null) pipelines.removeFromDraft(unit.companionIndex)
|
|
121
|
+
pipelines.removeFromDraft(unit.index)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Enable/disable a producer unit, keeping its attached companion's enable flag in sync. */
|
|
125
|
+
function toggleEnabled(unit: DraftUnit) {
|
|
126
|
+
pipelines.toggleDraftEnabled(unit.index)
|
|
127
|
+
if (unit.companionIndex !== null) {
|
|
128
|
+
pipelines.draftEnabled[unit.companionIndex] = pipelines.draftEnabled[unit.index] !== false
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function companionLabel(kind: string): string | null {
|
|
133
|
+
const companion = companionForProducer(kind)
|
|
134
|
+
return companion ? agentKindMeta(companion).label : null
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Surfaced as an inline hint: a gated step needs a task-estimator before it (mirrors the
|
|
138
|
+
// backend validation, which also rejects the save/start).
|
|
139
|
+
const gatingNeedsEstimator = computed(() => {
|
|
140
|
+
const kinds = pipelines.draft
|
|
141
|
+
for (let i = 0; i < kinds.length; i++) {
|
|
142
|
+
if (!pipelines.draftGating[i]?.enabled || pipelines.draftEnabled[i] === false) continue
|
|
143
|
+
const hasEstimator = kinds
|
|
144
|
+
.slice(0, i)
|
|
145
|
+
.some((k, j) => k === 'task-estimator' && pipelines.draftEnabled[j] !== false)
|
|
146
|
+
if (!hasEstimator) return true
|
|
147
|
+
}
|
|
148
|
+
return false
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
// ---- draft labels ----------------------------------------------------------
|
|
152
|
+
const newLabel = ref('')
|
|
153
|
+
function addLabel() {
|
|
154
|
+
const v = newLabel.value.trim()
|
|
155
|
+
if (v && !pipelines.draftLabels.includes(v)) pipelines.draftLabels.push(v)
|
|
156
|
+
newLabel.value = ''
|
|
157
|
+
}
|
|
158
|
+
function removeLabel(label: string) {
|
|
159
|
+
pipelines.draftLabels = pipelines.draftLabels.filter((l) => l !== label)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// ---- saved-pipeline library filtering --------------------------------------
|
|
163
|
+
const labelFilter = ref<string | null>(null)
|
|
164
|
+
const showArchived = ref(false)
|
|
165
|
+
const allLabels = computed(() =>
|
|
166
|
+
[...new Set(pipelines.pipelines.flatMap((p) => p.labels ?? []))].sort(),
|
|
167
|
+
)
|
|
168
|
+
const archivedCount = computed(() => pipelines.pipelines.filter((p) => p.archived).length)
|
|
169
|
+
const visiblePipelines = computed(() =>
|
|
170
|
+
pipelines.pipelines.filter((p) => {
|
|
171
|
+
if (!showArchived.value && p.archived) return false
|
|
172
|
+
if (labelFilter.value && !(p.labels ?? []).includes(labelFilter.value)) return false
|
|
173
|
+
return true
|
|
174
|
+
}),
|
|
175
|
+
)
|
|
176
|
+
async function toggleArchive(p: Pipeline) {
|
|
177
|
+
try {
|
|
178
|
+
if (p.archived) await pipelines.unarchive(p.id)
|
|
179
|
+
else await pipelines.archive(p.id)
|
|
180
|
+
} catch {
|
|
181
|
+
toast.add({ title: 'Could not update pipeline', color: 'error' })
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** Load a custom pipeline into the draft for in-place editing. */
|
|
186
|
+
function edit(p: Pipeline) {
|
|
187
|
+
pipelines.loadForEdit(p)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/** Clone any pipeline (incl. a read-only built-in) into an editable copy, then edit it. */
|
|
191
|
+
async function clone(p: Pipeline) {
|
|
192
|
+
try {
|
|
193
|
+
const copy = await pipelines.clonePipeline(p.id)
|
|
194
|
+
toast.add({
|
|
195
|
+
title: `Cloned “${p.name}” — now editing “${copy.name}”`,
|
|
196
|
+
color: 'success',
|
|
197
|
+
icon: 'i-lucide-copy',
|
|
198
|
+
})
|
|
199
|
+
} catch {
|
|
200
|
+
toast.add({ title: 'Could not clone pipeline', color: 'error' })
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
</script>
|
|
204
|
+
|
|
205
|
+
<template>
|
|
206
|
+
<USlideover v-model:open="open" title="Pipeline builder" side="left">
|
|
207
|
+
<template #body>
|
|
208
|
+
<div class="grid h-full grid-cols-2 gap-4">
|
|
209
|
+
<!-- agent palette -->
|
|
210
|
+
<div class="overflow-y-auto pr-1">
|
|
211
|
+
<div class="mb-2 flex items-center justify-between gap-2">
|
|
212
|
+
<h3 class="text-xs font-semibold uppercase tracking-wide text-slate-400">
|
|
213
|
+
Agent palette
|
|
214
|
+
</h3>
|
|
215
|
+
<UButton
|
|
216
|
+
color="primary"
|
|
217
|
+
variant="soft"
|
|
218
|
+
size="xs"
|
|
219
|
+
icon="i-lucide-plus"
|
|
220
|
+
@click="openAddAgent"
|
|
221
|
+
>
|
|
222
|
+
Add agent
|
|
223
|
+
</UButton>
|
|
224
|
+
</div>
|
|
225
|
+
<AgentPalette @add="add" />
|
|
226
|
+
</div>
|
|
227
|
+
|
|
228
|
+
<!-- draft chain -->
|
|
229
|
+
<div class="flex flex-col">
|
|
230
|
+
<div class="mb-2 flex items-center justify-between gap-2">
|
|
231
|
+
<h3 class="text-xs font-semibold uppercase tracking-wide text-slate-400">Pipeline</h3>
|
|
232
|
+
<UButton
|
|
233
|
+
color="neutral"
|
|
234
|
+
variant="soft"
|
|
235
|
+
size="xs"
|
|
236
|
+
icon="i-lucide-cpu"
|
|
237
|
+
title="Pick which model each agent kind runs on"
|
|
238
|
+
@click="ui.openModelDefaults()"
|
|
239
|
+
>
|
|
240
|
+
Configure models
|
|
241
|
+
</UButton>
|
|
242
|
+
</div>
|
|
243
|
+
<UInput
|
|
244
|
+
v-model="pipelines.draftName"
|
|
245
|
+
placeholder="Pipeline name"
|
|
246
|
+
size="sm"
|
|
247
|
+
class="mb-2"
|
|
248
|
+
/>
|
|
249
|
+
|
|
250
|
+
<!-- Labels: organize the pipeline in the library (filter/search). -->
|
|
251
|
+
<div class="mb-3 flex flex-wrap items-center gap-1.5">
|
|
252
|
+
<UBadge
|
|
253
|
+
v-for="l in pipelines.draftLabels"
|
|
254
|
+
:key="l"
|
|
255
|
+
color="neutral"
|
|
256
|
+
variant="soft"
|
|
257
|
+
size="xs"
|
|
258
|
+
class="gap-1"
|
|
259
|
+
>
|
|
260
|
+
{{ l }}
|
|
261
|
+
<button type="button" class="hover:text-rose-400" @click="removeLabel(l)">
|
|
262
|
+
<UIcon name="i-lucide-x" class="h-3 w-3" />
|
|
263
|
+
</button>
|
|
264
|
+
</UBadge>
|
|
265
|
+
<input
|
|
266
|
+
v-model="newLabel"
|
|
267
|
+
placeholder="+ label"
|
|
268
|
+
class="w-20 rounded border border-slate-700 bg-slate-900 px-1.5 py-0.5 text-[11px] text-slate-200 focus:w-28"
|
|
269
|
+
@keydown.enter.prevent="addLabel"
|
|
270
|
+
@blur="addLabel"
|
|
271
|
+
/>
|
|
272
|
+
</div>
|
|
273
|
+
|
|
274
|
+
<p
|
|
275
|
+
v-if="gatingNeedsEstimator"
|
|
276
|
+
class="mb-2 flex items-center gap-1.5 rounded-md border border-amber-800/50 bg-amber-950/30 px-2 py-1 text-[11px] text-amber-300"
|
|
277
|
+
>
|
|
278
|
+
<UIcon name="i-lucide-alert-triangle" class="h-3.5 w-3.5 shrink-0" />
|
|
279
|
+
A gated step needs a Task Estimator before it — add one or the pipeline won't save.
|
|
280
|
+
</p>
|
|
281
|
+
|
|
282
|
+
<div
|
|
283
|
+
v-if="pipelines.draft.length === 0"
|
|
284
|
+
class="flex flex-1 items-center justify-center rounded-lg border border-dashed border-slate-700 p-4 text-center text-xs text-slate-500"
|
|
285
|
+
>
|
|
286
|
+
Click agents on the left to assemble a linear pipeline.
|
|
287
|
+
</div>
|
|
288
|
+
|
|
289
|
+
<ol v-else class="flex-1 space-y-2 overflow-y-auto">
|
|
290
|
+
<li
|
|
291
|
+
v-for="(unit, vi) in pipelines.units"
|
|
292
|
+
:key="unit.index"
|
|
293
|
+
class="flex flex-col gap-2 rounded-lg border border-slate-700 bg-slate-800/60 p-2"
|
|
294
|
+
:class="{ 'opacity-50': pipelines.draftEnabled[unit.index] === false }"
|
|
295
|
+
>
|
|
296
|
+
<div class="flex items-center gap-1.5">
|
|
297
|
+
<span class="w-4 shrink-0 text-center text-[10px] text-slate-500">{{
|
|
298
|
+
vi + 1
|
|
299
|
+
}}</span>
|
|
300
|
+
<AgentKindIcon :kind="unit.kind" icon-class="h-4 w-4" />
|
|
301
|
+
<span
|
|
302
|
+
class="min-w-0 flex-1 truncate text-xs text-slate-100"
|
|
303
|
+
:class="{ 'line-through': pipelines.draftEnabled[unit.index] === false }"
|
|
304
|
+
:title="agentKindMeta(unit.kind).description"
|
|
305
|
+
>
|
|
306
|
+
{{ agentKindMeta(unit.kind).label }}
|
|
307
|
+
</span>
|
|
308
|
+
<div class="flex shrink-0 items-center">
|
|
309
|
+
<!-- Companion toggle: attach/detach the dependent reviewer for this producer. -->
|
|
310
|
+
<UButton
|
|
311
|
+
v-if="companionForProducer(unit.kind)"
|
|
312
|
+
:icon="
|
|
313
|
+
unit.companionIndex !== null ? 'i-lucide-scan-eye' : 'i-lucide-scan-search'
|
|
314
|
+
"
|
|
315
|
+
:color="unit.companionIndex !== null ? 'secondary' : 'neutral'"
|
|
316
|
+
variant="ghost"
|
|
317
|
+
size="xs"
|
|
318
|
+
:title="
|
|
319
|
+
unit.companionIndex !== null
|
|
320
|
+
? `Remove the ${companionLabel(unit.kind)} for this step`
|
|
321
|
+
: `Add the ${companionLabel(unit.kind)} (reviews this step, loops it back below threshold)`
|
|
322
|
+
"
|
|
323
|
+
@click="pipelines.toggleCompanion(unit.index)"
|
|
324
|
+
/>
|
|
325
|
+
<!-- Enable/disable: keep the step in the pipeline but skip it at run. -->
|
|
326
|
+
<UButton
|
|
327
|
+
:icon="
|
|
328
|
+
pipelines.draftEnabled[unit.index] === false
|
|
329
|
+
? 'i-lucide-eye-off'
|
|
330
|
+
: 'i-lucide-eye'
|
|
331
|
+
"
|
|
332
|
+
:color="pipelines.draftEnabled[unit.index] === false ? 'neutral' : 'primary'"
|
|
333
|
+
variant="ghost"
|
|
334
|
+
size="xs"
|
|
335
|
+
:title="
|
|
336
|
+
pipelines.draftEnabled[unit.index] === false
|
|
337
|
+
? 'Step disabled (skipped at run) — click to enable'
|
|
338
|
+
: 'Disable this step (kept in the pipeline but skipped at run)'
|
|
339
|
+
"
|
|
340
|
+
@click="toggleEnabled(unit)"
|
|
341
|
+
/>
|
|
342
|
+
<!-- Approval gate: pause after this step so a human reviews (and
|
|
343
|
+
can edit) its proposal before the next step runs. -->
|
|
344
|
+
<UButton
|
|
345
|
+
:icon="
|
|
346
|
+
pipelines.draftGates[unit.index] ? 'i-lucide-shield-check' : 'i-lucide-shield'
|
|
347
|
+
"
|
|
348
|
+
:color="pipelines.draftGates[unit.index] ? 'warning' : 'neutral'"
|
|
349
|
+
variant="ghost"
|
|
350
|
+
size="xs"
|
|
351
|
+
:title="
|
|
352
|
+
pipelines.draftGates[unit.index]
|
|
353
|
+
? 'Approval required after this step — click to remove the gate'
|
|
354
|
+
: 'Require human approval after this step'
|
|
355
|
+
"
|
|
356
|
+
@click="pipelines.toggleDraftGate(unit.index)"
|
|
357
|
+
/>
|
|
358
|
+
<!-- Consensus: run this step through the multi-model mechanism (eligible
|
|
359
|
+
kinds only — architect/analysis/task-estimator). -->
|
|
360
|
+
<UButton
|
|
361
|
+
v-if="isConsensusEligibleKind(unit.kind)"
|
|
362
|
+
:icon="
|
|
363
|
+
pipelines.draftConsensus[unit.index]?.enabled
|
|
364
|
+
? 'i-lucide-users-round'
|
|
365
|
+
: 'i-lucide-user'
|
|
366
|
+
"
|
|
367
|
+
:color="pipelines.draftConsensus[unit.index]?.enabled ? 'success' : 'neutral'"
|
|
368
|
+
variant="ghost"
|
|
369
|
+
size="xs"
|
|
370
|
+
:title="
|
|
371
|
+
pipelines.draftConsensus[unit.index]?.enabled
|
|
372
|
+
? 'Consensus enabled — click to revert to a single agent'
|
|
373
|
+
: 'Enable consensus (multi-model panel/debate/voting) for this step'
|
|
374
|
+
"
|
|
375
|
+
@click="pipelines.toggleDraftConsensus(unit.index)"
|
|
376
|
+
/>
|
|
377
|
+
<UButton
|
|
378
|
+
icon="i-lucide-chevron-up"
|
|
379
|
+
color="neutral"
|
|
380
|
+
variant="ghost"
|
|
381
|
+
size="xs"
|
|
382
|
+
:disabled="vi === 0"
|
|
383
|
+
@click="pipelines.moveUnit(vi, vi - 1)"
|
|
384
|
+
/>
|
|
385
|
+
<UButton
|
|
386
|
+
icon="i-lucide-chevron-down"
|
|
387
|
+
color="neutral"
|
|
388
|
+
variant="ghost"
|
|
389
|
+
size="xs"
|
|
390
|
+
:disabled="vi === pipelines.units.length - 1"
|
|
391
|
+
@click="pipelines.moveUnit(vi, vi + 1)"
|
|
392
|
+
/>
|
|
393
|
+
<UButton
|
|
394
|
+
icon="i-lucide-x"
|
|
395
|
+
color="error"
|
|
396
|
+
variant="ghost"
|
|
397
|
+
size="xs"
|
|
398
|
+
title="Remove this step from the pipeline"
|
|
399
|
+
@click="removeUnit(unit)"
|
|
400
|
+
/>
|
|
401
|
+
</div>
|
|
402
|
+
</div>
|
|
403
|
+
|
|
404
|
+
<!-- Attached companion: a dependent reviewer for this producer, optionally
|
|
405
|
+
gated on the task estimate. -->
|
|
406
|
+
<div
|
|
407
|
+
v-if="unit.companionIndex !== null"
|
|
408
|
+
class="ml-6 space-y-2 rounded-md border border-fuchsia-800/40 bg-fuchsia-950/20 p-2 text-xs"
|
|
409
|
+
>
|
|
410
|
+
<div class="flex items-center gap-1.5">
|
|
411
|
+
<UIcon name="i-lucide-corner-down-right" class="h-3.5 w-3.5 text-slate-500" />
|
|
412
|
+
<AgentKindIcon
|
|
413
|
+
:kind="pipelines.draft[unit.companionIndex]!"
|
|
414
|
+
icon-class="h-4 w-4"
|
|
415
|
+
/>
|
|
416
|
+
<span class="min-w-0 flex-1 truncate text-slate-200">
|
|
417
|
+
{{ agentKindMeta(pipelines.draft[unit.companionIndex]!).label }}
|
|
418
|
+
</span>
|
|
419
|
+
<UButton
|
|
420
|
+
:icon="
|
|
421
|
+
pipelines.draftGating[unit.companionIndex]?.enabled
|
|
422
|
+
? 'i-lucide-toggle-right'
|
|
423
|
+
: 'i-lucide-toggle-left'
|
|
424
|
+
"
|
|
425
|
+
:color="
|
|
426
|
+
pipelines.draftGating[unit.companionIndex]?.enabled ? 'success' : 'neutral'
|
|
427
|
+
"
|
|
428
|
+
variant="ghost"
|
|
429
|
+
size="xs"
|
|
430
|
+
label="Gate on estimate"
|
|
431
|
+
title="Only run this companion when the task estimate clears a threshold (needs a Task Estimator earlier)"
|
|
432
|
+
@click="pipelines.toggleDraftGating(unit.companionIndex)"
|
|
433
|
+
/>
|
|
434
|
+
</div>
|
|
435
|
+
<div
|
|
436
|
+
v-if="pipelines.draftGating[unit.companionIndex]?.enabled"
|
|
437
|
+
class="flex flex-wrap items-center gap-2 border-t border-slate-800 pt-2"
|
|
438
|
+
>
|
|
439
|
+
<span class="text-[10px] text-slate-500">run when (any):</span>
|
|
440
|
+
<label class="text-slate-400">complexity ≥</label>
|
|
441
|
+
<input
|
|
442
|
+
v-model.number="pipelines.draftGating[unit.companionIndex]!.minComplexity"
|
|
443
|
+
type="number"
|
|
444
|
+
min="0"
|
|
445
|
+
max="1"
|
|
446
|
+
step="0.1"
|
|
447
|
+
class="w-14 rounded border border-slate-700 bg-slate-900 px-1.5 py-0.5 text-slate-100"
|
|
448
|
+
/>
|
|
449
|
+
<label class="text-slate-400">risk ≥</label>
|
|
450
|
+
<input
|
|
451
|
+
v-model.number="pipelines.draftGating[unit.companionIndex]!.minRisk"
|
|
452
|
+
type="number"
|
|
453
|
+
min="0"
|
|
454
|
+
max="1"
|
|
455
|
+
step="0.1"
|
|
456
|
+
class="w-14 rounded border border-slate-700 bg-slate-900 px-1.5 py-0.5 text-slate-100"
|
|
457
|
+
/>
|
|
458
|
+
<label class="text-slate-400">impact ≥</label>
|
|
459
|
+
<input
|
|
460
|
+
v-model.number="pipelines.draftGating[unit.companionIndex]!.minImpact"
|
|
461
|
+
type="number"
|
|
462
|
+
min="0"
|
|
463
|
+
max="1"
|
|
464
|
+
step="0.1"
|
|
465
|
+
class="w-14 rounded border border-slate-700 bg-slate-900 px-1.5 py-0.5 text-slate-100"
|
|
466
|
+
/>
|
|
467
|
+
</div>
|
|
468
|
+
</div>
|
|
469
|
+
|
|
470
|
+
<!-- Consensus config (shown when the step is consensus-enabled). -->
|
|
471
|
+
<div
|
|
472
|
+
v-if="pipelines.draftConsensus[unit.index]?.enabled"
|
|
473
|
+
class="ml-6 space-y-2 rounded-md border border-emerald-800/40 bg-emerald-950/20 p-2 text-xs"
|
|
474
|
+
>
|
|
475
|
+
<div class="flex items-center gap-2">
|
|
476
|
+
<label class="text-slate-400">Strategy</label>
|
|
477
|
+
<select
|
|
478
|
+
v-model="pipelines.draftConsensus[unit.index]!.strategy"
|
|
479
|
+
class="rounded border border-slate-700 bg-slate-900 px-1.5 py-0.5 text-slate-100"
|
|
480
|
+
>
|
|
481
|
+
<option v-for="s in CONSENSUS_STRATEGIES" :key="s.value" :value="s.value">
|
|
482
|
+
{{ s.label }}
|
|
483
|
+
</option>
|
|
484
|
+
</select>
|
|
485
|
+
<label
|
|
486
|
+
v-if="pipelines.draftConsensus[unit.index]!.strategy === 'debate'"
|
|
487
|
+
class="ml-2 text-slate-400"
|
|
488
|
+
>Rounds</label
|
|
489
|
+
>
|
|
490
|
+
<input
|
|
491
|
+
v-if="pipelines.draftConsensus[unit.index]!.strategy === 'debate'"
|
|
492
|
+
v-model.number="pipelines.draftConsensus[unit.index]!.rounds"
|
|
493
|
+
type="number"
|
|
494
|
+
min="1"
|
|
495
|
+
max="5"
|
|
496
|
+
placeholder="2"
|
|
497
|
+
class="w-12 rounded border border-slate-700 bg-slate-900 px-1.5 py-0.5 text-slate-100"
|
|
498
|
+
/>
|
|
499
|
+
</div>
|
|
500
|
+
|
|
501
|
+
<!-- participants -->
|
|
502
|
+
<div class="space-y-1">
|
|
503
|
+
<div
|
|
504
|
+
v-for="(p, pIdx) in pipelines.draftConsensus[unit.index]!.participants"
|
|
505
|
+
:key="p.id"
|
|
506
|
+
class="flex items-center gap-1.5"
|
|
507
|
+
>
|
|
508
|
+
<input
|
|
509
|
+
v-model="p.role"
|
|
510
|
+
placeholder="Role"
|
|
511
|
+
class="w-28 rounded border border-slate-700 bg-slate-900 px-1.5 py-0.5 text-slate-100"
|
|
512
|
+
/>
|
|
513
|
+
<input
|
|
514
|
+
v-model="p.modelId"
|
|
515
|
+
placeholder="Model id (optional)"
|
|
516
|
+
class="flex-1 rounded border border-slate-700 bg-slate-900 px-1.5 py-0.5 text-slate-300"
|
|
517
|
+
/>
|
|
518
|
+
<UButton
|
|
519
|
+
icon="i-lucide-x"
|
|
520
|
+
color="error"
|
|
521
|
+
variant="ghost"
|
|
522
|
+
size="xs"
|
|
523
|
+
:disabled="pipelines.draftConsensus[unit.index]!.participants.length <= 2"
|
|
524
|
+
title="Remove participant (min 2)"
|
|
525
|
+
@click="removeParticipant(unit.index, pIdx)"
|
|
526
|
+
/>
|
|
527
|
+
</div>
|
|
528
|
+
<UButton
|
|
529
|
+
icon="i-lucide-plus"
|
|
530
|
+
color="neutral"
|
|
531
|
+
variant="ghost"
|
|
532
|
+
size="xs"
|
|
533
|
+
label="Add participant"
|
|
534
|
+
@click="addParticipant(unit.index)"
|
|
535
|
+
/>
|
|
536
|
+
</div>
|
|
537
|
+
|
|
538
|
+
<!-- gating -->
|
|
539
|
+
<div class="flex flex-wrap items-center gap-2 border-t border-slate-800 pt-2">
|
|
540
|
+
<UButton
|
|
541
|
+
:icon="
|
|
542
|
+
pipelines.draftConsensus[unit.index]!.gating?.enabled
|
|
543
|
+
? 'i-lucide-toggle-right'
|
|
544
|
+
: 'i-lucide-toggle-left'
|
|
545
|
+
"
|
|
546
|
+
:color="
|
|
547
|
+
pipelines.draftConsensus[unit.index]!.gating?.enabled ? 'success' : 'neutral'
|
|
548
|
+
"
|
|
549
|
+
variant="ghost"
|
|
550
|
+
size="xs"
|
|
551
|
+
label="Gate on estimate"
|
|
552
|
+
title="Only run consensus when the task estimate clears a threshold (else the standard agent runs)"
|
|
553
|
+
@click="toggleGating(unit.index)"
|
|
554
|
+
/>
|
|
555
|
+
<template v-if="pipelines.draftConsensus[unit.index]!.gating?.enabled">
|
|
556
|
+
<label class="text-slate-400">risk ≥</label>
|
|
557
|
+
<input
|
|
558
|
+
v-model.number="pipelines.draftConsensus[unit.index]!.gating!.minRisk"
|
|
559
|
+
type="number"
|
|
560
|
+
min="0"
|
|
561
|
+
max="1"
|
|
562
|
+
step="0.1"
|
|
563
|
+
class="w-14 rounded border border-slate-700 bg-slate-900 px-1.5 py-0.5 text-slate-100"
|
|
564
|
+
/>
|
|
565
|
+
<label class="text-slate-400">impact ≥</label>
|
|
566
|
+
<input
|
|
567
|
+
v-model.number="pipelines.draftConsensus[unit.index]!.gating!.minImpact"
|
|
568
|
+
type="number"
|
|
569
|
+
min="0"
|
|
570
|
+
max="1"
|
|
571
|
+
step="0.1"
|
|
572
|
+
class="w-14 rounded border border-slate-700 bg-slate-900 px-1.5 py-0.5 text-slate-100"
|
|
573
|
+
/>
|
|
574
|
+
</template>
|
|
575
|
+
</div>
|
|
576
|
+
</div>
|
|
577
|
+
</li>
|
|
578
|
+
</ol>
|
|
579
|
+
|
|
580
|
+
<!-- Saved pipelines: review the library + delete (the run affordance
|
|
581
|
+
moved to the task card / inspector when the palettes were removed). -->
|
|
582
|
+
<div v-if="pipelines.pipelines.length" class="mt-4 border-t border-slate-800 pt-3">
|
|
583
|
+
<div class="mb-2 flex items-center justify-between gap-2">
|
|
584
|
+
<h3 class="text-xs font-semibold uppercase tracking-wide text-slate-400">
|
|
585
|
+
Saved pipelines
|
|
586
|
+
</h3>
|
|
587
|
+
<UButton
|
|
588
|
+
v-if="archivedCount"
|
|
589
|
+
:icon="showArchived ? 'i-lucide-archive-restore' : 'i-lucide-archive'"
|
|
590
|
+
:color="showArchived ? 'primary' : 'neutral'"
|
|
591
|
+
variant="ghost"
|
|
592
|
+
size="xs"
|
|
593
|
+
@click="showArchived = !showArchived"
|
|
594
|
+
>
|
|
595
|
+
{{ showArchived ? 'Hide archived' : `Archived (${archivedCount})` }}
|
|
596
|
+
</UButton>
|
|
597
|
+
</div>
|
|
598
|
+
|
|
599
|
+
<!-- Label filter chips. -->
|
|
600
|
+
<div v-if="allLabels.length" class="mb-2 flex flex-wrap items-center gap-1">
|
|
601
|
+
<UBadge
|
|
602
|
+
:color="labelFilter === null ? 'primary' : 'neutral'"
|
|
603
|
+
variant="soft"
|
|
604
|
+
size="xs"
|
|
605
|
+
class="cursor-pointer"
|
|
606
|
+
@click="labelFilter = null"
|
|
607
|
+
>
|
|
608
|
+
All
|
|
609
|
+
</UBadge>
|
|
610
|
+
<UBadge
|
|
611
|
+
v-for="l in allLabels"
|
|
612
|
+
:key="l"
|
|
613
|
+
:color="labelFilter === l ? 'primary' : 'neutral'"
|
|
614
|
+
variant="soft"
|
|
615
|
+
size="xs"
|
|
616
|
+
class="cursor-pointer"
|
|
617
|
+
@click="labelFilter = labelFilter === l ? null : l"
|
|
618
|
+
>
|
|
619
|
+
{{ l }}
|
|
620
|
+
</UBadge>
|
|
621
|
+
</div>
|
|
622
|
+
|
|
623
|
+
<ul class="space-y-1.5">
|
|
624
|
+
<li
|
|
625
|
+
v-for="p in visiblePipelines"
|
|
626
|
+
:key="p.id"
|
|
627
|
+
class="group rounded-lg border border-slate-700 bg-slate-800/40"
|
|
628
|
+
:class="{ 'opacity-60': p.archived }"
|
|
629
|
+
>
|
|
630
|
+
<div class="flex items-center gap-2 px-2 py-1.5">
|
|
631
|
+
<button
|
|
632
|
+
type="button"
|
|
633
|
+
class="flex min-w-0 flex-1 items-center gap-2 text-left"
|
|
634
|
+
@click="toggleSaved(p.id)"
|
|
635
|
+
>
|
|
636
|
+
<UIcon
|
|
637
|
+
:name="
|
|
638
|
+
expandedSaved.has(p.id) ? 'i-lucide-chevron-down' : 'i-lucide-chevron-right'
|
|
639
|
+
"
|
|
640
|
+
class="h-3.5 w-3.5 shrink-0 text-slate-500"
|
|
641
|
+
/>
|
|
642
|
+
<span class="min-w-0 flex-1 truncate text-xs text-slate-200">{{ p.name }}</span>
|
|
643
|
+
<UBadge
|
|
644
|
+
v-for="l in p.labels ?? []"
|
|
645
|
+
:key="l"
|
|
646
|
+
color="info"
|
|
647
|
+
variant="soft"
|
|
648
|
+
size="xs"
|
|
649
|
+
class="shrink-0"
|
|
650
|
+
>
|
|
651
|
+
{{ l }}
|
|
652
|
+
</UBadge>
|
|
653
|
+
<UBadge
|
|
654
|
+
v-if="p.builtin"
|
|
655
|
+
color="neutral"
|
|
656
|
+
variant="soft"
|
|
657
|
+
size="xs"
|
|
658
|
+
class="shrink-0"
|
|
659
|
+
>
|
|
660
|
+
default
|
|
661
|
+
</UBadge>
|
|
662
|
+
<span class="shrink-0 text-[10px] text-slate-500">
|
|
663
|
+
{{ p.agentKinds.length }} {{ p.agentKinds.length === 1 ? 'step' : 'steps' }}
|
|
664
|
+
</span>
|
|
665
|
+
</button>
|
|
666
|
+
<div
|
|
667
|
+
class="flex shrink-0 items-center opacity-0 transition group-hover:opacity-100"
|
|
668
|
+
>
|
|
669
|
+
<!-- Archive/unarchive: organize the library without deleting. Works on
|
|
670
|
+
built-ins too (view metadata, not structure). -->
|
|
671
|
+
<UButton
|
|
672
|
+
:icon="p.archived ? 'i-lucide-archive-restore' : 'i-lucide-archive'"
|
|
673
|
+
color="neutral"
|
|
674
|
+
variant="ghost"
|
|
675
|
+
size="xs"
|
|
676
|
+
:title="p.archived ? 'Unarchive' : 'Archive (hide from the default view)'"
|
|
677
|
+
@click="toggleArchive(p)"
|
|
678
|
+
/>
|
|
679
|
+
<!-- Clone is available on every pipeline — it's how a read-only
|
|
680
|
+
built-in template becomes an editable copy. -->
|
|
681
|
+
<UButton
|
|
682
|
+
icon="i-lucide-copy"
|
|
683
|
+
color="neutral"
|
|
684
|
+
variant="ghost"
|
|
685
|
+
size="xs"
|
|
686
|
+
:title="p.builtin ? 'Clone this default into an editable copy' : 'Clone'"
|
|
687
|
+
@click="clone(p)"
|
|
688
|
+
/>
|
|
689
|
+
<!-- Built-in templates are read-only; only custom pipelines edit in place. -->
|
|
690
|
+
<UButton
|
|
691
|
+
v-if="!p.builtin"
|
|
692
|
+
icon="i-lucide-pencil"
|
|
693
|
+
color="neutral"
|
|
694
|
+
variant="ghost"
|
|
695
|
+
size="xs"
|
|
696
|
+
title="Edit this pipeline"
|
|
697
|
+
@click="edit(p)"
|
|
698
|
+
/>
|
|
699
|
+
<!-- Built-in templates are read-only — they can be cloned but not
|
|
700
|
+
deleted (the backend rejects it too); only custom ones delete. -->
|
|
701
|
+
<UButton
|
|
702
|
+
v-if="!p.builtin"
|
|
703
|
+
icon="i-lucide-trash-2"
|
|
704
|
+
color="neutral"
|
|
705
|
+
variant="ghost"
|
|
706
|
+
size="xs"
|
|
707
|
+
@click="pipelines.removePipeline(p.id)"
|
|
708
|
+
/>
|
|
709
|
+
</div>
|
|
710
|
+
</div>
|
|
711
|
+
|
|
712
|
+
<!-- Full ordered step list, revealed on click. -->
|
|
713
|
+
<ol
|
|
714
|
+
v-if="expandedSaved.has(p.id)"
|
|
715
|
+
class="space-y-1 border-t border-slate-800 px-2 py-2 pl-7"
|
|
716
|
+
>
|
|
717
|
+
<li
|
|
718
|
+
v-for="(k, i) in p.agentKinds"
|
|
719
|
+
:key="i"
|
|
720
|
+
class="flex items-center gap-2"
|
|
721
|
+
:class="{ 'opacity-50 line-through': p.enabled?.[i] === false }"
|
|
722
|
+
:title="p.enabled?.[i] === false ? 'Disabled — skipped at run' : undefined"
|
|
723
|
+
>
|
|
724
|
+
<span class="w-4 shrink-0 text-center text-[10px] text-slate-500">{{
|
|
725
|
+
i + 1
|
|
726
|
+
}}</span>
|
|
727
|
+
<AgentKindIcon :kind="k" show-label />
|
|
728
|
+
</li>
|
|
729
|
+
</ol>
|
|
730
|
+
</li>
|
|
731
|
+
</ul>
|
|
732
|
+
</div>
|
|
733
|
+
</div>
|
|
734
|
+
</div>
|
|
735
|
+
</template>
|
|
736
|
+
|
|
737
|
+
<template #footer>
|
|
738
|
+
<div class="flex w-full items-center justify-between">
|
|
739
|
+
<UButton color="neutral" variant="ghost" size="sm" @click="pipelines.clearDraft()">
|
|
740
|
+
{{ pipelines.editingId ? 'Cancel edit' : 'Clear' }}
|
|
741
|
+
</UButton>
|
|
742
|
+
<UButton
|
|
743
|
+
color="primary"
|
|
744
|
+
icon="i-lucide-save"
|
|
745
|
+
size="sm"
|
|
746
|
+
:disabled="pipelines.draft.length === 0"
|
|
747
|
+
@click="save"
|
|
748
|
+
>
|
|
749
|
+
{{ pipelines.editingId ? 'Update pipeline' : 'Save pipeline' }}
|
|
750
|
+
</UButton>
|
|
751
|
+
</div>
|
|
752
|
+
</template>
|
|
753
|
+
</USlideover>
|
|
754
|
+
|
|
755
|
+
<!-- Add-agent form -->
|
|
756
|
+
<UModal v-model:open="addAgentOpen" title="Add agent">
|
|
757
|
+
<template #body>
|
|
758
|
+
<div class="space-y-3">
|
|
759
|
+
<div>
|
|
760
|
+
<label
|
|
761
|
+
class="mb-1 block text-[11px] font-semibold uppercase tracking-wide text-slate-400"
|
|
762
|
+
>
|
|
763
|
+
Name
|
|
764
|
+
</label>
|
|
765
|
+
<UInput
|
|
766
|
+
v-model="newAgentName"
|
|
767
|
+
placeholder="e.g. Security Auditor"
|
|
768
|
+
size="sm"
|
|
769
|
+
class="w-full"
|
|
770
|
+
/>
|
|
771
|
+
</div>
|
|
772
|
+
<div>
|
|
773
|
+
<label
|
|
774
|
+
class="mb-1 block text-[11px] font-semibold uppercase tracking-wide text-slate-400"
|
|
775
|
+
>
|
|
776
|
+
Description
|
|
777
|
+
</label>
|
|
778
|
+
<UTextarea
|
|
779
|
+
v-model="newAgentDesc"
|
|
780
|
+
:rows="2"
|
|
781
|
+
autoresize
|
|
782
|
+
size="sm"
|
|
783
|
+
class="w-full"
|
|
784
|
+
placeholder="What does this agent do?"
|
|
785
|
+
/>
|
|
786
|
+
</div>
|
|
787
|
+
<UButton
|
|
788
|
+
color="neutral"
|
|
789
|
+
variant="soft"
|
|
790
|
+
size="xs"
|
|
791
|
+
icon="i-lucide-file-text"
|
|
792
|
+
block
|
|
793
|
+
@click="placeholder('Link context document')"
|
|
794
|
+
>
|
|
795
|
+
Link context document
|
|
796
|
+
</UButton>
|
|
797
|
+
</div>
|
|
798
|
+
</template>
|
|
799
|
+
|
|
800
|
+
<template #footer>
|
|
801
|
+
<div class="flex w-full items-center justify-end gap-2">
|
|
802
|
+
<UButton color="neutral" variant="ghost" size="sm" @click="addAgentOpen = false">
|
|
803
|
+
Cancel
|
|
804
|
+
</UButton>
|
|
805
|
+
<UButton
|
|
806
|
+
color="primary"
|
|
807
|
+
icon="i-lucide-plus"
|
|
808
|
+
size="sm"
|
|
809
|
+
:disabled="!newAgentName.trim()"
|
|
810
|
+
@click="createAgent"
|
|
811
|
+
>
|
|
812
|
+
Create agent
|
|
813
|
+
</UButton>
|
|
814
|
+
</div>
|
|
815
|
+
</template>
|
|
816
|
+
</UModal>
|
|
817
|
+
</template>
|