@aiscene/shared 8.0.1 → 8.0.3

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 (92) hide show
  1. package/dist/es/cli/cli-args.mjs +95 -0
  2. package/dist/es/cli/cli-error.mjs +24 -0
  3. package/dist/es/cli/cli-runner.mjs +10 -40
  4. package/dist/es/cli/index.mjs +4 -2
  5. package/dist/es/constants/example-code.mjs +2 -2
  6. package/dist/es/constants/index.mjs +6 -1
  7. package/dist/es/env/model-config-manager.mjs +3 -0
  8. package/dist/es/env/parse-model-config.mjs +6 -1
  9. package/dist/es/env/types.mjs +2 -1
  10. package/dist/es/env/utils.mjs +1 -9
  11. package/dist/es/extractor/index.mjs +2 -2
  12. package/dist/es/img/index.mjs +2 -2
  13. package/dist/es/img/info.mjs +7 -1
  14. package/dist/es/key-alias-utils.mjs +19 -0
  15. package/dist/es/mcp/base-server.mjs +10 -9
  16. package/dist/es/mcp/base-tools.mjs +72 -4
  17. package/dist/es/mcp/cli-report-session.mjs +78 -0
  18. package/dist/es/mcp/error-formatter.mjs +19 -0
  19. package/dist/es/mcp/index.mjs +2 -0
  20. package/dist/es/mcp/init-arg-utils.mjs +38 -0
  21. package/dist/es/mcp/inject-report-html-plugin.mjs +1 -1
  22. package/dist/es/mcp/tool-generator.mjs +65 -17
  23. package/dist/es/node/fs.mjs +1 -1
  24. package/dist/lib/cli/cli-args.js +138 -0
  25. package/dist/lib/cli/cli-error.js +61 -0
  26. package/dist/lib/cli/cli-runner.js +19 -46
  27. package/dist/lib/cli/index.js +8 -3
  28. package/dist/lib/constants/example-code.js +2 -2
  29. package/dist/lib/constants/index.js +23 -3
  30. package/dist/lib/env/model-config-manager.js +3 -0
  31. package/dist/lib/env/parse-model-config.js +6 -1
  32. package/dist/lib/env/types.js +4 -0
  33. package/dist/lib/env/utils.js +0 -11
  34. package/dist/lib/extractor/index.js +3 -0
  35. package/dist/lib/img/index.js +3 -0
  36. package/dist/lib/img/info.js +10 -1
  37. package/dist/lib/key-alias-utils.js +62 -0
  38. package/dist/lib/mcp/base-server.js +11 -10
  39. package/dist/lib/mcp/base-tools.js +74 -6
  40. package/dist/lib/mcp/cli-report-session.js +121 -0
  41. package/dist/lib/mcp/error-formatter.js +53 -0
  42. package/dist/lib/mcp/index.js +24 -10
  43. package/dist/lib/mcp/init-arg-utils.js +78 -0
  44. package/dist/lib/mcp/inject-report-html-plugin.js +1 -1
  45. package/dist/lib/mcp/tool-generator.js +74 -26
  46. package/dist/lib/node/fs.js +1 -1
  47. package/dist/types/cli/cli-args.d.ts +8 -0
  48. package/dist/types/cli/cli-error.d.ts +5 -0
  49. package/dist/types/cli/cli-runner.d.ts +4 -7
  50. package/dist/types/cli/index.d.ts +3 -1
  51. package/dist/types/constants/example-code.d.ts +1 -1
  52. package/dist/types/constants/index.d.ts +5 -0
  53. package/dist/types/env/types.d.ts +10 -2
  54. package/dist/types/env/utils.d.ts +0 -31
  55. package/dist/types/extractor/index.d.ts +1 -1
  56. package/dist/types/img/index.d.ts +1 -1
  57. package/dist/types/img/info.d.ts +5 -0
  58. package/dist/types/key-alias-utils.d.ts +9 -0
  59. package/dist/types/mcp/base-tools.d.ts +74 -5
  60. package/dist/types/mcp/cli-report-session.d.ts +12 -0
  61. package/dist/types/mcp/error-formatter.d.ts +12 -0
  62. package/dist/types/mcp/index.d.ts +2 -0
  63. package/dist/types/mcp/init-arg-utils.d.ts +13 -0
  64. package/dist/types/mcp/inject-report-html-plugin.d.ts +2 -2
  65. package/dist/types/mcp/launcher-helper.d.ts +3 -3
  66. package/dist/types/mcp/tool-generator.d.ts +3 -3
  67. package/dist/types/mcp/types.d.ts +9 -0
  68. package/package.json +1 -1
  69. package/src/cli/cli-args.ts +173 -0
  70. package/src/cli/cli-error.ts +24 -0
  71. package/src/cli/cli-runner.ts +37 -56
  72. package/src/cli/index.ts +3 -7
  73. package/src/constants/example-code.ts +2 -2
  74. package/src/constants/index.ts +10 -0
  75. package/src/env/model-config-manager.ts +3 -0
  76. package/src/env/parse-model-config.ts +19 -1
  77. package/src/env/types.ts +11 -2
  78. package/src/env/utils.ts +0 -50
  79. package/src/extractor/index.ts +1 -1
  80. package/src/img/index.ts +2 -0
  81. package/src/img/info.ts +27 -0
  82. package/src/key-alias-utils.ts +23 -0
  83. package/src/mcp/base-server.ts +10 -12
  84. package/src/mcp/base-tools.ts +218 -11
  85. package/src/mcp/cli-report-session.ts +130 -0
  86. package/src/mcp/error-formatter.ts +52 -0
  87. package/src/mcp/index.ts +2 -0
  88. package/src/mcp/init-arg-utils.ts +105 -0
  89. package/src/mcp/inject-report-html-plugin.ts +3 -3
  90. package/src/mcp/launcher-helper.ts +3 -3
  91. package/src/mcp/tool-generator.ts +98 -20
  92. package/src/mcp/types.ts +16 -1
