triagent 0.1.0-alpha9 → 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 -197
- 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,28 +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 toolName =
|
|
144
|
-
const 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 ?? {};
|
|
145
305
|
|
|
146
|
-
// Build display command based on tool type
|
|
147
306
|
const command = buildDisplayCommand(toolName, args);
|
|
148
307
|
|
|
149
|
-
setCurrentTool(toolName);
|
|
150
308
|
addMessage({
|
|
151
309
|
role: "tool",
|
|
152
310
|
content: command ? `$ ${command}` : `Executing ${toolName}...`,
|
|
153
311
|
toolName,
|
|
154
312
|
command,
|
|
155
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
|
+
}
|
|
156
331
|
}
|
|
157
332
|
},
|
|
158
333
|
});
|
|
@@ -161,24 +336,25 @@ function App() {
|
|
|
161
336
|
assistantContent += chunk;
|
|
162
337
|
}
|
|
163
338
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
339
|
+
if (status() !== "awaiting_approval") {
|
|
340
|
+
addMessage({
|
|
341
|
+
role: "assistant",
|
|
342
|
+
content: assistantContent,
|
|
343
|
+
});
|
|
169
344
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
]);
|
|
345
|
+
setConversationHistory((prev) => [
|
|
346
|
+
...prev,
|
|
347
|
+
{ role: "assistant", content: assistantContent },
|
|
348
|
+
]);
|
|
175
349
|
|
|
176
|
-
|
|
350
|
+
setStatus("complete");
|
|
351
|
+
toastSuccess("Investigation complete");
|
|
352
|
+
}
|
|
177
353
|
setCurrentTool(null);
|
|
178
354
|
} catch (err) {
|
|
179
355
|
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
180
|
-
setError(errorMsg);
|
|
181
356
|
setStatus("error");
|
|
357
|
+
toastError("Investigation failed", errorMsg);
|
|
182
358
|
addMessage({
|
|
183
359
|
role: "assistant",
|
|
184
360
|
content: `Error: ${errorMsg}`,
|
|
@@ -202,165 +378,70 @@ function App() {
|
|
|
202
378
|
setInputValue(value);
|
|
203
379
|
};
|
|
204
380
|
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
return "green";
|
|
211
|
-
case "error":
|
|
212
|
-
return "red";
|
|
213
|
-
default:
|
|
214
|
-
return "gray";
|
|
381
|
+
const handleApprovalInput = (value: string) => {
|
|
382
|
+
if (value.toLowerCase() === "y") {
|
|
383
|
+
handleApprovalDecision(true);
|
|
384
|
+
} else if (value.toLowerCase() === "n") {
|
|
385
|
+
handleApprovalDecision(false);
|
|
215
386
|
}
|
|
216
387
|
};
|
|
217
388
|
|
|
218
|
-
const
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
default:
|
|
227
|
-
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);
|
|
228
397
|
}
|
|
229
398
|
};
|
|
230
399
|
|
|
231
400
|
return (
|
|
232
401
|
<box flexDirection="column" width="100%" height="100%">
|
|
233
402
|
{/* Header */}
|
|
234
|
-
<
|
|
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
|
-
<Show
|
|
263
|
-
when={messages().length > 0}
|
|
264
|
-
fallback={
|
|
265
|
-
<box paddingTop={2} paddingBottom={2}>
|
|
266
|
-
<text fg="gray" attributes={ATTR_DIM}>
|
|
267
|
-
Enter an incident description to start investigating...
|
|
268
|
-
</text>
|
|
269
|
-
</box>
|
|
270
|
-
}
|
|
271
|
-
>
|
|
272
|
-
<For each={messages()}>
|
|
273
|
-
{(msg) => (
|
|
274
|
-
<box flexDirection="column" marginBottom={1}>
|
|
275
|
-
<Show when={msg.role === "user"}>
|
|
276
|
-
<box flexDirection="row" gap={1}>
|
|
277
|
-
<text fg="cyan" attributes={ATTR_BOLD}>
|
|
278
|
-
You:
|
|
279
|
-
</text>
|
|
280
|
-
<text fg="white">{msg.content}</text>
|
|
281
|
-
</box>
|
|
282
|
-
</Show>
|
|
283
|
-
<Show when={msg.role === "tool"}>
|
|
284
|
-
<box flexDirection="row" gap={1} alignItems="center">
|
|
285
|
-
<Show
|
|
286
|
-
when={status() === "investigating" && msg.id === messages().filter(m => m.role === "tool").at(-1)?.id}
|
|
287
|
-
fallback={<text fg="green">✓</text>}
|
|
288
|
-
>
|
|
289
|
-
<spinner name="dots" color="blue" />
|
|
290
|
-
</Show>
|
|
291
|
-
<text fg="blue" attributes={ATTR_DIM}>
|
|
292
|
-
[{msg.toolName}]
|
|
293
|
-
</text>
|
|
294
|
-
<text fg="gray" attributes={ATTR_DIM}>
|
|
295
|
-
{msg.content}
|
|
296
|
-
</text>
|
|
297
|
-
</box>
|
|
298
|
-
</Show>
|
|
299
|
-
<Show when={msg.role === "assistant"}>
|
|
300
|
-
<box flexDirection="column">
|
|
301
|
-
<text fg="green" attributes={ATTR_BOLD}>
|
|
302
|
-
Triagent:
|
|
303
|
-
</text>
|
|
304
|
-
<text fg="white" wrapMode="word">
|
|
305
|
-
{msg.content}
|
|
306
|
-
</text>
|
|
307
|
-
</box>
|
|
308
|
-
</Show>
|
|
309
|
-
</box>
|
|
310
|
-
)}
|
|
311
|
-
</For>
|
|
312
|
-
</Show>
|
|
313
|
-
</box>
|
|
314
|
-
</scrollbox>
|
|
315
|
-
|
|
316
|
-
{/* Input Area */}
|
|
317
|
-
<box
|
|
318
|
-
borderStyle="single"
|
|
319
|
-
borderColor={status() === "investigating" ? "yellow" : "cyan"}
|
|
320
|
-
paddingLeft={1}
|
|
321
|
-
paddingRight={1}
|
|
322
|
-
flexDirection="row"
|
|
323
|
-
gap={1}
|
|
324
|
-
>
|
|
325
|
-
<text fg="cyan" attributes={ATTR_BOLD}>
|
|
326
|
-
{">"}
|
|
327
|
-
</text>
|
|
328
|
-
<input
|
|
329
|
-
flexGrow={1}
|
|
330
|
-
focused={true}
|
|
331
|
-
value={inputValue()}
|
|
332
|
-
onInput={handleInput}
|
|
333
|
-
onSubmit={handleSubmit}
|
|
334
|
-
placeholder={
|
|
335
|
-
status() === "investigating"
|
|
336
|
-
? "Investigating..."
|
|
337
|
-
: "Describe the incident..."
|
|
338
|
-
}
|
|
339
|
-
textColor="white"
|
|
340
|
-
placeholderColor="gray"
|
|
341
|
-
focusedTextColor="white"
|
|
342
|
-
focusedBackgroundColor="#1a1a1a"
|
|
343
|
-
/>
|
|
344
|
-
</box>
|
|
345
|
-
|
|
346
|
-
{/* Footer */}
|
|
347
|
-
<box
|
|
348
|
-
paddingLeft={2}
|
|
349
|
-
paddingRight={2}
|
|
350
|
-
flexDirection="row"
|
|
351
|
-
justifyContent="space-between"
|
|
352
|
-
>
|
|
353
|
-
<text fg="gray" attributes={ATTR_DIM}>
|
|
354
|
-
Press Enter to submit | Ctrl+C to quit
|
|
355
|
-
</text>
|
|
356
|
-
<text fg="gray" attributes={ATTR_DIM}>
|
|
357
|
-
{messages().length} messages
|
|
358
|
-
</text>
|
|
359
|
-
</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
|
+
/>
|
|
360
431
|
</box>
|
|
361
432
|
);
|
|
362
433
|
}
|
|
363
434
|
|
|
435
|
+
function App() {
|
|
436
|
+
return (
|
|
437
|
+
<ToastProvider>
|
|
438
|
+
<ApprovalDialogProvider>
|
|
439
|
+
<AppContent />
|
|
440
|
+
</ApprovalDialogProvider>
|
|
441
|
+
</ToastProvider>
|
|
442
|
+
);
|
|
443
|
+
}
|
|
444
|
+
|
|
364
445
|
export interface TUIHandle {
|
|
365
446
|
shutdown: () => void;
|
|
366
447
|
handleWebhookIncident: (incident: IncidentInput) => Promise<string>;
|
|
@@ -371,13 +452,13 @@ export async function runTUI(): Promise<TUIHandle> {
|
|
|
371
452
|
|
|
372
453
|
return {
|
|
373
454
|
shutdown: () => {
|
|
374
|
-
// The render function handles cleanup
|
|
375
455
|
process.exit(0);
|
|
376
456
|
},
|
|
377
457
|
handleWebhookIncident: async (incident: IncidentInput) => {
|
|
378
|
-
// For webhook mode, we'd need to integrate differently
|
|
379
|
-
// This is a placeholder for now
|
|
380
458
|
return "Webhook incident handling not yet implemented in TUI mode";
|
|
381
459
|
},
|
|
382
460
|
};
|
|
383
461
|
}
|
|
462
|
+
|
|
463
|
+
// Export Message type for use in other components
|
|
464
|
+
export type { Message } from "./components/messages-panel.js";
|