From d19920d1d0c7fd99b18e1de0e3f6822ce6ce7e35 Mon Sep 17 00:00:00 2001 From: chenchen Date: Thu, 4 Jun 2026 17:32:03 +0800 Subject: [PATCH] =?UTF-8?q?fix(web):=20remove=20old=20"=E8=BF=90=E8=A1=8C?= =?UTF-8?q?=E6=80=BB=E8=A7=88"=20task=20UI;=20Overview=20shows=20new=20tem?= =?UTF-8?q?plate=20agents?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The old /deployments (运行总览) + /tasks/$id pages rendered the new template agents with meaningless old sub-task fields (待确认/sub_agile/子任务流/智能体任务图/产物/ SK快照/合并时间线 — all empty). Removed the whole old-model UI cluster and pointed the Overview dashboard at the real new endpoint: - deleted features/agent-console, features/agent-hub, features/tasks and the routes /deployments and /tasks/$id. - cockpit (Overview) now lists deployed template agents from /api/heicode/agents (status counts + recent agents), not the old listAgentDeployments. - removed the "运行总览" sidebar item + dead /deployments links in footer / top-nav / sidebar-config. - routeTree regenerated; frontend build + tsc clean. Co-Authored-By: Claude Opus 4.8 --- heicode/web/default/dist/index.html | 2 +- .../components/layout/components/footer.tsx | 1 - .../default/src/features/agent-console/api.ts | 521 ------- .../src/features/agent-console/pages.tsx | 1199 ----------------- .../default/src/features/agent-hub/index.tsx | 34 - .../dashboard/components/cockpit/index.tsx | 103 +- .../src/features/tasks/task-card-view.tsx | 519 ------- .../default/src/hooks/use-sidebar-config.ts | 1 - .../web/default/src/hooks/use-sidebar-data.ts | 6 - .../default/src/hooks/use-top-nav-links.ts | 3 - heicode/web/default/src/routeTree.gen.ts | 43 - .../_authenticated/deployments/index.tsx | 6 - .../src/routes/_authenticated/tasks/$id.tsx | 6 - 13 files changed, 55 insertions(+), 2389 deletions(-) delete mode 100644 heicode/web/default/src/features/agent-console/api.ts delete mode 100644 heicode/web/default/src/features/agent-console/pages.tsx delete mode 100644 heicode/web/default/src/features/agent-hub/index.tsx delete mode 100644 heicode/web/default/src/features/tasks/task-card-view.tsx delete mode 100644 heicode/web/default/src/routes/_authenticated/deployments/index.tsx delete mode 100644 heicode/web/default/src/routes/_authenticated/tasks/$id.tsx diff --git a/heicode/web/default/dist/index.html b/heicode/web/default/dist/index.html index a39573a..da4ba19 100644 --- a/heicode/web/default/dist/index.html +++ b/heicode/web/default/dist/index.html @@ -20,7 +20,7 @@ - +
diff --git a/heicode/web/default/src/components/layout/components/footer.tsx b/heicode/web/default/src/components/layout/components/footer.tsx index c3dfac4..78bfd58 100644 --- a/heicode/web/default/src/components/layout/components/footer.tsx +++ b/heicode/web/default/src/components/layout/components/footer.tsx @@ -77,7 +77,6 @@ export function Footer(props: FooterProps) { { title: t('footer.columns.docs.title'), links: [ - { text: t('Deployments'), href: '/deployments' }, { text: t('API Keys'), href: '/keys' }, ], }, diff --git a/heicode/web/default/src/features/agent-console/api.ts b/heicode/web/default/src/features/agent-console/api.ts deleted file mode 100644 index d2eecb5..0000000 --- a/heicode/web/default/src/features/agent-console/api.ts +++ /dev/null @@ -1,521 +0,0 @@ -import { api } from '@/lib/api' - -export type AgentSubMode = 'agile' | 'waterfall' - -/** Sub-agent cloud/runtime binding (passed to Agent on deploy). */ -export type AgentRuntimeExecution = { - profile_id?: string - cloud_principal_refs?: string[] - network_policy_ref?: string -} - -/** SK allow/deny policy attached to the agent in the deployment plan. */ -export type AgentSKAccessPolicy = { - policy_ref?: string - deny_skill_ids?: string[] - inherit_deployment_defaults?: boolean -} - -/** Git-backed SK source (`type: git`). */ -export type AgentRepoRef = { - connection_id?: string - repo_url?: string - ref: string - paths: string[] -} - -/** Single SK source entry (git or upload). */ -export type AgentSKSource = { - type?: string - artifact_id?: string - mime?: string - repo_ref?: AgentRepoRef -} - -export type AgentAgentPlan = { - role_template: string - goal: string - default_model_id?: string - sk_sources?: AgentSKSource[] - runtime_execution?: AgentRuntimeExecution - sk_access_policy?: AgentSKAccessPolicy - resource_grants?: Array<{ - grant_id?: string - resource_id?: string - resource_type?: string - user_id?: string - binding_scope?: string - target_role?: string - target_agent_ref?: string - permission_scope?: string[] - constraints?: Record - metadata?: Record - status?: 'pending' | 'active' | 'disabled' | 'revoked' - secret_ref?: string - audit?: Record - }> -} - -export type AgentBudget = { - max_tokens: number - max_cost_usd: number - max_duration_sec: number -} - -export type AgentUserContext = { - user_id: string - email?: string - role?: string - channel_id?: string - subscription_tier?: string -} - -export type AgentBillingContext = { - provider?: 'newapi' - newapi_user_ref?: string - newapi_group?: string - quota_ref?: string -} - -export type AgentAgentRuntime = { - platform?: 'agent' - agents?: Array<{ - role: string - model_ref: string - instance_count: number - }> -} - -export type AgentConstraints = { - /** Runtime model allow-list for Agent deployments; not a NewAPI billing map. */ - allowed_model_ids?: string[] -} - -export type AgentOrchestrationMetadata = { - /** - * Compatibility field for Agent routing scope; UI treats this as user scope, - * not billing tenant. - */ - tenant_id: string - /** - * Compatibility field for Agent routing scope; UI treats this as resource - * scope, not project control. - */ - project_id: string - correlation_id: string -} - -export type AgentOrchestrationPlan = { - intent_id: string - template_hint: string - objective: string - sub_mode?: AgentSubMode - risk_level: 'low' | 'medium' | 'high' - budget: AgentBudget - user_context: AgentUserContext - billing_context?: AgentBillingContext - agent_runtime?: AgentAgentRuntime - agents: AgentAgentPlan[] - constraints: AgentConstraints - metadata: AgentOrchestrationMetadata -} - -export type AgentCreateDeploymentBody = { - orchestration_plan: AgentOrchestrationPlan -} - -export type AgentCreateDeploymentResult = { - deployment_id: string - sub_mode?: AgentSubMode - status: string - agent_instances?: Array<{ - instance_id?: string - role?: string - phase?: string - }> - permission_manifest?: AgentPermissionManifest -} - -export type AgentPermissionManifest = { - user_id?: string - binding_scope?: string - agent_role?: string - target_agent_ref?: string - resource_grants?: Array<{ - grant_id?: string - resource_id?: string - resource_type?: string - resource_ref?: string - allowed_actions?: string[] - constraints?: Record - secret_ref?: string - status?: string - }> -} - -export type AgentDeployment = { - deployment_id: string - // Client-facing mode (sub_agile | swarm) + Manager-judged display_status, - // both populated by the backend list/detail responses. - mode?: string - display_status?: string - sub_mode?: AgentSubMode - status: string - phase: string - runtime_state?: string - runtime_deployment_id?: string - runtime_swarm_id?: string - runtime_last_sync_at?: string - failure_reason?: string - created_at: string - updated_at: string - permission_manifest?: AgentPermissionManifest - orchestration_plan: { - intent_id?: string - template_hint?: string - objective?: string - sub_mode?: AgentSubMode - risk_level?: string - budget?: AgentBudget - agents?: AgentAgentPlan[] - constraints?: AgentConstraints - metadata?: AgentOrchestrationMetadata - } -} - -export type AgentRuntimeDiagnostics = { - deployment_id: string - runtime_mode?: 'agent' | 'swarm' | string - sub_mode?: AgentSubMode - runtime_deployment_id?: string - runtime_swarm_id?: string - data_source?: string - http_status?: number - status?: string - phase?: string - progress?: unknown - error_message?: string - agents?: Array> - artifacts?: Array> - metrics?: Record - warnings?: string[] - checked_at?: string -} - -export type AgentApprovalRequest = { - approval_id: string - user_id: number - deployment_id?: string - binding_scope?: string - operation: string - resource_id: string - resource_type: string - resource_scope?: string - target_role: string - risk_level: 'low' | 'medium' | 'high' | 'critical' - requires_credential: boolean - credential_lease_id?: string - status: 'pending' | 'approved' | 'rejected' | 'expired' - requested_by?: string - decided_by?: string - request_reason?: string - decision_reason?: string - ttl_seconds: number - expires_at: number - decided_at?: number - created_at?: number - updated_at?: number - credential_lease?: AgentCredentialLease -} - -export type AgentCredentialLease = { - lease_id: string - credential_ref: string - approval_id: string - user_id: number - deployment_id?: string - binding_scope?: string - resource_id: string - resource_type: string - resource_scope?: string - target_role: string - status: 'active' | 'expired' | 'revoked' - ttl_seconds: number - expires_at: number - revoked_at?: number - created_at?: number - updated_at?: number -} - -type ApiEnvelope = { success: boolean; data?: T; message?: string } - -export type GitSourceUsage = 'project' | 'sk' | 'combined' - -export type GitSource = { - id: number - user_id: number - tenant_id?: string - name: string - provider: string - repo_url: string - ref: string - paths: string[] - usage: GitSourceUsage | string - status: string - created_at?: number - updated_at?: number -} - -export type GitSourcePayload = { - tenant_id?: string - name: string - provider: string - repo_url: string - ref: string - paths: string[] - usage: GitSourceUsage -} - -// Platform-recommended role catalog shape. Mirrors backend -// `AgentRoleTemplate` in controller/agent_role_template.go. The -// six canonical roles come from docs/product-package §13.3.3 — -// keys are stable identifiers, display strings can be translated. -export type AgentRoleTemplate = { - key: string - display_name: string - summary: string - default_model: string - default_permissions: string[] - risk_level: 'low' | 'medium' | 'high' -} - -// Cached at module level — the canonical six-role catalog doesn't -// change between page loads, so we avoid an extra request every -// time the create-deployment sheet opens. -let _roleTemplateCache: AgentRoleTemplate[] | null = null - -export async function listAgentRoleTemplates(): Promise { - if (_roleTemplateCache) return _roleTemplateCache - const res = await api.get>( - '/api/agent/role-templates' - ) - const items = res.data?.data?.items ?? [] - if (items.length > 0) { - _roleTemplateCache = items - } - return items -} - -export async function listAgentDeployments(): Promise { - const res = await api.get>( - '/api/agent/user/deployments' - ) - return res.data?.data?.items ?? [] -} - -export async function listAgentDeploymentsQuiet(): Promise { - const res = await api.get>( - '/api/agent/user/deployments', - { - skipBusinessError: true, - skipErrorHandler: true, - } as Record - ) - if (!res.data?.success) return [] - return res.data?.data?.items ?? [] -} - -export async function createAgentDeployment( - body: AgentCreateDeploymentBody -): Promise { - const res = await api.post>( - '/api/agent/user/deployments', - body - ) - const env = res.data - if (!env?.success) { - throw new Error(env?.message || 'Deployment request failed') - } - const data = env.data - if (!data?.deployment_id) { - throw new Error(env?.message || 'Invalid deployment response') - } - return data -} - -export async function getAgentDeploymentEvents(deploymentId: string) { - const res = await api.get< - ApiEnvelope<{ items?: Array> }> - >(`/api/agent/user/deployments/${deploymentId}/events`) - return res.data?.data?.items ?? [] -} - -export async function getAgentDeploymentArtifacts(deploymentId: string) { - const res = await api.get< - ApiEnvelope<{ items?: Array> }> - >(`/api/agent/user/deployments/${deploymentId}/artifacts`) - return res.data?.data?.items ?? [] -} - -export async function getAgentDeploymentSKSnapshots(deploymentId: string) { - const res = await api.get< - ApiEnvelope<{ items?: Array> }> - >(`/api/agent/user/deployments/${deploymentId}/sk-snapshots`) - return res.data?.data?.items ?? [] -} - -export async function getAgentDeploymentTimeline(deploymentId: string) { - const res = await api.get< - ApiEnvelope<{ - callbacks?: Array> - artifacts?: Array> - sk_snapshots?: Array> - timeline?: Array> - }> - >(`/api/agent/user/deployments/${deploymentId}/timeline`) - return ( - res.data?.data ?? { - callbacks: [], - artifacts: [], - sk_snapshots: [], - timeline: [], - } - ) -} - -export async function getAgentRuntimeDiagnostics( - deploymentId: string -): Promise { - const res = await api.get>( - `/api/agent/user/deployments/${deploymentId}/runtime-diagnostics`, - { - skipBusinessError: true, - skipErrorHandler: true, - } as Record - ) - if (!res.data?.success) return null - return res.data?.data ?? null -} - -export async function simulateAgentDeploymentEvents( - deploymentId: string, - events?: string[] -): Promise<{ deployment_id: string; simulated: boolean; total: number }> { - const res = await api.post< - ApiEnvelope<{ deployment_id: string; simulated: boolean; total: number }> - >(`/api/agent/user/deployments/${deploymentId}/simulate-events`, { - events: events ?? [], - }) - const env = res.data - if (!env?.success || !env.data) { - throw new Error(env?.message || 'simulateAgentDeploymentEvents failed') - } - return env.data -} - -export async function getAgentAuditLogs() { - const res = await api.get< - ApiEnvelope<{ items?: Array> }> - >('/api/agent/audit-logs') - return res.data?.data?.items ?? [] -} - -export async function listAgentApprovals(params?: { - status?: string - deployment_id?: string -}): Promise { - const res = await api.get>( - '/api/agent/approvals', - { params } - ) - return res.data?.data?.items ?? [] -} - -export async function approveAgentApproval( - approvalId: string, - reason?: string -): Promise { - const res = await api.post>( - `/api/agent/approvals/${approvalId}/approve`, - { reason } - ) - if (!res.data?.success || !res.data.data) { - throw new Error(res.data?.message || 'Approve request failed') - } - return res.data.data -} - -export async function rejectAgentApproval( - approvalId: string, - reason?: string -): Promise { - const res = await api.post>( - `/api/agent/approvals/${approvalId}/reject`, - { reason } - ) - if (!res.data?.success || !res.data.data) { - throw new Error(res.data?.message || 'Reject request failed') - } - return res.data.data -} - -export async function listAgentCredentialLeases(params?: { - status?: string - deployment_id?: string -}): Promise { - const res = await api.get>( - '/api/agent/credential-leases', - { params } - ) - return res.data?.data?.items ?? [] -} - -export async function revokeAgentCredentialLease( - leaseId: string, - reason?: string -): Promise { - const res = await api.post>( - `/api/agent/credential-leases/${leaseId}/revoke`, - { reason } - ) - if (!res.data?.success || !res.data.data) { - throw new Error(res.data?.message || 'Revoke lease failed') - } - return res.data.data -} - -export async function getAgentSnapshots(deploymentId: string) { - const res = await api.get< - ApiEnvelope<{ items?: Array> }> - >(`/api/agent/deployments/${deploymentId}/sk-snapshots`, { - skipBusinessError: true, - skipErrorHandler: true, - } as Record) - if (!res.data?.success) return [] - return res.data?.data?.items ?? [] -} - -export async function listGitSources(): Promise { - const res = - await api.get>('/api/git-sources/') - return res.data?.data?.items ?? [] -} - -export async function createGitSource( - body: GitSourcePayload -): Promise { - const res = await api.post>('/api/git-sources/', body) - if (!res.data?.success || !res.data.data) { - throw new Error(res.data?.message || 'Create Git source failed') - } - return res.data.data -} - -export async function deleteGitSource(id: number): Promise { - const res = await api.delete>( - `/api/git-sources/${id}` - ) - if (!res.data?.success) { - throw new Error(res.data?.message || 'Delete Git source failed') - } -} diff --git a/heicode/web/default/src/features/agent-console/pages.tsx b/heicode/web/default/src/features/agent-console/pages.tsx deleted file mode 100644 index d2f1590..0000000 --- a/heicode/web/default/src/features/agent-console/pages.tsx +++ /dev/null @@ -1,1199 +0,0 @@ -import { useMemo, useState, type ComponentType, type ReactNode } from 'react' -import { useQuery } from '@tanstack/react-query' -import { - Activity, - AlertOctagon, - ArrowUpRight, - CheckCircle2, - CircleDashed, - Cpu, - FileSearch, - Filter, - GitCommit, - PlayCircle, - Rocket, - Search, - ShieldCheck, - XCircle, -} from 'lucide-react' -import { useTranslation } from 'react-i18next' -// /audit pulls from mcp-server §5.10 stub now, not the Heicode-local -// controller — the contract doc names that endpoint as the canonical -// source. The shape of McpAuditEntry is wider than the legacy local one -// so the redacted-card renderer keeps working. -// /sk-sources resource list/create/revoke moved to mcp-server §2 P1 -// ResourceBinding (commit ?). -import { cn } from '@/lib/utils' -import { Input } from '@/components/ui/input' -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from '@/components/ui/select' -import { - Sheet, - SheetContent, - SheetDescription, - SheetHeader, - SheetTitle, -} from '@/components/ui/sheet' -import { Skeleton } from '@/components/ui/skeleton' -import { QueryState } from '@/components/query-state' -import { - getAgentDeploymentEvents, - getAgentRuntimeDiagnostics, - getAgentDeploymentTimeline, - listAgentDeployments, - type AgentDeployment, - type AgentRuntimeDiagnostics, -} from './api' - -type StatusKey = 'running' | 'success' | 'failed' | 'pending' - -const STATUS_TO_KEY: Record = { - running: 'running', - active: 'running', - in_progress: 'running', - stopped: 'success', - simulated: 'success', - succeeded: 'success', - success: 'success', - completed: 'success', - failed: 'failed', - error: 'failed', - rejected: 'failed', - // Manager display_status verdicts: a finished-but-no-deliverable result is - // not a green success; needs_codegen is still in-progress work. - completed_without_deliverable: 'failed', - needs_codegen: 'pending', - waiting_approval: 'pending', - pending: 'pending', - queued: 'pending', - awaiting: 'pending', -} - -function classifyStatus(status: string): StatusKey { - return STATUS_TO_KEY[(status || '').toLowerCase()] ?? 'pending' -} - -function formatStatusLabel( - status: string | undefined, - t: (key: string) => string -): string { - const value = (status || '').trim() - if (!value) return t('Unknown') - const normalized = value.toLowerCase() - if (normalized.startsWith('handoff:')) { - const tail = normalized.replace('handoff:', '') - return `${t('Handoff')}: ${formatStatusLabel(tail, t)}` - } - const labels: Record = { - running: t('Running'), - active: t('Running'), - in_progress: t('In progress'), - stopped: t('Stopped'), - simulated: t('Simulated'), - succeeded: t('Success'), - success: t('Success'), - completed: t('Completed'), - failed: t('Failed'), - error: t('Failed'), - rejected: t('Rejected'), - pending: t('Pending'), - queued: t('Queued'), - awaiting: t('Awaiting'), - completed_without_deliverable: t('Completed without deliverable'), - needs_codegen: t('Needs codegen'), - waiting_approval: t('Waiting approval'), - observed: t('Observed'), - blocked: t('Blocked'), - requested: t('Requested'), - high: t('High'), - medium: t('Medium'), - mid: t('Medium'), - low: t('Low'), - } - return labels[normalized] || value -} - -function StatusBadge({ phase }: { phase: string }) { - const { t } = useTranslation() - const k = classifyStatus(phase) - const map: Record = { - running: { - cls: 'bg-[color-mix(in_oklch,var(--primary)_22%,transparent)] text-primary ring-primary/40', - icon: , - }, - success: { - cls: 'bg-emerald-500/15 text-emerald-400 ring-emerald-500/30', - icon: , - }, - failed: { - cls: 'bg-rose-500/15 text-rose-400 ring-rose-500/30', - icon: , - }, - pending: { - cls: 'bg-amber-500/15 text-amber-400 ring-amber-500/30', - icon: , - }, - } - const m = map[k] - return ( - - {m.icon} - {formatStatusLabel(phase, t)} - - ) -} - -function PageSurface(props: { - title: string - subtitle?: string - toolbar?: ReactNode - children: ReactNode -}) { - return ( -
-
-
-

- Heicode Manager -

-

{props.title}

- {props.subtitle && ( -

- {props.subtitle} -

- )} -
- {props.toolbar && ( -
- {props.toolbar} -
- )} -
- {props.children} -
- ) -} - - -function LoadingGrid({ - rows = 4, - height = 'h-24', -}: { - rows?: number - height?: string -}) { - return ( -
- {Array.from({ length: rows }).map((_, idx) => ( - - ))} -
- ) -} - -function MetaPill({ - icon: Icon, - label, - value, -}: { - icon: ComponentType<{ className?: string }> - label: string - value: string -}) { - return ( - - - {label} - - {value} - - - ) -} - -function describeSubMode(dep: AgentDeployment): string { - return dep.sub_mode || dep.orchestration_plan?.sub_mode || 'agile' -} - -function formatSubModeLabel(mode: string, t: (key: string) => string): string { - const value = mode.toLowerCase() - if (value === 'agile') return t('Agile') - if (value === 'waterfall') return t('Waterfall') - return mode -} - -function compactRuntimeRef(value: string | undefined): string { - const trimmed = (value || '').trim() - if (!trimmed) return '—' - if (trimmed.length <= 18) return trimmed - return `${trimmed.slice(0, 10)}…${trimmed.slice(-6)}` -} - -function formatRecordSource(value: unknown): string { - const source = String(value || '').trim() - const normalized = source.toLowerCase() - if (!source || normalized.includes('manager')) return '管理端' - if (normalized.includes('simulate')) return '模拟' - if (normalized.includes('runtime') || normalized.includes('swarm')) - return '运行时' - return source -} - -function artifactTypeToneClass(value: unknown): string { - const artifactType = String(value || '').toLowerCase() - if (artifactType.includes('patch') || artifactType.includes('code')) { - return 'bg-sky-500/15 text-sky-300 ring-sky-500/30' - } - if (artifactType.includes('test') || artifactType.includes('report')) { - return 'bg-emerald-500/15 text-emerald-300 ring-emerald-500/30' - } - if (artifactType.includes('deploy')) { - return 'bg-amber-500/15 text-amber-300 ring-amber-500/30' - } - return 'bg-muted/40 text-muted-foreground ring-border/60' -} - -function isFallbackRuntimeArtifact(item: Record): boolean { - const title = String(item.title || '').toLowerCase() - const summary = String(item.summary || '').toLowerCase() - const uri = String(item.uri || '').toLowerCase() - return ( - title.includes('runtime execution summary') || - summary.includes('without per-agent artifacts') || - uri.includes('/artifacts/summary') - ) -} - -function isTaskFlowEvent(eventType: unknown): boolean { - const event = String(eventType || '').toLowerCase() - return event.startsWith('task.') || event.startsWith('handoff.') -} - -function recordPayloadValue( - item: Record, - key: string -): unknown { - const payload = item.payload - if (payload && typeof payload === 'object' && !Array.isArray(payload)) { - const fromPayload = (payload as Record)[key] - if (fromPayload !== undefined && fromPayload !== null) { - return fromPayload - } - } - return item[key] -} - -function taskFlowDetail(item: Record): string { - const taskId = String(recordPayloadValue(item, 'task_id') || '') - const fromRole = String(recordPayloadValue(item, 'from_role') || '') - const toRole = String(recordPayloadValue(item, 'to_role') || '') - const reason = String(recordPayloadValue(item, 'reason') || '') - const attempt = String(recordPayloadValue(item, 'attempt') || '') - const agentRole = String(recordPayloadValue(item, 'agent_role') || '') - const parts = [ - taskId && `任务 ${taskId}`, - agentRole && `角色 ${agentRole}`, - fromRole && toRole && `${fromRole} -> ${toRole}`, - reason && `原因 ${reason}`, - attempt && `第 ${attempt} 次`, - ].filter(Boolean) - return parts.join(' / ') || '—' -} - -type TaskFlowSummary = { - taskId: string - status: string - agentRole: string - handoff: string - source: string -} - -function buildTaskFlowSummaries( - records: Record[] -): TaskFlowSummary[] { - const byTask = new Map() - records.forEach((item, idx) => { - const event = String(item.event_type || item.event || '').toLowerCase() - const taskId = - String(recordPayloadValue(item, 'task_id') || '').trim() || - `task-${idx + 1}` - const current = - byTask.get(taskId) ?? - ({ - taskId, - status: 'observed', - agentRole: '', - handoff: '', - source: formatRecordSource(item.source), - } satisfies TaskFlowSummary) - const agentRole = String(recordPayloadValue(item, 'agent_role') || '') - const fromRole = String(recordPayloadValue(item, 'from_role') || '') - const toRole = String(recordPayloadValue(item, 'to_role') || '') - const reason = String(recordPayloadValue(item, 'reason') || '') - if (agentRole) current.agentRole = agentRole - if (event.startsWith('task.')) { - current.status = event.replace('task.', '') || current.status - } - if (event === 'handoff.requested' || event === 'handoff.completed') { - current.status = event.replace('handoff.', 'handoff:') - if (fromRole || toRole) - current.handoff = `${fromRole || '?'} -> ${toRole || '?'}` - } - if (reason && current.status === 'blocked') { - current.handoff = reason - } - current.source = formatRecordSource(item.source) - byTask.set(taskId, current) - }) - return Array.from(byTask.values()) -} - - -function formatRelativeTime(value: string | undefined): string { - if (!value) return '—' - const date = new Date(value) - if (Number.isNaN(date.getTime())) return value - const diff = Date.now() - date.getTime() - const sec = Math.round(diff / 1000) - if (sec < 60) return `${sec} 秒前` - const min = Math.round(sec / 60) - if (min < 60) return `${min} 分钟前` - const hr = Math.round(min / 60) - if (hr < 24) return `${hr} 小时前` - const day = Math.round(hr / 24) - return `${day} 天前` -} - - -function runtimeWarningLabel( - value: string, - t: (key: string) => string -): string { - switch (value) { - case 'runtime_agent_failed': - return t('Runtime agent failed') - case 'runtime_completed_with_failed_agents': - return t('Runtime completed with failed agents') - case 'runtime_summary_artifact_only': - return t('Only fallback summary artifact returned') - case 'runtime_zero_model_usage': - return t('Runtime usage metrics not returned yet') - case 'runtime_status_query_failed': - return t('Runtime status query failed') - case 'runtime_not_configured': - return t('Runtime not configured') - case 'runtime_identifiers_missing': - return t('Runtime identifiers missing') - default: - return value - } -} - -function runtimeModeLabel( - diagnostics: AgentRuntimeDiagnostics | null | undefined, - t: (key: string) => string -): string { - const mode = String(diagnostics?.runtime_mode || '').toLowerCase() - if (mode === 'swarm') return t('Swarm mode') - if (mode === 'agent') return t('Ordinary sub mode') - return mode || '—' -} - -function runtimeAgentRows( - diagnostics: AgentRuntimeDiagnostics | null | undefined -) { - return (diagnostics?.agents ?? []).slice(0, 4).map((agent, idx) => ({ - id: String(agent.agent_id || agent.instance_id || idx), - role: String(agent.role || '—'), - status: String(agent.status || agent.runtime_state || '—'), - output: String(agent.output || agent.failure_reason || ''), - })) -} - -function RuntimeDiagnosticsPanel({ - diagnostics, - isLoading, -}: { - diagnostics?: AgentRuntimeDiagnostics | null - isLoading: boolean -}) { - const { t } = useTranslation() - const warnings = diagnostics?.warnings ?? [] - const agents = runtimeAgentRows(diagnostics) - const hasWarning = warnings.length > 0 - return ( -
-
-
-

- - {t('Runtime diagnostics')} -

-

- {t( - 'Manager checks Runtime status separately from callback data, without mixing ordinary sub and swarm modes.' - )} -

-
- {isLoading ? ( - - ) : null} -
- -
- - - -
- - {hasWarning && ( -
- {warnings.map((warning) => ( - - {runtimeWarningLabel(warning, t)} - - ))} -
- )} - - {agents.length > 0 && ( -
- {agents.map((agent) => ( -
-
- {agent.role} - -
- {agent.output && ( -

- {agent.output} -

- )} -
- ))} -
- )} -
- ) -} - -function RunDetailPanel({ dep }: { dep: AgentDeployment }) { - const { t } = useTranslation() - const phase = dep.display_status || dep.status || dep.phase - const runtimeDiagnosticsQuery = useQuery({ - queryKey: ['agent', 'runtime-diagnostics', dep.deployment_id], - queryFn: () => getAgentRuntimeDiagnostics(dep.deployment_id), - enabled: Boolean(dep.deployment_id), - refetchInterval: 30_000, - }) - const runtimeDiagnostics = runtimeDiagnosticsQuery.data - - return ( -
-
-
-

- {t('Run detail')} -

-

- {dep.deployment_id} -

-

- {dep.orchestration_plan?.objective || t('No objective')} -

-
- -
- -
- - - - - -
- - -
- ) -} - -// ============================================================================= -// Deployments page -// ============================================================================= - -export function AgentDeploymentsPage() { - const { t } = useTranslation() - const [filter, setFilter] = useState<'all' | StatusKey>('all') - const [modeFilter, setModeFilter] = useState<'all' | 'sub_agile' | 'swarm'>( - 'all' - ) - const [keyword, setKeyword] = useState('') - const [selectedRunId, setSelectedRunId] = useState() - - const { - data = [], - isLoading, - error: deploymentsError, - refetch: refetchDeployments, - } = useQuery({ - queryKey: ['agent', 'deployments'], - queryFn: listAgentDeployments, - refetchInterval: 30_000, - retry: false, // QueryState handles error display; no silent retries - }) - - const filtered = useMemo(() => { - return data.filter((dep) => { - const status = classifyStatus(dep.display_status || dep.status || '') - if (filter !== 'all' && status !== filter) return false - if (modeFilter !== 'all' && (dep.mode || 'sub_agile') !== modeFilter) - return false - if (keyword.trim()) { - const k = keyword.toLowerCase() - const blob = - `${dep.deployment_id} ${dep.orchestration_plan?.objective || ''} ${ - dep.mode || '' - }`.toLowerCase() - if (!blob.includes(k)) return false - } - return true - }) - }, [data, filter, modeFilter, keyword]) - - const selectedRun = - filtered.find((dep) => dep.deployment_id === selectedRunId) ?? filtered[0] - - return ( - <> - -
- - setKeyword(e.target.value)} - placeholder={t('Find run / scope / objective')} - className='h-9 w-64 rounded-xl pl-8 text-xs' - /> -
- - - - } - > - void refetchDeployments()} - loadingFallback={} - emptyTitle={t('No runs match the current filter')} - emptyDescription={t( - 'Adjust filters or trigger a new orchestration plan.' - )} - > -
- {filtered.map((dep) => { - const phase = dep.display_status || dep.status || dep.phase - const objective = - dep.orchestration_plan?.objective || - dep.orchestration_plan?.template_hint || - t('No objective') - const selected = dep.deployment_id === selectedRun?.deployment_id - return ( -
setSelectedRunId(dep.deployment_id)} - onKeyDown={(event) => { - if (event.key === 'Enter' || event.key === ' ') { - event.preventDefault() - setSelectedRunId(dep.deployment_id) - } - }} - className={cn( - 'group hover:border-primary/45 focus-visible:ring-ring flex cursor-pointer flex-col gap-3 rounded-2xl border bg-[color-mix(in_oklch,var(--card)_55%,transparent)] p-4 transition focus-visible:ring-2 focus-visible:outline-none', - selected - ? 'border-primary/55' - : 'border-[color-mix(in_oklch,var(--primary)_18%,var(--border))]' - )} - > -
-

- {objective} -

-
- - - {dep.mode === 'swarm' ? 'Swarm' : 'Sub Agile'} - -
-
- -
- - {formatRelativeTime(dep.updated_at || dep.created_at)} - - - {t('Open for details')} - -
-
- ) - })} -
-
-
- {/* M9 — task detail drawer. Click a deployment card → audit - timeline + permission manifest fold render here. selectedRun - is derived from selectedRunId; the drawer mirrors that - source-of-truth and closes by clearing the id. */} - { - if (!o) setSelectedRunId(undefined) - }} - > - - {selectedRun && ( - <> - - - {selectedRun.orchestration_plan?.objective || - selectedRun.orchestration_plan?.template_hint || - selectedRun.deployment_id} - - - {selectedRun.deployment_id} - - -
- - - -
- - )} -
-
- - ) -} - -// RunAuditTimeline — M9. Renders the full audit-event stream for a -// single deployment, oldest-first (chronological). Data is the -// persistent audit table from Sprint 1; an empty list means either -// the deployment is too fresh to have generated audit rows yet, or -// the server lost connectivity to PostgreSQL (we log + swallow). -// -// Visual: vertical timeline with a coloured dot per event, the event -// name on top, then occurred_at + correlation_id below in muted text. -// "Error" and "rejected" events get a red dot; everything else uses -// the primary tone. Keeps the drawer skim-friendly under heavy -// timelines (60+ events) by setting max-height + overflow. -function RunAuditTimeline({ deploymentId }: { deploymentId: string }) { - const { t } = useTranslation() - const { data = [], isLoading } = useQuery({ - queryKey: ['agent', 'deployment-events', deploymentId], - queryFn: () => getAgentDeploymentEvents(deploymentId), - enabled: Boolean(deploymentId), - refetchInterval: 15_000, - }) - - return ( -
-
-
-

