robotrock 0.8.5 → 1.0.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.
@@ -213,12 +213,12 @@ function validateUrlValue(value, widget) {
213
213
  }
214
214
  return null;
215
215
  }
216
- function validateContextPublicUrls(context) {
217
- if (!context?.data) {
216
+ function validateContextPublicUrls(context2) {
217
+ if (!context2?.data) {
218
218
  return null;
219
219
  }
220
- for (const [field, value] of Object.entries(context.data)) {
221
- const widget = widgetType(context.ui, field);
220
+ for (const [field, value] of Object.entries(context2.data)) {
221
+ const widget = widgetType(context2.ui, field);
222
222
  if (!widget) {
223
223
  continue;
224
224
  }
@@ -251,13 +251,15 @@ var triggerHandlerSchema = webhookHandlerSchema.extend({
251
251
  tokenId: z.string().min(1)
252
252
  });
253
253
  var handlerSchema = z.discriminatedUnion("type", [webhookHandlerSchema, triggerHandlerSchema]);
254
- var taskActionSchema = z.object({
254
+ var taskActionInputSchema = z.object({
255
255
  id: z.string().min(1),
256
256
  title: z.string().min(1),
257
257
  description: z.string().optional(),
258
258
  schema: jsonSchema7Schema.optional(),
259
259
  ui: uiSchemaSchema.optional(),
260
- data: z.record(z.string(), z.unknown()).optional(),
260
+ data: z.record(z.string(), z.unknown()).optional()
261
+ });
262
+ var taskActionSchema = taskActionInputSchema.extend({
261
263
  // Optional handlers for this action - if present, must have at least 1
262
264
  handlers: z.array(handlerSchema).min(1).optional()
263
265
  });
@@ -273,7 +275,8 @@ var contextDataSchema = z.object({
273
275
  data: z.record(z.string(), z.unknown()),
274
276
  ui: contextUiSchema
275
277
  }).optional();
276
- var taskContextObjectSchema = z.object({
278
+ var TASK_CONTEXT_FORMAT_VERSION = 2;
279
+ var taskContextObjectBaseSchema = z.object({
277
280
  /** Source application id; omitted tasks are grouped in the default inbox. */
278
281
  app: z.string().min(1).optional(),
279
282
  type: z.string().min(1),
@@ -281,9 +284,22 @@ var taskContextObjectSchema = z.object({
281
284
  description: z.string().optional(),
282
285
  validUntil: z.string().optional(),
283
286
  context: contextDataSchema,
287
+ /** Task context wire format version. @default 2 */
288
+ contextVersion: z.literal(2).optional(),
289
+ /** @deprecated Use `contextVersion`. Accepted on ingest only. */
284
290
  version: z.literal(2).optional(),
285
291
  actions: z.array(taskActionSchema).min(1, "At least one action is required")
286
292
  });
293
+ function normalizeTaskContextVersion(data) {
294
+ const { version: legacyVersion, contextVersion, ...rest } = data;
295
+ return {
296
+ ...rest,
297
+ contextVersion: contextVersion ?? legacyVersion ?? TASK_CONTEXT_FORMAT_VERSION
298
+ };
299
+ }
300
+ var taskContextObjectSchema = taskContextObjectBaseSchema.transform(
301
+ normalizeTaskContextVersion
302
+ );
287
303
  function refineContextPublicUrls(data, ctx) {
288
304
  const error = validateContextPublicUrls(data.context);
289
305
  if (error) {
@@ -327,61 +343,11 @@ var threadUpdateInputSchema = z.object({
327
343
  });
328
344
  var taskPriorities = ["low", "normal", "high", "urgent"];
329
345
  var taskPrioritySchema = z.enum(taskPriorities);
330
- var nonNegativeInt = z.number().int().nonnegative();
331
- var agentCostTokensSchema = z.object({
332
- input: nonNegativeInt.optional(),
333
- output: nonNegativeInt.optional(),
334
- total: nonNegativeInt.optional()
335
- });
336
- var agentCostSchema = z.object({
337
- tokens: agentCostTokensSchema.optional(),
338
- eur: z.number().nonnegative().optional(),
339
- usd: z.number().nonnegative().optional()
340
- });
341
- var AGENT_INFO_MAX_KEYS = 32;
342
- var AGENT_TOOL_CALLS_MAX_KEYS = 64;
343
- var AGENT_OTEL_SPANS_MAX = 32;
344
- var agentToolCallsSchema = z.record(z.string().min(1), nonNegativeInt);
345
- var agentOtelSpanStatusSchema = z.enum(["ok", "error"]);
346
- var agentOtelSpanSummarySchema = z.object({
347
- name: z.string().min(1),
348
- durationMs: z.number().nonnegative(),
349
- status: agentOtelSpanStatusSchema
350
- });
351
- var agentOtelSchema = z.object({
352
- traceId: z.string().min(1),
353
- spanId: z.string().min(1).optional(),
354
- rootDurationMs: z.number().nonnegative().optional(),
355
- spans: z.array(agentOtelSpanSummarySchema).max(AGENT_OTEL_SPANS_MAX).optional()
356
- });
357
346
  var agentTelemetrySchema = z.object({
358
347
  /** Agent or app version (semver, git SHA, deploy tag — caller-defined). */
359
- version: z.string().min(1).optional(),
360
- /** Total tool invocations. When omitted but `toolCalls` is set, the sum of counts is used. */
361
- toolCallCount: nonNegativeInt.optional(),
362
- /** Per-tool invocation counts keyed by tool name. */
363
- toolCalls: agentToolCallsSchema.optional(),
364
- cost: agentCostSchema.optional(),
365
- /** Free-form metadata for experiments (model, prompt variant, etc.). */
366
- info: z.record(z.string(), z.unknown()).optional(),
367
- /** Structured OpenTelemetry span snapshot for feedback analysis. */
368
- otel: agentOtelSchema.optional()
369
- }).refine(
370
- (data) => {
371
- const keys = data.info ? Object.keys(data.info) : [];
372
- return keys.length <= AGENT_INFO_MAX_KEYS;
373
- },
374
- { message: `agent.info may contain at most ${AGENT_INFO_MAX_KEYS} keys` }
375
- ).refine(
376
- (data) => {
377
- const keys = data.toolCalls ? Object.keys(data.toolCalls) : [];
378
- return keys.length <= AGENT_TOOL_CALLS_MAX_KEYS;
379
- },
380
- {
381
- message: `agent.toolCalls may contain at most ${AGENT_TOOL_CALLS_MAX_KEYS} tool names`
382
- }
383
- );
384
- var createTaskBodySchema = taskContextObjectSchema.extend({
348
+ version: z.string().min(1)
349
+ });
350
+ var createTaskBodySchema = taskContextObjectBaseSchema.extend({
385
351
  assignTo: assignToSchema.optional(),
386
352
  /**
387
353
  * Groups related tasks together. When omitted, the server generates one and
@@ -398,9 +364,59 @@ var createTaskBodySchema = taskContextObjectSchema.extend({
398
364
  * the inbox status bar and the thread update log.
399
365
  */
400
366
  update: threadUpdateInputSchema.optional(),
401
- /** Agent telemetry (version, cost, tool calls) — not shown in inbox UI. */
367
+ /** Agent release version — not shown in inbox UI. */
402
368
  agent: agentTelemetrySchema.optional()
403
- }).superRefine(refineContextPublicUrls);
369
+ }).transform(normalizeTaskContextVersion).superRefine(refineContextPublicUrls);
370
+ var agentChatSeedActionSchema = z.object({
371
+ /** Stable action id echoed back on submit; auto-derived from title when omitted. */
372
+ id: z.string().min(1).optional(),
373
+ title: z.string().min(1),
374
+ description: z.string().optional(),
375
+ /** JSON Schema object for the form fields. */
376
+ schema: z.record(z.string(), z.unknown()).optional(),
377
+ /** RJSF ui schema overrides (ui:widget, ui:enumNames, etc.). */
378
+ ui: z.record(z.string(), z.unknown()).optional(),
379
+ /** Optional default field values. */
380
+ data: z.record(z.string(), z.unknown()).optional()
381
+ });
382
+ var agentChatSeedMessageSchema = z.object({
383
+ role: z.enum(["user", "assistant"]),
384
+ text: z.string().min(1),
385
+ /** Optional display-name override for the message sender. */
386
+ senderName: z.string().min(1).optional(),
387
+ /**
388
+ * Optional structured input request rendered as a styled RobotRock action
389
+ * widget below the message text. Use this instead of asking the user to reply
390
+ * in plain text so the request is always a proper action form.
391
+ */
392
+ requestActionInput: z.object({
393
+ prompt: z.string().optional(),
394
+ action: agentChatSeedActionSchema
395
+ }).optional()
396
+ });
397
+ var createAgentChatBodySchema = z.object({
398
+ /** Trigger chat agent id (task slug). Required unless `parentChatId` is set. */
399
+ agentIdentifier: z.string().min(1).optional(),
400
+ /** Inherit tenant/connection/agent from an existing chat (agent-spawned chats). */
401
+ parentChatId: z.string().min(1).optional(),
402
+ /** Convex tenantTriggerConnections id; resolved from the tenant when omitted. */
403
+ connectionId: z.string().min(1).optional(),
404
+ /** Source application id; groups the chat under an inbox section. */
405
+ app: z.string().min(1).optional(),
406
+ assignTo: assignToSchema.optional(),
407
+ title: z.string().min(1),
408
+ messages: z.array(agentChatSeedMessageSchema).default([]),
409
+ /** Provenance label stored on the session ("cron" | "agent" | ...). */
410
+ source: z.string().min(1).optional()
411
+ }).refine((data) => Boolean(data.agentIdentifier || data.parentChatId), {
412
+ message: "Provide either agentIdentifier or parentChatId"
413
+ });
414
+ var agentChatTransportBodySchema = z.object({
415
+ chatId: z.string().min(1),
416
+ publicAccessToken: z.string().min(1),
417
+ lastEventId: z.string().optional(),
418
+ isStreaming: z.boolean().optional()
419
+ });
404
420
 
405
421
  // src/schemas/index.ts
406
422
  var safeUrlSchema2 = z2.string().refine((url) => isPublicHttpUrl(url), {
@@ -423,13 +439,15 @@ var triggerHandlerSchema2 = webhookHandlerSchema2.extend({
423
439
  tokenId: z2.string().min(1)
424
440
  });
425
441
  var handlerSchema2 = z2.discriminatedUnion("type", [webhookHandlerSchema2, triggerHandlerSchema2]);
426
- var taskActionSchema2 = z2.object({
442
+ var taskActionInputSchema2 = z2.object({
427
443
  id: z2.string().min(1),
428
444
  title: z2.string().min(1),
429
445
  description: z2.string().optional(),
430
446
  schema: jsonSchema7Schema2.optional(),
431
447
  ui: uiSchemaSchema2.optional(),
432
- data: z2.record(z2.string(), z2.unknown()).optional(),
448
+ data: z2.record(z2.string(), z2.unknown()).optional()
449
+ });
450
+ var taskActionSchema2 = taskActionInputSchema2.extend({
433
451
  handlers: z2.array(handlerSchema2).min(1).optional()
434
452
  });
435
453
  var uiFieldSchemaSchema2 = z2.object({
@@ -444,16 +462,29 @@ var contextDataSchema2 = z2.object({
444
462
  data: z2.record(z2.string(), z2.unknown()),
445
463
  ui: contextUiSchema2
446
464
  }).optional();
447
- var taskContextObjectSchema2 = z2.object({
465
+ var TASK_CONTEXT_FORMAT_VERSION2 = 2;
466
+ var taskContextObjectBaseSchema2 = z2.object({
448
467
  app: z2.string().min(1).optional(),
449
468
  type: z2.string().min(1),
450
469
  name: z2.string().min(1),
451
470
  description: z2.string().optional(),
452
471
  validUntil: z2.string().optional(),
453
472
  context: contextDataSchema2,
473
+ contextVersion: z2.literal(2).optional(),
474
+ /** @deprecated Use `contextVersion`. Accepted on ingest only. */
454
475
  version: z2.literal(2).optional(),
455
476
  actions: z2.array(taskActionSchema2).min(1, "At least one action is required")
456
477
  });
478
+ function normalizeTaskContextVersion2(data) {
479
+ const { version: legacyVersion, contextVersion, ...rest } = data;
480
+ return {
481
+ ...rest,
482
+ contextVersion: contextVersion ?? legacyVersion ?? TASK_CONTEXT_FORMAT_VERSION2
483
+ };
484
+ }
485
+ var taskContextObjectSchema2 = taskContextObjectBaseSchema2.transform(
486
+ normalizeTaskContextVersion2
487
+ );
457
488
  function refineContextPublicUrls2(data, ctx) {
458
489
  const error = validateContextPublicUrls(data.context);
459
490
  if (error) {
@@ -497,56 +528,10 @@ var threadUpdateInputSchema2 = z2.object({
497
528
  });
498
529
  var taskPriorities2 = ["low", "normal", "high", "urgent"];
499
530
  var taskPrioritySchema2 = z2.enum(taskPriorities2);
500
- var nonNegativeInt2 = z2.number().int().nonnegative();
501
- var agentCostTokensSchema2 = z2.object({
502
- input: nonNegativeInt2.optional(),
503
- output: nonNegativeInt2.optional(),
504
- total: nonNegativeInt2.optional()
505
- });
506
- var agentCostSchema2 = z2.object({
507
- tokens: agentCostTokensSchema2.optional(),
508
- eur: z2.number().nonnegative().optional(),
509
- usd: z2.number().nonnegative().optional()
510
- });
511
- var AGENT_INFO_MAX_KEYS2 = 32;
512
- var AGENT_TOOL_CALLS_MAX_KEYS2 = 64;
513
- var AGENT_OTEL_SPANS_MAX2 = 32;
514
- var agentToolCallsSchema2 = z2.record(z2.string().min(1), nonNegativeInt2);
515
- var agentOtelSpanStatusSchema2 = z2.enum(["ok", "error"]);
516
- var agentOtelSpanSummarySchema2 = z2.object({
517
- name: z2.string().min(1),
518
- durationMs: z2.number().nonnegative(),
519
- status: agentOtelSpanStatusSchema2
520
- });
521
- var agentOtelSchema2 = z2.object({
522
- traceId: z2.string().min(1),
523
- spanId: z2.string().min(1).optional(),
524
- rootDurationMs: z2.number().nonnegative().optional(),
525
- spans: z2.array(agentOtelSpanSummarySchema2).max(AGENT_OTEL_SPANS_MAX2).optional()
526
- });
527
531
  var agentTelemetrySchema2 = z2.object({
528
- version: z2.string().min(1).optional(),
529
- toolCallCount: nonNegativeInt2.optional(),
530
- toolCalls: agentToolCallsSchema2.optional(),
531
- cost: agentCostSchema2.optional(),
532
- info: z2.record(z2.string(), z2.unknown()).optional(),
533
- otel: agentOtelSchema2.optional()
534
- }).refine(
535
- (data) => {
536
- const keys = data.info ? Object.keys(data.info) : [];
537
- return keys.length <= AGENT_INFO_MAX_KEYS2;
538
- },
539
- { message: `agent.info may contain at most ${AGENT_INFO_MAX_KEYS2} keys` }
540
- ).refine(
541
- (data) => {
542
- const keys = data.toolCalls ? Object.keys(data.toolCalls) : [];
543
- return keys.length <= AGENT_TOOL_CALLS_MAX_KEYS2;
544
- },
545
- {
546
- message: `agent.toolCalls may contain at most ${AGENT_TOOL_CALLS_MAX_KEYS2} tool names`
547
- }
548
- );
549
- var createTaskBodySchema2 = taskContextObjectSchema2.extend({
532
+ version: z2.string().min(1)
533
+ });
534
+ var createTaskBodySchema2 = taskContextObjectBaseSchema2.extend({
550
535
  assignTo: assignToSchema2.optional(),
551
536
  /**
552
537
  * Groups related tasks together. When omitted, the server generates one and
@@ -564,7 +549,7 @@ var createTaskBodySchema2 = taskContextObjectSchema2.extend({
564
549
  */
565
550
  update: threadUpdateInputSchema2.optional(),
566
551
  agent: agentTelemetrySchema2.optional()
567
- }).superRefine(refineContextPublicUrls2);
552
+ }).transform(normalizeTaskContextVersion2).superRefine(refineContextPublicUrls2);
568
553
  var threadUpdateBodySchema = threadUpdateInputSchema2;
569
554
 
570
555
  // src/approval-result.ts
@@ -594,12 +579,159 @@ function toDiscriminatedApprovalResult(actions, task) {
594
579
  };
595
580
  }
596
581
 
582
+ // src/http.ts
583
+ var RobotRockError = class extends Error {
584
+ constructor(message, statusCode, response) {
585
+ super(message);
586
+ this.statusCode = statusCode;
587
+ this.response = response;
588
+ this.name = "RobotRockError";
589
+ }
590
+ };
591
+ async function parseResponseBody(response) {
592
+ const contentType = response.headers.get("content-type") ?? "";
593
+ const bodyText = await response.text();
594
+ if (!bodyText) {
595
+ return null;
596
+ }
597
+ if (contentType.toLowerCase().includes("application/json")) {
598
+ try {
599
+ return JSON.parse(bodyText);
600
+ } catch {
601
+ }
602
+ }
603
+ try {
604
+ return JSON.parse(bodyText);
605
+ } catch {
606
+ return bodyText;
607
+ }
608
+ }
609
+ function getErrorMessage(data, fallback) {
610
+ if (data && typeof data === "object" && !Array.isArray(data)) {
611
+ const maybeMessage = data.message;
612
+ if (typeof maybeMessage === "string" && maybeMessage.trim()) {
613
+ return maybeMessage;
614
+ }
615
+ }
616
+ if (typeof data === "string" && data.trim()) {
617
+ const compact = data.replace(/\s+/g, " ").trim();
618
+ const snippet = compact.length > 180 ? `${compact.slice(0, 180)}...` : compact;
619
+ return `${fallback}. Server returned non-JSON response: ${snippet}`;
620
+ }
621
+ return fallback;
622
+ }
623
+
624
+ // ../core/src/handler-retry.ts
625
+ var HANDLER_RETRY_DELAYS_MS = [
626
+ 6e4,
627
+ // 1m
628
+ 3e5,
629
+ // 5m
630
+ 18e5,
631
+ // 30m
632
+ 36e5,
633
+ // 1h
634
+ 216e5,
635
+ // 6h
636
+ 864e5,
637
+ // 24h
638
+ 1728e5
639
+ // 48h
640
+ ];
641
+ var HANDLER_MAX_ATTEMPTS = HANDLER_RETRY_DELAYS_MS.length + 1;
642
+
643
+ // ../core/src/attribution/index.ts
644
+ import { z as z3 } from "zod";
645
+
646
+ // ../core/src/app-url.ts
647
+ var PRODUCTION_APP_URL = "https://app.robotrock.io";
648
+ var APP_SIGNUP_URL = `${PRODUCTION_APP_URL}/sign-up`;
649
+
650
+ // ../core/src/attribution/index.ts
651
+ var ATTRIBUTION_COOKIE_MAX_AGE = 60 * 60 * 24 * 90;
652
+ var attributionSchema = z3.object({
653
+ utmSource: z3.string().optional(),
654
+ utmMedium: z3.string().optional(),
655
+ utmCampaign: z3.string().optional(),
656
+ utmContent: z3.string().optional(),
657
+ utmTerm: z3.string().optional(),
658
+ gclid: z3.string().optional(),
659
+ landingUrl: z3.string().optional(),
660
+ referrer: z3.string().optional(),
661
+ capturedAt: z3.number()
662
+ });
663
+
664
+ // src/chats.ts
665
+ function createChatsApi(config) {
666
+ const headers = () => ({
667
+ "Content-Type": "application/json",
668
+ "X-Api-Key": config.apiKey
669
+ });
670
+ return {
671
+ async create(input) {
672
+ const bodyPayload = {
673
+ ...input,
674
+ app: input.app ?? config.app,
675
+ messages: input.messages ?? []
676
+ };
677
+ const validation = createAgentChatBodySchema.safeParse(bodyPayload);
678
+ if (!validation.success) {
679
+ throw new RobotRockError(
680
+ `Invalid chat: ${validation.error.issues[0]?.message}`,
681
+ 400,
682
+ validation.error.issues
683
+ );
684
+ }
685
+ const response = await fetch(`${config.baseUrl}/agent-chats`, {
686
+ method: "POST",
687
+ headers: headers(),
688
+ body: JSON.stringify(validation.data)
689
+ });
690
+ const data = await parseResponseBody(response);
691
+ if (!response.ok) {
692
+ throw new RobotRockError(
693
+ getErrorMessage(data, "Failed to create chat"),
694
+ response.status,
695
+ data
696
+ );
697
+ }
698
+ const result = data;
699
+ return { tenantSlug: result.tenantSlug, chats: result.chats };
700
+ },
701
+ async close(chatId, options) {
702
+ if (!chatId) {
703
+ throw new RobotRockError("chatId is required to close a chat", 400);
704
+ }
705
+ const response = await fetch(
706
+ `${config.baseUrl}/agent-chats/${encodeURIComponent(chatId)}/close`,
707
+ {
708
+ method: "POST",
709
+ headers: headers(),
710
+ body: JSON.stringify({ reason: options?.reason })
711
+ }
712
+ );
713
+ if (!response.ok) {
714
+ const data = await parseResponseBody(response);
715
+ throw new RobotRockError(
716
+ getErrorMessage(data, "Failed to close chat"),
717
+ response.status,
718
+ data
719
+ );
720
+ }
721
+ }
722
+ };
723
+ }
724
+
597
725
  // src/client.ts
598
726
  var DEFAULT_POLL_INTERVAL_MS = 2e3;
599
727
  var DEFAULT_TIMEOUT_MS = 24 * 60 * 60 * 1e3;
600
728
  function sleep(ms) {
601
729
  return new Promise((resolve) => setTimeout(resolve, ms));
602
730
  }
731
+ function resolveAgentVersionFromEnv() {
732
+ const fromEnv = process.env.AGENT_VERSION?.trim() || process.env.ROBOTROCK_AGENT_VERSION?.trim();
733
+ return fromEnv || void 0;
734
+ }
603
735
  function parseValidUntilMs(value) {
604
736
  if (value === void 0) {
605
737
  return void 0;
@@ -627,21 +759,18 @@ function serializeValidUntil(value) {
627
759
  }
628
760
  throw new RobotRockError("Invalid validUntil: expected a Date or parseable date string", 400);
629
761
  }
630
- var RobotRockError = class extends Error {
631
- constructor(message, statusCode, response) {
632
- super(message);
633
- this.statusCode = statusCode;
634
- this.response = response;
635
- this.name = "RobotRockError";
636
- }
637
- };
638
762
  var RobotRock = class {
639
763
  apiKey;
640
764
  baseUrl;
641
765
  app;
642
- version;
766
+ agentVersion;
767
+ contextVersion;
643
768
  webhook;
644
769
  polling;
770
+ /** Task CRUD: `create`, `get`, `cancel`, `sendUpdate`. */
771
+ tasks;
772
+ /** Chat CRUD: `create`, `close`. */
773
+ chats;
645
774
  constructor(config) {
646
775
  if (config.webhook && config.polling) {
647
776
  throw new Error(
@@ -658,26 +787,37 @@ var RobotRock = class {
658
787
  const rawBase = config.baseUrl ?? "https://api.robotrock.io/v1";
659
788
  this.baseUrl = rawBase.replace(/\/+$/, "");
660
789
  this.app = config.app;
661
- this.version = config.version ?? 2;
790
+ this.agentVersion = config.version ?? resolveAgentVersionFromEnv();
791
+ this.contextVersion = config.advanced?.contextVersion ?? TASK_CONTEXT_FORMAT_VERSION2;
662
792
  this.webhook = config.webhook;
663
793
  this.polling = config.polling ?? {};
794
+ this.tasks = {
795
+ create: (task) => this.createTaskRequest(task),
796
+ get: (taskId) => this.getTaskById(taskId),
797
+ cancel: (taskId) => this.cancelTaskRequest(taskId),
798
+ sendUpdate: (input) => this.sendThreadUpdate(input)
799
+ };
800
+ this.chats = createChatsApi({
801
+ baseUrl: this.baseUrl,
802
+ apiKey: this.apiKey,
803
+ app: this.app
804
+ });
664
805
  }
665
- /**
666
- * Create a task via POST /v1 without waiting for a human response.
667
- */
668
- async createTask(task) {
806
+ async createTaskRequest(task) {
669
807
  const normalizedTask = normalizeSendToHumanInput(task, {
670
808
  webhook: this.webhook,
671
809
  app: this.app,
672
- version: this.version
810
+ contextVersion: this.contextVersion,
811
+ agentVersion: this.agentVersion
673
812
  });
813
+ const agentVersion = task.version ?? this.agentVersion;
674
814
  const bodyPayload = {
675
815
  ...normalizedTask,
676
816
  ...task.assignTo !== void 0 ? { assignTo: task.assignTo } : {},
677
817
  ...task.threadId !== void 0 ? { threadId: task.threadId } : {},
678
818
  ...task.priority !== void 0 ? { priority: task.priority } : {},
679
819
  ...task.update !== void 0 ? { update: task.update } : {},
680
- ...task.agent !== void 0 ? { agent: task.agent } : {}
820
+ ...agentVersion !== void 0 ? { agent: { version: agentVersion } } : {}
681
821
  };
682
822
  const validation = createTaskBodySchema2.safeParse(bodyPayload);
683
823
  if (!validation.success) {
@@ -713,9 +853,10 @@ var RobotRock = class {
713
853
  const normalizedTask = normalizeSendToHumanInput(task, {
714
854
  webhook: this.webhook,
715
855
  app: this.app,
716
- version: this.version
856
+ contextVersion: this.contextVersion,
857
+ agentVersion: this.agentVersion
717
858
  });
718
- const createdTaskTask = await this.createTask(task);
859
+ const createdTaskTask = await this.createTaskRequest(task);
719
860
  const hasHandlers = normalizedTask.actions.some(
720
861
  (action) => Array.isArray(action.handlers) && action.handlers.length > 0
721
862
  );
@@ -732,7 +873,7 @@ var RobotRock = class {
732
873
  const deadline = validUntilMs !== void 0 ? Math.min(pollingDeadline, validUntilMs) : pollingDeadline;
733
874
  const taskId = createdTaskTask.taskId;
734
875
  while (Date.now() < deadline) {
735
- const existing = await this.getTask(taskId);
876
+ const existing = await this.getTaskById(taskId);
736
877
  if (existing?.status === "handled" && existing.handled) {
737
878
  return {
738
879
  mode: "handled",
@@ -754,10 +895,35 @@ var RobotRock = class {
754
895
  }
755
896
  throw new TaskTimeoutError(`No human response within ${timeoutMs}ms`);
756
897
  }
898
+ /**
899
+ * Create a task via POST /v1 without waiting for a human response.
900
+ * @deprecated Use `client.tasks.create()` instead.
901
+ */
902
+ async createTask(task) {
903
+ return this.tasks.create(task);
904
+ }
757
905
  /**
758
906
  * Get a task by public task id (returned as `task.taskId` from {@link sendToHuman}).
907
+ * @deprecated Use `client.tasks.get()` instead.
759
908
  */
760
909
  async getTask(taskId) {
910
+ return this.tasks.get(taskId);
911
+ }
912
+ /**
913
+ * Log a status update against a thread.
914
+ * @deprecated Use `client.tasks.sendUpdate()` instead.
915
+ */
916
+ async sendUpdate(input) {
917
+ return this.tasks.sendUpdate(input);
918
+ }
919
+ /**
920
+ * Cancel a task by public task id.
921
+ * @deprecated Use `client.tasks.cancel()` instead.
922
+ */
923
+ async cancelTask(taskId) {
924
+ return this.tasks.cancel(taskId);
925
+ }
926
+ async getTaskById(taskId) {
761
927
  const response = await fetch(`${this.baseUrl}/tasks/${taskId}`, {
762
928
  method: "GET",
763
929
  headers: {
@@ -777,11 +943,11 @@ var RobotRock = class {
777
943
  }
778
944
  return data;
779
945
  }
780
- /**
781
- * Log a status update against a thread. The update shows in the inbox status
782
- * bar and thread update log for every task in the thread.
783
- */
784
- async sendUpdate({ threadId, message, status }) {
946
+ async sendThreadUpdate({
947
+ threadId,
948
+ message,
949
+ status
950
+ }) {
785
951
  if (!threadId) {
786
952
  throw new RobotRockError("threadId is required to send an update", 400);
787
953
  }
@@ -814,7 +980,7 @@ var RobotRock = class {
814
980
  }
815
981
  return data.update;
816
982
  }
817
- async cancelTask(taskId) {
983
+ async cancelTaskRequest(taskId) {
818
984
  const response = await fetch(`${this.baseUrl}/tasks/${taskId}/cancel`, {
819
985
  method: "POST",
820
986
  headers: {
@@ -857,6 +1023,7 @@ function normalizeSendToHumanInput(task, clientDefaults) {
857
1023
  threadId: _threadId,
858
1024
  priority: _priority,
859
1025
  update: _update,
1026
+ version: _version,
860
1027
  validUntil,
861
1028
  app: taskApp,
862
1029
  ...rest
@@ -866,44 +1033,12 @@ function normalizeSendToHumanInput(task, clientDefaults) {
866
1033
  const app = taskApp ?? clientDefaults.app;
867
1034
  return {
868
1035
  ...rest,
869
- version: clientDefaults.version,
1036
+ contextVersion: clientDefaults.contextVersion,
870
1037
  ...app ? { app } : {},
871
1038
  ...validUntil !== void 0 ? { validUntil: serializeValidUntil(validUntil) } : {},
872
1039
  actions: normalizedActions
873
1040
  };
874
1041
  }
875
- async function parseResponseBody(response) {
876
- const contentType = response.headers.get("content-type") ?? "";
877
- const bodyText = await response.text();
878
- if (!bodyText) {
879
- return null;
880
- }
881
- if (contentType.toLowerCase().includes("application/json")) {
882
- try {
883
- return JSON.parse(bodyText);
884
- } catch {
885
- }
886
- }
887
- try {
888
- return JSON.parse(bodyText);
889
- } catch {
890
- return bodyText;
891
- }
892
- }
893
- function getErrorMessage(data, fallback) {
894
- if (data && typeof data === "object" && !Array.isArray(data)) {
895
- const maybeMessage = data.message;
896
- if (typeof maybeMessage === "string" && maybeMessage.trim()) {
897
- return maybeMessage;
898
- }
899
- }
900
- if (typeof data === "string" && data.trim()) {
901
- const compact = data.replace(/\s+/g, " ").trim();
902
- const snippet = compact.length > 180 ? `${compact.slice(0, 180)}...` : compact;
903
- return `${fallback}. Server returned non-JSON response: ${snippet}`;
904
- }
905
- return fallback;
906
- }
907
1042
 
908
1043
  // src/env.ts
909
1044
  var DEFAULT_BASE_URL = "https://api.robotrock.io/v1";
@@ -980,6 +1115,200 @@ function durationMsToTimeout(durationMs) {
980
1115
  return `${seconds}s`;
981
1116
  }
982
1117
 
1118
+ // src/record-handled-to-otel.ts
1119
+ import {
1120
+ context,
1121
+ trace,
1122
+ SpanStatusCode
1123
+ } from "@opentelemetry/api";
1124
+ var TRACER_NAME = "robotrock";
1125
+ var WAIT_SPAN_NAME = "robotrock.wait_for_human";
1126
+ var HANDLED_EVENT_NAME = "robotrock.task_handled";
1127
+ var INVALID_TRACE_ID = "00000000000000000000000000000000";
1128
+ function isValidTraceId(traceId) {
1129
+ return typeof traceId === "string" && traceId.length > 0 && traceId !== INVALID_TRACE_ID;
1130
+ }
1131
+ function resolveSpanContext(span) {
1132
+ const active = span ?? trace.getActiveSpan();
1133
+ const ctx = active?.spanContext();
1134
+ if (!ctx || !isValidTraceId(ctx.traceId)) {
1135
+ return void 0;
1136
+ }
1137
+ return { traceId: ctx.traceId, spanId: ctx.spanId };
1138
+ }
1139
+ function parseTaskCreatedAt(submittedAt) {
1140
+ if (submittedAt) {
1141
+ const parsed = Date.parse(submittedAt);
1142
+ if (!Number.isNaN(parsed)) {
1143
+ return parsed;
1144
+ }
1145
+ }
1146
+ return Date.now();
1147
+ }
1148
+ function parseHandledAtMs(handledAt) {
1149
+ if (handledAt instanceof Date) {
1150
+ return handledAt.getTime();
1151
+ }
1152
+ if (typeof handledAt === "number") {
1153
+ return handledAt;
1154
+ }
1155
+ const parsed = Date.parse(handledAt);
1156
+ return Number.isNaN(parsed) ? Date.now() : parsed;
1157
+ }
1158
+ function toRobotRockHandledOtelInput(handled) {
1159
+ if ("actionId" in handled) {
1160
+ return {
1161
+ taskId: handled.taskId,
1162
+ action: {
1163
+ id: handled.actionId,
1164
+ data: handled.data
1165
+ },
1166
+ handledBy: handled.handledBy,
1167
+ handledAt: handled.handledAt
1168
+ };
1169
+ }
1170
+ return {
1171
+ taskId: handled.taskId,
1172
+ action: {
1173
+ id: handled.action.id,
1174
+ title: handled.action.title,
1175
+ data: handled.action.data
1176
+ },
1177
+ handledBy: handled.handledBy,
1178
+ handledAt: handled.handledAt
1179
+ };
1180
+ }
1181
+ function captureRobotRockOtelHandle(task, options) {
1182
+ const spanCtx = resolveSpanContext(options?.span);
1183
+ return {
1184
+ traceId: spanCtx?.traceId ?? "",
1185
+ spanId: spanCtx?.spanId ?? "",
1186
+ taskId: task.taskId,
1187
+ threadId: task.threadId,
1188
+ taskCreatedAt: parseTaskCreatedAt(task.submittedAt)
1189
+ };
1190
+ }
1191
+ function startRobotRockHumanWaitSpan(handle, options) {
1192
+ const parent = options?.span ?? trace.getActiveSpan();
1193
+ if (!parent) {
1194
+ return void 0;
1195
+ }
1196
+ const tracer = trace.getTracer(options?.tracerName ?? TRACER_NAME);
1197
+ const span = tracer.startSpan(
1198
+ WAIT_SPAN_NAME,
1199
+ {},
1200
+ trace.setSpan(context.active(), parent)
1201
+ );
1202
+ span.setAttribute("robotrock.task_id", handle.taskId);
1203
+ if (handle.threadId) {
1204
+ span.setAttribute("robotrock.thread_id", handle.threadId);
1205
+ }
1206
+ return span;
1207
+ }
1208
+ function recordRobotRockHandledToOtel(handled, options = {}) {
1209
+ const span = options.span ?? trace.getActiveSpan();
1210
+ if (!span) {
1211
+ return;
1212
+ }
1213
+ const input = toRobotRockHandledOtelInput(handled);
1214
+ const handledAtMs = parseHandledAtMs(input.handledAt);
1215
+ const handle = options.handle;
1216
+ const humanWaitMs = handle != null ? Math.max(0, handledAtMs - handle.taskCreatedAt) : void 0;
1217
+ span.setAttribute("robotrock.task_id", input.taskId);
1218
+ span.setAttribute("robotrock.action.id", input.action.id);
1219
+ if (input.action.title) {
1220
+ span.setAttribute("robotrock.action.title", input.action.title);
1221
+ }
1222
+ if (input.handledBy) {
1223
+ span.setAttribute("robotrock.handled_by", input.handledBy);
1224
+ }
1225
+ span.setAttribute("robotrock.handled_at", new Date(handledAtMs).toISOString());
1226
+ if (humanWaitMs != null) {
1227
+ span.setAttribute("robotrock.human_wait_ms", humanWaitMs);
1228
+ }
1229
+ if (input.threadId) {
1230
+ span.setAttribute("robotrock.thread_id", input.threadId);
1231
+ }
1232
+ const eventAttributes = {
1233
+ "robotrock.task_id": input.taskId,
1234
+ "robotrock.action.id": input.action.id
1235
+ };
1236
+ if (input.handledBy) {
1237
+ eventAttributes["robotrock.handled_by"] = input.handledBy;
1238
+ }
1239
+ if (humanWaitMs != null) {
1240
+ eventAttributes["robotrock.human_wait_ms"] = humanWaitMs;
1241
+ }
1242
+ if (options.includeActionData && input.action.data != null) {
1243
+ const serialized = JSON.stringify(input.action.data);
1244
+ const truncated = serialized.length > 512 ? `${serialized.slice(0, 512)}\u2026` : serialized;
1245
+ span.setAttribute("robotrock.action.data", truncated);
1246
+ eventAttributes["robotrock.action.data"] = truncated;
1247
+ }
1248
+ span.addEvent(HANDLED_EVENT_NAME, eventAttributes);
1249
+ }
1250
+ function endRobotRockHumanWaitSpan(span, handled, options = {}) {
1251
+ if (!span) {
1252
+ return;
1253
+ }
1254
+ const outcome = options.outcome ?? (handled ? "handled" : "timeout");
1255
+ if (handled && outcome === "handled") {
1256
+ recordRobotRockHandledToOtel(handled, {
1257
+ span,
1258
+ handle: options.handle,
1259
+ includeActionData: options.includeActionData
1260
+ });
1261
+ span.setStatus({ code: SpanStatusCode.OK });
1262
+ } else {
1263
+ span.setAttribute("robotrock.outcome", outcome);
1264
+ span.setStatus({
1265
+ code: SpanStatusCode.ERROR,
1266
+ message: outcome === "timeout" ? "Human response timed out before validUntil" : "Human wait failed"
1267
+ });
1268
+ }
1269
+ span.end();
1270
+ }
1271
+
1272
+ // src/otel-platform.ts
1273
+ function shouldRecordRobotRockOtel(recordOtel) {
1274
+ if (recordOtel === true) {
1275
+ return true;
1276
+ }
1277
+ if (recordOtel === false) {
1278
+ return false;
1279
+ }
1280
+ const env = process.env.ROBOTROCK_OTEL_RECORD_HANDLED;
1281
+ return env === "true" || env === "1";
1282
+ }
1283
+ function stripPlatformOtelFields(payload) {
1284
+ const { recordOtel: _recordOtel, otelIncludeActionData: _otelIncludeActionData, ...rest } = payload;
1285
+ return rest;
1286
+ }
1287
+ function beginRobotRockHumanWaitOtel(task, options = {}) {
1288
+ const recordOtel = shouldRecordRobotRockOtel(options.recordOtel);
1289
+ if (!recordOtel) {
1290
+ return { recordOtel: false };
1291
+ }
1292
+ const handle = captureRobotRockOtelHandle(task);
1293
+ const waitSpan = startRobotRockHumanWaitSpan(handle);
1294
+ return {
1295
+ recordOtel: true,
1296
+ handle,
1297
+ waitSpan,
1298
+ includeActionData: options.otelIncludeActionData
1299
+ };
1300
+ }
1301
+ function finishRobotRockHumanWaitOtel(session, handled, outcome) {
1302
+ if (!session.recordOtel) {
1303
+ return;
1304
+ }
1305
+ endRobotRockHumanWaitSpan(session.waitSpan, handled, {
1306
+ handle: session.handle,
1307
+ outcome,
1308
+ includeActionData: session.includeActionData
1309
+ });
1310
+ }
1311
+
983
1312
  // src/workflow/index.ts
984
1313
  var APPROVE_BY_HUMAN_ACTIONS = [
985
1314
  { id: "approve", title: "Approve" },
@@ -993,6 +1322,7 @@ async function createRobotRockTaskForWebhook(input) {
993
1322
  baseUrl: baseConfig.baseUrl,
994
1323
  ...input.app ?? baseConfig.app ? { app: input.app ?? baseConfig.app } : {},
995
1324
  ...baseConfig.version ? { version: baseConfig.version } : {},
1325
+ ...baseConfig.advanced ? { advanced: baseConfig.advanced } : {},
996
1326
  webhook: { url: input.webhookUrl }
997
1327
  });
998
1328
  return client.sendToHuman({
@@ -1013,7 +1343,7 @@ async function parseRobotRockWebhookRequest(request) {
1013
1343
  async function sendToHumanInWorkflow(payload) {
1014
1344
  var _stack = [];
1015
1345
  try {
1016
- const { validUntil: validUntilInput, app, ...taskInput } = payload;
1346
+ const { validUntil: validUntilInput, app, recordOtel, otelIncludeActionData, ...taskFields } = payload;
1017
1347
  const { validUntil, timeout } = resolveWaitTiming(validUntilInput);
1018
1348
  const timeoutMs = parseValidUntilMs2(validUntil) - Date.now();
1019
1349
  const webhook = __using(_stack, createWebhook());
@@ -1021,31 +1351,54 @@ async function sendToHumanInWorkflow(payload) {
1021
1351
  webhookUrl: webhook.url,
1022
1352
  app,
1023
1353
  validUntil,
1024
- taskInput
1354
+ taskInput: stripPlatformOtelFields(taskFields)
1025
1355
  });
1026
- const outcome = await Promise.race([
1027
- webhook.then((request) => parseRobotRockWebhookRequest(request)),
1028
- sleep2(timeoutMs).then(() => ({ timedOut: true }))
1029
- ]);
1030
- if ("timedOut" in outcome) {
1031
- throw new Error(`Human response timed out before validUntil (${timeout})`);
1032
- }
1033
- const output = outcome;
1034
- return toDiscriminatedApprovalResult(payload.actions, {
1035
- id: output.taskId,
1036
- createdAt: /* @__PURE__ */ new Date(),
1037
- status: "handled",
1038
- context: sendResult.task.context,
1039
- validUntil: Date.now(),
1040
- handledAt: new Date(output.handledAt).getTime(),
1041
- handled: {
1042
- action: {
1043
- id: output.action.id,
1044
- data: output.action.data
1045
- },
1046
- handledBy: output.handledBy
1047
- }
1356
+ const otelSession = beginRobotRockHumanWaitOtel(sendResult.task, {
1357
+ recordOtel,
1358
+ otelIncludeActionData
1048
1359
  });
1360
+ let handledPayload = null;
1361
+ let waitOutcome = "pending";
1362
+ try {
1363
+ const outcome = await Promise.race([
1364
+ webhook.then((request) => parseRobotRockWebhookRequest(request)),
1365
+ sleep2(timeoutMs).then(() => ({ timedOut: true }))
1366
+ ]);
1367
+ if ("timedOut" in outcome) {
1368
+ waitOutcome = "timeout";
1369
+ throw new Error(`Human response timed out before validUntil (${timeout})`);
1370
+ }
1371
+ handledPayload = outcome;
1372
+ waitOutcome = "handled";
1373
+ return toDiscriminatedApprovalResult(payload.actions, {
1374
+ id: outcome.taskId,
1375
+ createdAt: /* @__PURE__ */ new Date(),
1376
+ status: "handled",
1377
+ context: sendResult.task.context,
1378
+ validUntil: Date.now(),
1379
+ handledAt: new Date(outcome.handledAt).getTime(),
1380
+ handled: {
1381
+ action: {
1382
+ id: outcome.action.id,
1383
+ data: outcome.action.data
1384
+ },
1385
+ handledBy: outcome.handledBy
1386
+ }
1387
+ });
1388
+ } catch (error) {
1389
+ if (waitOutcome === "pending") {
1390
+ waitOutcome = "error";
1391
+ }
1392
+ throw error;
1393
+ } finally {
1394
+ if (waitOutcome !== "pending") {
1395
+ finishRobotRockHumanWaitOtel(
1396
+ otelSession,
1397
+ handledPayload,
1398
+ waitOutcome === "handled" ? "handled" : waitOutcome
1399
+ );
1400
+ }
1401
+ }
1049
1402
  } catch (_) {
1050
1403
  var _error = _, _hasError = true;
1051
1404
  } finally {
@@ -1066,9 +1419,10 @@ async function sendRobotRockUpdate(payload) {
1066
1419
  apiKey: baseConfig.apiKey,
1067
1420
  baseUrl: baseConfig.baseUrl,
1068
1421
  ...app ?? baseConfig.app ? { app: app ?? baseConfig.app } : {},
1069
- ...baseConfig.version ? { version: baseConfig.version } : {}
1422
+ ...baseConfig.version ? { version: baseConfig.version } : {},
1423
+ ...baseConfig.advanced ? { advanced: baseConfig.advanced } : {}
1070
1424
  });
1071
- return client.sendUpdate(update);
1425
+ return client.tasks.sendUpdate(update);
1072
1426
  }
1073
1427
  async function sendUpdateInWorkflow(payload) {
1074
1428
  return sendRobotRockUpdate(payload);