Skip to content

feat(poll): cast votes on native Messages polls#148

Merged
steipete merged 3 commits into
openclaw:mainfrom
omarshahine:pr/poll-vote
Jul 1, 2026
Merged

feat(poll): cast votes on native Messages polls#148
steipete merged 3 commits into
openclaw:mainfrom
omarshahine:pr/poll-vote

Conversation

@omarshahine

@omarshahine omarshahine commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

feat(poll): cast votes on native Messages polls

What & why

imsg could create and read native Apple Messages polls but not vote on them.
This adds imsg poll vote, the poll.vote RPC method, and the send-poll-vote
bridge action. Additive only — existing poll send / poll.send behavior and
its response are unchanged.

How it works

A vote is an associated message (associatedMessageType 4000, bare poll GUID)
that also carries the Polls balloon payload. macOS 26 exposes no IMMessage
initializer that carries a balloon payload AND an associated message together, so
the vote is built with the associated-message initializer (the path reactions use,
which persists the association) and balloonBundleID/payloadData are then stamped
onto the message's backing item — the one place they survive [chat sendMessage:].

Option selection resolves a 1-based --option-index, an --option text
(case-insensitive), or a direct --option-id UUID to the stable optionIdentifier
via a new MessageStore.pollOptions(guid:), which also validates the poll exists
and the option belongs to it. The CLI and the RPC response return the resolved
option id/text so callers can suppress a redundant reply that restates the vote.
Both paths normalize a p:<part>/<guid> reference down to the bare poll GUID.

Surface added

  • CLI: imsg poll vote --chat <c> --poll <guid> (--option-index N | --option TEXT | --option-id UUID)
  • RPC: poll.vote / messages.poll.vote (response includes option_id, option_text, poll_guid)
  • Bridge action: send-poll-vote
  • MessageStore.pollOptions(guid:)

Evidence — real behavior proof (macOS 26.4.1, redacted)

Live CLI vote from this branch's build (GUIDs truncated, chat/handles omitted):

$ imsg poll vote --chat <redacted> --poll 465DCEE3… --option-index 1 --json
{
  "messageGuid": "21265724…",
  "pollMessageGuid": "465DCEE3…",
  "optionIdentifier": "8675F837…",
  "optionText": "Red"
}

Decode readback of the sent vote:

decoded kind: vote | option_text: Red | associated_type: 4000

Multiple live votes, from chat.db (GUIDs truncated, handles omitted). Each vote is
an associated message (type 4000) whose assoc GUID is the originating poll's GUID,
delivered with no error:

row  6478   me VOTE(4000) guid=DF8089B9… assoc=465DCEE3… sent=1 delivered=1 err=0
row  6476 peer POLL       guid=465DCEE3… assoc=None     sent=0 delivered=1 err=0
row  6475   me VOTE(4000) guid=3D30828C… assoc=7AB94568… sent=1 delivered=1 err=0
row  6471 peer POLL       guid=7AB94568… assoc=None     sent=0 delivered=1 err=0
row  6470   me VOTE(4000) guid=B1563D7E… assoc=4C511F80… sent=1 delivered=1 err=0
row  6466 peer POLL       guid=4C511F80… assoc=None     sent=0 delivered=1 err=0
  • swift build — Build complete.
  • Votes by index, text, and UUID all land as associated_message_type=4000 linked
    to the poll, is_delivered=1 error=0, and decode back to the selected option.

Addressed from ClawSweeper/Codex review

  • [P1] Preserve poll-send semantics — removed the extra question-text send from
    handleSendPoll; this PR no longer changes poll send behavior (additive only).
  • [P2] Return the option from RPC votespoll.vote now returns option_id /
    option_text / poll_guid.
  • RPC poll validation — the RPC vote path now decodes the poll and rejects an
    unknown poll or an option_id not belonging to it, matching the CLI.

Codex review (independent, gpt-5.4)

Reviewed to READY over 3 rounds (non-bare GUID, --option-id validation, RPC
validation all resolved). The private-API "associated init then stamp balloon
fields" construction was validated as sound.

@clawsweeper

clawsweeper Bot commented Jul 1, 2026

Copy link
Copy Markdown