- {t('Audit timeline')} -

-

- {t( - 'Every observable transition for this deployment. Survives container restarts (stored in DB).' - )} -

-
- - {data.length} {t('events')} - -
- - {isLoading ? ( -
- - - -
- ) : data.length === 0 ? ( -

- {t('No audit events yet for this deployment.')} -

- ) : ( -
    - {data.map((entry, idx) => { - const eventName = String(entry.event || entry.action || '(unknown)') - const occurred = String(entry.occurred_at || '') - const correlation = String(entry.correlation_id || '') - const level = classifyEventLevel(entry) - const dotClass = - level === 'error' - ? 'bg-rose-500 ring-rose-500/30' - : level === 'warn' - ? 'bg-amber-500 ring-amber-500/30' - : 'bg-primary ring-primary/30' - return ( -
  1. - -
    -

    - {eventName} -

    -

    - {occurred || '—'} - {correlation && ( - <> - · - {correlation} - - )} -

    -
    -
  2. - ) - })} -
- )} -
- ) -} - -function RunRelatedRecordsPanel({ deploymentId }: { deploymentId: string }) { - const { t } = useTranslation() - const { data, isLoading } = useQuery({ - queryKey: ['agent', 'deployment-timeline', deploymentId], - queryFn: () => getAgentDeploymentTimeline(deploymentId), - enabled: Boolean(deploymentId), - refetchInterval: 15_000, - }) - const callbacks = data?.callbacks ?? [] - const artifacts = data?.artifacts ?? [] - const snapshots = data?.sk_snapshots ?? [] - const timeline = data?.timeline ?? [] - const taskFlowRecords = useMemo( - () => - timeline.filter((item) => isTaskFlowEvent(item.event_type || item.event)), - [timeline] - ) - const taskFlowSummaries = useMemo( - () => buildTaskFlowSummaries(taskFlowRecords), - [taskFlowRecords] - ) - - return ( -
-
-
-

- {t('Related records')} -

-

- {t('Callbacks, artifacts, SK snapshots and merged timeline')} -

-
- {isLoading ? ( - - ) : null} -
- -
- - - - -
- -
-
-

- {t('Sub task flow')} -

- - {taskFlowRecords.length} - -
- {taskFlowRecords.length === 0 ? ( -

- {t('No task, retry or handoff callback records yet')} -

- ) : ( -
    - {taskFlowRecords.slice(0, 8).map((item, idx) => { - const event = String(item.event_type || item.event || t('Event')) - const source = formatRecordSource(item.source) - const detail = taskFlowDetail(item) - return ( -
  1. -
    -

    - {event} -

    - - {source} - -
    -

    - {detail} -

    -
  2. - ) - })} -
- )} -
- -
-
-

- {t('Agent task map')} -

- - {taskFlowSummaries.length} - -
- {taskFlowSummaries.length === 0 ? ( -

- {t( - 'Waiting for Runtime task graph callbacks. Manager will show task, Agent role, handoff and source here when callbacks arrive.' - )} -

- ) : ( -
- - - - - - - - - - - - {taskFlowSummaries.map((item) => ( - - - - - - - - ))} - -
{t('Task')}{t('Status')}{t('Agent role')}{t('Handoff')}{t('Source')}
- {item.taskId} - - - - {item.agentRole || '—'} - - {item.handoff || '—'} - - - {item.source} - -
-
- )} -
- -
-
-

- {t('Artifacts')} -

- {artifacts.length === 0 ? ( -

- {t('No artifacts yet')} -

- ) : ( -
    - {artifacts.slice(0, 5).map((item, idx) => ( -
  • -
    -

    - {String(item.title || item.artifact_id || t('Artifact'))} -

    - - {String(item.artifact_type || t('Artifact'))} - -
    -

    - {String(item.summary || item.uri || '—')} -

    - {isFallbackRuntimeArtifact(item) && ( -

    - {t( - 'Fallback summary only; not a final business deliverable.' - )} -

    - )} - {Boolean(item.uri) && ( -

    - {String(item.uri)} -

    - )} - {Boolean(item.artifact_id) && ( - - - {t('Download artifact')} - - )} -
  • - ))} -
- )} -
-
-

- {t('SK snapshots')} -

- {snapshots.length === 0 ? ( -

- {t('No SK snapshots yet')} -

- ) : ( -
    - {snapshots.slice(0, 5).map((item, idx) => ( -
  • -

    - {String(item.snapshot_id || t('Snapshot'))} -

    -

    - {String(item.source_type || t('Source'))} ·{' '} - {String(item.source_ref || '—')} -

    -
  • - ))} -
- )} -
-
- -
-

- {t('Merged timeline')} -

- {timeline.length === 0 ? ( -

- {t('No timeline records yet')} -

- ) : ( -
    - {timeline.slice(0, 8).map((item, idx) => { - const event = String(item.event_type || item.event || t('Event')) - const source = formatRecordSource(item.source) - return ( -
  1. -
    -

    {event}

    - - {source} - -
    - {Boolean(item.title || item.summary || item.checkpoint) && ( -

    - {String(item.title || item.summary || item.checkpoint)} -

    - )} -
  2. - ) - })} -
- )} -
-
- ) -} - -// ============================================================================= -// Events page -// ============================================================================= - -const EVENT_LEVELS = ['all', 'info', 'warn', 'error'] as const - -type EventLevel = (typeof EVENT_LEVELS)[number] - -function classifyEventLevel(entry: Record): EventLevel { - const candidate = String( - entry.level || entry.severity || entry.status || '' - ).toLowerCase() - if ( - candidate.includes('error') || - candidate.includes('fail') || - candidate.includes('rejected') - ) - return 'error' - if (candidate.includes('warn')) return 'warn' - if (!candidate) return 'info' - return 'info' -} - diff --git a/heicode/web/default/src/features/agent-hub/index.tsx b/heicode/web/default/src/features/agent-hub/index.tsx deleted file mode 100644 index fd8195b..0000000 --- a/heicode/web/default/src/features/agent-hub/index.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { Link } from '@tanstack/react-router' - -type AgentHubProps = { - title: string - description: string -} - -const quickLinks = [{ title: 'Deployments', to: '/deployments' as const }] - -export function AgentHub(props: AgentHubProps) { - return ( -
-
-

