mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-09-01 15:32:11 +08:00
fix(agent-manager): clarify browser diagnostics and restore DevTools
This commit is contained in:
@@ -3,4 +3,4 @@
|
||||
"kilo-code": minor
|
||||
---
|
||||
|
||||
Inspect local applications in Agent Manager with embedded developer tools and review-style element feedback for precise frontend changes.
|
||||
Preview local applications in Agent Manager with embedded developer tools, grouped diagnostics, and review-style element feedback for precise frontend changes.
|
||||
|
||||
@@ -495,14 +495,6 @@ export class BrowserBroker {
|
||||
entry.response = response.status()
|
||||
}
|
||||
})
|
||||
entry.page.on("requestfailed", (request) => {
|
||||
const target = request.url()
|
||||
if (this.allowed(entry, target)) return
|
||||
entry.state.errors++
|
||||
entry.state.error = `Blocked browser request: ${new URL(target).origin}`
|
||||
this.record(entry, entry.state.error)
|
||||
this.emit(entry.state)
|
||||
})
|
||||
entry.page.on("console", (message) => {
|
||||
const type = message.type()
|
||||
if (type === "error") entry.state.errors++
|
||||
@@ -516,8 +508,7 @@ export class BrowserBroker {
|
||||
})
|
||||
entry.page.on("popup", (page) => {
|
||||
entry.state.errors++
|
||||
entry.state.error = "Blocked browser popup"
|
||||
this.record(entry, entry.state.error)
|
||||
this.record(entry, "Blocked browser popup")
|
||||
this.emit(entry.state)
|
||||
void page.close().catch((error: unknown) => this.opts.log("Browser popup close failed", error))
|
||||
})
|
||||
@@ -536,6 +527,9 @@ export class BrowserBroker {
|
||||
}
|
||||
const origin = URL.canParse(target) ? new URL(target).origin : "invalid"
|
||||
this.opts.log("Blocked browser request", { sessionId: entry.route.sessionId, origin })
|
||||
entry.state.errors++
|
||||
this.record(entry, `Blocked browser request: ${origin}`)
|
||||
this.emit(entry.state)
|
||||
await route.abort("blockedbyclient")
|
||||
})
|
||||
await entry.context.routeWebSocket("**/*", async (socket) => {
|
||||
@@ -547,7 +541,7 @@ export class BrowserBroker {
|
||||
const origin = URL.canParse(target) ? new URL(target).origin : "invalid"
|
||||
this.opts.log("Blocked browser WebSocket", { sessionId: entry.route.sessionId, origin })
|
||||
entry.state.errors++
|
||||
entry.state.error = `Blocked browser request: ${origin}`
|
||||
this.record(entry, `Blocked browser request: ${origin}`)
|
||||
this.emit(entry.state)
|
||||
await socket.close({ code: 1008, reason: "Blocked browser origin" })
|
||||
})
|
||||
|
||||
@@ -103,7 +103,6 @@ export class BrowserDevtools {
|
||||
this.targets.set(browser, target)
|
||||
const path = `/browser/devtools/${browser}/${secret}`
|
||||
const query = new URLSearchParams({
|
||||
can_dock: "false",
|
||||
ws: `127.0.0.1:${this.port}${path}/connect`,
|
||||
})
|
||||
return `http://127.0.0.1:${this.port}${path}/inspector.html?${query}`
|
||||
|
||||
@@ -16,6 +16,7 @@ Object.assign(globalThis, {
|
||||
ResizeObserver: window.ResizeObserver,
|
||||
Event: window.Event,
|
||||
MouseEvent: window.MouseEvent,
|
||||
getComputedStyle: window.getComputedStyle.bind(window),
|
||||
requestAnimationFrame: window.requestAnimationFrame.bind(window),
|
||||
cancelAnimationFrame: window.cancelAnimationFrame.bind(window),
|
||||
})
|
||||
@@ -42,6 +43,8 @@ const [labels, update] = createSignal<BrowserLabels>({
|
||||
close: "Close",
|
||||
inspect: "Select element",
|
||||
devtoolsTitle: "Developer tools",
|
||||
diagnostics: "Browser diagnostics",
|
||||
diagnosticsHint: "Recent events from the automation browser. Security blocks are not console errors.",
|
||||
empty: "Open a local page",
|
||||
noSession: "Choose a session",
|
||||
screenshotAlt: "Preview",
|
||||
@@ -82,8 +85,11 @@ assert.equal(root.querySelector('[role="alert"]'), null)
|
||||
await window.happyDOM.waitUntilComplete()
|
||||
const frame = root.querySelector(".am-browser-frame")
|
||||
assert.ok(frame)
|
||||
assert.equal(root.querySelector(".am-browser-site"), null)
|
||||
receive?.({ type: "state", value: { ...state, logs: ["[info] Updated"] } })
|
||||
assert.equal(root.querySelector(".am-browser-frame"), frame)
|
||||
assert.equal(root.querySelector(".am-browser-diagnostics button")?.textContent, "Browser diagnostics")
|
||||
assert.equal(root.querySelector(".am-browser-console"), null)
|
||||
;(root.querySelector("button[aria-label=Reload]") as HTMLButtonElement).click()
|
||||
assert.deepEqual(sent.at(-1), { type: "refresh", scope })
|
||||
receive?.({ type: "state", value: { ...state, navigation: 1 } })
|
||||
@@ -135,8 +141,37 @@ assert.equal(references[0]?.selector, "#save")
|
||||
update((value) => ({ ...value, close: "Fermer" }))
|
||||
await window.happyDOM.waitUntilComplete()
|
||||
assert.ok(root.querySelector("button[aria-label=Fermer]"))
|
||||
const blocked = "Blocked browser request: http://127.0.0.1:4097"
|
||||
const other = "Blocked browser request: http://127.0.0.1:4098"
|
||||
receive?.({
|
||||
type: "state",
|
||||
value: { ...state, navigation: 1, errors: 5, logs: [blocked, blocked, other, blocked, "[error] Failed to load"] },
|
||||
})
|
||||
assert.equal(root.querySelector('[role="alert"]'), null)
|
||||
assert.equal(root.querySelector(".am-browser-error-count"), null)
|
||||
assert.ok(root.querySelector(".am-browser-tools-action button[aria-label='Developer tools']"))
|
||||
const diagnostics = root.querySelector(".am-browser-diagnostics button") as HTMLButtonElement
|
||||
assert.equal(diagnostics.textContent, "5 errors")
|
||||
assert.equal(diagnostics.getAttribute("aria-expanded"), "false")
|
||||
assert.equal(root.querySelector(".am-browser-console"), null)
|
||||
diagnostics.click()
|
||||
await window.happyDOM.waitUntilComplete()
|
||||
assert.equal(diagnostics.getAttribute("aria-expanded"), "true")
|
||||
const entries = [...root.querySelectorAll(".am-browser-console-entry")]
|
||||
assert.equal(entries.length, 3, root.querySelector(".am-browser-diagnostics")?.outerHTML)
|
||||
assert.equal(entries.at(0)?.textContent, `${blocked}×3`)
|
||||
assert.equal(entries.at(1)?.textContent, other)
|
||||
assert.equal(entries.at(2)?.textContent, "[error] Failed to load")
|
||||
assert.equal(root.querySelector(".am-browser-frame"), refreshed)
|
||||
;(root.querySelector(".am-browser-tools-action button") as HTMLButtonElement).click()
|
||||
assert.deepEqual(sent.at(-1), { type: "devtools", scope, theme: "light" })
|
||||
receive?.({ type: "devtools", value: { scope, browserId: state.browserId, url: "about:blank" } })
|
||||
await window.happyDOM.waitUntilComplete()
|
||||
assert.ok(root.querySelector(".am-browser-devtools-frame"))
|
||||
assert.equal(root.querySelectorAll(".am-browser-console-entry").length, 3)
|
||||
assert.equal(diagnostics.getAttribute("aria-expanded"), "true")
|
||||
receive?.({ type: "state", value: { ...state, navigation: 2 } })
|
||||
assert.equal(root.querySelector(".am-browser-diagnostics"), null)
|
||||
;(root.querySelector("button[aria-label=Fermer]") as HTMLButtonElement).click()
|
||||
assert.equal(closed, 1)
|
||||
assert.deepEqual(sent.at(-1), { type: "close", scope })
|
||||
|
||||
@@ -284,6 +284,7 @@ describe("BrowserBroker", () => {
|
||||
const second = await broker.devtools("second", "project", "light")
|
||||
expect(first.browserId).not.toBe(second.browserId)
|
||||
expect(first.url).not.toContain(env.KILO_BROWSER_BROKER_TOKEN)
|
||||
expect(new URL(first.url).searchParams.has("can_dock")).toBe(false)
|
||||
const frontend = await fetch(first.url)
|
||||
expect(frontend.status).toBe(200)
|
||||
expect(await frontend.text()).toContain('<script src="./kilo-bootstrap.js"></script>')
|
||||
@@ -414,8 +415,9 @@ describe("BrowserBroker", () => {
|
||||
expect(broker.sessions()).toEqual([])
|
||||
})
|
||||
|
||||
test("keeps browser contexts isolated by session", async () => {
|
||||
test("keeps browser contexts and HTTP rejection diagnostics isolated by session", async () => {
|
||||
const contexts: Array<{ close: () => Promise<void> }> = []
|
||||
const listeners = new Map<string, (value: unknown) => void>()
|
||||
let routeHandler:
|
||||
| ((
|
||||
route: { continue: () => Promise<void>; abort: () => Promise<void> },
|
||||
@@ -429,7 +431,9 @@ describe("BrowserBroker", () => {
|
||||
url: () => "http://localhost:3000",
|
||||
title: async () => "Local app",
|
||||
screenshot: async () => Buffer.from("jpeg"),
|
||||
on: (_type: string, _listener: (...args: never[]) => void) => undefined,
|
||||
on: (type: string, listener: (value: unknown) => void) => {
|
||||
listeners.set(type, listener)
|
||||
},
|
||||
mainFrame: () => undefined,
|
||||
goto: async () => undefined,
|
||||
}
|
||||
@@ -447,6 +451,7 @@ describe("BrowserBroker", () => {
|
||||
close: async () => undefined,
|
||||
}
|
||||
const broker = new BrowserBroker({ log: () => {}, launch: async () => browser })
|
||||
brokers.push(broker)
|
||||
await broker.open({ sessionId: "one", directory: "/tmp/project" }, "http://localhost:3000")
|
||||
await broker.open({ sessionId: "two", directory: "/tmp/project" }, "http://localhost:3000")
|
||||
expect(contexts).toHaveLength(2)
|
||||
@@ -456,12 +461,26 @@ describe("BrowserBroker", () => {
|
||||
{ url: () => "http://example.com" },
|
||||
)
|
||||
expect(aborted).toBe(true)
|
||||
const blocked = broker.get("two")
|
||||
expect(blocked).toMatchObject({
|
||||
errors: 1,
|
||||
logs: ["Blocked browser request: http://example.com"],
|
||||
error: undefined,
|
||||
})
|
||||
listeners.get("requestfailed")?.({ url: () => "http://example.com" })
|
||||
expect(broker.get("two")).toEqual(blocked)
|
||||
expect(broker.get("one")).toMatchObject({ errors: 0, logs: [], error: undefined })
|
||||
aborted = false
|
||||
await routeHandler!(
|
||||
{ continue: async () => undefined, abort: async () => void (aborted = true) },
|
||||
{ url: () => "data:text/html,<script>alert(1)</script>", isNavigationRequest: () => true },
|
||||
)
|
||||
expect(aborted).toBe(true)
|
||||
expect(broker.get("two")).toMatchObject({
|
||||
errors: 2,
|
||||
logs: ["Blocked browser request: http://example.com", "Blocked browser request: null"],
|
||||
error: undefined,
|
||||
})
|
||||
await broker.close("one")
|
||||
expect(broker.get("two")?.status).toBe("ready")
|
||||
})
|
||||
@@ -744,7 +763,7 @@ describe("BrowserBroker", () => {
|
||||
expect(broker.get("feedback", "project")?.logs).toHaveLength(20)
|
||||
})
|
||||
|
||||
test("blocks browser popups instead of opening a second page", async () => {
|
||||
test("blocks browser popups without replacing navigation failures", async () => {
|
||||
const listeners = new Map<string, (...args: never[]) => void>()
|
||||
const page = {
|
||||
url: () => "http://localhost:3000/",
|
||||
@@ -755,13 +774,26 @@ describe("BrowserBroker", () => {
|
||||
},
|
||||
mainFrame: () => undefined,
|
||||
goto: async () => undefined,
|
||||
reload: async () => {
|
||||
throw new Error("Navigation failed")
|
||||
},
|
||||
}
|
||||
const broker = fixture(page)
|
||||
await broker.open({ sessionId: "popup", directory: "/tmp/project" }, "http://localhost:3000/")
|
||||
let closed = false
|
||||
listeners.get("popup")!({ close: async () => void (closed = true) } as never)
|
||||
expect(closed).toBe(true)
|
||||
expect(broker.get("popup")?.error).toBe("Blocked browser popup")
|
||||
expect(broker.get("popup")).toMatchObject({ errors: 1, logs: ["Blocked browser popup"], error: undefined })
|
||||
await expect(broker.refresh("popup")).rejects.toThrow("Navigation failed")
|
||||
closed = false
|
||||
listeners.get("popup")!({ close: async () => void (closed = true) } as never)
|
||||
expect(closed).toBe(true)
|
||||
expect(broker.get("popup")).toMatchObject({
|
||||
status: "error",
|
||||
errors: 1,
|
||||
logs: ["Blocked browser popup"],
|
||||
error: "Navigation failed",
|
||||
})
|
||||
})
|
||||
|
||||
test("allows only same-origin WebSockets", async () => {
|
||||
@@ -799,12 +831,19 @@ describe("BrowserBroker", () => {
|
||||
})
|
||||
expect(connected).toBe(true)
|
||||
expect(closed).toBe(false)
|
||||
expect(broker.get("socket")).toMatchObject({ errors: 0, logs: [], error: undefined })
|
||||
connected = false
|
||||
await handler!({
|
||||
url: () => "ws://localhost:4000/private",
|
||||
connectToServer: () => void (connected = true),
|
||||
close: async () => void (closed = true),
|
||||
})
|
||||
expect(connected).toBe(false)
|
||||
expect(closed).toBe(true)
|
||||
expect(broker.get("socket")?.error).toBe("Blocked browser request: ws://localhost:4000")
|
||||
expect(broker.get("socket")).toMatchObject({
|
||||
errors: 1,
|
||||
logs: ["Blocked browser request: ws://localhost:4000"],
|
||||
error: undefined,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -143,6 +143,8 @@ function BrowserAdapter(props: {
|
||||
close: language.t("agentManager.browser.close"),
|
||||
inspect: language.t("agentManager.browser.inspect"),
|
||||
devtoolsTitle: language.t("agentManager.browser.devtoolsTitle"),
|
||||
diagnostics: language.t("agentManager.browser.diagnostics"),
|
||||
diagnosticsHint: language.t("agentManager.browser.diagnosticsHint"),
|
||||
empty: language.t("agentManager.browser.empty"),
|
||||
noSession: language.t("agentManager.browser.noSession"),
|
||||
screenshotAlt: language.t("agentManager.browser.screenshotAlt"),
|
||||
|
||||
+4
-1
@@ -253,7 +253,10 @@ export const dict = {
|
||||
"agentManager.browser.empty": "افتح تطبيقًا محليًا لمعاينته هنا.",
|
||||
"agentManager.browser.noSession": "اختر جلسة Agent Manager أولًا.",
|
||||
"agentManager.browser.screenshotAlt": "صفحة المتصفح الحالية",
|
||||
"agentManager.browser.errors": "{{count}} أخطاء في وحدة التحكم",
|
||||
"agentManager.browser.errors": "مشكلات المتصفح: {{count}}",
|
||||
"agentManager.browser.diagnostics": "تشخيص المتصفح",
|
||||
"agentManager.browser.diagnosticsHint":
|
||||
"الأحداث الأخيرة من متصفح الأتمتة. عمليات الحظر لأسباب أمنية ليست أخطاء في وحدة التحكم.",
|
||||
|
||||
"agentManager.pr.error.gh_auth.title": "مصادقة GitHub مطلوبة",
|
||||
"agentManager.pr.error.gh_auth.description": "شغّل 'gh auth login' في الطرفية لاستعادة حالة PR.",
|
||||
|
||||
+4
-1
@@ -258,7 +258,10 @@ export const dict = {
|
||||
"agentManager.browser.empty": "Abra uma aplicação local para visualizá-la aqui.",
|
||||
"agentManager.browser.noSession": "Selecione primeiro uma sessão do Agent Manager.",
|
||||
"agentManager.browser.screenshotAlt": "Página atual do navegador",
|
||||
"agentManager.browser.errors": "{{count}} erros do console",
|
||||
"agentManager.browser.errors": "Problemas do navegador: {{count}}",
|
||||
"agentManager.browser.diagnostics": "Diagnóstico do navegador",
|
||||
"agentManager.browser.diagnosticsHint":
|
||||
"Eventos recentes do navegador de automação. Bloqueios de segurança não são erros do console.",
|
||||
|
||||
"agentManager.pr.error.gh_auth.title": "Autenticação do GitHub necessária",
|
||||
"agentManager.pr.error.gh_auth.description": "Execute 'gh auth login' no terminal para restaurar o status do PR.",
|
||||
|
||||
+4
-1
@@ -256,7 +256,10 @@ export const dict = {
|
||||
"agentManager.browser.empty": "Otvorite lokalnu aplikaciju da biste je ovdje pregledali.",
|
||||
"agentManager.browser.noSession": "Najprije odaberite sesiju aplikacije Agent Manager.",
|
||||
"agentManager.browser.screenshotAlt": "Trenutna stranica preglednika",
|
||||
"agentManager.browser.errors": "{{count}} grešaka konzole",
|
||||
"agentManager.browser.errors": "Problemi preglednika: {{count}}",
|
||||
"agentManager.browser.diagnostics": "Dijagnostika preglednika",
|
||||
"agentManager.browser.diagnosticsHint":
|
||||
"Nedavni događaji iz preglednika za automatizaciju. Sigurnosne blokade nisu greške konzole.",
|
||||
|
||||
"agentManager.pr.error.gh_auth.title": "Potrebna GitHub autentikacija",
|
||||
"agentManager.pr.error.gh_auth.description": "Pokrenite 'gh auth login' u terminalu da vratite status PR-a.",
|
||||
|
||||
+4
-1
@@ -258,7 +258,10 @@ export const dict = {
|
||||
"agentManager.browser.empty": "Åbn en lokal applikation for at få vist en forhåndsvisning her.",
|
||||
"agentManager.browser.noSession": "Vælg først en session i Agent Manager.",
|
||||
"agentManager.browser.screenshotAlt": "Aktuel browserside",
|
||||
"agentManager.browser.errors": "{{count}} konsolfejl",
|
||||
"agentManager.browser.errors": "Browserproblemer: {{count}}",
|
||||
"agentManager.browser.diagnostics": "Browserdiagnostik",
|
||||
"agentManager.browser.diagnosticsHint":
|
||||
"Seneste hændelser fra automatiseringsbrowseren. Sikkerhedsblokeringer er ikke konsolfejl.",
|
||||
|
||||
"agentManager.pr.error.gh_auth.title": "GitHub-godkendelse påkrævet",
|
||||
"agentManager.pr.error.gh_auth.description": "Kør 'gh auth login' i din terminal for at gendanne PR-status.",
|
||||
|
||||
@@ -260,7 +260,10 @@ export const dict = {
|
||||
"agentManager.browser.empty": "Öffnen Sie eine lokale Anwendung, um sie hier in der Vorschau anzuzeigen.",
|
||||
"agentManager.browser.noSession": "Wählen Sie zuerst eine Sitzung im Agent Manager aus.",
|
||||
"agentManager.browser.screenshotAlt": "Aktuelle Browserseite",
|
||||
"agentManager.browser.errors": "{{count}} Konsolenfehler",
|
||||
"agentManager.browser.errors": "Browserprobleme: {{count}}",
|
||||
"agentManager.browser.diagnostics": "Browserdiagnose",
|
||||
"agentManager.browser.diagnosticsHint":
|
||||
"Aktuelle Ereignisse aus dem Automatisierungsbrowser. Sicherheitsblockierungen sind keine Konsolenfehler.",
|
||||
|
||||
"agentManager.pr.error.gh_auth.title": "GitHub-Authentifizierung erforderlich",
|
||||
"agentManager.pr.error.gh_auth.description":
|
||||
|
||||
@@ -247,7 +247,10 @@ export const dict = {
|
||||
"agentManager.browser.empty": "Open a local application to preview it here.",
|
||||
"agentManager.browser.noSession": "Select an Agent Manager session first.",
|
||||
"agentManager.browser.screenshotAlt": "Current browser page",
|
||||
"agentManager.browser.errors": "{{count}} console errors",
|
||||
"agentManager.browser.errors": "Browser issues: {{count}}",
|
||||
"agentManager.browser.diagnostics": "Browser diagnostics",
|
||||
"agentManager.browser.diagnosticsHint":
|
||||
"Recent events from the automation browser. Security blocks are not console errors.",
|
||||
|
||||
"agentManager.import.pullRequest": "Pull Request",
|
||||
"agentManager.import.pastePrUrl": "Paste PR URL...",
|
||||
|
||||
+4
-1
@@ -259,7 +259,10 @@ export const dict = {
|
||||
"agentManager.browser.empty": "Abre una aplicación local para previsualizarla aquí.",
|
||||
"agentManager.browser.noSession": "Selecciona primero una sesión de Agent Manager.",
|
||||
"agentManager.browser.screenshotAlt": "Página actual del navegador",
|
||||
"agentManager.browser.errors": "{{count}} errores de consola",
|
||||
"agentManager.browser.errors": "Problemas del navegador: {{count}}",
|
||||
"agentManager.browser.diagnostics": "Diagnóstico del navegador",
|
||||
"agentManager.browser.diagnosticsHint":
|
||||
"Eventos recientes del navegador de automatización. Los bloqueos de seguridad no son errores de consola.",
|
||||
|
||||
"agentManager.pr.error.gh_auth.title": "Se requiere autenticación de GitHub",
|
||||
"agentManager.pr.error.gh_auth.description":
|
||||
|
||||
+3
-1
@@ -261,7 +261,9 @@ export const dict = {
|
||||
"agentManager.browser.empty": "برای پیشنمایش، یک برنامه محلی را باز کنید.",
|
||||
"agentManager.browser.noSession": "ابتدا یک جلسه Agent Manager را انتخاب کنید.",
|
||||
"agentManager.browser.screenshotAlt": "صفحه فعلی مرورگر",
|
||||
"agentManager.browser.errors": "{{count}} خطای کنسول",
|
||||
"agentManager.browser.errors": "مشکلات مرورگر: {{count}}",
|
||||
"agentManager.browser.diagnostics": "عیبیابی مرورگر",
|
||||
"agentManager.browser.diagnosticsHint": "رویدادهای اخیر مرورگر خودکار. مسدودسازیهای امنیتی خطاهای کنسول نیستند.",
|
||||
|
||||
"agentManager.pr.error.gh_auth.title": "احراز هویت GitHub لازم است",
|
||||
"agentManager.pr.error.gh_auth.description": "برای بازیابی وضعیت PR، دستور 'gh auth login' را در ترمینال اجرا کنید.",
|
||||
|
||||
+4
-1
@@ -260,7 +260,10 @@ export const dict = {
|
||||
"agentManager.browser.empty": "Ouvrez une application locale pour l'afficher ici en aperçu.",
|
||||
"agentManager.browser.noSession": "Sélectionnez d'abord une session dans Agent Manager.",
|
||||
"agentManager.browser.screenshotAlt": "Page actuelle du navigateur",
|
||||
"agentManager.browser.errors": "{{count}} erreurs de console",
|
||||
"agentManager.browser.errors": "Problèmes du navigateur : {{count}}",
|
||||
"agentManager.browser.diagnostics": "Diagnostics du navigateur",
|
||||
"agentManager.browser.diagnosticsHint":
|
||||
"Événements récents du navigateur d'automatisation. Les blocages de sécurité ne sont pas des erreurs de console.",
|
||||
|
||||
"agentManager.pr.error.gh_auth.title": "Authentification GitHub requise",
|
||||
"agentManager.pr.error.gh_auth.description":
|
||||
|
||||
+4
-1
@@ -266,7 +266,10 @@ export const dict = {
|
||||
"agentManager.browser.empty": "Apri un'applicazione locale per visualizzarla qui.",
|
||||
"agentManager.browser.noSession": "Seleziona prima una sessione di Agent Manager.",
|
||||
"agentManager.browser.screenshotAlt": "Pagina corrente del browser",
|
||||
"agentManager.browser.errors": "{{count}} errori della console",
|
||||
"agentManager.browser.errors": "Problemi del browser: {{count}}",
|
||||
"agentManager.browser.diagnostics": "Diagnostica del browser",
|
||||
"agentManager.browser.diagnosticsHint":
|
||||
"Eventi recenti del browser di automazione. I blocchi di sicurezza non sono errori della console.",
|
||||
|
||||
"agentManager.pr.error.gh_auth.title": "Autenticazione GitHub richiesta",
|
||||
"agentManager.pr.error.gh_auth.description":
|
||||
|
||||
+4
-1
@@ -257,7 +257,10 @@ export const dict = {
|
||||
"agentManager.browser.empty": "ローカルアプリケーションを開くと、ここでプレビューできます。",
|
||||
"agentManager.browser.noSession": "先に Agent Manager セッションを選択してください。",
|
||||
"agentManager.browser.screenshotAlt": "現在のブラウザーページ",
|
||||
"agentManager.browser.errors": "{{count}} 件のコンソールエラー",
|
||||
"agentManager.browser.errors": "ブラウザーの問題: {{count}} 件",
|
||||
"agentManager.browser.diagnostics": "ブラウザー診断",
|
||||
"agentManager.browser.diagnosticsHint":
|
||||
"自動操作用ブラウザーの最近のイベントです。セキュリティによるブロックはコンソールエラーではありません。",
|
||||
|
||||
"agentManager.pr.error.gh_auth.title": "GitHub認証が必要です",
|
||||
"agentManager.pr.error.gh_auth.description":
|
||||
|
||||
+3
-1
@@ -255,7 +255,9 @@ export const dict = {
|
||||
"agentManager.browser.empty": "로컬 애플리케이션을 열어 여기에서 미리 보세요.",
|
||||
"agentManager.browser.noSession": "먼저 Agent Manager 세션을 선택하세요.",
|
||||
"agentManager.browser.screenshotAlt": "현재 브라우저 페이지",
|
||||
"agentManager.browser.errors": "{{count}}개 콘솔 오류",
|
||||
"agentManager.browser.errors": "브라우저 문제: {{count}}개",
|
||||
"agentManager.browser.diagnostics": "브라우저 진단",
|
||||
"agentManager.browser.diagnosticsHint": "자동화 브라우저의 최근 이벤트입니다. 보안 차단은 콘솔 오류가 아닙니다.",
|
||||
|
||||
"agentManager.pr.error.gh_auth.title": "GitHub 인증 필요",
|
||||
"agentManager.pr.error.gh_auth.description": "PR 상태를 복원하려면 터미널에서 'gh auth login'을 실행하세요.",
|
||||
|
||||
+4
-1
@@ -263,7 +263,10 @@ export const dict = {
|
||||
"agentManager.browser.empty": "Open een lokale applicatie om deze hier te bekijken.",
|
||||
"agentManager.browser.noSession": "Selecteer eerst een sessie in Agent Manager.",
|
||||
"agentManager.browser.screenshotAlt": "Huidige browserpagina",
|
||||
"agentManager.browser.errors": "{{count}} consolefouten",
|
||||
"agentManager.browser.errors": "Browserproblemen: {{count}}",
|
||||
"agentManager.browser.diagnostics": "Browserdiagnostiek",
|
||||
"agentManager.browser.diagnosticsHint":
|
||||
"Recente gebeurtenissen van de automatiseringsbrowser. Beveiligingsblokkeringen zijn geen consolefouten.",
|
||||
|
||||
"agentManager.pr.error.gh_auth.title": "GitHub-authenticatie vereist",
|
||||
"agentManager.pr.error.gh_auth.description": "Voer 'gh auth login' uit in je terminal om de PR-status te herstellen.",
|
||||
|
||||
+4
-1
@@ -255,7 +255,10 @@ export const dict = {
|
||||
"agentManager.browser.empty": "Åpne en lokal applikasjon for å forhåndsvise den her.",
|
||||
"agentManager.browser.noSession": "Velg en økt i Agent Manager først.",
|
||||
"agentManager.browser.screenshotAlt": "Gjeldende nettleserside",
|
||||
"agentManager.browser.errors": "{{count}} konsollfeil",
|
||||
"agentManager.browser.errors": "Nettleserproblemer: {{count}}",
|
||||
"agentManager.browser.diagnostics": "Nettleserdiagnostikk",
|
||||
"agentManager.browser.diagnosticsHint":
|
||||
"Nylige hendelser fra automatiseringsnettleseren. Sikkerhetsblokkeringer er ikke konsollfeil.",
|
||||
|
||||
"agentManager.pr.error.gh_auth.title": "GitHub-autentisering kreves",
|
||||
"agentManager.pr.error.gh_auth.description": "Kjør 'gh auth login' i terminalen for å gjenopprette PR-status.",
|
||||
|
||||
+4
-1
@@ -256,7 +256,10 @@ export const dict = {
|
||||
"agentManager.browser.empty": "Otwórz lokalną aplikację, aby wyświetlić ją tutaj.",
|
||||
"agentManager.browser.noSession": "Najpierw wybierz sesję aplikacji Agent Manager.",
|
||||
"agentManager.browser.screenshotAlt": "Bieżąca strona przeglądarki",
|
||||
"agentManager.browser.errors": "{{count}} błędów konsoli",
|
||||
"agentManager.browser.errors": "Problemy przeglądarki: {{count}}",
|
||||
"agentManager.browser.diagnostics": "Diagnostyka przeglądarki",
|
||||
"agentManager.browser.diagnosticsHint":
|
||||
"Ostatnie zdarzenia z przeglądarki używanej do automatyzacji. Blokady ze względów bezpieczeństwa nie są błędami konsoli.",
|
||||
|
||||
"agentManager.pr.error.gh_auth.title": "Wymagana autoryzacja GitHub",
|
||||
"agentManager.pr.error.gh_auth.description": "Uruchom 'gh auth login' w terminalu, aby przywrócić status PR.",
|
||||
|
||||
+4
-1
@@ -258,7 +258,10 @@ export const dict = {
|
||||
"agentManager.browser.empty": "Откройте локальное приложение, чтобы просмотреть его здесь.",
|
||||
"agentManager.browser.noSession": "Сначала выберите сеанс Agent Manager.",
|
||||
"agentManager.browser.screenshotAlt": "Текущая страница браузера",
|
||||
"agentManager.browser.errors": "{{count}} ошибок в консоли",
|
||||
"agentManager.browser.errors": "Проблемы браузера: {{count}}",
|
||||
"agentManager.browser.diagnostics": "Диагностика браузера",
|
||||
"agentManager.browser.diagnosticsHint":
|
||||
"Последние события из браузера, используемого для автоматизации. Блокировки в целях безопасности не являются ошибками консоли.",
|
||||
|
||||
"agentManager.pr.error.gh_auth.title": "Требуется аутентификация GitHub",
|
||||
"agentManager.pr.error.gh_auth.description": "Выполните 'gh auth login' в терминале, чтобы восстановить статус PR.",
|
||||
|
||||
+4
-1
@@ -251,7 +251,10 @@ export const dict = {
|
||||
"agentManager.browser.empty": "เปิดแอปพลิเคชันในเครื่องเพื่อดูตัวอย่างที่นี่",
|
||||
"agentManager.browser.noSession": "เลือกเซสชัน Agent Manager ก่อน",
|
||||
"agentManager.browser.screenshotAlt": "หน้าปัจจุบันของเบราว์เซอร์",
|
||||
"agentManager.browser.errors": "{{count}} ข้อผิดพลาดในคอนโซล",
|
||||
"agentManager.browser.errors": "ปัญหาเบราว์เซอร์: {{count}} รายการ",
|
||||
"agentManager.browser.diagnostics": "การวินิจฉัยเบราว์เซอร์",
|
||||
"agentManager.browser.diagnosticsHint":
|
||||
"เหตุการณ์ล่าสุดจากเบราว์เซอร์สำหรับงานอัตโนมัติ การบล็อกเพื่อความปลอดภัยไม่ใช่ข้อผิดพลาดในคอนโซล",
|
||||
|
||||
"agentManager.pr.error.gh_auth.title": "ต้องยืนยันตัวตน GitHub",
|
||||
"agentManager.pr.error.gh_auth.description": "รันคำสั่ง 'gh auth login' ในเทอร์มินัลเพื่อกู้คืนสถานะ PR",
|
||||
|
||||
+4
-1
@@ -265,7 +265,10 @@ export const dict = {
|
||||
"agentManager.browser.empty": "Burada önizlemek için yerel bir uygulama açın.",
|
||||
"agentManager.browser.noSession": "Önce bir Agent Manager oturumu seçin.",
|
||||
"agentManager.browser.screenshotAlt": "Geçerli tarayıcı sayfası",
|
||||
"agentManager.browser.errors": "{{count}} konsol hatası",
|
||||
"agentManager.browser.errors": "Tarayıcı sorunları: {{count}}",
|
||||
"agentManager.browser.diagnostics": "Tarayıcı tanılaması",
|
||||
"agentManager.browser.diagnosticsHint":
|
||||
"Otomasyon tarayıcısındaki son olaylar. Güvenlik engellemeleri konsol hatası değildir.",
|
||||
|
||||
"agentManager.pr.error.gh_auth.title": "GitHub kimlik doğrulaması gerekli",
|
||||
"agentManager.pr.error.gh_auth.description":
|
||||
|
||||
+4
-1
@@ -266,7 +266,10 @@ export const dict = {
|
||||
"agentManager.browser.empty": "Відкрийте локальну програму, щоб переглянути її тут.",
|
||||
"agentManager.browser.noSession": "Спочатку виберіть сесію Agent Manager.",
|
||||
"agentManager.browser.screenshotAlt": "Поточна сторінка браузера",
|
||||
"agentManager.browser.errors": "{{count}} помилок консолі",
|
||||
"agentManager.browser.errors": "Проблеми браузера: {{count}}",
|
||||
"agentManager.browser.diagnostics": "Діагностика браузера",
|
||||
"agentManager.browser.diagnosticsHint":
|
||||
"Останні події з браузера для автоматизації. Блокування з міркувань безпеки не є помилками консолі.",
|
||||
|
||||
"agentManager.pr.error.gh_auth.title": "Потрібна автентифікація GitHub",
|
||||
"agentManager.pr.error.gh_auth.description": "Виконайте 'gh auth login' у терміналі, щоб відновити статус PR.",
|
||||
|
||||
+3
-1
@@ -250,7 +250,9 @@ export const dict = {
|
||||
"agentManager.browser.empty": "打开本地应用以在此处预览。",
|
||||
"agentManager.browser.noSession": "请先选择 Agent Manager 会话。",
|
||||
"agentManager.browser.screenshotAlt": "当前浏览器页面",
|
||||
"agentManager.browser.errors": "{{count}} 个控制台错误",
|
||||
"agentManager.browser.errors": "浏览器问题:{{count}}",
|
||||
"agentManager.browser.diagnostics": "浏览器诊断",
|
||||
"agentManager.browser.diagnosticsHint": "来自自动化浏览器的近期事件。安全拦截不是控制台错误。",
|
||||
|
||||
"agentManager.pr.error.gh_auth.title": "需要 GitHub 身份验证",
|
||||
"agentManager.pr.error.gh_auth.description": "在终端中运行 'gh auth login' 以恢复 PR 状态。",
|
||||
|
||||
+3
-1
@@ -249,7 +249,9 @@ export const dict = {
|
||||
"agentManager.browser.empty": "開啟本機應用程式,即可在此處預覽。",
|
||||
"agentManager.browser.noSession": "請先選取 Agent Manager 工作階段。",
|
||||
"agentManager.browser.screenshotAlt": "目前的瀏覽器頁面",
|
||||
"agentManager.browser.errors": "{{count}} 個主控台錯誤",
|
||||
"agentManager.browser.errors": "瀏覽器問題:{{count}}",
|
||||
"agentManager.browser.diagnostics": "瀏覽器診斷",
|
||||
"agentManager.browser.diagnosticsHint": "來自自動化瀏覽器的近期事件。安全性封鎖不是主控台錯誤。",
|
||||
|
||||
"agentManager.pr.error.gh_auth.title": "需要 GitHub 驗證",
|
||||
"agentManager.pr.error.gh_auth.description": "在終端機中執行 'gh auth login' 以還原 PR 狀態。",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { For, Show, type Accessor, type Component } from "solid-js"
|
||||
import { For, Show, createMemo, type Accessor, type Component } from "solid-js"
|
||||
import { Card } from "@kilocode/kilo-ui/card"
|
||||
import { Collapsible } from "@kilocode/kilo-ui/collapsible"
|
||||
import { Icon } from "@kilocode/kilo-ui/icon"
|
||||
import { IconButton } from "@kilocode/kilo-ui/icon-button"
|
||||
import { Spinner } from "@kilocode/kilo-ui/spinner"
|
||||
@@ -28,10 +29,6 @@ const Toolbar: Component<{
|
||||
active: boolean
|
||||
}> = (props) => {
|
||||
const ready = () => !!props.controller.state()?.url && props.controller.state()?.status !== "closed"
|
||||
const diagnostics = () =>
|
||||
props.controller.state()?.errors
|
||||
? `${props.labels.devtoolsTitle}, ${props.labels.errors(props.controller.state()!.errors)}`
|
||||
: props.labels.devtoolsTitle
|
||||
return (
|
||||
<div class="am-browser-toolbar">
|
||||
<Tooltip value={props.labels.refresh} placement="bottom">
|
||||
@@ -52,11 +49,11 @@ const Toolbar: Component<{
|
||||
if (props.active && props.controller.url().trim() && !props.controller.loading()) props.controller.open()
|
||||
}}
|
||||
>
|
||||
<span class="am-browser-site" aria-hidden="true">
|
||||
<Show when={props.controller.loading()} fallback={<Icon name="globe" size="small" />}>
|
||||
<Show when={props.controller.loading()}>
|
||||
<span class="am-browser-site" aria-hidden="true">
|
||||
<Spinner />
|
||||
</Show>
|
||||
</span>
|
||||
</span>
|
||||
</Show>
|
||||
<TextField
|
||||
class="am-browser-url"
|
||||
variant="ghost"
|
||||
@@ -91,22 +88,17 @@ const Toolbar: Component<{
|
||||
/>
|
||||
</Tooltip>
|
||||
<div class="am-browser-tools-action">
|
||||
<Tooltip value={diagnostics()} placement="bottom">
|
||||
<Tooltip value={props.labels.devtoolsTitle} placement="bottom">
|
||||
<IconButton
|
||||
icon="console"
|
||||
size="small"
|
||||
variant={props.controller.tools() ? "secondary" : "ghost"}
|
||||
aria-label={diagnostics()}
|
||||
aria-label={props.labels.devtoolsTitle}
|
||||
aria-pressed={!!props.controller.tools()}
|
||||
onClick={props.controller.toggleTools}
|
||||
disabled={!ready() || props.controller.loading()}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Show when={(props.controller.state()?.errors ?? 0) > 0}>
|
||||
<span class="am-browser-error-count" aria-hidden="true">
|
||||
{(props.controller.state()?.errors ?? 0) > 99 ? "99+" : props.controller.state()?.errors}
|
||||
</span>
|
||||
</Show>
|
||||
</div>
|
||||
<Tooltip value={props.labels.close} placement="bottom">
|
||||
<IconButton
|
||||
@@ -227,19 +219,38 @@ const Tools: Component<{ url: string; labels: BrowserLabels }> = (props) => (
|
||||
</section>
|
||||
)
|
||||
|
||||
const Diagnostics: Component<{ logs: string[] }> = (props) => (
|
||||
<Show when={props.logs.length}>
|
||||
<div class="am-browser-console" role="log" aria-live="polite">
|
||||
<For each={props.logs}>
|
||||
{(line) => (
|
||||
<div class="am-browser-console-entry" data-level={line.match(/^\[([^\]]+)\]/)?.[1] ?? "error"}>
|
||||
{line}
|
||||
const Diagnostics: Component<{ logs: string[]; errors: number; labels: BrowserLabels }> = (props) => {
|
||||
const entries = createMemo(() => {
|
||||
const counts = new Map<string, number>()
|
||||
for (const text of props.logs) counts.set(text, (counts.get(text) ?? 0) + 1)
|
||||
return Array.from(counts, ([text, count]) => ({ text, count }))
|
||||
})
|
||||
return (
|
||||
<Show when={props.logs.length || props.errors}>
|
||||
<Collapsible variant="ghost" class="am-browser-diagnostics">
|
||||
<Collapsible.Trigger>
|
||||
<span>{props.errors ? props.labels.errors(props.errors) : props.labels.diagnostics}</span>
|
||||
<Collapsible.Arrow />
|
||||
</Collapsible.Trigger>
|
||||
<Collapsible.Content>
|
||||
<div class="am-browser-diagnostics-hint">{props.labels.diagnosticsHint}</div>
|
||||
<div class="am-browser-console" role="log" aria-live="polite">
|
||||
<For each={entries()}>
|
||||
{(entry) => (
|
||||
<div class="am-browser-console-entry" data-level={entry.text.match(/^\[([^\]]+)\]/)?.[1] ?? "error"}>
|
||||
<span>{entry.text}</span>
|
||||
<Show when={entry.count > 1}>
|
||||
<span class="am-browser-console-count">×{entry.count}</span>
|
||||
</Show>
|
||||
</div>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
</Show>
|
||||
)
|
||||
</Collapsible.Content>
|
||||
</Collapsible>
|
||||
</Show>
|
||||
)
|
||||
}
|
||||
|
||||
export interface BrowserPanelProps {
|
||||
scope: Accessor<BrowserScope | undefined>
|
||||
@@ -278,9 +289,7 @@ export const BrowserPanel: Component<BrowserPanelProps> = (props) => {
|
||||
{(entry) => <Tools url={entry.url} labels={props.labels} />}
|
||||
</Show>
|
||||
</div>
|
||||
<Show when={!controller.tools()}>
|
||||
<Diagnostics logs={state()?.logs ?? []} />
|
||||
</Show>
|
||||
<Diagnostics logs={state()?.logs ?? []} errors={state()?.errors ?? 0} labels={props.labels} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -74,21 +74,6 @@
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.am-browser-error-count {
|
||||
position: absolute;
|
||||
top: -3px;
|
||||
right: -3px;
|
||||
min-width: 12px;
|
||||
padding: 0 3px;
|
||||
font-size: var(--kilo-font-size-11);
|
||||
line-height: 14px;
|
||||
color: var(--text-on-accent);
|
||||
text-align: center;
|
||||
pointer-events: none;
|
||||
background: var(--syntax-diff-delete, #da3319);
|
||||
border-radius: 7px;
|
||||
}
|
||||
|
||||
.am-browser-workspace {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
@@ -193,20 +178,49 @@
|
||||
transform: none;
|
||||
}
|
||||
|
||||
[data-component="collapsible"].am-browser-diagnostics {
|
||||
flex-shrink: 0;
|
||||
border-top: 1px solid var(--border-weak-base);
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
[data-component="collapsible"].am-browser-diagnostics > [data-slot="collapsible-trigger"] {
|
||||
justify-content: space-between;
|
||||
padding: 0 12px;
|
||||
}
|
||||
|
||||
.am-browser-diagnostics [data-slot="collapsible-arrow"] {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.am-browser-diagnostics-hint {
|
||||
padding: 0 12px 4px;
|
||||
font-size: var(--font-size-small);
|
||||
color: var(--text-weak);
|
||||
}
|
||||
|
||||
.am-browser-console {
|
||||
max-height: 140px;
|
||||
padding: 8px 12px;
|
||||
padding: 4px 12px 8px;
|
||||
overflow: auto;
|
||||
font-family: var(--font-family-mono);
|
||||
font-size: var(--font-size-small);
|
||||
color: var(--text-weak);
|
||||
border-top: 1px solid var(--border-weak-base);
|
||||
}
|
||||
|
||||
.am-browser-console-entry {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.am-browser-console-count {
|
||||
flex-shrink: 0;
|
||||
color: var(--text-weak);
|
||||
}
|
||||
|
||||
.am-browser-console-entry[data-level="warning"],
|
||||
.am-browser-console-entry[data-level="warn"] {
|
||||
color: var(--text-warning, #d6a23a);
|
||||
|
||||
@@ -82,6 +82,8 @@ export interface BrowserLabels {
|
||||
close: string
|
||||
inspect: string
|
||||
devtoolsTitle: string
|
||||
diagnostics: string
|
||||
diagnosticsHint: string
|
||||
empty: string
|
||||
noSession: string
|
||||
screenshotAlt: string
|
||||
|
||||
Reference in New Issue
Block a user