@@ -17,6 +17,11 @@ import type {
declare global {
interface Window {
__QIMINGCLAW_EMBEDDED_DIGITAL__? : boolean ;
QimingClawBridge ? : {
digital ? : {
getSnapshot ? : ( ) = > Promise < QimingclawSnapshot > ;
} ;
} ;
}
}
@@ -36,6 +41,25 @@ interface AdapterState {
} ;
}
interface QimingclawServiceStatus {
running? : boolean ;
error? : string ;
engineType? : string ;
port? : number ;
serverCount? : number ;
serverNames? : string [ ] ;
}
interface QimingclawSnapshot {
generatedAt : string ;
services : Record < string , QimingclawServiceStatus | null > ;
sessions : {
total : number ;
active : number ;
items : Array < { id : string ; title? : string ; status? : string ; engineType? : string ; projectId? : string } > ;
} ;
}
const SKILLS : SkillSpec [ ] = [
{
skill_id : 'qimingclaw-acp-session' ,
@@ -180,23 +204,23 @@ export async function handleQimingclawDigitalApi<T>(
const pathname = url . pathname ;
if ( method === 'GET' && pathname === '/api/digital-employee/workday' ) {
return buildWorkday ( url . searchParams . get ( 'date' ) || todayInputDate ( ) ) as T ;
return buildWorkday ( url . searchParams . get ( 'date' ) || todayInputDate ( ) , await readQimingclawSnapshot ( ) ) as T ;
}
if ( method === 'POST' && pathname === '/api/digital-employee/workday/actions' ) {
return handleWorkdayAction ( parseJsonBody < DigitalEmployeeWorkdayActionRequest > ( options . body ) ) as T ;
return handleWorkdayAction ( parseJsonBody < DigitalEmployeeWorkdayActionRequest > ( options . body ) , await readQimingclawSnapshot ( ) ) as T ;
}
if ( method === 'GET' && pathname === '/api/scheduler/jobs' ) {
return { jobs : buildSchedulerJobs ( ) } as T ;
}
if ( method === 'GET' && pathname === '/api/debug/plans' ) {
return { plans : filterPlans ( url . searchParams . get ( 'q' ) ) } as T ;
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 ) as T ;
return buildTasksResponse ( planId , await readQimingclawSnapshot ( ) ) as T ;
}
if ( method === 'GET' && pathname . startsWith ( '/api/debug/plan/' ) && pathname . endsWith ( '/flow' ) ) {
return buildPlanFlow ( decodeURIComponent ( pathname . split ( '/' ) [ 4 ] || '' ) ) as T ;
return buildPlanFlow ( decodeURIComponent ( pathname . split ( '/' ) [ 4 ] || '' ) , await readQimingclawSnapshot ( ) ) as T ;
}
if ( method === 'POST' && pathname . startsWith ( '/api/debug/plan/' ) && pathname . endsWith ( '/dispatch' ) ) {
return dispatchPlan ( decodeURIComponent ( pathname . split ( '/' ) [ 4 ] || '' ) ) as T ;
@@ -244,6 +268,14 @@ function writeState(state: AdapterState): void {
window . localStorage . setItem ( STATE_KEY , JSON . stringify ( state ) ) ;
}
async function readQimingclawSnapshot ( ) : Promise < QimingclawSnapshot | null > {
try {
return await window . QimingClawBridge ? . digital ? . getSnapshot ? . ( ) ? ? null ;
} catch {
return null ;
}
}
function parseJsonBody < T > ( body : BodyInit | null | undefined ) : T {
if ( typeof body !== 'string' || ! body . trim ( ) ) return { } as T ;
return JSON . parse ( body ) as T ;
@@ -279,6 +311,43 @@ function planStatusTone(status: string): string {
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 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 '已完成' ;
@@ -313,9 +382,10 @@ function buildSchedulerJobs(): CronJob[] {
} ) ;
}
function buildDuties ( state : AdapterState , date : string ) : DigitalEmployeeDuty [ ] {
function buildDuties ( state : AdapterState , date : string , snapshot : QimingclawSnapshot | null ): DigitalEmployeeDuty [ ] {
const selected = selectedSetForDate ( state , date ) ;
return PLANS . map ( ( p lan) = > {
return PLANS . map ( ( rawP lan) = > {
const plan = withRuntimePlan ( rawPlan , snapshot ) ;
const schedule = PLAN_SCHEDULES [ plan . plan_id ] ? ? PLAN_SCHEDULES [ 'qimingclaw-client-readiness' ] ! ;
return {
id : plan.plan_id ,
@@ -328,19 +398,38 @@ function buildDuties(state: AdapterState, date: string): DigitalEmployeeDuty[] {
status_label : planStatusLabel ( plan . status ) ,
status_tone : planStatusTone ( plan . status ) ,
schedule_text : schedule.scheduleText ,
result_text : plan.status === 'running' ? '正在同步客户端运行状态' : '等待任务执行' ,
result_text : planResultText ( plan , snapshot ) ,
conversation_id : plan.plan_id ,
} ;
} ) ;
}
function buildTasks ( planId? : string | null ) : DebugTask [ ] {
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 [ ] {
return PLANS
. filter ( ( plan ) = > ! planId || plan . plan_id === planId )
. map ( ( plan ) = > withRuntimePlan ( plan , snapshot ) )
. flatMap ( ( plan ) = > ( plan . steps ? ? [ ] ) . flatMap ( ( step ) = > ( step . tasks ? ? [ ] ) . map ( ( task ) = > ( {
task_id : task.task_id ,
title : task.title ,
status : task. status,
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 ] ? ? '' ,
@@ -375,22 +464,23 @@ function buildRuns(tasks: DebugTask[]): DebugRun[] {
} ) ) ;
}
function buildTasksResponse ( planId? : string | null ) : { tasks : DebugTask [ ] ; runs : DebugRun [ ] ; total : number ; truncated : boolean } {
const tasks = buildTasks ( planId ) ;
function buildTasksResponse ( planId? : string | null , snapshot : QimingclawSnapshot | null = null ): { tasks : DebugTask [ ] ; runs : DebugRun [ ] ; total : number ; truncated : boolean } {
const tasks = buildTasks ( planId , snapshot );
return { tasks , runs : buildRuns ( tasks ) , total : tasks.length , truncated : false } ;
}
function filterPlans ( query? : string | null ) : DebugPlan [ ] {
function filterPlans ( query? : string | null , snapshot : QimingclawSnapshot | null = null ): DebugPlan [ ] {
const normalized = ( query ? ? '' ) . trim ( ) . toLowerCase ( ) ;
if ( ! normalized ) return PLANS ;
return PLANS . filter ( ( plan ) = > (
const plans = PLANS . map ( ( plan ) = > withRuntimePlan ( plan , 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 buildEvents ( duties : DigitalEmployeeDuty [ ] , confirmed : boolean ) : DigitalEmployeeWorkEvent [ ] {
function buildEvents ( duties : DigitalEmployeeDuty [ ] , confirmed : boolean , snapshot : QimingclawSnapshot | null ): DigitalEmployeeWorkEvent [ ] {
if ( ! confirmed ) {
return [ {
id : 'event-await-confirmation' ,
@@ -408,18 +498,18 @@ function buildEvents(duties: DigitalEmployeeDuty[], confirmed: boolean): Digital
}
const selectedDuties = duties . filter ( ( duty ) = > duty . selected ) ;
return selectedDuties . map ( ( duty , index ) = > ( {
return selectedDuties . map ( ( duty ) = > ( {
id : ` event- ${ duty . id } ` ,
time : index === 0 ? '09:05 ' : '待执行' ,
time : duty.status === 'running' ? '实时 ' : '待执行' ,
plan_title : duty.title ,
task_title : duty.title ,
step_title : index === 0 ? '接入 qimingclaw 执行链路 ' : '等待调度' ,
detail : index === 0
? '已接入 qimingclaw 客户端服务状态与执行入口,等待后续绑定真实 Run 事件。'
step_title : duty.status === 'running' ? '同步 qimingclaw 服务状态 ' : '等待调度' ,
detail : duty.status === 'running'
? ` ${ duty . result_text } 。快照时间: ${ snapshot ? . generatedAt ? ? '未连接 bridge' } `
: '任务已确认,将在后续阶段接入 ACP/MCP 真实执行事件。' ,
status : index === 0 ? 'running' : 'pending' ,
status_label : index === 0 ? '执行中' : '待执行' ,
status_tone : index === 0 ? 'running' : 'waiting' ,
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 ,
} ) ) ;
@@ -465,12 +555,12 @@ function buildRunDetails(events: DigitalEmployeeWorkEvent[]): DigitalEmployeeRun
} ) ) ;
}
function buildWorkday ( date : string ) : DigitalEmployeeWorkdayProjection {
function buildWorkday ( date : string , snapshot : QimingclawSnapshot | null ): DigitalEmployeeWorkdayProjection {
const state = readState ( ) ;
const confirmedAt = state . confirmedDates [ date ] ? ? null ;
const confirmed = Boolean ( confirmedAt ) ;
const duties = buildDuties ( state , date ) ;
const events = buildEvents ( duties , confirmed ) ;
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 ;
@@ -521,11 +611,14 @@ function buildWorkday(date: string): DigitalEmployeeWorkdayProjection {
decisions : [ ] ,
delivery : {
headline : confirmed ? 'qimingclaw 执行链路已接入' : '等待确认任务设置' ,
summary : '当前阶段已将数字员工页面切换到 qimingclaw adapter, 下一阶段接入真实 ACP/MCP Run 事件 。' ,
summary : '当前阶段已将数字员工页面切换到 qimingclaw adapter, 并开始读取客户端服务状态 。' ,
lines : [
'已吸收 sgRobot 数字员工 UI 和核心展示模型。' ,
'已建立 qimingclaw workday/plans/tasks/skills API adapter。' ,
'后续将把客户端服务状态、会话和执行事件写入真实状态模型。' ,
serviceLine ( snapshot , 'agent' , 'Agent' ) ,
serviceLine ( snapshot , 'mcp' , 'MCP' ) ,
serviceLine ( snapshot , 'computerServer' , 'Computer Server' ) ,
` 活跃会话: ${ snapshot ? . sessions . active ? ? 0 } / ${ snapshot ? . sessions . total ? ? 0 } ` ,
] ,
artifacts : [
{ name : '数字员工运行摘要' , status : generatedAt ? 'ready' : 'pending' } ,
@@ -541,7 +634,7 @@ function buildWorkday(date: string): DigitalEmployeeWorkdayProjection {
model : 'qimingclaw-adapter' ,
source : 'qimingclaw' ,
summary : generatedAt
? '今日已完成数字员工页面到 qimingclaw adapter 的接入,可继续接入真实执行事件 。'
? '今日已完成数字员工页面到 qimingclaw adapter 的接入,并读取了 qimingclaw 服务状态 。'
: '日报将在生成后汇总任务选择、运行明细和后续接入建议。' ,
totals : {
task_count : selectedCount ,
@@ -558,7 +651,7 @@ function buildWorkday(date: string): DigitalEmployeeWorkdayProjection {
} ;
}
function handleWorkdayAction ( body : DigitalEmployeeWorkdayActionRequest ) : DigitalEmployeeWorkdayProjection {
function handleWorkdayAction ( body : DigitalEmployeeWorkdayActionRequest , snapshot : QimingclawSnapshot | null ): DigitalEmployeeWorkdayProjection {
const state = readState ( ) ;
const date = body . date || todayInputDate ( ) ;
@@ -584,11 +677,11 @@ function handleWorkdayAction(body: DigitalEmployeeWorkdayActionRequest): Digital
}
writeState ( state ) ;
return buildWorkday ( date ) ;
return buildWorkday ( date , snapshot );
}
function buildPlanFlow ( planId : string ) : FlowResponse {
const plan = PLANS . find ( ( candidate ) = > candidate . plan_id === planId ) ? ? PLANS [ 0 ] ! ;
function buildPlanFlow ( planId : string , snapshot : QimingclawSnapshot | null = null ): FlowResponse {
const plan = withRuntimePlan ( PLANS. find ( ( candidate ) = > candidate . plan_id === planId ) ? ? PLANS [ 0 ] ! , snapshot ) ;
const timestamp = new Date ( ) . toISOString ( ) ;
return {
plan_id : plan.plan_id ,
@@ -622,8 +715,8 @@ function buildPlanFlow(planId: string): FlowResponse {
{
seq : 3 ,
lane : 'execution' ,
label : plan.status === 'running' ? '等待真实 Run 事件 ' : '等待调度' ,
sub_label : '后续阶段接入 qimingclaw 本地状态模型' ,
label : plan.status === 'running' ? '已读取 qimingclaw 服务状态 ' : '等待调度' ,
sub_label : planResultText ( plan , snapshot ) ,
timestamp ,
color : '#f59e0b' ,
source_type : 'run' ,