吸收数字员工管理端记忆策略预览

This commit is contained in:
baiyanyun
2026-06-15 10:40:32 +08:00
parent e57971488c
commit 6ad88cb6c4
7 changed files with 144 additions and 2 deletions

View File

@@ -155,6 +155,16 @@ public class DigitalEmployeeAdminWorkbenchRowDto {
private String lastGovernanceAction;
@JsonProperty("last_reviewed_at")
private String lastReviewedAt;
@JsonProperty("memory_policy_id")
private String memoryPolicyId;
@JsonProperty("memory_policy_status")
private String memoryPolicyStatus;
@JsonProperty("merge_policy_id")
private String mergePolicyId;
@JsonProperty("merge_action_disabled")
private Boolean mergeActionDisabled;
@JsonProperty("remote_memory_policy_disabled")
private Boolean remoteMemoryPolicyDisabled;
@JsonProperty("skill_id")
private String skillId;
@JsonProperty("skill_version")

View File

@@ -73,6 +73,7 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye
"artifact:mark_keep", "artifact:mark_expire", "artifact:mark_reviewed", "artifact:record_cleanup_request",
"daily_report:mark_delivered", "daily_report:mark_confirmed", "daily_report:request_approval", "daily_report:record_approval_decision",
"memory:archive_memory", "memory:restore_memory", "memory:mark_reviewed",
"memory_policy:pull_policy", "memory_policy:mark_reviewed", "memory_policy:dismiss",
"skill:mark_reviewed", "skill:dismiss",
"policy_snapshot:pull_policy", "policy_snapshot:mark_reviewed", "policy_snapshot:dismiss",
"scheduler:retry_scheduler_check", "scheduler:mark_reviewed", "scheduler:dismiss",
@@ -1146,6 +1147,11 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye
.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")))
.memoryPolicyId(firstString(businessView.get("memory_policy_id"), businessView.get("memoryPolicyId"), payload.get("memory_policy_id"), payload.get("memoryPolicyId")))
.memoryPolicyStatus(firstString(businessView.get("memory_policy_status"), businessView.get("memoryPolicyStatus"), payload.get("memory_policy_status"), payload.get("memoryPolicyStatus")))
.mergePolicyId(firstString(businessView.get("merge_policy_id"), businessView.get("mergePolicyId"), payload.get("merge_policy_id"), payload.get("mergePolicyId")))
.mergeActionDisabled(nullableBooleanValue(firstPresent(businessView.get("merge_action_disabled"), businessView.get("mergeActionDisabled"), payload.get("merge_action_disabled"), payload.get("mergeActionDisabled"))))
.remoteMemoryPolicyDisabled(nullableBooleanValue(firstPresent(businessView.get("remote_memory_policy_disabled"), businessView.get("remoteMemoryPolicyDisabled"), payload.get("remote_memory_policy_disabled"), payload.get("remoteMemoryPolicyDisabled"))))
.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")))
@@ -1206,6 +1212,7 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye
case "governance_commands" -> isGovernanceCommandRow(row);
case "route_governance" -> isRouteGovernanceRow(row);
case "memory_governance" -> isMemoryGovernanceRow(row);
case "memory_policies" -> isMemoryPolicyRow(row);
case "skill_audits" -> isSkillAuditRow(row);
case "policy_center" -> isPolicyCenterRow(row);
case "scheduler_operations" -> isSchedulerOperationsRow(row);
@@ -1364,6 +1371,20 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye
|| row.getMergeCandidateCount() != null;
}
private boolean isMemoryPolicyRow(DigitalEmployeeAdminWorkbenchRowDto row) {
String category = firstString(row.getCategory());
String kind = firstString(row.getKind());
return Set.of("memory_policy", "memory_merge_policy", "memory_retention_policy").contains(category)
|| contains(kind, "memory_policy")
|| contains(kind, "memory_merge_policy")
|| contains(kind, "memory_retention_policy")
|| StringUtils.isNotBlank(row.getMemoryPolicyId())
|| StringUtils.isNotBlank(row.getMemoryPolicyStatus())
|| StringUtils.isNotBlank(row.getMergePolicyId())
|| row.getMergeActionDisabled() != null
|| row.getRemoteMemoryPolicyDisabled() != null;
}
private boolean isSkillAuditRow(DigitalEmployeeAdminWorkbenchRowDto row) {
String category = firstString(row.getCategory());
String kind = firstString(row.getKind());

View File

@@ -506,6 +506,60 @@ class DigitalEmployeeSyncApplicationServiceImplTest {
assertThat(result.getRecords().get(1).getLastGovernanceAction()).isEqualTo("archive");
}
@Test
void queryAdminWorkbenchMemoryPoliciesReturnsPolicyRowsOnly() {
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-policy", "event", "event-memory-policy-1", 101L, new Date(10_000),
Map.ofEntries(
Map.entry("entity_type", "event"),
Map.entry("category", "memory_policy"),
Map.entry("kind", "memory_policy_snapshot"),
Map.entry("status", "synced"),
Map.entry("title", "记忆策略快照"),
Map.entry("memory_policy_id", "memory-policy-1"),
Map.entry("memory_policy_status", "remote_disabled"),
Map.entry("merge_policy_id", "merge-policy-1"),
Map.entry("merge_action_disabled", true),
Map.entry("remote_memory_policy_disabled", true),
Map.entry("policy_source", "remote")
),
Map.of("id", "event-memory-policy-1", "kind", "memory_policy_snapshot")),
record("outbox-memory-merge-policy", "event", "event-memory-policy-2", 101L, new Date(11_000),
Map.of(
"entity_type", "event",
"category", "memory_merge_policy",
"kind", "memory_merge_policy_pulled",
"status", "synced",
"title", "合并策略已拉取",
"merge_policy_id", "merge-policy-2",
"merge_action_disabled", true
),
Map.of("id", "event-memory-policy-2", "kind", "memory_merge_policy_pulled")),
record("outbox-memory", "memory", "memory-1", 101L, new Date(12_000),
Map.of("entity_type", "memory", "title", "普通记忆", "status", "active", "memory_id", "memory-1"),
Map.of("id", "memory-1", "status", "active")),
record("outbox-skill", "event", "event-skill-1", 101L, new Date(13_000),
Map.of("entity_type", "event", "category", "skill", "title", "技能", "status", "allowed"),
Map.of("id", "event-skill-1", "status", "allowed"))
)));
DigitalEmployeeAdminWorkbenchPageDto result = service.queryAdminWorkbench(
"memory_policies", 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-memory-policy-1", "event-memory-policy-2");
assertThat(result.getRecords().get(0).getView()).isEqualTo("memory_policies");
assertThat(result.getRecords().get(0).getMemoryPolicyId()).isEqualTo("memory-policy-1");
assertThat(result.getRecords().get(0).getMemoryPolicyStatus()).isEqualTo("remote_disabled");
assertThat(result.getRecords().get(0).getMergePolicyId()).isEqualTo("merge-policy-1");
assertThat(result.getRecords().get(0).getMergeActionDisabled()).isTrue();
assertThat(result.getRecords().get(0).getRemoteMemoryPolicyDisabled()).isTrue();
assertThat(result.getRecords().get(1).getMergePolicyId()).isEqualTo("merge-policy-2");
}
@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)))