Codex review: needs maintainer review before merge. Reviewed July 1, 2026, 1:15 AM ET / 05:15 UTC.

Summary
The PR adds an imsg poll vote CLI action, poll.vote/messages.poll.vote RPC methods, a send-poll-vote bridge action, and a MessageStore helper for resolving poll options.

Reproducibility: not applicable. for a feature PR. The relevant verification is live macOS Messages delivery plus source review; the PR body now includes redacted CLI output, decode readback, and chat.db rows for delivered vote messages.

Review metrics: 3 noteworthy metrics.

  • Changed surface: 6 files, +408/-5. The patch crosses Swift CLI/RPC, core bridge protocol, MessageStore reads, and the injected Objective-C send path.
  • Public send surfaces: 1 CLI action, 2 RPC names, 1 bridge action added. These names become user-facing API once released and need maintainer acceptance.
  • Validation files: 0 tests, 0 docs changed. The PR body includes live proof, but the branch itself does not add regression coverage or public usage docs for the new action.

Merge readiness
Overall: 🐚 platinum hermit
Proof: 🐚 platinum hermit
Patch quality: 🐚 platinum hermit
Result: ready for maintainer review.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Rank-up moves:

  • none.

Mantis proof suggestion
A visible native Messages poll state recording would materially improve confidence in the private bridge send path beyond terminal/chat.db proof. A maintainer can ask Mantis to capture proof by posting this exact PR comment:

@openclaw-mantis visual task: verify imsg poll vote casts a native Messages poll vote and capture redacted CLI output plus the resulting Messages poll state.

Risk before merge

  • [P1] Native poll vote sending depends on private IMCore selectors and post-init payload stamping; the redacted live proof is useful, but maintainers may still want Mantis or maintainer-run macOS proof before accepting the new send primitive.
  • [P1] The PR adds public CLI/RPC/bridge API surface without repo tests or docs updates in this branch; that is not a line-level correctness bug, but it is an upgrade and support consideration before merge.
  • [P1] The companion OpenClaw PR at feat(imessage): native poll support — create, read, vote openclaw#98421 depends on this PR for end-to-end iMessage poll voting and remains a separate review item.

Maintainer options:

  1. Require maintainer macOS proof before merge (recommended)
    Have a maintainer or Mantis verify the final head on macOS 26 by casting a vote and reading back the associated type 4000 row and rendered poll state.
  2. Accept the contributor proof as sufficient
    Maintainers can choose to accept the redacted terminal/chat.db proof in the PR body and merge after normal CI and code review.
  3. Pause until downstream integration is settled
    If the core CLI should not add vote support until the downstream OpenClaw poll workflow is approved, keep this PR open but pause merge decisions with the companion PR linked.

Next step before merge

  • [P2] Manual review is appropriate because the remaining decision is maintainer acceptance and validation of a new private IMCore send primitive, not a narrow automated repair.

Security
Cleared: No security or supply-chain issue was found; the diff adds no dependency, workflow, secret, permission, or external download path.

Review details

Best possible solution:

Land the additive vote surface after maintainer review of the private bridge construction, with focused CLI/RPC/store tests or documented validation for option resolution and a clear proof trail for macOS Messages delivery.

Do we have a high-confidence way to reproduce the issue?

Not applicable for a feature PR. The relevant verification is live macOS Messages delivery plus source review; the PR body now includes redacted CLI output, decode readback, and chat.db rows for delivered vote messages.

Is this the best way to solve the issue?

Likely yes, if maintainers want this feature in core. The patch is additive, preserves current poll-send behavior, returns option metadata through RPC, and follows existing poll/reaction bridge patterns; the main remaining decision is accepting the private native send surface and its validation burden.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against 6a20d3a42cad.

Label changes

Label changes:

  • add proof: sufficient: Contributor real behavior proof is sufficient. The PR body includes redacted after-fix CLI output, decode readback, and chat.db rows showing delivered associated poll-vote rows linked to poll GUIDs.
  • add rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🐚 platinum hermit and patch quality is 🐚 platinum hermit.
  • add feature: ✨ showcase: ClawSweeper spotlight: unusually compelling feature idea for maintainer attention. Native poll voting completes the existing create/read poll workflow with a direct CLI and RPC action for approval flows.
  • add status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (terminal): The PR body includes redacted after-fix CLI output, decode readback, and chat.db rows showing delivered associated poll-vote rows linked to poll GUIDs.
  • remove status: 📣 needs proof: Current PR status label is status: 👀 ready for maintainer look.
  • remove merge-risk: 🚨 compatibility: Current PR review merge-risk labels are merge-risk: 🚨 message-delivery.
  • remove rating: 🧂 unranked krab: Current PR rating is rating: 🐚 platinum hermit, so this older rating label is no longer current.

