branding: default to clawdis paths and launchd label
This commit is contained in:
@@ -30,11 +30,11 @@
|
|||||||
|
|
||||||
## Security & Configuration Tips
|
## Security & Configuration Tips
|
||||||
- Environment: copy `.env.example`; set Twilio creds and WhatsApp sender (`TWILIO_WHATSAPP_FROM`).
|
- 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.
|
- Media hosting relies on Tailscale Funnel when using Twilio; use `warelay webhook --ingress tailscale` or `--serve-media` for local hosting.
|
||||||
|
|
||||||
## Agent-Specific Notes
|
## 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.
|
- 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`.
|
- 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`.
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
- **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`.
|
- **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`.
|
- **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.
|
- **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
|
### Reliability & UX
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
- **Logged out:** Console prints “session logged out”; re-link with `warelay login --provider web`.
|
- **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`.
|
- **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.
|
- **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
|
## Helpful commands
|
||||||
- Start relay web-only: `pnpm warelay relay --provider web --verbose`
|
- Start relay web-only: `pnpm warelay relay --provider web --verbose`
|
||||||
|
|||||||
@@ -445,7 +445,7 @@ export async function getReplyFromConfig(
|
|||||||
triggerWarelayRestart();
|
triggerWarelayRestart();
|
||||||
cleanupTyping();
|
cleanupTyping();
|
||||||
return {
|
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.",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
import { spawn } from "node:child_process";
|
import { spawn } from "node:child_process";
|
||||||
|
|
||||||
const DEFAULT_LAUNCHD_LABEL = "com.steipete.warelay";
|
const DEFAULT_LAUNCHD_LABEL = "com.steipete.clawdis";
|
||||||
|
|
||||||
export function triggerWarelayRestart(): void {
|
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 =
|
const uid =
|
||||||
typeof process.getuid === "function" ? process.getuid() : undefined;
|
typeof process.getuid === "function" ? process.getuid() : undefined;
|
||||||
const target = uid !== undefined ? `gui/${uid}/${label}` : label;
|
const target = uid !== undefined ? `gui/${uid}/${label}` : label;
|
||||||
|
|||||||
@@ -9,8 +9,9 @@ const HOME = path.join(realOs.tmpdir(), "warelay-home-redirect");
|
|||||||
const mockRequest = vi.fn();
|
const mockRequest = vi.fn();
|
||||||
|
|
||||||
vi.doMock("node:os", () => ({
|
vi.doMock("node:os", () => ({
|
||||||
default: { homedir: () => HOME },
|
default: { homedir: () => HOME, tmpdir: () => realOs.tmpdir() },
|
||||||
homedir: () => HOME,
|
homedir: () => HOME,
|
||||||
|
tmpdir: () => realOs.tmpdir(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.doMock("node:https", () => ({
|
vi.doMock("node:https", () => ({
|
||||||
|
|||||||
@@ -7,8 +7,9 @@ const realOs = await vi.importActual<typeof import("node:os")>("node:os");
|
|||||||
const HOME = path.join(realOs.tmpdir(), "warelay-home-test");
|
const HOME = path.join(realOs.tmpdir(), "warelay-home-test");
|
||||||
|
|
||||||
vi.mock("node:os", () => ({
|
vi.mock("node:os", () => ({
|
||||||
default: { homedir: () => HOME },
|
default: { homedir: () => HOME, tmpdir: () => realOs.tmpdir() },
|
||||||
homedir: () => HOME,
|
homedir: () => HOME,
|
||||||
|
tmpdir: () => realOs.tmpdir(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const store = await import("./store.js");
|
const store = await import("./store.js");
|
||||||
|
|||||||
@@ -2,13 +2,13 @@ import crypto from "node:crypto";
|
|||||||
import { createWriteStream } from "node:fs";
|
import { createWriteStream } from "node:fs";
|
||||||
import fs from "node:fs/promises";
|
import fs from "node:fs/promises";
|
||||||
import { request } from "node:https";
|
import { request } from "node:https";
|
||||||
import os from "node:os";
|
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { pipeline } from "node:stream/promises";
|
import { pipeline } from "node:stream/promises";
|
||||||
|
|
||||||
import { detectMime, extensionForMime } from "./mime.js";
|
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 MAX_BYTES = 5 * 1024 * 1024; // 5MB
|
||||||
const DEFAULT_TTL_MS = 2 * 60 * 1000; // 2 minutes
|
const DEFAULT_TTL_MS = 2 * 60 * 1000; // 2 minutes
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import fs from "node:fs";
|
import fs from "node:fs";
|
||||||
import os from "node:os";
|
import os from "node:os";
|
||||||
|
import path from "node:path";
|
||||||
import { isVerbose, logVerbose } from "./globals.js";
|
import { isVerbose, logVerbose } from "./globals.js";
|
||||||
|
|
||||||
export async function ensureDir(dir: string) {
|
export async function ensureDir(dir: string) {
|
||||||
@@ -70,4 +71,10 @@ export function sleep(ms: number) {
|
|||||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
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;
|
||||||
|
})();
|
||||||
|
|||||||
@@ -22,7 +22,9 @@ afterEach(() => {
|
|||||||
|
|
||||||
describe("ipc hardening", () => {
|
describe("ipc hardening", () => {
|
||||||
it("creates private socket dir and socket with tight perms", async () => {
|
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;
|
process.env.HOME = tmpHome;
|
||||||
vi.resetModules();
|
vi.resetModules();
|
||||||
|
|
||||||
@@ -31,7 +33,7 @@ describe("ipc hardening", () => {
|
|||||||
const sendHandler = vi.fn().mockResolvedValue({ messageId: "msg1" });
|
const sendHandler = vi.fn().mockResolvedValue({ messageId: "msg1" });
|
||||||
ipc.startIpcServer(sendHandler);
|
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(dirStat.mode & 0o777).toBe(0o700);
|
||||||
|
|
||||||
expect(ipc.isRelayRunning()).toBe(true);
|
expect(ipc.isRelayRunning()).toBe(true);
|
||||||
@@ -47,10 +49,10 @@ describe("ipc hardening", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("refuses to start when IPC dir is a symlink", async () => {
|
it("refuses to start when IPC dir is a symlink", async () => {
|
||||||
const tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), "warelay-home-"));
|
const tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), "clawdis-home-"));
|
||||||
const warelayDir = path.join(tmpHome, ".warelay");
|
const clawdisDir = path.join(tmpHome, ".clawdis");
|
||||||
fs.mkdirSync(warelayDir, { recursive: true });
|
fs.mkdirSync(clawdisDir, { recursive: true });
|
||||||
fs.symlinkSync("/tmp", path.join(warelayDir, "ipc"));
|
fs.symlinkSync("/tmp", path.join(clawdisDir, "ipc"));
|
||||||
|
|
||||||
process.env.HOME = tmpHome;
|
process.env.HOME = tmpHome;
|
||||||
vi.resetModules();
|
vi.resetModules();
|
||||||
|
|||||||
@@ -10,12 +10,12 @@
|
|||||||
|
|
||||||
import fs from "node:fs";
|
import fs from "node:fs";
|
||||||
import net from "node:net";
|
import net from "node:net";
|
||||||
import os from "node:os";
|
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
|
|
||||||
import { getChildLogger } from "../logging.js";
|
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");
|
const SOCKET_PATH = path.join(SOCKET_DIR, "relay.sock");
|
||||||
|
|
||||||
export interface IpcSendRequest {
|
export interface IpcSendRequest {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { randomUUID } from "node:crypto";
|
import { randomUUID } from "node:crypto";
|
||||||
import fsSync from "node:fs";
|
import fsSync from "node:fs";
|
||||||
import fs from "node:fs/promises";
|
import fs from "node:fs/promises";
|
||||||
import os from "node:os";
|
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import {
|
import {
|
||||||
DisconnectReason,
|
DisconnectReason,
|
||||||
@@ -17,14 +16,10 @@ import { danger, info, success } from "../globals.js";
|
|||||||
import { getChildLogger } from "../logging.js";
|
import { getChildLogger } from "../logging.js";
|
||||||
import { defaultRuntime, type RuntimeEnv } from "../runtime.js";
|
import { defaultRuntime, type RuntimeEnv } from "../runtime.js";
|
||||||
import type { Provider } from "../utils.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";
|
import { VERSION } from "../version.js";
|
||||||
|
|
||||||
export const WA_WEB_AUTH_DIR = path.join(
|
export const WA_WEB_AUTH_DIR = path.join(CONFIG_DIR, "credentials");
|
||||||
os.homedir(),
|
|
||||||
".warelay",
|
|
||||||
"credentials",
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a Baileys socket backed by the multi-file auth store we keep on disk.
|
* Create a Baileys socket backed by the multi-file auth store we keep on disk.
|
||||||
|
|||||||
Reference in New Issue
Block a user