Release v1.0.127: document executionProfile stream for HTML token streaming
All checks were successful
技能自动化发布 / release (push) Successful in 6s

This commit is contained in:
2026-10-25 18:22:25 +08:00
parent 8a8fa3cb0c
commit d911bd28cd
14 changed files with 141 additions and 63 deletions

View File

@@ -16,7 +16,7 @@ from util.constants import SKILL_SLUG
_TEMPLATE_PLACEHOLDER_SLUG = "your-skill-slug"
_ALLOWED_PLACEMENTS = frozenset({"toolbar", "row", "batch", "cron", "agent", "skill-detail"})
_RESERVED_ACTION_FIELDS = frozenset({"concurrency", "locks"})
_ALLOWED_EXECUTION_PROFILES = frozenset({"sync", "async"})
_ALLOWED_EXECUTION_PROFILES = frozenset({"sync", "async", "stream"})
_REQUIRED_MANIFEST_KEYS = frozenset({"schemaVersion", "skill", "actions"})
_REQUIRED_ACTION_KEYS = frozenset(
{"id", "label", "description", "placements", "entrypoint", "executionProfile"}
@@ -232,7 +232,7 @@ class TestActionsManifest(unittest.TestCase):
self.assertIn(
profile,
_ALLOWED_EXECUTION_PROFILES,
msg=f"{action['id']} executionProfile must be sync|async, got {profile!r}",
msg=f"{action['id']} executionProfile must be sync|async|stream, got {profile!r}",
)
def test_schema_requires_execution_profile_and_defines_bind(self) -> None:
@@ -240,7 +240,7 @@ class TestActionsManifest(unittest.TestCase):
action_def = schema["$defs"]["action"]
self.assertIn("executionProfile", action_def.get("required") or [])
profile = action_def["properties"]["executionProfile"]
self.assertEqual(set(profile.get("enum") or []), {"sync", "async"})
self.assertEqual(set(profile.get("enum") or []), {"sync", "async", "stream"})
self.assertIn("bind", schema["$defs"])
self.assertIn("bind", action_def["properties"])
bind_def = schema["$defs"]["bind"]
@@ -424,14 +424,14 @@ class TestActionsManifest(unittest.TestCase):
self.assertEqual(missing, [], msg="\n".join(missing))
def test_schema_allows_all_placement_execution_profile_combinations(self) -> None:
"""POLICY-SKILL-ACTION-004:6 placements × 2 profiles 必须全部通过 Schema。"""
"""POLICY-SKILL-ACTION-004:6 placements × 3 profiles 必须全部通过 Schema。"""
try:
import jsonschema
except ImportError:
self.fail("jsonschema is required to validate placement×executionProfile orthogonality")
schema = _load_actions_schema()
placements = ("toolbar", "row", "batch", "cron", "agent", "skill-detail")
profiles = ("sync", "async")
profiles = ("sync", "async", "stream")
for placement in placements:
for profile in profiles:
action: dict = {

View File

@@ -1382,12 +1382,12 @@ class TestPolicySkillAction001(unittest.TestCase):
"action.properties.executionProfile missing",
),
)
self.assertEqual(set(profile.get("enum") or []), {"sync", "async"})
self.assertEqual(set(profile.get("enum") or []), {"sync", "async", "stream"})
def test_actions_md_documents_execution_profile(self) -> None:
path = os.path.join(get_skill_root(), "references", "ACTIONS.md")
text = _read_text(path)
for marker in ("executionProfile", "async", "任务中心", "SKILL_ACTION_RUNTIME"):
for marker in ("executionProfile", "async", "stream", "任务中心", "SKILL_ACTION_RUNTIME"):
self.assertIn(
marker,
text,
@@ -1407,7 +1407,7 @@ class TestPolicySkillAction001(unittest.TestCase):
for action in manifest.get("actions") or []:
action_id = action.get("id", "?")
profile = action.get("executionProfile")
if profile not in ("sync", "async"):
if profile not in ("sync", "async", "stream"):
offenders.append(f"{action_id}: executionProfile={profile!r}")
self.assertEqual(
offenders,
@@ -1415,7 +1415,8 @@ class TestPolicySkillAction001(unittest.TestCase):
msg=_policy_msg(
"POLICY-SKILL-ACTION-001",
"assets/actions.json",
"every action must set executionProfile to sync|async:\n" + "\n".join(offenders),
"every action must set executionProfile to sync|async|stream:\n"
+ "\n".join(offenders),
),
)
@@ -1469,7 +1470,10 @@ class TestPolicySkillAction002(unittest.TestCase):
"executionProfile must be in action.required",
),
)
self.assertEqual(set(action["properties"]["executionProfile"].get("enum") or []), {"sync", "async"})
self.assertEqual(
set(action["properties"]["executionProfile"].get("enum") or []),
{"sync", "async", "stream"},
)
def test_manifest_actions_declare_execution_profile(self) -> None:
manifest = _load_actions_json(get_skill_root())
@@ -1478,7 +1482,7 @@ class TestPolicySkillAction002(unittest.TestCase):
offenders: list[str] = []
for action in manifest.get("actions") or []:
profile = action.get("executionProfile")
if profile not in ("sync", "async"):
if profile not in ("sync", "async", "stream"):
offenders.append(f"{action.get('id', '?')}: {profile!r}")
self.assertEqual(
offenders,
@@ -1486,7 +1490,8 @@ class TestPolicySkillAction002(unittest.TestCase):
msg=_policy_msg(
"POLICY-SKILL-ACTION-002",
"assets/actions.json",
"every action must set executionProfile to sync|async:\n" + "\n".join(offenders),
"every action must set executionProfile to sync|async|stream:\n"
+ "\n".join(offenders),
),
)
@@ -1588,7 +1593,7 @@ class TestPolicySkillAction004(unittest.TestCase):
def test_runtime_doc_states_orthogonality(self) -> None:
text = _read_text(os.path.join(get_skill_root(), "development", "SKILL_ACTION_RUNTIME.md"))
for marker in ("正交", "sync|async", "toolbar"):
for marker in ("正交", "sync|async|stream", "toolbar"):
self.assertIn(
marker,
text,

View File

@@ -24,7 +24,7 @@ def _platform_kit_meets_template_minimum() -> bool:
return version_ge(installed, PLATFORM_KIT_MIN_VERSION)
def _run_main(args: list[str], timeout: int = 15) -> subprocess.CompletedProcess[str]:
def _run_main(args: list[str], timeout: int = 60) -> subprocess.CompletedProcess[str]:
root = get_skill_root()
main_py = os.path.join(root, "scripts", "main.py")
env = os.environ.copy()