/** * arcrun_report_feedback — explicit feedback tool for AI agents * * 對應 SDD .agents/specs/llm-interface/ M1.3 * * AI agent 每次完成 workflow / 卡住 / 解掉問題後 **MUST** call 此 tool。 * 結構化 issue_type enum 防自由文字難聚合。寫入 KBDB type=agent-feedback block。 * * 後續 M4 weekly_review workflow 聚合這些 block 產出 arcrun-roadmap。 * * 命名注意:M5 全面 rename u6u → arcrun 前,本 tool 直接用新名 arcrun_ prefix * 立下範例。其他 u6u_* tool 等 M5 一次切。 */ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { z } from "zod"; import { Env } from "../types.js"; import { kbdbFetch } from "../lib/kbdb-client.js"; const ISSUE_TYPES = [ "success_story", // 順利完成,值得記錄這個 pattern "doc_unclear", // AGENTS.md / skill / contract 講不清楚 "tool_missing", // 該有的 MCP tool 沒有 "error_unhelpful", // 錯誤訊息看不懂下一步 "unexpected_behavior", // 跟我預期的不一樣 "feature_request", // 我想要 X 功能 ] as const; export function registerReportFeedback(server: McpServer, env: Env, orgNamespace: string) { server.tool( "arcrun_report_feedback", "AI agent 完成 workflow 任務 / 卡住 / 解掉問題後 **必須** call 此 tool 回報。即使順利也要 call (issue_type=success_story),那是告訴平台「這 pattern 已 work,可推廣」。回饋會寫進 KBDB type=agent-feedback,週報自動聚合產出平台改善 roadmap。", { issue_type: z.enum(ISSUE_TYPES).describe( "回報類型。success_story=順利做完 / doc_unclear=文件不清楚 / tool_missing=該有的 MCP tool 缺 / error_unhelpful=錯誤訊息看不懂下一步 / unexpected_behavior=與預期不符 / feature_request=想要新功能" ), description: z.string().min(10).describe( "詳述:你做了什麼、發生什麼、為什麼這算 issue / story。至少 10 字。若是 success_story,描述 pattern 與適用情境" ), workflow_name: z.string().optional().describe("相關 workflow 名稱(若有)"), retry_count: z.number().int().min(0).optional().describe("為了搞定,你重試了幾次(含修 YAML / 改參數)"), blocked: z.boolean().optional().describe("是否完全擋住(true = 無法繼續),預設 false"), suggested_fix: z.string().optional().describe("你建議的修補方向(optional,但很有價值)"), agent_user_agent: z.string().optional().describe( "你(AI agent)的 client 識別字串。e.g. 'claude-code/1.x'、'cursor-mcp/0.4'、'mira-bot'。讓平台知道哪個 AI 客戶端踩到問題" ), }, async ({ issue_type, description, workflow_name, retry_count, blocked, suggested_fix, agent_user_agent }) => { try { if (!env.KBDB) { return { content: [{ type: "text", text: "Error: KBDB service binding unavailable" }], isError: true }; } // kbdb-base 9.7:寫進基本盤 entries(entry_type=agent-feedback)。 // 舊版打 v3 死 route /blocks(基本盤只 mount entries/templates/records)→ 404 假紅,已改。 // owner_id = 用戶 namespace(self-hosted 單租戶聚集)。基本盤無 source/api_key 欄 → 併入 metadata。 const entryBody = { entry_type: "agent-feedback", owner_id: orgNamespace, content: description, metadata_json: JSON.stringify({ issue_type, workflow_name, retry_count, blocked: blocked ?? false, suggested_fix, agent_user_agent, source: "mcp-tool-call", reported_at: new Date().toISOString(), }), tags_json: JSON.stringify([ "agent-feedback", `issue:${issue_type}`, ...(blocked ? ["blocked"] : []), ...(workflow_name ? [`wf:${workflow_name}`] : []), ]), }; // 走 KBDB service binding 打基本盤 /entries(薄殼模式不變) const createResp = await kbdbFetch(env, `/entries`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(entryBody), }); if (!createResp.ok) { const errBody = await createResp.text(); return { content: [ { type: "text", text: JSON.stringify({ ok: false, error_code: "kbdb_write_failed", human_message: `回饋寫入 KBDB 失敗:HTTP ${createResp.status}`, next_actions: [ "確認 KBDB 服務在線(KBDB worker /health)", "若持續失敗,可暫先在本地記下回饋,稍後重試", ], detail: errBody.slice(0, 200), }, null, 2), }, ], isError: true, }; } const data = await createResp.json().catch(() => null); return { content: [ { type: "text", text: JSON.stringify({ ok: true, data: { reported: true, issue_type, // 基本盤 /entries 回 { success, entry };舊 /blocks 回 { id } → 兩種都容忍 entry_id: (data as { entry?: { id?: string }; id?: string } | null)?.entry?.id ?? (data as { id?: string } | null)?.id, }, hints: [ issue_type === "success_story" ? "感謝記錄成功 pattern!這會被納入週報自動推廣。" : "感謝回報!平台週報會聚合這類問題(M4 完成後可看 arcrun-roadmap block)", "若還有相關問題(例如同 workflow 不同 issue),可繼續 call", ], }, null, 2), }, ], }; } catch (error) { return { content: [ { type: "text", text: JSON.stringify({ ok: false, error_code: "internal_error", human_message: `report_feedback 內部錯誤:${error instanceof Error ? error.message : String(error)}`, next_actions: ["重試一次", "若持續失敗,請告訴用戶這個 issue 並貼錯誤訊息給 leo"], }, null, 2), }, ], isError: true, }; } } ); }