吸收数字员工管理端诊断修复预览

This commit is contained in:
baiyanyun
2026-06-15 09:14:32 +08:00
parent cc55fd819f
commit a62b749ef2
7 changed files with 325 additions and 6 deletions

View File

@@ -164,6 +164,22 @@ public class DigitalEmployeeAdminWorkbenchRowDto {
@JsonProperty("billing_source")
private String billingSource;
private Boolean estimated;
@JsonProperty("diagnostic_id")
private String diagnosticId;
@JsonProperty("diagnostic_kind")
private String diagnosticKind;
@JsonProperty("issue_code")
private String issueCode;
@JsonProperty("repair_action")
private String repairAction;
@JsonProperty("repair_status")
private String repairStatus;
@JsonProperty("repair_reason")
private String repairReason;
@JsonProperty("safe_repair_disabled")
private Boolean safeRepairDisabled;
@JsonProperty("recommended_action")
private String recommendedAction;
@JsonProperty("policy_kind")
private String policyKind;
@JsonProperty("policy_key")

View File

@@ -78,6 +78,7 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye
"plan_step:mark_reviewed", "plan_step:dismiss", "plan_step:retry_policy_check",
"plan:mark_reviewed", "plan:dismiss", "plan:request_revision_review", "plan:record_escalation",
"revision:mark_reviewed", "revision:dismiss", "revision:request_revision_review",
"diagnostic:mark_reviewed", "diagnostic:dismiss", "diagnostic:retry_check", "diagnostic:record_repair_intent",
"audit:mark_reviewed", "audit:dismiss",
"event:mark_reviewed", "event:dismiss"
);
@@ -1023,6 +1024,11 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye
case "run_schedule_now" -> List.of("run_schedule_now_disabled");
case "start_scheduler" -> List.of("start_scheduler_disabled");
case "hard_interrupt" -> List.of("hard_interrupt_disabled");
case "run_shell_repair" -> List.of("run_shell_repair_disabled");
case "modify_system_config" -> List.of("modify_system_config_disabled");
case "restart_unlisted_service" -> List.of("restart_unlisted_service_disabled");
case "delete_file_repair" -> List.of("delete_file_repair_disabled");
case "remote_policy_edit" -> List.of("remote_policy_edit_disabled");
case "activate_plan", "cancel_run", "cancel_task", "accept_remote_approval_batch" -> List.of("high_risk_governance_action_disabled");
default -> List.of();
};
@@ -1144,6 +1150,14 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye
.billingEstimateId(firstString(businessView.get("billing_estimate_id"), businessView.get("billingEstimateId"), payload.get("billing_estimate_id"), payload.get("billingEstimateId")))
.billingSource(firstString(businessView.get("billing_source"), businessView.get("billingSource"), payload.get("billing_source"), payload.get("billingSource")))
.estimated(nullableBooleanValue(firstPresent(businessView.get("estimated"), payload.get("estimated"))))
.diagnosticId(firstString(businessView.get("diagnostic_id"), businessView.get("diagnosticId"), payload.get("diagnostic_id"), payload.get("diagnosticId")))
.diagnosticKind(firstString(businessView.get("diagnostic_kind"), businessView.get("diagnosticKind"), payload.get("diagnostic_kind"), payload.get("diagnosticKind")))
.issueCode(firstString(businessView.get("issue_code"), businessView.get("issueCode"), payload.get("issue_code"), payload.get("issueCode")))
.repairAction(firstString(businessView.get("repair_action"), businessView.get("repairAction"), payload.get("repair_action"), payload.get("repairAction")))
.repairStatus(firstString(businessView.get("repair_status"), businessView.get("repairStatus"), payload.get("repair_status"), payload.get("repairStatus")))
.repairReason(firstString(businessView.get("repair_reason"), businessView.get("repairReason"), payload.get("repair_reason"), payload.get("repairReason")))
.safeRepairDisabled(nullableBooleanValue(firstPresent(businessView.get("safe_repair_disabled"), businessView.get("safeRepairDisabled"), payload.get("safe_repair_disabled"), payload.get("safeRepairDisabled"))))
.recommendedAction(firstString(businessView.get("recommended_action"), businessView.get("recommendedAction"), payload.get("recommended_action"), payload.get("recommendedAction")))
.policyKind(firstString(businessView.get("policy_kind"), businessView.get("policyKind"), payload.get("policy_kind"), payload.get("policyKind")))
.policyKey(firstString(businessView.get("policy_key"), businessView.get("policyKey"), payload.get("policy_key"), payload.get("policyKey")))
.scheduleId(firstString(businessView.get("schedule_id"), businessView.get("scheduleId"), payload.get("schedule_id"), payload.get("scheduleId")))
@@ -1264,6 +1278,7 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye
private boolean isAuditRow(DigitalEmployeeAdminWorkbenchRowDto row) {
return Set.of("sandbox_audit", "token_cost", "tool_call_audit", "governance", "sync_failure", "audit_archive", "billing_estimate").contains(row.getCategory())
|| isDiagnosticRepairRow(row)
|| contains(row.getKind(), "audit")
|| contains(row.getKind(), "cost")
|| contains(row.getKind(), "billing")
@@ -1271,6 +1286,18 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye
|| StringUtils.isNotBlank(row.getBillingEstimateId());
}
private boolean isDiagnosticRepairRow(DigitalEmployeeAdminWorkbenchRowDto row) {
String category = firstString(row.getCategory());
String kind = firstString(row.getKind());
return Set.of("diagnostic_repair", "diagnostic", "doctor_issue", "policy_pull_error").contains(category)
|| startsWithAny(kind, "doctor_", "diagnostic_", "repair_")
|| contains(kind, "policy_pull_error")
|| StringUtils.isNotBlank(row.getDiagnosticId())
|| StringUtils.isNotBlank(row.getIssueCode())
|| StringUtils.isNotBlank(row.getRepairAction())
|| StringUtils.isNotBlank(row.getRecommendedAction());
}
private boolean isDailyReportRow(DigitalEmployeeAdminWorkbenchRowDto row) {
return StringUtils.equals(row.getCategory(), "daily_report")
|| startsWithAny(row.getKind(), "daily_report")
@@ -1427,6 +1454,7 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye
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, "doctor_", "diagnostic_", "repair_") || contains(kind, "policy_pull_error")) return "diagnostic_repair";
if (contains(kind, "policy")) return "policy_snapshot";
if (contains(kind, "scheduler") || contains(kind, "cron") || contains(kind, "lease")) return "scheduler_operation";
if (startsWithAny(kind, "plan_revision", "governance_revision")) return "plan_revision";

