plugin-agent-orchestrator 1.0.27 → 1.0.32
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.
- package/README.md +9 -7
- package/dist/client/index.js +1 -1
- package/dist/client-v2/{214.723affb37c13bf7a.js → 214.79650a549273f163.js} +1 -1
- package/dist/client-v2/264.718a107e43fc163c.js +10 -0
- package/dist/client-v2/373.f5d5292e53c4e832.js +10 -0
- package/dist/client-v2/{41.1805b2edfaa4afe2.js → 41.ba6e080cc0488143.js} +1 -1
- package/dist/client-v2/418.29e713f79131eece.js +10 -0
- package/dist/client-v2/619.bd3c5698b40705c3.js +10 -0
- package/dist/client-v2/677.a991ce0250ff5c77.js +10 -0
- package/dist/client-v2/{70.a15d7fcec7c41768.js → 70.bda9518881c05360.js} +1 -1
- package/dist/client-v2/925.f5370de8f6632d65.js +10 -0
- package/dist/client-v2/index.js +1 -1
- package/dist/externalVersion.js +7 -10
- package/dist/locale/en-US.json +94 -25
- package/dist/locale/vi-VN.json +94 -25
- package/dist/locale/zh-CN.json +94 -25
- package/dist/server/collections/agent-execution-spans.js +37 -0
- package/dist/server/collections/agent-harness-profiles.js +2 -2
- package/dist/server/collections/agent-memory-contexts.js +125 -0
- package/dist/server/collections/orchestrator-logs.js +2 -2
- package/dist/server/migrations/20260425000000-add-interaction-schema.js +3 -1
- package/dist/server/migrations/20260427000000-change-packages-to-text.js +3 -1
- package/dist/server/migrations/20260427000001-change-other-json-to-text.js +6 -2
- package/dist/server/migrations/20260524001000-add-plan-approval-and-harness-profiles.js +21 -19
- package/dist/server/migrations/20260621000000-native-policy-profile-defaults.js +193 -0
- package/dist/server/plugin.js +128 -74
- package/dist/server/resources/agent-monitor.js +454 -0
- package/dist/server/services/AgentHarness.js +24 -499
- package/dist/server/services/AgentMemoryContextService.js +216 -0
- package/dist/server/services/ExecutionSpanService.js +2 -2
- package/dist/server/services/NativeSubAgentObserver.js +413 -0
- package/dist/server/skill-hub/mcp/McpController.js +16 -5
- package/dist/server/skill-hub/plugin.js +81 -5
- package/dist/server/skill-hub/tasks/SkillExecutionTask.js +9 -3
- package/dist/server/tools/delegate-task.js +11 -589
- package/dist/server/utils/skill-settings.js +18 -1
- package/package.json +47 -49
- package/src/client/AIEmployeesContext.tsx +5 -18
- package/src/client/AgentRunsTab.tsx +2 -771
- package/src/client/HarnessProfilesTab.tsx +2 -257
- package/src/client/OrchestratorSettings.tsx +97 -106
- package/src/client/RulesTab.tsx +2 -788
- package/src/client/plugin.tsx +0 -2
- package/src/client/skill-hub/components/ExecutionHistory.tsx +200 -202
- package/src/client/skill-hub/components/ExecutionProgress.tsx +51 -55
- package/src/client/skill-hub/components/LoopSettings.tsx +331 -331
- package/src/client/skill-hub/components/SkillEditor.tsx +43 -39
- package/src/client/skill-hub/components/SkillManager.tsx +194 -181
- package/src/client/skill-hub/components/SkillTestPanel.tsx +141 -145
- package/src/client/skill-hub/locale.ts +16 -16
- package/src/client/skill-hub/tools/SkillHubCard.tsx +104 -109
- package/src/client/skill-hub/tools/loopTemplates.ts +52 -52
- package/src/client/skill-hub/utils/jsonFields.ts +7 -3
- package/src/client-v2/components/AIEmployeesContext.tsx +3 -16
- package/src/client-v2/components/AgentRunsTab.tsx +182 -455
- package/src/client-v2/components/HarnessProfilesTab.tsx +34 -31
- package/src/client-v2/components/RulesTab.tsx +2 -782
- package/src/client-v2/components/TracingTab.tsx +1 -1
- package/src/client-v2/hooks/useApiRequest.ts +8 -1
- package/src/client-v2/pages/RulesPage.tsx +2 -2
- package/src/client-v2/plugin.tsx +3 -3
- package/src/locale/en-US.json +94 -25
- package/src/locale/vi-VN.json +94 -25
- package/src/locale/zh-CN.json +94 -25
- package/src/server/__tests__/native-sub-agent-observer.test.ts +246 -0
- package/src/server/__tests__/skill-settings.test.ts +6 -6
- package/src/server/__tests__/smoke.test.ts +1 -0
- package/src/server/collections/agent-execution-spans.ts +37 -0
- package/src/server/collections/agent-harness-profiles.ts +59 -59
- package/src/server/collections/agent-loop-events.ts +71 -71
- package/src/server/collections/agent-loop-steps.ts +144 -144
- package/src/server/collections/agent-memory-contexts.ts +95 -0
- package/src/server/collections/orchestrator-logs.ts +4 -4
- package/src/server/collections/skill-definitions.ts +111 -111
- package/src/server/collections/skill-executions.ts +106 -106
- package/src/server/collections/skill-loop-configs.ts +65 -65
- package/src/server/migrations/20260423000000-add-progress-fields.ts +14 -14
- package/src/server/migrations/20260425000000-add-interaction-schema.ts +3 -1
- package/src/server/migrations/20260427000000-change-packages-to-text.ts +4 -2
- package/src/server/migrations/20260427000001-change-other-json-to-text.ts +9 -5
- package/src/server/migrations/20260524000000-add-agent-loop-fields-to-skill-executions.ts +30 -30
- package/src/server/migrations/20260524001000-add-plan-approval-and-harness-profiles.ts +145 -142
- package/src/server/migrations/20260615000000-normalize-ai-employee-tool-bindings.ts +2 -2
- package/src/server/migrations/20260621000000-native-policy-profile-defaults.ts +193 -0
- package/src/server/plugin.ts +151 -94
- package/src/server/resources/agent-monitor.ts +482 -0
- package/src/server/services/AgentHarness.ts +38 -623
- package/src/server/services/AgentMemoryContextService.ts +256 -0
- package/src/server/services/AgentPlanValidator.ts +73 -73
- package/src/server/services/ExecutionSpanService.ts +6 -2
- package/src/server/services/FileManager.ts +144 -144
- package/src/server/services/NativeSubAgentObserver.ts +507 -0
- package/src/server/services/SkillManager.ts +583 -583
- package/src/server/services/SkillRepositoryService.ts +5 -7
- package/src/server/services/TokenTracker.ts +3 -3
- package/src/server/services/WorkerEnvManager.ts +1 -2
- package/src/server/skill-hub/actions/git-import.ts +5 -7
- package/src/server/skill-hub/mcp/McpController.ts +41 -14
- package/src/server/skill-hub/plugin.ts +89 -6
- package/src/server/skill-hub/tasks/SkillExecutionTask.ts +470 -460
- package/src/server/skill-hub/utils/json-fields.ts +1 -1
- package/src/server/tools/delegate-task.ts +13 -847
- package/src/server/utils/skill-settings.ts +24 -6
- package/dist/client-v2/264.0533912e6c5ea2d7.js +0 -10
- package/dist/client-v2/418.5ae055abf141820e.js +0 -10
- package/dist/client-v2/619.d99d3c9e61c99064.js +0 -10
- package/dist/client-v2/892.72db4161511c8a16.js +0 -10
- package/dist/client-v2/926.87f660b670d85bcc.js +0 -10
- package/src/client/tools/PlanApprovalCard.tsx +0 -176
- package/src/client/tools/registerOrchestratorCards.ts +0 -17
|
@@ -25,7 +25,7 @@ export class SkillRepositoryService {
|
|
|
25
25
|
*/
|
|
26
26
|
async extractSkillPackage(skillName: string, zipFilePath: string) {
|
|
27
27
|
const targetDir = resolve(this.baseDir, skillName);
|
|
28
|
-
|
|
28
|
+
|
|
29
29
|
// Clean target dir if exists
|
|
30
30
|
if (existsSync(targetDir)) {
|
|
31
31
|
rmSync(targetDir, { recursive: true, force: true });
|
|
@@ -100,8 +100,6 @@ export class SkillRepositoryService {
|
|
|
100
100
|
});
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
-
|
|
104
|
-
|
|
105
103
|
private getSkillCodeFromDir(dir: string): string | null {
|
|
106
104
|
if (existsSync(resolve(dir, 'index.py'))) {
|
|
107
105
|
return readFileSync(resolve(dir, 'index.py'), 'utf8');
|
|
@@ -118,14 +116,14 @@ export class SkillRepositoryService {
|
|
|
118
116
|
|
|
119
117
|
private aggregateOtherMarkdownFiles(dir: string, baseDir = dir): string {
|
|
120
118
|
let combined = '';
|
|
121
|
-
|
|
119
|
+
|
|
122
120
|
if (!existsSync(dir)) return combined;
|
|
123
|
-
|
|
121
|
+
|
|
124
122
|
const items = readdirSync(dir);
|
|
125
123
|
for (const item of items) {
|
|
126
124
|
const fullPath = resolve(dir, item);
|
|
127
125
|
const stat = statSync(fullPath);
|
|
128
|
-
|
|
126
|
+
|
|
129
127
|
if (stat.isDirectory()) {
|
|
130
128
|
if (item !== 'node_modules' && item !== '.git') {
|
|
131
129
|
combined += this.aggregateOtherMarkdownFiles(fullPath, baseDir);
|
|
@@ -136,7 +134,7 @@ export class SkillRepositoryService {
|
|
|
136
134
|
combined += `\n\n--- Content from ${relPath} ---\n\n${content}`;
|
|
137
135
|
}
|
|
138
136
|
}
|
|
139
|
-
|
|
137
|
+
|
|
140
138
|
return combined;
|
|
141
139
|
}
|
|
142
140
|
}
|
|
@@ -32,14 +32,14 @@ function estimateCost(inputTokens: number, outputTokens: number): number {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
/**
|
|
35
|
-
* Extract usage_metadata from
|
|
35
|
+
* Extract usage_metadata from a legacy agent final state.
|
|
36
36
|
*
|
|
37
|
-
*
|
|
37
|
+
* Legacy agent execution returned a final state object with a `messages` array.
|
|
38
38
|
* The last AIMessage in that array carries `usage_metadata` populated by the
|
|
39
39
|
* LLM provider after the final generation step. This is the standard LangChain
|
|
40
40
|
* approach — no private API access needed.
|
|
41
41
|
*
|
|
42
|
-
* Expected shape
|
|
42
|
+
* Expected message shape:
|
|
43
43
|
* AIMessage.usage_metadata = {
|
|
44
44
|
* input_tokens: number,
|
|
45
45
|
* output_tokens: number,
|
|
@@ -101,8 +101,7 @@ export class WorkerEnvManager {
|
|
|
101
101
|
|
|
102
102
|
await (this as any).app.pubSubManager.publish('skill-hub.init-env.done', {
|
|
103
103
|
status: 'succeeded',
|
|
104
|
-
log:
|
|
105
|
-
'Sandbox environment is ready on this worker. Package installation is managed by the worker image/runtime; whitelist was refreshed.',
|
|
104
|
+
log: 'Sandbox environment is ready on this worker. Package installation is managed by the worker image/runtime; whitelist was refreshed.',
|
|
106
105
|
whitelist,
|
|
107
106
|
});
|
|
108
107
|
}
|
|
@@ -162,7 +162,7 @@ export async function gitSyncSkills(ctx: Context, next: () => Promise<void>) {
|
|
|
162
162
|
const parsed = parseSkillMarkdown(skillMd);
|
|
163
163
|
frontmatter = parsed.metadata;
|
|
164
164
|
instructions = parsed.body;
|
|
165
|
-
|
|
165
|
+
|
|
166
166
|
const otherMds = await fetchAllMarkdownInFolder(git, ref, skillBaseDir);
|
|
167
167
|
if (otherMds) instructions += otherMds;
|
|
168
168
|
} catch {
|
|
@@ -422,8 +422,6 @@ function parseBoolean(value: any, fallback: boolean) {
|
|
|
422
422
|
return Boolean(value);
|
|
423
423
|
}
|
|
424
424
|
|
|
425
|
-
|
|
426
|
-
|
|
427
425
|
function extractStringProperty(source: string, property: string) {
|
|
428
426
|
const match = source.match(new RegExp(`${property}\\s*:\\s*(['"\`])([\\s\\S]*?)\\1`));
|
|
429
427
|
return match ? match[2] : null;
|
|
@@ -470,7 +468,7 @@ async function fetchAllMarkdownInFolder(git: any, ref: string, folderPath: strin
|
|
|
470
468
|
try {
|
|
471
469
|
const filesOut = await git.raw(['ls-tree', '-r', '--name-only', `${ref}:${folderPath}/`]);
|
|
472
470
|
const files = filesOut.trim().split('\n').filter(Boolean);
|
|
473
|
-
|
|
471
|
+
|
|
474
472
|
for (const file of files) {
|
|
475
473
|
if (file.toLowerCase().endsWith('.md') && file.toUpperCase() !== 'SKILL.md') {
|
|
476
474
|
const content = await readOptionalGitFile(git, ref, joinGitPath(folderPath, file));
|
|
@@ -479,8 +477,8 @@ async function fetchAllMarkdownInFolder(git: any, ref: string, folderPath: strin
|
|
|
479
477
|
}
|
|
480
478
|
}
|
|
481
479
|
}
|
|
482
|
-
} catch (err) {
|
|
480
|
+
} catch (err) {
|
|
481
|
+
// Optional folder expansion can fail for missing paths or refs; keep import best-effort.
|
|
482
|
+
}
|
|
483
483
|
return combined;
|
|
484
484
|
}
|
|
485
|
-
|
|
486
|
-
|
|
@@ -13,13 +13,20 @@ export class McpController {
|
|
|
13
13
|
filter: { enabled: true },
|
|
14
14
|
});
|
|
15
15
|
|
|
16
|
-
const tools = await Promise.all(
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
16
|
+
const tools = await Promise.all(
|
|
17
|
+
skills.map(async (skill: any) => ({
|
|
18
|
+
name: skill
|
|
19
|
+
.get('name')
|
|
20
|
+
.toLowerCase()
|
|
21
|
+
.replace(/[^a-z0-9_]/g, '_')
|
|
22
|
+
.replace(/_+/g, '_'),
|
|
23
|
+
description:
|
|
24
|
+
typeof this.plugin.getSkillDescriptionForAI === 'function'
|
|
25
|
+
? await this.plugin.getSkillDescriptionForAI(skill)
|
|
26
|
+
: skill.get('description'),
|
|
21
27
|
inputSchema: parseJsonText(skill.get('inputSchema'), null),
|
|
22
|
-
}))
|
|
28
|
+
})),
|
|
29
|
+
);
|
|
23
30
|
|
|
24
31
|
ctx.body = {
|
|
25
32
|
tools,
|
|
@@ -44,8 +51,13 @@ export class McpController {
|
|
|
44
51
|
filter: { enabled: true },
|
|
45
52
|
});
|
|
46
53
|
|
|
47
|
-
const skill = skills.find(
|
|
48
|
-
s
|
|
54
|
+
const skill = skills.find(
|
|
55
|
+
(s: any) =>
|
|
56
|
+
s
|
|
57
|
+
.get('name')
|
|
58
|
+
.toLowerCase()
|
|
59
|
+
.replace(/[^a-z0-9_]/g, '_')
|
|
60
|
+
.replace(/_+/g, '_') === name,
|
|
49
61
|
);
|
|
50
62
|
|
|
51
63
|
if (!skill) {
|
|
@@ -54,15 +66,30 @@ export class McpController {
|
|
|
54
66
|
|
|
55
67
|
try {
|
|
56
68
|
const result = await this.plugin.executeSkill(skill, args || {}, ctx);
|
|
57
|
-
|
|
69
|
+
|
|
58
70
|
let textContent = `Executed successfully.`;
|
|
59
71
|
if (result.stdout) textContent += `\nOutput:\n${result.stdout}`;
|
|
60
72
|
if (result.stderr) textContent += `\nErrors:\n${result.stderr}`;
|
|
61
|
-
|
|
73
|
+
|
|
62
74
|
if (result.files?.length) {
|
|
63
|
-
textContent +=
|
|
64
|
-
|
|
65
|
-
|
|
75
|
+
textContent +=
|
|
76
|
+
`\nFiles generated:\n` +
|
|
77
|
+
result.files
|
|
78
|
+
.map((f: any) => {
|
|
79
|
+
return `- [${f.name}](${f.downloadUrl})`;
|
|
80
|
+
})
|
|
81
|
+
.join('\n');
|
|
82
|
+
|
|
83
|
+
// Embed a trustworthy file manifest so the chat file-preview plugin can resolve the
|
|
84
|
+
// real download URL by filename, instead of guessing it from LLM-rewritten links.
|
|
85
|
+
const manifest = result.files.map((f: any) => ({
|
|
86
|
+
name: f.name,
|
|
87
|
+
downloadUrl: f.downloadUrl,
|
|
88
|
+
mimetype: f.mimetype ?? f.mimeType ?? null,
|
|
89
|
+
size: f.size ?? null,
|
|
90
|
+
execId: result.execId ?? null,
|
|
91
|
+
}));
|
|
92
|
+
textContent += `\n<!--skillhub:files ${JSON.stringify(manifest)}-->`;
|
|
66
93
|
}
|
|
67
94
|
|
|
68
95
|
ctx.body = {
|
|
@@ -70,7 +97,7 @@ export class McpController {
|
|
|
70
97
|
{
|
|
71
98
|
type: 'text',
|
|
72
99
|
text: textContent,
|
|
73
|
-
}
|
|
100
|
+
},
|
|
74
101
|
],
|
|
75
102
|
isError: result.status !== 'succeeded',
|
|
76
103
|
};
|
|
@@ -16,6 +16,8 @@ import { tryGetAIToolsManager } from '../utils/ai-manager';
|
|
|
16
16
|
import type { ToolsRuntime } from '@nocobase/ai';
|
|
17
17
|
|
|
18
18
|
type ToolRuntimeInput = string | ToolsRuntime | undefined;
|
|
19
|
+
const SKILL_TASK_POLL_INTERVAL_MS = Number.parseInt(process.env.SKILL_HUB_TASK_POLL_INTERVAL_MS || '5000', 10);
|
|
20
|
+
const SKILL_TASK_POLL_LIMIT = Number.parseInt(process.env.SKILL_HUB_TASK_POLL_LIMIT || '3', 10);
|
|
19
21
|
|
|
20
22
|
function normalizeToolRuntime(runtime: ToolRuntimeInput): ToolsRuntime | undefined {
|
|
21
23
|
if (!runtime) return undefined;
|
|
@@ -100,6 +102,10 @@ export class SkillHubSubFeature {
|
|
|
100
102
|
private cleanupInterval: ReturnType<typeof setInterval> | null = null;
|
|
101
103
|
private initEnvDoneCallback: any = null;
|
|
102
104
|
private initEnvProgressCallback: any = null;
|
|
105
|
+
private skillTaskCallback: any = null;
|
|
106
|
+
private initEnvTaskCallback: any = null;
|
|
107
|
+
private skillTaskPoller: ReturnType<typeof setInterval> | null = null;
|
|
108
|
+
private skillTaskPolling = false;
|
|
103
109
|
private mcpController: McpController;
|
|
104
110
|
private skillRepoService: SkillRepositoryService;
|
|
105
111
|
private rateLimiter = new RateLimiter(
|
|
@@ -255,21 +261,24 @@ export class SkillHubSubFeature {
|
|
|
255
261
|
});
|
|
256
262
|
|
|
257
263
|
// 5. Subscribe PubSub — worker processes skill execution tasks
|
|
258
|
-
|
|
264
|
+
this.skillTaskCallback = async (payload: any) => {
|
|
259
265
|
if (process.env.SKILL_HUB_SANDBOX === 'false') return;
|
|
260
266
|
await this.onQueueTask(payload);
|
|
261
|
-
}
|
|
267
|
+
};
|
|
268
|
+
(this as any).app.pubSubManager.subscribe('skill-hub.task', this.skillTaskCallback);
|
|
262
269
|
|
|
263
270
|
// 5b. Subscribe PubSub — worker processes init-env tasks
|
|
264
|
-
|
|
271
|
+
this.initEnvTaskCallback = async (payload: any) => {
|
|
265
272
|
if (process.env.SKILL_HUB_SANDBOX === 'false') return;
|
|
266
273
|
await this.workerEnvManager.executeInit(payload);
|
|
267
|
-
}
|
|
274
|
+
};
|
|
275
|
+
(this as any).app.pubSubManager.subscribe('skill-hub.init-env', this.initEnvTaskCallback);
|
|
268
276
|
|
|
269
277
|
// 6. Register AI tools + subscriptions (deferred — after all plugins loaded)
|
|
270
278
|
(this as any).app.on('afterStart', async () => {
|
|
271
279
|
this.registerAITools();
|
|
272
280
|
this.startCleanupInterval();
|
|
281
|
+
this.startSkillTaskPoller();
|
|
273
282
|
await this.subscribeInitEnvDone();
|
|
274
283
|
// Ensure any newly added built-in skills are seeded automatically on upgrade/restart
|
|
275
284
|
await this.skillManager.seedDefaults().catch((e) => {
|
|
@@ -280,6 +289,15 @@ export class SkillHubSubFeature {
|
|
|
280
289
|
|
|
281
290
|
private async onQueueTask(message: { id: string }) {
|
|
282
291
|
(this as any).app.logger.info(`[skill-hub] Worker received queue task: ${message.id}`);
|
|
292
|
+
if (process.env.SKILL_HUB_SANDBOX === 'false') {
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
const claimed = await this.claimSkillExecution(message.id);
|
|
296
|
+
if (!claimed) {
|
|
297
|
+
(this as any).app.logger.debug(`[skill-hub] Task ${message.id} ignored: already claimed or not pending.`);
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
|
|
283
301
|
const execution = await (this as any).db.getRepository('skillExecutions').findOne({
|
|
284
302
|
filter: { id: message.id },
|
|
285
303
|
appends: ['skill'],
|
|
@@ -299,9 +317,56 @@ export class SkillHubSubFeature {
|
|
|
299
317
|
await task.run();
|
|
300
318
|
}
|
|
301
319
|
|
|
320
|
+
private async claimSkillExecution(id: string): Promise<boolean> {
|
|
321
|
+
const model = (this as any).db.getModel('skillExecutions');
|
|
322
|
+
const [affected] = await model.update(
|
|
323
|
+
{ status: 'running' },
|
|
324
|
+
{
|
|
325
|
+
where: {
|
|
326
|
+
id,
|
|
327
|
+
status: 'pending',
|
|
328
|
+
},
|
|
329
|
+
},
|
|
330
|
+
);
|
|
331
|
+
return affected > 0;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
private startSkillTaskPoller() {
|
|
335
|
+
if (process.env.SKILL_HUB_SANDBOX === 'false' || this.skillTaskPoller) {
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const tick = () => {
|
|
340
|
+
this.processPendingSkillExecutions().catch((error) => {
|
|
341
|
+
(this as any).app.logger.warn(`[skill-hub] Pending task poll failed: ${error?.message || error}`);
|
|
342
|
+
});
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
this.skillTaskPoller = setInterval(tick, SKILL_TASK_POLL_INTERVAL_MS);
|
|
346
|
+
(this.skillTaskPoller as any).unref?.();
|
|
347
|
+
setTimeout(tick, 1000);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
private async processPendingSkillExecutions() {
|
|
351
|
+
if (this.skillTaskPolling) return;
|
|
352
|
+
this.skillTaskPolling = true;
|
|
353
|
+
try {
|
|
354
|
+
const records = await (this as any).db.getRepository('skillExecutions').find({
|
|
355
|
+
filter: { status: 'pending' },
|
|
356
|
+
sort: ['createdAt'],
|
|
357
|
+
limit: SKILL_TASK_POLL_LIMIT,
|
|
358
|
+
});
|
|
359
|
+
for (const record of records) {
|
|
360
|
+
await this.onQueueTask({ id: String(record.get('id')) });
|
|
361
|
+
}
|
|
362
|
+
} finally {
|
|
363
|
+
this.skillTaskPolling = false;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
302
367
|
/**
|
|
303
368
|
* Execute skill — called by both AI tool and REST test endpoint.
|
|
304
|
-
* Dispatches to
|
|
369
|
+
* Dispatches to sandbox workers via PubSub, waits for result via PubSub.
|
|
305
370
|
* Pushes progress to SSE via runtime.writer (if within AI tool context).
|
|
306
371
|
* Includes rate limiting and graceful abort propagation.
|
|
307
372
|
*/
|
|
@@ -408,7 +473,7 @@ export class SkillHubSubFeature {
|
|
|
408
473
|
});
|
|
409
474
|
}
|
|
410
475
|
|
|
411
|
-
// NOW
|
|
476
|
+
// NOW dispatch to sandbox workers.
|
|
412
477
|
await (this as any).app.pubSubManager.publish('skill-hub.task', { id: execId });
|
|
413
478
|
|
|
414
479
|
// Wait for completion
|
|
@@ -759,6 +824,20 @@ export class SkillHubSubFeature {
|
|
|
759
824
|
|
|
760
825
|
async beforeStop() {
|
|
761
826
|
// Unsubscribe PubSub
|
|
827
|
+
if (this.skillTaskCallback) {
|
|
828
|
+
try {
|
|
829
|
+
await (this as any).app.pubSubManager.unsubscribe('skill-hub.task', this.skillTaskCallback);
|
|
830
|
+
} catch {
|
|
831
|
+
/* ignore */
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
if (this.initEnvTaskCallback) {
|
|
835
|
+
try {
|
|
836
|
+
await (this as any).app.pubSubManager.unsubscribe('skill-hub.init-env', this.initEnvTaskCallback);
|
|
837
|
+
} catch {
|
|
838
|
+
/* ignore */
|
|
839
|
+
}
|
|
840
|
+
}
|
|
762
841
|
if (this.initEnvDoneCallback) {
|
|
763
842
|
try {
|
|
764
843
|
await (this as any).app.pubSubManager.unsubscribe('skill-hub.init-env.done', this.initEnvDoneCallback);
|
|
@@ -779,6 +858,10 @@ export class SkillHubSubFeature {
|
|
|
779
858
|
clearInterval(this.cleanupInterval);
|
|
780
859
|
this.cleanupInterval = null;
|
|
781
860
|
}
|
|
861
|
+
if (this.skillTaskPoller) {
|
|
862
|
+
clearInterval(this.skillTaskPoller);
|
|
863
|
+
this.skillTaskPoller = null;
|
|
864
|
+
}
|
|
782
865
|
}
|
|
783
866
|
|
|
784
867
|
// --- Handlers ---
|