Files
smart-org/README.md
T
mjalmoudy94 d532929189 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
2026-10-14 23:19:05 +03:30

23 KiB
Raw Blame History

Smart Organization — پلتفرم نیروی کار هوش مصنوعی

نسخهٔ وب مستقل و چندسازمانی محصول. این پروژه کاملاً جدا از ربات تلگرام است و هیچ فایلی از ریشهٔ مخزن را در runtime خود import نمی‌کند.


۱. محصول چیست

یک وب اپلیکیشن چندسازمانی برای «نیروی کار هوش مصنوعی سازمانی».

کاربر نهایی این‌ها را می‌بیند: سازمان، مدیر هوش مصنوعی، کارمندهای هوش مصنوعی، پروژه، وظیفه، فایل/خروجی، مصرف و پرداخت.

کاربر نهایی این‌ها را نمی‌بیند: نام مدل، provider، base_url، API key، قیمت خام provider. این‌ها فقط در Admin Console و فقط برای مالک سامانه قابل مدیریت‌اند.

پلتفرم سه لایهٔ نقش دارد:

  1. مالک سامانه (platform owner) — دقیقاً یک حساب و صاحب کل سیستم. اکانت‌های agy، مدل‌ها، routing policy، تعرفه‌ها، sandbox templateها، مشاهدهٔ کل مصرف، و ساخت/تنزل مدیران. agy تنها ارائه‌دهندهٔ مدل سامانه است و بخش جداگانهٔ «ارائه‌دهنده‌ها» وجود ندارد.
  2. مدیر (platform manager) — حسابی که فقط مالک سامانه می‌سازد. هر مدیر می‌تواند سازمان‌های خودش را بسازد (و مالک آن‌ها شود) یا با دعوت صاحب سازمان، در سازمان مدیران دیگر عضو شود. مدیر به کنسول مالک سامانه دسترسی ندارد.
  3. کاربر عادی (platform user) — سازمان نمی‌سازد؛ فقط داخل سازمان‌هایی که با دعوت به آن‌ها پیوسته کار می‌کند.

ثبت‌نام عمومی وجود ندارد. حساب‌ها از دو مسیر ساخته می‌شوند:

  • ساخت مستقیم توسط مالک سامانه در /admin/users (برای مدیر یا کاربر عادی) که یک رمز موقت یک‌بارمصرف برمی‌گرداند؛
  • پذیرش لینک دعوت: دعوت‌شده اگر حساب نداشته باشد، در همان صفحه رمز خودش را تعیین می‌کند و حساب ساخته می‌شود. اگر حساب داشته باشد باید اول وارد شود — یک لینک دعوت هرگز جای reset رمز را نمی‌گیرد.

«فقط یک مالک» یک قاعدهٔ دیتابیسی است: ایندکس یکتای جزئی روی users.platform_role = 'owner'، پس حتی یک درج مستقیم هم نمی‌تواند مالک دوم بسازد. جابه‌جایی صندلی مالکیت فقط با POST /admin/transfer-ownership انجام می‌شود که در یک تراکنش، مالک فعلی را به مدیر تنزل می‌دهد و جانشین را مالک می‌کند.

اعتبار پیش‌پرداخت. سازمان‌ها با اعتبار پیش‌پرداخت کار می‌کنند. مالک سامانه در /admin/credits به هر سازمان اعتبار می‌دهد و هر رکورد مصرف (مدل، sandbox و بازتاب حافظه) به‌محض ثبت، از کیف پول همان سازمان کم می‌شود. وقتی موجودی به صفر یا کمتر برسد، کارهای آن سازمان متوقف می‌شوند و گفتگوی مدیر هم مدل را صدا نمی‌زند. کارهای متوقف‌شده پس از شارژ خودکار ادامه نمی‌یابند؛ با دکمهٔ «ادامه» دستی و از همان مرحله ادامه داده می‌شوند.


۲. پیش‌نیازها

ابزار نسخه
Node.js >= 20
pnpm 9.x
Python 3.13
Docker + Compose برای postgres/redis

فعال‌سازی pnpm در صورت نبود:

corepack enable pnpm
corepack prepare pnpm@9.12.0 --activate

۳. راه‌اندازی سریع

# ۱) نصب وابستگی‌های JS
pnpm install

# ۲) ساخت venv پایتون برای backend
pnpm api:venv

