feat(server-mode): spend quota per user — no new session, prompts refused once used up
An admin sets a default spend quota on the Users page and overrides it per
user (Edit user → Spend quota: default / no limit / their own), in dollars
per local day or per sliding 5 hours. Spend is the users page's own
hourlyTokenRows + calculateCost over the sessions a user owns.
Over the quota, until it opens again:
- spawnRun refuses every start/resume/home prompt with EQUOTA (429);
- a minute sweep writes each pane's AGENTHUB_QUOTA_FILE (host: data dir,
member: handoff dir via write-as) and hook-handler.js exits 2 on
UserPromptSubmit from that file alone (Claude Code, Codex with trusted
hooks) — fail-open, never asks the dashboard, never for Antigravity;
- a member's omp on the shared login loses its gateway key (clientsFor);
- panes no hook can stop (Antigravity, Cursor, Codex without trusted
hooks, omp on its own login) are ended.
GET /api/me/quota feeds a "Quota N%" chip in the status bar.
New: server/lib/user-quota.js, user_quotas table, PUT /api/users/quota-default,
PATCH /api/users/:id {quota}, docs in SERVER-MODE.md § Spend quotas.
This commit is contained in:
@@ -78,6 +78,13 @@
|
||||
"quotaWindow": {
|
||||
"wk": "Week",
|
||||
"mo": "Month"
|
||||
},
|
||||
"userQuota": "Quota {{pct}}%",
|
||||
"userQuotaTitle": "Your spend quota: {{spend}} of {{limit}} {{period}}",
|
||||
"userQuotaBlocked": "Your spend quota is used up ({{spend}} of {{limit}} {{period}}): no new session and no prompt until {{at}}",
|
||||
"userQuotaPeriod": {
|
||||
"day": "today",
|
||||
"5h": "in the last 5 hours"
|
||||
}
|
||||
},
|
||||
"version": "Version",
|
||||
|
||||
@@ -87,7 +87,8 @@
|
||||
"ELOCALMODE": "Accounts are managed in server mode only.",
|
||||
"ELASTADMIN": "The last admin cannot be deleted, demoted or disabled.",
|
||||
"EWEAKPASSWORD": "The new password does not meet every rule (or is the current one).",
|
||||
"EPWCHANGE": "Choose your own password first."
|
||||
"EPWCHANGE": "Choose your own password first.",
|
||||
"EBADQUOTA": "Enter a positive dollar limit and a period of a day or 5 hours."
|
||||
},
|
||||
"readOnly": "Analysts can view accounts but not change them.",
|
||||
"auditTitle": "Audit log",
|
||||
@@ -412,5 +413,37 @@
|
||||
"spendSub": "7 d {{week}} · 30 d {{month}}",
|
||||
"sessionsN": "{{n}} sessions",
|
||||
"workspacesN": "{{n}} workspaces",
|
||||
"tmuxCol": "tmux"
|
||||
"tmuxCol": "tmux",
|
||||
"quota": {
|
||||
"title": "Spend quota",
|
||||
"limit": "Limit in dollars",
|
||||
"period": "Period",
|
||||
"periods": {
|
||||
"day": "per day (from local midnight)",
|
||||
"5h": "per 5 hours (sliding)"
|
||||
},
|
||||
"per": {
|
||||
"day": "{{limit}} / day",
|
||||
"5h": "{{limit}} / 5 h"
|
||||
},
|
||||
"used": "{{spend}} of {{limit}}",
|
||||
"defaultTag": "(default)",
|
||||
"none": "No quota",
|
||||
"blockedUntil": "Used up — no new session and no prompt until {{at}}",
|
||||
"cardTitle": "Default spend quota",
|
||||
"cardDescription": "Every user without a quota of their own gets this one. Once it is used up they can start no session and their prompts are refused until it resets; a pane no hook can stop (Antigravity, Cursor, Codex without trusted hooks, omp on its own login) is ended.",
|
||||
"noDefault": "No default — no limit",
|
||||
"editDefault": "Edit",
|
||||
"defaultDialog": "Default spend quota",
|
||||
"defaultOn": "Limit what each user may spend",
|
||||
"defaultSaved": "Default spend quota saved",
|
||||
"enforceHint": "Over the quota: no new session, Claude Code and Codex prompts refused, other AI tools' panes ended — until it resets.",
|
||||
"modes": {
|
||||
"defaultOf": "Default ({{quota}})",
|
||||
"defaultNone": "Default (no limit)",
|
||||
"unlimited": "No limit",
|
||||
"custom": "Their own limit"
|
||||
},
|
||||
"now": "Now: {{spend}} of {{limit}}"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,6 +78,13 @@
|
||||
"quotaWindow": {
|
||||
"wk": "Tuần",
|
||||
"mo": "Tháng"
|
||||
},
|
||||
"userQuota": "Quota {{pct}}%",
|
||||
"userQuotaTitle": "Quota chi phí của bạn: {{spend}} trên {{limit}} {{period}}",
|
||||
"userQuotaBlocked": "Bạn đã dùng hết quota chi phí ({{spend}} trên {{limit}} {{period}}): không mở phiên mới, không gửi prompt đến {{at}}",
|
||||
"userQuotaPeriod": {
|
||||
"day": "hôm nay",
|
||||
"5h": "trong 5 giờ qua"
|
||||
}
|
||||
},
|
||||
"version": "Phiên bản",
|
||||
|
||||
@@ -87,7 +87,8 @@
|
||||
"ELOCALMODE": "Chỉ quản lý tài khoản được ở chế độ server.",
|
||||
"ELASTADMIN": "Không thể xoá, hạ quyền hay khoá admin cuối cùng.",
|
||||
"EWEAKPASSWORD": "Mật khẩu mới chưa đạt đủ các yêu cầu (hoặc trùng mật khẩu hiện tại).",
|
||||
"EPWCHANGE": "Hãy đặt mật khẩu riêng của bạn trước."
|
||||
"EPWCHANGE": "Hãy đặt mật khẩu riêng của bạn trước.",
|
||||
"EBADQUOTA": "Nhập hạn mức USD lớn hơn 0 và chu kỳ ngày hoặc 5 giờ."
|
||||
},
|
||||
"readOnly": "Analyst chỉ xem được tài khoản, không thay đổi được.",
|
||||
"auditTitle": "Nhật ký audit",
|
||||
@@ -412,5 +413,37 @@
|
||||
"spendSub": "7 ngày {{week}} · 30 ngày {{month}}",
|
||||
"sessionsN": "{{n}} session",
|
||||
"workspacesN": "{{n}} workspace",
|
||||
"tmuxCol": "tmux"
|
||||
"tmuxCol": "tmux",
|
||||
"quota": {
|
||||
"title": "Quota chi phí",
|
||||
"limit": "Hạn mức (USD)",
|
||||
"period": "Chu kỳ",
|
||||
"periods": {
|
||||
"day": "mỗi ngày (từ nửa đêm giờ địa phương)",
|
||||
"5h": "mỗi 5 giờ (trượt)"
|
||||
},
|
||||
"per": {
|
||||
"day": "{{limit}} / ngày",
|
||||
"5h": "{{limit}} / 5 giờ"
|
||||
},
|
||||
"used": "{{spend}} trên {{limit}}",
|
||||
"defaultTag": "(mặc định)",
|
||||
"none": "Không giới hạn",
|
||||
"blockedUntil": "Đã hết — không mở phiên mới, không gửi prompt đến {{at}}",
|
||||
"cardTitle": "Quota chi phí mặc định",
|
||||
"cardDescription": "Áp cho mọi user không có quota riêng. Khi dùng hết, user không mở được phiên mới và prompt bị từ chối đến khi quota mở lại; pane không hook nào chặn được (Antigravity, Cursor, Codex chưa tin hook, omp dùng login riêng) sẽ bị dừng.",
|
||||
"noDefault": "Chưa có mặc định — không giới hạn",
|
||||
"editDefault": "Sửa",
|
||||
"defaultDialog": "Quota chi phí mặc định",
|
||||
"defaultOn": "Giới hạn chi phí của mỗi user",
|
||||
"defaultSaved": "Đã lưu quota chi phí mặc định",
|
||||
"enforceHint": "Vượt quota: không mở phiên mới, prompt Claude Code và Codex bị từ chối, pane của các AI tool khác bị dừng — đến khi quota mở lại.",
|
||||
"modes": {
|
||||
"defaultOf": "Mặc định ({{quota}})",
|
||||
"defaultNone": "Mặc định (không giới hạn)",
|
||||
"unlimited": "Không giới hạn",
|
||||
"custom": "Hạn mức riêng"
|
||||
},
|
||||
"now": "Hiện tại: {{spend}} trên {{limit}}"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user