plugin-agent-orchestrator 1.0.14 → 1.0.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/README.md +96 -0
  2. package/dist/externalVersion.js +6 -6
  3. package/dist/server/plugin.js +11 -1
  4. package/dist/server/services/CodeValidator.js +1 -0
  5. package/dist/server/services/SkillManager.js +0 -39
  6. package/dist/server/skill-hub/plugin.js +3 -3
  7. package/dist/server/skill-hub/tasks/SkillExecutionTask.d.ts +2 -0
  8. package/dist/server/skill-hub/tasks/SkillExecutionTask.js +122 -0
  9. package/dist/server/tools/delegate-task.js +22 -2
  10. package/dist/server/tools/external-rag-search.d.ts +42 -0
  11. package/dist/server/tools/external-rag-search.js +140 -0
  12. package/dist/server/tools/skill-execute.d.ts +1 -1
  13. package/dist/server/tools/skill-execute.js +1 -2
  14. package/package.json +1 -1
  15. package/src/client/index.tsx +1 -1
  16. package/src/client/plugin.tsx +54 -54
  17. package/src/client/skill-hub/index.tsx +75 -75
  18. package/src/server/migrations/20260423000000-add-progress-fields.ts +5 -5
  19. package/src/server/migrations/20260425000000-add-interaction-schema.ts +5 -5
  20. package/src/server/migrations/20260427000000-add-tracing-detail-fields.ts +5 -5
  21. package/src/server/migrations/20260427000000-change-packages-to-text.ts +7 -7
  22. package/src/server/migrations/20260427000001-change-other-json-to-text.ts +10 -10
  23. package/src/server/migrations/20260429000000-add-llm-fields.ts +2 -2
  24. package/src/server/migrations/20260429000000-fix-inputargs-json-to-text.ts +2 -2
  25. package/src/server/migrations/20260503000000-add-orchestrator-trace-fields.ts +2 -2
  26. package/src/server/plugin.ts +23 -13
  27. package/src/server/services/CodeValidator.ts +5 -5
  28. package/src/server/services/SkillManager.ts +12 -52
  29. package/src/server/services/WorkerEnvManager.ts +5 -5
  30. package/src/server/skill-hub/plugin.ts +61 -61
  31. package/src/server/skill-hub/tasks/SkillExecutionTask.ts +162 -16
  32. package/src/server/tools/delegate-task.ts +25 -1
  33. package/src/server/tools/external-rag-search.ts +128 -0
  34. package/src/server/tools/skill-execute.ts +1 -2
@@ -1,5 +1,5 @@
1
- import { Database } from '@nocobase/database';
2
- import { stringifyJsonText } from '../skill-hub/utils/json-fields';
1
+ import { Database } from '@nocobase/database';
2
+ import { stringifyJsonText } from '../skill-hub/utils/json-fields';
3
3
 
