Files
Arcrun/cypher-executor/src/routes/mira.ts
T
Leo 660b32eafd 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 路線,封測前不擋。
2026-05-14 13:50:13 +08:00

111 lines
3.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Mira-specific routes — 給 mira applanding/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 keyacr 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 workflowservice_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);
});