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:
mjalmoudy94
2026-10-14 23:19:05 +03:30
parent fef5a9deab
commit d532929189
117 changed files with 13119 additions and 640 deletions
+6 -2
View File
@@ -38,6 +38,7 @@ from app.infrastructure.repositories.delivery import PipelineRunRepository
from app.infrastructure.repositories.engine import PipelineStateRepository
from app.infrastructure.repositories.workforce import EmployeeRepository, TaskRepository
from app.workers.ai_runtime import AiRuntimeWorker
from tests.billing_helpers import fund_org
from tests.conftest import auth_header
from tests.engine_fakes import ScriptedModel, ScriptedReply
from tests.test_org_wizard import _complete, _manager
@@ -107,6 +108,8 @@ async def test_a_manager_order_ships_through_the_engine(
org_id = body["organization"]["id"]
slug = body["organization"]["slug"]
project_id = body["project"]["id"]
# prepaid: the owner credits a new org before work
await fund_org(session_factory, org_id, 1000.0)
async with session_factory() as session:
roles = {e.role_key for e in await EmployeeRepository(session).list_for_org(org_id)}
@@ -132,8 +135,9 @@ async def test_a_manager_order_ships_through_the_engine(
thread_id = created.json()["id"]
# A manager order in that thread becomes one task with a pipeline state. The order goes to
# the engine's manager directly: the HTTP message route builds its manager without pipeline
# states (see test_integration_thread_project.py).
# the engine's manager directly, only to read the reply and the states in this session: the
# HTTP message route builds its manager with the pipeline states too
# (test_integration_order_route.py).
async with session_factory() as session:
await build_scheduler(session).manager.chat(
organization_id=org_id,