@zerowidth/workbench-sdk 2.0.0-alpha.0 → 2.0.0

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.
Files changed (37) hide show
  1. package/nodes/combine-document-chunks/combine-document-chunks.config.json +8 -0
  2. package/nodes/combine-document-chunks/combine-document-chunks.process.js +9 -2
  3. package/nodes/describe-tables/describe-tables.config.json +59 -0
  4. package/nodes/describe-tables/describe-tables.process.js +40 -0
  5. package/nodes/expand-chunk-context/expand-chunk-context.config.json +87 -0
  6. package/nodes/expand-chunk-context/expand-chunk-context.process.js +34 -0
  7. package/nodes/find-entity-path/find-entity-path.config.json +73 -0
  8. package/nodes/find-entity-path/find-entity-path.process.js +51 -0
  9. package/nodes/get-chunk-by-index/get-chunk-by-index.config.json +8 -0
  10. package/nodes/get-chunk-by-index/get-chunk-by-index.process.js +9 -2
  11. package/nodes/get-entity-neighbors/get-entity-neighbors.config.json +81 -0
  12. package/nodes/get-entity-neighbors/get-entity-neighbors.process.js +51 -0
  13. package/nodes/keyword-search/keyword-search.config.json +67 -0
  14. package/nodes/keyword-search/keyword-search.process.js +45 -0
  15. package/nodes/knowledge-base/knowledge-base.config.json +32 -0
  16. package/nodes/knowledge-base/knowledge-base.process.js +15 -0
  17. package/nodes/list-documents/list-documents.config.json +60 -0
  18. package/nodes/list-documents/list-documents.process.js +25 -0
  19. package/nodes/list-entities/list-entities.config.json +74 -0
  20. package/nodes/list-entities/list-entities.process.js +25 -0
  21. package/nodes/query-knowledge-base/query-knowledge-base.config.json +14 -7
  22. package/nodes/query-knowledge-base/query-knowledge-base.process.js +25 -5
  23. package/nodes/read-chunks/read-chunks.config.json +79 -0
  24. package/nodes/read-chunks/read-chunks.process.js +49 -0
  25. package/nodes/remote-mcp-tool/remote-mcp-tool.config.json +1 -0
  26. package/nodes/semantic-search/semantic-search.config.json +9 -1
  27. package/nodes/semantic-search/semantic-search.process.js +41 -18
  28. package/nodes/tool/tool.config.json +1 -0
  29. package/package.json +12 -7
  30. package/src/index.js +76 -9
  31. package/src/integrations/knowledge-base-interface.js +49 -0
  32. package/src/integrations/sqlite.js +535 -83
  33. package/src/types/knowledge_base.json +9 -0
  34. package/src/utilities/loaders.js +67 -11
  35. package/src/utilities/typers.js +15 -17
  36. package/src/utilities/validators.js +16 -1
  37. package/types/knowledge_base.json +9 -0
package/src/index.js CHANGED
@@ -364,6 +364,21 @@ export default class Workbench {
364
364
  id: node.id
365
365
  };
366
366
 
367
+ // Config-only nodes (remote-mcp-tool) have no process function —
368
+ // they exist for the plugin loop, not direct execution. If one
369
+ // lands in the queue anyway (e.g. a dangling MCP node with no
370
+ // plugin link), no-op instead of crashing the whole run.
371
+ if (typeof nodeDefinition.process !== 'function') {
372
+ this.logDebug(`Node [${node.id}] (${node.type}) is config-only; skipping direct execution.`);
373
+ const endDate = new Date();
374
+ timelineEntry.outputs = {};
375
+ timelineEntry.endTime = endDate.toISOString();
376
+ timelineEntry.durationMs = endDate - startDate;
377
+ timelineEntry.status = 'success';
378
+ this.timeline.push(timelineEntry);
379
+ return {};
380
+ }
381
+
367
382
  const outputs = await this._raceAbort(
368
383
  nodeDefinition.process({inputs, settings, config: this.config, nodeConfig})
369
384
  );
