diff --git a/src/cli/App.tsx b/src/cli/App.tsx index 51295b3..dc0192a 100644 --- a/src/cli/App.tsx +++ b/src/cli/App.tsx @@ -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..."); diff --git a/src/cli/commands/install-github-app.ts b/src/cli/commands/install-github-app.ts index 892d174..57746ba 100644 --- a/src/cli/commands/install-github-app.ts +++ b/src/cli/commands/install-github-app.ts @@ -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 }); diff --git a/src/cli/commands/listen.ts b/src/cli/commands/listen.ts index 18be4fd..be24fb7 100644 --- a/src/cli/commands/listen.ts +++ b/src/cli/commands/listen.ts @@ -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 diff --git a/src/cli/components/AgentInfoBar.tsx b/src/cli/components/AgentInfoBar.tsx index 7044356..71d48a2 100644 --- a/src/cli/components/AgentInfoBar.tsx +++ b/src/cli/components/AgentInfoBar.tsx @@ -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({ Open in ADE ↗ · - + View usage ↗ @@ -139,7 +140,7 @@ export const AgentInfoBar = memo(function AgentInfoBar({ {truncateText( - `Open in ADE: ${adeConversationUrl} · Usage: https://app.letta.com/settings/organization/usage`, + `Open in ADE: ${adeConversationUrl} · Usage: ${buildAppUrl("/settings/organization/usage")}`, rightWidth, )} diff --git a/src/cli/components/MemfsTreeViewer.tsx b/src/cli/components/MemfsTreeViewer.tsx index 4cba077..4167b17 100644 --- a/src/cli/components/MemfsTreeViewer.tsx +++ b/src/cli/components/MemfsTreeViewer.tsx @@ -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); diff --git a/src/cli/components/MemoryTabViewer.tsx b/src/cli/components/MemoryTabViewer.tsx index 259b68a..79f886b 100644 --- a/src/cli/components/MemoryTabViewer.tsx +++ b/src/cli/components/MemoryTabViewer.tsx @@ -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); diff --git a/src/cli/components/SessionStats.tsx b/src/cli/components/SessionStats.tsx index 6c28324..9ed8f10 100644 --- a/src/cli/components/SessionStats.tsx +++ b/src/cli/components/SessionStats.tsx @@ -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)})`, diff --git a/src/cli/helpers/appUrls.ts b/src/cli/helpers/appUrls.ts new file mode 100644 index 0000000..25a41bb --- /dev/null +++ b/src/cli/helpers/appUrls.ts @@ -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}`; +} diff --git a/src/cli/helpers/errorFormatter.ts b/src/cli/helpers/errorFormatter.ts index a716bd4..6bd4e21 100644 --- a/src/cli/helpers/errorFormatter.ts +++ b/src/cli/helpers/errorFormatter.ts @@ -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})`; } diff --git a/src/tests/cli/install-github-app.test.ts b/src/tests/cli/install-github-app.test.ts index 3dcc2a9..63443a5 100644 --- a/src/tests/cli/install-github-app.test.ts +++ b/src/tests/cli/install-github-app.test.ts @@ -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); }); }); diff --git a/src/tests/tools/task-background-helper.test.ts b/src/tests/tools/task-background-helper.test.ts index ab013e5..325cbb4 100644 --- a/src/tests/tools/task-background-helper.test.ts +++ b/src/tests/tools/task-background-helper.test.ts @@ -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); diff --git a/src/web/memory-viewer-template.txt b/src/web/memory-viewer-template.txt index f96a511..9c92976 100644 --- a/src/web/memory-viewer-template.txt +++ b/src/web/memory-viewer-template.txt @@ -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();