# ۳) کپی فایل env و پر کردن مقادیر (پیش‌فرض‌ها همه mock هستند)
cp .env.example .env

# ۴) بالا آوردن postgres و redis
docker compose -f infra/docker/docker-compose.yml up -d postgres redis

# ۵) اجرای migration و seed
pnpm db:migrate
pnpm db:seed

# ۶) اجرای هم‌زمان frontend و backend
pnpm dev

آدرس‌ها پس از اجرا:

حساب‌های نمونه (فقط محیط توسعه)

pnpm db:seed دو حساب می‌سازد:

نقش ایمیل رمز چه می‌بیند
مالک سامانه (admin console) owner@example.com owner12345 کنسول مالک سامانه، بدون هیچ سازمانی
مدیر نمونه demo@example.com demo12345 سازمان نمونه که خودش مالک آن است

مالک سامانه عضو هیچ سازمانی نمی‌شود — او مستأجرها را از کنسول می‌بیند، نه از داخل داده‌شان. برای دیدن داشبورد سازمانی با حساب مدیر نمونه وارد شوید.

این‌ها رمزهای توسعه هستند و در app/seed.py سخت‌کد شده‌اند. هیچ‌وقت در production از این‌ها استفاده نکنید و JWT_SECRET / ENCRYPTION_KEY واقعی بگذارید (نگهبان config در حالت production مقادیر placeholder را رد می‌کند).

نکته: دامنهٔ ایمیل عمداً example.com است. اگر .local یا .test بگذارید، email-validator آن را رد می‌کند و حساب ساخته می‌شود ولی ورود به آن ناممکن است. tests/test_seed.py همین را تضمین می‌کند.

اجرای کل استک با Docker

docker compose -f infra/docker/docker-compose.yml up --build

۴. اسکریپت‌ها

دستور کار
pnpm dev اجرای هم‌زمان Next.js و FastAPI
pnpm build build فرانت‌اند
pnpm lint ESLint (web) + ruff (api)
pnpm typecheck tsc (web) + mypy (api)
pnpm test vitest (web) + pytest (api)
pnpm format Prettier (web) + ruff format (api)
pnpm db:migrate alembic upgrade head
pnpm db:seed داده‌های نمونه (platform owner، کاربر نمونه، سازمان، templateها)
pnpm api:venv ساخت venv پایتون
pnpm worker:billing یک پاس worker صورتحساب
pnpm worker:ai-runtime یک پاس worker اجرای taskها
pnpm worker:sandbox-gateway یک پاس پاک‌سازی sessionهای بی‌استفاده

۵. معماری

۵.۱ سبک معماری

  • Clean Architecture + Hexagonal / Ports & Adapters
  • DDD برای مرزبندی bounded contextها
  • Event-Driven برای billing، audit و observability (الگوی Outbox)
  • Modular Monolith در فاز اول — microservice زودهنگام ممنوع

قاعدهٔ سخت: لایهٔ domain هیچ dependency به FastAPI، SQLAlchemy، Redis، HTTP یا provider خارجی ندارد. فقط application و infrastructure به آن وابسته‌اند، نه برعکس.

۵.۲ Bounded Contextها

identity · billing · ai_control · workforce · sandbox · knowledge · audit · projects · platform

۵.۳ Ports الزامی

ModelProviderPort · PaymentProviderPort · SandboxProviderPort · GitProviderPort · DomainRouterPort · StoragePort · VectorStorePort · NotificationPort · AuditPort · UsageLedgerPort

هر port حداقل یک adapter mock دارد تا کل استک بدون هیچ credential واقعی اجرا شود.

۵.۴ جریان درخواست

Browser → Web (Next.js/BFF) → API (FastAPI)
                                  ├── Domain Modules
                                  ├── Event Bus / Outbox → Workers
                                  ├── AI Runtime        → Model Providers
                                  ├── Sandbox Gateway   → E2B / Daytona
                                  └── Billing Processor → Stripe

۶. ساختار پوشه

