Option to Notify Workspace Owner When Usage Limit is Reached (#16969)

## Summary
- Replace the manual `/notify-owner` flow with an inline confirmation
prompt when a usage-based workspace member hits a credits-depleted
limit.
- Fetch the current workspace role from the live ChatGPT
`accounts/check/v4-2023-04-27` endpoint so owner/member behavior matches
the desktop and web clients.
- Keep owner, member, and spend-cap messaging distinct so we only offer
the owner nudge when the workspace is actually out of credits.

## What Changed
- `backend-client`
- Added a typed fetch for the current account role from
`accounts/check`.
  - Mapped backend role values into a Rust workspace-role enum.
- `app-server` and protocol
  - Added `workspaceRole` to `account/read` and `account/updated`.
- Derived `isWorkspaceOwner` from the live role, with a fallback to the
cached token claim when the role fetch is unavailable.
- `tui`
  - Removed the explicit `/notify-owner` slash command.
- When a member is blocked because the workspace is out of credits, the
error now prompts:
- `Your workspace is out of credits. Request more from your workspace
owner? [y/N]`
  - Choosing `y` sends the existing owner-notification request.
- Choosing `n`, pressing `Esc`, or accepting the default selection
dismisses the prompt without sending anything.
- Selection popups now honor explicit item shortcuts, which is how the
`y` / `n` interaction is wired.

## Reviewer Notes
- The main behavior change is scoped to usage-based workspace members
whose workspace credits are depleted.
- Spend-cap reached should not show the owner-notification prompt.
- Owners and admins should continue to see `/usage` guidance instead of
the member prompt.
- The live role fetch is best-effort; if it fails, we fall back to the
existing token-derived ownership signal.

## Testing
- Manual verification
  - Workspace owner does not see the member prompt.
- Workspace member with depleted credits sees the confirmation prompt
and can send the nudge with `y`.
- Workspace member with spend cap reached does not see the
owner-notification prompt.

### Workspace member out of usage

https://github.com/user-attachments/assets/341ac396-eff4-4a7f-bf0c-60660becbea1

### Workspace owner
<img width="1728" height="1086" alt="Screenshot 2026-04-09 at 11 48
22 AM"
src="https://github.com/user-attachments/assets/06262a45-e3fc-4cc4-8326-1cbedad46ed6"
/>
This commit is contained in:
richardopenai
2026-04-09 21:15:17 -07:00
committed by GitHub
parent 36712d8546
commit 9f2a585153
82 changed files with 3233 additions and 60 deletions

View File

@@ -635,6 +635,9 @@ pub enum Op {
/// Request a code review from the agent.
Review { review_request: ReviewRequest },
/// Ask the workspace owner to add Codex credits to the current ChatGPT workspace.
SendAddCreditsNudgeEmail,
/// Request to shut down codex instance.
Shutdown,
@@ -745,6 +748,7 @@ impl Op {
Self::Undo => "undo",
Self::ThreadRollback { .. } => "thread_rollback",
Self::Review { .. } => "review",
Self::SendAddCreditsNudgeEmail => "send_add_credits_nudge_email",
Self::Shutdown => "shutdown",
Self::RunUserShellCommand { .. } => "run_user_shell_command",
Self::ListModels => "list_models",
@@ -1503,6 +1507,9 @@ pub enum EventMsg {
/// List of skills available to the agent.
ListSkillsResponse(ListSkillsResponseEvent),
/// Response to SendAddCreditsNudgeEmail.
AddCreditsNudgeEmailResponse(AddCreditsNudgeEmailResponseEvent),
/// List of voices supported by realtime conversation streams.
RealtimeConversationListVoicesResponse(RealtimeConversationListVoicesResponseEvent),
@@ -1556,6 +1563,18 @@ pub enum EventMsg {
CollabResumeEnd(CollabResumeEndEvent),
}
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "snake_case")]
pub enum AddCreditsNudgeEmailStatus {
Sent,
CooldownActive,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, JsonSchema, TS)]
pub struct AddCreditsNudgeEmailResponseEvent {
pub result: Result<AddCreditsNudgeEmailStatus, String>,
}
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "snake_case")]
pub enum HookEventName {
@@ -2122,6 +2141,7 @@ pub struct RateLimitSnapshot {
pub primary: Option<RateLimitWindow>,
pub secondary: Option<RateLimitWindow>,
pub credits: Option<CreditsSnapshot>,
pub spend_control: Option<SpendControlSnapshot>,
pub plan_type: Option<crate::account::PlanType>,
}
@@ -2144,6 +2164,11 @@ pub struct CreditsSnapshot {
pub balance: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct SpendControlSnapshot {
pub reached: bool,
}
// Includes prompts, tools and space to call compact.
const BASELINE_TOKENS: i64 = 12000;