fix: daemon status guidance and telegram fetch

This commit is contained in:
Peter Steinberger
2026-01-08 08:39:55 +01:00
parent 5b397c0f15
commit 9a11325cc9
5 changed files with 47 additions and 22 deletions

View File

@@ -21,6 +21,7 @@
### Fixes
- CLI/Daemon: add `clawdbot logs` tailing and improve restart/service hints across platforms.
- Gateway/CLI/Doctor: tighten LAN bind auth checks, warn/migrate mis-keyed gateway tokens, and surface last gateway error when daemon looks running but the port is closed.
- CLI/Daemon: add `clawdbot daemon install --force` and expand daemon status guidance for config mismatches and probe targets.
- Auto-reply: keep typing indicators alive during tool execution without changing typing-mode semantics. Thanks @thesash for PR #452.
- macOS: harden Voice Wake tester/runtime (pause trigger, mic persistence, local-only tester) and keep transcript logs private. Thanks @xadenryan for PR #438.
- macOS: preserve node bridge tunnel port override so remote nodes connect on the bridge port. Thanks @sircrumpet for PR #364.
@@ -82,6 +83,7 @@
- Sub-agents: allow `sessions_spawn` model overrides and error on invalid models. Thanks @azade-c for PR #298.
- Sub-agents: skip invalid model overrides with a warning and keep the run alive; tool exceptions now return tool errors instead of crashing the agent.
- Sub-agents: allow `sessions_spawn` to target other agents via per-agent allowlists (`routing.agents.<agentId>.subagents.allowAgents`).
- Sub-agents: treat `sessions_spawn` allowlists as case-insensitive.
- Tools: add `agents_list` to reveal allowed `sessions_spawn` targets for the current agent.
- Sessions: forward explicit sessionKey through gateway/chat/node bridge to avoid sub-agent sessionId mixups.
- Heartbeat: default interval 30m; clarified default prompt usage and HEARTBEAT.md template behavior.
@@ -107,8 +109,7 @@
- Telegram: honor `/activation` session mode for group mention gating and clarify group activation docs. Thanks @julianengel for PR #377.
- Telegram: isolate forum topic transcripts per thread and validate Gemini turn ordering in multi-topic sessions. Thanks @hsrvc for PR #407.
- Telegram: render Telegram-safe HTML for outbound formatting and fall back to plain text on parse errors. Thanks @RandyVentures for PR #435.
- Telegram: force grammY to use native fetch under Bun for BAN compatibility (avoids TLS chain errors).
- Telegram: keep grammY default fetch on Node; only override under Bun to avoid Node 24 regressions.
- Telegram: prefer native fetch when available (Node 18+ + Bun) for BAN compatibility; still respects proxy override.
- iMessage: ignore disconnect errors during shutdown (avoid unhandled promise rejections). Thanks @antons for PR #359.
- Messages: stop defaulting ack reactions to 👀 when identity emoji is missing.
- Auto-reply: require slash for control commands to avoid false triggers in normal text.

View File

