feat: reset idle assistant sessions after one hour
When a conversation's assistant session has been idle longer than runtime.acp.assistantSessionResetIdleMs (default 1h, 0 disables) and its owner has no unfinished proposals, the next inbound message starts a fresh session instead of resuming. Binding lastActiveAt is persisted per turn so the decision survives restarts.
This commit is contained in:
@@ -645,8 +645,12 @@ export class AssistantManager implements ConversationRuntime {
|
||||
}
|
||||
await this.reserveAssistantCapacity();
|
||||
const persisted = this.store.getBinding(conversationKey);
|
||||
const binding = persisted && this.validBinding(persisted) ? persisted : undefined;
|
||||
let binding = persisted && this.validBinding(persisted) ? persisted : undefined;
|
||||
if (persisted && !binding) await this.store.deleteBinding(conversationKey);
|
||||
if (binding && this.shouldResetAssistantSession(conversationKey, binding)) {
|
||||
await this.store.deleteBinding(conversationKey);
|
||||
binding = undefined;
|
||||
}
|
||||
const cwd = this.prepareAssistantWorkspace(conversationKey, binding);
|
||||
const spawnWorker = async (nativeSessionId?: string): Promise<AcpWorker> => {
|
||||
const worker = new AcpWorker(this.bot, this.config, (crashed, error) => {
|
||||
@@ -688,7 +692,8 @@ export class AssistantManager implements ConversationRuntime {
|
||||
assistantWorkspace: cwd,
|
||||
botFingerprint: this.bot.fingerprint,
|
||||
createdAt: now,
|
||||
updatedAt: now
|
||||
updatedAt: now,
|
||||
lastActiveAt: now
|
||||
});
|
||||
} catch (error) {
|
||||
if (this.assistantWorkers.get(conversationKey) === worker) this.assistantWorkers.delete(conversationKey);
|
||||
@@ -703,6 +708,26 @@ export class AssistantManager implements ConversationRuntime {
|
||||
return binding.botFingerprint === this.bot.fingerprint && binding.agentId === this.bot.agent.id;
|
||||
}
|
||||
|
||||
// Lazy idle reset: a long-inactive assistant session is dropped (fresh session on the next
|
||||
// message) unless its owner still has an unfinished proposal that needs the old context.
|
||||
private shouldResetAssistantSession(conversationKey: string, binding: AssistantBinding): boolean {
|
||||
const resetMs = this.config.assistantSessionResetIdleMs;
|
||||
if (!resetMs) return false;
|
||||
const lastActiveAt = binding.lastActiveAt ?? binding.updatedAt;
|
||||
const idleMs = Date.now() - lastActiveAt;
|
||||
if (idleMs <= resetMs) return false;
|
||||
const separator = conversationKey.lastIndexOf("#");
|
||||
const chatKey = conversationKey.slice(0, separator);
|
||||
const userId = conversationKey.slice(separator + 1);
|
||||
const hasUnfinished = this.proposals.list().some((proposal) => proposal.ownerChatKey === chatKey
|
||||
&& proposal.requesterUserId === userId
|
||||
&& (proposal.status === "proposed" || proposal.status === "queued" || proposal.status === "working" || proposal.status === "pending"));
|
||||
if (hasUnfinished) return false;
|
||||
const keyHash = crypto.createHash("sha256").update(conversationKey).digest("hex").slice(0, 8);
|
||||
console.log(`Assistant session reset for conversation ${keyHash} after ${Math.floor(idleMs / 1000)}s idle`);
|
||||
return true;
|
||||
}
|
||||
|
||||
private async reserveAssistantCapacity(): Promise<void> {
|
||||
while (this.assistantWorkers.size >= this.maxAssistantSessions
|
||||
|| this.assistantWorkers.size + (this.active ? 1 : 0) >= this.config.maxProcesses) {
|
||||
|
||||
Reference in New Issue
Block a user