feat: board-first orchestration with Gateway Bridge, live-update, and flow-board
- GatewayBridgeController: MCP-artiger Kommando-Adapter für Agent-zu-Backend - TaskBridgeService + LiveUpdateService: SSE Live-Sync + Bridge-Kommandos - FlowBoard.vue: Board-first orchestration dashboard panel - live-sync.ts store + live.ts service: SSE-basierte Live-Updates - Nullability-Warnung in HealthController.cs gefixt - nginx.conf: SSE-Proxy + CORS für Bridge-Endpunkte - .gitignore: pnpm/corepack local caches ausgeschlossen - docs: architecture-board-first-orchestration.md hinzugefügt - README: Backend Bridge API dokumentiert
This commit is contained in:
@@ -17,6 +17,7 @@ import { Plus, X, CalendarDays, Clock3, ExternalLink, Link2, ListChecks, Save, A
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useAuthStore } from '../stores/auth'
|
||||
import { useTaskStore } from '../stores/tasks'
|
||||
import { useLiveSyncStore } from '../stores/live-sync'
|
||||
|
||||
type BoardTask = ReturnType<typeof flattenBoard>[number]
|
||||
|
||||
@@ -32,6 +33,7 @@ type TaskFormState = {
|
||||
const authStore = useAuthStore()
|
||||
const taskStore = useTaskStore()
|
||||
const router = useRouter()
|
||||
const liveSyncStore = useLiveSyncStore()
|
||||
const showCreateModal = ref(false)
|
||||
const showDetailPanel = ref(false)
|
||||
const showIrisPanel = ref(false)
|
||||
@@ -215,6 +217,8 @@ function hydrateDetailForm(task: BoardTask | null) {
|
||||
const staleCount = computed(() => taskStore.staleTasksList.length)
|
||||
const waitingForIrisCount = computed(() => taskStore.waitingForIrisTasks.length)
|
||||
const waitingForBaoCount = computed(() => taskStore.waitingForBaoTasks.length)
|
||||
const liveModeLabel = computed(() => liveSyncStore.liveIndicatorLabel)
|
||||
const liveModeClass = computed(() => `live-pill-${liveSyncStore.connectionHealth}`)
|
||||
|
||||
function expectedFromLabel(expected: string | null | undefined): string {
|
||||
if (!expected) return ''
|
||||
@@ -277,6 +281,14 @@ function hasChildTasks(taskId: string): boolean {
|
||||
return allBoardTasks.value.some(task => task.parentTaskId === taskId)
|
||||
}
|
||||
|
||||
function delegationBadge(task: BoardTask): string | null {
|
||||
if (task.childTaskCount && task.openChildTaskCount) return `${task.openChildTaskCount}/${task.childTaskCount} aktiv`
|
||||
if (task.childTaskCount) return `${task.childTaskCount} Child-Tasks`
|
||||
if (task.parentTaskId) return 'Child-Task'
|
||||
if (task.isAgentTask || task.hasVisibleDelegation) return 'delegiert'
|
||||
return null
|
||||
}
|
||||
|
||||
function assigneeLabel(assignedTo: string | null | undefined): string {
|
||||
return expectedFromLabel(assignedTo)
|
||||
}
|
||||
@@ -382,12 +394,14 @@ watch(showDetailPanel, (open) => {
|
||||
})
|
||||
|
||||
/* ── Lifecycle ────────────────────────────────────── */
|
||||
let agentOverviewInterval: ReturnType<typeof setInterval> | null = null
|
||||
|
||||
onMounted(() => {
|
||||
taskStore.startBoardPolling()
|
||||
taskStore.fetchAgentOverview()
|
||||
liveSyncStore.connect()
|
||||
window.addEventListener('keydown', onGlobalKeydown)
|
||||
// Refresh agent overview on the same interval
|
||||
setInterval(() => taskStore.fetchAgentOverview(), 30000)
|
||||
agentOverviewInterval = setInterval(() => taskStore.fetchAgentOverview(), 30000)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
@@ -396,6 +410,8 @@ onBeforeUnmount(() => {
|
||||
|
||||
onUnmounted(() => {
|
||||
taskStore.stopBoardPolling()
|
||||
liveSyncStore.disconnect()
|
||||
if (agentOverviewInterval) clearInterval(agentOverviewInterval)
|
||||
window.removeEventListener('keydown', onGlobalKeydown)
|
||||
})
|
||||
</script>
|
||||
@@ -406,6 +422,10 @@ onUnmounted(() => {
|
||||
<div>
|
||||
<h1><span class="grad-text">Aufgaben</span></h1>
|
||||
<p class="board-subtitle">Task Board — Übersicht aller Arbeitspakete</p>
|
||||
<div class="board-live-row">
|
||||
<span class="live-pill" :class="liveModeClass">{{ liveModeLabel }}</span>
|
||||
<span v-if="liveSyncStore.lastEventAt" class="live-meta">Letztes Event {{ relativeTime(liveSyncStore.lastEventAt) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="board-header-actions">
|
||||
<button
|
||||
@@ -546,6 +566,9 @@ onUnmounted(() => {
|
||||
<span v-if="task.expectedFrom" class="expected-badge" :title="'Erwartet: ' + task.expectedFrom">
|
||||
⏳ {{ task.expectedFrom }}
|
||||
</span>
|
||||
<span v-if="delegationBadge(task)" class="expected-badge expected-badge--delegation" title="Sichtbare Delegation im Board">
|
||||
↳ {{ delegationBadge(task) }}
|
||||
</span>
|
||||
<span
|
||||
v-if="task.assignedTo"
|
||||
class="assignee"
|
||||
@@ -595,6 +618,9 @@ onUnmounted(() => {
|
||||
<span v-if="task.expectedFrom" class="expected-badge" :title="'Erwartet: ' + task.expectedFrom">
|
||||
⏳ {{ task.expectedFrom }}
|
||||
</span>
|
||||
<span v-if="delegationBadge(task)" class="expected-badge expected-badge--delegation" title="Sichtbare Delegation im Board">
|
||||
↳ {{ delegationBadge(task) }}
|
||||
</span>
|
||||
<span
|
||||
v-if="task.assignedTo"
|
||||
class="assignee"
|
||||
@@ -644,6 +670,9 @@ onUnmounted(() => {
|
||||
<span v-if="task.expectedFrom" class="expected-badge" :title="'Erwartet: ' + task.expectedFrom">
|
||||
⏳ {{ task.expectedFrom }}
|
||||
</span>
|
||||
<span v-if="delegationBadge(task)" class="expected-badge expected-badge--delegation" title="Sichtbare Delegation im Board">
|
||||
↳ {{ delegationBadge(task) }}
|
||||
</span>
|
||||
<span
|
||||
v-if="task.assignedTo"
|
||||
class="assignee"
|
||||
@@ -737,6 +766,9 @@ onUnmounted(() => {
|
||||
<span v-if="task.expectedFrom" class="expected-badge" :title="'Erwartet: ' + task.expectedFrom">
|
||||
⏳ {{ task.expectedFrom }}
|
||||
</span>
|
||||
<span v-if="delegationBadge(task)" class="expected-badge expected-badge--delegation" title="Sichtbare Delegation im Board">
|
||||
↳ {{ delegationBadge(task) }}
|
||||
</span>
|
||||
<span
|
||||
v-if="task.assignedTo"
|
||||
class="assignee"
|
||||
@@ -837,6 +869,7 @@ onUnmounted(() => {
|
||||
<span v-if="selectedTask.isAgentTask" class="meta-agent-tag">🤖 Agent-Task</span>
|
||||
<span v-if="selectedTask.expectedFrom" class="meta-expected">⏳ Erwartet: {{ selectedTask.expectedFrom }}</span>
|
||||
<span v-if="selectedTask.parentTaskId" class="meta-expected">↳ Child-Task</span>
|
||||
<span v-if="delegationBadge(selectedTask)" class="meta-expected">↳ {{ delegationBadge(selectedTask) }}</span>
|
||||
<span><Clock3 :size="13" /> Aktualisiert {{ formatDate(selectedTask.updatedAt, true) }}</span>
|
||||
<span><CalendarDays :size="13" /> Erstellt {{ formatDate(selectedTask.createdAt) }}</span>
|
||||
<span v-if="selectedTask.isAgentTask"><MessageSquareText :size="13" /> Letzter Status {{ relativeTime(selectedTask.lastActivityAt ?? selectedTask.updatedAt) }}</span>
|
||||
|
||||
Reference in New Issue
Block a user