Files
Arcrun/cypher-executor/src/routes/webhooks.ts
T
Claude 2707fca32b feat(arcrun): implement arcrun MVP — open-source AI workflow engine
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
2026-04-16 04:06:25 +00:00

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);
});