From ab53f3433469bdde8067ca640e43cdcc83af1995 Mon Sep 17 00:00:00 2001 From: chenchen Date: Tue, 12 May 2026 13:46:27 +0800 Subject: [PATCH] fix(tasks): align HeicodeTaskCard fields with live mcp-server shape MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit End-to-end smoke against mcp-server (test account 55@55.com) revealed the actual task.card shape: { goal: string, scope: string[], generated_artifacts: string[], manager_actions: Array<{label, deeplink}> } My earlier TS types and TaskCardView assumed {objective, first_version_scope, auto_generated, pending_context} — keys that don't exist in the real response. Tasks created via the intent flow would have rendered with empty bullet lists. Changes: - lib/heicode-mcp.ts: rewrite HeicodeTaskCard to the live shape, add HeicodeManagerAction type - features/tasks/task-card-view.tsx: - read card.goal / card.scope / card.generated_artifacts - new readManagerActions() helper renders mcp-server's {label, deeplink} buttons in place of the hardcoded action row, with normalizeDeeplink() mapping /manager/resources → /sk-sources etc. to Manager-side routes - dropped pending_context (no such field); follow-up "Pending context" footnote is now a plain explanatory line per docs §10 Smoke verified end-to-end: login (POST /api/auth/login) → 200 + JWT intent (POST /api/user/tasks/intent) → 200 + configuring task list (GET /api/user/tasks) → items shape matches type detail (GET /api/user/tasks/{id}) → follow-ups parse correctly answer × 2 (POST .../answer) → state machine flips to running, card materialises with the 4 actual fields above audit (GET /api/agnet/audit-logs) → {items, total, next_cursor} agnet (GET /api/agnet/deployments) → {items, total} balance (GET /api/user/heicode/balance) → HEICODE_USER_NOT_FOUND (test account, expected; my code catches this and returns null) UTF-8 body through the proxy works fine (earlier "parse body" error was a Windows shell quoting issue, not a proxy bug). Co-Authored-By: Claude Opus 4.7 (1M context) --- .../src/features/tasks/task-card-view.tsx | 137 ++++++++++++------ heicode/web/default/src/lib/heicode-mcp.ts | 17 ++- 2 files changed, 102 insertions(+), 52 deletions(-) diff --git a/heicode/web/default/src/features/tasks/task-card-view.tsx b/heicode/web/default/src/features/tasks/task-card-view.tsx index b33a75c..b44fe25 100644 --- a/heicode/web/default/src/features/tasks/task-card-view.tsx +++ b/heicode/web/default/src/features/tasks/task-card-view.tsx @@ -39,6 +39,7 @@ import { answerHeicodeTask, getHeicodeTask, type HeicodeFollowup, + type HeicodeManagerAction, type HeicodeTask, type HeicodeTaskStatus, } from '@/lib/heicode-mcp' @@ -103,6 +104,41 @@ function readScopeArray(card: HeicodeTask['card'], key: string): string[] { 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. mcp-server returns + * `/manager/resources?from=task` but Manager's actual route is `/sk-sources`. */ +function normalizeDeeplink(deeplink: string): string { + return deeplink + .replace(/^\/manager\/resources/i, '/sk-sources') + .replace(/^\/manager\/team/i, '/sk-sources') + .replace(/^\/manager\/audit/i, '/audit') + .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--) { @@ -169,18 +205,21 @@ export function TaskCardView() { } 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).objective as string | undefined) || - task.intent || + (task.card && (task.card as Record).goal as string | undefined) || task.name || + task.intent || t('No objective') const firstVersionScope = - readScopeArray(task.card, 'first_version_scope').length > 0 - ? readScopeArray(task.card, 'first_version_scope') + readScopeArray(task.card, 'scope').length > 0 + ? readScopeArray(task.card, 'scope') : [task.intent || objective] const autoGenerated = - readScopeArray(task.card, 'auto_generated').length > 0 - ? readScopeArray(task.card, 'auto_generated') + readScopeArray(task.card, 'generated_artifacts').length > 0 + ? readScopeArray(task.card, 'generated_artifacts') : [ t('Product brief'), t('Prototype description'), @@ -188,14 +227,10 @@ export function TaskCardView() { t('Check list'), t('Deployment steps'), ] - const pendingContext = - readScopeArray(task.card, 'pending_context').length > 0 - ? readScopeArray(task.card, 'pending_context') - : [ - t( - 'Code, docs, cloud resources and high-risk approval rules. Connect them in the preparation checklist.' - ), - ] + const managerActions = readManagerActions(task.card) + const pendingContextLine = t( + 'Code, docs, cloud resources and high-risk approval rules. Connect them in the preparation checklist.' + ) return (
@@ -336,30 +371,47 @@ export function TaskCardView() { {t('Needs Manager assistance')}

- - - - + {managerActions.length > 0 ? ( + managerActions.map((act) => { + const Icon = iconForAction(act.deeplink) + const to = normalizeDeeplink(act.deeplink).split('?')[0] + return ( + + ) + }) + ) : ( + <> + + + + + )}
@@ -396,14 +448,7 @@ export function TaskCardView() {

{t('Pending context')}

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

{pendingContextLine}

) diff --git a/heicode/web/default/src/lib/heicode-mcp.ts b/heicode/web/default/src/lib/heicode-mcp.ts index a430104..f8b2d6f 100644 --- a/heicode/web/default/src/lib/heicode-mcp.ts +++ b/heicode/web/default/src/lib/heicode-mcp.ts @@ -106,13 +106,18 @@ export type HeicodeThreadEntry = { followups?: HeicodeFollowup[] } -/** Task card — populated when all followups answered (§6.4). */ +/** Task card — populated when all followups answered (§6.4). + * Shape verified via live smoke test against mcp-server stub. */ +export type HeicodeManagerAction = { + label: string + deeplink: string +} export type HeicodeTaskCard = { - objective?: string - first_version_scope?: string[] - auto_generated?: string[] - pending_context?: string[] - // additional fields per HeicodeTask card contract + goal?: string // 任务目标(docs §10 "目标") + scope?: string[] // 第一版范围 + generated_artifacts?: string[] // Heicode 自动生成 + manager_actions?: HeicodeManagerAction[] // Manager 辅助按钮 + // forward-compat passthrough [key: string]: unknown } | null