نسخهٔ وب مستقل — پلتفرم نیروی کار هوش مصنوعی

مخزن اختصاصی برنامهٔ وب؛ محتوای پوشهٔ web در ریشهٔ مخزن قرار گرفت.
تاریخچهٔ پروژهٔ ربات (agy-bot) در این مخزن وجود ندارد و هیچ وابستگی runtime به آن نیست.
This commit is contained in:
mjalmoudy94
2026-10-13 00:39:28 +03:30
commit 717e53a7ce
185 changed files with 39910 additions and 0 deletions
+252
View File
@@ -0,0 +1,252 @@
"""Agent Workforce domain: employees, chat threads, tasks, runs and artifacts."""
from __future__ import annotations
from dataclasses import dataclass, field
from datetime import datetime
from enum import StrEnum
from app.domain.base import new_id, utcnow
from app.domain.identity.models import Role
class EmployeeStatus(StrEnum):
ACTIVE = "active"
PAUSED = "paused"
ARCHIVED = "archived"
class RoleKey(StrEnum):
MANAGER = "manager"
PLANNER = "planner"
DEVELOPER = "developer"
REVIEWER = "reviewer"
TESTER = "tester"
RESEARCHER = "researcher"
CONTENT_CREATOR = "content_creator"
SUPPORT = "support"
class TaskStatus(StrEnum):
NEW = "new"
TRIAGED = "triaged"
WAITING_APPROVAL = "waiting_approval"
RUNNING = "running"
DONE = "done"
FAILED = "failed"
CANCELLED = "cancelled"
class RunStatus(StrEnum):
QUEUED = "queued"
PLANNING = "planning"
WAITING_APPROVAL = "waiting_approval"
RUNNING = "running"
TOOL_CALLING = "tool_calling"
SANDBOX_RUNNING = "sandbox_running"
COMPLETED = "completed"
FAILED = "failed"
CANCELLED = "cancelled"
TIMED_OUT = "timed_out"
TERMINAL_RUN_STATUSES: frozenset[RunStatus] = frozenset(
{RunStatus.COMPLETED, RunStatus.FAILED, RunStatus.CANCELLED, RunStatus.TIMED_OUT}
)
TERMINAL_TASK_STATUSES: frozenset[TaskStatus] = frozenset(
{TaskStatus.DONE, TaskStatus.FAILED, TaskStatus.CANCELLED}
)
#: Allowed run state transitions. Anything else raises in the application layer.
RUN_TRANSITIONS: dict[RunStatus, frozenset[RunStatus]] = {
RunStatus.QUEUED: frozenset({RunStatus.PLANNING, RunStatus.CANCELLED, RunStatus.FAILED}),
RunStatus.PLANNING: frozenset(
{RunStatus.RUNNING, RunStatus.WAITING_APPROVAL, RunStatus.FAILED, RunStatus.CANCELLED}
),
RunStatus.WAITING_APPROVAL: frozenset(
{RunStatus.RUNNING, RunStatus.CANCELLED, RunStatus.FAILED, RunStatus.TIMED_OUT}
),
RunStatus.RUNNING: frozenset(
{
RunStatus.TOOL_CALLING,
RunStatus.SANDBOX_RUNNING,
RunStatus.WAITING_APPROVAL,
RunStatus.COMPLETED,
RunStatus.FAILED,
RunStatus.CANCELLED,
RunStatus.TIMED_OUT,
}
),
RunStatus.TOOL_CALLING: frozenset(
{RunStatus.RUNNING, RunStatus.WAITING_APPROVAL, RunStatus.FAILED, RunStatus.TIMED_OUT}
),
RunStatus.SANDBOX_RUNNING: frozenset(
{RunStatus.RUNNING, RunStatus.FAILED, RunStatus.CANCELLED, RunStatus.TIMED_OUT}
),
RunStatus.COMPLETED: frozenset(),
RunStatus.FAILED: frozenset(),
RunStatus.CANCELLED: frozenset(),
RunStatus.TIMED_OUT: frozenset(),
}
def can_transition_run(current: RunStatus, target: RunStatus) -> bool:
return target in RUN_TRANSITIONS.get(current, frozenset())
class MessageRole(StrEnum):
USER = "user"
ASSISTANT = "assistant"
SYSTEM = "system"
class ToolCallStatus(StrEnum):
PENDING = "pending"
AWAITING_APPROVAL = "awaiting_approval"
APPROVED = "approved"
REJECTED = "rejected"
SUCCEEDED = "succeeded"
FAILED = "failed"
@dataclass(slots=True, kw_only=True)
class AgentTemplate:
name: str
role_key: RoleKey
description: str = ""
default_prompt: str = ""
default_tools_json: list[str] = field(default_factory=list)
default_routing_policy_id: str | None = None
id: str = field(default_factory=new_id)
is_system: bool = True
created_at: datetime = field(default_factory=utcnow)
@dataclass(slots=True, kw_only=True)
class Employee:
organization_id: str
name: str
role_key: RoleKey
template_id: str | None = None
project_id: str | None = None
status: EmployeeStatus = EmployeeStatus.ACTIVE
routing_policy_id: str | None = None
budget_limit_json: dict = field(default_factory=dict)
id: str = field(default_factory=new_id)
created_at: datetime = field(default_factory=utcnow)
@property
def is_available(self) -> bool:
return self.status == EmployeeStatus.ACTIVE
@dataclass(slots=True, kw_only=True)
class PromptVersion:
employee_id: str
version: int
content: str
created_by_user_id: str | None = None
id: str = field(default_factory=new_id)
created_at: datetime = field(default_factory=utcnow)
@dataclass(slots=True, kw_only=True)
class EmployeeToolPermission:
employee_id: str
tool_key: str
allowed: bool = False
policy_json: dict = field(default_factory=dict)
id: str = field(default_factory=new_id)
@dataclass(slots=True, kw_only=True)
class Thread:
organization_id: str
title: str = ""
project_id: str | None = None
created_by_user_id: str | None = None
id: str = field(default_factory=new_id)
created_at: datetime = field(default_factory=utcnow)
@dataclass(slots=True, kw_only=True)
class Message:
thread_id: str
role: MessageRole
content: str
employee_id: str | None = None
id: str = field(default_factory=new_id)
created_at: datetime = field(default_factory=utcnow)
@dataclass(slots=True, kw_only=True)
class Task:
organization_id: str
title: str
description: str = ""
project_id: str | None = None
thread_id: str | None = None
assigned_employee_id: str | None = None
created_by_user_id: str | None = None
status: TaskStatus = TaskStatus.NEW
id: str = field(default_factory=new_id)
created_at: datetime = field(default_factory=utcnow)
updated_at: datetime = field(default_factory=utcnow)
@dataclass(slots=True, kw_only=True)
class Run:
task_id: str
employee_id: str | None = None
status: RunStatus = RunStatus.QUEUED
error: str | None = None
id: str = field(default_factory=new_id)
started_at: datetime | None = None
finished_at: datetime | None = None
created_at: datetime = field(default_factory=utcnow)
@property
def is_terminal(self) -> bool:
return self.status in TERMINAL_RUN_STATUSES
@dataclass(slots=True, kw_only=True)
class RunStep:
run_id: str
index: int
kind: str
title: str = ""
payload_json: dict = field(default_factory=dict)
id: str = field(default_factory=new_id)
created_at: datetime = field(default_factory=utcnow)
@dataclass(slots=True, kw_only=True)
class ToolCall:
run_id: str
tool_key: str
arguments_json: dict = field(default_factory=dict)
result_json: dict = field(default_factory=dict)
status: ToolCallStatus = ToolCallStatus.PENDING
requires_approval: bool = False
approved_by_user_id: str | None = None
id: str = field(default_factory=new_id)
created_at: datetime = field(default_factory=utcnow)
@dataclass(slots=True, kw_only=True)
class Artifact:
organization_id: str
name: str
kind: str = "file"
content_ref: str = ""
project_id: str | None = None
run_id: str | None = None
size_bytes: int = 0
id: str = field(default_factory=new_id)
created_at: datetime = field(default_factory=utcnow)
#: Roles that may only be created by an org owner/admin.
ELEVATED_ROLE_KEYS: frozenset[RoleKey] = frozenset({RoleKey.MANAGER})
DEFAULT_EMPLOYEE_ROLE: Role = Role.MEMBER
+169
View File
@@ -0,0 +1,169 @@
"""Tool catalog for AI employees.
Three rules are encoded here as data rather than as scattered ``if`` statements:
1. Every tool has a stable key and an explicit approval requirement.
2. Dangerous tools require human approval **before** they run — the flag lives
with the tool, so a new call site cannot forget it.
3. Nothing is allowed by default; an employee needs an explicit permission row.
This module is pure: no I/O, no framework, no database.
"""
from __future__ import annotations
from dataclasses import dataclass
class ToolKey:
"""Stable tool identifiers. These appear in audit logs and permissions."""
GIT_ENSURE_REPO = "git.ensure_repo"
GIT_CREATE_BRANCH = "git.create_branch"
GIT_OPEN_PR = "git.open_pr"
GIT_MERGE_PR = "git.merge_pr"
GIT_DELETE_REPO = "git.delete_repo"
DOMAIN_CREATE_PREVIEW = "domain.create_preview"
DOMAIN_DELETE_PREVIEW = "domain.delete_preview"
SANDBOX_RUN_COMMAND = "sandbox.run_command"
SANDBOX_READ_FILE = "sandbox.read_file"
SANDBOX_WRITE_FILE = "sandbox.write_file"
SANDBOX_DELETE_FILE = "sandbox.delete_file"
SANDBOX_DESTROY = "sandbox.destroy"
KNOWLEDGE_SEARCH = "knowledge.search"
ARTIFACT_CREATE = "artifact.create"
@dataclass(frozen=True, slots=True)
class ToolSpec:
key: str
label: str
description: str
#: Human approval is required before this tool may execute.
requires_approval: bool = False
#: Irreversible or destructive: never auto-approved, always audited.
destructive: bool = False
#: Tools that touch the outside world are the ones worth approving.
category: str = "general"
TOOL_CATALOG: tuple[ToolSpec, ...] = (
ToolSpec(
key=ToolKey.GIT_ENSURE_REPO,
label="ساخت مخزن",
description="ساخت مخزن خصوصی Git برای پروژه.",
category="git",
),
ToolSpec(
key=ToolKey.GIT_CREATE_BRANCH,
label="ساخت شاخه",
description="ساخت یک شاخهٔ جدید از شاخهٔ مشخص.",
category="git",
),
ToolSpec(
key=ToolKey.GIT_OPEN_PR,
label="باز کردن درخواست ادغام",
description="ثبت یک PR از شاخهٔ کاری به شاخهٔ پایه.",
category="git",
),
ToolSpec(
key=ToolKey.GIT_MERGE_PR,
label="ادغام درخواست ادغام",
description="ادغام PR در شاخهٔ پایه. بازگشت‌ناپذیر.",
requires_approval=True,
destructive=True,
category="git",
),
ToolSpec(
key=ToolKey.GIT_DELETE_REPO,
label="حذف مخزن",
description="حذف کامل مخزن پروژه. بازگشت‌ناپذیر.",
requires_approval=True,
destructive=True,
category="git",
),
ToolSpec(
key=ToolKey.DOMAIN_CREATE_PREVIEW,
label="ساخت دامنهٔ تست",
description="انتشار موقت پروژه روی یک زیردامنه.",
category="domain",
),
ToolSpec(
key=ToolKey.DOMAIN_DELETE_PREVIEW,
label="حذف دامنهٔ تست",
description="برداشتن دامنهٔ تست از دسترس.",
requires_approval=True,
category="domain",
),
ToolSpec(
key=ToolKey.SANDBOX_RUN_COMMAND,
label="اجرای دستور در سندباکس",
description="اجرای یک دستور داخل محیط ایزوله با محدودیت زمان و خروجی.",
category="sandbox",
),
ToolSpec(
key=ToolKey.SANDBOX_READ_FILE,
label="خواندن فایل",
description="خواندن محتوای یک فایل در سندباکس.",
category="sandbox",
),
ToolSpec(
key=ToolKey.SANDBOX_WRITE_FILE,
label="نوشتن فایل",
description="نوشتن یا بازنویسی یک فایل در سندباکس.",
category="sandbox",
),
ToolSpec(
key=ToolKey.SANDBOX_DELETE_FILE,
label="حذف فایل",
description="حذف یک فایل از سندباکس. بازگشت‌ناپذیر.",
requires_approval=True,
destructive=True,
category="sandbox",
),
ToolSpec(
key=ToolKey.SANDBOX_DESTROY,
label="نابودی سندباکس",
description="پایان دادن به نشست سندباکس و آزادسازی منابع.",
requires_approval=True,
destructive=True,
category="sandbox",
),
ToolSpec(
key=ToolKey.KNOWLEDGE_SEARCH,
label="جست‌وجو در دانش",
description="جست‌وجو در پایگاه دانش سازمان.",
category="knowledge",
),
ToolSpec(
key=ToolKey.ARTIFACT_CREATE,
label="ساخت خروجی",
description="ثبت یک سند یا خروجی قابل مشاهده برای کاربر.",
category="artifact",
),
)
_BY_KEY: dict[str, ToolSpec] = {spec.key: spec for spec in TOOL_CATALOG}
#: Templates that reference an unknown tool key would silently grant nothing;
#: this set lets a test assert the seed data and the catalog agree.
KNOWN_TOOL_KEYS: frozenset[str] = frozenset(_BY_KEY)
def get_tool_spec(key: str) -> ToolSpec | None:
return _BY_KEY.get(key)
def is_known_tool(key: str) -> bool:
return key in _BY_KEY
def tool_requires_approval(key: str) -> bool:
"""Unknown tools are treated as requiring approval, never as safe."""
spec = _BY_KEY.get(key)
return True if spec is None else spec.requires_approval
def is_destructive(key: str) -> bool:
spec = _BY_KEY.get(key)
return False if spec is None else spec.destructive