@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.
Files changed (189) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +88 -0
  3. package/app/app.config.ts +8 -0
  4. package/app/app.vue +11 -0
  5. package/app/assets/css/main.css +100 -0
  6. package/app/components/auth/AuthGate.vue +24 -0
  7. package/app/components/auth/LoginScreen.vue +143 -0
  8. package/app/components/auth/UserMenu.vue +39 -0
  9. package/app/components/board/AddTaskModal.vue +444 -0
  10. package/app/components/board/AgentFailureCard.vue +97 -0
  11. package/app/components/board/AgentStopButton.vue +61 -0
  12. package/app/components/board/BoardCanvas.vue +183 -0
  13. package/app/components/board/ContextPicker.vue +367 -0
  14. package/app/components/board/RecurringPipelineModal.vue +219 -0
  15. package/app/components/board/TaskDependencyEdges.vue +132 -0
  16. package/app/components/board/nodes/AgentChip.vue +59 -0
  17. package/app/components/board/nodes/BlockNode.vue +433 -0
  18. package/app/components/board/nodes/DecisionBadge.vue +27 -0
  19. package/app/components/board/nodes/DraggableTask.vue +48 -0
  20. package/app/components/board/nodes/ModuleFrame.vue +97 -0
  21. package/app/components/board/nodes/TaskCard.vue +359 -0
  22. package/app/components/board/nodes/TaskPipelineMini.vue +159 -0
  23. package/app/components/bootstrap/BootstrapModal.vue +665 -0
  24. package/app/components/clarity/ClarityReviewWindow.vue +611 -0
  25. package/app/components/consensus/ConsensusSessionWindow.vue +210 -0
  26. package/app/components/documents/DocumentImportModal.vue +161 -0
  27. package/app/components/documents/DocumentSourceConnectModal.vue +127 -0
  28. package/app/components/documents/SpawnPreviewModal.vue +161 -0
  29. package/app/components/documents/TaskContextDocs.vue +83 -0
  30. package/app/components/focus/BlockFocusView.vue +171 -0
  31. package/app/components/fragments/FragmentLibraryPanel.vue +340 -0
  32. package/app/components/gates/GateResultView.vue +282 -0
  33. package/app/components/github/AddServiceFromRepoModal.vue +354 -0
  34. package/app/components/github/GitHubConnect.vue +183 -0
  35. package/app/components/github/GitHubOnboarding.vue +45 -0
  36. package/app/components/github/GitHubPanel.vue +584 -0
  37. package/app/components/github/RepoTreeBrowser.vue +171 -0
  38. package/app/components/layout/AccountTeamSettings.vue +237 -0
  39. package/app/components/layout/BoardSwitcher.vue +280 -0
  40. package/app/components/layout/BoardToolbar.vue +156 -0
  41. package/app/components/layout/CommandBar.vue +336 -0
  42. package/app/components/layout/GitHubPatBanner.vue +73 -0
  43. package/app/components/layout/NotificationsInbox.vue +175 -0
  44. package/app/components/layout/SideBar.vue +314 -0
  45. package/app/components/layout/SpendWarningBanner.vue +107 -0
  46. package/app/components/observability/StepMetricsBar.vue +102 -0
  47. package/app/components/palettes/AgentPalette.vue +86 -0
  48. package/app/components/panels/AgentStepDetail.vue +737 -0
  49. package/app/components/panels/DecisionModal.vue +71 -0
  50. package/app/components/panels/InspectorPanel.vue +465 -0
  51. package/app/components/panels/ObservabilityPanel.vue +351 -0
  52. package/app/components/panels/StepMetadataCard.vue +253 -0
  53. package/app/components/panels/StepRestartControl.vue +90 -0
  54. package/app/components/panels/StepResultViewHost.vue +40 -0
  55. package/app/components/panels/StepTestReport.vue +84 -0
  56. package/app/components/panels/inspector/ContainerSummary.vue +74 -0
  57. package/app/components/panels/inspector/RecurringScheduleSettings.vue +178 -0
  58. package/app/components/panels/inspector/ServiceFragments.vue +82 -0
  59. package/app/components/panels/inspector/ServiceTestConfig.vue +198 -0
  60. package/app/components/panels/inspector/TaskAgentConfig.vue +81 -0
  61. package/app/components/panels/inspector/TaskDependencies.vue +70 -0
  62. package/app/components/panels/inspector/TaskEstimateBadge.vue +56 -0
  63. package/app/components/panels/inspector/TaskExecution.vue +364 -0
  64. package/app/components/panels/inspector/TaskRunSettings.vue +187 -0
  65. package/app/components/panels/inspector/TaskStructure.vue +96 -0
  66. package/app/components/pipeline/AgentKindIcon.vue +30 -0
  67. package/app/components/pipeline/IterationCapPrompt.vue +70 -0
  68. package/app/components/pipeline/PipelineBuilder.vue +817 -0
  69. package/app/components/pipeline/PipelineProgress.vue +484 -0
  70. package/app/components/providers/ApiKeysSection.vue +273 -0
  71. package/app/components/providers/PersonalCredentialModal.vue +128 -0
  72. package/app/components/providers/PersonalSubscriptionSection.vue +225 -0
  73. package/app/components/providers/VendorCredentialsModal.vue +197 -0
  74. package/app/components/recurring/RecurrenceEditor.vue +124 -0
  75. package/app/components/requirements/RequirementsReviewWindow.vue +620 -0
  76. package/app/components/settings/DatadogPanel.vue +213 -0
  77. package/app/components/settings/LocalModelEndpointsPanel.vue +286 -0
  78. package/app/components/settings/MergeThresholdsPanel.vue +378 -0
  79. package/app/components/settings/ModelDefaultsPanel.vue +250 -0
  80. package/app/components/settings/ServiceFragmentDefaultsPanel.vue +124 -0
  81. package/app/components/settings/WorkspaceSettingsPanel.vue +142 -0
  82. package/app/components/slack/SlackPanel.vue +299 -0
  83. package/app/components/tasks/TaskContextIssues.vue +88 -0
  84. package/app/components/tasks/TaskImportModal.vue +207 -0
  85. package/app/components/tasks/TaskSourceConnectModal.vue +133 -0
  86. package/app/components/testing/TestReportWindow.vue +404 -0
  87. package/app/composables/api/accounts.ts +81 -0
  88. package/app/composables/api/auth.ts +45 -0
  89. package/app/composables/api/board.ts +101 -0
  90. package/app/composables/api/bootstrap.ts +62 -0
  91. package/app/composables/api/context.ts +25 -0
  92. package/app/composables/api/documents.ts +74 -0
  93. package/app/composables/api/execution.ts +127 -0
  94. package/app/composables/api/fragments.ts +71 -0
  95. package/app/composables/api/github.ts +131 -0
  96. package/app/composables/api/models.ts +127 -0
  97. package/app/composables/api/notifications.ts +23 -0
  98. package/app/composables/api/presets.ts +29 -0
  99. package/app/composables/api/recurring.ts +68 -0
  100. package/app/composables/api/releaseHealth.ts +43 -0
  101. package/app/composables/api/reviews.ts +146 -0
  102. package/app/composables/api/slack.ts +54 -0
  103. package/app/composables/api/tasks.ts +72 -0
  104. package/app/composables/api/workspaces.ts +36 -0
  105. package/app/composables/useApi.ts +89 -0
  106. package/app/composables/useBlockDrag.ts +90 -0
  107. package/app/composables/useBlockQueries.ts +154 -0
  108. package/app/composables/useBoardFlow.ts +11 -0
  109. package/app/composables/useContextLinking.ts +65 -0
  110. package/app/composables/useDepLabels.ts +26 -0
  111. package/app/composables/useFrameResize.ts +54 -0
  112. package/app/composables/useResultView.ts +48 -0
  113. package/app/composables/useReviewStage.ts +40 -0
  114. package/app/composables/useSemanticZoom.ts +31 -0
  115. package/app/composables/useStepApproval.ts +233 -0
  116. package/app/composables/useStepProse.ts +78 -0
  117. package/app/composables/useStepTimer.ts +63 -0
  118. package/app/composables/useTaskExpansion.ts +92 -0
  119. package/app/composables/useWorkspaceStream.ts +155 -0
  120. package/app/docs/architecture.md +31 -0
  121. package/app/pages/index.vue +141 -0
  122. package/app/stores/accounts.ts +152 -0
  123. package/app/stores/agentConfig.ts +35 -0
  124. package/app/stores/agentRuns.ts +122 -0
  125. package/app/stores/agents.ts +40 -0
  126. package/app/stores/apiKeys.ts +108 -0
  127. package/app/stores/auth.ts +166 -0
  128. package/app/stores/board.spec.ts +205 -0
  129. package/app/stores/board.ts +286 -0
  130. package/app/stores/bootstrap.ts +97 -0
  131. package/app/stores/clarity.ts +196 -0
  132. package/app/stores/consensus.ts +60 -0
  133. package/app/stores/documents.ts +176 -0
  134. package/app/stores/execution.ts +273 -0
  135. package/app/stores/fragmentLibrary.ts +147 -0
  136. package/app/stores/fragments.ts +40 -0
  137. package/app/stores/github.ts +305 -0
  138. package/app/stores/localModels.ts +51 -0
  139. package/app/stores/mergePresets.ts +58 -0
  140. package/app/stores/modelDefaults.ts +76 -0
  141. package/app/stores/models.ts +134 -0
  142. package/app/stores/notifications.ts +70 -0
  143. package/app/stores/observability.ts +144 -0
  144. package/app/stores/personalSubscriptions.ts +215 -0
  145. package/app/stores/pipelines.ts +327 -0
  146. package/app/stores/recurringPipelines.ts +112 -0
  147. package/app/stores/releaseHealth.ts +75 -0
  148. package/app/stores/requirements.spec.ts +94 -0
  149. package/app/stores/requirements.ts +208 -0
  150. package/app/stores/serviceFragmentDefaults.ts +29 -0
  151. package/app/stores/services.ts +87 -0
  152. package/app/stores/slack.ts +142 -0
  153. package/app/stores/taskExpansion.ts +36 -0
  154. package/app/stores/tasks.spec.ts +71 -0
  155. package/app/stores/tasks.ts +176 -0
  156. package/app/stores/tracker.ts +27 -0
  157. package/app/stores/ui.ts +434 -0
  158. package/app/stores/vendorCredentials.ts +54 -0
  159. package/app/stores/workspace.ts +215 -0
  160. package/app/stores/workspaceSettings.ts +36 -0
  161. package/app/types/accounts.ts +77 -0
  162. package/app/types/bootstrap.ts +83 -0
  163. package/app/types/clarity.ts +59 -0
  164. package/app/types/consensus.ts +91 -0
  165. package/app/types/documents.ts +104 -0
  166. package/app/types/domain.ts +495 -0
  167. package/app/types/execution.ts +383 -0
  168. package/app/types/fragments.ts +72 -0
  169. package/app/types/github.ts +173 -0
  170. package/app/types/localModels.ts +73 -0
  171. package/app/types/merge.ts +71 -0
  172. package/app/types/models.ts +157 -0
  173. package/app/types/notifications.ts +74 -0
  174. package/app/types/recurring.ts +69 -0
  175. package/app/types/releaseHealth.ts +31 -0
  176. package/app/types/requirements.ts +61 -0
  177. package/app/types/services.ts +27 -0
  178. package/app/types/slack.ts +57 -0
  179. package/app/types/tasks.ts +82 -0
  180. package/app/types/tracker.ts +18 -0
  181. package/app/utils/agentOutput.spec.ts +128 -0
  182. package/app/utils/agentOutput.ts +173 -0
  183. package/app/utils/catalog.spec.ts +112 -0
  184. package/app/utils/catalog.ts +455 -0
  185. package/app/utils/dnd.ts +29 -0
  186. package/app/utils/observability.ts +52 -0
  187. package/app/utils/pipelineRender.ts +151 -0
  188. package/nuxt.config.ts +55 -0
  189. 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' }
@@ -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
+ }