@@ -3,6 +3,7 @@ import { note } from "../../packages/terminal-core/src/note.js";
33import { resolveAgentWorkspaceDir , resolveDefaultAgentId } from "../agents/agent-scope.js" ;
44import { formatCliCommand } from "../cli/command-format.js" ;
55import type { OpenClawConfig } from "../config/types.openclaw.js" ;
6+ import type { HealthFinding } from "../flows/health-checks.js" ;
67import {
78 resolvePluginVersionDriftUpdateCommand ,
89 type PluginVersionDriftReport ,
@@ -19,37 +20,50 @@ type NoteWorkspaceStatusOptions = {
1920 pluginVersionDrift ?: PluginVersionDriftReport ;
2021} ;
2122
22- function noteFlowRecoveryHints ( ) {
23- const suspicious = listTaskFlowRecords ( ) . flatMap ( ( flow ) => {
23+ const WORKSPACE_STATUS_CHECK_ID = "core/doctor/workspace-status" ;
24+
25+ type TaskFlowRecoveryFinding = {
26+ flowId : string ;
27+ message : string ;
28+ } ;
29+
30+ function collectTaskFlowRecoveryFindings ( ) : TaskFlowRecoveryFinding [ ] {
31+ return listTaskFlowRecords ( ) . flatMap ( ( flow ) => {
2432 const tasks = listTasksForFlowId ( flow . flowId ) ;
25- const findings : string [ ] = [ ] ;
33+ const findings : TaskFlowRecoveryFinding [ ] = [ ] ;
2634 if (
2735 flow . syncMode === "managed" &&
2836 flow . status === "running" &&
2937 tasks . length === 0 &&
3038 flow . waitJson === undefined
3139 ) {
32- findings . push (
33- `${ flow . flowId } : running managed TaskFlow has no linked tasks or wait state; inspect or cancel it manually.` ,
34- ) ;
40+ findings . push ( {
41+ flowId : flow . flowId ,
42+ message : `${ flow . flowId } : running managed TaskFlow has no linked tasks or wait state; inspect or cancel it manually.` ,
43+ } ) ;
3544 }
3645 if (
3746 flow . status === "blocked" &&
3847 flow . blockedTaskId &&
3948 ! tasks . some ( ( task ) => task . taskId === flow . blockedTaskId )
4049 ) {
41- findings . push (
42- `${ flow . flowId } : blocked TaskFlow points at missing task ${ flow . blockedTaskId } ; inspect before retrying.` ,
43- ) ;
50+ findings . push ( {
51+ flowId : flow . flowId ,
52+ message : `${ flow . flowId } : blocked TaskFlow points at missing task ${ flow . blockedTaskId } ; inspect before retrying.` ,
53+ } ) ;
4454 }
4555 return findings ;
4656 } ) ;
57+ }
58+
59+ function noteFlowRecoveryHints ( ) {
60+ const suspicious = collectTaskFlowRecoveryFindings ( ) ;
4761 if ( suspicious . length === 0 ) {
4862 return ;
4963 }
5064 note (
5165 [
52- ...suspicious . slice ( 0 , 5 ) ,
66+ ...suspicious . slice ( 0 , 5 ) . map ( ( finding ) => finding . message ) ,
5367 suspicious . length > 5 ? `...and ${ suspicious . length - 5 } more.` : null ,
5468 `Inspect: ${ formatCliCommand ( "openclaw tasks flow show <flow-id>" ) } ` ,
5569 `Cancel: ${ formatCliCommand ( "openclaw tasks flow cancel <flow-id>" ) } ` ,
@@ -60,6 +74,89 @@ function noteFlowRecoveryHints() {
6074 ) ;
6175}
6276
77+ function pluginVersionDriftToHealthFindings (
78+ drift : PluginVersionDriftReport | undefined ,
79+ ) : HealthFinding [ ] {
80+ if ( ! drift || drift . drifts . length === 0 ) {
81+ return [ ] ;
82+ }
83+ return drift . drifts . map ( ( entry ) => {
84+ const updateCommand = formatCliCommand ( resolvePluginVersionDriftUpdateCommand ( entry ) ) ;
85+ return {
86+ checkId : WORKSPACE_STATUS_CHECK_ID ,
87+ severity : "warning" ,
88+ message : `Plugin ${ entry . pluginId } is ${ entry . installedVersion } , but the Gateway is ${ drift . gatewayVersion } .` ,
89+ path : `plugins.entries.${ entry . pluginId } ` ,
90+ target : entry . pluginId ,
91+ requirement : "plugin-version-drift" ,
92+ fixHint : `${ updateCommand } && ${ formatCliCommand ( "openclaw gateway restart" ) } ` ,
93+ } ;
94+ } ) ;
95+ }
96+
97+ function pluginCompatibilityWarningToHealthFinding ( message : string ) : HealthFinding {
98+ return {
99+ checkId : WORKSPACE_STATUS_CHECK_ID ,
100+ severity : "warning" ,
101+ message,
102+ path : "plugins" ,
103+ requirement : "plugin-compatibility" ,
104+ fixHint : "Update or replace the plugin so it no longer depends on legacy compatibility paths." ,
105+ } ;
106+ }
107+
108+ function pluginDiagnosticToHealthFinding (
109+ diagnostic : ReturnType < typeof buildPluginRegistrySnapshotReport > [ "diagnostics" ] [ number ] ,
110+ ) : HealthFinding {
111+ return {
112+ checkId : WORKSPACE_STATUS_CHECK_ID ,
113+ severity : diagnostic . level === "error" ? "error" : "warning" ,
114+ message : diagnostic . message ,
115+ ...( diagnostic . pluginId ? { path : `plugins.entries.${ diagnostic . pluginId } ` } : { } ) ,
116+ ...( diagnostic . pluginId ? { target : diagnostic . pluginId } : { } ) ,
117+ ...( diagnostic . source ? { source : diagnostic . source } : { } ) ,
118+ ...( diagnostic . code ? { requirement : diagnostic . code } : { requirement : "plugin-diagnostic" } ) ,
119+ } ;
120+ }
121+
122+ function taskFlowRecoveryToHealthFinding ( finding : TaskFlowRecoveryFinding ) : HealthFinding {
123+ return {
124+ checkId : WORKSPACE_STATUS_CHECK_ID ,
125+ severity : "warning" ,
126+ message : finding . message ,
127+ path : "tasks.flows" ,
128+ target : finding . flowId ,
129+ requirement : "taskflow-recovery" ,
130+ fixHint : [
131+ formatCliCommand ( `openclaw tasks flow show ${ finding . flowId } ` ) ,
132+ formatCliCommand ( `openclaw tasks flow cancel ${ finding . flowId } ` ) ,
133+ ] . join ( " or " ) ,
134+ } ;
135+ }
136+
137+ export function collectWorkspaceStatusHealthFindings (
138+ cfg : OpenClawConfig ,
139+ options : NoteWorkspaceStatusOptions = { } ,
140+ ) : HealthFinding [ ] {
141+ const workspaceDir = resolveAgentWorkspaceDir ( cfg , resolveDefaultAgentId ( cfg ) ) ;
142+ const pluginRegistry = buildPluginRegistrySnapshotReport ( {
143+ config : cfg ,
144+ workspaceDir,
145+ } ) ;
146+ const compatibilityWarnings = buildPluginCompatibilityWarnings ( {
147+ config : cfg ,
148+ workspaceDir,
149+ report : pluginRegistry ,
150+ } ) ;
151+
152+ return [
153+ ...pluginVersionDriftToHealthFindings ( options . pluginVersionDrift ) ,
154+ ...compatibilityWarnings . map ( pluginCompatibilityWarningToHealthFinding ) ,
155+ ...pluginRegistry . diagnostics . map ( pluginDiagnosticToHealthFinding ) ,
156+ ...collectTaskFlowRecoveryFindings ( ) . map ( taskFlowRecoveryToHealthFinding ) ,
157+ ] ;
158+ }
159+
63160function notePluginVersionDrift ( drift : PluginVersionDriftReport | undefined ) {
64161 if ( ! drift || drift . drifts . length === 0 ) {
65162 return ;
0 commit comments