feat: Make /init an interactive flow conducted by the primary agent [LET-7891] (#1356)
This commit is contained in:
180
src/cli/App.tsx
180
src/cli/App.tsx
@@ -224,11 +224,9 @@ import {
|
|||||||
import { formatCompact } from "./helpers/format";
|
import { formatCompact } from "./helpers/format";
|
||||||
import { parsePatchOperations } from "./helpers/formatArgsDisplay";
|
import { parsePatchOperations } from "./helpers/formatArgsDisplay";
|
||||||
import {
|
import {
|
||||||
buildLegacyInitMessage,
|
buildInitMessage,
|
||||||
buildMemoryInitRuntimePrompt,
|
|
||||||
fireAutoInit,
|
fireAutoInit,
|
||||||
gatherGitContext,
|
gatherGitContext,
|
||||||
hasActiveInitSubagent,
|
|
||||||
} from "./helpers/initCommand";
|
} from "./helpers/initCommand";
|
||||||
import {
|
import {
|
||||||
getReflectionSettings,
|
getReflectionSettings,
|
||||||
@@ -1711,27 +1709,12 @@ export default function App({
|
|||||||
const [showExitStats, setShowExitStats] = useState(false);
|
const [showExitStats, setShowExitStats] = useState(false);
|
||||||
|
|
||||||
const sharedReminderStateRef = useRef(createSharedReminderState());
|
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(
|
const systemPromptRecompileByConversationRef = useRef(
|
||||||
new Map<string, Promise<void>>(),
|
new Map<string, Promise<void>>(),
|
||||||
);
|
);
|
||||||
const queuedSystemPromptRecompileByConversationRef = useRef(
|
const queuedSystemPromptRecompileByConversationRef = useRef(
|
||||||
new Set<string>(),
|
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
|
// Track if we've set the conversation summary for this new conversation
|
||||||
// Initialized to true for resumed conversations (they already have context)
|
// Initialized to true for resumed conversations (they already have context)
|
||||||
@@ -9261,7 +9244,6 @@ export default function App({
|
|||||||
if (trimmed === "/init") {
|
if (trimmed === "/init") {
|
||||||
const cmd = commandRunner.start(msg, "Gathering project context...");
|
const cmd = commandRunner.start(msg, "Gathering project context...");
|
||||||
|
|
||||||
// Check for pending approvals before either path
|
|
||||||
const approvalCheck = await checkPendingApprovalsForSlashCommand();
|
const approvalCheck = await checkPendingApprovalsForSlashCommand();
|
||||||
if (approvalCheck.blocked) {
|
if (approvalCheck.blocked) {
|
||||||
cmd.fail(
|
cmd.fail(
|
||||||
@@ -9270,97 +9252,24 @@ export default function App({
|
|||||||
return { submitted: false };
|
return { submitted: false };
|
||||||
}
|
}
|
||||||
|
|
||||||
const gitContext = gatherGitContext();
|
// Interactive init: the primary agent conducts the flow,
|
||||||
|
// asks the user questions, and runs the initializing-memory skill.
|
||||||
if (settingsManager.isMemfsEnabled(agentId)) {
|
|
||||||
// MemFS path: background subagent
|
|
||||||
if (hasActiveInitSubagent()) {
|
|
||||||
cmd.fail(
|
|
||||||
"Memory initialization is already running in the background.",
|
|
||||||
);
|
|
||||||
return { submitted: true };
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
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: "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);
|
autoInitPendingAgentIdsRef.current.delete(agentId);
|
||||||
setCommandRunning(true);
|
setCommandRunning(true);
|
||||||
try {
|
try {
|
||||||
cmd.finish(
|
cmd.finish(
|
||||||
"Assimilating project context and defragmenting memories...",
|
"Building your memory palace... Start a new conversation with `letta --new` to work in parallel.",
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
|
|
||||||
const initMessage = buildLegacyInitMessage({
|
const gitContext = gatherGitContext();
|
||||||
|
const memoryDir = settingsManager.isMemfsEnabled(agentId)
|
||||||
|
? getMemoryFilesystemRoot(agentId)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
const initMessage = buildInitMessage({
|
||||||
gitContext,
|
gitContext,
|
||||||
memfsSection: "",
|
memoryDir,
|
||||||
});
|
});
|
||||||
|
|
||||||
await processConversation([
|
await processConversation([
|
||||||
@@ -9376,7 +9285,6 @@ export default function App({
|
|||||||
} finally {
|
} finally {
|
||||||
setCommandRunning(false);
|
setCommandRunning(false);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return { submitted: true };
|
return { submitted: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -9493,7 +9401,6 @@ export default function App({
|
|||||||
agentId,
|
agentId,
|
||||||
conversationId: conversationIdRef.current,
|
conversationId: conversationIdRef.current,
|
||||||
subagentType: "init",
|
subagentType: "init",
|
||||||
initDepth: "shallow",
|
|
||||||
success,
|
success,
|
||||||
error,
|
error,
|
||||||
},
|
},
|
||||||
@@ -9502,7 +9409,6 @@ export default function App({
|
|||||||
systemPromptRecompileByConversationRef.current,
|
systemPromptRecompileByConversationRef.current,
|
||||||
recompileQueuedByConversation:
|
recompileQueuedByConversation:
|
||||||
queuedSystemPromptRecompileByConversationRef.current,
|
queuedSystemPromptRecompileByConversationRef.current,
|
||||||
updateInitProgress,
|
|
||||||
logRecompileFailure: (message) =>
|
logRecompileFailure: (message) =>
|
||||||
debugWarn("memory", message),
|
debugWarn("memory", message),
|
||||||
},
|
},
|
||||||
@@ -9632,7 +9538,6 @@ ${SYSTEM_REMINDER_CLOSE}
|
|||||||
systemPromptRecompileByConversationRef.current,
|
systemPromptRecompileByConversationRef.current,
|
||||||
recompileQueuedByConversation:
|
recompileQueuedByConversation:
|
||||||
queuedSystemPromptRecompileByConversationRef.current,
|
queuedSystemPromptRecompileByConversationRef.current,
|
||||||
updateInitProgress,
|
|
||||||
logRecompileFailure: (message) =>
|
logRecompileFailure: (message) =>
|
||||||
debugWarn("memory", message),
|
debugWarn("memory", message),
|
||||||
},
|
},
|
||||||
@@ -9655,72 +9560,10 @@ ${SYSTEM_REMINDER_CLOSE}
|
|||||||
return false;
|
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(
|
syncReminderStateFromContextTracker(
|
||||||
sharedReminderStateRef.current,
|
sharedReminderStateRef.current,
|
||||||
contextTrackerRef.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 { getSkillSources } = await import("../agent/context");
|
||||||
const { parts: sharedReminderParts } = await buildSharedReminderParts({
|
const { parts: sharedReminderParts } = await buildSharedReminderParts({
|
||||||
mode: "interactive",
|
mode: "interactive",
|
||||||
@@ -9736,7 +9579,6 @@ ${SYSTEM_REMINDER_CLOSE}
|
|||||||
skillSources: getSkillSources(),
|
skillSources: getSkillSources(),
|
||||||
resolvePlanModeReminder: getPlanModeReminder,
|
resolvePlanModeReminder: getPlanModeReminder,
|
||||||
maybeLaunchReflectionSubagent,
|
maybeLaunchReflectionSubagent,
|
||||||
maybeLaunchDeepInitSubagent,
|
|
||||||
});
|
});
|
||||||
for (const part of sharedReminderParts) {
|
for (const part of sharedReminderParts) {
|
||||||
reminderParts.push(part);
|
reminderParts.push(part);
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ ${recentCommits}
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Depth instructions ────────────────────────────────────
|
// ── Shallow init (background subagent) ───────────────────
|
||||||
|
|
||||||
const SHALLOW_INSTRUCTIONS = `
|
const SHALLOW_INSTRUCTIONS = `
|
||||||
Shallow init — fast project basics only (~5 tool calls max):
|
Shallow init — fast project basics only (~5 tool calls max):
|
||||||
@@ -87,25 +87,13 @@ Shallow init — fast project basics only (~5 tool calls max):
|
|||||||
- Skip: deep directory exploration, architecture mapping, config analysis, historical sessions, persona files, reflection/checkpoint phase
|
- Skip: deep directory exploration, architecture mapping, config analysis, historical sessions, persona files, reflection/checkpoint phase
|
||||||
`.trim();
|
`.trim();
|
||||||
|
|
||||||
const DEEP_INSTRUCTIONS = `
|
/** Prompt for the background shallow-init subagent. */
|
||||||
Deep init — full exploration (follow the initializing-memory skill fully):
|
export function buildShallowInitPrompt(args: {
|
||||||
- Read all existing memory files first — do NOT recreate what already exists
|
|
||||||
- Then follow the full initializing-memory skill as your operating guide
|
|
||||||
- Expand and deepen existing shallow files, add new ones to reach 15-25 target
|
|
||||||
- If shallow init already ran, build on its output rather than starting over
|
|
||||||
`.trim();
|
|
||||||
|
|
||||||
// ── Prompt builders ────────────────────────────────────────
|
|
||||||
|
|
||||||
/** Prompt for the background init subagent (MemFS path). */
|
|
||||||
export function buildMemoryInitRuntimePrompt(args: {
|
|
||||||
agentId: string;
|
agentId: string;
|
||||||
workingDirectory: string;
|
workingDirectory: string;
|
||||||
memoryDir: string;
|
memoryDir: string;
|
||||||
gitContext: string;
|
gitContext: string;
|
||||||
depth?: "shallow" | "deep";
|
|
||||||
}): string {
|
}): string {
|
||||||
const depth = args.depth ?? "deep";
|
|
||||||
return `
|
return `
|
||||||
The user ran /init for the current project.
|
The user ran /init for the current project.
|
||||||
|
|
||||||
@@ -113,7 +101,7 @@ Runtime context:
|
|||||||
- parent_agent_id: ${args.agentId}
|
- parent_agent_id: ${args.agentId}
|
||||||
- working_directory: ${args.workingDirectory}
|
- working_directory: ${args.workingDirectory}
|
||||||
- memory_dir: ${args.memoryDir}
|
- memory_dir: ${args.memoryDir}
|
||||||
- research_depth: ${depth}
|
- research_depth: shallow
|
||||||
|
|
||||||
Git/project context:
|
Git/project context:
|
||||||
${args.gitContext}
|
${args.gitContext}
|
||||||
@@ -121,7 +109,7 @@ ${args.gitContext}
|
|||||||
Task:
|
Task:
|
||||||
Initialize or reorganize the parent agent's filesystem-backed memory for this project.
|
Initialize or reorganize the parent agent's filesystem-backed memory for this project.
|
||||||
|
|
||||||
${depth === "shallow" ? SHALLOW_INSTRUCTIONS : DEEP_INSTRUCTIONS}
|
${SHALLOW_INSTRUCTIONS}
|
||||||
|
|
||||||
Instructions:
|
Instructions:
|
||||||
- Use the pre-loaded initializing-memory skill as your operating guide
|
- Use the pre-loaded initializing-memory skill as your operating guide
|
||||||
@@ -148,12 +136,11 @@ export async function fireAutoInit(
|
|||||||
if (!settingsManager.isMemfsEnabled(agentId)) return false;
|
if (!settingsManager.isMemfsEnabled(agentId)) return false;
|
||||||
|
|
||||||
const gitContext = gatherGitContext();
|
const gitContext = gatherGitContext();
|
||||||
const initPrompt = buildMemoryInitRuntimePrompt({
|
const initPrompt = buildShallowInitPrompt({
|
||||||
agentId,
|
agentId,
|
||||||
workingDirectory: process.cwd(),
|
workingDirectory: process.cwd(),
|
||||||
memoryDir: getMemoryFilesystemRoot(agentId),
|
memoryDir: getMemoryFilesystemRoot(agentId),
|
||||||
gitContext,
|
gitContext,
|
||||||
depth: "shallow",
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const { spawnBackgroundSubagentTask } = await import("../../tools/impl/Task");
|
const { spawnBackgroundSubagentTask } = await import("../../tools/impl/Task");
|
||||||
@@ -168,14 +155,20 @@ export async function fireAutoInit(
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Message for the primary agent via processConversation (legacy non-MemFS path). */
|
// ── Interactive init (primary agent) ─────────────────────
|
||||||
export function buildLegacyInitMessage(args: {
|
|
||||||
|
/** Message for the primary agent via processConversation when user runs /init. */
|
||||||
|
export function buildInitMessage(args: {
|
||||||
gitContext: string;
|
gitContext: string;
|
||||||
memfsSection: string;
|
memoryDir?: string;
|
||||||
}): string {
|
}): string {
|
||||||
|
const memfsSection = args.memoryDir
|
||||||
|
? `\n## Memory filesystem\n\nMemory filesystem is enabled. Memory directory: \`${args.memoryDir}\`\n`
|
||||||
|
: "";
|
||||||
|
|
||||||
return `${SYSTEM_REMINDER_OPEN}
|
return `${SYSTEM_REMINDER_OPEN}
|
||||||
The user has requested memory initialization via /init.
|
The user has requested memory initialization via /init.
|
||||||
${args.memfsSection}
|
${memfsSection}
|
||||||
## 1. Invoke the initializing-memory skill
|
## 1. Invoke the initializing-memory skill
|
||||||
|
|
||||||
Use the \`Skill\` tool with \`skill: "initializing-memory"\` to load the comprehensive instructions for memory initialization.
|
Use the \`Skill\` tool with \`skill: "initializing-memory"\` to load the comprehensive instructions for memory initialization.
|
||||||
|
|||||||
@@ -4,71 +4,41 @@ import {
|
|||||||
} from "../../agent/modify";
|
} from "../../agent/modify";
|
||||||
|
|
||||||
export type MemorySubagentType = "init" | "reflection";
|
export type MemorySubagentType = "init" | "reflection";
|
||||||
export type MemoryInitDepth = "shallow" | "deep";
|
|
||||||
|
|
||||||
export interface MemoryInitProgressUpdate {
|
|
||||||
shallowCompleted: boolean;
|
|
||||||
deepFired: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
type RecompileAgentSystemPromptFn = (
|
type RecompileAgentSystemPromptFn = (
|
||||||
conversationId: string,
|
conversationId: string,
|
||||||
options?: RecompileAgentSystemPromptOptions,
|
options?: RecompileAgentSystemPromptOptions,
|
||||||
) => Promise<string>;
|
) => Promise<string>;
|
||||||
|
|
||||||
export type MemorySubagentCompletionArgs =
|
export interface MemorySubagentCompletionArgs {
|
||||||
| {
|
|
||||||
agentId: string;
|
agentId: string;
|
||||||
conversationId: string;
|
conversationId: string;
|
||||||
subagentType: "init";
|
subagentType: MemorySubagentType;
|
||||||
initDepth: MemoryInitDepth;
|
|
||||||
success: boolean;
|
success: boolean;
|
||||||
error?: string;
|
error?: string;
|
||||||
}
|
}
|
||||||
| {
|
|
||||||
agentId: string;
|
|
||||||
conversationId: string;
|
|
||||||
subagentType: "reflection";
|
|
||||||
initDepth?: never;
|
|
||||||
success: boolean;
|
|
||||||
error?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface MemorySubagentCompletionDeps {
|
export interface MemorySubagentCompletionDeps {
|
||||||
recompileByConversation: Map<string, Promise<void>>;
|
recompileByConversation: Map<string, Promise<void>>;
|
||||||
recompileQueuedByConversation: Set<string>;
|
recompileQueuedByConversation: Set<string>;
|
||||||
updateInitProgress: (
|
|
||||||
agentId: string,
|
|
||||||
update: Partial<MemoryInitProgressUpdate>,
|
|
||||||
) => void;
|
|
||||||
logRecompileFailure?: (message: string) => void;
|
logRecompileFailure?: (message: string) => void;
|
||||||
recompileAgentSystemPromptImpl?: RecompileAgentSystemPromptFn;
|
recompileAgentSystemPromptImpl?: RecompileAgentSystemPromptFn;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Finalize a memory-writing subagent by updating init progress, recompiling the
|
* Finalize a memory-writing subagent by recompiling the parent agent's
|
||||||
* parent agent's system prompt, and returning the user-facing completion text.
|
* system prompt and returning the user-facing completion text.
|
||||||
*/
|
*/
|
||||||
export async function handleMemorySubagentCompletion(
|
export async function handleMemorySubagentCompletion(
|
||||||
args: MemorySubagentCompletionArgs,
|
args: MemorySubagentCompletionArgs,
|
||||||
deps: MemorySubagentCompletionDeps,
|
deps: MemorySubagentCompletionDeps,
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
const { agentId, conversationId, subagentType, initDepth, success, error } =
|
const { agentId, conversationId, subagentType, success, error } = args;
|
||||||
args;
|
|
||||||
const recompileAgentSystemPromptFn =
|
const recompileAgentSystemPromptFn =
|
||||||
deps.recompileAgentSystemPromptImpl ?? recompileAgentSystemPrompt;
|
deps.recompileAgentSystemPromptImpl ?? recompileAgentSystemPrompt;
|
||||||
let recompileError: string | null = null;
|
let recompileError: string | null = null;
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
if (subagentType === "init") {
|
|
||||||
deps.updateInitProgress(
|
|
||||||
agentId,
|
|
||||||
initDepth === "shallow"
|
|
||||||
? { shallowCompleted: true }
|
|
||||||
: { deepFired: true },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let inFlight = deps.recompileByConversation.get(conversationId);
|
let inFlight = deps.recompileByConversation.get(conversationId);
|
||||||
|
|
||||||
@@ -106,9 +76,7 @@ export async function handleMemorySubagentCompletion(
|
|||||||
if (subagentType === "reflection") {
|
if (subagentType === "reflection") {
|
||||||
return `Tried to reflect, but got lost in the palace: ${normalizedError}`;
|
return `Tried to reflect, but got lost in the palace: ${normalizedError}`;
|
||||||
}
|
}
|
||||||
return initDepth === "deep"
|
return `Memory initialization failed: ${normalizedError}`;
|
||||||
? `Deep memory initialization failed: ${normalizedError}`
|
|
||||||
: `Memory initialization failed: ${normalizedError}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const baseMessage =
|
const baseMessage =
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ export type SharedReminderId =
|
|||||||
| "plan-mode"
|
| "plan-mode"
|
||||||
| "reflection-step-count"
|
| "reflection-step-count"
|
||||||
| "reflection-compaction"
|
| "reflection-compaction"
|
||||||
| "deep-init"
|
|
||||||
| "command-io"
|
| "command-io"
|
||||||
| "toolset-change"
|
| "toolset-change"
|
||||||
| "auto-init";
|
| "auto-init";
|
||||||
@@ -71,12 +70,6 @@ export const SHARED_REMINDER_CATALOG: ReadonlyArray<SharedReminderDefinition> =
|
|||||||
"Compaction-triggered reflection reminder/auto-launch behavior",
|
"Compaction-triggered reflection reminder/auto-launch behavior",
|
||||||
modes: ["interactive", "headless-one-shot", "headless-bidirectional"],
|
modes: ["interactive", "headless-one-shot", "headless-bidirectional"],
|
||||||
},
|
},
|
||||||
{
|
|
||||||
id: "deep-init",
|
|
||||||
description:
|
|
||||||
"Auto-launch deep memory init after shallow init + turn gate",
|
|
||||||
modes: ["interactive"],
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
id: "command-io",
|
id: "command-io",
|
||||||
description: "Recent slash command input/output context",
|
description: "Recent slash command input/output context",
|
||||||
|
|||||||
@@ -39,7 +39,6 @@ export interface SharedReminderContext {
|
|||||||
maybeLaunchReflectionSubagent?: (
|
maybeLaunchReflectionSubagent?: (
|
||||||
triggerSource: ReflectionTriggerSource,
|
triggerSource: ReflectionTriggerSource,
|
||||||
) => Promise<boolean>;
|
) => Promise<boolean>;
|
||||||
maybeLaunchDeepInitSubagent?: () => Promise<boolean>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ReminderTextPart = { type: "text"; text: string };
|
export type ReminderTextPart = { type: "text"; text: string };
|
||||||
@@ -213,29 +212,6 @@ async function buildAutoInitReminder(
|
|||||||
return AUTO_INIT_REMINDER;
|
return AUTO_INIT_REMINDER;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disabled: deep init at turn 8 + reflection at turn 10 is too chaotic.
|
|
||||||
// Re-enable once both subagent prompts are tuned to coexist.
|
|
||||||
const DEEP_INIT_AUTO_LAUNCH_ENABLED = false;
|
|
||||||
|
|
||||||
async function maybeLaunchDeepInit(
|
|
||||||
context: SharedReminderContext,
|
|
||||||
): Promise<string | null> {
|
|
||||||
if (!DEEP_INIT_AUTO_LAUNCH_ENABLED) return null;
|
|
||||||
if (!context.state.shallowInitCompleted) return null;
|
|
||||||
if (context.state.deepInitFired) return null;
|
|
||||||
if (context.state.turnCount < 8) return null;
|
|
||||||
|
|
||||||
const memfsEnabled = settingsManager.isMemfsEnabled(context.agent.id);
|
|
||||||
if (!memfsEnabled) return null;
|
|
||||||
|
|
||||||
if (context.maybeLaunchDeepInitSubagent) {
|
|
||||||
// Don't latch deepInitFired here — it's set in the onComplete callback
|
|
||||||
// only on success, so a failed deep init allows automatic retry.
|
|
||||||
await context.maybeLaunchDeepInitSubagent();
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const MAX_COMMAND_REMINDERS_PER_TURN = 10;
|
const MAX_COMMAND_REMINDERS_PER_TURN = 10;
|
||||||
const MAX_TOOLSET_REMINDERS_PER_TURN = 5;
|
const MAX_TOOLSET_REMINDERS_PER_TURN = 5;
|
||||||
const MAX_COMMAND_INPUT_CHARS = 2000;
|
const MAX_COMMAND_INPUT_CHARS = 2000;
|
||||||
@@ -349,7 +325,6 @@ export const sharedReminderProviders: Record<
|
|||||||
"plan-mode": buildPlanModeReminder,
|
"plan-mode": buildPlanModeReminder,
|
||||||
"reflection-step-count": buildReflectionStepReminder,
|
"reflection-step-count": buildReflectionStepReminder,
|
||||||
"reflection-compaction": buildReflectionCompactionReminder,
|
"reflection-compaction": buildReflectionCompactionReminder,
|
||||||
"deep-init": maybeLaunchDeepInit,
|
|
||||||
"command-io": buildCommandIoReminder,
|
"command-io": buildCommandIoReminder,
|
||||||
"toolset-change": buildToolsetChangeReminder,
|
"toolset-change": buildToolsetChangeReminder,
|
||||||
"auto-init": buildAutoInitReminder,
|
"auto-init": buildAutoInitReminder,
|
||||||
|
|||||||
@@ -28,8 +28,6 @@ export interface SharedReminderState {
|
|||||||
pendingAutoInitReminder: boolean;
|
pendingAutoInitReminder: boolean;
|
||||||
pendingCommandIoReminders: CommandIoReminder[];
|
pendingCommandIoReminders: CommandIoReminder[];
|
||||||
pendingToolsetChangeReminders: ToolsetChangeReminder[];
|
pendingToolsetChangeReminders: ToolsetChangeReminder[];
|
||||||
shallowInitCompleted: boolean;
|
|
||||||
deepInitFired: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createSharedReminderState(): SharedReminderState {
|
export function createSharedReminderState(): SharedReminderState {
|
||||||
@@ -42,8 +40,6 @@ export function createSharedReminderState(): SharedReminderState {
|
|||||||
pendingAutoInitReminder: false,
|
pendingAutoInitReminder: false,
|
||||||
pendingCommandIoReminders: [],
|
pendingCommandIoReminders: [],
|
||||||
pendingToolsetChangeReminders: [],
|
pendingToolsetChangeReminders: [],
|
||||||
shallowInitCompleted: false,
|
|
||||||
deepInitFired: false,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -95,23 +95,22 @@ describe("auto-init lifecycle guards", () => {
|
|||||||
expect(setDelete).toBeGreaterThan(firedCheck);
|
expect(setDelete).toBeGreaterThan(firedCheck);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("manual /init clears pending auto-init for current agent after spawn", () => {
|
test("manual /init clears pending auto-init for current agent", () => {
|
||||||
const appSource = readSource("../../cli/App.tsx");
|
const appSource = readSource("../../cli/App.tsx");
|
||||||
|
|
||||||
// The /init handler must delete the current agent from the pending set,
|
// The /init handler must delete the current agent from the pending set
|
||||||
// but only after the background subagent has been spawned (inside the try).
|
// before the interactive processConversation call.
|
||||||
const initHandlerIdx = appSource.indexOf('trimmed === "/init"');
|
const initHandlerIdx = appSource.indexOf('trimmed === "/init"');
|
||||||
expect(initHandlerIdx).toBeGreaterThan(-1);
|
expect(initHandlerIdx).toBeGreaterThan(-1);
|
||||||
|
|
||||||
// Search from the /init handler to the end of the block
|
|
||||||
const afterInit = appSource.slice(initHandlerIdx);
|
const afterInit = appSource.slice(initHandlerIdx);
|
||||||
const spawnIdx = afterInit.indexOf("spawnBackgroundSubagentTask({");
|
|
||||||
const deleteIdx = afterInit.indexOf(
|
const deleteIdx = afterInit.indexOf(
|
||||||
"autoInitPendingAgentIdsRef.current.delete(agentId)",
|
"autoInitPendingAgentIdsRef.current.delete(agentId)",
|
||||||
);
|
);
|
||||||
expect(spawnIdx).toBeGreaterThan(-1);
|
const processIdx = afterInit.indexOf("processConversation(");
|
||||||
expect(deleteIdx).toBeGreaterThan(-1);
|
expect(deleteIdx).toBeGreaterThan(-1);
|
||||||
expect(deleteIdx).toBeGreaterThan(spawnIdx);
|
expect(processIdx).toBeGreaterThan(-1);
|
||||||
|
expect(deleteIdx).toBeLessThan(processIdx);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("fireAutoInit returns false (not throw) when init subagent is active", () => {
|
test("fireAutoInit returns false (not throw) when init subagent is active", () => {
|
||||||
|
|||||||
@@ -1,46 +1,38 @@
|
|||||||
import { describe, expect, test } from "bun:test";
|
import { describe, expect, test } from "bun:test";
|
||||||
import { readFileSync } from "node:fs";
|
import { readFileSync } from "node:fs";
|
||||||
import { fileURLToPath } from "node:url";
|
import { fileURLToPath } from "node:url";
|
||||||
import { buildMemoryInitRuntimePrompt } from "../../cli/helpers/initCommand";
|
import {
|
||||||
|
buildInitMessage,
|
||||||
|
buildShallowInitPrompt,
|
||||||
|
} from "../../cli/helpers/initCommand";
|
||||||
|
|
||||||
describe("init background subagent wiring", () => {
|
describe("init wiring", () => {
|
||||||
const readSource = (relativePath: string) =>
|
const readSource = (relativePath: string) =>
|
||||||
readFileSync(
|
readFileSync(
|
||||||
fileURLToPath(new URL(relativePath, import.meta.url)),
|
fileURLToPath(new URL(relativePath, import.meta.url)),
|
||||||
"utf-8",
|
"utf-8",
|
||||||
);
|
);
|
||||||
|
|
||||||
test("App.tsx checks pending approvals before either branch", () => {
|
test("App.tsx checks pending approvals before /init runs", () => {
|
||||||
const appSource = readSource("../../cli/App.tsx");
|
const appSource = readSource("../../cli/App.tsx");
|
||||||
|
|
||||||
// The approval check must appear before the MemFS branch
|
|
||||||
const approvalIdx = appSource.indexOf(
|
const approvalIdx = appSource.indexOf(
|
||||||
"checkPendingApprovalsForSlashCommand",
|
"checkPendingApprovalsForSlashCommand",
|
||||||
appSource.indexOf('trimmed === "/init"'),
|
appSource.indexOf('trimmed === "/init"'),
|
||||||
);
|
);
|
||||||
const memfsBranchIdx = appSource.indexOf(
|
const initMessageIdx = appSource.indexOf(
|
||||||
"isMemfsEnabled",
|
"buildInitMessage",
|
||||||
appSource.indexOf('trimmed === "/init"'),
|
appSource.indexOf('trimmed === "/init"'),
|
||||||
);
|
);
|
||||||
expect(approvalIdx).toBeGreaterThan(-1);
|
expect(approvalIdx).toBeGreaterThan(-1);
|
||||||
expect(memfsBranchIdx).toBeGreaterThan(-1);
|
expect(initMessageIdx).toBeGreaterThan(-1);
|
||||||
expect(approvalIdx).toBeLessThan(memfsBranchIdx);
|
expect(approvalIdx).toBeLessThan(initMessageIdx);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("App.tsx branches on MemFS: background subagent vs legacy processConversation", () => {
|
test("App.tsx uses processConversation for /init", () => {
|
||||||
const appSource = readSource("../../cli/App.tsx");
|
const appSource = readSource("../../cli/App.tsx");
|
||||||
|
|
||||||
// MemFS path — background subagent
|
expect(appSource).toContain("buildInitMessage({");
|
||||||
expect(appSource).toContain("hasActiveInitSubagent()");
|
|
||||||
expect(appSource).toContain("buildMemoryInitRuntimePrompt({");
|
|
||||||
expect(appSource).toContain("spawnBackgroundSubagentTask({");
|
|
||||||
expect(appSource).toContain('subagentType: "init"');
|
|
||||||
expect(appSource).toContain("silentCompletion: true");
|
|
||||||
expect(appSource).toContain("appendTaskNotificationEvents(");
|
|
||||||
expect(appSource).toContain("Learning about you and your codebase");
|
|
||||||
|
|
||||||
// Legacy non-MemFS path — primary agent
|
|
||||||
expect(appSource).toContain("buildLegacyInitMessage({");
|
|
||||||
expect(appSource).toContain("processConversation(");
|
expect(appSource).toContain("processConversation(");
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -49,10 +41,8 @@ describe("init background subagent wiring", () => {
|
|||||||
|
|
||||||
expect(helperSource).toContain("export function hasActiveInitSubagent(");
|
expect(helperSource).toContain("export function hasActiveInitSubagent(");
|
||||||
expect(helperSource).toContain("export function gatherGitContext()");
|
expect(helperSource).toContain("export function gatherGitContext()");
|
||||||
expect(helperSource).toContain(
|
expect(helperSource).toContain("export function buildShallowInitPrompt(");
|
||||||
"export function buildMemoryInitRuntimePrompt(",
|
expect(helperSource).toContain("export function buildInitMessage(");
|
||||||
);
|
|
||||||
expect(helperSource).toContain("export function buildLegacyInitMessage(");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("init.md exists as a builtin subagent", () => {
|
test("init.md exists as a builtin subagent", () => {
|
||||||
@@ -79,36 +69,28 @@ describe("init background subagent wiring", () => {
|
|||||||
gitContext: "## Git context\nsome git info",
|
gitContext: "## Git context\nsome git info",
|
||||||
};
|
};
|
||||||
|
|
||||||
test('buildMemoryInitRuntimePrompt includes "research_depth: shallow" when depth is "shallow"', () => {
|
test("buildShallowInitPrompt produces shallow-only prompt", () => {
|
||||||
const prompt = buildMemoryInitRuntimePrompt({
|
const prompt = buildShallowInitPrompt(baseArgs);
|
||||||
...baseArgs,
|
|
||||||
depth: "shallow",
|
|
||||||
});
|
|
||||||
expect(prompt).toContain("research_depth: shallow");
|
expect(prompt).toContain("research_depth: shallow");
|
||||||
expect(prompt).toContain("Shallow init");
|
expect(prompt).toContain("Shallow init");
|
||||||
expect(prompt).not.toContain("Deep init");
|
expect(prompt).not.toContain("Deep init");
|
||||||
});
|
});
|
||||||
|
|
||||||
test('buildMemoryInitRuntimePrompt includes "research_depth: deep" when depth is "deep"', () => {
|
test("buildInitMessage includes memoryDir when provided", () => {
|
||||||
const prompt = buildMemoryInitRuntimePrompt({
|
const msg = buildInitMessage({
|
||||||
...baseArgs,
|
gitContext: "## Git\nsome info",
|
||||||
depth: "deep",
|
memoryDir: "/tmp/.memory",
|
||||||
});
|
});
|
||||||
expect(prompt).toContain("research_depth: deep");
|
expect(msg).toContain("Memory filesystem is enabled");
|
||||||
expect(prompt).toContain("Deep init");
|
expect(msg).toContain("/tmp/.memory");
|
||||||
expect(prompt).not.toContain("Shallow init");
|
expect(msg).toContain("initializing-memory");
|
||||||
});
|
});
|
||||||
|
|
||||||
test('buildMemoryInitRuntimePrompt defaults to "deep" when depth is omitted', () => {
|
test("buildInitMessage works without memoryDir", () => {
|
||||||
const prompt = buildMemoryInitRuntimePrompt(baseArgs);
|
const msg = buildInitMessage({
|
||||||
expect(prompt).toContain("research_depth: deep");
|
gitContext: "## Git\nsome info",
|
||||||
expect(prompt).toContain("Deep init");
|
|
||||||
});
|
});
|
||||||
|
expect(msg).not.toContain("Memory filesystem");
|
||||||
test("App.tsx contains maybeLaunchDeepInitSubagent", () => {
|
expect(msg).toContain("initializing-memory");
|
||||||
const appSource = readSource("../../cli/App.tsx");
|
|
||||||
expect(appSource).toContain("maybeLaunchDeepInitSubagent");
|
|
||||||
expect(appSource).toContain("Deep memory initialization");
|
|
||||||
expect(appSource).toContain('depth: "deep"');
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -24,42 +24,24 @@ describe("memory subagent recompile handling", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("updates init progress and recompiles after successful shallow init", async () => {
|
test("recompiles system prompt after successful init", async () => {
|
||||||
const progressUpdates: Array<{
|
|
||||||
agentId: string;
|
|
||||||
update: Record<string, boolean>;
|
|
||||||
}> = [];
|
|
||||||
|
|
||||||
const message = await handleMemorySubagentCompletion(
|
const message = await handleMemorySubagentCompletion(
|
||||||
{
|
{
|
||||||
agentId: "agent-init-1",
|
agentId: "agent-init-1",
|
||||||
conversationId: "conv-init-1",
|
conversationId: "conv-init-1",
|
||||||
subagentType: "init",
|
subagentType: "init",
|
||||||
initDepth: "shallow",
|
|
||||||
success: true,
|
success: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
recompileByConversation: new Map(),
|
recompileByConversation: new Map(),
|
||||||
recompileQueuedByConversation: new Set(),
|
recompileQueuedByConversation: new Set(),
|
||||||
recompileAgentSystemPromptImpl: recompileAgentSystemPromptMock,
|
recompileAgentSystemPromptImpl: recompileAgentSystemPromptMock,
|
||||||
updateInitProgress: (agentId, update) => {
|
|
||||||
progressUpdates.push({
|
|
||||||
agentId,
|
|
||||||
update: update as Record<string, boolean>,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(message).toBe(
|
expect(message).toBe(
|
||||||
"Built a memory palace of you. Visit it with /palace.",
|
"Built a memory palace of you. Visit it with /palace.",
|
||||||
);
|
);
|
||||||
expect(progressUpdates).toEqual([
|
|
||||||
{
|
|
||||||
agentId: "agent-init-1",
|
|
||||||
update: { shallowCompleted: true },
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
expect(recompileAgentSystemPromptMock).toHaveBeenCalledWith(
|
expect(recompileAgentSystemPromptMock).toHaveBeenCalledWith(
|
||||||
"conv-init-1",
|
"conv-init-1",
|
||||||
{},
|
{},
|
||||||
@@ -79,7 +61,6 @@ describe("memory subagent recompile handling", () => {
|
|||||||
recompileByConversation,
|
recompileByConversation,
|
||||||
recompileQueuedByConversation,
|
recompileQueuedByConversation,
|
||||||
recompileAgentSystemPromptImpl: recompileAgentSystemPromptMock,
|
recompileAgentSystemPromptImpl: recompileAgentSystemPromptMock,
|
||||||
updateInitProgress: () => {},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const first = handleMemorySubagentCompletion(
|
const first = handleMemorySubagentCompletion(
|
||||||
@@ -145,7 +126,6 @@ describe("memory subagent recompile handling", () => {
|
|||||||
recompileByConversation: new Map<string, Promise<void>>(),
|
recompileByConversation: new Map<string, Promise<void>>(),
|
||||||
recompileQueuedByConversation: new Set<string>(),
|
recompileQueuedByConversation: new Set<string>(),
|
||||||
recompileAgentSystemPromptImpl: recompileAgentSystemPromptMock,
|
recompileAgentSystemPromptImpl: recompileAgentSystemPromptMock,
|
||||||
updateInitProgress: () => {},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const [firstMessage, secondMessage] = await Promise.all([
|
const [firstMessage, secondMessage] = await Promise.all([
|
||||||
|
|||||||
@@ -10,11 +10,6 @@ import {
|
|||||||
reflectionSettingsToLegacyMode,
|
reflectionSettingsToLegacyMode,
|
||||||
shouldFireStepCountTrigger,
|
shouldFireStepCountTrigger,
|
||||||
} from "../../cli/helpers/memoryReminder";
|
} from "../../cli/helpers/memoryReminder";
|
||||||
import {
|
|
||||||
type SharedReminderContext,
|
|
||||||
sharedReminderProviders,
|
|
||||||
} from "../../reminders/engine";
|
|
||||||
import { createSharedReminderState } from "../../reminders/state";
|
|
||||||
import { settingsManager } from "../../settings-manager";
|
import { settingsManager } from "../../settings-manager";
|
||||||
|
|
||||||
const originalGetLocalProjectSettings = settingsManager.getLocalProjectSettings;
|
const originalGetLocalProjectSettings = settingsManager.getLocalProjectSettings;
|
||||||
@@ -175,121 +170,3 @@ describe("memoryReminder", () => {
|
|||||||
).toBe(false);
|
).toBe(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("deep-init trigger", () => {
|
|
||||||
const deepInitProvider = sharedReminderProviders["deep-init"];
|
|
||||||
|
|
||||||
function makeContext(
|
|
||||||
overrides: Partial<{
|
|
||||||
shallowInitCompleted: boolean;
|
|
||||||
deepInitFired: boolean;
|
|
||||||
turnCount: number;
|
|
||||||
memfsEnabled: boolean;
|
|
||||||
callback: (() => Promise<boolean>) | undefined;
|
|
||||||
}> = {},
|
|
||||||
): SharedReminderContext {
|
|
||||||
const state = createSharedReminderState();
|
|
||||||
state.shallowInitCompleted = overrides.shallowInitCompleted ?? false;
|
|
||||||
state.deepInitFired = overrides.deepInitFired ?? false;
|
|
||||||
state.turnCount = overrides.turnCount ?? 0;
|
|
||||||
|
|
||||||
const memfsEnabled = overrides.memfsEnabled ?? true;
|
|
||||||
(settingsManager as typeof settingsManager).isMemfsEnabled = (() =>
|
|
||||||
memfsEnabled) as typeof settingsManager.isMemfsEnabled;
|
|
||||||
|
|
||||||
return {
|
|
||||||
mode: "interactive",
|
|
||||||
agent: { id: "test-agent", name: "test" },
|
|
||||||
state,
|
|
||||||
sessionContextReminderEnabled: false,
|
|
||||||
reflectionSettings: {
|
|
||||||
trigger: "step-count",
|
|
||||||
behavior: "auto-launch",
|
|
||||||
stepCount: 25,
|
|
||||||
},
|
|
||||||
skillSources: [],
|
|
||||||
resolvePlanModeReminder: async () => "",
|
|
||||||
maybeLaunchDeepInitSubagent: overrides.callback,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
test("does not fire before turn 8", async () => {
|
|
||||||
let launched = false;
|
|
||||||
const ctx = makeContext({
|
|
||||||
shallowInitCompleted: true,
|
|
||||||
turnCount: 7,
|
|
||||||
callback: async () => {
|
|
||||||
launched = true;
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const result = await deepInitProvider(ctx);
|
|
||||||
expect(result).toBeNull();
|
|
||||||
expect(launched).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Deep init auto-launch is currently disabled (reflection + deep init
|
|
||||||
// at similar turn counts is too chaotic). This test documents the
|
|
||||||
// disabled behavior; re-enable when subagent prompts are tuned.
|
|
||||||
test("is currently disabled — does not launch even when conditions are met", async () => {
|
|
||||||
let launched = false;
|
|
||||||
const ctx = makeContext({
|
|
||||||
shallowInitCompleted: true,
|
|
||||||
turnCount: 8,
|
|
||||||
callback: async () => {
|
|
||||||
launched = true;
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const result = await deepInitProvider(ctx);
|
|
||||||
expect(result).toBeNull();
|
|
||||||
expect(launched).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("does not re-fire once deepInitFired is true", async () => {
|
|
||||||
let launched = false;
|
|
||||||
const ctx = makeContext({
|
|
||||||
shallowInitCompleted: true,
|
|
||||||
deepInitFired: true,
|
|
||||||
turnCount: 10,
|
|
||||||
callback: async () => {
|
|
||||||
launched = true;
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const result = await deepInitProvider(ctx);
|
|
||||||
expect(result).toBeNull();
|
|
||||||
expect(launched).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("does not fire when shallowInitCompleted is false", async () => {
|
|
||||||
let launched = false;
|
|
||||||
const ctx = makeContext({
|
|
||||||
shallowInitCompleted: false,
|
|
||||||
turnCount: 10,
|
|
||||||
callback: async () => {
|
|
||||||
launched = true;
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const result = await deepInitProvider(ctx);
|
|
||||||
expect(result).toBeNull();
|
|
||||||
expect(launched).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("does not fire when memfs is disabled", async () => {
|
|
||||||
let launched = false;
|
|
||||||
const ctx = makeContext({
|
|
||||||
shallowInitCompleted: true,
|
|
||||||
turnCount: 8,
|
|
||||||
memfsEnabled: false,
|
|
||||||
callback: async () => {
|
|
||||||
launched = true;
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const result = await deepInitProvider(ctx);
|
|
||||||
expect(result).toBeNull();
|
|
||||||
expect(launched).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|||||||
Reference in New Issue
Block a user