robotrock 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,53 +1,104 @@
1
- import { R as RobotRockConfig, a as RobotRock, C as CreateTaskOptions } from './client-BQ-j7q68.js';
2
- export { b as RobotRockError, c as createClient } from './client-BQ-j7q68.js';
3
- import { TaskAction, TaskContextInput, DiscriminatedApprovalResult, Task } from '@robotrock/core';
4
- export { ApprovalResult, DiscriminatedApprovalResult, Handler, InferActionData, Task, TaskAction, TaskContext, TaskContextInput, TaskResponse, TaskResult, TaskStatus, TriggerHandler, TupleElementIndices, WebhookHandler, taskContextSchema } from '@robotrock/core';
1
+ import { R as RobotRockConfig, b as RobotRock } from './client-Dhk9qxhL.js';
2
+ export { c as RobotRockError, g as RobotRockPollingClientConfig, i as RobotRockPollingOptions, f as RobotRockWebhookClientConfig, h as RobotRockWebhookConfig, S as SendToHumanActionInput, a as SendToHumanInput, k as SendToHumanResult, j as SendToHumanValidUntil, d as attachWebhookToActions, e as createClient } from './client-Dhk9qxhL.js';
3
+ import { Task, DiscriminatedApprovalResult } from './schemas/index.js';
4
+ export { ApprovalResult, AssignToInput, CreateTaskBody, CreateTaskBodyInput, Handler, InferActionData, TaskAction, TaskContext, TaskContextInput, TaskResponse, TaskResult, TaskStatus, TriggerHandler, TupleElementIndices, WebhookHandler, assignToSchema, createTaskBodySchema, taskContextSchema } from './schemas/index.js';
5
+ import { z } from 'zod';
6
+ export { R as RobotRockHandlerWebhookPayload } from './handler-webhook-BqEi6Bk-.js';
5
7
 
6
8
  /**
7
9
  * Read RobotRock client config from environment variables.
8
10
  *
9
11
  * - `ROBOTROCK_API_KEY` (required when not passed explicitly)
10
12
  * - `ROBOTROCK_BASE_URL` or `ROBOTROCK_API_URL` (optional)
13
+ * - `ROBOTROCK_APP` (optional inbox app bucket)
11
14
  */
12
15
  declare function resolveRobotRockConfig(overrides?: Partial<RobotRockConfig>): RobotRockConfig;
13
16
  /** Use an explicit client or create one from env / optional config overrides. */
14
17
  declare function resolveRobotRockClient(client?: RobotRock, configOverrides?: Partial<RobotRockConfig>): RobotRock;
15
18
 
