robotrock 0.1.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/README.md ADDED
@@ -0,0 +1,87 @@
1
+ # robotrock
2
+
3
+ Human-in-the-loop approval workflows for AI agents.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install robotrock
9
+ # or
10
+ bun add robotrock
11
+ ```
12
+
13
+ For Trigger.dev, also install `@trigger.dev/sdk`.
14
+
15
+ ## Quick Start
16
+
17
+ ```typescript
18
+ import { RobotRock, askHuman } from "robotrock";
19
+
20
+ // Or create a client explicitly
21
+ const client = new RobotRock({ apiKey: process.env.ROBOTROCK_API_KEY! });
22
+
23
+ const response = await client.createTask({
24
+ type: "budget-approval",
25
+ name: "Q1 Budget Approval",
26
+ actions: [
27
+ { id: "approve", title: "Approve" },
28
+ { id: "reject", title: "Reject" },
29
+ ],
30
+ });
31
+ ```
32
+
33
+ ## Block until a human responds
34
+
35
+ ```typescript
36
+ import { askHuman } from "robotrock";
37
+
38
+ const result = await askHuman({
39
+ task: {
40
+ type: "approval",
41
+ name: "Review change",
42
+ actions: [
43
+ { id: "approve", title: "Approve" },
44
+ { id: "reject", title: "Reject" },
45
+ ] as const,
46
+ },
47
+ });
48
+ ```
49
+
50
+ Uses `ROBOTROCK_API_KEY` from the environment when no `client` is passed.
51
+
52
+ ## Trigger.dev
53
+
54
+ ```typescript
55
+ import { task } from "@trigger.dev/sdk/v3";
56
+ import { askHuman } from "robotrock/trigger";
57
+
58
+ export const deploymentApproval = task({
59
+ id: "deployment-approval",
60
+ run: async () => {
61
+ const result = await askHuman({
62
+ task: {
63
+ type: "deployment",
64
+ name: "Deploy to production",
65
+ actions: [
66
+ { id: "approve", title: "Deploy" },
67
+ { id: "reject", title: "Cancel" },
68
+ ],
69
+ },
70
+ timeout: "1h",
71
+ });
72
+
73
+ return result.actionId === "approve";
74
+ },
75
+ });
76
+ ```
77
+
78
+ ## Exports
79
+
80
+ | Import | Description |
81
+ |--------|-------------|
82
+ | `robotrock` | Client, `askHuman` (poll), types, schemas |
83
+ | `robotrock/trigger` | `askHuman` via Trigger waitpoint tokens |
84
+
85
+ ## License
86
+
87
+ MIT
@@ -0,0 +1,209 @@
1
+ // ../core/src/schemas/task.ts
2
+ import { z } from "zod";
3
+ var safeUrlSchema = z.string().refine((url) => url.startsWith("http://") || url.startsWith("https://"), {
4
+ message: "URL must start with http:// or https://"
5
+ });
6
+ var jsonSchema7Schema = z.custom(
7
+ (val) => typeof val === "object" && val !== null,
8
+ { message: "Must be a valid JSON Schema object" }
9
+ );
10
+ var uiSchemaSchema = z.custom((val) => typeof val === "object" && val !== null, {
11
+ message: "Must be a valid UiSchema object"
12
+ });
13
+ var webhookHandlerSchema = z.object({
14
+ type: z.literal("webhook"),
15
+ url: safeUrlSchema,
16
+ headers: z.record(z.string())
17
+ });
18
+ var triggerHandlerSchema = webhookHandlerSchema.extend({
19
+ type: z.literal("trigger"),
20
+ tokenId: z.string().min(1)
21
+ });
22
+ var handlerSchema = z.discriminatedUnion("type", [webhookHandlerSchema, triggerHandlerSchema]);
23
+ var taskActionSchema = z.object({
24
+ id: z.string().min(1),
25
+ title: z.string().min(1),
26
+ description: z.string().optional(),
27
+ schema: jsonSchema7Schema.optional(),
28
+ ui: uiSchemaSchema.optional(),
29
+ data: z.record(z.unknown()).optional(),
30
+ // Optional handlers for this action - if present, must have at least 1
31
+ handlers: z.array(handlerSchema).min(1).optional()
32
+ });
33
+ var uiFieldSchemaSchema = z.object({
34
+ "ui:widget": z.string().optional(),
35
+ "ui:title": z.string().optional(),
36
+ "ui:description": z.string().optional(),
37
+ "ui:options": z.record(z.unknown()).optional(),
38
+ items: z.lazy(() => z.record(uiFieldSchemaSchema)).optional()
39
+ }).passthrough();
40
+ var contextUiSchema = z.record(uiFieldSchemaSchema).optional();
41
+ var contextDataSchema = z.object({
42
+ data: z.record(z.unknown()),
43
+ ui: contextUiSchema
44
+ }).optional();
45
+ var taskContextSchema = z.object({
46
+ /** Source application id; omitted tasks are grouped in the default inbox. */
47
+ app: z.string().min(1).optional(),
48
+ type: z.string().min(1),
49
+ name: z.string().min(1),
50
+ description: z.string().optional(),
51
+ validUntil: z.string().optional(),
52
+ context: contextDataSchema,
53
+ version: z.literal(2).optional(),
54
+ actions: z.array(taskActionSchema).min(1, "At least one action is required")
55
+ });
56
+
57
+ // src/client.ts
58
+ var RobotRockError = class extends Error {
59
+ constructor(message, statusCode, response) {
60
+ super(message);
61
+ this.statusCode = statusCode;
62
+ this.response = response;
63
+ this.name = "RobotRockError";
64
+ }
65
+ };
66
+ var RobotRock = class {
67
+ apiKey;
68
+ baseUrl;
69
+ constructor(config) {
70
+ if (!config.apiKey) {
71
+ throw new Error("API key is required");
72
+ }
73
+ this.apiKey = config.apiKey;
74
+ const rawBase = config.baseUrl ?? "https://api.robotrock.com/v1";
75
+ this.baseUrl = rawBase.replace(/\/+$/, "");
76
+ }
77
+ async createTask(task, options) {
78
+ const validation = taskContextSchema.safeParse(task);
79
+ if (!validation.success) {
80
+ throw new RobotRockError(
81
+ `Invalid task: ${validation.error.errors[0]?.message}`,
82
+ 400,
83
+ validation.error.errors
84
+ );
85
+ }
86
+ const headers = {
87
+ "Content-Type": "application/json",
88
+ "X-Api-Key": this.apiKey
89
+ };
90
+ if (options?.idempotencyKey) {
91
+ headers["Idempotency-Key"] = options.idempotencyKey;
92
+ }
93
+ const response = await fetch(`${this.baseUrl}/`, {
94
+ method: "POST",
95
+ headers,
96
+ body: JSON.stringify(validation.data)
97
+ });
98
+ const data = await response.json();
99
+ if (!response.ok) {
100
+ throw new RobotRockError(
101
+ data.message ?? "Failed to create task",
102
+ response.status,
103
+ data
104
+ );
105
+ }
106
+ return data;
107
+ }
108
+ /**
109
+ * Get a task by Redis stream entry id (returned as `task.streamId` from {@link createTask}).
110
+ */
111
+ async getTask(streamEntryId) {
112
+ const response = await fetch(`${this.baseUrl}/tasks/${streamEntryId}`, {
113
+ method: "GET",
114
+ headers: {
115
+ "X-Api-Key": this.apiKey
116
+ }
117
+ });
118
+ if (response.status === 404) {
119
+ return null;
120
+ }
121
+ const data = await response.json();
122
+ if (!response.ok) {
123
+ throw new RobotRockError(
124
+ data.message ?? "Failed to get task",
125
+ response.status,
126
+ data
127
+ );
128
+ }
129
+ return data;
130
+ }
131
+ async cancelTask(taskId) {
132
+ const response = await fetch(`${this.baseUrl}/tasks/${taskId}/cancel`, {
133
+ method: "POST",
134
+ headers: {
135
+ "X-Api-Key": this.apiKey
136
+ }
137
+ });
138
+ if (!response.ok) {
139
+ const data = await response.json();
140
+ throw new RobotRockError(
141
+ data.message ?? "Failed to cancel task",
142
+ response.status,
143
+ data
144
+ );
145
+ }
146
+ }
147
+ };
148
+ function createClient(config) {
149
+ return new RobotRock(config);
150
+ }
151
+
152
+ // src/env.ts
153
+ var DEFAULT_BASE_URL = "https://api.robotrock.com/v1";
154
+ function resolveRobotRockConfig(overrides) {
155
+ const apiKey = overrides?.apiKey ?? process.env.ROBOTROCK_API_KEY;
156
+ if (!apiKey) {
157
+ throw new Error(
158
+ "RobotRock API key is required. Set ROBOTROCK_API_KEY or pass apiKey / client to askHuman."
159
+ );
160
+ }
161
+ const baseUrl = overrides?.baseUrl ?? process.env.ROBOTROCK_BASE_URL ?? process.env.ROBOTROCK_API_URL ?? DEFAULT_BASE_URL;
162
+ return { apiKey, baseUrl };
163
+ }
164
+ function resolveRobotRockClient(client, configOverrides) {
165
+ if (client) {
166
+ return client;
167
+ }
168
+ return createClient(resolveRobotRockConfig(configOverrides));
169
+ }
170
+
171
+ // src/approval-result.ts
172
+ var AskHumanTimeoutError = class extends Error {
173
+ constructor(message) {
174
+ super(message);
175
+ this.name = "AskHumanTimeoutError";
176
+ }
177
+ };
178
+ var AskHumanExpiredError = class extends Error {
179
+ constructor(message) {
180
+ super(message);
181
+ this.name = "AskHumanExpiredError";
182
+ }
183
+ };
184
+ function toDiscriminatedApprovalResult(actions, task, streamEntryId) {
185
+ void actions;
186
+ if (!task.handled) {
187
+ throw new Error("Task has no handled result");
188
+ }
189
+ return {
190
+ actionId: task.handled.action.id,
191
+ data: task.handled.action.data,
192
+ handledBy: task.handled.handledBy,
193
+ handledAt: new Date(task.handledAt ?? Date.now()),
194
+ taskId: task.id || streamEntryId
195
+ };
196
+ }
197
+
198
+ export {
199
+ taskContextSchema,
200
+ RobotRockError,
201
+ RobotRock,
202
+ createClient,
203
+ resolveRobotRockConfig,
204
+ resolveRobotRockClient,
205
+ AskHumanTimeoutError,
206
+ AskHumanExpiredError,
207
+ toDiscriminatedApprovalResult
208
+ };
209
+ //# sourceMappingURL=chunk-TUQXDKV6.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../core/src/schemas/task.ts","../src/client.ts","../src/env.ts","../src/approval-result.ts"],"sourcesContent":["import { z } from \"zod\";\nimport type { JSONSchema7 } from \"./json-schema.js\";\n\n/**\n * Extended JSONSchema7 type that includes RJSF extensions like enumNames\n */\nexport type ExtendedJSONSchema7 = JSONSchema7 & {\n enumNames?: string[];\n [key: string]: unknown;\n};\n\n/**\n * UI Schema for RJSF form customization\n */\nexport type UiSchema = {\n \"ui:widget\"?: string;\n \"ui:title\"?: string;\n \"ui:description\"?: string;\n \"ui:placeholder\"?: string;\n \"ui:options\"?: Record<string, unknown>;\n [key: string]: unknown;\n};\n\n// Helper schemas\nconst safeUrlSchema = z\n .string()\n .refine((url) => url.startsWith(\"http://\") || url.startsWith(\"https://\"), {\n message: \"URL must start with http:// or https://\",\n });\n\n// JSONSchema7 validation\nconst jsonSchema7Schema = z.custom<ExtendedJSONSchema7>(\n (val) => typeof val === \"object\" && val !== null,\n { message: \"Must be a valid JSON Schema object\" }\n);\n\n// UiSchema validation\nconst uiSchemaSchema = z.custom<UiSchema>((val) => typeof val === \"object\" && val !== null, {\n message: \"Must be a valid UiSchema object\",\n});\n\n// Handler schemas (per-action handlers for webhooks, triggers, etc.)\nconst webhookHandlerSchema = z.object({\n type: z.literal(\"webhook\"),\n url: safeUrlSchema,\n headers: z.record(z.string()),\n});\n\nconst triggerHandlerSchema = webhookHandlerSchema.extend({\n type: z.literal(\"trigger\"),\n tokenId: z.string().min(1),\n});\n\nconst handlerSchema = z.discriminatedUnion(\"type\", [webhookHandlerSchema, triggerHandlerSchema]);\n\n// Action schema with JSONSchema-based feedback and optional handlers\nconst taskActionSchema = z.object({\n id: z.string().min(1),\n title: z.string().min(1),\n description: z.string().optional(),\n schema: jsonSchema7Schema.optional(),\n ui: uiSchemaSchema.optional(),\n data: z.record(z.unknown()).optional(),\n // Optional handlers for this action - if present, must have at least 1\n handlers: z.array(handlerSchema).min(1).optional(),\n});\n\n// UI Field Schema\nconst uiFieldSchemaSchema: z.ZodType<Record<string, unknown>> = z\n .object({\n \"ui:widget\": z.string().optional(),\n \"ui:title\": z.string().optional(),\n \"ui:description\": z.string().optional(),\n \"ui:options\": z.record(z.unknown()).optional(),\n items: z.lazy(() => z.record(uiFieldSchemaSchema)).optional(),\n })\n .passthrough();\n\n// Context UI Schema\nconst contextUiSchema = z.record(uiFieldSchemaSchema).optional();\n\n// Context data schema\nconst contextDataSchema = z\n .object({\n data: z.record(z.unknown()),\n ui: contextUiSchema,\n })\n .optional();\n\n// Main TaskContext schema\nexport const taskContextSchema = z.object({\n /** Source application id; omitted tasks are grouped in the default inbox. */\n app: z.string().min(1).optional(),\n type: z.string().min(1),\n name: z.string().min(1),\n description: z.string().optional(),\n validUntil: z.string().optional(),\n context: contextDataSchema,\n version: z.literal(2).optional(),\n actions: z.array(taskActionSchema).min(1, \"At least one action is required\"),\n});\n\n// Type exports\nexport type TaskContextInput = z.input<typeof taskContextSchema>;\nexport type TaskContextOutput = z.output<typeof taskContextSchema>;\nexport type TaskContext = TaskContextOutput;\nexport type SafeUrl = z.infer<typeof safeUrlSchema>;\nexport type TaskAction = z.infer<typeof taskActionSchema>;\nexport type WebhookHandler = z.infer<typeof webhookHandlerSchema>;\nexport type TriggerHandler = z.infer<typeof triggerHandlerSchema>;\nexport type Handler = z.infer<typeof handlerSchema>;\nexport type ContextData = z.infer<typeof contextDataSchema>;\nexport type ContextUiSchema = z.infer<typeof contextUiSchema>;\nexport type UiFieldSchema = z.infer<typeof uiFieldSchemaSchema>;\n","import { type TaskContextInput, taskContextSchema } from \"@robotrock/core\";\nimport type { Task, TaskResponse } from \"@robotrock/core\";\n\nexport interface RobotRockConfig {\n /** Your RobotRock API key */\n apiKey: string;\n /**\n * Base URL for the RobotRock API\n * @default \"https://api.robotrock.com/v1\"\n */\n baseUrl?: string;\n}\n\nexport interface CreateTaskOptions {\n /** Optional idempotency key to prevent duplicate tasks */\n idempotencyKey?: string;\n}\n\nexport class RobotRockError extends Error {\n constructor(\n message: string,\n public readonly statusCode: number,\n public readonly response?: unknown\n ) {\n super(message);\n this.name = \"RobotRockError\";\n }\n}\n\n/**\n * RobotRock API client for creating and querying human-in-the-loop tasks.\n */\nexport class RobotRock {\n private readonly apiKey: string;\n private readonly baseUrl: string;\n\n constructor(config: RobotRockConfig) {\n if (!config.apiKey) {\n throw new Error(\"API key is required\");\n }\n this.apiKey = config.apiKey;\n const rawBase = config.baseUrl ?? \"https://api.robotrock.com/v1\";\n this.baseUrl = rawBase.replace(/\\/+$/, \"\");\n }\n\n async createTask(\n task: TaskContextInput | Readonly<TaskContextInput>,\n options?: CreateTaskOptions\n ): Promise<TaskResponse> {\n const validation = taskContextSchema.safeParse(task);\n if (!validation.success) {\n throw new RobotRockError(\n `Invalid task: ${validation.error.errors[0]?.message}`,\n 400,\n validation.error.errors\n );\n }\n\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n \"X-Api-Key\": this.apiKey,\n };\n\n if (options?.idempotencyKey) {\n headers[\"Idempotency-Key\"] = options.idempotencyKey;\n }\n\n const response = await fetch(`${this.baseUrl}/`, {\n method: \"POST\",\n headers,\n body: JSON.stringify(validation.data),\n });\n\n const data = await response.json();\n\n if (!response.ok) {\n throw new RobotRockError(\n data.message ?? \"Failed to create task\",\n response.status,\n data\n );\n }\n\n return data as TaskResponse;\n }\n\n /**\n * Get a task by Redis stream entry id (returned as `task.streamId` from {@link createTask}).\n */\n async getTask(streamEntryId: string): Promise<Task | null> {\n const response = await fetch(`${this.baseUrl}/tasks/${streamEntryId}`, {\n method: \"GET\",\n headers: {\n \"X-Api-Key\": this.apiKey,\n },\n });\n\n if (response.status === 404) {\n return null;\n }\n\n const data = await response.json();\n\n if (!response.ok) {\n throw new RobotRockError(\n data.message ?? \"Failed to get task\",\n response.status,\n data\n );\n }\n\n return data as Task;\n }\n\n async cancelTask(taskId: string): Promise<void> {\n const response = await fetch(`${this.baseUrl}/tasks/${taskId}/cancel`, {\n method: \"POST\",\n headers: {\n \"X-Api-Key\": this.apiKey,\n },\n });\n\n if (!response.ok) {\n const data = await response.json();\n throw new RobotRockError(\n data.message ?? \"Failed to cancel task\",\n response.status,\n data\n );\n }\n }\n}\n\nexport function createClient(config: RobotRockConfig): RobotRock {\n return new RobotRock(config);\n}\n","import { createClient, type RobotRock, type RobotRockConfig } from \"./client.js\";\n\nconst DEFAULT_BASE_URL = \"https://api.robotrock.com/v1\";\n\n/**\n * Read RobotRock client config from environment variables.\n *\n * - `ROBOTROCK_API_KEY` (required when not passed explicitly)\n * - `ROBOTROCK_BASE_URL` or `ROBOTROCK_API_URL` (optional)\n */\nexport function resolveRobotRockConfig(\n overrides?: Partial<RobotRockConfig>\n): RobotRockConfig {\n const apiKey = overrides?.apiKey ?? process.env.ROBOTROCK_API_KEY;\n if (!apiKey) {\n throw new Error(\n \"RobotRock API key is required. Set ROBOTROCK_API_KEY or pass apiKey / client to askHuman.\"\n );\n }\n\n const baseUrl =\n overrides?.baseUrl ??\n process.env.ROBOTROCK_BASE_URL ??\n process.env.ROBOTROCK_API_URL ??\n DEFAULT_BASE_URL;\n\n return { apiKey, baseUrl };\n}\n\n/** Use an explicit client or create one from env / optional config overrides. */\nexport function resolveRobotRockClient(\n client?: RobotRock,\n configOverrides?: Partial<RobotRockConfig>\n): RobotRock {\n if (client) {\n return client;\n }\n return createClient(resolveRobotRockConfig(configOverrides));\n}\n","import type { DiscriminatedApprovalResult, Task, TaskAction } from \"@robotrock/core\";\n\nexport class AskHumanTimeoutError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"AskHumanTimeoutError\";\n }\n}\n\nexport class AskHumanExpiredError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"AskHumanExpiredError\";\n }\n}\n\n/**\n * Map a handled API task to a discriminated approval result.\n * Runtime validation is minimal; TypeScript narrows via `task.actions` at the call site.\n */\nexport function toDiscriminatedApprovalResult<A extends readonly TaskAction[]>(\n actions: A,\n task: Task,\n streamEntryId: string\n): DiscriminatedApprovalResult<A> {\n void actions;\n\n if (!task.handled) {\n throw new Error(\"Task has no handled result\");\n }\n\n return {\n actionId: task.handled.action.id,\n data: task.handled.action.data,\n handledBy: task.handled.handledBy,\n handledAt: new Date(task.handledAt ?? Date.now()),\n taskId: task.id || streamEntryId,\n } as DiscriminatedApprovalResult<A>;\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,37 @@
1
+ import { TaskContextInput, TaskResponse, Task } from '@robotrock/core';
2
+
3
+ interface RobotRockConfig {
4
+ /** Your RobotRock API key */
5
+ apiKey: string;
6
+ /**
7
+ * Base URL for the RobotRock API
8
+ * @default "https://api.robotrock.com/v1"
9
+ */
10
+ baseUrl?: string;
11
+ }
12
+ interface CreateTaskOptions {
13
+ /** Optional idempotency key to prevent duplicate tasks */
14
+ idempotencyKey?: string;
15
+ }
16
+ declare class RobotRockError extends Error {
17
+ readonly statusCode: number;
18
+ readonly response?: unknown | undefined;
19
+ constructor(message: string, statusCode: number, response?: unknown | undefined);
20
+ }
21
+ /**
22
+ * RobotRock API client for creating and querying human-in-the-loop tasks.
23
+ */
24
+ declare class RobotRock {
25
+ private readonly apiKey;
26
+ private readonly baseUrl;
27
+ constructor(config: RobotRockConfig);
28
+ createTask(task: TaskContextInput | Readonly<TaskContextInput>, options?: CreateTaskOptions): Promise<TaskResponse>;
29
+ /**
30
+ * Get a task by Redis stream entry id (returned as `task.streamId` from {@link createTask}).
31
+ */
32
+ getTask(streamEntryId: string): Promise<Task | null>;
33
+ cancelTask(taskId: string): Promise<void>;
34
+ }
35
+ declare function createClient(config: RobotRockConfig): RobotRock;
36
+
37
+ export { type CreateTaskOptions as C, type RobotRockConfig as R, RobotRock as a, RobotRockError as b, createClient as c };
@@ -0,0 +1,53 @@
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';
5
+
6
+ /**
7
+ * Read RobotRock client config from environment variables.
8
+ *
9
+ * - `ROBOTROCK_API_KEY` (required when not passed explicitly)
10
+ * - `ROBOTROCK_BASE_URL` or `ROBOTROCK_API_URL` (optional)
11
+ */
12
+ declare function resolveRobotRockConfig(overrides?: Partial<RobotRockConfig>): RobotRockConfig;
13
+ /** Use an explicit client or create one from env / optional config overrides. */
14
+ declare function resolveRobotRockClient(client?: RobotRock, configOverrides?: Partial<RobotRockConfig>): RobotRock;
15
+
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 {
42
+ constructor(message: string);
43
+ }
44
+ declare class AskHumanExpiredError extends Error {
45
+ constructor(message: string);
46
+ }
47
+ /**
48
+ * Map a handled API task to a discriminated approval result.
49
+ * Runtime validation is minimal; TypeScript narrows via `task.actions` at the call site.
50
+ */
51
+ declare function toDiscriminatedApprovalResult<A extends readonly TaskAction[]>(actions: A, task: Task, streamEntryId: string): DiscriminatedApprovalResult<A>;
52
+
53
+ export { AskHumanExpiredError, type AskHumanParams, type AskHumanTask, AskHumanTimeoutError, CreateTaskOptions, RobotRock, RobotRockConfig, askHuman, resolveRobotRockClient, resolveRobotRockConfig, toDiscriminatedApprovalResult };
package/dist/index.js ADDED
@@ -0,0 +1,60 @@
1
+ import {
2
+ AskHumanExpiredError,
3
+ AskHumanTimeoutError,
4
+ RobotRock,
5
+ RobotRockError,
6
+ createClient,
7
+ resolveRobotRockClient,
8
+ resolveRobotRockConfig,
9
+ taskContextSchema,
10
+ toDiscriminatedApprovalResult
11
+ } from "./chunk-TUQXDKV6.js";
12
+
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
+ }
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);
40
+ }
41
+ if (existing?.status === "expired") {
42
+ throw new AskHumanExpiredError("Task expired before a human completed it");
43
+ }
44
+ await sleep(pollInterval);
45
+ }
46
+ throw new AskHumanTimeoutError(`No human response within ${timeout}ms`);
47
+ }
48
+ export {
49
+ AskHumanExpiredError,
50
+ AskHumanTimeoutError,
51
+ RobotRock,
52
+ RobotRockError,
53
+ askHuman,
54
+ createClient,
55
+ resolveRobotRockClient,
56
+ resolveRobotRockConfig,
57
+ taskContextSchema,
58
+ toDiscriminatedApprovalResult
59
+ };
60
+ //# sourceMappingURL=index.js.map
@@ -0,0 +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":[]}
@@ -0,0 +1,44 @@
1
+ import { TaskAction, TaskContextInput, DiscriminatedApprovalResult } from '@robotrock/core';
2
+ export { ApprovalResult, DiscriminatedApprovalResult, TaskContextInput, TaskResult } from '@robotrock/core';
3
+ import { a as RobotRock, R as RobotRockConfig } from '../client-BQ-j7q68.js';
4
+
5
+ /**
6
+ * JSON body posted by Convex when an action handler runs.
7
+ * POSTing this to Trigger's `token.url` completes `wait.forToken`.
8
+ */
9
+ interface RobotRockHandlerWebhookPayload {
10
+ taskId: string;
11
+ action: {
12
+ id: string;
13
+ title: string;
14
+ data: unknown;
15
+ };
16
+ handledBy?: string;
17
+ handledAt: string;
18
+ handlerType: string;
19
+ }
20
+ interface AskHumanTriggerOptions {
21
+ app?: string;
22
+ /** Trigger waitpoint timeout (e.g. "1h", "30m", "7d"). @default "24h" */
23
+ timeout?: string;
24
+ /** Optional side-channel webhook URL (same payload as the wait token). */
25
+ callbackUrl?: string;
26
+ }
27
+ type AskHumanTriggerParams<A extends readonly TaskAction[]> = AskHumanTriggerOptions & {
28
+ task: Omit<TaskContextInput, "actions"> & {
29
+ readonly actions: A;
30
+ };
31
+ client?: RobotRock;
32
+ apiKey?: string;
33
+ baseUrl?: string;
34
+ };
35
+ declare function createTriggerClient(config: RobotRockConfig): RobotRock;
36
+ /**
37
+ * Ask a human inside a Trigger.dev task run (waitpoint token + RobotRock handlers).
38
+ *
39
+ * Return type is inferred from `task.actions` (`actionId` narrows `data`).
40
+ * Must run inside `task({ run })` with `@trigger.dev/sdk` installed.
41
+ */
42
+ declare function askHuman<const A extends readonly TaskAction[]>(params: AskHumanTriggerParams<A>): Promise<DiscriminatedApprovalResult<A>>;
43
+
44
+ export { type AskHumanTriggerOptions, type AskHumanTriggerParams, RobotRockConfig, type RobotRockHandlerWebhookPayload, askHuman, createTriggerClient };
@@ -0,0 +1,106 @@
1
+ import {
2
+ createClient,
3
+ resolveRobotRockClient,
4
+ toDiscriminatedApprovalResult
5
+ } from "../chunk-TUQXDKV6.js";
6
+
7
+ // src/trigger/index.ts
8
+ import { wait } from "@trigger.dev/sdk/v3";
9
+ function createTriggerClient(config) {
10
+ return createClient(config);
11
+ }
12
+ async function askHuman(params) {
13
+ const {
14
+ task,
15
+ app,
16
+ timeout = "24h",
17
+ callbackUrl,
18
+ client: explicitClient,
19
+ apiKey,
20
+ baseUrl
21
+ } = params;
22
+ assertTriggerTimeoutFormat(timeout);
23
+ const client = resolveRobotRockClient(explicitClient, { apiKey, baseUrl });
24
+ const token = await wait.createToken({ timeout });
25
+ const resolvedApp = app ?? task.app;
26
+ const taskWithHandlers = {
27
+ ...task,
28
+ ...resolvedApp !== void 0 ? { app: resolvedApp } : {},
29
+ actions: task.actions.map((action) => {
30
+ const waitHandler = {
31
+ type: "trigger",
32
+ url: token.url,
33
+ headers: {},
34
+ tokenId: token.id
35
+ };
36
+ const callbackHandlers = callbackUrl ? [
37
+ {
38
+ type: "trigger",
39
+ url: callbackUrl,
40
+ headers: {},
41
+ tokenId: crypto.randomUUID()
42
+ }
43
+ ] : [];
44
+ return {
45
+ ...action,
46
+ handlers: [waitHandler, ...action.handlers ?? [], ...callbackHandlers]
47
+ };
48
+ })
49
+ };
50
+ await client.createTask(taskWithHandlers);
51
+ const outcome = await wait.forToken(token.id);
52
+ if (!outcome.ok) {
53
+ throw new Error(`Human response timeout after ${timeout}`);
54
+ }
55
+ const output = outcome.output;
56
+ if (!isRobotRockHandlerWebhookPayload(output)) {
57
+ throw new Error(
58
+ "Wait token completed with unexpected payload; expected RobotRock handler body (taskId, action.id, action.data)."
59
+ );
60
+ }
61
+ return toDiscriminatedApprovalResult(
62
+ task.actions,
63
+ {
64
+ id: output.taskId,
65
+ createdAt: /* @__PURE__ */ new Date(),
66
+ status: "handled",
67
+ context: taskWithHandlers,
68
+ validUntil: Date.now(),
69
+ handledAt: new Date(output.handledAt).getTime(),
70
+ handled: {
71
+ action: {
72
+ id: output.action.id,
73
+ data: output.action.data
74
+ },
75
+ handledBy: output.handledBy
76
+ }
77
+ },
78
+ output.taskId
79
+ );
80
+ }
81
+ function assertTriggerTimeoutFormat(timeout) {
82
+ const match = timeout.match(/^(\d+)(s|m|h|d)$/);
83
+ if (!match) {
84
+ throw new Error(`Invalid timeout format: ${timeout}. Use format like "1h", "30m", "7d"`);
85
+ }
86
+ }
87
+ function isRobotRockHandlerWebhookPayload(value) {
88
+ if (typeof value !== "object" || value === null) {
89
+ return false;
90
+ }
91
+ const v = value;
92
+ if (typeof v.taskId !== "string" || typeof v.handledAt !== "string") {
93
+ return false;
94
+ }
95
+ const action = v.action;
96
+ if (typeof action !== "object" || action === null) {
97
+ return false;
98
+ }
99
+ const a = action;
100
+ return typeof a.id === "string" && "data" in a;
101
+ }
102
+ export {
103
+ askHuman,
104
+ createTriggerClient
105
+ };
106
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/trigger/index.ts"],"sourcesContent":["import type {\n DiscriminatedApprovalResult,\n TaskAction,\n TaskContextInput,\n} from \"@robotrock/core\";\nimport { wait } from \"@trigger.dev/sdk/v3\";\nimport { createClient, type RobotRock, type RobotRockConfig } from \"../client.js\";\nimport { resolveRobotRockClient } from \"../env.js\";\nimport { toDiscriminatedApprovalResult } from \"../approval-result.js\";\n\n/**\n * JSON body posted by Convex when an action handler runs.\n * POSTing this to Trigger's `token.url` completes `wait.forToken`.\n */\nexport interface RobotRockHandlerWebhookPayload {\n taskId: string;\n action: {\n id: string;\n title: string;\n data: unknown;\n };\n handledBy?: string;\n handledAt: string;\n handlerType: string;\n}\n\nexport interface AskHumanTriggerOptions {\n app?: string;\n /** Trigger waitpoint timeout (e.g. \"1h\", \"30m\", \"7d\"). @default \"24h\" */\n timeout?: string;\n /** Optional side-channel webhook URL (same payload as the wait token). */\n callbackUrl?: string;\n}\n\nexport type AskHumanTriggerParams<A extends readonly TaskAction[]> = AskHumanTriggerOptions & {\n task: Omit<TaskContextInput, \"actions\"> & { readonly actions: A };\n client?: RobotRock;\n apiKey?: string;\n baseUrl?: string;\n};\n\nexport function createTriggerClient(config: RobotRockConfig): RobotRock {\n return createClient(config);\n}\n\n/**\n * Ask a human inside a Trigger.dev task run (waitpoint token + RobotRock handlers).\n *\n * Return type is inferred from `task.actions` (`actionId` narrows `data`).\n * Must run inside `task({ run })` with `@trigger.dev/sdk` installed.\n */\nexport async function askHuman<const A extends readonly TaskAction[]>(\n params: AskHumanTriggerParams<A>\n): Promise<DiscriminatedApprovalResult<A>> {\n const {\n task,\n app,\n timeout = \"24h\",\n callbackUrl,\n client: explicitClient,\n apiKey,\n baseUrl,\n } = params;\n\n assertTriggerTimeoutFormat(timeout);\n\n const client = resolveRobotRockClient(explicitClient, { apiKey, baseUrl });\n\n const token = await wait.createToken({ timeout });\n\n const resolvedApp = app ?? task.app;\n const taskWithHandlers: TaskContextInput = {\n ...task,\n ...(resolvedApp !== undefined ? { app: resolvedApp } : {}),\n actions: task.actions.map((action: TaskAction) => {\n const waitHandler = {\n type: \"trigger\" as const,\n url: token.url,\n headers: {} as Record<string, string>,\n tokenId: token.id,\n };\n const callbackHandlers = callbackUrl\n ? [\n {\n type: \"trigger\" as const,\n url: callbackUrl,\n headers: {} as Record<string, string>,\n tokenId: crypto.randomUUID(),\n },\n ]\n : [];\n\n return {\n ...action,\n handlers: [waitHandler, ...(action.handlers ?? []), ...callbackHandlers],\n };\n }),\n };\n\n await client.createTask(taskWithHandlers);\n\n const outcome = await wait.forToken<RobotRockHandlerWebhookPayload>(token.id);\n\n if (!outcome.ok) {\n throw new Error(`Human response timeout after ${timeout}`);\n }\n\n const output = outcome.output;\n if (!isRobotRockHandlerWebhookPayload(output)) {\n throw new Error(\n \"Wait token completed with unexpected payload; expected RobotRock handler body (taskId, action.id, action.data).\"\n );\n }\n\n return toDiscriminatedApprovalResult(\n task.actions,\n {\n id: output.taskId,\n createdAt: new Date(),\n status: \"handled\",\n context: taskWithHandlers,\n validUntil: Date.now(),\n handledAt: new Date(output.handledAt).getTime(),\n handled: {\n action: {\n id: output.action.id,\n data: output.action.data,\n },\n handledBy: output.handledBy,\n },\n },\n output.taskId\n );\n}\n\nfunction assertTriggerTimeoutFormat(timeout: string): void {\n const match = timeout.match(/^(\\d+)(s|m|h|d)$/);\n if (!match) {\n throw new Error(`Invalid timeout format: ${timeout}. Use format like \"1h\", \"30m\", \"7d\"`);\n }\n}\n\nfunction isRobotRockHandlerWebhookPayload(\n value: unknown\n): value is RobotRockHandlerWebhookPayload {\n if (typeof value !== \"object\" || value === null) {\n return false;\n }\n const v = value as Record<string, unknown>;\n if (typeof v.taskId !== \"string\" || typeof v.handledAt !== \"string\") {\n return false;\n }\n const action = v.action;\n if (typeof action !== \"object\" || action === null) {\n return false;\n }\n const a = action as Record<string, unknown>;\n return typeof a.id === \"string\" && \"data\" in a;\n}\n\nexport type {\n ApprovalResult,\n DiscriminatedApprovalResult,\n TaskContextInput,\n TaskResult,\n} from \"@robotrock/core\";\nexport type { RobotRockConfig } from \"../client.js\";\n"],"mappings":";;;;;;;AAKA,SAAS,YAAY;AAoCd,SAAS,oBAAoB,QAAoC;AACtE,SAAO,aAAa,MAAM;AAC5B;AAQA,eAAsB,SACpB,QACyC;AACzC,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA,UAAU;AAAA,IACV;AAAA,IACA,QAAQ;AAAA,IACR;AAAA,IACA;AAAA,EACF,IAAI;AAEJ,6BAA2B,OAAO;AAElC,QAAM,SAAS,uBAAuB,gBAAgB,EAAE,QAAQ,QAAQ,CAAC;AAEzE,QAAM,QAAQ,MAAM,KAAK,YAAY,EAAE,QAAQ,CAAC;AAEhD,QAAM,cAAc,OAAO,KAAK;AAChC,QAAM,mBAAqC;AAAA,IACzC,GAAG;AAAA,IACH,GAAI,gBAAgB,SAAY,EAAE,KAAK,YAAY,IAAI,CAAC;AAAA,IACxD,SAAS,KAAK,QAAQ,IAAI,CAAC,WAAuB;AAChD,YAAM,cAAc;AAAA,QAClB,MAAM;AAAA,QACN,KAAK,MAAM;AAAA,QACX,SAAS,CAAC;AAAA,QACV,SAAS,MAAM;AAAA,MACjB;AACA,YAAM,mBAAmB,cACrB;AAAA,QACE;AAAA,UACE,MAAM;AAAA,UACN,KAAK;AAAA,UACL,SAAS,CAAC;AAAA,UACV,SAAS,OAAO,WAAW;AAAA,QAC7B;AAAA,MACF,IACA,CAAC;AAEL,aAAO;AAAA,QACL,GAAG;AAAA,QACH,UAAU,CAAC,aAAa,GAAI,OAAO,YAAY,CAAC,GAAI,GAAG,gBAAgB;AAAA,MACzE;AAAA,IACF,CAAC;AAAA,EACH;AAEA,QAAM,OAAO,WAAW,gBAAgB;AAExC,QAAM,UAAU,MAAM,KAAK,SAAyC,MAAM,EAAE;AAE5E,MAAI,CAAC,QAAQ,IAAI;AACf,UAAM,IAAI,MAAM,gCAAgC,OAAO,EAAE;AAAA,EAC3D;AAEA,QAAM,SAAS,QAAQ;AACvB,MAAI,CAAC,iCAAiC,MAAM,GAAG;AAC7C,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL,KAAK;AAAA,IACL;AAAA,MACE,IAAI,OAAO;AAAA,MACX,WAAW,oBAAI,KAAK;AAAA,MACpB,QAAQ;AAAA,MACR,SAAS;AAAA,MACT,YAAY,KAAK,IAAI;AAAA,MACrB,WAAW,IAAI,KAAK,OAAO,SAAS,EAAE,QAAQ;AAAA,MAC9C,SAAS;AAAA,QACP,QAAQ;AAAA,UACN,IAAI,OAAO,OAAO;AAAA,UAClB,MAAM,OAAO,OAAO;AAAA,QACtB;AAAA,QACA,WAAW,OAAO;AAAA,MACpB;AAAA,IACF;AAAA,IACA,OAAO;AAAA,EACT;AACF;AAEA,SAAS,2BAA2B,SAAuB;AACzD,QAAM,QAAQ,QAAQ,MAAM,kBAAkB;AAC9C,MAAI,CAAC,OAAO;AACV,UAAM,IAAI,MAAM,2BAA2B,OAAO,qCAAqC;AAAA,EACzF;AACF;AAEA,SAAS,iCACP,OACyC;AACzC,MAAI,OAAO,UAAU,YAAY,UAAU,MAAM;AAC/C,WAAO;AAAA,EACT;AACA,QAAM,IAAI;AACV,MAAI,OAAO,EAAE,WAAW,YAAY,OAAO,EAAE,cAAc,UAAU;AACnE,WAAO;AAAA,EACT;AACA,QAAM,SAAS,EAAE;AACjB,MAAI,OAAO,WAAW,YAAY,WAAW,MAAM;AACjD,WAAO;AAAA,EACT;AACA,QAAM,IAAI;AACV,SAAO,OAAO,EAAE,OAAO,YAAY,UAAU;AAC/C;","names":[]}
package/package.json ADDED
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "robotrock",
3
+ "version": "0.1.0",
4
+ "description": "Human-in-the-loop approval workflows for AI agents",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js"
13
+ },
14
+ "./trigger": {
15
+ "types": "./dist/trigger/index.d.ts",
16
+ "import": "./dist/trigger/index.js"
17
+ }
18
+ },
19
+ "files": [
20
+ "dist",
21
+ "README.md"
22
+ ],
23
+ "scripts": {
24
+ "build": "tsup",
25
+ "dev": "tsup --watch",
26
+ "typecheck": "tsc --noEmit",
27
+ "lint": "eslint src/",
28
+ "clean": "rm -rf dist"
29
+ },
30
+ "keywords": [
31
+ "robotrock",
32
+ "human-in-the-loop",
33
+ "approval",
34
+ "workflow",
35
+ "trigger.dev"
36
+ ],
37
+ "license": "MIT",
38
+ "repository": {
39
+ "type": "git",
40
+ "url": "https://github.com/robotrock/robotrock"
41
+ },
42
+ "dependencies": {
43
+ "zod": "^3.25.0"
44
+ },
45
+ "devDependencies": {
46
+ "@robotrock/core": "workspace:*",
47
+ "@robotrock/config": "workspace:*",
48
+ "@types/node": "^25.0.3",
49
+ "@trigger.dev/sdk": "4.4.6",
50
+ "tsup": "^8.0.0",
51
+ "typescript": "^5.9.3"
52
+ },
53
+ "peerDependencies": {
54
+ "@trigger.dev/sdk": "^3.0.0 || ^4.0.0"
55
+ },
56
+ "peerDependenciesMeta": {
57
+ "@trigger.dev/sdk": {
58
+ "optional": true
59
+ }
60
+ }
61
+ }