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