@smilintux/skcapstone 0.3.1 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/CUSTOM_AGENT.md +184 -0
- package/docs/GETTING_STARTED.md +3 -0
- package/package.json +1 -1
- package/scripts/archive-sessions.sh +72 -0
- package/scripts/nvidia-proxy.mjs +79 -15
- package/scripts/telegram-catchup-all.sh +136 -0
- package/src/skcapstone/blueprints/builtins/itil-operations.yaml +40 -0
- package/src/skcapstone/cli/__init__.py +2 -0
- package/src/skcapstone/cli/itil.py +434 -0
- package/src/skcapstone/coordination.py +1 -0
- package/src/skcapstone/itil.py +1104 -0
- package/src/skcapstone/mcp_server.py +258 -0
- package/src/skcapstone/mcp_tools/__init__.py +2 -0
- package/src/skcapstone/mcp_tools/gtd_tools.py +1 -1
- package/src/skcapstone/mcp_tools/itil_tools.py +657 -0
- package/src/skcapstone/scheduled_tasks.py +62 -0
- package/src/skcapstone/service_health.py +81 -2
|
@@ -0,0 +1,657 @@
|
|
|
1
|
+
"""ITIL service management tools — Incident, Problem, Change, KEDB."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from mcp.types import TextContent, Tool
|
|
6
|
+
|
|
7
|
+
from ._helpers import _error_response, _json_response, _shared_root
|
|
8
|
+
|
|
9
|
+
# ═══════════════════════════════════════════════════════════
|
|
10
|
+
# Tool Definitions
|
|
11
|
+
# ═══════════════════════════════════════════════════════════
|
|
12
|
+
|
|
13
|
+
TOOLS: list[Tool] = [
|
|
14
|
+
Tool(
|
|
15
|
+
name="itil_incident_create",
|
|
16
|
+
description=(
|
|
17
|
+
"Create a new ITIL incident for a service disruption. "
|
|
18
|
+
"Auto-creates a linked GTD item (next-action for sev1/sev2, inbox for sev3/sev4)."
|
|
19
|
+
),
|
|
20
|
+
inputSchema={
|
|
21
|
+
"type": "object",
|
|
22
|
+
"properties": {
|
|
23
|
+
"title": {
|
|
24
|
+
"type": "string",
|
|
25
|
+
"description": "Brief description of the incident",
|
|
26
|
+
},
|
|
27
|
+
"severity": {
|
|
28
|
+
"type": "string",
|
|
29
|
+
"enum": ["sev1", "sev2", "sev3", "sev4"],
|
|
30
|
+
"description": "Severity level (default: sev3)",
|
|
31
|
+
},
|
|
32
|
+
"source": {
|
|
33
|
+
"type": "string",
|
|
34
|
+
"enum": [
|
|
35
|
+
"service_health", "dreaming", "manual",
|
|
36
|
+
"daemon_error", "heartbeat",
|
|
37
|
+
],
|
|
38
|
+
"description": "Detection source (default: manual)",
|
|
39
|
+
},
|
|
40
|
+
"affected_services": {
|
|
41
|
+
"type": "array",
|
|
42
|
+
"items": {"type": "string"},
|
|
43
|
+
"description": "List of affected service names",
|
|
44
|
+
},
|
|
45
|
+
"impact": {
|
|
46
|
+
"type": "string",
|
|
47
|
+
"description": "Business impact description",
|
|
48
|
+
},
|
|
49
|
+
"managed_by": {
|
|
50
|
+
"type": "string",
|
|
51
|
+
"description": "Agent responsible for managing this incident",
|
|
52
|
+
},
|
|
53
|
+
"tags": {
|
|
54
|
+
"type": "array",
|
|
55
|
+
"items": {"type": "string"},
|
|
56
|
+
"description": "Tags for categorization",
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
"required": ["title"],
|
|
60
|
+
},
|
|
61
|
+
),
|
|
62
|
+
Tool(
|
|
63
|
+
name="itil_incident_update",
|
|
64
|
+
description=(
|
|
65
|
+
"Update an incident: transition status, escalate severity, "
|
|
66
|
+
"add timeline notes, or resolve. Valid status transitions: "
|
|
67
|
+
"detected->acknowledged->investigating->resolved->closed "
|
|
68
|
+
"(escalated branches off at any open state)."
|
|
69
|
+
),
|
|
70
|
+
inputSchema={
|
|
71
|
+
"type": "object",
|
|
72
|
+
"properties": {
|
|
73
|
+
"incident_id": {
|
|
74
|
+
"type": "string",
|
|
75
|
+
"description": "Incident ID (e.g. inc-a1b2c3d4)",
|
|
76
|
+
},
|
|
77
|
+
"agent": {
|
|
78
|
+
"type": "string",
|
|
79
|
+
"description": "Agent making the update",
|
|
80
|
+
},
|
|
81
|
+
"new_status": {
|
|
82
|
+
"type": "string",
|
|
83
|
+
"enum": [
|
|
84
|
+
"acknowledged", "investigating", "escalated",
|
|
85
|
+
"resolved", "closed",
|
|
86
|
+
],
|
|
87
|
+
"description": "New status to transition to",
|
|
88
|
+
},
|
|
89
|
+
"severity": {
|
|
90
|
+
"type": "string",
|
|
91
|
+
"enum": ["sev1", "sev2", "sev3", "sev4"],
|
|
92
|
+
"description": "New severity (for escalation/de-escalation)",
|
|
93
|
+
},
|
|
94
|
+
"note": {
|
|
95
|
+
"type": "string",
|
|
96
|
+
"description": "Timeline note",
|
|
97
|
+
},
|
|
98
|
+
"resolution_summary": {
|
|
99
|
+
"type": "string",
|
|
100
|
+
"description": "Resolution summary (when resolving)",
|
|
101
|
+
},
|
|
102
|
+
"related_problem_id": {
|
|
103
|
+
"type": "string",
|
|
104
|
+
"description": "Link to a related problem record",
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
"required": ["incident_id", "agent"],
|
|
108
|
+
},
|
|
109
|
+
),
|
|
110
|
+
Tool(
|
|
111
|
+
name="itil_incident_list",
|
|
112
|
+
description=(
|
|
113
|
+
"List ITIL incidents filtered by status, severity, or affected service."
|
|
114
|
+
),
|
|
115
|
+
inputSchema={
|
|
116
|
+
"type": "object",
|
|
117
|
+
"properties": {
|
|
118
|
+
"status": {
|
|
119
|
+
"type": "string",
|
|
120
|
+
"enum": [
|
|
121
|
+
"detected", "acknowledged", "investigating",
|
|
122
|
+
"escalated", "resolved", "closed",
|
|
123
|
+
],
|
|
124
|
+
"description": "Filter by status",
|
|
125
|
+
},
|
|
126
|
+
"severity": {
|
|
127
|
+
"type": "string",
|
|
128
|
+
"enum": ["sev1", "sev2", "sev3", "sev4"],
|
|
129
|
+
"description": "Filter by severity",
|
|
130
|
+
},
|
|
131
|
+
"service": {
|
|
132
|
+
"type": "string",
|
|
133
|
+
"description": "Filter by affected service name",
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
"required": [],
|
|
137
|
+
},
|
|
138
|
+
),
|
|
139
|
+
Tool(
|
|
140
|
+
name="itil_problem_create",
|
|
141
|
+
description=(
|
|
142
|
+
"Create a new ITIL problem record to investigate root cause. "
|
|
143
|
+
"Links to related incidents and auto-creates a GTD project."
|
|
144
|
+
),
|
|
145
|
+
inputSchema={
|
|
146
|
+
"type": "object",
|
|
147
|
+
"properties": {
|
|
148
|
+
"title": {
|
|
149
|
+
"type": "string",
|
|
150
|
+
"description": "Problem title",
|
|
151
|
+
},
|
|
152
|
+
"managed_by": {
|
|
153
|
+
"type": "string",
|
|
154
|
+
"description": "Agent responsible for investigation",
|
|
155
|
+
},
|
|
156
|
+
"related_incident_ids": {
|
|
157
|
+
"type": "array",
|
|
158
|
+
"items": {"type": "string"},
|
|
159
|
+
"description": "Related incident IDs",
|
|
160
|
+
},
|
|
161
|
+
"workaround": {
|
|
162
|
+
"type": "string",
|
|
163
|
+
"description": "Known workaround if any",
|
|
164
|
+
},
|
|
165
|
+
"tags": {
|
|
166
|
+
"type": "array",
|
|
167
|
+
"items": {"type": "string"},
|
|
168
|
+
"description": "Tags for categorization",
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
"required": ["title"],
|
|
172
|
+
},
|
|
173
|
+
),
|
|
174
|
+
Tool(
|
|
175
|
+
name="itil_problem_update",
|
|
176
|
+
description=(
|
|
177
|
+
"Update a problem record: transition status, set root cause, "
|
|
178
|
+
"add workaround, optionally create a KEDB entry. "
|
|
179
|
+
"Valid transitions: identified->analyzing->known_error->resolved."
|
|
180
|
+
),
|
|
181
|
+
inputSchema={
|
|
182
|
+
"type": "object",
|
|
183
|
+
"properties": {
|
|
184
|
+
"problem_id": {
|
|
185
|
+
"type": "string",
|
|
186
|
+
"description": "Problem ID (e.g. prb-e5f6g7h8)",
|
|
187
|
+
},
|
|
188
|
+
"agent": {
|
|
189
|
+
"type": "string",
|
|
190
|
+
"description": "Agent making the update",
|
|
191
|
+
},
|
|
192
|
+
"new_status": {
|
|
193
|
+
"type": "string",
|
|
194
|
+
"enum": ["analyzing", "known_error", "resolved"],
|
|
195
|
+
"description": "New status to transition to",
|
|
196
|
+
},
|
|
197
|
+
"root_cause": {
|
|
198
|
+
"type": "string",
|
|
199
|
+
"description": "Root cause description",
|
|
200
|
+
},
|
|
201
|
+
"workaround": {
|
|
202
|
+
"type": "string",
|
|
203
|
+
"description": "Workaround description",
|
|
204
|
+
},
|
|
205
|
+
"note": {
|
|
206
|
+
"type": "string",
|
|
207
|
+
"description": "Timeline note",
|
|
208
|
+
},
|
|
209
|
+
"create_kedb": {
|
|
210
|
+
"type": "boolean",
|
|
211
|
+
"description": "Create a KEDB entry from this problem (default: false)",
|
|
212
|
+
},
|
|
213
|
+
},
|
|
214
|
+
"required": ["problem_id", "agent"],
|
|
215
|
+
},
|
|
216
|
+
),
|
|
217
|
+
Tool(
|
|
218
|
+
name="itil_change_propose",
|
|
219
|
+
description=(
|
|
220
|
+
"Propose a change (RFC). Standard changes auto-approve. "
|
|
221
|
+
"Normal changes require CAB approval. Emergency changes have a "
|
|
222
|
+
"15-min timeout before auto-approval."
|
|
223
|
+
),
|
|
224
|
+
inputSchema={
|
|
225
|
+
"type": "object",
|
|
226
|
+
"properties": {
|
|
227
|
+
"title": {
|
|
228
|
+
"type": "string",
|
|
229
|
+
"description": "Change title",
|
|
230
|
+
},
|
|
231
|
+
"change_type": {
|
|
232
|
+
"type": "string",
|
|
233
|
+
"enum": ["standard", "normal", "emergency"],
|
|
234
|
+
"description": "Type of change (default: normal)",
|
|
235
|
+
},
|
|
236
|
+
"risk": {
|
|
237
|
+
"type": "string",
|
|
238
|
+
"enum": ["low", "medium", "high"],
|
|
239
|
+
"description": "Risk level (default: medium)",
|
|
240
|
+
},
|
|
241
|
+
"rollback_plan": {
|
|
242
|
+
"type": "string",
|
|
243
|
+
"description": "How to roll back if the change fails",
|
|
244
|
+
},
|
|
245
|
+
"test_plan": {
|
|
246
|
+
"type": "string",
|
|
247
|
+
"description": "How to verify the change works",
|
|
248
|
+
},
|
|
249
|
+
"managed_by": {
|
|
250
|
+
"type": "string",
|
|
251
|
+
"description": "Agent managing the change",
|
|
252
|
+
},
|
|
253
|
+
"implementer": {
|
|
254
|
+
"type": "string",
|
|
255
|
+
"description": "Agent who will implement the change",
|
|
256
|
+
},
|
|
257
|
+
"related_problem_id": {
|
|
258
|
+
"type": "string",
|
|
259
|
+
"description": "Related problem ID if applicable",
|
|
260
|
+
},
|
|
261
|
+
"tags": {
|
|
262
|
+
"type": "array",
|
|
263
|
+
"items": {"type": "string"},
|
|
264
|
+
"description": "Tags for categorization",
|
|
265
|
+
},
|
|
266
|
+
},
|
|
267
|
+
"required": ["title"],
|
|
268
|
+
},
|
|
269
|
+
),
|
|
270
|
+
Tool(
|
|
271
|
+
name="itil_change_update",
|
|
272
|
+
description=(
|
|
273
|
+
"Update a change: transition status (implementing, deployed, "
|
|
274
|
+
"verified, failed, closed) or add timeline notes."
|
|
275
|
+
),
|
|
276
|
+
inputSchema={
|
|
277
|
+
"type": "object",
|
|
278
|
+
"properties": {
|
|
279
|
+
"change_id": {
|
|
280
|
+
"type": "string",
|
|
281
|
+
"description": "Change ID (e.g. chg-i1j2k3l4)",
|
|
282
|
+
},
|
|
283
|
+
"agent": {
|
|
284
|
+
"type": "string",
|
|
285
|
+
"description": "Agent making the update",
|
|
286
|
+
},
|
|
287
|
+
"new_status": {
|
|
288
|
+
"type": "string",
|
|
289
|
+
"enum": [
|
|
290
|
+
"reviewing", "approved", "rejected", "implementing",
|
|
291
|
+
"deployed", "verified", "failed", "closed",
|
|
292
|
+
],
|
|
293
|
+
"description": "New status to transition to",
|
|
294
|
+
},
|
|
295
|
+
"note": {
|
|
296
|
+
"type": "string",
|
|
297
|
+
"description": "Timeline note",
|
|
298
|
+
},
|
|
299
|
+
},
|
|
300
|
+
"required": ["change_id", "agent"],
|
|
301
|
+
},
|
|
302
|
+
),
|
|
303
|
+
Tool(
|
|
304
|
+
name="itil_cab_vote",
|
|
305
|
+
description=(
|
|
306
|
+
"Submit a CAB (Change Advisory Board) vote for a proposed change. "
|
|
307
|
+
"Each agent writes its own vote file (conflict-free). "
|
|
308
|
+
"A human rejection blocks the change; a human approval unblocks it."
|
|
309
|
+
),
|
|
310
|
+
inputSchema={
|
|
311
|
+
"type": "object",
|
|
312
|
+
"properties": {
|
|
313
|
+
"change_id": {
|
|
314
|
+
"type": "string",
|
|
315
|
+
"description": "Change ID to vote on",
|
|
316
|
+
},
|
|
317
|
+
"agent": {
|
|
318
|
+
"type": "string",
|
|
319
|
+
"description": "Voting agent name",
|
|
320
|
+
},
|
|
321
|
+
"decision": {
|
|
322
|
+
"type": "string",
|
|
323
|
+
"enum": ["approved", "rejected", "abstain"],
|
|
324
|
+
"description": "Vote decision (default: abstain)",
|
|
325
|
+
},
|
|
326
|
+
"conditions": {
|
|
327
|
+
"type": "string",
|
|
328
|
+
"description": "Conditions for approval (e.g. 'deploy during maintenance window')",
|
|
329
|
+
},
|
|
330
|
+
},
|
|
331
|
+
"required": ["change_id", "agent"],
|
|
332
|
+
},
|
|
333
|
+
),
|
|
334
|
+
Tool(
|
|
335
|
+
name="itil_status",
|
|
336
|
+
description=(
|
|
337
|
+
"ITIL dashboard: open incidents by severity, active problems, "
|
|
338
|
+
"pending changes, and KEDB count."
|
|
339
|
+
),
|
|
340
|
+
inputSchema={"type": "object", "properties": {}, "required": []},
|
|
341
|
+
),
|
|
342
|
+
Tool(
|
|
343
|
+
name="itil_kedb_search",
|
|
344
|
+
description=(
|
|
345
|
+
"Search the Known Error Database by symptoms, service name, "
|
|
346
|
+
"or keywords. Returns matching entries with workarounds."
|
|
347
|
+
),
|
|
348
|
+
inputSchema={
|
|
349
|
+
"type": "object",
|
|
350
|
+
"properties": {
|
|
351
|
+
"query": {
|
|
352
|
+
"type": "string",
|
|
353
|
+
"description": "Search query (matches title, symptoms, root cause, tags)",
|
|
354
|
+
},
|
|
355
|
+
},
|
|
356
|
+
"required": ["query"],
|
|
357
|
+
},
|
|
358
|
+
),
|
|
359
|
+
]
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
# ═══════════════════════════════════════════════════════════
|
|
363
|
+
# Handlers
|
|
364
|
+
# ═══════════════════════════════════════════════════════════
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
async def _handle_itil_incident_create(args: dict) -> list[TextContent]:
|
|
368
|
+
"""Create a new incident."""
|
|
369
|
+
from ..itil import ITILManager
|
|
370
|
+
|
|
371
|
+
title = args.get("title", "").strip()
|
|
372
|
+
if not title:
|
|
373
|
+
return _error_response("title is required")
|
|
374
|
+
|
|
375
|
+
mgr = ITILManager(_shared_root())
|
|
376
|
+
incident = mgr.create_incident(
|
|
377
|
+
title=title,
|
|
378
|
+
severity=args.get("severity", "sev3"),
|
|
379
|
+
source=args.get("source", "manual"),
|
|
380
|
+
affected_services=args.get("affected_services", []),
|
|
381
|
+
impact=args.get("impact", ""),
|
|
382
|
+
managed_by=args.get("managed_by", ""),
|
|
383
|
+
tags=args.get("tags", []),
|
|
384
|
+
)
|
|
385
|
+
return _json_response({
|
|
386
|
+
"created": True,
|
|
387
|
+
"id": incident.id,
|
|
388
|
+
"title": incident.title,
|
|
389
|
+
"severity": incident.severity.value,
|
|
390
|
+
"status": incident.status.value,
|
|
391
|
+
"managed_by": incident.managed_by,
|
|
392
|
+
"gtd_item_ids": incident.gtd_item_ids,
|
|
393
|
+
})
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
async def _handle_itil_incident_update(args: dict) -> list[TextContent]:
|
|
397
|
+
"""Update an incident."""
|
|
398
|
+
from ..itil import ITILManager
|
|
399
|
+
|
|
400
|
+
incident_id = args.get("incident_id", "").strip()
|
|
401
|
+
agent = args.get("agent", "").strip()
|
|
402
|
+
if not incident_id or not agent:
|
|
403
|
+
return _error_response("incident_id and agent are required")
|
|
404
|
+
|
|
405
|
+
mgr = ITILManager(_shared_root())
|
|
406
|
+
try:
|
|
407
|
+
inc = mgr.update_incident(
|
|
408
|
+
incident_id=incident_id,
|
|
409
|
+
agent=agent,
|
|
410
|
+
new_status=args.get("new_status"),
|
|
411
|
+
severity=args.get("severity"),
|
|
412
|
+
note=args.get("note", ""),
|
|
413
|
+
resolution_summary=args.get("resolution_summary"),
|
|
414
|
+
related_problem_id=args.get("related_problem_id"),
|
|
415
|
+
)
|
|
416
|
+
return _json_response({
|
|
417
|
+
"updated": True,
|
|
418
|
+
"id": inc.id,
|
|
419
|
+
"title": inc.title,
|
|
420
|
+
"severity": inc.severity.value,
|
|
421
|
+
"status": inc.status.value,
|
|
422
|
+
"timeline_count": len(inc.timeline),
|
|
423
|
+
})
|
|
424
|
+
except ValueError as exc:
|
|
425
|
+
return _error_response(str(exc))
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
async def _handle_itil_incident_list(args: dict) -> list[TextContent]:
|
|
429
|
+
"""List incidents with optional filters."""
|
|
430
|
+
from ..itil import ITILManager
|
|
431
|
+
|
|
432
|
+
mgr = ITILManager(_shared_root())
|
|
433
|
+
incidents = mgr.list_incidents(
|
|
434
|
+
status=args.get("status"),
|
|
435
|
+
severity=args.get("severity"),
|
|
436
|
+
service=args.get("service"),
|
|
437
|
+
)
|
|
438
|
+
return _json_response({
|
|
439
|
+
"incidents": [
|
|
440
|
+
{
|
|
441
|
+
"id": i.id,
|
|
442
|
+
"title": i.title,
|
|
443
|
+
"severity": i.severity.value,
|
|
444
|
+
"status": i.status.value,
|
|
445
|
+
"managed_by": i.managed_by,
|
|
446
|
+
"affected_services": i.affected_services,
|
|
447
|
+
"detected_at": i.detected_at,
|
|
448
|
+
"resolved_at": i.resolved_at,
|
|
449
|
+
}
|
|
450
|
+
for i in incidents
|
|
451
|
+
],
|
|
452
|
+
"total": len(incidents),
|
|
453
|
+
})
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
async def _handle_itil_problem_create(args: dict) -> list[TextContent]:
|
|
457
|
+
"""Create a new problem."""
|
|
458
|
+
from ..itil import ITILManager
|
|
459
|
+
|
|
460
|
+
title = args.get("title", "").strip()
|
|
461
|
+
if not title:
|
|
462
|
+
return _error_response("title is required")
|
|
463
|
+
|
|
464
|
+
mgr = ITILManager(_shared_root())
|
|
465
|
+
problem = mgr.create_problem(
|
|
466
|
+
title=title,
|
|
467
|
+
managed_by=args.get("managed_by", ""),
|
|
468
|
+
related_incident_ids=args.get("related_incident_ids", []),
|
|
469
|
+
workaround=args.get("workaround", ""),
|
|
470
|
+
tags=args.get("tags", []),
|
|
471
|
+
)
|
|
472
|
+
return _json_response({
|
|
473
|
+
"created": True,
|
|
474
|
+
"id": problem.id,
|
|
475
|
+
"title": problem.title,
|
|
476
|
+
"status": problem.status.value,
|
|
477
|
+
"managed_by": problem.managed_by,
|
|
478
|
+
"related_incident_ids": problem.related_incident_ids,
|
|
479
|
+
})
|
|
480
|
+
|
|
481
|
+
|
|
482
|
+
async def _handle_itil_problem_update(args: dict) -> list[TextContent]:
|
|
483
|
+
"""Update a problem."""
|
|
484
|
+
from ..itil import ITILManager
|
|
485
|
+
|
|
486
|
+
problem_id = args.get("problem_id", "").strip()
|
|
487
|
+
agent = args.get("agent", "").strip()
|
|
488
|
+
if not problem_id or not agent:
|
|
489
|
+
return _error_response("problem_id and agent are required")
|
|
490
|
+
|
|
491
|
+
mgr = ITILManager(_shared_root())
|
|
492
|
+
try:
|
|
493
|
+
prb = mgr.update_problem(
|
|
494
|
+
problem_id=problem_id,
|
|
495
|
+
agent=agent,
|
|
496
|
+
new_status=args.get("new_status"),
|
|
497
|
+
root_cause=args.get("root_cause"),
|
|
498
|
+
workaround=args.get("workaround"),
|
|
499
|
+
note=args.get("note", ""),
|
|
500
|
+
create_kedb=args.get("create_kedb", False),
|
|
501
|
+
)
|
|
502
|
+
return _json_response({
|
|
503
|
+
"updated": True,
|
|
504
|
+
"id": prb.id,
|
|
505
|
+
"title": prb.title,
|
|
506
|
+
"status": prb.status.value,
|
|
507
|
+
"root_cause": prb.root_cause,
|
|
508
|
+
"kedb_id": prb.kedb_id,
|
|
509
|
+
"timeline_count": len(prb.timeline),
|
|
510
|
+
})
|
|
511
|
+
except ValueError as exc:
|
|
512
|
+
return _error_response(str(exc))
|
|
513
|
+
|
|
514
|
+
|
|
515
|
+
async def _handle_itil_change_propose(args: dict) -> list[TextContent]:
|
|
516
|
+
"""Propose a change (RFC)."""
|
|
517
|
+
from ..itil import ITILManager
|
|
518
|
+
|
|
519
|
+
title = args.get("title", "").strip()
|
|
520
|
+
if not title:
|
|
521
|
+
return _error_response("title is required")
|
|
522
|
+
|
|
523
|
+
mgr = ITILManager(_shared_root())
|
|
524
|
+
change = mgr.propose_change(
|
|
525
|
+
title=title,
|
|
526
|
+
change_type=args.get("change_type", "normal"),
|
|
527
|
+
risk=args.get("risk", "medium"),
|
|
528
|
+
rollback_plan=args.get("rollback_plan", ""),
|
|
529
|
+
test_plan=args.get("test_plan", ""),
|
|
530
|
+
managed_by=args.get("managed_by", ""),
|
|
531
|
+
implementer=args.get("implementer"),
|
|
532
|
+
related_problem_id=args.get("related_problem_id"),
|
|
533
|
+
tags=args.get("tags", []),
|
|
534
|
+
)
|
|
535
|
+
return _json_response({
|
|
536
|
+
"created": True,
|
|
537
|
+
"id": change.id,
|
|
538
|
+
"title": change.title,
|
|
539
|
+
"change_type": change.change_type.value,
|
|
540
|
+
"status": change.status.value,
|
|
541
|
+
"cab_required": change.cab_required,
|
|
542
|
+
"managed_by": change.managed_by,
|
|
543
|
+
})
|
|
544
|
+
|
|
545
|
+
|
|
546
|
+
async def _handle_itil_change_update(args: dict) -> list[TextContent]:
|
|
547
|
+
"""Update a change status."""
|
|
548
|
+
from ..itil import ITILManager
|
|
549
|
+
|
|
550
|
+
change_id = args.get("change_id", "").strip()
|
|
551
|
+
agent = args.get("agent", "").strip()
|
|
552
|
+
if not change_id or not agent:
|
|
553
|
+
return _error_response("change_id and agent are required")
|
|
554
|
+
|
|
555
|
+
mgr = ITILManager(_shared_root())
|
|
556
|
+
try:
|
|
557
|
+
chg = mgr.update_change(
|
|
558
|
+
change_id=change_id,
|
|
559
|
+
agent=agent,
|
|
560
|
+
new_status=args.get("new_status"),
|
|
561
|
+
note=args.get("note", ""),
|
|
562
|
+
)
|
|
563
|
+
return _json_response({
|
|
564
|
+
"updated": True,
|
|
565
|
+
"id": chg.id,
|
|
566
|
+
"title": chg.title,
|
|
567
|
+
"status": chg.status.value,
|
|
568
|
+
"timeline_count": len(chg.timeline),
|
|
569
|
+
})
|
|
570
|
+
except ValueError as exc:
|
|
571
|
+
return _error_response(str(exc))
|
|
572
|
+
|
|
573
|
+
|
|
574
|
+
async def _handle_itil_cab_vote(args: dict) -> list[TextContent]:
|
|
575
|
+
"""Submit a CAB vote."""
|
|
576
|
+
from ..itil import ITILManager
|
|
577
|
+
|
|
578
|
+
change_id = args.get("change_id", "").strip()
|
|
579
|
+
agent = args.get("agent", "").strip()
|
|
580
|
+
if not change_id or not agent:
|
|
581
|
+
return _error_response("change_id and agent are required")
|
|
582
|
+
|
|
583
|
+
mgr = ITILManager(_shared_root())
|
|
584
|
+
vote = mgr.submit_cab_vote(
|
|
585
|
+
change_id=change_id,
|
|
586
|
+
agent=agent,
|
|
587
|
+
decision=args.get("decision", "abstain"),
|
|
588
|
+
conditions=args.get("conditions", ""),
|
|
589
|
+
)
|
|
590
|
+
|
|
591
|
+
# Return current vote tally
|
|
592
|
+
all_votes = mgr.get_cab_votes(change_id)
|
|
593
|
+
tally = {
|
|
594
|
+
"approved": sum(1 for v in all_votes if v.decision.value == "approved"),
|
|
595
|
+
"rejected": sum(1 for v in all_votes if v.decision.value == "rejected"),
|
|
596
|
+
"abstain": sum(1 for v in all_votes if v.decision.value == "abstain"),
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
return _json_response({
|
|
600
|
+
"voted": True,
|
|
601
|
+
"change_id": vote.change_id,
|
|
602
|
+
"agent": vote.agent,
|
|
603
|
+
"decision": vote.decision.value,
|
|
604
|
+
"conditions": vote.conditions,
|
|
605
|
+
"tally": tally,
|
|
606
|
+
})
|
|
607
|
+
|
|
608
|
+
|
|
609
|
+
async def _handle_itil_status(_args: dict) -> list[TextContent]:
|
|
610
|
+
"""Return ITIL dashboard status."""
|
|
611
|
+
from ..itil import ITILManager
|
|
612
|
+
|
|
613
|
+
mgr = ITILManager(_shared_root())
|
|
614
|
+
status = mgr.get_status()
|
|
615
|
+
return _json_response(status)
|
|
616
|
+
|
|
617
|
+
|
|
618
|
+
async def _handle_itil_kedb_search(args: dict) -> list[TextContent]:
|
|
619
|
+
"""Search the Known Error Database."""
|
|
620
|
+
from ..itil import ITILManager
|
|
621
|
+
|
|
622
|
+
query = args.get("query", "").strip()
|
|
623
|
+
if not query:
|
|
624
|
+
return _error_response("query is required")
|
|
625
|
+
|
|
626
|
+
mgr = ITILManager(_shared_root())
|
|
627
|
+
results = mgr.search_kedb(query)
|
|
628
|
+
return _json_response({
|
|
629
|
+
"results": [
|
|
630
|
+
{
|
|
631
|
+
"id": e.id,
|
|
632
|
+
"title": e.title,
|
|
633
|
+
"symptoms": e.symptoms,
|
|
634
|
+
"root_cause": e.root_cause,
|
|
635
|
+
"workaround": e.workaround,
|
|
636
|
+
"permanent_fix_change_id": e.permanent_fix_change_id,
|
|
637
|
+
"related_problem_id": e.related_problem_id,
|
|
638
|
+
}
|
|
639
|
+
for e in results
|
|
640
|
+
],
|
|
641
|
+
"total": len(results),
|
|
642
|
+
"query": query,
|
|
643
|
+
})
|
|
644
|
+
|
|
645
|
+
|
|
646
|
+
HANDLERS: dict = {
|
|
647
|
+
"itil_incident_create": _handle_itil_incident_create,
|
|
648
|
+
"itil_incident_update": _handle_itil_incident_update,
|
|
649
|
+
"itil_incident_list": _handle_itil_incident_list,
|
|
650
|
+
"itil_problem_create": _handle_itil_problem_create,
|
|
651
|
+
"itil_problem_update": _handle_itil_problem_update,
|
|
652
|
+
"itil_change_propose": _handle_itil_change_propose,
|
|
653
|
+
"itil_change_update": _handle_itil_change_update,
|
|
654
|
+
"itil_cab_vote": _handle_itil_cab_vote,
|
|
655
|
+
"itil_status": _handle_itil_status,
|
|
656
|
+
"itil_kedb_search": _handle_itil_kedb_search,
|
|
657
|
+
}
|