@agent-assistant/webhook-runtime

中文文档
0.4.37发布于 2026/10/04TypeScriptES Module构建来源已验证近 30 天下载 4604

Shared webhook runtime primitives for normalizing provider events, registering local or HTTP consumers, and fanning events out consistently.

原始使用文档
文档目录14 节
  1. A2A Discovery And RPC
  2. Persona contract (cf-runtime)
  3. Interactive CLI
  4. Commands
  5. Personas — swap SDK knobs live
  6. BYOH end-to-end locally
  7. Bash stub for testing without spending API tokens
  8. Worker flags
  9. What the CLI proves
  10. What the CLI does NOT prove by default
  11. Triage guide
  12. Running the test suite
  13. Using the primitives directly
  14. Consumer lifecycle

@agent-assistant/webhook-runtime

Shared webhook runtime primitives for normalizing provider events, registering local or HTTP consumers, and fanning events out consistently.

This package removes duplicated webhook plumbing that sage / nightcto / my-senior-dev all hand-roll today.

A2A Discovery And RPC

registerA2aRoutes adds a reusable public Agent-to-Agent discovery surface to any Hono app. It publishes /.well-known/agent-card.json, redirects the legacy /.well-known/agent.json path, and handles authenticated message/send JSON-RPC calls on /a2a/rpc.

Provide the agent name, public URL, version, skill descriptors, an auth token, and a dispatchSkill function. The helper validates the card with @relaycast/a2a, decodes data or JSON text message parts, verifies Bearer auth by default, and returns a standard A2A task/message pair. Use buildAgentAssistantA2aCard when you only need to generate the card JSON.

Persona contract (cf-runtime)

Every persona package that targets the Cloudflare runtime exports two functions per ingress surface:

// Ingress side: signature verify + parse + persona-side policy
// (rate-limit, thread-gate, etc.). Returns a turn descriptor that is safe to
// enqueue, or an "ack-only" sentinel.
export function parseSlackWebhook(
  req: Request,
  env: PersonaBindings,
): Promise<PersonaTurnDescriptor | PersonaAckResponse>;

// Consumer side: runs the harness work synchronously relative to the queue
// handler. Any internal ctx.waitUntil is for legitimately out-of-band telemetry
// only; the turn itself is awaited.
export function runPersonaTurn(
  descriptor: PersonaTurnDescriptor,
  env: PersonaBindings,
  ctx: ExecutionContext,
): Promise<void>;

Why this shape, matching cloud/workflows/cf-runtime/SPEC.md invariant 6:

  • Dedup is owned by the cf-runtime ingress wrapper, once per delivery. Personas must not re-implement Slack/GitHub event dedup. A descriptor that arrives at runPersonaTurn is deduped by construction. Use SlackEventDedupGate (re-exported here from @agent-assistant/surfaces) as the canonical primitive, with your own KV-backed SlackEventDedupStore.
  • Signature verification stays inside parseSlackWebhook because the persona owns the per-workspace signing-secret lookup.
  • runPersonaTurn must not orphan promises past return. The fake ExecutionContext provided by cf-runtime collects waitUntil promises and awaits them before the consumer returns, which fixes the Slack-silence bug.

The first concrete example is sage's W0 split: parseSlackWebhook plus runSageTurn shipped in @agentworkforce/[email protected]. New personas (nightcto, my-senior-dev) follow the same pattern.

Do not wire personas via registerSlackSpecialistConsumer from ./specialist-bridge in the cf-runtime architecture. That bridge runs the specialist call synchronously inside the webhook fanout, which is the orphaned-promise pattern the cf-runtime is designed to remove. Enqueue a specialist_call queue message and resume on a specialist_result:<turnId> trigger instead.

Interactive CLI

The fastest way to exercise the runtime end-to-end is the REPL at examples/cli.ts:

cd packages/webhook-runtime
npm install      # first run only
npm run cli      # boots runtime on http://127.0.0.1:3777

You will see:

webhook-runtime listening at http://127.0.0.1:3777
Registered consumers: echo (all slack), github-sim (app_mention)
Type 'help' for commands.

webhook>

Commands

commandeffect
helpprint the command list
urlprint the webhook URL
personaslist the persona catalog
use <id>register a persona as an active consumer
drop <id>unregister a consumer by id
consumerslist currently active personas
mention <text>POST an app_mention fixture to /webhooks/slack
slack <json>POST raw Slack JSON to /webhooks/slack
nango <json>wrap your JSON as {from:"slack", payload:<json>} and POST to /webhooks/nango
file <path>load JSON from disk and POST to /webhooks/slack
quit / exitshut down (SIGINT also works)

Every post prints the HTTP status, the fanout result (succeeded / failed / skipped ids), and any log lines emitted by active personas.

Override the port with PORT=4000 npm run cli. While the REPL is running you can also hit the same URL from another shell with curl.

Personas — swap SDK knobs live

