fix: improve gmail tailscale errors
This commit is contained in:
@@ -30,6 +30,7 @@
|
|||||||
- Build: import tool-display JSON as a module instead of runtime file reads. Thanks @mukhtharcm for PR #312.
|
- Build: import tool-display JSON as a module instead of runtime file reads. Thanks @mukhtharcm for PR #312.
|
||||||
- Browser: fix `browser snapshot`/`browser act` timeouts under Bun by patching Playwright’s CDP WebSocket selection. Thanks @azade-c for PR #307.
|
- Browser: fix `browser snapshot`/`browser act` timeouts under Bun by patching Playwright’s CDP WebSocket selection. Thanks @azade-c for PR #307.
|
||||||
- Browser: add `--browser-profile` flag and honor profile in tabs routes + browser tool. Thanks @jamesgroat for PR #324.
|
- Browser: add `--browser-profile` flag and honor profile in tabs routes + browser tool. Thanks @jamesgroat for PR #324.
|
||||||
|
- Gmail: include tailscale command exit codes/output when hook setup fails (easier debugging).
|
||||||
- Telegram: stop typing after tool results. Thanks @AbhisekBasu1 for PR #322.
|
- Telegram: stop typing after tool results. Thanks @AbhisekBasu1 for PR #322.
|
||||||
- Telegram: include sender identity in group envelope headers. (#336)
|
- Telegram: include sender identity in group envelope headers. (#336)
|
||||||
- Messages: stop defaulting ack reactions to 👀 when identity emoji is missing.
|
- Messages: stop defaulting ack reactions to 👀 when identity emoji is missing.
|
||||||
|
|||||||
@@ -2,11 +2,59 @@ import fs from "node:fs";
|
|||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
|
|
||||||
import { hasBinary } from "../agents/skills.js";
|
import { hasBinary } from "../agents/skills.js";
|
||||||
import { runCommandWithTimeout } from "../process/exec.js";
|
import { runCommandWithTimeout, type SpawnResult } from "../process/exec.js";
|
||||||
import { resolveUserPath } from "../utils.js";
|
import { resolveUserPath } from "../utils.js";
|
||||||
import { normalizeServePath } from "./gmail.js";
|
import { normalizeServePath } from "./gmail.js";
|
||||||
|
|
||||||
let cachedPythonPath: string | null | undefined;
|
let cachedPythonPath: string | null | undefined;
|
||||||
|
const MAX_OUTPUT_CHARS = 800;
|
||||||
|
|
||||||
|
function trimOutput(value: string): string {
|
||||||
|
const trimmed = value.trim();
|
||||||
|
if (!trimmed) return "";
|
||||||
|
if (trimmed.length <= MAX_OUTPUT_CHARS) return trimmed;
|
||||||
|
return `${trimmed.slice(0, MAX_OUTPUT_CHARS)}…`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatCommandFailure(command: string, result: SpawnResult): string {
|
||||||
|
const code = result.code ?? "null";
|
||||||
|
const signal = result.signal ? `, signal=${result.signal}` : "";
|
||||||
|
const killed = result.killed ? ", killed=true" : "";
|
||||||
|
const stderr = trimOutput(result.stderr);
|
||||||
|
const stdout = trimOutput(result.stdout);
|
||||||
|
const lines = [`${command} failed (code=${code}${signal}${killed})`];
|
||||||
|
if (stderr) lines.push(`stderr: ${stderr}`);
|
||||||
|
if (stdout) lines.push(`stdout: ${stdout}`);
|
||||||
|
return lines.join("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatCommandResult(command: string, result: SpawnResult): string {
|
||||||
|
const code = result.code ?? "null";
|
||||||
|
const signal = result.signal ? `, signal=${result.signal}` : "";
|
||||||
|
const killed = result.killed ? ", killed=true" : "";
|
||||||
|
const stderr = trimOutput(result.stderr);
|
||||||
|
const stdout = trimOutput(result.stdout);
|
||||||
|
const lines = [`${command} exited (code=${code}${signal}${killed})`];
|
||||||
|
if (stderr) lines.push(`stderr: ${stderr}`);
|
||||||
|
if (stdout) lines.push(`stdout: ${stdout}`);
|
||||||
|
return lines.join("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatJsonParseFailure(
|
||||||
|
command: string,
|
||||||
|
result: SpawnResult,
|
||||||
|
err: unknown,
|
||||||
|
): string {
|
||||||
|
const reason = err instanceof Error ? err.message : String(err);
|
||||||
|
return `${command} returned invalid JSON: ${reason}\n${formatCommandResult(
|
||||||
|
command,
|
||||||
|
result,
|
||||||
|
)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatCommand(command: string, args: string[]): string {
|
||||||
|
return [command, ...args].join(" ");
|
||||||
|
}
|
||||||
|
|
||||||
function findExecutablesOnPath(bins: string[]): string[] {
|
function findExecutablesOnPath(bins: string[]): string[] {
|
||||||
const pathEnv = process.env.PATH ?? "";
|
const pathEnv = process.env.PATH ?? "";
|
||||||
@@ -218,18 +266,20 @@ export async function ensureTailscaleEndpoint(params: {
|
|||||||
}): Promise<string> {
|
}): Promise<string> {
|
||||||
if (params.mode === "off") return "";
|
if (params.mode === "off") return "";
|
||||||
|
|
||||||
const status = await runCommandWithTimeout(
|
const statusArgs = ["status", "--json"];
|
||||||
["tailscale", "status", "--json"],
|
const statusCommand = formatCommand("tailscale", statusArgs);
|
||||||
{
|
const status = await runCommandWithTimeout(["tailscale", ...statusArgs], {
|
||||||
timeoutMs: 30_000,
|
timeoutMs: 30_000,
|
||||||
},
|
});
|
||||||
);
|
|
||||||
if (status.code !== 0) {
|
if (status.code !== 0) {
|
||||||
throw new Error(status.stderr || "tailscale status failed");
|
throw new Error(formatCommandFailure(statusCommand, status));
|
||||||
|
}
|
||||||
|
let parsed: { Self?: { DNSName?: string } };
|
||||||
|
try {
|
||||||
|
parsed = JSON.parse(status.stdout) as { Self?: { DNSName?: string } };
|
||||||
|
} catch (err) {
|
||||||
|
throw new Error(formatJsonParseFailure(statusCommand, status, err));
|
||||||
}
|
}
|
||||||
const parsed = JSON.parse(status.stdout) as {
|
|
||||||
Self?: { DNSName?: string };
|
|
||||||
};
|
|
||||||
const dnsName = parsed.Self?.DNSName?.replace(/\.$/, "");
|
const dnsName = parsed.Self?.DNSName?.replace(/\.$/, "");
|
||||||
if (!dnsName) {
|
if (!dnsName) {
|
||||||
throw new Error("tailscale DNS name missing; run tailscale up");
|
throw new Error("tailscale DNS name missing; run tailscale up");
|
||||||
@@ -238,7 +288,6 @@ export async function ensureTailscaleEndpoint(params: {
|
|||||||
const target = String(params.port);
|
const target = String(params.port);
|
||||||
const pathArg = normalizeServePath(params.path);
|
const pathArg = normalizeServePath(params.path);
|
||||||
const funnelArgs = [
|
const funnelArgs = [
|
||||||
"tailscale",
|
|
||||||
params.mode,
|
params.mode,
|
||||||
"--bg",
|
"--bg",
|
||||||
"--set-path",
|
"--set-path",
|
||||||
@@ -246,11 +295,15 @@ export async function ensureTailscaleEndpoint(params: {
|
|||||||
"--yes",
|
"--yes",
|
||||||
target,
|
target,
|
||||||
];
|
];
|
||||||
const funnelResult = await runCommandWithTimeout(funnelArgs, {
|
const funnelCommand = formatCommand("tailscale", funnelArgs);
|
||||||
timeoutMs: 30_000,
|
const funnelResult = await runCommandWithTimeout(
|
||||||
});
|
["tailscale", ...funnelArgs],
|
||||||
|
{
|
||||||
|
timeoutMs: 30_000,
|
||||||
|
},
|
||||||
|
);
|
||||||
if (funnelResult.code !== 0) {
|
if (funnelResult.code !== 0) {
|
||||||
throw new Error(funnelResult.stderr || "tailscale funnel failed");
|
throw new Error(formatCommandFailure(funnelCommand, funnelResult));
|
||||||
}
|
}
|
||||||
|
|
||||||
const baseUrl = `https://${dnsName}${pathArg}`;
|
const baseUrl = `https://${dnsName}${pathArg}`;
|
||||||
|
|||||||
Reference in New Issue
Block a user