Quellcode durchsuchen

接口:增加阶段二续跑入口与脱敏任务观测视图

SamLee vor 1 Tag
Ursprung
Commit
4ac2397dea

+ 31 - 5
script_build_host/src/script_build_host/api/routes.py

@@ -10,11 +10,15 @@ from urllib.parse import urlsplit, urlunsplit
 
 from fastapi import APIRouter, Depends, Query, Request, WebSocket, WebSocketDisconnect
 
-from script_build_host.agents.prompts import phase_one_prompt_manifest
+from script_build_host.agents.prompt_catalog import script_prompt_requests
 from script_build_host.application.mission_service import (
     ScriptMissionService,
     StartScriptBuildCommand,
 )
+from script_build_host.application.observation_views import (
+    mission_snapshot_view,
+    task_detail_view,
+)
 from script_build_host.domain.ports import (
     MissionBindingRepository,
     PublicationRepository,
@@ -183,9 +187,25 @@ def create_script_build_router(
         script_build_id: int,
         actor: PrincipalDependency,
     ) -> dict[str, Any]:
+        await security.authorize(script_build_id, actor)
         result = await mission_service.stop(script_build_id, actor)
         return {"success": True, **asdict(result), "status": result.status.value}
 
+    @router.post("/api/pattern/script_builds/{script_build_id}/phase-two/advance")
+    async def advance_to_phase_two(
+        script_build_id: int,
+        actor: PrincipalDependency,
+    ) -> dict[str, Any]:
+        await security.authorize(script_build_id, actor)
+        result = await mission_service.advance_to_phase_two(script_build_id, actor)
+        return {
+            "success": True,
+            "script_build_id": result.script_build_id,
+            "status": result.status.value,
+            "root_trace_id": result.root_trace_id,
+            "input_snapshot_id": result.input_snapshot_id,
+        }
+
     @router.get("/api/pattern/script_builds/{script_build_id}/mission")
     async def mission_snapshot(
         script_build_id: int,
@@ -193,7 +213,7 @@ def create_script_build_router(
     ) -> dict[str, Any]:
         binding = await scope(script_build_id, actor)
         ledger = await coordinator.task_store.load(binding.root_trace_id)
-        return cast(dict[str, Any], ledger.to_dict())
+        return mission_snapshot_view(ledger)
 
     @router.get("/api/pattern/script_builds/{script_build_id}/mission/events")
     async def mission_events(
@@ -222,7 +242,7 @@ def create_script_build_router(
         task = ledger.tasks.get(task_id)
         if task is None:
             raise problem(404, "TASK_NOT_FOUND", "task does not exist in this build")
-        return task.to_dict() if hasattr(task, "to_dict") else asdict(task)
+        return task_detail_view(task)
 
     @router.get("/api/pattern/script_builds/{script_build_id}/artifacts/{artifact_version_id}")
     async def artifact_detail(
@@ -258,7 +278,7 @@ def create_script_build_router(
     ) -> dict[str, Any]:
         binding = await scope(script_build_id, actor)
         _same_root(binding.root_trace_id, root_trace_id)
-        return cast(dict[str, Any], (await coordinator.task_store.load(root_trace_id)).to_dict())
+        return mission_snapshot_view(await coordinator.task_store.load(root_trace_id))
 
     @router.get("/api/v2/script-builds/{script_build_id}/roots/{root_trace_id}/completion")
     async def v2_completion(
@@ -438,6 +458,12 @@ async def _start_command(
             "script_knowledge_retrieval_worker": "model_name_means_knowledge",
             "script_retrieval_validator": "model_name_script_evaluator",
             "script_candidate_validator": "model_name_script_evaluator",
+            "script_structure_worker": "model_name",
+            "script_paragraph_worker": "model_name",
+            "script_element_set_worker": "model_name",
+            "script_candidate_compare_worker": "model_name_script_evaluator",
+            "script_compose_worker": "model_name",
+            "script_candidate_portfolio_worker": "model_name_script_evaluator",
         }
         for name, key in preset_keys.items():
             preset_default = (
@@ -489,7 +515,7 @@ async def _start_command(
         data_source_url=safe_endpoint,
         strategies_always_on=tuple(body.strategies_always_on or ()),
         strategies_on_demand=tuple(body.strategies_on_demand or ()),
-        runtime_prompt_manifest=phase_one_prompt_manifest(),
+        prompt_requests=script_prompt_requests(),
         datasource_manifest=datasource_manifest,
         model_manifest={"presets": presets},
     )

+ 187 - 0
script_build_host/src/script_build_host/application/observation_views.py

@@ -0,0 +1,187 @@
+"""Bounded, non-authoritative observation projections for Host HTTP APIs."""
+
+from __future__ import annotations
+
+from typing import Any
+
+
+def mission_snapshot_view(ledger: Any) -> dict[str, Any]:
+    if not hasattr(ledger, "revision"):
+        raw = ledger.to_dict() if hasattr(ledger, "to_dict") else {}
+        return {
+            key: raw[key]
+            for key in (
+                "schema_version",
+                "revision",
+                "root_trace_id",
+                "root_task_id",
+                "root_objective",
+                "focused_task_id",
+            )
+            if key in raw
+        }
+    return {
+        "schema_version": 1,
+        "revision": int(ledger.revision),
+        "root_trace_id": str(ledger.root_trace_id),
+        "root_task_id": ledger.root_task_id,
+        "root_objective": _text(ledger.root_objective, 2_000),
+        "focused_task_id": ledger.focused_task_id,
+        "tasks": [task_detail_view(item) for item in ledger.tasks.values()],
+        "attempts": [_attempt_view(item) for item in ledger.attempts.values()],
+        "validations": [_validation_view(item) for item in ledger.validations.values()],
+        "decisions": [_decision_view(item) for item in ledger.decisions.values()],
+        "operations": [_operation_view(item) for item in ledger.operations.values()],
+    }
+
+
+def task_detail_view(task: Any) -> dict[str, Any]:
+    current = task.current_spec
+    return {
+        "task_id": task.task_id,
+        "parent_task_id": task.parent_task_id,
+        "display_path": task.display_path,
+        "status": _enum(task.status),
+        "current_spec": {
+            "version": current.version,
+            "objective": _text(current.objective, 2_000),
+            "acceptance_criteria": [
+                {
+                    "criterion_id": _text(value.criterion_id, 128),
+                    "description": _text(value.description, 500),
+                    "hard": bool(value.hard),
+                }
+                for value in current.acceptance_criteria
+            ],
+            "context_refs": [_safe_ref(value) for value in current.context_refs],
+        },
+        "child_task_ids": list(task.child_task_ids),
+        "attempt_ids": list(task.attempt_ids),
+        "validation_ids": list(task.validation_ids),
+        "decision_ids": list(task.decision_ids),
+        "blocked_reason": _optional_text(task.blocked_reason, 256),
+        "superseded_by": task.superseded_by,
+        "created_at": str(task.created_at),
+        "updated_at": str(task.updated_at),
+    }
+
+
+def _attempt_view(value: Any) -> dict[str, Any]:
+    submission = value.submission
+    return {
+        "attempt_id": value.attempt_id,
+        "task_id": value.task_id,
+        "spec_version": value.spec_version,
+        "worker_trace_id": value.worker_trace_id,
+        "worker_preset": value.worker_preset,
+        "status": _enum(value.status),
+        "operation_id": value.operation_id,
+        "snapshot_id": value.snapshot_id,
+        "accepted_child_decision_ids": (
+            list(value.accepted_child_decision_ids)
+            if value.accepted_child_decision_ids is not None
+            else None
+        ),
+        "submission": (
+            {
+                "summary": _text(submission.summary, 500),
+                "artifact_refs": [_artifact_ref(item) for item in submission.artifact_refs],
+                "evidence_refs": [_artifact_ref(item) for item in submission.evidence_refs],
+            }
+            if submission is not None
+            else None
+        ),
+        "error": _optional_text(value.error, 500),
+        "created_at": str(value.created_at),
+        "updated_at": str(value.updated_at),
+    }
+
+
+def _validation_view(value: Any) -> dict[str, Any]:
+    return {
+        "validation_id": value.validation_id,
+        "task_id": value.task_id,
+        "attempt_id": value.attempt_id,
+        "spec_version": value.spec_version,
+        "snapshot_id": value.snapshot_id,
+        "validator_trace_id": value.validator_trace_id,
+        "validator_preset": value.validator_preset,
+        "status": _enum(value.status),
+        "verdict": _enum(value.verdict),
+        "criterion_results": [
+            {
+                "criterion_id": _text(item.criterion_id, 128),
+                "verdict": _enum(item.verdict),
+                "reason": _text(item.reason, 500),
+                "evidence_refs": [_artifact_ref(ref) for ref in item.evidence_refs],
+            }
+            for item in value.criterion_results
+        ],
+        "summary": _text(value.summary, 500),
+        "recommendation": _text(value.recommendation, 256),
+        "error": _optional_text(value.error, 500),
+        "created_at": str(value.created_at),
+        "updated_at": str(value.updated_at),
+    }
+
+
+def _decision_view(value: Any) -> dict[str, Any]:
+    return {
+        "decision_id": value.decision_id,
+        "task_id": value.task_id,
+        "action": _enum(value.action),
+        "reason": _text(value.reason, 500),
+        "from_status": _enum(value.from_status),
+        "to_status": _enum(value.to_status),
+        "attempt_id": value.attempt_id,
+        "validation_id": value.validation_id,
+        # The raw payload may contain revised contracts, protected references,
+        # or model text.  The action/identities are the observation contract.
+        "created_at": str(value.created_at),
+    }
+
+
+def _operation_view(value: Any) -> dict[str, Any]:
+    return {
+        "operation_id": value.operation_id,
+        "kind": _enum(value.kind),
+        "status": _enum(value.status),
+        "task_ids": list(value.task_ids),
+        "attempt_ids": list(value.attempt_ids),
+        "validation_ids": list(value.validation_ids),
+        "deadline_at": str(value.deadline_at) if value.deadline_at else None,
+        "error": _optional_text(value.error, 500),
+        "created_at": str(value.created_at),
+        "updated_at": str(value.updated_at),
+    }
+
+
+def _artifact_ref(value: Any) -> dict[str, Any]:
+    return {
+        "uri": _safe_ref(value.uri),
+        "kind": _text(value.kind, 64),
+        "version": _optional_text(value.version, 128),
+        "digest": _optional_text(value.digest, 80),
+    }
+
+
+def _safe_ref(value: Any) -> str:
+    text = _text(value, 500)
+    return text if text.startswith("script-build://") else "[internal-ref]"
+
+
+def _optional_text(value: Any, limit: int) -> str | None:
+    return None if value is None else _text(value, limit)
+
+
+def _text(value: Any, limit: int) -> str:
+    return " ".join(str(value).split())[:limit]
+
+
+def _enum(value: Any) -> str | None:
+    if value is None:
+        return None
+    return str(getattr(value, "value", value))
+
+
+__all__ = ["mission_snapshot_view", "task_detail_view"]