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.
package/dist/index.js CHANGED
@@ -164,12 +164,12 @@ function validateUrlValue(value, widget) {
164
164
  }
165
165
  return null;
166
166
  }
167
- function validateContextPublicUrls(context) {
168
- if (!context?.data) {
167
+ function validateContextPublicUrls(context2) {
168
+ if (!context2?.data) {
169
169
  return null;
170
170
  }
171
- for (const [field, value] of Object.entries(context.data)) {
172
- const widget = widgetType(context.ui, field);
171
+ for (const [field, value] of Object.entries(context2.data)) {
172
+ const widget = widgetType(context2.ui, field);
173
173
  if (!widget) {
174
174
  continue;
175
175
  }
@@ -202,13 +202,15 @@ var triggerHandlerSchema = webhookHandlerSchema.extend({
202
202
  tokenId: z.string().min(1)
203
203
  });
204
204
  var handlerSchema = z.discriminatedUnion("type", [webhookHandlerSchema, triggerHandlerSchema]);
205
- var taskActionSchema = z.object({
205
+ var taskActionInputSchema = z.object({
206
206
  id: z.string().min(1),
207
207
  title: z.string().min(1),
208
208
  description: z.string().optional(),
209
209
  schema: jsonSchema7Schema.optional(),
210
210
  ui: uiSchemaSchema.optional(),
211
- data: z.record(z.string(), z.unknown()).optional(),
211
+ data: z.record(z.string(), z.unknown()).optional()
212
+ });
213
+ var taskActionSchema = taskActionInputSchema.extend({
212
214
  // Optional handlers for this action - if present, must have at least 1
213
215
  handlers: z.array(handlerSchema).min(1).optional()
214
216
  });
@@ -224,7 +226,8 @@ var contextDataSchema = z.object({
224
226
  data: z.record(z.string(), z.unknown()),
225
227
  ui: contextUiSchema
226
228
  }).optional();
227
- var taskContextObjectSchema = z.object({
229
+ var TASK_CONTEXT_FORMAT_VERSION = 2;
230
+ var taskContextObjectBaseSchema = z.object({
228
231
  /** Source application id; omitted tasks are grouped in the default inbox. */
229
232
  app: z.string().min(1).optional(),
230
233
  type: z.string().min(1),
@@ -232,9 +235,22 @@ var taskContextObjectSchema = z.object({
232
235
  description: z.string().optional(),
233
236
  validUntil: z.string().optional(),
234
237
  context: contextDataSchema,
238
+ /** Task context wire format version. @default 2 */
239
+ contextVersion: z.literal(2).optional(),
240
+ /** @deprecated Use `contextVersion`. Accepted on ingest only. */
235
241
  version: z.literal(2).optional(),
236
242
  actions: z.array(taskActionSchema).min(1, "At least one action is required")
237
243
  });
244
+ function normalizeTaskContextVersion(data) {
245
+ const { version: legacyVersion, contextVersion, ...rest } = data;
246
+ return {
247
+ ...rest,
248
+ contextVersion: contextVersion ?? legacyVersion ?? TASK_CONTEXT_FORMAT_VERSION
249
+ };
250
+ }
251
+ var taskContextObjectSchema = taskContextObjectBaseSchema.transform(
252
+ normalizeTaskContextVersion
253
+ );
238
254
  function refineContextPublicUrls(data, ctx) {
239
255
  const error = validateContextPublicUrls(data.context);
240
256
  if (error) {
@@ -278,61 +294,11 @@ var threadUpdateInputSchema = z.object({
278
294
  });
279
295
  var taskPriorities = ["low", "normal", "high", "urgent"];
280
296
  var taskPrioritySchema = z.enum(taskPriorities);
281
- var nonNegativeInt = z.number().int().nonnegative();
282
- var agentCostTokensSchema = z.object({
283
- input: nonNegativeInt.optional(),
284
- output: nonNegativeInt.optional(),
285
- total: nonNegativeInt.optional()
286
- });
287
- var agentCostSchema = z.object({
288
- tokens: agentCostTokensSchema.optional(),
289
- eur: z.number().nonnegative().optional(),
290
- usd: z.number().nonnegative().optional()
291
- });
292
- var AGENT_INFO_MAX_KEYS = 32;
293
- var AGENT_TOOL_CALLS_MAX_KEYS = 64;
294
- var AGENT_OTEL_SPANS_MAX = 32;
295
- var agentToolCallsSchema = z.record(z.string().min(1), nonNegativeInt);
296
- var agentOtelSpanStatusSchema = z.enum(["ok", "error"]);
297
- var agentOtelSpanSummarySchema = z.object({
298
- name: z.string().min(1),
299
- durationMs: z.number().nonnegative(),
300
- status: agentOtelSpanStatusSchema
301
- });
302
- var agentOtelSchema = z.object({
303
- traceId: z.string().min(1),
304
- spanId: z.string().min(1).optional(),
305
- rootDurationMs: z.number().nonnegative().optional(),
306
- spans: z.array(agentOtelSpanSummarySchema).max(AGENT_OTEL_SPANS_MAX).optional()
307
- });
308
297
  var agentTelemetrySchema = z.object({
309
298
  /** Agent or app version (semver, git SHA, deploy tag — caller-defined). */
310
- version: z.string().min(1).optional(),
311
- /** Total tool invocations. When omitted but `toolCalls` is set, the sum of counts is used. */
312
- toolCallCount: nonNegativeInt.optional(),
313
- /** Per-tool invocation counts keyed by tool name. */
314
- toolCalls: agentToolCallsSchema.optional(),
315
- cost: agentCostSchema.optional(),
316
- /** Free-form metadata for experiments (model, prompt variant, etc.). */
317
- info: z.record(z.string(), z.unknown()).optional(),
318
- /** Structured OpenTelemetry span snapshot for feedback analysis. */
319
- otel: agentOtelSchema.optional()
320
- }).refine(
321
- (data) => {
322
- const keys = data.info ? Object.keys(data.info) : [];
323
- return keys.length <= AGENT_INFO_MAX_KEYS;
324
- },
325
- { message: `agent.info may contain at most ${AGENT_INFO_MAX_KEYS} keys` }
326
- ).refine(
327
- (data) => {
328
- const keys = data.toolCalls ? Object.keys(data.toolCalls) : [];
329
- return keys.length <= AGENT_TOOL_CALLS_MAX_KEYS;
330
- },
331
- {
332
- message: `agent.toolCalls may contain at most ${AGENT_TOOL_CALLS_MAX_KEYS} tool names`
333
- }
334
- );
335
- var createTaskBodySchema = taskContextObjectSchema.extend({
299
+ version: z.string().min(1)
300
+ });
301
+ var createTaskBodySchema = taskContextObjectBaseSchema.extend({
336
302
  assignTo: assignToSchema.optional(),
337
303
  /**
338
304
  * Groups related tasks together. When omitted, the server generates one and
@@ -349,9 +315,59 @@ var createTaskBodySchema = taskContextObjectSchema.extend({
349
315
  * the inbox status bar and the thread update log.
350
316
  */
351
317
  update: threadUpdateInputSchema.optional(),
352
- /** Agent telemetry (version, cost, tool calls) — not shown in inbox UI. */
318
+ /** Agent release version — not shown in inbox UI. */
353
319
  agent: agentTelemetrySchema.optional()
354
- }).superRefine(refineContextPublicUrls);
320
+ }).transform(normalizeTaskContextVersion).superRefine(refineContextPublicUrls);
321
+ var agentChatSeedActionSchema = z.object({
322
+ /** Stable action id echoed back on submit; auto-derived from title when omitted. */
323
+ id: z.string().min(1).optional(),
324
+ title: z.string().min(1),
325
+ description: z.string().optional(),
326
+ /** JSON Schema object for the form fields. */
327
+ schema: z.record(z.string(), z.unknown()).optional(),
328
+ /** RJSF ui schema overrides (ui:widget, ui:enumNames, etc.). */
329
+ ui: z.record(z.string(), z.unknown()).optional(),
330
+ /** Optional default field values. */
331
+ data: z.record(z.string(), z.unknown()).optional()
332
+ });
333
+ var agentChatSeedMessageSchema = z.object({
334
+ role: z.enum(["user", "assistant"]),
335
+ text: z.string().min(1),
336
+ /** Optional display-name override for the message sender. */
337
+ senderName: z.string().min(1).optional(),
338
+ /**
339
+ * Optional structured input request rendered as a styled RobotRock action
340
+ * widget below the message text. Use this instead of asking the user to reply
341
+ * in plain text so the request is always a proper action form.
342
+ */
343
+ requestActionInput: z.object({
344
+ prompt: z.string().optional(),
345
+ action: agentChatSeedActionSchema
346
+ }).optional()
347
+ });
348
+ var createAgentChatBodySchema = z.object({
349
+ /** Trigger chat agent id (task slug). Required unless `parentChatId` is set. */
350
+ agentIdentifier: z.string().min(1).optional(),
351
+ /** Inherit tenant/connection/agent from an existing chat (agent-spawned chats). */
352
+ parentChatId: z.string().min(1).optional(),
353
+ /** Convex tenantTriggerConnections id; resolved from the tenant when omitted. */
354
+ connectionId: z.string().min(1).optional(),
355
+ /** Source application id; groups the chat under an inbox section. */
356
+ app: z.string().min(1).optional(),
357
+ assignTo: assignToSchema.optional(),
358
+ title: z.string().min(1),
359
+ messages: z.array(agentChatSeedMessageSchema).default([]),
360
+ /** Provenance label stored on the session ("cron" | "agent" | ...). */
361
+ source: z.string().min(1).optional()
362
+ }).refine((data) => Boolean(data.agentIdentifier || data.parentChatId), {
363
+ message: "Provide either agentIdentifier or parentChatId"
364
+ });
365
+ var agentChatTransportBodySchema = z.object({
366
+ chatId: z.string().min(1),
367
+ publicAccessToken: z.string().min(1),
368
+ lastEventId: z.string().optional(),
369
+ isStreaming: z.boolean().optional()
370
+ });
355
371
 
356
372
  // src/schemas/index.ts
357
373
  var safeUrlSchema2 = z2.string().refine((url) => isPublicHttpUrl(url), {
@@ -374,13 +390,15 @@ var triggerHandlerSchema2 = webhookHandlerSchema2.extend({
374
390
  tokenId: z2.string().min(1)
375
391
  });
376
392
  var handlerSchema2 = z2.discriminatedUnion("type", [webhookHandlerSchema2, triggerHandlerSchema2]);
377
- var taskActionSchema2 = z2.object({
393
+ var taskActionInputSchema2 = z2.object({
378
394
  id: z2.string().min(1),
379
395
  title: z2.string().min(1),
380
396
  description: z2.string().optional(),
381
397
  schema: jsonSchema7Schema2.optional(),
382
398
  ui: uiSchemaSchema2.optional(),
383
- data: z2.record(z2.string(), z2.unknown()).optional(),
399
+ data: z2.record(z2.string(), z2.unknown()).optional()
400
+ });
401
+ var taskActionSchema2 = taskActionInputSchema2.extend({
384
402
  handlers: z2.array(handlerSchema2).min(1).optional()
385
403
  });
386
404
  var uiFieldSchemaSchema2 = z2.object({
@@ -395,16 +413,29 @@ var contextDataSchema2 = z2.object({
395
413
  data: z2.record(z2.string(), z2.unknown()),
396
414
  ui: contextUiSchema2
397
415
  }).optional();
398
- var taskContextObjectSchema2 = z2.object({
416
+ var TASK_CONTEXT_FORMAT_VERSION2 = 2;
417
+ var taskContextObjectBaseSchema2 = z2.object({
399
418
  app: z2.string().min(1).optional(),
400
419
  type: z2.string().min(1),
401
420
  name: z2.string().min(1),
402
421
  description: z2.string().optional(),
403
422
  validUntil: z2.string().optional(),
404
423
  context: contextDataSchema2,
424
+ contextVersion: z2.literal(2).optional(),
425
+ /** @deprecated Use `contextVersion`. Accepted on ingest only. */
405
426
  version: z2.literal(2).optional(),
406
427
  actions: z2.array(taskActionSchema2).min(1, "At least one action is required")
407
428
  });
429
+ function normalizeTaskContextVersion2(data) {
430
+ const { version: legacyVersion, contextVersion, ...rest } = data;
431
+ return {
432
+ ...rest,
433
+ contextVersion: contextVersion ?? legacyVersion ?? TASK_CONTEXT_FORMAT_VERSION2
434
+ };
435
+ }
436
+ var taskContextObjectSchema2 = taskContextObjectBaseSchema2.transform(
437
+ normalizeTaskContextVersion2
438
+ );
408
439
  function refineContextPublicUrls2(data, ctx) {
409
440
  const error = validateContextPublicUrls(data.context);
410
441
  if (error) {
@@ -457,56 +488,10 @@ var TASK_PRIORITY_RANK = {
457
488
  high: 3,
458
489
  urgent: 4
459
490
  };
460
- var nonNegativeInt2 = z2.number().int().nonnegative();
461
- var agentCostTokensSchema2 = z2.object({
462
- input: nonNegativeInt2.optional(),
463
- output: nonNegativeInt2.optional(),
464
- total: nonNegativeInt2.optional()
465
- });
466
- var agentCostSchema2 = z2.object({
467
- tokens: agentCostTokensSchema2.optional(),
468
- eur: z2.number().nonnegative().optional(),
469
- usd: z2.number().nonnegative().optional()
470
- });
471
- var AGENT_INFO_MAX_KEYS2 = 32;
472
- var AGENT_TOOL_CALLS_MAX_KEYS2 = 64;
473
- var AGENT_OTEL_SPANS_MAX2 = 32;
474
- var agentToolCallsSchema2 = z2.record(z2.string().min(1), nonNegativeInt2);
475
- var agentOtelSpanStatusSchema2 = z2.enum(["ok", "error"]);
476
- var agentOtelSpanSummarySchema2 = z2.object({
477
- name: z2.string().min(1),
478
- durationMs: z2.number().nonnegative(),
479
- status: agentOtelSpanStatusSchema2
480
- });
481
- var agentOtelSchema2 = z2.object({
482
- traceId: z2.string().min(1),
483
- spanId: z2.string().min(1).optional(),
484
- rootDurationMs: z2.number().nonnegative().optional(),
485
- spans: z2.array(agentOtelSpanSummarySchema2).max(AGENT_OTEL_SPANS_MAX2).optional()
486
- });
487
491
  var agentTelemetrySchema2 = z2.object({
488
- version: z2.string().min(1).optional(),
489
- toolCallCount: nonNegativeInt2.optional(),
490
- toolCalls: agentToolCallsSchema2.optional(),
491
- cost: agentCostSchema2.optional(),
492
- info: z2.record(z2.string(), z2.unknown()).optional(),
493
- otel: agentOtelSchema2.optional()
494
- }).refine(
495
- (data) => {
496
- const keys = data.info ? Object.keys(data.info) : [];
497
- return keys.length <= AGENT_INFO_MAX_KEYS2;
498
- },
499
- { message: `agent.info may contain at most ${AGENT_INFO_MAX_KEYS2} keys` }
500
- ).refine(
501
- (data) => {
502
- const keys = data.toolCalls ? Object.keys(data.toolCalls) : [];
503
- return keys.length <= AGENT_TOOL_CALLS_MAX_KEYS2;
504
- },
505
- {
506
- message: `agent.toolCalls may contain at most ${AGENT_TOOL_CALLS_MAX_KEYS2} tool names`
507
- }
508
- );
509
- var createTaskBodySchema2 = taskContextObjectSchema2.extend({
492
+ version: z2.string().min(1)
493
+ });
494
+ var createTaskBodySchema2 = taskContextObjectBaseSchema2.extend({
510
495
  assignTo: assignToSchema2.optional(),
511
496
  /**
512
497
  * Groups related tasks together. When omitted, the server generates one and
@@ -524,7 +509,7 @@ var createTaskBodySchema2 = taskContextObjectSchema2.extend({
524
509
  */
525
510
  update: threadUpdateInputSchema2.optional(),
526
511
  agent: agentTelemetrySchema2.optional()
527
- }).superRefine(refineContextPublicUrls2);
512
+ }).transform(normalizeTaskContextVersion2).superRefine(refineContextPublicUrls2);
528
513
  var threadUpdateBodySchema = threadUpdateInputSchema2;
529
514
 
530
515
  // src/approval-result.ts
@@ -554,12 +539,159 @@ function toDiscriminatedApprovalResult(actions, task) {
554
539
  };
555
540
  }
556
541
 
542
+ // src/http.ts
543
+ var RobotRockError = class extends Error {
544
+ constructor(message, statusCode, response) {
545
+ super(message);
546
+ this.statusCode = statusCode;
547
+ this.response = response;
548
+ this.name = "RobotRockError";
549
+ }
550
+ };
551
+ async function parseResponseBody(response) {
552
+ const contentType = response.headers.get("content-type") ?? "";
553
+ const bodyText = await response.text();
554
+ if (!bodyText) {
555
+ return null;
556
+ }
557
+ if (contentType.toLowerCase().includes("application/json")) {
558
+ try {
559
+ return JSON.parse(bodyText);
560
+ } catch {
561
+ }
562
+ }
563
+ try {
564
+ return JSON.parse(bodyText);
565
+ } catch {
566
+ return bodyText;
567
+ }
568
+ }
569
+ function getErrorMessage(data, fallback) {
570
+ if (data && typeof data === "object" && !Array.isArray(data)) {
571
+ const maybeMessage = data.message;
572
+ if (typeof maybeMessage === "string" && maybeMessage.trim()) {
573
+ return maybeMessage;
574
+ }
575
+ }
576
+ if (typeof data === "string" && data.trim()) {
577
+ const compact = data.replace(/\s+/g, " ").trim();
578
+ const snippet = compact.length > 180 ? `${compact.slice(0, 180)}...` : compact;
579
+ return `${fallback}. Server returned non-JSON response: ${snippet}`;
580
+ }
581
+ return fallback;
582
+ }
583
+
584
+ // ../core/src/handler-retry.ts
585
+ var HANDLER_RETRY_DELAYS_MS = [
586
+ 6e4,
587
+ // 1m
588
+ 3e5,
589
+ // 5m
590
+ 18e5,
591
+ // 30m
592
+ 36e5,
593
+ // 1h
594
+ 216e5,
595
+ // 6h
596
+ 864e5,
597
+ // 24h
598
+ 1728e5
599
+ // 48h
600
+ ];
601
+ var HANDLER_MAX_ATTEMPTS = HANDLER_RETRY_DELAYS_MS.length + 1;
602
+
603
+ // ../core/src/attribution/index.ts
604
+ import { z as z3 } from "zod";
605
+
606
+ // ../core/src/app-url.ts
607
+ var PRODUCTION_APP_URL = "https://app.robotrock.io";
608
+ var APP_SIGNUP_URL = `${PRODUCTION_APP_URL}/sign-up`;
609
+
610
+ // ../core/src/attribution/index.ts
611
+ var ATTRIBUTION_COOKIE_MAX_AGE = 60 * 60 * 24 * 90;
612
+ var attributionSchema = z3.object({
613
+ utmSource: z3.string().optional(),
614
+ utmMedium: z3.string().optional(),
615
+ utmCampaign: z3.string().optional(),
616
+ utmContent: z3.string().optional(),
617
+ utmTerm: z3.string().optional(),
618
+ gclid: z3.string().optional(),
619
+ landingUrl: z3.string().optional(),
620
+ referrer: z3.string().optional(),
621
+ capturedAt: z3.number()
622
+ });
623
+
624
+ // src/chats.ts
625
+ function createChatsApi(config) {
626
+ const headers = () => ({
627
+ "Content-Type": "application/json",
628
+ "X-Api-Key": config.apiKey
629
+ });
630
+ return {
631
+ async create(input) {
632
+ const bodyPayload = {
633
+ ...input,
634
+ app: input.app ?? config.app,
635
+ messages: input.messages ?? []
636
+ };
637
+ const validation = createAgentChatBodySchema.safeParse(bodyPayload);
638
+ if (!validation.success) {
639
+ throw new RobotRockError(
640
+ `Invalid chat: ${validation.error.issues[0]?.message}`,
641
+ 400,
642
+ validation.error.issues
643
+ );
644
+ }
645
+ const response = await fetch(`${config.baseUrl}/agent-chats`, {
646
+ method: "POST",
647
+ headers: headers(),
648
+ body: JSON.stringify(validation.data)
649
+ });
650
+ const data = await parseResponseBody(response);
651
+ if (!response.ok) {
652
+ throw new RobotRockError(
653
+ getErrorMessage(data, "Failed to create chat"),
654
+ response.status,
655
+ data
656
+ );
657
+ }
658
+ const result = data;
659
+ return { tenantSlug: result.tenantSlug, chats: result.chats };
660
+ },
661
+ async close(chatId, options) {
662
+ if (!chatId) {
663
+ throw new RobotRockError("chatId is required to close a chat", 400);
664
+ }
665
+ const response = await fetch(
666
+ `${config.baseUrl}/agent-chats/${encodeURIComponent(chatId)}/close`,
667
+ {
668
+ method: "POST",
669
+ headers: headers(),
670
+ body: JSON.stringify({ reason: options?.reason })
671
+ }
672
+ );
673
+ if (!response.ok) {
674
+ const data = await parseResponseBody(response);
675
+ throw new RobotRockError(
676
+ getErrorMessage(data, "Failed to close chat"),
677
+ response.status,
678
+ data
679
+ );
680
+ }
681
+ }
682
+ };
683
+ }
684
+
557
685
  // src/client.ts
558
686
  var DEFAULT_POLL_INTERVAL_MS = 2e3;
559
687
  var DEFAULT_TIMEOUT_MS = 24 * 60 * 60 * 1e3;
560
688
  function sleep(ms) {
561
689
  return new Promise((resolve) => setTimeout(resolve, ms));
562
690
  }
691
+ function resolveAgentVersionFromEnv() {
692
+ const fromEnv = process.env.AGENT_VERSION?.trim() || process.env.ROBOTROCK_AGENT_VERSION?.trim();
693
+ return fromEnv || void 0;
694
+ }
563
695
  function parseValidUntilMs(value) {
564
696
  if (value === void 0) {
565
697
  return void 0;
@@ -587,21 +719,18 @@ function serializeValidUntil(value) {
587
719
  }
588
720
  throw new RobotRockError("Invalid validUntil: expected a Date or parseable date string", 400);
589
721
  }
590
- var RobotRockError = class extends Error {
591
- constructor(message, statusCode, response) {
592
- super(message);
593
- this.statusCode = statusCode;
594
- this.response = response;
595
- this.name = "RobotRockError";
596
- }
597
- };
598
722
  var RobotRock = class {
599
723
  apiKey;
600
724
  baseUrl;
601
725
  app;
602
- version;
726
+ agentVersion;
727
+ contextVersion;
603
728
  webhook;
604
729
  polling;
730
+ /** Task CRUD: `create`, `get`, `cancel`, `sendUpdate`. */
731
+ tasks;
732
+ /** Chat CRUD: `create`, `close`. */
733
+ chats;
605
734
  constructor(config) {
606
735
  if (config.webhook && config.polling) {
607
736
  throw new Error(
@@ -618,26 +747,37 @@ var RobotRock = class {
618
747
  const rawBase = config.baseUrl ?? "https://api.robotrock.io/v1";
619
748
  this.baseUrl = rawBase.replace(/\/+$/, "");
620
749
  this.app = config.app;
621
- this.version = config.version ?? 2;
750
+ this.agentVersion = config.version ?? resolveAgentVersionFromEnv();
751
+ this.contextVersion = config.advanced?.contextVersion ?? TASK_CONTEXT_FORMAT_VERSION2;
622
752
  this.webhook = config.webhook;
623
753
  this.polling = config.polling ?? {};
754
+ this.tasks = {
755
+ create: (task) => this.createTaskRequest(task),
756
+ get: (taskId) => this.getTaskById(taskId),
757
+ cancel: (taskId) => this.cancelTaskRequest(taskId),
758
+ sendUpdate: (input) => this.sendThreadUpdate(input)
759
+ };
760
+ this.chats = createChatsApi({
761
+ baseUrl: this.baseUrl,
762
+ apiKey: this.apiKey,
763
+ app: this.app
764
+ });
624
765
  }
625
- /**
626
- * Create a task via POST /v1 without waiting for a human response.
627
- */
628
- async createTask(task) {
766
+ async createTaskRequest(task) {
629
767
  const normalizedTask = normalizeSendToHumanInput(task, {
630
768
  webhook: this.webhook,
631
769
  app: this.app,
632
- version: this.version
770
+ contextVersion: this.contextVersion,
771
+ agentVersion: this.agentVersion
633
772
  });
773
+ const agentVersion = task.version ?? this.agentVersion;
634
774
  const bodyPayload = {
635
775
  ...normalizedTask,
636
776
  ...task.assignTo !== void 0 ? { assignTo: task.assignTo } : {},
637
777
  ...task.threadId !== void 0 ? { threadId: task.threadId } : {},
638
778
  ...task.priority !== void 0 ? { priority: task.priority } : {},
639
779
  ...task.update !== void 0 ? { update: task.update } : {},
640
- ...task.agent !== void 0 ? { agent: task.agent } : {}
780
+ ...agentVersion !== void 0 ? { agent: { version: agentVersion } } : {}
641
781
  };
642
782
  const validation = createTaskBodySchema2.safeParse(bodyPayload);
643
783
  if (!validation.success) {
@@ -673,9 +813,10 @@ var RobotRock = class {
673
813
  const normalizedTask = normalizeSendToHumanInput(task, {
674
814
  webhook: this.webhook,
675
815
  app: this.app,
676
- version: this.version
816
+ contextVersion: this.contextVersion,
817
+ agentVersion: this.agentVersion
677
818
  });
678
- const createdTaskTask = await this.createTask(task);
819
+ const createdTaskTask = await this.createTaskRequest(task);
679
820
  const hasHandlers = normalizedTask.actions.some(
680
821
  (action) => Array.isArray(action.handlers) && action.handlers.length > 0
681
822
  );
@@ -692,7 +833,7 @@ var RobotRock = class {
692
833
  const deadline = validUntilMs !== void 0 ? Math.min(pollingDeadline, validUntilMs) : pollingDeadline;
693
834
  const taskId = createdTaskTask.taskId;
694
835
  while (Date.now() < deadline) {
695
- const existing = await this.getTask(taskId);
836
+ const existing = await this.getTaskById(taskId);
696
837
  if (existing?.status === "handled" && existing.handled) {
697
838
  return {
698
839
  mode: "handled",
@@ -714,10 +855,35 @@ var RobotRock = class {
714
855
  }
715
856
  throw new TaskTimeoutError(`No human response within ${timeoutMs}ms`);
716
857
  }
858
+ /**
859
+ * Create a task via POST /v1 without waiting for a human response.
860
+ * @deprecated Use `client.tasks.create()` instead.
861
+ */
862
+ async createTask(task) {
863
+ return this.tasks.create(task);
864
+ }
717
865
  /**
718
866
  * Get a task by public task id (returned as `task.taskId` from {@link sendToHuman}).
867
+ * @deprecated Use `client.tasks.get()` instead.
719
868
  */
720
869
  async getTask(taskId) {
870
+ return this.tasks.get(taskId);
871
+ }
872
+ /**
873
+ * Log a status update against a thread.
874
+ * @deprecated Use `client.tasks.sendUpdate()` instead.
875
+ */
876
+ async sendUpdate(input) {
877
+ return this.tasks.sendUpdate(input);
878
+ }
879
+ /**
880
+ * Cancel a task by public task id.
881
+ * @deprecated Use `client.tasks.cancel()` instead.
882
+ */
883
+ async cancelTask(taskId) {
884
+ return this.tasks.cancel(taskId);
885
+ }
886
+ async getTaskById(taskId) {
721
887
  const response = await fetch(`${this.baseUrl}/tasks/${taskId}`, {
722
888
  method: "GET",
723
889
  headers: {
@@ -737,11 +903,11 @@ var RobotRock = class {
737
903
  }
738
904
  return data;
739
905
  }
740
- /**
741
- * Log a status update against a thread. The update shows in the inbox status
742
- * bar and thread update log for every task in the thread.
743
- */
744
- async sendUpdate({ threadId, message, status }) {
906
+ async sendThreadUpdate({
907
+ threadId,
908
+ message,
909
+ status
910
+ }) {
745
911
  if (!threadId) {
746
912
  throw new RobotRockError("threadId is required to send an update", 400);
747
913
  }
@@ -774,7 +940,7 @@ var RobotRock = class {
774
940
  }
775
941
  return data.update;
776
942
  }
777
- async cancelTask(taskId) {
943
+ async cancelTaskRequest(taskId) {
778
944
  const response = await fetch(`${this.baseUrl}/tasks/${taskId}/cancel`, {
779
945
  method: "POST",
780
946
  headers: {
@@ -817,6 +983,7 @@ function normalizeSendToHumanInput(task, clientDefaults) {
817
983
  threadId: _threadId,
818
984
  priority: _priority,
819
985
  update: _update,
986
+ version: _version,
820
987
  validUntil,
821
988
  app: taskApp,
822
989
  ...rest
@@ -826,44 +993,12 @@ function normalizeSendToHumanInput(task, clientDefaults) {
826
993
  const app = taskApp ?? clientDefaults.app;
827
994
  return {
828
995
  ...rest,
829
- version: clientDefaults.version,
996
+ contextVersion: clientDefaults.contextVersion,
830
997
  ...app ? { app } : {},
831
998
  ...validUntil !== void 0 ? { validUntil: serializeValidUntil(validUntil) } : {},
832
999
  actions: normalizedActions
833
1000
  };
834
1001
  }
835
- async function parseResponseBody(response) {
836
- const contentType = response.headers.get("content-type") ?? "";
837
- const bodyText = await response.text();
838
- if (!bodyText) {
839
- return null;
840
- }
841
- if (contentType.toLowerCase().includes("application/json")) {
842
- try {
843
- return JSON.parse(bodyText);
844
- } catch {
845
- }
846
- }
847
- try {
848
- return JSON.parse(bodyText);
849
- } catch {
850
- return bodyText;
851
- }
852
- }
853
- function getErrorMessage(data, fallback) {
854
- if (data && typeof data === "object" && !Array.isArray(data)) {
855
- const maybeMessage = data.message;
856
- if (typeof maybeMessage === "string" && maybeMessage.trim()) {
857
- return maybeMessage;
858
- }
859
- }
860
- if (typeof data === "string" && data.trim()) {
861
- const compact = data.replace(/\s+/g, " ").trim();
862
- const snippet = compact.length > 180 ? `${compact.slice(0, 180)}...` : compact;
863
- return `${fallback}. Server returned non-JSON response: ${snippet}`;
864
- }
865
- return fallback;
866
- }
867
1002
 
868
1003
  // src/env.ts
869
1004
  var DEFAULT_BASE_URL = "https://api.robotrock.io/v1";
@@ -978,21 +1113,18 @@ function shouldStopAgentForHandledAction(actionId) {
978
1113
 
979
1114
  // src/webhook.ts
980
1115
  import { createHmac, timingSafeEqual } from "crypto";
981
- import { z as z3 } from "zod";
1116
+ import { z as z4 } from "zod";
982
1117
  var ROBOTROCK_SIGNATURE_HEADER = "x-robotrock-signature";
983
- var robotRockWebhookPayloadBodySchema = z3.object({
984
- taskId: z3.string().min(1),
985
- action: z3.object({
986
- id: z3.string().min(1),
987
- title: z3.string().min(1),
988
- data: z3.unknown()
1118
+ var robotRockWebhookPayloadBodySchema = z4.object({
1119
+ taskId: z4.string().min(1),
1120
+ action: z4.object({
1121
+ id: z4.string().min(1),
1122
+ title: z4.string().min(1),
1123
+ data: z4.unknown()
989
1124
  }),
990
- handledBy: z3.string().min(1).optional(),
991
- handledAt: z3.string().min(1),
992
- handlerType: z3.string().min(1)
993
- });
994
- var robotRockWebhookPayloadSchema = robotRockWebhookPayloadBodySchema.extend({
995
- headers: z3.record(z3.string(), z3.string())
1125
+ handledBy: z4.string().min(1).optional(),
1126
+ handledAt: z4.string().min(1),
1127
+ handlerType: z4.string().min(1)
996
1128
  });
997
1129
  var RobotRockWebhookError = class extends Error {
998
1130
  constructor(message, code, details) {
@@ -1082,14 +1214,18 @@ function normalizeHeaders(headers) {
1082
1214
  return result;
1083
1215
  }
1084
1216
 
1085
- // src/agent-telemetry-from-otel.ts
1086
- import { trace } from "@opentelemetry/api";
1087
- var AGENT_INFO_MAX_KEYS3 = 32;
1088
- function sumToolCalls(toolCalls) {
1089
- return Object.values(toolCalls).reduce((sum, count) => sum + count, 0);
1090
- }
1217
+ // src/record-handled-to-otel.ts
1218
+ import {
1219
+ context,
1220
+ trace,
1221
+ SpanStatusCode
1222
+ } from "@opentelemetry/api";
1223
+ var TRACER_NAME = "robotrock";
1224
+ var WAIT_SPAN_NAME = "robotrock.wait_for_human";
1225
+ var HANDLED_EVENT_NAME = "robotrock.task_handled";
1226
+ var INVALID_TRACE_ID = "00000000000000000000000000000000";
1091
1227
  function isValidTraceId(traceId) {
1092
- return typeof traceId === "string" && traceId.length > 0 && traceId !== "00000000000000000000000000000000";
1228
+ return typeof traceId === "string" && traceId.length > 0 && traceId !== INVALID_TRACE_ID;
1093
1229
  }
1094
1230
  function resolveSpanContext(span) {
1095
1231
  const active = span ?? trace.getActiveSpan();
@@ -1099,98 +1235,179 @@ function resolveSpanContext(span) {
1099
1235
  }
1100
1236
  return { traceId: ctx.traceId, spanId: ctx.spanId };
1101
1237
  }
1102
- function countErrorSpans(spans) {
1103
- if (!spans) return 0;
1104
- return spans.filter((entry) => entry.status === "error").length;
1105
- }
1106
- function buildInfo(options, spanCtx) {
1107
- const info = { ...options.extraInfo };
1108
- if (spanCtx) {
1109
- info.traceId = spanCtx.traceId;
1110
- info.spanId = spanCtx.spanId;
1111
- }
1112
- if (options.runStartedAt != null) {
1113
- const durationMs = Date.now() - options.runStartedAt;
1114
- if (durationMs >= 0) {
1115
- info.durationMs = durationMs;
1238
+ function parseTaskCreatedAt(submittedAt) {
1239
+ if (submittedAt) {
1240
+ const parsed = Date.parse(submittedAt);
1241
+ if (!Number.isNaN(parsed)) {
1242
+ return parsed;
1116
1243
  }
1117
1244
  }
1118
- const errorCount = countErrorSpans(options.otel?.spans);
1119
- if (errorCount > 0) {
1120
- info.errorCount = errorCount;
1245
+ return Date.now();
1246
+ }
1247
+ function parseHandledAtMs(handledAt) {
1248
+ if (handledAt instanceof Date) {
1249
+ return handledAt.getTime();
1121
1250
  }
1122
- const keys = Object.keys(info);
1123
- if (keys.length === 0) {
1124
- return void 0;
1251
+ if (typeof handledAt === "number") {
1252
+ return handledAt;
1125
1253
  }
1126
- if (keys.length > AGENT_INFO_MAX_KEYS3) {
1127
- const trimmed = {};
1128
- for (const key of keys.slice(0, AGENT_INFO_MAX_KEYS3)) {
1129
- trimmed[key] = info[key];
1130
- }
1131
- return trimmed;
1254
+ const parsed = Date.parse(handledAt);
1255
+ return Number.isNaN(parsed) ? Date.now() : parsed;
1256
+ }
1257
+ function toRobotRockHandledOtelInput(handled) {
1258
+ if ("actionId" in handled) {
1259
+ return {
1260
+ taskId: handled.taskId,
1261
+ action: {
1262
+ id: handled.actionId,
1263
+ data: handled.data
1264
+ },
1265
+ handledBy: handled.handledBy,
1266
+ handledAt: handled.handledAt
1267
+ };
1132
1268
  }
1133
- return info;
1269
+ return {
1270
+ taskId: handled.taskId,
1271
+ action: {
1272
+ id: handled.action.id,
1273
+ title: handled.action.title,
1274
+ data: handled.action.data
1275
+ },
1276
+ handledBy: handled.handledBy,
1277
+ handledAt: handled.handledAt
1278
+ };
1134
1279
  }
1135
- function buildOtelBlock(options, spanCtx) {
1136
- const traceId = options.otel?.traceId ?? spanCtx?.traceId;
1137
- if (!isValidTraceId(traceId)) {
1280
+ function captureRobotRockOtelHandle(task, options) {
1281
+ const spanCtx = resolveSpanContext(options?.span);
1282
+ return {
1283
+ traceId: spanCtx?.traceId ?? "",
1284
+ spanId: spanCtx?.spanId ?? "",
1285
+ taskId: task.taskId,
1286
+ threadId: task.threadId,
1287
+ taskCreatedAt: parseTaskCreatedAt(task.submittedAt)
1288
+ };
1289
+ }
1290
+ function startRobotRockHumanWaitSpan(handle, options) {
1291
+ const parent = options?.span ?? trace.getActiveSpan();
1292
+ if (!parent) {
1138
1293
  return void 0;
1139
1294
  }
1140
- const spans = options.otel?.spans;
1141
- const rootDurationMs = options.otel?.rootDurationMs ?? (options.runStartedAt != null ? Math.max(0, Date.now() - options.runStartedAt) : void 0);
1142
- const block = { traceId };
1143
- const spanId = options.otel?.spanId ?? spanCtx?.spanId;
1144
- if (spanId) {
1145
- block.spanId = spanId;
1295
+ const tracer = trace.getTracer(options?.tracerName ?? TRACER_NAME);
1296
+ const span = tracer.startSpan(
1297
+ WAIT_SPAN_NAME,
1298
+ {},
1299
+ trace.setSpan(context.active(), parent)
1300
+ );
1301
+ span.setAttribute("robotrock.task_id", handle.taskId);
1302
+ if (handle.threadId) {
1303
+ span.setAttribute("robotrock.thread_id", handle.threadId);
1304
+ }
1305
+ return span;
1306
+ }
1307
+ function recordRobotRockHandledToOtel(handled, options = {}) {
1308
+ const span = options.span ?? trace.getActiveSpan();
1309
+ if (!span) {
1310
+ return;
1311
+ }
1312
+ const input = toRobotRockHandledOtelInput(handled);
1313
+ const handledAtMs = parseHandledAtMs(input.handledAt);
1314
+ const handle = options.handle;
1315
+ const humanWaitMs = handle != null ? Math.max(0, handledAtMs - handle.taskCreatedAt) : void 0;
1316
+ span.setAttribute("robotrock.task_id", input.taskId);
1317
+ span.setAttribute("robotrock.action.id", input.action.id);
1318
+ if (input.action.title) {
1319
+ span.setAttribute("robotrock.action.title", input.action.title);
1320
+ }
1321
+ if (input.handledBy) {
1322
+ span.setAttribute("robotrock.handled_by", input.handledBy);
1146
1323
  }
1147
- if (rootDurationMs != null) {
1148
- block.rootDurationMs = rootDurationMs;
1324
+ span.setAttribute("robotrock.handled_at", new Date(handledAtMs).toISOString());
1325
+ if (humanWaitMs != null) {
1326
+ span.setAttribute("robotrock.human_wait_ms", humanWaitMs);
1149
1327
  }
1150
- if (spans && spans.length > 0) {
1151
- block.spans = spans;
1328
+ if (input.threadId) {
1329
+ span.setAttribute("robotrock.thread_id", input.threadId);
1152
1330
  }
1153
- return block;
1331
+ const eventAttributes = {
1332
+ "robotrock.task_id": input.taskId,
1333
+ "robotrock.action.id": input.action.id
1334
+ };
1335
+ if (input.handledBy) {
1336
+ eventAttributes["robotrock.handled_by"] = input.handledBy;
1337
+ }
1338
+ if (humanWaitMs != null) {
1339
+ eventAttributes["robotrock.human_wait_ms"] = humanWaitMs;
1340
+ }
1341
+ if (options.includeActionData && input.action.data != null) {
1342
+ const serialized = JSON.stringify(input.action.data);
1343
+ const truncated = serialized.length > 512 ? `${serialized.slice(0, 512)}\u2026` : serialized;
1344
+ span.setAttribute("robotrock.action.data", truncated);
1345
+ eventAttributes["robotrock.action.data"] = truncated;
1346
+ }
1347
+ span.addEvent(HANDLED_EVENT_NAME, eventAttributes);
1154
1348
  }
1155
- function agentTelemetryFromOtel(options = {}) {
1156
- const spanCtx = resolveSpanContext(options.span);
1157
- const telemetry = {};
1158
- if (options.version) {
1159
- telemetry.version = options.version;
1160
- }
1161
- if (options.toolCalls && Object.keys(options.toolCalls).length > 0) {
1162
- telemetry.toolCalls = options.toolCalls;
1163
- }
1164
- if (options.toolCallCount !== void 0) {
1165
- telemetry.toolCallCount = options.toolCallCount;
1166
- } else if (telemetry.toolCalls) {
1167
- const sum = sumToolCalls(telemetry.toolCalls);
1168
- if (sum > 0) {
1169
- telemetry.toolCallCount = sum;
1170
- }
1349
+ function endRobotRockHumanWaitSpan(span, handled, options = {}) {
1350
+ if (!span) {
1351
+ return;
1171
1352
  }
1172
- if (options.cost) {
1173
- telemetry.cost = options.cost;
1353
+ const outcome = options.outcome ?? (handled ? "handled" : "timeout");
1354
+ if (handled && outcome === "handled") {
1355
+ recordRobotRockHandledToOtel(handled, {
1356
+ span,
1357
+ handle: options.handle,
1358
+ includeActionData: options.includeActionData
1359
+ });
1360
+ span.setStatus({ code: SpanStatusCode.OK });
1361
+ } else {
1362
+ span.setAttribute("robotrock.outcome", outcome);
1363
+ span.setStatus({
1364
+ code: SpanStatusCode.ERROR,
1365
+ message: outcome === "timeout" ? "Human response timed out before validUntil" : "Human wait failed"
1366
+ });
1174
1367
  }
1175
- const info = buildInfo(options, spanCtx);
1176
- if (info) {
1177
- telemetry.info = info;
1368
+ span.end();
1369
+ }
1370
+
1371
+ // src/otel-platform.ts
1372
+ function shouldRecordRobotRockOtel(recordOtel) {
1373
+ if (recordOtel === true) {
1374
+ return true;
1178
1375
  }
1179
- const otel = buildOtelBlock(options, spanCtx);
1180
- if (otel) {
1181
- telemetry.otel = otel;
1376
+ if (recordOtel === false) {
1377
+ return false;
1182
1378
  }
1183
- return agentTelemetrySchema2.parse(telemetry);
1379
+ const env = process.env.ROBOTROCK_OTEL_RECORD_HANDLED;
1380
+ return env === "true" || env === "1";
1381
+ }
1382
+ function stripPlatformOtelFields(payload) {
1383
+ const { recordOtel: _recordOtel, otelIncludeActionData: _otelIncludeActionData, ...rest } = payload;
1384
+ return rest;
1184
1385
  }
1185
- function toolCallsFromOtelSpans(spans) {
1186
- const counts = {};
1187
- for (const span of spans) {
1188
- counts[span.name] = (counts[span.name] ?? 0) + 1;
1386
+ function beginRobotRockHumanWaitOtel(task, options = {}) {
1387
+ const recordOtel = shouldRecordRobotRockOtel(options.recordOtel);
1388
+ if (!recordOtel) {
1389
+ return { recordOtel: false };
1189
1390
  }
1190
- return counts;
1391
+ const handle = captureRobotRockOtelHandle(task);
1392
+ const waitSpan = startRobotRockHumanWaitSpan(handle);
1393
+ return {
1394
+ recordOtel: true,
1395
+ handle,
1396
+ waitSpan,
1397
+ includeActionData: options.otelIncludeActionData
1398
+ };
1399
+ }
1400
+ function finishRobotRockHumanWaitOtel(session, handled, outcome) {
1401
+ if (!session.recordOtel) {
1402
+ return;
1403
+ }
1404
+ endRobotRockHumanWaitSpan(session.waitSpan, handled, {
1405
+ handle: session.handle,
1406
+ outcome,
1407
+ includeActionData: session.includeActionData
1408
+ });
1191
1409
  }
1192
1410
  export {
1193
- AGENT_OTEL_SPANS_MAX2 as AGENT_OTEL_SPANS_MAX,
1194
1411
  DEFAULT_TASK_PRIORITY,
1195
1412
  DEFAULT_THREAD_UPDATE_STATUS,
1196
1413
  LOWEST_TASK_PRIORITY,
@@ -1203,30 +1420,35 @@ export {
1203
1420
  RobotRock,
1204
1421
  RobotRockError,
1205
1422
  RobotRockWebhookError,
1423
+ TASK_CONTEXT_FORMAT_VERSION2 as TASK_CONTEXT_FORMAT_VERSION,
1206
1424
  TASK_PRIORITY_RANK,
1207
1425
  TaskExpiredError,
1208
1426
  TaskTimeoutError,
1209
- agentCostSchema2 as agentCostSchema,
1210
- agentCostTokensSchema2 as agentCostTokensSchema,
1211
- agentOtelSchema2 as agentOtelSchema,
1212
- agentOtelSpanStatusSchema2 as agentOtelSpanStatusSchema,
1213
- agentOtelSpanSummarySchema2 as agentOtelSpanSummarySchema,
1214
- agentTelemetryFromOtel,
1427
+ agentChatSeedMessageSchema,
1215
1428
  agentTelemetrySchema2 as agentTelemetrySchema,
1216
- agentToolCallsSchema2 as agentToolCallsSchema,
1217
1429
  assertNotPlatformRejectRequest,
1218
1430
  assignToSchema2 as assignToSchema,
1219
1431
  attachWebhookToActions,
1432
+ beginRobotRockHumanWaitOtel,
1433
+ captureRobotRockOtelHandle,
1434
+ createAgentChatBodySchema,
1435
+ createChatsApi,
1220
1436
  createClient,
1221
1437
  createTaskBodySchema2 as createTaskBodySchema,
1438
+ endRobotRockHumanWaitSpan,
1439
+ finishRobotRockHumanWaitOtel,
1222
1440
  isPlatformMarkDoneAction,
1223
1441
  isPlatformRejectRequestAction,
1224
1442
  isPlatformTerminalAction,
1225
1443
  parseHandledOutcome,
1226
1444
  parsePlatformRejectRequestData,
1445
+ recordRobotRockHandledToOtel,
1227
1446
  resolveRobotRockClient,
1228
1447
  resolveRobotRockConfig,
1448
+ shouldRecordRobotRockOtel,
1229
1449
  shouldStopAgentForHandledAction,
1450
+ startRobotRockHumanWaitSpan,
1451
+ stripPlatformOtelFields,
1230
1452
  taskContextSchema2 as taskContextSchema,
1231
1453
  taskPriorities2 as taskPriorities,
1232
1454
  taskPrioritySchema2 as taskPrioritySchema,
@@ -1235,7 +1457,7 @@ export {
1235
1457
  threadUpdateStatusSchema2 as threadUpdateStatusSchema,
1236
1458
  threadUpdateStatuses2 as threadUpdateStatuses,
1237
1459
  toDiscriminatedApprovalResult,
1238
- toolCallsFromOtelSpans,
1460
+ toRobotRockHandledOtelInput,
1239
1461
  verifyRobotRockWebhook
1240
1462
  };
1241
1463
  //# sourceMappingURL=index.js.map