chore: rename relay to gateway
This commit is contained in:
@@ -8,7 +8,7 @@ import type {
|
||||
} from "@mariozechner/pi-ai";
|
||||
import { piSpec } from "../agents/pi.js";
|
||||
import type { AgentMeta, AgentToolResult } from "../agents/types.js";
|
||||
import type { WarelayConfig } from "../config/config.js";
|
||||
import type { ClawdisConfig } from "../config/config.js";
|
||||
import { isVerbose, logVerbose } from "../globals.js";
|
||||
import { emitAgentEvent } from "../infra/agent-events.js";
|
||||
import { logError } from "../logger.js";
|
||||
@@ -141,7 +141,7 @@ function extractAssistantTextLoosely(raw: string): string | undefined {
|
||||
return last ? last.replace(/\\n/g, "\n").trim() : undefined;
|
||||
}
|
||||
|
||||
type CommandReplyConfig = NonNullable<WarelayConfig["inbound"]>["reply"] & {
|
||||
type CommandReplyConfig = NonNullable<ClawdisConfig["inbound"]>["reply"] & {
|
||||
mode: "command";
|
||||
};
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import crypto from "node:crypto";
|
||||
import { lookupContextTokens } from "../agents/context.js";
|
||||
import { DEFAULT_CONTEXT_TOKENS, DEFAULT_MODEL } from "../agents/defaults.js";
|
||||
import { resolveBundledPiBinary } from "../agents/pi-path.js";
|
||||
import { loadConfig, type WarelayConfig } from "../config/config.js";
|
||||
import { loadConfig, type ClawdisConfig } from "../config/config.js";
|
||||
import {
|
||||
DEFAULT_IDLE_MINUTES,
|
||||
DEFAULT_RESET_TRIGGER,
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
} from "../config/sessions.js";
|
||||
import { isVerbose, logVerbose } from "../globals.js";
|
||||
import { buildProviderSummary } from "../infra/provider-summary.js";
|
||||
import { triggerWarelayRestart } from "../infra/restart.js";
|
||||
import { triggerClawdisRestart } from "../infra/restart.js";
|
||||
import { drainSystemEvents } from "../infra/system-events.js";
|
||||
import { defaultRuntime } from "../runtime.js";
|
||||
import { resolveHeartbeatSeconds } from "../web/reconnect.js";
|
||||
@@ -42,7 +42,7 @@ const ABORT_TRIGGERS = new Set(["stop", "esc", "abort", "wait", "exit"]);
|
||||
const ABORT_MEMORY = new Map<string, boolean>();
|
||||
const SYSTEM_MARK = "⚙️";
|
||||
|
||||
type ReplyConfig = NonNullable<WarelayConfig["inbound"]>["reply"];
|
||||
type ReplyConfig = NonNullable<ClawdisConfig["inbound"]>["reply"];
|
||||
|
||||
export function extractThinkDirective(body?: string): {
|
||||
cleaned: string;
|
||||
@@ -112,7 +112,7 @@ function stripStructuralPrefixes(text: string): string {
|
||||
function stripMentions(
|
||||
text: string,
|
||||
ctx: MsgContext,
|
||||
cfg: WarelayConfig | undefined,
|
||||
cfg: ClawdisConfig | undefined,
|
||||
): string {
|
||||
let result = text;
|
||||
const patterns = cfg?.inbound?.groupChat?.mentionPatterns ?? [];
|
||||
@@ -161,7 +161,7 @@ function makeDefaultPiReply(): ReplyConfig {
|
||||
export async function getReplyFromConfig(
|
||||
ctx: MsgContext,
|
||||
opts?: GetReplyOptions,
|
||||
configOverride?: WarelayConfig,
|
||||
configOverride?: ClawdisConfig,
|
||||
): Promise<ReplyPayload | ReplyPayload[] | undefined> {
|
||||
// Choose reply from config: static text or external command stdout.
|
||||
const cfg = configOverride ?? loadConfig();
|
||||
@@ -503,7 +503,7 @@ export async function getReplyFromConfig(
|
||||
rawBodyNormalized === "restart" ||
|
||||
rawBodyNormalized.startsWith("/restart ")
|
||||
) {
|
||||
triggerWarelayRestart();
|
||||
triggerClawdisRestart();
|
||||
cleanupTyping();
|
||||
return {
|
||||
text: "⚙️ Restarting clawdis via launchctl; give me a few seconds to come back online.",
|
||||
|
||||
@@ -5,11 +5,11 @@ import path from "node:path";
|
||||
|
||||
import { lookupContextTokens } from "../agents/context.js";
|
||||
import { DEFAULT_CONTEXT_TOKENS, DEFAULT_MODEL } from "../agents/defaults.js";
|
||||
import type { WarelayConfig } from "../config/config.js";
|
||||
import type { ClawdisConfig } from "../config/config.js";
|
||||
import type { SessionEntry, SessionScope } from "../config/sessions.js";
|
||||
import type { ThinkLevel, VerboseLevel } from "./thinking.js";
|
||||
|
||||
type ReplyConfig = NonNullable<WarelayConfig["inbound"]>["reply"];
|
||||
type ReplyConfig = NonNullable<ClawdisConfig["inbound"]>["reply"];
|
||||
|
||||
type StatusArgs = {
|
||||
reply: ReplyConfig;
|
||||
|
||||
@@ -3,7 +3,7 @@ import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
|
||||
import type { WarelayConfig } from "../config/config.js";
|
||||
import type { ClawdisConfig } from "../config/config.js";
|
||||
import { isVerbose, logVerbose } from "../globals.js";
|
||||
import { runExec } from "../process/exec.js";
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
@@ -14,7 +14,7 @@ export function isAudio(mediaType?: string | null) {
|
||||
}
|
||||
|
||||
export async function transcribeInboundAudio(
|
||||
cfg: WarelayConfig,
|
||||
cfg: ClawdisConfig,
|
||||
ctx: MsgContext,
|
||||
runtime: RuntimeEnv,
|
||||
): Promise<{ text: string } | undefined> {
|
||||
|
||||
@@ -279,7 +279,7 @@ Examples:
|
||||
});
|
||||
program
|
||||
.command("gateway")
|
||||
.description("Run the WebSocket Gateway (replaces relay)")
|
||||
.description("Run the WebSocket Gateway")
|
||||
.option("--port <port>", "Port for the gateway WebSocket", "18789")
|
||||
.option(
|
||||
"--token <token>",
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
vi,
|
||||
} from "vitest";
|
||||
import * as commandReply from "../auto-reply/command-reply.js";
|
||||
import type { WarelayConfig } from "../config/config.js";
|
||||
import type { ClawdisConfig } from "../config/config.js";
|
||||
import * as configModule from "../config/config.js";
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
import { agentCommand } from "./agent.js";
|
||||
@@ -36,7 +36,7 @@ function makeStorePath() {
|
||||
|
||||
function mockConfig(
|
||||
storePath: string,
|
||||
replyOverrides?: Partial<NonNullable<WarelayConfig["inbound"]>["reply"]>,
|
||||
replyOverrides?: Partial<NonNullable<ClawdisConfig["inbound"]>["reply"]>,
|
||||
) {
|
||||
configSpy.mockReturnValue({
|
||||
inbound: {
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
type VerboseLevel,
|
||||
} from "../auto-reply/thinking.js";
|
||||
import { type CliDeps, createDefaultDeps } from "../cli/deps.js";
|
||||
import { loadConfig, type WarelayConfig } from "../config/config.js";
|
||||
import { loadConfig, type ClawdisConfig } from "../config/config.js";
|
||||
import {
|
||||
DEFAULT_IDLE_MINUTES,
|
||||
loadSessionStore,
|
||||
@@ -50,7 +50,7 @@ type SessionResolution = {
|
||||
persistedVerbose?: VerboseLevel;
|
||||
};
|
||||
|
||||
function assertCommandConfig(cfg: WarelayConfig) {
|
||||
function assertCommandConfig(cfg: ClawdisConfig) {
|
||||
const reply = cfg.inbound?.reply;
|
||||
if (!reply || reply.mode !== "command" || !reply.command?.length) {
|
||||
throw new Error(
|
||||
@@ -58,14 +58,14 @@ function assertCommandConfig(cfg: WarelayConfig) {
|
||||
);
|
||||
}
|
||||
return reply as NonNullable<
|
||||
NonNullable<WarelayConfig["inbound"]>["reply"]
|
||||
NonNullable<ClawdisConfig["inbound"]>["reply"]
|
||||
> & { mode: "command"; command: string[] };
|
||||
}
|
||||
|
||||
function resolveSession(opts: {
|
||||
to?: string;
|
||||
sessionId?: string;
|
||||
replyCfg: NonNullable<NonNullable<WarelayConfig["inbound"]>["reply"]>;
|
||||
replyCfg: NonNullable<NonNullable<ClawdisConfig["inbound"]>["reply"]>;
|
||||
}): SessionResolution {
|
||||
const sessionCfg = opts.replyCfg?.session;
|
||||
const scope = sessionCfg?.scope ?? "per-sender";
|
||||
|
||||
@@ -53,12 +53,12 @@ export async function sendCommand(
|
||||
return;
|
||||
}
|
||||
|
||||
// Try to send via IPC to running relay first (avoids Signal session corruption)
|
||||
// Try to send via IPC to running gateway first (avoids Signal session corruption)
|
||||
const ipcResult = await sendViaIpc(opts.to, opts.message, opts.media);
|
||||
if (ipcResult) {
|
||||
if (ipcResult.success) {
|
||||
runtime.log(
|
||||
success(`✅ Sent via relay IPC. Message ID: ${ipcResult.messageId}`),
|
||||
success(`✅ Sent via gateway IPC. Message ID: ${ipcResult.messageId}`),
|
||||
);
|
||||
if (opts.json) {
|
||||
runtime.log(
|
||||
@@ -77,7 +77,7 @@ export async function sendCommand(
|
||||
}
|
||||
return;
|
||||
}
|
||||
// IPC failed but relay is running - warn and fall back
|
||||
// IPC failed but gateway is running - warn and fall back
|
||||
runtime.log(
|
||||
info(
|
||||
`IPC send failed (${ipcResult.error}), falling back to direct connection`,
|
||||
|
||||
@@ -66,7 +66,7 @@ export type GroupChatConfig = {
|
||||
historyLimit?: number;
|
||||
};
|
||||
|
||||
export type WarelayConfig = {
|
||||
export type ClawdisConfig = {
|
||||
logging?: LoggingConfig;
|
||||
inbound?: {
|
||||
allowFrom?: string[]; // E.164 numbers allowed to trigger auto-reply (without whatsapp:)
|
||||
@@ -179,7 +179,7 @@ const ReplySchema = z
|
||||
},
|
||||
);
|
||||
|
||||
const WarelaySchema = z.object({
|
||||
const ClawdisSchema = z.object({
|
||||
logging: z
|
||||
.object({
|
||||
level: z
|
||||
@@ -252,7 +252,7 @@ const WarelaySchema = z.object({
|
||||
.optional(),
|
||||
});
|
||||
|
||||
export function loadConfig(): WarelayConfig {
|
||||
export function loadConfig(): ClawdisConfig {
|
||||
// Read config file (JSON5) if present.
|
||||
const configPath = CONFIG_PATH_CLAWDIS;
|
||||
try {
|
||||
@@ -260,7 +260,7 @@ export function loadConfig(): WarelayConfig {
|
||||
const raw = fs.readFileSync(configPath, "utf-8");
|
||||
const parsed = JSON5.parse(raw);
|
||||
if (typeof parsed !== "object" || parsed === null) return {};
|
||||
const validated = WarelaySchema.safeParse(parsed);
|
||||
const validated = ClawdisSchema.safeParse(parsed);
|
||||
if (!validated.success) {
|
||||
console.error("Invalid config:");
|
||||
for (const iss of validated.error.issues) {
|
||||
@@ -268,7 +268,7 @@ export function loadConfig(): WarelayConfig {
|
||||
}
|
||||
return {};
|
||||
}
|
||||
return validated.data as WarelayConfig;
|
||||
return validated.data as ClawdisConfig;
|
||||
} catch (err) {
|
||||
console.error(`Failed to read config at ${configPath}`, err);
|
||||
return {};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import chalk from "chalk";
|
||||
import { loadConfig, type WarelayConfig } from "../config/config.js";
|
||||
import { loadConfig, type ClawdisConfig } from "../config/config.js";
|
||||
import { normalizeE164 } from "../utils.js";
|
||||
import {
|
||||
getWebAuthAgeMs,
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
const DEFAULT_WEBCHAT_PORT = 18788;
|
||||
|
||||
export async function buildProviderSummary(
|
||||
cfg?: WarelayConfig,
|
||||
cfg?: ClawdisConfig,
|
||||
): Promise<string[]> {
|
||||
const effective = cfg ?? loadConfig();
|
||||
const lines: string[] = [];
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { acquireRelayLock, RelayLockError } from "./relay-lock.js";
|
||||
|
||||
const newLockPath = () =>
|
||||
path.join(
|
||||
os.tmpdir(),
|
||||
`clawdis-relay-lock-test-${process.pid}-${Math.random().toString(16).slice(2)}.sock`,
|
||||
);
|
||||
|
||||
describe("relay-lock", () => {
|
||||
it("prevents concurrent relay instances and releases cleanly", async () => {
|
||||
const lockPath = newLockPath();
|
||||
|
||||
const release1 = await acquireRelayLock(lockPath);
|
||||
expect(fs.existsSync(lockPath)).toBe(true);
|
||||
|
||||
await expect(acquireRelayLock(lockPath)).rejects.toBeInstanceOf(
|
||||
RelayLockError,
|
||||
);
|
||||
|
||||
await release1();
|
||||
expect(fs.existsSync(lockPath)).toBe(false);
|
||||
|
||||
// After release, lock can be reacquired.
|
||||
const release2 = await acquireRelayLock(lockPath);
|
||||
await release2();
|
||||
expect(fs.existsSync(lockPath)).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -1,102 +0,0 @@
|
||||
import fs from "node:fs";
|
||||
import net from "node:net";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
|
||||
const DEFAULT_LOCK_PATH = path.join(os.tmpdir(), "clawdis-relay.lock");
|
||||
|
||||
export class RelayLockError extends Error {}
|
||||
|
||||
type ReleaseFn = () => Promise<void>;
|
||||
|
||||
/**
|
||||
* Acquire an exclusive single-instance lock for the relay using a Unix domain socket.
|
||||
*
|
||||
* Why a socket? If the process crashes or is SIGKILLed, the socket file remains but
|
||||
* the next start will detect ECONNREFUSED when connecting and clean the stale path
|
||||
* before retrying. This keeps the lock self-healing without manual pidfile cleanup.
|
||||
*/
|
||||
export async function acquireRelayLock(
|
||||
lockPath = DEFAULT_LOCK_PATH,
|
||||
): Promise<ReleaseFn> {
|
||||
// Fast path: try to listen on the lock path.
|
||||
const attemptListen = (): Promise<net.Server> =>
|
||||
new Promise((resolve, reject) => {
|
||||
const server = net.createServer();
|
||||
|
||||
server.once("error", async (err: NodeJS.ErrnoException) => {
|
||||
if (err.code !== "EADDRINUSE") {
|
||||
reject(new RelayLockError(`lock listen failed: ${err.message}`));
|
||||
return;
|
||||
}
|
||||
|
||||
// Something is already bound. Try to connect to see if it is alive.
|
||||
const client = net.connect({ path: lockPath });
|
||||
|
||||
client.once("connect", () => {
|
||||
client.destroy();
|
||||
reject(
|
||||
new RelayLockError("another relay instance is already running"),
|
||||
);
|
||||
});
|
||||
|
||||
client.once("error", (connErr: NodeJS.ErrnoException) => {
|
||||
// Nothing is listening -> stale socket file. Remove and retry once.
|
||||
if (connErr.code === "ECONNREFUSED" || connErr.code === "ENOENT") {
|
||||
try {
|
||||
fs.rmSync(lockPath, { force: true });
|
||||
} catch (rmErr) {
|
||||
reject(
|
||||
new RelayLockError(
|
||||
`failed to clean stale lock at ${lockPath}: ${String(rmErr)}`,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
attemptListen().then(resolve, reject);
|
||||
return;
|
||||
}
|
||||
|
||||
reject(
|
||||
new RelayLockError(
|
||||
`failed to connect to existing lock (${lockPath}): ${connErr.message}`,
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
server.listen(lockPath, () => resolve(server));
|
||||
});
|
||||
|
||||
const server = await attemptListen();
|
||||
|
||||
let released = false;
|
||||
const release = async (): Promise<void> => {
|
||||
if (released) return;
|
||||
released = true;
|
||||
await new Promise<void>((resolve) => server.close(() => resolve()));
|
||||
try {
|
||||
fs.rmSync(lockPath, { force: true });
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
};
|
||||
|
||||
const cleanupSignals: NodeJS.Signals[] = ["SIGINT", "SIGTERM", "SIGHUP"];
|
||||
const handleSignal = async () => {
|
||||
await release();
|
||||
process.exit(0);
|
||||
};
|
||||
|
||||
for (const sig of cleanupSignals) {
|
||||
process.once(sig, () => {
|
||||
void handleSignal();
|
||||
});
|
||||
}
|
||||
process.once("exit", () => {
|
||||
// Exit handler must be sync-safe; release is async but close+rm are fast.
|
||||
void release();
|
||||
});
|
||||
|
||||
return release;
|
||||
}
|
||||
@@ -2,9 +2,8 @@ import { spawn } from "node:child_process";
|
||||
|
||||
const DEFAULT_LAUNCHD_LABEL = "com.steipete.clawdis";
|
||||
|
||||
export function triggerWarelayRestart(): void {
|
||||
export function triggerClawdisRestart(): void {
|
||||
const label =
|
||||
process.env.WARELAY_LAUNCHD_LABEL ||
|
||||
process.env.CLAWDIS_LAUNCHD_LABEL ||
|
||||
DEFAULT_LAUNCHD_LABEL;
|
||||
const uid =
|
||||
|
||||
@@ -56,7 +56,7 @@ function initSelfPresence() {
|
||||
function ensureSelfPresence() {
|
||||
// If the map was somehow cleared (e.g., hot reload or a new worker spawn that
|
||||
// skipped module evaluation), re-seed with a local entry so UIs always show
|
||||
// at least the current relay.
|
||||
// at least the current gateway.
|
||||
if (entries.size === 0) {
|
||||
initSelfPresence();
|
||||
}
|
||||
|
||||
@@ -150,7 +150,7 @@ export async function ensureFunnel(
|
||||
);
|
||||
runtime.error(
|
||||
info(
|
||||
"Tip: Funnel is optional for CLAWDIS. You can keep running the web relay without it: `pnpm clawdis gateway`",
|
||||
"Tip: Funnel is optional for CLAWDIS. You can keep running the web gateway without it: `pnpm clawdis gateway`",
|
||||
),
|
||||
);
|
||||
if (isVerbose()) {
|
||||
|
||||
@@ -3,7 +3,7 @@ import path from "node:path";
|
||||
import util from "node:util";
|
||||
|
||||
import { Logger as TsLogger } from "tslog";
|
||||
import { loadConfig, type WarelayConfig } from "./config/config.js";
|
||||
import { loadConfig, type ClawdisConfig } from "./config/config.js";
|
||||
import { isVerbose } from "./globals.js";
|
||||
|
||||
// Pin to /tmp so mac Debug UI and docs match; os.tmpdir() can be a per-user
|
||||
@@ -55,7 +55,7 @@ function normalizeLevel(level?: string): Level {
|
||||
}
|
||||
|
||||
function resolveSettings(): ResolvedSettings {
|
||||
const cfg: WarelayConfig["logging"] | undefined =
|
||||
const cfg: ClawdisConfig["logging"] | undefined =
|
||||
overrideSettings ?? loadConfig().logging;
|
||||
const level = normalizeLevel(cfg?.level);
|
||||
const file = cfg?.file ?? defaultRollingPathForToday();
|
||||
|
||||
@@ -131,7 +131,7 @@ class TauRpcClient {
|
||||
if (!ok) child.stdin.once("drain", () => resolve());
|
||||
});
|
||||
return await new Promise<TauRpcResult>((resolve, reject) => {
|
||||
// Hard cap to avoid stuck relays; resets on every line received.
|
||||
// Hard cap to avoid stuck gateways; resets on every line received.
|
||||
const capMs = Math.min(timeoutMs, 5 * 60 * 1000);
|
||||
const timer = setTimeout(() => {
|
||||
this.pending = undefined;
|
||||
|
||||
@@ -20,7 +20,7 @@ export async function monitorTelegramProvider(opts: MonitorTelegramOpts = {}) {
|
||||
const token = (opts.token ?? process.env.TELEGRAM_BOT_TOKEN)?.trim();
|
||||
if (!token) {
|
||||
throw new Error(
|
||||
"TELEGRAM_BOT_TOKEN or telegram.botToken is required for Telegram relay",
|
||||
"TELEGRAM_BOT_TOKEN or telegram.botToken is required for Telegram gateway",
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import path from "node:path";
|
||||
import sharp from "sharp";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import type { WarelayConfig } from "../config/config.js";
|
||||
import type { ClawdisConfig } from "../config/config.js";
|
||||
import { resetLogger, setLoggerOverride } from "../logging.js";
|
||||
import * as commandQueue from "../process/command-queue.js";
|
||||
import {
|
||||
@@ -65,7 +65,7 @@ describe("heartbeat helpers", () => {
|
||||
});
|
||||
|
||||
it("resolves heartbeat minutes with default and overrides", () => {
|
||||
const cfgBase: WarelayConfig = {
|
||||
const cfgBase: ClawdisConfig = {
|
||||
inbound: {
|
||||
reply: { mode: "command" as const },
|
||||
},
|
||||
@@ -94,7 +94,7 @@ describe("resolveHeartbeatRecipients", () => {
|
||||
it("returns the sole session recipient", async () => {
|
||||
const now = Date.now();
|
||||
const store = await makeSessionStore({ "+1000": { updatedAt: now } });
|
||||
const cfg: WarelayConfig = {
|
||||
const cfg: ClawdisConfig = {
|
||||
inbound: {
|
||||
allowFrom: ["+1999"],
|
||||
reply: { mode: "command", session: { store: store.storePath } },
|
||||
@@ -112,7 +112,7 @@ describe("resolveHeartbeatRecipients", () => {
|
||||
"+1000": { updatedAt: now },
|
||||
"+2000": { updatedAt: now - 10 },
|
||||
});
|
||||
const cfg: WarelayConfig = {
|
||||
const cfg: ClawdisConfig = {
|
||||
inbound: {
|
||||
allowFrom: ["+1999"],
|
||||
reply: { mode: "command", session: { store: store.storePath } },
|
||||
@@ -126,7 +126,7 @@ describe("resolveHeartbeatRecipients", () => {
|
||||
|
||||
it("filters wildcard allowFrom when no sessions exist", async () => {
|
||||
const store = await makeSessionStore({});
|
||||
const cfg: WarelayConfig = {
|
||||
const cfg: ClawdisConfig = {
|
||||
inbound: {
|
||||
allowFrom: ["*"],
|
||||
reply: { mode: "command", session: { store: store.storePath } },
|
||||
@@ -141,7 +141,7 @@ describe("resolveHeartbeatRecipients", () => {
|
||||
it("merges sessions and allowFrom when --all is set", async () => {
|
||||
const now = Date.now();
|
||||
const store = await makeSessionStore({ "+1000": { updatedAt: now } });
|
||||
const cfg: WarelayConfig = {
|
||||
const cfg: ClawdisConfig = {
|
||||
inbound: {
|
||||
allowFrom: ["+1999"],
|
||||
reply: { mode: "command", session: { store: store.storePath } },
|
||||
@@ -162,7 +162,7 @@ describe("partial reply gating", () => {
|
||||
|
||||
const replyResolver = vi.fn().mockResolvedValue({ text: "final reply" });
|
||||
|
||||
const mockConfig: WarelayConfig = {
|
||||
const mockConfig: ClawdisConfig = {
|
||||
inbound: {
|
||||
reply: { mode: "command" },
|
||||
allowFrom: ["*"],
|
||||
@@ -342,7 +342,7 @@ describe("runWebHeartbeatOnce", () => {
|
||||
|
||||
const replyResolver = vi.fn().mockResolvedValue({ text: HEARTBEAT_TOKEN });
|
||||
const runtime = { log: vi.fn(), error: vi.fn(), exit: vi.fn() } as never;
|
||||
const cfg: WarelayConfig = {
|
||||
const cfg: ClawdisConfig = {
|
||||
inbound: {
|
||||
allowFrom: ["+4367"],
|
||||
reply: {
|
||||
@@ -385,7 +385,7 @@ describe("runWebHeartbeatOnce", () => {
|
||||
}));
|
||||
|
||||
const resolver = vi.fn(async () => ({ text: HEARTBEAT_TOKEN }));
|
||||
const cfg: WarelayConfig = {
|
||||
const cfg: ClawdisConfig = {
|
||||
inbound: {
|
||||
allowFrom: ["+1999"],
|
||||
reply: {
|
||||
|
||||
@@ -41,7 +41,7 @@ export function setHeartbeatsEnabled(enabled: boolean) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a message via IPC if relay is running, otherwise fall back to direct.
|
||||
* Send a message via IPC if gateway is running, otherwise fall back to direct.
|
||||
* This avoids Signal session corruption from multiple Baileys connections.
|
||||
*/
|
||||
async function sendWithIpcFallback(
|
||||
@@ -52,7 +52,7 @@ async function sendWithIpcFallback(
|
||||
const ipcResult = await sendViaIpc(to, message, opts.mediaUrl);
|
||||
if (ipcResult?.success && ipcResult.messageId) {
|
||||
if (opts.verbose) {
|
||||
console.log(info(`Sent via relay IPC (avoiding session corruption)`));
|
||||
console.log(info(`Sent via gateway IPC (avoiding session corruption)`));
|
||||
}
|
||||
return { messageId: ipcResult.messageId, toJid: `${to}@s.whatsapp.net` };
|
||||
}
|
||||
@@ -720,7 +720,7 @@ export async function monitorWebProvider(
|
||||
);
|
||||
|
||||
// Avoid noisy MaxListenersExceeded warnings in test environments where
|
||||
// multiple relay instances may be constructed.
|
||||
// multiple gateway instances may be constructed.
|
||||
const currentMaxListeners = process.getMaxListeners?.() ?? 10;
|
||||
if (process.setMaxListeners && currentMaxListeners < 50) {
|
||||
process.setMaxListeners(50);
|
||||
@@ -1021,7 +1021,7 @@ export async function monitorWebProvider(
|
||||
// Surface a concise connection event for the next main-session turn/heartbeat.
|
||||
const { e164: selfE164 } = readWebSelfId();
|
||||
enqueueSystemEvent(
|
||||
`WhatsApp relay connected${selfE164 ? ` as ${selfE164}` : ""}.`,
|
||||
`WhatsApp gateway connected${selfE164 ? ` as ${selfE164}` : ""}.`,
|
||||
);
|
||||
|
||||
// Start IPC server so `clawdis send` can use this connection
|
||||
@@ -1099,10 +1099,10 @@ export async function monitorWebProvider(
|
||||
if (minutesSinceLastMessage && minutesSinceLastMessage > 30) {
|
||||
heartbeatLogger.warn(
|
||||
logData,
|
||||
"⚠️ web relay heartbeat - no messages in 30+ minutes",
|
||||
"⚠️ web gateway heartbeat - no messages in 30+ minutes",
|
||||
);
|
||||
} else {
|
||||
heartbeatLogger.info(logData, "web relay heartbeat");
|
||||
heartbeatLogger.info(logData, "web gateway heartbeat");
|
||||
}
|
||||
}, heartbeatSeconds * 1000);
|
||||
|
||||
@@ -1398,7 +1398,7 @@ export async function monitorWebProvider(
|
||||
);
|
||||
|
||||
enqueueSystemEvent(
|
||||
`WhatsApp relay disconnected (status ${status ?? "unknown"})`,
|
||||
`WhatsApp gateway disconnected (status ${status ?? "unknown"})`,
|
||||
);
|
||||
|
||||
if (loggedOut) {
|
||||
|
||||
@@ -64,7 +64,7 @@ export async function monitorWebInbox(options: {
|
||||
onCloseResolve = resolve;
|
||||
});
|
||||
try {
|
||||
// Advertise that the relay is online right after connecting.
|
||||
// Advertise that the gateway is online right after connecting.
|
||||
await sock.sendPresenceUpdate("available");
|
||||
if (isVerbose()) logVerbose("Sent global 'available' presence on connect");
|
||||
} catch (err) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* IPC server for clawdis relay.
|
||||
* IPC server for clawdis gateway.
|
||||
*
|
||||
* When the relay is running, it starts a Unix socket server that allows
|
||||
* When the gateway is running, it starts a Unix socket server that allows
|
||||
* `clawdis send` and `clawdis heartbeat` to send messages through the
|
||||
* existing WhatsApp connection instead of creating new ones.
|
||||
*
|
||||
@@ -40,7 +40,7 @@ type SendHandler = (
|
||||
let server: net.Server | null = null;
|
||||
|
||||
/**
|
||||
* Start the IPC server. Called by the relay when it starts.
|
||||
* Start the IPC server. Called by the gateway when it starts.
|
||||
*/
|
||||
export function startIpcServer(sendHandler: SendHandler): void {
|
||||
const logger = getChildLogger({ module: "ipc-server" });
|
||||
@@ -126,7 +126,7 @@ export function startIpcServer(sendHandler: SendHandler): void {
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the IPC server. Called when relay shuts down.
|
||||
* Stop the IPC server. Called when gateway shuts down.
|
||||
*/
|
||||
export function stopIpcServer(): void {
|
||||
if (server) {
|
||||
@@ -141,7 +141,7 @@ export function stopIpcServer(): void {
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the relay IPC server is running.
|
||||
* Check if the gateway IPC server is running.
|
||||
*/
|
||||
export function isRelayRunning(): boolean {
|
||||
try {
|
||||
@@ -154,8 +154,8 @@ export function isRelayRunning(): boolean {
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a message through the running relay's IPC.
|
||||
* Returns null if relay is not running.
|
||||
* Send a message through the running gateway's IPC.
|
||||
* Returns null if gateway is not running.
|
||||
*/
|
||||
export async function sendViaIpc(
|
||||
to: string,
|
||||
@@ -214,7 +214,7 @@ export async function sendViaIpc(
|
||||
if (!resolved) {
|
||||
resolved = true;
|
||||
clearTimeout(timeout);
|
||||
// Socket exists but can't connect - relay might have crashed
|
||||
// Socket exists but can't connect - gateway might have crashed
|
||||
resolve(null);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import type { WarelayConfig } from "../config/config.js";
|
||||
import type { ClawdisConfig } from "../config/config.js";
|
||||
import {
|
||||
computeBackoff,
|
||||
DEFAULT_HEARTBEAT_SECONDS,
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
} from "./reconnect.js";
|
||||
|
||||
describe("web reconnect helpers", () => {
|
||||
const cfg: WarelayConfig = {};
|
||||
const cfg: ClawdisConfig = {};
|
||||
|
||||
it("resolves sane reconnect defaults with clamps", () => {
|
||||
const policy = resolveReconnectPolicy(cfg, {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { randomUUID } from "node:crypto";
|
||||
|
||||
import type { WarelayConfig } from "../config/config.js";
|
||||
import type { ClawdisConfig } from "../config/config.js";
|
||||
|
||||
export type ReconnectPolicy = {
|
||||
initialMs: number;
|
||||
@@ -23,7 +23,7 @@ const clamp = (val: number, min: number, max: number) =>
|
||||
Math.max(min, Math.min(max, val));
|
||||
|
||||
export function resolveHeartbeatSeconds(
|
||||
cfg: WarelayConfig,
|
||||
cfg: ClawdisConfig,
|
||||
overrideSeconds?: number,
|
||||
): number {
|
||||
const candidate = overrideSeconds ?? cfg.web?.heartbeatSeconds;
|
||||
@@ -32,7 +32,7 @@ export function resolveHeartbeatSeconds(
|
||||
}
|
||||
|
||||
export function resolveReconnectPolicy(
|
||||
cfg: WarelayConfig,
|
||||
cfg: ClawdisConfig,
|
||||
overrides?: Partial<ReconnectPolicy>,
|
||||
): ReconnectPolicy {
|
||||
const merged = {
|
||||
|
||||
Reference in New Issue
Block a user