holmesgpt 0.13.2__py3-none-any.whl → 0.18.4__py3-none-any.whl

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 (188) hide show
  1. holmes/__init__.py +3 -5
  2. holmes/clients/robusta_client.py +20 -6
  3. holmes/common/env_vars.py +58 -3
  4. holmes/common/openshift.py +1 -1
  5. holmes/config.py +123 -148
  6. holmes/core/conversations.py +71 -15
  7. holmes/core/feedback.py +191 -0
  8. holmes/core/investigation.py +31 -39
  9. holmes/core/investigation_structured_output.py +3 -3
  10. holmes/core/issue.py +1 -1
  11. holmes/core/llm.py +508 -88
  12. holmes/core/models.py +108 -4
  13. holmes/core/openai_formatting.py +14 -1
  14. holmes/core/prompt.py +48 -3
  15. holmes/core/runbooks.py +1 -0
  16. holmes/core/safeguards.py +8 -6
  17. holmes/core/supabase_dal.py +295 -100
  18. holmes/core/tool_calling_llm.py +489 -428
  19. holmes/core/tools.py +325 -56
  20. holmes/core/tools_utils/token_counting.py +21 -0
  21. holmes/core/tools_utils/tool_context_window_limiter.py +40 -0
  22. holmes/core/tools_utils/tool_executor.py +0 -13
  23. holmes/core/tools_utils/toolset_utils.py +1 -0
  24. holmes/core/toolset_manager.py +191 -5
  25. holmes/core/tracing.py +19 -3
  26. holmes/core/transformers/__init__.py +23 -0
  27. holmes/core/transformers/base.py +63 -0
  28. holmes/core/transformers/llm_summarize.py +175 -0
  29. holmes/core/transformers/registry.py +123 -0
  30. holmes/core/transformers/transformer.py +32 -0
  31. holmes/core/truncation/compaction.py +94 -0
  32. holmes/core/truncation/dal_truncation_utils.py +23 -0
  33. holmes/core/truncation/input_context_window_limiter.py +219 -0
  34. holmes/interactive.py +228 -31
  35. holmes/main.py +23 -40
  36. holmes/plugins/interfaces.py +2 -1
  37. holmes/plugins/prompts/__init__.py +2 -1
  38. holmes/plugins/prompts/_fetch_logs.jinja2 +31 -6
  39. holmes/plugins/prompts/_general_instructions.jinja2 +1 -2
  40. holmes/plugins/prompts/_runbook_instructions.jinja2 +24 -12
  41. holmes/plugins/prompts/base_user_prompt.jinja2 +7 -0
  42. holmes/plugins/prompts/conversation_history_compaction.jinja2 +89 -0
  43. holmes/plugins/prompts/generic_ask.jinja2 +0 -4
  44. holmes/plugins/prompts/generic_ask_conversation.jinja2 +0 -1
  45. holmes/plugins/prompts/generic_ask_for_issue_conversation.jinja2 +0 -1
  46. holmes/plugins/prompts/generic_investigation.jinja2 +0 -1
  47. holmes/plugins/prompts/investigation_procedure.jinja2 +50 -1
  48. holmes/plugins/prompts/kubernetes_workload_ask.jinja2 +0 -1
  49. holmes/plugins/prompts/kubernetes_workload_chat.jinja2 +0 -1
  50. holmes/plugins/runbooks/__init__.py +145 -17
  51. holmes/plugins/runbooks/catalog.json +2 -0
  52. holmes/plugins/sources/github/__init__.py +4 -2
  53. holmes/plugins/sources/prometheus/models.py +1 -0
  54. holmes/plugins/toolsets/__init__.py +44 -27
  55. holmes/plugins/toolsets/aks-node-health.yaml +46 -0
  56. holmes/plugins/toolsets/aks.yaml +64 -0
  57. holmes/plugins/toolsets/atlas_mongodb/mongodb_atlas.py +38 -47
  58. holmes/plugins/toolsets/azure_sql/apis/alert_monitoring_api.py +3 -2
  59. holmes/plugins/toolsets/azure_sql/apis/azure_sql_api.py +2 -1
  60. holmes/plugins/toolsets/azure_sql/apis/connection_failure_api.py +3 -2
  61. holmes/plugins/toolsets/azure_sql/apis/connection_monitoring_api.py +3 -1
  62. holmes/plugins/toolsets/azure_sql/apis/storage_analysis_api.py +3 -1
  63. holmes/plugins/toolsets/azure_sql/azure_sql_toolset.py +12 -13
  64. holmes/plugins/toolsets/azure_sql/tools/analyze_connection_failures.py +15 -12
  65. holmes/plugins/toolsets/azure_sql/tools/analyze_database_connections.py +15 -12
  66. holmes/plugins/toolsets/azure_sql/tools/analyze_database_health_status.py +11 -11
  67. holmes/plugins/toolsets/azure_sql/tools/analyze_database_performance.py +11 -9
  68. holmes/plugins/toolsets/azure_sql/tools/analyze_database_storage.py +15 -12
  69. holmes/plugins/toolsets/azure_sql/tools/get_active_alerts.py +15 -15
  70. holmes/plugins/toolsets/azure_sql/tools/get_slow_queries.py +11 -8
  71. holmes/plugins/toolsets/azure_sql/tools/get_top_cpu_queries.py +11 -8
  72. holmes/plugins/toolsets/azure_sql/tools/get_top_data_io_queries.py +11 -8
  73. holmes/plugins/toolsets/azure_sql/tools/get_top_log_io_queries.py +11 -8
  74. holmes/plugins/toolsets/azure_sql/utils.py +0 -32
  75. holmes/plugins/toolsets/bash/argocd/__init__.py +3 -3
  76. holmes/plugins/toolsets/bash/aws/__init__.py +4 -4
  77. holmes/plugins/toolsets/bash/azure/__init__.py +4 -4
  78. holmes/plugins/toolsets/bash/bash_toolset.py +11 -15
  79. holmes/plugins/toolsets/bash/common/bash.py +23 -13
  80. holmes/plugins/toolsets/bash/common/bash_command.py +1 -1
  81. holmes/plugins/toolsets/bash/common/stringify.py +1 -1
  82. holmes/plugins/toolsets/bash/kubectl/__init__.py +2 -1
  83. holmes/plugins/toolsets/bash/kubectl/constants.py +0 -1
  84. holmes/plugins/toolsets/bash/kubectl/kubectl_get.py +3 -4
  85. holmes/plugins/toolsets/bash/parse_command.py +12 -13
  86. holmes/plugins/toolsets/cilium.yaml +284 -0
  87. holmes/plugins/toolsets/connectivity_check.py +124 -0
  88. holmes/plugins/toolsets/coralogix/api.py +132 -119
  89. holmes/plugins/toolsets/coralogix/coralogix.jinja2 +14 -0
  90. holmes/plugins/toolsets/coralogix/toolset_coralogix.py +219 -0
  91. holmes/plugins/toolsets/coralogix/utils.py +15 -79
  92. holmes/plugins/toolsets/datadog/datadog_api.py +525 -26
  93. holmes/plugins/toolsets/datadog/datadog_logs_instructions.jinja2 +55 -11
  94. holmes/plugins/toolsets/datadog/datadog_metrics_instructions.jinja2 +3 -3
  95. holmes/plugins/toolsets/datadog/datadog_models.py +59 -0
  96. holmes/plugins/toolsets/datadog/datadog_url_utils.py +213 -0
  97. holmes/plugins/toolsets/datadog/instructions_datadog_traces.jinja2 +165 -28
  98. holmes/plugins/toolsets/datadog/toolset_datadog_general.py +417 -241
  99. holmes/plugins/toolsets/datadog/toolset_datadog_logs.py +234 -214
  100. holmes/plugins/toolsets/datadog/toolset_datadog_metrics.py +167 -79
  101. holmes/plugins/toolsets/datadog/toolset_datadog_traces.py +374 -363
  102. holmes/plugins/toolsets/elasticsearch/__init__.py +6 -0
  103. holmes/plugins/toolsets/elasticsearch/elasticsearch.py +834 -0
  104. holmes/plugins/toolsets/elasticsearch/opensearch_ppl_query_docs.jinja2 +1616 -0
  105. holmes/plugins/toolsets/elasticsearch/opensearch_query_assist.py +78 -0
  106. holmes/plugins/toolsets/elasticsearch/opensearch_query_assist_instructions.jinja2 +223 -0
  107. holmes/plugins/toolsets/git.py +54 -50
  108. holmes/plugins/toolsets/grafana/base_grafana_toolset.py +16 -4
  109. holmes/plugins/toolsets/grafana/common.py +13 -29
  110. holmes/plugins/toolsets/grafana/grafana_tempo_api.py +455 -0
  111. holmes/plugins/toolsets/grafana/loki/instructions.jinja2 +25 -0
  112. holmes/plugins/toolsets/grafana/loki/toolset_grafana_loki.py +191 -0
  113. holmes/plugins/toolsets/grafana/loki_api.py +4 -0
  114. holmes/plugins/toolsets/grafana/toolset_grafana.py +293 -89
  115. holmes/plugins/toolsets/grafana/toolset_grafana_dashboard.jinja2 +49 -0
  116. holmes/plugins/toolsets/grafana/toolset_grafana_tempo.jinja2 +246 -11
  117. holmes/plugins/toolsets/grafana/toolset_grafana_tempo.py +820 -292
  118. holmes/plugins/toolsets/grafana/trace_parser.py +4 -3
  119. holmes/plugins/toolsets/internet/internet.py +15 -16
  120. holmes/plugins/toolsets/internet/notion.py +9 -11
  121. holmes/plugins/toolsets/investigator/core_investigation.py +44 -36
  122. holmes/plugins/toolsets/investigator/model.py +3 -1
  123. holmes/plugins/toolsets/json_filter_mixin.py +134 -0
  124. holmes/plugins/toolsets/kafka.py +36 -42
  125. holmes/plugins/toolsets/kubernetes.yaml +317 -113
  126. holmes/plugins/toolsets/kubernetes_logs.py +9 -9
  127. holmes/plugins/toolsets/kubernetes_logs.yaml +32 -0
  128. holmes/plugins/toolsets/logging_utils/logging_api.py +94 -8
  129. holmes/plugins/toolsets/mcp/toolset_mcp.py +218 -64
  130. holmes/plugins/toolsets/newrelic/new_relic_api.py +165 -0
  131. holmes/plugins/toolsets/newrelic/newrelic.jinja2 +65 -0
  132. holmes/plugins/toolsets/newrelic/newrelic.py +320 -0
  133. holmes/plugins/toolsets/openshift.yaml +283 -0
  134. holmes/plugins/toolsets/prometheus/prometheus.py +1202 -421
  135. holmes/plugins/toolsets/prometheus/prometheus_instructions.jinja2 +54 -5
  136. holmes/plugins/toolsets/prometheus/utils.py +28 -0
  137. holmes/plugins/toolsets/rabbitmq/api.py +23 -4
  138. holmes/plugins/toolsets/rabbitmq/toolset_rabbitmq.py +13 -14
  139. holmes/plugins/toolsets/robusta/robusta.py +239 -68
  140. holmes/plugins/toolsets/robusta/robusta_instructions.jinja2 +26 -9
  141. holmes/plugins/toolsets/runbook/runbook_fetcher.py +157 -27
  142. holmes/plugins/toolsets/service_discovery.py +1 -1
  143. holmes/plugins/toolsets/servicenow_tables/instructions.jinja2 +83 -0
  144. holmes/plugins/toolsets/servicenow_tables/servicenow_tables.py +426 -0
  145. holmes/plugins/toolsets/utils.py +88 -0
  146. holmes/utils/config_utils.py +91 -0
  147. holmes/utils/connection_utils.py +31 -0
  148. holmes/utils/console/result.py +10 -0
  149. holmes/utils/default_toolset_installation_guide.jinja2 +1 -22
  150. holmes/utils/env.py +7 -0
  151. holmes/utils/file_utils.py +2 -1
  152. holmes/utils/global_instructions.py +60 -11
  153. holmes/utils/holmes_status.py +6 -4
  154. holmes/utils/holmes_sync_toolsets.py +0 -2
  155. holmes/utils/krr_utils.py +188 -0
  156. holmes/utils/log.py +15 -0
  157. holmes/utils/markdown_utils.py +2 -3
  158. holmes/utils/memory_limit.py +58 -0
  159. holmes/utils/sentry_helper.py +64 -0
  160. holmes/utils/stream.py +69 -8
  161. holmes/utils/tags.py +4 -3
  162. holmes/version.py +37 -15
  163. holmesgpt-0.18.4.dist-info/LICENSE +178 -0
  164. {holmesgpt-0.13.2.dist-info → holmesgpt-0.18.4.dist-info}/METADATA +35 -31
  165. holmesgpt-0.18.4.dist-info/RECORD +258 -0
  166. holmes/core/performance_timing.py +0 -72
  167. holmes/plugins/toolsets/aws.yaml +0 -80
  168. holmes/plugins/toolsets/coralogix/toolset_coralogix_logs.py +0 -112
  169. holmes/plugins/toolsets/datadog/datadog_traces_formatter.py +0 -310
  170. holmes/plugins/toolsets/datadog/toolset_datadog_rds.py +0 -739
  171. holmes/plugins/toolsets/grafana/grafana_api.py +0 -42
  172. holmes/plugins/toolsets/grafana/tempo_api.py +0 -124
  173. holmes/plugins/toolsets/grafana/toolset_grafana_loki.py +0 -110
  174. holmes/plugins/toolsets/newrelic.py +0 -231
  175. holmes/plugins/toolsets/opensearch/opensearch.py +0 -257
  176. holmes/plugins/toolsets/opensearch/opensearch_logs.py +0 -161
  177. holmes/plugins/toolsets/opensearch/opensearch_traces.py +0 -218
  178. holmes/plugins/toolsets/opensearch/opensearch_traces_instructions.jinja2 +0 -12
  179. holmes/plugins/toolsets/opensearch/opensearch_utils.py +0 -166
  180. holmes/plugins/toolsets/servicenow/install.md +0 -37
  181. holmes/plugins/toolsets/servicenow/instructions.jinja2 +0 -3
  182. holmes/plugins/toolsets/servicenow/servicenow.py +0 -219
  183. holmes/utils/keygen_utils.py +0 -6
  184. holmesgpt-0.13.2.dist-info/LICENSE.txt +0 -21
  185. holmesgpt-0.13.2.dist-info/RECORD +0 -234
  186. /holmes/plugins/toolsets/{opensearch → newrelic}/__init__.py +0 -0
  187. {holmesgpt-0.13.2.dist-info → holmesgpt-0.18.4.dist-info}/WHEEL +0 -0
  188. {holmesgpt-0.13.2.dist-info → holmesgpt-0.18.4.dist-info}/entry_points.txt +0 -0
