@godscene/shared 1.7.11

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 (236) hide show
  1. package/README.md +9 -0
  2. package/dist/es/baseDB.mjs +109 -0
  3. package/dist/es/cli/cli-args.mjs +95 -0
  4. package/dist/es/cli/cli-error.mjs +24 -0
  5. package/dist/es/cli/cli-runner.mjs +122 -0
  6. package/dist/es/cli/index.mjs +4 -0
  7. package/dist/es/common.mjs +37 -0
  8. package/dist/es/constants/example-code.mjs +227 -0
  9. package/dist/es/constants/index.mjs +124 -0
  10. package/dist/es/env/basic.mjs +6 -0
  11. package/dist/es/env/constants.mjs +110 -0
  12. package/dist/es/env/global-config-manager.mjs +94 -0
  13. package/dist/es/env/helper.mjs +43 -0
  14. package/dist/es/env/index.mjs +5 -0
  15. package/dist/es/env/init-debug.mjs +18 -0
  16. package/dist/es/env/model-config-manager.mjs +79 -0
  17. package/dist/es/env/parse-model-config.mjs +165 -0
  18. package/dist/es/env/types.mjs +232 -0
  19. package/dist/es/env/utils.mjs +18 -0
  20. package/dist/es/extractor/constants.mjs +2 -0
  21. package/dist/es/extractor/cs_postmessage.mjs +61 -0
  22. package/dist/es/extractor/customLocator.mjs +641 -0
  23. package/dist/es/extractor/debug.mjs +6 -0
  24. package/dist/es/extractor/dom-util.mjs +96 -0
  25. package/dist/es/extractor/index.mjs +5 -0
  26. package/dist/es/extractor/locator.mjs +250 -0
  27. package/dist/es/extractor/tree.mjs +78 -0
  28. package/dist/es/extractor/util.mjs +245 -0
  29. package/dist/es/extractor/web-extractor.mjs +393 -0
  30. package/dist/es/img/box-select.mjs +824 -0
  31. package/dist/es/img/canvas-fallback.mjs +238 -0
  32. package/dist/es/img/get-photon.mjs +45 -0
  33. package/dist/es/img/get-sharp.mjs +11 -0
  34. package/dist/es/img/index.mjs +4 -0
  35. package/dist/es/img/info.mjs +35 -0
  36. package/dist/es/img/transform.mjs +275 -0
  37. package/dist/es/index.mjs +2 -0
  38. package/dist/es/key-alias-utils.mjs +19 -0
  39. package/dist/es/logger.mjs +64 -0
  40. package/dist/es/mcp/base-server.mjs +282 -0
  41. package/dist/es/mcp/base-tools.mjs +159 -0
  42. package/dist/es/mcp/chrome-path.mjs +35 -0
  43. package/dist/es/mcp/cli-report-session.mjs +78 -0
  44. package/dist/es/mcp/error-formatter.mjs +19 -0
  45. package/dist/es/mcp/index.mjs +9 -0
  46. package/dist/es/mcp/init-arg-utils.mjs +38 -0
  47. package/dist/es/mcp/inject-report-html-plugin.mjs +53 -0
  48. package/dist/es/mcp/launcher-helper.mjs +52 -0
  49. package/dist/es/mcp/tool-generator.mjs +419 -0
  50. package/dist/es/mcp/types.mjs +3 -0
  51. package/dist/es/node/fs.mjs +44 -0
  52. package/dist/es/node/index.mjs +2 -0
  53. package/dist/es/node/port.mjs +24 -0
  54. package/dist/es/polyfills/async-hooks.mjs +2 -0
  55. package/dist/es/polyfills/index.mjs +1 -0
  56. package/dist/es/types/index.mjs +3 -0
  57. package/dist/es/us-keyboard-layout.mjs +1414 -0
  58. package/dist/es/us-keyboard-layout.mjs.LICENSE.txt +5 -0
  59. package/dist/es/utils.mjs +72 -0
  60. package/dist/es/zod-schema-utils.mjs +54 -0
  61. package/dist/lib/baseDB.js +149 -0
  62. package/dist/lib/cli/cli-args.js +138 -0
  63. package/dist/lib/cli/cli-error.js +61 -0
  64. package/dist/lib/cli/cli-runner.js +181 -0
  65. package/dist/lib/cli/index.js +53 -0
  66. package/dist/lib/common.js +93 -0
  67. package/dist/lib/constants/example-code.js +264 -0
  68. package/dist/lib/constants/index.js +221 -0
  69. package/dist/lib/env/basic.js +40 -0
  70. package/dist/lib/env/constants.js +153 -0
  71. package/dist/lib/env/global-config-manager.js +128 -0
  72. package/dist/lib/env/helper.js +80 -0
  73. package/dist/lib/env/index.js +90 -0
  74. package/dist/lib/env/init-debug.js +52 -0
  75. package/dist/lib/env/model-config-manager.js +113 -0
  76. package/dist/lib/env/parse-model-config.js +211 -0
  77. package/dist/lib/env/types.js +572 -0
  78. package/dist/lib/env/utils.js +61 -0
  79. package/dist/lib/extractor/constants.js +42 -0
  80. package/dist/lib/extractor/cs_postmessage.js +98 -0
  81. package/dist/lib/extractor/customLocator.js +693 -0
  82. package/dist/lib/extractor/debug.js +12 -0
  83. package/dist/lib/extractor/dom-util.js +157 -0
  84. package/dist/lib/extractor/index.js +87 -0
  85. package/dist/lib/extractor/locator.js +296 -0
  86. package/dist/lib/extractor/tree.js +124 -0
  87. package/dist/lib/extractor/util.js +336 -0
  88. package/dist/lib/extractor/web-extractor.js +442 -0
  89. package/dist/lib/img/box-select.js +875 -0
  90. package/dist/lib/img/canvas-fallback.js +305 -0
  91. package/dist/lib/img/get-photon.js +82 -0
  92. package/dist/lib/img/get-sharp.js +45 -0
  93. package/dist/lib/img/index.js +95 -0
  94. package/dist/lib/img/info.js +92 -0
  95. package/dist/lib/img/transform.js +364 -0
  96. package/dist/lib/index.js +36 -0
  97. package/dist/lib/key-alias-utils.js +62 -0
  98. package/dist/lib/logger.js +114 -0
  99. package/dist/lib/mcp/base-server.js +332 -0
  100. package/dist/lib/mcp/base-tools.js +193 -0
  101. package/dist/lib/mcp/chrome-path.js +72 -0
  102. package/dist/lib/mcp/cli-report-session.js +121 -0
  103. package/dist/lib/mcp/error-formatter.js +53 -0
  104. package/dist/lib/mcp/index.js +114 -0
  105. package/dist/lib/mcp/init-arg-utils.js +78 -0
  106. package/dist/lib/mcp/inject-report-html-plugin.js +98 -0
  107. package/dist/lib/mcp/launcher-helper.js +86 -0
  108. package/dist/lib/mcp/tool-generator.js +456 -0
  109. package/dist/lib/mcp/types.js +40 -0
  110. package/dist/lib/node/fs.js +97 -0
  111. package/dist/lib/node/index.js +65 -0
  112. package/dist/lib/node/port.js +61 -0
  113. package/dist/lib/polyfills/async-hooks.js +36 -0
  114. package/dist/lib/polyfills/index.js +58 -0
  115. package/dist/lib/types/index.js +37 -0
  116. package/dist/lib/us-keyboard-layout.js +1457 -0
  117. package/dist/lib/us-keyboard-layout.js.LICENSE.txt +5 -0
  118. package/dist/lib/utils.js +148 -0
  119. package/dist/lib/zod-schema-utils.js +97 -0
  120. package/dist/types/baseDB.d.ts +25 -0
  121. package/dist/types/cli/cli-args.d.ts +8 -0
  122. package/dist/types/cli/cli-error.d.ts +5 -0
  123. package/dist/types/cli/cli-runner.d.ts +19 -0
  124. package/dist/types/cli/index.d.ts +4 -0
  125. package/dist/types/common.d.ts +12 -0
  126. package/dist/types/constants/example-code.d.ts +2 -0
  127. package/dist/types/constants/index.d.ts +61 -0
  128. package/dist/types/env/basic.d.ts +6 -0
  129. package/dist/types/env/constants.d.ts +50 -0
  130. package/dist/types/env/global-config-manager.d.ts +32 -0
  131. package/dist/types/env/helper.d.ts +4 -0
  132. package/dist/types/env/index.d.ts +4 -0
  133. package/dist/types/env/init-debug.d.ts +1 -0
  134. package/dist/types/env/model-config-manager.d.ts +25 -0
  135. package/dist/types/env/parse-model-config.d.ts +31 -0
  136. package/dist/types/env/types.d.ts +339 -0
  137. package/dist/types/env/utils.d.ts +7 -0
  138. package/dist/types/extractor/constants.d.ts +1 -0
  139. package/dist/types/extractor/cs_postmessage.d.ts +2 -0
  140. package/dist/types/extractor/customLocator.d.ts +69 -0
  141. package/dist/types/extractor/debug.d.ts +1 -0
  142. package/dist/types/extractor/dom-util.d.ts +57 -0
  143. package/dist/types/extractor/index.d.ts +33 -0
  144. package/dist/types/extractor/locator.d.ts +9 -0
  145. package/dist/types/extractor/tree.d.ts +6 -0
  146. package/dist/types/extractor/util.d.ts +47 -0
  147. package/dist/types/extractor/web-extractor.d.ts +24 -0
  148. package/dist/types/img/box-select.d.ts +26 -0
  149. package/dist/types/img/canvas-fallback.d.ts +105 -0
  150. package/dist/types/img/get-photon.d.ts +19 -0
  151. package/dist/types/img/get-sharp.d.ts +3 -0
  152. package/dist/types/img/index.d.ts +3 -0
  153. package/dist/types/img/info.d.ts +34 -0
  154. package/dist/types/img/transform.d.ts +98 -0
  155. package/dist/types/index.d.ts +2 -0
  156. package/dist/types/key-alias-utils.d.ts +9 -0
  157. package/dist/types/logger.d.ts +5 -0
  158. package/dist/types/mcp/base-server.d.ts +93 -0
  159. package/dist/types/mcp/base-tools.d.ts +148 -0
  160. package/dist/types/mcp/chrome-path.d.ts +2 -0
  161. package/dist/types/mcp/cli-report-session.d.ts +12 -0
  162. package/dist/types/mcp/error-formatter.d.ts +12 -0
  163. package/dist/types/mcp/index.d.ts +9 -0
  164. package/dist/types/mcp/init-arg-utils.d.ts +13 -0
  165. package/dist/types/mcp/inject-report-html-plugin.d.ts +18 -0
  166. package/dist/types/mcp/launcher-helper.d.ts +94 -0
  167. package/dist/types/mcp/tool-generator.d.ts +10 -0
  168. package/dist/types/mcp/types.d.ts +113 -0
  169. package/dist/types/node/fs.d.ts +15 -0
  170. package/dist/types/node/index.d.ts +2 -0
  171. package/dist/types/node/port.d.ts +8 -0
  172. package/dist/types/polyfills/async-hooks.d.ts +6 -0
  173. package/dist/types/polyfills/index.d.ts +4 -0
  174. package/dist/types/types/index.d.ts +36 -0
  175. package/dist/types/us-keyboard-layout.d.ts +32 -0
  176. package/dist/types/utils.d.ts +34 -0
  177. package/dist/types/zod-schema-utils.d.ts +23 -0
  178. package/package.json +125 -0
  179. package/src/baseDB.ts +158 -0
  180. package/src/cli/cli-args.ts +173 -0
  181. package/src/cli/cli-error.ts +24 -0
  182. package/src/cli/cli-runner.ts +230 -0
  183. package/src/cli/index.ts +4 -0
  184. package/src/common.ts +67 -0
  185. package/src/constants/example-code.ts +227 -0
  186. package/src/constants/index.ts +139 -0
  187. package/src/env/basic.ts +12 -0
  188. package/src/env/constants.ts +303 -0
  189. package/src/env/global-config-manager.ts +191 -0
  190. package/src/env/helper.ts +58 -0
  191. package/src/env/index.ts +4 -0
  192. package/src/env/init-debug.ts +34 -0
  193. package/src/env/model-config-manager.ts +149 -0
  194. package/src/env/parse-model-config.ts +357 -0
  195. package/src/env/types.ts +583 -0
  196. package/src/env/utils.ts +39 -0
  197. package/src/extractor/constants.ts +5 -0
  198. package/src/extractor/cs_postmessage.ts +136 -0
  199. package/src/extractor/customLocator.ts +1245 -0
  200. package/src/extractor/debug.ts +10 -0
  201. package/src/extractor/dom-util.ts +231 -0
  202. package/src/extractor/index.ts +50 -0
  203. package/src/extractor/locator.ts +469 -0
  204. package/src/extractor/tree.ts +179 -0
  205. package/src/extractor/util.ts +482 -0
  206. package/src/extractor/web-extractor.ts +617 -0
  207. package/src/img/box-select.ts +588 -0
  208. package/src/img/canvas-fallback.ts +393 -0
  209. package/src/img/get-photon.ts +108 -0
  210. package/src/img/get-sharp.ts +18 -0
  211. package/src/img/index.ts +27 -0
  212. package/src/img/info.ts +102 -0
  213. package/src/img/transform.ts +553 -0
  214. package/src/index.ts +1 -0
  215. package/src/key-alias-utils.ts +23 -0
  216. package/src/logger.ts +96 -0
  217. package/src/mcp/base-server.ts +500 -0
  218. package/src/mcp/base-tools.ts +391 -0
  219. package/src/mcp/chrome-path.ts +48 -0
  220. package/src/mcp/cli-report-session.ts +130 -0
  221. package/src/mcp/error-formatter.ts +52 -0
  222. package/src/mcp/index.ts +9 -0
  223. package/src/mcp/init-arg-utils.ts +105 -0
  224. package/src/mcp/inject-report-html-plugin.ts +119 -0
  225. package/src/mcp/launcher-helper.ts +200 -0
  226. package/src/mcp/tool-generator.ts +658 -0
  227. package/src/mcp/types.ts +131 -0
  228. package/src/node/fs.ts +84 -0
  229. package/src/node/index.ts +2 -0
  230. package/src/node/port.ts +37 -0
  231. package/src/polyfills/async-hooks.ts +6 -0
  232. package/src/polyfills/index.ts +4 -0
  233. package/src/types/index.ts +54 -0
  234. package/src/us-keyboard-layout.ts +723 -0
  235. package/src/utils.ts +149 -0
  236. package/src/zod-schema-utils.ts +133 -0
