Merge pull request 'feat(gateway): P5-001 plugin host module' (#92) from feat/p5-plugin-host into main
Some checks failed
ci/woodpecker/push/ci Pipeline failed

This commit was merged in pull request #92.
This commit is contained in:
2026-03-13 17:47:59 +00:00
8 changed files with 148 additions and 1 deletions

View File

@@ -18,6 +18,7 @@
"@mosaic/brain": "workspace:^", "@mosaic/brain": "workspace:^",
"@mosaic/coord": "workspace:^", "@mosaic/coord": "workspace:^",
"@mosaic/db": "workspace:^", "@mosaic/db": "workspace:^",
"@mosaic/discord-plugin": "workspace:^",
"@mosaic/log": "workspace:^", "@mosaic/log": "workspace:^",
"@mosaic/memory": "workspace:^", "@mosaic/memory": "workspace:^",
"@mosaic/types": "workspace:^", "@mosaic/types": "workspace:^",

View File

@@ -13,6 +13,7 @@ import { CoordModule } from './coord/coord.module.js';
import { MemoryModule } from './memory/memory.module.js'; import { MemoryModule } from './memory/memory.module.js';
import { LogModule } from './log/log.module.js'; import { LogModule } from './log/log.module.js';
import { SkillsModule } from './skills/skills.module.js'; import { SkillsModule } from './skills/skills.module.js';
import { PluginModule } from './plugin/plugin.module.js';
@Module({ @Module({
imports: [ imports: [
@@ -29,6 +30,7 @@ import { SkillsModule } from './skills/skills.module.js';
MemoryModule, MemoryModule,
LogModule, LogModule,
SkillsModule, SkillsModule,
PluginModule,
], ],
controllers: [HealthController], controllers: [HealthController],
}) })

View File

@@ -0,0 +1,5 @@
export interface IChannelPlugin {
readonly name: string;
start(): Promise<void>;
stop(): Promise<void>;
}

View File

@@ -0,0 +1,90 @@
import {
Global,
Inject,
Logger,
Module,
type OnModuleDestroy,
type OnModuleInit,
} from '@nestjs/common';
import { DiscordPlugin } from '@mosaic/discord-plugin';
import { PluginService } from './plugin.service.js';
import type { IChannelPlugin } from './plugin.interface.js';
export const PLUGIN_REGISTRY = Symbol('PLUGIN_REGISTRY');
class DiscordChannelPluginAdapter implements IChannelPlugin {
readonly name = 'discord';
constructor(private readonly plugin: DiscordPlugin) {}
async start(): Promise<void> {
await this.plugin.start();
}
async stop(): Promise<void> {
await this.plugin.stop();
}
}
const DEFAULT_GATEWAY_URL = 'http://localhost:4000';
function createPluginRegistry(logger: Logger): IChannelPlugin[] {
const plugins: IChannelPlugin[] = [];
const discordToken = process.env['DISCORD_BOT_TOKEN'];
const discordGuildId = process.env['DISCORD_GUILD_ID'];
const discordGatewayUrl = process.env['DISCORD_GATEWAY_URL'] ?? DEFAULT_GATEWAY_URL;
if (discordToken) {
plugins.push(
new DiscordChannelPluginAdapter(
new DiscordPlugin({
token: discordToken,
guildId: discordGuildId,
gatewayUrl: discordGatewayUrl,
}),
),
);
}
const telegramToken = process.env['TELEGRAM_BOT_TOKEN'];
const telegramGatewayUrl = process.env['TELEGRAM_GATEWAY_URL'] ?? DEFAULT_GATEWAY_URL;
if (telegramToken) {
logger.warn(
`Telegram plugin requested for ${telegramGatewayUrl}, but @mosaic/telegram-plugin is not implemented yet.`,
);
}
return plugins;
}
@Global()
@Module({
providers: [
{
provide: PLUGIN_REGISTRY,
useFactory: (): IChannelPlugin[] => createPluginRegistry(new Logger('PluginModule')),
},
PluginService,
],
exports: [PluginService, PLUGIN_REGISTRY],
})
export class PluginModule implements OnModuleInit, OnModuleDestroy {
private readonly logger = new Logger(PluginModule.name);
constructor(@Inject(PLUGIN_REGISTRY) private readonly plugins: IChannelPlugin[]) {}
async onModuleInit(): Promise<void> {
for (const plugin of this.plugins) {
this.logger.log(`Starting plugin: ${plugin.name}`);
await plugin.start();
}
}
async onModuleDestroy(): Promise<void> {
for (const plugin of [...this.plugins].reverse()) {
this.logger.log(`Stopping plugin: ${plugin.name}`);
await plugin.stop();
}
}
}

View File

@@ -0,0 +1,16 @@
import { Inject, Injectable } from '@nestjs/common';
import { PLUGIN_REGISTRY } from './plugin.module.js';
import type { IChannelPlugin } from './plugin.interface.js';
@Injectable()
export class PluginService {
constructor(@Inject(PLUGIN_REGISTRY) private readonly plugins: IChannelPlugin[]) {}
getPlugins(): IChannelPlugin[] {
return this.plugins;
}
getPlugin(name: string): IChannelPlugin | undefined {
return this.plugins.find((plugin: IChannelPlugin) => plugin.name === name);
}
}

View File

@@ -0,0 +1,30 @@
# Scratchpad — P5-001 Plugin Host
- Task: P5-001 / issue #41
- Branch: feat/p5-plugin-host
- Objective: add global NestJS plugin host module, wire Discord import, register active plugins from env, and attach to AppModule.
- TDD: skipped as optional for module wiring/integration work; relying on targeted typecheck/lint and module-level situational verification.
- Constraints: ESM .js imports, explicit @Inject(), follow existing gateway patterns, do not touch TASKS.md.
## Progress Log
- 2026-03-13: session started in worktree; loading gateway/plugin package context.
- 2026-03-13: implemented initial plugin module, service, interface, and AppModule wiring; pending verification.
- 2026-03-13: added `@mosaic/discord-plugin` as a gateway workspace dependency and regenerated `pnpm-lock.yaml`.
- 2026-03-13: built gateway dependency chain so workspace packages exported `dist/*` for clean TypeScript resolution in this fresh worktree.
- 2026-03-13: verification complete.
## Verification
- `pnpm --filter @mosaic/gateway... build`
- `pnpm --filter @mosaic/gateway typecheck`
- `pnpm --filter @mosaic/gateway lint`
- `pnpm format:check`
- `pnpm typecheck`
- `pnpm lint`
## Review
- Automated review: `~/.config/mosaic/tools/codex/codex-code-review.sh --uncommitted`
- Manual review: diff inspection of gateway plugin host changes
- Result: no blocker findings

3
pnpm-lock.yaml generated
View File

@@ -59,6 +59,9 @@ importers:
'@mosaic/db': '@mosaic/db':
specifier: workspace:^ specifier: workspace:^
version: link:../../packages/db version: link:../../packages/db
'@mosaic/discord-plugin':
specifier: workspace:^
version: link:../../plugins/discord
'@mosaic/log': '@mosaic/log':
specifier: workspace:^ specifier: workspace:^
version: link:../../packages/log version: link:../../packages/log

View File

@@ -11,7 +11,7 @@
"dependsOn": ["^lint"] "dependsOn": ["^lint"]
}, },
"typecheck": { "typecheck": {
"dependsOn": ["^typecheck"] "dependsOn": ["^build"]
}, },
"test": { "test": {
"dependsOn": ["^build"], "dependsOn": ["^build"],