webchat: show real ws errors
This commit is contained in:
@@ -7,6 +7,8 @@ if (!globalThis.process) {
|
|||||||
globalThis.process = { env: {} };
|
globalThis.process = { env: {} };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
import { formatError } from "./format-error.js";
|
||||||
|
|
||||||
const logStatus = (msg) => {
|
const logStatus = (msg) => {
|
||||||
try {
|
try {
|
||||||
console.log(msg);
|
console.log(msg);
|
||||||
@@ -331,7 +333,7 @@ const startChat = async () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
startChat().catch((err) => {
|
startChat().catch((err) => {
|
||||||
const msg = err?.stack || err?.message || String(err);
|
const msg = formatError(err);
|
||||||
logStatus(`boot failed: ${msg}`);
|
logStatus(`boot failed: ${msg}`);
|
||||||
document.body.dataset.webchatError = "1";
|
document.body.dataset.webchatError = "1";
|
||||||
ensureErrorStyles();
|
ensureErrorStyles();
|
||||||
|
|||||||
31
apps/macos/Sources/Clawdis/Resources/WebChat/format-error.js
Normal file
31
apps/macos/Sources/Clawdis/Resources/WebChat/format-error.js
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
// Shared formatter for WebChat bootstrap errors so UI shows actionable messages.
|
||||||
|
export const formatError = (err) => {
|
||||||
|
if (!err) return "Unknown error";
|
||||||
|
if (err instanceof Error) return err.stack || err.message || String(err);
|
||||||
|
|
||||||
|
const isCloseEvent =
|
||||||
|
(typeof CloseEvent !== "undefined" && err instanceof CloseEvent) ||
|
||||||
|
(typeof err?.code === "number" &&
|
||||||
|
(err?.reason !== undefined || err?.wasClean !== undefined));
|
||||||
|
if (isCloseEvent) {
|
||||||
|
const reason = err.reason?.trim();
|
||||||
|
const parts = [`WebSocket closed (${err.code})`];
|
||||||
|
if (reason) parts.push(`reason: ${reason}`);
|
||||||
|
if (err.wasClean) parts.push("clean close");
|
||||||
|
return parts.join("; ");
|
||||||
|
}
|
||||||
|
|
||||||
|
const isWsErrorEvent =
|
||||||
|
err?.type === "error" && typeof err?.target?.readyState === "number";
|
||||||
|
if (isWsErrorEvent) {
|
||||||
|
const states = ["connecting", "open", "closing", "closed"];
|
||||||
|
const stateLabel = states[err.target.readyState] ?? err.target.readyState;
|
||||||
|
return `WebSocket error (state: ${stateLabel})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return JSON.stringify(err);
|
||||||
|
} catch {
|
||||||
|
return String(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -43,6 +43,37 @@ var __require = /* @__PURE__ */ ((x$2) => typeof require !== "undefined" ? requi
|
|||||||
throw Error("Calling `require` for \"" + x$2 + "\" in an environment that doesn't expose the `require` function.");
|
throw Error("Calling `require` for \"" + x$2 + "\" in an environment that doesn't expose the `require` function.");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
//#endregion
|
||||||
|
//#region apps/macos/Sources/Clawdis/Resources/WebChat/format-error.js
|
||||||
|
const formatError = (err) => {
|
||||||
|
if (!err) return "Unknown error";
|
||||||
|
if (err instanceof Error) return err.stack || err.message || String(err);
|
||||||
|
const isCloseEvent = typeof CloseEvent !== "undefined" && err instanceof CloseEvent || typeof err?.code === "number" && (err?.reason !== undefined || err?.wasClean !== undefined);
|
||||||
|
if (isCloseEvent) {
|
||||||
|
const reason = err.reason?.trim();
|
||||||
|
const parts = [`WebSocket closed (${err.code})`];
|
||||||
|
if (reason) parts.push(`reason: ${reason}`);
|
||||||
|
if (err.wasClean) parts.push("clean close");
|
||||||
|
return parts.join("; ");
|
||||||
|
}
|
||||||
|
const isWsErrorEvent = err?.type === "error" && typeof err?.target?.readyState === "number";
|
||||||
|
if (isWsErrorEvent) {
|
||||||
|
const states = [
|
||||||
|
"connecting",
|
||||||
|
"open",
|
||||||
|
"closing",
|
||||||
|
"closed"
|
||||||
|
];
|
||||||
|
const stateLabel = states[err.target.readyState] ?? err.target.readyState;
|
||||||
|
return `WebSocket error (state: ${stateLabel})`;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return JSON.stringify(err);
|
||||||
|
} catch {
|
||||||
|
return String(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
//#endregion
|
//#endregion
|
||||||
//#region apps/macos/Sources/Clawdis/Resources/WebChat/pi-ai-stub.js
|
//#region apps/macos/Sources/Clawdis/Resources/WebChat/pi-ai-stub.js
|
||||||
var pi_ai_stub_exports = /* @__PURE__ */ __export({
|
var pi_ai_stub_exports = /* @__PURE__ */ __export({
|
||||||
@@ -196619,7 +196650,7 @@ const startChat = async () => {
|
|||||||
logStatus("boot: ready");
|
logStatus("boot: ready");
|
||||||
};
|
};
|
||||||
startChat().catch((err) => {
|
startChat().catch((err) => {
|
||||||
const msg = err?.stack || err?.message || String(err);
|
const msg = formatError(err);
|
||||||
logStatus(`boot failed: ${msg}`);
|
logStatus(`boot failed: ${msg}`);
|
||||||
document.body.dataset.webchatError = "1";
|
document.body.dataset.webchatError = "1";
|
||||||
ensureErrorStyles();
|
ensureErrorStyles();
|
||||||
|
|||||||
30
test/format-error.test.ts
Normal file
30
test/format-error.test.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { formatError } from "../apps/macos/Sources/Clawdis/Resources/WebChat/format-error.js";
|
||||||
|
|
||||||
|
describe("formatError", () => {
|
||||||
|
it("handles Error with stack", () => {
|
||||||
|
const err = new Error("boom");
|
||||||
|
err.stack = "stack trace";
|
||||||
|
expect(formatError(err)).toBe("stack trace");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("handles CloseEvent-like object", () => {
|
||||||
|
const err = { code: 1006, reason: "socket closed", wasClean: false };
|
||||||
|
expect(formatError(err)).toBe("WebSocket closed (1006); reason: socket closed");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("handles WebSocket error event with state", () => {
|
||||||
|
const err = { type: "error", target: { readyState: 2 } };
|
||||||
|
expect(formatError(err)).toBe("WebSocket error (state: closing)");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("stringifies plain objects", () => {
|
||||||
|
expect(formatError({ a: 1 })).toBe("{\"a\":1}");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("falls back to string", () => {
|
||||||
|
const circular = {} as any;
|
||||||
|
circular.self = circular;
|
||||||
|
expect(formatError(circular)).toBe("[object Object]");
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -2,7 +2,7 @@ import { defineConfig } from "vitest/config";
|
|||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
test: {
|
test: {
|
||||||
include: ["src/**/*.test.ts"],
|
include: ["src/**/*.test.ts", "test/format-error.test.ts"],
|
||||||
exclude: [
|
exclude: [
|
||||||
"dist/**",
|
"dist/**",
|
||||||
"apps/macos/**",
|
"apps/macos/**",
|
||||||
|
|||||||
Reference in New Issue
Block a user