@@ -1,6 +1,19 @@
1
- import { parseBase64 } from '../img';
2
- import { getDebug } from '../logger';
1
+ import { parseBase64 } from '@aiscene/shared/img';
2
+ import { getDebug } from '@aiscene/shared/logger';
3
3
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
4
+ import type { z } from 'zod';
5
+ import { camelToKebab, getKeyAliases } from '../key-alias-utils';
6
+ import {
7
+ type CliReportSession,
8
+ generateCliReportSession,
9
+ readCliReportSession,
10
+ writeCliReportSession,
11
+ } from './cli-report-session';
12
+ import {
13
+ createNamespacedInitArgSchema,
14
+ extractNamespacedArgs,
15
+ sanitizeNamespacedArgs,
16
+ } from './init-arg-utils';
4
17
  import {
5
18
  generateCommonTools,
6
19
  generateToolsFromActionSpace,
@@ -10,22 +23,68 @@ import type {
10
23
  BaseAgent,
11
24
  BaseDevice,
12
25
  IMidsceneTools,
26
+ ToolCliMetadata,
13
27
  ToolDefinition,
28
+ ToolSchema,
14
29
  } from './types';
15
30
 
16
31
  const debug = getDebug('mcp:base-tools');
17
32
 
18
33
  /**
19
- * Base class for platform-specific MCP tools
20
- * Generic type TAgent allows subclasses to use their specific agent types
34
+ * Declarative description of a platform's agent init args.
35
+ * Collapses the `extractAgentInitParam` / `sanitizeToolArgs` /
36
+ * `getAgentInitArgSchema` trio into a single data declaration.
37
+ */
38
+ export interface InitArgSpec<TInitParam> {
39
+ /** Arg namespace, e.g. `android`, `ios`. */
40
+ namespace: string;
41
+ /** Zod shape describing the init args. Field names drive the MCP schema. */
42
+ shape: Record<string, z.ZodTypeAny>;
43
+ /**
44
+ * Optional CLI presentation hints. These affect `--help` output for
45
+ * single-platform CLIs but do not alter MCP/YAML protocol keys.
46
+ */
47
+ cli?: {
48
+ /** Prefer bare `--device-id`-style options in platform CLI help output. */
49
+ preferBareKeys?: boolean;
50
+ /** Override the displayed option name for specific init arg fields. */
51
+ preferredNames?: Record<string, string>;
52
+ };
53
+ /**
54
+ * Adapt extracted namespaced args into the concrete `TInitParam` passed to
55
+ * `ensureAgent`. Defaults to returning the raw extracted record.
56
+ */
57
+ adapt?: (
58
+ extracted: Record<string, unknown> | undefined,
59
+ ) => TInitParam | undefined;
60
+ }
61
+
62
+ /**
63
+ * Base class for platform-specific MCP tools.
64
+ * @typeParam TAgent - Platform-specific agent type.
65
+ * @typeParam TInitParam - Platform-specific init parameter consumed by
66
+ * `ensureAgent`. Defaults to `undefined` for platforms that take no args.
21
67
  */
22
- export abstract class BaseMidsceneTools<TAgent extends BaseAgent = BaseAgent>
23
- implements IMidsceneTools
68
+ export abstract class BaseMidsceneTools<
69
+ TAgent extends BaseAgent = BaseAgent,
70
+ TInitParam = unknown,
71
+ > implements IMidsceneTools
24
72
  {
25
73
  protected mcpServer?: McpServer;
26
74
  protected agent?: TAgent;
27
75
  protected toolDefinitions: ToolDefinition[] = [];
28
76
 
77
+ /**
78
+ * Declarative init-arg spec. Subclasses that accept CLI/MCP init args should
79
+ * set this once and get `extractAgentInitParam` / `sanitizeToolArgs` /
80
+ * `getAgentInitArgSchema` auto-implemented.
81
+ *
82
+ * Declared with `declare` so that TS doesn't emit an `Object.defineProperty`
83
+ * for this field on the base constructor, which would otherwise overwrite
84
+ * a subclass field initializer under `useDefineForClassFields`.
85
+ */
86
+ protected declare readonly initArgSpec?: InitArgSpec<TInitParam>;
87
+
29
88
  /**
30
89
  * Ensure agent is initialized and ready for use.
31
90
  * Must be implemented by subclasses to create platform-specific agent.
@@ -33,7 +92,102 @@ export abstract class BaseMidsceneTools<TAgent extends BaseAgent = BaseAgent>
33
92
  * @returns Promise resolving to initialized agent instance
34
93
  * @throws Error if agent initialization fails
35
94
  */
36
- protected abstract ensureAgent(initParam?: string): Promise<TAgent>;
95
+ protected abstract ensureAgent(initParam?: TInitParam): Promise<TAgent>;
96
+
97
+ private getInitArgKeys(): readonly string[] {
98
+ return this.initArgSpec ? Object.keys(this.initArgSpec.shape) : [];
99
+ }
100
+
101
+ /**
102
+ * Extract a platform-specific agent init parameter from CLI/MCP tool args.
103
+ */
104
+ protected extractAgentInitParam(
105
+ args: Record<string, unknown>,
106
+ ): TInitParam | undefined {
107
+ if (!this.initArgSpec) {
108
+ return undefined;
109
+ }
110
+ const extracted = extractNamespacedArgs(
111
+ args,
112
+ this.initArgSpec.namespace,
113
+ this.getInitArgKeys(),
114
+ );
115
+ if (this.initArgSpec.adapt) {
116
+ return this.initArgSpec.adapt(extracted);
117
+ }
118
+ return extracted as TInitParam | undefined;
119
+ }
120
+
121
+ /**
122
+ * Remove platform-specific init args before dispatching a tool payload to the action itself.
123
+ */
124
+ protected sanitizeToolArgs(
125
+ args: Record<string, unknown>,
126
+ ): Record<string, unknown> {
127
+ if (!this.initArgSpec) {
128
+ return args;
129
+ }
130
+ return sanitizeNamespacedArgs(
131
+ args,
132
+ this.initArgSpec.namespace,
133
+ this.getInitArgKeys(),
134
+ );
135
+ }
136
+
137
+ /**
138
+ * Expose platform-specific init args on action/common tool schemas.
139
+ */
140
+ protected getAgentInitArgSchema(): ToolSchema {
141
+ if (!this.initArgSpec) {
142
+ return {};
143
+ }
144
+ return createNamespacedInitArgSchema(
145
+ this.initArgSpec.namespace,
146
+ this.initArgSpec.shape,
147
+ );
148
+ }
149
+
150
+ /**
151
+ * Expose CLI-only metadata for platform init args so single-platform help can
152
+ * show ergonomic bare flags while the underlying schema stays namespaced.
153
+ * When `preferBareKeys` is enabled, single-platform CLIs only accept the
154
+ * bare spellings; namespaced dotted spellings remain available through the
155
+ * MCP/YAML schema instead of the platform CLI surface.
156
+ */
157
+ protected getAgentInitArgCliMetadata(): ToolCliMetadata | undefined {
158
+ if (!this.initArgSpec?.cli) {
159
+ return undefined;
160
+ }
161
+
162
+ const options = Object.fromEntries(
163
+ this.getInitArgKeys().map((key) => {
164
+ const canonicalKey = `${this.initArgSpec!.namespace}.${key}`;
165
+ const preferredName =
166
+ this.initArgSpec!.cli?.preferredNames?.[key] ??
167
+ (this.initArgSpec!.cli?.preferBareKeys
168
+ ? camelToKebab(key)
169
+ : canonicalKey);
170
+
171
+ const acceptedNames = new Set<string>([
172
+ preferredName,
173
+ ...(this.initArgSpec!.cli?.preferBareKeys
174
+ ? getKeyAliases(key)
175
+ : getKeyAliases(canonicalKey)),
176
+ ]);
177
+ acceptedNames.delete(preferredName);
178
+
179
+ return [
180
+ canonicalKey,
181
+ {
182
+ preferredName,
183
+ aliases: [...acceptedNames],
184
+ },
185
+ ];
186
+ }),
187
+ );
188
+
189
+ return { options };
190
+ }
37
191
 
38
192
  /**
39
193
  * Optional: prepare platform-specific tools (e.g., device connection)
@@ -42,6 +196,52 @@ export abstract class BaseMidsceneTools<TAgent extends BaseAgent = BaseAgent>
42
196
  return [];
43
197
  }
44
198
 
199
+ protected getCliReportSessionName(): string | undefined {
200
+ return undefined;
201
+ }
202
+
203
+ protected createNewCliReportSession(
204
+ targetIdentity?: string,
205
+ ): CliReportSession | undefined {
206
+ const sessionName = this.getCliReportSessionName();
207
+ if (!sessionName) {
208
+ return undefined;
209
+ }
210
+ return generateCliReportSession(sessionName, targetIdentity);
211
+ }
212
+
213
+ protected commitCliReportSession(session?: CliReportSession): void {
214
+ if (session) {
215
+ writeCliReportSession(session);
216
+ }
217
+ }
218
+
219
+ protected readCliReportFileName(): string | undefined {
220
+ const sessionName = this.getCliReportSessionName();
221
+ if (!sessionName) {
222
+ return undefined;
223
+ }
224
+ return readCliReportSession(sessionName)?.reportFileName;
225
+ }
226
+
227
+ protected readCliReportAgentOptions():
228
+ | {
229
+ reportFileName: string;
230
+ reportAttributes: Record<string, string>;
231
+ }
232
+ | undefined {
233
+ const reportFileName = this.readCliReportFileName();
234
+ if (!reportFileName) {
235
+ return undefined;
236
+ }
237
+ return {
238
+ reportFileName,
239
+ reportAttributes: {
240
+ 'data-group-id': reportFileName,
241
+ },
242
+ };
243
+ }
244
+
45
245
  /**
46
246
  * Must be implemented by subclasses to create a temporary device instance
47
247
  * This allows getting real actionSpace without connecting to device
@@ -83,13 +283,20 @@ export abstract class BaseMidsceneTools<TAgent extends BaseAgent = BaseAgent>
83
283
  }
84
284
 
85
285
  // 3. Generate tools from action space (core innovation)
86
- const actionTools = generateToolsFromActionSpace(actionSpace, () =>
87
- this.ensureAgent(),
286
+ const actionTools = generateToolsFromActionSpace(
287
+ actionSpace,
288
+ (args = {}) => this.ensureAgent(this.extractAgentInitParam(args)),
289
+ (args = {}) => this.sanitizeToolArgs(args),
290
+ this.getAgentInitArgSchema(),
291
+ this.getAgentInitArgCliMetadata(),
88
292
  );
89
293
 
90
294
  // 4. Add common tools (screenshot, waitFor)
91
- const commonTools = generateCommonTools(() => this.ensureAgent());
92
-
295
+ const commonTools = generateCommonTools(
296
+ (args = {}) => this.ensureAgent(this.extractAgentInitParam(args)),
297
+ this.getAgentInitArgSchema(),
298
+ this.getAgentInitArgCliMetadata(),
299
+ );
93
300
  this.toolDefinitions.push(...actionTools, ...commonTools);
94
301
 
95
302
  debug('Total tools prepared:', this.toolDefinitions.length);
@@ -0,0 +1,130 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { getMidsceneRunBaseDir, getMidsceneRunSubDir } from '../common';
4
+
5
+ export interface CliReportSession {
6
+ version: 1;
7
+ sessionName: string;
8
+ targetIdentity?: string;
9
+ reportFileName: string;
10
+ reportPath: string;
11
+ createdAt: number;
12
+ }
13
+
14
+ const sessionDirName = 'cli-report-session';
15
+
16
+ function sanitizeSessionName(sessionName: string): string {
17
+ return sessionName.replace(/[^a-zA-Z0-9._-]/g, '_') || 'default';
18
+ }
19
+
20
+ function sanitizeFileSegment(segment: string): string {
21
+ const sanitized = segment.replace(/[^a-zA-Z0-9._-]/g, '_') || 'unknown';
22
+ return sanitized.slice(0, 80);
23
+ }
24
+
25
+ function ensureHtmlFileName(reportFileName: string): string {
26
+ return reportFileName.endsWith('.html')
27
+ ? reportFileName
28
+ : `${reportFileName}.html`;
29
+ }
30
+
31
+ function formatDateForFileName(date: Date): string {
32
+ const pad = (value: number) => String(value).padStart(2, '0');
33
+ const day = [
34
+ date.getFullYear(),
35
+ pad(date.getMonth() + 1),
36
+ pad(date.getDate()),
37
+ ].join('-');
38
+ const time = [
39
+ pad(date.getHours()),
40
+ pad(date.getMinutes()),
41
+ pad(date.getSeconds()),
42
+ ].join('-');
43
+ return `${day}_${time}`;
44
+ }
45
+
46
+ function randomId(): string {
47
+ return Math.random().toString(36).slice(2, 10);
48
+ }
49
+
50
+ function getCliReportSessionDir(): string {
51
+ const dir = join(getMidsceneRunBaseDir(), sessionDirName);
52
+ if (!existsSync(dir)) {
53
+ mkdirSync(dir, { recursive: true });
54
+ }
55
+ return dir;
56
+ }
57
+
58
+ function getCliReportSessionPath(sessionName: string): string {
59
+ return join(
60
+ getCliReportSessionDir(),
61
+ `${sanitizeSessionName(sessionName)}.json`,
62
+ );
63
+ }
64
+
65
+ export function generateCliReportSession(
66
+ sessionName: string,
67
+ targetIdentity?: string,
68
+ ): CliReportSession {
69
+ const identitySegment = targetIdentity
70
+ ? `-${sanitizeFileSegment(targetIdentity)}`
71
+ : '';
72
+ const reportFileName = `${sanitizeSessionName(sessionName)}${identitySegment}-${formatDateForFileName(new Date())}-${randomId()}`;
73
+ const reportPath = join(
74
+ getMidsceneRunSubDir('report'),
75
+ ensureHtmlFileName(reportFileName),
76
+ );
77
+ const session: CliReportSession = {
78
+ version: 1,
79
+ sessionName,
80
+ ...(targetIdentity ? { targetIdentity } : {}),
81
+ reportFileName,
82
+ reportPath,
83
+ createdAt: Date.now(),
84
+ };
85
+ return session;
86
+ }
87
+
88
+ export function writeCliReportSession(session: CliReportSession): void {
89
+ writeFileSync(
90
+ getCliReportSessionPath(session.sessionName),
91
+ JSON.stringify(session, null, 2),
92
+ 'utf-8',
93
+ );
94
+ }
95
+
96
+ export function createCliReportSession(
97
+ sessionName: string,
98
+ targetIdentity?: string,
99
+ ): CliReportSession {
100
+ const session = generateCliReportSession(sessionName, targetIdentity);
101
+ writeCliReportSession(session);
102
+ return session;
103
+ }
104
+
105
+ export function readCliReportSession(
106
+ sessionName: string,
107
+ ): CliReportSession | undefined {
108
+ const sessionPath = getCliReportSessionPath(sessionName);
109
+ if (!existsSync(sessionPath)) {
110
+ return undefined;
111
+ }
112
+
113
+ try {
114
+ const raw = readFileSync(sessionPath, 'utf-8');
115
+ const parsed = JSON.parse(raw) as Partial<CliReportSession>;
116
+ if (
117
+ parsed.version !== 1 ||
118
+ parsed.sessionName !== sessionName ||
119
+ typeof parsed.reportFileName !== 'string' ||
120
+ !parsed.reportFileName.trim() ||
121
+ /[\\/]/.test(parsed.reportFileName)
122
+ ) {
123
+ return undefined;
124
+ }
125
+
126
+ return parsed as CliReportSession;
127
+ } catch {
128
+ return undefined;
129
+ }
130
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Extract a human-readable message from an unknown thrown value.
3
+ *
4
+ * Many SDK/transport layers reject with structured objects (e.g.
5
+ * `{ code, message }`, `{ error: { message } }`, `{ cause: { message } }`)
6
+ * rather than `Error` instances. `String(obj)` collapses those to
7
+ * `"[object Object]"`, which is useless for diagnostics. This helper walks
8
+ * the common shapes, falls back to `JSON.stringify`, and finally to
9
+ * `Object.prototype.toString.call` so that callers always get something
10
+ * actionable in logs and surfaced tool results.
11
+ */
12
+ export function getErrorMessage(error: unknown): string {
13
+ if (error instanceof Error) return error.message;
14
+ if (error === null || error === undefined) return String(error);
15
+ if (typeof error !== 'object') return String(error);
16
+
17
+ const candidate = extractStringMessage(error);
18
+ if (candidate) return candidate;
19
+
20
+ try {
21
+ return JSON.stringify(error);
22
+ } catch {
23
+ return Object.prototype.toString.call(error);
24
+ }
25
+ }
26
+
27
+ function extractStringMessage(error: object): string | undefined {
28
+ const anyError = error as {
29
+ message?: unknown;
30
+ error?: { message?: unknown };
31
+ cause?: { message?: unknown };
32
+ };
33
+
34
+ if (typeof anyError.message === 'string' && anyError.message) {
35
+ return anyError.message;
36
+ }
37
+ if (
38
+ anyError.error &&
39
+ typeof anyError.error.message === 'string' &&
40
+ anyError.error.message
41
+ ) {
42
+ return anyError.error.message;
43
+ }
44
+ if (
45
+ anyError.cause &&
46
+ typeof anyError.cause.message === 'string' &&
47
+ anyError.cause.message
48
+ ) {
49
+ return anyError.cause.message;
50
+ }
51
+ return undefined;
52
+ }
package/src/mcp/index.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  export * from './base-server';
2
2
  export * from './base-tools';
3
+ export * from './init-arg-utils';
4
+ export * from './error-formatter';
3
5
  export * from './tool-generator';
4
6
  export * from './types';
5
7
  export * from './inject-report-html-plugin';
@@ -0,0 +1,105 @@
1
+ import type { z } from 'zod';
2
+ import { getKeyAliases, isRecord } from '../key-alias-utils';
3
+ import type { ToolSchema } from './types';
4
+
5
+ function readAliasedValue(
6
+ args: Record<string, unknown>,
7
+ key: string,
8
+ ): unknown | undefined {
9
+ for (const alias of getKeyAliases(key)) {
10
+ if (alias in args) {
11
+ return args[alias];
12
+ }
13
+ }
14
+
15
+ return undefined;
16
+ }
17
+
18
+ function readNamespacedArg(
19
+ args: Record<string, unknown>,
20
+ namespace: string,
21
+ key: string,
22
+ ): unknown | undefined {
23
+ // Lookup order: namespace object first, then flat dotted form, then bare key
24
+ // fallback. Namespace-aware inputs win so multi-platform callers cannot be
25
+ // cross-contaminated by a top-level bare `deviceId` leaking into the wrong
26
+ // platform.
27
+ const namespacedArgs = readAliasedValue(args, namespace);
28
+ if (isRecord(namespacedArgs)) {
29
+ const nestedValue = readAliasedValue(namespacedArgs, key);
30
+ if (nestedValue !== undefined) {
31
+ return nestedValue;
32
+ }
33
+ }
34
+
35
+ const dottedValue = readAliasedValue(args, `${namespace}.${key}`);
36
+ if (dottedValue !== undefined) {
37
+ return dottedValue;
38
+ }
39
+
40
+ const directValue = readAliasedValue(args, key);
41
+ if (directValue !== undefined) {
42
+ return directValue;
43
+ }
44
+
45
+ return undefined;
46
+ }
47
+
48
+ export function extractNamespacedArgs<
49
+ TFieldName extends string,
50
+ TArgs extends Record<string, unknown> = Record<string, unknown>,
51
+ >(
52
+ args: Record<string, unknown>,
53
+ namespace: string,
54
+ keys: readonly TFieldName[],
55
+ ): TArgs | undefined {
56
+ const extracted: Record<string, unknown> = {};
57
+
58
+ for (const key of keys) {
59
+ const value = readNamespacedArg(args, namespace, key);
60
+ if (value !== undefined) {
61
+ extracted[key] = value;
62
+ }
63
+ }
64
+
65
+ return Object.keys(extracted).length > 0 ? (extracted as TArgs) : undefined;
66
+ }
67
+
68
+ export function sanitizeNamespacedArgs(
69
+ args: Record<string, unknown>,
70
+ namespace: string,
71
+ keys: readonly string[],
72
+ ): Record<string, unknown> {
73
+ const excludedKeys = new Set<string>(getKeyAliases(namespace));
74
+
75
+ for (const key of keys) {
76
+ for (const alias of getKeyAliases(key)) {
77
+ excludedKeys.add(alias);
78
+ }
79
+
80
+ for (const alias of getKeyAliases(`${namespace}.${key}`)) {
81
+ excludedKeys.add(alias);
82
+ }
83
+ }
84
+
85
+ return Object.fromEntries(
86
+ Object.entries(args).filter(([key]) => !excludedKeys.has(key)),
87
+ );
88
+ }
89
+
90
+ /**
91
+ * Build a flat MCP tool schema whose keys are dotted `"<namespace>.<field>"`.
92
+ *
93
+ * We intentionally stay flat (rather than `{ namespace: z.object({...}) }`) so
94
+ * that CLI (`--android.device-id`), MCP clients, and `--help` output all share
95
+ * the same spelling. `readNamespacedArg` understands all three input shapes:
96
+ * nested namespace object, dotted flat key, and bare key fallback.
97
+ */
98
+ export function createNamespacedInitArgSchema(
99
+ namespace: string,
100
+ shape: Record<string, z.ZodTypeAny>,
101
+ ): ToolSchema {
102
+ return Object.fromEntries(
103
+ Object.entries(shape).map(([key, value]) => [`${namespace}.${key}`, value]),
104
+ );
105
+ }
@@ -10,12 +10,12 @@ interface RslibPluginApi {
10
10
  }
11
11
 
12
12
  /**
13
- * Rslib plugin to inject report HTML from @midscene/core dist into MCP bundle.
13
+ * Rslib plugin to inject report HTML from @aiscene/core dist into MCP bundle.
14
14
  * This runs after build and reads the already-injected HTML from core.
15
15
  *
16
16
  * Prerequisites:
17
17
  * - @midscene/report must be in devDependencies to ensure correct build order
18
- * - @midscene/core dist must exist with injected HTML
18
+ * - @aiscene/core dist must exist with injected HTML
19
19
  *
20
20
  * @param packageDir - The directory of the MCP package (use __dirname)
21
21
  */
@@ -35,7 +35,7 @@ export function injectReportHtmlFromCore(packageDir: string) {
35
35
 
36
36
  if (!fs.existsSync(coreUtilsPath)) {
37
37
  console.warn(
38
- '[inject-report-html] @midscene/core dist not found, skipping',
38
+ '[inject-report-html] @aiscene/core dist not found, skipping',
39
39
  );
40
40
  return;
41
41
  }
@@ -11,7 +11,7 @@ export interface LaunchMCPServerOptions extends HttpLaunchOptions {
11
11
  }
12
12
 
13
13
  /**
14
- * Generic agent type (avoid importing from @midscene/core to prevent circular deps)
14
+ * Generic agent type (avoid importing from @aiscene/core to prevent circular deps)
15
15
  */
16
16
  export interface GenericAgent<TDevice = any> {
17
17
  interface: TDevice;
@@ -64,8 +64,8 @@ export interface MCPServerLauncherConfig<
64
64
  *
65
65
  * @example
66
66
  * ```typescript
67
- * import { createMCPServerLauncher } from '@midscene/shared/mcp';
68
- * import { Agent } from '@midscene/core/agent';
67
+ * import { createMCPServerLauncher } from '@aiscene/shared/mcp';
68
+ * import { Agent } from '@aiscene/core/agent';
69
69
  * import { WebMidsceneTools } from './web-tools';
70
70
  * import { WebMCPServer } from './server';
71
71
  *