{props.title}

-

{props.description}

-
- -
- {quickLinks.map((item) => ( - -
{item.title}
-
- Open {item.title.toLowerCase()} workspace -
- - ))} -
-
- ) -} diff --git a/heicode/web/default/src/features/dashboard/components/cockpit/index.tsx b/heicode/web/default/src/features/dashboard/components/cockpit/index.tsx index 67b0584..a3401e5 100644 --- a/heicode/web/default/src/features/dashboard/components/cockpit/index.tsx +++ b/heicode/web/default/src/features/dashboard/components/cockpit/index.tsx @@ -15,13 +15,26 @@ import { import { api } from '@/lib/api' import { cn } from '@/lib/utils' import { Button } from '@/components/ui/button' -import { - listAgentDeployments, - type AgentDeployment, -} from '@/features/agent-console/api' import { WalletStatsCard } from '@/features/wallet/components/wallet-stats-card' import type { UserWalletData } from '@/features/wallet/types' +// Deployed template agents (new model). +type AgentItem = { + agent_id: string + template_id: string + subdomain: string + status: string + created_at: string + updated_at: string +} + +async function listMyAgents(): Promise { + const res = await api.get<{ data?: { items?: AgentItem[] } }>( + '/api/heicode/agents' + ) + return res.data?.data?.items ?? [] +} + // Current user's OWN balance/usage (NOT the admin-global aggregate) — same // source the 模型与余额 page uses. async function getSelfUsage(): Promise { @@ -149,9 +162,10 @@ export function CockpitView() { const { t } = useTranslation() const deploymentsQuery = useQuery({ - queryKey: ['cockpit', 'deployments'], - queryFn: listAgentDeployments, + queryKey: ['cockpit', 'agents'], + queryFn: listMyAgents, refetchInterval: 30_000, + retry: false, }) const selfQuery = useQuery({ @@ -162,7 +176,7 @@ export function CockpitView() { }) const stats = useMemo(() => { - const list: AgentDeployment[] = deploymentsQuery.data ?? [] + const list: AgentItem[] = deploymentsQuery.data ?? [] const counters: Record = { running: 0, success: 0, @@ -170,7 +184,7 @@ export function CockpitView() { pending: 0, } list.forEach((d) => { - counters[classifyStatus(d.display_status || d.status || '')]++ + counters[classifyStatus(d.status || '')]++ }) const total = list.length || 1 const successRate = Math.round( @@ -254,7 +268,7 @@ export function CockpitView() { - - ) - })} +

+ + {dep.subdomain || '—'} ·{' '} + {formatRelative(dep.updated_at || dep.created_at)} ago +

+ + + + ))} )} diff --git a/heicode/web/default/src/features/tasks/task-card-view.tsx b/heicode/web/default/src/features/tasks/task-card-view.tsx deleted file mode 100644 index e52f625..0000000 --- a/heicode/web/default/src/features/tasks/task-card-view.tsx +++ /dev/null @@ -1,519 +0,0 @@ -/** - * Task Card route — implements docs/product-package/10 §"任务卡" + 11 §3 - * 任务卡 wireframe, backed by mcp-server §6 HeicodeTask object: - * - * - status = "configuring" → render the open follow-up questions - * (each option becomes a clickable chip; clicking POSTs answer to - * /api/user/tasks/$id/answer and the backend state machine advances). - * - status = "running" / etc → render task.card with the four blocks - * the spec mandates: 目标 / 第一版范围 / 自动生成 / 待确认上下文. - * - * Forbidden per §10 高级展开: no JSON editor, no permission manifest, no - * resource_grant editor. Only the user-facing summary fields. - */ -import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' -import { Link, getRouteApi } from '@tanstack/react-router' -import { - ArrowLeft, - CheckCircle2, - CircleDashed, - GitBranch, - ListChecks, - MessageSquare, - PencilLine, - PlayCircle, - Rocket, - ScrollText, - ShieldCheck, - Sparkles, - Wallet, - XCircle, -} from 'lucide-react' -import { useTranslation } from 'react-i18next' -import { toast } from 'sonner' -import { - answerHeicodeTask, - createDeploymentDraftFromHeicodeTask, - getHeicodeTask, - type HeicodeFollowup, - type HeicodeManagerAction, - type HeicodeTask, - type HeicodeTaskStatus, -} from '@/lib/heicode-mcp' -import { cn } from '@/lib/utils' -import { Button } from '@/components/ui/button' -import { Skeleton } from '@/components/ui/skeleton' -import { - createAgentDeployment, - type AgentOrchestrationPlan, -} from '@/features/agent-console/api' - -const route = getRouteApi('/_authenticated/tasks/$id') - -type StatusKey = 'running' | 'success' | 'failed' | 'pending' - -const STATUS_MAP: Record = { - draft: 'pending', - configuring: 'pending', - awaiting_approval: 'pending', - running: 'running', - paused: 'pending', - completed: 'success', - failed: 'failed', -} - -function classifyStatus(s: string): StatusKey { - return STATUS_MAP[s] ?? 'pending' -} - -function StatusBadge({ status }: { status: HeicodeTaskStatus | string }) { - const k = classifyStatus(status) - const map: Record = { - running: { - cls: 'bg-[color-mix(in_oklch,var(--primary)_22%,transparent)] text-primary ring-primary/40', - Icon: PlayCircle, - }, - success: { - cls: 'bg-emerald-500/15 text-emerald-400 ring-emerald-500/30', - Icon: CheckCircle2, - }, - failed: { - cls: 'bg-rose-500/15 text-rose-400 ring-rose-500/30', - Icon: XCircle, - }, - pending: { - cls: 'bg-amber-500/15 text-amber-400 ring-amber-500/30', - Icon: CircleDashed, - }, - } - const p = map[k] - return ( - - - {status} - - ) -} - -function readScopeArray(card: HeicodeTask['card'], key: string): string[] { - if (!card) return [] - const v = (card as Record)[key] - if (Array.isArray(v)) - return v.filter((x): x is string => typeof x === 'string') - if (typeof v === 'string' && v.trim()) return [v] - return [] -} - -function readManagerActions(card: HeicodeTask['card']): HeicodeManagerAction[] { - if (!card) return [] - const v = (card as Record).manager_actions - if (!Array.isArray(v)) return [] - return v - .filter( - (x): x is HeicodeManagerAction => - typeof x === 'object' && - x != null && - typeof (x as Record).label === 'string' && - typeof (x as Record).deeplink === 'string' - ) - .slice(0, 6) -} - -/** Resolve helper icon for a manager action by deeplink keyword. */ -function iconForAction(deeplink: string): typeof GitBranch { - const k = deeplink.toLowerCase() - if ( - k.includes('resource') || - k.includes('preparation') || - k.includes('sk-source') - ) - return GitBranch - if (k.includes('audit')) return ShieldCheck - if (k.includes('wallet') || k.includes('budget')) return Wallet - if (k.includes('event') || k.includes('activity')) return ScrollText - return Rocket -} - -/** Manager-side route normalization. Legacy deep-links from older mcp-server - * payloads (resources / team / audit) now collapse to the live task overview; - * wallet keeps its own route. */ -function normalizeDeeplink(deeplink: string): string { - return deeplink - .replace(/^\/manager\/resources/i, '/deployments') - .replace(/^\/manager\/team/i, '/deployments') - .replace(/^\/manager\/audit/i, '/deployments') - .replace(/^\/manager\/wallet/i, '/wallet') - .replace(/^\/manager\//i, '/') -} - -/** Collect open follow-ups from the most recent Heicode thread entry. */ -function collectOpenFollowups(task: HeicodeTask): HeicodeFollowup[] { - for (let i = task.thread.length - 1; i >= 0; i--) { - const entry = task.thread[i]! - if ( - entry.kind === 'heicode' && - entry.followups && - entry.followups.length > 0 - ) { - return entry.followups - } - } - return [] -} - -export function TaskCardView() { - const { t } = useTranslation() - const { id } = route.useParams() - const queryClient = useQueryClient() - - const { data: task, isLoading } = useQuery({ - queryKey: ['heicode', 'task', id], - queryFn: () => getHeicodeTask(id), - refetchInterval: (q) => { - const status = (q.state.data as HeicodeTask | undefined)?.status - if (status === 'completed' || status === 'failed') return false - if (status === 'running' || status === 'awaiting_approval') return 3_000 - return 15_000 - }, - }) - - const answerMutation = useMutation({ - mutationFn: ({ qid, oid }: { qid: string; oid: string }) => - answerHeicodeTask(id, qid, oid), - onSuccess: (updated) => { - queryClient.setQueryData(['heicode', 'task', id], updated) - void queryClient.invalidateQueries({ - queryKey: ['heicode', 'tasks', 'recent'], - }) - if (updated.status === 'running') { - toast.success( - t( - 'All follow-ups answered. Heicode generated the recommendation summary.' - ) - ) - } - }, - onError: (err) => { - toast.error( - err instanceof Error ? err.message : t('Failed to answer follow-up') - ) - }, - }) - - const deploymentMutation = useMutation({ - mutationFn: async () => { - if (!task) throw new Error('Task not found') - const draft = await createDeploymentDraftFromHeicodeTask(task, { - sub_mode: 'agile', - binding_scope: `task-${task.id}`, - role_templates: ['backend'], - default_model_id: 'claude-sonnet-4-6', - }) - return createAgentDeployment({ - orchestration_plan: - draft.orchestration_plan as unknown as AgentOrchestrationPlan, - }) - }, - onSuccess: (deployment) => { - void queryClient.invalidateQueries({ queryKey: ['agent', 'deployments'] }) - toast.success( - t('Manager deployment created', { - deployment_id: deployment.deployment_id, - }) as string - ) - }, - onError: (err) => { - toast.error( - err instanceof Error ? err.message : t('Deployment request failed') - ) - }, - }) - - if (isLoading) { - return ( -
- - - -
- ) - } - - if (!task) { - return ( -
- -
-

- {t( - 'Task not found. It may have been removed or was never created.' - )} -

-
-
- ) - } - - const openFollowups = collectOpenFollowups(task) - // Card shape per live mcp-server smoke test (§6.4 returns): - // { goal: string, scope: string[], generated_artifacts: string[], - // manager_actions: Array<{label, deeplink}> } - const objective = - (task.card && - ((task.card as Record).goal as string | undefined)) || - task.name || - task.intent || - t('No objective') - const firstVersionScope = - readScopeArray(task.card, 'scope').length > 0 - ? readScopeArray(task.card, 'scope') - : [task.intent || objective] - const autoGenerated = - readScopeArray(task.card, 'generated_artifacts').length > 0 - ? readScopeArray(task.card, 'generated_artifacts') - : [ - t('Product brief'), - t('Prototype description'), - t('Development tasks'), - t('Check list'), - t('Deployment steps'), - ] - const managerActions = readManagerActions(task.card) - const pendingContextLine = t( - 'Code, docs, cloud resources and high-risk approval rules. Connect them in the preparation checklist.' - ) - - return ( -
-
- - - {task.id} - -
- - {/* ── Follow-up questions (only while status=configuring) ─────────── */} - {task.status === 'configuring' && openFollowups.length > 0 && ( -
-
- - - -
-

- {t('Heicode is asking')} -

-

- {task.status_caption || - t( - 'Answer a few questions so Heicode can draft the right plan.' - )} -

-
-
- -
    - {openFollowups.map((q) => ( -
  1. -

    {q.question}

    -
    - {q.options.map((opt) => ( - - ))} -
    -
  2. - ))} -
-
- )} - - {/* ── Task card (objective / scope / auto / pending) ───────────── */} -
-
-
-

