@kadoa/mcp 0.5.16 → 0.5.18

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 (3) hide show
  1. package/README.md +53 -2
  2. package/dist/index.js +731 -23
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -64,7 +64,14 @@ Point your client to `https://mcp.kadoa.com/mcp` with OAuth authentication.
64
64
  | `scrape` | Immediately fetch one URL as markdown or raw HTML (shown only for enabled workspaces) |
65
65
  | `create_workflow` | Create an agentic navigation workflow from a prompt |
66
66
  | `list_workflows` | List all workflows with status |
67
- | `get_workflow` | Get detailed workflow information |
67
+ | `get_workflow` | Get canonical intent, Assistant/session, template ownership, run health, schedule, location, monitoring, and validation details |
68
+ | `request_workflow_update` | Ask the workflow Assistant to change extraction intent, navigation, sourcing, pagination, or generated behavior without changing the workflow ID |
69
+ | `get_workflow_assistant` | Get the workflow Assistant's current activity and any pending clarification question |
70
+ | `answer_workflow_assistant_question` | Answer the workflow Assistant's current clarification question and resume it |
71
+ | `interrupt_workflow_assistant` | Safely interrupt active Assistant work without pausing the workflow schedule |
72
+ | `resume_workflow_assistant` | Resume an idle/interrupted Lighthouse or Julie session with its persisted persona |
73
+ | `stop_workflow_assistant` | Stop active Assistant work without deleting or replacing the workflow |
74
+ | `get_workflow_strategy` | Get the current customer-safe extraction/build strategy for an Assistant or custom-script workflow |
68
75
  | `get_workflow_history` | Get the workflow's configuration revision history (audit log) — who changed it, when, from which channel, and a `changedFields` summary per revision |
69
76
  | `list_workflow_runs` | List a workflow's execution run history (status, start/finish, record count, errors); filter by outcome and paginate |
70
77
  | `run_workflow` | Execute a workflow |
@@ -72,7 +79,7 @@ Point your client to `https://mcp.kadoa.com/mcp` with OAuth authentication.
72
79
  | `delete_workflow` | Delete a workflow |
73
80
  | `approve_workflow` | Approve and activate a workflow |
74
81
  | `pause_workflow` | Pause an active workflow |
75
- | `update_workflow` | Update workflow configuration and schema |
82
+ | `update_workflow` | Update deterministic configuration, including location, schedule timezone, monitoring conditions, and unlimited rows |
76
83
  | `whoami` | Show current user details, auth method, and team memberships |
77
84
  | `team_list` | List all teams you belong to and see which is active |
78
85
  | `team_switch` | Switch the active team by name or ID |
@@ -144,6 +151,50 @@ Claude calls run_workflow and waits for completion, then fetches
144
151
  the latest data with fetch_data so you can verify the changes.
145
152
  ```
146
153
 
154
+ ### Update deterministic workflow settings
155
+
156
+ ```
157
+ > You: Run wf_abc123 from the US at 8 AM New York time on weekdays,
158
+ alert when price changes above 100, and remove its row limit.
159
+
160
+ Claude calls get_workflow first because monitoring is a replacement setting,
161
+ then calls update_workflow with a manual US location, a weekday cron schedule
162
+ in `America/New_York`, monitoring fields/conditions, and `limit: null` for all
163
+ rows.
164
+ ```
165
+
166
+ ### Update an Assistant-built workflow without replacing it
167
+
168
+ ```
169
+ > You: Update workflow wf_abc123 to follow pagination and extract the detail-page URL.
170
+
171
+ Claude calls request_workflow_update. Kadoa keeps wf_abc123 and returns the
172
+ Assistant session, thread, and job identifiers.
173
+
174
+ > You: Does Kadoa need anything from me?
175
+
176
+ Claude calls get_workflow_assistant. If a clarification is pending, Claude
177
+ shows the question and calls answer_workflow_assistant_question with your
178
+ answer. It does not poll or sleep-wait for the Assistant.
179
+
180
+ > You: How does this workflow extract the data now?
181
+
182
+ Claude calls get_workflow_strategy and shows the customer-safe sourcing,
183
+ navigation, and extraction approach.
184
+
185
+ > You: Interrupt the Assistant for now, then resume it later.
186
+
187
+ Claude calls interrupt_workflow_assistant. When asked later, it calls
188
+ resume_workflow_assistant, which preserves whether the session is Lighthouse
189
+ or Julie. stop_workflow_assistant cancels active Assistant work without
190
+ pausing or deleting the workflow itself.
191
+ ```
192
+
193
+ Use `update_workflow` for deterministic metadata such as name, schedule, tags,
194
+ and limits. Use template version/apply tools for template-controlled parts.
195
+ Never delete and recreate a workflow to work around an Assistant update—the
196
+ workflow ID is part of downstream integrations and history.
197
+
147
198
  ### Monitor and clean up
148
199
 
149
200
  ```
package/dist/index.js CHANGED
@@ -45666,6 +45666,7 @@ function createAxiosInstance(params) {
45666
45666
  }
