Add project-folder runtime artifacts

This commit is contained in:
elipitc
2026-06-03 09:56:59 +08:00
parent f306f2700b
commit aae209574a
9 changed files with 524 additions and 8 deletions
+130
View File
@@ -3,6 +3,7 @@ from datetime import datetime, timezone
import hashlib
import hmac
import json
import mimetypes
from typing import Any, Dict, Optional
from fastapi import APIRouter, Depends, HTTPException, Request, status
@@ -22,6 +23,7 @@ from api.agnet.auth import verify_service_token
from api.agnet.validators import validate_no_sensitive_fields
from api.agnet.vault_client import vault_client
from api.swarm.artifact_store import load_azblob_artifact, load_runtime_artifact, runtime_uri_parts
from api.swarm.artifact_store import load_runtime_project_artifact, load_runtime_project_file
from api.status_projection import RuntimeDisplayStatus
from config.error_codes import ErrorCode
from config.settings import settings
@@ -478,6 +480,13 @@ async def get_deployment_artifact_content(
parts = runtime_uri_parts(artifact_payload.get("uri"))
if parts:
project_artifact = load_runtime_project_artifact(*parts)
if project_artifact:
return FileResponse(
path=project_artifact.archive_path,
media_type="application/zip",
filename=project_artifact.archive_path.name,
)
stored = load_runtime_artifact(*parts)
if stored:
return FileResponse(
@@ -513,6 +522,127 @@ async def get_deployment_artifact_content(
raise HTTPException(status_code=404, detail="Artifact content not found")
@user_router.get("/{deployment_id}/artifacts/{artifact_id}/manifest")
async def get_deployment_artifact_manifest(
deployment_id: str,
artifact_id: str,
db: Session = Depends(get_db),
token: str = Depends(verify_service_token),
):
"""Return manifest JSON for a project-folder artifact."""
_ensure_deployment(db, deployment_id)
events = (
db.query(Event)
.filter(Event.deployment_id == deployment_id, Event.event_type == "artifact.created")
.order_by(Event.occurred_at.asc())
.all()
)
artifact_payload = None
for event in events:
payload = event.payload or {}
artifact = payload.get("artifact") if isinstance(payload.get("artifact"), dict) else payload
if (artifact.get("artifact_id") or event.event_id) == artifact_id:
artifact_payload = artifact
break
if not artifact_payload:
raise HTTPException(status_code=404, detail="Artifact not found")
parts = runtime_uri_parts(artifact_payload.get("uri"))
if parts:
project_artifact = load_runtime_project_artifact(*parts)
if project_artifact:
return FileResponse(
path=project_artifact.manifest_path,
media_type="application/json",
filename=project_artifact.manifest_path.name,
)
raise HTTPException(status_code=404, detail="Project artifact manifest not found")
@user_router.get("/{deployment_id}/artifacts/{artifact_id}/archive.zip")
async def get_deployment_artifact_archive(
deployment_id: str,
artifact_id: str,
db: Session = Depends(get_db),
token: str = Depends(verify_service_token),
):
"""Return zip archive for a project-folder artifact."""
_ensure_deployment(db, deployment_id)
events = (
db.query(Event)
.filter(Event.deployment_id == deployment_id, Event.event_type == "artifact.created")
.order_by(Event.occurred_at.asc())
.all()
)
artifact_payload = None
for event in events:
payload = event.payload or {}
artifact = payload.get("artifact") if isinstance(payload.get("artifact"), dict) else payload
if (artifact.get("artifact_id") or event.event_id) == artifact_id:
artifact_payload = artifact
break
if not artifact_payload:
raise HTTPException(status_code=404, detail="Artifact not found")
parts = runtime_uri_parts(artifact_payload.get("uri"))
if parts:
project_artifact = load_runtime_project_artifact(*parts)
if project_artifact:
return FileResponse(
path=project_artifact.archive_path,
media_type="application/zip",
filename=project_artifact.archive_path.name,
)
raise HTTPException(status_code=404, detail="Project artifact archive not found")
@user_router.get("/{deployment_id}/artifacts/{artifact_id}/files/{file_path:path}")
async def get_deployment_artifact_file(
deployment_id: str,
artifact_id: str,
file_path: str,
db: Session = Depends(get_db),
token: str = Depends(verify_service_token),
):
"""Return a single file from a project-folder artifact."""
_ensure_deployment(db, deployment_id)
events = (
db.query(Event)
.filter(Event.deployment_id == deployment_id, Event.event_type == "artifact.created")
.order_by(Event.occurred_at.asc())
.all()
)
artifact_payload = None
for event in events:
payload = event.payload or {}
artifact = payload.get("artifact") if isinstance(payload.get("artifact"), dict) else payload
if (artifact.get("artifact_id") or event.event_id) == artifact_id:
artifact_payload = artifact
break
if not artifact_payload:
raise HTTPException(status_code=404, detail="Artifact not found")
parts = runtime_uri_parts(artifact_payload.get("uri"))
if parts:
resolved_file = load_runtime_project_file(parts[0], parts[1], file_path)
if resolved_file:
return FileResponse(
path=resolved_file,
media_type=mimetypes.guess_type(str(resolved_file))[0] or "text/plain",
filename=resolved_file.name,
)
raise HTTPException(status_code=404, detail="Project artifact file not found")
@user_router.get("/{deployment_id}/timeline")
async def list_deployment_timeline(
deployment_id: str,
+172
View File
@@ -2,10 +2,12 @@
import base64
import hashlib
import json
import logging
import mimetypes
import os
import re
import zipfile
from dataclasses import dataclass
from functools import lru_cache
from pathlib import Path
@@ -47,6 +49,21 @@ class StoredArtifact:
content_hash: str
@dataclass(frozen=True)
class StoredProjectArtifact:
"""Metadata for a persisted project-folder artifact."""
artifact_id: str
uri: str
root_dir: str
artifact_dir: Path
manifest_path: Path
archive_path: Path
file_count: int
directory_count: int
content_hash: str
def sanitize_artifact_id(value: str) -> str:
"""Return a filesystem-safe identifier while preserving readable IDs."""
cleaned = _SAFE_ID_RE.sub("_", value or "").strip("._-")
@@ -65,11 +82,32 @@ def runtime_artifact_download_path(swarm_id: str, artifact_id: str) -> str:
return f"/api/swarms/{sanitize_artifact_id(swarm_id)}/artifacts/{sanitize_artifact_id(artifact_id)}/content"
def runtime_artifact_manifest_path(swarm_id: str, artifact_id: str) -> str:
"""Build the HTTP path for a project-folder manifest."""
return f"/api/swarms/{sanitize_artifact_id(swarm_id)}/artifacts/{sanitize_artifact_id(artifact_id)}/manifest"
def runtime_artifact_archive_path(swarm_id: str, artifact_id: str) -> str:
"""Build the HTTP path for a project-folder archive."""
return f"/api/swarms/{sanitize_artifact_id(swarm_id)}/artifacts/{sanitize_artifact_id(artifact_id)}/archive.zip"
def runtime_artifact_file_path(swarm_id: str, artifact_id: str, file_path: str) -> str:
"""Build the HTTP path for a single file inside a project-folder artifact."""
safe_swarm_id = sanitize_artifact_id(swarm_id)
safe_artifact_id = sanitize_artifact_id(artifact_id)
return f"/api/swarms/{safe_swarm_id}/artifacts/{safe_artifact_id}/files/{file_path.lstrip('/')}"
def _artifact_dir(swarm_id: str) -> Path:
base_dir = Path(settings.RUNTIME_ARTIFACT_DIR)
return base_dir / sanitize_artifact_id(swarm_id)
def _project_artifact_dir(swarm_id: str, artifact_id: str) -> Path:
return _artifact_dir(swarm_id) / f"{sanitize_artifact_id(artifact_id)}.project"
def _extension_for_mime_type(mime_type: str) -> str:
if mime_type == "text/x-diff":
return ".patch"
@@ -275,6 +313,140 @@ def load_runtime_artifact(swarm_id: str, artifact_id: str) -> Optional[StoredArt
)
def _safe_project_file_path(value: str) -> str:
normalized = (value or "").strip().replace("\\", "/").lstrip("/")
normalized = re.sub(r"/+", "/", normalized)
if not normalized or normalized in {".", ".."} or normalized.startswith("../") or "/../" in normalized:
raise ValueError("invalid project file path")
return normalized
def _directory_count_for_files(root_dir: str, files: dict[str, str]) -> int:
directories = {root_dir}
for relative_path in files:
parts = relative_path.split("/")[:-1]
current = root_dir
for part in parts:
current = f"{current}/{part}" if current else part
directories.add(current)
return len(directories)
def store_project_artifact(
swarm_id: str,
artifact_id: str,
*,
root_dir: str,
files: dict[str, str],
) -> StoredProjectArtifact:
"""Persist a project-folder artifact with manifest and archive."""
safe_artifact_id = sanitize_artifact_id(artifact_id)
safe_root_dir = sanitize_artifact_id(root_dir)
if not files:
raise ValueError("project artifact requires at least one file")
artifact_dir = _project_artifact_dir(swarm_id, safe_artifact_id)
if artifact_dir.exists():
for child in sorted(artifact_dir.rglob("*"), reverse=True):
if child.is_file():
child.unlink()
elif child.is_dir():
child.rmdir()
artifact_dir.mkdir(parents=True, exist_ok=True)
root_path = artifact_dir / safe_root_dir
root_path.mkdir(parents=True, exist_ok=True)
normalized_files: dict[str, str] = {}
for relative_path, content in files.items():
safe_relative_path = _safe_project_file_path(relative_path)
normalized_files[safe_relative_path] = content or ""
file_path = root_path / safe_relative_path
file_path.parent.mkdir(parents=True, exist_ok=True)
file_path.write_text(content or "", encoding="utf-8")
manifest = {
"artifact_id": safe_artifact_id,
"artifact_type": "project_folder",
"root_dir": safe_root_dir,
"file_count": len(normalized_files),
"directory_count": _directory_count_for_files(safe_root_dir, normalized_files),
"files": [
{
"path": relative_path,
"size_bytes": len(content.encode("utf-8")),
"mime_type": mimetypes.guess_type(relative_path)[0] or "text/plain",
}
for relative_path, content in sorted(normalized_files.items())
],
}
manifest_path = artifact_dir / "manifest.json"
manifest_bytes = json.dumps(manifest, ensure_ascii=False, indent=2).encode("utf-8")
manifest_path.write_bytes(manifest_bytes)
archive_path = artifact_dir / f"{safe_artifact_id}.zip"
with zipfile.ZipFile(archive_path, "w", compression=zipfile.ZIP_DEFLATED) as zf:
for relative_path in sorted(normalized_files):
zf.write(root_path / relative_path, arcname=f"{safe_root_dir}/{relative_path}")
zf.write(manifest_path, arcname=f"{safe_root_dir}/heicode-artifact.json")
hash_source = hashlib.sha256()
hash_source.update(manifest_bytes)
for relative_path, content in sorted(normalized_files.items()):
hash_source.update(relative_path.encode("utf-8"))
hash_source.update((content or "").encode("utf-8"))
content_hash = "sha256:" + hash_source.hexdigest()
return StoredProjectArtifact(
artifact_id=safe_artifact_id,
uri=runtime_artifact_uri(swarm_id, safe_artifact_id),
root_dir=safe_root_dir,
artifact_dir=artifact_dir,
manifest_path=manifest_path,
archive_path=archive_path,
file_count=len(normalized_files),
directory_count=manifest["directory_count"],
content_hash=content_hash,
)
def load_runtime_project_artifact(swarm_id: str, artifact_id: str) -> Optional[StoredProjectArtifact]:
"""Load project-folder artifact metadata if it exists locally."""
safe_swarm_id = sanitize_artifact_id(swarm_id)
safe_artifact_id = sanitize_artifact_id(artifact_id)
artifact_dir = _project_artifact_dir(safe_swarm_id, safe_artifact_id)
manifest_path = artifact_dir / "manifest.json"
archive_path = artifact_dir / f"{safe_artifact_id}.zip"
if not artifact_dir.exists() or not manifest_path.exists() or not archive_path.exists():
return None
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
return StoredProjectArtifact(
artifact_id=safe_artifact_id,
uri=runtime_artifact_uri(safe_swarm_id, safe_artifact_id),
root_dir=manifest.get("root_dir") or safe_artifact_id,
artifact_dir=artifact_dir,
manifest_path=manifest_path,
archive_path=archive_path,
file_count=int(manifest.get("file_count") or 0),
directory_count=int(manifest.get("directory_count") or 0),
content_hash="sha256:" + hashlib.sha256(manifest_path.read_bytes() + archive_path.read_bytes()).hexdigest(),
)
def load_runtime_project_file(swarm_id: str, artifact_id: str, relative_path: str) -> Optional[Path]:
"""Resolve a single file inside a stored project-folder artifact."""
project = load_runtime_project_artifact(swarm_id, artifact_id)
if not project:
return None
safe_relative_path = _safe_project_file_path(relative_path)
file_path = project.artifact_dir / project.root_dir / safe_relative_path
if not file_path.exists() or not file_path.is_file():
return None
return file_path
def runtime_uri_parts(uri: str) -> Optional[tuple[str, str]]:
"""Parse runtime://<swarm_id>/artifacts/<artifact_id> URIs."""
if not uri or not uri.startswith("runtime://"):
+116 -2
View File
@@ -3,6 +3,7 @@
import asyncio
import json
import logging
import re
import uuid
from datetime import datetime, timedelta
from typing import Dict, List, Any, Optional
@@ -11,11 +12,24 @@ from sqlalchemy.orm import Session
from database import Swarm, SwarmAgent, SwarmMessage, SwarmStatus, SwarmAgentStatus
from k8s_manager import K8sManager
from .agent_client import SwarmAgentClient
from .artifact_store import store_text_artifact, runtime_artifact_uri, runtime_artifact_download_path
from .artifact_store import (
store_project_artifact,
store_text_artifact,
runtime_artifact_archive_path,
runtime_artifact_download_path,
runtime_artifact_file_path,
runtime_artifact_manifest_path,
runtime_artifact_uri,
)
from .callback_client import CallbackDeliveryClient
logger = logging.getLogger(__name__)
_CODE_BLOCK_RE = re.compile(r"```(?P<lang>[a-zA-Z0-9_+-]*)\n(?P<body>.*?)```", re.DOTALL)
_FILENAME_HINT_RE = re.compile(
r"^\s*(?:#|//|/\*+|\*|--)?\s*(?P<path>[A-Za-z0-9_.\-/]+\.[A-Za-z0-9_]+)\s*(?:\*/)?\s*$"
)
PHASE_MAP = {
"planning": ("requirements", "agent_running"),
@@ -1070,11 +1084,50 @@ class SwarmOrchestrator:
def _build_artifact(self, agent: SwarmAgent, response: Any, index: int) -> Dict[str, Any]:
"""Convert an agent response into a Heicode-visible artifact record."""
role = agent.role or "worker"
artifact_type = "code_patch" if role in {"backend", "frontend", "coder", "engineer"} else "document"
is_structural_code_role = role in {"backend", "frontend", "coder", "engineer", "fullstack"}
artifact_type = "project_folder" if is_structural_code_role else "document"
artifact_id = f"art_{self.swarm_id}_{role}_{index + 1}"
content = self._extract_deliverable_text(response)
if not content:
content = json.dumps(response, ensure_ascii=False, indent=2) if isinstance(response, (dict, list)) else str(response)
if is_structural_code_role:
files, root_dir = self._project_files_from_content(role, content)
stored_project = self._store_project_artifact(artifact_id, root_dir, files)
metadata = {
"redacted": True,
"agent_role": role,
"runtime_deployment_id": self.swarm_id,
"summary_only": False,
"artifact_layout": "project_folder",
"primary_read_path": "manifest",
"root_dir": stored_project.root_dir if stored_project else root_dir,
"file_count": stored_project.file_count if stored_project else len(files),
"directory_count": stored_project.directory_count if stored_project else 1,
"manifest_uri": runtime_artifact_manifest_path(self.swarm_id, artifact_id),
"archive_uri": runtime_artifact_archive_path(self.swarm_id, artifact_id),
}
if stored_project:
metadata.update(
{
"content_hash": stored_project.content_hash,
"download_path": runtime_artifact_archive_path(self.swarm_id, artifact_id),
"files_base_uri": runtime_artifact_file_path(self.swarm_id, artifact_id, ""),
}
)
return {
"artifact_id": artifact_id,
"artifact_type": artifact_type,
"title": f"{role} task delivery",
"summary": content[:1000],
"uri": stored_project.uri if stored_project else runtime_artifact_uri(self.swarm_id, artifact_id),
"agent_instance_id": agent.agent_id,
"mime_type": "application/zip",
"size_bytes": stored_project.archive_path.stat().st_size if stored_project else len(content.encode("utf-8")),
"stage": "development",
"checkpoint": "artifact_ready",
"metadata": metadata,
}
stored = self._store_artifact_content(artifact_id, content)
metadata = {
"redacted": True,
@@ -1211,3 +1264,64 @@ class SwarmOrchestrator:
except Exception as e:
logger.warning("Failed to persist runtime artifact %s: %s", artifact_id, e)
return None
def _store_project_artifact(self, artifact_id: str, root_dir: str, files: Dict[str, str]):
"""Persist a project-folder artifact without blocking runtime completion."""
try:
return store_project_artifact(self.swarm_id, artifact_id, root_dir=root_dir, files=files)
except Exception as e:
logger.warning("Failed to persist runtime project artifact %s: %s", artifact_id, e)
return None
def _project_files_from_content(self, role: str, content: str) -> tuple[Dict[str, str], str]:
"""Convert a code-oriented response into a minimal project-folder file set."""
root_dir = f"{role}-delivery"
files: Dict[str, str] = {}
for match in _CODE_BLOCK_RE.finditer(content):
body = (match.group("body") or "").strip("\n")
if not body:
continue
lines = body.splitlines()
first_line = lines[0].strip() if lines else ""
filename_match = _FILENAME_HINT_RE.match(first_line)
if filename_match:
file_path = filename_match.group("path")
file_content = "\n".join(lines[1:]).lstrip("\n")
else:
file_path = self._default_file_path_for_role(role, match.group("lang") or "", len(files))
file_content = body
files[file_path] = file_content or ""
if not files:
files[self._default_file_path_for_role(role, "", 0)] = content.strip() + "\n"
files.setdefault("README.md", self._project_readme(role, content))
files.setdefault("heicode-artifact.json", json.dumps({"role": role, "artifact_layout": "project_folder"}, ensure_ascii=False, indent=2))
return files, root_dir
def _default_file_path_for_role(self, role: str, language: str, index: int) -> str:
"""Choose a stable fallback file path when the agent output omits file names."""
normalized_role = (role or "worker").lower()
language = (language or "").lower()
if normalized_role == "backend":
if language in {"python", "py"}:
return "backend/app.py" if index == 0 else f"backend/module_{index + 1}.py"
return "backend/implementation.txt"
if normalized_role == "frontend":
if language in {"jsx", "tsx", "javascript", "js", "typescript", "ts"}:
return "frontend/OrderPage.jsx" if index == 0 else f"frontend/component_{index + 1}.jsx"
if language == "css":
return "frontend/styles.css"
return "frontend/implementation.txt"
if normalized_role == "reviewer":
return "review/review.md"
return f"{normalized_role}/artifact_{index + 1}.txt"
def _project_readme(self, role: str, content: str) -> str:
"""Build a lightweight README for project-folder artifacts."""
return (
f"# {role} delivery\n\n"
"This project-folder artifact was synthesized by the sub-mode runtime from the agent response.\n\n"
"## Summary\n\n"
f"{content[:1500].strip()}\n"
)
+81
View File
@@ -1,6 +1,7 @@
"""Sub-mode runtime compatibility router for legacy /api/swarms clients."""
import json
import mimetypes
import uuid
from datetime import datetime, timedelta
from typing import Dict, Any
@@ -15,8 +16,13 @@ from database import (
from k8s_manager import sanitize_k8s_name
from .artifact_store import (
load_azblob_artifact,
load_runtime_project_artifact,
load_runtime_project_file,
load_runtime_artifact,
runtime_artifact_archive_path,
runtime_artifact_download_path,
runtime_artifact_file_path,
runtime_artifact_manifest_path,
runtime_artifact_uri,
store_text_artifact,
)
@@ -552,6 +558,14 @@ async def get_swarm_artifact_content(
if not artifact:
raise HTTPException(status_code=404, detail="Artifact not found")
project_artifact = load_runtime_project_artifact(swarm_id, artifact_id)
if project_artifact:
return FileResponse(
path=project_artifact.archive_path,
media_type="application/zip",
filename=project_artifact.archive_path.name,
)
stored = load_runtime_artifact(swarm_id, artifact_id)
if stored:
return FileResponse(
@@ -572,6 +586,73 @@ async def get_swarm_artifact_content(
raise HTTPException(status_code=404, detail="Artifact content not found")
@swarms_router.get("/{swarm_id}/artifacts/{artifact_id}/manifest")
async def get_swarm_artifact_manifest(
swarm_id: str,
artifact_id: str,
db: Session = Depends(get_db),
):
"""Return manifest JSON for a project-folder artifact."""
swarm = db.query(Swarm).filter(Swarm.swarm_id == swarm_id).first()
if not swarm:
raise HTTPException(status_code=404, detail="Runtime deployment not found")
project_artifact = load_runtime_project_artifact(swarm_id, artifact_id)
if not project_artifact:
raise HTTPException(status_code=404, detail="Project artifact manifest not found")
return FileResponse(
path=project_artifact.manifest_path,
media_type="application/json",
filename=project_artifact.manifest_path.name,
)
@swarms_router.get("/{swarm_id}/artifacts/{artifact_id}/archive.zip")
async def get_swarm_artifact_archive(
swarm_id: str,
artifact_id: str,
db: Session = Depends(get_db),
):
"""Return zip archive for a project-folder artifact."""
swarm = db.query(Swarm).filter(Swarm.swarm_id == swarm_id).first()
if not swarm:
raise HTTPException(status_code=404, detail="Runtime deployment not found")
project_artifact = load_runtime_project_artifact(swarm_id, artifact_id)
if not project_artifact:
raise HTTPException(status_code=404, detail="Project artifact archive not found")
return FileResponse(
path=project_artifact.archive_path,
media_type="application/zip",
filename=project_artifact.archive_path.name,
)
@swarms_router.get("/{swarm_id}/artifacts/{artifact_id}/files/{file_path:path}")
async def get_swarm_artifact_file(
swarm_id: str,
artifact_id: str,
file_path: str,
db: Session = Depends(get_db),
):
"""Return a single file from a project-folder artifact."""
swarm = db.query(Swarm).filter(Swarm.swarm_id == swarm_id).first()
if not swarm:
raise HTTPException(status_code=404, detail="Runtime deployment not found")
resolved_file = load_runtime_project_file(swarm_id, artifact_id, file_path)
if not resolved_file:
raise HTTPException(status_code=404, detail="Project artifact file not found")
return FileResponse(
path=resolved_file,
media_type=mimetypes.guess_type(str(resolved_file))[0] or "text/plain",
filename=resolved_file.name,
)
@swarms_router.post("/{swarm_id}/approvals/{approval_id}")
async def receive_swarm_approval_decision(
swarm_id: str,
+4 -3
View File
@@ -359,9 +359,10 @@ artifact_type = project_folder
当前仓库现状说明:
- 当前实现仍以 `single_file_content` 作为主要真实产物形态
- 结构性代码任务尚未默认落成 `project_folder`
- 这属于当前实现缺口,不应被视为最终统一协议目标
- 结构性代码角色(如 `backend` / `frontend` / `coder` / `engineer` / `fullstack`)现在会优先产出 `project_folder`
- 普通摘要/兼容型产物仍可能是 `single_file_content`
- `project_folder` 当前已经支持 `manifest`、`archive.zip`、`files/{path}` 读取
- 更完整的 revision / conflict 协议仍属于后续增强项
接入方应按以下原则理解:
+1 -1
View File
@@ -31,7 +31,7 @@ spec:
containers:
- name: agent-manager
image: agnettaiji.azurecr.io/ai-agents/agent-manager:heicode-v2-runtime-20260601235948-arm64
image: agnettaiji.azurecr.io/ai-agents/agent-manager:heicode-v2-runtime-20260602171443-arm64
imagePullPolicy: Always
ports:
+1 -1
View File
@@ -22,7 +22,7 @@ spec:
- name: acr-secret
containers:
- name: agent-manager
image: agnettaiji.azurecr.io/ai-agents/agent-manager:heicode-v2-runtime-20260601235948-arm64
image: agnettaiji.azurecr.io/ai-agents/agent-manager:heicode-v2-runtime-20260602171443-arm64
imagePullPolicy: Always
ports:
- containerPort: 8000
+1 -1
View File
@@ -20,7 +20,7 @@ spec:
- name: acr-secret
containers:
- name: agent-manager
image: agnettaiji.azurecr.io/ai-agents/agent-manager:heicode-v2-runtime-20260601235948-arm64
image: agnettaiji.azurecr.io/ai-agents/agent-manager:heicode-v2-runtime-20260602171443-arm64
imagePullPolicy: Always
ports:
- containerPort: 8000
+18
View File
@@ -27,6 +27,9 @@ def test_primary_and_compat_runtime_routes_are_registered():
assert '@compat_router.post("/swarm-events")' in callbacks_source
assert 'prefix="/api/swarms"' in swarm_router_source
assert '@swarms_router.post("/{swarm_id}/approvals/{approval_id}")' in swarm_router_source
assert '@swarms_router.get("/{swarm_id}/artifacts/{artifact_id}/manifest")' in swarm_router_source
assert '@swarms_router.get("/{swarm_id}/artifacts/{artifact_id}/archive.zip")' in swarm_router_source
assert '@swarms_router.get("/{swarm_id}/artifacts/{artifact_id}/files/{file_path:path}")' in swarm_router_source
def test_projected_statuses_cover_manager_facing_contract():
@@ -64,3 +67,18 @@ def test_runtime_run_projection_remains_sub_mode_compatible():
assert project_runtime_run_status("completed") == RuntimeDisplayStatus.COMPLETED.value
assert project_runtime_run_status("failed") == RuntimeDisplayStatus.FAILED.value
assert project_runtime_run_status("stopped") == RuntimeDisplayStatus.STOPPED.value
def test_project_folder_contract_is_documented_and_flagged():
"""Structural code artifacts should be documented and tagged distinctly."""
doc_source = _read("docs/HEICODE_SUB_MODE_RUNTIME_INTEGRATION.md")
orchestrator_source = _read("api/swarm/orchestrator.py")
swarm_router_source = _read("api/swarm/router.py")
assert "project_folder artifact(结构性代码强制要求)" in doc_source
assert "manifest_uri" in doc_source
assert "archive_uri" in doc_source
assert "files/{path}" in doc_source
assert '"artifact_layout": "project_folder"' in orchestrator_source
assert '"primary_read_path": "manifest"' in orchestrator_source
assert '"summary_only": True' in swarm_router_source