plugin-agent-orchestrator 1.0.28 → 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/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/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
package/src/server/plugin.ts
CHANGED
|
@@ -1,23 +1,135 @@
|
|
|
1
1
|
import { Plugin } from '@nocobase/server';
|
|
2
2
|
import path from 'path';
|
|
3
|
-
import { createDelegateToolsProvider } from './tools/delegate-task';
|
|
4
3
|
import { createExternalRagSearchTool } from './tools/external-rag-search';
|
|
5
|
-
import { createOrchestratorPlanTools } from './tools/orchestrator-plan';
|
|
6
4
|
import { registerTracingResource } from './resources/tracing';
|
|
7
|
-
import {
|
|
8
|
-
import { getRunEventBus } from './services/RunEventBus';
|
|
5
|
+
import { registerAgentMonitorResource } from './resources/agent-monitor';
|
|
9
6
|
import SkillHubSubFeature from './skill-hub/plugin';
|
|
10
|
-
import {
|
|
11
|
-
import { isAdminUser, currentUserId } from './utils/ctx-utils';
|
|
7
|
+
import { NativeSubAgentObserver } from './services/NativeSubAgentObserver';
|
|
8
|
+
import { asObject, isAdminUser, currentUserId } from './utils/ctx-utils';
|
|
12
9
|
import { getAIToolsManager } from './utils/ai-manager';
|
|
13
10
|
|
|
11
|
+
function normalizeOptionalString(value: unknown) {
|
|
12
|
+
return typeof value === 'string' ? value.trim() : '';
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function readModelValue(record: unknown, key: string) {
|
|
16
|
+
const model = record as { get?: (name: string) => unknown; [key: string]: unknown };
|
|
17
|
+
return typeof model?.get === 'function' ? model.get(key) : model?.[key];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function buildAgentMemoryContextKey(values: { scope: string; userId?: unknown; aiEmployeeUsername?: string }) {
|
|
21
|
+
const userPart = values.scope === 'public' ? 'public' : String(values.userId || '');
|
|
22
|
+
const agentPart = values.aiEmployeeUsername || '*';
|
|
23
|
+
return `${values.scope}:${userPart}:${agentPart}`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async function validateAgentMemoryContextValues(ctx: any) {
|
|
27
|
+
const actionName = ctx.action?.actionName;
|
|
28
|
+
const values = ctx.action?.params?.values || {};
|
|
29
|
+
let nextValues = values;
|
|
30
|
+
let currentId = ctx.action?.params?.filterByTk;
|
|
31
|
+
|
|
32
|
+
if (actionName === 'update' && ctx.action?.params?.filterByTk) {
|
|
33
|
+
const existing = await ctx.db.getRepository('agentMemoryContexts').findOne({
|
|
34
|
+
filter: { id: ctx.action.params.filterByTk },
|
|
35
|
+
});
|
|
36
|
+
currentId = readModelValue(existing, 'id') || currentId;
|
|
37
|
+
nextValues = {
|
|
38
|
+
...(existing?.toJSON?.() || existing || {}),
|
|
39
|
+
...values,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const scope = normalizeOptionalString(nextValues.scope);
|
|
44
|
+
const userId = nextValues.userId;
|
|
45
|
+
const aiEmployeeUsername = normalizeOptionalString(nextValues.aiEmployeeUsername);
|
|
46
|
+
|
|
47
|
+
if (!['public', 'user', 'agent_user'].includes(scope)) {
|
|
48
|
+
ctx.throw(400, 'scope must be one of: public, user, agent_user.');
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (scope === 'public' && userId != null && userId !== '') {
|
|
53
|
+
ctx.throw(400, 'scope="public" requires userId to be empty.');
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if ((scope === 'user' || scope === 'agent_user') && (userId == null || userId === '')) {
|
|
58
|
+
ctx.throw(400, `scope="${scope}" requires userId.`);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (scope === 'agent_user' && !aiEmployeeUsername) {
|
|
63
|
+
ctx.throw(400, 'scope="agent_user" requires aiEmployeeUsername.');
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const normalizedValues = {
|
|
68
|
+
...values,
|
|
69
|
+
scope,
|
|
70
|
+
userId: scope === 'public' ? null : userId,
|
|
71
|
+
aiEmployeeUsername,
|
|
72
|
+
contextKey: buildAgentMemoryContextKey({
|
|
73
|
+
scope,
|
|
74
|
+
userId: scope === 'public' ? null : userId,
|
|
75
|
+
aiEmployeeUsername,
|
|
76
|
+
}),
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const repo = ctx.db.getRepository('agentMemoryContexts');
|
|
80
|
+
const duplicate =
|
|
81
|
+
(await repo.findOne({
|
|
82
|
+
filter: { contextKey: normalizedValues.contextKey },
|
|
83
|
+
})) ||
|
|
84
|
+
(await repo.findOne({
|
|
85
|
+
filter: {
|
|
86
|
+
scope,
|
|
87
|
+
userId: normalizedValues.userId,
|
|
88
|
+
aiEmployeeUsername,
|
|
89
|
+
},
|
|
90
|
+
}));
|
|
91
|
+
const duplicateId = readModelValue(duplicate, 'id');
|
|
92
|
+
if (duplicateId && String(duplicateId) !== String(currentId || '')) {
|
|
93
|
+
ctx.throw(400, 'An agent memory context already exists for this scope, user, and AI employee.');
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
ctx.action.params.values = normalizedValues;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async function resolveTracingRetentionDays(plugin: { db: any; app: any }) {
|
|
101
|
+
const envDays = Number(process.env.ORCHESTRATOR_LOG_RETENTION_DAYS);
|
|
102
|
+
if (Number.isFinite(envDays) && envDays > 0) return envDays;
|
|
103
|
+
|
|
104
|
+
try {
|
|
105
|
+
const defaultProfile = await plugin.db.getRepository('agentHarnessProfiles').findOne({
|
|
106
|
+
filter: {
|
|
107
|
+
tag: 'default',
|
|
108
|
+
enabled: true,
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
const settings = asObject(readModelValue(defaultProfile, 'settings'));
|
|
112
|
+
const profileDays = Number(settings.tracingRetentionDays);
|
|
113
|
+
if (Number.isFinite(profileDays) && profileDays > 0) {
|
|
114
|
+
return profileDays;
|
|
115
|
+
}
|
|
116
|
+
} catch (error) {
|
|
117
|
+
plugin.app.logger?.warn?.('[AgentOrchestrator] Failed to load tracing retention policy', error);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return 30;
|
|
121
|
+
}
|
|
122
|
+
|
|
14
123
|
export class PluginAgentOrchestratorServer extends Plugin {
|
|
15
124
|
skillHub: SkillHubSubFeature;
|
|
16
|
-
|
|
125
|
+
nativeObserver: NativeSubAgentObserver;
|
|
126
|
+
private readonly installNativeObserver = () => {
|
|
127
|
+
this.nativeObserver?.install();
|
|
128
|
+
};
|
|
17
129
|
|
|
18
130
|
async afterAdd() {
|
|
19
131
|
this.skillHub = new SkillHubSubFeature(this);
|
|
20
|
-
this.
|
|
132
|
+
this.nativeObserver = new NativeSubAgentObserver(this);
|
|
21
133
|
}
|
|
22
134
|
|
|
23
135
|
async beforeLoad() {
|
|
@@ -40,11 +152,11 @@ export class PluginAgentOrchestratorServer extends Plugin {
|
|
|
40
152
|
actions: [
|
|
41
153
|
'orchestratorConfig:*',
|
|
42
154
|
'orchestratorTracing:*',
|
|
43
|
-
'
|
|
155
|
+
'agentMonitor:*',
|
|
156
|
+
'agentMemoryContexts:*',
|
|
44
157
|
'agentLoopRuns:*',
|
|
45
158
|
'agentLoopSteps:*',
|
|
46
159
|
'agentLoopEvents:*',
|
|
47
|
-
'agentLoopEventsStream:*',
|
|
48
160
|
'agentHarnessProfiles:*',
|
|
49
161
|
'agentExecutionSpans:*',
|
|
50
162
|
'skillDefinitions:*',
|
|
@@ -69,6 +181,7 @@ export class PluginAgentOrchestratorServer extends Plugin {
|
|
|
69
181
|
this.app.acl.allow('skillHub', 'test', 'loggedIn');
|
|
70
182
|
this.app.acl.allow('skillHub', 'download', 'loggedIn');
|
|
71
183
|
this.app.acl.allow('skillHub', 'listTemplates', 'loggedIn');
|
|
184
|
+
this.app.acl.allow('agentMonitor', ['list', 'get'], 'loggedIn');
|
|
72
185
|
|
|
73
186
|
// Data scoping for skillExecutions: a logged-in non-admin user may only
|
|
74
187
|
// read their own executions. Rows hold inputArgs / stdout / output files,
|
|
@@ -90,87 +203,28 @@ export class PluginAgentOrchestratorServer extends Plugin {
|
|
|
90
203
|
{ tag: 'orchestrator-skill-executions-scope', after: 'acl' },
|
|
91
204
|
);
|
|
92
205
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
206
|
+
this.app.resourceManager.use(
|
|
207
|
+
async (ctx, next) => {
|
|
208
|
+
const { resourceName, actionName } = ctx.action || {};
|
|
209
|
+
if (resourceName === 'agentMemoryContexts' && (actionName === 'create' || actionName === 'update')) {
|
|
210
|
+
await validateAgentMemoryContextValues(ctx);
|
|
211
|
+
}
|
|
212
|
+
await next();
|
|
213
|
+
},
|
|
214
|
+
{ tag: 'orchestrator-agent-memory-context-policy', after: 'acl' },
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
// --- Register External Tool Only ---
|
|
218
|
+
// Native @nocobase/plugin-ai owns sub-agent dispatch. The orchestrator no
|
|
219
|
+
// longer registers delegate_* / dispatch_subagents_* tools or controller
|
|
220
|
+
// plan tools; Skill Hub still registers its own AI tools from its subfeature.
|
|
97
221
|
const toolsManager = getAIToolsManager(this.app);
|
|
98
|
-
toolsManager.registerTools(createOrchestratorPlanTools(this, this.agentLoopService));
|
|
99
222
|
toolsManager.registerTools(createExternalRagSearchTool(this));
|
|
100
|
-
toolsManager.registerDynamicTools(createDelegateToolsProvider(this));
|
|
101
|
-
|
|
102
|
-
// --- Register Agent Loop Resource ---
|
|
103
|
-
registerAgentLoopResource(this, this.agentLoopService);
|
|
104
|
-
|
|
105
|
-
// --- Register SSE Event Stream Resource (Phase 6) ---
|
|
106
|
-
this.app.resource({
|
|
107
|
-
name: 'agentLoopEventsStream',
|
|
108
|
-
actions: {
|
|
109
|
-
async stream(ctx, next) {
|
|
110
|
-
const runId = ctx.action.params?.runId || ctx.query?.runId || ctx.request.query?.runId;
|
|
111
|
-
if (!runId) {
|
|
112
|
-
ctx.throw(400, 'runId query parameter is required.');
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// Ownership check: a non-admin user may only stream events for a run
|
|
117
|
-
// they started. Run events can echo step inputs/outputs, so an
|
|
118
|
-
// unscoped stream would leak another user's run activity.
|
|
119
|
-
if (!isAdminUser(ctx)) {
|
|
120
|
-
const userId = currentUserId(ctx);
|
|
121
|
-
const run = await ctx.db.getRepository('agentLoopRuns').findOne({
|
|
122
|
-
filter: { id: runId },
|
|
123
|
-
});
|
|
124
|
-
if (!run) {
|
|
125
|
-
ctx.throw(404, 'Run not found.');
|
|
126
|
-
return;
|
|
127
|
-
}
|
|
128
|
-
const ownerId = run.get ? run.get('userId') : run.userId;
|
|
129
|
-
if (!userId || String(ownerId) !== String(userId)) {
|
|
130
|
-
ctx.throw(403, 'You cannot stream events for this run.');
|
|
131
|
-
return;
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
223
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
const unsubscribe = getRunEventBus().subscribe(runId, (event: any) => {
|
|
141
|
-
try {
|
|
142
|
-
ctx.res.write(`data: ${JSON.stringify(event)}\n\n`);
|
|
143
|
-
} catch {
|
|
144
|
-
unsubscribe();
|
|
145
|
-
}
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
const keepalive = setInterval(() => {
|
|
149
|
-
try {
|
|
150
|
-
ctx.res.write(': keepalive\n\n');
|
|
151
|
-
} catch {
|
|
152
|
-
clearInterval(keepalive);
|
|
153
|
-
unsubscribe();
|
|
154
|
-
}
|
|
155
|
-
}, 15000);
|
|
156
|
-
|
|
157
|
-
ctx.req.on('close', () => {
|
|
158
|
-
clearInterval(keepalive);
|
|
159
|
-
unsubscribe();
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
ctx.req.on('error', () => {
|
|
163
|
-
clearInterval(keepalive);
|
|
164
|
-
unsubscribe();
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
ctx.res.writeHead(200);
|
|
168
|
-
ctx.res.write(': connected\n\n');
|
|
169
|
-
|
|
170
|
-
await next();
|
|
171
|
-
},
|
|
172
|
-
},
|
|
173
|
-
});
|
|
224
|
+
// --- Native plugin-ai Monitor Resource ---
|
|
225
|
+
registerAgentMonitorResource(this);
|
|
226
|
+
this.installNativeObserver();
|
|
227
|
+
this.app.on?.('afterStart', this.installNativeObserver);
|
|
174
228
|
|
|
175
229
|
// --- Register Tracing Resource (Phase 5) ---
|
|
176
230
|
// Custom read-only resource for the Swarm Tracing admin page.
|
|
@@ -183,8 +237,7 @@ export class PluginAgentOrchestratorServer extends Plugin {
|
|
|
183
237
|
cronTime: '0 30 2 * * *',
|
|
184
238
|
onTick: async () => {
|
|
185
239
|
try {
|
|
186
|
-
const days =
|
|
187
|
-
if (!Number.isFinite(days) || days <= 0) return;
|
|
240
|
+
const days = await resolveTracingRetentionDays(this);
|
|
188
241
|
const cutoff = new Date(Date.now() - days * 86400000);
|
|
189
242
|
const repo = this.db.getRepository('orchestratorLogs');
|
|
190
243
|
const spansRepo = this.db.getRepository('agentExecutionSpans');
|
|
@@ -207,9 +260,8 @@ export class PluginAgentOrchestratorServer extends Plugin {
|
|
|
207
260
|
},
|
|
208
261
|
});
|
|
209
262
|
|
|
210
|
-
//
|
|
211
|
-
//
|
|
212
|
-
// If future versions need conversation logging, add it here.
|
|
263
|
+
// Native sub-agent conversations are owned by @nocobase/plugin-ai. This
|
|
264
|
+
// plugin only observes their execution spans and policy context.
|
|
213
265
|
}
|
|
214
266
|
|
|
215
267
|
async install() {
|
|
@@ -217,10 +269,15 @@ export class PluginAgentOrchestratorServer extends Plugin {
|
|
|
217
269
|
}
|
|
218
270
|
|
|
219
271
|
async afterEnable() {}
|
|
220
|
-
async afterDisable() {
|
|
272
|
+
async afterDisable() {
|
|
273
|
+
this.nativeObserver?.uninstall();
|
|
274
|
+
}
|
|
221
275
|
async remove() {}
|
|
222
276
|
|
|
223
277
|
async beforeStop() {
|
|
278
|
+
this.app.off?.('afterStart', this.installNativeObserver);
|
|
279
|
+
this.app.removeListener?.('afterStart', this.installNativeObserver);
|
|
280
|
+
this.nativeObserver?.uninstall();
|
|
224
281
|
await this.skillHub.beforeStop();
|
|
225
282
|
}
|
|
226
283
|
}
|