diff --git a/qiming-backend/app-platform-modules/app-platform-agent/app-platform-agent-core-adapter/src/main/java/com/xspaceagi/agent/core/adapter/dto/digital/DigitalEmployeeAdminWorkbenchRowDto.java b/qiming-backend/app-platform-modules/app-platform-agent/app-platform-agent-core-adapter/src/main/java/com/xspaceagi/agent/core/adapter/dto/digital/DigitalEmployeeAdminWorkbenchRowDto.java index f34bd288..38b68623 100644 --- a/qiming-backend/app-platform-modules/app-platform-agent/app-platform-agent-core-adapter/src/main/java/com/xspaceagi/agent/core/adapter/dto/digital/DigitalEmployeeAdminWorkbenchRowDto.java +++ b/qiming-backend/app-platform-modules/app-platform-agent/app-platform-agent-core-adapter/src/main/java/com/xspaceagi/agent/core/adapter/dto/digital/DigitalEmployeeAdminWorkbenchRowDto.java @@ -45,6 +45,16 @@ public class DigitalEmployeeAdminWorkbenchRowDto { private String routeDecisionId; @JsonProperty("service_id") private String serviceId; + @JsonProperty("memory_id") + private String memoryId; + @JsonProperty("memory_key") + private String memoryKey; + @JsonProperty("skill_id") + private String skillId; + @JsonProperty("tool_name") + private String toolName; + @JsonProperty("server_id") + private String serverId; @JsonProperty("occurred_at") private String occurredAt; @JsonProperty("synced_at") diff --git a/qiming-backend/app-platform-modules/app-platform-agent/app-platform-agent-core-application/src/main/java/com/xspaceagi/agent/core/application/service/DigitalEmployeeSyncApplicationServiceImpl.java b/qiming-backend/app-platform-modules/app-platform-agent/app-platform-agent-core-application/src/main/java/com/xspaceagi/agent/core/application/service/DigitalEmployeeSyncApplicationServiceImpl.java index eb36bfed..d29e63ab 100644 --- a/qiming-backend/app-platform-modules/app-platform-agent/app-platform-agent-core-application/src/main/java/com/xspaceagi/agent/core/application/service/DigitalEmployeeSyncApplicationServiceImpl.java +++ b/qiming-backend/app-platform-modules/app-platform-agent/app-platform-agent-core-application/src/main/java/com/xspaceagi/agent/core/application/service/DigitalEmployeeSyncApplicationServiceImpl.java @@ -70,6 +70,8 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye "notification:mark_read", "notification:dismiss", "artifact:mark_keep", "artifact:mark_expire", "artifact:mark_reviewed", "daily_report:mark_delivered", "daily_report:mark_confirmed", "daily_report:request_approval", + "memory:archive_memory", "memory:restore_memory", "memory:mark_reviewed", + "skill:mark_reviewed", "skill:dismiss", "audit:mark_reviewed", "audit:dismiss", "event:mark_reviewed", "event:dismiss" ); @@ -980,6 +982,11 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye .reportId(firstString(businessView.get("report_id"), payload.get("report_id"))) .routeDecisionId(firstString(businessView.get("route_decision_id"), payload.get("route_decision_id"))) .serviceId(firstString(businessView.get("service_id"), businessView.get("serviceId"), payload.get("service_id"), payload.get("serviceId"))) + .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"))) + .skillId(firstString(businessView.get("skill_id"), businessView.get("skillId"), payload.get("skill_id"), payload.get("skillId"))) + .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"))) .occurredAt(businessRow.getOccurredAt()) .syncedAt(record.getSyncedAt()) .clientKeyMasked(maskClientKey(record.getClientKey())) @@ -1005,6 +1012,8 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye case "daily_reports" -> isDailyReportRow(row); case "governance_commands" -> isGovernanceCommandRow(row); case "route_governance" -> isRouteGovernanceRow(row); + case "memory_governance" -> isMemoryGovernanceRow(row); + case "skill_audits" -> isSkillAuditRow(row); default -> true; }; } @@ -1087,6 +1096,31 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye || StringUtils.isNotBlank(row.getRouteDecisionId()); } + private boolean isMemoryGovernanceRow(DigitalEmployeeAdminWorkbenchRowDto row) { + String category = firstString(row.getCategory()); + String kind = firstString(row.getKind()); + return StringUtils.equals(row.getEntityType(), "memory") + || StringUtils.equals(category, "memory") + || StringUtils.equals(category, "memory_governance") + || startsWithAny(kind, "memory_") + || StringUtils.isNotBlank(row.getMemoryId()) + || StringUtils.isNotBlank(row.getMemoryKey()); + } + + private boolean isSkillAuditRow(DigitalEmployeeAdminWorkbenchRowDto row) { + String category = firstString(row.getCategory()); + String kind = firstString(row.getKind()); + return StringUtils.equals(category, "skill") + || StringUtils.equals(category, "skill_call_audit") + || startsWithAny(kind, "skill_call_") + || StringUtils.equals(category, "tool_call_audit") + || StringUtils.equals(category, "sandbox_audit") + || StringUtils.equals(category, "token_cost") + || StringUtils.isNotBlank(row.getSkillId()) + || StringUtils.isNotBlank(row.getToolName()) + || StringUtils.isNotBlank(row.getServerId()); + } + private String adminCategory(DigitalEmployeeBusinessViewRowDto row, Map businessView, Map payload) { if (booleanValue(businessView.get("approval_conflict_active"))) return "approval_conflict"; String kind = firstString(row.getKind(), businessView.get("kind"), payload.get("kind")); @@ -1104,6 +1138,8 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye } if (startsWithAny(kind, "artifact_")) return "artifact_lifecycle"; if (startsWithAny(kind, "daily_report")) return "daily_report"; + if (startsWithAny(kind, "memory_")) return "memory_governance"; + if (startsWithAny(kind, "skill_call_")) return "skill_call_audit"; if (startsWithAny(kind, "governance_")) return "governance"; if (contains(kind, "audit")) return kind; return row.getEntityType(); diff --git a/qiming-backend/app-platform-modules/app-platform-agent/app-platform-agent-core-application/src/test/java/com/xspaceagi/agent/core/application/service/DigitalEmployeeSyncApplicationServiceImplTest.java b/qiming-backend/app-platform-modules/app-platform-agent/app-platform-agent-core-application/src/test/java/com/xspaceagi/agent/core/application/service/DigitalEmployeeSyncApplicationServiceImplTest.java index 4bb9c1b7..2afcf454 100644 --- a/qiming-backend/app-platform-modules/app-platform-agent/app-platform-agent-core-application/src/test/java/com/xspaceagi/agent/core/application/service/DigitalEmployeeSyncApplicationServiceImplTest.java +++ b/qiming-backend/app-platform-modules/app-platform-agent/app-platform-agent-core-application/src/test/java/com/xspaceagi/agent/core/application/service/DigitalEmployeeSyncApplicationServiceImplTest.java @@ -349,6 +349,118 @@ class DigitalEmployeeSyncApplicationServiceImplTest { assertThat(result.getRecords().get(3).getApprovalId()).isEqualTo("approval-1"); } + @Test + void queryAdminWorkbenchMemoryGovernanceReturnsMemoryRowsOnly() { + 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-memory", "memory", "memory-1", 101L, new Date(10_000), + Map.of( + "entity_type", "memory", + "title", "客户偏好记忆", + "status", "active", + "memory_id", "memory-1", + "memory_key", "customer.preference", + "governance_status", "active", + "merge_candidate_count", 2, + "duplicate_memory_ids", List.of("memory-2") + ), + Map.of("id", "memory-1", "key", "customer.preference")), + record("outbox-memory-archive", "event", "event-memory-1", 101L, new Date(11_000), + Map.of( + "entity_type", "event", + "category", "memory_governance", + "kind", "memory_archived", + "status", "recorded", + "title", "记忆已归档", + "memory_id", "memory-3", + "memory_key", "customer.old", + "last_governance_action", "archive" + ), + Map.of("id", "event-memory-1", "kind", "memory_archived")), + record("outbox-task", "task", "task-1", 101L, new Date(12_000), + Map.of("entity_type", "task", "title", "普通任务", "status", "running"), + Map.of("id", "task-1", "status", "running")), + record("outbox-audit", "event", "event-audit-1", 101L, new Date(13_000), + Map.of("entity_type", "event", "category", "tool_call_audit", "title", "工具审计", "status", "warn"), + Map.of("id", "event-audit-1", "status", "warn")) + ))); + + DigitalEmployeeAdminWorkbenchPageDto result = service.queryAdminWorkbench( + "memory_governance", 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("memory-1", "event-memory-1"); + assertThat(result.getRecords().get(0).getView()).isEqualTo("memory_governance"); + assertThat(result.getRecords().get(0).getMemoryId()).isEqualTo("memory-1"); + assertThat(result.getRecords().get(0).getMemoryKey()).isEqualTo("customer.preference"); + assertThat(result.getRecords().get(0).getBusinessView()).containsEntry("merge_candidate_count", 2); + assertThat(result.getRecords().get(1).getCategory()).isEqualTo("memory_governance"); + } + + @Test + void queryAdminWorkbenchSkillAuditsReturnsSkillAndToolAuditRowsOnly() { + 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-allowed", "event", "event-skill-1", 101L, new Date(10_000), + Map.of( + "entity_type", "event", + "category", "skill", + "kind", "skill_call_allowed", + "status", "allowed", + "title", "技能调用允许", + "skill_id", "skill-web", + "tool_name", "browser.open", + "server_id", "mcp-browser", + "decision", "allowed", + "risk_level", "low" + ), + Map.of("id", "event-skill-1", "kind", "skill_call_allowed")), + record("outbox-tool-audit", "event", "event-skill-2", 101L, new Date(11_000), + Map.of( + "entity_type", "event", + "category", "tool_call_audit", + "kind", "tool_call_audit", + "status", "rejected", + "title", "工具调用拒绝", + "tool_name", "shell", + "server_id", "local", + "decision", "rejected" + ), + Map.of("id", "event-skill-2", "kind", "tool_call_audit")), + record("outbox-token-cost", "event", "event-skill-3", 101L, new Date(12_000), + Map.of( + "entity_type", "event", + "category", "token_cost", + "kind", "token_cost_recorded", + "status", "recorded", + "title", "Token 成本估算", + "model", "gpt-4.1", + "total_tokens", 180, + "estimated_cost_usd", 0.012 + ), + Map.of("id", "event-skill-3", "kind", "token_cost_recorded")), + record("outbox-notification", "notification", "notification-1", 101L, new Date(13_000), + Map.of("entity_type", "notification", "title", "普通通知", "status", "unread"), + Map.of("id", "notification-1", "status", "unread")) + ))); + + DigitalEmployeeAdminWorkbenchPageDto result = service.queryAdminWorkbench( + "skill_audits", null, null, null, null, null, null, null, null, null, null, null, 1L, 20L + ); + + assertThat(result.getTotal()).isEqualTo(3); + assertThat(result.getRecords()).extracting(DigitalEmployeeAdminWorkbenchRowDto::getEntityId) + .containsExactly("event-skill-1", "event-skill-2", "event-skill-3"); + assertThat(result.getRecords().get(0).getView()).isEqualTo("skill_audits"); + assertThat(result.getRecords().get(0).getSkillId()).isEqualTo("skill-web"); + assertThat(result.getRecords().get(0).getToolName()).isEqualTo("browser.open"); + assertThat(result.getRecords().get(0).getServerId()).isEqualTo("mcp-browser"); + assertThat(result.getRecords().get(1).getCategory()).isEqualTo("tool_call_audit"); + assertThat(result.getRecords().get(2).getBusinessView()).containsEntry("total_tokens", 180); + } + @Test void queryAdminWorkbenchServiceOperationsReturnsServiceHealthAndRestartRowsOnly() { when(repository.queryBusinessRecords(eq(101L), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null), eq(1L), eq(20L))) diff --git a/qiming/src/pages/SystemManagement/Content/DigitalEmployeeOps/index.tsx b/qiming/src/pages/SystemManagement/Content/DigitalEmployeeOps/index.tsx index 9b733a47..fdb68e10 100644 --- a/qiming/src/pages/SystemManagement/Content/DigitalEmployeeOps/index.tsx +++ b/qiming/src/pages/SystemManagement/Content/DigitalEmployeeOps/index.tsx @@ -47,6 +47,8 @@ const WORKBENCH_TABS: Array<{ { key: 'governance_commands', label: '治理命令' }, { key: 'route_governance', label: '路由治理' }, { key: 'policy_conflicts', label: '策略冲突' }, + { key: 'memory_governance', label: '记忆治理' }, + { key: 'skill_audits', label: '技能审计' }, ]; const ENTITY_TYPE_OPTIONS = { @@ -107,6 +109,9 @@ function renderSummary(record: DigitalEmployeeAdminWorkbenchRow) { record.artifact_id && `Artifact ${record.artifact_id}`, record.route_decision_id && `Route ${record.route_decision_id}`, record.policy_kind && `Policy ${record.policy_kind}`, + record.memory_id && `Memory ${record.memory_id}`, + record.skill_id && `Skill ${record.skill_id}`, + record.tool_name && `Tool ${record.tool_name}`, ].filter((item): item is string => Boolean(item)); return ( @@ -178,6 +183,12 @@ function actionTarget(record: DigitalEmployeeAdminWorkbenchRow, view: DigitalEmp entityId: record.policy_key || record.policy_kind, }; } + if (view === 'memory_governance') { + return { entityType: 'memory', entityId: record.memory_id || record.entity_id }; + } + if (view === 'skill_audits') { + return { entityType: 'skill', entityId: record.skill_id || record.tool_name || record.entity_id }; + } return { entityType: record.entity_type, entityId: record.entity_id }; } @@ -252,6 +263,19 @@ function allowedActions(record: DigitalEmployeeAdminWorkbenchRow, view: DigitalE { action: 'dismiss', label: '忽略' }, ]; } + if (view === 'memory_governance') { + return [ + { action: 'archive_memory', label: '批量归档' }, + { action: 'restore_memory', label: '批量恢复' }, + { action: 'mark_reviewed', label: '标记复核' }, + ]; + } + if (view === 'skill_audits') { + return [ + { action: 'mark_reviewed', label: '标记复核' }, + { action: 'dismiss', label: '忽略' }, + ]; + } return []; } @@ -715,6 +739,11 @@ const DigitalEmployeeOps: React.FC = () => { detailRecord.route_decision_id && `Route ${detailRecord.route_decision_id}`, detailRecord.policy_kind && `Policy ${detailRecord.policy_kind}`, + detailRecord.memory_id && `Memory ${detailRecord.memory_id}`, + detailRecord.memory_key && `MemoryKey ${detailRecord.memory_key}`, + detailRecord.skill_id && `Skill ${detailRecord.skill_id}`, + detailRecord.tool_name && `Tool ${detailRecord.tool_name}`, + detailRecord.server_id && `Server ${detailRecord.server_id}`, ] .filter(Boolean) .join(' / ') || '--'} @@ -770,6 +799,28 @@ const DigitalEmployeeOps: React.FC = () => { )} + {activeView === 'memory_governance' && ( + + 记忆治理: + {detailRecord.memory_key || detailRecord.memory_id || detailRecord.entity_id || '--'} + {detailRecord.status && ` · ${detailRecord.status}`} + + {' '} + · 展示重复候选与治理状态,批量归档/批量恢复只记录低风险意图。 + + + )} + {activeView === 'skill_audits' && ( + + 技能审计: + {detailRecord.skill_id || detailRecord.tool_name || detailRecord.category || '--'} + {detailRecord.status && ` · ${detailRecord.status}`} + + {' '} + · 只消费调用审计和安全摘要,不安装技能、不变更权限范围。 + + + )} {allowedActions(detailRecord, activeView).length > 0 && (
低风险动作 diff --git a/qiming/src/types/interfaces/systemManage.ts b/qiming/src/types/interfaces/systemManage.ts index 00990db5..66ba6a0a 100644 --- a/qiming/src/types/interfaces/systemManage.ts +++ b/qiming/src/types/interfaces/systemManage.ts @@ -567,7 +567,9 @@ export type DigitalEmployeeAdminWorkbenchView = | 'daily_reports' | 'governance_commands' | 'route_governance' - | 'policy_conflicts'; + | 'policy_conflicts' + | 'memory_governance' + | 'skill_audits'; export interface DigitalEmployeeAdminWorkbenchRow { view: DigitalEmployeeAdminWorkbenchView | string; @@ -592,6 +594,11 @@ export interface DigitalEmployeeAdminWorkbenchRow { report_id?: string; route_decision_id?: string; service_id?: string; + memory_id?: string; + memory_key?: string; + skill_id?: string; + tool_name?: string; + server_id?: string; policy_kind?: string; policy_key?: string; conflict_keys?: string[]; diff --git a/qimingclaw/crates/agent-electron-client/scripts/check-digital-employee.js b/qimingclaw/crates/agent-electron-client/scripts/check-digital-employee.js index 9a3ffcdc..d059b5f0 100644 --- a/qimingclaw/crates/agent-electron-client/scripts/check-digital-employee.js +++ b/qimingclaw/crates/agent-electron-client/scripts/check-digital-employee.js @@ -1449,6 +1449,102 @@ function checkDigitalAdminApprovalGovernanceWorkbench() { console.log("[DigitalEmployeeCheck] admin approval governance workbench hooks OK"); } +function checkDigitalAdminMemorySkillWorkbench() { + console.log("\n[DigitalEmployeeCheck] Verify admin memory governance and skill audit workbench hooks"); + const backendRoot = path.resolve(packageRoot, "..", "..", "..", "qiming-backend"); + const qimingRoot = path.resolve(packageRoot, "..", "..", "..", "qiming"); + const servicePath = path.join( + backendRoot, + "app-platform-modules", + "app-platform-agent", + "app-platform-agent-core-application", + "src", + "main", + "java", + "com", + "xspaceagi", + "agent", + "core", + "application", + "service", + "DigitalEmployeeSyncApplicationServiceImpl.java", + ); + const dtoPath = path.join( + backendRoot, + "app-platform-modules", + "app-platform-agent", + "app-platform-agent-core-adapter", + "src", + "main", + "java", + "com", + "xspaceagi", + "agent", + "core", + "adapter", + "dto", + "digital", + "DigitalEmployeeAdminWorkbenchRowDto.java", + ); + const testPath = path.join( + backendRoot, + "app-platform-modules", + "app-platform-agent", + "app-platform-agent-core-application", + "src", + "test", + "java", + "com", + "xspaceagi", + "agent", + "core", + "application", + "service", + "DigitalEmployeeSyncApplicationServiceImplTest.java", + ); + const qimingOpsPath = path.join(qimingRoot, "src", "pages", "SystemManagement", "Content", "DigitalEmployeeOps", "index.tsx"); + const qimingTypesPath = path.join(qimingRoot, "src", "types", "interfaces", "systemManage.ts"); + const docsPath = path.resolve(packageRoot, "..", "..", "docs", "digital-employee-frontend-integration-plan.md"); + const service = fs.readFileSync(servicePath, "utf8"); + const dto = fs.readFileSync(dtoPath, "utf8"); + const test = fs.readFileSync(testPath, "utf8"); + const qimingOps = fs.readFileSync(qimingOpsPath, "utf8"); + const qimingTypes = fs.readFileSync(qimingTypesPath, "utf8"); + const docs = fs.readFileSync(docsPath, "utf8"); + const requiredSnippets = [ + [service, "memory_governance", "backend memory governance view"], + [service, "skill_audits", "backend skill audits view"], + [service, "isMemoryGovernanceRow", "backend memory governance matcher"], + [service, "isSkillAuditRow", "backend skill audit matcher"], + [service, "skill_call_", "backend skill call classifier"], + [service, "tool_call_audit", "backend tool audit classifier"], + [service, "token_cost", "backend token cost classifier"], + [dto, "memory_id", "backend memory id row field"], + [dto, "memory_key", "backend memory key row field"], + [dto, "skill_id", "backend skill id row field"], + [dto, "tool_name", "backend tool name row field"], + [dto, "server_id", "backend server id row field"], + [test, "queryAdminWorkbenchMemoryGovernanceReturnsMemoryRowsOnly", "backend memory governance test"], + [test, "queryAdminWorkbenchSkillAuditsReturnsSkillAndToolAuditRowsOnly", "backend skill audits test"], + [qimingTypes, "memory_governance", "qiming memory governance view type"], + [qimingTypes, "skill_audits", "qiming skill audits view type"], + [qimingOps, "记忆治理", "qiming memory governance tab"], + [qimingOps, "技能审计", "qiming skill audits tab"], + [qimingOps, "批量归档", "qiming memory archive action"], + [qimingOps, "批量恢复", "qiming memory restore action"], + [qimingOps, "重复候选", "qiming memory duplicate copy"], + [docs, "正式管理端记忆治理预览", "docs memory governance absorption"], + [docs, "正式管理端技能审计预览", "docs skill audit absorption"], + ]; + const missing = requiredSnippets + .filter(([content, snippet]) => !content.includes(snippet)) + .map(([, , label]) => label); + if (missing.length > 0) { + throw new Error(`Missing admin memory governance and skill audit hooks: ${missing.join(", ")}`); + } + console.log("[DigitalEmployeeCheck] admin memory governance and skill audit hooks OK"); +} + function checkDigitalAdminServiceOperationsWorkbench() { console.log("\n[DigitalEmployeeCheck] Verify admin service operations workbench hooks"); const backendRoot = path.resolve(packageRoot, "..", "..", "..", "qiming-backend"); @@ -2053,6 +2149,7 @@ function main() { checkDigitalBackendMaterializedFacts(); checkDigitalAdminRecordBillingArchiveShell(); checkDigitalAdminApprovalGovernanceWorkbench(); + checkDigitalAdminMemorySkillWorkbench(); checkDigitalAdminServiceOperationsWorkbench(); checkDigitalAdminRouteGovernanceWorkbench(); checkDigitalAdminNotificationWorkbench(); diff --git a/qimingclaw/docs/digital-employee-frontend-integration-plan.md b/qimingclaw/docs/digital-employee-frontend-integration-plan.md index b486f36e..270d91ba 100644 --- a/qimingclaw/docs/digital-employee-frontend-integration-plan.md +++ b/qimingclaw/docs/digital-employee-frontend-integration-plan.md @@ -562,8 +562,8 @@ GET /api/digital-employee/approvals 3. **MemoryEntry / 长期记忆(已开始)** - 已吸收:qimingclaw `memoryService` 已投射为数字员工本地 `digital_memories`,支持从主客户端记忆列表同步、桥接新增和软删除;本地兜底记忆仍可在 memory 服务未初始化时工作。 - - 当前能力:embedded `/api/memory` 已由 qimingclaw adapter 接管,GET / POST / DELETE 会通过 preload bridge 读写 `digital_memories`;记忆新增、更新和删除会进入 `digital_sync_outbox`,entity type 为 `memory`,同步业务视图会提炼 key、category、source、status 和 content preview。记忆归档/恢复 UI、管理端长期记忆业务视图和记忆治理审计链已开始吸收,embedded 新增“记忆治理”入口,可从 runtime memories 派生 key/category/source/status/content preview、重复候选和同步状态,并通过本地 overlay/audit 批量归档、恢复或标记复核。 - - 后续缺口:真实合并执行、远端记忆策略下发和外部管理端长期记忆工作台仍待吸收。 + - 当前能力:embedded `/api/memory` 已由 qimingclaw adapter 接管,GET / POST / DELETE 会通过 preload bridge 读写 `digital_memories`;记忆新增、更新和删除会进入 `digital_sync_outbox`,entity type 为 `memory`,同步业务视图会提炼 key、category、source、status 和 content preview。记忆归档/恢复 UI、管理端长期记忆业务视图和记忆治理审计链已开始吸收,embedded 新增“记忆治理”入口,可从 runtime memories 派生 key/category/source/status/content preview、重复候选和同步状态,并通过本地 overlay/audit 批量归档、恢复或标记复核。正式管理端记忆治理预览已开始吸收,qiming-backend 新增 `memory_governance` 运营台视图聚合 memory fact、归档/恢复/复核事件和重复候选摘要,qiming“数字员工运营台”的“记忆治理”tab 可记录批量归档、批量恢复和标记复核等低风险动作意图。 + - 后续缺口:真实合并执行、远端记忆策略下发和更完整的外部管理端长期记忆策略工作台仍待吸收。 - 建议:下一轮围绕管理端视图和记忆治理,把长期记忆从本地事实继续扩展到人工可审计、可归档、可合并的运营闭环。 4. **治理命令完整生命周期** @@ -578,8 +578,8 @@ GET /api/digital-employee/approvals - 建议:先沉淀安全重启审计和人工确认体验,再评估更细粒度的服务恢复策略。 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 跳转聚焦。 - - 缺口:仍缺少技能安装、版本管理、细粒度权限范围和正式外部管理端调用审计工作台。 + - 已吸收: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 只开放标记复核和忽略意图,不安装技能、不变更权限范围。 + - 缺口:仍缺少技能安装、版本管理和细粒度权限范围。 - 建议:下一轮围绕管理端审计消费、技能安装和权限范围,把本地调用审计推进到更完整的跨端治理闭环。 7. **产物交付闭环**