@botpress/adk 1.7.19 → 1.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -654,7 +654,7 @@ var PRETTIER_CONFIG, formatCode = async (code, filepath) => {
654
654
  `));
655
655
  return code;
656
656
  }
657
- }, ADK_VERSION = "1.7.19", relative2 = (from, to) => {
657
+ }, ADK_VERSION = "1.8.1", relative2 = (from, to) => {
658
658
  const fromDir = path10.dirname(from);
659
659
  const relative3 = path10.relative(fromDir, to);
660
660
  return relative3.startsWith(".") ? relative3 : `./${relative3}`;
@@ -797,7 +797,7 @@ var init_integration_action_types = __esm(() => {
797
797
  var require_package = __commonJS((exports, module) => {
798
798
  module.exports = {
799
799
  name: "@botpress/adk",
800
- version: "1.7.19",
800
+ version: "1.8.1",
801
801
  description: "Core ADK library for building AI agents on Botpress",
802
802
  type: "module",
803
803
  main: "dist/index.js",
@@ -844,7 +844,7 @@ var require_package = __commonJS((exports, module) => {
844
844
  "@botpress/cli": "^4.23",
845
845
  "@botpress/client": "^1.27.2",
846
846
  "@botpress/cognitive": "^0.2.0",
847
- "@botpress/runtime": "^1.7.19",
847
+ "@botpress/runtime": "^1.8.1",
848
848
  "@botpress/sdk": "^4.18.1",
849
849
  "@bpinternal/yargs-extra": "^0.0.21",
850
850
  "@parcel/watcher": "^2.5.1",
@@ -4829,7 +4829,7 @@ class AgentProjectGenerator {
4829
4829
  deploy: "adk deploy"
4830
4830
  },
4831
4831
  dependencies: {
4832
- "@botpress/runtime": `^${"1.7.19"}`
4832
+ "@botpress/runtime": `^${"1.8.1"}`
4833
4833
  },
4834
4834
  devDependencies: {
4835
4835
  typescript: "^5.9.3"
@@ -7814,10 +7814,732 @@ class TableManager {
7814
7814
  };
7815
7815
  }
7816
7816
  }
7817
+ // src/knowledge/manager.ts
7818
+ import crypto4 from "crypto";
7819
+ import path31 from "path";
7820
+ import fs16 from "fs/promises";
7821
+ import { glob } from "glob";
7822
+ import { Client as Client12 } from "@botpress/client";
7823
+ import { DataSource } from "@botpress/runtime";
7824
+
7825
+ // src/knowledge/types.ts
7826
+ var KBSyncOperation;
7827
+ ((KBSyncOperation2) => {
7828
+ KBSyncOperation2["Sync"] = "sync";
7829
+ KBSyncOperation2["Skip"] = "skip";
7830
+ })(KBSyncOperation ||= {});
7831
+
7832
+ // src/knowledge/manager.ts
7833
+ var WellKnownTags = {
7834
+ KNOWLEDGE: "knowledge",
7835
+ KNOWLEDGE_BASE_NAME: "kbName",
7836
+ KNOWLEDGE_SOURCE_ID: "sourceId",
7837
+ KNOWLEDGE_SOURCE_TYPE: "sourceType"
7838
+ };
7839
+ function sourceTag(sourceId, field) {
7840
+ const sanitizedId = sourceId.replace(/[^a-zA-Z0-9]/g, "").toLowerCase();
7841
+ return `source${sanitizedId}${field}`;
7842
+ }
7843
+ var WellKnownMetadata = {
7844
+ TITLE: "title"
7845
+ };
7846
+ function isFileMetadata(metadata) {
7847
+ return typeof metadata === "object" && metadata !== null && "hash" in metadata && typeof metadata.hash === "string" && "relPath" in metadata && typeof metadata.relPath === "string";
7848
+ }
7849
+
7850
+ class KnowledgeManager {
7851
+ client;
7852
+ botId;
7853
+ project;
7854
+ fileHashCache = new Map;
7855
+ constructor(options) {
7856
+ this.botId = options.botId;
7857
+ this.project = options.project;
7858
+ }
7859
+ clearHashCache() {
7860
+ this.fileHashCache.clear();
7861
+ }
7862
+ async getClient() {
7863
+ if (!this.client) {
7864
+ const credentials = await auth.getActiveCredentials();
7865
+ this.assertBotId("initialize client");
7866
+ this.client = new Client12({
7867
+ token: credentials.token,
7868
+ apiUrl: credentials.apiUrl,
7869
+ botId: this.botId,
7870
+ headers: {
7871
+ "x-multiple-integrations": "true"
7872
+ }
7873
+ });
7874
+ }
7875
+ return this.client;
7876
+ }
7877
+ assertBotId(operation) {
7878
+ if (!this.botId) {
7879
+ throw new Error(`Operation "${operation}" requires a bot ID. ` + "Please deploy your agent first or create agent.json with botId and workspaceId.");
7880
+ }
7881
+ }
7882
+ formatError(error) {
7883
+ if (!(error instanceof Error)) {
7884
+ return String(error);
7885
+ }
7886
+ let message = error.message;
7887
+ if ("response" in error && error.response !== null && typeof error.response === "object" && "data" in error.response) {
7888
+ message += ` - API: ${JSON.stringify(error.response.data)}`;
7889
+ }
7890
+ return message;
7891
+ }
7892
+ getLocalKnowledgeBases() {
7893
+ const kbs = [];
7894
+ for (const kbRef of this.project.knowledge) {
7895
+ const definition = kbRef.definition;
7896
+ kbs.push({
7897
+ name: definition.name,
7898
+ description: definition.description,
7899
+ sourceCount: definition.sources?.length || 0
7900
+ });
7901
+ }
7902
+ return kbs;
7903
+ }
7904
+ computeContentHash(fileHashes) {
7905
+ const sortedEntries = Object.entries(fileHashes).sort(([a], [b]) => a.localeCompare(b));
7906
+ const combined = sortedEntries.map(([filePath, hash]) => `${filePath}:${hash}`).join(`
7907
+ `);
7908
+ return crypto4.createHash("sha256").update(combined).digest("hex");
7909
+ }
7910
+ async listRemoteKnowledgeBases() {
7911
+ const client = await this.getClient();
7912
+ const kbs = [];
7913
+ let nextToken;
7914
+ do {
7915
+ const response = await client.listKnowledgeBases({ nextToken });
7916
+ kbs.push(...response.knowledgeBases);
7917
+ nextToken = response.meta.nextToken;
7918
+ } while (nextToken);
7919
+ return kbs;
7920
+ }
7921
+ async findRemoteKB(name) {
7922
+ const kbs = await this.listRemoteKnowledgeBases();
7923
+ return kbs.find((kb) => kb.name === name);
7924
+ }
7925
+ async createKnowledgeBase(name) {
7926
+ const client = await this.getClient();
7927
+ await client.createKnowledgeBase({ name });
7928
+ const created = await this.findRemoteKB(name);
7929
+ if (!created) {
7930
+ throw new Error(`Failed to find KB "${name}" after creation`);
7931
+ }
7932
+ return created;
7933
+ }
7934
+ async getOrphanedKBs() {
7935
+ const localKbNames = this.getLocalKnowledgeBases().map((kb) => kb.name);
7936
+ const remoteKbs = await this.listRemoteKnowledgeBases();
7937
+ return remoteKbs.filter((kb) => !localKbNames.includes(kb.name));
7938
+ }
7939
+ async getOrphanedSources(kbName, localSourceIds) {
7940
+ const client = await this.getClient();
7941
+ const tags = {
7942
+ [WellKnownTags.KNOWLEDGE]: "true",
7943
+ [WellKnownTags.KNOWLEDGE_BASE_NAME]: kbName
7944
+ };
7945
+ const files = [];
7946
+ let nextToken;
7947
+ do {
7948
+ const response = await client.listFiles({ tags, nextToken });
7949
+ files.push(...response.files);
7950
+ nextToken = response.meta.nextToken;
7951
+ } while (nextToken);
7952
+ const filesBySourceId = new Map;
7953
+ for (const file of files) {
7954
+ const sourceId = file.tags?.[WellKnownTags.KNOWLEDGE_SOURCE_ID] || file.metadata?.sourceId;
7955
+ if (sourceId) {
7956
+ filesBySourceId.set(sourceId, (filesBySourceId.get(sourceId) || 0) + 1);
7957
+ }
7958
+ }
7959
+ const orphaned = [];
7960
+ for (const [sourceId, fileCount] of filesBySourceId) {
7961
+ if (!localSourceIds.includes(sourceId)) {
7962
+ orphaned.push({ sourceId, fileCount });
7963
+ }
7964
+ }
7965
+ return orphaned;
7966
+ }
7967
+ async deleteOrphanedSource(kbName, sourceId) {
7968
+ const client = await this.getClient();
7969
+ const tags = {
7970
+ [WellKnownTags.KNOWLEDGE]: "true",
7971
+ [WellKnownTags.KNOWLEDGE_BASE_NAME]: kbName,
7972
+ [WellKnownTags.KNOWLEDGE_SOURCE_ID]: sourceId
7973
+ };
7974
+ const files = await this.listExistingFiles(client, tags);
7975
+ console.log(` Deleting ${files.length} files from orphaned source "${sourceId}"...`);
7976
+ const results = await Promise.allSettled(files.map((f) => client.deleteFile({ id: f.id })));
7977
+ const deletedFiles = results.filter((r) => r.status === "fulfilled").length;
7978
+ const errors = results.filter((r) => r.status === "rejected").map((r) => String(r.reason));
7979
+ return { deletedFiles, errors };
7980
+ }
7981
+ async cleanupSourceTags(kbId, kbName, sourceIds, existingTags) {
7982
+ const client = await this.getClient();
7983
+ const newTags = { ...existingTags };
7984
+ for (const sourceId of sourceIds) {
7985
+ const sanitizedId = sourceId.replace(/[^a-zA-Z0-9]/g, "").toLowerCase();
7986
+ newTags[`source${sanitizedId}hash`] = "";
7987
+ newTags[`source${sanitizedId}lastupdatedat`] = "";
7988
+ }
7989
+ await client.updateKnowledgeBase({ id: kbId, name: kbName, tags: newTags });
7990
+ }
7991
+ async deleteKnowledgeBase(kbId, kbName) {
7992
+ const client = await this.getClient();
7993
+ const tags = {
7994
+ [WellKnownTags.KNOWLEDGE]: "true",
7995
+ [WellKnownTags.KNOWLEDGE_BASE_NAME]: kbName
7996
+ };
7997
+ console.log(` Listing files for KB "${kbName}"...`);
7998
+ const files = await this.listExistingFiles(client, tags);
7999
+ console.log(` Found ${files.length} files to delete`);
8000
+ const results = await Promise.allSettled(files.map((file) => client.deleteFile({ id: file.id })));
8001
+ const deletedFiles = results.filter((r) => r.status === "fulfilled").length;
8002
+ console.log(` Deleting KB "${kbName}"...`);
8003
+ await client.deleteKnowledgeBase({ id: kbId });
8004
+ return { deletedFiles };
8005
+ }
8006
+ getRemoteSourceHash(kb, sourceId) {
8007
+ return kb.tags?.[sourceTag(sourceId, "hash")];
8008
+ }
8009
+ async updateSourceHash(kbId, kbName, sourceId, hash, existingTags) {
8010
+ const client = await this.getClient();
8011
+ await client.updateKnowledgeBase({
8012
+ id: kbId,
8013
+ name: kbName,
8014
+ tags: {
8015
+ ...existingTags,
8016
+ [sourceTag(sourceId, "hash")]: hash,
8017
+ [sourceTag(sourceId, "lastupdatedat")]: new Date().toISOString()
8018
+ }
8019
+ });
8020
+ }
8021
+ computeConfigHash(config) {
8022
+ const sortedConfig = JSON.stringify(config, Object.keys(config).sort());
8023
+ return crypto4.createHash("sha256").update(sortedConfig).digest("hex");
8024
+ }
8025
+ async syncWebsiteSource(kbName, force) {
8026
+ const client = await this.getClient();
8027
+ const response = await client.createWorkflow({
8028
+ name: "builtin_knowledge_indexing",
8029
+ input: { kbName, force },
8030
+ status: "pending",
8031
+ timeoutAt: new Date(Date.now() + 180 * 60 * 1000).toISOString()
8032
+ });
8033
+ return { workflowId: response.workflow.id };
8034
+ }
8035
+ hasWebsiteSources(kbName) {
8036
+ const kbRef = this.project.knowledge.find((k) => k.definition.name === kbName);
8037
+ if (!kbRef)
8038
+ return false;
8039
+ return kbRef.definition.sources?.some((s) => DataSource.isWebsite(s)) || false;
8040
+ }
8041
+ getKBsWithWebsiteSources() {
8042
+ return this.project.knowledge.filter((k) => k.definition.sources?.some((s) => DataSource.isWebsite(s))).map((k) => k.definition.name);
8043
+ }
8044
+ async computeDirectorySourceHash(directoryPath, filterFn) {
8045
+ const fileHashes = await this.scanLocalFileHashes(directoryPath, filterFn);
8046
+ return this.computeContentHash(fileHashes);
8047
+ }
8048
+ async createSyncPlan() {
8049
+ const localKbs = this.getLocalKnowledgeBases();
8050
+ const remoteKbs = await this.listRemoteKnowledgeBases();
8051
+ const items = [];
8052
+ for (const kb of localKbs) {
8053
+ const kbRef = this.project.knowledge.find((k) => k.definition.name === kb.name);
8054
+ if (!kbRef)
8055
+ continue;
8056
+ const remoteKb = remoteKbs.find((r) => r.name === kb.name);
8057
+ if (!remoteKb) {
8058
+ const sources2 = (kbRef.definition.sources || []).map((source) => ({
8059
+ sourceId: source.id,
8060
+ sourceType: DataSource.isDirectory(source) ? "directory" : "website",
8061
+ needsSync: true,
8062
+ reason: "New KB"
8063
+ }));
8064
+ items.push({
8065
+ operation: "sync" /* Sync */,
8066
+ kb,
8067
+ reason: "Knowledge base not found remotely",
8068
+ needsCreation: true,
8069
+ sources: sources2
8070
+ });
8071
+ continue;
8072
+ }
8073
+ const sources = [];
8074
+ let hasChanges = false;
8075
+ for (const source of kbRef.definition.sources || []) {
8076
+ const remoteHash = this.getRemoteSourceHash(remoteKb, source.id);
8077
+ if (DataSource.isDirectory(source)) {
8078
+ const localHash = await this.computeDirectorySourceHash(source.directoryPath, source.filterFn);
8079
+ if (!remoteHash) {
8080
+ sources.push({
8081
+ sourceId: source.id,
8082
+ sourceType: "directory",
8083
+ needsSync: true,
8084
+ reason: "First-time sync"
8085
+ });
8086
+ hasChanges = true;
8087
+ } else if (localHash !== remoteHash) {
8088
+ const fileChanges = await this.detectDirectorySourceChanges(kb.name, source);
8089
+ sources.push({
8090
+ sourceId: source.id,
8091
+ sourceType: "directory",
8092
+ needsSync: true,
8093
+ reason: "Content changed",
8094
+ fileChanges
8095
+ });
8096
+ hasChanges = true;
8097
+ } else {
8098
+ sources.push({
8099
+ sourceId: source.id,
8100
+ sourceType: "directory",
8101
+ needsSync: false,
8102
+ reason: "No changes"
8103
+ });
8104
+ }
8105
+ } else if (DataSource.isWebsite(source)) {
8106
+ const config = source.getConfig();
8107
+ const localHash = this.computeConfigHash(config);
8108
+ if (!remoteHash) {
8109
+ sources.push({
8110
+ sourceId: source.id,
8111
+ sourceType: "website",
8112
+ needsSync: true,
8113
+ reason: "First-time crawl"
8114
+ });
8115
+ hasChanges = true;
8116
+ } else if (localHash !== remoteHash) {
8117
+ sources.push({
8118
+ sourceId: source.id,
8119
+ sourceType: "website",
8120
+ needsSync: true,
8121
+ reason: "Config changed - needs recrawl"
8122
+ });
8123
+ hasChanges = true;
8124
+ } else {
8125
+ sources.push({
8126
+ sourceId: source.id,
8127
+ sourceType: "website",
8128
+ needsSync: false,
8129
+ reason: "No config changes"
8130
+ });
8131
+ }
8132
+ }
8133
+ }
8134
+ const localSourceIds = (kbRef.definition.sources || []).map((s) => s.id);
8135
+ const orphaned = await this.getOrphanedSources(kb.name, localSourceIds);
8136
+ const orphanedSourceStatuses = orphaned.map((o) => ({
8137
+ sourceId: o.sourceId,
8138
+ fileCount: o.fileCount,
8139
+ willDelete: true
8140
+ }));
8141
+ const hasOrphanedSources = orphanedSourceStatuses.length > 0;
8142
+ items.push({
8143
+ operation: hasChanges || hasOrphanedSources ? "sync" /* Sync */ : "skip" /* Skip */,
8144
+ kb,
8145
+ reason: hasOrphanedSources ? hasChanges ? "Sources need sync and orphaned sources to remove" : "Orphaned sources to remove" : hasChanges ? "Sources need sync" : "No changes",
8146
+ sources,
8147
+ orphanedSources: hasOrphanedSources ? orphanedSourceStatuses : undefined
8148
+ });
8149
+ }
8150
+ const toSync = items.filter((i) => i.operation === "sync" /* Sync */).length;
8151
+ const toSkip = items.filter((i) => i.operation === "skip" /* Skip */).length;
8152
+ let sourcesToSync = 0;
8153
+ let sourcesToSkip = 0;
8154
+ let orphanedSourcesToDelete = 0;
8155
+ for (const item of items) {
8156
+ for (const source of item.sources || []) {
8157
+ if (source.needsSync) {
8158
+ sourcesToSync++;
8159
+ } else {
8160
+ sourcesToSkip++;
8161
+ }
8162
+ }
8163
+ orphanedSourcesToDelete += item.orphanedSources?.length || 0;
8164
+ }
8165
+ return {
8166
+ items,
8167
+ toSync,
8168
+ toSkip,
8169
+ hasChanges: toSync > 0,
8170
+ sourcesToSync,
8171
+ sourcesToSkip,
8172
+ orphanedSourcesToDelete
8173
+ };
8174
+ }
8175
+ async detectDirectorySourceChanges(kbName, source) {
8176
+ const client = await this.getClient();
8177
+ const added = [];
8178
+ const deleted = [];
8179
+ const modified = [];
8180
+ const tags = {
8181
+ [WellKnownTags.KNOWLEDGE]: "true",
8182
+ [WellKnownTags.KNOWLEDGE_SOURCE_ID]: source.id,
8183
+ [WellKnownTags.KNOWLEDGE_BASE_NAME]: kbName
8184
+ };
8185
+ const remoteFiles = await this.listExistingFiles(client, tags);
8186
+ const remoteHashes = {};
8187
+ for (const file of remoteFiles) {
8188
+ if (isFileMetadata(file.metadata)) {
8189
+ remoteHashes[file.metadata.relPath] = file.metadata.hash;
8190
+ }
8191
+ }
8192
+ const localHashes = await this.scanLocalFileHashes(source.directoryPath, source.filterFn);
8193
+ for (const [relPath, hash] of Object.entries(localHashes)) {
8194
+ if (!remoteHashes[relPath]) {
8195
+ added.push(relPath);
8196
+ } else if (remoteHashes[relPath] !== hash) {
8197
+ modified.push(relPath);
8198
+ }
8199
+ }
8200
+ for (const relPath of Object.keys(remoteHashes)) {
8201
+ if (!localHashes[relPath]) {
8202
+ deleted.push(relPath);
8203
+ }
8204
+ }
8205
+ return { added, deleted, modified };
8206
+ }
8207
+ async scanLocalFileHashes(directoryPath, filterFn) {
8208
+ const projectDir = this.project.path;
8209
+ const directory = path31.resolve(projectDir, directoryPath);
8210
+ if (this.fileHashCache.has(directory)) {
8211
+ return this.fileHashCache.get(directory);
8212
+ }
8213
+ const files = glob.sync(directory + "/**/*.*", { absolute: true, nodir: true }).filter((file) => !filterFn || filterFn(file));
8214
+ const hashes = {};
8215
+ for (const file of files) {
8216
+ const relPath = path31.relative(directory, file);
8217
+ const content = await fs16.readFile(file);
8218
+ hashes[relPath] = crypto4.createHash("sha256").update(content).digest("hex");
8219
+ }
8220
+ this.fileHashCache.set(directory, hashes);
8221
+ return hashes;
8222
+ }
8223
+ async executeSync(plan, options = {}) {
8224
+ this.assertBotId("sync knowledge bases");
8225
+ const client = await this.getClient();
8226
+ const result = {
8227
+ synced: [],
8228
+ skipped: [],
8229
+ failed: [],
8230
+ websiteSyncs: []
8231
+ };
8232
+ for (const item of plan.items) {
8233
+ if (item.operation === "skip" /* Skip */ && !options.force) {
8234
+ result.skipped.push({ name: item.kb.name, reason: item.reason });
8235
+ continue;
8236
+ }
8237
+ try {
8238
+ console.log(`Syncing KB "${item.kb.name}"...`);
8239
+ const kbRef = this.project.knowledge.find((k) => k.definition.name === item.kb.name);
8240
+ if (!kbRef) {
8241
+ throw new Error(`KB "${item.kb.name}" not found in project`);
8242
+ }
8243
+ let remoteKb = await this.findRemoteKB(item.kb.name);
8244
+ if (!remoteKb) {
8245
+ console.log(` Creating KB "${item.kb.name}"...`);
8246
+ remoteKb = await this.createKnowledgeBase(item.kb.name);
8247
+ }
8248
+ if (item.orphanedSources && item.orphanedSources.length > 0) {
8249
+ console.log(` Removing ${item.orphanedSources.length} orphaned source(s)...`);
8250
+ const deletedSourceIds = [];
8251
+ for (const orphaned of item.orphanedSources) {
8252
+ const { deletedFiles, errors } = await this.deleteOrphanedSource(item.kb.name, orphaned.sourceId);
8253
+ console.log(` ✕ ${orphaned.sourceId}: ${deletedFiles} files deleted`);
8254
+ if (errors.length === 0) {
8255
+ deletedSourceIds.push(orphaned.sourceId);
8256
+ } else {
8257
+ console.warn(` Warning: ${errors.length} errors during deletion`);
8258
+ }
8259
+ }
8260
+ if (deletedSourceIds.length > 0) {
8261
+ const freshKb = await this.findRemoteKB(item.kb.name);
8262
+ if (freshKb) {
8263
+ await this.cleanupSourceTags(freshKb.id, item.kb.name, deletedSourceIds, freshKb.tags || {});
8264
+ remoteKb = freshKb;
8265
+ }
8266
+ }
8267
+ }
8268
+ const syncOutput = {
8269
+ processed: 0,
8270
+ added: [],
8271
+ updated: [],
8272
+ deleted: [],
8273
+ errors: []
8274
+ };
8275
+ const sourcesToSync = options.force ? item.sources || [] : (item.sources || []).filter((s) => s.needsSync);
8276
+ const directorySourcesToSync = sourcesToSync.filter((s) => s.sourceType === "directory");
8277
+ const websiteSourcesToSync = sourcesToSync.filter((s) => s.sourceType === "website");
8278
+ for (const sourceStatus of directorySourcesToSync) {
8279
+ const source = kbRef.definition.sources?.find((s) => s.id === sourceStatus.sourceId);
8280
+ if (!source || !DataSource.isDirectory(source))
8281
+ continue;
8282
+ console.log(` Syncing directory source "${source.id}"...`);
8283
+ const sourceOutput = await this.syncDirectorySource(client, item.kb.name, source.id, source.directoryPath, source.filterFn, options.force || false);
8284
+ syncOutput.processed += sourceOutput.processed;
8285
+ syncOutput.added.push(...sourceOutput.added);
8286
+ syncOutput.updated.push(...sourceOutput.updated);
8287
+ syncOutput.deleted.push(...sourceOutput.deleted);
8288
+ syncOutput.errors.push(...sourceOutput.errors);
8289
+ const sourceHash = await this.computeDirectorySourceHash(source.directoryPath, source.filterFn);
8290
+ await this.updateSourceHash(remoteKb.id, item.kb.name, source.id, sourceHash, remoteKb.tags);
8291
+ }
8292
+ if (websiteSourcesToSync.length > 0) {
8293
+ try {
8294
+ console.log(` Triggering website sync workflow for ${websiteSourcesToSync.length} source(s)...`);
8295
+ const { workflowId } = await this.syncWebsiteSource(item.kb.name, options.force || false);
8296
+ result.websiteSyncs.push({ kbName: item.kb.name, workflowId });
8297
+ for (const sourceStatus of websiteSourcesToSync) {
8298
+ const source = kbRef.definition.sources?.find((s) => s.id === sourceStatus.sourceId);
8299
+ if (!source || !DataSource.isWebsite(source))
8300
+ continue;
8301
+ const config = source.getConfig();
8302
+ const configHash = this.computeConfigHash(config);
8303
+ await this.updateSourceHash(remoteKb.id, item.kb.name, source.id, configHash, remoteKb.tags);
8304
+ }
8305
+ } catch (error) {
8306
+ const errorMsg = error instanceof Error ? error.message : String(error);
8307
+ console.warn(` Warning: Could not trigger website sync: ${errorMsg}`);
8308
+ }
8309
+ }
8310
+ result.synced.push({ name: item.kb.name, result: syncOutput });
8311
+ } catch (error) {
8312
+ const errorMessage = this.formatError(error);
8313
+ console.error(`Failed to sync KB "${item.kb.name}": ${errorMessage}`);
8314
+ result.failed.push({ name: item.kb.name, error: errorMessage });
8315
+ }
8316
+ }
8317
+ return result;
8318
+ }
8319
+ async syncDirectorySource(client, kbName, sourceId, directoryPath, filterFn, force) {
8320
+ const projectDir = this.project.path;
8321
+ const directory = path31.resolve(projectDir, directoryPath);
8322
+ if (!directory.startsWith(projectDir)) {
8323
+ throw new Error("Directory path must be within the agent's directory");
8324
+ }
8325
+ const tags = {
8326
+ [WellKnownTags.KNOWLEDGE]: "true",
8327
+ [WellKnownTags.KNOWLEDGE_SOURCE_ID]: sourceId,
8328
+ [WellKnownTags.KNOWLEDGE_SOURCE_TYPE]: "directory",
8329
+ [WellKnownTags.KNOWLEDGE_BASE_NAME]: kbName
8330
+ };
8331
+ let allFiles = glob.sync(directory + "/**/*.*", { absolute: true, nodir: true }).filter((file) => {
8332
+ if (filterFn) {
8333
+ try {
8334
+ return filterFn(file);
8335
+ } catch {
8336
+ return false;
8337
+ }
8338
+ }
8339
+ return true;
8340
+ }).map((f) => ({
8341
+ abs: f,
8342
+ rel: path31.relative(directory, f),
8343
+ name: path31.basename(f)
8344
+ }));
8345
+ console.log(` Found ${allFiles.length} files in ${directoryPath}`);
8346
+ const cachedHashes = await this.scanLocalFileHashes(directoryPath, filterFn);
8347
+ const existingFiles = await this.listExistingFiles(client, tags);
8348
+ console.log(` Found ${existingFiles.length} existing files in Botpress`);
8349
+ const toRemove = existingFiles.filter((f) => !allFiles.find((af) => af.rel === f.metadata?.relPath));
8350
+ const toAdd = allFiles.filter((af) => !existingFiles.find((f) => f.metadata?.relPath === af.rel));
8351
+ const toUpdate = allFiles.filter((af) => existingFiles.find((f) => f.metadata?.relPath === af.rel));
8352
+ const output = {
8353
+ processed: allFiles.length,
8354
+ added: [],
8355
+ updated: [],
8356
+ deleted: [],
8357
+ errors: []
8358
+ };
8359
+ for (const file of toRemove) {
8360
+ try {
8361
+ await client.deleteFile({ id: file.id });
8362
+ output.deleted.push({
8363
+ file: file.id,
8364
+ name: file.key,
8365
+ hash: file.metadata?.hash ?? "",
8366
+ size: file.size ?? -1
8367
+ });
8368
+ } catch (error) {
8369
+ output.errors.push({
8370
+ file: file.id,
8371
+ error: error instanceof Error ? error.message : String(error)
8372
+ });
8373
+ }
8374
+ }
8375
+ for (const local of toAdd) {
8376
+ const result = await this.upsertFile(client, local, sourceId, tags, force, cachedHashes[local.rel]);
8377
+ if (result) {
8378
+ output.added.push(result);
8379
+ }
8380
+ }
8381
+ for (const local of toUpdate) {
8382
+ const result = await this.upsertFile(client, local, sourceId, tags, force, cachedHashes[local.rel]);
8383
+ if (result) {
8384
+ output.updated.push(result);
8385
+ }
8386
+ }
8387
+ console.log(` Synced: ${output.added.length} added, ${output.updated.length} updated, ${output.deleted.length} deleted`);
8388
+ return output;
8389
+ }
8390
+ async listExistingFiles(client, tags) {
8391
+ const files = [];
8392
+ let nextToken;
8393
+ do {
8394
+ const response = await client.listFiles({ tags, nextToken });
8395
+ files.push(...response.files.map((f) => ({
8396
+ id: f.id,
8397
+ key: f.key,
8398
+ size: f.size,
8399
+ metadata: isFileMetadata(f.metadata) ? f.metadata : undefined
8400
+ })));
8401
+ nextToken = response.meta.nextToken;
8402
+ } while (nextToken);
8403
+ return files;
8404
+ }
8405
+ async upsertFile(client, local, sourceId, tags, force, cachedHash) {
8406
+ const key = `data_source://directory/${sourceId}/${local.rel}`;
8407
+ const content = await fs16.readFile(local.abs);
8408
+ const hash = cachedHash ?? crypto4.createHash("sha256").update(content).digest("hex");
8409
+ try {
8410
+ const { file } = await client.getFile({ id: key });
8411
+ if (!force && isFileMetadata(file.metadata) && file.metadata.hash === hash) {
8412
+ return null;
8413
+ }
8414
+ } catch {}
8415
+ const title = path31.basename(local.name, path31.extname(local.name));
8416
+ const metadata = {
8417
+ hash,
8418
+ sourceId,
8419
+ sourceType: "directory",
8420
+ relPath: local.rel,
8421
+ [WellKnownMetadata.TITLE]: title
8422
+ };
8423
+ const uploaded = await client.uploadFile({
8424
+ key,
8425
+ content,
8426
+ accessPolicies: [],
8427
+ tags,
8428
+ index: true,
8429
+ metadata
8430
+ });
8431
+ return {
8432
+ file: uploaded.file.id,
8433
+ hash,
8434
+ name: key,
8435
+ size: uploaded.file.size ?? -1
8436
+ };
8437
+ }
8438
+ }
8439
+ // src/knowledge/sync-formatter.ts
8440
+ import { readFileSync } from "fs";
8441
+ import { join as join7 } from "path";
8442
+ var getAdkVersion = () => {
8443
+ try {
8444
+ const packageJson = require_package();
8445
+ return packageJson.version;
8446
+ } catch {
8447
+ try {
8448
+ const adkPackagePath = join7(process.cwd(), "node_modules/@botpress/adk/package.json");
8449
+ const pkg = JSON.parse(readFileSync(adkPackagePath, "utf-8"));
8450
+ return pkg.version;
8451
+ } catch {
8452
+ return "unknown";
8453
+ }
8454
+ }
8455
+ };
8456
+ function pluralize(count, word) {
8457
+ return `${count} ${word}${count !== 1 ? "s" : ""}`;
8458
+ }
8459
+
8460
+ class KBSyncFormatter {
8461
+ static format(plan, kbsWithWebsites = []) {
8462
+ const sections = [];
8463
+ sections.push("");
8464
+ sections.push(" ▄▀█ █▀▄ █▄▀ Botpress ADK");
8465
+ sections.push(` █▀█ █▄▀ █░█ v${getAdkVersion()}`);
8466
+ sections.push("");
8467
+ sections.push("Knowledge Base Sync");
8468
+ sections.push("");
8469
+ if (!plan.hasChanges) {
8470
+ sections.push("✓ All knowledge bases are up to date.");
8471
+ sections.push("");
8472
+ return sections.join(`
8473
+ `);
8474
+ }
8475
+ const kbsToSync = plan.items.filter((i) => i.operation === "sync" /* Sync */);
8476
+ const kbsToSkip = plan.items.filter((i) => i.operation === "skip" /* Skip */);
8477
+ if (kbsToSync.length > 0) {
8478
+ sections.push(`Knowledge Bases to Sync:
8479
+ `);
8480
+ for (const item of kbsToSync) {
8481
+ const icon = item.needsCreation ? "+" : "~";
8482
+ const action = item.needsCreation ? "CREATE" : "UPDATE";
8483
+ sections.push(` ${icon} ${item.kb.name}`);
8484
+ sections.push(` Action: ${action}`);
8485
+ sections.push(` Reason: ${item.reason}`);
8486
+ if (item.sources && item.sources.length > 0) {
8487
+ sections.push(" Sources:");
8488
+ for (const source of item.sources) {
8489
+ if (source.needsSync) {
8490
+ const typeLabel = source.sourceType === "directory" ? "\uD83D\uDCC1" : "\uD83C\uDF10";
8491
+ sections.push(` ${typeLabel} ${source.sourceId} (${source.sourceType})`);
8492
+ sections.push(` ${source.reason}`);
8493
+ if (source.fileChanges) {
8494
+ const { added, modified, deleted } = source.fileChanges;
8495
+ if (added.length > 0) {
8496
+ sections.push(` + ${pluralize(added.length, "file")} to add`);
8497
+ }
8498
+ if (modified.length > 0) {
8499
+ sections.push(` ~ ${pluralize(modified.length, "file")} to update`);
8500
+ }
8501
+ if (deleted.length > 0) {
8502
+ sections.push(` - ${pluralize(deleted.length, "file")} to delete`);
8503
+ }
8504
+ }
8505
+ }
8506
+ }
8507
+ }
8508
+ sections.push("");
8509
+ }
8510
+ }
8511
+ if (kbsToSkip.length > 0) {
8512
+ sections.push(`Knowledge Bases Already Up to Date:
8513
+ `);
8514
+ for (const item of kbsToSkip) {
8515
+ sections.push(` ✓ ${item.kb.name}`);
8516
+ }
8517
+ sections.push("");
8518
+ }
8519
+ if (kbsWithWebsites.length > 0) {
8520
+ sections.push("⚠️ Website Sources");
8521
+ sections.push(" Website crawling will run asynchronously in the bot runtime.");
8522
+ sections.push(` KBs with websites: ${kbsWithWebsites.join(", ")}`);
8523
+ sections.push("");
8524
+ }
8525
+ sections.push(`Summary of Actions
8526
+ `);
8527
+ if (plan.toSync > 0) {
8528
+ sections.push(` • Sync: ${pluralize(plan.toSync, "knowledge base")}`);
8529
+ }
8530
+ if (plan.toSkip > 0) {
8531
+ sections.push(` • Skip: ${pluralize(plan.toSkip, "knowledge base")} (up to date)`);
8532
+ }
8533
+ sections.push(` • Sources: ${plan.sourcesToSync} to sync, ${plan.sourcesToSkip} up to date`);
8534
+ sections.push("");
8535
+ return sections.join(`
8536
+ `);
8537
+ }
8538
+ }
7817
8539
  // src/file-watcher/watcher.ts
