Skip to content

feat(calls): add call consent commands - #29

Merged
purpshell merged 14 commits into
devfrom
t3code/calls-consent
Sep 23, 2026
Merged

purpshell merged 14 commits into
devfrom
t3code/calls-consent

Conversation

@purpshell

@purpshell purpshell commented Sep 20, 2026 •

Copy link
Copy Markdown
Member

Adds CLI commands for call consent and enrolled HMS history reads on the existing dev branch.

Call consent commands cover team policy, do-not-call entries, Cloud API call permission retrieval/request, and the pre-dial check. calls check returns exit 0 for a valid check even when allowed is false, so scripts must inspect allowed and refusal. Permission requests retain Retry-After in human and JSON errors. Imports use bounded 5,000-entry batches and report partial rejection positions.

History commands add chat list, chat view, chat message list, and chat message view. They preserve API cursors and accept an organization API key or an exact-project server token; Messaging client tokens are refused by the SDK. Help and README state the enrolled beta prerequisites.

This PR pins the exact published @polymorfa/sdk@0.1.0-dev.20260923162255 (lockfile integrity sha512-Z4MIheeT0qzM0TIywT6DvP5DMzDrMi1+ohU0/bqEdt7ar+4jHJjcNGfn3YkE6icluaLPSScO49tsvty00eMW8A==), built from merged SDK #284 at e05d882074ae680bb26698c3882825cafcd4a72a. The SDK contract source is merged API dev e72b51348e16e704f17b3e681ee60d02fcca8c7f, including HMS #202 and consent #226. Calls remains unreleased; history requires the messaging.history enrolled beta, scopes, HMS enablement, and regional read availability.

Verification

Clean Node 22 npm ci and npm run verify pass: formatting, lint, typecheck, 804 tests, build, package dry-run, and production audit with zero vulnerabilities. Tests use fixture HTTP responses; no real WhatsApp number, deployed CLI, or customer enrollment was exercised. Current-head CI and Codex review are pending.

Cross-surface assessment

Part Status
API Unchanged here. Consent routes and HMS history reads are in merged monorepo #226 and #202.
SDKs Updated in merged #284. CLI uses typed consent and history resources from the exact published prerelease.
CLI Updated. Commands, credential handling, paging, output, errors, help, and fixture tests cover both features.
Docs Updated. CLI README/help and command research; API public guides and references are in the merged monorepo. No shipped Calls changelog claim is added.
Feature releases Unchanged. Calls remains unreleased; HMS history remains an enrolled beta. No flag, entitlement, deployment, or rollout control changes.

Add the team call policy, do-not-call list, Cloud API call permission and
pre-dial check commands:

  calls policy get|set
  calls opt-outs list|add|import|remove
  calls permission get|request
  calls check

`calls policy` and `calls opt-outs` need an organization API key; the API
answers a project token or client token with 403. `calls permission get` and
`calls check` need a server API key, so the CLI refuses a Messaging client
token before it sends a request. `calls permission request` sends
callPermissionRequest content through the messages send route.

`calls opt-outs import` reads a CSV or JSON file, or standard input, sends
5,000 entries per request, and reports added, already listed and rejected
entries at their position in the file. It exits with code 12 when entries
were rejected or when a later batch failed after earlier batches were stored,
and with the request's own code when nothing was stored.

`calls check` exits with code 0 whether or not the call is allowed; read
`allowed` and `refusal` from the output.

Rate-limited commands now report the API's Retry-After wait in the message and
as `retryAfterSeconds` in --json error output. This covers every rate-limited
command, not only call permission requests.

The pinned SDK commit has no call consent resources, so these commands use the
SDK's own raw transport. src/services/call-consent.ts is the single place to
move to typed resources once the SDK ships them.
@coderabbitai

coderabbitai Bot commented Sep 20, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

  • Ask an admin to enable usage-based reviews

Open in CodeRabbit

Reviews can continue after your included limit without a manual trigger. An admin must approve usage-based billing.

Next included review available in 34 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 2 included reviews currently available. Your 69 included PR review attempts over the past 7 days set your current allowance at 2 reviews per hour.

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Team

Run ID: c9a1d776-686a-4fb2-9281-61072945d7f6

📥 Commits

Reviewing files that changed from the base of the PR and between 1fd51d6 and a9239ca.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (31)
  • README.md
  • docs/command-research/call-consent.md
  • docs/command-research/calls.md
  • docs/command-research/campaigns.md
  • package.json
  • src/api/request.ts
  • src/base-command.ts
  • src/commands/calls/check.ts
  • src/commands/calls/opt-outs/add.ts
  • src/commands/calls/opt-outs/import.ts
  • src/commands/calls/opt-outs/list.ts
  • src/commands/calls/opt-outs/remove.ts
  • src/commands/calls/permission/get.ts
  • src/commands/calls/permission/request.ts
  • src/commands/calls/policy/get.ts
  • src/commands/calls/policy/set.ts
  • src/commands/chat/list.ts
  • src/commands/chat/message/list.ts
  • src/commands/chat/message/view.ts
  • src/commands/chat/view.ts
  • src/context/environment.ts
  • src/errors/present.ts
  • src/output/contracts.ts
  • src/services/call-consent.ts
  • src/services/campaigns.ts
  • src/services/history.ts
  • test/api/request.test.ts
  • test/commands/call-consent.test.ts
  • test/commands/history.test.ts
  • test/errors/present.test.ts
  • test/services/call-consent.test.ts

