吸收数字员工管理端技能版本权限预览
This commit is contained in:
@@ -102,6 +102,16 @@ public class DigitalEmployeeAdminWorkbenchRowDto {
|
|||||||
private String memoryKey;
|
private String memoryKey;
|
||||||
@JsonProperty("skill_id")
|
@JsonProperty("skill_id")
|
||||||
private String skillId;
|
private String skillId;
|
||||||
|
@JsonProperty("skill_version")
|
||||||
|
private String skillVersion;
|
||||||
|
@JsonProperty("install_status")
|
||||||
|
private String installStatus;
|
||||||
|
@JsonProperty("permission_scope")
|
||||||
|
private String permissionScope;
|
||||||
|
@JsonProperty("policy_source")
|
||||||
|
private String policySource;
|
||||||
|
@JsonProperty("policy_status")
|
||||||
|
private String policyStatus;
|
||||||
@JsonProperty("tool_name")
|
@JsonProperty("tool_name")
|
||||||
private String toolName;
|
private String toolName;
|
||||||
@JsonProperty("server_id")
|
@JsonProperty("server_id")
|
||||||
|
|||||||
@@ -1093,6 +1093,11 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye
|
|||||||
.memoryId(firstString(businessView.get("memory_id"), businessView.get("memoryId"), payload.get("memory_id"), payload.get("memoryId"), "memory".equals(record.getEntityType()) ? record.getEntityId() : null))
|
.memoryId(firstString(businessView.get("memory_id"), businessView.get("memoryId"), payload.get("memory_id"), payload.get("memoryId"), "memory".equals(record.getEntityType()) ? record.getEntityId() : null))
|
||||||
.memoryKey(firstString(businessView.get("memory_key"), businessView.get("memoryKey"), businessView.get("key"), payload.get("memory_key"), payload.get("memoryKey"), payload.get("key")))
|
.memoryKey(firstString(businessView.get("memory_key"), businessView.get("memoryKey"), businessView.get("key"), payload.get("memory_key"), payload.get("memoryKey"), payload.get("key")))
|
||||||
.skillId(firstString(businessView.get("skill_id"), businessView.get("skillId"), payload.get("skill_id"), payload.get("skillId")))
|
.skillId(firstString(businessView.get("skill_id"), businessView.get("skillId"), payload.get("skill_id"), payload.get("skillId")))
|
||||||
|
.skillVersion(firstString(businessView.get("skill_version"), businessView.get("skillVersion"), businessView.get("version"), payload.get("skill_version"), payload.get("skillVersion"), payload.get("version")))
|
||||||
|
.installStatus(firstString(businessView.get("install_status"), businessView.get("installStatus"), payload.get("install_status"), payload.get("installStatus")))
|
||||||
|
.permissionScope(firstString(businessView.get("permission_scope"), businessView.get("permissionScope"), payload.get("permission_scope"), payload.get("permissionScope")))
|
||||||
|
.policySource(firstString(businessView.get("policy_source"), businessView.get("policySource"), businessView.get("source"), payload.get("policy_source"), payload.get("policySource"), payload.get("source")))
|
||||||
|
.policyStatus(firstString(businessView.get("policy_status"), businessView.get("policyStatus"), payload.get("policy_status"), payload.get("policyStatus")))
|
||||||
.toolName(firstString(businessView.get("tool_name"), businessView.get("toolName"), payload.get("tool_name"), payload.get("toolName")))
|
.toolName(firstString(businessView.get("tool_name"), businessView.get("toolName"), payload.get("tool_name"), payload.get("toolName")))
|
||||||
.serverId(firstString(businessView.get("server_id"), businessView.get("serverId"), payload.get("server_id"), payload.get("serverId")))
|
.serverId(firstString(businessView.get("server_id"), businessView.get("serverId"), payload.get("server_id"), payload.get("serverId")))
|
||||||
.policyKind(firstString(businessView.get("policy_kind"), businessView.get("policyKind"), payload.get("policy_kind"), payload.get("policyKind")))
|
.policyKind(firstString(businessView.get("policy_kind"), businessView.get("policyKind"), payload.get("policy_kind"), payload.get("policyKind")))
|
||||||
@@ -1236,11 +1241,17 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye
|
|||||||
String kind = firstString(row.getKind());
|
String kind = firstString(row.getKind());
|
||||||
return StringUtils.equals(category, "skill")
|
return StringUtils.equals(category, "skill")
|
||||||
|| StringUtils.equals(category, "skill_call_audit")
|
|| StringUtils.equals(category, "skill_call_audit")
|
||||||
|
|| StringUtils.equals(category, "skill_catalog")
|
||||||
|
|| StringUtils.equals(category, "skill_policy")
|
||||||
|| startsWithAny(kind, "skill_call_")
|
|| startsWithAny(kind, "skill_call_")
|
||||||
|
|| startsWithAny(kind, "skill_catalog", "skill_policy")
|
||||||
|| StringUtils.equals(category, "tool_call_audit")
|
|| StringUtils.equals(category, "tool_call_audit")
|
||||||
|| StringUtils.equals(category, "sandbox_audit")
|
|| StringUtils.equals(category, "sandbox_audit")
|
||||||
|| StringUtils.equals(category, "token_cost")
|
|| StringUtils.equals(category, "token_cost")
|
||||||
|| StringUtils.isNotBlank(row.getSkillId())
|
|| StringUtils.isNotBlank(row.getSkillId())
|
||||||
|
|| StringUtils.isNotBlank(row.getSkillVersion())
|
||||||
|
|| StringUtils.isNotBlank(row.getInstallStatus())
|
||||||
|
|| StringUtils.isNotBlank(row.getPermissionScope())
|
||||||
|| StringUtils.isNotBlank(row.getToolName())
|
|| StringUtils.isNotBlank(row.getToolName())
|
||||||
|| StringUtils.isNotBlank(row.getServerId());
|
|| StringUtils.isNotBlank(row.getServerId());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -461,6 +461,67 @@ class DigitalEmployeeSyncApplicationServiceImplTest {
|
|||||||
assertThat(result.getRecords().get(2).getBusinessView()).containsEntry("total_tokens", 180);
|
assertThat(result.getRecords().get(2).getBusinessView()).containsEntry("total_tokens", 180);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void queryAdminWorkbenchSkillAuditsReturnsVersionPermissionAndPolicyRowsOnly() {
|
||||||
|
when(repository.queryBusinessRecords(eq(101L), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null), eq(1L), eq(20L)))
|
||||||
|
.thenReturn(page(List.of(
|
||||||
|
record("outbox-skill-catalog", "event", "event-skill-catalog-1", 101L, new Date(10_000),
|
||||||
|
Map.of(
|
||||||
|
"entity_type", "event",
|
||||||
|
"category", "skill_catalog",
|
||||||
|
"kind", "skill_catalog_snapshot",
|
||||||
|
"status", "enabled",
|
||||||
|
"title", "Browser skill installed",
|
||||||
|
"summary", "浏览器技能已安装",
|
||||||
|
"skill_id", "skill-web",
|
||||||
|
"skill_version", "1.2.3",
|
||||||
|
"install_status", "installed",
|
||||||
|
"permission_scope", "browser.open,browser.screenshot",
|
||||||
|
"policy_source", "local"
|
||||||
|
),
|
||||||
|
Map.of("id", "event-skill-catalog-1", "kind", "skill_catalog_snapshot")),
|
||||||
|
record("outbox-skill-policy", "event", "event-skill-policy-1", 101L, new Date(11_000),
|
||||||
|
Map.of(
|
||||||
|
"entity_type", "event",
|
||||||
|
"category", "skill_policy",
|
||||||
|
"kind", "skill_policy_snapshot",
|
||||||
|
"status", "remote_disabled",
|
||||||
|
"title", "Browser skill policy",
|
||||||
|
"skill_id", "skill-web",
|
||||||
|
"skill_version", "1.2.3",
|
||||||
|
"policy_source", "remote",
|
||||||
|
"policy_status", "disabled",
|
||||||
|
"permission_scope", "browser.open"
|
||||||
|
),
|
||||||
|
Map.of("id", "event-skill-policy-1", "kind", "skill_policy_snapshot")),
|
||||||
|
record("outbox-notification", "notification", "notification-1", 101L, new Date(12_000),
|
||||||
|
Map.of("entity_type", "notification", "title", "普通通知", "status", "unread"),
|
||||||
|
Map.of("id", "notification-1", "status", "unread")),
|
||||||
|
record("outbox-approval", "approval", "approval-1", 101L, new Date(13_000),
|
||||||
|
Map.of("entity_type", "approval", "title", "审批", "status", "pending"),
|
||||||
|
Map.of("id", "approval-1", "status", "pending")),
|
||||||
|
record("outbox-route", "event", "event-route-1", 101L, new Date(14_000),
|
||||||
|
Map.of("entity_type", "event", "kind", "route_decision_blocked", "title", "路由阻断", "status", "blocked"),
|
||||||
|
Map.of("id", "event-route-1", "kind", "route_decision_blocked"))
|
||||||
|
)));
|
||||||
|
|
||||||
|
DigitalEmployeeAdminWorkbenchPageDto result = service.queryAdminWorkbench(
|
||||||
|
"skill_audits", null, null, null, null, null, null, null, null, null, null, null, 1L, 20L
|
||||||
|
);
|
||||||
|
|
||||||
|
assertThat(result.getTotal()).isEqualTo(2);
|
||||||
|
assertThat(result.getRecords()).extracting(DigitalEmployeeAdminWorkbenchRowDto::getEntityId)
|
||||||
|
.containsExactly("event-skill-catalog-1", "event-skill-policy-1");
|
||||||
|
assertThat(result.getRecords().get(0).getView()).isEqualTo("skill_audits");
|
||||||
|
assertThat(result.getRecords().get(0).getSkillId()).isEqualTo("skill-web");
|
||||||
|
assertThat(result.getRecords().get(0).getSkillVersion()).isEqualTo("1.2.3");
|
||||||
|
assertThat(result.getRecords().get(0).getInstallStatus()).isEqualTo("installed");
|
||||||
|
assertThat(result.getRecords().get(0).getPermissionScope()).isEqualTo("browser.open,browser.screenshot");
|
||||||
|
assertThat(result.getRecords().get(0).getPolicySource()).isEqualTo("local");
|
||||||
|
assertThat(result.getRecords().get(1).getPolicySource()).isEqualTo("remote");
|
||||||
|
assertThat(result.getRecords().get(1).getPolicyStatus()).isEqualTo("disabled");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void queryAdminWorkbenchPolicyCenterReturnsPolicySnapshotRowsOnly() {
|
void queryAdminWorkbenchPolicyCenterReturnsPolicySnapshotRowsOnly() {
|
||||||
when(repository.queryBusinessRecords(eq(101L), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null), eq(1L), eq(20L)))
|
when(repository.queryBusinessRecords(eq(101L), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null), eq(1L), eq(20L)))
|
||||||
|
|||||||
@@ -128,6 +128,10 @@ function renderSummary(record: DigitalEmployeeAdminWorkbenchRow) {
|
|||||||
record.daily_report_fact_id && `ReportFact ${record.daily_report_fact_id}`,
|
record.daily_report_fact_id && `ReportFact ${record.daily_report_fact_id}`,
|
||||||
record.memory_id && `Memory ${record.memory_id}`,
|
record.memory_id && `Memory ${record.memory_id}`,
|
||||||
record.skill_id && `Skill ${record.skill_id}`,
|
record.skill_id && `Skill ${record.skill_id}`,
|
||||||
|
record.skill_version && `Version ${record.skill_version}`,
|
||||||
|
record.install_status && `Install ${record.install_status}`,
|
||||||
|
record.permission_scope && `Scope ${record.permission_scope}`,
|
||||||
|
record.policy_source && `PolicySource ${record.policy_source}`,
|
||||||
record.tool_name && `Tool ${record.tool_name}`,
|
record.tool_name && `Tool ${record.tool_name}`,
|
||||||
record.schedule_id && `Schedule ${record.schedule_id}`,
|
record.schedule_id && `Schedule ${record.schedule_id}`,
|
||||||
record.job_id && `Job ${record.job_id}`,
|
record.job_id && `Job ${record.job_id}`,
|
||||||
@@ -835,6 +839,8 @@ const DigitalEmployeeOps: React.FC = () => {
|
|||||||
detailRecord.memory_id && `Memory ${detailRecord.memory_id}`,
|
detailRecord.memory_id && `Memory ${detailRecord.memory_id}`,
|
||||||
detailRecord.memory_key && `MemoryKey ${detailRecord.memory_key}`,
|
detailRecord.memory_key && `MemoryKey ${detailRecord.memory_key}`,
|
||||||
detailRecord.skill_id && `Skill ${detailRecord.skill_id}`,
|
detailRecord.skill_id && `Skill ${detailRecord.skill_id}`,
|
||||||
|
detailRecord.skill_version && `Version ${detailRecord.skill_version}`,
|
||||||
|
detailRecord.policy_source && `PolicySource ${detailRecord.policy_source}`,
|
||||||
detailRecord.tool_name && `Tool ${detailRecord.tool_name}`,
|
detailRecord.tool_name && `Tool ${detailRecord.tool_name}`,
|
||||||
detailRecord.server_id && `Server ${detailRecord.server_id}`,
|
detailRecord.server_id && `Server ${detailRecord.server_id}`,
|
||||||
detailRecord.schedule_id && `Schedule ${detailRecord.schedule_id}`,
|
detailRecord.schedule_id && `Schedule ${detailRecord.schedule_id}`,
|
||||||
@@ -977,9 +983,14 @@ const DigitalEmployeeOps: React.FC = () => {
|
|||||||
<Typography.Text strong>技能审计:</Typography.Text>
|
<Typography.Text strong>技能审计:</Typography.Text>
|
||||||
{detailRecord.skill_id || detailRecord.tool_name || detailRecord.category || '--'}
|
{detailRecord.skill_id || detailRecord.tool_name || detailRecord.category || '--'}
|
||||||
{detailRecord.status && ` · ${detailRecord.status}`}
|
{detailRecord.status && ` · ${detailRecord.status}`}
|
||||||
|
{detailRecord.skill_version && ` · 版本 ${detailRecord.skill_version}`}
|
||||||
|
{detailRecord.install_status && ` · 安装状态 ${detailRecord.install_status}`}
|
||||||
|
{detailRecord.permission_scope && ` · 权限范围 ${detailRecord.permission_scope}`}
|
||||||
|
{detailRecord.policy_source && ` · 策略来源 ${detailRecord.policy_source}`}
|
||||||
|
{detailRecord.policy_status && ` · 策略状态 ${detailRecord.policy_status}`}
|
||||||
<Typography.Text type="secondary">
|
<Typography.Text type="secondary">
|
||||||
{' '}
|
{' '}
|
||||||
· 只消费调用审计和安全摘要,不安装技能、不变更权限范围。
|
· 展示版本、安装状态、权限范围和策略来源,只消费调用审计和安全摘要,不安装技能、不变更权限范围。
|
||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
</Typography.Paragraph>
|
</Typography.Paragraph>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -628,6 +628,11 @@ export interface DigitalEmployeeAdminWorkbenchRow {
|
|||||||
memory_id?: string;
|
memory_id?: string;
|
||||||
memory_key?: string;
|
memory_key?: string;
|
||||||
skill_id?: string;
|
skill_id?: string;
|
||||||
|
skill_version?: string;
|
||||||
|
install_status?: string;
|
||||||
|
permission_scope?: string;
|
||||||
|
policy_source?: string;
|
||||||
|
policy_status?: string;
|
||||||
tool_name?: string;
|
tool_name?: string;
|
||||||
server_id?: string;
|
server_id?: string;
|
||||||
policy_kind?: string;
|
policy_kind?: string;
|
||||||
|
|||||||
@@ -1585,19 +1585,29 @@ function checkDigitalAdminMemorySkillWorkbench() {
|
|||||||
[dto, "memory_id", "backend memory id row field"],
|
[dto, "memory_id", "backend memory id row field"],
|
||||||
[dto, "memory_key", "backend memory key row field"],
|
[dto, "memory_key", "backend memory key row field"],
|
||||||
[dto, "skill_id", "backend skill id row field"],
|
[dto, "skill_id", "backend skill id row field"],
|
||||||
|
[dto, "skill_version", "backend skill version row field"],
|
||||||
|
[dto, "install_status", "backend skill install status row field"],
|
||||||
|
[dto, "permission_scope", "backend skill permission scope row field"],
|
||||||
|
[dto, "policy_source", "backend skill policy source row field"],
|
||||||
[dto, "tool_name", "backend tool name row field"],
|
[dto, "tool_name", "backend tool name row field"],
|
||||||
[dto, "server_id", "backend server id row field"],
|
[dto, "server_id", "backend server id row field"],
|
||||||
[test, "queryAdminWorkbenchMemoryGovernanceReturnsMemoryRowsOnly", "backend memory governance test"],
|
[test, "queryAdminWorkbenchMemoryGovernanceReturnsMemoryRowsOnly", "backend memory governance test"],
|
||||||
[test, "queryAdminWorkbenchSkillAuditsReturnsSkillAndToolAuditRowsOnly", "backend skill audits test"],
|
[test, "queryAdminWorkbenchSkillAuditsReturnsSkillAndToolAuditRowsOnly", "backend skill audits test"],
|
||||||
|
[test, "queryAdminWorkbenchSkillAuditsReturnsVersionPermissionAndPolicyRowsOnly", "backend skill version permission test"],
|
||||||
[qimingTypes, "memory_governance", "qiming memory governance view type"],
|
[qimingTypes, "memory_governance", "qiming memory governance view type"],
|
||||||
[qimingTypes, "skill_audits", "qiming skill audits view type"],
|
[qimingTypes, "skill_audits", "qiming skill audits view type"],
|
||||||
|
[qimingTypes, "skill_version", "qiming skill version field"],
|
||||||
|
[qimingTypes, "permission_scope", "qiming skill permission scope field"],
|
||||||
[qimingOps, "记忆治理", "qiming memory governance tab"],
|
[qimingOps, "记忆治理", "qiming memory governance tab"],
|
||||||
[qimingOps, "技能审计", "qiming skill audits tab"],
|
[qimingOps, "技能审计", "qiming skill audits tab"],
|
||||||
|
[qimingOps, "权限范围", "qiming skill permission scope copy"],
|
||||||
|
[qimingOps, "策略来源", "qiming skill policy source copy"],
|
||||||
[qimingOps, "批量归档", "qiming memory archive action"],
|
[qimingOps, "批量归档", "qiming memory archive action"],
|
||||||
[qimingOps, "批量恢复", "qiming memory restore action"],
|
[qimingOps, "批量恢复", "qiming memory restore action"],
|
||||||
[qimingOps, "重复候选", "qiming memory duplicate copy"],
|
[qimingOps, "重复候选", "qiming memory duplicate copy"],
|
||||||
[docs, "正式管理端记忆治理预览", "docs memory governance absorption"],
|
[docs, "正式管理端记忆治理预览", "docs memory governance absorption"],
|
||||||
[docs, "正式管理端技能审计预览", "docs skill audit absorption"],
|
[docs, "正式管理端技能审计预览", "docs skill audit absorption"],
|
||||||
|
[docs, "正式管理端技能版本权限预览", "docs skill version permission absorption"],
|
||||||
];
|
];
|
||||||
const missing = requiredSnippets
|
const missing = requiredSnippets
|
||||||
.filter(([content, snippet]) => !content.includes(snippet))
|
.filter(([content, snippet]) => !content.includes(snippet))
|
||||||
|
|||||||
@@ -578,8 +578,8 @@ GET /api/digital-employee/approvals
|
|||||||
- 建议:先沉淀安全重启审计和人工确认体验,再评估更细粒度的服务恢复策略。
|
- 建议:先沉淀安全重启审计和人工确认体验,再评估更细粒度的服务恢复策略。
|
||||||
|
|
||||||
6. **技能生命周期与策略**
|
6. **技能生命周期与策略**
|
||||||
- 已吸收:ACP / MCP / GUI / file server 能力已投射到 Skill Catalog,技能库不再使用 demo fallback;Skill Library 启用/禁用已写入 qimingclaw 本地 `digital_employee_skill_policies_v1.local_skills`,管理端下发策略写入 `remote_skills`,远端禁用优先生效且不会被本地按钮覆盖;入口路由候选技能会按有效策略过滤;MCP 注入 Agent 前会应用有效技能策略,ACP permission / tool update 会写入 `skill_call_allowed` / `skill_call_rejected` / `skill_call_observed` 审计事件,`policy_snapshot` 会包含本地/远端策略命中字段;embedded 已提供 `/api/digital-employee/skill-call-audits` 只读投影、`POST /api/skill/policies/pull` 手动拉取入口,技能调用策略与审计已开始闭环。管理端调用审计视图已开始吸收,embedded 新增“技能审计”入口,将 skill call audit、tool_call_audit、sandbox_audit 和 token_cost 统一成可筛选列表,技能库可按 skill id 跳转聚焦。正式管理端技能审计预览已开始吸收,qiming-backend 新增 `skill_audits` 运营台视图聚合 skill call、tool、sandbox 和 token/cost 安全摘要,qiming“数字员工运营台”的“技能审计”tab 只开放标记复核和忽略意图,不安装技能、不变更权限范围。
|
- 已吸收:ACP / MCP / GUI / file server 能力已投射到 Skill Catalog,技能库不再使用 demo fallback;Skill Library 启用/禁用已写入 qimingclaw 本地 `digital_employee_skill_policies_v1.local_skills`,管理端下发策略写入 `remote_skills`,远端禁用优先生效且不会被本地按钮覆盖;入口路由候选技能会按有效策略过滤;MCP 注入 Agent 前会应用有效技能策略,ACP permission / tool update 会写入 `skill_call_allowed` / `skill_call_rejected` / `skill_call_observed` 审计事件,`policy_snapshot` 会包含本地/远端策略命中字段;embedded 已提供 `/api/digital-employee/skill-call-audits` 只读投影、`POST /api/skill/policies/pull` 手动拉取入口,技能调用策略与审计已开始闭环。管理端调用审计视图已开始吸收,embedded 新增“技能审计”入口,将 skill call audit、tool_call_audit、sandbox_audit 和 token_cost 统一成可筛选列表,技能库可按 skill id 跳转聚焦。正式管理端技能审计预览已开始吸收,qiming-backend 新增 `skill_audits` 运营台视图聚合 skill call、tool、sandbox 和 token/cost 安全摘要,qiming“数字员工运营台”的“技能审计”tab 只开放标记复核和忽略意图,不安装技能、不变更权限范围。正式管理端技能版本权限预览已开始吸收,`skill_audits` 继续聚合 skill catalog / skill policy snapshot,qiming 详情可展示技能版本、安装状态、权限范围、策略来源和策略状态,用于解释权限命中但不开放安装或权限编辑。
|
||||||
- 缺口:仍缺少技能安装、版本管理和细粒度权限范围。
|
- 缺口:仍缺少真实技能安装、版本升级管理和细粒度权限范围编辑。
|
||||||
- 建议:下一轮围绕管理端审计消费、技能安装和权限范围,把本地调用审计推进到更完整的跨端治理闭环。
|
- 建议:下一轮围绕管理端审计消费、技能安装和权限范围,把本地调用审计推进到更完整的跨端治理闭环。
|
||||||
|
|
||||||
7. **产物交付闭环**
|
7. **产物交付闭环**
|
||||||
|
|||||||
Reference in New Issue
Block a user