@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,484 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { AgentState, ExecutionInstance } from '~/types/domain'
|
|
3
|
+
import { agentKindMeta } from '~/utils/catalog'
|
|
4
|
+
import {
|
|
5
|
+
subtaskIconClass,
|
|
6
|
+
gateCompanionFor,
|
|
7
|
+
COMPANION_STATE_META,
|
|
8
|
+
isCompanionKind,
|
|
9
|
+
isFailedStep,
|
|
10
|
+
FAILED_STEP_META,
|
|
11
|
+
} from '~/utils/pipelineRender'
|
|
12
|
+
import StepMetricsBar from '~/components/observability/StepMetricsBar.vue'
|
|
13
|
+
|
|
14
|
+
const props = defineProps<{ instance: ExecutionInstance }>()
|
|
15
|
+
const emit = defineEmits<{
|
|
16
|
+
openDecision: [decisionId: string]
|
|
17
|
+
openApproval: [approvalId: string]
|
|
18
|
+
}>()
|
|
19
|
+
|
|
20
|
+
const models = useModelsStore()
|
|
21
|
+
const ui = useUiStore()
|
|
22
|
+
const execution = useExecutionStore()
|
|
23
|
+
const reviews = useReviewStage()
|
|
24
|
+
|
|
25
|
+
// While an iterative reviewer gate (requirements-review / clarity-review) folds the
|
|
26
|
+
// answers / re-reviews in the background it needs NO human, so its parked approval is
|
|
27
|
+
// replaced by a working indicator — the human is summoned again only if findings remain.
|
|
28
|
+
function reviewStageLabel(agentKind: string | undefined): string | null {
|
|
29
|
+
if (!reviews.isBackground(agentKind, props.instance.blockId)) return null
|
|
30
|
+
const stage = reviews.stageForBlock(props.instance.blockId)
|
|
31
|
+
return stage === 'incorporating'
|
|
32
|
+
? 'Incorporating…'
|
|
33
|
+
: stage === 'reviewing'
|
|
34
|
+
? 'Re-reviewing…'
|
|
35
|
+
: null
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Clicking an agent opens its step-detail overlay — execution metadata (state,
|
|
39
|
+
// timing, model, subtasks) plus the full prose output when the agent produced one.
|
|
40
|
+
function openStep(i: number) {
|
|
41
|
+
ui.openStepDetail(props.instance.id, i)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// --- restart from a step -----------------------------------------------------
|
|
45
|
+
// Re-run the pipeline from a chosen step onward: the server resets that step +
|
|
46
|
+
// every later step's iteration counters and re-drives a fresh run, keeping the
|
|
47
|
+
// earlier steps' outputs as handoff context. Destructive (later results are
|
|
48
|
+
// dropped), so the hover button arms a two-click confirm. A step with its own
|
|
49
|
+
// unresolved approval is excluded — the approval rail owns that interaction.
|
|
50
|
+
const restartArmed = ref<number | null>(null)
|
|
51
|
+
const restarting = ref<number | null>(null)
|
|
52
|
+
function canRestart(s: ExecutionInstance['steps'][number]) {
|
|
53
|
+
return !(s.approval && s.approval.status === 'pending')
|
|
54
|
+
}
|
|
55
|
+
async function restartFromHere(i: number) {
|
|
56
|
+
if (restarting.value !== null) return
|
|
57
|
+
restarting.value = i
|
|
58
|
+
try {
|
|
59
|
+
await execution.restartFromStep(props.instance.id, i)
|
|
60
|
+
} finally {
|
|
61
|
+
restarting.value = null
|
|
62
|
+
restartArmed.value = null
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Visual language for an individual agent's runtime state. */
|
|
67
|
+
const STATE_META: Record<AgentState, { label: string; color: string; icon: string }> = {
|
|
68
|
+
pending: { label: 'Pending', color: '#64748b', icon: 'i-lucide-circle-dashed' },
|
|
69
|
+
working: { label: 'Working', color: '#6366f1', icon: 'i-lucide-loader' },
|
|
70
|
+
waiting_decision: { label: 'Needs decision', color: '#f59e0b', icon: 'i-lucide-circle-help' },
|
|
71
|
+
done: { label: 'Done', color: '#22c55e', icon: 'i-lucide-circle-check' },
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Visual language for the pipeline instance as a whole. */
|
|
75
|
+
const STATUS_META: Record<ExecutionInstance['status'], { label: string; chip: string }> = {
|
|
76
|
+
running: { label: 'Running', chip: 'primary' },
|
|
77
|
+
blocked: { label: 'Needs you', chip: 'warning' },
|
|
78
|
+
paused: { label: 'Paused (budget)', chip: 'neutral' },
|
|
79
|
+
done: { label: 'Completed', chip: 'success' },
|
|
80
|
+
failed: { label: 'Failed', chip: 'error' },
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const steps = computed(() => props.instance.steps)
|
|
84
|
+
const total = computed(() => steps.value.length)
|
|
85
|
+
|
|
86
|
+
// The conditionally-run companion (e.g. the Tester's `fixer`) each step drives, with
|
|
87
|
+
// its possible/running/completed/skipped state — rendered as a distinct sub-node so a
|
|
88
|
+
// human can see at a glance whether the fixer ran or was skipped.
|
|
89
|
+
const companionByStep = computed(() => steps.value.map((s) => gateCompanionFor(s, runFailed.value)))
|
|
90
|
+
|
|
91
|
+
// A failed run is no longer executing: a step left mid-flight (state still
|
|
92
|
+
// `working`, `startingContainer` still set) must stop looking live — no spinner,
|
|
93
|
+
// no pulse, no "spinning up container" phase.
|
|
94
|
+
const runFailed = computed(() => props.instance.status === 'failed')
|
|
95
|
+
/** A step that is genuinely, currently working (not a stale mid-flight step). */
|
|
96
|
+
function liveWorking(state: AgentState) {
|
|
97
|
+
return state === 'working' && !runFailed.value
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* The state visual (label/color/icon) for a step: a step left `working` when the run
|
|
101
|
+
* failed reads as "Failed" with a red cross, not a frozen "Working" loader.
|
|
102
|
+
*/
|
|
103
|
+
function stepVisual(state: AgentState) {
|
|
104
|
+
return isFailedStep(state, runFailed.value) ? FAILED_STEP_META : STATE_META[state]
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** A step counts as fully complete only once its state is `done`. */
|
|
108
|
+
const completedCount = computed(() => steps.value.filter((s) => s.state === 'done').length)
|
|
109
|
+
|
|
110
|
+
/** Effective 0..1 progress per step (a done step is always 100%). */
|
|
111
|
+
function stepProgress(state: AgentState, progress: number) {
|
|
112
|
+
return state === 'done' ? 1 : progress
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** Overall pipeline progress: the mean of every step's effective progress. */
|
|
116
|
+
const overallProgress = computed(() => {
|
|
117
|
+
if (!total.value) return 0
|
|
118
|
+
const sum = steps.value.reduce((acc, s) => acc + stepProgress(s.state, s.progress), 0)
|
|
119
|
+
return sum / total.value
|
|
120
|
+
})
|
|
121
|
+
const overallPct = computed(() => Math.round(overallProgress.value * 100))
|
|
122
|
+
|
|
123
|
+
const statusMeta = computed(() => STATUS_META[props.instance.status])
|
|
124
|
+
|
|
125
|
+
/** The agent the pipeline is currently centred on (for the summary line). */
|
|
126
|
+
const currentAgent = computed(() => {
|
|
127
|
+
const s = steps.value[props.instance.currentStep]
|
|
128
|
+
return s ? agentKindMeta(s.agentKind).label : null
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
/** Connector below a step is "lit" once that step has completed. */
|
|
132
|
+
function connectorDone(index: number) {
|
|
133
|
+
return steps.value[index]?.state === 'done'
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const legend: { state: AgentState }[] = [
|
|
137
|
+
{ state: 'done' },
|
|
138
|
+
{ state: 'working' },
|
|
139
|
+
{ state: 'waiting_decision' },
|
|
140
|
+
{ state: 'pending' },
|
|
141
|
+
]
|
|
142
|
+
|
|
143
|
+
// Icon per todo-item status, matching how the bootstrap card renders its
|
|
144
|
+
// subtask breakdown — so a zoomed-in task shows the same live todo list.
|
|
145
|
+
const ITEM_ICON: Record<string, string> = {
|
|
146
|
+
completed: 'i-lucide-check-circle-2',
|
|
147
|
+
in_progress: 'i-lucide-loader-circle',
|
|
148
|
+
pending: 'i-lucide-circle',
|
|
149
|
+
}
|
|
150
|
+
</script>
|
|
151
|
+
|
|
152
|
+
<template>
|
|
153
|
+
<div class="flex flex-col gap-5">
|
|
154
|
+
<!-- summary -->
|
|
155
|
+
<div class="rounded-xl border border-slate-800 bg-slate-900/60 p-4">
|
|
156
|
+
<div class="flex flex-wrap items-center gap-3">
|
|
157
|
+
<UBadge :color="statusMeta.chip as any" variant="subtle">{{ statusMeta.label }}</UBadge>
|
|
158
|
+
<span class="text-sm text-slate-300">
|
|
159
|
+
<span class="font-semibold text-white">{{ completedCount }}</span>
|
|
160
|
+
/ {{ total }} agents complete
|
|
161
|
+
</span>
|
|
162
|
+
<span v-if="currentAgent && instance.status === 'running'" class="text-xs text-slate-500">
|
|
163
|
+
· currently {{ currentAgent }}
|
|
164
|
+
</span>
|
|
165
|
+
<span class="ml-auto font-mono text-sm tabular-nums text-slate-200">{{ overallPct }}%</span>
|
|
166
|
+
</div>
|
|
167
|
+
<UProgress :model-value="overallPct" class="mt-3" />
|
|
168
|
+
|
|
169
|
+
<!-- legend -->
|
|
170
|
+
<div class="mt-3 flex flex-wrap items-center gap-x-4 gap-y-1">
|
|
171
|
+
<span
|
|
172
|
+
v-for="l in legend"
|
|
173
|
+
:key="l.state"
|
|
174
|
+
class="inline-flex items-center gap-1.5 text-[11px] text-slate-400"
|
|
175
|
+
>
|
|
176
|
+
<span
|
|
177
|
+
class="h-2 w-2 rounded-full"
|
|
178
|
+
:style="{ backgroundColor: STATE_META[l.state].color }"
|
|
179
|
+
/>
|
|
180
|
+
{{ STATE_META[l.state].label }}
|
|
181
|
+
</span>
|
|
182
|
+
</div>
|
|
183
|
+
</div>
|
|
184
|
+
|
|
185
|
+
<!-- agent chain as a vertical timeline -->
|
|
186
|
+
<ol class="flex flex-col">
|
|
187
|
+
<li v-for="(s, i) in steps" :key="i" class="relative flex gap-4 pb-5 last:pb-0">
|
|
188
|
+
<!-- connector line to the next step -->
|
|
189
|
+
<span
|
|
190
|
+
v-if="i < steps.length - 1"
|
|
191
|
+
class="absolute top-9 bottom-0 left-[17px] w-0.5 -translate-x-1/2"
|
|
192
|
+
:class="connectorDone(i) ? 'bg-emerald-500/60' : 'bg-slate-700'"
|
|
193
|
+
/>
|
|
194
|
+
|
|
195
|
+
<!-- rail node -->
|
|
196
|
+
<span
|
|
197
|
+
class="relative z-10 flex h-9 w-9 shrink-0 items-center justify-center rounded-full border-2 bg-slate-950"
|
|
198
|
+
:class="liveWorking(s.state) ? 'step-active' : ''"
|
|
199
|
+
:style="{ borderColor: stepVisual(s.state).color }"
|
|
200
|
+
>
|
|
201
|
+
<UIcon
|
|
202
|
+
:name="stepVisual(s.state).icon"
|
|
203
|
+
class="h-4 w-4"
|
|
204
|
+
:class="liveWorking(s.state) ? 'animate-spin' : ''"
|
|
205
|
+
:style="{ color: stepVisual(s.state).color }"
|
|
206
|
+
/>
|
|
207
|
+
</span>
|
|
208
|
+
|
|
209
|
+
<!-- step content card -->
|
|
210
|
+
<div
|
|
211
|
+
class="flex-1 rounded-xl border p-4 transition"
|
|
212
|
+
:class="[
|
|
213
|
+
i === instance.currentStep && instance.status !== 'done'
|
|
214
|
+
? 'border-indigo-500/70 bg-slate-900 shadow-lg shadow-indigo-500/10'
|
|
215
|
+
: 'border-slate-800 bg-slate-900/50',
|
|
216
|
+
s.state === 'pending' ? 'opacity-60' : '',
|
|
217
|
+
]"
|
|
218
|
+
>
|
|
219
|
+
<div
|
|
220
|
+
class="group flex cursor-pointer items-center gap-2"
|
|
221
|
+
:title="s.output ? 'View details & read output' : 'View step details'"
|
|
222
|
+
@click="openStep(i)"
|
|
223
|
+
>
|
|
224
|
+
<div
|
|
225
|
+
class="flex h-8 w-8 items-center justify-center rounded-lg"
|
|
226
|
+
:style="{ backgroundColor: agentKindMeta(s.agentKind).color + '22' }"
|
|
227
|
+
>
|
|
228
|
+
<UIcon
|
|
229
|
+
:name="agentKindMeta(s.agentKind).icon"
|
|
230
|
+
class="h-4 w-4"
|
|
231
|
+
:style="{ color: agentKindMeta(s.agentKind).color }"
|
|
232
|
+
/>
|
|
233
|
+
</div>
|
|
234
|
+
<div class="min-w-0">
|
|
235
|
+
<div class="flex items-center gap-1.5">
|
|
236
|
+
<span class="truncate text-sm font-semibold text-white">
|
|
237
|
+
{{ agentKindMeta(s.agentKind).label }}
|
|
238
|
+
</span>
|
|
239
|
+
<span
|
|
240
|
+
v-if="isCompanionKind(s.agentKind)"
|
|
241
|
+
class="shrink-0 rounded bg-slate-700/60 px-1 text-[9px] font-medium uppercase tracking-wide text-slate-300"
|
|
242
|
+
title="Companion of a producer step"
|
|
243
|
+
>
|
|
244
|
+
Companion
|
|
245
|
+
</span>
|
|
246
|
+
</div>
|
|
247
|
+
<div class="text-[10px] uppercase tracking-wide text-slate-500">
|
|
248
|
+
Step {{ i + 1 }} of {{ total }}
|
|
249
|
+
</div>
|
|
250
|
+
</div>
|
|
251
|
+
<span
|
|
252
|
+
class="ml-auto shrink-0 text-[11px] font-medium"
|
|
253
|
+
:style="{ color: stepVisual(s.state).color }"
|
|
254
|
+
>
|
|
255
|
+
{{ stepVisual(s.state).label }}
|
|
256
|
+
</span>
|
|
257
|
+
|
|
258
|
+
<!-- restart-from-here: revealed on row hover, arms a two-click confirm
|
|
259
|
+
(resetting later steps is destructive). Stops propagation so it
|
|
260
|
+
doesn't also open the step-detail overlay. -->
|
|
261
|
+
<template v-if="canRestart(s)">
|
|
262
|
+
<UButton
|
|
263
|
+
v-if="restartArmed !== i"
|
|
264
|
+
icon="i-lucide-rotate-ccw"
|
|
265
|
+
color="neutral"
|
|
266
|
+
variant="ghost"
|
|
267
|
+
size="xs"
|
|
268
|
+
class="shrink-0 opacity-0 transition-opacity group-hover:opacity-100"
|
|
269
|
+
title="Restart pipeline from this step"
|
|
270
|
+
@click.stop="restartArmed = i"
|
|
271
|
+
/>
|
|
272
|
+
<template v-else>
|
|
273
|
+
<UButton
|
|
274
|
+
color="warning"
|
|
275
|
+
variant="soft"
|
|
276
|
+
size="xs"
|
|
277
|
+
icon="i-lucide-rotate-ccw"
|
|
278
|
+
:loading="restarting === i"
|
|
279
|
+
class="shrink-0"
|
|
280
|
+
@click.stop="restartFromHere(i)"
|
|
281
|
+
>
|
|
282
|
+
Restart from here
|
|
283
|
+
</UButton>
|
|
284
|
+
<UButton
|
|
285
|
+
color="neutral"
|
|
286
|
+
variant="ghost"
|
|
287
|
+
size="xs"
|
|
288
|
+
class="shrink-0"
|
|
289
|
+
:disabled="restarting === i"
|
|
290
|
+
@click.stop="restartArmed = null"
|
|
291
|
+
>
|
|
292
|
+
Cancel
|
|
293
|
+
</UButton>
|
|
294
|
+
</template>
|
|
295
|
+
</template>
|
|
296
|
+
|
|
297
|
+
<UIcon
|
|
298
|
+
:name="s.output ? 'i-lucide-book-open-text' : 'i-lucide-info'"
|
|
299
|
+
class="h-4 w-4 shrink-0 text-slate-500 transition-colors group-hover:text-indigo-300"
|
|
300
|
+
/>
|
|
301
|
+
</div>
|
|
302
|
+
|
|
303
|
+
<!-- per-step progress (only while it has meaningful progress) -->
|
|
304
|
+
<UProgress
|
|
305
|
+
v-if="s.state === 'working' || s.state === 'done'"
|
|
306
|
+
:model-value="Math.round(stepProgress(s.state, s.progress) * 100)"
|
|
307
|
+
size="xs"
|
|
308
|
+
class="mt-3"
|
|
309
|
+
/>
|
|
310
|
+
|
|
311
|
+
<!-- container cold-boot phase: shown until the container is up and the
|
|
312
|
+
agent starts reporting progress -->
|
|
313
|
+
<div
|
|
314
|
+
v-if="s.startingContainer && !runFailed"
|
|
315
|
+
class="mt-2 flex items-center gap-1.5 text-[11px] text-sky-300"
|
|
316
|
+
>
|
|
317
|
+
<UIcon name="i-lucide-loader-circle" class="h-3.5 w-3.5 shrink-0 animate-spin" />
|
|
318
|
+
<span>Spinning up container…</span>
|
|
319
|
+
</div>
|
|
320
|
+
|
|
321
|
+
<!-- live subtask counts from the agent's todo list -->
|
|
322
|
+
<div v-if="s.subtasks && s.subtasks.total > 0" class="mt-2">
|
|
323
|
+
<div class="flex items-center justify-between text-[10px] text-slate-400">
|
|
324
|
+
<span>
|
|
325
|
+
{{ s.subtasks.completed }}/{{ s.subtasks.total }} subtasks
|
|
326
|
+
<span v-if="s.subtasks.inProgress > 0" class="text-indigo-300">
|
|
327
|
+
· {{ s.subtasks.inProgress }} in progress
|
|
328
|
+
</span>
|
|
329
|
+
</span>
|
|
330
|
+
</div>
|
|
331
|
+
<div class="mt-1 h-1 overflow-hidden rounded-full bg-slate-700/60">
|
|
332
|
+
<div
|
|
333
|
+
class="h-full rounded-full bg-indigo-400 transition-all duration-500"
|
|
334
|
+
:style="{ width: `${(s.subtasks.completed / s.subtasks.total) * 100}%` }"
|
|
335
|
+
/>
|
|
336
|
+
</div>
|
|
337
|
+
|
|
338
|
+
<!-- the actual todo breakdown, rendered the same way the bootstrap
|
|
339
|
+
card shows its subtasks (status icon + struck-through when done) -->
|
|
340
|
+
<ul v-if="s.subtasks.items?.length" class="mt-2 space-y-1">
|
|
341
|
+
<li
|
|
342
|
+
v-for="(item, i) in s.subtasks.items"
|
|
343
|
+
:key="i"
|
|
344
|
+
class="flex items-start gap-1.5 text-[11px]"
|
|
345
|
+
:class="
|
|
346
|
+
item.status === 'completed'
|
|
347
|
+
? 'text-slate-500 line-through'
|
|
348
|
+
: item.status === 'in_progress'
|
|
349
|
+
? 'text-slate-100'
|
|
350
|
+
: 'text-slate-400'
|
|
351
|
+
"
|
|
352
|
+
>
|
|
353
|
+
<UIcon
|
|
354
|
+
:name="ITEM_ICON[item.status]"
|
|
355
|
+
class="mt-px h-3 w-3 shrink-0"
|
|
356
|
+
:class="subtaskIconClass(item.status, runFailed)"
|
|
357
|
+
/>
|
|
358
|
+
<span>{{ item.label }}</span>
|
|
359
|
+
</li>
|
|
360
|
+
</ul>
|
|
361
|
+
</div>
|
|
362
|
+
|
|
363
|
+
<!-- model used for this step -->
|
|
364
|
+
<p
|
|
365
|
+
v-if="s.model"
|
|
366
|
+
class="mt-2 flex items-center gap-1 truncate text-[10px] text-slate-500"
|
|
367
|
+
:title="s.model"
|
|
368
|
+
>
|
|
369
|
+
<UIcon name="i-lucide-cpu" class="h-3 w-3 shrink-0" />
|
|
370
|
+
{{ models.labelForRef(s.model) }}
|
|
371
|
+
</p>
|
|
372
|
+
|
|
373
|
+
<!-- LLM observability rollup (tokens, output-limit headroom, transport-
|
|
374
|
+
vs-execution); click opens the full per-call activity panel. -->
|
|
375
|
+
<StepMetricsBar
|
|
376
|
+
v-if="s.metrics && s.metrics.calls > 0"
|
|
377
|
+
:metrics="s.metrics"
|
|
378
|
+
clickable
|
|
379
|
+
class="mt-2"
|
|
380
|
+
@inspect="ui.openObservability(instance.id)"
|
|
381
|
+
/>
|
|
382
|
+
|
|
383
|
+
<!-- A one-line hint that the agent produced prose; the full output (and
|
|
384
|
+
all step metadata) lives in the step-detail overlay opened by click. -->
|
|
385
|
+
<p v-if="s.output" class="mt-2 flex items-center gap-1 text-[11px] text-slate-500">
|
|
386
|
+
<UIcon name="i-lucide-book-open-text" class="h-3 w-3 shrink-0" />
|
|
387
|
+
Click to read this agent’s output
|
|
388
|
+
</p>
|
|
389
|
+
|
|
390
|
+
<!-- Conditionally-run companion (today the Tester's fixer): a distinct
|
|
391
|
+
sub-node marked possible / running / completed / skipped. -->
|
|
392
|
+
<div
|
|
393
|
+
v-if="companionByStep[i]"
|
|
394
|
+
class="mt-3 flex items-center gap-2 rounded-lg border border-dashed border-slate-700/70 bg-slate-900/40 px-2.5 py-1.5"
|
|
395
|
+
>
|
|
396
|
+
<span
|
|
397
|
+
class="flex h-6 w-6 shrink-0 items-center justify-center rounded-md border"
|
|
398
|
+
:class="COMPANION_STATE_META[companionByStep[i]!.state].dot"
|
|
399
|
+
>
|
|
400
|
+
<UIcon
|
|
401
|
+
:name="agentKindMeta(companionByStep[i]!.kind).icon"
|
|
402
|
+
class="h-3 w-3"
|
|
403
|
+
:class="[
|
|
404
|
+
COMPANION_STATE_META[companionByStep[i]!.state].text,
|
|
405
|
+
companionByStep[i]!.state === 'running' && !runFailed ? 'animate-spin' : '',
|
|
406
|
+
]"
|
|
407
|
+
/>
|
|
408
|
+
</span>
|
|
409
|
+
<span class="min-w-0 flex-1 truncate text-[12px] text-slate-300">
|
|
410
|
+
{{ agentKindMeta(companionByStep[i]!.kind).label }}
|
|
411
|
+
<span class="text-slate-500">(companion)</span>
|
|
412
|
+
</span>
|
|
413
|
+
<span
|
|
414
|
+
class="shrink-0 text-[11px] font-medium"
|
|
415
|
+
:class="COMPANION_STATE_META[companionByStep[i]!.state].text"
|
|
416
|
+
>
|
|
417
|
+
{{ COMPANION_STATE_META[companionByStep[i]!.state].label }}
|
|
418
|
+
</span>
|
|
419
|
+
</div>
|
|
420
|
+
|
|
421
|
+
<!-- reviewer gate folding/re-reviewing in the background: a working indicator,
|
|
422
|
+
NOT a "Review & approve" gate (the human is summoned only if needed) -->
|
|
423
|
+
<div
|
|
424
|
+
v-if="reviewStageLabel(s.agentKind)"
|
|
425
|
+
class="mt-3 inline-flex items-center gap-1 text-[11px] text-indigo-300"
|
|
426
|
+
>
|
|
427
|
+
<UIcon name="i-lucide-loader-circle" class="h-3 w-3 animate-spin" />
|
|
428
|
+
{{ reviewStageLabel(s.agentKind) }}
|
|
429
|
+
</div>
|
|
430
|
+
|
|
431
|
+
<!-- approval gate: review (and edit) the proposal before continuing -->
|
|
432
|
+
<div v-else-if="s.approval && s.approval.status === 'pending'" class="mt-3">
|
|
433
|
+
<UButton
|
|
434
|
+
color="warning"
|
|
435
|
+
variant="soft"
|
|
436
|
+
size="xs"
|
|
437
|
+
icon="i-lucide-shield-check"
|
|
438
|
+
@click="emit('openApproval', s.approval.id)"
|
|
439
|
+
>
|
|
440
|
+
Review & approve {{ agentKindMeta(s.agentKind).label }}'s proposal
|
|
441
|
+
</UButton>
|
|
442
|
+
</div>
|
|
443
|
+
|
|
444
|
+
<!-- decision: unresolved => prompt, resolved => show the choice -->
|
|
445
|
+
<div v-else-if="s.decision && !s.decision.chosen" class="mt-3">
|
|
446
|
+
<UButton
|
|
447
|
+
color="warning"
|
|
448
|
+
variant="soft"
|
|
449
|
+
size="xs"
|
|
450
|
+
icon="i-lucide-circle-help"
|
|
451
|
+
@click="emit('openDecision', s.decision.id)"
|
|
452
|
+
>
|
|
453
|
+
Resolve: {{ s.decision.question }}
|
|
454
|
+
</UButton>
|
|
455
|
+
</div>
|
|
456
|
+
<p
|
|
457
|
+
v-else-if="s.decision?.chosen"
|
|
458
|
+
class="mt-2 flex items-center gap-1 truncate text-[11px] text-emerald-400"
|
|
459
|
+
:title="s.decision.chosen"
|
|
460
|
+
>
|
|
461
|
+
<UIcon name="i-lucide-check" class="h-3 w-3 shrink-0" />
|
|
462
|
+
{{ s.decision.chosen }}
|
|
463
|
+
</p>
|
|
464
|
+
</div>
|
|
465
|
+
</li>
|
|
466
|
+
</ol>
|
|
467
|
+
</div>
|
|
468
|
+
</template>
|
|
469
|
+
|
|
470
|
+
<style scoped>
|
|
471
|
+
/* Soft indigo halo around the rail node of the actively-working step. */
|
|
472
|
+
@keyframes step-pulse {
|
|
473
|
+
0%,
|
|
474
|
+
100% {
|
|
475
|
+
box-shadow: 0 0 0 0 rgba(99, 102, 241, 0.5);
|
|
476
|
+
}
|
|
477
|
+
50% {
|
|
478
|
+
box-shadow: 0 0 0 6px rgba(99, 102, 241, 0);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
.step-active {
|
|
482
|
+
animation: step-pulse 1.6s ease-in-out infinite;
|
|
483
|
+
}
|
|
484
|
+
</style>
|