mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-26 18:31:34 +01:00
feat(plugins): forward checkbox selections
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
14f20be92b
commit
896295b163
7 files changed
+88
-5
No files matched your search
@@ -1496,6 +1496,8 @@ export interface WorkerToHostMethods {
|
||||
*/
|
||||
actorUserId?: string;
|
||||
reason?: string | null;
|
||||
/** Explicit checkbox selection for request_checkbox_confirmation accepts. */
|
||||
selectedOptionIds?: string[];
|
||||
},
|
||||
result: { interaction: IssueThreadInteraction; applied: boolean },
|
||||
];
|
||||
|
||||
@@ -1810,6 +1810,16 @@ export function createTestHarness(options: TestHarnessOptions): TestHarness {
|
||||
const resolved: IssueThreadInteraction = {
|
||||
...current,
|
||||
status: input.action === "accept" ? "accepted" : "rejected",
|
||||
...(input.action === "accept"
|
||||
&& current.kind === "request_checkbox_confirmation"
|
||||
&& input.selectedOptionIds !== undefined
|
||||
? {
|
||||
result: {
|
||||
outcome: "accepted",
|
||||
selectedOptionIds: input.selectedOptionIds,
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
updatedAt: new Date(),
|
||||
} as IssueThreadInteraction;
|
||||
const index = list.indexOf(current);
|
||||
|
||||
@@ -1530,7 +1530,13 @@ export interface PluginIssuesClient {
|
||||
respondInteraction(
|
||||
issueId: string,
|
||||
interactionId: string,
|
||||
input: { action: "accept" | "reject"; actorUserId?: string; reason?: string | null },
|
||||
input: {
|
||||
action: "accept" | "reject";
|
||||
actorUserId?: string;
|
||||
reason?: string | null;
|
||||
/** Explicit checkbox selection; omit to accept the interaction defaults. */
|
||||
selectedOptionIds?: string[];
|
||||
},
|
||||
companyId: string,
|
||||
): Promise<{ interaction: IssueThreadInteraction; applied: boolean }>;
|
||||
/** List attachment metadata for an issue. Requires `issue.attachments.read`. */
|
||||
|
||||
@@ -993,7 +993,12 @@ export function startWorkerRpcHost(options: WorkerRpcHostOptions): WorkerRpcHost
|
||||
async respondInteraction(
|
||||
issueId: string,
|
||||
interactionId: string,
|
||||
input: { action: "accept" | "reject"; actorUserId?: string; reason?: string | null },
|
||||
input: {
|
||||
action: "accept" | "reject";
|
||||
actorUserId?: string;
|
||||
reason?: string | null;
|
||||
selectedOptionIds?: string[];
|
||||
},
|
||||
companyId: string,
|
||||
) {
|
||||
return callHost("issues.respondInteraction", {
|
||||
@@ -1003,6 +1008,7 @@ export function startWorkerRpcHost(options: WorkerRpcHostOptions): WorkerRpcHost
|
||||
action: input.action,
|
||||
actorUserId: input.actorUserId,
|
||||
reason: input.reason,
|
||||
selectedOptionIds: input.selectedOptionIds,
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
@@ -337,10 +337,22 @@ describe("createHostClientHandlers capability gating for LOOA-641 methods", () =
|
||||
});
|
||||
await expect(
|
||||
handlers["issues.respondInteraction"]({
|
||||
issueId: "issue-a", interactionId: "int-a", companyId: "company-a", action: "accept", actorUserId: "user-a",
|
||||
issueId: "issue-a",
|
||||
interactionId: "int-a",
|
||||
companyId: "company-a",
|
||||
action: "accept",
|
||||
actorUserId: "user-a",
|
||||
selectedOptionIds: ["spike-b"],
|
||||
}, context),
|
||||
).resolves.toEqual({ interaction: { id: "i" }, applied: true });
|
||||
expect(respondInteraction).toHaveBeenCalledOnce();
|
||||
expect(respondInteraction).toHaveBeenCalledWith({
|
||||
issueId: "issue-a",
|
||||
interactionId: "int-a",
|
||||
companyId: "company-a",
|
||||
action: "accept",
|
||||
actorUserId: "user-a",
|
||||
selectedOptionIds: ["spike-b"],
|
||||
});
|
||||
});
|
||||
|
||||
it("denies approvals.decide without approvals.respond", async () => {
|
||||
|
||||
@@ -991,6 +991,51 @@ describeEmbeddedPostgres("plugin orchestration APIs", () => {
|
||||
expect(row?.status).toBe("accepted");
|
||||
});
|
||||
|
||||
it("respondInteraction forwards an explicit checkbox selection", async () => {
|
||||
const { companyId } = await seedCompanyAndAgent();
|
||||
const operatorUserId = randomUUID();
|
||||
await db.insert(companyMemberships).values({
|
||||
companyId, principalType: "user", principalId: operatorUserId, status: "active", membershipRole: "operator",
|
||||
});
|
||||
const issueId = randomUUID();
|
||||
await db.insert(issues).values({
|
||||
id: issueId, companyId, title: "Choose campaigns", status: "in_review", priority: "medium",
|
||||
});
|
||||
const interactionId = await seedInteraction(companyId, issueId, {
|
||||
kind: "request_checkbox_confirmation",
|
||||
payload: {
|
||||
version: 1,
|
||||
prompt: "Which spikes should become campaigns?",
|
||||
options: [
|
||||
{ id: "spike-a", label: "Spike A" },
|
||||
{ id: "spike-b", label: "Spike B" },
|
||||
],
|
||||
defaultSelectedOptionIds: [],
|
||||
minSelected: 0,
|
||||
maxSelected: 2,
|
||||
} as never,
|
||||
});
|
||||
const services = buildHostServices(db, "plugin-record-id", "paperclip.gateway", createEventBusStub());
|
||||
|
||||
const result = await services.issues.respondInteraction({
|
||||
issueId,
|
||||
interactionId,
|
||||
companyId,
|
||||
action: "accept",
|
||||
actorUserId: operatorUserId,
|
||||
selectedOptionIds: ["spike-b"],
|
||||
});
|
||||
|
||||
expect(result.applied).toBe(true);
|
||||
expect(result.interaction).toMatchObject({
|
||||
status: "accepted",
|
||||
result: {
|
||||
outcome: "accepted",
|
||||
selectedOptionIds: ["spike-b"],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("respondInteraction converges (applied:false) when the interaction is already resolved", async () => {
|
||||
const { companyId, agentId } = await seedCompanyAndAgent();
|
||||
const humanUserId = randomUUID();
|
||||
|
||||
@@ -2289,7 +2289,9 @@ export function buildHostServices(
|
||||
const result = await interactions.acceptInteraction(
|
||||
{ id: issue.id, companyId, projectId: issue.projectId ?? null, goalId: issue.goalId ?? null },
|
||||
params.interactionId,
|
||||
{},
|
||||
params.selectedOptionIds === undefined
|
||||
? {}
|
||||
: { selectedOptionIds: params.selectedOptionIds },
|
||||
actor,
|
||||
);
|
||||
resolved = result.interaction as typeof current;
|
||||
|
||||
Reference in new issue
Block a user