View File

@@ -1474,6 +1474,110 @@ class DigitalEmployeeSyncApplicationServiceImplTest {
assertThat(result.getRecords().get(4).getBusinessView()).containsEntry("command_id", "command-1");
}
@Test
void queryAdminWorkbenchAuditsReturnsDiagnosticRepairRowsOnly() {
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-doctor-issue", "event", "diagnostic-1", 101L, new Date(10_000),
Map.ofEntries(
Map.entry("entity_type", "event"),
Map.entry("category", "diagnostic_repair"),
Map.entry("kind", "doctor_issue_detected"),
Map.entry("status", "warn"),
Map.entry("title", "File Server 异常"),
Map.entry("summary", "建议复核服务状态"),
Map.entry("diagnostic_id", "diagnostic-1"),
Map.entry("diagnostic_kind", "doctor_service"),
Map.entry("issue_code", "file_server_unavailable"),
Map.entry("recommended_action", "safe_restart_service"),
Map.entry("safe_repair_disabled", true)
),
Map.of("id", "diagnostic-1", "kind", "doctor_issue_detected")),
record("outbox-repair-intent", "event", "repair-1", 101L, new Date(11_000),
Map.ofEntries(
Map.entry("entity_type", "event"),
Map.entry("category", "diagnostic_repair"),
Map.entry("kind", "repair_intent_recorded"),
Map.entry("status", "recorded"),
Map.entry("title", "记录修复意图"),
Map.entry("diagnostic_id", "diagnostic-1"),
Map.entry("issue_code", "file_server_unavailable"),
Map.entry("repair_action", "record_repair_intent"),
Map.entry("repair_status", "recorded"),
Map.entry("repair_reason", "operator_reviewed")
),
Map.of("id", "repair-1", "kind", "repair_intent_recorded")),
record("outbox-policy-pull-error", "event", "diagnostic-2", 101L, new Date(12_000),
Map.of(
"entity_type", "event",
"kind", "policy_pull_error",
"status", "failed",
"title", "策略拉取失败",
"diagnostic_id", "diagnostic-2",
"issue_code", "policy_pull_error",
"recommended_action", "retry_check"
),
Map.of("id", "diagnostic-2", "kind", "policy_pull_error")),
record("outbox-sandbox-audit", "event", "audit-1", 101L, new Date(13_000),
Map.of(
"entity_type", "event",
"category", "sandbox_audit",
"kind", "sandbox_audit",
"status", "warn",
"title", "Sandbox 风险",
"diagnostic_id", "diagnostic-3",
"issue_code", "sandbox_warn"
),
Map.of("id", "audit-1", "kind", "sandbox_audit")),
record("outbox-tool-audit", "event", "audit-2", 101L, new Date(14_000),
Map.of(
"entity_type", "event",
"category", "tool_call_audit",
"kind", "tool_call_audit",
"status", "rejected",
"title", "工具调用被拒绝",
"diagnostic_id", "diagnostic-4",
"issue_code", "tool_rejected"
),
Map.of("id", "audit-2", "kind", "tool_call_audit")),
record("outbox-task", "task", "task-1", 101L, new Date(15_000),
Map.of("entity_type", "task", "title", "普通任务", "status", "running"),
Map.of("id", "task-1", "status", "running")),
record("outbox-approval", "approval", "approval-1", 101L, new Date(16_000),
Map.of("entity_type", "approval", "title", "普通审批", "status", "pending"),
Map.of("id", "approval-1", "status", "pending")),
record("outbox-notification", "notification", "notification-1", 101L, new Date(17_000),
Map.of("entity_type", "notification", "title", "普通通知", "status", "unread"),
Map.of("id", "notification-1", "status", "unread")),
record("outbox-artifact", "artifact", "artifact-1", 101L, new Date(18_000),
Map.of("entity_type", "artifact", "title", "普通产物", "status", "available"),
Map.of("id", "artifact-1", "status", "available")),
record("outbox-route", "event", "route-1", 101L, new Date(19_000),
Map.of("entity_type", "event", "category", "route_intervention", "title", "普通路由", "status", "blocked"),
Map.of("id", "route-1", "status", "blocked"))
)));
DigitalEmployeeAdminWorkbenchPageDto result = service.queryAdminWorkbench(
"audits", null, null, null, null, null, null, null, null, null, null, null, 1L, 20L
);
assertThat(result.getTotal()).isEqualTo(5);
assertThat(result.getRecords()).extracting(DigitalEmployeeAdminWorkbenchRowDto::getEntityId)
.containsExactly("diagnostic-1", "repair-1", "diagnostic-2", "audit-1", "audit-2");
DigitalEmployeeAdminWorkbenchRowDto row = result.getRecords().get(0);
assertThat(row.getView()).isEqualTo("audits");
assertThat(row.getDiagnosticId()).isEqualTo("diagnostic-1");
assertThat(row.getDiagnosticKind()).isEqualTo("doctor_service");
assertThat(row.getIssueCode()).isEqualTo("file_server_unavailable");
assertThat(row.getRecommendedAction()).isEqualTo("safe_restart_service");
assertThat(row.getSafeRepairDisabled()).isTrue();
assertThat(row.getSummary()).isEqualTo("建议复核服务状态");
assertThat(result.getRecords().get(1).getRepairAction()).isEqualTo("record_repair_intent");
assertThat(result.getRecords().get(1).getRepairStatus()).isEqualTo("recorded");
assertThat(result.getRecords().get(1).getRepairReason()).isEqualTo("operator_reviewed");
assertThat(result.getRecords().get(2).getIssueCode()).isEqualTo("policy_pull_error");
}
@Test
void queryAdminWorkbenchAuditsRedactsSensitivePayloadFields() {
when(repository.queryBusinessRecords(eq(101L), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null), eq(1L), eq(20L)))
@@ -1666,6 +1770,56 @@ class DigitalEmployeeSyncApplicationServiceImplTest {
assertThat(takeoverLease.getReasonCodes()).contains("unsupported_admin_action");
}
@Test
void createAdminActionAllowsLowRiskDiagnosticRepairIntent() {
DigitalEmployeeAdminActionResultDto markReviewed = service.createAdminAction(
adminActionRequest("diagnostic", "diagnostic-1", "mark_reviewed")
);
DigitalEmployeeAdminActionResultDto dismiss = service.createAdminAction(
adminActionRequest("diagnostic", "diagnostic-1", "dismiss")
);
DigitalEmployeeAdminActionResultDto retryCheck = service.createAdminAction(
adminActionRequest("diagnostic", "diagnostic-1", "retry_check")
);
DigitalEmployeeAdminActionResultDto recordRepairIntent = service.createAdminAction(
adminActionRequest("diagnostic", "diagnostic-1", "record_repair_intent")
);
assertThat(markReviewed.getOk()).isTrue();
assertThat(dismiss.getOk()).isTrue();
assertThat(retryCheck.getOk()).isTrue();
assertThat(recordRepairIntent.getOk()).isTrue();
assertThat(recordRepairIntent.getStatus()).isEqualTo("pending");
assertThat(recordRepairIntent.getReasonCodes()).contains("action_recorded");
}
@Test
void createAdminActionRejectsHighRiskDiagnosticRepair() {
DigitalEmployeeAdminActionResultDto shellRepair = service.createAdminAction(
adminActionRequest("diagnostic", "diagnostic-1", "run_shell_repair")
);
DigitalEmployeeAdminActionResultDto modifyConfig = service.createAdminAction(
adminActionRequest("diagnostic", "diagnostic-1", "modify_system_config")
);
DigitalEmployeeAdminActionResultDto restartService = service.createAdminAction(
adminActionRequest("diagnostic", "diagnostic-1", "restart_unlisted_service")
);
DigitalEmployeeAdminActionResultDto deleteFileRepair = service.createAdminAction(
adminActionRequest("diagnostic", "diagnostic-1", "delete_file_repair")
);
DigitalEmployeeAdminActionResultDto remotePolicyEdit = service.createAdminAction(
adminActionRequest("diagnostic", "diagnostic-1", "remote_policy_edit")
);
assertThat(shellRepair.getOk()).isFalse();
assertThat(shellRepair.getStatus()).isEqualTo("rejected");
assertThat(shellRepair.getReasonCodes()).contains("run_shell_repair_disabled");
assertThat(modifyConfig.getReasonCodes()).contains("modify_system_config_disabled");
assertThat(restartService.getReasonCodes()).contains("restart_unlisted_service_disabled");
assertThat(deleteFileRepair.getReasonCodes()).contains("delete_file_repair_disabled");
assertThat(remotePolicyEdit.getReasonCodes()).contains("remote_policy_edit_disabled");
}
@Test
void queryPendingAdminActionsFiltersTenantClientDeviceAndMasksKey() {
when(adminActionRepository.queryActions(eq(101L), eq("client-key-001"), eq("device-001"), eq("pending"), eq(1L), eq(20L)))

View File

@@ -219,6 +219,9 @@ function actionTarget(record: DigitalEmployeeAdminWorkbenchRow, view: DigitalEmp
return { entityType: 'daily_report', entityId: record.report_id };
}
if (view === 'audits') {
if (record.diagnostic_id || record.issue_code || record.repair_action) {
return { entityType: 'diagnostic', entityId: record.diagnostic_id || record.entity_id };
}
return { entityType: 'audit', entityId: record.entity_id };
}
if (view === 'governance_commands') {
@@ -322,10 +325,18 @@ function allowedActions(record: DigitalEmployeeAdminWorkbenchRow, view: DigitalE
];
}
if (view === 'audits') {
return [
const actions = [
{ action: 'mark_reviewed', label: '标记复核' },
{ action: 'dismiss', label: '忽略' },
];
if (record.diagnostic_id || record.issue_code || record.repair_action) {
return [
...actions,
{ action: 'retry_check', label: '重试诊断' },
{ action: 'record_repair_intent', label: '记录修复意图' },
];
}
return actions;
}
if (view === 'governance_commands') {
return [
@@ -1061,15 +1072,22 @@ const DigitalEmployeeOps: React.FC = () => {
)}
{activeView === 'audits' && (
<Typography.Paragraph>
<Typography.Text strong>/</Typography.Text>
{detailRecord.audit_archive_id || detailRecord.billing_estimate_id || detailRecord.entity_id || '--'}
<Typography.Text strong>//</Typography.Text>
{detailRecord.diagnostic_id || detailRecord.audit_archive_id || detailRecord.billing_estimate_id || detailRecord.entity_id || '--'}
{detailRecord.diagnostic_kind && ` · 诊断类型 ${detailRecord.diagnostic_kind}`}
{detailRecord.issue_code && ` · 问题码 ${detailRecord.issue_code}`}
{detailRecord.recommended_action && ` · 建议动作 ${detailRecord.recommended_action}`}
{detailRecord.repair_action && ` · 修复动作 ${detailRecord.repair_action}`}
{detailRecord.repair_status && ` · 修复状态 ${detailRecord.repair_status}`}
{detailRecord.repair_reason && ` · 修复原因 ${detailRecord.repair_reason}`}
{detailRecord.safe_repair_disabled && ' · 安全修复禁用'}
{detailRecord.archive_status && ` · 归档状态 ${detailRecord.archive_status}`}
{detailRecord.archive_location && ` · 归档位置 ${detailRecord.archive_location}`}
{detailRecord.billing_source && ` · 估算来源 ${detailRecord.billing_source}`}
{detailRecord.estimated != null && ` · ${detailRecord.estimated ? '估算费用' : '真实费用'}`}
<Typography.Text type="secondary">
{' '}
· promptraw commandauthorization token
· shell promptraw commandauthorization token
</Typography.Text>
</Typography.Paragraph>
)}

View File

@@ -664,6 +664,14 @@ export interface DigitalEmployeeAdminWorkbenchRow {
billing_estimate_id?: string;
billing_source?: string;
estimated?: boolean;
diagnostic_id?: string;
diagnostic_kind?: string;
issue_code?: string;
repair_action?: string;
repair_status?: string;
repair_reason?: string;
safe_repair_disabled?: boolean;
recommended_action?: string;
policy_kind?: string;
policy_key?: string;
schedule_id?: string;

View File

@@ -2545,6 +2545,100 @@ function checkDigitalAdminAuditDiagnosticsWorkbench() {
console.log("[DigitalEmployeeCheck] admin audit diagnostics workbench hooks OK");
}
function checkDigitalAdminDiagnosticRepairPreview() {
console.log("\n[DigitalEmployeeCheck] Verify admin diagnostic repair preview 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, "isDiagnosticRepairRow", "backend diagnostic repair matcher"],
[service, "diagnostic:retry_check", "backend diagnostic retry action"],
[service, "diagnostic:record_repair_intent", "backend diagnostic repair intent action"],
[service, "run_shell_repair_disabled", "backend shell repair disabled reason"],
[service, "modify_system_config_disabled", "backend system config disabled reason"],
[dto, "diagnostic_id", "backend diagnostic id field"],
[dto, "issue_code", "backend issue code field"],
[dto, "repair_action", "backend repair action field"],
[dto, "repair_status", "backend repair status field"],
[dto, "safe_repair_disabled", "backend safe repair disabled field"],
[test, "queryAdminWorkbenchAuditsReturnsDiagnosticRepairRowsOnly", "backend diagnostic repair workbench test"],
[test, "createAdminActionAllowsLowRiskDiagnosticRepairIntent", "backend diagnostic low risk action test"],
[test, "createAdminActionRejectsHighRiskDiagnosticRepair", "backend diagnostic high risk action test"],
[qimingTypes, "diagnostic_id", "qiming diagnostic id field"],
[qimingTypes, "repair_action", "qiming repair action field"],
[qimingTypes, "safe_repair_disabled", "qiming safe repair disabled field"],
[qimingOps, "记录修复意图", "qiming repair intent action"],
[qimingOps, "重试诊断", "qiming retry diagnostic action"],
[qimingOps, "不执行 shell", "qiming shell repair guard copy"],
[qimingOps, "不修改系统配置", "qiming system config guard copy"],
[docs, "正式管理端诊断修复预览", "docs diagnostic repair absorption"],
];
const missing = requiredSnippets
.filter(([content, snippet]) => !content.includes(snippet))
.map(([, , label]) => label);
if (missing.length > 0) {
throw new Error(`Missing admin diagnostic repair preview hooks: ${missing.join(", ")}`);
}
console.log("[DigitalEmployeeCheck] admin diagnostic repair preview hooks OK");
}
function checkDigitalAdminDailyReportWorkbench() {
console.log("\n[DigitalEmployeeCheck] Verify admin daily report workbench hooks");
const backendRoot = path.resolve(packageRoot, "..", "..", "..", "qiming-backend");
@@ -2770,6 +2864,7 @@ function main() {
checkDigitalAdminNotificationWorkbench();
checkDigitalAdminArtifactLifecycleWorkbench();
checkDigitalAdminAuditDiagnosticsWorkbench();
checkDigitalAdminDiagnosticRepairPreview();
checkDigitalAdminDailyReportWorkbench();
checkDigitalAdminGovernanceCommandWorkbench();
checkLocalDatabaseSchema();

View File

@@ -604,8 +604,8 @@ GET /api/digital-employee/approvals
- 建议:下一轮围绕管理端正式路由 UI 和跨设备冲突可视化,把 embedded 批量干预入口推进到管理端运营台。
11. **诊断 / 成本 / 审计视图**
- 已吸收:同步失败诊断较完整,部分 sandbox / memory / service 日志在 qimingclaw 其他模块中存在embedded adapter 已接管 `/api/doctor``/api/cost``/api/audit`,从 snapshot、runtime records、sync status、managed services 和 artifact delivery facts 生成诊断、估算成本和审计投影sandbox audit、token/cost 和 CLI/tool 调用事件已开始通过 `digital_events` 与 outbox `business_view` 提炼为 `sandbox_audit``token_cost``tool_call_audit` 分类,并只同步 session、tool、server、operation、status、exit code、duration、model、token 计数和估算成本等安全字段;数字员工首页已展示诊断/审计/Token/估算成本摘要,`/api/digital-employee/audits` 已提供本地审计业务视图诊断与审计风险通知联动已开始闭环。管理端正式审计视图预览和正式管理端审计诊断预览已开始吸收qiming-backend 的 `audits` 运营台视图会聚合 sandbox / token cost / tool call / sync failure / governance 审计业务视图qiming“数字员工运营台”的“审计诊断”tab 可标记复核、忽略和脱敏归档。诊断修复动作预览已开始吸收,“处置台”可把 doctor/audit/service/policy 风险转为可筛选、可标记和可安全重试的运营事项。发送账单审计外壳已开始吸收后端提供估算账单查询和审计归档记录接口qiming 运营台可显示 Token/估算成本并标记脱敏归档;归档记录会剔除 token、authorization、raw_input、prompt 和 command 等敏感字段。正式管理端审计归档账单预览已开始吸收,审计诊断详情可展示 `audit_archive_id``archive_status``archive_location``billing_estimate_id``billing_source``estimated`,用于解释脱敏归档位置与账单估算来源。
- 缺口:真实计费账单、独立 sandbox audit 文件归档、自动诊断修复动作和正式外部管理端审计视图尚未统一吸收;当前账单仍是估算,审计归档只记录脱敏摘要和归档位置。
- 已吸收:同步失败诊断较完整,部分 sandbox / memory / service 日志在 qimingclaw 其他模块中存在embedded adapter 已接管 `/api/doctor``/api/cost``/api/audit`,从 snapshot、runtime records、sync status、managed services 和 artifact delivery facts 生成诊断、估算成本和审计投影sandbox audit、token/cost 和 CLI/tool 调用事件已开始通过 `digital_events` 与 outbox `business_view` 提炼为 `sandbox_audit``token_cost``tool_call_audit` 分类,并只同步 session、tool、server、operation、status、exit code、duration、model、token 计数和估算成本等安全字段;数字员工首页已展示诊断/审计/Token/估算成本摘要,`/api/digital-employee/audits` 已提供本地审计业务视图诊断与审计风险通知联动已开始闭环。管理端正式审计视图预览和正式管理端审计诊断预览已开始吸收qiming-backend 的 `audits` 运营台视图会聚合 sandbox / token cost / tool call / sync failure / governance 审计业务视图qiming“数字员工运营台”的“审计诊断”tab 可标记复核、忽略和脱敏归档。诊断修复动作预览已开始吸收,“处置台”可把 doctor/audit/service/policy 风险转为可筛选、可标记和可安全重试的运营事项。正式管理端诊断修复预览已开始吸收,审计诊断详情可展示诊断 ID、问题码、建议动作、修复动作、修复状态和安全修复禁用状态并只记录重试诊断、复核、忽略和修复意图不执行 shell、不修改系统配置、不自动修复。发送账单审计外壳已开始吸收后端提供估算账单查询和审计归档记录接口qiming 运营台可显示 Token/估算成本并标记脱敏归档;归档记录会剔除 token、authorization、raw_input、prompt 和 command 等敏感字段。正式管理端审计归档账单预览已开始吸收,审计诊断详情可展示 `audit_archive_id``archive_status``archive_location``billing_estimate_id``billing_source``estimated`,用于解释脱敏归档位置与账单估算来源。
- 缺口:真实计费账单、独立 sandbox audit 文件归档、真实自动诊断修复执行器和高风险修复审批流尚未统一吸收;当前账单仍是估算,审计归档只记录脱敏摘要和归档位置。
- 建议:下一轮围绕管理端正式审计视图和诊断修复动作,把本地可追溯审计继续推进到可运营处置。
12. **管理端业务查询模型**