robotrock 0.7.0 → 0.8.1

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.
@@ -1,17 +1,1453 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : /* @__PURE__ */ Symbol.for("Symbol." + name);
4
+ var __typeError = (msg) => {
5
+ throw TypeError(msg);
6
+ };
7
+ var __esm = (fn, res) => function __init() {
8
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
9
+ };
10
+ var __export = (target, all) => {
11
+ for (var name in all)
12
+ __defProp(target, name, { get: all[name], enumerable: true });
13
+ };
14
+ var __using = (stack, value, async) => {
15
+ if (value != null) {
16
+ if (typeof value !== "object" && typeof value !== "function") __typeError("Object expected");
17
+ var dispose, inner;
18
+ if (async) dispose = value[__knownSymbol("asyncDispose")];
19
+ if (dispose === void 0) {
20
+ dispose = value[__knownSymbol("dispose")];
21
+ if (async) inner = dispose;
22
+ }
23
+ if (typeof dispose !== "function") __typeError("Object not disposable");
24
+ if (inner) dispose = function() {
25
+ try {
26
+ inner.call(this);
27
+ } catch (e) {
28
+ return Promise.reject(e);
29
+ }
30
+ };
31
+ stack.push([async, dispose, value]);
32
+ } else if (async) {
33
+ stack.push([async]);
34
+ }
35
+ return value;
36
+ };
37
+ var __callDispose = (stack, error, hasError) => {
38
+ var E = typeof SuppressedError === "function" ? SuppressedError : function(e, s, m, _) {
39
+ return _ = Error(m), _.name = "SuppressedError", _.error = e, _.suppressed = s, _;
40
+ };
41
+ var fail = (e) => error = hasError ? new E(e, error, "An error was suppressed during disposal") : (hasError = true, e);
42
+ var next = (it) => {
43
+ while (it = stack.pop()) {
44
+ try {
45
+ var result = it[1] && it[1].call(it[2]);
46
+ if (it[0]) return Promise.resolve(result).then(next, (e) => (fail(e), next()));
47
+ } catch (e) {
48
+ fail(e);
49
+ }
50
+ }
51
+ if (hasError) throw error;
52
+ };
53
+ return next();
54
+ };
55
+
56
+ // src/schemas/index.ts
57
+ import { z } from "zod";
1
58
  import {
2
- applyRobotRockToolApprovalToTools,
3
- approveByHumanTool,
4
- createRobotRockAiTools,
5
- createRobotRockAiTriggerContext,
6
- createRobotRockNeedsApproval,
7
- createRobotRockToolApproval,
8
- createSendToHumanTool,
9
- createSendUpdateTool,
10
- resolveToolApprovalsViaRobotRock,
11
- runWithRobotRockApprovals
12
- } from "../chunk-55JHCNYX.js";
13
- import "../chunk-ZG2XVK6Y.js";
14
- import "../chunk-ZHASQUX6.js";
59
+ isPublicHttpUrl,
60
+ PUBLIC_HTTP_URL_ERROR
61
+ } from "@robotrock/core/utils";
62
+ import { validateContextPublicUrls } from "@robotrock/core/schemas";
63
+ function refineContextPublicUrls(data, ctx) {
64
+ const error = validateContextPublicUrls(data.context);
65
+ if (error) {
66
+ ctx.addIssue({
67
+ code: z.ZodIssueCode.custom,
68
+ message: error,
69
+ path: ["context"]
70
+ });
71
+ }
72
+ }
73
+ var safeUrlSchema, jsonSchema7Schema, uiSchemaSchema, webhookHandlerSchema, triggerHandlerSchema, handlerSchema, taskActionSchema, uiFieldSchemaSchema, contextUiSchema, contextDataSchema, taskContextObjectSchema, taskContextSchema, assignToSchema, threadUpdateMessageSchema, threadUpdateStatuses, threadUpdateStatusSchema, threadUpdateInputSchema, taskPriorities, taskPrioritySchema, nonNegativeInt, agentCostTokensSchema, agentCostSchema, AGENT_INFO_MAX_KEYS, agentTelemetrySchema, createTaskBodySchema, threadUpdateBodySchema;
74
+ var init_schemas = __esm({
75
+ "src/schemas/index.ts"() {
76
+ "use strict";
77
+ safeUrlSchema = z.string().refine((url) => isPublicHttpUrl(url), {
78
+ message: PUBLIC_HTTP_URL_ERROR
79
+ });
80
+ jsonSchema7Schema = z.custom(
81
+ (val) => typeof val === "object" && val !== null,
82
+ { message: "Must be a valid JSON Schema object" }
83
+ );
84
+ uiSchemaSchema = z.custom((val) => typeof val === "object" && val !== null, {
85
+ message: "Must be a valid UiSchema object"
86
+ });
87
+ webhookHandlerSchema = z.object({
88
+ type: z.literal("webhook"),
89
+ url: safeUrlSchema,
90
+ headers: z.record(z.string(), z.string())
91
+ });
92
+ triggerHandlerSchema = webhookHandlerSchema.extend({
93
+ type: z.literal("trigger"),
94
+ tokenId: z.string().min(1)
95
+ });
96
+ handlerSchema = z.discriminatedUnion("type", [webhookHandlerSchema, triggerHandlerSchema]);
97
+ taskActionSchema = z.object({
98
+ id: z.string().min(1),
99
+ title: z.string().min(1),
100
+ description: z.string().optional(),
101
+ schema: jsonSchema7Schema.optional(),
102
+ ui: uiSchemaSchema.optional(),
103
+ data: z.record(z.string(), z.unknown()).optional(),
104
+ handlers: z.array(handlerSchema).min(1).optional()
105
+ });
106
+ uiFieldSchemaSchema = z.object({
107
+ "ui:widget": z.string().optional(),
108
+ "ui:title": z.string().optional(),
109
+ "ui:description": z.string().optional(),
110
+ "ui:options": z.record(z.string(), z.unknown()).optional(),
111
+ items: z.lazy(() => z.record(z.string(), uiFieldSchemaSchema)).optional()
112
+ }).passthrough();
113
+ contextUiSchema = z.record(z.string(), uiFieldSchemaSchema).optional();
114
+ contextDataSchema = z.object({
115
+ data: z.record(z.string(), z.unknown()),
116
+ ui: contextUiSchema
117
+ }).optional();
118
+ taskContextObjectSchema = z.object({
119
+ app: z.string().min(1).optional(),
120
+ type: z.string().min(1),
121
+ name: z.string().min(1),
122
+ description: z.string().optional(),
123
+ validUntil: z.string().optional(),
124
+ context: contextDataSchema,
125
+ version: z.literal(2).optional(),
126
+ actions: z.array(taskActionSchema).min(1, "At least one action is required")
127
+ });
128
+ taskContextSchema = taskContextObjectSchema.superRefine(
129
+ refineContextPublicUrls
130
+ );
131
+ assignToSchema = z.object({
132
+ users: z.array(z.string().email()).optional(),
133
+ groups: z.array(z.string().min(1)).optional()
134
+ }).refine(
135
+ (data) => {
136
+ const groups = data.groups ?? [];
137
+ if (groups.includes("all") && groups.length > 1) {
138
+ return false;
139
+ }
140
+ return true;
141
+ },
142
+ { message: 'Cannot combine "all" with other group slugs' }
143
+ );
144
+ threadUpdateMessageSchema = z.string().min(1).max(500);
145
+ threadUpdateStatuses = [
146
+ "info",
147
+ "queued",
148
+ "running",
149
+ "waiting",
150
+ "succeeded",
151
+ "failed",
152
+ "cancelled"
153
+ ];
154
+ threadUpdateStatusSchema = z.enum(threadUpdateStatuses);
155
+ threadUpdateInputSchema = z.object({
156
+ message: threadUpdateMessageSchema,
157
+ status: threadUpdateStatusSchema.optional()
158
+ });
159
+ taskPriorities = ["low", "normal", "high", "urgent"];
160
+ taskPrioritySchema = z.enum(taskPriorities);
161
+ nonNegativeInt = z.number().int().nonnegative();
162
+ agentCostTokensSchema = z.object({
163
+ input: nonNegativeInt.optional(),
164
+ output: nonNegativeInt.optional(),
165
+ total: nonNegativeInt.optional()
166
+ });
167
+ agentCostSchema = z.object({
168
+ tokens: agentCostTokensSchema.optional(),
169
+ eur: z.number().nonnegative().optional(),
170
+ usd: z.number().nonnegative().optional()
171
+ });
172
+ AGENT_INFO_MAX_KEYS = 32;
173
+ agentTelemetrySchema = z.object({
174
+ version: z.string().min(1).optional(),
175
+ toolCallCount: nonNegativeInt.optional(),
176
+ cost: agentCostSchema.optional(),
177
+ info: z.record(z.string(), z.unknown()).optional()
178
+ }).refine(
179
+ (data) => {
180
+ const keys = data.info ? Object.keys(data.info) : [];
181
+ return keys.length <= AGENT_INFO_MAX_KEYS;
182
+ },
183
+ { message: `agent.info may contain at most ${AGENT_INFO_MAX_KEYS} keys` }
184
+ );
185
+ createTaskBodySchema = taskContextObjectSchema.extend({
186
+ assignTo: assignToSchema.optional(),
187
+ /**
188
+ * Groups related tasks together. When omitted, the server generates one and
189
+ * returns it so the caller can reuse it on later tasks in the same thread.
190
+ */
191
+ threadId: z.string().min(1).optional(),
192
+ /**
193
+ * Optional thread priority. When set, applies to the whole thread and
194
+ * overwrites any previous priority. Omit on later tasks to leave unchanged.
195
+ */
196
+ priority: taskPrioritySchema.optional(),
197
+ /**
198
+ * Optional initial status update logged against the task's thread. Shows in
199
+ * the inbox status bar and the thread update log.
200
+ */
201
+ update: threadUpdateInputSchema.optional(),
202
+ agent: agentTelemetrySchema.optional()
203
+ }).superRefine(refineContextPublicUrls);
204
+ threadUpdateBodySchema = threadUpdateInputSchema;
205
+ }
206
+ });
207
+
208
+ // src/approval-result.ts
209
+ function toDiscriminatedApprovalResult(actions, task2) {
210
+ void actions;
211
+ if (!task2.handled) {
212
+ throw new Error("Task has no handled result");
213
+ }
214
+ return {
215
+ actionId: task2.handled.action.id,
216
+ data: task2.handled.action.data,
217
+ handledBy: task2.handled.handledBy,
218
+ handledAt: new Date(task2.handledAt ?? Date.now()),
219
+ taskId: task2.id
220
+ };
221
+ }
222
+ var TaskTimeoutError, TaskExpiredError;
223
+ var init_approval_result = __esm({
224
+ "src/approval-result.ts"() {
225
+ "use strict";
226
+ TaskTimeoutError = class extends Error {
227
+ constructor(message) {
228
+ super(message);
229
+ this.name = "TaskTimeoutError";
230
+ }
231
+ };
232
+ TaskExpiredError = class extends Error {
233
+ constructor(message) {
234
+ super(message);
235
+ this.name = "TaskExpiredError";
236
+ }
237
+ };
238
+ }
239
+ });
240
+
241
+ // src/client.ts
242
+ function sleep(ms) {
243
+ return new Promise((resolve) => setTimeout(resolve, ms));
244
+ }
245
+ function parseValidUntilMs(value) {
246
+ if (value === void 0) {
247
+ return void 0;
248
+ }
249
+ if (value instanceof Date) {
250
+ const ms = value.getTime();
251
+ return Number.isNaN(ms) ? void 0 : ms;
252
+ }
253
+ if (typeof value === "number") {
254
+ return Number.isFinite(value) ? value : void 0;
255
+ }
256
+ const parsed = Date.parse(value);
257
+ return Number.isNaN(parsed) ? void 0 : parsed;
258
+ }
259
+ function serializeValidUntil(value) {
260
+ if (value instanceof Date) {
261
+ const ms = value.getTime();
262
+ if (Number.isNaN(ms)) {
263
+ throw new RobotRockError("Invalid validUntil: Date is invalid", 400);
264
+ }
265
+ return value.toISOString();
266
+ }
267
+ if (typeof value === "string" && !Number.isNaN(Date.parse(value))) {
268
+ return new Date(value).toISOString();
269
+ }
270
+ throw new RobotRockError("Invalid validUntil: expected a Date or parseable date string", 400);
271
+ }
272
+ function createClient(config) {
273
+ return new RobotRock(config);
274
+ }
275
+ function attachWebhookToActions(actions, webhook) {
276
+ return actions.map((action) => ({
277
+ ...action,
278
+ handlers: webhookToHandlers(webhook)
279
+ }));
280
+ }
281
+ function webhookToHandlers(webhook) {
282
+ return [
283
+ {
284
+ type: "webhook",
285
+ url: webhook.url,
286
+ headers: webhook.headers ?? {}
287
+ }
288
+ ];
289
+ }
290
+ function normalizeSendToHumanInput(task2, clientDefaults) {
291
+ const {
292
+ actions,
293
+ idempotencyKey: _idempotencyKey,
294
+ assignTo: _assignTo,
295
+ threadId: _threadId,
296
+ priority: _priority,
297
+ update: _update,
298
+ validUntil,
299
+ app: taskApp,
300
+ ...rest
301
+ } = task2;
302
+ const webhook = clientDefaults.webhook;
303
+ const normalizedActions = webhook ? attachWebhookToActions(actions, webhook) : actions;
304
+ const app = taskApp ?? clientDefaults.app;
305
+ return {
306
+ ...rest,
307
+ version: clientDefaults.version,
308
+ ...app ? { app } : {},
309
+ ...validUntil !== void 0 ? { validUntil: serializeValidUntil(validUntil) } : {},
310
+ actions: normalizedActions
311
+ };
312
+ }
313
+ async function parseResponseBody(response) {
314
+ const contentType = response.headers.get("content-type") ?? "";
315
+ const bodyText = await response.text();
316
+ if (!bodyText) {
317
+ return null;
318
+ }
319
+ if (contentType.toLowerCase().includes("application/json")) {
320
+ try {
321
+ return JSON.parse(bodyText);
322
+ } catch {
323
+ }
324
+ }
325
+ try {
326
+ return JSON.parse(bodyText);
327
+ } catch {
328
+ return bodyText;
329
+ }
330
+ }
331
+ function getErrorMessage(data, fallback) {
332
+ if (data && typeof data === "object" && !Array.isArray(data)) {
333
+ const maybeMessage = data.message;
334
+ if (typeof maybeMessage === "string" && maybeMessage.trim()) {
335
+ return maybeMessage;
336
+ }
337
+ }
338
+ if (typeof data === "string" && data.trim()) {
339
+ const compact = data.replace(/\s+/g, " ").trim();
340
+ const snippet = compact.length > 180 ? `${compact.slice(0, 180)}...` : compact;
341
+ return `${fallback}. Server returned non-JSON response: ${snippet}`;
342
+ }
343
+ return fallback;
344
+ }
345
+ var DEFAULT_POLL_INTERVAL_MS, DEFAULT_TIMEOUT_MS, RobotRockError, RobotRock;
346
+ var init_client = __esm({
347
+ "src/client.ts"() {
348
+ "use strict";
349
+ init_schemas();
350
+ init_approval_result();
351
+ DEFAULT_POLL_INTERVAL_MS = 2e3;
352
+ DEFAULT_TIMEOUT_MS = 24 * 60 * 60 * 1e3;
353
+ RobotRockError = class extends Error {
354
+ constructor(message, statusCode, response) {
355
+ super(message);
356
+ this.statusCode = statusCode;
357
+ this.response = response;
358
+ this.name = "RobotRockError";
359
+ }
360
+ };
361
+ RobotRock = class {
362
+ apiKey;
363
+ baseUrl;
364
+ app;
365
+ version;
366
+ webhook;
367
+ polling;
368
+ constructor(config) {
369
+ if (config.webhook && config.polling) {
370
+ throw new Error(
371
+ "RobotRock client cannot configure both webhook and polling. Use webhook for callbacks or polling to block until handled."
372
+ );
373
+ }
374
+ const apiKey = config.apiKey ?? process.env.ROBOTROCK_API_KEY;
375
+ if (!apiKey) {
376
+ throw new Error(
377
+ "RobotRock API key is required. Set ROBOTROCK_API_KEY or pass apiKey when creating the client."
378
+ );
379
+ }
380
+ this.apiKey = apiKey;
381
+ const rawBase = config.baseUrl ?? "https://api.robotrock.io/v1";
382
+ this.baseUrl = rawBase.replace(/\/+$/, "");
383
+ this.app = config.app;
384
+ this.version = config.version ?? 2;
385
+ this.webhook = config.webhook;
386
+ this.polling = config.polling ?? {};
387
+ }
388
+ /**
389
+ * Create a task via POST /v1 without waiting for a human response.
390
+ */
391
+ async createTask(task2) {
392
+ const normalizedTask = normalizeSendToHumanInput(task2, {
393
+ webhook: this.webhook,
394
+ app: this.app,
395
+ version: this.version
396
+ });
397
+ const bodyPayload = {
398
+ ...normalizedTask,
399
+ ...task2.assignTo !== void 0 ? { assignTo: task2.assignTo } : {},
400
+ ...task2.threadId !== void 0 ? { threadId: task2.threadId } : {},
401
+ ...task2.priority !== void 0 ? { priority: task2.priority } : {},
402
+ ...task2.update !== void 0 ? { update: task2.update } : {},
403
+ ...task2.agent !== void 0 ? { agent: task2.agent } : {}
404
+ };
405
+ const validation = createTaskBodySchema.safeParse(bodyPayload);
406
+ if (!validation.success) {
407
+ throw new RobotRockError(
408
+ `Invalid task: ${validation.error.issues[0]?.message}`,
409
+ 400,
410
+ validation.error.issues
411
+ );
412
+ }
413
+ const headers = {
414
+ "Content-Type": "application/json",
415
+ "X-Api-Key": this.apiKey
416
+ };
417
+ if (task2.idempotencyKey) {
418
+ headers["Idempotency-Key"] = task2.idempotencyKey;
419
+ }
420
+ const response = await fetch(`${this.baseUrl}/`, {
421
+ method: "POST",
422
+ headers,
423
+ body: JSON.stringify(validation.data)
424
+ });
425
+ const data = await parseResponseBody(response);
426
+ if (!response.ok) {
427
+ throw new RobotRockError(
428
+ getErrorMessage(data, "Failed to create task"),
429
+ response.status,
430
+ data
431
+ );
432
+ }
433
+ return data.task;
434
+ }
435
+ async sendToHuman(task2) {
436
+ const normalizedTask = normalizeSendToHumanInput(task2, {
437
+ webhook: this.webhook,
438
+ app: this.app,
439
+ version: this.version
440
+ });
441
+ const createdTaskTask = await this.createTask(task2);
442
+ const hasHandlers = normalizedTask.actions.some(
443
+ (action) => Array.isArray(action.handlers) && action.handlers.length > 0
444
+ );
445
+ if (hasHandlers) {
446
+ return {
447
+ mode: "created",
448
+ task: createdTaskTask
449
+ };
450
+ }
451
+ const timeoutMs = this.polling.timeoutMs ?? DEFAULT_TIMEOUT_MS;
452
+ const pollIntervalMs = this.polling.intervalMs ?? DEFAULT_POLL_INTERVAL_MS;
453
+ const pollingDeadline = Date.now() + timeoutMs;
454
+ const validUntilMs = parseValidUntilMs(createdTaskTask.validUntil);
455
+ const deadline = validUntilMs !== void 0 ? Math.min(pollingDeadline, validUntilMs) : pollingDeadline;
456
+ const taskId = createdTaskTask.taskId;
457
+ while (Date.now() < deadline) {
458
+ const existing = await this.getTask(taskId);
459
+ if (existing?.status === "handled" && existing.handled) {
460
+ return {
461
+ mode: "handled",
462
+ task: createdTaskTask,
463
+ ...toDiscriminatedApprovalResult(
464
+ normalizedTask.actions,
465
+ existing
466
+ )
467
+ };
468
+ }
469
+ if (existing?.status === "expired" || existing && Date.now() >= existing.validUntil) {
470
+ throw new TaskExpiredError("Task reached validUntil before a human completed it");
471
+ }
472
+ const remainingMs = deadline - Date.now();
473
+ await sleep(Math.min(pollIntervalMs, Math.max(0, remainingMs)));
474
+ }
475
+ if (validUntilMs !== void 0 && Date.now() >= validUntilMs) {
476
+ throw new TaskExpiredError("Task reached validUntil before a human completed it");
477
+ }
478
+ throw new TaskTimeoutError(`No human response within ${timeoutMs}ms`);
479
+ }
480
+ /**
481
+ * Get a task by public task id (returned as `task.taskId` from {@link sendToHuman}).
482
+ */
483
+ async getTask(taskId) {
484
+ const response = await fetch(`${this.baseUrl}/tasks/${taskId}`, {
485
+ method: "GET",
486
+ headers: {
487
+ "X-Api-Key": this.apiKey
488
+ }
489
+ });
490
+ if (response.status === 404) {
491
+ return null;
492
+ }
493
+ const data = await parseResponseBody(response);
494
+ if (!response.ok) {
495
+ throw new RobotRockError(
496
+ getErrorMessage(data, "Failed to get task"),
497
+ response.status,
498
+ data
499
+ );
500
+ }
501
+ return data;
502
+ }
503
+ /**
504
+ * Log a status update against a thread. The update shows in the inbox status
505
+ * bar and thread update log for every task in the thread.
506
+ */
507
+ async sendUpdate({ threadId, message, status }) {
508
+ if (!threadId) {
509
+ throw new RobotRockError("threadId is required to send an update", 400);
510
+ }
511
+ const validation = threadUpdateBodySchema.safeParse({ message, status });
512
+ if (!validation.success) {
513
+ throw new RobotRockError(
514
+ `Invalid update: ${validation.error.issues[0]?.message}`,
515
+ 400,
516
+ validation.error.issues
517
+ );
518
+ }
519
+ const response = await fetch(
520
+ `${this.baseUrl}/threads/${encodeURIComponent(threadId)}/updates`,
521
+ {
522
+ method: "POST",
523
+ headers: {
524
+ "Content-Type": "application/json",
525
+ "X-Api-Key": this.apiKey
526
+ },
527
+ body: JSON.stringify(validation.data)
528
+ }
529
+ );
530
+ const data = await parseResponseBody(response);
531
+ if (!response.ok) {
532
+ throw new RobotRockError(
533
+ getErrorMessage(data, "Failed to send update"),
534
+ response.status,
535
+ data
536
+ );
537
+ }
538
+ return data.update;
539
+ }
540
+ async cancelTask(taskId) {
541
+ const response = await fetch(`${this.baseUrl}/tasks/${taskId}/cancel`, {
542
+ method: "POST",
543
+ headers: {
544
+ "X-Api-Key": this.apiKey
545
+ }
546
+ });
547
+ if (!response.ok) {
548
+ const data = await parseResponseBody(response);
549
+ throw new RobotRockError(
550
+ getErrorMessage(data, "Failed to cancel task"),
551
+ response.status,
552
+ data
553
+ );
554
+ }
555
+ }
556
+ };
557
+ }
558
+ });
559
+
560
+ // src/env.ts
561
+ function resolveRobotRockConfig(overrides) {
562
+ const apiKey = overrides?.apiKey ?? process.env.ROBOTROCK_API_KEY;
563
+ if (!apiKey) {
564
+ throw new Error(
565
+ "RobotRock API key is required. Set ROBOTROCK_API_KEY or pass apiKey when creating the client."
566
+ );
567
+ }
568
+ const baseUrl = overrides?.baseUrl ?? process.env.ROBOTROCK_BASE_URL ?? process.env.ROBOTROCK_API_URL ?? DEFAULT_BASE_URL;
569
+ const app = overrides?.app ?? process.env.ROBOTROCK_APP;
570
+ return app ? { apiKey, baseUrl, app } : { apiKey, baseUrl };
571
+ }
572
+ var DEFAULT_BASE_URL;
573
+ var init_env = __esm({
574
+ "src/env.ts"() {
575
+ "use strict";
576
+ init_client();
577
+ DEFAULT_BASE_URL = "https://api.robotrock.io/v1";
578
+ }
579
+ });
580
+
581
+ // src/handler-webhook.ts
582
+ function isRobotRockHandlerWebhookPayload(value) {
583
+ if (typeof value !== "object" || value === null) {
584
+ return false;
585
+ }
586
+ const v = value;
587
+ if (typeof v.taskId !== "string" || typeof v.handledAt !== "string") {
588
+ return false;
589
+ }
590
+ const action = v.action;
591
+ if (typeof action !== "object" || action === null) {
592
+ return false;
593
+ }
594
+ const a = action;
595
+ return typeof a.id === "string" && "data" in a;
596
+ }
597
+ var init_handler_webhook = __esm({
598
+ "src/handler-webhook.ts"() {
599
+ "use strict";
600
+ }
601
+ });
602
+
603
+ // src/wait-timing.ts
604
+ function resolveWaitTiming(validUntilInput) {
605
+ const validUntilMs = validUntilInput !== void 0 ? parseValidUntilMs2(validUntilInput) : Date.now() + DEFAULT_WAIT_DURATION_MS;
606
+ const durationMs = validUntilMs - Date.now();
607
+ if (durationMs <= 0) {
608
+ throw new Error("validUntil must be in the future");
609
+ }
610
+ return {
611
+ validUntil: validUntilInput ?? new Date(validUntilMs),
612
+ timeout: durationMsToTimeout(durationMs)
613
+ };
614
+ }
615
+ function parseValidUntilMs2(value) {
616
+ if (value instanceof Date) {
617
+ const ms = value.getTime();
618
+ if (Number.isNaN(ms)) {
619
+ throw new Error("Invalid validUntil: Date is invalid");
620
+ }
621
+ return ms;
622
+ }
623
+ const parsed = Date.parse(value);
624
+ if (Number.isNaN(parsed)) {
625
+ throw new Error("Invalid validUntil: expected a parseable date string");
626
+ }
627
+ return parsed;
628
+ }
629
+ function durationMsToTimeout(durationMs) {
630
+ const seconds = Math.ceil(durationMs / 1e3);
631
+ if (seconds <= 0) {
632
+ throw new Error("validUntil must be in the future");
633
+ }
634
+ if (seconds >= 86400) {
635
+ return `${Math.ceil(seconds / 86400)}d`;
636
+ }
637
+ if (seconds >= 3600) {
638
+ return `${Math.ceil(seconds / 3600)}h`;
639
+ }
640
+ if (seconds >= 60) {
641
+ return `${Math.ceil(seconds / 60)}m`;
642
+ }
643
+ return `${seconds}s`;
644
+ }
645
+ var DEFAULT_WAIT_DURATION_MS;
646
+ var init_wait_timing = __esm({
647
+ "src/wait-timing.ts"() {
648
+ "use strict";
649
+ DEFAULT_WAIT_DURATION_MS = 7 * 24 * 60 * 60 * 1e3;
650
+ }
651
+ });
652
+
653
+ // src/trigger/index.ts
654
+ var trigger_exports = {};
655
+ __export(trigger_exports, {
656
+ approveByHumanTask: () => approveByHumanTask,
657
+ sendToHumanTask: () => sendToHumanTask
658
+ });
659
+ import { task, wait } from "@trigger.dev/sdk";
660
+ async function runSendToHuman(payload) {
661
+ const { validUntil: validUntilInput, ...taskInput } = payload;
662
+ const { validUntil, timeout } = resolveWaitTiming(validUntilInput);
663
+ const token = await wait.createToken({ timeout });
664
+ const baseConfig = resolveRobotRockConfig();
665
+ const client = createClient({
666
+ apiKey: baseConfig.apiKey,
667
+ baseUrl: baseConfig.baseUrl,
668
+ ...baseConfig.app ? { app: baseConfig.app } : {},
669
+ ...baseConfig.version ? { version: baseConfig.version } : {},
670
+ webhook: { url: token.url }
671
+ });
672
+ const sendResult = await client.sendToHuman({
673
+ ...taskInput,
674
+ validUntil
675
+ });
676
+ const outcome = await wait.forToken(token.id);
677
+ if (!outcome.ok) {
678
+ throw new Error(`Human response timed out before validUntil (${timeout})`);
679
+ }
680
+ const output = outcome.output;
681
+ if (!isRobotRockHandlerWebhookPayload(output)) {
682
+ throw new Error(
683
+ "Wait token completed with unexpected payload; expected RobotRock handler body (taskId, action.id, action.data)."
684
+ );
685
+ }
686
+ return toDiscriminatedApprovalResult(
687
+ payload.actions,
688
+ {
689
+ id: output.taskId,
690
+ createdAt: /* @__PURE__ */ new Date(),
691
+ status: "handled",
692
+ context: sendResult.task.context,
693
+ validUntil: Date.now(),
694
+ handledAt: new Date(output.handledAt).getTime(),
695
+ handled: {
696
+ action: {
697
+ id: output.action.id,
698
+ data: output.action.data
699
+ },
700
+ handledBy: output.handledBy
701
+ }
702
+ }
703
+ );
704
+ }
705
+ var APPROVE_BY_HUMAN_ACTIONS, sendToHumanTask, approveByHumanTask;
706
+ var init_trigger = __esm({
707
+ "src/trigger/index.ts"() {
708
+ "use strict";
709
+ init_client();
710
+ init_env();
711
+ init_approval_result();
712
+ init_handler_webhook();
713
+ init_wait_timing();
714
+ APPROVE_BY_HUMAN_ACTIONS = [
715
+ { id: "approve", title: "Approve" },
716
+ { id: "decline", title: "Decline" }
717
+ ];
718
+ sendToHumanTask = task({
719
+ id: "robotrock/send-to-human",
720
+ run: async (payload) => runSendToHuman(payload)
721
+ });
722
+ approveByHumanTask = task({
723
+ id: "robotrock/approve-by-human",
724
+ run: async (payload) => runSendToHuman({
725
+ ...payload,
726
+ actions: APPROVE_BY_HUMAN_ACTIONS
727
+ })
728
+ });
729
+ }
730
+ });
731
+
732
+ // src/workflow/index.ts
733
+ var workflow_exports = {};
734
+ __export(workflow_exports, {
735
+ approveByHumanInWorkflow: () => approveByHumanInWorkflow,
736
+ sendToHumanInWorkflow: () => sendToHumanInWorkflow,
737
+ sendUpdateInWorkflow: () => sendUpdateInWorkflow
738
+ });
739
+ import { createWebhook, sleep as sleep2 } from "workflow";
740
+ async function createRobotRockTaskForWebhook(input) {
741
+ "use step";
742
+ const baseConfig = resolveRobotRockConfig();
743
+ const client = createClient({
744
+ apiKey: baseConfig.apiKey,
745
+ baseUrl: baseConfig.baseUrl,
746
+ ...input.app ?? baseConfig.app ? { app: input.app ?? baseConfig.app } : {},
747
+ ...baseConfig.version ? { version: baseConfig.version } : {},
748
+ webhook: { url: input.webhookUrl }
749
+ });
750
+ return client.sendToHuman({
751
+ ...input.taskInput,
752
+ validUntil: input.validUntil
753
+ });
754
+ }
755
+ async function parseRobotRockWebhookRequest(request) {
756
+ "use step";
757
+ const body = await request.json();
758
+ if (!isRobotRockHandlerWebhookPayload(body)) {
759
+ throw new Error(
760
+ "Workflow webhook completed with unexpected payload; expected RobotRock handler body (taskId, action.id, action.data)."
761
+ );
762
+ }
763
+ return body;
764
+ }
765
+ async function sendToHumanInWorkflow(payload) {
766
+ var _stack = [];
767
+ try {
768
+ const { validUntil: validUntilInput, app, ...taskInput } = payload;
769
+ const { validUntil, timeout } = resolveWaitTiming(validUntilInput);
770
+ const timeoutMs = parseValidUntilMs2(validUntil) - Date.now();
771
+ const webhook = __using(_stack, createWebhook());
772
+ const sendResult = await createRobotRockTaskForWebhook({
773
+ webhookUrl: webhook.url,
774
+ app,
775
+ validUntil,
776
+ taskInput
777
+ });
778
+ const outcome = await Promise.race([
779
+ webhook.then((request) => parseRobotRockWebhookRequest(request)),
780
+ sleep2(timeoutMs).then(() => ({ timedOut: true }))
781
+ ]);
782
+ if ("timedOut" in outcome) {
783
+ throw new Error(`Human response timed out before validUntil (${timeout})`);
784
+ }
785
+ const output = outcome;
786
+ return toDiscriminatedApprovalResult(payload.actions, {
787
+ id: output.taskId,
788
+ createdAt: /* @__PURE__ */ new Date(),
789
+ status: "handled",
790
+ context: sendResult.task.context,
791
+ validUntil: Date.now(),
792
+ handledAt: new Date(output.handledAt).getTime(),
793
+ handled: {
794
+ action: {
795
+ id: output.action.id,
796
+ data: output.action.data
797
+ },
798
+ handledBy: output.handledBy
799
+ }
800
+ });
801
+ } catch (_) {
802
+ var _error = _, _hasError = true;
803
+ } finally {
804
+ __callDispose(_stack, _error, _hasError);
805
+ }
806
+ }
807
+ async function approveByHumanInWorkflow(payload) {
808
+ return sendToHumanInWorkflow({
809
+ ...payload,
810
+ actions: APPROVE_BY_HUMAN_ACTIONS2
811
+ });
812
+ }
813
+ async function sendRobotRockUpdate(payload) {
814
+ "use step";
815
+ const { app, ...update } = payload;
816
+ const baseConfig = resolveRobotRockConfig();
817
+ const client = createClient({
818
+ apiKey: baseConfig.apiKey,
819
+ baseUrl: baseConfig.baseUrl,
820
+ ...app ?? baseConfig.app ? { app: app ?? baseConfig.app } : {},
821
+ ...baseConfig.version ? { version: baseConfig.version } : {}
822
+ });
823
+ return client.sendUpdate(update);
824
+ }
825
+ async function sendUpdateInWorkflow(payload) {
826
+ return sendRobotRockUpdate(payload);
827
+ }
828
+ var APPROVE_BY_HUMAN_ACTIONS2;
829
+ var init_workflow = __esm({
830
+ "src/workflow/index.ts"() {
831
+ "use strict";
832
+ init_client();
833
+ init_env();
834
+ init_approval_result();
835
+ init_handler_webhook();
836
+ init_wait_timing();
837
+ APPROVE_BY_HUMAN_ACTIONS2 = [
838
+ { id: "approve", title: "Approve" },
839
+ { id: "decline", title: "Decline" }
840
+ ];
841
+ }
842
+ });
843
+
844
+ // src/ai/approve-by-human-tool.ts
845
+ import { tool } from "ai";
846
+
847
+ // src/ai/approve-by-human-tool-core.ts
848
+ import { z as z2 } from "zod";
849
+
850
+ // src/ai/context.ts
851
+ init_client();
852
+ init_env();
853
+ var APPROVE_BY_HUMAN_ACTIONS3 = [
854
+ { id: "approve", title: "Approve" },
855
+ { id: "decline", title: "Decline" }
856
+ ];
857
+ function createRobotRockAiTriggerContext(options = {}) {
858
+ return { mode: "trigger", ...options };
859
+ }
860
+ function isRobotRockClient(value) {
861
+ return typeof value === "object" && value !== null && "sendToHuman" in value && typeof value.sendToHuman === "function";
862
+ }
863
+ function normalizeRobotRockAiContext(clientOrContext) {
864
+ if (isRobotRockClient(clientOrContext)) {
865
+ return { mode: "polling", client: clientOrContext };
866
+ }
867
+ if (clientOrContext.mode === "trigger" || clientOrContext.mode === "workflow") {
868
+ return clientOrContext;
869
+ }
870
+ if (clientOrContext.mode === "polling" || clientOrContext.mode === void 0) {
871
+ if (!("client" in clientOrContext) || !clientOrContext.client) {
872
+ throw new Error("RobotRock AI polling mode requires `client` on the context object.");
873
+ }
874
+ return { mode: "polling", client: clientOrContext.client };
875
+ }
876
+ throw new Error(`Unknown RobotRock AI mode: ${String(clientOrContext.mode)}`);
877
+ }
878
+ async function sendToHumanForAi(context, payload) {
879
+ if (context.mode === "trigger") {
880
+ const { sendToHumanTask: sendToHumanTask2 } = await Promise.resolve().then(() => (init_trigger(), trigger_exports));
881
+ const waitResult = await sendToHumanTask2.triggerAndWait({
882
+ ...payload,
883
+ ...context.app ? { app: context.app } : {}
884
+ });
885
+ if (!waitResult.ok) {
886
+ throw waitResult.error;
887
+ }
888
+ return waitResult.output;
889
+ }
890
+ if (context.mode === "workflow") {
891
+ const { sendToHumanInWorkflow: sendToHumanInWorkflow2 } = await Promise.resolve().then(() => (init_workflow(), workflow_exports));
892
+ const result2 = await sendToHumanInWorkflow2({
893
+ ...payload,
894
+ ...context.app ? { app: context.app } : {}
895
+ });
896
+ return result2;
897
+ }
898
+ const result = await context.client.sendToHuman(payload);
899
+ if (result.mode !== "handled") {
900
+ throw new Error(
901
+ "RobotRock task was created but not handled. Configure client polling or webhook, or use mode: 'trigger' / 'workflow' for durable waits."
902
+ );
903
+ }
904
+ return {
905
+ actionId: result.actionId,
906
+ data: result.data,
907
+ handledBy: result.handledBy,
908
+ handledAt: result.handledAt,
909
+ taskId: result.taskId
910
+ };
911
+ }
912
+ async function sendUpdateForAi(context, payload) {
913
+ if (context.mode === "workflow") {
914
+ const { sendUpdateInWorkflow: sendUpdateInWorkflow2 } = await Promise.resolve().then(() => (init_workflow(), workflow_exports));
915
+ return sendUpdateInWorkflow2({
916
+ ...payload,
917
+ ...context.app ? { app: context.app } : {}
918
+ });
919
+ }
920
+ if (context.mode === "trigger") {
921
+ const client = createClient(
922
+ resolveRobotRockConfig(context.app ? { app: context.app } : void 0)
923
+ );
924
+ return client.sendUpdate(payload);
925
+ }
926
+ return context.client.sendUpdate(payload);
927
+ }
928
+ async function approveByHumanForAi(context, payload) {
929
+ if (context.mode === "trigger") {
930
+ const { approveByHumanTask: approveByHumanTask2 } = await Promise.resolve().then(() => (init_trigger(), trigger_exports));
931
+ const waitResult = await approveByHumanTask2.triggerAndWait({
932
+ ...payload,
933
+ ...context.app ? { app: context.app } : {}
934
+ });
935
+ if (!waitResult.ok) {
936
+ throw waitResult.error;
937
+ }
938
+ return waitResult.output;
939
+ }
940
+ if (context.mode === "workflow") {
941
+ const { approveByHumanInWorkflow: approveByHumanInWorkflow2 } = await Promise.resolve().then(() => (init_workflow(), workflow_exports));
942
+ return await approveByHumanInWorkflow2({
943
+ ...payload,
944
+ ...context.app ? { app: context.app } : {}
945
+ });
946
+ }
947
+ const result = await context.client.sendToHuman({
948
+ ...payload,
949
+ actions: APPROVE_BY_HUMAN_ACTIONS3
950
+ });
951
+ if (result.mode !== "handled") {
952
+ throw new Error(
953
+ "RobotRock approval was not handled. Configure client polling or use mode: 'trigger' / 'workflow' for durable waits."
954
+ );
955
+ }
956
+ return {
957
+ actionId: result.actionId,
958
+ data: result.data,
959
+ handledBy: result.handledBy,
960
+ handledAt: result.handledAt,
961
+ taskId: result.taskId
962
+ };
963
+ }
964
+
965
+ // src/ai/human-tool-result.ts
966
+ var APPROVE_IDS = /* @__PURE__ */ new Set(["approve", "approved"]);
967
+ var DECLINE_IDS = /* @__PURE__ */ new Set(["decline", "reject", "deny", "denied"]);
968
+ function toHumanToolResult(result) {
969
+ const payload = {
970
+ taskId: result.taskId,
971
+ actionId: result.actionId,
972
+ data: result.data,
973
+ handledBy: result.handledBy,
974
+ handledAt: result.handledAt.toISOString()
975
+ };
976
+ if (APPROVE_IDS.has(result.actionId)) {
977
+ payload.approved = true;
978
+ } else if (DECLINE_IDS.has(result.actionId)) {
979
+ payload.approved = false;
980
+ }
981
+ return payload;
982
+ }
983
+
984
+ // src/ai/approve-by-human-tool-core.ts
985
+ var approveByHumanInputSchema = z2.object({
986
+ type: z2.string().optional().describe("Task type slug; defaults to ai-approval"),
987
+ name: z2.string().describe("Short title for the approval request"),
988
+ description: z2.string().describe("What needs approval and the consequences of approving or declining"),
989
+ contextSummary: z2.string().optional().describe("Optional markdown summary shown to the reviewer")
990
+ });
991
+ function resolveApproveByHumanToolConfig(clientOrContext, maybeOptions = {}) {
992
+ const isDurable = typeof clientOrContext === "object" && clientOrContext !== null && "mode" in clientOrContext && (clientOrContext.mode === "trigger" || clientOrContext.mode === "workflow");
993
+ const aiContext = normalizeRobotRockAiContext(
994
+ isDurable ? {
995
+ mode: clientOrContext.mode,
996
+ app: clientOrContext.app
997
+ } : clientOrContext
998
+ );
999
+ const options = isDurable ? clientOrContext : maybeOptions;
1000
+ return { aiContext, options };
1001
+ }
1002
+ function buildApproveByHumanToolDefinition(clientOrContext, maybeOptions = {}) {
1003
+ const { aiContext, options } = resolveApproveByHumanToolConfig(
1004
+ clientOrContext,
1005
+ maybeOptions
1006
+ );
1007
+ const description = options.description ?? "Request explicit human approval before a sensitive or irreversible step. Returns whether the human approved or declined.";
1008
+ return {
1009
+ description,
1010
+ inputSchema: approveByHumanInputSchema,
1011
+ execute: async (input) => {
1012
+ const taskContext = input.contextSummary !== void 0 ? {
1013
+ data: { summary: input.contextSummary },
1014
+ ui: {
1015
+ summary: { "ui:widget": "textarea", "ui:options": { rows: 6 } }
1016
+ }
1017
+ } : void 0;
1018
+ const result = await approveByHumanForAi(aiContext, {
1019
+ type: input.type ?? options.defaultType ?? "ai-approval",
1020
+ name: input.name,
1021
+ description: input.description,
1022
+ context: taskContext
1023
+ });
1024
+ return toHumanToolResult(result);
1025
+ }
1026
+ };
1027
+ }
1028
+
1029
+ // src/ai/approve-by-human-tool.ts
1030
+ function approveByHumanTool(clientOrContext, maybeOptions = {}) {
1031
+ return tool(buildApproveByHumanToolDefinition(clientOrContext, maybeOptions));
1032
+ }
1033
+
1034
+ // src/ai/create-send-to-human-tool.ts
1035
+ import { tool as tool2 } from "ai";
1036
+
1037
+ // src/ai/create-send-to-human-tool-core.ts
1038
+ init_schemas();
1039
+ import { z as z3 } from "zod";
1040
+ var sendToHumanToolInputSchema = z3.object({
1041
+ type: z3.string().describe("Task type slug shown in the RobotRock inbox"),
1042
+ name: z3.string().describe("Short title for the human reviewer"),
1043
+ description: z3.string().optional().describe("What you need from the human and why you cannot proceed alone"),
1044
+ context: z3.object({
1045
+ data: z3.record(z3.string(), z3.unknown()).optional(),
1046
+ ui: z3.record(z3.string(), z3.unknown()).optional()
1047
+ }).optional().describe("Optional structured context for the inbox UI"),
1048
+ validUntil: z3.string().datetime().optional().describe("Optional ISO deadline for the task"),
1049
+ assignTo: assignToSchema.optional().describe(
1050
+ "Assign to tenant member emails and/or group slugs; narrows who sees the task in the inbox"
1051
+ )
1052
+ });
1053
+ function resolveSendToHumanToolConfig(clientOrOptions, maybeOptions) {
1054
+ const isDurable = typeof clientOrOptions === "object" && clientOrOptions !== null && "mode" in clientOrOptions && (clientOrOptions.mode === "trigger" || clientOrOptions.mode === "workflow");
1055
+ const aiContext = normalizeRobotRockAiContext(
1056
+ isDurable ? {
1057
+ mode: clientOrOptions.mode,
1058
+ app: clientOrOptions.app
1059
+ } : clientOrOptions
1060
+ );
1061
+ const options = isDurable ? clientOrOptions : maybeOptions;
1062
+ return { aiContext, options };
1063
+ }
1064
+ function buildSendToHumanToolDefinition(clientOrOptions, maybeOptions) {
1065
+ const { aiContext, options } = resolveSendToHumanToolConfig(clientOrOptions, maybeOptions);
1066
+ const description = options.description ?? "Request structured input or a decision from a human in the RobotRock inbox. Use when you lack required information, need policy approval, or must confirm an irreversible step.";
1067
+ return {
1068
+ description,
1069
+ inputSchema: sendToHumanToolInputSchema,
1070
+ execute: async (input) => {
1071
+ const taskContext = input.context ? {
1072
+ data: input.context.data ?? {},
1073
+ ui: input.context.ui
1074
+ } : void 0;
1075
+ const payload = {
1076
+ type: input.type ?? options.defaultType ?? "ai-human-input",
1077
+ name: input.name,
1078
+ description: input.description,
1079
+ context: taskContext,
1080
+ validUntil: input.validUntil,
1081
+ assignTo: input.assignTo,
1082
+ actions: options.actions,
1083
+ ...options.threadId ? { threadId: options.threadId } : {}
1084
+ };
1085
+ const result = await sendToHumanForAi(aiContext, payload);
1086
+ return toHumanToolResult(result);
1087
+ }
1088
+ };
1089
+ }
1090
+
1091
+ // src/ai/create-send-to-human-tool.ts
1092
+ function createSendToHumanTool(clientOrOptions, maybeOptions) {
1093
+ return tool2(buildSendToHumanToolDefinition(clientOrOptions, maybeOptions));
1094
+ }
1095
+
1096
+ // src/ai/create-send-update-tool.ts
1097
+ import { tool as tool3 } from "ai";
1098
+
1099
+ // src/ai/create-send-update-tool-core.ts
1100
+ init_schemas();
1101
+ init_client();
1102
+ import { z as z4 } from "zod";
1103
+ var sendUpdateToolInputSchema = z4.object({
1104
+ threadId: z4.string().optional().describe(
1105
+ "Thread to post the update to. Use the `threadId` returned by a prior send-to-human task. Omit only when a session threadId is configured on the tool."
1106
+ ),
1107
+ message: z4.string().describe("Short progress update (1-2 sentences) shown in the inbox status bar"),
1108
+ status: threadUpdateStatusSchema.optional().describe(
1109
+ "Lifecycle status driving the status-bar icon/color: info, queued, running, waiting, succeeded, failed, cancelled"
1110
+ )
1111
+ });
1112
+ function resolveSendUpdateToolConfig(clientOrOptions, maybeOptions = {}) {
1113
+ const isDurable = typeof clientOrOptions === "object" && clientOrOptions !== null && "mode" in clientOrOptions && (clientOrOptions.mode === "trigger" || clientOrOptions.mode === "workflow");
1114
+ const aiContext = normalizeRobotRockAiContext(
1115
+ isDurable ? {
1116
+ mode: clientOrOptions.mode,
1117
+ app: clientOrOptions.app
1118
+ } : clientOrOptions
1119
+ );
1120
+ const options = isDurable ? clientOrOptions : maybeOptions;
1121
+ return { aiContext, options };
1122
+ }
1123
+ function buildSendUpdateToolDefinition(clientOrOptions, maybeOptions = {}) {
1124
+ const { aiContext, options } = resolveSendUpdateToolConfig(clientOrOptions, maybeOptions);
1125
+ const description = options.description ?? "Post a short progress update to the RobotRock thread you are working on. Use to report status changes (running, succeeded, failed) so humans can follow along in the inbox.";
1126
+ return {
1127
+ description,
1128
+ inputSchema: sendUpdateToolInputSchema,
1129
+ execute: async (input) => {
1130
+ const threadId = input.threadId ?? options.threadId;
1131
+ if (!threadId) {
1132
+ throw new Error(
1133
+ "createSendUpdateTool: no threadId. Pass `threadId` from a prior send-to-human result, or configure a session `threadId` in the tool options."
1134
+ );
1135
+ }
1136
+ try {
1137
+ const update = await sendUpdateForAi(aiContext, {
1138
+ threadId,
1139
+ message: input.message,
1140
+ status: input.status
1141
+ });
1142
+ return { posted: true, threadId, update };
1143
+ } catch (error) {
1144
+ if (error instanceof RobotRockError && error.statusCode === 404) {
1145
+ return {
1146
+ posted: false,
1147
+ threadId,
1148
+ reason: "thread_not_found",
1149
+ message: `No task exists for thread "${threadId}" yet, so the update was not posted. Create a task on this thread with send-to-human before reporting progress.`
1150
+ };
1151
+ }
1152
+ throw error;
1153
+ }
1154
+ }
1155
+ };
1156
+ }
1157
+
1158
+ // src/ai/create-send-update-tool.ts
1159
+ function createSendUpdateTool(clientOrOptions, maybeOptions = {}) {
1160
+ return tool3(buildSendUpdateToolDefinition(clientOrOptions, maybeOptions));
1161
+ }
1162
+
1163
+ // src/ai/format-tool-approval-task.ts
1164
+ function defaultFormatToolApprovalTask(toolCall, options = {}) {
1165
+ const approveId = options.approveActionId ?? "approve";
1166
+ const denyId = options.denyActionId ?? "deny";
1167
+ let inputPreview;
1168
+ try {
1169
+ inputPreview = JSON.stringify(toolCall.input, null, 2);
1170
+ } catch {
1171
+ inputPreview = String(toolCall.input);
1172
+ }
1173
+ return {
1174
+ type: options.type ?? "ai-tool-approval",
1175
+ name: `Approve: ${toolCall.toolName}`,
1176
+ description: `Review the proposed \`${toolCall.toolName}\` tool call before it runs.`,
1177
+ context: {
1178
+ data: {
1179
+ toolName: toolCall.toolName,
1180
+ toolCallId: toolCall.toolCallId,
1181
+ input: toolCall.input
1182
+ },
1183
+ ui: {
1184
+ toolName: { "ui:widget": "text" },
1185
+ toolCallId: { "ui:widget": "text" },
1186
+ input: { "ui:widget": "textarea" }
1187
+ }
1188
+ },
1189
+ actions: [
1190
+ { id: approveId, title: "Approve execution" },
1191
+ { id: denyId, title: "Deny" }
1192
+ ]
1193
+ };
1194
+ }
1195
+
1196
+ // src/ai/tool-approval-bridge.ts
1197
+ function buildToolMatcher(config) {
1198
+ const names = config.tools ? new Set(config.tools) : null;
1199
+ return (toolCall) => {
1200
+ if (config.when?.(toolCall)) {
1201
+ return true;
1202
+ }
1203
+ if (names && names.has(toolCall.toolName)) {
1204
+ return true;
1205
+ }
1206
+ return false;
1207
+ };
1208
+ }
1209
+ function createRobotRockToolApproval(config) {
1210
+ const matches = buildToolMatcher(config);
1211
+ const toolApproval = async (options) => {
1212
+ return matches(options.toolCall) ? "user-approval" : void 0;
1213
+ };
1214
+ return toolApproval;
1215
+ }
1216
+ function createRobotRockNeedsApproval(config) {
1217
+ const matches = buildToolMatcher(config);
1218
+ return async (_input, options) => {
1219
+ const toolCall = findToolCallInMessages(options.messages, options.toolCallId);
1220
+ if (!toolCall) {
1221
+ return false;
1222
+ }
1223
+ return matches(toolCall);
1224
+ };
1225
+ }
1226
+ function applyRobotRockToolApprovalToTools(tools, config) {
1227
+ const names = config.tools ? new Set(config.tools) : null;
1228
+ const needsApproval = createRobotRockNeedsApproval(config);
1229
+ const next = { ...tools };
1230
+ for (const key of Object.keys(tools)) {
1231
+ const name = String(key);
1232
+ const shouldApply = names && names.has(name) || names === null && config.when !== void 0;
1233
+ if (!shouldApply) {
1234
+ continue;
1235
+ }
1236
+ const existing = tools[key];
1237
+ next[key] = {
1238
+ ...existing,
1239
+ needsApproval
1240
+ };
1241
+ }
1242
+ return next;
1243
+ }
1244
+ function findToolCallInMessages(messages, toolCallId) {
1245
+ if (!messages) {
1246
+ return void 0;
1247
+ }
1248
+ for (const message of messages) {
1249
+ if (typeof message !== "object" || message === null) {
1250
+ continue;
1251
+ }
1252
+ const role = message.role;
1253
+ if (role !== "assistant") {
1254
+ continue;
1255
+ }
1256
+ const content = message.content;
1257
+ if (!Array.isArray(content)) {
1258
+ continue;
1259
+ }
1260
+ for (const part of content) {
1261
+ if (typeof part !== "object" || part === null) {
1262
+ continue;
1263
+ }
1264
+ const p = part;
1265
+ if (p.type === "tool-call" && p.toolCallId === toolCallId) {
1266
+ return {
1267
+ toolName: String(p.toolName ?? ""),
1268
+ toolCallId,
1269
+ input: p.input
1270
+ };
1271
+ }
1272
+ }
1273
+ }
1274
+ return void 0;
1275
+ }
1276
+ function collectApprovalRequests(source) {
1277
+ const requests = [];
1278
+ const visit = (value) => {
1279
+ if (!value) {
1280
+ return;
1281
+ }
1282
+ if (Array.isArray(value)) {
1283
+ for (const item of value) {
1284
+ visit(item);
1285
+ }
1286
+ return;
1287
+ }
1288
+ if (typeof value !== "object") {
1289
+ return;
1290
+ }
1291
+ const obj = value;
1292
+ if (obj.type === "tool-approval-request" && typeof obj.approvalId === "string") {
1293
+ if (obj.isAutomatic === true) {
1294
+ return;
1295
+ }
1296
+ const embedded = obj.toolCall;
1297
+ const toolCallId = typeof obj.toolCallId === "string" ? obj.toolCallId : typeof embedded?.toolCallId === "string" ? embedded.toolCallId : void 0;
1298
+ let toolCall;
1299
+ if (embedded && typeof embedded.toolName === "string") {
1300
+ toolCall = {
1301
+ toolName: embedded.toolName,
1302
+ toolCallId: String(embedded.toolCallId ?? toolCallId ?? ""),
1303
+ input: embedded.input
1304
+ };
1305
+ } else if (toolCallId) {
1306
+ toolCall = findToolCallInMessages(
1307
+ source.messages,
1308
+ toolCallId
1309
+ );
1310
+ }
1311
+ requests.push({
1312
+ type: "tool-approval-request",
1313
+ approvalId: obj.approvalId,
1314
+ toolCallId,
1315
+ toolCall,
1316
+ isAutomatic: obj.isAutomatic === true
1317
+ });
1318
+ return;
1319
+ }
1320
+ if (Array.isArray(obj.content)) {
1321
+ visit(obj.content);
1322
+ }
1323
+ if (Array.isArray(obj.steps)) {
1324
+ visit(obj.steps);
1325
+ }
1326
+ if (obj.response && typeof obj.response === "object") {
1327
+ visit(obj.response.messages);
1328
+ }
1329
+ };
1330
+ visit(source);
1331
+ if (typeof source === "object" && source !== null && Array.isArray(source.content)) {
1332
+ visit(source.content);
1333
+ }
1334
+ const seen = /* @__PURE__ */ new Set();
1335
+ return requests.filter((r) => {
1336
+ if (seen.has(r.approvalId)) {
1337
+ return false;
1338
+ }
1339
+ seen.add(r.approvalId);
1340
+ return true;
1341
+ });
1342
+ }
1343
+ function resolveToolCallForRequest(request, source, messages) {
1344
+ if (request.toolCall) {
1345
+ return request.toolCall;
1346
+ }
1347
+ const toolCallId = request.toolCallId;
1348
+ if (toolCallId) {
1349
+ const fromMessages = findToolCallInMessages(messages, toolCallId);
1350
+ if (fromMessages) {
1351
+ return fromMessages;
1352
+ }
1353
+ }
1354
+ if (typeof source === "object" && source !== null && Array.isArray(source.toolCalls)) {
1355
+ for (const call of source.toolCalls) {
1356
+ if (call.toolCallId === toolCallId) {
1357
+ return {
1358
+ toolName: String(call.toolName ?? ""),
1359
+ toolCallId: String(call.toolCallId ?? ""),
1360
+ input: call.input
1361
+ };
1362
+ }
1363
+ }
1364
+ }
1365
+ throw new Error(
1366
+ `Could not resolve tool call for approval ${request.approvalId}. Pass messages that include the assistant tool-call.`
1367
+ );
1368
+ }
1369
+ async function resolveToolApprovalsViaRobotRock(clientOrContext, source, options = {}) {
1370
+ const context = normalizeRobotRockAiContext(clientOrContext);
1371
+ const approveId = options.approveActionId ?? "approve";
1372
+ const denyId = options.denyActionId ?? "deny";
1373
+ const formatTask = options.formatTask ?? defaultFormatToolApprovalTask;
1374
+ const baseMessages = typeof source === "object" && source !== null && Array.isArray(source.messages) ? [...source.messages] : [];
1375
+ const requests = collectApprovalRequests(source);
1376
+ const responses = [];
1377
+ for (const request of requests) {
1378
+ const toolCall = resolveToolCallForRequest(request, source, baseMessages);
1379
+ const taskInput = formatTask(toolCall, {
1380
+ approveActionId: approveId,
1381
+ denyActionId: denyId
1382
+ });
1383
+ const result = await sendToHumanForAi(context, taskInput);
1384
+ const approved = result.actionId === approveId;
1385
+ const reason = typeof result.data === "object" && result.data !== null && "reason" in result.data && typeof result.data.reason === "string" ? result.data.reason : approved ? "Approved in RobotRock inbox" : "Denied in RobotRock inbox";
1386
+ responses.push({
1387
+ type: "tool-approval-response",
1388
+ approvalId: request.approvalId,
1389
+ approved,
1390
+ reason
1391
+ });
1392
+ }
1393
+ const messages = [...baseMessages];
1394
+ if (responses.length > 0) {
1395
+ messages.push({ role: "tool", content: responses });
1396
+ }
1397
+ return { responses, messages };
1398
+ }
1399
+ async function runWithRobotRockApprovals(options) {
1400
+ const clientOrContext = options.context ?? (options.client ? options.client : (() => {
1401
+ throw new Error("runWithRobotRockApprovals requires `client` or `context`.");
1402
+ })());
1403
+ const maxRounds = options.maxRounds ?? 20;
1404
+ let messages = options.messages ? [...options.messages] : [];
1405
+ let lastResult;
1406
+ for (let round = 0; round < maxRounds; round++) {
1407
+ lastResult = await options.generate(messages);
1408
+ const { responses, messages: nextMessages } = await resolveToolApprovalsViaRobotRock(
1409
+ clientOrContext,
1410
+ { ...lastResult, messages },
1411
+ options.resolveOptions
1412
+ );
1413
+ if (responses.length === 0) {
1414
+ return lastResult;
1415
+ }
1416
+ messages = nextMessages;
1417
+ }
1418
+ throw new Error(`RobotRock approval loop exceeded maxRounds (${maxRounds})`);
1419
+ }
1420
+
1421
+ // src/ai/create-ai-tools.ts
1422
+ function createRobotRockAiTools(options) {
1423
+ const mode = options.mode ?? (options.client ? "polling" : "trigger");
1424
+ if (mode === "polling" && !options.client) {
1425
+ throw new Error("createRobotRockAiTools: polling mode requires `client`.");
1426
+ }
1427
+ const context = mode === "trigger" ? { mode: "trigger", app: options.app } : mode === "workflow" ? { mode: "workflow", app: options.app } : { mode: "polling", client: options.client };
1428
+ const durableContext = context.mode === "trigger" || context.mode === "workflow" ? context : null;
1429
+ const pollingClient = context.mode === "polling" ? context.client : void 0;
1430
+ return {
1431
+ context,
1432
+ approveByHuman: (toolOptions) => durableContext ? approveByHumanTool({ ...durableContext, ...toolOptions }) : approveByHumanTool(pollingClient, toolOptions),
1433
+ sendToHuman: (toolOptions) => durableContext ? createSendToHumanTool({
1434
+ ...durableContext,
1435
+ ...options.threadId ? { threadId: options.threadId } : {},
1436
+ ...toolOptions
1437
+ }) : createSendToHumanTool(pollingClient, {
1438
+ ...options.threadId ? { threadId: options.threadId } : {},
1439
+ ...toolOptions
1440
+ }),
1441
+ sendUpdate: (toolOptions = {}) => durableContext ? createSendUpdateTool({
1442
+ ...durableContext,
1443
+ ...options.threadId ? { threadId: options.threadId } : {},
1444
+ ...toolOptions
1445
+ }) : createSendUpdateTool(pollingClient, {
1446
+ ...options.threadId ? { threadId: options.threadId } : {},
1447
+ ...toolOptions
1448
+ })
1449
+ };
1450
+ }
15
1451
  export {
16
1452
  applyRobotRockToolApprovalToTools,
17
1453
  approveByHumanTool,