bangonit 0.4.3 → 0.5.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/README.md +2 -1
- package/app/desktopapp/dist/main/index.js +10 -4
- package/app/desktopapp/dist/main/ipc.js +9 -24
- package/app/desktopapp/dist/main/preload.js +0 -7
- package/app/desktopapp/dist/main/tabs.js +10 -5
- package/app/desktopapp/package.json +0 -1
- package/app/replay/dist/replay.js +2 -2
- package/app/webapp/.next/standalone/app/webapp/.next/BUILD_ID +1 -1
- package/app/webapp/.next/standalone/app/webapp/.next/app-build-manifest.json +11 -11
- package/app/webapp/.next/standalone/app/webapp/.next/app-path-routes-manifest.json +1 -1
- package/app/webapp/.next/standalone/app/webapp/.next/build-manifest.json +3 -3
- package/app/webapp/.next/standalone/app/webapp/.next/prerender-manifest.json +1 -1
- package/app/webapp/.next/standalone/app/webapp/.next/required-server-files.json +1 -1
- package/app/webapp/.next/standalone/app/webapp/.next/server/app/_not-found/page.js +1 -1
- package/app/webapp/.next/standalone/app/webapp/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/app/webapp/.next/standalone/app/webapp/.next/server/app/_not-found.html +1 -1
- package/app/webapp/.next/standalone/app/webapp/.next/server/app/_not-found.rsc +1 -1
- package/app/webapp/.next/standalone/app/webapp/.next/server/app/api/chat/route.js +1 -1
- package/app/webapp/.next/standalone/app/webapp/.next/server/app/api/screenshot/route.js +1 -1
- package/app/webapp/.next/standalone/app/webapp/.next/server/app/app/page.js +6 -6
- package/app/webapp/.next/standalone/app/webapp/.next/server/app/app/page_client-reference-manifest.js +1 -1
- package/app/webapp/.next/standalone/app/webapp/.next/server/app/app.html +1 -1
- package/app/webapp/.next/standalone/app/webapp/.next/server/app/app.rsc +2 -2
- package/app/webapp/.next/standalone/app/webapp/.next/server/app/index.html +1 -1
- package/app/webapp/.next/standalone/app/webapp/.next/server/app/index.rsc +1 -1
- package/app/webapp/.next/standalone/app/webapp/.next/server/app/page.js +1 -1
- package/app/webapp/.next/standalone/app/webapp/.next/server/app/page_client-reference-manifest.js +1 -1
- package/app/webapp/.next/standalone/app/webapp/.next/server/app-paths-manifest.json +1 -1
- package/app/webapp/.next/standalone/app/webapp/.next/server/chunks/679.js +1 -1
- package/app/webapp/.next/standalone/app/webapp/.next/server/chunks/708.js +3 -3
- package/app/webapp/.next/standalone/app/webapp/.next/server/middleware-build-manifest.js +1 -1
- package/app/webapp/.next/standalone/app/webapp/.next/server/pages/404.html +1 -1
- package/app/webapp/.next/standalone/app/webapp/.next/server/pages/500.html +1 -1
- package/app/webapp/.next/standalone/app/webapp/.next/server/server-reference-manifest.json +1 -1
- package/app/webapp/.next/standalone/app/webapp/.next/static/chunks/app/app/page-533a30559a8f39fa.js +1 -0
- package/app/webapp/.next/standalone/app/webapp/.next/static/chunks/app/layout-40f50d9380154ecf.js +1 -0
- package/app/webapp/.next/{static/chunks/main-app-106dd83f859b9dfa.js → standalone/app/webapp/.next/static/chunks/main-app-76384b941f0b51cb.js} +1 -1
- package/app/webapp/.next/standalone/app/webapp/package.json +2 -6
- package/app/webapp/.next/standalone/app/webapp/server.js +1 -1
- package/app/webapp/.next/standalone/package.json +18 -6
- package/app/webapp/.next/static/chunks/app/app/page-533a30559a8f39fa.js +1 -0
- package/app/webapp/.next/static/chunks/app/layout-40f50d9380154ecf.js +1 -0
- package/app/webapp/.next/{standalone/app/webapp/.next/static/chunks/main-app-106dd83f859b9dfa.js → static/chunks/main-app-76384b941f0b51cb.js} +1 -1
- package/app/webapp/package.json +2 -6
- package/app/webapp/skills/document-review.md +1 -0
- package/app/webapp/skills/gmail.md +2 -0
- package/app/webapp/src/app/globals.css +8 -3
- package/app/webapp/src/app/layout.tsx +2 -8
- package/app/webapp/src/shared/api/chat.ts +49 -25
- package/app/webapp/src/shared/api/screenshot.ts +11 -10
- package/app/webapp/src/shared/components/AppShell.tsx +80 -109
- package/app/webapp/src/shared/components/SessionView.tsx +335 -248
- package/app/webapp/src/shared/components/VirtualCursor.tsx +13 -14
- package/app/webapp/src/shared/lib/browser/cursor.ts +2 -7
- package/app/webapp/src/shared/lib/browser/index.ts +56 -36
- package/app/webapp/src/shared/lib/browser/mouse.ts +86 -21
- package/app/webapp/src/shared/lib/browser/navigate.ts +1 -4
- package/app/webapp/src/shared/lib/browser/recorder.ts +12 -5
- package/app/webapp/src/shared/lib/browser/screenshot.ts +4 -4
- package/app/webapp/src/shared/lib/browser/snapshot.ts +9 -5
- package/app/webapp/src/shared/lib/browser/tabs.ts +1 -1
- package/app/webapp/src/shared/lib/browser/types.ts +3 -2
- package/app/webapp/src/shared/lib/browser/wait.ts +1 -1
- package/app/webapp/src/shared/lib/recorder/session-recorder.ts +1 -1
- package/app/webapp/src/shared/types/global.d.ts +8 -19
- package/app/webapp/tailwind.config.js +1 -3
- package/bin/src/cli/bangonit.js +270 -177
- package/package.json +18 -6
- package/app/webapp/.next/standalone/app/webapp/.next/static/chunks/app/app/page-03dbc2fc67c26b74.js +0 -1
- package/app/webapp/.next/standalone/app/webapp/.next/static/chunks/app/layout-57acb80d8da0067a.js +0 -1
- package/app/webapp/.next/static/chunks/app/app/page-03dbc2fc67c26b74.js +0 -1
- package/app/webapp/.next/static/chunks/app/layout-57acb80d8da0067a.js +0 -1
- /package/app/webapp/.next/standalone/app/webapp/.next/static/{z2gRF0NKwztPLZ9d7ok06 → kz1a_SRPtSly3Fe8wHKDq}/_buildManifest.js +0 -0
- /package/app/webapp/.next/standalone/app/webapp/.next/static/{z2gRF0NKwztPLZ9d7ok06 → kz1a_SRPtSly3Fe8wHKDq}/_ssgManifest.js +0 -0
- /package/app/webapp/.next/static/{z2gRF0NKwztPLZ9d7ok06 → kz1a_SRPtSly3Fe8wHKDq}/_buildManifest.js +0 -0
- /package/app/webapp/.next/static/{z2gRF0NKwztPLZ9d7ok06 → kz1a_SRPtSly3Fe8wHKDq}/_ssgManifest.js +0 -0
|
@@ -64,8 +64,8 @@ Actions:
|
|
|
64
64
|
close — close current page
|
|
65
65
|
wait --timeout=<seconds> — pause execution (use sparingly — only when you must wait for an animation or timer, not for page loads which are handled automatically)
|
|
66
66
|
|
|
67
|
-
Observation — the "observe" field:
|
|
68
|
-
|
|
67
|
+
Observation — the "observe" field (REQUIRED):
|
|
68
|
+
Every browser tool call must include "observe". Two modes:
|
|
69
69
|
- "snapshot" — takes an accessibility snapshot (fast). Returns a text tree of all visible elements with refs (e.g. ref=e5) for precise clicking. This is your default — use it almost always.
|
|
70
70
|
- "snapshot_and_screenshot" — takes a snapshot AND a screenshot analyzed by a vision subagent (slower). Use only when the snapshot alone doesn't have the info you need (e.g. visual content like images, charts, colors, spatial layout, or sites using canvas/non-semantic HTML).
|
|
71
71
|
When using "snapshot_and_screenshot", pass "prompts" (top-level, alongside "observe") — an array of questions about the screenshot. Ask multiple questions per call to minimize screenshot usage.
|
|
@@ -143,19 +143,22 @@ export function createChatHandler() {
|
|
|
143
143
|
|
|
144
144
|
const tools = {
|
|
145
145
|
browser: tool({
|
|
146
|
-
description:
|
|
146
|
+
description:
|
|
147
|
+
"Control the browser. Pass an array of actions to execute sequentially. Set 'observe' to observe the page after actions complete. The system waits for network idle before observation.",
|
|
147
148
|
inputSchema: jsonSchema({
|
|
148
149
|
type: "object",
|
|
149
150
|
properties: {
|
|
150
151
|
observe: {
|
|
151
152
|
type: "string",
|
|
152
153
|
enum: ["snapshot", "snapshot_and_screenshot"],
|
|
153
|
-
description:
|
|
154
|
+
description:
|
|
155
|
+
"How to observe the page after actions. 'snapshot' takes a fast accessibility snapshot with element refs. 'snapshot_and_screenshot' also takes a screenshot analyzed by a vision subagent (slower — use only when the snapshot doesn't have the info you need).",
|
|
154
156
|
},
|
|
155
157
|
prompts: {
|
|
156
158
|
type: "array",
|
|
157
159
|
items: { type: "string" },
|
|
158
|
-
description:
|
|
160
|
+
description:
|
|
161
|
+
"Questions for screenshot vision analysis (only used with observe: 'snapshot_and_screenshot')",
|
|
159
162
|
},
|
|
160
163
|
actions: {
|
|
161
164
|
type: "array",
|
|
@@ -164,7 +167,18 @@ export function createChatHandler() {
|
|
|
164
167
|
properties: {
|
|
165
168
|
action: {
|
|
166
169
|
type: "string",
|
|
167
|
-
enum: [
|
|
170
|
+
enum: [
|
|
171
|
+
"navigate",
|
|
172
|
+
"back",
|
|
173
|
+
"forward",
|
|
174
|
+
"mouse",
|
|
175
|
+
"type",
|
|
176
|
+
"press",
|
|
177
|
+
"upload",
|
|
178
|
+
"tabs",
|
|
179
|
+
"wait",
|
|
180
|
+
"close",
|
|
181
|
+
],
|
|
168
182
|
description: "The browser action to perform",
|
|
169
183
|
},
|
|
170
184
|
url: { type: "string", description: "URL for navigate or new tab" },
|
|
@@ -175,7 +189,10 @@ export function createChatHandler() {
|
|
|
175
189
|
items: {
|
|
176
190
|
type: "object",
|
|
177
191
|
properties: {
|
|
178
|
-
action: {
|
|
192
|
+
action: {
|
|
193
|
+
type: "string",
|
|
194
|
+
enum: ["click", "dblclick", "down", "move", "up", "wheel", "wait"],
|
|
195
|
+
},
|
|
179
196
|
ref: { type: "string", description: "Element ref from snapshot" },
|
|
180
197
|
x: { type: "number" },
|
|
181
198
|
y: { type: "number" },
|
|
@@ -198,38 +215,40 @@ export function createChatHandler() {
|
|
|
198
215
|
},
|
|
199
216
|
},
|
|
200
217
|
},
|
|
201
|
-
required: ["actions"],
|
|
218
|
+
required: ["actions", "observe"],
|
|
202
219
|
}),
|
|
203
|
-
toModelOutput({output}: {output: string}) {
|
|
220
|
+
toModelOutput({ output }: { output: string }) {
|
|
204
221
|
const parsedOutput = browserToolOutputSchema.parse(JSON.parse(output));
|
|
205
222
|
if (!parsedOutput.imageOutput) {
|
|
206
223
|
return {
|
|
207
224
|
type: "content",
|
|
208
|
-
value: [
|
|
209
|
-
|
|
210
|
-
]
|
|
211
|
-
}
|
|
225
|
+
value: [{ type: "text", text: parsedOutput.textOutput }],
|
|
226
|
+
};
|
|
212
227
|
}
|
|
213
228
|
if (parsedOutput.imageOutput.type === "not_changed") {
|
|
214
229
|
return {
|
|
215
230
|
type: "content",
|
|
216
231
|
value: [
|
|
217
|
-
{
|
|
218
|
-
|
|
219
|
-
|
|
232
|
+
{
|
|
233
|
+
type: "text",
|
|
234
|
+
text: parsedOutput.textOutput + "\n\n[screenshot did not change since last screenshot]",
|
|
235
|
+
},
|
|
236
|
+
],
|
|
237
|
+
};
|
|
220
238
|
}
|
|
221
239
|
|
|
222
240
|
return {
|
|
223
241
|
type: "content",
|
|
224
242
|
value: [
|
|
225
|
-
{type: "text", text: parsedOutput.textOutput},
|
|
226
|
-
{type: "image-data", mediaType: "image/jpeg", data: parsedOutput.imageOutput.base64}
|
|
227
|
-
]
|
|
228
|
-
}
|
|
229
|
-
}
|
|
243
|
+
{ type: "text", text: parsedOutput.textOutput },
|
|
244
|
+
{ type: "image-data", mediaType: "image/jpeg", data: parsedOutput.imageOutput.base64 },
|
|
245
|
+
],
|
|
246
|
+
};
|
|
247
|
+
},
|
|
230
248
|
}),
|
|
231
249
|
todos: tool({
|
|
232
|
-
description:
|
|
250
|
+
description:
|
|
251
|
+
"Track progress on multi-step tasks. Call with the FULL list of todos to update, or omit 'todos' to read the current list.",
|
|
233
252
|
inputSchema: jsonSchema({
|
|
234
253
|
type: "object",
|
|
235
254
|
properties: {
|
|
@@ -248,12 +267,16 @@ export function createChatHandler() {
|
|
|
248
267
|
}),
|
|
249
268
|
}),
|
|
250
269
|
report_result: tool({
|
|
251
|
-
description:
|
|
270
|
+
description:
|
|
271
|
+
"Report the final test result. You MUST call this exactly once at the end of every test run. Pass 'pass' if all test steps succeeded, or 'fail' with a summary of what went wrong.",
|
|
252
272
|
inputSchema: jsonSchema({
|
|
253
273
|
type: "object",
|
|
254
274
|
properties: {
|
|
255
275
|
result: { type: "string", enum: ["pass", "fail"], description: "Whether the test passed or failed" },
|
|
256
|
-
summary: {
|
|
276
|
+
summary: {
|
|
277
|
+
type: "string",
|
|
278
|
+
description: "Brief summary of test results. For failures, describe what went wrong.",
|
|
279
|
+
},
|
|
257
280
|
},
|
|
258
281
|
required: ["result", "summary"],
|
|
259
282
|
}),
|
|
@@ -273,7 +296,8 @@ export function createChatHandler() {
|
|
|
273
296
|
{
|
|
274
297
|
type: "compact_20260112",
|
|
275
298
|
trigger: { type: "input_tokens", value: 120000 },
|
|
276
|
-
instructions:
|
|
299
|
+
instructions:
|
|
300
|
+
"Summarize the conversation concisely. Preserve: the test plan, all URLs visited, current page/tab state, actions completed, current test state (done/in-progress/remaining), errors and resolutions, key decisions made. Include exact URLs, filenames, and values.",
|
|
277
301
|
},
|
|
278
302
|
],
|
|
279
303
|
},
|
|
@@ -30,13 +30,15 @@ async function analyzeWithClaude(image: string, prompt: string): Promise<string>
|
|
|
30
30
|
model: "claude-haiku-4-5",
|
|
31
31
|
max_tokens: 4096,
|
|
32
32
|
system: SYSTEM_PROMPT,
|
|
33
|
-
messages: [
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
33
|
+
messages: [
|
|
34
|
+
{
|
|
35
|
+
role: "user",
|
|
36
|
+
content: [
|
|
37
|
+
{ type: "image", source: { type: "base64", media_type: "image/jpeg", data: image } },
|
|
38
|
+
{ type: "text", text: prompt },
|
|
39
|
+
],
|
|
40
|
+
},
|
|
41
|
+
],
|
|
40
42
|
});
|
|
41
43
|
|
|
42
44
|
return result.content
|
|
@@ -57,9 +59,8 @@ export function createScreenshotHandler() {
|
|
|
57
59
|
headers: { "Content-Type": "application/json", ...CORS_HEADERS },
|
|
58
60
|
});
|
|
59
61
|
}
|
|
60
|
-
const prompt =
|
|
61
|
-
? prompts[0]
|
|
62
|
-
: prompts.map((p: string, i: number) => `${i + 1}. ${p}`).join("\n");
|
|
62
|
+
const prompt =
|
|
63
|
+
prompts.length === 1 ? prompts[0] : prompts.map((p: string, i: number) => `${i + 1}. ${p}`).join("\n");
|
|
63
64
|
|
|
64
65
|
try {
|
|
65
66
|
const text = await analyzeWithClaude(image, prompt);
|
|
@@ -5,19 +5,6 @@ import SessionView from "./SessionView";
|
|
|
5
5
|
import { AgentConfig, AgentStatus } from "..";
|
|
6
6
|
import { SessionRecorder } from "../lib/recorder/session-recorder";
|
|
7
7
|
|
|
8
|
-
function patchIncompleteToolCalls(messages: any[]): any[] {
|
|
9
|
-
return messages.map((msg) => {
|
|
10
|
-
if (msg.role !== "assistant" || !msg.parts) return msg;
|
|
11
|
-
const patched = msg.parts.map((part: any) => {
|
|
12
|
-
if (part.type === "tool-invocation" && part.state !== "result") {
|
|
13
|
-
return { ...part, state: "result", output: "[Interrupted — app was restarted]" };
|
|
14
|
-
}
|
|
15
|
-
return part;
|
|
16
|
-
});
|
|
17
|
-
return { ...msg, parts: patched };
|
|
18
|
-
});
|
|
19
|
-
}
|
|
20
|
-
|
|
21
8
|
interface TestPlanMessage {
|
|
22
9
|
agentIndex: number;
|
|
23
10
|
testPlan: string;
|
|
@@ -31,7 +18,19 @@ interface TestPlanMessage {
|
|
|
31
18
|
export default function AppShell() {
|
|
32
19
|
const [agents, setAgents] = useState<AgentConfig[]>([]);
|
|
33
20
|
const [agentStatuses, setAgentStatuses] = useState<Record<string, AgentStatus>>({});
|
|
34
|
-
const [agentSessions, setAgentSessions] = useState<
|
|
21
|
+
const [agentSessions, setAgentSessions] = useState<
|
|
22
|
+
Record<
|
|
23
|
+
string,
|
|
24
|
+
{
|
|
25
|
+
initialPrompt: string;
|
|
26
|
+
initialMessages?: any[];
|
|
27
|
+
initialTabs?: { id: number; url: string; title: string }[];
|
|
28
|
+
initialActiveTabId?: number;
|
|
29
|
+
initialTodos?: { content: string; status: "pending" | "in_progress" | "completed" }[];
|
|
30
|
+
planDir?: string;
|
|
31
|
+
}
|
|
32
|
+
>
|
|
33
|
+
>({});
|
|
35
34
|
const [loaded, setLoaded] = useState(false);
|
|
36
35
|
const [retryKeys, setRetryKeys] = useState<Record<string, number>>({});
|
|
37
36
|
const retryConfigRef = useRef<Map<string, { maxRetries: number; attempt: number; testPlan: string }>>(new Map());
|
|
@@ -41,68 +40,44 @@ export default function AppShell() {
|
|
|
41
40
|
const sessionRecorderRef = useRef<SessionRecorder | null>(null);
|
|
42
41
|
const browserRecordersRef = useRef<Map<string, import("../lib/browser/recorder").BrowserRecorder>>(new Map());
|
|
43
42
|
|
|
44
|
-
//
|
|
43
|
+
// Initialize with a single agent on mount
|
|
45
44
|
useEffect(() => {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
savedAgents.push(first);
|
|
55
|
-
await window.bangonit?.setAgents(savedAgents);
|
|
56
|
-
}
|
|
57
|
-
setAgents(savedAgents);
|
|
58
|
-
|
|
59
|
-
const sessions: typeof agentSessions = {};
|
|
60
|
-
const statuses: Record<string, AgentStatus> = {};
|
|
61
|
-
for (const agent of savedAgents) {
|
|
62
|
-
const session = await window.bangonit?.getAgentSession(agent.id);
|
|
63
|
-
if (session && session.messages?.length > 0) {
|
|
64
|
-
sessions[agent.id] = {
|
|
65
|
-
initialPrompt: session.initialPrompt,
|
|
66
|
-
initialMessages: patchIncompleteToolCalls(session.messages),
|
|
67
|
-
initialTabs: session.tabs,
|
|
68
|
-
initialActiveTabId: session.activeTabId,
|
|
69
|
-
initialTodos: session.todos,
|
|
70
|
-
};
|
|
71
|
-
statuses[agent.id] = "idle";
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
setAgentSessions(sessions);
|
|
75
|
-
setAgentStatuses(statuses);
|
|
76
|
-
setActiveAgentId(savedAgents[0]?.id || null);
|
|
77
|
-
setLoaded(true);
|
|
78
|
-
})();
|
|
45
|
+
const first: AgentConfig = {
|
|
46
|
+
id: crypto.randomUUID(),
|
|
47
|
+
name: "Test 1",
|
|
48
|
+
createdAt: Date.now(),
|
|
49
|
+
};
|
|
50
|
+
setAgents([first]);
|
|
51
|
+
setActiveAgentId(first.id);
|
|
52
|
+
setLoaded(true);
|
|
79
53
|
}, []);
|
|
80
54
|
|
|
81
|
-
//
|
|
55
|
+
// Ref for async access to current agents list
|
|
82
56
|
const agentsRef = useRef(agents);
|
|
83
57
|
agentsRef.current = agents;
|
|
84
|
-
useEffect(() => {
|
|
85
|
-
if (loaded && agents.length > 0) {
|
|
86
|
-
window.bangonit?.setAgents(agents);
|
|
87
|
-
}
|
|
88
|
-
}, [agents, loaded]);
|
|
89
58
|
|
|
90
59
|
// Rerun a test — clears partition, bumps key to remount SessionView
|
|
91
|
-
const rerunAgent = useCallback(
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
60
|
+
const rerunAgent = useCallback(
|
|
61
|
+
async (agentId: string) => {
|
|
62
|
+
await window.bangonit?.clearPartition?.(agentId);
|
|
63
|
+
const session = agentSessions[agentId];
|
|
64
|
+
if (!session) return;
|
|
65
|
+
setAgentStatuses((prev) => ({ ...prev, [agentId]: "running" }));
|
|
66
|
+
setRetryKeys((prev) => ({ ...prev, [agentId]: (prev[agentId] || 0) + 1 }));
|
|
67
|
+
setAgentSessions((prev) => ({
|
|
68
|
+
...prev,
|
|
69
|
+
[agentId]: { initialPrompt: session.initialPrompt },
|
|
70
|
+
}));
|
|
71
|
+
},
|
|
72
|
+
[agentSessions],
|
|
73
|
+
);
|
|
102
74
|
|
|
103
|
-
const registerBrowserRecorder = useCallback(
|
|
104
|
-
|
|
105
|
-
|
|
75
|
+
const registerBrowserRecorder = useCallback(
|
|
76
|
+
(agentId: string, recorder: import("../lib/browser/recorder").BrowserRecorder) => {
|
|
77
|
+
browserRecordersRef.current.set(agentId, recorder);
|
|
78
|
+
},
|
|
79
|
+
[],
|
|
80
|
+
);
|
|
106
81
|
|
|
107
82
|
// Track statuses in a ref for async access
|
|
108
83
|
const agentStatusesRef = useRef(agentStatuses);
|
|
@@ -126,7 +101,7 @@ export default function AppShell() {
|
|
|
126
101
|
return {
|
|
127
102
|
id: a.id,
|
|
128
103
|
name: a.name,
|
|
129
|
-
result: st === "completed" ? "pass" as const : st === "failed" ? "fail" as const : undefined,
|
|
104
|
+
result: st === "completed" ? ("pass" as const) : st === "failed" ? ("fail" as const) : undefined,
|
|
130
105
|
};
|
|
131
106
|
});
|
|
132
107
|
|
|
@@ -134,32 +109,35 @@ export default function AppShell() {
|
|
|
134
109
|
await window.bangonit?.generateReplayHtml?.({ runDir, data: JSON.stringify(data) });
|
|
135
110
|
}, []);
|
|
136
111
|
|
|
137
|
-
const handleStatusChange = useCallback(
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
112
|
+
const handleStatusChange = useCallback(
|
|
113
|
+
(agentId: string, status: "running" | "idle", result?: "pass" | "fail") => {
|
|
114
|
+
let mapped: AgentStatus;
|
|
115
|
+
if (status === "running") mapped = "running";
|
|
116
|
+
else if (result === "pass") mapped = "completed";
|
|
117
|
+
else if (result === "fail") mapped = "failed";
|
|
118
|
+
else mapped = "idle";
|
|
119
|
+
setAgentStatuses((prev) => ({ ...prev, [agentId]: mapped }));
|
|
120
|
+
|
|
121
|
+
if (recording && (result === "pass" || result === "fail")) {
|
|
122
|
+
saveReplayData(agentId, mapped);
|
|
123
|
+
}
|
|
148
124
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
125
|
+
if (result === "fail") {
|
|
126
|
+
const config = retryConfigRef.current.get(agentId);
|
|
127
|
+
if (config && config.attempt < config.maxRetries) {
|
|
128
|
+
config.attempt++;
|
|
129
|
+
window.bangonit?.emitTestRetry?.({ agentId, attempt: config.attempt, maxRetries: config.maxRetries });
|
|
130
|
+
setTimeout(() => rerunAgent(agentId), 1000);
|
|
131
|
+
}
|
|
155
132
|
}
|
|
156
|
-
}
|
|
157
|
-
|
|
133
|
+
},
|
|
134
|
+
[rerunAgent, recording, saveReplayData],
|
|
135
|
+
);
|
|
158
136
|
|
|
159
137
|
// Start a test from the UI
|
|
160
138
|
const startTest = useCallback((agentId: string, prompt: string, name?: string) => {
|
|
161
139
|
if (name) {
|
|
162
|
-
setAgents((prev) => prev.map((a) => a.id === agentId ? { ...a, name } : a));
|
|
140
|
+
setAgents((prev) => prev.map((a) => (a.id === agentId ? { ...a, name } : a)));
|
|
163
141
|
}
|
|
164
142
|
setAgentSessions((prev) => ({
|
|
165
143
|
...prev,
|
|
@@ -201,7 +179,7 @@ export default function AppShell() {
|
|
|
201
179
|
});
|
|
202
180
|
}
|
|
203
181
|
if (name) {
|
|
204
|
-
next = next.map((a, i) => i === agentIndex ? { ...a, name } : a);
|
|
182
|
+
next = next.map((a, i) => (i === agentIndex ? { ...a, name } : a));
|
|
205
183
|
}
|
|
206
184
|
return next;
|
|
207
185
|
});
|
|
@@ -211,9 +189,7 @@ export default function AppShell() {
|
|
|
211
189
|
const agent = currentAgents[agentIndex];
|
|
212
190
|
if (!agent) return currentAgents;
|
|
213
191
|
|
|
214
|
-
const prompt = extraPrompt
|
|
215
|
-
? `${testPlan}\n\n## Additional Instructions\n${extraPrompt}`
|
|
216
|
-
: testPlan;
|
|
192
|
+
const prompt = extraPrompt ? `${testPlan}\n\n## Additional Instructions\n${extraPrompt}` : testPlan;
|
|
217
193
|
|
|
218
194
|
setAgentSessions((prev) => ({
|
|
219
195
|
...prev,
|
|
@@ -244,7 +220,6 @@ export default function AppShell() {
|
|
|
244
220
|
return <div className="flex h-screen bg-zinc-950" />;
|
|
245
221
|
}
|
|
246
222
|
|
|
247
|
-
const hasAnySessions = agents.some((a) => agentSessions[a.id]);
|
|
248
223
|
const activeAgent = agents.find((a) => a.id === activeAgentId);
|
|
249
224
|
const activeHasSession = activeAgentId ? !!agentSessions[activeAgentId] : false;
|
|
250
225
|
|
|
@@ -265,11 +240,14 @@ export default function AppShell() {
|
|
|
265
240
|
</div>
|
|
266
241
|
{agents.map((agent) => {
|
|
267
242
|
const status = agentStatuses[agent.id];
|
|
268
|
-
const
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
243
|
+
const dotClass =
|
|
244
|
+
status === "running"
|
|
245
|
+
? "bg-blue-500 animate-pulse"
|
|
246
|
+
: status === "completed"
|
|
247
|
+
? "bg-green-500"
|
|
248
|
+
: status === "failed"
|
|
249
|
+
? "bg-red-500"
|
|
250
|
+
: "bg-zinc-600";
|
|
273
251
|
return (
|
|
274
252
|
<button
|
|
275
253
|
key={agent.id}
|
|
@@ -316,16 +294,12 @@ export default function AppShell() {
|
|
|
316
294
|
planDir={agentSessions[agent.id].planDir}
|
|
317
295
|
/>
|
|
318
296
|
</div>
|
|
319
|
-
) : null
|
|
297
|
+
) : null,
|
|
320
298
|
)}
|
|
321
299
|
|
|
322
300
|
{/* Prompt screen — shown for the active agent if it has no session yet (hidden in CLI mode) */}
|
|
323
301
|
{activeAgent && !activeHasSession && !cliMode && (
|
|
324
|
-
<TestPrompt
|
|
325
|
-
agentId={activeAgent.id}
|
|
326
|
-
agentName={activeAgent.name}
|
|
327
|
-
onSubmit={startTest}
|
|
328
|
-
/>
|
|
302
|
+
<TestPrompt agentId={activeAgent.id} agentName={activeAgent.name} onSubmit={startTest} />
|
|
329
303
|
)}
|
|
330
304
|
</div>
|
|
331
305
|
</div>
|
|
@@ -334,7 +308,6 @@ export default function AppShell() {
|
|
|
334
308
|
|
|
335
309
|
function TestPrompt({
|
|
336
310
|
agentId,
|
|
337
|
-
agentName,
|
|
338
311
|
onSubmit,
|
|
339
312
|
}: {
|
|
340
313
|
agentId: string;
|
|
@@ -389,9 +362,7 @@ function TestPrompt({
|
|
|
389
362
|
Run
|
|
390
363
|
</button>
|
|
391
364
|
</div>
|
|
392
|
-
<p className="text-xs text-zinc-600 mt-2">
|
|
393
|
-
Press Enter to run. Shift+Enter for a new line.
|
|
394
|
-
</p>
|
|
365
|
+
<p className="text-xs text-zinc-600 mt-2">Press Enter to run. Shift+Enter for a new line.</p>
|
|
395
366
|
<div className="mt-4">
|
|
396
367
|
<p className="text-xs text-zinc-600 mb-2">Examples:</p>
|
|
397
368
|
<div className="flex flex-col gap-1.5">
|