mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-10-21 17:44:11 +00:00
🩹 fix: apply agent updates that match the newest version entry (#14810)
`updateAgent` returned early when the resulting state matched the newest `versions` entry, so `findOneAndUpdate` never ran and the caller's update was discarded behind a 200 response. Suppressing a redundant version entry is correct; suppressing the write is not. The document is regularly not equal to its newest version entry: `$push`/`$pull`/`$addToSet` updates snapshot the pre-update state (as `addAgentResourceFile` does on every file attach), `skipVersioning` writes snapshot nothing, and `removeAgentResourceFiles` bypasses `updateAgent` altogether. Any update that moved the document back onto that entry's content was then dropped, leaving the drifted state in place. Keep the version entry suppressed, apply the write, and still report the unchanged `versions` count as `version` so callers keep their existing "no new version" signal. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
abc669ab58
commit
da390fa919
2 changed files with 56 additions and 8 deletions
|
|
@ -1980,6 +1980,40 @@ describe('Agent Methods', () => {
|
|||
}
|
||||
});
|
||||
|
||||
test('should persist an update matching the newest version when the document has diverged from it', async () => {
|
||||
const agentId = `agent_${uuidv4()}`;
|
||||
const authorId = new mongoose.Types.ObjectId();
|
||||
|
||||
await createAgent({
|
||||
id: agentId,
|
||||
provider: 'test',
|
||||
model: 'test-model',
|
||||
author: authorId,
|
||||
tools: [],
|
||||
});
|
||||
|
||||
/** Atomic-operator updates snapshot the pre-update state as the new version, so the
|
||||
* document and `versions[versions.length - 1]` legitimately diverge. This is the
|
||||
* same shape `addAgentResourceFile` produces when it attaches a file. */
|
||||
await updateAgent({ id: agentId }, { $addToSet: { tools: 'file_search' } });
|
||||
|
||||
const afterAdd = await getAgent({ id: agentId });
|
||||
expect(afterAdd!.tools).toEqual(['file_search']);
|
||||
expect(afterAdd!.versions).toHaveLength(2);
|
||||
expect((afterAdd!.versions![1] as VersionEntry).tools).toEqual([]);
|
||||
|
||||
/** Removing the tool lands the document back on the newest version's content. That
|
||||
* adds no history, but the removal itself must still be written. */
|
||||
const removed = await updateAgent({ id: agentId }, { tools: [] });
|
||||
|
||||
expect(removed!.tools).toEqual([]);
|
||||
expect(removed!.versions).toHaveLength(2);
|
||||
|
||||
const reloaded = await getAgent({ id: agentId });
|
||||
expect(reloaded!.tools).toEqual([]);
|
||||
expect(reloaded!.versions).toHaveLength(2);
|
||||
});
|
||||
|
||||
test('should track updatedBy when a different user updates an agent', async () => {
|
||||
const agentId = `agent_${uuidv4()}`;
|
||||
const originalAuthor = new mongoose.Types.ObjectId();
|
||||
|
|
|
|||
|
|
@ -604,6 +604,9 @@ export function createAgentMethods(
|
|||
const Agent = mongoose.models.Agent as Model<IAgent>;
|
||||
const { updatingUserId = null, forceVersion = false, skipVersioning = false } = options;
|
||||
const mongoOptions = { new: true, upsert: false };
|
||||
/** Set when the update would snapshot a version identical to the newest one. The write
|
||||
* still lands; only the `versions` entry is dropped. */
|
||||
let suppressedVersionEntry = false;
|
||||
|
||||
const currentAgent = await Agent.findOne(searchParameter);
|
||||
if (currentAgent) {
|
||||
|
|
@ -694,12 +697,14 @@ export function createAgentMethods(
|
|||
actionsHash,
|
||||
);
|
||||
if (duplicateVersion && !forceVersion) {
|
||||
const agentObj = currentAgent.toObject() as IAgent & {
|
||||
version?: number;
|
||||
versions?: unknown[];
|
||||
};
|
||||
agentObj.version = (versions as unknown[]).length;
|
||||
return agentObj;
|
||||
/** A snapshot identical to the newest version adds no history, but the update
|
||||
* itself must still be applied. The document is not always equal to that
|
||||
* version: `$push`/`$pull`/`$addToSet` updates snapshot the pre-update state,
|
||||
* and `skipVersioning` writes snapshot nothing at all. Whenever the caller
|
||||
* moves the document back onto the newest version's content — removing a tool
|
||||
* `addAgentResourceFile` added, for instance — returning here dropped a real
|
||||
* change and reported success. */
|
||||
suppressedVersionEntry = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -717,7 +722,7 @@ export function createAgentMethods(
|
|||
versionEntry.updatedBy = new mongoose.Types.ObjectId(updatingUserId);
|
||||
}
|
||||
|
||||
if (shouldCreateVersion) {
|
||||
if (shouldCreateVersion && !suppressedVersionEntry) {
|
||||
updateData.$push = {
|
||||
...(($push as Record<string, unknown>) || {}),
|
||||
versions: versionEntry,
|
||||
|
|
@ -725,11 +730,20 @@ export function createAgentMethods(
|
|||
}
|
||||
}
|
||||
|
||||
return (await Agent.findOneAndUpdate(
|
||||
const updatedAgent = (await Agent.findOneAndUpdate(
|
||||
searchParameter,
|
||||
updateData,
|
||||
mongoOptions,
|
||||
).lean()) as IAgent | null;
|
||||
|
||||
/** Callers that create a version read `version` back from their own count of
|
||||
* `versions`; a suppressed entry leaves that count unchanged, so report it here to
|
||||
* keep the "no new version" signal these callers already relied on. */
|
||||
if (updatedAgent && suppressedVersionEntry) {
|
||||
(updatedAgent as IAgent & { version?: number }).version = updatedAgent.versions?.length ?? 0;
|
||||
}
|
||||
|
||||
return updatedAgent;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue