@qing3a/flow-rpa-app 0.1.0

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.
@@ -0,0 +1,107 @@
1
+ import { type PageOps } from '@qing3a/flow-rpa-engine/src/executor.js';
2
+ import type { EnvCheck } from '@qing3a/flow-rpa-engine/src/schema.js';
3
+ import { type DomSnapshot } from '@qing3a/flow-rpa-engine/src/export.js';
4
+ import { SuggestionStore } from '@qing3a/flow-rpa-engine/src/learning.js';
5
+ import { RunStore } from '@qing3a/flow-rpa-engine/src/storage.js';
6
+ import { LocalPlatform } from '@qing3a/flow-rpa-engine/src/export.js';
7
+ import { PerfGate } from '@qing3a/flow-rpa-engine/src/bench.js';
8
+ import { CooldownGate, SiteGate } from '@qing3a/flow-rpa-engine/src/gate.js';
9
+ import { FlowRegistry } from './registry.js';
10
+ import type { RunConfig } from './config.js';
11
+ /**
12
+ * run_flow 异步执行层(移植 Rust `server.rs` 的 execute_run/spawn_run + M9 D-R1/D-R2)。
13
+ *
14
+ * D-R1 串行 FIFO 队列:所有 run(无论 wait 参数)入队,同一时刻只执行一个;
15
+ * wait:false 排队(返回 running);wait:true 队列忙时拒绝(返回 failed「队列忙」)。
16
+ * D-R2 单实例锁:data/.lock 文件(pid + 时间戳);PID 存活 → 拒绝启动;
17
+ * 锁超 10 分钟(PID 已死残留)→ 清理后可启动。
18
+ */
19
+ /** 运行结果(内存态;running 期间 get_run 从这读) */
20
+ export interface RunFlowResult {
21
+ runId: string;
22
+ /** running / done / failed */
23
+ status: 'running' | 'done' | 'failed';
24
+ error?: string;
25
+ perfNote?: string;
26
+ }
27
+ export interface RunFlowArgs {
28
+ flowId: string;
29
+ vars: Record<string, string>;
30
+ wait: boolean;
31
+ }
32
+ /** 执行上下文(runner 持有全部共享状态) */
33
+ export interface ExecCtx {
34
+ registry: FlowRegistry;
35
+ store: RunStore;
36
+ runs: Map<string, RunFlowResult>;
37
+ suggestions: SuggestionStore;
38
+ platform: LocalPlatform;
39
+ siteGate: SiteGate;
40
+ coolGate: CooldownGate;
41
+ perf: PerfGate;
42
+ config: RunConfig;
43
+ }
44
+ /** 生成 run_id(r_<毫秒时间戳>) */
45
+ export declare function newRunId(): string;
46
+ /**
47
+ * D-S1:DOM 快照采集并落盘(snapshots/<runId>.json)。
48
+ * 快照失败(finalize 已关 tab、CDP 断开等)→ 静默跳过,不阻断 run 落盘。
49
+ * 页面不支持 snapshotDom → 跳过(老驱动兼容)。
50
+ */
51
+ export declare function captureSnapshot(ctx: ExecCtx, page: PageOps, runId: string): Promise<void>;
52
+ /**
53
+ * mock 页面(run_flow 的 mock 驱动):调试/无浏览器环境用。
54
+ * 对齐 Rust `server.rs` MockPage 语义(页面文本 = 最近输入值),并补列表语义
55
+ * (extract 返回候选人文本、click 列表选择器越界 = 列表耗尽)——让 --mock 能
56
+ * 跑通真实流程(boss_search 的 extract_list verify「候选人」/ loop 越界终止)。
57
+ */
58
+ export declare class MockPage implements PageOps {
59
+ /** 最近一次被注入的 ENV_CHECK 配置(静态:供测试断言 runner 注入链路) */
60
+ static lastEnvCheck?: EnvCheck;
61
+ /** 测试用:新实例的默认 slowWaitMs(executeRun 的 new MockPage() 继承此值;默认 0 = no-op) */
62
+ static defaultSlowWaitMs: number;
63
+ url: string;
64
+ /** runner 注入的 ENV_CHECK 配置(D-V1;测试断言注入链路用) */
65
+ envCheck?: EnvCheck;
66
+ private hit;
67
+ /** 模拟列表页候选人个数(点击 .resume-card-warp-new 越界 → 列表耗尽,loop 自然终止) */
68
+ candidates: number;
69
+ private extracted;
70
+ /** 测试用:waitMs 真实阻塞时长(默认 0 = no-op;设 >0 可让 run 慢下来验证队列忙) */
71
+ slowWaitMs: number;
72
+ setEnvCheck(config: EnvCheck | undefined): void;
73
+ navigate(url: string): Promise<void>;
74
+ click(sel: string, index: number): Promise<void>;
75
+ input(_sel: string, v: string): Promise<void>;
76
+ extract(sel: string): Promise<{
77
+ content: string;
78
+ count: number;
79
+ }>;
80
+ nextPage(): Promise<boolean>;
81
+ currentUrl(): Promise<string>;
82
+ pageText(): Promise<string>;
83
+ waitSelector(s: string): Promise<void>;
84
+ waitMs(ms: number): Promise<void>;
85
+ finalize(): Promise<void>;
86
+ /** mock 的 snapshotDom(D-S1 验收:mock 模式不采集 → 返回 undefined 语义由 executeRun 跳过处理) */
87
+ snapshotDom(): Promise<DomSnapshot>;
88
+ }
89
+ /** 同步执行一个 run(构造驱动 → 解释执行 → 落盘 → 更新运行状态) */
90
+ export declare function executeRun(ctx: ExecCtx, args: RunFlowArgs, runId: string): Promise<RunFlowResult>;
91
+ /** 流程的目标站点 host(取首个 navigate 步骤的 url;无 navigate 返回 undefined —— 不节流 mock) */
92
+ export declare function flowHost(ctx: ExecCtx, flowId: string): string | undefined;
93
+ /** 队列当前状态(自描述聚合用 D-S1;只读) */
94
+ export declare function queueState(): {
95
+ running: boolean;
96
+ pending: number;
97
+ };
98
+ /**
99
+ * 启动异步 run:入串行 FIFO 队列。
100
+ * wait=false → 立即返回 running(排队/后台执行);wait=true → 队列空闲则同步执行完返回,
101
+ * 队列忙则拒绝(failed「队列忙」)。
102
+ */
103
+ export declare function spawnRun(ctx: ExecCtx, args: RunFlowArgs, runId: string): Promise<RunFlowResult>;
104
+ /** 获取单实例锁:成功 true;已锁且 PID 存活 → false(拒绝启动);锁过期(>10 分钟)→ 覆盖 */
105
+ export declare function acquireInstanceLock(dataDir: string, pid?: number): boolean;
106
+ /** 释放单实例锁(进程退出时调用;仅当锁属于当前 pid) */
107
+ export declare function releaseInstanceLock(dataDir: string, pid?: number): void;
package/dist/runner.js ADDED
@@ -0,0 +1,284 @@
1
+ import { existsSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { ExecError, runFlow } from '@qing3a/flow-rpa-engine/src/executor.js';
4
+ import { snapshotFromText } from '@qing3a/flow-rpa-engine/src/export.js';
5
+ import { fromFlowResult } from '@qing3a/flow-rpa-engine/src/record.js';
6
+ import { learnFromRun, SuggestionStore } from '@qing3a/flow-rpa-engine/src/learning.js';
7
+ import { nowIso, RunStore } from '@qing3a/flow-rpa-engine/src/storage.js';
8
+ import { LocalPlatform } from '@qing3a/flow-rpa-engine/src/export.js';
9
+ import { PerfGate } from '@qing3a/flow-rpa-engine/src/bench.js';
10
+ import { CooldownGate, hostOf, SiteGate } from '@qing3a/flow-rpa-engine/src/gate.js';
11
+ import { FlowRegistry } from './registry.js';
12
+ /** 生成 run_id(r_<毫秒时间戳>) */
13
+ export function newRunId() {
14
+ return `r_${Date.now()}`;
15
+ }
16
+ /**
17
+ * D-S1:DOM 快照采集并落盘(snapshots/<runId>.json)。
18
+ * 快照失败(finalize 已关 tab、CDP 断开等)→ 静默跳过,不阻断 run 落盘。
19
+ * 页面不支持 snapshotDom → 跳过(老驱动兼容)。
20
+ */
21
+ export async function captureSnapshot(ctx, page, runId) {
22
+ const snapshotDom = page.snapshotDom;
23
+ if (typeof snapshotDom !== 'function')
24
+ return;
25
+ try {
26
+ ctx.store.saveSnapshot(runId, await snapshotDom.call(page));
27
+ }
28
+ catch {
29
+ // 快照失败不阻断 run
30
+ }
31
+ }
32
+ /**
33
+ * mock 页面(run_flow 的 mock 驱动):调试/无浏览器环境用。
34
+ * 对齐 Rust `server.rs` MockPage 语义(页面文本 = 最近输入值),并补列表语义
35
+ * (extract 返回候选人文本、click 列表选择器越界 = 列表耗尽)——让 --mock 能
36
+ * 跑通真实流程(boss_search 的 extract_list verify「候选人」/ loop 越界终止)。
37
+ */
38
+ export class MockPage {
39
+ /** 最近一次被注入的 ENV_CHECK 配置(静态:供测试断言 runner 注入链路) */
40
+ static lastEnvCheck;
41
+ /** 测试用:新实例的默认 slowWaitMs(executeRun 的 new MockPage() 继承此值;默认 0 = no-op) */
42
+ static defaultSlowWaitMs = 0;
43
+ url = '';
44
+ /** runner 注入的 ENV_CHECK 配置(D-V1;测试断言注入链路用) */
45
+ envCheck;
46
+ hit = [];
47
+ /** 模拟列表页候选人个数(点击 .resume-card-warp-new 越界 → 列表耗尽,loop 自然终止) */
48
+ candidates = 3;
49
+ extracted = [];
50
+ /** 测试用:waitMs 真实阻塞时长(默认 0 = no-op;设 >0 可让 run 慢下来验证队列忙) */
51
+ slowWaitMs = MockPage.defaultSlowWaitMs;
52
+ setEnvCheck(config) {
53
+ this.envCheck = config;
54
+ MockPage.lastEnvCheck = config;
55
+ }
56
+ async navigate(url) {
57
+ this.url = url;
58
+ }
59
+ async click(sel, index) {
60
+ // 列表点击越界 = 列表耗尽(D-L2 终止信号)
61
+ if (sel === '.resume-card-warp-new' && index >= this.candidates) {
62
+ throw ExecError.page(`索引越界: 列表长度 ${this.candidates}`, { category: 'index_out_of_range' });
63
+ }
64
+ }
65
+ async input(_sel, v) {
66
+ // 模拟真实输入:变量缺失时输入框为空(verify textContains 会失败)
67
+ if (!v.includes('{'))
68
+ this.hit.push(`input:${v}`);
69
+ }
70
+ async extract(sel) {
71
+ // 列表选择器返回候选人文本(含 verify 需要的「候选人」字样);其余返回占位
72
+ const content = sel.includes('resume-card') ? '候选人 张三 李四 王五' : `<extracted:${sel}>`;
73
+ this.extracted.push(content);
74
+ return { content, count: sel.includes('resume-card') ? this.candidates : 1 };
75
+ }
76
+ async nextPage() {
77
+ return false;
78
+ }
79
+ async currentUrl() {
80
+ return this.url;
81
+ }
82
+ async pageText() {
83
+ // 页面文本 = 最近输入值 + 已提取内容(模拟页面渲染了输入框与列表)
84
+ const last = this.hit.filter((h) => h.startsWith('input:')).pop();
85
+ const base = last ? last.slice('input:'.length) : '';
86
+ return [base, ...this.extracted].join(' ').trim();
87
+ }
88
+ async waitSelector(s) {
89
+ // mock:导航后 header 出现;其余一律命中(简化,真实选择器由 CdpPage 校准)
90
+ if (s === '.header-nav') {
91
+ if (this.url.includes('zhipin.com') || this.url.includes('liepin.com'))
92
+ return;
93
+ throw new Error('未导航到平台首页');
94
+ }
95
+ }
96
+ async waitMs(ms) {
97
+ // 默认 no-op(mock 跑得快);slowWaitMs > 0 时按传入 ms 真实等待(测试队列语义用)
98
+ if (this.slowWaitMs > 0)
99
+ await new Promise((r) => setTimeout(r, ms));
100
+ }
101
+ async finalize() { }
102
+ /** mock 的 snapshotDom(D-S1 验收:mock 模式不采集 → 返回 undefined 语义由 executeRun 跳过处理) */
103
+ async snapshotDom() {
104
+ return snapshotFromText(this.url);
105
+ }
106
+ }
107
+ /** 同步执行一个 run(构造驱动 → 解释执行 → 落盘 → 更新运行状态) */
108
+ export async function executeRun(ctx, args, runId) {
109
+ const cached = ctx.registry.get(args.flowId);
110
+ if (!cached) {
111
+ const r = { runId, status: 'failed', error: `流程不存在: ${args.flowId}` };
112
+ ctx.runs.set(runId, r);
113
+ return r;
114
+ }
115
+ const { flow, version } = cached;
116
+ // 驱动选择:mock(调试/无浏览器)或真实浏览器(M8 CdpPage)
117
+ let page;
118
+ if (ctx.config.mock) {
119
+ page = new MockPage();
120
+ }
121
+ else {
122
+ const { CdpPage } = await import('@qing3a/flow-rpa-engine/src/cdp/page.js');
123
+ const cdpPage = new CdpPage(ctx.config.cdpPort, ctx.config.simProfile);
124
+ await cdpPage.ensureBrowser(ctx.config.edgePath, ctx.config.userDataDir);
125
+ page = cdpPage;
126
+ }
127
+ // A1 ENV_CHECK 注入(D-V1):flow.json 顶层 envCheck → 页面检测配置;缺省不检测(D-V4)
128
+ if (flow.envCheck) {
129
+ const setEnvCheck = page.setEnvCheck;
130
+ setEnvCheck.call(page, flow.envCheck);
131
+ }
132
+ // 变量插值:vars 原样传执行器({keyword} 等由执行器插值;等价 Rust run_flow(&flow, &mut page, &args.vars))
133
+ const result = await runFlow(flow, page, args.vars);
134
+ // D-S1:DOM 快照采集(真实浏览器分支;finalize 可能已关 tab → 捕获异常,失败不阻断 run 落盘)
135
+ if (!ctx.config.mock) {
136
+ await captureSnapshot(ctx, page, runId);
137
+ }
138
+ // 敏感词集合(掩码前移:落盘时刻对 error 掩码关键词)
139
+ const keywords = [];
140
+ for (const step of flow.steps) {
141
+ for (const v of Object.values(step.params)) {
142
+ if (typeof v === 'string' && v.length > 0 && v !== '{i}' && v !== '{keyword}') {
143
+ keywords.push(v);
144
+ }
145
+ }
146
+ }
147
+ keywords.push(...Object.values(args.vars));
148
+ const rec = fromFlowResult(runId, flow.id, version, result, nowIso(), keywords);
149
+ ctx.store.saveRun(rec); // 落盘失败不阻断
150
+ // L2 学习:执行记录 → 结构化信号 → 改进建议
151
+ learnFromRun(rec, ctx.suggestions);
152
+ // 端到端性能基准(质量门)
153
+ const totalDuration = result.steps.reduce((a, s) => a + s.durationMs, 0);
154
+ const perfCheck = ctx.perf.record(flow.id, totalDuration, result.steps.length);
155
+ const r = {
156
+ runId,
157
+ status: result.status,
158
+ error: result.error,
159
+ perfNote: perfCheck.message,
160
+ };
161
+ ctx.runs.set(runId, r);
162
+ // D-C2(M5 半成品 #3 接通):run 结束后对带 cooldownKey 的步骤记账(key=flow#step#coolKey)
163
+ for (const step of result.steps) {
164
+ if (step.cooldownKey) {
165
+ ctx.coolGate.record(`${flow.id}#${step.stepId}#${step.cooldownKey}`);
166
+ }
167
+ }
168
+ return r;
169
+ }
170
+ /** 流程的目标站点 host(取首个 navigate 步骤的 url;无 navigate 返回 undefined —— 不节流 mock) */
171
+ export function flowHost(ctx, flowId) {
172
+ const cached = ctx.registry.get(flowId);
173
+ if (!cached)
174
+ return undefined;
175
+ const nav = cached.flow.steps.find((s) => s.type === 'navigate');
176
+ const url = typeof nav?.params.url === 'string' ? nav.params.url : undefined;
177
+ if (!url)
178
+ return undefined;
179
+ const h = hostOf(url);
180
+ return h.length > 0 ? h : undefined;
181
+ }
182
+ const queue = { running: false, pending: [] };
183
+ /** 队列当前状态(自描述聚合用 D-S1;只读) */
184
+ export function queueState() {
185
+ return { running: queue.running, pending: queue.pending.length };
186
+ }
187
+ /**
188
+ * 启动异步 run:入串行 FIFO 队列。
189
+ * wait=false → 立即返回 running(排队/后台执行);wait=true → 队列空闲则同步执行完返回,
190
+ * 队列忙则拒绝(failed「队列忙」)。
191
+ */
192
+ export async function spawnRun(ctx, args, runId) {
193
+ const runTask = async () => {
194
+ await executeRun(ctx, args, runId);
195
+ };
196
+ if (args.wait) {
197
+ if (queue.running) {
198
+ // wait:true 且队列忙 → 拒绝(避免同步等待撞 MCP 超时)
199
+ const r = {
200
+ runId,
201
+ status: 'failed',
202
+ error: '队列忙:已有流程在运行,请稍后重试(wait:false 可排队)',
203
+ };
204
+ ctx.runs.set(runId, r);
205
+ return r;
206
+ }
207
+ // 队列空闲 + wait:true → 同步执行完再返回(等价 Rust execute_run)
208
+ queue.running = true;
209
+ try {
210
+ await runTask();
211
+ return ctx.runs.get(runId);
212
+ }
213
+ finally {
214
+ queue.running = false;
215
+ }
216
+ }
217
+ // wait:false → 立即返回 running(排队/后台执行)
218
+ if (!queue.running) {
219
+ queue.running = true;
220
+ void runTask().then(drainQueue);
221
+ }
222
+ else {
223
+ queue.pending.push(runTask);
224
+ }
225
+ ctx.runs.set(runId, { runId, status: 'running', error: undefined, perfNote: undefined });
226
+ return { runId, status: 'running', error: undefined, perfNote: undefined };
227
+ }
228
+ async function drainQueue() {
229
+ // 循环取出 pending 依次执行;队列空时置 running=false
230
+ while (true) {
231
+ const next = queue.pending.shift();
232
+ if (!next) {
233
+ queue.running = false;
234
+ return;
235
+ }
236
+ await next();
237
+ }
238
+ }
239
+ // ---- 单实例锁(D-R2) ----
240
+ /** 获取单实例锁:成功 true;已锁且 PID 存活 → false(拒绝启动);锁过期(>10 分钟)→ 覆盖 */
241
+ export function acquireInstanceLock(dataDir, pid = process.pid) {
242
+ const lockPath = join(dataDir, '.lock');
243
+ if (existsSync(lockPath)) {
244
+ try {
245
+ const v = JSON.parse(readFileSync(lockPath, 'utf8'));
246
+ const lockPid = typeof v.pid === 'number' ? v.pid : -1;
247
+ const at = typeof v.at === 'number' ? v.at : 0;
248
+ const age = Date.now() - at;
249
+ const pidAlive = lockPid > 0 && pidAliveSync(lockPid);
250
+ if (pidAlive && age < 10 * 60_000) {
251
+ return false; // 另一实例存活
252
+ }
253
+ // PID 已死或锁过期 → 覆盖
254
+ }
255
+ catch {
256
+ // 锁文件损坏 → 覆盖
257
+ }
258
+ }
259
+ writeFileSync(lockPath, JSON.stringify({ pid, at: Date.now() }), 'utf8');
260
+ return true;
261
+ }
262
+ /** 释放单实例锁(进程退出时调用;仅当锁属于当前 pid) */
263
+ export function releaseInstanceLock(dataDir, pid = process.pid) {
264
+ const lockPath = join(dataDir, '.lock');
265
+ try {
266
+ const v = JSON.parse(readFileSync(lockPath, 'utf8'));
267
+ if (v.pid === pid)
268
+ unlinkSync(lockPath);
269
+ }
270
+ catch {
271
+ // 无锁或损坏:忽略
272
+ }
273
+ }
274
+ /** PID 存活检测(Windows:process.kill(pid, 0);POSIX 同) */
275
+ function pidAliveSync(pid) {
276
+ try {
277
+ process.kill(pid, 0);
278
+ return true;
279
+ }
280
+ catch {
281
+ return false;
282
+ }
283
+ }
284
+ //# sourceMappingURL=runner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runner.js","sourceRoot":"","sources":["../src/runner.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,59 @@
1
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
+ import { RunStore } from '@qing3a/flow-rpa-engine/src/storage.js';
3
+ import { SuggestionStore } from '@qing3a/flow-rpa-engine/src/learning.js';
4
+ import { LocalPlatform } from '@qing3a/flow-rpa-engine/src/export.js';
5
+ import { SiteGate, CooldownGate } from '@qing3a/flow-rpa-engine/src/gate.js';
6
+ import { PerfGate } from '@qing3a/flow-rpa-engine/src/bench.js';
7
+ import { FlowRegistry } from './registry.js';
8
+ import type { RunConfig } from './config.js';
9
+ /**
10
+ * flow-rpa MCP server —— 12 个工具(契约见 FLOW-SCHEMA-v1 / MIGRATION-NODE 红线 3)。
11
+ *
12
+ * 工具面(签名不变,2026-08-25 新增第 12 个 get_status,评审记录见 EXECUTION-SPEC-STATUS):
13
+ * list_flows / run_flow / get_run / validate_flow / list_suggestions /
14
+ * apply_suggestion / dismiss_suggestion / export_observation / sync_pending_uploads /
15
+ * get_perf / pkg_update / get_status。
16
+ *
17
+ * D-R5 EOF 优雅退出:stdio 连接结束(客户端关 stdin / EOF)→ 进程退出(exit 0)。
18
+ * D-R6 inputs 声明:每个工具声明 inputSchema(缺必填参数 → 工具 handler 校验报错)。
19
+ * 三条铁律:流程数据唯一来源是 flow.json(引擎零场景硬编码);引擎零业务依赖;
20
+ * 工具面流程级(不暴露页面原子操作)。
21
+ */
22
+ /** 执行上下文(MCP server 持有) */
23
+ export interface FlowMcpCtx {
24
+ registry: FlowRegistry;
25
+ store: RunStore;
26
+ runs: Map<string, unknown>;
27
+ suggestions: SuggestionStore;
28
+ platform: LocalPlatform;
29
+ siteGate: SiteGate;
30
+ coolGate: CooldownGate;
31
+ perf: PerfGate;
32
+ config: RunConfig;
33
+ /** 运行模式(自描述 lock.mode;stdio / http) */
34
+ mode: 'stdio' | 'http';
35
+ }
36
+ /** 构造执行上下文(供 serveStdio 与测试复用) */
37
+ export declare function buildCtx(flowsDir: string, dataDir: string, config: RunConfig, mode?: 'stdio' | 'http'): FlowMcpCtx;
38
+ /** 工具定义表(name/description/inputSchema → handler) */
39
+ interface ToolDef {
40
+ name: string;
41
+ description: string;
42
+ inputSchema: {
43
+ type: 'object';
44
+ properties?: Record<string, unknown>;
45
+ required?: string[];
46
+ };
47
+ handler: (args: Record<string, unknown>) => Promise<{
48
+ content: Array<{
49
+ type: 'text';
50
+ text: string;
51
+ }>;
52
+ }>;
53
+ }
54
+ export declare function buildTools(ctx: FlowMcpCtx): ToolDef[];
55
+ /** 注册全部工具(tools/list + tools/call 两个 handler,SDK 1.30 setRequestHandler) */
56
+ export declare function registerTools(server: Server, ctx: FlowMcpCtx): void;
57
+ /** 启动 MCP server(stdio 传输)。阻塞直到连接结束(EOF)→ 优雅退出。 */
58
+ export declare function serveStdio(flowsDir: string, dataDir: string, config: RunConfig): Promise<void>;
59
+ export {};