- {t('Task card')} -

-

- {objective} -

- {task.intent && task.intent !== objective && ( -

- {task.intent} -

- )} -
- -
- -
-
-

- - {t('First-version scope')} -

-
    - {firstVersionScope.map((line, i) => ( -
  • - - {line} -
  • - ))} -
-
- -
-

- - {t('Heicode auto-generates')} -

-

- {autoGenerated.join(' / ')} -

-

- {t( - 'These artifacts appear inside the desktop client as the task progresses.' - )} -

-
-
- -
-

- {t('Needs Manager assistance')} -

-
- {managerActions.length > 0 ? ( - managerActions.map((act) => { - const Icon = iconForAction(act.deeplink) - const to = normalizeDeeplink(act.deeplink).split('?')[0] - return ( - - ) - }) - ) : ( - - )} -
-
- -
- - -
-
- -
-

- {t('Pending context')} -

-

- {pendingContextLine} -

-
-
- ) -} diff --git a/heicode/web/default/src/hooks/use-sidebar-config.ts b/heicode/web/default/src/hooks/use-sidebar-config.ts index 03919de..3aefb18 100644 --- a/heicode/web/default/src/hooks/use-sidebar-config.ts +++ b/heicode/web/default/src/hooks/use-sidebar-config.ts @@ -40,7 +40,6 @@ const DEFAULT_SIDEBAR_MODULES: SidebarModulesAdminConfig = { const URL_TO_CONFIG_MAP: Record = { '/dashboard': { section: 'cockpit', module: 'overview' }, '/dashboard/overview': { section: 'cockpit', module: 'overview' }, - '/deployments': { section: 'cockpit', module: 'deployments' }, '/users': { section: 'admin', module: 'tenants' }, '/system-settings/general': { section: 'admin', module: 'settings' }, '/system-settings': { section: 'admin', module: 'settings' }, diff --git a/heicode/web/default/src/hooks/use-sidebar-data.ts b/heicode/web/default/src/hooks/use-sidebar-data.ts index 5a61e91..32c32a7 100644 --- a/heicode/web/default/src/hooks/use-sidebar-data.ts +++ b/heicode/web/default/src/hooks/use-sidebar-data.ts @@ -4,7 +4,6 @@ import { Download, GitBranch, LayoutDashboard, - Rocket, Settings, Smartphone, UserCog, @@ -58,11 +57,6 @@ export function useSidebarData(): SidebarData { url: '/deploy-agent', icon: Bot, }, - { - title: t('Agent runs'), - url: '/deployments', - icon: Rocket, - }, ], }, diff --git a/heicode/web/default/src/hooks/use-top-nav-links.ts b/heicode/web/default/src/hooks/use-top-nav-links.ts index 7fa4209..bbfaf1d 100644 --- a/heicode/web/default/src/hooks/use-top-nav-links.ts +++ b/heicode/web/default/src/hooks/use-top-nav-links.ts @@ -63,8 +63,5 @@ export function useTopNavLinks(): TopNavLink[] { if (modules?.overview !== false) { links.push({ title: t('Overview'), href: '/dashboard' }) } - if (modules?.deployments !== false) { - links.push({ title: t('Deployments'), href: '/deployments' }) - } return links } diff --git a/heicode/web/default/src/routeTree.gen.ts b/heicode/web/default/src/routeTree.gen.ts index 267cc0e..0fe8ce0 100644 --- a/heicode/web/default/src/routeTree.gen.ts +++ b/heicode/web/default/src/routeTree.gen.ts @@ -45,13 +45,11 @@ import { Route as AuthenticatedModelsIndexRouteImport } from './routes/_authenti import { Route as AuthenticatedKeysIndexRouteImport } from './routes/_authenticated/keys/index' import { Route as AuthenticatedDevicesIndexRouteImport } from './routes/_authenticated/devices/index' import { Route as AuthenticatedDesktopClientIndexRouteImport } from './routes/_authenticated/desktop-client/index' -import { Route as AuthenticatedDeploymentsIndexRouteImport } from './routes/_authenticated/deployments/index' import { Route as AuthenticatedDeployAgentIndexRouteImport } from './routes/_authenticated/deploy-agent/index' import { Route as AuthenticatedDashboardIndexRouteImport } from './routes/_authenticated/dashboard/index' import { Route as AuthenticatedChannelsIndexRouteImport } from './routes/_authenticated/channels/index' import { Route as AuthenticatedAvailableModelsIndexRouteImport } from './routes/_authenticated/available-models/index' import { Route as AuthenticatedUsageLogsSectionRouteImport } from './routes/_authenticated/usage-logs/$section' -import { Route as AuthenticatedTasksIdRouteImport } from './routes/_authenticated/tasks/$id' import { Route as AuthenticatedModelsSectionRouteImport } from './routes/_authenticated/models/$section' import { Route as AuthenticatedErrorsErrorRouteImport } from './routes/_authenticated/errors/$error' import { Route as AuthenticatedDashboardSectionRouteImport } from './routes/_authenticated/dashboard/$section' @@ -262,12 +260,6 @@ const AuthenticatedDesktopClientIndexRoute = path: '/desktop-client/', getParentRoute: () => AuthenticatedRouteRoute, } as any) -const AuthenticatedDeploymentsIndexRoute = - AuthenticatedDeploymentsIndexRouteImport.update({ - id: '/deployments/', - path: '/deployments/', - getParentRoute: () => AuthenticatedRouteRoute, - } as any) const AuthenticatedDeployAgentIndexRoute = AuthenticatedDeployAgentIndexRouteImport.update({ id: '/deploy-agent/', @@ -298,11 +290,6 @@ const AuthenticatedUsageLogsSectionRoute = path: '/usage-logs/$section', getParentRoute: () => AuthenticatedRouteRoute, } as any) -const AuthenticatedTasksIdRoute = AuthenticatedTasksIdRouteImport.update({ - id: '/tasks/$id', - path: '/tasks/$id', - getParentRoute: () => AuthenticatedRouteRoute, -} as any) const AuthenticatedModelsSectionRoute = AuthenticatedModelsSectionRouteImport.update({ id: '/models/$section', @@ -442,13 +429,11 @@ export interface FileRoutesByFullPath { '/dashboard/$section': typeof AuthenticatedDashboardSectionRoute '/errors/$error': typeof AuthenticatedErrorsErrorRoute '/models/$section': typeof AuthenticatedModelsSectionRoute - '/tasks/$id': typeof AuthenticatedTasksIdRoute '/usage-logs/$section': typeof AuthenticatedUsageLogsSectionRoute '/available-models/': typeof AuthenticatedAvailableModelsIndexRoute '/channels/': typeof AuthenticatedChannelsIndexRoute '/dashboard/': typeof AuthenticatedDashboardIndexRoute '/deploy-agent/': typeof AuthenticatedDeployAgentIndexRoute - '/deployments/': typeof AuthenticatedDeploymentsIndexRoute '/desktop-client/': typeof AuthenticatedDesktopClientIndexRoute '/devices/': typeof AuthenticatedDevicesIndexRoute '/keys/': typeof AuthenticatedKeysIndexRoute @@ -503,13 +488,11 @@ export interface FileRoutesByTo { '/dashboard/$section': typeof AuthenticatedDashboardSectionRoute '/errors/$error': typeof AuthenticatedErrorsErrorRoute '/models/$section': typeof AuthenticatedModelsSectionRoute - '/tasks/$id': typeof AuthenticatedTasksIdRoute '/usage-logs/$section': typeof AuthenticatedUsageLogsSectionRoute '/available-models': typeof AuthenticatedAvailableModelsIndexRoute '/channels': typeof AuthenticatedChannelsIndexRoute '/dashboard': typeof AuthenticatedDashboardIndexRoute '/deploy-agent': typeof AuthenticatedDeployAgentIndexRoute - '/deployments': typeof AuthenticatedDeploymentsIndexRoute '/desktop-client': typeof AuthenticatedDesktopClientIndexRoute '/devices': typeof AuthenticatedDevicesIndexRoute '/keys': typeof AuthenticatedKeysIndexRoute @@ -568,13 +551,11 @@ export interface FileRoutesById { '/_authenticated/dashboard/$section': typeof AuthenticatedDashboardSectionRoute '/_authenticated/errors/$error': typeof AuthenticatedErrorsErrorRoute '/_authenticated/models/$section': typeof AuthenticatedModelsSectionRoute - '/_authenticated/tasks/$id': typeof AuthenticatedTasksIdRoute '/_authenticated/usage-logs/$section': typeof AuthenticatedUsageLogsSectionRoute '/_authenticated/available-models/': typeof AuthenticatedAvailableModelsIndexRoute '/_authenticated/channels/': typeof AuthenticatedChannelsIndexRoute '/_authenticated/dashboard/': typeof AuthenticatedDashboardIndexRoute '/_authenticated/deploy-agent/': typeof AuthenticatedDeployAgentIndexRoute - '/_authenticated/deployments/': typeof AuthenticatedDeploymentsIndexRoute '/_authenticated/desktop-client/': typeof AuthenticatedDesktopClientIndexRoute '/_authenticated/devices/': typeof AuthenticatedDevicesIndexRoute '/_authenticated/keys/': typeof AuthenticatedKeysIndexRoute @@ -632,13 +613,11 @@ export interface FileRouteTypes { | '/dashboard/$section' | '/errors/$error' | '/models/$section' - | '/tasks/$id' | '/usage-logs/$section' | '/available-models/' | '/channels/' | '/dashboard/' | '/deploy-agent/' - | '/deployments/' | '/desktop-client/' | '/devices/' | '/keys/' @@ -693,13 +672,11 @@ export interface FileRouteTypes { | '/dashboard/$section' | '/errors/$error' | '/models/$section' - | '/tasks/$id' | '/usage-logs/$section' | '/available-models' | '/channels' | '/dashboard' | '/deploy-agent' - | '/deployments' | '/desktop-client' | '/devices' | '/keys' @@ -757,13 +734,11 @@ export interface FileRouteTypes { | '/_authenticated/dashboard/$section' | '/_authenticated/errors/$error' | '/_authenticated/models/$section' - | '/_authenticated/tasks/$id' | '/_authenticated/usage-logs/$section' | '/_authenticated/available-models/' | '/_authenticated/channels/' | '/_authenticated/dashboard/' | '/_authenticated/deploy-agent/' - | '/_authenticated/deployments/' | '/_authenticated/desktop-client/' | '/_authenticated/devices/' | '/_authenticated/keys/' @@ -1066,13 +1041,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof AuthenticatedDesktopClientIndexRouteImport parentRoute: typeof AuthenticatedRouteRoute } - '/_authenticated/deployments/': { - id: '/_authenticated/deployments/' - path: '/deployments' - fullPath: '/deployments/' - preLoaderRoute: typeof AuthenticatedDeploymentsIndexRouteImport - parentRoute: typeof AuthenticatedRouteRoute - } '/_authenticated/deploy-agent/': { id: '/_authenticated/deploy-agent/' path: '/deploy-agent' @@ -1108,13 +1076,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof AuthenticatedUsageLogsSectionRouteImport parentRoute: typeof AuthenticatedRouteRoute } - '/_authenticated/tasks/$id': { - id: '/_authenticated/tasks/$id' - path: '/tasks/$id' - fullPath: '/tasks/$id' - preLoaderRoute: typeof AuthenticatedTasksIdRouteImport - parentRoute: typeof AuthenticatedRouteRoute - } '/_authenticated/models/$section': { id: '/_authenticated/models/$section' path: '/models/$section' @@ -1339,13 +1300,11 @@ interface AuthenticatedRouteRouteChildren { AuthenticatedDashboardSectionRoute: typeof AuthenticatedDashboardSectionRoute AuthenticatedErrorsErrorRoute: typeof AuthenticatedErrorsErrorRoute AuthenticatedModelsSectionRoute: typeof AuthenticatedModelsSectionRoute - AuthenticatedTasksIdRoute: typeof AuthenticatedTasksIdRoute AuthenticatedUsageLogsSectionRoute: typeof AuthenticatedUsageLogsSectionRoute AuthenticatedAvailableModelsIndexRoute: typeof AuthenticatedAvailableModelsIndexRoute AuthenticatedChannelsIndexRoute: typeof AuthenticatedChannelsIndexRoute AuthenticatedDashboardIndexRoute: typeof AuthenticatedDashboardIndexRoute AuthenticatedDeployAgentIndexRoute: typeof AuthenticatedDeployAgentIndexRoute - AuthenticatedDeploymentsIndexRoute: typeof AuthenticatedDeploymentsIndexRoute AuthenticatedDesktopClientIndexRoute: typeof AuthenticatedDesktopClientIndexRoute AuthenticatedDevicesIndexRoute: typeof AuthenticatedDevicesIndexRoute AuthenticatedKeysIndexRoute: typeof AuthenticatedKeysIndexRoute @@ -1368,14 +1327,12 @@ const AuthenticatedRouteRouteChildren: AuthenticatedRouteRouteChildren = { AuthenticatedDashboardSectionRoute: AuthenticatedDashboardSectionRoute, AuthenticatedErrorsErrorRoute: AuthenticatedErrorsErrorRoute, AuthenticatedModelsSectionRoute: AuthenticatedModelsSectionRoute, - AuthenticatedTasksIdRoute: AuthenticatedTasksIdRoute, AuthenticatedUsageLogsSectionRoute: AuthenticatedUsageLogsSectionRoute, AuthenticatedAvailableModelsIndexRoute: AuthenticatedAvailableModelsIndexRoute, AuthenticatedChannelsIndexRoute: AuthenticatedChannelsIndexRoute, AuthenticatedDashboardIndexRoute: AuthenticatedDashboardIndexRoute, AuthenticatedDeployAgentIndexRoute: AuthenticatedDeployAgentIndexRoute, - AuthenticatedDeploymentsIndexRoute: AuthenticatedDeploymentsIndexRoute, AuthenticatedDesktopClientIndexRoute: AuthenticatedDesktopClientIndexRoute, AuthenticatedDevicesIndexRoute: AuthenticatedDevicesIndexRoute, AuthenticatedKeysIndexRoute: AuthenticatedKeysIndexRoute, diff --git a/heicode/web/default/src/routes/_authenticated/deployments/index.tsx b/heicode/web/default/src/routes/_authenticated/deployments/index.tsx deleted file mode 100644 index 8bd7f66..0000000 --- a/heicode/web/default/src/routes/_authenticated/deployments/index.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import { createFileRoute } from '@tanstack/react-router' -import { AgentDeploymentsPage } from '@/features/agent-console/pages' - -export const Route = createFileRoute('/_authenticated/deployments/')({ - component: AgentDeploymentsPage, -}) diff --git a/heicode/web/default/src/routes/_authenticated/tasks/$id.tsx b/heicode/web/default/src/routes/_authenticated/tasks/$id.tsx deleted file mode 100644 index 90ab710..0000000 --- a/heicode/web/default/src/routes/_authenticated/tasks/$id.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import { createFileRoute } from '@tanstack/react-router' -import { TaskCardView } from '@/features/tasks/task-card-view' - -export const Route = createFileRoute('/_authenticated/tasks/$id')({ - component: TaskCardView, -})