refactor: replace ADE /agents/ links with /chat/ and centralize URL building [LET-7798] (#1254)

Co-authored-by: Letta Code <noreply@letta.com>
This commit is contained in:
Charles Packer
2026-03-04 15:45:42 -08:00
committed by GitHub
parent 654fc2d213
commit 603e5941d8
12 changed files with 72 additions and 35 deletions

View File

@@ -193,6 +193,7 @@ import {
toLines,
} from "./helpers/accumulator";
import { classifyApprovals } from "./helpers/approvalClassification";
import { buildChatUrl } from "./helpers/appUrls";
import { backfillBuffers } from "./helpers/backfill";
import { chunkLog } from "./helpers/chunkLog";
import {
@@ -6275,7 +6276,7 @@ export default function App({
});
// Build success message with hints
const agentUrl = `https://app.letta.com/projects/default-project/agents/${agent.id}`;
const agentUrl = buildChatUrl(agent.id);
const memfsTip = settingsManager.isMemfsEnabled(agent.id)
? "Memory will be auto-initialized on your first message."
: "Tip: use /init to initialize your agent's memory system!";
@@ -6964,10 +6965,9 @@ export default function App({
// Special handling for /ade command - open agent in browser
if (trimmed === "/ade") {
const adeUrl =
conversationIdRef.current === "default"
? `https://app.letta.com/agents/${agentId}`
: `https://app.letta.com/agents/${agentId}?conversation=${conversationIdRef.current}`;
const adeUrl = buildChatUrl(agentId, {
conversationId: conversationIdRef.current,
});
const cmd = commandRunner.start("/ade", "Opening ADE...");

View File

@@ -9,6 +9,7 @@ import {
} from "node:fs";
import { tmpdir } from "node:os";
import { dirname, join } from "node:path";
import { buildChatUrl } from "../helpers/appUrls";
const DEFAULT_WORKFLOW_PATH = ".github/workflows/letta.yml";
const ALTERNATE_WORKFLOW_PATH = ".github/workflows/letta-code.yml";
@@ -512,9 +513,7 @@ export async function installGithubApp(
committed: false,
secretAction: "set",
agentId: resolvedAgentId,
agentUrl: resolvedAgentId
? `https://app.letta.com/agents/${resolvedAgentId}`
: null,
agentUrl: resolvedAgentId ? buildChatUrl(resolvedAgentId) : null,
};
}
@@ -540,9 +539,7 @@ export async function installGithubApp(
committed: true,
secretAction: "set",
agentId: resolvedAgentId,
agentUrl: resolvedAgentId
? `https://app.letta.com/agents/${resolvedAgentId}`
: null,
agentUrl: resolvedAgentId ? buildChatUrl(resolvedAgentId) : null,
};
} finally {
rmSync(tempDir, { recursive: true, force: true });

View File

@@ -9,6 +9,7 @@ import { settingsManager } from "../../settings-manager";
import { getErrorMessage } from "../../utils/error";
import { registerWithCloud } from "../../websocket/listen-register";
import type { Buffers, Line } from "../helpers/accumulator";
import { buildChatUrl } from "../helpers/appUrls";
// tiny helper for unique ids
function uid(prefix: string) {
@@ -179,11 +180,10 @@ export async function handleListen(
const buildConnectionUrl = (connId: string): string => {
if (!ctx.agentId) return "";
let url = `https://app.letta.com/agents/${ctx.agentId}?deviceId=${connId}`;
if (ctx.conversationId) {
url += `&conversationId=${ctx.conversationId}`;
}
return url;
return buildChatUrl(ctx.agentId, {
deviceId: connId,
conversationId: ctx.conversationId ?? undefined,
});
};
// Start listen flow

View File

@@ -6,6 +6,7 @@ import type { ModelReasoningEffort } from "../../agent/model";
import { DEFAULT_AGENT_NAME } from "../../constants";
import { settingsManager } from "../../settings-manager";
import { getVersion } from "../../version";
import { buildAppUrl, buildChatUrl } from "../helpers/appUrls";
import { useTerminalWidth } from "../hooks/useTerminalWidth";
import { colors } from "./colors";
import { Text } from "./Text";
@@ -71,7 +72,7 @@ export const AgentInfoBar = memo(function AgentInfoBar({
const isCloudUser = serverUrl?.includes("api.letta.com");
const adeConversationUrl =
agentId && agentId !== "loading"
? `https://app.letta.com/agents/${agentId}${conversationId && conversationId !== "default" ? `?conversation=${conversationId}` : ""}`
? buildChatUrl(agentId, { conversationId })
: "";
const showBottomBar = agentId && agentId !== "loading";
const reasoningLabel = formatReasoningLabel(currentReasoningEffort);
@@ -130,7 +131,7 @@ export const AgentInfoBar = memo(function AgentInfoBar({
<Text>Open in ADE </Text>
</Link>
<Text dimColor>· </Text>
<Link url="https://app.letta.com/settings/organization/usage">
<Link url={buildAppUrl("/settings/organization/usage")}>
<Text>View usage </Text>
</Link>
</Box>
@@ -139,7 +140,7 @@ export const AgentInfoBar = memo(function AgentInfoBar({
<Box width={rightWidth} flexShrink={1}>
<Text dimColor wrap="truncate-end">
{truncateText(
`Open in ADE: ${adeConversationUrl} · Usage: https://app.letta.com/settings/organization/usage`,
`Open in ADE: ${adeConversationUrl} · Usage: ${buildAppUrl("/settings/organization/usage")}`,
rightWidth,
)}
</Text>

View File

@@ -11,6 +11,7 @@ import {
type TreeNode,
} from "../../agent/memoryScanner";
import { generateAndOpenMemoryViewer } from "../../web/generate-memory-viewer";
import { buildChatUrl } from "../helpers/appUrls";
import { useTerminalWidth } from "../hooks/useTerminalWidth";
import { colors } from "./colors";
import { Text } from "./Text";
@@ -51,7 +52,7 @@ export function MemfsTreeViewer({
const terminalWidth = useTerminalWidth();
const solidLine = SOLID_LINE.repeat(Math.max(terminalWidth, 10));
const isTmux = Boolean(process.env.TMUX);
const adeUrl = `https://app.letta.com/agents/${agentId}?view=memory${conversationId && conversationId !== "default" ? `&conversation=${conversationId}` : ""}`;
const adeUrl = buildChatUrl(agentId, { view: "memory", conversationId });
// State
const [selectedIndex, setSelectedIndex] = useState(0);

View File

@@ -4,6 +4,7 @@ import Link from "ink-link";
import { useEffect, useState } from "react";
import { getClient } from "../../agent/client";
import { debugLog } from "../../utils/debug";
import { buildChatUrl } from "../helpers/appUrls";
import { useTerminalWidth } from "../hooks/useTerminalWidth";
import { colors } from "./colors";
import { MarkdownDisplay } from "./MarkdownDisplay";
@@ -40,7 +41,7 @@ export function MemoryTabViewer({
const terminalWidth = useTerminalWidth();
const solidLine = SOLID_LINE.repeat(Math.max(terminalWidth, 10));
const isTmux = Boolean(process.env.TMUX);
const adeUrl = `https://app.letta.com/agents/${agentId}?view=memory${conversationId && conversationId !== "default" ? `&conversation=${conversationId}` : ""}`;
const adeUrl = buildChatUrl(agentId, { view: "memory", conversationId });
const [selectedTabIndex, setSelectedTabIndex] = useState(0);
const [scrollOffset, setScrollOffset] = useState(0);

View File

@@ -1,4 +1,5 @@
import type { SessionStatsSnapshot } from "../../agent/stats";
import { buildAppUrl } from "../helpers/appUrls";
import { formatCompact } from "../helpers/format";
export function formatDuration(ms: number): string {
@@ -66,7 +67,7 @@ export function formatUsageStats({
outputLines.push(
`Plan: [${balance.billing_tier}]`,
"https://app.letta.com/settings/organization/usage",
buildAppUrl("/settings/organization/usage"),
"",
`Available credits: ◎${formatNumber(totalCredits)} ($${toDollars(totalCredits)})`,
`Monthly credits: ◎${formatNumber(monthlyCredits)} ($${toDollars(monthlyCredits)})`,

View File

@@ -0,0 +1,36 @@
const APP_BASE = "https://app.letta.com";
/**
* Build a chat URL for an agent, with optional conversation and extra query params.
*/
export function buildChatUrl(
agentId: string,
options?: {
conversationId?: string;
view?: string;
deviceId?: string;
},
): string {
const base = `${APP_BASE}/chat/${agentId}`;
const params = new URLSearchParams();
if (options?.view) {
params.set("view", options.view);
}
if (options?.deviceId) {
params.set("deviceId", options.deviceId);
}
if (options?.conversationId && options.conversationId !== "default") {
params.set("conversation", options.conversationId);
}
const qs = params.toString();
return qs ? `${base}?${qs}` : base;
}
/**
* Build a non-agent app URL (e.g. settings pages).
*/
export function buildAppUrl(path: string): string {
return `${APP_BASE}${path}`;
}

View File

@@ -1,10 +1,10 @@
import { APIError } from "@letta-ai/letta-client/core/error";
import { buildAppUrl, buildChatUrl } from "./appUrls";
import { getErrorContext } from "./errorContext";
import { checkZaiError } from "./zaiErrors";
const LETTA_USAGE_URL = "https://app.letta.com/settings/organization/usage";
const LETTA_AGENTS_URL =
"https://app.letta.com/projects/default-project/agents";
const LETTA_USAGE_URL = buildAppUrl("/settings/organization/usage");
const LETTA_AGENTS_URL = buildAppUrl("/projects/default-project/agents");
function extractReasonList(value: unknown): string[] {
if (!Array.isArray(value)) return [];
@@ -756,6 +756,6 @@ function createAgentLink(
agentId: string,
conversationId?: string,
): string {
const url = `https://app.letta.com/agents/${agentId}${conversationId && conversationId !== "default" ? `?conversation=${conversationId}` : ""}`;
const url = buildChatUrl(agentId, { conversationId });
return `View agent: \x1b]8;;${url}\x1b\\${agentId}\x1b]8;;\x1b\\ (run: ${runId})`;
}

View File

@@ -271,12 +271,12 @@ describe("success screen content", () => {
secretAction: "set",
agentId: "agent-aaaabbbb-cccc-dddd-eeee-ffffffffffff",
agentUrl:
"https://app.letta.com/agents/agent-aaaabbbb-cccc-dddd-eeee-ffffffffffff",
"https://app.letta.com/chat/agent-aaaabbbb-cccc-dddd-eeee-ffffffffffff",
};
test("agentUrl points to app.letta.com ADE", () => {
test("agentUrl points to app.letta.com chat", () => {
expect(baseResult.agentUrl).toBe(
`https://app.letta.com/agents/${baseResult.agentId}`,
`https://app.letta.com/chat/${baseResult.agentId}`,
);
});
@@ -361,15 +361,15 @@ describe("success screen content", () => {
expect(allText).not.toContain("Agent configured");
});
test("agent URL uses correct ADE format for any agent ID", () => {
test("agent URL uses correct chat format for any agent ID", () => {
const agentId = "agent-12345678-abcd-efgh-ijkl-123456789012";
const expectedUrl = `https://app.letta.com/agents/${agentId}`;
const expectedUrl = `https://app.letta.com/chat/${agentId}`;
// This mirrors the logic in installGithubApp
const agentUrl = agentId ? `https://app.letta.com/agents/${agentId}` : null;
const agentUrl = agentId ? `https://app.letta.com/chat/${agentId}` : null;
expect(agentUrl).toBe(expectedUrl);
expect(agentUrl).toContain("app.letta.com/agents/");
expect(agentUrl).toContain("app.letta.com/chat/");
expect(agentUrl).toContain(agentId);
});
});

View File

@@ -232,7 +232,7 @@ describe("waitForBackgroundSubagentLink", () => {
setTimeout(() => {
updateSubagent("subagent-link-1", {
agentURL: "https://app.letta.com/agents/agent-123",
agentURL: "https://app.letta.com/chat/agent-123",
});
}, 20);

View File

@@ -952,7 +952,7 @@ html.dark .warning-badge { background: hsl(42, 30%, 18%); color: hsl(42, 80%, 70
} else {
adeBase = 'https://app.letta.com';
}
agentIdEl.href = adeBase + '/agents/' + encodeURIComponent(agentId);
agentIdEl.href = adeBase + '/chat/' + encodeURIComponent(agentId);
agentIdEl.target = '_blank';
}
document.getElementById('generated-at').textContent = 'Generated ' + new Date(DATA.generatedAt).toLocaleString();