7818
8540
  import { watch as watch2, readdirSync as readdirSync2 } from "fs";
7819
8541
  import { EventEmitter as EventEmitter3 } from "events";
7820
- import { join as join7, relative as relative3 } from "path";
8542
+ import { join as join8, relative as relative3 } from "path";
7821
8543
  import { existsSync as existsSync6 } from "fs";
7822
8544
 
7823
8545
  class FileWatcher2 extends EventEmitter3 {
@@ -7835,12 +8557,12 @@ class FileWatcher2 extends EventEmitter3 {
7835
8557
  start() {
7836
8558
  const rootFiles = ["package.json", "agent.json", "agent.config.ts"];
7837
8559
  for (const file of rootFiles) {
7838
- const filePath = join7(this.projectPath, file);
8560
+ const filePath = join8(this.projectPath, file);
7839
8561
  if (existsSync6(filePath)) {
7840
8562
  this.watchFile(filePath);
7841
8563
  }
7842
8564
  }
7843
- const srcPath = join7(this.projectPath, "src");
8565
+ const srcPath = join8(this.projectPath, "src");
7844
8566
  if (existsSync6(srcPath)) {
7845
8567
  this.initializeDirectoryState(srcPath);
7846
8568
  this.watchDirectory(srcPath);
@@ -7850,7 +8572,7 @@ class FileWatcher2 extends EventEmitter3 {
7850
8572
  try {
7851
8573
  const entries = readdirSync2(dirPath, { withFileTypes: true });
7852
8574
  for (const entry of entries) {
7853
- const fullPath = join7(dirPath, entry.name);
8575
+ const fullPath = join8(dirPath, entry.name);
7854
8576
  if (entry.isDirectory()) {
7855
8577
  this.initializeDirectoryState(fullPath);
7856
8578
  } else if (entry.isFile()) {
@@ -7880,7 +8602,7 @@ class FileWatcher2 extends EventEmitter3 {
7880
8602
  try {
7881
8603
  const watcher = watch2(dirPath, { recursive: true, persistent: true }, (_eventType, filename) => {
7882
8604
  if (filename) {
7883
- const fullPath = join7(dirPath, filename);
8605
+ const fullPath = join8(dirPath, filename);
7884
8606
  this.handleFileChange(fullPath);
7885
8607
  }
7886
8608
  });
@@ -7954,14 +8676,14 @@ class FileWatcher2 extends EventEmitter3 {
7954
8676
  }
7955
8677
  }
7956
8678
  // src/preflight/checker.ts
7957
- import { Client as Client12 } from "@botpress/client";
7958
- import path31 from "path";
8679
+ import { Client as Client13 } from "@botpress/client";
8680
+ import path32 from "path";
7959
8681
 
7960
8682
  // src/preflight/types.ts
7961
8683
  function hasIntegrationChanges(integrations) {
7962
8684
  return integrations.toInstall.length > 0 || integrations.toRemove.length > 0 || integrations.toUpdate.length > 0;
7963
8685
  }
7964
- function pluralize(count, word) {
8686
+ function pluralize2(count, word) {
7965
8687
  return `${count} ${word}${count > 1 ? "s" : ""}`;
7966
8688
  }
7967
8689
 
@@ -8012,16 +8734,16 @@ class AgentConfigSyncManager {
8012
8734
  }
8013
8735
 
8014
8736
  // src/preflight/formatter.ts
8015
- import { readFileSync } from "fs";
8016
- import { join as join8 } from "path";
8017
- var getAdkVersion = () => {
8737
+ import { readFileSync as readFileSync2 } from "fs";
8738
+ import { join as join9 } from "path";
8739
+ var getAdkVersion2 = () => {
8018
8740
  try {
8019
8741
  const packageJson = require_package();
8020
8742
  return packageJson.version;
8021
8743
  } catch {
8022
8744
  try {
8023
- const adkPackagePath = join8(process.cwd(), "node_modules/@botpress/adk/package.json");
8024
- const pkg = JSON.parse(readFileSync(adkPackagePath, "utf-8"));
8745
+ const adkPackagePath = join9(process.cwd(), "node_modules/@botpress/adk/package.json");
8746
+ const pkg = JSON.parse(readFileSync2(adkPackagePath, "utf-8"));
8025
8747
  return pkg.version;
8026
8748
  } catch {
8027
8749
  return "unknown";
@@ -8034,7 +8756,7 @@ class PreflightFormatter {
8034
8756
  const sections = [];
8035
8757
  sections.push("");
8036
8758
  sections.push(" ▄▀█ █▀▄ █▄▀ Botpress ADK");
8037
- sections.push(` █▀█ █▄▀ █░█ v${getAdkVersion()}`);
8759
+ sections.push(` █▀█ █▄▀ █░█ v${getAdkVersion2()}`);
8038
8760
  sections.push("");
8039
8761
  sections.push("Running preflight checks...");
8040
8762
  sections.push("");
@@ -8108,16 +8830,16 @@ class PreflightFormatter {
8108
8830
  const update = result.integrations.toUpdate.length;
8109
8831
  const agentConfig = result.agentConfig.length;
8110
8832
  if (install > 0) {
8111
- sections.push(` • Install: ${pluralize(install, "integration")}`);
8833
+ sections.push(` • Install: ${pluralize2(install, "integration")}`);
8112
8834
  }
8113
8835
  if (remove > 0) {
8114
- sections.push(` • Remove: ${pluralize(remove, "integration")}`);
8836
+ sections.push(` • Remove: ${pluralize2(remove, "integration")}`);
8115
8837
  }
8116
8838
  if (update > 0) {
8117
- sections.push(` • Update: ${pluralize(update, "integration")}`);
8839
+ sections.push(` • Update: ${pluralize2(update, "integration")}`);
8118
8840
  }
8119
8841
  if (agentConfig > 0) {
8120
- sections.push(` • Agent config: ${pluralize(agentConfig, "change")}`);
8842
+ sections.push(` • Agent config: ${pluralize2(agentConfig, "change")}`);
8121
8843
  }
8122
8844
  sections.push("");
8123
8845
  return sections.join(`
@@ -8169,7 +8891,7 @@ class PreflightChecker {
8169
8891
  if (!workspaceId) {
8170
8892
  throw new Error('No workspace ID found. Please login with "adk login"');
8171
8893
  }
8172
- this.client = new Client12({
8894
+ this.client = new Client13({
8173
8895
  token: credentials.token,
8174
8896
  apiUrl: credentials.apiUrl,
8175
8897
  workspaceId,
@@ -8295,16 +9017,16 @@ class PreflightChecker {
8295
9017
  const integrationSyncer = new IntegrationSyncManager(this.projectPath);
8296
9018
  const syncResult = await integrationSyncer.applySyncPlan(integrationResults, botId);
8297
9019
  if (syncResult.installed > 0) {
8298
- options?.onSuccess?.(`Installed ${pluralize(syncResult.installed, "integration")}`);
9020
+ options?.onSuccess?.(`Installed ${pluralize2(syncResult.installed, "integration")}`);
8299
9021
  }
8300
9022
  if (syncResult.updated > 0) {
8301
- options?.onSuccess?.(`Updated ${pluralize(syncResult.updated, "integration")}`);
9023
+ options?.onSuccess?.(`Updated ${pluralize2(syncResult.updated, "integration")}`);
8302
9024
  }
8303
9025
  if (syncResult.removed > 0) {
8304
- options?.onSuccess?.(`Removed ${pluralize(syncResult.removed, "integration")}`);
9026
+ options?.onSuccess?.(`Removed ${pluralize2(syncResult.removed, "integration")}`);
8305
9027
  }
8306
9028
  if (syncResult.failed > 0) {
8307
- options?.onError?.(`Failed to sync ${pluralize(syncResult.failed, "integration")}`);
9029
+ options?.onError?.(`Failed to sync ${pluralize2(syncResult.failed, "integration")}`);
8308
9030
  for (const { alias, error } of syncResult.errors) {
8309
9031
  options?.onError?.(` ${alias}: ${error}`);
8310
9032
  }
@@ -8317,7 +9039,7 @@ class PreflightChecker {
8317
9039
  options?.onProgress?.("Regenerating bot project...");
8318
9040
  await generateBotProject({
8319
9041
  projectPath: this.projectPath,
8320
- outputPath: path31.join(this.projectPath, ".adk", "bot")
9042
+ outputPath: path32.join(this.projectPath, ".adk", "bot")
8321
9043
  });
8322
9044
  options?.onSuccess?.("Bot project regenerated");
8323
9045
  }
@@ -8344,6 +9066,9 @@ export {
8344
9066
  ProjectState,
8345
9067
  PreflightFormatter,
8346
9068
  PreflightChecker,
9069
+ KnowledgeManager,
9070
+ KBSyncOperation,
9071
+ KBSyncFormatter,
8347
9072
  InterfaceParser,
8348
9073
  InterfaceOperations,
8349
9074
  InterfaceManager,
@@ -8370,5 +9095,5 @@ export {
8370
9095
  AgentProject
8371
9096
  };
8372
9097
 
8373
- //# debugId=903A2AC825E269AB64756E2164756E21
9098
+ //# debugId=D1FBF4E6E55AF89264756E2164756E21
8374
9099
  //# sourceMappingURL=index.js.map