45667
45667
  function createClientDomains(params) {
45668
45668
  const { client } = params;
45669
+ const assistantService = new AssistantService(client.apis.agent);
45669
45670
  const changesService = new ChangesService(client);
45670
45671
  const userService = new UserService(client);
45671
45672
  const dataFetcherService = new DataFetcherService(client.apis.workflows);
@@ -45690,6 +45691,7 @@ function createClientDomains(params) {
45690
45691
  const validation = createValidationDomain(coreService, rulesService);
45691
45692
  const crawler = createCrawlerDomain(client);
45692
45693
  return {
45694
+ assistant: assistantService,
45693
45695
  changes: changesService,
45694
45696
  extractionBuilderService,
45695
45697
  extraction: extractionService,
@@ -45741,7 +45743,59 @@ function createNotificationDomain(params) {
45741
45743
  }
45742
45744
  };
45743
45745
  }
45744
- var import_debug, __require2, ChangeDifferenceType, KadoaErrorCode, _KadoaSdkException, KadoaSdkException, ERROR_MESSAGES, KadoaHttpException, createLogger = (namespace) => import_debug.default(`kadoa:${namespace}`), logger, debug, ChangesService = class {
45746
+ var import_debug, __require2, FREEFORM_ASSISTANT_ANSWER_KEY = "_freeform", KadoaErrorCode, _KadoaSdkException, KadoaSdkException, ERROR_MESSAGES, KadoaHttpException, AssistantService = class {
45747
+ constructor(agentApi) {
45748
+ this.agentApi = agentApi;
45749
+ }
45750
+ async requestWorkflowUpdate(workflowId, input) {
45751
+ const response = await this.agentApi.v5AgentWorkflowAssistantMessage({
45752
+ workflowId,
45753
+ workflowAssistantMessageRequest: {
45754
+ prompt: input.instructions,
45755
+ ...input.threadId != null && { threadId: input.threadId }
45756
+ }
45757
+ });
45758
+ const data = response.data?.data;
45759
+ if (!data?.workflowId || !data.sessionId || !data.threadId) {
45760
+ throw new KadoaSdkException("Workflow Assistant update response is missing required identifiers", {
45761
+ code: "INTERNAL_ERROR",
45762
+ details: { workflowId, response: response.data }
45763
+ });
45764
+ }
45765
+ return data;
45766
+ }
45767
+ async getPauseState(sessionId) {
45768
+ const response = await this.agentApi.v5AgentPauseState({ sessionId });
45769
+ return response.data.data;
45770
+ }
45771
+ async answerQuestion(input) {
45772
+ const response = await this.agentApi.v5AgentAnswer({
45773
+ assistantAnswerRequest: {
45774
+ sessionId: input.sessionId,
45775
+ questionId: input.questionId,
45776
+ answers: input.answers,
45777
+ ...input.threadId != null && { threadId: input.threadId }
45778
+ }
45779
+ });
45780
+ return response.data.data;
45781
+ }
45782
+ async getStrategy(sessionId) {
45783
+ const response = await this.agentApi.v5AgentStrategy({ sessionId });
45784
+ return response.data.data.strategy;
45785
+ }
45786
+ async interrupt(sessionId) {
45787
+ const response = await this.agentApi.v5AgentInterrupt({ sessionId });
45788
+ return response.data.data;
45789
+ }
45790
+ async resume(sessionId) {
45791
+ const response = await this.agentApi.v5AgentResume({ sessionId });
45792
+ return response.data.data;
45793
+ }
45794
+ async stop(sessionId) {
45795
+ const response = await this.agentApi.v5AgentStop({ sessionId });
45796
+ return response.data.data;
45797
+ }
45798
+ }, ChangeDifferenceType, createLogger = (namespace) => import_debug.default(`kadoa:${namespace}`), logger, debug, ChangesService = class {
45745
45799
  constructor(client) {
45746
45800
  this.client = client;
45747
45801
  }
@@ -45804,12 +45858,206 @@ var import_debug, __require2, ChangeDifferenceType, KadoaErrorCode, _KadoaSdkExc
45804
45858
  return needsSerialization ? JSON.stringify(value !== undefined ? value : {}, replaceWithSerializableTypeIfNeeded) : value || "";
45805
45859
  }, toPathString = function(url3) {
45806
45860
  return url3.pathname + url3.search + url3.hash;
45807
- }, createRequestFunction = function(axiosArgs, globalAxios11, BASE_PATH2, configuration) {
45808
- return (axios2 = globalAxios11, basePath = BASE_PATH2) => {
45861
+ }, createRequestFunction = function(axiosArgs, globalAxios12, BASE_PATH2, configuration) {
45862
+ return (axios2 = globalAxios12, basePath = BASE_PATH2) => {
45809
45863
  const axiosRequestArgs = { ...axiosArgs.options, url: (axios2.defaults.baseURL ? "" : configuration?.basePath ?? basePath) + axiosArgs.url };
45810
45864
  return axios2.request(axiosRequestArgs);
45811
45865
  };
45812
- }, CrawlerApiAxiosParamCreator = function(configuration) {
45866
+ }, AgentApiAxiosParamCreator = function(configuration) {
45867
+ return {
45868
+ v5AgentAnswer: async (assistantAnswerRequest, options = {}) => {
45869
+ assertParamExists("v5AgentAnswer", "assistantAnswerRequest", assistantAnswerRequest);
45870
+ const localVarPath = `/v5/agent/answer`;
45871
+ const localVarUrlObj = new URL$1(localVarPath, DUMMY_BASE_URL);
45872
+ let baseOptions;
45873
+ if (configuration) {
45874
+ baseOptions = configuration.baseOptions;
45875
+ }
45876
+ const localVarRequestOptions = { method: "POST", ...baseOptions, ...options };
45877
+ const localVarHeaderParameter = {};
45878
+ const localVarQueryParameter = {};
45879
+ localVarHeaderParameter["Content-Type"] = "application/json";
45880
+ localVarHeaderParameter["Accept"] = "application/json";
45881
+ setSearchParams(localVarUrlObj, localVarQueryParameter);
45882
+ let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
45883
+ localVarRequestOptions.headers = { ...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers };
45884
+ localVarRequestOptions.data = serializeDataIfNeeded(assistantAnswerRequest, localVarRequestOptions, configuration);
45885
+ return {
45886
+ url: toPathString(localVarUrlObj),
45887
+ options: localVarRequestOptions
45888
+ };
45889
+ },
45890
+ v5AgentInterrupt: async (sessionId, options = {}) => {
45891
+ assertParamExists("v5AgentInterrupt", "sessionId", sessionId);
45892
+ const localVarPath = `/v5/agent/{sessionId}/interrupt`.replace(`{${"sessionId"}}`, encodeURIComponent(String(sessionId)));
45893
+ const localVarUrlObj = new URL$1(localVarPath, DUMMY_BASE_URL);
45894
+ let baseOptions;
45895
+ if (configuration) {
45896
+ baseOptions = configuration.baseOptions;
45897
+ }
45898
+ const localVarRequestOptions = { method: "POST", ...baseOptions, ...options };
45899
+ const localVarHeaderParameter = {};
45900
+ const localVarQueryParameter = {};
45901
+ localVarHeaderParameter["Accept"] = "application/json";
45902
+ setSearchParams(localVarUrlObj, localVarQueryParameter);
45903
+ let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
45904
+ localVarRequestOptions.headers = { ...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers };
45905
+ return {
45906
+ url: toPathString(localVarUrlObj),
45907
+ options: localVarRequestOptions
45908
+ };
45909
+ },
45910
+ v5AgentPauseState: async (sessionId, options = {}) => {
45911
+ assertParamExists("v5AgentPauseState", "sessionId", sessionId);
45912
+ const localVarPath = `/v5/agent/{sessionId}/pause-state`.replace(`{${"sessionId"}}`, encodeURIComponent(String(sessionId)));
45913
+ const localVarUrlObj = new URL$1(localVarPath, DUMMY_BASE_URL);
45914
+ let baseOptions;
45915
+ if (configuration) {
45916
+ baseOptions = configuration.baseOptions;
45917
+ }
45918
+ const localVarRequestOptions = { method: "GET", ...baseOptions, ...options };
45919
+ const localVarHeaderParameter = {};
45920
+ const localVarQueryParameter = {};
45921
+ localVarHeaderParameter["Accept"] = "application/json";
45922
+ setSearchParams(localVarUrlObj, localVarQueryParameter);
45923
+ let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
45924
+ localVarRequestOptions.headers = { ...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers };
45925
+ return {
45926
+ url: toPathString(localVarUrlObj),
45927
+ options: localVarRequestOptions
45928
+ };
45929
+ },
45930
+ v5AgentResume: async (sessionId, options = {}) => {
45931
+ assertParamExists("v5AgentResume", "sessionId", sessionId);
45932
+ const localVarPath = `/v5/agent/{sessionId}/resume`.replace(`{${"sessionId"}}`, encodeURIComponent(String(sessionId)));
45933
+ const localVarUrlObj = new URL$1(localVarPath, DUMMY_BASE_URL);
45934
+ let baseOptions;
45935
+ if (configuration) {
45936
+ baseOptions = configuration.baseOptions;
45937
+ }
45938
+ const localVarRequestOptions = { method: "POST", ...baseOptions, ...options };
45939
+ const localVarHeaderParameter = {};
45940
+ const localVarQueryParameter = {};
45941
+ localVarHeaderParameter["Accept"] = "application/json";
45942
+ setSearchParams(localVarUrlObj, localVarQueryParameter);
45943
+ let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
45944
+ localVarRequestOptions.headers = { ...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers };
45945
+ return {
45946
+ url: toPathString(localVarUrlObj),
45947
+ options: localVarRequestOptions
45948
+ };
45949
+ },
45950
+ v5AgentStop: async (sessionId, options = {}) => {
45951
+ assertParamExists("v5AgentStop", "sessionId", sessionId);
45952
+ const localVarPath = `/v5/agent/{sessionId}/stop`.replace(`{${"sessionId"}}`, encodeURIComponent(String(sessionId)));
45953
+ const localVarUrlObj = new URL$1(localVarPath, DUMMY_BASE_URL);
45954
+ let baseOptions;
45955
+ if (configuration) {
45956
+ baseOptions = configuration.baseOptions;
45957
+ }
45958
+ const localVarRequestOptions = { method: "POST", ...baseOptions, ...options };
45959
+ const localVarHeaderParameter = {};
45960
+ const localVarQueryParameter = {};
45961
+ localVarHeaderParameter["Accept"] = "application/json";
45962
+ setSearchParams(localVarUrlObj, localVarQueryParameter);
45963
+ let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
45964
+ localVarRequestOptions.headers = { ...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers };
45965
+ return {
45966
+ url: toPathString(localVarUrlObj),
45967
+ options: localVarRequestOptions
45968
+ };
45969
+ },
45970
+ v5AgentStrategy: async (sessionId, options = {}) => {
45971
+ assertParamExists("v5AgentStrategy", "sessionId", sessionId);
45972
+ const localVarPath = `/v5/agent/data/{sessionId}/strategy`.replace(`{${"sessionId"}}`, encodeURIComponent(String(sessionId)));
45973
+ const localVarUrlObj = new URL$1(localVarPath, DUMMY_BASE_URL);
45974
+ let baseOptions;
45975
+ if (configuration) {
45976
+ baseOptions = configuration.baseOptions;
45977
+ }
45978
+ const localVarRequestOptions = { method: "GET", ...baseOptions, ...options };
45979
+ const localVarHeaderParameter = {};
45980
+ const localVarQueryParameter = {};
45981
+ localVarHeaderParameter["Accept"] = "application/json";
45982
+ setSearchParams(localVarUrlObj, localVarQueryParameter);
45983
+ let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
45984
+ localVarRequestOptions.headers = { ...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers };
45985
+ return {
45986
+ url: toPathString(localVarUrlObj),
45987
+ options: localVarRequestOptions
45988
+ };
45989
+ },
45990
+ v5AgentWorkflowAssistantMessage: async (workflowId, workflowAssistantMessageRequest, options = {}) => {
45991
+ assertParamExists("v5AgentWorkflowAssistantMessage", "workflowId", workflowId);
45992
+ assertParamExists("v5AgentWorkflowAssistantMessage", "workflowAssistantMessageRequest", workflowAssistantMessageRequest);
45993
+ const localVarPath = `/v5/agent/workflows/{workflowId}/assistant/messages`.replace(`{${"workflowId"}}`, encodeURIComponent(String(workflowId)));
45994
+ const localVarUrlObj = new URL$1(localVarPath, DUMMY_BASE_URL);
45995
+ let baseOptions;
45996
+ if (configuration) {
45997
+ baseOptions = configuration.baseOptions;
45998
+ }
45999
+ const localVarRequestOptions = { method: "POST", ...baseOptions, ...options };
46000
+ const localVarHeaderParameter = {};
46001
+ const localVarQueryParameter = {};
46002
+ localVarHeaderParameter["Content-Type"] = "application/json";
46003
+ localVarHeaderParameter["Accept"] = "application/json";
46004
+ setSearchParams(localVarUrlObj, localVarQueryParameter);
46005
+ let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
46006
+ localVarRequestOptions.headers = { ...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers };
46007
+ localVarRequestOptions.data = serializeDataIfNeeded(workflowAssistantMessageRequest, localVarRequestOptions, configuration);
46008
+ return {
46009
+ url: toPathString(localVarUrlObj),
46010
+ options: localVarRequestOptions
46011
+ };
46012
+ }
46013
+ };
46014
+ }, AgentApiFp = function(configuration) {
46015
+ const localVarAxiosParamCreator = AgentApiAxiosParamCreator(configuration);
46016
+ return {
46017
+ async v5AgentAnswer(assistantAnswerRequest, options) {
46018
+ const localVarAxiosArgs = await localVarAxiosParamCreator.v5AgentAnswer(assistantAnswerRequest, options);
46019
+ const localVarOperationServerIndex = configuration?.serverIndex ?? 0;
46020
+ const localVarOperationServerBasePath = operationServerMap["AgentApi.v5AgentAnswer"]?.[localVarOperationServerIndex]?.url;
46021
+ return (axios2, basePath) => createRequestFunction(localVarAxiosArgs, axios_default, BASE_PATH, configuration)(axios2, localVarOperationServerBasePath || basePath);
46022
+ },
46023
+ async v5AgentInterrupt(sessionId, options) {
46024
+ const localVarAxiosArgs = await localVarAxiosParamCreator.v5AgentInterrupt(sessionId, options);
46025
+ const localVarOperationServerIndex = configuration?.serverIndex ?? 0;
46026
+ const localVarOperationServerBasePath = operationServerMap["AgentApi.v5AgentInterrupt"]?.[localVarOperationServerIndex]?.url;
46027
+ return (axios2, basePath) => createRequestFunction(localVarAxiosArgs, axios_default, BASE_PATH, configuration)(axios2, localVarOperationServerBasePath || basePath);
46028
+ },
46029
+ async v5AgentPauseState(sessionId, options) {
46030
+ const localVarAxiosArgs = await localVarAxiosParamCreator.v5AgentPauseState(sessionId, options);
46031
+ const localVarOperationServerIndex = configuration?.serverIndex ?? 0;
46032
+ const localVarOperationServerBasePath = operationServerMap["AgentApi.v5AgentPauseState"]?.[localVarOperationServerIndex]?.url;
46033
+ return (axios2, basePath) => createRequestFunction(localVarAxiosArgs, axios_default, BASE_PATH, configuration)(axios2, localVarOperationServerBasePath || basePath);
46034
+ },
46035
+ async v5AgentResume(sessionId, options) {
46036
+ const localVarAxiosArgs = await localVarAxiosParamCreator.v5AgentResume(sessionId, options);
46037
+ const localVarOperationServerIndex = configuration?.serverIndex ?? 0;
46038
+ const localVarOperationServerBasePath = operationServerMap["AgentApi.v5AgentResume"]?.[localVarOperationServerIndex]?.url;
46039
+ return (axios2, basePath) => createRequestFunction(localVarAxiosArgs, axios_default, BASE_PATH, configuration)(axios2, localVarOperationServerBasePath || basePath);
46040
+ },
46041
+ async v5AgentStop(sessionId, options) {
46042
+ const localVarAxiosArgs = await localVarAxiosParamCreator.v5AgentStop(sessionId, options);
46043
+ const localVarOperationServerIndex = configuration?.serverIndex ?? 0;
46044
+ const localVarOperationServerBasePath = operationServerMap["AgentApi.v5AgentStop"]?.[localVarOperationServerIndex]?.url;
46045
+ return (axios2, basePath) => createRequestFunction(localVarAxiosArgs, axios_default, BASE_PATH, configuration)(axios2, localVarOperationServerBasePath || basePath);
46046
+ },
46047
+ async v5AgentStrategy(sessionId, options) {
46048
+ const localVarAxiosArgs = await localVarAxiosParamCreator.v5AgentStrategy(sessionId, options);
46049
+ const localVarOperationServerIndex = configuration?.serverIndex ?? 0;
46050
+ const localVarOperationServerBasePath = operationServerMap["AgentApi.v5AgentStrategy"]?.[localVarOperationServerIndex]?.url;
46051
+ return (axios2, basePath) => createRequestFunction(localVarAxiosArgs, axios_default, BASE_PATH, configuration)(axios2, localVarOperationServerBasePath || basePath);
46052
+ },
46053
+ async v5AgentWorkflowAssistantMessage(workflowId, workflowAssistantMessageRequest, options) {
46054
+ const localVarAxiosArgs = await localVarAxiosParamCreator.v5AgentWorkflowAssistantMessage(workflowId, workflowAssistantMessageRequest, options);
46055
+ const localVarOperationServerIndex = configuration?.serverIndex ?? 0;
46056
+ const localVarOperationServerBasePath = operationServerMap["AgentApi.v5AgentWorkflowAssistantMessage"]?.[localVarOperationServerIndex]?.url;
46057
+ return (axios2, basePath) => createRequestFunction(localVarAxiosArgs, axios_default, BASE_PATH, configuration)(axios2, localVarOperationServerBasePath || basePath);
46058
+ }
46059
+ };
46060
+ }, AgentApi, CrawlerApiAxiosParamCreator = function(configuration) {
45813
46061
  return {
45814
46062
  v4CrawlBucketDataFilenameb64Get: async (filenameb64, contentType, cacheControl, options = {}) => {
45815
46063
  assertParamExists("v4CrawlBucketDataFilenameb64Get", "filenameb64", filenameb64);
@@ -50028,7 +50276,7 @@ var import_debug, __require2, ChangeDifferenceType, KadoaErrorCode, _KadoaSdkExc
50028
50276
  }));
50029
50277
  return channels;
50030
50278
  }
50031
- }, PUBLIC_API_URI, WSS_API_URI, REALTIME_API_URI, SDK_VERSION = "0.36.0", SDK_NAME = "kadoa-node-sdk", SDK_LANGUAGE = "node", debug6, isDrainControlMessage = (message) => message.type === "control.draining", isRealtimeEvent = (message) => message.type !== "heartbeat" && message.type !== "control.draining", _Realtime = class _Realtime2 {
50279
+ }, PUBLIC_API_URI, WSS_API_URI, REALTIME_API_URI, SDK_VERSION = "0.37.1", SDK_NAME = "kadoa-node-sdk", SDK_LANGUAGE = "node", debug6, isDrainControlMessage = (message) => message.type === "control.draining", isRealtimeEvent = (message) => message.type !== "heartbeat" && message.type !== "control.draining", _Realtime = class _Realtime2 {
50032
50280
  constructor(config2) {
50033
50281
  this.drainingSockets = /* @__PURE__ */ new Set;
50034
50282
  this.lastHeartbeat = Date.now();
@@ -51061,6 +51309,9 @@ var import_debug, __require2, ChangeDifferenceType, KadoaErrorCode, _KadoaSdkExc
51061
51309
  }
51062
51310
  return this.cache.get(ApiClass);
51063
51311
  }
51312
+ get agent() {
51313
+ return this.get(AgentApi);
51314
+ }
51064
51315
  get schemas() {
51065
51316
  return this.get(SchemasApi);
51066
51317
  }
@@ -51113,6 +51364,7 @@ var import_debug, __require2, ChangeDifferenceType, KadoaErrorCode, _KadoaSdkExc
51113
51364
  });
