b1e302b3b5
kbdb-base Phase 9.6/9.7(HANDOFF §2 缺口① + §3b 連帶): - 9.6 cypher kbdb-proxy 補 /kbdb/entries CRUD(POST/GET list/GET :id/PATCH :id) 純轉發到 KBDB 基本盤 /entries,解鎖 mira _kbdb_client.py 主線遷移。 租戶隔離同 9.5:寫入注入 owner_id、list 強制本租戶過濾、PATCH 剝 owner_id。 刻意不開 DELETE(基本盤 delete 無 owner 檢查 → 跨租戶刪除風險)。 - 9.7 arcrun_report_feedback 從死 route /blocks 改打基本盤 /entries (entry_type=agent-feedback)。9.4 漏網的同類修;基本盤無 /blocks → 原本 404 假紅。 順帶(HANDOFF §6 harness 表達優化): - 重寫 cli/harness/CLAUDE.block.md 補三盲點(recipe 是公共投稿 / 缺能力補 API 不拼裝 / 自製零件退場路徑),目標 Haiku 級 CC 讀懂。 - README 零件 vs recipe 段對齊同三點。 cypher + mcp tsc exit 0。端到端 smoke test 隨後。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
152 lines
6.5 KiB
TypeScript
152 lines
6.5 KiB
TypeScript
/**
|
||
* 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,
|
||
};
|
||
}
|
||
}
|
||
);
|
||
}
|