From 246e1b38bf6d716f058d899689b0f47252041267 Mon Sep 17 00:00:00 2001 From: Dotta <34892728+cryppadotta@users.noreply.github.com> Date: Thu, 2 Jul 2026 18:39:16 -0500 Subject: [PATCH] [codex] Include checkbox selections in continuation wakes (#8893) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Issue-thread interactions are the subsystem that lets board users answer structured prompts and resume agent work > - Checkbox confirmations capture a selected subset of known options, then wake the assignee through continuation context > - The wake context previously carried generic interaction metadata, but not the accepted checkbox option ids or option labels > - That meant the resumed agent could be woken after a checkbox confirmation without seeing the board's selected options in the turn context > - This pull request carries accepted checkbox selections through the interaction continuation wake snapshot and renders them into the adapter wake prompt > - The benefit is that agents can act on checkbox-confirmation selections without refetching or guessing the user's choices ## Linked Issues or Issue Description No public GitHub issue exists for this bug. Searched public issues and PRs for checkbox confirmation / continuation selection duplicates and found no matching issue or PR. ### Pre-submission checklist - [x] I have searched existing open and closed issues and this is not a duplicate. - [x] I am on the latest released version of Paperclip or can reproduce on `master`. - [x] I have confirmed the error originates in Paperclip itself, not in my agent adapter, API provider, or local configuration. ### What happened? When a board user accepted a `request_checkbox_confirmation` interaction, the assignee continuation wake included generic interaction metadata but did not include the accepted checkbox selections. The resumed agent turn therefore had no in-prompt access to the selected option ids or option labels/descriptions. ### Expected behavior When a `request_checkbox_confirmation` interaction is accepted, the resumed agent wake should include the checkbox prompt, selected option ids, and selected option labels/descriptions so the agent can act on the selected subset directly. ### Steps to reproduce 1. Create an issue-thread `request_checkbox_confirmation` interaction with multiple options and `continuationPolicy: "wake_assignee"`. 2. Accept the interaction with one or more selected options. 3. Inspect the continuation wake payload/prompt received by the assignee. 4. Observe that the selected checkbox options are missing from the wake context before this fix. ### Paperclip version or commit Reproduced against the pre-fix code path on `master`; this PR head is `9d17e70bce373e4850117f30c015c973c4b61789`. ### Deployment mode Local dev (pnpm dev) / built from source. ### Installation method Built from source (pnpm dev / pnpm build). ### Agent adapter(s) involved Not adapter-specific (core bug). The Codex/local adapter path exposed the missing wake context, but the missing field was in core interaction continuation payload construction. ### Database mode Embedded PGlite or external Postgres; the bug is not database-mode specific. ### Access context Both. Board users resolve the checkbox interaction, and agent bearer-key wakes consume the continuation context. ### Node.js version `v22.22.2` ### Operating system Linux workspace. ### Relevant logs or output No runtime exception is required to reproduce this. The failure mode is missing `checkboxSelection` data in the resolved interaction continuation wake payload. ### Relevant config Not config-related. ### Additional context Root cause: accepted checkbox interaction results were not extracted into the continuation wake context, and adapter wake payload normalization/rendering had no typed `checkboxSelection` field. ### Privacy checklist - [x] I have reviewed all pasted output for PII (usernames, file paths, API keys, tokens, company names) and redacted where necessary. ## What Changed - Added checkbox selection extraction for accepted `request_checkbox_confirmation` interactions and stored it in interaction continuation wake context. - Included checkbox selection context in heartbeat wake payload construction. - Added adapter-utils normalization and wake prompt rendering for checkbox prompt, selected ids, and selected option details. - Added regression coverage for route continuation context, heartbeat payload summaries, and adapter wake prompt rendering. ## Verification - `pnpm exec vitest run packages/adapter-utils/src/server-utils.test.ts server/src/__tests__/heartbeat-context-summary.test.ts server/src/__tests__/issue-thread-interaction-routes.test.ts` - `git diff --check origin/master...HEAD` - `rg -n "checkbox|confirmation|interaction|wake|continuation" ROADMAP.md` - `gh pr list --state all --search "checkbox continuation selection repo:paperclipai/paperclip" --json number,title,state,url,headRefName --limit 20` - `gh issue list --state all --search "checkbox confirmation options repo:paperclipai/paperclip" --json number,title,state,url --limit 20` ## Risks Low risk. The new payload field is additive, only populated for accepted checkbox confirmations, and existing continuation fields are preserved. The main compatibility risk is downstream code assuming an exact wake payload shape; adapter normalization treats the new field as optional. > For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and discuss it in `#dev` before opening the PR. Feature PRs that overlap with planned core work may need to be redirected — check the roadmap first. See `CONTRIBUTING.md`. ## Model Used OpenAI Codex coding agent based on GPT-5, with shell/tool execution in this workspace. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Paperclip --- .../adapter-utils/src/server-utils.test.ts | 76 +++++++++++++++++++ packages/adapter-utils/src/server-utils.ts | 66 +++++++++++++++- .../heartbeat-context-summary.test.ts | 18 ++++- .../issue-thread-interaction-routes.test.ts | 74 +++++++++++++++++- server/src/routes/issues.ts | 38 ++++++++++ server/src/services/heartbeat.ts | 3 + 6 files changed, 272 insertions(+), 3 deletions(-) diff --git a/packages/adapter-utils/src/server-utils.test.ts b/packages/adapter-utils/src/server-utils.test.ts index 45b335f795..03c81933fd 100644 --- a/packages/adapter-utils/src/server-utils.test.ts +++ b/packages/adapter-utils/src/server-utils.test.ts @@ -643,6 +643,82 @@ describe("renderPaperclipWakePrompt", () => { expect(prompt).toContain("named unblock owner/action"); }); + it("renders resolved checkbox selections in scoped wake prompts", () => { + const payload = { + reason: "issue_commented", + issue: { + id: "issue-1", + identifier: "PAP-1581", + title: "Delete selected files", + status: "in_progress", + }, + interactionKind: "request_checkbox_confirmation", + interactionStatus: "accepted", + checkboxSelection: { + prompt: "Delete selected files?", + selectedOptionIds: ["file-b"], + selectedOptions: [{ id: "file-b", label: "b.txt", description: "Generated build output" }], + }, + commentWindow: { + requestedCount: 0, + includedCount: 0, + missingCount: 0, + }, + comments: [], + fallbackFetchNeeded: false, + }; + + const prompt = renderPaperclipWakePrompt(payload); + expect(prompt).toContain("- checkbox prompt: Delete selected files?"); + expect(prompt).toContain("- checkbox selection ids: file-b"); + expect(prompt).toContain("- checkbox selection options: file-b (b.txt) - Generated build output"); + expect(JSON.parse(stringifyPaperclipWakePayload(payload) ?? "{}")).toMatchObject({ + checkboxSelection: { + prompt: "Delete selected files?", + selectedOptionIds: ["file-b"], + selectedOptions: [{ id: "file-b", label: "b.txt", description: "Generated build output" }], + }, + }); + }); + + it("renders accepted empty checkbox selections explicitly", () => { + const payload = { + reason: "issue_commented", + issue: { + id: "issue-1", + identifier: "PAP-1581", + title: "Delete selected files", + status: "in_progress", + }, + interactionKind: "request_checkbox_confirmation", + interactionStatus: "accepted", + checkboxSelection: { + prompt: "Delete selected files?", + selectedOptionIds: [], + selectedOptions: [], + }, + commentWindow: { + requestedCount: 0, + includedCount: 0, + missingCount: 0, + }, + comments: [], + fallbackFetchNeeded: false, + }; + + const prompt = renderPaperclipWakePrompt(payload); + expect(prompt).toContain("- checkbox prompt: Delete selected files?"); + expect(prompt).toContain("- checkbox selection ids: (none)"); + expect(prompt).toContain("- checkbox selection options: (none)"); + expect(JSON.parse(stringifyPaperclipWakePayload(payload) ?? "{}")).toMatchObject({ + checkboxSelection: { + prompt: "Delete selected files?", + selectedOptionIds: [], + selectedOptions: [], + }, + }); + }); + it("preserves Chinese, Japanese, and Hindi issue and comment text in scoped wake prompts", () => { const title = "验证中文任务"; const commentBody = [ diff --git a/packages/adapter-utils/src/server-utils.ts b/packages/adapter-utils/src/server-utils.ts index 8261abdf36..b6ee56659c 100644 --- a/packages/adapter-utils/src/server-utils.ts +++ b/packages/adapter-utils/src/server-utils.ts @@ -581,6 +581,16 @@ type PaperclipWakeTreeHoldSummary = { reason: string | null; }; +type PaperclipWakeCheckboxSelection = { + prompt: string | null; + selectedOptionIds: string[]; + selectedOptions: Array<{ + id: string; + label: string; + description: string | null; + }>; +}; + type PaperclipWakePayload = { reason: string | null; issue: PaperclipWakeIssue | null; @@ -597,6 +607,7 @@ type PaperclipWakePayload = { taskWatchdog: PaperclipWakeTaskWatchdogContext | null; interactionKind: string | null; interactionStatus: string | null; + checkboxSelection: PaperclipWakeCheckboxSelection | null; annotationDeltas: PaperclipWakeAnnotationDelta[]; childIssueSummaries: PaperclipWakeChildIssueSummary[]; childIssueSummaryTruncated: boolean; @@ -929,6 +940,42 @@ function normalizePaperclipWakeTreeHoldSummary(value: unknown): PaperclipWakeTre return { holdId, rootIssueId, mode, reason }; } +function normalizePaperclipWakeCheckboxSelection(value: unknown): PaperclipWakeCheckboxSelection | null { + const selection = parseObject(value); + const hasExplicitSelection = + Object.prototype.hasOwnProperty.call(selection, "prompt") || + Object.prototype.hasOwnProperty.call(selection, "selectedOptionIds") || + Object.prototype.hasOwnProperty.call(selection, "selectedOptions"); + const prompt = asString(selection.prompt, "").trim() || null; + const selectedOptionIds = Array.isArray(selection.selectedOptionIds) + ? selection.selectedOptionIds + .map((entry) => asString(entry, "").trim()) + .filter(Boolean) + : []; + const selectedOptions = Array.isArray(selection.selectedOptions) + ? selection.selectedOptions + .map((entry) => { + const option = parseObject(entry); + const id = asString(option.id, "").trim(); + if (!id) return null; + return { + id, + label: asString(option.label, id).trim() || id, + description: asString(option.description, "").trim() || null, + }; + }) + .filter((entry): entry is { id: string; label: string; description: string | null } => Boolean(entry)) + : []; + + if (!hasExplicitSelection && selectedOptionIds.length === 0 && selectedOptions.length === 0 && !prompt) return null; + const optionById = new Map(selectedOptions.map((option) => [option.id, option])); + return { + prompt, + selectedOptionIds, + selectedOptions: selectedOptionIds.map((id) => optionById.get(id) ?? { id, label: id, description: null }), + }; +} + function normalizePaperclipWakeExecutionPrincipal(value: unknown): PaperclipWakeExecutionPrincipal | null { const principal = parseObject(value); const typeRaw = asString(principal.type, "").trim().toLowerCase(); @@ -1113,7 +1160,8 @@ export function normalizePaperclipWakePayload(value: unknown): PaperclipWakePayl : []; const activeTreeHold = normalizePaperclipWakeTreeHoldSummary(payload.activeTreeHold); - if (comments.length === 0 && commentIds.length === 0 && annotationDeltas.length === 0 && childIssueSummaries.length === 0 && unresolvedBlockerIssueIds.length === 0 && unresolvedBlockerSummaries.length === 0 && !activeTreeHold && !executionStage && !continuationSummary && !planReviewContext && !livenessContinuation && !taskWatchdog && !normalizePaperclipWakeIssue(payload.issue)) { + const checkboxSelection = normalizePaperclipWakeCheckboxSelection(payload.checkboxSelection); + if (comments.length === 0 && commentIds.length === 0 && annotationDeltas.length === 0 && childIssueSummaries.length === 0 && unresolvedBlockerIssueIds.length === 0 && unresolvedBlockerSummaries.length === 0 && !activeTreeHold && !executionStage && !continuationSummary && !planReviewContext && !livenessContinuation && !taskWatchdog && !checkboxSelection && !normalizePaperclipWakeIssue(payload.issue)) { return null; } @@ -1134,6 +1182,7 @@ export function normalizePaperclipWakePayload(value: unknown): PaperclipWakePayl taskWatchdog, interactionKind: asString(payload.interactionKind, "").trim() || null, interactionStatus: asString(payload.interactionStatus, "").trim() || null, + checkboxSelection, childIssueSummaries, childIssueSummaryTruncated: asBoolean(payload.childIssueSummaryTruncated, false), commentIds, @@ -1239,6 +1288,21 @@ export function renderPaperclipWakePrompt( if (normalized.issue?.priority) { lines.push(`- issue priority: ${normalized.issue.priority}`); } + if (normalized.checkboxSelection) { + if (normalized.checkboxSelection.prompt) { + lines.push(`- checkbox prompt: ${normalized.checkboxSelection.prompt}`); + } + const selectedOptionIds = normalized.checkboxSelection.selectedOptionIds.join(", ") || "(none)"; + const selectedOptions = normalized.checkboxSelection.selectedOptions + .map((option) => { + const label = option.label && option.label !== option.id ? ` (${option.label})` : ""; + const description = option.description ? ` - ${option.description}` : ""; + return `${option.id}${label}${description}`; + }) + .join(", ") || "(none)"; + lines.push(`- checkbox selection ids: ${selectedOptionIds}`); + lines.push(`- checkbox selection options: ${selectedOptions}`); + } if (normalized.issue?.workMode === "planning" && !normalized.taskWatchdog) { const hasWakeComments = normalized.comments.length > 0; const acceptedPlanContinuation = diff --git a/server/src/__tests__/heartbeat-context-summary.test.ts b/server/src/__tests__/heartbeat-context-summary.test.ts index 567317eabb..4463640d97 100644 --- a/server/src/__tests__/heartbeat-context-summary.test.ts +++ b/server/src/__tests__/heartbeat-context-summary.test.ts @@ -123,6 +123,11 @@ describe("mergeCoalescedContextSnapshot", () => { interactionKind: "request_confirmation", interactionStatus: "accepted", continuationPolicy: "wake_assignee_on_accept", + checkboxSelection: { + prompt: "Delete selected files?", + selectedOptionIds: ["file-b"], + selectedOptions: [{ id: "file-b", label: "b.txt", description: "Generated build output" }], + }, wakeReason: "issue_commented", }, { @@ -137,11 +142,12 @@ describe("mergeCoalescedContextSnapshot", () => { expect(merged.interactionKind).toBeUndefined(); expect(merged.interactionStatus).toBeUndefined(); expect(merged.continuationPolicy).toBeUndefined(); + expect(merged.checkboxSelection).toBeUndefined(); expect(merged.commentId).toBe("comment-1"); expect(merged.wakeCommentId).toBe("comment-1"); }); - it("preserves accepted-plan interaction state for the interaction wake itself", () => { + it("preserves resolved interaction state for the interaction wake itself", () => { const merged = mergeCoalescedContextSnapshot( { issueId: "issue-1", @@ -152,6 +158,11 @@ describe("mergeCoalescedContextSnapshot", () => { interactionKind: "request_confirmation", interactionStatus: "accepted", continuationPolicy: "wake_assignee_on_accept", + checkboxSelection: { + prompt: "Delete selected files?", + selectedOptionIds: ["file-b"], + selectedOptions: [{ id: "file-b", label: "b.txt", description: "Generated build output" }], + }, wakeReason: "issue_commented", }, ); @@ -160,6 +171,11 @@ describe("mergeCoalescedContextSnapshot", () => { expect(merged.interactionKind).toBe("request_confirmation"); expect(merged.interactionStatus).toBe("accepted"); expect(merged.continuationPolicy).toBe("wake_assignee_on_accept"); + expect(merged.checkboxSelection).toEqual({ + prompt: "Delete selected files?", + selectedOptionIds: ["file-b"], + selectedOptions: [{ id: "file-b", label: "b.txt", description: "Generated build output" }], + }); }); }); diff --git a/server/src/__tests__/issue-thread-interaction-routes.test.ts b/server/src/__tests__/issue-thread-interaction-routes.test.ts index d3574f7903..0c08bcb0d2 100644 --- a/server/src/__tests__/issue-thread-interaction-routes.test.ts +++ b/server/src/__tests__/issue-thread-interaction-routes.test.ts @@ -566,7 +566,7 @@ describe.sequential("issue thread interaction routes", () => { prompt: "Delete selected files?", options: [ { id: "file-a", label: "a.txt" }, - { id: "file-b", label: "b.txt" }, + { id: "file-b", label: "b.txt", description: "Generated build output" }, ], }, result: { @@ -602,6 +602,18 @@ describe.sequential("issue thread interaction routes", () => { interactionId: "interaction-checkbox", interactionKind: "request_checkbox_confirmation", interactionStatus: "accepted", + checkboxSelection: { + prompt: "Delete selected files?", + selectedOptionIds: ["file-b"], + selectedOptions: [{ id: "file-b", label: "b.txt", description: "Generated build output" }], + }, + }), + contextSnapshot: expect.objectContaining({ + checkboxSelection: { + prompt: "Delete selected files?", + selectedOptionIds: ["file-b"], + selectedOptions: [{ id: "file-b", label: "b.txt", description: "Generated build output" }], + }, }), }), ); @@ -617,6 +629,66 @@ describe.sequential("issue thread interaction routes", () => { ); }); + it("preserves accepted empty checkbox selections in assignee wake context", async () => { + mockInteractionService.acceptInteraction.mockResolvedValueOnce({ + interaction: { + id: "interaction-checkbox-empty", + companyId: "company-1", + issueId: "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + kind: "request_checkbox_confirmation", + status: "accepted", + continuationPolicy: "wake_assignee", + idempotencyKey: null, + sourceCommentId: null, + sourceRunId: "run-checkbox", + payload: { + version: 1, + prompt: "Delete selected files?", + options: [ + { id: "file-a", label: "a.txt", description: "Temporary export" }, + { id: "file-b", label: "b.txt", description: "Generated build output" }, + ], + }, + result: { + version: 1, + outcome: "accepted", + selectedOptionIds: [], + }, + createdAt: "2026-04-20T12:00:00.000Z", + updatedAt: "2026-04-20T12:05:00.000Z", + resolvedAt: "2026-04-20T12:05:00.000Z", + }, + createdIssues: [], + }); + const app = await createApp(); + + const res = await request(app) + .post("/api/issues/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/interactions/interaction-checkbox-empty/accept") + .send({ selectedOptionIds: [] }); + + expect(res.status).toBe(200); + expect(mockHeartbeatService.wakeup).toHaveBeenCalledTimes(1); + expect(mockHeartbeatService.wakeup).toHaveBeenCalledWith( + ASSIGNEE_AGENT_ID, + expect.objectContaining({ + payload: expect.objectContaining({ + checkboxSelection: { + prompt: "Delete selected files?", + selectedOptionIds: [], + selectedOptions: [], + }, + }), + contextSnapshot: expect.objectContaining({ + checkboxSelection: { + prompt: "Delete selected files?", + selectedOptionIds: [], + selectedOptions: [], + }, + }), + }), + ); + }); + it("forces a fresh workspace-aware session when accepting a planning confirmation", async () => { mockIssueService.getById.mockResolvedValueOnce(createIssue({ workMode: "planning" })); mockInteractionService.acceptInteraction.mockResolvedValueOnce({ diff --git a/server/src/routes/issues.ts b/server/src/routes/issues.ts index 4262972f14..cf9f37ae79 100644 --- a/server/src/routes/issues.ts +++ b/server/src/routes/issues.ts @@ -967,6 +967,7 @@ function queueResolvedInteractionContinuationWakeup(input: { const workspaceRefreshReason = readNonEmptyString(input.workspaceRefreshReason); const planTarget = readPlanConfirmationTargetForIssue(input.interaction.payload, input.issue.id); const interactionResult = readConfirmationResultForWake(input.interaction.result); + const checkboxSelection = readCheckboxSelectionForWake(input.interaction); const planReviewInteraction = planTarget && input.interaction.kind === "request_confirmation" ? { @@ -990,6 +991,7 @@ function queueResolvedInteractionContinuationWakeup(input: { sourceCommentId: input.interaction.sourceCommentId ?? null, sourceRunId: input.interaction.sourceRunId ?? null, ...(planReviewInteraction ? { planReviewInteraction } : {}), + ...(checkboxSelection ? { checkboxSelection } : {}), mutation: "interaction", }, requestedByActorType: input.actor.actorType, @@ -1003,6 +1005,7 @@ function queueResolvedInteractionContinuationWakeup(input: { sourceCommentId: input.interaction.sourceCommentId ?? null, sourceRunId: input.interaction.sourceRunId ?? null, ...(planReviewInteraction ? { planReviewInteraction } : {}), + ...(checkboxSelection ? { checkboxSelection } : {}), wakeReason: "issue_commented", source: input.source, ...(forceFreshSession ? { forceFreshSession: true } : {}), @@ -1016,6 +1019,41 @@ function queueResolvedInteractionContinuationWakeup(input: { }, "failed to wake assignee on issue interaction resolution")); } +function readCheckboxSelectionForWake(input: { + kind: string; + payload?: unknown; + result?: unknown; +}) { + if (input.kind !== "request_checkbox_confirmation") return null; + const result = readObject(input.result); + if (result.outcome !== "accepted") return null; + const selectedOptionIds = Array.isArray(result.selectedOptionIds) + ? result.selectedOptionIds.filter((value): value is string => typeof value === "string" && value.length > 0) + : []; + const payload = readObject(input.payload); + const options = Array.isArray(payload.options) + ? payload.options + .map((value) => { + const option = readObject(value); + const id = readNonEmptyString(option.id); + if (!id) return null; + return { + id, + label: readNonEmptyString(option.label) ?? id, + description: readNonEmptyString(option.description), + }; + }) + .filter((value): value is { id: string; label: string; description: string | null } => Boolean(value)) + : []; + const optionById = new Map(options.map((option) => [option.id, option])); + + return { + prompt: readNonEmptyString(payload.prompt), + selectedOptionIds, + selectedOptions: selectedOptionIds.map((id) => optionById.get(id) ?? { id, label: id, description: null }), + }; +} + function diffExecutionParticipants( previousPolicy: NormalizedExecutionPolicy | null, nextPolicy: NormalizedExecutionPolicy | null, diff --git a/server/src/services/heartbeat.ts b/server/src/services/heartbeat.ts index 70e5465494..67eb46f7bd 100644 --- a/server/src/services/heartbeat.ts +++ b/server/src/services/heartbeat.ts @@ -3545,6 +3545,7 @@ const INTERACTION_CONTINUATION_CONTEXT_KEYS = [ "interactionKind", "interactionStatus", "continuationPolicy", + "checkboxSelection", ] as const; function isInteractionResolutionWakePayload(payload: Record | null | undefined) { @@ -3844,6 +3845,7 @@ export async function buildPaperclipWakePayload(input: { const interactionId = readNonEmptyString(input.contextSnapshot.interactionId); const interactionKind = readNonEmptyString(input.contextSnapshot.interactionKind); const interactionStatus = readNonEmptyString(input.contextSnapshot.interactionStatus); + const checkboxSelection = parseObject(input.contextSnapshot.checkboxSelection); const planReviewContext = issueId ? await buildPlanReviewContext({ db: input.db, @@ -3888,6 +3890,7 @@ export async function buildPaperclipWakePayload(input: { : null, interactionKind, interactionStatus, + checkboxSelection: Object.keys(checkboxSelection).length > 0 ? checkboxSelection : null, checkedOutByHarness: input.contextSnapshot[PAPERCLIP_HARNESS_CHECKOUT_KEY] === true, dependencyBlockedInteraction: input.contextSnapshot.dependencyBlockedInteraction === true, treeHoldInteraction: input.contextSnapshot.treeHoldInteraction === true,