@skillbuilder/minitool-e2e 0.0.0-beta-20261027053041051

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/package.json ADDED
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "@skillbuilder/minitool-e2e",
3
+ "version": "0.0.0-beta-20261027053041051",
4
+ "description": "小工具端到端验证能力:在沙箱内用 Chromium + miniapp-simulator 真操作出码产物,并以 MCP server 形式对 Agent 暴露(游戏试玩只是其中一种模式)",
5
+ "main": "lib/index.js",
6
+ "types": "types/index.d.ts",
7
+ "bin": {
8
+ "minitool-e2e-mcp": "lib/mcp/stdio-entry.js"
9
+ },
10
+ "files": [
11
+ "lib",
12
+ "types",
13
+ "README.md"
14
+ ],
15
+ "engines": {
16
+ "node": ">=20.0.0"
17
+ },
18
+ "dependencies": {
19
+ "@modelcontextprotocol/sdk": "1.25.3",
20
+ "playwright-core": "1.63.0",
21
+ "zod": "4.3.5",
22
+ "@skillbuilder/miniapp-simulator": "0.0.0-beta-20261027053041051"
23
+ },
24
+ "license": "ISC"
25
+ }
@@ -0,0 +1,9 @@
1
+ export interface SimulatorAssets {
2
+ /** 模拟器包根目录 */
3
+ packageRoot: string;
4
+ /** ESM runtime 目录(含 index.js 与哈希命名的 chunk,必须整目录托管) */
5
+ libDir: string;
6
+ /** Service Worker 脚本的绝对路径 */
7
+ serviceWorkerFile: string;
8
+ }
9
+ export declare function resolveSimulatorAssets(): SimulatorAssets;
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Chromium 启动,面向沙箱容器(Alpine/musl、无 GPU、1 核 2GiB、非 root)。启动参数的由来:
3
+ * - **不用 playwright 自带的浏览器下载**:它的预编译 Chromium 是 glibc 构建,musl 上跑不起来。
4
+ * 镜像里装发行版 chromium 靠 executablePath 指过去,所以依赖只取 `playwright-core`。
5
+ * - `--use-gl=swiftshader --enable-unsafe-swiftshader`:无 GPU,WebGL 必须软渲染否则 Pixi
6
+ * 初始化失败;新版 Chromium 把 swiftshader 归为 unsafe,要第二个开关。
7
+ * - `--disable-dev-shm-usage`:容器 /dev/shm 只有 64MB,不关会随机崩标签页。
8
+ * - `--no-sandbox`:容器内已是受限环境,再套一层 namespace 沙箱通常起不来。
9
+ */
10
+ import { type Browser } from "playwright-core";
11
+ export interface LaunchChromiumOptions {
12
+ /** 缺省读 `MINITOOL_E2E_CHROMIUM_PATH`,再缺省交给 playwright-core 自查。 */
13
+ executablePath?: string;
14
+ /** 默认 true;本地排障可置 false 看真实窗口 */
15
+ headless?: boolean;
16
+ /** 追加启动参数(排障用,正常不需要) */
17
+ extraArgs?: string[];
18
+ launchTimeoutMs?: number;
19
+ }
20
+ export declare function resolveChromiumPath(explicit?: string): string | undefined;
21
+ export declare function launchChromium(opts?: LaunchChromiumOptions): Promise<Browser>;
22
+ /** 探测 chromium 是否可用;用于降级决策(不可用时不注册试玩工具面,而不是让出码失败)。 */
23
+ export declare function probeChromium(opts?: LaunchChromiumOptions): Promise<{
24
+ available: true;
25
+ } | {
26
+ available: false;
27
+ reason: string;
28
+ }>;
@@ -0,0 +1,82 @@
1
+ import { type LaunchChromiumOptions } from "./browser";
2
+ export interface MinitoolE2EPortOptions extends LaunchChromiumOptions {
3
+ /**
4
+ * 工作区 → 产物目录,默认 `<workspaceDir>/dist`。
5
+ * 刻意留在本包:出码侧只知道工作区在哪,「产物落在哪」是 e2e 侧的知识。
6
+ */
7
+ resolveDistDir?: (workspaceDir: string) => string;
8
+ viewport?: {
9
+ width: number;
10
+ height: number;
11
+ };
12
+ /**
13
+ * 截图上传回宿主的接口基准地址(含 /node_api 前缀,不带尾斜杠)。
14
+ * 未配置时 `e2e_screenshot` 只返回内联 base64,不进桶。
15
+ */
16
+ uploadBase?: string;
17
+ /** 上传接口的受限 token(宿主校验用,挡沙箱外的人;被模型读到也无所谓)。 */
18
+ uploadToken?: string;
19
+ }
20
+ /** 与 agentbase 的 `MCPConfig` 结构化兼容(那边字段全可选,这里把实际必填的两个收紧)。 */
21
+ export interface E2EMcpServerConfig {
22
+ type?: "stdio";
23
+ command: string;
24
+ args: string[];
25
+ env?: Record<string, string>;
26
+ }
27
+ /** 本轮任务的装配结果:工具白名单 + prompt 段 + 回合预算 + 每任务 MCP 配置。 */
28
+ export interface E2ERunPlan {
29
+ /** 已带 `mcp__minitool-e2e__` 前缀,出码侧只做 `includes()` 过滤 */
30
+ allowedToolNames: string[];
31
+ /** 追加到 systemPrompt 末尾,出码侧当不透明字符串 */
32
+ instruction: string;
33
+ extraTurns: number;
34
+ /** 每任务一份:env 里额外带上本任务 taskId(截图上传回调宿主时用于事件归流) */
35
+ mcpServers: Record<string, E2EMcpServerConfig>;
36
+ }
37
+ /**
38
+ * 用判别联合而不是 `E2ERunPlan | undefined`,是为了让「不放行」也带上可读的原因 ——
39
+ * 出码侧只把 `reason` 原样打进日志,不解读它。
40
+ */
41
+ export type E2ERunDecision = {
42
+ allowed: false;
43
+ reason: string;
44
+ } | {
45
+ allowed: true;
46
+ plan: E2ERunPlan;
47
+ };
48
+ /**
49
+ * 与 codegen-agent 的 `CodegenE2EPort` 同形。端口存在即代表「这台机器有 e2e 能力」,
50
+ * 所以没有任何表达「可能不可用」的可选字段。
51
+ */
52
+ export interface MinitoolE2EPort {
53
+ /**
54
+ * 每任务一次:闸口判定 + 放行时的装配。纯计算,不起浏览器也不连 MCP。
55
+ *
56
+ * @param taskId 本任务 id:截图上传回调宿主时用于把事件归到对应任务的 SSE 流。
57
+ * @param hasGameBridge 产物有没有挂 `window.__GAME_TEST__` 桥。这一个判断留在出码侧:
58
+ * 桥由出码模板注入,依据不在本包。
59
+ * @param gate 控制面固化的闸口快照原文,本包内归一化(fail-closed)。
60
+ */
61
+ resolveRun(input: {
62
+ workspaceDir: string;
63
+ appKind: string;
64
+ hasGameBridge: boolean;
65
+ gate: unknown;
66
+ taskId: string;
67
+ }): E2ERunDecision;
68
+ }
69
+ export { MINITOOL_E2E_MCP_SERVER_NAME } from "./contract";
70
+ /** 建端口。MCP 入口缺失会抛错,正常调用方是下面的 `IfAvailable` 版本。 */
71
+ export declare function createMinitoolE2EPort(opts?: MinitoolE2EPortOptions): MinitoolE2EPort;
72
+ /**
73
+ * 两道判据缺一不可:chromium 能真起停一次、MCP 入口文件存在。
74
+ * 刻意**不抛错** —— e2e 不可用绝不能让用户的出码任务失败,宿主拿到 undefined 就当没这能力,
75
+ * 任务照跑、只是没有验证环节。镜像里本包没构建全就是以这种方式暴露的。
76
+ */
77
+ export declare function createMinitoolE2EPortIfAvailable(opts?: MinitoolE2EPortOptions): Promise<{
78
+ port: MinitoolE2EPort;
79
+ } | {
80
+ port: undefined;
81
+ reason: string;
82
+ }>;
@@ -0,0 +1,43 @@
1
+ /**
2
+ * e2e 的对外契约:工具名、MCP 前缀名、prompt、额外回合数。
3
+ *
4
+ * 工具名以**字符串**形态存在两处(`mcp/server.ts` 的 registerTool、下面 prompt 里点名的名字),
5
+ * 漏改一处 tsc 一个字都不报,模型会去调一个不存在的工具、拿到 `Tool not found`,然后自己编
6
+ * 一套「能力未注入」的解释(线上踩过)。两处都从本文件同一个数组取,改名字只能改这里。
7
+ *
8
+ * 本文件**不 import 任何运行时模块**:它在 codegen-agent 主进程里被同步调用,必须零副作用。
9
+ */
10
+ /** 任何产物形态都成立。后 4 个签名已定、实现待补。 */
11
+ export declare const E2E_GENERIC_TOOL_NAMES: readonly ["e2e_open", "e2e_logs", "e2e_screenshot", "e2e_close", "e2e_network", "e2e_dom", "e2e_interact", "e2e_health"];
12
+ /**
13
+ * 全部经产物侧 `window.__GAME_TEST__` 桥,只有 game_2d / game_3d 模板挂它。
14
+ * `attach` 单独成一个工具是因为等桥就绪只能轮询到超时,非游戏产物必然白等满 10s ——
15
+ * 只让真要用游戏语义的调用方付这个时间。
16
+ */
17
+ export declare const E2E_GAME_TOOL_NAMES: readonly ["e2e_game_attach", "e2e_game_manifest", "e2e_game_snapshot", "e2e_game_act", "e2e_game_step", "e2e_game_reset"];
18
+ export declare const E2E_TOOL_NAMES: readonly ["e2e_open", "e2e_logs", "e2e_screenshot", "e2e_close", "e2e_network", "e2e_dom", "e2e_interact", "e2e_health", "e2e_game_attach", "e2e_game_manifest", "e2e_game_snapshot", "e2e_game_act", "e2e_game_step", "e2e_game_reset"];
19
+ export type E2EGenericToolName = (typeof E2E_GENERIC_TOOL_NAMES)[number];
20
+ export type E2EGameToolName = (typeof E2E_GAME_TOOL_NAMES)[number];
21
+ export type E2EToolName = E2EGenericToolName | E2EGameToolName;
22
+ /** 工具名会是 `mcp__minitool-e2e__e2e_*`(agentbase/src/mcp.ts:466 的命名规则)。 */
23
+ export declare const MINITOOL_E2E_MCP_SERVER_NAME = "minitool-e2e";
24
+ /** prompt 里必须写这个带前缀的名字 —— 写裸名模型就调不到。 */
25
+ export declare function toMcpToolName(name: E2EToolName): string;
26
+ /**
27
+ * 额外回合数,按有无游戏桥分别给(无桥约 4 次调用,有桥约 15 次)。
28
+ * 必须补:这些调用全落在出码 Agent 自己的 maxTurns 预算里,不补的话
29
+ * 「写完码正要去验」的任务会直接撞 maxTurns 收场。
30
+ */
31
+ export declare function e2eExtraTurns(hasGameBridge: boolean): number;
32
+ /** 本轮该放行给模型的工具名(已带 MCP 前缀)。无游戏桥时只给通用 8 个。 */
33
+ export declare function e2eAllowedToolNames(hasGameBridge: boolean): string[];
34
+ /**
35
+ * @param distDir 产物绝对路径,**必传** —— MCP 子进程的 cwd 不是任务工作区
36
+ * (`MCPConfig` 也没有 cwd 字段),只能写进 prompt 让模型照抄。
37
+ * @param hasGameBridge false 时**整段不提游戏工具**:点名一个没挂载的工具,模型会去调、
38
+ * 拿到 `Tool xxx not found`,然后自己编一套解释。
39
+ */
40
+ export declare function buildE2EInstruction(opts: {
41
+ distDir: string;
42
+ hasGameBridge: boolean;
43
+ }): string;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,29 @@
1
+ /**
2
+ * e2e 灰度闸口的后半段:把宿主给的快照收成可信结构 + 按 appKind 做最后一道判定。
3
+ * 前半段(用户维度)由控制面建任务时求值并固化 —— 只有它有身份与配置版本;形态维度只能等
4
+ * 出码侧的意图路由定出 appKind 才判。
5
+ *
6
+ * ⚠️ **fail-closed**:字段缺失、类型不对、老控制面没带这个字段,一律不放行 ——
7
+ * 宁可不开也不能在脏数据上误开,它会真起浏览器、烧 token。零依赖,不碰 playwright。
8
+ */
9
+ /** 与 server-online 的 WidgetE2EGateSnapshot 同形。 */
10
+ export interface WidgetE2EGate {
11
+ /** 控制面已判定:总开关 / 白名单 / 百分比分桶 */
12
+ userEnabled: boolean;
13
+ appKinds: string[];
14
+ /** 以下四个纯归因用 */
15
+ reason?: string;
16
+ bucket?: number;
17
+ configRevision?: string;
18
+ configSource?: string;
19
+ }
20
+ /** 不满足契约的输入一律收成 undefined,调用方按「不放行」处理。 */
21
+ export declare function normalizeE2EGate(value: unknown): WidgetE2EGate | undefined;
22
+ /** 最终判定:用户维度放行 **且** 当前形态在允许清单内。 */
23
+ export declare function isE2EEnabledForKind(gate: WidgetE2EGate | undefined, appKind: string): boolean;
24
+ /**
25
+ * 不放行时的一行归因摘要,由出码侧原样打进日志。
26
+ * 必须带上 appKinds:控制面那个输入框是自由 tag,形态名敲错不报错、只是永不命中,
27
+ * 对着这一行才看得出是拼写问题。
28
+ */
29
+ export declare function describeE2EGate(gate: WidgetE2EGate | undefined): string;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,15 @@
1
+ /**
2
+ * 宿主页:把 miniapp-simulator 实例化并挂到 `window.__MINITOOL_E2E__`,供 Node 侧 evaluate 驱动。
3
+ * 事件收进环形缓冲而不直接吐给 Node:两侧只有请求/响应式的 evaluate,没有反向推送通道。
4
+ */
5
+ export interface HostPageOptions {
6
+ /** 模拟器 ESM 入口 URL(同源) */
7
+ sdkEntry: string;
8
+ /** Service Worker 脚本 URL(同源,须在根路径,见 host-server.ts) */
9
+ serviceWorkerUrl: string;
10
+ viewport: {
11
+ width: number;
12
+ height: number;
13
+ };
14
+ }
15
+ export declare function renderHostPage(opts: HostPageOptions): string;
@@ -0,0 +1,17 @@
1
+ import { type SimulatorAssets } from "./assets";
2
+ export declare const SW_ROUTE = "/miniapp-sw.js";
3
+ export declare const SDK_ROUTE_PREFIX = "/sdk/";
4
+ export interface HostServerHandle {
5
+ /** 形如 http://127.0.0.1:54321,无尾斜杠 */
6
+ baseUrl: string;
7
+ assets: SimulatorAssets;
8
+ close(): Promise<void>;
9
+ }
10
+ export interface HostServerOptions {
11
+ /** 宿主页里模拟器的视口尺寸(CSS px),默认 390x844 */
12
+ viewport?: {
13
+ width: number;
14
+ height: number;
15
+ };
16
+ }
17
+ export declare function startHostServer(opts?: HostServerOptions): Promise<HostServerHandle>;
@@ -0,0 +1,30 @@
1
+ /**
2
+ * @skillbuilder/minitool-e2e —— 小工具端到端验证能力。
3
+ * 游戏「试玩」只是第一种模式,别把包名理解成只能玩游戏。
4
+ *
5
+ * **工具只经 MCP 暴露,没有进程内形态。** 本包被加载两次、扮演两个角色:
6
+ * 控制面 `codegen-port.ts`(出码主进程里 require,纯计算不碰 chromium);
7
+ * 数据面 `mcp/stdio-entry.ts`(被 spawn 成独立子进程,14 个真工具 + chromium 都在这侧)。
8
+ * 工具名与 prompt 的权威定义都在 `contract.ts`,两个角色都从那里取,所以不会漂移。
9
+ *
10
+ * 接入:`initCodegenAgent({ ...ports.context, e2e: (await createMinitoolE2EPortIfAvailable()).port })`
11
+ *
12
+ * 运行前置:容器内预装发行版 chromium(见 browser.ts)、已完整构建 miniapp-simulator(见 assets.ts)。
13
+ */
14
+ export { launchChromium, probeChromium, resolveChromiumPath } from "./browser";
15
+ export type { LaunchChromiumOptions } from "./browser";
16
+ export { resolveSimulatorAssets } from "./assets";
17
+ export type { SimulatorAssets } from "./assets";
18
+ export { startHostServer, SDK_ROUTE_PREFIX, SW_ROUTE } from "./host-server";
19
+ export type { HostServerHandle, HostServerOptions } from "./host-server";
20
+ export { renderHostPage } from "./host-page";
21
+ export type { HostPageOptions } from "./host-page";
22
+ export { MinitoolE2ESession, readDistDir } from "./session";
23
+ export type { DomQuery, GameAttachResult, InteractInput, MinitoolE2ESessionOptions, NetworkQuery, NotImplementedResult, OpenAppInput, OpenAppResult, ScreenshotResult, } from "./session";
24
+ export { createMinitoolE2EPort, createMinitoolE2EPortIfAvailable, MINITOOL_E2E_MCP_SERVER_NAME, } from "./codegen-port";
25
+ export type { E2EMcpServerConfig, E2ERunPlan, MinitoolE2EPort, MinitoolE2EPortOptions, } from "./codegen-port";
26
+ export { buildE2EInstruction, e2eAllowedToolNames, e2eExtraTurns, toMcpToolName, E2E_GENERIC_TOOL_NAMES, E2E_GAME_TOOL_NAMES, E2E_TOOL_NAMES, } from "./contract";
27
+ export type { E2EGameToolName, E2EGenericToolName, E2EToolName } from "./contract";
28
+ export { createMinitoolE2EMcpServer, registerMinitoolE2ETools } from "./mcp/server";
29
+ export type { MinitoolE2EMcpServerOptions } from "./mcp/server";
30
+ export { runMinitoolE2EStdioServer } from "./mcp/stdio";
@@ -0,0 +1,28 @@
1
+ /**
2
+ * 把一套会话暴露成 14 个 MCP tool(stdio + JSON-RPC)。工具分两组、名字就是边界,权威清单在
3
+ * `../contract.ts`;只剩 network 尚未实现,如实返回 not_implemented。
4
+ *
5
+ * 下面的名字仍写字面量(比 `E2E_TOOL_NAMES[3]` 可读),靠 `contract.test.ts` 断言
6
+ * 「注册集合 === contract 声明集合」双向一致兜底 —— 少注册或拼错都会红。
7
+ *
8
+ * MCP 这一层的意义是能力隔离:独立进程、独立依赖(chromium 只在这侧),出码 Agent 只认工具名,
9
+ * 以后换远端浏览器集群或给别的 Agent 复用都只需换端点。
10
+ *
11
+ * ⚠️ 一个进程只服务**一个**会话。并发靠起多个进程 —— 沙箱 1 核 2GiB,并发浏览器只会互相拖死。
12
+ */
13
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
14
+ import type { LaunchChromiumOptions } from "../browser";
15
+ export interface MinitoolE2EMcpServerOptions extends LaunchChromiumOptions {
16
+ /** 缺省读 `MINITOOL_E2E_DIST_DIR`。这侧不做 workspace→dist 推导:调用方比这里清楚。 */
17
+ distDir?: string;
18
+ viewport?: {
19
+ width: number;
20
+ height: number;
21
+ };
22
+ }
23
+ /**
24
+ * 把 E2E tools 注册到调用方提供的 server,供 redminitool 聚合认证、构建、发布等能力。
25
+ * E2E 包只拥有自己的工具与会话状态,不再强制拥有整个 MCP server。
26
+ */
27
+ export declare function registerMinitoolE2ETools(server: McpServer, opts?: MinitoolE2EMcpServerOptions): void;
28
+ export declare function createMinitoolE2EMcpServer(opts?: MinitoolE2EMcpServerOptions): McpServer;
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,7 @@
1
+ import type { MinitoolE2EMcpServerOptions } from "./server";
2
+ /**
3
+ * 在 stdio 上启动小工具 MCP server。
4
+ *
5
+ * stdout 是 MCP JSON-RPC 专用通道,这里以及调用方都不得向 stdout 输出日志。
6
+ */
7
+ export declare function runMinitoolE2EStdioServer(opts?: MinitoolE2EMcpServerOptions): Promise<void>;
@@ -0,0 +1,182 @@
1
+ import { type LaunchChromiumOptions } from "./browser";
2
+ /** 二进制文件在 evaluate 载荷里的表示:JSON 过不去 Uint8Array,只能 base64 中转。 */
3
+ interface BinaryFileMarker {
4
+ __binary: true;
5
+ base64: string;
6
+ }
7
+ type WireFileContent = string | BinaryFileMarker;
8
+ export interface MinitoolE2ESessionOptions extends LaunchChromiumOptions {
9
+ viewport?: {
10
+ width: number;
11
+ height: number;
12
+ };
13
+ /** 宿主页/模拟器就绪超时,默认 30s(沙箱冷启动 + SW 注册比本地慢) */
14
+ readyTimeoutMs?: number;
15
+ }
16
+ export interface OpenAppInput {
17
+ /** 出码产物目录(含 index.html),通常是 <workspaceDir>/dist */
18
+ distDir: string;
19
+ entry?: string;
20
+ viewport?: {
21
+ width: number;
22
+ height: number;
23
+ deviceScaleFactor?: number;
24
+ };
25
+ /**
26
+ * 追加 query,默认补 `playtest=1`。这个默认留在通用 open 里而不跟着游戏组走,是因为 query
27
+ * 必须在**加载时**就带上(游戏模板只在该 query 下才挂桥,页面加载完就补不上了);
28
+ * 对非游戏产物它只是一个无副作用的参数。桥到底挂上没有由 `gameAttach()` 判定。
29
+ */
30
+ query?: Record<string, string>;
31
+ }
32
+ export interface OpenAppResult {
33
+ entry: string;
34
+ fileCount: number;
35
+ /** 应用坐标空间(逻辑 CSS px)——与 screenshot / dom rect / interact 的 x,y 同一把尺子 */
36
+ viewport: {
37
+ width: number;
38
+ height: number;
39
+ };
40
+ }
41
+ export interface GameAttachResult {
42
+ /** 游戏语义桥是否可用;非游戏产物为 false,此时只能截图 + 读日志 */
43
+ bridgeAvailable: boolean;
44
+ /** 实际等了多久(ms),便于判断是「秒就绪」还是「等满超时」 */
45
+ waitedMs: number;
46
+ }
47
+ export interface ScreenshotResult {
48
+ /** base64 编码的 PNG */
49
+ data: string;
50
+ mimeType: string;
51
+ description: string;
52
+ /** 截图已上传进桶时的 CDN URL(未配置上传或上传失败时缺省,仍返回纯 base64) */
53
+ url?: string;
54
+ /** 上传后的 object key(与 url 同生共死) */
55
+ fileKey?: string;
56
+ }
57
+ export interface NetworkQuery {
58
+ /** all = 全部请求;failed = 只要失败的(非 2xx 或直接失败) */
59
+ scope?: "all" | "failed";
60
+ limit?: number;
61
+ }
62
+ export interface DomQuery {
63
+ /** 限定子树;不传则取整个 guest 文档 */
64
+ selector?: string;
65
+ maxNodes?: number;
66
+ }
67
+ export interface InteractInput {
68
+ /** 操作类型。加新操作三步:这里加枚举 + interact() 里加一个 case + 需要时加一个可选字段。 */
69
+ action: "tap" | "doubleTap" | "longPress" | "scroll" | "swipe" | "input" | "reload";
70
+ /** CSS selector,优先用它定位(取元素中心);与 x/y 二选一。 */
71
+ selector?: string;
72
+ /** action=input 时要输入的文本 */
73
+ text?: string;
74
+ /** 落点坐标(应用逻辑 CSS px):tap/scroll/longPress 的落点,swipe 的起点 */
75
+ x?: number;
76
+ y?: number;
77
+ /** action=swipe 的终点坐标(应用逻辑 CSS px) */
78
+ toX?: number;
79
+ toY?: number;
80
+ /** action=scroll 的横向/纵向滚动量 */
81
+ deltaX?: number;
82
+ deltaY?: number;
83
+ /** longPress 按住时长/ input 击键间隔(ms) / swipe 总时长 */
84
+ duration?: number;
85
+ }
86
+ /**
87
+ * ★ 刻意**不返回空集合**(不给 `requests: []` 这种形状):空集合会被模型读成
88
+ * 「查过了,没问题」并据此判 PASS,那比没有这个工具糟得多。
89
+ */
90
+ export interface NotImplementedResult {
91
+ status: "not_implemented";
92
+ capability: string;
93
+ message: string;
94
+ /** 落地这项能力还缺什么,写给人看的 */
95
+ blockedBy?: string[];
96
+ }
97
+ export declare class MinitoolE2ESession {
98
+ private readonly browser;
99
+ private readonly page;
100
+ private readonly host;
101
+ private viewport;
102
+ private constructor();
103
+ static create(opts?: MinitoolE2ESessionOptions): Promise<MinitoolE2ESession>;
104
+ /**
105
+ * 把产物目录读进内存交给模拟器挂载。刻意**不等游戏桥**(那段轮询在 `gameAttach()`):
106
+ * 等桥只能轮询到超时,非游戏产物必然白等满 10s,而多数验证只需「加载→截图→读日志」。
107
+ */
108
+ open(input: OpenAppInput): Promise<OpenAppResult>;
109
+ /**
110
+ * 等游戏语义桥就绪并判定它到底有没有。
111
+ *
112
+ * 必须轮询而非立刻判定:`loadFiles` resolve 只代表虚拟入口已挂载、iframe 已开始导航,
113
+ * **不代表 guest 的 JS 跑完了**(游戏模板要等 Pixi 初始化 + WebGL 首帧才挂上桥)。
114
+ * 反过来,**非游戏产物必然等满 timeoutMs 才返回 false** —— 无法预先判断产物形态,
115
+ * 这正是把它从 `open()` 拆出来的理由。
116
+ */
117
+ gameAttach(timeoutMs?: number): Promise<GameAttachResult>;
118
+ /** 读游戏 manifest(目标、可用动作、里程碑、胜负判据)。非游戏产物会 reject。 */
119
+ manifest(): Promise<unknown>;
120
+ /** 读当前语义状态快照。 */
121
+ snapshot(): Promise<unknown>;
122
+ /**
123
+ * 执行一个 manifest 声明的动作。走**语义桥**(模板注册的动作回调),不是真实指针事件。
124
+ *
125
+ * ⚠️ 真实 pointer 路径必须先渲染一帧:试玩模式下模板会 `app.stop()`,而 Pixi v8 的命中测试
126
+ * 依赖只在 `render()` 内部赋值的 `renderer.lastObjectRendered`。从未渲染过时
127
+ * `EventBoundary.mapEvent` 会**静默 return** —— 点击看似发生了却什么都不触发。
128
+ * 所以派发坐标事件前必须先 `step(16)`。该路径未实现,见 performPointerAction()。
129
+ */
130
+ act(action: Record<string, unknown>): Promise<unknown>;
131
+ /** 推进确定性时钟。同时也是「先渲染一帧」的手段(见 act 注释)。 */
132
+ step(ms: number): Promise<unknown>;
133
+ /** 重开,可带确定性随机种子。 */
134
+ reset(seed?: number): Promise<unknown>;
135
+ /** 取页面侧攒下的事件(console / guestError / CSP / 失败请求),取完即清。 */
136
+ logs(level?: "debug" | "info" | "warn" | "error"): Promise<unknown>;
137
+ /** 截当前帧。截的是模拟器容器,不是整个浏览器窗口。 */
138
+ screenshot(): Promise<ScreenshotResult>;
139
+ /**
140
+ * 读小工具发出的网络请求与响应。数据源已探针实测过,但**不是**模拟器的 `network` 事件 ——
141
+ * 那个事件声明了、转发了,却**没有任何地方 emit**,且 payload 里没有 body。
142
+ * 真正能拿到完整请求/响应的是 **bridge 协议日志**:guest 走 `xhs.miniTool.request` 时
143
+ * bridge 会把 invoke/callback 打进 console,用 `msg.args()` + `jsonValue()` 反序列化
144
+ * (console 文本里只是 "Object",拿不到)。
145
+ */
146
+ network(_query?: NetworkQuery): Promise<NotImplementedResult>;
147
+ /**
148
+ * 读当前渲染出的 DOM 摘要:白屏/是否渲染出内容不靠截图也能判。元素描述与模拟器
149
+ * `inspector/selector.ts` 的 ElementSelection 同 schema(label/selector/tag/class/text/rect)。
150
+ */
151
+ dom(query?: DomQuery): Promise<unknown>;
152
+ /**
153
+ * 通用交互适配:手势和 reload 只调用模拟器公开能力,文本输入才直接操作 guest DOM。
154
+ * 坐标空间 = 应用逻辑 CSS px(由 simulator 的 GestureHostBus 统一换算)。
155
+ */
156
+ interact(input: InteractInput): Promise<unknown>;
157
+ /**
158
+ * 运行时健康汇总:把 logs() 里攒下的事件按口径分类,返回显式计数 + 少量采样,不发空集合。
159
+ */
160
+ health(): Promise<unknown>;
161
+ /**
162
+ * 真实指针动作,未实现(前提见下面 throw 的文案)。探针已验明的坐标换算式:
163
+ * `canvasBox.origin + appCoord * (canvasBox.size / app.screen.size)`,其中
164
+ * `selectByRef` 返回的 `viewportRect` 已是宿主屏幕坐标 —— 但它有副作用(会打开选取层并盖
165
+ * 一层 z-index 极高的拦截元素),所以要等模拟器补一个无副作用的 `describeByRef`。
166
+ */
167
+ performPointerAction(_action: Record<string, unknown>): Promise<never>;
168
+ /** 定位 guest 应用 iframe(主 frame 之外的唯一子 frame)。 */
169
+ private guestFrame;
170
+ /**
171
+ * 读场景树(canvas / Pixi / Three 的 Scene Provider)。无副作用、只读;
172
+ * 产物没注册 provider 时返回空快照(DOM 形态就是如此)。
173
+ */
174
+ private readSceneTree;
175
+ close(): Promise<{
176
+ closed: true;
177
+ }>;
178
+ private invokeBridge;
179
+ }
180
+ /** 递归读产物目录,文本直读、二进制转 base64 标记。 */
181
+ export declare function readDistDir(dir: string, prefix?: string): Record<string, WireFileContent>;
182
+ export {};
@@ -0,0 +1,31 @@
1
+ /**
2
+ * 截图上传回宿主的能力门面:把 base64 PNG 原样 POST 回宿主的鉴权上传接口,
3
+ * 宿主用 OSS 凭证写桶并返回 `{ url, fileKey }`。OSS AK/SK 刻意不进沙箱容器
4
+ * (见 server-online `widget-env.ts` 的 `llmPassthroughEnv` 白名单说明),
5
+ * 这里只消费一个低价值、可轮换的受限 token,挡沙箱外的人打公开写接口。
6
+ */
7
+ export interface E2EUploadConfig {
8
+ /** 上传接口基准地址(含 /node_api 前缀,不带尾斜杠),即宿主下发的 WIDGET_TEMPLATES_API_BASE */
9
+ base: string;
10
+ /** 当前任务 id:宿主据此把截图事件归到对应任务的 SSE 流 */
11
+ taskId: string;
12
+ /** 受限上传 token(可选;宿主未配置时也可以不传) */
13
+ token?: string;
14
+ /** 上传请求硬超时;缺省 {@link DEFAULT_UPLOAD_TIMEOUT_MS} */
15
+ timeoutMs?: number;
16
+ }
17
+ /**
18
+ * 上传请求的硬超时。
19
+ *
20
+ * 没有它,宿主上传代理无响应 / 连接半开 / 服务端持续不返回时 `fetch` 会一直 pending,
21
+ * `session.screenshot()` 就永远不返回 —— 调用方那句「上传失败降级为纯 base64」也就永远不生效,
22
+ * e2e 判读会被一个卡死的上传拖死。
23
+ *
24
+ * 取值远高于实测耗时(SIT 真上传 ~0.5s),只兜「卡死」不兜「慢」。
25
+ */
26
+ export declare const DEFAULT_UPLOAD_TIMEOUT_MS = 20000;
27
+ export interface E2EUploadResult {
28
+ url: string;
29
+ fileKey: string;
30
+ }
31
+ export declare function uploadScreenshot(base64: string, config: E2EUploadConfig): Promise<E2EUploadResult>;
@@ -0,0 +1 @@
1
+ export {};