feat(cli): auto-init agent memory on first message [LET-7779] (#1231)
This commit is contained in:
@@ -220,6 +220,7 @@ import { parsePatchOperations } from "./helpers/formatArgsDisplay";
|
||||
import {
|
||||
buildLegacyInitMessage,
|
||||
buildMemoryInitRuntimePrompt,
|
||||
fireAutoInit,
|
||||
gatherGitContext,
|
||||
hasActiveInitSubagent,
|
||||
} from "./helpers/initCommand";
|
||||
@@ -1047,6 +1048,13 @@ export default function App({
|
||||
import("./helpers/conversationSwitchAlert").ConversationSwitchContext | null
|
||||
>(null);
|
||||
|
||||
// Pending auto-init for newly created agents — consumed on first user message.
|
||||
// A Set so multiple agents created before any message is sent are all tracked.
|
||||
const autoInitPendingAgentIdsRef = useRef<Set<string>>(new Set());
|
||||
// Tracks whether we've already consumed the startup agentProvenance.isNew flag,
|
||||
// so agent switches later in the session don't re-queue auto-init.
|
||||
const startupAutoInitConsumedRef = useRef(false);
|
||||
|
||||
// Track previous prop values to detect actual prop changes (not internal state changes)
|
||||
const prevInitialAgentIdRef = useRef(initialAgentId);
|
||||
const prevInitialAgentStateRef = useRef(initialAgentState);
|
||||
@@ -6228,6 +6236,11 @@ export default function App({
|
||||
);
|
||||
await enableMemfsIfCloud(agent.id);
|
||||
|
||||
// Queue auto-init for first message if memfs is enabled
|
||||
if (settingsManager.isMemfsEnabled(agent.id)) {
|
||||
autoInitPendingAgentIdsRef.current.add(agent.id);
|
||||
}
|
||||
|
||||
// Update project settings with new agent
|
||||
await updateProjectSettings({ lastAgent: agent.id });
|
||||
|
||||
@@ -6246,10 +6259,13 @@ export default function App({
|
||||
|
||||
// Build success message with hints
|
||||
const agentUrl = `https://app.letta.com/projects/default-project/agents/${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!";
|
||||
const successOutput = [
|
||||
`Created **${agent.name || agent.id}** (use /pin to save)`,
|
||||
`⎿ ${agentUrl}`,
|
||||
`⎿ Tip: use /init to initialize your agent's memory system!`,
|
||||
`⎿ ${memfsTip}`,
|
||||
].join("\n");
|
||||
cmd.finish(successOutput, true);
|
||||
const successItem: StaticItem = {
|
||||
@@ -9176,6 +9192,9 @@ export default function App({
|
||||
|
||||
// Special handling for /init command
|
||||
if (trimmed === "/init") {
|
||||
// Manual /init supersedes pending auto-init for this agent
|
||||
autoInitPendingAgentIdsRef.current.delete(agentId);
|
||||
|
||||
const cmd = commandRunner.start(msg, "Gathering project context...");
|
||||
|
||||
// Check for pending approvals before either path
|
||||
@@ -9217,7 +9236,7 @@ export default function App({
|
||||
onComplete: ({ success, error }) => {
|
||||
const msg = success
|
||||
? "Built a memory palace of you. Visit it with /palace."
|
||||
: `Memory initialization failed: ${error}`;
|
||||
: `Memory initialization failed: ${error || "Unknown error"}`;
|
||||
appendTaskNotificationEvents([msg]);
|
||||
},
|
||||
});
|
||||
@@ -9376,6 +9395,26 @@ export default function App({
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-init: fire background init on first message for newly created agents.
|
||||
// Only remove from the pending set after a confirmed launch so that a blocked
|
||||
// attempt (e.g. another /init subagent in flight) preserves the entry for retry.
|
||||
if (autoInitPendingAgentIdsRef.current.has(agentId) && !isSystemOnly) {
|
||||
try {
|
||||
const fired = await fireAutoInit(agentId, ({ success, error }) => {
|
||||
const msg = success
|
||||
? "Built a memory palace of you. Visit it with /palace."
|
||||
: `Memory initialization failed: ${error || "Unknown error"}`;
|
||||
appendTaskNotificationEvents([msg]);
|
||||
});
|
||||
if (fired) {
|
||||
autoInitPendingAgentIdsRef.current.delete(agentId);
|
||||
sharedReminderStateRef.current.pendingAutoInitReminder = true;
|
||||
}
|
||||
} catch {
|
||||
// Non-blocking: swallow failures so the user's message still goes through
|
||||
}
|
||||
}
|
||||
|
||||
// Build message content from display value (handles placeholders for text/images)
|
||||
const contentParts =
|
||||
overrideContentParts ?? buildMessageContentFromDisplay(msg);
|
||||
@@ -12499,6 +12538,26 @@ If using apply_patch, use this exact relative patch path: ${applyPatchRelativePa
|
||||
});
|
||||
}, [estimatedLiveHeight, terminalRows]);
|
||||
|
||||
// Queue auto-init for startup-created agents (--new-agent, --import, profile selector "new").
|
||||
// The consumed ref ensures this fires at most once per app lifetime, so later
|
||||
// agent switches (which change agentId but leave agentProvenance stale) don't
|
||||
// accidentally re-queue auto-init for an existing agent. This also means if
|
||||
// the user switches away from the startup agent and back, auto-init won't
|
||||
// re-queue — that's intentional (init is a one-shot at creation time).
|
||||
useEffect(() => {
|
||||
if (
|
||||
loadingState === "ready" &&
|
||||
agentProvenance?.isNew &&
|
||||
agentId &&
|
||||
!startupAutoInitConsumedRef.current
|
||||
) {
|
||||
startupAutoInitConsumedRef.current = true;
|
||||
if (settingsManager.isMemfsEnabled(agentId)) {
|
||||
autoInitPendingAgentIdsRef.current.add(agentId);
|
||||
}
|
||||
}
|
||||
}, [loadingState, agentProvenance, agentId]);
|
||||
|
||||
// Commit welcome snapshot once when ready for fresh sessions (no history)
|
||||
// Wait for agentProvenance to be available for new agents (continueSession=false)
|
||||
useEffect(() => {
|
||||
|
||||
@@ -6,7 +6,9 @@
|
||||
*/
|
||||
|
||||
import { execSync } from "node:child_process";
|
||||
import { getMemoryFilesystemRoot } from "../../agent/memoryFilesystem";
|
||||
import { SYSTEM_REMINDER_CLOSE, SYSTEM_REMINDER_OPEN } from "../../constants";
|
||||
import { settingsManager } from "../../settings-manager";
|
||||
import { getSnapshot as getSubagentSnapshot } from "./subagentState";
|
||||
|
||||
// ── Guard ──────────────────────────────────────────────────
|
||||
@@ -107,6 +109,37 @@ Instructions:
|
||||
`.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fire auto-init for a newly created agent.
|
||||
* Returns true if init was spawned, false if skipped (guard / memfs disabled).
|
||||
*/
|
||||
export async function fireAutoInit(
|
||||
agentId: string,
|
||||
onComplete: (result: { success: boolean; error?: string }) => void,
|
||||
): Promise<boolean> {
|
||||
if (hasActiveInitSubagent()) return false;
|
||||
if (!settingsManager.isMemfsEnabled(agentId)) return false;
|
||||
|
||||
const gitContext = gatherGitContext();
|
||||
const initPrompt = buildMemoryInitRuntimePrompt({
|
||||
agentId,
|
||||
workingDirectory: process.cwd(),
|
||||
memoryDir: getMemoryFilesystemRoot(agentId),
|
||||
gitContext,
|
||||
});
|
||||
|
||||
const { spawnBackgroundSubagentTask } = await import("../../tools/impl/Task");
|
||||
spawnBackgroundSubagentTask({
|
||||
subagentType: "init",
|
||||
prompt: initPrompt,
|
||||
description: "Initializing memory",
|
||||
silentCompletion: true,
|
||||
onComplete,
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Message for the primary agent via processConversation (legacy non-MemFS path). */
|
||||
export function buildLegacyInitMessage(args: {
|
||||
gitContext: string;
|
||||
|
||||
Reference in New Issue
Block a user