feat(webchat): auto-start at root
This commit is contained in:
@@ -14,7 +14,7 @@ The macOS menu bar app opens the gateway’s loopback web chat server in a WKWeb
|
|||||||
- WK logs: navigation lifecycle, readyState, js location, and JS errors/unhandled rejections are mirrored to OSLog for easier diagnosis.
|
- WK logs: navigation lifecycle, readyState, js location, and JS errors/unhandled rejections are mirrored to OSLog for easier diagnosis.
|
||||||
|
|
||||||
## How it’s wired
|
## How it’s wired
|
||||||
- Assets: `apps/macos/Sources/Clawdis/Resources/WebChat/` contains the `pi-web-ui` dist plus a local import map pointing at bundled vendor modules and a tiny `pi-ai` stub. Everything is served from the gateway at `/webchat/*`.
|
- Assets: `apps/macos/Sources/Clawdis/Resources/WebChat/` contains the `pi-web-ui` dist plus a local import map pointing at bundled vendor modules and a tiny `pi-ai` stub. Everything is served from the gateway at `/` (legacy `/webchat/*` still works).
|
||||||
- Bridge: none. The web UI calls `/webchat/rpc` directly; Swift no longer proxies messages. RPC is handled in-process inside the gateway (no CLI spawn/PATH dependency).
|
- Bridge: none. The web UI calls `/webchat/rpc` directly; Swift no longer proxies messages. RPC is handled in-process inside the gateway (no CLI spawn/PATH dependency).
|
||||||
- Session: always primary; multiple transports (WhatsApp/Telegram/Desktop) share the same session key so context is unified.
|
- Session: always primary; multiple transports (WhatsApp/Telegram/Desktop) share the same session key so context is unified.
|
||||||
|
|
||||||
|
|||||||
@@ -15,9 +15,10 @@ Updated: 2025-12-09
|
|||||||
- `webchat.gatewayPort` config can point at a non-default Gateway port if needed.
|
- `webchat.gatewayPort` config can point at a non-default Gateway port if needed.
|
||||||
|
|
||||||
## Endpoints
|
## Endpoints
|
||||||
- `GET /webchat/info?session=<key>` → `{ port, sessionId, initialMessages, basePath }` plus history from the Gateway session store.
|
- UI is now served at the root: `http://127.0.0.1:<port>/` (legacy `/webchat/` still works).
|
||||||
- `GET /webchat/*` → static assets.
|
- `GET /webchat/info?session=<key>` (alias `/info`) → `{ port, sessionId, initialMessages, basePath }` plus history from the Gateway session store.
|
||||||
- `POST /webchat/rpc` → proxies a chat/agent action through the Gateway connection and returns `{ ok, payloads?, error? }`.
|
- `GET /` (or `/webchat/*`) → static assets.
|
||||||
|
- `POST /webchat/rpc` (alias `/rpc`) → proxies a chat/agent action through the Gateway connection and returns `{ ok, payloads?, error? }`.
|
||||||
|
|
||||||
## How it connects
|
## How it connects
|
||||||
- On startup, the WebChat server dials the Gateway WebSocket and performs the mandatory `hello` handshake; the `hello-ok` snapshot seeds presence + health immediately.
|
- On startup, the WebChat server dials the Gateway WebSocket and performs the mandatory `hello` handshake; the `hello-ok` snapshot seeds presence + health immediately.
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ describe("cli program", () => {
|
|||||||
await program.parseAsync(["webchat", "--json"], { from: "user" });
|
await program.parseAsync(["webchat", "--json"], { from: "user" });
|
||||||
expect(startWebChatServer).toHaveBeenCalled();
|
expect(startWebChatServer).toHaveBeenCalled();
|
||||||
expect(runtime.log).toHaveBeenCalledWith(
|
expect(runtime.log).toHaveBeenCalledWith(
|
||||||
JSON.stringify({ port: 18788, basePath: "/webchat/", host: "127.0.0.1" }),
|
JSON.stringify({ port: 18788, basePath: "/", host: "127.0.0.1" }),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -13,7 +13,10 @@ import { loginWeb, logoutWeb } from "../provider-web.js";
|
|||||||
import { runRpcLoop } from "../rpc/loop.js";
|
import { runRpcLoop } from "../rpc/loop.js";
|
||||||
import { defaultRuntime } from "../runtime.js";
|
import { defaultRuntime } from "../runtime.js";
|
||||||
import { VERSION } from "../version.js";
|
import { VERSION } from "../version.js";
|
||||||
import { startWebChatServer } from "../webchat/server.js";
|
import {
|
||||||
|
ensureWebChatServerFromConfig,
|
||||||
|
startWebChatServer,
|
||||||
|
} from "../webchat/server.js";
|
||||||
import { createDefaultDeps } from "./deps.js";
|
import { createDefaultDeps } from "./deps.js";
|
||||||
import {
|
import {
|
||||||
forceFreePort,
|
forceFreePort,
|
||||||
@@ -282,6 +285,22 @@ Examples:
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await startGatewayServer(port);
|
await startGatewayServer(port);
|
||||||
|
try {
|
||||||
|
const webchat = await ensureWebChatServerFromConfig({
|
||||||
|
gatewayUrl: `ws://127.0.0.1:${port}`,
|
||||||
|
});
|
||||||
|
if (webchat) {
|
||||||
|
defaultRuntime.log(
|
||||||
|
info(
|
||||||
|
`webchat listening on http://127.0.0.1:${webchat.port}/`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
defaultRuntime.log(info("webchat disabled via config"));
|
||||||
|
}
|
||||||
|
} catch (webchatErr) {
|
||||||
|
defaultRuntime.error(`WebChat failed to start: ${String(webchatErr)}`);
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err instanceof GatewayLockError) {
|
if (err instanceof GatewayLockError) {
|
||||||
defaultRuntime.error(`Gateway failed to start: ${err.message}`);
|
defaultRuntime.error(`Gateway failed to start: ${err.message}`);
|
||||||
@@ -588,14 +607,14 @@ Shows token usage per session when the agent reports it; set inbound.reply.agent
|
|||||||
const server = await startWebChatServer(port);
|
const server = await startWebChatServer(port);
|
||||||
const payload = {
|
const payload = {
|
||||||
port: server.port,
|
port: server.port,
|
||||||
basePath: "/webchat/",
|
basePath: "/",
|
||||||
host: "127.0.0.1",
|
host: "127.0.0.1",
|
||||||
};
|
};
|
||||||
if (opts.json) {
|
if (opts.json) {
|
||||||
defaultRuntime.log(JSON.stringify(payload));
|
defaultRuntime.log(JSON.stringify(payload));
|
||||||
} else {
|
} else {
|
||||||
defaultRuntime.log(
|
defaultRuntime.log(
|
||||||
info(`webchat listening on http://127.0.0.1:${server.port}/webchat/`),
|
info(`webchat listening on http://127.0.0.1:${server.port}/`),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ vi.mock("../commands/health.js", () => ({
|
|||||||
vi.mock("../commands/status.js", () => ({
|
vi.mock("../commands/status.js", () => ({
|
||||||
getStatusSummary: vi.fn().mockResolvedValue({ ok: true }),
|
getStatusSummary: vi.fn().mockResolvedValue({ ok: true }),
|
||||||
}));
|
}));
|
||||||
|
vi.mock("../webchat/server.js", () => ({
|
||||||
|
ensureWebChatServerFromConfig: vi.fn().mockResolvedValue(null),
|
||||||
|
}));
|
||||||
vi.mock("../web/outbound.js", () => ({
|
vi.mock("../web/outbound.js", () => ({
|
||||||
sendMessageWhatsApp: vi
|
sendMessageWhatsApp: vi
|
||||||
.fn()
|
.fn()
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import { defaultRuntime } from "../runtime.js";
|
|||||||
import { monitorTelegramProvider } from "../telegram/monitor.js";
|
import { monitorTelegramProvider } from "../telegram/monitor.js";
|
||||||
import { sendMessageTelegram } from "../telegram/send.js";
|
import { sendMessageTelegram } from "../telegram/send.js";
|
||||||
import { sendMessageWhatsApp } from "../web/outbound.js";
|
import { sendMessageWhatsApp } from "../web/outbound.js";
|
||||||
|
import { ensureWebChatServerFromConfig } from "../webchat/server.js";
|
||||||
import {
|
import {
|
||||||
ErrorCodes,
|
ErrorCodes,
|
||||||
type ErrorShape,
|
type ErrorShape,
|
||||||
@@ -730,6 +731,13 @@ export async function startGatewayServer(port = 18789): Promise<GatewayServer> {
|
|||||||
);
|
);
|
||||||
defaultRuntime.log(`gateway log file: ${getResolvedLoggerSettings().file}`);
|
defaultRuntime.log(`gateway log file: ${getResolvedLoggerSettings().file}`);
|
||||||
|
|
||||||
|
// Start loopback WebChat server (unless disabled via config).
|
||||||
|
void ensureWebChatServerFromConfig({
|
||||||
|
gatewayUrl: `ws://127.0.0.1:${port}`,
|
||||||
|
}).catch((err) => {
|
||||||
|
logError(`gateway: webchat failed to start: ${String(err)}`);
|
||||||
|
});
|
||||||
|
|
||||||
// Launch configured providers (WhatsApp Web, Telegram) so gateway replies via the
|
// Launch configured providers (WhatsApp Web, Telegram) so gateway replies via the
|
||||||
// surface the message came from. Tests can opt out via CLAWDIS_SKIP_PROVIDERS.
|
// surface the message came from. Tests can opt out via CLAWDIS_SKIP_PROVIDERS.
|
||||||
if (process.env.CLAWDIS_SKIP_PROVIDERS !== "1") {
|
if (process.env.CLAWDIS_SKIP_PROVIDERS !== "1") {
|
||||||
|
|||||||
@@ -557,12 +557,14 @@ export function __broadcastGatewayEventForTests(
|
|||||||
broadcastAll({ type: "gateway-event", event, payload });
|
broadcastAll({ type: "gateway-event", event, payload });
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function ensureWebChatServerFromConfig() {
|
export async function ensureWebChatServerFromConfig(opts?: {
|
||||||
|
gatewayUrl?: string;
|
||||||
|
}) {
|
||||||
const cfg = loadConfig();
|
const cfg = loadConfig();
|
||||||
if (cfg.webchat?.enabled === false) return null;
|
if (cfg.webchat?.enabled === false) return null;
|
||||||
const port = cfg.webchat?.port ?? WEBCHAT_DEFAULT_PORT;
|
const port = cfg.webchat?.port ?? WEBCHAT_DEFAULT_PORT;
|
||||||
try {
|
try {
|
||||||
return await startWebChatServer(port);
|
return await startWebChatServer(port, opts?.gatewayUrl);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logDebug(`webchat server failed to start: ${String(err)}`);
|
logDebug(`webchat server failed to start: ${String(err)}`);
|
||||||
throw err;
|
throw err;
|
||||||
|
|||||||
Reference in New Issue
Block a user