fix: normalize gateway dev mode detection
This commit is contained in:
@@ -100,13 +100,9 @@ function mergeConfig(
|
||||
enabled: overrides?.remote?.batch?.enabled ?? defaults?.remote?.batch?.enabled ?? true,
|
||||
wait: overrides?.remote?.batch?.wait ?? defaults?.remote?.batch?.wait ?? true,
|
||||
pollIntervalMs:
|
||||
overrides?.remote?.batch?.pollIntervalMs ??
|
||||
defaults?.remote?.batch?.pollIntervalMs ??
|
||||
5000,
|
||||
overrides?.remote?.batch?.pollIntervalMs ?? defaults?.remote?.batch?.pollIntervalMs ?? 5000,
|
||||
timeoutMinutes:
|
||||
overrides?.remote?.batch?.timeoutMinutes ??
|
||||
defaults?.remote?.batch?.timeoutMinutes ??
|
||||
60,
|
||||
overrides?.remote?.batch?.timeoutMinutes ?? defaults?.remote?.batch?.timeoutMinutes ?? 60,
|
||||
};
|
||||
const remote = includeRemote
|
||||
? {
|
||||
|
||||
@@ -112,8 +112,9 @@ export function installSessionToolResultGuard(sessionManager: SessionManager): {
|
||||
|
||||
const result = originalAppend(sanitized as never);
|
||||
|
||||
const sessionFile = (sessionManager as { getSessionFile?: () => string | null })
|
||||
.getSessionFile?.();
|
||||
const sessionFile = (
|
||||
sessionManager as { getSessionFile?: () => string | null }
|
||||
).getSessionFile?.();
|
||||
if (sessionFile) {
|
||||
emitSessionTranscriptUpdate(sessionFile);
|
||||
}
|
||||
|
||||
@@ -44,11 +44,9 @@ describe("tool meta formatting", () => {
|
||||
|
||||
it("keeps exec flags outside markdown and moves them to the front", () => {
|
||||
vi.stubEnv("HOME", "/Users/test");
|
||||
const out = formatToolAggregate(
|
||||
"exec",
|
||||
["cd /Users/test/dir && gemini 2>&1 · elevated"],
|
||||
{ markdown: true },
|
||||
);
|
||||
const out = formatToolAggregate("exec", ["cd /Users/test/dir && gemini 2>&1 · elevated"], {
|
||||
markdown: true,
|
||||
});
|
||||
expect(out).toBe("🛠️ exec: elevated · `cd ~/dir && gemini 2>&1`");
|
||||
});
|
||||
|
||||
|
||||
@@ -5,9 +5,7 @@ export type ChannelEntryMatch<T> = {
|
||||
wildcardKey?: string;
|
||||
};
|
||||
|
||||
export function buildChannelKeyCandidates(
|
||||
...keys: Array<string | undefined | null>
|
||||
): string[] {
|
||||
export function buildChannelKeyCandidates(...keys: Array<string | undefined | null>): string[] {
|
||||
const seen = new Set<string>();
|
||||
const candidates: string[] = [];
|
||||
for (const key of keys) {
|
||||
|
||||
@@ -24,10 +24,7 @@ import {
|
||||
} from "./config-helpers.js";
|
||||
import { resolveDiscordGroupRequireMention } from "./group-mentions.js";
|
||||
import { formatPairingApproveHint } from "./helpers.js";
|
||||
import {
|
||||
looksLikeDiscordTargetId,
|
||||
normalizeDiscordMessagingTarget,
|
||||
} from "./normalize/discord.js";
|
||||
import { looksLikeDiscordTargetId, normalizeDiscordMessagingTarget } from "./normalize/discord.js";
|
||||
import { discordOnboardingAdapter } from "./onboarding/discord.js";
|
||||
import { PAIRING_APPROVED_MESSAGE } from "./pairing-message.js";
|
||||
import {
|
||||
|
||||
@@ -18,10 +18,7 @@ import {
|
||||
} from "./config-helpers.js";
|
||||
import { formatPairingApproveHint } from "./helpers.js";
|
||||
import { resolveChannelMediaMaxBytes } from "./media-limits.js";
|
||||
import {
|
||||
looksLikeSignalTargetId,
|
||||
normalizeSignalMessagingTarget,
|
||||
} from "./normalize/signal.js";
|
||||
import { looksLikeSignalTargetId, normalizeSignalMessagingTarget } from "./normalize/signal.js";
|
||||
import { signalOnboardingAdapter } from "./onboarding/signal.js";
|
||||
import { PAIRING_APPROVED_MESSAGE } from "./pairing-message.js";
|
||||
import {
|
||||
|
||||
@@ -31,9 +31,9 @@ describe("requireTargetKind", () => {
|
||||
});
|
||||
|
||||
it("throws when the kind is missing or mismatched", () => {
|
||||
expect(() => requireTargetKind({ platform: "Slack", target: undefined, kind: "channel" })).toThrow(
|
||||
/Slack channel id is required/,
|
||||
);
|
||||
expect(() =>
|
||||
requireTargetKind({ platform: "Slack", target: undefined, kind: "channel" }),
|
||||
).toThrow(/Slack channel id is required/);
|
||||
const target = buildMessagingTarget("user", "U123", "U123");
|
||||
expect(() => requireTargetKind({ platform: "Slack", target, kind: "channel" })).toThrow(
|
||||
/Slack channel id is required/,
|
||||
|
||||
@@ -110,7 +110,9 @@ export function registerMemoryCli(program: Command) {
|
||||
return;
|
||||
}
|
||||
if (opts.index) {
|
||||
const line = indexError ? `Memory index failed: ${indexError}` : "Memory index complete.";
|
||||
const line = indexError
|
||||
? `Memory index failed: ${indexError}`
|
||||
: "Memory index complete.";
|
||||
defaultRuntime.log(line);
|
||||
}
|
||||
const rich = isRich();
|
||||
@@ -127,7 +129,9 @@ export function registerMemoryCli(program: Command) {
|
||||
`(requested: ${status.requestedProvider})`,
|
||||
)}`,
|
||||
`${label("Model")} ${info(status.model)}`,
|
||||
status.sources?.length ? `${label("Sources")} ${info(status.sources.join(", "))}` : null,
|
||||
status.sources?.length
|
||||
? `${label("Sources")} ${info(status.sources.join(", "))}`
|
||||
: null,
|
||||
`${label("Indexed")} ${success(`${status.files} files · ${status.chunks} chunks`)}`,
|
||||
`${label("Dirty")} ${status.dirty ? warn("yes") : muted("no")}`,
|
||||
`${label("Store")} ${info(status.dbPath)}`,
|
||||
|
||||
@@ -5,18 +5,15 @@ import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { ChannelPlugin } from "../../channels/plugins/types.js";
|
||||
import { channelsCapabilitiesCommand } from "./capabilities.js";
|
||||
import { fetchSlackScopes } from "../../slack/scopes.js";
|
||||
import {
|
||||
getChannelPlugin,
|
||||
listChannelPlugins,
|
||||
} from "../../channels/plugins/index.js";
|
||||
import { getChannelPlugin, listChannelPlugins } from "../../channels/plugins/index.js";
|
||||
|
||||
const logs: string[] = [];
|
||||
const errors: string[] = [];
|
||||
|
||||
vi.mock("./shared.js", () => ({
|
||||
requireValidConfig: vi.fn(async () => ({ channels: {} })),
|
||||
formatChannelAccountLabel: vi.fn(({ channel, accountId }: { channel: string; accountId: string }) =>
|
||||
`${channel}:${accountId}`,
|
||||
formatChannelAccountLabel: vi.fn(
|
||||
({ channel, accountId }: { channel: string; accountId: string }) => `${channel}:${accountId}`,
|
||||
),
|
||||
}));
|
||||
|
||||
|
||||
@@ -146,8 +146,10 @@ function formatProbeLines(channelId: string, probe: unknown): string[] {
|
||||
}
|
||||
const flags: string[] = [];
|
||||
const canJoinGroups = (bot as { canJoinGroups?: boolean | null })?.canJoinGroups;
|
||||
const canReadAll = (bot as { canReadAllGroupMessages?: boolean | null })?.canReadAllGroupMessages;
|
||||
const inlineQueries = (bot as { supportsInlineQueries?: boolean | null })?.supportsInlineQueries;
|
||||
const canReadAll = (bot as { canReadAllGroupMessages?: boolean | null })
|
||||
?.canReadAllGroupMessages;
|
||||
const inlineQueries = (bot as { supportsInlineQueries?: boolean | null })
|
||||
?.supportsInlineQueries;
|
||||
if (typeof canJoinGroups === "boolean") flags.push(`joinGroups=${canJoinGroups}`);
|
||||
if (typeof canReadAll === "boolean") flags.push(`readAllGroupMessages=${canReadAll}`);
|
||||
if (typeof inlineQueries === "boolean") flags.push(`inlineQueries=${inlineQueries}`);
|
||||
@@ -187,14 +189,15 @@ function formatProbeLines(channelId: string, probe: unknown): string[] {
|
||||
const roles = Array.isArray(graph.roles)
|
||||
? graph.roles.map((role) => String(role).trim()).filter(Boolean)
|
||||
: [];
|
||||
const scopes = typeof graph.scopes === "string"
|
||||
? graph.scopes
|
||||
.split(/\s+/)
|
||||
.map((scope) => scope.trim())
|
||||
.filter(Boolean)
|
||||
: Array.isArray(graph.scopes)
|
||||
? graph.scopes.map((scope) => String(scope).trim()).filter(Boolean)
|
||||
: [];
|
||||
const scopes =
|
||||
typeof graph.scopes === "string"
|
||||
? graph.scopes
|
||||
.split(/\s+/)
|
||||
.map((scope) => scope.trim())
|
||||
.filter(Boolean)
|
||||
: Array.isArray(graph.scopes)
|
||||
? graph.scopes.map((scope) => String(scope).trim()).filter(Boolean)
|
||||
: [];
|
||||
if (graph.ok === false) {
|
||||
lines.push(`Graph: ${theme.error(graph.error ?? "failed")}`);
|
||||
} else if (roles.length > 0 || scopes.length > 0) {
|
||||
@@ -219,7 +222,8 @@ function formatProbeLines(channelId: string, probe: unknown): string[] {
|
||||
lines.push("Probe: ok");
|
||||
}
|
||||
if (ok === false) {
|
||||
const error = typeof probeObj.error === "string" && probeObj.error ? ` (${probeObj.error})` : "";
|
||||
const error =
|
||||
typeof probeObj.error === "string" && probeObj.error ? ` (${probeObj.error})` : "";
|
||||
lines.push(`Probe: ${theme.error(`failed${error}`)}`);
|
||||
}
|
||||
return lines;
|
||||
@@ -388,8 +392,7 @@ export async function channelsCapabilitiesCommand(
|
||||
const cfg = await requireValidConfig(runtime);
|
||||
if (!cfg) return;
|
||||
const timeoutMs = normalizeTimeout(opts.timeout, 10_000);
|
||||
const rawChannel =
|
||||
typeof opts.channel === "string" ? opts.channel.trim().toLowerCase() : "";
|
||||
const rawChannel = typeof opts.channel === "string" ? opts.channel.trim().toLowerCase() : "";
|
||||
const rawTarget = typeof opts.target === "string" ? opts.target.trim() : "";
|
||||
|
||||
if (opts.account && (!rawChannel || rawChannel === "all")) {
|
||||
@@ -483,9 +486,7 @@ export async function channelsCapabilitiesCommand(
|
||||
const label = perms.channelId ? ` (${perms.channelId})` : "";
|
||||
lines.push(`Permissions${label}: ${list}`);
|
||||
if (perms.missingRequired && perms.missingRequired.length > 0) {
|
||||
lines.push(
|
||||
`${theme.warn("Missing required:")} ${perms.missingRequired.join(", ")}`,
|
||||
);
|
||||
lines.push(`${theme.warn("Missing required:")} ${perms.missingRequired.join(", ")}`);
|
||||
} else {
|
||||
lines.push(theme.success("Missing required: none"));
|
||||
}
|
||||
|
||||
@@ -34,9 +34,10 @@ afterEach(() => {
|
||||
|
||||
describe("resolveGatewayDevMode", () => {
|
||||
it("detects dev mode for src ts entrypoints", () => {
|
||||
expect(
|
||||
resolveGatewayDevMode(["node", "/Users/me/clawdbot/src/cli/index.ts"]),
|
||||
).toBe(true);
|
||||
expect(resolveGatewayDevMode(["node", "/Users/me/clawdbot/src/cli/index.ts"])).toBe(true);
|
||||
expect(resolveGatewayDevMode(["node", "C:\\Users\\me\\clawdbot\\src\\cli\\index.ts"])).toBe(
|
||||
true,
|
||||
);
|
||||
expect(resolveGatewayDevMode(["node", "/Users/me/clawdbot/dist/cli/index.js"])).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import path from "node:path";
|
||||
|
||||
import { resolveGatewayLaunchAgentLabel } from "../daemon/constants.js";
|
||||
import { resolveGatewayProgramArguments } from "../daemon/program-args.js";
|
||||
import {
|
||||
@@ -20,7 +18,8 @@ export type GatewayInstallPlan = {
|
||||
|
||||
export function resolveGatewayDevMode(argv: string[] = process.argv): boolean {
|
||||
const entry = argv[1];
|
||||
return Boolean(entry?.includes(`${path.sep}src${path.sep}`) && entry.endsWith(".ts"));
|
||||
const normalizedEntry = entry?.replaceAll("\\", "/");
|
||||
return Boolean(normalizedEntry?.includes("/src/") && normalizedEntry.endsWith(".ts"));
|
||||
}
|
||||
|
||||
export async function buildGatewayInstallPlan(params: {
|
||||
|
||||
@@ -15,10 +15,7 @@ import {
|
||||
GATEWAY_DAEMON_RUNTIME_OPTIONS,
|
||||
type GatewayDaemonRuntime,
|
||||
} from "./daemon-runtime.js";
|
||||
import {
|
||||
buildGatewayInstallPlan,
|
||||
gatewayInstallErrorHint,
|
||||
} from "./daemon-install-helpers.js";
|
||||
import { buildGatewayInstallPlan, gatewayInstallErrorHint } from "./daemon-install-helpers.js";
|
||||
import { buildGatewayRuntimeHints, formatGatewayRuntimeSummary } from "./doctor-format.js";
|
||||
import type { DoctorOptions, DoctorPrompter } from "./doctor-prompter.js";
|
||||
import { healthCommand } from "./health.js";
|
||||
|
||||
@@ -4,10 +4,7 @@ import type { ClawdbotConfig } from "../config/config.js";
|
||||
import { resolveGatewayPort, resolveIsNixMode } from "../config/paths.js";
|
||||
import { findExtraGatewayServices, renderGatewayServiceCleanupHints } from "../daemon/inspect.js";
|
||||
import { findLegacyGatewayServices, uninstallLegacyGatewayServices } from "../daemon/legacy.js";
|
||||
import {
|
||||
renderSystemNodeWarning,
|
||||
resolveSystemNodeInfo,
|
||||
} from "../daemon/runtime-paths.js";
|
||||
import { renderSystemNodeWarning, resolveSystemNodeInfo } from "../daemon/runtime-paths.js";
|
||||
import { resolveGatewayService } from "../daemon/service.js";
|
||||
import {
|
||||
auditGatewayServiceConfig,
|
||||
@@ -16,10 +13,7 @@ import {
|
||||
} from "../daemon/service-audit.js";
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
import { note } from "../terminal/note.js";
|
||||
import {
|
||||
buildGatewayInstallPlan,
|
||||
gatewayInstallErrorHint,
|
||||
} from "./daemon-install-helpers.js";
|
||||
import { buildGatewayInstallPlan, gatewayInstallErrorHint } from "./daemon-install-helpers.js";
|
||||
import {
|
||||
DEFAULT_GATEWAY_DAEMON_RUNTIME,
|
||||
GATEWAY_DAEMON_RUNTIME_OPTIONS,
|
||||
|
||||
@@ -3,10 +3,7 @@ import { resolveGatewayService } from "../../../daemon/service.js";
|
||||
import { isSystemdUserServiceAvailable } from "../../../daemon/systemd.js";
|
||||
import type { RuntimeEnv } from "../../../runtime.js";
|
||||
import { DEFAULT_GATEWAY_DAEMON_RUNTIME, isGatewayDaemonRuntime } from "../../daemon-runtime.js";
|
||||
import {
|
||||
buildGatewayInstallPlan,
|
||||
gatewayInstallErrorHint,
|
||||
} from "../../daemon-install-helpers.js";
|
||||
import { buildGatewayInstallPlan, gatewayInstallErrorHint } from "../../daemon-install-helpers.js";
|
||||
import type { OnboardOptions } from "../../onboard-types.js";
|
||||
import { ensureSystemdUserLingerNonInteractive } from "../../systemd-linger.js";
|
||||
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import type { Guild, User } from "@buape/carbon";
|
||||
|
||||
import { buildChannelKeyCandidates, resolveChannelEntryMatch } from "../../channels/channel-config.js";
|
||||
import {
|
||||
buildChannelKeyCandidates,
|
||||
resolveChannelEntryMatch,
|
||||
} from "../../channels/channel-config.js";
|
||||
import { formatDiscordUserTag } from "./format.js";
|
||||
|
||||
export type DiscordAllowList = {
|
||||
|
||||
@@ -266,7 +266,9 @@ export async function preflightDiscordMessage(
|
||||
channelConfig?.matchSource ?? "none"
|
||||
}`;
|
||||
if (isGuildMessage && channelConfig?.enabled === false) {
|
||||
logVerbose(`Blocked discord channel ${message.channelId} (channel disabled, ${channelMatchMeta})`);
|
||||
logVerbose(
|
||||
`Blocked discord channel ${message.channelId} (channel disabled, ${channelMatchMeta})`,
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -535,7 +535,7 @@ async function dispatchDiscordCommandInteraction(params: {
|
||||
threadChannel: {
|
||||
id: rawChannelId,
|
||||
name: channelName,
|
||||
parentId: "parentId" in channel ? channel.parentId ?? undefined : undefined,
|
||||
parentId: "parentId" in channel ? (channel.parentId ?? undefined) : undefined,
|
||||
parent: undefined,
|
||||
},
|
||||
channelInfo,
|
||||
|
||||
@@ -55,11 +55,7 @@ describe("memory indexing with OpenAI batches", () => {
|
||||
let uploadedRequests: Array<{ custom_id?: string }> = [];
|
||||
const fetchMock = vi.fn(async (input: RequestInfo | URL, init?: RequestInit) => {
|
||||
const url =
|
||||
typeof input === "string"
|
||||
? input
|
||||
: input instanceof URL
|
||||
? input.toString()
|
||||
: input.url;
|
||||
typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
|
||||
if (url.endsWith("/files")) {
|
||||
const body = init?.body;
|
||||
if (!(body instanceof FormData)) {
|
||||
|
||||
@@ -71,10 +71,7 @@ describe("memory embedding batches", () => {
|
||||
await manager.sync({ force: true });
|
||||
|
||||
const status = manager.status();
|
||||
const totalTexts = embedBatch.mock.calls.reduce(
|
||||
(sum, call) => sum + (call[0]?.length ?? 0),
|
||||
0,
|
||||
);
|
||||
const totalTexts = embedBatch.mock.calls.reduce((sum, call) => sum + (call[0]?.length ?? 0), 0);
|
||||
expect(totalTexts).toBe(status.chunks);
|
||||
expect(embedBatch.mock.calls.length).toBeGreaterThan(1);
|
||||
});
|
||||
|
||||
@@ -711,7 +711,10 @@ export class MemoryIndexManager {
|
||||
}));
|
||||
}
|
||||
|
||||
private shouldSyncSessions(params?: { reason?: string; force?: boolean }, needsFullReindex = false) {
|
||||
private shouldSyncSessions(
|
||||
params?: { reason?: string; force?: boolean },
|
||||
needsFullReindex = false,
|
||||
) {
|
||||
if (!this.sources.has("sessions")) return false;
|
||||
if (params?.force) return true;
|
||||
const reason = params?.reason;
|
||||
@@ -876,9 +879,7 @@ export class MemoryIndexManager {
|
||||
force?: boolean;
|
||||
progress?: (update: MemorySyncProgressUpdate) => void;
|
||||
}) {
|
||||
const progress = params?.progress
|
||||
? this.createSyncProgress(params.progress)
|
||||
: undefined;
|
||||
const progress = params?.progress ? this.createSyncProgress(params.progress) : undefined;
|
||||
const vectorReady = await this.ensureVectorReady();
|
||||
const meta = this.readMeta();
|
||||
const needsFullReindex =
|
||||
@@ -972,7 +973,10 @@ export class MemoryIndexManager {
|
||||
}
|
||||
|
||||
private normalizeSessionText(value: string): string {
|
||||
return value.replace(/\s*\n+\s*/g, " ").replace(/\s+/g, " ").trim();
|
||||
return value
|
||||
.replace(/\s*\n+\s*/g, " ")
|
||||
.replace(/\s+/g, " ")
|
||||
.trim();
|
||||
}
|
||||
|
||||
private extractSessionText(content: unknown): string | null {
|
||||
|
||||
@@ -17,7 +17,14 @@ export function normalizeAllowListLower(list?: Array<string | number>) {
|
||||
export type SlackAllowListMatch = {
|
||||
allowed: boolean;
|
||||
matchKey?: string;
|
||||
matchSource?: "wildcard" | "id" | "prefixed-id" | "prefixed-user" | "name" | "prefixed-name" | "slug";
|
||||
matchSource?:
|
||||
| "wildcard"
|
||||
| "id"
|
||||
| "prefixed-id"
|
||||
| "prefixed-user"
|
||||
| "name"
|
||||
| "prefixed-name"
|
||||
| "slug";
|
||||
};
|
||||
|
||||
export function resolveSlackAllowListMatch(params: {
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import type { SlackReactionNotificationMode } from "../../config/config.js";
|
||||
import type { SlackMessageEvent } from "../types.js";
|
||||
import { buildChannelKeyCandidates, resolveChannelEntryMatch } from "../../channels/channel-config.js";
|
||||
import {
|
||||
buildChannelKeyCandidates,
|
||||
resolveChannelEntryMatch,
|
||||
} from "../../channels/channel-config.js";
|
||||
import { allowListMatches, normalizeAllowListLower, normalizeSlackSlug } from "./allow-list.js";
|
||||
|
||||
export type SlackChannelConfigResolved = {
|
||||
|
||||
@@ -77,7 +77,10 @@ async function callSlack(
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchSlackScopes(token: string, timeoutMs: number): Promise<SlackScopesResult> {
|
||||
export async function fetchSlackScopes(
|
||||
token: string,
|
||||
timeoutMs: number,
|
||||
): Promise<SlackScopesResult> {
|
||||
const client = new WebClient(token, { timeout: timeoutMs });
|
||||
const attempts: SlackScopesSource[] = ["auth.scopes", "apps.permissions.info"];
|
||||
const errors: string[] = [];
|
||||
|
||||
@@ -115,8 +115,8 @@ export async function auditTelegramGroupMembership(params: {
|
||||
matchKey: chatId,
|
||||
matchSource: "id",
|
||||
});
|
||||
continue;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
const status = isRecord((json as TelegramApiOk<unknown>).result)
|
||||
? ((json as TelegramApiOk<{ status?: string }>).result.status ?? null)
|
||||
: null;
|
||||
|
||||
@@ -48,8 +48,6 @@ describe("expandTextLinks", () => {
|
||||
it("preserves offsets from the original string", () => {
|
||||
const text = " Hello world";
|
||||
const entities = [{ type: "text_link", offset: 1, length: 5, url: "https://example.com" }];
|
||||
expect(expandTextLinks(text, entities)).toBe(
|
||||
" [Hello](https://example.com) world",
|
||||
);
|
||||
expect(expandTextLinks(text, entities)).toBe(" [Hello](https://example.com) world");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -121,10 +121,7 @@ type TelegramTextLinkEntity = {
|
||||
url?: string;
|
||||
};
|
||||
|
||||
export function expandTextLinks(
|
||||
text: string,
|
||||
entities?: TelegramTextLinkEntity[] | null,
|
||||
): string {
|
||||
export function expandTextLinks(text: string, entities?: TelegramTextLinkEntity[] | null): string {
|
||||
if (!text || !entities?.length) return text;
|
||||
|
||||
const textLinks = entities
|
||||
@@ -140,7 +137,8 @@ export function expandTextLinks(
|
||||
for (const entity of textLinks) {
|
||||
const linkText = text.slice(entity.offset, entity.offset + entity.length);
|
||||
const markdown = `[${linkText}](${entity.url})`;
|
||||
result = result.slice(0, entity.offset) + markdown + result.slice(entity.offset + entity.length);
|
||||
result =
|
||||
result.slice(0, entity.offset) + markdown + result.slice(entity.offset + entity.length);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -70,9 +70,7 @@ export async function probeTelegram(
|
||||
id: meJson.result?.id ?? null,
|
||||
username: meJson.result?.username ?? null,
|
||||
canJoinGroups:
|
||||
typeof meJson.result?.can_join_groups === "boolean"
|
||||
? meJson.result?.can_join_groups
|
||||
: null,
|
||||
typeof meJson.result?.can_join_groups === "boolean" ? meJson.result?.can_join_groups : null,
|
||||
canReadAllGroupMessages:
|
||||
typeof meJson.result?.can_read_all_group_messages === "boolean"
|
||||
? meJson.result?.can_read_all_group_messages
|
||||
|
||||
@@ -180,7 +180,9 @@ export async function finalizeOnboardingWizard(options: FinalizeOnboardingOption
|
||||
} catch (err) {
|
||||
installError = err instanceof Error ? err.message : String(err);
|
||||
} finally {
|
||||
progress.stop(installError ? "Gateway daemon install failed." : "Gateway daemon installed.");
|
||||
progress.stop(
|
||||
installError ? "Gateway daemon install failed." : "Gateway daemon installed.",
|
||||
);
|
||||
}
|
||||
if (installError) {
|
||||
await prompter.note(`Gateway daemon install failed: ${installError}`, "Gateway");
|
||||
|
||||
Reference in New Issue
Block a user