forked from bai/curriculum-project-hub
fix(hub): agent 会话记忆在配置变更/发版后丢失 + Feishu thread 400 (#19)
Co-authored-by: Hong Jiarong <me@jrhim.com> Co-committed-by: Hong Jiarong <me@jrhim.com>
This commit is contained in:
@@ -173,7 +173,7 @@ export class OrganizationAgentConfiguration {
|
||||
where: { id: skill.id },
|
||||
data: { disabledAt: new Date() },
|
||||
});
|
||||
await archiveRoleSessions(
|
||||
await invalidateRoleSessionClaudeIds(
|
||||
tx,
|
||||
input.organizationId,
|
||||
skill.roleBindings.map((binding) => binding.role.roleId),
|
||||
@@ -271,7 +271,7 @@ export class OrganizationAgentConfiguration {
|
||||
},
|
||||
});
|
||||
if (previous !== null && previous.contentDigest !== skill.contentDigest) {
|
||||
await archiveRoleSessions(
|
||||
await invalidateRoleSessionClaudeIds(
|
||||
tx,
|
||||
input.organizationId,
|
||||
skill.roleBindings.map((binding) => binding.role.roleId),
|
||||
@@ -379,7 +379,7 @@ export class OrganizationAgentConfiguration {
|
||||
if (activeDefaultCount !== 1) {
|
||||
throw new Error(`organization ${input.organizationId} must have exactly one active default role`);
|
||||
}
|
||||
if (executionSurfaceChanged) await archiveRoleSessions(tx, input.organizationId, [input.roleId]);
|
||||
if (executionSurfaceChanged) await invalidateRoleSessionClaudeIds(tx, input.organizationId, [input.roleId]);
|
||||
await tx.auditEntry.create({
|
||||
data: {
|
||||
organizationId: input.organizationId,
|
||||
@@ -449,7 +449,7 @@ export class OrganizationAgentConfiguration {
|
||||
})),
|
||||
});
|
||||
}
|
||||
await archiveRoleSessions(tx, input.organizationId, [input.roleId]);
|
||||
await invalidateRoleSessionClaudeIds(tx, input.organizationId, [input.roleId]);
|
||||
await tx.auditEntry.create({
|
||||
data: {
|
||||
organizationId: input.organizationId,
|
||||
@@ -472,7 +472,22 @@ export class OrganizationAgentConfiguration {
|
||||
}
|
||||
}
|
||||
|
||||
async function archiveRoleSessions(
|
||||
/**
|
||||
* Invalidate the provider session cursor (e.g. `claudeSessionId`) for every
|
||||
* active session of the given roles, WITHOUT archiving the session.
|
||||
*
|
||||
* Execution-surface changes (role model/systemPrompt/tools, skill content or
|
||||
* binding changes) make a stale provider session cursor unsafe to resume: the
|
||||
* prior turns were produced under a different config. But the conversation
|
||||
* history itself (AgentMessage rows) is still valuable and the logical Hub
|
||||
* session should stay continuous — the next run re-seeds context from the
|
||||
* transcript instead of resuming the old provider session. So we drop only
|
||||
* the cursor, not the session.
|
||||
*
|
||||
* `userResumable` is cleared because the session is no longer backed by a
|
||||
* live provider cursor the user can drop back into.
|
||||
*/
|
||||
async function invalidateRoleSessionClaudeIds(
|
||||
tx: Prisma.TransactionClient,
|
||||
organizationId: string,
|
||||
roleIds: readonly string[],
|
||||
@@ -482,20 +497,18 @@ async function archiveRoleSessions(
|
||||
where: {
|
||||
roleId: { in: [...new Set(roleIds)] },
|
||||
project: { organizationId },
|
||||
archivedAt: null,
|
||||
},
|
||||
select: { id: true, archivedAt: true, metadata: true },
|
||||
select: { id: true, metadata: true },
|
||||
});
|
||||
const archivedAt = new Date();
|
||||
for (const session of sessions) {
|
||||
const metadata = typeof session.metadata === "object" && session.metadata !== null && !Array.isArray(session.metadata)
|
||||
? session.metadata as Prisma.JsonObject
|
||||
: {};
|
||||
const { claudeSessionId: _drop, ...rest } = metadata;
|
||||
await tx.agentSession.update({
|
||||
where: { id: session.id },
|
||||
data: {
|
||||
...(session.archivedAt === null ? { archivedAt } : {}),
|
||||
metadata: { ...metadata, userResumable: false },
|
||||
},
|
||||
data: { metadata: { ...rest, userResumable: false } },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user