Skip to content

Commit b083c75

Browse files
Copilothotlong
andauthored
fix: pre-inject core service fallbacks before Phase 2 so ctx.getService('metadata') succeeds during plugin starts
Agent-Logs-Url: https://github.com/objectstack-ai/framework/sessions/f9c31840-b5a5-498e-85f1-f42b38e65983 Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
1 parent 5dcf58b commit b083c75

File tree

2 files changed

+43
-6
lines changed

2 files changed

+43
-6
lines changed

CHANGELOG.md

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -72,12 +72,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7272

7373
### Fixed
7474
- **AI Chat agent selector missing `data_chat` and `metadata_assistant`** — Fixed `GET /api/v1/ai/agents`
75-
returning 404, which caused the Studio AI Chat panel to show only "General Chat". Root cause was a
76-
redundant second `ctx.getService('metadata')` call inside `AIServicePlugin.start()` that shadowed
77-
the outer resolved variable and failed silently, preventing `buildAgentRoutes()` from being called.
78-
Fix: reuse the already-resolved `metadataService` variable instead of re-fetching it. Additionally,
79-
added a fallback in `DispatcherPlugin.start()` that recovers AI routes from the `kernel.__aiRoutes`
80-
cache in case the `ai:routes` hook fires before the listener is registered (timing edge case).
75+
returning 404, which caused the Studio AI Chat panel to show only "General Chat". There were two
76+
root causes addressed by this fix:
77+
1. **Kernel bootstrap timing** (`packages/core/src/kernel.ts`): 'core' service in-memory fallbacks
78+
(e.g. the 'metadata' service) were only injected in `validateSystemRequirements()` which runs
79+
AFTER all plugin `start()` methods execute. This meant `ctx.getService('metadata')` always threw
80+
during `AIServicePlugin.start()` when no explicit `MetadataPlugin` was loaded. Fix: added
81+
`preInjectCoreFallbacks()` called between Phase 1 (init) and Phase 2 (start), ensuring all core
82+
service fallbacks are available before any plugin's `start()` runs.
83+
2. **Shadowed variable** (`packages/services/service-ai/src/plugin.ts`): a redundant second
84+
`ctx.getService('metadata')` call declared a new `const metadataService` that shadowed the outer
85+
`let metadataService` and failed silently, preventing `buildAgentRoutes()` from being called even
86+
if the metadata service was available. Fix: reuse the already-resolved outer variable.
87+
Additionally, added a fallback in `DispatcherPlugin.start()` that recovers AI routes from the
88+
`kernel.__aiRoutes` cache in case the `ai:routes` hook fires before the listener is registered
89+
(timing edge case).
8190
- **ObjectQLPlugin: cold-start metadata restoration**`ObjectQLPlugin.start()` now calls
8291
`protocol.loadMetaFromDb()` after driver initialization and before schema sync, restoring
8392
all persisted metadata (objects, views, apps, etc.) from the `sys_metadata` table into the

packages/core/src/kernel.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,28 @@ export class ObjectKernel {
213213
return this;
214214
}
215215

216+
/**
217+
* Pre-inject in-memory fallbacks for 'core' services that were not registered
218+
* by plugins during Phase 1. Called before Phase 2 so that all core services
219+
* (e.g. 'metadata', 'cache', 'queue') are resolvable via ctx.getService()
220+
* when plugin start() methods execute.
221+
*/
222+
private preInjectCoreFallbacks() {
223+
if (this.config.skipSystemValidation) return;
224+
for (const [serviceName, criticality] of Object.entries(ServiceRequirementDef)) {
225+
if (criticality !== 'core') continue;
226+
const hasService = this.services.has(serviceName) || this.pluginLoader.hasService(serviceName);
227+
if (!hasService) {
228+
const factory = CORE_FALLBACK_FACTORIES[serviceName];
229+
if (factory) {
230+
const fallback = factory();
231+
this.registerService(serviceName, fallback);
232+
this.logger.debug(`[Kernel] Pre-injected in-memory fallback for '${serviceName}' before Phase 2`);
233+
}
234+
}
235+
}
236+
}
237+
216238
/**
217239
* Validate Critical System Requirements
218240
*/
@@ -291,6 +313,12 @@ export class ObjectKernel {
291313
await this.initPluginWithTimeout(plugin);
292314
}
293315

316+
// Pre-inject in-memory fallbacks for 'core' services that were not
317+
// registered by any plugin during Phase 1. This ensures services like
318+
// 'metadata', 'cache', 'queue', etc. are always available when plugins
319+
// call ctx.getService() during their start() methods.
320+
this.preInjectCoreFallbacks();
321+
294322
// Phase 2: Start - Plugins execute business logic
295323
this.logger.info('Phase 2: Start plugins');
296324
this.state = 'running';

0 commit comments

Comments
 (0)