@uwa4d/openapi-mcp 0.2.0-beta.7 → 0.2.0-beta.9
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 +69 -6
- package/dist/annotate-dashboard.d.ts +48 -0
- package/dist/annotate-dashboard.js +413 -0
- package/dist/client.js +4 -13
- package/dist/composite/report-diagnosis.js +178 -6
- package/dist/error-hints.d.ts +13 -0
- package/dist/error-hints.js +55 -0
- package/dist/indicator-dashboard-keys.d.ts +7 -1
- package/dist/indicator-dashboard-keys.js +20 -0
- package/dist/presets.js +5 -0
- package/dist/server-instructions.d.ts +5 -0
- package/dist/server-instructions.js +16 -0
- package/dist/server.js +2 -1
- package/dist/tools.d.ts +5 -0
- package/dist/tools.js +192 -36
- package/dist/version-guide.d.ts +5 -0
- package/dist/version-guide.js +82 -4
- package/package.json +1 -1
- package/spec/uwa-openapi.json +1 -1
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { z } from './types.js';
|
|
2
|
-
import { errorResult, jsonToolResult, requireReportKey } from './helpers.js';
|
|
2
|
+
import { errorResult, isObj, jsonToolResult, requireReportKey } from './helpers.js';
|
|
3
3
|
import { fetchOverviewStatistic, fetchReportIdentity, fetchSceneStatistic, } from './route-overview.js';
|
|
4
4
|
import { viewOverview } from './overview-view.js';
|
|
5
5
|
import { runTopFunctions } from './top-functions.js';
|
|
6
6
|
import { runTopResources } from './top-resources.js';
|
|
7
|
+
import { extractPssPeakCard } from '../annotate-dashboard.js';
|
|
7
8
|
function summarizeScenes(data) {
|
|
8
9
|
if (!data)
|
|
9
10
|
return { sceneCount: 0 };
|
|
@@ -20,6 +21,25 @@ function summarizeScenes(data) {
|
|
|
20
21
|
}
|
|
21
22
|
return { keys: Object.keys(obj).slice(0, 30) };
|
|
22
23
|
}
|
|
24
|
+
/** list_scenes 返回起止帧;归一成 sceneIdx + sceneName + frameStart/End。 */
|
|
25
|
+
function normalizeSceneList(data) {
|
|
26
|
+
const arr = Array.isArray(data)
|
|
27
|
+
? data
|
|
28
|
+
: data && typeof data === 'object' && Array.isArray(data['scenes'])
|
|
29
|
+
? data['scenes']
|
|
30
|
+
: [];
|
|
31
|
+
return arr.slice(0, 50).map((item, idx) => {
|
|
32
|
+
if (!item || typeof item !== 'object')
|
|
33
|
+
return { sceneIdx: idx, raw: item };
|
|
34
|
+
const o = item;
|
|
35
|
+
return {
|
|
36
|
+
sceneIdx: idx,
|
|
37
|
+
sceneName: o['sceneName'] ?? o['name'] ?? null,
|
|
38
|
+
frameStart: o['frameStart'] ?? o['startFrame'] ?? null,
|
|
39
|
+
frameEnd: o['frameEnd'] ?? o['endFrame'] ?? null,
|
|
40
|
+
};
|
|
41
|
+
});
|
|
42
|
+
}
|
|
23
43
|
/** brief 全量可能很长,体检只保留常见关键指标(含 Unity key 与 UE 中文 label)。 */
|
|
24
44
|
function pickImportantBrief(brief) {
|
|
25
45
|
const preferExact = [
|
|
@@ -57,17 +77,110 @@ function pickImportantBrief(brief) {
|
|
|
57
77
|
}
|
|
58
78
|
return out;
|
|
59
79
|
}
|
|
80
|
+
function numOf(v) {
|
|
81
|
+
if (typeof v === 'number' && Number.isFinite(v))
|
|
82
|
+
return v;
|
|
83
|
+
if (Array.isArray(v) && typeof v[0] === 'number')
|
|
84
|
+
return v[0];
|
|
85
|
+
if (typeof v === 'string' && v.trim() && Number.isFinite(Number(v)))
|
|
86
|
+
return Number(v);
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
/** 从 overview brief/summary 尽量抽出 PSS 峰值答题卡。 */
|
|
90
|
+
function extractPssFromView(brief, summary) {
|
|
91
|
+
const sources = [brief, summary];
|
|
92
|
+
for (const src of sources) {
|
|
93
|
+
const peak = numOf(src['android_memory_pss_maximum']) ??
|
|
94
|
+
numOf(src['android_memory_pss@max']) ??
|
|
95
|
+
numOf(src['PSS峰值']) ??
|
|
96
|
+
numOf(src['设备内存峰值(MB)']);
|
|
97
|
+
const frame = numOf(src['android_memory_pss_maximum_frame']) ??
|
|
98
|
+
numOf(src['android_memory_pss@max_frame']) ??
|
|
99
|
+
null;
|
|
100
|
+
if (peak == null)
|
|
101
|
+
continue;
|
|
102
|
+
// brief 里设备内存峰值多为 MB;android_memory_pss 多为 KB
|
|
103
|
+
const looksMb = peak < 50_000 && (src['设备内存峰值(MB)'] !== undefined || peak < 2048);
|
|
104
|
+
const peakKb = looksMb ? Math.round(peak * 1024) : peak;
|
|
105
|
+
return {
|
|
106
|
+
pss_peak_kb: peakKb,
|
|
107
|
+
pss_peak_mb: Math.round((peakKb / 1024) * 100) / 100,
|
|
108
|
+
pss_peak_frame: frame,
|
|
109
|
+
pss_unit: 'KB',
|
|
110
|
+
_pssHint: 'Android PSS 峰值请优先读 pss_peak_kb/mb/frame;合法面板名 android_memory_pss@max(不是 android_pss_max)。',
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
/** 优先走 indicator statistic 拿完整 PSS 峰值+帧;失败再回退 overview brief。 */
|
|
116
|
+
async function resolvePssPeakCard(client, identity, brief, summary, apisUsed, limitations) {
|
|
117
|
+
const keyQuery = {
|
|
118
|
+
indicatorDashboards: 'android_memory_pss@max',
|
|
119
|
+
};
|
|
120
|
+
if (identity.dataKey)
|
|
121
|
+
keyQuery['dataKey'] = identity.dataKey;
|
|
122
|
+
else if (identity.recordId != null)
|
|
123
|
+
keyQuery['recordId'] = identity.recordId;
|
|
124
|
+
else
|
|
125
|
+
return extractPssFromView(brief, summary);
|
|
126
|
+
try {
|
|
127
|
+
const raw = await client.call('GET', '/openapi/v1/data/gotonline/overview/indicator/statistic/dashboard', 'v1.0.1', keyQuery);
|
|
128
|
+
apisUsed.push('gotonline_overview_indicator_statistic_dashboard');
|
|
129
|
+
const card = extractPssPeakCard(raw);
|
|
130
|
+
if (card)
|
|
131
|
+
return card;
|
|
132
|
+
limitations.push('indicator PSS 面板无峰值字段,已回退 overview 摘要');
|
|
133
|
+
}
|
|
134
|
+
catch (e) {
|
|
135
|
+
limitations.push(`PSS 面板查询失败,已回退 overview 摘要:${e instanceof Error ? e.message : String(e)}`);
|
|
136
|
+
}
|
|
137
|
+
return extractPssFromView(brief, summary);
|
|
138
|
+
}
|
|
139
|
+
function isWaitForVsyncName(name) {
|
|
140
|
+
return /wait\s*for\s*vsync|WaitForVsync|Gfx\.WaitForPresent/i.test(String(name ?? ''));
|
|
141
|
+
}
|
|
142
|
+
function buildBottleneckCandidates(topFunctions, gpuBoundEmpty) {
|
|
143
|
+
const items = topFunctions && Array.isArray(topFunctions['items']) ? topFunctions['items'] : [];
|
|
144
|
+
const out = [];
|
|
145
|
+
for (const raw of items.slice(0, 8)) {
|
|
146
|
+
if (!isObj(raw))
|
|
147
|
+
continue;
|
|
148
|
+
const name = raw['name'] ?? raw['methodName'] ?? raw['stackMethodName'];
|
|
149
|
+
const wait = isWaitForVsyncName(name);
|
|
150
|
+
out.push({
|
|
151
|
+
name,
|
|
152
|
+
selfTimeMean: raw['selfTimeMean'] ?? raw['value'] ?? null,
|
|
153
|
+
source: 'top_functions',
|
|
154
|
+
role: wait ? 'frame_sync_wait' : 'cpu_candidate',
|
|
155
|
+
note: wait
|
|
156
|
+
? 'WaitForVsync/Present 等待是帧同步表现,不能直接当卡顿根因或 GPU Bound 证据'
|
|
157
|
+
: '需结合 stutter 合并树确认是否为卡顿根因',
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
if (gpuBoundEmpty === true) {
|
|
161
|
+
out.push({
|
|
162
|
+
name: 'GPU Bound',
|
|
163
|
+
source: 'gotonline_gpu_bound',
|
|
164
|
+
role: 'not_detected',
|
|
165
|
+
note: 'targetFps=30 下未检出 GPU Bound 区间',
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
return out;
|
|
169
|
+
}
|
|
60
170
|
export const reportDiagnosisTool = {
|
|
61
171
|
id: 'report_diagnosis',
|
|
62
172
|
title: '报告体检',
|
|
63
173
|
engines: ['unity', 'unreal'],
|
|
64
174
|
filterKeys: ['report_diagnosis', 'composite', 'preset.default', 'overview'],
|
|
65
175
|
description: [
|
|
66
|
-
'对单份 GOT Online 报告做一次结构化体检:自动选对 Overview 统计 v1/v2,并串联 Top 资源、Top
|
|
176
|
+
'对单份 GOT Online 报告做一次结构化体检:自动选对 Overview 统计 v1/v2,并串联 Top 资源、Top 函数、场景摘要与 GPU Bound@30。',
|
|
67
177
|
'适用引擎:Unity / UE|复合工具',
|
|
68
178
|
'调用方不必自己判断 2026-07-09 / 2026-03-25 分界或 SDK 版本——内部按 get_report_detail 路由。',
|
|
69
|
-
'
|
|
70
|
-
'
|
|
179
|
+
'返回答题卡:forbidden_claims、bottleneck_candidates(与 topFunctions 分栏)、gpuBound、pss_peak_*、comparability_note。',
|
|
180
|
+
'卡顿根因:优先 stutter_full_tree / stutter_scene_tree;禁止把 WaitForVsync 写成根因或 GPU 主瓶颈。',
|
|
181
|
+
'gpuBound 为空时只能说「未检出 Bound」,禁止断言 GPU 是主要瓶颈。',
|
|
182
|
+
'查面板统计/曲线前先调 gotonline_overview_indicator_dashboard_keys(勿把 fps_mean 等统计字段当面板名)。',
|
|
183
|
+
'scenes 含 list_scenes 起止帧(sceneIdx/sceneName/frameStart/frameEnd)。',
|
|
71
184
|
'返回摘要而非原始大包;需要明细时再按 _apisUsed 中的原子工具深挖。',
|
|
72
185
|
].join('\n'),
|
|
73
186
|
inputSchema: {
|
|
@@ -121,16 +234,68 @@ export const reportDiagnosisTool = {
|
|
|
121
234
|
apisUsed.push(scene.target.id);
|
|
122
235
|
}
|
|
123
236
|
else {
|
|
124
|
-
limitations.push('当前引擎无 Unity
|
|
237
|
+
limitations.push('当前引擎无 Unity 场景统计路由,已跳过场景统计摘要');
|
|
125
238
|
}
|
|
126
239
|
}
|
|
127
240
|
catch (e) {
|
|
128
241
|
limitations.push(`场景统计失败:${e instanceof Error ? e.message : String(e)}`);
|
|
129
242
|
}
|
|
243
|
+
// list_scenes 补起止帧(场景统计摘要经常只有名称/索引)
|
|
244
|
+
try {
|
|
245
|
+
const keyQuery = {};
|
|
246
|
+
if (identity.dataKey)
|
|
247
|
+
keyQuery['dataKey'] = identity.dataKey;
|
|
248
|
+
else if (identity.recordId)
|
|
249
|
+
keyQuery['recordId'] = identity.recordId;
|
|
250
|
+
const sceneList = await ctx.client.call('GET', '/openapi/v1/data/gotonline/scene/info', 'v1.0.1', keyQuery);
|
|
251
|
+
const list = normalizeSceneList(sceneList);
|
|
252
|
+
if (list.length) {
|
|
253
|
+
scenes = { ...(scenes ?? {}), sceneCount: list.length, scenes: list };
|
|
254
|
+
if (!apisUsed.includes('list_scenes'))
|
|
255
|
+
apisUsed.push('list_scenes');
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
catch (e) {
|
|
259
|
+
limitations.push(`场景列表(list_scenes)失败:${e instanceof Error ? e.message : String(e)}`);
|
|
260
|
+
}
|
|
261
|
+
let gpuBound = null;
|
|
262
|
+
let gpuBoundEmpty = null;
|
|
263
|
+
try {
|
|
264
|
+
const boundQuery = { targetFps: 30 };
|
|
265
|
+
if (identity.dataKey)
|
|
266
|
+
boundQuery['dataKey'] = identity.dataKey;
|
|
267
|
+
else if (identity.recordId)
|
|
268
|
+
boundQuery['recordId'] = identity.recordId;
|
|
269
|
+
gpuBound = await ctx.client.call('GET', '/openapi/v1/data/gotonline/gpu/bound', 'v1.0.1', boundQuery);
|
|
270
|
+
apisUsed.push('gotonline_gpu_bound');
|
|
271
|
+
const intervals = Array.isArray(gpuBound)
|
|
272
|
+
? gpuBound
|
|
273
|
+
: isObj(gpuBound) && Array.isArray(gpuBound['data'])
|
|
274
|
+
? gpuBound['data']
|
|
275
|
+
: null;
|
|
276
|
+
gpuBoundEmpty = Array.isArray(intervals) ? intervals.length === 0 : null;
|
|
277
|
+
}
|
|
278
|
+
catch (e) {
|
|
279
|
+
limitations.push(`gpu_bound 失败:${e instanceof Error ? e.message : String(e)}`);
|
|
280
|
+
}
|
|
130
281
|
// 仅在明确未开启时提示;hasResource === null 表示无法判定,不误报关闭
|
|
131
282
|
if (view.hasResource === false) {
|
|
132
283
|
limitations.push('未开启资源采集,逐资源 Top 可能为空');
|
|
133
284
|
}
|
|
285
|
+
const forbidden_claims = [];
|
|
286
|
+
if (gpuBoundEmpty === true) {
|
|
287
|
+
forbidden_claims.push('GPU是主要瓶颈', 'GPU Bound明显', 'GPU主瓶颈');
|
|
288
|
+
}
|
|
289
|
+
forbidden_claims.push('WaitForVsync是卡顿根因');
|
|
290
|
+
const pssCard = await resolvePssPeakCard(ctx.client, identity, view.brief, view.summary, apisUsed, limitations);
|
|
291
|
+
const sceneCount = isObj(scenes) ? Number(scenes['sceneCount'] ?? 0) : 0;
|
|
292
|
+
const comparability_note = [
|
|
293
|
+
'与其它报告对比时:请核对场景名/数量、测试时长、目标帧率、SDK/引擎版本是否一致。',
|
|
294
|
+
sceneCount > 0 ? `本报告场景数=${sceneCount}。` : '',
|
|
295
|
+
'仅比较 FPS 均值不能视为严格同场景对比。',
|
|
296
|
+
]
|
|
297
|
+
.filter(Boolean)
|
|
298
|
+
.join(' ');
|
|
134
299
|
const result = {
|
|
135
300
|
report: {
|
|
136
301
|
dataKey: identity.dataKey,
|
|
@@ -146,6 +311,11 @@ export const reportDiagnosisTool = {
|
|
|
146
311
|
hasResource: view.hasResource,
|
|
147
312
|
summary: view.summary,
|
|
148
313
|
brief: pickImportantBrief(view.brief),
|
|
314
|
+
...(pssCard ?? {}),
|
|
315
|
+
gpuBound: Array.isArray(gpuBound) ? gpuBound : isObj(gpuBound) ? gpuBound['data'] ?? gpuBound : gpuBound,
|
|
316
|
+
gpuBoundEmpty,
|
|
317
|
+
forbidden_claims,
|
|
318
|
+
bottleneck_candidates: buildBottleneckCandidates(topFunctions, gpuBoundEmpty),
|
|
149
319
|
topResources: topResources
|
|
150
320
|
? {
|
|
151
321
|
items: topResources['items'],
|
|
@@ -159,12 +329,14 @@ export const reportDiagnosisTool = {
|
|
|
159
329
|
metric: topFunctions['metric'],
|
|
160
330
|
uniqueFunctions: topFunctions['uniqueFunctions'],
|
|
161
331
|
note: topFunctions['_note'],
|
|
332
|
+
_columnNote: 'topFunctions 是耗时排行,不等于卡顿根因;根因看 stutter 合并树与 bottleneck_candidates。',
|
|
162
333
|
}
|
|
163
334
|
: null,
|
|
164
335
|
scenes,
|
|
336
|
+
comparability_note,
|
|
165
337
|
_apisUsed: apisUsed,
|
|
166
338
|
_limitations: limitations,
|
|
167
|
-
_note: '
|
|
339
|
+
_note: '结构化体检摘要(含答题卡)。GPU Bound 空时禁止 GPU 主瓶颈结论;卡顿根因优先 stutter 树。细节请按 _apisUsed 调用对应原子工具。',
|
|
168
340
|
};
|
|
169
341
|
return jsonToolResult(result, ctx.maxChars);
|
|
170
342
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAPI / 下游错误码 → AI 友好排查建议。
|
|
3
|
+
* 产品语义按引擎在此投影;got-query 只提供稳定 rawMessage 原因码。
|
|
4
|
+
*/
|
|
5
|
+
export declare const FILE_META_REASONS: {
|
|
6
|
+
readonly LG_META_ABSENT: "LG_META_ABSENT";
|
|
7
|
+
readonly LG_RUNTIME_PATH_MISSING: "LG_RUNTIME_PATH_MISSING";
|
|
8
|
+
readonly AT_META_ABSENT: "AT_META_ABSENT";
|
|
9
|
+
readonly AT_META_TYPE_INVALID: "AT_META_TYPE_INVALID";
|
|
10
|
+
readonly AT_META_PATH_MISSING: "AT_META_PATH_MISSING";
|
|
11
|
+
};
|
|
12
|
+
/** 组装给模型看的排查建议;优先 rawMessage 原因码 + OpenAPI message。 */
|
|
13
|
+
export declare function resolveErrorHint(code: number, rawMessage: string, apiMessage: string): string | undefined;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAPI / 下游错误码 → AI 友好排查建议。
|
|
3
|
+
* 产品语义按引擎在此投影;got-query 只提供稳定 rawMessage 原因码。
|
|
4
|
+
*/
|
|
5
|
+
export const FILE_META_REASONS = {
|
|
6
|
+
LG_META_ABSENT: 'LG_META_ABSENT',
|
|
7
|
+
LG_RUNTIME_PATH_MISSING: 'LG_RUNTIME_PATH_MISSING',
|
|
8
|
+
AT_META_ABSENT: 'AT_META_ABSENT',
|
|
9
|
+
AT_META_TYPE_INVALID: 'AT_META_TYPE_INVALID',
|
|
10
|
+
AT_META_PATH_MISSING: 'AT_META_PATH_MISSING',
|
|
11
|
+
};
|
|
12
|
+
const ERROR_HINTS_BY_CODE = {
|
|
13
|
+
20001: '业务参数不合法,核对参数名、取值范围和必填项(注意批量接口的参数名多为复数,如 dataKeys)',
|
|
14
|
+
23508: '数据服务错误,常见原因是报告不适用该接口版本(如新报告调用了 1.0 接口,或旧报告调用了 2.0 接口),改用对应版本重试',
|
|
15
|
+
24050: '该账号未开通 Open API 权限,请联系 UWA 工作人员开通',
|
|
16
|
+
24052: '请求参数有误,请对照接口文档检查',
|
|
17
|
+
24054: 'AppId 不存在,检查 appId 是否正确、是否用错了环境(sandbox / 线上凭证不通用)',
|
|
18
|
+
24056: '签名错误,检查 appSecret 是否正确',
|
|
19
|
+
24057: '时间戳过期,本机时间与服务端偏差不能超过 20 分钟',
|
|
20
|
+
30001: '服务端错误,常见原因是用错了引擎对应的接口(如对 UE 报告调用了 Unity 专用接口);勿把瞬时失败说成「一定没有数据」',
|
|
21
|
+
};
|
|
22
|
+
/** OpenAPI 已按引擎写好的稳定文案(优先沿用,再补 AI 纪律)。 */
|
|
23
|
+
function hintForOpenApiRuntimeLogMessage(apiMessage) {
|
|
24
|
+
if (/暂不支持获取当前报告的运行日志/.test(apiMessage)) {
|
|
25
|
+
return 'UE 运行日志方案有过切换:只能说暂不支持获取当前报告的运行日志,勿断定报告本身无日志。旧报告可试 gotonline_overview_log_export。';
|
|
26
|
+
}
|
|
27
|
+
if (/运行日志未解析|运行日志解析未完成/.test(apiMessage)) {
|
|
28
|
+
return 'Unity:该报告无可用运行日志数据(未解析或解析未完成)。旧报告可试 gotonline_overview_log_export。';
|
|
29
|
+
}
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
function hintForFileMetaReason(rawMessage, apiMessage) {
|
|
33
|
+
const fromApi = hintForOpenApiRuntimeLogMessage(apiMessage);
|
|
34
|
+
if (fromApi)
|
|
35
|
+
return fromApi;
|
|
36
|
+
if (rawMessage.includes(FILE_META_REASONS.LG_META_ABSENT) || rawMessage.includes(FILE_META_REASONS.LG_RUNTIME_PATH_MISSING)) {
|
|
37
|
+
return ('运行日志 file_meta 不可用(原因码见 rawMessage)。' +
|
|
38
|
+
'Unity:通常表示未解析/无可用日志数据;UE:只能说暂不支持获取当前报告的运行日志,勿断定报告本身无日志。' +
|
|
39
|
+
'旧报告可试 gotonline_overview_log_export。');
|
|
40
|
+
}
|
|
41
|
+
if (rawMessage.includes(FILE_META_REASONS.AT_META_ABSENT) ||
|
|
42
|
+
rawMessage.includes(FILE_META_REASONS.AT_META_TYPE_INVALID) ||
|
|
43
|
+
rawMessage.includes(FILE_META_REASONS.AT_META_PATH_MISSING)) {
|
|
44
|
+
return 'AT 资源 meta 不可用:可能未开启资源采集或未解析完成,勿把失败说成「一定没有资源」。';
|
|
45
|
+
}
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
/** 组装给模型看的排查建议;优先 rawMessage 原因码 + OpenAPI message。 */
|
|
49
|
+
export function resolveErrorHint(code, rawMessage, apiMessage) {
|
|
50
|
+
if (code === 80108) {
|
|
51
|
+
return (hintForFileMetaReason(rawMessage, apiMessage) ??
|
|
52
|
+
'文件 meta 不存在(FILE_META_NOT_EXIST)。运行日志/AT 等场景请结合接口 message 与报告引擎解释;勿臆造数据。');
|
|
53
|
+
}
|
|
54
|
+
return ERROR_HINTS_BY_CODE[code];
|
|
55
|
+
}
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
/** indicatorDashboards 合法面板传参名称(与 got-query OverviewIndicatorDashboardCatalogService 同源)。 */
|
|
2
2
|
export declare const INDICATOR_DASHBOARD_KEYS: readonly string[];
|
|
3
3
|
/** 常见误传:统计维度后缀 / 子指标名,不是面板标识符。 */
|
|
4
|
-
export declare const INDICATOR_DASHBOARD_NEGATIVE_EXAMPLES: readonly ["fps_mean", "fps_maximum", "frametime_min", "frametime_gt_40_pct", "temperature_mean", "drawcall_maximum"];
|
|
4
|
+
export declare const INDICATOR_DASHBOARD_NEGATIVE_EXAMPLES: readonly ["fps_mean", "fps_maximum", "frametime_min", "frametime_gt_40_pct", "temperature_mean", "drawcall_maximum", "android_pss_max"];
|
|
5
5
|
export declare function splitIndicatorDashboards(raw: unknown): string[];
|
|
6
6
|
export declare function findUnknownIndicatorDashboards(raw: unknown): string[];
|
|
7
|
+
/** 拆成已接受 / 未识别面板名(保持请求顺序,已接受去重)。 */
|
|
8
|
+
export declare function partitionIndicatorDashboards(raw: unknown): {
|
|
9
|
+
accepted: string[];
|
|
10
|
+
unknown: string[];
|
|
11
|
+
all: string[];
|
|
12
|
+
};
|
|
@@ -12,6 +12,7 @@ export const INDICATOR_DASHBOARD_NEGATIVE_EXAMPLES = [
|
|
|
12
12
|
'frametime_gt_40_pct',
|
|
13
13
|
'temperature_mean',
|
|
14
14
|
'drawcall_maximum',
|
|
15
|
+
'android_pss_max',
|
|
15
16
|
];
|
|
16
17
|
export function splitIndicatorDashboards(raw) {
|
|
17
18
|
if (typeof raw !== 'string' || !raw.trim())
|
|
@@ -28,3 +29,22 @@ export function findUnknownIndicatorDashboards(raw) {
|
|
|
28
29
|
const valid = new Set(INDICATOR_DASHBOARD_KEYS);
|
|
29
30
|
return parts.filter((k) => !valid.has(k));
|
|
30
31
|
}
|
|
32
|
+
/** 拆成已接受 / 未识别面板名(保持请求顺序,已接受去重)。 */
|
|
33
|
+
export function partitionIndicatorDashboards(raw) {
|
|
34
|
+
const all = splitIndicatorDashboards(raw);
|
|
35
|
+
const valid = new Set(INDICATOR_DASHBOARD_KEYS);
|
|
36
|
+
const accepted = [];
|
|
37
|
+
const unknown = [];
|
|
38
|
+
const seen = new Set();
|
|
39
|
+
for (const k of all) {
|
|
40
|
+
if (!valid.has(k)) {
|
|
41
|
+
unknown.push(k);
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
if (seen.has(k))
|
|
45
|
+
continue;
|
|
46
|
+
seen.add(k);
|
|
47
|
+
accepted.push(k);
|
|
48
|
+
}
|
|
49
|
+
return { accepted, unknown, all };
|
|
50
|
+
}
|
package/dist/presets.js
CHANGED
|
@@ -22,9 +22,14 @@ export const PRESET_DEFAULT = [
|
|
|
22
22
|
'list_optimization_tasks',
|
|
23
23
|
'gotonline_overview_indicator_statistic_dashboard',
|
|
24
24
|
'gotonline_overview_indicator_curve_dashboard',
|
|
25
|
+
'gotonline_overview_indicator_dashboard_keys',
|
|
25
26
|
'gotonline_overview_memory_manage_data_report',
|
|
26
27
|
'gotonline_overview_method_group_statistic',
|
|
28
|
+
'gotonline_overview_stack_id_map',
|
|
27
29
|
'gotonline_overview_stack_stutter_full_presign',
|
|
30
|
+
'gotonline_overview_stack_stutter_full_tree_presign',
|
|
31
|
+
'gotonline_overview_stack_stutter_scene_tree_presign',
|
|
32
|
+
'gotonline_overview_stack_tree_frame_presign',
|
|
28
33
|
// 复合工具(手写,见 src/composite/)
|
|
29
34
|
'top_resources',
|
|
30
35
|
'top_functions',
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP server instructions:客户裸问时的全局纪律(勿依赖用户粘贴规则)。
|
|
3
|
+
* 与 docs/EMPTY_STATE_CONTRACT.md 对齐。
|
|
4
|
+
*/
|
|
5
|
+
export const SERVER_INSTRUCTIONS = [
|
|
6
|
+
'你是 UWA GOT Online OpenAPI MCP 助手。用人话查报告时遵守以下纪律:',
|
|
7
|
+
'',
|
|
8
|
+
'1. 多份候选报告:先列出备注/设备/时间,再选或先问用户;禁止静默默认却假装只有一份。',
|
|
9
|
+
'2. 没有某类报告(如 GPU/Mono):看 list 返回的 module_available;为 false 时明确说没有并停手。禁止用 Overview 的 RenderTexture / WaitForVsync 冒充 GPU 分析。',
|
|
10
|
+
'3. 说「GPU Bound / GPU 是主要瓶颈」之前:必须先查 gotonline_gpu_bound(或看 report_diagnosis.gpuBound)。空数组 [] 只能说「未检出 Bound」,不能说主要瓶颈是 GPU。',
|
|
11
|
+
'4. 卡顿根因:优先 gotonline_overview_stack_stutter_full_tree_presign / stutter_scene_tree;禁止把 WaitForVsync 直接当根因。尖峰再查单帧树。',
|
|
12
|
+
'5. 非法面板名要纠正(fps_mean→fps_avg;android_pss_max→android_memory_pss@max);数值带单位。PSS 优先读 pss_peak_kb/mb/frame。',
|
|
13
|
+
'6. 接口失败时说明原因:运行日志 80108 — Unity 无 LG file_meta 表示未解析、无可用日志;UE 无 meta 只能说「暂不支持获取当前报告的运行日志」(方案切换过),勿断定报告本身无日志。其它失败勿把瞬时错误说成「一定没有数据」。',
|
|
14
|
+
'',
|
|
15
|
+
'体检优先调用 report_diagnosis;细节再按 _apisUsed 深挖。对比多份报告时说明场景/时长/目标帧是否可比(见 comparability_note)。',
|
|
16
|
+
].join('\n');
|
package/dist/server.js
CHANGED
|
@@ -8,6 +8,7 @@ import { registerCompositeTools, COMPOSITE_TOOLS, selectCompositeTools } from '.
|
|
|
8
8
|
import { selectOperations } from './presets.js';
|
|
9
9
|
import { loadSpec } from './spec.js';
|
|
10
10
|
import { makeHandler, toolConfig, toolName } from './tools.js';
|
|
11
|
+
import { SERVER_INSTRUCTIONS } from './server-instructions.js';
|
|
11
12
|
function readPackageVersion() {
|
|
12
13
|
try {
|
|
13
14
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
@@ -28,7 +29,7 @@ export function createServer(opts) {
|
|
|
28
29
|
credentials: { appId: opts.appId, appSecret: opts.appSecret },
|
|
29
30
|
timeoutMs: opts.timeoutMs,
|
|
30
31
|
});
|
|
31
|
-
const server = new McpServer({ name: 'uwa-openapi-mcp', version: PACKAGE_VERSION });
|
|
32
|
+
const server = new McpServer({ name: 'uwa-openapi-mcp', version: PACKAGE_VERSION }, { instructions: SERVER_INSTRUCTIONS });
|
|
32
33
|
for (const op of operations) {
|
|
33
34
|
server.registerTool(toolName(op.id, opts.nameCase, opts.namePrefix), toolConfig(op), makeHandler(op, client, opts.maxRows, opts.maxChars));
|
|
34
35
|
}
|
package/dist/tools.d.ts
CHANGED
|
@@ -32,6 +32,9 @@ export declare function annotateCurveAxes(data: unknown): unknown;
|
|
|
32
32
|
* 自定义面板 statistic/curve 返回空 `{}` 时,优先提示面板名传错(静默失败高发)。
|
|
33
33
|
*/
|
|
34
34
|
export declare function annotateDashboardEmptyResult(op: Operation, data: unknown, args: Record<string, unknown>): unknown;
|
|
35
|
+
/** 各 GOT Online 子模块是否有报告;供模型「无 GPU 停手」。 */
|
|
36
|
+
export declare function buildModuleAvailable(group: Record<string, unknown>): Record<string, boolean>;
|
|
37
|
+
export declare function annotateModuleAvailable(data: unknown): unknown;
|
|
35
38
|
/**
|
|
36
39
|
* 报告列表接口一次返回、按项目组分组。但上游会把同一份报告挂到每个项目组下
|
|
37
40
|
* (沙箱实测多组两两交集 100%),直接按组统计会成倍放大。
|
|
@@ -40,6 +43,8 @@ export declare function annotateDashboardEmptyResult(op: Operation, data: unknow
|
|
|
40
43
|
* 不额外打接口。上游修好后检测不到重复,本函数原样返回。
|
|
41
44
|
*/
|
|
42
45
|
export declare function annotateProjectGroups(data: unknown): unknown;
|
|
46
|
+
/** 空 Bound / 空日志列表等:补可教模型的语义,避免误判。 */
|
|
47
|
+
export declare function annotateEmptyStateSemantics(op: Operation, data: unknown): unknown;
|
|
43
48
|
export declare function makeHandler(op: Operation, client: UwaClient, defaultMaxRows: number, maxChars: number): (args: Record<string, unknown>) => Promise<ToolResult>;
|
|
44
49
|
export declare function toolConfig(op: Operation): {
|
|
45
50
|
title: string;
|