51114
51365
  this.apis = new ApiRegistry(this._apiKey, this._baseUrl, this._axiosInstance, headers);
51115
51366
  const domains = createClientDomains({ client: this });
51367
+ this.assistant = domains.assistant;
51116
51368
  this.changes = domains.changes;
51117
51369
  this.user = domains.user;
51118
51370
  this.extraction = domains.extraction;
@@ -51206,11 +51458,6 @@ var init_dist2 = __esm(() => {
51206
51458
  return __require.apply(this, arguments);
51207
51459
  throw Error('Dynamic require of "' + x + '" is not supported');
51208
51460
  });
51209
- ChangeDifferenceType = {
51210
- Added: "added",
51211
- Removed: "removed",
51212
- Changed: "changed"
51213
- };
51214
51461
  KadoaErrorCode = {
51215
51462
  VALIDATION_ERROR: "VALIDATION_ERROR",
51216
51463
  BAD_REQUEST: "BAD_REQUEST",
@@ -51407,6 +51654,11 @@ var init_dist2 = __esm(() => {
51407
51654
  return "UNKNOWN";
51408
51655
  }
51409
51656
  };
51657
+ ChangeDifferenceType = {
51658
+ Added: "added",
51659
+ Removed: "removed",
51660
+ Changed: "changed"
51661
+ };
51410
51662
  logger = {
51411
51663
  changes: createLogger("changes"),
51412
51664
  client: createLogger("client"),
@@ -51431,6 +51683,29 @@ var init_dist2 = __esm(() => {
51431
51683
  }
51432
51684
  };
51433
51685
  operationServerMap = {};
51686
+ AgentApi = class extends BaseAPI {
51687
+ v5AgentAnswer(requestParameters, options) {
51688
+ return AgentApiFp(this.configuration).v5AgentAnswer(requestParameters.assistantAnswerRequest, options).then((request) => request(this.axios, this.basePath));
51689
+ }
51690
+ v5AgentInterrupt(requestParameters, options) {
51691
+ return AgentApiFp(this.configuration).v5AgentInterrupt(requestParameters.sessionId, options).then((request) => request(this.axios, this.basePath));
51692
+ }
51693
+ v5AgentPauseState(requestParameters, options) {
51694
+ return AgentApiFp(this.configuration).v5AgentPauseState(requestParameters.sessionId, options).then((request) => request(this.axios, this.basePath));
51695
+ }
51696
+ v5AgentResume(requestParameters, options) {
51697
+ return AgentApiFp(this.configuration).v5AgentResume(requestParameters.sessionId, options).then((request) => request(this.axios, this.basePath));
51698
+ }
51699
+ v5AgentStop(requestParameters, options) {
51700
+ return AgentApiFp(this.configuration).v5AgentStop(requestParameters.sessionId, options).then((request) => request(this.axios, this.basePath));
51701
+ }
51702
+ v5AgentStrategy(requestParameters, options) {
51703
+ return AgentApiFp(this.configuration).v5AgentStrategy(requestParameters.sessionId, options).then((request) => request(this.axios, this.basePath));
51704
+ }
51705
+ v5AgentWorkflowAssistantMessage(requestParameters, options) {
51706
+ return AgentApiFp(this.configuration).v5AgentWorkflowAssistantMessage(requestParameters.workflowId, requestParameters.workflowAssistantMessageRequest, options).then((request) => request(this.axios, this.basePath));
51707
+ }
51708
+ };
51434
51709
  CrawlerApi = class extends BaseAPI {
51435
51710
  v4CrawlBucketDataFilenameb64Get(requestParameters, options) {
51436
51711
  return CrawlerApiFp(this.configuration).v4CrawlBucketDataFilenameb64Get(requestParameters.filenameb64, requestParameters.contentType, requestParameters.cacheControl, options).then((request) => request(this.axios, this.basePath));
@@ -52175,6 +52450,67 @@ function mapRunStatus(state) {
52175
52450
  return state ?? "unknown";
52176
52451
  }
52177
52452
  }
52453
+ function isAssistantSessionResumable(status) {
52454
+ return status != null && RESUMABLE_ASSISTANT_STATUSES.has(status);
52455
+ }
52456
+ function deriveAssistantActivity(input) {
52457
+ const { pauseState } = input;
52458
+ if (pauseState?.pendingQuestion && pauseState.pause?.status === "active")
52459
+ return "waiting_for_input";
52460
+ if (pauseState?.pause?.status === "resuming")
52461
+ return "working";
52462
+ if (pauseState?.pause?.reason === "interrupt" && pauseState.pause.status === "active")
52463
+ return "interrupted";
52464
+ if (!input.hasSession)
52465
+ return input.assistantEligible ? "not_started" : "unsupported";
52466
+ switch (input.sessionStatus) {
52467
+ case "pending":
52468
+ case "created":
52469
+ case "active":
52470
+ return "working";
52471
+ case "idle":
52472
+ case "inactive":
52473
+ return "idle";
52474
+ case "interrupted":
52475
+ return "interrupted";
52476
+ case "failed":
52477
+ case "errored":
52478
+ return "failed";
52479
+ case "aborted":
52480
+ case "timed_out":
52481
+ return "closed";
52482
+ default:
52483
+ return "unknown";
52484
+ }
52485
+ }
52486
+ function assistantControlTransitionError(input) {
52487
+ const { action, sessionStatus, pauseState } = input;
52488
+ const pause = pauseState.pause;
52489
+ if (CLOSED_ASSISTANT_STATUSES.has(sessionStatus ?? "")) {
52490
+ const verb = action === "resume" ? "resumed" : action === "interrupt" ? "interrupted" : "stopped";
52491
+ return `The Assistant session is closed (${sessionStatus}) and cannot be ${verb}. Request a new workflow update to start a resumable session.`;
52492
+ }
52493
+ if (action === "resume") {
52494
+ if (pause?.status === "resuming")
52495
+ return "The Assistant session is already resuming.";
52496
+ if (pause?.status === "active" && pause.reason === "question") {
52497
+ return "The Assistant is waiting for a clarification answer. Use answer_workflow_assistant_question instead of resume.";
52498
+ }
52499
+ if (pause?.status === "active" && pause.reason === "interrupt")
52500
+ return null;
52501
+ if (IDLE_ASSISTANT_STATUSES.has(sessionStatus ?? ""))
52502
+ return null;
52503
+ return `The Assistant cannot be resumed while its session status is '${sessionStatus ?? "unknown"}'.`;
52504
+ }
52505
+ if (pause?.status === "active") {
52506
+ if (action === "stop")
52507
+ return null;
52508
+ return pause.reason === "question" ? "The Assistant is already paused for a clarification question. Answer it or stop the session." : "The Assistant session is already interrupted.";
52509
+ }
52510
+ if (pause?.status === "resuming" || ACTIVE_ASSISTANT_STATUSES.has(sessionStatus ?? ""))
52511
+ return null;
52512
+ return `The Assistant cannot be ${action}ed while its session status is '${sessionStatus ?? "unknown"}'.`;
52513
+ }
52178
52514
  function workflowDashboardUrl(workflowId) {
52179
52515
  return `${DASHBOARD_BASE_URL}/workflow/${workflowId}`;
52180
52516
  }
@@ -52435,7 +52771,10 @@ function registerTools(server, ctx, capabilities) {
52435
52771
  return (builder) => {
52436
52772
  const entity = builder.entity(args.entity || "Data");
52437
52773
  for (const field of args.schema) {
52438
- entity.field(field.name, field.description || field.name, field.dataType || "STRING", { example: field.example });
52774
+ entity.field(field.name, field.description || field.name, field.dataType || "STRING", {
52775
+ example: field.example,
52776
+ isKey: field.isKey
52777
+ });
52439
52778
  }
52440
52779
  return entity;
52441
52780
  };
@@ -52701,7 +53040,7 @@ function registerTools(server, ctx, capabilities) {
52701
53040
  });
52702
53041
  }));