Comment @coderabbitai help to get the list of available commands.

The polymorfa/sdks branch t3code/calls-consent adds the typed call consent
resources, but its repository root package is now the private workspace root
polymorfa-sdks with no built dist, so github:polymorfa/sdks#<commit> no longer
installs @polymorfa/sdk. Repinning needs a published prerelease.
@purpshell

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 22, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-23T16:37:02.733507Z a9239ca Manual request
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bd1d69a6e8

ℹ️ 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".

Comment thread src/services/call-consent.ts Outdated
...identity,
},
})
items.push(...response.data.data)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Enforce the max-items cap on returned pages

When --paginate --max-items N is used and the API returns more items than the requested page limit, this appends the entire page before checking items.length, so the command can emit more than the promised maximum. The shared pagination helpers already handle this by slicing to the remaining count and suppressing the unusable continuation cursor; this path should do the same so a nonconforming or version-skewed server response cannot defeat the user’s safety limit.

Useful? React with 👍 / 👎.

Comment thread src/services/call-consent.ts Outdated
Comment on lines +281 to +285
if (page.nextCursor === cursor) {
throw new PolymorfaValidationError(
'The API returned the same continuation cursor for the next page.',
{code: 'pagination_cursor_unchanged'},
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Classify repeated cursors as server failures

When the API repeats a cursor during --paginate, the defect is in the server response, but throwing PolymorfaValidationError reports exit code 2 and a non-retryable validation category as though the user supplied invalid input. The existing assertCursorAdvanced helper classifies this exact condition as PolymorfaServerError; using that behavior here preserves the CLI’s error semantics and lets callers handle the failure as transient/server-side.

Useful? React with 👍 / 👎.

Comment thread src/commands/calls/opt-outs/import.ts Outdated
const reason = presentError(progress.error).message
throw new CliConditionError(
'partial_success',
`Stored ${summary.batches} of ${Math.ceil(entries.length / IMPORT_BATCH_SIZE)} batches, then a request failed: ${reason} Run the import again; entries already stored are counted as already listed.`,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Count only requestable import batches

The total here includes entries that importCallOptOuts removes as local rejections before batching. For example, with 10,001 input entries where 5,000 are valid, 5,000 are over-length, and the final entry is valid, only two API batches exist; if the second request fails, this reports Stored 1 of 3 batches. Track the sendable batch count in the import progress instead so partial-failure guidance accurately describes how many requests remain.

Useful? React with 👍 / 👎.

Comment thread src/commands/calls/policy/get.ts Outdated
Comment on lines +8 to +9
static override description =
'Read the team call policy: the blocked country codes, the number of entries on the do-not-call list, and the revision to pass to `calls policy set --expected-revision`. The revision is 0 before the codes are first saved. Requires an organization API key; project tokens and client tokens are refused (exit code 3).'

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Align credential errors with the advertised exit code

With only POLYMORFA_CLIENT_TOKEN or POLYMORFA_PROJECT_TOKEN configured, platformClient() never reaches the API: platform environment resolution requires POLYMORFA_API_KEY and raises a configuration error, which exits with code 2. Thus these credentials cannot produce the documented HTTP 403/exit-code-3 behavior; either allow them through for server authorization or document the actual local refusal and exit code.

Useful? React with 👍 / 👎.

purpshell and others added 2 commits September 22, 2026 12:32
…d count sent batches

--max-items slices an oversized page and withholds its cursor, a repeated
cursor is a server failure (assertCursorAdvanced), a failed import reports
the batches it actually sends, and the call policy help states the real
exit codes for project and client tokens.

Co-Authored-By: Claude <noreply@anthropic.com>
@purpshell

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. You're on a roll.

Reviewed commit: 70ca2532b9

ℹ️ 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".

@purpshell
purpshell marked this pull request as ready for review September 23, 2026 16:28
@purpshell

Copy link
Copy Markdown
Member Author

@codex review

@purpshell
purpshell merged commit db302f9 into dev Sep 23, 2026
3 checks passed

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a9239caf9c

ℹ️ 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".

if (text[position + 1] === '"') {
cell += '"'
position += 1
} else quoted = false

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Reject characters after a closing CSV quote

When a quoted field is followed by non-delimiter text, this immediately leaves quoted mode and appends that text to the field; for example, +14155550123,"asked"oops is silently imported with note askedoops. Because this malformed CSV can alter identifiers or notes before they are sent to the API, track the just-closed state and reject anything other than a delimiter, row ending, or EOF.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant