const DIGITAL_EMPLOYEE_AGENT = 'hermes' export const DIGITAL_EMPLOYEE_SKILL_CATEGORY_OPTIONS = ['积累', '升级', '整理', '评估'] const TASK_TYPE_LABELS = { daily_risk_scan: '每日风险巡检', global_risk_scan: '全局风险巡检', weekly_ar_summary: '周度应收账龄汇总', weekly_expense_report: '周度费用洞察', rule_review_digest: '规则待审摘要', knowledge_index_sync: '知识库归集', finance_policy_knowledge_organize: '整理公司财务知识制度', llm_wiki_rule_formation: '知识库归集', x_financial_callback: '任务回调上报' } const TASK_TYPE_SKILL_CATEGORIES = { daily_risk_scan: '评估', global_risk_scan: '评估', weekly_ar_summary: '整理', weekly_expense_report: '整理', rule_review_digest: '升级', knowledge_index_sync: '积累', finance_policy_knowledge_organize: '整理', llm_wiki_rule_formation: '积累', x_financial_callback: '升级' } const CONTENT_LABELS = { task_type: '任务类型', skill_category: '技能类型', skill_category_options: '技能类型范围', schedule: '执行计划', cron: '调度表达式', folder: '归集范围', changed_only: '仅处理变更', force: '强制重建', index_engine: '索引引擎', callback_type: '回调类型', status: '回写状态', summary: '结果摘要' } const HIDDEN_CONTENT_KEYS = new Set([ 'agent', 'target_agent', 'callback_token', 'token', 'api_key', 'authorization' ]) export function normalizeDigitalEmployeeText(value) { return String(value ?? '').trim() } export function sanitizeDigitalEmployeeText(value, fallback = '') { const text = normalizeDigitalEmployeeText(value) .replace(/hermes/gi, '数字员工') .replace(/赫尔墨斯/g, '数字员工') .replace(/\s+/g, ' ') .trim() return text || fallback } export function sanitizeDigitalEmployeeSource(value, fallback = '') { const text = normalizeDigitalEmployeeText(value) .replace(/hermes/gi, '数字员工') .replace(/赫尔墨斯/g, '数字员工') .trim() return text || fallback } export function sanitizeDigitalEmployeeName(value, fallback = '数字员工技能') { const text = sanitizeDigitalEmployeeText(value, fallback) .replace(/^数字员工[\s·::-]*/i, '') .trim() return text || fallback } export function parseDigitalEmployeeContent(value) { if (!value) { return {} } if (typeof value === 'object' && !Array.isArray(value)) { return value } if (typeof value !== 'string') { return {} } try { const parsed = JSON.parse(value) return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {} } catch { return {} } } export function resolveDigitalEmployeeTaskType(source = {}, content = {}) { const config = source.config_json || source.configJson || {} const raw = normalizeDigitalEmployeeText(content.task_type) || normalizeDigitalEmployeeText(config.task_type) || normalizeDigitalEmployeeText(source.task_type) || normalizeDigitalEmployeeText(source.code).replace(/^task\.hermes\./i, '') return raw.replace(/[-.]/g, '_') } export function resolveDigitalEmployeeSkillCategory(source = {}, content = {}) { const config = source.config_json || source.configJson || {} const taskType = resolveDigitalEmployeeTaskType(source, content) const explicitCategory = normalizeDigitalEmployeeText(config.skill_category) || normalizeDigitalEmployeeText(config.skillCategory) || normalizeDigitalEmployeeText(content.skill_category) || normalizeDigitalEmployeeText(content.skillCategory) if (DIGITAL_EMPLOYEE_SKILL_CATEGORY_OPTIONS.includes(explicitCategory)) { return explicitCategory } return TASK_TYPE_SKILL_CATEGORIES[taskType] || '整理' } export function isDigitalEmployeeAsset(source = {}) { const config = source.config_json || source.configJson || {} const haystack = [ source.asset_type, source.code, source.name, source.description, config.agent, config.target_agent, config.worker, config.runtime_agent ] .map((item) => normalizeDigitalEmployeeText(item).toLowerCase()) .filter(Boolean) .join(' ') return ( normalizeDigitalEmployeeText(source.asset_type) === 'task' && (haystack.includes(DIGITAL_EMPLOYEE_AGENT) || haystack.includes('task.hermes.')) ) } export function formatDigitalEmployeeCron(value) { const raw = normalizeDigitalEmployeeText(value) if (!raw) { return '手动触发' } const parts = raw.split(/\s+/) if (parts.length < 5) { return sanitizeDigitalEmployeeText(raw) } const [minute, hour, dayOfMonth, month, dayOfWeek] = parts const hourNumber = Number(hour) const minuteNumber = Number(minute) const timeLabel = Number.isFinite(hourNumber) && Number.isFinite(minuteNumber) ? `${String(hourNumber).padStart(2, '0')}:${String(minuteNumber).padStart(2, '0')}` : `${hour}:${minute}` if (dayOfMonth === '*' && month === '*' && dayOfWeek === '*') { return `每天 ${timeLabel}` } if (dayOfMonth === '*' && month === '*' && dayOfWeek !== '*') { const weekdayLabels = { '0': '周日', '1': '周一', '2': '周二', '3': '周三', '4': '周四', '5': '周五', '6': '周六', '7': '周日' } return `每${weekdayLabels[dayOfWeek] || `周${dayOfWeek}`} ${timeLabel}` } return sanitizeDigitalEmployeeText(raw) } export function resolveDigitalEmployeeSchedule(source = {}, content = {}) { const config = source.config_json || source.configJson || {} const raw = normalizeDigitalEmployeeText(config.cron) || normalizeDigitalEmployeeText(config.schedule) || normalizeDigitalEmployeeText(config.cron_expression) || normalizeDigitalEmployeeText(content.schedule) return { value: raw, label: formatDigitalEmployeeCron(raw) } } export function resolveDigitalEmployeeEnabled(source = {}) { const config = source.config_json || source.configJson || {} if (config.enabled === false || config.is_enabled === false) { return false } if (source.enabled === false || source.is_enabled === false) { return false } return normalizeDigitalEmployeeText(source.status || 'active') === 'active' } export function resolveDigitalEmployeeDisplayCode(source = {}, content = {}) { const taskType = resolveDigitalEmployeeTaskType(source, content) return taskType ? `digital.${taskType}` : 'digital.skill' } function formatDigitalEmployeeValue(value) { if (typeof value === 'boolean') { return value ? '是' : '否' } if (Array.isArray(value)) { return value.map((item) => sanitizeDigitalEmployeeText(item)).filter(Boolean).join('、') || '-' } if (value && typeof value === 'object') { return sanitizeDigitalEmployeeText(JSON.stringify(value, null, 2)) } return sanitizeDigitalEmployeeText(value, '-') } export function buildDigitalEmployeeContentRows(content = {}) { return Object.entries(content) .filter(([key]) => !HIDDEN_CONTENT_KEYS.has(normalizeDigitalEmployeeText(key).toLowerCase())) .map(([key, value]) => ({ key, label: CONTENT_LABELS[key] || key, value: formatDigitalEmployeeValue(value) })) } export function buildDigitalEmployeeContentPreview(content = {}) { const visiblePayload = {} for (const [key, value] of Object.entries(content)) { if (HIDDEN_CONTENT_KEYS.has(normalizeDigitalEmployeeText(key).toLowerCase())) { continue } visiblePayload[key] = value } return sanitizeDigitalEmployeeText(JSON.stringify(visiblePayload, null, 2)) } function resolveDigitalEmployeeMarkdownFromContent(content = {}, config = {}) { const candidates = [ content.skill_markdown, content.skills_markdown, content.source_markdown, content.markdown, content.skill_source, config.skill_markdown, config.skills_markdown, config.source_markdown, config.skill_source ] return candidates.find((item) => normalizeDigitalEmployeeText(item)) || '' } function buildDefaultDigitalEmployeeSource(source = {}, listMeta = {}, schedule = {}) { const name = listMeta.name || '数字员工技能' const description = listMeta.summary || sanitizeDigitalEmployeeText(source.description, '该技能用于后台自动执行指定任务。') return [ '---', `name: ${listMeta.code || 'digital.skill'}`, `description: ${description}`, '---', '', `# ${name}`, '', '## 功能说明', '', description, '', '## 执行方式', '', `- 技能类型:${listMeta.skillCategory || '整理'}`, `- 可选类型:${DIGITAL_EMPLOYEE_SKILL_CATEGORY_OPTIONS.join('、')}`, `- 执行计划:${schedule.label || '手动触发'}`, `- 触发方式:${listMeta.executionMode || '手动触发'}`, '', '## 操作要求', '', '- 按任务参数读取业务数据。', '- 运行完成后写回业务结果或运行日志。' ].join('\n') } export function buildDigitalEmployeeSourceMarkdown(source = {}, content = {}, listMeta = {}) { const config = source.config_json || source.configJson || {} if ( normalizeDigitalEmployeeText(source.current_version_content_type) === 'markdown' && typeof source.current_version_content === 'string' ) { return sanitizeDigitalEmployeeSource(source.current_version_content) } const schedule = resolveDigitalEmployeeSchedule(source, content) const sourceMarkdown = resolveDigitalEmployeeMarkdownFromContent(content, config) return sanitizeDigitalEmployeeSource( sourceMarkdown, buildDefaultDigitalEmployeeSource(source, listMeta, schedule) ) } function buildDigitalEmployeeBasicRows(source = {}, listMeta = {}, schedule = {}) { return [ { label: '技能编号', value: listMeta.code }, { label: '技能类型', value: listMeta.skillCategory }, { label: '维护人', value: listMeta.owner }, { label: '执行计划', value: schedule.label }, { label: '当前版本', value: source.working_version || source.current_version || '-' }, { label: '最近更新', value: source.updated_at || '-' } ] } export function buildDigitalEmployeeListMeta(source = {}) { const content = parseDigitalEmployeeContent(source.current_version_content) const taskType = resolveDigitalEmployeeTaskType(source, content) const skillCategory = resolveDigitalEmployeeSkillCategory(source, content) const schedule = resolveDigitalEmployeeSchedule(source, content) const enabled = resolveDigitalEmployeeEnabled(source) const fallbackName = TASK_TYPE_LABELS[taskType] || '数字员工技能' return { name: sanitizeDigitalEmployeeName(source.name, fallbackName), code: resolveDigitalEmployeeDisplayCode(source, content), summary: sanitizeDigitalEmployeeText(source.description, '面向后台自动执行的数字员工技能。'), category: '数字员工', skillCategory, skillCategoryOptions: DIGITAL_EMPLOYEE_SKILL_CATEGORY_OPTIONS, owner: sanitizeDigitalEmployeeText(source.owner, '平台运营'), reviewer: sanitizeDigitalEmployeeText(source.reviewer, '系统'), scope: schedule.label, scheduleLabel: schedule.label, executionMode: schedule.value ? '定时执行' : '手动触发', enabled, enabledLabel: enabled ? '已启动' : '未启动', enabledTone: enabled ? 'success' : 'disabled', taskType } } export function buildDigitalEmployeeDetailMeta(source = {}) { const content = parseDigitalEmployeeContent(source.current_version_content) const listMeta = buildDigitalEmployeeListMeta({ ...source, current_version_content: content }) const schedule = resolveDigitalEmployeeSchedule(source, content) const contentRows = buildDigitalEmployeeContentRows(content) const sourceMarkdown = buildDigitalEmployeeSourceMarkdown(source, content, listMeta) return { ...listMeta, rawCode: normalizeDigitalEmployeeText(source.code), description: sanitizeDigitalEmployeeText( source.description, '该技能由后台数字员工按计划执行,并把结果沉淀到对应业务资产或运行日志中。' ), sourceMarkdown, basicRows: buildDigitalEmployeeBasicRows(source, listMeta, schedule), contentRows, contentPreview: buildDigitalEmployeeContentPreview(content), scheduleRows: [ { label: '执行计划', value: schedule.label }, { label: '调度表达式', value: schedule.value || '手动触发' }, { label: '启动状态', value: listMeta.enabledLabel, tone: listMeta.enabledTone }, { label: '执行方式', value: listMeta.executionMode }, { label: '技能类型', value: listMeta.skillCategory } ], overviewRows: [ { label: '能力编号', value: listMeta.code }, { label: '业务归口', value: listMeta.owner }, { label: '当前版本', value: source.working_version || source.current_version || '-' }, { label: '最近更新', value: source.updated_at || '-' } ] } }