Fix AI chat object visibility by bridging SchemaRegistry to metadata service#1092
Fix AI chat object visibility by bridging SchemaRegistry to metadata service#1092
Conversation
…sibility Agent-Logs-Url: https://github.com/objectstack-ai/framework/sessions/9cc89a9f-9105-4283-9392-af18ad52aa5b Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
@claude[agent] 测试下来还是不能,你能自己启动服务,测试tool吗? |
…DB-loaded ones Agent-Logs-Url: https://github.com/objectstack-ai/framework/sessions/9416386f-16ce-4a3e-83b8-b2c80c7cc6d4 Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
I found and fixed the issue! The problem was that my original fix only bridged objects loaded from Root cause: Plugins register objects via Fix in commit 862ab8e:
The bridging now happens at the end of You should now see objects when testing the |
There was a problem hiding this comment.
Pull request overview
This PR fixes Studio AI chat reporting zero objects by ensuring all ObjectQL SchemaRegistry objects (from sys_metadata DB restore and plugin registrations) are visible through IMetadataService, which the AI tools rely on.
Changes:
- Adds a startup bridge that registers missing
SchemaRegistryobjects into the metadata service after schema sync. - Updates DB restore logging to clarify that restoration hydrates
SchemaRegistry(not the metadata service).
| const existing = await metadataService.getObject(obj.name); | ||
| if (!existing) { | ||
| // Register object that exists in SchemaRegistry but not in metadata service | ||
| await metadataService.register('object', obj.name, obj); | ||
| bridged++; |
There was a problem hiding this comment.
bridgeObjectsToMetadataService() uses metadataService.register('object', ...) which (per MetadataManager.register) persists to any write-capable 'datasource:' loader (DatabaseLoader/sys_metadata). This means bridging can inadvertently persist plugin/system objects into sys_metadata, potentially freezing their schemas and causing upgrade drift when the plugin later changes the object definition. Consider adding/using a non-persisting registration path (e.g., a registerRuntime/registerInMemory API or a persist:false option) so the bridge only affects runtime visibility in IMetadataService without writing to database loaders.
| const objects = this.ql.registry.getAllObjects(); | ||
| let bridged = 0; | ||
|
|
||
| for (const obj of objects) { | ||
| try { | ||
| // Check if object is already in metadata service to avoid duplicates | ||
| const existing = await metadataService.getObject(obj.name); | ||
| if (!existing) { | ||
| // Register object that exists in SchemaRegistry but not in metadata service | ||
| await metadataService.register('object', obj.name, obj); | ||
| bridged++; | ||
| } | ||
| } catch (e: unknown) { | ||
| ctx.logger.debug('Failed to bridge object to metadata service', { | ||
| object: obj.name, | ||
| error: e instanceof Error ? e.message : String(e), | ||
| }); | ||
| } | ||
| } | ||
|
|
There was a problem hiding this comment.
The bridge currently does an N+1 sequence of async calls (await getObject for every SchemaRegistry object, then possibly await register). On instances with many objects (and especially if register writes to a DB loader), this can significantly slow startup. Prefer a bulk prefetch (e.g., listNames('object')/listObjects()) to build a Set of existing names, then only register the missing ones; also consider bounded concurrency for registrations.
| try { | ||
| const metadataService = ctx.getService<any>('metadata'); | ||
| if (!metadataService || typeof metadataService.register !== 'function') { | ||
| ctx.logger.debug('Metadata service unavailable for bridging, skipping'); | ||
| return; | ||
| } |
There was a problem hiding this comment.
Type safety is lost by using ctx.getService('metadata') and then duck-typing method presence. Since IMetadataService is already defined in @objectstack/spec/contracts (and used elsewhere via ctx.getService('metadata')), consider typing this as IMetadataService and validating both getObject and register up front to avoid per-object failures/log spam when a partial implementation is provided.
| // Bridge all SchemaRegistry objects to metadata service | ||
| // This ensures AI tools and other IMetadataService consumers can see all objects | ||
| await this.bridgeObjectsToMetadataService(ctx); | ||
|
|
There was a problem hiding this comment.
New behavior (bridging SchemaRegistry objects into IMetadataService on startup) isn’t covered by tests. Since packages/objectql has integration tests for plugin startup ordering and metadata restoration, please add coverage asserting that: (1) plugin-registered and DB-restored objects become visible via metadataService.listObjects(), and (2) bridging is a no-op when metadata service is unavailable.
AI chat in Studio reported zero objects despite system objects existing in the kernel. The root cause: objects in SchemaRegistry (from both
sys_metadatadatabase and plugin registrations) were invisible toIMetadataService.listObjects(), which the AI tools query.Changes
ObjectQLPlugin.bridgeObjectsToMetadataService() - New dedicated method that bridges ALL SchemaRegistry objects:
syncRegisteredSchemas()at the end ofObjectQLPlugin.start()getAllObjects()metadataService.getObject()to prevent duplicatesmetadataService.register('object', ...)ObjectQLPlugin.restoreMetadataFromDb() - Simplified to only handle DB restoration:
loaded > 0sys_metadatainto SchemaRegistryArchitecture
Two separate registries existed:
sys_metadatadatabase records + plugin-registered objects.ts/.yamldefinitionsAI tools query the metadata service, which had no visibility into SchemaRegistry objects. The bridge ensures all sources are queryable via
IMetadataService.Key Fix
The original implementation only bridged objects when they were loaded from the database (
if (loaded > 0)), missing objects registered by plugins duringinit()phase (likesys_user,sys_session, etc.). The new approach bridges ALL objects regardless of source.Gracefully degrades if metadata service unavailable. Execution order ensures filesystem objects load first (MetadataPlugin.start), then all SchemaRegistry objects bridge (ObjectQLPlugin.start).