The CLI ships with a catalog at examples/personas.ts. Each persona maps to a distinct choice an @agent-assistant/webhook-runtime consumer can make. echo and github-stub are registered on startup; the rest you opt into with use <id>.

personaknob exercised
echokind: "local" consumer with no predicate. Baseline — shows normalized event shape.
github-stubregisterSlackSpecialistConsumer with a stub specialistFactory returning a canned string. Shows specialist-bridge wiring without touching a real specialist.
github-realSame consumer but no factory override — falls through to the default dynamic import of @agent-assistant/specialists. Exercises the real SDK path.
byoh-relayFactory dynamic-imports @agent-assistant/harness and routes the event through createAgentRelayExecutionAdapter. Exercises the BYOH path.
http-forwardkind: "http" consumer that POSTs the normalized event to HTTP_FORWARD_URL. Useful for piping into webhook.site / ngrok / a local debug server.
failerFactory throws. Shows the failed[] fanout entry and [error] log line without crafting a broken payload.

Environment prerequisites for the non-default personas:

  • github-real — @agent-assistant/specialists built and its runtime deps resolvable.
  • byoh-relay — install @agent-assistant/harness (declared as an optional peer dep; the persona dynamic-imports @agent-assistant/harness/agent-relay). Needs a running Relay broker and a worker already registered on the channel (the adapter rejects sendMessage to unknown agent names with Agent "<name>" not found). Easiest path: run the bundled worker bridge in a separate terminal — see "BYOH end-to-end locally" below. Env vars: RELAY_CHANNEL, RELAY_WORKER, RELAY_AUTO_SPAWN, RELAY_CLI, RELAY_MODEL.
  • http-forward — HTTP_FORWARD_URL pointing at any JSON-accepting endpoint.

Example session:

webhook> personas
webhook> use failer
webhook> mention hi                # watch the fanout: github-stub succeeds, failer fails
webhook> drop failer
webhook> use http-forward          # HTTP_FORWARD_URL must be set before this
webhook> mention ping              # same event now fans out to the HTTP target too

BYOH end-to-end locally

The byoh-relay persona publishes an agent-assistant.execution-request.v1 message over a real Relay broker to a named worker that must be registered on the channel before the request arrives. The bundled bridge at examples/byoh-worker.ts is that worker — it listens, invokes a non-interactive CLI session (claude / codex / opencode / gemini), and emits a protocol-compliant agent-assistant.execution-result.v1 back.

Run it alongside the REPL in two terminals:

Terminal 1 — the worker:

# from the repo root
npm run worker -- --cli claude --model claude-sonnet-4-6

# or whichever CLI you have installed:
npm run worker -- --cli codex
npm run worker -- --cli opencode --model anthropic/claude-sonnet-4-5
npm run worker -- --cli gemini

You'll see:

[byoh-worker] registered as 'specialist-worker' on channel 'specialists' (cli=claude, cwd=/Users/you/...)
[byoh-worker] invoking 'claude' per request; timeout=120000ms

Terminal 2 — the webhook runtime:

# from the repo root
npm run agent

That starts the REPL with RELAY_AUTO_SPAWN=false (the worker you started in terminal 1 owns that responsibility) and env set for the specialists channel and specialist-worker name. Then at the prompt:

webhook> use byoh-relay
webhook> mention summarize the open github issues

The byoh-relay persona's createAgentRelayExecutionAdapter will publish the request to specialist-worker. The worker in terminal 1 receives it, invokes the configured CLI with the instruction as the prompt, captures stdout, and replies with the result. You'll see the final response logged back in terminal 2 as [byoh-relay] channel=C_CLI eventType=app_mention -> <CLI output>.

Bash stub for testing without spending API tokens

npm run worker -- --cli bash --cli-args 'read -d "" prompt; echo "stub echoed: ${prompt:0:40}"'

Useful for validating the transport without invoking any real AI. The same pattern drives the src/byoh-worker-bridge.test.ts smoke tests.

Worker flags

flagmeaningdefault
--cli <name>claude / codex / opencode / gemini / bashclaude
--cli-args <string>shell-split extra args, only meaningful for --cli bash(empty)
--channel <id>Relay channel to listen on$RELAY_CHANNEL or specialists
--worker-name <name>agent name registered with broker$RELAY_WORKER or specialist-worker
--cwd <path>broker cwd (for .agent-relay/connection.json discovery)process.cwd()
--model <id>model passed to the CLI$RELAY_MODEL or CLI default
--timeout-ms <n>per-invocation CLI subprocess timeout120000

What the CLI proves

