吸收数字员工管理端记忆合并候选预览

This commit is contained in:
baiyanyun
2026-06-13 16:36:54 +08:00
parent 4fdc95da75
commit 797ffeee82
7 changed files with 72 additions and 4 deletions

View File

@@ -5,6 +5,7 @@ import lombok.Builder;
import lombok.Data;
import java.util.Date;
import java.util.List;
import java.util.Map;
@Data
@@ -100,6 +101,18 @@ public class DigitalEmployeeAdminWorkbenchRowDto {
private String memoryId;
@JsonProperty("memory_key")
private String memoryKey;
@JsonProperty("governance_status")
private String governanceStatus;
@JsonProperty("merge_candidate_count")
private Long mergeCandidateCount;
@JsonProperty("duplicate_memory_ids")
private List<String> duplicateMemoryIds;
@JsonProperty("latest_memory_id")
private String latestMemoryId;
@JsonProperty("last_governance_action")
private String lastGovernanceAction;
@JsonProperty("last_reviewed_at")
private String lastReviewedAt;
@JsonProperty("skill_id")
private String skillId;
@JsonProperty("skill_version")

View File

@@ -46,6 +46,7 @@ import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Date;
import java.util.LinkedHashMap;
@@ -1092,6 +1093,12 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye
.autoRecoveryStatus(firstString(businessView.get("auto_recovery_status"), businessView.get("autoRecoveryStatus"), payload.get("auto_recovery_status"), payload.get("autoRecoveryStatus")))
.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")))
.governanceStatus(firstString(businessView.get("governance_status"), businessView.get("governanceStatus"), payload.get("governance_status"), payload.get("governanceStatus")))
.mergeCandidateCount(numberValue(firstPresent(businessView.get("merge_candidate_count"), businessView.get("mergeCandidateCount"), payload.get("merge_candidate_count"), payload.get("mergeCandidateCount"))))
.duplicateMemoryIds(stringListValue(firstPresent(businessView.get("duplicate_memory_ids"), businessView.get("duplicateMemoryIds"), payload.get("duplicate_memory_ids"), payload.get("duplicateMemoryIds"))))
.latestMemoryId(firstString(businessView.get("latest_memory_id"), businessView.get("latestMemoryId"), payload.get("latest_memory_id"), payload.get("latestMemoryId")))
.lastGovernanceAction(firstString(businessView.get("last_governance_action"), businessView.get("lastGovernanceAction"), payload.get("last_governance_action"), payload.get("lastGovernanceAction")))
.lastReviewedAt(firstString(businessView.get("last_reviewed_at"), businessView.get("lastReviewedAt"), payload.get("last_reviewed_at"), payload.get("lastReviewedAt")))
.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")))
@@ -1233,7 +1240,10 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye
|| StringUtils.equals(category, "memory_governance")
|| startsWithAny(kind, "memory_")
|| StringUtils.isNotBlank(row.getMemoryId())
|| StringUtils.isNotBlank(row.getMemoryKey());
|| StringUtils.isNotBlank(row.getMemoryKey())
|| StringUtils.isNotBlank(row.getLatestMemoryId())
|| StringUtils.isNotBlank(row.getGovernanceStatus())
|| row.getMergeCandidateCount() != null;
}
private boolean isSkillAuditRow(DigitalEmployeeAdminWorkbenchRowDto row) {
@@ -1607,6 +1617,21 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye
return null;
}
private List<String> stringListValue(Object value) {
if (value instanceof List<?> list) {
return list.stream()
.map(item -> stringValue(item, null))
.filter(StringUtils::isNotBlank)
.toList();
}
String text = stringValue(value, null);
if (StringUtils.isBlank(text)) return List.of();
return Arrays.stream(text.split(","))
.map(String::trim)
.filter(StringUtils::isNotBlank)
.toList();
}
private String stringValue(Object value, String fallback) {
if (value == null) return fallback;
String text = Objects.toString(value, "").trim();

View File

@@ -395,8 +395,11 @@ class DigitalEmployeeSyncApplicationServiceImplTest {
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(0).getGovernanceStatus()).isEqualTo("active");
assertThat(result.getRecords().get(0).getMergeCandidateCount()).isEqualTo(2L);
assertThat(result.getRecords().get(0).getDuplicateMemoryIds()).containsExactly("memory-2");
assertThat(result.getRecords().get(1).getCategory()).isEqualTo("memory_governance");
assertThat(result.getRecords().get(1).getLastGovernanceAction()).isEqualTo("archive");
}
@Test

View File

