@wayai/cli 0.3.103 → 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();
@@ -11978,8 +12417,8 @@ __export(push_exports, {
11978
12417
  selectExistingHub: () => selectExistingHub,
11979
12418
  shouldWarnIgnoredPreviewLabel: () => shouldWarnIgnoredPreviewLabel
11980
12419
  });
11981
- import * as fs13 from "fs";
11982
- import * as path15 from "path";
12420
+ import * as fs15 from "fs";
12421
+ import * as path17 from "path";
11983
12422
  import * as yaml6 from "js-yaml";
11984
12423
  function parseArgs5(args2) {
11985
12424
  let autoConfirm = false;
@@ -12072,13 +12511,13 @@ function printErrors(errors) {
12072
12511
  }
12073
12512
  }
12074
12513
  async function autoRenameAgentFiles(client, hubId, hubFolder, organizationId) {
12075
- const agentsDir = path15.join(hubFolder, "agents");
12514
+ const agentsDir = path17.join(hubFolder, "agents");
12076
12515
  let agentsWithIds = [];
12077
- if (fs13.existsSync(agentsDir)) {
12078
- 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"));
12079
12518
  for (const file of yamlFiles) {
12080
12519
  try {
12081
- const content = fs13.readFileSync(path15.join(agentsDir, file), "utf-8");
12520
+ const content = fs15.readFileSync(path17.join(agentsDir, file), "utf-8");
12082
12521
  const agent = yaml6.load(content);
12083
12522
  if (agent?.id && agent.name) {
12084
12523
  agentsWithIds.push({ id: agent.id, name: agent.name });
@@ -12091,7 +12530,7 @@ async function autoRenameAgentFiles(client, hubId, hubFolder, organizationId) {
12091
12530
  if (agentsWithIds.length === 0) {
12092
12531
  const yamlPath = resolveHubYamlPath(hubFolder);
12093
12532
  if (!yamlPath) return;
12094
- const yamlContent = fs13.readFileSync(yamlPath, "utf-8");
12533
+ const yamlContent = fs15.readFileSync(yamlPath, "utf-8");
12095
12534
  const config = yaml6.load(yamlContent);
12096
12535
  agentsWithIds = (config.agents || []).filter((a) => !!a.id && !!a.name);
12097
12536
  }
@@ -12121,18 +12560,18 @@ async function autoRenameAgentFiles(client, hubId, hubFolder, organizationId) {
12121
12560
  }
12122
12561
  }
12123
12562
  if (renames.length === 0) return;
12124
- if (!fs13.existsSync(agentsDir)) return;
12125
- for (const file of fs13.readdirSync(agentsDir)) {
12563
+ if (!fs15.existsSync(agentsDir)) return;
12564
+ for (const file of fs15.readdirSync(agentsDir)) {
12126
12565
  if (file.startsWith("__rename_temp_") && (file.endsWith(".md") || file.endsWith(".yaml"))) {
12127
12566
  console.warn(` Warning: removing orphaned temp file agents/${file}`);
12128
- fs13.unlinkSync(path15.join(agentsDir, file));
12567
+ fs15.unlinkSync(path17.join(agentsDir, file));
12129
12568
  }
12130
12569
  }
12131
12570
  const renameFileIfExists = (dir, oldName, newName) => {
12132
- const oldPath = path15.join(dir, oldName);
12133
- const newPath = path15.join(dir, newName);
12134
- if (!fs13.existsSync(oldPath)) return false;
12135
- 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);
12136
12575
  return true;
12137
12576
  };
12138
12577
  const oldSlugs = new Set(renames.map((r) => r.oldSlug));
@@ -12165,9 +12604,9 @@ async function autoRenameAgentFiles(client, hubId, hubFolder, organizationId) {
12165
12604
  }
12166
12605
  } else {
12167
12606
  for (const { oldSlug, newSlug } of renames) {
12168
- 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}`)));
12169
12608
  if (!hasOldFile) continue;
12170
- 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}`)));
12171
12610
  if (hasNewFile) {
12172
12611
  console.warn(` Warning: skipping rename agents/${oldSlug}.* \u2192 agents/${newSlug}.* (target already exists)`);
12173
12612
  continue;
@@ -12182,7 +12621,7 @@ async function autoRenameAgentFiles(client, hubId, hubFolder, organizationId) {
12182
12621
  if (completedRenames.length === 0) return;
12183
12622
  const mainYamlPath = resolveHubYamlPath(hubFolder);
12184
12623
  if (mainYamlPath) {
12185
- const mainYamlContent = fs13.readFileSync(mainYamlPath, "utf-8");
12624
+ const mainYamlContent = fs15.readFileSync(mainYamlPath, "utf-8");
12186
12625
  const substitutionMap = /* @__PURE__ */ new Map();
12187
12626
  for (const { oldSlug, newSlug } of completedRenames) {
12188
12627
  substitutionMap.set(`agents/${oldSlug}.md`, `agents/${newSlug}.md`);
@@ -12193,8 +12632,8 @@ async function autoRenameAgentFiles(client, hubId, hubFolder, organizationId) {
12193
12632
  (_match, prefix, pathMatch) => `${prefix}${substitutionMap.get(pathMatch) ?? pathMatch}`
12194
12633
  );
12195
12634
  if (updatedYaml !== mainYamlContent) {
12196
- fs13.writeFileSync(mainYamlPath, updatedYaml, "utf-8");
12197
- 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)}`);
12198
12637
  }
12199
12638
  }
12200
12639
  }
@@ -12255,7 +12694,7 @@ async function pushSingleHub(client, hubId, hubFolder, autoConfirm, organization
12255
12694
  function selectExistingHub(workspaceDir, allHubs, selector, wsLabel) {
12256
12695
  if (selector) {
12257
12696
  const match = allHubs.find(
12258
- (h) => h.hubId === selector || path15.basename(h.hubFolder) === selector
12697
+ (h) => h.hubId === selector || path17.basename(h.hubFolder) === selector
12259
12698
  );
12260
12699
  if (!match) {
12261
12700
  console.error(`No hub matching --hub ${selector} found in ${wsLabel}/.`);
@@ -12298,7 +12737,7 @@ New hub: "${newHub.hubName}" (${hubType})`);
12298
12737
  process.exit(1);
12299
12738
  }
12300
12739
  console.log(`Hub created: ${createdHub.hub_name} (${hubId})`);
12301
- const content = fs13.readFileSync(hubYamlPath, "utf-8");
12740
+ const content = fs15.readFileSync(hubYamlPath, "utf-8");
12302
12741
  const hasVersion = content.match(/^version:\s/m);
12303
12742
  let updated;
12304
12743
  if (hasVersion) {
@@ -12314,7 +12753,7 @@ hub_id: "${hubId}"
12314
12753
  hub_environment: preview
12315
12754
  ${content}`;
12316
12755
  }
12317
- fs13.writeFileSync(hubYamlPath, updated, "utf-8");
12756
+ fs15.writeFileSync(hubYamlPath, updated, "utf-8");
12318
12757
  autoBindIfUnbound(hubId);
12319
12758
  await pushSingleHub(client, hubId, newHub.hubFolder, opts.autoConfirm, opts.organizationId, { skipAgentRename: true });
12320
12759
  }
@@ -12323,7 +12762,7 @@ function reportUnresolvedPushTarget(reason, existingHubs, newHubs, selector, wsL
12323
12762
  if (existingHubs.length > 0) {
12324
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\`:`);
12325
12764
  for (const h of existingHubs) {
12326
- console.error(` ${path15.basename(h.hubFolder)} (${h.hubId}, production mirror)`);
12765
+ console.error(` ${path17.basename(h.hubFolder)} (${h.hubId}, production mirror)`);
12327
12766
  }
12328
12767
  } else {
12329
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\`).`);
@@ -12336,10 +12775,10 @@ function reportUnresolvedPushTarget(reason, existingHubs, newHubs, selector, wsL
12336
12775
  console.error(`Multiple hub folders found in ${wsLabel}/. Pass --hub <uuid|folder-name> to choose, or run from inside a hub folder:`);
12337
12776
  }
12338
12777
  for (const h of existingHubs) {
12339
- console.error(` ${path15.basename(h.hubFolder)} (${h.hubId})`);
12778
+ console.error(` ${path17.basename(h.hubFolder)} (${h.hubId})`);
12340
12779
  }
12341
12780
  for (const h of newHubs) {
12342
- console.error(` ${path15.basename(h.hubFolder)} (new \u2014 "${h.hubName}")`);
12781
+ console.error(` ${path17.basename(h.hubFolder)} (new \u2014 "${h.hubName}")`);
12343
12782
  }
12344
12783
  process.exit(1);
12345
12784
  }
@@ -12358,7 +12797,7 @@ async function pushCommand(args2) {
12358
12797
  console.error(`Created ${file}`);
12359
12798
  }
12360
12799
  }