Running the REPL exercises these code paths end-to-end:

  • HTTP ingestion — the Hono routes on /webhooks/slack and /webhooks/nango in src/http-runtime.ts, including 400 handling for malformed bodies.
  • Payload parsing and normalization — parseSlackEvent in src/slack-parser.ts. Lets you confirm what the detected eventType, workspaceId, channel, deliveryId, and data fields look like for a given raw Slack payload.
  • Nango envelope unwrapping — the /webhooks/nango route strips {from, connectionId, payload} and re-normalizes the inner Slack event.
  • Registry fanout semantics — webhook-registry.ts: predicate matching, error isolation across consumers, and the total / succeeded / failed / skipped counts returned to the HTTP caller.
  • Specialist-bridge wiring shape — registerSlackSpecialistConsumer constructs a consumer that builds an instruction from the event, calls specialistFactory, invokes handler.execute, and hands the result to egress.

In other words, if your incident is "events aren't reaching the right consumer" or "my payload normalizes wrong", the CLI is a complete reproduction.

What the CLI does NOT prove by default

With only the default personas registered (echo + github-stub), the REPL does not validate:

  • That the real specialist from @agent-assistant/specialists handles the event correctly (the stub factory never touches it). → use github-real.
  • That the BYOH / Relay execution adapter routes events to a worker and returns a real ExecutionResult. → use byoh-relay.
  • That external egress — posting back into Slack, writing to a DB, notifying a queue — works. github-stub's egress just logs. → use http-forward.
  • Slack request-signature verification. There is no signing middleware wired into the HTTP runtime today. No persona covers this.
  • Any provider other than Slack. /webhooks/nango only accepts from: slack today.

Each persona above is an opt-in probe. The first three bullets are fully addressable by swapping personas; the last two still require code changes.

Triage guide

When triaging an issue, reach for the CLI first and use the output to localize:

symptomlikely causewhere to look
POST returns [400] {"error":"..."}payload did not parsesrc/slack-parser.ts
[200] but your consumer is in skippedpredicate did not matchpredicate passed to registerSlackSpecialistConsumer / registry.register
[200] but your consumer is in failedhandler threwstack logged via [error] — inspect the area field for the source module
normalized event.data missing fields you expectedSlack event shape driftsrc/slack-parser.ts — extend the normalizer
/webhooks/nango returns Unsupported Nango providerenvelope from was not slacksrc/http-runtime.ts validateNangoSlackEnvelope
succeeded empty when you expected a hitconsumer not registered, or registered on the wrong providerregistry.register(...) call site

Each of these maps to a file you can edit and re-test in the same REPL — there is no build step between iterations because the CLI runs via tsx.

Running the test suite

npm test                       # all suites
npm test -- slack-parser       # filter to one file
npm test -- --reporter=verbose

Four suites cover the primitives directly: webhook-registry, slack-parser, http-runtime, specialist-bridge.

Using the primitives directly

If you want to embed the runtime in your own process instead of using the CLI, the shape looks like:

import {
  createWebhookRegistry,
  parseSlackEvent,
  startHttpRuntime,
} from "@agent-assistant/webhook-runtime";

const registry = createWebhookRegistry();
registry.register({
  id: "my-consumer",
  kind: "local",
  provider: "slack",
  handler: (event) => console.log(event.eventType, event.data),
});

const runtime = startHttpRuntime({ registry, port: 3777 });
console.log(`listening at ${runtime.url}`);

Consumer lifecycle

WebhookRegistry keeps registered consumer ids in a long-lived in-memory map. Register stable process-level consumers once at startup. If a caller creates consumer ids dynamically, it must call unregister(id) when that consumer is no longer needed or clear() during teardown to release the entries.

版本与发布记录

@agent-assistant/webhook-runtime 共发布 46 个版本,首次发布于 2026/04/23;过去 12 个月发布了 46 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.4.37latest2026/10/041 个月前
0.4.362026/10/041 个月前
0.4.352026/05/235 个月前
0.4.332026/05/155 个月前
0.4.322026/05/125 个月前
0.4.312026/05/086 个月前

关于 @agent-assistant/webhook-runtime 的常见问题

@agent-assistant/webhook-runtime 是什么?

Shared webhook runtime primitives for normalizing provider events, registering local or HTTP consumers, and fanning events out consistently.

如何安装 @agent-assistant/webhook-runtime?

在项目目录运行 npm install @agent-assistant/webhook-runtime,或使用 pnpm add / yarn add / bun add。

@agent-assistant/webhook-runtime 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@agent-assistant/webhook-runtime 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@agent-assistant/webhook-runtime 可以商用吗?

@agent-assistant/webhook-runtime 使用 未声明 许可。package.json 没有声明许可证。没有许可证的代码默认保留全部权利,商用前请联系作者或查看仓库中的 LICENSE 文件。以上为概括,不构成法律意见,请以许可原文为准。

@agent-assistant/webhook-runtime 的下载量有多少?

根据 npm 官方下载统计,@agent-assistant/webhook-runtime 在 2026/10/05 – 2026/11/03 这 30 天内被下载 4,604 次,日均约 153 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@agent-assistant/webhook-runtime 还在维护吗?

最新版本 0.4.37 发布于 2026/10/04(1 个月前);至今共发布 46 个版本,其中过去 12 个月发布了 46 个;首次发布于 2026/04/23。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。