web/
  PLAN.md                 پلن محصول و معماری
  TODO.md                 چک‌لیست اجرایی
  README.md               همین فایل
  package.json            اسکریپت‌های ریشه
  pnpm-workspace.yaml     تعریف workspace
  .env.example            نمونهٔ env (هیچ secret واقعی ندارد)
  apps/
    web/                  Next.js + TypeScript (UI)
    api/                  FastAPI + SQLAlchemy (backend)
  services/
    ai-runtime/           worker اجرای Agent
    sandbox-gateway/      wrapper ایزوله‌سازی اجرا
    billing-worker/       پردازش usage و invoice
  packages/
    contracts/            تایپ‌ها و قراردادهای مشترک
    ui/                   componentهای پایه (design system)
  infra/
    docker/               docker-compose
    migrations/           محل migrationها
  docs/
    architecture/         مستندات معماری
    product/              مستندات محصول
    current-infra-notes.md

۷. کجا کد بنویسیم

می‌خواهم... محل
صفحه یا component UI بسازم apps/web/src/app یا apps/web/src/features
component پایه/design system packages/ui
endpoint جدید بسازم apps/api/app/interfaces/http
use case بنویسم apps/api/app/application
منطق دامنه بنویسم apps/api/app/domain/<context>
adapter یا اتصال به سرویس بیرونی apps/api/app/infrastructure
worker پس‌زمینه بنویسم apps/api/app/workers (پوستهٔ اجرا در services/<name>)
خطا ثبت/پاک‌سازی کنم apps/api/app/core/redact.py + apps/api/app/infrastructure/error_log.py
قرارداد مشترک front/back packages/contracts

ممنوع: نوشتن هر کد جدید در فایل‌های ریشهٔ مخزن (bot.py, web_panel.py, bot_menus.py, bot_messages.py) و هر وابستگی به telegram, chat_id, callback_data, InlineKeyboardMarkup.


۸. سرویس‌های خارجی و حالت mock

همهٔ سرویس‌های بیرونی پیش‌فرض روی mock هستند. برای فعال‌سازی واقعی، این envها را پر کنید:

قابلیت env مقادیر
Git GIT_PROVIDER mock | gitea
Workspace گیت GIT_WORKSPACE fake (پیش‌فرض، برای تست) | local (git روی host)
دامنهٔ تست DOMAIN_ROUTER mock | caddy
دامنهٔ سفارشی ALLOW_CUSTOM_DOMAINS false (پیش‌فرض) | true
Sandbox SANDBOX_PROVIDER mock | docker | daytona | e2b
استقرار روی شبکهٔ خصوصی DEPLOY_ALLOW_PRIVATE_HOSTS false (پیش‌فرض)؛ true فقط برای مقصدی در شبکهٔ خصوصی که قابل اعتماد است
MCP gateway MCP_GATEWAY_ENABLED true (پیش‌فرض) | false
پرداخت PAYMENT_PROVIDER mock | stripe
اعتبار اولیهٔ سازمان‌های جدید BILLING_STARTING_CREDIT عدد؛ پیش‌فرض 0 (یعنی به سازمان جدید اعتباری داده نمی‌شود)

اگر credential واقعی نداریم، قابلیت حذف نمی‌شود: مدل دیتابیس، interface، UI و adapter mock کامل ساخته می‌شود و adapter واقعی پشت همین flag آماده می‌ماند.

با پیش‌فرض‌ها (mock، fake و false) تست‌ها بدون docker، git یا شبکه اجرا می‌شوند.

حالت واقعی (real mode): وقتی سامانه باید روی یک host واقعی با docker و caddy اجرا شود، این envها را تنظیم کنید:

SANDBOX_PROVIDER=docker GIT_WORKSPACE=local DOMAIN_ROUTER=caddy GIT_PROVIDER=gitea

