契约骨架补充:运行路径上的能力登记函数给出和今天一致的默认实现(并行开发互不卡住),build_tools 加 allow_write 参数;前端补 ViewRenderer、PrincipalPicker 的接口桩和按草稿预览的参数

This commit is contained in:
ThearchyHelios
2026-10-14 17:56:54 +08:00
parent 8f64853bf6
commit c38c0043a4
5 changed files with 66 additions and 7 deletions
+9 -5
View File
@@ -28,25 +28,29 @@ async def sync_all(session: AsyncSession) -> int:
raise NotImplementedError("BE-1")
# 运行路径上会被别的包调到的几个函数,契约骨架里给的是「和今天行为一致」的默认实现(不查登记、不改变任何判定),
# 并行开发时一个包没写完不会弄挂别的包的测试。BE-1 换成真实现,签名不变。
async def get_capability(session: AsyncSession, name: str) -> Any | None:
"""一行能力登记(db.models_capabilities.Capability),没有就是 None。"""
raise NotImplementedError("BE-1")
return None
def effective_effect(capability: Any) -> str:
"""治理后的效果:effect 为空时按 effect_system。"""
raise NotImplementedError("BE-1")
return getattr(capability, "effect", None) or getattr(capability, "effect_system", None) or "dynamic"
async def capability_snapshot(session: AsyncSession, tool_names: list[str]) -> dict[str, Any]:
"""运行发起时用到的能力的治理快照:{名字: {effect, approval, unattended_write, version, endpoint_id, masked}}。
写进 Run.options["capabilities"],恢复和重放用同一份。"""
raise NotImplementedError("BE-1")
return {}
async def capability_versions_for(session: AsyncSession, tool_names: list[str]) -> dict[str, int]:
"""发布时记下的能力版本:{名字: version}。"""
raise NotImplementedError("BE-1")
return {}
def is_platform_query(name: str) -> bool:
@@ -66,4 +70,4 @@ async def endpoint_request(session: AsyncSession, capability: Any, args: dict[st
def masked_paths(capability: Any) -> list[str]:
"""能力的遮罩字段(JSON 指针)。"""
raise NotImplementedError("BE-1")
return list(getattr(capability, "masked", None) or [])
+4 -1
View File
@@ -140,10 +140,13 @@ def build_tool(spec: ToolSpec, ctx: ToolContext) -> BaseTool:
async def build_tools(
names: list[str], ctx: ToolContext, *, session: Any = None
names: list[str], ctx: ToolContext, *, session: Any = None, allow_write: bool = False
) -> list[BaseTool]:
"""按名字列表组装工具。
allow_write(应用平台 A5):写入能力只能放在调用工具节点里——只有调用工具节点传 True;agent、协作节点用默认值,
能力登记里治理后效果为 write 的工具在这里就被拒绝(BE-1 实现判定;契约骨架阶段不拦,行为同以前)。
四种来源:内置(静态注册)、MCP(mcp:<server>/<tool>)、数据源
(db_query__<源名> / db_schema__<源名>)、自定义(数据库里的 CustomTool)。
后两种都是运行时从库里读出来动态构造的,不在静态注册表里。
+4 -1
View File
@@ -32,9 +32,12 @@ export const apps = {
grants: (id: string) => get<{ items: AppGrant[] }>(`${at(id)}/grants`),
setGrants: (id: string, items: AppGrant[]) => put<{ items: AppGrant[] }>(`${at(id)}/grants`, { items }),
/** 页面解析:服务端解析好的视图模型(不可见的组件不下发,遮罩值不出服务端) */
page: (id: string, page: string, params?: { instance?: string; params?: Record<string, unknown> }, opts?: RequestOptions) =>
page: (id: string, page: string,
params?: { instance?: string; params?: Record<string, unknown>; draft?: boolean }, opts?: RequestOptions) =>
get<ViewModel>(`${at(id)}/pages/${encodeURIComponent(page)}${qs({
instance: params?.instance, params: params?.params ? JSON.stringify(params.params) : undefined,
// 编辑器预览:按草稿解析(不发起运行,视图取钉住版本已有的封存运行)
draft: params?.draft ? 1 : undefined,
})}`, opts),
/** 发起一个实例 */
start: (id: string, entry: string, values: Record<string, unknown>) =>
+27
View File
@@ -0,0 +1,27 @@
/**
* 视图渲染器:按服务端解析好的视图模型(agentlab.view/1)渲染应用页(FE-5 实现)。
*
* 前端只有一张「组件名 → 渲染器」的表;不认识的组件显示「此组件需要更新前端」,不报错、不空白。应用编辑器的预览
* (FE-6)也用它:传 draft 解析出的视图模型即可。契约骨架里只有接口和最简实现,props 不变。
*/
import type { ViewModel, ViewNode } from '../typesApp'
export interface ViewRendererProps {
model: ViewModel
appId: string
/** 实例页传入:组件里的待办、动作、进度按这个实例解析 */
runId?: string
/** 预览:不发起运行、不提交表单,点数字照样能看出处 */
preview?: boolean
/** 页内跳转(页签、页面链接) */
onNavigate?: (pageId: string) => void
/** 视图刷新、实例发起之后要重新取视图模型 */
onReload?: () => void
}
export function ViewRenderer({ model }: ViewRendererProps) {
const walk = (nodes: ViewNode[]) => nodes.map((n) => (
<div key={n.id} data-component={n.type}>{n.children && walk(n.children)}</div>
))
return <div>{walk(model.tree)}</div>
}
@@ -0,0 +1,22 @@
/**
* 选人或组(FE-5 实现):岗位绑定、应用授权、改派共用。关登录时只能手填名字(没有用户表)。
* 契约骨架里只有接口和最简实现,props 不变。
*/
export interface Principal {
kind: 'user' | 'group' | 'everyone'
name: string
}
export interface PrincipalPickerProps {
value: Principal[]
onChange: (value: Principal[]) => void
/** 允许选「所有人」(应用授权用) */
allowEveryone?: boolean
/** 只能选一个 */
single?: boolean
label?: string
}
export function PrincipalPicker({ value }: PrincipalPickerProps) {
return <div>{value.map((p) => p.name).join('、')}</div>
}