@@ -1077,7 +1092,21 @@ export default class Workbench {
1077
1092
  await this.config.integrations.sqlite.disconnect();
1078
1093
  delete this.config.integrations.sqlite;
1079
1094
  }
1080
-
1095
+
1096
+ // Clean up node-level knowledge base integrations (keyed knowledgeBase:<uuid>).
1097
+ if (this.config.integrations) {
1098
+ for (const key of Object.keys(this.config.integrations)) {
1099
+ if (!key.startsWith('knowledgeBase:')) continue;
1100
+ this.logDebug(`Cleaning up node-level knowledge base integration ${key}...`);
1101
+ try {
1102
+ await this.config.integrations[key].disconnect();
1103
+ } catch (err) {
1104
+ this.logDebug(`Failed to disconnect ${key}: ${err.message}`);
1105
+ }
1106
+ delete this.config.integrations[key];
1107
+ }
1108
+ }
1109
+
1081
1110
  // Clean up any imported engines that were created
1082
1111
  // These are stored in the cache when import nodes are processed
1083
1112
  const rawStore = this.cache.getRawStore();
@@ -2464,6 +2493,15 @@ export default class Workbench {
2464
2493
  * @returns {boolean} True if the node is a plugin or macro
2465
2494
  */
2466
2495
  isLocalNodePlugin(node) {
2496
+ // Remote MCP tool nodes and manual tool nodes carry is_plugin —
2497
+ // the flag drives shared plugin BEHAVIOR (entry-node exclusion,
2498
+ // propagation skip, the canvas's plugin drag-and-dock UX) — but
2499
+ // neither is a LOCAL plugin: MCP tools load their schemas from
2500
+ // the remote server (isRemoteMCPTool branch) and manual tools
2501
+ // are caller-executed pass-throughs (isManualToolNode branch).
2502
+ // Classifying either as local would misroute them in the plugin
2503
+ // loop (bogus single-tool schema / unwanted process execution).
2504
+ if (isRemoteMCPTool(node) || isManualToolNode(node)) return false;
2467
2505
  const thisNodeConfig = this.nodes[node.type]?.config || {};
2468
2506
  return thisNodeConfig.is_plugin || thisNodeConfig.is_macro;
2469
2507
  }
@@ -2879,21 +2917,44 @@ export default class Workbench {
2879
2917
  * @private
2880
2918
  */
2881
2919
  _trackKnowledgeFiles() {
2882
- // Track main flow's knowledge base file
2883
- if (this.flow.knowledgeDbPath) {
2884
- this.trackKnowledgeFile(this.flow.knowledgeDbPath);
2885
- }
2920
+ // Host-provided knowledge db paths are HOST-OWNED: the engine must
2921
+ // never delete them unless the host explicitly opts in (hosted
2922
+ // runners that hand the engine per-run temp copies set
2923
+ // config.knowledgeBase.cleanupDbFiles = true). The engine's own
2924
+ // archive extractions live under ./.temp and are always cleaned.
2925
+ const optIn = this.config?.knowledgeBase?.cleanupDbFiles === true;
2926
+ const track = (filePath) => {
2927
+ if (!filePath) return;
2928
+ if (optIn || this._isEngineTempPath(filePath)) {
2929
+ this.trackKnowledgeFile(filePath);
2930
+ }
2931
+ };
2932
+
2933
+ track(this.flow.knowledgeDbPath);
2886
2934
 
2887
- // Track import knowledge base files
2888
2935
  if (this.flow.imports && Array.isArray(this.flow.imports)) {
2889
2936
  for (const importDef of this.flow.imports) {
2890
- if (importDef.knowledgeDbPath) {
2891
- this.trackKnowledgeFile(importDef.knowledgeDbPath);
2892
- }
2937
+ track(importDef.knowledgeDbPath);
2938
+ }
2939
+ }
2940
+
2941
+ if (this.flow.knowledgeDbPaths && typeof this.flow.knowledgeDbPaths === 'object') {
2942
+ for (const dbPath of Object.values(this.flow.knowledgeDbPaths)) {
2943
+ track(dbPath);
2893
2944
  }
2894
2945
  }
2895
2946
  }
2896
2947
 
2948
+ /** Paths inside the engine's own ./.temp extraction dir. @private */
2949
+ _isEngineTempPath(filePath) {
2950
+ try {
2951
+ const tempDir = path.resolve(process.cwd(), '.temp') + path.sep;
2952
+ return path.resolve(filePath).startsWith(tempDir);
2953
+ } catch {
2954
+ return false;
2955
+ }
2956
+ }
2957
+
2897
2958
  /**
2898
2959
  * Clean up temporary knowledge base files for this specific engine instance
2899
2960
  * @private
@@ -2996,3 +3057,9 @@ export default class Workbench {
2996
3057
  }
2997
3058
  }
2998
3059
  }
3060
+
3061
+ // Base class for bring-your-own knowledge bases: implement its methods
3062
+ // over your own store (SQL database, vector store, HTTP service) and
3063
+ // pass an instance via `config.knowledgeBase.instance` (flow-global)
3064
+ // or `config.knowledgeBase.instances[kbUuid]` (per Knowledge Base node).
3065
+ export { KnowledgeBaseInterface } from './integrations/knowledge-base-interface.js';
@@ -116,4 +116,53 @@ export class KnowledgeBaseInterface {
116
116
  async disconnect() {
117
117
  throw new Error('disconnect() method must be implemented by knowledge base integration');
118
118
  }
119
+
120
+ /**
121
+ * Keyword (exact-match) search over chunk content.
122
+ * @param {string} query @param {Object} options - { limit, document_id }
123
+ * @returns {Promise<Array>} chunk-shaped rows
124
+ */
125
+ async keywordSearch(query, options = {}) {
126
+ throw new Error('keywordSearch() method must be implemented by knowledge base integration');
127
+ }
128
+
129
+ /** List documents. @param {Object} options - { limit, offset } */
130
+ async listDocuments(options = {}) {
131
+ throw new Error('listDocuments() method must be implemented by knowledge base integration');
132
+ }
133
+
134
+ /** Page one document's chunks. @param {Object} options - { startIndex, limit } */
135
+ async getChunks(documentId, options = {}) {
136
+ throw new Error('getChunks() method must be implemented by knowledge base integration');
137
+ }
138
+
139
+ /** One chunk plus its neighbors. @param {Object} options - { before, after } */
140
+ async getChunkWindow(documentId, chunkIndex, options = {}) {
141
+ throw new Error('getChunkWindow() method must be implemented by knowledge base integration');
142
+ }
143
+
144
+ /** Tabular registry: table schemas + sample rows. @param {Object} options - { sampleLimit } */
145
+ async listTables(options = {}) {
146
+ throw new Error('listTables() method must be implemented by knowledge base integration');
147
+ }
148
+
149
+ /** Graph: entities, most-connected first. @param {Object} options - { limit, offset, type, search } */
150
+ async listEntities(options = {}) {
151
+ throw new Error('listEntities() method must be implemented by knowledge base integration');
152
+ }
153
+
154
+ /** Graph: resolve an entity by id or case-insensitive name. */
155
+ async getEntity(ref) {
156
+ throw new Error('getEntity() method must be implemented by knowledge base integration');
157
+ }
158
+
159
+ /** Graph: relations touching an entity. @param {Object} options - { direction, relation_type, limit } */
160
+ async getNeighbors(entityRef, options = {}) {
161
+ throw new Error('getNeighbors() method must be implemented by knowledge base integration');
162
+ }
163
+
164
+ /** Graph: shortest relation chain between two entities. @param {Object} options - { max_depth } */
165
+ async findPath(fromRef, toRef, options = {}) {
166
+ throw new Error('findPath() method must be implemented by knowledge base integration');
167
+ }
119
168
  }