feat(plugin-sdk): utilize details prop for richer o11y on waiting-deps

This commit is contained in:
Makito
2026-03-01 01:19:46 +07:00
parent b630db0860
commit ab22af5841
2 changed files with 55 additions and 1 deletions
@@ -1,6 +1,7 @@
import { join } from 'node:path'
import { createContext, defineEventa, defineInvokeHandler } from '@moeru/eventa'
import { moduleStatus } from '@proj-airi/plugin-protocol/types'
import { describe, expect, it, vi } from 'vitest'
import { FileSystemLoader, PluginHost } from '.'
@@ -268,6 +269,50 @@ describe('for PluginHost', () => {
expect(session.phase).toBe('ready')
})
it('should emit dependency wait details while waiting for required capabilities', async () => {
const host = new PluginHost({
runtime: 'electron',
transport: { kind: 'in-memory' },
})
const session = await host.load(testManifest, { cwd: '' })
const statusEvents: Array<{ body?: Record<string, unknown> }> = []
session.channels.host.on(moduleStatus, (payload) => {
statusEvents.push(payload as unknown as { body?: Record<string, unknown> })
})
const started = host.init(session.id, {
requiredCapabilities: ['cap:custom'],
capabilityWaitTimeoutMs: 2000,
})
await new Promise(resolve => setTimeout(resolve, 20))
const waitingStatus = statusEvents.find((event) => {
const body = event.body
return body?.phase === 'preparing' && typeof body.reason === 'string' && body.reason.includes('Waiting for capabilities:')
})
expect(waitingStatus).toBeDefined()
expect(waitingStatus?.body).toMatchObject({
phase: 'preparing',
details: {
lifecyclePhase: 'waiting-deps',
requiredCapabilities: ['cap:custom'],
unresolvedCapabilities: ['cap:custom'],
timeoutMs: 2000,
},
})
reportPluginCapability(host, {
key: 'cap:custom',
state: 'ready',
metadata: { source: 'test' },
})
const initialized = await started
expect(initialized.phase).toBe('ready')
})
it('should fail when required capabilities timeout', async () => {
const host = new PluginHost({
runtime: 'electron',
+10 -1
View File
@@ -635,14 +635,23 @@ export class PluginHost {
// Optional dependency gate before plugin-owned initialization.
if (options.requiredCapabilities?.length) {
const capabilityTimeoutMs = options.capabilityWaitTimeoutMs ?? 15000
const unresolvedCapabilities = options.requiredCapabilities.filter(key => !this.isCapabilityReady(key))
assertTransition(session, 'waiting-deps')
session.channels.host.emit(moduleStatus, {
identity: session.identity,
phase: 'preparing',
reason: `Waiting for capabilities: ${options.requiredCapabilities.join(', ')}`,
details: {
// For richer observability
lifecyclePhase: 'waiting-deps',
requiredCapabilities: options.requiredCapabilities,
unresolvedCapabilities,
timeoutMs: capabilityTimeoutMs,
},
})
await this.waitForCapabilities(options.requiredCapabilities, options.capabilityWaitTimeoutMs)
await this.waitForCapabilities(options.requiredCapabilities, capabilityTimeoutMs)
assertTransition(session, 'prepared')
session.channels.host.emit(modulePrepared, {
identity: session.identity,