/** * Mira-specific routes — 給 mira app(landing/app/mira/feed)從前端 fire-and-forget * 觸發 wiki_synthesis workflow,而不需要前端持有 mira_token / partner key / block IDs。 * * SDD: polaris/mira/.agents/specs/mira-app/design.md §3.5.12 + §5.3 * 對應 task: mira 7B.3h(簡化版:以 frontend fire-and-forget 取代 cron 觸發; * 真正的 cron 排程留 arcrun.md P1 #3 — cypher-executor scheduled() handler) * * 設定(self-host fork): * wrangler secret put MIRA_CONFIG (cypher-executor) * 值為 JSON 字串,欄位見 MiraConfig type */ import { Hono } from 'hono'; import type { Bindings } from '../types'; import { executeWebhookGraph } from '../actions/webhook-handlers'; export const miraRouter = new Hono<{ Bindings: Bindings }>(); type MiraConfig = { service_api_key: string; // 部署 wiki_synthesis 的 partner key(acr push 用) data_api_key: string; // mira 寫 KBDB 用的 partner key(前端 /me 拿的) schema_block_id: string; // mira-wiki-schema block skill_block_id: string; // mira-wiki-skill block entities_block_id: string; // mira-wiki-index-entities block mira_token: string; // claude_api → mira daemon 的 bearer }; function parseMiraConfig(raw?: string): MiraConfig | null { if (!raw) return null; try { const parsed = JSON.parse(raw) as Partial; if ( !parsed.service_api_key || !parsed.data_api_key || !parsed.schema_block_id || !parsed.skill_block_id || !parsed.entities_block_id || !parsed.mira_token ) { return null; } return parsed as MiraConfig; } catch { return null; } } // POST /mira/wiki-from-raw — 對一個 raw block 跑 wiki_synthesis // Body: { raw_block_id: string } // 給前端 fire-and-forget 用,不等結果回(workflow 跑 60-90s) miraRouter.post('/mira/wiki-from-raw', async (c) => { const cfg = parseMiraConfig(c.env.MIRA_CONFIG); if (!cfg) { return c.json( { error: 'Mira 未配置:請 wrangler secret put MIRA_CONFIG(見 routes/mira.ts header)', }, 501, ); } let body: { raw_block_id?: string } = {}; try { body = (await c.req.json()) as { raw_block_id?: string }; } catch { return c.json({ error: 'body 必須是 JSON' }, 400); } if (!body.raw_block_id) { return c.json({ error: 'raw_block_id 必填' }, 400); } // 從 KV 拿 wiki_synthesis workflow 定義(部署在 service_api_key 名下) const wfKey = `webhook:${cfg.service_api_key}:wiki_synthesis`; const raw = await c.env.WEBHOOKS.get(wfKey, 'text'); if (!raw) { return c.json( { error: '找不到 wiki_synthesis workflow(service_api_key 是否與 acr push 用的一致?)' }, 404, ); } let record: { graph: Record }; try { record = JSON.parse(raw) as { graph: Record }; } catch { return c.json({ error: 'workflow 定義損毀' }, 500); } const triggerContext: Record = { api_key: cfg.data_api_key, mira_token: cfg.mira_token, schema_block_id: cfg.schema_block_id, skill_block_id: cfg.skill_block_id, entities_block_id: cfg.entities_block_id, index_entries_block_id: cfg.entities_block_id, // 7B.3f:暫共用 entities block 當 index parent raw_block_id: body.raw_block_id, }; // fire-and-forget:用 waitUntil 在 background 跑,立刻回 202 // 若用戶 cookie session 不要等 const promise = executeWebhookGraph(c.env, record.graph, triggerContext, 'wiki_synthesis', cfg.service_api_key); c.executionCtx.waitUntil( promise.then( (r) => console.log('[mira/wiki-from-raw] done', r.success, r.duration_ms), (e) => console.error('[mira/wiki-from-raw] failed', e), ), ); return c.json({ accepted: true, raw_block_id: body.raw_block_id }, 202); });