View File

@@ -50,6 +50,7 @@ const WORKBENCH_TABS: Array<{
{ key: 'route_governance', label: '路由治理' },
{ key: 'policy_conflicts', label: '策略冲突' },
{ key: 'memory_governance', label: '记忆治理' },
{ key: 'memory_policies', label: '记忆策略' },
{ key: 'skill_audits', label: '技能审计' },
{ key: 'policy_center', label: '策略中心' },
{ key: 'scheduler_operations', label: '调度运营' },
@@ -139,6 +140,9 @@ function renderSummary(record: DigitalEmployeeAdminWorkbenchRow) {
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.memory_policy_id && `MemoryPolicy ${record.memory_policy_id}`,
record.memory_policy_status && `MemoryPolicyStatus ${record.memory_policy_status}`,
record.merge_policy_id && `MergePolicy ${record.merge_policy_id}`,
record.skill_id && `Skill ${record.skill_id}`,
record.skill_version && `Version ${record.skill_version}`,
record.install_status && `Install ${record.install_status}`,
@@ -248,6 +252,9 @@ function actionTarget(record: DigitalEmployeeAdminWorkbenchRow, view: DigitalEmp
if (view === 'memory_governance') {
return { entityType: 'memory', entityId: record.memory_id || record.entity_id };
}
if (view === 'memory_policies') {
return { entityType: 'memory_policy', entityId: record.memory_policy_id || record.merge_policy_id || record.entity_id };
}
if (view === 'skill_audits') {
return { entityType: 'skill', entityId: record.skill_id || record.tool_name || record.entity_id };
}
@@ -371,6 +378,13 @@ function allowedActions(record: DigitalEmployeeAdminWorkbenchRow, view: DigitalE
{ action: 'mark_reviewed', label: '标记复核' },
];
}
if (view === 'memory_policies') {
return [
{ action: 'pull_policy', label: '拉取记忆策略' },
{ action: 'mark_reviewed', label: '标记复核' },
{ action: 'dismiss', label: '忽略' },
];
}
if (view === 'skill_audits') {
return [
{ action: 'mark_reviewed', label: '标记复核' },
@@ -1178,6 +1192,22 @@ const DigitalEmployeeOps: React.FC = () => {
</Typography.Text>
</Typography.Paragraph>
)}
{activeView === 'memory_policies' && (
<Typography.Paragraph>
<Typography.Text strong></Typography.Text>
{detailRecord.memory_policy_id || detailRecord.merge_policy_id || detailRecord.entity_id || '--'}
{detailRecord.status && ` · ${detailRecord.status}`}
{detailRecord.memory_policy_status && ` · 策略状态 ${detailRecord.memory_policy_status}`}
{detailRecord.memory_policy_id && ` · 记忆策略 ${detailRecord.memory_policy_id}`}
{detailRecord.merge_policy_id && ` · 合并策略 ${detailRecord.merge_policy_id}`}
{detailRecord.remote_memory_policy_disabled && ' · 远端记忆策略禁用'}
{detailRecord.merge_action_disabled && ' · 自动合并禁用'}
<Typography.Text type="secondary">
{' '}
·
</Typography.Text>
</Typography.Paragraph>
)}
{activeView === 'skill_audits' && (
<Typography.Paragraph>
<Typography.Text strong></Typography.Text>

View File

@@ -571,6 +571,7 @@ export type DigitalEmployeeAdminWorkbenchView =
| 'route_governance'
| 'policy_conflicts'
| 'memory_governance'
| 'memory_policies'
| 'skill_audits'
| 'policy_center'
| 'scheduler_operations'
@@ -660,6 +661,11 @@ export interface DigitalEmployeeAdminWorkbenchRow {
latest_memory_id?: string;
last_governance_action?: string;
last_reviewed_at?: string;
memory_policy_id?: string;
memory_policy_status?: string;
merge_policy_id?: string;
merge_action_disabled?: boolean;
remote_memory_policy_disabled?: boolean;
skill_id?: string;
skill_version?: string;
install_status?: string;
@@ -749,6 +755,11 @@ export interface DigitalEmployeePolicySnapshotRow {
missing_context_count?: number;
not_mapped_count?: number;
intervention_conflict_status?: string;
memory_policy_id?: string;
memory_policy_status?: string;
merge_policy_id?: string;
merge_action_disabled?: boolean;
remote_memory_policy_disabled?: boolean;
approval_policy_id?: string;
conflict_policy_id?: string;
decision_guard_status?: string;

View File

@@ -1675,8 +1675,10 @@ function checkDigitalAdminMemorySkillWorkbench() {
const docs = fs.readFileSync(docsPath, "utf8");
const requiredSnippets = [
[service, "memory_governance", "backend memory governance view"],
[service, "memory_policies", "backend memory policies view"],
[service, "skill_audits", "backend skill audits view"],
[service, "isMemoryGovernanceRow", "backend memory governance matcher"],
[service, "isMemoryPolicyRow", "backend memory policies matcher"],
[service, "isSkillAuditRow", "backend skill audit matcher"],
[service, "skill_call_", "backend skill call classifier"],
[service, "tool_call_audit", "backend tool audit classifier"],
@@ -1686,6 +1688,11 @@ function checkDigitalAdminMemorySkillWorkbench() {
[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, "memory_policy_id", "backend memory policy id row field"],
[dto, "memory_policy_status", "backend memory policy status row field"],
[dto, "merge_policy_id", "backend merge policy id row field"],
[dto, "merge_action_disabled", "backend merge action disabled row field"],
[dto, "remote_memory_policy_disabled", "backend remote memory policy disabled 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"],
@@ -1695,15 +1702,21 @@ function checkDigitalAdminMemorySkillWorkbench() {
[dto, "server_id", "backend server id row field"],
[test, "queryAdminWorkbenchMemoryGovernanceReturnsMemoryRowsOnly", "backend memory governance test"],
[test, "getMergeCandidateCount", "backend memory merge candidate assertion"],
[test, "queryAdminWorkbenchMemoryPoliciesReturnsPolicyRowsOnly", "backend memory policies test"],
[test, "getMemoryPolicyId", "backend memory policy 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, "memory_policies", "qiming memory policies view type"],
[qimingTypes, "memory_policy_id", "qiming memory policy id field"],
[qimingTypes, "remote_memory_policy_disabled", "qiming remote memory policy disabled field"],
[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 policies tab"],
[qimingOps, "技能审计", "qiming skill audits tab"],
[qimingOps, "权限范围", "qiming skill permission scope copy"],
[qimingOps, "策略来源", "qiming skill policy source copy"],
@@ -1711,8 +1724,11 @@ function checkDigitalAdminMemorySkillWorkbench() {
[qimingOps, "批量恢复", "qiming memory restore action"],
[qimingOps, "重复候选", "qiming memory duplicate copy"],
[qimingOps, "不自动合并记忆", "qiming memory merge safety copy"],
[qimingOps, "远端记忆策略", "qiming memory policy copy"],
[qimingOps, "不触发自动合并", "qiming memory policy merge safety copy"],
[docs, "正式管理端记忆治理预览", "docs memory governance absorption"],
[docs, "正式管理端记忆合并候选预览", "docs memory merge candidate absorption"],
[docs, "远端记忆策略预览", "docs memory policy absorption"],
[docs, "正式管理端技能审计预览", "docs skill audit absorption"],
[docs, "正式管理端技能版本权限预览", "docs skill version permission absorption"],
];

View File

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