@@ -0,0 +1,391 @@
1
+ import { parseBase64 } from '@godscene/shared/img';
2
+ import { getDebug } from '@godscene/shared/logger';
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';
17
+ import {
18
+ generateCommonTools,
19
+ generateToolsFromActionSpace,
20
+ } from './tool-generator';
21
+ import type {
22
+ ActionSpaceItem,
23
+ BaseAgent,
24
+ BaseDevice,
25
+ IMidsceneTools,
26
+ ToolCliMetadata,
27
+ ToolDefinition,
28
+ ToolSchema,
29
+ } from './types';
30
+
31
+ const debug = getDebug('mcp:base-tools');
32
+
33
+ /**
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.
67
+ */
68
+ export abstract class BaseMidsceneTools<
69
+ TAgent extends BaseAgent = BaseAgent,
70
+ TInitParam = unknown,
71
+ > implements IMidsceneTools {
72
+ protected mcpServer?: McpServer;
73
+ protected agent?: TAgent;
74
+ protected toolDefinitions: ToolDefinition[] = [];
75
+
76
+ /**
77
+ * Declarative init-arg spec. Subclasses that accept CLI/MCP init args should
78
+ * set this once and get `extractAgentInitParam` / `sanitizeToolArgs` /
79
+ * `getAgentInitArgSchema` auto-implemented.
80
+ *
81
+ * Declared with `declare` so that TS doesn't emit an `Object.defineProperty`
82
+ * for this field on the base constructor, which would otherwise overwrite
83
+ * a subclass field initializer under `useDefineForClassFields`.
84
+ */
85
+ protected declare readonly initArgSpec?: InitArgSpec<TInitParam>;
86
+
87
+ /**
88
+ * Ensure agent is initialized and ready for use.
89
+ * Must be implemented by subclasses to create platform-specific agent.
90
+ * @param initParam Optional initialization parameter (platform-specific, e.g., URL, device ID)
91
+ * @returns Promise resolving to initialized agent instance
92
+ * @throws Error if agent initialization fails
93
+ */
94
+ protected abstract ensureAgent(initParam?: TInitParam): Promise<TAgent>;
95
+
96
+ private getInitArgKeys(): readonly string[] {
97
+ return this.initArgSpec ? Object.keys(this.initArgSpec.shape) : [];
98
+ }
99
+
100
+ /**
101
+ * Extract a platform-specific agent init parameter from CLI/MCP tool args.
102
+ */
103
+ protected extractAgentInitParam(
104
+ args: Record<string, unknown>,
105
+ ): TInitParam | undefined {
106
+ if (!this.initArgSpec) {
107
+ return undefined;
108
+ }
109
+ const extracted = extractNamespacedArgs(
110
+ args,
111
+ this.initArgSpec.namespace,
112
+ this.getInitArgKeys(),
113
+ );
114
+ if (this.initArgSpec.adapt) {
115
+ return this.initArgSpec.adapt(extracted);
116
+ }
117
+ return extracted as TInitParam | undefined;
118
+ }
119
+
120
+ /**
121
+ * Remove platform-specific init args before dispatching a tool payload to the action itself.
122
+ */
123
+ protected sanitizeToolArgs(
124
+ args: Record<string, unknown>,
125
+ ): Record<string, unknown> {
126
+ if (!this.initArgSpec) {
127
+ return args;
128
+ }
129
+ return sanitizeNamespacedArgs(
130
+ args,
131
+ this.initArgSpec.namespace,
132
+ this.getInitArgKeys(),
133
+ );
134
+ }
135
+
136
+ /**
137
+ * Expose platform-specific init args on action/common tool schemas.
138
+ */
139
+ protected getAgentInitArgSchema(): ToolSchema {
140
+ if (!this.initArgSpec) {
141
+ return {};
142
+ }
143
+ return createNamespacedInitArgSchema(
144
+ this.initArgSpec.namespace,
145
+ this.initArgSpec.shape,
146
+ );
147
+ }
148
+
149
+ /**
150
+ * Expose CLI-only metadata for platform init args so single-platform help can
151
+ * show ergonomic bare flags while the underlying schema stays namespaced.
152
+ * When `preferBareKeys` is enabled, single-platform CLIs only accept the
153
+ * bare spellings; namespaced dotted spellings remain available through the
154
+ * MCP/YAML schema instead of the platform CLI surface.
155
+ */
156
+ protected getAgentInitArgCliMetadata(): ToolCliMetadata | undefined {
157
+ if (!this.initArgSpec?.cli) {
158
+ return undefined;
159
+ }
160
+
161
+ const options = Object.fromEntries(
162
+ this.getInitArgKeys().map((key) => {
163
+ const canonicalKey = `${this.initArgSpec!.namespace}.${key}`;
164
+ const preferredName =
165
+ this.initArgSpec!.cli?.preferredNames?.[key] ??
166
+ (this.initArgSpec!.cli?.preferBareKeys
167
+ ? camelToKebab(key)
168
+ : canonicalKey);
169
+
170
+ const acceptedNames = new Set<string>([
171
+ preferredName,
172
+ ...(this.initArgSpec!.cli?.preferBareKeys
173
+ ? getKeyAliases(key)
174
+ : getKeyAliases(canonicalKey)),
175
+ ]);
176
+ acceptedNames.delete(preferredName);
177
+
178
+ return [
179
+ canonicalKey,
180
+ {
181
+ preferredName,
182
+ aliases: [...acceptedNames],
183
+ },
184
+ ];
185
+ }),
186
+ );
187
+
188
+ return { options };
189
+ }
190
+
191
+ /**
192
+ * Optional: prepare platform-specific tools (e.g., device connection)
193
+ */
194
+ protected preparePlatformTools(): ToolDefinition[] {
195
+ return [];
196
+ }
197
+
198
+ protected getCliReportSessionName(): string | undefined {
199
+ return undefined;
200
+ }
201
+
202
+ protected createNewCliReportSession(
203
+ targetIdentity?: string,
204
+ ): CliReportSession | undefined {
205
+ const sessionName = this.getCliReportSessionName();
206
+ if (!sessionName) {
207
+ return undefined;
208
+ }
209
+ return generateCliReportSession(sessionName, targetIdentity);
210
+ }
211
+
212
+ protected commitCliReportSession(session?: CliReportSession): void {
213
+ if (session) {
214
+ writeCliReportSession(session);
215
+ }
216
+ }
217
+
218
+ protected readCliReportFileName(): string | undefined {
219
+ const sessionName = this.getCliReportSessionName();
220
+ if (!sessionName) {
221
+ return undefined;
222
+ }
223
+ return readCliReportSession(sessionName)?.reportFileName;
224
+ }
225
+
226
+ protected readCliReportAgentOptions():
227
+ | {
228
+ reportFileName: string;
229
+ reportAttributes: Record<string, string>;
230
+ }
231
+ | undefined {
232
+ const reportFileName = this.readCliReportFileName();
233
+ if (!reportFileName) {
234
+ return undefined;
235
+ }
236
+ return {
237
+ reportFileName,
238
+ reportAttributes: {
239
+ 'data-group-id': reportFileName,
240
+ },
241
+ };
242
+ }
243
+
244
+ /**
245
+ * Must be implemented by subclasses to create a temporary device instance
246
+ * This allows getting real actionSpace without connecting to device
247
+ */
248
+ protected abstract createTemporaryDevice(): BaseDevice;
249
+
250
+ /**
251
+ * Initialize all tools by querying actionSpace
252
+ * Uses two-layer fallback strategy:
253
+ * 1. Try to get actionSpace from connected agent (if available)
254
+ * 2. Create temporary device instance to read actionSpace (always succeeds)
255
+ */
256
+ public async initTools(): Promise<void> {
257
+ this.toolDefinitions = [];
258
+
259
+ // 1. Add platform-specific tools first (device connection, etc.)
260
+ // These don't require an agent and should always be available
261
+ const platformTools = this.preparePlatformTools();
262
+ this.toolDefinitions.push(...platformTools);
263
+
264
+ // 2. Get action space: use pre-set agent if available, otherwise temp device.
265
+ // When called via mcpKitForAgent(), agent is set before initTools().
266
+ // For CLI usage, agent is deferred to the first real command.
267
+ let actionSpace: ActionSpaceItem[];
268
+ if (this.agent) {
269
+ actionSpace = await this.agent.getActionSpace();
270
+ debug(
271
+ 'Action space from agent:',
272
+ actionSpace.map((a) => a.name).join(', '),
273
+ );
274
+ } else {
275
+ const tempDevice = this.createTemporaryDevice();
276
+ actionSpace = tempDevice.actionSpace();
277
+ await tempDevice.destroy?.();
278
+ debug(
279
+ 'Action space from temporary device:',
280
+ actionSpace.map((a) => a.name).join(', '),
281
+ );
282
+ }
283
+
284
+ // 3. Generate tools from action space (core innovation)
285
+ const actionTools = generateToolsFromActionSpace(
286
+ actionSpace,
287
+ (args = {}) => this.ensureAgent(this.extractAgentInitParam(args)),
288
+ (args = {}) => this.sanitizeToolArgs(args),
289
+ this.getAgentInitArgSchema(),
290
+ this.getAgentInitArgCliMetadata(),
291
+ );
292
+
293
+ // 4. Add common tools (screenshot, waitFor)
294
+ const commonTools = generateCommonTools(
295
+ (args = {}) => this.ensureAgent(this.extractAgentInitParam(args)),
296
+ this.getAgentInitArgSchema(),
297
+ this.getAgentInitArgCliMetadata(),
298
+ );
299
+ this.toolDefinitions.push(...actionTools, ...commonTools);
300
+
301
+ debug('Total tools prepared:', this.toolDefinitions.length);
302
+ }
303
+
304
+ /**
305
+ * Attach to MCP server and register all tools
306
+ */
307
+ public attachToServer(server: McpServer): void {
308
+ this.mcpServer = server;
309
+
310
+ if (this.toolDefinitions.length === 0) {
311
+ debug('Warning: No tools to register. Tools may be initialized lazily.');
312
+ }
313
+
314
+ for (const toolDef of this.toolDefinitions) {
315
+ this.mcpServer.tool(
316
+ toolDef.name,
317
+ toolDef.description,
318
+ toolDef.schema,
319
+ toolDef.handler,
320
+ );
321
+ }
322
+
323
+ debug('Registered', this.toolDefinitions.length, 'tools');
324
+ }
325
+
326
+ /**
327
+ * Cleanup method - destroy agent and release resources
328
+ */
329
+ public async destroy(): Promise<void> {
330
+ await this.agent?.destroy?.();
331
+ }
332
+
333
+ /**
334
+ * Get tool definitions
335
+ */
336
+ public getToolDefinitions(): ToolDefinition[] {
337
+ return this.toolDefinitions;
338
+ }
339
+
340
+ /**
341
+ * Set agent for the tools manager
342
+ */
343
+ public setAgent(agent: TAgent): void {
344
+ this.agent = agent;
345
+ }
346
+
347
+ /**
348
+ * Helper: Convert base64 screenshot to image content array
349
+ */
350
+ protected buildScreenshotContent(screenshot: string) {
351
+ const { mimeType, body } = parseBase64(screenshot);
352
+ return [
353
+ {
354
+ type: 'image' as const,
355
+ data: body,
356
+ mimeType,
357
+ },
358
+ ];
359
+ }
360
+
361
+ /**
362
+ * Helper: Build a simple text result for tool responses
363
+ */
364
+ protected buildTextResult(text: string) {
365
+ return {
366
+ content: [{ type: 'text' as const, text }],
367
+ };
368
+ }
369
+
370
+ /**
371
+ * Create a disconnect handler for releasing platform resources
372
+ * @param platformName Human-readable platform name for the response message
373
+ * @returns Handler function that destroys the agent and returns appropriate response
374
+ */
375
+ protected createDisconnectHandler(platformName: string) {
376
+ return async () => {
377
+ if (!this.agent) {
378
+ return this.buildTextResult('No active connection to disconnect');
379
+ }
380
+
381
+ try {
382
+ await this.agent.destroy?.();
383
+ } catch (error) {
384
+ debug('Failed to destroy agent during disconnect:', error);
385
+ }
386
+ this.agent = undefined;
387
+
388
+ return this.buildTextResult(`Disconnected from ${platformName}`);
389
+ };
390
+ }
391
+ }
@@ -0,0 +1,48 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { MIDSCENE_MCP_CHROME_PATH, globalConfigManager } from '../env';
3
+
4
+ export function getSystemChromePath(): string | undefined {
5
+ const platform = process.platform;
6
+
7
+ const chromePaths: Record<string, string[]> = {
8
+ darwin: [
9
+ '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
10
+ '/Applications/Chromium.app/Contents/MacOS/Chromium',
11
+ ],
12
+ win32: [
13
+ 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
14
+ 'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe',
15
+ `C:\\Users\\${process.env.USERNAME ?? process.env.USER}\\AppData\\Local\\Google\\Chrome\\Application\\chrome.exe`,
16
+ ],
17
+ linux: [
18
+ // Prefer actual binaries over wrapper scripts.
19
+ // Wrappers in /usr/bin may strip --user-data-dir, causing
20
+ // "DevTools remote debugging requires a non-default data directory" errors.
21
+ '/opt/google/chrome/chrome',
22
+ '/opt/google/chrome/google-chrome',
23
+ '/usr/bin/google-chrome-stable',
24
+ '/usr/bin/google-chrome',
25
+ '/usr/bin/chromium-browser',
26
+ '/usr/bin/chromium',
27
+ '/snap/bin/chromium',
28
+ ],
29
+ };
30
+
31
+ const paths = chromePaths[platform] ?? [];
32
+ return paths.find((p) => existsSync(p));
33
+ }
34
+
35
+ export function resolveChromePath(): string {
36
+ const envPath = globalConfigManager.getEnvConfigValue(
37
+ MIDSCENE_MCP_CHROME_PATH,
38
+ );
39
+ if (envPath && envPath !== 'auto' && existsSync(envPath)) {
40
+ return envPath;
41
+ }
42
+ const systemPath = getSystemChromePath();
43
+ if (systemPath) return systemPath;
44
+
45
+ throw new Error(
46
+ 'Chrome not found. Install Google Chrome or set MIDSCENE_MCP_CHROME_PATH environment variable.',
47
+ );
48
+ }
@@ -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
+ }
@@ -0,0 +1,9 @@
1
+ export * from './base-server';
2
+ export * from './base-tools';
3
+ export * from './init-arg-utils';
4
+ export * from './error-formatter';
5
+ export * from './tool-generator';
6
+ export * from './types';
7
+ export * from './inject-report-html-plugin';
8
+ export * from './launcher-helper';
9
+ export * from './chrome-path';