نیازمندی‌های host در این حالت:

  • docker یا podman در PATH (با SANDBOX_DOCKER_BIN انتخاب می‌شود). کد کاربر فقط داخل کانتینرهای سخت‌شده اجرا می‌شود: --read-only، کاربر غیر root، --cap-drop ALL، و محدودیت CPU، حافظه و PID.
  • git نسخهٔ 2.20 یا جدیدتر در PATH. git را خود پلتفرم اجرا می‌کند؛ hookها غیرفعال‌اند و اعتبارنامه‌ها هرگز در argv یا URL نمی‌آیند.
  • caddy در PATH و این خط در Caddyfile: import /etc/caddy/agy-preview/*.caddy. بدون این خط adapter اجرا نمی‌شود، تا یک موفقیت کاذب گزارش نشود.
  • اجازهٔ بدون رمز برای caddy reload --config <file> به کاربر سرویس (روی systemd با sudoers یا polkit، نه با root).
  • پوشه‌های قابل‌نوشتن با فضای کافی زیر SANDBOX_WORKSPACES_ROOT و ARTIFACTS_ROOT (پیش‌فرض ./.data/…). پاک‌سازی خودکار runs/، artifacts/ و فایل‌های پشتیبان FTP هنوز پیاده نشده است.
  • هدف استقرار ftps (TLS صریح) است. هدف ftp ساده پذیرفته می‌شود، ولی بدون رمزنگاری است.
  • تست‌های یکپارچگی docker و استقرار با SANDBOX_DOCKER_IT=1 و DEPLOY_IT=1 فعال می‌شوند و بدون آن‌ها رد (skip) می‌شوند.

ارائه‌دهندهٔ agy (Google Antigravity) — تنها ارائه‌دهندهٔ مدل

agy کلید API ندارد؛ اکانت‌ها هویت Google هستند و از طریق OAuth وارد می‌شوند. کاتالوگ مدل‌ها در seed ساخته می‌شود و همان اول فعال است. برای شروع:

  1. با حساب مالک سامانه وارد کنسول مدیریت شوید و از /admin/agy-accounts → «افزودن اکانت agy» شروع کنید: لینک احراز هویت گوگل باز می‌شود، بعد از تأیید، URL بازگشت را از نوار آدرس مرورگر کپی و Paste کنید (صفحهٔ مقصد عمداً بارگذاری نمی‌شود؛ پارامترها همان‌جا هستند).
  2. در /admin/models زنجیرهٔ افزودن مدل کاملاً انتخابی است: ارائه‌دهنده (agy) → اکانت → مدل (کاتالوگ لحظه‌ای و زندهٔ همان اکانت) → افورت استدلال (فقط برای مدل‌های استدلالی). هیچ نام مدلی دستی تایپ نمی‌شود و مشتری نام مدل را هرگز نمی‌بیند.

هر تعداد اکانت Google که بخواهید اضافه کنید. پول چرخشی، بار را بین اکانت‌ها پخش می‌کند و اکانتی که ۴۲۹ گرفته تا پایان پنجرهٔ سهمیهٔ آن کنار گذاشته می‌شود (هم در حافظه و هم در دیتابیس، تا workerها همان تصویر را ببینند). refresh tokenها فقط encrypted at rest ذخیره می‌شوند و هیچ‌وقت از API برنمی‌گردند.


۹. Workerهای پس‌زمینه

پنج process پس‌زمینه وجود دارد: چهار worker و یک MCP gateway. همه در apps/api/app/workers/ پیاده شده‌اند (MCP gateway در apps/api/app/interfaces/mcp/) و services/<name> پوستهٔ اجرای آن‌هاست:

worker کار دستور
billing-worker جمع‌بندی usage ledger به فاکتور draft (هر دوره یک‌بار، idempotent)، و در همان پاس حذف نرم درس‌های حافظه‌ای منقضی‌شده (به‌جز موارد pin شده) pnpm worker:billing
ai-runtime برداشتن taskهای در صف و اجرای آن‌ها از مسیر run engine، و یک Scheduler.tick() در هر پاس (فاصلهٔ پاس‌ها با ENGINE_TICK_SECONDS) pnpm worker:ai-runtime
sandbox-gateway متوقف‌کردن sessionهای بی‌استفاده، حذف containerهای یتیم با label smart-org.*، و ثبت audit pnpm worker:sandbox-gateway
delivery اجرای pipeline runهای در صف (از checkout تا route) و بررسی ریموت‌ها برای pushهای بیرونی هر DELIVERY_POLL_SECONDS pnpm worker:delivery

پیش‌پرداخت: پاس ai-runtime تسک‌های سازمانی را که کیف پول آن‌ها صفر، منفی یا نامشخص است، پیش از خرج کردن هر turn متوقف می‌کند. این تسک‌ها پس از شارژ دستی ادامه داده می‌شوند.

MCP gateway یک سرویس مداوم است که با pnpm mcp:dev اجرا می‌شود. عامل‌های بیرونی AI با توکن‌های محدود به یک سازمان به آن وصل می‌شوند. جزئیات در services/mcp-gateway/README.md.

هر دستور worker یک پاس اجرا می‌کند و خارج می‌شود (شکل مناسب cron). برای اجرای مستمر --loop بدهید:

cd apps/api && .venv/bin/python -m app.workers billing --loop --interval 3600

اصل مهم: workerها هیچ منطق تجاری ندارند. ai-runtime، sandbox-gateway و delivery سرویس‌های خود را از همان builderهای app/application/container*.py می‌سازند که HTTP API می‌سازد، پس آن‌ها و API هرگز نمی‌توانند دربارهٔ اینکه کدام adapter پشت یک port است اختلاف داشته باشند. billing-worker مستقیم از repositoryها استفاده می‌کند. جزئیات هر کدام در services/*/README.md.


