11import fs from "node:fs/promises" ;
22import path from "node:path" ;
33import { beforeAll , beforeEach , describe , expect , it , vi } from "vitest" ;
4+ import { resolveInstalledPluginIndexPolicyHash } from "../plugins/installed-plugin-index-policy.js" ;
5+ import type { PluginMetadataSnapshot } from "../plugins/plugin-metadata-snapshot.js" ;
46import {
57 CUSTOM_PROXY_MODELS_CONFIG ,
68 installModelsConfigTestHooks ,
@@ -13,15 +15,63 @@ const planOpenClawModelsJsonMock = vi.fn();
1315installModelsConfigTestHooks ( ) ;
1416
1517let ensureOpenClawModelsJson : typeof import ( "./models-config.js" ) . ensureOpenClawModelsJson ;
18+ let clearCurrentPluginMetadataSnapshot : typeof import ( "../plugins/current-plugin-metadata-snapshot.js" ) . clearCurrentPluginMetadataSnapshot ;
19+ let setCurrentPluginMetadataSnapshot : typeof import ( "../plugins/current-plugin-metadata-snapshot.js" ) . setCurrentPluginMetadataSnapshot ;
20+
21+ function createPluginMetadataSnapshot ( workspaceDir : string ) : PluginMetadataSnapshot {
22+ const policyHash = resolveInstalledPluginIndexPolicyHash ( { } ) ;
23+ return {
24+ policyHash,
25+ workspaceDir,
26+ index : {
27+ version : 1 ,
28+ hostContractVersion : "test" ,
29+ compatRegistryVersion : "test" ,
30+ migrationVersion : 1 ,
31+ policyHash,
32+ generatedAtMs : 1 ,
33+ installRecords : { } ,
34+ plugins : [ ] ,
35+ diagnostics : [ ] ,
36+ } ,
37+ registryDiagnostics : [ ] ,
38+ manifestRegistry : { plugins : [ ] , diagnostics : [ ] } ,
39+ plugins : [ ] ,
40+ diagnostics : [ ] ,
41+ byPluginId : new Map ( ) ,
42+ normalizePluginId : ( pluginId ) => pluginId ,
43+ owners : {
44+ channels : new Map ( ) ,
45+ channelConfigs : new Map ( ) ,
46+ providers : new Map ( ) ,
47+ modelCatalogProviders : new Map ( ) ,
48+ cliBackends : new Map ( ) ,
49+ setupProviders : new Map ( ) ,
50+ commandAliases : new Map ( ) ,
51+ contracts : new Map ( ) ,
52+ } ,
53+ metrics : {
54+ registrySnapshotMs : 0 ,
55+ manifestRegistryMs : 0 ,
56+ ownerMapsMs : 0 ,
57+ totalMs : 0 ,
58+ indexPluginCount : 0 ,
59+ manifestPluginCount : 0 ,
60+ } ,
61+ } ;
62+ }
1663
1764beforeAll ( async ( ) => {
1865 vi . doMock ( "./models-config.plan.js" , ( ) => ( {
1966 planOpenClawModelsJson : ( ...args : unknown [ ] ) => planOpenClawModelsJsonMock ( ...args ) ,
2067 } ) ) ;
2168 ( { ensureOpenClawModelsJson } = await import ( "./models-config.js" ) ) ;
69+ ( { clearCurrentPluginMetadataSnapshot, setCurrentPluginMetadataSnapshot } =
70+ await import ( "../plugins/current-plugin-metadata-snapshot.js" ) ) ;
2271} ) ;
2372
2473beforeEach ( ( ) => {
74+ clearCurrentPluginMetadataSnapshot ( ) ;
2575 planOpenClawModelsJsonMock
2676 . mockReset ( )
2777 . mockImplementation ( async ( params : { cfg ?: typeof CUSTOM_PROXY_MODELS_CONFIG } ) => ( {
@@ -31,6 +81,38 @@ beforeEach(() => {
3181} ) ;
3282
3383describe ( "models-config write serialization" , ( ) => {
84+ it ( "does not reuse default workspace plugin metadata for explicit agent dirs without workspace" , async ( ) => {
85+ await withModelsTempHome ( async ( home ) => {
86+ const snapshot = createPluginMetadataSnapshot ( path . join ( home , "default-workspace" ) ) ;
87+ setCurrentPluginMetadataSnapshot ( snapshot , { config : { } } ) ;
88+ const agentDir = path . join ( home , "agent-non-default" ) ;
89+
90+ await ensureOpenClawModelsJson ( { } , agentDir ) ;
91+
92+ expect ( planOpenClawModelsJsonMock ) . toHaveBeenCalledWith (
93+ expect . not . objectContaining ( { pluginMetadataSnapshot : snapshot } ) ,
94+ ) ;
95+ } ) ;
96+ } ) ;
97+
98+ it ( "reuses current plugin metadata for explicit agent dirs with matching workspace" , async ( ) => {
99+ await withModelsTempHome ( async ( home ) => {
100+ const workspaceDir = path . join ( home , "agent-workspace" ) ;
101+ const snapshot = createPluginMetadataSnapshot ( workspaceDir ) ;
102+ setCurrentPluginMetadataSnapshot ( snapshot , { config : { } } ) ;
103+ const agentDir = path . join ( home , "agent-non-default" ) ;
104+
105+ await ensureOpenClawModelsJson ( { } , agentDir , { workspaceDir } ) ;
106+
107+ expect ( planOpenClawModelsJsonMock ) . toHaveBeenCalledWith (
108+ expect . objectContaining ( {
109+ workspaceDir,
110+ pluginMetadataSnapshot : snapshot ,
111+ } ) ,
112+ ) ;
113+ } ) ;
114+ } ) ;
115+
34116 it ( "serializes concurrent models.json writes to avoid overlap" , async ( ) => {
35117 await withModelsTempHome ( async ( ) => {
36118 const first = structuredClone ( CUSTOM_PROXY_MODELS_CONFIG ) ;
0 commit comments