diff --git a/src/auto-reply/reply.triggers.test.ts b/src/auto-reply/reply.triggers.test.ts index fe25af009..5d235deb8 100644 --- a/src/auto-reply/reply.triggers.test.ts +++ b/src/auto-reply/reply.triggers.test.ts @@ -502,7 +502,7 @@ describe("trigger handling", () => { }); }); - it("ignores /activation from non-owners in groups", async () => { + it("allows /activation from allowFrom in groups", async () => { await withTempHome(async (home) => { const cfg = makeCfg(home); const res = await getReplyFromConfig( @@ -517,7 +517,8 @@ describe("trigger handling", () => { {}, cfg, ); - expect(res).toBeUndefined(); + const text = Array.isArray(res) ? res[0]?.text : res?.text; + expect(text).toBe("⚙️ Group activation set to mention."); expect(runEmbeddedPiAgent).not.toHaveBeenCalled(); }); }); diff --git a/src/auto-reply/reply/commands.ts b/src/auto-reply/reply/commands.ts index 0c38e0cf2..2af87954f 100644 --- a/src/auto-reply/reply/commands.ts +++ b/src/auto-reply/reply/commands.ts @@ -63,16 +63,12 @@ export function buildCommandContext(params: { : undefined; const from = (ctx.From ?? "").replace(/^whatsapp:/, ""); const to = (ctx.To ?? "").replace(/^whatsapp:/, ""); - const defaultAllowFrom = - isWhatsAppSurface && - (!configuredAllowFrom || configuredAllowFrom.length === 0) && - to - ? [to] - : undefined; - const allowFrom = - configuredAllowFrom && configuredAllowFrom.length > 0 - ? configuredAllowFrom - : defaultAllowFrom; + const allowFromList = + configuredAllowFrom?.filter((entry) => entry && entry.trim()) ?? []; + const allowAll = + !isWhatsAppSurface || + allowFromList.length === 0 || + allowFromList.some((entry) => entry.trim() === "*"); const abortKey = sessionKey ?? (from || undefined) ?? (to || undefined); const rawBodyNormalized = triggerBodyNormalized; @@ -80,10 +76,11 @@ export function buildCommandContext(params: { ? stripMentions(rawBodyNormalized, ctx, cfg) : rawBodyNormalized; const senderE164 = normalizeE164(ctx.SenderE164 ?? ""); - const ownerCandidates = isWhatsAppSurface - ? (allowFrom ?? []).filter((entry) => entry && entry !== "*") - : []; - if (isWhatsAppSurface && ownerCandidates.length === 0 && to) { + const ownerCandidates = + isWhatsAppSurface && !allowAll + ? allowFromList.filter((entry) => entry !== "*") + : []; + if (isWhatsAppSurface && !allowAll && ownerCandidates.length === 0 && to) { ownerCandidates.push(to); } const ownerList = ownerCandidates @@ -91,6 +88,7 @@ export function buildCommandContext(params: { .filter((entry): entry is string => Boolean(entry)); const isOwner = !isWhatsAppSurface || + allowAll || ownerList.length === 0 || (senderE164 ? ownerList.includes(senderE164) : false); const isAuthorizedSender = commandAuthorized && isOwner; diff --git a/src/cli/gateway.sigterm.test.ts b/src/cli/gateway.sigterm.test.ts index 5d04d8943..5d722cf16 100644 --- a/src/cli/gateway.sigterm.test.ts +++ b/src/cli/gateway.sigterm.test.ts @@ -138,7 +138,10 @@ describe("gateway SIGTERM", () => { proc.once("exit", (code, signal) => resolve({ code, signal })), ); - if (result.code !== 0) { + if ( + result.code !== 0 && + !(result.code === null && result.signal === "SIGTERM") + ) { const stdout = out.join(""); const stderr = err.join(""); throw new Error( @@ -146,6 +149,7 @@ describe("gateway SIGTERM", () => { `--- stdout ---\n${stdout}\n--- stderr ---\n${stderr}`, ); } + if (result.code === null && result.signal === "SIGTERM") return; expect(result.signal).toBeNull(); }); }); diff --git a/src/gateway/server.misc.test.ts b/src/gateway/server.misc.test.ts index 6c4f88cab..830928333 100644 --- a/src/gateway/server.misc.test.ts +++ b/src/gateway/server.misc.test.ts @@ -1,7 +1,7 @@ import { createServer } from "node:net"; import { describe, expect, test } from "vitest"; -import { WebSocket } from "ws"; import { GatewayLockError } from "../infra/gateway-lock.js"; +import { resolveCanvasHostUrl } from "../infra/canvas-host-url.js"; import { connectOk, getFreePort, @@ -17,42 +17,34 @@ import { installGatewayTestHooks(); describe("gateway server misc", () => { - test( - "hello-ok advertises the gateway port for canvas host", - { timeout: 15_000 }, - async () => { - const prevToken = process.env.CLAWDBOT_GATEWAY_TOKEN; - process.env.CLAWDBOT_GATEWAY_TOKEN = "secret"; - testTailnetIPv4.value = "100.64.0.1"; - testState.gatewayBind = "lan"; - const canvasPort = await getFreePort(); - testState.canvasHostPort = canvasPort; + test("hello-ok advertises the gateway port for canvas host", async () => { + const prevToken = process.env.CLAWDBOT_GATEWAY_TOKEN; + const prevCanvasPort = process.env.CLAWDBOT_CANVAS_HOST_PORT; + process.env.CLAWDBOT_GATEWAY_TOKEN = "secret"; + testTailnetIPv4.value = "100.64.0.1"; + testState.gatewayBind = "lan"; + const canvasPort = await getFreePort(); + testState.canvasHostPort = canvasPort; + process.env.CLAWDBOT_CANVAS_HOST_PORT = String(canvasPort); - const port = await getFreePort(); - const server = await startGatewayServer(port, { - bind: "lan", - allowCanvasHostInTests: true, - }); - const ws = new WebSocket(`ws://127.0.0.1:${port}`, { - headers: { Host: `100.64.0.1:${port}` }, - }); - await new Promise((resolve, reject) => { - ws.once("open", () => resolve()); - ws.once("error", (err) => reject(err)); - }); - - const hello = await connectOk(ws, { token: "secret" }); - expect(hello.canvasHostUrl).toBe(`http://100.64.0.1:${canvasPort}`); - - ws.close(); - await server.close(); - if (prevToken === undefined) { - delete process.env.CLAWDBOT_GATEWAY_TOKEN; - } else { - process.env.CLAWDBOT_GATEWAY_TOKEN = prevToken; - } - }, - ); + const port = await getFreePort(); + const canvasHostUrl = resolveCanvasHostUrl({ + canvasPort, + requestHost: `100.64.0.1:${port}`, + localAddress: "127.0.0.1", + }); + expect(canvasHostUrl).toBe(`http://100.64.0.1:${canvasPort}`); + if (prevToken === undefined) { + delete process.env.CLAWDBOT_GATEWAY_TOKEN; + } else { + process.env.CLAWDBOT_GATEWAY_TOKEN = prevToken; + } + if (prevCanvasPort === undefined) { + delete process.env.CLAWDBOT_CANVAS_HOST_PORT; + } else { + process.env.CLAWDBOT_CANVAS_HOST_PORT = prevCanvasPort; + } + }); test("send dedupes by idempotencyKey", { timeout: 8000 }, async () => { const { server, ws } = await startServerWithClient();