triagent 0.1.0-alpha8 → 0.1.0-beta2
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/README.md +101 -1
- package/package.json +9 -3
- package/src/cli/config.ts +118 -2
- package/src/config.ts +23 -3
- package/src/index.ts +262 -6
- 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 +164 -109
- package/src/mastra/index.ts +2 -2
- 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/sandbox/bashlet.ts +76 -10
- 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 +278 -198
- package/src/tui/components/approval-dialog.tsx +147 -0
- package/src/tui/components/approval-modal.tsx +278 -0
- package/src/tui/components/centered-layout.tsx +33 -0
- package/src/tui/components/editor.tsx +87 -0
- package/src/tui/components/header.tsx +53 -0
- package/src/tui/components/index.ts +55 -0
- package/src/tui/components/message-item.tsx +131 -0
- package/src/tui/components/messages-panel.tsx +71 -0
- package/src/tui/components/status-badge.tsx +20 -0
- package/src/tui/components/status-bar.tsx +39 -0
- package/src/tui/components/styled-span.tsx +24 -0
- package/src/tui/components/timeline.tsx +223 -0
- package/src/tui/components/toast.tsx +104 -0
- package/src/tui/theme/index.ts +21 -0
- package/src/tui/theme/tokens.ts +180 -0
package/src/tui/app.tsx
CHANGED
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
/* @jsxImportSource @opentui/solid */
|
|
2
2
|
import { render } from "@opentui/solid";
|
|
3
|
-
import { createSignal,
|
|
4
|
-
import {
|
|
5
|
-
import "
|
|
3
|
+
import { createSignal, onMount, type JSX } from "solid-js";
|
|
4
|
+
import { exec } from "child_process";
|
|
5
|
+
import { promisify } from "util";
|
|
6
6
|
import { getDebuggerAgent, buildIncidentPrompt } from "../mastra/index.js";
|
|
7
7
|
import type { IncidentInput } from "../mastra/agents/debugger.js";
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
}
|
|
8
|
+
import { approvalStore } from "../mastra/tools/approval-store.js";
|
|
9
|
+
import { ToastProvider, toastSuccess, toastError, toastWarning, toastInfo } from "./components/toast.js";
|
|
10
|
+
import { ApprovalDialogProvider, useApprovalDialog } from "./components/approval-dialog.js";
|
|
11
|
+
import { Header } from "./components/header.js";
|
|
12
|
+
import { MessagesPanel, type Message } from "./components/messages-panel.js";
|
|
13
|
+
import { Editor } from "./components/editor.js";
|
|
14
|
+
import { StatusBar } from "./components/status-bar.js";
|
|
15
|
+
import { type AppStatus, type RiskLevel } from "./theme/index.js";
|
|
16
|
+
import { loadConfig } from "../config.js";
|
|
17
|
+
|
|
18
|
+
const execAsync = promisify(exec);
|
|
17
19
|
|
|
18
20
|
// Conversation history for multi-turn debugging
|
|
19
21
|
interface ConversationMessage {
|
|
@@ -33,11 +35,6 @@ function formatHistoryAsPrompt(history: ConversationMessage[], newMessage: strin
|
|
|
33
35
|
return `Previous conversation:\n${historyText}\n\nUser: ${newMessage}`;
|
|
34
36
|
}
|
|
35
37
|
|
|
36
|
-
type AppStatus = "idle" | "investigating" | "complete" | "error";
|
|
37
|
-
|
|
38
|
-
const ATTR_DIM = createTextAttributes({ dim: true });
|
|
39
|
-
const ATTR_BOLD = createTextAttributes({ bold: true });
|
|
40
|
-
|
|
41
38
|
function buildDisplayCommand(toolName: string, args: unknown): string | undefined {
|
|
42
39
|
if (!args || typeof args !== "object") return undefined;
|
|
43
40
|
|
|
@@ -45,11 +42,9 @@ function buildDisplayCommand(toolName: string, args: unknown): string | undefine
|
|
|
45
42
|
|
|
46
43
|
switch (toolName) {
|
|
47
44
|
case "cli":
|
|
48
|
-
// CLI tool has direct command
|
|
49
45
|
return "command" in a ? String(a.command) : undefined;
|
|
50
46
|
|
|
51
47
|
case "git": {
|
|
52
|
-
// Build git command: git <command> [args...] [path]
|
|
53
48
|
if (!("command" in a)) return undefined;
|
|
54
49
|
const parts = ["git", String(a.command)];
|
|
55
50
|
if ("args" in a && Array.isArray(a.args)) {
|
|
@@ -62,7 +57,6 @@ function buildDisplayCommand(toolName: string, args: unknown): string | undefine
|
|
|
62
57
|
}
|
|
63
58
|
|
|
64
59
|
case "filesystem": {
|
|
65
|
-
// Build filesystem display: <operation> <path> [pattern]
|
|
66
60
|
if (!("operation" in a)) return undefined;
|
|
67
61
|
const op = String(a.operation);
|
|
68
62
|
const path = "path" in a ? String(a.path) : "";
|
|
@@ -79,18 +73,51 @@ function buildDisplayCommand(toolName: string, args: unknown): string | undefine
|
|
|
79
73
|
}
|
|
80
74
|
|
|
81
75
|
default:
|
|
82
|
-
// Fallback: try to use command if it exists
|
|
83
76
|
return "command" in a ? String(a.command) : undefined;
|
|
84
77
|
}
|
|
85
78
|
}
|
|
86
79
|
|
|
87
|
-
|
|
80
|
+
// Pending approval state for HITL
|
|
81
|
+
interface PendingApprovalState {
|
|
82
|
+
approvalId: string;
|
|
83
|
+
command: string;
|
|
84
|
+
riskLevel: RiskLevel;
|
|
85
|
+
selectedOption: number;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function AppContent() {
|
|
88
89
|
const [messages, setMessages] = createSignal<Message[]>([]);
|
|
89
90
|
const [conversationHistory, setConversationHistory] = createSignal<ConversationMessage[]>([]);
|
|
90
91
|
const [status, setStatus] = createSignal<AppStatus>("idle");
|
|
91
92
|
const [currentTool, setCurrentTool] = createSignal<string | null>(null);
|
|
92
93
|
const [inputValue, setInputValue] = createSignal("");
|
|
93
|
-
const [
|
|
94
|
+
const [kubeContext, setKubeContext] = createSignal<string>("loading...");
|
|
95
|
+
const [modelName, setModelName] = createSignal<string>("loading...");
|
|
96
|
+
|
|
97
|
+
// HITL approval state
|
|
98
|
+
const [pendingApproval, setPendingApproval] = createSignal<PendingApprovalState | null>(null);
|
|
99
|
+
|
|
100
|
+
// Approval dialog hook
|
|
101
|
+
const { showApproval } = useApprovalDialog();
|
|
102
|
+
|
|
103
|
+
// Fetch kubectl context and config on mount
|
|
104
|
+
onMount(async () => {
|
|
105
|
+
// Load kubectl context
|
|
106
|
+
try {
|
|
107
|
+
const { stdout } = await execAsync("kubectl config current-context");
|
|
108
|
+
setKubeContext(stdout.trim());
|
|
109
|
+
} catch {
|
|
110
|
+
setKubeContext("not connected");
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Load model name from config
|
|
114
|
+
try {
|
|
115
|
+
const config = await loadConfig();
|
|
116
|
+
setModelName(config.aiModel);
|
|
117
|
+
} catch {
|
|
118
|
+
setModelName("unknown");
|
|
119
|
+
}
|
|
120
|
+
});
|
|
94
121
|
|
|
95
122
|
const addMessage = (msg: Omit<Message, "id" | "timestamp">) => {
|
|
96
123
|
setMessages((prev) => [
|
|
@@ -103,27 +130,160 @@ function App() {
|
|
|
103
130
|
]);
|
|
104
131
|
};
|
|
105
132
|
|
|
133
|
+
const handleApprovalDecision = async (approved: boolean) => {
|
|
134
|
+
const approval = pendingApproval();
|
|
135
|
+
if (!approval) return;
|
|
136
|
+
|
|
137
|
+
if (approved) {
|
|
138
|
+
const token = approvalStore.approve(approval.approvalId);
|
|
139
|
+
if (!token) {
|
|
140
|
+
addMessage({
|
|
141
|
+
role: "assistant",
|
|
142
|
+
content: "Approval expired. Please try the operation again.",
|
|
143
|
+
});
|
|
144
|
+
setPendingApproval(null);
|
|
145
|
+
setStatus("complete");
|
|
146
|
+
toastWarning("Approval expired", "Please retry the operation");
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
addMessage({
|
|
151
|
+
role: "user",
|
|
152
|
+
content: `Approved: ${approval.command}`,
|
|
153
|
+
});
|
|
154
|
+
toastSuccess("Command approved");
|
|
155
|
+
|
|
156
|
+
setPendingApproval(null);
|
|
157
|
+
const approvalMessage = `User approved the command. The approval token is: ${token}. Please execute the command: ${approval.command} with approvalToken: "${token}"`;
|
|
158
|
+
|
|
159
|
+
setConversationHistory((prev) => [
|
|
160
|
+
...prev,
|
|
161
|
+
{ role: "user", content: approvalMessage },
|
|
162
|
+
]);
|
|
163
|
+
|
|
164
|
+
setStatus("investigating");
|
|
165
|
+
continueWithApproval(approvalMessage);
|
|
166
|
+
} else {
|
|
167
|
+
approvalStore.reject(approval.approvalId);
|
|
168
|
+
addMessage({
|
|
169
|
+
role: "user",
|
|
170
|
+
content: `Rejected: ${approval.command}`,
|
|
171
|
+
});
|
|
172
|
+
addMessage({
|
|
173
|
+
role: "assistant",
|
|
174
|
+
content: "Command rejected by user. How would you like to proceed?",
|
|
175
|
+
});
|
|
176
|
+
setPendingApproval(null);
|
|
177
|
+
setStatus("complete");
|
|
178
|
+
toastWarning("Command rejected");
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
// Handle approval using dialog overlay
|
|
183
|
+
const handleApprovalRequest = async (approvalData: PendingApprovalState) => {
|
|
184
|
+
setPendingApproval(approvalData);
|
|
185
|
+
setStatus("awaiting_approval");
|
|
186
|
+
|
|
187
|
+
const approved = await showApproval({
|
|
188
|
+
command: approvalData.command,
|
|
189
|
+
riskLevel: approvalData.riskLevel,
|
|
190
|
+
description: `This ${approvalData.riskLevel}-risk operation requires your approval.`,
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
await handleApprovalDecision(approved);
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
const continueWithApproval = async (message: string) => {
|
|
197
|
+
try {
|
|
198
|
+
const agent = getDebuggerAgent();
|
|
199
|
+
let assistantContent = "";
|
|
200
|
+
|
|
201
|
+
const prompt = formatHistoryAsPrompt(conversationHistory(), message);
|
|
202
|
+
|
|
203
|
+
const stream = await agent.stream(prompt, {
|
|
204
|
+
maxSteps: 20,
|
|
205
|
+
onStepFinish: ({ toolCalls, toolResults }) => {
|
|
206
|
+
if (toolCalls && toolCalls.length > 0) {
|
|
207
|
+
const toolCallChunk = toolCalls[0] as { payload?: { toolName?: string; args?: unknown } };
|
|
208
|
+
const toolName = toolCallChunk?.payload?.toolName ?? "tool";
|
|
209
|
+
const args = toolCallChunk?.payload?.args ?? {};
|
|
210
|
+
|
|
211
|
+
const command = buildDisplayCommand(toolName, args);
|
|
212
|
+
|
|
213
|
+
addMessage({
|
|
214
|
+
role: "tool",
|
|
215
|
+
content: command ? `$ ${command}` : `Executing ${toolName}...`,
|
|
216
|
+
toolName,
|
|
217
|
+
command,
|
|
218
|
+
});
|
|
219
|
+
// Note: Tool is already complete when onStepFinish fires
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (toolResults && toolResults.length > 0) {
|
|
223
|
+
for (const toolResult of toolResults) {
|
|
224
|
+
const tr = toolResult as any;
|
|
225
|
+
const data = tr?.payload?.result ?? tr?.result ?? tr;
|
|
226
|
+
|
|
227
|
+
if (data?.requiresApproval && data?.approvalId) {
|
|
228
|
+
handleApprovalRequest({
|
|
229
|
+
approvalId: data.approvalId,
|
|
230
|
+
command: data.command || "unknown command",
|
|
231
|
+
riskLevel: (data.riskLevel as RiskLevel) || "medium",
|
|
232
|
+
selectedOption: 0,
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
},
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
for await (const chunk of stream.textStream) {
|
|
241
|
+
assistantContent += chunk;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (status() !== "awaiting_approval") {
|
|
245
|
+
addMessage({
|
|
246
|
+
role: "assistant",
|
|
247
|
+
content: assistantContent,
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
setConversationHistory((prev) => [
|
|
251
|
+
...prev,
|
|
252
|
+
{ role: "assistant", content: assistantContent },
|
|
253
|
+
]);
|
|
254
|
+
|
|
255
|
+
setStatus("complete");
|
|
256
|
+
toastSuccess("Command executed");
|
|
257
|
+
}
|
|
258
|
+
setCurrentTool(null);
|
|
259
|
+
} catch (err) {
|
|
260
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
261
|
+
setStatus("error");
|
|
262
|
+
toastError("Execution failed", errorMsg);
|
|
263
|
+
addMessage({
|
|
264
|
+
role: "assistant",
|
|
265
|
+
content: `Error: ${errorMsg}`,
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
};
|
|
269
|
+
|
|
106
270
|
const investigate = async (incident: IncidentInput) => {
|
|
107
271
|
setStatus("investigating");
|
|
108
|
-
setError(null);
|
|
109
272
|
setCurrentTool(null);
|
|
273
|
+
toastInfo("Investigation started", incident.title);
|
|
110
274
|
|
|
111
|
-
// Add user message to UI
|
|
112
275
|
addMessage({
|
|
113
276
|
role: "user",
|
|
114
277
|
content: incident.description,
|
|
115
278
|
});
|
|
116
279
|
|
|
117
|
-
// Build prompt: use full incident prompt for first message, include history for follow-ups
|
|
118
280
|
const isFirstMessage = conversationHistory().length === 0;
|
|
119
281
|
const userContent = isFirstMessage
|
|
120
282
|
? buildIncidentPrompt(incident)
|
|
121
283
|
: incident.description;
|
|
122
284
|
|
|
123
|
-
// Format prompt with conversation history
|
|
124
285
|
const prompt = formatHistoryAsPrompt(conversationHistory(), userContent);
|
|
125
286
|
|
|
126
|
-
// Add user message to conversation history
|
|
127
287
|
setConversationHistory((prev) => [
|
|
128
288
|
...prev,
|
|
129
289
|
{ role: "user", content: userContent },
|
|
@@ -131,29 +291,43 @@ function App() {
|
|
|
131
291
|
|
|
132
292
|
try {
|
|
133
293
|
const agent = getDebuggerAgent();
|
|
134
|
-
|
|
135
294
|
let assistantContent = "";
|
|
136
295
|
|
|
137
|
-
// Send the formatted prompt to the agent
|
|
138
296
|
const stream = await agent.stream(prompt, {
|
|
139
297
|
maxSteps: 20,
|
|
140
|
-
onStepFinish: (
|
|
298
|
+
onStepFinish: (stepResult) => {
|
|
299
|
+
const { toolCalls, toolResults } = stepResult;
|
|
300
|
+
|
|
141
301
|
if (toolCalls && toolCalls.length > 0) {
|
|
142
|
-
const
|
|
143
|
-
const
|
|
144
|
-
const
|
|
145
|
-
const args = payload?.args ?? {};
|
|
302
|
+
const toolCallChunk = toolCalls[0] as { payload?: { toolName?: string; args?: unknown } };
|
|
303
|
+
const toolName = toolCallChunk?.payload?.toolName ?? "tool";
|
|
304
|
+
const args = toolCallChunk?.payload?.args ?? {};
|
|
146
305
|
|
|
147
|
-
// Build display command based on tool type
|
|
148
306
|
const command = buildDisplayCommand(toolName, args);
|
|
149
307
|
|
|
150
|
-
setCurrentTool(toolName);
|
|
151
308
|
addMessage({
|
|
152
309
|
role: "tool",
|
|
153
310
|
content: command ? `$ ${command}` : `Executing ${toolName}...`,
|
|
154
311
|
toolName,
|
|
155
312
|
command,
|
|
156
313
|
});
|
|
314
|
+
// Note: Tool is already complete when onStepFinish fires
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (toolResults && toolResults.length > 0) {
|
|
318
|
+
for (const toolResult of toolResults) {
|
|
319
|
+
const tr = toolResult as any;
|
|
320
|
+
const data = tr?.payload?.result ?? tr?.result ?? tr;
|
|
321
|
+
|
|
322
|
+
if (data?.requiresApproval && data?.approvalId) {
|
|
323
|
+
handleApprovalRequest({
|
|
324
|
+
approvalId: data.approvalId,
|
|
325
|
+
command: data.command || "unknown command",
|
|
326
|
+
riskLevel: (data.riskLevel as RiskLevel) || "medium",
|
|
327
|
+
selectedOption: 0,
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
}
|
|
157
331
|
}
|
|
158
332
|
},
|
|
159
333
|
});
|
|
@@ -162,24 +336,25 @@ function App() {
|
|
|
162
336
|
assistantContent += chunk;
|
|
163
337
|
}
|
|
164
338
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
339
|
+
if (status() !== "awaiting_approval") {
|
|
340
|
+
addMessage({
|
|
341
|
+
role: "assistant",
|
|
342
|
+
content: assistantContent,
|
|
343
|
+
});
|
|
170
344
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
]);
|
|
345
|
+
setConversationHistory((prev) => [
|
|
346
|
+
...prev,
|
|
347
|
+
{ role: "assistant", content: assistantContent },
|
|
348
|
+
]);
|
|
176
349
|
|
|
177
|
-
|
|
350
|
+
setStatus("complete");
|
|
351
|
+
toastSuccess("Investigation complete");
|
|
352
|
+
}
|
|
178
353
|
setCurrentTool(null);
|
|
179
354
|
} catch (err) {
|
|
180
355
|
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
181
|
-
setError(errorMsg);
|
|
182
356
|
setStatus("error");
|
|
357
|
+
toastError("Investigation failed", errorMsg);
|
|
183
358
|
addMessage({
|
|
184
359
|
role: "assistant",
|
|
185
360
|
content: `Error: ${errorMsg}`,
|
|
@@ -203,165 +378,70 @@ function App() {
|
|
|
203
378
|
setInputValue(value);
|
|
204
379
|
};
|
|
205
380
|
|
|
206
|
-
const
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
return "green";
|
|
212
|
-
case "error":
|
|
213
|
-
return "red";
|
|
214
|
-
default:
|
|
215
|
-
return "gray";
|
|
381
|
+
const handleApprovalInput = (value: string) => {
|
|
382
|
+
if (value.toLowerCase() === "y") {
|
|
383
|
+
handleApprovalDecision(true);
|
|
384
|
+
} else if (value.toLowerCase() === "n") {
|
|
385
|
+
handleApprovalDecision(false);
|
|
216
386
|
}
|
|
217
387
|
};
|
|
218
388
|
|
|
219
|
-
const
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
default:
|
|
228
|
-
return "Ready";
|
|
389
|
+
const handleApprovalKeyDown = (key: { name: string }) => {
|
|
390
|
+
const approval = pendingApproval();
|
|
391
|
+
if (!approval) return;
|
|
392
|
+
|
|
393
|
+
if (key.name === "up" || key.name === "down") {
|
|
394
|
+
setPendingApproval({ ...approval, selectedOption: approval.selectedOption === 0 ? 1 : 0 });
|
|
395
|
+
} else if (key.name === "return") {
|
|
396
|
+
handleApprovalDecision(approval.selectedOption === 0);
|
|
229
397
|
}
|
|
230
398
|
};
|
|
231
399
|
|
|
232
400
|
return (
|
|
233
401
|
<box flexDirection="column" width="100%" height="100%">
|
|
234
402
|
{/* Header */}
|
|
235
|
-
<
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
<Show
|
|
264
|
-
when={messages().length > 0}
|
|
265
|
-
fallback={
|
|
266
|
-
<box paddingTop={2} paddingBottom={2}>
|
|
267
|
-
<text fg="gray" attributes={ATTR_DIM}>
|
|
268
|
-
Enter an incident description to start investigating...
|
|
269
|
-
</text>
|
|
270
|
-
</box>
|
|
271
|
-
}
|
|
272
|
-
>
|
|
273
|
-
<For each={messages()}>
|
|
274
|
-
{(msg) => (
|
|
275
|
-
<box flexDirection="column" marginBottom={1}>
|
|
276
|
-
<Show when={msg.role === "user"}>
|
|
277
|
-
<box flexDirection="row" gap={1}>
|
|
278
|
-
<text fg="cyan" attributes={ATTR_BOLD}>
|
|
279
|
-
You:
|
|
280
|
-
</text>
|
|
281
|
-
<text fg="white">{msg.content}</text>
|
|
282
|
-
</box>
|
|
283
|
-
</Show>
|
|
284
|
-
<Show when={msg.role === "tool"}>
|
|
285
|
-
<box flexDirection="row" gap={1} alignItems="center">
|
|
286
|
-
<Show
|
|
287
|
-
when={status() === "investigating" && msg.id === messages().filter(m => m.role === "tool").at(-1)?.id}
|
|
288
|
-
fallback={<text fg="green">✓</text>}
|
|
289
|
-
>
|
|
290
|
-
<spinner name="dots" color="blue" />
|
|
291
|
-
</Show>
|
|
292
|
-
<text fg="blue" attributes={ATTR_DIM}>
|
|
293
|
-
[{msg.toolName}]
|
|
294
|
-
</text>
|
|
295
|
-
<text fg="gray" attributes={ATTR_DIM}>
|
|
296
|
-
{msg.content}
|
|
297
|
-
</text>
|
|
298
|
-
</box>
|
|
299
|
-
</Show>
|
|
300
|
-
<Show when={msg.role === "assistant"}>
|
|
301
|
-
<box flexDirection="column">
|
|
302
|
-
<text fg="green" attributes={ATTR_BOLD}>
|
|
303
|
-
Triagent:
|
|
304
|
-
</text>
|
|
305
|
-
<text fg="white" wrapMode="word">
|
|
306
|
-
{msg.content}
|
|
307
|
-
</text>
|
|
308
|
-
</box>
|
|
309
|
-
</Show>
|
|
310
|
-
</box>
|
|
311
|
-
)}
|
|
312
|
-
</For>
|
|
313
|
-
</Show>
|
|
314
|
-
</box>
|
|
315
|
-
</scrollbox>
|
|
316
|
-
|
|
317
|
-
{/* Input Area */}
|
|
318
|
-
<box
|
|
319
|
-
borderStyle="single"
|
|
320
|
-
borderColor={status() === "investigating" ? "yellow" : "cyan"}
|
|
321
|
-
paddingLeft={1}
|
|
322
|
-
paddingRight={1}
|
|
323
|
-
flexDirection="row"
|
|
324
|
-
gap={1}
|
|
325
|
-
>
|
|
326
|
-
<text fg="cyan" attributes={ATTR_BOLD}>
|
|
327
|
-
{">"}
|
|
328
|
-
</text>
|
|
329
|
-
<input
|
|
330
|
-
flexGrow={1}
|
|
331
|
-
focused={true}
|
|
332
|
-
value={inputValue()}
|
|
333
|
-
onInput={handleInput}
|
|
334
|
-
onSubmit={handleSubmit}
|
|
335
|
-
placeholder={
|
|
336
|
-
status() === "investigating"
|
|
337
|
-
? "Investigating..."
|
|
338
|
-
: "Describe the incident..."
|
|
339
|
-
}
|
|
340
|
-
textColor="white"
|
|
341
|
-
placeholderColor="gray"
|
|
342
|
-
focusedTextColor="white"
|
|
343
|
-
focusedBackgroundColor="#1a1a1a"
|
|
344
|
-
/>
|
|
345
|
-
</box>
|
|
346
|
-
|
|
347
|
-
{/* Footer */}
|
|
348
|
-
<box
|
|
349
|
-
paddingLeft={2}
|
|
350
|
-
paddingRight={2}
|
|
351
|
-
flexDirection="row"
|
|
352
|
-
justifyContent="space-between"
|
|
353
|
-
>
|
|
354
|
-
<text fg="gray" attributes={ATTR_DIM}>
|
|
355
|
-
Press Enter to submit | Ctrl+C to quit
|
|
356
|
-
</text>
|
|
357
|
-
<text fg="gray" attributes={ATTR_DIM}>
|
|
358
|
-
{messages().length} messages
|
|
359
|
-
</text>
|
|
360
|
-
</box>
|
|
403
|
+
<Header
|
|
404
|
+
status={status()}
|
|
405
|
+
currentTool={currentTool()}
|
|
406
|
+
kubeContext={kubeContext()}
|
|
407
|
+
modelName={modelName()}
|
|
408
|
+
/>
|
|
409
|
+
|
|
410
|
+
{/* Messages Panel */}
|
|
411
|
+
<MessagesPanel
|
|
412
|
+
messages={messages()}
|
|
413
|
+
status={status()}
|
|
414
|
+
/>
|
|
415
|
+
|
|
416
|
+
{/* Editor */}
|
|
417
|
+
<Editor
|
|
418
|
+
status={status()}
|
|
419
|
+
value={inputValue()}
|
|
420
|
+
onInput={handleInput}
|
|
421
|
+
onSubmit={handleSubmit}
|
|
422
|
+
onApprovalInput={handleApprovalInput}
|
|
423
|
+
onApprovalKeyDown={handleApprovalKeyDown}
|
|
424
|
+
/>
|
|
425
|
+
|
|
426
|
+
{/* Status Bar */}
|
|
427
|
+
<StatusBar
|
|
428
|
+
status={status()}
|
|
429
|
+
messageCount={messages().length}
|
|
430
|
+
/>
|
|
361
431
|
</box>
|
|
362
432
|
);
|
|
363
433
|
}
|
|
364
434
|
|
|
435
|
+
function App() {
|
|
436
|
+
return (
|
|
437
|
+
<ToastProvider>
|
|
438
|
+
<ApprovalDialogProvider>
|
|
439
|
+
<AppContent />
|
|
440
|
+
</ApprovalDialogProvider>
|
|
441
|
+
</ToastProvider>
|
|
442
|
+
);
|
|
443
|
+
}
|
|
444
|
+
|
|
365
445
|
export interface TUIHandle {
|
|
366
446
|
shutdown: () => void;
|
|
367
447
|
handleWebhookIncident: (incident: IncidentInput) => Promise<string>;
|
|
@@ -372,13 +452,13 @@ export async function runTUI(): Promise<TUIHandle> {
|
|
|
372
452
|
|
|
373
453
|
return {
|
|
374
454
|
shutdown: () => {
|
|
375
|
-
// The render function handles cleanup
|
|
376
455
|
process.exit(0);
|
|
377
456
|
},
|
|
378
457
|
handleWebhookIncident: async (incident: IncidentInput) => {
|
|
379
|
-
// For webhook mode, we'd need to integrate differently
|
|
380
|
-
// This is a placeholder for now
|
|
381
458
|
return "Webhook incident handling not yet implemented in TUI mode";
|
|
382
459
|
},
|
|
383
460
|
};
|
|
384
461
|
}
|
|
462
|
+
|
|
463
|
+
// Export Message type for use in other components
|
|
464
|
+
export type { Message } from "./components/messages-panel.js";
|