@@ -1,63 +1,174 @@
1
1
  import logging
2
+ import os
2
3
  import textwrap
3
- from typing import Any, Dict, List, Optional
4
+ from pathlib import Path
5
+ from typing import Any, Dict, List, Optional, Union, cast
4
6
 
7
+ from holmes.core.supabase_dal import SupabaseDal
5
8
  from holmes.core.tools import (
6
9
  StructuredToolResult,
10
+ StructuredToolResultStatus,
7
11
  Tool,
12
+ ToolInvokeContext,
8
13
  ToolParameter,
9
- ToolResultStatus,
10
14
  Toolset,
11
15
  ToolsetTag,
12
16
  )
13
-
14
- from holmes.plugins.runbooks import get_runbook_by_path, DEFAULT_RUNBOOK_SEARCH_PATH
17
+ from holmes.plugins.runbooks import (
18
+ DEFAULT_RUNBOOK_SEARCH_PATH,
19
+ get_runbook_by_path,
20
+ load_runbook_catalog,
21
+ )
15
22
  from holmes.plugins.toolsets.utils import toolset_name_for_one_liner
16
23
 
17
24
 
18
- # TODO(mainred): currently we support fetch runbooks hosted internally, in the future we may want to support fetching
19
- # runbooks from external sources as well.
20
25
  class RunbookFetcher(Tool):