12361
- if (!fs13.existsSync(workspaceDir)) {
12800
+ if (!fs15.existsSync(workspaceDir)) {
12362
12801
  console.error(`No ${wsLabel}/ directory found. Run \`wayai pull\` first or create hub files in ${wsLabel}/<hub>/hub.yaml.`);
12363
12802
  process.exit(1);
12364
12803
  }
@@ -12377,8 +12816,8 @@ async function pushCommand(args2) {
12377
12816
  if (label) {
12378
12817
  console.warn("Note: --label is only used when creating a new hub. To change an existing hub's label, use `wayai relabel`.");
12379
12818
  }
12380
- console.log(`Target hub: ${path15.basename(existing.hubFolder)} (${existing.hubId})`);
12381
- 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));
12382
12821
  await pushSingleHub(client, existing.hubId, existing.hubFolder, autoConfirm, organizationId);
12383
12822
  return;
12384
12823
  }
@@ -12408,8 +12847,8 @@ var create_exports = {};
12408
12847
  __export(create_exports, {
12409
12848
  createCommand: () => createCommand
12410
12849
  });
12411
- import * as path16 from "path";
12412
- import * as fs14 from "fs";
12850
+ import * as path18 from "path";
12851
+ import * as fs16 from "fs";
12413
12852
  function parseArgs6(args2) {
12414
12853
  let autoConfirm = false;
12415
12854
  let folderSelector;
@@ -12436,7 +12875,7 @@ async function createCommand(args2) {
12436
12875
  const gitRoot = findGitRoot();
12437
12876
  const wsLabel = hubsDirLabel(gitRoot);
12438
12877
  if (gitRoot) warnLayoutOnce(gitRoot);
12439
- if (!fs14.existsSync(workspaceDir)) {
12878
+ if (!fs16.existsSync(workspaceDir)) {
12440
12879
  console.error(`No ${wsLabel}/ directory found. Create hub files in ${wsLabel}/<hub>/hub.yaml with \`hub: { name: ... }\` first.`);
12441
12880
  process.exit(1);
12442
12881
  }
@@ -12445,7 +12884,7 @@ async function createCommand(args2) {
12445
12884
  const resolution = resolveNewHubForCreate(workspaceDir, existingHubs, newHubs, folderSelector);
12446
12885
  if (!resolution.ok) {
12447
12886
  if (resolution.reason === "exists") {
12448
- const folder = path16.basename(resolution.existing.hubFolder);
12887
+ const folder = path18.basename(resolution.existing.hubFolder);
12449
12888
  console.error(`Hub "${folder}" already exists (${resolution.existing.hubId}). Use \`wayai push --hub ${folder}\` to update it.`);
12450
12889
  process.exit(1);
12451
12890
  }
@@ -12459,7 +12898,7 @@ async function createCommand(args2) {
12459
12898
  }
12460
12899
  console.error(`Multiple new hub folders found in ${wsLabel}/. Pass the folder name (\`wayai create <folder>\`) or run from inside one:`);
12461
12900
  for (const h of newHubs) {
12462
- console.error(` ${path16.basename(h.hubFolder)} (new \u2014 "${h.hubName}")`);
12901
+ console.error(` ${path18.basename(h.hubFolder)} (new \u2014 "${h.hubName}")`);
12463
12902
  }
12464
12903
  process.exit(1);
12465
12904
  }
@@ -12641,12 +13080,12 @@ function typeMatches2(typeField, allowed) {
12641
13080
  if (Array.isArray(typeField)) return typeField.every((t) => typeof t === "string" && allowed.has(t));
12642
13081
  return false;
12643
13082
  }
12644
- function validateSchema2(schema, path28, errors, opts = {}) {
13083
+ function validateSchema2(schema, path30, errors, opts = {}) {
12645
13084
  if (typeof schema === "boolean") return;
12646
13085
  const depth = opts.depth ?? 0;
12647
13086
  if (depth > MAX_SCHEMA_DEPTH2) {
12648
13087
  errors.push({
12649
- path: path28 || "<root>",
13088
+ path: path30 || "<root>",
12650
13089
  message: `schema nesting exceeds ${MAX_SCHEMA_DEPTH2} levels`,
12651
13090
  suggestion: "Flatten the schema; LLM providers reject deeply nested tool input_schemas."
12652
13091
  });
@@ -12654,7 +13093,7 @@ function validateSchema2(schema, path28, errors, opts = {}) {
12654
13093
  }
12655
13094
  if (!isRecord2(schema)) {
12656
13095
  errors.push({
12657
- path: path28,
13096
+ path: path30,
12658
13097
  message: `expected object, got ${schema === null ? "null" : typeof schema}`
12659
13098
  });
12660
13099
  return;
@@ -12662,14 +13101,14 @@ function validateSchema2(schema, path28, errors, opts = {}) {
12662
13101
  if (opts.isRoot) {
12663
13102
  if ("type" in schema && schema.type !== "object") {
12664
13103
  errors.push({
12665
- path: path28 ? `${path28}.type` : "type",
13104
+ path: path30 ? `${path30}.type` : "type",
12666
13105
  message: `root type must be 'object', got ${JSON.stringify(schema.type)}`,
12667
13106
  suggestion: "Tool parameters at the root must be an object: `{ type: 'object', properties: { ... } }`."
12668
13107
  });
12669
13108
  }
12670
13109
  } else if ("type" in schema && !typeMatches2(schema.type, ALLOWED_TYPES2)) {
12671
13110
  errors.push({
12672
- path: `${path28}.type`,
13111
+ path: `${path30}.type`,
12673
13112
  message: `type must be one of ${[...ALLOWED_TYPES2].join("/")} or an array of those, got ${JSON.stringify(schema.type)}`
12674
13113
  });
12675
13114
  }
@@ -12678,25 +13117,25 @@ function validateSchema2(schema, path28, errors, opts = {}) {
12678
13117
  if (typeof e === "string") {
12679
13118
  const isPlaceholder = PLACEHOLDER_TOKENS2.includes(e);
12680
13119
  errors.push({
12681
- path: `${path28}.enum`,
13120
+ path: `${path30}.enum`,
12682
13121
  message: `enum must be a non-empty array of primitives, got string ${JSON.stringify(e)}`,
12683
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"]`.'
12684
13123
  });
12685
13124
  } else if (!Array.isArray(e)) {
12686
13125
  errors.push({
12687
- path: `${path28}.enum`,
13126
+ path: `${path30}.enum`,
12688
13127
  message: `enum must be a non-empty array of primitives, got ${typeof e}`
12689
13128
  });
12690
13129
  } else if (e.length === 0) {
12691
13130
  errors.push({
12692
- path: `${path28}.enum`,
13131
+ path: `${path30}.enum`,
12693
13132
  message: "enum must not be empty"
12694
13133
  });
12695
13134
  } else {
12696
13135
  for (let i = 0; i < e.length; i++) {
12697
13136
  if (!isPrimitive2(e[i])) {
12698
13137
  errors.push({
12699
- path: `${path28}.enum[${i}]`,
13138
+ path: `${path30}.enum[${i}]`,
12700
13139
  message: `enum entry must be a primitive (string/number/boolean/null), got ${typeof e[i]}`
12701
13140
  });
12702
13141
  }
@@ -12706,7 +13145,7 @@ function validateSchema2(schema, path28, errors, opts = {}) {
12706
13145
  for (const key of ["exclusiveMinimum", "exclusiveMaximum"]) {
12707
13146
  if (key in schema && typeof schema[key] === "boolean") {
12708
13147
  errors.push({
12709
- path: `${path28}.${key}`,
13148
+ path: `${path30}.${key}`,
12710
13149
  message: `${key} as boolean is draft-04 syntax; draft 2020-12 requires a numeric value`,
12711
13150
  suggestion: `Replace with the numeric bound, e.g. \`${key}: 0\`.`
12712
13151
  });
@@ -12715,45 +13154,45 @@ function validateSchema2(schema, path28, errors, opts = {}) {
12715
13154
  if ("properties" in schema) {
12716
13155
  if (!isRecord2(schema.properties)) {
12717
13156
  errors.push({
12718
- path: `${path28}.properties`,
13157
+ path: `${path30}.properties`,
12719
13158
  message: `properties must be an object, got ${Array.isArray(schema.properties) ? "array" : typeof schema.properties}`
12720
13159
  });
12721
13160
  } else {
12722
13161
  for (const [propName, propSchema] of Object.entries(schema.properties)) {
12723
- validateSchema2(propSchema, `${path28}.properties.${propName}`, errors, { depth: depth + 1 });
13162
+ validateSchema2(propSchema, `${path30}.properties.${propName}`, errors, { depth: depth + 1 });
12724
13163
  }
12725
13164
  }
12726
13165
  }
12727
13166
  if (schemaTypeIncludes2(schema, "array") && "items" in schema) {
12728
13167
  if (Array.isArray(schema.items)) {
12729
- 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 }));
12730
13169
  } else {
12731
- validateSchema2(schema.items, `${path28}.items`, errors, { depth: depth + 1 });
13170
+ validateSchema2(schema.items, `${path30}.items`, errors, { depth: depth + 1 });
12732
13171
  }
12733
13172
  }
12734
13173
  for (const key of SUBSCHEMA_OBJECT_KEYWORDS2) {
12735
13174
  if (key in schema && isRecord2(schema[key])) {
12736
- validateSchema2(schema[key], `${path28}.${key}`, errors, { depth: depth + 1 });
13175
+ validateSchema2(schema[key], `${path30}.${key}`, errors, { depth: depth + 1 });
12737
13176
  }
12738
13177
  }
12739
13178
  for (const key of SUBSCHEMA_LIST_KEYWORDS2) {
12740
13179
  const list = schema[key];
12741
13180
  if (Array.isArray(list)) {
12742
- 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 }));
12743
13182
  }
12744
13183
  }
12745
13184
  for (const key of SUBSCHEMA_MAP_KEYWORDS2) {
12746
13185
  const map = schema[key];
12747
13186
  if (isRecord2(map)) {
12748
13187
  for (const [name, sub] of Object.entries(map)) {
12749
- validateSchema2(sub, `${path28}.${key}.${name}`, errors, { depth: depth + 1 });
13188
+ validateSchema2(sub, `${path30}.${key}.${name}`, errors, { depth: depth + 1 });
12750
13189
  }
12751
13190
  }
12752
13191
  }
12753
13192
  const reportedPaths = new Set(errors.map((e) => e.path));
12754
13193
  for (const [k, v] of Object.entries(schema)) {
12755
13194
  if (typeof v !== "string") continue;
12756
- const fieldPath = `${path28}.${k}`;
13195
+ const fieldPath = `${path30}.${k}`;
12757
13196
  if (reportedPaths.has(fieldPath)) continue;
12758
13197
  for (const token of PLACEHOLDER_TOKENS2) {
12759
13198
  if (v === token) {
@@ -12828,15 +13267,17 @@ function describeRole2(role) {
12828
13267
  return `a ${typeof role}`;
12829
13268
  }
12830
13269
  function asEvalInputRecord2(input) {
12831
- const value = typeof input === "string" ? (() => {
12832
- try {
12833
- return JSON.parse(input);
12834
- } catch {
12835
- return null;
12836
- }
12837
- })() : input;
13270
+ const value = parseMaybeJson2(input);
12838
13271
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
12839
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
+ }
12840
13281
  function evalInputRoleError2(input) {
12841
13282
  const record = asEvalInputRecord2(input);
12842
13283
  if (record === null) return null;
@@ -12847,6 +13288,32 @@ function evalInputRoleError2(input) {
12847
13288
  }
12848
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`;
12849
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
+ }
12850
13317
  function refineEvalMessageTextRole2(value, ctx) {
12851
13318
  const error = evalInputRoleError2(value.eval.message_text);
12852
13319
  if (error) {
@@ -12857,6 +13324,38 @@ function refineEvalMessageTextRole2(value, ctx) {
12857
13324
  });
12858
13325
  }
12859
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
+ }
12860
13359
  function refineHubAsCodeCustomTools2(config, ctx) {
12861
13360
  const agents = config.agents;
12862
13361
  if (!Array.isArray(agents)) return;
@@ -12964,10 +13463,84 @@ function refineHubAsCodeEvals2(config, ctx) {
12964
13463
  }
12965
13464
  }
12966
13465
  }
12967
- function isSafeTemplatePath2(path28) {
12968
- if (path28.length === 0 || path28.length > 300) return false;
12969
- if (path28.startsWith("/") || path28.includes("\\")) return false;
12970
- 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("/");
12971
13544
  return segments.every(
12972
13545
  (seg) => seg.length > 0 && seg !== "." && seg !== ".." && /^[A-Za-z0-9._-]+$/.test(seg)
12973
13546
  );
@@ -12995,14 +13568,14 @@ function isPlainObject(value) {
12995
13568
  const proto = Object.getPrototypeOf(value);
12996
13569
  return proto === Object.prototype || proto === null;
12997
13570
  }
12998
- function isSanitizableSliceFile(path28) {
12999
- if (!/\.ya?ml$/i.test(path28)) return false;
13000
- 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/");
13001
13574
  }
13002
13575
  function distinctUsesBackends(uses) {
13003
13576
  return [...new Set((uses ?? []).map((u) => u.backend))];
13004
13577
  }
13005
- 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;
13006
13579
  var init_contracts = __esm({
13007
13580
  "../../packages/core/dist/contracts/index.js"() {
13008
13581
  "use strict";
@@ -14174,10 +14747,10 @@ var init_contracts = __esm({
14174
14747
  ROLE_ECHO_MAX2 = 40;
14175
14748
  createEvalBody2 = external_exports.object({
14176
14749
  eval: external_exports.record(external_exports.unknown())
14177
- }).superRefine(refineEvalMessageTextRole2);
14750
+ }).superRefine(refineEvalMessageTextRole2).superRefine(refineEvalAttachments2);
14178
14751
  updateEvalBody2 = external_exports.object({
14179
14752
  eval: external_exports.record(external_exports.unknown())
14180
- }).superRefine(refineEvalMessageTextRole2);
14753
+ }).superRefine(refineEvalMessageTextRole2).superRefine(refineEvalAttachments2);
14181
14754
  getEvalsQuery2 = external_exports.object({
14182
14755
  hub_id: external_exports.string().uuid(),
14183
14756
  agent_id: external_exports.string().uuid().optional(),
@@ -14304,6 +14877,15 @@ var init_contracts = __esm({
14304
14877
  journey_name: external_exports.string().min(1).max(200),
14305
14878
  evaluator_instructions: external_exports.string().max(4e3).optional()
14306
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";
14307
14889
  journeyIdParam2 = external_exports.object({ id: external_exports.string().uuid() });
14308
14890
  journeyToolCallSchema2 = external_exports.object({
14309
14891
  id: external_exports.string(),
@@ -14315,7 +14897,10 @@ var init_contracts = __esm({
14315
14897
  content: external_exports.string().nullable().optional(),
14316
14898
  tool_calls: external_exports.array(journeyToolCallSchema2).optional(),
14317
14899
  tool_call_id: external_exports.string().optional(),
14318
- 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
14319
14904
  });
14320
14905
  journeyStepOverrideSchema2 = external_exports.object({
14321
14906
  number_of_runs: external_exports.number().int().positive().max(100).optional(),
@@ -14820,6 +15405,10 @@ var init_contracts = __esm({
14820
15405
  agent_id: external_exports.string().uuid().nullable().optional(),
14821
15406
  trigger_message: external_exports.string().nullable().optional()
14822
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;
14823
15412
  uuidPattern2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
14824
15413
  ciUuidSchema2 = external_exports.string().regex(uuidPattern2, "must be a valid UUID");
14825
15414
  hubAsCodeConfigSchema2 = external_exports.object({
@@ -14832,6 +15421,7 @@ var init_contracts = __esm({
14832
15421
  refineHubAsCodeKanbanStatuses2(config, ctx);
14833
15422
  refineHubAsCodeLanes2(config, ctx);
14834
15423
  refineHubAsCodeEvals2(config, ctx);
15424
+ refineHubAsCodeEvalAttachments2(config, ctx);
14835
15425
  });
14836
15426
  ciPullHubIdParam2 = external_exports.object({
14837
15427
  hub_id: ciUuidSchema2
@@ -15721,8 +16311,8 @@ var replicate_exports = {};
15721
16311
  __export(replicate_exports, {
15722
16312
  replicateCommand: () => replicateCommand
15723
16313
  });
15724
- import * as fs15 from "fs";
15725
- import * as path17 from "path";
16314
+ import * as fs17 from "fs";
16315
+ import * as path19 from "path";
15726
16316
  function parseArgs8(args2) {
15727
16317
  let label;
15728
16318
  let hubSelector;
@@ -15768,7 +16358,7 @@ async function replicateCommand(args2) {
15768
16358
  payload.preview_label,
15769
16359
  payload.branch_name
15770
16360
  );
15771
- fs15.mkdirSync(path17.dirname(hubFolder), { recursive: true });
16361
+ fs17.mkdirSync(path19.dirname(hubFolder), { recursive: true });
15772
16362
  writeHubFolder(hubFolder, payload);
15773
16363
  await downloadBinaryResourceFiles(hubFolder, payload);
15774
16364
  hubFolder = autoRenameHubFolder(
@@ -15780,8 +16370,8 @@ async function replicateCommand(args2) {
15780
16370
  payload.branch_name
15781
16371
  );
15782
16372
  autoBindIfUnbound(previewHubId);
15783
- console.log(`Preview written to ${path17.basename(hubFolder)}`);
15784
- 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)}`);
15785
16375
  }
15786
16376
  var init_replicate = __esm({
15787
16377
  "src/commands/replicate.ts"() {
@@ -15803,7 +16393,7 @@ var relabel_exports = {};
15803
16393
  __export(relabel_exports, {
15804
16394
  relabelCommand: () => relabelCommand
15805
16395
  });
15806
- import * as path18 from "path";
16396
+ import * as path20 from "path";
15807
16397
  function parseArgs9(args2) {
15808
16398
  let label;
15809
16399
  let clear = false;
@@ -15847,7 +16437,7 @@ async function relabelCommand(args2) {
15847
16437
  } else {
15848
16438
  console.error(`Multiple hubs found in ${wsLabel}/. Pass --hub <uuid|folder-name> to choose, or run from inside a hub folder:`);
15849
16439
  for (const h of previewHubs) {
15850
- console.error(` ${path18.basename(h.hubFolder)} (${h.hubId})`);
16440
+ console.error(` ${path20.basename(h.hubFolder)} (${h.hubId})`);
15851
16441
  }
15852
16442
  }
15853
16443
  process.exit(1);
@@ -15856,7 +16446,7 @@ async function relabelCommand(args2) {
15856
16446
  console.log("This is a read-only production mirror \u2014 production hubs have no preview label. Relabel the linked preview hub instead.");
15857
16447
  return;
15858
16448
  }
15859
- assertHubMatchesBinding(target.hubId, path18.basename(target.hubFolder));
16449
+ assertHubMatchesBinding(target.hubId, path20.basename(target.hubFolder));
15860
16450
  console.log(normalizedLabel ? `Setting preview label to "${normalizedLabel}"...` : "Clearing preview label...");
15861
16451
  const { data } = await client.relabelPreview(target.hubId, normalizedLabel);
15862
16452
  const row = data[0];
@@ -15872,7 +16462,7 @@ async function relabelCommand(args2) {
15872
16462
  );
15873
16463
  console.log(serverLabel ? `Preview label set to "${serverLabel}".` : "Preview label cleared.");
15874
16464
  if (finalFolder !== target.hubFolder) {
15875
- console.log(`Hub folder is now ${path18.basename(finalFolder)}`);
16465
+ console.log(`Hub folder is now ${path20.basename(finalFolder)}`);
15876
16466
  }
15877
16467
  }
15878
16468
  var init_relabel = __esm({
@@ -15898,7 +16488,7 @@ __export(publish_exports, {
15898
16488
  renderHubDiff: () => renderHubDiff,
15899
16489
  runPublish: () => runPublish
15900
16490
  });
15901
- import * as path19 from "path";
16491
+ import * as path21 from "path";
15902
16492
  function parseArgs10(args2) {
15903
16493
  let autoConfirm = false;
15904
16494
  let hubSelector;
@@ -16004,7 +16594,7 @@ async function publishCommand(args2) {
16004
16594
  return;
16005
16595
  }
16006
16596
  }
16007
- assertHubMatchesBinding(hubId, hubFolder ? path19.basename(hubFolder) : void 0);
16597
+ assertHubMatchesBinding(hubId, hubFolder ? path21.basename(hubFolder) : void 0);
16008
16598
  await runPublish(client, { hubId, localConfig, organizationId, autoConfirm });
16009
16599
  }
16010
16600
  var STATUS_STYLE;
@@ -16036,7 +16626,7 @@ var use_exports = {};
16036
16626
  __export(use_exports, {
16037
16627
  useCommand: () => useCommand
16038
16628
  });
16039
- import * as path20 from "path";
16629
+ import * as path22 from "path";
16040
16630
  async function useCommand(args2) {
16041
16631
  const target = args2[0];
16042
16632
  if (!target || target.startsWith("-")) {
@@ -16058,7 +16648,7 @@ async function useCommand(args2) {
16058
16648
  const match = findHubByFolderName(workspaceDir, target);
16059
16649
  if (!match) {
16060
16650
  const newHubs = scanNewHubs(workspaceDir);
16061
- const pending = newHubs.find((h) => path20.basename(h.hubFolder) === target);
16651
+ const pending = newHubs.find((h) => path22.basename(h.hubFolder) === target);
16062
16652
  if (pending) {
16063
16653
  console.error(
16064
16654
  `"${target}" is a new hub that hasn't been created on the platform yet, so it has no id to bind to.
@@ -16069,10 +16659,10 @@ Create it first \u2014 the worktree binds automatically afterward:
16069
16659
  }
16070
16660
  console.error(`No hub matching "${target}" found in ${wsLabel}/. Pass a UUID or a folder name from:`);
16071
16661
  for (const h of scanWorkspaceHubs(workspaceDir)) {
16072
- console.error(` ${path20.basename(h.hubFolder)} (${h.hubId})`);
16662
+ console.error(` ${path22.basename(h.hubFolder)} (${h.hubId})`);
16073
16663
  }
16074
16664
  for (const h of newHubs) {
16075
- 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)}\`)`);
16076
16666
  }
16077
16667
  process.exit(1);
16078
16668
  }
@@ -16111,11 +16701,11 @@ __export(migrate_exports, {
16111
16701
  migrateCommand: () => migrateCommand
16112
16702
  });
16113
16703
  import { execFileSync as execFileSync3 } from "child_process";
16114
- import * as fs16 from "fs";
16115
- import * as path21 from "path";
16704
+ import * as fs18 from "fs";
16705
+ import * as path23 from "path";
16116
16706
  function isTracked(gitRoot, p) {
16117
16707
  try {
16118
- execFileSync3("git", ["ls-files", "--error-unmatch", "--", path21.relative(gitRoot, p)], {
16708
+ execFileSync3("git", ["ls-files", "--error-unmatch", "--", path23.relative(gitRoot, p)], {
16119
16709
  cwd: gitRoot,
16120
16710
  stdio: ["pipe", "pipe", "pipe"]
16121
16711
  });
@@ -16125,10 +16715,10 @@ function isTracked(gitRoot, p) {
16125
16715
  }
16126
16716
  }
16127
16717
  function moveDir(gitRoot, from, to) {
16128
- fs16.mkdirSync(path21.dirname(to), { recursive: true });
16718
+ fs18.mkdirSync(path23.dirname(to), { recursive: true });
16129
16719
  if (isTracked(gitRoot, from)) {
16130
16720
  try {
16131
- execFileSync3("git", ["mv", path21.relative(gitRoot, from), path21.relative(gitRoot, to)], {
16721
+ execFileSync3("git", ["mv", path23.relative(gitRoot, from), path23.relative(gitRoot, to)], {
16132
16722
  cwd: gitRoot,
16133
16723
  stdio: ["pipe", "pipe", "pipe"]
16134
16724
  });
@@ -16136,7 +16726,7 @@ function moveDir(gitRoot, from, to) {
16136
16726
  } catch {
16137
16727
  }
16138
16728
  }
16139
- fs16.renameSync(from, to);
16729
+ fs18.renameSync(from, to);
16140
16730
  return "fs";
16141
16731
  }
16142
16732
  async function migrateCommand(_args) {
@@ -16145,12 +16735,12 @@ async function migrateCommand(_args) {
16145
16735
  console.error("Not inside a git repository.");
16146
16736
  process.exit(1);
16147
16737
  }
16148
- const rel = (p) => path21.relative(gitRoot, p);
16149
- const newWs = path21.join(gitRoot, WAYAI_LAYOUT.wsDir);
16150
- const legacyWs = path21.join(gitRoot, WAYAI_LAYOUT.legacy.wsDir);
16151
- const legacyOrg = path21.join(gitRoot, WAYAI_LAYOUT.legacy.orgAtRoot);
16152
- const newHubs = path21.join(newWs, WAYAI_LAYOUT.hubsSubdir);
16153
- 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);
16154
16744
  const hasLegacyWs = isDirectory(legacyWs);
16155
16745
  const hasLegacyOrg = isDirectory(legacyOrg);
16156
16746
  if (!hasLegacyWs && !hasLegacyOrg) {
@@ -16224,12 +16814,12 @@ var send_message_exports = {};
16224
16814
  __export(send_message_exports, {
16225
16815
  sendMessageCommand: () => sendMessageCommand
16226
16816
  });
16227
- import * as fs17 from "fs";
16228
- import * as path22 from "path";
16817
+ import * as fs19 from "fs";
16818
+ import * as path24 from "path";
16229
16819
  function statAttachment(filePath) {
16230
16820
  let stat;
16231
16821
  try {
16232
- stat = fs17.statSync(filePath);
16822
+ stat = fs19.statSync(filePath);
16233
16823
  } catch {
16234
16824
  console.error(`Error: file not found: ${filePath}`);
16235
16825
  process.exit(1);
@@ -16241,11 +16831,11 @@ function statAttachment(filePath) {
16241
16831
  return { filePath, size: stat.size };
16242
16832
  }
16243
16833
  function readAttachment(filePath, size) {
16244
- const fileName = path22.basename(filePath);
16245
- const ext = path22.extname(fileName).replace(/^\./, "");
16834
+ const fileName = path24.basename(filePath);
16835
+ const ext = path24.extname(fileName).replace(/^\./, "");
16246
16836
  return {
16247
16837
  file_name: fileName,
16248
- file_binary: fs17.readFileSync(filePath).toString("base64"),
16838
+ file_binary: fs19.readFileSync(filePath).toString("base64"),
16249
16839
  file_size: size,
16250
16840
  ...ext && { file_extension: ext }
16251
16841
  };
@@ -16292,7 +16882,7 @@ async function sendMessageCommand(args2) {
16292
16882
  }
16293
16883
  const stats = filePaths.map(statAttachment);
16294
16884
  const filesTotal = stats.reduce((sum, s) => sum + base64Length(s.size), 0);
16295
- 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(", ");
16296
16886
  if (filesTotal > MAX_MESSAGE_BODY_BYTES2) {
16297
16887
  console.error(
16298
16888
  `Error: ${fileDetail()} encode to ~${asMb(filesTotal)} MB, over the ${asMb(MAX_MESSAGE_BODY_BYTES2)} MB per-request limit.`
@@ -17527,7 +18117,7 @@ Timeout after ${timeoutSeconds}s. Session is still running.`);
17527
18117
  console.log(`Check results: wayai eval-results --session ${sessionId}`);
17528
18118
  }
17529
18119
  function sleep(ms) {
17530
- return new Promise((resolve4) => setTimeout(resolve4, ms));
18120
+ return new Promise((resolve6) => setTimeout(resolve6, ms));
17531
18121
  }
17532
18122
  function parsePacing(raw) {
17533
18123
  const NAMED = EVAL_PACING_PRESET_NAMES.filter((p) => p !== "custom");
@@ -17752,8 +18342,8 @@ var eval_capture_exports = {};
17752
18342
  __export(eval_capture_exports, {
17753
18343
  evalCaptureCommand: () => evalCaptureCommand
17754
18344
  });
17755
- import * as fs18 from "fs";
17756
- import * as path23 from "path";
18345
+ import * as fs20 from "fs";
18346
+ import * as path25 from "path";
17757
18347
  import * as yaml7 from "js-yaml";
17758
18348
  function isValidSetName(name) {
17759
18349
  if (name.length === 0 || name === "." || name === "..") return false;
@@ -17819,15 +18409,15 @@ async function evalCaptureCommand(args2) {
17819
18409
  const setFolderName = targetSetName;
17820
18410
  const scenarioName = parsed.evalName ?? `Capture ${parsed.conversationId.slice(0, 8)}`;
17821
18411
  const slug = slugify(scenarioName);
17822
- const evalsDir = path23.join(hubFolder, "evals");
17823
- const targetDir = path23.join(evalsDir, setFolderName);
17824
- const targetPath = path23.join(targetDir, `${slug}.yaml`);
17825
- if (!targetPath.startsWith(evalsDir + path23.sep)) {
17826
- 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.`);
17827
18417
  process.exit(1);
17828
18418
  }
17829
- if (fs18.existsSync(targetPath)) {
17830
- 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.`);
17831
18421
  process.exit(1);
17832
18422
  }
17833
18423
  console.log("Resolving scenario set...");
@@ -17859,9 +18449,9 @@ async function evalCaptureCommand(args2) {
17859
18449
  ...captured.evaluator_instructions ? { evaluator_instructions: captured.evaluator_instructions } : {}
17860
18450
  };
17861
18451
  const yamlObj = buildEvalYamlObject(evalEntry, slug);
17862
- fs18.mkdirSync(targetDir, { recursive: true });
17863
- fs18.writeFileSync(targetPath, yaml7.dump(yamlObj, YAML_DUMP_OPTIONS), "utf-8");
17864
- 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);
17865
18455
  console.log(`
17866
18456
  Wrote ${relPath}`);
17867
18457
  console.log("Run `wayai pull` to refresh the agent display name, then commit. The scenario is already on the platform.");
@@ -19088,23 +19678,23 @@ var init_set_connection_credential = __esm({
19088
19678
  });
19089
19679
 
19090
19680
  // src/lib/org-workspace.ts
19091
- import * as fs19 from "fs";
19092
- import * as path24 from "path";
19681
+ import * as fs21 from "fs";
19682
+ import * as path26 from "path";
19093
19683
  import * as yaml8 from "js-yaml";
19094
19684
  function getOrgDir(gitRoot) {
19095
19685
  return resolveLayout(gitRoot).orgDir;
19096
19686
  }
19097
19687
  function orgManifestExists(orgDir) {
19098
- return fs19.existsSync(path24.join(orgDir, ORG_MANIFEST_NAME));
19688
+ return fs21.existsSync(path26.join(orgDir, ORG_MANIFEST_NAME));
19099
19689
  }
19100
19690
  function parseOrgResources(orgDir) {
19101
- const manifestPath = path24.join(orgDir, ORG_MANIFEST_NAME);
19691
+ const manifestPath = path26.join(orgDir, ORG_MANIFEST_NAME);
19102
19692
  let manifest = {};
19103
- if (fs19.existsSync(manifestPath)) {
19104
- manifest = yaml8.load(fs19.readFileSync(manifestPath, "utf-8")) ?? {};
19693
+ if (fs21.existsSync(manifestPath)) {
19694
+ manifest = yaml8.load(fs21.readFileSync(manifestPath, "utf-8")) ?? {};
19105
19695
  }
19106
19696
  const rawResources = Array.isArray(manifest.resources) ? manifest.resources : [];
19107
- const resourcesDir = path24.join(orgDir, "resources");
19697
+ const resourcesDir = path26.join(orgDir, "resources");
19108
19698
  const resources = rawResources.map((res) => {
19109
19699
  const resource = { name: res.name };
19110
19700
  if (res.id) resource.id = res.id;
@@ -19116,8 +19706,8 @@ function parseOrgResources(orgDir) {
19116
19706
  if (res.environment) resource.environment = res.environment;
19117
19707
  if (Array.isArray(res.tags)) resource.tags = res.tags;
19118
19708
  if (Array.isArray(res.folders)) resource.folders = res.folders;
19119
- const resDir = path24.join(resourcesDir, slugify(resource.name));
19120
- if (fs19.existsSync(resDir)) {
19709
+ const resDir = path26.join(resourcesDir, slugify(resource.name));
19710
+ if (fs21.existsSync(resDir)) {
19121
19711
  const files = scanResourceFiles(resDir, "");
19122
19712
  if (files.length > 0) resource.files = files;
19123
19713
  }
@@ -19126,28 +19716,28 @@ function parseOrgResources(orgDir) {
19126
19716
  return { version: 1, resources };
19127
19717
  }
19128
19718
  function writeOrgResources(orgDir, payload) {
19129
- fs19.mkdirSync(orgDir, { recursive: true });
19719
+ fs21.mkdirSync(orgDir, { recursive: true });
19130
19720
  const resources = payload.resources ?? [];
19131
19721
  const manifestResources = resources.map((r) => {
19132
19722
  const { files: _files, ...rest } = r;
19133
19723
  return rest;
19134
19724
  });
19135
- fs19.writeFileSync(
19136
- path24.join(orgDir, ORG_MANIFEST_NAME),
19725
+ fs21.writeFileSync(
19726
+ path26.join(orgDir, ORG_MANIFEST_NAME),
19137
19727
  yaml8.dump({ version: 1, resources: manifestResources }, YAML_DUMP_OPTIONS),
19138
19728
  "utf-8"
19139
19729
  );
19140
- const resourcesDir = path24.join(orgDir, "resources");
19730
+ const resourcesDir = path26.join(orgDir, "resources");
19141
19731
  const currentSlugs = /* @__PURE__ */ new Set();
19142
19732
  for (const resource of resources) {
19143
19733
  const resSlug = slugify(resource.name);
19144
19734
  currentSlugs.add(resSlug);
19145
- writeResourceFileTree(path24.join(resourcesDir, resSlug), resource.files || []);
19735
+ writeResourceFileTree(path26.join(resourcesDir, resSlug), resource.files || [], orgDir);
19146
19736
  }
19147
- if (fs19.existsSync(resourcesDir)) {
19148
- for (const entry of fs19.readdirSync(resourcesDir, { withFileTypes: true })) {
19737
+ if (fs21.existsSync(resourcesDir)) {
19738
+ for (const entry of fs21.readdirSync(resourcesDir, { withFileTypes: true })) {
19149
19739
  if (entry.isDirectory() && !currentSlugs.has(entry.name)) {
19150
- fs19.rmSync(path24.join(resourcesDir, entry.name), { recursive: true, force: true });
19740
+ fs21.rmSync(path26.join(resourcesDir, entry.name), { recursive: true, force: true });
19151
19741
  }
19152
19742
  }
19153
19743
  }
@@ -19156,8 +19746,8 @@ async function downloadOrgBinaryFiles(orgDir, payload) {
19156
19746
  let count = 0;
19157
19747
  for (const resource of payload.resources ?? []) {
19158
19748
  if (!resource.files) continue;
19159
- const resDir = path24.join(orgDir, "resources", slugify(resource.name));
19160
- count += await downloadBinaryFiles(resDir, resource.files);
19749
+ const resDir = path26.join(orgDir, "resources", slugify(resource.name));
19750
+ count += await downloadBinaryFiles(resDir, resource.files, orgDir);
19161
19751
  }
19162
19752
  if (count > 0) console.log(`Downloaded ${count} binary resource file(s).`);
19163
19753
  }
@@ -19412,20 +20002,20 @@ var init_list = __esm({
19412
20002
  });
19413
20003
 
19414
20004
  // src/lib/template-files.ts
19415
- import * as fs20 from "fs";
19416
- import * as path25 from "path";
20005
+ import * as fs22 from "fs";
20006
+ import * as path27 from "path";
19417
20007
  function readDirToFileMap(rootDir) {
19418
20008
  const out = {};
19419
20009
  function walk(dir, prefix) {
19420
- 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));
19421
20011
  for (const entry of entries) {
19422
- const abs = path25.join(dir, entry.name);
20012
+ const abs = path27.join(dir, entry.name);
19423
20013
  const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
19424
20014
  if (entry.isDirectory()) walk(abs, rel);
19425
- else if (entry.isFile()) out[rel] = fs20.readFileSync(abs, "utf-8");
20015
+ else if (entry.isFile()) out[rel] = fs22.readFileSync(abs, "utf-8");
19426
20016
  }
19427
20017
  }
19428
- if (fs20.existsSync(rootDir)) walk(rootDir, "");
20018
+ if (fs22.existsSync(rootDir)) walk(rootDir, "");
19429
20019
  return out;
19430
20020
  }
19431
20021
  function detectCollisions(targetDir, files) {
@@ -19434,7 +20024,7 @@ function detectCollisions(targetDir, files) {
19434
20024
  if (!isSafeTemplatePath2(rel)) {
19435
20025
  throw new Error(`Refusing to inspect unsafe template path: ${rel}`);
19436
20026
  }
19437
- if (fs20.existsSync(path25.join(targetDir, rel))) collisions.push(rel);
20027
+ if (fs22.existsSync(path27.join(targetDir, rel))) collisions.push(rel);
19438
20028
  }
19439
20029
  return collisions.sort((a, b) => a.localeCompare(b));
19440
20030
  }
@@ -19444,9 +20034,9 @@ function writeFileMap(targetDir, files) {
19444
20034
  if (!isSafeTemplatePath2(rel)) {
19445
20035
  throw new Error(`Refusing to write unsafe template path: ${rel}`);
19446
20036
  }
19447
- const abs = path25.join(targetDir, rel);
19448
- fs20.mkdirSync(path25.dirname(abs), { recursive: true });
19449
- 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");
19450
20040
  written.push(rel);
19451
20041
  }
19452
20042
  return written;
@@ -19454,20 +20044,20 @@ function writeFileMap(targetDir, files) {
19454
20044
  function listDirRelFiles(dir) {
19455
20045
  const out = [];
19456
20046
  function walk(d, prefix) {
19457
- for (const entry of fs20.readdirSync(d, { withFileTypes: true })) {
20047
+ for (const entry of fs22.readdirSync(d, { withFileTypes: true })) {
19458
20048
  const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
19459
- if (entry.isDirectory()) walk(path25.join(d, entry.name), rel);
20049
+ if (entry.isDirectory()) walk(path27.join(d, entry.name), rel);
19460
20050
  else if (entry.isFile()) out.push(rel);
19461
20051
  }
19462
20052
  }
19463
- if (fs20.existsSync(dir)) walk(dir, "");
20053
+ if (fs22.existsSync(dir)) walk(dir, "");
19464
20054
  return out;
19465
20055
  }
19466
20056
  function writeSliceSnapshot(targetDir, files) {
19467
20057
  const keep = new Set(Object.keys(files));
19468
20058
  const removed = listDirRelFiles(targetDir).filter((rel) => !keep.has(rel)).sort((a, b) => a.localeCompare(b));
19469
20059
  const written = writeFileMap(targetDir, files).sort((a, b) => a.localeCompare(b));
19470
- if (fs20.existsSync(targetDir)) cleanOrphanFiles(targetDir, "", keep);
20060
+ if (fs22.existsSync(targetDir)) cleanOrphanFiles(targetDir, "", keep);
19471
20061
  return { written, removed };
19472
20062
  }
19473
20063
  var init_template_files = __esm({
@@ -19483,7 +20073,7 @@ var templates_exports = {};
19483
20073
  __export(templates_exports, {
19484
20074
  templateCommand: () => templateCommand
19485
20075
  });
19486
- import * as path26 from "path";
20076
+ import * as path28 from "path";
19487
20077
  async function templateCommand(args2) {
19488
20078
  const [sub, ...rest] = args2;
19489
20079
  switch (sub) {
@@ -19599,8 +20189,8 @@ async function runTemplatePull(rest) {
19599
20189
  console.log(`Template "${slug}" has no WayAI slice${hint}`);
19600
20190
  return;
19601
20191
  }
19602
- const targetDir = path26.join(resolveLayout(gitRoot).hubsDir, slug);
19603
- const relTarget = path26.relative(gitRoot, targetDir);
20192
+ const targetDir = path28.join(resolveLayout(gitRoot).hubsDir, slug);
20193
+ const relTarget = path28.relative(gitRoot, targetDir);
19604
20194
  const collisions = detectCollisions(targetDir, detail.files);
19605
20195
  if (dryRun) {
19606
20196
  const planned = Object.keys(detail.files).sort((a, b) => a.localeCompare(b));
@@ -19726,21 +20316,37 @@ import * as yaml9 from "js-yaml";
19726
20316
  function sanitizeWayaiSliceFiles(files) {
19727
20317
  const out = {};
19728
20318
  for (const [filePath, body] of Object.entries(files)) {
19729
- out[filePath] = isSanitizableSliceFile(filePath) ? sanitizeOne(body) : body;
20319
+ out[filePath] = isSanitizableSliceFile(filePath) ? sanitizeOne(filePath, body) : body;
19730
20320
  }
19731
20321
  return out;
19732
20322
  }
19733
- function sanitizeOne(body) {
20323
+ function sanitizeOne(filePath, body) {
19734
20324
  let parsed;
19735
20325
  try {
19736
20326
  parsed = yaml9.load(body);
19737
20327
  } catch {
19738
20328
  return body;
19739
20329
  }
19740
- const scrubbed = excludeTemplateAuthorData(stripTemplateInstanceIds(parsed));
20330
+ const scrubbed = stripEvalAttachments(filePath, excludeTemplateAuthorData(stripTemplateInstanceIds(parsed)));
19741
20331
  if (JSON.stringify(parsed) === JSON.stringify(scrubbed)) return body;
19742
20332
  return yaml9.dump(scrubbed, YAML_DUMP_OPTIONS);
19743
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
+ }
19744
20350
  var init_template_sanitize = __esm({
19745
20351
  "src/lib/template-sanitize.ts"() {
19746
20352
  "use strict";
@@ -19774,8 +20380,8 @@ var admin_exports = {};
19774
20380
  __export(admin_exports, {
19775
20381
  adminCommand: () => adminCommand
19776
20382
  });
19777
- import * as fs21 from "fs";
19778
- import * as path27 from "path";
20383
+ import * as fs23 from "fs";
20384
+ import * as path29 from "path";
19779
20385
  import * as yaml10 from "js-yaml";
19780
20386
  async function adminCommand(args2) {
19781
20387
  const [group, ...afterGroup] = args2;
@@ -20097,7 +20703,7 @@ async function runArchiveRead(positional, flagArgs) {
20097
20703
  exitOnApiError(err);
20098
20704
  throw err;
20099
20705
  }
20100
- fs21.writeFileSync(outPath, zip);
20706
+ fs23.writeFileSync(outPath, zip);
20101
20707
  console.log(`Wrote ${zip.byteLength} bytes to ${outPath}`);
20102
20708
  return;
20103
20709
  }
@@ -20229,15 +20835,15 @@ function loadTemplateManifest(slug) {
20229
20835
  console.error("Not in a git repository. Run from the tutorials/ authoring repo.");
20230
20836
  process.exit(1);
20231
20837
  }
20232
- const templateDir = path27.join(resolveTemplatesDir(gitRoot), slug);
20233
- const manifestPath = path27.join(templateDir, "template.yaml");
20234
- if (!fs21.existsSync(manifestPath)) {
20235
- 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)}`);
20236
20842
  process.exit(1);
20237
20843
  }
20238
20844
  let manifest;
20239
20845
  try {
20240
- manifest = templateYamlSchema2.parse(yaml10.load(fs21.readFileSync(manifestPath, "utf-8")));
20846
+ manifest = templateYamlSchema2.parse(yaml10.load(fs23.readFileSync(manifestPath, "utf-8")));
20241
20847
  } catch (err) {
20242
20848
  console.error(`Invalid template.yaml: ${err instanceof Error ? err.message : String(err)}`);
20243
20849
  process.exit(1);
@@ -20267,20 +20873,20 @@ async function runTemplatePush(positional) {
20267
20873
  const wayaiEntry = manifest.wayai.locales[locale];
20268
20874
  let wayaiFiles;
20269
20875
  if (wayaiEntry) {
20270
- const sliceDir = path27.resolve(templateDir, wayaiEntry.source);
20271
- if (!fs21.existsSync(sliceDir)) {
20876
+ const sliceDir = path29.resolve(templateDir, wayaiEntry.source);
20877
+ if (!fs23.existsSync(sliceDir)) {
20272
20878
  console.error(`wayai source not found for ${locale}: ${wayaiEntry.source} (resolved ${sliceDir})`);
20273
20879
  process.exit(1);
20274
20880
  }
20275
20881
  if (!isInsideTemplateDir(sliceDir, templateDir)) {
20276
20882
  console.warn(
20277
- `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.`
20278
20884
  );
20279
20885
  }
20280
20886
  wayaiFiles = readDirToFileMap(sliceDir);
20281
20887
  }
20282
- const readmePath = path27.join(templateDir, `README.${locale}.md`);
20283
- 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") : "";
20284
20890
  locales[locale] = {
20285
20891
  readmeMarkdown,
20286
20892
  ...wayaiFiles && { wayaiFiles },
@@ -20291,9 +20897,9 @@ async function runTemplatePush(positional) {
20291
20897
  }
20292
20898
  let hero;
20293
20899
  if (manifest.hero) {
20294
- const heroPath = path27.resolve(templateDir, manifest.hero);
20295
- if (fs21.existsSync(heroPath)) {
20296
- 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") };
20297
20903
  } else {
20298
20904
  console.error(`Warning: hero not found at ${manifest.hero}; skipping.`);
20299
20905
  }
@@ -20319,7 +20925,7 @@ async function runTemplatePush(positional) {
20319
20925
  }
20320
20926
  }
20321
20927
  function isInsideTemplateDir(dest, templateDir) {
20322
- return dest === templateDir || dest.startsWith(templateDir + path27.sep);
20928
+ return dest === templateDir || dest.startsWith(templateDir + path29.sep);
20323
20929
  }
20324
20930
  async function runTemplateFreeze(positional, flagArgs) {
20325
20931
  const slug = positional[0];
@@ -20380,7 +20986,7 @@ async function runTemplateFreeze(positional, flagArgs) {
20380
20986
  process.exit(1);
20381
20987
  }
20382
20988
  const entry = manifest.wayai.locales[targetLocale];
20383
- const dest = path27.resolve(templateDir, entry.source);
20989
+ const dest = path29.resolve(templateDir, entry.source);
20384
20990
  if (!isInsideTemplateDir(dest, templateDir) || dest === templateDir) {
20385
20991
  console.error(
20386
20992
  `source for "${targetLocale}" must be a committed path inside ${slug}/ (e.g. "./${targetLocale}/slice"), but resolved to:
@@ -20399,7 +21005,7 @@ Set it to a subdir of the template folder and re-run freeze.`
20399
21005
  const hubs = scanWorkspaceHubs(hubsDir);
20400
21006
  if (hubs.length) {
20401
21007
  console.error("Available hubs:");
20402
- 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})`);
20403
21009
  }
20404
21010
  console.error(`
20405
21011
  Pass --from <folder|uuid>${selector ? "." : " or run `wayai use <hub>` first."}`);
@@ -20407,12 +21013,12 @@ Pass --from <folder|uuid>${selector ? "." : " or run `wayai use <hub>` first."}`
20407
21013
  }
20408
21014
  const liveFiles = readDirToFileMap(hubFolder);
20409
21015
  if (Object.keys(liveFiles).length === 0) {
20410
- 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)}`);
20411
21017
  process.exit(1);
20412
21018
  }
20413
21019
  const sanitized = sanitizeWayaiSliceFiles(liveFiles);
20414
- const relDest = path27.relative(gitRoot, dest);
20415
- const relSrc = path27.relative(gitRoot, hubFolder);
21020
+ const relDest = path29.relative(gitRoot, dest);
21021
+ const relSrc = path29.relative(gitRoot, hubFolder);
20416
21022
  if (dryRun) {
20417
21023
  const existing = new Set(listDirRelFiles(dest));
20418
21024
  const nextKeys = new Set(Object.keys(sanitized));
@@ -20472,7 +21078,7 @@ async function runTemplatePull2(positional, flagArgs) {
20472
21078
  console.log(JSON.stringify(detail, null, 2));
20473
21079
  return;
20474
21080
  }
20475
- 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);
20476
21082
  const written = writeFileMap(target, detail.files);
20477
21083
  console.log(`Pulled "${slug}" from the store \u2192 ${target} (${written.length} file${written.length === 1 ? "" : "s"}).`);
20478
21084
  const uses = detail.uses ?? [];
@@ -20495,13 +21101,13 @@ async function runSkillInstall(positional) {
20495
21101
  throw err;
20496
21102
  }
20497
21103
  const root = findGitRoot() ?? process.cwd();
20498
- 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)));
20499
21105
  const targets = present.length > 0 ? present : HARNESS_SKILL_DIRS;
20500
21106
  const fileCount = Object.keys(res.files).length;
20501
21107
  const relDirs = targets.map((harness) => {
20502
- const targetDir = path27.join(root, harness, "skills", name);
21108
+ const targetDir = path29.join(root, harness, "skills", name);
20503
21109
  writeFileMap(targetDir, res.files);
20504
- return `${path27.relative(root, targetDir)}/`;
21110
+ return `${path29.relative(root, targetDir)}/`;
20505
21111
  });
20506
21112
  console.log(`Installed skill "${name}" (${fileCount} file${fileCount === 1 ? "" : "s"}) \u2192 ${relDirs.join(", ")}`);
20507
21113
  console.log("Reload your agent (e.g. restart Claude Code) to pick up the skill.");
@@ -21843,9 +22449,9 @@ var init_update = __esm({
21843
22449
 
21844
22450
  // src/index.ts
21845
22451
  init_sentry();
21846
- import { readFileSync as readFileSync19 } from "fs";
22452
+ import { readFileSync as readFileSync20 } from "fs";
21847
22453
  import { fileURLToPath as fileURLToPath2 } from "url";
21848
- import { dirname as dirname9, join as join25 } from "path";
22454
+ import { dirname as dirname9, join as join27 } from "path";
21849
22455
 
21850
22456
  // src/lib/errors.ts
21851
22457
  init_api_client();
@@ -21877,7 +22483,7 @@ init_skill_version();
21877
22483
  import { exec } from "child_process";
21878
22484
  var REFRESH_TIMEOUT_MS = 1e4;
21879
22485
  function refreshCliCache() {
21880
- return new Promise((resolve4) => {
22486
+ return new Promise((resolve6) => {
21881
22487
  exec("npm view @wayai/cli version", { timeout: REFRESH_TIMEOUT_MS }, (err, stdout) => {
21882
22488
  if (!err) {
21883
22489
  const latest = stdout.trim();
@@ -21888,7 +22494,7 @@ function refreshCliCache() {
21888
22494
  }
21889
22495
  }
21890
22496
  }
21891
- resolve4();
22497
+ resolve6();
21892
22498
  });
21893
22499
  });
21894
22500
  }
@@ -21914,8 +22520,8 @@ async function refreshSkillCache() {
21914
22520
  }
21915
22521
  async function refreshAdminSkillCache() {
21916
22522
  let timer;
21917
- const deadline = new Promise((resolve4) => {
21918
- timer = setTimeout(resolve4, REFRESH_TIMEOUT_MS);
22523
+ const deadline = new Promise((resolve6) => {
22524
+ timer = setTimeout(resolve6, REFRESH_TIMEOUT_MS);
21919
22525
  });
21920
22526
  await Promise.race([deadline, fetchAndCacheAdminSkillVersion().catch(() => {
21921
22527
  })]);
@@ -22025,7 +22631,7 @@ Run \`wayai admin skill install\` to update.`);
22025
22631
 
22026
22632
  // src/index.ts
22027
22633
  var __dirname = dirname9(fileURLToPath2(import.meta.url));
22028
- var pkg = JSON.parse(readFileSync19(join25(__dirname, "..", "package.json"), "utf-8"));
22634
+ var pkg = JSON.parse(readFileSync20(join27(__dirname, "..", "package.json"), "utf-8"));
22029
22635
  var [, , command, ...args] = process.argv;
22030
22636
  var isBackgroundRefresh = command === REFRESH_COMMAND;
22031
22637
  if (!isBackgroundRefresh) initSentry(command, pkg.version);