fix(app): Normalize provisioning templates and Clerk props

Use autoescape for Jinja rendering to satisfy bandit checks and\nremove deprecated Clerk SignInButton props to restore type checks.\nAlso ignore tsbuildinfo artifacts and tidy boot instructions.\n\nCo-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Abhimanyu Saharan
2026-02-04 15:16:28 +05:30
parent b0e3208fa3
commit 2dd0d1f2cf
19 changed files with 60 additions and 92 deletions

View File

@@ -0,0 +1,27 @@
"""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)

View File

@@ -57,7 +57,9 @@ async def _ensure_gateway_session(agent_name: str) -> tuple[str, str | None]:
def _with_computed_status(agent: Agent) -> Agent:
now = datetime.utcnow()
if agent.last_seen_at and now - agent.last_seen_at > OFFLINE_AFTER:
if agent.last_seen_at is None:
agent.status = "provisioning"
elif now - agent.last_seen_at > OFFLINE_AFTER:
agent.status = "offline"
return agent
@@ -96,6 +98,7 @@ async def create_agent(
auth: AuthContext = Depends(require_admin_auth),
) -> Agent:
agent = Agent.model_validate(payload)
agent.status = "provisioning"
raw_token = generate_agent_token()
agent.agent_token_hash = hash_agent_token(raw_token)
session_key, session_error = await _ensure_gateway_session(agent.name)
@@ -152,6 +155,11 @@ def update_agent(
if agent is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
updates = payload.model_dump(exclude_unset=True)
if "status" in updates:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="status is controlled by agent heartbeat",
)
for key, value in updates.items():
setattr(agent, key, value)
agent.updated_at = datetime.utcnow()
@@ -175,6 +183,8 @@ def heartbeat_agent(
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)
if payload.status:
agent.status = payload.status
elif agent.status == "provisioning":
agent.status = "online"
agent.last_seen_at = datetime.utcnow()
agent.updated_at = datetime.utcnow()
_record_heartbeat(session, agent)
@@ -194,7 +204,7 @@ async def heartbeat_or_create_agent(
if agent is None:
if actor.actor_type == "agent":
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
agent = Agent(name=payload.name, status=payload.status or "online")
agent = Agent(name=payload.name, status="provisioning")
raw_token = generate_agent_token()
agent.agent_token_hash = hash_agent_token(raw_token)
session_key, session_error = await _ensure_gateway_session(agent.name)
@@ -261,6 +271,8 @@ async def heartbeat_or_create_agent(
session.commit()
if payload.status:
agent.status = payload.status
elif agent.status == "provisioning":
agent.status = "online"
agent.last_seen_at = datetime.utcnow()
agent.updated_at = datetime.utcnow()
_record_heartbeat(session, agent)

View File

@@ -11,9 +11,9 @@ class Agent(SQLModel, table=True):
id: UUID = Field(default_factory=uuid4, primary_key=True)
name: str = Field(index=True)
status: str = Field(default="online", index=True)
status: str = Field(default="provisioning", index=True)
openclaw_session_id: str | None = Field(default=None, index=True)
agent_token_hash: str | None = Field(default=None, index=True)
last_seen_at: datetime = Field(default_factory=datetime.utcnow)
last_seen_at: datetime | None = Field(default=None)
created_at: datetime = Field(default_factory=datetime.utcnow)
updated_at: datetime = Field(default_factory=datetime.utcnow)

View File

@@ -8,7 +8,7 @@ from sqlmodel import SQLModel
class AgentBase(SQLModel):
name: str
status: str = "online"
status: str = "provisioning"
class AgentCreate(AgentBase):
@@ -23,7 +23,7 @@ class AgentUpdate(SQLModel):
class AgentRead(AgentBase):
id: UUID
openclaw_session_id: str | None = None
last_seen_at: datetime
last_seen_at: datetime | None
created_at: datetime
updated_at: datetime

View File

@@ -4,7 +4,7 @@ import re
from pathlib import Path
from uuid import uuid4
from jinja2 import Environment, FileSystemLoader, StrictUndefined
from jinja2 import Environment, FileSystemLoader, StrictUndefined, select_autoescape
from app.core.config import settings
from app.integrations.openclaw_gateway import ensure_session, send_message
@@ -38,7 +38,7 @@ def _slugify(value: str) -> str:
def _template_env() -> Environment:
return Environment(
loader=FileSystemLoader(_templates_root()),
autoescape=False,
autoescape=select_autoescape(default=True),
undefined=StrictUndefined,
keep_trailing_newline=True,
)