@wayai/cli 0.3.102 → 0.3.104

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
@@ -441,11 +441,11 @@ function captureException2(error, context) {
441
441
  Sentry.captureException(error);
442
442
  });
443
443
  }
444
- function addApiBreadcrumb(method, path28) {
444
+ function addApiBreadcrumb(method, path30) {
445
445
  if (!initialized) return;
446
446
  Sentry.addBreadcrumb({
447
447
  category: "http",
448
- message: `${method} ${path28}`,
448
+ message: `${method} ${path30}`,
449
449
  level: "info"
450
450
  });
451
451
  }
@@ -503,13 +503,13 @@ var init_api_client = __esm({
503
503
  init_sentry();
504
504
  init_mask_secrets();
505
505
  RETRYABLE_BACKOFF_MS = [500, 1e3, 2e3];
506
- delay = (ms) => new Promise((resolve4) => setTimeout(resolve4, ms));
506
+ delay = (ms) => new Promise((resolve6) => setTimeout(resolve6, ms));
507
507
  ApiError = class extends Error {
508
508
  status;
509
509
  body;
510
- constructor(method, path28, status, body) {
510
+ constructor(method, path30, status, body) {
511
511
  const safeBody = maskSecretsInMessage(body);
512
- super(`API request failed: ${method} ${path28} (${status}): ${safeBody}`);
512
+ super(`API request failed: ${method} ${path30} (${status}): ${safeBody}`);
513
513
  this.name = "ApiError";
514
514
  this.status = status;
515
515
  this.body = safeBody;
@@ -657,8 +657,8 @@ var init_api_client = __esm({
657
657
  ...organizationId && { organization_id: organizationId }
658
658
  });
659
659
  }
660
- async lookup(path28, opts) {
661
- const params = new URLSearchParams({ path: path28 });
660
+ async lookup(path30, opts) {
661
+ const params = new URLSearchParams({ path: path30 });
662
662
  if (opts?.organizationId) params.set("organization_id", opts.organizationId);
663
663
  return this.request("GET", `/api/ci/lookup?${params.toString()}`);
664
664
  }
@@ -1058,9 +1058,9 @@ var init_api_client = __esm({
1058
1058
  * sandbox for this conversation, or the blob was purged).
1059
1059
  */
1060
1060
  async downloadArchiveSandboxFs(hubId, conversationId) {
1061
- const path28 = `/api/archive/${encodeURIComponent(hubId)}/conversations/${encodeURIComponent(conversationId)}/sandbox`;
1062
- addApiBreadcrumb("GET", path28);
1063
- const url = `${this.apiUrl}${path28}`;
1061
+ const path30 = `/api/archive/${encodeURIComponent(hubId)}/conversations/${encodeURIComponent(conversationId)}/sandbox`;
1062
+ addApiBreadcrumb("GET", path30);
1063
+ const url = `${this.apiUrl}${path30}`;
1064
1064
  let response = await this.send(url, "GET");
1065
1065
  if (response.status === 401 && this.onUnauthorized) {
1066
1066
  let refreshed;
@@ -1074,7 +1074,7 @@ var init_api_client = __esm({
1074
1074
  }
1075
1075
  }
1076
1076
  if (!response.ok) {
1077
- throw new ApiError("GET", path28, response.status, await response.text());
1077
+ throw new ApiError("GET", path30, response.status, await response.text());
1078
1078
  }
1079
1079
  return new Uint8Array(await response.arrayBuffer());
1080
1080
  }
@@ -1114,9 +1114,9 @@ var init_api_client = __esm({
1114
1114
  `/api/admin/data-explorer/debug/observability/${encodeURIComponent(hubId)}/conversations/${encodeURIComponent(conversationId)}${qs}`
1115
1115
  );
1116
1116
  }
1117
- async request(method, path28, body) {
1118
- addApiBreadcrumb(method, path28);
1119
- const url = `${this.apiUrl}${path28}`;
1117
+ async request(method, path30, body) {
1118
+ addApiBreadcrumb(method, path30);
1119
+ const url = `${this.apiUrl}${path30}`;
1120
1120
  let refreshedOn401 = false;
1121
1121
  for (let retry = 0; ; retry++) {
1122
1122
  let response = await this.send(url, method, body);
@@ -1140,7 +1140,7 @@ var init_api_client = __esm({
1140
1140
  await delay(RETRYABLE_BACKOFF_MS[retry]);
1141
1141
  continue;
1142
1142
  }
1143
- throw new ApiError(method, path28, response.status, errorBody);
1143
+ throw new ApiError(method, path30, response.status, errorBody);
1144
1144
  }
1145
1145
  }
1146
1146
  /** Issue a single authenticated request with the client's current token. */
@@ -1575,8 +1575,8 @@ var init_parseUtil = __esm({
1575
1575
  init_errors();
1576
1576
  init_en();
1577
1577
  makeIssue = (params) => {
1578
- const { data, path: path28, errorMaps, issueData } = params;
1579
- const fullPath = [...path28, ...issueData.path || []];
1578
+ const { data, path: path30, errorMaps, issueData } = params;
1579
+ const fullPath = [...path30, ...issueData.path || []];
1580
1580
  const fullIssue = {
1581
1581
  ...issueData,
1582
1582
  path: fullPath
@@ -1887,11 +1887,11 @@ var init_types = __esm({
1887
1887
  init_parseUtil();
1888
1888
  init_util();
1889
1889
  ParseInputLazyPath = class {
1890
- constructor(parent, value, path28, key) {
1890
+ constructor(parent, value, path30, key) {
1891
1891
  this._cachedPath = [];
1892
1892
  this.parent = parent;
1893
1893
  this.data = value;
1894
- this._path = path28;
1894
+ this._path = path30;
1895
1895
  this._key = key;
1896
1896
  }
1897
1897
  get path() {
@@ -5375,12 +5375,12 @@ function typeMatches(typeField, allowed) {
5375
5375
  if (Array.isArray(typeField)) return typeField.every((t) => typeof t === "string" && allowed.has(t));
5376
5376
  return false;
5377
5377
  }
5378
- function validateSchema(schema, path28, errors, opts = {}) {
5378
+ function validateSchema(schema, path30, errors, opts = {}) {
5379
5379
  if (typeof schema === "boolean") return;
5380
5380
  const depth = opts.depth ?? 0;
5381
5381
  if (depth > MAX_SCHEMA_DEPTH) {
5382
5382
  errors.push({
5383
- path: path28 || "<root>",
5383
+ path: path30 || "<root>",
5384
5384
  message: `schema nesting exceeds ${MAX_SCHEMA_DEPTH} levels`,
5385
5385
  suggestion: "Flatten the schema; LLM providers reject deeply nested tool input_schemas."
5386
5386
  });
@@ -5388,7 +5388,7 @@ function validateSchema(schema, path28, errors, opts = {}) {
5388
5388
  }
5389
5389
  if (!isRecord(schema)) {
5390
5390
  errors.push({
5391
- path: path28,
5391
+ path: path30,
5392
5392
  message: `expected object, got ${schema === null ? "null" : typeof schema}`
5393
5393
  });
5394
5394
  return;
@@ -5396,14 +5396,14 @@ function validateSchema(schema, path28, errors, opts = {}) {
5396
5396
  if (opts.isRoot) {
5397
5397
  if ("type" in schema && schema.type !== "object") {
5398
5398
  errors.push({
5399
- path: path28 ? `${path28}.type` : "type",
5399
+ path: path30 ? `${path30}.type` : "type",
5400
5400
  message: `root type must be 'object', got ${JSON.stringify(schema.type)}`,
5401
5401
  suggestion: "Tool parameters at the root must be an object: `{ type: 'object', properties: { ... } }`."
5402
5402
  });
5403
5403
  }
5404
5404
  } else if ("type" in schema && !typeMatches(schema.type, ALLOWED_TYPES)) {
5405
5405
  errors.push({
5406
- path: `${path28}.type`,
5406
+ path: `${path30}.type`,
5407
5407
  message: `type must be one of ${[...ALLOWED_TYPES].join("/")} or an array of those, got ${JSON.stringify(schema.type)}`
5408
5408
  });
5409
5409
  }
@@ -5412,25 +5412,25 @@ function validateSchema(schema, path28, errors, opts = {}) {
5412
5412
  if (typeof e === "string") {
5413
5413
  const isPlaceholder = PLACEHOLDER_TOKENS.includes(e);
5414
5414
  errors.push({
5415
- path: `${path28}.enum`,
5415
+ path: `${path30}.enum`,
5416
5416
  message: `enum must be a non-empty array of primitives, got string ${JSON.stringify(e)}`,
5417
5417
  suggestion: isPlaceholder ? "Set `operation: 'update_kanban_status'` on the tool and ensure at least one hub.kanban_status has `allowsAgentUpdate: true` so the platform can render the placeholder into a valid array." : 'Wrap the value in an array: `enum: ["value"]`.'
5418
5418
  });
5419
5419
  } else if (!Array.isArray(e)) {
5420
5420
  errors.push({
5421
- path: `${path28}.enum`,
5421
+ path: `${path30}.enum`,
5422
5422
  message: `enum must be a non-empty array of primitives, got ${typeof e}`
5423
5423
  });
5424
5424
  } else if (e.length === 0) {
5425
5425
  errors.push({
5426
- path: `${path28}.enum`,
5426
+ path: `${path30}.enum`,
5427
5427
  message: "enum must not be empty"
5428
5428
  });
5429
5429
  } else {
5430
5430
  for (let i = 0; i < e.length; i++) {
5431
5431
  if (!isPrimitive(e[i])) {
5432
5432
  errors.push({
5433
- path: `${path28}.enum[${i}]`,
5433
+ path: `${path30}.enum[${i}]`,
5434
5434
  message: `enum entry must be a primitive (string/number/boolean/null), got ${typeof e[i]}`
5435
5435
  });
5436
5436
  }
@@ -5440,7 +5440,7 @@ function validateSchema(schema, path28, errors, opts = {}) {
5440
5440
  for (const key of ["exclusiveMinimum", "exclusiveMaximum"]) {
5441
5441
  if (key in schema && typeof schema[key] === "boolean") {
5442
5442
  errors.push({
5443
- path: `${path28}.${key}`,
5443
+ path: `${path30}.${key}`,
5444
5444
  message: `${key} as boolean is draft-04 syntax; draft 2020-12 requires a numeric value`,
5445
5445
  suggestion: `Replace with the numeric bound, e.g. \`${key}: 0\`.`
5446
5446
  });
@@ -5449,45 +5449,45 @@ function validateSchema(schema, path28, errors, opts = {}) {
5449
5449
  if ("properties" in schema) {
5450
5450
  if (!isRecord(schema.properties)) {
5451
5451
  errors.push({
5452
- path: `${path28}.properties`,
5452
+ path: `${path30}.properties`,
5453
5453
  message: `properties must be an object, got ${Array.isArray(schema.properties) ? "array" : typeof schema.properties}`
5454
5454
  });
5455
5455
  } else {
5456
5456
  for (const [propName, propSchema] of Object.entries(schema.properties)) {
5457
- validateSchema(propSchema, `${path28}.properties.${propName}`, errors, { depth: depth + 1 });
5457
+ validateSchema(propSchema, `${path30}.properties.${propName}`, errors, { depth: depth + 1 });
5458
5458
  }
5459
5459
  }
5460
5460
  }
5461
5461
  if (schemaTypeIncludes(schema, "array") && "items" in schema) {
5462
5462
  if (Array.isArray(schema.items)) {
5463
- schema.items.forEach((sub, i) => validateSchema(sub, `${path28}.items[${i}]`, errors, { depth: depth + 1 }));
5463
+ schema.items.forEach((sub, i) => validateSchema(sub, `${path30}.items[${i}]`, errors, { depth: depth + 1 }));
5464
5464
  } else {
5465
- validateSchema(schema.items, `${path28}.items`, errors, { depth: depth + 1 });
5465
+ validateSchema(schema.items, `${path30}.items`, errors, { depth: depth + 1 });
5466
5466
  }
5467
5467
  }
5468
5468
  for (const key of SUBSCHEMA_OBJECT_KEYWORDS) {
5469
5469
  if (key in schema && isRecord(schema[key])) {
5470
- validateSchema(schema[key], `${path28}.${key}`, errors, { depth: depth + 1 });
5470
+ validateSchema(schema[key], `${path30}.${key}`, errors, { depth: depth + 1 });
5471
5471
  }
5472
5472
  }
5473
5473
  for (const key of SUBSCHEMA_LIST_KEYWORDS) {
5474
5474
  const list = schema[key];
5475
5475
  if (Array.isArray(list)) {
5476
- list.forEach((sub, i) => validateSchema(sub, `${path28}.${key}[${i}]`, errors, { depth: depth + 1 }));
5476
+ list.forEach((sub, i) => validateSchema(sub, `${path30}.${key}[${i}]`, errors, { depth: depth + 1 }));
5477
5477
  }
5478
5478
  }
5479
5479
  for (const key of SUBSCHEMA_MAP_KEYWORDS) {
5480
5480
  const map = schema[key];
5481
5481
  if (isRecord(map)) {
5482
5482
  for (const [name, sub] of Object.entries(map)) {
5483
- validateSchema(sub, `${path28}.${key}.${name}`, errors, { depth: depth + 1 });
5483
+ validateSchema(sub, `${path30}.${key}.${name}`, errors, { depth: depth + 1 });
5484
5484
  }
5485
5485
  }
5486
5486
  }
5487
5487
  const reportedPaths = new Set(errors.map((e) => e.path));
5488
5488
  for (const [k, v] of Object.entries(schema)) {
5489
5489
  if (typeof v !== "string") continue;
5490
- const fieldPath = `${path28}.${k}`;
5490
+ const fieldPath = `${path30}.${k}`;
5491
5491
  if (reportedPaths.has(fieldPath)) continue;
5492
5492
  for (const token of PLACEHOLDER_TOKENS) {
5493
5493
  if (v === token) {
@@ -5562,15 +5562,17 @@ function describeRole(role) {
5562
5562
  return `a ${typeof role}`;
5563
5563
  }
5564
5564
  function asEvalInputRecord(input) {
5565
- const value = typeof input === "string" ? (() => {
5566
- try {
5567
- return JSON.parse(input);
5568
- } catch {
5569
- return null;
5570
- }
5571
- })() : input;
5565
+ const value = parseMaybeJson(input);
5572
5566
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
5573
5567
  }
5568
+ function parseMaybeJson(value) {
5569
+ if (typeof value !== "string") return value;
5570
+ try {
5571
+ return JSON.parse(value);
5572
+ } catch {
5573
+ return null;
5574
+ }
5575
+ }
5574
5576
  function evalInputRoleError(input) {
5575
5577
  const record = asEvalInputRecord(input);
5576
5578
  if (record === null) return null;
@@ -5581,6 +5583,32 @@ function evalInputRoleError(input) {
5581
5583
  }
5582
5584
  return `role must be ${EVAL_INPUT_ROLE_LIST} (received ${describeRole(role)}) \u2014 an eval trigger is what the responder answers, and an assistant or tool role gives it nothing to answer`;
5583
5585
  }
5586
+ function evalTurnAttachmentsError(turn) {
5587
+ const record = asEvalInputRecord(turn);
5588
+ if (record === null) return null;
5589
+ const attachments = record.attachments;
5590
+ if (attachments === void 0 || attachments === null) return null;
5591
+ if (!Array.isArray(attachments)) return "attachments must be an array";
5592
+ if (attachments.length === 0) return null;
5593
+ if (attachments.length > MAX_ATTACHMENTS_PER_MESSAGE) {
5594
+ return `attachments must have at most ${MAX_ATTACHMENTS_PER_MESSAGE} items`;
5595
+ }
5596
+ if (!turnMayCarryAttachments({ role: String(record.role), attachments })) {
5597
+ return ATTACHMENTS_USER_ONLY_MESSAGE;
5598
+ }
5599
+ for (let i = 0; i < attachments.length; i++) {
5600
+ const parsed = evalAttachmentRef.safeParse(attachments[i]);
5601
+ if (!parsed.success) {
5602
+ const issue = parsed.error.issues[0];
5603
+ return `attachments[${i}].${issue?.path.join(".") || "?"} is invalid: ${issue?.message ?? "malformed"}`;
5604
+ }
5605
+ }
5606
+ return null;
5607
+ }
5608
+ function asEvalHistoryArray(history) {
5609
+ const value = parseMaybeJson(history);
5610
+ return Array.isArray(value) ? value : null;
5611
+ }
5584
5612
  function refineEvalMessageTextRole(value, ctx) {
5585
5613
  const error = evalInputRoleError(value.eval.message_text);
5586
5614
  if (error) {
@@ -5591,6 +5619,64 @@ function refineEvalMessageTextRole(value, ctx) {
5591
5619
  });
5592
5620
  }
5593
5621
  }
5622
+ function refineEvalAttachments(value, ctx) {
5623
+ const triggerError = evalTurnAttachmentsError(value.eval.message_text);
5624
+ if (triggerError) {
5625
+ ctx.addIssue({
5626
+ code: external_exports.ZodIssueCode.custom,
5627
+ path: ["eval", "message_text", "attachments"],
5628
+ message: triggerError
5629
+ });
5630
+ }
5631
+ const history = asEvalHistoryArray(value.eval.message_history);
5632
+ if (!history) return;
5633
+ for (let i = 0; i < history.length; i++) {
5634
+ const error = evalTurnAttachmentsError(history[i]);
5635
+ if (error) {
5636
+ ctx.addIssue({
5637
+ code: external_exports.ZodIssueCode.custom,
5638
+ path: ["eval", "message_history", i, "attachments"],
5639
+ message: error
5640
+ });
5641
+ }
5642
+ }
5643
+ }
5644
+ function collectTurnAttachmentHashes(turn) {
5645
+ const atts = asEvalInputRecord(turn)?.attachments;
5646
+ if (!Array.isArray(atts)) return [];
5647
+ const hashes = [];
5648
+ for (const a of atts) {
5649
+ const parsed = evalAttachmentRef.safeParse(a);
5650
+ if (parsed.success) hashes.push(parsed.data.hash);
5651
+ }
5652
+ return hashes;
5653
+ }
5654
+ function foldDiacritics(input) {
5655
+ return input.normalize("NFKD").replace(/[̀-ͯ]/g, "");
5656
+ }
5657
+ function slugifyKebab(input, maxLength = 64) {
5658
+ if (typeof input !== "string") return "";
5659
+ return foldDiacritics(input).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/-+/g, "-").slice(0, maxLength).replace(/^-|-$/g, "");
5660
+ }
5661
+ function ensureUniqueSlug(slug, used) {
5662
+ if (!used.has(slug)) {
5663
+ used.add(slug);
5664
+ return slug;
5665
+ }
5666
+ for (let i = 2; i < 1e6; i++) {
5667
+ const suffix = `_${i}`;
5668
+ const baseLen = Math.max(1, 50 - suffix.length);
5669
+ const candidate = `${slug.slice(0, baseLen)}${suffix}`;
5670
+ if (!used.has(candidate)) {
5671
+ used.add(candidate);
5672
+ return candidate;
5673
+ }
5674
+ }
5675
+ throw new Error(`slug collision space exhausted for "${slug}"`);
5676
+ }
5677
+ function slugifySkillName(name) {
5678
+ return slugifyKebab(name, 64);
5679
+ }
5594
5680
  function refineHubAsCodeCustomTools(config, ctx) {
5595
5681
  const agents = config.agents;
5596
5682
  if (!Array.isArray(agents)) return;
@@ -5698,10 +5784,84 @@ function refineHubAsCodeEvals(config, ctx) {
5698
5784
  }
5699
5785
  }
5700
5786
  }
5701
- function isSafeTemplatePath(path28) {
5702
- if (path28.length === 0 || path28.length > 300) return false;
5703
- if (path28.startsWith("/") || path28.includes("\\")) return false;
5704
- const segments = path28.split("/");
5787
+ function refineHubAsCodeEvalAttachments(config, ctx) {
5788
+ const cfg = config;
5789
+ const referencedHashes = /* @__PURE__ */ new Set();
5790
+ const addTurnIssue = (path30, turn, name) => {
5791
+ const error = evalTurnAttachmentsError(turn);
5792
+ if (error) {
5793
+ ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: path30, message: `${name}: ${error}` });
5794
+ return;
5795
+ }
5796
+ for (const hash of collectTurnAttachmentHashes(turn)) referencedHashes.add(hash);
5797
+ };
5798
+ if (Array.isArray(cfg.evals)) {
5799
+ cfg.evals.forEach((entry, e) => {
5800
+ const ev = entry;
5801
+ const name = `eval "${ev?.name ?? "<unnamed>"}"`;
5802
+ addTurnIssue(["evals", e, "input"], ev?.input, name);
5803
+ if (Array.isArray(ev?.history)) {
5804
+ ev.history.forEach((turn, h) => addTurnIssue(["evals", e, "history", h], turn, name));
5805
+ }
5806
+ });
5807
+ }
5808
+ if (Array.isArray(cfg.journeys)) {
5809
+ cfg.journeys.forEach((entry, j) => {
5810
+ const jr = entry;
5811
+ const name = `journey "${jr?.name ?? "<unnamed>"}"`;
5812
+ if (Array.isArray(jr?.transcript)) {
5813
+ jr.transcript.forEach((turn, t) => addTurnIssue(["journeys", j, "transcript", t], turn, name));
5814
+ }
5815
+ });
5816
+ }
5817
+ const carriedHashes = /* @__PURE__ */ new Set();
5818
+ const files = cfg.eval_attachment_files;
5819
+ if (Array.isArray(files)) {
5820
+ if (files.length > MAX_EVAL_ATTACHMENT_CARRIERS) {
5821
+ ctx.addIssue({
5822
+ code: external_exports.ZodIssueCode.custom,
5823
+ path: ["eval_attachment_files"],
5824
+ message: `too many attachment carriers (${files.length}); the maximum is ${MAX_EVAL_ATTACHMENT_CARRIERS}`
5825
+ });
5826
+ }
5827
+ let aggregateEncoded = 0;
5828
+ files.forEach((f, i) => {
5829
+ const h = f?.hash;
5830
+ if (typeof h === "string") carriedHashes.add(h);
5831
+ const b64 = f?.content_base64;
5832
+ if (typeof b64 === "string") {
5833
+ aggregateEncoded += b64.length;
5834
+ if (b64.length > MAX_EVAL_ATTACHMENT_ENCODED_BYTES) {
5835
+ ctx.addIssue({
5836
+ code: external_exports.ZodIssueCode.custom,
5837
+ path: ["eval_attachment_files", i, "content_base64"],
5838
+ message: `attachment ${typeof h === "string" ? `${h.slice(0, 12)}\u2026 ` : ""}exceeds the ${(MAX_RESOURCE_FILE_SIZE / 1024 / 1024).toFixed(0)}MB size limit`
5839
+ });
5840
+ }
5841
+ }
5842
+ });
5843
+ if (aggregateEncoded > MAX_EVAL_ATTACHMENT_AGGREGATE_ENCODED_BYTES) {
5844
+ ctx.addIssue({
5845
+ code: external_exports.ZodIssueCode.custom,
5846
+ path: ["eval_attachment_files"],
5847
+ message: `total attachment bytes exceed the ${(MAX_EVAL_ATTACHMENT_AGGREGATE_ENCODED_BYTES / 1024 / 1024).toFixed(0)}MB per-push limit \u2014 split the push or reduce attachments`
5848
+ });
5849
+ }
5850
+ }
5851
+ for (const hash of referencedHashes) {
5852
+ if (!carriedHashes.has(hash)) {
5853
+ ctx.addIssue({
5854
+ code: external_exports.ZodIssueCode.custom,
5855
+ path: ["eval_attachment_files"],
5856
+ message: `attachment ${hash.slice(0, 12)}\u2026 is referenced by a turn but its bytes are missing from eval_attachment_files`
5857
+ });
5858
+ }
5859
+ }
5860
+ }
5861
+ function isSafeTemplatePath(path30) {
5862
+ if (path30.length === 0 || path30.length > 300) return false;
5863
+ if (path30.startsWith("/") || path30.includes("\\")) return false;
5864
+ const segments = path30.split("/");
5705
5865
  return segments.every(
5706
5866
  (seg) => seg.length > 0 && seg !== "." && seg !== ".." && /^[A-Za-z0-9._-]+$/.test(seg)
5707
5867
  );
@@ -5711,32 +5871,6 @@ function identifyTokenType(token) {
5711
5871
  if (token.startsWith("eyJ")) return "jwt";
5712
5872
  return "unknown";
5713
5873
  }
5714
- function foldDiacritics(input) {
5715
- return input.normalize("NFKD").replace(/[̀-ͯ]/g, "");
5716
- }
5717
- function slugifyKebab(input, maxLength = 64) {
5718
- if (typeof input !== "string") return "";
5719
- return foldDiacritics(input).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/-+/g, "-").slice(0, maxLength).replace(/^-|-$/g, "");
5720
- }
5721
- function ensureUniqueSlug(slug, used) {
5722
- if (!used.has(slug)) {
5723
- used.add(slug);
5724
- return slug;
5725
- }
5726
- for (let i = 2; i < 1e6; i++) {
5727
- const suffix = `_${i}`;
5728
- const baseLen = Math.max(1, 50 - suffix.length);
5729
- const candidate = `${slug.slice(0, baseLen)}${suffix}`;
5730
- if (!used.has(candidate)) {
5731
- used.add(candidate);
5732
- return candidate;
5733
- }
5734
- }
5735
- throw new Error(`slug collision space exhausted for "${slug}"`);
5736
- }
5737
- function slugifySkillName(name) {
5738
- return slugifyKebab(name, 64);
5739
- }
5740
5874
  function findStepBoundaries(transcript) {
5741
5875
  const out = [];
5742
5876
  const n = transcript.length;
@@ -5771,7 +5905,7 @@ function findStepBoundaries(transcript) {
5771
5905
  }
5772
5906
  return out;
5773
5907
  }
5774
- var UUID_RE, HEX_RE, WORKOS_ID_RE, OPAQUE_SECRET_RE, BASE64_SECRET_RE, BASE64_MARKER_RE, TOKEN_RE, SENSITIVE_FIELDS, SENSITIVE_KEY_ALTERNATION, SENSITIVE_KEY_PATTERN, JSON_CREDENTIAL_RE, QUERY_CREDENTIAL_RE, REDACTED, PROVIDER_TOKEN_RE, APP_ERROR_BRAND, AppError, LLM_RATE_LIMIT_BRAND, LlmRateLimitError, LLM_PROVIDER_HTTP_BRAND, LlmProviderHttpError, LLM_PROVIDER_CONTENT_BRAND, LlmProviderContentError, LLM_PROVIDER_TIMEOUT_BRAND, LlmProviderTimeoutError, ExternalServiceError, CHANNEL_PROVIDER_HTTP_BRAND, ChannelProviderHttpError, MEDIA_PROVIDER_HTTP_BRAND, MediaProviderHttpError, AUTH_TYPE, ORG_CREDENTIAL_AUTH_TYPES, AUTH_TYPE_DISPLAY, LEGACY_AUTH_TYPE_MAP, VALID_AUTH_TYPES, AGENT_ROLES, SAFE_USER_ID_REGEX, SUPPORTED_LOCALES, uuidSchema, paginationSchema, timestampSchema, idParamSchema, clientOptionalString, clientOptionalBoolean, clientOptionalNumber, messageAttachment, MAX_MESSAGE_BODY_BYTES, MAX_ATTACHMENTS_PER_MESSAGE, primaryRegionSchema, placementSourceSchema, orgIdParam, orgAdminIdParam, createOrganizationBody, updateOrganizationBody, addOrgAdminBody, SUMMARIZATION_THRESHOLD_MIN, SUMMARIZATION_THRESHOLD_MAX, summarizationThresholdField, monitorConfigField, agentIdParam, listAgentsQuery, agentConnectionsQuery, createAgentBody, updateAgentBody, AGENT_PARAMETER_TYPES, AGENT_PARAMETER_NAME_REGEX, agentParameterCreateSchema, MAX_AGENT_PARAMETERS_PER_REQUEST, createAgentParametersBody, SLUG_REGEX, slugSchema, INVISIBLE_NAME_CHARS, MAX_KANBAN_STATUSES, MAX_FOLLOWUPS_PER_STATUS, MAX_LANES, MAX_ADDITIONAL_CONTEXT_SCHEMA_BYTES, RESERVED_TEMPLATE_DUMP_NAME, kanbanStatusSlugSchema, followupSchema, EXCLUSIVE_FLAG_PAIRS, kanbanStatusSchema, kanbanStatusesArraySchema, laneSchema, lanesArraySchema, hubIdParam, hubAdminIdParam, hubUserIdParam, hubIdentityIdParam, hubTeamIdParam, hubTeamUserDeleteParam, hubSchemaQuery, PREVIEW_LABEL_MAX_LENGTH, MAX_ENDED_INDEX_RETENTION_DAYS, previewLabelField, createHubBody, updateHubBody, addHubAdminBody, addHubIdentityAuthorizationBody, contactDecisionBody, approveContactBody, listContactAccessQuery, createHubTeamBody, updateHubTeamBody, addHubTeamUserBody, reassignHubUserBody, replicatePreviewBody, updateChannelTestIdentitiesBody, claimCodeChannelParam, claimCodeDeleteParam, connectionIdParam, connectorIdParam, listConnectionsQuery, deleteConnectionQuery, addConnectionBody, editConnectionBody, PRODUCTION_DIRECT_FIELDS, setProductionCredentialBody, registerWhatsappQuery, registerWhatsappBody, retryProvisioningQuery, resendDomainStatusQuery, PLACEHOLDER_TOKENS, ALLOWED_TYPES, SUBSCHEMA_OBJECT_KEYWORDS, SUBSCHEMA_LIST_KEYWORDS, SUBSCHEMA_MAP_KEYWORDS, MAX_SCHEMA_DEPTH, toolIdParam, listToolsQuery, toolAgentQuery, deleteToolQuery, toolConnectionsQuery, nativeToolIdSchema, addNativeToolBody, addMcpToolBody, createCustomToolBody, updateCustomToolBody, updateToolExecutionConfigBody, initialValueCreateSchema, initialValueUpdateSchema, stateIdParam, listStatesQuery, stateNameSchema, createStateBody, updateStateBody, reorderStatesBody, variantAiModeSchema, experimentStatusSchema, overlayUpdateSchema, experimentIdParam, variantIdParam, listExperimentsQuery, listVariantsQuery, listOverridesQuery, deleteOverrideQuery, createExperimentBody, updateExperimentBody, setExperimentStatusBody, createVariantBody, updateVariantBody, upsertOverrideBody, orgTagIdParam, listOrgTagsQuery, createOrgTagBody, updateOrgTagBody, orgCredentialIdParam, listOrgCredentialsQuery, createOrgCredentialBody, updateOrgCredentialBody, orgResourceIdParam, orgResourceFileIdParam, orgResourceFolderIdParam, hubOrgResourceParam, listOrgResourcesQuery, orgIdQuery, createOrgResourceBody, updateOrgResourceBody, createOrgResourceFolderBody, updateOrgResourceFolderBody, uploadOrgResourceFileBody, updateOrgResourceFileBody, uploadOrgSkillZipBody, linkOrgResourceBody, resourceIdParam, listResourcesQuery, createResourceBody, updateResourceBody, syncSkillsBody, resourceFileIdParam, listResourceFilesQuery, createResourceFileBody, updateResourceFileBody, uploadResourceFileBody, uploadSkillZipBody, resourceFolderIdParam, listResourceFoldersQuery, createResourceFolderBody, updateResourceFolderBody, agentResourceQuery, hubResourceQuery, linkAgentResourceBody, updateAgentResourceBody, unlinkAgentResourceQuery, navItemSchema, hubCountResetBody, navItemCountResetBody, typingBody, analyticsHubIdParam, analyticsVariableIdParam, updateVariablePinBody, updateAnalyticsViewBody, NUMERIC_FILTER_OPS, TEXT_FILTER_OPS, CATEGORICAL_FILTER_OPS, VARIABLE_FILTER_OPS, STRUCTURED_QUERY_OPS, STRUCTURED_QUERY_AGGREGATIONS, filterValueSchema, conversationsBody, analyticsConversationIdParam, messagesQuery, conversationDetailDataQuery, analyticsDataBody, structuredQueryBody, analyticsSqlBody, analyticsSqlSchemaQuery, EVAL_PACING_PRESET_NAMES, PACING_INTERVAL_MIN_MS, PACING_INTERVAL_MAX_MS, EVAL_RUN_DEADLINE_MIN_MS, EVAL_RUN_DEADLINE_MAX_MS, paginationSchema2, evalIdParam, sessionIdParam, scenarioSetIdParam, hubIdQuery, EVAL_INPUT_ROLES, EVAL_INPUT_ROLE_LIST, ROLE_ECHO_MAX, createEvalBody, updateEvalBody, getEvalsQuery, toggleEvalBody, bulkToggleEvalsBody, pacingConfigSchema, sessionConfigSchema, createSessionBody, getSessionsQuery, getSessionResultsQuery, getSessionRunsQuery, evalAnalyticsQuery, getSessionCostQuery, compareSessionsQuery, evalsSqlBody, evalsSqlSchemaQuery, exportResultsQuery, validateEvalBody, hubAgentsQuery, createScenarioSetBody, updateScenarioSetBody, scenarioSetsHubQuery, createScenarioFromConversationBody, createJourneyFromConversationBody, journeyIdParam, journeyToolCallSchema, journeyTurnSchema, journeyStepOverrideSchema, fixtureField, createJourneyBody, updateJourneyBody, getConversationsQuery, getMessagesQuery, appMessageSenderType, appMessageReceiverType, appMessageBody, updateUserBody, updateProfileBody, pathnameRegex, updatePreferencesBody, registerPushTokenBody, deletePushTokenQuery, activityQuery, deletionModeSchema, deletionRequestBody, archiveHubIdParam, archiveConversationParams, archiveListQuery, archiveMessageObservabilityParams, conversationIdParam, claimBody, releaseBody, transferBody, closeBody, additionalContextPayloadSchema, kanbanUpdateBody, annotateConversationBody, observabilityParams, observabilityListParams, deleteUserConversationsBody, sendMessageBody, listConversationsQuery, getConversationDetailParams, getConversationDetailQuery, listWhatsAppTemplatesQuery, sendWhatsAppTemplateBody, billingOrgIdParam, subscriptionItemParams, spendCapBody, growthPacksBody, portalSessionBody, checkoutSessionBody, updateBillingCurrencyBody, invoicesQuery, downloadBody, uploadQuery, signUrlBody, outboundSchemaQuery, templateIdParam, listTemplatesQuery, deleteTemplateQuery, templateStatusQuery, createTemplateBody, updateTemplateBody, submitTemplateBody, testTemplateBody, contactIdParam, listContactsQuery, createContactBody, updateContactBody, listIdParam, listListsQuery, listListContactsQuery, createListBody, updateListBody, addListContactsBody, removeListContactsBody, scheduleIdParam, listSchedulesQuery, listExecutionsQuery, createScheduleBody, updateScheduleBody, uuidPattern, ciUuidSchema, hubAsCodeConfigSchema, ciPullHubIdParam, ciBranchParam, ciPullQuery, ciHubsQuery, ciLookupQuery, ciBranchesQuery, ciSyncBody, ciPushBody, ciDiffBody, ciPublishBody, ciPublishPreviewBody, ciSyncMcpBody, ciOrgResourcesParam, orgResourcesConfigSchema, ciOrgResourcesPushBody, ciOrgResourcesDiffBody, adminOrgIdParam, adminOrgAdminIdParam, adminUserIdParam, adminPlanIdParam, adminHubIdParam, adminOrganizationsQuery, adminUserSearchQuery, kanbanSlugMigrationQuery, adjustQuotaBody, updatePlanBody, addAdminUserBody, updatePlatformConfigBody, updateFreeOrgLimitBody, adminSetOrgHarnessBody, adminSetHubHarnessBody, adminScopedTargetBody, updatePricingPlanBody, dataExplorerSearchQuery, dataExplorerOrgIdParam, dataExplorerHubIdParam, dataExplorerConvIdParam, dataExplorerDoInstancesQuery, dataExplorerNsIdParam, dataExplorerKvKeysQuery, dataExplorerKvKeyParam, uuidOrHexId, dataExplorerDeleteOrgParam, dataExplorerDeleteHubParam, dataExplorerDeleteConvParam, dataExplorerDeleteUserParam, dataExplorerDeleteQuery, dataExplorerKvDeleteQuery, pitrDoNamespace, pitrBookmarkParam, pitrRestoreBody, healthSamplingTriggerBody, dataExplorerDebugDoType, DEBUG_READ_MAX_LIMIT, DEBUG_READ_DEFAULT_LIMIT, DEBUG_DO_HEX_PREFIX, debugDoEntityId, debugTableName, dataExplorerDebugTablesParam, dataExplorerDebugRowsParam, dataExplorerDebugRowsQuery, dataExplorerDebugAnalyticsTable, debugUuid, dataExplorerDebugAnalyticsRowsParam, dataExplorerDebugAnalyticsRowsQuery, debugHubConvParam, debugMessageIdQuery, sandboxEgressPolicy, SANDBOX_EXEC_MAX_CMD_LENGTH, SANDBOX_EXEC_MAX_ALLOWLIST, SANDBOX_EXEC_MAX_TIMEOUT_MS, adminSandboxExecBody, whatsappCallbackBody, authMeResponse, wsTicketResponse, sessionCheckResponse, logoutResponse, magicCodeSendBody, magicCodeSendResponse, magicCodeVerifyBody, magicCodeVerifyResponse, browserParams, browserFoldersQuery, browserFilesQuery, stateOperationBody, conversationListResourcesQuery, conversationReadResourceQuery, listPendingSchedulesQuery, listHubAggregateSchedulesQuery, reportSourceSchema, intakeReportSourceSchema, reportClassificationSchema, reportStatusSchema, reportLocaleSchema, reportMessageAuthorRoleSchema, sentryRefStatusSchema, createReportBody, editReportBody, contestReportBody, reporterListQuery, listReportsQuery, GITHUB_ISSUE_URL_REGEX, githubIssueUrlSchema, transitionReportBody, groupReportsBody, holdReportBody, clickhouseReadyResponse, bootstrapQuery, requestSnapshotChunkMessage, MAX_NAME_LENGTH, MAX_VALUE_LENGTH, MAX_TAGS, MAX_TAG_LENGTH, MAX_SCOPE_HUBS, MAX_SCOPE_TAGS, vaultSecretScopeSchema, vaultCreateBody, vaultUpdateBody, PERMISSIONS, MAX_NAME_LENGTH2, MAX_HUBS_PER_GRANT, MAX_HUB_TAGS_PER_GRANT, MAX_GRANTS, permissionEnum, grantScopeSchema, grantSchema, createTokenBody, tokenOrgIdParam, tokenGrantScopeSchema, tokenGrantSchema, tokenMetadataSchema, listTokensResponse, createTokenResponse, orgTokenSummarySchema, listOrgTokensResponse, invitePreviewQuery, invitePreviewResponse, discordTier, discordBillingCycle, discordMetadata, discordOAuthStartResponse, discordOAuthFinalizeBody, discordOAuthFinalizeResponse, discordDisconnectResponse, jsonSchemaSchema, hubUserIdentity, stateValueEntry, getHubUserContextResponse, updateHubUserBody, updateHubUserResponse, unlockHubUserNameResponse, stateResetParams, stateResetResponse, hubAlertsParam, hubAlertSeverity, hubAlert, hubAlertsListResponse, noticeSeveritySchema, noticeStatusSchema, NOTICE_SCOPE_REGEX, noticeScopeSchema, noticeLinkSchema, createNoticeBody, updateNoticeBody, listNoticesQuery, noticeIdParamSchema, templateLocaleSchema, TEMPLATE_SLUG_REGEX, templateSlugSchema, templateStatusSchema, templateFileMapSchema, templateTextSchema, templateUsesEntrySchema, templateUsesSchema, templateYamlLocaleEntry, templateYamlWayaiBlock, templateTagsSchema, templateYamlSchema, templateHeroSchema, templatePushLocaleSchema, templatePushBody, templateListQuery, templateSlugParam, templateDetailQuery, ADMIN_SKILL_NAME_REGEX, adminSkillNameParam, WAYAI_WORKSPACE_LAYOUT, MAX_RESOURCE_FILE_SIZE;
5908
+ var UUID_RE, HEX_RE, WORKOS_ID_RE, OPAQUE_SECRET_RE, BASE64_SECRET_RE, BASE64_MARKER_RE, TOKEN_RE, SENSITIVE_FIELDS, SENSITIVE_KEY_ALTERNATION, SENSITIVE_KEY_PATTERN, JSON_CREDENTIAL_RE, QUERY_CREDENTIAL_RE, REDACTED, PROVIDER_TOKEN_RE, APP_ERROR_BRAND, AppError, LLM_RATE_LIMIT_BRAND, LlmRateLimitError, LLM_PROVIDER_HTTP_BRAND, LlmProviderHttpError, LLM_PROVIDER_CONTENT_BRAND, LlmProviderContentError, LLM_PROVIDER_TIMEOUT_BRAND, LlmProviderTimeoutError, ExternalServiceError, CHANNEL_PROVIDER_HTTP_BRAND, ChannelProviderHttpError, MEDIA_PROVIDER_HTTP_BRAND, MediaProviderHttpError, AUTH_TYPE, ORG_CREDENTIAL_AUTH_TYPES, AUTH_TYPE_DISPLAY, LEGACY_AUTH_TYPE_MAP, VALID_AUTH_TYPES, AGENT_ROLES, SAFE_USER_ID_REGEX, SUPPORTED_LOCALES, uuidSchema, paginationSchema, timestampSchema, idParamSchema, clientOptionalString, clientOptionalBoolean, clientOptionalNumber, messageAttachment, MAX_MESSAGE_BODY_BYTES, MAX_ATTACHMENTS_PER_MESSAGE, primaryRegionSchema, placementSourceSchema, orgIdParam, orgAdminIdParam, createOrganizationBody, updateOrganizationBody, addOrgAdminBody, SUMMARIZATION_THRESHOLD_MIN, SUMMARIZATION_THRESHOLD_MAX, summarizationThresholdField, monitorConfigField, agentIdParam, listAgentsQuery, agentConnectionsQuery, createAgentBody, updateAgentBody, AGENT_PARAMETER_TYPES, AGENT_PARAMETER_NAME_REGEX, agentParameterCreateSchema, MAX_AGENT_PARAMETERS_PER_REQUEST, createAgentParametersBody, SLUG_REGEX, slugSchema, INVISIBLE_NAME_CHARS, MAX_KANBAN_STATUSES, MAX_FOLLOWUPS_PER_STATUS, MAX_LANES, MAX_ADDITIONAL_CONTEXT_SCHEMA_BYTES, RESERVED_TEMPLATE_DUMP_NAME, kanbanStatusSlugSchema, followupSchema, EXCLUSIVE_FLAG_PAIRS, kanbanStatusSchema, kanbanStatusesArraySchema, laneSchema, lanesArraySchema, hubIdParam, hubAdminIdParam, hubUserIdParam, hubIdentityIdParam, hubTeamIdParam, hubTeamUserDeleteParam, hubSchemaQuery, PREVIEW_LABEL_MAX_LENGTH, MAX_ENDED_INDEX_RETENTION_DAYS, previewLabelField, createHubBody, updateHubBody, addHubAdminBody, addHubIdentityAuthorizationBody, contactDecisionBody, approveContactBody, listContactAccessQuery, createHubTeamBody, updateHubTeamBody, addHubTeamUserBody, reassignHubUserBody, replicatePreviewBody, updateChannelTestIdentitiesBody, claimCodeChannelParam, claimCodeDeleteParam, connectionIdParam, connectorIdParam, listConnectionsQuery, deleteConnectionQuery, addConnectionBody, editConnectionBody, PRODUCTION_DIRECT_FIELDS, setProductionCredentialBody, registerWhatsappQuery, registerWhatsappBody, retryProvisioningQuery, resendDomainStatusQuery, PLACEHOLDER_TOKENS, ALLOWED_TYPES, SUBSCHEMA_OBJECT_KEYWORDS, SUBSCHEMA_LIST_KEYWORDS, SUBSCHEMA_MAP_KEYWORDS, MAX_SCHEMA_DEPTH, toolIdParam, listToolsQuery, toolAgentQuery, deleteToolQuery, toolConnectionsQuery, nativeToolIdSchema, addNativeToolBody, addMcpToolBody, createCustomToolBody, updateCustomToolBody, updateToolExecutionConfigBody, initialValueCreateSchema, initialValueUpdateSchema, stateIdParam, listStatesQuery, stateNameSchema, createStateBody, updateStateBody, reorderStatesBody, variantAiModeSchema, experimentStatusSchema, overlayUpdateSchema, experimentIdParam, variantIdParam, listExperimentsQuery, listVariantsQuery, listOverridesQuery, deleteOverrideQuery, createExperimentBody, updateExperimentBody, setExperimentStatusBody, createVariantBody, updateVariantBody, upsertOverrideBody, orgTagIdParam, listOrgTagsQuery, createOrgTagBody, updateOrgTagBody, orgCredentialIdParam, listOrgCredentialsQuery, createOrgCredentialBody, updateOrgCredentialBody, orgResourceIdParam, orgResourceFileIdParam, orgResourceFolderIdParam, hubOrgResourceParam, listOrgResourcesQuery, orgIdQuery, createOrgResourceBody, updateOrgResourceBody, createOrgResourceFolderBody, updateOrgResourceFolderBody, uploadOrgResourceFileBody, updateOrgResourceFileBody, uploadOrgSkillZipBody, linkOrgResourceBody, resourceIdParam, listResourcesQuery, createResourceBody, updateResourceBody, syncSkillsBody, resourceFileIdParam, listResourceFilesQuery, createResourceFileBody, updateResourceFileBody, uploadResourceFileBody, uploadSkillZipBody, resourceFolderIdParam, listResourceFoldersQuery, createResourceFolderBody, updateResourceFolderBody, agentResourceQuery, hubResourceQuery, linkAgentResourceBody, updateAgentResourceBody, unlinkAgentResourceQuery, navItemSchema, hubCountResetBody, navItemCountResetBody, typingBody, analyticsHubIdParam, analyticsVariableIdParam, updateVariablePinBody, updateAnalyticsViewBody, NUMERIC_FILTER_OPS, TEXT_FILTER_OPS, CATEGORICAL_FILTER_OPS, VARIABLE_FILTER_OPS, STRUCTURED_QUERY_OPS, STRUCTURED_QUERY_AGGREGATIONS, filterValueSchema, conversationsBody, analyticsConversationIdParam, messagesQuery, conversationDetailDataQuery, analyticsDataBody, structuredQueryBody, analyticsSqlBody, analyticsSqlSchemaQuery, EVAL_PACING_PRESET_NAMES, PACING_INTERVAL_MIN_MS, PACING_INTERVAL_MAX_MS, EVAL_RUN_DEADLINE_MIN_MS, EVAL_RUN_DEADLINE_MAX_MS, paginationSchema2, evalIdParam, sessionIdParam, scenarioSetIdParam, hubIdQuery, EVAL_INPUT_ROLES, EVAL_INPUT_ROLE_LIST, ROLE_ECHO_MAX, createEvalBody, updateEvalBody, getEvalsQuery, toggleEvalBody, bulkToggleEvalsBody, pacingConfigSchema, sessionConfigSchema, createSessionBody, getSessionsQuery, getSessionResultsQuery, getSessionRunsQuery, evalAnalyticsQuery, getSessionCostQuery, compareSessionsQuery, evalsSqlBody, evalsSqlSchemaQuery, exportResultsQuery, validateEvalBody, hubAgentsQuery, createScenarioSetBody, updateScenarioSetBody, scenarioSetsHubQuery, createScenarioFromConversationBody, createJourneyFromConversationBody, EVAL_ATTACHMENT_HASH_REGEX, evalAttachmentRef, turnMayCarryAttachments, ATTACHMENTS_USER_ONLY_MESSAGE, journeyIdParam, journeyToolCallSchema, journeyTurnSchema, journeyStepOverrideSchema, fixtureField, createJourneyBody, updateJourneyBody, getConversationsQuery, getMessagesQuery, appMessageSenderType, appMessageReceiverType, appMessageBody, updateUserBody, updateProfileBody, pathnameRegex, updatePreferencesBody, registerPushTokenBody, deletePushTokenQuery, activityQuery, deletionModeSchema, deletionRequestBody, archiveHubIdParam, archiveConversationParams, archiveListQuery, archiveMessageObservabilityParams, conversationIdParam, claimBody, releaseBody, transferBody, closeBody, additionalContextPayloadSchema, kanbanUpdateBody, annotateConversationBody, observabilityParams, observabilityListParams, deleteUserConversationsBody, sendMessageBody, listConversationsQuery, getConversationDetailParams, getConversationDetailQuery, listWhatsAppTemplatesQuery, sendWhatsAppTemplateBody, billingOrgIdParam, subscriptionItemParams, spendCapBody, growthPacksBody, portalSessionBody, checkoutSessionBody, updateBillingCurrencyBody, invoicesQuery, downloadBody, uploadQuery, signUrlBody, outboundSchemaQuery, templateIdParam, listTemplatesQuery, deleteTemplateQuery, templateStatusQuery, createTemplateBody, updateTemplateBody, submitTemplateBody, testTemplateBody, contactIdParam, listContactsQuery, createContactBody, updateContactBody, listIdParam, listListsQuery, listListContactsQuery, createListBody, updateListBody, addListContactsBody, removeListContactsBody, scheduleIdParam, listSchedulesQuery, listExecutionsQuery, createScheduleBody, updateScheduleBody, MAX_RESOURCE_FILE_SIZE, MAX_EVAL_ATTACHMENT_ENCODED_BYTES, MAX_EVAL_ATTACHMENT_CARRIERS, MAX_EVAL_ATTACHMENT_AGGREGATE_ENCODED_BYTES, uuidPattern, ciUuidSchema, hubAsCodeConfigSchema, ciPullHubIdParam, ciBranchParam, ciPullQuery, ciHubsQuery, ciLookupQuery, ciBranchesQuery, ciSyncBody, ciPushBody, ciDiffBody, ciPublishBody, ciPublishPreviewBody, ciSyncMcpBody, ciOrgResourcesParam, orgResourcesConfigSchema, ciOrgResourcesPushBody, ciOrgResourcesDiffBody, adminOrgIdParam, adminOrgAdminIdParam, adminUserIdParam, adminPlanIdParam, adminHubIdParam, adminOrganizationsQuery, adminUserSearchQuery, kanbanSlugMigrationQuery, adjustQuotaBody, updatePlanBody, addAdminUserBody, updatePlatformConfigBody, updateFreeOrgLimitBody, adminSetOrgHarnessBody, adminSetHubHarnessBody, adminScopedTargetBody, updatePricingPlanBody, dataExplorerSearchQuery, dataExplorerOrgIdParam, dataExplorerHubIdParam, dataExplorerConvIdParam, dataExplorerDoInstancesQuery, dataExplorerNsIdParam, dataExplorerKvKeysQuery, dataExplorerKvKeyParam, uuidOrHexId, dataExplorerDeleteOrgParam, dataExplorerDeleteHubParam, dataExplorerDeleteConvParam, dataExplorerDeleteUserParam, dataExplorerDeleteQuery, dataExplorerKvDeleteQuery, pitrDoNamespace, pitrBookmarkParam, pitrRestoreBody, healthSamplingTriggerBody, dataExplorerDebugDoType, DEBUG_READ_MAX_LIMIT, DEBUG_READ_DEFAULT_LIMIT, DEBUG_DO_HEX_PREFIX, debugDoEntityId, debugTableName, dataExplorerDebugTablesParam, dataExplorerDebugRowsParam, dataExplorerDebugRowsQuery, dataExplorerDebugAnalyticsTable, debugUuid, dataExplorerDebugAnalyticsRowsParam, dataExplorerDebugAnalyticsRowsQuery, debugHubConvParam, debugMessageIdQuery, sandboxEgressPolicy, SANDBOX_EXEC_MAX_CMD_LENGTH, SANDBOX_EXEC_MAX_ALLOWLIST, SANDBOX_EXEC_MAX_TIMEOUT_MS, adminSandboxExecBody, whatsappCallbackBody, authMeResponse, wsTicketResponse, sessionCheckResponse, logoutResponse, magicCodeSendBody, magicCodeSendResponse, magicCodeVerifyBody, magicCodeVerifyResponse, browserParams, browserFoldersQuery, browserFilesQuery, stateOperationBody, conversationListResourcesQuery, conversationReadResourceQuery, listPendingSchedulesQuery, listHubAggregateSchedulesQuery, reportSourceSchema, intakeReportSourceSchema, reportClassificationSchema, reportStatusSchema, reportLocaleSchema, reportMessageAuthorRoleSchema, sentryRefStatusSchema, createReportBody, editReportBody, contestReportBody, reporterListQuery, listReportsQuery, GITHUB_ISSUE_URL_REGEX, githubIssueUrlSchema, transitionReportBody, groupReportsBody, holdReportBody, clickhouseReadyResponse, bootstrapQuery, requestSnapshotChunkMessage, MAX_NAME_LENGTH, MAX_VALUE_LENGTH, MAX_TAGS, MAX_TAG_LENGTH, MAX_SCOPE_HUBS, MAX_SCOPE_TAGS, vaultSecretScopeSchema, vaultCreateBody, vaultUpdateBody, PERMISSIONS, MAX_NAME_LENGTH2, MAX_HUBS_PER_GRANT, MAX_HUB_TAGS_PER_GRANT, MAX_GRANTS, permissionEnum, grantScopeSchema, grantSchema, createTokenBody, tokenOrgIdParam, tokenGrantScopeSchema, tokenGrantSchema, tokenMetadataSchema, listTokensResponse, createTokenResponse, orgTokenSummarySchema, listOrgTokensResponse, invitePreviewQuery, invitePreviewResponse, discordTier, discordBillingCycle, discordMetadata, discordOAuthStartResponse, discordOAuthFinalizeBody, discordOAuthFinalizeResponse, discordDisconnectResponse, jsonSchemaSchema, hubUserIdentity, stateValueEntry, getHubUserContextResponse, updateHubUserBody, updateHubUserResponse, unlockHubUserNameResponse, stateResetParams, stateResetResponse, hubAlertsParam, hubAlertSeverity, hubAlert, hubAlertsListResponse, noticeSeveritySchema, noticeStatusSchema, NOTICE_SCOPE_REGEX, noticeScopeSchema, noticeLinkSchema, createNoticeBody, updateNoticeBody, listNoticesQuery, noticeIdParamSchema, templateLocaleSchema, TEMPLATE_SLUG_REGEX, templateSlugSchema, templateStatusSchema, templateFileMapSchema, templateTextSchema, templateUsesEntrySchema, templateUsesSchema, templateYamlLocaleEntry, templateYamlWayaiBlock, templateTagsSchema, templateYamlSchema, templateHeroSchema, templatePushLocaleSchema, templatePushBody, templateListQuery, templateSlugParam, templateDetailQuery, ADMIN_SKILL_NAME_REGEX, adminSkillNameParam, WAYAI_WORKSPACE_LAYOUT;
5775
5909
  var init_dist = __esm({
5776
5910
  "../../packages/core/dist/index.js"() {
5777
5911
  "use strict";
@@ -7100,10 +7234,10 @@ var init_dist = __esm({
7100
7234
  ROLE_ECHO_MAX = 40;
7101
7235
  createEvalBody = external_exports.object({
7102
7236
  eval: external_exports.record(external_exports.unknown())
7103
- }).superRefine(refineEvalMessageTextRole);
7237
+ }).superRefine(refineEvalMessageTextRole).superRefine(refineEvalAttachments);
7104
7238
  updateEvalBody = external_exports.object({
7105
7239
  eval: external_exports.record(external_exports.unknown())
7106
- }).superRefine(refineEvalMessageTextRole);
7240
+ }).superRefine(refineEvalMessageTextRole).superRefine(refineEvalAttachments);
7107
7241
  getEvalsQuery = external_exports.object({
7108
7242
  hub_id: external_exports.string().uuid(),
7109
7243
  agent_id: external_exports.string().uuid().optional(),
@@ -7230,6 +7364,15 @@ var init_dist = __esm({
7230
7364
  journey_name: external_exports.string().min(1).max(200),
7231
7365
  evaluator_instructions: external_exports.string().max(4e3).optional()
7232
7366
  });
7367
+ EVAL_ATTACHMENT_HASH_REGEX = /^[a-f0-9]{64}$/;
7368
+ evalAttachmentRef = external_exports.object({
7369
+ hash: external_exports.string().regex(EVAL_ATTACHMENT_HASH_REGEX),
7370
+ file_name: external_exports.string().min(1).max(255),
7371
+ mime_type: external_exports.string().min(1).max(255),
7372
+ file_size: external_exports.number().int().positive().optional()
7373
+ });
7374
+ turnMayCarryAttachments = (turn) => !turn.attachments || turn.attachments.length === 0 || turn.role === "user";
7375
+ ATTACHMENTS_USER_ONLY_MESSAGE = "attachments are only valid on user turns";
7233
7376
  journeyIdParam = external_exports.object({ id: external_exports.string().uuid() });
7234
7377
  journeyToolCallSchema = external_exports.object({
7235
7378
  id: external_exports.string(),
@@ -7241,7 +7384,10 @@ var init_dist = __esm({
7241
7384
  content: external_exports.string().nullable().optional(),
7242
7385
  tool_calls: external_exports.array(journeyToolCallSchema).optional(),
7243
7386
  tool_call_id: external_exports.string().optional(),
7244
- step_id: external_exports.string().optional()
7387
+ step_id: external_exports.string().optional(),
7388
+ attachments: external_exports.array(evalAttachmentRef).max(MAX_ATTACHMENTS_PER_MESSAGE).optional()
7389
+ }).refine(turnMayCarryAttachments, {
7390
+ message: ATTACHMENTS_USER_ONLY_MESSAGE
7245
7391
  });
7246
7392
  journeyStepOverrideSchema = external_exports.object({
7247
7393
  number_of_runs: external_exports.number().int().positive().max(100).optional(),
@@ -7746,6 +7892,10 @@ var init_dist = __esm({
7746
7892
  agent_id: external_exports.string().uuid().nullable().optional(),
7747
7893
  trigger_message: external_exports.string().nullable().optional()
7748
7894
  });
7895
+ MAX_RESOURCE_FILE_SIZE = 10 * 1024 * 1024;
7896
+ MAX_EVAL_ATTACHMENT_ENCODED_BYTES = Math.ceil(MAX_RESOURCE_FILE_SIZE / 3) * 4 + 16;
7897
+ MAX_EVAL_ATTACHMENT_CARRIERS = 1e3;
7898
+ MAX_EVAL_ATTACHMENT_AGGREGATE_ENCODED_BYTES = 16 * 1024 * 1024;
7749
7899
  uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
7750
7900
  ciUuidSchema = external_exports.string().regex(uuidPattern, "must be a valid UUID");
7751
7901
  hubAsCodeConfigSchema = external_exports.object({
@@ -7758,6 +7908,7 @@ var init_dist = __esm({
7758
7908
  refineHubAsCodeKanbanStatuses(config, ctx);
7759
7909
  refineHubAsCodeLanes(config, ctx);
7760
7910
  refineHubAsCodeEvals(config, ctx);
7911
+ refineHubAsCodeEvalAttachments(config, ctx);
7761
7912
  });
7762
7913
  ciPullHubIdParam = external_exports.object({
7763
7914
  hub_id: ciUuidSchema
@@ -8589,7 +8740,6 @@ var init_dist = __esm({
8589
8740
  /** Subdir of `wsDir` holding org-as-code. */
8590
8741
  orgSubdir: "org"
8591
8742
  };
8592
- MAX_RESOURCE_FILE_SIZE = 10 * 1024 * 1024;
8593
8743
  }
8594
8744
  });
8595
8745
 
@@ -9145,24 +9295,24 @@ import * as path4 from "path";
9145
9295
  import * as readline from "readline";
9146
9296
  function prompt(question) {
9147
9297
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
9148
- return new Promise((resolve4) => {
9298
+ return new Promise((resolve6) => {
9149
9299
  rl.question(question, (answer) => {
9150
9300
  rl.close();
9151
- resolve4(answer.trim());
9301
+ resolve6(answer.trim());
9152
9302
  });
9153
9303
  });
9154
9304
  }
9155
9305
  function confirm(question) {
9156
9306
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
9157
- return new Promise((resolve4) => {
9307
+ return new Promise((resolve6) => {
9158
9308
  rl.question(`${question} [y/N]: `, (answer) => {
9159
9309
  rl.close();
9160
- resolve4(answer.trim().toLowerCase() === "y");
9310
+ resolve6(answer.trim().toLowerCase() === "y");
9161
9311
  });
9162
9312
  });
9163
9313
  }
9164
9314
  function promptSecret(question) {
9165
- return new Promise((resolve4) => {
9315
+ return new Promise((resolve6) => {
9166
9316
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
9167
9317
  const originalWrite = rl._writeToOutput;
9168
9318
  let firstWrite = true;
@@ -9178,18 +9328,18 @@ function promptSecret(question) {
9178
9328
  rl._writeToOutput = originalWrite;
9179
9329
  process.stdout.write("\n");
9180
9330
  rl.close();
9181
- resolve4(answer);
9331
+ resolve6(answer);
9182
9332
  });
9183
9333
  });
9184
9334
  }
9185
9335
  function readStdin() {
9186
- return new Promise((resolve4, reject) => {
9336
+ return new Promise((resolve6, reject) => {
9187
9337
  let data = "";
9188
9338
  process.stdin.setEncoding("utf-8");
9189
9339
  process.stdin.on("data", (chunk) => {
9190
9340
  data += chunk;
9191
9341
  });
9192
- process.stdin.on("end", () => resolve4(data.trim()));
9342
+ process.stdin.on("end", () => resolve6(data.trim()));
9193
9343
  process.stdin.on("error", reject);
9194
9344
  });
9195
9345
  }
@@ -9351,9 +9501,9 @@ function getVersionCachePath(filename = CLI_CACHE_FILE) {
9351
9501
  }
9352
9502
  function readVersionCache(filename = CLI_CACHE_FILE) {
9353
9503
  try {
9354
- const path28 = getVersionCachePath(filename);
9355
- if (!existsSync4(path28)) return null;
9356
- const parsed = JSON.parse(readFileSync5(path28, "utf-8"));
9504
+ const path30 = getVersionCachePath(filename);
9505
+ if (!existsSync4(path30)) return null;
9506
+ const parsed = JSON.parse(readFileSync5(path30, "utf-8"));
9357
9507
  if (typeof parsed.lastCheck !== "number") return null;
9358
9508
  if (parsed.latest !== null && typeof parsed.latest !== "string") return null;
9359
9509
  return parsed;
@@ -9373,10 +9523,10 @@ function isVersionCacheStale(filename = CLI_CACHE_FILE, maxAgeMs = MAX_AGE_24H_M
9373
9523
  return Date.now() - cache.lastCheck > maxAgeMs;
9374
9524
  }
9375
9525
  function writeVersionCache(filename, cache) {
9376
- const path28 = getVersionCachePath(filename);
9377
- const dir = dirname3(path28);
9526
+ const path30 = getVersionCachePath(filename);
9527
+ const dir = dirname3(path30);
9378
9528
  if (!existsSync4(dir)) mkdirSync(dir, { recursive: true });
9379
- writeFileSync2(path28, JSON.stringify(cache));
9529
+ writeFileSync2(path30, JSON.stringify(cache));
9380
9530
  }
9381
9531
  function touchVersionCache(filename) {
9382
9532
  writeVersionCache(filename, { lastCheck: Date.now(), latest: readVersionCache(filename)?.latest ?? null });
@@ -9415,14 +9565,14 @@ function parseFrontmatterVersion(content) {
9415
9565
  function findInstalledSkills(projectRoot, paths = SKILL_INSTALL_PATHS) {
9416
9566
  const found = [];
9417
9567
  for (const rel of paths) {
9418
- const path28 = join7(projectRoot, rel);
9419
- if (!existsSync5(path28)) continue;
9568
+ const path30 = join7(projectRoot, rel);
9569
+ if (!existsSync5(path30)) continue;
9420
9570
  let version = null;
9421
9571
  try {
9422
- version = parseFrontmatterVersion(readFileSync6(path28, "utf-8"));
9572
+ version = parseFrontmatterVersion(readFileSync6(path30, "utf-8"));
9423
9573
  } catch {
9424
9574
  }
9425
- found.push({ path: path28, version });
9575
+ found.push({ path: path30, version });
9426
9576
  }
9427
9577
  return found;
9428
9578
  }
@@ -9883,7 +10033,7 @@ async function validateToken(apiUrl, token) {
9883
10033
  }
9884
10034
  }
9885
10035
  function startCallbackServer(port, expectedState, timeoutMs = 12e4) {
9886
- return new Promise((resolve4, reject) => {
10036
+ return new Promise((resolve6, reject) => {
9887
10037
  const server = http.createServer((req, res) => {
9888
10038
  const url = new URL(req.url || "/", `http://127.0.0.1:${port}`);
9889
10039
  if (url.pathname === "/callback") {
@@ -9909,7 +10059,7 @@ function startCallbackServer(port, expectedState, timeoutMs = 12e4) {
9909
10059
  res.writeHead(200, { "Content-Type": "text/html" });
9910
10060
  res.end("<html><body><h2>Login successful!</h2><p>You can close this tab and return to your terminal.</p></body></html>");
9911
10061
  server.close();
9912
- resolve4({ code, port });
10062
+ resolve6({ code, port });
9913
10063
  } else {
9914
10064
  res.writeHead(400, { "Content-Type": "text/html" });
9915
10065
  res.end("<html><body><h2>Login failed</h2><p>No authorization code received</p></body></html>");
@@ -10119,10 +10269,10 @@ import * as readline2 from "readline";
10119
10269
  function prompt2(question, defaultValue) {
10120
10270
  const rl = readline2.createInterface({ input: process.stdin, output: process.stdout });
10121
10271
  const display = defaultValue ? `${question} [${defaultValue}]: ` : `${question}: `;
10122
- return new Promise((resolve4) => {
10272
+ return new Promise((resolve6) => {
10123
10273
  rl.question(display, (answer) => {
10124
10274
  rl.close();
10125
- resolve4(answer.trim() || defaultValue || "");
10275
+ resolve6(answer.trim() || defaultValue || "");
10126
10276
  });
10127
10277
  });
10128
10278
  }
@@ -10240,8 +10390,8 @@ async function tryOpenBrowser(url) {
10240
10390
  cmd = "xdg-open";
10241
10391
  args2 = [url];
10242
10392
  }
10243
- return new Promise((resolve4) => {
10244
- execFile(cmd, args2, (err) => resolve4(!err));
10393
+ return new Promise((resolve6) => {
10394
+ execFile(cmd, args2, (err) => resolve6(!err));
10245
10395
  });
10246
10396
  } catch {
10247
10397
  return false;
@@ -10881,9 +11031,86 @@ var init_init = __esm({
10881
11031
  }
10882
11032
  });
10883
11033
 
10884
- // src/lib/resource-files.ts
11034
+ // src/lib/fs-safety.ts
10885
11035
  import * as fs9 from "fs";
10886
11036
  import * as path11 from "path";
11037
+ function realpathContained(root, abs) {
11038
+ let realRoot;
11039
+ let realAbs;
11040
+ try {
11041
+ realRoot = fs9.realpathSync(root);
11042
+ realAbs = fs9.realpathSync(abs);
11043
+ } catch {
11044
+ return false;
11045
+ }
11046
+ return realAbs === realRoot || realAbs.startsWith(realRoot + path11.sep);
11047
+ }
11048
+ function mkdirTolerateRace(dir) {
11049
+ try {
11050
+ fs9.mkdirSync(dir);
11051
+ return true;
11052
+ } catch (err) {
11053
+ if (err.code !== "EEXIST") throw err;
11054
+ let st;
11055
+ try {
11056
+ st = fs9.lstatSync(dir);
11057
+ } catch {
11058
+ st = void 0;
11059
+ }
11060
+ return !!st?.isDirectory();
11061
+ }
11062
+ }
11063
+ function ensureRealSubdirNoSymlink(root, target, create) {
11064
+ const rootResolved = path11.resolve(root);
11065
+ const targetResolved = path11.resolve(target);
11066
+ if (targetResolved !== rootResolved && !targetResolved.startsWith(rootResolved + path11.sep)) return false;
11067
+ try {
11068
+ if (!fs9.lstatSync(rootResolved).isDirectory()) return false;
11069
+ } catch {
11070
+ return false;
11071
+ }
11072
+ const rel = path11.relative(rootResolved, targetResolved);
11073
+ if (rel === "") return true;
11074
+ let cur = rootResolved;
11075
+ for (const segment of rel.split(path11.sep)) {
11076
+ cur = path11.join(cur, segment);
11077
+ let st;
11078
+ try {
11079
+ st = fs9.lstatSync(cur);
11080
+ } catch {
11081
+ st = void 0;
11082
+ }
11083
+ if (st) {
11084
+ if (!st.isDirectory()) return false;
11085
+ } else if (create) {
11086
+ if (!mkdirTolerateRace(cur)) return false;
11087
+ } else {
11088
+ return true;
11089
+ }
11090
+ }
11091
+ return true;
11092
+ }
11093
+ function writeFileNoFollow(root, abs, data) {
11094
+ const parent = path11.dirname(abs);
11095
+ if (!ensureRealSubdirNoSymlink(root, parent, true)) return false;
11096
+ let st;
11097
+ try {
11098
+ st = fs9.lstatSync(abs);
11099
+ } catch {
11100
+ }
11101
+ if (st?.isSymbolicLink()) fs9.rmSync(abs);
11102
+ fs9.writeFileSync(abs, data);
11103
+ return true;
11104
+ }
11105
+ var init_fs_safety = __esm({
11106
+ "src/lib/fs-safety.ts"() {
11107
+ "use strict";
11108
+ }
11109
+ });
11110
+
11111
+ // src/lib/resource-files.ts
11112
+ import * as fs10 from "fs";
11113
+ import * as path12 from "path";
10887
11114
  import * as crypto3 from "crypto";
10888
11115
  function isBinaryFile(filename) {
10889
11116
  const ext = filename.split(".").pop()?.toLowerCase() || "";
@@ -10929,14 +11156,14 @@ function computeHash(data) {
10929
11156
  }
10930
11157
  function scanResourceFiles(dir, prefix = "") {
10931
11158
  const files = [];
10932
- const entries = fs9.readdirSync(dir, { withFileTypes: true });
11159
+ const entries = fs10.readdirSync(dir, { withFileTypes: true });
10933
11160
  for (const entry of entries) {
10934
11161
  const relPath = prefix ? `${prefix}/${entry.name}` : entry.name;
10935
11162
  if (entry.isDirectory()) {
10936
- files.push(...scanResourceFiles(path11.join(dir, entry.name), relPath));
11163
+ files.push(...scanResourceFiles(path12.join(dir, entry.name), relPath));
10937
11164
  } else if (entry.isFile()) {
10938
- const fullPath = path11.join(dir, entry.name);
10939
- const stat = fs9.statSync(fullPath);
11165
+ const fullPath = path12.join(dir, entry.name);
11166
+ const stat = fs10.statSync(fullPath);
10940
11167
  if (stat.size > MAX_RESOURCE_FILE_SIZE2) {
10941
11168
  console.warn(` Warning: skipping ${relPath} (${(stat.size / 1024 / 1024).toFixed(1)}MB exceeds 10MB limit)`);
10942
11169
  continue;
@@ -10946,7 +11173,7 @@ function scanResourceFiles(dir, prefix = "") {
10946
11173
  mime_type: guessMimeType(entry.name),
10947
11174
  file_size: stat.size
10948
11175
  };
10949
- const data = fs9.readFileSync(fullPath);
11176
+ const data = fs10.readFileSync(fullPath);
10950
11177
  fileEntry.hash = computeHash(data);
10951
11178
  if (isBinaryFile(entry.name)) {
10952
11179
  fileEntry.content_base64 = data.toString("base64");
@@ -10958,49 +11185,61 @@ function scanResourceFiles(dir, prefix = "") {
10958
11185
  }
10959
11186
  return files;
10960
11187
  }
10961
- function writeResourceFileTree(resDir, files) {
11188
+ function writeResourceFileTree(resDir, files, root) {
11189
+ if (!ensureRealSubdirNoSymlink(root, resDir, files.length > 0)) {
11190
+ console.warn(` Warning: skipping resource dir (not reachable inside the workspace without a symlink)`);
11191
+ return;
11192
+ }
10962
11193
  if (files.length === 0) {
10963
- if (fs9.existsSync(resDir)) cleanOrphanFiles(resDir, "", /* @__PURE__ */ new Set());
11194
+ if (fs10.existsSync(resDir)) cleanOrphanFiles(resDir, "", /* @__PURE__ */ new Set());
10964
11195
  return;
10965
11196
  }
10966
11197
  const writtenPaths = /* @__PURE__ */ new Set();
10967
11198
  for (const file of files) {
10968
- const filePath = path11.join(resDir, file.path);
10969
- if (!filePath.startsWith(resDir + path11.sep) && filePath !== resDir) {
11199
+ const filePath = path12.join(resDir, file.path);
11200
+ if (!filePath.startsWith(resDir + path12.sep) && filePath !== resDir) {
10970
11201
  console.warn(` Warning: skipping ${file.path} (path traversal detected)`);
10971
11202
  continue;
10972
11203
  }
10973
- const fileDir = path11.dirname(filePath);
10974
- if (!fs9.existsSync(fileDir)) fs9.mkdirSync(fileDir, { recursive: true });
10975
11204
  writtenPaths.add(file.path);
10976
11205
  if (file.content !== void 0) {
10977
- fs9.writeFileSync(filePath, file.content, "utf-8");
11206
+ if (!writeFileNoFollow(resDir, filePath, Buffer.from(file.content, "utf-8"))) {
11207
+ console.warn(` Warning: skipping ${file.path} (destination escapes the resource folder via a symlink)`);
11208
+ writtenPaths.delete(file.path);
11209
+ }
10978
11210
  } else if (file.content_base64) {
10979
- fs9.writeFileSync(filePath, Buffer.from(file.content_base64, "base64"));
11211
+ if (!writeFileNoFollow(resDir, filePath, Buffer.from(file.content_base64, "base64"))) {
11212
+ console.warn(` Warning: skipping ${file.path} (destination escapes the resource folder via a symlink)`);
11213
+ writtenPaths.delete(file.path);
11214
+ }
10980
11215
  }
10981
11216
  }
10982
- if (fs9.existsSync(resDir)) cleanOrphanFiles(resDir, "", writtenPaths);
11217
+ if (fs10.existsSync(resDir)) cleanOrphanFiles(resDir, "", writtenPaths);
10983
11218
  }
10984
11219
  function cleanOrphanFiles(dir, prefix, writtenPaths) {
10985
- const entries = fs9.readdirSync(dir, { withFileTypes: true });
11220
+ const entries = fs10.readdirSync(dir, { withFileTypes: true });
10986
11221
  for (const entry of entries) {
10987
11222
  const relPath = prefix ? `${prefix}/${entry.name}` : entry.name;
10988
- const fullPath = path11.join(dir, entry.name);
11223
+ const fullPath = path12.join(dir, entry.name);
10989
11224
  if (entry.isDirectory()) {
10990
11225
  cleanOrphanFiles(fullPath, relPath, writtenPaths);
10991
- if (fs9.readdirSync(fullPath).length === 0) fs9.rmdirSync(fullPath);
11226
+ if (fs10.readdirSync(fullPath).length === 0) fs10.rmdirSync(fullPath);
10992
11227
  } else if (!writtenPaths.has(relPath)) {
10993
- fs9.unlinkSync(fullPath);
11228
+ fs10.unlinkSync(fullPath);
10994
11229
  }
10995
11230
  }
10996
11231
  }
10997
- async function downloadBinaryFiles(resDir, files) {
11232
+ async function downloadBinaryFiles(resDir, files, root) {
11233
+ if (!ensureRealSubdirNoSymlink(root, resDir, true)) {
11234
+ console.warn(` Warning: skipping resource downloads (dir not reachable inside the workspace without a symlink)`);
11235
+ return 0;
11236
+ }
10998
11237
  let count = 0;
10999
11238
  for (const file of files) {
11000
11239
  if (!file.download_url || file.content !== void 0) continue;
11001
11240
  try {
11002
- const filePath = path11.join(resDir, file.path);
11003
- if (!filePath.startsWith(resDir + path11.sep) && filePath !== resDir) {
11241
+ const filePath = path12.join(resDir, file.path);
11242
+ if (!filePath.startsWith(resDir + path12.sep) && filePath !== resDir) {
11004
11243
  console.warn(` Warning: skipping ${file.path} (path traversal detected)`);
11005
11244
  continue;
11006
11245
  }
@@ -11010,10 +11249,8 @@ async function downloadBinaryFiles(resDir, files) {
11010
11249
  continue;
11011
11250
  }
11012
11251
  const buffer = Buffer.from(await response.arrayBuffer());
11013
- const fileDir = path11.dirname(filePath);
11014
- if (!fs9.existsSync(fileDir)) fs9.mkdirSync(fileDir, { recursive: true });
11015
- fs9.writeFileSync(filePath, buffer);
11016
- count++;
11252
+ if (writeFileNoFollow(resDir, filePath, buffer)) count++;
11253
+ else console.warn(` Warning: skipping ${file.path} (destination escapes the resource folder via a symlink)`);
11017
11254
  } catch (err) {
11018
11255
  console.warn(` Warning: failed to download ${file.path} (${err instanceof Error ? err.message : String(err)})`);
11019
11256
  }
@@ -11024,6 +11261,7 @@ var BINARY_EXTENSIONS, MAX_RESOURCE_FILE_SIZE2;
11024
11261
  var init_resource_files = __esm({
11025
11262
  "src/lib/resource-files.ts"() {
11026
11263
  "use strict";
11264
+ init_fs_safety();
11027
11265
  BINARY_EXTENSIONS = /* @__PURE__ */ new Set([
11028
11266
  "pdf",
11029
11267
  "png",
@@ -11049,8 +11287,8 @@ var init_resource_files = __esm({
11049
11287
  });
11050
11288
 
11051
11289
  // src/lib/yaml-writer.ts
11052
- import * as fs10 from "fs";
11053
- import * as path12 from "path";
11290
+ import * as fs11 from "fs";
11291
+ import * as path13 from "path";
11054
11292
  import * as yaml4 from "js-yaml";
11055
11293
  function buildEvalYamlObject(evalEntry, slug) {
11056
11294
  const out = {};
@@ -11084,42 +11322,42 @@ function buildEvalYamlObject(evalEntry, slug) {
11084
11322
  return out;
11085
11323
  }
11086
11324
  function writeHubFolder(hubFolder, payload, options = {}) {
11087
- const agentsDir = path12.join(hubFolder, "agents");
11088
- if (!fs10.existsSync(hubFolder)) {
11089
- fs10.mkdirSync(hubFolder, { recursive: true });
11325
+ const agentsDir = path13.join(hubFolder, "agents");
11326
+ if (!fs11.existsSync(hubFolder)) {
11327
+ fs11.mkdirSync(hubFolder, { recursive: true });
11090
11328
  }
11091
- if (!fs10.existsSync(agentsDir)) {
11092
- fs10.mkdirSync(agentsDir, { recursive: true });
11329
+ if (!fs11.existsSync(agentsDir)) {
11330
+ fs11.mkdirSync(agentsDir, { recursive: true });
11093
11331
  }
11094
11332
  const yamlPayload = buildYamlPayload(payload);
11095
11333
  const agentFiles = extractAgentFiles(payload);
11096
11334
  const yamlContent = yaml4.dump(yamlPayload, YAML_DUMP_OPTIONS);
11097
- fs10.writeFileSync(path12.join(hubFolder, "hub.yaml"), yamlContent, "utf-8");
11335
+ fs11.writeFileSync(path13.join(hubFolder, "hub.yaml"), yamlContent, "utf-8");
11098
11336
  if (options.seedAgentContext ?? true) {
11099
- writeIfAbsent(path12.join(hubFolder, "AGENTS.md"), perHubAgentsMd(path12.basename(hubFolder)));
11100
- writeIfAbsent(path12.join(hubFolder, "CLAUDE.md"), CLAUDE_MD_SHIM);
11337
+ writeIfAbsent(path13.join(hubFolder, "AGENTS.md"), perHubAgentsMd(path13.basename(hubFolder)));
11338
+ writeIfAbsent(path13.join(hubFolder, "CLAUDE.md"), CLAUDE_MD_SHIM);
11101
11339
  }
11102
- const oldYamlPath = path12.join(hubFolder, "wayai.yaml");
11103
- if (fs10.existsSync(oldYamlPath)) {
11104
- fs10.unlinkSync(oldYamlPath);
11340
+ const oldYamlPath = path13.join(hubFolder, "wayai.yaml");
11341
+ if (fs11.existsSync(oldYamlPath)) {
11342
+ fs11.unlinkSync(oldYamlPath);
11105
11343
  }
11106
11344
  const yamlSlugs = writeAgentYamlFiles(agentsDir, payload.agents || []);
11107
11345
  const mdSlugs = /* @__PURE__ */ new Set();
11108
11346
  for (const { slug, content } of agentFiles) {
11109
11347
  mdSlugs.add(slug);
11110
- fs10.writeFileSync(path12.join(agentsDir, `${slug}.md`), content, "utf-8");
11348
+ fs11.writeFileSync(path13.join(agentsDir, `${slug}.md`), content, "utf-8");
11111
11349
  }
11112
- const existingFiles = fs10.readdirSync(agentsDir);
11350
+ const existingFiles = fs11.readdirSync(agentsDir);
11113
11351
  for (const file of existingFiles) {
11114
11352
  if (file.endsWith(".yaml")) {
11115
11353
  const slug = file.slice(0, -5);
11116
11354
  if (!yamlSlugs.has(slug)) {
11117
- fs10.unlinkSync(path12.join(agentsDir, file));
11355
+ fs11.unlinkSync(path13.join(agentsDir, file));
11118
11356
  }
11119
11357
  } else if (file.endsWith(".md")) {
11120
11358
  const slug = file.slice(0, -3);
11121
11359
  if (!mdSlugs.has(slug)) {
11122
- fs10.unlinkSync(path12.join(agentsDir, file));
11360
+ fs11.unlinkSync(path13.join(agentsDir, file));
11123
11361
  }
11124
11362
  }
11125
11363
  }
@@ -11130,10 +11368,10 @@ function writeHubFolder(hubFolder, payload, options = {}) {
11130
11368
  function setPreviewLabelInHubYaml(hubFolder, label) {
11131
11369
  const yamlPath = resolveHubYamlPath(hubFolder);
11132
11370
  if (!yamlPath) return;
11133
- const obj = yaml4.load(fs10.readFileSync(yamlPath, "utf-8")) ?? {};
11371
+ const obj = yaml4.load(fs11.readFileSync(yamlPath, "utf-8")) ?? {};
11134
11372
  if (label) obj.preview_label = label;
11135
11373
  else delete obj.preview_label;
11136
- fs10.writeFileSync(yamlPath, yaml4.dump(obj, YAML_DUMP_OPTIONS), "utf-8");
11374
+ fs11.writeFileSync(yamlPath, yaml4.dump(obj, YAML_DUMP_OPTIONS), "utf-8");
11137
11375
  }
11138
11376
  function buildYamlPayload(payload) {
11139
11377
  const result = {
@@ -11179,7 +11417,7 @@ function writeAgentYamlFiles(agentsDir, agents) {
11179
11417
  const entry = { ...agent };
11180
11418
  delete entry.instructions;
11181
11419
  const yamlContent = yaml4.dump(entry, YAML_DUMP_OPTIONS);
11182
- fs10.writeFileSync(path12.join(agentsDir, `${slug}.yaml`), yamlContent, "utf-8");
11420
+ fs11.writeFileSync(path13.join(agentsDir, `${slug}.yaml`), yamlContent, "utf-8");
11183
11421
  }
11184
11422
  return slugs;
11185
11423
  }
@@ -11196,65 +11434,65 @@ function extractAgentFiles(payload) {
11196
11434
  return files;
11197
11435
  }
11198
11436
  function writeEvalYamlFiles(hubFolder, evals) {
11199
- const evalsDir = path12.join(hubFolder, "evals");
11437
+ const evalsDir = path13.join(hubFolder, "evals");
11200
11438
  if (evals.length === 0) {
11201
- if (fs10.existsSync(evalsDir)) {
11439
+ if (fs11.existsSync(evalsDir)) {
11202
11440
  cleanEvalOrphans(evalsDir, /* @__PURE__ */ new Set());
11203
11441
  try {
11204
- if (fs10.readdirSync(evalsDir).length === 0) fs10.rmdirSync(evalsDir);
11442
+ if (fs11.readdirSync(evalsDir).length === 0) fs11.rmdirSync(evalsDir);
11205
11443
  } catch {
11206
11444
  }
11207
11445
  }
11208
11446
  return;
11209
11447
  }
11210
- if (!fs10.existsSync(evalsDir)) {
11211
- fs10.mkdirSync(evalsDir, { recursive: true });
11448
+ if (!fs11.existsSync(evalsDir)) {
11449
+ fs11.mkdirSync(evalsDir, { recursive: true });
11212
11450
  }
11213
11451
  const writtenRelPaths = /* @__PURE__ */ new Set();
11214
11452
  for (const evalEntry of evals) {
11215
11453
  const slug = slugify(evalEntry.name);
11216
11454
  const setName = evalEntry.path && evalEntry.path.trim().length > 0 ? evalEntry.path : null;
11217
11455
  const relPath = setName ? `${setName}/${slug}.yaml` : `${slug}.yaml`;
11218
- const targetPath = path12.join(evalsDir, relPath);
11219
- if (!targetPath.startsWith(evalsDir + path12.sep)) {
11456
+ const targetPath = path13.join(evalsDir, relPath);
11457
+ if (!targetPath.startsWith(evalsDir + path13.sep)) {
11220
11458
  console.warn(` Warning: skipping eval "${evalEntry.name}" (scenario set "${setName}" escapes evals/ \u2014 possible bad backend data)`);
11221
11459
  continue;
11222
11460
  }
11223
11461
  if (setName) {
11224
- const setDir = path12.join(evalsDir, setName);
11225
- if (!fs10.existsSync(setDir)) {
11226
- fs10.mkdirSync(setDir, { recursive: true });
11462
+ const setDir = path13.join(evalsDir, setName);
11463
+ if (!fs11.existsSync(setDir)) {
11464
+ fs11.mkdirSync(setDir, { recursive: true });
11227
11465
  }
11228
11466
  }
11229
11467
  const yamlContent = yaml4.dump(buildEvalYamlObject(evalEntry, slug), YAML_DUMP_OPTIONS);
11230
- fs10.writeFileSync(targetPath, yamlContent, "utf-8");
11468
+ fs11.writeFileSync(targetPath, yamlContent, "utf-8");
11231
11469
  writtenRelPaths.add(relPath);
11232
11470
  }
11233
11471
  cleanEvalOrphans(evalsDir, writtenRelPaths);
11234
11472
  }
11235
11473
  function cleanEvalOrphans(evalsDir, writtenRelPaths) {
11236
- const entries = fs10.readdirSync(evalsDir, { withFileTypes: true });
11474
+ const entries = fs11.readdirSync(evalsDir, { withFileTypes: true });
11237
11475
  for (const entry of entries) {
11238
- const fullPath = path12.join(evalsDir, entry.name);
11476
+ const fullPath = path13.join(evalsDir, entry.name);
11239
11477
  if (entry.isFile()) {
11240
11478
  if (entry.name.endsWith(".yaml") && !writtenRelPaths.has(entry.name)) {
11241
- fs10.unlinkSync(fullPath);
11479
+ fs11.unlinkSync(fullPath);
11242
11480
  }
11243
11481
  continue;
11244
11482
  }
11245
11483
  if (entry.isDirectory()) {
11246
11484
  const setName = entry.name;
11247
- const subEntries = fs10.readdirSync(fullPath, { withFileTypes: true });
11485
+ const subEntries = fs11.readdirSync(fullPath, { withFileTypes: true });
11248
11486
  for (const sub of subEntries) {
11249
11487
  if (sub.isFile() && sub.name.endsWith(".yaml")) {
11250
11488
  const relPath = `${setName}/${sub.name}`;
11251
11489
  if (!writtenRelPaths.has(relPath)) {
11252
- fs10.unlinkSync(path12.join(fullPath, sub.name));
11490
+ fs11.unlinkSync(path13.join(fullPath, sub.name));
11253
11491
  }
11254
11492
  }
11255
11493
  }
11256
11494
  try {
11257
- if (fs10.readdirSync(fullPath).length === 0) fs10.rmdirSync(fullPath);
11495
+ if (fs11.readdirSync(fullPath).length === 0) fs11.rmdirSync(fullPath);
11258
11496
  } catch {
11259
11497
  }
11260
11498
  }
@@ -11290,19 +11528,19 @@ function buildJourneyYamlObject(journeyEntry, slug) {
11290
11528
  return out;
11291
11529
  }
11292
11530
  function writeJourneyYamlFiles(hubFolder, journeys) {
11293
- const journeysDir = path12.join(hubFolder, "journeys");
11531
+ const journeysDir = path13.join(hubFolder, "journeys");
11294
11532
  if (journeys.length === 0) {
11295
- if (fs10.existsSync(journeysDir)) {
11533
+ if (fs11.existsSync(journeysDir)) {
11296
11534
  cleanJourneyOrphans(journeysDir, /* @__PURE__ */ new Set());
11297
11535
  try {
11298
- if (fs10.readdirSync(journeysDir).length === 0) fs10.rmdirSync(journeysDir);
11536
+ if (fs11.readdirSync(journeysDir).length === 0) fs11.rmdirSync(journeysDir);
11299
11537
  } catch {
11300
11538
  }
11301
11539
  }
11302
11540
  return;
11303
11541
  }
11304
- if (!fs10.existsSync(journeysDir)) {
11305
- fs10.mkdirSync(journeysDir, { recursive: true });
11542
+ if (!fs11.existsSync(journeysDir)) {
11543
+ fs11.mkdirSync(journeysDir, { recursive: true });
11306
11544
  }
11307
11545
  const writtenFiles = /* @__PURE__ */ new Set();
11308
11546
  const usedSlugs = /* @__PURE__ */ new Set();
@@ -11310,33 +11548,33 @@ function writeJourneyYamlFiles(hubFolder, journeys) {
11310
11548
  const slug = ensureUniqueSlug(slugify(journeyEntry.name), usedSlugs);
11311
11549
  const fileName = `${slug}.yaml`;
11312
11550
  const yamlContent = yaml4.dump(buildJourneyYamlObject(journeyEntry, slug), YAML_DUMP_OPTIONS);
11313
- fs10.writeFileSync(path12.join(journeysDir, fileName), yamlContent, "utf-8");
11551
+ fs11.writeFileSync(path13.join(journeysDir, fileName), yamlContent, "utf-8");
11314
11552
  writtenFiles.add(fileName);
11315
11553
  }
11316
11554
  cleanJourneyOrphans(journeysDir, writtenFiles);
11317
11555
  }
11318
11556
  function cleanJourneyOrphans(journeysDir, writtenFiles) {
11319
- const entries = fs10.readdirSync(journeysDir, { withFileTypes: true });
11557
+ const entries = fs11.readdirSync(journeysDir, { withFileTypes: true });
11320
11558
  for (const entry of entries) {
11321
11559
  if (entry.isFile() && entry.name.endsWith(".yaml") && !writtenFiles.has(entry.name)) {
11322
- fs10.unlinkSync(path12.join(journeysDir, entry.name));
11560
+ fs11.unlinkSync(path13.join(journeysDir, entry.name));
11323
11561
  }
11324
11562
  }
11325
11563
  }
11326
11564
  function writeResourceFiles(hubFolder, resources) {
11327
- const resourcesDir = path12.join(hubFolder, "resources");
11565
+ const resourcesDir = path13.join(hubFolder, "resources");
11328
11566
  const currentSlugs = /* @__PURE__ */ new Set();
11329
11567
  for (const resource of resources) {
11330
11568
  const resSlug = slugify(resource.name);
11331
11569
  currentSlugs.add(resSlug);
11332
- const resDir = path12.join(resourcesDir, resSlug);
11333
- writeResourceFileTree(resDir, resource.files || []);
11570
+ const resDir = path13.join(resourcesDir, resSlug);
11571
+ writeResourceFileTree(resDir, resource.files || [], hubFolder);
11334
11572
  }
11335
- if (fs10.existsSync(resourcesDir)) {
11336
- const existingDirs = fs10.readdirSync(resourcesDir, { withFileTypes: true });
11573
+ if (fs11.existsSync(resourcesDir)) {
11574
+ const existingDirs = fs11.readdirSync(resourcesDir, { withFileTypes: true });
11337
11575
  for (const entry of existingDirs) {
11338
11576
  if (entry.isDirectory() && !currentSlugs.has(entry.name)) {
11339
- fs10.rmSync(path12.join(resourcesDir, entry.name), { recursive: true, force: true });
11577
+ fs11.rmSync(path13.join(resourcesDir, entry.name), { recursive: true, force: true });
11340
11578
  }
11341
11579
  }
11342
11580
  }
@@ -11361,9 +11599,187 @@ var init_yaml_writer = __esm({
11361
11599
  }
11362
11600
  });
11363
11601
 
11602
+ // src/lib/eval-attachments.ts
11603
+ import * as fs12 from "fs";
11604
+ import * as path14 from "path";
11605
+ function resolveTurnAttachments(hubFolder, turn, label, bytesByHash) {
11606
+ const raw = turn.attachments;
11607
+ if (raw === void 0 || raw === null) return void 0;
11608
+ if (!Array.isArray(raw) || raw.length === 0) {
11609
+ if (Array.isArray(raw)) return void 0;
11610
+ throw expected(`${label}: "attachments" must be a list of file paths.`);
11611
+ }
11612
+ if (turn.role !== "user") {
11613
+ throw expected(`${label}: attachments are only allowed on user turns (this turn is "${String(turn.role)}").`);
11614
+ }
11615
+ const refs = [];
11616
+ for (const entry of raw) {
11617
+ if (typeof entry !== "string" || !entry.trim()) {
11618
+ throw expected(`${label}: each attachment must be a hub-relative file path string.`);
11619
+ }
11620
+ const abs = path14.resolve(hubFolder, entry);
11621
+ const root = path14.resolve(hubFolder);
11622
+ if (abs !== root && !abs.startsWith(root + path14.sep)) {
11623
+ throw expected(`${label}: attachment "${entry}" escapes the hub folder.`);
11624
+ }
11625
+ let stat;
11626
+ try {
11627
+ stat = fs12.statSync(abs);
11628
+ } catch {
11629
+ }
11630
+ if (!stat?.isFile()) {
11631
+ throw expected(`${label}: attachment "${entry}" not found.`);
11632
+ }
11633
+ if (!realpathContained(root, abs)) {
11634
+ throw expected(`${label}: attachment "${entry}" resolves outside the hub folder (symlinks are not allowed).`);
11635
+ }
11636
+ if (stat.size > MAX_RESOURCE_FILE_SIZE2) {
11637
+ throw expected(
11638
+ `${label}: attachment "${entry}" is ${(stat.size / 1024 / 1024).toFixed(1)}MB, over the 10MB limit.`
11639
+ );
11640
+ }
11641
+ const data = fs12.readFileSync(abs);
11642
+ const hash = computeHash(data);
11643
+ const fileName = path14.basename(abs);
11644
+ const mimeType = guessMimeType(fileName) ?? "application/octet-stream";
11645
+ refs.push({ hash, file_name: fileName, mime_type: mimeType, file_size: stat.size });
11646
+ if (!bytesByHash.has(hash)) {
11647
+ bytesByHash.set(hash, {
11648
+ hash,
11649
+ content_base64: data.toString("base64"),
11650
+ file_name: fileName,
11651
+ mime_type: mimeType,
11652
+ file_size: stat.size
11653
+ });
11654
+ }
11655
+ }
11656
+ return refs;
11657
+ }
11658
+ function applyTurnAttachments(hubFolder, turn, label, bytesByHash) {
11659
+ const refs = resolveTurnAttachments(hubFolder, turn, label, bytesByHash);
11660
+ if (refs) turn.attachments = refs;
11661
+ else delete turn.attachments;
11662
+ }
11663
+ function forEachAttachableTurn(payload, fn) {
11664
+ for (const ev of payload.evals ?? []) {
11665
+ if (ev.input) fn(ev.input);
11666
+ for (const t of ev.history ?? []) fn(t);
11667
+ }
11668
+ for (const jr of payload.journeys ?? []) {
11669
+ for (const t of jr.transcript ?? []) fn(t);
11670
+ }
11671
+ }
11672
+ function rewriteEvalAttachmentsToLocalPaths(payload) {
11673
+ const urlByHash = new Map((payload.eval_attachment_files ?? []).map((f) => [f.hash, f.download_url]));
11674
+ const safeName = (ref) => {
11675
+ const base = path14.basename(ref.file_name);
11676
+ return base === "" || base === "." || base === ".." ? ref.hash.slice(0, 16) : base;
11677
+ };
11678
+ const hashesByName = /* @__PURE__ */ new Map();
11679
+ const collect = (turn) => {
11680
+ if (!Array.isArray(turn.attachments)) return;
11681
+ for (const ref of turn.attachments) {
11682
+ if (!ref?.hash || !ref.file_name) continue;
11683
+ const name = safeName(ref);
11684
+ let set = hashesByName.get(name);
11685
+ if (!set) {
11686
+ set = /* @__PURE__ */ new Set();
11687
+ hashesByName.set(name, set);
11688
+ }
11689
+ set.add(ref.hash);
11690
+ }
11691
+ };
11692
+ forEachAttachableTurn(payload, collect);
11693
+ const pathByKey = /* @__PURE__ */ new Map();
11694
+ const usedLower = /* @__PURE__ */ new Set();
11695
+ const localPathFor = (ref) => {
11696
+ const name = safeName(ref);
11697
+ const key = `${ref.hash} ${name}`;
11698
+ const existing = pathByKey.get(key);
11699
+ if (existing) return existing;
11700
+ const ext = path14.extname(name);
11701
+ const stem = path14.basename(name, ext);
11702
+ const collides = (hashesByName.get(name)?.size ?? 0) > 1;
11703
+ let rel = collides ? `${ATTACHMENTS_DIR}/${stem}-${ref.hash.slice(0, 8)}${ext}` : `${ATTACHMENTS_DIR}/${name}`;
11704
+ if (usedLower.has(rel.toLowerCase())) {
11705
+ let n = 12;
11706
+ while (usedLower.has(rel.toLowerCase()) && n <= ref.hash.length) {
11707
+ rel = `${ATTACHMENTS_DIR}/${stem}-${ref.hash.slice(0, n)}${ext}`;
11708
+ n += 4;
11709
+ }
11710
+ for (let extra = 2; usedLower.has(rel.toLowerCase()); extra++) {
11711
+ rel = `${ATTACHMENTS_DIR}/${stem}-${ref.hash}-${extra}${ext}`;
11712
+ }
11713
+ }
11714
+ usedLower.add(rel.toLowerCase());
11715
+ pathByKey.set(key, rel);
11716
+ return rel;
11717
+ };
11718
+ const downloads = /* @__PURE__ */ new Map();
11719
+ forEachAttachableTurn(payload, (turn) => {
11720
+ if (!Array.isArray(turn.attachments)) return;
11721
+ turn.attachments = turn.attachments.map((ref) => {
11722
+ const rel = localPathFor(ref);
11723
+ const url = urlByHash.get(ref.hash);
11724
+ if (url && !downloads.has(rel)) downloads.set(rel, { relPath: rel, url, hash: ref.hash });
11725
+ return rel;
11726
+ });
11727
+ });
11728
+ delete payload.eval_attachment_files;
11729
+ return [...downloads.values()];
11730
+ }
11731
+ async function downloadEvalAttachments(hubFolder, downloads) {
11732
+ const attachDir = path14.resolve(hubFolder, ATTACHMENTS_DIR);
11733
+ let written = 0;
11734
+ if (!ensureRealSubdirNoSymlink(hubFolder, attachDir, downloads.length > 0)) {
11735
+ console.warn(` Warning: ${ATTACHMENTS_DIR}/ is not reachable inside the hub folder without a symlink; skipping attachment sync.`);
11736
+ return written;
11737
+ }
11738
+ if (!fs12.existsSync(attachDir)) return written;
11739
+ for (const dl of downloads) {
11740
+ const abs = path14.resolve(hubFolder, dl.relPath);
11741
+ if (abs !== attachDir && !abs.startsWith(attachDir + path14.sep)) continue;
11742
+ let existing;
11743
+ try {
11744
+ existing = fs12.lstatSync(abs);
11745
+ } catch {
11746
+ }
11747
+ if (existing?.isFile() && !existing.isSymbolicLink() && computeHash(fs12.readFileSync(abs)) === dl.hash) {
11748
+ continue;
11749
+ }
11750
+ try {
11751
+ const res = await fetch(dl.url);
11752
+ if (!res.ok) {
11753
+ console.warn(` Warning: failed to download ${dl.relPath} (HTTP ${res.status})`);
11754
+ continue;
11755
+ }
11756
+ if (writeFileNoFollow(hubFolder, abs, Buffer.from(await res.arrayBuffer()))) written++;
11757
+ } catch (err) {
11758
+ console.warn(` Warning: failed to download ${dl.relPath}: ${err instanceof Error ? err.message : String(err)}`);
11759
+ }
11760
+ }
11761
+ const keep = new Set(downloads.map((d) => path14.resolve(hubFolder, d.relPath)));
11762
+ for (const name of fs12.readdirSync(attachDir)) {
11763
+ const abs = path14.join(attachDir, name);
11764
+ const st = fs12.lstatSync(abs);
11765
+ if ((st.isFile() || st.isSymbolicLink()) && !keep.has(abs)) fs12.rmSync(abs);
11766
+ }
11767
+ return written;
11768
+ }
11769
+ var ATTACHMENTS_DIR;
11770
+ var init_eval_attachments = __esm({
11771
+ "src/lib/eval-attachments.ts"() {
11772
+ "use strict";
11773
+ init_resource_files();
11774
+ init_fs_safety();
11775
+ init_parser();
11776
+ ATTACHMENTS_DIR = "attachments";
11777
+ }
11778
+ });
11779
+
11364
11780
  // src/lib/parser.ts
11365
- import * as fs11 from "fs";
11366
- import * as path13 from "path";
11781
+ import * as fs13 from "fs";
11782
+ import * as path15 from "path";
11367
11783
  import * as yaml5 from "js-yaml";
11368
11784
  function expected(message) {
11369
11785
  const err = new Error(message);
@@ -11377,7 +11793,7 @@ function parseHubFolder(hubFolder, opts) {
11377
11793
  err.isExpected = true;
11378
11794
  throw err;
11379
11795
  }
11380
- const yamlContent = fs11.readFileSync(yamlPath, "utf-8");
11796
+ const yamlContent = fs13.readFileSync(yamlPath, "utf-8");
11381
11797
  let config;
11382
11798
  try {
11383
11799
  config = yaml5.load(yamlContent);
@@ -11412,9 +11828,9 @@ function parseHubFolder(hubFolder, opts) {
11412
11828
  const resolved = { ...agent };
11413
11829
  if (typeof resolved.instructions === "string" && resolved.instructions.endsWith(".md")) {
11414
11830
  const instrValue = resolved.instructions;
11415
- const instructionsPath = instrValue.startsWith("agents/") ? path13.join(hubFolder, instrValue) : path13.join(hubFolder, "agents", instrValue);
11416
- if (fs11.existsSync(instructionsPath)) {
11417
- resolved.instructions = fs11.readFileSync(instructionsPath, "utf-8");
11831
+ const instructionsPath = instrValue.startsWith("agents/") ? path15.join(hubFolder, instrValue) : path15.join(hubFolder, "agents", instrValue);
11832
+ if (fs13.existsSync(instructionsPath)) {
11833
+ resolved.instructions = fs13.readFileSync(instructionsPath, "utf-8");
11418
11834
  } else {
11419
11835
  const err = new Error(
11420
11836
  `Agent instructions file not found: ${instructionsPath} (referenced by agent "${agent.name}")`
@@ -11423,9 +11839,9 @@ function parseHubFolder(hubFolder, opts) {
11423
11839
  throw err;
11424
11840
  }
11425
11841
  } else if (resolved.instructions === void 0 && typeof agent.name === "string") {
11426
- const conventionPath = path13.join(hubFolder, "agents", `${slugify(agent.name)}.md`);
11427
- if (fs11.existsSync(conventionPath)) {
11428
- resolved.instructions = fs11.readFileSync(conventionPath, "utf-8");
11842
+ const conventionPath = path15.join(hubFolder, "agents", `${slugify(agent.name)}.md`);
11843
+ if (fs13.existsSync(conventionPath)) {
11844
+ resolved.instructions = fs13.readFileSync(conventionPath, "utf-8");
11429
11845
  }
11430
11846
  }
11431
11847
  return resolved;
@@ -11464,58 +11880,62 @@ function parseHubFolder(hubFolder, opts) {
11464
11880
  if (config.outbound_schedules && config.outbound_schedules.length > 0) {
11465
11881
  payload.outbound_schedules = config.outbound_schedules;
11466
11882
  }
11467
- const evals = scanEvalYamlFiles(hubFolder);
11883
+ const bytesByHash = /* @__PURE__ */ new Map();
11884
+ const evals = scanEvalYamlFiles(hubFolder, bytesByHash);
11468
11885
  if (evals.length > 0) {
11469
11886
  payload.evals = evals;
11470
11887
  }
11471
- const journeys = scanJourneyYamlFiles(hubFolder);
11888
+ const journeys = scanJourneyYamlFiles(hubFolder, bytesByHash);
11472
11889
  if (journeys.length > 0) {
11473
11890
  payload.journeys = journeys;
11474
11891
  }
11892
+ if (bytesByHash.size > 0) {
11893
+ payload.eval_attachment_files = [...bytesByHash.values()];
11894
+ }
11475
11895
  return payload;
11476
11896
  }
11477
- function scanEvalYamlFiles(hubFolder) {
11478
- const evalsDir = path13.join(hubFolder, "evals");
11479
- if (!fs11.existsSync(evalsDir)) return [];
11897
+ function scanEvalYamlFiles(hubFolder, bytesByHash) {
11898
+ const evalsDir = path15.join(hubFolder, "evals");
11899
+ if (!fs13.existsSync(evalsDir)) return [];
11480
11900
  const evals = [];
11481
11901
  const seen = /* @__PURE__ */ new Set();
11482
- const topEntries = fs11.readdirSync(evalsDir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
11902
+ const topEntries = fs13.readdirSync(evalsDir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
11483
11903
  for (const entry of topEntries) {
11484
11904
  if (entry.isFile() && entry.name.endsWith(".yaml")) {
11485
- collectEval(evals, seen, path13.join(evalsDir, entry.name), `evals/${entry.name}`, null);
11905
+ collectEval(evals, seen, path15.join(evalsDir, entry.name), `evals/${entry.name}`, null, hubFolder, bytesByHash);
11486
11906
  } else if (entry.isDirectory()) {
11487
11907
  const setName = entry.name;
11488
- const setDir = path13.join(evalsDir, setName);
11489
- const setEntries = fs11.readdirSync(setDir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
11908
+ const setDir = path15.join(evalsDir, setName);
11909
+ const setEntries = fs13.readdirSync(setDir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
11490
11910
  for (const sub of setEntries) {
11491
11911
  if (sub.isDirectory()) {
11492
11912
  throw expected(
11493
- `Nested scenario sets are not supported: ${path13.relative(hubFolder, path13.join(setDir, sub.name))}. Move scenarios up to evals/${setName}/.`
11913
+ `Nested scenario sets are not supported: ${path15.relative(hubFolder, path15.join(setDir, sub.name))}. Move scenarios up to evals/${setName}/.`
11494
11914
  );
11495
11915
  }
11496
11916
  if (sub.isFile() && sub.name.endsWith(".yaml")) {
11497
- collectEval(evals, seen, path13.join(setDir, sub.name), `evals/${setName}/${sub.name}`, setName);
11917
+ collectEval(evals, seen, path15.join(setDir, sub.name), `evals/${setName}/${sub.name}`, setName, hubFolder, bytesByHash);
11498
11918
  }
11499
11919
  }
11500
11920
  }
11501
11921
  }
11502
11922
  return evals;
11503
11923
  }
11504
- function collectEval(evals, seen, filePath, relPath, setName) {
11505
- const evalEntry = parseEvalYaml(filePath, relPath, setName);
11924
+ function collectEval(evals, seen, filePath, relPath, setName, hubFolder, bytesByHash) {
11925
+ const evalEntry = parseEvalYaml(filePath, relPath, setName, hubFolder, bytesByHash);
11506
11926
  if (!evalEntry) return;
11507
11927
  const key = `${evalEntry.name}|${setName ?? ""}`;
11508
11928
  if (seen.has(key)) {
11509
11929
  const where = setName ? `scenario set "${setName}"` : "root";
11510
11930
  throw expected(
11511
- `Duplicate eval at ${where}: "${evalEntry.name}" (in ${path13.basename(filePath)}). Each (name, scenario set) pair must be unique.`
11931
+ `Duplicate eval at ${where}: "${evalEntry.name}" (in ${path15.basename(filePath)}). Each (name, scenario set) pair must be unique.`
11512
11932
  );
11513
11933
  }
11514
11934
  seen.add(key);
11515
11935
  evals.push(evalEntry);
11516
11936
  }
11517
- function parseEvalYaml(filePath, relPath, setName) {
11518
- const content = fs11.readFileSync(filePath, "utf-8");
11937
+ function parseEvalYaml(filePath, relPath, setName, hubFolder, bytesByHash) {
11938
+ const content = fs13.readFileSync(filePath, "utf-8");
11519
11939
  let raw;
11520
11940
  try {
11521
11941
  raw = yaml5.load(content);
@@ -11530,7 +11950,7 @@ function parseEvalYaml(filePath, relPath, setName) {
11530
11950
  return null;
11531
11951
  }
11532
11952
  const data = raw;
11533
- const fileSlug = path13.basename(filePath, ".yaml");
11953
+ const fileSlug = path15.basename(filePath, ".yaml");
11534
11954
  const evalName = typeof data.name === "string" && data.name.trim().length > 0 ? data.name : fileSlug;
11535
11955
  if (typeof data.agent !== "string" || data.agent.trim().length === 0) {
11536
11956
  throw expected(`Eval "${evalName}" in ${relPath}: missing required field "agent" (string).`);
@@ -11541,11 +11961,13 @@ function parseEvalYaml(filePath, relPath, setName) {
11541
11961
  if (!data.expected || typeof data.expected !== "object" || Array.isArray(data.expected)) {
11542
11962
  throw expected(`Eval "${evalName}" in ${relPath}: missing required field "expected" (object).`);
11543
11963
  }
11964
+ const input = { ...data.input };
11965
+ applyTurnAttachments(hubFolder, input, `Eval "${evalName}" input`, bytesByHash);
11544
11966
  const evalEntry = {
11545
11967
  name: evalName,
11546
11968
  agent: data.agent,
11547
11969
  path: setName,
11548
- input: data.input,
11970
+ input,
11549
11971
  expected: data.expected
11550
11972
  };
11551
11973
  if (typeof data.id === "string") evalEntry.id = data.id;
@@ -11553,7 +11975,11 @@ function parseEvalYaml(filePath, relPath, setName) {
11553
11975
  if (typeof data.runs === "number") evalEntry.runs = data.runs;
11554
11976
  if (data.enabled === false) evalEntry.enabled = false;
11555
11977
  if (Array.isArray(data.history)) {
11556
- evalEntry.history = data.history;
11978
+ evalEntry.history = data.history.map((turn, i) => {
11979
+ const t = { ...turn };
11980
+ applyTurnAttachments(hubFolder, t, `Eval "${evalName}" history[${i}]`, bytesByHash);
11981
+ return t;
11982
+ });
11557
11983
  }
11558
11984
  if (typeof data.evaluator_instructions === "string") {
11559
11985
  evalEntry.evaluator_instructions = data.evaluator_instructions;
@@ -11583,20 +12009,20 @@ function parseFixtureField(raw, label, key) {
11583
12009
  const trimmed = raw.trim();
11584
12010
  return trimmed.length > 0 ? trimmed : void 0;
11585
12011
  }
11586
- function scanJourneyYamlFiles(hubFolder) {
11587
- const journeysDir = path13.join(hubFolder, "journeys");
11588
- if (!fs11.existsSync(journeysDir)) return [];
12012
+ function scanJourneyYamlFiles(hubFolder, bytesByHash) {
12013
+ const journeysDir = path15.join(hubFolder, "journeys");
12014
+ if (!fs13.existsSync(journeysDir)) return [];
11589
12015
  const journeys = [];
11590
12016
  const seen = /* @__PURE__ */ new Set();
11591
- const entries = fs11.readdirSync(journeysDir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
12017
+ const entries = fs13.readdirSync(journeysDir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
11592
12018
  for (const entry of entries) {
11593
12019
  if (entry.isDirectory()) {
11594
12020
  throw expected(
11595
- `Subfolders are not supported under journeys/: ${path13.relative(hubFolder, path13.join(journeysDir, entry.name))}. A journey owns its own managed scenario set \u2014 put each journey in a single journeys/<slug>.yaml file.`
12021
+ `Subfolders are not supported under journeys/: ${path15.relative(hubFolder, path15.join(journeysDir, entry.name))}. A journey owns its own managed scenario set \u2014 put each journey in a single journeys/<slug>.yaml file.`
11596
12022
  );
11597
12023
  }
11598
12024
  if (!entry.isFile() || !entry.name.endsWith(".yaml")) continue;
11599
- const journeyEntry = parseJourneyYaml(path13.join(journeysDir, entry.name), `journeys/${entry.name}`);
12025
+ const journeyEntry = parseJourneyYaml(path15.join(journeysDir, entry.name), `journeys/${entry.name}`, hubFolder, bytesByHash);
11600
12026
  if (!journeyEntry) continue;
11601
12027
  if (seen.has(journeyEntry.name)) {
11602
12028
  throw expected(
@@ -11608,8 +12034,8 @@ function scanJourneyYamlFiles(hubFolder) {
11608
12034
  }
11609
12035
  return journeys;
11610
12036
  }
11611
- function parseJourneyYaml(filePath, relPath) {
11612
- const content = fs11.readFileSync(filePath, "utf-8");
12037
+ function parseJourneyYaml(filePath, relPath, hubFolder, bytesByHash) {
12038
+ const content = fs13.readFileSync(filePath, "utf-8");
11613
12039
  let raw;
11614
12040
  try {
11615
12041
  raw = yaml5.load(content);
@@ -11624,7 +12050,7 @@ function parseJourneyYaml(filePath, relPath) {
11624
12050
  return null;
11625
12051
  }
11626
12052
  const data = raw;
11627
- const fileSlug = path13.basename(filePath, ".yaml");
12053
+ const fileSlug = path15.basename(filePath, ".yaml");
11628
12054
  const journeyName = typeof data.name === "string" && data.name.trim().length > 0 ? data.name : fileSlug;
11629
12055
  if (typeof data.agent !== "string" || data.agent.trim().length === 0) {
11630
12056
  throw expected(`Journey "${journeyName}" in ${relPath}: missing required field "agent" (string).`);
@@ -11646,7 +12072,11 @@ function parseJourneyYaml(filePath, relPath) {
11646
12072
  journeyEntry.evaluator_instructions = data.evaluator_instructions;
11647
12073
  }
11648
12074
  if (Array.isArray(data.transcript)) {
11649
- journeyEntry.transcript = data.transcript;
12075
+ journeyEntry.transcript = data.transcript.map((turn, i) => {
12076
+ const t = { ...turn };
12077
+ applyTurnAttachments(hubFolder, t, `Journey "${journeyName}" transcript[${i}]`, bytesByHash);
12078
+ return t;
12079
+ });
11650
12080
  }
11651
12081
  if (data.step_config && typeof data.step_config === "object" && !Array.isArray(data.step_config)) {
11652
12082
  journeyEntry.step_config = data.step_config;
@@ -11660,14 +12090,14 @@ function parseJourneyYaml(filePath, relPath) {
11660
12090
  return journeyEntry;
11661
12091
  }
11662
12092
  function scanAgentYamlFiles(hubFolder) {
11663
- const agentsDir = path13.join(hubFolder, "agents");
11664
- if (!fs11.existsSync(agentsDir)) return [];
11665
- const yamlFiles = fs11.readdirSync(agentsDir).filter((f) => f.endsWith(".yaml")).sort();
12093
+ const agentsDir = path15.join(hubFolder, "agents");
12094
+ if (!fs13.existsSync(agentsDir)) return [];
12095
+ const yamlFiles = fs13.readdirSync(agentsDir).filter((f) => f.endsWith(".yaml")).sort();
11666
12096
  if (yamlFiles.length === 0) return [];
11667
12097
  const agents = [];
11668
12098
  for (const file of yamlFiles) {
11669
- const filePath = path13.join(agentsDir, file);
11670
- const content = fs11.readFileSync(filePath, "utf-8");
12099
+ const filePath = path15.join(agentsDir, file);
12100
+ const content = fs13.readFileSync(filePath, "utf-8");
11671
12101
  let agent;
11672
12102
  try {
11673
12103
  agent = yaml5.load(content);
@@ -11689,7 +12119,7 @@ function scanAgentYamlFiles(hubFolder) {
11689
12119
  return agents;
11690
12120
  }
11691
12121
  function parseResources(hubFolder, configResources) {
11692
- const resourcesDir = path13.join(hubFolder, "resources");
12122
+ const resourcesDir = path15.join(hubFolder, "resources");
11693
12123
  const results = [];
11694
12124
  for (const res of configResources) {
11695
12125
  const resource = {
@@ -11702,8 +12132,8 @@ function parseResources(hubFolder, configResources) {
11702
12132
  if (res.user_browsable) resource.user_browsable = res.user_browsable;
11703
12133
  if (res.skill_name) resource.skill_name = res.skill_name;
11704
12134
  const resSlug = slugify(resource.name);
11705
- const resDir = path13.join(resourcesDir, resSlug);
11706
- if (fs11.existsSync(resDir)) {
12135
+ const resDir = path15.join(resourcesDir, resSlug);
12136
+ if (fs13.existsSync(resDir)) {
11707
12137
  const files = scanResourceFiles(resDir, "");
11708
12138
  if (files.length > 0) {
11709
12139
  resource.files = files;
@@ -11718,6 +12148,7 @@ var init_parser = __esm({
11718
12148
  "use strict";
11719
12149
  init_utils();
11720
12150
  init_resource_files();
12151
+ init_eval_attachments();
11721
12152
  }
11722
12153
  });
11723
12154
 
@@ -11757,8 +12188,8 @@ __export(pull_exports, {
11757
12188
  resolveHubTarget: () => resolveHubTarget,
11758
12189
  writeProductionMirror: () => writeProductionMirror
11759
12190
  });
11760
- import * as fs12 from "fs";
11761
- import * as path14 from "path";
12191
+ import * as fs14 from "fs";
12192
+ import * as path16 from "path";
11762
12193
  function parseArgs4(args2) {
11763
12194
  let autoConfirm = false;
11764
12195
  let hubSelector;
@@ -11777,7 +12208,7 @@ function resolveHubTarget(workspaceDir, selector) {
11777
12208
  const allHubs = scanWorkspaceHubs(workspaceDir);
11778
12209
  if (selector) {
11779
12210
  const match = allHubs.find(
11780
- (h) => h.hubId === selector || path14.basename(h.hubFolder) === selector
12211
+ (h) => h.hubId === selector || path16.basename(h.hubFolder) === selector
11781
12212
  );
11782
12213
  if (match) return { hubId: match.hubId, hubFolder: match.hubFolder };
11783
12214
  if (UUID_RE2.test(selector)) return { hubId: selector, hubFolder: null };
@@ -11794,7 +12225,7 @@ function resolveHubTarget(workspaceDir, selector) {
11794
12225
  }
11795
12226
  console.error(`Multiple hubs found in ${wsLabel}/. Pass --hub <uuid|folder-name> to choose, or run from inside a hub folder:`);
11796
12227
  for (const h of previewHubs) {
11797
- console.error(` ${path14.basename(h.hubFolder)} (${h.hubId})`);
12228
+ console.error(` ${path16.basename(h.hubFolder)} (${h.hubId})`);
11798
12229
  }
11799
12230
  process.exit(1);
11800
12231
  }
@@ -11821,6 +12252,7 @@ async function pullCommand(args2) {
11821
12252
  return;
11822
12253
  }
11823
12254
  assertHubMatchesBinding(hubId);
12255
+ const attachmentDownloads = rewriteEvalAttachmentsToLocalPaths(payload);
11824
12256
  let hubFolder = existingFolder;
11825
12257
  let wroteFiles = false;
11826
12258
  if (!hubFolder) {
@@ -11832,10 +12264,11 @@ async function pullCommand(args2) {
11832
12264
  payload.preview_label,
11833
12265
  payload.branch_name
11834
12266
  );
11835
- fs12.mkdirSync(path14.dirname(hubFolder), { recursive: true });
12267
+ fs14.mkdirSync(path16.dirname(hubFolder), { recursive: true });
11836
12268
  console.log("Writing hub configuration...");
11837
12269
  writeHubFolder(hubFolder, payload);
11838
12270
  await downloadBinaryResourceFiles(hubFolder, payload);
12271
+ await downloadEvalAttachments(hubFolder, attachmentDownloads);
11839
12272
  const finalFolder = autoRenameHubFolder(hubFolder, payload.hub.name, payload.hub_environment, payload.hub_id, payload.preview_label, payload.branch_name);
11840
12273
  console.log(`Hub configuration written to ${finalFolder}`);
11841
12274
  wroteFiles = true;
@@ -11848,6 +12281,7 @@ async function pullCommand(args2) {
11848
12281
  if (!diffResult.has_changes && !connectionsChanged) {
11849
12282
  writeHubFolder(hubFolder, payload);
11850
12283
  await downloadBinaryResourceFiles(hubFolder, payload);
12284
+ await downloadEvalAttachments(hubFolder, attachmentDownloads);
11851
12285
  const finalFolder = autoRenameHubFolder(hubFolder, payload.hub.name, payload.hub_environment, payload.hub_id, payload.preview_label, payload.branch_name);
11852
12286
  wroteFiles = true;
11853
12287
  if (finalFolder === hubFolder) {
@@ -11871,6 +12305,7 @@ async function pullCommand(args2) {
11871
12305
  if (!cancelled) {
11872
12306
  writeHubFolder(hubFolder, payload);
11873
12307
  await downloadBinaryResourceFiles(hubFolder, payload);
12308
+ await downloadEvalAttachments(hubFolder, attachmentDownloads);
11874
12309
  const finalFolder = autoRenameHubFolder(hubFolder, payload.hub.name, payload.hub_environment, payload.hub_id, payload.preview_label, payload.branch_name);
11875
12310
  console.log(`Hub configuration written to ${finalFolder}`);
11876
12311
  wroteFiles = true;
@@ -11880,6 +12315,7 @@ async function pullCommand(args2) {
11880
12315
  console.log("Writing hub configuration...");
11881
12316
  writeHubFolder(hubFolder, payload);
11882
12317
  await downloadBinaryResourceFiles(hubFolder, payload);
12318
+ await downloadEvalAttachments(hubFolder, attachmentDownloads);
11883
12319
  const finalFolder = autoRenameHubFolder(hubFolder, payload.hub.name, payload.hub_environment, payload.hub_id, payload.preview_label, payload.branch_name);
11884
12320
  console.log(`Hub configuration written to ${finalFolder}`);
11885
12321
  wroteFiles = true;
@@ -11893,9 +12329,11 @@ async function pullCommand(args2) {
11893
12329
  }
11894
12330
  async function writeProductionMirror(workspaceDir, prodPayload) {
11895
12331
  const folder = resolveHubFolder(workspaceDir, prodPayload.hub_id, prodPayload.hub.name, "production", null, null);
11896
- fs12.mkdirSync(path14.dirname(folder), { recursive: true });
12332
+ fs14.mkdirSync(path16.dirname(folder), { recursive: true });
12333
+ const attachmentDownloads = rewriteEvalAttachmentsToLocalPaths(prodPayload);
11897
12334
  writeHubFolder(folder, prodPayload, { seedAgentContext: false });
11898
12335
  await downloadBinaryResourceFiles(folder, prodPayload);
12336
+ await downloadEvalAttachments(folder, attachmentDownloads);
11899
12337
  const finalFolder = autoRenameHubFolder(folder, prodPayload.hub.name, "production", prodPayload.hub_id, null, null);
11900
12338
  prependMirrorMarker(finalFolder, prodPayload.hub_id);
11901
12339
  return finalFolder;
@@ -11910,13 +12348,13 @@ async function mirrorLinkedProduction(client, workspaceDir, productionHubId, org
11910
12348
  }
11911
12349
  }
11912
12350
  function prependMirrorMarker(hubFolder, productionHubId) {
11913
- const hubYaml = path14.join(hubFolder, "hub.yaml");
12351
+ const hubYaml = path16.join(hubFolder, "hub.yaml");
11914
12352
  try {
11915
- const content = fs12.readFileSync(hubYaml, "utf-8");
12353
+ const content = fs14.readFileSync(hubYaml, "utf-8");
11916
12354
  if (content.startsWith(MIRROR_MARKER_PREFIX)) return;
11917
12355
  const marker = `${MIRROR_MARKER_PREFIX} ${productionHubId}. Edits are ignored; push is blocked. Edit the linked preview hub instead.
11918
12356
  `;
11919
- fs12.writeFileSync(hubYaml, marker + content, "utf-8");
12357
+ fs14.writeFileSync(hubYaml, marker + content, "utf-8");
11920
12358
  } catch {
11921
12359
  }
11922
12360
  }
@@ -11925,8 +12363,8 @@ async function downloadBinaryResourceFiles(hubFolder, payload) {
11925
12363
  let downloadCount = 0;
11926
12364
  for (const resource of resources) {
11927
12365
  if (!resource.files) continue;
11928
- const resDir = path14.join(hubFolder, "resources", slugify(resource.name));
11929
- downloadCount += await downloadBinaryFiles(resDir, resource.files);
12366
+ const resDir = path16.join(hubFolder, "resources", slugify(resource.name));
12367
+ downloadCount += await downloadBinaryFiles(resDir, resource.files, hubFolder);
11930
12368
  }
11931
12369
  if (downloadCount > 0) {
11932
12370
  console.log(`Downloaded ${downloadCount} binary resource file(s).`);
@@ -11956,6 +12394,7 @@ var init_pull = __esm({
11956
12394
  init_diff_display();
11957
12395
  init_utils();
11958
12396
  init_resource_files();
12397
+ init_eval_attachments();
11959
12398
  init_workspace();
11960
12399
  init_layout();
11961
12400
  init_workspace_files();
@@ -11970,6 +12409,7 @@ var push_exports = {};
11970
12409
  __export(push_exports, {
11971
12410
  autoRenameAgentFiles: () => autoRenameAgentFiles,
11972
12411
  createAndPushNewHub: () => createAndPushNewHub,
12412
+ printErrors: () => printErrors,
11973
12413
  printSyncResult: () => printSyncResult,
11974
12414
  printWarnings: () => printWarnings,
11975
12415
  pushCommand: () => pushCommand,
@@ -11977,8 +12417,8 @@ __export(push_exports, {
11977
12417
  selectExistingHub: () => selectExistingHub,
11978
12418
  shouldWarnIgnoredPreviewLabel: () => shouldWarnIgnoredPreviewLabel
11979
12419
  });
11980
- import * as fs13 from "fs";
11981
- import * as path15 from "path";
12420
+ import * as fs15 from "fs";
12421
+ import * as path17 from "path";
11982
12422
  import * as yaml6 from "js-yaml";
11983
12423
  function parseArgs5(args2) {
11984
12424
  let autoConfirm = false;
@@ -12000,6 +12440,10 @@ function shouldWarnIgnoredPreviewLabel(localLabel, serverLabel) {
12000
12440
  return Boolean(localLabel) && serverLabel !== void 0 && localLabel !== serverLabel;
12001
12441
  }
12002
12442
  function printSyncResult(result) {
12443
+ if (result.errors && result.errors.length > 0) {
12444
+ printErrors(result.errors);
12445
+ throw new Error(`Sync failed with ${result.errors.length} error(s). No completion was reported; inspect the errors above.`);
12446
+ }
12003
12447
  console.log(`
12004
12448
  Sync complete. Preview hub: ${result.preview_hub_id}`);
12005
12449
  const c = result.changes;
@@ -12046,9 +12490,6 @@ Sync complete. Preview hub: ${result.preview_hub_id}`);
12046
12490
  if (parts.length > 0) {
12047
12491
  console.log(`Changes: ${parts.join(", ")}`);
12048
12492
  }
12049
- if (result.errors && result.errors.length > 0) {
12050
- printWarnings(result.errors);
12051
- }
12052
12493
  if (result.warnings && result.warnings.length > 0) {
12053
12494
  printWarnings(result.warnings.map((w) => w.message));
12054
12495
  }
@@ -12061,14 +12502,22 @@ function printWarnings(warnings) {
12061
12502
  console.log(` \x1B[33m! ${w}\x1B[0m`);
12062
12503
  }
12063
12504
  }
12505
+ function printErrors(errors) {
12506
+ if (errors.length === 0) return;
12507
+ console.error(`
12508
+ \x1B[31mErrors (${errors.length}):\x1B[0m`);
12509
+ for (const error of errors) {
12510
+ console.error(` \x1B[31m! ${error}\x1B[0m`);
12511
+ }
12512
+ }
12064
12513
  async function autoRenameAgentFiles(client, hubId, hubFolder, organizationId) {
12065
- const agentsDir = path15.join(hubFolder, "agents");
12514
+ const agentsDir = path17.join(hubFolder, "agents");
12066
12515
  let agentsWithIds = [];
12067
- if (fs13.existsSync(agentsDir)) {
12068
- const yamlFiles = fs13.readdirSync(agentsDir).filter((f) => f.endsWith(".yaml"));
12516
+ if (fs15.existsSync(agentsDir)) {
12517
+ const yamlFiles = fs15.readdirSync(agentsDir).filter((f) => f.endsWith(".yaml"));
12069
12518
  for (const file of yamlFiles) {
12070
12519
  try {
12071
- const content = fs13.readFileSync(path15.join(agentsDir, file), "utf-8");
12520
+ const content = fs15.readFileSync(path17.join(agentsDir, file), "utf-8");
12072
12521
  const agent = yaml6.load(content);
12073
12522
  if (agent?.id && agent.name) {
12074
12523
  agentsWithIds.push({ id: agent.id, name: agent.name });
@@ -12081,7 +12530,7 @@ async function autoRenameAgentFiles(client, hubId, hubFolder, organizationId) {
12081
12530
  if (agentsWithIds.length === 0) {
12082
12531
  const yamlPath = resolveHubYamlPath(hubFolder);
12083
12532
  if (!yamlPath) return;
12084
- const yamlContent = fs13.readFileSync(yamlPath, "utf-8");
12533
+ const yamlContent = fs15.readFileSync(yamlPath, "utf-8");
12085
12534
  const config = yaml6.load(yamlContent);
12086
12535
  agentsWithIds = (config.agents || []).filter((a) => !!a.id && !!a.name);
12087
12536
  }
@@ -12111,18 +12560,18 @@ async function autoRenameAgentFiles(client, hubId, hubFolder, organizationId) {
12111
12560
  }
12112
12561
  }
12113
12562
  if (renames.length === 0) return;
12114
- if (!fs13.existsSync(agentsDir)) return;
12115
- for (const file of fs13.readdirSync(agentsDir)) {
12563
+ if (!fs15.existsSync(agentsDir)) return;
12564
+ for (const file of fs15.readdirSync(agentsDir)) {
12116
12565
  if (file.startsWith("__rename_temp_") && (file.endsWith(".md") || file.endsWith(".yaml"))) {
12117
12566
  console.warn(` Warning: removing orphaned temp file agents/${file}`);
12118
- fs13.unlinkSync(path15.join(agentsDir, file));
12567
+ fs15.unlinkSync(path17.join(agentsDir, file));
12119
12568
  }
12120
12569
  }
12121
12570
  const renameFileIfExists = (dir, oldName, newName) => {
12122
- const oldPath = path15.join(dir, oldName);
12123
- const newPath = path15.join(dir, newName);
12124
- if (!fs13.existsSync(oldPath)) return false;
12125
- fs13.renameSync(oldPath, newPath);
12571
+ const oldPath = path17.join(dir, oldName);
12572
+ const newPath = path17.join(dir, newName);
12573
+ if (!fs15.existsSync(oldPath)) return false;
12574
+ fs15.renameSync(oldPath, newPath);
12126
12575
  return true;
12127
12576
  };
12128
12577
  const oldSlugs = new Set(renames.map((r) => r.oldSlug));
@@ -12155,9 +12604,9 @@ async function autoRenameAgentFiles(client, hubId, hubFolder, organizationId) {
12155
12604
  }
12156
12605
  } else {
12157
12606
  for (const { oldSlug, newSlug } of renames) {
12158
- const hasOldFile = extensions.some((ext) => fs13.existsSync(path15.join(agentsDir, `${oldSlug}${ext}`)));
12607
+ const hasOldFile = extensions.some((ext) => fs15.existsSync(path17.join(agentsDir, `${oldSlug}${ext}`)));
12159
12608
  if (!hasOldFile) continue;
12160
- const hasNewFile = extensions.some((ext) => fs13.existsSync(path15.join(agentsDir, `${newSlug}${ext}`)));
12609
+ const hasNewFile = extensions.some((ext) => fs15.existsSync(path17.join(agentsDir, `${newSlug}${ext}`)));
12161
12610
  if (hasNewFile) {
12162
12611
  console.warn(` Warning: skipping rename agents/${oldSlug}.* \u2192 agents/${newSlug}.* (target already exists)`);
12163
12612
  continue;
@@ -12172,7 +12621,7 @@ async function autoRenameAgentFiles(client, hubId, hubFolder, organizationId) {
12172
12621
  if (completedRenames.length === 0) return;
12173
12622
  const mainYamlPath = resolveHubYamlPath(hubFolder);
12174
12623
  if (mainYamlPath) {
12175
- const mainYamlContent = fs13.readFileSync(mainYamlPath, "utf-8");
12624
+ const mainYamlContent = fs15.readFileSync(mainYamlPath, "utf-8");
12176
12625
  const substitutionMap = /* @__PURE__ */ new Map();
12177
12626
  for (const { oldSlug, newSlug } of completedRenames) {
12178
12627
  substitutionMap.set(`agents/${oldSlug}.md`, `agents/${newSlug}.md`);
@@ -12183,8 +12632,8 @@ async function autoRenameAgentFiles(client, hubId, hubFolder, organizationId) {
12183
12632
  (_match, prefix, pathMatch) => `${prefix}${substitutionMap.get(pathMatch) ?? pathMatch}`
12184
12633
  );
12185
12634
  if (updatedYaml !== mainYamlContent) {
12186
- fs13.writeFileSync(mainYamlPath, updatedYaml, "utf-8");
12187
- console.log(` Updated instructions paths in ${path15.basename(mainYamlPath)}`);
12635
+ fs15.writeFileSync(mainYamlPath, updatedYaml, "utf-8");
12636
+ console.log(` Updated instructions paths in ${path17.basename(mainYamlPath)}`);
12188
12637
  }
12189
12638
  }
12190
12639
  }
@@ -12245,7 +12694,7 @@ async function pushSingleHub(client, hubId, hubFolder, autoConfirm, organization
12245
12694
  function selectExistingHub(workspaceDir, allHubs, selector, wsLabel) {
12246
12695
  if (selector) {
12247
12696
  const match = allHubs.find(
12248
- (h) => h.hubId === selector || path15.basename(h.hubFolder) === selector
12697
+ (h) => h.hubId === selector || path17.basename(h.hubFolder) === selector
12249
12698
  );
12250
12699
  if (!match) {
12251
12700
  console.error(`No hub matching --hub ${selector} found in ${wsLabel}/.`);
@@ -12288,7 +12737,7 @@ New hub: "${newHub.hubName}" (${hubType})`);
12288
12737
  process.exit(1);
12289
12738
  }
12290
12739
  console.log(`Hub created: ${createdHub.hub_name} (${hubId})`);
12291
- const content = fs13.readFileSync(hubYamlPath, "utf-8");
12740
+ const content = fs15.readFileSync(hubYamlPath, "utf-8");
12292
12741
  const hasVersion = content.match(/^version:\s/m);
12293
12742
  let updated;
12294
12743
  if (hasVersion) {
@@ -12304,7 +12753,7 @@ hub_id: "${hubId}"
12304
12753
  hub_environment: preview
12305
12754
  ${content}`;
12306
12755
  }
12307
- fs13.writeFileSync(hubYamlPath, updated, "utf-8");
12756
+ fs15.writeFileSync(hubYamlPath, updated, "utf-8");
12308
12757
  autoBindIfUnbound(hubId);
12309
12758
  await pushSingleHub(client, hubId, newHub.hubFolder, opts.autoConfirm, opts.organizationId, { skipAgentRename: true });
12310
12759
  }
@@ -12313,7 +12762,7 @@ function reportUnresolvedPushTarget(reason, existingHubs, newHubs, selector, wsL
12313
12762
  if (existingHubs.length > 0) {
12314
12763
  console.error(`No pushable hub found in ${wsLabel}/ \u2014 only read-only production mirror folder(s). Edit the linked preview hub, pull one, or create a new hub with \`wayai create\`:`);
12315
12764
  for (const h of existingHubs) {
12316
- console.error(` ${path15.basename(h.hubFolder)} (${h.hubId}, production mirror)`);
12765
+ console.error(` ${path17.basename(h.hubFolder)} (${h.hubId}, production mirror)`);
12317
12766
  }
12318
12767
  } else {
12319
12768
  console.error(`No hub folders found. Create hub files in ${wsLabel}/<hub>/hub.yaml with \`hub: { name: ... }\` and run \`wayai push\` (or \`wayai create\`).`);
@@ -12326,10 +12775,10 @@ function reportUnresolvedPushTarget(reason, existingHubs, newHubs, selector, wsL
12326
12775
  console.error(`Multiple hub folders found in ${wsLabel}/. Pass --hub <uuid|folder-name> to choose, or run from inside a hub folder:`);
12327
12776
  }
12328
12777
  for (const h of existingHubs) {
12329
- console.error(` ${path15.basename(h.hubFolder)} (${h.hubId})`);
12778
+ console.error(` ${path17.basename(h.hubFolder)} (${h.hubId})`);
12330
12779
  }
12331
12780
  for (const h of newHubs) {
12332
- console.error(` ${path15.basename(h.hubFolder)} (new \u2014 "${h.hubName}")`);
12781
+ console.error(` ${path17.basename(h.hubFolder)} (new \u2014 "${h.hubName}")`);
12333
12782
  }
12334
12783
  process.exit(1);
12335
12784
  }
@@ -12348,7 +12797,7 @@ async function pushCommand(args2) {
12348
12797
  console.error(`Created ${file}`);
12349
12798
  }
12350
12799
  }
12351
- if (!fs13.existsSync(workspaceDir)) {
12800
+ if (!fs15.existsSync(workspaceDir)) {
12352
12801
  console.error(`No ${wsLabel}/ directory found. Run \`wayai pull\` first or create hub files in ${wsLabel}/<hub>/hub.yaml.`);
12353
12802
  process.exit(1);
12354
12803
  }
@@ -12367,8 +12816,8 @@ async function pushCommand(args2) {
12367
12816
  if (label) {
12368
12817
  console.warn("Note: --label is only used when creating a new hub. To change an existing hub's label, use `wayai relabel`.");
12369
12818
  }
12370
- console.log(`Target hub: ${path15.basename(existing.hubFolder)} (${existing.hubId})`);
12371
- assertHubMatchesBinding(existing.hubId, path15.basename(existing.hubFolder));
12819
+ console.log(`Target hub: ${path17.basename(existing.hubFolder)} (${existing.hubId})`);
12820
+ assertHubMatchesBinding(existing.hubId, path17.basename(existing.hubFolder));
12372
12821
  await pushSingleHub(client, existing.hubId, existing.hubFolder, autoConfirm, organizationId);
12373
12822
  return;
12374
12823
  }
@@ -12398,8 +12847,8 @@ var create_exports = {};
12398
12847
  __export(create_exports, {
12399
12848
  createCommand: () => createCommand
12400
12849
  });
12401
- import * as path16 from "path";
12402
- import * as fs14 from "fs";
12850
+ import * as path18 from "path";
12851
+ import * as fs16 from "fs";
12403
12852
  function parseArgs6(args2) {
12404
12853
  let autoConfirm = false;
12405
12854
  let folderSelector;
@@ -12426,7 +12875,7 @@ async function createCommand(args2) {
12426
12875
  const gitRoot = findGitRoot();
12427
12876
  const wsLabel = hubsDirLabel(gitRoot);
12428
12877
  if (gitRoot) warnLayoutOnce(gitRoot);
12429
- if (!fs14.existsSync(workspaceDir)) {
12878
+ if (!fs16.existsSync(workspaceDir)) {
12430
12879
  console.error(`No ${wsLabel}/ directory found. Create hub files in ${wsLabel}/<hub>/hub.yaml with \`hub: { name: ... }\` first.`);
12431
12880
  process.exit(1);
12432
12881
  }
@@ -12435,7 +12884,7 @@ async function createCommand(args2) {
12435
12884
  const resolution = resolveNewHubForCreate(workspaceDir, existingHubs, newHubs, folderSelector);
12436
12885
  if (!resolution.ok) {
12437
12886
  if (resolution.reason === "exists") {
12438
- const folder = path16.basename(resolution.existing.hubFolder);
12887
+ const folder = path18.basename(resolution.existing.hubFolder);
12439
12888
  console.error(`Hub "${folder}" already exists (${resolution.existing.hubId}). Use \`wayai push --hub ${folder}\` to update it.`);
12440
12889
  process.exit(1);
12441
12890
  }
@@ -12449,7 +12898,7 @@ async function createCommand(args2) {
12449
12898
  }
12450
12899
  console.error(`Multiple new hub folders found in ${wsLabel}/. Pass the folder name (\`wayai create <folder>\`) or run from inside one:`);
12451
12900
  for (const h of newHubs) {
12452
- console.error(` ${path16.basename(h.hubFolder)} (new \u2014 "${h.hubName}")`);
12901
+ console.error(` ${path18.basename(h.hubFolder)} (new \u2014 "${h.hubName}")`);
12453
12902
  }
12454
12903
  process.exit(1);
12455
12904
  }
@@ -12631,12 +13080,12 @@ function typeMatches2(typeField, allowed) {
12631
13080
  if (Array.isArray(typeField)) return typeField.every((t) => typeof t === "string" && allowed.has(t));
12632
13081
  return false;
12633
13082
  }
12634
- function validateSchema2(schema, path28, errors, opts = {}) {
13083
+ function validateSchema2(schema, path30, errors, opts = {}) {
12635
13084
  if (typeof schema === "boolean") return;
12636
13085
  const depth = opts.depth ?? 0;
12637
13086
  if (depth > MAX_SCHEMA_DEPTH2) {
12638
13087
  errors.push({
12639
- path: path28 || "<root>",
13088
+ path: path30 || "<root>",
12640
13089
  message: `schema nesting exceeds ${MAX_SCHEMA_DEPTH2} levels`,
12641
13090
  suggestion: "Flatten the schema; LLM providers reject deeply nested tool input_schemas."
12642
13091
  });
@@ -12644,7 +13093,7 @@ function validateSchema2(schema, path28, errors, opts = {}) {
12644
13093
  }
12645
13094
  if (!isRecord2(schema)) {
12646
13095
  errors.push({
12647
- path: path28,
13096
+ path: path30,
12648
13097
  message: `expected object, got ${schema === null ? "null" : typeof schema}`
12649
13098
  });
12650
13099
  return;
@@ -12652,14 +13101,14 @@ function validateSchema2(schema, path28, errors, opts = {}) {
12652
13101
  if (opts.isRoot) {
12653
13102
  if ("type" in schema && schema.type !== "object") {
12654
13103
  errors.push({
12655
- path: path28 ? `${path28}.type` : "type",
13104
+ path: path30 ? `${path30}.type` : "type",
12656
13105
  message: `root type must be 'object', got ${JSON.stringify(schema.type)}`,
12657
13106
  suggestion: "Tool parameters at the root must be an object: `{ type: 'object', properties: { ... } }`."
12658
13107
  });
12659
13108
  }
12660
13109
  } else if ("type" in schema && !typeMatches2(schema.type, ALLOWED_TYPES2)) {
12661
13110
  errors.push({
12662
- path: `${path28}.type`,
13111
+ path: `${path30}.type`,
12663
13112
  message: `type must be one of ${[...ALLOWED_TYPES2].join("/")} or an array of those, got ${JSON.stringify(schema.type)}`
12664
13113
  });
12665
13114
  }
@@ -12668,25 +13117,25 @@ function validateSchema2(schema, path28, errors, opts = {}) {
12668
13117
  if (typeof e === "string") {
12669
13118
  const isPlaceholder = PLACEHOLDER_TOKENS2.includes(e);
12670
13119
  errors.push({
12671
- path: `${path28}.enum`,
13120
+ path: `${path30}.enum`,
12672
13121
  message: `enum must be a non-empty array of primitives, got string ${JSON.stringify(e)}`,
12673
13122
  suggestion: isPlaceholder ? "Set `operation: 'update_kanban_status'` on the tool and ensure at least one hub.kanban_status has `allowsAgentUpdate: true` so the platform can render the placeholder into a valid array." : 'Wrap the value in an array: `enum: ["value"]`.'
12674
13123
  });
12675
13124
  } else if (!Array.isArray(e)) {
12676
13125
  errors.push({
12677
- path: `${path28}.enum`,
13126
+ path: `${path30}.enum`,
12678
13127
  message: `enum must be a non-empty array of primitives, got ${typeof e}`
12679
13128
  });
12680
13129
  } else if (e.length === 0) {
12681
13130
  errors.push({
12682
- path: `${path28}.enum`,
13131
+ path: `${path30}.enum`,
12683
13132
  message: "enum must not be empty"
12684
13133
  });
12685
13134
  } else {
12686
13135
  for (let i = 0; i < e.length; i++) {
12687
13136
  if (!isPrimitive2(e[i])) {
12688
13137
  errors.push({
12689
- path: `${path28}.enum[${i}]`,
13138
+ path: `${path30}.enum[${i}]`,
12690
13139
  message: `enum entry must be a primitive (string/number/boolean/null), got ${typeof e[i]}`
12691
13140
  });
12692
13141
  }
@@ -12696,7 +13145,7 @@ function validateSchema2(schema, path28, errors, opts = {}) {
12696
13145
  for (const key of ["exclusiveMinimum", "exclusiveMaximum"]) {
12697
13146
  if (key in schema && typeof schema[key] === "boolean") {
12698
13147
  errors.push({
12699
- path: `${path28}.${key}`,
13148
+ path: `${path30}.${key}`,
12700
13149
  message: `${key} as boolean is draft-04 syntax; draft 2020-12 requires a numeric value`,
12701
13150
  suggestion: `Replace with the numeric bound, e.g. \`${key}: 0\`.`
12702
13151
  });
@@ -12705,45 +13154,45 @@ function validateSchema2(schema, path28, errors, opts = {}) {
12705
13154
  if ("properties" in schema) {
12706
13155
  if (!isRecord2(schema.properties)) {
12707
13156
  errors.push({
12708
- path: `${path28}.properties`,
13157
+ path: `${path30}.properties`,
12709
13158
  message: `properties must be an object, got ${Array.isArray(schema.properties) ? "array" : typeof schema.properties}`
12710
13159
  });
12711
13160
  } else {
12712
13161
  for (const [propName, propSchema] of Object.entries(schema.properties)) {
12713
- validateSchema2(propSchema, `${path28}.properties.${propName}`, errors, { depth: depth + 1 });
13162
+ validateSchema2(propSchema, `${path30}.properties.${propName}`, errors, { depth: depth + 1 });
12714
13163
  }
12715
13164
  }
12716
13165
  }
12717
13166
  if (schemaTypeIncludes2(schema, "array") && "items" in schema) {
12718
13167
  if (Array.isArray(schema.items)) {
12719
- schema.items.forEach((sub, i) => validateSchema2(sub, `${path28}.items[${i}]`, errors, { depth: depth + 1 }));
13168
+ schema.items.forEach((sub, i) => validateSchema2(sub, `${path30}.items[${i}]`, errors, { depth: depth + 1 }));
12720
13169
  } else {
12721
- validateSchema2(schema.items, `${path28}.items`, errors, { depth: depth + 1 });
13170
+ validateSchema2(schema.items, `${path30}.items`, errors, { depth: depth + 1 });
12722
13171
  }
12723
13172
  }
12724
13173
  for (const key of SUBSCHEMA_OBJECT_KEYWORDS2) {
12725
13174
  if (key in schema && isRecord2(schema[key])) {
12726
- validateSchema2(schema[key], `${path28}.${key}`, errors, { depth: depth + 1 });
13175
+ validateSchema2(schema[key], `${path30}.${key}`, errors, { depth: depth + 1 });
12727
13176
  }
12728
13177
  }
12729
13178
  for (const key of SUBSCHEMA_LIST_KEYWORDS2) {
12730
13179
  const list = schema[key];
12731
13180
  if (Array.isArray(list)) {
12732
- list.forEach((sub, i) => validateSchema2(sub, `${path28}.${key}[${i}]`, errors, { depth: depth + 1 }));
13181
+ list.forEach((sub, i) => validateSchema2(sub, `${path30}.${key}[${i}]`, errors, { depth: depth + 1 }));
12733
13182
  }
12734
13183
  }
12735
13184
  for (const key of SUBSCHEMA_MAP_KEYWORDS2) {
12736
13185
  const map = schema[key];
12737
13186
  if (isRecord2(map)) {
12738
13187
  for (const [name, sub] of Object.entries(map)) {
12739
- validateSchema2(sub, `${path28}.${key}.${name}`, errors, { depth: depth + 1 });
13188
+ validateSchema2(sub, `${path30}.${key}.${name}`, errors, { depth: depth + 1 });
12740
13189
  }
12741
13190
  }
12742
13191
  }
12743
13192
  const reportedPaths = new Set(errors.map((e) => e.path));
12744
13193
  for (const [k, v] of Object.entries(schema)) {
12745
13194
  if (typeof v !== "string") continue;
12746
- const fieldPath = `${path28}.${k}`;
13195
+ const fieldPath = `${path30}.${k}`;
12747
13196
  if (reportedPaths.has(fieldPath)) continue;
12748
13197
  for (const token of PLACEHOLDER_TOKENS2) {
12749
13198
  if (v === token) {
@@ -12818,15 +13267,17 @@ function describeRole2(role) {
12818
13267
  return `a ${typeof role}`;
12819
13268
  }
12820
13269
  function asEvalInputRecord2(input) {
12821
- const value = typeof input === "string" ? (() => {
12822
- try {
12823
- return JSON.parse(input);
12824
- } catch {
12825
- return null;
12826
- }
12827
- })() : input;
13270
+ const value = parseMaybeJson2(input);
12828
13271
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
12829
13272
  }
13273
+ function parseMaybeJson2(value) {
13274
+ if (typeof value !== "string") return value;
13275
+ try {
13276
+ return JSON.parse(value);
13277
+ } catch {
13278
+ return null;
13279
+ }
13280
+ }
12830
13281
  function evalInputRoleError2(input) {
12831
13282
  const record = asEvalInputRecord2(input);
12832
13283
  if (record === null) return null;
@@ -12837,6 +13288,32 @@ function evalInputRoleError2(input) {
12837
13288
  }
12838
13289
  return `role must be ${EVAL_INPUT_ROLE_LIST2} (received ${describeRole2(role)}) \u2014 an eval trigger is what the responder answers, and an assistant or tool role gives it nothing to answer`;
12839
13290
  }
13291
+ function evalTurnAttachmentsError2(turn) {
13292
+ const record = asEvalInputRecord2(turn);
13293
+ if (record === null) return null;
13294
+ const attachments = record.attachments;
13295
+ if (attachments === void 0 || attachments === null) return null;
13296
+ if (!Array.isArray(attachments)) return "attachments must be an array";
13297
+ if (attachments.length === 0) return null;
13298
+ if (attachments.length > MAX_ATTACHMENTS_PER_MESSAGE2) {
13299
+ return `attachments must have at most ${MAX_ATTACHMENTS_PER_MESSAGE2} items`;
13300
+ }
13301
+ if (!turnMayCarryAttachments2({ role: String(record.role), attachments })) {
13302
+ return ATTACHMENTS_USER_ONLY_MESSAGE2;
13303
+ }
13304
+ for (let i = 0; i < attachments.length; i++) {
13305
+ const parsed = evalAttachmentRef2.safeParse(attachments[i]);
13306
+ if (!parsed.success) {
13307
+ const issue = parsed.error.issues[0];
13308
+ return `attachments[${i}].${issue?.path.join(".") || "?"} is invalid: ${issue?.message ?? "malformed"}`;
13309
+ }
13310
+ }
13311
+ return null;
13312
+ }
13313
+ function asEvalHistoryArray2(history) {
13314
+ const value = parseMaybeJson2(history);
13315
+ return Array.isArray(value) ? value : null;
13316
+ }
12840
13317
  function refineEvalMessageTextRole2(value, ctx) {
12841
13318
  const error = evalInputRoleError2(value.eval.message_text);
12842
13319
  if (error) {
@@ -12847,6 +13324,38 @@ function refineEvalMessageTextRole2(value, ctx) {
12847
13324
  });
12848
13325
  }
12849
13326
  }
13327
+ function refineEvalAttachments2(value, ctx) {
13328
+ const triggerError = evalTurnAttachmentsError2(value.eval.message_text);
13329
+ if (triggerError) {
13330
+ ctx.addIssue({
13331
+ code: external_exports.ZodIssueCode.custom,
13332
+ path: ["eval", "message_text", "attachments"],
13333
+ message: triggerError
13334
+ });
13335
+ }
13336
+ const history = asEvalHistoryArray2(value.eval.message_history);
13337
+ if (!history) return;
13338
+ for (let i = 0; i < history.length; i++) {
13339
+ const error = evalTurnAttachmentsError2(history[i]);
13340
+ if (error) {
13341
+ ctx.addIssue({
13342
+ code: external_exports.ZodIssueCode.custom,
13343
+ path: ["eval", "message_history", i, "attachments"],
13344
+ message: error
13345
+ });
13346
+ }
13347
+ }
13348
+ }
13349
+ function collectTurnAttachmentHashes2(turn) {
13350
+ const atts = asEvalInputRecord2(turn)?.attachments;
13351
+ if (!Array.isArray(atts)) return [];
13352
+ const hashes = [];
13353
+ for (const a of atts) {
13354
+ const parsed = evalAttachmentRef2.safeParse(a);
13355
+ if (parsed.success) hashes.push(parsed.data.hash);
13356
+ }
13357
+ return hashes;
13358
+ }
12850
13359
  function refineHubAsCodeCustomTools2(config, ctx) {
12851
13360
  const agents = config.agents;
12852
13361
  if (!Array.isArray(agents)) return;
@@ -12954,10 +13463,84 @@ function refineHubAsCodeEvals2(config, ctx) {
12954
13463
  }
12955
13464
  }
12956
13465
  }
12957
- function isSafeTemplatePath2(path28) {
12958
- if (path28.length === 0 || path28.length > 300) return false;
12959
- if (path28.startsWith("/") || path28.includes("\\")) return false;
12960
- const segments = path28.split("/");
13466
+ function refineHubAsCodeEvalAttachments2(config, ctx) {
13467
+ const cfg = config;
13468
+ const referencedHashes = /* @__PURE__ */ new Set();
13469
+ const addTurnIssue = (path30, turn, name) => {
13470
+ const error = evalTurnAttachmentsError2(turn);
13471
+ if (error) {
13472
+ ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: path30, message: `${name}: ${error}` });
13473
+ return;
13474
+ }
13475
+ for (const hash of collectTurnAttachmentHashes2(turn)) referencedHashes.add(hash);
13476
+ };
13477
+ if (Array.isArray(cfg.evals)) {
13478
+ cfg.evals.forEach((entry, e) => {
13479
+ const ev = entry;
13480
+ const name = `eval "${ev?.name ?? "<unnamed>"}"`;
13481
+ addTurnIssue(["evals", e, "input"], ev?.input, name);
13482
+ if (Array.isArray(ev?.history)) {
13483
+ ev.history.forEach((turn, h) => addTurnIssue(["evals", e, "history", h], turn, name));
13484
+ }
13485
+ });
13486
+ }
13487
+ if (Array.isArray(cfg.journeys)) {
13488
+ cfg.journeys.forEach((entry, j) => {
13489
+ const jr = entry;
13490
+ const name = `journey "${jr?.name ?? "<unnamed>"}"`;
13491
+ if (Array.isArray(jr?.transcript)) {
13492
+ jr.transcript.forEach((turn, t) => addTurnIssue(["journeys", j, "transcript", t], turn, name));
13493
+ }
13494
+ });
13495
+ }
13496
+ const carriedHashes = /* @__PURE__ */ new Set();
13497
+ const files = cfg.eval_attachment_files;
13498
+ if (Array.isArray(files)) {
13499
+ if (files.length > MAX_EVAL_ATTACHMENT_CARRIERS2) {
13500
+ ctx.addIssue({
13501
+ code: external_exports.ZodIssueCode.custom,
13502
+ path: ["eval_attachment_files"],
13503
+ message: `too many attachment carriers (${files.length}); the maximum is ${MAX_EVAL_ATTACHMENT_CARRIERS2}`
13504
+ });
13505
+ }
13506
+ let aggregateEncoded = 0;
13507
+ files.forEach((f, i) => {
13508
+ const h = f?.hash;
13509
+ if (typeof h === "string") carriedHashes.add(h);
13510
+ const b64 = f?.content_base64;
13511
+ if (typeof b64 === "string") {
13512
+ aggregateEncoded += b64.length;
13513
+ if (b64.length > MAX_EVAL_ATTACHMENT_ENCODED_BYTES2) {
13514
+ ctx.addIssue({
13515
+ code: external_exports.ZodIssueCode.custom,
13516
+ path: ["eval_attachment_files", i, "content_base64"],
13517
+ message: `attachment ${typeof h === "string" ? `${h.slice(0, 12)}\u2026 ` : ""}exceeds the ${(MAX_RESOURCE_FILE_SIZE3 / 1024 / 1024).toFixed(0)}MB size limit`
13518
+ });
13519
+ }
13520
+ }
13521
+ });
13522
+ if (aggregateEncoded > MAX_EVAL_ATTACHMENT_AGGREGATE_ENCODED_BYTES2) {
13523
+ ctx.addIssue({
13524
+ code: external_exports.ZodIssueCode.custom,
13525
+ path: ["eval_attachment_files"],
13526
+ message: `total attachment bytes exceed the ${(MAX_EVAL_ATTACHMENT_AGGREGATE_ENCODED_BYTES2 / 1024 / 1024).toFixed(0)}MB per-push limit \u2014 split the push or reduce attachments`
13527
+ });
13528
+ }
13529
+ }
13530
+ for (const hash of referencedHashes) {
13531
+ if (!carriedHashes.has(hash)) {
13532
+ ctx.addIssue({
13533
+ code: external_exports.ZodIssueCode.custom,
13534
+ path: ["eval_attachment_files"],
13535
+ message: `attachment ${hash.slice(0, 12)}\u2026 is referenced by a turn but its bytes are missing from eval_attachment_files`
13536
+ });
13537
+ }
13538
+ }
13539
+ }
13540
+ function isSafeTemplatePath2(path30) {
13541
+ if (path30.length === 0 || path30.length > 300) return false;
13542
+ if (path30.startsWith("/") || path30.includes("\\")) return false;
13543
+ const segments = path30.split("/");
12961
13544
  return segments.every(
12962
13545
  (seg) => seg.length > 0 && seg !== "." && seg !== ".." && /^[A-Za-z0-9._-]+$/.test(seg)
12963
13546
  );
@@ -12985,14 +13568,14 @@ function isPlainObject(value) {
12985
13568
  const proto = Object.getPrototypeOf(value);
12986
13569
  return proto === Object.prototype || proto === null;
12987
13570
  }
12988
- function isSanitizableSliceFile(path28) {
12989
- if (!/\.ya?ml$/i.test(path28)) return false;
12990
- return path28 !== "resources" && !path28.startsWith("resources/");
13571
+ function isSanitizableSliceFile(path30) {
13572
+ if (!/\.ya?ml$/i.test(path30)) return false;
13573
+ return path30 !== "resources" && !path30.startsWith("resources/");
12991
13574
  }
12992
13575
  function distinctUsesBackends(uses) {
12993
13576
  return [...new Set((uses ?? []).map((u) => u.backend))];
12994
13577
  }
12995
- var uuidSchema2, paginationSchema3, timestampSchema2, idParamSchema2, clientOptionalString2, clientOptionalBoolean2, clientOptionalNumber2, messageAttachment2, MAX_MESSAGE_BODY_BYTES2, MAX_ATTACHMENTS_PER_MESSAGE2, primaryRegionSchema2, placementSourceSchema2, orgIdParam2, orgAdminIdParam2, createOrganizationBody2, updateOrganizationBody2, addOrgAdminBody2, AUTH_TYPE3, ORG_CREDENTIAL_AUTH_TYPES3, AUTH_TYPE_DISPLAY3, LEGACY_AUTH_TYPE_MAP3, VALID_AUTH_TYPES3, AGENT_ROLES2, SAFE_USER_ID_REGEX2, SUPPORTED_LOCALES2, SUMMARIZATION_THRESHOLD_MIN2, SUMMARIZATION_THRESHOLD_MAX2, summarizationThresholdField2, monitorConfigField2, agentIdParam2, listAgentsQuery2, agentConnectionsQuery2, createAgentBody2, updateAgentBody2, AGENT_PARAMETER_TYPES2, AGENT_PARAMETER_NAME_REGEX2, agentParameterCreateSchema2, MAX_AGENT_PARAMETERS_PER_REQUEST2, createAgentParametersBody2, SLUG_REGEX2, slugSchema2, INVISIBLE_NAME_CHARS2, MAX_KANBAN_STATUSES2, MAX_FOLLOWUPS_PER_STATUS2, MAX_LANES2, MAX_ADDITIONAL_CONTEXT_SCHEMA_BYTES2, RESERVED_TEMPLATE_DUMP_NAME2, kanbanStatusSlugSchema2, followupSchema2, EXCLUSIVE_FLAG_PAIRS2, kanbanStatusSchema2, kanbanStatusesArraySchema2, laneSchema2, lanesArraySchema2, hubIdParam2, hubAdminIdParam2, hubUserIdParam2, hubIdentityIdParam2, hubTeamIdParam2, hubTeamUserDeleteParam2, hubSchemaQuery2, PREVIEW_LABEL_MAX_LENGTH2, MAX_ENDED_INDEX_RETENTION_DAYS2, previewLabelField2, createHubBody2, updateHubBody2, addHubAdminBody2, addHubIdentityAuthorizationBody2, contactDecisionBody2, approveContactBody2, listContactAccessQuery2, createHubTeamBody2, updateHubTeamBody2, addHubTeamUserBody2, reassignHubUserBody2, replicatePreviewBody2, updateChannelTestIdentitiesBody2, claimCodeChannelParam2, claimCodeDeleteParam2, connectionIdParam2, connectorIdParam2, listConnectionsQuery2, deleteConnectionQuery2, addConnectionBody2, editConnectionBody2, PRODUCTION_DIRECT_FIELDS2, setProductionCredentialBody2, registerWhatsappQuery2, registerWhatsappBody2, retryProvisioningQuery2, resendDomainStatusQuery2, PLACEHOLDER_TOKENS2, ALLOWED_TYPES2, SUBSCHEMA_OBJECT_KEYWORDS2, SUBSCHEMA_LIST_KEYWORDS2, SUBSCHEMA_MAP_KEYWORDS2, MAX_SCHEMA_DEPTH2, toolIdParam2, listToolsQuery2, toolAgentQuery2, deleteToolQuery2, toolConnectionsQuery2, nativeToolIdSchema2, addNativeToolBody2, addMcpToolBody2, createCustomToolBody2, updateCustomToolBody2, updateToolExecutionConfigBody2, initialValueCreateSchema2, initialValueUpdateSchema2, stateIdParam2, listStatesQuery2, stateNameSchema2, createStateBody2, updateStateBody2, reorderStatesBody2, variantAiModeSchema2, experimentStatusSchema2, overlayUpdateSchema2, experimentIdParam2, variantIdParam2, listExperimentsQuery2, listVariantsQuery2, listOverridesQuery2, deleteOverrideQuery2, createExperimentBody2, updateExperimentBody2, setExperimentStatusBody2, createVariantBody2, updateVariantBody2, upsertOverrideBody2, orgTagIdParam2, listOrgTagsQuery2, createOrgTagBody2, updateOrgTagBody2, orgCredentialIdParam2, listOrgCredentialsQuery2, createOrgCredentialBody2, updateOrgCredentialBody2, orgResourceIdParam2, orgResourceFileIdParam2, orgResourceFolderIdParam2, hubOrgResourceParam2, listOrgResourcesQuery2, orgIdQuery2, createOrgResourceBody2, updateOrgResourceBody2, createOrgResourceFolderBody2, updateOrgResourceFolderBody2, uploadOrgResourceFileBody2, updateOrgResourceFileBody2, uploadOrgSkillZipBody2, linkOrgResourceBody2, resourceIdParam2, listResourcesQuery2, createResourceBody2, updateResourceBody2, syncSkillsBody2, resourceFileIdParam2, listResourceFilesQuery2, createResourceFileBody2, updateResourceFileBody2, uploadResourceFileBody2, uploadSkillZipBody2, resourceFolderIdParam2, listResourceFoldersQuery2, createResourceFolderBody2, updateResourceFolderBody2, agentResourceQuery2, hubResourceQuery2, linkAgentResourceBody2, updateAgentResourceBody2, unlinkAgentResourceQuery2, navItemSchema2, hubCountResetBody2, navItemCountResetBody2, typingBody2, analyticsHubIdParam2, analyticsVariableIdParam2, updateVariablePinBody2, updateAnalyticsViewBody2, NUMERIC_FILTER_OPS2, TEXT_FILTER_OPS2, CATEGORICAL_FILTER_OPS2, VARIABLE_FILTER_OPS2, STRUCTURED_QUERY_OPS2, STRUCTURED_QUERY_AGGREGATIONS2, filterValueSchema2, conversationsBody2, analyticsConversationIdParam2, messagesQuery2, conversationDetailDataQuery2, analyticsDataBody2, structuredQueryBody2, analyticsSqlBody2, analyticsSqlSchemaQuery2, EVAL_PACING_PRESET_NAMES2, PACING_INTERVAL_MIN_MS2, PACING_INTERVAL_MAX_MS2, EVAL_RUN_DEADLINE_MIN_MS2, EVAL_RUN_DEADLINE_MAX_MS2, paginationSchema22, evalIdParam2, sessionIdParam2, scenarioSetIdParam2, hubIdQuery2, EVAL_INPUT_ROLES2, EVAL_INPUT_ROLE_LIST2, ROLE_ECHO_MAX2, createEvalBody2, updateEvalBody2, getEvalsQuery2, toggleEvalBody2, bulkToggleEvalsBody2, pacingConfigSchema2, sessionConfigSchema2, createSessionBody2, getSessionsQuery2, getSessionResultsQuery2, getSessionRunsQuery2, evalAnalyticsQuery2, getSessionCostQuery2, compareSessionsQuery2, evalsSqlBody2, evalsSqlSchemaQuery2, exportResultsQuery2, validateEvalBody2, hubAgentsQuery2, createScenarioSetBody2, updateScenarioSetBody2, scenarioSetsHubQuery2, createScenarioFromConversationBody2, createJourneyFromConversationBody2, journeyIdParam2, journeyToolCallSchema2, journeyTurnSchema2, journeyStepOverrideSchema2, fixtureField2, createJourneyBody2, updateJourneyBody2, getConversationsQuery2, getMessagesQuery2, appMessageSenderType2, appMessageReceiverType2, appMessageBody2, updateUserBody2, updateProfileBody2, pathnameRegex2, updatePreferencesBody2, registerPushTokenBody2, deletePushTokenQuery2, activityQuery2, deletionModeSchema2, deletionRequestBody2, archiveHubIdParam2, archiveConversationParams2, archiveListQuery2, archiveMessageObservabilityParams2, conversationIdParam2, claimBody2, releaseBody2, transferBody2, closeBody2, additionalContextPayloadSchema2, kanbanUpdateBody2, annotateConversationBody2, observabilityParams2, observabilityListParams2, deleteUserConversationsBody2, sendMessageBody2, listConversationsQuery2, getConversationDetailParams2, getConversationDetailQuery2, listWhatsAppTemplatesQuery2, sendWhatsAppTemplateBody2, billingOrgIdParam2, subscriptionItemParams2, spendCapBody2, growthPacksBody2, portalSessionBody2, checkoutSessionBody2, updateBillingCurrencyBody2, invoicesQuery2, downloadBody2, uploadQuery2, signUrlBody2, outboundSchemaQuery2, templateIdParam2, listTemplatesQuery2, deleteTemplateQuery2, templateStatusQuery2, createTemplateBody2, updateTemplateBody2, submitTemplateBody2, testTemplateBody2, contactIdParam2, listContactsQuery2, createContactBody2, updateContactBody2, listIdParam2, listListsQuery2, listListContactsQuery2, createListBody2, updateListBody2, addListContactsBody2, removeListContactsBody2, scheduleIdParam2, listSchedulesQuery2, listExecutionsQuery2, createScheduleBody2, updateScheduleBody2, uuidPattern2, ciUuidSchema2, hubAsCodeConfigSchema2, ciPullHubIdParam2, ciBranchParam2, ciPullQuery2, ciHubsQuery2, ciLookupQuery2, ciBranchesQuery2, ciSyncBody2, ciPushBody2, ciDiffBody2, ciPublishBody2, ciPublishPreviewBody2, ciSyncMcpBody2, ciOrgResourcesParam2, orgResourcesConfigSchema2, ciOrgResourcesPushBody2, ciOrgResourcesDiffBody2, adminOrgIdParam2, adminOrgAdminIdParam2, adminUserIdParam2, adminPlanIdParam2, adminHubIdParam2, adminOrganizationsQuery2, adminUserSearchQuery2, kanbanSlugMigrationQuery2, adjustQuotaBody2, updatePlanBody2, addAdminUserBody2, updatePlatformConfigBody2, updateFreeOrgLimitBody2, adminSetOrgHarnessBody2, adminSetHubHarnessBody2, adminScopedTargetBody2, updatePricingPlanBody2, dataExplorerSearchQuery2, dataExplorerOrgIdParam2, dataExplorerHubIdParam2, dataExplorerConvIdParam2, dataExplorerDoInstancesQuery2, dataExplorerNsIdParam2, dataExplorerKvKeysQuery2, dataExplorerKvKeyParam2, uuidOrHexId2, dataExplorerDeleteOrgParam2, dataExplorerDeleteHubParam2, dataExplorerDeleteConvParam2, dataExplorerDeleteUserParam2, dataExplorerDeleteQuery2, dataExplorerKvDeleteQuery2, pitrDoNamespace2, pitrBookmarkParam2, pitrRestoreBody2, healthSamplingTriggerBody2, dataExplorerDebugDoType2, DEBUG_READ_MAX_LIMIT2, DEBUG_READ_DEFAULT_LIMIT2, DEBUG_DO_HEX_PREFIX2, debugDoEntityId2, debugTableName2, dataExplorerDebugTablesParam2, dataExplorerDebugRowsParam2, dataExplorerDebugRowsQuery2, dataExplorerDebugAnalyticsTable2, debugUuid2, dataExplorerDebugAnalyticsRowsParam2, dataExplorerDebugAnalyticsRowsQuery2, debugHubConvParam2, debugMessageIdQuery2, sandboxEgressPolicy2, SANDBOX_EXEC_MAX_CMD_LENGTH2, SANDBOX_EXEC_MAX_ALLOWLIST2, SANDBOX_EXEC_MAX_TIMEOUT_MS2, adminSandboxExecBody2, whatsappCallbackBody2, authMeResponse2, wsTicketResponse2, sessionCheckResponse2, logoutResponse2, magicCodeSendBody2, magicCodeSendResponse2, magicCodeVerifyBody2, magicCodeVerifyResponse2, browserParams2, browserFoldersQuery2, browserFilesQuery2, stateOperationBody2, conversationListResourcesQuery2, conversationReadResourceQuery2, listPendingSchedulesQuery2, listHubAggregateSchedulesQuery2, reportSourceSchema2, intakeReportSourceSchema2, reportClassificationSchema2, reportStatusSchema2, reportLocaleSchema2, reportMessageAuthorRoleSchema2, sentryRefStatusSchema2, createReportBody2, editReportBody2, contestReportBody2, reporterListQuery2, listReportsQuery2, GITHUB_ISSUE_URL_REGEX2, githubIssueUrlSchema2, transitionReportBody2, groupReportsBody2, holdReportBody2, clickhouseReadyResponse2, bootstrapQuery2, requestSnapshotChunkMessage2, MAX_NAME_LENGTH3, MAX_VALUE_LENGTH2, MAX_TAGS2, MAX_TAG_LENGTH2, MAX_SCOPE_HUBS2, MAX_SCOPE_TAGS2, vaultSecretScopeSchema2, vaultCreateBody2, vaultUpdateBody2, PERMISSIONS2, MAX_NAME_LENGTH22, MAX_HUBS_PER_GRANT2, MAX_HUB_TAGS_PER_GRANT2, MAX_GRANTS2, permissionEnum2, grantScopeSchema2, grantSchema2, createTokenBody2, tokenOrgIdParam2, tokenGrantScopeSchema2, tokenGrantSchema2, tokenMetadataSchema2, listTokensResponse2, createTokenResponse2, orgTokenSummarySchema2, listOrgTokensResponse2, invitePreviewQuery2, invitePreviewResponse2, discordTier2, discordBillingCycle2, discordMetadata2, discordOAuthStartResponse2, discordOAuthFinalizeBody2, discordOAuthFinalizeResponse2, discordDisconnectResponse2, jsonSchemaSchema2, hubUserIdentity2, stateValueEntry2, getHubUserContextResponse2, updateHubUserBody2, updateHubUserResponse2, unlockHubUserNameResponse2, stateResetParams2, stateResetResponse2, hubAlertsParam2, hubAlertSeverity2, hubAlert2, hubAlertsListResponse2, noticeSeveritySchema2, noticeStatusSchema2, NOTICE_SCOPE_REGEX2, noticeScopeSchema2, noticeLinkSchema2, createNoticeBody2, updateNoticeBody2, listNoticesQuery2, noticeIdParamSchema2, templateLocaleSchema2, TEMPLATE_SLUG_REGEX2, templateSlugSchema2, templateStatusSchema2, templateFileMapSchema2, TEMPLATE_INSTANCE_ID_KEYS, TEMPLATE_OPAQUE_BLOB_KEYS, TEMPLATE_AUTHOR_DATA_KEYS, templateTextSchema2, templateUsesEntrySchema2, templateUsesSchema2, templateYamlLocaleEntry2, templateYamlWayaiBlock2, templateTagsSchema2, templateYamlSchema2, templateHeroSchema2, templatePushLocaleSchema2, templatePushBody2, templateListQuery2, templateSlugParam2, templateDetailQuery2, ADMIN_SKILL_NAME_REGEX2, adminSkillNameParam2;
13578
+ var uuidSchema2, paginationSchema3, timestampSchema2, idParamSchema2, clientOptionalString2, clientOptionalBoolean2, clientOptionalNumber2, messageAttachment2, MAX_MESSAGE_BODY_BYTES2, MAX_ATTACHMENTS_PER_MESSAGE2, primaryRegionSchema2, placementSourceSchema2, orgIdParam2, orgAdminIdParam2, createOrganizationBody2, updateOrganizationBody2, addOrgAdminBody2, AUTH_TYPE3, ORG_CREDENTIAL_AUTH_TYPES3, AUTH_TYPE_DISPLAY3, LEGACY_AUTH_TYPE_MAP3, VALID_AUTH_TYPES3, AGENT_ROLES2, SAFE_USER_ID_REGEX2, SUPPORTED_LOCALES2, SUMMARIZATION_THRESHOLD_MIN2, SUMMARIZATION_THRESHOLD_MAX2, summarizationThresholdField2, monitorConfigField2, agentIdParam2, listAgentsQuery2, agentConnectionsQuery2, createAgentBody2, updateAgentBody2, AGENT_PARAMETER_TYPES2, AGENT_PARAMETER_NAME_REGEX2, agentParameterCreateSchema2, MAX_AGENT_PARAMETERS_PER_REQUEST2, createAgentParametersBody2, SLUG_REGEX2, slugSchema2, INVISIBLE_NAME_CHARS2, MAX_KANBAN_STATUSES2, MAX_FOLLOWUPS_PER_STATUS2, MAX_LANES2, MAX_ADDITIONAL_CONTEXT_SCHEMA_BYTES2, RESERVED_TEMPLATE_DUMP_NAME2, kanbanStatusSlugSchema2, followupSchema2, EXCLUSIVE_FLAG_PAIRS2, kanbanStatusSchema2, kanbanStatusesArraySchema2, laneSchema2, lanesArraySchema2, hubIdParam2, hubAdminIdParam2, hubUserIdParam2, hubIdentityIdParam2, hubTeamIdParam2, hubTeamUserDeleteParam2, hubSchemaQuery2, PREVIEW_LABEL_MAX_LENGTH2, MAX_ENDED_INDEX_RETENTION_DAYS2, previewLabelField2, createHubBody2, updateHubBody2, addHubAdminBody2, addHubIdentityAuthorizationBody2, contactDecisionBody2, approveContactBody2, listContactAccessQuery2, createHubTeamBody2, updateHubTeamBody2, addHubTeamUserBody2, reassignHubUserBody2, replicatePreviewBody2, updateChannelTestIdentitiesBody2, claimCodeChannelParam2, claimCodeDeleteParam2, connectionIdParam2, connectorIdParam2, listConnectionsQuery2, deleteConnectionQuery2, addConnectionBody2, editConnectionBody2, PRODUCTION_DIRECT_FIELDS2, setProductionCredentialBody2, registerWhatsappQuery2, registerWhatsappBody2, retryProvisioningQuery2, resendDomainStatusQuery2, PLACEHOLDER_TOKENS2, ALLOWED_TYPES2, SUBSCHEMA_OBJECT_KEYWORDS2, SUBSCHEMA_LIST_KEYWORDS2, SUBSCHEMA_MAP_KEYWORDS2, MAX_SCHEMA_DEPTH2, toolIdParam2, listToolsQuery2, toolAgentQuery2, deleteToolQuery2, toolConnectionsQuery2, nativeToolIdSchema2, addNativeToolBody2, addMcpToolBody2, createCustomToolBody2, updateCustomToolBody2, updateToolExecutionConfigBody2, initialValueCreateSchema2, initialValueUpdateSchema2, stateIdParam2, listStatesQuery2, stateNameSchema2, createStateBody2, updateStateBody2, reorderStatesBody2, variantAiModeSchema2, experimentStatusSchema2, overlayUpdateSchema2, experimentIdParam2, variantIdParam2, listExperimentsQuery2, listVariantsQuery2, listOverridesQuery2, deleteOverrideQuery2, createExperimentBody2, updateExperimentBody2, setExperimentStatusBody2, createVariantBody2, updateVariantBody2, upsertOverrideBody2, orgTagIdParam2, listOrgTagsQuery2, createOrgTagBody2, updateOrgTagBody2, orgCredentialIdParam2, listOrgCredentialsQuery2, createOrgCredentialBody2, updateOrgCredentialBody2, orgResourceIdParam2, orgResourceFileIdParam2, orgResourceFolderIdParam2, hubOrgResourceParam2, listOrgResourcesQuery2, orgIdQuery2, createOrgResourceBody2, updateOrgResourceBody2, createOrgResourceFolderBody2, updateOrgResourceFolderBody2, uploadOrgResourceFileBody2, updateOrgResourceFileBody2, uploadOrgSkillZipBody2, linkOrgResourceBody2, resourceIdParam2, listResourcesQuery2, createResourceBody2, updateResourceBody2, syncSkillsBody2, resourceFileIdParam2, listResourceFilesQuery2, createResourceFileBody2, updateResourceFileBody2, uploadResourceFileBody2, uploadSkillZipBody2, resourceFolderIdParam2, listResourceFoldersQuery2, createResourceFolderBody2, updateResourceFolderBody2, agentResourceQuery2, hubResourceQuery2, linkAgentResourceBody2, updateAgentResourceBody2, unlinkAgentResourceQuery2, navItemSchema2, hubCountResetBody2, navItemCountResetBody2, typingBody2, analyticsHubIdParam2, analyticsVariableIdParam2, updateVariablePinBody2, updateAnalyticsViewBody2, NUMERIC_FILTER_OPS2, TEXT_FILTER_OPS2, CATEGORICAL_FILTER_OPS2, VARIABLE_FILTER_OPS2, STRUCTURED_QUERY_OPS2, STRUCTURED_QUERY_AGGREGATIONS2, filterValueSchema2, conversationsBody2, analyticsConversationIdParam2, messagesQuery2, conversationDetailDataQuery2, analyticsDataBody2, structuredQueryBody2, analyticsSqlBody2, analyticsSqlSchemaQuery2, EVAL_PACING_PRESET_NAMES2, PACING_INTERVAL_MIN_MS2, PACING_INTERVAL_MAX_MS2, EVAL_RUN_DEADLINE_MIN_MS2, EVAL_RUN_DEADLINE_MAX_MS2, paginationSchema22, evalIdParam2, sessionIdParam2, scenarioSetIdParam2, hubIdQuery2, EVAL_INPUT_ROLES2, EVAL_INPUT_ROLE_LIST2, ROLE_ECHO_MAX2, createEvalBody2, updateEvalBody2, getEvalsQuery2, toggleEvalBody2, bulkToggleEvalsBody2, pacingConfigSchema2, sessionConfigSchema2, createSessionBody2, getSessionsQuery2, getSessionResultsQuery2, getSessionRunsQuery2, evalAnalyticsQuery2, getSessionCostQuery2, compareSessionsQuery2, evalsSqlBody2, evalsSqlSchemaQuery2, exportResultsQuery2, validateEvalBody2, hubAgentsQuery2, createScenarioSetBody2, updateScenarioSetBody2, scenarioSetsHubQuery2, createScenarioFromConversationBody2, createJourneyFromConversationBody2, EVAL_ATTACHMENT_HASH_REGEX2, evalAttachmentRef2, turnMayCarryAttachments2, ATTACHMENTS_USER_ONLY_MESSAGE2, journeyIdParam2, journeyToolCallSchema2, journeyTurnSchema2, journeyStepOverrideSchema2, fixtureField2, createJourneyBody2, updateJourneyBody2, getConversationsQuery2, getMessagesQuery2, appMessageSenderType2, appMessageReceiverType2, appMessageBody2, updateUserBody2, updateProfileBody2, pathnameRegex2, updatePreferencesBody2, registerPushTokenBody2, deletePushTokenQuery2, activityQuery2, deletionModeSchema2, deletionRequestBody2, archiveHubIdParam2, archiveConversationParams2, archiveListQuery2, archiveMessageObservabilityParams2, conversationIdParam2, claimBody2, releaseBody2, transferBody2, closeBody2, additionalContextPayloadSchema2, kanbanUpdateBody2, annotateConversationBody2, observabilityParams2, observabilityListParams2, deleteUserConversationsBody2, sendMessageBody2, listConversationsQuery2, getConversationDetailParams2, getConversationDetailQuery2, listWhatsAppTemplatesQuery2, sendWhatsAppTemplateBody2, billingOrgIdParam2, subscriptionItemParams2, spendCapBody2, growthPacksBody2, portalSessionBody2, checkoutSessionBody2, updateBillingCurrencyBody2, invoicesQuery2, downloadBody2, uploadQuery2, signUrlBody2, outboundSchemaQuery2, templateIdParam2, listTemplatesQuery2, deleteTemplateQuery2, templateStatusQuery2, createTemplateBody2, updateTemplateBody2, submitTemplateBody2, testTemplateBody2, contactIdParam2, listContactsQuery2, createContactBody2, updateContactBody2, listIdParam2, listListsQuery2, listListContactsQuery2, createListBody2, updateListBody2, addListContactsBody2, removeListContactsBody2, scheduleIdParam2, listSchedulesQuery2, listExecutionsQuery2, createScheduleBody2, updateScheduleBody2, MAX_RESOURCE_FILE_SIZE3, MAX_EVAL_ATTACHMENT_ENCODED_BYTES2, MAX_EVAL_ATTACHMENT_CARRIERS2, MAX_EVAL_ATTACHMENT_AGGREGATE_ENCODED_BYTES2, uuidPattern2, ciUuidSchema2, hubAsCodeConfigSchema2, ciPullHubIdParam2, ciBranchParam2, ciPullQuery2, ciHubsQuery2, ciLookupQuery2, ciBranchesQuery2, ciSyncBody2, ciPushBody2, ciDiffBody2, ciPublishBody2, ciPublishPreviewBody2, ciSyncMcpBody2, ciOrgResourcesParam2, orgResourcesConfigSchema2, ciOrgResourcesPushBody2, ciOrgResourcesDiffBody2, adminOrgIdParam2, adminOrgAdminIdParam2, adminUserIdParam2, adminPlanIdParam2, adminHubIdParam2, adminOrganizationsQuery2, adminUserSearchQuery2, kanbanSlugMigrationQuery2, adjustQuotaBody2, updatePlanBody2, addAdminUserBody2, updatePlatformConfigBody2, updateFreeOrgLimitBody2, adminSetOrgHarnessBody2, adminSetHubHarnessBody2, adminScopedTargetBody2, updatePricingPlanBody2, dataExplorerSearchQuery2, dataExplorerOrgIdParam2, dataExplorerHubIdParam2, dataExplorerConvIdParam2, dataExplorerDoInstancesQuery2, dataExplorerNsIdParam2, dataExplorerKvKeysQuery2, dataExplorerKvKeyParam2, uuidOrHexId2, dataExplorerDeleteOrgParam2, dataExplorerDeleteHubParam2, dataExplorerDeleteConvParam2, dataExplorerDeleteUserParam2, dataExplorerDeleteQuery2, dataExplorerKvDeleteQuery2, pitrDoNamespace2, pitrBookmarkParam2, pitrRestoreBody2, healthSamplingTriggerBody2, dataExplorerDebugDoType2, DEBUG_READ_MAX_LIMIT2, DEBUG_READ_DEFAULT_LIMIT2, DEBUG_DO_HEX_PREFIX2, debugDoEntityId2, debugTableName2, dataExplorerDebugTablesParam2, dataExplorerDebugRowsParam2, dataExplorerDebugRowsQuery2, dataExplorerDebugAnalyticsTable2, debugUuid2, dataExplorerDebugAnalyticsRowsParam2, dataExplorerDebugAnalyticsRowsQuery2, debugHubConvParam2, debugMessageIdQuery2, sandboxEgressPolicy2, SANDBOX_EXEC_MAX_CMD_LENGTH2, SANDBOX_EXEC_MAX_ALLOWLIST2, SANDBOX_EXEC_MAX_TIMEOUT_MS2, adminSandboxExecBody2, whatsappCallbackBody2, authMeResponse2, wsTicketResponse2, sessionCheckResponse2, logoutResponse2, magicCodeSendBody2, magicCodeSendResponse2, magicCodeVerifyBody2, magicCodeVerifyResponse2, browserParams2, browserFoldersQuery2, browserFilesQuery2, stateOperationBody2, conversationListResourcesQuery2, conversationReadResourceQuery2, listPendingSchedulesQuery2, listHubAggregateSchedulesQuery2, reportSourceSchema2, intakeReportSourceSchema2, reportClassificationSchema2, reportStatusSchema2, reportLocaleSchema2, reportMessageAuthorRoleSchema2, sentryRefStatusSchema2, createReportBody2, editReportBody2, contestReportBody2, reporterListQuery2, listReportsQuery2, GITHUB_ISSUE_URL_REGEX2, githubIssueUrlSchema2, transitionReportBody2, groupReportsBody2, holdReportBody2, clickhouseReadyResponse2, bootstrapQuery2, requestSnapshotChunkMessage2, MAX_NAME_LENGTH3, MAX_VALUE_LENGTH2, MAX_TAGS2, MAX_TAG_LENGTH2, MAX_SCOPE_HUBS2, MAX_SCOPE_TAGS2, vaultSecretScopeSchema2, vaultCreateBody2, vaultUpdateBody2, PERMISSIONS2, MAX_NAME_LENGTH22, MAX_HUBS_PER_GRANT2, MAX_HUB_TAGS_PER_GRANT2, MAX_GRANTS2, permissionEnum2, grantScopeSchema2, grantSchema2, createTokenBody2, tokenOrgIdParam2, tokenGrantScopeSchema2, tokenGrantSchema2, tokenMetadataSchema2, listTokensResponse2, createTokenResponse2, orgTokenSummarySchema2, listOrgTokensResponse2, invitePreviewQuery2, invitePreviewResponse2, discordTier2, discordBillingCycle2, discordMetadata2, discordOAuthStartResponse2, discordOAuthFinalizeBody2, discordOAuthFinalizeResponse2, discordDisconnectResponse2, jsonSchemaSchema2, hubUserIdentity2, stateValueEntry2, getHubUserContextResponse2, updateHubUserBody2, updateHubUserResponse2, unlockHubUserNameResponse2, stateResetParams2, stateResetResponse2, hubAlertsParam2, hubAlertSeverity2, hubAlert2, hubAlertsListResponse2, noticeSeveritySchema2, noticeStatusSchema2, NOTICE_SCOPE_REGEX2, noticeScopeSchema2, noticeLinkSchema2, createNoticeBody2, updateNoticeBody2, listNoticesQuery2, noticeIdParamSchema2, templateLocaleSchema2, TEMPLATE_SLUG_REGEX2, templateSlugSchema2, templateStatusSchema2, templateFileMapSchema2, TEMPLATE_INSTANCE_ID_KEYS, TEMPLATE_OPAQUE_BLOB_KEYS, TEMPLATE_AUTHOR_DATA_KEYS, templateTextSchema2, templateUsesEntrySchema2, templateUsesSchema2, templateYamlLocaleEntry2, templateYamlWayaiBlock2, templateTagsSchema2, templateYamlSchema2, templateHeroSchema2, templatePushLocaleSchema2, templatePushBody2, templateListQuery2, templateSlugParam2, templateDetailQuery2, ADMIN_SKILL_NAME_REGEX2, adminSkillNameParam2;
12996
13579
  var init_contracts = __esm({
12997
13580
  "../../packages/core/dist/contracts/index.js"() {
12998
13581
  "use strict";
@@ -14164,10 +14747,10 @@ var init_contracts = __esm({
14164
14747
  ROLE_ECHO_MAX2 = 40;
14165
14748
  createEvalBody2 = external_exports.object({
14166
14749
  eval: external_exports.record(external_exports.unknown())
14167
- }).superRefine(refineEvalMessageTextRole2);
14750
+ }).superRefine(refineEvalMessageTextRole2).superRefine(refineEvalAttachments2);
14168
14751
  updateEvalBody2 = external_exports.object({
14169
14752
  eval: external_exports.record(external_exports.unknown())
14170
- }).superRefine(refineEvalMessageTextRole2);
14753
+ }).superRefine(refineEvalMessageTextRole2).superRefine(refineEvalAttachments2);
14171
14754
  getEvalsQuery2 = external_exports.object({
14172
14755
  hub_id: external_exports.string().uuid(),
14173
14756
  agent_id: external_exports.string().uuid().optional(),
@@ -14294,6 +14877,15 @@ var init_contracts = __esm({
14294
14877
  journey_name: external_exports.string().min(1).max(200),
14295
14878
  evaluator_instructions: external_exports.string().max(4e3).optional()
14296
14879
  });
14880
+ EVAL_ATTACHMENT_HASH_REGEX2 = /^[a-f0-9]{64}$/;
14881
+ evalAttachmentRef2 = external_exports.object({
14882
+ hash: external_exports.string().regex(EVAL_ATTACHMENT_HASH_REGEX2),
14883
+ file_name: external_exports.string().min(1).max(255),
14884
+ mime_type: external_exports.string().min(1).max(255),
14885
+ file_size: external_exports.number().int().positive().optional()
14886
+ });
14887
+ turnMayCarryAttachments2 = (turn) => !turn.attachments || turn.attachments.length === 0 || turn.role === "user";
14888
+ ATTACHMENTS_USER_ONLY_MESSAGE2 = "attachments are only valid on user turns";
14297
14889
  journeyIdParam2 = external_exports.object({ id: external_exports.string().uuid() });
14298
14890
  journeyToolCallSchema2 = external_exports.object({
14299
14891
  id: external_exports.string(),
@@ -14305,7 +14897,10 @@ var init_contracts = __esm({
14305
14897
  content: external_exports.string().nullable().optional(),
14306
14898
  tool_calls: external_exports.array(journeyToolCallSchema2).optional(),
14307
14899
  tool_call_id: external_exports.string().optional(),
14308
- step_id: external_exports.string().optional()
14900
+ step_id: external_exports.string().optional(),
14901
+ attachments: external_exports.array(evalAttachmentRef2).max(MAX_ATTACHMENTS_PER_MESSAGE2).optional()
14902
+ }).refine(turnMayCarryAttachments2, {
14903
+ message: ATTACHMENTS_USER_ONLY_MESSAGE2
14309
14904
  });
14310
14905
  journeyStepOverrideSchema2 = external_exports.object({
14311
14906
  number_of_runs: external_exports.number().int().positive().max(100).optional(),
@@ -14810,6 +15405,10 @@ var init_contracts = __esm({
14810
15405
  agent_id: external_exports.string().uuid().nullable().optional(),
14811
15406
  trigger_message: external_exports.string().nullable().optional()
14812
15407
  });
15408
+ MAX_RESOURCE_FILE_SIZE3 = 10 * 1024 * 1024;
15409
+ MAX_EVAL_ATTACHMENT_ENCODED_BYTES2 = Math.ceil(MAX_RESOURCE_FILE_SIZE3 / 3) * 4 + 16;
15410
+ MAX_EVAL_ATTACHMENT_CARRIERS2 = 1e3;
15411
+ MAX_EVAL_ATTACHMENT_AGGREGATE_ENCODED_BYTES2 = 16 * 1024 * 1024;
14813
15412
  uuidPattern2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
14814
15413
  ciUuidSchema2 = external_exports.string().regex(uuidPattern2, "must be a valid UUID");
14815
15414
  hubAsCodeConfigSchema2 = external_exports.object({
@@ -14822,6 +15421,7 @@ var init_contracts = __esm({
14822
15421
  refineHubAsCodeKanbanStatuses2(config, ctx);
14823
15422
  refineHubAsCodeLanes2(config, ctx);
14824
15423
  refineHubAsCodeEvals2(config, ctx);
15424
+ refineHubAsCodeEvalAttachments2(config, ctx);
14825
15425
  });
14826
15426
  ciPullHubIdParam2 = external_exports.object({
14827
15427
  hub_id: ciUuidSchema2
@@ -15711,8 +16311,8 @@ var replicate_exports = {};
15711
16311
  __export(replicate_exports, {
15712
16312
  replicateCommand: () => replicateCommand
15713
16313
  });
15714
- import * as fs15 from "fs";
15715
- import * as path17 from "path";
16314
+ import * as fs17 from "fs";
16315
+ import * as path19 from "path";
15716
16316
  function parseArgs8(args2) {
15717
16317
  let label;
15718
16318
  let hubSelector;
@@ -15758,7 +16358,7 @@ async function replicateCommand(args2) {
15758
16358
  payload.preview_label,
15759
16359
  payload.branch_name
15760
16360
  );
15761
- fs15.mkdirSync(path17.dirname(hubFolder), { recursive: true });
16361
+ fs17.mkdirSync(path19.dirname(hubFolder), { recursive: true });
15762
16362
  writeHubFolder(hubFolder, payload);
15763
16363
  await downloadBinaryResourceFiles(hubFolder, payload);
15764
16364
  hubFolder = autoRenameHubFolder(
@@ -15770,8 +16370,8 @@ async function replicateCommand(args2) {
15770
16370
  payload.branch_name
15771
16371
  );
15772
16372
  autoBindIfUnbound(previewHubId);
15773
- console.log(`Preview written to ${path17.basename(hubFolder)}`);
15774
- console.log(` Switch to it with: wayai use ${path17.basename(hubFolder)}`);
16373
+ console.log(`Preview written to ${path19.basename(hubFolder)}`);
16374
+ console.log(` Switch to it with: wayai use ${path19.basename(hubFolder)}`);
15775
16375
  }
15776
16376
  var init_replicate = __esm({
15777
16377
  "src/commands/replicate.ts"() {
@@ -15793,7 +16393,7 @@ var relabel_exports = {};
15793
16393
  __export(relabel_exports, {
15794
16394
  relabelCommand: () => relabelCommand
15795
16395
  });
15796
- import * as path18 from "path";
16396
+ import * as path20 from "path";
15797
16397
  function parseArgs9(args2) {
15798
16398
  let label;
15799
16399
  let clear = false;
@@ -15837,7 +16437,7 @@ async function relabelCommand(args2) {
15837
16437
  } else {
15838
16438
  console.error(`Multiple hubs found in ${wsLabel}/. Pass --hub <uuid|folder-name> to choose, or run from inside a hub folder:`);
15839
16439
  for (const h of previewHubs) {
15840
- console.error(` ${path18.basename(h.hubFolder)} (${h.hubId})`);
16440
+ console.error(` ${path20.basename(h.hubFolder)} (${h.hubId})`);
15841
16441
  }
15842
16442
  }
15843
16443
  process.exit(1);
@@ -15846,7 +16446,7 @@ async function relabelCommand(args2) {
15846
16446
  console.log("This is a read-only production mirror \u2014 production hubs have no preview label. Relabel the linked preview hub instead.");
15847
16447
  return;
15848
16448
  }
15849
- assertHubMatchesBinding(target.hubId, path18.basename(target.hubFolder));
16449
+ assertHubMatchesBinding(target.hubId, path20.basename(target.hubFolder));
15850
16450
  console.log(normalizedLabel ? `Setting preview label to "${normalizedLabel}"...` : "Clearing preview label...");
15851
16451
  const { data } = await client.relabelPreview(target.hubId, normalizedLabel);
15852
16452
  const row = data[0];
@@ -15862,7 +16462,7 @@ async function relabelCommand(args2) {
15862
16462
  );
15863
16463
  console.log(serverLabel ? `Preview label set to "${serverLabel}".` : "Preview label cleared.");
15864
16464
  if (finalFolder !== target.hubFolder) {
15865
- console.log(`Hub folder is now ${path18.basename(finalFolder)}`);
16465
+ console.log(`Hub folder is now ${path20.basename(finalFolder)}`);
15866
16466
  }
15867
16467
  }
15868
16468
  var init_relabel = __esm({
@@ -15888,7 +16488,7 @@ __export(publish_exports, {
15888
16488
  renderHubDiff: () => renderHubDiff,
15889
16489
  runPublish: () => runPublish
15890
16490
  });
15891
- import * as path19 from "path";
16491
+ import * as path21 from "path";
15892
16492
  function parseArgs10(args2) {
15893
16493
  let autoConfirm = false;
15894
16494
  let hubSelector;
@@ -15994,7 +16594,7 @@ async function publishCommand(args2) {
15994
16594
  return;
15995
16595
  }
15996
16596
  }
15997
- assertHubMatchesBinding(hubId, hubFolder ? path19.basename(hubFolder) : void 0);
16597
+ assertHubMatchesBinding(hubId, hubFolder ? path21.basename(hubFolder) : void 0);
15998
16598
  await runPublish(client, { hubId, localConfig, organizationId, autoConfirm });
15999
16599
  }
16000
16600
  var STATUS_STYLE;
@@ -16026,7 +16626,7 @@ var use_exports = {};
16026
16626
  __export(use_exports, {
16027
16627
  useCommand: () => useCommand
16028
16628
  });
16029
- import * as path20 from "path";
16629
+ import * as path22 from "path";
16030
16630
  async function useCommand(args2) {
16031
16631
  const target = args2[0];
16032
16632
  if (!target || target.startsWith("-")) {
@@ -16048,7 +16648,7 @@ async function useCommand(args2) {
16048
16648
  const match = findHubByFolderName(workspaceDir, target);
16049
16649
  if (!match) {
16050
16650
  const newHubs = scanNewHubs(workspaceDir);
16051
- const pending = newHubs.find((h) => path20.basename(h.hubFolder) === target);
16651
+ const pending = newHubs.find((h) => path22.basename(h.hubFolder) === target);
16052
16652
  if (pending) {
16053
16653
  console.error(
16054
16654
  `"${target}" is a new hub that hasn't been created on the platform yet, so it has no id to bind to.
@@ -16059,10 +16659,10 @@ Create it first \u2014 the worktree binds automatically afterward:
16059
16659
  }
16060
16660
  console.error(`No hub matching "${target}" found in ${wsLabel}/. Pass a UUID or a folder name from:`);
16061
16661
  for (const h of scanWorkspaceHubs(workspaceDir)) {
16062
- console.error(` ${path20.basename(h.hubFolder)} (${h.hubId})`);
16662
+ console.error(` ${path22.basename(h.hubFolder)} (${h.hubId})`);
16063
16663
  }
16064
16664
  for (const h of newHubs) {
16065
- console.error(` ${path20.basename(h.hubFolder)} (new \u2014 "${h.hubName}", run \`wayai create ${path20.basename(h.hubFolder)}\`)`);
16665
+ console.error(` ${path22.basename(h.hubFolder)} (new \u2014 "${h.hubName}", run \`wayai create ${path22.basename(h.hubFolder)}\`)`);
16066
16666
  }
16067
16667
  process.exit(1);
16068
16668
  }
@@ -16101,11 +16701,11 @@ __export(migrate_exports, {
16101
16701
  migrateCommand: () => migrateCommand
16102
16702
  });
16103
16703
  import { execFileSync as execFileSync3 } from "child_process";
16104
- import * as fs16 from "fs";
16105
- import * as path21 from "path";
16704
+ import * as fs18 from "fs";
16705
+ import * as path23 from "path";
16106
16706
  function isTracked(gitRoot, p) {
16107
16707
  try {
16108
- execFileSync3("git", ["ls-files", "--error-unmatch", "--", path21.relative(gitRoot, p)], {
16708
+ execFileSync3("git", ["ls-files", "--error-unmatch", "--", path23.relative(gitRoot, p)], {
16109
16709
  cwd: gitRoot,
16110
16710
  stdio: ["pipe", "pipe", "pipe"]
16111
16711
  });
@@ -16115,10 +16715,10 @@ function isTracked(gitRoot, p) {
16115
16715
  }
16116
16716
  }
16117
16717
  function moveDir(gitRoot, from, to) {
16118
- fs16.mkdirSync(path21.dirname(to), { recursive: true });
16718
+ fs18.mkdirSync(path23.dirname(to), { recursive: true });
16119
16719
  if (isTracked(gitRoot, from)) {
16120
16720
  try {
16121
- execFileSync3("git", ["mv", path21.relative(gitRoot, from), path21.relative(gitRoot, to)], {
16721
+ execFileSync3("git", ["mv", path23.relative(gitRoot, from), path23.relative(gitRoot, to)], {
16122
16722
  cwd: gitRoot,
16123
16723
  stdio: ["pipe", "pipe", "pipe"]
16124
16724
  });
@@ -16126,7 +16726,7 @@ function moveDir(gitRoot, from, to) {
16126
16726
  } catch {
16127
16727
  }
16128
16728
  }
16129
- fs16.renameSync(from, to);
16729
+ fs18.renameSync(from, to);
16130
16730
  return "fs";
16131
16731
  }
16132
16732
  async function migrateCommand(_args) {
@@ -16135,12 +16735,12 @@ async function migrateCommand(_args) {
16135
16735
  console.error("Not inside a git repository.");
16136
16736
  process.exit(1);
16137
16737
  }
16138
- const rel = (p) => path21.relative(gitRoot, p);
16139
- const newWs = path21.join(gitRoot, WAYAI_LAYOUT.wsDir);
16140
- const legacyWs = path21.join(gitRoot, WAYAI_LAYOUT.legacy.wsDir);
16141
- const legacyOrg = path21.join(gitRoot, WAYAI_LAYOUT.legacy.orgAtRoot);
16142
- const newHubs = path21.join(newWs, WAYAI_LAYOUT.hubsSubdir);
16143
- const newOrg = path21.join(newWs, WAYAI_LAYOUT.orgSubdir);
16738
+ const rel = (p) => path23.relative(gitRoot, p);
16739
+ const newWs = path23.join(gitRoot, WAYAI_LAYOUT.wsDir);
16740
+ const legacyWs = path23.join(gitRoot, WAYAI_LAYOUT.legacy.wsDir);
16741
+ const legacyOrg = path23.join(gitRoot, WAYAI_LAYOUT.legacy.orgAtRoot);
16742
+ const newHubs = path23.join(newWs, WAYAI_LAYOUT.hubsSubdir);
16743
+ const newOrg = path23.join(newWs, WAYAI_LAYOUT.orgSubdir);
16144
16744
  const hasLegacyWs = isDirectory(legacyWs);
16145
16745
  const hasLegacyOrg = isDirectory(legacyOrg);
16146
16746
  if (!hasLegacyWs && !hasLegacyOrg) {
@@ -16214,12 +16814,12 @@ var send_message_exports = {};
16214
16814
  __export(send_message_exports, {
16215
16815
  sendMessageCommand: () => sendMessageCommand
16216
16816
  });
16217
- import * as fs17 from "fs";
16218
- import * as path22 from "path";
16817
+ import * as fs19 from "fs";
16818
+ import * as path24 from "path";
16219
16819
  function statAttachment(filePath) {
16220
16820
  let stat;
16221
16821
  try {
16222
- stat = fs17.statSync(filePath);
16822
+ stat = fs19.statSync(filePath);
16223
16823
  } catch {
16224
16824
  console.error(`Error: file not found: ${filePath}`);
16225
16825
  process.exit(1);
@@ -16231,11 +16831,11 @@ function statAttachment(filePath) {
16231
16831
  return { filePath, size: stat.size };
16232
16832
  }
16233
16833
  function readAttachment(filePath, size) {
16234
- const fileName = path22.basename(filePath);
16235
- const ext = path22.extname(fileName).replace(/^\./, "");
16834
+ const fileName = path24.basename(filePath);
16835
+ const ext = path24.extname(fileName).replace(/^\./, "");
16236
16836
  return {
16237
16837
  file_name: fileName,
16238
- file_binary: fs17.readFileSync(filePath).toString("base64"),
16838
+ file_binary: fs19.readFileSync(filePath).toString("base64"),
16239
16839
  file_size: size,
16240
16840
  ...ext && { file_extension: ext }
16241
16841
  };
@@ -16282,7 +16882,7 @@ async function sendMessageCommand(args2) {
16282
16882
  }
16283
16883
  const stats = filePaths.map(statAttachment);
16284
16884
  const filesTotal = stats.reduce((sum, s) => sum + base64Length(s.size), 0);
16285
- const fileDetail = () => stats.map((s) => `${path22.basename(s.filePath)} ${asMb(s.size)} MB`).join(", ");
16885
+ const fileDetail = () => stats.map((s) => `${path24.basename(s.filePath)} ${asMb(s.size)} MB`).join(", ");
16286
16886
  if (filesTotal > MAX_MESSAGE_BODY_BYTES2) {
16287
16887
  console.error(
16288
16888
  `Error: ${fileDetail()} encode to ~${asMb(filesTotal)} MB, over the ${asMb(MAX_MESSAGE_BODY_BYTES2)} MB per-request limit.`
@@ -17517,7 +18117,7 @@ Timeout after ${timeoutSeconds}s. Session is still running.`);
17517
18117
  console.log(`Check results: wayai eval-results --session ${sessionId}`);
17518
18118
  }
17519
18119
  function sleep(ms) {
17520
- return new Promise((resolve4) => setTimeout(resolve4, ms));
18120
+ return new Promise((resolve6) => setTimeout(resolve6, ms));
17521
18121
  }
17522
18122
  function parsePacing(raw) {
17523
18123
  const NAMED = EVAL_PACING_PRESET_NAMES.filter((p) => p !== "custom");
@@ -17742,8 +18342,8 @@ var eval_capture_exports = {};
17742
18342
  __export(eval_capture_exports, {
17743
18343
  evalCaptureCommand: () => evalCaptureCommand
17744
18344
  });
17745
- import * as fs18 from "fs";
17746
- import * as path23 from "path";
18345
+ import * as fs20 from "fs";
18346
+ import * as path25 from "path";
17747
18347
  import * as yaml7 from "js-yaml";
17748
18348
  function isValidSetName(name) {
17749
18349
  if (name.length === 0 || name === "." || name === "..") return false;
@@ -17809,15 +18409,15 @@ async function evalCaptureCommand(args2) {
17809
18409
  const setFolderName = targetSetName;
17810
18410
  const scenarioName = parsed.evalName ?? `Capture ${parsed.conversationId.slice(0, 8)}`;
17811
18411
  const slug = slugify(scenarioName);
17812
- const evalsDir = path23.join(hubFolder, "evals");
17813
- const targetDir = path23.join(evalsDir, setFolderName);
17814
- const targetPath = path23.join(targetDir, `${slug}.yaml`);
17815
- if (!targetPath.startsWith(evalsDir + path23.sep)) {
17816
- console.error(`Resolved path "${path23.relative(hubFolder, targetPath)}" escapes evals/. Aborting.`);
18412
+ const evalsDir = path25.join(hubFolder, "evals");
18413
+ const targetDir = path25.join(evalsDir, setFolderName);
18414
+ const targetPath = path25.join(targetDir, `${slug}.yaml`);
18415
+ if (!targetPath.startsWith(evalsDir + path25.sep)) {
18416
+ console.error(`Resolved path "${path25.relative(hubFolder, targetPath)}" escapes evals/. Aborting.`);
17817
18417
  process.exit(1);
17818
18418
  }
17819
- if (fs18.existsSync(targetPath)) {
17820
- console.error(`File already exists: ${path23.relative(hubFolder, targetPath)}. Use --name to choose a different name.`);
18419
+ if (fs20.existsSync(targetPath)) {
18420
+ console.error(`File already exists: ${path25.relative(hubFolder, targetPath)}. Use --name to choose a different name.`);
17821
18421
  process.exit(1);
17822
18422
  }
17823
18423
  console.log("Resolving scenario set...");
@@ -17849,9 +18449,9 @@ async function evalCaptureCommand(args2) {
17849
18449
  ...captured.evaluator_instructions ? { evaluator_instructions: captured.evaluator_instructions } : {}
17850
18450
  };
17851
18451
  const yamlObj = buildEvalYamlObject(evalEntry, slug);
17852
- fs18.mkdirSync(targetDir, { recursive: true });
17853
- fs18.writeFileSync(targetPath, yaml7.dump(yamlObj, YAML_DUMP_OPTIONS), "utf-8");
17854
- const relPath = path23.relative(process.cwd(), targetPath);
18452
+ fs20.mkdirSync(targetDir, { recursive: true });
18453
+ fs20.writeFileSync(targetPath, yaml7.dump(yamlObj, YAML_DUMP_OPTIONS), "utf-8");
18454
+ const relPath = path25.relative(process.cwd(), targetPath);
17855
18455
  console.log(`
17856
18456
  Wrote ${relPath}`);
17857
18457
  console.log("Run `wayai pull` to refresh the agent display name, then commit. The scenario is already on the platform.");
@@ -19078,23 +19678,23 @@ var init_set_connection_credential = __esm({
19078
19678
  });
19079
19679
 
19080
19680
  // src/lib/org-workspace.ts
19081
- import * as fs19 from "fs";
19082
- import * as path24 from "path";
19681
+ import * as fs21 from "fs";
19682
+ import * as path26 from "path";
19083
19683
  import * as yaml8 from "js-yaml";
19084
19684
  function getOrgDir(gitRoot) {
19085
19685
  return resolveLayout(gitRoot).orgDir;
19086
19686
  }
19087
19687
  function orgManifestExists(orgDir) {
19088
- return fs19.existsSync(path24.join(orgDir, ORG_MANIFEST_NAME));
19688
+ return fs21.existsSync(path26.join(orgDir, ORG_MANIFEST_NAME));
19089
19689
  }
19090
19690
  function parseOrgResources(orgDir) {
19091
- const manifestPath = path24.join(orgDir, ORG_MANIFEST_NAME);
19691
+ const manifestPath = path26.join(orgDir, ORG_MANIFEST_NAME);
19092
19692
  let manifest = {};
19093
- if (fs19.existsSync(manifestPath)) {
19094
- manifest = yaml8.load(fs19.readFileSync(manifestPath, "utf-8")) ?? {};
19693
+ if (fs21.existsSync(manifestPath)) {
19694
+ manifest = yaml8.load(fs21.readFileSync(manifestPath, "utf-8")) ?? {};
19095
19695
  }
19096
19696
  const rawResources = Array.isArray(manifest.resources) ? manifest.resources : [];
19097
- const resourcesDir = path24.join(orgDir, "resources");
19697
+ const resourcesDir = path26.join(orgDir, "resources");
19098
19698
  const resources = rawResources.map((res) => {
19099
19699
  const resource = { name: res.name };
19100
19700
  if (res.id) resource.id = res.id;
@@ -19106,8 +19706,8 @@ function parseOrgResources(orgDir) {
19106
19706
  if (res.environment) resource.environment = res.environment;
19107
19707
  if (Array.isArray(res.tags)) resource.tags = res.tags;
19108
19708
  if (Array.isArray(res.folders)) resource.folders = res.folders;
19109
- const resDir = path24.join(resourcesDir, slugify(resource.name));
19110
- if (fs19.existsSync(resDir)) {
19709
+ const resDir = path26.join(resourcesDir, slugify(resource.name));
19710
+ if (fs21.existsSync(resDir)) {
19111
19711
  const files = scanResourceFiles(resDir, "");
19112
19712
  if (files.length > 0) resource.files = files;
19113
19713
  }
@@ -19116,28 +19716,28 @@ function parseOrgResources(orgDir) {
19116
19716
  return { version: 1, resources };
19117
19717
  }
19118
19718
  function writeOrgResources(orgDir, payload) {
19119
- fs19.mkdirSync(orgDir, { recursive: true });
19719
+ fs21.mkdirSync(orgDir, { recursive: true });
19120
19720
  const resources = payload.resources ?? [];
19121
19721
  const manifestResources = resources.map((r) => {
19122
19722
  const { files: _files, ...rest } = r;
19123
19723
  return rest;
19124
19724
  });
19125
- fs19.writeFileSync(
19126
- path24.join(orgDir, ORG_MANIFEST_NAME),
19725
+ fs21.writeFileSync(
19726
+ path26.join(orgDir, ORG_MANIFEST_NAME),
19127
19727
  yaml8.dump({ version: 1, resources: manifestResources }, YAML_DUMP_OPTIONS),
19128
19728
  "utf-8"
19129
19729
  );
19130
- const resourcesDir = path24.join(orgDir, "resources");
19730
+ const resourcesDir = path26.join(orgDir, "resources");
19131
19731
  const currentSlugs = /* @__PURE__ */ new Set();
19132
19732
  for (const resource of resources) {
19133
19733
  const resSlug = slugify(resource.name);
19134
19734
  currentSlugs.add(resSlug);
19135
- writeResourceFileTree(path24.join(resourcesDir, resSlug), resource.files || []);
19735
+ writeResourceFileTree(path26.join(resourcesDir, resSlug), resource.files || [], orgDir);
19136
19736
  }
19137
- if (fs19.existsSync(resourcesDir)) {
19138
- for (const entry of fs19.readdirSync(resourcesDir, { withFileTypes: true })) {
19737
+ if (fs21.existsSync(resourcesDir)) {
19738
+ for (const entry of fs21.readdirSync(resourcesDir, { withFileTypes: true })) {
19139
19739
  if (entry.isDirectory() && !currentSlugs.has(entry.name)) {
19140
- fs19.rmSync(path24.join(resourcesDir, entry.name), { recursive: true, force: true });
19740
+ fs21.rmSync(path26.join(resourcesDir, entry.name), { recursive: true, force: true });
19141
19741
  }
19142
19742
  }
19143
19743
  }
@@ -19146,8 +19746,8 @@ async function downloadOrgBinaryFiles(orgDir, payload) {
19146
19746
  let count = 0;
19147
19747
  for (const resource of payload.resources ?? []) {
19148
19748
  if (!resource.files) continue;
19149
- const resDir = path24.join(orgDir, "resources", slugify(resource.name));
19150
- count += await downloadBinaryFiles(resDir, resource.files);
19749
+ const resDir = path26.join(orgDir, "resources", slugify(resource.name));
19750
+ count += await downloadBinaryFiles(resDir, resource.files, orgDir);
19151
19751
  }
19152
19752
  if (count > 0) console.log(`Downloaded ${count} binary resource file(s).`);
19153
19753
  }
@@ -19402,20 +20002,20 @@ var init_list = __esm({
19402
20002
  });
19403
20003
 
19404
20004
  // src/lib/template-files.ts
19405
- import * as fs20 from "fs";
19406
- import * as path25 from "path";
20005
+ import * as fs22 from "fs";
20006
+ import * as path27 from "path";
19407
20007
  function readDirToFileMap(rootDir) {
19408
20008
  const out = {};
19409
20009
  function walk(dir, prefix) {
19410
- const entries = fs20.readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
20010
+ const entries = fs22.readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
19411
20011
  for (const entry of entries) {
19412
- const abs = path25.join(dir, entry.name);
20012
+ const abs = path27.join(dir, entry.name);
19413
20013
  const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
19414
20014
  if (entry.isDirectory()) walk(abs, rel);
19415
- else if (entry.isFile()) out[rel] = fs20.readFileSync(abs, "utf-8");
20015
+ else if (entry.isFile()) out[rel] = fs22.readFileSync(abs, "utf-8");
19416
20016
  }
19417
20017
  }
19418
- if (fs20.existsSync(rootDir)) walk(rootDir, "");
20018
+ if (fs22.existsSync(rootDir)) walk(rootDir, "");
19419
20019
  return out;
19420
20020
  }
19421
20021
  function detectCollisions(targetDir, files) {
@@ -19424,7 +20024,7 @@ function detectCollisions(targetDir, files) {
19424
20024
  if (!isSafeTemplatePath2(rel)) {
19425
20025
  throw new Error(`Refusing to inspect unsafe template path: ${rel}`);
19426
20026
  }
19427
- if (fs20.existsSync(path25.join(targetDir, rel))) collisions.push(rel);
20027
+ if (fs22.existsSync(path27.join(targetDir, rel))) collisions.push(rel);
19428
20028
  }
19429
20029
  return collisions.sort((a, b) => a.localeCompare(b));
19430
20030
  }
@@ -19434,9 +20034,9 @@ function writeFileMap(targetDir, files) {
19434
20034
  if (!isSafeTemplatePath2(rel)) {
19435
20035
  throw new Error(`Refusing to write unsafe template path: ${rel}`);
19436
20036
  }
19437
- const abs = path25.join(targetDir, rel);
19438
- fs20.mkdirSync(path25.dirname(abs), { recursive: true });
19439
- fs20.writeFileSync(abs, body, "utf-8");
20037
+ const abs = path27.join(targetDir, rel);
20038
+ fs22.mkdirSync(path27.dirname(abs), { recursive: true });
20039
+ fs22.writeFileSync(abs, body, "utf-8");
19440
20040
  written.push(rel);
19441
20041
  }
19442
20042
  return written;
@@ -19444,20 +20044,20 @@ function writeFileMap(targetDir, files) {
19444
20044
  function listDirRelFiles(dir) {
19445
20045
  const out = [];
19446
20046
  function walk(d, prefix) {
19447
- for (const entry of fs20.readdirSync(d, { withFileTypes: true })) {
20047
+ for (const entry of fs22.readdirSync(d, { withFileTypes: true })) {
19448
20048
  const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
19449
- if (entry.isDirectory()) walk(path25.join(d, entry.name), rel);
20049
+ if (entry.isDirectory()) walk(path27.join(d, entry.name), rel);
19450
20050
  else if (entry.isFile()) out.push(rel);
19451
20051
  }
19452
20052
  }
19453
- if (fs20.existsSync(dir)) walk(dir, "");
20053
+ if (fs22.existsSync(dir)) walk(dir, "");
19454
20054
  return out;
19455
20055
  }
19456
20056
  function writeSliceSnapshot(targetDir, files) {
19457
20057
  const keep = new Set(Object.keys(files));
19458
20058
  const removed = listDirRelFiles(targetDir).filter((rel) => !keep.has(rel)).sort((a, b) => a.localeCompare(b));
19459
20059
  const written = writeFileMap(targetDir, files).sort((a, b) => a.localeCompare(b));
19460
- if (fs20.existsSync(targetDir)) cleanOrphanFiles(targetDir, "", keep);
20060
+ if (fs22.existsSync(targetDir)) cleanOrphanFiles(targetDir, "", keep);
19461
20061
  return { written, removed };
19462
20062
  }
19463
20063
  var init_template_files = __esm({
@@ -19473,7 +20073,7 @@ var templates_exports = {};
19473
20073
  __export(templates_exports, {
19474
20074
  templateCommand: () => templateCommand
19475
20075
  });
19476
- import * as path26 from "path";
20076
+ import * as path28 from "path";
19477
20077
  async function templateCommand(args2) {
19478
20078
  const [sub, ...rest] = args2;
19479
20079
  switch (sub) {
@@ -19589,8 +20189,8 @@ async function runTemplatePull(rest) {
19589
20189
  console.log(`Template "${slug}" has no WayAI slice${hint}`);
19590
20190
  return;
19591
20191
  }
19592
- const targetDir = path26.join(resolveLayout(gitRoot).hubsDir, slug);
19593
- const relTarget = path26.relative(gitRoot, targetDir);
20192
+ const targetDir = path28.join(resolveLayout(gitRoot).hubsDir, slug);
20193
+ const relTarget = path28.relative(gitRoot, targetDir);
19594
20194
  const collisions = detectCollisions(targetDir, detail.files);
19595
20195
  if (dryRun) {
19596
20196
  const planned = Object.keys(detail.files).sort((a, b) => a.localeCompare(b));
@@ -19716,21 +20316,37 @@ import * as yaml9 from "js-yaml";
19716
20316
  function sanitizeWayaiSliceFiles(files) {
19717
20317
  const out = {};
19718
20318
  for (const [filePath, body] of Object.entries(files)) {
19719
- out[filePath] = isSanitizableSliceFile(filePath) ? sanitizeOne(body) : body;
20319
+ out[filePath] = isSanitizableSliceFile(filePath) ? sanitizeOne(filePath, body) : body;
19720
20320
  }
19721
20321
  return out;
19722
20322
  }
19723
- function sanitizeOne(body) {
20323
+ function sanitizeOne(filePath, body) {
19724
20324
  let parsed;
19725
20325
  try {
19726
20326
  parsed = yaml9.load(body);
19727
20327
  } catch {
19728
20328
  return body;
19729
20329
  }
19730
- const scrubbed = excludeTemplateAuthorData(stripTemplateInstanceIds(parsed));
20330
+ const scrubbed = stripEvalAttachments(filePath, excludeTemplateAuthorData(stripTemplateInstanceIds(parsed)));
19731
20331
  if (JSON.stringify(parsed) === JSON.stringify(scrubbed)) return body;
19732
20332
  return yaml9.dump(scrubbed, YAML_DUMP_OPTIONS);
19733
20333
  }
20334
+ function stripEvalAttachments(filePath, value) {
20335
+ if (!value || typeof value !== "object" || Array.isArray(value)) return value;
20336
+ const doc = { ...value };
20337
+ const dropOnTurn = (turn) => {
20338
+ if (!turn || typeof turn !== "object" || Array.isArray(turn) || !("attachments" in turn)) return turn;
20339
+ const { attachments: _drop, ...rest } = turn;
20340
+ return rest;
20341
+ };
20342
+ if (/^evals\//.test(filePath)) {
20343
+ if ("input" in doc) doc.input = dropOnTurn(doc.input);
20344
+ if (Array.isArray(doc.history)) doc.history = doc.history.map(dropOnTurn);
20345
+ } else if (/^journeys\//.test(filePath)) {
20346
+ if (Array.isArray(doc.transcript)) doc.transcript = doc.transcript.map(dropOnTurn);
20347
+ }
20348
+ return doc;
20349
+ }
19734
20350
  var init_template_sanitize = __esm({
19735
20351
  "src/lib/template-sanitize.ts"() {
19736
20352
  "use strict";
@@ -19764,8 +20380,8 @@ var admin_exports = {};
19764
20380
  __export(admin_exports, {
19765
20381
  adminCommand: () => adminCommand
19766
20382
  });
19767
- import * as fs21 from "fs";
19768
- import * as path27 from "path";
20383
+ import * as fs23 from "fs";
20384
+ import * as path29 from "path";
19769
20385
  import * as yaml10 from "js-yaml";
19770
20386
  async function adminCommand(args2) {
19771
20387
  const [group, ...afterGroup] = args2;
@@ -20087,7 +20703,7 @@ async function runArchiveRead(positional, flagArgs) {
20087
20703
  exitOnApiError(err);
20088
20704
  throw err;
20089
20705
  }
20090
- fs21.writeFileSync(outPath, zip);
20706
+ fs23.writeFileSync(outPath, zip);
20091
20707
  console.log(`Wrote ${zip.byteLength} bytes to ${outPath}`);
20092
20708
  return;
20093
20709
  }
@@ -20219,15 +20835,15 @@ function loadTemplateManifest(slug) {
20219
20835
  console.error("Not in a git repository. Run from the tutorials/ authoring repo.");
20220
20836
  process.exit(1);
20221
20837
  }
20222
- const templateDir = path27.join(resolveTemplatesDir(gitRoot), slug);
20223
- const manifestPath = path27.join(templateDir, "template.yaml");
20224
- if (!fs21.existsSync(manifestPath)) {
20225
- console.error(`template.yaml not found at ${path27.relative(gitRoot, manifestPath)}`);
20838
+ const templateDir = path29.join(resolveTemplatesDir(gitRoot), slug);
20839
+ const manifestPath = path29.join(templateDir, "template.yaml");
20840
+ if (!fs23.existsSync(manifestPath)) {
20841
+ console.error(`template.yaml not found at ${path29.relative(gitRoot, manifestPath)}`);
20226
20842
  process.exit(1);
20227
20843
  }
20228
20844
  let manifest;
20229
20845
  try {
20230
- manifest = templateYamlSchema2.parse(yaml10.load(fs21.readFileSync(manifestPath, "utf-8")));
20846
+ manifest = templateYamlSchema2.parse(yaml10.load(fs23.readFileSync(manifestPath, "utf-8")));
20231
20847
  } catch (err) {
20232
20848
  console.error(`Invalid template.yaml: ${err instanceof Error ? err.message : String(err)}`);
20233
20849
  process.exit(1);
@@ -20257,20 +20873,20 @@ async function runTemplatePush(positional) {
20257
20873
  const wayaiEntry = manifest.wayai.locales[locale];
20258
20874
  let wayaiFiles;
20259
20875
  if (wayaiEntry) {
20260
- const sliceDir = path27.resolve(templateDir, wayaiEntry.source);
20261
- if (!fs21.existsSync(sliceDir)) {
20876
+ const sliceDir = path29.resolve(templateDir, wayaiEntry.source);
20877
+ if (!fs23.existsSync(sliceDir)) {
20262
20878
  console.error(`wayai source not found for ${locale}: ${wayaiEntry.source} (resolved ${sliceDir})`);
20263
20879
  process.exit(1);
20264
20880
  }
20265
20881
  if (!isInsideTemplateDir(sliceDir, templateDir)) {
20266
20882
  console.warn(
20267
- `Note: ${locale} source points at a live hub (${wayaiEntry.source}); it can go stale on a hub recreate. Run \`wayai admin template freeze ${slug} --locale ${locale} --from ${path27.basename(sliceDir)}\` to commit a stable frozen slice.`
20883
+ `Note: ${locale} source points at a live hub (${wayaiEntry.source}); it can go stale on a hub recreate. Run \`wayai admin template freeze ${slug} --locale ${locale} --from ${path29.basename(sliceDir)}\` to commit a stable frozen slice.`
20268
20884
  );
20269
20885
  }
20270
20886
  wayaiFiles = readDirToFileMap(sliceDir);
20271
20887
  }
20272
- const readmePath = path27.join(templateDir, `README.${locale}.md`);
20273
- const readmeMarkdown = fs21.existsSync(readmePath) ? fs21.readFileSync(readmePath, "utf-8") : "";
20888
+ const readmePath = path29.join(templateDir, `README.${locale}.md`);
20889
+ const readmeMarkdown = fs23.existsSync(readmePath) ? fs23.readFileSync(readmePath, "utf-8") : "";
20274
20890
  locales[locale] = {
20275
20891
  readmeMarkdown,
20276
20892
  ...wayaiFiles && { wayaiFiles },
@@ -20281,9 +20897,9 @@ async function runTemplatePush(positional) {
20281
20897
  }
20282
20898
  let hero;
20283
20899
  if (manifest.hero) {
20284
- const heroPath = path27.resolve(templateDir, manifest.hero);
20285
- if (fs21.existsSync(heroPath)) {
20286
- hero = { filename: path27.basename(heroPath), dataBase64: fs21.readFileSync(heroPath).toString("base64") };
20900
+ const heroPath = path29.resolve(templateDir, manifest.hero);
20901
+ if (fs23.existsSync(heroPath)) {
20902
+ hero = { filename: path29.basename(heroPath), dataBase64: fs23.readFileSync(heroPath).toString("base64") };
20287
20903
  } else {
20288
20904
  console.error(`Warning: hero not found at ${manifest.hero}; skipping.`);
20289
20905
  }
@@ -20309,7 +20925,7 @@ async function runTemplatePush(positional) {
20309
20925
  }
20310
20926
  }
20311
20927
  function isInsideTemplateDir(dest, templateDir) {
20312
- return dest === templateDir || dest.startsWith(templateDir + path27.sep);
20928
+ return dest === templateDir || dest.startsWith(templateDir + path29.sep);
20313
20929
  }
20314
20930
  async function runTemplateFreeze(positional, flagArgs) {
20315
20931
  const slug = positional[0];
@@ -20370,7 +20986,7 @@ async function runTemplateFreeze(positional, flagArgs) {
20370
20986
  process.exit(1);
20371
20987
  }
20372
20988
  const entry = manifest.wayai.locales[targetLocale];
20373
- const dest = path27.resolve(templateDir, entry.source);
20989
+ const dest = path29.resolve(templateDir, entry.source);
20374
20990
  if (!isInsideTemplateDir(dest, templateDir) || dest === templateDir) {
20375
20991
  console.error(
20376
20992
  `source for "${targetLocale}" must be a committed path inside ${slug}/ (e.g. "./${targetLocale}/slice"), but resolved to:
@@ -20389,7 +21005,7 @@ Set it to a subdir of the template folder and re-run freeze.`
20389
21005
  const hubs = scanWorkspaceHubs(hubsDir);
20390
21006
  if (hubs.length) {
20391
21007
  console.error("Available hubs:");
20392
- for (const h of hubs) console.error(` ${path27.basename(h.hubFolder)} (${h.hubId})`);
21008
+ for (const h of hubs) console.error(` ${path29.basename(h.hubFolder)} (${h.hubId})`);
20393
21009
  }
20394
21010
  console.error(`
20395
21011
  Pass --from <folder|uuid>${selector ? "." : " or run `wayai use <hub>` first."}`);
@@ -20397,12 +21013,12 @@ Pass --from <folder|uuid>${selector ? "." : " or run `wayai use <hub>` first."}`
20397
21013
  }
20398
21014
  const liveFiles = readDirToFileMap(hubFolder);
20399
21015
  if (Object.keys(liveFiles).length === 0) {
20400
- console.error(`Source hub folder has no files to freeze: ${path27.relative(gitRoot, hubFolder)}`);
21016
+ console.error(`Source hub folder has no files to freeze: ${path29.relative(gitRoot, hubFolder)}`);
20401
21017
  process.exit(1);
20402
21018
  }
20403
21019
  const sanitized = sanitizeWayaiSliceFiles(liveFiles);
20404
- const relDest = path27.relative(gitRoot, dest);
20405
- const relSrc = path27.relative(gitRoot, hubFolder);
21020
+ const relDest = path29.relative(gitRoot, dest);
21021
+ const relSrc = path29.relative(gitRoot, hubFolder);
20406
21022
  if (dryRun) {
20407
21023
  const existing = new Set(listDirRelFiles(dest));
20408
21024
  const nextKeys = new Set(Object.keys(sanitized));
@@ -20462,7 +21078,7 @@ async function runTemplatePull2(positional, flagArgs) {
20462
21078
  console.log(JSON.stringify(detail, null, 2));
20463
21079
  return;
20464
21080
  }
20465
- const target = outDir ? path27.resolve(outDir) : path27.join(process.cwd(), "template-store-pull", slug);
21081
+ const target = outDir ? path29.resolve(outDir) : path29.join(process.cwd(), "template-store-pull", slug);
20466
21082
  const written = writeFileMap(target, detail.files);
20467
21083
  console.log(`Pulled "${slug}" from the store \u2192 ${target} (${written.length} file${written.length === 1 ? "" : "s"}).`);
20468
21084
  const uses = detail.uses ?? [];
@@ -20485,13 +21101,13 @@ async function runSkillInstall(positional) {
20485
21101
  throw err;
20486
21102
  }
20487
21103
  const root = findGitRoot() ?? process.cwd();
20488
- const present = HARNESS_SKILL_DIRS.filter((dir) => fs21.existsSync(path27.join(root, dir)));
21104
+ const present = HARNESS_SKILL_DIRS.filter((dir) => fs23.existsSync(path29.join(root, dir)));
20489
21105
  const targets = present.length > 0 ? present : HARNESS_SKILL_DIRS;
20490
21106
  const fileCount = Object.keys(res.files).length;
20491
21107
  const relDirs = targets.map((harness) => {
20492
- const targetDir = path27.join(root, harness, "skills", name);
21108
+ const targetDir = path29.join(root, harness, "skills", name);
20493
21109
  writeFileMap(targetDir, res.files);
20494
- return `${path27.relative(root, targetDir)}/`;
21110
+ return `${path29.relative(root, targetDir)}/`;
20495
21111
  });
20496
21112
  console.log(`Installed skill "${name}" (${fileCount} file${fileCount === 1 ? "" : "s"}) \u2192 ${relDirs.join(", ")}`);
20497
21113
  console.log("Reload your agent (e.g. restart Claude Code) to pick up the skill.");
@@ -21833,9 +22449,9 @@ var init_update = __esm({
21833
22449
 
21834
22450
  // src/index.ts
21835
22451
  init_sentry();
21836
- import { readFileSync as readFileSync19 } from "fs";
22452
+ import { readFileSync as readFileSync20 } from "fs";
21837
22453
  import { fileURLToPath as fileURLToPath2 } from "url";
21838
- import { dirname as dirname9, join as join25 } from "path";
22454
+ import { dirname as dirname9, join as join27 } from "path";
21839
22455
 
21840
22456
  // src/lib/errors.ts
21841
22457
  init_api_client();
@@ -21867,7 +22483,7 @@ init_skill_version();
21867
22483
  import { exec } from "child_process";
21868
22484
  var REFRESH_TIMEOUT_MS = 1e4;
21869
22485
  function refreshCliCache() {
21870
- return new Promise((resolve4) => {
22486
+ return new Promise((resolve6) => {
21871
22487
  exec("npm view @wayai/cli version", { timeout: REFRESH_TIMEOUT_MS }, (err, stdout) => {
21872
22488
  if (!err) {
21873
22489
  const latest = stdout.trim();
@@ -21878,7 +22494,7 @@ function refreshCliCache() {
21878
22494
  }
21879
22495
  }
21880
22496
  }
21881
- resolve4();
22497
+ resolve6();
21882
22498
  });
21883
22499
  });
21884
22500
  }
@@ -21904,8 +22520,8 @@ async function refreshSkillCache() {
21904
22520
  }
21905
22521
  async function refreshAdminSkillCache() {
21906
22522
  let timer;
21907
- const deadline = new Promise((resolve4) => {
21908
- timer = setTimeout(resolve4, REFRESH_TIMEOUT_MS);
22523
+ const deadline = new Promise((resolve6) => {
22524
+ timer = setTimeout(resolve6, REFRESH_TIMEOUT_MS);
21909
22525
  });
21910
22526
  await Promise.race([deadline, fetchAndCacheAdminSkillVersion().catch(() => {
21911
22527
  })]);
@@ -22015,7 +22631,7 @@ Run \`wayai admin skill install\` to update.`);
22015
22631
 
22016
22632
  // src/index.ts
22017
22633
  var __dirname = dirname9(fileURLToPath2(import.meta.url));
22018
- var pkg = JSON.parse(readFileSync19(join25(__dirname, "..", "package.json"), "utf-8"));
22634
+ var pkg = JSON.parse(readFileSync20(join27(__dirname, "..", "package.json"), "utf-8"));
22019
22635
  var [, , command, ...args] = process.argv;
22020
22636
  var isBackgroundRefresh = command === REFRESH_COMMAND;
22021
22637
  if (!isBackgroundRefresh) initSentry(command, pkg.version);