52703
53042
  server.registerTool("get_workflow", {
52704
- description: "Get detailed information about a specific workflow",
53043
+ description: "Get canonical workflow details, including Assistant/session linkage, extraction intent, template-controlled parts, stale-data state, job/run state, schedule timezone, location, monitoring, validation, and realtime health. For Assistant-built workflows, extractionSpec—not the legacy prompt—is authoritative.",
52705
53044
  inputSchema: {
52706
53045
  workflowId: exports_external.string().describe("The workflow ID")
52707
53046
  },
@@ -52709,28 +53048,317 @@ function registerTools(server, ctx, capabilities) {
52709
53048
  }, withErrorHandling("get_workflow", async (args) => {
52710
53049
  const workflow = await ctx.client.workflow.get(args.workflowId);
52711
53050
  const additionalData = workflow.additionalData;
53051
+ const extractionSpec = workflow.extractionSpec ?? null;
53052
+ const legacyPrompt = typeof additionalData?.userPrompt === "string" ? additionalData.userPrompt : null;
53053
+ const controlledParts = [
53054
+ workflow.templateHasPrompt && "prompt",
53055
+ workflow.templateHasSchema && "schema",
53056
+ workflow.templateHasSchemaValidationRules && "schemaValidationRules",
53057
+ workflow.templateHasNotifications && "notifications",
53058
+ workflow.templateHasFrequency && "frequency"
53059
+ ].filter((part) => typeof part === "string");
53060
+ const hasTemplate = workflow.templateId != null || workflow.templateVersion != null;
53061
+ const isRealtime = workflow.isRealTime === true || isRealTimeInterval(workflow.updateInterval);
52712
53062
  return jsonResult({
52713
53063
  id: workflow.id,
52714
53064
  name: workflow.name,
52715
53065
  dashboardUrl: workflowDashboardUrl(workflow.id),
52716
53066
  description: workflow.description,
52717
53067
  status: deriveStatusLabel(workflow),
53068
+ state: workflow.state ?? null,
53069
+ displayState: workflow.displayState ?? null,
53070
+ runState: workflow.runState ?? null,
52718
53071
  urls: workflow.urls,
52719
- prompt: additionalData?.userPrompt,
53072
+ prompt: extractionSpec ? null : legacyPrompt,
53073
+ intentSource: extractionSpec ? "extraction_spec" : legacyPrompt ? "legacy_prompt" : null,
53074
+ extractionSpec,
52720
53075
  entity: workflow.entity,
52721
53076
  schema: workflow.schema,
52722
53077
  navigationMode: workflow.navigationMode,
53078
+ dataStale: workflow.dataStale ?? false,
53079
+ assistantEligible: workflow.assistantEligible === true,
53080
+ assistantSessionId: workflow.assistantSessionId ?? null,
53081
+ assistantThreadId: workflow.assistantThreadId ?? null,
53082
+ assistantSessionStatus: workflow.sessionStatus ?? null,
53083
+ assistant: {
53084
+ eligible: workflow.assistantEligible === true,
53085
+ sessionId: workflow.assistantSessionId ?? null,
53086
+ threadId: workflow.assistantThreadId ?? null,
53087
+ sessionStatus: workflow.sessionStatus ?? null
53088
+ },
53089
+ template: hasTemplate ? {
53090
+ id: workflow.templateId ?? null,
53091
+ name: workflow.templateName ?? null,
53092
+ version: workflow.templateVersion ?? null,
53093
+ controlledParts,
53094
+ hasSchemaValidationRules: workflow.hasSchemaValidationRules ?? false
53095
+ } : null,
52723
53096
  updateInterval: workflow.updateInterval,
52724
53097
  schedules: workflow.schedules,
53098
+ scheduleTimezone: workflow.scheduleTimezone ?? null,
52725
53099
  nextInvocation: workflow.nextInvocation,
52726
- limit: workflow.limit,
53100
+ location: workflow.location ?? null,
53101
+ limit: workflow.limit ?? null,
52727
53102
  tags: workflow.tags,
52728
53103
  totalRecords: workflow.totalRecords,
53104
+ job: workflow.jobId ? {
53105
+ id: workflow.jobId,
53106
+ state: workflow.runState ?? null,
53107
+ isFirstRun: workflow.isFirstRun ?? null,
53108
+ startedAt: workflow.startedAt ?? null,
53109
+ finishedAt: workflow.finishedAt ?? null,
53110
+ estimatedFinishedAtUtc: workflow.estimatedFinishedAtUtc ?? null,
53111
+ dataQualityIssues: workflow.dataQualityIssues ?? null,
53112
+ errors: workflow.errors ?? []
53113
+ } : null,
52729
53114
  startedAt: workflow.startedAt,
52730
53115
  finishedAt: workflow.finishedAt,
53116
+ estimatedFinishedAtUtc: workflow.estimatedFinishedAtUtc ?? null,
52731
53117
  errors: workflow.errors,
52732
53118
  createdAt: workflow.createdAt,
52733
- notificationConfig: workflow.notificationConfig
53119
+ notificationConfig: workflow.notificationConfig,
53120
+ channelCounts: workflow.channelCounts ?? {},
53121
+ dataValidation: workflow.dataValidation ?? null,
53122
+ validationStatistics: workflow.validationStatistics ?? null,
53123
+ realtime: isRealtime ? {
53124
+ lastDataChangedAt: workflow.lastDataChangedAt ?? null,
53125
+ observerHealth: workflow.observerHealth ?? null
53126
+ } : null
53127
+ });
53128
+ }));
53129
+ server.registerTool("request_workflow_update", {
53130
+ description: "Request an Assistant-driven change to an existing workflow while preserving its workflowId. " + "Use for extraction intent, navigation, pagination, data sourcing, generated-script changes, and repair after a site redesign. " + "Use update_workflow instead for deterministic metadata such as name, schedule, location, tags, and limits. " + "For template-controlled prompt or schema changes, create and apply a template version instead. " + "The update is asynchronous: do not poll or sleep-wait; use get_workflow_assistant later. Never delete and recreate the workflow as a workaround.",
53131
+ inputSchema: strictSchema({
53132
+ workflowId: exports_external.string().min(1).describe("The existing workflow ID; it will not change"),
53133
+ instructions: exports_external.string().min(1).describe("Natural-language instructions for the workflow Assistant"),
53134
+ threadId: exports_external.string().optional().describe("Optional Assistant thread ID to continue")
53135
+ }),
53136
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false }
53137
+ }, withErrorHandling("request_workflow_update", async (args) => {
53138
+ const result = await ctx.client.assistant.requestWorkflowUpdate(args.workflowId, {
53139
+ instructions: args.instructions,
53140
+ threadId: args.threadId
53141
+ });
53142
+ if (result.workflowId !== args.workflowId) {
53143
+ return errorResult(`The Assistant returned a different workflow ID (${result.workflowId}) than requested (${args.workflowId}). ` + "The update was not reported as successful because workflow identity must be preserved.");
53144
+ }
53145
+ return jsonResult({
53146
+ success: true,
53147
+ status: "accepted",
53148
+ workflowId: result.workflowId,
53149
+ sessionId: result.sessionId,
53150
+ threadId: result.threadId,
53151
+ jobId: result.jobId,
53152
+ inputEventId: result.inputEventId,
53153
+ dashboardUrl: workflowDashboardUrl(result.workflowId),
53154
+ message: "Workflow update accepted. Use get_workflow_assistant later to check whether the Assistant is working or needs input; do not poll or sleep-wait."
53155
+ });
53156
+ }));
53157
+ server.registerTool("get_workflow_assistant", {
53158
+ description: "Get the current Assistant lifecycle for a workflow, including whether it is working, waiting for input, idle, interrupted, or closed and any pending customer question. Works for both Shelly/Lighthouse and Julie workflows.",
53159
+ inputSchema: strictSchema({
53160
+ workflowId: exports_external.string().min(1).describe("The workflow ID")
53161
+ }),
53162
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true }
53163
+ }, withErrorHandling("get_workflow_assistant", async (args) => {
53164
+ const workflow = await ctx.client.workflow.get(args.workflowId);
53165
+ const sessionId = workflow.assistantSessionId ?? null;
53166
+ const assistantEligible = workflow.assistantEligible === true;
53167
+ const pauseState = sessionId ? await ctx.client.assistant.getPauseState(sessionId) : null;
53168
+ const activity = deriveAssistantActivity({
53169
+ assistantEligible,
53170
+ hasSession: sessionId != null,
53171
+ sessionStatus: workflow.sessionStatus,
53172
+ pauseState
53173
+ });
53174
+ const canAcceptUpdate = ![
53175
+ "unsupported",
53176
+ "waiting_for_input",
53177
+ "failed",
53178
+ "closed",
53179
+ "unknown"
53180
+ ].includes(activity);
53181
+ return jsonResult({
53182
+ workflowId: workflow.id ?? args.workflowId,
53183
+ assistantEligible,
53184
+ sessionId,
53185
+ threadId: workflow.assistantThreadId ?? null,
53186
+ sessionStatus: workflow.sessionStatus ?? null,
53187
+ activity,
53188
+ canAcceptUpdate,
53189
+ isTerminal: activity === "failed" || activity === "closed",
53190
+ paused: pauseState?.paused ?? false,
53191
+ pause: pauseState?.pause ?? null,
53192
+ pendingQuestion: pauseState?.pendingQuestion ?? null
53193
+ });
53194
+ }));
53195
+ server.registerTool("answer_workflow_assistant_question", {
53196
+ description: "Answer the workflow Assistant's currently pending clarification question and resume it. " + "Use `answer` for a freeform response, or `answers` for the structured key/value response described by get_workflow_assistant. Provide exactly one. The question ID must still be current.",
53197
+ inputSchema: strictSchema({
53198
+ workflowId: exports_external.string().min(1).describe("The workflow ID"),
53199
+ questionId: exports_external.string().min(1).describe("The pending question ID returned by get_workflow_assistant"),
53200
+ answer: exports_external.string().min(1).optional().describe("Freeform answer"),
53201
+ answers: exports_external.preprocess(coerceJson(), exports_external.record(exports_external.string(), exports_external.string())).optional().describe("Structured answer map; keys identify questions and values contain the selected/freeform answers")
53202
+ }),
53203
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false }
53204
+ }, withErrorHandling("answer_workflow_assistant_question", async (args) => {
53205
+ const hasFreeform = args.answer != null;
53206
+ const hasStructured = args.answers != null;
53207
+ if (hasFreeform === hasStructured) {
53208
+ return errorResult("Provide exactly one of 'answer' (freeform) or 'answers' (structured).");
53209
+ }
53210
+ const workflow = await ctx.client.workflow.get(args.workflowId);
53211
+ const sessionId = workflow.assistantSessionId;
53212
+ if (!sessionId) {
53213
+ return errorResult("This workflow does not have an Assistant session with a pending question.");
53214
+ }
53215
+ const pauseState = await ctx.client.assistant.getPauseState(sessionId);
53216
+ const pendingQuestion = pauseState.pendingQuestion;
53217
+ if (!pendingQuestion) {
53218
+ return errorResult("The workflow Assistant does not currently have a pending question.");
53219
+ }
53220
+ if (pendingQuestion.questionId !== args.questionId) {
53221
+ return errorResult(`Question '${args.questionId}' is stale. The current pending question ID is '${pendingQuestion.questionId}'. ` + "Call get_workflow_assistant to review it before answering.");
53222
+ }
53223
+ const answers = hasFreeform ? { [FREEFORM_ASSISTANT_ANSWER_KEY]: args.answer } : args.answers;
53224
+ const threadId = pendingQuestion.threadId ?? workflow.assistantThreadId ?? undefined;
53225
+ await ctx.client.assistant.answerQuestion({
53226
+ sessionId,
53227
+ questionId: args.questionId,
53228
+ threadId,
53229
+ answers
53230
+ });
53231
+ return jsonResult({
53232
+ success: true,
53233
+ workflowId: workflow.id ?? args.workflowId,
53234
+ sessionId,
53235
+ threadId: threadId ?? null,
53236
+ questionId: args.questionId,
53237
+ message: "Answer submitted. The workflow Assistant has been asked to resume."
53238
+ });
53239
+ }));
53240
+ server.registerTool("interrupt_workflow_assistant", {
53241
+ description: "Safely interrupt the active Assistant working on a workflow. This pauses Assistant reasoning/update work; it does not pause the workflow's extraction schedule. Use pause_workflow for scheduling instead.",
53242
+ inputSchema: strictSchema({
53243
+ workflowId: exports_external.string().min(1).describe("Workflow whose Assistant session should be interrupted")
53244
+ }),
53245
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false }
53246
+ }, withErrorHandling("interrupt_workflow_assistant", async (args) => {
53247
+ const workflow = await ctx.client.workflow.get(args.workflowId);
53248
+ const sessionId = workflow.assistantSessionId;
53249
+ if (!sessionId)
53250
+ return errorResult("This workflow does not have an Assistant session to interrupt.");
53251
+ const pauseState = await ctx.client.assistant.getPauseState(sessionId);
53252
+ const transitionError = assistantControlTransitionError({
53253
+ action: "interrupt",
53254
+ sessionStatus: workflow.sessionStatus,
53255
+ pauseState
53256
+ });
53257
+ if (transitionError)
53258
+ return errorResult(transitionError);
53259
+ const result = await ctx.client.assistant.interrupt(sessionId);
53260
+ return jsonResult({
53261
+ success: result.success,
53262
+ status: "accepted",
53263
+ action: "interrupt",
53264
+ workflowId: workflow.id ?? args.workflowId,
53265
+ sessionId,
53266
+ delivery: result.delivery ?? null,
53267
+ message: "Assistant interrupt accepted. Check get_workflow_assistant later; do not poll or sleep-wait."
53268
+ });
53269
+ }));
53270
+ server.registerTool("resume_workflow_assistant", {
53271
+ description: "Resume an idle or interrupted workflow Assistant using the persona and conversation persisted on its session. Works for both Lighthouse and Julie. This does not resume a paused workflow schedule; use approve_workflow for that.",
53272
+ inputSchema: strictSchema({
53273
+ workflowId: exports_external.string().min(1).describe("Workflow whose Assistant session should resume")
53274
+ }),
53275
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false }
53276
+ }, withErrorHandling("resume_workflow_assistant", async (args) => {
53277
+ const workflow = await ctx.client.workflow.get(args.workflowId);
53278
+ const sessionId = workflow.assistantSessionId;
53279
+ if (!sessionId)
53280
+ return errorResult("This workflow does not have an Assistant session to resume.");
53281
+ const pauseState = await ctx.client.assistant.getPauseState(sessionId);
53282
+ const transitionError = assistantControlTransitionError({
53283
+ action: "resume",
53284
+ sessionStatus: workflow.sessionStatus,
53285
+ pauseState
53286
+ });
53287
+ if (transitionError)
53288
+ return errorResult(transitionError);
53289
+ const result = await ctx.client.assistant.resume(sessionId);
53290
+ if (result.sessionId !== sessionId) {
53291
+ return errorResult(`The resume response returned session '${result.sessionId}' instead of '${sessionId}'. The action was not reported as successful because Assistant session identity must be preserved.`);
53292
+ }
53293
+ return jsonResult({
53294
+ success: true,
53295
+ status: "accepted",
53296
+ action: "resume",
53297
+ workflowId: workflow.id ?? args.workflowId,
53298
+ sessionId,
53299
+ jobId: result.jobId,
53300
+ message: "Assistant resume accepted. Check get_workflow_assistant later; do not poll or sleep-wait."
53301
+ });
53302
+ }));
53303
+ server.registerTool("stop_workflow_assistant", {
53304
+ description: "Stop the active Assistant update for a workflow. This cancels Assistant work but does not delete, pause, or replace the workflow and does not change workflowId. Use pause_workflow separately to stop scheduled extraction runs.",
53305
+ inputSchema: strictSchema({
53306
+ workflowId: exports_external.string().min(1).describe("Workflow whose active Assistant session should stop")
53307
+ }),
53308
+ annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false }
53309
+ }, withErrorHandling("stop_workflow_assistant", async (args) => {
53310
+ const workflow = await ctx.client.workflow.get(args.workflowId);
53311
+ const sessionId = workflow.assistantSessionId;
53312
+ if (!sessionId)
53313
+ return errorResult("This workflow does not have an Assistant session to stop.");
53314
+ const pauseState = await ctx.client.assistant.getPauseState(sessionId);
53315
+ const transitionError = assistantControlTransitionError({
53316
+ action: "stop",
53317
+ sessionStatus: workflow.sessionStatus,
53318
+ pauseState
53319
+ });
53320
+ if (transitionError)
53321
+ return errorResult(transitionError);
53322
+ const result = await ctx.client.assistant.stop(sessionId);
53323
+ return jsonResult({
53324
+ success: result.success,
53325
+ status: "accepted",
53326
+ action: "stop",
53327
+ workflowId: workflow.id ?? args.workflowId,
53328
+ sessionId,
53329
+ message: "Assistant stop accepted. The workflow itself was left unchanged."
53330
+ });
53331
+ }));
53332
+ server.registerTool("get_workflow_strategy", {
53333
+ description: "Get the current customer-safe extraction/build strategy for a workflow: source approach, endpoints, interaction style, selectors, and realtime interval when applicable. Returns building rather than an error when an Assistant session has no build strategy yet.",
53334
+ inputSchema: strictSchema({
53335
+ workflowId: exports_external.string().min(1).describe("The workflow ID")
53336
+ }),
53337
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true }
53338
+ }, withErrorHandling("get_workflow_strategy", async (args) => {
53339
+ const workflow = await ctx.client.workflow.get(args.workflowId);
53340
+ const sessionId = workflow.assistantSessionId;
53341
+ if (sessionId) {
53342
+ const strategy2 = await ctx.client.assistant.getStrategy(sessionId);
53343
+ const status = strategy2 ? "ready" : isAssistantSessionResumable(workflow.sessionStatus) ? "building" : "unavailable";
53344
+ return jsonResult({
53345
+ workflowId: workflow.id ?? args.workflowId,
53346
+ sessionId,
53347
+ source: "assistant_build",
53348
+ status,
53349
+ strategy: strategy2,
53350
+ ...status === "building" && {
53351
+ message: "The Assistant session exists, but its build strategy is not ready yet. Check again later; do not poll."
53352
+ }
53353
+ });
53354
+ }
53355
+ const strategy = workflow.extractionStrategySummary ?? null;
53356
+ return jsonResult({
53357
+ workflowId: workflow.id ?? args.workflowId,
53358
+ sessionId: null,
53359
+ source: strategy ? "workflow_metadata" : null,
53360
+ status: strategy ? "ready" : "unavailable",
53361
+ strategy
52734
53362
  });
52735
53363
  }));
