import type { ApprovalEntry, ArtifactEntry, CanonicalEvent, CronJob, CronRun, DashboardResponse, DebugPlan, DebugRun, DebugStoreResponse, DebugTask, DigitalEmployeeDecision, DigitalEmployeeDuty, DigitalEmployeeManagedService, DigitalEmployeePlanAgentState, DigitalEmployeeRunDetail, DigitalEmployeeTaskAgentState, DigitalEmployeeTaskExecutionSummary, DigitalEmployeeWorkEvent, DigitalEmployeeWorkdayActionRequest, DigitalEmployeeWorkdayProjection, FlowResponse, GovernanceCommandRequest, GovernanceCommandResponse, PlanDispatchResponse, PlanStepSummary, SkillSpec, } from '@/types/api'; declare global { interface Window { __QIMINGCLAW_EMBEDDED_DIGITAL__?: boolean; QimingClawBridge?: { digital?: { getSnapshot?: () => Promise; getRuntimeRecords?: () => Promise; getUiState?: () => Promise; getSkillCapabilities?: () => Promise; getPlanTemplates?: () => Promise; saveUiState?: (update: { state: AdapterState; action?: string; date?: string; metadata?: Record }) => Promise; getSyncStatus?: () => Promise; flushSync?: () => Promise; getCronSettings?: () => Promise; saveCronSettings?: (patch: Partial) => Promise; runSchedulerCheck?: () => Promise<{ activated: number; due?: number; skipped?: number }>; runScheduleNow?: (scheduleId: string) => Promise<{ ok?: boolean; scheduleRunId?: string; error?: string }>; getScheduleRuns?: (scheduleId: string, limit?: number) => Promise; recordGovernanceCommand?: (command: QimingclawGovernanceCommandRecord) => Promise; respondPermission?: (sessionId: string, permissionId: string, response: 'once' | 'always' | 'reject') => Promise<{ success?: boolean; error?: string }>; }; }; } } const STATE_KEY = 'qimingclaw:digital-employee-adapter-state'; const DEFAULT_REPORT_TIME = '18:00'; interface AdapterState { selectedDutyIdsByDate: Record; confirmedDates: Record; reportScheduleTime: string; reportGeneratedAtByDate: Record; decisionResultsByDate: Record>; consoleRole?: string; rolePreferences?: Record; profile: { name: string; company: string; position: string; currentStatus: string; }; } interface QimingclawServiceStatus { running?: boolean; error?: string; engineType?: string; port?: number; serverCount?: number; serverNames?: string[]; } interface QimingclawSyncStatus { running: boolean; syncing: boolean; enabled: boolean; endpoint: string | null; missingCredentials?: string[]; pending: number; failed: number; lastSyncAt: string | null; lastSyncError: string | null; failureSummary?: QimingclawSyncFailureSummary; recentFailures?: QimingclawSyncFailure[]; } interface QimingclawSyncFailureSummary { total: number; dueForRetry: number; backoff: number; byEntityType: Array<{ entityType: string; count: number }>; } interface QimingclawSyncAttempt { attemptNo: number; status: string; error: string | null; endpoint?: string | null; startedAt: string; finishedAt: string; } interface QimingclawSyncFailure { id: string; entityType: string; entityId: string; operation: string; attempts: number; lastAttemptAt: string | null; nextRetryAt?: string | null; dueForRetry?: boolean; managementRecordUrl?: string | null; attemptHistory?: QimingclawSyncAttempt[]; error: string | null; createdAt: string; updatedAt: string; } interface QimingclawSnapshot { generatedAt: string; services: Record; managedServices?: QimingclawManagedService[] | null; sessions: { total: number; active: number; items: Array<{ id: string; title?: string; status?: string; engineType?: string; projectId?: string }>; }; sync?: QimingclawSyncStatus | null; runtime?: QimingclawRuntimeRecords | null; uiState?: AdapterState | null; planTemplates?: QimingclawPlanTemplateRecord[] | null; } interface QimingclawManagedService { serviceId: string; name: string; kind: string; status: string; statusLabel: string; running: boolean; error?: string | null; requiresHumanAction: boolean; dependentTasks: string[]; restartCount: number; lastSeenAt: string; instance?: Record; lease?: Record; } interface QimingclawPlanTemplateList { generatedAt: string; templateDir: string; templates: QimingclawPlanTemplateRecord[]; } interface QimingclawPlanTemplateRecord extends DebugPlan { source: 'qimingclaw-plan-template'; template_path: string; updated_at: string; } interface QimingclawGovernanceCommandRecord { commandKind: string; commandId: string; correlationId?: string; planId?: string | null; scheduleId?: string | null; accepted?: boolean; message?: string; error?: string | null; payload?: Record | null; result?: Record | null; } interface QimingclawRuntimeRecords { generatedAt: string; plans: QimingclawPlanRecord[]; planSteps?: QimingclawPlanStepRecord[]; schedules?: QimingclawScheduleRecord[]; scheduleRuns?: QimingclawScheduleRunRecord[]; tasks: QimingclawTaskRecord[]; runs: QimingclawRunRecord[]; events: QimingclawEventRecord[]; artifacts?: QimingclawArtifactRecord[]; approvals?: QimingclawApprovalRecord[]; } interface QimingclawCronSettings { enabled: boolean; catch_up_on_startup: boolean; max_run_history: number; max_catch_up_runs: number; max_concurrent_schedule_runs: number; } interface QimingclawScheduleRecord { id: string; remoteId?: string | null; planId?: string | null; name: string; description?: string | null; cronExpr: string; timezone: string; status: string; enabled: boolean; prompt?: string | null; command?: string | null; payload: unknown; nextRunAt?: string | null; lastRunAt?: string | null; catchUpOnStartup: boolean; maxCatchUpRuns: number; maxRunHistory: number; failureCount: number; lastError?: string | null; syncStatus: string; createdAt: string; updatedAt: string; deletedAt?: string | null; } interface QimingclawScheduleRunRecord { id: string; scheduleId: string; planId?: string | null; runId?: string | null; dueAt: string; startedAt?: string | null; finishedAt?: string | null; status: string; attemptNo: number; triggerKind: string; error?: string | null; payload: unknown; createdAt: string; updatedAt: string; } interface QimingclawPlanRecord { id: string; remoteId?: string | null; title: string; objective?: string | null; status: string; source: string; payload: unknown; syncStatus: string; lastSyncedAt?: string | null; syncError?: string | null; createdAt: string; updatedAt: string; } interface QimingclawPlanStepRecord { id: string; remoteId?: string | null; planId: string; title: string; status: string; seq: number; dependsOn: string[]; preferredSkillIds: string[]; payload: unknown; syncStatus: string; lastSyncedAt?: string | null; syncError?: string | null; createdAt: string; updatedAt: string; } interface QimingclawTaskRecord { id: string; remoteId?: string | null; planId?: string | null; title: string; status: string; assignedSkillId?: string | null; payload: unknown; syncStatus: string; lastSyncedAt?: string | null; syncError?: string | null; createdAt: string; updatedAt: string; } interface QimingclawRunRecord { id: string; remoteId?: string | null; planId?: string | null; taskId?: string | null; status: string; startedAt?: string | null; finishedAt?: string | null; payload: unknown; syncStatus: string; lastSyncedAt?: string | null; syncError?: string | null; createdAt: string; updatedAt: string; } interface QimingclawEventRecord { id: string; remoteId?: string | null; planId?: string | null; taskId?: string | null; runId?: string | null; kind: string; message: string; payload: unknown; syncStatus: string; lastSyncedAt?: string | null; syncError?: string | null; occurredAt: string; createdAt: string; } interface QimingclawArtifactRecord { id: string; remoteId?: string | null; planId?: string | null; taskId?: string | null; runId?: string | null; kind: string; name?: string | null; uri?: string | null; payload: unknown; syncStatus: string; lastSyncedAt?: string | null; syncError?: string | null; createdAt: string; } interface QimingclawApprovalRecord { id: string; remoteId?: string | null; planId?: string | null; taskId?: string | null; runId?: string | null; title: string; status: string; payload: unknown; syncStatus: string; lastSyncedAt?: string | null; syncError?: string | null; createdAt: string; updatedAt: string; } interface QimingclawSkillCapabilities { generatedAt: string; skills: SkillSpec[]; } const SKILLS: SkillSpec[] = [ { skill_id: 'qimingclaw-acp-session', name: 'ACP 会话执行', version: '1.0.0', enabled: true, description: '通过 qimingclaw 的 ACP 引擎创建会话、执行任务并跟踪结果。', category: '执行', capability_level: 'core', }, { skill_id: 'qimingclaw-mcp-tools', name: 'MCP 工具编排', version: '1.0.0', enabled: true, description: '读取已配置 MCP 服务与工具,作为数字员工的工具能力池。', category: '工具', capability_level: 'core', }, { skill_id: 'qimingclaw-computer-control', name: '客户端电脑控制', version: '1.0.0', enabled: true, description: '承接 /computer/chat 执行入口,面向远程任务和本地自动化操作。', category: '桌面', capability_level: 'specialized', }, { skill_id: 'qimingclaw-artifact-report', name: '产物与日报整理', version: '1.0.0', enabled: true, description: '把执行记录、关键产物和异常汇总成数字员工日报。', category: '汇报', capability_level: 'standard', }, ]; const PLANS: DebugPlan[] = [ { plan_id: 'qimingclaw-client-readiness', title: '客户端运行状态巡检', objective: '确认 qimingclaw 客户端、Agent、MCP 和远程接入能力是否处于可执行状态。', status: 'running', created_at: todayAt(9, 0), steps: [ { step_id: 'agent-status', title: '检查 Agent 引擎状态', depends_on: [], preferred_skills: ['qimingclaw-acp-session'], tasks: [{ task_id: 'task-agent-status', title: '读取 Agent 服务状态', status: 'running' }], }, { step_id: 'mcp-status', title: '检查 MCP 工具能力', depends_on: ['agent-status'], preferred_skills: ['qimingclaw-mcp-tools'], tasks: [{ task_id: 'task-mcp-status', title: '同步 MCP 工具列表', status: 'pending' }], }, ], }, { plan_id: 'qimingclaw-remote-task-ingress', title: '远程任务接入准备', objective: '检查 lanproxy、computer server 和文件服务,为后端下发数字员工任务做准备。', status: 'pending', created_at: todayAt(9, 30), steps: [ { step_id: 'computer-server', title: '确认 /computer/chat 执行入口', depends_on: [], preferred_skills: ['qimingclaw-computer-control'], tasks: [{ task_id: 'task-computer-server', title: '检查电脑控制服务', status: 'pending' }], }, { step_id: 'file-artifact', title: '准备产物回传目录', depends_on: ['computer-server'], preferred_skills: ['qimingclaw-artifact-report'], tasks: [{ task_id: 'task-file-artifact', title: '整理执行产物通道', status: 'pending' }], }, ], }, { plan_id: 'qimingclaw-daily-report', title: '数字员工日报生成', objective: '汇总今日执行记录、服务状态和待介入事项,生成面向业务的日报。', status: 'pending', created_at: todayAt(17, 30), steps: [ { step_id: 'collect-events', title: '汇总运行事件', depends_on: [], preferred_skills: ['qimingclaw-artifact-report'], tasks: [{ task_id: 'task-collect-events', title: '整理今日运行明细', status: 'pending' }], }, { step_id: 'write-report', title: '生成日报摘要', depends_on: ['collect-events'], preferred_skills: ['qimingclaw-artifact-report'], tasks: [{ task_id: 'task-write-report', title: '生成数字员工日报', status: 'pending' }], }, ], }, ]; const PLAN_SCHEDULES: Record = { 'qimingclaw-client-readiness': { expression: '0 9 * * *', nextRun: todayAt(9, 0), scheduleText: '已定时 09:00 执行', }, 'qimingclaw-remote-task-ingress': { expression: '30 9 * * *', nextRun: todayAt(9, 30), scheduleText: '已定时 09:30 执行', }, 'qimingclaw-daily-report': { expression: '0 18 * * *', nextRun: todayAt(18, 0), scheduleText: '已定时 18:00 执行', }, }; export function isQimingclawDigitalApiEnabled(): boolean { return window.__QIMINGCLAW_EMBEDDED_DIGITAL__ === true; } export async function handleQimingclawDigitalApi( path: string, options: RequestInit = {}, ): Promise { if (!isQimingclawDigitalApiEnabled()) return undefined; const url = new URL(path, window.location.origin); const method = (options.method || 'GET').toUpperCase(); const pathname = url.pathname; if (method === 'GET' && pathname === '/api/digital-employee/workday') { return buildWorkday(url.searchParams.get('date') || todayInputDate(), await readQimingclawSnapshot()) as T; } if (method === 'POST' && pathname === '/api/digital-employee/workday/actions') { return await handleWorkdayAction(parseJsonBody(options.body), await readQimingclawSnapshot()) as T; } if (method === 'POST' && pathname === '/api/governance/command') { return await handleGovernanceCommand(parseJsonBody(options.body), await readQimingclawSnapshot()) as T; } if (method === 'GET' && pathname === '/api/scheduler/jobs') { return { jobs: buildSchedulerJobs(await readQimingclawSnapshot()) } as T; } if (method === 'GET' && pathname === '/api/cron') { return { jobs: buildSchedulerJobs(await readQimingclawSnapshot()) } as T; } if (method === 'POST' && pathname === '/api/cron') { return await handleCronCreate(parseJsonBody>(options.body), await readQimingclawSnapshot()) as T; } if (method === 'GET' && pathname === '/api/cron/settings') { return await readCronSettings() as T; } if (method === 'PATCH' && pathname === '/api/cron/settings') { const patch = parseJsonBody>(options.body); return await saveCronSettings(patch) as T; } if (method === 'POST' && pathname === '/api/cron/check') { const result = await window.QimingClawBridge?.digital?.runSchedulerCheck?.(); return { ok: true, activated: result?.activated ?? 0, ...result } as T; } const cronRunMatch = pathname.match(/^\/api\/cron\/([^/]+)\/runs$/); if (method === 'GET' && cronRunMatch) { const runs = await readCronRuns(decodeURIComponent(cronRunMatch[1] || ''), Number(url.searchParams.get('limit')) || 20); return { runs } as T; } const cronJobMatch = pathname.match(/^\/api\/cron\/([^/]+)$/); if ((method === 'PATCH' || method === 'DELETE') && cronJobMatch) { const scheduleId = decodeURIComponent(cronJobMatch[1] || ''); if (method === 'DELETE') { await handleCronCommand('delete_schedule', scheduleId, {}, await readQimingclawSnapshot()); return undefined as T; } return await handleCronPatch(scheduleId, parseJsonBody>(options.body), await readQimingclawSnapshot()) as T; } if (method === 'GET' && pathname === '/api/debug/plans') { return { plans: filterPlans(url.searchParams.get('q'), await readQimingclawSnapshot()) } as T; } if (method === 'GET' && pathname === '/api/debug/tasks') { const planId = url.searchParams.get('plan_id'); return buildTasksResponse(planId, await readQimingclawSnapshot()) as T; } if (method === 'GET' && pathname === '/api/debug/store') { return buildDebugStore(await readQimingclawSnapshot()) as T; } if (method === 'GET' && pathname === '/api/dashboard') { return buildDashboard(await readQimingclawSnapshot()) as T; } if (method === 'GET' && pathname === '/api/artifact') { return { artifacts: buildArtifacts(await readQimingclawSnapshot()) } as T; } if (method === 'GET' && pathname.startsWith('/api/debug/plan/') && pathname.endsWith('/flow')) { return buildPlanFlow(decodeURIComponent(pathname.split('/')[4] || ''), await readQimingclawSnapshot()) as T; } if (method === 'GET' && pathname.startsWith('/api/debug/task/') && pathname.endsWith('/flow')) { return buildTaskFlow(decodeURIComponent(pathname.split('/')[4] || ''), await readQimingclawSnapshot()) as T; } if (method === 'POST' && pathname.startsWith('/api/debug/plan/') && pathname.endsWith('/dispatch')) { return await dispatchPlan(decodeURIComponent(pathname.split('/')[4] || ''), await readQimingclawSnapshot()) as T; } if (method === 'GET' && pathname.startsWith('/api/plan/') && pathname.endsWith('/messages')) { return { messages: buildPlanMessages(decodeURIComponent(pathname.split('/')[3] || ''), await readQimingclawSnapshot()) } as T; } if (method === 'GET' && pathname === '/api/skill') { return { skills: await buildSkills() } as T; } if (method === 'POST' && pathname.startsWith('/api/skill/') && pathname.endsWith('/enabled')) { const skillId = decodeURIComponent(pathname.split('/')[3] || ''); const body = parseJsonBody<{ enabled?: boolean }>(options.body); return { ok: true, skill_id: skillId, enabled: body.enabled ?? true } as T; } return undefined; } function defaultState(): AdapterState { return { selectedDutyIdsByDate: {}, confirmedDates: {}, reportScheduleTime: DEFAULT_REPORT_TIME, reportGeneratedAtByDate: {}, decisionResultsByDate: {}, consoleRole: 'sales', rolePreferences: {}, profile: { name: '飞天数字员工', company: 'qimingclaw 客户端', position: '客户端任务执行员', currentStatus: 'working', }, }; } function readState(): AdapterState { try { const raw = window.localStorage.getItem(STATE_KEY); return raw ? { ...defaultState(), ...JSON.parse(raw) as Partial } : defaultState(); } catch { return defaultState(); } } function writeState(state: AdapterState): void { window.localStorage.setItem(STATE_KEY, JSON.stringify(state)); } async function readQimingclawSnapshot(): Promise { try { const snapshot = await window.QimingClawBridge?.digital?.getSnapshot?.() ?? null; if (!snapshot) return null; const [sync, runtime, uiState, planTemplates] = await Promise.all([ window.QimingClawBridge?.digital?.getSyncStatus?.().catch(() => null) ?? null, window.QimingClawBridge?.digital?.getRuntimeRecords?.().catch(() => null) ?? null, readBridgeUiState(), window.QimingClawBridge?.digital?.getPlanTemplates?.().catch(() => null) ?? null, ]); return { ...snapshot, sync, runtime, uiState, planTemplates: planTemplates?.templates ?? [] }; } catch { return null; } } async function readBridgeUiState(): Promise { try { return await window.QimingClawBridge?.digital?.getUiState?.() ?? null; } catch { return null; } } async function readBridgeSkillCapabilities(): Promise { try { const result = await window.QimingClawBridge?.digital?.getSkillCapabilities?.() ?? null; return Array.isArray(result?.skills) ? result.skills : []; } catch { return []; } } async function buildSkills(): Promise { return dedupeSkills([...SKILLS, ...await readBridgeSkillCapabilities()]); } function dedupeSkills(skills: SkillSpec[]): SkillSpec[] { const seen = new Set(); const result: SkillSpec[] = []; for (const skill of skills) { const skillId = typeof skill.skill_id === 'string' ? skill.skill_id.trim() : ''; if (!skillId || seen.has(skillId)) continue; seen.add(skillId); result.push({ ...skill, skill_id: skillId }); } return result; } async function saveState( state: AdapterState, action?: string, date?: string, metadata?: Record, ): Promise { writeState(state); try { return await window.QimingClawBridge?.digital?.saveUiState?.({ state, action, date, metadata }) ?? state; } catch { return state; } } async function recordGovernanceCommand( body: GovernanceCommandRequest, response: GovernanceCommandResponse, ): Promise { try { await window.QimingClawBridge?.digital?.recordGovernanceCommand?.({ commandKind: body.command_kind, commandId: response.command_id, correlationId: response.correlation_id, planId: stringFromUnknown(response.result?.plan_id) || stringFromUnknown(body.context_refs?.plan_id) || stringFromUnknown(body.payload?.plan_id) || null, scheduleId: stringFromUnknown(response.result?.schedule_id) || stringFromUnknown(body.context_refs?.schedule_id) || stringFromUnknown(body.payload?.schedule_id) || null, accepted: response.accepted, message: response.message, error: response.error ?? null, payload: body.payload ?? null, result: response.result ?? null, }); } catch { // Bridge persistence is best-effort; the adapter response should not block UI flow. } } async function readCronSettings(): Promise { try { return await window.QimingClawBridge?.digital?.getCronSettings?.() ?? defaultCronSettings(); } catch { return defaultCronSettings(); } } async function saveCronSettings(patch: Partial): Promise { try { return await window.QimingClawBridge?.digital?.saveCronSettings?.(patch) ?? { ...defaultCronSettings(), ...patch }; } catch { return { ...defaultCronSettings(), ...patch }; } } function defaultCronSettings(): QimingclawCronSettings { return { enabled: true, catch_up_on_startup: true, max_run_history: 100, max_catch_up_runs: 5, max_concurrent_schedule_runs: 1, }; } async function handleCronCreate( body: Record, snapshot: QimingclawSnapshot | null, ): Promise<{ status: string; job: CronJob }> { const stamp = Date.now(); const scheduleId = stringFromUnknown(body.id) || stringFromUnknown(body.schedule_id) || `cron-${slugifyIdPart(stringFromUnknown(body.name) || 'job')}-${stamp}`; await handleGovernanceCommand({ command_kind: 'create_schedule', context_refs: { schedule_id: scheduleId, plan_id: stringFromUnknown(body.plan_id) || stringFromUnknown(body.template_id) || undefined }, payload: { ...body, schedule_id: scheduleId, cron_expr: stringFromUnknown(body.schedule) || stringFromUnknown(body.cron_expr), }, correlation_id: `qimingclaw-cron-create-${stamp}`, idempotency_key: `qimingclaw-cron-create-${scheduleId}-${stamp}`, }, snapshot); const refreshed = await readQimingclawSnapshot(); return { status: 'created', job: buildSchedulerJobs(refreshed).find((job) => job.id === scheduleId) ?? cronJobFromBody(scheduleId, body) }; } async function handleCronPatch( scheduleId: string, patch: Record, snapshot: QimingclawSnapshot | null, ): Promise<{ status: string; job: CronJob }> { await handleCronCommand('update_schedule', scheduleId, patch, snapshot); const refreshed = await readQimingclawSnapshot(); return { status: 'updated', job: buildSchedulerJobs(refreshed).find((job) => job.id === scheduleId) ?? cronJobFromBody(scheduleId, patch) }; } async function handleCronCommand( commandKind: GovernanceCommandRequest['command_kind'], scheduleId: string, payload: Record, snapshot: QimingclawSnapshot | null, ): Promise { const stamp = Date.now(); return handleGovernanceCommand({ command_kind: commandKind, context_refs: { schedule_id: scheduleId, plan_id: stringFromUnknown(payload.plan_id) || stringFromUnknown(payload.template_id) || undefined }, payload: { ...payload, schedule_id: scheduleId, cron_expr: stringFromUnknown(payload.schedule) || stringFromUnknown(payload.cron_expr), }, correlation_id: `qimingclaw-cron-${commandKind}-${stamp}`, idempotency_key: `qimingclaw-cron-${commandKind}-${scheduleId}-${stamp}`, }, snapshot); } function cronJobFromBody(id: string, body: Record): CronJob { const now = new Date().toISOString(); const expression = stringFromUnknown(body.schedule) || stringFromUnknown(body.cron_expr) || '0 9 * * *'; return { id, name: stringFromUnknown(body.name) || id, expression, command: stringFromUnknown(body.command) || `digital-employee:${id}`, prompt: stringFromUnknown(body.prompt) || null, job_type: stringFromUnknown(body.job_type) || 'qimingclaw_schedule', schedule: { expression, schedule_id: id }, enabled: body.enabled !== false, delivery: { mode: 'qimingclaw_schedule', schedule_id: id }, template_id: stringFromUnknown(body.template_id) || stringFromUnknown(body.plan_id) || id, trigger_kind: 'scheduled', delete_after_run: body.delete_after_run === true, created_at: now, next_run: now, last_run: null, last_status: null, last_output: null, }; } async function readCronRuns(scheduleId: string, limit: number): Promise { const runs = await window.QimingClawBridge?.digital?.getScheduleRuns?.(scheduleId, limit).catch(() => []) ?? []; return runs.map((run, index) => ({ id: Number.isFinite(Date.parse(run.createdAt)) ? Date.parse(run.createdAt) + index : index + 1, job_id: run.scheduleId, started_at: run.startedAt || run.createdAt, finished_at: run.finishedAt || '', status: run.status, output: run.error || null, duration_ms: run.startedAt && run.finishedAt ? Math.max(0, Date.parse(run.finishedAt) - Date.parse(run.startedAt)) : null, })); } function parseJsonBody(body: BodyInit | null | undefined): T { if (typeof body !== 'string' || !body.trim()) return {} as T; return JSON.parse(body) as T; } function todayInputDate(): string { const date = new Date(); return [ date.getFullYear(), String(date.getMonth() + 1).padStart(2, '0'), String(date.getDate()).padStart(2, '0'), ].join('-'); } function todayAt(hour: number, minute: number): string { const date = new Date(); date.setHours(hour, minute, 0, 0); return date.toISOString(); } function displayDate(date: string): string { return date.split('-').join('/'); } function formatTime(value?: string | null): string { if (!value) return '未知'; const date = new Date(value); if (Number.isNaN(date.getTime())) return value; return new Intl.DateTimeFormat('zh-CN', { hour: '2-digit', minute: '2-digit', }).format(date); } function selectedSetForDate(state: AdapterState, date: string): Set { return new Set(state.selectedDutyIdsByDate[date] ?? []); } function planStatusTone(status: string): string { if (['completed', 'succeeded', 'success'].includes(status)) return 'success'; if (['running', 'active'].includes(status)) return 'running'; if (['failed', 'error'].includes(status)) return 'danger'; return 'waiting'; } function isRunning(snapshot: QimingclawSnapshot | null, key: string): boolean { return snapshot?.services[key]?.running === true; } function serviceError(snapshot: QimingclawSnapshot | null, key: string): string | undefined { return snapshot?.services[key]?.error; } function runtimeStatusForPlan(plan: DebugPlan, snapshot: QimingclawSnapshot | null): string { if (!snapshot) return plan.status; if (plan.plan_id === 'qimingclaw-client-readiness') { if (isRunning(snapshot, 'agent') && isRunning(snapshot, 'mcp')) return 'running'; if (serviceError(snapshot, 'agent') || serviceError(snapshot, 'mcp')) return 'failed'; return 'pending'; } if (plan.plan_id === 'qimingclaw-remote-task-ingress') { if (isRunning(snapshot, 'computerServer') || isRunning(snapshot, 'lanproxy') || isRunning(snapshot, 'fileServer')) return 'running'; if (serviceError(snapshot, 'computerServer') || serviceError(snapshot, 'lanproxy') || serviceError(snapshot, 'fileServer')) return 'failed'; return 'pending'; } return plan.status; } function serviceLine(snapshot: QimingclawSnapshot | null, key: string, label: string): string { const service = snapshot?.services[key]; if (!service) return `${label}:未连接`; if (service.running) { const suffix = service.port ? `(端口 ${service.port})` : ''; return `${label}:运行中${suffix}`; } return `${label}:未运行${service.error ? `,${service.error}` : ''}`; } function buildManagedServices(snapshot: QimingclawSnapshot | null): DigitalEmployeeManagedService[] { const fromSnapshot = snapshot?.managedServices ?? []; if (fromSnapshot.length > 0) { return fromSnapshot.map((service) => ({ service_id: service.serviceId, name: service.name, kind: service.kind, status: service.status, status_label: service.statusLabel, running: service.running, error: service.error ?? null, requires_human_action: service.requiresHumanAction, dependent_tasks: service.dependentTasks ?? [], restart_count: service.restartCount ?? 0, last_seen_at: service.lastSeenAt, instance: service.instance, lease: service.lease, })); } return [ fallbackManagedService(snapshot, 'agent', 'Agent', 'agent', ['ACP 会话执行', 'Plan Agent']), fallbackManagedService(snapshot, 'mcp', 'MCP Proxy', 'mcp', ['Skill Catalog', 'MCP 工具调用']), fallbackManagedService(snapshot, 'computerServer', 'Computer Server', 'desktop', ['/computer/chat', '远程任务接入']), fallbackManagedService(snapshot, 'fileServer', 'File Server', 'file', ['Artifact 上传', '文件交付']), fallbackManagedService(snapshot, 'lanproxy', 'Lanproxy', 'network', ['远程访问', '管理端回连']), fallbackManagedService(snapshot, 'guiServer', 'GUI Agent Server', 'gui', ['GUI 自动化']), ]; } function fallbackManagedService( snapshot: QimingclawSnapshot | null, key: string, name: string, kind: string, dependentTasks: string[], ): DigitalEmployeeManagedService { const service = snapshot?.services[key]; const running = service?.running === true; const status = service?.error ? 'degraded' : running ? 'running' : 'stopped'; return { service_id: key, name, kind, status, status_label: status === 'running' ? '运行中' : status === 'degraded' ? '异常' : '未运行', running, error: service?.error ?? null, requires_human_action: Boolean(service?.error), dependent_tasks: dependentTasks, restart_count: 0, last_seen_at: snapshot?.generatedAt ?? new Date().toISOString(), instance: service ? { port: service.port, engineType: service.engineType, serverCount: service.serverCount, serverNames: service.serverNames, } : undefined, lease: { state: status === 'running' ? 'active' : status, updatedAt: snapshot?.generatedAt ?? new Date().toISOString(), }, }; } function managedServiceLine(service: DigitalEmployeeManagedService): string { const deps = service.dependent_tasks.length > 0 ? `;依赖任务:${service.dependent_tasks.slice(0, 2).join('、')}` : ''; return `${service.name}:${service.status_label}${service.error ? `,${service.error}` : ''}${deps}`; } function managedServiceSummary(services: DigitalEmployeeManagedService[]): string { const runningCount = services.filter((service) => service.running).length; const issueCount = services.filter((service) => service.requires_human_action || service.status === 'degraded').length; return `ManagedService:${runningCount}/${services.length} 运行,${issueCount} 项需处理`; } function syncLine(snapshot: QimingclawSnapshot | null): string { const sync = snapshot?.sync; if (!sync) return '管理端同步:未连接'; if (!sync.enabled) return '管理端同步:已停用'; if (!sync.endpoint) return '管理端同步:未配置地址'; if (sync.syncing) return `管理端同步:同步中,待同步 ${sync.pending} 条`; if (sync.failed > 0) return `管理端同步:${sync.failed} 条失败,待同步 ${sync.pending} 条`; if (sync.pending > 0) return `管理端同步:待同步 ${sync.pending} 条`; return sync.lastSyncAt ? `管理端同步:已同步,最近 ${sync.lastSyncAt}` : '管理端同步:等待本地数据'; } function compactSyncText(value?: string | null): string { return String(value || '').replace(/\s+/g, ' ').trim(); } function runtimePlans(snapshot: QimingclawSnapshot | null): QimingclawPlanRecord[] { return snapshot?.runtime?.plans ?? []; } function runtimePlanSteps(snapshot: QimingclawSnapshot | null): QimingclawPlanStepRecord[] { return snapshot?.runtime?.planSteps ?? []; } function runtimeTasks(snapshot: QimingclawSnapshot | null): QimingclawTaskRecord[] { return snapshot?.runtime?.tasks ?? []; } function runtimeRuns(snapshot: QimingclawSnapshot | null): QimingclawRunRecord[] { return snapshot?.runtime?.runs ?? []; } function runtimeEvents(snapshot: QimingclawSnapshot | null): QimingclawEventRecord[] { return snapshot?.runtime?.events ?? []; } function runtimeArtifacts(snapshot: QimingclawSnapshot | null): QimingclawArtifactRecord[] { return snapshot?.runtime?.artifacts ?? []; } function runtimeApprovals(snapshot: QimingclawSnapshot | null): QimingclawApprovalRecord[] { return snapshot?.runtime?.approvals ?? []; } function hasRuntimeRecords(snapshot: QimingclawSnapshot | null): boolean { return runtimePlans(snapshot).length > 0 || runtimePlanSteps(snapshot).length > 0 || runtimeTasks(snapshot).length > 0 || runtimeArtifacts(snapshot).length > 0 || runtimeApprovals(snapshot).length > 0; } function latestRuntimeTime(plan: QimingclawPlanRecord, tasks: QimingclawTaskRecord[]): string { const sorted = [plan.updatedAt, ...tasks.map((task) => task.updatedAt)] .filter(Boolean) .sort(); return sorted[sorted.length - 1] ?? plan.updatedAt ?? plan.createdAt; } function taskStatusTone(status: string): 'running' | 'success' | 'danger' | 'waiting' { const normalized = status.toLowerCase(); if (['completed', 'succeeded', 'success', 'done'].includes(normalized)) return 'success'; if (['failed', 'error', 'rejected'].includes(normalized)) return 'danger'; if (['running', 'active', 'leased', 'syncing'].includes(normalized)) return 'running'; return 'waiting'; } function taskStatusLabel(status: string): string { const tone = taskStatusTone(status); if (tone === 'success') return '已完成'; if (tone === 'danger') return '异常'; if (tone === 'running') return '执行中'; return '待执行'; } function runtimePlanTasks(snapshot: QimingclawSnapshot | null, planId: string): QimingclawTaskRecord[] { return runtimeTasks(snapshot).filter((task) => task.planId === planId); } function runtimeStepsForPlan(snapshot: QimingclawSnapshot | null, planId: string): QimingclawPlanStepRecord[] { return runtimePlanSteps(snapshot) .filter((step) => step.planId === planId) .sort((left, right) => left.seq - right.seq || left.createdAt.localeCompare(right.createdAt)); } function taskStepId(task: QimingclawTaskRecord): string { const record = asRecord(task.payload); return stringValue(record?.stepId ?? record?.step_id) || (task.planId ? `${task.planId}:runtime` : 'qimingclaw-runtime'); } function planRecordStatus(plan: QimingclawPlanRecord, tasks: QimingclawTaskRecord[]): string { if (tasks.some((task) => taskStatusTone(task.status) === 'danger')) return 'failed'; if (tasks.some((task) => taskStatusTone(task.status) === 'running')) return 'running'; if (tasks.length > 0 && tasks.every((task) => taskStatusTone(task.status) === 'success')) return 'completed'; return plan.status || 'pending'; } function buildPlanAgents(snapshot: QimingclawSnapshot | null): DigitalEmployeePlanAgentState[] { return runtimePlans(snapshot).map((plan) => { const tasks = runtimePlanTasks(snapshot, plan.id); const status = planRecordStatus(plan, tasks); const tone = planStatusTone(status); const failedTaskCount = tasks.filter((task) => taskStatusTone(task.status) === 'danger').length; const runningTaskCount = tasks.filter((task) => taskStatusTone(task.status) === 'running').length; return { agent_id: `plan-agent-${slugifyIdPart(plan.id)}`, plan_id: plan.id, title: plan.title, lifecycle: status, status_label: planStatusLabel(status), status_tone: tone, managed_task_count: tasks.length, running_task_count: runningTaskCount, failed_task_count: failedTaskCount, next_action: failedTaskCount > 0 ? 'review_failures' : runningTaskCount > 0 ? 'monitor_runs' : tone === 'success' ? 'archive_results' : 'activate_plan', can_replan: failedTaskCount > 0, can_pause: runningTaskCount > 0, can_resume: status === 'paused', updated_at: latestRuntimeTime(plan, tasks), }; }); } function buildTaskAgents(snapshot: QimingclawSnapshot | null): DigitalEmployeeTaskAgentState[] { const runs = runtimeRuns(snapshot); return runtimeTasks(snapshot).map((task) => { const latestRun = latestRunForTask(runs, task.id); const tone = taskStatusTone(task.status); return { agent_id: `task-agent-${slugifyIdPart(task.id)}`, task_id: task.id, plan_id: task.planId ?? null, title: task.title, execution_state: task.status, status_label: taskStatusLabel(task.status), status_tone: tone, assigned_skill_id: task.assignedSkillId ?? null, latest_run_id: latestRun?.id ?? null, can_retry: tone === 'danger', can_skip: tone === 'danger' || tone === 'waiting', can_cancel: tone === 'running', next_action: tone === 'danger' ? 'retry_or_skip' : tone === 'running' ? 'watch_progress' : tone === 'success' ? 'collect_artifacts' : 'start_run', updated_at: latestRun?.updatedAt ?? task.updatedAt, }; }); } function latestRunForTask(runs: QimingclawRunRecord[], taskId: string): QimingclawRunRecord | null { const sorted = runs .filter((run) => run.taskId === taskId) .sort((left, right) => (right.updatedAt || right.createdAt).localeCompare(left.updatedAt || left.createdAt)); return sorted[0] ?? null; } function runtimePlanToDebugPlan(plan: QimingclawPlanRecord, snapshot: QimingclawSnapshot | null): DebugPlan { const tasks = runtimePlanTasks(snapshot, plan.id); const planSteps = runtimeStepsForPlan(snapshot, plan.id); const status = planRecordStatus(plan, tasks); const knownStepIds = new Set(planSteps.map((step) => step.id)); const orphanTasks = tasks.filter((task) => !knownStepIds.has(taskStepId(task))); const steps = planSteps.length > 0 ? [ ...planSteps.map((step) => runtimePlanStepToSummary(step, tasks)), ...(orphanTasks.length > 0 ? [runtimeFallbackStepToSummary(plan.id, orphanTasks)] : []), ] : [runtimeFallbackStepToSummary(plan.id, tasks)]; return { plan_id: plan.id, title: plan.title, objective: plan.objective ?? undefined, status, created_at: plan.createdAt, updated_at: latestRuntimeTime(plan, tasks), source: plan.source, sync_status: plan.syncStatus, steps, }; } function runtimePlanStepToSummary( step: QimingclawPlanStepRecord, tasks: QimingclawTaskRecord[], ): PlanStepSummary { return { step_id: step.id, title: step.title, depends_on: step.dependsOn, preferred_skills: step.preferredSkillIds, tasks: tasks .filter((task) => taskStepId(task) === step.id) .map((task) => ({ task_id: task.id, title: task.title, status: task.status, })), }; } function runtimeFallbackStepToSummary( planId: string, tasks: QimingclawTaskRecord[], ): PlanStepSummary { return { step_id: `${planId}:runtime`, title: 'qimingclaw 执行记录', depends_on: [], preferred_skills: [...new Set(tasks.map((task) => task.assignedSkillId).filter((value): value is string => Boolean(value)))], tasks: tasks.map((task) => ({ task_id: task.id, title: task.title, status: task.status, })), }; } function runtimeTaskToDebugTask(task: QimingclawTaskRecord): DebugTask { return { task_id: task.id, title: task.title, status: task.status, plan_id: task.planId ?? null, step_id: taskStepId(task), assigned_skill_id: task.assignedSkillId ?? '', description: task.syncError ?? '', created_at: task.createdAt, updated_at: task.updatedAt, started_at: task.createdAt, finished_at: taskStatusTone(task.status) === 'success' || taskStatusTone(task.status) === 'danger' ? task.updatedAt : null, error_message: task.syncError ?? null, sync_status: task.syncStatus, }; } function runtimeRunToDebugRun(run: QimingclawRunRecord, events: QimingclawEventRecord[]): DebugRun { return { run_id: run.id, task_id: run.taskId ?? '', plan_id: run.planId ?? null, skill_id: undefined, attempt_no: 1, lifecycle: taskStatusTone(run.status) === 'success' ? 'Succeeded' : taskStatusTone(run.status) === 'danger' ? 'Failed' : taskStatusTone(run.status) === 'running' ? 'Running' : run.status, created_at: run.startedAt ?? run.createdAt, completed_at: run.finishedAt ?? null, events: events.filter((event) => event.runId === run.id).map((event) => ({ event_id: event.id, kind: event.kind, occurred_at: event.occurredAt, message: event.message, })), artifacts: artifactsForRun(run, events), sync_status: run.syncStatus, }; } function syncFailureLine(failure: QimingclawSyncFailure): string { const error = compactSyncText(failure.error); const detail = error.length > 120 ? `${error.slice(0, 120)}...` : error; const retryText = failure.dueForRetry ? '等待补偿' : failure.nextRetryAt ? `下次重试 ${failure.nextRetryAt}` : '等待重试'; return `${failure.entityType}/${failure.operation} ${failure.id} 重试 ${failure.attempts} 次,${retryText}${detail ? `,${detail}` : ''}`; } function withRuntimePlan(plan: DebugPlan, snapshot: QimingclawSnapshot | null): DebugPlan { return { ...plan, status: runtimeStatusForPlan(plan, snapshot) }; } function planStatusLabel(status: string): string { if (status === 'running') return '执行中'; if (status === 'completed' || status === 'succeeded') return '已完成'; if (status === 'failed') return '异常'; return '待执行'; } function runtimeDebugPlans(snapshot: QimingclawSnapshot | null): DebugPlan[] { const plans = runtimePlans(snapshot); if (plans.length > 0) return plans.map((plan) => runtimePlanToDebugPlan(plan, snapshot)); const tasks = runtimeTasks(snapshot); if (tasks.length === 0) return []; return tasks.map((task) => ({ plan_id: task.id, title: task.title, objective: '未绑定 Plan 的 qimingclaw 本地任务记录。', status: task.status, created_at: task.createdAt, updated_at: task.updatedAt, source: 'qimingclaw', sync_status: task.syncStatus, steps: [ { step_id: `${task.id}:runtime`, title: 'qimingclaw 执行记录', depends_on: [], preferred_skills: task.assignedSkillId ? [task.assignedSkillId] : [], tasks: [{ task_id: task.id, title: task.title, status: task.status }], }, ], })); } function templatePlans(snapshot: QimingclawSnapshot | null): DebugPlan[] { const customTemplates = snapshot?.planTemplates ?? []; const byId = new Map(); for (const plan of [...customTemplates, ...PLANS]) { if (!byId.has(plan.plan_id)) byId.set(plan.plan_id, plan); } return Array.from(byId.values()); } function mergedDebugPlans(snapshot: QimingclawSnapshot | null): DebugPlan[] { const byId = new Map(); for (const plan of [...runtimeDebugPlans(snapshot), ...templatePlans(snapshot).map((plan) => withRuntimePlan(plan, snapshot))]) { if (!byId.has(plan.plan_id)) byId.set(plan.plan_id, plan); } return Array.from(byId.values()); } function planTemplateJob(plan: DebugPlan, snapshot: QimingclawSnapshot | null): CronJob { const normalizedPlan = withRuntimePlan(plan, snapshot); const schedule = PLAN_SCHEDULES[plan.plan_id] ?? PLAN_SCHEDULES['qimingclaw-client-readiness']!; return { id: `schedule-${plan.plan_id}`, name: plan.title, expression: schedule.expression, command: `digital-employee:${plan.plan_id}`, prompt: plan.objective ?? null, job_type: 'plan_template', schedule: { expression: schedule.expression, template_id: plan.plan_id }, enabled: true, delivery: { mode: 'plan_template', template_id: plan.plan_id }, template_id: plan.plan_id, trigger_kind: 'scheduled', step_count: plan.steps?.length ?? 0, skill_ids: [...new Set((plan.steps ?? []).flatMap((step) => step.preferred_skills ?? []))], delete_after_run: false, created_at: plan.created_at, next_run: schedule.nextRun, last_run: normalizedPlan.status === 'running' ? todayAt(9, 5) : null, last_status: normalizedPlan.status === 'running' ? 'running' : null, last_output: null, }; } function runtimeScheduleJob(schedule: QimingclawScheduleRecord, snapshot: QimingclawSnapshot | null): CronJob { const plan = schedule.planId ? mergedDebugPlans(snapshot).find((item) => item.plan_id === schedule.planId) : null; const runs = snapshot?.runtime?.scheduleRuns ?? []; const latestRun = runs.find((run) => run.scheduleId === schedule.id); return { id: schedule.id, name: schedule.name, expression: schedule.cronExpr, command: schedule.command || `digital-employee:${schedule.planId || schedule.id}`, prompt: schedule.prompt ?? schedule.description ?? null, job_type: 'qimingclaw_schedule', schedule: { mode: 'qimingclaw_schedule', schedule_id: schedule.id, plan_id: schedule.planId, expression: schedule.cronExpr, timezone: schedule.timezone, status: schedule.status, failure_count: schedule.failureCount, last_error: schedule.lastError, }, enabled: schedule.enabled && schedule.status === 'enabled' && !schedule.deletedAt, delivery: { mode: 'qimingclaw_schedule', schedule_id: schedule.id, plan_id: schedule.planId }, template_id: schedule.planId ?? schedule.id, trigger_kind: 'scheduled', step_count: plan?.steps?.length ?? 0, skill_ids: [...new Set((plan?.steps ?? []).flatMap((step) => step.preferred_skills ?? []))], delete_after_run: false, created_at: schedule.createdAt, next_run: schedule.nextRunAt || schedule.updatedAt, last_run: schedule.lastRunAt ?? latestRun?.finishedAt ?? latestRun?.startedAt ?? null, last_status: latestRun?.status ?? (schedule.lastError ? 'failed' : null), last_output: latestRun?.error ?? schedule.lastError ?? null, }; } function buildSchedulerJobs(snapshot: QimingclawSnapshot | null = null): CronJob[] { const realScheduleJobs = (snapshot?.runtime?.schedules ?? []) .filter((schedule) => schedule.status !== 'deleted') .map((schedule) => runtimeScheduleJob(schedule, snapshot)); const templateJobs = templatePlans(snapshot).map((plan) => planTemplateJob(plan, snapshot)); if (realScheduleJobs.length > 0) { const realTemplateIds = new Set(realScheduleJobs.map((job) => job.template_id)); return [ ...realScheduleJobs, ...templateJobs.filter((job) => !realTemplateIds.has(job.template_id)), ]; } if (hasRuntimeRecords(snapshot)) { const runtimeJobs = runtimeDebugPlans(snapshot).map((plan) => ({ id: `runtime-${plan.plan_id}`, name: plan.title, expression: '', command: `qimingclaw-runtime:${plan.plan_id}`, prompt: plan.objective ?? null, job_type: 'qimingclaw_runtime', schedule: { mode: 'runtime_record', plan_id: plan.plan_id }, enabled: true, delivery: { mode: 'qimingclaw_runtime', plan_id: plan.plan_id }, template_id: plan.plan_id, trigger_kind: 'runtime_record', step_count: plan.steps?.length ?? 0, skill_ids: [...new Set((plan.steps ?? []).flatMap((step) => step.preferred_skills ?? []))], delete_after_run: false, created_at: plan.created_at, next_run: plan.updated_at ?? plan.created_at, last_run: plan.updated_at ?? plan.created_at, last_status: plan.status, last_output: null, })); const runtimeIds = new Set(runtimeJobs.map((job) => job.template_id)); return [ ...runtimeJobs, ...templateJobs.filter((job) => !runtimeIds.has(job.template_id)), ]; } return templateJobs; } function buildDuties(state: AdapterState, date: string, snapshot: QimingclawSnapshot | null): DigitalEmployeeDuty[] { const selected = selectedSetForDate(state, date); if (hasRuntimeRecords(snapshot)) { const plans = runtimePlans(snapshot); const tasksWithoutPlan = runtimeTasks(snapshot).filter((task) => !task.planId); const planDuties = plans.map((plan, index) => { const tasks = runtimePlanTasks(snapshot, plan.id); const status = planRecordStatus(plan, tasks); const successCount = tasks.filter((task) => taskStatusTone(task.status) === 'success').length; const failureCount = tasks.filter((task) => taskStatusTone(task.status) === 'danger').length; const selectedByDefault = selected.size === 0 ? index < 3 : selected.has(plan.id); return { id: plan.id, title: plan.title, summary: plan.objective || 'qimingclaw 本地执行记录已同步到数字员工。', implemented: true, selectable: true, selected: selectedByDefault, status, status_label: taskStatusLabel(status), status_tone: taskStatusTone(status), schedule_text: tasks.length > 0 ? `最近更新 ${formatTime(latestRuntimeTime(plan, tasks))}` : '等待任务记录', result_text: tasks.length > 0 ? `${tasks.length} 项任务,${successCount} 项完成,${failureCount} 项异常` : `同步状态:${plan.syncStatus}`, conversation_id: plan.id, }; }); const standaloneDuties = tasksWithoutPlan.map((task, index) => ({ id: task.id, title: task.title, summary: '未绑定 Plan 的 qimingclaw 本地任务记录。', implemented: true, selectable: true, selected: selected.size === 0 ? planDuties.length + index < 3 : selected.has(task.id), status: task.status, status_label: taskStatusLabel(task.status), status_tone: taskStatusTone(task.status), schedule_text: `最近更新 ${formatTime(task.updatedAt)}`, result_text: task.syncError || `同步状态:${task.syncStatus}`, conversation_id: task.id, })); const runtimeDutyIds = new Set([...planDuties, ...standaloneDuties].map((duty) => duty.id)); return [ ...planDuties, ...standaloneDuties, ...templateDuties(state, date, snapshot).filter((duty) => !runtimeDutyIds.has(duty.id)), ]; } return templateDuties(state, date, snapshot); } function templateDuties( state: AdapterState, date: string, snapshot: QimingclawSnapshot | null, ): DigitalEmployeeDuty[] { const selected = selectedSetForDate(state, date); return templatePlans(snapshot).map((rawPlan) => { const plan = withRuntimePlan(rawPlan, snapshot); const schedule = PLAN_SCHEDULES[plan.plan_id] ?? PLAN_SCHEDULES['qimingclaw-client-readiness']!; return { id: plan.plan_id, title: plan.title, summary: plan.objective ?? '等待 qimingclaw 执行链路补充任务说明。', implemented: true, selectable: true, selected: selected.has(plan.plan_id), status: plan.status, status_label: planStatusLabel(plan.status), status_tone: planStatusTone(plan.status), schedule_text: schedule.scheduleText, result_text: planResultText(plan, snapshot), conversation_id: plan.plan_id, }; }); } function planResultText(plan: DebugPlan, snapshot: QimingclawSnapshot | null): string { if (plan.plan_id === 'qimingclaw-client-readiness') { return [ serviceLine(snapshot, 'agent', 'Agent'), serviceLine(snapshot, 'mcp', 'MCP'), `活跃会话:${snapshot?.sessions.active ?? 0}`, ].join(';'); } if (plan.plan_id === 'qimingclaw-remote-task-ingress') { return [ serviceLine(snapshot, 'computerServer', 'Computer Server'), serviceLine(snapshot, 'lanproxy', 'Lanproxy'), serviceLine(snapshot, 'fileServer', 'File Server'), ].join(';'); } return plan.status === 'running' ? '正在同步客户端运行状态' : '等待任务执行'; } function buildTasks(planId?: string | null, snapshot: QimingclawSnapshot | null = null): DebugTask[] { const templateTasks = templatePlans(snapshot) .filter((plan) => !planId || plan.plan_id === planId) .map((plan) => withRuntimePlan(plan, snapshot)) .flatMap(templatePlanTasks); if (hasRuntimeRecords(snapshot)) { const runtimeResult = runtimeTasks(snapshot) .filter((task) => !planId || task.planId === planId || task.id === planId) .map(runtimeTaskToDebugTask); const runtimeTaskIds = new Set(runtimeResult.map((task) => task.task_id)); return [ ...runtimeResult, ...templateTasks.filter((task) => !runtimeTaskIds.has(task.task_id)), ]; } return templateTasks; } function templatePlanTasks(plan: DebugPlan): DebugTask[] { return (plan.steps ?? []).flatMap((step) => (step.tasks ?? []).map((task) => ({ task_id: task.task_id, title: task.title, status: plan.status === 'running' && task.status === 'pending' ? 'running' : task.status, plan_id: plan.plan_id, step_id: step.step_id, assigned_skill_id: step.preferred_skills?.[0] ?? '', description: `${plan.title} · ${step.title}`, created_at: plan.created_at, updated_at: plan.status === 'running' ? new Date().toISOString() : plan.created_at, started_at: task.status === 'running' ? todayAt(9, 5) : null, finished_at: task.status === 'completed' ? todayAt(9, 10) : null, }))); } function buildRuns(tasks: DebugTask[]): DebugRun[] { return tasks .filter((task) => task.status === 'running' || task.status === 'completed') .map((task, index) => ({ run_id: `qimingclaw-run-${index + 1}`, task_id: task.task_id, plan_id: task.plan_id, skill_id: task.assigned_skill_id, attempt_no: 1, lifecycle: task.status === 'running' ? 'Running' : 'Succeeded', created_at: task.started_at ?? task.created_at, completed_at: task.finished_at, events: [ { event_id: `${task.task_id}-event-start`, kind: 'RunStarted', occurred_at: task.started_at ?? task.created_at, message: `${task.title} 已进入 qimingclaw 执行链路`, }, ], })); } function buildTasksResponse(planId?: string | null, snapshot: QimingclawSnapshot | null = null): { tasks: DebugTask[]; runs: DebugRun[]; total: number; truncated: boolean } { const tasks = buildTasks(planId, snapshot); if (hasRuntimeRecords(snapshot)) { const taskIds = new Set(tasks.map((task) => task.task_id)); const runs = runtimeRuns(snapshot) .filter((run) => !run.taskId || taskIds.has(run.taskId)) .map((run) => runtimeRunToDebugRun(run, runtimeEvents(snapshot))); return { tasks, runs, total: tasks.length, truncated: false }; } return { tasks, runs: buildRuns(tasks), total: tasks.length, truncated: false }; } function buildDebugStore(snapshot: QimingclawSnapshot | null): DebugStoreResponse { const plans = filterPlans(null, snapshot); const tasks = buildTasks(null, snapshot); const runs = buildTasksResponse(null, snapshot).runs; const realPlanSteps = runtimePlanSteps(snapshot); return { tables: { canonical_events: buildCanonicalEvents(snapshot, tasks, runs), tasks, runs, plans, plan_steps: realPlanSteps.length > 0 ? realPlanSteps.map((step) => runtimePlanStepToSummary(step, runtimeTasks(snapshot))) : plans.flatMap((plan) => plan.steps ?? []), approvals: buildApprovals(snapshot), artifacts: buildArtifacts(snapshot), }, }; } function buildDashboard(snapshot: QimingclawSnapshot | null): DashboardResponse { const tasks = buildTasks(null, snapshot); const queued = tasks.filter((task) => taskStatusTone(task.status) === 'waiting').length; const running = tasks.filter((task) => taskStatusTone(task.status) === 'running').length; const succeeded = tasks.filter((task) => taskStatusTone(task.status) === 'success').length; const failed = tasks.filter((task) => taskStatusTone(task.status) === 'danger').length; const approvals = buildApprovals(snapshot); return { runtime: { active_tasks: running, failed_tasks: failed, pending_confirmations: approvals.filter((approval) => approval.status === 'pending').length, }, tasks: { total: tasks.length, queued, running, succeeded, failed, }, approvals: { total: approvals.length, pending: approvals.filter((approval) => approval.status === 'pending').length, }, }; } function buildCanonicalEvents( snapshot: QimingclawSnapshot | null, tasks: DebugTask[], runs: DebugRun[], ): CanonicalEvent[] { const runtimeCanonicalEvents = runtimeEvents(snapshot).map((event) => ({ event_id: event.id, aggregate_type: event.runId ? 'run' : event.taskId ? 'task' : 'plan', aggregate_id: event.runId ?? event.taskId ?? event.planId ?? event.id, event_type: event.kind, kind: event.kind, message: event.message, occurred_at: event.occurredAt, timestamp: event.occurredAt, plan_id: event.planId ?? null, task_id: event.taskId ?? null, })); const runEvents = runs.flatMap((run) => (run.events ?? []).map((event) => ({ event_id: event.event_id ?? `${run.run_id}:${event.kind ?? 'event'}:${event.occurred_at ?? run.created_at}`, aggregate_type: 'run', aggregate_id: run.run_id, event_type: event.kind ?? run.lifecycle, kind: event.kind ?? run.lifecycle, message: event.message ?? run.lifecycle, occurred_at: event.occurred_at ?? run.completed_at ?? run.created_at, timestamp: event.occurred_at ?? run.completed_at ?? run.created_at, plan_id: run.plan_id ?? null, task_id: run.task_id || null, }))); const taskEvents = tasks.map((task) => ({ event_id: `task-status-${task.task_id}`, aggregate_type: 'task', aggregate_id: task.task_id, event_type: 'task_status', kind: task.status, message: `${task.title}:${taskStatusLabel(task.status)}`, occurred_at: task.updated_at ?? task.created_at, timestamp: task.updated_at ?? task.created_at, plan_id: task.plan_id ?? null, task_id: task.task_id, })); const syncEvents = (snapshot?.sync?.recentFailures ?? []).map((failure) => ({ event_id: `digital-sync-failure-${failure.id}`, aggregate_type: 'sync_outbox', aggregate_id: failure.id, event_type: 'digital_sync_failed', kind: 'digital_sync_failed', message: syncFailureLine(failure), occurred_at: failure.lastAttemptAt ?? failure.updatedAt ?? failure.createdAt, timestamp: failure.lastAttemptAt ?? failure.updatedAt ?? failure.createdAt, plan_id: null, task_id: null, })); return dedupeCanonicalEvents([ ...runtimeCanonicalEvents, ...runEvents, ...taskEvents, ...syncEvents, ]).sort((left, right) => (right.occurred_at ?? '').localeCompare(left.occurred_at ?? '')); } function dedupeCanonicalEvents(events: CanonicalEvent[]): CanonicalEvent[] { const seen = new Set(); return events.filter((event) => { if (!event.event_id || seen.has(event.event_id)) return false; seen.add(event.event_id); return true; }); } function buildApprovals(snapshot: QimingclawSnapshot | null): ApprovalEntry[] { const runtimeApprovalEntries = runtimeApprovals(snapshot).map((approval) => ({ approval_id: approval.id, task_id: approval.taskId ?? '', status: approval.status, created_at: approval.createdAt, plan_id: approval.planId ?? null, run_id: approval.runId ?? null, title: approval.title, reversible: approval.status === 'pending', source: 'qimingclaw-runtime', sync_status: approval.syncStatus, sync_error: approval.syncError, payload: approval.payload, })); const failures = snapshot?.sync?.recentFailures ?? []; const syncReviewEntries = failures .filter((failure) => failure.dueForRetry === false || /credential|auth|token|unauthor/i.test(failure.error ?? '')) .slice(0, 20) .map((failure) => ({ approval_id: `sync-review-${failure.id}`, task_id: failure.entityId, status: 'pending', created_at: failure.createdAt, title: `同步异常待处理:${failure.entityType}/${failure.entityId}`, reversible: false, source: 'qimingclaw-sync', error: failure.error, })); return [...runtimeApprovalEntries, ...syncReviewEntries]; } function buildWorkdayDecisions( state: AdapterState, date: string, snapshot: QimingclawSnapshot | null, ): DigitalEmployeeDecision[] { const plansById = new Map(runtimePlans(snapshot).map((plan) => [plan.id, plan])); const tasksById = new Map(runtimeTasks(snapshot).map((task) => [task.id, task])); const resultByDecisionId = state.decisionResultsByDate?.[date] ?? {}; return buildApprovals(snapshot).filter((approval) => stringValue(approval.source) === 'qimingclaw-runtime').map((approval) => { const payload = asRecord(approval.payload); const decisionId = stringValue(approval.approval_id) || `approval-${slugifyIdPart(stringValue(approval.task_id) || stringValue(approval.title) || 'item')}`; const result = resultByDecisionId[decisionId]; const status = decisionStatus(result, stringValue(approval.status) || 'pending'); const planId = stringValue(approval.plan_id); const taskId = stringValue(approval.task_id); const plan = planId ? plansById.get(planId) : null; const task = taskId ? tasksById.get(taskId) : null; return { id: decisionId, title: stringValue(approval.title) || stringValue(payload?.title) || '待确认事项', scenario: decisionScenario(approval, payload), status, status_label: decisionStatusLabel(status, result), plan_title: plan?.title || stringValue(payload?.plan_title) || stringValue(approval.plan_id) || 'qimingclaw 本地记录', task_title: task?.title || stringValue(payload?.task_title) || stringValue(approval.task_id) || '待确认任务', actions: isDecisionOpenStatus(status) ? [ { id: 'approved', label: '同意', tone: 'primary' }, { id: 'rejected', label: '驳回', tone: 'secondary' }, ] : [], }; }); } function decisionScenario(approval: ApprovalEntry, payload: Record | null): string { return stringValue(payload?.scenario) || stringValue(payload?.summary) || stringValue(payload?.reason) || stringValue(payload?.message) || stringValue(approval.error) || '来自 qimingclaw 本地运行记录的人工确认项。'; } function decisionStatus(result: string | undefined, originalStatus: string): string { const normalizedResult = String(result || '').toLowerCase(); if (['approve', 'approved', 'accept', 'accepted'].includes(normalizedResult)) return 'approved'; if (['reject', 'rejected', 'deny', 'denied'].includes(normalizedResult)) return 'rejected'; if (['dismiss', 'dismissed', 'skip', 'skipped'].includes(normalizedResult)) return 'dismissed'; return originalStatus || 'pending'; } function decisionStatusLabel(status: string, result?: string): string { if (result) { if (status === 'approved') return '已同意'; if (status === 'rejected') return '已驳回'; if (status === 'dismissed') return '已忽略'; return '已处理'; } if (status === 'approved') return '已同意'; if (status === 'rejected') return '已驳回'; if (status === 'completed') return '已完成'; if (status === 'failed') return '异常'; return '待确认'; } function isDecisionOpenStatus(status: string): boolean { return ['pending', 'requested'].includes(String(status || '').toLowerCase()); } function buildArtifacts(snapshot: QimingclawSnapshot | null): ArtifactEntry[] { const artifacts = [ ...runtimeArtifacts(snapshot).map(artifactFromRuntimeRecord), ...runtimeRuns(snapshot).flatMap((run) => artifactsForRun(run, runtimeEvents(snapshot))), ...runtimeEvents(snapshot).flatMap((event) => artifactsFromPayload(event.id, event.payload, event.occurredAt, { sourceType: 'event', sourceEventId: event.id, sourceLabel: `Event ${event.kind}`, planId: event.planId, taskId: event.taskId, runId: event.runId, })), ]; return dedupeArtifacts(artifacts); } function artifactFromRuntimeRecord(artifact: QimingclawArtifactRecord): ArtifactEntry { const payload = asRecord(artifact.payload); return { artifact_id: artifact.id, subject_id: artifact.runId ?? artifact.taskId ?? artifact.planId ?? artifact.id, kind: artifact.kind, name: artifact.name || stringValue(payload?.name ?? payload?.title) || artifactName(artifact.uri ?? '') || artifact.kind, uri: artifact.uri || stringValue(payload?.uri ?? payload?.url ?? payload?.path) || undefined, value: stringValue(payload?.value ?? payload?.summary ?? payload?.text) || artifact.uri || artifact.name || artifact.kind, plan_id: artifact.planId ?? null, task_id: artifact.taskId ?? null, run_id: artifact.runId ?? null, created_at: artifact.createdAt, source: 'qimingclaw-runtime', source_type: 'artifact', source_label: artifactSourceLabel('artifact', artifact.id), sync_status: artifact.syncStatus, sync_error: artifact.syncError, payload: artifact.payload, }; } function artifactsForRun(run: QimingclawRunRecord, events: QimingclawEventRecord[]): ArtifactEntry[] { return dedupeArtifacts([ ...artifactsFromPayload(run.id, run.payload, run.finishedAt ?? run.startedAt ?? run.createdAt, { sourceType: 'run', sourceLabel: artifactSourceLabel('run', run.id), planId: run.planId, taskId: run.taskId, runId: run.id, }), ...events .filter((event) => event.runId === run.id) .flatMap((event) => artifactsFromPayload(run.id, event.payload, event.occurredAt, { sourceType: 'event', sourceEventId: event.id, sourceLabel: `Event ${event.kind}`, planId: event.planId, taskId: event.taskId, runId: event.runId, })), ]); } interface ArtifactSourceContext { sourceType: string; sourceLabel: string; sourceEventId?: string | null; planId?: string | null; taskId?: string | null; runId?: string | null; } function artifactsFromPayload( subjectId: string, payload: unknown, createdAt?: string | null, sourceContext?: ArtifactSourceContext, ): ArtifactEntry[] { const record = asRecord(payload); if (!record) return typeof payload === 'string' ? [artifactFromValue(subjectId, payload, 0, createdAt, sourceContext)] : []; const explicitArtifacts = [ ...arrayValue(record.artifacts), ...arrayValue(record.outputs), ...arrayValue(record.files), ...arrayValue(record.attachments), ]; const artifacts = explicitArtifacts.map((item, index) => artifactFromValue(subjectId, item, index, createdAt, sourceContext)); for (const key of ['artifact', 'outputPath', 'output_path', 'filePath', 'file_path', 'path', 'uri', 'url']) { if (record[key]) artifacts.push(artifactFromValue(subjectId, record[key], artifacts.length, createdAt, sourceContext)); } return artifacts.filter((artifact) => artifact.name || artifact.uri || artifact.value); } function artifactFromValue( subjectId: string, value: unknown, index: number, createdAt?: string | null, sourceContext?: ArtifactSourceContext, ): ArtifactEntry { const record = asRecord(value); if (record) { const uri = stringValue(record.uri ?? record.url ?? record.path ?? record.filePath ?? record.file_path ?? record.outputPath ?? record.output_path); const name = stringValue(record.name ?? record.title ?? record.filename) || artifactName(uri) || `产物 ${index + 1}`; return { artifact_id: stringValue(record.artifact_id ?? record.id) || `artifact-${slugifyIdPart(subjectId)}-${index + 1}`, subject_id: stringValue(record.subject_id) || subjectId, kind: stringValue(record.kind ?? record.type) || 'artifact', name, uri, value: stringValue(record.value ?? record.summary ?? record.text) || uri, created_at: (stringValue(record.created_at ?? record.createdAt) || createdAt) ?? undefined, source: 'qimingclaw-runtime', source_type: stringValue(record.source_type) || sourceContext?.sourceType || 'payload', source_event_id: stringValue(record.source_event_id) || sourceContext?.sourceEventId || null, source_label: stringValue(record.source_label) || sourceContext?.sourceLabel || artifactSourceLabel('payload', subjectId), plan_id: stringValue(record.plan_id) || sourceContext?.planId || null, task_id: stringValue(record.task_id) || sourceContext?.taskId || null, run_id: stringValue(record.run_id) || sourceContext?.runId || null, }; } const text = String(value ?? '').trim(); return { artifact_id: `artifact-${slugifyIdPart(subjectId)}-${index + 1}`, subject_id: subjectId, kind: 'artifact', name: artifactName(text) || `产物 ${index + 1}`, uri: looksLikeUri(text) ? text : undefined, value: text, created_at: createdAt ?? undefined, source: 'qimingclaw-runtime', source_type: sourceContext?.sourceType || 'payload', source_event_id: sourceContext?.sourceEventId || null, source_label: sourceContext?.sourceLabel || artifactSourceLabel('payload', subjectId), plan_id: sourceContext?.planId || null, task_id: sourceContext?.taskId || null, run_id: sourceContext?.runId || null, }; } function artifactSourceLabel(sourceType: string, sourceId?: string | null): string { const suffix = sourceId ? ` ${sourceId}` : ''; switch (sourceType) { case 'artifact': return `Artifact${suffix}`; case 'event': return `Event${suffix}`; case 'run': return `Run${suffix}`; case 'task': return `Task${suffix}`; case 'plan': return `Plan${suffix}`; default: return `Payload${suffix}`; } } function dedupeArtifacts(artifacts: ArtifactEntry[]): ArtifactEntry[] { const seen = new Set(); return artifacts.filter((artifact) => { const key = artifact.artifact_id || `${artifact.subject_id}:${artifact.uri || artifact.value || artifact.name}`; if (!key || seen.has(key)) return false; seen.add(key); return Boolean(artifact.name || artifact.uri || artifact.value); }); } function asRecord(value: unknown): Record | null { return value && typeof value === 'object' && !Array.isArray(value) ? value as Record : null; } function arrayValue(value: unknown): unknown[] { return Array.isArray(value) ? value : []; } function stringValue(value: unknown): string { return typeof value === 'string' ? value.trim() : ''; } function looksLikeUri(value: string): boolean { return /^(file|https?):\/\//i.test(value) || value.includes('/') || value.includes('\\'); } function artifactName(value: string): string { if (!value) return ''; const normalized = value.split(/[?#]/)[0] || value; return normalized.split(/[\\/]/).filter(Boolean).pop() ?? ''; } function slugifyIdPart(value: string): string { const slug = value .trim() .toLowerCase() .replace(/[^a-z0-9]+/g, '-') .replace(/^-+|-+$/g, ''); if (slug) return slug; let hash = 0; for (let index = 0; index < value.length; index += 1) { hash = (hash * 31 + value.charCodeAt(index)) >>> 0; } return `item-${hash.toString(36)}`; } function filterPlans(query?: string | null, snapshot: QimingclawSnapshot | null = null): DebugPlan[] { const normalized = (query ?? '').trim().toLowerCase(); const plans = mergedDebugPlans(snapshot); if (!normalized) return plans; return plans.filter((plan) => ( plan.title.toLowerCase().includes(normalized) || plan.plan_id.toLowerCase().includes(normalized) || (plan.objective ?? '').toLowerCase().includes(normalized) )); } function buildRuntimeWorkEvents(snapshot: QimingclawSnapshot | null): DigitalEmployeeWorkEvent[] { const plansById = new Map(runtimePlans(snapshot).map((plan) => [plan.id, plan])); const tasksById = new Map(runtimeTasks(snapshot).map((task) => [task.id, task])); const events = runtimeEvents(snapshot).map((event) => { const task = event.taskId ? tasksById.get(event.taskId) : undefined; const planId = event.planId ?? task?.planId ?? null; const plan = planId ? plansById.get(planId) : undefined; const status = task?.status ?? plan?.status ?? (event.kind.includes('failed') || event.kind.includes('error') ? 'failed' : 'running'); const tone = taskStatusTone(status); return { id: event.id, time: formatTime(event.occurredAt), plan_title: plan?.title ?? 'qimingclaw 本地执行', task_title: task?.title ?? event.message, step_title: event.kind, detail: event.message || event.kind, status, status_label: taskStatusLabel(status), status_tone: tone, updated_at: event.occurredAt, conversation_id: planId ?? task?.id ?? event.id, }; }); if (events.length > 0) return events; return runtimeTasks(snapshot).map((task) => { const plan = task.planId ? plansById.get(task.planId) : undefined; return { id: `task-event-${task.id}`, time: formatTime(task.updatedAt), plan_title: plan?.title ?? 'qimingclaw 本地任务', task_title: task.title, step_title: task.assignedSkillId || 'qimingclaw-runtime', detail: task.syncError || `${task.title}:${taskStatusLabel(task.status)},同步状态 ${task.syncStatus}`, status: task.status, status_label: taskStatusLabel(task.status), status_tone: taskStatusTone(task.status), updated_at: task.updatedAt, conversation_id: task.planId ?? task.id, }; }); } function buildEvents(duties: DigitalEmployeeDuty[], confirmed: boolean, snapshot: QimingclawSnapshot | null): DigitalEmployeeWorkEvent[] { if (hasRuntimeRecords(snapshot)) { const runtimeWorkEvents = buildRuntimeWorkEvents(snapshot); return appendSyncEvent(runtimeWorkEvents, snapshot); } if (!confirmed) { return [{ id: 'event-await-confirmation', time: '待确认', plan_title: '任务设置', task_title: '等待确认今日任务', step_title: '选择职责', detail: '请先确认今天要执行的任务,确认后数字员工会开始执行。', status: 'pending', status_label: '待确认', status_tone: 'waiting', updated_at: new Date().toISOString(), conversation_id: null, }]; } const selectedDuties = duties.filter((duty) => duty.selected); const events = selectedDuties.map((duty) => ({ id: `event-${duty.id}`, time: duty.status === 'running' ? '实时' : '待执行', plan_title: duty.title, task_title: duty.title, step_title: duty.status === 'running' ? '同步 qimingclaw 服务状态' : '等待调度', detail: duty.status === 'running' ? `${duty.result_text}。快照时间:${snapshot?.generatedAt ?? '未连接 bridge'}` : '任务已确认,将在后续阶段接入 ACP/MCP 真实执行事件。', status: duty.status === 'running' ? 'running' : 'pending', status_label: duty.status === 'running' ? '执行中' : '待执行', status_tone: duty.status === 'running' ? 'running' : 'waiting', updated_at: new Date().toISOString(), conversation_id: duty.conversation_id, })); return appendSyncEvent(events, snapshot); } function appendSyncEvent(events: DigitalEmployeeWorkEvent[], snapshot: QimingclawSnapshot | null): DigitalEmployeeWorkEvent[] { const sync = snapshot?.sync; if (sync && (sync.pending > 0 || sync.failed > 0 || sync.lastSyncAt)) { const recentFailureText = (sync.recentFailures || []) .slice(0, 3) .map(syncFailureLine) .join(';'); return [...events, { id: 'event-management-sync', time: sync.syncing ? '实时' : '同步', plan_title: '管理端联动', task_title: '同步数字员工本地状态', step_title: sync.failed > 0 ? '同步补偿' : 'outbox 推送', detail: [ syncLine(snapshot), sync.lastSyncError ? `错误:${sync.lastSyncError}` : '', recentFailureText ? `最近失败:${recentFailureText}` : '', ].filter(Boolean).join('。'), status: sync.failed > 0 ? 'failed' : sync.syncing || sync.pending > 0 ? 'running' : 'completed', status_label: sync.failed > 0 ? '异常' : sync.syncing || sync.pending > 0 ? '同步中' : '已同步', status_tone: sync.failed > 0 ? 'danger' : sync.syncing || sync.pending > 0 ? 'running' : 'success', updated_at: sync.lastSyncAt ?? new Date().toISOString(), conversation_id: 'qimingclaw-management-sync', }]; } return events; } function buildExecutionSummaries(duties: DigitalEmployeeDuty[], events: DigitalEmployeeWorkEvent[]): DigitalEmployeeTaskExecutionSummary[] { const selectedDuties = duties.some((duty) => duty.selected) ? duties.filter((duty) => duty.selected) : duties; return selectedDuties.map((duty) => { const related = events.filter((event) => event.conversation_id === duty.conversation_id); const running = related.filter((event) => event.status === 'running').length; const success = related.filter((event) => event.status === 'completed').length; const failure = related.filter((event) => taskStatusTone(event.status) === 'danger').length; const waiting = Math.max(0, related.length - running - success - failure); return { id: duty.id, plan_id: duty.id, title: duty.title, description: duty.summary, selected: duty.selected, total_count: Math.max(related.length, 1), success_count: success, failure_count: failure, running_count: running, waiting_count: related.length === 0 ? 1 : waiting, latest_status_label: failure > 0 ? '异常' : running > 0 ? '执行中' : success > 0 ? '已完成' : '待执行', latest_status_tone: failure > 0 ? 'danger' : running > 0 ? 'running' : success > 0 ? 'success' : 'waiting', business_summary: duty.result_text, ai_summary: '当前由 qimingclaw 本地运行记录生成。', }; }); } function buildRunDetails(events: DigitalEmployeeWorkEvent[]): DigitalEmployeeRunDetail[] { return events.map((event) => ({ id: `run-detail-${event.id}`, time: event.time, task_name: event.task_title, step_name: event.step_title, operation_name: event.plan_title, status_label: event.status_label, status_tone: event.status_tone, business_detail: event.detail, raw_detail: event.detail, updated_at: event.updated_at, conversation_id: event.conversation_id, })); } function buildWorkday(date: string, snapshot: QimingclawSnapshot | null): DigitalEmployeeWorkdayProjection { const state = snapshot?.uiState ?? readState(); const confirmedAt = state.confirmedDates[date] ?? null; const confirmed = Boolean(confirmedAt); const duties = buildDuties(state, date, snapshot); const events = buildEvents(duties, confirmed, snapshot); const executionSummaries = buildExecutionSummaries(duties, events); const generatedAt = state.reportGeneratedAtByDate[date] ?? null; const selectedCount = duties.filter((duty) => duty.selected).length; const runningCount = events.filter((event) => event.status === 'running').length; const successCount = events.filter((event) => taskStatusTone(event.status) === 'success').length; const failureCount = events.filter((event) => taskStatusTone(event.status) === 'danger').length; const liveRecordCount = runtimePlans(snapshot).length + runtimeTasks(snapshot).length; const hasLiveRuntime = hasRuntimeRecords(snapshot); const sync = snapshot?.sync; const artifactSources = reportArtifactSources(buildArtifacts(snapshot)); const managedServices = buildManagedServices(snapshot); const managedServiceIssues = managedServices.filter((service) => service.requires_human_action || service.status === 'degraded'); const runningManagedServiceCount = managedServices.filter((service) => service.running).length; const planAgents = buildPlanAgents(snapshot); const taskAgents = buildTaskAgents(snapshot); const actionableTaskAgents = taskAgents.filter((agent) => agent.can_retry || agent.can_skip || agent.can_cancel); return { source: 'live', date, employee: { name: state.profile.name, title: '数字员工', major: 'qimingclaw 客户端任务执行', company: state.profile.company, position: state.profile.position, current_status: state.profile.currentStatus, current_status_label: state.profile.currentStatus === 'resting' ? '休息中' : '工作中', motto: '承接客户端任务,调度 ACP 与 MCP 能力完成执行。', }, workday: { confirmed, confirmed_at: confirmedAt, current_status: state.profile.currentStatus, current_status_label: state.profile.currentStatus === 'resting' ? '休息中' : '工作中', status_label: hasLiveRuntime ? (failureCount > 0 || managedServiceIssues.length > 0 ? '有异常' : runningCount > 0 ? '执行中' : '已同步') : confirmed ? '运行中' : '待确认', status_tone: hasLiveRuntime ? (failureCount > 0 || managedServiceIssues.length > 0 ? 'danger' : runningCount > 0 ? 'running' : 'success') : confirmed ? 'running' : 'waiting', summary: hasLiveRuntime ? `当前日期 ${displayDate(date)} 已读取 ${liveRecordCount} 条 qimingclaw 本地运行记录,${managedServiceSummary(managedServices)}。` : confirmed ? `当前日期 ${displayDate(date)} 已确认 ${selectedCount} 项任务。` : `当前日期 ${displayDate(date)} 待确认今日任务。`, bubble_text: hasLiveRuntime ? managedServiceIssues.length > 0 ? `我发现 ${managedServiceIssues.length} 项常驻服务需要处理,会把影响到的任务标出来。` : '我已接入 qimingclaw 本地运行记录,会持续展示真实任务、事件和同步状态。' : confirmed ? '我已接入 qimingclaw 客户端执行链路,正在等待真实 Run/Event 数据接入。' : '请先确认今天要执行的任务,我会把它们映射到 qimingclaw 执行链路。', }, overview: { title: '今日执行', status_label: hasLiveRuntime ? '本地真实记录' : confirmed ? '真实数据' : '待确认', status_tone: failureCount > 0 ? 'danger' : hasLiveRuntime || confirmed ? 'success' : 'waiting', metrics: [ { label: '已选任务', value: String(selectedCount) }, { label: '运行记录', value: String(events.length) }, { label: '执行中', value: String(runningCount) }, { label: '已完成', value: String(successCount) }, { label: '异常', value: String(failureCount) }, { label: '待同步', value: String(sync?.pending ?? 0) }, { label: '同步异常', value: String(sync?.failed ?? 0) }, { label: '服务运行', value: `${runningManagedServiceCount}/${managedServices.length}` }, { label: '需处理服务', value: String(managedServiceIssues.length) }, { label: 'Plan Agent', value: String(planAgents.length) }, { label: 'Task Agent', value: String(taskAgents.length) }, { label: '可治理任务', value: String(actionableTaskAgents.length) }, ], }, managed_services: managedServices, plan_agents: planAgents, task_agents: taskAgents, duties, events, execution_summaries: executionSummaries, run_details: buildRunDetails(events), decisions: buildWorkdayDecisions(state, date, snapshot), delivery: { headline: hasLiveRuntime ? 'qimingclaw 本地记录已接入' : confirmed ? 'qimingclaw 执行链路已接入' : '等待确认任务设置', summary: hasLiveRuntime ? '数字员工页面正在读取本地 Plan / Task / Run / Event,并同步展示管理端 outbox 状态。' : '当前阶段已将数字员工页面切换到 qimingclaw adapter,并开始读取客户端服务状态。', lines: [ '已吸收 sgRobot 数字员工 UI 和核心展示模型。', hasLiveRuntime ? `已读取本地记录:Plan ${runtimePlans(snapshot).length} / Task ${runtimeTasks(snapshot).length} / Run ${runtimeRuns(snapshot).length} / Event ${runtimeEvents(snapshot).length}。` : '已建立 qimingclaw workday/plans/tasks/skills API adapter。', serviceLine(snapshot, 'agent', 'Agent'), serviceLine(snapshot, 'mcp', 'MCP'), serviceLine(snapshot, 'computerServer', 'Computer Server'), managedServiceSummary(managedServices), ...managedServiceIssues.slice(0, 3).map(managedServiceLine), `Plan Agent:${planAgents.length} 个计划代理,Task Agent:${taskAgents.length} 个任务代理。`, actionableTaskAgents[0] ? `待治理任务:${actionableTaskAgents[0].title},下一步 ${actionableTaskAgents[0].next_action}。` : 'Task Agent:暂无需要重试、跳过或取消的任务。', syncLine(snapshot), `活跃会话:${snapshot?.sessions.active ?? 0} / ${snapshot?.sessions.total ?? 0}`, ], artifacts: [ { name: '数字员工运行摘要', status: generatedAt ? 'ready' : 'pending' }, ...artifactSources.slice(0, 4).map((artifact) => ({ name: artifact.name, status: generatedAt ? 'ready' : 'available', uri: artifact.uri ?? null, source_label: artifact.source_label ?? null, source_type: artifact.source_type ?? null, })), ], }, daily_report: { date, title: `${displayDate(date)} 飞天数字员工日报`, report_schedule_time: state.reportScheduleTime, status: generatedAt ? 'ready' : 'not_generated', generated_at: generatedAt, generated_by: generatedAt ? 'ai' : 'fallback', model: 'qimingclaw-adapter', source: 'qimingclaw', summary: generatedAt ? `今日已完成数字员工页面到 qimingclaw adapter 的接入,并读取了 qimingclaw 服务状态。${syncLine(snapshot)}。` : '日报将在生成后汇总任务选择、运行明细和后续接入建议。', totals: { task_count: selectedCount, execution_count: events.length, success_count: successCount, failure_count: failureCount, running_count: runningCount, }, task_sections: executionSummaries, detail_lines: events.map((event) => `${event.time} ${event.task_title}:${event.detail}`), artifact_sources: artifactSources, pdf_available: Boolean(generatedAt), pdf_url: null, }, }; } function reportArtifactSources(artifacts: ArtifactEntry[]) { return artifacts.slice(0, 20).map((artifact) => ({ id: stringValue(artifact.artifact_id) || `${stringValue(artifact.subject_id)}:${stringValue(artifact.uri ?? artifact.name)}`, name: stringValue(artifact.name) || stringValue(artifact.uri) || stringValue(artifact.value) || '未命名产物', kind: stringValue(artifact.kind) || null, uri: stringValue(artifact.uri) || null, source_label: stringValue(artifact.source_label) || null, source_type: stringValue(artifact.source_type) || null, source_event_id: stringValue(artifact.source_event_id) || null, plan_id: stringValue(artifact.plan_id) || null, task_id: stringValue(artifact.task_id) || null, run_id: stringValue(artifact.run_id) || null, created_at: stringValue(artifact.created_at) || null, })); } async function handleWorkdayAction(body: DigitalEmployeeWorkdayActionRequest, snapshot: QimingclawSnapshot | null): Promise { const state = snapshot?.uiState ?? await readBridgeUiState() ?? readState(); const date = body.date || todayInputDate(); if (body.action === 'confirm_workday') { state.selectedDutyIdsByDate[date] = body.duty_ids ?? []; state.confirmedDates[date] = new Date().toISOString(); } if (body.action === 'update_profile') { state.profile = { name: body.name || state.profile.name, company: body.company || state.profile.company, position: body.position || state.profile.position, currentStatus: body.current_status || state.profile.currentStatus, }; } if (body.action === 'update_report_schedule') { state.reportScheduleTime = body.report_schedule_time || state.reportScheduleTime; } if (body.action === 'generate_daily_report') { if (body.duty_ids) state.selectedDutyIdsByDate[date] = body.duty_ids; if (body.report_schedule_time) state.reportScheduleTime = body.report_schedule_time; state.reportGeneratedAtByDate[date] = new Date().toISOString(); } if (body.action === 'decide_approval' && body.decision_id) { state.decisionResultsByDate = state.decisionResultsByDate ?? {}; state.decisionResultsByDate[date] = { ...(state.decisionResultsByDate[date] ?? {}), [body.decision_id]: body.decision || 'resolved', }; } const acpPermissionResult = body.action === 'decide_approval' ? await respondAcpPermissionForDecision(body, snapshot) : null; const savedState = await saveState( state, body.action, date, body.action === 'decide_approval' ? { decision_id: body.decision_id, decision: body.decision, acp_permission: acpPermissionResult, } : undefined, ); return buildWorkday(date, { ...(snapshot ?? { generatedAt: new Date().toISOString(), services: {}, sessions: { total: 0, active: 0, items: [] } }), uiState: savedState }); } async function respondAcpPermissionForDecision( body: DigitalEmployeeWorkdayActionRequest, snapshot: QimingclawSnapshot | null, ): Promise | null> { if (!body.decision_id || !body.decision) return null; const approval = findApprovalForDecision(body.decision_id, snapshot); if (!approval) return null; const refs = acpPermissionRefs(approval); if (!refs) return null; const response = acpResponseForDecision(body.decision); const respondPermission = window.QimingClawBridge?.digital?.respondPermission; if (!respondPermission) return null; try { const result = await respondPermission(refs.sessionId, refs.permissionId, response); return { permission_id: refs.permissionId, session_id: refs.sessionId, response, success: result?.success !== false, error: result?.error, }; } catch (error) { return { permission_id: refs.permissionId, session_id: refs.sessionId, response, success: false, error: error instanceof Error ? error.message : String(error), }; } } function findApprovalForDecision(decisionId: string, snapshot: QimingclawSnapshot | null): ApprovalEntry | null { return buildApprovals(snapshot).find((approval) => stringValue(approval.approval_id) === decisionId) ?? null; } function acpPermissionRefs(approval: ApprovalEntry): { permissionId: string; sessionId: string } | null { const payload = asRecord(approval.payload); const nestedPayload = asRecord(payload?.payload); const permissionId = stringValue(payload?.permissionId) || stringValue(payload?.permission_id) || stringValue(payload?.id) || stringValue(nestedPayload?.permissionId) || stringValue(nestedPayload?.permission_id) || stringValue(nestedPayload?.id); const sessionId = stringValue(payload?.sessionId) || stringValue(payload?.session_id) || stringValue(nestedPayload?.sessionId) || stringValue(nestedPayload?.session_id); const source = stringValue(payload?.source); if (!permissionId || !sessionId || (source && source !== 'acp-permission')) return null; return { permissionId, sessionId }; } function acpResponseForDecision(decision: string): 'once' | 'always' | 'reject' { const normalized = decision.toLowerCase(); if (normalized.includes('always')) return 'always'; if (normalized.includes('reject') || normalized.includes('deny')) return 'reject'; return 'once'; } async function handleGovernanceCommand( body: GovernanceCommandRequest, snapshot: QimingclawSnapshot | null, ): Promise { const commandId = `qimingclaw-digital-${body.command_kind}-${Date.now()}`; const correlationId = body.correlation_id || commandId; const planId = stringFromUnknown(body.context_refs?.plan_id) || stringFromUnknown(body.payload?.plan_id) || stringFromUnknown(body.payload?.original_plan_id); const taskId = stringFromUnknown(body.context_refs?.task_id) || stringFromUnknown(body.payload?.task_id) || stringFromUnknown(body.payload?.original_task_id); const scheduleId = stringFromUnknown(body.context_refs?.schedule_id) || stringFromUnknown(body.payload?.schedule_id); if (body.command_kind === 'create_plan') { const createdPlanId = `qimingclaw-local-${slugifyIdPart(stringFromUnknown(body.payload?.title) || planId || 'plan')}-${Date.now()}`; const response = governanceAccepted(commandId, correlationId, '已在 qimingclaw 兼容层创建本地计划草稿。', { plan_id: createdPlanId, status: 'draft', source: 'qimingclaw-adapter', requested_action: body.payload?.requested_action, original_plan_id: body.payload?.original_plan_id ?? planId, }); await recordGovernanceCommand(body, response); return response; } if (body.command_kind === 'activate_plan' || body.command_kind === 'run_schedule_now') { const targetPlanId = planId || runtimeDebugPlans(snapshot)[0]?.plan_id || templatePlans(snapshot)[0]?.plan_id || 'qimingclaw-client-readiness'; const dispatch = await dispatchPlan(targetPlanId, snapshot, { recordCommand: false }); const response = governanceAccepted(commandId, correlationId, '已映射为 qimingclaw 本地任务激活。', { plan_id: targetPlanId, status: 'active', dispatched_tasks: dispatch.dispatched_tasks, source: 'qimingclaw-adapter', }); await recordGovernanceCommand(body, response); return response; } if (body.command_kind === 'create_schedule') { const response = governanceAccepted(commandId, correlationId, '已记录 qimingclaw 兼容定时任务请求。', { schedule_id: scheduleId || `qimingclaw-schedule-${Date.now()}`, plan_id: planId, status: 'enabled', source: 'qimingclaw-adapter', }); await recordGovernanceCommand(body, response); return response; } if (['update_schedule', 'pause_schedule', 'resume_schedule', 'delete_schedule'].includes(body.command_kind)) { const status = body.command_kind === 'pause_schedule' ? 'paused' : body.command_kind === 'delete_schedule' ? 'deleted' : 'enabled'; const response = governanceAccepted(commandId, correlationId, `已接受 ${body.command_kind},并更新 qimingclaw 本地定时任务状态。`, { schedule_id: scheduleId || `qimingclaw-schedule-${Date.now()}`, plan_id: planId, status, source: 'qimingclaw-adapter', }); await recordGovernanceCommand(body, response); return response; } if (['submit_plan', 'approve_plan', 'amend_plan', 'resume_plan', 'pause_plan', 'cancel_plan'].includes(body.command_kind)) { const response = governanceAccepted(commandId, correlationId, `已接受 ${body.command_kind},当前由 qimingclaw 本地 adapter 记录兼容状态。`, { plan_id: planId, status: body.command_kind === 'approve_plan' ? 'approved' : body.command_kind === 'submit_plan' ? 'reviewing' : 'accepted', source: 'qimingclaw-adapter', }); await recordGovernanceCommand(body, response); return response; } if (['retry_task', 'skip_task', 'pause_task', 'resume_task', 'provide_task_input', 'cancel_task', 'start_run', 'cancel_run'].includes(body.command_kind)) { const targetTask = taskId || runtimeTasks(snapshot)[0]?.id || 'qimingclaw-task-agent'; const response = governanceAccepted(commandId, correlationId, `已接受 ${body.command_kind},并记录到 qimingclaw Task Agent 控制平面。`, { plan_id: planId || runtimeTasks(snapshot).find((task) => task.id === targetTask)?.planId || 'qimingclaw-task-agent-plan', task_id: targetTask, task_agent_id: `task-agent-${slugifyIdPart(targetTask)}`, status: taskGovernanceStatus(body.command_kind), next_action: taskGovernanceNextAction(body.command_kind), source: 'qimingclaw-task-agent', }); await recordGovernanceCommand(body, response); return response; } const response = { command_id: commandId, accepted: false, correlation_id: correlationId, error: 'missing_capability', message: `qimingclaw 数字员工兼容层尚未支持 ${body.command_kind}。`, }; await recordGovernanceCommand(body, response); return response; } function governanceAccepted( commandId: string, correlationId: string, message: string, result: Record, ): GovernanceCommandResponse { return { command_id: commandId, accepted: true, correlation_id: correlationId, message, result, }; } function taskGovernanceStatus(commandKind: string): string { switch (commandKind) { case 'retry_task': case 'start_run': case 'resume_task': return 'running'; case 'skip_task': return 'skipped'; case 'cancel_task': case 'cancel_run': return 'cancelled'; case 'pause_task': return 'paused'; default: return 'accepted'; } } function taskGovernanceNextAction(commandKind: string): string { switch (commandKind) { case 'retry_task': return 'start_retry_run'; case 'skip_task': return 'continue_next_task'; case 'pause_task': return 'await_resume'; case 'resume_task': return 'watch_progress'; case 'provide_task_input': return 'resume_with_input'; case 'cancel_task': case 'cancel_run': return 'close_execution'; case 'start_run': return 'watch_progress'; default: return 'record_control_event'; } } function stringFromUnknown(value: unknown): string { return typeof value === 'string' && value.trim() ? value.trim() : ''; } function buildPlanFlow(planId: string, snapshot: QimingclawSnapshot | null = null): FlowResponse { if (hasRuntimeRecords(snapshot)) { const plan = runtimePlans(snapshot).find((candidate) => candidate.id === planId); const tasks = buildTasks(planId, snapshot); const planSteps = runtimeStepsForPlan(snapshot, planId); const runs = buildTasksResponse(planId, snapshot).runs; const events = runtimeEvents(snapshot).filter((event) => event.planId === planId || tasks.some((task) => task.task_id === event.taskId)); const title = plan?.title ?? tasks[0]?.title ?? planId; const objective = plan?.objective ?? 'qimingclaw 本地运行记录'; const qimingclawFlowSteps: FlowResponse['steps'] = []; let nextSeq = 2; if (planSteps.length > 0) { const knownStepIds = new Set(planSteps.map((step) => step.id)); for (const step of planSteps) { qimingclawFlowSteps.push({ seq: nextSeq++, lane: 'qimingclaw', label: step.title, sub_label: `${taskStatusLabel(step.status)} · 依赖 ${step.dependsOn.length} · 技能 ${step.preferredSkillIds.length}`, timestamp: step.updatedAt, color: '#22c55e', source_type: 'plan_step', }); for (const task of tasks.filter((candidate) => candidate.step_id === step.id)) { qimingclawFlowSteps.push(runtimeTaskFlowStep(task, nextSeq++)); } } const orphanTasks = tasks.filter((task) => !knownStepIds.has(task.step_id || '')); if (orphanTasks.length > 0) { qimingclawFlowSteps.push({ seq: nextSeq++, lane: 'qimingclaw', label: '未归属步骤', sub_label: `包含 ${orphanTasks.length} 个任务`, timestamp: orphanTasks[0]?.updated_at ?? new Date().toISOString(), color: '#22c55e', source_type: 'plan_step', }); for (const task of orphanTasks) qimingclawFlowSteps.push(runtimeTaskFlowStep(task, nextSeq++)); } } else { for (const task of tasks) qimingclawFlowSteps.push(runtimeTaskFlowStep(task, nextSeq++)); } return { plan_id: planId, plan_title: title, plan_status: plan ? planRecordStatus(plan, runtimePlanTasks(snapshot, plan.id)) : tasks[0]?.status, plan_objective: objective, lanes: [ { key: 'intent', label: '目标', color: '#38bdf8' }, { key: 'qimingclaw', label: 'qimingclaw', color: '#22c55e' }, { key: 'execution', label: '执行', color: '#f59e0b' }, ], steps: [ { seq: 1, lane: 'intent', label: title, sub_label: objective, timestamp: plan?.createdAt ?? tasks[0]?.created_at ?? new Date().toISOString(), color: '#38bdf8', source_type: 'plan', }, ...qimingclawFlowSteps, ...runs.map((run, index) => ({ seq: nextSeq + index, lane: 'execution', label: run.lifecycle || 'Run', sub_label: run.run_id, timestamp: run.completed_at ?? run.created_at, color: '#f59e0b', source_type: 'run', })), ], events: events.map((event) => ({ event_id: event.id, kind: event.kind, occurred_at: event.occurredAt, message: event.message, })), }; } const plan = withRuntimePlan(templatePlans(snapshot).find((candidate) => candidate.plan_id === planId) ?? templatePlans(snapshot)[0]!, snapshot); const timestamp = new Date().toISOString(); return { plan_id: plan.plan_id, plan_title: plan.title, plan_status: plan.status, plan_objective: plan.objective, lanes: [ { key: 'intent', label: '目标', color: '#38bdf8' }, { key: 'qimingclaw', label: 'qimingclaw', color: '#22c55e' }, { key: 'execution', label: '执行', color: '#f59e0b' }, ], steps: [ { seq: 1, lane: 'intent', label: plan.title, sub_label: plan.objective, timestamp: plan.created_at, color: '#38bdf8', source_type: 'plan', }, { seq: 2, lane: 'qimingclaw', label: '接入客户端能力', sub_label: 'ACP / MCP / computer server adapter', timestamp, color: '#22c55e', source_type: 'adapter', }, { seq: 3, lane: 'execution', label: plan.status === 'running' ? '已读取 qimingclaw 服务状态' : '等待调度', sub_label: planResultText(plan, snapshot), timestamp, color: '#f59e0b', source_type: 'run', }, ], events: [ { event_id: `${plan.plan_id}-adapter-event`, kind: 'qimingclaw_adapter', occurred_at: timestamp, message: '数字员工页面已通过 qimingclaw adapter 获取数据。', }, ], }; } function runtimeTaskFlowStep(task: DebugTask, seq: number): FlowResponse['steps'][number] { return { seq, lane: 'qimingclaw', label: task.title, sub_label: `${taskStatusLabel(task.status)} · ${task.assigned_skill_id || 'qimingclaw-runtime'}`, timestamp: task.updated_at, color: '#22c55e', source_type: 'task', }; } function buildTaskFlow(taskId: string, snapshot: QimingclawSnapshot | null = null): FlowResponse { const task = runtimeTasks(snapshot).find((candidate) => candidate.id === taskId); if (!task) { return buildPlanFlow(taskId, snapshot); } const plan = task.planId ? runtimePlans(snapshot).find((candidate) => candidate.id === task.planId) : null; const runs = runtimeRuns(snapshot).filter((run) => run.taskId === task.id); const events = runtimeEvents(snapshot).filter((event) => event.taskId === task.id || runs.some((run) => run.id === event.runId)); return { plan_id: task.planId ?? task.id, plan_title: plan?.title ?? task.title, plan_status: task.status, plan_objective: plan?.objective ?? 'qimingclaw 本地任务记录', lanes: [ { key: 'task', label: '任务', color: '#38bdf8' }, { key: 'execution', label: '执行', color: '#22c55e' }, { key: 'event', label: '事件', color: '#f59e0b' }, ], steps: [ { seq: 1, lane: 'task', label: task.title, sub_label: `${taskStatusLabel(task.status)} · ${task.assignedSkillId || 'qimingclaw-runtime'}`, timestamp: task.updatedAt, color: '#38bdf8', source_type: 'task', }, ...runs.map((run, index) => ({ seq: index + 2, lane: 'execution', label: run.status || 'Run', sub_label: run.id, timestamp: run.finishedAt ?? run.startedAt ?? run.createdAt, color: '#22c55e', source_type: 'run', })), ...events.map((event, index) => ({ seq: runs.length + index + 2, lane: 'event', label: event.kind, sub_label: event.message, timestamp: event.occurredAt, color: '#f59e0b', source_type: 'event', })), ], events: events.map((event) => ({ event_id: event.id, kind: event.kind, occurred_at: event.occurredAt, message: event.message, })), }; } async function dispatchPlan( planId: string, snapshot: QimingclawSnapshot | null = null, options: { recordCommand?: boolean } = {}, ): Promise { const state = snapshot?.uiState ?? await readBridgeUiState() ?? readState(); const date = todayInputDate(); const plan = mergedDebugPlans(snapshot).find((candidate) => candidate.plan_id === planId); const activatedAt = Date.now(); const correlationId = `dispatch-${planId}-${activatedAt}`; const selected = new Set(state.selectedDutyIdsByDate[date] ?? []); selected.add(planId); state.selectedDutyIdsByDate[date] = [...selected]; state.confirmedDates[date] = state.confirmedDates[date] ?? new Date().toISOString(); await saveState(state, 'confirm_workday', date); const dispatchedTasks = buildTasks(planId, snapshot).map((task) => ({ task_id: task.task_id, title: task.title, status: task.status === 'pending' ? 'running' : task.status, })); if (options.recordCommand !== false) { await recordGovernanceCommand( { command_kind: 'activate_plan', context_refs: { plan_id: planId }, payload: { plan_id: planId, title: plan?.title, objective: plan?.objective, requested_action: 'dispatch_plan', }, correlation_id: correlationId, }, governanceAccepted( `qimingclaw-digital-dispatch-${slugifyIdPart(planId)}-${activatedAt}`, correlationId, '已将数字员工模板执行写入 qimingclaw 本地运行记录。', { plan_id: planId, status: 'active', dispatched_tasks: dispatchedTasks, source: 'qimingclaw-adapter', }, ), ); } return { ok: true, plan_id: planId, source_plan_id: planId, dispatched_tasks: dispatchedTasks, }; } function buildPlanMessages(planId: string, snapshot: QimingclawSnapshot | null = null): Array<{ role: string; content: string; created_at?: string | null }> { const runtimePlan = runtimePlans(snapshot).find((candidate) => candidate.id === planId); if (runtimePlan) { const events = runtimeEvents(snapshot).filter((event) => event.planId === planId).slice(0, 12); return [ { role: 'user', content: `查看 qimingclaw 本地任务:${runtimePlan.title}`, created_at: runtimePlan.createdAt, }, ...events.map((event) => ({ role: 'assistant', content: `${event.kind}:${event.message}`, created_at: event.occurredAt, })), ]; } const plan = templatePlans(snapshot).find((candidate) => candidate.plan_id === planId) ?? templatePlans(snapshot)[0]!; return [ { role: 'user', content: `执行数字员工任务:${plan.title}`, created_at: plan.created_at, }, { role: 'assistant', content: `已接入 qimingclaw adapter。目标:${plan.objective ?? plan.title}`, created_at: new Date().toISOString(), }, ]; }