aiwg
Version:
Deployment tool and support utility for AI context. Copies agents, skills, commands, rules, and behaviors into the paths each AI platform reads (Claude Code, Codex, Copilot, Cursor, Warp, OpenClaw, and 6 more) so one source of truth works across 10 platfo
120 lines • 4.96 kB
TypeScript
/** Header name (case-insensitive). */
export declare const SIGNATURE_HEADER = "x-aiwg-signature";
/** Five minutes — RFC 8941 timestamp tolerance. */
export declare const DEFAULT_TIMESTAMP_TOLERANCE_SECONDS = 300;
/** Idempotency cache size. Old entries are evicted FIFO. */
export declare const DEFAULT_IDEMPOTENCY_CAPACITY = 4096;
export interface VerifyResult {
ok: boolean;
/** When `ok=false`, a stable machine code suitable for ProblemDetails. */
code?: 'signature_missing' | 'signature_malformed' | 'signature_mismatch' | 'timestamp_skew' | 'secret_unknown';
/** Human-readable detail. */
detail?: string;
/** Parsed timestamp on success (epoch seconds). */
timestamp?: number;
}
export interface VerifyOptions {
/**
* Function to look up the per-config secret given the `configId` query
* parameter on the webhook URL. Returning `null` causes a
* `secret_unknown` failure.
*
* Implementations should fetch from a per-mission store populated when
* createPushNotificationConfig is called.
*/
lookupSecret: (configId: string) => string | null | Promise<string | null>;
/** Skew tolerance in seconds. Defaults to 5 minutes. */
toleranceSeconds?: number;
/** Clock override for testing. */
now?: () => number;
}
/**
* Verify the `X-AIWG-Signature` header against the raw request body.
*
* Stripe-style format:
* `X-AIWG-Signature: t=1700000000,v1=<hex hmac-sha256>`
*
* The HMAC is computed over `t=<timestamp>.<raw body>` (the timestamp and
* a literal dot, prepended to the body). Multiple `v1=` entries may be
* present in a single header (during a key rotation); any match accepts.
*/
export declare function verifyWebhookSignature(signature: string | undefined, body: Buffer, configId: string, opts: VerifyOptions): Promise<VerifyResult>;
/** Parse `t=...,v1=...,(v1=...,)*` into structured fields. */
export declare function parseSignatureHeader(header: string): {
timestamp: number;
v1: string[];
} | null;
/**
* Bounded FIFO event-id deduper. The first call with a given id returns
* true; subsequent calls return false until the id falls out of the
* window.
*
* Subscribers MUST dedupe per the spec — the executor's delivery worker
* retries on non-2xx responses, so a flaky downstream handler will
* receive the same event-id multiple times.
*/
export declare class IdempotencyCache {
private readonly capacity;
private readonly seen;
private readonly order;
constructor(capacity?: number);
/** Returns true if `id` is new (and stores it); false if it's a duplicate. */
markFresh(id: string): boolean;
size(): number;
}
/**
* Maps `configId` to its symmetric HMAC secret + optional mission
* routing metadata. Populated when the AIWG client calls
* createPushNotificationConfig on mission start; torn down on mission
* complete.
*
* Threading model: single-process in-memory map. For multi-replica
* deployments swap in a shared backing store (Redis, etc.) with the
* same interface.
*/
export interface PushSecretRegistryEntry {
configId: string;
secret: string;
/** Mission this config belongs to — used for routing webhook payloads. */
missionId?: string;
/** Task this config belongs to — used to scope StreamEvent application. */
taskId?: string;
/** Free-form metadata returned alongside the entry on lookup. */
metadata?: Record<string, unknown>;
}
export declare class PushSecretRegistry {
private readonly entries;
register(entry: PushSecretRegistryEntry): void;
lookup(configId: string): PushSecretRegistryEntry | null;
unregister(configId: string): boolean;
/** Test/debug helper. */
size(): number;
}
export interface WebhookHandlerOptions {
registry: PushSecretRegistry;
idempotency: IdempotencyCache;
/**
* Route the verified, deduplicated StreamEvent into the mission state
* machine. Implementations typically forward to the same event handler
* SSE uses.
*/
route: (entry: PushSecretRegistryEntry, event: unknown) => void | Promise<void>;
toleranceSeconds?: number;
now?: () => number;
}
export interface HandleResult {
status: number;
/** ProblemDetails-shape body for non-2xx; minimal `{ ok: true }` for 2xx. */
body: Record<string, unknown>;
}
/**
* One-shot processor: takes a raw webhook request (configId + body +
* signature header + event-id header), verifies, dedupes, routes.
*
* Returns the status code and body the HTTP layer should emit. The HTTP
* adapter (serve.ts) is responsible for reading the raw body bytes
* BEFORE any JSON parsing — the signature is computed over raw bytes
* (whitespace-sensitive).
*/
export declare function handleWebhook(configId: string, body: Buffer, signature: string | undefined, eventId: string | undefined, opts: WebhookHandlerOptions): Promise<HandleResult>;
//# sourceMappingURL=webhook.d.ts.map