fix(cli): resolve global LRU agent on directory switch instead of cre… (#951)

Co-authored-by: Letta <noreply@letta.com>
This commit is contained in:
Charles Packer
2026-02-13 23:02:38 -08:00
committed by GitHub
parent 652628083d
commit e337330dbe
9 changed files with 747 additions and 70 deletions

View File

@@ -31,7 +31,7 @@ const INCOGNITO_DESCRIPTION =
*/
export const DEFAULT_AGENT_CONFIGS: Record<string, CreateAgentOptions> = {
memo: {
name: "Memo",
name: "Letta Code",
description: MEMO_DESCRIPTION,
// Uses default memory blocks and tools (full stateful config)
// Override persona block with Memo-specific personality
@@ -90,6 +90,11 @@ export async function ensureDefaultAgents(
const { agent } = await createAgent(DEFAULT_AGENT_CONFIGS.memo);
await addTagToAgent(client, agent.id, MEMO_TAG);
settingsManager.pinGlobal(agent.id);
// Enable memfs by default on Letta Cloud
const { enableMemfsIfCloud } = await import("./memoryFilesystem");
await enableMemfsIfCloud(agent.id);
return agent;
} catch (err) {
// Re-throw so caller can handle/exit appropriately

View File

@@ -215,3 +215,21 @@ export async function applyMemfsFlags(
pullSummary,
};
}
/**
* Enable memfs for a newly created agent if on Letta Cloud.
* Non-fatal: logs a warning on failure. Skips on self-hosted.
*/
export async function enableMemfsIfCloud(agentId: string): Promise<void> {
const { getServerUrl } = await import("./client");
const serverUrl = getServerUrl();
if (!serverUrl.includes("api.letta.com")) return;
try {
await applyMemfsFlags(agentId, true, undefined);
} catch (error) {
console.warn(
`Warning: Could not enable memfs for new agent: ${error instanceof Error ? error.message : String(error)}`,
);
}
}

View File

@@ -0,0 +1,88 @@
/**
* Pure startup agent resolution logic.
*
* Encodes the decision tree for which agent to use when `letta` starts:
* local LRU → global LRU → selector → create default
*
* Extracted from index.ts/headless.ts so it can be unit-tested without
* React effects or real network calls.
*/
export type StartupTarget =
| { action: "resume"; agentId: string; conversationId?: string }
| { action: "select" }
| { action: "create" };
export interface StartupResolutionInput {
/** Agent ID from local project LRU (via getLocalLastAgentId) */
localAgentId: string | null;
/** Conversation ID from local project LRU */
localConversationId: string | null;
/** Whether the local agent still exists on the server */
localAgentExists: boolean;
/** Agent ID from global LRU (via getGlobalLastAgentId) */
globalAgentId: string | null;
/** Whether the global agent still exists on the server */
globalAgentExists: boolean;
/** Number of merged pinned agents (local + global) */
mergedPinnedCount: number;
/** --new-agent flag: skip all resume logic, create fresh */
forceNew: boolean;
/** Self-hosted server with no available default model */
needsModelPicker: boolean;
}
/**
* Determine which agent to start with based on available context.
*
* Decision tree:
* 1. forceNew → create
* 2. local LRU valid → resume (with local conversation)
* 3. global LRU valid → resume (no conversation — project-scoped)
* 4. needsModelPicker → select
* 5. pinned agents exist → select
* 6. nothing → create
*/
export function resolveStartupTarget(
input: StartupResolutionInput,
): StartupTarget {
// --new-agent always creates
if (input.forceNew) {
return { action: "create" };
}
// Step 1: Local project LRU
if (input.localAgentId && input.localAgentExists) {
return {
action: "resume",
agentId: input.localAgentId,
conversationId: input.localConversationId ?? undefined,
};
}
// Step 2: Global LRU (directory-switching fallback)
// Do NOT restore global conversation — keep conversations project-scoped
if (input.globalAgentId && input.globalAgentExists) {
return {
action: "resume",
agentId: input.globalAgentId,
};
}
// Step 3: Self-hosted model picker
if (input.needsModelPicker) {
return { action: "select" };
}
// Step 4: Show selector if any pinned agents exist
if (input.mergedPinnedCount > 0) {
return { action: "select" };
}
// Step 5: True fresh user — create default agent
return { action: "create" };
}

View File

@@ -5136,6 +5136,12 @@ export default function App({
// Create the new agent
const { agent } = await createAgent(name);
// Enable memfs by default on Letta Cloud for new agents
const { enableMemfsIfCloud } = await import(
"../agent/memoryFilesystem"
);
await enableMemfsIfCloud(agent.id);
// Update project settings with new agent
await updateProjectSettings({ lastAgent: agent.id });

View File

@@ -82,8 +82,6 @@ export async function handleHeadlessCommand(
skillsDirectory?: string,
noSkills?: boolean,
) {
const settings = settingsManager.getSettings();
// Parse CLI args
// Include all flags from index.ts to prevent them from being treated as positionals
const { values, positionals } = parseArgs({
@@ -601,47 +599,57 @@ export async function handleHeadlessCommand(
};
const result = await createAgent(createOptions);
agent = result.agent;
// Enable memfs by default on Letta Cloud for new agents
const { enableMemfsIfCloud } = await import("./agent/memoryFilesystem");
await enableMemfsIfCloud(agent.id);
}
// Priority 4: Try to resume from project settings (.letta/settings.local.json)
// Store local conversation ID for use in conversation resolution below
let resolvedLocalConvId: string | null = null;
if (!agent) {
await settingsManager.loadLocalProjectSettings();
const localProjectSettings = settingsManager.getLocalProjectSettings();
if (localProjectSettings?.lastAgent) {
const localAgentId = settingsManager.getLocalLastAgentId(process.cwd());
if (localAgentId) {
try {
agent = await client.agents.retrieve(localProjectSettings.lastAgent);
agent = await client.agents.retrieve(localAgentId);
// Store local conversation for downstream resolution
const localSession = settingsManager.getLocalLastSession(process.cwd());
resolvedLocalConvId = localSession?.conversationId ?? null;
} catch (_error) {
// Local LRU agent doesn't exist - log and continue
console.error(
`Unable to locate agent ${localProjectSettings.lastAgent} in .letta/`,
);
console.error(`Unable to locate agent ${localAgentId} in .letta/`);
}
}
}
// Priority 5: Try to reuse global lastAgent if --continue flag is passed
if (!agent && shouldContinue) {
if (settings.lastAgent) {
// Priority 5: Try to reuse global LRU (covers directory-switching case)
// Do NOT restore global conversation — use default (project-scoped conversations)
if (!agent) {
const globalAgentId = settingsManager.getGlobalLastAgentId();
if (globalAgentId) {
try {
agent = await client.agents.retrieve(settings.lastAgent);
agent = await client.agents.retrieve(globalAgentId);
} catch (_error) {
// Global LRU agent doesn't exist
}
}
// --continue requires an LRU agent to exist
if (!agent) {
console.error("No recent session found in .letta/ or ~/.letta.");
console.error("Run 'letta' to get started.");
process.exit(1);
}
}
// Priority 6: Fresh user with no LRU - create Memo (same as interactive mode)
// Priority 6: --continue with no agent found → error
if (!agent && shouldContinue) {
console.error("No recent session found in .letta/ or ~/.letta.");
console.error("Run 'letta' to get started.");
process.exit(1);
}
// Priority 7: Fresh user with no LRU - create default agent
if (!agent) {
const { ensureDefaultAgents } = await import("./agent/defaults");
const memoAgent = await ensureDefaultAgents(client);
if (memoAgent) {
agent = memoAgent;
const defaultAgent = await ensureDefaultAgents(client);
if (defaultAgent) {
agent = defaultAgent;
}
}
@@ -786,8 +794,21 @@ export async function handleHeadlessCommand(
isolated_block_labels: isolatedBlockLabels,
});
conversationId = conversation.id;
} else if (resolvedLocalConvId) {
// Resumed from local LRU — restore the local conversation
if (resolvedLocalConvId === "default") {
conversationId = "default";
} else {
try {
await client.conversations.retrieve(resolvedLocalConvId);
conversationId = resolvedLocalConvId;
} catch {
// Local conversation no longer exists — fall back to default
conversationId = "default";
}
}
} else {
// Default (including --new-agent, --agent): use the agent's "default" conversation
// Default (including --new-agent, --agent, global LRU fallback): use "default" conversation
conversationId = "default";
}
markMilestone("HEADLESS_CONVERSATION_READY");

View File

@@ -1147,7 +1147,6 @@ async function main(): Promise<void> {
// Load settings
await settingsManager.loadLocalProjectSettings();
const localSettings = settingsManager.getLocalProjectSettings();
const globalPinned = settingsManager.getGlobalPinnedAgents();
const client = await getClient();
// For self-hosted servers, pre-fetch available models
@@ -1330,65 +1329,91 @@ async function main(): Promise<void> {
// =====================================================================
// DEFAULT PATH: No special flags
// Check local LRU, then selector, then defaults
// Check local LRU → global LRU → selector → create default
// =====================================================================
// Check if user would see selector (fresh dir, no bypass flags)
const wouldShowSelector =
!localSettings.lastAgent && !forceNew && !agentIdArg && !fromAfFile;
// Short-circuit: flags handled by init() skip resolution entirely
if (forceNew || agentIdArg || fromAfFile) {
setLoadingState("assembling");
return;
}
if (
wouldShowSelector &&
globalPinned.length === 0 &&
!needsModelPicker
) {
// New user with no pinned agents - create a fresh Memo agent
// NOTE: Always creates a new agent (no server-side tag lookup) to avoid
// picking up agents created by other users on shared orgs.
// Skip if needsModelPicker is true - let user select a model first.
const { ensureDefaultAgents } = await import("./agent/defaults");
// Step 1: Check local project LRU (session helpers centralize legacy fallback)
const localAgentId = settingsManager.getLocalLastAgentId(process.cwd());
let localAgentExists = false;
if (localAgentId) {
try {
const memoAgent = await ensureDefaultAgents(client);
if (memoAgent) {
setSelectedGlobalAgentId(memoAgent.id);
setLoadingState("assembling");
return;
}
// If memoAgent is null (createDefaultAgents disabled), fall through
} catch (err) {
console.error(
`Failed to create default agents: ${err instanceof Error ? err.message : String(err)}`,
await client.agents.retrieve(localAgentId);
localAgentExists = true;
} catch {
setFailedAgentMessage(
`Unable to locate recently used agent ${localAgentId}`,
);
process.exit(1);
}
}
// If there's a local LRU, use it directly (takes priority over model picker)
if (localSettings.lastAgent) {
// Step 2: Check global LRU (covers directory-switching case)
const globalAgentId = settingsManager.getGlobalLastAgentId();
let globalAgentExists = false;
if (globalAgentId && globalAgentId !== localAgentId) {
try {
await client.agents.retrieve(localSettings.lastAgent);
await client.agents.retrieve(globalAgentId);
globalAgentExists = true;
} catch {
// Global agent doesn't exist either
}
} else if (globalAgentId && globalAgentId === localAgentId) {
globalAgentExists = localAgentExists;
}
// Step 3: Resolve startup target using pure decision logic
const mergedPinned = settingsManager.getMergedPinnedAgents(
process.cwd(),
);
const { resolveStartupTarget } = await import(
"./agent/resolve-startup-agent"
);
const target = resolveStartupTarget({
localAgentId,
localConversationId: null, // DEFAULT PATH always uses default conv
localAgentExists,
globalAgentId,
globalAgentExists,
mergedPinnedCount: mergedPinned.length,
forceNew: false, // forceNew short-circuited above
needsModelPicker,
});
switch (target.action) {
case "resume":
setSelectedGlobalAgentId(target.agentId);
// Don't set selectedConversationId — DEFAULT PATH uses default conv.
// Conversation restoration is handled by --continue path instead.
setLoadingState("assembling");
return;
} catch {
// LRU agent doesn't exist, show message and fall through to selector
setFailedAgentMessage(
`Unable to locate recently used agent ${localSettings.lastAgent}`,
);
case "select":
setLoadingState("selecting_global");
return;
case "create": {
const { ensureDefaultAgents } = await import("./agent/defaults");
try {
const defaultAgent = await ensureDefaultAgents(client);
if (defaultAgent) {
setSelectedGlobalAgentId(defaultAgent.id);
setLoadingState("assembling");
return;
}
// If null (createDefaultAgents disabled), fall through
} catch (err) {
console.error(
`Failed to create default agent: ${err instanceof Error ? err.message : String(err)}`,
);
process.exit(1);
}
break;
}
}
// On self-hosted with unavailable default model, show selector to pick a model
if (needsModelPicker) {
setLoadingState("selecting_global");
return;
}
// Show selector if there are pinned agents to choose from
if (wouldShowSelector && globalPinned.length > 0) {
setLoadingState("selecting_global");
return;
}
setLoadingState("assembling");
}
checkAndStart();
@@ -1613,6 +1638,12 @@ async function main(): Promise<void> {
});
agent = result.agent;
setAgentProvenance(result.provenance);
// Enable memfs by default on Letta Cloud for new agents
const { enableMemfsIfCloud } = await import(
"./agent/memoryFilesystem"
);
await enableMemfsIfCloud(agent.id);
}
// Priority 4: Try to resume from project settings LRU (.letta/settings.local.json)

View File

@@ -0,0 +1,201 @@
import { describe, expect, test } from "bun:test";
import {
resolveStartupTarget,
type StartupResolutionInput,
} from "../agent/resolve-startup-agent";
/**
* Unit tests for the NUX (new user experience) agent resolution logic.
*
* Core invariant: switching directories with a valid global LRU
* should NOT create a new agent — it should resume the global agent.
*/
function makeInput(
overrides: Partial<StartupResolutionInput> = {},
): StartupResolutionInput {
return {
localAgentId: null,
localConversationId: null,
localAgentExists: false,
globalAgentId: null,
globalAgentExists: false,
mergedPinnedCount: 0,
forceNew: false,
needsModelPicker: false,
...overrides,
};
}
describe("resolveStartupTarget", () => {
test("fresh dir + valid global LRU → resumes global agent", () => {
const result = resolveStartupTarget(
makeInput({
globalAgentId: "agent-global-123",
globalAgentExists: true,
}),
);
expect(result).toEqual({
action: "resume",
agentId: "agent-global-123",
});
});
test("fresh dir + invalid global LRU + has pinned → select", () => {
const result = resolveStartupTarget(
makeInput({
globalAgentId: "agent-global-deleted",
globalAgentExists: false,
mergedPinnedCount: 3,
}),
);
expect(result).toEqual({ action: "select" });
});
test("fresh dir + invalid global LRU + no pinned → create", () => {
const result = resolveStartupTarget(
makeInput({
globalAgentId: "agent-global-deleted",
globalAgentExists: false,
mergedPinnedCount: 0,
}),
);
expect(result).toEqual({ action: "create" });
});
test("dir with local LRU + valid agent → resumes local with conversation", () => {
const result = resolveStartupTarget(
makeInput({
localAgentId: "agent-local-456",
localConversationId: "conv-local-789",
localAgentExists: true,
globalAgentId: "agent-global-123",
globalAgentExists: true,
}),
);
expect(result).toEqual({
action: "resume",
agentId: "agent-local-456",
conversationId: "conv-local-789",
});
});
test("dir with local LRU + invalid agent + valid global → resumes global (no conv)", () => {
const result = resolveStartupTarget(
makeInput({
localAgentId: "agent-local-deleted",
localConversationId: "conv-local-789",
localAgentExists: false,
globalAgentId: "agent-global-123",
globalAgentExists: true,
}),
);
expect(result).toEqual({
action: "resume",
agentId: "agent-global-123",
});
});
test("true fresh user (no local, no global, no pinned) → create", () => {
const result = resolveStartupTarget(makeInput());
expect(result).toEqual({ action: "create" });
});
test("no LRU but pinned agents exist → select", () => {
const result = resolveStartupTarget(
makeInput({
mergedPinnedCount: 2,
}),
);
expect(result).toEqual({ action: "select" });
});
test("forceNew = true → create (even with valid LRU)", () => {
const result = resolveStartupTarget(
makeInput({
localAgentId: "agent-local-456",
localAgentExists: true,
globalAgentId: "agent-global-123",
globalAgentExists: true,
forceNew: true,
}),
);
expect(result).toEqual({ action: "create" });
});
test("needsModelPicker + no valid agents → select (not create)", () => {
const result = resolveStartupTarget(
makeInput({
needsModelPicker: true,
}),
);
expect(result).toEqual({ action: "select" });
});
test("needsModelPicker takes priority over pinned selector", () => {
const result = resolveStartupTarget(
makeInput({
needsModelPicker: true,
mergedPinnedCount: 5,
}),
);
expect(result).toEqual({ action: "select" });
});
test("local LRU with null conversation → resumes without conversation", () => {
const result = resolveStartupTarget(
makeInput({
localAgentId: "agent-local-456",
localConversationId: null,
localAgentExists: true,
}),
);
expect(result).toEqual({
action: "resume",
agentId: "agent-local-456",
});
});
test("global LRU never restores conversation (project-scoped)", () => {
// Even if global session had a conversation, resolveStartupTarget
// should NOT include it — conversations are project-scoped
const result = resolveStartupTarget(
makeInput({
globalAgentId: "agent-global-123",
globalAgentExists: true,
}),
);
expect(result).toEqual({
action: "resume",
agentId: "agent-global-123",
});
// Verify no conversationId key (not even undefined)
expect("conversationId" in result).toBe(false);
});
test("same local/global ID invalid + no pinned → create", () => {
const result = resolveStartupTarget(
makeInput({
localAgentId: "agent-same",
localAgentExists: false,
globalAgentId: "agent-same",
globalAgentExists: false,
mergedPinnedCount: 0,
}),
);
expect(result).toEqual({ action: "create" });
});
test("same local/global ID invalid + pinned → select", () => {
const result = resolveStartupTarget(
makeInput({
localAgentId: "agent-same",
localAgentExists: false,
globalAgentId: "agent-same",
globalAgentExists: false,
mergedPinnedCount: 1,
}),
);
expect(result).toEqual({ action: "select" });
});
});

View File

@@ -124,3 +124,20 @@ describe("Startup Flow - Flag Conflicts", () => {
);
});
});
describe("Startup Flow - Smoke", () => {
test("--name conflicts with --new-agent", async () => {
const result = await runCli(["--name", "MyAgent", "--new-agent"], {
expectExit: 1,
});
expect(result.stderr).toContain("--name cannot be used with --new");
});
test("--new-agent headless parses and reaches credential check", async () => {
const result = await runCli(["--new-agent", "-p", "Say OK"], {
expectExit: 1,
});
expect(result.stderr).toContain("Missing LETTA_API_KEY");
expect(result.stderr).not.toContain("No recent session found");
});
});

View File

@@ -0,0 +1,290 @@
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { dirname, join } from "node:path";
import { resolveStartupTarget } from "../agent/resolve-startup-agent";
import { settingsManager } from "../settings-manager";
const originalHome = process.env.HOME;
const originalCwd = process.cwd();
let testHomeDir: string;
let testProjectDir: string;
async function writeJson(path: string, value: unknown): Promise<void> {
await mkdir(dirname(path), { recursive: true });
await writeFile(path, `${JSON.stringify(value, null, 2)}\n`, "utf-8");
}
async function writeGlobalSettings(settings: Record<string, unknown>) {
await writeJson(join(testHomeDir, ".letta", "settings.json"), settings);
}
async function writeLocalSettings(settings: Record<string, unknown>) {
await writeJson(
join(testProjectDir, ".letta", "settings.local.json"),
settings,
);
}
async function resolveFromSettings(options?: {
existingAgentIds?: string[];
includeLocalConversation?: boolean;
forceNew?: boolean;
needsModelPicker?: boolean;
}) {
const existing = new Set(options?.existingAgentIds ?? []);
await settingsManager.initialize();
await settingsManager.loadLocalProjectSettings(testProjectDir);
const localAgentId = settingsManager.getLocalLastAgentId(testProjectDir);
const localSession = settingsManager.getLocalLastSession(testProjectDir);
const globalAgentId = settingsManager.getGlobalLastAgentId();
const localAgentExists = localAgentId ? existing.has(localAgentId) : false;
const globalAgentExists = globalAgentId ? existing.has(globalAgentId) : false;
const mergedPinnedCount =
settingsManager.getMergedPinnedAgents(testProjectDir).length;
return resolveStartupTarget({
localAgentId,
localConversationId: options?.includeLocalConversation
? (localSession?.conversationId ?? null)
: null,
localAgentExists,
globalAgentId,
globalAgentExists,
mergedPinnedCount,
forceNew: options?.forceNew ?? false,
needsModelPicker: options?.needsModelPicker ?? false,
});
}
beforeEach(async () => {
await settingsManager.reset();
testHomeDir = await mkdtemp(join(tmpdir(), "letta-startup-home-"));
testProjectDir = await mkdtemp(join(tmpdir(), "letta-startup-project-"));
process.env.HOME = testHomeDir;
process.chdir(testProjectDir);
});
afterEach(async () => {
await settingsManager.reset();
process.chdir(originalCwd);
process.env.HOME = originalHome;
await rm(testHomeDir, { recursive: true, force: true });
await rm(testProjectDir, { recursive: true, force: true });
});
describe("startup resolution from settings files", () => {
test("no local/global settings files => create", async () => {
const target = await resolveFromSettings();
expect(target).toEqual({ action: "create" });
});
test("fresh dir + valid global session => resume global agent", async () => {
await writeGlobalSettings({
sessionsByServer: {
"api.letta.com": {
agentId: "agent-global",
conversationId: "conv-global",
},
},
});
const target = await resolveFromSettings({
existingAgentIds: ["agent-global"],
});
expect(target).toEqual({
action: "resume",
agentId: "agent-global",
});
});
test("local session + valid local agent => resume local agent", async () => {
await writeLocalSettings({
sessionsByServer: {
"api.letta.com": {
agentId: "agent-local",
conversationId: "conv-local",
},
},
});
const target = await resolveFromSettings({
existingAgentIds: ["agent-local"],
});
expect(target).toEqual({
action: "resume",
agentId: "agent-local",
});
});
test("headless parity mode: local session can carry local conversation", async () => {
await writeLocalSettings({
sessionsByServer: {
"api.letta.com": {
agentId: "agent-local",
conversationId: "conv-local",
},
},
});
const target = await resolveFromSettings({
existingAgentIds: ["agent-local"],
includeLocalConversation: true,
});
expect(target).toEqual({
action: "resume",
agentId: "agent-local",
conversationId: "conv-local",
});
});
test("invalid local + valid global => fallback resume global", async () => {
await writeLocalSettings({
sessionsByServer: {
"api.letta.com": {
agentId: "agent-local-missing",
conversationId: "conv-local",
},
},
});
await writeGlobalSettings({
sessionsByServer: {
"api.letta.com": {
agentId: "agent-global",
conversationId: "conv-global",
},
},
});
const target = await resolveFromSettings({
existingAgentIds: ["agent-global"],
});
expect(target).toEqual({
action: "resume",
agentId: "agent-global",
});
});
test("invalid local/global + global pinned => select", async () => {
await writeLocalSettings({
sessionsByServer: {
"api.letta.com": {
agentId: "agent-local-missing",
conversationId: "conv-local",
},
},
});
await writeGlobalSettings({
sessionsByServer: {
"api.letta.com": {
agentId: "agent-global-missing",
conversationId: "conv-global",
},
},
pinnedAgentsByServer: {
"api.letta.com": ["agent-pinned-global"],
},
});
const target = await resolveFromSettings();
expect(target).toEqual({ action: "select" });
});
test("invalid local/global + local pinned only => select", async () => {
await writeLocalSettings({
sessionsByServer: {
"api.letta.com": {
agentId: "agent-local-missing",
conversationId: "conv-local",
},
},
pinnedAgentsByServer: {
"api.letta.com": ["agent-pinned-local"],
},
});
const target = await resolveFromSettings();
expect(target).toEqual({ action: "select" });
});
test("no valid sessions + no pinned + needsModelPicker => select", async () => {
const target = await resolveFromSettings({ needsModelPicker: true });
expect(target).toEqual({ action: "select" });
});
test("forceNew always creates", async () => {
await writeLocalSettings({
sessionsByServer: {
"api.letta.com": {
agentId: "agent-local",
conversationId: "conv-local",
},
},
});
await writeGlobalSettings({
sessionsByServer: {
"api.letta.com": {
agentId: "agent-global",
conversationId: "conv-global",
},
},
});
const target = await resolveFromSettings({
existingAgentIds: ["agent-local", "agent-global"],
forceNew: true,
});
expect(target).toEqual({ action: "create" });
});
test("sessionsByServer takes precedence over legacy lastAgent (global)", async () => {
await writeGlobalSettings({
lastAgent: "agent-legacy-global",
sessionsByServer: {
"api.letta.com": {
agentId: "agent-session-global",
conversationId: "conv-session-global",
},
},
});
const target = await resolveFromSettings({
existingAgentIds: ["agent-session-global"],
});
expect(target).toEqual({
action: "resume",
agentId: "agent-session-global",
});
});
test("sessionsByServer takes precedence over legacy lastAgent (local)", async () => {
await writeLocalSettings({
lastAgent: "agent-legacy-local",
sessionsByServer: {
"api.letta.com": {
agentId: "agent-session-local",
conversationId: "conv-session-local",
},
},
});
const target = await resolveFromSettings({
existingAgentIds: ["agent-session-local"],
});
expect(target).toEqual({
action: "resume",
agentId: "agent-session-local",
});
});
});