Skip to content

feat(chat): interactive button contract + workflow trigger - #76

Merged
xergioalex merged 3 commits into
mainfrom
feat/interactive-buttons-workflow-trigger
Jul 24, 2026
Merged

feat(chat): interactive button contract + workflow trigger#76
xergioalex merged 3 commits into
mainfrom
feat/interactive-buttons-workflow-trigger

Conversation

@xergioalex

@xergioalex xergioalex commented Jul 23, 2026

Copy link
Copy Markdown
Member

Summary

  • Adopt the public API interactive-button contract v3 on dailybot chat send / update: --buttons JSON passthrough (no key stripping), approval-flow flags (--approve-button / --reject-button / --callback-url / --callback-bearer), and --workflow-button, with light client-side pre-validation (≤25 buttons, required label, mutual exclusivity of the five callbacks).
  • Add dailybot workflow trigger <uuid> [--payload] (POST /v1/workflows/{uuid}/trigger/) for api_trigger workflows, plus workflow list --filter api_trigger.
  • Map the new button_* / workflow_* error codes; chat --json now emits structured {error, status, code, detail} on failure. README + API_REFERENCE examples updated.

Companion skill-pack PR: DailybotHQ/agent-skill#43

Test plan

  • pytest tests/chat_commands_test.py tests/workflow_commands_test.py tests/api_client_test.py::TestDailyBotClientWorkflowTrigger tests/api_error_codes_alignment_test.py
  • dailybot chat send --help shows approval / workflow-button / --buttons examples
  • dailybot workflow trigger --help documents api_trigger + async 202
  • Smoke (against staging/local API): approval flow with --callback-url, --workflow-button, workflow trigger --payload '{"env":"prod"}' --json
  • Confirm --payload-json / unknown button keys still forward untouched

Expose the public API button v3 fields on chat send/update (JSON
passthrough, approval/workflow ergonomic flags, light pre-validation)
and add dailybot workflow trigger for api_trigger automations.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@xergioalex xergioalex added the Ready Trigger AI code review label Jul 24, 2026
@github-actions

This comment has been minimized.

github-actions[bot]

This comment was marked as outdated.

@github-actions github-actions Bot added the pr-reviewed Passed AI code review label Jul 24, 2026
## Summary
Resolve the five live review comments on #76 before re-running the gate.

## Change Log
- Annotate auth_hint / rate_msg locals (Rule 2)
- Code-less 401/403 now exit EXIT_NOT_AUTHENTICATED / EXIT_PERMISSION_DENIED
- Prefer env-var wording on --callback-bearer help
- Wire API_TRIGGER_TYPE into --filter help + empty-filter tip
- Measure trigger payload size with httpx-compatible JSON serialization

Co-authored-by: Cursor <cursoragent@cursor.com>
@xergioalex xergioalex added Ready Trigger AI code review and removed Ready Trigger AI code review labels Jul 24, 2026
@github-actions

github-actions Bot commented Jul 24, 2026

Copy link
Copy Markdown

AI review for b37882b — ✅ done

View review →

Highest severity: info

Strictness gate: ✅ highest severity info ≤ critical threshold

4 inline comment(s) attached.

Iteration-Aware Review: gen 2, round 1, policy=first-pass-exhaustive (new_commits) — 4 surfaced.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict

Solid, well-tested feature — no blocking issues. A few low-severity consistency/UX notes; nothing that should hold up merge.

The interactive-button contract v3 and workflow trigger land cleanly: callbacks stay thin (parse → resolve → client → display), every client.* call is wrapped in try/except APIError, error handling dispatches on the machine-readable code field (not detail), new error codes are mapped in ERROR_CODE_MESSAGES with an alignment test, all HTTP is mocked in tests, magic numbers are extracted to module-level constants, and user-facing strings consistently use "Dailybot". Credential hygiene is good — the --callback-bearer token is never echoed (success --json emits the server response, not the payload) and the help text steers users toward $TOKEN env indirection.

Findings table

