feat(integration): prepaid billing, engine UI, credits console, direct tasks, v2 final
- Prepaid credit system: org wallet, debit on usage, chat gate, admin credit console - Engine UI: /manager, /tasks, /tasks/[id], /workforce, /dashboard, /traces pages - Direct task creation (HTTP POST /tasks, MCP tasks_create) at pending_manager_review - Pause/resume/cancel/answer actions in engine API and UI - Employee cap (7 per org default), review pipeline guard (active reviewer required) - Scheduler: wide window prevents org starvation; archived employees excluded from cap - Tenant isolation: project-id cross-org 404, engine never uses another tenant's sandbox - Order route fix: manager built with pipeline states + employee profiles - Contract extension: ModelGrantPort.get_org_policy with DefaultOrgPolicy - Engine event types in @agy/contracts (task.step, task.gate, run.log, run.tool, team.changed) - api-engine.ts typed client; api-credit.ts client - Alembic migration c4d5e6f7a8b9: prepaid credit + engine UI tables - 859 tests passed, 1 skipped; ruff/mypy/tsc/eslint/vitest all green Refs: PLAN-v2.md §9 (T1–T43), todo-v2-final.md
This commit is contained in:
@@ -19,6 +19,7 @@ truth, the status column is the summary existing APIs keep reading.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from app.application.engine.events import emit_task_step
|
||||
from app.core.errors import ConflictError, NotFoundError
|
||||
from app.domain.workforce.pipeline import (
|
||||
SIDE_STAGES,
|
||||
@@ -46,14 +47,14 @@ async def advance(
|
||||
|
||||
Raises ``ConflictError`` when the transition is illegal or when another
|
||||
worker moved the task first, and ``NotFoundError`` when the task has no
|
||||
engine state row at all.
|
||||
engine state row at all. A move that lands publishes one ``task.step``.
|
||||
"""
|
||||
if not can_transition_stage(expected_from, to):
|
||||
raise ConflictError(
|
||||
f"گذار نابجا از {expected_from.value} به {to.value}."
|
||||
)
|
||||
try:
|
||||
return await states.advance(task_id=task_id, expected_from=expected_from, to=to)
|
||||
moved = await states.advance(task_id=task_id, expected_from=expected_from, to=to)
|
||||
except UnknownTaskError as exc:
|
||||
raise NotFoundError("این کار وضعیت موتور ندارد.") from exc
|
||||
except InvalidTransitionError as exc:
|
||||
@@ -62,6 +63,15 @@ async def advance(
|
||||
raise ConflictError(
|
||||
"این کار هماکنون توسط موتور دیگری در حرکت است؛ دوباره تلاش کنید."
|
||||
) from exc
|
||||
# The publisher never raises, so a broken bus cannot undo the move.
|
||||
await emit_task_step(
|
||||
task_id=moved.task_id,
|
||||
organization_id=moved.organization_id,
|
||||
from_stage=expected_from,
|
||||
to_stage=to,
|
||||
pipeline=moved.pipeline,
|
||||
)
|
||||
return moved
|
||||
|
||||
|
||||
async def park_dependents(
|
||||
|
||||
Reference in New Issue
Block a user