Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
69 commits
Select commit Hold shift + click to select a range
fc903dd
feat: add Mission, AgentProfile, Execution data models and migration
yuzzjj Apr 15, 2026
e527352
feat: add RuntimeProvider abstraction and Claude Code provider
yuzzjj Apr 15, 2026
f19cdf0
feat: add ExecutionService, ExecutionRepository, and snapshot reducer
yuzzjj Apr 15, 2026
59d5111
feat: add MissionService, AgentProfileService, and dispatch logic
yuzzjj Apr 15, 2026
30e6d3f
feat: add CLIContainerService and credential/skill/config injectors
yuzzjj Apr 15, 2026
49f0384
feat: add ExecutionRunner E2E orchestrator
yuzzjj Apr 15, 2026
3a63009
feat: add WebSocket execution streaming with subscribe/unsubscribe
yuzzjj Apr 15, 2026
4019194
feat: add REST API endpoints for Mission, AgentProfile, Execution
yuzzjj Apr 15, 2026
91564e1
feat: add CLI agent Docker image with Claude Code pre-installed
yuzzjj Apr 15, 2026
3450d19
feat: add integration tests for mission-to-execution pipeline
yuzzjj Apr 15, 2026
b1099b8
feat: implement mission-driven multi-agent execution architecture wit…
yuzzjj Apr 15, 2026
7342c71
fix: address critical review issues — credential handling, enum namin…
yuzzjj Apr 15, 2026
7886432
fix: correct CurrentUser parameter ordering in API endpoints
yuzzjj Apr 15, 2026
ec2b8a8
feat: add Mission/Agent navigation, API clients, types, and hooks
yuzzjj Apr 15, 2026
8df9f9f
feat: add Agent management page with grid view and create/edit dialog
yuzzjj Apr 16, 2026
efc1bf9
feat: add Mission Board with kanban view, cards, and create dialog
yuzzjj Apr 16, 2026
80b462f
feat: add Mission Detail Panel with agent assignment and dispatch
yuzzjj Apr 16, 2026
e959608
feat: add Execution Timeline with real-time event rendering
yuzzjj Apr 16, 2026
f33ef12
docs: add Mission multi-agent execution spec and MVP plan
yuzzjj Apr 16, 2026
37dd1f2
feat: add Codex and OpenClaw runtime providers
yuzzjj Apr 16, 2026
b55aa2a
feat: add WebSocket execution streaming and frontend polish
yuzzjj Apr 16, 2026
c8b7072
fix: replace remaining hardcoded colors with CSS variables in priorit…
yuzzjj Apr 16, 2026
235d6f9
feat: add session registry and approval/intervention API endpoints
yuzzjj Apr 16, 2026
e0e983c
feat: add Coordinator tools for multi-agent orchestration (spawn_agen…
yuzzjj Apr 16, 2026
634241e
fix: address critical review issues — batched events, env passthrough…
yuzzjj Apr 16, 2026
a50f220
fix: unwrap {items} from list API responses to fix .map() TypeError
yuzzjj Apr 16, 2026
c611b9c
refactor: reorganize sidebar into three logical groups with dividers
yuzzjj Apr 16, 2026
c5588a3
feat: add mission comments and close mission-execution lifecycle loop
yuzzjj Apr 16, 2026
f89cae8
fix: resolve drain TypeError, frontend payload crash, and stale dispa…
yuzzjj Apr 16, 2026
34b3b24
refactor: simplify — deduplicate configs, extract agent picker, add i…
yuzzjj Apr 16, 2026
1eca402
feat: add container pool with 30-min TTL and Claude session resume
yuzzjj Apr 16, 2026
6a91a03
fix: credential passthrough, is_error detection, frontend polling and…
yuzzjj Apr 17, 2026
2ef076f
fix: add global mutation error handler, remove per-call-site duplicates
yuzzjj Apr 17, 2026
eeee3c5
refactor: global error handling — exception translation, safe tasks, …
yuzzjj Apr 17, 2026
df5b545
refactor: simplify — dead imports, session bug, redundant catches
yuzzjj Apr 17, 2026
ff8edd8
fix: security — SQL injection, execution ownership, command denylist,…
yuzzjj Apr 17, 2026
6439b65
fix: critical ImportError in missions.py, simplify security commit
yuzzjj Apr 17, 2026
b428b64
feat: P0 mission features — auto_approve, @mention dispatch, schedule…
yuzzjj Apr 18, 2026
9e7dc20
feat: add RunnerCallbacks Protocol to break circular dependency
yuzzjj Apr 18, 2026
e338ddf
refactor: ExecutionRunner uses injected callbacks instead of deferred…
yuzzjj Apr 18, 2026
0e561eb
feat: add ExecutionLifecycleService as cross-domain mediator
yuzzjj Apr 18, 2026
39bf464
feat: wire ExecutionLifecycleService into runner startup path
yuzzjj Apr 18, 2026
8013bd1
feat: add dispatch/comment-dispatch to ExecutionLifecycleService
yuzzjj Apr 18, 2026
f2537ad
refactor: API + scheduler switch to ExecutionLifecycleService
yuzzjj Apr 18, 2026
8f9ba05
refactor: CommentService returns trigger signals instead of dispatchi…
yuzzjj Apr 18, 2026
68c2d03
refactor: slim MissionService to pure CRUD — remove all execution logic
yuzzjj Apr 18, 2026
d01be07
feat: add mission-scoped execution endpoints (message/approve/events/…
yuzzjj Apr 18, 2026
cc84f97
fix: add FK constraint on missions.current_execution_id
yuzzjj Apr 18, 2026
c3ddf2e
fix: replace ValueError with typed exceptions (NotFoundException/BadR…
yuzzjj Apr 18, 2026
8f98452
security: execution endpoints now require workspace role, not just login
yuzzjj Apr 18, 2026
11bcc22
feat: enforce max_concurrent_tasks limit on agent dispatch
yuzzjj Apr 18, 2026
066e916
docs: mark INTERRUPT_WAIT as reserved legacy status in execution model
yuzzjj Apr 18, 2026
d5e2f14
feat: mission-first frontend architecture with API-driven transitions
yuzzjj Apr 18, 2026
13808cd
feat: drop redundant source_id column from executions table and docum…
yuzzjj Apr 18, 2026
399801d
refactor: consolidate execution management into mission service and r…
yuzzjj Apr 18, 2026
aaf900c
feat: add PulsingDot component, update mission board UI with status i…
yuzzjj Apr 18, 2026
df83344
feat: mission detail panel bug fixes, i18n, layout refactor, and agen…
yuzzjj Apr 18, 2026
414bcdd
refactor: simplify review — extract shared hooks, fix approval bug, c…
yuzzjj Apr 18, 2026
8cd3910
refactor: improve error handling and logging by standardizing API err…
yuzzjj Apr 18, 2026
5a896d0
refactor: enhance error handling by centralizing API error extraction…
yuzzjj Apr 18, 2026
3fd8565
chore: remove obsolete documentation and unused backend/frontend file…
yuzzjj Apr 19, 2026
f3673e8
refactor: remove deprecated legacy code, cleanup unused utilities, an…
yuzzjj Apr 19, 2026
a0f2dbd
refactor: apply consistent code formatting and style improvements acr…
yuzzjj Apr 19, 2026
8a78613
refactor: remove legacy custom-tools store, dead types, and migration…
yuzzjj Apr 19, 2026
4bfccd1
refactor: improve type safety with assertions, schema-based event res…
yuzzjj Apr 19, 2026
3aafa88
fix cancel task remove container
Apr 21, 2026
79e992f
fix claude code outputs
Apr 21, 2026
c130c70
fix commit
Apr 21, 2026
8710378
add skill for agent
Apr 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ frontend/typings
frontend/.DS_Store
Thumbs.db

executions
/executions
openai-realtime-console
src/django_app/staticfiles/*

Expand Down
43 changes: 24 additions & 19 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,7 @@ repos:
- id: ruff-format
files: ^backend/

# Backend - Ruff check (强制检查,不允许错误)
# 注意:此 hook 会检查整个 backend 目录,确保没有 lint 错误
# Backend - Ruff strict check
- repo: local
hooks:
- id: backend-ruff-check
Expand All @@ -39,22 +38,10 @@ repos:
language: system
files: ^backend/.*\.py$
pass_filenames: false
always_run: false # 只在有 Python 文件更改时运行
stages: [commit]
always_run: false
stages: [pre-commit]

# Python - Type checking (mypy runs in Backend CI job only; skipped in pre-commit)
# - repo: https://github.com/pre-commit/mirrors-mypy
# rev: v1.13.0
# hooks:
# - id: mypy
# files: ^backend/app/
# args: [--ignore-missing-imports]
# additional_dependencies:
# - types-requests
# - types-PyYAML

# Frontend - ESLint (强制检查,不允许错误)
# 注意:此 hook 会检查整个 frontend 目录,确保没有 lint 错误
# Frontend - ESLint + TypeScript type-check + Prettier format check
- repo: local
hooks:
- id: frontend-lint
Expand All @@ -63,8 +50,26 @@ repos:
language: system
files: ^frontend/.*\.(ts|tsx|js|jsx)$
pass_filenames: false
always_run: false # 只在有前端文件更改时运行
stages: [commit]
always_run: false
stages: [pre-commit]

- id: frontend-type-check
name: Frontend TypeScript Check
entry: bash -c 'cd frontend && bun run type-check || exit 1'
language: system
files: ^frontend/.*\.(ts|tsx)$
pass_filenames: false
always_run: false
stages: [pre-commit]

- id: frontend-format-check
name: Frontend Prettier Check
entry: bash -c 'cd frontend && bun run format:check || exit 1'
language: system
files: ^frontend/.*\.(ts|tsx|js|jsx|json|css|md)$
pass_filenames: false
always_run: false
stages: [pre-commit]

# CI configuration
ci:
Expand Down
4 changes: 2 additions & 2 deletions DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ This document provides detailed instructions for setting up and running the JoyS
Using Docker (recommended):

```bash
cd backend/docker
./start.sh
cd deploy
docker compose -f docker-compose-middleware.yml up -d
```

Or manually start PostgreSQL and Redis on your system.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,274 @@
"""add_mission_agent_execution_tables

Revision ID: b4b3b2b1b0a9
Revises: 0f7082711f20
Create Date: 2026-04-15 00:00:00.000000

"""

from typing import Sequence, Union

import sqlalchemy as sa
from sqlalchemy.dialects import postgresql

from alembic import op

# revision identifiers, used by Alembic.
revision: str = "b4b3b2b1b0a9"
down_revision: Union[str, None] = "0f7082711f20"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


def upgrade() -> None:
# --- Enum types ---
op.execute("""
DO $$ BEGIN
CREATE TYPE missionstatus AS ENUM ('backlog','todo','in_progress','in_review','done','blocked','cancelled');
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
""")
op.execute("""
DO $$ BEGIN
CREATE TYPE missionpriority AS ENUM ('none','low','medium','high','urgent');
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
""")
op.execute("""
DO $$ BEGIN
CREATE TYPE agentstatus AS ENUM ('idle','working','blocked','error','offline');
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
""")
op.execute("""
DO $$ BEGIN
CREATE TYPE missionexecutionstatus AS ENUM ('queued','dispatched','running','interrupt_wait','approval_wait','completed','failed','cancelled');
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
""")
op.execute("""
DO $$ BEGIN
CREATE TYPE executionsource AS ENUM ('mission','chat','graph','coordinator','api');
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
""")

# --- missions ---
op.create_table(
"missions",
sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True),
sa.Column(
"workspace_id",
postgresql.UUID(as_uuid=True),
sa.ForeignKey("workspaces.id", ondelete="CASCADE"),
nullable=False,
),
sa.Column("title", sa.String(500), nullable=False),
sa.Column("description", sa.Text(), nullable=True),
sa.Column("objective", sa.Text(), nullable=True),
sa.Column(
"status",
postgresql.ENUM(
"backlog",
"todo",
"in_progress",
"in_review",
"done",
"blocked",
"cancelled",
name="missionstatus",
create_type=False,
),
nullable=False,
server_default="backlog",
),
sa.Column(
"priority",
postgresql.ENUM("none", "low", "medium", "high", "urgent", name="missionpriority", create_type=False),
nullable=False,
server_default="none",
),
sa.Column("assignee_type", sa.String(50), nullable=True),
sa.Column("assignee_id", postgresql.UUID(as_uuid=True), nullable=True),
sa.Column("creator_id", sa.String(255), sa.ForeignKey("user.id", ondelete="CASCADE"), nullable=False),
sa.Column(
"parent_mission_id",
postgresql.UUID(as_uuid=True),
sa.ForeignKey("missions.id", ondelete="SET NULL"),
nullable=True,
),
sa.Column("current_execution_id", postgresql.UUID(as_uuid=True), nullable=True),
sa.Column("due_date", sa.DateTime(timezone=True), nullable=True),
sa.Column("position", sa.Float(), nullable=False, server_default="0.0"),
sa.Column("tags", postgresql.JSONB(), nullable=True),
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
)
op.create_index("missions_workspace_status_idx", "missions", ["workspace_id", "status"])
op.create_index("missions_assignee_idx", "missions", ["assignee_type", "assignee_id"])
op.create_index("missions_creator_idx", "missions", ["creator_id", "created_at"])

# --- agent_profiles ---
op.create_table(
"agent_profiles",
sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True),
sa.Column(
"workspace_id",
postgresql.UUID(as_uuid=True),
sa.ForeignKey("workspaces.id", ondelete="CASCADE"),
nullable=False,
),
sa.Column("name", sa.String(255), nullable=False),
sa.Column("avatar", sa.String(500), nullable=True),
sa.Column("description", sa.Text(), nullable=True),
sa.Column("runtime_type", sa.String(50), nullable=False),
sa.Column(
"status",
postgresql.ENUM("idle", "working", "blocked", "error", "offline", name="agentstatus", create_type=False),
nullable=False,
server_default="offline",
),
sa.Column("max_concurrent_tasks", sa.Integer(), nullable=False, server_default="1"),
sa.Column("skill_ids", postgresql.JSONB(), nullable=True),
sa.Column("instructions", sa.Text(), nullable=True),
sa.Column("custom_env", postgresql.JSONB(), nullable=True),
sa.Column("runtime_config", postgresql.JSONB(), nullable=True),
sa.Column("visibility", sa.String(50), nullable=False, server_default="workspace"),
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
)
op.create_index("agent_profiles_workspace_idx", "agent_profiles", ["workspace_id"])
op.create_index("agent_profiles_workspace_status_idx", "agent_profiles", ["workspace_id", "status"])

# --- executions ---
op.create_table(
"executions",
sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True),
sa.Column(
"workspace_id",
postgresql.UUID(as_uuid=True),
sa.ForeignKey("workspaces.id", ondelete="CASCADE"),
nullable=False,
),
sa.Column("user_id", sa.String(255), sa.ForeignKey("user.id", ondelete="CASCADE"), nullable=False),
sa.Column(
"source",
postgresql.ENUM(
"mission", "chat", "graph", "coordinator", "api", name="executionsource", create_type=False
),
nullable=False,
),
sa.Column("source_id", sa.String(255), nullable=True),
sa.Column(
"status",
postgresql.ENUM(
"queued",
"dispatched",
"running",
"interrupt_wait",
"approval_wait",
"completed",
"failed",
"cancelled",
name="missionexecutionstatus",
create_type=False,
),
nullable=False,
server_default="queued",
),
sa.Column("title", sa.String(500), nullable=True),
sa.Column(
"mission_id",
postgresql.UUID(as_uuid=True),
sa.ForeignKey("missions.id", ondelete="SET NULL"),
nullable=True,
),
sa.Column(
"agent_profile_id",
postgresql.UUID(as_uuid=True),
sa.ForeignKey("agent_profiles.id", ondelete="SET NULL"),
nullable=True,
),
sa.Column(
"parent_execution_id",
postgresql.UUID(as_uuid=True),
sa.ForeignKey("executions.id", ondelete="SET NULL"),
nullable=True,
),
sa.Column("result_summary", postgresql.JSONB(), nullable=True),
sa.Column("error_code", sa.String(100), nullable=True),
sa.Column("error_message", sa.Text(), nullable=True),
sa.Column("runtime_type", sa.String(50), nullable=False),
sa.Column("runtime_config", postgresql.JSONB(), nullable=True),
sa.Column("container_id", sa.String(255), nullable=True),
sa.Column("started_at", sa.DateTime(timezone=True), nullable=True),
sa.Column("finished_at", sa.DateTime(timezone=True), nullable=True),
sa.Column("last_heartbeat_at", sa.DateTime(timezone=True), nullable=True),
sa.Column("last_seq", sa.BigInteger(), nullable=False, server_default="0"),
sa.Column("prior_session_id", sa.String(255), nullable=True),
sa.Column("session_id", sa.String(255), nullable=True),
sa.Column("work_dir", sa.String(500), nullable=True),
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
)
op.create_index("executions_workspace_status_idx", "executions", ["workspace_id", "status"])
op.create_index("executions_mission_idx", "executions", ["mission_id"])
op.create_index("executions_agent_profile_idx", "executions", ["agent_profile_id"])
op.create_index("executions_parent_idx", "executions", ["parent_execution_id"])
op.create_index("executions_user_created_idx", "executions", ["user_id", "created_at"])

# --- execution_events ---
op.create_table(
"execution_events",
sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True),
sa.Column(
"execution_id",
postgresql.UUID(as_uuid=True),
sa.ForeignKey("executions.id", ondelete="CASCADE"),
nullable=False,
),
sa.Column("seq", sa.BigInteger(), nullable=False),
sa.Column("event_type", sa.String(100), nullable=False),
sa.Column("payload", postgresql.JSONB(), nullable=False, server_default="{}"),
sa.Column("trace_id", postgresql.UUID(as_uuid=True), nullable=True),
sa.Column("observation_id", postgresql.UUID(as_uuid=True), nullable=True),
sa.Column("parent_observation_id", postgresql.UUID(as_uuid=True), nullable=True),
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
)
op.create_unique_constraint("uq_execution_events_exec_seq", "execution_events", ["execution_id", "seq"])
op.create_index("execution_events_exec_created_idx", "execution_events", ["execution_id", "created_at"])

# --- execution_snapshots ---
op.create_table(
"execution_snapshots",
sa.Column(
"execution_id",
postgresql.UUID(as_uuid=True),
sa.ForeignKey("executions.id", ondelete="CASCADE"),
primary_key=True,
),
sa.Column("last_seq", sa.BigInteger(), nullable=False, server_default="0"),
sa.Column("status", sa.String(100), nullable=False),
sa.Column("projection", postgresql.JSONB(), nullable=False, server_default="{}"),
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
)


def downgrade() -> None:
op.drop_table("execution_snapshots")
op.drop_table("execution_events")
op.drop_table("executions")
op.drop_table("agent_profiles")
op.drop_table("missions")
op.execute("DROP TYPE IF EXISTS executionsource")
op.execute("DROP TYPE IF EXISTS missionexecutionstatus")
op.execute("DROP TYPE IF EXISTS agentstatus")
op.execute("DROP TYPE IF EXISTS missionpriority")
op.execute("DROP TYPE IF EXISTS missionstatus")
Loading
Loading