@@ -127,6 +127,8 @@ function renderSummary(record: DigitalEmployeeAdminWorkbenchRow) {
record.subscription_policy_id && `Subscription ${record.subscription_policy_id}`,
record.daily_report_fact_id && `ReportFact ${record.daily_report_fact_id}`,
record.memory_id && `Memory ${record.memory_id}`,
record.merge_candidate_count != null && `Merge ${record.merge_candidate_count}`,
record.governance_status && `Governance ${record.governance_status}`,
record.skill_id && `Skill ${record.skill_id}`,
record.skill_version && `Version ${record.skill_version}`,
record.install_status && `Install ${record.install_status}`,
@@ -838,6 +840,8 @@ const DigitalEmployeeOps: React.FC = () => {
detailRecord.policy_kind && `Policy ${detailRecord.policy_kind}`,
detailRecord.memory_id && `Memory ${detailRecord.memory_id}`,
detailRecord.memory_key && `MemoryKey ${detailRecord.memory_key}`,
detailRecord.latest_memory_id &&
`LatestMemory ${detailRecord.latest_memory_id}`,
detailRecord.skill_id && `Skill ${detailRecord.skill_id}`,
detailRecord.skill_version && `Version ${detailRecord.skill_version}`,
detailRecord.policy_source && `PolicySource ${detailRecord.policy_source}`,
@@ -972,9 +976,18 @@ const DigitalEmployeeOps: React.FC = () => {
<Typography.Text strong></Typography.Text>
{detailRecord.memory_key || detailRecord.memory_id || detailRecord.entity_id || '--'}
{detailRecord.status && ` · ${detailRecord.status}`}
{detailRecord.governance_status && ` · 治理状态 ${detailRecord.governance_status}`}
{detailRecord.merge_candidate_count != null &&
` · 合并候选 ${detailRecord.merge_candidate_count}`}
{detailRecord.duplicate_memory_ids?.length
? ` · 重复记忆 ${detailRecord.duplicate_memory_ids.join('/')}`
: ''}
{detailRecord.last_governance_action &&
` · 最近治理 ${detailRecord.last_governance_action}`}
{detailRecord.last_reviewed_at && ` · 复核 ${detailRecord.last_reviewed_at}`}
<Typography.Text type="secondary">
{' '}
· /
· /
</Typography.Text>
</Typography.Paragraph>
)}

View File

@@ -627,6 +627,12 @@ export interface DigitalEmployeeAdminWorkbenchRow {
auto_recovery_status?: string;
memory_id?: string;
memory_key?: string;
governance_status?: string;
merge_candidate_count?: number;
duplicate_memory_ids?: string[];
latest_memory_id?: string;
last_governance_action?: string;
last_reviewed_at?: string;
skill_id?: string;
skill_version?: string;
install_status?: string;

View File

@@ -1584,6 +1584,9 @@ function checkDigitalAdminMemorySkillWorkbench() {
[service, "token_cost", "backend token cost classifier"],
[dto, "memory_id", "backend memory id row field"],
[dto, "memory_key", "backend memory key row field"],
[dto, "governance_status", "backend memory governance status row field"],
[dto, "merge_candidate_count", "backend memory merge candidate count row field"],
[dto, "duplicate_memory_ids", "backend duplicate memory ids 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"],
@@ -1592,9 +1595,12 @@ function checkDigitalAdminMemorySkillWorkbench() {
[dto, "tool_name", "backend tool name row field"],
[dto, "server_id", "backend server id row field"],
[test, "queryAdminWorkbenchMemoryGovernanceReturnsMemoryRowsOnly", "backend memory governance test"],
[test, "getMergeCandidateCount", "backend memory merge candidate assertion"],
[test, "queryAdminWorkbenchSkillAuditsReturnsSkillAndToolAuditRowsOnly", "backend skill audits test"],
[test, "queryAdminWorkbenchSkillAuditsReturnsVersionPermissionAndPolicyRowsOnly", "backend skill version permission test"],
[qimingTypes, "memory_governance", "qiming memory governance view type"],
[qimingTypes, "merge_candidate_count", "qiming memory merge candidate count field"],
[qimingTypes, "duplicate_memory_ids", "qiming duplicate memory ids field"],
[qimingTypes, "skill_audits", "qiming skill audits view type"],
[qimingTypes, "skill_version", "qiming skill version field"],
[qimingTypes, "permission_scope", "qiming skill permission scope field"],
@@ -1605,7 +1611,9 @@ function checkDigitalAdminMemorySkillWorkbench() {
[qimingOps, "批量归档", "qiming memory archive action"],
[qimingOps, "批量恢复", "qiming memory restore action"],
[qimingOps, "重复候选", "qiming memory duplicate copy"],
[qimingOps, "不自动合并记忆", "qiming memory merge safety copy"],
[docs, "正式管理端记忆治理预览", "docs memory governance absorption"],
[docs, "正式管理端记忆合并候选预览", "docs memory merge candidate absorption"],
[docs, "正式管理端技能审计预览", "docs skill audit absorption"],
[docs, "正式管理端技能版本权限预览", "docs skill version permission absorption"],
];

View File

@@ -562,7 +562,7 @@ 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 批量归档、恢复或标记复核。正式管理端记忆治理预览已开始吸收qiming-backend 新增 `memory_governance` 运营台视图聚合 memory fact、归档/恢复/复核事件和重复候选摘要qiming“数字员工运营台”的“记忆治理”tab 可记录批量归档、批量恢复和标记复核等低风险动作意图。
- 当前能力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 可记录批量归档、批量恢复和标记复核等低风险动作意图。正式管理端记忆合并候选预览已开始吸收,运营台详情可展示治理状态、合并候选数、重复记忆 ID、最近治理动作和复核时间但不自动合并记忆。
- 后续缺口:真实合并执行、远端记忆策略下发和更完整的外部管理端长期记忆策略工作台仍待吸收。
- 建议:下一轮围绕管理端视图和记忆治理,把长期记忆从本地事实继续扩展到人工可审计、可归档、可合并的运营闭环。