diff --git a/AGENTS.md b/AGENTS.md index 459197c42..492d73102 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -30,11 +30,11 @@ ## Security & Configuration Tips - Environment: copy `.env.example`; set Twilio creds and WhatsApp sender (`TWILIO_WHATSAPP_FROM`). -- Web provider stores creds at `~/.warelay/credentials/`; rerun `warelay login` if logged out. +- Web provider stores creds at `~/.clawdis/credentials/` (legacy fallback: `~/.warelay/credentials/`); rerun `warelay login` if logged out. - Media hosting relies on Tailscale Funnel when using Twilio; use `warelay webhook --ingress tailscale` or `--serve-media` for local hosting. ## Agent-Specific Notes -- Relay is managed by launchctl (label `com.steipete.warelay`). After code changes restart with `launchctl kickstart -k gui/$UID/com.steipete.warelay` and verify via `launchctl list | grep warelay`. Use tmux only if you spin up a temporary relay yourself and clean it up afterward. +- Relay is managed by launchctl (new label `com.steipete.clawdis`). After code changes restart with `launchctl kickstart -k gui/$UID/com.steipete.clawdis` and verify via `launchctl list | grep clawdis`. Legacy label `com.steipete.warelay` still exists for rollback; prefer the new one. Use tmux only if you spin up a temporary relay yourself and clean it up afterward. - Also read the shared guardrails at `~/Projects/oracle/AGENTS.md` and `~/Projects/agent-scripts/AGENTS.MD` before making changes; align with any cross-repo rules noted there. - When asked to open a “session” file, open the Pi/Tau session logs under `~/.pi/agent/sessions/warelay/*.jsonl` (newest unless a specific ID is given), not the default `sessions.json`. diff --git a/CHANGELOG.md b/CHANGELOG.md index 96ef1577c..2eaf82fb8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,7 +20,7 @@ - **Pi/Tau stability:** RPC replies buffered until the assistant turn finishes; parsers return consistent `texts[]`; web auto-replies keep a warm Tau RPC process to avoid cold starts. - **Claude prompt flow:** One-time `sessionIntro` with per-message `/think:high` bodyPrefix; system prompt always sent on first turn even with `sendSystemOnce`. - **Heartbeat UX:** Backpressure skips reply heartbeats while other commands run; skips don’t refresh session `updatedAt`; web/Twilio heartbeats normalize array payloads and optional `heartbeatCommand`. -- **Control via WhatsApp:** Send `/restart` to restart the warelay launchd service (`com.steipete.warelay`) from your allowed numbers. +- **Control via WhatsApp:** Send `/restart` to restart the launchd service (`com.steipete.clawdis`; legacy `com.steipete.warelay`) from your allowed numbers. - **Tau completion signal:** RPC now resolves on Tau’s `agent_end` (or process exit) so late assistant messages aren’t truncated; 5-minute hard cap only as a failsafe. ### Reliability & UX diff --git a/docs/refactor/web-relay-troubleshooting.md b/docs/refactor/web-relay-troubleshooting.md index 214b28f2c..997791b85 100644 --- a/docs/refactor/web-relay-troubleshooting.md +++ b/docs/refactor/web-relay-troubleshooting.md @@ -5,7 +5,7 @@ - **Logged out:** Console prints “session logged out”; re-link with `warelay login --provider web`. - **Repeated retries then exit:** Reconnects are capped (default 12 attempts). Tune with `--web-retries`, `--web-retry-initial`, `--web-retry-max`, or config `web.reconnect`. - **No inbound messages:** Ensure the QR-linked account is online in WhatsApp, and check logs for `web-heartbeat` to confirm auth age/connection. -- **Fast nuke:** From an allowed WhatsApp sender you can send `/restart` to kick `com.steipete.warelay` via launchd; wait a few seconds for it to relink. +- **Fast nuke:** From an allowed WhatsApp sender you can send `/restart` to kick `com.steipete.clawdis` via launchd (legacy: `com.steipete.warelay`); wait a few seconds for it to relink. ## Helpful commands - Start relay web-only: `pnpm warelay relay --provider web --verbose` diff --git a/src/auto-reply/reply.ts b/src/auto-reply/reply.ts index b1f04c638..60f978310 100644 --- a/src/auto-reply/reply.ts +++ b/src/auto-reply/reply.ts @@ -445,7 +445,7 @@ export async function getReplyFromConfig( triggerWarelayRestart(); cleanupTyping(); return { - text: "Restarting warelay via launchctl; give me a few seconds to come back online.", + text: "Restarting clawdis via launchctl; give me a few seconds to come back online.", }; } diff --git a/src/infra/restart.ts b/src/infra/restart.ts index b213b3fe1..73f6d6515 100644 --- a/src/infra/restart.ts +++ b/src/infra/restart.ts @@ -1,9 +1,12 @@ import { spawn } from "node:child_process"; -const DEFAULT_LAUNCHD_LABEL = "com.steipete.warelay"; +const DEFAULT_LAUNCHD_LABEL = "com.steipete.clawdis"; export function triggerWarelayRestart(): void { - const label = process.env.WARELAY_LAUNCHD_LABEL || DEFAULT_LAUNCHD_LABEL; + const label = + process.env.WARELAY_LAUNCHD_LABEL || + process.env.CLAWDIS_LAUNCHD_LABEL || + DEFAULT_LAUNCHD_LABEL; const uid = typeof process.getuid === "function" ? process.getuid() : undefined; const target = uid !== undefined ? `gui/${uid}/${label}` : label; diff --git a/src/media/store.redirect.test.ts b/src/media/store.redirect.test.ts index e933665ba..07126de39 100644 --- a/src/media/store.redirect.test.ts +++ b/src/media/store.redirect.test.ts @@ -9,8 +9,9 @@ const HOME = path.join(realOs.tmpdir(), "warelay-home-redirect"); const mockRequest = vi.fn(); vi.doMock("node:os", () => ({ - default: { homedir: () => HOME }, + default: { homedir: () => HOME, tmpdir: () => realOs.tmpdir() }, homedir: () => HOME, + tmpdir: () => realOs.tmpdir(), })); vi.doMock("node:https", () => ({ diff --git a/src/media/store.test.ts b/src/media/store.test.ts index e95a08b0c..182106818 100644 --- a/src/media/store.test.ts +++ b/src/media/store.test.ts @@ -7,8 +7,9 @@ const realOs = await vi.importActual("node:os"); const HOME = path.join(realOs.tmpdir(), "warelay-home-test"); vi.mock("node:os", () => ({ - default: { homedir: () => HOME }, + default: { homedir: () => HOME, tmpdir: () => realOs.tmpdir() }, homedir: () => HOME, + tmpdir: () => realOs.tmpdir(), })); const store = await import("./store.js"); diff --git a/src/media/store.ts b/src/media/store.ts index 31664aab9..f237973f7 100644 --- a/src/media/store.ts +++ b/src/media/store.ts @@ -2,13 +2,13 @@ import crypto from "node:crypto"; import { createWriteStream } from "node:fs"; import fs from "node:fs/promises"; import { request } from "node:https"; -import os from "node:os"; import path from "node:path"; import { pipeline } from "node:stream/promises"; import { detectMime, extensionForMime } from "./mime.js"; +import { CONFIG_DIR } from "../utils.js"; -const MEDIA_DIR = path.join(os.homedir(), ".warelay", "media"); +const MEDIA_DIR = path.join(CONFIG_DIR, "media"); const MAX_BYTES = 5 * 1024 * 1024; // 5MB const DEFAULT_TTL_MS = 2 * 60 * 1000; // 2 minutes diff --git a/src/utils.ts b/src/utils.ts index 4fe5dfb09..df6125d2c 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,5 +1,6 @@ import fs from "node:fs"; import os from "node:os"; +import path from "node:path"; import { isVerbose, logVerbose } from "./globals.js"; export async function ensureDir(dir: string) { @@ -70,4 +71,10 @@ export function sleep(ms: number) { return new Promise((resolve) => setTimeout(resolve, ms)); } -export const CONFIG_DIR = `${os.homedir()}/.warelay`; +// Prefer new branding directory; fall back to legacy for compatibility. +export const CONFIG_DIR = (() => { + const clawdis = path.join(os.homedir(), ".clawdis"); + const legacy = path.join(os.homedir(), ".warelay"); + if (fs.existsSync(clawdis)) return clawdis; + return legacy; +})(); diff --git a/src/web/ipc.test.ts b/src/web/ipc.test.ts index 4d13ed689..7409f2a17 100644 --- a/src/web/ipc.test.ts +++ b/src/web/ipc.test.ts @@ -22,7 +22,9 @@ afterEach(() => { describe("ipc hardening", () => { it("creates private socket dir and socket with tight perms", async () => { - const tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), "warelay-home-")); + const tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), "clawdis-home-")); + const clawdisDir = path.join(tmpHome, ".clawdis"); + fs.mkdirSync(clawdisDir, { recursive: true }); process.env.HOME = tmpHome; vi.resetModules(); @@ -31,7 +33,7 @@ describe("ipc hardening", () => { const sendHandler = vi.fn().mockResolvedValue({ messageId: "msg1" }); ipc.startIpcServer(sendHandler); - const dirStat = fs.lstatSync(path.join(tmpHome, ".warelay", "ipc")); + const dirStat = fs.lstatSync(path.join(tmpHome, ".clawdis", "ipc")); expect(dirStat.mode & 0o777).toBe(0o700); expect(ipc.isRelayRunning()).toBe(true); @@ -47,10 +49,10 @@ describe("ipc hardening", () => { }); it("refuses to start when IPC dir is a symlink", async () => { - const tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), "warelay-home-")); - const warelayDir = path.join(tmpHome, ".warelay"); - fs.mkdirSync(warelayDir, { recursive: true }); - fs.symlinkSync("/tmp", path.join(warelayDir, "ipc")); + const tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), "clawdis-home-")); + const clawdisDir = path.join(tmpHome, ".clawdis"); + fs.mkdirSync(clawdisDir, { recursive: true }); + fs.symlinkSync("/tmp", path.join(clawdisDir, "ipc")); process.env.HOME = tmpHome; vi.resetModules(); diff --git a/src/web/ipc.ts b/src/web/ipc.ts index ac99194ae..0d3943532 100644 --- a/src/web/ipc.ts +++ b/src/web/ipc.ts @@ -10,12 +10,12 @@ import fs from "node:fs"; import net from "node:net"; -import os from "node:os"; import path from "node:path"; import { getChildLogger } from "../logging.js"; +import { CONFIG_DIR } from "../utils.js"; -const SOCKET_DIR = path.join(os.homedir(), ".warelay", "ipc"); +const SOCKET_DIR = path.join(CONFIG_DIR, "ipc"); const SOCKET_PATH = path.join(SOCKET_DIR, "relay.sock"); export interface IpcSendRequest { diff --git a/src/web/session.ts b/src/web/session.ts index ff16feb71..38c185792 100644 --- a/src/web/session.ts +++ b/src/web/session.ts @@ -1,7 +1,6 @@ import { randomUUID } from "node:crypto"; import fsSync from "node:fs"; import fs from "node:fs/promises"; -import os from "node:os"; import path from "node:path"; import { DisconnectReason, @@ -17,14 +16,10 @@ import { danger, info, success } from "../globals.js"; import { getChildLogger } from "../logging.js"; import { defaultRuntime, type RuntimeEnv } from "../runtime.js"; import type { Provider } from "../utils.js"; -import { ensureDir, jidToE164 } from "../utils.js"; +import { CONFIG_DIR, ensureDir, jidToE164 } from "../utils.js"; import { VERSION } from "../version.js"; -export const WA_WEB_AUTH_DIR = path.join( - os.homedir(), - ".warelay", - "credentials", -); +export const WA_WEB_AUTH_DIR = path.join(CONFIG_DIR, "credentials"); /** * Create a Baileys socket backed by the multi-file auth store we keep on disk.