@aiscene/shared 8.0.2 → 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 +18 -21
  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 +21 -21
  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 +36 -45
  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
@@ -0,0 +1,78 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { getMidsceneRunBaseDir, getMidsceneRunSubDir } from "../common.mjs";
4
+ const sessionDirName = 'cli-report-session';
5
+ function sanitizeSessionName(sessionName) {
6
+ return sessionName.replace(/[^a-zA-Z0-9._-]/g, '_') || 'default';
7
+ }
8
+ function sanitizeFileSegment(segment) {
9
+ const sanitized = segment.replace(/[^a-zA-Z0-9._-]/g, '_') || 'unknown';
10
+ return sanitized.slice(0, 80);
11
+ }
12
+ function ensureHtmlFileName(reportFileName) {
13
+ return reportFileName.endsWith('.html') ? reportFileName : `${reportFileName}.html`;
14
+ }
15
+ function formatDateForFileName(date) {
16
+ const pad = (value)=>String(value).padStart(2, '0');
17
+ const day = [
18
+ date.getFullYear(),
19
+ pad(date.getMonth() + 1),
20
+ pad(date.getDate())
21
+ ].join('-');
22
+ const time = [
23
+ pad(date.getHours()),
24
+ pad(date.getMinutes()),
25
+ pad(date.getSeconds())
26
+ ].join('-');
27
+ return `${day}_${time}`;
28
+ }
29
+ function randomId() {
30
+ return Math.random().toString(36).slice(2, 10);
31
+ }
32
+ function getCliReportSessionDir() {
33
+ const dir = join(getMidsceneRunBaseDir(), sessionDirName);
34
+ if (!existsSync(dir)) mkdirSync(dir, {
35
+ recursive: true
36
+ });
37
+ return dir;
38
+ }
39
+ function getCliReportSessionPath(sessionName) {
40
+ return join(getCliReportSessionDir(), `${sanitizeSessionName(sessionName)}.json`);
41
+ }
42
+ function generateCliReportSession(sessionName, targetIdentity) {
43
+ const identitySegment = targetIdentity ? `-${sanitizeFileSegment(targetIdentity)}` : '';
44
+ const reportFileName = `${sanitizeSessionName(sessionName)}${identitySegment}-${formatDateForFileName(new Date())}-${randomId()}`;
45
+ const reportPath = join(getMidsceneRunSubDir('report'), ensureHtmlFileName(reportFileName));
46
+ const session = {
47
+ version: 1,
48
+ sessionName,
49
+ ...targetIdentity ? {
50
+ targetIdentity
51
+ } : {},
52
+ reportFileName,
53
+ reportPath,
54
+ createdAt: Date.now()
55
+ };
56
+ return session;
57
+ }
58
+ function writeCliReportSession(session) {
59
+ writeFileSync(getCliReportSessionPath(session.sessionName), JSON.stringify(session, null, 2), 'utf-8');
60
+ }
61
+ function createCliReportSession(sessionName, targetIdentity) {
62
+ const session = generateCliReportSession(sessionName, targetIdentity);
63
+ writeCliReportSession(session);
64
+ return session;
65
+ }
66
+ function readCliReportSession(sessionName) {
67
+ const sessionPath = getCliReportSessionPath(sessionName);
68
+ if (!existsSync(sessionPath)) return;
69
+ try {
70
+ const raw = readFileSync(sessionPath, 'utf-8');
71
+ const parsed = JSON.parse(raw);
72
+ if (1 !== parsed.version || parsed.sessionName !== sessionName || 'string' != typeof parsed.reportFileName || !parsed.reportFileName.trim() || /[\\/]/.test(parsed.reportFileName)) return;
73
+ return parsed;
74
+ } catch {
75
+ return;
76
+ }
77
+ }
78
+ export { createCliReportSession, generateCliReportSession, readCliReportSession, writeCliReportSession };
@@ -0,0 +1,19 @@
1
+ function getErrorMessage(error) {
2
+ if (error instanceof Error) return error.message;
3
+ if (null == error) return String(error);
4
+ if ('object' != typeof error) return String(error);
5
+ const candidate = extractStringMessage(error);
6
+ if (candidate) return candidate;
7
+ try {
8
+ return JSON.stringify(error);
9
+ } catch {
10
+ return Object.prototype.toString.call(error);
11
+ }
12
+ }
13
+ function extractStringMessage(error) {
14
+ const anyError = error;
15
+ if ('string' == typeof anyError.message && anyError.message) return anyError.message;
16
+ if (anyError.error && 'string' == typeof anyError.error.message && anyError.error.message) return anyError.error.message;
17
+ if (anyError.cause && 'string' == typeof anyError.cause.message && anyError.cause.message) return anyError.cause.message;
18
+ }
19
+ export { getErrorMessage };
@@ -1,5 +1,7 @@
1
1
  export * from "./base-server.mjs";
2
2
  export * from "./base-tools.mjs";
