triagent 0.1.0-alpha13 → 0.1.0-alpha18
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/package.json +3 -4
- package/src/cli/config.ts +96 -0
- package/src/index.ts +201 -3
- package/src/integrations/elasticsearch/client.ts +210 -0
- package/src/integrations/grafana/client.ts +186 -0
- package/src/integrations/kubernetes/multi-cluster.ts +199 -0
- package/src/integrations/kubernetes/types.ts +24 -0
- package/src/integrations/loki/client.ts +219 -0
- package/src/integrations/prometheus/client.ts +163 -0
- package/src/integrations/slack/client.ts +265 -0
- package/src/integrations/teams/client.ts +199 -0
- package/src/mastra/agents/debugger.ts +152 -108
- package/src/mastra/tools/approval-store.ts +180 -0
- package/src/mastra/tools/cli.ts +94 -2
- package/src/mastra/tools/cost.ts +389 -0
- package/src/mastra/tools/logs.ts +210 -0
- package/src/mastra/tools/network.ts +253 -0
- package/src/mastra/tools/prometheus.ts +221 -0
- package/src/mastra/tools/remediation.ts +365 -0
- package/src/mastra/tools/runbook.ts +186 -0
- package/src/server/routes/history.ts +207 -0
- package/src/server/routes/notifications.ts +236 -0
- package/src/server/webhook.ts +36 -2
- package/src/storage/index.ts +3 -0
- package/src/storage/investigation-history.ts +277 -0
- package/src/storage/runbook-index.ts +330 -0
- package/src/storage/types.ts +72 -0
- package/src/tui/app.tsx +492 -76
- package/src/tui/components/approval-dialog.tsx +156 -0
- package/src/tui/components/approval-modal.tsx +278 -0
- package/src/tui/components/index.ts +38 -0
- package/src/tui/components/styled-span.tsx +24 -0
- package/src/tui/components/timeline.tsx +223 -0
- package/src/tui/components/toast.tsx +101 -0
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { Hono } from "hono";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { getHistoryStore } from "../../storage/index.js";
|
|
4
|
+
import type { HistoryQueryOptions } from "../../storage/types.js";
|
|
5
|
+
|
|
6
|
+
const QueryParamsSchema = z.object({
|
|
7
|
+
limit: z.coerce.number().int().positive().max(100).optional(),
|
|
8
|
+
offset: z.coerce.number().int().nonnegative().optional(),
|
|
9
|
+
status: z.enum(["pending", "running", "completed", "failed"]).optional(),
|
|
10
|
+
cluster: z.string().optional(),
|
|
11
|
+
tags: z.string().optional(), // comma-separated
|
|
12
|
+
startDate: z.string().datetime().optional(),
|
|
13
|
+
endDate: z.string().datetime().optional(),
|
|
14
|
+
search: z.string().optional(),
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
export function createHistoryRoutes() {
|
|
18
|
+
const app = new Hono();
|
|
19
|
+
const store = getHistoryStore();
|
|
20
|
+
|
|
21
|
+
// List investigations with filtering
|
|
22
|
+
app.get("/", async (c) => {
|
|
23
|
+
try {
|
|
24
|
+
const query = c.req.query();
|
|
25
|
+
const parsed = QueryParamsSchema.safeParse(query);
|
|
26
|
+
|
|
27
|
+
if (!parsed.success) {
|
|
28
|
+
return c.json({ error: "Invalid query parameters", details: parsed.error.errors }, 400);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const options: HistoryQueryOptions = {
|
|
32
|
+
limit: parsed.data.limit,
|
|
33
|
+
offset: parsed.data.offset,
|
|
34
|
+
status: parsed.data.status,
|
|
35
|
+
cluster: parsed.data.cluster,
|
|
36
|
+
tags: parsed.data.tags?.split(",").map((t) => t.trim()),
|
|
37
|
+
startDate: parsed.data.startDate ? new Date(parsed.data.startDate) : undefined,
|
|
38
|
+
endDate: parsed.data.endDate ? new Date(parsed.data.endDate) : undefined,
|
|
39
|
+
searchQuery: parsed.data.search,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const investigations = await store.list(options);
|
|
43
|
+
|
|
44
|
+
return c.json({
|
|
45
|
+
investigations: investigations.map((inv) => ({
|
|
46
|
+
id: inv.id,
|
|
47
|
+
incident: inv.incident,
|
|
48
|
+
status: inv.status,
|
|
49
|
+
cluster: inv.cluster,
|
|
50
|
+
startedAt: inv.startedAt.toISOString(),
|
|
51
|
+
completedAt: inv.completedAt?.toISOString(),
|
|
52
|
+
tags: inv.tags,
|
|
53
|
+
toolCallCount: inv.toolCalls.length,
|
|
54
|
+
eventCount: inv.events.length,
|
|
55
|
+
})),
|
|
56
|
+
count: investigations.length,
|
|
57
|
+
});
|
|
58
|
+
} catch (error) {
|
|
59
|
+
return c.json(
|
|
60
|
+
{ error: "Internal server error", message: error instanceof Error ? error.message : String(error) },
|
|
61
|
+
500
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// Get investigation statistics
|
|
67
|
+
app.get("/stats", async (c) => {
|
|
68
|
+
try {
|
|
69
|
+
const stats = await store.getStats();
|
|
70
|
+
return c.json(stats);
|
|
71
|
+
} catch (error) {
|
|
72
|
+
return c.json(
|
|
73
|
+
{ error: "Internal server error", message: error instanceof Error ? error.message : String(error) },
|
|
74
|
+
500
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// Get a specific investigation by ID
|
|
80
|
+
app.get("/:id", async (c) => {
|
|
81
|
+
try {
|
|
82
|
+
const id = c.req.param("id");
|
|
83
|
+
const investigation = await store.get(id);
|
|
84
|
+
|
|
85
|
+
if (!investigation) {
|
|
86
|
+
return c.json({ error: "Investigation not found" }, 404);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return c.json({
|
|
90
|
+
id: investigation.id,
|
|
91
|
+
incident: investigation.incident,
|
|
92
|
+
status: investigation.status,
|
|
93
|
+
cluster: investigation.cluster,
|
|
94
|
+
startedAt: investigation.startedAt.toISOString(),
|
|
95
|
+
completedAt: investigation.completedAt?.toISOString(),
|
|
96
|
+
result: investigation.result,
|
|
97
|
+
rawResult: investigation.rawResult,
|
|
98
|
+
error: investigation.error,
|
|
99
|
+
tags: investigation.tags,
|
|
100
|
+
toolCalls: investigation.toolCalls.map((tc) => ({
|
|
101
|
+
...tc,
|
|
102
|
+
timestamp: tc.timestamp.toISOString(),
|
|
103
|
+
})),
|
|
104
|
+
events: investigation.events.map((ev) => ({
|
|
105
|
+
...ev,
|
|
106
|
+
timestamp: ev.timestamp.toISOString(),
|
|
107
|
+
})),
|
|
108
|
+
});
|
|
109
|
+
} catch (error) {
|
|
110
|
+
return c.json(
|
|
111
|
+
{ error: "Internal server error", message: error instanceof Error ? error.message : String(error) },
|
|
112
|
+
500
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// Get events for an investigation
|
|
118
|
+
app.get("/:id/events", async (c) => {
|
|
119
|
+
try {
|
|
120
|
+
const id = c.req.param("id");
|
|
121
|
+
const investigation = await store.get(id);
|
|
122
|
+
|
|
123
|
+
if (!investigation) {
|
|
124
|
+
return c.json({ error: "Investigation not found" }, 404);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return c.json({
|
|
128
|
+
events: investigation.events.map((ev) => ({
|
|
129
|
+
...ev,
|
|
130
|
+
timestamp: ev.timestamp.toISOString(),
|
|
131
|
+
})),
|
|
132
|
+
});
|
|
133
|
+
} catch (error) {
|
|
134
|
+
return c.json(
|
|
135
|
+
{ error: "Internal server error", message: error instanceof Error ? error.message : String(error) },
|
|
136
|
+
500
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// Get tool calls for an investigation
|
|
142
|
+
app.get("/:id/toolcalls", async (c) => {
|
|
143
|
+
try {
|
|
144
|
+
const id = c.req.param("id");
|
|
145
|
+
const investigation = await store.get(id);
|
|
146
|
+
|
|
147
|
+
if (!investigation) {
|
|
148
|
+
return c.json({ error: "Investigation not found" }, 404);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return c.json({
|
|
152
|
+
toolCalls: investigation.toolCalls.map((tc) => ({
|
|
153
|
+
...tc,
|
|
154
|
+
timestamp: tc.timestamp.toISOString(),
|
|
155
|
+
})),
|
|
156
|
+
});
|
|
157
|
+
} catch (error) {
|
|
158
|
+
return c.json(
|
|
159
|
+
{ error: "Internal server error", message: error instanceof Error ? error.message : String(error) },
|
|
160
|
+
500
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// Delete an investigation
|
|
166
|
+
app.delete("/:id", async (c) => {
|
|
167
|
+
try {
|
|
168
|
+
const id = c.req.param("id");
|
|
169
|
+
const deleted = await store.delete(id);
|
|
170
|
+
|
|
171
|
+
if (!deleted) {
|
|
172
|
+
return c.json({ error: "Investigation not found" }, 404);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return c.json({ message: "Investigation deleted" });
|
|
176
|
+
} catch (error) {
|
|
177
|
+
return c.json(
|
|
178
|
+
{ error: "Internal server error", message: error instanceof Error ? error.message : String(error) },
|
|
179
|
+
500
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
// Export investigation as JSON
|
|
185
|
+
app.get("/:id/export", async (c) => {
|
|
186
|
+
try {
|
|
187
|
+
const id = c.req.param("id");
|
|
188
|
+
const investigation = await store.get(id);
|
|
189
|
+
|
|
190
|
+
if (!investigation) {
|
|
191
|
+
return c.json({ error: "Investigation not found" }, 404);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
c.header("Content-Type", "application/json");
|
|
195
|
+
c.header("Content-Disposition", `attachment; filename="investigation-${id}.json"`);
|
|
196
|
+
|
|
197
|
+
return c.json(investigation);
|
|
198
|
+
} catch (error) {
|
|
199
|
+
return c.json(
|
|
200
|
+
{ error: "Internal server error", message: error instanceof Error ? error.message : String(error) },
|
|
201
|
+
500
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
return app;
|
|
207
|
+
}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { Hono } from "hono";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { getSlackClient } from "../../integrations/slack/client.js";
|
|
4
|
+
import { getTeamsClient } from "../../integrations/teams/client.js";
|
|
5
|
+
import { getHistoryStore } from "../../storage/index.js";
|
|
6
|
+
|
|
7
|
+
const InvestigationNotificationSchema = z.object({
|
|
8
|
+
investigationId: z.string(),
|
|
9
|
+
channel: z.string().optional(),
|
|
10
|
+
includeDetails: z.boolean().default(true),
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
const AlertNotificationSchema = z.object({
|
|
14
|
+
name: z.string(),
|
|
15
|
+
severity: z.enum(["critical", "warning", "info"]),
|
|
16
|
+
message: z.string(),
|
|
17
|
+
source: z.string().optional(),
|
|
18
|
+
channel: z.string().optional(),
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const CustomMessageSchema = z.object({
|
|
22
|
+
text: z.string(),
|
|
23
|
+
channel: z.string().optional(),
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
export function createNotificationRoutes() {
|
|
27
|
+
const app = new Hono();
|
|
28
|
+
|
|
29
|
+
// Send investigation summary to Slack
|
|
30
|
+
app.post("/slack/investigation", async (c) => {
|
|
31
|
+
try {
|
|
32
|
+
const slackClient = getSlackClient();
|
|
33
|
+
if (!slackClient) {
|
|
34
|
+
return c.json({ error: "Slack not configured" }, 400);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const body = await c.req.json();
|
|
38
|
+
const parsed = InvestigationNotificationSchema.safeParse(body);
|
|
39
|
+
if (!parsed.success) {
|
|
40
|
+
return c.json({ error: "Invalid request", details: parsed.error.errors }, 400);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const { investigationId, channel, includeDetails } = parsed.data;
|
|
44
|
+
const historyStore = getHistoryStore();
|
|
45
|
+
const investigation = await historyStore.get(investigationId);
|
|
46
|
+
|
|
47
|
+
if (!investigation) {
|
|
48
|
+
return c.json({ error: "Investigation not found" }, 404);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const message = slackClient.buildInvestigationMessage({
|
|
52
|
+
id: investigation.id,
|
|
53
|
+
title: investigation.incident.title,
|
|
54
|
+
status: investigation.status,
|
|
55
|
+
summary: investigation.result?.summary,
|
|
56
|
+
severity: investigation.incident.severity,
|
|
57
|
+
rootCause: includeDetails ? investigation.result?.rootCause : undefined,
|
|
58
|
+
recommendations: includeDetails
|
|
59
|
+
? investigation.result?.recommendations?.map((r) => r.action)
|
|
60
|
+
: undefined,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
if (channel) {
|
|
64
|
+
message.channel = channel;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const result = await slackClient.sendMessage(message);
|
|
68
|
+
|
|
69
|
+
if (result.ok) {
|
|
70
|
+
return c.json({ success: true, ts: result.ts, channel: result.channel });
|
|
71
|
+
} else {
|
|
72
|
+
return c.json({ error: result.error || "Failed to send message" }, 500);
|
|
73
|
+
}
|
|
74
|
+
} catch (error) {
|
|
75
|
+
return c.json(
|
|
76
|
+
{ error: "Internal server error", message: error instanceof Error ? error.message : String(error) },
|
|
77
|
+
500
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// Send alert to Slack
|
|
83
|
+
app.post("/slack/alert", async (c) => {
|
|
84
|
+
try {
|
|
85
|
+
const slackClient = getSlackClient();
|
|
86
|
+
if (!slackClient) {
|
|
87
|
+
return c.json({ error: "Slack not configured" }, 400);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const body = await c.req.json();
|
|
91
|
+
const parsed = AlertNotificationSchema.safeParse(body);
|
|
92
|
+
if (!parsed.success) {
|
|
93
|
+
return c.json({ error: "Invalid request", details: parsed.error.errors }, 400);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const message = slackClient.buildAlertMessage({
|
|
97
|
+
...parsed.data,
|
|
98
|
+
timestamp: new Date(),
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
if (parsed.data.channel) {
|
|
102
|
+
message.channel = parsed.data.channel;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const result = await slackClient.sendMessage(message);
|
|
106
|
+
|
|
107
|
+
if (result.ok) {
|
|
108
|
+
return c.json({ success: true, ts: result.ts });
|
|
109
|
+
} else {
|
|
110
|
+
return c.json({ error: result.error || "Failed to send message" }, 500);
|
|
111
|
+
}
|
|
112
|
+
} catch (error) {
|
|
113
|
+
return c.json(
|
|
114
|
+
{ error: "Internal server error", message: error instanceof Error ? error.message : String(error) },
|
|
115
|
+
500
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// Send custom message to Slack
|
|
121
|
+
app.post("/slack/message", async (c) => {
|
|
122
|
+
try {
|
|
123
|
+
const slackClient = getSlackClient();
|
|
124
|
+
if (!slackClient) {
|
|
125
|
+
return c.json({ error: "Slack not configured" }, 400);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const body = await c.req.json();
|
|
129
|
+
const parsed = CustomMessageSchema.safeParse(body);
|
|
130
|
+
if (!parsed.success) {
|
|
131
|
+
return c.json({ error: "Invalid request", details: parsed.error.errors }, 400);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const result = await slackClient.sendMessage({
|
|
135
|
+
text: parsed.data.text,
|
|
136
|
+
channel: parsed.data.channel,
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
if (result.ok) {
|
|
140
|
+
return c.json({ success: true, ts: result.ts });
|
|
141
|
+
} else {
|
|
142
|
+
return c.json({ error: result.error || "Failed to send message" }, 500);
|
|
143
|
+
}
|
|
144
|
+
} catch (error) {
|
|
145
|
+
return c.json(
|
|
146
|
+
{ error: "Internal server error", message: error instanceof Error ? error.message : String(error) },
|
|
147
|
+
500
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// Send investigation summary to Teams
|
|
153
|
+
app.post("/teams/investigation", async (c) => {
|
|
154
|
+
try {
|
|
155
|
+
const teamsClient = getTeamsClient();
|
|
156
|
+
if (!teamsClient) {
|
|
157
|
+
return c.json({ error: "Teams not configured" }, 400);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const body = await c.req.json();
|
|
161
|
+
const parsed = InvestigationNotificationSchema.safeParse(body);
|
|
162
|
+
if (!parsed.success) {
|
|
163
|
+
return c.json({ error: "Invalid request", details: parsed.error.errors }, 400);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const { investigationId, includeDetails } = parsed.data;
|
|
167
|
+
const historyStore = getHistoryStore();
|
|
168
|
+
const investigation = await historyStore.get(investigationId);
|
|
169
|
+
|
|
170
|
+
if (!investigation) {
|
|
171
|
+
return c.json({ error: "Investigation not found" }, 404);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const message = teamsClient.buildInvestigationMessage({
|
|
175
|
+
id: investigation.id,
|
|
176
|
+
title: investigation.incident.title,
|
|
177
|
+
status: investigation.status,
|
|
178
|
+
summary: investigation.result?.summary,
|
|
179
|
+
severity: investigation.incident.severity,
|
|
180
|
+
rootCause: includeDetails ? investigation.result?.rootCause : undefined,
|
|
181
|
+
recommendations: includeDetails
|
|
182
|
+
? investigation.result?.recommendations?.map((r) => r.action)
|
|
183
|
+
: undefined,
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
const success = await teamsClient.sendMessage(message);
|
|
187
|
+
|
|
188
|
+
if (success) {
|
|
189
|
+
return c.json({ success: true });
|
|
190
|
+
} else {
|
|
191
|
+
return c.json({ error: "Failed to send message" }, 500);
|
|
192
|
+
}
|
|
193
|
+
} catch (error) {
|
|
194
|
+
return c.json(
|
|
195
|
+
{ error: "Internal server error", message: error instanceof Error ? error.message : String(error) },
|
|
196
|
+
500
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
// Send alert to Teams
|
|
202
|
+
app.post("/teams/alert", async (c) => {
|
|
203
|
+
try {
|
|
204
|
+
const teamsClient = getTeamsClient();
|
|
205
|
+
if (!teamsClient) {
|
|
206
|
+
return c.json({ error: "Teams not configured" }, 400);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const body = await c.req.json();
|
|
210
|
+
const parsed = AlertNotificationSchema.safeParse(body);
|
|
211
|
+
if (!parsed.success) {
|
|
212
|
+
return c.json({ error: "Invalid request", details: parsed.error.errors }, 400);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const message = teamsClient.buildAlertMessage({
|
|
216
|
+
...parsed.data,
|
|
217
|
+
timestamp: new Date(),
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
const success = await teamsClient.sendMessage(message);
|
|
221
|
+
|
|
222
|
+
if (success) {
|
|
223
|
+
return c.json({ success: true });
|
|
224
|
+
} else {
|
|
225
|
+
return c.json({ error: "Failed to send message" }, 500);
|
|
226
|
+
}
|
|
227
|
+
} catch (error) {
|
|
228
|
+
return c.json(
|
|
229
|
+
{ error: "Internal server error", message: error instanceof Error ? error.message : String(error) },
|
|
230
|
+
500
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
return app;
|
|
236
|
+
}
|
package/src/server/webhook.ts
CHANGED
|
@@ -7,6 +7,9 @@ import {
|
|
|
7
7
|
buildIncidentPrompt,
|
|
8
8
|
type IncidentInput,
|
|
9
9
|
} from "../mastra/index.js";
|
|
10
|
+
import { createHistoryRoutes } from "./routes/history.js";
|
|
11
|
+
import { createNotificationRoutes } from "./routes/notifications.js";
|
|
12
|
+
import { getHistoryStore, type InvestigationHistory } from "../storage/index.js";
|
|
10
13
|
|
|
11
14
|
const IncidentRequestSchema = z.object({
|
|
12
15
|
title: z.string().min(1),
|
|
@@ -29,6 +32,13 @@ const investigations = new Map<string, Investigation>();
|
|
|
29
32
|
|
|
30
33
|
export function createWebhookServer() {
|
|
31
34
|
const app = new Hono();
|
|
35
|
+
const historyStore = getHistoryStore();
|
|
36
|
+
|
|
37
|
+
// Mount history routes
|
|
38
|
+
app.route("/history", createHistoryRoutes());
|
|
39
|
+
|
|
40
|
+
// Mount notification routes
|
|
41
|
+
app.route("/notifications", createNotificationRoutes());
|
|
32
42
|
|
|
33
43
|
// Health check
|
|
34
44
|
app.get("/health", (c) => {
|
|
@@ -134,6 +144,18 @@ async function runInvestigation(id: string): Promise<void> {
|
|
|
134
144
|
|
|
135
145
|
investigation.status = "running";
|
|
136
146
|
|
|
147
|
+
// Create persistent history record
|
|
148
|
+
const historyStore = getHistoryStore();
|
|
149
|
+
const historyRecord: InvestigationHistory = {
|
|
150
|
+
id,
|
|
151
|
+
incident: investigation.incident,
|
|
152
|
+
status: "running",
|
|
153
|
+
startedAt: investigation.startedAt,
|
|
154
|
+
events: [],
|
|
155
|
+
toolCalls: [],
|
|
156
|
+
};
|
|
157
|
+
await historyStore.save(historyRecord);
|
|
158
|
+
|
|
137
159
|
try {
|
|
138
160
|
const agent = getDebuggerAgent();
|
|
139
161
|
const prompt = buildIncidentPrompt(investigation.incident);
|
|
@@ -145,15 +167,21 @@ async function runInvestigation(id: string): Promise<void> {
|
|
|
145
167
|
{ role: "user", content: prompt },
|
|
146
168
|
], {
|
|
147
169
|
maxSteps: 20,
|
|
148
|
-
onStepFinish: ({ toolCalls }) => {
|
|
170
|
+
onStepFinish: async ({ toolCalls }) => {
|
|
149
171
|
if (toolCalls && toolCalls.length > 0) {
|
|
150
172
|
const toolCall = toolCalls[0];
|
|
151
|
-
const toolName = "toolName" in toolCall ? toolCall.toolName : "tool";
|
|
173
|
+
const toolName = "toolName" in toolCall ? String(toolCall.toolName) : "tool";
|
|
152
174
|
const args = "args" in toolCall ? toolCall.args : {};
|
|
153
175
|
console.log(`[Investigation ${id}] Tool: ${toolName}`);
|
|
154
176
|
if (args && typeof args === "object" && "command" in args) {
|
|
155
177
|
console.log(`[Investigation ${id}] $ ${args.command}`);
|
|
156
178
|
}
|
|
179
|
+
|
|
180
|
+
// Record tool call in history
|
|
181
|
+
await historyStore.addToolCall(id, {
|
|
182
|
+
toolName,
|
|
183
|
+
args: args as Record<string, unknown>,
|
|
184
|
+
});
|
|
157
185
|
}
|
|
158
186
|
},
|
|
159
187
|
});
|
|
@@ -162,12 +190,18 @@ async function runInvestigation(id: string): Promise<void> {
|
|
|
162
190
|
investigation.completedAt = new Date();
|
|
163
191
|
investigation.result = response.text;
|
|
164
192
|
|
|
193
|
+
// Update history with completion
|
|
194
|
+
await historyStore.updateStatus(id, "completed", undefined, response.text);
|
|
195
|
+
|
|
165
196
|
console.log(`[Investigation ${id}] Completed`);
|
|
166
197
|
} catch (error) {
|
|
167
198
|
investigation.status = "failed";
|
|
168
199
|
investigation.completedAt = new Date();
|
|
169
200
|
investigation.error = error instanceof Error ? error.message : String(error);
|
|
170
201
|
|
|
202
|
+
// Update history with failure
|
|
203
|
+
await historyStore.updateStatus(id, "failed", undefined, undefined, investigation.error);
|
|
204
|
+
|
|
171
205
|
console.error(`[Investigation ${id}] Failed:`, investigation.error);
|
|
172
206
|
}
|
|
173
207
|
}
|