2707fca32b
Phase 1-5 complete per .agents/specs/u6u-core-mvp/: **Phase 1 — Cherry-pick & cleanup** - Create arcrun/ from cypher-executor, credentials, builtins, registry - Remove 9 InkStone Service Bindings (KBDB, REGISTRY, CLINIC_*, AICEO, MINI_ME) - Rewrite component-loader: 3-layer (builtin → WASM_BUCKET R2 → error) - Remove autoPublishMissing.ts, proxy.ts (AICEO), execution-logger.ts (KBDB) - Clean all KV namespace IDs and InkStone internal URLs from config files **Phase 2 — contract.yaml completeness** - Add credentials_required to gmail, google_sheets, telegram, line_notify - Add config_example to all 21 components with annotated field descriptions **Phase 3 — Credential injection** - Add credential-injector.ts: AES-GCM decrypt from CREDENTIALS_KV - Integrate into GraphExecutor before WASM execution - Structured errors with repair instructions when credential missing **Phase 4 — CLI (acr)** - cli/package.json: arcrun package, bin: acr, deps: commander/js-yaml/chalk/ora - 8 commands: init, creds push, push, run, validate, parts, list, logs - Standard mode: writes directly to user's CF KV via CF REST API - acr init: interactive setup with arcrun.dev API Key registration **Phase 5 — Open source release prep** - README.md: 5-minute quickstart, component table, workflow YAML syntax - CONTRIBUTING.md: TinyGo dev env, component scaffolding, submission flow - Security audit: no InkStone internal URLs/IDs in committed files - .gitignore: exclude credentials.yaml, .wrangler, *.wasm https://claude.ai/code/session_01BnCdSLVH8tUed9VrrPavgT
74 lines
2.4 KiB
TypeScript
74 lines
2.4 KiB
TypeScript
import { Hono } from 'hono';
|
|
import type { Bindings } from '../types';
|
|
import { generateToken, validateAndParseWebhook, executeWebhookGraph } from '../actions/webhook-handlers';
|
|
import { resolveWebhookGraph } from '../actions/webhook-graph-resolver';
|
|
|
|
export const webhooksRouter = new Hono<{ Bindings: Bindings }>();
|
|
|
|
type WebhookRecord = {
|
|
graph: Record<string, unknown>;
|
|
description: string;
|
|
created_at: string;
|
|
};
|
|
|
|
// POST /webhooks — 接受 graph、triplets 或直接 nodes/edges
|
|
webhooksRouter.post('/webhooks', async (c) => {
|
|
const body = await c.req.json().catch(() => null);
|
|
if (!body) return c.json({ error: 'invalid json' }, 400);
|
|
|
|
const description = typeof body.description === 'string' ? body.description : '';
|
|
const resolved = await resolveWebhookGraph(body as Record<string, unknown>, description, c.env);
|
|
|
|
if (resolved.error) {
|
|
const statusCode = resolved.missingNodes ? 422 : 400;
|
|
return c.json(
|
|
{ error: resolved.error, ...(resolved.missingNodes && { missing: resolved.missingNodes }) },
|
|
statusCode,
|
|
);
|
|
}
|
|
|
|
const token = generateToken();
|
|
const record: WebhookRecord = {
|
|
graph: resolved.resolvedGraph,
|
|
description,
|
|
created_at: new Date().toISOString(),
|
|
};
|
|
|
|
await c.env.WEBHOOKS.put(token, JSON.stringify(record));
|
|
|
|
const baseUrl = new URL(c.req.url).origin;
|
|
return c.json({
|
|
token,
|
|
webhook_url: `${baseUrl}/webhooks/${token}/trigger`,
|
|
description: record.description,
|
|
created_at: record.created_at,
|
|
}, 201);
|
|
});
|
|
|
|
// POST /webhooks/:token/trigger — 觸發執行
|
|
webhooksRouter.post('/webhooks/:token/trigger', async (c) => {
|
|
const token = c.req.param('token');
|
|
if (!token || token.length < 16) {
|
|
return c.json({ error: 'invalid token' }, 400);
|
|
}
|
|
|
|
const raw = await c.env.WEBHOOKS.get(token, 'text');
|
|
if (!raw) return c.json({ error: 'webhook not found' }, 404);
|
|
|
|
const record = await validateAndParseWebhook(raw);
|
|
if (!record) return c.json({ error: 'webhook 定義損毀' }, 500);
|
|
|
|
let triggerContext: Record<string, unknown> = {};
|
|
try {
|
|
const body = await c.req.json().catch(() => null);
|
|
if (body && typeof body === 'object') {
|
|
triggerContext = body as Record<string, unknown>;
|
|
}
|
|
} catch {
|
|
// 無 body 時使用空 context
|
|
}
|
|
|
|
const result = await executeWebhookGraph(c.env, record.graph, triggerContext, token);
|
|
return c.json(result, result.success ? 200 : 500);
|
|
});
|