M5:百工与司命——能力注册表、授契行契请命、禁域裁决与观察标证

- 百工域 domains/baigong:capabilities(统一能力契约:执行类型 builtin/qiankun/mcp/http + 四维风险标注 + 来源版本)、mandates(授契 grant / 行契 charter 预算时间窗,撤销立即生效)、tool_calls(净化摘要 + 观察标证);请命复用 pending_confirmations 加 expires_at/brief_json(超时=拒绝执行 HJ-1302,六要素说明)
- 司命裁决器 siming.py:四维取最高档(能力/数据/后果/情境);禁域为代码常量不可配置(递归删根/格式化/写裸设备/fork 炸弹、file:// 协议、密钥材料外发)——专项测试断言恶意标注与篡改对象仍拒 HJ-1301
- 执行链:禁域→裁决(HJ-1303 未授契拒且双留痕)→请命→执行(乾坤经 SandboxClient HMAC;MCP 经 call_tool;内置无副作用)→净化标证(观察非事实,敏感遮蔽+体量截断)→行动账
- MCP 登记/启停/刷新自动派生与撤下 capabilities(博闻登记接入点);内置能力种子(echo/datetime/qiankun.shell/web.fetch)
- 端点:/capabilities /mandates /petitions /tool-calls + /tools 兼容投影;前端百工页四区(能力目录四维标注/授契行契/请命批准拒绝/行动账观察标证)
- 退役:modules/safety|skills|tool_runtime|agent、application/tool_runtime、services/skills|safety、tool_policies/skills/model_endpoint_overrides 表(迁移 006)、前端 SkillManager/ToolsConsole/旧 tools 页
- 验收(test_baigong_siming 四用例):未授契被拒留痕、请命超时不执行、禁域不可关闭、结果为观察不入忆;后端 454、前端 220、BFF 4、神识 5 包全绿(2 个预存环境失败不变)
This commit is contained in:
chenxin committed 2026-08-27 19:17:21 +08:00
1 parent 144415247e
commit 684261f4b4
97 files changed
+3075 -5554

No files matched your search

+4 -123
View File
@@ -14,7 +14,6 @@ from app.models.governance import (
Goal,
PerceptionChannel,
Plan,
ToolPolicy,
WorldEntity,
)
from app.modules.runtime.events import Event
@@ -39,11 +38,7 @@ from app.schemas.admin import (
WorldEntityResponse,
)
from app.schemas.safety import (
CreateToolPolicyRequest,
SafetyPolicyItem,
ToolCallDetail,
ToolPolicyResponse,
UpdateToolPolicyRequest,
)
from app.services.audit import record_audit_event
from app.services.serializers import (
@@ -51,8 +46,6 @@ from app.services.serializers import (
goal_to_response,
perception_channel_to_response,
plan_to_response,
tool_policy_to_response,
tool_policy_to_safety_item,
world_entity_to_response,
)
@@ -81,28 +74,17 @@ async def load_active_goals_context(character_id: str) -> str | None:
目标按优先级排序(``prioritize_active_goals``),最高优先级在前。
"""
from app.modules.goal_orchestrator import prioritize_active_goals
from app.modules.runtime.goals import build_goals_context
async with AsyncSessionLocal() as session:
goals = await governance_repo.list_goals_by_character(
session, character_id, status_filter="active"
)
if not goals:
return None
goals_dicts = [
{
"title": g.title,
"level": g.level,
"status": g.status,
"priority": g.priority,
"progress": g.progress,
}
for g in goals
lines = [
f"- [{g.level}] {g.title}(优先级 {g.priority},进度 {g.progress}%)"
for g in sorted(goals, key=lambda g: -(g.priority or 0))
]
# 优先级排序后注入运行时,使最高优先级目标最先被看到
sorted_goals = prioritize_active_goals(goals_dicts)
return build_goals_context(sorted_goals)
return "【当前目标】\n" + "\n".join(lines)
async def emit_goal_event(
@@ -520,107 +502,6 @@ async def delete_agent(role: str) -> None:
# —— 百工枢 · 工具策略(§15 / §16 共享)——
def _builtin_tool_names() -> set[str]:
"""builtin 工具名集合(惰性 import,避免 application→services 循环依赖)。"""
from app.application.tool_runtime.builtins import builtin_specs
return set(builtin_specs().keys())
async def list_tool_policies() -> list[ToolPolicyResponse]:
"""GET /tools:完整元数据,executable 按 builtin 派生。"""
async with AsyncSessionLocal() as session:
policies = await governance_repo.list_tool_policies(session)
builtins = _builtin_tool_names()
return [tool_policy_to_response(p, executable=p.name in builtins) for p in policies]
async def list_safety_policies() -> list[SafetyPolicyItem]:
"""GET /safety/policies:策略子集。"""
async with AsyncSessionLocal() as session:
policies = await governance_repo.list_tool_policies(session)
return [tool_policy_to_safety_item(p) for p in policies]
async def create_tool_policy(payload: CreateToolPolicyRequest) -> ToolPolicyResponse:
"""POST /tools、POST /safety/policies:注册工具策略(source=custom)。"""
async with AsyncSessionLocal() as session:
if await governance_repo.get_tool_policy(session, payload.name) is not None:
raise AppError("invalid_request", f"工具 {payload.name} 已存在。")
policy = ToolPolicy(
name=payload.name,
description=payload.description,
risk_level=payload.risk_level,
permission_policy=payload.permission_policy,
side_effect=payload.side_effect,
audit_required=payload.audit_required,
source="custom",
enabled=payload.enabled,
)
await governance_repo.add_tool_policy(session, policy)
await record_audit_event(
session,
action="tool.policy_created",
actor="user",
risk_level=payload.risk_level,
input_summary=payload.name,
resource_type="tool_policy",
resource_id=policy.name,
)
await session.commit()
await session.refresh(policy)
return tool_policy_to_response(policy, executable=policy.name in _builtin_tool_names())
async def update_tool_policy(name: str, payload: UpdateToolPolicyRequest) -> ToolPolicyResponse:
"""PATCH /tools/{name}、PATCH /safety/policies/{tool}:编辑策略元数据。"""
async with AsyncSessionLocal() as session:
policy = await governance_repo.get_tool_policy(session, name)
if policy is None:
raise AppError("not_found", f"工具 {name} 不存在。")
updates = payload.model_dump(exclude_unset=True)
if not updates:
raise AppError("invalid_request", "至少需要提供一个待更新字段。")
for key, value in updates.items():
setattr(policy, key, value)
policy.updated_at = utc_now()
await record_audit_event(
session,
action="tool.policy_updated",
actor="user",
risk_level=policy.risk_level,
input_summary=policy.name,
resource_type="tool_policy",
resource_id=policy.name,
)
await session.commit()
await session.refresh(policy)
return tool_policy_to_response(policy, executable=policy.name in _builtin_tool_names())
async def delete_tool_policy(name: str) -> None:
"""DELETE /tools/{name}、DELETE /safety/policies/{tool}:注销策略。
builtin 工具的策略记录不可删除(执行实现仍在代码层);只允许注销 custom 工具。
"""
async with AsyncSessionLocal() as session:
policy = await governance_repo.get_tool_policy(session, name)
if policy is None:
raise AppError("not_found", f"工具 {name} 不存在。")
if policy.source == "builtin":
raise AppError("invalid_request", f"内置工具 {name} 不可删除,可改用 PATCH 禁用。")
await session.delete(policy)
await record_audit_event(
session,
action="tool.policy_deleted",
actor="user",
input_summary=policy.name,
resource_type="tool_policy",
resource_id=policy.name,
)
await session.commit()
async def get_tool_call(tool_call_id: str) -> ToolCallDetail:
"""GET /tools/calls/{tool_call_id}:单条 tool.* 事件详情。