@wayai/cli 0.3.221 → 0.3.223
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 +1964 -1015
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4604,6 +4604,137 @@ var init_zod = __esm({
|
|
|
4604
4604
|
}
|
|
4605
4605
|
});
|
|
4606
4606
|
|
|
4607
|
+
// ../../packages/core/dist/chunk-FK6HBXM7.js
|
|
4608
|
+
var orgResourceIdParam, orgResourceFileIdParam, orgResourceFolderIdParam, hubOrgResourceParam, listOrgResourcesQuery, orgIdQuery, createOrgResourceBody, updateOrgResourceBody, createOrgResourceFolderBody, updateOrgResourceFolderBody, uploadOrgResourceFileBody, updateOrgResourceFileBody, uploadOrgSkillZipBody, linkOrgResourceBody;
|
|
4609
|
+
var init_chunk_FK6HBXM7 = __esm({
|
|
4610
|
+
"../../packages/core/dist/chunk-FK6HBXM7.js"() {
|
|
4611
|
+
"use strict";
|
|
4612
|
+
init_zod();
|
|
4613
|
+
orgResourceIdParam = external_exports.object({
|
|
4614
|
+
id: external_exports.string().uuid()
|
|
4615
|
+
});
|
|
4616
|
+
orgResourceFileIdParam = external_exports.object({
|
|
4617
|
+
file_id: external_exports.string().uuid()
|
|
4618
|
+
});
|
|
4619
|
+
orgResourceFolderIdParam = external_exports.object({
|
|
4620
|
+
folder_id: external_exports.string().uuid()
|
|
4621
|
+
});
|
|
4622
|
+
hubOrgResourceParam = external_exports.object({
|
|
4623
|
+
id: external_exports.string().uuid(),
|
|
4624
|
+
org_resource_id: external_exports.string().uuid()
|
|
4625
|
+
});
|
|
4626
|
+
listOrgResourcesQuery = external_exports.object({
|
|
4627
|
+
organization_id: external_exports.string().uuid(),
|
|
4628
|
+
hub_id: external_exports.string().uuid().optional()
|
|
4629
|
+
});
|
|
4630
|
+
orgIdQuery = external_exports.object({
|
|
4631
|
+
organization_id: external_exports.string().uuid()
|
|
4632
|
+
});
|
|
4633
|
+
createOrgResourceBody = external_exports.object({
|
|
4634
|
+
organization_id: external_exports.string().uuid(),
|
|
4635
|
+
resource_name: external_exports.string().min(1).max(255),
|
|
4636
|
+
resource_description: external_exports.string().max(1e3).nullish(),
|
|
4637
|
+
resource_type: external_exports.enum(["knowledge", "skill"]).default("knowledge"),
|
|
4638
|
+
// Skill-only fields. Required for `resource_type = 'skill'` (validated in
|
|
4639
|
+
// the route layer since cross-field refinement complicates the inferred
|
|
4640
|
+
// input type for callers). Ignored for `knowledge`.
|
|
4641
|
+
skill_name: external_exports.string().min(1).max(255).nullish(),
|
|
4642
|
+
skill_version: external_exports.string().max(50).nullish(),
|
|
4643
|
+
folder_metadata_schema: external_exports.record(external_exports.unknown()).optional(),
|
|
4644
|
+
file_metadata_schema: external_exports.record(external_exports.unknown()).optional(),
|
|
4645
|
+
enabled: external_exports.boolean().optional(),
|
|
4646
|
+
tags: external_exports.array(external_exports.string().uuid()).optional(),
|
|
4647
|
+
environment: external_exports.enum(["preview", "production", "all"]).optional()
|
|
4648
|
+
});
|
|
4649
|
+
updateOrgResourceBody = external_exports.object({
|
|
4650
|
+
resource_name: external_exports.string().min(1).max(255).optional(),
|
|
4651
|
+
resource_description: external_exports.string().max(1e3).nullish(),
|
|
4652
|
+
skill_name: external_exports.string().min(1).max(255).nullish(),
|
|
4653
|
+
skill_version: external_exports.string().max(50).nullish(),
|
|
4654
|
+
folder_metadata_schema: external_exports.record(external_exports.unknown()).optional(),
|
|
4655
|
+
file_metadata_schema: external_exports.record(external_exports.unknown()).optional(),
|
|
4656
|
+
enabled: external_exports.boolean().optional(),
|
|
4657
|
+
tags: external_exports.array(external_exports.string().uuid()).optional(),
|
|
4658
|
+
environment: external_exports.enum(["preview", "production", "all"]).optional()
|
|
4659
|
+
});
|
|
4660
|
+
createOrgResourceFolderBody = external_exports.object({
|
|
4661
|
+
org_resource_id: external_exports.string().uuid(),
|
|
4662
|
+
folder_name: external_exports.string().min(1).max(255),
|
|
4663
|
+
folder_description: external_exports.string().max(1e3).nullish(),
|
|
4664
|
+
parent_folder_id: external_exports.string().uuid().nullish(),
|
|
4665
|
+
order_index: external_exports.number().int().nonnegative().optional(),
|
|
4666
|
+
enabled: external_exports.boolean().optional(),
|
|
4667
|
+
metadata: external_exports.record(external_exports.unknown()).optional()
|
|
4668
|
+
});
|
|
4669
|
+
updateOrgResourceFolderBody = external_exports.object({
|
|
4670
|
+
folder_name: external_exports.string().min(1).max(255).optional(),
|
|
4671
|
+
folder_description: external_exports.string().max(1e3).nullish(),
|
|
4672
|
+
parent_folder_id: external_exports.string().uuid().nullish(),
|
|
4673
|
+
order_index: external_exports.number().int().nonnegative().optional(),
|
|
4674
|
+
enabled: external_exports.boolean().optional(),
|
|
4675
|
+
metadata: external_exports.record(external_exports.unknown()).optional()
|
|
4676
|
+
});
|
|
4677
|
+
uploadOrgResourceFileBody = external_exports.object({
|
|
4678
|
+
org_resource_id: external_exports.string().uuid(),
|
|
4679
|
+
folder_id: external_exports.string().uuid().nullish(),
|
|
4680
|
+
file_name: external_exports.string().min(1).max(255),
|
|
4681
|
+
file_data: external_exports.string(),
|
|
4682
|
+
// base64
|
|
4683
|
+
mime_type: external_exports.string().optional(),
|
|
4684
|
+
file_size: external_exports.number().int().nonnegative().optional(),
|
|
4685
|
+
resource_file_tags: external_exports.array(external_exports.string()).optional(),
|
|
4686
|
+
metadata: external_exports.record(external_exports.unknown()).optional()
|
|
4687
|
+
});
|
|
4688
|
+
updateOrgResourceFileBody = external_exports.object({
|
|
4689
|
+
resource_file_title: external_exports.string().min(1).max(255).optional(),
|
|
4690
|
+
folder_id: external_exports.string().uuid().nullish(),
|
|
4691
|
+
order_index: external_exports.number().int().nonnegative().optional(),
|
|
4692
|
+
enabled: external_exports.boolean().optional(),
|
|
4693
|
+
resource_file_tags: external_exports.array(external_exports.string()).optional(),
|
|
4694
|
+
metadata: external_exports.record(external_exports.unknown()).optional()
|
|
4695
|
+
});
|
|
4696
|
+
uploadOrgSkillZipBody = external_exports.object({
|
|
4697
|
+
zip_data: external_exports.string().min(1),
|
|
4698
|
+
replace_existing: external_exports.boolean().optional()
|
|
4699
|
+
});
|
|
4700
|
+
linkOrgResourceBody = external_exports.object({
|
|
4701
|
+
org_resource_id: external_exports.string().uuid()
|
|
4702
|
+
});
|
|
4703
|
+
}
|
|
4704
|
+
});
|
|
4705
|
+
|
|
4706
|
+
// ../../packages/core/dist/chunk-VXEAR36B.js
|
|
4707
|
+
var orgTagIdParam, listOrgTagsQuery, ORG_TAG_NAME_REGEX, ORG_TAG_NAME_MAX_LENGTH, createOrgTagBody, updateOrgTagBody;
|
|
4708
|
+
var init_chunk_VXEAR36B = __esm({
|
|
4709
|
+
"../../packages/core/dist/chunk-VXEAR36B.js"() {
|
|
4710
|
+
"use strict";
|
|
4711
|
+
init_zod();
|
|
4712
|
+
orgTagIdParam = external_exports.object({
|
|
4713
|
+
id: external_exports.string().uuid()
|
|
4714
|
+
});
|
|
4715
|
+
listOrgTagsQuery = external_exports.object({
|
|
4716
|
+
organization_id: external_exports.string().uuid()
|
|
4717
|
+
});
|
|
4718
|
+
ORG_TAG_NAME_REGEX = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
|
4719
|
+
ORG_TAG_NAME_MAX_LENGTH = 50;
|
|
4720
|
+
createOrgTagBody = external_exports.object({
|
|
4721
|
+
organization_id: external_exports.string().uuid(),
|
|
4722
|
+
name: external_exports.string().min(1).max(ORG_TAG_NAME_MAX_LENGTH).regex(ORG_TAG_NAME_REGEX, {
|
|
4723
|
+
message: 'Must be lowercase alphanumeric with hyphens (e.g. "my-tag")'
|
|
4724
|
+
}),
|
|
4725
|
+
display_name: external_exports.string().max(100).nullish(),
|
|
4726
|
+
color: external_exports.string().max(20).nullish()
|
|
4727
|
+
});
|
|
4728
|
+
updateOrgTagBody = external_exports.object({
|
|
4729
|
+
// Required so the route targets the org the UI is managing (which may differ
|
|
4730
|
+
// from the token's default `auth.org_id` for multi-org users) — mirrors create/list.
|
|
4731
|
+
organization_id: external_exports.string().uuid(),
|
|
4732
|
+
display_name: external_exports.string().max(100).nullish(),
|
|
4733
|
+
color: external_exports.string().max(20).nullish()
|
|
4734
|
+
});
|
|
4735
|
+
}
|
|
4736
|
+
});
|
|
4737
|
+
|
|
4607
4738
|
// ../../packages/core/dist/chunk-HZ6CADSY.js
|
|
4608
4739
|
function optionalTrimmedText(max) {
|
|
4609
4740
|
return external_exports.string().trim().max(max).optional().transform((value) => value ? value : void 0);
|
|
@@ -5152,133 +5283,21 @@ var init_chunk_SXDX4GHS = __esm({
|
|
|
5152
5283
|
}
|
|
5153
5284
|
});
|
|
5154
5285
|
|
|
5155
|
-
// ../../packages/core/dist/chunk-
|
|
5156
|
-
var
|
|
5157
|
-
var
|
|
5158
|
-
"../../packages/core/dist/chunk-
|
|
5159
|
-
"use strict";
|
|
5160
|
-
init_zod();
|
|
5161
|
-
orgResourceIdParam = external_exports.object({
|
|
5162
|
-
id: external_exports.string().uuid()
|
|
5163
|
-
});
|
|
5164
|
-
orgResourceFileIdParam = external_exports.object({
|
|
5165
|
-
file_id: external_exports.string().uuid()
|
|
5166
|
-
});
|
|
5167
|
-
orgResourceFolderIdParam = external_exports.object({
|
|
5168
|
-
folder_id: external_exports.string().uuid()
|
|
5169
|
-
});
|
|
5170
|
-
hubOrgResourceParam = external_exports.object({
|
|
5171
|
-
id: external_exports.string().uuid(),
|
|
5172
|
-
org_resource_id: external_exports.string().uuid()
|
|
5173
|
-
});
|
|
5174
|
-
listOrgResourcesQuery = external_exports.object({
|
|
5175
|
-
organization_id: external_exports.string().uuid(),
|
|
5176
|
-
hub_id: external_exports.string().uuid().optional()
|
|
5177
|
-
});
|
|
5178
|
-
orgIdQuery = external_exports.object({
|
|
5179
|
-
organization_id: external_exports.string().uuid()
|
|
5180
|
-
});
|
|
5181
|
-
createOrgResourceBody = external_exports.object({
|
|
5182
|
-
organization_id: external_exports.string().uuid(),
|
|
5183
|
-
resource_name: external_exports.string().min(1).max(255),
|
|
5184
|
-
resource_description: external_exports.string().max(1e3).nullish(),
|
|
5185
|
-
resource_type: external_exports.enum(["knowledge", "skill"]).default("knowledge"),
|
|
5186
|
-
// Skill-only fields. Required for `resource_type = 'skill'` (validated in
|
|
5187
|
-
// the route layer since cross-field refinement complicates the inferred
|
|
5188
|
-
// input type for callers). Ignored for `knowledge`.
|
|
5189
|
-
skill_name: external_exports.string().min(1).max(255).nullish(),
|
|
5190
|
-
skill_version: external_exports.string().max(50).nullish(),
|
|
5191
|
-
folder_metadata_schema: external_exports.record(external_exports.unknown()).optional(),
|
|
5192
|
-
file_metadata_schema: external_exports.record(external_exports.unknown()).optional(),
|
|
5193
|
-
enabled: external_exports.boolean().optional(),
|
|
5194
|
-
tags: external_exports.array(external_exports.string().uuid()).optional(),
|
|
5195
|
-
environment: external_exports.enum(["preview", "production", "all"]).optional()
|
|
5196
|
-
});
|
|
5197
|
-
updateOrgResourceBody = external_exports.object({
|
|
5198
|
-
resource_name: external_exports.string().min(1).max(255).optional(),
|
|
5199
|
-
resource_description: external_exports.string().max(1e3).nullish(),
|
|
5200
|
-
skill_name: external_exports.string().min(1).max(255).nullish(),
|
|
5201
|
-
skill_version: external_exports.string().max(50).nullish(),
|
|
5202
|
-
folder_metadata_schema: external_exports.record(external_exports.unknown()).optional(),
|
|
5203
|
-
file_metadata_schema: external_exports.record(external_exports.unknown()).optional(),
|
|
5204
|
-
enabled: external_exports.boolean().optional(),
|
|
5205
|
-
tags: external_exports.array(external_exports.string().uuid()).optional(),
|
|
5206
|
-
environment: external_exports.enum(["preview", "production", "all"]).optional()
|
|
5207
|
-
});
|
|
5208
|
-
createOrgResourceFolderBody = external_exports.object({
|
|
5209
|
-
org_resource_id: external_exports.string().uuid(),
|
|
5210
|
-
folder_name: external_exports.string().min(1).max(255),
|
|
5211
|
-
folder_description: external_exports.string().max(1e3).nullish(),
|
|
5212
|
-
parent_folder_id: external_exports.string().uuid().nullish(),
|
|
5213
|
-
order_index: external_exports.number().int().nonnegative().optional(),
|
|
5214
|
-
enabled: external_exports.boolean().optional(),
|
|
5215
|
-
metadata: external_exports.record(external_exports.unknown()).optional()
|
|
5216
|
-
});
|
|
5217
|
-
updateOrgResourceFolderBody = external_exports.object({
|
|
5218
|
-
folder_name: external_exports.string().min(1).max(255).optional(),
|
|
5219
|
-
folder_description: external_exports.string().max(1e3).nullish(),
|
|
5220
|
-
parent_folder_id: external_exports.string().uuid().nullish(),
|
|
5221
|
-
order_index: external_exports.number().int().nonnegative().optional(),
|
|
5222
|
-
enabled: external_exports.boolean().optional(),
|
|
5223
|
-
metadata: external_exports.record(external_exports.unknown()).optional()
|
|
5224
|
-
});
|
|
5225
|
-
uploadOrgResourceFileBody = external_exports.object({
|
|
5226
|
-
org_resource_id: external_exports.string().uuid(),
|
|
5227
|
-
folder_id: external_exports.string().uuid().nullish(),
|
|
5228
|
-
file_name: external_exports.string().min(1).max(255),
|
|
5229
|
-
file_data: external_exports.string(),
|
|
5230
|
-
// base64
|
|
5231
|
-
mime_type: external_exports.string().optional(),
|
|
5232
|
-
file_size: external_exports.number().int().nonnegative().optional(),
|
|
5233
|
-
resource_file_tags: external_exports.array(external_exports.string()).optional(),
|
|
5234
|
-
metadata: external_exports.record(external_exports.unknown()).optional()
|
|
5235
|
-
});
|
|
5236
|
-
updateOrgResourceFileBody = external_exports.object({
|
|
5237
|
-
resource_file_title: external_exports.string().min(1).max(255).optional(),
|
|
5238
|
-
folder_id: external_exports.string().uuid().nullish(),
|
|
5239
|
-
order_index: external_exports.number().int().nonnegative().optional(),
|
|
5240
|
-
enabled: external_exports.boolean().optional(),
|
|
5241
|
-
resource_file_tags: external_exports.array(external_exports.string()).optional(),
|
|
5242
|
-
metadata: external_exports.record(external_exports.unknown()).optional()
|
|
5243
|
-
});
|
|
5244
|
-
uploadOrgSkillZipBody = external_exports.object({
|
|
5245
|
-
zip_data: external_exports.string().min(1),
|
|
5246
|
-
replace_existing: external_exports.boolean().optional()
|
|
5247
|
-
});
|
|
5248
|
-
linkOrgResourceBody = external_exports.object({
|
|
5249
|
-
org_resource_id: external_exports.string().uuid()
|
|
5250
|
-
});
|
|
5251
|
-
}
|
|
5252
|
-
});
|
|
5253
|
-
|
|
5254
|
-
// ../../packages/core/dist/chunk-VXEAR36B.js
|
|
5255
|
-
var orgTagIdParam, listOrgTagsQuery, ORG_TAG_NAME_REGEX, ORG_TAG_NAME_MAX_LENGTH, createOrgTagBody, updateOrgTagBody;
|
|
5256
|
-
var init_chunk_VXEAR36B = __esm({
|
|
5257
|
-
"../../packages/core/dist/chunk-VXEAR36B.js"() {
|
|
5286
|
+
// ../../packages/core/dist/chunk-GPM7PE44.js
|
|
5287
|
+
var bootstrapQuery;
|
|
5288
|
+
var init_chunk_GPM7PE44 = __esm({
|
|
5289
|
+
"../../packages/core/dist/chunk-GPM7PE44.js"() {
|
|
5258
5290
|
"use strict";
|
|
5259
5291
|
init_zod();
|
|
5260
|
-
|
|
5261
|
-
|
|
5262
|
-
|
|
5263
|
-
|
|
5264
|
-
|
|
5265
|
-
|
|
5266
|
-
|
|
5267
|
-
|
|
5268
|
-
|
|
5269
|
-
organization_id: external_exports.string().uuid(),
|
|
5270
|
-
name: external_exports.string().min(1).max(ORG_TAG_NAME_MAX_LENGTH).regex(ORG_TAG_NAME_REGEX, {
|
|
5271
|
-
message: 'Must be lowercase alphanumeric with hyphens (e.g. "my-tag")'
|
|
5272
|
-
}),
|
|
5273
|
-
display_name: external_exports.string().max(100).nullish(),
|
|
5274
|
-
color: external_exports.string().max(20).nullish()
|
|
5275
|
-
});
|
|
5276
|
-
updateOrgTagBody = external_exports.object({
|
|
5277
|
-
// Required so the route targets the org the UI is managing (which may differ
|
|
5278
|
-
// from the token's default `auth.org_id` for multi-org users) — mirrors create/list.
|
|
5279
|
-
organization_id: external_exports.string().uuid(),
|
|
5280
|
-
display_name: external_exports.string().max(100).nullish(),
|
|
5281
|
-
color: external_exports.string().max(20).nullish()
|
|
5292
|
+
bootstrapQuery = external_exports.object({
|
|
5293
|
+
active_hub_id: external_exports.string().uuid().optional(),
|
|
5294
|
+
active_conversation_id: external_exports.string().uuid().optional(),
|
|
5295
|
+
active_nav: external_exports.enum(["chat", "task", "support"]).optional(),
|
|
5296
|
+
/**
|
|
5297
|
+
* The people thread the URL names (`/chat/threads/:threadId`, gh #4661): the snapshot
|
|
5298
|
+
* preloads it as `people.active_thread` when the viewer is a member.
|
|
5299
|
+
*/
|
|
5300
|
+
active_thread_id: external_exports.string().uuid().optional()
|
|
5282
5301
|
});
|
|
5283
5302
|
}
|
|
5284
5303
|
});
|
|
@@ -5553,6 +5572,583 @@ var init_chunk_5DGHIY7L = __esm({
|
|
|
5553
5572
|
}
|
|
5554
5573
|
});
|
|
5555
5574
|
|
|
5575
|
+
// ../../packages/core/dist/chunk-F3JHI4Y4.js
|
|
5576
|
+
var DATA_PROXY_MOUNT, DATA_PROXY_PREFIX, BASE_DESTRUCTION_MOUNT, BASE_DESTRUCTION_PREFIX, BASE_DESTRUCTION_CONFIRM_PATH, COMMUNITY_EMAIL_UNSUBSCRIBE_PATH, CONTACT_BOOK_EMAIL_UNSUBSCRIBE_PATH, AUTOMATION_WEBHOOK_MOUNT;
|
|
5577
|
+
var init_chunk_F3JHI4Y4 = __esm({
|
|
5578
|
+
"../../packages/core/dist/chunk-F3JHI4Y4.js"() {
|
|
5579
|
+
"use strict";
|
|
5580
|
+
DATA_PROXY_MOUNT = "/data";
|
|
5581
|
+
DATA_PROXY_PREFIX = `/api${DATA_PROXY_MOUNT}`;
|
|
5582
|
+
BASE_DESTRUCTION_MOUNT = "/bases";
|
|
5583
|
+
BASE_DESTRUCTION_PREFIX = `/api${BASE_DESTRUCTION_MOUNT}`;
|
|
5584
|
+
BASE_DESTRUCTION_CONFIRM_PATH = `${BASE_DESTRUCTION_PREFIX}/destruction/confirm`;
|
|
5585
|
+
COMMUNITY_EMAIL_UNSUBSCRIBE_PATH = "/api/communities/email-unsubscribe";
|
|
5586
|
+
CONTACT_BOOK_EMAIL_UNSUBSCRIBE_PATH = "/api/contact-book/email-unsubscribe";
|
|
5587
|
+
AUTOMATION_WEBHOOK_MOUNT = "/webhooks/automations";
|
|
5588
|
+
}
|
|
5589
|
+
});
|
|
5590
|
+
|
|
5591
|
+
// ../../packages/core/dist/chunk-IEWJ5MZH.js
|
|
5592
|
+
function matchPath(pattern, path42) {
|
|
5593
|
+
const normalizedPath = path42.replace(/\/$/, "");
|
|
5594
|
+
if (pattern.endsWith("/*")) {
|
|
5595
|
+
const prefix = pattern.slice(0, -2);
|
|
5596
|
+
return normalizedPath === prefix || normalizedPath.startsWith(prefix + "/");
|
|
5597
|
+
}
|
|
5598
|
+
return pattern === normalizedPath;
|
|
5599
|
+
}
|
|
5600
|
+
function getEndpointConfig(path42) {
|
|
5601
|
+
for (const excluded of EXCLUDED_PATHS) {
|
|
5602
|
+
if (matchPath(excluded, path42)) {
|
|
5603
|
+
return null;
|
|
5604
|
+
}
|
|
5605
|
+
}
|
|
5606
|
+
const normalizedPath = path42.replace(/\/$/, "");
|
|
5607
|
+
if (!normalizedPath.endsWith("/*") && Object.prototype.hasOwnProperty.call(ENDPOINT_CONFIGS, normalizedPath)) {
|
|
5608
|
+
return ENDPOINT_CONFIGS[normalizedPath];
|
|
5609
|
+
}
|
|
5610
|
+
const sortedPatterns = Object.keys(ENDPOINT_CONFIGS).sort((a, b) => b.length - a.length);
|
|
5611
|
+
for (const pattern of sortedPatterns) {
|
|
5612
|
+
if (matchPath(pattern, path42)) {
|
|
5613
|
+
return ENDPOINT_CONFIGS[pattern];
|
|
5614
|
+
}
|
|
5615
|
+
}
|
|
5616
|
+
return { tier: "STANDARD" };
|
|
5617
|
+
}
|
|
5618
|
+
var ENDPOINT_CONFIGS, EXCLUDED_PATHS;
|
|
5619
|
+
var init_chunk_IEWJ5MZH = __esm({
|
|
5620
|
+
"../../packages/core/dist/chunk-IEWJ5MZH.js"() {
|
|
5621
|
+
"use strict";
|
|
5622
|
+
init_chunk_F3JHI4Y4();
|
|
5623
|
+
ENDPOINT_CONFIGS = {
|
|
5624
|
+
// OAuth callbacks - strict, fail-closed (prevent brute force)
|
|
5625
|
+
"/oauth/callback/*": { tier: "STRICT", failClosed: true },
|
|
5626
|
+
// Auth endpoints - strict, fail-closed
|
|
5627
|
+
"/auth/phone": { tier: "STRICT", failClosed: true },
|
|
5628
|
+
"/auth/mcp/exchange": { tier: "MODERATE", failClosed: true },
|
|
5629
|
+
// Native magic-code login (pre-auth) — STRICT per-IP bounds a single host; WorkOS owns
|
|
5630
|
+
// the per-email lockout / TTL / single-use guarantees.
|
|
5631
|
+
"/api/auth/magic-code/*": { tier: "STRICT", failClosed: true },
|
|
5632
|
+
// Native password login (pre-auth) — same posture as magic-code above. STRICT per-IP bounds a
|
|
5633
|
+
// single host but FAILS OPEN on a binding error (like every IP limit); the `failClosed` flag
|
|
5634
|
+
// here is inert on a pre-auth route (it's only enforced on the per-user branch, which needs an
|
|
5635
|
+
// authenticated caller). The fail-CLOSED brute-force guarantee for the static credential comes
|
|
5636
|
+
// from the route's per-email caps (burst + cumulative hourly), not this flag.
|
|
5637
|
+
"/api/auth/password/*": { tier: "STRICT", failClosed: true },
|
|
5638
|
+
// Every `/api/*` group whose paths carry ids pins `userLimitKey` (gh #4756), held there by
|
|
5639
|
+
// `security/endpoint-invariants.test.ts`.
|
|
5640
|
+
// Billing - moderate, fail-closed with user limits
|
|
5641
|
+
"/api/billing/*": {
|
|
5642
|
+
tier: "MODERATE",
|
|
5643
|
+
userLimit: 100,
|
|
5644
|
+
failClosed: true,
|
|
5645
|
+
skipAuthenticatedUsers: true,
|
|
5646
|
+
userLimitKey: "/api/billing/*"
|
|
5647
|
+
},
|
|
5648
|
+
"/webhooks/stripe": { tier: "MODERATE", failClosed: true },
|
|
5649
|
+
"/webhooks/workos": { tier: "MODERATE", failClosed: true },
|
|
5650
|
+
// API endpoints - standard with user limits
|
|
5651
|
+
// 500 because the web's settings live updates refetch per hub in every open tab, whatever page
|
|
5652
|
+
// it shows (`settingsLiveUpdateStore.ts`): one batch of changes costs about 10 + 4 per agent
|
|
5653
|
+
// whose tools were opened + 2 per loaded resource, so a CLI push and publish (both hubs) with
|
|
5654
|
+
// two tabs open passes 150 in a minute. A multi-file resource upload adds 2–5 per file.
|
|
5655
|
+
"/api/setup/*": {
|
|
5656
|
+
tier: "STANDARD",
|
|
5657
|
+
userLimit: 500,
|
|
5658
|
+
skipAuthenticatedUsers: true,
|
|
5659
|
+
userLimitKey: "/api/setup/*"
|
|
5660
|
+
},
|
|
5661
|
+
// 500 because an eval run's drill-down (`SessionResults.tsx`) fetches its list and then every
|
|
5662
|
+
// LLM turn's record at once: up to 201 requests (`OBSERVABILITY_LIST_CAP`, 200). Sending keeps
|
|
5663
|
+
// the 150 it had when each path held its own budget, on the exact key below.
|
|
5664
|
+
"/api/conversations/*": {
|
|
5665
|
+
tier: "STANDARD",
|
|
5666
|
+
userLimit: 500,
|
|
5667
|
+
skipAuthenticatedUsers: true,
|
|
5668
|
+
userLimitKey: "/api/conversations/*"
|
|
5669
|
+
},
|
|
5670
|
+
"/api/conversations/send-message": { tier: "STANDARD", userLimit: 150, skipAuthenticatedUsers: true },
|
|
5671
|
+
"/api/users/*": { tier: "STANDARD", userLimit: 150, skipAuthenticatedUsers: true },
|
|
5672
|
+
"/api/analytics/*": {
|
|
5673
|
+
tier: "STANDARD",
|
|
5674
|
+
userLimit: 150,
|
|
5675
|
+
skipAuthenticatedUsers: true,
|
|
5676
|
+
userLimitKey: "/api/analytics/*"
|
|
5677
|
+
},
|
|
5678
|
+
// 500 because `wayai eval-results --eval` reads completed sessions one by one, newest first,
|
|
5679
|
+
// until one holds the eval (up to the 1000 the list returns). Tenant SQL keeps the 150 it had
|
|
5680
|
+
// when each path held its own budget: each query may run 30 s on shared-tenant ClickHouse
|
|
5681
|
+
// after its caller has gone (`sql-validator.ts`, stage 10b).
|
|
5682
|
+
"/api/evals/*": {
|
|
5683
|
+
tier: "STANDARD",
|
|
5684
|
+
userLimit: 500,
|
|
5685
|
+
skipAuthenticatedUsers: true,
|
|
5686
|
+
userLimitKey: "/api/evals/*"
|
|
5687
|
+
},
|
|
5688
|
+
"/api/evals/sql": { tier: "STANDARD", userLimit: 150, skipAuthenticatedUsers: true },
|
|
5689
|
+
// An eval run's calls (gh #4418), kept off the end user's call budgets below. Create keeps the
|
|
5690
|
+
// 150 it had: it opens a provider session on the org's own key, billed per second. The routes
|
|
5691
|
+
// on one call take a budget of their own so no eval read can spend it: a refused hang-up
|
|
5692
|
+
// leaves the call running, and billing. 500 because a call suite polls each run's record
|
|
5693
|
+
// every second, about 64 requests a minute per suite.
|
|
5694
|
+
"/api/evals/calls": { tier: "STANDARD", userLimit: 150, skipAuthenticatedUsers: true },
|
|
5695
|
+
"/api/evals/calls/*": {
|
|
5696
|
+
tier: "STANDARD",
|
|
5697
|
+
userLimit: 500,
|
|
5698
|
+
skipAuthenticatedUsers: true,
|
|
5699
|
+
userLimitKey: "/api/evals/calls/*"
|
|
5700
|
+
},
|
|
5701
|
+
"/api/files/*": { tier: "STANDARD", userLimit: 150, skipAuthenticatedUsers: true },
|
|
5702
|
+
// The org contact book (wayai#4931 PR 2b). A CSV import sends one request per
|
|
5703
|
+
// `CONTACT_IMPORT_BATCH_MAX` rows, so 150 a minute imports 75,000 rows a minute.
|
|
5704
|
+
"/api/contact-book/*": {
|
|
5705
|
+
tier: "STANDARD",
|
|
5706
|
+
userLimit: 150,
|
|
5707
|
+
skipAuthenticatedUsers: true,
|
|
5708
|
+
userLimitKey: "/api/contact-book/*"
|
|
5709
|
+
},
|
|
5710
|
+
// An automation email's one-click unsubscribe (wayai#4931 PR 2d) — public: its token is its one
|
|
5711
|
+
// credential, so this per-IP limit is its one bound, and runs before the route reads anything.
|
|
5712
|
+
// MODERATE (30/min per IP), as the community email's unsubscribe, for the same reason: mail
|
|
5713
|
+
// clients' one-click unsubscribe POSTs it from their own servers, which many recipients' clicks
|
|
5714
|
+
// share. An exact key, so it wins over the wildcard above.
|
|
5715
|
+
[CONTACT_BOOK_EMAIL_UNSUBSCRIBE_PATH]: { tier: "MODERATE" },
|
|
5716
|
+
"/api/automations/*": {
|
|
5717
|
+
tier: "STANDARD",
|
|
5718
|
+
userLimit: 150,
|
|
5719
|
+
skipAuthenticatedUsers: true,
|
|
5720
|
+
userLimitKey: "/api/automations/*"
|
|
5721
|
+
},
|
|
5722
|
+
// A hub's own contact lists (wayai#4931 PR 2c): config edits, at the automations' rate.
|
|
5723
|
+
"/api/hub-contact-lists/*": {
|
|
5724
|
+
tier: "STANDARD",
|
|
5725
|
+
userLimit: 150,
|
|
5726
|
+
skipAuthenticatedUsers: true,
|
|
5727
|
+
userLimitKey: "/api/hub-contact-lists/*"
|
|
5728
|
+
},
|
|
5729
|
+
"/api/message-templates/*": {
|
|
5730
|
+
tier: "STANDARD",
|
|
5731
|
+
userLimit: 150,
|
|
5732
|
+
skipAuthenticatedUsers: true,
|
|
5733
|
+
userLimitKey: "/api/message-templates/*"
|
|
5734
|
+
},
|
|
5735
|
+
"/api/live-updates/*": { tier: "STANDARD", userLimit: 150, skipAuthenticatedUsers: true },
|
|
5736
|
+
"/api/hub-users/*": {
|
|
5737
|
+
tier: "STANDARD",
|
|
5738
|
+
userLimit: 150,
|
|
5739
|
+
skipAuthenticatedUsers: true,
|
|
5740
|
+
userLimitKey: "/api/hub-users/*"
|
|
5741
|
+
},
|
|
5742
|
+
"/api/conversation-state/*": {
|
|
5743
|
+
tier: "STANDARD",
|
|
5744
|
+
userLimit: 150,
|
|
5745
|
+
skipAuthenticatedUsers: true,
|
|
5746
|
+
userLimitKey: "/api/conversation-state/*"
|
|
5747
|
+
},
|
|
5748
|
+
// The authenticated byte route (`routes/files/r2.ts`), mounted after its `authMiddleware` so
|
|
5749
|
+
// the caller is known: each request reads the HubDO its key names, and a hub id never created
|
|
5750
|
+
// still wakes a Durable Object (gh #4747; since gh #4775 T5 one that writes nothing). The key is pinned
|
|
5751
|
+
// because the paths are file keys, which would otherwise each get a fresh budget (and one
|
|
5752
|
+
// over 256 characters would be refused outright, `checkUserLimitNative`).
|
|
5753
|
+
//
|
|
5754
|
+
// 500 a minute because the web chat fetches every attachment of every loaded message at
|
|
5755
|
+
// once, with no list virtualization: up to 50 per 50-message page, 200 per ended
|
|
5756
|
+
// conversation or history entry at one file per message, and 4 per message at most in its
|
|
5757
|
+
// image grid. Repeats come from the browser's HTTP cache (`private, max-age=3600`); mobile
|
|
5758
|
+
// renders through a disk cache and plays audio from `/files/signed`. The budget is shared by
|
|
5759
|
+
// a caller's tabs and devices. The other two byte routes, `/files/signed/*` and
|
|
5760
|
+
// `/files/public/*`, mount no limiter (the reason is at their entries in
|
|
5761
|
+
// `security/endpoint-invariants.test.ts`'s route registry).
|
|
5762
|
+
"/files/auth/*": {
|
|
5763
|
+
tier: "STANDARD",
|
|
5764
|
+
userLimit: 500,
|
|
5765
|
+
skipAuthenticatedUsers: true,
|
|
5766
|
+
userLimitKey: "/files/auth/*"
|
|
5767
|
+
},
|
|
5768
|
+
// Channel webhooks - high throughput (external systems, never authenticated)
|
|
5769
|
+
"/webhooks/whatsapp/*": { tier: "HIGH" },
|
|
5770
|
+
"/webhooks/instagram/*": { tier: "HIGH" },
|
|
5771
|
+
// An automation's webhook deliveries (wayai#4931 D13) — no bearer token: the automation's
|
|
5772
|
+
// secret is the credential, checked against one HubDO read. This per-IP limit runs first, before
|
|
5773
|
+
// anything is read. HIGH, as the channel webhooks: senders are servers, and an integration
|
|
5774
|
+
// platform's egress addresses carry many tenants' deliveries. The per-automation budget is
|
|
5775
|
+
// `AUTOMATION_WEBHOOK_PER_AUTOMATION_LIMIT`, counted after the delivery is verified.
|
|
5776
|
+
[`${AUTOMATION_WEBHOOK_MOUNT}/*`]: { tier: "HIGH" },
|
|
5777
|
+
// Reports (CLI → platform_report triage queue). One per-user budget covers intake
|
|
5778
|
+
// (POST), the content edit (PATCH /:id), the reporter read-back (GET, GET /:id), and the
|
|
5779
|
+
// verification loop (POST /:id/accept, /:id/contest). The limiter is path-based, so a
|
|
5780
|
+
// method-level split (loose reads, strict writes) isn't expressible — GET /:id and PATCH
|
|
5781
|
+
// /:id share a path. So the budget is sized for the read-heavy poll loop (a reporter agent
|
|
5782
|
+
// polling `report get`/`list` while it waits for the fix), at 60/hour. Intake-flooding stays
|
|
5783
|
+
// bounded regardless: identical reports collapse on `fingerprint` (ON CONFLICT DO NOTHING),
|
|
5784
|
+
// so a runaway create loop merges rather than floods. Still STRICT tier + failClosed so a
|
|
5785
|
+
// genuinely runaway distinct-report loop surfaces as 429. The `/*` is load-bearing — an
|
|
5786
|
+
// exact `/api/reports` key would leave the `/:id` paths unmatched (→ unthrottled).
|
|
5787
|
+
// `userLimitKey` is equally load-bearing: without it, middleware falls back to each request's
|
|
5788
|
+
// path and report IDs/actions receive independent counters instead of this shared budget.
|
|
5789
|
+
"/api/reports/*": {
|
|
5790
|
+
tier: "STRICT",
|
|
5791
|
+
userLimit: 60,
|
|
5792
|
+
userWindow: 3600,
|
|
5793
|
+
failClosed: true,
|
|
5794
|
+
skipAuthenticatedUsers: true,
|
|
5795
|
+
userLimitKey: "/api/reports/*"
|
|
5796
|
+
},
|
|
5797
|
+
// Admin report mutations (transition/group/ungroup/edit) — platform-admin
|
|
5798
|
+
// gated at the route, rate-limited here as defense-in-depth. Generous STANDARD
|
|
5799
|
+
// cap so legitimate triage bursts aren't throttled (grouping is one request
|
|
5800
|
+
// even for many members); deliberately NOT failClosed so a Redis blip never
|
|
5801
|
+
// blocks an admin's triage. The bare GET reads share this budget harmlessly.
|
|
5802
|
+
"/api/admin/reports/*": { tier: "STANDARD", userLimit: 150, userLimitKey: "/api/admin/reports/*" },
|
|
5803
|
+
// GitHub webhook (issues.closed → platform_report addressed). MODERATE +
|
|
5804
|
+
// fail-closed; GitHub retries on transient failures so a brief Redis blip
|
|
5805
|
+
// doesn't lose state.
|
|
5806
|
+
"/webhooks/github": { tier: "MODERATE", failClosed: true },
|
|
5807
|
+
// Account deletion — strict on the destructive POST + public undo. The
|
|
5808
|
+
// /api/users/* default tier above ALSO matches these, but `getEndpointConfig`
|
|
5809
|
+
// returns the longest-prefix match, so the more specific entries here win.
|
|
5810
|
+
// `skipAuthenticatedUsers` deliberately omitted: every caller is authenticated,
|
|
5811
|
+
// so including it would make `userLimit` a no-op — the per-user cap IS the gate.
|
|
5812
|
+
"/api/users/me/deletion": { tier: "STRICT", userLimit: 3, userWindow: 3600, failClosed: true },
|
|
5813
|
+
"/api/users/me/deletion/preflight": { tier: "STANDARD", userLimit: 30, failClosed: true },
|
|
5814
|
+
"/api/users/deletion/undo": { tier: "STRICT", failClosed: true },
|
|
5815
|
+
// Production-base destruction.
|
|
5816
|
+
//
|
|
5817
|
+
// Keyed off the CONTRACT constants, not literals: `BASE_DESTRUCTION_CONFIRM_PATH`'s own
|
|
5818
|
+
// docstring promises that the mount, the `AUTH_BYPASS_PATHS` entry, the rate-limit key and
|
|
5819
|
+
// the URL the email embeds move together, and a literal here is exactly how that promise
|
|
5820
|
+
// goes quietly false — leaving an unauthenticated HMAC endpoint on the default tier.
|
|
5821
|
+
//
|
|
5822
|
+
// The emailed GET is PUBLIC, so it gets the IP-tier bound the pre-auth routes get.
|
|
5823
|
+
// `failClosed` is deliberately NOT set: it is only consulted on the per-USER branch, which
|
|
5824
|
+
// needs an authenticated caller, so on a pre-auth route it is inert — the same thing
|
|
5825
|
+
// `/api/auth/magic-code/send` above says about itself. The IP limiter fails OPEN on a
|
|
5826
|
+
// limiter error by design (`packages/runtime/src/rate-limiter.ts`), and claiming otherwise
|
|
5827
|
+
// would tell the next auditor a protection exists that does not.
|
|
5828
|
+
[BASE_DESTRUCTION_CONFIRM_PATH]: { tier: "STRICT" },
|
|
5829
|
+
// The console's read, which every production base's settings pane issues on mount. Without
|
|
5830
|
+
// its own entry it inherits the mount-wide budget below, and an admin clicking through
|
|
5831
|
+
// thirty bases in an hour would be 429'd by a limit sized for irreversible writes.
|
|
5832
|
+
[`${BASE_DESTRUCTION_PREFIX}/destructions`]: { tier: "STANDARD", userLimit: 150 },
|
|
5833
|
+
// The mutating routes: initiate, the authenticated confirm, and cancel. Per user rather than
|
|
5834
|
+
// per IP — all three are authenticated and org-admin gated — and hourly, since a single human
|
|
5835
|
+
// touching thirty production-deletion operations in an hour is already far past normal.
|
|
5836
|
+
// Longest-prefix wins, so the two entries above are not widened by this one. The key is
|
|
5837
|
+
// pinned so those thirty are counted across every base the caller touches (gh #4756).
|
|
5838
|
+
[`${BASE_DESTRUCTION_PREFIX}/*`]: {
|
|
5839
|
+
tier: "STANDARD",
|
|
5840
|
+
userLimit: 30,
|
|
5841
|
+
userWindow: 3600,
|
|
5842
|
+
failClosed: true,
|
|
5843
|
+
userLimitKey: `${BASE_DESTRUCTION_PREFIX}/*`
|
|
5844
|
+
},
|
|
5845
|
+
// Email-invite preview — public, fail-closed. MODERATE (30/min IP) leaves
|
|
5846
|
+
// headroom for the signup-page server-fetch + reloads + prefetches; STRICT
|
|
5847
|
+
// would throttle legitimate users opening the link in multiple tabs.
|
|
5848
|
+
"/api/invites/preview": { tier: "MODERATE", failClosed: true },
|
|
5849
|
+
// "Don't email me again" (gh #4661) — public; one POST per click on its confirm page. STRICT
|
|
5850
|
+
// per IP. No `failClosed`: it is consulted only on the per-user branch, which a pre-auth route
|
|
5851
|
+
// never reaches (see `BASE_DESTRUCTION_CONFIRM_PATH` above).
|
|
5852
|
+
"/api/invites/unsubscribe": { tier: "STRICT" },
|
|
5853
|
+
// Admin DO debug reads — the ONE deliberate deviation from the Data Explorer
|
|
5854
|
+
// catch-all below, and the only reason a per-route key still exists here.
|
|
5855
|
+
// Platform-admin is enforced at the route; this is the independent
|
|
5856
|
+
// defense-in-depth signal so a runaway investigation script surfaces as 429.
|
|
5857
|
+
//
|
|
5858
|
+
// STRICT is kept ON PURPOSE even though the catch-all moved off it: per IP, at 5/min vs
|
|
5859
|
+
// STANDARD's 100/min, walking 10k ids takes ~33h instead of ~100min — a detection window
|
|
5860
|
+
// worth keeping on the routes that return RAW DO ROWS.
|
|
5861
|
+
//
|
|
5862
|
+
// The cost, stated rather than elided: STRICT is one bucket shared with
|
|
5863
|
+
// `/oauth/callback/*` and `/api/auth/magic-code/*`, so a debug session from a
|
|
5864
|
+
// NAT'd office IP can 429 a colleague's login for up to a minute. That trade is
|
|
5865
|
+
// accepted for a rare, deliberate investigation surface — and is exactly why the
|
|
5866
|
+
// browse routes below are NOT here.
|
|
5867
|
+
//
|
|
5868
|
+
// The key is pinned, so a sweep of `/debug/do/hub/<id>/tables` across ids spends one
|
|
5869
|
+
// per-admin budget (gh #4756). 120 an hour: an agent's investigation of a dozen
|
|
5870
|
+
// conversations made 37 debug reads in its busiest hour.
|
|
5871
|
+
"/api/admin/data-explorer/debug/*": {
|
|
5872
|
+
tier: "STRICT",
|
|
5873
|
+
userLimit: 120,
|
|
5874
|
+
userWindow: 3600,
|
|
5875
|
+
failClosed: true,
|
|
5876
|
+
userLimitKey: "/api/admin/data-explorer/debug/*"
|
|
5877
|
+
},
|
|
5878
|
+
// Catch-all for the rest of the Data Explorer (`/search`, `/entity/*`, `/do/*`,
|
|
5879
|
+
// `/kv/*`, the DELETE routes). Added as a catch-all rather than another per-route
|
|
5880
|
+
// key because this budget kept lagging behind routes joining the debug boundary:
|
|
5881
|
+
// with no match the path falls to the default, which sets no `userLimit`, so the
|
|
5882
|
+
// per-user layer never runs at all. `getEndpointConfig` takes the longest match,
|
|
5883
|
+
// so `debug/*` above still wins.
|
|
5884
|
+
//
|
|
5885
|
+
// `/entity/*` deliberately has NO key of its own: it needs precisely this config,
|
|
5886
|
+
// and a duplicate entry is how the two drift apart on the next edit.
|
|
5887
|
+
//
|
|
5888
|
+
// `tier` is deliberately STANDARD, not STRICT. `tier` is the per-IP knob and
|
|
5889
|
+
// STRICT is 5/min keyed on IP ALONE — one bucket shared across every STRICT
|
|
5890
|
+
// endpoint. On these browse routes that breaks the console (open Browse KV and
|
|
5891
|
+
// inspect four values = 6 requests; expand six org rows in the search results =
|
|
5892
|
+
// 6 `/entity/org/:id` calls) and would drain the same IP's budget for
|
|
5893
|
+
// `/oauth/callback/*` and `/api/auth/magic-code/*`, which on a NAT'd office IP
|
|
5894
|
+
// means all admins. The per-user hourly cap is the leg that matters here.
|
|
5895
|
+
//
|
|
5896
|
+
// The canonical key is deliberate: request paths contain DO ids and arbitrary
|
|
5897
|
+
// malformed requests can contain credential material. Keying on the request path
|
|
5898
|
+
// both stores that material in Redis/logs and gives every target a fresh counter.
|
|
5899
|
+
// One stable identity makes every browse request consume the same per-admin
|
|
5900
|
+
// budget without storing request targets.
|
|
5901
|
+
//
|
|
5902
|
+
// 600/hour preserves the original pagination requirement (about 30k rows at
|
|
5903
|
+
// 50/page) while now genuinely bounding id/key enumeration across the surface.
|
|
5904
|
+
"/api/admin/data-explorer/*": {
|
|
5905
|
+
tier: "STANDARD",
|
|
5906
|
+
userLimit: 600,
|
|
5907
|
+
userWindow: 3600,
|
|
5908
|
+
failClosed: true,
|
|
5909
|
+
userLimitKey: "/api/admin/data-explorer/*"
|
|
5910
|
+
},
|
|
5911
|
+
// Admin harness incident-response (harness-agents PR 2.9b) — mass-destroy reaps
|
|
5912
|
+
// EVERY live harness session/token/slot in scope, so it gets a strict budget
|
|
5913
|
+
// (STRICT + a low per-user hourly cap + fail-closed). Platform-admin is enforced
|
|
5914
|
+
// at the route; this is the independent defense-in-depth signal so a runaway
|
|
5915
|
+
// reap loop surfaces as 429. The break-glass disable/enable rides through
|
|
5916
|
+
// `/api/admin/config` (its own STANDARD budget), not this key.
|
|
5917
|
+
"/api/admin/harness/*": {
|
|
5918
|
+
tier: "STRICT",
|
|
5919
|
+
userLimit: 10,
|
|
5920
|
+
userWindow: 3600,
|
|
5921
|
+
failClosed: true,
|
|
5922
|
+
userLimitKey: "/api/admin/harness/*"
|
|
5923
|
+
},
|
|
5924
|
+
// Platform-wide team-orphan repair — a single call can WRITE across every hub of
|
|
5925
|
+
// every org (HubDO purge + UserDO mirror fan-out), so it gets the same strict,
|
|
5926
|
+
// fail-closed budget as the other cross-platform admin write. Exact path (not a
|
|
5927
|
+
// prefix): the sibling `/api/admin/hubs/:hubId/harness` flag flips stay on the
|
|
5928
|
+
// default admin budget. Platform-admin is enforced at the route; this is the
|
|
5929
|
+
// defense-in-depth signal that a repair loop surfaces as 429.
|
|
5930
|
+
"/api/admin/hubs/repair-team-orphans": { tier: "STRICT", userLimit: 10, userWindow: 3600, failClosed: true },
|
|
5931
|
+
// Branching-orphan repair (#4577) — deletes named production rows, and its report mode
|
|
5932
|
+
// fans out over every production hub of every org, so it takes the same strict,
|
|
5933
|
+
// fail-closed budget as the team-orphan repair. Exact path, for the same reason.
|
|
5934
|
+
"/api/admin/hubs/repair-branching-orphans": { tier: "STRICT", userLimit: 10, userWindow: 3600, failClosed: true },
|
|
5935
|
+
// CI/GitOps endpoints - standard with user limits. 500 because `wayai push` makes four requests
|
|
5936
|
+
// (pull, diff, push, pull) and a GitOps job may push every hub at once with one token; when
|
|
5937
|
+
// each path held its own 100, that ran to 100 hubs a minute.
|
|
5938
|
+
"/api/ci/*": { tier: "STANDARD", userLimit: 500, skipAuthenticatedUsers: true, userLimitKey: "/api/ci/*" },
|
|
5939
|
+
// Attested `/v1` proxy onto the Rekor Worker. `userLimitKey` is load-bearing here: the
|
|
5940
|
+
// per-user budget defaults to the full request path, and this surface's paths carry base,
|
|
5941
|
+
// record, and file ids — so without a pinned key every distinct resource would get its own
|
|
5942
|
+
// fresh budget and the per-user limit would bound nothing. One shared budget across the
|
|
5943
|
+
// whole data surface is the intent.
|
|
5944
|
+
[`${DATA_PROXY_PREFIX}/*`]: {
|
|
5945
|
+
tier: "STANDARD",
|
|
5946
|
+
userLimit: 150,
|
|
5947
|
+
skipAuthenticatedUsers: true,
|
|
5948
|
+
userLimitKey: `${DATA_PROXY_PREFIX}/*`
|
|
5949
|
+
},
|
|
5950
|
+
// Voice calls — the end user's call routes (`routes/api/calls`). Every caller is signed
|
|
5951
|
+
// in, so the per-IP tier is skipped for them: Brazilian mobile carriers put many callers
|
|
5952
|
+
// behind one IP, and a shared bucket would refuse one caller for another's traffic.
|
|
5953
|
+
//
|
|
5954
|
+
// Create is the only route that spends: it opens a provider session on the org's own
|
|
5955
|
+
// key, billed per second. So it takes its own exact key and an hourly per-user cap, which
|
|
5956
|
+
// lives on Redis and is fail-closed like the other hourly caps. The exact key wins over
|
|
5957
|
+
// the wildcard below, which also matches `/api/calls` (see `getEndpointConfig`).
|
|
5958
|
+
"/api/calls": {
|
|
5959
|
+
tier: "STANDARD",
|
|
5960
|
+
userLimit: 30,
|
|
5961
|
+
userWindow: 3600,
|
|
5962
|
+
failClosed: true,
|
|
5963
|
+
skipAuthenticatedUsers: true,
|
|
5964
|
+
userLimitKey: "/api/calls"
|
|
5965
|
+
},
|
|
5966
|
+
// The routes on one call: `ready`, hang-up, the delegation report and the status read; and
|
|
5967
|
+
// the opening read (`/api/calls/openings/:language`), one per call, before it exists.
|
|
5968
|
+
// NEVER fail-closed: a refused hang-up leaves the call running, and billing, until a
|
|
5969
|
+
// limit ends it. The 60 s budget runs on the native binding, which fails open. The key is
|
|
5970
|
+
// pinned because the paths carry the call id, which would otherwise give every call a
|
|
5971
|
+
// fresh budget.
|
|
5972
|
+
"/api/calls/*": {
|
|
5973
|
+
tier: "STANDARD",
|
|
5974
|
+
userLimit: 150,
|
|
5975
|
+
skipAuthenticatedUsers: true,
|
|
5976
|
+
userLimitKey: "/api/calls/*"
|
|
5977
|
+
},
|
|
5978
|
+
// Contacts between accounts (gh #4661, `routes/api/contacts`). Every caller is signed in, so
|
|
5979
|
+
// the per-IP tier is skipped. The keys are pinned because the paths carry user and invite
|
|
5980
|
+
// ids, which would otherwise give each id a fresh budget. Blocks take their own, lower
|
|
5981
|
+
// budget (#4661 Limits: blocks are rate-limited). The invites-per-day cap and every list
|
|
5982
|
+
// cap are counted in the caller's UserDO, in the same write they bound, not here.
|
|
5983
|
+
"/api/contacts/*": {
|
|
5984
|
+
tier: "STANDARD",
|
|
5985
|
+
userLimit: 60,
|
|
5986
|
+
skipAuthenticatedUsers: true,
|
|
5987
|
+
userLimitKey: "/api/contacts/*"
|
|
5988
|
+
},
|
|
5989
|
+
"/api/contacts/blocks/*": {
|
|
5990
|
+
tier: "STANDARD",
|
|
5991
|
+
userLimit: 30,
|
|
5992
|
+
skipAuthenticatedUsers: true,
|
|
5993
|
+
userLimitKey: "/api/contacts/blocks/*"
|
|
5994
|
+
},
|
|
5995
|
+
// Person-to-person threads (gh #4661, `routes/api/threads`): the per-user send limit, 60 a
|
|
5996
|
+
// minute on the native binding. Every thread route shares the one budget, reads included:
|
|
5997
|
+
// the paths carry thread and message ids, so the key is pinned, and a pattern here cannot
|
|
5998
|
+
// tell a send from a read of the same path. Like every 60 s budget it fails open on a
|
|
5999
|
+
// binding error, so it bounds sends in the steady state only.
|
|
6000
|
+
"/api/threads/*": {
|
|
6001
|
+
tier: "STANDARD",
|
|
6002
|
+
userLimit: 60,
|
|
6003
|
+
skipAuthenticatedUsers: true,
|
|
6004
|
+
userLimitKey: "/api/threads/*"
|
|
6005
|
+
},
|
|
6006
|
+
// An org admin's erasure of one person's thread messages (gh #4661 owner decision 7): any user
|
|
6007
|
+
// can own a free org, so each request arms that org's alarm, and this caps how fast one person
|
|
6008
|
+
// can ask. The org's own daily cap (`PEOPLE_ORG_ERASURES_PER_DAY`, counted in its OrgDO in the
|
|
6009
|
+
// write it bounds) is the fail-closed bound; this budget, on the native binding, fails open.
|
|
6010
|
+
"/api/threads/org-erasures": {
|
|
6011
|
+
tier: "STANDARD",
|
|
6012
|
+
userLimit: 10,
|
|
6013
|
+
skipAuthenticatedUsers: true,
|
|
6014
|
+
userLimitKey: "/api/threads/org-erasures"
|
|
6015
|
+
},
|
|
6016
|
+
// A person's report of a message, thread or person (`people-reports.ts`): each one written
|
|
6017
|
+
// arms the PlatformDO's alarm and emails the platform admins, so it takes its own, lower
|
|
6018
|
+
// budget. The reporter's daily cap (`PEOPLE_REPORTS_PER_DAY`, counted in the report store in
|
|
6019
|
+
// the write it bounds) is the fail-closed bound; this budget, on the native binding, fails open.
|
|
6020
|
+
"/api/threads/reports": {
|
|
6021
|
+
tier: "STANDARD",
|
|
6022
|
+
userLimit: 10,
|
|
6023
|
+
skipAuthenticatedUsers: true,
|
|
6024
|
+
userLimitKey: "/api/threads/reports"
|
|
6025
|
+
},
|
|
6026
|
+
// Communities (gh #4872, `routes/api/communities`): every signed-in route under the mount (the
|
|
6027
|
+
// preview below excepted) shares one per-user budget, which is also each member's send limit and
|
|
6028
|
+
// bounds the reads that scan a community's rows (a title search, the stats); a community's own
|
|
6029
|
+
// limits (its rate, the daily caps, slow mode) are counted in its CommunityDO, in the write they
|
|
6030
|
+
// bound. Every caller is signed in, so the per-IP tier is skipped; the key is pinned because the
|
|
6031
|
+
// paths carry community, room and message ids.
|
|
6032
|
+
"/api/communities/*": {
|
|
6033
|
+
tier: "STANDARD",
|
|
6034
|
+
userLimit: 60,
|
|
6035
|
+
skipAuthenticatedUsers: true,
|
|
6036
|
+
userLimitKey: "/api/communities/*"
|
|
6037
|
+
},
|
|
6038
|
+
// The join page's preview of a link — public, fetched from the visitor's browser, so this
|
|
6039
|
+
// per-IP limit is its one bound and runs before the route reads anything (#4872
|
|
6040
|
+
// owner-delegated decision 9). MODERATE (30/min per IP) leaves room for reloads and several
|
|
6041
|
+
// tabs. An exact key, so it wins over the wildcard above.
|
|
6042
|
+
"/api/communities/join-preview": { tier: "MODERATE" },
|
|
6043
|
+
// An announcement email's unsubscribe (#4872 PR 8a) — public: its token is its one credential, so
|
|
6044
|
+
// this per-IP limit is its one bound, and runs before the route reads anything. MODERATE (30/min
|
|
6045
|
+
// per IP), not STRICT: mail clients' one-click unsubscribe POSTs it from their own servers, which
|
|
6046
|
+
// many members' clicks share, and STRICT's 5/min bucket is shared with sign-in. An exact key, so
|
|
6047
|
+
// it wins over the wildcard above.
|
|
6048
|
+
[COMMUNITY_EMAIL_UNSUBSCRIBE_PATH]: { tier: "MODERATE" },
|
|
6049
|
+
// The WayAI Guide's help entry (wayai#4948, `routes/api/guide.ts`): one POST per click by a viewer
|
|
6050
|
+
// the snapshot does not show enrolled, which reads the person's row on the Guide hub and writes only
|
|
6051
|
+
// what is missing (an enrolled viewer's click opens the hub without a request). Every caller is
|
|
6052
|
+
// signed in, so the per-IP tier is skipped; 10 a minute per person on the native binding (fails
|
|
6053
|
+
// open, like every 60 s budget) bounds what a looping client costs the one Guide HubDO.
|
|
6054
|
+
"/api/guide/access": {
|
|
6055
|
+
tier: "STANDARD",
|
|
6056
|
+
userLimit: 10,
|
|
6057
|
+
skipAuthenticatedUsers: true,
|
|
6058
|
+
userLimitKey: "/api/guide/access"
|
|
6059
|
+
},
|
|
6060
|
+
// App channel - high throughput with user limits
|
|
6061
|
+
"/channels/app/*": { tier: "HIGH", userLimit: 500, skipAuthenticatedUsers: true },
|
|
6062
|
+
// API channel (api-channel-connector) - machine-to-machine, high throughput
|
|
6063
|
+
"/channels/api/*": { tier: "HIGH", userLimit: 500, skipAuthenticatedUsers: true },
|
|
6064
|
+
// System/scheduled - high throughput (internal)
|
|
6065
|
+
"/channels/system/*": { tier: "HIGH", skipAuthenticatedUsers: true },
|
|
6066
|
+
"/scheduled/*": { tier: "HIGH", skipAuthenticatedUsers: true },
|
|
6067
|
+
// WebSocket - standard with user limit to prevent connection spam
|
|
6068
|
+
"/live-updates/ws": { tier: "STANDARD", userLimit: 100, failClosed: true },
|
|
6069
|
+
// A community's live socket (gh #4872 PR 5, `GET /communities/:communityId/live`): outside
|
|
6070
|
+
// `/api/*`, with no bearer token, so its per-IP tier is its one bound before the CommunityDO
|
|
6071
|
+
// checks the ticket. STANDARD, as `/live-updates/ws`: a client opens one per community it shows
|
|
6072
|
+
// and reconnects within the hour.
|
|
6073
|
+
"/communities/*": { tier: "STANDARD" },
|
|
6074
|
+
// WS ticket mint - per-user 60/min accommodates aggressive reconnect patterns
|
|
6075
|
+
// without opening abuse vectors; fail-closed so Redis outages don't let
|
|
6076
|
+
// runaway clients mint unbounded tickets.
|
|
6077
|
+
"/api/auth/ws-ticket": { tier: "MODERATE", userLimit: 60, failClosed: true, skipAuthenticatedUsers: true },
|
|
6078
|
+
"/api/auth/session-check": { tier: "STANDARD", userLimit: 120, skipAuthenticatedUsers: true },
|
|
6079
|
+
"/api/auth/logout": { tier: "MODERATE", userLimit: 10, failClosed: true, skipAuthenticatedUsers: true }
|
|
6080
|
+
};
|
|
6081
|
+
EXCLUDED_PATHS = /* @__PURE__ */ new Set(["/health"]);
|
|
6082
|
+
}
|
|
6083
|
+
});
|
|
6084
|
+
|
|
6085
|
+
// ../../packages/core/dist/chunk-CGOLULK2.js
|
|
6086
|
+
function fileSnapshotChanged(run) {
|
|
6087
|
+
return run.written + run.removed > 0;
|
|
6088
|
+
}
|
|
6089
|
+
async function runFileSnapshot(opts) {
|
|
6090
|
+
const run = { pages: 0, written: 0, removed: 0, unchanged: 0 };
|
|
6091
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6092
|
+
let cursor;
|
|
6093
|
+
while (run.pages < FILE_SNAPSHOT_MAX_PAGES) {
|
|
6094
|
+
const page = await sendWithRetry(opts, cursor);
|
|
6095
|
+
run.pages++;
|
|
6096
|
+
run.written += page.written;
|
|
6097
|
+
run.removed += page.removed;
|
|
6098
|
+
run.unchanged += page.unchanged;
|
|
6099
|
+
opts.onPage?.(page, run.pages);
|
|
6100
|
+
if (page.next_cursor === null) return run;
|
|
6101
|
+
if (seen.has(page.next_cursor)) {
|
|
6102
|
+
throw new Error(`The preview file snapshot repeated its cursor after page ${run.pages}, so it would never end.`);
|
|
6103
|
+
}
|
|
6104
|
+
seen.add(page.next_cursor);
|
|
6105
|
+
cursor = page.next_cursor;
|
|
6106
|
+
}
|
|
6107
|
+
throw new Error(`The preview file snapshot did not end after ${FILE_SNAPSHOT_MAX_PAGES} pages.`);
|
|
6108
|
+
}
|
|
6109
|
+
async function sendWithRetry(opts, cursor) {
|
|
6110
|
+
for (let attempt = 1; ; attempt++) {
|
|
6111
|
+
let body;
|
|
6112
|
+
try {
|
|
6113
|
+
body = await opts.sendPage(cursor);
|
|
6114
|
+
} catch (error) {
|
|
6115
|
+
if (attempt < FILE_SNAPSHOT_CONFLICT_ATTEMPTS && opts.isConflict(error)) continue;
|
|
6116
|
+
throw error;
|
|
6117
|
+
}
|
|
6118
|
+
const parsed = rekorFileSnapshotAnswerSchema.safeParse(body);
|
|
6119
|
+
if (!parsed.success) {
|
|
6120
|
+
throw new Error("The data product answered a preview file snapshot page WayAI cannot read: nothing more was sent.");
|
|
6121
|
+
}
|
|
6122
|
+
return parsed.data.data;
|
|
6123
|
+
}
|
|
6124
|
+
}
|
|
6125
|
+
var rekorFileSnapshotAnswerSchema, FILE_SNAPSHOT_MAX_PAGES, FILE_SNAPSHOT_CONFLICT_ATTEMPTS, FILE_SNAPSHOT_PAGE_SPACING_MS, FILE_SNAPSHOT_CONFLICT_STATUS;
|
|
6126
|
+
var init_chunk_CGOLULK2 = __esm({
|
|
6127
|
+
"../../packages/core/dist/chunk-CGOLULK2.js"() {
|
|
6128
|
+
"use strict";
|
|
6129
|
+
init_chunk_IEWJ5MZH();
|
|
6130
|
+
init_chunk_F3JHI4Y4();
|
|
6131
|
+
init_zod();
|
|
6132
|
+
rekorFileSnapshotAnswerSchema = external_exports.object({
|
|
6133
|
+
data: external_exports.object({
|
|
6134
|
+
next_cursor: external_exports.string().nullable(),
|
|
6135
|
+
/** Files the page created or replaced on the preview. */
|
|
6136
|
+
written: external_exports.number().int(),
|
|
6137
|
+
/** The preview's own files, ones production does not hold, the page removed. */
|
|
6138
|
+
removed: external_exports.number().int(),
|
|
6139
|
+
/** Files the page found already equal to production's. */
|
|
6140
|
+
unchanged: external_exports.number().int()
|
|
6141
|
+
})
|
|
6142
|
+
});
|
|
6143
|
+
FILE_SNAPSHOT_MAX_PAGES = 1e4;
|
|
6144
|
+
FILE_SNAPSHOT_CONFLICT_ATTEMPTS = 3;
|
|
6145
|
+
FILE_SNAPSHOT_PAGE_SPACING_MS = Math.ceil(
|
|
6146
|
+
6e4 / Math.max(1, Math.floor((getEndpointConfig(`${DATA_PROXY_PREFIX}/files/snapshot`)?.userLimit ?? 60) * 4 / 5))
|
|
6147
|
+
);
|
|
6148
|
+
FILE_SNAPSHOT_CONFLICT_STATUS = 409;
|
|
6149
|
+
}
|
|
6150
|
+
});
|
|
6151
|
+
|
|
5556
6152
|
// ../../packages/core/dist/chunk-BEOHBREG.js
|
|
5557
6153
|
var REKOR_ACTOR_TYPE_HEADER, REKOR_ACTOR_ID_HEADER, REKOR_ACTOR_LABEL_HEADER, REKOR_ACTOR_ORG_HEADER, REKOR_ACTOR_ADMIN_HEADER, REKOR_CLIENT_HEADER, REKOR_TOOL_ID_HEADER, REKOR_CLIENTS, rekorClientSchema, MAX_REKOR_TOOL_ID_LENGTH, rekorToolIdSchema, REKOR_V1_PREFIX, DATA_PROXY_ORG_QUERY_PARAM, MAX_PROVISIONING_BODY_BYTES, dataProxyQuery, rekorAttestation, REQUIRED_REKOR_ATTESTATION_HEADERS, REKOR_BOUND_BASE_HEADER, REKOR_BOUND_TOOLSET_HEADER, REKOR_BOUND_TOKEN_HEADER, REKOR_BOUND_CUSTOMER_HEADER, DATA_BASE_GENERATION_HEADER, REKOR_MAX_ACTOR_ID_LENGTH, REKOR_MAX_ACTOR_LABEL_LENGTH;
|
|
5558
6154
|
var init_chunk_BEOHBREG = __esm({
|
|
@@ -5596,14 +6192,20 @@ var init_chunk_BEOHBREG = __esm({
|
|
|
5596
6192
|
}
|
|
5597
6193
|
});
|
|
5598
6194
|
|
|
5599
|
-
// ../../packages/core/dist/chunk-
|
|
5600
|
-
|
|
5601
|
-
|
|
5602
|
-
|
|
6195
|
+
// ../../packages/core/dist/chunk-4MRBBR5L.js
|
|
6196
|
+
function toolsetFileToolName(entry, op) {
|
|
6197
|
+
const names = entry.names;
|
|
6198
|
+
const stated = names?.[op];
|
|
6199
|
+
return stated !== void 0 && stated !== null ? String(stated) : `${op}_${entry.name ?? entry.file_type}`;
|
|
6200
|
+
}
|
|
6201
|
+
var TOOLSET_SLUG_SETTING, TOOLSET_SLUG_PATTERN, REKOR_STRUCTURAL_ID_PATTERN, baseEnvironmentSchema, TOOLSET_FILE_OPERATIONS, toolsetFileToolSchema, toolsetSurfaceSchema, toolsetConnectionBindingSchema, rekorToolsetToolsSchema, rekorBoundAttestation, rekorResolvedToolsetSchema, resolvedFileToolSchema, rekorToolsetFileSchema, rekorToolsetFileListSchema, rekorToolsetFileGetSchema, rekorToolsetFilePutSchema, TOOLSET_CUSTOMER_FACING_REFUSAL_MARK, TOOLSET_CUSTOMER_FACING_REFUSAL;
|
|
6202
|
+
var init_chunk_4MRBBR5L = __esm({
|
|
6203
|
+
"../../packages/core/dist/chunk-4MRBBR5L.js"() {
|
|
5603
6204
|
"use strict";
|
|
5604
6205
|
init_chunk_BEOHBREG();
|
|
5605
6206
|
init_chunk_DHPUPI4Y();
|
|
5606
6207
|
init_zod();
|
|
6208
|
+
TOOLSET_SLUG_SETTING = "toolset";
|
|
5607
6209
|
TOOLSET_SLUG_PATTERN = /^[A-Za-z0-9_-]{1,128}$/;
|
|
5608
6210
|
REKOR_STRUCTURAL_ID_PATTERN = TOOLSET_SLUG_PATTERN;
|
|
5609
6211
|
baseEnvironmentSchema = external_exports.enum(["preview", "production"]);
|
|
@@ -5900,25 +6502,6 @@ var init_chunk_IPDOHAZ5 = __esm({
|
|
|
5900
6502
|
}
|
|
5901
6503
|
});
|
|
5902
6504
|
|
|
5903
|
-
// ../../packages/core/dist/chunk-GPM7PE44.js
|
|
5904
|
-
var bootstrapQuery;
|
|
5905
|
-
var init_chunk_GPM7PE44 = __esm({
|
|
5906
|
-
"../../packages/core/dist/chunk-GPM7PE44.js"() {
|
|
5907
|
-
"use strict";
|
|
5908
|
-
init_zod();
|
|
5909
|
-
bootstrapQuery = external_exports.object({
|
|
5910
|
-
active_hub_id: external_exports.string().uuid().optional(),
|
|
5911
|
-
active_conversation_id: external_exports.string().uuid().optional(),
|
|
5912
|
-
active_nav: external_exports.enum(["chat", "task", "support"]).optional(),
|
|
5913
|
-
/**
|
|
5914
|
-
* The people thread the URL names (`/chat/threads/:threadId`, gh #4661): the snapshot
|
|
5915
|
-
* preloads it as `people.active_thread` when the viewer is a member.
|
|
5916
|
-
*/
|
|
5917
|
-
active_thread_id: external_exports.string().uuid().optional()
|
|
5918
|
-
});
|
|
5919
|
-
}
|
|
5920
|
-
});
|
|
5921
|
-
|
|
5922
6505
|
// ../../packages/core/dist/chunk-YUZFHVB2.js
|
|
5923
6506
|
var invitePreviewQuery, inviteRolePreviewResponse, inviteContactPreviewResponse, invitePreviewResponse, inviteUnsubscribeBody;
|
|
5924
6507
|
var init_chunk_YUZFHVB2 = __esm({
|
|
@@ -6206,7 +6789,7 @@ var init_chunk_E4DIIOOE = __esm({
|
|
|
6206
6789
|
}
|
|
6207
6790
|
});
|
|
6208
6791
|
|
|
6209
|
-
// ../../packages/core/dist/chunk-
|
|
6792
|
+
// ../../packages/core/dist/chunk-4MXPUC6I.js
|
|
6210
6793
|
function spokenLineField(label, description2) {
|
|
6211
6794
|
return { type: "text", label, maxLength: SPOKEN_LINE_MAX_CHARS, default: "", description: description2 };
|
|
6212
6795
|
}
|
|
@@ -6216,8 +6799,8 @@ function byLowercaseName(a, b) {
|
|
|
6216
6799
|
return x < y ? -1 : x > y ? 1 : 0;
|
|
6217
6800
|
}
|
|
6218
6801
|
var CALLER_LANGUAGE, TTS_VOICE_REPLY_ENABLED_FIELD, COPILOT_TRIGGER_FIELD, SANDBOX_AGENT_FIELDS, AUDIO_LANGUAGE_OPTIONS, whatsapp, instagram, resend, telegram, API_CHANNEL_DELIVERY_EVENTS, apiChannel, OPENAI_REASONING_MODELS, openai, anthropic, googleAiStudio, openRouter, xai, groqStt, openaiStt, elevenLabsStt, openaiTts, groqTts, elevenLabsTts, GEMINI_TTS_VOICES, googleTts, wayai, externalResources, restApiTool, mcpServer, REKOR_TOOLSET_SERVICE_NAME, rekorToolset, CLAUDE_HARNESS_MODELS, CLAUDE_HARNESS_MODEL_OPTIONS, HARNESS_MCP_SERVERS_FIELD, HARNESS_EGRESS_FIELDS, claudeManagedAgents, SPOKEN_LINE_MAX_CHARS, gptLive, CONNECTORS;
|
|
6219
|
-
var
|
|
6220
|
-
"../../packages/core/dist/chunk-
|
|
6802
|
+
var init_chunk_4MXPUC6I = __esm({
|
|
6803
|
+
"../../packages/core/dist/chunk-4MXPUC6I.js"() {
|
|
6221
6804
|
"use strict";
|
|
6222
6805
|
CALLER_LANGUAGE = "caller";
|
|
6223
6806
|
TTS_VOICE_REPLY_ENABLED_FIELD = {
|
|
@@ -6245,7 +6828,17 @@ var init_chunk_FMXFEZN4 = __esm({
|
|
|
6245
6828
|
type: "toggle",
|
|
6246
6829
|
label: "Code Execution Sandbox",
|
|
6247
6830
|
default: false,
|
|
6248
|
-
description: "Let this agent run code in a WayAI sandbox with the sandbox tools, bash, copy_to_sandbox and copy_from_sandbox (add them under Tools):
|
|
6831
|
+
description: "Let this agent run code in a WayAI sandbox with the sandbox tools, bash, copy_to_sandbox and copy_from_sandbox (add them under Tools): a disposable Linux container, shared with the conversation's other agents or private to this agent (Sandbox Scope), started on first use and ended after 10 minutes without use, 60 minutes in all, or when the conversation closes."
|
|
6832
|
+
},
|
|
6833
|
+
sandbox_scope: {
|
|
6834
|
+
type: "select",
|
|
6835
|
+
label: "Sandbox Scope",
|
|
6836
|
+
options: [
|
|
6837
|
+
{ value: "conversation", label: "Conversation (shared with the conversation's other agents)" },
|
|
6838
|
+
{ value: "agent", label: "Agent (private to this agent)" }
|
|
6839
|
+
],
|
|
6840
|
+
default: "conversation",
|
|
6841
|
+
description: "Whose sandbox this agent uses. Conversation: shared with the conversation's other agents; every agent sharing it sees its files, and agents sharing it need the same Sandbox Network setting, or a command from one ends the sandbox and starts an empty one. Agent: private to this agent; no other agent sees its files. Set Agent for an agent that handles files others must not see, such as a copilot with internal documents. Each agent sandbox is its own container, counts toward the organization's limit on running sandboxes, and ends with the conversation."
|
|
6249
6842
|
},
|
|
6250
6843
|
sandbox_network: {
|
|
6251
6844
|
type: "select",
|
|
@@ -7171,13 +7764,13 @@ var init_chunk_FMXFEZN4 = __esm({
|
|
|
7171
7764
|
}
|
|
7172
7765
|
});
|
|
7173
7766
|
|
|
7174
|
-
// ../../packages/core/dist/chunk-
|
|
7767
|
+
// ../../packages/core/dist/chunk-NTJMGSSO.js
|
|
7175
7768
|
var evalCallInstant, EVAL_CALL_MIN_SECONDS, EVAL_CALL_MAX_SECONDS, EVAL_CALL_SPOKEN_LINE_MAX_CHARS, hubScoped2, createEvalCallBody, createEvalCallResponse, evalCallConversationQuery, evalCallRecordQuery, evalCallSpeaker, evalCallUtterance, evalCallTurn, evalCallRecording, evalCallRecordResponse, evalCallFinishResponse;
|
|
7176
|
-
var
|
|
7177
|
-
"../../packages/core/dist/chunk-
|
|
7769
|
+
var init_chunk_NTJMGSSO = __esm({
|
|
7770
|
+
"../../packages/core/dist/chunk-NTJMGSSO.js"() {
|
|
7178
7771
|
"use strict";
|
|
7179
7772
|
init_chunk_E4DIIOOE();
|
|
7180
|
-
|
|
7773
|
+
init_chunk_4MXPUC6I();
|
|
7181
7774
|
init_chunk_HZ6CADSY();
|
|
7182
7775
|
init_chunk_MIWZOSPI();
|
|
7183
7776
|
init_zod();
|
|
@@ -7544,15 +8137,15 @@ var init_chunk_MH4O3YEX = __esm({
|
|
|
7544
8137
|
}
|
|
7545
8138
|
});
|
|
7546
8139
|
|
|
7547
|
-
// ../../packages/core/dist/chunk-
|
|
8140
|
+
// ../../packages/core/dist/chunk-CWUV6NL3.js
|
|
7548
8141
|
function catalogToolSideEffects(toolName) {
|
|
7549
8142
|
const sideEffects = CATALOG_SIDE_EFFECTS.get(toolName);
|
|
7550
8143
|
if (sideEffects === void 0) return "unknown";
|
|
7551
8144
|
return sideEffects ? "side_effects" : "none";
|
|
7552
8145
|
}
|
|
7553
8146
|
var NATIVE_TOOL_SCHEMAS, WAYAI_CONNECTOR, SANDBOX_AGENT_ROLES, BASE_NATIVE_TOOLS, NATIVE_TOOLS, CATALOG_SIDE_EFFECTS;
|
|
7554
|
-
var
|
|
7555
|
-
"../../packages/core/dist/chunk-
|
|
8147
|
+
var init_chunk_CWUV6NL3 = __esm({
|
|
8148
|
+
"../../packages/core/dist/chunk-CWUV6NL3.js"() {
|
|
7556
8149
|
"use strict";
|
|
7557
8150
|
init_chunk_DHPUPI4Y();
|
|
7558
8151
|
NATIVE_TOOL_SCHEMAS = {
|
|
@@ -7900,14 +8493,14 @@ var init_chunk_LDSABSRL = __esm({
|
|
|
7900
8493
|
"copy_to_sandbox": {
|
|
7901
8494
|
"tool_config": {
|
|
7902
8495
|
"name": "copy_to_sandbox",
|
|
7903
|
-
"description": `Copy one of this conversation's files into the code sandbox, so commands run with bash can read or edit it there. path names the file as read_file does: this conversation's attachments under "conversation/" (e.g. "conversation/report.xlsx"),
|
|
8496
|
+
"description": `Copy one of this conversation's files, or one of your documents, into the code sandbox, so commands run with bash can read or edit it there. path names the file as read_file does: this conversation's attachments under "conversation/" (e.g. "conversation/report.xlsx"), an earlier conversation's under "conversations/<conversation_id>/<name>", or a document under "data/<file-type>/" (e.g. "data/contracts/acme/msa.docx"), whose result gives the version you copied: pass it to copy_from_sandbox to copy the document back. Hub resource files cannot be copied. The copy lands at sandbox_path, replacing any file there; a file larger than 25 MiB is refused, and a document larger than 1 MiB. If no sandbox is running, the copy starts an empty one.`,
|
|
7904
8497
|
"strict": false,
|
|
7905
8498
|
"parameters": {
|
|
7906
8499
|
"type": "object",
|
|
7907
8500
|
"properties": {
|
|
7908
8501
|
"path": {
|
|
7909
8502
|
"type": "string",
|
|
7910
|
-
"description": `The file to copy, as read_file names it: "conversation/<name>" (a shorter unambiguous form or the file's id also works)
|
|
8503
|
+
"description": `The file to copy, as read_file names it: "conversation/<name>" (a shorter unambiguous form or the file's id also works), "conversations/<conversation_id>/<name>", or a document's full path "data/<file-type>/<path>".`
|
|
7911
8504
|
},
|
|
7912
8505
|
"sandbox_path": {
|
|
7913
8506
|
"type": "string",
|
|
@@ -7925,7 +8518,7 @@ var init_chunk_LDSABSRL = __esm({
|
|
|
7925
8518
|
"copy_from_sandbox": {
|
|
7926
8519
|
"tool_config": {
|
|
7927
8520
|
"name": "copy_from_sandbox",
|
|
7928
|
-
"description":
|
|
8521
|
+
"description": `Copy a file out of the code sandbox into this conversation, so what your commands produced (a chart, a converted document) is kept after the sandbox ends. The file is stored for the support team on an internal note and is NOT sent to the customer: the result gives its path under "conversation/", and send_files with that path delivers it when it should reach them. A file larger than 25 MiB is refused. With path, the file is written instead to one of your documents under "data/<file-type>/", with the version you copied it in at: the write goes through only while that is still the document's current version, so a document changed since is refused, and only where you may write documents. A document holds at most 1 MiB.`,
|
|
7929
8522
|
"strict": false,
|
|
7930
8523
|
"parameters": {
|
|
7931
8524
|
"type": "object",
|
|
@@ -7936,7 +8529,15 @@ var init_chunk_LDSABSRL = __esm({
|
|
|
7936
8529
|
},
|
|
7937
8530
|
"name": {
|
|
7938
8531
|
"type": "string",
|
|
7939
|
-
"description": `The stored file's name, with no folders (e.g. "q3-report.xlsx"). Defaults to the file's name in the sandbox.`
|
|
8532
|
+
"description": `The stored file's name, with no folders (e.g. "q3-report.xlsx"). Defaults to the file's name in the sandbox. Not with path.`
|
|
8533
|
+
},
|
|
8534
|
+
"path": {
|
|
8535
|
+
"type": "string",
|
|
8536
|
+
"description": 'A document to write the file to instead, by its full path, e.g. "data/contracts/acme/msa.docx", as copy_to_sandbox or list_files gives it. Leave it out to keep the file with this conversation.'
|
|
8537
|
+
},
|
|
8538
|
+
"version": {
|
|
8539
|
+
"type": "integer",
|
|
8540
|
+
"description": "With path: the version copy_to_sandbox (or read_file) gave for the document, to replace it. Leave it out to create a new document."
|
|
7940
8541
|
}
|
|
7941
8542
|
},
|
|
7942
8543
|
"required": [
|
|
@@ -8524,11 +9125,18 @@ var init_chunk_LDSABSRL = __esm({
|
|
|
8524
9125
|
// setting is on, on any provider. Side effects: under an Allowlist or Web network setting, the
|
|
8525
9126
|
// code it runs can send data out of the conversation.
|
|
8526
9127
|
{ tool_native_id: "nt-030", tool_name: "bash", tool_display_name: "Run Code (Bash)", tool_description: "Run a Bash command in the conversation's code sandbox", connector_id: WAYAI_CONNECTOR, operation: "bash", tool_method: "internal", tool_group: "Native Tools", is_delegation: false, is_auto_set: false, execution_config: null, side_effects: true, replay: "reconcile", assignable_roles: SANDBOX_AGENT_ROLES },
|
|
8527
|
-
// Copies a conversation file
|
|
8528
|
-
// leaves the conversation.
|
|
9128
|
+
// Copies a conversation file, or a toolset document (`data/`, read through the toolset's `get`),
|
|
9129
|
+
// INTO the sandbox: the sandbox is conversation state, so nothing leaves the conversation. The
|
|
9130
|
+
// conversation's sandbox is shared by its agents whose `sandbox_scope` is `conversation`, so a
|
|
9131
|
+
// document copied in is there for each of them, as anything else an agent leaves in it; an agent
|
|
9132
|
+
// set to `agent` copies into its own, which no other agent reaches (decision 147).
|
|
8529
9133
|
{ tool_native_id: "nt-031", tool_name: "copy_to_sandbox", tool_display_name: "Copy File to Sandbox", tool_description: "Copy one of the conversation's files into its code sandbox", connector_id: WAYAI_CONNECTOR, operation: "copy_to_sandbox", tool_method: "internal", tool_group: "Native Tools", is_delegation: false, is_auto_set: false, execution_config: null, side_effects: false, replay: "rerun", assignable_roles: SANDBOX_AGENT_ROLES },
|
|
8530
9134
|
// Copies a sandbox file OUT into the conversation, as an internal note for the team: nothing
|
|
8531
|
-
// reaches the customer until `send_files` sends it.
|
|
9135
|
+
// reaches the customer until `send_files` sends it. With `path`, it writes a toolset document
|
|
9136
|
+
// instead, through the toolset's `put` with its version check: such a call is `reconcile`, not
|
|
9137
|
+
// `keyed`, as `write_file` is (`toolCallReplay` reads its arguments). Its side effects stay `false`:
|
|
9138
|
+
// only an agent holding a toolset `put` can write one, never a customer-facing role (premise 8),
|
|
9139
|
+
// and a voice call's turns, which read this flag, run the pilot track, which is customer-facing.
|
|
8532
9140
|
{ tool_native_id: "nt-032", tool_name: "copy_from_sandbox", tool_display_name: "Copy File from Sandbox", tool_description: "Copy a file out of the code sandbox into the conversation, for the team", connector_id: WAYAI_CONNECTOR, operation: "copy_from_sandbox", tool_method: "internal", tool_group: "Native Tools", is_delegation: false, is_auto_set: false, execution_config: null, side_effects: false, replay: "keyed", assignable_roles: SANDBOX_AGENT_ROLES },
|
|
8533
9141
|
// The `data/` mount's writes (#5142 step 2): a document of the agent's Base Toolset, written
|
|
8534
9142
|
// through the toolset's own `put` tool with its version check. `reconcile`, not `keyed`: a re-run
|
|
@@ -8569,11 +9177,104 @@ var init_chunk_IBL7FANV = __esm({
|
|
|
8569
9177
|
});
|
|
8570
9178
|
|
|
8571
9179
|
// ../../packages/core/dist/chunk-PFDZEKDE.js
|
|
8572
|
-
|
|
9180
|
+
function parseExpr(expr) {
|
|
9181
|
+
const fnMatch = expr.match(/^([a-zA-Z_][a-zA-Z0-9_]*)\s*\((.*?)\)\s*(?:\.(.*))?$/);
|
|
9182
|
+
if (!fnMatch) return null;
|
|
9183
|
+
const [, fn, rawArgs, rawPath] = fnMatch;
|
|
9184
|
+
const args2 = splitArgs(rawArgs);
|
|
9185
|
+
const path42 = rawPath ? rawPath.split(".").flatMap((seg) => {
|
|
9186
|
+
const indexed = [];
|
|
9187
|
+
seg.replace(/([^\[\]]+)|\[(\d+)\]/g, (_, prop, idx) => {
|
|
9188
|
+
if (prop) indexed.push(prop);
|
|
9189
|
+
else if (idx !== void 0) indexed.push(idx);
|
|
9190
|
+
return "";
|
|
9191
|
+
});
|
|
9192
|
+
return indexed;
|
|
9193
|
+
}) : [];
|
|
9194
|
+
return { fn, args: args2, path: path42 };
|
|
9195
|
+
}
|
|
9196
|
+
function splitArgs(rawArgs) {
|
|
9197
|
+
const trimmed = rawArgs.trim();
|
|
9198
|
+
return trimmed ? trimmed.split(",").map((s) => stripQuotes(s.trim())) : [];
|
|
9199
|
+
}
|
|
9200
|
+
function stripQuotes(s) {
|
|
9201
|
+
if (s.startsWith('"') && s.endsWith('"') || s.startsWith("'") && s.endsWith("'")) {
|
|
9202
|
+
return s.slice(1, -1);
|
|
9203
|
+
}
|
|
9204
|
+
return s;
|
|
9205
|
+
}
|
|
9206
|
+
function parseCatalogArgs(args2) {
|
|
9207
|
+
let kind = null;
|
|
9208
|
+
let tree = false;
|
|
9209
|
+
for (const token of args2) {
|
|
9210
|
+
if (token === "tree") tree = true;
|
|
9211
|
+
else if ((token === "kb" || token === "skill") && kind === null) kind = token;
|
|
9212
|
+
}
|
|
9213
|
+
return { kind, tree };
|
|
9214
|
+
}
|
|
9215
|
+
function widenKinds(kinds) {
|
|
9216
|
+
if (kinds.has("all") || kinds.size > 1) return "all";
|
|
9217
|
+
return [...kinds][0] ?? null;
|
|
9218
|
+
}
|
|
9219
|
+
function* parseCalls(template) {
|
|
9220
|
+
for (const match of template.matchAll(PLACEHOLDER_RE)) {
|
|
9221
|
+
const parsed = parseExpr(match[1].trim());
|
|
9222
|
+
if (parsed) yield parsed;
|
|
9223
|
+
}
|
|
9224
|
+
}
|
|
9225
|
+
function scanTemplateResources(template) {
|
|
9226
|
+
if (!template || !template.includes("{{")) return EMPTY_SCAN;
|
|
9227
|
+
const scan = { ...EMPTY_SCAN };
|
|
9228
|
+
const names = /* @__PURE__ */ new Set();
|
|
9229
|
+
const paths = /* @__PURE__ */ new Set();
|
|
9230
|
+
const treeKinds = /* @__PURE__ */ new Set();
|
|
9231
|
+
const legacyKinds = /* @__PURE__ */ new Set();
|
|
9232
|
+
for (const { fn, args: args2 } of parseCalls(template)) {
|
|
9233
|
+
switch (fn) {
|
|
9234
|
+
case CATALOG_FN:
|
|
9235
|
+
case LEGACY_CATALOG_FN: {
|
|
9236
|
+
scan.needs_catalog = true;
|
|
9237
|
+
scan.has_catalog_call = true;
|
|
9238
|
+
const { kind, tree } = parseCatalogArgs(args2);
|
|
9239
|
+
if (fn === LEGACY_CATALOG_FN) legacyKinds.add(kind ?? "all");
|
|
9240
|
+
if (tree) treeKinds.add(kind ?? "all");
|
|
9241
|
+
break;
|
|
9242
|
+
}
|
|
9243
|
+
case "agent_skills":
|
|
9244
|
+
scan.needs_catalog = true;
|
|
9245
|
+
break;
|
|
9246
|
+
case "resource_content":
|
|
9247
|
+
if (args2[0]) names.add(args2[0]);
|
|
9248
|
+
break;
|
|
9249
|
+
case "file_content":
|
|
9250
|
+
if (args2[0]) paths.add(args2[0]);
|
|
9251
|
+
break;
|
|
9252
|
+
default:
|
|
9253
|
+
break;
|
|
9254
|
+
}
|
|
9255
|
+
}
|
|
9256
|
+
scan.tree_kinds = widenKinds(treeKinds);
|
|
9257
|
+
scan.legacy_kinds = widenKinds(legacyKinds);
|
|
9258
|
+
scan.resource_content_names = [...names];
|
|
9259
|
+
scan.file_content_paths = [...paths];
|
|
9260
|
+
return scan;
|
|
9261
|
+
}
|
|
9262
|
+
var PREVIOUS_CONVERSATIONS_MAX, PLACEHOLDER_RE, CATALOG_FN, LEGACY_CATALOG_FN, EMPTY_SCAN;
|
|
8573
9263
|
var init_chunk_PFDZEKDE = __esm({
|
|
8574
9264
|
"../../packages/core/dist/chunk-PFDZEKDE.js"() {
|
|
8575
9265
|
"use strict";
|
|
8576
9266
|
PREVIOUS_CONVERSATIONS_MAX = 20;
|
|
9267
|
+
PLACEHOLDER_RE = /\{\{\s*([^{}]+?)\s*\}\}/g;
|
|
9268
|
+
CATALOG_FN = "files";
|
|
9269
|
+
LEGACY_CATALOG_FN = "resources";
|
|
9270
|
+
EMPTY_SCAN = {
|
|
9271
|
+
needs_catalog: false,
|
|
9272
|
+
has_catalog_call: false,
|
|
9273
|
+
legacy_kinds: null,
|
|
9274
|
+
tree_kinds: null,
|
|
9275
|
+
resource_content_names: [],
|
|
9276
|
+
file_content_paths: []
|
|
9277
|
+
};
|
|
8577
9278
|
}
|
|
8578
9279
|
});
|
|
8579
9280
|
|
|
@@ -8764,7 +9465,7 @@ var init_chunk_T7KQKDET = __esm({
|
|
|
8764
9465
|
}
|
|
8765
9466
|
});
|
|
8766
9467
|
|
|
8767
|
-
// ../../packages/core/dist/chunk-
|
|
9468
|
+
// ../../packages/core/dist/chunk-7PJOBY4O.js
|
|
8768
9469
|
function resolveMonitorTrigger(monitorConfig) {
|
|
8769
9470
|
if (!monitorConfig || typeof monitorConfig !== "object") return "idle";
|
|
8770
9471
|
const raw = monitorConfig.trigger;
|
|
@@ -9070,10 +9771,10 @@ function refineMonitorRulesStructuredOutput(body, ctx) {
|
|
|
9070
9771
|
if (refusal2) ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["response_format"], message: refusal2 });
|
|
9071
9772
|
}
|
|
9072
9773
|
var NATIVE_TOOL_NAMES, previousConversationsCountField, summarizationThresholdField, flagConditionSchema, MONITOR_TRIGGERS, monitorTriggerSchema, AUTOMATION_GATE_TRIGGERS, NON_FIRING_TRIGGERS, MONITOR_FIRING_TRIGGERS, MONITOR_DELAY_SECONDS_MIN, MONITOR_IDLE_DELAY_REQUIRED_MESSAGE, MONITOR_HISTORY_MESSAGES_MAX, monitorHistoryMessagesSchema, monitorIncludeToolResultsSchema, MONITOR_INPUT_SHAPING_KEYS, MONITOR_INPUT_SHAPING_SCHEMAS, MONITOR_INPUT_SHAPING_IDLE_MESSAGE, monitorArgumentSourceSchema, MONITOR_NOTE_TEMPLATE_MAX, MONITOR_STEER_NOTE_MAX, MONITOR_GATE_AGENT_NAME_MAX, monitorActionSchema, monitorRuleSchema, MONITOR_RULE_KEYS, MONITOR_RULE_SCHEMAS, MONITOR_RULE_TRIGGERS, MONITOR_RULE_TRIGGER_MESSAGE, CALL_UTTERANCE_SPEAKERS, callUtteranceSpeakerSchema, MONITOR_CALL_KEYS, MONITOR_CALL_SCHEMAS, MONITOR_CALL_KEY_MESSAGE, CALL_STEERING_MIN_CONFIDENCE, CALL_UTTERANCE_RULE_NOT_CONFIDENT_MESSAGE, CALL_UTTERANCE_FALLBACK_MESSAGE, CALL_UTTERANCE_FLAG_CONDITIONS_MESSAGE, CALL_STEER_NOTE_BLANK_MESSAGE, MONITOR_USER_MESSAGE_ACTION_KINDS, MONITOR_ASSISTANT_REPLY_ACTION_KINDS, MONITOR_CALL_UTTERANCE_ACTION_KINDS, MONITOR_AUTOMATION_FIRE_ACTION_KINDS, MONITOR_AUTOMATION_ITEM_ACTION_KINDS, INSERT_NOTE_TOOL_NAME, RUN_MONITOR_TOOL_NAME, MONITOR_RULE_ALLOWED_NATIVE_TOOLS, MONITOR_RULE_REENTRY_TOOLS, MONITOR_RULE_REENTRY_TRACKS, AUTOMATION_FIRE_FLAG_CONDITIONS_MESSAGE, AUTOMATION_FIRE_INPUT_SHAPING_MESSAGE, AUTOMATION_ITEM_FLAG_CONDITIONS_MESSAGE, AUTOMATION_ITEM_INPUT_SHAPING_MESSAGE, AUTOMATION_GATE_KEY_MESSAGES, monitorConfigField, flagConditionsField, agentIdParam, listAgentsQuery, agentConnectionsQuery, createAgentBody, updateAgentBody, AGENT_PARAMETER_TYPES, AGENT_PARAMETER_NAME_REGEX, agentParameterCreateSchema, MAX_AGENT_PARAMETERS_PER_REQUEST, createAgentParametersBody;
|
|
9073
|
-
var
|
|
9074
|
-
"../../packages/core/dist/chunk-
|
|
9774
|
+
var init_chunk_7PJOBY4O = __esm({
|
|
9775
|
+
"../../packages/core/dist/chunk-7PJOBY4O.js"() {
|
|
9075
9776
|
"use strict";
|
|
9076
|
-
|
|
9777
|
+
init_chunk_CWUV6NL3();
|
|
9077
9778
|
init_chunk_IBL7FANV();
|
|
9078
9779
|
init_chunk_PFDZEKDE();
|
|
9079
9780
|
init_chunk_HZ6CADSY();
|
|
@@ -9296,12 +9997,12 @@ var init_chunk_7EGDXEYG = __esm({
|
|
|
9296
9997
|
}
|
|
9297
9998
|
});
|
|
9298
9999
|
|
|
9299
|
-
// ../../packages/core/dist/chunk-
|
|
10000
|
+
// ../../packages/core/dist/chunk-CYBQP65X.js
|
|
9300
10001
|
var conversationIdParam, conversationHubQuery, claimBody, releaseBody, transferBody, closeBody, additionalContextPayloadSchema, kanbanUpdateBody, annotateConversationBody, observabilityParams, observabilityListParams, deleteUserConversationsBody, deleteUserConversationsResponse, sendMessageBody, listConversationsQuery, flagSourceSchema, getConversationsResponse, getConversationDetailParams, getConversationDetailQuery, getConversationDetailResponse, listWhatsAppTemplatesQuery, sendWhatsAppTemplateBody;
|
|
9301
|
-
var
|
|
9302
|
-
"../../packages/core/dist/chunk-
|
|
10002
|
+
var init_chunk_CYBQP65X = __esm({
|
|
10003
|
+
"../../packages/core/dist/chunk-CYBQP65X.js"() {
|
|
9303
10004
|
"use strict";
|
|
9304
|
-
|
|
10005
|
+
init_chunk_7PJOBY4O();
|
|
9305
10006
|
init_chunk_HZ6CADSY();
|
|
9306
10007
|
init_chunk_DHPUPI4Y();
|
|
9307
10008
|
init_zod();
|
|
@@ -9757,7 +10458,7 @@ var init_chunk_BPCNB5RT = __esm({
|
|
|
9757
10458
|
}
|
|
9758
10459
|
});
|
|
9759
10460
|
|
|
9760
|
-
// ../../packages/core/dist/chunk-
|
|
10461
|
+
// ../../packages/core/dist/chunk-NRJINQVD.js
|
|
9761
10462
|
function idempotencyHeaderRefusal(value) {
|
|
9762
10463
|
if (typeof value !== "string" || !/^[A-Za-z0-9][A-Za-z0-9-]{0,63}$/.test(value)) {
|
|
9763
10464
|
return "idempotency_header must be a header name: letters, digits and dashes, at most 64";
|
|
@@ -9768,10 +10469,10 @@ function idempotencyHeaderRefusal(value) {
|
|
|
9768
10469
|
return null;
|
|
9769
10470
|
}
|
|
9770
10471
|
var CATALOG_REPLAY, REPLAY_OVERRIDES, RESERVED_HEADERS;
|
|
9771
|
-
var
|
|
9772
|
-
"../../packages/core/dist/chunk-
|
|
10472
|
+
var init_chunk_NRJINQVD = __esm({
|
|
10473
|
+
"../../packages/core/dist/chunk-NRJINQVD.js"() {
|
|
9773
10474
|
"use strict";
|
|
9774
|
-
|
|
10475
|
+
init_chunk_CWUV6NL3();
|
|
9775
10476
|
CATALOG_REPLAY = new Map(
|
|
9776
10477
|
NATIVE_TOOLS.map((tool) => [tool.tool_name, tool.replay])
|
|
9777
10478
|
);
|
|
@@ -10004,7 +10705,7 @@ var init_chunk_WEXVYJ5H = __esm({
|
|
|
10004
10705
|
}
|
|
10005
10706
|
});
|
|
10006
10707
|
|
|
10007
|
-
// ../../packages/core/dist/chunk-
|
|
10708
|
+
// ../../packages/core/dist/chunk-QXHWCZ74.js
|
|
10008
10709
|
function refineToolConfig(value, ctx) {
|
|
10009
10710
|
const raw = value.tool_config;
|
|
10010
10711
|
if (raw == null) return;
|
|
@@ -10040,10 +10741,10 @@ function refineToolConfig(value, ctx) {
|
|
|
10040
10741
|
}
|
|
10041
10742
|
}
|
|
10042
10743
|
var toolIdParam, listToolsQuery, toolAgentQuery, deleteToolQuery, toolConnectionsQuery, nativeToolIdSchema, CONTEXT_BOUNDARIES, contextBoundarySchema, addNativeToolBody, addMcpToolBody, toolReplaySettingsFields, toolReplaySettingsSchema, createCustomToolBody, updateCustomToolBody, updateToolExecutionConfigBody;
|
|
10043
|
-
var
|
|
10044
|
-
"../../packages/core/dist/chunk-
|
|
10744
|
+
var init_chunk_QXHWCZ74 = __esm({
|
|
10745
|
+
"../../packages/core/dist/chunk-QXHWCZ74.js"() {
|
|
10045
10746
|
"use strict";
|
|
10046
|
-
|
|
10747
|
+
init_chunk_NRJINQVD();
|
|
10047
10748
|
init_chunk_WEXVYJ5H();
|
|
10048
10749
|
init_chunk_HZ6CADSY();
|
|
10049
10750
|
init_zod();
|
|
@@ -11415,7 +12116,7 @@ var init_chunk_6NI5RAM4 = __esm({
|
|
|
11415
12116
|
}
|
|
11416
12117
|
});
|
|
11417
12118
|
|
|
11418
|
-
// ../../packages/core/dist/chunk-
|
|
12119
|
+
// ../../packages/core/dist/chunk-QJTHNMWD.js
|
|
11419
12120
|
function refineHubAsCodeCustomTools(config, ctx) {
|
|
11420
12121
|
const agents = config.agents;
|
|
11421
12122
|
if (!Array.isArray(agents)) return;
|
|
@@ -11951,12 +12652,12 @@ function refineHubAsCodeMonitorRulesOutput(config, ctx) {
|
|
|
11951
12652
|
});
|
|
11952
12653
|
}
|
|
11953
12654
|
var MAX_10MB_BASE64_ENCODED_BYTES, MAX_EVAL_ATTACHMENT_ENCODED_BYTES, MAX_EVAL_ATTACHMENT_CARRIERS, MAX_EVAL_ATTACHMENT_AGGREGATE_ENCODED_BYTES, MAX_RESOURCE_ENCODED_BYTES, MAX_HUB_AS_CODE_RESOURCES, MAX_HUB_AS_CODE_RESOURCE_FILES, MAX_RESOURCE_AGGREGATE_ENCODED_BYTES, MAX_CI_CONFIG_BODY_BYTES, MAX_CALL_OPENING_CARRIERS, MAX_CALL_OPENING_AGGREGATE_ENCODED_BYTES, hubAsCodeStateSchema, hubAsCodeResourceFileSchema, hubAsCodeResourceSchema, uuidPattern, ciUuidSchema, boundedHubAsCodeResourcesSchema, hubAsCodeConfigSchema, ciPullHubIdParam, ciBranchParam, ciPullQuery, ciHubsQuery, ciLookupQuery, ciBranchesQuery, ciSyncBody, ciPushBody, ciDiffBody, ciPublishBody, ciPublishPreviewBody, ciSyncMcpBody, ciOrgResourcesParam, orgResourcesConfigSchema, ciOrgResourcesPushBody, ciOrgResourcesDiffBody, CI_APPLY_ENTITY_KINDS, CI_APPLY_OPERATIONS, ciApplyErrorBaseSchema, ciApplyErrorSchema, ciSyncCountSchema, ciSyncChangesSchema, ciSyncWarningSchema, ciSyncResponseSchema;
|
|
11954
|
-
var
|
|
11955
|
-
"../../packages/core/dist/chunk-
|
|
12655
|
+
var init_chunk_QJTHNMWD = __esm({
|
|
12656
|
+
"../../packages/core/dist/chunk-QJTHNMWD.js"() {
|
|
11956
12657
|
"use strict";
|
|
11957
|
-
|
|
12658
|
+
init_chunk_QXHWCZ74();
|
|
11958
12659
|
init_chunk_623SRYWZ();
|
|
11959
|
-
|
|
12660
|
+
init_chunk_7PJOBY4O();
|
|
11960
12661
|
init_chunk_LBDH7LCG();
|
|
11961
12662
|
init_chunk_4LXDRTO4();
|
|
11962
12663
|
init_chunk_T4Q5COKJ();
|
|
@@ -12965,29 +13666,13 @@ var init_chunk_LQY2OCQL = __esm({
|
|
|
12965
13666
|
}
|
|
12966
13667
|
});
|
|
12967
13668
|
|
|
12968
|
-
// ../../packages/core/dist/chunk-
|
|
12969
|
-
var DATA_PROXY_MOUNT, DATA_PROXY_PREFIX, BASE_DESTRUCTION_MOUNT, BASE_DESTRUCTION_PREFIX, BASE_DESTRUCTION_CONFIRM_PATH, COMMUNITY_EMAIL_UNSUBSCRIBE_PATH, CONTACT_BOOK_EMAIL_UNSUBSCRIBE_PATH, AUTOMATION_WEBHOOK_MOUNT;
|
|
12970
|
-
var init_chunk_F3JHI4Y4 = __esm({
|
|
12971
|
-
"../../packages/core/dist/chunk-F3JHI4Y4.js"() {
|
|
12972
|
-
"use strict";
|
|
12973
|
-
DATA_PROXY_MOUNT = "/data";
|
|
12974
|
-
DATA_PROXY_PREFIX = `/api${DATA_PROXY_MOUNT}`;
|
|
12975
|
-
BASE_DESTRUCTION_MOUNT = "/bases";
|
|
12976
|
-
BASE_DESTRUCTION_PREFIX = `/api${BASE_DESTRUCTION_MOUNT}`;
|
|
12977
|
-
BASE_DESTRUCTION_CONFIRM_PATH = `${BASE_DESTRUCTION_PREFIX}/destruction/confirm`;
|
|
12978
|
-
COMMUNITY_EMAIL_UNSUBSCRIBE_PATH = "/api/communities/email-unsubscribe";
|
|
12979
|
-
CONTACT_BOOK_EMAIL_UNSUBSCRIBE_PATH = "/api/contact-book/email-unsubscribe";
|
|
12980
|
-
AUTOMATION_WEBHOOK_MOUNT = "/webhooks/automations";
|
|
12981
|
-
}
|
|
12982
|
-
});
|
|
12983
|
-
|
|
12984
|
-
// ../../packages/core/dist/chunk-GCKAPAPC.js
|
|
13669
|
+
// ../../packages/core/dist/chunk-UWFYCJCK.js
|
|
12985
13670
|
var AUTOMATION_TRIGGER_TYPES, AUTOMATION_EVENT_TYPES, AUTOMATION_TARGET_TYPES, AUTOMATION_ACTION_TYPES, AUTOMATION_CHANNEL_TYPES, AUTOMATION_PURPOSES, AUTOMATION_NAME_MAX, AUTOMATION_DESCRIPTION_MAX, AUTOMATION_INSTRUCTIONS_MAX, AUTOMATION_TEXT_MAX, AUTOMATION_CRON_MAX, AUTOMATION_WEBHOOK_AUTHS, AUTOMATION_WEBHOOK_HEADERS, AUTOMATION_WEBHOOK_BODY_MAX_BYTES, AUTOMATION_WEBHOOK_HEADER_NAME_MAX, WEBHOOK_RESERVED_HEADERS, automationTriggerSchema, automationTargetSchema, automationActionSchema, automationGateSchema, automationName, automationDescription, automationIdParam, automationHubQuery, automationWebhookParams, AUTOMATION_RUNS_PAGE_MAX, listAutomationRunsQuery, createAutomationBody, updateAutomationBody, AUTOMATION_ALERT_CATEGORIES, AUTOMATION_BLOCKED_ALERT_CATEGORY, automationEventSubscriberParam;
|
|
12986
|
-
var
|
|
12987
|
-
"../../packages/core/dist/chunk-
|
|
13671
|
+
var init_chunk_UWFYCJCK = __esm({
|
|
13672
|
+
"../../packages/core/dist/chunk-UWFYCJCK.js"() {
|
|
12988
13673
|
"use strict";
|
|
12989
13674
|
init_chunk_LQY2OCQL();
|
|
12990
|
-
|
|
13675
|
+
init_chunk_7PJOBY4O();
|
|
12991
13676
|
init_chunk_3HSB6J4J();
|
|
12992
13677
|
init_chunk_F3JHI4Y4();
|
|
12993
13678
|
init_chunk_DHPUPI4Y();
|
|
@@ -14232,28 +14917,29 @@ var init_chunk_TS4EYU3W = __esm({
|
|
|
14232
14917
|
var init_contracts = __esm({
|
|
14233
14918
|
"../../packages/core/dist/contracts/index.js"() {
|
|
14234
14919
|
"use strict";
|
|
14920
|
+
init_chunk_FK6HBXM7();
|
|
14921
|
+
init_chunk_VXEAR36B();
|
|
14235
14922
|
init_chunk_TIZ57ZBA();
|
|
14236
14923
|
init_chunk_Q7KIQHAZ();
|
|
14237
14924
|
init_chunk_2U4BTOR7();
|
|
14238
14925
|
init_chunk_IPXBFWUU();
|
|
14239
14926
|
init_chunk_GK27ENTT();
|
|
14240
14927
|
init_chunk_SXDX4GHS();
|
|
14241
|
-
|
|
14242
|
-
init_chunk_VXEAR36B();
|
|
14928
|
+
init_chunk_GPM7PE44();
|
|
14243
14929
|
init_chunk_DGKMMFIR();
|
|
14244
14930
|
init_chunk_ZC6YGHNX();
|
|
14245
14931
|
init_chunk_ACKF2MVU();
|
|
14246
14932
|
init_chunk_Y7CFCYX4();
|
|
14247
14933
|
init_chunk_5DGHIY7L();
|
|
14248
|
-
|
|
14934
|
+
init_chunk_CGOLULK2();
|
|
14935
|
+
init_chunk_4MRBBR5L();
|
|
14249
14936
|
init_chunk_BEOHBREG();
|
|
14250
14937
|
init_chunk_IPDOHAZ5();
|
|
14251
|
-
init_chunk_GPM7PE44();
|
|
14252
14938
|
init_chunk_YUZFHVB2();
|
|
14253
14939
|
init_chunk_34LE6P7E();
|
|
14254
14940
|
init_chunk_FPSDNS2E();
|
|
14255
14941
|
init_chunk_ZXK6N2HN();
|
|
14256
|
-
|
|
14942
|
+
init_chunk_NTJMGSSO();
|
|
14257
14943
|
init_chunk_C4HEZ7ZI();
|
|
14258
14944
|
init_chunk_IR2NOSI3();
|
|
14259
14945
|
init_chunk_EA23LSDS();
|
|
@@ -14261,12 +14947,12 @@ var init_contracts = __esm({
|
|
|
14261
14947
|
init_chunk_LWMLR2WI();
|
|
14262
14948
|
init_chunk_ACGAI25T();
|
|
14263
14949
|
init_chunk_MH4O3YEX();
|
|
14264
|
-
|
|
14950
|
+
init_chunk_CYBQP65X();
|
|
14265
14951
|
init_chunk_KSTYTQ7I();
|
|
14266
14952
|
init_chunk_LKBSHANA();
|
|
14267
14953
|
init_chunk_BPCNB5RT();
|
|
14268
|
-
|
|
14269
|
-
|
|
14954
|
+
init_chunk_QJTHNMWD();
|
|
14955
|
+
init_chunk_QXHWCZ74();
|
|
14270
14956
|
init_chunk_623SRYWZ();
|
|
14271
14957
|
init_chunk_JDJ4EUOL();
|
|
14272
14958
|
init_chunk_3KYTITOR();
|
|
@@ -14276,9 +14962,9 @@ var init_contracts = __esm({
|
|
|
14276
14962
|
init_chunk_YULHXJ5M();
|
|
14277
14963
|
init_chunk_V4FRPRH5();
|
|
14278
14964
|
init_chunk_ZHJEQBXJ();
|
|
14279
|
-
|
|
14965
|
+
init_chunk_UWFYCJCK();
|
|
14280
14966
|
init_chunk_LQY2OCQL();
|
|
14281
|
-
|
|
14967
|
+
init_chunk_7PJOBY4O();
|
|
14282
14968
|
init_chunk_3HSB6J4J();
|
|
14283
14969
|
init_chunk_IG6QBZMA();
|
|
14284
14970
|
init_chunk_ZJJ5ODOD();
|
|
@@ -14287,9 +14973,9 @@ var init_contracts = __esm({
|
|
|
14287
14973
|
init_chunk_4LXDRTO4();
|
|
14288
14974
|
init_chunk_E4DIIOOE();
|
|
14289
14975
|
init_chunk_7CYHPF35();
|
|
14290
|
-
|
|
14291
|
-
|
|
14292
|
-
|
|
14976
|
+
init_chunk_4MXPUC6I();
|
|
14977
|
+
init_chunk_NRJINQVD();
|
|
14978
|
+
init_chunk_CWUV6NL3();
|
|
14293
14979
|
init_chunk_NVKU5PPQ();
|
|
14294
14980
|
init_chunk_2FCLSQSQ();
|
|
14295
14981
|
init_chunk_FECLIZTY();
|
|
@@ -14302,6 +14988,7 @@ var init_contracts = __esm({
|
|
|
14302
14988
|
init_chunk_OMPT4ZHR();
|
|
14303
14989
|
init_chunk_2PZIF56U();
|
|
14304
14990
|
init_chunk_UEJOG2Q7();
|
|
14991
|
+
init_chunk_IEWJ5MZH();
|
|
14305
14992
|
init_chunk_SOJMWM63();
|
|
14306
14993
|
init_chunk_WEXVYJ5H();
|
|
14307
14994
|
init_chunk_LZDVDC2S();
|
|
@@ -14709,6 +15396,61 @@ var init_api_client = __esm({
|
|
|
14709
15396
|
}
|
|
14710
15397
|
return new Uint8Array(await response.arrayBuffer());
|
|
14711
15398
|
}
|
|
15399
|
+
// ---- A preview hub's knowledge cut-over (`wayai knowledge cut-over`) ----
|
|
15400
|
+
//
|
|
15401
|
+
// The setup routes the web console writes through, so each write takes the same checks: the
|
|
15402
|
+
// as-code push never sees a hub's copies of organization resources or their agent links.
|
|
15403
|
+
async listAgents(hubId) {
|
|
15404
|
+
const params = new URLSearchParams({ hub_id: hubId });
|
|
15405
|
+
return this.request("GET", `/api/setup/agents?${params}`);
|
|
15406
|
+
}
|
|
15407
|
+
/** Patch an agent of a preview hub: the text it reads (`updateAgentBody` takes more). */
|
|
15408
|
+
async updateAgent(hubId, agentId, patch) {
|
|
15409
|
+
const params = new URLSearchParams({ hub_id: hubId });
|
|
15410
|
+
await this.request("PATCH", `/api/setup/agents/${encodeURIComponent(agentId)}?${params}`, patch);
|
|
15411
|
+
}
|
|
15412
|
+
async listAgentResources(hubId, agentId) {
|
|
15413
|
+
const params = new URLSearchParams({ hub_id: hubId, agent_id: agentId });
|
|
15414
|
+
return this.request("GET", `/api/setup/resources/agent-resources?${params}`);
|
|
15415
|
+
}
|
|
15416
|
+
async updateAgentResource(body) {
|
|
15417
|
+
return this.request("PATCH", "/api/setup/resources/agent-resources", body);
|
|
15418
|
+
}
|
|
15419
|
+
/** An agent's tools, and the native and connection tools it could add (`available_action: 'Add'`). */
|
|
15420
|
+
async listAgentTools(hubId, agentId) {
|
|
15421
|
+
const params = new URLSearchParams({ hub_id: hubId, agent_id: agentId });
|
|
15422
|
+
return this.request("GET", `/api/setup/tools?${params}`);
|
|
15423
|
+
}
|
|
15424
|
+
async addNativeTool(hubId, agentId, toolNativeId) {
|
|
15425
|
+
return this.request("POST", "/api/setup/tools/native", {
|
|
15426
|
+
hub_id: hubId,
|
|
15427
|
+
agent_id: agentId,
|
|
15428
|
+
tool_native_id: toolNativeId
|
|
15429
|
+
});
|
|
15430
|
+
}
|
|
15431
|
+
async addMcpTool(hubId, agentId, mcpToolId) {
|
|
15432
|
+
return this.request("POST", "/api/setup/tools/mcp", { hub_id: hubId, agent_id: agentId, mcp_tool_id: mcpToolId });
|
|
15433
|
+
}
|
|
15434
|
+
async enableTool(hubId, agentId, toolId) {
|
|
15435
|
+
const params = new URLSearchParams({ hub_id: hubId, agent_id: agentId });
|
|
15436
|
+
await this.request("POST", `/api/setup/tools/${encodeURIComponent(toolId)}/enable?${params}`);
|
|
15437
|
+
}
|
|
15438
|
+
/** Add a token-holding connection (a Base Toolset's) to a preview hub; the answer lists the hub's connections. */
|
|
15439
|
+
async addConnection(body) {
|
|
15440
|
+
return this.request("POST", "/api/setup/connections", body);
|
|
15441
|
+
}
|
|
15442
|
+
async listAutomations(hubId) {
|
|
15443
|
+
const params = new URLSearchParams({ hub_id: hubId });
|
|
15444
|
+
return this.request("GET", `/api/automations?${params}`);
|
|
15445
|
+
}
|
|
15446
|
+
async updateAutomation(hubId, automationId, body) {
|
|
15447
|
+
const params = new URLSearchParams({ hub_id: hubId });
|
|
15448
|
+
await this.request("PATCH", `/api/automations/${encodeURIComponent(automationId)}?${params}`, body);
|
|
15449
|
+
}
|
|
15450
|
+
/** Record one knowledge resource's move into the org's knowledge base (#5142 step 4). */
|
|
15451
|
+
async recordKnowledgeMove(body) {
|
|
15452
|
+
return this.request("POST", "/api/setup/knowledge/moves", body);
|
|
15453
|
+
}
|
|
14712
15454
|
async me() {
|
|
14713
15455
|
return this.request("GET", "/api/auth/me");
|
|
14714
15456
|
}
|
|
@@ -15523,32 +16265,6 @@ var init_chunk_BF4WRSOQ = __esm({
|
|
|
15523
16265
|
});
|
|
15524
16266
|
|
|
15525
16267
|
// ../../packages/core/dist/index.js
|
|
15526
|
-
function matchPath(pattern, path42) {
|
|
15527
|
-
const normalizedPath = path42.replace(/\/$/, "");
|
|
15528
|
-
if (pattern.endsWith("/*")) {
|
|
15529
|
-
const prefix = pattern.slice(0, -2);
|
|
15530
|
-
return normalizedPath === prefix || normalizedPath.startsWith(prefix + "/");
|
|
15531
|
-
}
|
|
15532
|
-
return pattern === normalizedPath;
|
|
15533
|
-
}
|
|
15534
|
-
function getEndpointConfig(path42) {
|
|
15535
|
-
for (const excluded of EXCLUDED_PATHS) {
|
|
15536
|
-
if (matchPath(excluded, path42)) {
|
|
15537
|
-
return null;
|
|
15538
|
-
}
|
|
15539
|
-
}
|
|
15540
|
-
const normalizedPath = path42.replace(/\/$/, "");
|
|
15541
|
-
if (!normalizedPath.endsWith("/*") && Object.prototype.hasOwnProperty.call(ENDPOINT_CONFIGS, normalizedPath)) {
|
|
15542
|
-
return ENDPOINT_CONFIGS[normalizedPath];
|
|
15543
|
-
}
|
|
15544
|
-
const sortedPatterns = Object.keys(ENDPOINT_CONFIGS).sort((a, b) => b.length - a.length);
|
|
15545
|
-
for (const pattern of sortedPatterns) {
|
|
15546
|
-
if (matchPath(pattern, path42)) {
|
|
15547
|
-
return ENDPOINT_CONFIGS[pattern];
|
|
15548
|
-
}
|
|
15549
|
-
}
|
|
15550
|
-
return { tier: "STANDARD" };
|
|
15551
|
-
}
|
|
15552
16268
|
function bytesToUuid(bytes) {
|
|
15553
16269
|
const hex = [];
|
|
15554
16270
|
for (let i = 0; i < 16; i++) hex.push(bytes[i].toString(16).padStart(2, "0"));
|
|
@@ -15596,10 +16312,11 @@ function markdownToPlainText(text) {
|
|
|
15596
16312
|
result = result.replace(/^[-*_]{3,}$/gm, "");
|
|
15597
16313
|
return result;
|
|
15598
16314
|
}
|
|
15599
|
-
var
|
|
16315
|
+
var UUID_RE2, WAYAI_WORKSPACE_LAYOUT;
|
|
15600
16316
|
var init_dist = __esm({
|
|
15601
16317
|
"../../packages/core/dist/index.js"() {
|
|
15602
16318
|
"use strict";
|
|
16319
|
+
init_chunk_IEWJ5MZH();
|
|
15603
16320
|
init_chunk_SOJMWM63();
|
|
15604
16321
|
init_chunk_WEXVYJ5H();
|
|
15605
16322
|
init_chunk_LZDVDC2S();
|
|
@@ -15624,465 +16341,6 @@ var init_dist = __esm({
|
|
|
15624
16341
|
init_chunk_L5F7ZOUJ();
|
|
15625
16342
|
init_chunk_TS4EYU3W();
|
|
15626
16343
|
init_chunk_R7VSUKKX();
|
|
15627
|
-
ENDPOINT_CONFIGS = {
|
|
15628
|
-
// OAuth callbacks - strict, fail-closed (prevent brute force)
|
|
15629
|
-
"/oauth/callback/*": { tier: "STRICT", failClosed: true },
|
|
15630
|
-
// Auth endpoints - strict, fail-closed
|
|
15631
|
-
"/auth/phone": { tier: "STRICT", failClosed: true },
|
|
15632
|
-
"/auth/mcp/exchange": { tier: "MODERATE", failClosed: true },
|
|
15633
|
-
// Native magic-code login (pre-auth) — STRICT per-IP bounds a single host; WorkOS owns
|
|
15634
|
-
// the per-email lockout / TTL / single-use guarantees.
|
|
15635
|
-
"/api/auth/magic-code/*": { tier: "STRICT", failClosed: true },
|
|
15636
|
-
// Native password login (pre-auth) — same posture as magic-code above. STRICT per-IP bounds a
|
|
15637
|
-
// single host but FAILS OPEN on a binding error (like every IP limit); the `failClosed` flag
|
|
15638
|
-
// here is inert on a pre-auth route (it's only enforced on the per-user branch, which needs an
|
|
15639
|
-
// authenticated caller). The fail-CLOSED brute-force guarantee for the static credential comes
|
|
15640
|
-
// from the route's per-email caps (burst + cumulative hourly), not this flag.
|
|
15641
|
-
"/api/auth/password/*": { tier: "STRICT", failClosed: true },
|
|
15642
|
-
// Every `/api/*` group whose paths carry ids pins `userLimitKey` (gh #4756), held there by
|
|
15643
|
-
// `security/endpoint-invariants.test.ts`.
|
|
15644
|
-
// Billing - moderate, fail-closed with user limits
|
|
15645
|
-
"/api/billing/*": {
|
|
15646
|
-
tier: "MODERATE",
|
|
15647
|
-
userLimit: 100,
|
|
15648
|
-
failClosed: true,
|
|
15649
|
-
skipAuthenticatedUsers: true,
|
|
15650
|
-
userLimitKey: "/api/billing/*"
|
|
15651
|
-
},
|
|
15652
|
-
"/webhooks/stripe": { tier: "MODERATE", failClosed: true },
|
|
15653
|
-
"/webhooks/workos": { tier: "MODERATE", failClosed: true },
|
|
15654
|
-
// API endpoints - standard with user limits
|
|
15655
|
-
// 500 because the web's settings live updates refetch per hub in every open tab, whatever page
|
|
15656
|
-
// it shows (`settingsLiveUpdateStore.ts`): one batch of changes costs about 10 + 4 per agent
|
|
15657
|
-
// whose tools were opened + 2 per loaded resource, so a CLI push and publish (both hubs) with
|
|
15658
|
-
// two tabs open passes 150 in a minute. A multi-file resource upload adds 2–5 per file.
|
|
15659
|
-
"/api/setup/*": {
|
|
15660
|
-
tier: "STANDARD",
|
|
15661
|
-
userLimit: 500,
|
|
15662
|
-
skipAuthenticatedUsers: true,
|
|
15663
|
-
userLimitKey: "/api/setup/*"
|
|
15664
|
-
},
|
|
15665
|
-
// 500 because an eval run's drill-down (`SessionResults.tsx`) fetches its list and then every
|
|
15666
|
-
// LLM turn's record at once: up to 201 requests (`OBSERVABILITY_LIST_CAP`, 200). Sending keeps
|
|
15667
|
-
// the 150 it had when each path held its own budget, on the exact key below.
|
|
15668
|
-
"/api/conversations/*": {
|
|
15669
|
-
tier: "STANDARD",
|
|
15670
|
-
userLimit: 500,
|
|
15671
|
-
skipAuthenticatedUsers: true,
|
|
15672
|
-
userLimitKey: "/api/conversations/*"
|
|
15673
|
-
},
|
|
15674
|
-
"/api/conversations/send-message": { tier: "STANDARD", userLimit: 150, skipAuthenticatedUsers: true },
|
|
15675
|
-
"/api/users/*": { tier: "STANDARD", userLimit: 150, skipAuthenticatedUsers: true },
|
|
15676
|
-
"/api/analytics/*": {
|
|
15677
|
-
tier: "STANDARD",
|
|
15678
|
-
userLimit: 150,
|
|
15679
|
-
skipAuthenticatedUsers: true,
|
|
15680
|
-
userLimitKey: "/api/analytics/*"
|
|
15681
|
-
},
|
|
15682
|
-
// 500 because `wayai eval-results --eval` reads completed sessions one by one, newest first,
|
|
15683
|
-
// until one holds the eval (up to the 1000 the list returns). Tenant SQL keeps the 150 it had
|
|
15684
|
-
// when each path held its own budget: each query may run 30 s on shared-tenant ClickHouse
|
|
15685
|
-
// after its caller has gone (`sql-validator.ts`, stage 10b).
|
|
15686
|
-
"/api/evals/*": {
|
|
15687
|
-
tier: "STANDARD",
|
|
15688
|
-
userLimit: 500,
|
|
15689
|
-
skipAuthenticatedUsers: true,
|
|
15690
|
-
userLimitKey: "/api/evals/*"
|
|
15691
|
-
},
|
|
15692
|
-
"/api/evals/sql": { tier: "STANDARD", userLimit: 150, skipAuthenticatedUsers: true },
|
|
15693
|
-
// An eval run's calls (gh #4418), kept off the end user's call budgets below. Create keeps the
|
|
15694
|
-
// 150 it had: it opens a provider session on the org's own key, billed per second. The routes
|
|
15695
|
-
// on one call take a budget of their own so no eval read can spend it: a refused hang-up
|
|
15696
|
-
// leaves the call running, and billing. 500 because a call suite polls each run's record
|
|
15697
|
-
// every second, about 64 requests a minute per suite.
|
|
15698
|
-
"/api/evals/calls": { tier: "STANDARD", userLimit: 150, skipAuthenticatedUsers: true },
|
|
15699
|
-
"/api/evals/calls/*": {
|
|
15700
|
-
tier: "STANDARD",
|
|
15701
|
-
userLimit: 500,
|
|
15702
|
-
skipAuthenticatedUsers: true,
|
|
15703
|
-
userLimitKey: "/api/evals/calls/*"
|
|
15704
|
-
},
|
|
15705
|
-
"/api/files/*": { tier: "STANDARD", userLimit: 150, skipAuthenticatedUsers: true },
|
|
15706
|
-
// The org contact book (wayai#4931 PR 2b). A CSV import sends one request per
|
|
15707
|
-
// `CONTACT_IMPORT_BATCH_MAX` rows, so 150 a minute imports 75,000 rows a minute.
|
|
15708
|
-
"/api/contact-book/*": {
|
|
15709
|
-
tier: "STANDARD",
|
|
15710
|
-
userLimit: 150,
|
|
15711
|
-
skipAuthenticatedUsers: true,
|
|
15712
|
-
userLimitKey: "/api/contact-book/*"
|
|
15713
|
-
},
|
|
15714
|
-
// An automation email's one-click unsubscribe (wayai#4931 PR 2d) — public: its token is its one
|
|
15715
|
-
// credential, so this per-IP limit is its one bound, and runs before the route reads anything.
|
|
15716
|
-
// MODERATE (30/min per IP), as the community email's unsubscribe, for the same reason: mail
|
|
15717
|
-
// clients' one-click unsubscribe POSTs it from their own servers, which many recipients' clicks
|
|
15718
|
-
// share. An exact key, so it wins over the wildcard above.
|
|
15719
|
-
[CONTACT_BOOK_EMAIL_UNSUBSCRIBE_PATH]: { tier: "MODERATE" },
|
|
15720
|
-
"/api/automations/*": {
|
|
15721
|
-
tier: "STANDARD",
|
|
15722
|
-
userLimit: 150,
|
|
15723
|
-
skipAuthenticatedUsers: true,
|
|
15724
|
-
userLimitKey: "/api/automations/*"
|
|
15725
|
-
},
|
|
15726
|
-
// A hub's own contact lists (wayai#4931 PR 2c): config edits, at the automations' rate.
|
|
15727
|
-
"/api/hub-contact-lists/*": {
|
|
15728
|
-
tier: "STANDARD",
|
|
15729
|
-
userLimit: 150,
|
|
15730
|
-
skipAuthenticatedUsers: true,
|
|
15731
|
-
userLimitKey: "/api/hub-contact-lists/*"
|
|
15732
|
-
},
|
|
15733
|
-
"/api/message-templates/*": {
|
|
15734
|
-
tier: "STANDARD",
|
|
15735
|
-
userLimit: 150,
|
|
15736
|
-
skipAuthenticatedUsers: true,
|
|
15737
|
-
userLimitKey: "/api/message-templates/*"
|
|
15738
|
-
},
|
|
15739
|
-
"/api/live-updates/*": { tier: "STANDARD", userLimit: 150, skipAuthenticatedUsers: true },
|
|
15740
|
-
"/api/hub-users/*": {
|
|
15741
|
-
tier: "STANDARD",
|
|
15742
|
-
userLimit: 150,
|
|
15743
|
-
skipAuthenticatedUsers: true,
|
|
15744
|
-
userLimitKey: "/api/hub-users/*"
|
|
15745
|
-
},
|
|
15746
|
-
"/api/conversation-state/*": {
|
|
15747
|
-
tier: "STANDARD",
|
|
15748
|
-
userLimit: 150,
|
|
15749
|
-
skipAuthenticatedUsers: true,
|
|
15750
|
-
userLimitKey: "/api/conversation-state/*"
|
|
15751
|
-
},
|
|
15752
|
-
// The authenticated byte route (`routes/files/r2.ts`), mounted after its `authMiddleware` so
|
|
15753
|
-
// the caller is known: each request reads the HubDO its key names, and a hub id never created
|
|
15754
|
-
// still wakes a Durable Object (gh #4747; since gh #4775 T5 one that writes nothing). The key is pinned
|
|
15755
|
-
// because the paths are file keys, which would otherwise each get a fresh budget (and one
|
|
15756
|
-
// over 256 characters would be refused outright, `checkUserLimitNative`).
|
|
15757
|
-
//
|
|
15758
|
-
// 500 a minute because the web chat fetches every attachment of every loaded message at
|
|
15759
|
-
// once, with no list virtualization: up to 50 per 50-message page, 200 per ended
|
|
15760
|
-
// conversation or history entry at one file per message, and 4 per message at most in its
|
|
15761
|
-
// image grid. Repeats come from the browser's HTTP cache (`private, max-age=3600`); mobile
|
|
15762
|
-
// renders through a disk cache and plays audio from `/files/signed`. The budget is shared by
|
|
15763
|
-
// a caller's tabs and devices. The other two byte routes, `/files/signed/*` and
|
|
15764
|
-
// `/files/public/*`, mount no limiter (the reason is at their entries in
|
|
15765
|
-
// `security/endpoint-invariants.test.ts`'s route registry).
|
|
15766
|
-
"/files/auth/*": {
|
|
15767
|
-
tier: "STANDARD",
|
|
15768
|
-
userLimit: 500,
|
|
15769
|
-
skipAuthenticatedUsers: true,
|
|
15770
|
-
userLimitKey: "/files/auth/*"
|
|
15771
|
-
},
|
|
15772
|
-
// Channel webhooks - high throughput (external systems, never authenticated)
|
|
15773
|
-
"/webhooks/whatsapp/*": { tier: "HIGH" },
|
|
15774
|
-
"/webhooks/instagram/*": { tier: "HIGH" },
|
|
15775
|
-
// An automation's webhook deliveries (wayai#4931 D13) — no bearer token: the automation's
|
|
15776
|
-
// secret is the credential, checked against one HubDO read. This per-IP limit runs first, before
|
|
15777
|
-
// anything is read. HIGH, as the channel webhooks: senders are servers, and an integration
|
|
15778
|
-
// platform's egress addresses carry many tenants' deliveries. The per-automation budget is
|
|
15779
|
-
// `AUTOMATION_WEBHOOK_PER_AUTOMATION_LIMIT`, counted after the delivery is verified.
|
|
15780
|
-
[`${AUTOMATION_WEBHOOK_MOUNT}/*`]: { tier: "HIGH" },
|
|
15781
|
-
// Reports (CLI → platform_report triage queue). One per-user budget covers intake
|
|
15782
|
-
// (POST), the content edit (PATCH /:id), the reporter read-back (GET, GET /:id), and the
|
|
15783
|
-
// verification loop (POST /:id/accept, /:id/contest). The limiter is path-based, so a
|
|
15784
|
-
// method-level split (loose reads, strict writes) isn't expressible — GET /:id and PATCH
|
|
15785
|
-
// /:id share a path. So the budget is sized for the read-heavy poll loop (a reporter agent
|
|
15786
|
-
// polling `report get`/`list` while it waits for the fix), at 60/hour. Intake-flooding stays
|
|
15787
|
-
// bounded regardless: identical reports collapse on `fingerprint` (ON CONFLICT DO NOTHING),
|
|
15788
|
-
// so a runaway create loop merges rather than floods. Still STRICT tier + failClosed so a
|
|
15789
|
-
// genuinely runaway distinct-report loop surfaces as 429. The `/*` is load-bearing — an
|
|
15790
|
-
// exact `/api/reports` key would leave the `/:id` paths unmatched (→ unthrottled).
|
|
15791
|
-
// `userLimitKey` is equally load-bearing: without it, middleware falls back to each request's
|
|
15792
|
-
// path and report IDs/actions receive independent counters instead of this shared budget.
|
|
15793
|
-
"/api/reports/*": {
|
|
15794
|
-
tier: "STRICT",
|
|
15795
|
-
userLimit: 60,
|
|
15796
|
-
userWindow: 3600,
|
|
15797
|
-
failClosed: true,
|
|
15798
|
-
skipAuthenticatedUsers: true,
|
|
15799
|
-
userLimitKey: "/api/reports/*"
|
|
15800
|
-
},
|
|
15801
|
-
// Admin report mutations (transition/group/ungroup/edit) — platform-admin
|
|
15802
|
-
// gated at the route, rate-limited here as defense-in-depth. Generous STANDARD
|
|
15803
|
-
// cap so legitimate triage bursts aren't throttled (grouping is one request
|
|
15804
|
-
// even for many members); deliberately NOT failClosed so a Redis blip never
|
|
15805
|
-
// blocks an admin's triage. The bare GET reads share this budget harmlessly.
|
|
15806
|
-
"/api/admin/reports/*": { tier: "STANDARD", userLimit: 150, userLimitKey: "/api/admin/reports/*" },
|
|
15807
|
-
// GitHub webhook (issues.closed → platform_report addressed). MODERATE +
|
|
15808
|
-
// fail-closed; GitHub retries on transient failures so a brief Redis blip
|
|
15809
|
-
// doesn't lose state.
|
|
15810
|
-
"/webhooks/github": { tier: "MODERATE", failClosed: true },
|
|
15811
|
-
// Account deletion — strict on the destructive POST + public undo. The
|
|
15812
|
-
// /api/users/* default tier above ALSO matches these, but `getEndpointConfig`
|
|
15813
|
-
// returns the longest-prefix match, so the more specific entries here win.
|
|
15814
|
-
// `skipAuthenticatedUsers` deliberately omitted: every caller is authenticated,
|
|
15815
|
-
// so including it would make `userLimit` a no-op — the per-user cap IS the gate.
|
|
15816
|
-
"/api/users/me/deletion": { tier: "STRICT", userLimit: 3, userWindow: 3600, failClosed: true },
|
|
15817
|
-
"/api/users/me/deletion/preflight": { tier: "STANDARD", userLimit: 30, failClosed: true },
|
|
15818
|
-
"/api/users/deletion/undo": { tier: "STRICT", failClosed: true },
|
|
15819
|
-
// Production-base destruction.
|
|
15820
|
-
//
|
|
15821
|
-
// Keyed off the CONTRACT constants, not literals: `BASE_DESTRUCTION_CONFIRM_PATH`'s own
|
|
15822
|
-
// docstring promises that the mount, the `AUTH_BYPASS_PATHS` entry, the rate-limit key and
|
|
15823
|
-
// the URL the email embeds move together, and a literal here is exactly how that promise
|
|
15824
|
-
// goes quietly false — leaving an unauthenticated HMAC endpoint on the default tier.
|
|
15825
|
-
//
|
|
15826
|
-
// The emailed GET is PUBLIC, so it gets the IP-tier bound the pre-auth routes get.
|
|
15827
|
-
// `failClosed` is deliberately NOT set: it is only consulted on the per-USER branch, which
|
|
15828
|
-
// needs an authenticated caller, so on a pre-auth route it is inert — the same thing
|
|
15829
|
-
// `/api/auth/magic-code/send` above says about itself. The IP limiter fails OPEN on a
|
|
15830
|
-
// limiter error by design (`packages/runtime/src/rate-limiter.ts`), and claiming otherwise
|
|
15831
|
-
// would tell the next auditor a protection exists that does not.
|
|
15832
|
-
[BASE_DESTRUCTION_CONFIRM_PATH]: { tier: "STRICT" },
|
|
15833
|
-
// The console's read, which every production base's settings pane issues on mount. Without
|
|
15834
|
-
// its own entry it inherits the mount-wide budget below, and an admin clicking through
|
|
15835
|
-
// thirty bases in an hour would be 429'd by a limit sized for irreversible writes.
|
|
15836
|
-
[`${BASE_DESTRUCTION_PREFIX}/destructions`]: { tier: "STANDARD", userLimit: 150 },
|
|
15837
|
-
// The mutating routes: initiate, the authenticated confirm, and cancel. Per user rather than
|
|
15838
|
-
// per IP — all three are authenticated and org-admin gated — and hourly, since a single human
|
|
15839
|
-
// touching thirty production-deletion operations in an hour is already far past normal.
|
|
15840
|
-
// Longest-prefix wins, so the two entries above are not widened by this one. The key is
|
|
15841
|
-
// pinned so those thirty are counted across every base the caller touches (gh #4756).
|
|
15842
|
-
[`${BASE_DESTRUCTION_PREFIX}/*`]: {
|
|
15843
|
-
tier: "STANDARD",
|
|
15844
|
-
userLimit: 30,
|
|
15845
|
-
userWindow: 3600,
|
|
15846
|
-
failClosed: true,
|
|
15847
|
-
userLimitKey: `${BASE_DESTRUCTION_PREFIX}/*`
|
|
15848
|
-
},
|
|
15849
|
-
// Email-invite preview — public, fail-closed. MODERATE (30/min IP) leaves
|
|
15850
|
-
// headroom for the signup-page server-fetch + reloads + prefetches; STRICT
|
|
15851
|
-
// would throttle legitimate users opening the link in multiple tabs.
|
|
15852
|
-
"/api/invites/preview": { tier: "MODERATE", failClosed: true },
|
|
15853
|
-
// "Don't email me again" (gh #4661) — public; one POST per click on its confirm page. STRICT
|
|
15854
|
-
// per IP. No `failClosed`: it is consulted only on the per-user branch, which a pre-auth route
|
|
15855
|
-
// never reaches (see `BASE_DESTRUCTION_CONFIRM_PATH` above).
|
|
15856
|
-
"/api/invites/unsubscribe": { tier: "STRICT" },
|
|
15857
|
-
// Admin DO debug reads — the ONE deliberate deviation from the Data Explorer
|
|
15858
|
-
// catch-all below, and the only reason a per-route key still exists here.
|
|
15859
|
-
// Platform-admin is enforced at the route; this is the independent
|
|
15860
|
-
// defense-in-depth signal so a runaway investigation script surfaces as 429.
|
|
15861
|
-
//
|
|
15862
|
-
// STRICT is kept ON PURPOSE even though the catch-all moved off it: per IP, at 5/min vs
|
|
15863
|
-
// STANDARD's 100/min, walking 10k ids takes ~33h instead of ~100min — a detection window
|
|
15864
|
-
// worth keeping on the routes that return RAW DO ROWS.
|
|
15865
|
-
//
|
|
15866
|
-
// The cost, stated rather than elided: STRICT is one bucket shared with
|
|
15867
|
-
// `/oauth/callback/*` and `/api/auth/magic-code/*`, so a debug session from a
|
|
15868
|
-
// NAT'd office IP can 429 a colleague's login for up to a minute. That trade is
|
|
15869
|
-
// accepted for a rare, deliberate investigation surface — and is exactly why the
|
|
15870
|
-
// browse routes below are NOT here.
|
|
15871
|
-
//
|
|
15872
|
-
// The key is pinned, so a sweep of `/debug/do/hub/<id>/tables` across ids spends one
|
|
15873
|
-
// per-admin budget (gh #4756). 120 an hour: an agent's investigation of a dozen
|
|
15874
|
-
// conversations made 37 debug reads in its busiest hour.
|
|
15875
|
-
"/api/admin/data-explorer/debug/*": {
|
|
15876
|
-
tier: "STRICT",
|
|
15877
|
-
userLimit: 120,
|
|
15878
|
-
userWindow: 3600,
|
|
15879
|
-
failClosed: true,
|
|
15880
|
-
userLimitKey: "/api/admin/data-explorer/debug/*"
|
|
15881
|
-
},
|
|
15882
|
-
// Catch-all for the rest of the Data Explorer (`/search`, `/entity/*`, `/do/*`,
|
|
15883
|
-
// `/kv/*`, the DELETE routes). Added as a catch-all rather than another per-route
|
|
15884
|
-
// key because this budget kept lagging behind routes joining the debug boundary:
|
|
15885
|
-
// with no match the path falls to the default, which sets no `userLimit`, so the
|
|
15886
|
-
// per-user layer never runs at all. `getEndpointConfig` takes the longest match,
|
|
15887
|
-
// so `debug/*` above still wins.
|
|
15888
|
-
//
|
|
15889
|
-
// `/entity/*` deliberately has NO key of its own: it needs precisely this config,
|
|
15890
|
-
// and a duplicate entry is how the two drift apart on the next edit.
|
|
15891
|
-
//
|
|
15892
|
-
// `tier` is deliberately STANDARD, not STRICT. `tier` is the per-IP knob and
|
|
15893
|
-
// STRICT is 5/min keyed on IP ALONE — one bucket shared across every STRICT
|
|
15894
|
-
// endpoint. On these browse routes that breaks the console (open Browse KV and
|
|
15895
|
-
// inspect four values = 6 requests; expand six org rows in the search results =
|
|
15896
|
-
// 6 `/entity/org/:id` calls) and would drain the same IP's budget for
|
|
15897
|
-
// `/oauth/callback/*` and `/api/auth/magic-code/*`, which on a NAT'd office IP
|
|
15898
|
-
// means all admins. The per-user hourly cap is the leg that matters here.
|
|
15899
|
-
//
|
|
15900
|
-
// The canonical key is deliberate: request paths contain DO ids and arbitrary
|
|
15901
|
-
// malformed requests can contain credential material. Keying on the request path
|
|
15902
|
-
// both stores that material in Redis/logs and gives every target a fresh counter.
|
|
15903
|
-
// One stable identity makes every browse request consume the same per-admin
|
|
15904
|
-
// budget without storing request targets.
|
|
15905
|
-
//
|
|
15906
|
-
// 600/hour preserves the original pagination requirement (about 30k rows at
|
|
15907
|
-
// 50/page) while now genuinely bounding id/key enumeration across the surface.
|
|
15908
|
-
"/api/admin/data-explorer/*": {
|
|
15909
|
-
tier: "STANDARD",
|
|
15910
|
-
userLimit: 600,
|
|
15911
|
-
userWindow: 3600,
|
|
15912
|
-
failClosed: true,
|
|
15913
|
-
userLimitKey: "/api/admin/data-explorer/*"
|
|
15914
|
-
},
|
|
15915
|
-
// Admin harness incident-response (harness-agents PR 2.9b) — mass-destroy reaps
|
|
15916
|
-
// EVERY live harness session/token/slot in scope, so it gets a strict budget
|
|
15917
|
-
// (STRICT + a low per-user hourly cap + fail-closed). Platform-admin is enforced
|
|
15918
|
-
// at the route; this is the independent defense-in-depth signal so a runaway
|
|
15919
|
-
// reap loop surfaces as 429. The break-glass disable/enable rides through
|
|
15920
|
-
// `/api/admin/config` (its own STANDARD budget), not this key.
|
|
15921
|
-
"/api/admin/harness/*": {
|
|
15922
|
-
tier: "STRICT",
|
|
15923
|
-
userLimit: 10,
|
|
15924
|
-
userWindow: 3600,
|
|
15925
|
-
failClosed: true,
|
|
15926
|
-
userLimitKey: "/api/admin/harness/*"
|
|
15927
|
-
},
|
|
15928
|
-
// Platform-wide team-orphan repair — a single call can WRITE across every hub of
|
|
15929
|
-
// every org (HubDO purge + UserDO mirror fan-out), so it gets the same strict,
|
|
15930
|
-
// fail-closed budget as the other cross-platform admin write. Exact path (not a
|
|
15931
|
-
// prefix): the sibling `/api/admin/hubs/:hubId/harness` flag flips stay on the
|
|
15932
|
-
// default admin budget. Platform-admin is enforced at the route; this is the
|
|
15933
|
-
// defense-in-depth signal that a repair loop surfaces as 429.
|
|
15934
|
-
"/api/admin/hubs/repair-team-orphans": { tier: "STRICT", userLimit: 10, userWindow: 3600, failClosed: true },
|
|
15935
|
-
// Branching-orphan repair (#4577) — deletes named production rows, and its report mode
|
|
15936
|
-
// fans out over every production hub of every org, so it takes the same strict,
|
|
15937
|
-
// fail-closed budget as the team-orphan repair. Exact path, for the same reason.
|
|
15938
|
-
"/api/admin/hubs/repair-branching-orphans": { tier: "STRICT", userLimit: 10, userWindow: 3600, failClosed: true },
|
|
15939
|
-
// CI/GitOps endpoints - standard with user limits. 500 because `wayai push` makes four requests
|
|
15940
|
-
// (pull, diff, push, pull) and a GitOps job may push every hub at once with one token; when
|
|
15941
|
-
// each path held its own 100, that ran to 100 hubs a minute.
|
|
15942
|
-
"/api/ci/*": { tier: "STANDARD", userLimit: 500, skipAuthenticatedUsers: true, userLimitKey: "/api/ci/*" },
|
|
15943
|
-
// Attested `/v1` proxy onto the Rekor Worker. `userLimitKey` is load-bearing here: the
|
|
15944
|
-
// per-user budget defaults to the full request path, and this surface's paths carry base,
|
|
15945
|
-
// record, and file ids — so without a pinned key every distinct resource would get its own
|
|
15946
|
-
// fresh budget and the per-user limit would bound nothing. One shared budget across the
|
|
15947
|
-
// whole data surface is the intent.
|
|
15948
|
-
[`${DATA_PROXY_PREFIX}/*`]: {
|
|
15949
|
-
tier: "STANDARD",
|
|
15950
|
-
userLimit: 150,
|
|
15951
|
-
skipAuthenticatedUsers: true,
|
|
15952
|
-
userLimitKey: `${DATA_PROXY_PREFIX}/*`
|
|
15953
|
-
},
|
|
15954
|
-
// Voice calls — the end user's call routes (`routes/api/calls`). Every caller is signed
|
|
15955
|
-
// in, so the per-IP tier is skipped for them: Brazilian mobile carriers put many callers
|
|
15956
|
-
// behind one IP, and a shared bucket would refuse one caller for another's traffic.
|
|
15957
|
-
//
|
|
15958
|
-
// Create is the only route that spends: it opens a provider session on the org's own
|
|
15959
|
-
// key, billed per second. So it takes its own exact key and an hourly per-user cap, which
|
|
15960
|
-
// lives on Redis and is fail-closed like the other hourly caps. The exact key wins over
|
|
15961
|
-
// the wildcard below, which also matches `/api/calls` (see `getEndpointConfig`).
|
|
15962
|
-
"/api/calls": {
|
|
15963
|
-
tier: "STANDARD",
|
|
15964
|
-
userLimit: 30,
|
|
15965
|
-
userWindow: 3600,
|
|
15966
|
-
failClosed: true,
|
|
15967
|
-
skipAuthenticatedUsers: true,
|
|
15968
|
-
userLimitKey: "/api/calls"
|
|
15969
|
-
},
|
|
15970
|
-
// The routes on one call: `ready`, hang-up, the delegation report and the status read; and
|
|
15971
|
-
// the opening read (`/api/calls/openings/:language`), one per call, before it exists.
|
|
15972
|
-
// NEVER fail-closed: a refused hang-up leaves the call running, and billing, until a
|
|
15973
|
-
// limit ends it. The 60 s budget runs on the native binding, which fails open. The key is
|
|
15974
|
-
// pinned because the paths carry the call id, which would otherwise give every call a
|
|
15975
|
-
// fresh budget.
|
|
15976
|
-
"/api/calls/*": {
|
|
15977
|
-
tier: "STANDARD",
|
|
15978
|
-
userLimit: 150,
|
|
15979
|
-
skipAuthenticatedUsers: true,
|
|
15980
|
-
userLimitKey: "/api/calls/*"
|
|
15981
|
-
},
|
|
15982
|
-
// Contacts between accounts (gh #4661, `routes/api/contacts`). Every caller is signed in, so
|
|
15983
|
-
// the per-IP tier is skipped. The keys are pinned because the paths carry user and invite
|
|
15984
|
-
// ids, which would otherwise give each id a fresh budget. Blocks take their own, lower
|
|
15985
|
-
// budget (#4661 Limits: blocks are rate-limited). The invites-per-day cap and every list
|
|
15986
|
-
// cap are counted in the caller's UserDO, in the same write they bound, not here.
|
|
15987
|
-
"/api/contacts/*": {
|
|
15988
|
-
tier: "STANDARD",
|
|
15989
|
-
userLimit: 60,
|
|
15990
|
-
skipAuthenticatedUsers: true,
|
|
15991
|
-
userLimitKey: "/api/contacts/*"
|
|
15992
|
-
},
|
|
15993
|
-
"/api/contacts/blocks/*": {
|
|
15994
|
-
tier: "STANDARD",
|
|
15995
|
-
userLimit: 30,
|
|
15996
|
-
skipAuthenticatedUsers: true,
|
|
15997
|
-
userLimitKey: "/api/contacts/blocks/*"
|
|
15998
|
-
},
|
|
15999
|
-
// Person-to-person threads (gh #4661, `routes/api/threads`): the per-user send limit, 60 a
|
|
16000
|
-
// minute on the native binding. Every thread route shares the one budget, reads included:
|
|
16001
|
-
// the paths carry thread and message ids, so the key is pinned, and a pattern here cannot
|
|
16002
|
-
// tell a send from a read of the same path. Like every 60 s budget it fails open on a
|
|
16003
|
-
// binding error, so it bounds sends in the steady state only.
|
|
16004
|
-
"/api/threads/*": {
|
|
16005
|
-
tier: "STANDARD",
|
|
16006
|
-
userLimit: 60,
|
|
16007
|
-
skipAuthenticatedUsers: true,
|
|
16008
|
-
userLimitKey: "/api/threads/*"
|
|
16009
|
-
},
|
|
16010
|
-
// An org admin's erasure of one person's thread messages (gh #4661 owner decision 7): any user
|
|
16011
|
-
// can own a free org, so each request arms that org's alarm, and this caps how fast one person
|
|
16012
|
-
// can ask. The org's own daily cap (`PEOPLE_ORG_ERASURES_PER_DAY`, counted in its OrgDO in the
|
|
16013
|
-
// write it bounds) is the fail-closed bound; this budget, on the native binding, fails open.
|
|
16014
|
-
"/api/threads/org-erasures": {
|
|
16015
|
-
tier: "STANDARD",
|
|
16016
|
-
userLimit: 10,
|
|
16017
|
-
skipAuthenticatedUsers: true,
|
|
16018
|
-
userLimitKey: "/api/threads/org-erasures"
|
|
16019
|
-
},
|
|
16020
|
-
// A person's report of a message, thread or person (`people-reports.ts`): each one written
|
|
16021
|
-
// arms the PlatformDO's alarm and emails the platform admins, so it takes its own, lower
|
|
16022
|
-
// budget. The reporter's daily cap (`PEOPLE_REPORTS_PER_DAY`, counted in the report store in
|
|
16023
|
-
// the write it bounds) is the fail-closed bound; this budget, on the native binding, fails open.
|
|
16024
|
-
"/api/threads/reports": {
|
|
16025
|
-
tier: "STANDARD",
|
|
16026
|
-
userLimit: 10,
|
|
16027
|
-
skipAuthenticatedUsers: true,
|
|
16028
|
-
userLimitKey: "/api/threads/reports"
|
|
16029
|
-
},
|
|
16030
|
-
// Communities (gh #4872, `routes/api/communities`): every signed-in route under the mount (the
|
|
16031
|
-
// preview below excepted) shares one per-user budget, which is also each member's send limit and
|
|
16032
|
-
// bounds the reads that scan a community's rows (a title search, the stats); a community's own
|
|
16033
|
-
// limits (its rate, the daily caps, slow mode) are counted in its CommunityDO, in the write they
|
|
16034
|
-
// bound. Every caller is signed in, so the per-IP tier is skipped; the key is pinned because the
|
|
16035
|
-
// paths carry community, room and message ids.
|
|
16036
|
-
"/api/communities/*": {
|
|
16037
|
-
tier: "STANDARD",
|
|
16038
|
-
userLimit: 60,
|
|
16039
|
-
skipAuthenticatedUsers: true,
|
|
16040
|
-
userLimitKey: "/api/communities/*"
|
|
16041
|
-
},
|
|
16042
|
-
// The join page's preview of a link — public, fetched from the visitor's browser, so this
|
|
16043
|
-
// per-IP limit is its one bound and runs before the route reads anything (#4872
|
|
16044
|
-
// owner-delegated decision 9). MODERATE (30/min per IP) leaves room for reloads and several
|
|
16045
|
-
// tabs. An exact key, so it wins over the wildcard above.
|
|
16046
|
-
"/api/communities/join-preview": { tier: "MODERATE" },
|
|
16047
|
-
// An announcement email's unsubscribe (#4872 PR 8a) — public: its token is its one credential, so
|
|
16048
|
-
// this per-IP limit is its one bound, and runs before the route reads anything. MODERATE (30/min
|
|
16049
|
-
// per IP), not STRICT: mail clients' one-click unsubscribe POSTs it from their own servers, which
|
|
16050
|
-
// many members' clicks share, and STRICT's 5/min bucket is shared with sign-in. An exact key, so
|
|
16051
|
-
// it wins over the wildcard above.
|
|
16052
|
-
[COMMUNITY_EMAIL_UNSUBSCRIBE_PATH]: { tier: "MODERATE" },
|
|
16053
|
-
// The WayAI Guide's help entry (wayai#4948, `routes/api/guide.ts`): one POST per click by a viewer
|
|
16054
|
-
// the snapshot does not show enrolled, which reads the person's row on the Guide hub and writes only
|
|
16055
|
-
// what is missing (an enrolled viewer's click opens the hub without a request). Every caller is
|
|
16056
|
-
// signed in, so the per-IP tier is skipped; 10 a minute per person on the native binding (fails
|
|
16057
|
-
// open, like every 60 s budget) bounds what a looping client costs the one Guide HubDO.
|
|
16058
|
-
"/api/guide/access": {
|
|
16059
|
-
tier: "STANDARD",
|
|
16060
|
-
userLimit: 10,
|
|
16061
|
-
skipAuthenticatedUsers: true,
|
|
16062
|
-
userLimitKey: "/api/guide/access"
|
|
16063
|
-
},
|
|
16064
|
-
// App channel - high throughput with user limits
|
|
16065
|
-
"/channels/app/*": { tier: "HIGH", userLimit: 500, skipAuthenticatedUsers: true },
|
|
16066
|
-
// API channel (api-channel-connector) - machine-to-machine, high throughput
|
|
16067
|
-
"/channels/api/*": { tier: "HIGH", userLimit: 500, skipAuthenticatedUsers: true },
|
|
16068
|
-
// System/scheduled - high throughput (internal)
|
|
16069
|
-
"/channels/system/*": { tier: "HIGH", skipAuthenticatedUsers: true },
|
|
16070
|
-
"/scheduled/*": { tier: "HIGH", skipAuthenticatedUsers: true },
|
|
16071
|
-
// WebSocket - standard with user limit to prevent connection spam
|
|
16072
|
-
"/live-updates/ws": { tier: "STANDARD", userLimit: 100, failClosed: true },
|
|
16073
|
-
// A community's live socket (gh #4872 PR 5, `GET /communities/:communityId/live`): outside
|
|
16074
|
-
// `/api/*`, with no bearer token, so its per-IP tier is its one bound before the CommunityDO
|
|
16075
|
-
// checks the ticket. STANDARD, as `/live-updates/ws`: a client opens one per community it shows
|
|
16076
|
-
// and reconnects within the hour.
|
|
16077
|
-
"/communities/*": { tier: "STANDARD" },
|
|
16078
|
-
// WS ticket mint - per-user 60/min accommodates aggressive reconnect patterns
|
|
16079
|
-
// without opening abuse vectors; fail-closed so Redis outages don't let
|
|
16080
|
-
// runaway clients mint unbounded tickets.
|
|
16081
|
-
"/api/auth/ws-ticket": { tier: "MODERATE", userLimit: 60, failClosed: true, skipAuthenticatedUsers: true },
|
|
16082
|
-
"/api/auth/session-check": { tier: "STANDARD", userLimit: 120, skipAuthenticatedUsers: true },
|
|
16083
|
-
"/api/auth/logout": { tier: "MODERATE", userLimit: 10, failClosed: true, skipAuthenticatedUsers: true }
|
|
16084
|
-
};
|
|
16085
|
-
EXCLUDED_PATHS = /* @__PURE__ */ new Set(["/health"]);
|
|
16086
16344
|
UUID_RE2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
16087
16345
|
WAYAI_WORKSPACE_LAYOUT = {
|
|
16088
16346
|
/** Product-namespaced root dir at the git root. */
|
|
@@ -33235,7 +33493,7 @@ token (\`wayai login --token\`, or WAYAI_TOKEN) is refused.
|
|
|
33235
33493
|
var init_rekor_toolset = __esm({
|
|
33236
33494
|
"../../packages/core/dist/contracts/rekor-toolset.js"() {
|
|
33237
33495
|
"use strict";
|
|
33238
|
-
|
|
33496
|
+
init_chunk_4MRBBR5L();
|
|
33239
33497
|
init_chunk_BEOHBREG();
|
|
33240
33498
|
init_chunk_DHPUPI4Y();
|
|
33241
33499
|
}
|
|
@@ -33450,6 +33708,12 @@ var init_resource_paths = __esm({
|
|
|
33450
33708
|
});
|
|
33451
33709
|
|
|
33452
33710
|
// src/knowledge/source.ts
|
|
33711
|
+
function orgSourceKey(orgResourceId) {
|
|
33712
|
+
return `organization resource ${orgResourceId}`;
|
|
33713
|
+
}
|
|
33714
|
+
function hubSourceKey(productionHubId, resourceId) {
|
|
33715
|
+
return `hub ${productionHubId} resource ${resourceId}`;
|
|
33716
|
+
}
|
|
33453
33717
|
function movesFromOrg(resource) {
|
|
33454
33718
|
return resource.resource_type === "knowledge";
|
|
33455
33719
|
}
|
|
@@ -33499,7 +33763,7 @@ function orgSources(resources, rows) {
|
|
|
33499
33763
|
skill_name: r.skill_name,
|
|
33500
33764
|
resource_type: r.resource_type,
|
|
33501
33765
|
created_at: r.created_at,
|
|
33502
|
-
sourceKey:
|
|
33766
|
+
sourceKey: orgSourceKey(r.org_resource_id),
|
|
33503
33767
|
description: r.resource_description ?? "",
|
|
33504
33768
|
folderSchema: r.folder_metadata_schema,
|
|
33505
33769
|
fileSchema: r.file_metadata_schema,
|
|
@@ -33548,7 +33812,7 @@ function hubSources(hubId, resources, rows) {
|
|
|
33548
33812
|
skill_name: r.skill_name,
|
|
33549
33813
|
resource_type: r.resource_type,
|
|
33550
33814
|
created_at: r.created_at,
|
|
33551
|
-
sourceKey:
|
|
33815
|
+
sourceKey: hubSourceKey(hubId, r.resource_id),
|
|
33552
33816
|
description: r.resource_description ?? "",
|
|
33553
33817
|
folderSchema: r.folder_metadata_schema,
|
|
33554
33818
|
fileSchema: r.file_metadata_schema,
|
|
@@ -33619,7 +33883,10 @@ function orgFileTypeId(resource) {
|
|
|
33619
33883
|
return resource.slug;
|
|
33620
33884
|
}
|
|
33621
33885
|
function hubFileTypeId(hubSlug, resource) {
|
|
33622
|
-
return `${hubSlug}
|
|
33886
|
+
return `${hubSlug}${HUB_PART_SEPARATOR}${resource.slug}`;
|
|
33887
|
+
}
|
|
33888
|
+
function hubPartOf(fileTypeId) {
|
|
33889
|
+
return fileTypeId.split(HUB_PART_SEPARATOR)[0];
|
|
33623
33890
|
}
|
|
33624
33891
|
function toolAlias(id) {
|
|
33625
33892
|
const hash = createHash5("sha256").update(id).digest("hex").slice(0, 6);
|
|
@@ -34158,7 +34425,7 @@ function describeSkip(skip) {
|
|
|
34158
34425
|
return `${skip.fileType}: folder "${skip.folder}" has a description but no path, so it gets no ${INDEX_FILE}.`;
|
|
34159
34426
|
}
|
|
34160
34427
|
}
|
|
34161
|
-
var MAX_TOOL_BASE_LENGTH, INDEX_FILE, PROVENANCE_PREFIX, MAX_NAME_LENGTH3, MAX_PATH_LENGTH, TITLE_SCHEMA, TAGS_SCHEMA, MAX_NOTE_VALUES, DEFINITION_KEYS, DRAFT, MAX_PATTERNED_VALUE_LENGTH, SUBSCHEMA_KEYS, SUBSCHEMA_MAP_KEYS;
|
|
34428
|
+
var MAX_TOOL_BASE_LENGTH, INDEX_FILE, PROVENANCE_PREFIX, MAX_NAME_LENGTH3, MAX_PATH_LENGTH, TITLE_SCHEMA, TAGS_SCHEMA, MAX_NOTE_VALUES, HUB_PART_SEPARATOR, DEFINITION_KEYS, DRAFT, MAX_PATTERNED_VALUE_LENGTH, SUBSCHEMA_KEYS, SUBSCHEMA_MAP_KEYS;
|
|
34162
34429
|
var init_plan = __esm({
|
|
34163
34430
|
"src/knowledge/plan.ts"() {
|
|
34164
34431
|
"use strict";
|
|
@@ -34173,6 +34440,7 @@ var init_plan = __esm({
|
|
|
34173
34440
|
TITLE_SCHEMA = { type: "string" };
|
|
34174
34441
|
TAGS_SCHEMA = { type: "array", items: { type: "string" } };
|
|
34175
34442
|
MAX_NOTE_VALUES = 3;
|
|
34443
|
+
HUB_PART_SEPARATOR = "--";
|
|
34176
34444
|
DEFINITION_KEYS = ["$defs", "definitions"];
|
|
34177
34445
|
DRAFT = "2020-12";
|
|
34178
34446
|
MAX_PATTERNED_VALUE_LENGTH = 1024;
|
|
@@ -34200,7 +34468,10 @@ var init_plan = __esm({
|
|
|
34200
34468
|
|
|
34201
34469
|
// src/knowledge/migrate.ts
|
|
34202
34470
|
import { isDeepStrictEqual as isDeepStrictEqual3 } from "util";
|
|
34203
|
-
|
|
34471
|
+
function refreshPreviewCommand(orgId, base) {
|
|
34472
|
+
return `wayai knowledge refresh-preview --org ${orgId} --base ${safe(base)}`;
|
|
34473
|
+
}
|
|
34474
|
+
async function resolveBases(kb, baseId2, orgId) {
|
|
34204
34475
|
const base = await kb.getBase(baseId2);
|
|
34205
34476
|
if (!base || base.deleted) throw expected(`Base "${safe(baseId2)}" not found in this organization.`);
|
|
34206
34477
|
if (base.environment === "preview") {
|
|
@@ -34220,7 +34491,7 @@ async function resolveBases(kb, baseId2) {
|
|
|
34220
34491
|
}
|
|
34221
34492
|
if (previews.length !== 1) {
|
|
34222
34493
|
throw expected(
|
|
34223
|
-
previews.length === 0 ? `Base "${safe(baseId2)}" has no preview
|
|
34494
|
+
previews.length === 0 ? `Base "${safe(baseId2)}" has no preview. Create one with \`wayai bases create-preview ${safe(baseId2)} --name <name>\`, then copy production's files into it with \`${refreshPreviewCommand(orgId, baseId2)}\`.` : `Base "${safe(baseId2)}" has ${previews.length} previews (${previews.map(safe).join(", ")}). Pass the one to use as --base.`
|
|
34224
34495
|
);
|
|
34225
34496
|
}
|
|
34226
34497
|
return { production: base.id, preview: previews[0] };
|
|
@@ -34277,17 +34548,17 @@ function sameFileType(remote, spec) {
|
|
|
34277
34548
|
function isObject(value) {
|
|
34278
34549
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
34279
34550
|
}
|
|
34280
|
-
function promotionProblems(result, ids) {
|
|
34551
|
+
function promotionProblems(result, ids, section = "file_types") {
|
|
34281
34552
|
const answer = isObject(result) ? result : {};
|
|
34282
34553
|
const problems2 = [];
|
|
34283
|
-
for (const [
|
|
34554
|
+
for (const [kind, bucket2] of Object.entries(isObject(answer.diff) ? answer.diff : {})) {
|
|
34284
34555
|
if (!isObject(bucket2)) continue;
|
|
34285
34556
|
for (const change of ["added", "modified", "removed"]) {
|
|
34286
34557
|
for (const entry of Array.isArray(bucket2[change]) ? bucket2[change] : []) {
|
|
34287
34558
|
const row = isObject(entry) ? isObject(entry.after) ? entry.after : entry : {};
|
|
34288
34559
|
const id = typeof row.id === "string" ? row.id : JSON.stringify(row).slice(0, 80);
|
|
34289
|
-
if (
|
|
34290
|
-
problems2.push(`it would also apply ${
|
|
34560
|
+
if (kind === section && change !== "removed" && ids.has(id)) continue;
|
|
34561
|
+
problems2.push(`it would also apply ${kind} ${change}: "${id}"`);
|
|
34291
34562
|
}
|
|
34292
34563
|
}
|
|
34293
34564
|
}
|
|
@@ -34326,7 +34597,7 @@ function printNotes(io, notes) {
|
|
|
34326
34597
|
}
|
|
34327
34598
|
}
|
|
34328
34599
|
async function migrateKnowledge(reader, kb, opts, io) {
|
|
34329
|
-
const { production: productionId, preview } = await resolveBases(kb, opts.base);
|
|
34600
|
+
const { production: productionId, preview } = await resolveBases(kb, opts.base, opts.orgId);
|
|
34330
34601
|
const hubs = opts.hubId === void 0 ? [] : await reader.listHubs();
|
|
34331
34602
|
const hub = opts.hubId === void 0 ? void 0 : productionHub(hubs, opts.hubId);
|
|
34332
34603
|
const production = await Production.read(kb, productionId);
|
|
@@ -34465,7 +34736,7 @@ Dry run: nothing written (${writes === 0 ? "nothing to write" : `${writes} write
|
|
|
34465
34736
|
for (const file of creates) await kb.putFile(productionId, file, await bodyBytes(reader, file));
|
|
34466
34737
|
io.log(
|
|
34467
34738
|
`
|
|
34468
|
-
Done: ${toWrite.length} file type(s) written, ${toPromote.length} promoted, ${creates.length} file(s) written. The preview holds none of these files
|
|
34739
|
+
Done: ${toWrite.length} file type(s) written, ${toPromote.length} promoted, ${creates.length} file(s) written. The preview holds none of these files: copy them with \`${refreshPreviewCommand(opts.orgId, opts.base)}\`.`
|
|
34469
34740
|
);
|
|
34470
34741
|
return { ...result, written: true };
|
|
34471
34742
|
}
|
|
@@ -34556,6 +34827,253 @@ var init_headers = __esm({
|
|
|
34556
34827
|
}
|
|
34557
34828
|
});
|
|
34558
34829
|
|
|
34830
|
+
// src/data/commands/tokens.ts
|
|
34831
|
+
import { Command } from "commander";
|
|
34832
|
+
function rawTokenFrom(data) {
|
|
34833
|
+
const token = typeof data === "object" && data !== null && "token" in data ? data.token : void 0;
|
|
34834
|
+
if (typeof token !== "string" || !RAW_TOKEN_PATTERN.test(token)) {
|
|
34835
|
+
throw new Error("Token creation response did not contain a valid rec_ token");
|
|
34836
|
+
}
|
|
34837
|
+
return token;
|
|
34838
|
+
}
|
|
34839
|
+
function addTokenLifecycleOptions(cmd) {
|
|
34840
|
+
return cmd.option("--expires-at <date>", "Expiration date (ISO 8601, e.g. 2026-06-01T00:00:00Z)").option(
|
|
34841
|
+
"--ttl <duration>",
|
|
34842
|
+
"Relative expiry, e.g. 10m, 2h, 7d (use for disposable probe/test tokens)"
|
|
34843
|
+
).option(
|
|
34844
|
+
"--description <text>",
|
|
34845
|
+
'What holds this token (e.g. "behind credential X"); shown in list and revoke warnings'
|
|
34846
|
+
).addHelpText("after", TOKEN_OUTPUT_HELP);
|
|
34847
|
+
}
|
|
34848
|
+
function tokenCreationOutput(cmd, opts) {
|
|
34849
|
+
return opts.tokenOnly ? "token" : outputFormat(cmd);
|
|
34850
|
+
}
|
|
34851
|
+
function printCreatedToken(data, output) {
|
|
34852
|
+
if (output !== "token") {
|
|
34853
|
+
printOutput(data, output);
|
|
34854
|
+
return;
|
|
34855
|
+
}
|
|
34856
|
+
console.log(rawTokenFrom(data));
|
|
34857
|
+
}
|
|
34858
|
+
function resolveExpiresAt(opts) {
|
|
34859
|
+
if (opts.ttl && opts.expiresAt) {
|
|
34860
|
+
throw expected("--ttl and --expires-at are mutually exclusive \u2014 pass one.");
|
|
34861
|
+
}
|
|
34862
|
+
if (opts.ttl) return new Date(Date.now() + parseDuration(opts.ttl, "--ttl")).toISOString();
|
|
34863
|
+
return opts.expiresAt;
|
|
34864
|
+
}
|
|
34865
|
+
function applyTokenLifecycleOpts(body, opts) {
|
|
34866
|
+
const expiresAt = resolveExpiresAt(opts);
|
|
34867
|
+
if (expiresAt) body.expires_at = expiresAt;
|
|
34868
|
+
if (opts.description) body.description = opts.description;
|
|
34869
|
+
}
|
|
34870
|
+
function listAllTokens(client) {
|
|
34871
|
+
return client.collectPages(
|
|
34872
|
+
(cursor) => `/v1/tokens?limit=${TOKEN_LIST_PAGE_LIMIT}${cursor ? `&cursor=${encodeURIComponent(cursor)}` : ""}`
|
|
34873
|
+
);
|
|
34874
|
+
}
|
|
34875
|
+
function pruneRow(t, reason) {
|
|
34876
|
+
return {
|
|
34877
|
+
token_id: t.token_id,
|
|
34878
|
+
name: t.name,
|
|
34879
|
+
reason,
|
|
34880
|
+
last_used: t.last_used_at ?? "never"
|
|
34881
|
+
};
|
|
34882
|
+
}
|
|
34883
|
+
function buildBasesTokensCommand() {
|
|
34884
|
+
const tokens2 = new Command("tokens").description("Manage API tokens");
|
|
34885
|
+
addTokenLifecycleOptions(
|
|
34886
|
+
tokens2.command("create").description("Create a scoped API token").requiredOption("--name <name>", "Token name").requiredOption("--grants <json>", "Grant definitions as JSON array").option(
|
|
34887
|
+
"--token-only",
|
|
34888
|
+
"Print only the raw rec_ token for secure command substitution (shown once)"
|
|
34889
|
+
)
|
|
34890
|
+
).action(async function(opts) {
|
|
34891
|
+
const grants = parseData(opts.grants, "--grants");
|
|
34892
|
+
const body = { name: opts.name, grants };
|
|
34893
|
+
applyTokenLifecycleOpts(body, opts);
|
|
34894
|
+
const client = await createDataClient();
|
|
34895
|
+
const data = await client.request("POST", "/v1/tokens", body);
|
|
34896
|
+
printCreatedToken(data, tokenCreationOutput(this, opts));
|
|
34897
|
+
});
|
|
34898
|
+
addTokenLifecycleOptions(
|
|
34899
|
+
// The only token verb that addresses a base, so it alone declares `--base`.
|
|
34900
|
+
// Putting it on the `tokens` group would advertise a flag that does nothing
|
|
34901
|
+
// on `list`, `revoke` and `prune` — the defect `withBaseOption` exists to
|
|
34902
|
+
// avoid.
|
|
34903
|
+
withBaseOption(tokens2.command("create-for-toolset <slug>")).description(
|
|
34904
|
+
"Create a toolset-bound (consumer) token whose authorization IS the toolset tool surface \u2014 it can call exactly the tools that toolset exposes (those record types + operations, relationships, batch, and sql only if enabled) and nothing else. Binds to the configured --base."
|
|
34905
|
+
).option("--name <name>", 'Token name (default: "<slug> consumer")').option(
|
|
34906
|
+
"--token-only",
|
|
34907
|
+
"Print only the raw rec_ token for secure command substitution (shown once)"
|
|
34908
|
+
)
|
|
34909
|
+
).action(async function(slug, opts) {
|
|
34910
|
+
const base = requireBase(this);
|
|
34911
|
+
const body = {
|
|
34912
|
+
name: opts.name ?? `${slug} consumer`,
|
|
34913
|
+
toolset_binding: { base_id: base, toolset_id: slug }
|
|
34914
|
+
};
|
|
34915
|
+
applyTokenLifecycleOpts(body, opts);
|
|
34916
|
+
const client = await createDataClient();
|
|
34917
|
+
const data = await client.request("POST", "/v1/tokens", body);
|
|
34918
|
+
const output = tokenCreationOutput(this, opts);
|
|
34919
|
+
printCreatedToken(data, output);
|
|
34920
|
+
if (output === "table") {
|
|
34921
|
+
console.log(`
|
|
34922
|
+
MCP URL: ${toolsetMcpUrl(slug)}`);
|
|
34923
|
+
}
|
|
34924
|
+
});
|
|
34925
|
+
tokens2.command("list").description("List API tokens (status, last_used_at, expires_at, description)").action(async function() {
|
|
34926
|
+
const client = await createDataClient();
|
|
34927
|
+
printOutput(await listAllTokens(client), outputFormat(this));
|
|
34928
|
+
});
|
|
34929
|
+
tokens2.command("revoke <token_id>").description(
|
|
34930
|
+
"Revoke an API token (permanent). A recently-used or toolset-bound token requires --force."
|
|
34931
|
+
).option("--force", "Revoke even if the token appears to be in live use").action(async function(tokenId, opts) {
|
|
34932
|
+
const client = await createDataClient();
|
|
34933
|
+
const id = pathSegment(tokenId, "token id");
|
|
34934
|
+
const path42 = (force) => `/v1/tokens/${id}${force ? "?force=true" : ""}`;
|
|
34935
|
+
const format = outputFormat(this);
|
|
34936
|
+
const revoked = (forced) => printOutput({ token_id: tokenId, revoked: true, forced }, format);
|
|
34937
|
+
if (opts.force) {
|
|
34938
|
+
await client.request("DELETE", path42(true));
|
|
34939
|
+
revoked(true);
|
|
34940
|
+
return;
|
|
34941
|
+
}
|
|
34942
|
+
try {
|
|
34943
|
+
await client.request("DELETE", path42(false));
|
|
34944
|
+
revoked(false);
|
|
34945
|
+
} catch (err) {
|
|
34946
|
+
if (!(err instanceof ApiError) || err.status !== 409 || dataErrorDetails(err)?.requires_force !== true) {
|
|
34947
|
+
throw err;
|
|
34948
|
+
}
|
|
34949
|
+
console.error(err.message);
|
|
34950
|
+
if (!isInteractive()) {
|
|
34951
|
+
console.error("\nRe-run with --force to revoke anyway.");
|
|
34952
|
+
return process.exit(1);
|
|
34953
|
+
}
|
|
34954
|
+
if (!await confirm("Revoke anyway?")) {
|
|
34955
|
+
console.error("Aborted.");
|
|
34956
|
+
return process.exit(1);
|
|
34957
|
+
}
|
|
34958
|
+
await client.request("DELETE", path42(true));
|
|
34959
|
+
revoked(true);
|
|
34960
|
+
}
|
|
34961
|
+
});
|
|
34962
|
+
tokens2.command("prune").description(
|
|
34963
|
+
"Revoke stale tokens, selected by staleness only (expired, or unused past --unused-since) \u2014 never by name. Previews by default; revoking requires --yes or an interactive confirmation."
|
|
34964
|
+
).option(
|
|
34965
|
+
"--unused-since <duration>",
|
|
34966
|
+
"Also prune tokens with no activity for this long (e.g. 30d). Skips toolset-bound and legacy usage-untracked tokens."
|
|
34967
|
+
).option("--include-bound", "Allow pruning stale toolset-bound (consumer) tokens").option("-y, --yes", "Revoke without prompting (required when non-interactive)").action(async function(opts) {
|
|
34968
|
+
const staleMs = opts.unusedSince ? parseDuration(opts.unusedSince, "--unused-since") : void 0;
|
|
34969
|
+
const client = await createDataClient();
|
|
34970
|
+
const allTokens = await listAllTokens(client);
|
|
34971
|
+
const now = Date.now();
|
|
34972
|
+
const targets = [];
|
|
34973
|
+
const skipped = [];
|
|
34974
|
+
for (const t of allTokens) {
|
|
34975
|
+
const expired = t.status ? t.status === "expired" : !!t.expires_at && Date.parse(t.expires_at) <= now;
|
|
34976
|
+
if (expired) {
|
|
34977
|
+
targets.push(pruneRow(t, "expired"));
|
|
34978
|
+
continue;
|
|
34979
|
+
}
|
|
34980
|
+
if (staleMs === void 0) continue;
|
|
34981
|
+
const lastActivity = Date.parse(t.last_used_at ?? t.created_at);
|
|
34982
|
+
if (!(now - lastActivity > staleMs)) continue;
|
|
34983
|
+
if (t.usage_tracked !== true) {
|
|
34984
|
+
skipped.push(
|
|
34985
|
+
pruneRow(
|
|
34986
|
+
t,
|
|
34987
|
+
"usage untracked (legacy token) \u2014 revoke individually if truly unused"
|
|
34988
|
+
)
|
|
34989
|
+
);
|
|
34990
|
+
} else if (t.toolset_binding && !opts.includeBound) {
|
|
34991
|
+
skipped.push(
|
|
34992
|
+
pruneRow(
|
|
34993
|
+
t,
|
|
34994
|
+
`toolset-bound ('${t.toolset_binding.toolset_id}') \u2014 pass --include-bound to prune`
|
|
34995
|
+
)
|
|
34996
|
+
);
|
|
34997
|
+
} else {
|
|
34998
|
+
targets.push(pruneRow(t, `unused for over ${opts.unusedSince}`));
|
|
34999
|
+
}
|
|
35000
|
+
}
|
|
35001
|
+
const format = outputFormat(this);
|
|
35002
|
+
const json = format === "json";
|
|
35003
|
+
const note = (line) => json ? console.error(line) : console.log(line);
|
|
35004
|
+
const printPreview = () => {
|
|
35005
|
+
if (targets.length === 0) {
|
|
35006
|
+
note("Nothing to prune.");
|
|
35007
|
+
} else {
|
|
35008
|
+
note(`Would revoke ${targets.length} token(s):`);
|
|
35009
|
+
if (!json) printOutput(targets, format);
|
|
35010
|
+
}
|
|
35011
|
+
if (skipped.length === 0) return;
|
|
35012
|
+
note(`
|
|
35013
|
+
Skipped ${skipped.length} stale token(s):`);
|
|
35014
|
+
if (!json) printOutput(skipped, format);
|
|
35015
|
+
};
|
|
35016
|
+
printPreview();
|
|
35017
|
+
if (targets.length === 0) {
|
|
35018
|
+
if (json) printOutput({ revoked: 0, failed: 0, targets, skipped }, format);
|
|
35019
|
+
return;
|
|
35020
|
+
}
|
|
35021
|
+
if (!opts.yes) {
|
|
35022
|
+
if (!isInteractive()) {
|
|
35023
|
+
console.error("\nPreview only \u2014 re-run with --yes to revoke these tokens.");
|
|
35024
|
+
if (json) printOutput({ preview: true, revoked: 0, failed: 0, targets, skipped }, format);
|
|
35025
|
+
return process.exit(1);
|
|
35026
|
+
}
|
|
35027
|
+
if (!await confirm(`
|
|
35028
|
+
Revoke ${targets.length} token(s)?`)) {
|
|
35029
|
+
console.error("Aborted.");
|
|
35030
|
+
return process.exit(1);
|
|
35031
|
+
}
|
|
35032
|
+
}
|
|
35033
|
+
let revoked = 0;
|
|
35034
|
+
let failed = 0;
|
|
35035
|
+
for (const t of targets) {
|
|
35036
|
+
try {
|
|
35037
|
+
await client.request(
|
|
35038
|
+
"DELETE",
|
|
35039
|
+
`/v1/tokens/${pathSegment(t.token_id, "token id")}?force=true`
|
|
35040
|
+
);
|
|
35041
|
+
revoked++;
|
|
35042
|
+
} catch (err) {
|
|
35043
|
+
failed++;
|
|
35044
|
+
console.error(
|
|
35045
|
+
`Failed to revoke ${t.token_id} ('${sanitizeTerminalText(t.name)}'): ${err instanceof Error ? err.message : String(err)}`
|
|
35046
|
+
);
|
|
35047
|
+
}
|
|
35048
|
+
}
|
|
35049
|
+
if (json) printOutput({ revoked, failed, targets, skipped }, format);
|
|
35050
|
+
else console.log(`Revoked ${revoked} token(s)${failed > 0 ? `, ${failed} failed` : ""}.`);
|
|
35051
|
+
if (failed > 0) return process.exit(1);
|
|
35052
|
+
});
|
|
35053
|
+
return tokens2;
|
|
35054
|
+
}
|
|
35055
|
+
var RAW_TOKEN_PATTERN, TOKEN_OUTPUT_HELP, TOKEN_LIST_PAGE_LIMIT;
|
|
35056
|
+
var init_tokens = __esm({
|
|
35057
|
+
"src/data/commands/tokens.ts"() {
|
|
35058
|
+
"use strict";
|
|
35059
|
+
init_client();
|
|
35060
|
+
init_output();
|
|
35061
|
+
init_helpers();
|
|
35062
|
+
init_api_client();
|
|
35063
|
+
init_terminal_output();
|
|
35064
|
+
init_utils();
|
|
35065
|
+
init_expected();
|
|
35066
|
+
RAW_TOKEN_PATTERN = /^rec_[A-Za-z0-9_-]{43}$/;
|
|
35067
|
+
TOKEN_OUTPUT_HELP = `
|
|
35068
|
+
Output:
|
|
35069
|
+
--json prints the unwrapped response object; the secret is at top-level .token, never .data.token.
|
|
35070
|
+
--token-only prints only a validated rec_ token and overrides table/JSON formatting.
|
|
35071
|
+
The raw token is shown once. Capture it securely and do not log it.
|
|
35072
|
+
`;
|
|
35073
|
+
TOKEN_LIST_PAGE_LIMIT = 250;
|
|
35074
|
+
}
|
|
35075
|
+
});
|
|
35076
|
+
|
|
34559
35077
|
// src/knowledge/remote.ts
|
|
34560
35078
|
function pacedRequestsPerMinute(path42, fallback) {
|
|
34561
35079
|
const budget = getEndpointConfig(path42)?.userLimit;
|
|
@@ -34606,6 +35124,14 @@ function knowledgeBase(client) {
|
|
|
34606
35124
|
const request = (method, path42, body) => pacer.run(() => client.request(method, path42, body));
|
|
34607
35125
|
const baseSeg = (id) => pathSegment(id, "--base");
|
|
34608
35126
|
const typeSeg = (id) => pathSegment(id, "file type");
|
|
35127
|
+
const toolsetSeg = (id) => pathSegment(id, "toolset slug");
|
|
35128
|
+
const promoteOnly = (production, preview, section, ids, dryRun) => request("POST", `/v1/${baseSeg(production)}/promote`, {
|
|
35129
|
+
source_base_id: preview,
|
|
35130
|
+
dry_run: dryRun,
|
|
35131
|
+
...Object.fromEntries(ENTITY_KINDS.map((kind) => [kind, []])),
|
|
35132
|
+
file_types: [],
|
|
35133
|
+
[section]: ids
|
|
35134
|
+
});
|
|
34609
35135
|
return {
|
|
34610
35136
|
getBase: (id) => unlessMissing(() => pacer.run(() => getBaseRecord(client, id))),
|
|
34611
35137
|
listPreviews: (id) => pacer.run(
|
|
@@ -34617,14 +35143,7 @@ function knowledgeBase(client) {
|
|
|
34617
35143
|
const { id, ...body } = spec;
|
|
34618
35144
|
await request("PUT", `/v1/${baseSeg(base)}/file-types/${typeSeg(id)}`, body);
|
|
34619
35145
|
},
|
|
34620
|
-
|
|
34621
|
-
return request("POST", `/v1/${baseSeg(production)}/promote`, {
|
|
34622
|
-
source_base_id: preview,
|
|
34623
|
-
dry_run: dryRun,
|
|
34624
|
-
...Object.fromEntries(ENTITY_KINDS.map((kind) => [kind, []])),
|
|
34625
|
-
file_types: ids
|
|
34626
|
-
});
|
|
34627
|
-
},
|
|
35146
|
+
promoteFileTypes: (production, preview, ids, dryRun) => promoteOnly(production, preview, "file_types", ids, dryRun),
|
|
34628
35147
|
async listFiles(base, fileType) {
|
|
34629
35148
|
const rows = [];
|
|
34630
35149
|
for (let page = 0; page < MAX_LIST_PAGES; page++) {
|
|
@@ -34635,6 +35154,16 @@ function knowledgeBase(client) {
|
|
|
34635
35154
|
}
|
|
34636
35155
|
throw expected(`The listing of file type "${fileType}" on base "${base}" did not end after ${MAX_LIST_PAGES} pages.`);
|
|
34637
35156
|
},
|
|
35157
|
+
getToolset: (base, slug) => unlessMissing(() => request("GET", `/v1/${baseSeg(base)}/toolsets/${toolsetSeg(slug)}`)),
|
|
35158
|
+
async putToolset(base, spec) {
|
|
35159
|
+
const { id, ...body } = spec;
|
|
35160
|
+
await request("PUT", `/v1/${baseSeg(base)}/toolsets/${toolsetSeg(id)}`, { ...body, actions: [] });
|
|
35161
|
+
},
|
|
35162
|
+
promoteToolsets: (production, preview, slugs, dryRun) => promoteOnly(production, preview, "toolsets", slugs, dryRun),
|
|
35163
|
+
mintToolsetToken: async (base, slug, name) => rawTokenFrom(await request("POST", "/v1/tokens", { name, toolset_binding: { base_id: base, toolset_id: slug } })),
|
|
35164
|
+
// `requestRaw`: the page is read whole, `{ data: … }`, as WayAI's copy of its answer declares it
|
|
35165
|
+
// (`@wayai/core/contracts` `rekorFileSnapshotAnswerSchema`).
|
|
35166
|
+
snapshotPage: (preview, cursor) => pacer.run(() => client.requestRaw("POST", `/v1/${baseSeg(preview)}/files/snapshot`, cursor === void 0 ? {} : { cursor })),
|
|
34638
35167
|
async putFile(base, file, bytes) {
|
|
34639
35168
|
await pacer.run(
|
|
34640
35169
|
() => client.upload(
|
|
@@ -34647,7 +35176,70 @@ function knowledgeBase(client) {
|
|
|
34647
35176
|
}
|
|
34648
35177
|
};
|
|
34649
35178
|
}
|
|
34650
|
-
|
|
35179
|
+
function connectionsOf(rows) {
|
|
35180
|
+
return (rows ?? []).flatMap(
|
|
35181
|
+
(c) => c.connection_id ? [{ connection_id: c.connection_id, connection_display_name: c.connection_display_name, connector_id: c.connector_id, settings: c.settings }] : []
|
|
35182
|
+
);
|
|
35183
|
+
}
|
|
35184
|
+
function hubSetup(api, orgId, pacers = {
|
|
35185
|
+
setup: pacerFor(CUT_OVER_BUDGETS.setup, "setup"),
|
|
35186
|
+
automations: pacerFor(CUT_OVER_BUDGETS.automations, "automations")
|
|
35187
|
+
}) {
|
|
35188
|
+
const run = (send) => pacers.setup.run(send);
|
|
35189
|
+
const auto = (send) => pacers.automations.run(send);
|
|
35190
|
+
return {
|
|
35191
|
+
listHubs: async () => hubsOf(await run(() => api.getUserSchema()), orgId),
|
|
35192
|
+
hubDiff: async (hubId) => (await run(() => api.getHubDiff(hubId))).data,
|
|
35193
|
+
// The rows are the hub's `resource` rows whole, the move's mark (`moved_file_type`) among them.
|
|
35194
|
+
listResources: async (hubId) => (await run(() => api.listHubResources(hubId))).data ?? [],
|
|
35195
|
+
listAgents: async (hubId) => (await run(() => api.listAgents(hubId))).data ?? [],
|
|
35196
|
+
listAgentLinks: async (hubId, agentId) => (await run(() => api.listAgentResources(hubId, agentId))).resources ?? [],
|
|
35197
|
+
listAgentTools: async (hubId, agentId) => {
|
|
35198
|
+
const tools = await run(() => api.listAgentTools(hubId, agentId));
|
|
35199
|
+
return { native: tools.tools ?? [], connection: (tools.mcp_tools ?? []).map((t) => ({ ...t, tool_id: t.tool_id ?? null })) };
|
|
35200
|
+
},
|
|
35201
|
+
listConnections: async (hubId) => connectionsOf((await run(() => api.listConnections(hubId))).connections),
|
|
35202
|
+
listAutomations: async (hubId) => (await auto(() => api.listAutomations(hubId))).automations ?? [],
|
|
35203
|
+
// The answer lists the hub's connections, the new one among them.
|
|
35204
|
+
addToolsetConnection: async (hubId, input) => connectionsOf(
|
|
35205
|
+
(await run(
|
|
35206
|
+
() => api.addConnection({
|
|
35207
|
+
hub_id: hubId,
|
|
35208
|
+
connector_id: input.connectorId,
|
|
35209
|
+
connection_display_name: input.name,
|
|
35210
|
+
settings: { [TOOLSET_SLUG_SETTING]: input.toolset },
|
|
35211
|
+
access_token: input.token
|
|
35212
|
+
})
|
|
35213
|
+
)).connections
|
|
35214
|
+
),
|
|
35215
|
+
async syncConnection(hubId, connectionId) {
|
|
35216
|
+
const answer = await run(() => api.syncMcp(hubId, connectionId));
|
|
35217
|
+
if (!answer.success) {
|
|
35218
|
+
throw expected(`Syncing connection ${connectionId}'s tools failed: ${sanitizeTerminalText(answer.message ?? "no reason given")}`);
|
|
35219
|
+
}
|
|
35220
|
+
},
|
|
35221
|
+
async addNativeTool(hubId, agentId, toolNativeId) {
|
|
35222
|
+
await run(() => api.addNativeTool(hubId, agentId, toolNativeId));
|
|
35223
|
+
},
|
|
35224
|
+
async addConnectionTool(hubId, agentId, mcpToolId) {
|
|
35225
|
+
await run(() => api.addMcpTool(hubId, agentId, mcpToolId));
|
|
35226
|
+
},
|
|
35227
|
+
async enableTool(hubId, agentId, toolId) {
|
|
35228
|
+
await run(() => api.enableTool(hubId, agentId, toolId));
|
|
35229
|
+
},
|
|
35230
|
+
async updateAgentText(hubId, agentId, patch) {
|
|
35231
|
+
await run(() => api.updateAgent(hubId, agentId, patch));
|
|
35232
|
+
},
|
|
35233
|
+
async updateAutomationAction(hubId, automationId, action) {
|
|
35234
|
+
await auto(() => api.updateAutomation(hubId, automationId, { action }));
|
|
35235
|
+
},
|
|
35236
|
+
async disableLink(hubId, agentId, resourceId) {
|
|
35237
|
+
await run(() => api.updateAgentResource({ hub_id: hubId, agent_id: agentId, resource_id: resourceId, enabled: false }));
|
|
35238
|
+
},
|
|
35239
|
+
recordMove: async (body) => (await run(() => api.recordKnowledgeMove(body))).data.recorded
|
|
35240
|
+
};
|
|
35241
|
+
}
|
|
35242
|
+
var LIST_PAGE, MAX_LIST_PAGES, MIGRATION_LABEL, READER_BUDGETS, CUT_OVER_BUDGETS;
|
|
34651
35243
|
var init_remote = __esm({
|
|
34652
35244
|
"src/knowledge/remote.ts"() {
|
|
34653
35245
|
"use strict";
|
|
@@ -34660,10 +35252,559 @@ var init_remote = __esm({
|
|
|
34660
35252
|
init_types2();
|
|
34661
35253
|
init_headers();
|
|
34662
35254
|
init_helpers();
|
|
35255
|
+
init_terminal_output();
|
|
35256
|
+
init_tokens();
|
|
35257
|
+
init_rekor_toolset();
|
|
34663
35258
|
LIST_PAGE = 1e3;
|
|
34664
35259
|
MAX_LIST_PAGES = 1e3;
|
|
34665
35260
|
MIGRATION_LABEL = "wayai knowledge migrate";
|
|
34666
35261
|
READER_BUDGETS = { setup: "/api/setup/organization-resources", sign: "/api/files/sign" };
|
|
35262
|
+
CUT_OVER_BUDGETS = { setup: "/api/setup/agents", automations: "/api/automations" };
|
|
35263
|
+
}
|
|
35264
|
+
});
|
|
35265
|
+
|
|
35266
|
+
// src/knowledge/refresh-preview.ts
|
|
35267
|
+
async function refreshPreviewKnowledge(kb, opts, io) {
|
|
35268
|
+
const { production, preview } = await resolveBases(kb, opts.base, opts.orgId);
|
|
35269
|
+
const question = `Make the files of preview "${sanitizeTerminalText(preview)}" equal production "${sanitizeTerminalText(production)}"'s? Files the preview changed are replaced, and files only it holds are removed.`;
|
|
35270
|
+
if (!opts.yes && !await io.confirm(question)) {
|
|
35271
|
+
io.log("Cancelled.");
|
|
35272
|
+
return null;
|
|
35273
|
+
}
|
|
35274
|
+
io.log(`Copying the files of production base "${sanitizeTerminalText(production)}" into its preview "${sanitizeTerminalText(preview)}".`);
|
|
35275
|
+
const run = await runFileSnapshot({
|
|
35276
|
+
sendPage: (cursor) => kb.snapshotPage(preview, cursor),
|
|
35277
|
+
// The data product refuses a page whose preview file changed while it was being copied, and
|
|
35278
|
+
// asks for the same cursor again.
|
|
35279
|
+
isConflict: (error) => error instanceof ApiError && error.status === FILE_SNAPSHOT_CONFLICT_STATUS,
|
|
35280
|
+
onPage: (page, n) => io.log(` Page ${n}: ${page.written} written, ${page.removed} removed, ${page.unchanged} unchanged.`)
|
|
35281
|
+
});
|
|
35282
|
+
io.log(
|
|
35283
|
+
!fileSnapshotChanged(run) ? `Nothing changed in ${run.pages} page(s): the preview's ${run.unchanged} file(s) already equal production's.` : `Done in ${run.pages} page(s): ${run.written} written, ${run.removed} removed, ${run.unchanged} unchanged. The preview's files now equal production's.`
|
|
35284
|
+
);
|
|
35285
|
+
return run;
|
|
35286
|
+
}
|
|
35287
|
+
var init_refresh_preview = __esm({
|
|
35288
|
+
"src/knowledge/refresh-preview.ts"() {
|
|
35289
|
+
"use strict";
|
|
35290
|
+
init_contracts();
|
|
35291
|
+
init_api_client();
|
|
35292
|
+
init_terminal_output();
|
|
35293
|
+
init_migrate2();
|
|
35294
|
+
}
|
|
35295
|
+
});
|
|
35296
|
+
|
|
35297
|
+
// ../../packages/core/dist/catalog/index.js
|
|
35298
|
+
function getConnectors(filter) {
|
|
35299
|
+
if (!filter?.connector_type && !filter?.service_name) return CONNECTORS;
|
|
35300
|
+
return CONNECTORS.filter(
|
|
35301
|
+
(c) => (!filter.connector_type || c.connector_type === filter.connector_type) && (!filter.service_name || c.service_name === filter.service_name)
|
|
35302
|
+
);
|
|
35303
|
+
}
|
|
35304
|
+
function getConnectorById(id) {
|
|
35305
|
+
return connectorById.get(id) ?? null;
|
|
35306
|
+
}
|
|
35307
|
+
function isRekorToolsetConnector(connector) {
|
|
35308
|
+
return connector?.service_name === REKOR_TOOLSET_SERVICE_NAME;
|
|
35309
|
+
}
|
|
35310
|
+
function isRekorToolsetConnectorId(connectorId) {
|
|
35311
|
+
return typeof connectorId === "string" && isRekorToolsetConnector(connectorById.get(connectorId));
|
|
35312
|
+
}
|
|
35313
|
+
var COUNTER_KEYS, STT_PRICING, CONTAINER_PRICING, connectorById, nativeToolById;
|
|
35314
|
+
var init_catalog = __esm({
|
|
35315
|
+
"../../packages/core/dist/catalog/index.js"() {
|
|
35316
|
+
"use strict";
|
|
35317
|
+
init_chunk_4MXPUC6I();
|
|
35318
|
+
init_chunk_NRJINQVD();
|
|
35319
|
+
init_chunk_CWUV6NL3();
|
|
35320
|
+
init_chunk_MIWZOSPI();
|
|
35321
|
+
init_chunk_DHPUPI4Y();
|
|
35322
|
+
COUNTER_KEYS = Object.keys({
|
|
35323
|
+
input_tokens: true,
|
|
35324
|
+
output_tokens: true,
|
|
35325
|
+
cache_read_input_tokens: true,
|
|
35326
|
+
cache_creation_input_tokens: true,
|
|
35327
|
+
cache_creation_1h_input_tokens: true
|
|
35328
|
+
});
|
|
35329
|
+
STT_PRICING = {
|
|
35330
|
+
"Openai/whisper-1": { per_second: 6e-3 / 60 },
|
|
35331
|
+
"Openai/gpt-4o-mini-transcribe": { per_second: 3e-3 / 60 },
|
|
35332
|
+
"Groq/whisper-large-v3-turbo": { per_second: 0.04 / 3600 },
|
|
35333
|
+
"ElevenLabs/scribe_v2": { per_second: 0.22 / 3600 }
|
|
35334
|
+
};
|
|
35335
|
+
CONTAINER_PRICING = {
|
|
35336
|
+
anthropic_per_request_usd: 0.05 * 10 / 3600,
|
|
35337
|
+
// $0.05/hr × ~10s/request ≈ $0.000139
|
|
35338
|
+
openai_per_session_usd: 0.03
|
|
35339
|
+
// 1GB tier, 20-min session
|
|
35340
|
+
};
|
|
35341
|
+
connectorById = new Map(CONNECTORS.map((c) => [c.connector_id, c]));
|
|
35342
|
+
nativeToolById = new Map(NATIVE_TOOLS.map((t) => [t.tool_native_id, t]));
|
|
35343
|
+
}
|
|
35344
|
+
});
|
|
35345
|
+
|
|
35346
|
+
// src/knowledge/cut-over.ts
|
|
35347
|
+
import { createHash as createHash6 } from "crypto";
|
|
35348
|
+
import { isDeepStrictEqual as isDeepStrictEqual4 } from "util";
|
|
35349
|
+
function toolsetSlug(productionHubId, fileTypes) {
|
|
35350
|
+
const hash = createHash6("sha256").update([productionHubId, ...fileTypes].join("\n")).digest("hex").slice(0, 12);
|
|
35351
|
+
return `${CUT_OVER_TOOLSET_PREFIX}${hash}`;
|
|
35352
|
+
}
|
|
35353
|
+
function toolsetEntry(fileType) {
|
|
35354
|
+
const base = toolBase(fileType);
|
|
35355
|
+
return { file_type: fileType, operations: [...FILE_OPERATIONS], ...base === fileType ? {} : { name: base } };
|
|
35356
|
+
}
|
|
35357
|
+
function toolsetToolName(op, fileType) {
|
|
35358
|
+
return toolsetFileToolName(toolsetEntry(fileType), op);
|
|
35359
|
+
}
|
|
35360
|
+
function rewriteCitations(text, fileTypeBySlug) {
|
|
35361
|
+
return text.replace(CITATION_RE, (whole, slash, slug) => {
|
|
35362
|
+
const fileType = fileTypeBySlug.get(slug.toLowerCase());
|
|
35363
|
+
return fileType ? `${slash}${DATA_NAMESPACE}/${fileType}` : whole;
|
|
35364
|
+
});
|
|
35365
|
+
}
|
|
35366
|
+
function knowledgePlaceholders(template, losing) {
|
|
35367
|
+
if (losing.length === 0 || !template.includes("{{")) return [];
|
|
35368
|
+
const scan = scanTemplateResources(template);
|
|
35369
|
+
const kinds = scanTemplateResources(template.replace(/\{\{(\s*)files(\s*\()/g, "{{$1resources$2")).legacy_kinds;
|
|
35370
|
+
const uses = [];
|
|
35371
|
+
if (kinds === "all" || kinds === "kb") uses.push("a {{files()}} catalog that lists knowledge");
|
|
35372
|
+
const names = new Set(losing.map((r) => r.name));
|
|
35373
|
+
for (const name of scan.resource_content_names) if (names.has(name)) uses.push(`{{resource_content(${name})}}`);
|
|
35374
|
+
const slugs = new Set(losing.map((r) => r.slug));
|
|
35375
|
+
for (const path42 of scan.file_content_paths) {
|
|
35376
|
+
const [namespace, slug] = cleanRelativePath(path42).split("/");
|
|
35377
|
+
if (namespace?.toLowerCase() === RESOURCE_NAMESPACE && slug && slugs.has(slug.toLowerCase())) {
|
|
35378
|
+
uses.push(`{{file_content(${path42})}}`);
|
|
35379
|
+
}
|
|
35380
|
+
}
|
|
35381
|
+
return uses;
|
|
35382
|
+
}
|
|
35383
|
+
function sameToolset(remote, spec) {
|
|
35384
|
+
if (!remote) return false;
|
|
35385
|
+
const files = (entries) => [...entries ?? []].map((e) => ({ file_type: e.file_type, operations: [...e.operations].sort(), name: e.name ?? null, path_prefix: e.path_prefix || null })).sort((a, b) => a.file_type.localeCompare(b.file_type));
|
|
35386
|
+
return remote.name === spec.name && (remote.description ?? "") === spec.description && (remote.actions ?? []).length === 0 && (remote.relationships ?? []).length === 0 && !remote.batch && !remote.sql_query && isDeepStrictEqual4(files(remote.files), files(spec.files));
|
|
35387
|
+
}
|
|
35388
|
+
function toolsetConnectorId() {
|
|
35389
|
+
const connector = getConnectors().find((c) => isRekorToolsetConnector(c));
|
|
35390
|
+
if (!connector) throw new Error("The Base Toolset connector is missing from the catalog");
|
|
35391
|
+
return connector.connector_id;
|
|
35392
|
+
}
|
|
35393
|
+
function cutOverToolsetOf(connection) {
|
|
35394
|
+
const toolset = connection.settings?.[TOOLSET_SLUG_SETTING];
|
|
35395
|
+
return isRekorToolsetConnectorId(connection.connector_id) && typeof toolset === "string" && CUT_OVER_TOOLSET_RE.test(toolset) ? toolset : null;
|
|
35396
|
+
}
|
|
35397
|
+
function wantedConnectionTools(fileTypes) {
|
|
35398
|
+
return fileTypes.flatMap((ft) => FILE_OPERATIONS.map((op) => toolsetToolName(op, ft)));
|
|
35399
|
+
}
|
|
35400
|
+
function toolWork(tools, wanted, connectionId) {
|
|
35401
|
+
const native = [];
|
|
35402
|
+
const unavailable = [];
|
|
35403
|
+
for (const name of MOUNT_TOOLS) {
|
|
35404
|
+
const row = tools.native.find((t) => t.tool_name === name);
|
|
35405
|
+
if (!row) unavailable.push(name);
|
|
35406
|
+
else if (row.available_action !== "Disable") native.push(row);
|
|
35407
|
+
}
|
|
35408
|
+
const connection = [];
|
|
35409
|
+
const uncatalogued = [];
|
|
35410
|
+
for (const name of wanted) {
|
|
35411
|
+
const row = connectionId ? tools.connection.find((t) => t.connection_id === connectionId && t.tool_name === name) : void 0;
|
|
35412
|
+
if (!row) uncatalogued.push(name);
|
|
35413
|
+
else if (row.available_action !== "Disable") connection.push(row);
|
|
35414
|
+
}
|
|
35415
|
+
return { native, connection, uncatalogued, unavailable };
|
|
35416
|
+
}
|
|
35417
|
+
function describeToolWork(work) {
|
|
35418
|
+
return [
|
|
35419
|
+
...work.native.map((t) => `${t.available_action === "Enable" ? "enable" : "add"} ${t.tool_name}`),
|
|
35420
|
+
...work.connection.map((t) => `${t.available_action === "Enable" ? "enable" : "add"} ${t.tool_name}`),
|
|
35421
|
+
...work.uncatalogued.map((name) => `add ${name}`)
|
|
35422
|
+
];
|
|
35423
|
+
}
|
|
35424
|
+
async function cutOverHub(hub, kb, opts, io) {
|
|
35425
|
+
const { production, preview } = await resolveBases(kb, opts.base, opts.orgId);
|
|
35426
|
+
const stops = [];
|
|
35427
|
+
const hubs = await hub.listHubs();
|
|
35428
|
+
const previewHub = hubs.find((h) => h.hub_id === opts.hubId);
|
|
35429
|
+
if (!previewHub) throw expected(`Hub ${safe2(opts.hubId)} not found in this organization, or you cannot read it.`);
|
|
35430
|
+
if (previewHub.hub_environment !== "preview") {
|
|
35431
|
+
throw expected(`Hub ${safe2(opts.hubId)} is a production hub. A hub is cut over on its preview; publishing carries it to production.`);
|
|
35432
|
+
}
|
|
35433
|
+
const productionHub2 = previewHub.production_hub_id ? hubs.find((h) => h.hub_id === previewHub.production_hub_id) : void 0;
|
|
35434
|
+
if (!productionHub2) {
|
|
35435
|
+
throw expected(`Hub ${safe2(opts.hubId)} has not been published, and its knowledge moved from its production hub. Publish it, then migrate its knowledge first.`);
|
|
35436
|
+
}
|
|
35437
|
+
const productionHubId = productionHub2.hub_id;
|
|
35438
|
+
const fileTypes = await kb.listFileTypes(production);
|
|
35439
|
+
const fileTypeBySource = /* @__PURE__ */ new Map();
|
|
35440
|
+
for (const ft of fileTypes) {
|
|
35441
|
+
const source = provenanceOf(ft.metadata_schema);
|
|
35442
|
+
if (source) fileTypeBySource.set(source, ft.id);
|
|
35443
|
+
}
|
|
35444
|
+
const resources = await hub.listResources(opts.hubId);
|
|
35445
|
+
const ownKnowledge = (rows) => rows.filter((r) => r.resource_type === "knowledge" && !r.org_resource_id);
|
|
35446
|
+
const productionOwn = ownKnowledge(await hub.listResources(productionHubId));
|
|
35447
|
+
const previewOwn = ownKnowledge(resources);
|
|
35448
|
+
const diff = await hub.hubDiff(opts.hubId);
|
|
35449
|
+
const marked = new Set(previewOwn.filter((r) => r.moved_file_type).map((r) => r.resource_name));
|
|
35450
|
+
const knowledgeNames = new Set([...previewOwn, ...productionOwn].map((r) => r.resource_name).filter((name) => !marked.has(name)));
|
|
35451
|
+
const changed = [
|
|
35452
|
+
...diff.resources.filter((e) => knowledgeNames.has(e.name)).map((e) => `resource "${e.name}" (${e.status})`),
|
|
35453
|
+
...(diff.resource_folders ?? []).filter((e) => e.type && knowledgeNames.has(e.type)).map((e) => `a folder of "${e.type}"`),
|
|
35454
|
+
...(diff.resource_files ?? []).filter((e) => e.type && knowledgeNames.has(e.type)).map((e) => `a file of "${e.type}"`)
|
|
35455
|
+
];
|
|
35456
|
+
if (changed.length > 0) {
|
|
35457
|
+
stops.push(
|
|
35458
|
+
`The preview's knowledge resources differ from production's, which moved (${[...new Set(changed)].join(", ")}): sync or discard those changes first, so its agents are cut over to what they read.`
|
|
35459
|
+
);
|
|
35460
|
+
}
|
|
35461
|
+
const fileTypeOf = /* @__PURE__ */ new Map();
|
|
35462
|
+
for (const resource of resources) {
|
|
35463
|
+
if (resource.resource_type !== "knowledge") continue;
|
|
35464
|
+
if (resource.org_resource_id) {
|
|
35465
|
+
const ft2 = fileTypeBySource.get(orgSourceKey(resource.org_resource_id));
|
|
35466
|
+
if (ft2) fileTypeOf.set(resource.resource_id, ft2);
|
|
35467
|
+
continue;
|
|
35468
|
+
}
|
|
35469
|
+
if (resource.moved_file_type) {
|
|
35470
|
+
fileTypeOf.set(resource.resource_id, resource.moved_file_type);
|
|
35471
|
+
continue;
|
|
35472
|
+
}
|
|
35473
|
+
const twins = productionOwn.filter((r) => r.resource_name === resource.resource_name);
|
|
35474
|
+
const previewTwins = previewOwn.filter((r) => r.resource_name === resource.resource_name);
|
|
35475
|
+
if (twins.length > 1 || previewTwins.length > 1) {
|
|
35476
|
+
stops.push(`Two knowledge resources are named "${resource.resource_name}": rename one (in the preview, then sync), so each pairs with its production copy.`);
|
|
35477
|
+
continue;
|
|
35478
|
+
}
|
|
35479
|
+
const ft = twins[0] ? fileTypeBySource.get(hubSourceKey(productionHubId, twins[0].resource_id)) : void 0;
|
|
35480
|
+
if (ft) fileTypeOf.set(resource.resource_id, ft);
|
|
35481
|
+
}
|
|
35482
|
+
const productionHubs = hubs.filter((h) => h.hub_environment === "production");
|
|
35483
|
+
for (const resource of previewOwn) {
|
|
35484
|
+
const ft = fileTypeOf.get(resource.resource_id);
|
|
35485
|
+
if (!ft || resource.moved_file_type) continue;
|
|
35486
|
+
const hubPart = hubPartOf(ft);
|
|
35487
|
+
const holders = productionHubs.filter((h) => slugifySkillName(h.hub_name) === hubPart);
|
|
35488
|
+
if (holders.length > 1) {
|
|
35489
|
+
stops.push(
|
|
35490
|
+
`File type "${ft}" names the hub slug "${hubPart}", which ${holders.length} production hubs share (${holders.map((h) => `"${h.hub_name}"`).join(", ")}): its move can't be recorded until all but one are renamed.`
|
|
35491
|
+
);
|
|
35492
|
+
}
|
|
35493
|
+
}
|
|
35494
|
+
const slugById = buildResourceSlugMap(resources);
|
|
35495
|
+
const fileTypeBySlug = /* @__PURE__ */ new Map();
|
|
35496
|
+
for (const [resourceId, ft] of fileTypeOf) {
|
|
35497
|
+
const slug = slugById.get(resourceId);
|
|
35498
|
+
if (slug) fileTypeBySlug.set(slug, ft);
|
|
35499
|
+
}
|
|
35500
|
+
const byId = new Map(resources.map((r) => [r.resource_id, r]));
|
|
35501
|
+
const connections = await hub.listConnections(opts.hubId);
|
|
35502
|
+
const previewToolsets = /* @__PURE__ */ new Map();
|
|
35503
|
+
const connectionBySlug = /* @__PURE__ */ new Map();
|
|
35504
|
+
for (const connection of connections) {
|
|
35505
|
+
const slug = cutOverToolsetOf(connection);
|
|
35506
|
+
if (!slug) continue;
|
|
35507
|
+
connectionBySlug.set(slug, connection);
|
|
35508
|
+
if (!previewToolsets.has(slug)) previewToolsets.set(slug, await kb.getToolset(preview, slug));
|
|
35509
|
+
}
|
|
35510
|
+
const toolsetTypes = (slug) => (previewToolsets.get(slug)?.files ?? []).map((e) => e.file_type);
|
|
35511
|
+
const slugByConnection = new Map([...connectionBySlug].map(([slug, c]) => [c.connection_id, slug]));
|
|
35512
|
+
const agents = await hub.listAgents(opts.hubId);
|
|
35513
|
+
const plans = [];
|
|
35514
|
+
for (const agent of agents) {
|
|
35515
|
+
const links2 = (await hub.listAgentLinks(opts.hubId, agent.agent_id)).filter((l) => l.resource_type === "knowledge");
|
|
35516
|
+
const enabled = links2.filter((l) => l.enabled);
|
|
35517
|
+
const tools = await hub.listAgentTools(opts.hubId, agent.agent_id);
|
|
35518
|
+
const set = /* @__PURE__ */ new Set();
|
|
35519
|
+
for (const link of enabled) {
|
|
35520
|
+
const ft = fileTypeOf.get(link.resource_id);
|
|
35521
|
+
const resource = byId.get(link.resource_id);
|
|
35522
|
+
if (!ft) {
|
|
35523
|
+
stops.push(
|
|
35524
|
+
`Agent "${agent.agent_name}" reads knowledge resource "${resource?.resource_name ?? link.resource_id}", which has not moved: move it first (\`wayai knowledge migrate --org ${opts.orgId} --base ${opts.base}${resource?.org_resource_id ? "" : ` --hub ${productionHubId}`}\`).`
|
|
35525
|
+
);
|
|
35526
|
+
continue;
|
|
35527
|
+
}
|
|
35528
|
+
set.add(ft);
|
|
35529
|
+
}
|
|
35530
|
+
for (const tool of tools.connection) {
|
|
35531
|
+
const slug = tool.connection_id ? slugByConnection.get(tool.connection_id) : void 0;
|
|
35532
|
+
if (slug && tool.available_action === "Disable") for (const ft of toolsetTypes(slug)) set.add(ft);
|
|
35533
|
+
}
|
|
35534
|
+
const runner = connections.find((c) => c.connection_id === agent.connection_id);
|
|
35535
|
+
if (enabled.length > 0 && runner && getConnectorById(runner.connector_id)?.is_harness === true) {
|
|
35536
|
+
stops.push(
|
|
35537
|
+
`Agent "${agent.agent_name}" runs on a hosted harness, which takes no Base Toolset's tools yet, so it would lose its knowledge: connect it to a toolset through an MCP Server connection, or detach its knowledge links, then run again.`
|
|
35538
|
+
);
|
|
35539
|
+
}
|
|
35540
|
+
const losing = enabled.flatMap((link) => {
|
|
35541
|
+
const resource = byId.get(link.resource_id);
|
|
35542
|
+
const slug = slugById.get(link.resource_id);
|
|
35543
|
+
return resource && slug ? [{ slug, name: resourceDisplayName(resource) }] : [];
|
|
35544
|
+
});
|
|
35545
|
+
for (const [field, template] of [
|
|
35546
|
+
["instructions", agent.instructions],
|
|
35547
|
+
["additional context", agent.additional_context_template]
|
|
35548
|
+
]) {
|
|
35549
|
+
for (const use of knowledgePlaceholders(template ?? "", losing)) {
|
|
35550
|
+
stops.push(
|
|
35551
|
+
`Agent "${agent.agent_name}" uses ${use} in its ${field}, which reads nothing once its knowledge moves (K19): move that text into its instructions or a skill, then run again.`
|
|
35552
|
+
);
|
|
35553
|
+
}
|
|
35554
|
+
}
|
|
35555
|
+
if (set.size > 0 || enabled.length > 0) plans.push({ agent, fileTypes: [...set].sort(), linksToDisable: enabled, tools });
|
|
35556
|
+
}
|
|
35557
|
+
const groups = /* @__PURE__ */ new Map();
|
|
35558
|
+
for (const plan of plans) {
|
|
35559
|
+
if (plan.fileTypes.length === 0) continue;
|
|
35560
|
+
const slug = toolsetSlug(productionHubId, plan.fileTypes);
|
|
35561
|
+
const group = groups.get(slug);
|
|
35562
|
+
if (group) {
|
|
35563
|
+
group.agents.push(plan.agent);
|
|
35564
|
+
plan.group = group;
|
|
35565
|
+
continue;
|
|
35566
|
+
}
|
|
35567
|
+
plan.group = {
|
|
35568
|
+
slug,
|
|
35569
|
+
fileTypes: plan.fileTypes,
|
|
35570
|
+
agents: [plan.agent],
|
|
35571
|
+
connection: connectionBySlug.get(slug),
|
|
35572
|
+
created: !connectionBySlug.has(slug),
|
|
35573
|
+
spec: {
|
|
35574
|
+
id: slug,
|
|
35575
|
+
name: `Knowledge of ${previewHub.hub_name}`.slice(0, 200),
|
|
35576
|
+
description: `Read-only knowledge for agents of hub ${productionHubId}, written by wayai knowledge cut-over: list and get on ${plan.fileTypes.join(", ")}.`,
|
|
35577
|
+
files: plan.fileTypes.map(toolsetEntry)
|
|
35578
|
+
}
|
|
35579
|
+
};
|
|
35580
|
+
groups.set(slug, plan.group);
|
|
35581
|
+
}
|
|
35582
|
+
const plannedTypes = [...new Set([...groups.values()].flatMap((g) => g.fileTypes))].sort();
|
|
35583
|
+
const missing = [];
|
|
35584
|
+
for (const ft of plannedTypes) {
|
|
35585
|
+
const [inProduction, inPreview] = await Promise.all([kb.listFiles(production, ft), filesOrNone(kb, preview, ft)]);
|
|
35586
|
+
const there = new Set(inPreview.map((f) => f.path));
|
|
35587
|
+
for (const file of inProduction) if (!there.has(file.path)) missing.push(`${ft}/${file.path}`);
|
|
35588
|
+
}
|
|
35589
|
+
if (missing.length > 0) {
|
|
35590
|
+
stops.push(
|
|
35591
|
+
`The preview base "${preview}" lacks ${missing.length} file(s) production holds (${missing.slice(0, 5).join(", ")}${missing.length > 5 ? ", \u2026" : ""}), so the preview's agents would read none of them: copy them in with \`${refreshPreviewCommand(opts.orgId, opts.base)}\`, then run again.`
|
|
35592
|
+
);
|
|
35593
|
+
}
|
|
35594
|
+
const toolsetsToWrite = [];
|
|
35595
|
+
const toolsetsToPromote = [];
|
|
35596
|
+
for (const group of groups.values()) {
|
|
35597
|
+
const [inPreview, inProduction] = await Promise.all([
|
|
35598
|
+
previewToolsets.has(group.slug) ? previewToolsets.get(group.slug) : kb.getToolset(preview, group.slug),
|
|
35599
|
+
kb.getToolset(production, group.slug)
|
|
35600
|
+
]);
|
|
35601
|
+
if (!sameToolset(inPreview, group.spec)) toolsetsToWrite.push(group.spec);
|
|
35602
|
+
if (!sameToolset(inProduction, group.spec)) toolsetsToPromote.push(group.spec);
|
|
35603
|
+
}
|
|
35604
|
+
const connectionsToCreate = [...groups.values()].filter((g) => g.created);
|
|
35605
|
+
const toolLines = [];
|
|
35606
|
+
let toolWrites = 0;
|
|
35607
|
+
for (const plan of plans) {
|
|
35608
|
+
const group = plan.group;
|
|
35609
|
+
if (!group) continue;
|
|
35610
|
+
const work = toolWork(plan.tools, wantedConnectionTools(group.fileTypes), group.connection?.connection_id);
|
|
35611
|
+
for (const name of work.unavailable) stops.push(`Agent "${plan.agent.agent_name}" can't be given ${name}: the hub offers it no such tool.`);
|
|
35612
|
+
const lines = describeToolWork(work);
|
|
35613
|
+
toolWrites += lines.length;
|
|
35614
|
+
if (lines.length > 0) toolLines.push(` ${safe2(plan.agent.agent_name)}: ${lines.map(safe2).join(", ")} (${group.slug})`);
|
|
35615
|
+
}
|
|
35616
|
+
const agentRewrites = [];
|
|
35617
|
+
for (const agent of agents) {
|
|
35618
|
+
const patch = {};
|
|
35619
|
+
const instructions = rewriteCitations(agent.instructions ?? "", fileTypeBySlug);
|
|
35620
|
+
if (instructions !== (agent.instructions ?? "")) patch.instructions = instructions;
|
|
35621
|
+
const context = rewriteCitations(agent.additional_context_template ?? "", fileTypeBySlug);
|
|
35622
|
+
if (context !== (agent.additional_context_template ?? "")) patch.additional_context_template = context;
|
|
35623
|
+
if (Object.keys(patch).length > 0) agentRewrites.push({ agent, patch });
|
|
35624
|
+
}
|
|
35625
|
+
const automationRewrites = [];
|
|
35626
|
+
for (const automation of await hub.listAutomations(opts.hubId)) {
|
|
35627
|
+
const text = automation.action.type === "run_agent" ? automation.action.instructions : null;
|
|
35628
|
+
if (!text) continue;
|
|
35629
|
+
const rewritten = rewriteCitations(text, fileTypeBySlug);
|
|
35630
|
+
if (rewritten !== text) automationRewrites.push({ automation, action: { ...automation.action, instructions: rewritten } });
|
|
35631
|
+
}
|
|
35632
|
+
const moves = [];
|
|
35633
|
+
for (const resource of previewOwn) {
|
|
35634
|
+
const ft = fileTypeOf.get(resource.resource_id);
|
|
35635
|
+
if (!ft || resource.moved_file_type) continue;
|
|
35636
|
+
moves.push({
|
|
35637
|
+
body: { scope: "hub", hub_id: opts.hubId, resource_id: resource.resource_id, file_type: ft, base_id: production },
|
|
35638
|
+
label: `hub resource "${resource.resource_name}" \u2192 ${ft}`
|
|
35639
|
+
});
|
|
35640
|
+
}
|
|
35641
|
+
const orgRecorded = /* @__PURE__ */ new Set();
|
|
35642
|
+
for (const plan of plans) {
|
|
35643
|
+
for (const link of plan.linksToDisable) {
|
|
35644
|
+
const resource = byId.get(link.resource_id);
|
|
35645
|
+
const ft = fileTypeOf.get(link.resource_id);
|
|
35646
|
+
if (!resource?.org_resource_id || !ft || orgRecorded.has(resource.org_resource_id)) continue;
|
|
35647
|
+
orgRecorded.add(resource.org_resource_id);
|
|
35648
|
+
moves.push({
|
|
35649
|
+
body: { scope: "organization", organization_id: opts.orgId, org_resource_id: resource.org_resource_id, file_type: ft, base_id: production },
|
|
35650
|
+
label: `organization resource "${resource.resource_name}" \u2192 ${ft}`
|
|
35651
|
+
});
|
|
35652
|
+
}
|
|
35653
|
+
}
|
|
35654
|
+
const links = plans.flatMap((plan) => plan.linksToDisable.map((link) => ({ plan, link })));
|
|
35655
|
+
const result = {
|
|
35656
|
+
toolsets: [...groups.values()].map((g) => ({ slug: g.slug, fileTypes: g.fileTypes, agents: g.agents.map((a) => a.agent_name) })),
|
|
35657
|
+
toolsetsWritten: toolsetsToWrite.map((s) => s.id),
|
|
35658
|
+
toolsetsPromoted: toolsetsToPromote.map((s) => s.id),
|
|
35659
|
+
connectionsCreated: connectionsToCreate.map((g) => g.slug),
|
|
35660
|
+
toolsAssigned: toolLines,
|
|
35661
|
+
agentsRewritten: agentRewrites.map((r) => r.agent.agent_name),
|
|
35662
|
+
automationsRewritten: automationRewrites.map((r) => r.automation.name),
|
|
35663
|
+
movesRecorded: moves.map((m) => m.label),
|
|
35664
|
+
linksDisabled: links.map(({ plan, link }) => `${plan.agent.agent_name}: ${byId.get(link.resource_id)?.resource_name ?? link.resource_id}`),
|
|
35665
|
+
written: false
|
|
35666
|
+
};
|
|
35667
|
+
io.log(`Cut-over of hub "${safe2(previewHub.hub_name)}" (${opts.hubId}) to base "${safe2(production)}" (toolsets written in "${safe2(preview)}")`);
|
|
35668
|
+
io.log("\nToolsets (read-only: list and get):");
|
|
35669
|
+
if (groups.size === 0) io.log(" (none: no agent reads moved knowledge)");
|
|
35670
|
+
for (const group of groups.values()) {
|
|
35671
|
+
const mark = toolsetsToPromote.includes(group.spec) || toolsetsToWrite.includes(group.spec) ? "+" : "=";
|
|
35672
|
+
io.log(` ${mark} ${group.slug}: ${group.fileTypes.map(safe2).join(", ")} \u2014 ${group.agents.map((a) => safe2(a.agent_name)).join(", ")}`);
|
|
35673
|
+
}
|
|
35674
|
+
io.log("\nConnections:");
|
|
35675
|
+
for (const group of groups.values()) {
|
|
35676
|
+
io.log(` ${group.connection ? "=" : "+"} Base Toolset "${group.slug}"${group.connection ? "" : ", with a new token bound to it on the preview base"}`);
|
|
35677
|
+
}
|
|
35678
|
+
io.log("\nAgent tools:");
|
|
35679
|
+
if (toolLines.length === 0) io.log(" (nothing to assign)");
|
|
35680
|
+
for (const line of toolLines) io.log(line);
|
|
35681
|
+
io.log("\nPaths rewritten to data/ (K13):");
|
|
35682
|
+
if (agentRewrites.length + automationRewrites.length === 0) io.log(" (none)");
|
|
35683
|
+
for (const { agent, patch } of agentRewrites) io.log(` agent "${safe2(agent.agent_name)}": ${Object.keys(patch).join(", ")}`);
|
|
35684
|
+
for (const { automation } of automationRewrites) io.log(` automation "${safe2(automation.name)}": instructions`);
|
|
35685
|
+
io.log("\nMoves recorded:");
|
|
35686
|
+
if (moves.length === 0) io.log(" (none)");
|
|
35687
|
+
for (const move of moves) io.log(` + ${safe2(move.label)}`);
|
|
35688
|
+
io.log("\nKnowledge links disabled:");
|
|
35689
|
+
if (links.length === 0) io.log(" (none)");
|
|
35690
|
+
for (const line of result.linksDisabled) io.log(` - ${safe2(line)}`);
|
|
35691
|
+
if (stops.length > 0) {
|
|
35692
|
+
io.log("\nStopped:");
|
|
35693
|
+
for (const stop of stops) io.log(` ${safe2(stop)}`);
|
|
35694
|
+
throw expected(`The cut-over stops on ${stops.length} reason(s) above; nothing was written.`);
|
|
35695
|
+
}
|
|
35696
|
+
const writes = toolsetsToWrite.length + toolsetsToPromote.length + connectionsToCreate.length + toolWrites + agentRewrites.length + automationRewrites.length + moves.length + links.length;
|
|
35697
|
+
const printProduction = () => printProductionSteps(io, opts, production, productionHubId, [...groups.values()]);
|
|
35698
|
+
if (opts.dryRun) {
|
|
35699
|
+
io.log(`
|
|
35700
|
+
Dry run: nothing written (${writes === 0 ? "nothing to write" : `${writes} write(s) planned`}).`);
|
|
35701
|
+
printProduction();
|
|
35702
|
+
return result;
|
|
35703
|
+
}
|
|
35704
|
+
if (writes === 0) {
|
|
35705
|
+
io.log("\nNothing to write: this hub is cut over.");
|
|
35706
|
+
printProduction();
|
|
35707
|
+
return result;
|
|
35708
|
+
}
|
|
35709
|
+
if (!opts.yes && !await io.confirm(`Make these ${writes} change(s) to the preview hub and its knowledge base?`)) {
|
|
35710
|
+
io.log("Cancelled.");
|
|
35711
|
+
return result;
|
|
35712
|
+
}
|
|
35713
|
+
for (const move of moves) await hub.recordMove(move.body);
|
|
35714
|
+
for (const spec of toolsetsToWrite) await kb.putToolset(preview, spec);
|
|
35715
|
+
if (toolsetsToPromote.length > 0) {
|
|
35716
|
+
const slugs = toolsetsToPromote.map((s) => s.id);
|
|
35717
|
+
const problems2 = promotionProblems(await kb.promoteToolsets(production, preview, slugs, true), new Set(slugs), "toolsets");
|
|
35718
|
+
if (problems2.length > 0) {
|
|
35719
|
+
throw expected(
|
|
35720
|
+
`The promotion into "${safe2(production)}" would not apply as planned (${problems2.map(safe2).join("; ")}). The toolsets are written in "${safe2(preview)}"; nothing reached production, and the hub's agents are unchanged.`
|
|
35721
|
+
);
|
|
35722
|
+
}
|
|
35723
|
+
await kb.promoteToolsets(production, preview, slugs, false);
|
|
35724
|
+
}
|
|
35725
|
+
if (connectionsToCreate.length > 0) {
|
|
35726
|
+
const connectorId = toolsetConnectorId();
|
|
35727
|
+
for (const group of connectionsToCreate) {
|
|
35728
|
+
const token = await kb.mintToolsetToken(preview, group.slug, `${group.slug} (hub ${opts.hubId})`);
|
|
35729
|
+
const after = await hub.addToolsetConnection(opts.hubId, { connectorId, name: group.slug, toolset: group.slug, token });
|
|
35730
|
+
group.connection = after.find((c) => cutOverToolsetOf(c) === group.slug);
|
|
35731
|
+
if (!group.connection) throw new Error(`The Base Toolset connection "${group.slug}" was not found after it was created.`);
|
|
35732
|
+
}
|
|
35733
|
+
}
|
|
35734
|
+
const synced2 = /* @__PURE__ */ new Set();
|
|
35735
|
+
for (const plan of plans) {
|
|
35736
|
+
const group = plan.group;
|
|
35737
|
+
if (!group?.connection) continue;
|
|
35738
|
+
const connectionId = group.connection.connection_id;
|
|
35739
|
+
const wanted = wantedConnectionTools(group.fileTypes);
|
|
35740
|
+
const stale = group.created || synced2.has(connectionId);
|
|
35741
|
+
let tools = stale ? await hub.listAgentTools(opts.hubId, plan.agent.agent_id) : plan.tools;
|
|
35742
|
+
let work = toolWork(tools, wanted, connectionId);
|
|
35743
|
+
if (work.uncatalogued.length > 0 && !synced2.has(connectionId)) {
|
|
35744
|
+
await hub.syncConnection(opts.hubId, connectionId);
|
|
35745
|
+
synced2.add(connectionId);
|
|
35746
|
+
tools = await hub.listAgentTools(opts.hubId, plan.agent.agent_id);
|
|
35747
|
+
work = toolWork(tools, wanted, connectionId);
|
|
35748
|
+
}
|
|
35749
|
+
if (work.uncatalogued.length > 0) {
|
|
35750
|
+
throw new Error(
|
|
35751
|
+
`Connection "${group.slug}" catalogs no ${safe2(work.uncatalogued.join(", "))} after a sync, so agent "${safe2(plan.agent.agent_name)}" can't be given them.`
|
|
35752
|
+
);
|
|
35753
|
+
}
|
|
35754
|
+
for (const tool of [...work.native, ...work.connection]) {
|
|
35755
|
+
if (tool.available_action === "Enable" && tool.tool_id) await hub.enableTool(opts.hubId, plan.agent.agent_id, tool.tool_id);
|
|
35756
|
+
else if (tool.tool_native_id) await hub.addNativeTool(opts.hubId, plan.agent.agent_id, tool.tool_native_id);
|
|
35757
|
+
else if (tool.mcp_tool_id) await hub.addConnectionTool(opts.hubId, plan.agent.agent_id, tool.mcp_tool_id);
|
|
35758
|
+
}
|
|
35759
|
+
}
|
|
35760
|
+
for (const { agent, patch } of agentRewrites) await hub.updateAgentText(opts.hubId, agent.agent_id, patch);
|
|
35761
|
+
for (const { automation, action } of automationRewrites) await hub.updateAutomationAction(opts.hubId, automation.automation_id, action);
|
|
35762
|
+
for (const { plan, link } of links) await hub.disableLink(opts.hubId, plan.agent.agent_id, link.resource_id);
|
|
35763
|
+
io.log(`
|
|
35764
|
+
Done: the preview hub's agents read their knowledge from "${safe2(preview)}" through ${groups.size} toolset(s).`);
|
|
35765
|
+
printProduction();
|
|
35766
|
+
return { ...result, written: true };
|
|
35767
|
+
}
|
|
35768
|
+
async function filesOrNone(kb, base, fileType) {
|
|
35769
|
+
try {
|
|
35770
|
+
return await kb.listFiles(base, fileType);
|
|
35771
|
+
} catch (err) {
|
|
35772
|
+
if (err instanceof ApiError && err.status === 404) return [];
|
|
35773
|
+
throw err;
|
|
35774
|
+
}
|
|
35775
|
+
}
|
|
35776
|
+
function printProductionSteps(io, opts, production, productionHubId, groups) {
|
|
35777
|
+
if (groups.length === 0) return;
|
|
35778
|
+
io.log("\nProduction (not run by this command), once the preview passes its evals:");
|
|
35779
|
+
io.log(` 1. Publish the preview: wayai publish --hub ${opts.hubId}`);
|
|
35780
|
+
io.log(" 2. For each toolset, mint a token bound to it on the production base, and save it on production's connection:");
|
|
35781
|
+
for (const group of groups) {
|
|
35782
|
+
io.log(
|
|
35783
|
+
` wayai bases tokens create-for-toolset ${group.slug} --base ${safe2(production)} --token-only | wayai set-connection-credential --hub ${productionHubId} --connection "${group.slug}" --field access_token --stdin`
|
|
35784
|
+
);
|
|
35785
|
+
}
|
|
35786
|
+
}
|
|
35787
|
+
var CUT_OVER_TOOLSET_PREFIX, CUT_OVER_TOOLSET_RE, FILE_OPERATIONS, MOUNT_TOOLS, DATA_NAMESPACE, safe2, CITATION_RE;
|
|
35788
|
+
var init_cut_over = __esm({
|
|
35789
|
+
"src/knowledge/cut-over.ts"() {
|
|
35790
|
+
"use strict";
|
|
35791
|
+
init_dist();
|
|
35792
|
+
init_catalog();
|
|
35793
|
+
init_rekor_toolset();
|
|
35794
|
+
init_resource_paths();
|
|
35795
|
+
init_expected();
|
|
35796
|
+
init_terminal_output();
|
|
35797
|
+
init_api_client();
|
|
35798
|
+
init_migrate2();
|
|
35799
|
+
init_plan();
|
|
35800
|
+
init_source();
|
|
35801
|
+
CUT_OVER_TOOLSET_PREFIX = "knowledge-";
|
|
35802
|
+
CUT_OVER_TOOLSET_RE = /^knowledge-[0-9a-f]{12}$/;
|
|
35803
|
+
FILE_OPERATIONS = ["list", "get"];
|
|
35804
|
+
MOUNT_TOOLS = ["list_files", "read_file"];
|
|
35805
|
+
DATA_NAMESPACE = "data";
|
|
35806
|
+
safe2 = (text) => sanitizeTerminalText(text);
|
|
35807
|
+
CITATION_RE = /(?<![\w./-])(\/?)resources\/([a-z0-9]+(?:-[a-z0-9]+)*)(?![a-z0-9_]|-[a-z0-9]|\.\w)/gi;
|
|
34667
35808
|
}
|
|
34668
35809
|
});
|
|
34669
35810
|
|
|
@@ -34688,15 +35829,27 @@ function readArgs(args2) {
|
|
|
34688
35829
|
function parseKnowledgeArgs(args2) {
|
|
34689
35830
|
const { values, positionals } = readArgs(args2);
|
|
34690
35831
|
const [verb, ...extra] = positionals;
|
|
34691
|
-
if (verb !== "migrate"
|
|
35832
|
+
if (verb !== "migrate" && verb !== "refresh-preview" && verb !== "cut-over") {
|
|
35833
|
+
throw usageError3(verb === void 0 ? "No subcommand given." : `Unknown subcommand ${JSON.stringify(verb)}.`);
|
|
35834
|
+
}
|
|
34692
35835
|
if (extra.length > 0) throw usageError3(`Unexpected argument ${JSON.stringify(extra[0])}.`);
|
|
34693
35836
|
if (!values.org) throw usageError3("--org <org_id> is required.");
|
|
34694
35837
|
if (!UUID_RE3.test(values.org)) throw usageError3(`Invalid --org ${JSON.stringify(values.org)}: expected an organization id (a UUID).`);
|
|
34695
35838
|
if (!values.base) throw usageError3("--base <knowledge base> is required.");
|
|
35839
|
+
if (verb === "refresh-preview") {
|
|
35840
|
+
if (values.hub !== void 0) throw usageError3("--hub is migrate's and cut-over's: a refresh copies the whole knowledge base.");
|
|
35841
|
+
if (values["dry-run"] !== void 0) throw usageError3("--dry-run is migrate's and cut-over's: a refresh has none.");
|
|
35842
|
+
return { verb, orgId: values.org, base: values.base, yes: values.yes === true };
|
|
35843
|
+
}
|
|
34696
35844
|
if (values.hub !== void 0 && !UUID_RE3.test(values.hub)) {
|
|
34697
35845
|
throw usageError3(`Invalid --hub ${JSON.stringify(values.hub)}: expected a hub id (a UUID).`);
|
|
34698
35846
|
}
|
|
35847
|
+
if (verb === "cut-over") {
|
|
35848
|
+
if (values.hub === void 0) throw usageError3("--hub <preview hub_id> is required: a cut-over is one preview hub's.");
|
|
35849
|
+
return { verb, orgId: values.org, base: values.base, hubId: values.hub, dryRun: values["dry-run"] === true, yes: values.yes === true };
|
|
35850
|
+
}
|
|
34699
35851
|
return {
|
|
35852
|
+
verb,
|
|
34700
35853
|
orgId: values.org,
|
|
34701
35854
|
base: values.base,
|
|
34702
35855
|
hubId: values.hub,
|
|
@@ -34710,16 +35863,24 @@ async function knowledgeCommand(args2) {
|
|
|
34710
35863
|
return;
|
|
34711
35864
|
}
|
|
34712
35865
|
const options = parseKnowledgeArgs(args2);
|
|
35866
|
+
if (options.verb === "refresh-preview") {
|
|
35867
|
+
if (!options.yes && !isInteractive()) throw usageError3("It asks before it copies; pass -y when not running in a terminal.");
|
|
35868
|
+
const data2 = await createDataClient(options.orgId, { refreshLogin: true });
|
|
35869
|
+
await refreshPreviewKnowledge(knowledgeBase(data2), options, { log: (line) => console.log(line), confirm });
|
|
35870
|
+
return;
|
|
35871
|
+
}
|
|
34713
35872
|
if (!options.dryRun && !options.yes && !isInteractive()) {
|
|
34714
35873
|
throw usageError3("It asks before it writes; pass -y when not running in a terminal, or --dry-run.");
|
|
34715
35874
|
}
|
|
34716
35875
|
const { config, accessToken } = await requireAuth();
|
|
34717
35876
|
const api = new ApiClient({ apiUrl: config.api_url, accessToken, onUnauthorized: () => getAccessToken(config) });
|
|
34718
35877
|
const data = await createDataClient(options.orgId, { refreshLogin: true });
|
|
34719
|
-
|
|
34720
|
-
|
|
34721
|
-
|
|
34722
|
-
|
|
35878
|
+
const io = { log: (line) => console.log(line), confirm };
|
|
35879
|
+
if (options.verb === "cut-over") {
|
|
35880
|
+
await cutOverHub(hubSetup(api, options.orgId), knowledgeBase(data), options, io);
|
|
35881
|
+
return;
|
|
35882
|
+
}
|
|
35883
|
+
await migrateKnowledge(knowledgeReader(api, options.orgId), knowledgeBase(data), options, io);
|
|
34723
35884
|
}
|
|
34724
35885
|
var USAGE4, OPTIONS;
|
|
34725
35886
|
var init_knowledge = __esm({
|
|
@@ -34733,11 +35894,17 @@ var init_knowledge = __esm({
|
|
|
34733
35894
|
init_helpers();
|
|
34734
35895
|
init_migrate2();
|
|
34735
35896
|
init_remote();
|
|
35897
|
+
init_refresh_preview();
|
|
35898
|
+
init_cut_over();
|
|
34736
35899
|
USAGE4 = `
|
|
34737
|
-
wayai knowledge
|
|
35900
|
+
wayai knowledge \u2014 the organization's knowledge base
|
|
34738
35901
|
|
|
34739
35902
|
Usage:
|
|
34740
35903
|
wayai knowledge migrate --org <org_id> --base <knowledge base> [--hub <hub_id>] [--dry-run] [-y]
|
|
35904
|
+
wayai knowledge refresh-preview --org <org_id> --base <knowledge base> [-y]
|
|
35905
|
+
wayai knowledge cut-over --org <org_id> --base <knowledge base> --hub <preview hub_id> [--dry-run] [-y]
|
|
35906
|
+
|
|
35907
|
+
migrate: move knowledge resources into the organization's knowledge base.
|
|
34741
35908
|
|
|
34742
35909
|
Without --hub, it moves the organization's knowledge resources; with --hub, that hub's own
|
|
34743
35910
|
(read from its production hub), once the organization's have moved. Each resource becomes a
|
|
@@ -34748,6 +35915,30 @@ Usage:
|
|
|
34748
35915
|
written in production.
|
|
34749
35916
|
--dry-run List what it would write, and every reason it would stop, without writing.
|
|
34750
35917
|
-y, --yes Skip the question before writing.
|
|
35918
|
+
|
|
35919
|
+
refresh-preview: make the knowledge base's preview hold its production base's files \u2014 after
|
|
35920
|
+
creating the preview, and whenever it should be current again. Files the preview changed are
|
|
35921
|
+
replaced and files only it holds are removed; nothing else on it changes, and production is
|
|
35922
|
+
only read. It prints what each page copied, removed and left alone; a re-run of a current
|
|
35923
|
+
preview changes nothing.
|
|
35924
|
+
|
|
35925
|
+
--base The production knowledge base, or its preview.
|
|
35926
|
+
-y, --yes Skip the question before copying.
|
|
35927
|
+
|
|
35928
|
+
cut-over: switch a preview hub's agents from their knowledge resources to the knowledge base, once
|
|
35929
|
+
its knowledge has moved. Each distinct set of knowledge its agents read gets one read-only
|
|
35930
|
+
toolset on the knowledge base, reached through a Base Toolset connection; each agent is given
|
|
35931
|
+
list_files, read_file and its toolset's list_ and get_ tools, its resources/<slug>/ paths become
|
|
35932
|
+
data/<file type>/, its knowledge links are disabled, and each move is recorded. It changes the
|
|
35933
|
+
preview only, and prints the production steps. The preview base must hold production's files
|
|
35934
|
+
first (refresh-preview): the preview's agents read them there. It stops before writing on a resource placeholder
|
|
35935
|
+
({{files()}}, {{resource_content()}}, {{file_content()}}) that reads knowledge an agent is about
|
|
35936
|
+
to lose: move that text into its instructions or a skill first.
|
|
35937
|
+
|
|
35938
|
+
--hub The preview hub.
|
|
35939
|
+
--base The production knowledge base, or its preview.
|
|
35940
|
+
--dry-run List every change, and every reason it would stop, without writing.
|
|
35941
|
+
-y, --yes Skip the question before writing.
|
|
34751
35942
|
`;
|
|
34752
35943
|
OPTIONS = {
|
|
34753
35944
|
org: { type: "string" },
|
|
@@ -34803,17 +35994,17 @@ async function adminCommand(args2) {
|
|
|
34803
35994
|
process.exit(1);
|
|
34804
35995
|
}
|
|
34805
35996
|
if (group === "archive") {
|
|
34806
|
-
const { positional: positional2, flagArgs: flagArgs2 } =
|
|
35997
|
+
const { positional: positional2, flagArgs: flagArgs2 } = splitArgs2(afterGroup);
|
|
34807
35998
|
await runArchiveRead(positional2, flagArgs2);
|
|
34808
35999
|
return;
|
|
34809
36000
|
}
|
|
34810
36001
|
if (group === "observability") {
|
|
34811
|
-
const { positional: positional2, flagArgs: flagArgs2 } =
|
|
36002
|
+
const { positional: positional2, flagArgs: flagArgs2 } = splitArgs2(afterGroup);
|
|
34812
36003
|
await runObservabilityRead(positional2, flagArgs2);
|
|
34813
36004
|
return;
|
|
34814
36005
|
}
|
|
34815
36006
|
const [sub, ...rest] = afterGroup;
|
|
34816
|
-
const { positional, flagArgs } =
|
|
36007
|
+
const { positional, flagArgs } = splitArgs2(rest);
|
|
34817
36008
|
if (group === "do") {
|
|
34818
36009
|
if (!sub) {
|
|
34819
36010
|
printHelp2();
|
|
@@ -35101,7 +36292,7 @@ async function adminCommand(args2) {
|
|
|
35101
36292
|
printHelp2();
|
|
35102
36293
|
process.exit(1);
|
|
35103
36294
|
}
|
|
35104
|
-
function
|
|
36295
|
+
function splitArgs2(args2) {
|
|
35105
36296
|
const positional = [];
|
|
35106
36297
|
const flagArgs = [];
|
|
35107
36298
|
for (const a of args2) {
|
|
@@ -37226,9 +38417,9 @@ var init_update = __esm({
|
|
|
37226
38417
|
});
|
|
37227
38418
|
|
|
37228
38419
|
// src/data/commands/actions.ts
|
|
37229
|
-
import { Command } from "commander";
|
|
38420
|
+
import { Command as Command2 } from "commander";
|
|
37230
38421
|
function buildActionsCommand() {
|
|
37231
|
-
const actions = new
|
|
38422
|
+
const actions = new Command2("actions").description(
|
|
37232
38423
|
"Manage Actions \u2014 reusable, named definitions: one record_type + one operation, or a composite of atomic write steps (--config with `steps`)"
|
|
37233
38424
|
);
|
|
37234
38425
|
actions.command("upsert <id>").description(
|
|
@@ -37297,7 +38488,7 @@ var init_actions = __esm({
|
|
|
37297
38488
|
});
|
|
37298
38489
|
|
|
37299
38490
|
// src/data/commands/attachments.ts
|
|
37300
|
-
import { Command as
|
|
38491
|
+
import { Command as Command3 } from "commander";
|
|
37301
38492
|
import { readFileSync as readFileSync19 } from "fs";
|
|
37302
38493
|
function findAttachmentByFilename(attachments, filename) {
|
|
37303
38494
|
return attachments.find((a) => a.key.endsWith(`/${filename}`)) ?? null;
|
|
@@ -37324,7 +38515,7 @@ function uploadPathFrom(uploadUrl) {
|
|
|
37324
38515
|
return uploadUrl;
|
|
37325
38516
|
}
|
|
37326
38517
|
function buildAttachmentsCommand() {
|
|
37327
|
-
const attachments = new
|
|
38518
|
+
const attachments = new Command3("attachments").description("Manage record attachments");
|
|
37328
38519
|
attachments.command("upload <record_type> <id>").description(
|
|
37329
38520
|
"Get a presigned upload URL for a record attachment. With --file, uploads the file content in one step."
|
|
37330
38521
|
).requiredOption("--filename <name>", "File name or path (e.g. docs/guide.md)").option("--content-type <type>", "MIME type", "application/octet-stream").option("--file <path>", "Local file to upload (skips presigned URL output, uploads directly)").action(async function(recordType2, id, opts) {
|
|
@@ -37404,9 +38595,9 @@ var init_attachments = __esm({
|
|
|
37404
38595
|
});
|
|
37405
38596
|
|
|
37406
38597
|
// src/data/commands/batch.ts
|
|
37407
|
-
import { Command as
|
|
38598
|
+
import { Command as Command4 } from "commander";
|
|
37408
38599
|
function buildBasesBatchCommand() {
|
|
37409
|
-
return withBaseOption(new
|
|
38600
|
+
return withBaseOption(new Command4("batch")).description("Execute atomic batch operations (up to 1,000 operations)").requiredOption("--operations <json>", "Operations array (inline JSON or @filename)").action(async function(opts) {
|
|
37410
38601
|
const base = pathSegment(requireBase(this), "--base");
|
|
37411
38602
|
const operations = parseData(opts.operations, "--operations");
|
|
37412
38603
|
const client = await createDataClient();
|
|
@@ -37435,7 +38626,7 @@ import { createReadStream } from "fs";
|
|
|
37435
38626
|
import { access, constants, stat } from "fs/promises";
|
|
37436
38627
|
import { createInterface as createInterface3 } from "readline";
|
|
37437
38628
|
import { randomUUID } from "crypto";
|
|
37438
|
-
import { Command as
|
|
38629
|
+
import { Command as Command5 } from "commander";
|
|
37439
38630
|
function isRetryable(err) {
|
|
37440
38631
|
if (isNetworkError(err)) return true;
|
|
37441
38632
|
if (!(err instanceof ApiError)) return false;
|
|
@@ -37511,7 +38702,7 @@ async function beginSession(state, client, base, recordType2, requestKey) {
|
|
|
37511
38702
|
return res.session;
|
|
37512
38703
|
}
|
|
37513
38704
|
function buildBasesImportCommand() {
|
|
37514
|
-
const importCmd = withBaseOption(new
|
|
38705
|
+
const importCmd = withBaseOption(new Command5("import")).description(
|
|
37515
38706
|
"Bulk historical import: load an NDJSON backfill, list sessions, or undo one"
|
|
37516
38707
|
);
|
|
37517
38708
|
importCmd.command("run <record_type>").description(
|
|
@@ -37738,7 +38929,7 @@ var init_import = __esm({
|
|
|
37738
38929
|
});
|
|
37739
38930
|
|
|
37740
38931
|
// src/data/commands/providers.ts
|
|
37741
|
-
import { Command as
|
|
38932
|
+
import { Command as Command6 } from "commander";
|
|
37742
38933
|
import { writeFileSync as writeFileSync10 } from "fs";
|
|
37743
38934
|
function providerSegment(provider) {
|
|
37744
38935
|
if (!VALID_PROVIDERS.includes(provider)) {
|
|
@@ -37747,7 +38938,7 @@ function providerSegment(provider) {
|
|
|
37747
38938
|
return pathSegment(provider, "provider");
|
|
37748
38939
|
}
|
|
37749
38940
|
function buildBasesProvidersCommand() {
|
|
37750
|
-
const providers = withBaseOption(new
|
|
38941
|
+
const providers = withBaseOption(new Command6("providers")).description(
|
|
37751
38942
|
"Import/export tool definitions between LLM providers and record types"
|
|
37752
38943
|
);
|
|
37753
38944
|
providers.command("import <provider>").description(`Import tool definitions as record types. Providers: ${VALID_PROVIDERS_HELP}`).requiredOption("--tools <json>", "Tool definitions (inline JSON or @filename)").action(async function(provider, opts) {
|
|
@@ -37818,7 +39009,7 @@ var init_providers = __esm({
|
|
|
37818
39009
|
});
|
|
37819
39010
|
|
|
37820
39011
|
// src/data/commands/report.ts
|
|
37821
|
-
import { Command as
|
|
39012
|
+
import { Command as Command7 } from "commander";
|
|
37822
39013
|
import { readFileSync as readFileSync20 } from "fs";
|
|
37823
39014
|
import { dirname as dirname15, join as join36 } from "path";
|
|
37824
39015
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
@@ -37862,7 +39053,7 @@ function reportActionError2(err) {
|
|
|
37862
39053
|
throw err;
|
|
37863
39054
|
}
|
|
37864
39055
|
function buildBasesReportCommand() {
|
|
37865
|
-
const report2 = new
|
|
39056
|
+
const report2 = new Command7("report").description(
|
|
37866
39057
|
"File a report to the Data triage queue and verify the outcome"
|
|
37867
39058
|
);
|
|
37868
39059
|
report2.command("create").description("Submit a report to the Data triage queue (deduplicated)").requiredOption("--title <title>", "Short summary").requiredOption("--description <text>", "What happened").option("--source <source>", "Origin: cli_report (default) | review | security_audit").option("--classification <c>", "Nature: bug (default) | flaky_test | security | enhancement").option(
|
|
@@ -38029,7 +39220,7 @@ var init_report3 = __esm({
|
|
|
38029
39220
|
});
|
|
38030
39221
|
|
|
38031
39222
|
// src/data/commands/credentials.ts
|
|
38032
|
-
import { Command as
|
|
39223
|
+
import { Command as Command8 } from "commander";
|
|
38033
39224
|
import { readFileSync as readFileSync21 } from "fs";
|
|
38034
39225
|
function withValueSourceOptions(cmd, what) {
|
|
38035
39226
|
return cmd.option(
|
|
@@ -38064,7 +39255,7 @@ function credentialPath(base, id) {
|
|
|
38064
39255
|
}
|
|
38065
39256
|
function buildBasesCredentialsCommand() {
|
|
38066
39257
|
const credentials = withBaseOption(
|
|
38067
|
-
new
|
|
39258
|
+
new Command8("credentials").description("Manage a base's integration credentials")
|
|
38068
39259
|
);
|
|
38069
39260
|
withValueSourceOptions(
|
|
38070
39261
|
credentials.command("create").description("Store a credential on this base (value read from stdin, a masked prompt, or --file)").requiredOption("--name <name>", "Credential name \u2014 what a credential:<name> reference resolves").option("--content-type <mime>", "MIME type of the value (e.g. application/x-pkcs12)").option(
|
|
@@ -38191,10 +39382,10 @@ var init_credentials = __esm({
|
|
|
38191
39382
|
});
|
|
38192
39383
|
|
|
38193
39384
|
// src/data/commands/sql.ts
|
|
38194
|
-
import { Command as
|
|
39385
|
+
import { Command as Command9 } from "commander";
|
|
38195
39386
|
import { readFileSync as readFileSync22 } from "fs";
|
|
38196
39387
|
function buildBasesSqlCommand() {
|
|
38197
|
-
return withBaseOption(new
|
|
39388
|
+
return withBaseOption(new Command9("sql")).description("Execute a read-only SQL query against base data").argument("[query]", "SQL query (SELECT only)").option("--file <path>", "Read SQL from a file instead of the argument").option(
|
|
38198
39389
|
"--param <kv...>",
|
|
38199
39390
|
"Named parameters as key=value pairs (e.g. --param status=issued)"
|
|
38200
39391
|
).action(async function(queryArg, opts) {
|
|
@@ -38236,250 +39427,6 @@ var init_sql = __esm({
|
|
|
38236
39427
|
}
|
|
38237
39428
|
});
|
|
38238
39429
|
|
|
38239
|
-
// src/data/commands/tokens.ts
|
|
38240
|
-
import { Command as Command9 } from "commander";
|
|
38241
|
-
function addTokenLifecycleOptions(cmd) {
|
|
38242
|
-
return cmd.option("--expires-at <date>", "Expiration date (ISO 8601, e.g. 2026-06-01T00:00:00Z)").option(
|
|
38243
|
-
"--ttl <duration>",
|
|
38244
|
-
"Relative expiry, e.g. 10m, 2h, 7d (use for disposable probe/test tokens)"
|
|
38245
|
-
).option(
|
|
38246
|
-
"--description <text>",
|
|
38247
|
-
'What holds this token (e.g. "behind credential X"); shown in list and revoke warnings'
|
|
38248
|
-
).addHelpText("after", TOKEN_OUTPUT_HELP);
|
|
38249
|
-
}
|
|
38250
|
-
function tokenCreationOutput(cmd, opts) {
|
|
38251
|
-
return opts.tokenOnly ? "token" : outputFormat(cmd);
|
|
38252
|
-
}
|
|
38253
|
-
function printCreatedToken(data, output) {
|
|
38254
|
-
if (output !== "token") {
|
|
38255
|
-
printOutput(data, output);
|
|
38256
|
-
return;
|
|
38257
|
-
}
|
|
38258
|
-
const token = typeof data === "object" && data !== null && "token" in data ? data.token : void 0;
|
|
38259
|
-
if (typeof token !== "string" || !RAW_TOKEN_PATTERN.test(token)) {
|
|
38260
|
-
throw new Error("Token creation response did not contain a valid rec_ token");
|
|
38261
|
-
}
|
|
38262
|
-
console.log(token);
|
|
38263
|
-
}
|
|
38264
|
-
function resolveExpiresAt(opts) {
|
|
38265
|
-
if (opts.ttl && opts.expiresAt) {
|
|
38266
|
-
throw expected("--ttl and --expires-at are mutually exclusive \u2014 pass one.");
|
|
38267
|
-
}
|
|
38268
|
-
if (opts.ttl) return new Date(Date.now() + parseDuration(opts.ttl, "--ttl")).toISOString();
|
|
38269
|
-
return opts.expiresAt;
|
|
38270
|
-
}
|
|
38271
|
-
function applyTokenLifecycleOpts(body, opts) {
|
|
38272
|
-
const expiresAt = resolveExpiresAt(opts);
|
|
38273
|
-
if (expiresAt) body.expires_at = expiresAt;
|
|
38274
|
-
if (opts.description) body.description = opts.description;
|
|
38275
|
-
}
|
|
38276
|
-
function listAllTokens(client) {
|
|
38277
|
-
return client.collectPages(
|
|
38278
|
-
(cursor) => `/v1/tokens?limit=${TOKEN_LIST_PAGE_LIMIT}${cursor ? `&cursor=${encodeURIComponent(cursor)}` : ""}`
|
|
38279
|
-
);
|
|
38280
|
-
}
|
|
38281
|
-
function pruneRow(t, reason) {
|
|
38282
|
-
return {
|
|
38283
|
-
token_id: t.token_id,
|
|
38284
|
-
name: t.name,
|
|
38285
|
-
reason,
|
|
38286
|
-
last_used: t.last_used_at ?? "never"
|
|
38287
|
-
};
|
|
38288
|
-
}
|
|
38289
|
-
function buildBasesTokensCommand() {
|
|
38290
|
-
const tokens2 = new Command9("tokens").description("Manage API tokens");
|
|
38291
|
-
addTokenLifecycleOptions(
|
|
38292
|
-
tokens2.command("create").description("Create a scoped API token").requiredOption("--name <name>", "Token name").requiredOption("--grants <json>", "Grant definitions as JSON array").option(
|
|
38293
|
-
"--token-only",
|
|
38294
|
-
"Print only the raw rec_ token for secure command substitution (shown once)"
|
|
38295
|
-
)
|
|
38296
|
-
).action(async function(opts) {
|
|
38297
|
-
const grants = parseData(opts.grants, "--grants");
|
|
38298
|
-
const body = { name: opts.name, grants };
|
|
38299
|
-
applyTokenLifecycleOpts(body, opts);
|
|
38300
|
-
const client = await createDataClient();
|
|
38301
|
-
const data = await client.request("POST", "/v1/tokens", body);
|
|
38302
|
-
printCreatedToken(data, tokenCreationOutput(this, opts));
|
|
38303
|
-
});
|
|
38304
|
-
addTokenLifecycleOptions(
|
|
38305
|
-
// The only token verb that addresses a base, so it alone declares `--base`.
|
|
38306
|
-
// Putting it on the `tokens` group would advertise a flag that does nothing
|
|
38307
|
-
// on `list`, `revoke` and `prune` — the defect `withBaseOption` exists to
|
|
38308
|
-
// avoid.
|
|
38309
|
-
withBaseOption(tokens2.command("create-for-toolset <slug>")).description(
|
|
38310
|
-
"Create a toolset-bound (consumer) token whose authorization IS the toolset tool surface \u2014 it can call exactly the tools that toolset exposes (those record types + operations, relationships, batch, and sql only if enabled) and nothing else. Binds to the configured --base."
|
|
38311
|
-
).option("--name <name>", 'Token name (default: "<slug> consumer")').option(
|
|
38312
|
-
"--token-only",
|
|
38313
|
-
"Print only the raw rec_ token for secure command substitution (shown once)"
|
|
38314
|
-
)
|
|
38315
|
-
).action(async function(slug, opts) {
|
|
38316
|
-
const base = requireBase(this);
|
|
38317
|
-
const body = {
|
|
38318
|
-
name: opts.name ?? `${slug} consumer`,
|
|
38319
|
-
toolset_binding: { base_id: base, toolset_id: slug }
|
|
38320
|
-
};
|
|
38321
|
-
applyTokenLifecycleOpts(body, opts);
|
|
38322
|
-
const client = await createDataClient();
|
|
38323
|
-
const data = await client.request("POST", "/v1/tokens", body);
|
|
38324
|
-
const output = tokenCreationOutput(this, opts);
|
|
38325
|
-
printCreatedToken(data, output);
|
|
38326
|
-
if (output === "table") {
|
|
38327
|
-
console.log(`
|
|
38328
|
-
MCP URL: ${toolsetMcpUrl(slug)}`);
|
|
38329
|
-
}
|
|
38330
|
-
});
|
|
38331
|
-
tokens2.command("list").description("List API tokens (status, last_used_at, expires_at, description)").action(async function() {
|
|
38332
|
-
const client = await createDataClient();
|
|
38333
|
-
printOutput(await listAllTokens(client), outputFormat(this));
|
|
38334
|
-
});
|
|
38335
|
-
tokens2.command("revoke <token_id>").description(
|
|
38336
|
-
"Revoke an API token (permanent). A recently-used or toolset-bound token requires --force."
|
|
38337
|
-
).option("--force", "Revoke even if the token appears to be in live use").action(async function(tokenId, opts) {
|
|
38338
|
-
const client = await createDataClient();
|
|
38339
|
-
const id = pathSegment(tokenId, "token id");
|
|
38340
|
-
const path42 = (force) => `/v1/tokens/${id}${force ? "?force=true" : ""}`;
|
|
38341
|
-
const format = outputFormat(this);
|
|
38342
|
-
const revoked = (forced) => printOutput({ token_id: tokenId, revoked: true, forced }, format);
|
|
38343
|
-
if (opts.force) {
|
|
38344
|
-
await client.request("DELETE", path42(true));
|
|
38345
|
-
revoked(true);
|
|
38346
|
-
return;
|
|
38347
|
-
}
|
|
38348
|
-
try {
|
|
38349
|
-
await client.request("DELETE", path42(false));
|
|
38350
|
-
revoked(false);
|
|
38351
|
-
} catch (err) {
|
|
38352
|
-
if (!(err instanceof ApiError) || err.status !== 409 || dataErrorDetails(err)?.requires_force !== true) {
|
|
38353
|
-
throw err;
|
|
38354
|
-
}
|
|
38355
|
-
console.error(err.message);
|
|
38356
|
-
if (!isInteractive()) {
|
|
38357
|
-
console.error("\nRe-run with --force to revoke anyway.");
|
|
38358
|
-
return process.exit(1);
|
|
38359
|
-
}
|
|
38360
|
-
if (!await confirm("Revoke anyway?")) {
|
|
38361
|
-
console.error("Aborted.");
|
|
38362
|
-
return process.exit(1);
|
|
38363
|
-
}
|
|
38364
|
-
await client.request("DELETE", path42(true));
|
|
38365
|
-
revoked(true);
|
|
38366
|
-
}
|
|
38367
|
-
});
|
|
38368
|
-
tokens2.command("prune").description(
|
|
38369
|
-
"Revoke stale tokens, selected by staleness only (expired, or unused past --unused-since) \u2014 never by name. Previews by default; revoking requires --yes or an interactive confirmation."
|
|
38370
|
-
).option(
|
|
38371
|
-
"--unused-since <duration>",
|
|
38372
|
-
"Also prune tokens with no activity for this long (e.g. 30d). Skips toolset-bound and legacy usage-untracked tokens."
|
|
38373
|
-
).option("--include-bound", "Allow pruning stale toolset-bound (consumer) tokens").option("-y, --yes", "Revoke without prompting (required when non-interactive)").action(async function(opts) {
|
|
38374
|
-
const staleMs = opts.unusedSince ? parseDuration(opts.unusedSince, "--unused-since") : void 0;
|
|
38375
|
-
const client = await createDataClient();
|
|
38376
|
-
const allTokens = await listAllTokens(client);
|
|
38377
|
-
const now = Date.now();
|
|
38378
|
-
const targets = [];
|
|
38379
|
-
const skipped = [];
|
|
38380
|
-
for (const t of allTokens) {
|
|
38381
|
-
const expired = t.status ? t.status === "expired" : !!t.expires_at && Date.parse(t.expires_at) <= now;
|
|
38382
|
-
if (expired) {
|
|
38383
|
-
targets.push(pruneRow(t, "expired"));
|
|
38384
|
-
continue;
|
|
38385
|
-
}
|
|
38386
|
-
if (staleMs === void 0) continue;
|
|
38387
|
-
const lastActivity = Date.parse(t.last_used_at ?? t.created_at);
|
|
38388
|
-
if (!(now - lastActivity > staleMs)) continue;
|
|
38389
|
-
if (t.usage_tracked !== true) {
|
|
38390
|
-
skipped.push(
|
|
38391
|
-
pruneRow(
|
|
38392
|
-
t,
|
|
38393
|
-
"usage untracked (legacy token) \u2014 revoke individually if truly unused"
|
|
38394
|
-
)
|
|
38395
|
-
);
|
|
38396
|
-
} else if (t.toolset_binding && !opts.includeBound) {
|
|
38397
|
-
skipped.push(
|
|
38398
|
-
pruneRow(
|
|
38399
|
-
t,
|
|
38400
|
-
`toolset-bound ('${t.toolset_binding.toolset_id}') \u2014 pass --include-bound to prune`
|
|
38401
|
-
)
|
|
38402
|
-
);
|
|
38403
|
-
} else {
|
|
38404
|
-
targets.push(pruneRow(t, `unused for over ${opts.unusedSince}`));
|
|
38405
|
-
}
|
|
38406
|
-
}
|
|
38407
|
-
const format = outputFormat(this);
|
|
38408
|
-
const json = format === "json";
|
|
38409
|
-
const note = (line) => json ? console.error(line) : console.log(line);
|
|
38410
|
-
const printPreview = () => {
|
|
38411
|
-
if (targets.length === 0) {
|
|
38412
|
-
note("Nothing to prune.");
|
|
38413
|
-
} else {
|
|
38414
|
-
note(`Would revoke ${targets.length} token(s):`);
|
|
38415
|
-
if (!json) printOutput(targets, format);
|
|
38416
|
-
}
|
|
38417
|
-
if (skipped.length === 0) return;
|
|
38418
|
-
note(`
|
|
38419
|
-
Skipped ${skipped.length} stale token(s):`);
|
|
38420
|
-
if (!json) printOutput(skipped, format);
|
|
38421
|
-
};
|
|
38422
|
-
printPreview();
|
|
38423
|
-
if (targets.length === 0) {
|
|
38424
|
-
if (json) printOutput({ revoked: 0, failed: 0, targets, skipped }, format);
|
|
38425
|
-
return;
|
|
38426
|
-
}
|
|
38427
|
-
if (!opts.yes) {
|
|
38428
|
-
if (!isInteractive()) {
|
|
38429
|
-
console.error("\nPreview only \u2014 re-run with --yes to revoke these tokens.");
|
|
38430
|
-
if (json) printOutput({ preview: true, revoked: 0, failed: 0, targets, skipped }, format);
|
|
38431
|
-
return process.exit(1);
|
|
38432
|
-
}
|
|
38433
|
-
if (!await confirm(`
|
|
38434
|
-
Revoke ${targets.length} token(s)?`)) {
|
|
38435
|
-
console.error("Aborted.");
|
|
38436
|
-
return process.exit(1);
|
|
38437
|
-
}
|
|
38438
|
-
}
|
|
38439
|
-
let revoked = 0;
|
|
38440
|
-
let failed = 0;
|
|
38441
|
-
for (const t of targets) {
|
|
38442
|
-
try {
|
|
38443
|
-
await client.request(
|
|
38444
|
-
"DELETE",
|
|
38445
|
-
`/v1/tokens/${pathSegment(t.token_id, "token id")}?force=true`
|
|
38446
|
-
);
|
|
38447
|
-
revoked++;
|
|
38448
|
-
} catch (err) {
|
|
38449
|
-
failed++;
|
|
38450
|
-
console.error(
|
|
38451
|
-
`Failed to revoke ${t.token_id} ('${sanitizeTerminalText(t.name)}'): ${err instanceof Error ? err.message : String(err)}`
|
|
38452
|
-
);
|
|
38453
|
-
}
|
|
38454
|
-
}
|
|
38455
|
-
if (json) printOutput({ revoked, failed, targets, skipped }, format);
|
|
38456
|
-
else console.log(`Revoked ${revoked} token(s)${failed > 0 ? `, ${failed} failed` : ""}.`);
|
|
38457
|
-
if (failed > 0) return process.exit(1);
|
|
38458
|
-
});
|
|
38459
|
-
return tokens2;
|
|
38460
|
-
}
|
|
38461
|
-
var RAW_TOKEN_PATTERN, TOKEN_OUTPUT_HELP, TOKEN_LIST_PAGE_LIMIT;
|
|
38462
|
-
var init_tokens = __esm({
|
|
38463
|
-
"src/data/commands/tokens.ts"() {
|
|
38464
|
-
"use strict";
|
|
38465
|
-
init_client();
|
|
38466
|
-
init_output();
|
|
38467
|
-
init_helpers();
|
|
38468
|
-
init_api_client();
|
|
38469
|
-
init_terminal_output();
|
|
38470
|
-
init_utils();
|
|
38471
|
-
init_expected();
|
|
38472
|
-
RAW_TOKEN_PATTERN = /^rec_[A-Za-z0-9_-]{43}$/;
|
|
38473
|
-
TOKEN_OUTPUT_HELP = `
|
|
38474
|
-
Output:
|
|
38475
|
-
--json prints the unwrapped response object; the secret is at top-level .token, never .data.token.
|
|
38476
|
-
--token-only prints only a validated rec_ token and overrides table/JSON formatting.
|
|
38477
|
-
The raw token is shown once. Capture it securely and do not log it.
|
|
38478
|
-
`;
|
|
38479
|
-
TOKEN_LIST_PAGE_LIMIT = 250;
|
|
38480
|
-
}
|
|
38481
|
-
});
|
|
38482
|
-
|
|
38483
39430
|
// src/data/base-tags.ts
|
|
38484
39431
|
async function resolveBaseTags(rawTags, client) {
|
|
38485
39432
|
const refs = splitList(rawTags);
|
|
@@ -39393,7 +40340,7 @@ function isConflict(err) {
|
|
|
39393
40340
|
return err instanceof ApiError && (err.status === 409 || err.status === 412);
|
|
39394
40341
|
}
|
|
39395
40342
|
function conflictLine(c, texts) {
|
|
39396
|
-
return `${
|
|
40343
|
+
return `${safe3(c.rel)}: ${texts[c.reason]}${c.other === void 0 ? "" : ` "${safe3(c.other)}"`}`;
|
|
39397
40344
|
}
|
|
39398
40345
|
function remedy(conflicts, overwriteCommand) {
|
|
39399
40346
|
const isClash = (c) => c.reason === "case_twin" || c.reason === "file_folder";
|
|
@@ -39414,7 +40361,7 @@ async function pullFiles(opts) {
|
|
|
39414
40361
|
const plan = planPull({ synced: existing?.files ?? {}, local, remote, overwrite: opts.overwrite });
|
|
39415
40362
|
if (plan.replacedLocal.length > 0) {
|
|
39416
40363
|
console.log("These local changes would be replaced with the base's copies:");
|
|
39417
|
-
for (const rel of plan.replacedLocal) console.log(` ${
|
|
40364
|
+
for (const rel of plan.replacedLocal) console.log(` ${safe3(rel)}`);
|
|
39418
40365
|
if (!await agree(`Replace ${plan.replacedLocal.length} locally changed file(s)?`, opts.yes)) {
|
|
39419
40366
|
console.log("Cancelled.");
|
|
39420
40367
|
return;
|
|
@@ -39435,7 +40382,7 @@ async function pullFiles(opts) {
|
|
|
39435
40382
|
}
|
|
39436
40383
|
removeLocal(dir, rel);
|
|
39437
40384
|
delete record.files[rel];
|
|
39438
|
-
changes.push(`${MARK.delete} ${
|
|
40385
|
+
changes.push(`${MARK.delete} ${safe3(rel)}`);
|
|
39439
40386
|
}
|
|
39440
40387
|
for (const w of plan.writes) {
|
|
39441
40388
|
const clash = diskClash(dir, w.rel);
|
|
@@ -39468,7 +40415,7 @@ async function pullFiles(opts) {
|
|
|
39468
40415
|
const hash = computeHash(bytes);
|
|
39469
40416
|
writeLocal(dir, w.rel, bytes);
|
|
39470
40417
|
record.files[w.rel] = { version, content_sha256: hash };
|
|
39471
|
-
changes.push(`${local.has(w.rel) ? MARK.update : MARK.create} ${
|
|
40418
|
+
changes.push(`${local.has(w.rel) ? MARK.update : MARK.create} ${safe3(w.rel)}`);
|
|
39472
40419
|
}
|
|
39473
40420
|
} finally {
|
|
39474
40421
|
writeRecord(dir, record);
|
|
@@ -39476,13 +40423,13 @@ async function pullFiles(opts) {
|
|
|
39476
40423
|
const count = Object.keys(record.files).length;
|
|
39477
40424
|
console.log(sanitizeTerminalText(`Pulled ${describeSync(target)} \u2192 ${dir} (${count} file${count === 1 ? "" : "s"}).`));
|
|
39478
40425
|
printSection("Changes:", changes);
|
|
39479
|
-
printSection("Kept \u2014 your local changes:", plan.kept.map((k) => `${
|
|
40426
|
+
printSection("Kept \u2014 your local changes:", plan.kept.map((k) => `${safe3(k.rel)}: ${PULL_KEPT[k.reason]}`));
|
|
39480
40427
|
printSection(
|
|
39481
40428
|
`Conflicts \u2014 your copy is kept; to settle them, ${remedy(conflicts, pullCommand2(target, opts.dir, true))}:`,
|
|
39482
40429
|
conflicts.map((c) => conflictLine(c, PULL_CONFLICT))
|
|
39483
40430
|
);
|
|
39484
|
-
printSection("Changed or deleted on the base while pulling \u2014 left as is; pull again to take it:", changedWhilePulling.map(
|
|
39485
|
-
printSection(`Not synced \u2014 names a synced folder leaves out (${OUTSIDE_SYNC.join(", ")}):`, excluded.map(
|
|
40431
|
+
printSection("Changed or deleted on the base while pulling \u2014 left as is; pull again to take it:", changedWhilePulling.map(safe3));
|
|
40432
|
+
printSection(`Not synced \u2014 names a synced folder leaves out (${OUTSIDE_SYNC.join(", ")}):`, excluded.map(safe3));
|
|
39486
40433
|
}
|
|
39487
40434
|
function isTextType(contentType) {
|
|
39488
40435
|
const ct = contentType.split(";")[0].trim().toLowerCase();
|
|
@@ -39500,7 +40447,7 @@ function changedLines(before, after) {
|
|
|
39500
40447
|
async function describeSteps(client, t, dir, steps) {
|
|
39501
40448
|
const lines = [];
|
|
39502
40449
|
for (const step of steps) {
|
|
39503
|
-
const rel =
|
|
40450
|
+
const rel = safe3(step.rel);
|
|
39504
40451
|
if (step.op === "create") {
|
|
39505
40452
|
lines.push(`${MARK.create} ${rel} (new, ${step.local.size} bytes)`);
|
|
39506
40453
|
} else if (step.op === "delete") {
|
|
@@ -39527,10 +40474,10 @@ async function describeSteps(client, t, dir, steps) {
|
|
|
39527
40474
|
return lines;
|
|
39528
40475
|
}
|
|
39529
40476
|
function printPushNotes(plan) {
|
|
39530
|
-
printSection("Changed on the base since your last pull \u2014 left as is; pull to take it:", plan.changedOnBase.map(
|
|
39531
|
-
printSection("Deleted on the base since your last pull \u2014 left as is; pull to remove your copy:", plan.deletedOnBase.map(
|
|
39532
|
-
printSection("On the base, not in your files \u2014 left as is; pull to fetch it:", plan.onlyOnBase.map(
|
|
39533
|
-
printSection("Deleted in your files \u2014 kept on the base; push with --prune to delete it:", plan.keptOnBase.map(
|
|
40477
|
+
printSection("Changed on the base since your last pull \u2014 left as is; pull to take it:", plan.changedOnBase.map(safe3));
|
|
40478
|
+
printSection("Deleted on the base since your last pull \u2014 left as is; pull to remove your copy:", plan.deletedOnBase.map(safe3));
|
|
40479
|
+
printSection("On the base, not in your files \u2014 left as is; pull to fetch it:", plan.onlyOnBase.map(safe3));
|
|
40480
|
+
printSection("Deleted in your files \u2014 kept on the base; push with --prune to delete it:", plan.keptOnBase.map(safe3));
|
|
39534
40481
|
}
|
|
39535
40482
|
async function pushFiles(opts) {
|
|
39536
40483
|
const label = opts.label === void 0 ? void 0 : labelValue(opts.label);
|
|
@@ -39593,7 +40540,7 @@ Push ${n} change${n === 1 ? "" : "s"} to ${describeSync(target)}?`, opts.yes)) {
|
|
|
39593
40540
|
} else {
|
|
39594
40541
|
const bytes = readLocal(dir, step.rel);
|
|
39595
40542
|
if (computeHash(bytes) !== step.local.content_sha256) {
|
|
39596
|
-
throw expected(`${
|
|
40543
|
+
throw expected(`${safe3(step.rel)} changed while the push ran. Nothing more was pushed; run the push again.`);
|
|
39597
40544
|
}
|
|
39598
40545
|
const contentType = step.op === "update" ? step.remote.content_type : guessMimeType(step.rel) ?? "application/octet-stream";
|
|
39599
40546
|
const row = await client.upload(
|
|
@@ -39608,7 +40555,7 @@ Push ${n} change${n === 1 ? "" : "s"} to ${describeSync(target)}?`, opts.yes)) {
|
|
|
39608
40555
|
};
|
|
39609
40556
|
}
|
|
39610
40557
|
pushed++;
|
|
39611
|
-
console.log(` ${MARK[step.op]} ${
|
|
40558
|
+
console.log(` ${MARK[step.op]} ${safe3(step.rel)}`);
|
|
39612
40559
|
} catch (err) {
|
|
39613
40560
|
if (step.op === "delete" && err instanceof ApiError && err.status === 404) {
|
|
39614
40561
|
delete record.files[step.rel];
|
|
@@ -39635,7 +40582,7 @@ function finishWithConflicts(t, dir, conflicts) {
|
|
|
39635
40582
|
`${count} conflict${count === 1 ? "" : "s"} not pushed \u2014 the base's copies of those files were left as they are. To settle them, ${remedy(conflicts, pullCommand2(t, dir, true))}, then push again.`
|
|
39636
40583
|
);
|
|
39637
40584
|
}
|
|
39638
|
-
var LIST_PAGE2, MAX_LIST_PAGES2, MAX_DIFF_BYTES, MARK,
|
|
40585
|
+
var LIST_PAGE2, MAX_LIST_PAGES2, MAX_DIFF_BYTES, MARK, safe3, PULL_KEPT, PULL_CONFLICT, PUSH_CONFLICT;
|
|
39639
40586
|
var init_sync3 = __esm({
|
|
39640
40587
|
"src/data/files-sync/sync.ts"() {
|
|
39641
40588
|
"use strict";
|
|
@@ -39657,7 +40604,7 @@ var init_sync3 = __esm({
|
|
|
39657
40604
|
MAX_LIST_PAGES2 = 1e3;
|
|
39658
40605
|
MAX_DIFF_BYTES = 256 * 1024;
|
|
39659
40606
|
MARK = { create: "+", update: "~", delete: "-" };
|
|
39660
|
-
|
|
40607
|
+
safe3 = (rel) => sanitizeTerminalText(rel);
|
|
39661
40608
|
PULL_KEPT = {
|
|
39662
40609
|
edited: "edited in your files, not pushed yet",
|
|
39663
40610
|
deleted: "deleted in your files, not pushed yet (push --prune deletes it from the base)"
|
|
@@ -41278,6 +42225,8 @@ Commands:
|
|
|
41278
42225
|
communities An organization's communities: list, join link (get/rotate/off), stats, access list (list/add/remove) \u2014 \`wayai communities --help\`
|
|
41279
42226
|
contacts Link a contact to its customer record in a base, or unlink it (browser sign-in only) \u2014 \`wayai contacts --help\`
|
|
41280
42227
|
knowledge migrate Move an organization's knowledge resources, then a hub's (--hub), into its knowledge base as file types and files \u2014 \`wayai knowledge --help\`
|
|
42228
|
+
knowledge refresh-preview Make the knowledge base's preview hold its production base's files \u2014 \`wayai knowledge --help\`
|
|
42229
|
+
knowledge cut-over Switch a preview hub's agents from their knowledge resources to the knowledge base \u2014 \`wayai knowledge --help\`
|
|
41281
42230
|
report create Report a platform bug (submits to the triage queue)
|
|
41282
42231
|
report edit Amend your own pending report (title/description/error/steps/context)
|
|
41283
42232
|
update Update CLI to the latest version
|