52736
53364
  server.registerTool("get_workflow_history", {
@@ -52999,9 +53627,9 @@ function registerTools(server, ctx, capabilities) {
52999
53627
  server.registerTool("update_workflow", {
53000
53628
  description: `Update a workflow's configuration. All fields are optional — only provided fields will be updated. Use this to change the name, URLs, extraction schema, entity, prompt, schedule, or other metadata.
53001
53629
 
53002
- ` + `IMPORTANT: You cannot change a workflow's interval to or from REAL_TIME. Real-time workflows are architecturally different (Observer service vs Scraper) and must be created as real-time from the start using create_workflow with interval='REAL_TIME'. To switch a workflow between real-time and regular, delete it and create a new one with the correct interval.
53630
+ ` + `IMPORTANT: You cannot change a workflow's interval to or from REAL_TIME. Real-time workflows are architecturally different from scheduled workflows and must be created with create_realtime_monitor from the start. Existing workflows cannot be converted between these modes in place; do not delete and recreate a workflow as an update workaround.
53003
53631
 
53004
- ` + "PROMPT EDITS: `userPrompt` works on most workflows and re-derives the extraction intent. Some agent-built (Shelly) scheduled workflows reject it with `SHELLY_INTENT_REQUIRES_EXTRACTION_SPEC` because their intent is owned elsewhere. " + "If that happens, tell the user to change the extraction intent in Lighthouse chat in the Kadoa dashboard. NEVER delete and recreate the workflow to work around it that mints a new workflowId, breaking anything downstream keyed to the old one (warehouse tables, connectors) and discarding the workflow's history.",
53632
+ ` + "ASSISTANT-OWNED INTENT: Use request_workflow_update—not `userPrompt`—for agent-built workflow changes to extraction intent, navigation, pagination, data sourcing, repair, or generated scripts. `userPrompt` may be rejected with `SHELLY_INTENT_REQUIRES_EXTRACTION_SPEC` because those workflows' canonical intent is owned by the Assistant. " + "Call get_workflow first. If its template.controlledParts contains the setting, create and apply a template version instead of overriding the workflow directly. NEVER delete and recreate a workflow to work around an update limitation—that changes workflowId, breaks downstream tables/connectors, and discards history.",
53005
53633
  inputSchema: strictSchema({
53006
53634
  workflowId: exports_external.string().describe("The workflow ID to update"),
53007
53635
  name: exports_external.string().optional().describe("New name for the workflow"),
@@ -53031,7 +53659,10 @@ function registerTools(server, ctx, capabilities) {
53031
53659
  "CUSTOM"
53032
53660
  ]).optional().describe("How often the workflow should run. CUSTOM requires schedules. Note: REAL_TIME is NOT allowed here — real-time workflows must be created via create_workflow."),
53033
53661
  schedules: exports_external.preprocess(coerceArray(true), exports_external.array(exports_external.string())).optional().describe("Cron expressions for CUSTOM update interval"),
53034
- limit: exports_external.preprocess(coerceNumber(), exports_external.number()).optional().describe("Maximum number of records to extract per run")
53662
+ timezone: exports_external.string().min(1).optional().describe("IANA timezone for cron schedules, for example America/New_York. A timezone-only update preserves existing cron expressions."),
53663
+ location: exports_external.preprocess(coerceJson(), LocationSchema).optional().describe("Scraping location: {type:'auto'} or {type:'manual', isoCode:'US'}"),
53664
+ monitoring: exports_external.preprocess(coerceJson(), MonitoringSchema).optional().describe("Complete monitoring replacement: fields to monitor and optional AND/OR conditions. Call get_workflow first because this replaces the existing monitoring configuration."),
53665
+ limit: exports_external.preprocess(coerceNumber(), exports_external.number().positive().nullable()).optional().describe("Maximum records per run. Pass null to clear the limit and extract all rows.")
53035
53666
  }),
53036
53667
  annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true }
53037
53668
  }, withErrorHandling("update_workflow", async (args) => {
@@ -53041,15 +53672,22 @@ function registerTools(server, ctx, capabilities) {
53041
53672
  const isCurrentlyRealTime = isRealTimeInterval(workflow.updateInterval);
53042
53673
  const isRequestingRealTime = isRealTimeInterval(updates.updateInterval);
53043
53674
  if (isRequestingRealTime && !isCurrentlyRealTime) {
53044
- return errorResult("Cannot change a regular workflow's interval to REAL_TIME. " + "Real-time workflows use a different service (Observer) and must be created as real-time from the start. " + "Delete this workflow and create a new one with interval='REAL_TIME' using create_workflow.");
53675
+ return errorResult("Cannot change a regular workflow's interval to REAL_TIME. " + "Real-time workflows use a different service and cannot replace this workflow in place. " + "The existing workflow was left unchanged.");
53045
53676
  }
53046
53677
  if (!isRequestingRealTime && isCurrentlyRealTime) {
53047
- return errorResult("Cannot change a real-time workflow's interval to a scheduled interval. " + "Real-time workflows are architecturally different and cannot be converted to regular workflows. " + "Delete this workflow and create a new one with the desired interval using create_workflow.");
53678
+ return errorResult("Cannot change a real-time workflow's interval to a scheduled interval. " + "Real-time workflows are architecturally different and cannot be converted in place. " + "The existing workflow was left unchanged.");
53048
53679
  }
53049
53680
  }
53050
53681
  if (updates.updateInterval === "CUSTOM" && (!updates.schedules || updates.schedules.length === 0)) {
53051
53682
  return errorResult("updateInterval='CUSTOM' requires at least one cron expression in the 'schedules' field (e.g. '0 8 * * 2' for Tuesdays at 8am UTC).");
53052
53683
  }
53684
+ if (updates.monitoring?.conditions) {
53685
+ const monitoredFields = new Set(updates.monitoring.fields.map((field) => field.fieldName));
53686
+ const unknownField = updates.monitoring.conditions.conditions.find((condition) => condition.type === "SINGLE" && condition.field && !monitoredFields.has(condition.field))?.field;
53687
+ if (unknownField) {
53688
+ return errorResult(`Monitoring condition field '${unknownField}' is not present in monitoring.fields. Include it in the replacement monitoring configuration.`);
53689
+ }
53690
+ }
53053
53691
  const result = await ctx.client.workflow.update(workflowId, updates);
53054
53692
  return jsonResult({
53055
53693
  success: true,
@@ -53699,7 +54337,7 @@ function registerTools(server, ctx, capabilities) {
53699
54337
  });
53700
54338
  }));
53701
54339
  }
53702
- var SchemaFieldShape, DASHBOARD_BASE_URL = "https://www.kadoa.com", WORKFLOW_AUDIT_WATCHED_KEYS;
54340
+ var SchemaFieldShape, LocationSchema, MonitoringValueOperators, MonitoringValuelessOperators, MonitoringConditionOperatorSchema, MonitoringConditionSchema, MonitoringSchema, RESUMABLE_ASSISTANT_STATUSES, ACTIVE_ASSISTANT_STATUSES, IDLE_ASSISTANT_STATUSES, CLOSED_ASSISTANT_STATUSES, DASHBOARD_BASE_URL = "https://www.kadoa.com", WORKFLOW_AUDIT_WATCHED_KEYS;
53703
54341
  var init_tools = __esm(() => {
53704
54342
  init_dist2();
53705
54343
  init_zod();
@@ -53711,6 +54349,76 @@ var init_tools = __esm(() => {
53711
54349
  dataType: exports_external.enum(["STRING", "NUMBER", "BOOLEAN", "DATE", "DATETIME", "MONEY", "IMAGE", "LINK", "OBJECT", "ARRAY"]).optional().describe("Data type for the field"),
53712
54350
  isKey: exports_external.preprocess(coerceBoolean(), exports_external.boolean()).optional().describe("Marks this field as a key field — the stable identity used to match records across runs. Change detection diffs records by their key fields, so a monitored or real-time workflow without one cannot tell an updated record from a new one. Set it on whatever uniquely identifies a row (a detail URL, an ID, a ticker).")
53713
54351
  };
54352
+ LocationSchema = exports_external.object({
54353
+ type: exports_external.enum(["auto", "manual"]),
54354
+ isoCode: exports_external.string().trim().min(2).optional()
54355
+ }).strict().superRefine((location, ctx) => {
54356
+ if (location.type === "manual" && !location.isoCode) {
54357
+ ctx.addIssue({ code: exports_external.ZodIssueCode.custom, path: ["isoCode"], message: "isoCode is required for manual location" });
54358
+ }
54359
+ if (location.type === "auto" && location.isoCode) {
54360
+ ctx.addIssue({ code: exports_external.ZodIssueCode.custom, path: ["isoCode"], message: "omit isoCode for auto location" });
54361
+ }
54362
+ });
54363
+ MonitoringValueOperators = [
54364
+ "MATCHES_REGEX",
54365
+ "EQUALS",
54366
+ "NOT_EQUALS",
54367
+ "CONTAINS",
54368
+ "NOT_CONTAINS",
54369
+ "STARTS_WITH",
54370
+ "ENDS_WITH",
54371
+ "GREATER_THAN",
54372
+ "LESS_THAN",
54373
+ "GREATER_THAN_OR_EQUAL",
54374
+ "LESS_THAN_OR_EQUAL",
54375
+ "IN",
54376
+ "NOT_IN",
54377
+ "BEFORE",
54378
+ "AFTER",
54379
+ "WITHIN_LAST_DAYS"
54380
+ ];
54381
+ MonitoringValuelessOperators = ["IS_NULL", "IS_NOT_NULL", "IS_EMPTY", "IS_NOT_EMPTY"];
54382
+ MonitoringConditionOperatorSchema = exports_external.enum([
54383
+ ...MonitoringValueOperators,
54384
+ ...MonitoringValuelessOperators
54385
+ ]);
54386
+ MonitoringConditionSchema = exports_external.object({
54387
+ type: exports_external.enum(["SINGLE", "ANY"]),
54388
+ field: exports_external.string().trim().min(1).optional(),
54389
+ operator: MonitoringConditionOperatorSchema,
54390
+ value: exports_external.union([exports_external.string(), exports_external.number(), exports_external.boolean()]).optional()
54391
+ }).strict().superRefine((condition, ctx) => {
54392
+ if (condition.type === "SINGLE" && !condition.field) {
54393
+ ctx.addIssue({ code: exports_external.ZodIssueCode.custom, path: ["field"], message: "field is required for SINGLE conditions" });
54394
+ }
54395
+ const needsValue = MonitoringValueOperators.includes(condition.operator);
54396
+ if (needsValue && condition.value === undefined) {
54397
+ ctx.addIssue({ code: exports_external.ZodIssueCode.custom, path: ["value"], message: `${condition.operator} requires a value` });
54398
+ }
54399
+ if (!needsValue && condition.value !== undefined) {
54400
+ ctx.addIssue({
54401
+ code: exports_external.ZodIssueCode.custom,
54402
+ path: ["value"],
54403
+ message: `${condition.operator} does not accept a value`
54404
+ });
54405
+ }
54406
+ });
54407
+ MonitoringSchema = exports_external.object({
54408
+ fields: exports_external.preprocess(coerceArray(), exports_external.array(exports_external.object({
54409
+ fieldName: exports_external.string().trim().min(1),
54410
+ operator: exports_external.enum(["changed", "added", "removed"]),
54411
+ isKeyField: exports_external.preprocess(coerceBoolean(), exports_external.boolean()).optional()
54412
+ }).strict()).min(1)),
54413
+ conditions: exports_external.object({
54414
+ logicalOperator: exports_external.enum(["AND", "OR"]),
54415
+ conditions: exports_external.preprocess(coerceArray(), exports_external.array(MonitoringConditionSchema).min(1))
54416
+ }).strict().optional()
54417
+ }).strict();
54418
+ RESUMABLE_ASSISTANT_STATUSES = new Set(["pending", "created", "active", "idle", "inactive", "interrupted"]);
54419
+ ACTIVE_ASSISTANT_STATUSES = new Set(["pending", "created", "active"]);
54420
+ IDLE_ASSISTANT_STATUSES = new Set(["idle", "inactive", "interrupted"]);
54421
+ CLOSED_ASSISTANT_STATUSES = new Set(["failed", "errored", "aborted", "timed_out"]);
53714
54422
  WORKFLOW_AUDIT_WATCHED_KEYS = [
53715
54423
  "name",
53716
54424
  "tags",
@@ -53729,7 +54437,7 @@ var package_default;
53729
54437
  var init_package = __esm(() => {
53730
54438
  package_default = {
53731
54439
  name: "@kadoa/mcp",
53732
- version: "0.5.16",
54440
+ version: "0.5.18",
53733
54441
  description: "Kadoa MCP Server — manage workflows from Claude Desktop, Cursor, and other MCP clients",
53734
54442
  type: "module",
53735
54443
  main: "dist/index.js",
@@ -53753,7 +54461,7 @@ var init_package = __esm(() => {
53753
54461
  prepublishOnly: "bun run check-types && bun run test:unit && bun run build"
53754
54462
  },
53755
54463
  dependencies: {
53756
- "@kadoa/node-sdk": "^0.36.0",
54464
+ "@kadoa/node-sdk": "^0.37.1",
53757
54465
  "@modelcontextprotocol/sdk": "^1.26.0",
53758
54466
  express: "^5.2.1",
53759
54467
  "express-rate-limit": "^8.2.1",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kadoa/mcp",
3
- "version": "0.5.16",
3
+ "version": "0.5.18",
4
4
  "description": "Kadoa MCP Server — manage workflows from Claude Desktop, Cursor, and other MCP clients",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -24,7 +24,7 @@
24
24
  "prepublishOnly": "bun run check-types && bun run test:unit && bun run build"
25
25
  },
26
26
  "dependencies": {
27
- "@kadoa/node-sdk": "^0.36.0",
27
+ "@kadoa/node-sdk": "^0.37.1",
28
28
  "@modelcontextprotocol/sdk": "^1.26.0",
29
29
  "express": "^5.2.1",
30
30
  "express-rate-limit": "^8.2.1",