# Severity File Summary
1 ℹ️ info dailybot_cli/commands/chat.py:354 Code-less 429 exits 1 while a coded 429 routes to exit_for_api_error (exit 6) — inconsistent with the 401/403 alignment done just above
2 ℹ️ info dailybot_cli/commands/workflow.py:206 Size-guard comment claims it matches httpx's json= serialization, but httpx uses compact separators=(",", ":") while this uses default (spaced) separators
3 ℹ️ info dailybot_cli/commands/workflow.py:131 --filter runs client-side on only the fetched page(s); the pagination footer still reports the unfiltered server count/next, which can mislead when --all isn't passed
4 ℹ️ info dailybot_cli/commands/chat.py:450 Praise — thoughtful credential-hygiene guidance on --callback-bearer

Cross-cutting notes

  • Exit-code change (intentional, tests updated): code-less 401/403 chat errors now exit 3/4 (via EXIT_NOT_AUTHENTICATED/EXIT_PERMISSION_DENIED) instead of the previous 1. This is a headless-contract change; it's consistent with exit_for_api_error and the updated tests assert it, so it looks deliberate — just calling it out for release notes.
  • Auth resolution order (AGENTS.md Rule 14) is untouched; trigger_workflow reuses _request/_handle_response exactly like get_workflow. No credential-file writes, no env.json changes, all HTTP mocked at httpx.*. Good.

Recommendation: approve

EXIT_NOT_AUTHENTICATED if e.status_code == 401 else EXIT_PERMISSION_DENIED
)
raise SystemExit(exit_code)
if e.status_code == 429 and e.code is None:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment above the 401/403 block says it matches exit_for_api_error's status→exit-code contract "whether or not the server sent code". This 429 branch breaks that symmetry: a code-less 429 exits 1, but a 429 that carries any code falls through to exit_for_api_error(...), which maps 429 → EXIT_RATE_LIMITED (6). So headless consumers get a different exit code for the same HTTP status depending on whether the backend attached a code.

Low impact (429s rarely carry a code), but if the goal is a stable code, consider using EXIT_RATE_LIMITED here too (and dropping the e.code is None guard) so plain and coded 429s agree.

Comment on lines +204 to +206
# Match httpx's json= serialization (default separators + ensure_ascii=False)
# so the local size guard measures the same bytes that hit the wire.
encoded: bytes = json.dumps(parsed, ensure_ascii=False).encode("utf-8")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment claims this measures "the same bytes that hit the wire" as httpx's json= serialization, but they don't quite match: httpx (0.28) serializes with compact separators=(",", ":"), whereas json.dumps(parsed, ensure_ascii=False) here uses the default spaced separators (", " / ": "). For a payload with many keys this local count is larger than the wire bytes, so a request just under 8 KiB on the wire could be falsely rejected near the boundary.

The server is authoritative (workflow_trigger_payload_invalid), so this is a heuristic nit, not a correctness bug — but to actually match the wire:

Suggested change
# Match httpx's json= serialization (default separators + ensure_ascii=False)
# so the local size guard measures the same bytes that hit the wire.
encoded: bytes = json.dumps(parsed, ensure_ascii=False).encode("utf-8")
encoded: bytes = json.dumps(parsed, ensure_ascii=False, separators=(",", ":")).encode("utf-8")

Comment on lines +127 to +131
if trigger_filter:
needle: str = trigger_filter.strip().lower()
# Compare against the same canonical value used by chat buttons /
# ``workflow trigger`` (API_TRIGGER_TYPE) and any other server type.
workflows = [wf for wf in workflows if str(wf.get("trigger_type", "")).lower() == needle]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

--filter is applied client-side to only the workflows already fetched. Without --all (or a page containing them) a matching api_trigger workflow on a later page is invisible, and the footer at print_pagination_footer(len(workflows), meta.get("count"), ...) still reports the unfiltered server count/next — so the output can read like "Showing 1 of 50, more available" after filtering, which is confusing.

The empty-result hint is a nice touch; consider also nudging toward --all in it (it only fires for api_trigger + empty today). Docs already recommend --all, so this is minor.

default=None,
help=(
"Optional bearer token for callback_auth on approval buttons. Prefer "
'passing via an env var (e.g. --callback-bearer "$TOKEN") — a raw '

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice — proactively warning that a raw --callback-bearer token lands in shell history and process lists, and steering toward $TOKEN env indirection, is exactly the right credential-hygiene posture for a public CLI (AGENTS.md Rule 11). The token is also correctly kept out of all output paths (success --json emits the server response, not the payload).

@xergioalex
xergioalex merged commit dc87849 into main Jul 24, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pr-reviewed Passed AI code review Ready Trigger AI code review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant