robotrock 0.8.4 → 0.9.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
  }
@@ -224,7 +224,8 @@ var contextDataSchema = z.object({
224
224
  data: z.record(z.string(), z.unknown()),
225
225
  ui: contextUiSchema
226
226
  }).optional();
227
- var taskContextObjectSchema = z.object({
227
+ var TASK_CONTEXT_FORMAT_VERSION = 2;
228
+ var taskContextObjectBaseSchema = z.object({
228
229
  /** Source application id; omitted tasks are grouped in the default inbox. */
229
230
  app: z.string().min(1).optional(),
230
231
  type: z.string().min(1),
@@ -232,9 +233,22 @@ var taskContextObjectSchema = z.object({
232
233
  description: z.string().optional(),
233
234
  validUntil: z.string().optional(),
234
235
  context: contextDataSchema,
236
+ /** Task context wire format version. @default 2 */
237
+ contextVersion: z.literal(2).optional(),
238
+ /** @deprecated Use `contextVersion`. Accepted on ingest only. */
235
239
  version: z.literal(2).optional(),
236
240
  actions: z.array(taskActionSchema).min(1, "At least one action is required")
237
241
  });
242
+ function normalizeTaskContextVersion(data) {
243
+ const { version: legacyVersion, contextVersion, ...rest } = data;
244
+ return {
245
+ ...rest,
246
+ contextVersion: contextVersion ?? legacyVersion ?? TASK_CONTEXT_FORMAT_VERSION
247
+ };
248
+ }
249
+ var taskContextObjectSchema = taskContextObjectBaseSchema.transform(
250
+ normalizeTaskContextVersion
251
+ );
238
252
  function refineContextPublicUrls(data, ctx) {
239
253
  const error = validateContextPublicUrls(data.context);
240
254
  if (error) {
@@ -278,61 +292,11 @@ var threadUpdateInputSchema = z.object({
278
292
  });
279
293
  var taskPriorities = ["low", "normal", "high", "urgent"];
280
294
  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
295
  var agentTelemetrySchema = z.object({
309
296
  /** 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({
297
+ version: z.string().min(1)
298
+ });
299
+ var createTaskBodySchema = taskContextObjectBaseSchema.extend({
336
300
  assignTo: assignToSchema.optional(),
337
301
  /**
338
302
  * Groups related tasks together. When omitted, the server generates one and
@@ -349,9 +313,9 @@ var createTaskBodySchema = taskContextObjectSchema.extend({
349
313
  * the inbox status bar and the thread update log.
350
314
  */
351
315
  update: threadUpdateInputSchema.optional(),
352
- /** Agent telemetry (version, cost, tool calls) — not shown in inbox UI. */
316
+ /** Agent release version — not shown in inbox UI. */
353
317
  agent: agentTelemetrySchema.optional()
354
- }).superRefine(refineContextPublicUrls);
318
+ }).transform(normalizeTaskContextVersion).superRefine(refineContextPublicUrls);
355
319
 
356
320
  // src/schemas/index.ts
357
321
  var safeUrlSchema2 = z2.string().refine((url) => isPublicHttpUrl(url), {
@@ -395,16 +359,29 @@ var contextDataSchema2 = z2.object({
395
359
  data: z2.record(z2.string(), z2.unknown()),
396
360
  ui: contextUiSchema2
397
361
  }).optional();
398
- var taskContextObjectSchema2 = z2.object({
362
+ var TASK_CONTEXT_FORMAT_VERSION2 = 2;
363
+ var taskContextObjectBaseSchema2 = z2.object({
399
364
  app: z2.string().min(1).optional(),
400
365
  type: z2.string().min(1),
401
366
  name: z2.string().min(1),
402
367
  description: z2.string().optional(),
403
368
  validUntil: z2.string().optional(),
404
369
  context: contextDataSchema2,
370
+ contextVersion: z2.literal(2).optional(),
371
+ /** @deprecated Use `contextVersion`. Accepted on ingest only. */
405
372
  version: z2.literal(2).optional(),
406
373
  actions: z2.array(taskActionSchema2).min(1, "At least one action is required")
407
374
  });
375
+ function normalizeTaskContextVersion2(data) {
376
+ const { version: legacyVersion, contextVersion, ...rest } = data;
377
+ return {
378
+ ...rest,
379
+ contextVersion: contextVersion ?? legacyVersion ?? TASK_CONTEXT_FORMAT_VERSION2
380
+ };
381
+ }
382
+ var taskContextObjectSchema2 = taskContextObjectBaseSchema2.transform(
383
+ normalizeTaskContextVersion2
384
+ );
408
385
  function refineContextPublicUrls2(data, ctx) {
409
386
  const error = validateContextPublicUrls(data.context);
410
387
  if (error) {
@@ -457,56 +434,10 @@ var TASK_PRIORITY_RANK = {
457
434
  high: 3,
458
435
  urgent: 4
459
436
  };
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
437
  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({
438
+ version: z2.string().min(1)
439
+ });
440
+ var createTaskBodySchema2 = taskContextObjectBaseSchema2.extend({
510
441
  assignTo: assignToSchema2.optional(),
511
442
  /**
512
443
  * Groups related tasks together. When omitted, the server generates one and
@@ -524,7 +455,7 @@ var createTaskBodySchema2 = taskContextObjectSchema2.extend({
524
455
  */
525
456
  update: threadUpdateInputSchema2.optional(),
526
457
  agent: agentTelemetrySchema2.optional()
527
- }).superRefine(refineContextPublicUrls2);
458
+ }).transform(normalizeTaskContextVersion2).superRefine(refineContextPublicUrls2);
528
459
  var threadUpdateBodySchema = threadUpdateInputSchema2;
529
460
 
530
461
  // src/approval-result.ts
@@ -560,6 +491,10 @@ var DEFAULT_TIMEOUT_MS = 24 * 60 * 60 * 1e3;
560
491
  function sleep(ms) {
561
492
  return new Promise((resolve) => setTimeout(resolve, ms));
562
493
  }
494
+ function resolveAgentVersionFromEnv() {
495
+ const fromEnv = process.env.AGENT_VERSION?.trim() || process.env.ROBOTROCK_AGENT_VERSION?.trim();
496
+ return fromEnv || void 0;
497
+ }
563
498
  function parseValidUntilMs(value) {
564
499
  if (value === void 0) {
565
500
  return void 0;
@@ -599,7 +534,8 @@ var RobotRock = class {
599
534
  apiKey;
600
535
  baseUrl;
601
536
  app;
602
- version;
537
+ agentVersion;
538
+ contextVersion;
603
539
  webhook;
604
540
  polling;
605
541
  constructor(config) {
@@ -618,7 +554,8 @@ var RobotRock = class {
618
554
  const rawBase = config.baseUrl ?? "https://api.robotrock.io/v1";
619
555
  this.baseUrl = rawBase.replace(/\/+$/, "");
620
556
  this.app = config.app;
621
- this.version = config.version ?? 2;
557
+ this.agentVersion = config.version ?? resolveAgentVersionFromEnv();
558
+ this.contextVersion = config.advanced?.contextVersion ?? TASK_CONTEXT_FORMAT_VERSION2;
622
559
  this.webhook = config.webhook;
623
560
  this.polling = config.polling ?? {};
624
561
  }
@@ -629,15 +566,17 @@ var RobotRock = class {
629
566
  const normalizedTask = normalizeSendToHumanInput(task, {
630
567
  webhook: this.webhook,
631
568
  app: this.app,
632
- version: this.version
569
+ contextVersion: this.contextVersion,
570
+ agentVersion: this.agentVersion
633
571
  });
572
+ const agentVersion = task.version ?? this.agentVersion;
634
573
  const bodyPayload = {
635
574
  ...normalizedTask,
636
575
  ...task.assignTo !== void 0 ? { assignTo: task.assignTo } : {},
637
576
  ...task.threadId !== void 0 ? { threadId: task.threadId } : {},
638
577
  ...task.priority !== void 0 ? { priority: task.priority } : {},
639
578
  ...task.update !== void 0 ? { update: task.update } : {},
640
- ...task.agent !== void 0 ? { agent: task.agent } : {}
579
+ ...agentVersion !== void 0 ? { agent: { version: agentVersion } } : {}
641
580
  };
642
581
  const validation = createTaskBodySchema2.safeParse(bodyPayload);
643
582
  if (!validation.success) {
@@ -673,7 +612,8 @@ var RobotRock = class {
673
612
  const normalizedTask = normalizeSendToHumanInput(task, {
674
613
  webhook: this.webhook,
675
614
  app: this.app,
676
- version: this.version
615
+ contextVersion: this.contextVersion,
616
+ agentVersion: this.agentVersion
677
617
  });
678
618
  const createdTaskTask = await this.createTask(task);
679
619
  const hasHandlers = normalizedTask.actions.some(
@@ -817,6 +757,7 @@ function normalizeSendToHumanInput(task, clientDefaults) {
817
757
  threadId: _threadId,
818
758
  priority: _priority,
819
759
  update: _update,
760
+ version: _version,
820
761
  validUntil,
821
762
  app: taskApp,
822
763
  ...rest
@@ -826,7 +767,7 @@ function normalizeSendToHumanInput(task, clientDefaults) {
826
767
  const app = taskApp ?? clientDefaults.app;
827
768
  return {
828
769
  ...rest,
829
- version: clientDefaults.version,
770
+ contextVersion: clientDefaults.contextVersion,
830
771
  ...app ? { app } : {},
831
772
  ...validUntil !== void 0 ? { validUntil: serializeValidUntil(validUntil) } : {},
832
773
  actions: normalizedActions
@@ -885,6 +826,97 @@ function resolveRobotRockClient(client, configOverrides) {
885
826
  return createClient(resolveRobotRockConfig(configOverrides));
886
827
  }
887
828
 
829
+ // src/platform-actions.ts
830
+ var PLATFORM_MARK_DONE_ACTION_ID = "robotrock:mark-done";
831
+ var PLATFORM_REJECT_REQUEST_ACTION_ID = "robotrock:reject-request";
832
+ var PLATFORM_MARK_DONE_ACTION_TITLE = "Mark as done";
833
+ var PLATFORM_REJECT_REQUEST_ACTION_TITLE = "Reject request";
834
+ var PLATFORM_TERMINAL_ACTION_IDS = [
835
+ PLATFORM_MARK_DONE_ACTION_ID,
836
+ PLATFORM_REJECT_REQUEST_ACTION_ID
837
+ ];
838
+ var PlatformRejectRequestError = class extends Error {
839
+ actionId = PLATFORM_REJECT_REQUEST_ACTION_ID;
840
+ feedback;
841
+ constructor(feedback, message) {
842
+ super(message ?? `Human rejected the request: ${feedback}`);
843
+ this.name = "PlatformRejectRequestError";
844
+ this.feedback = feedback;
845
+ }
846
+ };
847
+ function isPlatformMarkDoneAction(actionId) {
848
+ return actionId === PLATFORM_MARK_DONE_ACTION_ID;
849
+ }
850
+ function isPlatformRejectRequestAction(actionId) {
851
+ return actionId === PLATFORM_REJECT_REQUEST_ACTION_ID;
852
+ }
853
+ function isPlatformTerminalAction(actionId) {
854
+ return isPlatformMarkDoneAction(actionId) || isPlatformRejectRequestAction(actionId);
855
+ }
856
+ function parsePlatformRejectRequestData(data) {
857
+ if (data == null || typeof data !== "object") {
858
+ return null;
859
+ }
860
+ const feedback = data.feedback;
861
+ if (typeof feedback !== "string") {
862
+ return null;
863
+ }
864
+ const trimmed = feedback.trim();
865
+ if (!trimmed) {
866
+ return null;
867
+ }
868
+ return { feedback: trimmed };
869
+ }
870
+ function toHandledAt(value) {
871
+ if (value === void 0) {
872
+ return void 0;
873
+ }
874
+ return value instanceof Date ? value : new Date(value);
875
+ }
876
+ function parseHandledOutcome(input) {
877
+ const handledAt = toHandledAt(input.handledAt);
878
+ const handledBy = input.handledBy;
879
+ if (isPlatformMarkDoneAction(input.actionId)) {
880
+ return {
881
+ source: "platform",
882
+ kind: "mark-done",
883
+ actionId: PLATFORM_MARK_DONE_ACTION_ID,
884
+ data: {},
885
+ handledBy,
886
+ handledAt
887
+ };
888
+ }
889
+ if (isPlatformRejectRequestAction(input.actionId)) {
890
+ return {
891
+ source: "platform",
892
+ kind: "reject-request",
893
+ actionId: PLATFORM_REJECT_REQUEST_ACTION_ID,
894
+ data: parsePlatformRejectRequestData(input.data) ?? { feedback: "" },
895
+ handledBy,
896
+ handledAt
897
+ };
898
+ }
899
+ return {
900
+ source: "task",
901
+ actionId: input.actionId,
902
+ data: input.data ?? {},
903
+ handledBy,
904
+ handledAt
905
+ };
906
+ }
907
+ function assertNotPlatformRejectRequest(actionId, data) {
908
+ if (!isPlatformRejectRequestAction(actionId)) {
909
+ return;
910
+ }
911
+ const parsed = parsePlatformRejectRequestData(data);
912
+ throw new PlatformRejectRequestError(
913
+ parsed?.feedback ?? "No feedback provided"
914
+ );
915
+ }
916
+ function shouldStopAgentForHandledAction(actionId) {
917
+ return isPlatformTerminalAction(actionId);
918
+ }
919
+
888
920
  // src/webhook.ts
889
921
  import { createHmac, timingSafeEqual } from "crypto";
890
922
  import { z as z3 } from "zod";
@@ -900,9 +932,6 @@ var robotRockWebhookPayloadBodySchema = z3.object({
900
932
  handledAt: z3.string().min(1),
901
933
  handlerType: z3.string().min(1)
902
934
  });
903
- var robotRockWebhookPayloadSchema = robotRockWebhookPayloadBodySchema.extend({
904
- headers: z3.record(z3.string(), z3.string())
905
- });
906
935
  var RobotRockWebhookError = class extends Error {
907
936
  constructor(message, code, details) {
908
937
  super(message);
@@ -991,14 +1020,18 @@ function normalizeHeaders(headers) {
991
1020
  return result;
992
1021
  }
993
1022
 
994
- // src/agent-telemetry-from-otel.ts
995
- import { trace } from "@opentelemetry/api";
996
- var AGENT_INFO_MAX_KEYS3 = 32;
997
- function sumToolCalls(toolCalls) {
998
- return Object.values(toolCalls).reduce((sum, count) => sum + count, 0);
999
- }
1023
+ // src/record-handled-to-otel.ts
1024
+ import {
1025
+ context,
1026
+ trace,
1027
+ SpanStatusCode
1028
+ } from "@opentelemetry/api";
1029
+ var TRACER_NAME = "robotrock";
1030
+ var WAIT_SPAN_NAME = "robotrock.wait_for_human";
1031
+ var HANDLED_EVENT_NAME = "robotrock.task_handled";
1032
+ var INVALID_TRACE_ID = "00000000000000000000000000000000";
1000
1033
  function isValidTraceId(traceId) {
1001
- return typeof traceId === "string" && traceId.length > 0 && traceId !== "00000000000000000000000000000000";
1034
+ return typeof traceId === "string" && traceId.length > 0 && traceId !== INVALID_TRACE_ID;
1002
1035
  }
1003
1036
  function resolveSpanContext(span) {
1004
1037
  const active = span ?? trace.getActiveSpan();
@@ -1008,121 +1041,217 @@ function resolveSpanContext(span) {
1008
1041
  }
1009
1042
  return { traceId: ctx.traceId, spanId: ctx.spanId };
1010
1043
  }
1011
- function countErrorSpans(spans) {
1012
- if (!spans) return 0;
1013
- return spans.filter((entry) => entry.status === "error").length;
1014
- }
1015
- function buildInfo(options, spanCtx) {
1016
- const info = { ...options.extraInfo };
1017
- if (spanCtx) {
1018
- info.traceId = spanCtx.traceId;
1019
- info.spanId = spanCtx.spanId;
1020
- }
1021
- if (options.runStartedAt != null) {
1022
- const durationMs = Date.now() - options.runStartedAt;
1023
- if (durationMs >= 0) {
1024
- info.durationMs = durationMs;
1044
+ function parseTaskCreatedAt(submittedAt) {
1045
+ if (submittedAt) {
1046
+ const parsed = Date.parse(submittedAt);
1047
+ if (!Number.isNaN(parsed)) {
1048
+ return parsed;
1025
1049
  }
1026
1050
  }
1027
- const errorCount = countErrorSpans(options.otel?.spans);
1028
- if (errorCount > 0) {
1029
- info.errorCount = errorCount;
1051
+ return Date.now();
1052
+ }
1053
+ function parseHandledAtMs(handledAt) {
1054
+ if (handledAt instanceof Date) {
1055
+ return handledAt.getTime();
1030
1056
  }
1031
- const keys = Object.keys(info);
1032
- if (keys.length === 0) {
1033
- return void 0;
1057
+ if (typeof handledAt === "number") {
1058
+ return handledAt;
1034
1059
  }
1035
- if (keys.length > AGENT_INFO_MAX_KEYS3) {
1036
- const trimmed = {};
1037
- for (const key of keys.slice(0, AGENT_INFO_MAX_KEYS3)) {
1038
- trimmed[key] = info[key];
1039
- }
1040
- return trimmed;
1060
+ const parsed = Date.parse(handledAt);
1061
+ return Number.isNaN(parsed) ? Date.now() : parsed;
1062
+ }
1063
+ function toRobotRockHandledOtelInput(handled) {
1064
+ if ("actionId" in handled) {
1065
+ return {
1066
+ taskId: handled.taskId,
1067
+ action: {
1068
+ id: handled.actionId,
1069
+ data: handled.data
1070
+ },
1071
+ handledBy: handled.handledBy,
1072
+ handledAt: handled.handledAt
1073
+ };
1041
1074
  }
1042
- return info;
1075
+ return {
1076
+ taskId: handled.taskId,
1077
+ action: {
1078
+ id: handled.action.id,
1079
+ title: handled.action.title,
1080
+ data: handled.action.data
1081
+ },
1082
+ handledBy: handled.handledBy,
1083
+ handledAt: handled.handledAt
1084
+ };
1085
+ }
1086
+ function captureRobotRockOtelHandle(task, options) {
1087
+ const spanCtx = resolveSpanContext(options?.span);
1088
+ return {
1089
+ traceId: spanCtx?.traceId ?? "",
1090
+ spanId: spanCtx?.spanId ?? "",
1091
+ taskId: task.taskId,
1092
+ threadId: task.threadId,
1093
+ taskCreatedAt: parseTaskCreatedAt(task.submittedAt)
1094
+ };
1043
1095
  }
1044
- function buildOtelBlock(options, spanCtx) {
1045
- const traceId = options.otel?.traceId ?? spanCtx?.traceId;
1046
- if (!isValidTraceId(traceId)) {
1096
+ function startRobotRockHumanWaitSpan(handle, options) {
1097
+ const parent = options?.span ?? trace.getActiveSpan();
1098
+ if (!parent) {
1047
1099
  return void 0;
1048
1100
  }
1049
- const spans = options.otel?.spans;
1050
- const rootDurationMs = options.otel?.rootDurationMs ?? (options.runStartedAt != null ? Math.max(0, Date.now() - options.runStartedAt) : void 0);
1051
- const block = { traceId };
1052
- const spanId = options.otel?.spanId ?? spanCtx?.spanId;
1053
- if (spanId) {
1054
- block.spanId = spanId;
1101
+ const tracer = trace.getTracer(options?.tracerName ?? TRACER_NAME);
1102
+ const span = tracer.startSpan(
1103
+ WAIT_SPAN_NAME,
1104
+ {},
1105
+ trace.setSpan(context.active(), parent)
1106
+ );
1107
+ span.setAttribute("robotrock.task_id", handle.taskId);
1108
+ if (handle.threadId) {
1109
+ span.setAttribute("robotrock.thread_id", handle.threadId);
1110
+ }
1111
+ return span;
1112
+ }
1113
+ function recordRobotRockHandledToOtel(handled, options = {}) {
1114
+ const span = options.span ?? trace.getActiveSpan();
1115
+ if (!span) {
1116
+ return;
1117
+ }
1118
+ const input = toRobotRockHandledOtelInput(handled);
1119
+ const handledAtMs = parseHandledAtMs(input.handledAt);
1120
+ const handle = options.handle;
1121
+ const humanWaitMs = handle != null ? Math.max(0, handledAtMs - handle.taskCreatedAt) : void 0;
1122
+ span.setAttribute("robotrock.task_id", input.taskId);
1123
+ span.setAttribute("robotrock.action.id", input.action.id);
1124
+ if (input.action.title) {
1125
+ span.setAttribute("robotrock.action.title", input.action.title);
1126
+ }
1127
+ if (input.handledBy) {
1128
+ span.setAttribute("robotrock.handled_by", input.handledBy);
1129
+ }
1130
+ span.setAttribute("robotrock.handled_at", new Date(handledAtMs).toISOString());
1131
+ if (humanWaitMs != null) {
1132
+ span.setAttribute("robotrock.human_wait_ms", humanWaitMs);
1133
+ }
1134
+ if (input.threadId) {
1135
+ span.setAttribute("robotrock.thread_id", input.threadId);
1136
+ }
1137
+ const eventAttributes = {
1138
+ "robotrock.task_id": input.taskId,
1139
+ "robotrock.action.id": input.action.id
1140
+ };
1141
+ if (input.handledBy) {
1142
+ eventAttributes["robotrock.handled_by"] = input.handledBy;
1055
1143
  }
1056
- if (rootDurationMs != null) {
1057
- block.rootDurationMs = rootDurationMs;
1144
+ if (humanWaitMs != null) {
1145
+ eventAttributes["robotrock.human_wait_ms"] = humanWaitMs;
1058
1146
  }
1059
- if (spans && spans.length > 0) {
1060
- block.spans = spans;
1147
+ if (options.includeActionData && input.action.data != null) {
1148
+ const serialized = JSON.stringify(input.action.data);
1149
+ const truncated = serialized.length > 512 ? `${serialized.slice(0, 512)}\u2026` : serialized;
1150
+ span.setAttribute("robotrock.action.data", truncated);
1151
+ eventAttributes["robotrock.action.data"] = truncated;
1061
1152
  }
1062
- return block;
1153
+ span.addEvent(HANDLED_EVENT_NAME, eventAttributes);
1063
1154
  }
1064
- function agentTelemetryFromOtel(options = {}) {
1065
- const spanCtx = resolveSpanContext(options.span);
1066
- const telemetry = {};
1067
- if (options.version) {
1068
- telemetry.version = options.version;
1069
- }
1070
- if (options.toolCalls && Object.keys(options.toolCalls).length > 0) {
1071
- telemetry.toolCalls = options.toolCalls;
1072
- }
1073
- if (options.toolCallCount !== void 0) {
1074
- telemetry.toolCallCount = options.toolCallCount;
1075
- } else if (telemetry.toolCalls) {
1076
- const sum = sumToolCalls(telemetry.toolCalls);
1077
- if (sum > 0) {
1078
- telemetry.toolCallCount = sum;
1079
- }
1080
- }
1081
- if (options.cost) {
1082
- telemetry.cost = options.cost;
1155
+ function endRobotRockHumanWaitSpan(span, handled, options = {}) {
1156
+ if (!span) {
1157
+ return;
1158
+ }
1159
+ const outcome = options.outcome ?? (handled ? "handled" : "timeout");
1160
+ if (handled && outcome === "handled") {
1161
+ recordRobotRockHandledToOtel(handled, {
1162
+ span,
1163
+ handle: options.handle,
1164
+ includeActionData: options.includeActionData
1165
+ });
1166
+ span.setStatus({ code: SpanStatusCode.OK });
1167
+ } else {
1168
+ span.setAttribute("robotrock.outcome", outcome);
1169
+ span.setStatus({
1170
+ code: SpanStatusCode.ERROR,
1171
+ message: outcome === "timeout" ? "Human response timed out before validUntil" : "Human wait failed"
1172
+ });
1083
1173
  }
1084
- const info = buildInfo(options, spanCtx);
1085
- if (info) {
1086
- telemetry.info = info;
1174
+ span.end();
1175
+ }
1176
+
1177
+ // src/otel-platform.ts
1178
+ function shouldRecordRobotRockOtel(recordOtel) {
1179
+ if (recordOtel === true) {
1180
+ return true;
1087
1181
  }
1088
- const otel = buildOtelBlock(options, spanCtx);
1089
- if (otel) {
1090
- telemetry.otel = otel;
1182
+ if (recordOtel === false) {
1183
+ return false;
1091
1184
  }
1092
- return agentTelemetrySchema2.parse(telemetry);
1185
+ const env = process.env.ROBOTROCK_OTEL_RECORD_HANDLED;
1186
+ return env === "true" || env === "1";
1093
1187
  }
1094
- function toolCallsFromOtelSpans(spans) {
1095
- const counts = {};
1096
- for (const span of spans) {
1097
- counts[span.name] = (counts[span.name] ?? 0) + 1;
1188
+ function stripPlatformOtelFields(payload) {
1189
+ const { recordOtel: _recordOtel, otelIncludeActionData: _otelIncludeActionData, ...rest } = payload;
1190
+ return rest;
1191
+ }
1192
+ function beginRobotRockHumanWaitOtel(task, options = {}) {
1193
+ const recordOtel = shouldRecordRobotRockOtel(options.recordOtel);
1194
+ if (!recordOtel) {
1195
+ return { recordOtel: false };
1098
1196
  }
1099
- return counts;
1197
+ const handle = captureRobotRockOtelHandle(task);
1198
+ const waitSpan = startRobotRockHumanWaitSpan(handle);
1199
+ return {
1200
+ recordOtel: true,
1201
+ handle,
1202
+ waitSpan,
1203
+ includeActionData: options.otelIncludeActionData
1204
+ };
1205
+ }
1206
+ function finishRobotRockHumanWaitOtel(session, handled, outcome) {
1207
+ if (!session.recordOtel) {
1208
+ return;
1209
+ }
1210
+ endRobotRockHumanWaitSpan(session.waitSpan, handled, {
1211
+ handle: session.handle,
1212
+ outcome,
1213
+ includeActionData: session.includeActionData
1214
+ });
1100
1215
  }
1101
1216
  export {
1102
- AGENT_OTEL_SPANS_MAX2 as AGENT_OTEL_SPANS_MAX,
1103
1217
  DEFAULT_TASK_PRIORITY,
1104
1218
  DEFAULT_THREAD_UPDATE_STATUS,
1105
1219
  LOWEST_TASK_PRIORITY,
1220
+ PLATFORM_MARK_DONE_ACTION_ID,
1221
+ PLATFORM_MARK_DONE_ACTION_TITLE,
1222
+ PLATFORM_REJECT_REQUEST_ACTION_ID,
1223
+ PLATFORM_REJECT_REQUEST_ACTION_TITLE,
1224
+ PLATFORM_TERMINAL_ACTION_IDS,
1225
+ PlatformRejectRequestError,
1106
1226
  RobotRock,
1107
1227
  RobotRockError,
1108
1228
  RobotRockWebhookError,
1229
+ TASK_CONTEXT_FORMAT_VERSION2 as TASK_CONTEXT_FORMAT_VERSION,
1109
1230
  TASK_PRIORITY_RANK,
1110
1231
  TaskExpiredError,
1111
1232
  TaskTimeoutError,
1112
- agentCostSchema2 as agentCostSchema,
1113
- agentCostTokensSchema2 as agentCostTokensSchema,
1114
- agentOtelSchema2 as agentOtelSchema,
1115
- agentOtelSpanStatusSchema2 as agentOtelSpanStatusSchema,
1116
- agentOtelSpanSummarySchema2 as agentOtelSpanSummarySchema,
1117
- agentTelemetryFromOtel,
1118
1233
  agentTelemetrySchema2 as agentTelemetrySchema,
1119
- agentToolCallsSchema2 as agentToolCallsSchema,
1234
+ assertNotPlatformRejectRequest,
1120
1235
  assignToSchema2 as assignToSchema,
1121
1236
  attachWebhookToActions,
1237
+ beginRobotRockHumanWaitOtel,
1238
+ captureRobotRockOtelHandle,
1122
1239
  createClient,
1123
1240
  createTaskBodySchema2 as createTaskBodySchema,
1241
+ endRobotRockHumanWaitSpan,
1242
+ finishRobotRockHumanWaitOtel,
1243
+ isPlatformMarkDoneAction,
1244
+ isPlatformRejectRequestAction,
1245
+ isPlatformTerminalAction,
1246
+ parseHandledOutcome,
1247
+ parsePlatformRejectRequestData,
1248
+ recordRobotRockHandledToOtel,
1124
1249
  resolveRobotRockClient,
1125
1250
  resolveRobotRockConfig,
1251
+ shouldRecordRobotRockOtel,
1252
+ shouldStopAgentForHandledAction,
1253
+ startRobotRockHumanWaitSpan,
1254
+ stripPlatformOtelFields,
1126
1255
  taskContextSchema2 as taskContextSchema,
1127
1256
  taskPriorities2 as taskPriorities,
1128
1257
  taskPrioritySchema2 as taskPrioritySchema,
@@ -1131,7 +1260,7 @@ export {
1131
1260
  threadUpdateStatusSchema2 as threadUpdateStatusSchema,
1132
1261
  threadUpdateStatuses2 as threadUpdateStatuses,
1133
1262
  toDiscriminatedApprovalResult,
1134
- toolCallsFromOtelSpans,
1263
+ toRobotRockHandledOtelInput,
1135
1264
  verifyRobotRockWebhook
1136
1265
  };
1137
1266
  //# sourceMappingURL=index.js.map