fix: recover managed agent instructions from disk
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
parent
a315838d43
commit
1adfd30b3b
2 changed files with 68 additions and 7 deletions
|
|
@ -161,4 +161,35 @@ describe("agent instructions service", () => {
|
|||
"docs/TOOLS.md",
|
||||
]);
|
||||
});
|
||||
|
||||
it("recovers a managed bundle from disk when bundle config metadata is missing", async () => {
|
||||
const paperclipHome = await makeTempDir("paperclip-agent-instructions-recover-");
|
||||
cleanupDirs.add(paperclipHome);
|
||||
process.env.PAPERCLIP_HOME = paperclipHome;
|
||||
process.env.PAPERCLIP_INSTANCE_ID = "test-instance";
|
||||
|
||||
const managedRoot = path.join(
|
||||
paperclipHome,
|
||||
"instances",
|
||||
"test-instance",
|
||||
"companies",
|
||||
"company-1",
|
||||
"agents",
|
||||
"agent-1",
|
||||
"instructions",
|
||||
);
|
||||
await fs.mkdir(managedRoot, { recursive: true });
|
||||
await fs.writeFile(path.join(managedRoot, "AGENTS.md"), "# Recovered Agent\n", "utf8");
|
||||
|
||||
const svc = agentInstructionsService();
|
||||
const agent = makeAgent({});
|
||||
|
||||
const bundle = await svc.getBundle(agent);
|
||||
const exported = await svc.exportFiles(agent);
|
||||
|
||||
expect(bundle.mode).toBe("managed");
|
||||
expect(bundle.rootPath).toBe(managedRoot);
|
||||
expect(bundle.files.map((file) => file.path)).toEqual(["AGENTS.md"]);
|
||||
expect(exported.files).toEqual({ "AGENTS.md": "# Recovered Agent\n" });
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -272,6 +272,24 @@ function deriveBundleState(agent: AgentLike): BundleState {
|
|||
};
|
||||
}
|
||||
|
||||
async function recoverManagedBundleState(agent: AgentLike, state: BundleState): Promise<BundleState> {
|
||||
if (state.rootPath) return state;
|
||||
|
||||
const managedRootPath = resolveManagedInstructionsRoot(agent);
|
||||
const stat = await statIfExists(managedRootPath);
|
||||
if (!stat?.isDirectory()) return state;
|
||||
|
||||
const files = await listFilesRecursive(managedRootPath);
|
||||
if (files.length === 0) return state;
|
||||
|
||||
return {
|
||||
...state,
|
||||
mode: "managed",
|
||||
rootPath: managedRootPath,
|
||||
resolvedEntryPath: path.resolve(managedRootPath, state.entryFile),
|
||||
};
|
||||
}
|
||||
|
||||
function toBundle(agent: AgentLike, state: BundleState, files: AgentInstructionsFileSummary[]): AgentInstructionsBundle {
|
||||
const nextFiles = [...files];
|
||||
if (state.legacyPromptTemplateActive && !nextFiles.some((file) => file.path === LEGACY_PROMPT_TEMPLATE_PATH)) {
|
||||
|
|
@ -366,7 +384,7 @@ export function syncInstructionsBundleConfigFromFilePath(
|
|||
|
||||
export function agentInstructionsService() {
|
||||
async function getBundle(agent: AgentLike): Promise<AgentInstructionsBundle> {
|
||||
const state = deriveBundleState(agent);
|
||||
const state = await recoverManagedBundleState(agent, deriveBundleState(agent));
|
||||
if (!state.rootPath) return toBundle(agent, state, []);
|
||||
const stat = await statIfExists(state.rootPath);
|
||||
if (!stat?.isDirectory()) {
|
||||
|
|
@ -381,7 +399,7 @@ export function agentInstructionsService() {
|
|||
}
|
||||
|
||||
async function readFile(agent: AgentLike, relativePath: string): Promise<AgentInstructionsFileDetail> {
|
||||
const state = deriveBundleState(agent);
|
||||
const state = await recoverManagedBundleState(agent, deriveBundleState(agent));
|
||||
if (relativePath === LEGACY_PROMPT_TEMPLATE_PATH) {
|
||||
const content = asString(state.config[PROMPT_KEY]);
|
||||
if (content === null) throw notFound("Instructions file not found");
|
||||
|
|
@ -422,9 +440,21 @@ export function agentInstructionsService() {
|
|||
agent: AgentLike,
|
||||
options?: { clearLegacyPromptTemplate?: boolean },
|
||||
): Promise<{ adapterConfig: Record<string, unknown>; state: BundleState }> {
|
||||
const current = deriveBundleState(agent);
|
||||
const derived = deriveBundleState(agent);
|
||||
const current = await recoverManagedBundleState(agent, derived);
|
||||
if (current.rootPath && current.mode) {
|
||||
return { adapterConfig: current.config, state: current };
|
||||
const adapterConfig = derived.rootPath
|
||||
? current.config
|
||||
: applyBundleConfig(current.config, {
|
||||
mode: current.mode,
|
||||
rootPath: current.rootPath,
|
||||
entryFile: current.entryFile,
|
||||
clearLegacyPromptTemplate: options?.clearLegacyPromptTemplate,
|
||||
});
|
||||
return {
|
||||
adapterConfig,
|
||||
state: deriveBundleState({ ...agent, adapterConfig }),
|
||||
};
|
||||
}
|
||||
|
||||
const managedRoot = resolveManagedInstructionsRoot(agent);
|
||||
|
|
@ -462,7 +492,7 @@ export function agentInstructionsService() {
|
|||
clearLegacyPromptTemplate?: boolean;
|
||||
},
|
||||
): Promise<{ bundle: AgentInstructionsBundle; adapterConfig: Record<string, unknown> }> {
|
||||
const state = deriveBundleState(agent);
|
||||
const state = await recoverManagedBundleState(agent, deriveBundleState(agent));
|
||||
const nextMode = input.mode ?? state.mode ?? "managed";
|
||||
const nextEntryFile = input.entryFile ? normalizeRelativeFilePath(input.entryFile) : state.entryFile;
|
||||
let nextRootPath: string;
|
||||
|
|
@ -544,7 +574,7 @@ export function agentInstructionsService() {
|
|||
bundle: AgentInstructionsBundle;
|
||||
adapterConfig: Record<string, unknown>;
|
||||
}> {
|
||||
const state = deriveBundleState(agent);
|
||||
const state = await recoverManagedBundleState(agent, deriveBundleState(agent));
|
||||
if (relativePath === LEGACY_PROMPT_TEMPLATE_PATH) {
|
||||
throw unprocessable("Cannot delete the legacy promptTemplate pseudo-file");
|
||||
}
|
||||
|
|
@ -564,7 +594,7 @@ export function agentInstructionsService() {
|
|||
entryFile: string;
|
||||
warnings: string[];
|
||||
}> {
|
||||
const state = deriveBundleState(agent);
|
||||
const state = await recoverManagedBundleState(agent, deriveBundleState(agent));
|
||||
if (state.rootPath) {
|
||||
const stat = await statIfExists(state.rootPath);
|
||||
if (stat?.isDirectory()) {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue