feat: prompt to resume global pinned agent in fresh directories (#358) (#383)

Co-authored-by: Letta <noreply@letta.com>
This commit is contained in:
Charles Packer
2025-12-24 10:44:44 -08:00
committed by GitHub
parent 25950133b8
commit 50598233f0
3 changed files with 80 additions and 13 deletions

View File

@@ -51,6 +51,7 @@ type LoadingState =
| "importing"
| "initializing"
| "checking"
| "selecting_global"
| "ready";
/**

View File

@@ -53,21 +53,23 @@ function formatModel(agent: AgentState): string {
return agent.llm_config?.model || "unknown";
}
function getLabel(option: ProfileOption): string {
function getLabel(option: ProfileOption, freshRepoMode?: boolean): string {
const parts: string[] = [];
if (option.isLru) parts.push("last used");
if (option.isLocal) parts.push("pinned");
else if (!option.isLru) parts.push("global"); // Pinned globally but not locally
else if (!option.isLru && !freshRepoMode) parts.push("global"); // Pinned globally but not locally
return parts.length > 0 ? ` (${parts.join(", ")})` : "";
}
function ProfileSelectionUI({
lruAgentId,
externalLoading,
externalFreshRepoMode,
onComplete,
}: {
lruAgentId: string | null;
externalLoading?: boolean;
externalFreshRepoMode?: boolean;
onComplete: (result: ProfileSelectionResult) => void;
}) {
const [options, setOptions] = useState<ProfileOption[]>([]);
@@ -175,9 +177,11 @@ function ProfileSelectionUI({
});
const hasLocalDir = settingsManager.hasLocalLettaDir();
const contextMessage = hasLocalDir
? "Existing `.letta` folder detected."
: `${options.length} agent profile${options.length !== 1 ? "s" : ""} detected.`;
const contextMessage = externalFreshRepoMode
? `${options.length} pinned agent${options.length !== 1 ? "s" : ""} available.`
: hasLocalDir
? "Existing `.letta` folder detected."
: `${options.length} agent profile${options.length !== 1 ? "s" : ""} detected.`;
return (
<Box flexDirection="column">
@@ -202,7 +206,7 @@ function ProfileSelectionUI({
const isSelected = index === selectedIndex;
const displayName =
option.agent?.name || option.agentId.slice(0, 20);
const label = getLabel(option);
const label = getLabel(option, externalFreshRepoMode);
return (
<Box key={option.agentId} flexDirection="column">
@@ -290,12 +294,14 @@ function ProfileSelectionUI({
export function ProfileSelectionInline({
lruAgentId,
loading: externalLoading,
freshRepoMode,
onSelect,
onCreateNew,
onExit,
}: {
lruAgentId: string | null;
loading?: boolean;
freshRepoMode?: boolean;
onSelect: (agentId: string) => void;
onCreateNew: () => void;
onExit: () => void;
@@ -313,6 +319,7 @@ export function ProfileSelectionInline({
return React.createElement(ProfileSelectionUI, {
lruAgentId,
externalLoading,
externalFreshRepoMode: freshRepoMode,
onComplete: handleComplete,
});
}

View File

@@ -6,6 +6,7 @@ import { getClient } from "./agent/client";
import { initializeLoadedSkillsFlag, setAgentContext } from "./agent/context";
import type { AgentProvenance } from "./agent/create";
import { LETTA_CLOUD_API_URL } from "./auth/oauth";
import { ProfileSelectionInline } from "./cli/profile-selection";
import { permissionMode } from "./permissions/mode";
import { settingsManager } from "./settings-manager";
import { telemetry } from "./telemetry";
@@ -615,6 +616,7 @@ async function main(): Promise<void> {
}) {
const [loadingState, setLoadingState] = useState<
| "selecting"
| "selecting_global"
| "assembling"
| "upserting"
| "updating_tools"
@@ -629,16 +631,39 @@ async function main(): Promise<void> {
const [isResumingSession, setIsResumingSession] = useState(false);
const [agentProvenance, setAgentProvenance] =
useState<AgentProvenance | null>(null);
const [selectedGlobalAgentId, setSelectedGlobalAgentId] = useState<
string | null
>(null);
// Initialize on mount - no selector, just start immediately
// Initialize on mount - check if we should show global agent selector
useEffect(() => {
async function checkAndStart() {
// Load settings
await settingsManager.loadLocalProjectSettings();
const localSettings = settingsManager.getLocalProjectSettings();
const globalPinned = settingsManager.getGlobalPinnedAgents();
// Show selector if:
// 1. No lastAgent in this project (fresh directory)
// 2. No explicit flags that bypass selection (--new, --agent, --from-af, --continue)
// 3. Has global pinned agents available
const shouldShowSelector =
!localSettings.lastAgent &&
!forceNew &&
!agentIdArg &&
!fromAfFile &&
!continueSession &&
globalPinned.length > 0;
if (shouldShowSelector) {
setLoadingState("selecting_global");
return;
}
setLoadingState("assembling");
}
checkAndStart();
}, []);
}, [forceNew, agentIdArg, fromAfFile, continueSession]);
// Main initialization effect - runs after profile selection
useEffect(() => {
@@ -682,6 +707,16 @@ async function main(): Promise<void> {
// Agent no longer exists
}
}
// Priority 4: Use agent selected from global selector
if (!resumingAgentId && selectedGlobalAgentId) {
try {
await client.agents.retrieve(selectedGlobalAgentId);
resumingAgentId = selectedGlobalAgentId;
} catch {
// Agent doesn't exist, will create new
}
}
}
// Set resuming state early so loading messages are accurate
@@ -1057,16 +1092,40 @@ async function main(): Promise<void> {
systemPromptId,
fromAfFile,
loadingState,
selectedGlobalAgentId,
]);
// Profile selector is no longer shown at startup
// Users can access it via /pinned or /agents commands
// Don't render anything during initial "selecting" phase - wait for checkAndStart
if (loadingState === "selecting") {
return null;
}
// Show global agent selector in fresh repos with global pinned agents
if (loadingState === "selecting_global") {
return React.createElement(ProfileSelectionInline, {
lruAgentId: null, // No LRU in fresh repo
loading: false,
freshRepoMode: true, // Hides "(global)" labels and simplifies context message
onSelect: (agentId: string) => {
// Auto-pin the selected global agent to this project
settingsManager.pinLocal(agentId);
setSelectedGlobalAgentId(agentId);
setLoadingState("assembling");
},
onCreateNew: () => {
setLoadingState("assembling");
},
onExit: () => {
process.exit(0);
},
});
}
if (!agentId) {
return React.createElement(App, {
agentId: "loading",
loadingState:
loadingState === "selecting" ? "assembling" : loadingState,
loadingState,
continueSession: isResumingSession,
startupApproval: resumeData?.pendingApproval ?? null,
startupApprovals: resumeData?.pendingApprovals ?? [],
@@ -1079,7 +1138,7 @@ async function main(): Promise<void> {
return React.createElement(App, {
agentId,
agentState,
loadingState: loadingState === "selecting" ? "assembling" : loadingState,
loadingState,
continueSession: isResumingSession,
startupApproval: resumeData?.pendingApproval ?? null,
startupApprovals: resumeData?.pendingApprovals ?? [],