21
26
  toolset: "RunbookToolset"
27
+ available_runbooks: List[str] = []
28
+ additional_search_paths: Optional[List[str]] = None
29
+ _dal: Optional[SupabaseDal] = None
30
+
31
+ def __init__(
32
+ self,
33
+ toolset: "RunbookToolset",
34
+ additional_search_paths: Optional[List[str]] = None,
35
+ dal: Optional[SupabaseDal] = None,
36
+ custom_catalog_paths: Optional[List[Union[str, Path]]] = None,
37
+ ):
38
+ catalog = load_runbook_catalog(
39
+ dal=dal, custom_catalog_paths=custom_catalog_paths
40
+ )
41
+ available_runbooks = []
42
+ if catalog:
43
+ available_runbooks = catalog.list_available_runbooks()
44
+
45
+ if additional_search_paths:
46
+ for search_path in additional_search_paths:
47
+ if not os.path.isdir(search_path):
48
+ continue
49
+
50
+ for file in os.listdir(search_path):
51
+ if file.endswith(".md") and file not in available_runbooks:
52
+ available_runbooks.append(f"{file}")
53
+
54
+ runbook_list = ", ".join([f'"{rb}"' for rb in available_runbooks])
22
55
 
23
- def __init__(self, toolset: "RunbookToolset"):
24
56
  super().__init__(
25
57
  name="fetch_runbook",
26
58
  description="Get runbook content by runbook link. Use this to get troubleshooting steps for incidents",
27
59
  parameters={
28
- # use link as a more generic term for runbook path, considering we may have external links in the future
29
- "link": ToolParameter(
30
- description="The link to the runbook",
60
+ "runbook_id": ToolParameter(
61
+ description=f"The runbook_id: either a UUID or a .md filename. Must be one of: {runbook_list}",
31
62
  type="string",
32
63
  required=True,
33
64
  ),
34
65
  },
35
- toolset=toolset, # type: ignore
66
+ toolset=toolset, # type: ignore[call-arg]
67
+ available_runbooks=available_runbooks, # type: ignore[call-arg]
68
+ additional_search_paths=additional_search_paths, # type: ignore[call-arg]
36
69
  )
70
+ self._dal = dal
71
+
72
+ def _invoke(self, params: dict, context: ToolInvokeContext) -> StructuredToolResult:
73
+ runbook_id: str = params.get("runbook_id", "")
74
+ is_md_file: bool = True if runbook_id.endswith(".md") else False
75
+
76
+ # Validate link is not empty
77
+ if not runbook_id or not runbook_id.strip():
78
+ err_msg = (
79
+ "Runbook link cannot be empty. Please provide a valid runbook path."
80
+ )
81
+ logging.error(err_msg)
82
+ return StructuredToolResult(
83
+ status=StructuredToolResultStatus.ERROR,
84
+ error=err_msg,
85
+ params=params,
86
+ )
87
+
88
+ if is_md_file:
89
+ return self._get_md_runbook(runbook_id, params)
90
+ else:
91
+ return self._get_robusta_runbook(runbook_id, params)
37
92
 
38
- def _invoke(
39
- self, params: dict, user_approved: bool = False
40
- ) -> StructuredToolResult:
41
- link: str = params["link"]
93
+ def _get_robusta_runbook(self, link: str, params: dict) -> StructuredToolResult:
94
+ if self._dal and self._dal.enabled:
95
+ try:
96
+ runbook_content = self._dal.get_runbook_content(link)
97
+ if runbook_content:
98
+ return StructuredToolResult(
99
+ status=StructuredToolResultStatus.SUCCESS,
100
+ data=runbook_content.pretty(),
101
+ params=params,
102
+ )
103
+ else:
104
+ err_msg = f"Runbook with UUID '{link}' not found in remote storage."
105
+ logging.error(err_msg)
106
+ return StructuredToolResult(
107
+ status=StructuredToolResultStatus.ERROR,
108
+ error=err_msg,
109
+ params=params,
110
+ )
111
+ except Exception as e:
112
+ err_msg = f"Failed to fetch runbook with UUID '{link}': {str(e)}"
113
+ logging.error(err_msg)
114
+ return StructuredToolResult(
115
+ status=StructuredToolResultStatus.ERROR,
116
+ error=err_msg,
117
+ params=params,
118
+ )
119
+ else:
120
+ err_msg = "Runbook link appears to be a UUID, but no remote data access layer (dal) is enabled."
121
+ logging.error(err_msg)
122
+ return StructuredToolResult(
123
+ status=StructuredToolResultStatus.ERROR,
124
+ error=err_msg,
125
+ params=params,
126
+ )
42
127
 
128
+ def _get_md_runbook(self, link: str, params: dict) -> StructuredToolResult:
43
129
  search_paths = [DEFAULT_RUNBOOK_SEARCH_PATH]
44
- if self.toolset.config and "additional_search_paths" in self.toolset.config:
45
- search_paths.extend(self.toolset.config["additional_search_paths"])
130
+ if self.additional_search_paths:
131
+ search_paths.extend(self.additional_search_paths)
132
+ # Validate link is in the available runbooks list OR is a valid path within allowed directories
133
+ if link not in self.available_runbooks:
134
+ # Check if the link would resolve to a valid path within allowed directories
135
+ # This prevents path traversal attacks like ../../secret.md
136
+ is_valid_path = False
137
+ for search_path in search_paths:
138
+ candidate_path = os.path.join(search_path, link)
139
+ # Canonicalize both paths to resolve any .. or . components
140
+ real_search_path = os.path.realpath(search_path)
141
+ real_candidate_path = os.path.realpath(candidate_path)
46
142
 
47
- runbook_path = get_runbook_by_path(link, search_paths)
143
+ # Check if the resolved path is within the allowed directory
144
+ if (
145
+ real_candidate_path.startswith(real_search_path + os.sep)
146
+ or real_candidate_path == real_search_path
147
+ ):
148
+ if os.path.isfile(real_candidate_path):
149
+ is_valid_path = True
150
+ break
48
151
 
152
+ if not is_valid_path:
153
+ err_msg = f"Invalid runbook link '{link}'. Must be one of: {', '.join(self.available_runbooks) if self.available_runbooks else 'No runbooks available'}"
154
+ logging.error(err_msg)
155
+ return StructuredToolResult(
156
+ status=StructuredToolResultStatus.ERROR,
157
+ error=err_msg,
158
+ params=params,
159
+ )
160
+
161
+ runbook_path = get_runbook_by_path(link, search_paths)
49
162
  if runbook_path is None:
50
163
  err_msg = (
51
164
  f"Runbook '{link}' not found in any of the search paths: {search_paths}"
52
165
  )
53
166
  logging.error(err_msg)
54
167
  return StructuredToolResult(
55
- status=ToolResultStatus.ERROR,
168
+ status=StructuredToolResultStatus.ERROR,
56
169
  error=err_msg,
57
170
  params=params,
58
171
  )
59
-
60
- # Read and return the runbook content
61
172
  try:
62
173
  with open(runbook_path, "r") as file:
63
174
  content = file.read()
@@ -96,7 +207,7 @@ class RunbookFetcher(Tool):
96
207
  </example>
97
208
  """)
98
209
  return StructuredToolResult(
99
- status=ToolResultStatus.SUCCESS,
210
+ status=StructuredToolResultStatus.SUCCESS,
100
211
  data=wrapped_content,
101
212
  params=params,
102
213
  )
@@ -104,29 +215,47 @@ class RunbookFetcher(Tool):
104
215
  err_msg = f"Failed to read runbook {runbook_path}: {str(e)}"
105
216
  logging.error(err_msg)
106
217
  return StructuredToolResult(
107
- status=ToolResultStatus.ERROR,
218
+ status=StructuredToolResultStatus.ERROR,
108
219
  error=err_msg,
109
220
  params=params,
110
221
  )
111
222
 
112
223
  def get_parameterized_one_liner(self, params) -> str:
113
- path: str = params.get("link", "")
224
+ path: str = params.get("runbook_id", "")
114
225
  return f"{toolset_name_for_one_liner(self.toolset.name)}: Fetch Runbook {path}"
115
226
 
116
227
 
117
228
  class RunbookToolset(Toolset):
118
- def __init__(self, additional_search_paths: Optional[List[str]] = None):
119
- # Store additional search paths in config
229
+ def __init__(
230
+ self,
231
+ dal: Optional[SupabaseDal],
232
+ additional_search_paths: Optional[List[str]] = None,
233
+ ):
234
+ # Store additional search paths in config for RunbookFetcher to access
120
235
  config = {}
121
236
  if additional_search_paths:
122
237
  config["additional_search_paths"] = additional_search_paths
123
238
 
239
+ # Compute custom catalog paths from additional search paths
240
+ custom_catalog_paths = None
241
+ if additional_search_paths:
242
+ custom_catalog_paths = [
243
+ os.path.join(search_path, "catalog.json")
244
+ for search_path in additional_search_paths
245
+ if os.path.isfile(os.path.join(search_path, "catalog.json"))
246
+ ]
247
+
124
248
  super().__init__(
125
249
  name="runbook",
126
250
  description="Fetch runbooks",
127
251
  icon_url="https://platform.robusta.dev/demos/runbook.svg",
128
252
  tools=[
129
- RunbookFetcher(self),
253
+ RunbookFetcher(
254
+ self,
255
+ additional_search_paths,
256
+ dal,
257
+ cast(Optional[List[Union[str, Path]]], custom_catalog_paths),
258
+ ),
130
259
  ],
131
260
  docs_url="https://holmesgpt.dev/data-sources/",
132
261
  tags=[
@@ -134,6 +263,7 @@ class RunbookToolset(Toolset):
134
263
  ],
135
264
  is_default=True,
136
265
  config=config,
266
+ enabled=True,
137
267
  )
138
268
 
139
269
  def get_example_config(self) -> Dict[str, Any]:
@@ -36,7 +36,7 @@ def find_service_url(label_selector):
36
36
  port = svc.spec.ports[0].port
37
37
  url = f"http://{name}.{namespace}.svc.{CLUSTER_DOMAIN}:{port}"
38
38
  logging.info(
39
- f"discovered service with label-selector: `{label_selector}` at url: `{url}`"
39
+ f"Discovered service with label-selector: `{label_selector}` at url: `{url}`"
40
40
  )
41
41
  return url
42
42
  except Exception:
@@ -0,0 +1,83 @@
1
+ ## ServiceNow Tables Toolset
2
+
3
+ This toolset provides access to ServiceNow tables for retrieving records and data.
4
+
5
+ ### Available Tools
6
+
7
+ 1. **servicenow_get_records**: Retrieve multiple records from a ServiceNow table with filtering capabilities
8
+ 2. **servicenow_get_record**: Retrieve a single record by its sys_id
9
+
10
+ **IMPORTANT**: For servicenow_get_record, you MUST have a valid sys_id value that was either:
11
+ - Provided by the user
12
+ - Obtained from a previous servicenow_get_records call
13
+ - Never guess or make up sys_id values - they are unique identifiers that must come from a legitimate source
14
+
15
+ ### Query Syntax
16
+
17
+ The `sysparm_query` parameter supports a powerful query language:
18
+ - Use `^` for AND conditions
19
+ - Use `^OR` for OR conditions
20
+ - Common operators:
21
+ - `=` (equals) - Use for exact matches, REQUIRED for reference fields
22
+ - `!=` (not equals)
23
+ - `LIKE` (contains) - Use for partial string matches on text fields only
24
+ - `STARTSWITH`
25
+ - `ENDSWITH`
26
+ - `CONTAINS`
27
+ - `ISNOTEMPTY`
28
+ - `ISEMPTY`
29
+ - `<`, `<=`, `>`, `>=` (comparisons)
30
+ - `BETWEEN` (for date ranges)
31
+
32
+ Example: `active=true^priority=1^short_descriptionLIKEerror`
33
+
34
+ **CRITICAL**: Reference fields (fields that link to other records) MUST use the `=` operator with the exact sys_id value. Never use `LIKE` with reference fields.
35
+ - ✅ Correct: `cmdb_ci=b0ccabf1c0a80009001f14fd151d8df0`
36
+ - ❌ Wrong: `cmdb_ciLIKE827b692d0ad337021abb72d3737ff401`
37
+
38
+ **Date Queries - Best Practice**:
39
+ Use `>=` and `<=` operators for date ranges. When using date-only format (YYYY-MM-DD), ServiceNow includes the entire day.
40
+ - ✅ Recommended: `sys_created_on>=2024-01-01^sys_created_on<=2024-01-31` (includes all of Jan 31)
41
+ - With time: `sys_created_on>=2024-01-01 00:00:00^sys_created_on<=2024-01-31 23:59:59`
42
+ - Avoid BETWEEN operator as date handling can be inconsistent
43
+
44
+ ### Understanding Field Types
45
+
46
+ In ServiceNow, fields can be of different types:
47
+ - **Text fields**: Store plain text (use LIKE, CONTAINS, STARTSWITH for partial matches)
48
+ - **Reference fields**: Store links to other records via sys_id (MUST use = for exact match)
49
+ - **Boolean fields**: Store true/false values
50
+ - **Date/Time fields**: Store timestamps (use comparison operators)
51
+
52
+ How to identify reference fields:
53
+ - When `sysparm_display_value=all`, reference fields return an object with {display_value, link, value}
54
+ - The 'value' property contains the sys_id
55
+ - Common reference fields: cmdb_ci, assigned_to, caller_id, company, location
56
+
57
+ ### Useful Tables
58
+
59
+ - **cmdb_ci** - Configuration items (applications, servers, infrastructure). Search by name to get sys_id.
60
+ - **change_request** - Links to CIs via cmdb_ci field. Use CI's sys_id to find who requested changes, when, and completion status.
61
+
62
+ {# Additional tables can be added by user #}
63
+
64
+ ### Discovering Tables
65
+
66
+ To discover available tables in ServiceNow, you can query the system table catalog:
67
+
68
+ ```
69
+ table_name: sys_db_object
70
+ sysparm_fields: name,label,description
71
+ sysparm_query: nameSTARTSWITHincident
72
+ ```
73
+
74
+ This will return tables with names starting with "incident". You can modify the query to search for other table patterns.
75
+
76
+ **Note**: ServiceNow contains many tables, so it's recommended to filter your search using `STARTSWITH`, `CONTAINS`, or `LIKE` operators to find relevant tables efficiently.
77
+
78
+ ### Tips for Usage
79
+
80
+ 1. Use `sysparm_view=mobile` to get a concise set of fields when exploring data
81
+ 2. Use `sysparm_fields` to specify exactly which fields you need to reduce response size
82
+ 3. The default limit is 100 records; increase it if you need more data
83
+ 4. Display values are returned by default (human-readable format)