feat: ask_user follow-ups — harness convergence, channel finalization + reactions, native cards, docked web panel#110372
Conversation
… stream summaries
# Conflicts: # docs/.generated/plugin-sdk-api-baseline.sha256
# Conflicts: # ui/src/pages/chat/chat-view.ts # ui/src/pages/chat/components/chat-composer.ts # ui/src/pages/chat/components/chat-question-card.test.ts # ui/src/pages/chat/components/chat-question-card.ts
# Conflicts: # docs/.generated/plugin-sdk-api-baseline.sha256
…t delivery, non-blocking question refresh
…tions, reserve-before-request
…; reset terminal retention on replayed pending
…pe finalization labels
# Conflicts: # apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatTransport.swift # apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatView.swift # docs/.generated/plugin-sdk-api-baseline.sha256 # docs/platforms/mac/webchat.md # scripts/plugin-sdk-surface-report.mjs
|
Codex review: needs real behavior proof before merge. Reviewed July 18, 2026, 3:12 AM ET / 07:12 UTC. Summary Reproducibility: not applicable. as an issue reproduction: this is a feature PR, but source-level review establishes two concrete upgrade and reconciliation failures in its proposed implementation. Review metrics: 1 noteworthy metric.
Stored data model Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Rank-up moves:
Proof guidance:
Mantis proof suggestion Risk before merge
Maintainer options:
Next step before merge
Maintainer decision needed
Security Review findings
Review detailsBest possible solution: Preserve the new unified question runtime, but add an explicit, user-visible reauthorization or migration path for stored operator tokens, reconcile missing records through Do we have a high-confidence way to reproduce the issue? Not applicable as an issue reproduction: this is a feature PR, but source-level review establishes two concrete upgrade and reconciliation failures in its proposed implementation. Is this the best way to solve the issue? No. The unified gateway question model is the right direction, but the branch needs an explicit stored-token upgrade contract and terminal-record recovery rather than relying on fresh pairing and pending-only refreshes. Full review comments:
Overall correctness: patch is incorrect AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against cad4e395d21f. Label changesLabel justifications:
Evidence reviewedSecurity concerns:
What I checked:
Likely related people:
What the crustacean ranks mean
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
Review history (4 earlier review cycles)
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 03b0a95ff0
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| prompt.status === "pending" && | ||
| prompt.sessionKey !== undefined && | ||
| areUiSessionKeysEquivalent(prompt.sessionKey, props.sessionKey), |
There was a problem hiding this comment.
Scope docked questions by agent
When a pending question is registered for a global session, the record carries both sessionKey: "global" and its owning agentId, but this filter only checks the session key. In a global-scope multi-agent UI, switching/splitting the global chat to another agent still renders the first agent's docked prompt, and submitting it calls question.resolve by id only, so the answer can be delivered to the wrong run; include the prompt agent check against currentAgentId here.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f88e093191
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| val existing = current.associateBy { it.record.id } | ||
| val nowMs = System.currentTimeMillis() | ||
| val listedIds = records.mapTo(mutableSetOf()) { it.id } | ||
| val retainedTerminal = current.filter { it.record.id !in listedIds && it.shouldRetainAfterList(nowMs) } |
There was a problem hiding this comment.
Fetch missing question records before dropping cards
When this client misses question.resolved during reconnect/seqGap, this reconciliation drops any pending card that is absent from question.list; the Gateway list only returns pending records (src/gateway/question-manager.ts:140-145) while the terminal record remains available via question.get for the 15s cleanup grace (src/gateway/question-manager.ts:321-325). The web path already probes missing pending IDs before removal (ui/src/app/question-prompt.ts:434-480), but Android here (and the Swift mirror in apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatQuestionCard.swift:363-377) removes the card, so “answered elsewhere”, cancelled, or expired state never renders after a missed event.
Useful? React with 👍 / 👎.
| // sessions.patch (model switching); stored tokens keep their granted scopes. | ||
| "operator.admin", | ||
| "operator.approvals", | ||
| "operator.questions", |
There was a problem hiding this comment.
Migrate existing native tokens to question scope
Adding operator.questions here only helps fresh Android connects; existing paired installs reconnect through operatorConnectScopesForAuth, which returns operatorScopesForStoredDeviceToken(storedOperatorScopes) and preserves the recorded scopes unchanged (apps/android/app/src/main/java/ai/openclaw/app/NodeRuntime.kt:7548-7555, ConnectionManager.kt:55-61). Users paired before this scope existed will keep connecting without operator.questions, question.list is swallowed as unavailable, and native question cards never appear; the iOS device-token path has the same stored-scope bypass in GatewayChannel.swift:826-831, so this needs a stored-token upgrade/migration path.
Useful? React with 👍 / 👎.
# Conflicts: # ui/src/pages/chat/components/chat-composer.ts
… + reactions, native cards, docked web panel (openclaw#110372) * feat(ask-user): follow-up harness slice * feat(ask-user): follow-up channels slice * feat(ask-user): follow-up native slice * feat(ui): dock question panel above composer with stepper and compact stream summaries * docs: refresh follow-up integration maps * test(ui): align terminal summary proof * fix(infra): echo declared option answers in terminal status when free-text is allowed * fix(infra): keep reaction answering when display labels are formatter-adjusted * fix(agents): settle plain-text claims only after question registration commits * fix(agents,apps): commit-ordered claim persistence, claim-aware prompt delivery, non-blocking question refresh * fix(harness,infra): reaction-appropriate question copy, caller presentations honored * fix(native,infra,agents): local-expiry eviction, value-addressed reactions, reserve-before-request * fix(infra,android): dual-mode question resolver for compact callbacks; reset terminal retention on replayed pending * fix(harness,discord): claim-aware prompt delivery in run helper; escape finalization labels * fix(macos): merge transient-content visibility with question cards after main sync * fix: repair ask user follow-up CI * test: update limited bootstrap scope expectation * fix: retain shared question card API * chore: refresh native i18n inventory
Related: #109922 (base ask_user feature; this PR is the complete follow-up set named there)
What Problem This Solves
The
ask_userquestion system shipped in #109922 with four named gaps: Codex/Copilot harness questions still used the legacy text-only path, channel buttons stayed live after resolution, reaction-only channels (WhatsApp/Signal/iMessage) had no tap answering, and the native apps ignored question events. Separately, the web card sat inside the message stream, replayed after answering, and lacked the stepper interaction of its reference design.Why This Change Was Made
Four parallel slices, integrated into one branch:
request_user_inputand CopilotonUserInputRequestnow register gateway questions (shared runnersrc/agents/harness/gateway-question.tsexposed viaopenclaw/plugin-sdk/agent-harness-runtime), gaining the same web card, channel buttons, and plain-text claim asask_user. The feat(codex): surface native questions and goals #109724 token/command answer path (/codex answer,user-input-actions.ts, streamQuestionStatus, composer command submission) is deleted — one canonical model (−525 lines). Secret questions stay on the warned text path and never register.Answered: <labels>with Discord markdown-inert escaping /Expired/Cancelled) via a core finalization runtime with per-channel message-reference capture. WhatsApp/Signal/iMessage attach 1️⃣–4️⃣ reactions to eligible prompts (single-select, non-secret, ≤4 options); a tap resolves the gateway question by stored option value (robust to caller-reordered presentations); stale taps are consumed quietly.question.list,operator.questionsscope on fresh pairings. The four protocol-event-coverage allowlist entries are removed — the gate now enforces real handlers.1/2chip, Back/Next), options carry numeric keyboard shortcuts, free-text Other and Skip (= cancel, agent proceeds) are always available, and the panel can collapse to a slim bar. After resolution the panel disappears; the stream keeps only a compactHeader: answerssummary — the full card never replays.Codex upstream contract checked directly at sibling
../codex(codex-rs/app-server-protocol/src/protocol/v2/item.rs:1585,codex-rs/app-server/src/bespoke_event_handling.rs:703,:1602,codex-rs/protocol/src/request_user_input.rs:8,codex-rs/app-server/tests/suite/v2/request_user_input.rs:112). Copilot SDK 1.0.5 contract checked in@github/copilot-sdktypes/session source. MCP elicitation remains deliberately excluded until the 2026-07-28 MRTR spec revision finalizes.User Impact
Harness-driven questions look and answer exactly like native
ask_userones on every surface. Channel questions clean up after themselves instead of leaving stale buttons. WhatsApp/Signal/iMessage users answer with a reaction tap. iPhone, Mac, and Android app users get native question cards. On the web, questions feel like part of the composer:Docked panel above the composer (stepper, numbered options, Skip):




Answered → panel gone, compact summary in stream:
MultiSelect step:
Stepper + terminal states (expired / answered elsewhere):
Evidence
--devgateway,anthropic/claude-opus-4-8): agent calledask_userwith a single-select + multiSelect pair; the docked panel rendered above the composer, auto-advanced1/2 → 2/2on selection, took Integration+E2E toggles, and on Submit the panel disappeared, compactDeploy env: Staging (Recommended)/Smoke tests: Integration, E2Esummaries appeared in the stream, and the agent resumed with exactly those answers.tbx_01kxs9k5gkqtc7ypcb9ayn9frs+ local): UI chat suite 4,385 tests; core question runtime, harness bridges, and all six channel question suites green; Playwright question-flow e2e 3/3 with regenerated screenshots (above); OpenClawKit build + 913 Swift tests;pnpm ios:buildBUILD SUCCEEDED; Android unit tests + assemble + lint; protocol coverage 40/40; six tsgo lanes; i18n verify (en.ts only); SDK surface budgets; fullpnpm buildwith no ineffective-dynamic-import findings.nativeStringcomposable claim (it is the non-composable accessor; Testbox assemble green), single-select "deselect" claim (code always stores the label), and the buffered-claim acknowledgment ordering (documented tradeoff: the question stays live for re-answer on every surface). The generated Kotlin protocol file was excluded from review bundles via a temp commit because the reviewer's secret scanner false-positives on the schema's booleanisSecretfield (scanner fix filed separately); the file is generator-owned and compiled/tested on every platform.Follow-ups intentionally deferred: MCP elicitation routing (post-2026-07-28 spec), reaction answering beyond 4 options, per-option preview content.