feat: Make /init an interactive flow conducted by the primary agent [LET-7891] (#1356)
This commit is contained in:
218
src/cli/App.tsx
218
src/cli/App.tsx
@@ -224,11 +224,9 @@ import {
|
||||
import { formatCompact } from "./helpers/format";
|
||||
import { parsePatchOperations } from "./helpers/formatArgsDisplay";
|
||||
import {
|
||||
buildLegacyInitMessage,
|
||||
buildMemoryInitRuntimePrompt,
|
||||
buildInitMessage,
|
||||
fireAutoInit,
|
||||
gatherGitContext,
|
||||
hasActiveInitSubagent,
|
||||
} from "./helpers/initCommand";
|
||||
import {
|
||||
getReflectionSettings,
|
||||
@@ -1711,27 +1709,12 @@ export default function App({
|
||||
const [showExitStats, setShowExitStats] = useState(false);
|
||||
|
||||
const sharedReminderStateRef = useRef(createSharedReminderState());
|
||||
// Per-agent init progression — survives agent/conversation switches unlike SharedReminderState.
|
||||
const initProgressByAgentRef = useRef(
|
||||
new Map<string, { shallowCompleted: boolean; deepFired: boolean }>(),
|
||||
);
|
||||
const systemPromptRecompileByConversationRef = useRef(
|
||||
new Map<string, Promise<void>>(),
|
||||
);
|
||||
const queuedSystemPromptRecompileByConversationRef = useRef(
|
||||
new Set<string>(),
|
||||
);
|
||||
const updateInitProgress = (
|
||||
forAgentId: string,
|
||||
update: Partial<{ shallowCompleted: boolean; deepFired: boolean }>,
|
||||
) => {
|
||||
const progress = initProgressByAgentRef.current.get(forAgentId) ?? {
|
||||
shallowCompleted: false,
|
||||
deepFired: false,
|
||||
};
|
||||
Object.assign(progress, update);
|
||||
initProgressByAgentRef.current.set(forAgentId, progress);
|
||||
};
|
||||
|
||||
// Track if we've set the conversation summary for this new conversation
|
||||
// Initialized to true for resumed conversations (they already have context)
|
||||
@@ -9261,7 +9244,6 @@ export default function App({
|
||||
if (trimmed === "/init") {
|
||||
const cmd = commandRunner.start(msg, "Gathering project context...");
|
||||
|
||||
// Check for pending approvals before either path
|
||||
const approvalCheck = await checkPendingApprovalsForSlashCommand();
|
||||
if (approvalCheck.blocked) {
|
||||
cmd.fail(
|
||||
@@ -9270,112 +9252,38 @@ export default function App({
|
||||
return { submitted: false };
|
||||
}
|
||||
|
||||
const gitContext = gatherGitContext();
|
||||
// Interactive init: the primary agent conducts the flow,
|
||||
// asks the user questions, and runs the initializing-memory skill.
|
||||
autoInitPendingAgentIdsRef.current.delete(agentId);
|
||||
setCommandRunning(true);
|
||||
try {
|
||||
cmd.finish(
|
||||
"Building your memory palace... Start a new conversation with `letta --new` to work in parallel.",
|
||||
true,
|
||||
);
|
||||
|
||||
if (settingsManager.isMemfsEnabled(agentId)) {
|
||||
// MemFS path: background subagent
|
||||
if (hasActiveInitSubagent()) {
|
||||
cmd.fail(
|
||||
"Memory initialization is already running in the background.",
|
||||
);
|
||||
return { submitted: true };
|
||||
}
|
||||
const gitContext = gatherGitContext();
|
||||
const memoryDir = settingsManager.isMemfsEnabled(agentId)
|
||||
? getMemoryFilesystemRoot(agentId)
|
||||
: undefined;
|
||||
|
||||
try {
|
||||
const initPrompt = buildMemoryInitRuntimePrompt({
|
||||
agentId,
|
||||
workingDirectory: process.cwd(),
|
||||
memoryDir: getMemoryFilesystemRoot(agentId),
|
||||
gitContext,
|
||||
depth: "deep",
|
||||
});
|
||||
const initMessage = buildInitMessage({
|
||||
gitContext,
|
||||
memoryDir,
|
||||
});
|
||||
|
||||
const { spawnBackgroundSubagentTask } = await import(
|
||||
"../tools/impl/Task"
|
||||
);
|
||||
spawnBackgroundSubagentTask({
|
||||
subagentType: "init",
|
||||
prompt: initPrompt,
|
||||
description: "Initializing memory",
|
||||
silentCompletion: true,
|
||||
onComplete: async ({ success, error }) => {
|
||||
const msg = await handleMemorySubagentCompletion(
|
||||
{
|
||||
agentId,
|
||||
conversationId: conversationIdRef.current,
|
||||
subagentType: "init",
|
||||
initDepth: "deep",
|
||||
success,
|
||||
error,
|
||||
},
|
||||
{
|
||||
recompileByConversation:
|
||||
systemPromptRecompileByConversationRef.current,
|
||||
recompileQueuedByConversation:
|
||||
queuedSystemPromptRecompileByConversationRef.current,
|
||||
updateInitProgress,
|
||||
logRecompileFailure: (message) =>
|
||||
debugWarn("memory", message),
|
||||
},
|
||||
);
|
||||
appendTaskNotificationEvents([msg]);
|
||||
},
|
||||
});
|
||||
|
||||
// Clear pending auto-init only after spawn succeeded
|
||||
autoInitPendingAgentIdsRef.current.delete(agentId);
|
||||
|
||||
cmd.finish(
|
||||
"Learning about you and your codebase in the background. You'll be notified when ready.",
|
||||
true,
|
||||
);
|
||||
|
||||
// TODO: Remove this hack once commandRunner supports a
|
||||
// "silent" finish that skips the reminder callback.
|
||||
// Currently cmd.finish() always enqueues a command-IO
|
||||
// reminder, which leaks the /init context into the
|
||||
// primary agent's next turn and causes it to invoke the
|
||||
// initializing-memory skill itself.
|
||||
const reminders =
|
||||
sharedReminderStateRef.current.pendingCommandIoReminders;
|
||||
const idx = reminders.findIndex((r) => r.input === "/init");
|
||||
if (idx !== -1) {
|
||||
reminders.splice(idx, 1);
|
||||
}
|
||||
} catch (error) {
|
||||
const errorDetails = formatErrorDetails(error, agentId);
|
||||
cmd.fail(
|
||||
`Failed to start memory initialization: ${errorDetails}`,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// Legacy path: primary agent processConversation
|
||||
autoInitPendingAgentIdsRef.current.delete(agentId);
|
||||
setCommandRunning(true);
|
||||
try {
|
||||
cmd.finish(
|
||||
"Assimilating project context and defragmenting memories...",
|
||||
true,
|
||||
);
|
||||
|
||||
const initMessage = buildLegacyInitMessage({
|
||||
gitContext,
|
||||
memfsSection: "",
|
||||
});
|
||||
|
||||
await processConversation([
|
||||
{
|
||||
type: "message",
|
||||
role: "user",
|
||||
content: buildTextParts(initMessage),
|
||||
},
|
||||
]);
|
||||
} catch (error) {
|
||||
const errorDetails = formatErrorDetails(error, agentId);
|
||||
cmd.fail(`Failed: ${errorDetails}`);
|
||||
} finally {
|
||||
setCommandRunning(false);
|
||||
}
|
||||
await processConversation([
|
||||
{
|
||||
type: "message",
|
||||
role: "user",
|
||||
content: buildTextParts(initMessage),
|
||||
},
|
||||
]);
|
||||
} catch (error) {
|
||||
const errorDetails = formatErrorDetails(error, agentId);
|
||||
cmd.fail(`Failed: ${errorDetails}`);
|
||||
} finally {
|
||||
setCommandRunning(false);
|
||||
}
|
||||
return { submitted: true };
|
||||
}
|
||||
@@ -9493,7 +9401,6 @@ export default function App({
|
||||
agentId,
|
||||
conversationId: conversationIdRef.current,
|
||||
subagentType: "init",
|
||||
initDepth: "shallow",
|
||||
success,
|
||||
error,
|
||||
},
|
||||
@@ -9502,7 +9409,6 @@ export default function App({
|
||||
systemPromptRecompileByConversationRef.current,
|
||||
recompileQueuedByConversation:
|
||||
queuedSystemPromptRecompileByConversationRef.current,
|
||||
updateInitProgress,
|
||||
logRecompileFailure: (message) =>
|
||||
debugWarn("memory", message),
|
||||
},
|
||||
@@ -9632,7 +9538,6 @@ ${SYSTEM_REMINDER_CLOSE}
|
||||
systemPromptRecompileByConversationRef.current,
|
||||
recompileQueuedByConversation:
|
||||
queuedSystemPromptRecompileByConversationRef.current,
|
||||
updateInitProgress,
|
||||
logRecompileFailure: (message) =>
|
||||
debugWarn("memory", message),
|
||||
},
|
||||
@@ -9655,72 +9560,10 @@ ${SYSTEM_REMINDER_CLOSE}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
const maybeLaunchDeepInitSubagent = async () => {
|
||||
if (!memfsEnabledForAgent) return false;
|
||||
if (hasActiveInitSubagent()) return false;
|
||||
try {
|
||||
const gitContext = gatherGitContext();
|
||||
const initPrompt = buildMemoryInitRuntimePrompt({
|
||||
agentId,
|
||||
workingDirectory: process.cwd(),
|
||||
memoryDir: getMemoryFilesystemRoot(agentId),
|
||||
gitContext,
|
||||
depth: "deep",
|
||||
});
|
||||
const { spawnBackgroundSubagentTask } = await import(
|
||||
"../tools/impl/Task"
|
||||
);
|
||||
spawnBackgroundSubagentTask({
|
||||
subagentType: "init",
|
||||
prompt: initPrompt,
|
||||
description: "Deep memory initialization",
|
||||
silentCompletion: true,
|
||||
onComplete: async ({ success, error }) => {
|
||||
const msg = await handleMemorySubagentCompletion(
|
||||
{
|
||||
agentId,
|
||||
conversationId: conversationIdRef.current,
|
||||
subagentType: "init",
|
||||
initDepth: "deep",
|
||||
success,
|
||||
error,
|
||||
},
|
||||
{
|
||||
recompileByConversation:
|
||||
systemPromptRecompileByConversationRef.current,
|
||||
recompileQueuedByConversation:
|
||||
queuedSystemPromptRecompileByConversationRef.current,
|
||||
updateInitProgress,
|
||||
logRecompileFailure: (message) =>
|
||||
debugWarn("memory", message),
|
||||
},
|
||||
);
|
||||
appendTaskNotificationEvents([msg]);
|
||||
},
|
||||
});
|
||||
debugLog("memory", "Auto-launched deep init subagent");
|
||||
return true;
|
||||
} catch (error) {
|
||||
debugWarn(
|
||||
"memory",
|
||||
`Failed to auto-launch deep init subagent: ${
|
||||
error instanceof Error ? error.message : String(error)
|
||||
}`,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
syncReminderStateFromContextTracker(
|
||||
sharedReminderStateRef.current,
|
||||
contextTrackerRef.current,
|
||||
);
|
||||
// Hydrate init progression from the per-agent map into the shared state
|
||||
// so the deep-init provider sees the correct flags for the current agent.
|
||||
const initProgress = initProgressByAgentRef.current.get(agentId);
|
||||
sharedReminderStateRef.current.shallowInitCompleted =
|
||||
initProgress?.shallowCompleted ?? false;
|
||||
sharedReminderStateRef.current.deepInitFired =
|
||||
initProgress?.deepFired ?? false;
|
||||
const { getSkillSources } = await import("../agent/context");
|
||||
const { parts: sharedReminderParts } = await buildSharedReminderParts({
|
||||
mode: "interactive",
|
||||
@@ -9736,7 +9579,6 @@ ${SYSTEM_REMINDER_CLOSE}
|
||||
skillSources: getSkillSources(),
|
||||
resolvePlanModeReminder: getPlanModeReminder,
|
||||
maybeLaunchReflectionSubagent,
|
||||
maybeLaunchDeepInitSubagent,
|
||||
});
|
||||
for (const part of sharedReminderParts) {
|
||||
reminderParts.push(part);
|
||||
|
||||
Reference in New Issue
Block a user