16
- /** Task payload for {@link askHuman}; `actions` must stay a literal tuple for inference. */
17
- type AskHumanTask<A extends readonly TaskAction[]> = Omit<TaskContextInput, "actions"> & {
18
- readonly actions: A;
19
- };
20
- type AskHumanParams<A extends readonly TaskAction[]> = {
21
- task: AskHumanTask<A>;
22
- /** Pre-configured client; when omitted, one is created from env / `apiKey` / `baseUrl`. */
23
- client?: RobotRock;
24
- apiKey?: string;
25
- baseUrl?: string;
26
- /** Poll interval while waiting for a human (ms). @default 2000 */
27
- pollInterval?: number;
28
- /** Max wait time (ms). @default 86400000 (24h) */
29
- timeout?: number;
30
- } & Pick<CreateTaskOptions, "idempotencyKey">;
31
- /**
32
- * Create a human-in-the-loop task and block until someone completes it.
33
- *
34
- * Uses polling against the RobotRock API. For durable waits inside Trigger.dev,
35
- * use `robotrock/trigger` instead.
36
- *
37
- * The return type is inferred from `task.actions`: `actionId` narrows `data`.
38
- */
39
- declare function askHuman<const A extends readonly TaskAction[]>(params: AskHumanParams<A>): Promise<DiscriminatedApprovalResult<A>>;
40
-
41
- declare class AskHumanTimeoutError extends Error {
19
+ declare class TaskTimeoutError extends Error {
42
20
  constructor(message: string);
43
21
  }
44
- declare class AskHumanExpiredError extends Error {
22
+ declare class TaskExpiredError extends Error {
45
23
  constructor(message: string);
46
24
  }
47
25
  /**
48
26
  * Map a handled API task to a discriminated approval result.
49
27
  * Runtime validation is minimal; TypeScript narrows via `task.actions` at the call site.
50
28
  */
51
- declare function toDiscriminatedApprovalResult<A extends readonly TaskAction[]>(actions: A, task: Task, streamEntryId: string): DiscriminatedApprovalResult<A>;
29
+ declare function toDiscriminatedApprovalResult<A extends readonly {
30
+ id: string;
31
+ schema?: unknown;
32
+ }[]>(actions: A, task: Task): DiscriminatedApprovalResult<A>;
33
+
34
+ declare const robotRockWebhookPayloadSchema: z.ZodObject<{
35
+ taskId: z.ZodString;
36
+ action: z.ZodObject<{
37
+ id: z.ZodString;
38
+ title: z.ZodString;
39
+ data: z.ZodUnknown;
40
+ }, "strip", z.ZodTypeAny, {
41
+ id: string;
42
+ title: string;
43
+ data?: unknown;
44
+ }, {
45
+ id: string;
46
+ title: string;
47
+ data?: unknown;
48
+ }>;
49
+ handledBy: z.ZodOptional<z.ZodString>;
50
+ handledAt: z.ZodString;
51
+ handlerType: z.ZodString;
52
+ } & {
53
+ headers: z.ZodRecord<z.ZodString, z.ZodString>;
54
+ }, "strip", z.ZodTypeAny, {
55
+ headers: Record<string, string>;
56
+ taskId: string;
57
+ handledAt: string;
58
+ action: {
59
+ id: string;
60
+ title: string;
61
+ data?: unknown;
62
+ };
63
+ handlerType: string;
64
+ handledBy?: string | undefined;
65
+ }, {
66
+ headers: Record<string, string>;
67
+ taskId: string;
68
+ handledAt: string;
69
+ action: {
70
+ id: string;
71
+ title: string;
72
+ data?: unknown;
73
+ };
74
+ handlerType: string;
75
+ handledBy?: string | undefined;
76
+ }>;
77
+ type RobotRockWebhookErrorCode = "MISSING_WEBHOOK_SECRET" | "MISSING_SIGNATURE" | "INVALID_SIGNATURE" | "INVALID_JSON" | "INVALID_PAYLOAD";
78
+ declare class RobotRockWebhookError extends Error {
79
+ readonly code: RobotRockWebhookErrorCode;
80
+ readonly details?: unknown | undefined;
81
+ constructor(message: string, code: RobotRockWebhookErrorCode, details?: unknown | undefined);
82
+ }
83
+ type RobotRockWebhookPayload = z.infer<typeof robotRockWebhookPayloadSchema>;
84
+ interface VerifyRobotRockWebhookOptions {
85
+ /**
86
+ * Override shared secret (defaults to ROBOTROCK_WEBHOOK_SECRET).
87
+ * Keep undefined in production to enforce the canonical env var.
88
+ */
89
+ secret?: string;
90
+ /**
91
+ * Resolve the tenant signing secret by public task id (hosted MCP uses Convex).
92
+ * Used when secret is not passed explicitly.
93
+ */
94
+ resolveSecret?: (taskId: string) => Promise<string | undefined>;
95
+ /** Signature header to read. @default "x-robotrock-signature" */
96
+ signatureHeader?: string;
97
+ }
98
+ /**
99
+ * Verify a RobotRock webhook request and return a validated payload.
100
+ * Throws RobotRockWebhookError with machine-readable `code` for audit logging.
101
+ */
102
+ declare function verifyRobotRockWebhook(request: Request, options?: VerifyRobotRockWebhookOptions): Promise<RobotRockWebhookPayload>;
52
103
 
53
- export { AskHumanExpiredError, type AskHumanParams, type AskHumanTask, AskHumanTimeoutError, CreateTaskOptions, RobotRock, RobotRockConfig, askHuman, resolveRobotRockClient, resolveRobotRockConfig, toDiscriminatedApprovalResult };
104
+ export { DiscriminatedApprovalResult, RobotRock, RobotRockConfig, RobotRockWebhookError, type RobotRockWebhookErrorCode, type RobotRockWebhookPayload, Task, TaskExpiredError, TaskTimeoutError, type VerifyRobotRockWebhookOptions, resolveRobotRockClient, resolveRobotRockConfig, toDiscriminatedApprovalResult, verifyRobotRockWebhook };
package/dist/index.js CHANGED
@@ -1,60 +1,139 @@
1
1
  import {
2
- AskHumanExpiredError,
3
- AskHumanTimeoutError,
4
2
  RobotRock,
5
3
  RobotRockError,
4
+ TaskExpiredError,
5
+ TaskTimeoutError,
6
+ attachWebhookToActions,
6
7
  createClient,
7
8
  resolveRobotRockClient,
8
9
  resolveRobotRockConfig,
9
- taskContextSchema,
10
10
  toDiscriminatedApprovalResult
11
- } from "./chunk-TUQXDKV6.js";
11
+ } from "./chunk-KOXJCIST.js";
12
+ import {
13
+ assignToSchema,
14
+ createTaskBodySchema,
15
+ taskContextSchema
16
+ } from "./chunk-LXM7VS4Q.js";
12
17
 
13
- // src/ask-human.ts
14
- var DEFAULT_POLL_INTERVAL_MS = 2e3;
15
- var DEFAULT_TIMEOUT_MS = 24 * 60 * 60 * 1e3;
16
- function sleep(ms) {
17
- return new Promise((resolve) => setTimeout(resolve, ms));
18
+ // src/webhook.ts
19
+ import { createHmac, timingSafeEqual } from "crypto";
20
+ import { z } from "zod";
21
+ var ROBOTROCK_SIGNATURE_HEADER = "x-robotrock-signature";
22
+ var robotRockWebhookPayloadBodySchema = z.object({
23
+ taskId: z.string().min(1),
24
+ action: z.object({
25
+ id: z.string().min(1),
26
+ title: z.string().min(1),
27
+ data: z.unknown()
28
+ }),
29
+ handledBy: z.string().min(1).optional(),
30
+ handledAt: z.string().min(1),
31
+ handlerType: z.string().min(1)
32
+ });
33
+ var robotRockWebhookPayloadSchema = robotRockWebhookPayloadBodySchema.extend({
34
+ headers: z.record(z.string())
35
+ });
36
+ var RobotRockWebhookError = class extends Error {
37
+ constructor(message, code, details) {
38
+ super(message);
39
+ this.code = code;
40
+ this.details = details;
41
+ this.name = "RobotRockWebhookError";
42
+ }
43
+ };
44
+ async function verifyRobotRockWebhook(request, options = {}) {
45
+ const signatureHeaderName = options.signatureHeader ?? ROBOTROCK_SIGNATURE_HEADER;
46
+ const signature = request.headers.get(signatureHeaderName);
47
+ if (!signature) {
48
+ throw new RobotRockWebhookError(
49
+ `Missing webhook signature header: ${signatureHeaderName}`,
50
+ "MISSING_SIGNATURE"
51
+ );
52
+ }
53
+ const rawBody = await request.text();
54
+ const secret = await resolveWebhookSigningSecret(rawBody, options);
55
+ if (!secret) {
56
+ throw new RobotRockWebhookError(
57
+ "Missing webhook signing secret for verification",
58
+ "MISSING_WEBHOOK_SECRET"
59
+ );
60
+ }
61
+ assertValidSignature(rawBody, signature, secret);
62
+ let parsedBody;
63
+ try {
64
+ parsedBody = JSON.parse(rawBody);
65
+ } catch (error) {
66
+ throw new RobotRockWebhookError("Webhook body is not valid JSON", "INVALID_JSON", {
67
+ cause: error instanceof Error ? error.message : String(error)
68
+ });
69
+ }
70
+ const payloadResult = robotRockWebhookPayloadBodySchema.safeParse(parsedBody);
71
+ if (!payloadResult.success) {
72
+ throw new RobotRockWebhookError(
73
+ "Webhook payload schema validation failed",
74
+ "INVALID_PAYLOAD",
75
+ payloadResult.error.flatten()
76
+ );
77
+ }
78
+ return {
79
+ ...payloadResult.data,
80
+ headers: normalizeHeaders(request.headers)
81
+ };
18
82
  }
19
- async function askHuman(params) {
20
- const {
21
- task,
22
- client: explicitClient,
23
- apiKey,
24
- baseUrl,
25
- pollInterval = DEFAULT_POLL_INTERVAL_MS,
26
- timeout = DEFAULT_TIMEOUT_MS,
27
- idempotencyKey
28
- } = params;
29
- const client = resolveRobotRockClient(explicitClient, { apiKey, baseUrl });
30
- const response = await client.createTask(
31
- task,
32
- { idempotencyKey }
33
- );
34
- const streamId = response.task.streamId;
35
- const deadline = Date.now() + timeout;
36
- while (Date.now() < deadline) {
37
- const existing = await client.getTask(streamId);
38
- if (existing?.status === "handled" && existing.handled) {
39
- return toDiscriminatedApprovalResult(task.actions, existing, streamId);
83
+ async function resolveWebhookSigningSecret(rawBody, options) {
84
+ if (options.secret) {
85
+ return options.secret;
86
+ }
87
+ if (options.resolveSecret) {
88
+ const taskId = peekWebhookTaskId(rawBody);
89
+ if (taskId) {
90
+ const resolved = await options.resolveSecret(taskId);
91
+ if (resolved) {
92
+ return resolved;
93
+ }
40
94
  }
41
- if (existing?.status === "expired") {
42
- throw new AskHumanExpiredError("Task expired before a human completed it");
95
+ }
96
+ return process.env.ROBOTROCK_WEBHOOK_SECRET;
97
+ }
98
+ function peekWebhookTaskId(rawBody) {
99
+ try {
100
+ const parsed = JSON.parse(rawBody);
101
+ if (typeof parsed === "object" && parsed !== null && "taskId" in parsed && typeof parsed.taskId === "string") {
102
+ return parsed.taskId;
43
103
  }
44
- await sleep(pollInterval);
104
+ } catch {
45
105
  }
46
- throw new AskHumanTimeoutError(`No human response within ${timeout}ms`);
106
+ return void 0;
107
+ }
108
+ function assertValidSignature(rawBody, signature, secret) {
109
+ const expected = `sha256=${createHmac("sha256", secret).update(rawBody).digest("hex")}`;
110
+ const expectedBuffer = Buffer.from(expected);
111
+ const receivedBuffer = Buffer.from(signature);
112
+ if (expectedBuffer.length !== receivedBuffer.length || !timingSafeEqual(expectedBuffer, receivedBuffer)) {
113
+ throw new RobotRockWebhookError("Webhook signature verification failed", "INVALID_SIGNATURE");
114
+ }
115
+ }
116
+ function normalizeHeaders(headers) {
117
+ const result = {};
118
+ headers.forEach((value, key) => {
119
+ result[key] = value;
120
+ });
121
+ return result;
47
122
  }
48
123
  export {
49
- AskHumanExpiredError,
50
- AskHumanTimeoutError,
51
124
  RobotRock,
52
125
  RobotRockError,
53
- askHuman,
126
+ RobotRockWebhookError,
127
+ TaskExpiredError,
128
+ TaskTimeoutError,
129
+ assignToSchema,
130
+ attachWebhookToActions,
54
131
  createClient,
132
+ createTaskBodySchema,
55
133
  resolveRobotRockClient,
56
134
  resolveRobotRockConfig,
57
135
  taskContextSchema,
58
- toDiscriminatedApprovalResult
136
+ toDiscriminatedApprovalResult,
137
+ verifyRobotRockWebhook
59
138
  };
60
139
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/ask-human.ts"],"sourcesContent":["import {\n AskHumanExpiredError,\n AskHumanTimeoutError,\n toDiscriminatedApprovalResult,\n} from \"./approval-result.js\";\nimport type { RobotRock, RobotRockConfig, CreateTaskOptions } from \"./client.js\";\nimport { resolveRobotRockClient } from \"./env.js\";\nimport type {\n DiscriminatedApprovalResult,\n TaskAction,\n TaskContextInput,\n} from \"@robotrock/core\";\n\nconst DEFAULT_POLL_INTERVAL_MS = 2_000;\nconst DEFAULT_TIMEOUT_MS = 24 * 60 * 60 * 1_000;\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/** Task payload for {@link askHuman}; `actions` must stay a literal tuple for inference. */\nexport type AskHumanTask<A extends readonly TaskAction[]> = Omit<TaskContextInput, \"actions\"> & {\n readonly actions: A;\n};\n\nexport type AskHumanParams<A extends readonly TaskAction[]> = {\n task: AskHumanTask<A>;\n /** Pre-configured client; when omitted, one is created from env / `apiKey` / `baseUrl`. */\n client?: RobotRock;\n apiKey?: string;\n baseUrl?: string;\n /** Poll interval while waiting for a human (ms). @default 2000 */\n pollInterval?: number;\n /** Max wait time (ms). @default 86400000 (24h) */\n timeout?: number;\n} & Pick<CreateTaskOptions, \"idempotencyKey\">;\n\n/**\n * Create a human-in-the-loop task and block until someone completes it.\n *\n * Uses polling against the RobotRock API. For durable waits inside Trigger.dev,\n * use `robotrock/trigger` instead.\n *\n * The return type is inferred from `task.actions`: `actionId` narrows `data`.\n */\nexport async function askHuman<const A extends readonly TaskAction[]>(\n params: AskHumanParams<A>\n): Promise<DiscriminatedApprovalResult<A>> {\n const {\n task,\n client: explicitClient,\n apiKey,\n baseUrl,\n pollInterval = DEFAULT_POLL_INTERVAL_MS,\n timeout = DEFAULT_TIMEOUT_MS,\n idempotencyKey,\n } = params;\n\n const client = resolveRobotRockClient(explicitClient, { apiKey, baseUrl });\n const response = await client.createTask(\n task as unknown as TaskContextInput,\n { idempotencyKey }\n );\n const streamId = response.task.streamId;\n const deadline = Date.now() + timeout;\n\n while (Date.now() < deadline) {\n const existing = await client.getTask(streamId);\n\n if (existing?.status === \"handled\" && existing.handled) {\n return toDiscriminatedApprovalResult(task.actions, existing, streamId);\n }\n\n if (existing?.status === \"expired\") {\n throw new AskHumanExpiredError(\"Task expired before a human completed it\");\n }\n\n await sleep(pollInterval);\n }\n\n throw new AskHumanTimeoutError(`No human response within ${timeout}ms`);\n}\n\nexport type { RobotRockConfig };\n"],"mappings":";;;;;;;;;;;;;AAaA,IAAM,2BAA2B;AACjC,IAAM,qBAAqB,KAAK,KAAK,KAAK;AAE1C,SAAS,MAAM,IAA2B;AACxC,SAAO,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,EAAE,CAAC;AACzD;AA2BA,eAAsB,SACpB,QACyC;AACzC,QAAM;AAAA,IACJ;AAAA,IACA,QAAQ;AAAA,IACR;AAAA,IACA;AAAA,IACA,eAAe;AAAA,IACf,UAAU;AAAA,IACV;AAAA,EACF,IAAI;AAEJ,QAAM,SAAS,uBAAuB,gBAAgB,EAAE,QAAQ,QAAQ,CAAC;AACzE,QAAM,WAAW,MAAM,OAAO;AAAA,IAC5B;AAAA,IACA,EAAE,eAAe;AAAA,EACnB;AACA,QAAM,WAAW,SAAS,KAAK;AAC/B,QAAM,WAAW,KAAK,IAAI,IAAI;AAE9B,SAAO,KAAK,IAAI,IAAI,UAAU;AAC5B,UAAM,WAAW,MAAM,OAAO,QAAQ,QAAQ;AAE9C,QAAI,UAAU,WAAW,aAAa,SAAS,SAAS;AACtD,aAAO,8BAA8B,KAAK,SAAS,UAAU,QAAQ;AAAA,IACvE;AAEA,QAAI,UAAU,WAAW,WAAW;AAClC,YAAM,IAAI,qBAAqB,0CAA0C;AAAA,IAC3E;AAEA,UAAM,MAAM,YAAY;AAAA,EAC1B;AAEA,QAAM,IAAI,qBAAqB,4BAA4B,OAAO,IAAI;AACxE;","names":[]}
1
+ {"version":3,"sources":["../src/webhook.ts"],"sourcesContent":["import { createHmac, timingSafeEqual } from \"node:crypto\";\nimport { z } from \"zod\";\n\nconst ROBOTROCK_SIGNATURE_HEADER = \"x-robotrock-signature\";\n\nconst robotRockWebhookPayloadBodySchema = z.object({\n taskId: z.string().min(1),\n action: z.object({\n id: z.string().min(1),\n title: z.string().min(1),\n data: z.unknown(),\n }),\n handledBy: z.string().min(1).optional(),\n handledAt: z.string().min(1),\n handlerType: z.string().min(1),\n});\n\nconst robotRockWebhookPayloadSchema = robotRockWebhookPayloadBodySchema.extend({\n headers: z.record(z.string()),\n});\n\nexport type RobotRockWebhookErrorCode =\n | \"MISSING_WEBHOOK_SECRET\"\n | \"MISSING_SIGNATURE\"\n | \"INVALID_SIGNATURE\"\n | \"INVALID_JSON\"\n | \"INVALID_PAYLOAD\";\n\nexport class RobotRockWebhookError extends Error {\n constructor(\n message: string,\n public readonly code: RobotRockWebhookErrorCode,\n public readonly details?: unknown\n ) {\n super(message);\n this.name = \"RobotRockWebhookError\";\n }\n}\n\nexport type RobotRockWebhookPayload = z.infer<typeof robotRockWebhookPayloadSchema>;\n\nexport interface VerifyRobotRockWebhookOptions {\n /**\n * Override shared secret (defaults to ROBOTROCK_WEBHOOK_SECRET).\n * Keep undefined in production to enforce the canonical env var.\n */\n secret?: string;\n /**\n * Resolve the tenant signing secret by public task id (hosted MCP uses Convex).\n * Used when secret is not passed explicitly.\n */\n resolveSecret?: (taskId: string) => Promise<string | undefined>;\n /** Signature header to read. @default \"x-robotrock-signature\" */\n signatureHeader?: string;\n}\n\n/**\n * Verify a RobotRock webhook request and return a validated payload.\n * Throws RobotRockWebhookError with machine-readable `code` for audit logging.\n */\nexport async function verifyRobotRockWebhook(\n request: Request,\n options: VerifyRobotRockWebhookOptions = {}\n): Promise<RobotRockWebhookPayload> {\n const signatureHeaderName = options.signatureHeader ?? ROBOTROCK_SIGNATURE_HEADER;\n const signature = request.headers.get(signatureHeaderName);\n\n if (!signature) {\n throw new RobotRockWebhookError(\n `Missing webhook signature header: ${signatureHeaderName}`,\n \"MISSING_SIGNATURE\"\n );\n }\n\n const rawBody = await request.text();\n const secret = await resolveWebhookSigningSecret(rawBody, options);\n\n if (!secret) {\n throw new RobotRockWebhookError(\n \"Missing webhook signing secret for verification\",\n \"MISSING_WEBHOOK_SECRET\"\n );\n }\n\n assertValidSignature(rawBody, signature, secret);\n\n let parsedBody: unknown;\n try {\n parsedBody = JSON.parse(rawBody);\n } catch (error) {\n throw new RobotRockWebhookError(\"Webhook body is not valid JSON\", \"INVALID_JSON\", {\n cause: error instanceof Error ? error.message : String(error),\n });\n }\n\n const payloadResult = robotRockWebhookPayloadBodySchema.safeParse(parsedBody);\n if (!payloadResult.success) {\n throw new RobotRockWebhookError(\n \"Webhook payload schema validation failed\",\n \"INVALID_PAYLOAD\",\n payloadResult.error.flatten()\n );\n }\n\n return {\n ...payloadResult.data,\n headers: normalizeHeaders(request.headers),\n };\n}\n\nasync function resolveWebhookSigningSecret(\n rawBody: string,\n options: VerifyRobotRockWebhookOptions\n): Promise<string | undefined> {\n if (options.secret) {\n return options.secret;\n }\n\n if (options.resolveSecret) {\n const taskId = peekWebhookTaskId(rawBody);\n if (taskId) {\n const resolved = await options.resolveSecret(taskId);\n if (resolved) {\n return resolved;\n }\n }\n }\n\n return process.env.ROBOTROCK_WEBHOOK_SECRET;\n}\n\nfunction peekWebhookTaskId(rawBody: string): string | undefined {\n try {\n const parsed: unknown = JSON.parse(rawBody);\n if (\n typeof parsed === \"object\" &&\n parsed !== null &&\n \"taskId\" in parsed &&\n typeof (parsed as { taskId: unknown }).taskId === \"string\"\n ) {\n return (parsed as { taskId: string }).taskId;\n }\n } catch {\n // fall through\n }\n return undefined;\n}\n\nfunction assertValidSignature(rawBody: string, signature: string, secret: string): void {\n const expected = `sha256=${createHmac(\"sha256\", secret).update(rawBody).digest(\"hex\")}`;\n const expectedBuffer = Buffer.from(expected);\n const receivedBuffer = Buffer.from(signature);\n\n if (\n expectedBuffer.length !== receivedBuffer.length ||\n !timingSafeEqual(expectedBuffer, receivedBuffer)\n ) {\n throw new RobotRockWebhookError(\"Webhook signature verification failed\", \"INVALID_SIGNATURE\");\n }\n}\n\nfunction normalizeHeaders(headers: Headers): Record<string, string> {\n const result: Record<string, string> = {};\n headers.forEach((value, key) => {\n result[key] = value;\n });\n return result;\n}\n"],"mappings":"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","names":[]}