@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,455 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AgentArchetype,
|
|
3
|
+
AgentCategory,
|
|
4
|
+
AgentKind,
|
|
5
|
+
BlockStatus,
|
|
6
|
+
BlockType,
|
|
7
|
+
} from '~/types/domain'
|
|
8
|
+
|
|
9
|
+
/** Simple unique id helper (fine for a client-only prototype). */
|
|
10
|
+
export function uid(prefix = 'id'): string {
|
|
11
|
+
return `${prefix}_${Math.random().toString(36).slice(2, 9)}`
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Ordered palette categories — the collapsible sections the pipeline builder groups the
|
|
16
|
+
* agent archetypes under. The order here is the order they render.
|
|
17
|
+
*/
|
|
18
|
+
export const AGENT_CATEGORIES: { id: AgentCategory; label: string }[] = [
|
|
19
|
+
{ id: 'review', label: 'Review & triage' },
|
|
20
|
+
{ id: 'design', label: 'Design & research' },
|
|
21
|
+
{ id: 'build', label: 'Implementation' },
|
|
22
|
+
{ id: 'test', label: 'Testing' },
|
|
23
|
+
{ id: 'docs', label: 'Documentation' },
|
|
24
|
+
{ id: 'gates', label: 'Gates & observability' },
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
/** The agent palette — the building blocks of a development pipeline, grouped by category. */
|
|
28
|
+
export const AGENT_ARCHETYPES: AgentArchetype[] = [
|
|
29
|
+
{
|
|
30
|
+
kind: 'requirements-review',
|
|
31
|
+
label: 'Requirements Reviewer',
|
|
32
|
+
icon: 'i-lucide-clipboard-check',
|
|
33
|
+
color: '#f59e0b',
|
|
34
|
+
category: 'review',
|
|
35
|
+
description:
|
|
36
|
+
'Reviews the collected context (description + linked PRDs/RFCs) for gaps, ambiguities, assumptions and risks before the architect starts.',
|
|
37
|
+
// Opens the dedicated structured review window (answer/dismiss findings → incorporate
|
|
38
|
+
// → re-review loop) instead of the generic prose step-detail panel.
|
|
39
|
+
resultView: 'requirements-review',
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
kind: 'clarity-review',
|
|
43
|
+
label: 'Clarity Reviewer',
|
|
44
|
+
icon: 'i-lucide-bug',
|
|
45
|
+
color: '#f59e0b',
|
|
46
|
+
category: 'review',
|
|
47
|
+
description:
|
|
48
|
+
'Triages a bug report for fixability — raising questions, gaps and assumptions about the report before anyone starts fixing it.',
|
|
49
|
+
// Opens the dedicated structured review window (answer/dismiss findings → incorporate
|
|
50
|
+
// → re-review loop) instead of the generic prose step-detail panel.
|
|
51
|
+
resultView: 'clarity-review',
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
// A read-only `/explore` agent (like the architect), so it's a first-class palette block
|
|
55
|
+
// a user can add to any pipeline — not just the `pl_bugfix` preset where it leads. No
|
|
56
|
+
// `resultView`: the enriched report is prose, so it uses the generic step-detail panel.
|
|
57
|
+
kind: 'bug-investigator',
|
|
58
|
+
label: 'Bug Investigator',
|
|
59
|
+
icon: 'i-lucide-search-code',
|
|
60
|
+
color: '#38bdf8',
|
|
61
|
+
category: 'review',
|
|
62
|
+
description:
|
|
63
|
+
'Read-only codebase investigation that traces the bug to its root cause and produces an enriched report (no code changes).',
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
kind: 'task-estimator',
|
|
67
|
+
label: 'Task Estimator',
|
|
68
|
+
icon: 'i-lucide-gauge',
|
|
69
|
+
color: '#eab308',
|
|
70
|
+
category: 'review',
|
|
71
|
+
description:
|
|
72
|
+
'Triages the task after requirements are clarified — rates Complexity, Risk and Impact (0..1). Used to gate consensus and conditional companion steps, and shown as ratings on the task.',
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
kind: 'architect',
|
|
76
|
+
label: 'Architect',
|
|
77
|
+
icon: 'i-lucide-drafting-compass',
|
|
78
|
+
color: '#a78bfa',
|
|
79
|
+
category: 'design',
|
|
80
|
+
description: 'Designs the shape of the solution and breaks down the work.',
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
kind: 'researcher',
|
|
84
|
+
label: 'Researcher',
|
|
85
|
+
icon: 'i-lucide-telescope',
|
|
86
|
+
color: '#38bdf8',
|
|
87
|
+
category: 'design',
|
|
88
|
+
description: 'Investigates prior art, libraries and constraints.',
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
kind: 'coder',
|
|
92
|
+
label: 'Coder',
|
|
93
|
+
icon: 'i-lucide-code-xml',
|
|
94
|
+
color: '#34d399',
|
|
95
|
+
category: 'build',
|
|
96
|
+
description: 'Implements the block according to the design.',
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
kind: 'integrator',
|
|
100
|
+
label: 'Integrator',
|
|
101
|
+
icon: 'i-lucide-plug-zap',
|
|
102
|
+
color: '#fb923c',
|
|
103
|
+
category: 'build',
|
|
104
|
+
description: 'Wires the block into the surrounding system.',
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
kind: 'mocker',
|
|
108
|
+
label: 'Mock Builder',
|
|
109
|
+
icon: 'i-lucide-server-cog',
|
|
110
|
+
color: '#fb7185',
|
|
111
|
+
category: 'build',
|
|
112
|
+
description: 'Builds WireMock mocks for external services and wires them into local/CI runs.',
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
kind: 'tester',
|
|
116
|
+
label: 'Tester',
|
|
117
|
+
icon: 'i-lucide-flask-conical',
|
|
118
|
+
color: '#fbbf24',
|
|
119
|
+
category: 'test',
|
|
120
|
+
description: 'Exercises the change against the mocks + spec scenarios and reports outcomes.',
|
|
121
|
+
// Opens the dedicated structured test-report window (scenarios → outcomes →
|
|
122
|
+
// concerns tree) instead of the generic prose step-detail panel.
|
|
123
|
+
resultView: 'tester',
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
kind: 'playwright',
|
|
127
|
+
label: 'Acceptance Test Author',
|
|
128
|
+
icon: 'i-lucide-theater',
|
|
129
|
+
color: '#e879f9',
|
|
130
|
+
category: 'test',
|
|
131
|
+
description:
|
|
132
|
+
"Turns scenarios into runnable tests — Playwright for frontend, the project's own framework for backend; adds only new ones.",
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
kind: 'documenter',
|
|
136
|
+
label: 'Documenter',
|
|
137
|
+
icon: 'i-lucide-book-open-text',
|
|
138
|
+
color: '#818cf8',
|
|
139
|
+
category: 'docs',
|
|
140
|
+
description: 'Produces docs and usage examples.',
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
kind: 'business-documenter',
|
|
144
|
+
label: 'Domain Rules Documenter',
|
|
145
|
+
icon: 'i-lucide-scroll-text',
|
|
146
|
+
color: '#84cc16',
|
|
147
|
+
category: 'docs',
|
|
148
|
+
description:
|
|
149
|
+
'Reads the implementation and writes/updates business-logic & domain-rule docs in the repo, weaving in linked context documents.',
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
kind: 'business-reviewer',
|
|
153
|
+
label: 'Domain Rules Reviewer',
|
|
154
|
+
icon: 'i-lucide-shield-alert',
|
|
155
|
+
color: '#ef4444',
|
|
156
|
+
category: 'docs',
|
|
157
|
+
description:
|
|
158
|
+
'Reviews a change against the documented domain rules and reports violations, undocumented changes and unexpected drift.',
|
|
159
|
+
},
|
|
160
|
+
]
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Companion archetypes — dependent agents that review a specific producer and loop it back
|
|
164
|
+
* for rework. They are NOT free palette blocks: the builder surfaces them as a toggle on
|
|
165
|
+
* their producer step (a reviewer right after the coder it reviews), because a companion
|
|
166
|
+
* makes no sense without its producer or anywhere else in the chain. They still need display
|
|
167
|
+
* metadata (icons / labels) for the run timeline + saved-pipeline rendering, and their model
|
|
168
|
+
* is pinnable, so they live here and are folded into {@link AGENT_BY_KIND}. See
|
|
169
|
+
* {@link companionForProducer} for the producer→companion mapping (mirrors the backend
|
|
170
|
+
* `COMPANIONS` registry).
|
|
171
|
+
*/
|
|
172
|
+
export const COMPANION_ARCHETYPES: AgentArchetype[] = [
|
|
173
|
+
{
|
|
174
|
+
kind: 'reviewer',
|
|
175
|
+
label: 'Reviewer (companion)',
|
|
176
|
+
icon: 'i-lucide-scan-eye',
|
|
177
|
+
color: '#f472b6',
|
|
178
|
+
description:
|
|
179
|
+
"Coder's companion: rates the change for quality/correctness and loops it back for automatic rework below the threshold.",
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
kind: 'architect-companion',
|
|
183
|
+
label: 'Architect Companion',
|
|
184
|
+
icon: 'i-lucide-bug-play',
|
|
185
|
+
color: '#c084fc',
|
|
186
|
+
description:
|
|
187
|
+
"Challenges the architect's design for quality and completeness, looping it back for rework below the threshold before a human reviews it.",
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
kind: 'spec-companion',
|
|
191
|
+
label: 'Spec Reviewer',
|
|
192
|
+
icon: 'i-lucide-list-checks',
|
|
193
|
+
color: '#2dd4bf',
|
|
194
|
+
description:
|
|
195
|
+
'Reviews the spec — especially acceptance-scenario coverage — rating it and looping the Spec Writer back for automatic rework below the threshold, instead of requiring a human review.',
|
|
196
|
+
},
|
|
197
|
+
]
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Producer agent kind → its companion agent kind. Mirrors the backend `COMPANIONS` registry
|
|
201
|
+
* (`@cat-factory/agents`). The builder shows an "add companion" toggle on a producer step
|
|
202
|
+
* found here, and inserts/removes the companion immediately after it.
|
|
203
|
+
*/
|
|
204
|
+
export const COMPANION_FOR_PRODUCER: Record<string, AgentKind> = {
|
|
205
|
+
coder: 'reviewer',
|
|
206
|
+
architect: 'architect-companion',
|
|
207
|
+
'spec-writer': 'spec-companion',
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const COMPANION_KINDS: ReadonlySet<string> = new Set(COMPANION_ARCHETYPES.map((a) => a.kind))
|
|
211
|
+
|
|
212
|
+
/** The companion kind that depends on a producer kind, or undefined if it has none. */
|
|
213
|
+
export function companionForProducer(kind: string): AgentKind | undefined {
|
|
214
|
+
return COMPANION_FOR_PRODUCER[kind]
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Whether a kind is a dependent producer-companion (reviewer / architect-companion /
|
|
219
|
+
* spec-companion) — rendered as a toggle on its producer, not a standalone palette block.
|
|
220
|
+
* Distinct from `pipelineRender`'s `isCompanionKind`, which also counts the Tester's `fixer`.
|
|
221
|
+
*/
|
|
222
|
+
export function isProducerCompanion(kind: string): boolean {
|
|
223
|
+
return COMPANION_KINDS.has(kind)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export const AGENT_BY_KIND: Record<AgentKind, AgentArchetype> = Object.fromEntries(
|
|
227
|
+
[...AGENT_ARCHETYPES, ...COMPANION_ARCHETYPES].map((a) => [a.kind, a]),
|
|
228
|
+
) as Record<AgentKind, AgentArchetype>
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Agent kinds eligible for the optional consensus mechanism (the pipeline builder shows an
|
|
232
|
+
* "Enable Consensus" toggle for these). Mirrors the backend default-eligible set assigned by
|
|
233
|
+
* `registerConsensusTraits()` in `@cat-factory/consensus` — hand-synced, like the other
|
|
234
|
+
* frontend mirrors. In CONSENSUS mode `architect`/`analysis` reason over the provided context
|
|
235
|
+
* rather than exploring a checkout (a deliberate trade, gated by the task estimate).
|
|
236
|
+
*/
|
|
237
|
+
export const CONSENSUS_ELIGIBLE_KINDS: ReadonlySet<string> = new Set([
|
|
238
|
+
'architect',
|
|
239
|
+
'analysis',
|
|
240
|
+
'reviewer',
|
|
241
|
+
'task-estimator',
|
|
242
|
+
])
|
|
243
|
+
|
|
244
|
+
/** Whether a step kind can be flipped into the consensus execution mode. */
|
|
245
|
+
export function isConsensusEligibleKind(kind: string): boolean {
|
|
246
|
+
return CONSENSUS_ELIGIBLE_KINDS.has(kind)
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Display metadata for the engine-driven "system" kinds — the gate/automation
|
|
251
|
+
* steps (blueprint mapper, conflicts gate + resolver, CI gate + fixer, merger)
|
|
252
|
+
* that appear in seeded pipelines and run timelines but are NOT user-addable
|
|
253
|
+
* palette archetypes, so they're intentionally absent from {@link AGENT_ARCHETYPES}
|
|
254
|
+
* / {@link AGENT_BY_KIND}. Looked up through {@link agentKindMeta}.
|
|
255
|
+
*/
|
|
256
|
+
export const SYSTEM_AGENT_META: Record<string, AgentArchetype> = {
|
|
257
|
+
'spec-writer': {
|
|
258
|
+
kind: 'spec-writer',
|
|
259
|
+
label: 'Spec Writer',
|
|
260
|
+
icon: 'i-lucide-clipboard-list',
|
|
261
|
+
color: '#c084fc',
|
|
262
|
+
description:
|
|
263
|
+
"Aggregates every task's clarified requirements into the service's in-repo specification (spec.json) with full acceptance-scenario coverage, derived into Gherkin.",
|
|
264
|
+
},
|
|
265
|
+
blueprints: {
|
|
266
|
+
kind: 'blueprints',
|
|
267
|
+
label: 'Blueprinter',
|
|
268
|
+
icon: 'i-lucide-map',
|
|
269
|
+
color: '#22d3ee',
|
|
270
|
+
description: 'Maps the repository into the service → modules blueprint.',
|
|
271
|
+
},
|
|
272
|
+
conflicts: {
|
|
273
|
+
kind: 'conflicts',
|
|
274
|
+
label: 'Conflicts Gate',
|
|
275
|
+
icon: 'i-lucide-git-merge',
|
|
276
|
+
color: '#f97316',
|
|
277
|
+
description: 'Ensures the PR is mergeable with its base, looping the resolver on conflicts.',
|
|
278
|
+
// Opens the dedicated gate window (verdict, attempts, conflict detail) instead of
|
|
279
|
+
// the generic prose step-detail panel. Shared with the CI gate.
|
|
280
|
+
resultView: 'gate',
|
|
281
|
+
},
|
|
282
|
+
'conflict-resolver': {
|
|
283
|
+
kind: 'conflict-resolver',
|
|
284
|
+
label: 'Conflict Resolver',
|
|
285
|
+
icon: 'i-lucide-git-merge',
|
|
286
|
+
color: '#f97316',
|
|
287
|
+
description: 'Merges the base in and resolves conflicts on the PR branch.',
|
|
288
|
+
},
|
|
289
|
+
ci: {
|
|
290
|
+
kind: 'ci',
|
|
291
|
+
label: 'CI Gate',
|
|
292
|
+
icon: 'i-lucide-shield-check',
|
|
293
|
+
color: '#38bdf8',
|
|
294
|
+
description: 'Gates the PR on green CI, looping the CI fixer on failure.',
|
|
295
|
+
// Opens the dedicated gate window (verdict, attempts, the failing checks) instead
|
|
296
|
+
// of the generic prose step-detail panel. Shared with the conflicts gate.
|
|
297
|
+
resultView: 'gate',
|
|
298
|
+
},
|
|
299
|
+
'ci-fixer': {
|
|
300
|
+
kind: 'ci-fixer',
|
|
301
|
+
label: 'CI Fixer',
|
|
302
|
+
icon: 'i-lucide-wrench',
|
|
303
|
+
color: '#38bdf8',
|
|
304
|
+
description: 'Fixes failing CI and pushes back to the PR branch.',
|
|
305
|
+
},
|
|
306
|
+
fixer: {
|
|
307
|
+
kind: 'fixer',
|
|
308
|
+
label: 'Fixer',
|
|
309
|
+
icon: 'i-lucide-wrench',
|
|
310
|
+
color: '#fbbf24',
|
|
311
|
+
description:
|
|
312
|
+
"Tester's companion: fixes the bugs the tester found and pushes back, then the tester re-runs.",
|
|
313
|
+
},
|
|
314
|
+
merger: {
|
|
315
|
+
kind: 'merger',
|
|
316
|
+
label: 'Merger',
|
|
317
|
+
icon: 'i-lucide-git-pull-request',
|
|
318
|
+
color: '#a3e635',
|
|
319
|
+
description: 'Scores the PR and auto-merges within the task thresholds, or asks for review.',
|
|
320
|
+
},
|
|
321
|
+
// A polling gate (no model of its own) that watches the released PR's observability
|
|
322
|
+
// signals after merge and escalates to the on-call agent on a regression. NOT in any
|
|
323
|
+
// default pipeline and NOT a standing palette archetype — the palette surfaces it
|
|
324
|
+
// conditionally (only with an observability integration connected, see AgentPalette),
|
|
325
|
+
// but it still needs display metadata here so timelines/saved pipelines render it.
|
|
326
|
+
'post-release-health': {
|
|
327
|
+
kind: 'post-release-health',
|
|
328
|
+
label: 'Post-Release Health',
|
|
329
|
+
icon: 'i-lucide-activity',
|
|
330
|
+
color: '#f43f5e',
|
|
331
|
+
category: 'gates',
|
|
332
|
+
description:
|
|
333
|
+
'Watches the released PR’s Datadog monitors/SLOs after merge and escalates to the on-call agent on a regression.',
|
|
334
|
+
// Opens the dedicated gate window (verdict, attempts, watch window) like the other gates.
|
|
335
|
+
resultView: 'gate',
|
|
336
|
+
},
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* The observability-gated palette block. NOT in {@link AGENT_ARCHETYPES} (so it never
|
|
341
|
+
* pollutes model-defaults — it runs no model — and is never offered unconditionally):
|
|
342
|
+
* the pipeline builder appends it to the palette ONLY when the workspace has an
|
|
343
|
+
* observability integration connected, and the backend rejects it otherwise.
|
|
344
|
+
*/
|
|
345
|
+
export const OBSERVABILITY_GATE_ARCHETYPE: AgentArchetype =
|
|
346
|
+
SYSTEM_AGENT_META['post-release-health']!
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Engine-driven kinds that still run an LLM, so their model is worth pinning per
|
|
350
|
+
* workspace even though they aren't user-addable palette archetypes. Surfaced in the
|
|
351
|
+
* Default Models settings alongside {@link AGENT_ARCHETYPES} (but NOT in the pipeline
|
|
352
|
+
* palette). The pure gates (`ci`, `conflicts`) are excluded — they run no model, so a
|
|
353
|
+
* default model would do nothing for them.
|
|
354
|
+
*/
|
|
355
|
+
export const MODEL_CONFIGURABLE_SYSTEM_KINDS: AgentArchetype[] = [
|
|
356
|
+
...['spec-writer', 'blueprints', 'conflict-resolver', 'ci-fixer', 'fixer', 'merger'].map(
|
|
357
|
+
(kind) => SYSTEM_AGENT_META[kind]!,
|
|
358
|
+
),
|
|
359
|
+
// Companions run LLMs but aren't palette-addable (they're producer toggles), so include
|
|
360
|
+
// them here to keep their per-workspace default model pinnable in the Model Defaults panel.
|
|
361
|
+
...COMPANION_ARCHETYPES,
|
|
362
|
+
]
|
|
363
|
+
|
|
364
|
+
/** Fallback metadata for any kind with no archetype or system entry (unknown/custom). */
|
|
365
|
+
const FALLBACK_AGENT_META: Omit<AgentArchetype, 'kind'> = {
|
|
366
|
+
label: 'Agent',
|
|
367
|
+
icon: 'i-lucide-bot',
|
|
368
|
+
color: '#94a3b8',
|
|
369
|
+
description: 'Agent step.',
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Resolve display metadata for ANY agent kind — a palette archetype (incl. custom
|
|
374
|
+
* agents registered into {@link AGENT_BY_KIND}), an engine system kind, or an
|
|
375
|
+
* unknown one — ALWAYS returning a usable icon/label/color. This is the single
|
|
376
|
+
* lookup every pipeline / run renderer should use so a kind missing from the
|
|
377
|
+
* archetype map (e.g. `ci`/`merger`/`blueprints` in a seeded pipeline) can never
|
|
378
|
+
* blow up a component with an undefined access.
|
|
379
|
+
*/
|
|
380
|
+
export function agentKindMeta(kind: string): AgentArchetype {
|
|
381
|
+
return (
|
|
382
|
+
AGENT_BY_KIND[kind as AgentKind] ??
|
|
383
|
+
SYSTEM_AGENT_META[kind] ?? { kind: kind as AgentKind, ...FALLBACK_AGENT_META }
|
|
384
|
+
)
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/** Visual metadata for each architecture block type. */
|
|
388
|
+
export const BLOCK_TYPE_META: Record<BlockType, { label: string; icon: string; accent: string }> = {
|
|
389
|
+
frontend: { label: 'Frontend', icon: 'i-lucide-monitor', accent: '#60a5fa' },
|
|
390
|
+
service: { label: 'Service', icon: 'i-lucide-server', accent: '#a78bfa' },
|
|
391
|
+
api: { label: 'API', icon: 'i-lucide-route', accent: '#22d3ee' },
|
|
392
|
+
database: { label: 'Database', icon: 'i-lucide-database', accent: '#34d399' },
|
|
393
|
+
queue: { label: 'Queue', icon: 'i-lucide-list-ordered', accent: '#fbbf24' },
|
|
394
|
+
integration: {
|
|
395
|
+
label: 'Integration',
|
|
396
|
+
icon: 'i-lucide-workflow',
|
|
397
|
+
accent: '#fb923c',
|
|
398
|
+
},
|
|
399
|
+
external: { label: 'External', icon: 'i-lucide-globe', accent: '#94a3b8' },
|
|
400
|
+
environment: {
|
|
401
|
+
label: 'Environment',
|
|
402
|
+
icon: 'i-lucide-container',
|
|
403
|
+
accent: '#2dd4bf',
|
|
404
|
+
},
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/** Color + iconography for each block status. */
|
|
408
|
+
export const STATUS_META: Record<
|
|
409
|
+
BlockStatus,
|
|
410
|
+
{ label: string; color: string; chip: string; icon: string }
|
|
411
|
+
> = {
|
|
412
|
+
planned: {
|
|
413
|
+
label: 'Planned',
|
|
414
|
+
color: '#64748b',
|
|
415
|
+
chip: 'neutral',
|
|
416
|
+
icon: 'i-lucide-circle-dashed',
|
|
417
|
+
},
|
|
418
|
+
ready: {
|
|
419
|
+
label: 'Ready',
|
|
420
|
+
color: '#3b82f6',
|
|
421
|
+
chip: 'info',
|
|
422
|
+
icon: 'i-lucide-circle-play',
|
|
423
|
+
},
|
|
424
|
+
in_progress: {
|
|
425
|
+
label: 'In progress',
|
|
426
|
+
color: '#6366f1',
|
|
427
|
+
chip: 'primary',
|
|
428
|
+
icon: 'i-lucide-loader',
|
|
429
|
+
},
|
|
430
|
+
blocked: {
|
|
431
|
+
// Generic copy: `blocked` is overloaded — a run parks here for a human
|
|
432
|
+
// decision, an approval gate, OR a terminal failure. Surfaces that know the
|
|
433
|
+
// specific reason (TaskCard, the inspector) show the precise label/action;
|
|
434
|
+
// this fallback must NOT imply a decision is the only thing it can be.
|
|
435
|
+
label: 'Needs attention',
|
|
436
|
+
color: '#f59e0b',
|
|
437
|
+
chip: 'warning',
|
|
438
|
+
icon: 'i-lucide-alert-triangle',
|
|
439
|
+
},
|
|
440
|
+
pr_ready: {
|
|
441
|
+
label: 'PR ready',
|
|
442
|
+
color: '#22c55e',
|
|
443
|
+
chip: 'success',
|
|
444
|
+
icon: 'i-lucide-git-pull-request',
|
|
445
|
+
},
|
|
446
|
+
done: {
|
|
447
|
+
label: 'Done',
|
|
448
|
+
color: '#16a34a',
|
|
449
|
+
chip: 'success',
|
|
450
|
+
icon: 'i-lucide-circle-check',
|
|
451
|
+
},
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/** Visual metadata for module sub-frames. */
|
|
455
|
+
export const MODULE_META = { icon: 'i-lucide-package', color: '#a78bfa' }
|
package/app/utils/dnd.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { BlockType } from '~/types/domain'
|
|
2
|
+
|
|
3
|
+
/** MIME-ish key used to carry palette payloads across the HTML5 DnD boundary. */
|
|
4
|
+
export const DND_MIME = 'application/agent-board'
|
|
5
|
+
|
|
6
|
+
export type DndPayload =
|
|
7
|
+
| { kind: 'block'; blockType: BlockType }
|
|
8
|
+
| { kind: 'pipeline'; pipelineId: string }
|
|
9
|
+
|
|
10
|
+
export function setDndPayload(event: DragEvent, payload: DndPayload) {
|
|
11
|
+
event.dataTransfer?.setData(DND_MIME, JSON.stringify(payload))
|
|
12
|
+
if (event.dataTransfer) event.dataTransfer.effectAllowed = 'copy'
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function readDndPayload(event: DragEvent): DndPayload | null {
|
|
16
|
+
const raw = event.dataTransfer?.getData(DND_MIME)
|
|
17
|
+
if (!raw) return null
|
|
18
|
+
try {
|
|
19
|
+
return JSON.parse(raw) as DndPayload
|
|
20
|
+
} catch {
|
|
21
|
+
return null
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Walk up from the drop target to find the block it landed on, if any. */
|
|
26
|
+
export function blockIdFromEvent(event: DragEvent): string | null {
|
|
27
|
+
const el = (event.target as HTMLElement | null)?.closest('[data-block-id]')
|
|
28
|
+
return el?.getAttribute('data-block-id') ?? null
|
|
29
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// Formatting + derivation helpers for the LLM observability surfaces (inline step
|
|
2
|
+
// rollups + the drill-down panel). Kept here so the components stay declarative and
|
|
3
|
+
// the number-crunching is unit-testable.
|
|
4
|
+
|
|
5
|
+
import type { StepMetrics } from '~/types/execution'
|
|
6
|
+
|
|
7
|
+
/** Compact token count: 1234 → "1.2k", 980 → "980", 2_500_000 → "2.5M". */
|
|
8
|
+
export function formatTokens(n: number): string {
|
|
9
|
+
if (n < 1000) return `${n}`
|
|
10
|
+
if (n < 1_000_000) return `${(n / 1000).toFixed(n < 10_000 ? 1 : 0)}k`
|
|
11
|
+
return `${(n / 1_000_000).toFixed(1)}M`
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Compact duration: 850 → "850ms", 1500 → "1.5s", 90_000 → "1m 30s". */
|
|
15
|
+
export function formatMs(ms: number): string {
|
|
16
|
+
if (ms < 1000) return `${Math.round(ms)}ms`
|
|
17
|
+
const totalSec = ms / 1000
|
|
18
|
+
if (totalSec < 60) return `${totalSec.toFixed(totalSec < 10 ? 1 : 0)}s`
|
|
19
|
+
const m = Math.floor(totalSec / 60)
|
|
20
|
+
const sec = Math.round(totalSec % 60)
|
|
21
|
+
return sec ? `${m}m ${sec}s` : `${m}m`
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** A ratio (0..1) as a whole-number percentage. */
|
|
25
|
+
export function pct(ratio: number): number {
|
|
26
|
+
return Math.round(ratio * 100)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Output-limit headroom for a step's rollup: the fraction of the output ceiling the
|
|
31
|
+
* closest call consumed (0..1), or null when the ceiling is unknown. 1 (or any
|
|
32
|
+
* truncated call) means a call hit the limit and was cut short.
|
|
33
|
+
*/
|
|
34
|
+
export function headroomRatio(
|
|
35
|
+
m: Pick<StepMetrics, 'peakCompletionTokens' | 'maxOutputTokens'>,
|
|
36
|
+
): number | null {
|
|
37
|
+
if (m.maxOutputTokens == null || m.maxOutputTokens <= 0) return null
|
|
38
|
+
return Math.min(1, m.peakCompletionTokens / m.maxOutputTokens)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Share of a step's latency spent in transport/proxy overhead (0..1), or null. */
|
|
42
|
+
export function transportRatio(m: Pick<StepMetrics, 'upstreamMs' | 'overheadMs'>): number | null {
|
|
43
|
+
const total = m.upstreamMs + m.overheadMs
|
|
44
|
+
return total > 0 ? m.overheadMs / total : null
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Tailwind text/bg colour for an output-headroom level (green → amber → red). */
|
|
48
|
+
export function headroomColor(ratio: number | null, truncated: boolean): string {
|
|
49
|
+
if (truncated || (ratio != null && ratio >= 0.98)) return 'text-rose-400'
|
|
50
|
+
if (ratio != null && ratio >= 0.8) return 'text-amber-400'
|
|
51
|
+
return 'text-emerald-400'
|
|
52
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
// Shared rendering helpers for the run-step / pipeline views (PipelineProgress,
|
|
2
|
+
// TaskPipelineMini, AgentStepDetail), so the "is this step still live?" logic stays
|
|
3
|
+
// in one place rather than being re-derived as inline ternaries per component.
|
|
4
|
+
|
|
5
|
+
import type { AgentState, PipelineStep } from '~/types/execution'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Visual state of a conditionally-run companion attached to a gate step (today the
|
|
9
|
+
* Tester's `fixer`): it MIGHT run (`possible`), is running now (`running`), ran at
|
|
10
|
+
* least once (`completed`), the gate passed without ever needing it (`skipped`), or
|
|
11
|
+
* it was mid-run when the pipeline failed and gave up (`failed`).
|
|
12
|
+
*/
|
|
13
|
+
export type CompanionState = 'possible' | 'running' | 'completed' | 'skipped' | 'failed'
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Visual language for a step (or its companion) that was left `working` when its
|
|
17
|
+
* run failed. A failed mid-flight step is NOT live — it should read as "Failed" with
|
|
18
|
+
* a red cross, never a frozen/spinning loader or a misleading "Working" label.
|
|
19
|
+
*/
|
|
20
|
+
export const FAILED_STEP_META = {
|
|
21
|
+
label: 'Failed',
|
|
22
|
+
color: '#ef4444',
|
|
23
|
+
icon: 'i-lucide-circle-x',
|
|
24
|
+
} as const
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Whether a step left in `working` state should be rendered as failed: it never
|
|
28
|
+
* finished, and its run has terminated as `failed`, so the engine gave up on it.
|
|
29
|
+
*/
|
|
30
|
+
export function isFailedStep(state: AgentState, runFailed: boolean): boolean {
|
|
31
|
+
return runFailed && state === 'working'
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Descriptor for the companion node a gate step renders beneath itself. */
|
|
35
|
+
export interface GateCompanion {
|
|
36
|
+
/** Agent kind of the companion (resolved through `agentKindMeta` for icon/label). */
|
|
37
|
+
kind: string
|
|
38
|
+
state: CompanionState
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Display metadata per companion state (badge label + Tailwind colour classes). */
|
|
42
|
+
export const COMPANION_STATE_META: Record<
|
|
43
|
+
CompanionState,
|
|
44
|
+
{ label: string; dot: string; text: string; icon: string }
|
|
45
|
+
> = {
|
|
46
|
+
possible: {
|
|
47
|
+
label: 'May run',
|
|
48
|
+
dot: 'border-slate-600 bg-slate-800/40',
|
|
49
|
+
text: 'text-slate-400',
|
|
50
|
+
icon: 'i-lucide-circle-dashed',
|
|
51
|
+
},
|
|
52
|
+
running: {
|
|
53
|
+
label: 'Running',
|
|
54
|
+
dot: 'border-amber-400 bg-amber-500/20',
|
|
55
|
+
text: 'text-amber-300',
|
|
56
|
+
icon: 'i-lucide-loader',
|
|
57
|
+
},
|
|
58
|
+
completed: {
|
|
59
|
+
label: 'Ran',
|
|
60
|
+
dot: 'border-emerald-500 bg-emerald-500/20',
|
|
61
|
+
text: 'text-emerald-300',
|
|
62
|
+
icon: 'i-lucide-circle-check',
|
|
63
|
+
},
|
|
64
|
+
skipped: {
|
|
65
|
+
label: 'Skipped',
|
|
66
|
+
dot: 'border-slate-700 bg-slate-800/40',
|
|
67
|
+
text: 'text-slate-500',
|
|
68
|
+
icon: 'i-lucide-circle-slash',
|
|
69
|
+
},
|
|
70
|
+
failed: {
|
|
71
|
+
label: 'Gave up',
|
|
72
|
+
dot: 'border-rose-500 bg-rose-500/20',
|
|
73
|
+
text: 'text-rose-400',
|
|
74
|
+
icon: 'i-lucide-circle-x',
|
|
75
|
+
},
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* The conditionally-run companion (if any) a gate step drives, with its current
|
|
80
|
+
* state — so the pipeline views can render it as a distinct sub-node marked
|
|
81
|
+
* possible / running / completed / skipped. The Tester's `fixer` loop is modelled via
|
|
82
|
+
* `step.test`; the polling gates (`ci` → `ci-fixer`, `conflicts` → `conflict-resolver`)
|
|
83
|
+
* via `step.gate`, which all share the same possible/running/completed/skipped shape.
|
|
84
|
+
*/
|
|
85
|
+
export function gateCompanionFor(step: PipelineStep, runFailed = false): GateCompanion | null {
|
|
86
|
+
if (step.agentKind === 'tester') {
|
|
87
|
+
const attempts = step.test?.attempts ?? 0
|
|
88
|
+
if (step.state === 'done') {
|
|
89
|
+
// The gate finished: it ran the fixer iff it ever dispatched one.
|
|
90
|
+
return { kind: 'fixer', state: attempts > 0 ? 'completed' : 'skipped' }
|
|
91
|
+
}
|
|
92
|
+
// A fixer caught mid-loop by a failed run gave up, not "running".
|
|
93
|
+
if (step.test?.phase === 'fixing')
|
|
94
|
+
return { kind: 'fixer', state: runFailed ? 'failed' : 'running' }
|
|
95
|
+
if (attempts > 0) return { kind: 'fixer', state: 'completed' }
|
|
96
|
+
// Pending, or testing with no attempt yet — the fixer might still be needed.
|
|
97
|
+
return { kind: 'fixer', state: 'possible' }
|
|
98
|
+
}
|
|
99
|
+
const helper =
|
|
100
|
+
step.agentKind === 'ci'
|
|
101
|
+
? 'ci-fixer'
|
|
102
|
+
: step.agentKind === 'conflicts'
|
|
103
|
+
? 'conflict-resolver'
|
|
104
|
+
: null
|
|
105
|
+
if (helper) {
|
|
106
|
+
const attempts = step.gate?.attempts ?? 0
|
|
107
|
+
if (step.state === 'done') {
|
|
108
|
+
// The gate passed: it ran the helper iff it ever dispatched one.
|
|
109
|
+
return { kind: helper, state: attempts > 0 ? 'completed' : 'skipped' }
|
|
110
|
+
}
|
|
111
|
+
// A helper (ci-fixer / conflict-resolver) caught mid-run when the gate exhausted
|
|
112
|
+
// its attempt budget and the run failed gave up — never show it as "running".
|
|
113
|
+
if (step.gate?.phase === 'working')
|
|
114
|
+
return { kind: helper, state: runFailed ? 'failed' : 'running' }
|
|
115
|
+
if (attempts > 0) return { kind: helper, state: 'completed' }
|
|
116
|
+
// Checking the precheck with no escalation yet — the helper might still be needed.
|
|
117
|
+
return { kind: helper, state: 'possible' }
|
|
118
|
+
}
|
|
119
|
+
return null
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Whether an agent kind is a companion of a producer step (the quality companions
|
|
124
|
+
* that grade-and-loop, plus the Tester's `fixer`). Used to give companion steps a
|
|
125
|
+
* visually distinct treatment in the pipeline.
|
|
126
|
+
*/
|
|
127
|
+
export function isCompanionKind(kind: string): boolean {
|
|
128
|
+
return (
|
|
129
|
+
kind === 'reviewer' ||
|
|
130
|
+
kind === 'architect-companion' ||
|
|
131
|
+
kind === 'spec-companion' ||
|
|
132
|
+
kind === 'fixer'
|
|
133
|
+
)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Tailwind classes for a subtask-item status icon. An in-progress item spins only
|
|
138
|
+
* while the run is live: once the run has failed, a step left mid-flight (its item
|
|
139
|
+
* state still `in_progress`) keeps its colour but stops spinning, matching the frozen
|
|
140
|
+
* failure card. Completed items are emerald, everything else muted.
|
|
141
|
+
*/
|
|
142
|
+
export function subtaskIconClass(status: string, runFailed: boolean): string[] {
|
|
143
|
+
return [
|
|
144
|
+
status === 'in_progress'
|
|
145
|
+
? runFailed
|
|
146
|
+
? 'text-indigo-400'
|
|
147
|
+
: 'animate-spin text-indigo-400'
|
|
148
|
+
: '',
|
|
149
|
+
status === 'completed' ? 'text-emerald-400' : 'text-slate-500',
|
|
150
|
+
]
|
|
151
|
+
}
|