@wayai/cli 0.3.105 → 0.3.106

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, path30) {
444
+ function addApiBreadcrumb(method, path31) {
445
445
  if (!initialized) return;
446
446
  Sentry.addBreadcrumb({
447
447
  category: "http",
448
- message: `${method} ${path30}`,
448
+ message: `${method} ${path31}`,
449
449
  level: "info"
450
450
  });
451
451
  }
@@ -507,9 +507,9 @@ var init_api_client = __esm({
507
507
  ApiError = class extends Error {
508
508
  status;
509
509
  body;
510
- constructor(method, path30, status, body) {
510
+ constructor(method, path31, status, body) {
511
511
  const safeBody = maskSecretsInMessage(body);
512
- super(`API request failed: ${method} ${path30} (${status}): ${safeBody}`);
512
+ super(`API request failed: ${method} ${path31} (${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(path30, opts) {
661
- const params = new URLSearchParams({ path: path30 });
660
+ async lookup(path31, opts) {
661
+ const params = new URLSearchParams({ path: path31 });
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 path30 = `/api/archive/${encodeURIComponent(hubId)}/conversations/${encodeURIComponent(conversationId)}/sandbox`;
1062
- addApiBreadcrumb("GET", path30);
1063
- const url = `${this.apiUrl}${path30}`;
1061
+ const path31 = `/api/archive/${encodeURIComponent(hubId)}/conversations/${encodeURIComponent(conversationId)}/sandbox`;
1062
+ addApiBreadcrumb("GET", path31);
1063
+ const url = `${this.apiUrl}${path31}`;
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", path30, response.status, await response.text());
1077
+ throw new ApiError("GET", path31, 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, path30, body) {
1118
- addApiBreadcrumb(method, path30);
1119
- const url = `${this.apiUrl}${path30}`;
1117
+ async request(method, path31, body) {
1118
+ addApiBreadcrumb(method, path31);
1119
+ const url = `${this.apiUrl}${path31}`;
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, path30, response.status, errorBody);
1143
+ throw new ApiError(method, path31, 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: path30, errorMaps, issueData } = params;
1579
- const fullPath = [...path30, ...issueData.path || []];
1578
+ const { data, path: path31, errorMaps, issueData } = params;
1579
+ const fullPath = [...path31, ...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, path30, key) {
1890
+ constructor(parent, value, path31, key) {
1891
1891
  this._cachedPath = [];
1892
1892
  this.parent = parent;
1893
1893
  this.data = value;
1894
- this._path = path30;
1894
+ this._path = path31;
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, path30, errors, opts = {}) {
5378
+ function validateSchema(schema, path31, 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: path30 || "<root>",
5383
+ path: path31 || "<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, path30, errors, opts = {}) {
5388
5388
  }
5389
5389
  if (!isRecord(schema)) {
5390
5390
  errors.push({
5391
- path: path30,
5391
+ path: path31,
5392
5392
  message: `expected object, got ${schema === null ? "null" : typeof schema}`
5393
5393
  });
5394
5394
  return;
@@ -5396,14 +5396,14 @@ function validateSchema(schema, path30, errors, opts = {}) {
5396
5396
  if (opts.isRoot) {
5397
5397
  if ("type" in schema && schema.type !== "object") {
5398
5398
  errors.push({
5399
- path: path30 ? `${path30}.type` : "type",
5399
+ path: path31 ? `${path31}.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: `${path30}.type`,
5406
+ path: `${path31}.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, path30, errors, opts = {}) {
5412
5412
  if (typeof e === "string") {
5413
5413
  const isPlaceholder = PLACEHOLDER_TOKENS.includes(e);
5414
5414
  errors.push({
5415
- path: `${path30}.enum`,
5415
+ path: `${path31}.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: `${path30}.enum`,
5421
+ path: `${path31}.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: `${path30}.enum`,
5426
+ path: `${path31}.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: `${path30}.enum[${i}]`,
5433
+ path: `${path31}.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, path30, 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: `${path30}.${key}`,
5443
+ path: `${path31}.${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, path30, errors, opts = {}) {
5449
5449
  if ("properties" in schema) {
5450
5450
  if (!isRecord(schema.properties)) {
5451
5451
  errors.push({
5452
- path: `${path30}.properties`,
5452
+ path: `${path31}.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, `${path30}.properties.${propName}`, errors, { depth: depth + 1 });
5457
+ validateSchema(propSchema, `${path31}.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, `${path30}.items[${i}]`, errors, { depth: depth + 1 }));
5463
+ schema.items.forEach((sub, i) => validateSchema(sub, `${path31}.items[${i}]`, errors, { depth: depth + 1 }));
5464
5464
  } else {
5465
- validateSchema(schema.items, `${path30}.items`, errors, { depth: depth + 1 });
5465
+ validateSchema(schema.items, `${path31}.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], `${path30}.${key}`, errors, { depth: depth + 1 });
5470
+ validateSchema(schema[key], `${path31}.${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, `${path30}.${key}[${i}]`, errors, { depth: depth + 1 }));
5476
+ list.forEach((sub, i) => validateSchema(sub, `${path31}.${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, `${path30}.${key}.${name}`, errors, { depth: depth + 1 });
5483
+ validateSchema(sub, `${path31}.${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 = `${path30}.${k}`;
5490
+ const fieldPath = `${path31}.${k}`;
5491
5491
  if (reportedPaths.has(fieldPath)) continue;
5492
5492
  for (const token of PLACEHOLDER_TOKENS) {
5493
5493
  if (v === token) {
@@ -5787,10 +5787,10 @@ function refineHubAsCodeEvals(config, ctx) {
5787
5787
  function refineHubAsCodeEvalAttachments(config, ctx) {
5788
5788
  const cfg = config;
5789
5789
  const referencedHashes = /* @__PURE__ */ new Set();
5790
- const addTurnIssue = (path30, turn, name) => {
5790
+ const addTurnIssue = (path31, turn, name) => {
5791
5791
  const error = evalTurnAttachmentsError(turn);
5792
5792
  if (error) {
5793
- ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: path30, message: `${name}: ${error}` });
5793
+ ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: path31, message: `${name}: ${error}` });
5794
5794
  return;
5795
5795
  }
5796
5796
  for (const hash of collectTurnAttachmentHashes(turn)) referencedHashes.add(hash);
@@ -5911,10 +5911,10 @@ function refineHubAsCodeResources(config, ctx) {
5911
5911
  });
5912
5912
  }
5913
5913
  }
5914
- function isSafeTemplatePath(path30) {
5915
- if (path30.length === 0 || path30.length > 300) return false;
5916
- if (path30.startsWith("/") || path30.includes("\\")) return false;
5917
- const segments = path30.split("/");
5914
+ function isSafeTemplatePath(path31) {
5915
+ if (path31.length === 0 || path31.length > 300) return false;
5916
+ if (path31.startsWith("/") || path31.includes("\\")) return false;
5917
+ const segments = path31.split("/");
5918
5918
  return segments.every(
5919
5919
  (seg) => seg.length > 0 && seg !== "." && seg !== ".." && /^[A-Za-z0-9._-]+$/.test(seg)
5920
5920
  );
@@ -9614,9 +9614,9 @@ function getVersionCachePath(filename = CLI_CACHE_FILE) {
9614
9614
  }
9615
9615
  function readVersionCache(filename = CLI_CACHE_FILE) {
9616
9616
  try {
9617
- const path30 = getVersionCachePath(filename);
9618
- if (!existsSync4(path30)) return null;
9619
- const parsed = JSON.parse(readFileSync5(path30, "utf-8"));
9617
+ const path31 = getVersionCachePath(filename);
9618
+ if (!existsSync4(path31)) return null;
9619
+ const parsed = JSON.parse(readFileSync5(path31, "utf-8"));
9620
9620
  if (typeof parsed.lastCheck !== "number") return null;
9621
9621
  if (parsed.latest !== null && typeof parsed.latest !== "string") return null;
9622
9622
  return parsed;
@@ -9636,10 +9636,10 @@ function isVersionCacheStale(filename = CLI_CACHE_FILE, maxAgeMs = MAX_AGE_24H_M
9636
9636
  return Date.now() - cache.lastCheck > maxAgeMs;
9637
9637
  }
9638
9638
  function writeVersionCache(filename, cache) {
9639
- const path30 = getVersionCachePath(filename);
9640
- const dir = dirname3(path30);
9639
+ const path31 = getVersionCachePath(filename);
9640
+ const dir = dirname3(path31);
9641
9641
  if (!existsSync4(dir)) mkdirSync(dir, { recursive: true });
9642
- writeFileSync2(path30, JSON.stringify(cache));
9642
+ writeFileSync2(path31, JSON.stringify(cache));
9643
9643
  }
9644
9644
  function touchVersionCache(filename) {
9645
9645
  writeVersionCache(filename, { lastCheck: Date.now(), latest: readVersionCache(filename)?.latest ?? null });
@@ -9678,14 +9678,14 @@ function parseFrontmatterVersion(content) {
9678
9678
  function findInstalledSkills(projectRoot, paths = SKILL_INSTALL_PATHS) {
9679
9679
  const found = [];
9680
9680
  for (const rel of paths) {
9681
- const path30 = join7(projectRoot, rel);
9682
- if (!existsSync5(path30)) continue;
9681
+ const path31 = join7(projectRoot, rel);
9682
+ if (!existsSync5(path31)) continue;
9683
9683
  let version = null;
9684
9684
  try {
9685
- version = parseFrontmatterVersion(readFileSync6(path30, "utf-8"));
9685
+ version = parseFrontmatterVersion(readFileSync6(path31, "utf-8"));
9686
9686
  } catch {
9687
9687
  }
9688
- found.push({ path: path30, version });
9688
+ found.push({ path: path31, version });
9689
9689
  }
9690
9690
  return found;
9691
9691
  }
@@ -11304,12 +11304,12 @@ function typeMatches2(typeField, allowed) {
11304
11304
  if (Array.isArray(typeField)) return typeField.every((t) => typeof t === "string" && allowed.has(t));
11305
11305
  return false;
11306
11306
  }
11307
- function validateSchema2(schema, path30, errors, opts = {}) {
11307
+ function validateSchema2(schema, path31, errors, opts = {}) {
11308
11308
  if (typeof schema === "boolean") return;
11309
11309
  const depth = opts.depth ?? 0;
11310
11310
  if (depth > MAX_SCHEMA_DEPTH2) {
11311
11311
  errors.push({
11312
- path: path30 || "<root>",
11312
+ path: path31 || "<root>",
11313
11313
  message: `schema nesting exceeds ${MAX_SCHEMA_DEPTH2} levels`,
11314
11314
  suggestion: "Flatten the schema; LLM providers reject deeply nested tool input_schemas."
11315
11315
  });
@@ -11317,7 +11317,7 @@ function validateSchema2(schema, path30, errors, opts = {}) {
11317
11317
  }
11318
11318
  if (!isRecord2(schema)) {
11319
11319
  errors.push({
11320
- path: path30,
11320
+ path: path31,
11321
11321
  message: `expected object, got ${schema === null ? "null" : typeof schema}`
11322
11322
  });
11323
11323
  return;
@@ -11325,14 +11325,14 @@ function validateSchema2(schema, path30, errors, opts = {}) {
11325
11325
  if (opts.isRoot) {
11326
11326
  if ("type" in schema && schema.type !== "object") {
11327
11327
  errors.push({
11328
- path: path30 ? `${path30}.type` : "type",
11328
+ path: path31 ? `${path31}.type` : "type",
11329
11329
  message: `root type must be 'object', got ${JSON.stringify(schema.type)}`,
11330
11330
  suggestion: "Tool parameters at the root must be an object: `{ type: 'object', properties: { ... } }`."
11331
11331
  });
11332
11332
  }
11333
11333
  } else if ("type" in schema && !typeMatches2(schema.type, ALLOWED_TYPES2)) {
11334
11334
  errors.push({
11335
- path: `${path30}.type`,
11335
+ path: `${path31}.type`,
11336
11336
  message: `type must be one of ${[...ALLOWED_TYPES2].join("/")} or an array of those, got ${JSON.stringify(schema.type)}`
11337
11337
  });
11338
11338
  }
@@ -11341,25 +11341,25 @@ function validateSchema2(schema, path30, errors, opts = {}) {
11341
11341
  if (typeof e === "string") {
11342
11342
  const isPlaceholder = PLACEHOLDER_TOKENS2.includes(e);
11343
11343
  errors.push({
11344
- path: `${path30}.enum`,
11344
+ path: `${path31}.enum`,
11345
11345
  message: `enum must be a non-empty array of primitives, got string ${JSON.stringify(e)}`,
11346
11346
  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"]`.'
11347
11347
  });
11348
11348
  } else if (!Array.isArray(e)) {
11349
11349
  errors.push({
11350
- path: `${path30}.enum`,
11350
+ path: `${path31}.enum`,
11351
11351
  message: `enum must be a non-empty array of primitives, got ${typeof e}`
11352
11352
  });
11353
11353
  } else if (e.length === 0) {
11354
11354
  errors.push({
11355
- path: `${path30}.enum`,
11355
+ path: `${path31}.enum`,
11356
11356
  message: "enum must not be empty"
11357
11357
  });
11358
11358
  } else {
11359
11359
  for (let i = 0; i < e.length; i++) {
11360
11360
  if (!isPrimitive2(e[i])) {
11361
11361
  errors.push({
11362
- path: `${path30}.enum[${i}]`,
11362
+ path: `${path31}.enum[${i}]`,
11363
11363
  message: `enum entry must be a primitive (string/number/boolean/null), got ${typeof e[i]}`
11364
11364
  });
11365
11365
  }
@@ -11369,7 +11369,7 @@ function validateSchema2(schema, path30, errors, opts = {}) {
11369
11369
  for (const key of ["exclusiveMinimum", "exclusiveMaximum"]) {
11370
11370
  if (key in schema && typeof schema[key] === "boolean") {
11371
11371
  errors.push({
11372
- path: `${path30}.${key}`,
11372
+ path: `${path31}.${key}`,
11373
11373
  message: `${key} as boolean is draft-04 syntax; draft 2020-12 requires a numeric value`,
11374
11374
  suggestion: `Replace with the numeric bound, e.g. \`${key}: 0\`.`
11375
11375
  });
@@ -11378,45 +11378,45 @@ function validateSchema2(schema, path30, errors, opts = {}) {
11378
11378
  if ("properties" in schema) {
11379
11379
  if (!isRecord2(schema.properties)) {
11380
11380
  errors.push({
11381
- path: `${path30}.properties`,
11381
+ path: `${path31}.properties`,
11382
11382
  message: `properties must be an object, got ${Array.isArray(schema.properties) ? "array" : typeof schema.properties}`
11383
11383
  });
11384
11384
  } else {
11385
11385
  for (const [propName, propSchema] of Object.entries(schema.properties)) {
11386
- validateSchema2(propSchema, `${path30}.properties.${propName}`, errors, { depth: depth + 1 });
11386
+ validateSchema2(propSchema, `${path31}.properties.${propName}`, errors, { depth: depth + 1 });
11387
11387
  }
11388
11388
  }
11389
11389
  }
11390
11390
  if (schemaTypeIncludes2(schema, "array") && "items" in schema) {
11391
11391
  if (Array.isArray(schema.items)) {
11392
- schema.items.forEach((sub, i) => validateSchema2(sub, `${path30}.items[${i}]`, errors, { depth: depth + 1 }));
11392
+ schema.items.forEach((sub, i) => validateSchema2(sub, `${path31}.items[${i}]`, errors, { depth: depth + 1 }));
11393
11393
  } else {
11394
- validateSchema2(schema.items, `${path30}.items`, errors, { depth: depth + 1 });
11394
+ validateSchema2(schema.items, `${path31}.items`, errors, { depth: depth + 1 });
11395
11395
  }
11396
11396
  }
11397
11397
  for (const key of SUBSCHEMA_OBJECT_KEYWORDS2) {
11398
11398
  if (key in schema && isRecord2(schema[key])) {
11399
- validateSchema2(schema[key], `${path30}.${key}`, errors, { depth: depth + 1 });
11399
+ validateSchema2(schema[key], `${path31}.${key}`, errors, { depth: depth + 1 });
11400
11400
  }
11401
11401
  }
11402
11402
  for (const key of SUBSCHEMA_LIST_KEYWORDS2) {
11403
11403
  const list = schema[key];
11404
11404
  if (Array.isArray(list)) {
11405
- list.forEach((sub, i) => validateSchema2(sub, `${path30}.${key}[${i}]`, errors, { depth: depth + 1 }));
11405
+ list.forEach((sub, i) => validateSchema2(sub, `${path31}.${key}[${i}]`, errors, { depth: depth + 1 }));
11406
11406
  }
11407
11407
  }
11408
11408
  for (const key of SUBSCHEMA_MAP_KEYWORDS2) {
11409
11409
  const map = schema[key];
11410
11410
  if (isRecord2(map)) {
11411
11411
  for (const [name, sub] of Object.entries(map)) {
11412
- validateSchema2(sub, `${path30}.${key}.${name}`, errors, { depth: depth + 1 });
11412
+ validateSchema2(sub, `${path31}.${key}.${name}`, errors, { depth: depth + 1 });
11413
11413
  }
11414
11414
  }
11415
11415
  }
11416
11416
  const reportedPaths = new Set(errors.map((e) => e.path));
11417
11417
  for (const [k, v] of Object.entries(schema)) {
11418
11418
  if (typeof v !== "string") continue;
11419
- const fieldPath = `${path30}.${k}`;
11419
+ const fieldPath = `${path31}.${k}`;
11420
11420
  if (reportedPaths.has(fieldPath)) continue;
11421
11421
  for (const token of PLACEHOLDER_TOKENS2) {
11422
11422
  if (v === token) {
@@ -11690,10 +11690,10 @@ function refineHubAsCodeEvals2(config, ctx) {
11690
11690
  function refineHubAsCodeEvalAttachments2(config, ctx) {
11691
11691
  const cfg = config;
11692
11692
  const referencedHashes = /* @__PURE__ */ new Set();
11693
- const addTurnIssue = (path30, turn, name) => {
11693
+ const addTurnIssue = (path31, turn, name) => {
11694
11694
  const error = evalTurnAttachmentsError2(turn);
11695
11695
  if (error) {
11696
- ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: path30, message: `${name}: ${error}` });
11696
+ ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: path31, message: `${name}: ${error}` });
11697
11697
  return;
11698
11698
  }
11699
11699
  for (const hash of collectTurnAttachmentHashes2(turn)) referencedHashes.add(hash);
@@ -11814,10 +11814,10 @@ function refineHubAsCodeResources2(config, ctx) {
11814
11814
  });
11815
11815
  }
11816
11816
  }
11817
- function isSafeTemplatePath2(path30) {
11818
- if (path30.length === 0 || path30.length > 300) return false;
11819
- if (path30.startsWith("/") || path30.includes("\\")) return false;
11820
- const segments = path30.split("/");
11817
+ function isSafeTemplatePath2(path31) {
11818
+ if (path31.length === 0 || path31.length > 300) return false;
11819
+ if (path31.startsWith("/") || path31.includes("\\")) return false;
11820
+ const segments = path31.split("/");
11821
11821
  return segments.every(
11822
11822
  (seg) => seg.length > 0 && seg !== "." && seg !== ".." && /^[A-Za-z0-9._-]+$/.test(seg)
11823
11823
  );
@@ -11845,9 +11845,9 @@ function isPlainObject(value) {
11845
11845
  const proto = Object.getPrototypeOf(value);
11846
11846
  return proto === Object.prototype || proto === null;
11847
11847
  }
11848
- function isSanitizableSliceFile(path30) {
11849
- if (!/\.ya?ml$/i.test(path30)) return false;
11850
- return path30 !== "resources" && !path30.startsWith("resources/");
11848
+ function isSanitizableSliceFile(path31) {
11849
+ if (!/\.ya?ml$/i.test(path31)) return false;
11850
+ return path31 !== "resources" && !path31.startsWith("resources/");
11851
11851
  }
11852
11852
  function distinctUsesBackends(uses) {
11853
11853
  return [...new Set((uses ?? []).map((u) => u.backend))];
@@ -14864,897 +14864,951 @@ var init_resource_files = __esm({
14864
14864
  }
14865
14865
  });
14866
14866
 
14867
- // src/lib/yaml-writer.ts
14867
+ // src/lib/eval-attachments.ts
14868
14868
  import * as fs11 from "fs";
14869
14869
  import * as path13 from "path";
14870
- import * as yaml4 from "js-yaml";
14871
- function buildEvalYamlObject(evalEntry, slug) {
14872
- const out = {};
14873
- if (evalEntry.id) out.id = evalEntry.id;
14874
- if (evalEntry.name !== slug) out.name = evalEntry.name;
14875
- out.agent = evalEntry.agent;
14876
- if (evalEntry.agent_id) out.agent_id = evalEntry.agent_id;
14877
- if (typeof evalEntry.runs === "number" && evalEntry.runs !== 1) {
14878
- out.runs = evalEntry.runs;
14879
- }
14880
- if (evalEntry.enabled === false) {
14881
- out.enabled = false;
14882
- }
14883
- if (evalEntry.history && evalEntry.history.length > 0) {
14884
- out.history = evalEntry.history;
14885
- }
14886
- if (typeof evalEntry.fixture === "string" && evalEntry.fixture.trim()) {
14887
- out.fixture = evalEntry.fixture.trim();
14888
- }
14889
- if (typeof evalEntry.target_base === "string" && evalEntry.target_base.trim()) {
14890
- out.target_base = evalEntry.target_base.trim();
14870
+ function resolveTurnAttachments(hubFolder, turn, label, bytesByHash) {
14871
+ const raw = turn.attachments;
14872
+ if (raw === void 0 || raw === null) return void 0;
14873
+ if (!Array.isArray(raw) || raw.length === 0) {
14874
+ if (Array.isArray(raw)) return void 0;
14875
+ throw expected(`${label}: "attachments" must be a list of file paths.`);
14891
14876
  }
14892
- if (evalEntry.variables && Object.keys(evalEntry.variables).length > 0) {
14893
- out.variables = evalEntry.variables;
14877
+ if (turn.role !== "user") {
14878
+ throw expected(`${label}: attachments are only allowed on user turns (this turn is "${String(turn.role)}").`);
14894
14879
  }
14895
- out.input = evalEntry.input;
14896
- out.expected = evalEntry.expected;
14897
- if (evalEntry.evaluator_instructions) {
14898
- out.evaluator_instructions = evalEntry.evaluator_instructions;
14880
+ const refs = [];
14881
+ for (const entry of raw) {
14882
+ if (typeof entry !== "string" || !entry.trim()) {
14883
+ throw expected(`${label}: each attachment must be a hub-relative file path string.`);
14884
+ }
14885
+ const abs = path13.resolve(hubFolder, entry);
14886
+ const root = path13.resolve(hubFolder);
14887
+ if (abs !== root && !abs.startsWith(root + path13.sep)) {
14888
+ throw expected(`${label}: attachment "${entry}" escapes the hub folder.`);
14889
+ }
14890
+ let stat;
14891
+ try {
14892
+ stat = fs11.statSync(abs);
14893
+ } catch {
14894
+ }
14895
+ if (!stat?.isFile()) {
14896
+ throw expected(`${label}: attachment "${entry}" not found.`);
14897
+ }
14898
+ if (!realpathContained(root, abs)) {
14899
+ throw expected(`${label}: attachment "${entry}" resolves outside the hub folder (symlinks are not allowed).`);
14900
+ }
14901
+ if (stat.size > MAX_RESOURCE_FILE_SIZE3) {
14902
+ throw expected(
14903
+ `${label}: attachment "${entry}" is ${(stat.size / 1024 / 1024).toFixed(1)}MB, over the 10MB limit.`
14904
+ );
14905
+ }
14906
+ const data = fs11.readFileSync(abs);
14907
+ const hash = computeHash(data);
14908
+ const fileName = path13.basename(abs);
14909
+ const mimeType = guessMimeType(fileName) ?? "application/octet-stream";
14910
+ refs.push({ hash, file_name: fileName, mime_type: mimeType, file_size: stat.size });
14911
+ if (!bytesByHash.has(hash)) {
14912
+ bytesByHash.set(hash, {
14913
+ hash,
14914
+ content_base64: data.toString("base64"),
14915
+ file_name: fileName,
14916
+ mime_type: mimeType,
14917
+ file_size: stat.size
14918
+ });
14919
+ }
14899
14920
  }
14900
- return out;
14921
+ return refs;
14901
14922
  }
14902
- function writeHubFolder(hubFolder, payload, options = {}) {
14903
- const agentsDir = path13.join(hubFolder, "agents");
14904
- if (!fs11.existsSync(hubFolder)) {
14905
- fs11.mkdirSync(hubFolder, { recursive: true });
14906
- }
14907
- if (!fs11.existsSync(agentsDir)) {
14908
- fs11.mkdirSync(agentsDir, { recursive: true });
14909
- }
14910
- const yamlPayload = buildYamlPayload(payload);
14911
- const agentFiles = extractAgentFiles(payload);
14912
- const yamlContent = yaml4.dump(yamlPayload, YAML_DUMP_OPTIONS);
14913
- fs11.writeFileSync(path13.join(hubFolder, "hub.yaml"), yamlContent, "utf-8");
14914
- if (options.seedAgentContext ?? true) {
14915
- writeIfAbsent(path13.join(hubFolder, "AGENTS.md"), perHubAgentsMd(path13.basename(hubFolder)));
14916
- writeIfAbsent(path13.join(hubFolder, "CLAUDE.md"), CLAUDE_MD_SHIM);
14923
+ function applyTurnAttachments(hubFolder, turn, label, bytesByHash) {
14924
+ const refs = resolveTurnAttachments(hubFolder, turn, label, bytesByHash);
14925
+ if (refs) turn.attachments = refs;
14926
+ else delete turn.attachments;
14927
+ }
14928
+ function assertAttachmentsAreLocalPaths(turn, label) {
14929
+ const attachments = turn?.attachments;
14930
+ if (!Array.isArray(attachments)) return;
14931
+ for (const entry of attachments) {
14932
+ if (entry !== null && typeof entry === "object" && "hash" in entry) {
14933
+ throw new Error(
14934
+ `wire-shape attachment ref reached YAML serialization \u2014 CLI bug (${label}): the payload was not run through materializeHubFolder before writing.`
14935
+ );
14936
+ }
14917
14937
  }
14918
- const oldYamlPath = path13.join(hubFolder, "wayai.yaml");
14919
- if (fs11.existsSync(oldYamlPath)) {
14920
- fs11.unlinkSync(oldYamlPath);
14938
+ }
14939
+ function forEachAttachableTurn(payload, fn) {
14940
+ for (const ev of payload.evals ?? []) {
14941
+ if (ev.input) fn(ev.input);
14942
+ for (const t of ev.history ?? []) fn(t);
14921
14943
  }
14922
- const yamlSlugs = writeAgentYamlFiles(agentsDir, payload.agents || []);
14923
- const mdSlugs = /* @__PURE__ */ new Set();
14924
- for (const { slug, content } of agentFiles) {
14925
- mdSlugs.add(slug);
14926
- fs11.writeFileSync(path13.join(agentsDir, `${slug}.md`), content, "utf-8");
14944
+ for (const jr of payload.journeys ?? []) {
14945
+ for (const t of jr.transcript ?? []) fn(t);
14927
14946
  }
14928
- const existingFiles = fs11.readdirSync(agentsDir);
14929
- for (const file of existingFiles) {
14930
- if (file.endsWith(".yaml")) {
14931
- const slug = file.slice(0, -5);
14932
- if (!yamlSlugs.has(slug)) {
14933
- fs11.unlinkSync(path13.join(agentsDir, file));
14947
+ }
14948
+ function rewriteEvalAttachmentsToLocalPaths(payload) {
14949
+ const urlByHash = new Map((payload.eval_attachment_files ?? []).map((f) => [f.hash, f.download_url]));
14950
+ const safeName = (ref) => {
14951
+ const base = path13.basename(ref.file_name);
14952
+ return base === "" || base === "." || base === ".." ? ref.hash.slice(0, 16) : base;
14953
+ };
14954
+ const hashesByName = /* @__PURE__ */ new Map();
14955
+ const collect = (turn) => {
14956
+ if (!Array.isArray(turn.attachments)) return;
14957
+ for (const ref of turn.attachments) {
14958
+ if (!ref?.hash || !ref.file_name) continue;
14959
+ const name = safeName(ref);
14960
+ let set = hashesByName.get(name);
14961
+ if (!set) {
14962
+ set = /* @__PURE__ */ new Set();
14963
+ hashesByName.set(name, set);
14934
14964
  }
14935
- } else if (file.endsWith(".md")) {
14936
- const slug = file.slice(0, -3);
14937
- if (!mdSlugs.has(slug)) {
14938
- fs11.unlinkSync(path13.join(agentsDir, file));
14965
+ set.add(ref.hash);
14966
+ }
14967
+ };
14968
+ forEachAttachableTurn(payload, collect);
14969
+ const pathByKey = /* @__PURE__ */ new Map();
14970
+ const usedLower = /* @__PURE__ */ new Set();
14971
+ const localPathFor = (ref) => {
14972
+ const name = safeName(ref);
14973
+ const key = `${ref.hash} ${name}`;
14974
+ const existing = pathByKey.get(key);
14975
+ if (existing) return existing;
14976
+ const ext = path13.extname(name);
14977
+ const stem = path13.basename(name, ext);
14978
+ const collides = (hashesByName.get(name)?.size ?? 0) > 1;
14979
+ let rel = collides ? `${ATTACHMENTS_DIR}/${stem}-${ref.hash.slice(0, 8)}${ext}` : `${ATTACHMENTS_DIR}/${name}`;
14980
+ if (usedLower.has(rel.toLowerCase())) {
14981
+ let n = 12;
14982
+ while (usedLower.has(rel.toLowerCase()) && n <= ref.hash.length) {
14983
+ rel = `${ATTACHMENTS_DIR}/${stem}-${ref.hash.slice(0, n)}${ext}`;
14984
+ n += 4;
14985
+ }
14986
+ for (let extra = 2; usedLower.has(rel.toLowerCase()); extra++) {
14987
+ rel = `${ATTACHMENTS_DIR}/${stem}-${ref.hash}-${extra}${ext}`;
14939
14988
  }
14940
14989
  }
14941
- }
14942
- writeResourceFiles(hubFolder, payload.resources || []);
14943
- writeEvalYamlFiles(hubFolder, payload.evals || []);
14944
- writeJourneyYamlFiles(hubFolder, payload.journeys || []);
14945
- }
14946
- function setPreviewLabelInHubYaml(hubFolder, label) {
14947
- const yamlPath = resolveHubYamlPath(hubFolder);
14948
- if (!yamlPath) return;
14949
- const obj = yaml4.load(fs11.readFileSync(yamlPath, "utf-8")) ?? {};
14950
- if (label) obj.preview_label = label;
14951
- else delete obj.preview_label;
14952
- fs11.writeFileSync(yamlPath, yaml4.dump(obj, YAML_DUMP_OPTIONS), "utf-8");
14953
- }
14954
- function buildYamlPayload(payload) {
14955
- const result = {
14956
- version: payload.version,
14957
- hub_id: payload.hub_id,
14958
- hub_environment: payload.hub_environment
14990
+ usedLower.add(rel.toLowerCase());
14991
+ pathByKey.set(key, rel);
14992
+ return rel;
14959
14993
  };
14960
- if (payload.preview_label) {
14961
- result.preview_label = payload.preview_label;
14962
- }
14963
- result.hub = payload.hub;
14964
- if (payload.resources && payload.resources.length > 0) {
14965
- result.resources = payload.resources.map((r) => {
14966
- const { files, ...rest } = r;
14967
- return rest;
14994
+ const downloads = /* @__PURE__ */ new Map();
14995
+ forEachAttachableTurn(payload, (turn) => {
14996
+ if (!Array.isArray(turn.attachments)) return;
14997
+ turn.attachments = turn.attachments.map((ref) => {
14998
+ const rel = localPathFor(ref);
14999
+ const url = urlByHash.get(ref.hash);
15000
+ if (url && !downloads.has(rel)) downloads.set(rel, { relPath: rel, url, hash: ref.hash });
15001
+ return rel;
14968
15002
  });
14969
- }
14970
- if (payload.states && payload.states.length > 0) {
14971
- result.states = payload.states;
14972
- }
14973
- if (payload.teams && payload.teams.length > 0) {
14974
- result.teams = payload.teams;
14975
- }
14976
- if (payload.connections && payload.connections.length > 0) {
14977
- result.connections = payload.connections;
14978
- }
14979
- if (payload.outbound_contacts && payload.outbound_contacts.length > 0) {
14980
- result.outbound_contacts = payload.outbound_contacts;
14981
- }
14982
- if (payload.outbound_lists && payload.outbound_lists.length > 0) {
14983
- result.outbound_lists = payload.outbound_lists;
14984
- }
14985
- if (payload.outbound_schedules && payload.outbound_schedules.length > 0) {
14986
- result.outbound_schedules = payload.outbound_schedules;
14987
- }
14988
- return result;
14989
- }
14990
- function writeAgentYamlFiles(agentsDir, agents) {
14991
- const slugs = /* @__PURE__ */ new Set();
14992
- for (const agent of agents) {
14993
- const slug = slugify(agent.name);
14994
- slugs.add(slug);
14995
- const entry = { ...agent };
14996
- delete entry.instructions;
14997
- const yamlContent = yaml4.dump(entry, YAML_DUMP_OPTIONS);
14998
- fs11.writeFileSync(path13.join(agentsDir, `${slug}.yaml`), yamlContent, "utf-8");
14999
- }
15000
- return slugs;
15003
+ });
15004
+ delete payload.eval_attachment_files;
15005
+ return [...downloads.values()];
15001
15006
  }
15002
- function extractAgentFiles(payload) {
15003
- const files = [];
15004
- for (const agent of payload.agents || []) {
15005
- if (agent.instructions) {
15006
- files.push({
15007
- slug: slugify(agent.name),
15008
- content: agent.instructions
15009
- });
15010
- }
15007
+ async function downloadEvalAttachments(hubFolder, downloads) {
15008
+ const attachDir = path13.resolve(hubFolder, ATTACHMENTS_DIR);
15009
+ let written = 0;
15010
+ if (!ensureRealSubdirNoSymlink(hubFolder, attachDir, downloads.length > 0)) {
15011
+ console.warn(` Warning: ${ATTACHMENTS_DIR}/ is not reachable inside the hub folder without a symlink; skipping attachment sync.`);
15012
+ return written;
15011
15013
  }
15012
- return files;
15013
- }
15014
- function writeEvalYamlFiles(hubFolder, evals) {
15015
- const evalsDir = path13.join(hubFolder, "evals");
15016
- if (evals.length === 0) {
15017
- if (fs11.existsSync(evalsDir)) {
15018
- cleanEvalOrphans(evalsDir, /* @__PURE__ */ new Set());
15019
- try {
15020
- if (fs11.readdirSync(evalsDir).length === 0) fs11.rmdirSync(evalsDir);
15021
- } catch {
15022
- }
15014
+ if (!fs11.existsSync(attachDir)) return written;
15015
+ for (const dl of downloads) {
15016
+ const abs = path13.resolve(hubFolder, dl.relPath);
15017
+ if (abs !== attachDir && !abs.startsWith(attachDir + path13.sep)) continue;
15018
+ let existing;
15019
+ try {
15020
+ existing = fs11.lstatSync(abs);
15021
+ } catch {
15023
15022
  }
15024
- return;
15025
- }
15026
- if (!fs11.existsSync(evalsDir)) {
15027
- fs11.mkdirSync(evalsDir, { recursive: true });
15028
- }
15029
- const writtenRelPaths = /* @__PURE__ */ new Set();
15030
- for (const evalEntry of evals) {
15031
- const slug = slugify(evalEntry.name);
15032
- const setName = evalEntry.path && evalEntry.path.trim().length > 0 ? evalEntry.path : null;
15033
- const relPath = setName ? `${setName}/${slug}.yaml` : `${slug}.yaml`;
15034
- const targetPath = path13.join(evalsDir, relPath);
15035
- if (!targetPath.startsWith(evalsDir + path13.sep)) {
15036
- console.warn(` Warning: skipping eval "${evalEntry.name}" (scenario set "${setName}" escapes evals/ \u2014 possible bad backend data)`);
15023
+ if (existing?.isFile() && !existing.isSymbolicLink() && computeHash(fs11.readFileSync(abs)) === dl.hash) {
15037
15024
  continue;
15038
15025
  }
15039
- if (setName) {
15040
- const setDir = path13.join(evalsDir, setName);
15041
- if (!fs11.existsSync(setDir)) {
15042
- fs11.mkdirSync(setDir, { recursive: true });
15026
+ try {
15027
+ const res = await fetch(dl.url);
15028
+ if (!res.ok) {
15029
+ console.warn(` Warning: failed to download ${dl.relPath} (HTTP ${res.status})`);
15030
+ continue;
15043
15031
  }
15032
+ if (writeFileNoFollow(hubFolder, abs, Buffer.from(await res.arrayBuffer()))) written++;
15033
+ } catch (err) {
15034
+ console.warn(` Warning: failed to download ${dl.relPath}: ${err instanceof Error ? err.message : String(err)}`);
15044
15035
  }
15045
- const yamlContent = yaml4.dump(buildEvalYamlObject(evalEntry, slug), YAML_DUMP_OPTIONS);
15046
- fs11.writeFileSync(targetPath, yamlContent, "utf-8");
15047
- writtenRelPaths.add(relPath);
15048
15036
  }
15049
- cleanEvalOrphans(evalsDir, writtenRelPaths);
15050
- }
15051
- function cleanEvalOrphans(evalsDir, writtenRelPaths) {
15052
- const entries = fs11.readdirSync(evalsDir, { withFileTypes: true });
15053
- for (const entry of entries) {
15054
- const fullPath = path13.join(evalsDir, entry.name);
15055
- if (entry.isFile()) {
15056
- if (entry.name.endsWith(".yaml") && !writtenRelPaths.has(entry.name)) {
15057
- fs11.unlinkSync(fullPath);
15058
- }
15059
- continue;
15037
+ const keep = new Set(downloads.map((d) => path13.resolve(hubFolder, d.relPath)));
15038
+ for (const name of fs11.readdirSync(attachDir)) {
15039
+ const abs = path13.join(attachDir, name);
15040
+ const st = fs11.lstatSync(abs);
15041
+ if ((st.isFile() || st.isSymbolicLink()) && !keep.has(abs)) fs11.rmSync(abs);
15042
+ }
15043
+ return written;
15044
+ }
15045
+ var ATTACHMENTS_DIR;
15046
+ var init_eval_attachments = __esm({
15047
+ "src/lib/eval-attachments.ts"() {
15048
+ "use strict";
15049
+ init_resource_files();
15050
+ init_fs_safety();
15051
+ init_parser();
15052
+ ATTACHMENTS_DIR = "attachments";
15053
+ }
15054
+ });
15055
+
15056
+ // src/lib/parser.ts
15057
+ import * as fs12 from "fs";
15058
+ import * as path14 from "path";
15059
+ import * as yaml4 from "js-yaml";
15060
+ function expected(message) {
15061
+ const err = new Error(message);
15062
+ err.isExpected = true;
15063
+ return err;
15064
+ }
15065
+ function parseHubFolder(hubFolder, opts) {
15066
+ const yamlPath = resolveHubYamlPath(hubFolder);
15067
+ if (!yamlPath) {
15068
+ const err = new Error(`hub.yaml not found in ${hubFolder}`);
15069
+ err.isExpected = true;
15070
+ throw err;
15071
+ }
15072
+ const yamlContent = fs12.readFileSync(yamlPath, "utf-8");
15073
+ let config;
15074
+ try {
15075
+ config = yaml4.load(yamlContent);
15076
+ } catch (err) {
15077
+ if (err instanceof yaml4.YAMLException) {
15078
+ const userError = new Error(`Invalid YAML in ${yamlPath}: ${err.message}`);
15079
+ userError.isExpected = true;
15080
+ throw userError;
15060
15081
  }
15061
- if (entry.isDirectory()) {
15062
- const setName = entry.name;
15063
- const subEntries = fs11.readdirSync(fullPath, { withFileTypes: true });
15064
- for (const sub of subEntries) {
15065
- if (sub.isFile() && sub.name.endsWith(".yaml")) {
15066
- const relPath = `${setName}/${sub.name}`;
15067
- if (!writtenRelPaths.has(relPath)) {
15068
- fs11.unlinkSync(path13.join(fullPath, sub.name));
15069
- }
15070
- }
15082
+ throw err;
15083
+ }
15084
+ if (!config || typeof config !== "object") {
15085
+ const invalidError = new Error(`Invalid YAML in ${yamlPath}`);
15086
+ invalidError.isExpected = true;
15087
+ throw invalidError;
15088
+ }
15089
+ if (!opts?.allowNewHub) {
15090
+ if (!config.hub_id) {
15091
+ const err = new Error(`Missing hub_id in ${yamlPath}`);
15092
+ err.isExpected = true;
15093
+ throw err;
15094
+ }
15095
+ if (!config.hub_environment) {
15096
+ const err = new Error(`Missing hub_environment in ${yamlPath}`);
15097
+ err.isExpected = true;
15098
+ throw err;
15099
+ }
15100
+ }
15101
+ const agentYamlFiles = scanAgentYamlFiles(hubFolder);
15102
+ const rawAgents = agentYamlFiles.length > 0 ? agentYamlFiles : config.agents;
15103
+ const agents = rawAgents?.map((agent) => {
15104
+ const resolved = { ...agent };
15105
+ if (typeof resolved.instructions === "string" && resolved.instructions.endsWith(".md")) {
15106
+ const instrValue = resolved.instructions;
15107
+ const instructionsPath = instrValue.startsWith("agents/") ? path14.join(hubFolder, instrValue) : path14.join(hubFolder, "agents", instrValue);
15108
+ if (fs12.existsSync(instructionsPath)) {
15109
+ resolved.instructions = fs12.readFileSync(instructionsPath, "utf-8");
15110
+ } else {
15111
+ const err = new Error(
15112
+ `Agent instructions file not found: ${instructionsPath} (referenced by agent "${agent.name}")`
15113
+ );
15114
+ err.isExpected = true;
15115
+ throw err;
15071
15116
  }
15072
- try {
15073
- if (fs11.readdirSync(fullPath).length === 0) fs11.rmdirSync(fullPath);
15074
- } catch {
15117
+ } else if (resolved.instructions === void 0 && typeof agent.name === "string") {
15118
+ const conventionPath = path14.join(hubFolder, "agents", `${slugify(agent.name)}.md`);
15119
+ if (fs12.existsSync(conventionPath)) {
15120
+ resolved.instructions = fs12.readFileSync(conventionPath, "utf-8");
15075
15121
  }
15076
15122
  }
15123
+ return resolved;
15124
+ });
15125
+ const resources = parseResources(hubFolder, config.resources || []);
15126
+ const payload = {
15127
+ version: config.version || 1,
15128
+ hub_id: config.hub_id || "",
15129
+ hub_environment: config.hub_environment || "preview",
15130
+ hub: config.hub
15131
+ };
15132
+ if (config.preview_label) {
15133
+ payload.preview_label = config.preview_label;
15077
15134
  }
15078
- }
15079
- function buildJourneyYamlObject(journeyEntry, slug) {
15080
- const out = {};
15081
- if (journeyEntry.id) out.id = journeyEntry.id;
15082
- if (journeyEntry.name !== slug) out.name = journeyEntry.name;
15083
- out.agent = journeyEntry.agent;
15084
- if (journeyEntry.agent_id) out.agent_id = journeyEntry.agent_id;
15085
- if (journeyEntry.enabled === false) {
15086
- out.enabled = false;
15135
+ if (agents && agents.length > 0) {
15136
+ payload.agents = agents;
15087
15137
  }
15088
- if (journeyEntry.evaluator_instructions) {
15089
- out.evaluator_instructions = journeyEntry.evaluator_instructions;
15138
+ if (resources.length > 0) {
15139
+ payload.resources = resources;
15090
15140
  }
15091
- if (typeof journeyEntry.fixture === "string" && journeyEntry.fixture.trim()) {
15092
- out.fixture = journeyEntry.fixture.trim();
15141
+ if (config.states && config.states.length > 0) {
15142
+ payload.states = config.states;
15093
15143
  }
15094
- if (typeof journeyEntry.target_base === "string" && journeyEntry.target_base.trim()) {
15095
- out.target_base = journeyEntry.target_base.trim();
15144
+ if (config.teams && config.teams.length > 0) {
15145
+ payload.teams = config.teams;
15096
15146
  }
15097
- if (journeyEntry.variables && Object.keys(journeyEntry.variables).length > 0) {
15098
- out.variables = journeyEntry.variables;
15147
+ if (Array.isArray(config.connections)) {
15148
+ payload.connections = config.connections;
15099
15149
  }
15100
- if (journeyEntry.transcript && journeyEntry.transcript.length > 0) {
15101
- out.transcript = journeyEntry.transcript;
15150
+ if (config.outbound_contacts && config.outbound_contacts.length > 0) {
15151
+ payload.outbound_contacts = config.outbound_contacts;
15102
15152
  }
15103
- if (journeyEntry.step_config && Object.keys(journeyEntry.step_config).length > 0) {
15104
- out.step_config = journeyEntry.step_config;
15153
+ if (config.outbound_lists && config.outbound_lists.length > 0) {
15154
+ payload.outbound_lists = config.outbound_lists;
15105
15155
  }
15106
- return out;
15107
- }
15108
- function writeJourneyYamlFiles(hubFolder, journeys) {
15109
- const journeysDir = path13.join(hubFolder, "journeys");
15110
- if (journeys.length === 0) {
15111
- if (fs11.existsSync(journeysDir)) {
15112
- cleanJourneyOrphans(journeysDir, /* @__PURE__ */ new Set());
15113
- try {
15114
- if (fs11.readdirSync(journeysDir).length === 0) fs11.rmdirSync(journeysDir);
15115
- } catch {
15116
- }
15117
- }
15118
- return;
15156
+ if (config.outbound_schedules && config.outbound_schedules.length > 0) {
15157
+ payload.outbound_schedules = config.outbound_schedules;
15158
+ }
15159
+ const bytesByHash = /* @__PURE__ */ new Map();
15160
+ const evals = scanEvalYamlFiles(hubFolder, bytesByHash);
15161
+ if (evals.length > 0) {
15162
+ payload.evals = evals;
15119
15163
  }
15120
- if (!fs11.existsSync(journeysDir)) {
15121
- fs11.mkdirSync(journeysDir, { recursive: true });
15164
+ const journeys = scanJourneyYamlFiles(hubFolder, bytesByHash);
15165
+ if (journeys.length > 0) {
15166
+ payload.journeys = journeys;
15122
15167
  }
15123
- const writtenFiles = /* @__PURE__ */ new Set();
15124
- const usedSlugs = /* @__PURE__ */ new Set();
15125
- for (const journeyEntry of journeys) {
15126
- const slug = ensureUniqueSlug(slugify(journeyEntry.name), usedSlugs);
15127
- const fileName = `${slug}.yaml`;
15128
- const yamlContent = yaml4.dump(buildJourneyYamlObject(journeyEntry, slug), YAML_DUMP_OPTIONS);
15129
- fs11.writeFileSync(path13.join(journeysDir, fileName), yamlContent, "utf-8");
15130
- writtenFiles.add(fileName);
15168
+ if (bytesByHash.size > 0) {
15169
+ payload.eval_attachment_files = [...bytesByHash.values()];
15131
15170
  }
15132
- cleanJourneyOrphans(journeysDir, writtenFiles);
15171
+ return payload;
15133
15172
  }
15134
- function cleanJourneyOrphans(journeysDir, writtenFiles) {
15135
- const entries = fs11.readdirSync(journeysDir, { withFileTypes: true });
15136
- for (const entry of entries) {
15137
- if (entry.isFile() && entry.name.endsWith(".yaml") && !writtenFiles.has(entry.name)) {
15138
- fs11.unlinkSync(path13.join(journeysDir, entry.name));
15173
+ function scanEvalYamlFiles(hubFolder, bytesByHash) {
15174
+ const evalsDir = path14.join(hubFolder, "evals");
15175
+ if (!fs12.existsSync(evalsDir)) return [];
15176
+ const evals = [];
15177
+ const seen = /* @__PURE__ */ new Set();
15178
+ const topEntries = fs12.readdirSync(evalsDir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
15179
+ for (const entry of topEntries) {
15180
+ if (entry.isFile() && entry.name.endsWith(".yaml")) {
15181
+ collectEval(evals, seen, path14.join(evalsDir, entry.name), `evals/${entry.name}`, null, hubFolder, bytesByHash);
15182
+ } else if (entry.isDirectory()) {
15183
+ const setName = entry.name;
15184
+ const setDir = path14.join(evalsDir, setName);
15185
+ const setEntries = fs12.readdirSync(setDir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
15186
+ for (const sub of setEntries) {
15187
+ if (sub.isDirectory()) {
15188
+ throw expected(
15189
+ `Nested scenario sets are not supported: ${path14.relative(hubFolder, path14.join(setDir, sub.name))}. Move scenarios up to evals/${setName}/.`
15190
+ );
15191
+ }
15192
+ if (sub.isFile() && sub.name.endsWith(".yaml")) {
15193
+ collectEval(evals, seen, path14.join(setDir, sub.name), `evals/${setName}/${sub.name}`, setName, hubFolder, bytesByHash);
15194
+ }
15195
+ }
15139
15196
  }
15140
15197
  }
15198
+ return evals;
15141
15199
  }
15142
- function writeResourceFiles(hubFolder, resources) {
15143
- const resourcesDir = path13.join(hubFolder, "resources");
15144
- const currentSlugs = /* @__PURE__ */ new Set();
15145
- for (const resource of resources) {
15146
- const resSlug = slugify(resource.name);
15147
- currentSlugs.add(resSlug);
15148
- const resDir = path13.join(resourcesDir, resSlug);
15149
- writeResourceFileTree(resDir, resource.files || [], hubFolder);
15200
+ function collectEval(evals, seen, filePath, relPath, setName, hubFolder, bytesByHash) {
15201
+ const evalEntry = parseEvalYaml(filePath, relPath, setName, hubFolder, bytesByHash);
15202
+ if (!evalEntry) return;
15203
+ const key = `${evalEntry.name}|${setName ?? ""}`;
15204
+ if (seen.has(key)) {
15205
+ const where = setName ? `scenario set "${setName}"` : "root";
15206
+ throw expected(
15207
+ `Duplicate eval at ${where}: "${evalEntry.name}" (in ${path14.basename(filePath)}). Each (name, scenario set) pair must be unique.`
15208
+ );
15150
15209
  }
15151
- if (fs11.existsSync(resourcesDir)) {
15152
- const existingDirs = fs11.readdirSync(resourcesDir, { withFileTypes: true });
15153
- for (const entry of existingDirs) {
15154
- if (entry.isDirectory() && !currentSlugs.has(entry.name)) {
15155
- fs11.rmSync(path13.join(resourcesDir, entry.name), { recursive: true, force: true });
15156
- }
15210
+ seen.add(key);
15211
+ evals.push(evalEntry);
15212
+ }
15213
+ function parseEvalYaml(filePath, relPath, setName, hubFolder, bytesByHash) {
15214
+ const content = fs12.readFileSync(filePath, "utf-8");
15215
+ let raw;
15216
+ try {
15217
+ raw = yaml4.load(content);
15218
+ } catch (err) {
15219
+ if (err instanceof yaml4.YAMLException) {
15220
+ throw expected(`Invalid YAML in ${relPath}: ${err.message}`);
15157
15221
  }
15222
+ throw err;
15158
15223
  }
15159
- }
15160
- var YAML_DUMP_OPTIONS;
15161
- var init_yaml_writer = __esm({
15162
- "src/lib/yaml-writer.ts"() {
15163
- "use strict";
15164
- init_dist();
15165
- init_utils();
15166
- init_resource_files();
15167
- init_workspace_files();
15168
- YAML_DUMP_OPTIONS = {
15169
- lineWidth: -1,
15170
- // don't wrap lines
15171
- noRefs: true,
15172
- // don't use YAML anchors
15173
- quotingType: '"',
15174
- forceQuotes: false,
15175
- sortKeys: false
15176
- };
15224
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
15225
+ console.warn(` Warning: skipping ${relPath} (not a YAML object)`);
15226
+ return null;
15177
15227
  }
15178
- });
15179
-
15180
- // src/lib/eval-attachments.ts
15181
- import * as fs12 from "fs";
15182
- import * as path14 from "path";
15183
- function resolveTurnAttachments(hubFolder, turn, label, bytesByHash) {
15184
- const raw = turn.attachments;
15185
- if (raw === void 0 || raw === null) return void 0;
15186
- if (!Array.isArray(raw) || raw.length === 0) {
15187
- if (Array.isArray(raw)) return void 0;
15188
- throw expected(`${label}: "attachments" must be a list of file paths.`);
15228
+ const data = raw;
15229
+ const fileSlug = path14.basename(filePath, ".yaml");
15230
+ const evalName = typeof data.name === "string" && data.name.trim().length > 0 ? data.name : fileSlug;
15231
+ if (typeof data.agent !== "string" || data.agent.trim().length === 0) {
15232
+ throw expected(`Eval "${evalName}" in ${relPath}: missing required field "agent" (string).`);
15189
15233
  }
15190
- if (turn.role !== "user") {
15191
- throw expected(`${label}: attachments are only allowed on user turns (this turn is "${String(turn.role)}").`);
15234
+ if (!data.input || typeof data.input !== "object" || Array.isArray(data.input)) {
15235
+ throw expected(`Eval "${evalName}" in ${relPath}: missing required field "input" (object).`);
15192
15236
  }
15193
- const refs = [];
15194
- for (const entry of raw) {
15195
- if (typeof entry !== "string" || !entry.trim()) {
15196
- throw expected(`${label}: each attachment must be a hub-relative file path string.`);
15197
- }
15198
- const abs = path14.resolve(hubFolder, entry);
15199
- const root = path14.resolve(hubFolder);
15200
- if (abs !== root && !abs.startsWith(root + path14.sep)) {
15201
- throw expected(`${label}: attachment "${entry}" escapes the hub folder.`);
15202
- }
15203
- let stat;
15204
- try {
15205
- stat = fs12.statSync(abs);
15206
- } catch {
15207
- }
15208
- if (!stat?.isFile()) {
15209
- throw expected(`${label}: attachment "${entry}" not found.`);
15210
- }
15211
- if (!realpathContained(root, abs)) {
15212
- throw expected(`${label}: attachment "${entry}" resolves outside the hub folder (symlinks are not allowed).`);
15213
- }
15214
- if (stat.size > MAX_RESOURCE_FILE_SIZE3) {
15237
+ if (!data.expected || typeof data.expected !== "object" || Array.isArray(data.expected)) {
15238
+ throw expected(`Eval "${evalName}" in ${relPath}: missing required field "expected" (object).`);
15239
+ }
15240
+ const input = { ...data.input };
15241
+ applyTurnAttachments(hubFolder, input, `Eval "${evalName}" input`, bytesByHash);
15242
+ const evalEntry = {
15243
+ name: evalName,
15244
+ agent: data.agent,
15245
+ path: setName,
15246
+ input,
15247
+ expected: data.expected
15248
+ };
15249
+ if (typeof data.id === "string") evalEntry.id = data.id;
15250
+ if (typeof data.agent_id === "string") evalEntry.agent_id = data.agent_id;
15251
+ if (typeof data.runs === "number") evalEntry.runs = data.runs;
15252
+ if (data.enabled === false) evalEntry.enabled = false;
15253
+ if (Array.isArray(data.history)) {
15254
+ evalEntry.history = data.history.map((turn, i) => {
15255
+ const t = { ...turn };
15256
+ applyTurnAttachments(hubFolder, t, `Eval "${evalName}" history[${i}]`, bytesByHash);
15257
+ return t;
15258
+ });
15259
+ }
15260
+ if (typeof data.evaluator_instructions === "string") {
15261
+ evalEntry.evaluator_instructions = data.evaluator_instructions;
15262
+ }
15263
+ const variables = parseVariablesMap(data.variables, `Eval "${evalName}" in ${relPath}`);
15264
+ if (variables) evalEntry.variables = variables;
15265
+ const fixture = parseFixtureField(data.fixture, `Eval "${evalName}" in ${relPath}`, "fixture");
15266
+ if (fixture) evalEntry.fixture = fixture;
15267
+ const targetBase = parseFixtureField(data.target_base, `Eval "${evalName}" in ${relPath}`, "target_base");
15268
+ if (targetBase) evalEntry.target_base = targetBase;
15269
+ return evalEntry;
15270
+ }
15271
+ function parseVariablesMap(raw, label) {
15272
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return void 0;
15273
+ const vars = raw;
15274
+ if (Object.keys(vars).length === 0) return void 0;
15275
+ if (!Object.values(vars).every((v) => Array.isArray(v))) {
15276
+ throw expected(`${label}: "variables" must be a map of arrays (variables.<name>: [ ... ]).`);
15277
+ }
15278
+ return vars;
15279
+ }
15280
+ function parseFixtureField(raw, label, key) {
15281
+ if (raw === void 0 || raw === null) return void 0;
15282
+ if (typeof raw !== "string") {
15283
+ throw expected(`${label}: "${key}" must be a string (the Rekor fixture name).`);
15284
+ }
15285
+ const trimmed = raw.trim();
15286
+ return trimmed.length > 0 ? trimmed : void 0;
15287
+ }
15288
+ function scanJourneyYamlFiles(hubFolder, bytesByHash) {
15289
+ const journeysDir = path14.join(hubFolder, "journeys");
15290
+ if (!fs12.existsSync(journeysDir)) return [];
15291
+ const journeys = [];
15292
+ const seen = /* @__PURE__ */ new Set();
15293
+ const entries = fs12.readdirSync(journeysDir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
15294
+ for (const entry of entries) {
15295
+ if (entry.isDirectory()) {
15215
15296
  throw expected(
15216
- `${label}: attachment "${entry}" is ${(stat.size / 1024 / 1024).toFixed(1)}MB, over the 10MB limit.`
15297
+ `Subfolders are not supported under journeys/: ${path14.relative(hubFolder, path14.join(journeysDir, entry.name))}. A journey owns its own managed scenario set \u2014 put each journey in a single journeys/<slug>.yaml file.`
15217
15298
  );
15218
15299
  }
15219
- const data = fs12.readFileSync(abs);
15220
- const hash = computeHash(data);
15221
- const fileName = path14.basename(abs);
15222
- const mimeType = guessMimeType(fileName) ?? "application/octet-stream";
15223
- refs.push({ hash, file_name: fileName, mime_type: mimeType, file_size: stat.size });
15224
- if (!bytesByHash.has(hash)) {
15225
- bytesByHash.set(hash, {
15226
- hash,
15227
- content_base64: data.toString("base64"),
15228
- file_name: fileName,
15229
- mime_type: mimeType,
15230
- file_size: stat.size
15231
- });
15300
+ if (!entry.isFile() || !entry.name.endsWith(".yaml")) continue;
15301
+ const journeyEntry = parseJourneyYaml(path14.join(journeysDir, entry.name), `journeys/${entry.name}`, hubFolder, bytesByHash);
15302
+ if (!journeyEntry) continue;
15303
+ if (seen.has(journeyEntry.name)) {
15304
+ throw expected(
15305
+ `Duplicate journey: "${journeyEntry.name}" (in ${entry.name}). Each journey name must be unique.`
15306
+ );
15232
15307
  }
15308
+ seen.add(journeyEntry.name);
15309
+ journeys.push(journeyEntry);
15233
15310
  }
15234
- return refs;
15235
- }
15236
- function applyTurnAttachments(hubFolder, turn, label, bytesByHash) {
15237
- const refs = resolveTurnAttachments(hubFolder, turn, label, bytesByHash);
15238
- if (refs) turn.attachments = refs;
15239
- else delete turn.attachments;
15311
+ return journeys;
15240
15312
  }
15241
- function forEachAttachableTurn(payload, fn) {
15242
- for (const ev of payload.evals ?? []) {
15243
- if (ev.input) fn(ev.input);
15244
- for (const t of ev.history ?? []) fn(t);
15313
+ function parseJourneyYaml(filePath, relPath, hubFolder, bytesByHash) {
15314
+ const content = fs12.readFileSync(filePath, "utf-8");
15315
+ let raw;
15316
+ try {
15317
+ raw = yaml4.load(content);
15318
+ } catch (err) {
15319
+ if (err instanceof yaml4.YAMLException) {
15320
+ throw expected(`Invalid YAML in ${relPath}: ${err.message}`);
15321
+ }
15322
+ throw err;
15245
15323
  }
15246
- for (const jr of payload.journeys ?? []) {
15247
- for (const t of jr.transcript ?? []) fn(t);
15324
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
15325
+ console.warn(` Warning: skipping ${relPath} (not a YAML object)`);
15326
+ return null;
15248
15327
  }
15249
- }
15250
- function rewriteEvalAttachmentsToLocalPaths(payload) {
15251
- const urlByHash = new Map((payload.eval_attachment_files ?? []).map((f) => [f.hash, f.download_url]));
15252
- const safeName = (ref) => {
15253
- const base = path14.basename(ref.file_name);
15254
- return base === "" || base === "." || base === ".." ? ref.hash.slice(0, 16) : base;
15255
- };
15256
- const hashesByName = /* @__PURE__ */ new Map();
15257
- const collect = (turn) => {
15258
- if (!Array.isArray(turn.attachments)) return;
15259
- for (const ref of turn.attachments) {
15260
- if (!ref?.hash || !ref.file_name) continue;
15261
- const name = safeName(ref);
15262
- let set = hashesByName.get(name);
15263
- if (!set) {
15264
- set = /* @__PURE__ */ new Set();
15265
- hashesByName.set(name, set);
15266
- }
15267
- set.add(ref.hash);
15268
- }
15269
- };
15270
- forEachAttachableTurn(payload, collect);
15271
- const pathByKey = /* @__PURE__ */ new Map();
15272
- const usedLower = /* @__PURE__ */ new Set();
15273
- const localPathFor = (ref) => {
15274
- const name = safeName(ref);
15275
- const key = `${ref.hash} ${name}`;
15276
- const existing = pathByKey.get(key);
15277
- if (existing) return existing;
15278
- const ext = path14.extname(name);
15279
- const stem = path14.basename(name, ext);
15280
- const collides = (hashesByName.get(name)?.size ?? 0) > 1;
15281
- let rel = collides ? `${ATTACHMENTS_DIR}/${stem}-${ref.hash.slice(0, 8)}${ext}` : `${ATTACHMENTS_DIR}/${name}`;
15282
- if (usedLower.has(rel.toLowerCase())) {
15283
- let n = 12;
15284
- while (usedLower.has(rel.toLowerCase()) && n <= ref.hash.length) {
15285
- rel = `${ATTACHMENTS_DIR}/${stem}-${ref.hash.slice(0, n)}${ext}`;
15286
- n += 4;
15287
- }
15288
- for (let extra = 2; usedLower.has(rel.toLowerCase()); extra++) {
15289
- rel = `${ATTACHMENTS_DIR}/${stem}-${ref.hash}-${extra}${ext}`;
15290
- }
15291
- }
15292
- usedLower.add(rel.toLowerCase());
15293
- pathByKey.set(key, rel);
15294
- return rel;
15328
+ const data = raw;
15329
+ const fileSlug = path14.basename(filePath, ".yaml");
15330
+ const journeyName = typeof data.name === "string" && data.name.trim().length > 0 ? data.name : fileSlug;
15331
+ if (typeof data.agent !== "string" || data.agent.trim().length === 0) {
15332
+ throw expected(`Journey "${journeyName}" in ${relPath}: missing required field "agent" (string).`);
15333
+ }
15334
+ if (data.transcript !== void 0 && !Array.isArray(data.transcript)) {
15335
+ throw expected(`Journey "${journeyName}" in ${relPath}: "transcript" must be an array.`);
15336
+ }
15337
+ if (data.step_config !== void 0 && (typeof data.step_config !== "object" || Array.isArray(data.step_config) || data.step_config === null)) {
15338
+ throw expected(`Journey "${journeyName}" in ${relPath}: "step_config" must be an object keyed by step_id.`);
15339
+ }
15340
+ const journeyEntry = {
15341
+ name: journeyName,
15342
+ agent: data.agent
15295
15343
  };
15296
- const downloads = /* @__PURE__ */ new Map();
15297
- forEachAttachableTurn(payload, (turn) => {
15298
- if (!Array.isArray(turn.attachments)) return;
15299
- turn.attachments = turn.attachments.map((ref) => {
15300
- const rel = localPathFor(ref);
15301
- const url = urlByHash.get(ref.hash);
15302
- if (url && !downloads.has(rel)) downloads.set(rel, { relPath: rel, url, hash: ref.hash });
15303
- return rel;
15344
+ if (typeof data.id === "string") journeyEntry.id = data.id;
15345
+ if (typeof data.agent_id === "string") journeyEntry.agent_id = data.agent_id;
15346
+ if (data.enabled === false) journeyEntry.enabled = false;
15347
+ if (typeof data.evaluator_instructions === "string") {
15348
+ journeyEntry.evaluator_instructions = data.evaluator_instructions;
15349
+ }
15350
+ if (Array.isArray(data.transcript)) {
15351
+ journeyEntry.transcript = data.transcript.map((turn, i) => {
15352
+ const t = { ...turn };
15353
+ applyTurnAttachments(hubFolder, t, `Journey "${journeyName}" transcript[${i}]`, bytesByHash);
15354
+ return t;
15304
15355
  });
15305
- });
15306
- delete payload.eval_attachment_files;
15307
- return [...downloads.values()];
15308
- }
15309
- async function downloadEvalAttachments(hubFolder, downloads) {
15310
- const attachDir = path14.resolve(hubFolder, ATTACHMENTS_DIR);
15311
- let written = 0;
15312
- if (!ensureRealSubdirNoSymlink(hubFolder, attachDir, downloads.length > 0)) {
15313
- console.warn(` Warning: ${ATTACHMENTS_DIR}/ is not reachable inside the hub folder without a symlink; skipping attachment sync.`);
15314
- return written;
15315
15356
  }
15316
- if (!fs12.existsSync(attachDir)) return written;
15317
- for (const dl of downloads) {
15318
- const abs = path14.resolve(hubFolder, dl.relPath);
15319
- if (abs !== attachDir && !abs.startsWith(attachDir + path14.sep)) continue;
15320
- let existing;
15357
+ if (data.step_config && typeof data.step_config === "object" && !Array.isArray(data.step_config)) {
15358
+ journeyEntry.step_config = data.step_config;
15359
+ }
15360
+ const variables = parseVariablesMap(data.variables, `Journey "${journeyName}" in ${relPath}`);
15361
+ if (variables) journeyEntry.variables = variables;
15362
+ const fixture = parseFixtureField(data.fixture, `Journey "${journeyName}" in ${relPath}`, "fixture");
15363
+ if (fixture) journeyEntry.fixture = fixture;
15364
+ const targetBase = parseFixtureField(data.target_base, `Journey "${journeyName}" in ${relPath}`, "target_base");
15365
+ if (targetBase) journeyEntry.target_base = targetBase;
15366
+ return journeyEntry;
15367
+ }
15368
+ function scanAgentYamlFiles(hubFolder) {
15369
+ const agentsDir = path14.join(hubFolder, "agents");
15370
+ if (!fs12.existsSync(agentsDir)) return [];
15371
+ const yamlFiles = fs12.readdirSync(agentsDir).filter((f) => f.endsWith(".yaml")).sort();
15372
+ if (yamlFiles.length === 0) return [];
15373
+ const agents = [];
15374
+ for (const file of yamlFiles) {
15375
+ const filePath = path14.join(agentsDir, file);
15376
+ const content = fs12.readFileSync(filePath, "utf-8");
15377
+ let agent;
15321
15378
  try {
15322
- existing = fs12.lstatSync(abs);
15323
- } catch {
15379
+ agent = yaml4.load(content);
15380
+ } catch (err) {
15381
+ if (err instanceof yaml4.YAMLException) {
15382
+ const userError = new Error(`Invalid YAML in agents/${file}: ${err.message}`);
15383
+ userError.isExpected = true;
15384
+ throw userError;
15385
+ }
15386
+ throw err;
15324
15387
  }
15325
- if (existing?.isFile() && !existing.isSymbolicLink() && computeHash(fs12.readFileSync(abs)) === dl.hash) {
15388
+ if (!agent || typeof agent !== "object") continue;
15389
+ if (!agent.name) {
15390
+ console.warn(` Warning: skipping ${file} (missing 'name' field)`);
15326
15391
  continue;
15327
15392
  }
15328
- try {
15329
- const res = await fetch(dl.url);
15330
- if (!res.ok) {
15331
- console.warn(` Warning: failed to download ${dl.relPath} (HTTP ${res.status})`);
15332
- continue;
15393
+ agents.push(agent);
15394
+ }
15395
+ return agents;
15396
+ }
15397
+ function parseResources(hubFolder, configResources) {
15398
+ const resourcesDir = path14.join(hubFolder, "resources");
15399
+ const results = [];
15400
+ for (const res of configResources) {
15401
+ const resource = {
15402
+ name: res.name
15403
+ };
15404
+ if (res.id) resource.id = res.id;
15405
+ if (res.type === "knowledge" || res.type === "skill") resource.type = res.type;
15406
+ else if (res.type !== void 0) throw new Error(`Invalid resource type for "${resource.name}": expected knowledge or skill`);
15407
+ if (res.description) resource.description = res.description;
15408
+ if (res.enabled === false) resource.enabled = false;
15409
+ if (res.user_browsable) resource.user_browsable = res.user_browsable;
15410
+ if (res.skill_name) resource.skill_name = res.skill_name;
15411
+ const resSlug = slugify(resource.name);
15412
+ const resDir = path14.join(resourcesDir, resSlug);
15413
+ if (fs12.existsSync(resDir)) {
15414
+ const files = scanResourceFiles(resDir, "");
15415
+ if (files.length > 0) {
15416
+ resource.files = files;
15333
15417
  }
15334
- if (writeFileNoFollow(hubFolder, abs, Buffer.from(await res.arrayBuffer()))) written++;
15335
- } catch (err) {
15336
- console.warn(` Warning: failed to download ${dl.relPath}: ${err instanceof Error ? err.message : String(err)}`);
15337
15418
  }
15419
+ results.push(resource);
15338
15420
  }
15339
- const keep = new Set(downloads.map((d) => path14.resolve(hubFolder, d.relPath)));
15340
- for (const name of fs12.readdirSync(attachDir)) {
15341
- const abs = path14.join(attachDir, name);
15342
- const st = fs12.lstatSync(abs);
15343
- if ((st.isFile() || st.isSymbolicLink()) && !keep.has(abs)) fs12.rmSync(abs);
15344
- }
15345
- return written;
15421
+ return results;
15346
15422
  }
15347
- var ATTACHMENTS_DIR;
15348
- var init_eval_attachments = __esm({
15349
- "src/lib/eval-attachments.ts"() {
15423
+ var init_parser = __esm({
15424
+ "src/lib/parser.ts"() {
15350
15425
  "use strict";
15426
+ init_utils();
15351
15427
  init_resource_files();
15352
- init_fs_safety();
15353
- init_parser();
15354
- ATTACHMENTS_DIR = "attachments";
15428
+ init_eval_attachments();
15355
15429
  }
15356
15430
  });
15357
15431
 
15358
- // src/lib/parser.ts
15359
- import * as fs13 from "fs";
15360
- import * as path15 from "path";
15361
- import * as yaml5 from "js-yaml";
15362
- function expected(message) {
15363
- const err = new Error(message);
15364
- err.isExpected = true;
15365
- return err;
15366
- }
15367
- function parseHubFolder(hubFolder, opts) {
15368
- const yamlPath = resolveHubYamlPath(hubFolder);
15369
- if (!yamlPath) {
15370
- const err = new Error(`hub.yaml not found in ${hubFolder}`);
15371
- err.isExpected = true;
15372
- throw err;
15373
- }
15374
- const yamlContent = fs13.readFileSync(yamlPath, "utf-8");
15375
- let config;
15376
- try {
15377
- config = yaml5.load(yamlContent);
15378
- } catch (err) {
15379
- if (err instanceof yaml5.YAMLException) {
15380
- const userError = new Error(`Invalid YAML in ${yamlPath}: ${err.message}`);
15381
- userError.isExpected = true;
15382
- throw userError;
15432
+ // src/lib/diff-display.ts
15433
+ function printDiff(summary) {
15434
+ const lines = summary.split("\n");
15435
+ for (const line of lines) {
15436
+ if (line.startsWith("**") && line.endsWith("**")) {
15437
+ console.log(`
15438
+ \x1B[1m${line.replace(/\*\*/g, "")}\x1B[0m`);
15439
+ } else if (line.trimStart().startsWith("+ ")) {
15440
+ console.log(`\x1B[32m${line}\x1B[0m`);
15441
+ } else if (line.trimStart().startsWith("- ")) {
15442
+ console.log(`\x1B[31m${line}\x1B[0m`);
15443
+ } else if (line.trimStart().startsWith("~ ")) {
15444
+ console.log(`\x1B[33m${line}\x1B[0m`);
15445
+ } else if (line.includes("(no changes)")) {
15446
+ console.log(`\x1B[2m${line}\x1B[0m`);
15447
+ } else {
15448
+ console.log(line);
15383
15449
  }
15384
- throw err;
15385
- }
15386
- if (!config || typeof config !== "object") {
15387
- const invalidError = new Error(`Invalid YAML in ${yamlPath}`);
15388
- invalidError.isExpected = true;
15389
- throw invalidError;
15390
15450
  }
15391
- if (!opts?.allowNewHub) {
15392
- if (!config.hub_id) {
15393
- const err = new Error(`Missing hub_id in ${yamlPath}`);
15394
- err.isExpected = true;
15395
- throw err;
15396
- }
15397
- if (!config.hub_environment) {
15398
- const err = new Error(`Missing hub_environment in ${yamlPath}`);
15399
- err.isExpected = true;
15400
- throw err;
15401
- }
15451
+ console.log();
15452
+ }
15453
+ var init_diff_display = __esm({
15454
+ "src/lib/diff-display.ts"() {
15455
+ "use strict";
15402
15456
  }
15403
- const agentYamlFiles = scanAgentYamlFiles(hubFolder);
15404
- const rawAgents = agentYamlFiles.length > 0 ? agentYamlFiles : config.agents;
15405
- const agents = rawAgents?.map((agent) => {
15406
- const resolved = { ...agent };
15407
- if (typeof resolved.instructions === "string" && resolved.instructions.endsWith(".md")) {
15408
- const instrValue = resolved.instructions;
15409
- const instructionsPath = instrValue.startsWith("agents/") ? path15.join(hubFolder, instrValue) : path15.join(hubFolder, "agents", instrValue);
15410
- if (fs13.existsSync(instructionsPath)) {
15411
- resolved.instructions = fs13.readFileSync(instructionsPath, "utf-8");
15412
- } else {
15413
- const err = new Error(
15414
- `Agent instructions file not found: ${instructionsPath} (referenced by agent "${agent.name}")`
15415
- );
15416
- err.isExpected = true;
15417
- throw err;
15418
- }
15419
- } else if (resolved.instructions === void 0 && typeof agent.name === "string") {
15420
- const conventionPath = path15.join(hubFolder, "agents", `${slugify(agent.name)}.md`);
15421
- if (fs13.existsSync(conventionPath)) {
15422
- resolved.instructions = fs13.readFileSync(conventionPath, "utf-8");
15423
- }
15424
- }
15425
- return resolved;
15426
- });
15427
- const resources = parseResources(hubFolder, config.resources || []);
15428
- const payload = {
15429
- version: config.version || 1,
15430
- hub_id: config.hub_id || "",
15431
- hub_environment: config.hub_environment || "preview",
15432
- hub: config.hub
15433
- };
15434
- if (config.preview_label) {
15435
- payload.preview_label = config.preview_label;
15457
+ });
15458
+
15459
+ // src/lib/yaml-writer.ts
15460
+ import * as fs13 from "fs";
15461
+ import * as path15 from "path";
15462
+ import * as yaml5 from "js-yaml";
15463
+ function buildEvalYamlObject(evalEntry, slug) {
15464
+ const out = {};
15465
+ if (evalEntry.id) out.id = evalEntry.id;
15466
+ if (evalEntry.name !== slug) out.name = evalEntry.name;
15467
+ out.agent = evalEntry.agent;
15468
+ if (evalEntry.agent_id) out.agent_id = evalEntry.agent_id;
15469
+ if (typeof evalEntry.runs === "number" && evalEntry.runs !== 1) {
15470
+ out.runs = evalEntry.runs;
15436
15471
  }
15437
- if (agents && agents.length > 0) {
15438
- payload.agents = agents;
15472
+ if (evalEntry.enabled === false) {
15473
+ out.enabled = false;
15439
15474
  }
15440
- if (resources.length > 0) {
15441
- payload.resources = resources;
15475
+ if (evalEntry.history && evalEntry.history.length > 0) {
15476
+ for (const turn of evalEntry.history) assertAttachmentsAreLocalPaths(turn, `eval "${evalEntry.name}" history`);
15477
+ out.history = evalEntry.history;
15442
15478
  }
15443
- if (config.states && config.states.length > 0) {
15444
- payload.states = config.states;
15479
+ if (typeof evalEntry.fixture === "string" && evalEntry.fixture.trim()) {
15480
+ out.fixture = evalEntry.fixture.trim();
15445
15481
  }
15446
- if (config.teams && config.teams.length > 0) {
15447
- payload.teams = config.teams;
15482
+ if (typeof evalEntry.target_base === "string" && evalEntry.target_base.trim()) {
15483
+ out.target_base = evalEntry.target_base.trim();
15448
15484
  }
15449
- if (Array.isArray(config.connections)) {
15450
- payload.connections = config.connections;
15485
+ if (evalEntry.variables && Object.keys(evalEntry.variables).length > 0) {
15486
+ out.variables = evalEntry.variables;
15451
15487
  }
15452
- if (config.outbound_contacts && config.outbound_contacts.length > 0) {
15453
- payload.outbound_contacts = config.outbound_contacts;
15488
+ assertAttachmentsAreLocalPaths(evalEntry.input, `eval "${evalEntry.name}" input`);
15489
+ out.input = evalEntry.input;
15490
+ out.expected = evalEntry.expected;
15491
+ if (evalEntry.evaluator_instructions) {
15492
+ out.evaluator_instructions = evalEntry.evaluator_instructions;
15454
15493
  }
15455
- if (config.outbound_lists && config.outbound_lists.length > 0) {
15456
- payload.outbound_lists = config.outbound_lists;
15494
+ return out;
15495
+ }
15496
+ function writeHubFolder(hubFolder, payload, options = {}) {
15497
+ const agentsDir = path15.join(hubFolder, "agents");
15498
+ if (!fs13.existsSync(hubFolder)) {
15499
+ fs13.mkdirSync(hubFolder, { recursive: true });
15457
15500
  }
15458
- if (config.outbound_schedules && config.outbound_schedules.length > 0) {
15459
- payload.outbound_schedules = config.outbound_schedules;
15501
+ if (!fs13.existsSync(agentsDir)) {
15502
+ fs13.mkdirSync(agentsDir, { recursive: true });
15460
15503
  }
15461
- const bytesByHash = /* @__PURE__ */ new Map();
15462
- const evals = scanEvalYamlFiles(hubFolder, bytesByHash);
15463
- if (evals.length > 0) {
15464
- payload.evals = evals;
15504
+ const yamlPayload = buildYamlPayload(payload);
15505
+ const agentFiles = extractAgentFiles(payload);
15506
+ const yamlContent = yaml5.dump(yamlPayload, YAML_DUMP_OPTIONS);
15507
+ fs13.writeFileSync(path15.join(hubFolder, "hub.yaml"), yamlContent, "utf-8");
15508
+ if (options.seedAgentContext ?? true) {
15509
+ writeIfAbsent(path15.join(hubFolder, "AGENTS.md"), perHubAgentsMd(path15.basename(hubFolder)));
15510
+ writeIfAbsent(path15.join(hubFolder, "CLAUDE.md"), CLAUDE_MD_SHIM);
15465
15511
  }
15466
- const journeys = scanJourneyYamlFiles(hubFolder, bytesByHash);
15467
- if (journeys.length > 0) {
15468
- payload.journeys = journeys;
15512
+ const oldYamlPath = path15.join(hubFolder, "wayai.yaml");
15513
+ if (fs13.existsSync(oldYamlPath)) {
15514
+ fs13.unlinkSync(oldYamlPath);
15469
15515
  }
15470
- if (bytesByHash.size > 0) {
15471
- payload.eval_attachment_files = [...bytesByHash.values()];
15516
+ const yamlSlugs = writeAgentYamlFiles(agentsDir, payload.agents || []);
15517
+ const mdSlugs = /* @__PURE__ */ new Set();
15518
+ for (const { slug, content } of agentFiles) {
15519
+ mdSlugs.add(slug);
15520
+ fs13.writeFileSync(path15.join(agentsDir, `${slug}.md`), content, "utf-8");
15472
15521
  }
15473
- return payload;
15474
- }
15475
- function scanEvalYamlFiles(hubFolder, bytesByHash) {
15476
- const evalsDir = path15.join(hubFolder, "evals");
15477
- if (!fs13.existsSync(evalsDir)) return [];
15478
- const evals = [];
15479
- const seen = /* @__PURE__ */ new Set();
15480
- const topEntries = fs13.readdirSync(evalsDir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
15481
- for (const entry of topEntries) {
15482
- if (entry.isFile() && entry.name.endsWith(".yaml")) {
15483
- collectEval(evals, seen, path15.join(evalsDir, entry.name), `evals/${entry.name}`, null, hubFolder, bytesByHash);
15484
- } else if (entry.isDirectory()) {
15485
- const setName = entry.name;
15486
- const setDir = path15.join(evalsDir, setName);
15487
- const setEntries = fs13.readdirSync(setDir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
15488
- for (const sub of setEntries) {
15489
- if (sub.isDirectory()) {
15490
- throw expected(
15491
- `Nested scenario sets are not supported: ${path15.relative(hubFolder, path15.join(setDir, sub.name))}. Move scenarios up to evals/${setName}/.`
15492
- );
15493
- }
15494
- if (sub.isFile() && sub.name.endsWith(".yaml")) {
15495
- collectEval(evals, seen, path15.join(setDir, sub.name), `evals/${setName}/${sub.name}`, setName, hubFolder, bytesByHash);
15496
- }
15522
+ const existingFiles = fs13.readdirSync(agentsDir);
15523
+ for (const file of existingFiles) {
15524
+ if (file.endsWith(".yaml")) {
15525
+ const slug = file.slice(0, -5);
15526
+ if (!yamlSlugs.has(slug)) {
15527
+ fs13.unlinkSync(path15.join(agentsDir, file));
15528
+ }
15529
+ } else if (file.endsWith(".md")) {
15530
+ const slug = file.slice(0, -3);
15531
+ if (!mdSlugs.has(slug)) {
15532
+ fs13.unlinkSync(path15.join(agentsDir, file));
15497
15533
  }
15498
15534
  }
15499
15535
  }
15500
- return evals;
15536
+ writeResourceFiles(hubFolder, payload.resources || []);
15537
+ writeEvalYamlFiles(hubFolder, payload.evals || []);
15538
+ writeJourneyYamlFiles(hubFolder, payload.journeys || []);
15501
15539
  }
15502
- function collectEval(evals, seen, filePath, relPath, setName, hubFolder, bytesByHash) {
15503
- const evalEntry = parseEvalYaml(filePath, relPath, setName, hubFolder, bytesByHash);
15504
- if (!evalEntry) return;
15505
- const key = `${evalEntry.name}|${setName ?? ""}`;
15506
- if (seen.has(key)) {
15507
- const where = setName ? `scenario set "${setName}"` : "root";
15508
- throw expected(
15509
- `Duplicate eval at ${where}: "${evalEntry.name}" (in ${path15.basename(filePath)}). Each (name, scenario set) pair must be unique.`
15510
- );
15511
- }
15512
- seen.add(key);
15513
- evals.push(evalEntry);
15540
+ function setPreviewLabelInHubYaml(hubFolder, label) {
15541
+ const yamlPath = resolveHubYamlPath(hubFolder);
15542
+ if (!yamlPath) return;
15543
+ const obj = yaml5.load(fs13.readFileSync(yamlPath, "utf-8")) ?? {};
15544
+ if (label) obj.preview_label = label;
15545
+ else delete obj.preview_label;
15546
+ fs13.writeFileSync(yamlPath, yaml5.dump(obj, YAML_DUMP_OPTIONS), "utf-8");
15514
15547
  }
15515
- function parseEvalYaml(filePath, relPath, setName, hubFolder, bytesByHash) {
15516
- const content = fs13.readFileSync(filePath, "utf-8");
15517
- let raw;
15518
- try {
15519
- raw = yaml5.load(content);
15520
- } catch (err) {
15521
- if (err instanceof yaml5.YAMLException) {
15522
- throw expected(`Invalid YAML in ${relPath}: ${err.message}`);
15523
- }
15524
- throw err;
15548
+ function buildYamlPayload(payload) {
15549
+ const result = {
15550
+ version: payload.version,
15551
+ hub_id: payload.hub_id,
15552
+ hub_environment: payload.hub_environment
15553
+ };
15554
+ if (payload.preview_label) {
15555
+ result.preview_label = payload.preview_label;
15525
15556
  }
15526
- if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
15527
- console.warn(` Warning: skipping ${relPath} (not a YAML object)`);
15528
- return null;
15557
+ result.hub = payload.hub;
15558
+ if (payload.resources && payload.resources.length > 0) {
15559
+ result.resources = payload.resources.map((r) => {
15560
+ const { files, ...rest } = r;
15561
+ return rest;
15562
+ });
15529
15563
  }
15530
- const data = raw;
15531
- const fileSlug = path15.basename(filePath, ".yaml");
15532
- const evalName = typeof data.name === "string" && data.name.trim().length > 0 ? data.name : fileSlug;
15533
- if (typeof data.agent !== "string" || data.agent.trim().length === 0) {
15534
- throw expected(`Eval "${evalName}" in ${relPath}: missing required field "agent" (string).`);
15564
+ if (payload.states && payload.states.length > 0) {
15565
+ result.states = payload.states;
15535
15566
  }
15536
- if (!data.input || typeof data.input !== "object" || Array.isArray(data.input)) {
15537
- throw expected(`Eval "${evalName}" in ${relPath}: missing required field "input" (object).`);
15567
+ if (payload.teams && payload.teams.length > 0) {
15568
+ result.teams = payload.teams;
15538
15569
  }
15539
- if (!data.expected || typeof data.expected !== "object" || Array.isArray(data.expected)) {
15540
- throw expected(`Eval "${evalName}" in ${relPath}: missing required field "expected" (object).`);
15570
+ if (payload.connections && payload.connections.length > 0) {
15571
+ result.connections = payload.connections;
15541
15572
  }
15542
- const input = { ...data.input };
15543
- applyTurnAttachments(hubFolder, input, `Eval "${evalName}" input`, bytesByHash);
15544
- const evalEntry = {
15545
- name: evalName,
15546
- agent: data.agent,
15547
- path: setName,
15548
- input,
15549
- expected: data.expected
15550
- };
15551
- if (typeof data.id === "string") evalEntry.id = data.id;
15552
- if (typeof data.agent_id === "string") evalEntry.agent_id = data.agent_id;
15553
- if (typeof data.runs === "number") evalEntry.runs = data.runs;
15554
- if (data.enabled === false) evalEntry.enabled = false;
15555
- if (Array.isArray(data.history)) {
15556
- evalEntry.history = data.history.map((turn, i) => {
15557
- const t = { ...turn };
15558
- applyTurnAttachments(hubFolder, t, `Eval "${evalName}" history[${i}]`, bytesByHash);
15559
- return t;
15560
- });
15573
+ if (payload.outbound_contacts && payload.outbound_contacts.length > 0) {
15574
+ result.outbound_contacts = payload.outbound_contacts;
15561
15575
  }
15562
- if (typeof data.evaluator_instructions === "string") {
15563
- evalEntry.evaluator_instructions = data.evaluator_instructions;
15576
+ if (payload.outbound_lists && payload.outbound_lists.length > 0) {
15577
+ result.outbound_lists = payload.outbound_lists;
15564
15578
  }
15565
- const variables = parseVariablesMap(data.variables, `Eval "${evalName}" in ${relPath}`);
15566
- if (variables) evalEntry.variables = variables;
15567
- const fixture = parseFixtureField(data.fixture, `Eval "${evalName}" in ${relPath}`, "fixture");
15568
- if (fixture) evalEntry.fixture = fixture;
15569
- const targetBase = parseFixtureField(data.target_base, `Eval "${evalName}" in ${relPath}`, "target_base");
15570
- if (targetBase) evalEntry.target_base = targetBase;
15571
- return evalEntry;
15579
+ if (payload.outbound_schedules && payload.outbound_schedules.length > 0) {
15580
+ result.outbound_schedules = payload.outbound_schedules;
15581
+ }
15582
+ return result;
15572
15583
  }
15573
- function parseVariablesMap(raw, label) {
15574
- if (!raw || typeof raw !== "object" || Array.isArray(raw)) return void 0;
15575
- const vars = raw;
15576
- if (Object.keys(vars).length === 0) return void 0;
15577
- if (!Object.values(vars).every((v) => Array.isArray(v))) {
15578
- throw expected(`${label}: "variables" must be a map of arrays (variables.<name>: [ ... ]).`);
15584
+ function writeAgentYamlFiles(agentsDir, agents) {
15585
+ const slugs = /* @__PURE__ */ new Set();
15586
+ for (const agent of agents) {
15587
+ const slug = slugify(agent.name);
15588
+ slugs.add(slug);
15589
+ const entry = { ...agent };
15590
+ delete entry.instructions;
15591
+ const yamlContent = yaml5.dump(entry, YAML_DUMP_OPTIONS);
15592
+ fs13.writeFileSync(path15.join(agentsDir, `${slug}.yaml`), yamlContent, "utf-8");
15579
15593
  }
15580
- return vars;
15594
+ return slugs;
15581
15595
  }
15582
- function parseFixtureField(raw, label, key) {
15583
- if (raw === void 0 || raw === null) return void 0;
15584
- if (typeof raw !== "string") {
15585
- throw expected(`${label}: "${key}" must be a string (the Rekor fixture name).`);
15596
+ function extractAgentFiles(payload) {
15597
+ const files = [];
15598
+ for (const agent of payload.agents || []) {
15599
+ if (agent.instructions) {
15600
+ files.push({
15601
+ slug: slugify(agent.name),
15602
+ content: agent.instructions
15603
+ });
15604
+ }
15586
15605
  }
15587
- const trimmed = raw.trim();
15588
- return trimmed.length > 0 ? trimmed : void 0;
15606
+ return files;
15589
15607
  }
15590
- function scanJourneyYamlFiles(hubFolder, bytesByHash) {
15591
- const journeysDir = path15.join(hubFolder, "journeys");
15592
- if (!fs13.existsSync(journeysDir)) return [];
15593
- const journeys = [];
15594
- const seen = /* @__PURE__ */ new Set();
15595
- const entries = fs13.readdirSync(journeysDir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
15608
+ function writeEvalYamlFiles(hubFolder, evals) {
15609
+ const evalsDir = path15.join(hubFolder, "evals");
15610
+ if (evals.length === 0) {
15611
+ if (fs13.existsSync(evalsDir)) {
15612
+ cleanEvalOrphans(evalsDir, /* @__PURE__ */ new Set());
15613
+ try {
15614
+ if (fs13.readdirSync(evalsDir).length === 0) fs13.rmdirSync(evalsDir);
15615
+ } catch {
15616
+ }
15617
+ }
15618
+ return;
15619
+ }
15620
+ if (!fs13.existsSync(evalsDir)) {
15621
+ fs13.mkdirSync(evalsDir, { recursive: true });
15622
+ }
15623
+ const writtenRelPaths = /* @__PURE__ */ new Set();
15624
+ for (const evalEntry of evals) {
15625
+ const slug = slugify(evalEntry.name);
15626
+ const setName = evalEntry.path && evalEntry.path.trim().length > 0 ? evalEntry.path : null;
15627
+ const relPath = setName ? `${setName}/${slug}.yaml` : `${slug}.yaml`;
15628
+ const targetPath = path15.join(evalsDir, relPath);
15629
+ if (!targetPath.startsWith(evalsDir + path15.sep)) {
15630
+ console.warn(` Warning: skipping eval "${evalEntry.name}" (scenario set "${setName}" escapes evals/ \u2014 possible bad backend data)`);
15631
+ continue;
15632
+ }
15633
+ if (setName) {
15634
+ const setDir = path15.join(evalsDir, setName);
15635
+ if (!fs13.existsSync(setDir)) {
15636
+ fs13.mkdirSync(setDir, { recursive: true });
15637
+ }
15638
+ }
15639
+ const yamlContent = yaml5.dump(buildEvalYamlObject(evalEntry, slug), YAML_DUMP_OPTIONS);
15640
+ fs13.writeFileSync(targetPath, yamlContent, "utf-8");
15641
+ writtenRelPaths.add(relPath);
15642
+ }
15643
+ cleanEvalOrphans(evalsDir, writtenRelPaths);
15644
+ }
15645
+ function cleanEvalOrphans(evalsDir, writtenRelPaths) {
15646
+ const entries = fs13.readdirSync(evalsDir, { withFileTypes: true });
15596
15647
  for (const entry of entries) {
15597
- if (entry.isDirectory()) {
15598
- throw expected(
15599
- `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.`
15600
- );
15648
+ const fullPath = path15.join(evalsDir, entry.name);
15649
+ if (entry.isFile()) {
15650
+ if (entry.name.endsWith(".yaml") && !writtenRelPaths.has(entry.name)) {
15651
+ fs13.unlinkSync(fullPath);
15652
+ }
15653
+ continue;
15601
15654
  }
15602
- if (!entry.isFile() || !entry.name.endsWith(".yaml")) continue;
15603
- const journeyEntry = parseJourneyYaml(path15.join(journeysDir, entry.name), `journeys/${entry.name}`, hubFolder, bytesByHash);
15604
- if (!journeyEntry) continue;
15605
- if (seen.has(journeyEntry.name)) {
15606
- throw expected(
15607
- `Duplicate journey: "${journeyEntry.name}" (in ${entry.name}). Each journey name must be unique.`
15608
- );
15655
+ if (entry.isDirectory()) {
15656
+ const setName = entry.name;
15657
+ const subEntries = fs13.readdirSync(fullPath, { withFileTypes: true });
15658
+ for (const sub of subEntries) {
15659
+ if (sub.isFile() && sub.name.endsWith(".yaml")) {
15660
+ const relPath = `${setName}/${sub.name}`;
15661
+ if (!writtenRelPaths.has(relPath)) {
15662
+ fs13.unlinkSync(path15.join(fullPath, sub.name));
15663
+ }
15664
+ }
15665
+ }
15666
+ try {
15667
+ if (fs13.readdirSync(fullPath).length === 0) fs13.rmdirSync(fullPath);
15668
+ } catch {
15669
+ }
15609
15670
  }
15610
- seen.add(journeyEntry.name);
15611
- journeys.push(journeyEntry);
15612
15671
  }
15613
- return journeys;
15614
15672
  }
15615
- function parseJourneyYaml(filePath, relPath, hubFolder, bytesByHash) {
15616
- const content = fs13.readFileSync(filePath, "utf-8");
15617
- let raw;
15618
- try {
15619
- raw = yaml5.load(content);
15620
- } catch (err) {
15621
- if (err instanceof yaml5.YAMLException) {
15622
- throw expected(`Invalid YAML in ${relPath}: ${err.message}`);
15623
- }
15624
- throw err;
15625
- }
15626
- if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
15627
- console.warn(` Warning: skipping ${relPath} (not a YAML object)`);
15628
- return null;
15673
+ function buildJourneyYamlObject(journeyEntry, slug) {
15674
+ const out = {};
15675
+ if (journeyEntry.id) out.id = journeyEntry.id;
15676
+ if (journeyEntry.name !== slug) out.name = journeyEntry.name;
15677
+ out.agent = journeyEntry.agent;
15678
+ if (journeyEntry.agent_id) out.agent_id = journeyEntry.agent_id;
15679
+ if (journeyEntry.enabled === false) {
15680
+ out.enabled = false;
15629
15681
  }
15630
- const data = raw;
15631
- const fileSlug = path15.basename(filePath, ".yaml");
15632
- const journeyName = typeof data.name === "string" && data.name.trim().length > 0 ? data.name : fileSlug;
15633
- if (typeof data.agent !== "string" || data.agent.trim().length === 0) {
15634
- throw expected(`Journey "${journeyName}" in ${relPath}: missing required field "agent" (string).`);
15682
+ if (journeyEntry.evaluator_instructions) {
15683
+ out.evaluator_instructions = journeyEntry.evaluator_instructions;
15635
15684
  }
15636
- if (data.transcript !== void 0 && !Array.isArray(data.transcript)) {
15637
- throw expected(`Journey "${journeyName}" in ${relPath}: "transcript" must be an array.`);
15685
+ if (typeof journeyEntry.fixture === "string" && journeyEntry.fixture.trim()) {
15686
+ out.fixture = journeyEntry.fixture.trim();
15638
15687
  }
15639
- if (data.step_config !== void 0 && (typeof data.step_config !== "object" || Array.isArray(data.step_config) || data.step_config === null)) {
15640
- throw expected(`Journey "${journeyName}" in ${relPath}: "step_config" must be an object keyed by step_id.`);
15688
+ if (typeof journeyEntry.target_base === "string" && journeyEntry.target_base.trim()) {
15689
+ out.target_base = journeyEntry.target_base.trim();
15641
15690
  }
15642
- const journeyEntry = {
15643
- name: journeyName,
15644
- agent: data.agent
15645
- };
15646
- if (typeof data.id === "string") journeyEntry.id = data.id;
15647
- if (typeof data.agent_id === "string") journeyEntry.agent_id = data.agent_id;
15648
- if (data.enabled === false) journeyEntry.enabled = false;
15649
- if (typeof data.evaluator_instructions === "string") {
15650
- journeyEntry.evaluator_instructions = data.evaluator_instructions;
15691
+ if (journeyEntry.variables && Object.keys(journeyEntry.variables).length > 0) {
15692
+ out.variables = journeyEntry.variables;
15651
15693
  }
15652
- if (Array.isArray(data.transcript)) {
15653
- journeyEntry.transcript = data.transcript.map((turn, i) => {
15654
- const t = { ...turn };
15655
- applyTurnAttachments(hubFolder, t, `Journey "${journeyName}" transcript[${i}]`, bytesByHash);
15656
- return t;
15657
- });
15694
+ if (journeyEntry.transcript && journeyEntry.transcript.length > 0) {
15695
+ for (const turn of journeyEntry.transcript) assertAttachmentsAreLocalPaths(turn, `journey "${journeyEntry.name}" transcript`);
15696
+ out.transcript = journeyEntry.transcript;
15658
15697
  }
15659
- if (data.step_config && typeof data.step_config === "object" && !Array.isArray(data.step_config)) {
15660
- journeyEntry.step_config = data.step_config;
15698
+ if (journeyEntry.step_config && Object.keys(journeyEntry.step_config).length > 0) {
15699
+ out.step_config = journeyEntry.step_config;
15661
15700
  }
15662
- const variables = parseVariablesMap(data.variables, `Journey "${journeyName}" in ${relPath}`);
15663
- if (variables) journeyEntry.variables = variables;
15664
- const fixture = parseFixtureField(data.fixture, `Journey "${journeyName}" in ${relPath}`, "fixture");
15665
- if (fixture) journeyEntry.fixture = fixture;
15666
- const targetBase = parseFixtureField(data.target_base, `Journey "${journeyName}" in ${relPath}`, "target_base");
15667
- if (targetBase) journeyEntry.target_base = targetBase;
15668
- return journeyEntry;
15701
+ return out;
15669
15702
  }
15670
- function scanAgentYamlFiles(hubFolder) {
15671
- const agentsDir = path15.join(hubFolder, "agents");
15672
- if (!fs13.existsSync(agentsDir)) return [];
15673
- const yamlFiles = fs13.readdirSync(agentsDir).filter((f) => f.endsWith(".yaml")).sort();
15674
- if (yamlFiles.length === 0) return [];
15675
- const agents = [];
15676
- for (const file of yamlFiles) {
15677
- const filePath = path15.join(agentsDir, file);
15678
- const content = fs13.readFileSync(filePath, "utf-8");
15679
- let agent;
15680
- try {
15681
- agent = yaml5.load(content);
15682
- } catch (err) {
15683
- if (err instanceof yaml5.YAMLException) {
15684
- const userError = new Error(`Invalid YAML in agents/${file}: ${err.message}`);
15685
- userError.isExpected = true;
15686
- throw userError;
15703
+ function writeJourneyYamlFiles(hubFolder, journeys) {
15704
+ const journeysDir = path15.join(hubFolder, "journeys");
15705
+ if (journeys.length === 0) {
15706
+ if (fs13.existsSync(journeysDir)) {
15707
+ cleanJourneyOrphans(journeysDir, /* @__PURE__ */ new Set());
15708
+ try {
15709
+ if (fs13.readdirSync(journeysDir).length === 0) fs13.rmdirSync(journeysDir);
15710
+ } catch {
15687
15711
  }
15688
- throw err;
15689
15712
  }
15690
- if (!agent || typeof agent !== "object") continue;
15691
- if (!agent.name) {
15692
- console.warn(` Warning: skipping ${file} (missing 'name' field)`);
15693
- continue;
15713
+ return;
15714
+ }
15715
+ if (!fs13.existsSync(journeysDir)) {
15716
+ fs13.mkdirSync(journeysDir, { recursive: true });
15717
+ }
15718
+ const writtenFiles = /* @__PURE__ */ new Set();
15719
+ const usedSlugs = /* @__PURE__ */ new Set();
15720
+ for (const journeyEntry of journeys) {
15721
+ const slug = ensureUniqueSlug(slugify(journeyEntry.name), usedSlugs);
15722
+ const fileName = `${slug}.yaml`;
15723
+ const yamlContent = yaml5.dump(buildJourneyYamlObject(journeyEntry, slug), YAML_DUMP_OPTIONS);
15724
+ fs13.writeFileSync(path15.join(journeysDir, fileName), yamlContent, "utf-8");
15725
+ writtenFiles.add(fileName);
15726
+ }
15727
+ cleanJourneyOrphans(journeysDir, writtenFiles);
15728
+ }
15729
+ function cleanJourneyOrphans(journeysDir, writtenFiles) {
15730
+ const entries = fs13.readdirSync(journeysDir, { withFileTypes: true });
15731
+ for (const entry of entries) {
15732
+ if (entry.isFile() && entry.name.endsWith(".yaml") && !writtenFiles.has(entry.name)) {
15733
+ fs13.unlinkSync(path15.join(journeysDir, entry.name));
15694
15734
  }
15695
- agents.push(agent);
15696
15735
  }
15697
- return agents;
15698
15736
  }
15699
- function parseResources(hubFolder, configResources) {
15737
+ function writeResourceFiles(hubFolder, resources) {
15700
15738
  const resourcesDir = path15.join(hubFolder, "resources");
15701
- const results = [];
15702
- for (const res of configResources) {
15703
- const resource = {
15704
- name: res.name
15705
- };
15706
- if (res.id) resource.id = res.id;
15707
- if (res.type === "knowledge" || res.type === "skill") resource.type = res.type;
15708
- else if (res.type !== void 0) throw new Error(`Invalid resource type for "${resource.name}": expected knowledge or skill`);
15709
- if (res.description) resource.description = res.description;
15710
- if (res.enabled === false) resource.enabled = false;
15711
- if (res.user_browsable) resource.user_browsable = res.user_browsable;
15712
- if (res.skill_name) resource.skill_name = res.skill_name;
15739
+ const currentSlugs = /* @__PURE__ */ new Set();
15740
+ for (const resource of resources) {
15713
15741
  const resSlug = slugify(resource.name);
15742
+ currentSlugs.add(resSlug);
15714
15743
  const resDir = path15.join(resourcesDir, resSlug);
15715
- if (fs13.existsSync(resDir)) {
15716
- const files = scanResourceFiles(resDir, "");
15717
- if (files.length > 0) {
15718
- resource.files = files;
15744
+ writeResourceFileTree(resDir, resource.files || [], hubFolder);
15745
+ }
15746
+ if (fs13.existsSync(resourcesDir)) {
15747
+ const existingDirs = fs13.readdirSync(resourcesDir, { withFileTypes: true });
15748
+ for (const entry of existingDirs) {
15749
+ if (entry.isDirectory() && !currentSlugs.has(entry.name)) {
15750
+ fs13.rmSync(path15.join(resourcesDir, entry.name), { recursive: true, force: true });
15719
15751
  }
15720
15752
  }
15721
- results.push(resource);
15722
15753
  }
15723
- return results;
15724
15754
  }
15725
- var init_parser = __esm({
15726
- "src/lib/parser.ts"() {
15755
+ var YAML_DUMP_OPTIONS;
15756
+ var init_yaml_writer = __esm({
15757
+ "src/lib/yaml-writer.ts"() {
15727
15758
  "use strict";
15759
+ init_dist();
15728
15760
  init_utils();
15729
15761
  init_resource_files();
15730
15762
  init_eval_attachments();
15763
+ init_workspace_files();
15764
+ YAML_DUMP_OPTIONS = {
15765
+ lineWidth: -1,
15766
+ // don't wrap lines
15767
+ noRefs: true,
15768
+ // don't use YAML anchors
15769
+ quotingType: '"',
15770
+ forceQuotes: false,
15771
+ sortKeys: false
15772
+ };
15731
15773
  }
15732
15774
  });
15733
15775
 
15734
- // src/lib/diff-display.ts
15735
- function printDiff(summary) {
15736
- const lines = summary.split("\n");
15737
- for (const line of lines) {
15738
- if (line.startsWith("**") && line.endsWith("**")) {
15739
- console.log(`
15740
- \x1B[1m${line.replace(/\*\*/g, "")}\x1B[0m`);
15741
- } else if (line.trimStart().startsWith("+ ")) {
15742
- console.log(`\x1B[32m${line}\x1B[0m`);
15743
- } else if (line.trimStart().startsWith("- ")) {
15744
- console.log(`\x1B[31m${line}\x1B[0m`);
15745
- } else if (line.trimStart().startsWith("~ ")) {
15746
- console.log(`\x1B[33m${line}\x1B[0m`);
15747
- } else if (line.includes("(no changes)")) {
15748
- console.log(`\x1B[2m${line}\x1B[0m`);
15749
- } else {
15750
- console.log(line);
15751
- }
15776
+ // src/lib/hub-materializer.ts
15777
+ import * as path16 from "path";
15778
+ async function materializeHubFolder(hubFolder, payload, options = {}) {
15779
+ if (materialized.has(payload)) {
15780
+ throw new Error(
15781
+ "materializeHubFolder called twice on the same payload object \u2014 CLI bug. The first run rewrote the payload in place; fetch a fresh payload to write again."
15782
+ );
15752
15783
  }
15753
- console.log();
15784
+ materialized.add(payload);
15785
+ const attachmentDownloads = rewriteEvalAttachmentsToLocalPaths(payload);
15786
+ writeHubFolder(hubFolder, payload, options);
15787
+ await downloadBinaryResourceFiles(hubFolder, payload);
15788
+ const attachmentsWritten = await downloadEvalAttachments(hubFolder, attachmentDownloads);
15789
+ return { attachmentsWritten };
15754
15790
  }
15755
- var init_diff_display = __esm({
15756
- "src/lib/diff-display.ts"() {
15791
+ async function downloadBinaryResourceFiles(hubFolder, payload) {
15792
+ const resources = payload.resources || [];
15793
+ let downloadCount = 0;
15794
+ for (const resource of resources) {
15795
+ if (!resource.files) continue;
15796
+ const resDir = path16.join(hubFolder, "resources", slugify(resource.name));
15797
+ downloadCount += await downloadBinaryFiles(resDir, resource.files, hubFolder);
15798
+ }
15799
+ if (downloadCount > 0) {
15800
+ console.log(`Downloaded ${downloadCount} binary resource file(s).`);
15801
+ }
15802
+ }
15803
+ var materialized;
15804
+ var init_hub_materializer = __esm({
15805
+ "src/lib/hub-materializer.ts"() {
15757
15806
  "use strict";
15807
+ init_yaml_writer();
15808
+ init_eval_attachments();
15809
+ init_resource_files();
15810
+ init_utils();
15811
+ materialized = /* @__PURE__ */ new WeakSet();
15758
15812
  }
15759
15813
  });
15760
15814
 
@@ -15762,13 +15816,12 @@ var init_diff_display = __esm({
15762
15816
  var pull_exports = {};
15763
15817
  __export(pull_exports, {
15764
15818
  connectionsEqual: () => connectionsEqual,
15765
- downloadBinaryResourceFiles: () => downloadBinaryResourceFiles,
15766
15819
  pullCommand: () => pullCommand,
15767
15820
  resolveHubTarget: () => resolveHubTarget,
15768
15821
  writeProductionMirror: () => writeProductionMirror
15769
15822
  });
15770
15823
  import * as fs14 from "fs";
15771
- import * as path16 from "path";
15824
+ import * as path17 from "path";
15772
15825
  function parseArgs4(args2) {
15773
15826
  let autoConfirm = false;
15774
15827
  let hubSelector;
@@ -15787,7 +15840,7 @@ function resolveHubTarget(workspaceDir, selector) {
15787
15840
  const allHubs = scanWorkspaceHubs(workspaceDir);
15788
15841
  if (selector) {
15789
15842
  const match = allHubs.find(
15790
- (h) => h.hubId === selector || path16.basename(h.hubFolder) === selector
15843
+ (h) => h.hubId === selector || path17.basename(h.hubFolder) === selector
15791
15844
  );
15792
15845
  if (match) return { hubId: match.hubId, hubFolder: match.hubFolder };
15793
15846
  if (UUID_RE2.test(selector)) return { hubId: selector, hubFolder: null };
@@ -15804,7 +15857,7 @@ function resolveHubTarget(workspaceDir, selector) {
15804
15857
  }
15805
15858
  console.error(`Multiple hubs found in ${wsLabel}/. Pass --hub <uuid|folder-name> to choose, or run from inside a hub folder:`);
15806
15859
  for (const h of previewHubs) {
15807
- console.error(` ${path16.basename(h.hubFolder)} (${h.hubId})`);
15860
+ console.error(` ${path17.basename(h.hubFolder)} (${h.hubId})`);
15808
15861
  }
15809
15862
  process.exit(1);
15810
15863
  }
@@ -15831,7 +15884,6 @@ async function pullCommand(args2) {
15831
15884
  return;
15832
15885
  }
15833
15886
  assertHubMatchesBinding(hubId);
15834
- const attachmentDownloads = rewriteEvalAttachmentsToLocalPaths(payload);
15835
15887
  let hubFolder = existingFolder;
15836
15888
  let wroteFiles = false;
15837
15889
  if (!hubFolder) {
@@ -15843,31 +15895,20 @@ async function pullCommand(args2) {
15843
15895
  payload.preview_label,
15844
15896
  payload.branch_name
15845
15897
  );
15846
- fs14.mkdirSync(path16.dirname(hubFolder), { recursive: true });
15898
+ fs14.mkdirSync(path17.dirname(hubFolder), { recursive: true });
15847
15899
  console.log("Writing hub configuration...");
15848
- writeHubFolder(hubFolder, payload);
15849
- await downloadBinaryResourceFiles(hubFolder, payload);
15850
- await downloadEvalAttachments(hubFolder, attachmentDownloads);
15900
+ await materializeHubFolder(hubFolder, payload);
15851
15901
  const finalFolder = autoRenameHubFolder(hubFolder, payload.hub.name, payload.hub_environment, payload.hub_id, payload.preview_label, payload.branch_name);
15852
15902
  console.log(`Hub configuration written to ${finalFolder}`);
15853
15903
  wroteFiles = true;
15854
15904
  } else {
15855
- let cancelled = false;
15905
+ let action = "overwrite";
15856
15906
  try {
15857
15907
  const localPayload = parseHubFolder(hubFolder);
15858
15908
  const diffResult = await client.diff(hubId, prepareResourcePayload(localPayload, payload), "pull", organizationId);
15859
15909
  const connectionsChanged = !connectionsEqual(localPayload.connections, payload.connections);
15860
15910
  if (!diffResult.has_changes && !connectionsChanged) {
15861
- writeHubFolder(hubFolder, payload);
15862
- await downloadBinaryResourceFiles(hubFolder, payload);
15863
- await downloadEvalAttachments(hubFolder, attachmentDownloads);
15864
- const finalFolder = autoRenameHubFolder(hubFolder, payload.hub.name, payload.hub_environment, payload.hub_id, payload.preview_label, payload.branch_name);
15865
- wroteFiles = true;
15866
- if (finalFolder === hubFolder) {
15867
- console.log("Metadata synced \u2014 no config changes.");
15868
- } else {
15869
- console.log("Metadata synced; folder renamed.");
15870
- }
15911
+ action = "sync_metadata";
15871
15912
  } else {
15872
15913
  console.log("\nChanges to apply locally:");
15873
15914
  if (connectionsChanged) {
@@ -15878,28 +15919,27 @@ async function pullCommand(args2) {
15878
15919
  const ok = await confirm("Apply these changes?");
15879
15920
  if (!ok) {
15880
15921
  console.log("Cancelled.");
15881
- cancelled = true;
15922
+ return;
15882
15923
  }
15883
15924
  }
15884
- if (!cancelled) {
15885
- writeHubFolder(hubFolder, payload);
15886
- await downloadBinaryResourceFiles(hubFolder, payload);
15887
- await downloadEvalAttachments(hubFolder, attachmentDownloads);
15888
- const finalFolder = autoRenameHubFolder(hubFolder, payload.hub.name, payload.hub_environment, payload.hub_id, payload.preview_label, payload.branch_name);
15889
- console.log(`Hub configuration written to ${finalFolder}`);
15890
- wroteFiles = true;
15891
- }
15925
+ action = "apply";
15892
15926
  }
15893
15927
  } catch {
15894
- console.log("Writing hub configuration...");
15895
- writeHubFolder(hubFolder, payload);
15896
- await downloadBinaryResourceFiles(hubFolder, payload);
15897
- await downloadEvalAttachments(hubFolder, attachmentDownloads);
15898
- const finalFolder = autoRenameHubFolder(hubFolder, payload.hub.name, payload.hub_environment, payload.hub_id, payload.preview_label, payload.branch_name);
15928
+ action = "overwrite";
15929
+ }
15930
+ if (action === "overwrite") console.log("Writing hub configuration...");
15931
+ await materializeHubFolder(hubFolder, payload);
15932
+ const finalFolder = autoRenameHubFolder(hubFolder, payload.hub.name, payload.hub_environment, payload.hub_id, payload.preview_label, payload.branch_name);
15933
+ wroteFiles = true;
15934
+ if (action === "sync_metadata") {
15935
+ if (finalFolder === hubFolder) {
15936
+ console.log("Metadata synced \u2014 no config changes.");
15937
+ } else {
15938
+ console.log("Metadata synced; folder renamed.");
15939
+ }
15940
+ } else {
15899
15941
  console.log(`Hub configuration written to ${finalFolder}`);
15900
- wroteFiles = true;
15901
15942
  }
15902
- if (cancelled) return;
15903
15943
  }
15904
15944
  if (wroteFiles) autoBindIfUnbound(hubId);
15905
15945
  if (payload.production_hub_id) {
@@ -15908,11 +15948,8 @@ async function pullCommand(args2) {
15908
15948
  }
15909
15949
  async function writeProductionMirror(workspaceDir, prodPayload) {
15910
15950
  const folder = resolveHubFolder(workspaceDir, prodPayload.hub_id, prodPayload.hub.name, "production", null, null);
15911
- fs14.mkdirSync(path16.dirname(folder), { recursive: true });
15912
- const attachmentDownloads = rewriteEvalAttachmentsToLocalPaths(prodPayload);
15913
- writeHubFolder(folder, prodPayload, { seedAgentContext: false });
15914
- await downloadBinaryResourceFiles(folder, prodPayload);
15915
- await downloadEvalAttachments(folder, attachmentDownloads);
15951
+ fs14.mkdirSync(path17.dirname(folder), { recursive: true });
15952
+ await materializeHubFolder(folder, prodPayload, { seedAgentContext: false });
15916
15953
  const finalFolder = autoRenameHubFolder(folder, prodPayload.hub.name, "production", prodPayload.hub_id, null, null);
15917
15954
  prependMirrorMarker(finalFolder, prodPayload.hub_id);
15918
15955
  return finalFolder;
@@ -15927,7 +15964,7 @@ async function mirrorLinkedProduction(client, workspaceDir, productionHubId, org
15927
15964
  }
15928
15965
  }
15929
15966
  function prependMirrorMarker(hubFolder, productionHubId) {
15930
- const hubYaml = path16.join(hubFolder, "hub.yaml");
15967
+ const hubYaml = path17.join(hubFolder, "hub.yaml");
15931
15968
  try {
15932
15969
  const content = fs14.readFileSync(hubYaml, "utf-8");
15933
15970
  if (content.startsWith(MIRROR_MARKER_PREFIX)) return;
@@ -15937,18 +15974,6 @@ function prependMirrorMarker(hubFolder, productionHubId) {
15937
15974
  } catch {
15938
15975
  }
15939
15976
  }
15940
- async function downloadBinaryResourceFiles(hubFolder, payload) {
15941
- const resources = payload.resources || [];
15942
- let downloadCount = 0;
15943
- for (const resource of resources) {
15944
- if (!resource.files) continue;
15945
- const resDir = path16.join(hubFolder, "resources", slugify(resource.name));
15946
- downloadCount += await downloadBinaryFiles(resDir, resource.files, hubFolder);
15947
- }
15948
- if (downloadCount > 0) {
15949
- console.log(`Downloaded ${downloadCount} binary resource file(s).`);
15950
- }
15951
- }
15952
15977
  function connectionsEqual(a, b) {
15953
15978
  const listA = a || [];
15954
15979
  const listB = b || [];
@@ -15968,12 +15993,11 @@ var init_pull = __esm({
15968
15993
  "use strict";
15969
15994
  init_auth();
15970
15995
  init_api_client();
15971
- init_yaml_writer();
15972
15996
  init_parser();
15973
15997
  init_diff_display();
15974
15998
  init_utils();
15975
15999
  init_resource_files();
15976
- init_eval_attachments();
16000
+ init_hub_materializer();
15977
16001
  init_workspace();
15978
16002
  init_layout();
15979
16003
  init_workspace_files();
@@ -15994,10 +16018,11 @@ __export(push_exports, {
15994
16018
  pushCommand: () => pushCommand,
15995
16019
  reportUnresolvedPushTarget: () => reportUnresolvedPushTarget,
15996
16020
  selectExistingHub: () => selectExistingHub,
15997
- shouldWarnIgnoredPreviewLabel: () => shouldWarnIgnoredPreviewLabel
16021
+ shouldWarnIgnoredPreviewLabel: () => shouldWarnIgnoredPreviewLabel,
16022
+ syncAfterPush: () => syncAfterPush
15998
16023
  });
15999
16024
  import * as fs15 from "fs";
16000
- import * as path17 from "path";
16025
+ import * as path18 from "path";
16001
16026
  import * as yaml6 from "js-yaml";
16002
16027
  function parseArgs5(args2) {
16003
16028
  let autoConfirm = false;
@@ -16090,13 +16115,13 @@ function printErrors(errors) {
16090
16115
  }
16091
16116
  }
16092
16117
  async function autoRenameAgentFiles(client, hubId, hubFolder, organizationId, remoteConfig) {
16093
- const agentsDir = path17.join(hubFolder, "agents");
16118
+ const agentsDir = path18.join(hubFolder, "agents");
16094
16119
  let agentsWithIds = [];
16095
16120
  if (fs15.existsSync(agentsDir)) {
16096
16121
  const yamlFiles = fs15.readdirSync(agentsDir).filter((f) => f.endsWith(".yaml"));
16097
16122
  for (const file of yamlFiles) {
16098
16123
  try {
16099
- const content = fs15.readFileSync(path17.join(agentsDir, file), "utf-8");
16124
+ const content = fs15.readFileSync(path18.join(agentsDir, file), "utf-8");
16100
16125
  const agent = yaml6.load(content);
16101
16126
  if (agent?.id && agent.name) {
16102
16127
  agentsWithIds.push({ id: agent.id, name: agent.name });
@@ -16145,12 +16170,12 @@ async function autoRenameAgentFiles(client, hubId, hubFolder, organizationId, re
16145
16170
  for (const file of fs15.readdirSync(agentsDir)) {
16146
16171
  if (file.startsWith("__rename_temp_") && (file.endsWith(".md") || file.endsWith(".yaml"))) {
16147
16172
  console.warn(` Warning: removing orphaned temp file agents/${file}`);
16148
- fs15.unlinkSync(path17.join(agentsDir, file));
16173
+ fs15.unlinkSync(path18.join(agentsDir, file));
16149
16174
  }
16150
16175
  }
16151
16176
  const renameFileIfExists = (dir, oldName, newName) => {
16152
- const oldPath = path17.join(dir, oldName);
16153
- const newPath = path17.join(dir, newName);
16177
+ const oldPath = path18.join(dir, oldName);
16178
+ const newPath = path18.join(dir, newName);
16154
16179
  if (!fs15.existsSync(oldPath)) return false;
16155
16180
  fs15.renameSync(oldPath, newPath);
16156
16181
  return true;
@@ -16185,9 +16210,9 @@ async function autoRenameAgentFiles(client, hubId, hubFolder, organizationId, re
16185
16210
  }
16186
16211
  } else {
16187
16212
  for (const { oldSlug, newSlug } of renames) {
16188
- const hasOldFile = extensions.some((ext) => fs15.existsSync(path17.join(agentsDir, `${oldSlug}${ext}`)));
16213
+ const hasOldFile = extensions.some((ext) => fs15.existsSync(path18.join(agentsDir, `${oldSlug}${ext}`)));
16189
16214
  if (!hasOldFile) continue;
16190
- const hasNewFile = extensions.some((ext) => fs15.existsSync(path17.join(agentsDir, `${newSlug}${ext}`)));
16215
+ const hasNewFile = extensions.some((ext) => fs15.existsSync(path18.join(agentsDir, `${newSlug}${ext}`)));
16191
16216
  if (hasNewFile) {
16192
16217
  console.warn(` Warning: skipping rename agents/${oldSlug}.* \u2192 agents/${newSlug}.* (target already exists)`);
16193
16218
  continue;
@@ -16214,15 +16239,14 @@ async function autoRenameAgentFiles(client, hubId, hubFolder, organizationId, re
16214
16239
  );
16215
16240
  if (updatedYaml !== mainYamlContent) {
16216
16241
  fs15.writeFileSync(mainYamlPath, updatedYaml, "utf-8");
16217
- console.log(` Updated instructions paths in ${path17.basename(mainYamlPath)}`);
16242
+ console.log(` Updated instructions paths in ${path18.basename(mainYamlPath)}`);
16218
16243
  }
16219
16244
  }
16220
16245
  }
16221
16246
  async function syncAfterPush(client, hubId, hubFolder, organizationId) {
16222
16247
  try {
16223
16248
  const remoteConfig = await client.pull(hubId, organizationId);
16224
- writeHubFolder(hubFolder, remoteConfig);
16225
- await downloadBinaryResourceFiles(hubFolder, remoteConfig);
16249
+ await materializeHubFolder(hubFolder, remoteConfig);
16226
16250
  console.log("Local files synced with server.");
16227
16251
  const finalFolder = autoRenameHubFolder(hubFolder, remoteConfig.hub.name, remoteConfig.hub_environment, hubId, remoteConfig.preview_label, remoteConfig.branch_name);
16228
16252
  if (finalFolder !== hubFolder) {
@@ -16283,7 +16307,7 @@ async function pushSingleHub(client, hubId, hubFolder, autoConfirm, organization
16283
16307
  function selectExistingHub(workspaceDir, allHubs, selector, wsLabel) {
16284
16308
  if (selector) {
16285
16309
  const match = allHubs.find(
16286
- (h) => h.hubId === selector || path17.basename(h.hubFolder) === selector
16310
+ (h) => h.hubId === selector || path18.basename(h.hubFolder) === selector
16287
16311
  );
16288
16312
  if (!match) {
16289
16313
  console.error(`No hub matching --hub ${selector} found in ${wsLabel}/.`);
@@ -16351,7 +16375,7 @@ function reportUnresolvedPushTarget(reason, existingHubs, newHubs, selector, wsL
16351
16375
  if (existingHubs.length > 0) {
16352
16376
  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\`:`);
16353
16377
  for (const h of existingHubs) {
16354
- console.error(` ${path17.basename(h.hubFolder)} (${h.hubId}, production mirror)`);
16378
+ console.error(` ${path18.basename(h.hubFolder)} (${h.hubId}, production mirror)`);
16355
16379
  }
16356
16380
  } else {
16357
16381
  console.error(`No hub folders found. Create hub files in ${wsLabel}/<hub>/hub.yaml with \`hub: { name: ... }\` and run \`wayai push\` (or \`wayai create\`).`);
@@ -16364,10 +16388,10 @@ function reportUnresolvedPushTarget(reason, existingHubs, newHubs, selector, wsL
16364
16388
  console.error(`Multiple hub folders found in ${wsLabel}/. Pass --hub <uuid|folder-name> to choose, or run from inside a hub folder:`);
16365
16389
  }
16366
16390
  for (const h of existingHubs) {
16367
- console.error(` ${path17.basename(h.hubFolder)} (${h.hubId})`);
16391
+ console.error(` ${path18.basename(h.hubFolder)} (${h.hubId})`);
16368
16392
  }
16369
16393
  for (const h of newHubs) {
16370
- console.error(` ${path17.basename(h.hubFolder)} (new \u2014 "${h.hubName}")`);
16394
+ console.error(` ${path18.basename(h.hubFolder)} (new \u2014 "${h.hubName}")`);
16371
16395
  }
16372
16396
  process.exit(1);
16373
16397
  }
@@ -16405,8 +16429,8 @@ async function pushCommand(args2) {
16405
16429
  if (label) {
16406
16430
  console.warn("Note: --label is only used when creating a new hub. To change an existing hub's label, use `wayai relabel`.");
16407
16431
  }
16408
- console.log(`Target hub: ${path17.basename(existing.hubFolder)} (${existing.hubId})`);
16409
- assertHubMatchesBinding(existing.hubId, path17.basename(existing.hubFolder));
16432
+ console.log(`Target hub: ${path18.basename(existing.hubFolder)} (${existing.hubId})`);
16433
+ assertHubMatchesBinding(existing.hubId, path18.basename(existing.hubFolder));
16410
16434
  await pushSingleHub(client, existing.hubId, existing.hubFolder, autoConfirm, organizationId);
16411
16435
  return;
16412
16436
  }
@@ -16419,8 +16443,7 @@ var init_push = __esm({
16419
16443
  init_auth();
16420
16444
  init_api_client();
16421
16445
  init_parser();
16422
- init_yaml_writer();
16423
- init_pull();
16446
+ init_hub_materializer();
16424
16447
  init_resource_files();
16425
16448
  init_diff_display();
16426
16449
  init_utils();
@@ -16437,7 +16460,7 @@ var create_exports = {};
16437
16460
  __export(create_exports, {
16438
16461
  createCommand: () => createCommand
16439
16462
  });
16440
- import * as path18 from "path";
16463
+ import * as path19 from "path";
16441
16464
  import * as fs16 from "fs";
16442
16465
  function parseArgs6(args2) {
16443
16466
  let autoConfirm = false;
@@ -16474,7 +16497,7 @@ async function createCommand(args2) {
16474
16497
  const resolution = resolveNewHubForCreate(workspaceDir, existingHubs, newHubs, folderSelector);
16475
16498
  if (!resolution.ok) {
16476
16499
  if (resolution.reason === "exists") {
16477
- const folder = path18.basename(resolution.existing.hubFolder);
16500
+ const folder = path19.basename(resolution.existing.hubFolder);
16478
16501
  console.error(`Hub "${folder}" already exists (${resolution.existing.hubId}). Use \`wayai push --hub ${folder}\` to update it.`);
16479
16502
  process.exit(1);
16480
16503
  }
@@ -16488,7 +16511,7 @@ async function createCommand(args2) {
16488
16511
  }
16489
16512
  console.error(`Multiple new hub folders found in ${wsLabel}/. Pass the folder name (\`wayai create <folder>\`) or run from inside one:`);
16490
16513
  for (const h of newHubs) {
16491
- console.error(` ${path18.basename(h.hubFolder)} (new \u2014 "${h.hubName}")`);
16514
+ console.error(` ${path19.basename(h.hubFolder)} (new \u2014 "${h.hubName}")`);
16492
16515
  }
16493
16516
  process.exit(1);
16494
16517
  }
@@ -16600,7 +16623,7 @@ __export(replicate_exports, {
16600
16623
  replicateCommand: () => replicateCommand
16601
16624
  });
16602
16625
  import * as fs17 from "fs";
16603
- import * as path19 from "path";
16626
+ import * as path20 from "path";
16604
16627
  function parseArgs8(args2) {
16605
16628
  let label;
16606
16629
  let hubSelector;
@@ -16646,9 +16669,8 @@ async function replicateCommand(args2) {
16646
16669
  payload.preview_label,
16647
16670
  payload.branch_name
16648
16671
  );
16649
- fs17.mkdirSync(path19.dirname(hubFolder), { recursive: true });
16650
- writeHubFolder(hubFolder, payload);
16651
- await downloadBinaryResourceFiles(hubFolder, payload);
16672
+ fs17.mkdirSync(path20.dirname(hubFolder), { recursive: true });
16673
+ await materializeHubFolder(hubFolder, payload);
16652
16674
  hubFolder = autoRenameHubFolder(
16653
16675
  hubFolder,
16654
16676
  payload.hub.name,
@@ -16658,15 +16680,15 @@ async function replicateCommand(args2) {
16658
16680
  payload.branch_name
16659
16681
  );
16660
16682
  autoBindIfUnbound(previewHubId);
16661
- console.log(`Preview written to ${path19.basename(hubFolder)}`);
16662
- console.log(` Switch to it with: wayai use ${path19.basename(hubFolder)}`);
16683
+ console.log(`Preview written to ${path20.basename(hubFolder)}`);
16684
+ console.log(` Switch to it with: wayai use ${path20.basename(hubFolder)}`);
16663
16685
  }
16664
16686
  var init_replicate = __esm({
16665
16687
  "src/commands/replicate.ts"() {
16666
16688
  "use strict";
16667
16689
  init_auth();
16668
16690
  init_api_client();
16669
- init_yaml_writer();
16691
+ init_hub_materializer();
16670
16692
  init_pull();
16671
16693
  init_workspace();
16672
16694
  init_layout();
@@ -16681,7 +16703,7 @@ var relabel_exports = {};
16681
16703
  __export(relabel_exports, {
16682
16704
  relabelCommand: () => relabelCommand
16683
16705
  });
16684
- import * as path20 from "path";
16706
+ import * as path21 from "path";
16685
16707
  function parseArgs9(args2) {
16686
16708
  let label;
16687
16709
  let clear = false;
@@ -16725,7 +16747,7 @@ async function relabelCommand(args2) {
16725
16747
  } else {
16726
16748
  console.error(`Multiple hubs found in ${wsLabel}/. Pass --hub <uuid|folder-name> to choose, or run from inside a hub folder:`);
16727
16749
  for (const h of previewHubs) {
16728
- console.error(` ${path20.basename(h.hubFolder)} (${h.hubId})`);
16750
+ console.error(` ${path21.basename(h.hubFolder)} (${h.hubId})`);
16729
16751
  }
16730
16752
  }
16731
16753
  process.exit(1);
@@ -16734,7 +16756,7 @@ async function relabelCommand(args2) {
16734
16756
  console.log("This is a read-only production mirror \u2014 production hubs have no preview label. Relabel the linked preview hub instead.");
16735
16757
  return;
16736
16758
  }
16737
- assertHubMatchesBinding(target.hubId, path20.basename(target.hubFolder));
16759
+ assertHubMatchesBinding(target.hubId, path21.basename(target.hubFolder));
16738
16760
  console.log(normalizedLabel ? `Setting preview label to "${normalizedLabel}"...` : "Clearing preview label...");
16739
16761
  const { data } = await client.relabelPreview(target.hubId, normalizedLabel);
16740
16762
  const row = data[0];
@@ -16750,7 +16772,7 @@ async function relabelCommand(args2) {
16750
16772
  );
16751
16773
  console.log(serverLabel ? `Preview label set to "${serverLabel}".` : "Preview label cleared.");
16752
16774
  if (finalFolder !== target.hubFolder) {
16753
- console.log(`Hub folder is now ${path20.basename(finalFolder)}`);
16775
+ console.log(`Hub folder is now ${path21.basename(finalFolder)}`);
16754
16776
  }
16755
16777
  }
16756
16778
  var init_relabel = __esm({
@@ -16776,7 +16798,7 @@ __export(publish_exports, {
16776
16798
  renderHubDiff: () => renderHubDiff,
16777
16799
  runPublish: () => runPublish
16778
16800
  });
16779
- import * as path21 from "path";
16801
+ import * as path22 from "path";
16780
16802
  function parseArgs10(args2) {
16781
16803
  let autoConfirm = false;
16782
16804
  let hubSelector;
@@ -16890,7 +16912,7 @@ async function publishCommand(args2) {
16890
16912
  return;
16891
16913
  }
16892
16914
  }
16893
- assertHubMatchesBinding(hubId, hubFolder ? path21.basename(hubFolder) : void 0);
16915
+ assertHubMatchesBinding(hubId, hubFolder ? path22.basename(hubFolder) : void 0);
16894
16916
  await runPublish(client, { hubId, localConfig, organizationId, autoConfirm });
16895
16917
  }
16896
16918
  var STATUS_STYLE;
@@ -16923,7 +16945,7 @@ var use_exports = {};
16923
16945
  __export(use_exports, {
16924
16946
  useCommand: () => useCommand
16925
16947
  });
16926
- import * as path22 from "path";
16948
+ import * as path23 from "path";
16927
16949
  async function useCommand(args2) {
16928
16950
  const target = args2[0];
16929
16951
  if (!target || target.startsWith("-")) {
@@ -16945,7 +16967,7 @@ async function useCommand(args2) {
16945
16967
  const match = findHubByFolderName(workspaceDir, target);
16946
16968
  if (!match) {
16947
16969
  const newHubs = scanNewHubs(workspaceDir);
16948
- const pending = newHubs.find((h) => path22.basename(h.hubFolder) === target);
16970
+ const pending = newHubs.find((h) => path23.basename(h.hubFolder) === target);
16949
16971
  if (pending) {
16950
16972
  console.error(
16951
16973
  `"${target}" is a new hub that hasn't been created on the platform yet, so it has no id to bind to.
@@ -16956,10 +16978,10 @@ Create it first \u2014 the worktree binds automatically afterward:
16956
16978
  }
16957
16979
  console.error(`No hub matching "${target}" found in ${wsLabel}/. Pass a UUID or a folder name from:`);
16958
16980
  for (const h of scanWorkspaceHubs(workspaceDir)) {
16959
- console.error(` ${path22.basename(h.hubFolder)} (${h.hubId})`);
16981
+ console.error(` ${path23.basename(h.hubFolder)} (${h.hubId})`);
16960
16982
  }
16961
16983
  for (const h of newHubs) {
16962
- console.error(` ${path22.basename(h.hubFolder)} (new \u2014 "${h.hubName}", run \`wayai create ${path22.basename(h.hubFolder)}\`)`);
16984
+ console.error(` ${path23.basename(h.hubFolder)} (new \u2014 "${h.hubName}", run \`wayai create ${path23.basename(h.hubFolder)}\`)`);
16963
16985
  }
16964
16986
  process.exit(1);
16965
16987
  }
@@ -16999,10 +17021,10 @@ __export(migrate_exports, {
16999
17021
  });
17000
17022
  import { execFileSync as execFileSync3 } from "child_process";
17001
17023
  import * as fs18 from "fs";
17002
- import * as path23 from "path";
17024
+ import * as path24 from "path";
17003
17025
  function isTracked(gitRoot, p) {
17004
17026
  try {
17005
- execFileSync3("git", ["ls-files", "--error-unmatch", "--", path23.relative(gitRoot, p)], {
17027
+ execFileSync3("git", ["ls-files", "--error-unmatch", "--", path24.relative(gitRoot, p)], {
17006
17028
  cwd: gitRoot,
17007
17029
  stdio: ["pipe", "pipe", "pipe"]
17008
17030
  });
@@ -17012,10 +17034,10 @@ function isTracked(gitRoot, p) {
17012
17034
  }
17013
17035
  }
17014
17036
  function moveDir(gitRoot, from, to) {
17015
- fs18.mkdirSync(path23.dirname(to), { recursive: true });
17037
+ fs18.mkdirSync(path24.dirname(to), { recursive: true });
17016
17038
  if (isTracked(gitRoot, from)) {
17017
17039
  try {
17018
- execFileSync3("git", ["mv", path23.relative(gitRoot, from), path23.relative(gitRoot, to)], {
17040
+ execFileSync3("git", ["mv", path24.relative(gitRoot, from), path24.relative(gitRoot, to)], {
17019
17041
  cwd: gitRoot,
17020
17042
  stdio: ["pipe", "pipe", "pipe"]
17021
17043
  });
@@ -17032,12 +17054,12 @@ async function migrateCommand(_args) {
17032
17054
  console.error("Not inside a git repository.");
17033
17055
  process.exit(1);
17034
17056
  }
17035
- const rel = (p) => path23.relative(gitRoot, p);
17036
- const newWs = path23.join(gitRoot, WAYAI_LAYOUT.wsDir);
17037
- const legacyWs = path23.join(gitRoot, WAYAI_LAYOUT.legacy.wsDir);
17038
- const legacyOrg = path23.join(gitRoot, WAYAI_LAYOUT.legacy.orgAtRoot);
17039
- const newHubs = path23.join(newWs, WAYAI_LAYOUT.hubsSubdir);
17040
- const newOrg = path23.join(newWs, WAYAI_LAYOUT.orgSubdir);
17057
+ const rel = (p) => path24.relative(gitRoot, p);
17058
+ const newWs = path24.join(gitRoot, WAYAI_LAYOUT.wsDir);
17059
+ const legacyWs = path24.join(gitRoot, WAYAI_LAYOUT.legacy.wsDir);
17060
+ const legacyOrg = path24.join(gitRoot, WAYAI_LAYOUT.legacy.orgAtRoot);
17061
+ const newHubs = path24.join(newWs, WAYAI_LAYOUT.hubsSubdir);
17062
+ const newOrg = path24.join(newWs, WAYAI_LAYOUT.orgSubdir);
17041
17063
  const hasLegacyWs = isDirectory(legacyWs);
17042
17064
  const hasLegacyOrg = isDirectory(legacyOrg);
17043
17065
  if (!hasLegacyWs && !hasLegacyOrg) {
@@ -17112,7 +17134,7 @@ __export(send_message_exports, {
17112
17134
  sendMessageCommand: () => sendMessageCommand
17113
17135
  });
17114
17136
  import * as fs19 from "fs";
17115
- import * as path24 from "path";
17137
+ import * as path25 from "path";
17116
17138
  function statAttachment(filePath) {
17117
17139
  let stat;
17118
17140
  try {
@@ -17128,8 +17150,8 @@ function statAttachment(filePath) {
17128
17150
  return { filePath, size: stat.size };
17129
17151
  }
17130
17152
  function readAttachment(filePath, size) {
17131
- const fileName = path24.basename(filePath);
17132
- const ext = path24.extname(fileName).replace(/^\./, "");
17153
+ const fileName = path25.basename(filePath);
17154
+ const ext = path25.extname(fileName).replace(/^\./, "");
17133
17155
  return {
17134
17156
  file_name: fileName,
17135
17157
  file_binary: fs19.readFileSync(filePath).toString("base64"),
@@ -17179,7 +17201,7 @@ async function sendMessageCommand(args2) {
17179
17201
  }
17180
17202
  const stats = filePaths.map(statAttachment);
17181
17203
  const filesTotal = stats.reduce((sum, s) => sum + base64Length(s.size), 0);
17182
- const fileDetail = () => stats.map((s) => `${path24.basename(s.filePath)} ${asMb(s.size)} MB`).join(", ");
17204
+ const fileDetail = () => stats.map((s) => `${path25.basename(s.filePath)} ${asMb(s.size)} MB`).join(", ");
17183
17205
  if (filesTotal > MAX_MESSAGE_BODY_BYTES2) {
17184
17206
  console.error(
17185
17207
  `Error: ${fileDetail()} encode to ~${asMb(filesTotal)} MB, over the ${asMb(MAX_MESSAGE_BODY_BYTES2)} MB per-request limit.`
@@ -18640,7 +18662,7 @@ __export(eval_capture_exports, {
18640
18662
  evalCaptureCommand: () => evalCaptureCommand
18641
18663
  });
18642
18664
  import * as fs20 from "fs";
18643
- import * as path25 from "path";
18665
+ import * as path26 from "path";
18644
18666
  import * as yaml7 from "js-yaml";
18645
18667
  function isValidSetName(name) {
18646
18668
  if (name.length === 0 || name === "." || name === "..") return false;
@@ -18706,15 +18728,15 @@ async function evalCaptureCommand(args2) {
18706
18728
  const setFolderName = targetSetName;
18707
18729
  const scenarioName = parsed.evalName ?? `Capture ${parsed.conversationId.slice(0, 8)}`;
18708
18730
  const slug = slugify(scenarioName);
18709
- const evalsDir = path25.join(hubFolder, "evals");
18710
- const targetDir = path25.join(evalsDir, setFolderName);
18711
- const targetPath = path25.join(targetDir, `${slug}.yaml`);
18712
- if (!targetPath.startsWith(evalsDir + path25.sep)) {
18713
- console.error(`Resolved path "${path25.relative(hubFolder, targetPath)}" escapes evals/. Aborting.`);
18731
+ const evalsDir = path26.join(hubFolder, "evals");
18732
+ const targetDir = path26.join(evalsDir, setFolderName);
18733
+ const targetPath = path26.join(targetDir, `${slug}.yaml`);
18734
+ if (!targetPath.startsWith(evalsDir + path26.sep)) {
18735
+ console.error(`Resolved path "${path26.relative(hubFolder, targetPath)}" escapes evals/. Aborting.`);
18714
18736
  process.exit(1);
18715
18737
  }
18716
18738
  if (fs20.existsSync(targetPath)) {
18717
- console.error(`File already exists: ${path25.relative(hubFolder, targetPath)}. Use --name to choose a different name.`);
18739
+ console.error(`File already exists: ${path26.relative(hubFolder, targetPath)}. Use --name to choose a different name.`);
18718
18740
  process.exit(1);
18719
18741
  }
18720
18742
  console.log("Resolving scenario set...");
@@ -18748,7 +18770,7 @@ async function evalCaptureCommand(args2) {
18748
18770
  const yamlObj = buildEvalYamlObject(evalEntry, slug);
18749
18771
  fs20.mkdirSync(targetDir, { recursive: true });
18750
18772
  fs20.writeFileSync(targetPath, yaml7.dump(yamlObj, YAML_DUMP_OPTIONS), "utf-8");
18751
- const relPath = path25.relative(process.cwd(), targetPath);
18773
+ const relPath = path26.relative(process.cwd(), targetPath);
18752
18774
  console.log(`
18753
18775
  Wrote ${relPath}`);
18754
18776
  console.log("Run `wayai pull` to refresh the agent display name, then commit. The scenario is already on the platform.");
@@ -19976,22 +19998,22 @@ var init_set_connection_credential = __esm({
19976
19998
 
19977
19999
  // src/lib/org-workspace.ts
19978
20000
  import * as fs21 from "fs";
19979
- import * as path26 from "path";
20001
+ import * as path27 from "path";
19980
20002
  import * as yaml8 from "js-yaml";
19981
20003
  function getOrgDir(gitRoot) {
19982
20004
  return resolveLayout(gitRoot).orgDir;
19983
20005
  }
19984
20006
  function orgManifestExists(orgDir) {
19985
- return fs21.existsSync(path26.join(orgDir, ORG_MANIFEST_NAME));
20007
+ return fs21.existsSync(path27.join(orgDir, ORG_MANIFEST_NAME));
19986
20008
  }
19987
20009
  function parseOrgResources(orgDir) {
19988
- const manifestPath = path26.join(orgDir, ORG_MANIFEST_NAME);
20010
+ const manifestPath = path27.join(orgDir, ORG_MANIFEST_NAME);
19989
20011
  let manifest = {};
19990
20012
  if (fs21.existsSync(manifestPath)) {
19991
20013
  manifest = yaml8.load(fs21.readFileSync(manifestPath, "utf-8")) ?? {};
19992
20014
  }
19993
20015
  const rawResources = Array.isArray(manifest.resources) ? manifest.resources : [];
19994
- const resourcesDir = path26.join(orgDir, "resources");
20016
+ const resourcesDir = path27.join(orgDir, "resources");
19995
20017
  const resources = rawResources.map((res) => {
19996
20018
  const resource = { name: res.name };
19997
20019
  if (res.id) resource.id = res.id;
@@ -20003,7 +20025,7 @@ function parseOrgResources(orgDir) {
20003
20025
  if (res.environment) resource.environment = res.environment;
20004
20026
  if (Array.isArray(res.tags)) resource.tags = res.tags;
20005
20027
  if (Array.isArray(res.folders)) resource.folders = res.folders;
20006
- const resDir = path26.join(resourcesDir, slugify(resource.name));
20028
+ const resDir = path27.join(resourcesDir, slugify(resource.name));
20007
20029
  if (fs21.existsSync(resDir)) {
20008
20030
  const files = scanResourceFiles(resDir, "");
20009
20031
  if (files.length > 0) resource.files = files;
@@ -20020,21 +20042,21 @@ function writeOrgResources(orgDir, payload) {
20020
20042
  return rest;
20021
20043
  });
20022
20044
  fs21.writeFileSync(
20023
- path26.join(orgDir, ORG_MANIFEST_NAME),
20045
+ path27.join(orgDir, ORG_MANIFEST_NAME),
20024
20046
  yaml8.dump({ version: 1, resources: manifestResources }, YAML_DUMP_OPTIONS),
20025
20047
  "utf-8"
20026
20048
  );
20027
- const resourcesDir = path26.join(orgDir, "resources");
20049
+ const resourcesDir = path27.join(orgDir, "resources");
20028
20050
  const currentSlugs = /* @__PURE__ */ new Set();
20029
20051
  for (const resource of resources) {
20030
20052
  const resSlug = slugify(resource.name);
20031
20053
  currentSlugs.add(resSlug);
20032
- writeResourceFileTree(path26.join(resourcesDir, resSlug), resource.files || [], orgDir);
20054
+ writeResourceFileTree(path27.join(resourcesDir, resSlug), resource.files || [], orgDir);
20033
20055
  }
20034
20056
  if (fs21.existsSync(resourcesDir)) {
20035
20057
  for (const entry of fs21.readdirSync(resourcesDir, { withFileTypes: true })) {
20036
20058
  if (entry.isDirectory() && !currentSlugs.has(entry.name)) {
20037
- fs21.rmSync(path26.join(resourcesDir, entry.name), { recursive: true, force: true });
20059
+ fs21.rmSync(path27.join(resourcesDir, entry.name), { recursive: true, force: true });
20038
20060
  }
20039
20061
  }
20040
20062
  }
@@ -20043,7 +20065,7 @@ async function downloadOrgBinaryFiles(orgDir, payload) {
20043
20065
  let count = 0;
20044
20066
  for (const resource of payload.resources ?? []) {
20045
20067
  if (!resource.files) continue;
20046
- const resDir = path26.join(orgDir, "resources", slugify(resource.name));
20068
+ const resDir = path27.join(orgDir, "resources", slugify(resource.name));
20047
20069
  count += await downloadBinaryFiles(resDir, resource.files, orgDir);
20048
20070
  }
20049
20071
  if (count > 0) console.log(`Downloaded ${count} binary resource file(s).`);
@@ -20300,13 +20322,13 @@ var init_list = __esm({
20300
20322
 
20301
20323
  // src/lib/template-files.ts
20302
20324
  import * as fs22 from "fs";
20303
- import * as path27 from "path";
20325
+ import * as path28 from "path";
20304
20326
  function readDirToFileMap(rootDir) {
20305
20327
  const out = {};
20306
20328
  function walk(dir, prefix) {
20307
20329
  const entries = fs22.readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
20308
20330
  for (const entry of entries) {
20309
- const abs = path27.join(dir, entry.name);
20331
+ const abs = path28.join(dir, entry.name);
20310
20332
  const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
20311
20333
  if (entry.isDirectory()) walk(abs, rel);
20312
20334
  else if (entry.isFile()) out[rel] = fs22.readFileSync(abs, "utf-8");
@@ -20321,7 +20343,7 @@ function detectCollisions(targetDir, files) {
20321
20343
  if (!isSafeTemplatePath2(rel)) {
20322
20344
  throw new Error(`Refusing to inspect unsafe template path: ${rel}`);
20323
20345
  }
20324
- if (fs22.existsSync(path27.join(targetDir, rel))) collisions.push(rel);
20346
+ if (fs22.existsSync(path28.join(targetDir, rel))) collisions.push(rel);
20325
20347
  }
20326
20348
  return collisions.sort((a, b) => a.localeCompare(b));
20327
20349
  }
@@ -20331,8 +20353,8 @@ function writeFileMap(targetDir, files) {
20331
20353
  if (!isSafeTemplatePath2(rel)) {
20332
20354
  throw new Error(`Refusing to write unsafe template path: ${rel}`);
20333
20355
  }
20334
- const abs = path27.join(targetDir, rel);
20335
- fs22.mkdirSync(path27.dirname(abs), { recursive: true });
20356
+ const abs = path28.join(targetDir, rel);
20357
+ fs22.mkdirSync(path28.dirname(abs), { recursive: true });
20336
20358
  fs22.writeFileSync(abs, body, "utf-8");
20337
20359
  written.push(rel);
20338
20360
  }
@@ -20343,7 +20365,7 @@ function listDirRelFiles(dir) {
20343
20365
  function walk(d, prefix) {
20344
20366
  for (const entry of fs22.readdirSync(d, { withFileTypes: true })) {
20345
20367
  const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
20346
- if (entry.isDirectory()) walk(path27.join(d, entry.name), rel);
20368
+ if (entry.isDirectory()) walk(path28.join(d, entry.name), rel);
20347
20369
  else if (entry.isFile()) out.push(rel);
20348
20370
  }
20349
20371
  }
@@ -20370,7 +20392,7 @@ var templates_exports = {};
20370
20392
  __export(templates_exports, {
20371
20393
  templateCommand: () => templateCommand
20372
20394
  });
20373
- import * as path28 from "path";
20395
+ import * as path29 from "path";
20374
20396
  async function templateCommand(args2) {
20375
20397
  const [sub, ...rest] = args2;
20376
20398
  switch (sub) {
@@ -20486,8 +20508,8 @@ async function runTemplatePull(rest) {
20486
20508
  console.log(`Template "${slug}" has no WayAI slice${hint}`);
20487
20509
  return;
20488
20510
  }
20489
- const targetDir = path28.join(resolveLayout(gitRoot).hubsDir, slug);
20490
- const relTarget = path28.relative(gitRoot, targetDir);
20511
+ const targetDir = path29.join(resolveLayout(gitRoot).hubsDir, slug);
20512
+ const relTarget = path29.relative(gitRoot, targetDir);
20491
20513
  const collisions = detectCollisions(targetDir, detail.files);
20492
20514
  if (dryRun) {
20493
20515
  const planned = Object.keys(detail.files).sort((a, b) => a.localeCompare(b));
@@ -20678,7 +20700,7 @@ __export(admin_exports, {
20678
20700
  adminCommand: () => adminCommand
20679
20701
  });
20680
20702
  import * as fs23 from "fs";
20681
- import * as path29 from "path";
20703
+ import * as path30 from "path";
20682
20704
  import * as yaml10 from "js-yaml";
20683
20705
  async function adminCommand(args2) {
20684
20706
  const [group, ...afterGroup] = args2;
@@ -21132,10 +21154,10 @@ function loadTemplateManifest(slug) {
21132
21154
  console.error("Not in a git repository. Run from the tutorials/ authoring repo.");
21133
21155
  process.exit(1);
21134
21156
  }
21135
- const templateDir = path29.join(resolveTemplatesDir(gitRoot), slug);
21136
- const manifestPath = path29.join(templateDir, "template.yaml");
21157
+ const templateDir = path30.join(resolveTemplatesDir(gitRoot), slug);
21158
+ const manifestPath = path30.join(templateDir, "template.yaml");
21137
21159
  if (!fs23.existsSync(manifestPath)) {
21138
- console.error(`template.yaml not found at ${path29.relative(gitRoot, manifestPath)}`);
21160
+ console.error(`template.yaml not found at ${path30.relative(gitRoot, manifestPath)}`);
21139
21161
  process.exit(1);
21140
21162
  }
21141
21163
  let manifest;
@@ -21170,19 +21192,19 @@ async function runTemplatePush(positional) {
21170
21192
  const wayaiEntry = manifest.wayai.locales[locale];
21171
21193
  let wayaiFiles;
21172
21194
  if (wayaiEntry) {
21173
- const sliceDir = path29.resolve(templateDir, wayaiEntry.source);
21195
+ const sliceDir = path30.resolve(templateDir, wayaiEntry.source);
21174
21196
  if (!fs23.existsSync(sliceDir)) {
21175
21197
  console.error(`wayai source not found for ${locale}: ${wayaiEntry.source} (resolved ${sliceDir})`);
21176
21198
  process.exit(1);
21177
21199
  }
21178
21200
  if (!isInsideTemplateDir(sliceDir, templateDir)) {
21179
21201
  console.warn(
21180
- `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.`
21202
+ `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 ${path30.basename(sliceDir)}\` to commit a stable frozen slice.`
21181
21203
  );
21182
21204
  }
21183
21205
  wayaiFiles = readDirToFileMap(sliceDir);
21184
21206
  }
21185
- const readmePath = path29.join(templateDir, `README.${locale}.md`);
21207
+ const readmePath = path30.join(templateDir, `README.${locale}.md`);
21186
21208
  const readmeMarkdown = fs23.existsSync(readmePath) ? fs23.readFileSync(readmePath, "utf-8") : "";
21187
21209
  locales[locale] = {
21188
21210
  readmeMarkdown,
@@ -21194,9 +21216,9 @@ async function runTemplatePush(positional) {
21194
21216
  }
21195
21217
  let hero;
21196
21218
  if (manifest.hero) {
21197
- const heroPath = path29.resolve(templateDir, manifest.hero);
21219
+ const heroPath = path30.resolve(templateDir, manifest.hero);
21198
21220
  if (fs23.existsSync(heroPath)) {
21199
- hero = { filename: path29.basename(heroPath), dataBase64: fs23.readFileSync(heroPath).toString("base64") };
21221
+ hero = { filename: path30.basename(heroPath), dataBase64: fs23.readFileSync(heroPath).toString("base64") };
21200
21222
  } else {
21201
21223
  console.error(`Warning: hero not found at ${manifest.hero}; skipping.`);
21202
21224
  }
@@ -21222,7 +21244,7 @@ async function runTemplatePush(positional) {
21222
21244
  }
21223
21245
  }
21224
21246
  function isInsideTemplateDir(dest, templateDir) {
21225
- return dest === templateDir || dest.startsWith(templateDir + path29.sep);
21247
+ return dest === templateDir || dest.startsWith(templateDir + path30.sep);
21226
21248
  }
21227
21249
  async function runTemplateFreeze(positional, flagArgs) {
21228
21250
  const slug = positional[0];
@@ -21283,7 +21305,7 @@ async function runTemplateFreeze(positional, flagArgs) {
21283
21305
  process.exit(1);
21284
21306
  }
21285
21307
  const entry = manifest.wayai.locales[targetLocale];
21286
- const dest = path29.resolve(templateDir, entry.source);
21308
+ const dest = path30.resolve(templateDir, entry.source);
21287
21309
  if (!isInsideTemplateDir(dest, templateDir) || dest === templateDir) {
21288
21310
  console.error(
21289
21311
  `source for "${targetLocale}" must be a committed path inside ${slug}/ (e.g. "./${targetLocale}/slice"), but resolved to:
@@ -21302,7 +21324,7 @@ Set it to a subdir of the template folder and re-run freeze.`
21302
21324
  const hubs = scanWorkspaceHubs(hubsDir);
21303
21325
  if (hubs.length) {
21304
21326
  console.error("Available hubs:");
21305
- for (const h of hubs) console.error(` ${path29.basename(h.hubFolder)} (${h.hubId})`);
21327
+ for (const h of hubs) console.error(` ${path30.basename(h.hubFolder)} (${h.hubId})`);
21306
21328
  }
21307
21329
  console.error(`
21308
21330
  Pass --from <folder|uuid>${selector ? "." : " or run `wayai use <hub>` first."}`);
@@ -21310,12 +21332,12 @@ Pass --from <folder|uuid>${selector ? "." : " or run `wayai use <hub>` first."}`
21310
21332
  }
21311
21333
  const liveFiles = readDirToFileMap(hubFolder);
21312
21334
  if (Object.keys(liveFiles).length === 0) {
21313
- console.error(`Source hub folder has no files to freeze: ${path29.relative(gitRoot, hubFolder)}`);
21335
+ console.error(`Source hub folder has no files to freeze: ${path30.relative(gitRoot, hubFolder)}`);
21314
21336
  process.exit(1);
21315
21337
  }
21316
21338
  const sanitized = sanitizeWayaiSliceFiles(liveFiles);
21317
- const relDest = path29.relative(gitRoot, dest);
21318
- const relSrc = path29.relative(gitRoot, hubFolder);
21339
+ const relDest = path30.relative(gitRoot, dest);
21340
+ const relSrc = path30.relative(gitRoot, hubFolder);
21319
21341
  if (dryRun) {
21320
21342
  const existing = new Set(listDirRelFiles(dest));
21321
21343
  const nextKeys = new Set(Object.keys(sanitized));
@@ -21375,7 +21397,7 @@ async function runTemplatePull2(positional, flagArgs) {
21375
21397
  console.log(JSON.stringify(detail, null, 2));
21376
21398
  return;
21377
21399
  }
21378
- const target = outDir ? path29.resolve(outDir) : path29.join(process.cwd(), "template-store-pull", slug);
21400
+ const target = outDir ? path30.resolve(outDir) : path30.join(process.cwd(), "template-store-pull", slug);
21379
21401
  const written = writeFileMap(target, detail.files);
21380
21402
  console.log(`Pulled "${slug}" from the store \u2192 ${target} (${written.length} file${written.length === 1 ? "" : "s"}).`);
21381
21403
  const uses = detail.uses ?? [];
@@ -21398,13 +21420,13 @@ async function runSkillInstall(positional) {
21398
21420
  throw err;
21399
21421
  }
21400
21422
  const root = findGitRoot() ?? process.cwd();
21401
- const present = HARNESS_SKILL_DIRS.filter((dir) => fs23.existsSync(path29.join(root, dir)));
21423
+ const present = HARNESS_SKILL_DIRS.filter((dir) => fs23.existsSync(path30.join(root, dir)));
21402
21424
  const targets = present.length > 0 ? present : HARNESS_SKILL_DIRS;
21403
21425
  const fileCount = Object.keys(res.files).length;
21404
21426
  const relDirs = targets.map((harness) => {
21405
- const targetDir = path29.join(root, harness, "skills", name);
21427
+ const targetDir = path30.join(root, harness, "skills", name);
21406
21428
  writeFileMap(targetDir, res.files);
21407
- return `${path29.relative(root, targetDir)}/`;
21429
+ return `${path30.relative(root, targetDir)}/`;
21408
21430
  });
21409
21431
  console.log(`Installed skill "${name}" (${fileCount} file${fileCount === 1 ? "" : "s"}) \u2192 ${relDirs.join(", ")}`);
21410
21432
  console.log("Reload your agent (e.g. restart Claude Code) to pick up the skill.");
@@ -22748,7 +22770,7 @@ var init_update = __esm({
22748
22770
  init_sentry();
22749
22771
  import { readFileSync as readFileSync20 } from "fs";
22750
22772
  import { fileURLToPath as fileURLToPath2 } from "url";
22751
- import { dirname as dirname9, join as join27 } from "path";
22773
+ import { dirname as dirname9, join as join28 } from "path";
22752
22774
 
22753
22775
  // src/lib/errors.ts
22754
22776
  init_api_client();
@@ -22928,7 +22950,7 @@ Run \`wayai admin skill install\` to update.`);
22928
22950
 
22929
22951
  // src/index.ts
22930
22952
  var __dirname = dirname9(fileURLToPath2(import.meta.url));
22931
- var pkg = JSON.parse(readFileSync20(join27(__dirname, "..", "package.json"), "utf-8"));
22953
+ var pkg = JSON.parse(readFileSync20(join28(__dirname, "..", "package.json"), "utf-8"));
22932
22954
  var [, , command, ...args] = process.argv;
22933
22955
  var isBackgroundRefresh = command === REFRESH_COMMAND;
22934
22956
  if (!isBackgroundRefresh) initSentry(command, pkg.version);