feat(integration): prepaid billing, engine UI, credits console, direct tasks, v2 final
- Prepaid credit system: org wallet, debit on usage, chat gate, admin credit console - Engine UI: /manager, /tasks, /tasks/[id], /workforce, /dashboard, /traces pages - Direct task creation (HTTP POST /tasks, MCP tasks_create) at pending_manager_review - Pause/resume/cancel/answer actions in engine API and UI - Employee cap (7 per org default), review pipeline guard (active reviewer required) - Scheduler: wide window prevents org starvation; archived employees excluded from cap - Tenant isolation: project-id cross-org 404, engine never uses another tenant's sandbox - Order route fix: manager built with pipeline states + employee profiles - Contract extension: ModelGrantPort.get_org_policy with DefaultOrgPolicy - Engine event types in @agy/contracts (task.step, task.gate, run.log, run.tool, team.changed) - api-engine.ts typed client; api-credit.ts client - Alembic migration c4d5e6f7a8b9: prepaid credit + engine UI tables - 859 tests passed, 1 skipped; ruff/mypy/tsc/eslint/vitest all green Refs: PLAN-v2.md §9 (T1–T43), todo-v2-final.md
This commit is contained in:
@@ -88,6 +88,7 @@ from app.infrastructure.repositories.ai_control import (
|
||||
RateCardRepository,
|
||||
)
|
||||
from app.infrastructure.repositories.audit import UsageLedgerRepository
|
||||
from app.infrastructure.repositories.billing import CreditWalletRepository
|
||||
from app.infrastructure.repositories.engine import (
|
||||
EmployeeProfileRepository,
|
||||
PipelineStateRepository,
|
||||
@@ -109,6 +110,11 @@ CHAT_MAX_ATTEMPTS = 2
|
||||
#: The message a new organization sees when the owner has granted nothing yet.
|
||||
NO_MODEL_GRANTED_MESSAGE = "منتظر تخصیص مدل توسط مالک سامانه"
|
||||
|
||||
#: The reply a zero-credit organization gets instead of a model call (prepaid, D-B).
|
||||
CHAT_NO_CREDIT_MESSAGE = (
|
||||
"اعتبار سازمان صفر است؛ مدیر پس از افزودن اعتبار توسط مالک سامانه پاسخ میدهد."
|
||||
)
|
||||
|
||||
#: Bootstrap tiers per role, in the plan's order (strongest / balanced / fast).
|
||||
BOOTSTRAP_TIERS: dict[RoleKey, str] = {
|
||||
RoleKey.MANAGER: "strong",
|
||||
@@ -223,6 +229,7 @@ class ManagerTurns:
|
||||
states: PipelineStateRepository | None = None,
|
||||
runner: TurnRunner | None = None,
|
||||
feed=None,
|
||||
wallets: CreditWalletRepository | None = None,
|
||||
) -> None:
|
||||
self.provider = provider
|
||||
self.employees = employees
|
||||
@@ -239,6 +246,7 @@ class ManagerTurns:
|
||||
self.states = states
|
||||
self.runner = runner
|
||||
self.feed = feed
|
||||
self.wallets = wallets
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
# 3.6 bootstrap #
|
||||
@@ -404,6 +412,17 @@ class ManagerTurns:
|
||||
user_message = await self.messages.create(
|
||||
Message(thread_id=thread.id, role=MessageRole.USER, content=content)
|
||||
)
|
||||
# Prepaid: a reply costs a model call, so an org with no credit gets the
|
||||
# note instead. No model is called and no tag is executed.
|
||||
if await self._out_of_credit(organization_id):
|
||||
note = await self.messages.create(
|
||||
Message(
|
||||
thread_id=thread.id,
|
||||
role=MessageRole.ASSISTANT,
|
||||
content=CHAT_NO_CREDIT_MESSAGE,
|
||||
)
|
||||
)
|
||||
return user_message, note
|
||||
employee = await self._manager_of(organization_id, employee_id)
|
||||
extra = "\n\n".join(
|
||||
part
|
||||
@@ -492,6 +511,13 @@ class ManagerTurns:
|
||||
)
|
||||
return user_message, assistant_message
|
||||
|
||||
async def _out_of_credit(self, organization_id: str) -> bool:
|
||||
"""True when a wired wallet shows no credit left (a missing wallet counts as none)."""
|
||||
if self.wallets is None:
|
||||
return False
|
||||
wallet = await self.wallets.get_for_org(organization_id)
|
||||
return wallet is None or wallet.balance <= 0
|
||||
|
||||
def _chat_context(
|
||||
self,
|
||||
*,
|
||||
@@ -1083,6 +1109,7 @@ class ManagerTurns:
|
||||
|
||||
__all__ = [
|
||||
"BOOTSTRAP_TIERS",
|
||||
"CHAT_NO_CREDIT_MESSAGE",
|
||||
"CHAT_TURN_TIMEOUT_SECONDS",
|
||||
"HIRABLE_ROLES",
|
||||
"NO_MODEL_GRANTED_MESSAGE",
|
||||
|
||||
Reference in New Issue
Block a user