fix: use passive QQ event replies

This commit is contained in:
zenord
2026-08-18 22:20:55 +08:00
parent 4af5049605
commit ffedf616e3
9 changed files with 560 additions and 48 deletions
+76 -15
View File
@@ -135,9 +135,30 @@ export class AssistantManager implements ConversationRuntime {
}
status(platform: string, chatId: string, userId?: string): Record<string, string | number | boolean> {
const chatKey = chatKeyFor(platform, chatId);
const proposals = this.proposals.list();
const owned = (proposal: Proposal) => Boolean(userId) && proposal.ownerChatKey === chatKey && proposal.requesterUserId === userId;
const mine = proposals.filter(owned);
const active = this.active;
const activeProposal = active ? this.proposals.get(active.proposalId) : undefined;
const awaiting = proposals.filter((proposal) => proposal.status === "awaiting_user_confirmation");
const working = proposals.filter((proposal) => proposal.status === "working");
const myQueued = mine.filter((proposal) => proposal.status === "queued").length;
const myPending = mine.filter((proposal) => proposal.status === "awaiting_user_confirmation").length;
const myProposed = mine.filter((proposal) => proposal.status === "proposed").length;
const schedulerState = active || working.length > 0 ? "working" : awaiting.length > 0 ? "awaiting_confirmation" : "idle";
const blockedReason = awaiting.length > 0
? (awaiting.some(owned) ? "your proposal is awaiting your confirmation" : "another proposal is awaiting owner confirmation")
: active || working.length > 0
? (working.some(owned) || (activeProposal && owned(activeProposal)) ? "your proposal is working" : "another proposal is working")
: "none";
const nextAction = myPending > 0
? "confirm your pending proposal"
: myProposed > 0
? "confirm your proposed proposal to queue it"
: myQueued > 0
? (schedulerState === "idle" ? "start_next" : "wait for the current proposal to settle")
: "none";
return {
bot: this.bot.id,
agent: this.bot.agent.id,
@@ -145,10 +166,15 @@ export class AssistantManager implements ConversationRuntime {
assistantSessions: this.assistantWorkers.size,
proposals: proposals.length,
queuedProposals: proposals.filter((proposal) => proposal.status === "queued").length,
workingProposals: proposals.filter((proposal) => proposal.status === "working").length,
awaitingConfirmation: proposals.filter((proposal) => proposal.status === "awaiting_user_confirmation").length,
workingProposals: working.length,
awaitingConfirmation: awaiting.length,
workerRunning: Boolean(active?.worker.inFlight),
owner: Boolean(userId && activeProposal && activeProposal.ownerChatKey === chatKeyFor(platform, chatId) && activeProposal.requesterUserId === userId)
owner: Boolean(userId && activeProposal && owned(activeProposal)),
myQueuedProposals: myQueued,
myPendingConfirmations: myPending,
schedulerState,
blockedReason,
nextAction
};
}
@@ -188,8 +214,8 @@ export class AssistantManager implements ConversationRuntime {
const parsed = await this.parseAssistantReply(worker, reply);
if (worker.isolationViolated) throw new Error("Assistant session attempted forbidden tool activity");
await this.store.touchBinding(conversationKey);
await this.executeActions(chatKeyFor(request.platform, request.chatId), request, parsed.actions);
return parsed.reply;
const corrections = await this.executeActions(chatKeyFor(request.platform, request.chatId), request, parsed.actions);
return corrections.length > 0 ? `${parsed.reply}\n${corrections.join("\n")}` : parsed.reply;
} catch (error) {
if (worker.isolationViolated) await this.store.deleteBinding(conversationKey).catch(() => undefined);
throw error;
@@ -203,7 +229,8 @@ export class AssistantManager implements ConversationRuntime {
return parsed;
}
private async executeActions(chatKey: string, request: ConversationRequest, actions: AssistantAction[]): Promise<void> {
private async executeActions(chatKey: string, request: ConversationRequest, actions: AssistantAction[]): Promise<string[]> {
const corrections: string[] = [];
for (const action of actions) {
if (action.type === "create_proposal") {
await this.proposals.create({
@@ -214,15 +241,33 @@ export class AssistantManager implements ConversationRuntime {
requesterUserId: request.userId
});
} else if (action.type === "confirm") {
await this.scheduler(() => this.confirmLocked(chatKey, request.userId, action.id, action.answer));
const ok = await this.scheduler(() => this.confirmLocked(chatKey, request.userId, action.id, action.answer));
if (!ok) corrections.push("没有可确认的提案;你只能操作自己发起的任务。");
} else if (action.type === "start_next") {
await this.scheduler(() => this.tryStartLocked(chatKey, request.userId));
const started = await this.scheduler(() => this.tryStartLocked(chatKey, request.userId));
if (!started) corrections.push(this.startNextCorrection(chatKey, request.userId));
} else if (action.type === "cancel") {
await this.scheduler(() => this.cancelLocked(chatKey, request.userId, action.id));
const ok = await this.scheduler(() => this.cancelLocked(chatKey, request.userId, action.id));
if (!ok) corrections.push("没有可取消的提案;你只能操作自己发起的任务。");
} else if (action.type === "stop") {
await this.scheduler(() => this.stopActiveLocked(chatKey, request.userId));
const ok = await this.scheduler(() => this.stopActiveLocked(chatKey, request.userId));
if (!ok) corrections.push("没有可停止的任务;你只能操作自己发起的任务。");
}
}
return corrections;
}
private startNextCorrection(chatKey: string, userId: string): string {
const queuedMine = this.proposals.list({ status: "queued" })
.some((proposal) => proposal.ownerChatKey === chatKey && proposal.requesterUserId === userId);
const kept = queuedMine ? "你的任务已保留在队列里。" : "";
if (this.proposals.list({ status: "awaiting_user_confirmation" }).length > 0) {
return `还不能开始:还有一个任务在等发起人确认。${kept}`.trim();
}
if (this.active || this.proposals.list({ status: "working" }).length > 0) {
return `还不能开始:还有一个任务正在执行。${kept}`.trim();
}
return "还不能开始:你没有已确认并排队的任务。";
}
private async confirmLocked(chatKey: string, userId: string, id?: string, answer?: string): Promise<boolean> {
@@ -310,15 +355,16 @@ export class AssistantManager implements ConversationRuntime {
await this.proposals.update(proposalId, { workerProcessGroup: undefined });
}
private async tryStartLocked(chatKey: string, userId: string): Promise<void> {
if (this.active) return;
if (this.proposals.list({ status: "working" }).length > 0) return;
if (this.proposals.list({ status: "awaiting_user_confirmation" }).length > 0) return;
private async tryStartLocked(chatKey: string, userId: string): Promise<boolean> {
if (this.active) return false;
if (this.proposals.list({ status: "working" }).length > 0) return false;
if (this.proposals.list({ status: "awaiting_user_confirmation" }).length > 0) return false;
const next = this.proposals.list({ status: "queued" })
.find((proposal) => proposal.ownerChatKey === chatKey && proposal.requesterUserId === userId);
if (!next) return;
if (!next) return false;
await this.proposals.update(next.id, { status: "working", startedAt: Date.now() });
this.launchWorker(next.id);
return true;
}
private resolveTargetProposal(chatKey: string, userId: string, id: string | undefined, statuses: Proposal["status"][]): Proposal | undefined {
@@ -600,11 +646,26 @@ export class AssistantManager implements ConversationRuntime {
"[Proposal states]",
...this.proposalSummaries(chatKey, request.userId),
"",
"[Scheduler state]",
this.schedulerStateSummary(chatKey, request.userId),
"",
"[Worker state]",
this.workerStateSummary(chatKey, request.userId)
].join("\n");
}
private schedulerStateSummary(chatKey: string, userId: string): string {
const owned = (proposal: Proposal) => proposal.ownerChatKey === chatKey && proposal.requesterUserId === userId;
const awaitingOther = this.proposals.list({ status: "awaiting_user_confirmation" }).some((proposal) => !owned(proposal));
if (awaitingOther) return "blocked: another proposal is awaiting owner confirmation";
const active = this.active;
const activeProposal = active ? this.proposals.get(active.proposalId) : undefined;
const workingOther = this.proposals.list({ status: "working" }).some((proposal) => !owned(proposal))
|| Boolean(activeProposal && !owned(activeProposal));
if (workingOther) return "busy: another proposal is working";
return "idle";
}
private proposalSummaries(chatKey: string, userId: string): string[] {
const proposals = this.proposals.list()
.filter((proposal) => proposal.ownerChatKey === chatKey && proposal.requesterUserId === userId);