Co-authored-by: Letta <noreply@letta.com>
This commit is contained in:
@@ -51,6 +51,7 @@ type LoadingState =
|
||||
| "importing"
|
||||
| "initializing"
|
||||
| "checking"
|
||||
| "selecting_global"
|
||||
| "ready";
|
||||
|
||||
/**
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
|
||||
73
src/index.ts
73
src/index.ts
@@ -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 ?? [],
|
||||
|
||||
Reference in New Issue
Block a user