openclaw/src/interactive
Ayaan Zaidi efd65c9f8d
fix(channels): make system-agent approval cards actionable (#147152)
## What Problem This Solves

The OpenClaw Change approval card appears, but its buttons cannot approve. A late callback can overwrite the application result, and a Slack card can stay pending after its delivery receipt is lost.

## Fix and Impact

Accept the existing three approval kinds in Discord, Slack, and shared presentation validation. Native updates and callbacks share a per-message queue in each channel. Callbacks read current controls before editing, so old pending cards finish and final Applied, Not applied, or Denied cards survive.

Native delivery keeps its existing frozen runtime selection from #121673. Restart/list replay does not send a second card; an authorized operator can still resolve the original card. No schema, migration, config-key, protocol, dependency, or delivery-policy change.

Supersedes #137169 by @LiuwqGit. Contributor authorship is preserved. Reported by @anyech in #137142. Refs #134670.

## Consumers

The shared presentation normalizer also feeds Telegram and the fallback renderers for other channels. Telegram already accepts the system-agent callback kind. Its existing typed-action tests remain intact. Other renderers retain their existing unsupported-control and text-fallback behavior; this change does not claim native approval support for them.

## Documentation

No product documentation change is required. `docs/plugins/sdk-channel-plugins/approvals.md:104` already requires all three kinds and the final application result. The Slack codec unit test covers a direct encoder/decoder round trip; the registered-handler and real Gateway evidence below establish the complete approval flow.

## Evidence

- Main `37130969fa`: both emitted Discord buttons returned “This approval is no longer valid”; SQLite stayed pending and the resolver was not reached.
- Slack baseline `fc188ff0b943`: after native delivery and runtime stop, Allow Once and Deny persisted their decisions but left the original card pending with buttons.
- Narrowed candidate `b5e76a8386f03380a369dd5150f57b5e488cd64b`: three fresh real Slack handler → Gateway → application/SQLite → card cases passed. Actual stop/start/list replay selects no replacement and sends no second card. Original Allow Once and Deny clear the controls; the threaded case reads and finishes the exact original reply. Full captures: `runtime-narrowed-3.json`.
- Retained evidence: 21 non-replay Slack cases at `73fba5283a4b` cover both native/callback orders, Applied/Not applied/Denied, generic controls, stale winners, authority checks, and exec/plugin siblings. Source-aware review confirmed unchanged inputs. These are reused records, not new executions. Earlier replacement-delivery records are historical only. The retained 21 Discord Gateway scenarios cover the real dispatcher, approval resolver, application executor, SQLite results, and final cards against local HTTP responses.

The boundary uses a real bundled Gateway and native HTTP clients with isolated local HTTP and deterministic inference fixtures. Recorded harness commands (`SLACK_PROOF` is the retained evidence directory; start runs in a separate terminal):

```sh
node "$SLACK_PROOF/start.mjs" narrowed
node "$SLACK_PROOF/call.mjs" narrowed
node "$SLACK_PROOF/verify.mjs"
```

Fresh merge `f9ccdd51aa0d590adb72d35c83eb937bbe9fa13e` has direct parents candidate `b5e76a8386f03380a369dd5150f57b5e488cd64b` and main `adb6e518a4`. The affected approval lifecycle group passed 836 tests (2 skipped), full Slack passed 3,017, and the Discord native/registered-callback sibling passed 44. All three commands exited 0.

```sh
pnpm changed:lanes --json --base 37130969fa --head f9ccdd51aa0d590adb72d35c83eb937bbe9fa13e
node --import tsx scripts/ci-run-node-test-shard.mts
```

The retained plan and command/result receipts specify each CI config, include list, and environment. Local `pnpm exec oxlint --config .oxlintrc.json` passed for all 15 retained PR files plus both reverted files; `oxlint-final.json` records the exact command and exit. Unchanged suite evidence remains tied to its earlier merge `68930a5e67a2`, rather than being counted as a new run. CI run `34773959228` completed successfully at landing head `f08ae38f5237e35276bdf9bb2f50c68cbe4af31b`, including the previously failing type-aware jobs. The last commit changes only two Discord test fixtures; production and the accepted Gateway evidence are unchanged. Both affected test files also passed all 44 local tests.

Limit: no human click.

Co-authored-by: LiuwqGit <liu.weiqin@xydigit.com>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-09-14 00:31:30 +05:30
..
payload.fallback.test.ts
payload.test.ts
payload.ts fix(channels): make system-agent approval cards actionable (#147152) 2026-09-14 00:31:30 +05:30