From bc6360ccfc9b152254f8909dafc926242020a60f Mon Sep 17 00:00:00 2001 From: richblack Date: Thu, 14 May 2026 16:06:46 +0800 Subject: [PATCH] feat(arcrun): http_request body_json + error heuristic; mira feed fire-and-forget MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit http_request 零件擴展(registry/components/http_request): - 加 body_json 物件欄位(內部 JSON.stringify),yaml 端不用手組 JSON 字串 - 新增 JSON 回應的 error 欄位偵測 → 若 body 含 `{"error":"..."}` 則零件回 success=false 解 cascade bug:mira_feed_watcher 用 http_request trigger wiki_synthesis, 原本 4xx response 也被當 success,ON_SUCCESS 鏈會誤觸發 根因架構債:host fn 沒回 HTTP status code(arcrun.md 列為 P1 follow-up) landing 河道 feed (landing/app/mira/feed/page.tsx): - 加回 triggerWikiSynthesis fire-and-forget 對 cypher.arcrun.dev/webhooks/named/ wiki_synthesis/trigger 公開觸發 endpoint(arcrun-native,非 mira-specific route) - 不走 watcher 是因為 cypher-executor 自己 fetch 自己 workers.dev URL = CF 1042 self-fetch 擋 watcher 仍存在當 cron backup,但目前因 self-fetch 1042 不會真正觸發下游 wiki_synthesis(arcrun.md 列為 P1 follow-up)。 --- landing/app/mira/feed/page.tsx | 29 +++++++++++++- .../http_request/component.contract.yaml | 6 ++- registry/components/http_request/main.go | 38 ++++++++++++++++--- 3 files changed, 65 insertions(+), 8 deletions(-) diff --git a/landing/app/mira/feed/page.tsx b/landing/app/mira/feed/page.tsx index fcf1ead..b8928db 100644 --- a/landing/app/mira/feed/page.tsx +++ b/landing/app/mira/feed/page.tsx @@ -158,6 +158,29 @@ export default function MiraPage() { // ─── AI 回覆觸發器(fire-and-forget)────────────────────── +async function triggerWikiSynthesis(opts: { apiKey: string; rawBlockId: string }) { + // 觸發 arcrun wiki_synthesis workflow(arcrun-native public trigger endpoint) + // 不等結果(workflow 60-90s 含 2 次 claude_api pause/resume) + try { + const res = await fetch(`${API_BASE}/webhooks/named/wiki_synthesis/trigger`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-Arcrun-API-Key': opts.apiKey, + }, + body: JSON.stringify({ api_key: opts.apiKey, raw_block_id: opts.rawBlockId }), + }); + if (!res.ok) { + console.warn('[wiki_synthesis trigger] non-ok:', res.status); + return; + } + const data = await res.json().catch(() => ({})); + console.log('[wiki_synthesis trigger] response:', data); + } catch (e) { + console.warn('[wiki_synthesis trigger] error:', e); + } +} + async function triggerAiReply(opts: { apiKey: string; postContent: string; @@ -272,8 +295,10 @@ function PostComposer({ parentBlockId: postBlockId, pageName, }); - // 7B.3h:wiki_synthesis 由 arcrun cron-triggered workflow `mira_feed_watcher` - // 自動處理(每分鐘掃未處理 raw block),不需前端觸發。 + // 7B.3h:fire-and-forget 觸發 wiki_synthesis(browser → cypher.arcrun.dev,arcrun-native) + // 不走 watcher 是因為 cypher-executor 自己 fetch 自己 workers.dev URL 被 CF 1042 擋 + // watcher 仍作為 cron-driven backup(漏掉的 raws 5 分鐘後補跑),但需先解 self-fetch 問題 + void triggerWikiSynthesis({ apiKey: me.api_key, rawBlockId: postBlockId }); onAiTriggered(pageName); // 給 D1 GROUP BY 查詢看到新資料的時間 diff --git a/registry/components/http_request/component.contract.yaml b/registry/components/http_request/component.contract.yaml index c89d50c..0a4deaf 100644 --- a/registry/components/http_request/component.contract.yaml +++ b/registry/components/http_request/component.contract.yaml @@ -31,7 +31,11 @@ input_schema: additionalProperties: type: string body: - description: 請求 body(任意 JSON) + type: string + description: 模式 A — body 字串(自行 stringify 後傳) + body_json: + type: object + description: 模式 B — body 物件,零件內部 JSON.stringify(yaml 端不用手組字串) output_schema: type: object properties: diff --git a/registry/components/http_request/main.go b/registry/components/http_request/main.go index a7a3d4f..fff14b8 100644 --- a/registry/components/http_request/main.go +++ b/registry/components/http_request/main.go @@ -24,10 +24,11 @@ func hostHttpRequest( ) uint32 type Input struct { - URL string `json:"url"` - Method string `json:"method"` - Headers map[string]string `json:"headers"` - Body string `json:"body"` + URL string `json:"url"` + Method string `json:"method"` + Headers map[string]string `json:"headers"` + Body string `json:"body"` // 模式 A:直接 string body + BodyJSON map[string]interface{} `json:"body_json"` // 模式 B:物件,內部 stringify(避免 yaml 端要自己組 JSON 字串) } // dummy byte for safe zero-length unsafe.Pointer operations @@ -71,10 +72,19 @@ func main() { headersJSON = string(b) } + // body 來源優先順序:body_json(物件 → JSON 字串)> body(直接 string) + bodyStr := input.Body + if input.BodyJSON != nil { + b, err := json.Marshal(input.BodyJSON) + if err == nil { + bodyStr = string(b) + } + } + urlBytes := []byte(input.URL) methodBytes := []byte(method) headersBytes := []byte(headersJSON) - bodyBytes := []byte(input.Body) + bodyBytes := []byte(bodyStr) outBuf := make([]byte, 65536) // 64KB output buffer var outLen uint32 @@ -97,6 +107,24 @@ func main() { } responseStr := string(outBuf[:outLen]) + + // 2026-05-14:偵測 JSON `{"error":"..."}` 模式視為 4xx 失敗 + // 理由:host function 沒回 HTTP status code(架構債),先用 body 啟發式 catch。 + // 標準 API(cypher-executor / KBDB / 多數 REST)失敗時都回 {"error":...} JSON。 + // 對應 SDD: arcrun.md 三-A P1 #4「http_request status code 缺乏 surface」。 + var parsed map[string]interface{} + if err := json.Unmarshal([]byte(responseStr), &parsed); err == nil { + if errVal, ok := parsed["error"]; ok && errVal != nil { + out, _ := json.Marshal(map[string]interface{}{ + "success": false, + "error": errVal, + "data": map[string]interface{}{"body": responseStr}, + }) + os.Stdout.Write(out) + return + } + } + out, _ := json.Marshal(map[string]interface{}{ "success": true, "data": map[string]interface{}{"body": responseStr},