Label justifications:

  • P2: This is a normal-priority feature PR with limited blast radius but real native bridge/API merge risk.
  • merge-risk: 🚨 message-delivery: The diff sends a new native Messages associated poll-vote payload through private IMCore selectors that CI cannot fully validate.
  • rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🐚 platinum hermit and patch quality is 🐚 platinum hermit.
  • feature: ✨ showcase: ClawSweeper spotlight: unusually compelling feature idea for maintainer attention. Native poll voting completes the existing create/read poll workflow with a direct CLI and RPC action for approval flows.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (terminal): The PR body includes redacted after-fix CLI output, decode readback, and chat.db rows showing delivered associated poll-vote rows linked to poll GUIDs.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body includes redacted after-fix CLI output, decode readback, and chat.db rows showing delivered associated poll-vote rows linked to poll GUIDs.
Evidence reviewed

What I checked:

  • Repository policy read: AGENTS.md was read fully; its focused Swift CLI, testing, and macOS permission guidance applies to this native bridge/CLI review. (AGENTS.md:1, 6a20d3a42cad)
  • Current main has poll vote decoding but no outgoing vote API: A current-main search found poll-vote event decoding/docs, but no poll.vote, messages.poll.vote, send-poll-vote, or poll vote command surface, so the PR is not already implemented on main. (Sources/IMsgCore/MessagePolls.swift:119, 6a20d3a42cad)
  • PR surface: The PR head changes 6 files with +408/-5 across bridge protocol, MessageStore poll lookup, injected Objective-C bridge send code, CLI, and RPC handlers. (2ca0fadd8453)
  • Poll-send behavior preserved in updated head: The updated head keeps handleSendPoll as a single poll-balloon send path and adds vote handling later in the injected helper, addressing the previous poll-send semantics finding. (Sources/IMsgHelper/IMsgInjected.m:2730, 2ca0fadd8453)
  • RPC vote response returns resolved option metadata: The updated RPC handler includes poll_guid, option_id, and option_text in the response after validating that the option belongs to the decoded poll. (Sources/imsg/RPCServer+BridgeMessageHandlers.swift:164, 2ca0fadd8453)
  • Real behavior proof present: The PR body now includes copied redacted CLI output, decode readback, and chat.db rows showing delivered associated type 4000 vote rows linked to poll GUIDs. (2ca0fadd8453)

Likely related people:

  • veteranbv: Introduced native poll send/readback support that this PR extends. (role: native poll feature introducer; confidence: high; commits: 0c9d85db44f0, 9a21cdbe9017, 2529aa7ad011; files: Sources/IMsgHelper/IMsgInjected.m, Sources/IMsgCore/MessagePolls.swift, Tests/IMsgCoreTests/MessagePollTests.swift)
  • steipete: Recently split poll command helpers, touched poll/RPC behavior, and prepared the release containing the current poll code. (role: recent poll/RPC area contributor; confidence: medium; commits: fed89d6b4f9d, 9db467fe5347, f97a116dc8df; files: Sources/imsg/Commands/PollCommand.swift, Sources/imsg/RPCServer+BridgeMessageHandlers.swift, Sources/IMsgHelper/IMsgInjected.m)
  • omarshahine: Prior merged history introduced and repaired private IMCore bridge paths that the vote sender extends, beyond authorship of this PR. (role: adjacent private bridge contributor; confidence: medium; commits: c56c24d488ef, 2d7b506d1736, 243226951fd8; files: Sources/IMsgHelper/IMsgInjected.m, Sources/IMsgCore/IMsgBridgeProtocol.swift, Sources/imsg/RPCServer.swift)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

