fix: daemon status guidance and telegram fetch
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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") {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user