feat(sdk): f.human `to` forms (slack:#channel, slack:@user, github:@user, approver) — reported as humanWait.recipient, required by flowRequirements

Merged
KH
9d ago
+421-12

What

`f.human(question, { to })` now has a defined delivery contract. `to` is one of four forms, parsed by `parseHumanRecipient` (`packages/sdk/src/human-to.ts`) and reported as `humanWait.recipient = { provider, kind, target }` beside the raw string on the parked report and on `flows answer`:

`to`Cloud deliverswho may answer
`"slack:#eng"`a message in that channelanyone in the channel
`"slack:@khaliq"`a DM to that Slack userthat user
`"github:@khaliq"`a comment on the triggering issue / PR, mentioning themthat user
`"khaliq"`the deploy's approver, on the channel the run was triggered fromthat user

`flowRequirements` reads a literal `to` from each `f.human` call and requires its provider (`slack (f.human to)` / `github (f.human to)`), so `flows check` prints it and `flows deploy` / the wizard require it connected (#465 / cloud #3788). A bare approver or a computed `to` (`input.approver`) adds nothing — Cloud resolves those at park time against the run's own trigger.

`flows answer` is unchanged. docs/SURFACE.md §5 documents the forms and how the person answers where they were asked (thread reply / ✅ ❌ / `@relay yes|no`).

The Cloud half (delivery on park, answer ingestion from Slack/GitHub, resume) is cloud PR stacked on #3789.

Tests

  • `tests/human-to.test.ts`: all four forms, provider derivation, requirements from literal `to` (once per provider, ordered after helpers), nothing for approver/computed.
  • `tests/human-live.test.ts`: the real-daemon park now asserts `recipient`.
  • `flow-requirements`, `cloud-connect` suites green; `tsc` src + tests clean.

Closes #468.

🤖 Generated with Claude Code


[!NOTE]
Medium Risk
Changes human-gate execution and deploy-time integration detection; malformed to now fails fast at runtime, and literal slack:/github: recipients can block deploy until connected.

Overview
f.human now treats to as a fixed delivery contract: slack:#channel, slack:@user, github:@user, or a bare approver handle. A new human-to parser validates at call time and rejects bad values with human_to_invalid before any ordinal or journal write, so runs cannot park on undeliverable questions.

Parked runs and flows answer still expose the raw to string but also add humanWait.recipient ({ provider, kind, target }). flowRequirements statically scans each f.human call for a literal to and surfaces slack (f.human to) / github (f.human to) for check/deploy; bare approvers and computed to values add no integration requirement. docs/SURFACE.md §5 is updated with the table, answer channels, and deploy/connect expectations.

Reviewed by Cursor Bugbot for commit 5625927d8c80631727216a9aeb8de7bff9ce8f1b. Bugbot is set up for automated code reviews on this repo. Configure here.


Summary by cubic

Defines the delivery contract for f.human(question, { to }). The to value has four fixed forms parsed into humanWait.recipient, and a literal slack: or github: to now requires that integration at deploy time.

  • Accepts slack:#channel, slack:@user, github:@user, or a bare approver name; malformed forms are refused at the call as human_to_invalid before anything is journaled.
  • parseHumanRecipient reports { provider, kind, target } next to the raw string on parked runs and flows answer.
  • flows check prints the required provider as slack (f.human to) / github (f.human to), and deploy requires it connected; the scan reads only each call's own literal to (ignoring strings, comments, and nested objects), so a computed to (like input.approver) adds no requirement.
  • docs/SURFACE.md documents the forms, who may answer, and how.

Closes #468.


Agent Relay sessions

  • claude session a4df5178 · opened via gh pr create · last active 2026-09-18