feat: share the proposal board across chats

The unfinished-proposal board is now visible to every conversation,
with entries marked scope=own/scope=other (chat type only, no IDs), so
anyone can see what the single worker is busy with. All actions remain
owner-only, and pending reminders still target only the owner.
This commit is contained in:
zenord
2026-08-19 17:08:48 +08:00
parent f17fee7383
commit 91327dc632
7 changed files with 67 additions and 25 deletions
+11 -7
View File
@@ -329,7 +329,9 @@ export class Gateway {
}
case "list": {
if (!this.runtime.listProposals) return "当前运行时不支持列出提案。";
return renderProposalPanel(this.runtime.listProposals(message.platform, message.chatId, message.userId));
const chatKey = chatKeyFor(message.platform, message.chatId);
const owned = (proposal: Proposal) => proposal.ownerChatKey === chatKey && proposal.requesterUserId === message.userId;
return renderProposalPanel(this.runtime.listProposals(message.platform, message.chatId, message.userId), owned);
}
case "confirm": {
if (!this.runtime.confirm) return "当前运行时不支持确认操作。";
@@ -357,8 +359,10 @@ export class Gateway {
}
function renderProposalPanel(proposals: Proposal[]): string {
function renderProposalPanel(proposals: Proposal[], owned: (proposal: Proposal) => boolean): string {
if (proposals.length === 0) return "当前没有提案。";
// The board is globally visible; ownership is annotated without exposing chat/user IDs.
const scope = (proposal: Proposal) => owned(proposal) ? "(你的)" : "(其他成员)";
const pending = proposals.filter((proposal) => proposal.status === "pending");
const working = proposals.filter((proposal) => proposal.status === "working");
const queued = proposals.filter((proposal) => proposal.status === "queued");
@@ -371,26 +375,26 @@ function renderProposalPanel(proposals: Proposal[]): string {
sections.push("待确认(等你处理):");
for (const proposal of pending) {
const card = proposal.pending;
sections.push(`- ${proposal.id}「${proposal.title}」${card ? `:${card.summary}${card.question ? `(问你:${card.question})` : ""}` : ""}(说 /finish 结束,或直接说要求继续改)`);
sections.push(`- ${proposal.id}「${proposal.title}」${scope(proposal)}${card ? `:${card.summary}${card.question ? `(问你:${card.question})` : ""}` : ""}(说 /finish 结束,或直接说要求继续改)`);
}
}
if (working.length > 0) {
sections.push("进行中:");
for (const proposal of working) sections.push(`- ${proposal.id}「${proposal.title}」正在执行(/stop 可中止)`);
for (const proposal of working) sections.push(`- ${proposal.id}「${proposal.title}」${scope(proposal)}正在执行(/stop 可中止)`);
}
if (queued.length > 0) {
sections.push("排队中:");
for (const proposal of queued) sections.push(`- ${proposal.id}「${proposal.title}」`);
for (const proposal of queued) sections.push(`- ${proposal.id}「${proposal.title}」${scope(proposal)}`);
}
if (proposed.length > 0) {
sections.push("未确认(proposed):");
for (const proposal of proposed) sections.push(`- ${proposal.id}「${proposal.title}」(/confirm 确认后排队)`);
for (const proposal of proposed) sections.push(`- ${proposal.id}「${proposal.title}」${scope(proposal)}(/confirm 确认后排队)`);
}
if (finished.length > 0) {
sections.push("最近结束:");
for (const proposal of finished) {
const state = proposal.finishKind === "cancelled" ? "已取消" : "已完成";
sections.push(`- ${proposal.id}「${proposal.title}」${state}${proposal.finishNote ? `(${proposal.finishNote})` : ""}`);
sections.push(`- ${proposal.id}「${proposal.title}」${scope(proposal)}${state}${proposal.finishNote ? `(${proposal.finishNote})` : ""}`);
}
}
return sections.join("\n");