۱۰. اصول تغییرناپذیر

  1. اجرای کد کاربر یا agent روی host اصلی ممنوع — فقط از مسیر Sandbox Gateway.
  2. هر endpoint چندسازمانی باید organization_id را enforce کند.
  3. هیچ secret، token یا API key در frontend یا لاگ چاپ نمی‌شود.
  4. هر عملیات destructive باید confirmation و audit log داشته باشد.
  5. مدل و provider از دید کاربر عادی پنهان است.
  6. هر مصرفی در usage ledger داخلی ثبت می‌شود (Stripe فقط adapter است).

۱۱. کنسول خطا

هر خطایی که سامانه بگیرد — در درخواست HTTP، در worker پس‌زمینه، یا در زمان راه‌اندازی — با جزئیات کامل در جدول error_events ثبت می‌شود و در /admin/errors (فقط مالک سامانه) قابل مشاهده است.

چه چیزی ثبت می‌شود

منبع چه زمانی شدت
استثنای مدیریت‌نشده و خطاهای ۵xx همیشه critical / error
ValueError بدون نوع مشخص همیشه warning
سایر پاسخ‌های ۴xx فقط با ERROR_LOG_CLIENT_ERRORS=1 warning

۴xx به‌صورت پیش‌فرض ثبت نمی‌شود چون یک API عمومی دائماً ۴۰۴ از اسکنرها می‌گیرد و ثبت همهٔ آن‌ها خطاهای واقعی را زیر خود دفن می‌کند. خطاهای ۵xx همیشه ثبت می‌شوند، مستقل از این تنظیم.

هر رکورد چه چیزی دارد

  • kind، error_type، message و stack (ردِّ کامل خطا)
  • method، path، status_code و request_id
  • organization_id و user_id (پس از احراز هویت)
  • fingerprint — هش پایدار از هویت خطا، برای گروه‌بندی
  • metadata_json — جزئیات اضافی (مثلاً task_id)

گروه‌بندی و رسیدگی

خطاهای یکسان بر اساس fingerprint در یک ردیف جمع می‌شوند، پس یک باگ که ۴۰۰۰ بار رخ داده یک خط با شمارنده است، نه ۴۰۰۰ خط. fingerprint عمداً سازمان و کاربر را در نظر نمی‌گیرد: یک باگ که دو مستأجر را می‌زند هنوز یک باگ است. بستن یک گروه همهٔ رخدادهای باز آن را می‌بندد و در audit log ثبت می‌شود.

پاک‌سازی خودکار secretها

هیچ خطایی بدون عبور از app/core/redact.py ذخیره نمی‌شود. سه لایه: کلیدهایی که معنایشان secret است (password، api_key، …)، الگوهای شناختنی (JWT، Bearer، scheme://user:pass@host، sk-…، ghp_…)، و خودِ مقادیر secret تنظیم‌شده در settings. یک لاگ خطا که رمز عبور ذخیره کند از نداشتن لاگ خطا بدتر است.

استفاده از request_id

هر پاسخ X-Request-ID دارد و هر بدنهٔ خطا هم همان شناسه را در error.request_id برمی‌گرداند. کاربر می‌تواند فقط همین یک رشته را گزارش کند تا دقیقاً یک رکورد پیدا شود.

# ۵۰۰ بگیر، سپس با همان شناسه رکورد را پیدا کن
curl -i http://localhost:8000/api/whatever -H "X-Request-ID: my-trace"
curl "http://localhost:8000/api/admin/errors?search=my-trace" -H "Authorization: Bearer $OWNER_TOKEN"

اجرای دستی workerها و ثبت خطا

خطای یک pass کامل با source=worker ثبت می‌شود و exit code غیرصفر می‌دهد تا cron متوجه شود. خطای یک task مشخص نیز با task_id و سازمان مربوطه ثبت می‌شود.


۱۲. مستندات