3
+ export * from "./init-arg-utils.mjs";
4
+ export * from "./error-formatter.mjs";
3
5
  export * from "./tool-generator.mjs";
4
6
  export * from "./types.mjs";
5
7
  export * from "./inject-report-html-plugin.mjs";
@@ -0,0 +1,38 @@
1
+ import { getKeyAliases, isRecord } from "../key-alias-utils.mjs";
2
+ function readAliasedValue(args, key) {
3
+ for (const alias of getKeyAliases(key))if (alias in args) return args[alias];
4
+ }
5
+ function readNamespacedArg(args, namespace, key) {
6
+ const namespacedArgs = readAliasedValue(args, namespace);
7
+ if (isRecord(namespacedArgs)) {
8
+ const nestedValue = readAliasedValue(namespacedArgs, key);
9
+ if (void 0 !== nestedValue) return nestedValue;
10
+ }
11
+ const dottedValue = readAliasedValue(args, `${namespace}.${key}`);
12
+ if (void 0 !== dottedValue) return dottedValue;
13
+ const directValue = readAliasedValue(args, key);
14
+ if (void 0 !== directValue) return directValue;
15
+ }
16
+ function extractNamespacedArgs(args, namespace, keys) {
17
+ const extracted = {};
18
+ for (const key of keys){
19
+ const value = readNamespacedArg(args, namespace, key);
20
+ if (void 0 !== value) extracted[key] = value;
21
+ }
22
+ return Object.keys(extracted).length > 0 ? extracted : void 0;
23
+ }
24
+ function sanitizeNamespacedArgs(args, namespace, keys) {
25
+ const excludedKeys = new Set(getKeyAliases(namespace));
26
+ for (const key of keys){
27
+ for (const alias of getKeyAliases(key))excludedKeys.add(alias);
28
+ for (const alias of getKeyAliases(`${namespace}.${key}`))excludedKeys.add(alias);
29
+ }
30
+ return Object.fromEntries(Object.entries(args).filter(([key])=>!excludedKeys.has(key)));
31
+ }
32
+ function createNamespacedInitArgSchema(namespace, shape) {
33
+ return Object.fromEntries(Object.entries(shape).map(([key, value])=>[
34
+ `${namespace}.${key}`,
35
+ value
36
+ ]));
37
+ }
38
+ export { createNamespacedInitArgSchema, extractNamespacedArgs, sanitizeNamespacedArgs };
@@ -9,7 +9,7 @@ function injectReportHtmlFromCore(packageDir) {
9
9
  setup (api) {
10
10
  api.onAfterBuild(()=>{
11
11
  const coreUtilsPath = node_path.resolve(packageDir, '..', 'core', 'dist', 'lib', 'utils.js');
12
- if (!node_fs.existsSync(coreUtilsPath)) return void console.warn('[inject-report-html] @midscene/core dist not found, skipping');
12
+ if (!node_fs.existsSync(coreUtilsPath)) return void console.warn('[inject-report-html] @aiscene/core dist not found, skipping');
13
13
  const coreContent = node_fs.readFileSync(coreUtilsPath, 'utf-8');
14
14
  if (!coreContent.includes(REPLACED_MARK)) return void console.warn('[inject-report-html] HTML not found in core dist. Ensure report builds first.');
15
15
  const markerIndex = coreContent.indexOf(REPLACED_MARK);
@@ -1,9 +1,7 @@
1
+ import { parseBase64 } from "@aiscene/shared/img";
1
2
  import { z } from "zod";
2
- import { parseBase64 } from "../img/index.mjs";
3
3
  import { getZodDescription, getZodTypeName, isMidsceneLocatorField, unwrapZodField } from "../zod-schema-utils.mjs";
4
- function getErrorMessage(error) {
5
- return error instanceof Error ? error.message : String(error);
6
- }
4
+ import { getErrorMessage } from "./error-formatter.mjs";
7
5
  function describeActionForMCP(action) {
8
6
  const actionDesc = action.description || `Execute ${action.name} action`;
9
7
  if (!action.paramSchema) return `${action.name} action, ${actionDesc}`;
@@ -70,10 +68,13 @@ function transformSchemaField(key, value) {
70
68
  value
71
69
  ];
72
70
  }
73
- function extractActionSchema(paramSchema) {
71
+ function extractActionSchema(paramSchema, actionName) {
74
72
  if (!paramSchema) return {};
75
73
  const shape = getZodObjectShape(paramSchema);
76
- if (!shape) return paramSchema;
74
+ if (!shape) {
75
+ const typeName = paramSchema?._def?.typeName ?? 'unknown';
76
+ throw new Error(`Action "${actionName}" declared a non-object paramSchema (${typeName}). CLI and MCP tool schemas must be a ZodObject (e.g. z.object({ uri: z.string() })) or undefined. Wrap primitive fields in an object schema.`);
77
+ }
77
78
  return Object.fromEntries(Object.entries(shape).map(([key, value])=>transformSchemaField(key, value)));
78
79
  }
79
80
  function getPromptText(prompt) {
@@ -279,17 +280,30 @@ async function captureFailureResult(agent, actionName, errorMessage) {
279
280
  };
280
281
  }
281
282
  }
282
- function generateToolsFromActionSpace(actionSpace, getAgent) {
283
+ function mergeToolCliMetadata(base, extra) {
284
+ const options = {
285
+ ...base?.options ?? {},
286
+ ...extra?.options ?? {}
287
+ };
288
+ return Object.keys(options).length > 0 ? {
289
+ options
290
+ } : void 0;
291
+ }
292
+ function generateToolsFromActionSpace(actionSpace, getAgent, sanitizeArgs = (args)=>args, initArgSchema = {}, initArgCliMetadata) {
283
293
  return actionSpace.map((action)=>{
284
- const schema = extractActionSchema(action.paramSchema);
294
+ const schema = {
295
+ ...extractActionSchema(action.paramSchema, action.name),
296
+ ...initArgSchema
297
+ };
285
298
  return {
286
299
  name: action.name,
287
300
  description: describeActionForMCP(action),
288
301
  schema,
302
+ cli: initArgCliMetadata,
289
303
  handler: async (args)=>{
290
304
  try {
291
- const agent = await getAgent();
292
- const normalizedArgs = normalizeActionArgs(args, action.paramSchema);
305
+ const agent = await getAgent(args);
306
+ const normalizedArgs = normalizeActionArgs(sanitizeArgs(args), action.paramSchema);
293
307
  let actionResult;
294
308
  try {
295
309
  actionResult = await executeAction(agent, action.name, normalizedArgs);
@@ -308,15 +322,18 @@ function generateToolsFromActionSpace(actionSpace, getAgent) {
308
322
  };
309
323
  });
310
324
  }
311
- function generateCommonTools(getAgent) {
325
+ function generateCommonTools(getAgent, initArgSchema = {}, initArgCliMetadata) {
312
326
  return [
313
327
  {
314
328
  name: 'take_screenshot',
315
329
  description: 'Capture screenshot of current page/screen',
316
- schema: {},
317
- handler: async ()=>{
330
+ schema: {
331
+ ...initArgSchema
332
+ },
333
+ cli: initArgCliMetadata,
334
+ handler: async (args = {})=>{
318
335
  try {
319
- const agent = await getAgent();
336
+ const agent = await getAgent(args);
320
337
  const screenshot = await agent.page?.screenshotBase64();
321
338
  if (!screenshot) return createErrorResult('Screenshot not available');
322
339
  const { mimeType, body } = parseBase64(screenshot);
@@ -340,12 +357,14 @@ function generateCommonTools(getAgent) {
340
357
  name: 'act',
341
358
  description: 'Execute a natural language action. The AI will plan and perform multi-step operations in a single invocation, useful for transient UI interactions (e.g., Spotlight, dropdown menus) that disappear between separate commands.',
342
359
  schema: {
343
- prompt: z.string().describe('Natural language description of the action to perform, e.g. "press Command+Space, type Safari, press Enter"')
360
+ prompt: z.string().describe('Natural language description of the action to perform, e.g. "press Command+Space, type Safari, press Enter"'),
361
+ ...initArgSchema
344
362
  },
345
- handler: async (args)=>{
363
+ cli: mergeToolCliMetadata(void 0, initArgCliMetadata),
364
+ handler: async (args = {})=>{
346
365
  const prompt = args.prompt;
347
366
  try {
348
- const agent = await getAgent();
367
+ const agent = await getAgent(args);
349
368
  if (!agent.aiAction) return createErrorResult('act is not supported by this agent');
350
369
  const result = await agent.aiAction(prompt, {
351
370
  deepThink: false
@@ -365,6 +384,35 @@ function generateCommonTools(getAgent) {
365
384
  return createErrorResult(`Failed to execute act: ${errorMessage}`);
366
385
  }
367
386
  }
387
+ },
388
+ {
389
+ name: 'assert',
390
+ description: 'Assert a natural language statement against the current page/screen.',
391
+ schema: {
392
+ prompt: z.string().describe('Natural language assertion to verify, e.g. "there is a login button visible"'),
393
+ ...initArgSchema
394
+ },
395
+ cli: mergeToolCliMetadata(void 0, initArgCliMetadata),
396
+ handler: async (args = {})=>{
397
+ const prompt = args.prompt;
398
+ try {
399
+ const agent = await getAgent(args);
400
+ if (!agent.aiAssert) return createErrorResult('assert is not supported by this agent');
401
+ await agent.aiAssert(prompt);
402
+ return {
403
+ content: [
404
+ {
405
+ type: 'text',
406
+ text: 'Assertion passed.'
407
+ }
408
+ ]
409
+ };
410
+ } catch (error) {
411
+ const errorMessage = getErrorMessage(error);
412
+ console.error('Error executing assert:', errorMessage);
413
+ return createErrorResult(`Failed to execute assert: ${errorMessage}`);
414
+ }
415
+ }
368
416
  }
369
417
  ];
370
418
  }