feat(mira): 河道 → wiki 自動化(fire-and-forget 觸發 wiki_synthesis)
對應 polaris/mira/.agents/specs/mira-app/tasks.md 7B.3h(簡化版)。
原計畫用 arcrun cron 零件 → cypher-executor scheduled() handler,但發現
cron 零件只是 validator,cypher-executor 還沒實作 scheduled()。為了不擋
「河道書寫 → 自動產 wiki」這條 UX,先做 fire-and-forget 版本:
- 新 cypher-executor route POST /mira/wiki-from-raw
- body: { raw_block_id }
- server 端從 MIRA_CONFIG secret 補 partner key / mira_token / 三個 block IDs
- waitUntil 背景跑 executeWebhookGraph,立刻回 202
- landing 河道 post composer 成功寫 raw 後 fire-and-forget triggerWikiSynthesis()
跟既有 triggerAiReply() 同範式
- types.ts 加 MIRA_CONFIG?: string
部署後需手動:
echo '{"service_api_key":"ak_...","data_api_key":"ak_...","schema_block_id":"...","skill_block_id":"...","entities_block_id":"...","mira_token":"..."}' \
| wrangler secret put MIRA_CONFIG
UX:河道貼一則 → AI reply 30s 內 → wiki 60-90s 內出現在 /mira/wiki。
arcrun.md 記 P1 #3:cypher-executor 加 scheduled() handler,那是真正的
cron 路線,封測前不擋。
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
/**
|
||||
* 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<MiraConfig>;
|
||||
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<string, unknown> };
|
||||
try {
|
||||
record = JSON.parse(raw) as { graph: Record<string, unknown> };
|
||||
} catch {
|
||||
return c.json({ error: 'workflow 定義損毀' }, 500);
|
||||
}
|
||||
|
||||
const triggerContext: Record<string, unknown> = {
|
||||
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);
|
||||
});
|
||||
Reference in New Issue
Block a user