@clawsweeper clawsweeper Bot added rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P2 Normal priority bug or improvement with limited blast radius. merge-risk: 🚨 compatibility 🚨 Merging this PR could break existing users, config, migrations, defaults, or upgrades. merge-risk: 🚨 message-delivery 🚨 Merging this PR could drop, duplicate, misroute, suppress, or wrongly target messages. labels Jul 1, 2026
Adds `imsg poll vote` plus RPC `poll.vote` and the `send-poll-vote`
bridge action, so the CLI can cast a vote on an existing native Messages
poll. The vote is an associated message (associatedMessageType 4000, bare
poll GUID) carrying the Polls balloon payload.

Key detail: macOS 26 exposes no IMMessage initializer that carries a
balloon payload AND an associated message together, so the vote is built
with the associated-message initializer (the path reactions use, which
persists the association) and the balloonBundleID/payloadData are then
stamped onto the message's backing item.

Option selection resolves a 1-based index or option text to the stable
optionIdentifier via a new `MessageStore.pollOptions(guid:)`, and the
resolved option label is returned so callers can suppress a redundant
text reply that merely restates the vote.
@omarshahine

omarshahine commented Jul 1, 2026

Copy link
Copy Markdown
Contributor Author

Addressed the review findings and added real behavior proof to the PR body:

  • [P1] poll-send semantics preserved — removed the extra question-text send from handleSendPoll; this PR is now additive (no change to poll send).
  • [P2] RPC returns the optionpoll.vote response now includes option_id, option_text, poll_guid.
  • RPC poll validation — the RPC path now decodes the poll and rejects unknown-poll / non-member option-id, matching the CLI.
  • Real behavior proof — redacted live CLI vote output + decode readback + chat.db rows showing votes as associated_message_type=4000 linked to their poll GUID, delivered=1 err=0.

@clawsweeper re-review please.

@clawsweeper clawsweeper Bot added proof: sufficient Contributor real behavior proof is sufficient. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. feature: ✨ showcase ClawSweeper spotlight: unusually compelling feature idea for maintainer attention. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. and removed rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. merge-risk: 🚨 compatibility 🚨 Merging this PR could break existing users, config, migrations, defaults, or upgrades. labels Jul 1, 2026
@steipete

steipete commented Jul 1, 2026

Copy link
Copy Markdown
Collaborator

Maintainer review and fixups complete on 4f93a4a.

Improvements:

  • validate exactly one CLI option selector and canonicalize RPC option text from the decoded poll;
  • derive the voter identity inside the injected bridge instead of accepting a caller override;
  • fail closed unless balloon id and payload land on the same native message object;
  • expose the exact pollVoteMessage selector separately from poll creation;
  • deduplicate poll option decoding; add CLI/RPC/bridge regression tests, README/history docs, and the maintainer changelog entry.

Proof:

  • make lint — completed; pre-existing warnings only, no serious violations.
  • make test — 356 tests passed.
  • make build — universal imsg and bridge helper built for arm64/x86_64.
  • swift test --filter 'injectedHelperWiresNativePollSend|injectedHelperWiresFailClosedNativePollVote' — 2 passed after the final selector split.
  • Codex autoreview (--mode local, default gpt-5.5) — clean after one accepted fix cycle; no actionable findings.
  • Direct macOS 26.5.2 IMCore runtime probe confirmed the 13-argument associated-message initializer and backing-item payload setters used by the vote path. The PR body's macOS 26.4.1 live proof covers real CLI votes, type-4000 association, decode readback, and delivered/error state; the native construction remains the same, with stricter failure handling.

Known proof gap: this maintainer Mac has SIP enabled, and the online MacBook has Remote Login disabled, so I could not repeat the injected Messages send locally. CI plus the contributor's real final-path delivery proof cover the merge decision.

@steipete
steipete merged commit 617d941 into openclaw:main Jul 1, 2026
2 checks passed
@omarshahine
omarshahine deleted the pr/poll-vote branch July 18, 2026 17:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature: ✨ showcase ClawSweeper spotlight: unusually compelling feature idea for maintainer attention. merge-risk: 🚨 message-delivery 🚨 Merging this PR could drop, duplicate, misroute, suppress, or wrongly target messages. P2 Normal priority bug or improvement with limited blast radius. proof: sufficient Contributor real behavior proof is sufficient. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants