1- import crypto from "node:crypto" ;
21import fs from "node:fs" ;
32import path from "node:path" ;
43import { resolveSessionAgentId } from "../../agents/agent-scope.js" ;
@@ -10,8 +9,8 @@ import {
109 shouldRefreshSnapshotForVersion ,
1110} from "../../agents/skills/refresh-state.js" ;
1211import { ensureSkillsWatcher } from "../../agents/skills/refresh.js" ;
12+ import { fingerprintSkillSnapshotConfig } from "../../agents/skills/snapshot-fingerprint.js" ;
1313import { hydrateResolvedSkills } from "../../agents/skills/snapshot-hydration.js" ;
14- import { stableStringify } from "../../agents/stable-stringify.js" ;
1514import {
1615 resolveSessionFilePath ,
1716 resolveSessionFilePathOptions ,
@@ -42,71 +41,6 @@ export function __testing_resetResolvedSkillsCache(): void {
4241 resolvedSkillsCache . clear ( ) ;
4342}
4443
45- function isSensitiveConfigKey ( key : string ) : boolean {
46- const normalized = key . toLowerCase ( ) . replaceAll ( / [ ^ a - z 0 - 9 ] / g, "" ) ;
47- return (
48- normalized . endsWith ( "apikey" ) ||
49- normalized . endsWith ( "token" ) ||
50- normalized . endsWith ( "secret" ) ||
51- normalized . endsWith ( "password" ) ||
52- normalized . endsWith ( "privatekey" ) ||
53- normalized . endsWith ( "clientsecret" )
54- ) ;
55- }
56-
57- function redactSensitiveConfigValue ( value : unknown ) : unknown {
58- if ( value === undefined || value === null || value === false || value === "" ) {
59- return value ;
60- }
61- if ( typeof value === "string" ) {
62- return value . trim ( ) ? "[redacted:string]" : "" ;
63- }
64- if ( typeof value === "number" ) {
65- return Number . isFinite ( value ) && value !== 0 ? "[redacted:number]" : value ;
66- }
67- if ( typeof value === "boolean" ) {
68- return value ;
69- }
70- if ( Array . isArray ( value ) ) {
71- return value . length === 0 ? [ ] : "[redacted:array]" ;
72- }
73- return "[redacted:object]" ;
74- }
75-
76- function redactConfigForSkillSnapshotCache ( value : unknown , stack = new WeakSet < object > ( ) ) : unknown {
77- if ( ! value || typeof value !== "object" ) {
78- return value ;
79- }
80- if ( stack . has ( value ) ) {
81- return "[Circular]" ;
82- }
83- stack . add ( value ) ;
84- try {
85- if ( Array . isArray ( value ) ) {
86- return value . map ( ( entry ) => redactConfigForSkillSnapshotCache ( entry , stack ) ) ;
87- }
88- const redacted : Record < string , unknown > = { } ;
89- for ( const key of Object . keys ( value as Record < string , unknown > ) . toSorted ( ) ) {
90- const field = ( value as Record < string , unknown > ) [ key ] ;
91- redacted [ key ] = isSensitiveConfigKey ( key )
92- ? redactSensitiveConfigValue ( field )
93- : redactConfigForSkillSnapshotCache ( field , stack ) ;
94- }
95- return redacted ;
96- } finally {
97- stack . delete ( value ) ;
98- }
99- }
100-
101- // Skill frontmatter `requires.config` reads the full OpenClaw config, so cache
102- // reuse must follow the same boundary without putting raw secrets in Map keys.
103- function fingerprintSkillSnapshotConfig ( config : OpenClawConfig ) : string {
104- return crypto
105- . createHash ( "sha256" )
106- . update ( stableStringify ( redactConfigForSkillSnapshotCache ( config ) ) )
107- . digest ( "hex" ) ;
108- }
109-
11044function cacheResolvedSkills ( cacheKey : string , snapshot : SkillSnapshot ) : SkillSnapshot {
11145 resolvedSkillsCache . set ( cacheKey , snapshot . resolvedSkills ) ;
11246 if ( resolvedSkillsCache . size > RESOLVED_SKILLS_CACHE_MAX ) {
@@ -261,9 +195,11 @@ export async function ensureSkillSnapshot(params: {
261195 const snapshotVersion = getSkillsSnapshotVersion ( workspaceDir ) ;
262196 const existingSnapshot = nextEntry ?. skillsSnapshot ;
263197 ensureSkillsWatcher ( { workspaceDir, config : cfg } ) ;
198+ const configFingerprint = fingerprintSkillSnapshotConfig ( cfg ) ;
264199 const shouldRefreshSnapshot =
265200 shouldRefreshSnapshotForVersion ( existingSnapshot ?. version , snapshotVersion ) ||
266- ! matchesSkillFilter ( existingSnapshot ?. skillFilter , skillFilter ) ;
201+ ! matchesSkillFilter ( existingSnapshot ?. skillFilter , skillFilter ) ||
202+ existingSnapshot ?. configFingerprint !== configFingerprint ;
267203 const buildSnapshot = ( ) => {
268204 return buildWorkspaceSkillSnapshot ( workspaceDir , {
269205 config : cfg ,
@@ -274,7 +210,6 @@ export async function ensureSkillSnapshot(params: {
274210 } ) ;
275211 } ;
276212
277- const configFingerprint = fingerprintSkillSnapshotConfig ( cfg ) ;
278213 const snapshotCacheKey = JSON . stringify ( [
279214 workspaceDir ,
280215 snapshotVersion ,
0 commit comments