@wayai/cli 0.3.53 → 0.3.54

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
@@ -133,11 +133,11 @@ function captureException2(error, context) {
133
133
  Sentry.captureException(error);
134
134
  });
135
135
  }
136
- function addApiBreadcrumb(method, path19) {
136
+ function addApiBreadcrumb(method, path22) {
137
137
  if (!initialized) return;
138
138
  Sentry.addBreadcrumb({
139
139
  category: "http",
140
- message: `${method} ${path19}`,
140
+ message: `${method} ${path22}`,
141
141
  level: "info"
142
142
  });
143
143
  }
@@ -189,13 +189,13 @@ var init_api_client = __esm({
189
189
  init_sentry();
190
190
  init_redact();
191
191
  RETRYABLE_BACKOFF_MS = [500, 1e3, 2e3];
192
- delay = (ms) => new Promise((resolve4) => setTimeout(resolve4, ms));
192
+ delay = (ms) => new Promise((resolve5) => setTimeout(resolve5, ms));
193
193
  ApiError = class extends Error {
194
194
  status;
195
195
  body;
196
- constructor(method, path19, status, body) {
196
+ constructor(method, path22, status, body) {
197
197
  const safeBody = redactSecrets(body);
198
- super(`API request failed: ${method} ${path19} (${status}): ${safeBody}`);
198
+ super(`API request failed: ${method} ${path22} (${status}): ${safeBody}`);
199
199
  this.name = "ApiError";
200
200
  this.status = status;
201
201
  this.body = safeBody;
@@ -252,6 +252,21 @@ var init_api_client = __esm({
252
252
  commit_sha: commitSha
253
253
  });
254
254
  }
255
+ // --- Templates gallery ---
256
+ // Consumer (any authed user): list the joint template set + pull the wayai slice.
257
+ async templateList() {
258
+ return this.request("GET", "/api/ci/templates");
259
+ }
260
+ async templatePull(slug) {
261
+ return this.request("GET", `/api/ci/templates/${encodeURIComponent(slug)}`);
262
+ }
263
+ // Author (platform-admin): push a template into the store / pull it for inspection.
264
+ async adminTemplatePush(body) {
265
+ return this.request("POST", "/api/admin/templates/push", body);
266
+ }
267
+ async adminTemplatePull(slug) {
268
+ return this.request("GET", `/api/admin/templates/${encodeURIComponent(slug)}`);
269
+ }
255
270
  /**
256
271
  * Re-discover an MCP connection's tool/resource catalog (refreshes stale
257
272
  * input schemas that `push` leaves untouched). `connection` is the display
@@ -264,8 +279,8 @@ var init_api_client = __esm({
264
279
  ...organizationId && { organization_id: organizationId }
265
280
  });
266
281
  }
267
- async lookup(path19, opts) {
268
- const params = new URLSearchParams({ path: path19 });
282
+ async lookup(path22, opts) {
283
+ const params = new URLSearchParams({ path: path22 });
269
284
  if (opts?.organizationId) params.set("organization_id", opts.organizationId);
270
285
  return this.request("GET", `/api/ci/lookup?${params.toString()}`);
271
286
  }
@@ -615,9 +630,9 @@ var init_api_client = __esm({
615
630
  `/api/admin/data-explorer/debug/observability/${encodeURIComponent(hubId)}/conversations/${encodeURIComponent(conversationId)}${qs}`
616
631
  );
617
632
  }
618
- async request(method, path19, body) {
619
- addApiBreadcrumb(method, path19);
620
- const url = `${this.apiUrl}${path19}`;
633
+ async request(method, path22, body) {
634
+ addApiBreadcrumb(method, path22);
635
+ const url = `${this.apiUrl}${path22}`;
621
636
  let refreshedOn401 = false;
622
637
  for (let retry = 0; ; retry++) {
623
638
  let response = await this.send(url, method, body);
@@ -641,7 +656,7 @@ var init_api_client = __esm({
641
656
  await delay(RETRYABLE_BACKOFF_MS[retry]);
642
657
  continue;
643
658
  }
644
- throw new ApiError(method, path19, response.status, errorBody);
659
+ throw new ApiError(method, path22, response.status, errorBody);
645
660
  }
646
661
  }
647
662
  /** Issue a single authenticated request with the client's current token. */
@@ -1076,8 +1091,8 @@ var init_parseUtil = __esm({
1076
1091
  init_errors();
1077
1092
  init_en();
1078
1093
  makeIssue = (params) => {
1079
- const { data, path: path19, errorMaps, issueData } = params;
1080
- const fullPath = [...path19, ...issueData.path || []];
1094
+ const { data, path: path22, errorMaps, issueData } = params;
1095
+ const fullPath = [...path22, ...issueData.path || []];
1081
1096
  const fullIssue = {
1082
1097
  ...issueData,
1083
1098
  path: fullPath
@@ -1388,11 +1403,11 @@ var init_types = __esm({
1388
1403
  init_parseUtil();
1389
1404
  init_util();
1390
1405
  ParseInputLazyPath = class {
1391
- constructor(parent, value, path19, key) {
1406
+ constructor(parent, value, path22, key) {
1392
1407
  this._cachedPath = [];
1393
1408
  this.parent = parent;
1394
1409
  this.data = value;
1395
- this._path = path19;
1410
+ this._path = path22;
1396
1411
  this._key = key;
1397
1412
  }
1398
1413
  get path() {
@@ -4783,6 +4798,38 @@ function normalizeAuthType(raw) {
4783
4798
  function visibleLength(s) {
4784
4799
  return s.replace(INVISIBLE_NAME_CHARS, "").length;
4785
4800
  }
4801
+ function validateLaneReferences(lanes, statuses) {
4802
+ const issues = [];
4803
+ if (!Array.isArray(statuses) || statuses.length === 0) return issues;
4804
+ const declared = new Set((Array.isArray(lanes) ? lanes : []).map((l) => l.slug).filter(Boolean));
4805
+ statuses.forEach((s, i) => {
4806
+ const lane = s.lane_slug;
4807
+ if (typeof lane !== "string" || lane.length === 0) return;
4808
+ const label = s.slug ? `"${s.slug}"` : `[${i}]`;
4809
+ if (s.isInitialStatus === true) {
4810
+ issues.push({
4811
+ path: [i, "lane_slug"],
4812
+ message: `kanban status ${label}: the initial status cannot belong to a lane (it is the shared entry point)`
4813
+ });
4814
+ return;
4815
+ }
4816
+ if (!declared.has(lane)) {
4817
+ issues.push({
4818
+ path: [i, "lane_slug"],
4819
+ message: `kanban status ${label}: lane_slug references unknown lane "${lane}"`
4820
+ });
4821
+ }
4822
+ });
4823
+ return issues;
4824
+ }
4825
+ function refineLaneReferences(val, ctx) {
4826
+ const lanes = val.lanes;
4827
+ const statuses = val.kanban_statuses;
4828
+ if (lanes == null || statuses == null) return;
4829
+ for (const issue of validateLaneReferences(lanes, statuses)) {
4830
+ ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["kanban_statuses", ...issue.path], message: issue.message });
4831
+ }
4832
+ }
4786
4833
  function containsKanbanPlaceholders(value) {
4787
4834
  if (value == null) return false;
4788
4835
  const serialized = typeof value === "string" ? value : JSON.stringify(value);
@@ -4814,12 +4861,12 @@ function typeMatches(typeField, allowed) {
4814
4861
  if (Array.isArray(typeField)) return typeField.every((t) => typeof t === "string" && allowed.has(t));
4815
4862
  return false;
4816
4863
  }
4817
- function validateSchema(schema, path19, errors, opts = {}) {
4864
+ function validateSchema(schema, path22, errors, opts = {}) {
4818
4865
  if (typeof schema === "boolean") return;
4819
4866
  const depth = opts.depth ?? 0;
4820
4867
  if (depth > MAX_SCHEMA_DEPTH) {
4821
4868
  errors.push({
4822
- path: path19 || "<root>",
4869
+ path: path22 || "<root>",
4823
4870
  message: `schema nesting exceeds ${MAX_SCHEMA_DEPTH} levels`,
4824
4871
  suggestion: "Flatten the schema; LLM providers reject deeply nested tool input_schemas."
4825
4872
  });
@@ -4827,7 +4874,7 @@ function validateSchema(schema, path19, errors, opts = {}) {
4827
4874
  }
4828
4875
  if (!isRecord(schema)) {
4829
4876
  errors.push({
4830
- path: path19,
4877
+ path: path22,
4831
4878
  message: `expected object, got ${schema === null ? "null" : typeof schema}`
4832
4879
  });
4833
4880
  return;
@@ -4835,14 +4882,14 @@ function validateSchema(schema, path19, errors, opts = {}) {
4835
4882
  if (opts.isRoot) {
4836
4883
  if ("type" in schema && schema.type !== "object") {
4837
4884
  errors.push({
4838
- path: path19 ? `${path19}.type` : "type",
4885
+ path: path22 ? `${path22}.type` : "type",
4839
4886
  message: `root type must be 'object', got ${JSON.stringify(schema.type)}`,
4840
4887
  suggestion: "Tool parameters at the root must be an object: `{ type: 'object', properties: { ... } }`."
4841
4888
  });
4842
4889
  }
4843
4890
  } else if ("type" in schema && !typeMatches(schema.type, ALLOWED_TYPES)) {
4844
4891
  errors.push({
4845
- path: `${path19}.type`,
4892
+ path: `${path22}.type`,
4846
4893
  message: `type must be one of ${[...ALLOWED_TYPES].join("/")} or an array of those, got ${JSON.stringify(schema.type)}`
4847
4894
  });
4848
4895
  }
@@ -4851,25 +4898,25 @@ function validateSchema(schema, path19, errors, opts = {}) {
4851
4898
  if (typeof e === "string") {
4852
4899
  const isPlaceholder = PLACEHOLDER_TOKENS.includes(e);
4853
4900
  errors.push({
4854
- path: `${path19}.enum`,
4901
+ path: `${path22}.enum`,
4855
4902
  message: `enum must be a non-empty array of primitives, got string ${JSON.stringify(e)}`,
4856
4903
  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"]`.'
4857
4904
  });
4858
4905
  } else if (!Array.isArray(e)) {
4859
4906
  errors.push({
4860
- path: `${path19}.enum`,
4907
+ path: `${path22}.enum`,
4861
4908
  message: `enum must be a non-empty array of primitives, got ${typeof e}`
4862
4909
  });
4863
4910
  } else if (e.length === 0) {
4864
4911
  errors.push({
4865
- path: `${path19}.enum`,
4912
+ path: `${path22}.enum`,
4866
4913
  message: "enum must not be empty"
4867
4914
  });
4868
4915
  } else {
4869
4916
  for (let i = 0; i < e.length; i++) {
4870
4917
  if (!isPrimitive(e[i])) {
4871
4918
  errors.push({
4872
- path: `${path19}.enum[${i}]`,
4919
+ path: `${path22}.enum[${i}]`,
4873
4920
  message: `enum entry must be a primitive (string/number/boolean/null), got ${typeof e[i]}`
4874
4921
  });
4875
4922
  }
@@ -4879,7 +4926,7 @@ function validateSchema(schema, path19, errors, opts = {}) {
4879
4926
  for (const key of ["exclusiveMinimum", "exclusiveMaximum"]) {
4880
4927
  if (key in schema && typeof schema[key] === "boolean") {
4881
4928
  errors.push({
4882
- path: `${path19}.${key}`,
4929
+ path: `${path22}.${key}`,
4883
4930
  message: `${key} as boolean is draft-04 syntax; draft 2020-12 requires a numeric value`,
4884
4931
  suggestion: `Replace with the numeric bound, e.g. \`${key}: 0\`.`
4885
4932
  });
@@ -4888,45 +4935,45 @@ function validateSchema(schema, path19, errors, opts = {}) {
4888
4935
  if ("properties" in schema) {
4889
4936
  if (!isRecord(schema.properties)) {
4890
4937
  errors.push({
4891
- path: `${path19}.properties`,
4938
+ path: `${path22}.properties`,
4892
4939
  message: `properties must be an object, got ${Array.isArray(schema.properties) ? "array" : typeof schema.properties}`
4893
4940
  });
4894
4941
  } else {
4895
4942
  for (const [propName, propSchema] of Object.entries(schema.properties)) {
4896
- validateSchema(propSchema, `${path19}.properties.${propName}`, errors, { depth: depth + 1 });
4943
+ validateSchema(propSchema, `${path22}.properties.${propName}`, errors, { depth: depth + 1 });
4897
4944
  }
4898
4945
  }
4899
4946
  }
4900
4947
  if (schemaTypeIncludes(schema, "array") && "items" in schema) {
4901
4948
  if (Array.isArray(schema.items)) {
4902
- schema.items.forEach((sub, i) => validateSchema(sub, `${path19}.items[${i}]`, errors, { depth: depth + 1 }));
4949
+ schema.items.forEach((sub, i) => validateSchema(sub, `${path22}.items[${i}]`, errors, { depth: depth + 1 }));
4903
4950
  } else {
4904
- validateSchema(schema.items, `${path19}.items`, errors, { depth: depth + 1 });
4951
+ validateSchema(schema.items, `${path22}.items`, errors, { depth: depth + 1 });
4905
4952
  }
4906
4953
  }
4907
4954
  for (const key of SUBSCHEMA_OBJECT_KEYWORDS) {
4908
4955
  if (key in schema && isRecord(schema[key])) {
4909
- validateSchema(schema[key], `${path19}.${key}`, errors, { depth: depth + 1 });
4956
+ validateSchema(schema[key], `${path22}.${key}`, errors, { depth: depth + 1 });
4910
4957
  }
4911
4958
  }
4912
4959
  for (const key of SUBSCHEMA_LIST_KEYWORDS) {
4913
4960
  const list = schema[key];
4914
4961
  if (Array.isArray(list)) {
4915
- list.forEach((sub, i) => validateSchema(sub, `${path19}.${key}[${i}]`, errors, { depth: depth + 1 }));
4962
+ list.forEach((sub, i) => validateSchema(sub, `${path22}.${key}[${i}]`, errors, { depth: depth + 1 }));
4916
4963
  }
4917
4964
  }
4918
4965
  for (const key of SUBSCHEMA_MAP_KEYWORDS) {
4919
4966
  const map = schema[key];
4920
4967
  if (isRecord(map)) {
4921
4968
  for (const [name, sub] of Object.entries(map)) {
4922
- validateSchema(sub, `${path19}.${key}.${name}`, errors, { depth: depth + 1 });
4969
+ validateSchema(sub, `${path22}.${key}.${name}`, errors, { depth: depth + 1 });
4923
4970
  }
4924
4971
  }
4925
4972
  }
4926
4973
  const reportedPaths = new Set(errors.map((e) => e.path));
4927
4974
  for (const [k, v] of Object.entries(schema)) {
4928
4975
  if (typeof v !== "string") continue;
4929
- const fieldPath = `${path19}.${k}`;
4976
+ const fieldPath = `${path22}.${k}`;
4930
4977
  if (reportedPaths.has(fieldPath)) continue;
4931
4978
  for (const token of PLACEHOLDER_TOKENS) {
4932
4979
  if (v === token) {
@@ -5059,6 +5106,37 @@ function refineHubAsCodeKanbanStatuses(config, ctx) {
5059
5106
  }
5060
5107
  }
5061
5108
  }
5109
+ function refineHubAsCodeLanes(config, ctx) {
5110
+ const hub = config.hub;
5111
+ if (hub == null) return;
5112
+ if ("lanes" in hub && hub.lanes != null) {
5113
+ if (!Array.isArray(hub.lanes)) {
5114
+ ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["hub", "lanes"], message: "lanes must be an array" });
5115
+ return;
5116
+ }
5117
+ const result = lanesArraySchema.safeParse(hub.lanes);
5118
+ if (!result.success) {
5119
+ for (const issue of result.error.issues) {
5120
+ ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["hub", "lanes", ...issue.path], message: issue.message });
5121
+ }
5122
+ return;
5123
+ }
5124
+ }
5125
+ const statuses = hub.kanban_statuses;
5126
+ if (Array.isArray(statuses)) {
5127
+ for (const issue of validateLaneReferences(hub.lanes, statuses)) {
5128
+ ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["hub", "kanban_statuses", ...issue.path], message: issue.message });
5129
+ }
5130
+ }
5131
+ }
5132
+ function isSafeTemplatePath(path22) {
5133
+ if (path22.length === 0 || path22.length > 300) return false;
5134
+ if (path22.startsWith("/") || path22.includes("\\")) return false;
5135
+ const segments = path22.split("/");
5136
+ return segments.every(
5137
+ (seg) => seg.length > 0 && seg !== "." && seg !== ".." && /^[A-Za-z0-9._-]+$/.test(seg)
5138
+ );
5139
+ }
5062
5140
  function identifyTokenType(token) {
5063
5141
  if (token.startsWith("way_")) return "way_token";
5064
5142
  if (token.startsWith("eyJ")) return "jwt";
@@ -5124,7 +5202,7 @@ function findStepBoundaries(transcript) {
5124
5202
  }
5125
5203
  return out;
5126
5204
  }
5127
- var APP_ERROR_BRAND, AppError, LLM_RATE_LIMIT_BRAND, LlmRateLimitError, LLM_PROVIDER_HTTP_BRAND, LlmProviderHttpError, LLM_PROVIDER_TIMEOUT_BRAND, LlmProviderTimeoutError, ExternalServiceError, CHANNEL_PROVIDER_HTTP_BRAND, ChannelProviderHttpError, MEDIA_PROVIDER_HTTP_BRAND, MediaProviderHttpError, AUTH_TYPE, ORG_CREDENTIAL_AUTH_TYPES, AUTH_TYPE_DISPLAY, LEGACY_AUTH_TYPE_MAP, VALID_AUTH_TYPES, AGENT_ROLES, SAFE_USER_ID_REGEX, uuidSchema, paginationSchema, timestampSchema, idParamSchema, primaryRegionSchema, placementSourceSchema, orgIdParam, orgAdminIdParam, createOrganizationBody, updateOrganizationBody, addOrgAdminBody, SUMMARIZATION_THRESHOLD_MIN, SUMMARIZATION_THRESHOLD_MAX, summarizationThresholdField, monitorConfigField, agentIdParam, listAgentsQuery, agentConnectionsQuery, createAgentBody, updateAgentBody, AGENT_PARAMETER_TYPES, AGENT_PARAMETER_NAME_REGEX, agentParameterCreateSchema, MAX_AGENT_PARAMETERS_PER_REQUEST, createAgentParametersBody, SLUG_REGEX, slugSchema, INVISIBLE_NAME_CHARS, MAX_KANBAN_STATUSES, MAX_FOLLOWUPS_PER_STATUS, MAX_ADDITIONAL_CONTEXT_SCHEMA_BYTES, RESERVED_TEMPLATE_DUMP_NAME, kanbanStatusSlugSchema, followupSchema, EXCLUSIVE_FLAG_PAIRS, kanbanStatusSchema, kanbanStatusesArraySchema, hubIdParam, hubAdminIdParam, hubUserIdParam, hubIdentityIdParam, hubTeamIdParam, hubTeamUserDeleteParam, hubSchemaQuery, createHubBody, updateHubBody, addHubAdminBody, addHubIdentityAuthorizationBody, contactDecisionBody, approveContactBody, listContactAccessQuery, createHubTeamBody, updateHubTeamBody, addHubTeamUserBody, reassignHubUserBody, replicatePreviewBody, claimCodeChannelParam, claimCodeDeleteParam, connectionIdParam, connectorIdParam, listConnectionsQuery, deleteConnectionQuery, addConnectionBody, editConnectionBody, registerWhatsappQuery, registerWhatsappBody, retryProvisioningQuery, resendDomainStatusQuery, PLACEHOLDER_TOKENS, ALLOWED_TYPES, SUBSCHEMA_OBJECT_KEYWORDS, SUBSCHEMA_LIST_KEYWORDS, SUBSCHEMA_MAP_KEYWORDS, MAX_SCHEMA_DEPTH, toolIdParam, listToolsQuery, toolAgentQuery, deleteToolQuery, toolConnectionsQuery, nativeToolIdSchema, addNativeToolBody, addMcpToolBody, createCustomToolBody, updateCustomToolBody, updateToolExecutionConfigBody, initialValueCreateSchema, initialValueUpdateSchema, stateIdParam, listStatesQuery, stateNameSchema, createStateBody, updateStateBody, reorderStatesBody, orgTagIdParam, listOrgTagsQuery, createOrgTagBody, updateOrgTagBody, orgCredentialIdParam, listOrgCredentialsQuery, createOrgCredentialBody, updateOrgCredentialBody, orgResourceIdParam, orgResourceFileIdParam, orgResourceFolderIdParam, hubOrgResourceParam, listOrgResourcesQuery, orgIdQuery, createOrgResourceBody, updateOrgResourceBody, createOrgResourceFolderBody, updateOrgResourceFolderBody, uploadOrgResourceFileBody, updateOrgResourceFileBody, uploadOrgSkillZipBody, linkOrgResourceBody, resourceIdParam, listResourcesQuery, createResourceBody, updateResourceBody, syncSkillsBody, resourceFileIdParam, listResourceFilesQuery, createResourceFileBody, updateResourceFileBody, uploadResourceFileBody, uploadSkillZipBody, resourceFolderIdParam, listResourceFoldersQuery, createResourceFolderBody, updateResourceFolderBody, agentResourceQuery, hubResourceQuery, linkAgentResourceBody, updateAgentResourceBody, unlinkAgentResourceQuery, navItemSchema, hubCountResetBody, navItemCountResetBody, typingBody, analyticsHubIdParam, analyticsVariableIdParam, updateVariablePinBody, updateAnalyticsViewBody, NUMERIC_FILTER_OPS, TEXT_FILTER_OPS, CATEGORICAL_FILTER_OPS, VARIABLE_FILTER_OPS, STRUCTURED_QUERY_OPS, STRUCTURED_QUERY_AGGREGATIONS, filterValueSchema, conversationsBody, analyticsConversationIdParam, messagesQuery, conversationDetailDataQuery, analyticsDataBody, structuredQueryBody, analyticsSqlBody, analyticsSqlSchemaQuery, PACING_INTERVAL_MIN_MS, PACING_INTERVAL_MAX_MS, EVAL_RUN_DEADLINE_MIN_MS, EVAL_RUN_DEADLINE_MAX_MS, paginationSchema2, evalIdParam, sessionIdParam, scenarioSetIdParam, hubIdQuery, createEvalBody, updateEvalBody, getEvalsQuery, toggleEvalBody, bulkToggleEvalsBody, pacingConfigSchema, sessionConfigSchema, createSessionBody, getSessionsQuery, getSessionResultsQuery, getSessionRunsQuery, evalAnalyticsQuery, getSessionCostQuery, compareSessionsQuery, evalsSqlBody, evalsSqlSchemaQuery, exportResultsQuery, validateEvalBody, hubAgentsQuery, createScenarioSetBody, updateScenarioSetBody, scenarioSetsHubQuery, createScenarioFromConversationBody, createJourneyFromConversationBody, journeyIdParam, journeyToolCallSchema, journeyTurnSchema, journeyStepOverrideSchema, createJourneyBody, updateJourneyBody, getConversationsQuery, getMessagesQuery, updateUserBody, updateProfileBody, pathnameRegex, updatePreferencesBody, registerPushTokenBody, deletePushTokenQuery, activityQuery, deletionModeSchema, deletionRequestBody, archiveHubIdParam, archiveConversationParams, archiveListQuery, archiveMessageObservabilityParams, conversationIdParam, claimBody, releaseBody, transferBody, closeBody, additionalContextPayloadSchema, kanbanUpdateBody, observabilityParams, observabilityListParams, deleteUserConversationsBody, sendMessageBody, listConversationsQuery, getConversationDetailParams, getConversationDetailQuery, listWhatsAppTemplatesQuery, sendWhatsAppTemplateBody, billingOrgIdParam, subscriptionItemParams, spendCapBody, growthPacksBody, portalSessionBody, checkoutSessionBody, updateBillingCurrencyBody, invoicesQuery, downloadBody, uploadQuery, signUrlBody, completionsBody, outboundSchemaQuery, templateIdParam, listTemplatesQuery, deleteTemplateQuery, templateStatusQuery, createTemplateBody, updateTemplateBody, submitTemplateBody, testTemplateBody, contactIdParam, listContactsQuery, createContactBody, updateContactBody, listIdParam, listListsQuery, listListContactsQuery, createListBody, updateListBody, addListContactsBody, removeListContactsBody, scheduleIdParam, listSchedulesQuery, listExecutionsQuery, createScheduleBody, updateScheduleBody, uuidPattern, ciUuidSchema, hubAsCodeConfigSchema, ciPullHubIdParam, ciBranchParam, ciPullQuery, ciHubsQuery, ciLookupQuery, ciBranchesQuery, ciSyncBody, ciPushBody, ciDiffBody, ciPublishBody, ciPublishPreviewBody, ciSyncMcpBody, ciOrgResourcesParam, orgResourcesConfigSchema, ciOrgResourcesPushBody, ciOrgResourcesDiffBody, adminOrgIdParam, adminOrgAdminIdParam, adminUserIdParam, adminPlanIdParam, adminOrganizationsQuery, adminUserSearchQuery, kanbanSlugMigrationQuery, adjustQuotaBody, updatePlanBody, addAdminUserBody, updatePlatformConfigBody, updateFreeOrgLimitBody, updatePricingPlanBody, dataExplorerSearchQuery, dataExplorerOrgIdParam, dataExplorerHubIdParam, dataExplorerConvIdParam, dataExplorerDoInstancesQuery, dataExplorerNsIdParam, dataExplorerKvKeysQuery, dataExplorerKvKeyParam, uuidOrHexId, dataExplorerDeleteOrgParam, dataExplorerDeleteHubParam, dataExplorerDeleteConvParam, dataExplorerDeleteUserParam, dataExplorerDeleteQuery, dataExplorerKvDeleteQuery, pitrDoNamespace, pitrBookmarkParam, pitrRestoreBody, healthSamplingTriggerBody, dataExplorerDebugDoType, DEBUG_READ_MAX_LIMIT, DEBUG_READ_DEFAULT_LIMIT, DEBUG_DO_HEX_PREFIX, debugDoEntityId, debugTableName, dataExplorerDebugTablesParam, dataExplorerDebugRowsParam, dataExplorerDebugRowsQuery, dataExplorerDebugAnalyticsTable, debugUuid, dataExplorerDebugAnalyticsRowsParam, dataExplorerDebugAnalyticsRowsQuery, debugHubConvParam, debugMessageIdQuery, whatsappCallbackBody, authMeResponse, wsTicketResponse, sessionCheckResponse, logoutResponse, magicCodeSendBody, magicCodeSendResponse, magicCodeVerifyBody, magicCodeVerifyResponse, browserParams, browserFoldersQuery, browserFilesQuery, stateOperationBody, conversationListResourcesQuery, conversationReadResourceQuery, listPendingSchedulesQuery, listHubAggregateSchedulesQuery, reportSourceSchema, intakeReportSourceSchema, reportClassificationSchema, reportStatusSchema, reportLocaleSchema, reportMessageAuthorRoleSchema, sentryRefStatusSchema, createReportBody, editReportBody, contestReportBody, reporterListQuery, listReportsQuery, GITHUB_ISSUE_URL_REGEX, githubIssueUrlSchema, transitionReportBody, groupReportsBody, holdReportBody, clickhouseReadyResponse, bootstrapQuery, requestSnapshotChunkMessage, MAX_NAME_LENGTH, MAX_VALUE_LENGTH, MAX_TAGS, MAX_TAG_LENGTH, MAX_SCOPE_HUBS, MAX_SCOPE_TAGS, vaultSecretScopeSchema, vaultCreateBody, vaultUpdateBody, PERMISSIONS, MAX_NAME_LENGTH2, MAX_HUBS_PER_GRANT, MAX_HUB_TAGS_PER_GRANT, MAX_GRANTS, permissionEnum, grantScopeSchema, grantSchema, createTokenBody, tokenOrgIdParam, tokenGrantScopeSchema, tokenGrantSchema, tokenMetadataSchema, listTokensResponse, createTokenResponse, orgTokenSummarySchema, listOrgTokensResponse, invitePreviewQuery, invitePreviewResponse, discordTier, discordBillingCycle, discordMetadata, discordOAuthStartResponse, discordOAuthFinalizeBody, discordOAuthFinalizeResponse, discordDisconnectResponse, jsonSchemaSchema, hubUserIdentity, stateValueEntry, getHubUserContextResponse, updateHubUserBody, updateHubUserResponse, unlockHubUserNameResponse, stateResetParams, stateResetResponse, hubAlertsParam, hubAlertSeverity, hubAlert, hubAlertsListResponse, noticeSeveritySchema, noticeStatusSchema, NOTICE_SCOPE_REGEX, noticeScopeSchema, noticeLinkSchema, createNoticeBody, updateNoticeBody, listNoticesQuery, noticeIdParamSchema, WAYAI_WORKSPACE_LAYOUT, MAX_RESOURCE_FILE_SIZE;
5205
+ var APP_ERROR_BRAND, AppError, LLM_RATE_LIMIT_BRAND, LlmRateLimitError, LLM_PROVIDER_HTTP_BRAND, LlmProviderHttpError, LLM_PROVIDER_TIMEOUT_BRAND, LlmProviderTimeoutError, ExternalServiceError, CHANNEL_PROVIDER_HTTP_BRAND, ChannelProviderHttpError, MEDIA_PROVIDER_HTTP_BRAND, MediaProviderHttpError, AUTH_TYPE, ORG_CREDENTIAL_AUTH_TYPES, AUTH_TYPE_DISPLAY, LEGACY_AUTH_TYPE_MAP, VALID_AUTH_TYPES, AGENT_ROLES, SAFE_USER_ID_REGEX, uuidSchema, paginationSchema, timestampSchema, idParamSchema, primaryRegionSchema, placementSourceSchema, orgIdParam, orgAdminIdParam, createOrganizationBody, updateOrganizationBody, addOrgAdminBody, SUMMARIZATION_THRESHOLD_MIN, SUMMARIZATION_THRESHOLD_MAX, summarizationThresholdField, monitorConfigField, agentIdParam, listAgentsQuery, agentConnectionsQuery, createAgentBody, updateAgentBody, AGENT_PARAMETER_TYPES, AGENT_PARAMETER_NAME_REGEX, agentParameterCreateSchema, MAX_AGENT_PARAMETERS_PER_REQUEST, createAgentParametersBody, SLUG_REGEX, slugSchema, INVISIBLE_NAME_CHARS, MAX_KANBAN_STATUSES, MAX_FOLLOWUPS_PER_STATUS, MAX_LANES, MAX_ADDITIONAL_CONTEXT_SCHEMA_BYTES, RESERVED_TEMPLATE_DUMP_NAME, kanbanStatusSlugSchema, followupSchema, EXCLUSIVE_FLAG_PAIRS, kanbanStatusSchema, kanbanStatusesArraySchema, laneSchema, lanesArraySchema, hubIdParam, hubAdminIdParam, hubUserIdParam, hubIdentityIdParam, hubTeamIdParam, hubTeamUserDeleteParam, hubSchemaQuery, createHubBody, updateHubBody, addHubAdminBody, addHubIdentityAuthorizationBody, contactDecisionBody, approveContactBody, listContactAccessQuery, createHubTeamBody, updateHubTeamBody, addHubTeamUserBody, reassignHubUserBody, replicatePreviewBody, claimCodeChannelParam, claimCodeDeleteParam, connectionIdParam, connectorIdParam, listConnectionsQuery, deleteConnectionQuery, addConnectionBody, editConnectionBody, registerWhatsappQuery, registerWhatsappBody, retryProvisioningQuery, resendDomainStatusQuery, PLACEHOLDER_TOKENS, ALLOWED_TYPES, SUBSCHEMA_OBJECT_KEYWORDS, SUBSCHEMA_LIST_KEYWORDS, SUBSCHEMA_MAP_KEYWORDS, MAX_SCHEMA_DEPTH, toolIdParam, listToolsQuery, toolAgentQuery, deleteToolQuery, toolConnectionsQuery, nativeToolIdSchema, addNativeToolBody, addMcpToolBody, createCustomToolBody, updateCustomToolBody, updateToolExecutionConfigBody, initialValueCreateSchema, initialValueUpdateSchema, stateIdParam, listStatesQuery, stateNameSchema, createStateBody, updateStateBody, reorderStatesBody, orgTagIdParam, listOrgTagsQuery, createOrgTagBody, updateOrgTagBody, orgCredentialIdParam, listOrgCredentialsQuery, createOrgCredentialBody, updateOrgCredentialBody, orgResourceIdParam, orgResourceFileIdParam, orgResourceFolderIdParam, hubOrgResourceParam, listOrgResourcesQuery, orgIdQuery, createOrgResourceBody, updateOrgResourceBody, createOrgResourceFolderBody, updateOrgResourceFolderBody, uploadOrgResourceFileBody, updateOrgResourceFileBody, uploadOrgSkillZipBody, linkOrgResourceBody, resourceIdParam, listResourcesQuery, createResourceBody, updateResourceBody, syncSkillsBody, resourceFileIdParam, listResourceFilesQuery, createResourceFileBody, updateResourceFileBody, uploadResourceFileBody, uploadSkillZipBody, resourceFolderIdParam, listResourceFoldersQuery, createResourceFolderBody, updateResourceFolderBody, agentResourceQuery, hubResourceQuery, linkAgentResourceBody, updateAgentResourceBody, unlinkAgentResourceQuery, navItemSchema, hubCountResetBody, navItemCountResetBody, typingBody, analyticsHubIdParam, analyticsVariableIdParam, updateVariablePinBody, updateAnalyticsViewBody, NUMERIC_FILTER_OPS, TEXT_FILTER_OPS, CATEGORICAL_FILTER_OPS, VARIABLE_FILTER_OPS, STRUCTURED_QUERY_OPS, STRUCTURED_QUERY_AGGREGATIONS, filterValueSchema, conversationsBody, analyticsConversationIdParam, messagesQuery, conversationDetailDataQuery, analyticsDataBody, structuredQueryBody, analyticsSqlBody, analyticsSqlSchemaQuery, PACING_INTERVAL_MIN_MS, PACING_INTERVAL_MAX_MS, EVAL_RUN_DEADLINE_MIN_MS, EVAL_RUN_DEADLINE_MAX_MS, paginationSchema2, evalIdParam, sessionIdParam, scenarioSetIdParam, hubIdQuery, createEvalBody, updateEvalBody, getEvalsQuery, toggleEvalBody, bulkToggleEvalsBody, pacingConfigSchema, sessionConfigSchema, createSessionBody, getSessionsQuery, getSessionResultsQuery, getSessionRunsQuery, evalAnalyticsQuery, getSessionCostQuery, compareSessionsQuery, evalsSqlBody, evalsSqlSchemaQuery, exportResultsQuery, validateEvalBody, hubAgentsQuery, createScenarioSetBody, updateScenarioSetBody, scenarioSetsHubQuery, createScenarioFromConversationBody, createJourneyFromConversationBody, journeyIdParam, journeyToolCallSchema, journeyTurnSchema, journeyStepOverrideSchema, createJourneyBody, updateJourneyBody, getConversationsQuery, getMessagesQuery, updateUserBody, updateProfileBody, pathnameRegex, updatePreferencesBody, registerPushTokenBody, deletePushTokenQuery, activityQuery, deletionModeSchema, deletionRequestBody, archiveHubIdParam, archiveConversationParams, archiveListQuery, archiveMessageObservabilityParams, conversationIdParam, claimBody, releaseBody, transferBody, closeBody, additionalContextPayloadSchema, kanbanUpdateBody, observabilityParams, observabilityListParams, deleteUserConversationsBody, sendMessageBody, listConversationsQuery, getConversationDetailParams, getConversationDetailQuery, listWhatsAppTemplatesQuery, sendWhatsAppTemplateBody, billingOrgIdParam, subscriptionItemParams, spendCapBody, growthPacksBody, portalSessionBody, checkoutSessionBody, updateBillingCurrencyBody, invoicesQuery, downloadBody, uploadQuery, signUrlBody, completionsBody, outboundSchemaQuery, templateIdParam, listTemplatesQuery, deleteTemplateQuery, templateStatusQuery, createTemplateBody, updateTemplateBody, submitTemplateBody, testTemplateBody, contactIdParam, listContactsQuery, createContactBody, updateContactBody, listIdParam, listListsQuery, listListContactsQuery, createListBody, updateListBody, addListContactsBody, removeListContactsBody, scheduleIdParam, listSchedulesQuery, listExecutionsQuery, createScheduleBody, updateScheduleBody, uuidPattern, ciUuidSchema, hubAsCodeConfigSchema, ciPullHubIdParam, ciBranchParam, ciPullQuery, ciHubsQuery, ciLookupQuery, ciBranchesQuery, ciSyncBody, ciPushBody, ciDiffBody, ciPublishBody, ciPublishPreviewBody, ciSyncMcpBody, ciOrgResourcesParam, orgResourcesConfigSchema, ciOrgResourcesPushBody, ciOrgResourcesDiffBody, adminOrgIdParam, adminOrgAdminIdParam, adminUserIdParam, adminPlanIdParam, adminOrganizationsQuery, adminUserSearchQuery, kanbanSlugMigrationQuery, adjustQuotaBody, updatePlanBody, addAdminUserBody, updatePlatformConfigBody, updateFreeOrgLimitBody, updatePricingPlanBody, dataExplorerSearchQuery, dataExplorerOrgIdParam, dataExplorerHubIdParam, dataExplorerConvIdParam, dataExplorerDoInstancesQuery, dataExplorerNsIdParam, dataExplorerKvKeysQuery, dataExplorerKvKeyParam, uuidOrHexId, dataExplorerDeleteOrgParam, dataExplorerDeleteHubParam, dataExplorerDeleteConvParam, dataExplorerDeleteUserParam, dataExplorerDeleteQuery, dataExplorerKvDeleteQuery, pitrDoNamespace, pitrBookmarkParam, pitrRestoreBody, healthSamplingTriggerBody, dataExplorerDebugDoType, DEBUG_READ_MAX_LIMIT, DEBUG_READ_DEFAULT_LIMIT, DEBUG_DO_HEX_PREFIX, debugDoEntityId, debugTableName, dataExplorerDebugTablesParam, dataExplorerDebugRowsParam, dataExplorerDebugRowsQuery, dataExplorerDebugAnalyticsTable, debugUuid, dataExplorerDebugAnalyticsRowsParam, dataExplorerDebugAnalyticsRowsQuery, debugHubConvParam, debugMessageIdQuery, whatsappCallbackBody, authMeResponse, wsTicketResponse, sessionCheckResponse, logoutResponse, magicCodeSendBody, magicCodeSendResponse, magicCodeVerifyBody, magicCodeVerifyResponse, browserParams, browserFoldersQuery, browserFilesQuery, stateOperationBody, conversationListResourcesQuery, conversationReadResourceQuery, listPendingSchedulesQuery, listHubAggregateSchedulesQuery, reportSourceSchema, intakeReportSourceSchema, reportClassificationSchema, reportStatusSchema, reportLocaleSchema, reportMessageAuthorRoleSchema, sentryRefStatusSchema, createReportBody, editReportBody, contestReportBody, reporterListQuery, listReportsQuery, GITHUB_ISSUE_URL_REGEX, githubIssueUrlSchema, transitionReportBody, groupReportsBody, holdReportBody, clickhouseReadyResponse, bootstrapQuery, requestSnapshotChunkMessage, MAX_NAME_LENGTH, MAX_VALUE_LENGTH, MAX_TAGS, MAX_TAG_LENGTH, MAX_SCOPE_HUBS, MAX_SCOPE_TAGS, vaultSecretScopeSchema, vaultCreateBody, vaultUpdateBody, PERMISSIONS, MAX_NAME_LENGTH2, MAX_HUBS_PER_GRANT, MAX_HUB_TAGS_PER_GRANT, MAX_GRANTS, permissionEnum, grantScopeSchema, grantSchema, createTokenBody, tokenOrgIdParam, tokenGrantScopeSchema, tokenGrantSchema, tokenMetadataSchema, listTokensResponse, createTokenResponse, orgTokenSummarySchema, listOrgTokensResponse, invitePreviewQuery, invitePreviewResponse, discordTier, discordBillingCycle, discordMetadata, discordOAuthStartResponse, discordOAuthFinalizeBody, discordOAuthFinalizeResponse, discordDisconnectResponse, jsonSchemaSchema, hubUserIdentity, stateValueEntry, getHubUserContextResponse, updateHubUserBody, updateHubUserResponse, unlockHubUserNameResponse, stateResetParams, stateResetResponse, hubAlertsParam, hubAlertSeverity, hubAlert, hubAlertsListResponse, noticeSeveritySchema, noticeStatusSchema, NOTICE_SCOPE_REGEX, noticeScopeSchema, noticeLinkSchema, createNoticeBody, updateNoticeBody, listNoticesQuery, noticeIdParamSchema, TEMPLATE_SLUG_REGEX, templateSlugSchema, templateStatusSchema, templateFileMapSchema, templateWayaiBlockSchema, templateRekorBlockSchema, templateYamlWayaiBlock, templateYamlRekorBlock, templateYamlSchema, templatePushMetadataSchema, templateHeroSchema, templatePushBody, templateSlugParam, WAYAI_WORKSPACE_LAYOUT, MAX_RESOURCE_FILE_SIZE;
5128
5206
  var init_dist = __esm({
5129
5207
  "../../packages/core/dist/index.js"() {
5130
5208
  "use strict";
@@ -5172,6 +5250,7 @@ var init_dist = __esm({
5172
5250
  init_zod();
5173
5251
  init_zod();
5174
5252
  init_zod();
5253
+ init_zod();
5175
5254
  APP_ERROR_BRAND = /* @__PURE__ */ Symbol.for("wayai.AppError");
5176
5255
  AppError = class extends Error {
5177
5256
  constructor(message, code, statusCode = 500, details) {
@@ -5417,6 +5496,7 @@ var init_dist = __esm({
5417
5496
  INVISIBLE_NAME_CHARS = /[\s\p{Default_Ignorable_Code_Point}]/gu;
5418
5497
  MAX_KANBAN_STATUSES = 50;
5419
5498
  MAX_FOLLOWUPS_PER_STATUS = 20;
5499
+ MAX_LANES = 20;
5420
5500
  MAX_ADDITIONAL_CONTEXT_SCHEMA_BYTES = 16384;
5421
5501
  RESERVED_TEMPLATE_DUMP_NAME = "additional_data";
5422
5502
  kanbanStatusSlugSchema = slugSchema;
@@ -5473,7 +5553,14 @@ var init_dist = __esm({
5473
5553
  /** Slugs this status may transition to. `undefined` = unrestricted (any
5474
5554
  * next status). Empty array `[]` is rejected — author must use
5475
5555
  * `isTerminalStatus: true` to express "no outbound transitions". */
5476
- allowed_next_statuses: external_exports.array(external_exports.string()).min(1, "allowed_next_statuses cannot be empty \u2014 use isTerminalStatus: true for no outbound transitions").optional()
5556
+ allowed_next_statuses: external_exports.array(external_exports.string()).min(1, "allowed_next_statuses cannot be empty \u2014 use isTerminalStatus: true for no outbound transitions").optional(),
5557
+ /** Optional presentational grouping — the slug of a hub `lanes[]` entry this
5558
+ * status belongs to. `undefined` = ungrouped. Purely a board-filter/organization
5559
+ * concept; it does NOT constrain transitions (those live in `allowed_next_statuses`)
5560
+ * and is not exposed to agents. The cross-reference invariant (must point at a
5561
+ * declared lane; the initial status must not have one) is enforced at the hub
5562
+ * level via `validateLaneReferences` — this schema only sees the status array. */
5563
+ lane_slug: kanbanStatusSlugSchema.optional()
5477
5564
  }).passthrough().superRefine((status, ctx) => {
5478
5565
  const label = status.name ? `"${status.name}"` : status.slug ? `"${status.slug}"` : "<unnamed>";
5479
5566
  for (const [a, b] of EXCLUSIVE_FLAG_PAIRS) {
@@ -5564,6 +5651,28 @@ var init_dist = __esm({
5564
5651
  });
5565
5652
  });
5566
5653
  });
5654
+ laneSchema = external_exports.object({
5655
+ slug: kanbanStatusSlugSchema,
5656
+ name: external_exports.string().min(1, "name is required").max(100, "name must be 100 characters or fewer").refine((v) => visibleLength(v) > 0, "name must not be blank"),
5657
+ color: external_exports.string().optional(),
5658
+ order: external_exports.number().int().nonnegative().optional()
5659
+ }).passthrough();
5660
+ lanesArraySchema = external_exports.array(laneSchema).max(MAX_LANES, `lanes must have at most ${MAX_LANES} entries`).superRefine((lanes, ctx) => {
5661
+ const seen = /* @__PURE__ */ new Map();
5662
+ lanes.forEach((l, i) => {
5663
+ if (!l.slug) return;
5664
+ const arr = seen.get(l.slug) ?? [];
5665
+ arr.push(i);
5666
+ seen.set(l.slug, arr);
5667
+ });
5668
+ for (const [slug, indices] of seen) {
5669
+ if (indices.length > 1) {
5670
+ for (const i of indices) {
5671
+ ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: [i, "slug"], message: `lanes: duplicate slug "${slug}"` });
5672
+ }
5673
+ }
5674
+ }
5675
+ });
5567
5676
  hubIdParam = external_exports.object({ id: uuidSchema });
5568
5677
  hubAdminIdParam = external_exports.object({ id: uuidSchema, adminId: uuidSchema });
5569
5678
  hubUserIdParam = external_exports.object({ id: uuidSchema, hubUserId: uuidSchema });
@@ -5576,14 +5685,16 @@ var init_dist = __esm({
5576
5685
  hub_name: external_exports.string().min(1, "hub_name is required"),
5577
5686
  hub_description: external_exports.string().optional(),
5578
5687
  kanban_statuses: kanbanStatusesArraySchema.nullable().optional(),
5688
+ lanes: lanesArraySchema.nullable().optional(),
5579
5689
  // Write-once at creation; updateHubBody does not accept this.
5580
5690
  primary_region: primaryRegionSchema.optional()
5581
- }).passthrough();
5691
+ }).passthrough().superRefine(refineLaneReferences);
5582
5692
  updateHubBody = external_exports.object({
5583
5693
  support_model: external_exports.enum(["conversation", "account"]).optional(),
5584
5694
  unassigned_policy: external_exports.enum(["default_team", "auto_assign_random", "auto_assign_round_robin", "pull_queue"]).optional(),
5585
5695
  default_hub_team_id: uuidSchema.nullable().optional(),
5586
5696
  kanban_statuses: kanbanStatusesArraySchema.nullable().optional(),
5697
+ lanes: lanesArraySchema.nullable().optional(),
5587
5698
  // summarization_threshold_tokens relocated to the summarizer AGENT (PR4) — validated/
5588
5699
  // persisted via the agent path (.passthrough() agent body → HubDO column), not here.
5589
5700
  auto_close_inactive_days: external_exports.number().int().min(1).max(180).optional(),
@@ -5592,7 +5703,7 @@ var init_dist = __esm({
5592
5703
  language: external_exports.enum(["en", "pt", "es"]).optional(),
5593
5704
  access_request_message: external_exports.string().max(1e3).nullable().optional(),
5594
5705
  access_approval_role: external_exports.enum(["admin", "team"]).optional()
5595
- }).passthrough();
5706
+ }).passthrough().superRefine(refineLaneReferences);
5596
5707
  addHubAdminBody = external_exports.object({
5597
5708
  user_email: external_exports.string().email("valid email is required")
5598
5709
  });
@@ -6768,6 +6879,7 @@ var init_dist = __esm({
6768
6879
  }).passthrough().superRefine((config, ctx) => {
6769
6880
  refineHubAsCodeCustomTools(config, ctx);
6770
6881
  refineHubAsCodeKanbanStatuses(config, ctx);
6882
+ refineHubAsCodeLanes(config, ctx);
6771
6883
  });
6772
6884
  ciPullHubIdParam = external_exports.object({
6773
6885
  hub_id: ciUuidSchema
@@ -7439,6 +7551,62 @@ var init_dist = __esm({
7439
7551
  offset: external_exports.coerce.number().int().min(0).optional()
7440
7552
  });
7441
7553
  noticeIdParamSchema = external_exports.object({ id: external_exports.string().min(1) });
7554
+ TEMPLATE_SLUG_REGEX = /^[a-z0-9][a-z0-9-]{0,62}$/;
7555
+ templateSlugSchema = external_exports.string().regex(TEMPLATE_SLUG_REGEX, "slug must be lowercase alphanumeric with hyphens (max 63)");
7556
+ templateStatusSchema = external_exports.enum(["published", "draft", "internal"]);
7557
+ templateFileMapSchema = external_exports.record(external_exports.string()).refine((m) => Object.keys(m).every(isSafeTemplatePath), {
7558
+ message: 'file map contains an unsafe path (no absolute paths, "..", or backslashes)'
7559
+ });
7560
+ templateWayaiBlockSchema = external_exports.object({
7561
+ metaTitle: external_exports.string().min(1).max(200),
7562
+ metaDescription: external_exports.string().max(1e3).default(""),
7563
+ installCommand: external_exports.string().min(1).max(300)
7564
+ });
7565
+ templateRekorBlockSchema = templateWayaiBlockSchema.extend({
7566
+ mcpEndpoint: external_exports.string().url().optional(),
7567
+ url: external_exports.string().url().optional()
7568
+ });
7569
+ templateYamlWayaiBlock = templateWayaiBlockSchema.extend({
7570
+ source: external_exports.string().min(1)
7571
+ });
7572
+ templateYamlRekorBlock = templateRekorBlockSchema.extend({
7573
+ source: external_exports.string().min(1)
7574
+ });
7575
+ templateYamlSchema = external_exports.object({
7576
+ slug: templateSlugSchema,
7577
+ category: external_exports.string().min(1).max(60),
7578
+ status: templateStatusSchema.default("draft"),
7579
+ order: external_exports.number().int().min(0).max(1e5).optional(),
7580
+ hero: external_exports.string().optional(),
7581
+ // relative path to a hero image inside the template folder
7582
+ wayai: templateYamlWayaiBlock.optional(),
7583
+ rekor: templateYamlRekorBlock.optional()
7584
+ }).refine((c) => Boolean(c.wayai) || Boolean(c.rekor), {
7585
+ message: "template.yaml must define at least one of `wayai:` or `rekor:`"
7586
+ });
7587
+ templatePushMetadataSchema = external_exports.object({
7588
+ category: external_exports.string().min(1).max(60),
7589
+ status: templateStatusSchema,
7590
+ order: external_exports.number().int().min(0).max(1e5).default(0),
7591
+ wayai: templateWayaiBlockSchema.optional(),
7592
+ rekor: templateRekorBlockSchema.optional()
7593
+ });
7594
+ templateHeroSchema = external_exports.object({
7595
+ filename: external_exports.string().min(1).max(200).regex(/\.(png|jpe?g|webp|gif)$/i, "hero must be a raster image (png/jpg/webp/gif)"),
7596
+ // ~8MB of base64 ≈ a ~6MB image — a generous cap that bounds the inline payload
7597
+ // (the endpoint is platform-admin-only, but an unbounded blob is still avoidable).
7598
+ dataBase64: external_exports.string().min(1).max(8e6)
7599
+ });
7600
+ templatePushBody = external_exports.object({
7601
+ slug: templateSlugSchema,
7602
+ metadata: templatePushMetadataSchema,
7603
+ // wayai slice file-map (path -> body); ABSENT for a Rekor-only template.
7604
+ wayaiFiles: templateFileMapSchema.optional(),
7605
+ // raw README markdown; the backend renders + sanitizes (single authority).
7606
+ readmeMarkdown: external_exports.string().default(""),
7607
+ hero: templateHeroSchema.optional()
7608
+ });
7609
+ templateSlugParam = external_exports.object({ slug: templateSlugSchema });
7442
7610
  WAYAI_WORKSPACE_LAYOUT = {
7443
7611
  /** Product-namespaced root dir at the git root. */
7444
7612
  wsDir: "wayai-ws",
@@ -7490,6 +7658,9 @@ function resolveLayout(gitRoot, layout = WAYAI_LAYOUT) {
7490
7658
  legacyAlsoPresent: false
7491
7659
  };
7492
7660
  }
7661
+ function resolveTemplatesDir(gitRoot) {
7662
+ return path.join(gitRoot, TEMPLATES_AUTHORING_SUBDIR);
7663
+ }
7493
7664
  function hubsDirLabel(gitRoot) {
7494
7665
  if (!gitRoot) return path.join(WAYAI_LAYOUT.wsDir, WAYAI_LAYOUT.hubsSubdir);
7495
7666
  return path.relative(gitRoot, resolveLayout(gitRoot).hubsDir);
@@ -7515,7 +7686,7 @@ function warnLayoutOnce(gitRoot) {
7515
7686
  );
7516
7687
  }
7517
7688
  }
7518
- var WAYAI_LAYOUT, _layoutWarned;
7689
+ var WAYAI_LAYOUT, TEMPLATES_AUTHORING_SUBDIR, _layoutWarned;
7519
7690
  var init_layout = __esm({
7520
7691
  "src/lib/layout.ts"() {
7521
7692
  "use strict";
@@ -7524,6 +7695,7 @@ var init_layout = __esm({
7524
7695
  ...WAYAI_WORKSPACE_LAYOUT,
7525
7696
  legacy: { wsDir: "workspace", orgAtRoot: "org" }
7526
7697
  };
7698
+ TEMPLATES_AUTHORING_SUBDIR = "templates";
7527
7699
  _layoutWarned = false;
7528
7700
  }
7529
7701
  });
@@ -7814,24 +7986,24 @@ import * as path4 from "path";
7814
7986
  import * as readline from "readline";
7815
7987
  function prompt(question) {
7816
7988
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
7817
- return new Promise((resolve4) => {
7989
+ return new Promise((resolve5) => {
7818
7990
  rl.question(question, (answer) => {
7819
7991
  rl.close();
7820
- resolve4(answer.trim());
7992
+ resolve5(answer.trim());
7821
7993
  });
7822
7994
  });
7823
7995
  }
7824
7996
  function confirm(question) {
7825
7997
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
7826
- return new Promise((resolve4) => {
7998
+ return new Promise((resolve5) => {
7827
7999
  rl.question(`${question} [y/N]: `, (answer) => {
7828
8000
  rl.close();
7829
- resolve4(answer.trim().toLowerCase() === "y");
8001
+ resolve5(answer.trim().toLowerCase() === "y");
7830
8002
  });
7831
8003
  });
7832
8004
  }
7833
8005
  function promptSecret(question) {
7834
- return new Promise((resolve4) => {
8006
+ return new Promise((resolve5) => {
7835
8007
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
7836
8008
  const originalWrite = rl._writeToOutput;
7837
8009
  let firstWrite = true;
@@ -7847,18 +8019,18 @@ function promptSecret(question) {
7847
8019
  rl._writeToOutput = originalWrite;
7848
8020
  process.stdout.write("\n");
7849
8021
  rl.close();
7850
- resolve4(answer);
8022
+ resolve5(answer);
7851
8023
  });
7852
8024
  });
7853
8025
  }
7854
8026
  function readStdin() {
7855
- return new Promise((resolve4, reject) => {
8027
+ return new Promise((resolve5, reject) => {
7856
8028
  let data = "";
7857
8029
  process.stdin.setEncoding("utf-8");
7858
8030
  process.stdin.on("data", (chunk) => {
7859
8031
  data += chunk;
7860
8032
  });
7861
- process.stdin.on("end", () => resolve4(data.trim()));
8033
+ process.stdin.on("end", () => resolve5(data.trim()));
7862
8034
  process.stdin.on("error", reject);
7863
8035
  });
7864
8036
  }
@@ -8019,9 +8191,9 @@ function getVersionCachePath(filename = CLI_CACHE_FILE) {
8019
8191
  }
8020
8192
  function readVersionCache(filename = CLI_CACHE_FILE) {
8021
8193
  try {
8022
- const path19 = getVersionCachePath(filename);
8023
- if (!existsSync4(path19)) return null;
8024
- const parsed = JSON.parse(readFileSync5(path19, "utf-8"));
8194
+ const path22 = getVersionCachePath(filename);
8195
+ if (!existsSync4(path22)) return null;
8196
+ const parsed = JSON.parse(readFileSync5(path22, "utf-8"));
8025
8197
  if (typeof parsed.lastCheck !== "number") return null;
8026
8198
  if (parsed.latest !== null && typeof parsed.latest !== "string") return null;
8027
8199
  return parsed;
@@ -8041,10 +8213,10 @@ function isVersionCacheStale(filename = CLI_CACHE_FILE, maxAgeMs = MAX_AGE_24H_M
8041
8213
  return Date.now() - cache.lastCheck > maxAgeMs;
8042
8214
  }
8043
8215
  function writeVersionCache(filename, cache) {
8044
- const path19 = getVersionCachePath(filename);
8045
- const dir = dirname3(path19);
8216
+ const path22 = getVersionCachePath(filename);
8217
+ const dir = dirname3(path22);
8046
8218
  if (!existsSync4(dir)) mkdirSync(dir, { recursive: true });
8047
- writeFileSync2(path19, JSON.stringify(cache));
8219
+ writeFileSync2(path22, JSON.stringify(cache));
8048
8220
  }
8049
8221
  function touchVersionCache(filename) {
8050
8222
  writeVersionCache(filename, { lastCheck: Date.now(), latest: readVersionCache(filename)?.latest ?? null });
@@ -8079,14 +8251,14 @@ function parseFrontmatterVersion(content) {
8079
8251
  function findInstalledSkills(projectRoot) {
8080
8252
  const found = [];
8081
8253
  for (const rel of SKILL_INSTALL_PATHS) {
8082
- const path19 = join7(projectRoot, rel);
8083
- if (!existsSync5(path19)) continue;
8254
+ const path22 = join7(projectRoot, rel);
8255
+ if (!existsSync5(path22)) continue;
8084
8256
  let version = null;
8085
8257
  try {
8086
- version = parseFrontmatterVersion(readFileSync6(path19, "utf-8"));
8258
+ version = parseFrontmatterVersion(readFileSync6(path22, "utf-8"));
8087
8259
  } catch {
8088
8260
  }
8089
- found.push({ path: path19, version });
8261
+ found.push({ path: path22, version });
8090
8262
  }
8091
8263
  return found;
8092
8264
  }
@@ -8516,7 +8688,7 @@ async function validateToken(apiUrl, token) {
8516
8688
  }
8517
8689
  }
8518
8690
  function startCallbackServer(port, expectedState, timeoutMs = 12e4) {
8519
- return new Promise((resolve4, reject) => {
8691
+ return new Promise((resolve5, reject) => {
8520
8692
  const server = http.createServer((req, res) => {
8521
8693
  const url = new URL(req.url || "/", `http://127.0.0.1:${port}`);
8522
8694
  if (url.pathname === "/callback") {
@@ -8542,7 +8714,7 @@ function startCallbackServer(port, expectedState, timeoutMs = 12e4) {
8542
8714
  res.writeHead(200, { "Content-Type": "text/html" });
8543
8715
  res.end("<html><body><h2>Login successful!</h2><p>You can close this tab and return to your terminal.</p></body></html>");
8544
8716
  server.close();
8545
- resolve4({ code, port });
8717
+ resolve5({ code, port });
8546
8718
  } else {
8547
8719
  res.writeHead(400, { "Content-Type": "text/html" });
8548
8720
  res.end("<html><body><h2>Login failed</h2><p>No authorization code received</p></body></html>");
@@ -8707,10 +8879,10 @@ import * as readline2 from "readline";
8707
8879
  function prompt2(question, defaultValue) {
8708
8880
  const rl = readline2.createInterface({ input: process.stdin, output: process.stdout });
8709
8881
  const display = defaultValue ? `${question} [${defaultValue}]: ` : `${question}: `;
8710
- return new Promise((resolve4) => {
8882
+ return new Promise((resolve5) => {
8711
8883
  rl.question(display, (answer) => {
8712
8884
  rl.close();
8713
- resolve4(answer.trim() || defaultValue || "");
8885
+ resolve5(answer.trim() || defaultValue || "");
8714
8886
  });
8715
8887
  });
8716
8888
  }
@@ -8828,8 +9000,8 @@ async function tryOpenBrowser(url) {
8828
9000
  cmd = "xdg-open";
8829
9001
  args2 = [url];
8830
9002
  }
8831
- return new Promise((resolve4) => {
8832
- execFile(cmd, args2, (err) => resolve4(!err));
9003
+ return new Promise((resolve5) => {
9004
+ execFile(cmd, args2, (err) => resolve5(!err));
8833
9005
  });
8834
9006
  } catch {
8835
9007
  return false;
@@ -11948,7 +12120,7 @@ Timeout after ${timeoutSeconds}s. Session is still running.`);
11948
12120
  console.log(`Check results: wayai eval-results --session ${sessionId}`);
11949
12121
  }
11950
12122
  function sleep(ms) {
11951
- return new Promise((resolve4) => setTimeout(resolve4, ms));
12123
+ return new Promise((resolve5) => setTimeout(resolve5, ms));
11952
12124
  }
11953
12125
  function extractApiMessage(err) {
11954
12126
  if (err instanceof ApiError) {
@@ -12531,6 +12703,38 @@ function normalizeAuthType2(raw) {
12531
12703
  function visibleLength2(s) {
12532
12704
  return s.replace(INVISIBLE_NAME_CHARS2, "").length;
12533
12705
  }
12706
+ function validateLaneReferences2(lanes, statuses) {
12707
+ const issues = [];
12708
+ if (!Array.isArray(statuses) || statuses.length === 0) return issues;
12709
+ const declared = new Set((Array.isArray(lanes) ? lanes : []).map((l) => l.slug).filter(Boolean));
12710
+ statuses.forEach((s, i) => {
12711
+ const lane = s.lane_slug;
12712
+ if (typeof lane !== "string" || lane.length === 0) return;
12713
+ const label = s.slug ? `"${s.slug}"` : `[${i}]`;
12714
+ if (s.isInitialStatus === true) {
12715
+ issues.push({
12716
+ path: [i, "lane_slug"],
12717
+ message: `kanban status ${label}: the initial status cannot belong to a lane (it is the shared entry point)`
12718
+ });
12719
+ return;
12720
+ }
12721
+ if (!declared.has(lane)) {
12722
+ issues.push({
12723
+ path: [i, "lane_slug"],
12724
+ message: `kanban status ${label}: lane_slug references unknown lane "${lane}"`
12725
+ });
12726
+ }
12727
+ });
12728
+ return issues;
12729
+ }
12730
+ function refineLaneReferences2(val, ctx) {
12731
+ const lanes = val.lanes;
12732
+ const statuses = val.kanban_statuses;
12733
+ if (lanes == null || statuses == null) return;
12734
+ for (const issue of validateLaneReferences2(lanes, statuses)) {
12735
+ ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["kanban_statuses", ...issue.path], message: issue.message });
12736
+ }
12737
+ }
12534
12738
  function containsKanbanPlaceholders2(value) {
12535
12739
  if (value == null) return false;
12536
12740
  const serialized = typeof value === "string" ? value : JSON.stringify(value);
@@ -12562,12 +12766,12 @@ function typeMatches2(typeField, allowed) {
12562
12766
  if (Array.isArray(typeField)) return typeField.every((t) => typeof t === "string" && allowed.has(t));
12563
12767
  return false;
12564
12768
  }
12565
- function validateSchema2(schema, path19, errors, opts = {}) {
12769
+ function validateSchema2(schema, path22, errors, opts = {}) {
12566
12770
  if (typeof schema === "boolean") return;
12567
12771
  const depth = opts.depth ?? 0;
12568
12772
  if (depth > MAX_SCHEMA_DEPTH2) {
12569
12773
  errors.push({
12570
- path: path19 || "<root>",
12774
+ path: path22 || "<root>",
12571
12775
  message: `schema nesting exceeds ${MAX_SCHEMA_DEPTH2} levels`,
12572
12776
  suggestion: "Flatten the schema; LLM providers reject deeply nested tool input_schemas."
12573
12777
  });
@@ -12575,7 +12779,7 @@ function validateSchema2(schema, path19, errors, opts = {}) {
12575
12779
  }
12576
12780
  if (!isRecord2(schema)) {
12577
12781
  errors.push({
12578
- path: path19,
12782
+ path: path22,
12579
12783
  message: `expected object, got ${schema === null ? "null" : typeof schema}`
12580
12784
  });
12581
12785
  return;
@@ -12583,14 +12787,14 @@ function validateSchema2(schema, path19, errors, opts = {}) {
12583
12787
  if (opts.isRoot) {
12584
12788
  if ("type" in schema && schema.type !== "object") {
12585
12789
  errors.push({
12586
- path: path19 ? `${path19}.type` : "type",
12790
+ path: path22 ? `${path22}.type` : "type",
12587
12791
  message: `root type must be 'object', got ${JSON.stringify(schema.type)}`,
12588
12792
  suggestion: "Tool parameters at the root must be an object: `{ type: 'object', properties: { ... } }`."
12589
12793
  });
12590
12794
  }
12591
12795
  } else if ("type" in schema && !typeMatches2(schema.type, ALLOWED_TYPES2)) {
12592
12796
  errors.push({
12593
- path: `${path19}.type`,
12797
+ path: `${path22}.type`,
12594
12798
  message: `type must be one of ${[...ALLOWED_TYPES2].join("/")} or an array of those, got ${JSON.stringify(schema.type)}`
12595
12799
  });
12596
12800
  }
@@ -12599,25 +12803,25 @@ function validateSchema2(schema, path19, errors, opts = {}) {
12599
12803
  if (typeof e === "string") {
12600
12804
  const isPlaceholder = PLACEHOLDER_TOKENS2.includes(e);
12601
12805
  errors.push({
12602
- path: `${path19}.enum`,
12806
+ path: `${path22}.enum`,
12603
12807
  message: `enum must be a non-empty array of primitives, got string ${JSON.stringify(e)}`,
12604
12808
  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"]`.'
12605
12809
  });
12606
12810
  } else if (!Array.isArray(e)) {
12607
12811
  errors.push({
12608
- path: `${path19}.enum`,
12812
+ path: `${path22}.enum`,
12609
12813
  message: `enum must be a non-empty array of primitives, got ${typeof e}`
12610
12814
  });
12611
12815
  } else if (e.length === 0) {
12612
12816
  errors.push({
12613
- path: `${path19}.enum`,
12817
+ path: `${path22}.enum`,
12614
12818
  message: "enum must not be empty"
12615
12819
  });
12616
12820
  } else {
12617
12821
  for (let i = 0; i < e.length; i++) {
12618
12822
  if (!isPrimitive2(e[i])) {
12619
12823
  errors.push({
12620
- path: `${path19}.enum[${i}]`,
12824
+ path: `${path22}.enum[${i}]`,
12621
12825
  message: `enum entry must be a primitive (string/number/boolean/null), got ${typeof e[i]}`
12622
12826
  });
12623
12827
  }
@@ -12627,7 +12831,7 @@ function validateSchema2(schema, path19, errors, opts = {}) {
12627
12831
  for (const key of ["exclusiveMinimum", "exclusiveMaximum"]) {
12628
12832
  if (key in schema && typeof schema[key] === "boolean") {
12629
12833
  errors.push({
12630
- path: `${path19}.${key}`,
12834
+ path: `${path22}.${key}`,
12631
12835
  message: `${key} as boolean is draft-04 syntax; draft 2020-12 requires a numeric value`,
12632
12836
  suggestion: `Replace with the numeric bound, e.g. \`${key}: 0\`.`
12633
12837
  });
@@ -12636,45 +12840,45 @@ function validateSchema2(schema, path19, errors, opts = {}) {
12636
12840
  if ("properties" in schema) {
12637
12841
  if (!isRecord2(schema.properties)) {
12638
12842
  errors.push({
12639
- path: `${path19}.properties`,
12843
+ path: `${path22}.properties`,
12640
12844
  message: `properties must be an object, got ${Array.isArray(schema.properties) ? "array" : typeof schema.properties}`
12641
12845
  });
12642
12846
  } else {
12643
12847
  for (const [propName, propSchema] of Object.entries(schema.properties)) {
12644
- validateSchema2(propSchema, `${path19}.properties.${propName}`, errors, { depth: depth + 1 });
12848
+ validateSchema2(propSchema, `${path22}.properties.${propName}`, errors, { depth: depth + 1 });
12645
12849
  }
12646
12850
  }
12647
12851
  }
12648
12852
  if (schemaTypeIncludes2(schema, "array") && "items" in schema) {
12649
12853
  if (Array.isArray(schema.items)) {
12650
- schema.items.forEach((sub, i) => validateSchema2(sub, `${path19}.items[${i}]`, errors, { depth: depth + 1 }));
12854
+ schema.items.forEach((sub, i) => validateSchema2(sub, `${path22}.items[${i}]`, errors, { depth: depth + 1 }));
12651
12855
  } else {
12652
- validateSchema2(schema.items, `${path19}.items`, errors, { depth: depth + 1 });
12856
+ validateSchema2(schema.items, `${path22}.items`, errors, { depth: depth + 1 });
12653
12857
  }
12654
12858
  }
12655
12859
  for (const key of SUBSCHEMA_OBJECT_KEYWORDS2) {
12656
12860
  if (key in schema && isRecord2(schema[key])) {
12657
- validateSchema2(schema[key], `${path19}.${key}`, errors, { depth: depth + 1 });
12861
+ validateSchema2(schema[key], `${path22}.${key}`, errors, { depth: depth + 1 });
12658
12862
  }
12659
12863
  }
12660
12864
  for (const key of SUBSCHEMA_LIST_KEYWORDS2) {
12661
12865
  const list = schema[key];
12662
12866
  if (Array.isArray(list)) {
12663
- list.forEach((sub, i) => validateSchema2(sub, `${path19}.${key}[${i}]`, errors, { depth: depth + 1 }));
12867
+ list.forEach((sub, i) => validateSchema2(sub, `${path22}.${key}[${i}]`, errors, { depth: depth + 1 }));
12664
12868
  }
12665
12869
  }
12666
12870
  for (const key of SUBSCHEMA_MAP_KEYWORDS2) {
12667
12871
  const map = schema[key];
12668
12872
  if (isRecord2(map)) {
12669
12873
  for (const [name, sub] of Object.entries(map)) {
12670
- validateSchema2(sub, `${path19}.${key}.${name}`, errors, { depth: depth + 1 });
12874
+ validateSchema2(sub, `${path22}.${key}.${name}`, errors, { depth: depth + 1 });
12671
12875
  }
12672
12876
  }
12673
12877
  }
12674
12878
  const reportedPaths = new Set(errors.map((e) => e.path));
12675
12879
  for (const [k, v] of Object.entries(schema)) {
12676
12880
  if (typeof v !== "string") continue;
12677
- const fieldPath = `${path19}.${k}`;
12881
+ const fieldPath = `${path22}.${k}`;
12678
12882
  if (reportedPaths.has(fieldPath)) continue;
12679
12883
  for (const token of PLACEHOLDER_TOKENS2) {
12680
12884
  if (v === token) {
@@ -12807,7 +13011,38 @@ function refineHubAsCodeKanbanStatuses2(config, ctx) {
12807
13011
  }
12808
13012
  }
12809
13013
  }
12810
- var uuidSchema2, paginationSchema3, timestampSchema2, idParamSchema2, primaryRegionSchema2, placementSourceSchema2, orgIdParam2, orgAdminIdParam2, createOrganizationBody2, updateOrganizationBody2, addOrgAdminBody2, AUTH_TYPE2, ORG_CREDENTIAL_AUTH_TYPES2, AUTH_TYPE_DISPLAY2, LEGACY_AUTH_TYPE_MAP2, VALID_AUTH_TYPES2, AGENT_ROLES2, SAFE_USER_ID_REGEX2, SUMMARIZATION_THRESHOLD_MIN2, SUMMARIZATION_THRESHOLD_MAX2, summarizationThresholdField2, monitorConfigField2, agentIdParam2, listAgentsQuery2, agentConnectionsQuery2, createAgentBody2, updateAgentBody2, AGENT_PARAMETER_TYPES2, AGENT_PARAMETER_NAME_REGEX2, agentParameterCreateSchema2, MAX_AGENT_PARAMETERS_PER_REQUEST2, createAgentParametersBody2, SLUG_REGEX2, slugSchema2, INVISIBLE_NAME_CHARS2, MAX_KANBAN_STATUSES2, MAX_FOLLOWUPS_PER_STATUS2, MAX_ADDITIONAL_CONTEXT_SCHEMA_BYTES2, RESERVED_TEMPLATE_DUMP_NAME2, kanbanStatusSlugSchema2, followupSchema2, EXCLUSIVE_FLAG_PAIRS2, kanbanStatusSchema2, kanbanStatusesArraySchema2, hubIdParam2, hubAdminIdParam2, hubUserIdParam2, hubIdentityIdParam2, hubTeamIdParam2, hubTeamUserDeleteParam2, hubSchemaQuery2, createHubBody2, updateHubBody2, addHubAdminBody2, addHubIdentityAuthorizationBody2, contactDecisionBody2, approveContactBody2, listContactAccessQuery2, createHubTeamBody2, updateHubTeamBody2, addHubTeamUserBody2, reassignHubUserBody2, replicatePreviewBody2, claimCodeChannelParam2, claimCodeDeleteParam2, connectionIdParam2, connectorIdParam2, listConnectionsQuery2, deleteConnectionQuery2, addConnectionBody2, editConnectionBody2, registerWhatsappQuery2, registerWhatsappBody2, retryProvisioningQuery2, resendDomainStatusQuery2, PLACEHOLDER_TOKENS2, ALLOWED_TYPES2, SUBSCHEMA_OBJECT_KEYWORDS2, SUBSCHEMA_LIST_KEYWORDS2, SUBSCHEMA_MAP_KEYWORDS2, MAX_SCHEMA_DEPTH2, toolIdParam2, listToolsQuery2, toolAgentQuery2, deleteToolQuery2, toolConnectionsQuery2, nativeToolIdSchema2, addNativeToolBody2, addMcpToolBody2, createCustomToolBody2, updateCustomToolBody2, updateToolExecutionConfigBody2, initialValueCreateSchema2, initialValueUpdateSchema2, stateIdParam2, listStatesQuery2, stateNameSchema2, createStateBody2, updateStateBody2, reorderStatesBody2, orgTagIdParam2, listOrgTagsQuery2, createOrgTagBody2, updateOrgTagBody2, orgCredentialIdParam2, listOrgCredentialsQuery2, createOrgCredentialBody2, updateOrgCredentialBody2, orgResourceIdParam2, orgResourceFileIdParam2, orgResourceFolderIdParam2, hubOrgResourceParam2, listOrgResourcesQuery2, orgIdQuery2, createOrgResourceBody2, updateOrgResourceBody2, createOrgResourceFolderBody2, updateOrgResourceFolderBody2, uploadOrgResourceFileBody2, updateOrgResourceFileBody2, uploadOrgSkillZipBody2, linkOrgResourceBody2, resourceIdParam2, listResourcesQuery2, createResourceBody2, updateResourceBody2, syncSkillsBody2, resourceFileIdParam2, listResourceFilesQuery2, createResourceFileBody2, updateResourceFileBody2, uploadResourceFileBody2, uploadSkillZipBody2, resourceFolderIdParam2, listResourceFoldersQuery2, createResourceFolderBody2, updateResourceFolderBody2, agentResourceQuery2, hubResourceQuery2, linkAgentResourceBody2, updateAgentResourceBody2, unlinkAgentResourceQuery2, navItemSchema2, hubCountResetBody2, navItemCountResetBody2, typingBody2, analyticsHubIdParam2, analyticsVariableIdParam2, updateVariablePinBody2, updateAnalyticsViewBody2, NUMERIC_FILTER_OPS2, TEXT_FILTER_OPS2, CATEGORICAL_FILTER_OPS2, VARIABLE_FILTER_OPS2, STRUCTURED_QUERY_OPS2, STRUCTURED_QUERY_AGGREGATIONS2, filterValueSchema2, conversationsBody2, analyticsConversationIdParam2, messagesQuery2, conversationDetailDataQuery2, analyticsDataBody2, structuredQueryBody2, analyticsSqlBody2, analyticsSqlSchemaQuery2, PACING_INTERVAL_MIN_MS2, PACING_INTERVAL_MAX_MS2, EVAL_RUN_DEADLINE_MIN_MS2, EVAL_RUN_DEADLINE_MAX_MS2, paginationSchema22, evalIdParam2, sessionIdParam2, scenarioSetIdParam2, hubIdQuery2, createEvalBody2, updateEvalBody2, getEvalsQuery2, toggleEvalBody2, bulkToggleEvalsBody2, pacingConfigSchema2, sessionConfigSchema2, createSessionBody2, getSessionsQuery2, getSessionResultsQuery2, getSessionRunsQuery2, evalAnalyticsQuery2, getSessionCostQuery2, compareSessionsQuery2, evalsSqlBody2, evalsSqlSchemaQuery2, exportResultsQuery2, validateEvalBody2, hubAgentsQuery2, createScenarioSetBody2, updateScenarioSetBody2, scenarioSetsHubQuery2, createScenarioFromConversationBody2, createJourneyFromConversationBody2, journeyIdParam2, journeyToolCallSchema2, journeyTurnSchema2, journeyStepOverrideSchema2, createJourneyBody2, updateJourneyBody2, getConversationsQuery2, getMessagesQuery2, updateUserBody2, updateProfileBody2, pathnameRegex2, updatePreferencesBody2, registerPushTokenBody2, deletePushTokenQuery2, activityQuery2, deletionModeSchema2, deletionRequestBody2, archiveHubIdParam2, archiveConversationParams2, archiveListQuery2, archiveMessageObservabilityParams2, conversationIdParam2, claimBody2, releaseBody2, transferBody2, closeBody2, additionalContextPayloadSchema2, kanbanUpdateBody2, observabilityParams2, observabilityListParams2, deleteUserConversationsBody2, sendMessageBody2, listConversationsQuery2, getConversationDetailParams2, getConversationDetailQuery2, listWhatsAppTemplatesQuery2, sendWhatsAppTemplateBody2, billingOrgIdParam2, subscriptionItemParams2, spendCapBody2, growthPacksBody2, portalSessionBody2, checkoutSessionBody2, updateBillingCurrencyBody2, invoicesQuery2, downloadBody2, uploadQuery2, signUrlBody2, completionsBody2, outboundSchemaQuery2, templateIdParam2, listTemplatesQuery2, deleteTemplateQuery2, templateStatusQuery2, createTemplateBody2, updateTemplateBody2, submitTemplateBody2, testTemplateBody2, contactIdParam2, listContactsQuery2, createContactBody2, updateContactBody2, listIdParam2, listListsQuery2, listListContactsQuery2, createListBody2, updateListBody2, addListContactsBody2, removeListContactsBody2, scheduleIdParam2, listSchedulesQuery2, listExecutionsQuery2, createScheduleBody2, updateScheduleBody2, uuidPattern2, ciUuidSchema2, hubAsCodeConfigSchema2, ciPullHubIdParam2, ciBranchParam2, ciPullQuery2, ciHubsQuery2, ciLookupQuery2, ciBranchesQuery2, ciSyncBody2, ciPushBody2, ciDiffBody2, ciPublishBody2, ciPublishPreviewBody2, ciSyncMcpBody2, ciOrgResourcesParam2, orgResourcesConfigSchema2, ciOrgResourcesPushBody2, ciOrgResourcesDiffBody2, adminOrgIdParam2, adminOrgAdminIdParam2, adminUserIdParam2, adminPlanIdParam2, adminOrganizationsQuery2, adminUserSearchQuery2, kanbanSlugMigrationQuery2, adjustQuotaBody2, updatePlanBody2, addAdminUserBody2, updatePlatformConfigBody2, updateFreeOrgLimitBody2, updatePricingPlanBody2, dataExplorerSearchQuery2, dataExplorerOrgIdParam2, dataExplorerHubIdParam2, dataExplorerConvIdParam2, dataExplorerDoInstancesQuery2, dataExplorerNsIdParam2, dataExplorerKvKeysQuery2, dataExplorerKvKeyParam2, uuidOrHexId2, dataExplorerDeleteOrgParam2, dataExplorerDeleteHubParam2, dataExplorerDeleteConvParam2, dataExplorerDeleteUserParam2, dataExplorerDeleteQuery2, dataExplorerKvDeleteQuery2, pitrDoNamespace2, pitrBookmarkParam2, pitrRestoreBody2, healthSamplingTriggerBody2, dataExplorerDebugDoType2, DEBUG_READ_MAX_LIMIT2, DEBUG_READ_DEFAULT_LIMIT2, DEBUG_DO_HEX_PREFIX2, debugDoEntityId2, debugTableName2, dataExplorerDebugTablesParam2, dataExplorerDebugRowsParam2, dataExplorerDebugRowsQuery2, dataExplorerDebugAnalyticsTable2, debugUuid2, dataExplorerDebugAnalyticsRowsParam2, dataExplorerDebugAnalyticsRowsQuery2, debugHubConvParam2, debugMessageIdQuery2, whatsappCallbackBody2, authMeResponse2, wsTicketResponse2, sessionCheckResponse2, logoutResponse2, magicCodeSendBody2, magicCodeSendResponse2, magicCodeVerifyBody2, magicCodeVerifyResponse2, browserParams2, browserFoldersQuery2, browserFilesQuery2, stateOperationBody2, conversationListResourcesQuery2, conversationReadResourceQuery2, listPendingSchedulesQuery2, listHubAggregateSchedulesQuery2, reportSourceSchema2, intakeReportSourceSchema2, reportClassificationSchema2, reportStatusSchema2, reportLocaleSchema2, reportMessageAuthorRoleSchema2, sentryRefStatusSchema2, createReportBody2, editReportBody2, contestReportBody2, reporterListQuery2, listReportsQuery2, GITHUB_ISSUE_URL_REGEX2, githubIssueUrlSchema2, transitionReportBody2, groupReportsBody2, holdReportBody2, clickhouseReadyResponse2, bootstrapQuery2, requestSnapshotChunkMessage2, MAX_NAME_LENGTH3, MAX_VALUE_LENGTH2, MAX_TAGS2, MAX_TAG_LENGTH2, MAX_SCOPE_HUBS2, MAX_SCOPE_TAGS2, vaultSecretScopeSchema2, vaultCreateBody2, vaultUpdateBody2, PERMISSIONS2, MAX_NAME_LENGTH22, MAX_HUBS_PER_GRANT2, MAX_HUB_TAGS_PER_GRANT2, MAX_GRANTS2, permissionEnum2, grantScopeSchema2, grantSchema2, createTokenBody2, tokenOrgIdParam2, tokenGrantScopeSchema2, tokenGrantSchema2, tokenMetadataSchema2, listTokensResponse2, createTokenResponse2, orgTokenSummarySchema2, listOrgTokensResponse2, invitePreviewQuery2, invitePreviewResponse2, discordTier2, discordBillingCycle2, discordMetadata2, discordOAuthStartResponse2, discordOAuthFinalizeBody2, discordOAuthFinalizeResponse2, discordDisconnectResponse2, jsonSchemaSchema2, hubUserIdentity2, stateValueEntry2, getHubUserContextResponse2, updateHubUserBody2, updateHubUserResponse2, unlockHubUserNameResponse2, stateResetParams2, stateResetResponse2, hubAlertsParam2, hubAlertSeverity2, hubAlert2, hubAlertsListResponse2, noticeSeveritySchema2, noticeStatusSchema2, NOTICE_SCOPE_REGEX2, noticeScopeSchema2, noticeLinkSchema2, createNoticeBody2, updateNoticeBody2, listNoticesQuery2, noticeIdParamSchema2;
13014
+ function refineHubAsCodeLanes2(config, ctx) {
13015
+ const hub = config.hub;
13016
+ if (hub == null) return;
13017
+ if ("lanes" in hub && hub.lanes != null) {
13018
+ if (!Array.isArray(hub.lanes)) {
13019
+ ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["hub", "lanes"], message: "lanes must be an array" });
13020
+ return;
13021
+ }
13022
+ const result = lanesArraySchema2.safeParse(hub.lanes);
13023
+ if (!result.success) {
13024
+ for (const issue of result.error.issues) {
13025
+ ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["hub", "lanes", ...issue.path], message: issue.message });
13026
+ }
13027
+ return;
13028
+ }
13029
+ }
13030
+ const statuses = hub.kanban_statuses;
13031
+ if (Array.isArray(statuses)) {
13032
+ for (const issue of validateLaneReferences2(hub.lanes, statuses)) {
13033
+ ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["hub", "kanban_statuses", ...issue.path], message: issue.message });
13034
+ }
13035
+ }
13036
+ }
13037
+ function isSafeTemplatePath2(path22) {
13038
+ if (path22.length === 0 || path22.length > 300) return false;
13039
+ if (path22.startsWith("/") || path22.includes("\\")) return false;
13040
+ const segments = path22.split("/");
13041
+ return segments.every(
13042
+ (seg) => seg.length > 0 && seg !== "." && seg !== ".." && /^[A-Za-z0-9._-]+$/.test(seg)
13043
+ );
13044
+ }
13045
+ var uuidSchema2, paginationSchema3, timestampSchema2, idParamSchema2, primaryRegionSchema2, placementSourceSchema2, orgIdParam2, orgAdminIdParam2, createOrganizationBody2, updateOrganizationBody2, addOrgAdminBody2, AUTH_TYPE2, ORG_CREDENTIAL_AUTH_TYPES2, AUTH_TYPE_DISPLAY2, LEGACY_AUTH_TYPE_MAP2, VALID_AUTH_TYPES2, AGENT_ROLES2, SAFE_USER_ID_REGEX2, SUMMARIZATION_THRESHOLD_MIN2, SUMMARIZATION_THRESHOLD_MAX2, summarizationThresholdField2, monitorConfigField2, agentIdParam2, listAgentsQuery2, agentConnectionsQuery2, createAgentBody2, updateAgentBody2, AGENT_PARAMETER_TYPES2, AGENT_PARAMETER_NAME_REGEX2, agentParameterCreateSchema2, MAX_AGENT_PARAMETERS_PER_REQUEST2, createAgentParametersBody2, SLUG_REGEX2, slugSchema2, INVISIBLE_NAME_CHARS2, MAX_KANBAN_STATUSES2, MAX_FOLLOWUPS_PER_STATUS2, MAX_LANES2, MAX_ADDITIONAL_CONTEXT_SCHEMA_BYTES2, RESERVED_TEMPLATE_DUMP_NAME2, kanbanStatusSlugSchema2, followupSchema2, EXCLUSIVE_FLAG_PAIRS2, kanbanStatusSchema2, kanbanStatusesArraySchema2, laneSchema2, lanesArraySchema2, hubIdParam2, hubAdminIdParam2, hubUserIdParam2, hubIdentityIdParam2, hubTeamIdParam2, hubTeamUserDeleteParam2, hubSchemaQuery2, createHubBody2, updateHubBody2, addHubAdminBody2, addHubIdentityAuthorizationBody2, contactDecisionBody2, approveContactBody2, listContactAccessQuery2, createHubTeamBody2, updateHubTeamBody2, addHubTeamUserBody2, reassignHubUserBody2, replicatePreviewBody2, claimCodeChannelParam2, claimCodeDeleteParam2, connectionIdParam2, connectorIdParam2, listConnectionsQuery2, deleteConnectionQuery2, addConnectionBody2, editConnectionBody2, registerWhatsappQuery2, registerWhatsappBody2, retryProvisioningQuery2, resendDomainStatusQuery2, PLACEHOLDER_TOKENS2, ALLOWED_TYPES2, SUBSCHEMA_OBJECT_KEYWORDS2, SUBSCHEMA_LIST_KEYWORDS2, SUBSCHEMA_MAP_KEYWORDS2, MAX_SCHEMA_DEPTH2, toolIdParam2, listToolsQuery2, toolAgentQuery2, deleteToolQuery2, toolConnectionsQuery2, nativeToolIdSchema2, addNativeToolBody2, addMcpToolBody2, createCustomToolBody2, updateCustomToolBody2, updateToolExecutionConfigBody2, initialValueCreateSchema2, initialValueUpdateSchema2, stateIdParam2, listStatesQuery2, stateNameSchema2, createStateBody2, updateStateBody2, reorderStatesBody2, orgTagIdParam2, listOrgTagsQuery2, createOrgTagBody2, updateOrgTagBody2, orgCredentialIdParam2, listOrgCredentialsQuery2, createOrgCredentialBody2, updateOrgCredentialBody2, orgResourceIdParam2, orgResourceFileIdParam2, orgResourceFolderIdParam2, hubOrgResourceParam2, listOrgResourcesQuery2, orgIdQuery2, createOrgResourceBody2, updateOrgResourceBody2, createOrgResourceFolderBody2, updateOrgResourceFolderBody2, uploadOrgResourceFileBody2, updateOrgResourceFileBody2, uploadOrgSkillZipBody2, linkOrgResourceBody2, resourceIdParam2, listResourcesQuery2, createResourceBody2, updateResourceBody2, syncSkillsBody2, resourceFileIdParam2, listResourceFilesQuery2, createResourceFileBody2, updateResourceFileBody2, uploadResourceFileBody2, uploadSkillZipBody2, resourceFolderIdParam2, listResourceFoldersQuery2, createResourceFolderBody2, updateResourceFolderBody2, agentResourceQuery2, hubResourceQuery2, linkAgentResourceBody2, updateAgentResourceBody2, unlinkAgentResourceQuery2, navItemSchema2, hubCountResetBody2, navItemCountResetBody2, typingBody2, analyticsHubIdParam2, analyticsVariableIdParam2, updateVariablePinBody2, updateAnalyticsViewBody2, NUMERIC_FILTER_OPS2, TEXT_FILTER_OPS2, CATEGORICAL_FILTER_OPS2, VARIABLE_FILTER_OPS2, STRUCTURED_QUERY_OPS2, STRUCTURED_QUERY_AGGREGATIONS2, filterValueSchema2, conversationsBody2, analyticsConversationIdParam2, messagesQuery2, conversationDetailDataQuery2, analyticsDataBody2, structuredQueryBody2, analyticsSqlBody2, analyticsSqlSchemaQuery2, PACING_INTERVAL_MIN_MS2, PACING_INTERVAL_MAX_MS2, EVAL_RUN_DEADLINE_MIN_MS2, EVAL_RUN_DEADLINE_MAX_MS2, paginationSchema22, evalIdParam2, sessionIdParam2, scenarioSetIdParam2, hubIdQuery2, createEvalBody2, updateEvalBody2, getEvalsQuery2, toggleEvalBody2, bulkToggleEvalsBody2, pacingConfigSchema2, sessionConfigSchema2, createSessionBody2, getSessionsQuery2, getSessionResultsQuery2, getSessionRunsQuery2, evalAnalyticsQuery2, getSessionCostQuery2, compareSessionsQuery2, evalsSqlBody2, evalsSqlSchemaQuery2, exportResultsQuery2, validateEvalBody2, hubAgentsQuery2, createScenarioSetBody2, updateScenarioSetBody2, scenarioSetsHubQuery2, createScenarioFromConversationBody2, createJourneyFromConversationBody2, journeyIdParam2, journeyToolCallSchema2, journeyTurnSchema2, journeyStepOverrideSchema2, createJourneyBody2, updateJourneyBody2, getConversationsQuery2, getMessagesQuery2, updateUserBody2, updateProfileBody2, pathnameRegex2, updatePreferencesBody2, registerPushTokenBody2, deletePushTokenQuery2, activityQuery2, deletionModeSchema2, deletionRequestBody2, archiveHubIdParam2, archiveConversationParams2, archiveListQuery2, archiveMessageObservabilityParams2, conversationIdParam2, claimBody2, releaseBody2, transferBody2, closeBody2, additionalContextPayloadSchema2, kanbanUpdateBody2, observabilityParams2, observabilityListParams2, deleteUserConversationsBody2, sendMessageBody2, listConversationsQuery2, getConversationDetailParams2, getConversationDetailQuery2, listWhatsAppTemplatesQuery2, sendWhatsAppTemplateBody2, billingOrgIdParam2, subscriptionItemParams2, spendCapBody2, growthPacksBody2, portalSessionBody2, checkoutSessionBody2, updateBillingCurrencyBody2, invoicesQuery2, downloadBody2, uploadQuery2, signUrlBody2, completionsBody2, outboundSchemaQuery2, templateIdParam2, listTemplatesQuery2, deleteTemplateQuery2, templateStatusQuery2, createTemplateBody2, updateTemplateBody2, submitTemplateBody2, testTemplateBody2, contactIdParam2, listContactsQuery2, createContactBody2, updateContactBody2, listIdParam2, listListsQuery2, listListContactsQuery2, createListBody2, updateListBody2, addListContactsBody2, removeListContactsBody2, scheduleIdParam2, listSchedulesQuery2, listExecutionsQuery2, createScheduleBody2, updateScheduleBody2, uuidPattern2, ciUuidSchema2, hubAsCodeConfigSchema2, ciPullHubIdParam2, ciBranchParam2, ciPullQuery2, ciHubsQuery2, ciLookupQuery2, ciBranchesQuery2, ciSyncBody2, ciPushBody2, ciDiffBody2, ciPublishBody2, ciPublishPreviewBody2, ciSyncMcpBody2, ciOrgResourcesParam2, orgResourcesConfigSchema2, ciOrgResourcesPushBody2, ciOrgResourcesDiffBody2, adminOrgIdParam2, adminOrgAdminIdParam2, adminUserIdParam2, adminPlanIdParam2, adminOrganizationsQuery2, adminUserSearchQuery2, kanbanSlugMigrationQuery2, adjustQuotaBody2, updatePlanBody2, addAdminUserBody2, updatePlatformConfigBody2, updateFreeOrgLimitBody2, updatePricingPlanBody2, dataExplorerSearchQuery2, dataExplorerOrgIdParam2, dataExplorerHubIdParam2, dataExplorerConvIdParam2, dataExplorerDoInstancesQuery2, dataExplorerNsIdParam2, dataExplorerKvKeysQuery2, dataExplorerKvKeyParam2, uuidOrHexId2, dataExplorerDeleteOrgParam2, dataExplorerDeleteHubParam2, dataExplorerDeleteConvParam2, dataExplorerDeleteUserParam2, dataExplorerDeleteQuery2, dataExplorerKvDeleteQuery2, pitrDoNamespace2, pitrBookmarkParam2, pitrRestoreBody2, healthSamplingTriggerBody2, dataExplorerDebugDoType2, DEBUG_READ_MAX_LIMIT2, DEBUG_READ_DEFAULT_LIMIT2, DEBUG_DO_HEX_PREFIX2, debugDoEntityId2, debugTableName2, dataExplorerDebugTablesParam2, dataExplorerDebugRowsParam2, dataExplorerDebugRowsQuery2, dataExplorerDebugAnalyticsTable2, debugUuid2, dataExplorerDebugAnalyticsRowsParam2, dataExplorerDebugAnalyticsRowsQuery2, debugHubConvParam2, debugMessageIdQuery2, whatsappCallbackBody2, authMeResponse2, wsTicketResponse2, sessionCheckResponse2, logoutResponse2, magicCodeSendBody2, magicCodeSendResponse2, magicCodeVerifyBody2, magicCodeVerifyResponse2, browserParams2, browserFoldersQuery2, browserFilesQuery2, stateOperationBody2, conversationListResourcesQuery2, conversationReadResourceQuery2, listPendingSchedulesQuery2, listHubAggregateSchedulesQuery2, reportSourceSchema2, intakeReportSourceSchema2, reportClassificationSchema2, reportStatusSchema2, reportLocaleSchema2, reportMessageAuthorRoleSchema2, sentryRefStatusSchema2, createReportBody2, editReportBody2, contestReportBody2, reporterListQuery2, listReportsQuery2, GITHUB_ISSUE_URL_REGEX2, githubIssueUrlSchema2, transitionReportBody2, groupReportsBody2, holdReportBody2, clickhouseReadyResponse2, bootstrapQuery2, requestSnapshotChunkMessage2, MAX_NAME_LENGTH3, MAX_VALUE_LENGTH2, MAX_TAGS2, MAX_TAG_LENGTH2, MAX_SCOPE_HUBS2, MAX_SCOPE_TAGS2, vaultSecretScopeSchema2, vaultCreateBody2, vaultUpdateBody2, PERMISSIONS2, MAX_NAME_LENGTH22, MAX_HUBS_PER_GRANT2, MAX_HUB_TAGS_PER_GRANT2, MAX_GRANTS2, permissionEnum2, grantScopeSchema2, grantSchema2, createTokenBody2, tokenOrgIdParam2, tokenGrantScopeSchema2, tokenGrantSchema2, tokenMetadataSchema2, listTokensResponse2, createTokenResponse2, orgTokenSummarySchema2, listOrgTokensResponse2, invitePreviewQuery2, invitePreviewResponse2, discordTier2, discordBillingCycle2, discordMetadata2, discordOAuthStartResponse2, discordOAuthFinalizeBody2, discordOAuthFinalizeResponse2, discordDisconnectResponse2, jsonSchemaSchema2, hubUserIdentity2, stateValueEntry2, getHubUserContextResponse2, updateHubUserBody2, updateHubUserResponse2, unlockHubUserNameResponse2, stateResetParams2, stateResetResponse2, hubAlertsParam2, hubAlertSeverity2, hubAlert2, hubAlertsListResponse2, noticeSeveritySchema2, noticeStatusSchema2, NOTICE_SCOPE_REGEX2, noticeScopeSchema2, noticeLinkSchema2, createNoticeBody2, updateNoticeBody2, listNoticesQuery2, noticeIdParamSchema2, TEMPLATE_SLUG_REGEX2, templateSlugSchema2, templateStatusSchema2, templateFileMapSchema2, templateWayaiBlockSchema2, templateRekorBlockSchema2, templateYamlWayaiBlock2, templateYamlRekorBlock2, templateYamlSchema2, templatePushMetadataSchema2, templateHeroSchema2, templatePushBody2, templateSlugParam2;
12811
13046
  var init_contracts = __esm({
12812
13047
  "../../packages/core/dist/contracts/index.js"() {
12813
13048
  "use strict";
@@ -12855,6 +13090,7 @@ var init_contracts = __esm({
12855
13090
  init_zod();
12856
13091
  init_zod();
12857
13092
  init_zod();
13093
+ init_zod();
12858
13094
  uuidSchema2 = external_exports.string().uuid();
12859
13095
  paginationSchema3 = external_exports.object({
12860
13096
  limit: external_exports.coerce.number().int().min(1).max(100).default(50),
@@ -13016,6 +13252,7 @@ var init_contracts = __esm({
13016
13252
  INVISIBLE_NAME_CHARS2 = /[\s\p{Default_Ignorable_Code_Point}]/gu;
13017
13253
  MAX_KANBAN_STATUSES2 = 50;
13018
13254
  MAX_FOLLOWUPS_PER_STATUS2 = 20;
13255
+ MAX_LANES2 = 20;
13019
13256
  MAX_ADDITIONAL_CONTEXT_SCHEMA_BYTES2 = 16384;
13020
13257
  RESERVED_TEMPLATE_DUMP_NAME2 = "additional_data";
13021
13258
  kanbanStatusSlugSchema2 = slugSchema2;
@@ -13072,7 +13309,14 @@ var init_contracts = __esm({
13072
13309
  /** Slugs this status may transition to. `undefined` = unrestricted (any
13073
13310
  * next status). Empty array `[]` is rejected — author must use
13074
13311
  * `isTerminalStatus: true` to express "no outbound transitions". */
13075
- allowed_next_statuses: external_exports.array(external_exports.string()).min(1, "allowed_next_statuses cannot be empty \u2014 use isTerminalStatus: true for no outbound transitions").optional()
13312
+ allowed_next_statuses: external_exports.array(external_exports.string()).min(1, "allowed_next_statuses cannot be empty \u2014 use isTerminalStatus: true for no outbound transitions").optional(),
13313
+ /** Optional presentational grouping — the slug of a hub `lanes[]` entry this
13314
+ * status belongs to. `undefined` = ungrouped. Purely a board-filter/organization
13315
+ * concept; it does NOT constrain transitions (those live in `allowed_next_statuses`)
13316
+ * and is not exposed to agents. The cross-reference invariant (must point at a
13317
+ * declared lane; the initial status must not have one) is enforced at the hub
13318
+ * level via `validateLaneReferences` — this schema only sees the status array. */
13319
+ lane_slug: kanbanStatusSlugSchema2.optional()
13076
13320
  }).passthrough().superRefine((status, ctx) => {
13077
13321
  const label = status.name ? `"${status.name}"` : status.slug ? `"${status.slug}"` : "<unnamed>";
13078
13322
  for (const [a, b] of EXCLUSIVE_FLAG_PAIRS2) {
@@ -13163,6 +13407,28 @@ var init_contracts = __esm({
13163
13407
  });
13164
13408
  });
13165
13409
  });
13410
+ laneSchema2 = external_exports.object({
13411
+ slug: kanbanStatusSlugSchema2,
13412
+ name: external_exports.string().min(1, "name is required").max(100, "name must be 100 characters or fewer").refine((v) => visibleLength2(v) > 0, "name must not be blank"),
13413
+ color: external_exports.string().optional(),
13414
+ order: external_exports.number().int().nonnegative().optional()
13415
+ }).passthrough();
13416
+ lanesArraySchema2 = external_exports.array(laneSchema2).max(MAX_LANES2, `lanes must have at most ${MAX_LANES2} entries`).superRefine((lanes, ctx) => {
13417
+ const seen = /* @__PURE__ */ new Map();
13418
+ lanes.forEach((l, i) => {
13419
+ if (!l.slug) return;
13420
+ const arr = seen.get(l.slug) ?? [];
13421
+ arr.push(i);
13422
+ seen.set(l.slug, arr);
13423
+ });
13424
+ for (const [slug, indices] of seen) {
13425
+ if (indices.length > 1) {
13426
+ for (const i of indices) {
13427
+ ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: [i, "slug"], message: `lanes: duplicate slug "${slug}"` });
13428
+ }
13429
+ }
13430
+ }
13431
+ });
13166
13432
  hubIdParam2 = external_exports.object({ id: uuidSchema2 });
13167
13433
  hubAdminIdParam2 = external_exports.object({ id: uuidSchema2, adminId: uuidSchema2 });
13168
13434
  hubUserIdParam2 = external_exports.object({ id: uuidSchema2, hubUserId: uuidSchema2 });
@@ -13175,14 +13441,16 @@ var init_contracts = __esm({
13175
13441
  hub_name: external_exports.string().min(1, "hub_name is required"),
13176
13442
  hub_description: external_exports.string().optional(),
13177
13443
  kanban_statuses: kanbanStatusesArraySchema2.nullable().optional(),
13444
+ lanes: lanesArraySchema2.nullable().optional(),
13178
13445
  // Write-once at creation; updateHubBody does not accept this.
13179
13446
  primary_region: primaryRegionSchema2.optional()
13180
- }).passthrough();
13447
+ }).passthrough().superRefine(refineLaneReferences2);
13181
13448
  updateHubBody2 = external_exports.object({
13182
13449
  support_model: external_exports.enum(["conversation", "account"]).optional(),
13183
13450
  unassigned_policy: external_exports.enum(["default_team", "auto_assign_random", "auto_assign_round_robin", "pull_queue"]).optional(),
13184
13451
  default_hub_team_id: uuidSchema2.nullable().optional(),
13185
13452
  kanban_statuses: kanbanStatusesArraySchema2.nullable().optional(),
13453
+ lanes: lanesArraySchema2.nullable().optional(),
13186
13454
  // summarization_threshold_tokens relocated to the summarizer AGENT (PR4) — validated/
13187
13455
  // persisted via the agent path (.passthrough() agent body → HubDO column), not here.
13188
13456
  auto_close_inactive_days: external_exports.number().int().min(1).max(180).optional(),
@@ -13191,7 +13459,7 @@ var init_contracts = __esm({
13191
13459
  language: external_exports.enum(["en", "pt", "es"]).optional(),
13192
13460
  access_request_message: external_exports.string().max(1e3).nullable().optional(),
13193
13461
  access_approval_role: external_exports.enum(["admin", "team"]).optional()
13194
- }).passthrough();
13462
+ }).passthrough().superRefine(refineLaneReferences2);
13195
13463
  addHubAdminBody2 = external_exports.object({
13196
13464
  user_email: external_exports.string().email("valid email is required")
13197
13465
  });
@@ -14367,6 +14635,7 @@ var init_contracts = __esm({
14367
14635
  }).passthrough().superRefine((config, ctx) => {
14368
14636
  refineHubAsCodeCustomTools2(config, ctx);
14369
14637
  refineHubAsCodeKanbanStatuses2(config, ctx);
14638
+ refineHubAsCodeLanes2(config, ctx);
14370
14639
  });
14371
14640
  ciPullHubIdParam2 = external_exports.object({
14372
14641
  hub_id: ciUuidSchema2
@@ -15038,6 +15307,62 @@ var init_contracts = __esm({
15038
15307
  offset: external_exports.coerce.number().int().min(0).optional()
15039
15308
  });
15040
15309
  noticeIdParamSchema2 = external_exports.object({ id: external_exports.string().min(1) });
15310
+ TEMPLATE_SLUG_REGEX2 = /^[a-z0-9][a-z0-9-]{0,62}$/;
15311
+ templateSlugSchema2 = external_exports.string().regex(TEMPLATE_SLUG_REGEX2, "slug must be lowercase alphanumeric with hyphens (max 63)");
15312
+ templateStatusSchema2 = external_exports.enum(["published", "draft", "internal"]);
15313
+ templateFileMapSchema2 = external_exports.record(external_exports.string()).refine((m) => Object.keys(m).every(isSafeTemplatePath2), {
15314
+ message: 'file map contains an unsafe path (no absolute paths, "..", or backslashes)'
15315
+ });
15316
+ templateWayaiBlockSchema2 = external_exports.object({
15317
+ metaTitle: external_exports.string().min(1).max(200),
15318
+ metaDescription: external_exports.string().max(1e3).default(""),
15319
+ installCommand: external_exports.string().min(1).max(300)
15320
+ });
15321
+ templateRekorBlockSchema2 = templateWayaiBlockSchema2.extend({
15322
+ mcpEndpoint: external_exports.string().url().optional(),
15323
+ url: external_exports.string().url().optional()
15324
+ });
15325
+ templateYamlWayaiBlock2 = templateWayaiBlockSchema2.extend({
15326
+ source: external_exports.string().min(1)
15327
+ });
15328
+ templateYamlRekorBlock2 = templateRekorBlockSchema2.extend({
15329
+ source: external_exports.string().min(1)
15330
+ });
15331
+ templateYamlSchema2 = external_exports.object({
15332
+ slug: templateSlugSchema2,
15333
+ category: external_exports.string().min(1).max(60),
15334
+ status: templateStatusSchema2.default("draft"),
15335
+ order: external_exports.number().int().min(0).max(1e5).optional(),
15336
+ hero: external_exports.string().optional(),
15337
+ // relative path to a hero image inside the template folder
15338
+ wayai: templateYamlWayaiBlock2.optional(),
15339
+ rekor: templateYamlRekorBlock2.optional()
15340
+ }).refine((c) => Boolean(c.wayai) || Boolean(c.rekor), {
15341
+ message: "template.yaml must define at least one of `wayai:` or `rekor:`"
15342
+ });
15343
+ templatePushMetadataSchema2 = external_exports.object({
15344
+ category: external_exports.string().min(1).max(60),
15345
+ status: templateStatusSchema2,
15346
+ order: external_exports.number().int().min(0).max(1e5).default(0),
15347
+ wayai: templateWayaiBlockSchema2.optional(),
15348
+ rekor: templateRekorBlockSchema2.optional()
15349
+ });
15350
+ templateHeroSchema2 = external_exports.object({
15351
+ filename: external_exports.string().min(1).max(200).regex(/\.(png|jpe?g|webp|gif)$/i, "hero must be a raster image (png/jpg/webp/gif)"),
15352
+ // ~8MB of base64 ≈ a ~6MB image — a generous cap that bounds the inline payload
15353
+ // (the endpoint is platform-admin-only, but an unbounded blob is still avoidable).
15354
+ dataBase64: external_exports.string().min(1).max(8e6)
15355
+ });
15356
+ templatePushBody2 = external_exports.object({
15357
+ slug: templateSlugSchema2,
15358
+ metadata: templatePushMetadataSchema2,
15359
+ // wayai slice file-map (path -> body); ABSENT for a Rekor-only template.
15360
+ wayaiFiles: templateFileMapSchema2.optional(),
15361
+ // raw README markdown; the backend renders + sanitizes (single authority).
15362
+ readmeMarkdown: external_exports.string().default(""),
15363
+ hero: templateHeroSchema2.optional()
15364
+ });
15365
+ templateSlugParam2 = external_exports.object({ slug: templateSlugSchema2 });
15041
15366
  }
15042
15367
  });
15043
15368
 
@@ -16231,6 +16556,171 @@ var init_list = __esm({
16231
16556
  }
16232
16557
  });
16233
16558
 
16559
+ // src/lib/template-files.ts
16560
+ import * as fs16 from "fs";
16561
+ import * as path19 from "path";
16562
+ function readDirToFileMap(rootDir) {
16563
+ const out = {};
16564
+ function walk(dir, prefix) {
16565
+ for (const entry of fs16.readdirSync(dir, { withFileTypes: true })) {
16566
+ const abs = path19.join(dir, entry.name);
16567
+ const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
16568
+ if (entry.isDirectory()) walk(abs, rel);
16569
+ else if (entry.isFile()) out[rel] = fs16.readFileSync(abs, "utf-8");
16570
+ }
16571
+ }
16572
+ if (fs16.existsSync(rootDir)) walk(rootDir, "");
16573
+ return out;
16574
+ }
16575
+ function writeFileMap(targetDir, files) {
16576
+ const written = [];
16577
+ for (const [rel, body] of Object.entries(files)) {
16578
+ if (!isSafeTemplatePath2(rel)) {
16579
+ throw new Error(`Refusing to write unsafe template path: ${rel}`);
16580
+ }
16581
+ const abs = path19.join(targetDir, rel);
16582
+ fs16.mkdirSync(path19.dirname(abs), { recursive: true });
16583
+ fs16.writeFileSync(abs, body, "utf-8");
16584
+ written.push(rel);
16585
+ }
16586
+ return written;
16587
+ }
16588
+ var init_template_files = __esm({
16589
+ "src/lib/template-files.ts"() {
16590
+ "use strict";
16591
+ init_contracts();
16592
+ }
16593
+ });
16594
+
16595
+ // src/commands/templates.ts
16596
+ var templates_exports = {};
16597
+ __export(templates_exports, {
16598
+ templateCommand: () => templateCommand
16599
+ });
16600
+ import * as path20 from "path";
16601
+ async function templateCommand(args2) {
16602
+ const [sub, ...rest] = args2;
16603
+ switch (sub) {
16604
+ case "list":
16605
+ await runTemplateList();
16606
+ return;
16607
+ case "pull":
16608
+ await runTemplatePull(rest);
16609
+ return;
16610
+ case void 0:
16611
+ case "help":
16612
+ case "--help":
16613
+ case "-h":
16614
+ printTemplateHelp();
16615
+ return;
16616
+ default:
16617
+ console.error(`Unknown template subcommand: ${sub}`);
16618
+ printTemplateHelp();
16619
+ process.exit(1);
16620
+ }
16621
+ }
16622
+ function includesLabel(includes) {
16623
+ const parts = [];
16624
+ if (includes.wayai) parts.push("wayai agent");
16625
+ if (includes.rekor) parts.push("rekor data layer");
16626
+ return parts.length ? parts.join(" + ") : "(none)";
16627
+ }
16628
+ async function runTemplateList() {
16629
+ const { config, accessToken } = await requireAuth();
16630
+ const client = new ApiClient({ apiUrl: config.api_url, accessToken });
16631
+ let response;
16632
+ try {
16633
+ response = await client.templateList();
16634
+ } catch (err) {
16635
+ exitOnTemplateApiError(err);
16636
+ throw err;
16637
+ }
16638
+ const { templates } = response;
16639
+ if (templates.length === 0) {
16640
+ console.log("(no templates published yet)");
16641
+ return;
16642
+ }
16643
+ const slugWidth = Math.max(4, ...templates.map((t) => t.slug.length));
16644
+ const catWidth = Math.max(8, ...templates.map((t) => t.category.length));
16645
+ console.log(`${"SLUG".padEnd(slugWidth)} ${"CATEGORY".padEnd(catWidth)} INCLUDES`);
16646
+ for (const t of templates) {
16647
+ console.log(`${t.slug.padEnd(slugWidth)} ${t.category.padEnd(catWidth)} ${includesLabel(t.includes)}`);
16648
+ }
16649
+ }
16650
+ async function runTemplatePull(rest) {
16651
+ const slug = rest[0];
16652
+ if (!slug) {
16653
+ console.error("wayai template pull <slug>");
16654
+ process.exit(1);
16655
+ }
16656
+ if (!TEMPLATE_SLUG_REGEX2.test(slug)) {
16657
+ console.error(`Invalid template slug: ${slug}`);
16658
+ process.exit(1);
16659
+ }
16660
+ const gitRoot = findGitRoot();
16661
+ if (!gitRoot) {
16662
+ console.error("Not in a git repository. Run `wayai template pull` inside your project repo.");
16663
+ process.exit(1);
16664
+ }
16665
+ const { config, accessToken } = await requireAuth();
16666
+ const client = new ApiClient({ apiUrl: config.api_url, accessToken });
16667
+ let detail;
16668
+ try {
16669
+ detail = await client.templatePull(slug);
16670
+ } catch (err) {
16671
+ exitOnTemplateApiError(err);
16672
+ throw err;
16673
+ }
16674
+ if (!detail.includes.wayai) {
16675
+ console.log(`Template "${slug}" has no WayAI slice \u2014 run \`rekor template pull ${slug}\` for its data layer.`);
16676
+ return;
16677
+ }
16678
+ const targetDir = path20.join(resolveLayout(gitRoot).hubsDir, slug);
16679
+ const written = writeFileMap(targetDir, detail.files);
16680
+ console.log(`Pulled template "${slug}" \u2192 ${path20.relative(gitRoot, targetDir)}/ (${written.length} file${written.length === 1 ? "" : "s"}).`);
16681
+ console.log(`Next: \`wayai push\` to deploy it into your own hub.`);
16682
+ if (detail.includes.rekor) {
16683
+ console.log(`
16684
+ This template includes a Rekor data layer \u2014 run \`rekor template pull ${slug}\` to add it.`);
16685
+ }
16686
+ }
16687
+ function printTemplateHelp() {
16688
+ console.log(`wayai template \u2014 install ready-made hub templates
16689
+
16690
+ Usage:
16691
+ wayai template list List available templates
16692
+ wayai template pull <slug> Write a template's WayAI slice into wayai-ws/hubs/<slug>/
16693
+
16694
+ After pulling, run \`wayai push\` to deploy the template into your own hub.`);
16695
+ }
16696
+ function exitOnTemplateApiError(err) {
16697
+ if (err instanceof ApiError) {
16698
+ if (err.status === 401) {
16699
+ console.error("Authentication failed. Run `wayai login` again.");
16700
+ } else if (err.status === 404) {
16701
+ console.error("Template not found. Run `wayai template list` to see available templates.");
16702
+ } else if (err.status === 429) {
16703
+ console.error("Rate limited. Please retry in a moment.");
16704
+ } else {
16705
+ console.error(err.message);
16706
+ }
16707
+ process.exit(1);
16708
+ }
16709
+ console.error(err instanceof Error ? err.message : String(err));
16710
+ process.exit(1);
16711
+ }
16712
+ var init_templates = __esm({
16713
+ "src/commands/templates.ts"() {
16714
+ "use strict";
16715
+ init_auth();
16716
+ init_api_client();
16717
+ init_workspace();
16718
+ init_layout();
16719
+ init_template_files();
16720
+ init_contracts();
16721
+ }
16722
+ });
16723
+
16234
16724
  // src/lib/report-edit-args.ts
16235
16725
  function parseReportEditArgs(args2) {
16236
16726
  const patch = {};
@@ -16276,6 +16766,9 @@ var admin_exports = {};
16276
16766
  __export(admin_exports, {
16277
16767
  adminCommand: () => adminCommand
16278
16768
  });
16769
+ import * as fs17 from "fs";
16770
+ import * as path21 from "path";
16771
+ import * as yaml9 from "js-yaml";
16279
16772
  async function adminCommand(args2) {
16280
16773
  const [group, ...afterGroup] = args2;
16281
16774
  if (!group) {
@@ -16389,6 +16882,23 @@ async function adminCommand(args2) {
16389
16882
  process.exit(1);
16390
16883
  }
16391
16884
  }
16885
+ if (group === "template") {
16886
+ if (!sub) {
16887
+ printHelp2();
16888
+ process.exit(1);
16889
+ }
16890
+ switch (sub) {
16891
+ case "push":
16892
+ await runTemplatePush(positional);
16893
+ return;
16894
+ case "pull":
16895
+ await runTemplatePull2(positional, flagArgs);
16896
+ return;
16897
+ default:
16898
+ printHelp2();
16899
+ process.exit(1);
16900
+ }
16901
+ }
16392
16902
  printHelp2();
16393
16903
  process.exit(1);
16394
16904
  }
@@ -16613,6 +17123,139 @@ function parseFlags2(flagArgs, allowed) {
16613
17123
  }
16614
17124
  return out;
16615
17125
  }
17126
+ async function runTemplatePush(positional) {
17127
+ const slug = positional[0];
17128
+ if (!slug) {
17129
+ console.error("wayai admin template push <slug>");
17130
+ process.exit(1);
17131
+ }
17132
+ if (!TEMPLATE_SLUG_REGEX2.test(slug)) {
17133
+ console.error(`Invalid template slug: ${slug}`);
17134
+ process.exit(1);
17135
+ }
17136
+ const gitRoot = findGitRoot();
17137
+ if (!gitRoot) {
17138
+ console.error("Not in a git repository. Run from the tutorials/ authoring repo.");
17139
+ process.exit(1);
17140
+ }
17141
+ const templateDir = path21.join(resolveTemplatesDir(gitRoot), slug);
17142
+ const manifestPath = path21.join(templateDir, "template.yaml");
17143
+ if (!fs17.existsSync(manifestPath)) {
17144
+ console.error(`template.yaml not found at ${path21.relative(gitRoot, manifestPath)}`);
17145
+ process.exit(1);
17146
+ }
17147
+ let manifest;
17148
+ try {
17149
+ manifest = templateYamlSchema2.parse(yaml9.load(fs17.readFileSync(manifestPath, "utf-8")));
17150
+ } catch (err) {
17151
+ console.error(`Invalid template.yaml: ${err instanceof Error ? err.message : String(err)}`);
17152
+ process.exit(1);
17153
+ }
17154
+ if (manifest.slug !== slug) {
17155
+ console.error(`template.yaml slug "${manifest.slug}" does not match folder "${slug}".`);
17156
+ process.exit(1);
17157
+ }
17158
+ let wayaiFiles;
17159
+ if (manifest.wayai) {
17160
+ const sliceDir = path21.resolve(templateDir, manifest.wayai.source);
17161
+ if (!fs17.existsSync(sliceDir)) {
17162
+ console.error(`wayai source not found: ${manifest.wayai.source} (resolved ${sliceDir})`);
17163
+ process.exit(1);
17164
+ }
17165
+ wayaiFiles = readDirToFileMap(sliceDir);
17166
+ }
17167
+ const readmePath = path21.join(templateDir, "README.md");
17168
+ const readmeMarkdown = fs17.existsSync(readmePath) ? fs17.readFileSync(readmePath, "utf-8") : "";
17169
+ let hero;
17170
+ if (manifest.hero) {
17171
+ const heroPath = path21.resolve(templateDir, manifest.hero);
17172
+ if (fs17.existsSync(heroPath)) {
17173
+ hero = { filename: path21.basename(heroPath), dataBase64: fs17.readFileSync(heroPath).toString("base64") };
17174
+ } else {
17175
+ console.error(`Warning: hero not found at ${manifest.hero}; skipping.`);
17176
+ }
17177
+ }
17178
+ const metadata = {
17179
+ category: manifest.category,
17180
+ status: manifest.status,
17181
+ order: manifest.order ?? 0,
17182
+ ...manifest.wayai && {
17183
+ wayai: {
17184
+ metaTitle: manifest.wayai.metaTitle,
17185
+ metaDescription: manifest.wayai.metaDescription,
17186
+ installCommand: manifest.wayai.installCommand
17187
+ }
17188
+ },
17189
+ ...manifest.rekor && {
17190
+ rekor: {
17191
+ metaTitle: manifest.rekor.metaTitle,
17192
+ metaDescription: manifest.rekor.metaDescription,
17193
+ installCommand: manifest.rekor.installCommand,
17194
+ ...manifest.rekor.mcpEndpoint && { mcpEndpoint: manifest.rekor.mcpEndpoint }
17195
+ }
17196
+ }
17197
+ };
17198
+ const body = { slug, metadata, wayaiFiles, readmeMarkdown, hero };
17199
+ const { config, accessToken } = await requireAuth();
17200
+ const client = new ApiClient({ apiUrl: config.api_url, accessToken });
17201
+ let result;
17202
+ try {
17203
+ result = await client.adminTemplatePush(body);
17204
+ } catch (err) {
17205
+ exitOnApiError(err);
17206
+ throw err;
17207
+ }
17208
+ console.log(`Template "${result.slug}": ${result.action} (${result.filesWritten} written, ${result.filesDeleted} deleted).`);
17209
+ if (manifest.status !== "published") {
17210
+ console.log(`(status=${manifest.status} \u2192 unpublished from the store)`);
17211
+ }
17212
+ }
17213
+ async function runTemplatePull2(positional, flagArgs) {
17214
+ const slug = positional[0];
17215
+ if (!slug) {
17216
+ console.error("wayai admin template pull <slug> [--out DIR] [--json]");
17217
+ process.exit(1);
17218
+ }
17219
+ if (!TEMPLATE_SLUG_REGEX2.test(slug)) {
17220
+ console.error(`Invalid template slug: ${slug}`);
17221
+ process.exit(1);
17222
+ }
17223
+ let outDir;
17224
+ let jsonOutput = false;
17225
+ for (let i = 0; i < flagArgs.length; i++) {
17226
+ if (flagArgs[i] === "--json") {
17227
+ jsonOutput = true;
17228
+ continue;
17229
+ }
17230
+ if (flagArgs[i] === "--out") {
17231
+ outDir = flagArgs[++i];
17232
+ if (!outDir) {
17233
+ console.error("--out requires a value");
17234
+ process.exit(1);
17235
+ }
17236
+ continue;
17237
+ }
17238
+ console.error(`Unknown flag: ${flagArgs[i]}`);
17239
+ process.exit(1);
17240
+ }
17241
+ const { config, accessToken } = await requireAuth();
17242
+ const client = new ApiClient({ apiUrl: config.api_url, accessToken });
17243
+ let detail;
17244
+ try {
17245
+ detail = await client.adminTemplatePull(slug);
17246
+ } catch (err) {
17247
+ exitOnApiError(err);
17248
+ throw err;
17249
+ }
17250
+ if (jsonOutput) {
17251
+ console.log(JSON.stringify(detail, null, 2));
17252
+ return;
17253
+ }
17254
+ const target = outDir ? path21.resolve(outDir) : path21.join(process.cwd(), "template-store-pull", slug);
17255
+ const written = writeFileMap(target, detail.files);
17256
+ console.log(`Pulled "${slug}" from the store \u2192 ${target} (${written.length} file${written.length === 1 ? "" : "s"}).`);
17257
+ console.log(`includes: wayai=${detail.includes.wayai} rekor=${detail.includes.rekor}`);
17258
+ }
16616
17259
  function exitOnApiError(err) {
16617
17260
  if (err instanceof ApiError) {
16618
17261
  if (err.status === 401) {
@@ -17024,6 +17667,9 @@ Usage:
17024
17667
  wayai admin notice list [--status active|resolved] [--limit N] [--offset N] [--json]
17025
17668
  wayai admin notice resolve <notice_id>
17026
17669
 
17670
+ wayai admin template push <slug> Publish a template from tutorials/templates/<slug>/ into the store
17671
+ wayai admin template pull <slug> [--out DIR] [--json] Pull a published template from the store for inspection
17672
+
17027
17673
  Sources:
17028
17674
  do Live DO SQLite (existing conversation, hub config, etc.).
17029
17675
  analytics ClickHouse projections (conversation, message, billing_event, schedule_event).
@@ -17081,6 +17727,9 @@ var init_admin = __esm({
17081
17727
  init_api_client();
17082
17728
  init_observability();
17083
17729
  init_report_edit_args();
17730
+ init_workspace();
17731
+ init_layout();
17732
+ init_template_files();
17084
17733
  init_contracts();
17085
17734
  VALID_TYPES = dataExplorerDebugDoType2.options;
17086
17735
  VALID_ANALYTICS_TABLES = dataExplorerDebugAnalyticsTable2.options;
@@ -17089,12 +17738,12 @@ var init_admin = __esm({
17089
17738
  });
17090
17739
 
17091
17740
  // src/commands/report-create.ts
17092
- import { readFileSync as readFileSync14 } from "fs";
17093
- import { dirname as dirname7, join as join20 } from "path";
17741
+ import { readFileSync as readFileSync16 } from "fs";
17742
+ import { dirname as dirname8, join as join23 } from "path";
17094
17743
  import { fileURLToPath as fileURLToPath2 } from "url";
17095
17744
  function getCliVersion() {
17096
17745
  try {
17097
- const pkg2 = JSON.parse(readFileSync14(join20(__dirname, "..", "..", "package.json"), "utf-8"));
17746
+ const pkg2 = JSON.parse(readFileSync16(join23(__dirname, "..", "..", "package.json"), "utf-8"));
17098
17747
  return `cli@${pkg2.version}`;
17099
17748
  } catch {
17100
17749
  return "cli@unknown";
@@ -17201,7 +17850,7 @@ var init_report_create = __esm({
17201
17850
  init_auth();
17202
17851
  init_repo_config();
17203
17852
  init_api_client();
17204
- __dirname = dirname7(fileURLToPath2(import.meta.url));
17853
+ __dirname = dirname8(fileURLToPath2(import.meta.url));
17205
17854
  }
17206
17855
  });
17207
17856
 
@@ -17476,9 +18125,9 @@ var init_update = __esm({
17476
18125
 
17477
18126
  // src/index.ts
17478
18127
  init_sentry();
17479
- import { readFileSync as readFileSync15 } from "fs";
18128
+ import { readFileSync as readFileSync17 } from "fs";
17480
18129
  import { fileURLToPath as fileURLToPath3 } from "url";
17481
- import { dirname as dirname8, join as join21 } from "path";
18130
+ import { dirname as dirname9, join as join24 } from "path";
17482
18131
 
17483
18132
  // src/lib/errors.ts
17484
18133
  init_api_client();
@@ -17503,7 +18152,7 @@ init_skill_version();
17503
18152
  import { exec } from "child_process";
17504
18153
  var REFRESH_TIMEOUT_MS = 1e4;
17505
18154
  function refreshCliCache() {
17506
- return new Promise((resolve4) => {
18155
+ return new Promise((resolve5) => {
17507
18156
  exec("npm view @wayai/cli version", { timeout: REFRESH_TIMEOUT_MS }, (err, stdout) => {
17508
18157
  if (!err) {
17509
18158
  const latest = stdout.trim();
@@ -17514,7 +18163,7 @@ function refreshCliCache() {
17514
18163
  }
17515
18164
  }
17516
18165
  }
17517
- resolve4();
18166
+ resolve5();
17518
18167
  });
17519
18168
  });
17520
18169
  }
@@ -17606,8 +18255,8 @@ Run \`npx skills add wayai-pro/wayai-skill -y\` to update.`);
17606
18255
  }
17607
18256
 
17608
18257
  // src/index.ts
17609
- var __dirname2 = dirname8(fileURLToPath3(import.meta.url));
17610
- var pkg = JSON.parse(readFileSync15(join21(__dirname2, "..", "package.json"), "utf-8"));
18258
+ var __dirname2 = dirname9(fileURLToPath3(import.meta.url));
18259
+ var pkg = JSON.parse(readFileSync17(join24(__dirname2, "..", "package.json"), "utf-8"));
17611
18260
  var [, , command, ...args] = process.argv;
17612
18261
  var isBackgroundRefresh = command === REFRESH_COMMAND;
17613
18262
  if (!isBackgroundRefresh) initSentry(command);
@@ -17739,6 +18388,11 @@ async function main() {
17739
18388
  await listCommand2(args);
17740
18389
  break;
17741
18390
  }
18391
+ case "template": {
18392
+ const { templateCommand: templateCommand2 } = await Promise.resolve().then(() => (init_templates(), templates_exports));
18393
+ await templateCommand2(args);
18394
+ break;
18395
+ }
17742
18396
  case "admin": {
17743
18397
  const { adminCommand: adminCommand2 } = await Promise.resolve().then(() => (init_admin(), admin_exports));
17744
18398
  await adminCommand2(args);
@@ -17805,6 +18459,8 @@ Commands:
17805
18459
  sync-skills Sync skills to Anthropic/OpenAI provider connections
17806
18460
  sync-mcp Re-discover an MCP connection's tools (refresh stale schemas)
17807
18461
  list List organizations and hubs
18462
+ template list List ready-made hub templates
18463
+ template pull Write a template's WayAI slice into wayai-ws/hubs/<slug>/
17808
18464
  report create Report a platform bug (submits to the triage queue)
17809
18465
  report edit Amend your own pending report (title/description/error/steps/context)
17810
18466
  update Update CLI to the latest version