feat: share finish stop and cancel across the board

Proposal visibility is already global; this change makes finish, stop,
and cancel shared queue-management actions so any user can unblock the
single worker and shared queue. Confirm, adjust, follow_up, and
start_next remain owner-only. Assistant/bootstrap copy, /help, /list,
and runtime checks now align with that split.
This commit is contained in:
zenord
2026-08-22 00:02:26 +08:00
parent 91327dc632
commit 5229059fe3
9 changed files with 111 additions and 45 deletions
+37 -24
View File
@@ -392,18 +392,19 @@ test("proposals are owned per chat+user: a second user in the same chat cannot c
assert.equal(await harness.manager.confirm("webhook", "chat-1", "user-b"), false);
await harness.manager.prompt(request("confirm", "user-b"));
assert.equal(harness.proposals.get(proposal.id)!.status, "proposed");
assert.equal(await harness.manager.cancel("webhook", "chat-1", "user-b"), false);
await harness.manager.prompt(request("cancel proposal", "user-b"));
assert.equal(harness.proposals.get(proposal.id)!.status, "proposed");
assert.equal(await harness.manager.finish("webhook", "chat-1", "user-b"), false);
assert.equal(await harness.manager.stop("webhook", "chat-1", "user-b"), false);
assert.equal(await harness.manager.cancel("webhook", "chat-1", "user-b"), true);
assert.equal(harness.proposals.get(proposal.id)!.status, "finished");
assert.equal(harness.proposals.get(proposal.id)!.finishKind, "cancelled");
await harness.manager.prompt(request("create proposal: hang", "user-a"));
const running = harness.proposals.list().find((item) => item.status === "proposed")!;
// The board is globally visible: both users see the same proposal, tagged by scope.
assert.equal(harness.manager.listProposals("webhook", "chat-1", "user-b").length, 1);
assert.equal(harness.manager.listProposals("webhook", "chat-1", "user-a").length, 1);
assert.equal(harness.manager.listProposals("webhook", "chat-1", "user-b").some((item) => item.id === running.id), true);
assert.equal(harness.manager.listProposals("webhook", "chat-1", "user-a").some((item) => item.id === running.id), true);
assert.equal(await harness.manager.confirm("webhook", "chat-1", "user-a"), true);
assert.equal(harness.proposals.get(proposal.id)!.status, "working");
assert.equal(harness.proposals.get(running.id)!.status, "working");
await harness.manager.prompt(request("hello owner", "user-a"));
const aPrompt = readLog(harness.logFile).find((entry) => entry.method === "session/prompt" && entry.text?.startsWith("[User message]\nhello owner"));
@@ -420,11 +421,11 @@ test("proposals are owned per chat+user: a second user in the same chat cannot c
assert.match(bPrompt.text!, /scope=other chat=direct/);
assert.doesNotMatch(bPrompt.text!, /user-a/);
assert.equal(await harness.manager.stop("webhook", "chat-1", "user-b"), false);
assert.equal(harness.proposals.get(proposal.id)!.status, "working");
assert.equal(await harness.manager.stop("webhook", "chat-1", "user-a"), true);
assert.equal(harness.proposals.get(proposal.id)!.status, "pending");
assert.equal(harness.proposals.get(proposal.id)!.pending?.summary, "被用户中止");
assert.equal(await harness.manager.stop("webhook", "chat-1", "user-b"), true);
assert.equal(harness.proposals.get(running.id)!.status, "pending");
assert.equal(harness.proposals.get(running.id)!.pending?.summary, "被用户中止");
assert.equal(await harness.manager.finish("webhook", "chat-1", "user-b"), true);
assert.equal(harness.proposals.get(running.id)!.status, "finished");
} finally {
await closeHarness(harness);
}
@@ -662,32 +663,44 @@ test("a working proposal blocks start_next for everyone with a sanitized reason"
}
});
test("confirm, finish, stop, cancel, and follow_up actions with no matching proposal append an ownership correction", async () => {
test("confirm and follow_up stay owner-only while finish, stop, and cancel are shared", async () => {
const harness = await createHarness();
try {
await harness.manager.prompt(request("create proposal: hang", "user-a"));
const proposed = harness.proposals.list()[0]!;
const confirmed = await harness.manager.prompt(request("confirm", "user-b"));
assert.match(confirmed.text, /没有可确认的提案/);
assert.match(confirmed.text, /自己发起/);
const cancelled = await harness.manager.prompt(request("cancel proposal", "user-b"));
assert.equal(cancelled.text, "cancelling");
assert.equal(harness.proposals.get(proposed.id)!.status, "finished");
assert.equal(harness.proposals.get(proposed.id)!.finishKind, "cancelled");
await harness.manager.prompt(request("create proposal: ask", "user-a"));
const pending = harness.proposals.list().find((proposal) => proposal.status === "proposed")!;
await harness.manager.prompt(request("confirm", "user-a"));
await waitFor(() => harness.proposals.get(pending.id)!.status === "pending");
const finished = await harness.manager.prompt(request("finish", "user-b"));
assert.match(finished.text, /没有可结束的任务/);
assert.match(finished.text, /自己发起/);
assert.equal(finished.text, "finishing");
assert.equal(harness.proposals.get(pending.id)!.status, "finished");
assert.equal(harness.proposals.get(pending.id)!.finishKind, "done");
await harness.manager.prompt(request("create proposal: hang", "user-a"));
const running = harness.proposals.list().find((proposal) => proposal.status === "proposed")!;
await harness.manager.prompt(request("confirm", "user-a"));
assert.equal(harness.proposals.get(running.id)!.status, "working");
const stopped = await harness.manager.prompt(request("stop", "user-b"));
assert.match(stopped.text, /没有可停止的任务/);
assert.match(stopped.text, /自己发起/);
const cancelled = await harness.manager.prompt(request("cancel proposal", "user-b"));
assert.match(cancelled.text, /没有可取消的提案/);
assert.match(cancelled.text, /自己发起/);
assert.equal(stopped.text, "stopping");
assert.equal(harness.proposals.get(running.id)!.status, "pending");
assert.equal(harness.proposals.get(running.id)!.pending?.summary, "被用户中止");
const followedUp = await harness.manager.prompt(request("follow up: more", "user-b"));
assert.match(followedUp.text, /现在没法继续/);
assert.match(followedUp.text, /自己发起/);
assert.equal(harness.proposals.list()[0]!.status, "proposed");
} finally {
await closeHarness(harness);
}