@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,198 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, ref } from 'vue'
|
|
3
|
+
import type { Block, CloudProvider, InstanceSize } from '~/types/domain'
|
|
4
|
+
import RepoTreeBrowser from '~/components/github/RepoTreeBrowser.vue'
|
|
5
|
+
|
|
6
|
+
// Service-level (frame) configuration: where the Tester's local-mode infra comes
|
|
7
|
+
// from (a docker-compose path, or an explicit "no infra dependencies" toggle — a
|
|
8
|
+
// Tester pipeline can't start until one is set), plus the cloud provider + instance
|
|
9
|
+
// size the service's container jobs run on. Autodiscovery suggests a compose path
|
|
10
|
+
// when the service is added; it can be set/changed here later — or browsed for in
|
|
11
|
+
// the backing repository.
|
|
12
|
+
const props = defineProps<{
|
|
13
|
+
block: Block
|
|
14
|
+
// Repo backing this service, supplied by the add-service modal when the block is
|
|
15
|
+
// too fresh to be resolvable from the stores yet. Otherwise resolved below.
|
|
16
|
+
repo?: { githubId: number; directory?: string | null }
|
|
17
|
+
}>()
|
|
18
|
+
|
|
19
|
+
const board = useBoardStore()
|
|
20
|
+
const accounts = useAccountsStore()
|
|
21
|
+
const github = useGitHubStore()
|
|
22
|
+
const services = useServicesStore()
|
|
23
|
+
|
|
24
|
+
const composePath = computed(() => props.block.testComposePath ?? '')
|
|
25
|
+
const noInfra = computed(() => props.block.noInfraDependencies === true)
|
|
26
|
+
|
|
27
|
+
// The repo + service subdirectory backing this frame, for the compose-file browser.
|
|
28
|
+
// A monorepo service isn't on the `github_repos` blockId link (that stays null), so
|
|
29
|
+
// fall back to the service catalog mapping, which carries the repo + directory.
|
|
30
|
+
const repoContext = computed<{ githubId: number; directory?: string | null } | undefined>(() => {
|
|
31
|
+
if (props.repo) return props.repo
|
|
32
|
+
const svc = services.serviceByFrameBlock[props.block.id]
|
|
33
|
+
if (svc?.repoGithubId != null) return { githubId: svc.repoGithubId, directory: svc.directory }
|
|
34
|
+
const r = github.repoForBlock(props.block.id)
|
|
35
|
+
return r ? { githubId: r.githubId } : undefined
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
// Compose-file picker: browse the repo and pin the compose file. The Tester runs
|
|
39
|
+
// `docker compose -f <path>` from the CLONE ROOT, so the stored path is relative to
|
|
40
|
+
// the repo root (the browser starts inside the service's subdirectory for convenience).
|
|
41
|
+
const browseOpen = ref(false)
|
|
42
|
+
const pickedPath = ref<string | undefined>(undefined)
|
|
43
|
+
function openBrowse() {
|
|
44
|
+
pickedPath.value = composePath.value || undefined
|
|
45
|
+
browseOpen.value = true
|
|
46
|
+
}
|
|
47
|
+
function applyPicked() {
|
|
48
|
+
if (pickedPath.value) setComposePath(pickedPath.value)
|
|
49
|
+
browseOpen.value = false
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// A service with no explicit provider inherits the active account's default (else the
|
|
53
|
+
// built-in `cloudflare`); show that as the selected chip so the inherited value is visible.
|
|
54
|
+
const effectiveProvider = computed<CloudProvider>(
|
|
55
|
+
() => props.block.cloudProvider ?? accounts.activeAccount?.defaultCloudProvider ?? 'cloudflare',
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
function setComposePath(value: string) {
|
|
59
|
+
board.updateBlock(props.block.id, { testComposePath: value.trim() })
|
|
60
|
+
}
|
|
61
|
+
function toggleNoInfra(value: boolean) {
|
|
62
|
+
board.updateBlock(props.block.id, { noInfraDependencies: value })
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const PROVIDERS: { value: CloudProvider; label: string }[] = [
|
|
66
|
+
{ value: 'cloudflare', label: 'Cloudflare' },
|
|
67
|
+
{ value: 'docker', label: 'Docker (local)' },
|
|
68
|
+
{ value: 'aws', label: 'AWS' },
|
|
69
|
+
{ value: 'gcp', label: 'GCP' },
|
|
70
|
+
{ value: 'azure', label: 'Azure' },
|
|
71
|
+
{ value: 'custom', label: 'Custom' },
|
|
72
|
+
]
|
|
73
|
+
const SIZES: { value: InstanceSize; label: string }[] = [
|
|
74
|
+
{ value: 'small', label: 'Small' },
|
|
75
|
+
{ value: 'medium', label: 'Medium' },
|
|
76
|
+
{ value: 'large', label: 'Large' },
|
|
77
|
+
{ value: 'xlarge', label: 'XLarge' },
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
function setProvider(value: CloudProvider) {
|
|
81
|
+
board.updateBlock(props.block.id, { cloudProvider: value })
|
|
82
|
+
}
|
|
83
|
+
function setSize(value: InstanceSize) {
|
|
84
|
+
board.updateBlock(props.block.id, { instanceSize: value })
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const missingInfra = computed(() => !noInfra.value && composePath.value.trim() === '')
|
|
88
|
+
</script>
|
|
89
|
+
|
|
90
|
+
<template>
|
|
91
|
+
<div class="space-y-3">
|
|
92
|
+
<div class="text-[11px] font-semibold uppercase tracking-wide text-slate-500">
|
|
93
|
+
Test infrastructure
|
|
94
|
+
</div>
|
|
95
|
+
|
|
96
|
+
<div class="space-y-1">
|
|
97
|
+
<label class="text-[11px] text-slate-400">docker-compose path</label>
|
|
98
|
+
<div class="flex items-center gap-1">
|
|
99
|
+
<UInput
|
|
100
|
+
:model-value="composePath"
|
|
101
|
+
size="xs"
|
|
102
|
+
class="flex-1"
|
|
103
|
+
placeholder="docker-compose.yml"
|
|
104
|
+
:disabled="noInfra"
|
|
105
|
+
@blur="(e: FocusEvent) => setComposePath((e.target as HTMLInputElement).value)"
|
|
106
|
+
@keydown.enter="
|
|
107
|
+
(e: KeyboardEvent) => setComposePath((e.target as HTMLInputElement).value)
|
|
108
|
+
"
|
|
109
|
+
/>
|
|
110
|
+
<UButton
|
|
111
|
+
v-if="repoContext"
|
|
112
|
+
size="xs"
|
|
113
|
+
variant="soft"
|
|
114
|
+
color="neutral"
|
|
115
|
+
icon="i-lucide-folder-search"
|
|
116
|
+
:disabled="noInfra"
|
|
117
|
+
title="Browse the repository for the compose file"
|
|
118
|
+
@click="openBrowse"
|
|
119
|
+
/>
|
|
120
|
+
</div>
|
|
121
|
+
<p class="text-[11px] leading-snug text-slate-500">
|
|
122
|
+
Used by the Tester to stand up the service's dependencies locally.
|
|
123
|
+
</p>
|
|
124
|
+
</div>
|
|
125
|
+
|
|
126
|
+
<UModal v-model:open="browseOpen" title="Select the docker-compose file">
|
|
127
|
+
<template #body>
|
|
128
|
+
<div v-if="repoContext" class="space-y-3">
|
|
129
|
+
<p class="text-xs text-slate-400">
|
|
130
|
+
Pick the compose file in the repository — its path is stored relative to the repo root.
|
|
131
|
+
</p>
|
|
132
|
+
<RepoTreeBrowser
|
|
133
|
+
v-model="pickedPath"
|
|
134
|
+
:repo-github-id="repoContext.githubId"
|
|
135
|
+
mode="file"
|
|
136
|
+
:start-path="repoContext.directory ?? ''"
|
|
137
|
+
/>
|
|
138
|
+
<div class="flex items-center justify-between gap-2">
|
|
139
|
+
<p class="truncate text-xs text-slate-400">
|
|
140
|
+
<template v-if="pickedPath">
|
|
141
|
+
Selected: <code class="text-slate-200">{{ pickedPath }}</code>
|
|
142
|
+
</template>
|
|
143
|
+
<template v-else>No file selected.</template>
|
|
144
|
+
</p>
|
|
145
|
+
<UButton size="xs" color="primary" :disabled="!pickedPath" @click="applyPicked">
|
|
146
|
+
Use this file
|
|
147
|
+
</UButton>
|
|
148
|
+
</div>
|
|
149
|
+
</div>
|
|
150
|
+
</template>
|
|
151
|
+
</UModal>
|
|
152
|
+
|
|
153
|
+
<label class="flex items-center gap-2 text-[11px] text-slate-400">
|
|
154
|
+
<UCheckbox
|
|
155
|
+
:model-value="noInfra"
|
|
156
|
+
@update:model-value="(v: boolean | 'indeterminate') => toggleNoInfra(v === true)"
|
|
157
|
+
/>
|
|
158
|
+
No infra dependencies (the Tester spins nothing up)
|
|
159
|
+
</label>
|
|
160
|
+
|
|
161
|
+
<p v-if="missingInfra" class="text-[11px] leading-snug text-amber-500">
|
|
162
|
+
Set a docker-compose path or enable “no infra dependencies”, otherwise a pipeline with a
|
|
163
|
+
Tester won't start.
|
|
164
|
+
</p>
|
|
165
|
+
|
|
166
|
+
<div class="space-y-1">
|
|
167
|
+
<span class="text-[11px] text-slate-400">Cloud provider</span>
|
|
168
|
+
<div class="flex flex-wrap gap-1">
|
|
169
|
+
<UButton
|
|
170
|
+
v-for="p in PROVIDERS"
|
|
171
|
+
:key="p.value"
|
|
172
|
+
:color="effectiveProvider === p.value ? 'primary' : 'neutral'"
|
|
173
|
+
:variant="effectiveProvider === p.value ? 'soft' : 'ghost'"
|
|
174
|
+
size="xs"
|
|
175
|
+
@click="setProvider(p.value)"
|
|
176
|
+
>
|
|
177
|
+
{{ p.label }}
|
|
178
|
+
</UButton>
|
|
179
|
+
</div>
|
|
180
|
+
</div>
|
|
181
|
+
|
|
182
|
+
<div class="space-y-1">
|
|
183
|
+
<span class="text-[11px] text-slate-400">Instance size</span>
|
|
184
|
+
<div class="flex flex-wrap gap-1">
|
|
185
|
+
<UButton
|
|
186
|
+
v-for="s in SIZES"
|
|
187
|
+
:key="s.value"
|
|
188
|
+
:color="(block.instanceSize ?? 'medium') === s.value ? 'primary' : 'neutral'"
|
|
189
|
+
:variant="(block.instanceSize ?? 'medium') === s.value ? 'soft' : 'ghost'"
|
|
190
|
+
size="xs"
|
|
191
|
+
@click="setSize(s.value)"
|
|
192
|
+
>
|
|
193
|
+
{{ s.label }}
|
|
194
|
+
</UButton>
|
|
195
|
+
</div>
|
|
196
|
+
</div>
|
|
197
|
+
</div>
|
|
198
|
+
</template>
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from 'vue'
|
|
3
|
+
import type { Block } from '~/types/domain'
|
|
4
|
+
import { useAgentConfigStore } from '~/stores/agentConfig'
|
|
5
|
+
import { useExecutionStore } from '~/stores/execution'
|
|
6
|
+
|
|
7
|
+
// Task-level configuration contributed by the agents in this task's selected
|
|
8
|
+
// pipeline (e.g. the Tester's environment: local vs ephemeral). Each value is
|
|
9
|
+
// editable until its contributing agent's step starts, then it freezes (the run is
|
|
10
|
+
// already consuming it). Persisted as a sparse id→value map on the block.
|
|
11
|
+
const props = defineProps<{ block: Block }>()
|
|
12
|
+
|
|
13
|
+
const board = useBoardStore()
|
|
14
|
+
const agentConfig = useAgentConfigStore()
|
|
15
|
+
const execution = useExecutionStore()
|
|
16
|
+
|
|
17
|
+
// The descriptors that apply: those contributed by the task's pinned pipeline, plus
|
|
18
|
+
// any whose value is already set (so an existing choice always stays visible/editable
|
|
19
|
+
// even if the pinned pipeline changed).
|
|
20
|
+
const descriptors = computed(() => {
|
|
21
|
+
const byPipeline = agentConfig.forPipeline(props.block.pipelineId)
|
|
22
|
+
const seen = new Set(byPipeline.map((d) => d.id))
|
|
23
|
+
const fromValues = Object.keys(props.block.agentConfig ?? {})
|
|
24
|
+
.filter((id) => !seen.has(id))
|
|
25
|
+
.map((id) => agentConfig.descriptors.find((d) => d.id === id))
|
|
26
|
+
.filter((d): d is NonNullable<typeof d> => Boolean(d))
|
|
27
|
+
return [...byPipeline, ...fromValues]
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
const run = computed(() => execution.getByBlock(props.block.id))
|
|
31
|
+
|
|
32
|
+
/** A descriptor freezes once its contributing agent's step has left `pending`. */
|
|
33
|
+
function isFrozen(agentKind: string): boolean {
|
|
34
|
+
const steps = run.value?.steps
|
|
35
|
+
if (!steps) return false
|
|
36
|
+
const step = steps.find((s) => s.agentKind === agentKind)
|
|
37
|
+
return Boolean(step && step.state !== 'pending')
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function valueOf(id: string, fallback: string): string {
|
|
41
|
+
return props.block.agentConfig?.[id] ?? fallback
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function setValue(id: string, value: string) {
|
|
45
|
+
const next = { ...props.block.agentConfig, [id]: value }
|
|
46
|
+
board.updateBlock(props.block.id, { agentConfig: next })
|
|
47
|
+
}
|
|
48
|
+
</script>
|
|
49
|
+
|
|
50
|
+
<template>
|
|
51
|
+
<div v-if="descriptors.length" class="space-y-3">
|
|
52
|
+
<div class="text-[11px] font-semibold uppercase tracking-wide text-slate-500">
|
|
53
|
+
Agent configuration
|
|
54
|
+
</div>
|
|
55
|
+
<div v-for="d in descriptors" :key="d.id" class="space-y-1">
|
|
56
|
+
<div class="flex items-center justify-between">
|
|
57
|
+
<span class="text-[11px] text-slate-400">{{ d.label }}</span>
|
|
58
|
+
<UIcon
|
|
59
|
+
v-if="isFrozen(d.agentKind)"
|
|
60
|
+
name="i-lucide-lock"
|
|
61
|
+
class="h-3 w-3 text-slate-500"
|
|
62
|
+
title="Frozen — the agent has started"
|
|
63
|
+
/>
|
|
64
|
+
</div>
|
|
65
|
+
<div class="flex flex-wrap gap-1">
|
|
66
|
+
<UButton
|
|
67
|
+
v-for="opt in d.options"
|
|
68
|
+
:key="opt.value"
|
|
69
|
+
:color="valueOf(d.id, d.default) === opt.value ? 'primary' : 'neutral'"
|
|
70
|
+
:variant="valueOf(d.id, d.default) === opt.value ? 'soft' : 'ghost'"
|
|
71
|
+
size="xs"
|
|
72
|
+
:disabled="isFrozen(d.agentKind)"
|
|
73
|
+
@click="setValue(d.id, opt.value)"
|
|
74
|
+
>
|
|
75
|
+
{{ opt.label }}
|
|
76
|
+
</UButton>
|
|
77
|
+
</div>
|
|
78
|
+
<p class="text-[11px] leading-snug text-slate-500">{{ d.description }}</p>
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
</template>
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { Block } from '~/types/domain'
|
|
3
|
+
|
|
4
|
+
const props = defineProps<{ block: Block }>()
|
|
5
|
+
|
|
6
|
+
const board = useBoardStore()
|
|
7
|
+
const { depLabel } = useDepLabels()
|
|
8
|
+
|
|
9
|
+
const deps = computed(() =>
|
|
10
|
+
(props.block.dependsOn ?? []).map((id) => board.getBlock(id)).filter((b): b is Block => !!b),
|
|
11
|
+
)
|
|
12
|
+
const runnable = computed(() => board.isRunnable(props.block.id))
|
|
13
|
+
|
|
14
|
+
/** Label a dependency relative to this task's container. */
|
|
15
|
+
const label = (dep: Block) => depLabel(dep, props.block.parentId)
|
|
16
|
+
|
|
17
|
+
// candidate tasks to depend on: any other task not already a dependency
|
|
18
|
+
const depMenu = computed(() => {
|
|
19
|
+
const current = new Set(props.block.dependsOn)
|
|
20
|
+
return board.allTasks
|
|
21
|
+
.filter((t) => t.id !== props.block.id && !current.has(t.id))
|
|
22
|
+
.map((t) => ({
|
|
23
|
+
label: label(t),
|
|
24
|
+
icon: 'i-lucide-plus',
|
|
25
|
+
onSelect: () => board.toggleDependency(props.block.id, t.id),
|
|
26
|
+
}))
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
function removeDep(depId: string) {
|
|
30
|
+
board.removeDependency(props.block.id, depId)
|
|
31
|
+
}
|
|
32
|
+
</script>
|
|
33
|
+
|
|
34
|
+
<template>
|
|
35
|
+
<div>
|
|
36
|
+
<div class="mb-1 flex items-center justify-between">
|
|
37
|
+
<span class="text-[11px] font-semibold uppercase tracking-wide text-slate-400">
|
|
38
|
+
Depends on
|
|
39
|
+
</span>
|
|
40
|
+
<UDropdownMenu v-if="depMenu.length" :items="depMenu">
|
|
41
|
+
<UButton
|
|
42
|
+
size="xs"
|
|
43
|
+
variant="ghost"
|
|
44
|
+
color="neutral"
|
|
45
|
+
icon="i-lucide-plus"
|
|
46
|
+
trailing-icon="i-lucide-chevron-down"
|
|
47
|
+
/>
|
|
48
|
+
</UDropdownMenu>
|
|
49
|
+
</div>
|
|
50
|
+
<div v-if="deps.length" class="flex flex-wrap gap-1">
|
|
51
|
+
<UBadge
|
|
52
|
+
v-for="d in deps"
|
|
53
|
+
:key="d.id"
|
|
54
|
+
:color="d.status === 'done' ? 'neutral' : 'warning'"
|
|
55
|
+
variant="subtle"
|
|
56
|
+
size="sm"
|
|
57
|
+
class="cursor-pointer"
|
|
58
|
+
:title="d.status === 'done' ? 'Merged' : 'Not merged yet'"
|
|
59
|
+
@click="removeDep(d.id)"
|
|
60
|
+
>
|
|
61
|
+
{{ label(d) }}
|
|
62
|
+
<UIcon name="i-lucide-x" class="ml-0.5 h-3 w-3" />
|
|
63
|
+
</UBadge>
|
|
64
|
+
</div>
|
|
65
|
+
<div v-else class="text-[11px] text-slate-500">No dependencies — can run any time.</div>
|
|
66
|
+
<div v-if="!runnable" class="mt-1 text-[10px] text-amber-400">
|
|
67
|
+
Blocked until dependencies merge.
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
</template>
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// Compact display of a task's estimator triage (Complexity / Risk / Impact), shown on the
|
|
3
|
+
// inspector once a `task-estimator` step has run. Read-only — produced by the estimator,
|
|
4
|
+
// used to gate consensus steps. Hidden when no estimate exists.
|
|
5
|
+
import { computed } from 'vue'
|
|
6
|
+
import type { Block } from '~/types/domain'
|
|
7
|
+
|
|
8
|
+
const props = defineProps<{ block: Block }>()
|
|
9
|
+
|
|
10
|
+
const estimate = computed(() => props.block.estimate ?? null)
|
|
11
|
+
|
|
12
|
+
const AXES = [
|
|
13
|
+
{ key: 'complexity', label: 'Complexity' },
|
|
14
|
+
{ key: 'risk', label: 'Risk' },
|
|
15
|
+
{ key: 'impact', label: 'Impact' },
|
|
16
|
+
] as const
|
|
17
|
+
|
|
18
|
+
function pct(n: number): number {
|
|
19
|
+
return Math.round(n * 100)
|
|
20
|
+
}
|
|
21
|
+
/** Cool→hot bar colour by severity (low = sky, mid = amber, high = rose). */
|
|
22
|
+
function barClass(n: number): string {
|
|
23
|
+
if (n >= 0.66) return 'bg-rose-500'
|
|
24
|
+
if (n >= 0.33) return 'bg-amber-500'
|
|
25
|
+
return 'bg-sky-500'
|
|
26
|
+
}
|
|
27
|
+
</script>
|
|
28
|
+
|
|
29
|
+
<template>
|
|
30
|
+
<section v-if="estimate" class="space-y-2">
|
|
31
|
+
<div
|
|
32
|
+
class="flex items-center gap-1.5 text-xs font-semibold uppercase tracking-wide text-slate-400"
|
|
33
|
+
>
|
|
34
|
+
<UIcon name="i-lucide-gauge" class="h-3.5 w-3.5" />
|
|
35
|
+
Estimate
|
|
36
|
+
</div>
|
|
37
|
+
<div class="space-y-1.5 rounded-lg border border-slate-800 bg-slate-900/40 p-2.5">
|
|
38
|
+
<div v-for="axis in AXES" :key="axis.key" class="flex items-center gap-2">
|
|
39
|
+
<span class="w-20 shrink-0 text-xs text-slate-400">{{ axis.label }}</span>
|
|
40
|
+
<div class="h-1.5 flex-1 overflow-hidden rounded-full bg-slate-800">
|
|
41
|
+
<div
|
|
42
|
+
class="h-full rounded-full"
|
|
43
|
+
:class="barClass(estimate[axis.key])"
|
|
44
|
+
:style="{ width: `${pct(estimate[axis.key])}%` }"
|
|
45
|
+
/>
|
|
46
|
+
</div>
|
|
47
|
+
<span class="w-9 shrink-0 text-right text-xs tabular-nums text-slate-300"
|
|
48
|
+
>{{ pct(estimate[axis.key]) }}%</span
|
|
49
|
+
>
|
|
50
|
+
</div>
|
|
51
|
+
<p v-if="estimate.rationale" class="pt-1 text-xs leading-relaxed text-slate-500">
|
|
52
|
+
{{ estimate.rationale }}
|
|
53
|
+
</p>
|
|
54
|
+
</div>
|
|
55
|
+
</section>
|
|
56
|
+
</template>
|