@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,71 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { agentKindMeta } from '~/utils/catalog'
|
|
3
|
+
|
|
4
|
+
const execution = useExecutionStore()
|
|
5
|
+
const board = useBoardStore()
|
|
6
|
+
const ui = useUiStore()
|
|
7
|
+
|
|
8
|
+
const ctx = computed(() => ui.decisionContext)
|
|
9
|
+
|
|
10
|
+
const instance = computed(() => execution.getInstance(ctx.value?.instanceId))
|
|
11
|
+
const step = computed(() =>
|
|
12
|
+
instance.value?.steps.find((s) => s.decision?.id === ctx.value?.decisionId),
|
|
13
|
+
)
|
|
14
|
+
const decision = computed(() => step.value?.decision ?? null)
|
|
15
|
+
const block = computed(() => (instance.value ? board.getBlock(instance.value.blockId) : undefined))
|
|
16
|
+
const agent = computed(() => (step.value ? agentKindMeta(step.value.agentKind) : null))
|
|
17
|
+
|
|
18
|
+
const open = computed({
|
|
19
|
+
get: () => !!ctx.value && !!decision.value,
|
|
20
|
+
set: (v: boolean) => {
|
|
21
|
+
if (!v) ui.closeDecision()
|
|
22
|
+
},
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
function choose(option: string) {
|
|
26
|
+
if (!ctx.value) return
|
|
27
|
+
execution.resolveDecision(ctx.value.instanceId, ctx.value.decisionId, option)
|
|
28
|
+
ui.closeDecision()
|
|
29
|
+
}
|
|
30
|
+
</script>
|
|
31
|
+
|
|
32
|
+
<template>
|
|
33
|
+
<UModal v-model:open="open" title="Decision required">
|
|
34
|
+
<template #body>
|
|
35
|
+
<div v-if="decision && agent" class="space-y-4">
|
|
36
|
+
<div class="flex items-center gap-2 text-sm text-slate-400">
|
|
37
|
+
<div
|
|
38
|
+
class="flex h-8 w-8 items-center justify-center rounded-lg"
|
|
39
|
+
:style="{ backgroundColor: agent.color + '22' }"
|
|
40
|
+
>
|
|
41
|
+
<UIcon :name="agent.icon" class="h-4 w-4" :style="{ color: agent.color }" />
|
|
42
|
+
</div>
|
|
43
|
+
<div>
|
|
44
|
+
<span class="font-medium text-slate-200">{{ agent.label }}</span>
|
|
45
|
+
<span v-if="block"> on </span>
|
|
46
|
+
<span v-if="block" class="font-medium text-slate-200">{{ block.title }}</span>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
|
|
50
|
+
<p class="text-base font-medium text-white">{{ decision.question }}</p>
|
|
51
|
+
|
|
52
|
+
<div class="grid gap-2">
|
|
53
|
+
<UButton
|
|
54
|
+
v-for="opt in decision.options"
|
|
55
|
+
:key="opt"
|
|
56
|
+
color="primary"
|
|
57
|
+
variant="soft"
|
|
58
|
+
block
|
|
59
|
+
class="justify-start"
|
|
60
|
+
@click="choose(opt)"
|
|
61
|
+
>
|
|
62
|
+
{{ opt }}
|
|
63
|
+
</UButton>
|
|
64
|
+
</div>
|
|
65
|
+
<p class="text-[11px] text-slate-500">
|
|
66
|
+
This is a visualization — any choice simply resumes the pipeline.
|
|
67
|
+
</p>
|
|
68
|
+
</div>
|
|
69
|
+
</template>
|
|
70
|
+
</UModal>
|
|
71
|
+
</template>
|
|
@@ -0,0 +1,465 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { Block, BlockStatus } from '~/types/domain'
|
|
3
|
+
import { BLOCK_TYPE_META, STATUS_META } from '~/utils/catalog'
|
|
4
|
+
import TaskContextDocs from '~/components/documents/TaskContextDocs.vue'
|
|
5
|
+
import TaskContextIssues from '~/components/tasks/TaskContextIssues.vue'
|
|
6
|
+
import TaskAgentConfig from '~/components/panels/inspector/TaskAgentConfig.vue'
|
|
7
|
+
import ServiceTestConfig from '~/components/panels/inspector/ServiceTestConfig.vue'
|
|
8
|
+
import ServiceFragments from '~/components/panels/inspector/ServiceFragments.vue'
|
|
9
|
+
import ContainerSummary from '~/components/panels/inspector/ContainerSummary.vue'
|
|
10
|
+
import TaskDependencies from '~/components/panels/inspector/TaskDependencies.vue'
|
|
11
|
+
import TaskStructure from '~/components/panels/inspector/TaskStructure.vue'
|
|
12
|
+
import TaskRunSettings from '~/components/panels/inspector/TaskRunSettings.vue'
|
|
13
|
+
import TaskExecution from '~/components/panels/inspector/TaskExecution.vue'
|
|
14
|
+
import RecurringScheduleSettings from '~/components/panels/inspector/RecurringScheduleSettings.vue'
|
|
15
|
+
import AgentFailureCard from '~/components/board/AgentFailureCard.vue'
|
|
16
|
+
import AgentStopButton from '~/components/board/AgentStopButton.vue'
|
|
17
|
+
|
|
18
|
+
const board = useBoardStore()
|
|
19
|
+
const pipelines = usePipelinesStore()
|
|
20
|
+
const execution = useExecutionStore()
|
|
21
|
+
const ui = useUiStore()
|
|
22
|
+
const documents = useDocumentsStore()
|
|
23
|
+
const tasks = useTasksStore()
|
|
24
|
+
const fragments = useFragmentsStore()
|
|
25
|
+
const agentRuns = useAgentRunsStore()
|
|
26
|
+
const github = useGitHubStore()
|
|
27
|
+
const recurring = useRecurringPipelinesStore()
|
|
28
|
+
const requirements = useRequirementsStore()
|
|
29
|
+
|
|
30
|
+
// When the selected task block backs a recurring pipeline, the inspector shows the
|
|
31
|
+
// schedule controls + history, and "Delete" removes the schedule (block + history).
|
|
32
|
+
const schedule = computed(() => (block.value ? recurring.byBlock(block.value.id) : undefined))
|
|
33
|
+
|
|
34
|
+
onMounted(() => {
|
|
35
|
+
fragments.ensureLoaded()
|
|
36
|
+
github.ensureLoaded()
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
/** Open the document import/spawn flow, targeting this container's frame. */
|
|
40
|
+
function spawnFromDocument() {
|
|
41
|
+
if (!block.value) return
|
|
42
|
+
const frameId = isFrame.value ? block.value.id : (board.serviceOf(block.value)?.id ?? null)
|
|
43
|
+
ui.openDocumentImport(frameId)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const block = computed<Block | undefined>(() =>
|
|
47
|
+
ui.selectedBlockId ? board.getBlock(ui.selectedBlockId) : undefined,
|
|
48
|
+
)
|
|
49
|
+
const level = computed(() => block.value?.level ?? 'frame')
|
|
50
|
+
const isFrame = computed(() => level.value === 'frame')
|
|
51
|
+
const isContainer = computed(() => level.value === 'frame' || level.value === 'module')
|
|
52
|
+
const isTask = computed(() => level.value === 'task')
|
|
53
|
+
|
|
54
|
+
const instance = computed(() => execution.getInstance(block.value?.executionId))
|
|
55
|
+
const typeMeta = computed(() => (block.value ? BLOCK_TYPE_META[block.value.type] : null))
|
|
56
|
+
|
|
57
|
+
// Containers show a derived activity status (never "done"); tasks use their own.
|
|
58
|
+
const FRAME_LABEL: Record<BlockStatus, string> = {
|
|
59
|
+
planned: 'No tasks',
|
|
60
|
+
ready: 'Live',
|
|
61
|
+
in_progress: 'Active',
|
|
62
|
+
blocked: 'Needs attention',
|
|
63
|
+
pr_ready: 'Active',
|
|
64
|
+
done: 'Live',
|
|
65
|
+
}
|
|
66
|
+
const effectiveStatus = computed<BlockStatus>(() =>
|
|
67
|
+
isContainer.value ? board.frameStatus(block.value!.id) : block.value!.status,
|
|
68
|
+
)
|
|
69
|
+
const statusMeta = computed(() => (block.value ? STATUS_META[effectiveStatus.value] : null))
|
|
70
|
+
const statusLabel = computed(() =>
|
|
71
|
+
isContainer.value ? FRAME_LABEL[effectiveStatus.value] : statusMeta.value!.label,
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
const runnable = computed(() => (block.value ? board.isRunnable(block.value.id) : false))
|
|
75
|
+
|
|
76
|
+
// The delete control names what it removes, so selecting a task and deleting it
|
|
77
|
+
// reads as "Delete task" rather than ambiguously removing the whole service.
|
|
78
|
+
const deleteLabel = computed(() =>
|
|
79
|
+
schedule.value
|
|
80
|
+
? 'Delete recurring pipeline'
|
|
81
|
+
: isTask.value
|
|
82
|
+
? 'Delete task'
|
|
83
|
+
: level.value === 'module'
|
|
84
|
+
? 'Delete module'
|
|
85
|
+
: 'Delete service',
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
// A task is "started" once a pipeline has been launched on it (it has an
|
|
89
|
+
// execution, or has moved past the pre-run states). Until then the user can keep
|
|
90
|
+
// editing its title + description; afterwards those details are locked. Non-task
|
|
91
|
+
// containers (frames / modules) are always editable.
|
|
92
|
+
const started = computed(
|
|
93
|
+
() =>
|
|
94
|
+
isTask.value &&
|
|
95
|
+
(!!block.value?.executionId || !['planned', 'ready'].includes(block.value!.status)),
|
|
96
|
+
)
|
|
97
|
+
const editable = computed(() => !started.value)
|
|
98
|
+
|
|
99
|
+
function saveTitle() {
|
|
100
|
+
const b = block.value
|
|
101
|
+
if (!b) return
|
|
102
|
+
const next = b.title.trim()
|
|
103
|
+
if (next) board.updateBlock(b.id, { title: next })
|
|
104
|
+
}
|
|
105
|
+
function saveDescription() {
|
|
106
|
+
const b = block.value
|
|
107
|
+
if (!b) return
|
|
108
|
+
board.updateBlock(b.id, { description: b.description ?? '' })
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// The GitHub repo backing this service (a frame), if one is linked. Linkage lives
|
|
112
|
+
// on the github_repos projection (its `blockId`), not on the block itself.
|
|
113
|
+
const serviceRepo = computed(() =>
|
|
114
|
+
isFrame.value && block.value ? github.repoForBlock(block.value.id) : undefined,
|
|
115
|
+
)
|
|
116
|
+
const serviceRepoUrl = computed(() =>
|
|
117
|
+
serviceRepo.value ? github.repoUrl(serviceRepo.value.githubId) : null,
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
// A task's work branch on GitHub, once the agent has pushed one (a PR branch is
|
|
121
|
+
// recorded on the block). Repo linkage lives on the owning service frame, not the
|
|
122
|
+
// task, so resolve the repo by walking up to the frame; fall back to deriving the
|
|
123
|
+
// repo base from the PR url when the projection hasn't loaded. Null until a branch
|
|
124
|
+
// exists, so the link only appears after one is created.
|
|
125
|
+
const taskBranchUrl = computed(() => {
|
|
126
|
+
const pr = isTask.value ? block.value?.pullRequest : undefined
|
|
127
|
+
if (!pr?.branch || !block.value) return null
|
|
128
|
+
const frame = board.serviceOf(block.value)
|
|
129
|
+
const repo = frame ? github.repoForBlock(frame.id) : undefined
|
|
130
|
+
const base = repo ? github.repoUrl(repo.githubId) : pr.url.replace(/\/pull\/\d+$/, '')
|
|
131
|
+
return base ? `${base}/tree/${pr.branch}` : null
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
const runMenu = computed(() =>
|
|
135
|
+
pipelines.pipelines.map((p) => ({
|
|
136
|
+
label: p.name,
|
|
137
|
+
icon: 'i-lucide-play',
|
|
138
|
+
onSelect: () => block.value && execution.start(block.value.id, p),
|
|
139
|
+
})),
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
function remove() {
|
|
143
|
+
if (!block.value) return
|
|
144
|
+
const id = block.value.id
|
|
145
|
+
// Close the inspector right away; the stores hide the block optimistically and
|
|
146
|
+
// restore it (with a toast) only if the backend delete fails.
|
|
147
|
+
ui.select(null)
|
|
148
|
+
// A schedule owns its reused block + run history — removing the schedule deletes
|
|
149
|
+
// them server-side, so delete the schedule rather than orphaning it.
|
|
150
|
+
if (schedule.value) {
|
|
151
|
+
void recurring.remove(schedule.value.id)
|
|
152
|
+
return
|
|
153
|
+
}
|
|
154
|
+
execution.cancel(id)
|
|
155
|
+
void board.removeBlock(id)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// ---- failed agent run (bootstrap or execution) ------------------------------
|
|
159
|
+
// A block whose current run failed surfaces the shared failure banner + retry,
|
|
160
|
+
// keyed by block id — covering a failed "bootstrap repo" frame and (for tasks) a
|
|
161
|
+
// failed pipeline execution alike.
|
|
162
|
+
const failedRun = computed(() => {
|
|
163
|
+
const run = block.value ? agentRuns.byBlock[block.value.id] : undefined
|
|
164
|
+
return run && run.status === 'failed' ? run : null
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
// A running run on a container frame (a "bootstrapping…" service). Tasks surface
|
|
168
|
+
// their own Stop in TaskExecution, so this covers the bootstrap case the board was
|
|
169
|
+
// previously unable to stop. Drives the inspector's Stop control.
|
|
170
|
+
const runningRun = computed(() => {
|
|
171
|
+
if (!isContainer.value) return null
|
|
172
|
+
const run = block.value ? agentRuns.byBlock[block.value.id] : undefined
|
|
173
|
+
return run && run.status === 'running' ? run : null
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
// ---- requirements review (collected requirements → react → rework) ----------
|
|
177
|
+
// The reviewer runs automatically as the first pipeline gate step (no manual entry
|
|
178
|
+
// point), but the inspector still probes + caches the block's review so the
|
|
179
|
+
// description can freeze in favour of the reworked requirements document once it
|
|
180
|
+
// exists (and so a prior incorporated doc can surface as a base after a reset).
|
|
181
|
+
watch(
|
|
182
|
+
() => (isTask.value ? block.value?.id : undefined),
|
|
183
|
+
(id) => {
|
|
184
|
+
if (id) void requirements.load(id)
|
|
185
|
+
},
|
|
186
|
+
{ immediate: true },
|
|
187
|
+
)
|
|
188
|
+
const reqReview = computed(() => (block.value ? requirements.reviewFor(block.value.id) : null))
|
|
189
|
+
const reqReworked = computed(() => reqReview.value?.status === 'incorporated')
|
|
190
|
+
const reqReworkedText = computed(() => reqReview.value?.incorporatedRequirements ?? '')
|
|
191
|
+
// Once a task's requirements have been reworked, the standardized document is what
|
|
192
|
+
// every agent step consumes — so the raw description is frozen (read-only) and hidden
|
|
193
|
+
// behind an expander, with the reworked requirements taking focus instead.
|
|
194
|
+
const frozenByRework = computed(() => isTask.value && reqReworked.value)
|
|
195
|
+
// After a "stop & reset" the task is editable again (phase zero) but the LAST incorporated
|
|
196
|
+
// requirements survive on the review as a base to rework from — surfaced read-only here.
|
|
197
|
+
const reqHasPriorDoc = computed(() => isTask.value && !reqReworked.value && !!reqReworkedText.value)
|
|
198
|
+
const showOriginalDescription = ref(false)
|
|
199
|
+
</script>
|
|
200
|
+
|
|
201
|
+
<template>
|
|
202
|
+
<div
|
|
203
|
+
v-if="block && statusMeta && typeMeta"
|
|
204
|
+
class="absolute right-4 top-4 z-20 w-80 overflow-hidden rounded-2xl border border-slate-700 bg-slate-900/95 shadow-2xl backdrop-blur"
|
|
205
|
+
>
|
|
206
|
+
<div class="h-1.5 w-full" :style="{ backgroundColor: statusMeta.color }" />
|
|
207
|
+
<!-- A tall task (execution steps + scenarios + docs) can overflow the
|
|
208
|
+
viewport; cap the body height and let it scroll so the lower controls
|
|
209
|
+
(Run / Focus / Delete) stay reachable. The status bar above stays put. -->
|
|
210
|
+
<div class="max-h-[calc(100vh-5rem)] space-y-4 overflow-y-auto p-4">
|
|
211
|
+
<!-- header -->
|
|
212
|
+
<div class="flex items-start justify-between gap-2">
|
|
213
|
+
<div class="flex items-center gap-2">
|
|
214
|
+
<div
|
|
215
|
+
class="flex h-9 w-9 items-center justify-center rounded-lg"
|
|
216
|
+
:style="{ backgroundColor: typeMeta.accent + '22' }"
|
|
217
|
+
>
|
|
218
|
+
<UIcon :name="typeMeta.icon" class="h-5 w-5" :style="{ color: typeMeta.accent }" />
|
|
219
|
+
</div>
|
|
220
|
+
<div>
|
|
221
|
+
<div class="text-sm font-semibold text-white">{{ block.title }}</div>
|
|
222
|
+
<div class="mt-0.5 flex items-center gap-1.5">
|
|
223
|
+
<UBadge :color="statusMeta.chip as any" variant="subtle" size="sm">
|
|
224
|
+
{{ statusLabel }}
|
|
225
|
+
</UBadge>
|
|
226
|
+
<span class="text-[10px] uppercase tracking-wide text-slate-500">{{ level }}</span>
|
|
227
|
+
</div>
|
|
228
|
+
</div>
|
|
229
|
+
</div>
|
|
230
|
+
<UButton
|
|
231
|
+
icon="i-lucide-x"
|
|
232
|
+
color="neutral"
|
|
233
|
+
variant="ghost"
|
|
234
|
+
size="xs"
|
|
235
|
+
@click="ui.select(null)"
|
|
236
|
+
/>
|
|
237
|
+
</div>
|
|
238
|
+
|
|
239
|
+
<!-- editable identity: title + description. Edits persist to the backend.
|
|
240
|
+
A task's details lock once it has been started (a pipeline launched), and
|
|
241
|
+
once its requirements have been reworked the description is frozen in favour
|
|
242
|
+
of the standardized requirements document. -->
|
|
243
|
+
<div class="space-y-2">
|
|
244
|
+
<UInput
|
|
245
|
+
v-if="editable && !frozenByRework"
|
|
246
|
+
v-model="block.title"
|
|
247
|
+
size="sm"
|
|
248
|
+
class="w-full"
|
|
249
|
+
placeholder="Title…"
|
|
250
|
+
@change="saveTitle"
|
|
251
|
+
@blur="saveTitle"
|
|
252
|
+
/>
|
|
253
|
+
|
|
254
|
+
<!-- reworked: the standardized requirements document takes focus; the raw
|
|
255
|
+
description is frozen and tucked behind an expander. -->
|
|
256
|
+
<template v-if="frozenByRework">
|
|
257
|
+
<div class="rounded-lg border border-emerald-900/60 bg-emerald-950/20 p-3">
|
|
258
|
+
<div
|
|
259
|
+
class="mb-1.5 flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wide text-emerald-400"
|
|
260
|
+
>
|
|
261
|
+
<UIcon name="i-lucide-file-check-2" class="h-3.5 w-3.5" />
|
|
262
|
+
Reworked requirements
|
|
263
|
+
</div>
|
|
264
|
+
<p class="line-clamp-5 whitespace-pre-line text-[13px] leading-relaxed text-slate-300">
|
|
265
|
+
{{ reqReworkedText }}
|
|
266
|
+
</p>
|
|
267
|
+
<div class="mt-2 flex items-center justify-between gap-2">
|
|
268
|
+
<p class="text-[11px] text-slate-500">Agent steps use this document.</p>
|
|
269
|
+
<UButton
|
|
270
|
+
color="neutral"
|
|
271
|
+
variant="link"
|
|
272
|
+
size="xs"
|
|
273
|
+
icon="i-lucide-maximize-2"
|
|
274
|
+
@click="ui.openRequirementReview(block.id)"
|
|
275
|
+
>
|
|
276
|
+
Open
|
|
277
|
+
</UButton>
|
|
278
|
+
</div>
|
|
279
|
+
</div>
|
|
280
|
+
<UButton
|
|
281
|
+
color="neutral"
|
|
282
|
+
variant="ghost"
|
|
283
|
+
size="xs"
|
|
284
|
+
:icon="showOriginalDescription ? 'i-lucide-chevron-down' : 'i-lucide-chevron-right'"
|
|
285
|
+
@click="showOriginalDescription = !showOriginalDescription"
|
|
286
|
+
>
|
|
287
|
+
Original description (frozen)
|
|
288
|
+
</UButton>
|
|
289
|
+
<UTextarea
|
|
290
|
+
v-if="showOriginalDescription"
|
|
291
|
+
:model-value="block.description"
|
|
292
|
+
:rows="2"
|
|
293
|
+
autoresize
|
|
294
|
+
size="sm"
|
|
295
|
+
class="w-full"
|
|
296
|
+
disabled
|
|
297
|
+
placeholder="No description"
|
|
298
|
+
/>
|
|
299
|
+
</template>
|
|
300
|
+
|
|
301
|
+
<!-- normal: editable (or started-locked) description -->
|
|
302
|
+
<template v-else>
|
|
303
|
+
<UTextarea
|
|
304
|
+
v-model="block.description"
|
|
305
|
+
:rows="2"
|
|
306
|
+
autoresize
|
|
307
|
+
size="sm"
|
|
308
|
+
class="w-full"
|
|
309
|
+
:disabled="!editable"
|
|
310
|
+
placeholder="Describe this block…"
|
|
311
|
+
@change="saveDescription"
|
|
312
|
+
@blur="saveDescription"
|
|
313
|
+
/>
|
|
314
|
+
<p v-if="isTask && !editable" class="flex items-center gap-1 text-[11px] text-slate-500">
|
|
315
|
+
<UIcon name="i-lucide-lock" class="h-3 w-3" />
|
|
316
|
+
This task has started — its details are locked.
|
|
317
|
+
</p>
|
|
318
|
+
|
|
319
|
+
<!-- prior incorporated requirements kept as a base after a review-driven reset -->
|
|
320
|
+
<div v-if="reqHasPriorDoc" class="rounded-lg border border-slate-700 bg-slate-800/40 p-3">
|
|
321
|
+
<div
|
|
322
|
+
class="mb-1.5 flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wide text-slate-400"
|
|
323
|
+
>
|
|
324
|
+
<UIcon name="i-lucide-history" class="h-3.5 w-3.5" />
|
|
325
|
+
Last incorporated requirements
|
|
326
|
+
</div>
|
|
327
|
+
<p class="line-clamp-5 whitespace-pre-line text-[13px] leading-relaxed text-slate-300">
|
|
328
|
+
{{ reqReworkedText }}
|
|
329
|
+
</p>
|
|
330
|
+
<p class="mt-2 text-[11px] text-slate-500">
|
|
331
|
+
From the previous review. Use it as a base — edit the description above and resubmit.
|
|
332
|
+
</p>
|
|
333
|
+
</div>
|
|
334
|
+
</template>
|
|
335
|
+
</div>
|
|
336
|
+
|
|
337
|
+
<!-- failed run (bootstrap or execution): shared failure banner + retry -->
|
|
338
|
+
<AgentFailureCard v-if="failedRun" :run="failedRun" />
|
|
339
|
+
|
|
340
|
+
<!-- running bootstrap: let the user stop it (kills the container) -->
|
|
341
|
+
<div
|
|
342
|
+
v-else-if="runningRun"
|
|
343
|
+
class="flex items-center justify-between gap-2 rounded-lg border border-amber-900/60 bg-amber-950/30 px-3 py-2"
|
|
344
|
+
>
|
|
345
|
+
<span class="flex items-center gap-1.5 text-xs text-amber-300">
|
|
346
|
+
<UIcon name="i-lucide-loader-circle" class="h-3.5 w-3.5 animate-spin" />
|
|
347
|
+
Bootstrapping…
|
|
348
|
+
</span>
|
|
349
|
+
<AgentStopButton :run-id="runningRun.runId" :kind="runningRun.kind" size="xs" />
|
|
350
|
+
</div>
|
|
351
|
+
|
|
352
|
+
<!-- external links -->
|
|
353
|
+
<div class="flex flex-wrap gap-2">
|
|
354
|
+
<UButton
|
|
355
|
+
v-if="serviceRepoUrl"
|
|
356
|
+
:to="serviceRepoUrl"
|
|
357
|
+
target="_blank"
|
|
358
|
+
rel="noopener"
|
|
359
|
+
color="neutral"
|
|
360
|
+
variant="soft"
|
|
361
|
+
size="xs"
|
|
362
|
+
icon="i-lucide-github"
|
|
363
|
+
trailing-icon="i-lucide-external-link"
|
|
364
|
+
>
|
|
365
|
+
{{ serviceRepo!.owner }}/{{ serviceRepo!.name }}
|
|
366
|
+
</UButton>
|
|
367
|
+
<UButton
|
|
368
|
+
v-if="taskBranchUrl"
|
|
369
|
+
:to="taskBranchUrl"
|
|
370
|
+
target="_blank"
|
|
371
|
+
rel="noopener"
|
|
372
|
+
color="neutral"
|
|
373
|
+
variant="soft"
|
|
374
|
+
size="xs"
|
|
375
|
+
icon="i-lucide-git-branch"
|
|
376
|
+
trailing-icon="i-lucide-external-link"
|
|
377
|
+
>
|
|
378
|
+
{{ block!.pullRequest!.branch }}
|
|
379
|
+
</UButton>
|
|
380
|
+
<UButton
|
|
381
|
+
v-if="tasks.available"
|
|
382
|
+
color="neutral"
|
|
383
|
+
variant="soft"
|
|
384
|
+
size="xs"
|
|
385
|
+
icon="i-lucide-ticket"
|
|
386
|
+
@click="ui.openTaskImport()"
|
|
387
|
+
>
|
|
388
|
+
{{ tasks.anyConnected ? 'Import Jira issue' : 'Connect Jira' }}
|
|
389
|
+
</UButton>
|
|
390
|
+
<UButton
|
|
391
|
+
v-if="isContainer && documents.available && documents.anyConnected"
|
|
392
|
+
color="neutral"
|
|
393
|
+
variant="soft"
|
|
394
|
+
size="xs"
|
|
395
|
+
icon="i-lucide-wand-sparkles"
|
|
396
|
+
@click="spawnFromDocument"
|
|
397
|
+
>
|
|
398
|
+
Spawn from document
|
|
399
|
+
</UButton>
|
|
400
|
+
</div>
|
|
401
|
+
|
|
402
|
+
<!-- task: context documents -->
|
|
403
|
+
<TaskContextDocs v-if="isTask" :block="block" />
|
|
404
|
+
|
|
405
|
+
<!-- task: context issues (tracker) -->
|
|
406
|
+
<TaskContextIssues v-if="isTask" :block="block" />
|
|
407
|
+
|
|
408
|
+
<!-- service / module: tasks summary -->
|
|
409
|
+
<ContainerSummary v-if="isContainer" :block="block" />
|
|
410
|
+
<!-- service (frame): test infra + provisioning configuration -->
|
|
411
|
+
<ServiceTestConfig v-if="isFrame" :block="block" />
|
|
412
|
+
|
|
413
|
+
<!-- service (frame): best-practice fragments for code-aware agents -->
|
|
414
|
+
<ServiceFragments v-if="isFrame" :block="block" />
|
|
415
|
+
|
|
416
|
+
<!-- task: dependencies, structure, agent config, run settings, execution -->
|
|
417
|
+
<template v-else-if="isTask">
|
|
418
|
+
<RecurringScheduleSettings :block="block" />
|
|
419
|
+
<TaskDependencies :block="block" />
|
|
420
|
+
<TaskStructure :block="block" />
|
|
421
|
+
<TaskAgentConfig :block="block" />
|
|
422
|
+
<TaskEstimateBadge :block="block" />
|
|
423
|
+
<TaskRunSettings :block="block" />
|
|
424
|
+
<TaskExecution :block="block" />
|
|
425
|
+
</template>
|
|
426
|
+
|
|
427
|
+
<!-- actions -->
|
|
428
|
+
<div class="flex items-center gap-2">
|
|
429
|
+
<UDropdownMenu v-if="isTask" :items="runMenu">
|
|
430
|
+
<UButton
|
|
431
|
+
:color="runnable ? 'primary' : 'neutral'"
|
|
432
|
+
variant="soft"
|
|
433
|
+
size="sm"
|
|
434
|
+
:icon="runnable ? 'i-lucide-play' : 'i-lucide-lock'"
|
|
435
|
+
trailing-icon="i-lucide-chevron-down"
|
|
436
|
+
:disabled="!runnable"
|
|
437
|
+
>
|
|
438
|
+
{{ instance ? 'Re-run' : 'Run' }}
|
|
439
|
+
</UButton>
|
|
440
|
+
</UDropdownMenu>
|
|
441
|
+
<UButton
|
|
442
|
+
v-if="isTask"
|
|
443
|
+
color="neutral"
|
|
444
|
+
variant="soft"
|
|
445
|
+
size="sm"
|
|
446
|
+
icon="i-lucide-maximize-2"
|
|
447
|
+
@click="ui.focus(block.id)"
|
|
448
|
+
>
|
|
449
|
+
Focus
|
|
450
|
+
</UButton>
|
|
451
|
+
<UButton
|
|
452
|
+
color="error"
|
|
453
|
+
variant="ghost"
|
|
454
|
+
size="sm"
|
|
455
|
+
icon="i-lucide-trash-2"
|
|
456
|
+
class="ml-auto"
|
|
457
|
+
:title="deleteLabel"
|
|
458
|
+
@click="remove"
|
|
459
|
+
>
|
|
460
|
+
{{ deleteLabel }}
|
|
461
|
+
</UButton>
|
|
462
|
+
</div>
|
|
463
|
+
</div>
|
|
464
|
+
</div>
|
|
465
|
+
</template>
|