feat(agents): Add identity and soul template fields to board creation
This commit is contained in:
@@ -1,27 +0,0 @@
|
||||
"""add agent heartbeat config column
|
||||
|
||||
Revision ID: 2b4c2f7b3eda
|
||||
Revises: 69858cb75533
|
||||
Create Date: 2026-02-04 16:36:55.587762
|
||||
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from alembic import op
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '2b4c2f7b3eda'
|
||||
down_revision = '69858cb75533'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.execute(
|
||||
"ALTER TABLE agents ADD COLUMN IF NOT EXISTS heartbeat_config JSON"
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.execute("ALTER TABLE agents DROP COLUMN IF EXISTS heartbeat_config")
|
||||
@@ -1,574 +0,0 @@
|
||||
"""init
|
||||
|
||||
Revision ID: 5630abfa60f8
|
||||
Revises:
|
||||
Create Date: 2026-02-03 17:52:47.887105
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
import sqlmodel
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "5630abfa60f8"
|
||||
down_revision = None
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table(
|
||||
"orgs",
|
||||
sa.Column("id", sa.Uuid(), nullable=False),
|
||||
sa.Column("name", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||
sa.Column("slug", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
op.create_index(op.f("ix_orgs_slug"), "orgs", ["slug"], unique=True)
|
||||
op.create_table(
|
||||
"users",
|
||||
sa.Column("id", sa.Uuid(), nullable=False),
|
||||
sa.Column("clerk_user_id", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||
sa.Column("email", sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
||||
sa.Column("name", sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
||||
sa.Column("is_super_admin", sa.Boolean(), nullable=False),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
op.create_index(op.f("ix_users_clerk_user_id"), "users", ["clerk_user_id"], unique=True)
|
||||
op.create_index(op.f("ix_users_email"), "users", ["email"], unique=False)
|
||||
op.create_table(
|
||||
"workspaces",
|
||||
sa.Column("id", sa.Uuid(), nullable=False),
|
||||
sa.Column("org_id", sa.Uuid(), nullable=False),
|
||||
sa.Column("name", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||
sa.Column("slug", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||
sa.ForeignKeyConstraint(
|
||||
["org_id"],
|
||||
["orgs.id"],
|
||||
),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
op.create_index(op.f("ix_workspaces_org_id"), "workspaces", ["org_id"], unique=False)
|
||||
op.create_index(op.f("ix_workspaces_slug"), "workspaces", ["slug"], unique=False)
|
||||
op.create_table(
|
||||
"agents",
|
||||
sa.Column("org_id", sa.Uuid(), nullable=False),
|
||||
sa.Column("workspace_id", sa.Uuid(), nullable=False),
|
||||
sa.Column("id", sa.Uuid(), nullable=False),
|
||||
sa.Column("name", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||
sa.Column("role", sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
||||
sa.Column("status", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||
sa.Column("openclaw_session_id", sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
||||
sa.Column("api_token_hash", sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
||||
sa.Column("api_token_last_used_at", sa.DateTime(), nullable=True),
|
||||
sa.Column("created_at", sa.DateTime(), nullable=False),
|
||||
sa.Column("updated_at", sa.DateTime(), nullable=False),
|
||||
sa.ForeignKeyConstraint(
|
||||
["org_id"],
|
||||
["orgs.id"],
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["workspace_id"],
|
||||
["workspaces.id"],
|
||||
),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
op.create_index(
|
||||
op.f("ix_agents_openclaw_session_id"), "agents", ["openclaw_session_id"], unique=False
|
||||
)
|
||||
op.create_index(op.f("ix_agents_org_id"), "agents", ["org_id"], unique=False)
|
||||
op.create_index(op.f("ix_agents_workspace_id"), "agents", ["workspace_id"], unique=False)
|
||||
op.create_table(
|
||||
"gateway_configs",
|
||||
sa.Column("id", sa.Uuid(), nullable=False),
|
||||
sa.Column("org_id", sa.Uuid(), nullable=False),
|
||||
sa.Column("workspace_id", sa.Uuid(), nullable=True),
|
||||
sa.Column("name", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||
sa.Column("base_url", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||
sa.Column("token", sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
||||
sa.Column("created_at", sa.DateTime(), nullable=False),
|
||||
sa.ForeignKeyConstraint(
|
||||
["org_id"],
|
||||
["orgs.id"],
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["workspace_id"],
|
||||
["workspaces.id"],
|
||||
),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
op.create_index(op.f("ix_gateway_configs_org_id"), "gateway_configs", ["org_id"], unique=False)
|
||||
op.create_index(
|
||||
op.f("ix_gateway_configs_workspace_id"), "gateway_configs", ["workspace_id"], unique=False
|
||||
)
|
||||
op.create_table(
|
||||
"memberships",
|
||||
sa.Column("org_id", sa.Uuid(), nullable=False),
|
||||
sa.Column("workspace_id", sa.Uuid(), nullable=False),
|
||||
sa.Column("id", sa.Uuid(), nullable=False),
|
||||
sa.Column("user_id", sa.Uuid(), nullable=False),
|
||||
sa.Column("role", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||
sa.Column("created_at", sa.DateTime(), nullable=False),
|
||||
sa.ForeignKeyConstraint(
|
||||
["org_id"],
|
||||
["orgs.id"],
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["user_id"],
|
||||
["users.id"],
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["workspace_id"],
|
||||
["workspaces.id"],
|
||||
),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
op.create_index(op.f("ix_memberships_org_id"), "memberships", ["org_id"], unique=False)
|
||||
op.create_index(op.f("ix_memberships_user_id"), "memberships", ["user_id"], unique=False)
|
||||
op.create_index(
|
||||
op.f("ix_memberships_workspace_id"), "memberships", ["workspace_id"], unique=False
|
||||
)
|
||||
op.create_table(
|
||||
"orchestration_templates",
|
||||
sa.Column("org_id", sa.Uuid(), nullable=False),
|
||||
sa.Column("workspace_id", sa.Uuid(), nullable=False),
|
||||
sa.Column("id", sa.Uuid(), nullable=False),
|
||||
sa.Column("kind", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||
sa.Column("title", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||
sa.Column("template_markdown", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||
sa.Column("created_at", sa.DateTime(), nullable=False),
|
||||
sa.Column("updated_at", sa.DateTime(), nullable=False),
|
||||
sa.ForeignKeyConstraint(
|
||||
["org_id"],
|
||||
["orgs.id"],
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["workspace_id"],
|
||||
["workspaces.id"],
|
||||
),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
op.create_index(
|
||||
op.f("ix_orchestration_templates_kind"), "orchestration_templates", ["kind"], unique=False
|
||||
)
|
||||
op.create_index(
|
||||
op.f("ix_orchestration_templates_org_id"),
|
||||
"orchestration_templates",
|
||||
["org_id"],
|
||||
unique=False,
|
||||
)
|
||||
op.create_index(
|
||||
op.f("ix_orchestration_templates_workspace_id"),
|
||||
"orchestration_templates",
|
||||
["workspace_id"],
|
||||
unique=False,
|
||||
)
|
||||
op.create_table(
|
||||
"projects",
|
||||
sa.Column("org_id", sa.Uuid(), nullable=False),
|
||||
sa.Column("workspace_id", sa.Uuid(), nullable=False),
|
||||
sa.Column("id", sa.Uuid(), nullable=False),
|
||||
sa.Column("name", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||
sa.Column("description", sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
||||
sa.Column("status", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||
sa.Column("created_at", sa.DateTime(), nullable=False),
|
||||
sa.Column("updated_at", sa.DateTime(), nullable=False),
|
||||
sa.ForeignKeyConstraint(
|
||||
["org_id"],
|
||||
["orgs.id"],
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["workspace_id"],
|
||||
["workspaces.id"],
|
||||
),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
op.create_index(op.f("ix_projects_org_id"), "projects", ["org_id"], unique=False)
|
||||
op.create_index(op.f("ix_projects_status"), "projects", ["status"], unique=False)
|
||||
op.create_index(op.f("ix_projects_workspace_id"), "projects", ["workspace_id"], unique=False)
|
||||
op.create_table(
|
||||
"workspace_api_tokens",
|
||||
sa.Column("org_id", sa.Uuid(), nullable=False),
|
||||
sa.Column("workspace_id", sa.Uuid(), nullable=False),
|
||||
sa.Column("id", sa.Uuid(), nullable=False),
|
||||
sa.Column("token_hash", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||
sa.Column("label", sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
||||
sa.Column("last_used_at", sa.DateTime(), nullable=True),
|
||||
sa.Column("created_at", sa.DateTime(), nullable=False),
|
||||
sa.ForeignKeyConstraint(
|
||||
["org_id"],
|
||||
["orgs.id"],
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["workspace_id"],
|
||||
["workspaces.id"],
|
||||
),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
op.create_index(
|
||||
op.f("ix_workspace_api_tokens_org_id"), "workspace_api_tokens", ["org_id"], unique=False
|
||||
)
|
||||
op.create_index(
|
||||
op.f("ix_workspace_api_tokens_token_hash"),
|
||||
"workspace_api_tokens",
|
||||
["token_hash"],
|
||||
unique=True,
|
||||
)
|
||||
op.create_index(
|
||||
op.f("ix_workspace_api_tokens_workspace_id"),
|
||||
"workspace_api_tokens",
|
||||
["workspace_id"],
|
||||
unique=False,
|
||||
)
|
||||
op.create_table(
|
||||
"openclaw_sessions",
|
||||
sa.Column("org_id", sa.Uuid(), nullable=False),
|
||||
sa.Column("workspace_id", sa.Uuid(), nullable=False),
|
||||
sa.Column("id", sa.Uuid(), nullable=False),
|
||||
sa.Column("session_id", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||
sa.Column("agent_id", sa.Uuid(), nullable=True),
|
||||
sa.Column("status", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||
sa.Column("last_seen_at", sa.DateTime(), nullable=True),
|
||||
sa.Column("created_at", sa.DateTime(), nullable=False),
|
||||
sa.ForeignKeyConstraint(
|
||||
["agent_id"],
|
||||
["agents.id"],
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["org_id"],
|
||||
["orgs.id"],
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["workspace_id"],
|
||||
["workspaces.id"],
|
||||
),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
op.create_index(
|
||||
op.f("ix_openclaw_sessions_org_id"), "openclaw_sessions", ["org_id"], unique=False
|
||||
)
|
||||
op.create_index(
|
||||
op.f("ix_openclaw_sessions_session_id"), "openclaw_sessions", ["session_id"], unique=True
|
||||
)
|
||||
op.create_index(
|
||||
op.f("ix_openclaw_sessions_status"), "openclaw_sessions", ["status"], unique=False
|
||||
)
|
||||
op.create_index(
|
||||
op.f("ix_openclaw_sessions_workspace_id"),
|
||||
"openclaw_sessions",
|
||||
["workspace_id"],
|
||||
unique=False,
|
||||
)
|
||||
op.create_table(
|
||||
"tasks",
|
||||
sa.Column("org_id", sa.Uuid(), nullable=False),
|
||||
sa.Column("workspace_id", sa.Uuid(), nullable=False),
|
||||
sa.Column("id", sa.Uuid(), nullable=False),
|
||||
sa.Column("project_id", sa.Uuid(), nullable=False),
|
||||
sa.Column("title", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||
sa.Column("description", sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
||||
sa.Column("status", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||
sa.Column("priority", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||
sa.Column("due_at", sa.DateTime(), nullable=True),
|
||||
sa.Column("assigned_agent_id", sa.Uuid(), nullable=True),
|
||||
sa.Column("created_by_user_id", sa.Uuid(), nullable=True),
|
||||
sa.Column("created_at", sa.DateTime(), nullable=False),
|
||||
sa.Column("updated_at", sa.DateTime(), nullable=False),
|
||||
sa.ForeignKeyConstraint(
|
||||
["assigned_agent_id"],
|
||||
["agents.id"],
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["created_by_user_id"],
|
||||
["users.id"],
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["org_id"],
|
||||
["orgs.id"],
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["project_id"],
|
||||
["projects.id"],
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["workspace_id"],
|
||||
["workspaces.id"],
|
||||
),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
op.create_index(
|
||||
op.f("ix_tasks_assigned_agent_id"), "tasks", ["assigned_agent_id"], unique=False
|
||||
)
|
||||
op.create_index(
|
||||
op.f("ix_tasks_created_by_user_id"), "tasks", ["created_by_user_id"], unique=False
|
||||
)
|
||||
op.create_index(op.f("ix_tasks_org_id"), "tasks", ["org_id"], unique=False)
|
||||
op.create_index(op.f("ix_tasks_priority"), "tasks", ["priority"], unique=False)
|
||||
op.create_index(op.f("ix_tasks_project_id"), "tasks", ["project_id"], unique=False)
|
||||
op.create_index(op.f("ix_tasks_status"), "tasks", ["status"], unique=False)
|
||||
op.create_index(op.f("ix_tasks_workspace_id"), "tasks", ["workspace_id"], unique=False)
|
||||
op.create_table(
|
||||
"task_activities",
|
||||
sa.Column("org_id", sa.Uuid(), nullable=False),
|
||||
sa.Column("workspace_id", sa.Uuid(), nullable=False),
|
||||
sa.Column("id", sa.Uuid(), nullable=False),
|
||||
sa.Column("task_id", sa.Uuid(), nullable=False),
|
||||
sa.Column("activity_type", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||
sa.Column("message", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||
sa.Column("actor_user_id", sa.Uuid(), nullable=True),
|
||||
sa.Column("actor_agent_id", sa.Uuid(), nullable=True),
|
||||
sa.Column("created_at", sa.DateTime(), nullable=False),
|
||||
sa.ForeignKeyConstraint(
|
||||
["actor_agent_id"],
|
||||
["agents.id"],
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["actor_user_id"],
|
||||
["users.id"],
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["org_id"],
|
||||
["orgs.id"],
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["task_id"],
|
||||
["tasks.id"],
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["workspace_id"],
|
||||
["workspaces.id"],
|
||||
),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
op.create_index(op.f("ix_task_activities_org_id"), "task_activities", ["org_id"], unique=False)
|
||||
op.create_index(
|
||||
op.f("ix_task_activities_task_id"), "task_activities", ["task_id"], unique=False
|
||||
)
|
||||
op.create_index(
|
||||
op.f("ix_task_activities_workspace_id"), "task_activities", ["workspace_id"], unique=False
|
||||
)
|
||||
op.create_table(
|
||||
"task_deliverables",
|
||||
sa.Column("org_id", sa.Uuid(), nullable=False),
|
||||
sa.Column("workspace_id", sa.Uuid(), nullable=False),
|
||||
sa.Column("id", sa.Uuid(), nullable=False),
|
||||
sa.Column("task_id", sa.Uuid(), nullable=False),
|
||||
sa.Column("title", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||
sa.Column("markdown_content", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||
sa.Column("created_by_user_id", sa.Uuid(), nullable=True),
|
||||
sa.Column("created_by_agent_id", sa.Uuid(), nullable=True),
|
||||
sa.Column("created_at", sa.DateTime(), nullable=False),
|
||||
sa.ForeignKeyConstraint(
|
||||
["created_by_agent_id"],
|
||||
["agents.id"],
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["created_by_user_id"],
|
||||
["users.id"],
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["org_id"],
|
||||
["orgs.id"],
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["task_id"],
|
||||
["tasks.id"],
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["workspace_id"],
|
||||
["workspaces.id"],
|
||||
),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
op.create_index(
|
||||
op.f("ix_task_deliverables_org_id"), "task_deliverables", ["org_id"], unique=False
|
||||
)
|
||||
op.create_index(
|
||||
op.f("ix_task_deliverables_task_id"), "task_deliverables", ["task_id"], unique=False
|
||||
)
|
||||
op.create_index(
|
||||
op.f("ix_task_deliverables_workspace_id"),
|
||||
"task_deliverables",
|
||||
["workspace_id"],
|
||||
unique=False,
|
||||
)
|
||||
op.create_table(
|
||||
"task_status_history",
|
||||
sa.Column("org_id", sa.Uuid(), nullable=False),
|
||||
sa.Column("workspace_id", sa.Uuid(), nullable=False),
|
||||
sa.Column("id", sa.Uuid(), nullable=False),
|
||||
sa.Column("task_id", sa.Uuid(), nullable=False),
|
||||
sa.Column("from_status", sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
||||
sa.Column("to_status", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||
sa.Column("actor_user_id", sa.Uuid(), nullable=True),
|
||||
sa.Column("actor_agent_id", sa.Uuid(), nullable=True),
|
||||
sa.Column("created_at", sa.DateTime(), nullable=False),
|
||||
sa.ForeignKeyConstraint(
|
||||
["actor_agent_id"],
|
||||
["agents.id"],
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["actor_user_id"],
|
||||
["users.id"],
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["org_id"],
|
||||
["orgs.id"],
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["task_id"],
|
||||
["tasks.id"],
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["workspace_id"],
|
||||
["workspaces.id"],
|
||||
),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
op.create_index(
|
||||
op.f("ix_task_status_history_org_id"), "task_status_history", ["org_id"], unique=False
|
||||
)
|
||||
op.create_index(
|
||||
op.f("ix_task_status_history_task_id"), "task_status_history", ["task_id"], unique=False
|
||||
)
|
||||
op.create_index(
|
||||
op.f("ix_task_status_history_workspace_id"),
|
||||
"task_status_history",
|
||||
["workspace_id"],
|
||||
unique=False,
|
||||
)
|
||||
op.create_table(
|
||||
"task_subagents",
|
||||
sa.Column("org_id", sa.Uuid(), nullable=False),
|
||||
sa.Column("workspace_id", sa.Uuid(), nullable=False),
|
||||
sa.Column("id", sa.Uuid(), nullable=False),
|
||||
sa.Column("task_id", sa.Uuid(), nullable=False),
|
||||
sa.Column("agent_name", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||
sa.Column("openclaw_session_id", sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
||||
sa.Column("created_at", sa.DateTime(), nullable=False),
|
||||
sa.ForeignKeyConstraint(
|
||||
["org_id"],
|
||||
["orgs.id"],
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["task_id"],
|
||||
["tasks.id"],
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["workspace_id"],
|
||||
["workspaces.id"],
|
||||
),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
op.create_index(op.f("ix_task_subagents_org_id"), "task_subagents", ["org_id"], unique=False)
|
||||
op.create_index(op.f("ix_task_subagents_task_id"), "task_subagents", ["task_id"], unique=False)
|
||||
op.create_index(
|
||||
op.f("ix_task_subagents_workspace_id"), "task_subagents", ["workspace_id"], unique=False
|
||||
)
|
||||
op.create_table(
|
||||
"transcripts",
|
||||
sa.Column("org_id", sa.Uuid(), nullable=False),
|
||||
sa.Column("workspace_id", sa.Uuid(), nullable=False),
|
||||
sa.Column("id", sa.Uuid(), nullable=False),
|
||||
sa.Column("task_id", sa.Uuid(), nullable=True),
|
||||
sa.Column("session_id", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||
sa.Column("full_text", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||
sa.Column("created_at", sa.DateTime(), nullable=False),
|
||||
sa.ForeignKeyConstraint(
|
||||
["org_id"],
|
||||
["orgs.id"],
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["task_id"],
|
||||
["tasks.id"],
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["workspace_id"],
|
||||
["workspaces.id"],
|
||||
),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
op.create_index(op.f("ix_transcripts_org_id"), "transcripts", ["org_id"], unique=False)
|
||||
op.create_index(op.f("ix_transcripts_session_id"), "transcripts", ["session_id"], unique=False)
|
||||
op.create_index(op.f("ix_transcripts_task_id"), "transcripts", ["task_id"], unique=False)
|
||||
op.create_index(
|
||||
op.f("ix_transcripts_workspace_id"), "transcripts", ["workspace_id"], unique=False
|
||||
)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_index(op.f("ix_transcripts_workspace_id"), table_name="transcripts")
|
||||
op.drop_index(op.f("ix_transcripts_task_id"), table_name="transcripts")
|
||||
op.drop_index(op.f("ix_transcripts_session_id"), table_name="transcripts")
|
||||
op.drop_index(op.f("ix_transcripts_org_id"), table_name="transcripts")
|
||||
op.drop_table("transcripts")
|
||||
op.drop_index(op.f("ix_task_subagents_workspace_id"), table_name="task_subagents")
|
||||
op.drop_index(op.f("ix_task_subagents_task_id"), table_name="task_subagents")
|
||||
op.drop_index(op.f("ix_task_subagents_org_id"), table_name="task_subagents")
|
||||
op.drop_table("task_subagents")
|
||||
op.drop_index(op.f("ix_task_status_history_workspace_id"), table_name="task_status_history")
|
||||
op.drop_index(op.f("ix_task_status_history_task_id"), table_name="task_status_history")
|
||||
op.drop_index(op.f("ix_task_status_history_org_id"), table_name="task_status_history")
|
||||
op.drop_table("task_status_history")
|
||||
op.drop_index(op.f("ix_task_deliverables_workspace_id"), table_name="task_deliverables")
|
||||
op.drop_index(op.f("ix_task_deliverables_task_id"), table_name="task_deliverables")
|
||||
op.drop_index(op.f("ix_task_deliverables_org_id"), table_name="task_deliverables")
|
||||
op.drop_table("task_deliverables")
|
||||
op.drop_index(op.f("ix_task_activities_workspace_id"), table_name="task_activities")
|
||||
op.drop_index(op.f("ix_task_activities_task_id"), table_name="task_activities")
|
||||
op.drop_index(op.f("ix_task_activities_org_id"), table_name="task_activities")
|
||||
op.drop_table("task_activities")
|
||||
op.drop_index(op.f("ix_tasks_workspace_id"), table_name="tasks")
|
||||
op.drop_index(op.f("ix_tasks_status"), table_name="tasks")
|
||||
op.drop_index(op.f("ix_tasks_project_id"), table_name="tasks")
|
||||
op.drop_index(op.f("ix_tasks_priority"), table_name="tasks")
|
||||
op.drop_index(op.f("ix_tasks_org_id"), table_name="tasks")
|
||||
op.drop_index(op.f("ix_tasks_created_by_user_id"), table_name="tasks")
|
||||
op.drop_index(op.f("ix_tasks_assigned_agent_id"), table_name="tasks")
|
||||
op.drop_table("tasks")
|
||||
op.drop_index(op.f("ix_openclaw_sessions_workspace_id"), table_name="openclaw_sessions")
|
||||
op.drop_index(op.f("ix_openclaw_sessions_status"), table_name="openclaw_sessions")
|
||||
op.drop_index(op.f("ix_openclaw_sessions_session_id"), table_name="openclaw_sessions")
|
||||
op.drop_index(op.f("ix_openclaw_sessions_org_id"), table_name="openclaw_sessions")
|
||||
op.drop_table("openclaw_sessions")
|
||||
op.drop_index(op.f("ix_workspace_api_tokens_workspace_id"), table_name="workspace_api_tokens")
|
||||
op.drop_index(op.f("ix_workspace_api_tokens_token_hash"), table_name="workspace_api_tokens")
|
||||
op.drop_index(op.f("ix_workspace_api_tokens_org_id"), table_name="workspace_api_tokens")
|
||||
op.drop_table("workspace_api_tokens")
|
||||
op.drop_index(op.f("ix_projects_workspace_id"), table_name="projects")
|
||||
op.drop_index(op.f("ix_projects_status"), table_name="projects")
|
||||
op.drop_index(op.f("ix_projects_org_id"), table_name="projects")
|
||||
op.drop_table("projects")
|
||||
op.drop_index(
|
||||
op.f("ix_orchestration_templates_workspace_id"), table_name="orchestration_templates"
|
||||
)
|
||||
op.drop_index(op.f("ix_orchestration_templates_org_id"), table_name="orchestration_templates")
|
||||
op.drop_index(op.f("ix_orchestration_templates_kind"), table_name="orchestration_templates")
|
||||
op.drop_table("orchestration_templates")
|
||||
op.drop_index(op.f("ix_memberships_workspace_id"), table_name="memberships")
|
||||
op.drop_index(op.f("ix_memberships_user_id"), table_name="memberships")
|
||||
op.drop_index(op.f("ix_memberships_org_id"), table_name="memberships")
|
||||
op.drop_table("memberships")
|
||||
op.drop_index(op.f("ix_gateway_configs_workspace_id"), table_name="gateway_configs")
|
||||
op.drop_index(op.f("ix_gateway_configs_org_id"), table_name="gateway_configs")
|
||||
op.drop_table("gateway_configs")
|
||||
op.drop_index(op.f("ix_agents_workspace_id"), table_name="agents")
|
||||
op.drop_index(op.f("ix_agents_org_id"), table_name="agents")
|
||||
op.drop_index(op.f("ix_agents_openclaw_session_id"), table_name="agents")
|
||||
op.drop_table("agents")
|
||||
op.drop_index(op.f("ix_workspaces_slug"), table_name="workspaces")
|
||||
op.drop_index(op.f("ix_workspaces_org_id"), table_name="workspaces")
|
||||
op.drop_table("workspaces")
|
||||
op.drop_index(op.f("ix_users_email"), table_name="users")
|
||||
op.drop_index(op.f("ix_users_clerk_user_id"), table_name="users")
|
||||
op.drop_table("users")
|
||||
op.drop_index(op.f("ix_orgs_slug"), table_name="orgs")
|
||||
op.drop_table("orgs")
|
||||
# ### end Alembic commands ###
|
||||
@@ -1,29 +0,0 @@
|
||||
"""add agent heartbeat config
|
||||
|
||||
Revision ID: 69858cb75533
|
||||
Revises: f1a2b3c4d5e6
|
||||
Create Date: 2026-02-04 16:32:42.028772
|
||||
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from alembic import op
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '69858cb75533'
|
||||
down_revision = 'f1a2b3c4d5e6'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.execute(
|
||||
"ALTER TABLE agents ADD COLUMN IF NOT EXISTS heartbeat_config JSON"
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.execute(
|
||||
"ALTER TABLE agents DROP COLUMN IF EXISTS heartbeat_config"
|
||||
)
|
||||
@@ -1,41 +0,0 @@
|
||||
"""add agent provision confirmation
|
||||
|
||||
Revision ID: 6df47d330227
|
||||
Revises: e0f28e965fa5
|
||||
Create Date: 2026-02-04 17:16:44.472239
|
||||
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from alembic import op
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '6df47d330227'
|
||||
down_revision = 'e0f28e965fa5'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.execute(
|
||||
"ALTER TABLE agents ADD COLUMN IF NOT EXISTS provision_requested_at TIMESTAMP"
|
||||
)
|
||||
op.execute(
|
||||
"ALTER TABLE agents ADD COLUMN IF NOT EXISTS provision_confirm_token_hash VARCHAR"
|
||||
)
|
||||
op.execute(
|
||||
"ALTER TABLE agents ADD COLUMN IF NOT EXISTS provision_action VARCHAR"
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.execute(
|
||||
"ALTER TABLE agents DROP COLUMN IF EXISTS provision_action"
|
||||
)
|
||||
op.execute(
|
||||
"ALTER TABLE agents DROP COLUMN IF EXISTS provision_confirm_token_hash"
|
||||
)
|
||||
op.execute(
|
||||
"ALTER TABLE agents DROP COLUMN IF EXISTS provision_requested_at"
|
||||
)
|
||||
@@ -1,64 +0,0 @@
|
||||
"""add boards and task board id
|
||||
|
||||
Revision ID: 7e3d9b8c1f4a
|
||||
Revises: 5630abfa60f8
|
||||
Create Date: 2026-02-03 20:12:00.000000
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
import sqlmodel
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "7e3d9b8c1f4a"
|
||||
down_revision = "5630abfa60f8"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.create_table(
|
||||
"boards",
|
||||
sa.Column("org_id", sa.Uuid(), nullable=False),
|
||||
sa.Column("workspace_id", sa.Uuid(), nullable=False),
|
||||
sa.Column("id", sa.Uuid(), nullable=False),
|
||||
sa.Column("name", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||
sa.Column("slug", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||
sa.Column("created_at", sa.DateTime(), nullable=False),
|
||||
sa.Column("updated_at", sa.DateTime(), nullable=False),
|
||||
sa.ForeignKeyConstraint(
|
||||
["org_id"],
|
||||
["orgs.id"],
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["workspace_id"],
|
||||
["workspaces.id"],
|
||||
),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
op.create_index(op.f("ix_boards_org_id"), "boards", ["org_id"], unique=False)
|
||||
op.create_index(
|
||||
op.f("ix_boards_workspace_id"), "boards", ["workspace_id"], unique=False
|
||||
)
|
||||
op.create_index(op.f("ix_boards_slug"), "boards", ["slug"], unique=False)
|
||||
|
||||
op.add_column("tasks", sa.Column("board_id", sa.Uuid(), nullable=True))
|
||||
op.create_index(op.f("ix_tasks_board_id"), "tasks", ["board_id"], unique=False)
|
||||
op.create_foreign_key(
|
||||
"fk_tasks_board_id_boards", "tasks", "boards", ["board_id"], ["id"]
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_constraint("fk_tasks_board_id_boards", "tasks", type_="foreignkey")
|
||||
op.drop_index(op.f("ix_tasks_board_id"), table_name="tasks")
|
||||
op.drop_column("tasks", "board_id")
|
||||
|
||||
op.drop_index(op.f("ix_boards_slug"), table_name="boards")
|
||||
op.drop_index(op.f("ix_boards_workspace_id"), table_name="boards")
|
||||
op.drop_index(op.f("ix_boards_org_id"), table_name="boards")
|
||||
op.drop_table("boards")
|
||||
@@ -1,36 +0,0 @@
|
||||
"""add task assigned agent
|
||||
|
||||
Revision ID: 8045fbfb157f
|
||||
Revises: 6df47d330227
|
||||
Create Date: 2026-02-04 17:28:57.465934
|
||||
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from alembic import op
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '8045fbfb157f'
|
||||
down_revision = '6df47d330227'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.execute(
|
||||
"ALTER TABLE tasks ADD COLUMN IF NOT EXISTS assigned_agent_id UUID"
|
||||
)
|
||||
op.execute(
|
||||
"ALTER TABLE tasks ADD CONSTRAINT IF NOT EXISTS tasks_assigned_agent_id_fkey "
|
||||
"FOREIGN KEY (assigned_agent_id) REFERENCES agents(id)"
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.execute(
|
||||
"ALTER TABLE tasks DROP CONSTRAINT IF EXISTS tasks_assigned_agent_id_fkey"
|
||||
)
|
||||
op.execute(
|
||||
"ALTER TABLE tasks DROP COLUMN IF EXISTS assigned_agent_id"
|
||||
)
|
||||
@@ -1,63 +0,0 @@
|
||||
"""drop projects and task project_id
|
||||
|
||||
Revision ID: 8b6d1b8f4b21
|
||||
Revises: 7e3d9b8c1f4a
|
||||
Create Date: 2026-02-03 23:05:00.000000
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
import sqlmodel
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "8b6d1b8f4b21"
|
||||
down_revision = "7e3d9b8c1f4a"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.drop_constraint("tasks_project_id_fkey", "tasks", type_="foreignkey")
|
||||
op.drop_index(op.f("ix_tasks_project_id"), table_name="tasks")
|
||||
op.drop_column("tasks", "project_id")
|
||||
|
||||
op.drop_index(op.f("ix_projects_workspace_id"), table_name="projects")
|
||||
op.drop_index(op.f("ix_projects_status"), table_name="projects")
|
||||
op.drop_index(op.f("ix_projects_org_id"), table_name="projects")
|
||||
op.drop_table("projects")
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.create_table(
|
||||
"projects",
|
||||
sa.Column("org_id", sa.Uuid(), nullable=False),
|
||||
sa.Column("workspace_id", sa.Uuid(), nullable=False),
|
||||
sa.Column("id", sa.Uuid(), nullable=False),
|
||||
sa.Column("name", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||
sa.Column("description", sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
||||
sa.Column("status", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||
sa.Column("created_at", sa.DateTime(), nullable=False),
|
||||
sa.Column("updated_at", sa.DateTime(), nullable=False),
|
||||
sa.ForeignKeyConstraint(
|
||||
["org_id"],
|
||||
["orgs.id"],
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["workspace_id"],
|
||||
["workspaces.id"],
|
||||
),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
op.create_index(op.f("ix_projects_org_id"), "projects", ["org_id"], unique=False)
|
||||
op.create_index(op.f("ix_projects_status"), "projects", ["status"], unique=False)
|
||||
op.create_index(op.f("ix_projects_workspace_id"), "projects", ["workspace_id"], unique=False)
|
||||
|
||||
op.add_column("tasks", sa.Column("project_id", sa.Uuid(), nullable=True))
|
||||
op.create_index(op.f("ix_tasks_project_id"), "tasks", ["project_id"], unique=False)
|
||||
op.create_foreign_key(
|
||||
"tasks_project_id_fkey", "tasks", "projects", ["project_id"], ["id"]
|
||||
)
|
||||
147
backend/alembic/versions/939a1d2dc607_init.py
Normal file
147
backend/alembic/versions/939a1d2dc607_init.py
Normal file
@@ -0,0 +1,147 @@
|
||||
"""init
|
||||
|
||||
Revision ID: 939a1d2dc607
|
||||
Revises:
|
||||
Create Date: 2026-02-04 19:34:33.600751
|
||||
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
import sqlmodel
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '939a1d2dc607'
|
||||
down_revision = None
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table('boards',
|
||||
sa.Column('id', sa.Uuid(), nullable=False),
|
||||
sa.Column('name', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||
sa.Column('slug', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||
sa.Column('gateway_url', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
||||
sa.Column('gateway_token', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
||||
sa.Column('gateway_main_session_key', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
||||
sa.Column('gateway_workspace_root', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
||||
sa.Column('identity_template', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
||||
sa.Column('soul_template', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
||||
sa.Column('created_at', sa.DateTime(), nullable=False),
|
||||
sa.Column('updated_at', sa.DateTime(), nullable=False),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_index(op.f('ix_boards_slug'), 'boards', ['slug'], unique=False)
|
||||
op.create_table('users',
|
||||
sa.Column('id', sa.Uuid(), nullable=False),
|
||||
sa.Column('clerk_user_id', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||
sa.Column('email', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
||||
sa.Column('name', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
||||
sa.Column('preferred_name', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
||||
sa.Column('pronouns', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
||||
sa.Column('timezone', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
||||
sa.Column('notes', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
||||
sa.Column('context', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
||||
sa.Column('is_super_admin', sa.Boolean(), nullable=False),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_index(op.f('ix_users_clerk_user_id'), 'users', ['clerk_user_id'], unique=True)
|
||||
op.create_index(op.f('ix_users_email'), 'users', ['email'], unique=False)
|
||||
op.create_table('agents',
|
||||
sa.Column('id', sa.Uuid(), nullable=False),
|
||||
sa.Column('board_id', sa.Uuid(), nullable=True),
|
||||
sa.Column('name', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||
sa.Column('status', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||
sa.Column('openclaw_session_id', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
||||
sa.Column('agent_token_hash', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
||||
sa.Column('heartbeat_config', sa.JSON(), nullable=True),
|
||||
sa.Column('provision_requested_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('provision_confirm_token_hash', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
||||
sa.Column('provision_action', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
||||
sa.Column('delete_requested_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('delete_confirm_token_hash', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
||||
sa.Column('last_seen_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('created_at', sa.DateTime(), nullable=False),
|
||||
sa.Column('updated_at', sa.DateTime(), nullable=False),
|
||||
sa.ForeignKeyConstraint(['board_id'], ['boards.id'], ),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_index(op.f('ix_agents_agent_token_hash'), 'agents', ['agent_token_hash'], unique=False)
|
||||
op.create_index(op.f('ix_agents_board_id'), 'agents', ['board_id'], unique=False)
|
||||
op.create_index(op.f('ix_agents_delete_confirm_token_hash'), 'agents', ['delete_confirm_token_hash'], unique=False)
|
||||
op.create_index(op.f('ix_agents_name'), 'agents', ['name'], unique=False)
|
||||
op.create_index(op.f('ix_agents_openclaw_session_id'), 'agents', ['openclaw_session_id'], unique=False)
|
||||
op.create_index(op.f('ix_agents_provision_action'), 'agents', ['provision_action'], unique=False)
|
||||
op.create_index(op.f('ix_agents_provision_confirm_token_hash'), 'agents', ['provision_confirm_token_hash'], unique=False)
|
||||
op.create_index(op.f('ix_agents_status'), 'agents', ['status'], unique=False)
|
||||
op.create_table('tasks',
|
||||
sa.Column('id', sa.Uuid(), nullable=False),
|
||||
sa.Column('board_id', sa.Uuid(), nullable=True),
|
||||
sa.Column('title', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||
sa.Column('description', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
||||
sa.Column('status', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||
sa.Column('priority', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||
sa.Column('due_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('in_progress_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('created_by_user_id', sa.Uuid(), nullable=True),
|
||||
sa.Column('assigned_agent_id', sa.Uuid(), nullable=True),
|
||||
sa.Column('created_at', sa.DateTime(), nullable=False),
|
||||
sa.Column('updated_at', sa.DateTime(), nullable=False),
|
||||
sa.ForeignKeyConstraint(['assigned_agent_id'], ['agents.id'], ),
|
||||
sa.ForeignKeyConstraint(['board_id'], ['boards.id'], ),
|
||||
sa.ForeignKeyConstraint(['created_by_user_id'], ['users.id'], ),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_index(op.f('ix_tasks_assigned_agent_id'), 'tasks', ['assigned_agent_id'], unique=False)
|
||||
op.create_index(op.f('ix_tasks_board_id'), 'tasks', ['board_id'], unique=False)
|
||||
op.create_index(op.f('ix_tasks_created_by_user_id'), 'tasks', ['created_by_user_id'], unique=False)
|
||||
op.create_index(op.f('ix_tasks_priority'), 'tasks', ['priority'], unique=False)
|
||||
op.create_index(op.f('ix_tasks_status'), 'tasks', ['status'], unique=False)
|
||||
op.create_table('activity_events',
|
||||
sa.Column('id', sa.Uuid(), nullable=False),
|
||||
sa.Column('event_type', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||
sa.Column('message', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
||||
sa.Column('agent_id', sa.Uuid(), nullable=True),
|
||||
sa.Column('task_id', sa.Uuid(), nullable=True),
|
||||
sa.Column('created_at', sa.DateTime(), nullable=False),
|
||||
sa.ForeignKeyConstraint(['agent_id'], ['agents.id'], ),
|
||||
sa.ForeignKeyConstraint(['task_id'], ['tasks.id'], ),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_index(op.f('ix_activity_events_agent_id'), 'activity_events', ['agent_id'], unique=False)
|
||||
op.create_index(op.f('ix_activity_events_event_type'), 'activity_events', ['event_type'], unique=False)
|
||||
op.create_index(op.f('ix_activity_events_task_id'), 'activity_events', ['task_id'], unique=False)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_index(op.f('ix_activity_events_task_id'), table_name='activity_events')
|
||||
op.drop_index(op.f('ix_activity_events_event_type'), table_name='activity_events')
|
||||
op.drop_index(op.f('ix_activity_events_agent_id'), table_name='activity_events')
|
||||
op.drop_table('activity_events')
|
||||
op.drop_index(op.f('ix_tasks_status'), table_name='tasks')
|
||||
op.drop_index(op.f('ix_tasks_priority'), table_name='tasks')
|
||||
op.drop_index(op.f('ix_tasks_created_by_user_id'), table_name='tasks')
|
||||
op.drop_index(op.f('ix_tasks_board_id'), table_name='tasks')
|
||||
op.drop_index(op.f('ix_tasks_assigned_agent_id'), table_name='tasks')
|
||||
op.drop_table('tasks')
|
||||
op.drop_index(op.f('ix_agents_status'), table_name='agents')
|
||||
op.drop_index(op.f('ix_agents_provision_confirm_token_hash'), table_name='agents')
|
||||
op.drop_index(op.f('ix_agents_provision_action'), table_name='agents')
|
||||
op.drop_index(op.f('ix_agents_openclaw_session_id'), table_name='agents')
|
||||
op.drop_index(op.f('ix_agents_name'), table_name='agents')
|
||||
op.drop_index(op.f('ix_agents_delete_confirm_token_hash'), table_name='agents')
|
||||
op.drop_index(op.f('ix_agents_board_id'), table_name='agents')
|
||||
op.drop_index(op.f('ix_agents_agent_token_hash'), table_name='agents')
|
||||
op.drop_table('agents')
|
||||
op.drop_index(op.f('ix_users_email'), table_name='users')
|
||||
op.drop_index(op.f('ix_users_clerk_user_id'), table_name='users')
|
||||
op.drop_table('users')
|
||||
op.drop_index(op.f('ix_boards_slug'), table_name='boards')
|
||||
op.drop_table('boards')
|
||||
# ### end Alembic commands ###
|
||||
@@ -1,56 +0,0 @@
|
||||
"""drop tenancy tables and columns
|
||||
|
||||
Revision ID: 9c4f1a2b3d4e
|
||||
Revises: 8b6d1b8f4b21
|
||||
Create Date: 2026-02-03 23:35:00.000000
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from alembic import op
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "9c4f1a2b3d4e"
|
||||
down_revision = "8b6d1b8f4b21"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.drop_table("task_subagents")
|
||||
op.drop_table("task_status_history")
|
||||
op.drop_table("task_deliverables")
|
||||
op.drop_table("task_activities")
|
||||
op.drop_table("transcripts")
|
||||
op.drop_table("openclaw_sessions")
|
||||
op.drop_table("workspace_api_tokens")
|
||||
op.drop_table("orchestration_templates")
|
||||
op.drop_table("memberships")
|
||||
op.drop_table("gateway_configs")
|
||||
|
||||
op.drop_constraint("tasks_assigned_agent_id_fkey", "tasks", type_="foreignkey")
|
||||
op.drop_constraint("tasks_org_id_fkey", "tasks", type_="foreignkey")
|
||||
op.drop_constraint("tasks_workspace_id_fkey", "tasks", type_="foreignkey")
|
||||
op.drop_index(op.f("ix_tasks_assigned_agent_id"), table_name="tasks")
|
||||
op.drop_index(op.f("ix_tasks_org_id"), table_name="tasks")
|
||||
op.drop_index(op.f("ix_tasks_workspace_id"), table_name="tasks")
|
||||
op.drop_column("tasks", "assigned_agent_id")
|
||||
op.drop_column("tasks", "org_id")
|
||||
op.drop_column("tasks", "workspace_id")
|
||||
|
||||
op.drop_constraint("boards_org_id_fkey", "boards", type_="foreignkey")
|
||||
op.drop_constraint("boards_workspace_id_fkey", "boards", type_="foreignkey")
|
||||
op.drop_index(op.f("ix_boards_org_id"), table_name="boards")
|
||||
op.drop_index(op.f("ix_boards_workspace_id"), table_name="boards")
|
||||
op.drop_column("boards", "org_id")
|
||||
op.drop_column("boards", "workspace_id")
|
||||
|
||||
op.drop_table("agents")
|
||||
op.drop_table("workspaces")
|
||||
op.drop_table("orgs")
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
raise NotImplementedError("Downgrade not supported for simplified tenancy removal.")
|
||||
@@ -1,70 +0,0 @@
|
||||
"""add agents and activity events
|
||||
|
||||
Revision ID: a1b2c3d4e5f6
|
||||
Revises: 9c4f1a2b3d4e
|
||||
Create Date: 2026-02-03 23:50:00.000000
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
import sqlmodel
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "a1b2c3d4e5f6"
|
||||
down_revision = "9c4f1a2b3d4e"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.create_table(
|
||||
"agents",
|
||||
sa.Column("id", sa.Uuid(), nullable=False),
|
||||
sa.Column("name", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||
sa.Column("status", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||
sa.Column("last_seen_at", sa.DateTime(), nullable=False),
|
||||
sa.Column("created_at", sa.DateTime(), nullable=False),
|
||||
sa.Column("updated_at", sa.DateTime(), nullable=False),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
op.create_index(op.f("ix_agents_name"), "agents", ["name"], unique=False)
|
||||
op.create_index(op.f("ix_agents_status"), "agents", ["status"], unique=False)
|
||||
|
||||
op.create_table(
|
||||
"activity_events",
|
||||
sa.Column("id", sa.Uuid(), nullable=False),
|
||||
sa.Column("event_type", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||
sa.Column("message", sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
||||
sa.Column("agent_id", sa.Uuid(), nullable=True),
|
||||
sa.Column("task_id", sa.Uuid(), nullable=True),
|
||||
sa.Column("created_at", sa.DateTime(), nullable=False),
|
||||
sa.ForeignKeyConstraint(["agent_id"], ["agents.id"]),
|
||||
sa.ForeignKeyConstraint(["task_id"], ["tasks.id"]),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
op.create_index(
|
||||
op.f("ix_activity_events_agent_id"), "activity_events", ["agent_id"], unique=False
|
||||
)
|
||||
op.create_index(
|
||||
op.f("ix_activity_events_event_type"),
|
||||
"activity_events",
|
||||
["event_type"],
|
||||
unique=False,
|
||||
)
|
||||
op.create_index(
|
||||
op.f("ix_activity_events_task_id"), "activity_events", ["task_id"], unique=False
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_index(op.f("ix_activity_events_task_id"), table_name="activity_events")
|
||||
op.drop_index(op.f("ix_activity_events_event_type"), table_name="activity_events")
|
||||
op.drop_index(op.f("ix_activity_events_agent_id"), table_name="activity_events")
|
||||
op.drop_table("activity_events")
|
||||
op.drop_index(op.f("ix_agents_status"), table_name="agents")
|
||||
op.drop_index(op.f("ix_agents_name"), table_name="agents")
|
||||
op.drop_table("agents")
|
||||
@@ -1,29 +0,0 @@
|
||||
"""add task comments index
|
||||
|
||||
Revision ID: b9d22e2a4d50
|
||||
Revises: 8045fbfb157f
|
||||
Create Date: 2026-02-04 17:32:06.204331
|
||||
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from alembic import op
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'b9d22e2a4d50'
|
||||
down_revision = '8045fbfb157f'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.execute(
|
||||
"CREATE INDEX IF NOT EXISTS ix_activity_events_task_comment "
|
||||
"ON activity_events (task_id, created_at) "
|
||||
"WHERE event_type = 'task.comment'"
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.execute("DROP INDEX IF EXISTS ix_activity_events_task_comment")
|
||||
@@ -1,31 +0,0 @@
|
||||
"""add task in_progress_at
|
||||
|
||||
Revision ID: c1a2b3c4d5e7
|
||||
Revises: b9d22e2a4d50
|
||||
Create Date: 2026-02-04 13:34:25.000000
|
||||
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from alembic import op
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "c1a2b3c4d5e7"
|
||||
down_revision = "b9d22e2a4d50"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.execute(
|
||||
"ALTER TABLE tasks ADD COLUMN IF NOT EXISTS in_progress_at TIMESTAMP WITHOUT TIME ZONE"
|
||||
)
|
||||
op.execute(
|
||||
"CREATE INDEX IF NOT EXISTS ix_tasks_in_progress_at ON tasks (in_progress_at)"
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.execute("DROP INDEX IF EXISTS ix_tasks_in_progress_at")
|
||||
op.execute("ALTER TABLE tasks DROP COLUMN IF EXISTS in_progress_at")
|
||||
@@ -1,38 +0,0 @@
|
||||
"""add agent openclaw session id
|
||||
|
||||
Revision ID: c7f0a2b1d4e3
|
||||
Revises: a1b2c3d4e5f6
|
||||
Create Date: 2026-02-04 02:20:00.000000
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
import sqlmodel
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "c7f0a2b1d4e3"
|
||||
down_revision = "a1b2c3d4e5f6"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.add_column(
|
||||
"agents",
|
||||
sa.Column("openclaw_session_id", sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
||||
)
|
||||
op.create_index(
|
||||
op.f("ix_agents_openclaw_session_id"),
|
||||
"agents",
|
||||
["openclaw_session_id"],
|
||||
unique=False,
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_index(op.f("ix_agents_openclaw_session_id"), table_name="agents")
|
||||
op.drop_column("agents", "openclaw_session_id")
|
||||
@@ -1,29 +0,0 @@
|
||||
"""ensure heartbeat config column
|
||||
|
||||
Revision ID: cefef25d4634
|
||||
Revises: 2b4c2f7b3eda
|
||||
Create Date: 2026-02-04 16:38:25.234627
|
||||
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from alembic import op
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'cefef25d4634'
|
||||
down_revision = '2b4c2f7b3eda'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.execute(
|
||||
"ALTER TABLE agents ADD COLUMN IF NOT EXISTS heartbeat_config JSON"
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.execute(
|
||||
"ALTER TABLE agents DROP COLUMN IF EXISTS heartbeat_config"
|
||||
)
|
||||
@@ -1,38 +0,0 @@
|
||||
"""add agent token hash
|
||||
|
||||
Revision ID: d3e4f5a6b7c8
|
||||
Revises: c7f0a2b1d4e3
|
||||
Create Date: 2026-02-04 06:50:00.000000
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
import sqlmodel
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "d3e4f5a6b7c8"
|
||||
down_revision = "c7f0a2b1d4e3"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.add_column(
|
||||
"agents",
|
||||
sa.Column("agent_token_hash", sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
||||
)
|
||||
op.create_index(
|
||||
op.f("ix_agents_agent_token_hash"),
|
||||
"agents",
|
||||
["agent_token_hash"],
|
||||
unique=False,
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_index(op.f("ix_agents_agent_token_hash"), table_name="agents")
|
||||
op.drop_column("agents", "agent_token_hash")
|
||||
@@ -1,35 +0,0 @@
|
||||
"""add agent delete confirmation
|
||||
|
||||
Revision ID: e0f28e965fa5
|
||||
Revises: cefef25d4634
|
||||
Create Date: 2026-02-04 16:55:33.389505
|
||||
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from alembic import op
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'e0f28e965fa5'
|
||||
down_revision = 'cefef25d4634'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.execute(
|
||||
"ALTER TABLE agents ADD COLUMN IF NOT EXISTS delete_requested_at TIMESTAMP"
|
||||
)
|
||||
op.execute(
|
||||
"ALTER TABLE agents ADD COLUMN IF NOT EXISTS delete_confirm_token_hash VARCHAR"
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.execute(
|
||||
"ALTER TABLE agents DROP COLUMN IF EXISTS delete_confirm_token_hash"
|
||||
)
|
||||
op.execute(
|
||||
"ALTER TABLE agents DROP COLUMN IF EXISTS delete_requested_at"
|
||||
)
|
||||
@@ -1,27 +0,0 @@
|
||||
"""make agent last_seen_at nullable
|
||||
|
||||
Revision ID: e4f5a6b7c8d9
|
||||
Revises: d3e4f5a6b7c8
|
||||
Create Date: 2026-02-04 07:10:00.000000
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "e4f5a6b7c8d9"
|
||||
down_revision = "d3e4f5a6b7c8"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.alter_column("agents", "last_seen_at", existing_type=sa.DateTime(), nullable=True)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.alter_column("agents", "last_seen_at", existing_type=sa.DateTime(), nullable=False)
|
||||
@@ -1,47 +0,0 @@
|
||||
"""add board gateway config
|
||||
|
||||
Revision ID: f1a2b3c4d5e6
|
||||
Revises: e4f5a6b7c8d9
|
||||
Create Date: 2026-02-04 00:00:00.000000
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "f1a2b3c4d5e6"
|
||||
down_revision = "e4f5a6b7c8d9"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.add_column("boards", sa.Column("gateway_url", sa.String(), nullable=True))
|
||||
op.add_column("boards", sa.Column("gateway_token", sa.String(), nullable=True))
|
||||
op.add_column(
|
||||
"boards", sa.Column("gateway_main_session_key", sa.String(), nullable=True)
|
||||
)
|
||||
op.add_column(
|
||||
"boards", sa.Column("gateway_workspace_root", sa.String(), nullable=True)
|
||||
)
|
||||
|
||||
op.add_column("agents", sa.Column("board_id", sa.Uuid(), nullable=True))
|
||||
op.create_foreign_key(
|
||||
"agents_board_id_fkey", "agents", "boards", ["board_id"], ["id"]
|
||||
)
|
||||
op.create_index(op.f("ix_agents_board_id"), "agents", ["board_id"], unique=False)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_index(op.f("ix_agents_board_id"), table_name="agents")
|
||||
op.drop_constraint("agents_board_id_fkey", "agents", type_="foreignkey")
|
||||
op.drop_column("agents", "board_id")
|
||||
|
||||
op.drop_column("boards", "gateway_workspace_root")
|
||||
op.drop_column("boards", "gateway_main_session_key")
|
||||
op.drop_column("boards", "gateway_token")
|
||||
op.drop_column("boards", "gateway_url")
|
||||
@@ -208,7 +208,9 @@ async def create_agent(
|
||||
)
|
||||
session.commit()
|
||||
try:
|
||||
await send_provisioning_message(agent, board, raw_token, provision_token)
|
||||
await send_provisioning_message(
|
||||
agent, board, raw_token, provision_token, auth.user
|
||||
)
|
||||
record_activity(
|
||||
session,
|
||||
event_type="agent.provision.requested",
|
||||
@@ -288,7 +290,9 @@ async def update_agent(
|
||||
session.commit()
|
||||
session.refresh(agent)
|
||||
try:
|
||||
await send_update_message(agent, board, raw_token, provision_token)
|
||||
await send_update_message(
|
||||
agent, board, raw_token, provision_token, auth.user
|
||||
)
|
||||
record_activity(
|
||||
session,
|
||||
event_type="agent.update.requested",
|
||||
@@ -375,7 +379,9 @@ async def heartbeat_or_create_agent(
|
||||
)
|
||||
session.commit()
|
||||
try:
|
||||
await send_provisioning_message(agent, board, raw_token, provision_token)
|
||||
await send_provisioning_message(
|
||||
agent, board, raw_token, provision_token, actor.user
|
||||
)
|
||||
record_activity(
|
||||
session,
|
||||
event_type="agent.provision.requested",
|
||||
@@ -405,7 +411,9 @@ async def heartbeat_or_create_agent(
|
||||
try:
|
||||
board = _require_board(session, str(agent.board_id) if agent.board_id else None)
|
||||
config = _require_gateway_config(board)
|
||||
await send_provisioning_message(agent, board, raw_token, provision_token)
|
||||
await send_provisioning_message(
|
||||
agent, board, raw_token, provision_token, actor.user
|
||||
)
|
||||
record_activity(
|
||||
session,
|
||||
event_type="agent.provision.requested",
|
||||
|
||||
@@ -101,6 +101,10 @@ def create_board(
|
||||
data = payload.model_dump()
|
||||
if data.get("gateway_token") == "":
|
||||
data["gateway_token"] = None
|
||||
if data.get("identity_template") == "":
|
||||
data["identity_template"] = None
|
||||
if data.get("soul_template") == "":
|
||||
data["soul_template"] = None
|
||||
if data.get("gateway_url"):
|
||||
if not data.get("gateway_main_session_key"):
|
||||
raise HTTPException(
|
||||
@@ -137,6 +141,10 @@ def update_board(
|
||||
updates = payload.model_dump(exclude_unset=True)
|
||||
if updates.get("gateway_token") == "":
|
||||
updates["gateway_token"] = None
|
||||
if updates.get("identity_template") == "":
|
||||
updates["identity_template"] = None
|
||||
if updates.get("soul_template") == "":
|
||||
updates["soul_template"] = None
|
||||
for key, value in updates.items():
|
||||
setattr(board, key, value)
|
||||
if board.gateway_url:
|
||||
|
||||
@@ -3,7 +3,7 @@ from __future__ import annotations
|
||||
from datetime import datetime
|
||||
from uuid import UUID
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
||||
from sqlalchemy import asc, desc
|
||||
from sqlmodel import Session, col, select
|
||||
|
||||
@@ -70,11 +70,26 @@ def has_valid_recent_comment(
|
||||
|
||||
@router.get("", response_model=list[TaskRead])
|
||||
def list_tasks(
|
||||
status_filter: str | None = Query(default=None, alias="status"),
|
||||
assigned_agent_id: UUID | None = None,
|
||||
unassigned: bool | None = None,
|
||||
limit: int | None = Query(default=None, ge=1, le=200),
|
||||
board: Board = Depends(get_board_or_404),
|
||||
session: Session = Depends(get_session),
|
||||
actor: ActorContext = Depends(require_admin_or_agent),
|
||||
) -> list[Task]:
|
||||
return list(session.exec(select(Task).where(Task.board_id == board.id)))
|
||||
statement = select(Task).where(Task.board_id == board.id)
|
||||
if status_filter:
|
||||
statuses = [s.strip() for s in status_filter.split(",") if s.strip()]
|
||||
if statuses:
|
||||
statement = statement.where(col(Task.status).in_(statuses))
|
||||
if assigned_agent_id is not None:
|
||||
statement = statement.where(col(Task.assigned_agent_id) == assigned_agent_id)
|
||||
if unassigned:
|
||||
statement = statement.where(col(Task.assigned_agent_id).is_(None))
|
||||
if limit is not None:
|
||||
statement = statement.limit(limit)
|
||||
return list(session.exec(statement))
|
||||
|
||||
|
||||
@router.post("", response_model=TaskRead)
|
||||
|
||||
36
backend/app/api/users.py
Normal file
36
backend/app/api/users.py
Normal file
@@ -0,0 +1,36 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlmodel import Session
|
||||
|
||||
from app.core.auth import AuthContext, get_auth_context
|
||||
from app.db.session import get_session
|
||||
from app.models.users import User
|
||||
from app.schemas.users import UserRead, UserUpdate
|
||||
|
||||
router = APIRouter(prefix="/users", tags=["users"])
|
||||
|
||||
|
||||
@router.get("/me", response_model=UserRead)
|
||||
async def get_me(auth: AuthContext = Depends(get_auth_context)) -> UserRead:
|
||||
if auth.actor_type != "user" or auth.user is None:
|
||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)
|
||||
return UserRead.model_validate(auth.user)
|
||||
|
||||
|
||||
@router.patch("/me", response_model=UserRead)
|
||||
async def update_me(
|
||||
payload: UserUpdate,
|
||||
session: Session = Depends(get_session),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
) -> UserRead:
|
||||
if auth.actor_type != "user" or auth.user is None:
|
||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)
|
||||
updates = payload.model_dump(exclude_unset=True)
|
||||
user: User = auth.user
|
||||
for key, value in updates.items():
|
||||
setattr(user, key, value)
|
||||
session.add(user)
|
||||
session.commit()
|
||||
session.refresh(user)
|
||||
return UserRead.model_validate(user)
|
||||
@@ -25,5 +25,10 @@ class Settings(BaseSettings):
|
||||
# Database lifecycle
|
||||
db_auto_migrate: bool = False
|
||||
|
||||
# Logging
|
||||
log_level: str = "INFO"
|
||||
log_format: str = "text"
|
||||
log_use_utc: bool = False
|
||||
|
||||
|
||||
settings = Settings()
|
||||
|
||||
@@ -1,14 +1,171 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
from datetime import datetime, timezone
|
||||
from typing import Any
|
||||
|
||||
from app.core.config import settings
|
||||
from app.core.version import APP_NAME, APP_VERSION
|
||||
|
||||
TRACE_LEVEL = 5
|
||||
logging.addLevelName(TRACE_LEVEL, "TRACE")
|
||||
|
||||
|
||||
def _trace(self: logging.Logger, message: str, *args: Any, **kwargs: Any) -> None:
|
||||
if self.isEnabledFor(TRACE_LEVEL):
|
||||
self._log(TRACE_LEVEL, message, args, **kwargs)
|
||||
|
||||
|
||||
logging.Logger.trace = _trace # type: ignore[attr-defined]
|
||||
|
||||
_STANDARD_LOG_RECORD_ATTRS = {
|
||||
"args",
|
||||
"asctime",
|
||||
"created",
|
||||
"exc_info",
|
||||
"exc_text",
|
||||
"filename",
|
||||
"funcName",
|
||||
"levelname",
|
||||
"levelno",
|
||||
"lineno",
|
||||
"module",
|
||||
"msecs",
|
||||
"message",
|
||||
"msg",
|
||||
"name",
|
||||
"pathname",
|
||||
"process",
|
||||
"processName",
|
||||
"relativeCreated",
|
||||
"stack_info",
|
||||
"thread",
|
||||
"threadName",
|
||||
"taskName",
|
||||
"app",
|
||||
"version",
|
||||
}
|
||||
|
||||
|
||||
class AppLogFilter(logging.Filter):
|
||||
def __init__(self, app_name: str, version: str) -> None:
|
||||
super().__init__()
|
||||
self._app_name = app_name
|
||||
self._version = version
|
||||
|
||||
def filter(self, record: logging.LogRecord) -> bool:
|
||||
record.app = self._app_name
|
||||
record.version = self._version
|
||||
return True
|
||||
|
||||
|
||||
class JsonFormatter(logging.Formatter):
|
||||
def format(self, record: logging.LogRecord) -> str:
|
||||
payload: dict[str, Any] = {
|
||||
"timestamp": datetime.fromtimestamp(
|
||||
record.created, tz=timezone.utc
|
||||
).isoformat(),
|
||||
"level": record.levelname,
|
||||
"logger": record.name,
|
||||
"message": record.getMessage(),
|
||||
"app": getattr(record, "app", APP_NAME),
|
||||
"version": getattr(record, "version", APP_VERSION),
|
||||
"module": record.module,
|
||||
"function": record.funcName,
|
||||
"line": record.lineno,
|
||||
}
|
||||
if record.exc_info:
|
||||
payload["exception"] = self.formatException(record.exc_info)
|
||||
if record.stack_info:
|
||||
payload["stack"] = self.formatStack(record.stack_info)
|
||||
for key, value in record.__dict__.items():
|
||||
if key in _STANDARD_LOG_RECORD_ATTRS or key in payload:
|
||||
continue
|
||||
payload[key] = value
|
||||
return json.dumps(payload, separators=(",", ":"), default=str)
|
||||
|
||||
|
||||
class KeyValueFormatter(logging.Formatter):
|
||||
def format(self, record: logging.LogRecord) -> str:
|
||||
base = super().format(record)
|
||||
extras = {
|
||||
key: value
|
||||
for key, value in record.__dict__.items()
|
||||
if key not in _STANDARD_LOG_RECORD_ATTRS
|
||||
}
|
||||
if not extras:
|
||||
return base
|
||||
extra_bits = " ".join(f"{key}={value}" for key, value in extras.items())
|
||||
return f"{base} {extra_bits}"
|
||||
|
||||
|
||||
class AppLogger:
|
||||
_configured = False
|
||||
|
||||
@classmethod
|
||||
def _resolve_level(cls) -> tuple[str, int]:
|
||||
level_name = (settings.log_level or os.getenv("LOG_LEVEL", "INFO")).upper()
|
||||
if level_name == "TRACE":
|
||||
return level_name, TRACE_LEVEL
|
||||
if level_name.isdigit():
|
||||
return level_name, int(level_name)
|
||||
return level_name, logging._nameToLevel.get(level_name, logging.INFO)
|
||||
|
||||
@classmethod
|
||||
def configure(cls, *, force: bool = False) -> None:
|
||||
if cls._configured and not force:
|
||||
return
|
||||
|
||||
level_name, level = cls._resolve_level()
|
||||
|
||||
handler = logging.StreamHandler(sys.stdout)
|
||||
handler.addFilter(AppLogFilter(APP_NAME, APP_VERSION))
|
||||
format_name = (settings.log_format or "text").lower()
|
||||
if format_name == "json":
|
||||
formatter: logging.Formatter = JsonFormatter()
|
||||
else:
|
||||
formatter = KeyValueFormatter(
|
||||
"%(asctime)s %(levelname)s %(name)s %(message)s app=%(app)s version=%(version)s"
|
||||
)
|
||||
if settings.log_use_utc:
|
||||
formatter.converter = time.gmtime
|
||||
handler.setFormatter(formatter)
|
||||
|
||||
root = logging.getLogger()
|
||||
root.setLevel(level)
|
||||
root.handlers.clear()
|
||||
root.addHandler(handler)
|
||||
|
||||
# Uvicorn & HTTP clients
|
||||
for logger_name in ("uvicorn", "uvicorn.error", "uvicorn.access"):
|
||||
logging.getLogger(logger_name).setLevel(level)
|
||||
logging.getLogger("httpx").setLevel(logging.WARNING)
|
||||
logging.getLogger("httpcore").setLevel(logging.WARNING)
|
||||
|
||||
# SQL logs only at TRACE
|
||||
sql_loggers = ("sqlalchemy", "sqlalchemy.engine", "sqlalchemy.pool")
|
||||
if level_name == "TRACE":
|
||||
for name in sql_loggers:
|
||||
logger = logging.getLogger(name)
|
||||
logger.disabled = False
|
||||
logger.setLevel(logging.INFO)
|
||||
else:
|
||||
for name in sql_loggers:
|
||||
logger = logging.getLogger(name)
|
||||
logger.disabled = True
|
||||
|
||||
cls._configured = True
|
||||
|
||||
@classmethod
|
||||
def get_logger(cls, name: str | None = None) -> logging.Logger:
|
||||
if not cls._configured:
|
||||
cls.configure()
|
||||
return logging.getLogger(name)
|
||||
|
||||
|
||||
def configure_logging() -> None:
|
||||
level_name = os.getenv("LOG_LEVEL", "INFO").upper()
|
||||
level = logging._nameToLevel.get(level_name, logging.INFO)
|
||||
logging.basicConfig(
|
||||
level=level,
|
||||
format="%(asctime)s %(levelname)s %(name)s %(message)s",
|
||||
force=True,
|
||||
)
|
||||
AppLogger.configure()
|
||||
|
||||
2
backend/app/core/version.py
Normal file
2
backend/app/core/version.py
Normal file
@@ -0,0 +1,2 @@
|
||||
APP_NAME = "mission-control"
|
||||
APP_VERSION = "0.1.0"
|
||||
@@ -9,6 +9,7 @@ from app.api.auth import router as auth_router
|
||||
from app.api.boards import router as boards_router
|
||||
from app.api.gateway import router as gateway_router
|
||||
from app.api.tasks import router as tasks_router
|
||||
from app.api.users import router as users_router
|
||||
from app.core.config import settings
|
||||
from app.core.logging import configure_logging
|
||||
from app.db.session import init_db
|
||||
@@ -38,6 +39,16 @@ def health() -> dict[str, bool]:
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
@app.get("/healthz")
|
||||
def healthz() -> dict[str, bool]:
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
@app.get("/readyz")
|
||||
def readyz() -> dict[str, bool]:
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
api_v1 = APIRouter(prefix="/api/v1")
|
||||
api_v1.include_router(auth_router)
|
||||
api_v1.include_router(agents_router)
|
||||
@@ -45,4 +56,5 @@ api_v1.include_router(activity_router)
|
||||
api_v1.include_router(gateway_router)
|
||||
api_v1.include_router(boards_router)
|
||||
api_v1.include_router(tasks_router)
|
||||
api_v1.include_router(users_router)
|
||||
app.include_router(api_v1)
|
||||
|
||||
@@ -18,5 +18,7 @@ class Board(TenantScoped, table=True):
|
||||
gateway_token: str | None = Field(default=None)
|
||||
gateway_main_session_key: str | None = Field(default=None)
|
||||
gateway_workspace_root: str | None = Field(default=None)
|
||||
identity_template: str | None = Field(default=None)
|
||||
soul_template: str | None = Field(default=None)
|
||||
created_at: datetime = Field(default_factory=datetime.utcnow)
|
||||
updated_at: datetime = Field(default_factory=datetime.utcnow)
|
||||
|
||||
@@ -12,4 +12,9 @@ class User(SQLModel, table=True):
|
||||
clerk_user_id: str = Field(index=True, unique=True)
|
||||
email: str | None = Field(default=None, index=True)
|
||||
name: str | None = None
|
||||
preferred_name: str | None = None
|
||||
pronouns: str | None = None
|
||||
timezone: str | None = None
|
||||
notes: str | None = None
|
||||
context: str | None = None
|
||||
is_super_admin: bool = Field(default=False)
|
||||
|
||||
@@ -2,7 +2,7 @@ from app.schemas.activity_events import ActivityEventRead
|
||||
from app.schemas.agents import AgentCreate, AgentRead, AgentUpdate
|
||||
from app.schemas.boards import BoardCreate, BoardRead, BoardUpdate
|
||||
from app.schemas.tasks import TaskCreate, TaskRead, TaskUpdate
|
||||
from app.schemas.users import UserCreate, UserRead
|
||||
from app.schemas.users import UserCreate, UserRead, UserUpdate
|
||||
|
||||
__all__ = [
|
||||
"ActivityEventRead",
|
||||
@@ -17,4 +17,5 @@ __all__ = [
|
||||
"TaskUpdate",
|
||||
"UserCreate",
|
||||
"UserRead",
|
||||
"UserUpdate",
|
||||
]
|
||||
|
||||
@@ -12,6 +12,8 @@ class BoardBase(SQLModel):
|
||||
gateway_url: str | None = None
|
||||
gateway_main_session_key: str | None = None
|
||||
gateway_workspace_root: str | None = None
|
||||
identity_template: str | None = None
|
||||
soul_template: str | None = None
|
||||
|
||||
|
||||
class BoardCreate(BoardBase):
|
||||
@@ -25,6 +27,8 @@ class BoardUpdate(SQLModel):
|
||||
gateway_token: str | None = None
|
||||
gateway_main_session_key: str | None = None
|
||||
gateway_workspace_root: str | None = None
|
||||
identity_template: str | None = None
|
||||
soul_template: str | None = None
|
||||
|
||||
|
||||
class BoardRead(BoardBase):
|
||||
|
||||
@@ -9,12 +9,26 @@ class UserBase(SQLModel):
|
||||
clerk_user_id: str
|
||||
email: str | None = None
|
||||
name: str | None = None
|
||||
preferred_name: str | None = None
|
||||
pronouns: str | None = None
|
||||
timezone: str | None = None
|
||||
notes: str | None = None
|
||||
context: str | None = None
|
||||
|
||||
|
||||
class UserCreate(UserBase):
|
||||
pass
|
||||
|
||||
|
||||
class UserUpdate(SQLModel):
|
||||
name: str | None = None
|
||||
preferred_name: str | None = None
|
||||
pronouns: str | None = None
|
||||
timezone: str | None = None
|
||||
notes: str | None = None
|
||||
context: str | None = None
|
||||
|
||||
|
||||
class UserRead(UserBase):
|
||||
id: UUID
|
||||
is_super_admin: bool
|
||||
|
||||
@@ -12,6 +12,7 @@ from app.core.config import settings
|
||||
from app.integrations.openclaw_gateway import GatewayConfig, ensure_session, send_message
|
||||
from app.models.agents import Agent
|
||||
from app.models.boards import Board
|
||||
from app.models.users import User
|
||||
|
||||
TEMPLATE_FILES = [
|
||||
"AGENTS.md",
|
||||
@@ -64,11 +65,18 @@ def _template_env() -> Environment:
|
||||
)
|
||||
|
||||
|
||||
def _read_templates(context: dict[str, str]) -> dict[str, str]:
|
||||
def _read_templates(
|
||||
context: dict[str, str], overrides: dict[str, str] | None = None
|
||||
) -> dict[str, str]:
|
||||
env = _template_env()
|
||||
templates: dict[str, str] = {}
|
||||
override_map = overrides or {}
|
||||
for name in TEMPLATE_FILES:
|
||||
path = _templates_root() / name
|
||||
override = override_map.get(name)
|
||||
if override:
|
||||
templates[name] = env.from_string(override).render(**context).strip()
|
||||
continue
|
||||
if not path.exists():
|
||||
templates[name] = ""
|
||||
continue
|
||||
@@ -90,7 +98,9 @@ def _workspace_path(agent_name: str, workspace_root: str) -> str:
|
||||
return f"{root}/workspace-{_slugify(agent_name)}"
|
||||
|
||||
|
||||
def _build_context(agent: Agent, board: Board, auth_token: str) -> dict[str, str]:
|
||||
def _build_context(
|
||||
agent: Agent, board: Board, auth_token: str, user: User | None
|
||||
) -> dict[str, str]:
|
||||
if not board.gateway_workspace_root:
|
||||
raise ValueError("gateway_workspace_root is required")
|
||||
if not board.gateway_main_session_key:
|
||||
@@ -111,25 +121,32 @@ def _build_context(agent: Agent, board: Board, auth_token: str) -> dict[str, str
|
||||
"auth_token": auth_token,
|
||||
"main_session_key": main_session_key,
|
||||
"workspace_root": workspace_root,
|
||||
"user_name": "Unset",
|
||||
"user_preferred_name": "Unset",
|
||||
"user_timezone": "Unset",
|
||||
"user_notes": "Fill in user context.",
|
||||
"user_name": user.name if user else "",
|
||||
"user_preferred_name": user.preferred_name if user else "",
|
||||
"user_pronouns": user.pronouns if user else "",
|
||||
"user_timezone": user.timezone if user else "",
|
||||
"user_notes": user.notes if user else "",
|
||||
"user_context": user.context if user else "",
|
||||
}
|
||||
|
||||
|
||||
def _build_file_blocks(context: dict[str, str]) -> str:
|
||||
templates = _read_templates(context)
|
||||
def _build_file_blocks(context: dict[str, str], board: Board) -> str:
|
||||
overrides: dict[str, str] = {}
|
||||
if board.identity_template:
|
||||
overrides["IDENTITY.md"] = board.identity_template
|
||||
if board.soul_template:
|
||||
overrides["SOUL.md"] = board.soul_template
|
||||
templates = _read_templates(context, overrides=overrides)
|
||||
return "".join(
|
||||
_render_file_block(name, templates.get(name, "")) for name in TEMPLATE_FILES
|
||||
)
|
||||
|
||||
|
||||
def build_provisioning_message(
|
||||
agent: Agent, board: Board, auth_token: str, confirm_token: str
|
||||
agent: Agent, board: Board, auth_token: str, confirm_token: str, user: User | None
|
||||
) -> str:
|
||||
context = _build_context(agent, board, auth_token)
|
||||
file_blocks = _build_file_blocks(context)
|
||||
context = _build_context(agent, board, auth_token, user)
|
||||
file_blocks = _build_file_blocks(context, board)
|
||||
heartbeat_snippet = json.dumps(
|
||||
{
|
||||
"id": _agent_key(agent),
|
||||
@@ -173,10 +190,10 @@ def build_provisioning_message(
|
||||
|
||||
|
||||
def build_update_message(
|
||||
agent: Agent, board: Board, auth_token: str, confirm_token: str
|
||||
agent: Agent, board: Board, auth_token: str, confirm_token: str, user: User | None
|
||||
) -> str:
|
||||
context = _build_context(agent, board, auth_token)
|
||||
file_blocks = _build_file_blocks(context)
|
||||
context = _build_context(agent, board, auth_token, user)
|
||||
file_blocks = _build_file_blocks(context, board)
|
||||
heartbeat_snippet = json.dumps(
|
||||
{
|
||||
"id": _agent_key(agent),
|
||||
@@ -223,6 +240,7 @@ async def send_provisioning_message(
|
||||
board: Board,
|
||||
auth_token: str,
|
||||
confirm_token: str,
|
||||
user: User | None,
|
||||
) -> None:
|
||||
if not board.gateway_url:
|
||||
return
|
||||
@@ -231,7 +249,7 @@ async def send_provisioning_message(
|
||||
main_session = board.gateway_main_session_key
|
||||
config = GatewayConfig(url=board.gateway_url, token=board.gateway_token)
|
||||
await ensure_session(main_session, config=config, label="Main Agent")
|
||||
message = build_provisioning_message(agent, board, auth_token, confirm_token)
|
||||
message = build_provisioning_message(agent, board, auth_token, confirm_token, user)
|
||||
await send_message(message, session_key=main_session, config=config, deliver=False)
|
||||
|
||||
|
||||
@@ -240,6 +258,7 @@ async def send_update_message(
|
||||
board: Board,
|
||||
auth_token: str,
|
||||
confirm_token: str,
|
||||
user: User | None,
|
||||
) -> None:
|
||||
if not board.gateway_url:
|
||||
return
|
||||
@@ -248,5 +267,5 @@ async def send_update_message(
|
||||
main_session = board.gateway_main_session_key
|
||||
config = GatewayConfig(url=board.gateway_url, token=board.gateway_token)
|
||||
await ensure_session(main_session, config=config, label="Main Agent")
|
||||
message = build_update_message(agent, board, auth_token, confirm_token)
|
||||
message = build_update_message(agent, board, auth_token, confirm_token, user)
|
||||
await send_message(message, session_key=main_session, config=config, deliver=False)
|
||||
|
||||
Reference in New Issue
Block a user