@@ -106,15 +106,14 @@ export function createSessionsSpawnTool(opts?: {
const allowAgents =
resolveAgentConfig(cfg, requesterAgentId)?.subagents?.allowAgents ??
[];
const allowAny = allowAgents.some(
(value) => value.trim() === "*",
);
const allowAny = allowAgents.some((value) => value.trim() === "*");
const normalizedTargetId = targetAgentId.toLowerCase();
const allowSet = new Set(
allowAgents
.filter((value) => value.trim() && value.trim() !== "*")
.map((value) => normalizeAgentId(value)),
.map((value) => normalizeAgentId(value).toLowerCase()),
);
if (!allowAny && !allowSet.has(targetAgentId)) {
if (!allowAny && !allowSet.has(normalizedTargetId)) {
const allowedText = allowAny
? "*"
: allowSet.size > 0

View File

@@ -130,6 +130,7 @@ export type DaemonInstallOptions = {
port?: string | number;
runtime?: string;
token?: string;
force?: boolean;
};
function parsePort(raw: unknown): number | null {
@@ -192,6 +193,14 @@ function safeDaemonEnv(env: Record<string, string> | undefined): string[] {
return lines;
}
function normalizeListenerAddress(raw: string): string {
let value = raw.trim();
if (!value) return value;
value = value.replace(/^TCP\s+/i, "");
value = value.replace(/\s+\(LISTEN\)\s*$/i, "");
return value.trim();
}
async function probeGatewayStatus(opts: {
url: string;
token?: string;
@@ -330,7 +339,7 @@ async function gatherDaemonStatus(opts: {
const serviceEnv = command?.environment ?? undefined;
const mergedDaemonEnv = {
...(process.env as Record<string, string | undefined>),
...(serviceEnv ?? {}),
...(serviceEnv ?? undefined),
} satisfies Record<string, string | undefined>;
const cliConfigPath = resolveConfigPath(
@@ -389,7 +398,7 @@ async function gatherDaemonStatus(opts: {
const probeUrl = probeUrlOverride ?? `ws://${probeHost}:${daemonPort}`;
const probeNote =
!probeUrlOverride && bindMode === "lan"
? "Local probe uses loopback (127.0.0.1); gateway bind=lan listens on 0.0.0.0."
? "Local probe uses loopback (127.0.0.1). bind=lan listens on 0.0.0.0 (all interfaces); use a LAN IP for remote clients."
: !probeUrlOverride && bindMode === "loopback"
? "Loopback-only gateway; only local clients can connect."
: undefined;
@@ -539,6 +548,9 @@ function printDaemonStatus(status: DaemonStatus, opts: { json: boolean }) {
defaultRuntime.error(
"Root cause: CLI and daemon are using different config paths (likely a profile/state-dir mismatch).",
);
defaultRuntime.error(
"Fix: rerun `clawdbot daemon install --force` from the same --profile / CLAWDBOT_STATE_DIR you expect.",
);
}
}
if (status.gateway) {
@@ -610,7 +622,7 @@ function printDaemonStatus(status: DaemonStatus, opts: { json: boolean }) {
const addrs = Array.from(
new Set(
status.port.listeners
.map((l) => l.address?.trim())
.map((l) => (l.address ? normalizeListenerAddress(l.address) : ""))
.filter((v): v is string => Boolean(v)),
),
);
@@ -729,8 +741,11 @@ export async function runDaemonInstall(opts: DaemonInstallOptions) {
return;
}
if (loaded) {
defaultRuntime.log(`Gateway service already ${service.loadedText}.`);
return;
if (!opts.force) {
defaultRuntime.log(`Gateway service already ${service.loadedText}.`);
defaultRuntime.log("Reinstall with: clawdbot daemon install --force");
return;
}
}
const devMode =
@@ -896,6 +911,7 @@ export function registerDaemonCli(program: Command) {
.option("--port <port>", "Gateway port")
.option("--runtime <runtime>", "Daemon runtime (node|bun). Default: node")
.option("--token <token>", "Gateway token (token auth)")
.option("--force", "Reinstall/overwrite if already installed", false)
.action(async (opts) => {
await runDaemonInstall(opts);
});

View File

@@ -19,14 +19,18 @@ function formatSpawnDetail(result: {
}): string {
const clean = (value: string | Buffer | null | undefined) => {
const text =
typeof value === "string"
? value
: value
? value.toString()
: "";
typeof value === "string" ? value : value ? value.toString() : "";
return text.replace(/\s+/g, " ").trim();
};
if (result.error) return String(result.error);
if (result.error) {
if (result.error instanceof Error) return result.error.message;
if (typeof result.error === "string") return result.error;
try {
return JSON.stringify(result.error);
} catch {
return "unknown error";
}
}
const stderr = clean(result.stderr);
if (stderr) return stderr;
const stdout = clean(result.stdout);
@@ -42,6 +46,9 @@ function normalizeSystemdUnit(raw?: string): string {
}
export function triggerClawdbotRestart(): RestartAttempt {
if (process.env.VITEST || process.env.NODE_ENV === "test") {
return { ok: true, method: "supervisor", detail: "test mode" };
}
const tried: string[] = [];
if (process.platform !== "darwin") {
if (process.platform === "linux") {

View File

@@ -1,13 +1,15 @@
// Bun compatibility: force native fetch under Bun; keep grammY defaults on Node.
// Ensure native fetch is used when available (Bun + Node 18+).
export function resolveTelegramFetch(
proxyFetch?: typeof fetch,
): typeof fetch | undefined {
if (proxyFetch) return proxyFetch;
const isBun = "Bun" in globalThis || Boolean(process?.versions?.bun);
if (!isBun) return undefined;
const fetchImpl = globalThis.fetch;
const isBun = "Bun" in globalThis || Boolean(process?.versions?.bun);
if (!fetchImpl) {
throw new Error("fetch is not available; set telegram.proxy in config");
if (isBun) {
throw new Error("fetch is not available; set telegram.proxy in config");
}
return undefined;
}
return fetchImpl;
}