4
4
  export class SkillManager {
5
5
  constructor(private db: Database) {}
@@ -203,16 +203,16 @@ export class SkillManager {
203
203
 
204
204
  for (const seed of seeds) {
205
205
  try {
206
- const count = await repo.count({ filter: { name: seed.name } });
207
- if (count === 0) {
208
- await repo.create({
209
- values: {
210
- ...seed,
211
- inputSchema: stringifyJsonText(seed.inputSchema),
212
- packages: stringifyJsonText(seed.packages, []),
213
- },
214
- });
215
- }
206
+ const count = await repo.count({ filter: { name: seed.name } });
207
+ if (count === 0) {
208
+ await repo.create({
209
+ values: {
210
+ ...seed,
211
+ inputSchema: stringifyJsonText(seed.inputSchema),
212
+ packages: stringifyJsonText(seed.packages, []),
213
+ },
214
+ });
215
+ }
216
216
  } catch (err) {
217
217
  console.error(`[import-skill] Failed to insert ${seed.name}:`, err);
218
218
  // Skip if already exists (unique constraint on name)
@@ -330,46 +330,6 @@ doc.build(story)
330
330
  print('Generated: report.pdf')
331
331
  `;
332
332
 
333
- const SEED_PPTX = `import os, json
334
- from pptx import Presentation
335
- from pptx.util import Inches, Pt
336
- from pptx.enum.text import PP_ALIGN
337
-
338
- title = json.loads('''{{title}}''') if '{{title}}'.startswith('"') else '{{title}}'
339
- subtitle_raw = '''{{subtitle}}'''
340
- subtitle = json.loads(subtitle_raw) if subtitle_raw.startswith('"') else subtitle_raw if subtitle_raw != '{{' + 'subtitle}}' else ''
341
- slides_data = json.loads('''{{slides}}''')
342
-
343
- prs = Presentation()
344
- prs.slide_width = Inches(13.333)
345
- prs.slide_height = Inches(7.5)
346
-
347
- # Title slide
348
- slide = prs.slides.add_slide(prs.slide_layouts[0])
349
- slide.shapes.title.text = title
350
- if subtitle and slide.placeholders[1]:
351
- slide.placeholders[1].text = subtitle
352
-
353
- # Content slides
354
- for s in slides_data:
355
- slide = prs.slides.add_slide(prs.slide_layouts[1])
356
- slide.shapes.title.text = s.get('title', '')
357
- body = slide.placeholders[1].text_frame
358
- body.clear()
359
- for i, bullet in enumerate(s.get('bullets', [])):
360
- if i == 0:
361
- body.paragraphs[0].text = bullet
362
- else:
363
- p = body.add_paragraph()
364
- p.text = bullet
365
- body.paragraphs[-1].font.size = Pt(18)
366
-
367
- output_dir = os.environ.get('OUTPUT_DIR', '/output')
368
- filepath = os.path.join(output_dir, 'presentation.pptx')
369
- prs.save(filepath)
370
- print('Generated: presentation.pptx')
371
- `;
372
-
373
333
  const SEED_CHART = `import os, json
374
334
  import matplotlib
375
335
  matplotlib.use('Agg')
@@ -42,7 +42,7 @@ export class WorkerEnvManager {
42
42
  ) {}
43
43
 
44
44
  async getOrCreateConfig() {
45
- const repo = this.db.getRepository('skillWorkerConfigs');
45
+ const repo = (this as any).db.getRepository('skillWorkerConfigs');
46
46
  let config = await repo.findOne();
47
47
  if (config) return config;
48
48
 
@@ -72,7 +72,7 @@ export class WorkerEnvManager {
72
72
  }),
73
73
  });
74
74
 
75
- await this.app.pubSubManager.publish('skill-hub.init-env', {
75
+ await (this as any).app.pubSubManager.publish('skill-hub.init-env', {
76
76
  ...config,
77
77
  storagePath: this.storagePath,
78
78
  queuedAt: new Date().toISOString(),
@@ -89,17 +89,17 @@ export class WorkerEnvManager {
89
89
  apt: Array.from(new Set([...(DEFAULT_WHITELIST.apt || []), ...(customPackages.apt || [])])),
90
90
  };
91
91
 
92
- await this.app.pubSubManager.publish('skill-hub.init-env.progress', {
92
+ await (this as any).app.pubSubManager.publish('skill-hub.init-env.progress', {
93
93
  percent: 25,
94
94
  log: 'Resolved sandbox package whitelist',
95
95
  });
96
96
 
97
- await this.app.pubSubManager.publish('skill-hub.init-env.progress', {
97
+ await (this as any).app.pubSubManager.publish('skill-hub.init-env.progress', {
98
98
  percent: 75,
99
99
  log: 'Sandbox runtime uses the local worker environment',
100
100
  });
101
101
 
102
- await this.app.pubSubManager.publish('skill-hub.init-env.done', {
102
+ await (this as any).app.pubSubManager.publish('skill-hub.init-env.done', {
103
103
  status: 'succeeded',
104
104
  log:
105
105
  'Sandbox environment is ready on this worker. Package installation is managed by the worker image/runtime; whitelist was refreshed.',
@@ -95,14 +95,14 @@ export class SkillHubSubFeature {
95
95
  // 2. Init services
96
96
  const storagePath = resolve(process.cwd(), 'storage', 'plugin-skill-hub'); // Keep old storage path for backwards compatibility
97
97
  this.fileManager = new FileManager(storagePath);
98
- this.sandboxRunner = new SandboxRunner(this.fileManager, this.app.logger, storagePath);
99
- this.skillManager = new SkillManager(this.db);
100
- this.workerEnvManager = new WorkerEnvManager(this.app, this.db, storagePath);
98
+ this.sandboxRunner = new SandboxRunner(this.fileManager, (this as any).app.logger, storagePath);
99
+ this.skillManager = new SkillManager((this as any).db);
100
+ this.workerEnvManager = new WorkerEnvManager((this as any).app, (this as any).db, storagePath);
101
101
  this.skillRepoService = new SkillRepositoryService(storagePath);
102
102
  this.mcpController = new McpController(this);
103
103
 
104
104
  // 3. Register REST actions
105
- this.app.resourceManager.define({
105
+ (this as any).app.resourceManager.define({
106
106
  name: 'skillHub',
107
107
  actions: {
108
108
  download: this.handleDownload.bind(this),
@@ -119,7 +119,7 @@ export class SkillHubSubFeature {
119
119
 
120
120
 
121
121
  // 4.5. Register DB hooks for automatic storage physical cleanup
122
- this.db.on('skillExecutions.afterDestroy', async (model, options) => {
122
+ (this as any).db.on('skillExecutions.afterDestroy', async (model, options) => {
123
123
  const execId = model.get('id');
124
124
  try {
125
125
  const dir = this.fileManager.getExecDir(String(execId));
@@ -127,29 +127,29 @@ export class SkillHubSubFeature {
127
127
  require('fs').rmSync(dir, { recursive: true, force: true });
128
128
  }
129
129
  } catch (err) {
130
- this.app.logger.error(`[skill-hub] Failed to cleanup physical storage for execId ${execId}`, { error: err });
130
+ (this as any).app.logger.error(`[skill-hub] Failed to cleanup physical storage for execId ${execId}`, { error: err });
131
131
  }
132
132
  });
133
133
 
134
- this.db.on('skillDefinitions.afterSave', async (model, options) => {
134
+ (this as any).db.on('skillDefinitions.afterSave', async (model, options) => {
135
135
  // If a zip file was uploaded, extract it and update the skill record
136
136
  if (model.changed('fileId') && model.get('fileId')) {
137
137
  try {
138
- const attachment = await this.db.getRepository('attachments').findOne({
138
+ const attachment = await (this as any).db.getRepository('attachments').findOne({
139
139
  filter: { id: model.get('fileId') },
140
140
  transaction: options.transaction,
141
141
  });
142
142
 
143
143
  if (attachment) {
144
- const fileManager = this.app.pm.get('@nocobase/plugin-file-manager') as any;
144
+ const fileManager = (this as any).app.pm.get('@nocobase/plugin-file-manager') as any;
145
145
  if (!fileManager) {
146
- this.app.logger.warn('[skill-hub] plugin-file-manager not found, cannot extract skill package');
146
+ (this as any).app.logger.warn('[skill-hub] plugin-file-manager not found, cannot extract skill package');
147
147
  return;
148
148
  }
149
149
 
150
150
  const streamData = await fileManager.getFileStream(attachment);
151
151
  if (!streamData || !streamData.stream) {
152
- this.app.logger.warn(`[skill-hub] Could not get file stream for attachment ${attachment.get('id')}`);
152
+ (this as any).app.logger.warn(`[skill-hub] Could not get file stream for attachment ${attachment.get('id')}`);
153
153
  return;
154
154
  }
155
155
 
@@ -179,54 +179,54 @@ export class SkillHubSubFeature {
179
179
  if (metadata.timeoutSeconds) updateValues.timeoutSeconds = metadata.timeoutSeconds;
180
180
  if (instructions) updateValues.instructions = instructions;
181
181
 
182
- await this.db.getRepository('skillDefinitions').update({
182
+ await (this as any).db.getRepository('skillDefinitions').update({
183
183
  filter: { id: model.get('id') },
184
184
  values: updateValues,
185
185
  transaction: options.transaction,
186
186
  });
187
187
 
188
188
  unlinkSync(tempZipPath);
189
- this.app.logger.info(`[skill-hub] Successfully extracted zip and updated skill: ${skillName}`);
189
+ (this as any).app.logger.info(`[skill-hub] Successfully extracted zip and updated skill: ${skillName}`);
190
190
  }
191
191
  }
192
192
  } catch (err) {
193
- this.app.logger.error(`[skill-hub] Failed to unpack skill zip`, { error: err });
193
+ (this as any).app.logger.error(`[skill-hub] Failed to unpack skill zip`, { error: err });
194
194
  }
195
195
  }
196
196
  });
197
197
 
198
198
  // 5. Subscribe PubSub — worker processes skill execution tasks
199
- this.app.pubSubManager.subscribe('skill-hub.task', async (payload: any) => {
199
+ (this as any).app.pubSubManager.subscribe('skill-hub.task', async (payload: any) => {
200
200
  if (process.env.SKILL_HUB_SANDBOX === 'false') return;
201
201
  await this.onQueueTask(payload);
202
202
  });
203
203
 
204
204
  // 5b. Subscribe PubSub — worker processes init-env tasks
205
- this.app.pubSubManager.subscribe('skill-hub.init-env', async (payload: any) => {
205
+ (this as any).app.pubSubManager.subscribe('skill-hub.init-env', async (payload: any) => {
206
206
  if (process.env.SKILL_HUB_SANDBOX === 'false') return;
207
207
  await this.workerEnvManager.executeInit(payload);
208
208
  });
209
209
 
210
210
  // 6. Register AI tools + subscriptions (deferred — after all plugins loaded)
211
- this.app.on('afterStart', async () => {
211
+ (this as any).app.on('afterStart', async () => {
212
212
  this.registerAITools();
213
213
  this.startCleanupInterval();
214
214
  await this.subscribeInitEnvDone();
215
215
  // Ensure any newly added built-in skills are seeded automatically on upgrade/restart
216
216
  await this.skillManager.seedDefaults().catch((e) => {
217
- this.app.logger.error(`[skill-hub] Failed to seed default skills: ${e.message}`);
217
+ (this as any).app.logger.error(`[skill-hub] Failed to seed default skills: ${e.message}`);
218
218
  });
219
219
  });
220
220
  }
221
221
 
222
222
  private async onQueueTask(message: { id: string }) {
223
- this.app.logger.info(`[skill-hub] Worker received queue task: ${message.id}`);
224
- const execution = await this.db.getRepository('skillExecutions').findOne({
223
+ (this as any).app.logger.info(`[skill-hub] Worker received queue task: ${message.id}`);
224
+ const execution = await (this as any).db.getRepository('skillExecutions').findOne({
225
225
  filter: { id: message.id },
226
226
  appends: ['skill'],
227
227
  });
228
228
  if (!execution) {
229
- this.app.logger.warn(`[skill-hub] Task ${message.id} ignored: execution record not found.`);
229
+ (this as any).app.logger.warn(`[skill-hub] Task ${message.id} ignored: execution record not found.`);
230
230
  return;
231
231
  }
232
232
 
@@ -235,7 +235,7 @@ export class SkillHubSubFeature {
235
235
  this.sandboxRunner,
236
236
  this.fileManager,
237
237
  this.skillRepoService,
238
- this.app,
238
+ (this as any).app,
239
239
  );
240
240
  await task.run();
241
241
  }
@@ -259,7 +259,7 @@ export class SkillHubSubFeature {
259
259
  }
260
260
  }
261
261
 
262
- const execution = await this.db.getRepository('skillExecutions').create({
262
+ const execution = await (this as any).db.getRepository('skillExecutions').create({
263
263
  values: {
264
264
  skillId: skill.id,
265
265
  status: 'pending',
@@ -271,7 +271,7 @@ export class SkillHubSubFeature {
271
271
 
272
272
  const execId = String(execution.id);
273
273
 
274
- this.app.logger.info(
274
+ (this as any).app.logger.info(
275
275
  `[skill-hub] Queued execution ${execId}: skill=${skill.get ? skill.get('name') : skill.name}, ` +
276
276
  `user=${userId || 'system'}`,
277
277
  );
@@ -317,10 +317,10 @@ export class SkillHubSubFeature {
317
317
  };
318
318
 
319
319
  // Subscribe progress and completion FIRST (before dispatching)
320
- await this.app.pubSubManager.subscribe(progressChannel, progressCallback);
320
+ await (this as any).app.pubSubManager.subscribe(progressChannel, progressCallback);
321
321
  cleanups.push({ channel: progressChannel, callback: progressCallback });
322
322
 
323
- await this.app.pubSubManager.subscribe(doneChannel, doneCallback);
323
+ await (this as any).app.pubSubManager.subscribe(doneChannel, doneCallback);
324
324
  cleanups.push({ channel: doneChannel, callback: doneCallback });
325
325
 
326
326
  // Handle user abort (cancel chat) → propagate to worker
@@ -329,9 +329,9 @@ export class SkillHubSubFeature {
329
329
  signal.addEventListener?.('abort', () => {
330
330
  clearTimeout(timeout);
331
331
  // Publish abort to worker via PubSub
332
- this.app.pubSubManager.publish(abortChannel, { reason: 'user_cancel' }).catch(() => {});
332
+ (this as any).app.pubSubManager.publish(abortChannel, { reason: 'user_cancel' }).catch(() => {});
333
333
  // Also update the execution status
334
- this.db.getRepository('skillExecutions').update({
334
+ (this as any).db.getRepository('skillExecutions').update({
335
335
  filter: { id: execId },
336
336
  values: { status: 'canceled' },
337
337
  }).catch(() => {});
@@ -340,7 +340,7 @@ export class SkillHubSubFeature {
340
340
  }
341
341
 
342
342
  // NOW Dispatch to worker via EventQueue
343
- await this.app.pubSubManager.publish('skill-hub.task', { id: execId });
343
+ await (this as any).app.pubSubManager.publish('skill-hub.task', { id: execId });
344
344
 
345
345
  // Wait for completion
346
346
  result = await resultPromise;
@@ -348,7 +348,7 @@ export class SkillHubSubFeature {
348
348
  // Cleanup all PubSub subscriptions
349
349
  for (const { channel, callback } of cleanups) {
350
350
  try {
351
- await this.app.pubSubManager.unsubscribe(channel, callback);
351
+ await (this as any).app.pubSubManager.unsubscribe(channel, callback);
352
352
  } catch {
353
353
  // ignore cleanup errors
354
354
  }
@@ -383,7 +383,7 @@ export class SkillHubSubFeature {
383
383
  ctx.throw(401, 'Unauthorized');
384
384
  }
385
385
 
386
- const execution = await this.db.getRepository('skillExecutions').findOne({
386
+ const execution = await (this as any).db.getRepository('skillExecutions').findOne({
387
387
  filter: { id: execId },
388
388
  });
389
389
 
@@ -414,7 +414,7 @@ export class SkillHubSubFeature {
414
414
  ctx.throw(400, 'Missing skillId');
415
415
  }
416
416
 
417
- const skill = await this.db.getRepository('skillDefinitions').findOne({
417
+ const skill = await (this as any).db.getRepository('skillDefinitions').findOne({
418
418
  filter: { id: skillId },
419
419
  });
420
420
  if (!skill) {
@@ -465,20 +465,20 @@ export class SkillHubSubFeature {
465
465
  if (data.status === 'succeeded' && data.whitelist) {
466
466
  values.packageWhitelist = stringifyJsonText(data.whitelist, { python: [], node: [], apt: [] });
467
467
  }
468
- await this.db.getRepository('skillWorkerConfigs').update({
468
+ await (this as any).db.getRepository('skillWorkerConfigs').update({
469
469
  filter: {},
470
470
  values,
471
471
  forceUpdate: true,
472
472
  });
473
- this.app.logger.info(`[skill-hub] Init env ${data.status}`);
473
+ (this as any).app.logger.info(`[skill-hub] Init env ${data.status}`);
474
474
  } catch (err) {
475
- this.app.logger.warn('[skill-hub] Failed to update init env status:', err);
475
+ (this as any).app.logger.warn('[skill-hub] Failed to update init env status:', err);
476
476
  }
477
477
  };
478
478
 
479
479
  this.initEnvProgressCallback = async (data: any) => {
480
480
  try {
481
- await this.db.getRepository('skillWorkerConfigs').update({
481
+ await (this as any).db.getRepository('skillWorkerConfigs').update({
482
482
  filter: {},
483
483
  values: {
484
484
  initProgressPercent: data.percent,
@@ -491,15 +491,15 @@ export class SkillHubSubFeature {
491
491
  }
492
492
  };
493
493
 
494
- await this.app.pubSubManager.subscribe('skill-hub.init-env.done', this.initEnvDoneCallback);
495
- await this.app.pubSubManager.subscribe('skill-hub.init-env.progress', this.initEnvProgressCallback);
494
+ await (this as any).app.pubSubManager.subscribe('skill-hub.init-env.done', this.initEnvDoneCallback);
495
+ await (this as any).app.pubSubManager.subscribe('skill-hub.init-env.progress', this.initEnvProgressCallback);
496
496
  }
497
497
 
498
498
  private registerAITools() {
499
499
  try {
500
- const aiPlugin = this.app.pm.get('@nocobase/plugin-ai') as any;
500
+ const aiPlugin = (this as any).app.pm.get('@nocobase/plugin-ai') as any;
501
501
  if (!aiPlugin?.ai?.toolsManager) {
502
- this.app.logger.warn('[skill-hub] plugin-ai not available, skip AI tool registration.');
502
+ (this as any).app.logger.warn('[skill-hub] plugin-ai not available, skip AI tool registration.');
503
503
  return;
504
504
  }
505
505
 
@@ -509,7 +509,7 @@ export class SkillHubSubFeature {
509
509
  // 2. Dynamic tools — each enabled skill becomes a separate AI tool.
510
510
  aiPlugin.ai.toolsManager.registerDynamicTools(async (register: { registerTools: (options: any) => void }) => {
511
511
  try {
512
- const skills = await this.db.getRepository('skillDefinitions').find({
512
+ const skills = await (this as any).db.getRepository('skillDefinitions').find({
513
513
  filter: { enabled: true },
514
514
  });
515
515
 
@@ -539,11 +539,11 @@ export class SkillHubSubFeature {
539
539
  },
540
540
  invoke: async (toolCtx: any, args: any) => {
541
541
  // Re-fetch skill to get latest version (hot-reload support)
542
- const latestSkill = await this.db.getRepository('skillDefinitions').findOne({
542
+ const latestSkill = await (this as any).db.getRepository('skillDefinitions').findOne({
543
543
  filter: { id: skill.get('id'), enabled: true },
544
544
  });
545
545
  if (!latestSkill) {
546
- return { error: `Skill "${skill.get('name')}" is no longer available` };
546
+ return { status: 'error', content: `Skill "${skill.get('name')}" is no longer available` };
547
547
  }
548
548
  const result = await this.executeSkill(latestSkill, args, toolCtx);
549
549
  return {
@@ -556,13 +556,13 @@ export class SkillHubSubFeature {
556
556
 
557
557
  register.registerTools(tools);
558
558
  } catch (err) {
559
- this.app.logger.warn('[skill-hub] Failed to provide dynamic tools', err);
559
+ (this as any).app.logger.warn('[skill-hub] Failed to provide dynamic tools', err);
560
560
  }
561
561
  });
562
562
 
563
- this.app.logger.info('[skill-hub] AI tools registered (dynamic provider + general tool).');
563
+ (this as any).app.logger.info('[skill-hub] AI tools registered (dynamic provider + general tool).');
564
564
  } catch (error) {
565
- this.app.logger.warn('[skill-hub] Failed to register AI tools:', error);
565
+ (this as any).app.logger.warn('[skill-hub] Failed to register AI tools:', error);
566
566
  }
567
567
  }
568
568
 
@@ -573,27 +573,27 @@ export class SkillHubSubFeature {
573
573
  this.cleanupInterval = setInterval(async () => {
574
574
  // 1. Storage Retention Cleanup
575
575
  try {
576
- const config = await this.db.getRepository('skillWorkerConfigs').findOne();
576
+ const config = await (this as any).db.getRepository('skillWorkerConfigs').findOne();
577
577
  const hours = config ? config.get('retentionHours') : 24;
578
578
 
579
579
  if (hours && hours > 0) {
580
580
  const MAX_AGE_MS = hours * 60 * 60 * 1000;
581
581
  const cutoff = new Date(Date.now() - MAX_AGE_MS);
582
- const repo = this.db.getRepository('skillExecutions');
582
+ const repo = (this as any).db.getRepository('skillExecutions');
583
583
 
584
584
  const outdated = await repo.find({
585
- where: { createdAt: { $lt: cutoff } }
585
+ filter: { createdAt: { $lt: cutoff } }
586
586
  });
587
587
 
588
588
  if (outdated.length > 0) {
589
589
  for (const record of outdated) {
590
590
  await record.destroy(); // Fires afterDestroy hook which removes physical folder
591
591
  }
592
- this.app.logger.info(`[skill-hub] Auto-cleaned up ${outdated.length} expired execution records`);
592
+ (this as any).app.logger.info(`[skill-hub] Auto-cleaned up ${outdated.length} expired execution records`);
593
593
  }
594
594
  }
595
595
  } catch (err) {
596
- this.app.logger.warn('[skill-hub] Auto Cleanup error:', err);
596
+ (this as any).app.logger.warn('[skill-hub] Auto Cleanup error:', err);
597
597
  }
598
598
 
599
599
  // 2. Cleanup rate limiter stale entries
@@ -605,12 +605,12 @@ export class SkillHubSubFeature {
605
605
  // Unsubscribe PubSub
606
606
  if (this.initEnvDoneCallback) {
607
607
  try {
608
- await this.app.pubSubManager.unsubscribe('skill-hub.init-env.done', this.initEnvDoneCallback);
608
+ await (this as any).app.pubSubManager.unsubscribe('skill-hub.init-env.done', this.initEnvDoneCallback);
609
609
  } catch { /* ignore */ }
610
610
  }
611
611
  if (this.initEnvProgressCallback) {
612
612
  try {
613
- await this.app.pubSubManager.unsubscribe('skill-hub.init-env.progress', this.initEnvProgressCallback);
613
+ await (this as any).app.pubSubManager.unsubscribe('skill-hub.init-env.progress', this.initEnvProgressCallback);
614
614
  } catch { /* ignore */ }
615
615
  }
616
616
 
@@ -624,7 +624,7 @@ export class SkillHubSubFeature {
624
624
  // --- Handlers ---
625
625
  private async handleClearStorage(ctx: any, next: () => Promise<any>) {
626
626
  const { type } = ctx.request.body || ctx.action.params.values;
627
- const repo = this.db.getRepository('skillExecutions');
627
+ const repo = (this as any).db.getRepository('skillExecutions');
628
628
  let count = 0;
629
629
 
630
630
  if (type === 'all') {
@@ -634,11 +634,11 @@ export class SkillHubSubFeature {
634
634
  }
635
635
  count = results.length;
636
636
  } else if (type === 'expired') {
637
- const config = await this.db.getRepository('skillWorkerConfigs').findOne();
637
+ const config = await (this as any).db.getRepository('skillWorkerConfigs').findOne();
638
638
  const hours = config ? config.get('retentionHours') : 24;
639
639
  if (hours > 0) {
640
640
  const cutoff = new Date(Date.now() - hours * 60 * 60 * 1000);
641
- const results = await repo.find({ where: { createdAt: { $lt: cutoff } }, fields: ['id'] });
641
+ const results = await repo.find({ filter: { createdAt: { $lt: cutoff } }, fields: ['id'] });
642
642
  for (const rec of results) {
643
643
  await rec.destroy();
644
644
  }
@@ -653,7 +653,7 @@ export class SkillHubSubFeature {
653
653
  private async handleListTemplates(ctx: any, next: () => Promise<any>) {
654
654
  // Dynamic Pull: discover templates from all active plugins in the system
655
655
  try {
656
- const allPlugins = this.app.pm.getPlugins();
656
+ const allPlugins = (this as any).app.pm.getPlugins();
657
657
  for (const [, pluginInstance] of allPlugins) {
658
658
  if (typeof (pluginInstance as any).getSkillTemplates === 'function') {
659
659
  const pluginSkills = (pluginInstance as any).getSkillTemplates();
@@ -667,7 +667,7 @@ export class SkillHubSubFeature {
667
667
  }
668
668
  }
669
669
  } catch (e) {
670
- this.app.logger.warn(`[skill-hub] Failed to discover some plugin skills: ${e.message}`);
670
+ (this as any).app.logger.warn(`[skill-hub] Failed to discover some plugin skills: ${e.message}`);
671
671
  }
672
672
 
673
673
  ctx.body = { data: Array.from(this.skillTemplates.values()) };
@@ -683,7 +683,7 @@ export class SkillHubSubFeature {
683
683
  */
684
684
  registerSkillTemplate(pluginName: string, skillDef: any) {
685
685
  this.skillTemplates.set(skillDef.name, this.hydrateSkillTemplate(pluginName, skillDef));
686
- this.app.logger.info(`[skill-hub] Registered skill template "${skillDef.name}" from plugin "${pluginName}"`);
686
+ (this as any).app.logger.info(`[skill-hub] Registered skill template "${skillDef.name}" from plugin "${pluginName}"`);
687
687
  }
688
688
 
689
689
  resolveSkillTemplate(templateName: string) {
@@ -692,7 +692,7 @@ export class SkillHubSubFeature {
692
692
  if (cached) return cached;
693
693
 
694
694
  try {
695
- const allPlugins = this.app.pm.getPlugins();
695
+ const allPlugins = (this as any).app.pm.getPlugins();
696
696
  for (const [, pluginInstance] of allPlugins) {
697
697
  if (typeof (pluginInstance as any).getSkillTemplates !== 'function') continue;
698
698
  const pluginSkills = (pluginInstance as any).getSkillTemplates();
@@ -705,7 +705,7 @@ export class SkillHubSubFeature {
705
705
  }
706
706
  }
707
707
  } catch (e: any) {
708
- this.app.logger.warn(`[skill-hub] Failed to resolve plugin skill "${templateName}": ${e.message}`);
708
+ (this as any).app.logger.warn(`[skill-hub] Failed to resolve plugin skill "${templateName}": ${e.message}`);
709
709
  }
710
710
 
711
711
  return null;